【学习视频链接:https://www.bilibili.com/video/BV1Y7411d7Ys?p=5&t=3299】
1 构建数据集(Prepare dataset)
Pytorch中,计算图都是mini-batch形式,所有 X X X与 Y Y Y都应该为Tensor。
[ y p r e d ( 1 ) y p r e d ( 2 ) ⋮ y p r e d ( n ) ] = ω [ x ( 1 ) x ( 2 ) ⋮ x ( n ) ] + b ; X , Y , ω , b ∈ R \begin{bmatrix} y_{pred}^{(1)}\\ y_{pred}^{(2)}\\ \vdots \\ y_{pred}^{(n)} \end{bmatrix} = \omega \begin{bmatrix} x^{(1)}\\ x^{(2)}\\ \vdots \\ x^{(n)} \end{bmatrix}+ b ;X,Y,\omega,b \in \mathbb{R} ⎣⎢⎢⎢⎢⎡ypred(1)ypred(2)⋮ypred(n)⎦⎥⎥⎥⎥⎤=ω⎣⎢⎢⎢⎡x(1)x(2)⋮x(n)⎦⎥⎥⎥⎤+b;X,Y,ω,b∈R
假设 X X X和 Y Y Y是 n × 1 n \times 1 n×1维的矩阵,式中的 ω \omega ω和 b b b会被通过
如同numpy数组的广播将其扩展为 n × 1 n \times 1 n×1维的矩阵。 在这里 ω \omega ω和 b b b随着 X X X的长度变化而变化,广播的为原来的 ω \omega ω和 b b b的值,如 b = [ b , b , … , b ] n T b=[b,b,\dots,b]_n^T b=[b,b,…,b]nT
[ L o s s ( 1 ) L o s s ( 2 ) ⋮ L o s s ( n ) ] = ( [ y ^ 1 y ^ 2 ⋮ y ^ n ] − [ y 1 y 2 ⋮ y n ] ) 2 \begin{bmatrix} Loss^{(1)}\\ Loss^{(2)}\\ \vdots \\ Loss^{(n)} \end{bmatrix} = \left( \begin{bmatrix} \hat y_{1}\\ \hat y_{2}\\ \vdots \\ \hat y_{n} \end{bmatrix} - \begin{bmatrix} y_{1}\\ y_{2}\\ \vdots \\ y_{n} \end{bmatrix}\right)^2 ⎣⎢⎢⎢⎡Loss(1)Loss(2)⋮Loss(n)⎦⎥⎥⎥⎤=⎝⎜⎜⎜⎛⎣⎢⎢⎢⎡y^1y^2⋮y^n⎦⎥⎥⎥⎤−⎣⎢⎢⎢⎡y1y2⋮yn⎦⎥⎥⎥⎤⎠⎟⎟⎟⎞2
import torch
# Pytorch中的数据等均为Tensor变量,即矩阵
x_data = torch.Tensor([[1],[2],[3]]) # 相当于一个numpy.array
y_data = torch.Tensor([[2],[4],[6]])
2 设计模型(Design model using Class)
2.1 设计模型的流程及注意事项
之前训练是,我们是通过人工来求导数,根据函数的解析式一步步来求导数的值,然后通过链式法则来进行的计算。在Pytorch中,我们就不用分析怎么求导数,然后用链式法则等进行计算了,我们的重点的目标变为构建计算图,通过张量之间的计算,自动计算出梯度。
① 通过x与y的输入,从而确定出w和b的维度大小,从而确定出 y ^ \hat y y^,当然y与 y ^ \hat y y^的维度肯定是一样的。
② 通过设计的模型构建出计算图,其计算流程如下图所示。
③ x到loss为前馈;通过loss调用backward,计算出w和b,然后通过梯度下降法来更新w和b从而进行训练,此为反馈过程。

