一、自动梯度计算和预定义算子

虽然我们能够通过模块化的方式比较好地对神经网络进行组装,但是每个模块的梯度计算过程仍然十分繁琐且容易出错。在深度学习框架中,已经封装了自动梯度计算的功能,我们只需要聚焦模型架构,不再需要耗费精力进行计算梯度。

1. 利用预定义算子重新实现前馈神经网络

1.1使用torch的预定义算子来重新实现二分类任务。

主要使用到的预定义算子为torch.nn.Linear

class torch.nn.Linear(in_features, out_features, weight_attr=None, bias_attr=None, name=None)

torch.nn.Linear算子可以接受一个形状为[batch_size,∗,in_features]的输入张量,其中"∗"表示张量中可以有任意的其它额外维度,并计算它与形状为[in_features, out_features]的权重矩阵的乘积,然后生成形状为[batch_size,∗,out_features]的输出张量。 torch.nn.Linear算子默认有偏置参数,可以通过bias_attr=False设置不带偏置。

class Model_MLP_L2_V2(nn.Module):def __init__(self, input_size, hidden_size, output_size):super(Model_MLP_L2_V2, self).__init__()self.fc1 = nn.Linear(input_size, hidden_size)self.fc2 = nn.Linear(hidden_size, output_size)# 使用'torch.nn.functional.sigmoid'定义 Logistic 激活函数self.act_fn = F.sigmoid# 前向计算def forward(self, inputs):z1 = self.fc1(inputs)a1 = self.act_fn(z1)z2 = self.fc2(a1)a2 = self.act_fn(z2)return a2

1.2增加一个3个神经元的隐藏层,再次实现二分类。

class Model_MLP_L2_V3(torch.nn.Module):def __init__(self, input_size, hidden_size, hidden_size2, output_size):super(Model_MLP_L2_V3, self).__init__()self.fc1 = nn.Linear(input_size, hidden_size)w1=torch.normal(0,0.1,size=(hidden_size,input_size),requires_grad=True)self.fc1.weight = nn.Parameter(w1)self.fc2 = nn.Linear(hidden_size, hidden_size2)w2 = torch.normal(0, 0.1, size=(hidden_size2, hidden_size), requires_grad=True)self.fc2.weight = nn.Parameter(w2)self.fc3 = nn.Linear(hidden_size2, output_size)w3 = torch.normal(0, 0.1, size=(output_size, hidden_size2), requires_grad=True)self.fc3.weight = nn.Parameter(w3)# 使用'torch.nn.functional.sigmoid'定义 Logistic 激活函数self.act_fn = torch.sigmoid# 前向计算def forward(self, inputs):z1 = self.fc1(inputs)a1 = self.act_fn(z1)z2 = self.fc2(a1)a2 = self.act_fn(z2)z3 = self.fc3(a2)a3 = self.act_fn(z3)return a3

设置的模型

input_size = 2
hidden_size = 5
hidden_size2 = 3
output_size = 1
model = Model_MLP_L2_V3(input_size=input_size, hidden_size=hidden_size, hidden_size2=hidden_size2, output_size=output_size)

执行结果:

