Pytorch basic knowledge

Pytorch 基础

Posted by biggan on August 14, 2018

Pytorch 基础

— ModuleList 和Sequential比较

Sequential实现了forward函数,然而ModuleList里面没有forward函数。ModuleList本质是多个Moudle的list,与普通list的区别是它能被主module所识别,其参数会加入主module的参数中。

1.Sequential用法

实例化

(1)先实例化nn.Sequential(),再调用add_module方法添加子模块。

net = nn.Sequential()
net.add_module('conv', nn.Conv2d(3, 3, 3))
net.add_module('batchnorm', nn.BatchNorm2d(3))

(2)实例化nn.Sequential()同时,加入各子模块。

net = nn.Sequential(
        nn.Conv2d(3, 3, 3),
        nn.BatchNorm2d(3)
        )

(3)实例化nn.Sequential()同时,使用OrderedDict加入各子模块并命名。

from collections import OrderedDict
net= nn.Sequential(OrderedDict([
          ('conv', nn.Conv2d(3, 3, 3)),
          ('batchnorm', nn.BatchNorm2d(3)),
          ('activation_layer', nn.ReLU())
        ]))

调用

(1)直接调用整个模块

input = torch.rand(1, 3, 4, 4)
output = net(input) #此时会一次性调用模块中各子模块,故在定义时,各子模块的维度和顺序要安排好,即前一个子模块的输出符合下一个子模块的输入要求。

(2)依次调用各子模块

input = torch.rand(1, 3, 4, 4)
'''名字调用子模块'''
output = net.conv(input) 
output = net.batchnorm(output)
'''索引调用子模块'''
# output = net[0](input)
#output = net[1](output)

2.ModuleList用法

