pytorch从基础到实战

1、前言

  1. 受博士学长推荐,入手了pytorch,基本看一些论文的复现代码,大多数会使用pytorch。而且由于它的灵活性,在学术科研领域用的都比较广泛
  2. 基础部分:pytorch特点以及其自动求导模块(核心)
  3. 实战部分:pytorch拟合log函数曲线,代码逐句解释,让你真正明白pytorch的运行原理
  4. 兄弟别被目录吓到,心中默念三句,我能行。kaggle notebook 源代码
  5. 要求:提前安装好pytorch库,pytorch官方文档入口

2、基础部分

2.1.pytorch特点

说到torch的优势,那肯定就离不开谈论动态计算图了。我们使用tensorflow很明显就能感受到其静态计算图带来的不灵活性。举个全网通用的例子,当我们要实现如下的计算图时:

计算图
用TensorFlow是这样的:
在这里插入图片描述
而用pytorch是这样的:

在这里插入图片描述

这里我再解释下:

  • tensorflow 的x = tf.placeholder()这行代码,表示先占个位置,等我把计算图搭建成功再说。然后你看它通过最后一条语句sess.run里面的feed_dict,再把数据 “喂”进计算图的嘴里。(古板的人,硬是要把所有东西都搞好,才能吃饭,但是稳重,事情(数据)多了起来能hold住)
  • pytorch就牛逼多了,你看它就是直接把变量输入到计算图中计算,它其实是边计算边构建计算图(灵活的人,边吃饭边干活,但是事情(数据)多了没条理,忙不过来)
  • 这个简单的例子,你可能感觉两者区别并不大。但当计算图很大的时候,你使用tensorflow只能一气呵成,出了bug也不能输出中间变量来观察;而这个时候,如果使用pytorch就能慢慢的搭建计算图,边输出中间变量调bug。这也就是为什么pytorch会更适合入门选手的原因。
    还没看懂的人,转向这篇博客

2.2. torch的自动求导

这个自动求导算是很棒的了。也因为有了自动求导,搞学术研究的人才能更方便的复现他们的paper。这也是pytorch多用于学术领域的原因之一。接下来我就详细讲下如何使用。

  • requires_grad有两个值:True和False,True代表此变量处需要计算梯度,False代表不需要。变量的“requires_grad”值是一个参数,默认是False。
  • grad_fn的值可以得知该变量是否是一个计算结果,也就是说该变量是不是一个函数的输出值。若是,则grad_fn返回一个与该函数相关的对象,否则是None。
import torch
from torch.autograd import Variable
x=torch.randn(2,2)
y=torch.randn(2,2)
z=torch.randn(2,2,requires_grad=True)
a=x+y
b=a+z

以上代码就形成了如下的计算图:
在这里插入图片描述

  • 用户创建变量:x,y,z
  • 运算结果变量:a,b

