每一次反向传播前,都要把梯度清零:当GPU显存较少时,又想要调大batch-size,此时就可以利用PyTorch默认进行梯度累加的性质来进行backward。
梯度累加就是,每次获取1个batch的数据,计算1次梯度,梯度不清空,不断累加,累加一定次数后,根据累加的梯度更新网络参数,然后清空梯度,进行下一次循环。
一定条件下,batchsize越大训练效果越好,梯度累加则实现了batchsize的变相扩大,如果accumulation_steps为8,则batchsize '变相' 扩大了8倍,是我们这种乞丐实验室解决显存受限的一个不错的trick,使用时需要注意,学习率也要适当放大。
——知乎Pascal
加入ReLU激活函数,分类的准确率显著提高:非线性变换。
为什么激活函数是非线性的?如果不用激励函数(相当于激励函数是f(x)=x),在这种情况下,每一层的输出都是上一层的线性函数,无论神经网络有多少层,输出都是输入的线性组合,这与一个隐藏层的效果相当(这种情况就是多层感知机MPL)。但当我们需要进行深度神经网络训练(多个隐藏层)的时候,如果激活函数仍然使用线性的,多层的隐藏函数与一层的隐藏函数作用的相当的,就失去了深度神经网络的意义,所以引入非线性函数作为激活函数。
一般在描述神经网络层数时不包括输入层。

2.1.1 构建线性模型分类
learning_rate = 1e-3
lambda_l2 = 1e-5
# nn 包用来创建线性模型
# 每一个线性模型都包含 weight 和 bias
model = nn.Sequential(
nn.Linear(D, H),
nn.Linear(H, C)
)
model.to(device) # 把模型放到GPU上
# nn 包含多种不同的损失函数,这里使用的是交叉熵(cross entropy loss)损失函数
criterion = torch.nn.CrossEntropyLoss()
# 这里使用 optim 包进行随机梯度下降(stochastic gradient descent)优化
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay=lambda_l2)
# 开始训练
for t in range(1000):
# 把数据输入模型,得到预测结果
y_pred = model(X)
# 计算损失和准确率
loss = criterion(y_pred, Y)
score, predicted = torch.max(y_pred, 1)
acc = (Y == predicted).sum().float() / len(Y)
print('[EPOCH]: %i, [LOSS]: %.6f, [ACCURACY]: %.3f' % (t, loss.item(), acc))
display.clear_output(wait=True)
# 反向传播前把梯度置 0
optimizer.zero_grad()
# 反向传播优化
loss.backward()
# 更新全部参数
optimizer.step()
[EPOCH]: 999, [LOSS]: 0.861541, [ACCURACY]: 0.504

2.1.2 构建两层神经网络分类
learning_rate = 1e-3
lambda_l2 = 1e-5
# 这里可以看到,和上面模型不同的是,在两层之间加入了一个 ReLU 激活函数
model = nn.Sequential(
nn.Linear(D, H),
nn.ReLU(),
nn.Linear(H, C)
)
model.to(device)
# 下面的代码和之前是完全一样的,这里不过多叙述
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate, weight_decay=lambda_l2) # built-in L2
# 训练模型,和之前的代码是完全一样的
for t in range(1000):
y_pred = model(X)
loss = criterion(y_pred, Y)
score, predicted = torch.max(y_pred, 1)
acc = ((Y == predicted).sum().float() / len(Y))
print("[EPOCH]: %i, [LOSS]: %.6f, [ACCURACY]: %.3f" % (t, loss.item(), acc))
display.clear_output(wait=True)
# zero the gradients before running the backward pass.
optimizer.zero_grad()
# Backward pass to compute the gradient
loss.backward()
# Update params
optimizer.step()
[EPOCH]: 999, [LOSS]: 0.183681, [ACCURACY]: 0.943

2.2 回归分析
两点思考:
对比三类激活函数:
函数
优点
缺点
Sigmoid
Sigmoid函数是深度学习领域开始时使用频率最高的激活函数,它是便于求导的平滑函数,能够将输出值压缩到0-1范围之内
容易出现梯度消失;输出不是zero-centered;幂运算相对耗时
Tanh
全程可导;输出区间为-1到1;解决了zero-centered的输出问题
梯度消失的问题和幂运算的问题仍然存在
ReLU
解决了梯度消失的问题 (在正区间);计算速度非常快,只需要判断输入是否大于0;收敛速度远快于Sigmoid和Tanh;ReLU会使一部分神经元的输出为0,这样就造成了网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生
输出不是zero-centered;某些神经元可能永远不会被激活,导致相应的参数永远不能被更新(Dead ReLU Problem)
有两个主要原因可能导致Dead ReLU Problem:
非常不幸的参数初始化,这种情况比较少见
学习速率太高导致在训练过程中参数更新太大,不幸使网络进入这种状态
解决方法:可以采用Xavier初始化方法,以及避免将学习速率设置太大或使用adagrad等自动调节学习速率的算法。
ReLU与Tanh表现效果不同:前者是分段的线性函数,而后者是连续且平滑的回归。
The former is a piecewise linear function, whereas the latter is a continuous and smooth regression.