下面给一个小的例子,比如我们待拟合的函数是 y = 5*x
,(假设你对深度学习有基础,懂损失函数之类的,没有的话建议看完再继续),则我们需要一堆的x以及对应labbel,比如(x,label) = (1,5) (2,10) (3,15),然后我们构造一个前向传播 y' = w *x
,其中这个里面w
就是我们最终要求的,也就是5,所以需要对w进行更新。简单代码如下 :
import torch
from torch.autograd import Variable
x = torch.Tensor([2])
y = torch.Tensor([10])
loss = torch.nn.MSELoss()
w = Variable(torch.randn(1),requires_grad=True)
print(w)
for i in range(10):
y_ = w*x
l = loss(y,y_)
l.backward()
print(w.grad.data)
print(-2*x*(y-y_))
首先,我们定义了一个输入和输出也就是x和y,x为2,y为10,之后我们定义了一个均方误差loss,下面定义了一个w
,注意w
是一个Variable形式,并且requires_grad=True
,为什么需要这样,因为w
是一个待求的,所以需要进行梯度下降,所以包装成一个requires_grad=True
的Variable,这样pytorch才能认识,并且w我们是随机生成的,类似于之前学习两层的神经网络时候的w,只不过这个比较简单。
之后我么就进行了10次迭代,计算了loss
并进行反向传播,然后打印出来,print(-2*x*(y-y_))
是我手动计算了一下l对w的导数,然后输出了出来。最后输出如下(部分):
tensor([-0.8820], requires_grad=True)
tensor([-47.0557])
tensor([-47.0557], grad_fn=<MulBackward0>)
tensor([-94.1115])
tensor([-47.0557], grad_fn=<MulBackward0>)
tensor([-141.1672])
tensor([-47.0557], grad_fn=<MulBackward0>)
tensor([-188.2230])
可以发现我们的w初始为-0.8820
,计算出来的导数为-47.0557
第2行是backward计算的,第3行是我们自己计算的。但是发现下面的第4、5行和想象的不一样,手动计算的好像没问题还是-47.0557
,但为什么backward计算的变成了-94.1115
,似乎是-47.0557*2
,其实是因为pytorch设计就是这样的,它的梯度默认会保存累加,所以这次的梯度是我们这次的加上上一次的。讲到这里,估计也就明白了为什么我们在进行梯度下降的时候需要用optimizer.zero_grad()
了。主要就是清空梯度。上面的例子只是计算了梯度,并没有进行梯度更新,下面对for循环里面的代码增加点东西:
y_ = w*x
l = loss(y,y_)
l.backward()
print(w.grad.data)
w = Variable(w - 0.1 * w.grad.data,requires_grad=True)
print(w)
# w.grad.data = torch.Tensor([0])
print(-2*x*(y-y_))
核心就是增加了w = Variable(w - 0.1 * w.grad.data,requires_grad=True)
这个代码,熟悉深度学习的应该不陌生,其实就是更新了w,0.1相当于学习率。最后输出为:
tensor([-2.0593], requires_grad=True)
tensor([-56.4745])
tensor([3.5881], requires_grad=True)
tensor([-56.4745], grad_fn=<MulBackward0>)
tensor([-11.2949])
tensor([4.7176], requires_grad=True)
tensor([-11.2949], grad_fn=<MulBackward0>)
tensor([-2.2590])
tensor([4.9435], requires_grad=True)
tensor([-2.2590], grad_fn=<MulBackward0>)
tensor([-0.4518])
tensor([4.9887], requires_grad=True)
tensor([-0.4518], grad_fn=<MulBackward0>)
tensor([-0.0904])
tensor([4.9977], requires_grad=True)
tensor([-0.0904], grad_fn=<MulBackward0>)
tensor([-0.0181])
tensor([4.9995], requires_grad=True)
tensor([-0.0181], grad_fn=<MulBackward0>)
tensor([-0.0036])
tensor([4.9999], requires_grad=True)
tensor([-0.0036], grad_fn=<MulBackward0>)
tensor([-0.0007])
tensor([5.0000], requires_grad=True)
tensor([-0.0007], grad_fn=<MulBackward0>)
tensor([-0.0001])
tensor([5.0000], requires_grad=True)
tensor([-0.0001], grad_fn=<MulBackward0>)
tensor([-3.0518e-05])
tensor([5.0000], requires_grad=True)
tensor([-3.0518e-05], grad_fn=<MulBackward0>)
很明显看出最后w更新为5了,和我们的函数拟合上了。这里你可能会问这个也没有做梯度置为0的操作啊,之所以没做是因为我们w重新赋了值,所以里面的w.grad.data变为None了,所以不用重新置为0了。
说在最后,其实上面这个例子过于简单,所以可能看完还是和你用的nn.Linear
这种操作无法联系到一起,其实我们的w就相当于nn.Linear
中的w,只不过pytorch封装起来了,你看不到,而x就相当于你输入的数据,比如mnist的12828的那个矩阵。