PyTorch系列之线性回归|Softmax与分类模型|多层感知机

线性回归|Softmax与分类模型|多层感知机

Posted by Young on February 11, 2020

Task01:线性回归|Softmax与分类模型|多层感知机

Torch 操作

  • torch 乘法

    • torch.mm(a, b)
      • mm只能进行矩阵乘法,不可以是tensor,也就是输入的两个tensor维度只能是 $(n\times m)$ 和 $(m\times p)$
    • torch.mul(a, b)
      • 是矩阵a和b对应位相乘,a和b的维度必须相等,比如a的维度是(1, 2),b的维度是(1, 2),返回的仍是(1, 2)的矩阵
    • torch.matmul(a,b)
      • 可以进行张量乘法, 输入可以是高维
    • torch.bmm(a,b)
      • 两个三维张量相乘, 两个输入tensor维度是 $(b\times n\times m)$ 和 $(b\times m\times p)$ , 第一维b代表batch size,输出为 $(b\times n \times p)$
  • torch 加法

    • 第一种 x + y
    • 第二种 torch.add(x, y)
    • 第三种 torch.add(x, y, out=result)
    • 第四种 y.add_(x)
  • 查看 torch 的大小

    • torch.size()
    • torch.shape
    • torch.view() 用来改变 torch 的 shape
    • torch.item() 查看 tensor 的 data 值
  • Tensor 和其他类型相互转换

    • Tensor转NumPy:
      • a = torch.ones(5).numpy() 使用的相同的内存
    • NumPy数组转Tensor
      • b = torch.from_numpy(np.ones(5)) 使用的相同的内存
      • c = torch.tensor(a) 进行数据拷贝,返回的Tensor和原来的数据不再共享内存
  • 常数初始化:

    • torch.empty(size) 返回形状为size的空tensor
    • torch.zeros(size) 全部是0的tensor
    • torch.zeros_like(input) 返回跟input的tensor一个size的全零tensor
    • torch.ones(size) 全部是1的tensor
    • torch.ones_like(input) 返回跟input的tensor一个size的全一tensor
    • torch.arange(start=0, end, step=1) 返回一个从start到end的序列,可以只输入一个end参数,就跟python的range()一样了。实际上PyTorch也有range(),但是这个要被废掉了,替换成arange了
    • torch.full(size, fill_value) 这个有时候比较方便,把fill_value这个数字变成size形状的张量
  • 随机抽样(随机初始化):
    • torch.rand(size) [0,1)内的均匀分布随机数
    • torch.rand_like(input) 返回跟input的tensor一样size的0-1随机数
    • torch.randn(size) 返回标准正太分布N(0,1)的随机数
    • torch.normal(mean, std, out=None) 正态分布。这里注意,mean和std都是tensor,返回的形状由mean和std的形状决定,一般要求两者形状一样。如果,mean缺失,则默认为均值0,如果std缺失,则默认标准差为1.
  • 自动求梯度

    • Tensor是这个包的核心类,如果将其属性.requires_grad设置为True,它将开始追踪(track)在其上的所有操作。完成计算后,可以调用.backward()来完成所有梯度计算。此Tensor的梯度将累积到.grad属性中。

    • 反向传播更新参数 以下三句话一句也不能少:

      1. 将上次迭代计算的梯度值清0 optimizer.zero_grad()
      2. 反向传播,计算梯度值 loss.backward()
      3. 更新权值参数 optimizer.step()
    • 直接调用backward()方法,会计算对计算图叶节点 x 的导数。

      • 如果你要求导的是一个标量,那么gradients默认为None,所以前面可以直接调用J.backward()就行了

      • 如果你要求导的是一个量,那么gradients应该传入一个Tensor

    • 一个计算图只能backward一次:计算图在进行反向求导之后,为了节省内存,就会销毁了。

    • 中断梯度追踪with torch.no_grad():

  • 奇技淫巧

    • torch.manual_seed(1)

      利用随机数种子来使pytorch中的结果可以复现

