前馈神经网络

  • Post author:
  • Post category:其他




多分类任务


第一步:导包


导入所使用的的包

import numpy as np
import torch
from torch.utils import data
from matplotlib import pyplot as plt
import torchvision
import torchvision.transforms as transforms


第二步:构建数据集


加载或者下载所训练和测试数据集

参数:位置,是否是训练集,下载与否,

mnist_train = torchvision.datasets.MNIST(root='~/Datasets/MNIST', train=True, download=True,
                                         transform=transforms.ToTensor())
mnist_test = torchvision.datasets.MNIST(root='~/Datasets/MNIST', train=False, transform=transforms.ToTensor())


第三步:定义数据迭代器


定义批次量batch_size为128,然后定义迭代器iter,shuffle为是否打散数据。num_workers=0是windows系统。

# 通过DataLoader 读取小批量数据样本
batch_size = 128
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=0)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=0)


第四步:定义模型及其前向传播过程


首先定义模型参数,输入大小num_inputs,输出大小num_outputs,中间大小num_hiddens。

第一层的权重和偏置,第二层的权重和偏置。

self.params


是包含可训练参数的列表,接下来requires_grad属性设置为True是计算网络的前向传播和反向传播过程中,上述参数会被保留并计算梯度。一边根据损失函数更新参数的值。通过requires_grad会自动求导和参数的更新。很大程度上降低了难度。

接下来定义模型的结构。三层结构,输入层,隐藏层和输出层。

  • 输入层:self.inputs_layer 将输入张量x进行形状的变换,将其视为一个二位矩阵,并且保持批次大小不变,view就是将其视为二位张量,其中第一维是批次大小,第二维是自动根据原始形状计算的到的。
  • 隐藏层:self.hiddens_layer对输入进行线性变换、加权求和和非线性激活。具体而言就是先对输入张量x和权重矩阵W1进行矩阵乘法运算,然后加上偏置向量b1,最后通过self.my_ReLU()方法进行ReLU激活韩素华的应用。
  • 输出层:self.ouputs_layer 是对隐藏层的输出进行线性变换并返回最后的输出结果。首先对隐藏层的输出张量x和权重矩阵W2进行矩阵乘法,然后加上偏置b2,最后的到输出结果。

    通过调用这些函数来完成数据的处理和特征的提取,最终获得网络的输出。
	@staticmethod
    def my_ReLU(x):
        return torch.max(input=x, other=torch.tensor(0.0))



这段代码定义自己的ReLU激活函数。对矩阵中的每个张量进行与0比较大小,取大值。最后将新的张量作为返回值。

然后就是神经网络的前向传播过程,通过数据x通过各层(输入,隐藏和输出)的运算(就是上面定义的)饭后最终的输出结果。

首先调用输入层,也就是进行扁平化。然后将得到的输出作为输入投入隐藏层。最后将结果作为输出层的输入传入输出层。得到结果。

class Net():
    def __init__(self):
        # 定义并初始化模型参数
        num_inputs, num_outputs, num_hiddens = 784, 10, 256
        W1 = torch.tensor(np.random.normal(0, 0.01, (num_hiddens, num_inputs)), dtype=torch.float32)
        b1 = torch.zeros(1, dtype=torch.float32)
        W2 = torch.tensor(np.random.normal(0, 0.01, (num_outputs, num_hiddens)), dtype=torch.float32)
        b2 = torch.zeros(1, dtype=torch.float32)
        # 上述四个变量求梯度
        self.params = [W1, b1, W2, b2]
        for param in self.params:
            param.requires_grad_(requires_grad=True)
        # 定义模型的结构
        self.inputs_layer = lambda x: x.view(x.shape[0], -1)
        self.hiddens_layer = lambda x: self.my_ReLU(torch.matmul(x, W1.t()) + b1)
        self.outputs_layer = lambda x: torch.matmul(x, W2.t()) + b2

    @staticmethod
    def my_ReLU(x):
        return torch.max(input=x, other=torch.tensor(0.0))

    def forward(self, x):
        flatten_input = self.inputs_layer(x)
        hidden_output = self.hiddens_layer(flatten_input)
        final_output = self.outputs_layer(hidden_output)
        return final_output


第五步:定义损失函数及优化算法



loss_func = torch.nn.CrossEntropyLoss()

用于计算交叉熵损失的CrossENtropLoss对象。交叉熵损失经常用于多分类任务,在神经网络中作为损失函数来衡量预测结果与真实标签之间的差距。

使用随机梯度下降(SGD)优化算法,第一个参数params包含待更新参数的列表或者迭代器。lr是学习率表示更新参数时的步长大小。

