给出结论:误差反向传播算法,传播的是损失函数对各个参数的偏导数(就是导数),也可以简单理解成梯度(梯度的定义可不是这样的)
大致流程是搭一个简单网络,找到里面的各个参数,训练一次网络并进行误差反向传播,再次查看各个参数,验证结果,以下是详细过程。
这里我们新建一个简化的网络,输入层、隐藏层和输出层都只设置一个节点,并且不加激励函数,因为像relu这种函数会过掉小于0的数据,影响计算
import torch # 搭建神经网络 class Net(torch.nn.Module): def __init__(self, n_feature, n_hidden, n_output): super().__init__() self.hidden = torch.nn.Linear(n_feature, n_hidden) self.predict = torch.nn.Linear(n_hidden, n_output) def forward(self, x): # 前向计算 # x = F.relu(self.hidden(x)) x = self.hidden(x) x = self.predict(x) return x net = Net(1, 1, 1) print(net) # Net( # (hidden): Linear(in_features=1, out_features=1, bias=True) # (predict): Linear(in_features=1, out_features=1, bias=True) # )
查看一下网络里的权重和偏置:
for name, param in net.named_parameters(): print('-->name:', name, '-->grad_requirs:', param.requires_grad, ' -->grad_value:', param.grad) for p in param: print(p) print('----------------')
打印结果:
-->name: hidden.weight -->grad_requirs: True -->grad_value: None tensor([-0.6566], grad_fn=<UnbindBackward>) ---------------- -->name: hidden.bias -->grad_requirs: True -->grad_value: None tensor(-0.1939, grad_fn=<UnbindBackward>) ---------------- -->name: predict.weight -->grad_requirs: True -->grad_value: None tensor([0.9205], grad_fn=<UnbindBackward>) ---------------- -->name: predict.bias -->grad_requirs: True -->grad_value: None tensor(0.1605, grad_fn=<UnbindBackward>) ----------------
可以看到网络里一共4个参数,分别是隐藏层hidden和输出层predict的权重和偏置,这里我们记输入层节点为x、隐藏层节点为h、输入层为p,隐藏层hidden的权重和偏置为w1、b1,输出层predict的权重和偏置为w2(此时w2的值为0.9205)、b2,就可以有以下两个公式(各个层的输出结果):
准备数据,定义优化器、损失函数,并训练一次网络,最后查看参数(这里优化器采用常用的SGD,损失函数采用均方误差MSELoss,且为了方便,x的数据元素也只有一个)
# x 的数据之生成一个,方便计算和观察 x = torch.unsqueeze(torch.linspace(-1, 1, 1), dim=1) y = x.pow(2) + 0.2 * torch.rand(x.size()) # 优化器和损失函数,学习率lr设为0.1 omptimzer = torch.optim.SGD(net.parameters(), lr=0.1) loss_func = torch.nn.MSELoss() prediction = net(x) # 带入x,执行一次网络,得到预测值p loss = loss_func(prediction, y) # 计算均方误差 omptimzer.zero_grad() # 先将所有参数的梯度每次置零 loss.backward() # 计算所有节点的梯度 omptimzer.step() # 用上面的计算值,和学习率更新网络参数 # 打印一些参数 print(f'x:{x.data.numpy()}, y:{y.data.numpy()}') print(f'pred:{prediction.tolist()}, loss:{loss.tolist()}\n') for name, param in net.named_parameters(): print('-->name:', name, '-->grad_requirs:', param.requires_grad, ' -->grad_value:', param.grad) for p in param: print(p) print('----------------')
打印结果:
x:[[-1.]], y:[[1.1921173]] pred:[[0.5864818096160889]], loss:0.3667943775653839 -->name: hidden.weight -->grad_requirs: True -->grad_value: tensor([[1.1150]]) tensor([-0.7681], grad_fn=<UnbindBackward>) ---------------- -->name: hidden.bias -->grad_requirs: True -->grad_value: tensor([-1.1150]) tensor(-0.0824, grad_fn=<UnbindBackward>) ---------------- -->name: predict.weight -->grad_requirs: True -->grad_value: tensor([[-0.5605]]) tensor([0.9766], grad_fn=<UnbindBackward>) ---------------- -->name: predict.bias -->grad_requirs: True -->grad_value: tensor([-1.2113]) tensor(0.2817, grad_fn=<UnbindBackward>) ----------------
可以看到 -->grad_value 后跟着一个tensor值,这个值就是当前变量的梯度(w2的梯度值为-0.5605,w2更新之后的值为0.9766),下面开始验证:
没有训练之前各个参数的值为:
开始验证梯度反向传播过程:
到这里就已经结束了,其实可以看到往后传播的是损失函数对各个参数的梯度,而不是计算出来的误差值loss(均方误差),当然也只验证了这一例子,如果有错,可在评论区讨论
其实后面可以再添加一段训练网络的代码(将omptimzer.zero_grad()注释掉),就可以验证,每次计算的梯度是会累加到grad属性上来,这也就说明了,为什么在每一轮训练是都要就梯度置0了,这里附上代码,供需要的人验证
#!/usr/bin/env python import torch # 搭建神经网络 class Net(torch.nn.Module): def __init__(self, n_feature, n_hidden, n_output): super().__init__() self.hidden = torch.nn.Linear(n_feature, n_hidden) self.predict = torch.nn.Linear(n_hidden, n_output) def forward(self, x): # 前向计算 # x = F.relu(self.hidden(x)) x = self.hidden(x) x = self.predict(x) return x # 打印网络结构 net = Net(1, 1, 1) print(net) # 查看一下网络里的权重和偏置 for name, param in net.named_parameters(): print('-->name:', name, '-->grad_requirs:', param.requires_grad, ' -->grad_value:', param.grad) for p in param: print(p) print('----------------') # 生成数据 # x 的数据之生成一个,方便计算和观察 x = torch.unsqueeze(torch.linspace(-1, 1, 1), dim=1) y = x.pow(2) + 0.2 * torch.rand(x.size()) # 优化器和损失函数,学习率lr设为0.1 omptimzer = torch.optim.SGD(net.parameters(), lr=0.1) loss_func = torch.nn.MSELoss() ###################################################################### # 验证反向传播 prediction = net(x) # 带入x,执行一次网络,得到预测值p loss = loss_func(prediction, y) # 计算均方误差 omptimzer.zero_grad() # 先将所有参数的梯度每次置零 loss.backward() # 计算所有节点的梯度 omptimzer.step() # 用上面的计算值,和学习率更新网络参数 # 打印一些参数 print(f'\nx:{x.data.numpy()}, y:{y.data.numpy()}') print(f'pred:{prediction.tolist()}, loss:{loss.tolist()}\n') for name, param in net.named_parameters(): print('-->name:', name, '-->grad_requirs:', param.requires_grad, ' -->grad_value:', param.grad) for p in param: print(p) print('----------------') ###################################################################### # 再训练一次网络,验证梯度是否是累加 prediction = net(x) # 带入x,执行一次网络,得到预测值p loss = loss_func(prediction, y) # 计算均方误差 #omptimzer.zero_grad() # 先将所有参数的梯度每次置零 loss.backward() # 计算所有节点的梯度 omptimzer.step() # 用上面的计算值,和学习率更新网络参数 # 打印一些参数 print(f'\nx:{x.data.numpy()}, y:{y.data.numpy()}') print(f'pred:{prediction.tolist()}, loss:{loss.tolist()}\n') for name, param in net.named_parameters(): print('-->name:', name, '-->grad_requirs:', param.requires_grad, ' -->grad_value:', param.grad) for p in param: print(p) print('----------------')