Pytorch 神经网络工具箱

Pytorch 神经网络工具箱


本文章代码: gitee

1、神经网络核心组件

  • 神经网络看起来很复杂,节点很多,层数多,参数更多。但核心部分或组件不多,把这些组件确定后,这个神经网络基本就确定了。这些核心组件包括:

    1. 神经网络的基本结构,将输入张量转换为输出张量
    2. 模型层构成的网络
    3. 损失函数参数学习的目标函数,通过最小化损失函数来学习各种参数
    4. 优化器如何是损失函数最小,这就涉及到优化器
  • 多个层链接在一起构成一个模型或网络

    输入数据通过这个模型转换为预测值,然后**损失函数把预测值与真实值进行比较,得到损失值**(损失值可以是距离、概率值等),该损失值用于衡量预测值与目标结果的匹配或相似程度

    优化器利用损失值更新权重参数,从而使损失值越来越小。这是一个循环过程,损失值达到一个阀值或循环次数到达指定次数,循环结束。


2、实现神经网络实例

使用 Pytorch 构建神经网络使用的主要工具(或类)及相互关系:

在这里插入图片描述


3、如何构建神经网络?

关键就是选择网络层构建网络,然后选择损失函数优化器。在 nn 工具箱中,可以直接引用的网络很多,有全连接层、卷积层、循环层、正则化层、激活层等等。假设这些层都定义好了,接下来就是如何组织或构建这些层?

3.1、构建网络层

  1. 采用 torch.nn.Sequential()来构建网络层,这个有点类似 Keras 的 models.Sequential(),使用起来就像搭积木一样,非常方便。不过,这种方法每层的编码是默认的数字,不易区分;
  2. 如果要对每层定义一个名称,我们可以采用 Sequential 的一种改进方法,在 Sequential 的基础上,通过add_module() 添加每一层,并且为每一层增加一个单独的名字
  3. 还可以在 Sequential 基础上,通过字典的形式添加每一层,并且设置单独的层名称
class Net(torch.nn.Module):
    def __init__(self):
        super(Net4, self).__init__()
        self.conv = torch.nn.Sequential(
            OrderedDict(
                [
                    ("conv1", torch.nn.Conv2d(3, 32, 3, 1, 1)),
                    ("relu1", torch.nn.ReLU()),
                    ("pool", torch.nn.MaxPool2d(2))
                ]
            ))
 
        self.dense = torch.nn.Sequential(
            OrderedDict([
                ("dense1", torch.nn.Linear(32 * 3 * 3, 128)),
                ("relu2", torch.nn.ReLU()),
                ("dense2", torch.nn.Linear(128, 10))
            ])
        )

3.2、前向传播

定义好每层后,最后还需要通过前向传播的方式把这些串起来。这就是涉及如何定义 forward 函数的问题:

  • forward 函数的任务需要把输入层、网络层、输出层链接起来,实现信息的前向传导

  • 该函数的参数一般为输入数据,返回值为输出数据

  • 在 forward 函数中,有些层来自 nn.Module,也可以使用 nn.functional 定义:

    来自 nn.Module 的需要实例化,而使用 nn.functional 定义的可以直接使用。

3.3、反向传播

前向传播函数定义好以后,接下来就是梯度的反向传播

  • Pytorch 提供了自动反向传播的功能,使用 nn 工具箱,我们无需自己编写反向传播,直接让损失函数(loss)调用 backward() 即可,非常方便和高效!
  • 在反向传播过程中,优化器是一个重要角色

3.4、训练模型

层、模型、损失函数和优化器等都定义或创建好,接下来就是训练模型

  • 训练模型时需要注意使模型处于训练模式,即调用 model.train()。调用 model.train() 会把所有的 module 设置为训练模式;

    如果是测试或验证阶段,需要使模型处于验证阶段,即调用 model.eval()。调用 model.eval() 会把所有的 training 属性设置为 False;

  • 缺省情况下梯度是累加的,需要手工把梯度初始化或清零,调用 optimizer.zero_grad() 即可

    训练过程中,正向传播生成网络的输出,计算输出和实际值之间的损失值

    调用 loss.backward() 自动生成梯度,然后使用 optimizer.step() 执行优化器,把梯度传播回每个网络

  • 如果希望用 GPU 训练,需要把模型、训练数据、测试数据发送到 GPU 上,即调用 .to(device)。

    如果需要使用多 GPU 进行处理,可使模型或相关数据引用 nn.DataParallel。


