第五讲 - 用 PyTorch 实现线性回归【跟随 up 主 “刘二大人” 学习 pytorch】
前言
- 本专栏是我这个小菜鸡跟随 B 站 up 主
刘二大人
学习pytorch
完成的课后作业,原视频请戳这里
题目
- 比较各个优化器的
性能
分析
- 既然要比较
性能
,那就必须有指标
来体现 - 这里我选择
四
个指标,分别是迭代开始
到到达拐点
所用的时间
- 出现拐点时的
迭代次数
,以及此时的权重 w
,权重 b
- 要实现
比较各优化器的性能
这个需求,就需要记录上述四个指标 - 同时,为了展示训练结果,还要记录
损失
,即随着迭代次数的增加,模型的损失
值
功能实现
1. 构造数据集
- 还是用老数据集
import torch
x_data = torch.Tensor([[1.0], [2.0], [3.0]])
y_data = torch.Tensor([[2.0], [4.0], [6.0]])
2. 自定义线性模型
class LinearModel(torch.nn.Module):
def __init__(self):
super(LinearModel, self).__init__()
# 参数 1, 1 代表的是输入维度是 1,输出维度也是 1
self.linear = torch.nn.Linear(1, 1)
# 重写 forward 函数
def forward(self, x):
y_pred = self.linear(x)
return y_pred
3. 记录每个优化器的 四
个指标
- 我写了一个函数,传入的参数是
优化器名称
- 该函数实现的功能是:
- 实例化
model
,用于计算
y ^ \hat{y} y^ - 构造
损失函数
和优化器
,损失函数这里使用MSE
,优化器就根据传入的优化器名称创建
- 开始迭代,每一次迭代的过程都是
计算
y ^ \hat{y} y^,计算损失
,梯度清零
,反向传播
,更新
,判断是否为拐点
- 拐点的计算公式(我自认为没有问题的公式)为
l o s s = d a t a / e p o c h d a t a = e p o c h ∗ l o s s ∂ l o s s ∂ e p o c h = − d a t a e p o c h 2 = − e p o c h ∗ l o s s e p o c h 2 = − l o s s e p o c h loss = data / epoch \\ data = epoch * loss \\ \frac{\partial loss}{\partial epoch} = -\frac{data}{{epoch}^{2}}=-\frac{epoch * loss}{{epoch}^{2}}=-\frac{loss}{epoch} loss=data/epochdata=epoch∗loss∂epoch∂loss=−epoch2data=−epoch2epoch∗loss=−epochloss- 其中
epoch
代表迭代次数
,loss
代表损失
,data
表示epoch 和 loss 的乘积
- 因为这是个反比例函数,所以
epoch
和loss
的乘积为定值
- 其中
- 最后记录所用时间
- 实例化
- 代码如下
result = {
'loss': {}, # 损失,用于展示损失下降情况
'time': {}, # 所用时间
'turn_step': {}, # 出现拐点时对应的迭代次数
'turn_w': {}, # 出现拐点时对应的权重 w
'turn_b': {} # 出现拐点时对应的权重 b
}
# 设置迭代次数
step = 1000
def calculation(algotirthm):
# 因为要记录每一次迭代的损失值,所以要先初始化 loss 为一个列表
result['loss'][algotirthm] = []
# 实例化线性模型
model = LinearModel()
# 实例化 MSE 损失函数
criterion = torch.nn.MSELoss(size_average=False)
# 实例化优化器,因为实例化优化器传参差不多,所以可以使用下列模板进行实例化
optimizer = eval(f'torch.optim.{algotirthm}(model.parameters(), lr=1e-2)')
# 设置开始的时间
start = time.perf_counter()
# 进行迭代
for epoch in range(1, step+1):
# 有些优化器更新时需要多次重新计算函数,必须传入一个闭包
# 因此,把计算损失、梯度清零和反向传播包装成一个函数
def closure():
# 计算损失
y_pred = model(x_data)
loss = criterion(y_pred, y_data)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
return loss
# 先进行一次计算,以便不需要多次计算函数的优化器获得损失值
loss = closure()
# 捕捉更新时的异常,出现异常即代表优化器需要传入一个闭包
try:
optimizer.step()
except:
optimizer.step(closure)
finally:
# 显示迭代结果
print('\r{} Process: {:>.2f}% Epoch: {}/{} [{}{}] loss = {:>.2f} {:>.2f}s'.format(
algotirthm, epoch / step * 100, epoch, step,
'■' * int(epoch / step * 20),
'□' * (20 - int(epoch / step * 20)),
loss.item(), time.perf_counter() - start
), end='')
# 如果 迭代次数-损失 函数此时的梯度小于预设值(只考虑大小,不考虑符号)
# 并且是第一次小于预设值
# 即认为是拐点
if (loss.item() / epoch) <= 1e-3 and algotirthm not in result['turn_step']:
# 记录所用时间
result['time'][algotirthm] = time.perf_counter() - start
# 记录本次迭代次数
result['turn_step'][algotirthm] = epoch
# 记录此时权重 w 的值
result['turn_w'][algotirthm] = round(model.linear.weight.item(), 2)
# 记录此时权重 b 的值
result['turn_b'][algotirthm] = round(model.linear.bias.item(), 2)
# 记录本次迭代的损失
result['loss'][algotirthm].append(loss.item())
4. 获得优化器名称列表,并进行计算
- 优化器名称列表我是用的
XPath
分析PyTorch
文档得到的
- 代码如下
import time
# 优化器名称列表
algorithm_list = ['Adadelta', 'Adagrad', 'Adam', 'AdamW', 'Adamax',
'ASGD', 'LBFGS', 'RMSprop', 'Rprop', 'SGD']
for algorithm in algorithm_list:
calculation(algorithm)
# 防止测试不同优化器性能时干扰
time.sleep(3)
print('\n')
- 部分运行结果如下
5. 查看一下结果
- 查看出现拐点的优化器个数
In[]: len(result['turn_step']) Out[]: 8
- 代表只有
8
个优化器迭代过程中会出现拐点 - 那剩下
2
个优化器迭代过程损失是不断下降的
,迭代完毕后未收敛
- 代表只有
- 查看一下没有出现拐点的优化器
In[]: [alg for alg in algorithm_list if alg not in result['turn_step'].keys()] Out[]: ['Adadelta', 'Adagrad']
6. 用图表将结果进行展示
-
首先是
迭代次数 - 损失
图- 代码如下
import matplotlib.pyplot as plt # 画出每个优化器 迭代次数-损失 图表 for algorithm in algorithm_list: plt.plot(range(step), result['loss'][algorithm], label=algorithm) plt.xlabel('epoch') plt.ylabel('loss') plt.title('epoch- loss') plt.legend() plt.show()
- 运行结果
- 可以看到大部分曲线是有
拐点
存在的 - 第五步输出结果对比,确认结果无误
-
其次是剩下的四张图
- 代码如下
def show(key): # 画出柱状图 plt.bar(result[key].keys(), result[key].values(), width=0.6) # 设置纵轴范围 min_data, max_data = min(result[key].values()), max(result[key].values()) if min_data >= 0 and max_data >= 0: plt.ylim(0, max_data * 1.1) elif min_data <= 0 and max_data <= 0: plt.ylim(min_data * 1.1, 0) else: plt.ylim(min_data * 1.1, max_data * 1.1) # 显示每个柱子的数据 drift = (max_data - min_data) / 40 for index, data in enumerate(result[key].values()): plt.text(index-0.3, data+drift if data>=0 else data-drift, round(data, 2)) plt.xlabel('optimizer') plt.ylabel(key) plt.xticks(rotation=30) plt.title(f'optimizer - {key}') plt.show() keys = list(result.keys()) keys.remove('loss') for key in keys: show(key)
- 结果如下
7. 分析结果
- 由上图可知
- 优化器
LBFGS
到达拐点时达到相较之下的最优权重
,但同时,所耗时间最长
- 原因:
LBFGS
在每次迭代过程中更新权重时会多次重新计算函数
,导致所用时间最长
- 原因:
- 优化器
SGD
从迭代开始到出现拐点迭代次数最少
、所耗时间最短
,并且此时达到较优权重
,目测效果更好一些
代码展示
- 整理一下代码
In[]: import time
import torch
import numpy as np
import matplotlib.pyplot as plt
# 1. 构造数据集
In[]: x_data = torch.Tensor([[1.0], [2.0], [3.0]])
y_data = torch.Tensor([[2.0], [4.0], [6.0]])
# 2. 自定义线性模型
In[]: class LinearModel(torch.nn.Module):
def __init__(self):
super(LinearModel, self).__init__()
self.linear = torch.nn.Linear(1, 1)
def forward(self, x):
y_pred = self.linear(x)
return y_pred
# 3. 记录每个优化器的五个指标
In[]: result = {
'loss': {},
'time': {},
'turn_step': {},
'turn_w': {},
'turn_b': {}
}
step = 1000
def calculation(algotirthm):
result['loss'][algotirthm] = []
model = LinearModel()
criterion = torch.nn.MSELoss(size_average=False)
optimizer = eval(f'torch.optim.{algotirthm}(model.parameters(), lr=1e-2)')
start = time.perf_counter()
for epoch in range(1, step+1):
def closure():
y_pred = model(x_data)
loss = criterion(y_pred, y_data)
optimizer.zero_grad()
loss.backward()
return loss
loss = closure()
try:
optimizer.step()
except:
optimizer.step(closure)
finally:
print('\r{} Process: {:>.2f}% Epoch: {}/{} [{}{}] loss = {:>.2f} {:>.2f}s'.format(
algotirthm, epoch / step * 100, epoch, step,
'■' * int(epoch / step * 20),
'□' * (20 - int(epoch / step * 20)),
loss.item(), time.perf_counter() - start
), end='')
if (loss.item() / epoch) <= 1e-5 and algotirthm not in result['turn_step']:
result['time'][algotirthm] = time.perf_counter() - start
result['turn_step'][algotirthm] = epoch
result['turn_w'][algotirthm] = round(model.linear.weight.item(), 2)
result['turn_b'][algotirthm] = round(model.linear.bias.item(), 2)
result['loss'][algotirthm].append(loss.item())
# 4. 获得优化器名称列表,并进行计算
In[]: algorithm_list = ['Adadelta', 'Adagrad', 'Adam', 'AdamW', 'Adamax',
'ASGD', 'LBFGS', 'RMSprop', 'Rprop', 'SGD']'
for algorithm in algorithm_list:
calculation(algorithm)
time.sleep(3)
print('\n')
# 5. 将结果用图表展示
In[]: for algorithm in algorithm_list:
plt.plot(range(step), result['loss'][algorithm], label=algorithm)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.title('epoch - loss')
plt.legend()
plt.show()
In[]: def show(key):
plt.bar(result[key].keys(), result[key].values(), width=0.6)
min_data, max_data = min(result[key].values()), max(result[key].values())
if min_data >= 0 and max_data >= 0:
plt.ylim(0, max_data * 1.1)
elif min_data <= 0 and max_data <= 0:
plt.ylim(min_data * 1.1, 0)
else:
plt.ylim(min_data * 1.1, max_data * 1.1)
drift = (max_data - min_data) / 40
for index, data in enumerate(result[key].values()):
plt.text(index-0.3, data+drift if data>=0 else data-drif, round(data, 2))
plt.xlabel('optimizer')
plt.xticks(rotation=30)
plt.ylabel(key)
plt.title(f'optimizer - {key}')
plt.show()
keys = list(result.keys())
keys.remove('loss')
for key in keys:
show(key)
- 完成任务~
结尾
以上就是我要分享的内容,因为学识尚浅,会有不足,还请各位大佬指正。
有什么问题也可在评论区留言。