(官方文档中所说的“graph leaves”,“leaf variables”,都是指像x,y,z这样的事先创建的、而非运算得到的变量,本文我们把这样的变量称为创建变量,像a,b那样的称为结果变量

  • 运算结果变量的“requires_grad”值是取决于输入的变量的,例如变量b:其是由a和z计算得到的,如果a或者z需要计算关于自己的梯度(requires_grad=True),因为梯度要反向传播,那么b的“requires_grad”就是True;如果a和z的“requires_grad”都是False那么,b的也是False。
  • 而且运算结果变量的“requires_grad”是不可以更改的,且不会改变。用户创建变量的“requires_grad”是可以更改的。
    在这里插入图片描述在这里插入图片描述Gradients:
    我们再来建立一个能体现出梯度计算过程的计算图:
import torch
x = torch.ones(2,3,requires_grad = True)
y = x + 2
z = y*y
out = torch.mean(z)

在这里插入图片描述
先看下里面的变量都有什么:
在这里插入图片描述
再来看一下上面讨论过的grad_fn的值:
在这里插入图片描述

  • 可见作为leaf Variable的x,是一个用户创建的变量,它的grad_fn是None。而其余三个变量都是一个运算的结果,其grad_fn是一个与运算对应的对象。

  • 计算图已经构建好了,下面来计算梯度。所计算的梯度都是结果变量关于创建变量的梯度。在这个计算图中,能计算的梯度有三个,分别是out,z和y关于x的梯度,以out关于x的梯度为例:

  • 要在变量out处执行.backward(),这时开始计算梯度,由梯度计算的链式法则算到所有的结果变量(graph leaves),这个图中只有一个x。然后在创建变量处执行.grad,获得结果变量out关于创建变量x的梯度。
    niha你在这里插入图片描述

  • 若是关于graph leaves求导的结果变量是一个标量,那么gradient默认为None,或者指定为“torch.Tensor([1.0])”
  • 若是关于graph leaves求导的结果变量是一个向量,那么gradient是不能缺省的,要是和该向量同纬度的tensor
  • 还不明白,转至知乎,以上内容大部分引用该篇博客

3、实战部分

  • 终于来到了我擅长的部分,首先我会讲一下大致思路,后续也会对代码进行详解
  1. 构造数据,使用log函数进行构造并加入高斯一些噪音点
  2. 网络结构搭建,较为简单。使用全连接层并加入sigmoid激活函数
  3. 定义MSE(均方差)损失函数和SGD(随机梯度下降)优化器
  4. 训练初始模型,建立baseline(基线)
  5. 模型预测,观察baseline的表现如何
  6. 模型参数调优,对learning rate,momentum进行调整
  7. 使用调好的参数进行模型预测,与baseline进行对比
  8. 模型保存与模型加载
  • 以上步骤算是比较全面的,生米煮成熟饭就看这波的了

3.1. 构造数据

import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor,Lambda,Compose
import matplotlib.pyplot as plt 
import numpy as np
import random

# 伪造数据,进行学习
feature = np.linspace(2,100,1000)
label = np.log(feature)+np.random.rand(1000)*0.2

# 这里之所以改成float32类型,是因为pytorch训练网络默认使用float32进行运算
# 使用float64都会报错
feature = np.array(feature,np.float32)
label = np.array(label,np.float32)

plt.figure(figsize = (10,6))
plt.plot(feature,label)
plt.xlabel('feature')
plt.ylabel('label')
plt.title('real data')
plt.show()

Out:
在这里插入图片描述

3.2. 网络结构搭建

# 选择合适的处理器,有gpu就用gpu,没有就会使用cpu。一般默认也是使用cpu
device = "cuda" if torch.cuda.is_available() else "cpu"
print ('we ll use {} device'.format(device))

# Define model
class SimpleNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear_relu = nn.Sequential(
            nn.Linear(1,10),
            nn.Sigmoid(),
#             nn.Linear(100,10),
#             nn.ReLU(),
            nn.Linear(10,1),
#             nn.ReLU()
        )
    def forward(self,x):
        logits = self.linear_relu(x)
        return logits
        
model = SimpleNet().to(device)
print (model)

Out:

  • 以上 super().init()表示将父类的属性也导入字类当中,使用class类型构建网络模型,既高端又上档次。还不明白,点这里

在这里插入图片描述

3.3. 定义损失函数以及优化器

# define optimize
loss = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(),lr = 1e-4)

# prepare the training data
batch_size = 10
# 这里可能会感觉奇怪,为什么要用dataloader,没必要,batchsize直接切块numpy就能简单解决
# 这里主要感受torch中DataLoader的使用,权当学习
data_loader = DataLoader(np.concatenate((feature.reshape(1000,-1),label.reshape(1000,-1)),axis = 1),batch_size=10)
  • 其中MSE损失函数不明白,点这里;优化器是随机梯度下降算法,反向传播结束后,我们会把梯度值存到torch 的grad属性中去,我们要用一种比较好的算法,使用grad值来更新权重
  • 使用dataloader权当学习,你也可以print出来,研究研究

3.4. 建立baseline

def train(data_loader,model,loss_fn,optimize):
    loss = 0
    for batch,data in enumerate(data_loader):
#         一般训练神经网络都要以batch的形式传入
#         这里我们只有一个特征,所以维度是[10,1]。就算设置batch为1,网络接收的维度也得是[1,1]
        x = data[:,0].reshape(10,-1)
        y = data[:,1].reshape(10,-1)
        pred = model(x)
        loss = loss_fn(pred,y)
#         backpropagation 反向传播
        optimize.zero_grad()
        loss.backward()
        optimize.step()
        if batch % 10 == 0:
            loss , current = loss.item(),batch*len(x)
#             print ('loss equal {},current sample number equal {}.'.format(loss,current))
            
    return loss

# 训练模型建立baseline
# 由于batchsize=10,数据大小1000,所以一个epoch更新100次参数
# 设置500个epoch,也就是说,要更新5万次参数
epochs = 500
loss_plot = []
for e in range(epochs):
    loss_val = train(data_loader,model,loss,optimizer)
    if e%10 == 0:
        loss_plot.append(loss_val)
    if e % 100 == 0:
        print ('Epoch is {},loss is {}'.format(e+1,loss_val))
print ('over')
plt.plot([e for e in range(0,epochs,batch_size)],loss_plot)
plt.xlabel('epoch')
plt.ylabel('loss of MSE')
plt.title('loss of MSE changed owing to epoch')
plt.show()