在函数内部,使用一个循环遍历params中的每个参数param,param.grad表示param的梯度值。

对于每个参数都通过

param.data -= lr * param.grad

这样的操作进行更新,即减去学习率乘以梯度值的方式,更新参数数值。

SGD这种优化方法更具梯度值和学习率更新参数数值,以最小化损失函数。


为什么通过减去学习率乘以梯度的方式更新参数的数值?



这样做是为了沿着梯度的反方向进行参数更新,从而逐渐接近或者达到损失函数的最小值。 梯度代表了损失函数关于参数的变化率,梯度的方向指示了损失函数下降最快的方向,即参数更新的方向,学习率决定了每次参数更新的步长大小。

loss_func = torch.nn.CrossEntropyLoss()
def SGD(params, lr):
    for param in params:
        param.data -= lr * param.grad


第六步:定义测试函数

def tesst(data_iter, net, loss_func):
    test_loss_sum, c = 0.0, 0 # 累计测试集上的损失值和样本数量
    for X, y in data_iter:# X是输入特征,y是对应的真实标签。
        result = net.forward(X)# 调用前向 传播方法,
        test_loss_sum += loss_func(result, y).item() # 调用损失函数。
        c += 1 # 样本+1
    return test_loss_sum / c	# 返回平均损失。

三个参数,(数据迭代器,神经网络模型,计算损失的函数),这个函数的作用主要是评估模型的性能。通过计算平均损失来衡量模型的拟合效果。较低的平均损失值代表了模型对测试集的预测结果和真是标签之间的差距较小,即模型具有较好的性能。


第七步:定义模型训练函数


首先分析一下接受的参数(神经网路模型,训练数据的迭代器,损失函数,训练轮次,小批量样本的数量,学习率,优化器)。

两个列表用于保存每轮训练集和测试集上的损失值。


dim指的是哪一个维度,y_hat是一个大小为[batch_size,num_classes]的张量,

def train(net, train_iter, loss_func, num_epochs, batch_size, lr=None, optimizer=None):
    train_loss_list = []
    test_loss_list = []
    for epoch in range(num_epochs):
        train_l_sum, train_acc_sum, n, c = 0.0, 0.0, 0, 0# 损失值和准确率,样本数量,批次量
        for X, y in train_iter:  # x和y分别是小批量样本的特征和标签
            y_hat = net.forward(X)# 前向传播,
            l = loss_func(y_hat, y)# 计算损失
            l.backward()# 反向传播,计算梯度
            optimizer(net.params, lr)# 优化器更新模型的参数
            for param in net.params:
                param.grad.data.zero_()#  初始化
            train_l_sum += l.item()# 累加损失值
            train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item() # 累加精确度,
            n += y.shape[0]# 累加样本数
            c += 1# 累加批次量
        test_loss = tesst(test_iter, net, loss_func)# 计算在测试集上的损失
        train_loss_list.append(train_l_sum / c)# 将训练集的损失值添加进去
        test_loss_list.append(test_loss)# 将测试集的损失值添加进去
        # draw_loss(train_l_sum/c, test_loss, None)
        print('epoch %d, train_loss %.4f,test_loss %.4f' % (epoch + 1, train_l_sum / c, test_loss))# 打印轮次。
    return train_loss_list, test_loss_list


第八步:结果可视化


传入的参数(训练集上的损失值列表,测试集上的损失值列表,验证集的损失值(可选可不选))。

首先生成一个长度为列表长度的等差序列。

def draw_loss(train_loss, test_loss, valid_loss=None):
    x = np.linspace(0, len(train_loss), len(train_loss)) \
        if valid_loss is None else np.linspace(0, len(train_loss), len(test_loss), len(valid_loss))
    plt.plot(x, train_loss, label="Train_Loss", linewidth=1.5)
    plt.plot(x, test_loss, label="Test_Loss", linewidth=1.5)
    if valid_loss is not None:
        plt.plot(x, test_loss, label="Valid_loss", linewidth=1.5)
    plt.xlabel("Epoch")
    plt.ylabel("Loss")
    plt.legend()# 添加图例
    plt.show()


第九步:训练模型

if __name__ == "__main__":
    net = Net()
    num_epochs = 100
    lr = 0.03
    optimizer = SGD
    train_loss, test_loss = train(net, train_iter, loss_func, num_epochs, batch_size, lr, optimizer)
    draw_loss(train_loss, test_loss)

通过Net()创建一个神经网络实例,然后定义迭代轮次,学习率,优化器。然后进行训练。然后通过draw_loss进行可视化。



版权声明:本文为weixin_45911397原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。