第五讲 - 用 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. 记录每个优化器的 个指标
  • 我写了一个函数,传入的参数是 优化器名称
  • 该函数实现的功能是:
    1. 实例化 model ,用于 计算 y ^ \hat{y} y^
    2. 构造 损失函数优化器,损失函数这里使用 MSE,优化器就 根据传入的优化器名称创建
    3. 开始迭代,每一次迭代的过程都是 计算 y ^ \hat{y} y^计算损失梯度清零反向传播更新判断是否为拐点
    4. 拐点的计算公式(我自认为没有问题的公式)为
      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=epochlossepochloss=epoch2data=epoch2epochloss=epochloss
      • 其中 epoch 代表 迭代次数loss 代表 损失data 表示 epoch 和 loss 的乘积
      • 因为这是个反比例函数,所以 epochloss 的乘积为定值
    5. 最后记录所用时间
  • 代码如下
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 文档得到的
  1. 代码如下
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')
  1. 部分运行结果如下
    在这里插入图片描述
5. 查看一下结果
  1. 查看出现拐点的优化器个数
    In[]:	len(result['turn_step'])
    
    Out[]:	8
    
    • 代表只有 8 个优化器迭代过程中会出现拐点
    • 那剩下 2 个优化器迭代过程 损失是不断下降的,迭代完毕后 未收敛
  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)
  • 完成任务~



结尾

以上就是我要分享的内容,因为学识尚浅,会有不足,还请各位大佬指正。
有什么问题也可在评论区留言。
在这里插入图片描述

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值