深度之眼 PyTorch 训练营第 4 期(6):torch.nn 总览 & 线性连接层 & 激活函数

15 篇文章 3 订阅
15 篇文章 1 订阅

1 torch.nn 总览

PyTorch 把与深度学习模型搭建相关的全部类全部在 torch.nn 这个子模块中。根据类的功能分类,常用的有如下十几个部分:

  • Containers:容器类,如 torch.nn.Module
  • Convolution Layers:卷积层,如 torch.nn.Conv2d
  • Pooling Layers:池化层,如 torch.nn.MaxPool2d
  • Non-linear activations:非线性激活层,如 torch.nn.ReLU
  • Normalization layers:归一化层,如 torch.nn.BatchNorm2d
  • Recurrent layers:循环神经层,如 torch.nn.LSTM
  • Transformer layers:transformer 层,如 torch.nn.TransformerEncoder
  • Linear layers:线性连接层,如 torch.nn.Linear
  • Dropout layers:dropout 层,如 torch.nn.Dropout
  • Sparse layers:稀疏层,如 torch.nn.Embedding
  • Vision layers:vision 层,如 torch.nn.Upsample
  • DataParallel layers:平行计算层,如 torch.nn.DataParallel
  • Utilities:其它功能,如 torch.nn.utils.clip_grad_value_

而在 torch.nn 下面还有一个子模块 torch.nn.functional,基本上是 torch.nn 里对应类的函数,比如 torch.nn.ReLU 的对应函数是 torch.nn.functional.relu。为什么要这么做呢?

