[PyTorch]利用torch.nn实现前馈神经网络

文章目录

  • 前馈神经网络
  • 实验要求
  • 一、利用torch.nn实现前馈神经网络
  • 二、对比三种不同的激活函数的实验结果
  • 三、使用不同的隐藏层层数和隐藏单元个数,对比实验结果
  • 3.1 隐藏单元个数
  • 3.2 隐藏层层数
  • 四、利用torch.nn实现dropout
  • 五、利用torch.nn实现L2正则化
  • 六、k折交叉验证
  • 前馈神经网络

    前馈神经网络,又称作深度前馈网络、多层感知机,信息流经过中间的函数计算, 最终达到输出,被称为“前向”。模型的输出与模型本身没有反馈连接。
    前馈神经网络中的隐含层需要使用非线性激活,如果不使用非线性激活函数,那么每一层都是线性的,导致多层的线性组合仍然是线性的,最终的输出也是线性拟合,无法泛化非线性的问题。

    实验要求

    1. 使用torch.nn在Fashion-MNIST数据集完成前馈神经网络,绘制训练集和测试集的loss曲线(使用Fashion-MNIST数据集)
    2. 使用三种不同的激活函数,对比实验结果
    3. 使用不同的隐藏层层数和隐藏单元个数,对比实验结果
    4. 在上面实验中分别手动实现和利用torch.nn实现dropout,探究不同丢弃率对结果的影响
    5. 分别手动实现和利用torch.nn实现L2正则化,探究不同惩罚项权重对结果的影响
    6. 选择上述实验中效果最好的模型,采用10折交叉验证评估实验结果

    一、利用torch.nn实现前馈神经网络

    导入包和加载Fashion-MNIST数据集可参考之前的博客,下面直接开始构建模型的部分

    # 3、构建模型
    num_inputs = 784
    num_outputs = 10  # 共10类
    num_hiddens = 256
    
    
    class FlattenLayer(torch.nn.Module):  # Flatten层
        def __init__(self):
            super(FlattenLayer, self).__init__()
    
        def forward(self, x):
            return x.view(x.shape[0], -1)
    
    
    class SoftmaxLayer(torch.nn.Module):
        def __init__(self):
            super(SoftmaxLayer, self).__init__()
    
        def forward(self, X):
            X_exp = X.exp()  # 对每个元素做指数运算
            partition = X_exp.sum(dim=1, keepdim=True)  # 求列和,即对同行元素求和 n*1
            return X_exp / partition  # broadcast
    
    
    net = torch.nn.Sequential(
        FlattenLayer(),
        torch.nn.Linear(num_inputs, num_hiddens),
        # 下面是三种可选用的激活函数
        torch.nn.ReLU(),  # Relu激活函数
        # torch.nn.Softplus(),  # Softplus激活函数
        # torch.nn.Tanh(),  # Tanh激活函数
        torch.nn.Linear(num_hiddens, num_outputs),
        SoftmaxLayer(),
    )
    
    

    初始化模型参数

    # 4、初始化模型参数
    for params in net.parameters():  # 对网络中的每个参数
        torch.nn.init.normal_(params, mean=0, std=0.01)  # 初始化为服从均值0标准差0.01正态分布
    

    损失函数与优化器

    # 5、损失函数与优化器
    num_epochs = 10  # 训练轮次
    lr = 0.1
    loss = torch.nn.CrossEntropyLoss()  # 交叉熵损失函数
    optimizer = torch.optim.SGD(net.parameters(), lr)
    

    评估函数

    # 评估函数
    def evaluate(data_iter, net):
        right_sum, n, loss_sum = 0.0, 0, 0.0
        for x, y in data_iter:
            y_ = net(x)
            l = loss(y_, y).sum()
            right_sum += (y_.argmax(dim=1) == y).float().sum().item()
            n += y.shape[0]
            loss_sum += l.item()
        return right_sum / n, loss_sum / n
    

    模型训练与评估

    train_l_ = []
    test_l_ = []
    train_acc_ = []
    test_acc_ = []
    
    
    def train(net, loss, num_epochs, optimizer, train_iter, test_iter):
        for epoch in range(num_epochs):
            train_r_num, train_l, n = 0.0, 0.0, 0
            for X, y in tqdm(train_iter):
                y_hat = net(X)
                l = loss(y_hat, y)
                l.backward()
                optimizer.step()
                optimizer.zero_grad()
                train_r_num += (y_hat.argmax(dim=1) == y).sum().item()
                train_l += l.item()
                n += y.shape[0]
            test_acc, test_l = evaluate(test_iter, net)
            train_l_.append(train_l / n)
            train_acc_.append(train_r_num / n)
            test_l_.append(test_l)
            test_acc_.append(test_acc)
            print('epoch %d, train loss %.4f, train acc %.3f' % (epoch + 1, train_l / n, train_r_num / n))
            print('test loss %.4f, test acc %.3f' % (test_l, test_acc))
    
    
    train(net, loss, num_epochs, optimizer, train_iter, test_iter)
    

    绘制loss曲线以及准确率曲线

    
    # 绘制函数
    def draw_(x, train_Y, test_Y, ylabel):
        plt.plot(x, train_Y, label='train_' + ylabel, linewidth=1.5)
        plt.plot(x, test_Y, label='test_' + ylabel, linewidth=1.5)
        plt.xlabel('epoch')
        plt.ylabel(ylabel)
        plt.legend()  # 加上图例
        plt.show()
    
    
    # 绘制loss曲线
    x = np.linspace(0, len(train_l_), len(train_l_))
    draw_(x, train_l_, test_l_, 'loss')
    draw_(x, train_acc_, test_acc_, 'accuracy')
    

    二、对比三种不同的激活函数的实验结果

    1、ReLu激活函数

    训练结果
    loss曲线 acc曲线

    2、Softplus激活函数

    训练结果
    loss曲线 acc曲线

    3、Tanh激活函数

    训练结果
    loss曲线 acc曲线

    三、使用不同的隐藏层层数和隐藏单元个数,对比实验结果

    3.1 隐藏单元个数

    通过修改num_hiddens来调节隐藏单元个数。以下的实验隐藏层1层,lr=0.2,epoch=5,实验结果如下:

    实验结果最好是跑多次计算平均值,由于时间关系本文实验的每种情况都只跑了一次。从表格结果来看,在本任务上,隐藏层神经元越多,相同的epoch下实验效果越好。

    3.2 隐藏层层数

    每层隐藏层神经元为64,实验结果如下:

    隐藏层越多,模型越复杂,应该是收敛速度变慢,在相同的epoch下实验结果会变差。

    四、利用torch.nn实现dropout

    手动实现

    # 手动实现dropout的代码
    # 设丢弃率为p,有p的概率隐藏单元h_i会被清零(丢弃),有(1-p)的概率h_i会除以(1-p)做拉伸
    # def dropout(X, drop_prob):
    #     X = X.float()
    #     # 检查丢弃概率是否在0~1
    #     assert 0 <= drop_prob <= 1
    #     keep_prob = 1 - drop_prob
    #     # 这种情况把全部元素丢弃
    #     if keep_prob == 0:
    #         return torch.zeros_like(X)
    #     # 生产mask矩阵(向量)
    #     mask = (torch.rand(X.shape) < (keep_prob)).float()
    #     # 按照mask进行对X的变换
    #     return mask * X / keep_prob
    

    利用torch.nn实现

    drop_prob1 = 0.2
    
    net = torch.nn.Sequential(
        FlattenLayer(),
        torch.nn.Linear(num_inputs, num_hiddens),
        torch.nn.ReLU(),  # Relu激活函数
        torch.nn.Dropout(drop_prob1),
        torch.nn.Linear(num_hiddens, num_outputs),
        SoftmaxLayer(),
    )
    

    实验结果

    在这里dropout使模型的效果变差了一点,因为dropout是防止过拟合的一项措施,本任务的模型简单并且没有出现过拟合,所以dropout对本模型没有提升效果

    五、利用torch.nn实现L2正则化

    使用torch.optim的weight_decay参数实现L2范数正则化(也叫做权重衰减weight_decay)

    # optimizer = torch.optim.SGD(net.parameters(), lr)
    optimizer_w = torch.optim.SGD(net.parameters(), lr=lr, weight_decay=1e-2)
    

    实验结果

    实验结果也变差了一点,本文构建的模型不够复杂

    六、k折交叉验证

    k折交叉验证:将数据集分层划分为K个大小相似的互斥子集,每次用K-1个子集的并集作为训练集,剩下的子集作为测试集,最终返回k个测试结果的均值
    获取第i折的训练集和验证集

    def get_kfold_data(k, i, data):  # 获取第i+1(i=0~k-1)折的训练集和验证集
        # train_features = mnist_train.data  # 训练集特征数据
        # train_labels = mnist_train.targets  # 训练集标签数据
        fold_size = data.targets.shape[0] // k  # 每份数据个数
        valid_data = deepcopy(data)
        train_data = deepcopy(data)
        start_ = i*fold_size
        if i != k-1:
            end_ = (i+1)*fold_size
            valid_data.data = valid_data.data[start_:end_]  # 验证集
            valid_data.targets = valid_data.targets[start_:end_]  # 验证集
            train_data.data = torch.cat((train_data.data[0:start_], train_data.data[end_:]), dim=0)  # cat拼接
            train_data.targets = torch.cat((train_data.targets[0:start_], train_data.targets[end_:]), dim=0)  # cat拼接
        else:  # 是最后一折
            valid_data.data, valid_data.targets = valid_data.data[start_:], valid_data.targets[start_:]  #
            train_data.data, train_data.targets = train_data.data[0:start_], train_data.targets[0:start_]
        return train_data, valid_data
    

    训练

    
    def k_train(net, train_data, valid_data):
        train_iter = Data.DataLoader(
            dataset=train_data,  # torch TensorDataset format
            batch_size=batch_size,
            shuffle=True,  # 是否打乱数据
            num_workers=0,  # 多线程来读数据,在Win下需要设置为0
        )
        valid_iter = Data.DataLoader(
            dataset=valid_data,
            batch_size=batch_size,
            shuffle=False,
            num_workers=0,
        )
    
        train_acc, train_l = 0.0, 0.0
        valid_acc, valid_l = 0.0, 0.0
    
        optimizer = torch.optim.SGD(net.parameters(), lr=lr)
    
        for epoch in range(num_epochs):
            train_r_num, train_l_, n = 0.0, 0.0, 0
            for X, y in train_iter:
                y_hat = net(X)
                l = loss(y_hat, y)
                l.backward()
                # optimizer.step()
                optimizer.step()
                # optimizer.zero_grad()
                optimizer.zero_grad()
                train_r_num += (y_hat.argmax(dim=1) == y).sum().item()
                train_l_ += l.item()
                n += y.shape[0]
            v_acc, v_l = evaluate(valid_iter, net)
            valid_acc += v_acc
            valid_l += v_l
            train_acc += train_r_num / n
            train_l += train_l_ / n
        return train_l/num_epochs, valid_l/num_epochs, train_acc/num_epochs, valid_acc/num_epochs
    
    
    def kfold_train(k):
        train_loss_sum, valid_loss_sum = 0, 0
        train_acc_sum, valid_acc_sum = 0, 0
        for i in range(k):
            print('第', i+1, '折验证')
            train_data, valid_data = get_kfold_data(k, i, mnist_train)
            net_ = torch.nn.Sequential(
                FlattenLayer(),
                torch.nn.Linear(num_inputs, num_hiddens),
                torch.nn.ReLU(),  # Relu激活函数
                torch.nn.Linear(num_hiddens, num_outputs),
                SoftmaxLayer(),
            )
            for params in net_.parameters():  # 对网络中的每个参数
                torch.nn.init.normal_(params, mean=0, std=0.01)  # 初始化为服从均值0标准差0.01正态分布
                
            train_loss, val_loss, train_acc, val_acc = k_train(net_, train_data, valid_data)
            print('train loss %.4f, val loss %.4f, train acc %.3f, val acc %.3f' % (train_loss, val_loss, train_acc, val_acc))
    
            train_loss_sum += train_loss
            valid_loss_sum += val_loss
            train_acc_sum += train_acc
            valid_acc_sum += val_acc
        print('\n最终k折交叉验证结果:')
        print('ave train loss: %.4f, ave train acc: %.3f' % (train_loss_sum/k, train_acc_sum/k))
        print('ave valid loss: %.4f, ave valid acc: %.3f' % (valid_loss_sum/k, valid_acc_sum/k))
    
    kfold_train(10)
    

    验证结果

    第 1 折验证
    train loss 0.0069, val loss 0.0069, train acc 0.734, val acc 0.768
    第 2 折验证
    train loss 0.0069, val loss 0.0069, train acc 0.727, val acc 0.755
    第 3 折验证
    train loss 0.0069, val loss 0.0069, train acc 0.727, val acc 0.763
    第 4 折验证
    train loss 0.0069, val loss 0.0069, train acc 0.731, val acc 0.760
    第 5 折验证
    train loss 0.0069, val loss 0.0069, train acc 0.729, val acc 0.761
    第 6 折验证
    train loss 0.0069, val loss 0.0068, train acc 0.729, val acc 0.774
    第 7 折验证
    train loss 0.0069, val loss 0.0069, train acc 0.726, val acc 0.770
    第 8 折验证
    train loss 0.0069, val loss 0.0069, train acc 0.730, val acc 0.764
    第 9 折验证
    train loss 0.0069, val loss 0.0069, train acc 0.735, val acc 0.768
    第 10 折验证
    train loss 0.0071, val loss 0.0071, train acc 0.671, val acc 0.711
    
    最终k折交叉验证结果:
    ave train loss: 0.0069, ave train acc: 0.724
    ave valid loss: 0.0069, ave valid acc: 0.759
    
    Process finished with exit code 0
    

    来源:番茄牛腩煲

    物联沃分享整理
    物联沃-IOTWORD物联网 » [PyTorch]利用torch.nn实现前馈神经网络

    发表评论