Torch 复杂函数理解

  • torch.gather()

    • 函数torch.gather(input, dim, index, out=None) → Tensor 沿给定轴 dim ,将输入索引张量 index 指定位置的值进行聚合.

    • 应用:softmax+交叉熵损失

    • dim = 1 按列聚合

      import torch
      a = torch.randint(0, 30, (2, 3, 5))
      print(a)
      '''
      tensor([[[ 18.,   5.,   7.,   1.,   1.],
               [  3.,  26.,   9.,   7.,   9.],
               [ 10.,  28.,  22.,  27.,   0.]],
          
              [[ 26.,  10.,  20.,  29.,  18.],
               [  5.,  24.,  26.,  21.,   3.],
               [ 10.,  29.,  10.,   0.,  22.]]])
      '''
      index = torch.LongTensor([[[0,1,2,0,2],
                                [0,0,0,0,0],
                                [1,1,1,1,1]],
                              [[1,2,2,2,2],
                               [0,0,0,0,0],
                               [2,2,2,2,2]]])
      print(a.size()==index.size())
      b = torch.gather(a, 1,index)
      print(b)
      '''
      True
      tensor([[[ 18.,  26.,  22.,   1.,   0.],
               [ 18.,   5.,   7.,   1.,   1.],
               [  3.,  26.,   9.,   7.,   9.]],
          
              [[  5.,  29.,  10.,   0.,  22.],
               [ 26.,  10.,  20.,  29.,  18.],
               [ 10.,  29.,  10.,   0.,  22.]]])
      可以看到沿着dim=1,也就是列的时候。输出tensor第一页内容,
      第一行分别是 按照index指定的,
      input tensor的第一页 
      第一列的下标为0的元素 第二列的下标为1元素 第三列的下标为2的元素,第四列下标为0元素,第五列下标为2元素
      index-->0,1,2,0,2    output--> 18.,  26.,  22.,   1.,   0.
      '''
      
    • dim =2 按行聚合

      c = torch.gather(a, 2,index)
      print(c)
      '''
      tensor([[[ 18.,   5.,   7.,  18.,   7.],
               [  3.,   3.,   3.,   3.,   3.],
               [ 28.,  28.,  28.,  28.,  28.]],
          
              [[ 10.,  20.,  20.,  20.,  20.],
               [  5.,   5.,   5.,   5.,   5.],
               [ 10.,  10.,  10.,  10.,  10.]]])
      dim = 2的时候就安装 行 聚合了。参照上面的举一反三。
      '''
      
    • dim = 0 按批量batch_size聚合

      index2 = torch.LongTensor([[[0,1,1,0,1],
                                [0,1,1,1,1],
                                [1,1,1,1,1]],
                              [[1,0,0,0,0],
                               [0,0,0,0,0],
                               [1,1,0,0,0]]])
      d = torch.gather(a, 0,index2)
      print(d)
      '''
      tensor([[[ 18.,  10.,  20.,   1.,  18.],
               [  3.,  24.,  26.,  21.,   3.],
               [ 10.,  29.,  10.,   0.,  22.]],
          
              [[ 26.,   5.,   7.,   1.,   1.],
               [  3.,  26.,   9.,   7.,   9.],
               [ 10.,  29.,  22.,  27.,   0.]]])
      这个有点特殊,dim = 0的时候(三维情况下),是从不同的页收集元素的。
      这里举的例子只有两页。所有index在0,1两个之间选择。
      输出的矩阵元素也是按照index的指定。分别在第一页和第二页之间跳着选的。
      index [0,1,1,0,1]的意思就是。
      在第一页选这个位置的元素,在第二页选这个位置的元素,在第二页选,第一页选,第二页选。
          
      '''
      
  • nn.Linear

    • SOURCE class Linear(Module):

      class Linear(Module):
          r"""Applies a linear transformation to the incoming data: :math:`y = xA^T + b`
      
          Args:
              in_features: size of each input sample
              out_features: size of each output sample
              bias: If set to ``False``, the layer will not learn an additive bias.
                  Default: ``True``
      
          Examples::
              >>> m = nn.Linear(20, 30)
              >>> input = torch.randn(128, 20)
              >>> output = m(input)
              >>> print(output.size())
          torch.Size([128, 30])
          """
          __constants__ = ['bias', 'in_features', 'out_features']
          
          def __init__(self, in_features, out_features, bias=True):
              super(Linear, self).__init__()
              self.in_features = in_features
              self.out_features = out_features
              self.weight = Parameter(torch.Tensor(out_features, in_features))
              if bias:
              self.bias = Parameter(torch.Tensor(out_features))
              else:
              self.register_parameter('bias', None)
              self.reset_parameters()
          
          def forward(self, input):
              return F.linear(input, self.weight, self.bias)
      