2.2 Pytorch设计模型
① 将模型定义为一个类(class)
② 定义的模型都要继承torch.nn.Module
③ 构建的函数至少需要两个函数,第一个为def init(self):【称为构造函数,用于初始化对象所要调用的函数】 ,第二个为def forward(self,x)【用于在前馈计算所要执行的计算】。之所以没有反馈的函数,是因为我们调用的Module会自动根据里面的计算图来帮助你自动实现反馈。
④ 若我们要用的模块在Pytorch中没有定义,如果你需要的模块可以由Pytorch支持的基本运算,可以自己将其封装为一个Module,将来通过实例化调用即可自动进行反向传播。
⑤ 如果你觉得Pytorch的计算图算起来效率不高,在计算导数有更高效的方法,这时可以从Functions中继承。Functions是Pytorch的一个类,这个类是需要自己实现反向传播的,这样就可以构建自己的计算块。但是你的基本计算块都可以由现成Pytorch的模块构成,从而进行计算,用Module是最简单的,不用去设计反向传播的倒数来怎么求。
# 通过魔法命令?,查看帮助信息如下
torch.nn.Linear?
- 初始:torch.nn.Linear(in_features,out_features,bias = True)
- 对输入数据应用线性变换:$y = xA^T + b $
- in_features:每个输入样本的大小
- out_features:每个输出样本的大小
- 偏置:如果设置为"False",则图层将不会添加偏置。默认值:“True”
# 我们可以通过dir获取Module里面有哪些函数
# 通过判断可以发现在里面
"forward" in dir(torch.nn.Module) and "__call__" in dir(torch.nn.Module)
True
# 自定义一个Pytorch线性模型的类
class LinerModel(torch.nn.Module):
def __init__(self):
# super 父类,调用父类的构造,这一步必须有
# 第一个参数为定义类的名称,第二个为self
super(LinerModel,self).__init__()
'''构造一个对象,包含了权重与偏置Tensor
Linear是属于Module的,因此可以自动实现前馈和反馈的计算
nn:neural network的简写'''
self.linear = torch.nn.Linear(1,1)
def forward(self,x):
# 实际上是类的重写,该函数实际上已经在里面被写入了,这里需要重写一下
# 当类中有"__call__"时才可调用
y_pred = self.linear(x) # 实现一个可调用的对象
return y_pred
# 实例化类 ,该类为callable
model = LinerModel()# 是一个含有"__call__"的类,因此可以直接调用
# model(x) # x 就会送入至forward的函数里,然后对x进行计算
定义一个可调用的类:
class CC:
def __init__(self):
pass
# 定义一个可调用
def __call__(self,*args,**kwargs):
print("hello" + str(args[0]))
# 实例化类
d = CC()
d(1,2,3)
hello1
# 一一对应函数传递的写法
def f(a,b,c,x,y):
print(a)
f(1,2,3,x=3,y=3)
1
# *args:表示不指定变量的多个值传入至函数中,结果以元组的方式返回
# **kwargs:表示指定变量的多个变量传入至函数中,其结果为一个字典
def ff(*args,**kwargs):
print(args)
print(kwargs)
ff(1,2,3,x=3,y=3)
(1, 2, 3)
{'x': 3, 'y': 3}
3 构造损失函数和优化器(Construct loss optimizer using Pytorch API)
3.1 构造损失函数
- torch.nn.MSELoss(size_average=None, reduce=None, reduction=‘mean’)
- size_average(布尔型,可选):默认损失是该批次中每个损失要素的平均值。如果设置为False,是将每个小批量的损失相加,不求均值。默认值:True(当数据个数不相同时用,含有缺失值时,其余用不用无所谓,还能少有一步计算。)
- reduce(布尔型,可选):不推荐使用。默认情况下,根据每个小批量的观测值,是否对损失进行平均或求和,具体取决于在size_average上。默认值:True
- reduction (字符串,可选): none:无操作;mean:输出的总和除以输出中的元素;sum:将对输出求和。默认值:mean。
创建一个标准来测量之间的均方误差(L2范数的平方)
ℓ ( x , y ) = L = { l 1 , … , l N } ⊤ , l n = ( x n − y n ) 2 \ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad l_n = \left( x_n - y_n \right)^2 ℓ(x,y)=L={l1,…,lN}⊤,ln=(xn−yn)2
ℓ ( x , y ) = { mean ( L ) , if reduction = ’mean’; sum ( L ) , if reduction = ’sum’. \ell(x, y) = \begin{cases} \operatorname{mean}(L), & \text{if reduction} = \text{'mean';}\\ \operatorname{sum}(L), & \text{if reduction} = \text{'sum'.} \end{cases} ℓ(x,y)={mean(L),sum(L),if reduction=’mean’;if reduction=’sum’.
x 和 y x和y x和y是任意形状的张量, n n n总计每个元素的个数。
# 需要的数据是"\hat y" 与"y"
criterion = torch.nn.MSELoss(size_average=False)
3.2 构造优化器
torch.optim.SGD(
params,
lr=<required parameter>,
momentum=0,
dampening=0,
weight_decay=0,
nesterov=False,
)
- params(可迭代):参数的可迭代以优化或命令定义参数组
- lr(浮动):学习率,可以是根据需要动态变化的
- momentum(float,可选):动量因子(默认值:0)
- weight_decay(float,可选):正则化(默认值:0)
- 衰减动量(float,可选):衰减动量(默认值:0)
- nesterov(布尔型,可选):启用Nesterov动量(默认值:False)
'''
model.parameters()检查model里面的所有成员
如果有相应的权重,则就会将其都加入到最后要进行训练的参数集合中
model.parameters()实际上调用的是torch.nn.Linear.parameters()
'''
optimizer = torch.optim.SGD(model.parameters(),lr=0.01)
4 训练周期(Training cycle)
前馈:计算损失 , y ^ \hat y y^和 l o s s loss loss
反馈:计算梯度
更新:梯度下降算法更新权重
for epoch in range(1000):
# 前馈计算出 \hat y,即y_pred
y_pred = model(x_data)
# 前馈计算出 loss
loss = criterion(y_pred,y_data)
# 查看损失,loss输出的时候回自动变为标量
# print(epoch,loss.data.item())
# 所有的梯度归零
optimizer.zero_grad()
# 反馈,方向传播
loss.backward()
# 更新,根据所有参数和学习率来更新
optimizer.step()
# 输出权重(weight)和偏置(bias)
print(f"w={model.linear.weight.item()}")
print(f"b={model.linear.bias.item()}")
w=1.9997389316558838
b=0.0005934041691944003
# 测试模型(Test Model)
x_test = torch.Tensor([[4]])
y_test = model(x_test)
print(f"y_pred={y_test.data}")
y_pred=tensor([[7.9995]])
如果训练没有达到预想的情况,可适当的增加训练次数。但是这样会有一定的风险,对于训练集上的损失会越来越少,对于测试集上的损失可能就会少着少着就会上升了。因此,在训练时需要对训练集和测试集的损失综合来观察,避免出现过拟合。
5 代码合并与
# 自定义一个Pytorch线性模型的类
class LinerModel(torch.nn.Module):
def __init__(self):
# super 父类,调用父类的构造,这一步必须有
# 第一个参数为定义类的名称,第二个为self
super(LinerModel,self).__init__()
'''构造一个对象,包含了权重与偏置Tensor
Linear是属于Module的,因此可以自动实现前馈和反馈的计算
nn:neural network的简写'''
self.linear = torch.nn.Linear(1,1)
def forward(self,x):
# 实际上是类的重写,该函数实际上已经在里面被写入了,这里需要重写一下
# 当类中有"__call__"时才可调用
y_pred = self.linear(x) # 实现一个可调用的对象
return y_pred
def train(times,name,lr):
# 实例化类 ,该类为callable
model = LinerModel()# 是一个含有"__call__"的类,因此可以直接调用
# model(x) # x 就会送入至forward的函数里,然后对x进行计算
# 需要的数据是"\hat y" 与"y"
criterion = torch.nn.MSELoss(size_average=False)
# 构建优化器
if name == "SGD":
optimizer = torch.optim.SGD(model.parameters(),lr=lr)
elif name == "Adagrad":
optimizer = torch.optim.Adagrad(model.parameters(),lr=lr)
elif name == "Adam":
optimizer = torch.optim.Adam(model.parameters(),lr=lr)
elif name == "Adamax":
optimizer = torch.optim.Adamax(model.parameters(),lr=lr)
elif name == "ASGD":
optimizer = torch.optim.ASGD(model.parameters(),lr=lr)
elif name == "RMSprop":
optimizer = torch.optim.RMSprop(model.parameters(),lr=lr)
elif name == "Rprop":
optimizer = torch.optim.Rprop(model.parameters(),lr=lr)
aa = []
for epoch in range(times):
# 前馈计算出 \hat y,即y_pred
y_pred = model(x_data)
# 前馈计算出 loss
loss = criterion(y_pred,y_data)
aa.append(loss.data.item())
# 查看损失,loss输出的时候回自动变为标量
# print(epoch,loss.data.item())
# 所有的梯度归零
optimizer.zero_grad()
# 反馈,方向传播
loss.backward()
# 更新,根据所有参数和学习率来更新
optimizer.step()
# 输出权重(weight)和偏置(bias)
print(f"w={model.linear.weight.item()}")
print(f"b={model.linear.bias.item()}")
# 测试模型(Test Model)
x_test = torch.Tensor([[4]])
y_test = model(x_test)
print(f"y_pred={y_test.data}")
return aa
import torch
if __name__ == '__main__':
name = ["Adagrad","Adam","Adamax","ASGD","RMSprop","Rprop","SGD"]
data = dict()
for i in name:
print("*"*20)
# Pytorch中的数据等均为Tensor变量,即矩阵
x_data = torch.Tensor([[1],[2],[3]]) # 相当于一个numpy.array
y_data = torch.Tensor([[2],[4],[6]])
print(i)
data[i] = train(200,i,0.01)
print("执行结束")
********************
Adagrad
w=-0.2695870101451874
b=0.6175152063369751
y_pred=tensor([[-0.4608]])
********************
Adam
w=1.536633849143982
b=0.958001971244812
y_pred=tensor([[7.1045]])
********************
Adamax
w=1.5282803773880005
b=1.012595295906067
y_pred=tensor([[7.1257]])
********************
ASGD
w=1.8811336755752563
b=0.2701973021030426
y_pred=tensor([[7.7947]])
********************
RMSprop
w=1.0703529119491577
b=1.7555208206176758
y_pred=tensor([[6.0369]])
********************
Rprop
w=1.9999990463256836
b=5.34028117726848e-07
y_pred=tensor([[8.0000]])
********************
SGD
w=1.9451408386230469
b=0.12470770627260208
y_pred=tensor([[7.9053]])
执行结束
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False # 用来正常显示负号
plt.figure(figsize=(10,6),dpi=300)
for i in data.keys():
y = data[i]
x = list(range(200))
plt.plot(x,y,label=i)
plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=0,
ncol=len(data.keys()), mode="expand", borderaxespad=0.)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.grid()
plt.show()