#1.实例化
linears = nn.ModuleList([nn.Linear(input_size,layers_size)])
#2.扩展
linears.extend([nn.Linear(layers_size, layers_size) for i in range(1, self.num_layers-1)])
#3.添加元素
linears.append(nn.Linear(layers_size, output_size)
#4.调用
	#错误操作
    #output = modelist(input)	#modellist没有实现forward方法
    #正确操作
    for model in modellist:
    	input = model(input)          

#####

—Parameter相关

  1. Module.parameters()

构建好神经网络后,网络的参数都保存在parameters属性当中。

一般先实例化一个预先定义好的网络net,再net.parameters()得到这个网络的参数,用作优化器的输入,则网络参数会随着训练进行更新。

当模型涉及多个网络时,可将这多个网络的parameters转化成list,再相加,作为优化器的输入。

   param = list(encoder.parameters()) 	+list(decoder.parameters())
   optimizer = optim.Adam(param, lr=1e-3)
  1. torch.nn.Parameter()

    self.v = torch.nn.Parameter(torch.FloatTensor(hidden_size))
    

可理解为一个类型转换函数,将一个不可训练的类型Tensor转换成可以训练的类型parameter类型,并将这个parameter绑定到这个module里面(即网络的Module.parameters()中)。

  1. torch.nn.ParameterList()

类似于moduleList(),都在定义网络时使用。只不过ParameterList()列表元素是Parameter()类型的变量,而moduleList()列表元素是各种module。

   class MyModule(nn.Module):
       def __init__(self):
           super(MyModule, self).__init__()
           self.params = nn.ParameterList([nn.Parameter(torch.randn(10, 10)) for i in range(10)])
   
       def forward(self, x):
           for i, p in enumerate(self.params):
               x = self.params[i // 2].mm(x) + p.mm(x)
           return x

— 模型保存和加载

1.两种模型保存和加载方式

  • 保存整个网络的的结构信息和模型参数信息,对象是网络net。

    # 保存整个模型
    torch.save(model_object, 'resnet.pth')
    # 加载整个模型
    model = torch.load('resnet.pth')
    
  • 保存神经网络的模型参数,对象是网络的参数net.state_dict()。

    # 保存模型参数
    torch.save(my_resnet.state_dict(), "my_resnet.pth")
    # 加载模型参数
    my_resnet = resnet() #加载模型参数前要先实例化模型
    my_resnet.load_state_dict(torch.load("my_resnet.pth"))
    

2.GPU上训练好的模型,怎么在CPU上运行

# torch.load时添加 map_location 和loc参数
encoder = EncoderRNN(lang_size, hidden_size)
encoder.load_state_dict(torch.load('./encoder.pth', map_location=lambda storage, loc: storage))

—variable, tensor与numpy相互转化的方法

  1. list、numpy $ \rightarrow$ Tensor、Variable、cuda
s_list = [1, 2, 3]
s_numpy = np.array(s_list)				#list	----	numpy		
s_tensor = torch.Tensor(s_list)			#list	----	tensor
s_tensor = torch.from_numpy(s_numpy)	#numpy	----	tensor
s_variable = Variable(s_tensor)			#tensor	----	variable
s_cuda = s_tensor.cuda()				#tensor	----	cuda
s_cuda = s_variable.cuda()				#variable	----	cuda
  1. Tensor、Variable、cuda $ \rightarrow$ list、numpy
s_list = s_cuda.cpu().data.numpy().tolist()	#cuda - variable - tensor - numpy - list
s = s_tensor.item()	#当tensor是0维时使用,返回其对应的数值。 一般用在累加loss中。

—Python、Numpy、Torch中的类型转换

  • Python

    a = 1
    b = str(a)
    print(a, type(a))   # 1 <class 'int'>
    print(b, type(b))   # 1 <class 'str'>
    
  • Numpy

    a = np.array([1, 2, 3])
    b = a.astype(np.int16)
    c = a.astype(float) # 同np.float、np.float64
    print(a, a.dtype)   # [1 2 3] int32
    print(b, b.dtype)   # [1 2 3] int16
    print(c, c.dtype)   # [1. 2. 3.] float64
    print(type(a))		# <class 'numpy.ndarray'>
    
  • Torch

    a = torch.Tensor([0, 1, 0])
    b = a.bool()
    c = a.type_as(torch.BoolTensor())
    d = a.type(torch.BoolTensor)
    print(a, a.type())	# tensor([0., 1., 0.]) torch.FloatTensor
    print(b, b.type())	# tensor([False,  True, False]) torch.BoolTensor
    print(c, c.type())	# tensor([False,  True, False]) torch.BoolTensor
    print(d, d.type())	# tensor([False,  True, False]) torch.BoolTensor
    print(type(a))	# <class 'torch.Tensor'>
    

— Sigmoid、BCELoss、BCEWithLogitsLoss

1. sigmoid

 sigmoid函数输出范围(0,1),常作为二分类问题的输出层。

  • 输入输出

    Input:(N, *),*表示后面可以添加任意数量个维度。

    Output:(N, *),维度同Input。

  • 计算公式 \(\text{Sigmoid}(x) = \frac{1}{1 + \exp(-x)}\)

  $x$为Input中某一个元素的值。

2.BCELoss

 用作二分类问题的损失函数。

  • 参数
    • weight:用于调整各个样本的权重,默认是一个全1的向量。
    • reduction:默认为mean,表示对batch的数据的损失求平均。作用和size_average、reduce重合。
  • 输入输出

    • 输入

      • Input:(N,*) ,Input为经过sigmoid的网络输出。

      • Target:(N, *),维度和输入相同。Target各元素的值一般为0或1。

        其中,N为batch_size,即一批有N个样本。

        若(N,*)中的“*”为”d1,…,dk”,则表示一个样本中有d1*…*dk个标签要进行二分类。

    • 输出

      • Output: scalar,所有样本,所有标签的平均损失。

        若reduction参数为none,则维度和Target相同,为(N, *)。当样本中含有填充的标签时,考虑使用此种类型的Output与mask结合,进行特殊的平均化。

  • 计算公式

    • 若不对所有样本的损失取平均,即reduction=none时, \(\ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad l_n = - w_n \left[ y_n \cdot \log x_n + (1 - y_n) \cdot \log (1 - x_n) \right]\) $N$是 batch size,表示有N个样本。

      $l_n$为第n个样本的损失。

      $x$为预测的二分类样本向量,$y$为实际的二分类样本向量(label);$x_n$,$y_n$分别和第n个样本对应。

      $w_n$为第n个样本的权重, $w_n$默认为1。

    • 若对所有样本的损失取平均 \(\ell(x, y) = \operatorname{mean}(L)\)

3. BCEWithLogitsLoss

 BCEWithLogitsLoss相当于Sigmoid+BCELoss

  • 参数和输入输出和BCELoss基本一致

    仅多一个参数pos_weight

    • 维度等于Input的最低维度或者和Input相同,若为Input的最低维度则系统会扩展至Input同维度。

      pos_weight的元素$p_n$表示Input对应元素为正例时的权重。p_n > 1 增加 recall, p_n < 1 增加 precision。

  • 计算公式

    • 不考虑调整正例权重时 \(\ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad l_n = - w_n \left[ t_n \cdot \log \sigma(x_n) + (1 - t_n) \cdot \log (1 - \sigma(x_n)) \right]\)

    • 考虑调整正例权重时 \(\ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad l_n = - w_n \left[ p_n y_n \cdot \log \sigma(x_n) + (1 - y_n) \cdot \log (1 - \sigma(x_n)) \right]\)

    • 对所有样本所有标签的损失求平均 \(\ell(x, y) = \operatorname{mean}(L)\)

— (log)Softmax 、NLLLoss、CrossEntropyLoss

CrossEntropyLoss 相当于nn.LogSoftmax()+nn.NLLLoss()

1. softmax

Softmax用于多分类过程中,它将每个类别的输出,映射到(0,1)区间内,看作是这个类别的概率,从而来进行多分类,各类别概率和为1。

  • 计算公式
\[\text{Softmax}(x_{i}) = \frac{\exp(x_i)}{\sum_j \exp(x_j)}\]

​ 其中$x_i$为第i个类别对应神经元的输出,$Softmax(x_i)$为第i个类别的概率。

  • Softmax求导 $$

    $$

  • nn.Softmax() 解析

    • 输入输出

      input维度无要求,output和input同维度。

    • 参数

      dim:对Input的哪个维度使用Softmax进行计算,Output相应维度的和将为1。

2.Logsoftmax

  • LogSoftmax是在Softmax基础上做一个log运算:
\[\text{LogSoftmax}(x_{i}) = \log\left(\frac{\exp(x_i) }{ \sum_j \exp(x_j)} \right)\]

由于$Softmax$输出都是$0-1$之间的,因此$LogSoftmax$输出的是小于$0$的数,输出值的范围在$[-inf ,0]$

  • LogSoftmax求导 $$

    $$

  • 实际中,一般都是用LogSoftmax(),因为它比Softmax()计算更快,有更好的数值特性。

3. NLLLoss()

 negative log likelihood loss,负对数似然函数,用于多分类(0,1,… ,C-1)问题的损失函数,要求输入的每一个类别的概率为log-probabilities,故一般和LogSoftmax联合使用。

  • 参数
    • weight:(C),用于调整各个类别的权重,默认是一个全1的向量。
    • reduction:默认为mean,表示对batch的数据的损失求平均。作用和size_average、reduce重合。
    • ignore_index :忽略目标值为ignore_index的样本的损失,也就是在对所有样本的损失求平均时不考虑此类样本。一般用于含有填充项的问题中,如机器翻译,文本分类等。
  • 输入,输出

    • 输入

      • Input:(N, C) 或 (N, C, d_1, d_2, …, d_K) ,经过网络的输出。

      • Target:(N) 或 (N, d_1, d_2, …, d_K),真实的类别,即Label。

      其中,N为batch_size,即一批有N个样本。

      C表示有每一个标签有C个类别,后面扩展的d1…dk可理解成有d1*…*dk个标签要进行C分类。

    • 输出

      • Output:scalar ,所有样本的所有标签的平均损失。

        若reduction参数为none,则维度和Target相同,为(N) 或 (N, d_1, d_2, …, d_K)。

  • 计算公式

    • reduction参数为none,即不对batch样本loss取平均
    \[\ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad l_n = - w_{y_n} x_{n,y_n}, \quad w_{c} = \text{weight}[c] \cdot \mathbb{1}\{c \not= \text{ignore_index}\\]

    x为Input,$x_n$为第n个样本对应的向量(维度为(C))。

    y为Target,$y_n$为第n个样本的对应的真实类别(scalar),$x_{n,y_n}$为,向量$x_n$中与类别$y_n$对应的元素值。

    $l_n$为第n个样本的损失,$w_{y_n}$为类别$y_n$的权重。

    • 若reduction参数为mean,默认如此。
    \[\ell(x, y) = \sum_{n=1}^N \frac{1}{\sum_{n=1}^N w_{y_n}} l_n\]

4. CrossEntropyLoss()

  • 相当于nn.LogSoftmax +nn.NLLLoss

  • 参数和输入输出和NLLLoss()基本一致

  • 计算公式

    • 不考虑各类别的权重
    \[\text{loss}(x, class) = -\log\left(\frac{\exp(x[class])}{\sum_j \exp(x[j])}\right) = -x[class] + \log\left(\sum_j \exp(x[j])\right)\]

    其中,x为input,class为Target。

    • 考虑各类别的权重 \(\text{loss}(x, class) = weight[class] \left(-x[class] + \log\left(\sum_j \exp(x[j])\right)\right)\)

— torch.nn与torch.nn.functional之间的区别和联系

torch.nn一般要先放在init()中实例化,而torch.nn.functional直接在forword()中使用,即torch.nn必须添加到nn.Module容器中才能使用,而torch.nn.functional则作为一个函数调用。

以Conv2d为例,nn.Conv2d是一个类,而F.conv2d()是一个函数,nn.Conv2d的forward()函数是用F.conv2d()实现的。F.conv2d()仅仅是做一个数学计算,并不会能像nn.Conv2d那样构建一个层,并随着训练更新层次中的参数。

在建图过程中,往往有两种层,一种如全连接层,卷积层等,当中有Variable,另一种如Pooling层,Relu层等,当中没有Variable。对于有Variable的层,为了能更新其中的参数,必须用torch.nn来定义,而对于没有Variable的层既可以用torch.nn定义,也可以直接使用torch.nn.functional。