引言
pytorch作为一门主流的深度学习框架,API是非常多的,但使用pytorch搭建神经网络模型时,往往套路又较为固定。因此通过一些实际的例子去熟悉如何搭建模型、如何训练、测试数据,在实践中学习的效果往往要比漫无目的的学语法和API要好的多。
本文主要从最为基础的线性回归着手,严格来说线性回归更多的在机器学习中讨论,但是我们也能搭建一个简单的“神经网络”,用深度学习的套路来解决这个问题,而这个基本套路,即使是在搭建极为复杂的神经网络中也是适用的。
本文不会对深度学习的一些基本概念做过多的阐述,主要是以应用为导向,穿插着介绍pytorch的一些常用方法。
任务目标
线性回归的式子满足:y = w*x + b,其中w和b均为参数,x,y可以理解成特征值和目标值,在程序中我们指定x,w,b,目标值y也随之确定了下来。然后将x通过神经网络进行训练,我们希望通过训练,使得随机初始化的参数w和b能够逼近一开始我们指定的w,b,使得预测值pred和实际的目标值y能够无限接近。
初始化数据
这里我们假设有50个数据,采用随机赋值的方式定义x,然后取w=3,b=0.8,得到的y = 3 * x + 0.8作为目标值,这里x的随机赋值用pytorch的rand()方法实现:
# 1. 定义数据
x = torch.rand([50,1])
y = x*3 + 0.8
这里我们观察到,x的形状是[50,1],也就是50行1列的数据,50代表一共有50条数据,相当于batch_size,这里由于样本数量本身就不多,所以并没有分成多个batch,而是一次性将所有样本放到神经网络中去训练。
y通过上面的运算,形状也是[50,1],对应着x的目标值。
构建模型
nn.Modul
是torch.nn
提供的一个类,是pytorch中我们自定义网络的一个基类,在这个类中定义了很多有用的方法,让我们在继承这个类定义网络的时候非常简单。
当我们自定义网络的时候,有两个方法需要特别注意:
-
__init__
需要调用super
方法,继承父类的属性和方法 -
farward
方法必须实现,用来定义我们的网络的向前计算(前向传播)的过程
用前面的y = wx+b
的模型举例如下:
from torch import nn
class Lr(nn.Module):
def __init__(self):
super(Lr, self).__init__() #继承父类init的参数
self.linear = nn.Linear(1, 1)
def forward(self, x):
out = self.linear(x)
return out
注意:
-
nn.Linear
为torch预定义好的线性模型,也被称为全链接层,传入的参数为输入的数量,输出的数量(in_features, out_features),是不算(batch_size的列数),在线性回归的模型中,x除了50这个batch_size的维度之后,其实际的特征只有一列,因输出也只要一列就足够,因此nn.Linear的参数是(1,1)。 -
nn.Module
定义了__call__
方法,实现的就是调用forward
方法,即Lr
的实例,能够直接被传入参数调用,实际上调用的是forward
方法并传入参数。如上面代码中所示,一般我们都在__init__方法中定义模型一些所需要的层,而在forward方法中,将传入模型的数据通过__init__方法中定义的层,还能在里面加一些激活函数等,也就是对数据进行处理,最后将这个值返回。
定义了模型后,使用这个模型可以分为以下两步:
# 实例化模型
model = Lr()
# 传入数据,计算结果
predict = model(x)
优化器类
优化器(optimizer
),可以理解为torch为我们封装的用来进行更新参数的方法,比如常见的随机梯度下降(stochastic gradient descent,SGD
)
优化器类都是由torch.optim
提供的,例如
-
torch.optim.SGD(参数,学习率)
-
torch.optim.Adam(参数,学习率)
注意:
-
参数可以使用
model.parameters()
来获取,获取模型中所有requires_grad=True
的参数 -
优化类的使用方法
-
实例化
-
所有参数的梯度,将其值置为0
-
反向传播计算梯度
-
更新参数值
-
示例如下:
optimizer = optim.SGD(model.parameters(), lr=1e-3) #1. 实例化,1e-3也可以写成0.001
optimizer.zero_grad() #2. 梯度置为0
loss.backward() #3. 计算梯度
optimizer.step() #4. 更新参数的值
损失函数
前面的例子是一个回归问题,torch中也预测了很多损失函数
-
均方误差:
nn.MSELoss()
,常用于回归问题 -
交叉熵损失:
nn.CrossEntropyLoss()
,常用于分类问题
使用方法:
model = Lr() #1. 实例化模型
criterion = nn.MSELoss() #2. 实例化损失函数
optimizer = optim.SGD(model.parameters(), lr=1e-3) #3. 实例化优化器类
for i in range(100):
y_predict = model(x_true) #4. 向前计算预测值
loss = criterion(y_true,y_predict) #5. 调用损失函数传入真实值和预测值,得到损失结果
optimizer.zero_grad() #5. 当前循环参数梯度置为0
loss.backward() #6. 计算梯度
optimizer.step() #7. 更新参数的值
训练模型
在训练模型中,一般会有以下几个步骤:
- 定义一个epoch,代表需要将所有数据训练epoch个轮次
- 数据传入模型,获取预测值
- 将预测值和目标值传入损失函数,计算损失
- 优化器的梯度归零,在每次更新参数中必须进行此步骤,否则梯度会一直累加
- 计算梯度,此步骤在4之后进行
- 更新梯度,参数随之更新
- (可选)在训练过程中每隔一段时间打印下损失,观察收敛速度
代码如下:
#训练模型
for i in range(30000):
out = model(x) #3.1 获取预测值
loss = criterion(y,out) #3.2 计算损失
optimizer.zero_grad() #3.3 梯度归零
loss.backward() #3.4 计算梯度
optimizer.step() # 3.5 更新梯度
if (i+1) % 20 == 0:
print('Epoch[{}/{}], loss: {:.6f}'.format(i,30000,loss.data))
模型评估/测试
在模型的测试中,我们一般会使用测试集来评估训练得到的模型,这时候我们不需要梯度相关的操作,只需要将数据通过模型,得到损失、精确率等即可。测试中有以下需要注意:
model.eval()
表示设置模型为评估模式,即预测模式
model.train(mode=True)
表示设置模型为训练模式
在当前的线性回归中,上述并无区别
但是在其他的一些模型中,训练的参数和预测的参数会不相同,到时候就需要具体告诉程序我们是在进行训练还是预测,比如模型中存在Dropout,BatchNorm的时候。
为了更直观的观察预测值和真实值的拟合情况,我们使用matplotlib绘制了两个图,代码如下:
# 训练模型
for i in range(30000):
out = model(x) #3.1 获取预测值
loss = criterion(y,out) #3.2 计算损失
optimizer.zero_grad() #3.3 梯度归零
loss.backward() #3.4 计算梯度
optimizer.step() # 3.5 更新梯度
if (i+1) % 20 == 0:
print('Epoch[{}/{}], loss: {:.6f}'.format(i,30000,loss.data))
线性回归完整代码
import torch
from torch import nn
from torch import optim
import numpy as np
from matplotlib import pyplot as plt
# 1. 定义数据
x = torch.rand([50,1])
y = x*3 + 0.8
# 2 .定义模型
class Lr(nn.Module):
def __init__(self):
super(Lr,self).__init__()
self.linear = nn.Linear(1,1)
def forward(self, x):
out = self.linear(x)
return out
# 2. 实例化模型,loss,和优化器
model = Lr()
# 损失函数
criterion = nn.MSELoss()
# 优化器
optimizer = optim.SGD(model.parameters(), lr=1e-3)
# 3. 训练模型
for i in range(30000):
out = model(x) #3.1 获取预测值
loss = criterion(y,out) #3.2 计算损失
optimizer.zero_grad() #3.3 梯度归零
loss.backward() #3.4 计算梯度
optimizer.step() # 3.5 更新梯度
if (i+1) % 20 == 0:
print('Epoch[{}/{}], loss: {:.6f}'.format(i,30000,loss.data))
# 4. 模型评估
model.eval() #设置模型为评估模式,即预测模式
predict = model(x)
predict = predict.data.numpy()
plt.scatter(x.data.numpy(),y.data.numpy(),c="r")
plt.plot(x.data.numpy(),predict)
plt.show()
在GPU上运行
深度学习的一些模型一般都会用到GPU来进行训练,此时我们需要将代码稍作调整:
-
判断GPU是否可用
torch.cuda.is_available()
torch.device("cuda:0" if torch.cuda.is_available() else "cpu") >>device(type='cuda', index=0) #使用gpu >>device(type='cpu') #使用cpu
-
把模型参数和input数据转化为cuda的支持类型
model.to(device) x_true.to(device)
-
在GPU上计算结果也为cuda的数据类型,需要转化为numpy或者torch的cpu的tensor类型
predict = predict.cpu().detach().numpy()
detach()
的效果和data的相似,但是detach()
是深拷贝,data是取值,是浅拷贝
GPU上运行的完整代码
import torch
from torch import nn
from torch import optim
import numpy as np
from matplotlib import pyplot as plt
import time
# 1. 定义数据
x = torch.rand([50,1])
y = x*3 + 0.8
# 2 .定义模型
class Lr(nn.Module):
def __init__(self):
super(Lr,self).__init__()
self.linear = nn.Linear(1,1)
def forward(self, x):
out = self.linear(x)
return out
# 2. 实例化模型,loss,和优化器
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
x,y = x.to(device),y.to(device)
model = Lr().to(device)
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=1e-3)
# 3. 训练模型
for i in range(30000):
out = model(x)
loss = criterion(y,out)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (i+1) % 20 == 0:
print('Epoch[{}/{}], loss: {:.6f}'.format(i,30000,loss.data))
# 4. 模型评估
model.eval()
predict = model(x)
predict = predict.cpu().detach().numpy() #转化为numpy数组
plt.scatter(x.cpu().data.numpy(),y.cpu().data.numpy(),c="r")
plt.plot(x.cpu().data.numpy(),predict)
plt.show()