1 . PyTorch 实现了如今最常用的三种循环神经网络(RNN):RNN(vanilla RNN)、LSTM 和 GRU,此外还有对应的三种 RNNCell,RNN 和 RNNCell 层的区别在于前者能够处理整个序列,而后者一次只处理序列中一个时间点的数据,前者封装更完备更易于使用,后者更具灵活性。实际上 RNN 层的一种后端实现方式就是调用 RNNCell 来实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
import torch as t
from torch import nn

t.manual_seed(1000)
# 输入:batch_size=3,序列长度都为 2,序列中每个元素占 4 维
input = t.randn(2, 3, 4)
# lstm 输入向量 4 维,隐藏元 3,1 层
lstm = nn.LSTM(4, 3, 1)
# 初始状态:1 层,batch_size=3,3 个隐藏元
h0 = t.randn(1, 3, 3)
c0 = t.randn(1, 3, 3)
out, hn = lstm(input, (h0, c0))
print(out)
1
2
3
4
5
6
7
8
9
10
11
t.manual_seed(1000)
input = t.randn(2, 3, 4)
# 一个 LSTMCell 对应的层数只能是一层
lstm = nn.LSTMCell(4, 3)
hx = t.randn(3, 3)
cx = t.randn(3, 3)
out = []
for i_ in input:
hx, cx = lstm(i_, (hx, cx))
out.append(hx)
t.stack(out)

词向量在自然语言中应用十分普及,PyTorch 同样提供了 Embedding 层。

1
2
3
4
5
6
7
8
# 有 4 个词,每个词用 5 维的向量表示
embedding = nn.Embedding(4, 5)
# 可以用训练好的词向量初始化 embedding
embedding.weight.data = t.arange(0, 20).view(4, 5)

input = t.arange(3, 0, -1).long()
output = embedding(input)
print(output)

2 . 在深度学习中要用到各种各样的损失函数(loss function),这些损失函数可以看成是一种特殊的 layer,PyTorch 也将这些损失函数实现为 nn.Module 的子类,然而在实际应用中通常将这些 loss function 专门提取出来,和主模型互相独立。

交叉熵损失(CrossEntropyLoss):

1
2
3
4
5
6
7
8
9
# batch_size=3,计算对应每个类别的分数(只有两个类别)
score = t.randn(3, 2)
# 三个样本分别属于 1, 0, 1 类,label 必须是 LongTensor
label = t.Tensor([1, 0, 1]).long()

# loss 与普通的 layer 无差异
criterion = nn.CrossEntropyLoss()
loss = criterion(score, label)
print(loss)

3 . PyTorch 将深度学习中常用的优化方法全部封装在 torch.optim 中,能够方便地扩展成自定义的优化方法,所有的优化方法都是继承基类 optim.Optimizer,并实现了自己的优化步骤,下面以随机梯度下降(SGD)说明:

  • 优化方法的基本使用方法;
  • 如何对模型的不同部分设置不同的学习率;
  • 如何调整学习率。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import torch as t
from torch import nn
from torch import optim


# 自定义一个 LeNet 网络
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 6, 5),
nn.ReLU(),
nn.MaxPool2d(2, 2),
nn.Conv2d(6, 16, 5),
nn.ReLU(),
nn.MaxPool2d(2, 2)
)
self.classifier = nn.Sequential(
nn.Linear(16 * 5 * 5, 120),
nn.ReLU(),
nn.Linear(120, 84),
nn.ReLU(),
nn.Linear(84, 10)
)

def forward(self, x):
x = self.features(x)
x = x.view(-1, 16 * 5 * 5)
x = self.classifier(x)
return x


net = Net()

optimizer = optim.SGD(params=net.parameters(), lr=1)
optimizer.zero_grad() # 梯度清零,等价于 net.zero_grad()

input = t.randn(1, 3, 32, 32)
output = net(input)
output.backward(output) # fake backward

optimizer.step()

为不同的子网络设置不同的学习率,在 finetune 中经常用到,如果对某个参数不指定学习率,就使用最外层的默认学习率。

1
2
3
4
5
6
optimizer = optim.SGD([
{'params': net.features.parameters()},
{'params': net.classifier.parameters(), 'lr': 1e-2}
], lr=1e-5)

print(optimizer)

只为两个全连接层设置较大的学习率,其余层的学习率较小。

1
2
3
4
5
6
7
8
9
10
special_layers = nn.ModuleList([net.classifier[0], net.classifier[3]])
special_layers_params = list(map(id, special_layers.parameters()))
base_params = filter(lambda p: id(p) not in special_layers_params, net.parameters())

optimizer = optim.SGD([
{'params': base_params},
{'params': special_layers.parameters(), 'lr': 0.01}
], lr=0.001)

print(optimizer)

id:用于获取对象的内存地址。
map(function, iterable,...): 第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。
filter(function, iterable):用于过滤掉不符合条件的元素,返回由符合条件的元素组成的新列表。iterable 是可迭代对象。

对于如何调整学习率,主要有两种做法,一种是修改 optimizer.param_groups 中对应的学习率,另一种是更简单也是较为推荐的做法 —— 新建优化器,但是后者对于使用动量的优化器(如 Adam),会丢失动量状态信息,可能会造成损失函数的收敛出现震荡等情况。

1
2
3
4
5
6
7
8
9
10
11
12
# 方法1:调整学习率,手动 decay,保存动量
for param_group in optimizer.param_groups:
param_group['lr'] *= 0.1 # 学习率为之前的 0.1 倍
print(optimizer)

# 方法2:调整学习率,新建一个 optimizer
old_lr = 0.1
optimizer1 = optim.SGD([
{'params': net.features.parameters()},
{'params': net.classifier.parameters(), 'lr': old_lr * 0.1}
], lr=1e-5)
print(optimizer1)

笔记来源:《pytorch-book》