Out:

  • optimize.zero_grad(): PyTorch文档中提到,如果grad属性不为空,新计算出来的梯度值会直接加到旧值上面。 为什么不直接覆盖旧的结果呢?这是因为有些Tensor可能有多个输出,那么就需要调用多个backward。 叠加的处理方式使得backward不需要考虑之前有没有被计算过导数,只需要加上去就行了。我们的情况很简单,就一个输出,所以需要使用这条语句
  • loss.backward():这条语句并不会更新参数,它只会求出各个中间变量的grad(梯度)值
  • optimize.step():这个时候它出现了,之前我们定义过optimizer(优化器),也添加了如学习率的参数。使用这条语句就能按照你设定的优化策略来更新参数,当然就会用到loss.backward求出的梯度信息。
    在这里插入图片描述

3.5. 模型预测,观察baseline表现

# 对比真实数据和预测数据
pred = model(torch.Tensor(feature).reshape(-1,1))
pred = pred.reshape(1000).detach().numpy()
plt.figure(figsize = (10,6))
plt.plot(feature,label,color='black',label = 'real data')
plt.plot(feature,pred,color='blue',label = 'pred data',marker='o')
plt.xlabel('feature')
plt.ylabel('result')
plt.title('result changed with feature')
plt.legend()

plt.show()
# 目前预测模型可认定为baseline(基线),拟合效果只能说一般般

Out:
在这里插入图片描述

3.6. 模型参数调优

  • 对learning-rate,momentum进行优化
  • 个人水平有限,以下代码可读性较差,理解下吧
# 这一步开始模型参数调优,主要对于leaningrate,学习率自适应算法momentum以及epochs进行网格搜索
# 其实网络模型也可以优化,这也属于调优的一部分,这里就不做这一部分了
def model_tuning(parameter_str,lr,momentum,epochs = 100):
    plt.figure(figsize = (10,8))
    tuning_parameter = lr if parameter_str == 'lr' else momentum
    loss_min = []
    for i in range(len(tuning_parameter)):
    #     初始化model,保证lr在同一起跑线上
        model = SimpleNet()
        optimizer = None
        if parameter_str == 'lr':
            optimizer = torch.optim.SGD(model.parameters(),lr = tuning_parameter[i],momentum=1)
        else:
            optimizer = torch.optim.SGD(model.parameters(),lr = lr,momentum=tuning_parameter[i])
        loss_plot = []
        for e in range(epochs):
            loss_val = train(data_loader,model,loss,optimizer)
            if e % 10 == 0:
                loss_plot.append(loss_val)
        print ('when {} equal {},min loss equal {} ,here epoch set as 100'.format(parameter_str,tuning_parameter[i],np.min(loss_plot)))
        loss_min.append(np.min(loss_plot))
        plt.plot([e for e in range(0,epochs,batch_size)],loss_plot,label = tuning_parameter[i])
    print ('the best {} is {},it got {} min loss,congratulation!'.format(parameter_str,tuning_parameter[np.argmin(loss_min)],np.min(loss_min)))
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('loss of MSE')
    plt.title('loss of MSE changed owing to epoch when differnt {}'.format(parameter_str))
    plt.show()
# learningrate adjustment
lr = [1e-3,1e-4,1e-5,1e-6,1e-7]
momentum = 1
model_tuning('lr',lr,momentum)
# 从下图的信息可知,1e-4和1e-5较为稳定,暂且我们就使用1e-4作为我们的learningrate(其实我觉得1e-5也可以)
# momentum adjustment
# momentum原理 https://blog.csdn.net/yinruiyang94/article/details/77944338
# 看到这里是不是发现,调参实际上就是控制变量阿
lr = 1e-4
momentum = [0.1,0.3,0.5,0.7,0.9]
model_tuning('momentum',lr,momentum)
# 由下图可知,momentum设置为0.9是个不错的选择

Out:

  • 如下只是单纯做了个网格搜索。通过下图可视化,发现learning-rate为1e-4,momentum为0.9 时模型表现较为优异。momentum详解
    在这里插入图片描述在这里插入图片描述

3.7. 使用调好的参数进行模型预测

# 对比真实数据和预测数据
pred = model(torch.Tensor(feature).reshape(-1,1))
pred = pred.reshape(1000).detach().numpy()
plt.figure(figsize = (10,6))
plt.plot(feature,label,color='black',label = 'real data')
plt.plot(feature,pred,color='blue',label = 'pred data',marker='o')
plt.xlabel('feature')
plt.ylabel('result')
plt.title('result changed with feature')
plt.legend()

plt.show()
# 看这个图就很明显,这不比baseline强多了!!!

Out:

  • 看吧,这比baseline要好多少
    在这里插入图片描述

3.8. 模型保存与模型加载

# save the model
torch.save(model.state_dict(),'model.pth')
print ('have already save model to model.pth')
model = SimpleNet()
model.load_state_dict(torch.load('model.pth'))
  • 模型的保存以及加载就如上了,以下我再打印下model.state_dict()给你康康
    在这里插入图片描述如有疑惑,以下评论区留言。力所能及,必答之。
  • 10
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值