本文为 PyTorch 学习总结,讲解建造神经网络。欢迎交流
前言
神经网络分为两种类型,一种是回归,一种是分类。回归的输出值是连续问题,分类的输出值是离散值,我们将分别搭建这两种神经网络。
关系拟合(回归)
首先引入库:
import torch
from torch.autograd import Variable
import torch.nn.functional as F # 激励函数
import matplotlib.pyplot as plt
然后需要自己造一些数据,并设置噪声:
# x data (tensor), shape=(100, 1)
# 在torch中只会处理二维数据,用unsqueeze将一维转换为二维
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1)
# noisy y data (tensor), shape=(100, 1)
y = x.pow(2) + 0.2*torch.rand(x.size())
因为神经网络只能输入 Variable
型数据,将数据装入 Variable
:
x, y = Variable(x), Variable(y)
然后画出散点图,进行数据可视化:
plt.scatter(x.data.numpy(), y.data.numpy())
plt.show()
得到的散点图如下:
接着就要定义我们的神经网络了,其中包括了搭建神经网络所需的信息 __init__
和前向传播过程 forward
,详解见注释:
# 用class定义神经网络,继承torch.n.Module
class Net(torch.nn.Module):
# 官方步骤。搭建神经网络层所需的信息
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__() # 搭图前继承Net
# 接下来是自己的内容
# 层信息都是属性,隐藏层线性输出
# n_feature为输入数据个数,n_hidden为隐藏层神经元个数
self.hidden = torch.nn.Linear(n_feature, n_hidden)
# 预测的神经层,n_hidden为接收的隐藏层神经元个数,n_output为输出个数
self.predict = torch.nn.Linear(n_hidden, n_output)
# 前向传播过程
def forward(self, x): # x为输入信息
# 先用hidden加工x得到隐藏层输出的信息,再用激励函数加工
x = F.relu(self.hidden(x))
# 输出信息,预测无需用激励函数
x = self.predict(x)
return x
搭建完神经网络后,就可以定义 net
了:
net = Net(1, 10, 1)
# 输出神经网络所有层结构
print(net)
得到神经网络所有层结构:
然后还需要对神经网络进行优化,并定义代价函数(损失函数):
# 优化神经网络。传入net的所有参数, 学习率lr<1
optimizer = torch.optim.SGD(net.parameters(), lr=0.5)
# 预测值和真实值的误差计算公式 (均方差),均方差足以应对回归问题
loss_func = torch.nn.MSELoss()
定义完这些步骤后,就可以开始训练我们的神经网络了:
for t in range(100):
prediction = net(x) # 假设函数的预测值
loss = loss_func(prediction, y) # 误差
# 开始优化
optimizer.zero_grad() # 将所有梯度降为0。清空上一步的梯度
loss.backward() # 误差反向传播, 计算参数更新值
# 以学习率0.5优化梯度
optimizer.step() # 将参数更新值施加到net的parameters上
这就是整个神经网络的训练过程了,如果要可视化这个过程,还需要添加一些代码:
# 可视化
plt.ion() # 设置实时打印
plt.show()
optimizer = torch.optim.SGD(net.parameters(), lr=0.5)
loss_func = torch.nn.MSELoss()
for t in range(100):
...
if t % 5 == 0: # 每5步打印
# plot and show learning process
plt.cla()
plt.scatter(x.data.numpy(), y.data.numpy()) # 原始数据
# 神经网络学习到什么程度了
plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)
# 打印误差
plt.text(0.5, 0, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color': 'red'})
plt.pause(0.1)
plt.ioff()
plt.show()
搭建出的神经网络结果为:
由此,我们搭建出了一个简单的神经网络,学习了如何看神经网络,它是如何学习的。
区分类型(分类)
这部分的代码与上一部分相似,只需要进行部分修改,下面进行详细讲解。
数据部分需要进行修改,重新生成一些数据,注意 x,y
部分的数据类型:
n_data = torch.ones(100, 2) # data的基数
x0 = torch.normal(2*n_data, 1) # 类型0 x data (tensor), shape=(100, 2)
y0 = torch.zeros(100) # 类型0 y data (tensor), shape=(100, )
x1 = torch.normal(-2*n_data, 1) # 类型1 x data (tensor), shape=(100, 1)
y1 = torch.ones(100) # 类型1 y data (tensor), shape=(100, )
# 下面为x, y数据的规定形式 (torch.cat合并数据)
# x合并为数据,使用32位FloatTensor的浮点数
x = torch.cat((x0, x1), 0).type(torch.FloatTensor)
# y合并为标签,使用64位LongTensor的整型
y = torch.cat((y0, y1), ).type(torch.LongTensor)
x, y = Variable(x), Variable(y) # 神经网络只能输入Variable
# 打印散点图
# plt.scatter(x.data.numpy()[:, 0], x.data.numpy()[:, 1], c=y.data.numpy(), s=100, lw=0, cmap='RdYlGn')
# plt.show()
绘制生成训练集的图像:
搭建神经网络的代码是类似的,我们神经网络输入两个特征,分别为坐标 x,y
,输出为 2个。分别代表两个类别:
net = Net(2, 10, 2)
运行后得到神经网络的结构:
这里将优化器的学习率选小一些,方便我们观察。分类问题的代价函数需要使用 CrossEntropyLoss()
,计算出每个类型的概率与标签的误差:
optimizer = torch.optim.SGD(net.parameters(), lr=0.02)
loss_func = torch.nn.CrossEntropyLoss()
开始训练,用 softmax
将神经网络的输出转换为概率。每两步绘制一张图:
for t in range(100):
out = net(x) # 喂给net训练数据x, 输出分析值
loss = loss_func(out, y) # 计算两者的误差
optimizer.zero_grad() # 清空上一步的残余更新参数值
loss.backward() # 误差反向传播, 计算参数更新值
optimizer.step() # 将参数更新值施加到 net 的 parameters 上
if t % 2 == 0:
plt.cla()
# 经过softmax的激励函数后的最大概率才是预测值
# 得到最大概率值的位置,[0]返回最大值
prediction = torch.max(F.softmax(out), 1)[1]
# 画图
pred_y = prediction.data.numpy().squeeze()
target_y = y.data.numpy()
plt.scatter(x.data.numpy()[:, 0], x.data.numpy()[:, 1], c=pred_y, s=100, lw=0, cmap='RdYlGn')
accuracy = sum(pred_y == target_y)/200. # 预测中有多少和真实值一样
plt.text(1.5, -4, 'Accuracy=%.2f' % accuracy, fontdict={'size': 20, 'color': 'red'})
plt.pause(0.1)
可以得到神经网络的学习结果:
快速搭建法
我们可以用更加快捷的方式搭建前面的所有神经网络。这里对上文中进行分类的神经网络进行修改,将 class
类可以替换为下面这种方法:
# method2
# 在Sequential中累加神经层
net2 = torch.nn.Sequential(
torch.nn.Linear(2, 10),
# 激励函数也可以当作神经层加在里面
torch.nn.ReLU(),
torch.nn.Linear(10, 2),
)
print(net2)
因为在以前的使用中,我们直接使用了 ReLU
函数,而没有层结构的名字。得到的结果会有 ReLU
层。搭建出的神经网络的结果相同:
保存提取
前面的过程已经训练好了神经网络,我们想要保存现在的状态,之后再提取出来。
首先创建一些数据:
torch.manual_seed(1) # 设计随机初始化种子,用来初始化神经网络
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1) # x data (tensor), shape=(100, 1)
y = x.pow(2) + 0.2*torch.rand(x.size()) # noisy y data (tensor), shape=(100, 1)
在 save
中创建一个神经网络,然后进行保存:
def save():
net1 = torch.nn.Sequential(
torch.nn.Linear(1, 10),
torch.nn.ReLU(),
torch.nn.Linear(10, 1),
)
optimizer = torch.optim.SGD(net1.parameters(), lr=0.2)
loss_func = torch.nn.MSELoss()
for t in range(1000):
prediction = net1(x)
loss = loss_func(prediction, y)
optimizer.zero_grad()
optimizer.step()
torch.save(net1, 'net.pkl') # 保存整个神经网络
torch.save(net1.state_dict(), 'net_params.pkl') # 保存整个参数
# 画图
plt.figure(1, figsize=(10, 3))
plt.subplot(131)
plt.title('Net1')
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)
提取整个神经网络十分简单,但网络大的时候可能会比较慢:
def restore_net():
net2 = torch.load('net.pkl') # 提取
prediction = net2(x)
# 画图
plt.subplot(132)
plt.title('Net2')
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)
提取神经网络的所有参数时,要建立和 net1
一模一样的神经网络,并且比提取整个神经网络要快:
def restore_params():
net3 = torch.nn.Sequential( # 要将net1
torch.nn.Linear(1, 10),
torch.nn.ReLU(),
torch.nn.Linear(10, 1),
)
# 加载一个字典
net3.load_state_dict(torch.load('net_params.pkl'))
prediction = net3(x)
# 画图
plt.subplot(133)
plt.title('Net3')
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)
plt.show()
最后调用三个函数,得到相同的结果:
如果没有得到拟合成功的图像,可以尝试修改学习率和迭代次数。
批训练(min-batch)
有时候神经网络的训练集很大,我们需要用批训练的方式,可以提高神经网络的训练效率。
首先引入库,其中 Data
是我们进行小批训练的途径。定义每次批训练的样本数后生成数据:
import torch
import torch.utils.data as Data
BATCH_SIZE = 5
x = torch.linspace(1, 10, 10)
y = torch.linspace(10, 1, 10)
# 将x,y放入数据库中
torch_dataset = Data.TensorDataset(x, y)
使训练变成小批:
loader = Data.DataLoader(
dataset = torch_dataset,
batch_size=BATCH_SIZE,
shuffle=True, # 每次打乱数据进行抽样,一般会带来更好的效果
# 下面这行windows用户不能使用,会报错
# num_workers=2, # 每次提取x,y使用2个线程,提高效率
)
使用批训练,将数据整体训练 3 次,每次将数据进行拆分:
# 将数据整体训练3次
for epoch in range(3):
# enumerate:loader在每次提取时,都施加索引
for step, (batch_x, batch_y) in enumerate(loader):
# 训练代码,模拟训练
print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
batch_x.numpy(), '| batch y: ', batch_y.numpy())
运行后得到批训练的过程:
如果将批训练的规模设置为 8 个,则 step1 训练完 8 个样本后,剩余的 2 个样本会留到 step2 进行训练。
加速训练
神经网络通常需要大量的训练样本进行训练,这会花费很长的时间。接下来介绍几种优化方法:
首先是最常用的 Stochastic 梯度下降(SGD)。每次将整个数据集放入神经网络中训练。我们还可以做一些优化,将整个数据拆成小批,每次在神经网络中放入一个小批的数据进行训练。虽然这种方式不能确定一定会收敛到最优解,但通常会近似满足最优解,并且训练速度也会大大加快。
传统神经网络参数更新是
(其中
为学习率)这会使学习过程十分曲折,就像一个醉汉摇摇晃晃才能到达目的地。而 Momentum 将醉汉从平地放到斜坡上,往下走一点就会由于惯性而一直下降,走的弯路会变少,其数学形式为:
另一种加速方式是 AdaGrad,通过修改学习率使得每个参数的更新都有不同的学习效率。相当于给醉汉一个不好走的鞋子,走弯路时较疼,鞋子成了走弯路的阻力,逼着他直走。数学形式为:
还可以将上述两种方法结合起来,就得到 RMSProp 法,让其同时具备两者的优势,其数学形式为:
不过还缺少
部分。我们在 Adam 法中补充这部分,计算
时有 Momentum 的下坡属性,更新
时有 AdaGrad 的阻力属性,更新参数时将
都考虑进去:
实验证明,使用 Adam 通常能又快又好的达到目标。
Optimizer 优化器
首先引入模块:
import torch
import torch.utils.data as Data
import torch.nn.functional as F
from torch.autograd import Variable
import matplotlib.pyplot as plt
然后定义一些超参数:
LR = 0.01
BATCH_SIZE = 32
EPOCH = 12
随机生成一些数据:
x = torch.unsqueeze(torch.linspace(-1, 1, 1000), dim=1)
y = x.pow(2) + 0.1*torch.normal(torch.zeros(*x.size()))
plt.scatter(x.numpy(), y.numpy())
plt.show()
绘制出数据的散点图为:
然后对数据进行分批:
torch_dataset = Data.TensorDataset(x, y)
loader = Data.DataLoader(dataset=torch_dataset, batch_size=BATCH_SIZE, shuffle=True)
构建默认形式的神经网络:
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.hidden = torch.nn.Linear(1, 20) # hidden layer
self.predict = torch.nn.Linear(20, 1) # output layer
def forward(self, x):
x = F.relu(self.hidden(x)) # activation function for hidden layer
x = self.predict(x) # linear output
return x
然后建立 4 个神经网络,用不同的优化器进行优化:
net_SGD = Net()
net_Momentum = Net()
net_RMSprop = Net()
net_Adam = Net()
# 合并为list,以便后面用for循环提取
nets = [net_SGD, net_Momentum, net_RMSprop, net_Adam]
建立 4 个优化器:
opt_SGD = torch.optim.SGD(net_SGD.parameters(), lr=LR)
opt_Momentum = torch.optim.SGD(net_Momentum.parameters(), lr=LR, momentum=0.8) # 增加了动量的效果
opt_RMSprop = torch.optim.RMSprop(net_RMSprop.parameters(), lr=LR, alpha=0.9)
opt_Adam = torch.optim.Adam(net_Adam.parameters(), lr=LR, betas=(0.9, 0.99))
optimizers = [opt_SGD, opt_Momentum, opt_RMSprop, opt_Adam]
建立代价函数:
loss_func = torch.nn.MSELoss()
losses_his = [[], [], [], []] # 记录神经网络的误差
开始训练 min-batch 数据并绘图:
for epoch in range(EPOCH):
print('Epoch: ', epoch) # 查看网络是否在训练
for step, (b_x, b_y) in enumerate(loader):
# 用Variable包装数据
b_x = Variable(batch_x)
b_y = Variable(batch_y)
# 对每个优化器, 优化属于他的神经网络
for net, opt, l_his in zip(nets, optimizers, losses_his):
output = net(b_x) # get output for every net
loss = loss_func(output, b_y) # compute loss for every net
opt.zero_grad() # clear gradients for next train
loss.backward() # backpropagation, compute gradients
opt.step() # apply gradients
l_his.append(loss.data.numpy())# loss recoder
# 画图
labels = ['SGD', 'Momentum', 'RMSprop', 'Adam']
for i, l_his in enumerate(losses_his):
plt.plot(l_his, label=labels[i])
plt.legend(loc='best')
plt.xlabel('Steps')
plt.ylabel('Loss')
plt.ylim((0, 0.2))
plt.show()
最终得到 4 种优化器优化的效率:
我们可以尝试更换优化器的参数,得到更好的效果。