1. model.train()有什么用?
如果模型中有BN层(Batch Normalization)和Dropout,需要在训练时添加model.train(),在测试时添加model.eval()。
其中model.train()是保证BN层用每一批数据的均值和方差,
而model.eval()是保证BN用全部训练数据的均值和方差;
而对于Dropout,model.train()是随机取一部分网络连接来训练更新参数,而model.eval()是利用到了所有网络连接。
2. PyTorch中在反向传播前为什么要手动将梯度清零?
在训练模型的时候常常会看到:
optimizer.zero_grad()
一般来讲,pytorch训练是按照如下模板:
optimizer.zero_grad() ## 梯度清零
preds = model(inputs) ## inference
loss = criterion(preds, targets) ## 求解loss
loss.backward() ## 反向传播求解梯度
optimizer.step() ## 更新权重参数
也可以这样表示:
for i,(images,target) in enumerate(train_loader):
# 1. input output
images = images.cuda(non_blocking=True)
target = torch.from_numpy(np.array(target)).float().cuda(non_blocking=True)
outputs = model(images)
loss = criterion(outputs,target)
# 2. backward
optimizer.zero_grad() # reset gradient
loss.backward()
optimizer.step()
由于pytorch的动态计算图,当我们使用loss.backward()和opimizer.step()进行梯度下降更新参数的时候,梯度并不会自动清零。并且这两个操作是独立操作。
backward():反向传播求解梯度。
step():更新权重参数。
基于以上几点,正好说明了pytorch的一个特点是每一步都是独立功能的操作,因此也就有需要梯度清零的说法,如若不显示的进行optimizer.zero_grad()这一步操作,backward()的时候就会累加梯度,也就有了梯度累加这种trick。
使用梯度累加是这么写的:
for i,(images,target) in enumerate(train_loader):
# 1. input output
images = images.cuda(non_blocking=True)
target = torch.from_numpy(np.array(target)).float().cuda(non_blocking=True)
outputs = model(images)
loss = criterion(outputs,target)
# 2.1 loss regularization
loss = loss/accumulation_steps
# 2.2 back propagation
loss.backward()
# 3. update parameters of net
if((i+1)%accumulation_steps)==0:
# optimizer the net
optimizer.step() # update parameters of net
optimizer.zero_grad() # reset gradient
一定条件下,batchsize越大训练效果越好,梯度累加则实现了batchsize的变相扩大,如果accumulation_steps为8,则batchsize ‘变相’ 扩大了8倍,是我们这种乞丐实验室解决显存受限的一个不错的trick,使用时需要注意,学习率也要适当放大。
pytroch中nn.embedding
需要注意的是nn.embedding是默认随机赋值, 也就是说他并没有使用跳字模型或者连续词袋模型进行词嵌入,而是根据你规定的词向量大小随机赋值,但是可以加载训练好的词向量。
>>> # an Embedding module containing 10 tensors of size 3
>>> embedding = nn.Embedding(10, 3)
>>> # a batch of 2 samples of 4 indices each
>>> input = torch.LongTensor([[1,2,4,5],[4,3,2,9]])
>>> embedding(input)
tensor([[[-0.0251, -1.6902, 0.7172],
[-0.6431, 0.0748, 0.6969],
[ 1.4970, 1.3448, -0.9685],
[-0.3677, -2.7265, -0.1685]],
[[ 1.4970, 1.3448, -0.9685],
[ 0.4362, -0.4004, 0.9400],
[-0.6431, 0.0748, 0.6969],
[ 0.9124, -2.3616, 1.1151]]])
>>> # example with padding_idx
>>> embedding = nn.Embedding(10, 3, padding_idx=0)
>>> input = torch.LongTensor([[0,2,0,5]])
>>> embedding(input)
tensor([[[ 0.0000, 0.0000, 0.0000],
[ 0.1535, -2.0309, 0.9315],
[ 0.0000, 0.0000, 0.0000],
[-0.1655, 0.9897, 0.0635]]])
如何load训练好的词向量?
使用from_pretrained()函数
CLASSMETHOD from_pretrained(embeddings, freeze=True, padding_idx=None, max_norm=None, norm_type=2.0, scale_grad_by_freq=False, sparse=False)
>>> # FloatTensor containing pretrained weights
>>> weight = torch.FloatTensor([[1, 2.3, 3], [4, 5.1, 6.3]])
>>> embedding = nn.Embedding.from_pretrained(weight)
>>> # Get embeddings for index 1
>>> input = torch.LongTensor([1])
>>> embedding(input)
tensor([[ 4.0000, 5.1000, 6.3000]])
Pytorch中的RNN之pack_padded_sequence()和pad_packed_sequence()
在我们使用 PyTorch 搭建 RNN 与其各种变体 (比如 LSTM、GRU) 的模型时,若搭配 PyTorch 所提供的 Embedding 层当作模型第一层的嵌入层,那麽,我们经常会碰到不同长度序列的文章。
很多人会推荐使用 pack_padded_sequence 和 pad_packed_sequence 来调整可变长度序列的句子。以下,就来一步步介绍该如何使用这两个函式。
除此之外,文章中还会使用到 PyTorch 所提供的 pad() 函式来进行 Padding (将序列填充 “0” 到同样长度) 以及使用 torch.cat() 来串接不同序列,详细的介绍可以参考文章末的连结。
简单来说,pack_padded_sequence() 是用来压缩序列的,而 pad_packed_sequence() 则是用来展开序列成原本形状的。
以下是一个简单的示范,扣掉注解和印出序列的部份,实际程式码不到 15 行。
# coding: utf-8
import torch
import torch.nn.functional as F
from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence
# Sequences
a = torch.tensor([1, 2])
b = torch.tensor([3, 4, 5])
c = torch.tensor([6, 7, 8, 9])
print('a:', a)
print('b:', b)
print('c:', c)
# Settings
seq_lens = [len(a), len(b), len(c)]
max_len = max(seq_lens)
# Zero padding
a = F.pad(a, (0, max_len-len(a)))
b = F.pad(b, (0, max_len-len(b)))
c = F.pad(c, (0, max_len-len(c)))
# Merge the sequences
seq = torch.cat((a, b, c), 0).view(-1, max_len)
print('Sequence:', seq)
# Pack
packed_seq = pack_padded_sequence(seq, seq_lens, batch_first=True, enforce_sorted=False)
print('Pack:', packed_seq)
# Unpack
unpacked_seq, unpacked_lens = pad_packed_sequence(packed_seq, batch_first=True)
print('Unpack:', unpacked_seq)
print('length:', unpacked_lens)
# Reduction
a = unpacked_seq[0][:unpacked_lens[0]]
b = unpacked_seq[1][:unpacked_lens[1]]
c = unpacked_seq[2][:unpacked_lens[2]]
print('Recutions:')
print('a:', a)
print('b:', b)
print('c:', c)
Output
a: tensor([1, 2])
b: tensor([3, 4, 5])
c: tensor([6, 7, 8, 9])
Sequence:
tensor([[1, 2, 0, 0],
[3, 4, 5, 0],
[6, 7, 8, 9]])
Pack:
PackedSequence(data=tensor([6, 3, 1, 7, 4, 2, 8, 5, 9]),
batch_sizes=tensor([3, 3, 2, 1]),
sorted_indices=tensor([2, 1, 0]),
unsorted_indices=tensor([2, 1, 0]))
Unpack:
tensor([[1, 2, 0, 0],
[3, 4, 5, 0],
[6, 7, 8, 9]])
length: tensor([2, 3, 4])
Recutions:
a: tensor([1, 2])
b: tensor([3, 4, 5])
c: tensor([6, 7, 8, 9])
首先,我有 a, b, c 三组不同长度的序列,我要做的只有下面几个步骤:
纪录每个序列原始长度
决定填充的最大长度
填充每个序列至同样的长度
将合併后的序列使用 pack_padded_sequence() 压缩
将压缩后的序列使用 pad_packed_sequence() 还原本来形状
根据纪录的原始长度恢复原始序列尺寸
可以看到,我们能透过 pack_padded_sequence() 将序列压缩,再用 pad_packed_sequence() 还原,是可以将资料完整恢复的。