PyTorch笔记3:神经网络对数据进行分类

Sep 12,2018   8022 words   29 min

Tags: DeepLearning

1.神经网络数据分类

在这篇博客中,我们基于上篇博客所建立的神经网络对数据进行分类,代码如下。

import torch
from matplotlib import pyplot as plt
import torch.nn.functional as F


# 自定义一个Net类,继承于torch.nn.Module类
# 这个神经网络的设计是只有一层隐含层,隐含层神经元个数可随意指定
class Net(torch.nn.Module):
    # Net类的初始化函数
    def __init__(self, n_feature, n_hidden, n_output):
        # 继承父类的初始化函数
        super(Net, self).__init__()
        # 网络的隐藏层创建,名称可以随便起
        self.hidden_layer = torch.nn.Linear(n_feature, n_hidden)
        # 输出层(预测层)创建,接收来自隐含层的数据
        self.predict_layer = torch.nn.Linear(n_hidden, n_output)

    # 网络的前向传播函数,构造计算图
    def forward(self, x):
        # 用relu函数处理隐含层输出的结果并传给输出层
        hidden_result = self.hidden_layer(x)
        relu_result = F.relu(hidden_result)
        predict_result = self.predict_layer(relu_result)
        return predict_result


# 训练次数
TRAIN_TIMES = 200
# 输入输出的数据维度
INPUT_FEATURE_DIM = 2
OUTPUT_FEATURE_DIM = 2
# 隐含层中神经元的个数
NEURON_NUM = 10
# 学习率,越大学的越快,但也容易造成不稳定,准确率上下波动的情况
LEARNING_RATE = 0.01

# 数据构造
# 先生成一个100行,2列的基础数据
n_data = torch.ones(100, 2)
# normal函数用于生成符合指定条件的正太分布数据,传入的参数是均值和标准差
x0 = torch.normal(2 * n_data, 1)
x1 = torch.normal(-2 * n_data, 1)
# 这里的y并不是y坐标的意思,而是代表数据的类型标签,是整个网络的输出,以0、1表示不同类别
# 输入:(x[0],x[1])   输出:y
y0 = torch.zeros(100)
y1 = torch.ones(100)
# torch.cat是在合并数据,将a,b按行放在一起,如果第二个参数是0,则按列放在一起
x = torch.cat((x0, x1), 0).type(torch.FloatTensor)  # FloatTensor = 32-bit floating
y = torch.cat((y0, y1), ).type(torch.LongTensor)  # LongTensor = 64-bit integer

# 建立网络
net = Net(n_feature=INPUT_FEATURE_DIM, n_hidden=NEURON_NUM, n_output=OUTPUT_FEATURE_DIM)
print(net)

# 训练网络
# 这里也可以使用其它的优化方法
optimizer = torch.optim.Adam(net.parameters(), lr=LEARNING_RATE)
# 定义一个误差计算方法,分类问题可以采用交叉熵来衡量
loss_func = torch.nn.CrossEntropyLoss()

for i in range(TRAIN_TIMES):
    # 输入数据进行预测
    prediction = net(x)
    # 计算预测值与真值误差,注意参数顺序问题
    # 第一个参数为预测值,第二个为真值
    loss = loss_func(prediction, y)

    # 开始优化步骤
    # 每次开始优化前将梯度置为0
    optimizer.zero_grad()
    # 误差反向传播
    loss.backward()
    # 按照最小loss优化参数
    optimizer.step()

    # 可视化训练结果
    plt.cla()
    prediction = torch.max(prediction, 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 = float((pred_y == target_y).astype(int).sum()) / float(target_y.size)
    plt.text(-1, -4.7, 'Time=%d Accuracy=%.2f' % (i, accuracy), fontdict={'size': 15, 'color': 'red'})
    plt.pause(0.1)

和之前代码相比,主要改动在数据生成和可视化部分,以及更换了代价函数。 同时为了演示减少了神经元个数和学习率,否则学习速度太快了,看不出变化的过程。 下面是运行的结果。 采用SGD算法。 采用Adam算法。可以发现Adam算法在30步左右即到了100%,而SGD算法在60步左右才100%。以上便是利用神经网络解决简单二分类问题的代码。 和上一篇的拟合相比,核心的神经网络架构都没有变化,只是修改了输入输出,但却实现了不同的功能。所以也可以体会到神经网络适用性还是比较广的。

关于torch中随机数生成的各种方法,可以参考这篇博客,总结地挺好的。

2.批训练

在之前TensorFlow的笔记中也提到过,相比于将数据整体放入网络训练,采用分批训练效果会更好一些。 在PyTorch中提供了DataLoader用于实现这个需求,放在torch.utils.data中。示例代码如下。

import torch
import torch.utils.data as Data

torch.manual_seed(1)  # reproducible

BATCH_SIZE = 4  # 批训练的数据个数

x = torch.linspace(1, 10, 10)  # x data (torch tensor)
y = torch.linspace(10, 1, 10)  # y data (torch tensor)

# 先转换成 torch 能识别的 Dataset
torch_dataset = Data.TensorDataset(x, y)

# 把 dataset 放入 DataLoader
loader = Data.DataLoader(
    dataset=torch_dataset,  # torch TensorDataset format
    batch_size=BATCH_SIZE,  # mini batch size
    shuffle=True,  # 要不要打乱数据 (打乱比较好)
    # 多线程来读数据,在windows下这句话可能会报错,如果报错就注释掉,可能是个bug
    # num_workers=2,
)

for epoch in range(3):  # 训练所有!整套!数据 3 次
    for step, (batch_x, batch_y) in enumerate(loader):  # 每一步 loader 释放一小批数据用来学习
        # 假设这里就是你训练的地方...

        # 打出来一些数据
        print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
              batch_x.numpy(), '| batch y: ', batch_y.numpy())