Linear Regression

  • 线性回归:均方误差 + mini-batch SGD

  • 线性回归模型从零开始的实现

    • 初始化模型参数

      w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float32)
      b = torch.zeros(1, dtype=torch.float32)
          
      w.requires_grad_(requires_grad=True)
      b.requires_grad_(requires_grad=True)
      
    • 定义模型

      def linreg(X, w, b):  
          return torch.mm(X, w) + b
      
    • 定义损失函数

      def squared_loss(y_hat, y):  
          return (y_hat - y.view(y_hat.size())) ** 2 / 2
      
    • 定义优化算法

      def sgd(params, lr, batch_size): 
          for param in params:
              param.data -= lr * param.grad / batch_size # 注意这里更改param时用的param.data
      
    • 训练模型

      for epoch in range(num_epochs):  
          for X, y in data_iter(batch_size, features, labels):
              l = loss(net(X, w, b), y).sum()  
              l.backward()  
              sgd([w, b], lr, batch_size)  
          
              w.grad.data.zero_()
              b.grad.data.zero_()
          train_l = loss(net(features, w, b), labels)
          print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
      
  • 线性回归模型使用pytorch的简洁实现

    • 定义模型
      class LinearNet(nn.Module):
          def __init__(self, n_feature):
              super(LinearNet, self).__init__()
              self.linear = nn.Linear(n_feature, 1)
      
          def forward(self, x):
              y = self.linear(x)
              return y
      
      net = LinearNet(num_inputs)
      
    • 初始化模型参数

      from torch.nn import init
            
      init.normal_(net[0].weight, mean=0.0, std=0.01)
      init.constant_(net[0].bias, val=0.0) 
      
    • 定义损失函数

      loss = nn.MSELoss()
      
    • 定义优化算法

      import torch.optim as optim
            
      optimizer = optim.SGD(net.parameters(), lr=0.03)
      
    • 训练模型

      for epoch in range(1, num_epochs + 1):
          for X, y in data_iter:
              output = net(X)
              l = loss(output, y.view(-1, 1))
              optimizer.zero_grad() 
            l.backward()
          optimizer.step()
          print('epoch %d, loss: %f' % (epoch, l.item()))
      

softmax和分类模型

  • softmax和分类模型:输出层加 softmax + 交叉熵损失

  • softmax回归模型的从零开始实现

    • 定义模型

      def softmax(X):
          X_exp = X.exp()
          partition = X_exp.sum(dim=1, keepdim=True)
          return X_exp / partition  # 这里应用了广播机制
      
      def net(X):
          return softmax(torch.mm(X.view((-1, num_inputs)), W) + b)
      
    • 定义损失函数

      def cross_entropy(y_hat, y):
          return - torch.log(y_hat.gather(1, y.view(-1, 1)))
      
  • softmax使用pytorch的简洁实现

    • 定义模型

      class FlattenLayer(nn.Module):
          def __init__(self):
              super(FlattenLayer, self).__init__()
          def forward(self, x): # x shape: (batch, *, *, ...)
              return x.view(x.shape[0], -1)
          
      from collections import OrderedDict
      net = nn.Sequential(
              # FlattenLayer(),
              # nn.Linear(num_inputs, num_outputs)
              OrderedDict([
                ('flatten', FlattenLayer()),
                ('linear', nn.Linear(num_inputs, num_outputs))])
              )
      
    • softmax与交叉熵损失函数

      loss = nn.CrossEntropyLoss()
      
    • 优化算法

      optimizer = torch.optim.SGD(net.parameters(), lr=0.1)
      

多层感知机

  • 多层感知机:隐层加激活函数 -》全连接层+relu+全连接层

  • 多层感知机从零开始实现

    • 定义模型

      def relu(X):
          return torch.max(input=X, other=torch.tensor(0.0))
            
      def net(X):
          X = X.view((-1, num_inputs))
          H = relu(torch.matmul(X, W1) + b1)
          return torch.matmul(H, W2) + b2 
      
  • 多层感知机使用pytorch的简洁实现

    • 模型定义

      num_inputs, num_outputs, num_hiddens = 784, 10, 256
              
      net = nn.Sequential(
              d2l.FlattenLayer(),
              nn.Linear(num_inputs, num_hiddens),
              nn.ReLU(),
              nn.Linear(num_hiddens, num_outputs), 
              )
              
      for params in net.parameters():
          init.normal_(params, mean=0, std=0.01)