pytorch学习笔记四————一个简单的学习模型
本次将尝试用微分和梯度下降法进行一个最基础的学习模型:线性回归
以下是两组数据
t_c = [0.5, 14.0, 15.0, 28.0, 11.0, 8.0, 3.0, -4.0, 6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c)
t_u = torch.tensor(t_u)
先用旧温度计记录下温度数据,再用新温度计测量,然后记录下来
t_c
是以摄氏度为单位的温度,而t_u
是我们未知的单位,我们想搞清楚两组数据的关系
我们的未知数据可能遵循一个线性模型
于是利用线性模型进行验证:
t
c
=
w
×
t
u
+
b
t_c=w\times t_u +b
tc=w×tu+b
这里的w和b有两个专业的名字,分别称作权重和偏置
损失函数
有了参数,就需要对这些参数的正确性进行评估,而评估的方法函数就称作为损失函数,这里我们用目标与实际的差的平方来作为损失函数
l
o
s
s
=
(
t
p
−
t
c
)
2
loss=(t_p-t_c)^2
loss=(tp−tc)2
注意这里的t_p是t_u推导出来的,而t_c则就是实际的t_c,与第一个公式里的t_c含义不太一样
用pytorch实现模型和损失函数就如下:
def model(t_u,w,b):
return w*t_u+b
def loss_fn(t_p,t_c):
squared_diffs=(t_p-t_c)**2
return squared_diffs.mean()
减小损失
既然我们有了损失函数,所以我们肯定要想办法使得我们的损失函数尽可能的小,如果只靠感觉的话很有可能不准,所以为了能够定量的使其减小,我们采用导数来计算出损失函数在某个点的形状,此时再沿着导数下降,就太不会出现下降过多或者下降过慢的情况
为了能能够计算损失对一个参数的导数,我们可以采用链式法则,先计算损失对其输入输出的导数,再乘模型对参数的导数,如下:
∂
l
o
s
s
f
n
∂
w
=
∂
l
o
s
s
f
n
∂
t
p
⋅
∂
t
p
∂
w
\frac{\partial loss_{f_n}}{\partial w}=\frac{\partial loss_{f_n}}{\partial t_p}\cdot \frac{\partial t_p}{\partial w}
∂w∂lossfn=∂tp∂lossfn⋅∂w∂tp
所以则有以下的损失函数计算公式:
def dloss_fn(t_p,t_c):
dsq_diffs=2*(t_p-t_c)/t_p.size(0)
return dsq_diffs
def dmodel_dw(t_u,w,b):
return t_u
def dmodel_db(t_u,w,b):
return 1.0
这里有几个注意点,一个是dloss_fn
的最后要除一个向量的长度,这是因为loss_fn
返回的是平均值,而平均值的导数即是导数的平均值,因此求完导后得到一个矩阵,再对矩阵求平均值即可,再后面的链式求导数直接用sum()
即可
由此我们得到最后的梯度计算公式
def grad_fn(t_u,t_c,t_p,w,b):
dloss_dtp=dloss_fn(t_p,t_c)
dloss_dw=dloss_dtp*dmodel_dw(t_u,w,b)
dloss_db=dloss_dtp*dmodel_db(t_u,w,b)
return torch.stack([dloss_dw.sum(),dloss_db.sum()])
注:torch.stack
不是栈,而是拼接函数
迭代训练
既然已经获得了导数,那么我们只需要顺着导数进行迭代训练,使得损失函数最小即可达到我们的目标
代码如下:
def training_loop(n_epochs,learning_rate,params,t_u,t_c):
for epoch in range(1,n_epochs+1):
w,b=params
t_p=model(t_u,w,b)
loss=loss_fn(t_p,t_c)
grad=grad_fn(t_u,t_c,t_p,w,b)
params=params-learning_rate*grad
print('Epoch %d,Loss %f' %(epoch,float(loss)))
print('params:',params)
print('Grad:',grad)
return params
学习率learning rate是一个需要仔细调得参数,如果参数过大,则会导致它们的值来回波动,每次更新修正过度,它们的优化的过程则是不稳定的,而如果太小则会优化的太慢
归一化输入
大多数情况下,不同的参数的修正处于不同的比例空间下,在这种情况下,如果学习率过大,可以有效更新其中一个参数,而对于另一个参数来说,学习率就会变得非常的不稳定,而一个只适于另一个参数的学习率也不足以有效的改变前者
为了解决这个问题,用一个简单的方法:改变输入,而不是改变模型可以简单的改变学习率
我们将t_u乘0.1来获得一个更好的输入,如下
t_un=0.1*t_u
params=training_loop(n_epochs=5000,learning_rate=1e-2,params=torch.tensor([1.0,0.0]),t_u=t_un,t_c=t_c)
在这调整完成后只需将相应的参数放缩到对应的比例即可完成训练
可视化输出
训练完成对其进行可视化输出即可,所有的代码参考如下:
附一张训练完成的图片:
import torch
t_c = [0.5, 14.0, 15.0, 28.0, 11.0, 8.0, 3.0, -4.0, 6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c)
t_u = torch.tensor(t_u)
w=torch.ones(())
b=torch.zeros(())
def model(t_u,w,b):
return w*t_u+b
def loss_fn(t_p,t_c):
squared_diffs=(t_p-t_c)**2
return squared_diffs.mean()
def dloss_fn(t_p,t_c):
dsq_diffs=2*(t_p-t_c)/t_p.size(0)
return dsq_diffs
def dmodel_dw(t_u,w,b):
return t_u
def dmodel_db(t_u,w,b):
return 1.0
def grad_fn(t_u,t_c,t_p,w,b):
dloss_dtp=dloss_fn(t_p,t_c)
dloss_dw=dloss_dtp*dmodel_dw(t_u,w,b)
dloss_db=dloss_dtp*dmodel_db(t_u,w,b)
return torch.stack([dloss_dw.sum(),dloss_db.sum()])
def training_loop(n_epochs,learning_rate,params,t_u,t_c):
for epoch in range(1,n_epochs+1):
w,b=params
t_p=model(t_u,w,b)
loss=loss_fn(t_p,t_c)
grad=grad_fn(t_u,t_c,t_p,w,b)
params=params-learning_rate*grad
print('Epoch %d,Loss %f' %(epoch,float(loss)))
print('params:',params)
print('Grad:',grad)
return params
t_un=0.1*t_u
params=training_loop(n_epochs=5000,learning_rate=1e-2,params=torch.tensor([1.0,0.0]),t_u=t_un,t_c=t_c)
from matplotlib import pyplot as plt
t_p=model(t_un,*params)
fig=plt.figure()
plt.xlabel("X")
plt.ylabel("Y")
plt.plot(t_u.numpy(),t_p.detach().numpy())
plt.plot(t_u.numpy(),t_c.numpy(),'o')
plt.show()
# loss=loss_fn(t_p,t_c)
# print(loss)
'''
delta=0.1
loss_rate_of_change_w=\
(loss_fn(model(t_u,w+delta,b),t_c)-
loss_fn(model(t_u,w-delta,b),t_c))/(2.0*delta)
loss_rate_of_change_b=\
(loss_fn(model(t_u,w,b+delta),t_c)-
loss_fn(model(t_u,w,b-delta),t_c))/(2.0*delta)
w=w-learning_rate*loss_rate_of_change_w
b=b-learning_rate*loss_rate_of_change_b
'''