【PyTorch】建造神经网络

本文为 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)

运行后得到神经网络的结构:

agZMcj.png

这里将优化器的学习率选小一些,方便我们观察。分类问题的代价函数需要使用 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)。每次将整个数据集放入神经网络中训练。我们还可以做一些优化,将整个数据拆成小批,每次在神经网络中放入一个小批的数据进行训练。虽然这种方式不能确定一定会收敛到最优解,但通常会近似满足最优解,并且训练速度也会大大加快。

传统神经网络参数更新是 W + = l r d x W+=-lr*dx (其中 l r lr 为学习率)这会使学习过程十分曲折,就像一个醉汉摇摇晃晃才能到达目的地。而 Momentum 将醉汉从平地放到斜坡上,往下走一点就会由于惯性而一直下降,走的弯路会变少,其数学形式为:
m = b 1 m l r d x m=b_1*m-lr*dx

W + = m W+=m

另一种加速方式是 AdaGrad,通过修改学习率使得每个参数的更新都有不同的学习效率。相当于给醉汉一个不好走的鞋子,走弯路时较疼,鞋子成了走弯路的阻力,逼着他直走。数学形式为:
v + = d x 2 v+=dx^2

W + = l r d x / v W+=-lr*dx/\sqrt{v}

还可以将上述两种方法结合起来,就得到 RMSProp 法,让其同时具备两者的优势,其数学形式为:
v = b 1 v + ( 1 b 1 ) d x 2 v=b_1*v+(1-b_1)*dx^2

W + = l r d x / v W+=-lr*dx/\sqrt{v}

不过还缺少 α d x -\alpha*dx 部分。我们在 Adam 法中补充这部分,计算 m m 时有 Momentum 的下坡属性,更新 v v 时有 AdaGrad 的阻力属性,更新参数时将 m , v m,v 都考虑进去:
m = b 1 m + ( 1 b 1 ) d x m=b_1*m+(1-b_1)*dx

v = b 2 v + ( 1 b 2 ) d x 2 v=b_2*v+(1-b_2)*dx^2

W + = l r d x / v W+=-lr*dx/\sqrt{v}

实验证明,使用 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 种优化器优化的效率:

在这里插入图片描述

我们可以尝试更换优化器的参数,得到更好的效果。

猜你喜欢

转载自blog.csdn.net/weixin_44413191/article/details/107849852