[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.50000
[Train] epoch: 0/1000, loss: 0.6949098110198975
[Train] epoch: 50/1000, loss: 0.6932579278945923
[Train] epoch: 100/1000, loss: 0.6932216882705688
[Train] epoch: 150/1000, loss: 0.6931880712509155
[Train] epoch: 200/1000, loss: 0.6931560039520264
[Train] epoch: 250/1000, loss: 0.6931244730949402
[Train] epoch: 300/1000, loss: 0.6930925846099854
[Train] epoch: 350/1000, loss: 0.6930593848228455
[Train] epoch: 400/1000, loss: 0.6930238604545593
[Train] epoch: 450/1000, loss: 0.6929848790168762
[Train] epoch: 500/1000, loss: 0.6929409503936768
[Train] epoch: 550/1000, loss: 0.692890465259552
[Train] epoch: 600/1000, loss: 0.6928313374519348
[Train] epoch: 650/1000, loss: 0.6927607655525208
[Train] epoch: 700/1000, loss: 0.692674994468689
[Train] epoch: 750/1000, loss: 0.6925693154335022
[Train] epoch: 800/1000, loss: 0.6924368143081665
[Train] epoch: 850/1000, loss: 0.6922679543495178
[Evaluate] best accuracy performence has been updated: 0.50000 --> 0.50625
[Train] epoch: 900/1000, loss: 0.6920491456985474
[Evaluate] best accuracy performence has been updated: 0.50625 --> 0.51250
[Evaluate] best accuracy performence has been updated: 0.51250 --> 0.51875
[Evaluate] best accuracy performence has been updated: 0.51875 --> 0.52500
[Evaluate] best accuracy performence has been updated: 0.52500 --> 0.53125
[Evaluate] best accuracy performence has been updated: 0.53125 --> 0.54375
[Evaluate] best accuracy performence has been updated: 0.54375 --> 0.55000
[Train] epoch: 950/1000, loss: 0.6917603015899658
[Evaluate] best accuracy performence has been updated: 0.55000 --> 0.55625
[Evaluate] best accuracy performence has been updated: 0.55625 --> 0.56250
[Evaluate] best accuracy performence has been updated: 0.56250 --> 0.56875
[Evaluate] best accuracy performence has been updated: 0.56875 --> 0.57500Process finished with exit code 0

添加了一个隐藏层hidden_size2 ,然后它含有3个神经元,所以hidden_size2=3,然后在传播计算参数的forward部分也需要进行相关参数的更新。可见Ir=5的时候运行结果比较理想。

2.完善Runner 类

基于上个实验中的Runner类,本次实验中的Runner类加入了自动梯度计算;模型保存时,使用state_dict方法获取模型参数;模型加载时,使用set_state_dict方法加载模型参数.

class RunnerV2_2(nn.Module):def __init__(self, model, optimizer, metric, loss_fn, **kwargs):super().__init__()self.model = modelself.optimizer = optimizerself.loss_fn = loss_fnself.metric = metric# 记录训练过程中的评估指标变化情况self.train_scores = []self.dev_scores = []# 记录训练过程中的评价指标变化情况self.train_loss = []self.dev_loss = []def train(self, train_set, dev_set, **kwargs):# 将模型切换为训练模式self.model.train()# 传入训练轮数,如果没有传入值则默认为0num_epochs = kwargs.get("num_epochs", 0)# 传入log打印频率,如果没有传入值则默认为100log_epochs = kwargs.get("log_epochs", 100)# 传入模型保存路径,如果没有传入值则默认为"best_model.pdparams"save_path = kwargs.get("save_path", "best_model.pdparams")# log打印函数,如果没有传入则默认为"None"custom_print_log = kwargs.get("custom_print_log", None)# 记录全局最优指标best_score = 0# 进行num_epochs轮训练for epoch in range(num_epochs):X, y = train_set# 获取模型预测logits = self.model(X)# 计算交叉熵损失trn_loss = self.loss_fn(logits, y)self.train_loss.append(trn_loss.item())# 计算评估指标trn_score = self.metric(logits, y).item()self.train_scores.append(trn_score)# 自动计算参数梯度trn_loss.backward()if custom_print_log is not None:# 打印每一层的梯度custom_print_log(self)# 参数更新self.optimizer.step()# 清空梯度self.optimizer.zero_grad()dev_score, dev_loss = self.evaluate(dev_set)# 如果当前指标为最优指标,保存该模型if dev_score > best_score:self.save_model(save_path)print(f"[Evaluate] best accuracy performence has been updated: {best_score:.5f} --> {dev_score:.5f}")best_score = dev_scoreif log_epochs and epoch % log_epochs == 0:print(f"[Train] epoch: {epoch}/{num_epochs}, loss: {trn_loss.item()}")# 模型评估阶段,使用'paddle.no_grad()'控制不计算和存储梯度@torch.no_grad()def evaluate(self, data_set):# 将模型切换为评估模式self.model.eval()X, y = data_set# 计算模型输出logits = self.model(X)# 计算损失函数loss = self.loss_fn(logits, y).item()self.dev_loss.append(loss)# 计算评估指标score = self.metric(logits, y).item()self.dev_scores.append(score)return score, loss# 模型测试阶段,使用'paddle.no_grad()'控制不计算和存储梯度@torch.no_grad()def predict(self, X):# 将模型切换为评估模式self.model.eval()return self.model(X)# 使用'model.state_dict()'获取模型参数,并进行保存def save_model(self, saved_path):torch.save(self.model.state_dict(), saved_path)# 使用'model.set_state_dict'加载模型参数def load_model(self, model_path):state_dict = torch.load(model_path)self.model.set_state_dict(state_dict)

3.模型训练

实例化Runner类,并传入训练配置

# 设置模型
input_size = 2
hidden_size = 5
output_size = 1
model = Model_MLP_L2_V2(input_size=input_size, hidden_size=hidden_size, output_size=output_size)# 设置损失函数
loss_fn = F.binary_cross_entropy
# 设置优化器
optimizer = torch.optim.SGD(model.parameters(), lr=0.2)
# 设置评价指标
metric = accuracy
# 其他参数
epoch_num = 1000
saved_path = 'best_model.pdparams'
# 实例化RunnerV2类,并传入训练配置
runner = RunnerV2_2(model, optimizer, metric, loss_fn)
runner.train([X_train, y_train], [X_dev, y_dev], num_epochs=epoch_num, log_epochs=50, save_path="best_model.pdparams")

计算准确率函数

# 准确率 函数
def accuracy(preds, labels):# 判断是二分类任务还是多分类任务,preds.shape[1]=1时为二分类任务,preds.shape[1]>1时为多分类任务if preds.shape[1] == 1:# 二分类时,判断每个概率值是否大于0.5,当大于0.5时,类别为1,否则类别为0# preds的数据类型转换为float32类型preds = (preds >= 0.5).to(torch.float32)else:# 多分类时,使用torch.argmax计算最大元素索引作为类别preds = torch.argmax(preds, 1)preds = preds.to(torch.int32)return torch.mean(torch.as_tensor((preds == labels), dtype=torch.float32))
# 假设模型的预测值为[[0.],[1.],[1.],[0.]],真实类别为[[1.],[1.],[0.],[0.]],计算准确率
preds = torch.tensor([[0.], [1.], [1.], [0.]])
labels = torch.tensor([[1.], [1.], [0.], [0.]])
print("accuracy is:", accuracy(preds, labels))

执行结果:

[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.21875
[Train] epoch: 0/1000, loss: 0.7022157311439514
[Evaluate] best accuracy performence has been updated: 0.21875 --> 0.26250
[Evaluate] best accuracy performence has been updated: 0.26250 --> 0.31875
[Evaluate] best accuracy performence has been updated: 0.31875 --> 0.43750
[Evaluate] best accuracy performence has been updated: 0.43750 --> 0.48750
[Evaluate] best accuracy performence has been updated: 0.48750 --> 0.52500
[Evaluate] best accuracy performence has been updated: 0.52500 --> 0.53125
[Evaluate] best accuracy performence has been updated: 0.53125 --> 0.54375
[Evaluate] best accuracy performence has been updated: 0.54375 --> 0.55625
[Evaluate] best accuracy performence has been updated: 0.55625 --> 0.57500
[Evaluate] best accuracy performence has been updated: 0.57500 --> 0.59375
[Evaluate] best accuracy performence has been updated: 0.59375 --> 0.60625
[Evaluate] best accuracy performence has been updated: 0.60625 --> 0.63125
[Evaluate] best accuracy performence has been updated: 0.63125 --> 0.66875
[Evaluate] best accuracy performence has been updated: 0.66875 --> 0.68125
[Evaluate] best accuracy performence has been updated: 0.68125 --> 0.71875
[Evaluate] best accuracy performence has been updated: 0.71875 --> 0.72500
[Evaluate] best accuracy performence has been updated: 0.72500 --> 0.75000
[Evaluate] best accuracy performence has been updated: 0.75000 --> 0.75625
[Evaluate] best accuracy performence has been updated: 0.75625 --> 0.76875
[Evaluate] best accuracy performence has been updated: 0.76875 --> 0.78125
[Evaluate] best accuracy performence has been updated: 0.78125 --> 0.80000
[Evaluate] best accuracy performence has been updated: 0.80000 --> 0.81250
[Evaluate] best accuracy performence has been updated: 0.81250 --> 0.81875
[Evaluate] best accuracy performence has been updated: 0.81875 --> 0.82500
[Train] epoch: 50/1000, loss: 0.6558495759963989
[Train] epoch: 100/1000, loss: 0.5948771238327026
[Train] epoch: 150/1000, loss: 0.5388158559799194
[Train] epoch: 200/1000, loss: 0.5058477520942688
[Train] epoch: 250/1000, loss: 0.4894803464412689
[Train] epoch: 300/1000, loss: 0.4813789427280426
[Train] epoch: 350/1000, loss: 0.47720932960510254
[Train] epoch: 400/1000, loss: 0.47499004006385803
[Train] epoch: 450/1000, loss: 0.4737810492515564
[Train] epoch: 500/1000, loss: 0.4731082022190094
[Train] epoch: 550/1000, loss: 0.47272247076034546
[Train] epoch: 600/1000, loss: 0.4724907875061035
[Train] epoch: 650/1000, loss: 0.4723418354988098
[Train] epoch: 700/1000, loss: 0.4722374379634857
[Train] epoch: 750/1000, loss: 0.47215747833251953
[Train] epoch: 800/1000, loss: 0.4720911979675293
[Train] epoch: 850/1000, loss: 0.47203296422958374
[Train] epoch: 900/1000, loss: 0.4719797670841217
[Train] epoch: 950/1000, loss: 0.4719299376010895

可视化准确率变化

# 可视化观察训练集与验证集的指标变化情况
def plot(runner, fig_name):plt.figure(figsize=(10, 5))epochs = [i for i in range(len(runner.train_scores))]plt.subplot(1, 2, 1)plt.plot(epochs, runner.train_loss, color='#e4007f', label="Train loss")plt.plot(epochs, runner.dev_loss, color='#f19ec2', linestyle='--', label="Dev loss")# 绘制坐标轴和图例plt.ylabel("loss", fontsize='large')plt.xlabel("epoch", fontsize='large')plt.legend(loc='upper right', fontsize='x-large')plt.subplot(1, 2, 2)plt.plot(epochs, runner.train_scores, color='#e4007f', label="Train accuracy")plt.plot(epochs, runner.dev_scores, color='#f19ec2', linestyle='--', label="Dev accuracy")# 绘制坐标轴和图例plt.ylabel("score", fontsize='large')plt.xlabel("epoch", fontsize='large')plt.legend(loc='lower right', fontsize='x-large')plt.savefig(fig_name)plt.show()plot(runner, 'fw-acc.pdf')

4.性能评价

使用测试数据对训练完成后的最优模型进行评价,观察模型在测试集上的准确率以及loss情况。

# 模型评价
runner.load_model("best_model.pdparams")
score, loss = runner.evaluate([X_test, y_test])
print("[Test] score/loss: {:.4f}/{:.4f}".format(score, loss))

执行结果:

[Test] score/loss: 0.7600/0.4883

模型在测试集上取得了较高的准确率。

5.思考题

自定义梯度计算和自动梯度计算从计算性能、计算结果等多方面比较,谈谈自己的看法。

自动梯度:神经网络的参数主要通过梯度下降来进行优化。当确定了风险函数以及网络结构后,我们就可以手动用链式法则来计算风险函数对每个参数的梯度, 并用代码进行实现。但是手动求导并转换为计算机程序的过程非常琐碎并容易出错,导致实现神经网络变得十分低效。实际上,参数的梯度可以让计算机来自动计算。目前,几乎所有的主流深度学习框架都包含了自动梯度计算的功能,即我们可以只考虑网络结构并用代码实现,其梯度可以自动进行计算, 无需人工干预,这样可以大幅提高开发效率。

自定义梯度:需要对某些节点的梯度进行一些定制,特别是该节点操作不可导,如果实在需要对这个节点进行操作,而且希望其可以反向传播,那么就需要对其进行自定义反向传播时的梯度。

计算性能上一般自动梯度会好于自定义梯度,有时自定义梯度的参数选择也会导致梯度计算的效果不好,不容易控制。

二、优化问题

通过实践来发现神经网络模型的优化问题,并思考如何改进。

1.参数初始化

实现一个神经网络前,需要先初始化模型参数。如果对每一层的权重和偏置都用0初始化,那么通过第一遍前向计算,所有隐藏层神经元的激活值都相同;在反向传播时,所有权重的更新也都相同,这样会导致隐藏层神经元没有差异性,出现对称权重现象。

接下来,将模型参数全都初始化为0,看实验结果。这里重新定义了一个类TwoLayerNet_Zeros,两个线性层的参数全都初始化为0。

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import torch.nn.functional as Ffrom nndl2.dataset import make_moons
import os
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"class Model_MLP_L2_V4(nn.Module):def __init__(self, input_size, hidden_size, output_size):super(Model_MLP_L2_V4, self).__init__()self.fc1 = nn.Linear(input_size, hidden_size)self.fc2 = nn.Linear(hidden_size, output_size)self.act_fn = F.sigmoid# 前向计算def forward(self, inputs):z1 = self.fc1(inputs)a1 = self.act_fn(z1)z2 = self.fc2(a1)a2 = self.act_fn(z2)return a2

利用Runner类训练模型:

# 设置模型
input_size = 2
hidden_size = 5
output_size = 1
model = Model_MLP_L2_V4(input_size=input_size, hidden_size=hidden_size, output_size=output_size)# 设置损失函数
loss_fn = F.binary_cross_entropy# 设置优化器
optimizer = torch.optim.SGD(model.parameters(), lr=0.2)# 设置评价指标
metric = accuracy# 其他参数
epoch = 2000
saved_path = 'best_model.pdparams'
# 实例化RunnerV2类,并传入训练配置
runner = RunnerV2_2(model, optimizer, metric, loss_fn)runner.train([X_train, y_train], [X_dev, y_dev], num_epochs=5, log_epochs=50, save_path="best_model.pdparams", custom_print_log=print_weights)

权重和梯度

The weights of the Layers:
('fc1.weight', Parameter containing:
tensor([[ 0.4618, -0.2339],[-0.5633,  0.3300],[-0.6991, -0.2421],[ 0.1939, -0.0767],[-0.0565,  0.4028]], requires_grad=True))
('fc1.bias', Parameter containing:
tensor([0.2812, 0.5646, 0.1304, 0.3827, 0.0918], requires_grad=True))
('fc2.weight', Parameter containing:
tensor([[ 0.0198,  0.0295, -0.1418,  0.4028, -0.2293]], requires_grad=True))
('fc2.bias', Parameter containing:
tensor([-0.3413], requires_grad=True))
('fc1.weight', Parameter containing:
tensor([[ 0.4618, -0.2339],[-0.5633,  0.3300],[-0.6991, -0.2421],[ 0.1939, -0.0767],[-0.0565,  0.4028]], requires_grad=True))
('fc1.bias', Parameter containing:
tensor([0.2812, 0.5646, 0.1304, 0.3827, 0.0918], requires_grad=True))
('fc2.weight', Parameter containing:
tensor([[ 0.0198,  0.0295, -0.1418,  0.4028, -0.2293]], requires_grad=True))
('fc2.bias', Parameter containing:
tensor([-0.3413], requires_grad=True))
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.41250
[Train] epoch: 0/5, loss: 0.6785968542098999
The weights of the Layers:
('fc1.weight', Parameter containing:
tensor([[ 0.4620, -0.2341],[-0.5630,  0.3297],[-0.7005, -0.2408],[ 0.1988, -0.0802],[-0.0596,  0.4048]], requires_grad=True))
('fc1.bias', Parameter containing:
tensor([0.2812, 0.5647, 0.1303, 0.3831, 0.0914], requires_grad=True))
('fc2.weight', Parameter containing:
tensor([[ 0.0312,  0.0240, -0.1446,  0.4098, -0.2303]], requires_grad=True))
('fc2.bias', Parameter containing:
tensor([-0.3347], requires_grad=True))
('fc1.weight', Parameter containing:
tensor([[ 0.4620, -0.2341],[-0.5630,  0.3297],[-0.7005, -0.2408],[ 0.1988, -0.0802],[-0.0596,  0.4048]], requires_grad=True))
('fc1.bias', Parameter containing:
tensor([0.2812, 0.5647, 0.1303, 0.3831, 0.0914], requires_grad=True))
('fc2.weight', Parameter containing:
tensor([[ 0.0312,  0.0240, -0.1446,  0.4098, -0.2303]], requires_grad=True))
('fc2.bias', Parameter containing:
tensor([-0.3347], requires_grad=True))
The weights of the Layers:
('fc1.weight', Parameter containing:
tensor([[ 0.4623, -0.2343],[-0.5628,  0.3296],[-0.7020, -0.2395],[ 0.2036, -0.0839],[-0.0626,  0.4068]], requires_grad=True))
('fc1.bias', Parameter containing:
tensor([0.2812, 0.5647, 0.1302, 0.3834, 0.0910], requires_grad=True))
('fc2.weight', Parameter containing:
tensor([[ 0.0421,  0.0181, -0.1477,  0.4166, -0.2316]], requires_grad=True))
('fc2.bias', Parameter containing:
tensor([-0.3288], requires_grad=True))
('fc1.weight', Parameter containing:
tensor([[ 0.4623, -0.2343],[-0.5628,  0.3296],[-0.7020, -0.2395],[ 0.2036, -0.0839],[-0.0626,  0.4068]], requires_grad=True))
('fc1.bias', Parameter containing:
tensor([0.2812, 0.5647, 0.1302, 0.3834, 0.0910], requires_grad=True))
('fc2.weight', Parameter containing:
tensor([[ 0.0421,  0.0181, -0.1477,  0.4166, -0.2316]], requires_grad=True))
('fc2.bias', Parameter containing:
tensor([-0.3288], requires_grad=True))
The weights of the Layers:
('fc1.weight', Parameter containing:
tensor([[ 0.4627, -0.2347],[-0.5626,  0.3294],[-0.7034, -0.2383],[ 0.2085, -0.0876],[-0.0656,  0.4088]], requires_grad=True))
('fc1.bias', Parameter containing:
tensor([0.2812, 0.5648, 0.1301, 0.3836, 0.0906], requires_grad=True))
('fc2.weight', Parameter containing:
tensor([[ 0.0527,  0.0120, -0.1511,  0.4231, -0.2333]], requires_grad=True))
('fc2.bias', Parameter containing:
tensor([-0.3234], requires_grad=True))
('fc1.weight', Parameter containing:
tensor([[ 0.4627, -0.2347],[-0.5626,  0.3294],[-0.7034, -0.2383],[ 0.2085, -0.0876],[-0.0656,  0.4088]], requires_grad=True))
('fc1.bias', Parameter containing:
tensor([0.2812, 0.5648, 0.1301, 0.3836, 0.0906], requires_grad=True))
('fc2.weight', Parameter containing:
tensor([[ 0.0527,  0.0120, -0.1511,  0.4231, -0.2333]], requires_grad=True))
('fc2.bias', Parameter containing:
tensor([-0.3234], requires_grad=True))
The weights of the Layers:
('fc1.weight', Parameter containing:
tensor([[ 0.4633, -0.2352],[-0.5624,  0.3293],[-0.7048, -0.2369],[ 0.2135, -0.0914],[-0.0686,  0.4108]], requires_grad=True))
('fc1.bias', Parameter containing:
tensor([0.2812, 0.5648, 0.1301, 0.3838, 0.0902], requires_grad=True))
('fc2.weight', Parameter containing:
tensor([[ 0.0630,  0.0056, -0.1547,  0.4293, -0.2353]], requires_grad=True))
('fc2.bias', Parameter containing:
tensor([-0.3185], requires_grad=True))
('fc1.weight', Parameter containing:
tensor([[ 0.4633, -0.2352],[-0.5624,  0.3293],[-0.7048, -0.2369],[ 0.2135, -0.0914],[-0.0686,  0.4108]], requires_grad=True))
('fc1.bias', Parameter containing:
tensor([0.2812, 0.5648, 0.1301, 0.3838, 0.0902], requires_grad=True))
('fc2.weight', Parameter containing:
tensor([[ 0.0630,  0.0056, -0.1547,  0.4293, -0.2353]], requires_grad=True))
('fc2.bias', Parameter containing:
tensor([-0.3185], requires_grad=True))

可视化训练和验证集上的主准确率和loss变化:

plot(runner, "fw-zero.pdf")

从输出结果看,二分类准确率为50%左右,说明模型没有学到任何内容。

为了避免对称权重现象,可以使用高斯分布或均匀分布初始化神经网络的参数。

高斯分布和均匀分布采样的实现和可视化代码如下:

# 使用'torch.normal'实现高斯分布采样,其中'mean'为高斯分布的均值,'std'为高斯分布的标准差,'shape'为输出形状
gausian_weights = torch.normal(mean=0.0, std=1.0, size=[10000])
# 使用'torch.uniform'实现在[min,max)范围内的均匀分布采样,其中'shape'为输出形状
uniform_weights = torch.Tensor(10000)
uniform_weights.uniform_(-1,1)
print(uniform_weights)
# 绘制两种参数分布
plt.figure()
plt.subplot(1,2,1)
plt.title('Gausian Distribution')
plt.hist(gausian_weights, bins=200, density=True, color='#f19ec2')
plt.subplot(1,2,2)
plt.title('Uniform Distribution')
plt.hist(uniform_weights, bins=200, density=True, color='#e4007f')
plt.savefig('fw-gausian-uniform.pdf')
plt.show()

2.梯度消失问题

在神经网络的构建过程中,随着网络层数的增加,理论上网络的拟合能力也应该是越来越好的。但是随着网络变深,参数学习更加困难,容易出现梯度消失问题。

由于Sigmoid型函数的饱和性,饱和区的导数更接近于0,误差经过每一层传递都会不断衰减。当网络层数很深时,梯度就会不停衰减,甚至消失,使得整个网络很难训练,这就是所谓的梯度消失问题。
在深度神经网络中,减轻梯度消失问题的方法有很多种,一种简单有效的方式就是使用导数比较大的激活函数,如:ReLU。

下面通过一个简单的实验观察前馈神经网络的梯度消失现象和改进方法。

2.1模型构建·

定义一个前馈神经网络,包含4个隐藏层和1个输出层,通过传入的参数指定激活函数。

# 定义多层前馈神经网络
class Model_MLP_L5(nn.Module):def __init__(self, input_size, output_size, act='sigmoid', w_init=torch.normal(mean=torch.tensor(0.0), std=torch.tensor(0.01)), b_init=torch.tensor(1.0)):super(Model_MLP_L5, self).__init__()self.fc1 = torch.nn.Linear(input_size, 3)self.fc2 = torch.nn.Linear(3, 3)self.fc3 = torch.nn.Linear(3, 3)self.fc4 = torch.nn.Linear(3, 3)self.fc5 = torch.nn.Linear(3, output_size)# 定义网络使用的激活函数if act == 'sigmoid':self.act = F.sigmoidelif act == 'relu':self.act = F.reluelif act == 'lrelu':self.act = F.leaky_reluelse:raise ValueError("Please enter sigmoid relu or lrelu!")# 初始化线性层权重和偏置参数self.init_weights(w_init, b_init)# 初始化线性层权重和偏置参数def init_weights(self, w_init, b_init):# 使用'named_sublayers'遍历所有网络层for n, m in self.named_parameters():# 如果是线性层,则使用指定方式进行参数初始化if isinstance(m, nn.Linear):w_init(m.weight)b_init(m.bias)def forward(self, inputs):outputs = self.fc1(inputs)outputs = self.act(outputs)outputs = self.fc2(outputs)outputs = self.act(outputs)outputs = self.fc3(outputs)outputs = self.act(outputs)outputs = self.fc4(outputs)outputs = self.act(outputs)outputs = self.fc5(outputs)outputs = F.sigmoid(outputs)return outputs

2.2使用Sigmoid型函数进行训练

使用Sigmoid型函数作为激活函数,为了便于观察梯度消失现象,只进行一轮网络优化。

定义梯度打印函数:

def print_grads(runner):# 打印每一层的权重的模print('The gradient of the Layers:')for name, item in runner.model.named_parameters():if(len(item.size())==2):print(name, torch.norm(input=item, p=2))
torch.manual_seed(102)
# 学习率大小
lr = 0.01# 定义网络,激活函数使用sigmoid
model = Model_MLP_L5(input_size=2, output_size=1, act='sigmoid')# 定义优化器
optimizer = torch.optim.SGD(model.parameters(), lr)# 定义损失函数,使用交叉熵损失函数
loss_fn = F.binary_cross_entropy# 定义评价指标
metric = accuracy# 指定梯度打印函数
custom_print_log = print_grads

实例化Runner类,并传入训练配置。代码实现如下:

runner = RunnerV2_2(model, optimizer, metric, loss_fn)

模型训练,打印网络每层梯度值的ℓ2范数。代码实现如下:

# 启动训练
runner.train([X_train, y_train], [X_dev, y_dev],num_epochs=1, log_epochs=None,save_path="best_model.pdparams",custom_print_log=custom_print_log)

执行结果:

The gradient of the Layers:
fc1.weight tensor(1.0447, grad_fn=<NormBackward1>)
fc2.weight tensor(1.2803, grad_fn=<NormBackward1>)
fc3.weight tensor(0.8694, grad_fn=<NormBackward1>)
fc4.weight tensor(1.0071, grad_fn=<NormBackward1>)
fc5.weight tensor(0.5389, grad_fn=<NormBackward1>)
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.53125

梯度经过神经元每一层的传递都会不断衰减,当传递到第一层时,梯度几乎完全消失。

2.3使用ReLU函数进行模型训练

torch.manual_seed(102)
lr = 0.01  # 学习率大小# 定义网络,激活函数使用relu
model =Model_MLP_L5(input_size=2, output_size=1, act='relu')# 定义优化器
optimizer = torch.optim.SGD(model.parameters(), lr)# 定义损失函数
# 定义损失函数,这里使用交叉熵损失函数
loss_fn = F.binary_cross_entropy# 定义评估指标
metric = accuracy# 实例化Runner
runner = RunnerV2_2(model, optimizer, metric, loss_fn)# 启动训练
runner.train([X_train, y_train], [X_dev, y_dev],num_epochs=1, log_epochs=None,save_path="best_model.pdparams",custom_print_log=custom_print_log)

执行结果:

The gradient of the Layers:
fc1.weight tensor(0.8176, grad_fn=<NormBackward1>)
fc2.weight tensor(0.9802, grad_fn=<NormBackward1>)
fc3.weight tensor(0.9874, grad_fn=<NormBackward1>)
fc4.weight tensor(1.0451, grad_fn=<NormBackward1>)
fc5.weight tensor(0.4850, grad_fn=<NormBackward1>)
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.53125

上图展示了使用不同激活函数时,网络每层梯度值的ℓ2范数情况。从结果可以看到,5层的全连接前馈神经网络使用Sigmoid型函数作为激活函数时,梯度经过每一个神经层的传递都会不断衰减,最终传递到第一个神经层时,梯度几乎完全消失。改为ReLU激活函数后,梯度消失现象得到了缓解,每一层的参数都具有梯度值。

3.死亡 ReLU 问题

ReLU激活函数可以一定程度上改善梯度消失问题,但是ReLU函数在某些情况下容易出现死亡 ReLU问题,使得网络难以训练。这是由于当x<0时,ReLU函数的输出恒为0。在训练过程中,如果参数在一次不恰当的更新后,某个ReLU神经元在所有训练数据上都不能被激活(即输出为0),那么这个神经元自身参数的梯度永远都会是0,在以后的训练过程中永远都不能被激活。而一种简单有效的优化方式就是将激活函数更换为Leaky ReLU、ELU等ReLU的变种。

3.1使用ReLU进行模型训练

使用上面定义的多层全连接前馈网络进行实验,使用ReLU作为激活函数,观察死亡ReLU现象和优化方法。当神经层的偏置被初始化为一个相对于权重较大的负值时,可以想像,输入经过神经层的处理,最终的输出会为负值,从而导致死亡ReLU现象。

# 定义网络,并使用较大的负值来初始化偏置
model = Model_MLP_L5(input_size=2, output_size=1, act='relu', b_init=torch.tensor(-0.8))

实例化Runner类,启动模型训练,打印网络每层梯度值的ℓ2范数

# 实例化Runner类
runner = RunnerV2_2(model, optimizer, metric, loss_fn)# 启动训练
runner.train([X_train, y_train], [X_dev, y_dev],num_epochs=1, log_epochs=0,save_path="best_model.pdparams",custom_print_log=custom_print_log)

执行结果:

The gradient of the Layers:
linear_14 0.0
linear_15 0.0
linear_16 0.0
linear_17 0.0
linear_18 0.0
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.53750

从输出结果可以发现,使用 ReLU 作为激活函数,当满足条件时,会发生死亡ReLU问题,网络训练过程中 ReLU 神经元的梯度始终为0,参数无法更新。

针对死亡ReLU问题,一种简单有效的优化方式就是将激活函数更换为Leaky ReLU、ELU等ReLU 的变种。接下来,观察将激活函数更换为 Leaky ReLU时的梯度情况。

3.2使用Leaky ReLU进行模型训练

将激活函数更换为Leaky ReLU进行模型训练,观察梯度情况

# 重新定义网络,使用Leaky ReLU激活函数
model =  Model_MLP_L5(input_size=2, output_size=1, act='lrelu', b_init=torch.tensor(-0.8))# 实例化Runner类
runner = RunnerV2_2(model, optimizer, metric, loss_fn)# 启动训练
runner.train([X_train, y_train], [X_dev, y_dev],num_epochs=1, log_epochps=None,save_path="best_model.pdparams",custom_print_log=custom_print_log)

执行结果:

The gradient of the Layers:
fc1.weight tensor(0.7548, grad_fn=<NormBackward1>)
fc2.weight tensor(1.1612, grad_fn=<NormBackward1>)
fc3.weight tensor(1.0495, grad_fn=<NormBackward1>)
fc4.weight tensor(1.0805, grad_fn=<NormBackward1>)
fc5.weight tensor(0.5799, grad_fn=<NormBackward1>)
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.4965
[Train] epoch: 0/1, loss: 0.7061845328474692Process finished with exit code 0

从输出结果可以看到,将激活函数更换为Leaky ReLU后,死亡ReLU问题得到了改善,梯度恢复正常,参数也可以正常更新。但是由于 Leaky ReLU 中,x<0时的斜率默认只有0.01,所以反向传播时,随着网络层数的加深,梯度值越来越小。如果想要改善这一现象,将 Leaky ReLU 中,x<0时的斜率调大即可。

4.Git、GitHub

4.1Git是什么

Git 是一个免费和开源的分布式版本控制系统,也是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件,旨在以速度和效率处理从小型到大型项目的所有内容。
Git 易于学习,占用空间小,性能快如闪电。 它优于 SCM 工具,如 Subversion、CVS、Perforce 和 ClearCase,具有廉价的本地分支、方便的暂存区域和多个工作流等功能。
通过学习Git的使用,我们能够更好地进行版本控制和项目协作。

Git的功能特性

4.2GitHub

GitHub作为免费的远程仓库,如果是个人的开源项目,放到GitHub上是完全没有问题的。其实GitHub还是一个开源协作社区,通过GitHub,既可以让别人参与你的开源项目,也可以参与别人的开源项目。

在GitHub出现以前,开源项目开源容易,但让广大人民群众参与进来比较困难,因为要参与,就要提交代码,而给每个想提交代码的群众都开一个账号那是不现实的,因此,群众也仅限于报个bug,即使能改掉bug,也只能把diff文件用邮件发过去,很不方便。

但是在GitHub上,利用Git极其强大的克隆和分支功能,广大人民群众真正可以第一次自由参与各种开源项目了。

在github.com可以注册,输入个人信息,邮箱等等。

这个我是第一次使用,目前还没有什么具体的心得,大家可以一起慢慢了解。

5.ref

NNDL 实验4(上) - HBU_DAVID - 博客园

NNDL 实验4(下) - HBU_DAVID - 博客园

2.5. 自动微分 — 动手学深度学习 2.0.0-beta1 documentation

4.7. 前向传播、反向传播和计算图 — 动手学深度学习 2.0.0-beta1 documentation

Git教程 - 廖雪峰的官方网站

使用GitHub - 廖雪峰的官方网站

实验五 前馈神经网络(2)自动梯度计算 优化问题相关推荐

  1. NNDL 实验五 前馈神经网络(2)自动梯度计算 优化问题

    目录 4.3 自动梯度计算 1. 使用pytorch的预定义算子来重新实现二分类任务.(必做) 4.3.1 利用预定义算子重新实现前馈神经网络 4.3.2 完善Runner类 4.3.3 模型训练 4 ...

  2. NNDL实验五 前馈神经网络(2)自动梯度计算 优化问题

    4.3 自动梯度计算 虽然我们能够通过模块化的方式比较好地对神经网络进行组装,但是每个模块的梯度计算过程仍然十分繁琐且容易出错.在深度学习框架中,已经封装了自动梯度计算的功能,我们只需要聚焦模型架构, ...

  3. HBU-NNDL 实验五 前馈神经网络(3)鸢尾花分类

    目录 深入研究鸢尾花数据集 4.5 实践:基于前馈神经网络完成鸢尾花分类 4.5.1 小批量梯度下降法 4.5.1.1 数据分组 4.5.2 数据处理 4.5.2.2 用DataLoader进行封装 ...

  4. NNDL 实验五 前馈神经网络(1)二分类任务

    目录 前言 一.4.1 神经元 4.1.1 净活性值 [思考题]加权相加与仿射变换之间有什么区别和联系? 4.1.2 激活函数 动手实现<神经网络与深度学习>4.1节中提到的其他激活函数: ...

  5. 神经网络与深度学习(五)前馈神经网络(2)自动梯度计算和优化问题

    注:本次使用的数据集依旧是前两章的Moon1000数据集  from nndl.dataset import make_moons [详细代码见 神经网络与深度学习(五)前馈神经网络(1)--二分类任 ...

  6. MATLAB实现智能计算方法实验:实验五 Hopfield神经网络

    资源链接 MATLAB实现智能计算方法课程所有实验代码资源链接为:MATLAB实现智能计算方法课程所有实验代码资源 实验汇总 MATLAB实现智能计算方法课程所有实验汇总博客链接为:MATLAB实现智 ...

  7. mxnet基础到提高(38)-自动梯度计算

    from mxnet import nd from mxnet import autograd import math x=nd.array([[10,20],[1,2]]) x.attach_gra ...

  8. NLP(七):前馈神经网络基础回顾(NN模型及其正则化,dropout,各种梯度求解算法等模型优化策略)

    目录 1.前馈神经网络及其相关概念 2.前馈神经网络的Tensorflow实现 2.1tensorflow的图模式下的NN实现 2.2tf.keras实现 3.1Sigmoid 3.2Tanh​ 3. ...

  9. 神经网络与深度学习(五)前馈神经网络(3)鸢尾花分类

    目录 4.5实践:基于前馈神经网络完成鸢尾花分类 深入研究鸢尾花数据集 4.5.1 小批量梯度下降法 4.5.1.1 数据分组 4.5.2 数据处理 4.5.2.2 用DataLoader进行封装 4 ...

最新文章

  1. Chrome与chromedriver版本对应
  2. JavaWeb(五)之JSTL标签库
  3. BK资本管理公司创始人阐述BCH接下来的三种“催化剂”
  4. C++ 字符串中小写字母转换成大写字母
  5. 正则表达式——获取指定IP的物理地址
  6. 华为5G英国首秀,BBC主持人震惊了!到底网速有多快?
  7. iptables基本用法和linux网络相关
  8. 代码走查和代码审查_代码审查是个好主意的其他原因
  9. 图书馆管理系统mysql的创建_简单的图书馆管理系统数据库设计
  10. 摄影基础知识——白平衡
  11. python随笔12(传递任意数量的实参)
  12. 查看ftp服务器里的文件,查看ftp服务器所有文件
  13. Cannot find current proxy: Set ‘exposeProxy‘ property on Advised to ‘true‘ to make it available,and.
  14. 短线王的盯盘宝怎么样_股票盯盘系统app怎么样
  15. 4-AT命令交互之-COPS选择营运商
  16. 中大计算机保研复试,过来人分享:平凡的我如何成功保研中山大学?
  17. BIM模型文件下载——精装修样板间模型
  18. Java代码实现非对称加密RSA算法示例
  19. 工业CT分辨率——空间分辨率和密度分辨率
  20. Django 中间件之限制ip访问次数 和 黑名单

热门文章

  1. 人才早已过剩,计算机专业还值得报吗?
  2. 【keil5】keil官网PACK包不显示的解决方案
  3. 实验17:DS18B20温度传感器
  4. Cocos Creator Spine动画产生位移时,动画位置获取问题 (root的使用)
  5. arcgis属性表选择两个条件_arcgis中给属性表字段按条件批量赋值
  6. Matlab求解线性方程组的三种方法(wzl)
  7. scipy.signal信号处理的库(笔记06)
  8. 2019-8-6-在-Gitlab-开启-MatterMost-机器人
  9. 达梦8-单实例安装部署
  10. 怎样培养自己的自信心?