4、神经网络工具箱 nn

  • 使用 autograd 及 Tensor 实现机器学习实例时,需要做不少设置,如对叶子节点的参数 requires_grad 设置为 True,然后调用 backward,再从 grad 属性中提取梯度

    对于大规模的网络,autograd 太过于底层和繁琐。为了简单、有效解决这个问题,nn 是一个有效工具。

4.1、nn.Module

  • 它是专门为深度学习设计的一个模块,而 nn.Module 是 nn 的一个核心数据结构:

    nn.Module 可以是神经网络的某个层(layer),也可以是包含多层的神经网络

    在实际使用中,最常见的做法是继承 nn.Module,生成自己的网络/层

    nn 中已实现了绝大多数层,包括全连接层、损失层、激活层、卷积层、循环层等等,这些层都是 nn.Module 的子类,能够自动检测到自己的 Parameter,并将其作为学习参数,且针对 GPU 运行进行了 CuDNN 优化。

4.2、nn.functional

  • nn 中的层,一类是继承了 nn.Module,其命名一般为 nn.Xxx(第一个是大写),

    如 nn.Linear、nn.Conv2d、nn.CrossEntropyLoss 等。

    另一类是 nn.functional 中的函数

    其名称一般为 nn.funtional.xxx,如 nn.funtional.linear、nn.funtional.conv2d、nn.funtional.cross_entropy 等。

  • 从功能来说两者相当,基于 nn.Mudle 能实现的层,使用 nn.funtional 也可实现,反之亦然,而且性能方面两者也没有太大差异。不过在具体使用时,两者还是有区别,主要区别如下:

    1. nn.Xxx 继承于 nn.Module,nn.Xxx 需要先实例化并传入参数,然后以函数调用的方式调用实例化的对象并传入输入数据它能够很好的与 nn.Sequential 结合使用,而 nn.functional.xxx 无法与 nn.Sequential 结合使用
    2. nn.Xxx 不需要自己定义和管理 weight、bias 参数而 nn.functional.xxx 需要你自己定义 weight、bias,每次调用的时候都需要手动传入 weight、bias 等参数, 不利于代码复用
    3. dropout 操作在训练和测试阶段是有区别的使用 nn.Xxx 方式定义 dropout,在调用 model.eval() 之后,自动实现状态的转换,而使用 nn.functional.xxx 却无此功能

    具有学习参数的(例如,conv2d, linear, batch_norm)采用 nn.Xxx 方式。

    没有学习参数的(例如,maxpool, loss func, activation func)等根据个人选择使用 nn.functional.xxx 或者 nn.Xxx 方式。


5、优化器

  • Pytoch 常用的优化方法都封装在 torch.optim 里面,其设计很灵活,可以扩展为自定义的优化方法。所有的优化方法都是继承了基类 optim.Optimizer。并实现了自己的优化步骤。

5.1、使用优化器的一般步骤

  1. 建立优化器实例

    导入 optim 模块,实例化 SGD 优化器,这里使用动量参数 momentum(该值一般在(0,1)之间),是 SGD 的改进版,效果一般比不使用动量规则的要好:

    import torch.optim as optim
    optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)
    

以下步骤在训练模型的 for 循环中

  1. 向前传播

    把输入数据传入神经网络 Net 实例化对象 model 中,自动执行 forward 函数,得到 out 输出值,然后用 out 与标记 label 计算损失值 loss

    out = model(img)
    loss = criterion(out, label)
    
  2. 清空梯度

    缺省情况梯度是累加的,在梯度反向传播前,先需把梯度清零。

    optimizer.zero_grad()
    
  3. 反向传播

    基于损失值,把梯度进行反向传播。

    loss.backward()
    
  4. 更新参数

    基于当前梯度(存储在参数的 .grad 属性中)更新参数。

    optimizer.step()
    

