选择IDE为pycharm,文中y_hat表示预测值
案例如下:
已知x=1时,y为2;x=2时,y为4;x=3时,y为6,现利用线性模型(不含偏置量,仅包含斜率参数,即y_hat=w*x
)预测x=4时y的值
import torch
x_data = [1.0,2.0,3.0]
y_data = [2.0,4.0,6.0]
w = torch.tensor([1.0])
w.requires_grad = True
def forward(x):
return x * w
def loss(x,y):
y_pred = forward(x)
return (y_pred - y) ** 2
print("Predict(before training)",4,forward(4).item())
for epoch in range(100):
for x,y in zip(x_data,y_data):
l = loss(x,y)
l.backward()
print("\tgrad:",x,y,w.grad.item())
w.data = w.data - 0.01* w.grad.data
w.grad.data.zero_()
print("progress:",epoch,l.item())
print("predict(after training)",4,forward(4).item())
此为全部代码,现分块进行讲解
import torch
x_data = [1.0,2.0,3.0]
y_data = [2.0,4.0,6.0]
w = torch.tensor([1.0])
w.requires_grad = True
首先导入torch
包,然后初始化已知数据x_data
和y_data
然后,暂时停止讲解,我们稍微介绍一下tensor
,一种十分重要的数据类型,类似于numpy
中的array
数据类型
tensor
类型中会存放两个东西,一个是data
,存放标量、矩阵或高阶矩阵;另一个是grad
,存放最终损失函数对该data
的梯度(后续都会使用到)。如图所示
接着讲代码。最后两行中首先初始化了一个名为w
的tensor
,之后将该tensor
的requires_grad
属性修改为True
(默认为False
)。该值为True
,意味着之后会计算损失函数对于该参数的导数(也就是需要计算grad
)
后续更改参数时需要使用随机梯度下降,当然需要计算这里的梯度啦,所以为True
def forward(x):
return x * w
这里定义了前馈函数forward()
,作用是计算出预测值y_hat
。
注意,w
是tensor
类型,在计算时,*
号会重载为适合tensor
类型的乘法,且会将x
类型也暂时转换为tensor
类型进行计算,最终返回值为tensor
类型
def loss(x,y):
y_pred = forward(x)
return (y_pred - y) ** 2
该部分计算损失函数。
注意,forward()
函数的返回值也是tensor
类型,于是按照之前的说明,loss()
函数的返回值也会是一个tensor
类型的数据
print("Predict(before training)",4,forward(4).item())
for epoch in range(100):
for x,y in zip(x_data,y_data):
l = loss(x,y)
l.backward()
print("\tgrad:",x,y,w.grad.item())
w.data = w.data - 0.01* w.grad.data
w.grad.data.zero_()
print("progress:",epoch,l.item())
print("predict(after training)",4,forward(4).item())
我们训练了100轮,在每一轮中,使用随机梯度下降进行计算(梯度下降即计算损失函数对于参数w
的偏导数,而这里的随机梯度下降中,每一次计算用于更新的偏导数值时,仅取3项求和项中的一项进行计算,这样有很多优势,读者可自己查询)。
具体到某一次时,我们进入这一部分代码
l = loss(x,y)
l.backward()
print("\tgrad:",x,y,w.grad.item())
w.data = w.data - 0.01* w.grad.data
w.grad.data.zero_()
首先通过l = loss(x,y)
计算出损失函数。正如之前所讲,每一次含有tensor
类型的变量进行运算时,其他类型的变量会被重载。在这个计算过程中,就会构造出一张计算图
解释一下:对于稍微复杂一些的神经网络,梯度的手工求导计算是不可能的。我们需要pytorch进行自动计算,而pytorch计算导数的机制是基于计算图的(类似于我们的链式求导,它会将最终l
部分与参数w
部分之间的若干个偏导计算出来进行相乘),所以,对于requires_grad
为True
的w
来说,每一次含有它的运算都会构建出一点计算图,最终就构建出了如上图所示的计算图(该参数为真的tensor
变量的每一次计算就会多构建出一点计算图,读者可以自己根据代码手绘出上述计算图)
然后是代码l.backward()
。这里调用了张量l
的backward()
方法,它的作用是把计算图上需要计算偏导的地方都求出来,然后自动相乘,最终得到l
关于w
的偏导数(也就是梯度),同时释放计算图(计算图不再存在)。
之后,w.data = w.data - 0.01* w.grad.data
对梯度进行更新。这里注意,更新时没有直接使用w
进行更新,而使用了w.data
。原因很简单,因为直接对w
的使用意味着构建计算图,我们这里的本意只是想利用数值更新梯度,而不是想要构建新的计算图,所以使用w.data
。
梯度更新部分 w.data = w.data - 0.01* w.grad.data
不再啰嗦。参数0.01是超参数。
稍微多提一句,代码中可以看见w.grad.data
和w.grad.item()
,它们的区别是什么?data
表示这个tensor
的数据信息,即维度和具体的值,而item()
是一个将0阶张量 标量化的函数。
最后,w.grad.data.zero_()
表示将w
的梯度的值清零。原因:如果没有这一步梯度清零操作,下一次计算出的梯度值会累加上一次的值,这并不是我们想要的结果。至于程序为什么没有自动清零?因为有时候我们并不需要清零操作。这一步记住该机制即可
这里讲的应该比较清楚了,读者可以试着将模型加上偏置量,亦或使用二元函数模型进行预测,来检验是否学懂。
如有错误,欢迎指出,谢谢