0%

[DL] PyTorch 折桂 14:其它功能

本以为 PyTorch 的文章要写两个月,结果发现 PyTorch 真的太轻了,写了不到一个月就写完了。本篇为完结篇,对一些零星的功能进行总结。

1. torch.nn.utils 里的一些功能

1.1 梯度剪枝

PyTorch 折桂 8:torch.nn.init 里提到过梯度爆炸的问题,当时我们的解决方法是对神经元权重的初始化进行控制,这里再介绍一个简单粗暴的方式:直接限制权重的上限。对应导数超过上限的权重,将其导数重置为上限值。

torch.nn.utils.clip_grad_value_(parameters, clip_value)

  • parameters:需要修改的权重
  • clip_value:权重的上限
    1
    2
    3
    4
    5
    6
    7
    8
    >>> weight = torch.tensor((10.), requires_grad=True)
    >>> relu = nn.ReLU() # 对大于 0 的值,ReLU 的处理结果为 1.
    >>> out = relu(weight)
    >>> weight.backward() # 反向传播
    >>> nn.utils.clip_grad_value_(weight, 0.5) # 梯度从 1.0 被限制为 0.5

    >>> print(weight.grad)
    tensor(0.5000)

可以看到,clip_grad_value_ 为 inplace 操作,需要在 Tensor.backwardoptimizer.step 之间使用。

除此以外,还有一个 torch.nn.utils.clip_grad_norm_(parameters, max_norm, norm_type=2) 函数,将若干个权重修改为服从正态分布的范围,这里不多赘述。

1.2 PyTorch 对可变长度序列的处理

在 NLP 任务中,我们经常要处理不定长度的序列。PyTorch 提供了将不定长度的序列进行打包的函数。

  • torch.nn.utils.rnn.pad\_sequence(sequences, batch\_first=False, padding\_value=0)

将不定长序列补全至最长序列的长度。接受三个参数:

  • sequences:接受补全的序列;
  • batch\_first:批是否为第一个维度,默认为 False;
  • padding\_value:填充的值,默认为 0。
    1
    2
    3
    4
    5
    >>> a = torch.ones(25, 300)
    >>> b = torch.ones(22, 300)
    >>> c = torch.ones(15, 300)
    >>> torch.nn.utils.rnn.pad_sequence([a, b, c]).size()
    torch.Size([25, 3, 300])
  • torch.nn.utils.rnn.pack\_sequence(sequences, enforce\_sorted=True)

将序列直接打包成一个 PackedSequence 实例。有两个参数:

  • sequences:要打包的序列;
  • enforce\_sorted:若为 True,则将序列以长度的降序进行排列,默认为 True。
    1
    2
    3
    4
    5
    >>> a = torch.tensor([1,2,3])
    >>> b = torch.tensor([4,5])
    >>> c = torch.tensor([6])
    >>> torch.nn.utils.rnn.pack_sequence([a, b, c], enforce_sorted=False)
    PackedSequence(data=tensor([1, 4, 6, 2, 5, 3]), batch_sizes=tensor([3, 2, 1]), sorted_indices=tensor([0, 1, 2]), unsorted_indices=tensor([0, 1, 2]))

除了 padding 与裁剪将所有序列统一为定长以外,PyTorch 还提供了两个函数将不定长度序列打包和解包。

  • torch.nn.utils.rnn.pack\_padded\_sequence(input, lengths, batch\_first=False, enforce\_sorted=True)

将一个不定长度的序列进行打包,返回一个 PackedSequence 实例。有 4 个参数:

  • input:一个 T x B x * 尺寸的序列,T 为序列中最长的序列的长度,B 为 batch 的数量,* 为每个序列的维度(可以为 0);
  • lengths:单个序列长度的列表;
  • batch\_first:是否以 batch 为第一个维度;
  • enforce\_sorted:是否对序列以每个序列的长度进行降序排序。
    1
    2
    3
    4
    5
    6
    >>> seq = torch.tensor([[1,2,0], [3,0,0], [4,5,6]])
    >>> lens = [2, 1, 3]
    >>> packed = torch.nn.utils.rnn.pack_padded_sequence(seq, lens, batch_first=True, enforce_sorted=False)
    >>> packed
    PackedSequence(data=tensor([4, 1, 3, 5, 2, 6]), batch_sizes=tensor([3, 2, 1]),
    sorted_indices=tensor([2, 0, 1]), unsorted_indices=tensor([1, 2, 0]))

还有一个与之相反的解包函数:

  • torch.nn.utils.rnn.pad\_packed\_sequence(sequence, batch\_first=False, padding\_value=0.0, total\_length=None)

这个函数接受一个 PackedSequence 实例,有 4 个参数:

  • sequence:需要进行解包的序列;
  • batch\_first:是否以 batch 为第一维;
  • padding\_value:解包后填充的值,默认为 0;
  • total\_length:将所有序列填充至 total\_length 的长度。如果这个值小于最长序列的长度,将抛出异常。

这个函数返回两个张量,解包后的序列和原始序列的长度。

1
2
3
4
5
6
7
>>> seq_unpacked, lens_unpacked = torch.nn.utils.rnn.pad_packed_sequence(packed, batch_first=True)
>>> seq_unpacked
tensor([[1, 2, 0],
[3, 0, 0],
[4, 5, 6]])
>>> lens_unpacked
tensor([2, 1, 3])

2. GPU 的使用

2.1 检查系统内 GPU 的状态

可以使用 nvidia-smi 命令。在 Jupyter Notebook 里要在命令前加上 !。下面为 Google Colab 上的 GPU 状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> !nvidia-smi
Thu Jun 4 16:47:42 2020
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.82 Driver Version: 418.67 CUDA Version: 10.1 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 Tesla K80 Off | 00000000:00:04.0 Off | 0 |
| N/A 69C P8 33W / 149W | 11MiB / 11441MiB | 0% Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+

2.2 在 PyTorch 内检查可用 GPU

可以使用 torch.cuda.is\_available()

1
2
>>> torch.cuda.is_available()
True

2.3 将神经网络与张量在 CPU 与 GPU 之间移动

有两种方法:

1
2
3
4
5
6
7
# 第一种方法
x = x.cuda() # 将 x 移动到 GPU 上
x = x.cpu() # 将 x 移动到 CPU 上

# 第二种方法
x = x.to('cuda') # 将 x 移动到 GPU 上
x = x.to('cpu') # 将 x 移动到 CPU 上

以上仅为一张 GPU 的情况。GPU 上仅可以进行运算,其它操作需要将张量移动到 CPU 上完成。

3. 模型的保存与读取

保存的模型如果在 GPU 上,需要先转移到 CPU 上。保存模型既可以保存整个模型,也可以只保存模型的参数权重。

1
2
3
4
5
6
7
8
9
# 保存整个模型
torch.save(the_model, PATH)
# 只保存模型的参数
torch.save(the_model.state_dict(), PATH)

# 读取整个模型
the_model = torch.load(PATH)
# 只读取模型的权重
the_model.load_state_dict(torch.load(PATH))

欢迎关注我的微信公众号“花解语 NLP”:

欢迎关注我的其它发布渠道