目的:
神经网络的训练有时候可能希望保持一部分的网络参数不变,只对其中一部分的参数进行调整,或者训练部分分支网络,并不让其梯度对主网络的梯度造成影响,这个时候我妈就需要使用detach()函数来切断一些分支的反向传播。
tensor.detach()
tensor.detach()会返回一个新的tensor,从当前的计算图中分离下来,但是仍指向原变量的存放位置,不同之处只是requires_grad为false, 得到的这个tensor永远不需要计算其梯度,不具有grad。
即使之后重新将它的requires_grad置为true, 它也不会具有梯度grad, 这样我们就会继续使用这个新的tensor进行计算,后面当我们进行反向传播时,到该调用detach()的tensor就会停止,不能再继续向前进行传播。
注意:使用detach返回的tensor和原始的tensor共享一个内存,即一个修改则另一个也会跟着改变
import torch
a = torch.tensor([1, 2, 3.], requires_grad=True)
print(a)
print(a.grad)
out = a.sigmoid()
out.sum().backward()
print(a.grad)
import torch
a = torch.tensor([1, 2, 3.], requires_grad=True)
print(a.grad)
out = a.sigmoid()
print(out)
#添加detach(),c的requires_grad为False
c = out.detach()
print("c--> \t",c)
#这时候没有对c进行更改,所以并不会影响backward()
out.sum().backward()
print("a.grad-->",a.grad)
参考博客
y.backward(torch.ones_like(x), retain_graph=True)
因为这里的y是一个向量,x也是向量,向量y对向量x的求导需要
pytorch在求导的过程中,分为下面两种情况:
reduction = 'none’表示直接返回n分样本的loss,是一个张量
reduction=‘none’)参考博客
原则上,在我们确定所有的超参数之前,我们不希望用到测试集。**如果我们在模型选择过程中使用测试数据,可能会有过拟合测试数据的风险。**所以不能依靠测试数据和训练数据进行模型选择。所以将数据集划分为训练集,测试集,验证集。但测试集和验证集之间的边界模糊,在《动手学深度学习》这本书中两者不做区分。
欠拟合
训练误差和验证误差都很严重,但它们之间仅有一点差距。即模型过于简单,无法捕获试图学习的模式
过拟合
训练误差很小,但是验证误差比较大
help(d2l.synthetic_data)
这里也可以用d2l.synthetic_data?或者d2l.synthetic_data??来查询函数相关信息
已知线性回归的损失函数为:
L ( w , b ) = 1 n ∑ i = 1 n l ( i ) ( w , b ) = 1 n ∑ i = 1 n 1 2 ( w ⊤ x ( i ) + b − y ( i ) ) 2 . L(\mathbf{w}, b)=\frac{1}{n} \sum_{i=1}^{n} l^{(i)}(\mathbf{w}, b)=\frac{1}{n} \sum_{i=1}^{n} \frac{1}{2}\left(\mathbf{w}^{\top} \mathbf{x}^{(i)}+b-y^{(i)}\right)^{2} . L(w,b)=n1i=1∑nl(i)(w,b)=n1i=1∑n21(w⊤x(i)+b−y(i))2.
我们希望找到一组参数 ( w ∗ , b ∗ ) (\boldsymbol{w^{*}},b^{*}) (w∗,b∗)这组参数能最小化在所有训练样本上的总损失。如下式:
w ∗ , b ∗ = argmin w , b L ( w , b ) \mathbf{w}^{*}, b^{*}=\underset{\mathbf{w}, b}{\operatorname{argmin}} L(\mathbf{w}, b) w∗,b∗=w,bargminL(w,b)
通过求偏导可以得到解析解为:
w ∗ = ( X T X ) − 1 X T y \boldsymbol{w^{*}=\left(X^{T}X\right)^{-1}X^{T}y} w∗=(XTX)−1XTy
随机梯度下降法来求解 w ∗ , b ∗ \boldsymbol{w^{*}},b^{*} w∗,b∗
我们用下面的数学公式来表示这一更新过程( ∂ \partial ∂ 表示偏导数):
( w , b ) ← ( w , b ) − η ∣ B ∣ ∑ i ∈ B ∂ ( w , b ) l ( i ) ( w , b ) (\mathbf{w}, b) \leftarrow(\mathbf{w}, b)-\frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{(\mathbf{w}, b)} l^{(i)}(\mathbf{w}, b) (w,b)←(w,b)−∣B∣ηi∈B∑∂(w,b)l(i)(w,b)
总结一下,算法的步骤如下: (1)初始化模型参数的值,如随机初始化; (2)从数据集中随机抽取小批量样本且在负梯度的方向上更新参数,并不断迭代这一步骤。 对于平方损失和仿射变换,我们可以明确地写成如下形式:
w ← w − η ∣ B ∣ ∑ i ∈ B ∂ w l ( i ) ( w , b ) = w − η ∣ B ∣ ∑ i ∈ B x ( i ) ( w ⊤ x ( i ) + b − y ( i ) ) , b ← b − η ∣ B ∣ ∑ i ∈ B ∂ b l ( i ) ( w , b ) = b − η ∣ B ∣ ∑ i ∈ B ( w ⊤ x ( i ) + b − y ( i ) ) \begin{aligned} \mathbf{w} & \leftarrow \mathbf{w}-\frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{\mathbf{w}} l^{(i)}(\mathbf{w}, b)=\mathbf{w}-\frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \mathbf{x}^{(i)}\left(\mathbf{w}^{\top} \mathbf{x}^{(i)}+b-y^{(i)}\right), \\ b \leftarrow b-\frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{b} l^{(i)}(\mathbf{w}, b)=b-\frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}}\left(\mathbf{w}^{\top} \mathbf{x}^{(i)}+b-y^{(i)}\right) \end{aligned} wb←b−∣B∣ηi∈B∑∂bl(i)(w,b)=b−∣B∣ηi∈B∑(w⊤x(i)+b−y(i))←w−∣B∣ηi∈B∑∂wl(i)(w,b)=w−∣B∣ηi∈B∑x(i)(w⊤x(i)+b−y(i)),
其中, η \eta η 表示学习率learning rate, ∣ B ∣ \mid \mathcal{B}\mid ∣B∣表示每个小批量中的样本数,这也称为批量大小(batch size)。批量大小和学习率的值通常是手动预先指定,而不是通过模型训练得到的。 这些可以调整但不在训练过程中更新的参数称为超参数
当模型过于复杂的时候会出现过拟合现象,可以用正则化方法来解决过拟合问题。
那如何控制模型的容量呢?
method1: 控制参数的个数
method2:限制参数值的选择范围来控制模型容量 ∣ ∣ w ∣ ∣ 2 < = θ ||\boldsymbol{w}||^{2}<= \theta ∣∣w∣∣2<=θ
小的 θ \theta θ意味着更强的正则项
权重衰减∈正则化的一种方式:
L ( w , b ) + λ 2 ∥ w ∥ 2 L(\mathbf{w}, b)+\frac{\lambda}{2}\|\mathbf{w}\|^{2} L(w,b)+2λ∥w∥2
对于 λ = 0 \lambda = 0 λ=0,我们恢复了原来的损失函数。 对于 λ > 0 \lambda > 0 λ>0,我们限制 ∣ ∣ w ∣ ∣ ||\boldsymbol{w}|| ∣∣w∣∣的大小。这里我们仍然除以2:当我们取一个二次函数的导数时, 2和 1 2 \frac{1}{2} 21会抵消,以确保更新表达式看起来既漂亮又简单.
通过平方 L 2 L_{2} L2范数,我们去掉平方根,留下权重向量每个分量的平方和。 这使得惩罚的导数很容易计算:导数的和等于和的导数。
L 2 L_{2} L2正则化回归的小批量随机梯度下降更新如下式:
w ← ( 1 − η λ ) w − η ∣ B ∣ ∑ i ∈ B x ( i ) ( w ⊤ x ( i ) + b − y ( i ) ) \mathbf{w} \leftarrow(1-\eta \lambda) \mathbf{w}-\frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \mathbf{x}^{(i)}\left(\mathbf{w}^{\top} \mathbf{x}^{(i)}+b-y^{(i)}\right) w←(1−ηλ)w−∣B∣ηi∈B∑x(i)(w⊤x(i)+b−y(i))
λ \lambda λ为正则化常数, η \eta η为learning rate
求和目的:
之前自动求导那节讲到了向量求梯度比较麻烦 都通过sum()转化为标量再求梯度
求和之后后面会除batch_size,可以计算均值
求和本身让 l l l 以标量的形式表现出来。所有的梯度也叠加了嘛,所以sgd里面除了batch_size
参考博客,这个人的学习笔记写的很好
def sgd(params, lr, batch_size): #@save
"""小批量随机梯度下降"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
1)with torch.no_grad():
更新时不要计算梯度
2)param.grad.zero_()
pytorch会不断的累加变量的梯度,所以每更新一次参数,就要让其对应的梯度清零
3)for param in params:
对每个参数进行计算(可能是w,可能是b)
4)
不除以batch_size损失函数就是平方和误差
我们只要误差就行了
自动微分
动机:
一个好的模型需要对输入数据的扰动鲁棒
那如何加入这种噪声呢?无偏向的方式注入噪声
一种想法是以一种**无偏向(unbiased)**的方式注入噪声。 这样在固定住其他层时,每一层的期望值等于没有噪音时的值。
将高斯噪声添加到线性模型的输入中。 在每次训练迭代中,他将从均值为零的分布 ϵ ∼ N ( 0 , σ 2 ) \epsilon \sim \mathcal{N}\left(0, \sigma^{2}\right) ϵ∼N(0,σ2)采样噪声添加到输入 x \mathrm{x} x, 从而产生扰动点 x ′ = x + ϵ \mathrm{x_{'}=x+\epsilon} x′=x+ϵ, 预期是 E [ x ′ ] = x E[\mathrm{x_{'}}]=\mathrm{x} E[x′]=x。
在标准暂退法正则化中,通过按保留(未丢弃)的节点的分数进行规范化来消除每一层的偏差。 换言之,每个中间活性值以暂退概率由随机变量替换,如下所示:
h ′ = { 0 概率为 p h 1 − p 其他情况 h^{\prime}=\left\{\begin{array}{ll} 0 & \text { 概率为 } p \\ \frac{h}{1-p} & \text { 其他情况 } \end{array}\right. h′={01−ph 概率为 p 其他情况
根据此模型的设计,其期望值保持不变,即 E [ h ′ ] = h E[\mathrm{h_{'}}]=\mathrm{h} E[h′]=h。
实现 dropout_layer 函数
从均匀分布中抽取样本,样本数与这层神经网络的维度一致。 实现 dropout_layer 函数, 该函数以dropout的概率丢弃张量输入X中的元素, 如上所述重新缩放剩余部分:将剩余部分除以1.0-dropout(保证了这一层虽然经过dropout操作,但是期望不变)。
import torch
from torch import nn
from d2l import torch as d2l
def dropout_layer(X, dropout):
assert 0 <= dropout <= 1
# 在本情况中,所有元素都被丢弃
if dropout == 1:
return torch.zeros_like(X)
# 在本情况中,所有元素都被保留
if dropout == 0:
return X
# torch.rand返回服从[0,1)之间的均匀分布的初始化后的tensor
print(torch.rand(X.shape))
mask = (torch.rand(X.shape) > dropout).float()
return mask * X / (1.0 - dropout)
注意,如果dropout=0.5,那只是说有0.5的概率来决定一个神经元的去留,而不是说这一层一定只有一半的神经元留下