运行结果如下。 这里简单再说一下batch、batch size、step和epoch的关系。 对于一堆数据,我们可以将其分成好几份(Batch)来训练,每份中包含的数据个数即为batch size,每训练一份即是一个Step。 当把这几份全部训练完的时候也就是一个Epoch。 所以Epoch可以理解为从头到尾训练一遍全部数据的次数。 所以训练的总次数(输入输出一次数据算一次)等于Epoch×Batch Size×Batch。 例如一共有10个数据,分成5个batch,每个batch有两个数据,整体训练3次。 总次数就是2×5×3=30。

3.优化器

详细优化器介绍可参考教程。 SGD是最普通的优化器,也可以说没有加速效果,而Momentum是SGD的改良版,它加入了动量原则。 后面的后面的RMSprop又是Momentum的升级版,而Adam又是RMSprop的升级版。 但并不是越先进的优化器,结果越佳。我们在自己的试验中可以尝试不同的优化器,找到那个最适合你数据/网络的优化器。 下面代码分别用4种优化器进行训练。

import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt

# torch.manual_seed(1)    # reproducible

LR = 0.1

# fake dataset
x = torch.unsqueeze(torch.linspace(-2, 2, 500), dim=1)
y = x.pow(3) + 0.1 * torch.normal(torch.zeros(*x.size()))


# default network
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


if __name__ == '__main__':
    # different nets
    net_SGD = Net()
    net_Momentum = Net()
    net_RMSprop = Net()
    net_Adam = Net()

    # different optimizers
    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))

    loss_func = torch.nn.MSELoss()
    losses_his = [[], [], [], []]  # record loss

    plt.figure(figsize=(10, 8))

    # training
    for step in range(200):  # for each training step

        output1 = net_SGD(x)
        loss1 = loss_func(output1, y)
        opt_SGD.zero_grad()
        loss1.backward()
        opt_SGD.step()
        losses_his[0].append(loss1.data.numpy())

        plt.subplot(221).cla()
        plt.scatter(x.numpy(), y.numpy(), c='blue')
        plt.plot(x.numpy(), output1.data.numpy(), c='red')
        plt.title('SGD,Time=%d Loss=%.4f' % (step, loss1.data.numpy()))

        output2 = net_Momentum(x)
        loss2 = loss_func(output2, y)
        opt_Momentum.zero_grad()
        loss2.backward()
        opt_Momentum.step()
        losses_his[1].append(loss2.data.numpy())

        plt.subplot(222).cla()
        plt.scatter(x.numpy(), y.numpy(), c='blue')
        plt.plot(x.numpy(), output2.data.numpy(), c='red')
        plt.title('Momentum,Time=%d Loss=%.4f' % (step, loss2.data.numpy()))

        output3 = net_RMSprop(x)
        loss3 = loss_func(output3, y)
        opt_RMSprop.zero_grad()
        loss3.backward()
        opt_RMSprop.step()
        losses_his[2].append(loss3.data.numpy())

        plt.subplot(223).cla()
        plt.scatter(x.numpy(), y.numpy(), c='blue')
        plt.plot(x.numpy(), output3.data.numpy(), c='red')
        plt.title('RMSprop,Time=%d Loss=%.4f' % (step, loss3.data.numpy()))

        output4 = net_Adam(x)
        loss4 = loss_func(output4, y)
        opt_Adam.zero_grad()
        loss4.backward()
        opt_Adam.step()
        losses_his[3].append(loss4.data.numpy())

        plt.subplot(224).cla()
        plt.scatter(x.numpy(), y.numpy(), c='blue')
        plt.plot(x.numpy(), output4.data.numpy(), c='red')
        plt.title('Adam,Time=%d Loss=%.4f' % (step, loss4.data.numpy()))

        plt.pause(0.1)

    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.show()

学习率为0.1,训练200次运行效果如下。 以及不同方法收敛速度对比图如下。 此外,也用教程中给出的代码采用分批训练跑了一下,代码如下。

import torch
import torch.utils.data as Data
import torch.nn.functional as F
import matplotlib.pyplot as plt

# torch.manual_seed(1)    # reproducible

LR = 0.01
BATCH_SIZE = 32
EPOCH = 12

# fake dataset
x = torch.unsqueeze(torch.linspace(-1, 1, 1000), dim=1)
y = x.pow(2) + 0.1*torch.normal(torch.zeros(*x.size()))

# # plot dataset
# plt.scatter(x.numpy(), y.numpy())
# plt.show()

# put dateset into torch dataset
torch_dataset = Data.TensorDataset(x, y)
loader = Data.DataLoader(dataset=torch_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2,)


# default network
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

if __name__ == '__main__':
    # different nets
    net_SGD         = Net()
    net_Momentum    = Net()
    net_RMSprop     = Net()
    net_Adam        = Net()
    nets = [net_SGD, net_Momentum, net_RMSprop, net_Adam]

    # different optimizers
    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 = [[], [], [], []]   # record loss

    # training
    for epoch in range(EPOCH):
        print('Epoch: ', epoch)
        for step, (b_x, b_y) in enumerate(loader):          # for each training step
            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()

结果如下。

本文作者原创,未经许可不得转载,谢谢配合

返回顶部