5.2、动态修改学习率参数

  • 修改参数的方式可以通过修改参数 optimizer.params_groups 或新建 optimizer。

    新建 optimizer 比较简单,optimizer 十分轻量级,所以开销很小。但是新的优化器会初始化动量等状态信息,这对于使用动量的优化器(momentum 参数的 sgd)可能会造成收敛中的震荡。所以,这里我们采用直接修改参数optimizer.params_groups。

    optimizer.param_groups:长度 1 的 list,

    optimizer.param_groups[0]:长度为 6 的字典,包括权重参数,lr,momentum 等参数。

for epoch in range(num_epoches):
    #动态修改参数学习率
    if epoch%5==0:
        optimizer.param_groups[0]['lr']*=0.1
        print(optimizer.param_groups[0]['lr'])

5.3、优化器比较

  1. 导入需要的模块

    import torch
    import torch.utils.data as Data
    import torch.nn.functional as F
    import matplotlib.pyplot as plt
    %matplotlib inline
     
     
    # 超参数
    LR = 0.01
    BATCH_SIZE = 32
    EPOCH = 12
    
  2. 生成数据

    # 生成训练数据
    # torch.unsqueeze() 的作用是将一维变二维,torch只能处理二维的数据
    x = torch.unsqueeze(torch.linspace(-1, 1, 1000), dim=1)  
    # 0.1 * torch.normal(x.size())增加噪点
    y = x.pow(2) + 0.1 * torch.normal(torch.zeros(*x.size()))
     
    torch_dataset = Data.TensorDataset(x,y)
    #得到一个代批量的生成器
    loader = Data.DataLoader(dataset=torch_dataset, batch_size=BATCH_SIZE, shuffle=True)
    
  3. 构建神经网络

    class Net(torch.nn.Module):
        # 初始化
        def __init__(self):
            super(Net, self).__init__()
            self.hidden = torch.nn.Linear(1, 20)
            self.predict = torch.nn.Linear(20, 1)
     
        # 前向传递
        def forward(self, x):
            x = F.relu(self.hidden(x))
            x = self.predict(x)
            return x
    
  4. 使用多种优化器

    net_SGD = Net()
    net_Momentum = Net()
    net_RMSProp = Net()
    net_Adam = Net()
     
    nets = [net_SGD, net_Momentum, net_RMSProp, net_Adam]
     
    opt_SGD = torch.optim.SGD(net_SGD.parameters(), lr=LR)
    opt_Momentum = torch.optim.SGD(net_Momentum.parameters(), lr=LR, momentum=0.9)
    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]
    
  5. 训练模型

    loss_func = torch.nn.MSELoss() 
    loss_his = [[], [], [], []]  # 记录损失 
    for epoch in range(EPOCH):
        for step, (batch_x, batch_y) in enumerate(loader):
            for net, opt,l_his in zip(nets, optimizers, loss_his):
                output = net(batch_x)  # get output for every net
                loss = loss_func(output, batch_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']
    
  6. 可视化结果

    for i, l_his in enumerate(loss_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()
    

    在这里插入图片描述


6、手写数字识别案例

  1. 利用 Pytorch 内置函数 mnist 下载数据;

    导包:

    import numpy as np
    import torch
    
    # 导入 pytorch 内置的 mnist 数据
    from torchvision.datasets import mnist 
    
    #导入预处理模块
    import torchvision.transforms as transforms
    from torch.utils.data import DataLoader
    
    #导入nn及优化器
    import torch.nn.functional as F
    import torch.optim as optim
    from torch import nn
    
    import matplotlib.pyplot as plt
    

    定义一些超参数:

    # 定义一些超参数
    train_batch_size = 64
    test_batch_size = 128
    learning_rate = 0.01
    num_epoches = 20
    lr = 0.01
    momentum = 0.5
    

    下载数据:

    #定义预处理函数,这些预处理依次放在Compose函数中。
    transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize([0.5], [0.5])])
    
    #下载数据,并对数据进行预处理
    train_dataset = mnist.MNIST('G:\\work\\coding\\python\\pytorch\\data\\MNIST\\raw', train=True, transform=transform, download=True)
    test_dataset = mnist.MNIST('G:\\work\\coding\\python\\pytorch\\data\\MNIST\\raw', train=False, transform=transform)
    
    • transforms.Compose 可以把一些转换函数组合在一起;

    • Normalize([0.5], [0.5]) 对张量进行归一化,这里两个 0.5 分别表示对张量进行归一化的全局平均值和方差。

      因图像是灰色的只有一个通道,如果有多个通道,需要有多个数字,如三个通道,应该是 Normalize([m1,m2,m3], [n1,n2,n3])

    • download 参数控制是否需要下载,如果 G:\work\coding\python\pytorch\data\MNIST\raw 目录下已有 MNIST,可选择 False;

    • 用 DataLoader 得到生成器,这可节省内存。

  2. 利用 torchvision 对数据进行预处理,调用 torch.utils 建立一个数据迭代器;

    #Dataloader是一个可迭代对象,可以使用迭代器一样使用。
    train_loader = DataLoader(train_dataset, batch_size=train_batch_size, shuffle=True)
    test_loader = DataLoader(test_dataset, batch_size=test_batch_size, shuffle=False)
    
  3. 可视化源数据;

    examples = enumerate(test_loader)
    batch_idx, (example_data, example_targets) = next(examples)
     
    fig = plt.figure()
    for i in range(6):
      plt.subplot(2,3,i+1)
      plt.tight_layout()
      plt.imshow(example_data[i][0], cmap='gray', interpolation='none')
      plt.title("Ground Truth: {}".format(example_targets[i]))
      plt.xticks([])
      plt.yticks([])
    
  4. 利用 nn 工具箱构建神经网络模型;

    神经网络的结构如下:

    在这里插入图片描述

    class Net(nn.Module):
        """
        使用sequential构建网络,Sequential()函数的功能是将网络的层组合到一起
        """
        def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
            super(Net, self).__init__()
            self.layer1 = nn.Sequential(nn.Linear(in_dim, n_hidden_1),nn.BatchNorm1d(n_hidden_1))
            self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, n_hidden_2),nn.BatchNorm1d(n_hidden_2))
            self.layer3 = nn.Sequential(nn.Linear(n_hidden_2, out_dim))
            
     
        def forward(self, x):
            x = F.relu(self.layer1(x))
            x = F.relu(self.layer2(x))
            x = self.layer3(x)
            return x
    
  5. 实例化模型,并定义损失函数及优化器;

    #检测是否有可用的GPU,有则使用,否则使用CPU
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    
    #实例化网络
    model = Net(28 * 28, 300, 100, 10)
    model.to(device)
     
    # 定义损失函数和优化器
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)
    
  6. 训练模型;

    # 开始训练
    losses = []
    acces = []
    eval_losses = []
    eval_acces = []
     
     
    for epoch in range(num_epoches):
        train_loss = 0
        train_acc = 0
        model.train()
        #动态修改参数学习率
        if epoch%5==0:
            optimizer.param_groups[0]['lr']*=0.1
        for img, label in train_loader:
            img=img.to(device)
            label = label.to(device)
            img = img.view(img.size(0), -1)
            # 前向传播
            out = model(img)
            loss = criterion(out, label)
            # 反向传播
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            # 记录误差
            train_loss += loss.item()
            # 计算分类的准确率
            _, pred = out.max(1)
            num_correct = (pred == label).sum().item()
            acc = num_correct / img.shape[0]
            train_acc += acc
            
        losses.append(train_loss / len(train_loader))
        acces.append(train_acc / len(train_loader))
        # 在测试集上检验效果
        eval_loss = 0
        eval_acc = 0
        # 将模型改为预测模式
        model.eval()
        for img, label in test_loader:
            img=img.to(device)
            label = label.to(device)
            img = img.view(img.size(0), -1)
            out = model(img)
            loss = criterion(out, label)
            # 记录误差
            eval_loss += loss.item()
            # 记录准确率
            _, pred = out.max(1)
            num_correct = (pred == label).sum().item()
            acc = num_correct / img.shape[0]
            eval_acc += acc
            
        eval_losses.append(eval_loss / len(test_loader))
        eval_acces.append(eval_acc / len(test_loader))
        print('epoch: {}, Train Loss: {:.4f}, Train Acc: {:.4f}, Test Loss: {:.4f}, Test Acc: {:.4f}'
              .format(epoch, train_loss / len(train_loader), train_acc / len(train_loader), 
                         eval_loss / len(test_loader), eval_acc / len(test_loader)))
    
  7. 可视化结果。

    plt.title('train loss')
    plt.plot(np.arange(len(losses)), losses)
    plt.legend(['Train Loss'], loc='upper right')
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值