深度学习入门

谁说我不能喝 提交于 2019-12-02 14:50:43

Deep Learning with Pytorch_002

chapter03_深入研究神经网络的构建块

  1. 在上一章中,我们使用Py Torch的低级操作来构建模块,如网络体系结构、损失函数和优化器。在本章中,我们将探讨解决现实世界问题所需的神经网络的一些重要组成部分,以及PyTorch如何通过提供大量的高级函数来抽象出大量的复杂性。
    本章将讨论以下主题:
  • 深入研究神经网络的各种构建块
  • 探索PyTorch中的高级功能来构建深度学习体系结构
  • 将深度学习应用在一个真实图像分类问题
  1. 任何深入的学习训练都需要获取数据,构建一个总体上是将一堆层聚集在一起的体系结构。
  2. 理解PyTorch为构建层、损失函数和优化器提供的更高层次的抽象。
  • 层次——神经网络的基本块

    • 最重要的层之一——线性层

      对输入的数据应用线性变换:y=xAT+byy = xA^T + by

       torch.randn(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor
      

      返回一个由均值为0、方差为1的正态分布(标准正态分布)中的随机数组成的张量。
      outiN(0,1)out i ∼N(0,1)
      torch.from_numpy(ndarray) → Tensor是将numpy类型转换为Tensor的函数

       # from_numpy 测试 ,从numpy.ndarray中创建一个张量,
       # 返回的张量和ndarray共享相同的内存。张量的变化将反映在ndarray中,反之亦然;
       # 返回的张量是不可调整的(这句话是什么意思)
       a = numpy.array([1, 2, 3])
       t = torch.from_numpy(a)
       print(t)
       # tensor([ 1,  2,  3])
       print(a)
       t[0] = -1
       print(a)
       # array([-1,  2,  3])
       print(t)
       # tensor([-1,  2,  3])
      

      完整的线性变换实现代码(还没用linear):

       import torch
       torch.__version__
       import numpy as np
       import matplotlib.pyplot as plt
       from torch.autograd import Variable
       #   Creating data for our neural network
       def get_data():
           train_X = np.asarray([3.3, 4.4, 5.5, 6.71, 6.93, 4.168, 9.779, 6.182, 7.59, 2.167, 7.042, 10.791, 5.313, 7.997, 5.654, 9.27, 3.1])
           train_Y = np.asarray([1.7, 2.76, 2.09, 3.19, 1.694, 1.573, 3.366, 2.596, 2.53, 1.221, 2.827, 3.465, 1.65, 2.904, 2.42, 2.94, 1.3])
           dtype = torch.FloatTensor
           X = Variable(torch.from_numpy(train_X).type(dtype), requires_grad=False).view(17, 1)    # 对X进行转置
           y = Variable(torch.from_numpy(train_Y).type(dtype), requires_grad=False)
           return X, y
       # Creating learnable parameters
       def get_weights():
           w = Variable(torch.randn(1), requires_grad=True)
           b = Variable(torch.zeros(1), requires_grad=True)
           return w, b
       #   Network implementation
       def simple_network(x):
           y_pred = torch.matmul(x, w) + b
           return y_pred
       #   loss function
       def loss_fn(y, y_pred):
           loss = torch.mean((y - y_pred) ** 2)
           for param in [w, b]:
               if not param.grad is None: 
                  param.grad.data.zero_()  # 第一次时,需要将梯度清零将梯度
                  loss.backward()  # 计算可学习参数w和b的梯度
           return loss.data
       # Optimize the neural network
       def optimize(learning_rate):
           w.data -= learning_rate * w.grad.data
           b.data -= learning_rate * b.grad.data
       learning_rate = 0.005
       x,y = get_data()
       w,b = get_weights()
       num_epochs = 100    # 这个数据不能太大,太大会加大误差
       for epoch in range(num_epochs):
           inputs = x
           targets = y
           # Forward pass
           outputs = simple_network(inputs)
           # Backward and optimize
           loss = loss_fn(outputs, targets)
           optimize(learning_rate)
           if (epoch + 1) % 10 == 0:
              print('Epoch [{}/{}], Loss: {:.4f}'.format(epoch + 1, num_epochs, loss.item()))
              print('outputs:')
              print(outputs)
              print(loss.item())
       plt.plot(x.detach().numpy(), y.detach().numpy(), 'ro', label='Original Data')
       plt.plot(x.detach().numpy(), outputs.detach().numpy(), label='Fitting Line')
       plt.legend()
       plt.show()
      

      但在Pytorch中,它的强大之处在于, 对于线性层的线性变换:Y=Wx+b,在上面编写的整个函数可以用一行代码编写,如下所示:

      from torch.nn import Linear, ReLU
      myLayer = Linear(in_features=10,out_features=5,bias=True)  # 输  入张量大小 为10,输出为5
      
      torch.nn.Linear(in_features, out_features, bias=True)   #in_feature ——每个输入样本的大小 ;out_feature——每个输出样本的大小;bias——如果设置为False,则该层将不会学习加性偏差,默认值:True。
      

      具体实现代码如下(包含所需的导入包):

      import torch
      from torch.autograd import Variable
      from torch.nn import Linear, ReLU
      inp = Variable(torch.randn(1,10))    #创建输入数据
      print('inp:')
      print(inp)
      myLayer = Linear(in_features=10,out_features=5,bias=True)  # 输入张量大小为10,输出为5
      myLayer(inp)
      print('myLayer(inp):')
      print(myLayer(inp))
      print('myLayer.weight:')
      print(myLayer.weight)
      print('myLayer.bias:')
      print(myLayer.bias)
      

      实现线性变换,使用Pytorch的框架Linear:

      # 使用Pytorch中的Linear实现线性变换(能实现的代码)
      import torch
      import torch.nn as nn
      import numpy as np
      import matplotlib.pyplot as plt
      # Hyper-parameters
      input_size = 1
      output_size = 1
      num_epochs = 60
      learning_rate = 0.001
      # Toy dataset
      x_train = np.array([[3.3], [4.4], [5.5], [6.71], [6.93], [4.168],[9.779], [6.182], [7.59], [2.167], [7.042],[10.791], [5.313], [7.997], [3.1]], dtype=np.float32)
      y_train = np.array([[1.7], [2.76], [2.09], [3.19], [1.694], [1.573],[3.366], [2.596], [2.53], [1.221], [2.827],[3.465], [1.65], [2.904], [1.3]], dtype=np.float32)
      # Linear regression model
      model = nn.Linear(input_size, output_size)
      # Loss and optimizer
      criterion = nn.MSELoss()
      optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
      # Train the model
      for epoch in range(num_epochs):
          # Convert numpy arrays to torch tensors
          inputs = torch.from_numpy(x_train)
          targets = torch.from_numpy(y_train)
          # Forward pass
          outputs = model(inputs)
          loss = criterion(outputs, targets)
          # Backward and optimize
          optimizer.zero_grad()
          loss.backward()
          optimizer.step()
          if (epoch + 1) % 5 == 0:
             print('Epoch [{}/{}], Loss: {:.4f}'.format(epoch + 1, num_epochs, loss.item()))
      # Plot the graph
      predicted = model(torch.from_numpy(x_train)).detach().numpy()
      plt.plot(x_train, y_train, 'ro', label='Original data')
      plt.plot(x_train, predicted, label='Fitted line')
      plt.legend()
      plt.show()
      

      线性层由不同的名称调用,例如跨不同框架的密集层或完全连接层。用于解决真实世界用例的深层学习体系结构通常包含一个以上的层,在PyTorch中,我们可以通过多种方式来实现它,如下所示:

      • 一种简单的方法是将一个层的输出传递给另一个层
        # 将一个层的输出传递给另一个层
        myLayer1 = Linear(10,5)
        myLayer2 = Linear(5,2)
        myLayer2(myLayer1(inp))
        print('myLayer1(inp):')
        print(myLayer1(inp))
        print('myLayer2(myLayer1(inp)):')
        print(myLayer2(myLayer1(inp)))
        

      线性层的缺点:具有两个不同层的体系结构可以简单地表示为具有不同层的单层。因此,仅仅叠加多个线性层将无助于算法学习任何新的东西。

      • 示例:
        Y=2(3X1)              #  2 Linear Layers
        Y=6(X1)               #1 Linear Layer
        
  • 非线性激活
    非线性激活是接受输入,然后应用数学变换并产生输出的函数。为了解决线性变换中的问题,引进不同的非线性函数来帮助学习不同的关系,而不是只关注线性关系。PyTorch将这些非线性功能作为层提供,能够像使用线性层一样使用它们。
    一些常用的非线性函数如下:

    • Sigmoid

      • Sigmoid函数的表达式如下:
        σ(x)=1/(1+e(x))σ(x)=1/(1+e^(-x^) )
      • Sigmoid函数直观地获取一个实数,并输出在0到1之间的一个数字;对于较大的负数,它返回接近于零的值;对于一个较大的正数,它返回接近1的值。
      • Sigmoid函数的缺点:当Sigmoid函数的输出接近于零或1时,Sigmoid函数之前各层的梯度接近于零。前一层的可学习参数使得梯度接近于零,权重不被调整,导致神经元死亡。
    • Tanh

      • tanh非线性函数将实数压缩在-1和1的范围内,但当tanh输出接近-1 和1的极值时,tanh也面临同样的饱和梯度问题;然而,它比Sigmoid更 好,因为tanh的输出是以零为中心的。
    • ReLU

      • 在最近几年里,ReLU变得越来越流行;它有一个简单的数学公式:
        f(x)=max(0,x)f(x)=max(0,x)
      • ReLU将所有输入为负值的数压缩为0,并留下正数。
      • ReLU帮助优化器更快地找到正确的权重集;更严格地说,它使随机梯度下降的收敛速度更快。
      • ReLU在计算上很便宜,因为只是在进行阈值处理,而不是像对Sigmoid和切线函数那样计算。
      • ReLU有一个缺点:当一个大的梯度在反向传播过程中通过它时。常常变得没有反应;它们被称为神经元死亡,它可以通过仔细选择学习率来控制。
    • Leaky ReLU

      • Leaky ReLU试图解决一个濒死的问题,而不是饱和到零,我们饱和到一个非常小的数字,例如0.001;对于某些使用情况,该激活功能为其他应用提供了卓越的性能,但这并不一致。
    • ReLU(线性整流函数)、Leaky ReLU(带泄露线性整流函数)、PReLU(参数化修正线性单元)和RReLU(随机纠正线性单元)的比较:在这里插入图片描述

  • PyTorch 非线性激活

    代码:

    #  A quick example of how to use the ReLU function in PyTorch
    sample_data = Variable(torch.Tensor([[1,2,-1,-1]]))
    myReLU = ReLU()
    print('myReLU(sample_data):')
    print(myReLU(sample_data))
    print('myReLU(sample_data).size():')
    print(myReLU(sample_data).size())
    

    在上面的代码中,取一个具有两个正值和两个负值的张量,并在其上应用一个ReLU函数处理,它将负数阈值化为0并保留原来的正数。
    输出结果如下:

    myReLU(sample_data):
    tensor([[1., 2., 0., 0.]])
    myReLU(sample_data).size():
    torch.Size([1, 4])
    
  1. 构建深度学习算法的PyTorch方法
  • Pytorch中的所有网络都是由类实现的,而Pytorch中的子类由nn.Module调用
  • 类中应该实现__init__方法和__forward__方法
  • 在__init__方法中初始化所有层,例如线性层
  • 在__forward__方法中,将输入数据传递到在__init__方法中初始化的层中,并返回最终输出。
  • 非线性函数通常直接应用于forward函数中,也有一些在init方法中使用。
  • 下面的代码片段展示了如何在PyTorch中实现深度学习架构:
    class MyFirstNetwork(nn.Module):
       def __init__(self,input_size,hidden_size,output_size):
          super(MyFirstNetwork,self).__init__()
          self.layer1 = nn.Linear(input_size,hidden_size)
          self.layer2 = nn.Linear(hidden_size,output_size)
       def __forward__(self,input):
          out = self.layer1(input)
          out = nn.ReLU(out)
          out = self.layer2(out)
          return out
    
    上面所示代码中做的是继承父类并在该类中实现两种方法,在Python中,我们将父类作为参数传递给类名,从而实现子类;init方法充当构造函数,super用于将子类的参数传递给父类,在上面的例子中是nn.Module
  1. 不同机器学习问题的模型结构
  • 我们正在解决的问题将主要决定我们将使用哪些层,从线性层到用于顺序数据的长短期存储器(LSTM),根据所要解决的问题的类型,最后一层是确定的;我们通常使用机器学习或深度学习算法来解决三个问题。
    • 1.对于回归问题,例如预测t恤的价格,我们使用最后一层作为一个线性层,具有一个输出,并且输出一个连续的值。
    • 2.要将给定的图像分类为t恤或非t恤,可以使用Sigmoid激活函数,因为它输出的值要么接近1,要么接近于0,这通常称为二进制分类问题。
    • 3.对于多类别分类,我们必须将给定图像分类为T恤、牛仔裤、衬衫或连衣裙;我们将在网络的末尾使用Softmax层,例如,它接受前一个线性层的输入,并为给定数量的示例输出概率,在我们的例子中,它将被训练来预测每种类型的图像的四个概率,并且这四个概率和为1。
  1. Loss functions
  • 一旦我们定义了我们的网络架构,我们就剩下两个重要的步骤:一个是计算我们的网络在执行特定的回归、分类;下一步就是优化权重。

  • 优化器(梯度下降)通常接受一个标量值,所以loss函数应该生成一个标量值,并且该值在训练中必须最小化。而对于某些用例,如预测道路上的障碍物并将其划分为行人或非行人,将需要两个或更多的损失函数。但即使在这种情况下,也需要将损失合并为一个标量,以便优化器最小化。

  • PyTorch中用于回归和分类的loss函数,应用示例:

    # Loss functions
    loss = nn.MSELoss()
    input = Variable(torch.randn(3,5),requires_grad=True)
    target = Variable(torch.randn(3,5))
    output = loss(input,target)
    output.backward()
    print('input:')
    print(input)
    print('target:')
    print(target)
    print('output:')
    print(output)
    
  • 交叉熵损失:它计算了一个预测概率的分类网络的损失,这个概率应该加起来总和为1,就像Softmax层一样;当预测概率偏离正确概率时,交叉熵损失增加;例如,如果我们的分类算法预测某图像的0.1概率是猫,但实际上是熊猫,那么交叉熵损失将更高;如果它预测与实际标签相似,那么交叉熵损失就会降低。

    • 交叉熵损失函数的原理
      在二分类问题模型:例如逻辑回归「Logistic Regression」、神经网络「Neural Network」等,真实样本的标签为 [0,1],分别表示负类和正类。交叉熵损失模型的最后通常会经过一个 Sigmoid 函数,输出一个概率值,这个概率值反映了预测为正类的可能性:概率越大,可能性越大。
      预测输出即 Sigmoid 函数的输出表征了当前样本标签为 1 的概率可表达为:
      y^=P(y=1x)\hat y=P(y=1|x)
      当前样本标签为 0 的概率就可以表达成:
      1y^=P(y=0x)1-\hat y=P(y=0|x)
    • 单个样本交叉熵损失的公式:
      L=[ylog y^+(1y)log (1y^)]L=-[ylog\ \hat y+(1-y)log\ (1-\hat y)]
    • 交叉熵损失函数定义:
    # cross_entropy loss
    def cross_entropy(true_label,prediction):
        if true_label == 1:
            return -log(prediction)    # log这块会提示出错,导入了from math import log
        else:
            return -log(1 - prediction)
    

    在分类问题中使用交叉熵损失:

    # Use a cross-entropy loss in a classification problem
    loss = nn.CrossEntropyLoss()
    input = Variable(torch.randn(3,5),requires_grad=True)
    target = Variable(torch.LongTensor(3).random_(5))
    output = loss(input,target)
    output.backward()
    print('output.backward():')
    print(output.backward())
    

    但是在上面的代码中会报如下错误:
    在这里插入图片描述
    解决方法是:将output.backward()改为output.backward(retain_graph=True),该问题是指在默认情况下,网络在反向传播中不允许多个backward()。需要在第一个backward设置retain_graph=True
    正确输出如下:
    在这里插入图片描述

    • Pytorch中的一些loss函数及其应用范围:
      • L1 loss——主要用作正则化器
      • MSE loss——回归问题的损失函数
      • Cross-entropy loss——用于二进制和多级分类问题
      • NLL Loss——用于分类问题,并允许我们使用特定的权重来处理不平衡的数据集。
      • NLL Loss2d——用于逐像素分类,主要用于与图像分割有关的问题。
  1. 优化网络体系结构
    一旦我们计算出我们的网络损失,我们将优化权重以减少损失,从而提高算法的精度,为了简单起见,让我们将这些优化器看作黑匣子,它们接受丢失函数和所有可学习的参数,并稍微移动它们以提高性能。Pytorch所提供的一些优化器如下所示:
  • ADADELTA
  • Adagrad——自适应梯度算法
  • Adam
  • SparseAdam
  • Adamax
  • ASGD
  • LBFGS
  • RMSProp
  • Rprop
  • SGD——随机梯度下降
  1. 创建一个SGD优化器,它将网络的所有可学习参数作为第一个参数,并创建了一个学习速率,它决定可以对可学习的参数进行多大比例的更改:

     optimizer = optim.SGD(model.parameters(),lr = 0.01)  # 这处的调试为什么不会继续往下执行,而且model不管导入哪个包都出错,所有关于model的包都没有这个attribute
    
     loss = nn.MSELoss()
     for input,target in dataset:
         optimizer.zero_grad()
         output = model(input)
         losss = loss(output,target)
         loss.backward()
         optimizer.step()
         print('loss.backward():')
         print(loss.backward())
    

    一旦创建了优化器对象,就要在循环中调用zero_grad(),因为参数将累积在上一次优化器调用期间创建的梯度;当调用了loss函数中的backward函数(计算梯度更新权值)中后,就需要调用optimizer.step(),对可学习参数进行实际更改。

  2. 基于深度学习的图像分类
    Dogs vs. Cats数据集

  • 调试出现的错误:

    import os
    dir = 'F:\\inner\\kaggle'             # 数据集路径
    list_img = []
    list_label = []
    data_size = 0
    dir = dir + '/train/'
    for file in os.listdir(dir):  # 遍历dir文件夹
        list_img.append(dir + file)  # 将图片路径和文件名添加至image list
        data_size += 1  # 数据集增1
        name = file.split(sep='.')  # 分割文件名,"cat.0.jpg"将分割成"cat",".","jpg"3个元素
        # label采用one-hot编码,"1,0"表示猫,"0,1"表示狗,任何情况只有一个位置 为"1",在采用CrossEntropyLoss()计算Loss情况下,label只需要输入"1"的索引,即猫应输入0,狗应输入1
        if name[0] == 'cat':
            list_label.append(0)  # 图片为猫,label为0
        else:
            list_label.append(1)  # 图片为狗,label为1,注意:list_img和list_label中的内容是一一配对的
    print('data_size:')
    print(data_size)
    

    同一段代码,在两个项目里运行结果不一样

    • 在本地运行时结果:
      在这里插入图片描述
    • 服务器运行结果:
      在这里插入图片描述
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!