你可能会疑惑为什么需要这两个功能如此相近的模块,其实这么设计是有其原因的。如果我们只保留 nn.functional 下的函数的话,在训练或者使用时,我们就要手动去维护 weight,bias,stride 这些中间量的值,这显然是给用户带来了不便。而如果我们只保留 nn 下的类的话,其实就牺牲了一部分灵活性,因为做一些简单的计算都需要创造一个类,这也与 PyTorch 的风格不符。(知乎回答

torch.nn 可以被 nn.Module 识别,并成为网络组成的一部分;torch.nn.functional 则不行。比较以下两个模型:

>>> class Simple(nn.Module):
...     def __init__(self):
...         super(Simple, self).__init__()
...         self.fc = nn.Linear(10, 1)
...         self.dropout = nn.Dropout(0.5) # 使用 nn.Dropout 类
        
...     def forward(self, x):
...         x = self.fc(x)
...         x = self.dropout(x)
...         return x
>>> simple = Simple()
>>> print(simple)
Simple(
  (fc): Linear(in_features=10, out_features=1, bias=True)
  (dropout): Dropout(p=0.5, inplace=False) #可以被识别成一层
)

>>> class Simple2(nn.Module):
...     def __init__(self):
...         super(Simple2, self).__init__()
...         self.fc = nn.Linear(10, 1)
        
...     def forward(self, x):
...         x = F.dropout(self.fc(x)) # 使用 nn.functional.dropout,不能被识别
...         return x
>>> simple2 = Simple2()
>>> print(simple2)
Simple2(
  (fc): Linear(in_features=10, out_features=1, bias=True)
)

什么时候调用 torch.nn,什么时候调用 torch.nn.functional 呢?个人的经验是:不需要存储权重的时候使用 torch.nn.functional,需要存储权重的时候使用 torch.nn

  • 层、dropout 使用 torch.nn
  • 激活函数使用 torch.nn.functional

这里要额外说一下 dropout 层。理论上 dropout 没有权重,可以使用 torch.nn.functional.dropout,然而 dropout 有traineval 模式,使用 torch.nn.Dropout 可以方便地对模式进行控制,而函数就不行。所以为了方便,推荐使用 torch.nn.Dropout

以后若没有特殊说明,均在引入模块时省略 torch 模块名称。

2. nn.Linear

线性连接层又叫做全连接层(fully connected layer),指的是通过矩阵乘法将前一层的矩阵变换为下一层的矩阵:
l a y e r 1 ∗ W + b = l a y e r 2 layer1*W+b=layer2 layer1W+b=layer2
在这里插入图片描述
W 被称为全连接层的 weights,b 被称为全连接层的 bias。通常为了演示方便,我们忽略 bias。
layer1 如果是一个 m ∗ n m*n mn 的矩阵, W W W 是一个 n ∗ k n*k nk 的矩阵,那么下一层 layer2 就是一个 m ∗ k m*k mk 的矩阵。n 称为输入特征数(input size),k 称为输出特征数(output size),那么这个线性连接层可以被这样初始化:

fc = nn.Linear(input_size, output_size)

multilayer perception(多层感知机,MLP)就是通过若干个全连接层组合而成的。但是事实证明 MLP 的性能并不好,为什么呢?假设一个 MLP 由三个全连接层组成,三层分别为
x 3 = x 2 ∗ W 2 x_3=x_2*W_2 x3=x2W2
x 2 = x 1 ∗ W 1 x_2=x_1*W_1 x2=x1W1
我们把第二个式子中的 x 2 x_2 x2 代入第一个式子,可得:
X 3 = ( x 1 ∗ W 1 ) ∗ W 2 = x 1 ∗ ( W 1 ∗ W 2 ) X_3=(x_1*W_1)*W_2=x_1*(W_1*W_2) X3=(x1W1)W2=x1(W1W2)
可见若干层全连接层相连,最终可以化简为一个全连接层。为了解决这个问题,激活函数(activation function)出现了。

3. 激活函数

激活函数就是非线性连接层,通过非线性函数将一层变为另一层。常用的激活函数有 sigmoidtanhrelu 及其变种。虽然 torch.nn 有激活函数层,因为激活函数比较轻量级,使用 torch.nn.functional 里的函数功能就足够了。通常我们将 torch.nn.functional 写成 F

import torch.nn.functional as F
  • F.sigmoid
    在这里插入图片描述
    sigmoid 又叫做 logistic,通常写作 σ \sigma σ,公式为
    s i g m o i d ( x ) = σ ( x ) = 1 1 + e − x sigmoid(x)=\sigma(x)=\frac{1}{1+e^{-x}} sigmoid(x)=σ(x)=1+ex1
    sigmoid 的值域为 ( 0 , 1 ) (0,1) (0,1),所以通常用于二分类问题:大于 0.5 0.5 0.5 为一类,小于 0.5 0.5 0.5 为另一类。sigmoid 的导数公式为
    σ ′ ( x ) = σ ( x ) ( 1 − σ ( x ) ) \sigma'(x)=\sigma(x)(1-\sigma(x)) σ(x)=σ(x)(1σ(x))
    导数的值域为 ( 0 , 0.25 ) (0,0.25) (0,0.25)sigmoid 函数的特点为:
  1. 函数的值在 ( 0 , 1 ) (0,1) (0,1) 之间,符合概率分布;
  2. 导数的值域为 ( 0 , 0.25 ) (0,0.25) (0,0.25),容易造成梯度消失;
  3. 输出为非对称正值,破坏数据分布。
  • F.tanh
    在这里插入图片描述
    tanh 是正切函数,公式为
    t a n h ( x ) = s i n ( x ) c o s ( x ) = e x + e − x e x + e − x tanh(x)=\frac{sin(x)}{cos(x)}=\frac{e^x+e^{-x}}{e^x+e^{-x}} tanh(x)=cos(x)sin(x)=ex+exex+ex
    tanh 的值域为 ( 0 , 1 ) (0,1) (0,1),对称分布。它的导数公式为
    t a n h ′ ( x ) = 1 − t a n h 2 ( x ) tanh'(x)=1-tanh^2(x) tanh(x)=1tanh2(x)
    导数的值域为 ( 0 , 1 ) (0,1) (0,1)tanh 的特点为:
  1. 函数值域为 ( 0 , 1 ) (0,1) (0,1),对称分布;
  2. 导数值域为 ( 0 , 1 ) (0,1) (0,1),容易造成梯度消失。
  • F.relu
    在这里插入图片描述
    为了解决上述两个激活函数容易产生梯度消失的问题,Rectified Linear Unit(relu) 横空出世了。它实际上是一个分段函数:
    r e l u ( x ) = { 0 ,   x < 0 x ,   x > 0 relu(x)= \begin{cases} 0,\ x<0\\ x,\ x>0 \end{cases} relu(x)={0, x<0x, x>0
    relu 的优点在于求导非常方便,而且非常稳定:
    r e l u ′ ( x ) = { 0 ,   x < 0 unidentified ,   x = 0 1 ,   x > 0 relu'(x)= \begin{cases} 0,\ x<0\\ \text{unidentified},\ x=0\\ 1,\ x>0 \end{cases} relu(x)=0, x<0unidentified, x=01, x>0
    缺点在于
  1. x < 0 x<0 x<0 时导数为 0,神经元“死亡”,即不再更新;
  2. 虽然没有梯度消失的问题,但有梯度爆炸的问题。
  • F.leakyrelu
    在这里插入图片描述
    为了解决 relu 的问题,对其稍加改动成为了 leakyrelu
    r e l u ( x ) = { 0 ,   x < 0 α x ,   x > 0 relu(x)= \begin{cases} 0,\ x<0\\ \alpha x,\ x>0 \end{cases} relu(x)={0, x<0αx, x>0
    α \alpha α 是一个很小的数,通常是 0.01。这样它的导数就变成了
    r e l u ( x ) = { 0 ,   x < 0 α ,   x > 0 relu(x)= \begin{cases} 0,\ x<0\\ \alpha,\ x>0 \end{cases} relu(x)={0, x<0α, x>0
    欢迎关注我的微信公众号“花解语 NLP”:
    在这里插入图片描述
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值