pytorch从基础到实战

目录

    1、前言
    2、基础部分
        2.1.pytorch特点
        2.2. torch的自动求导
    3、实战部分
        3.1. 构造数据
        3.2. 网络结构搭建
        3.3. 定义损失函数以及优化器
        3.4. 建立baseline
        3.5. 模型预测,观察baseline表现
        3.6. 模型参数调优
        3.7. 使用调好的参数进行模型预测
        3.8. 模型保存与模型加载

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()给你康康
    在这里插入图片描述
    如有疑惑,以下评论区留言。力所能及,必答之。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容