此博文是关于pytorch中文教程中手动在网络中实现前向传播和反向传播部分的代码解析。先贴上教程来源与代码:
教程为:https://pytorch.apachecn.org/docs/0.3/pytorch_with_examples_pytorch-tensors.html代码如下:
import torch dtype = torch.FloatTensor # dtype = torch.cuda.FloatTensor # 取消注释以在GPU上运行 # N 批量大小; D_in是输入尺寸; # H是隐藏尺寸; D_out是输出尺寸. N, D_in, H, D_out = 64, 1000, 100, 10 # 创建随机输入和输出数据 x = torch.randn(N, D_in).type(dtype) y = torch.randn(N, D_out).type(dtype) # 随机初始化权重 w1 = torch.randn(D_in, H).type(dtype) w2 = torch.randn(H, D_out).type(dtype) learning_rate = 1e-6 for t in range(500): # 正向传递:计算预测y h = x.mm(w1) h_relu = h.clamp(min=0) y_pred = h_relu.mm(w2) # 计算并打印loss loss = (y_pred - y).pow(2).sum() print(t, loss) # 反向传播计算关于损失的w1和w2的梯度 grad_y_pred = 2.0 * (y_pred - y) grad_w2 = h_relu.t().mm(grad_y_pred) grad_h_relu = grad_y_pred.mm(w2.t()) grad_h = grad_h_relu.clone() grad_h[h < 0] = 0 grad_w1 = x.t().mm(grad_h) # 使用梯度下降更新权重 w1 -= learning_rate * grad_w1 w2 -= learning_rate * grad_w2这是模拟的一个一层的全连接网络,包含两个参数矩阵,一个输入,一个输出。
首先创建了输入矩阵x与输出矩阵y,这里就是调用了一下torch的随机函数,创建了大小为64*1000的输入和64*10的输出,其中64为batch大小: x = torch.randn(N, D_in).type(dtype) y = torch.randn(N, D_out).type(dtype) 然后是网络的权重矩阵w1与w2,也是调用torch的随机函数创建,大小分别为1000*100和100*10。 w1 = torch.randn(D_in, H).type(dtype) w2 = torch.randn(H, D_out).type(dtype) 接下来是比较重要的正向传播部分,首先使用输入矩阵乘以权重矩阵w1,mm()函数表示矩阵乘法,具体可以点击函数查看官方文档的介绍。然后使用clamp()作为激励函数Relu的实现,将小于0的值都截断。最后再做一次矩阵乘法得到输出y_pred,至此正向传播完成: h = x.mm(w1) # h [64,100] h_relu = h.clamp(min=0) # h_relu [64,100] y_pred = h_relu.mm(w2) # y_pred [62,10] 正向传播之后需要计算误差值,在此处使用的是非一般形式的均方误差,也就是矩阵相减后对每个元素取平方然后再求所有元素的和。 loss = (y_pred - y).pow(2).sum() 下面是最重要的反向传播部分,首先第一行均方误差对y_pred的梯度比较简单,因为对矩阵中的每一个元素来说都有: ∂ L o s s ∂ y _ p r e d i , j = 2 ∗ ( y _ p r e d i , j − y i , j ) \frac {\partial Loss}{\partial y\_pred_{i,j}} = 2*(y\_pred_{i,j}-y_{i,j}) ∂y_predi,j∂Loss=2∗(y_predi,j−yi,j) 因此可以直接使用下面的求导依照公式: g r a d _ y _ p r e d = ∂ L o s s ∂ y _ p r e d = ∂ ( y _ p r e d − Y ) 2 ∂ y _ p r e d = 2 ∗ ( y _ p r e d − Y ) grad\_y\_pred = \frac{\partial Loss}{\partial y\_pred}=\frac{\partial (y\_pred-Y)^2}{\partial y\_pred}=2*(y\_pred-Y) grad_y_pred=∂y_pred∂Loss=∂y_pred∂(y_pred−Y)2=2∗(y_pred−Y) grad_y_pred = 2.0 * (y_pred - y) 然后比较难的地方是w2与h_relu的梯度计算,需要用到矩阵乘法的导数公式,将代码翻译为公式是下面这样的,这里的转换过程涉及到矩阵乘法的梯度运算,具体可以参照这个博文,写得十分详细且直观,在这部分理解上给了我很大的帮助。:g r a d _ W 2 = ∂ L o s s ∂ W 2 = ∂ h _ r e l u × W 2 ∂ W 2 × ∂ L o s s ∂ h _ r e l u × W 2 = h _ r e l u T × g r a d _ y _ p r e d grad\_W2 = \frac {\partial Loss}{\partial W2}=\frac {\partial h\_relu\times W2}{\partial W2}\times \frac{\partial Loss}{\partial h\_relu\times W2}=h\_relu^T \times grad\_y\_pred grad_W2=∂W2∂Loss=∂W2∂h_relu×W2×∂h_relu×W2∂Loss=h_reluT×grad_y_pred g r a d _ h _ r e l u = ∂ L o s s ∂ h _ r e l u = ∂ L o s s ∂ h _ r e l u × W 2 × ∂ h _ r e l u × W 2 ∂ h _ r e l u = g r a d _ y _ p r e d × W 2 T grad\_h\_relu = \frac {\partial Loss}{\partial h\_relu}= \frac{\partial Loss}{\partial h\_relu\times W2}\times \frac {\partial h\_relu\times W2}{\partial h\_relu}=grad\_y\_pred\times W2^T grad_h_relu=∂h_relu∂Loss=∂h_relu×W2∂Loss×∂h_relu∂h_relu×W2=grad_y_pred×W2T
grad_w2 = h_relu.t().mm(grad_y_pred) grad_h_relu = grad_y_pred.mm(w2.t()) 接下来是对激励函数Relu的梯度计算,这个比较简单,因为在大于0的区域h=h_relu,而在小于0的区域直接归零,所以对梯度也做类似操作就可以了,即: g r a d _ h = { g r a d _ h _ r e l u i f g r a d _ h _ r e l u > 0 0 e l s e grad\_h= \begin{cases}grad\_h\_relu &if\ grad\_h\_relu\gt 0\\ 0 &else \end{cases} grad_h={grad_h_relu0if grad_h_relu>0else grad_h = grad_h_relu.clone() grad_h[h < 0] = 0 最后就是计算w1的梯度,这里也是按照矩阵梯度计算公式来算的,和计算w2梯度时类似,就不多说了。 grad_w1 = x.t().mm(grad_h)以上就是使用pytorch手动实现一层神经网络的正向与反向传播全过程,虽然pytorch有自动的反向传播机制(autograd),但是自己手动分析一遍还是能够加深不少理解的。