Embedding 层
see: https://blog.csdn.net/zhaohongfei_358/article/details/122809709
与独热编码不同,Embedding 可以将词表降维,且可进行学习。也就是做了 word2vec
1import torch2from torch import nn3
4embedding = nn.Embedding(20, 5)5# 输入必须为 long 类型6embedding(torch.LongTensor([0,1,2]))
1# 初始状态为随机编码,会随着梯度下降逐渐学习2tensor([[ 0.4471, 0.3875, -1.0195, -1.1125, 1.3481],3 [-1.7230, -0.1964, -0.0420, 0.5782, 0.4514],4 [-0.0310, -1.9674, -1.1344, -1.6752, 1.0801]],5 grad_fn=<EmbeddingBackward0>)
- 类参数
padding_idx
:指定填充的索引,这个索引的向量会被初始化为 0。 - 关于形状:
nl: num_layers
,GRU 的隐藏层个数ns: num_steps
,时间步- ?
n: batch_size
各层
-
Encoder.Embedding 学会将原始语言(en)词元转换为 vec
-
Encoder.GRU 学会对 en vec 和上一时刻隐状态给出良好的神秘输出(无意义)和下一时刻 en 隐状态
-
Decoder.Embedding 将学会将目标语言(fr)词元转换为 vec
-
Decoder.GRU 学会对 fr vec 和上一时刻隐状态给出良好的神秘输出和下一时刻 fr 隐状态
-
Decoder.Dense 学会将神秘输出转换为词元概率
-
Li Mu:
- 训练时 Decoder 输入为正确答案:
- 推理时 Decoder 输入为上一时刻输出:
外层训练入口
[train]
训练时,每对数据仅预测一步,获取损失
1optimizer = torch.optim.Adam(net.parameters(), lr=lr)2loss = MaskedSoftmaxCELoss()3for epoch in range(num_epochs):4 timer = d2l.Timer()5 for batch in data_iter:6 optimizer.zero_grad()7 X, X_valid_len, Y, Y_valid_len = [x.to(device) for x in batch]8 # X 和 Y 的形状:(n, ns)9 bos = torch.tensor([tgt_vocab['<bos>']] * Y.shape[0],10 device=device).reshape(-1, 1)11 # bos 的形状:(n, 1)12 dec_input = torch.cat([bos, Y[:, :-1]], 1) # 强制教学13 # dec_input 的形状:(n, ns),第一个改为 <bos>14 # 强制教学把答案当做输入15
10 collapsed lines
16 # 使 embedding 学会正确编码法语, s.t. GPU 可以正确给出下一时刻的 output + state17 # s.t. 使得 Linear 可以根据 output 正确给出词元概率 && 这个 state 可以帮助下一时刻正确给出 output 和 state18
19 # enc_X=X, dec_X=dec_input, *args20 Y_hat, _ = net(X, dec_input, X_valid_len)21 l = loss(Y_hat, Y, Y_valid_len)22 l.sum().backward() # 损失函数的标量进行“反向传播”23 d2l.grad_clipping(net, 1)24 num_tokens = Y_valid_len.sum()25 optimizer.step()
[train.net()]
其中enc_X
输入为原始语言序列,dec_X
输入为目标语言序列答案。
1def forward(self, enc_X, dec_X, *args):2 enc_outputs = self.encoder(enc_X, *args)3 # enc_outputs: (output(ns, n, nh), state(nl, n, nh))4 dec_state = self.decoder.init_state(enc_outputs, *args)5 # dec_state: 即 state(nl, n, nh)6 return self.decoder(dec_X, dec_state)
- 损失函数就是预测出来概率与答案的交叉熵损失. 但是
<pad>
需要被排除在外. [train.loss]
loss 传入解码器的预测概率(长度为词表大小)和答案序列计算损失,会对valid_len
以外的<pad>
进行 mask,使之不产生损失。
外层预测入口
- 首先将整个原始句子传入编码器,得到浓缩的所有信息 $bold(c)$
- 传给解码器一步步预测。$bold(c)$ 作为第一步预测的隐状态,接下来每步隐状态会不断更新
1# def predict_seq2seq(net, src_sentence, src_vocab, tgt_vocab, num_steps,2# device, save_attention_weights=False):3"""4此处设置解码器网路内部 num_steps = 1,解码器输入的时间长度也为 1,每次只预测一个单词.5"""6
7src_tokens = src_vocab[src_sentence.lower().split(' ')] + [8 src_vocab['<eos>']]9enc_valid_len = torch.tensor([len(src_tokens)], device=device)10src_tokens = d2l.truncate_pad(src_tokens, num_steps, src_vocab['<pad>'])11
12# 添加批量轴13# 在 shape[0] 处加一维(长度为 1)14# enc_X: shape = (1, len(src_tokens))15enc_X = torch.unsqueeze(26 collapsed lines
16 torch.tensor(src_tokens, dtype=torch.long, device=device), dim=0)17# 现将整个 enc_X 传入编码器18# enc_outputs: 包含 output, state19enc_outputs = net.encoder(enc_X, enc_valid_len)20# 此处 [0] output: (ns, n, nh)21# [1] state: (nl, n, nh)22dec_state = net.decoder.init_state(enc_outputs, enc_valid_len)23# dec_state shape: (nl, n, nh) 不包含每个时间步24dec_X = torch.unsqueeze(torch.tensor(25 [tgt_vocab['<bos>']], dtype=torch.long, device=device), dim=0)26# dec_X: shape (n, ns) ex (1, 1)27output_seq, attention_weight_seq = [], []28for _ in range(num_steps):29 Y, dec_state = net.decoder(dec_X, dec_state)30 # Y shape = (1, 1, vocab_size) (n, ns, nvocab)31 # 我们使用具有预测最高可能性的词元,作为解码器在下一时间步的输入32 dec_X = Y.argmax(dim=2)33 pred = dec_X.squeeze(dim=0).type(torch.int32).item()34 # 保存注意力权重(稍后讨论)35 if save_attention_weights:36 attention_weight_seq.append(net.decoder.attention_weights)37 # 一旦序列结束词元被预测,输出序列的生成就完成了38 if pred == tgt_vocab['<eos>']:39 break40 output_seq.append(pred)41return ' '.join(tgt_vocab.to_tokens(output_seq)), attention_weight_seq
类
1class Seq2SeqEncoder(d2l.Encoder):2 def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,3 dropout=0, **kwargs):4 super(Seq2SeqEncoder, self).__init__(**kwargs)5 # 嵌入层6 self.embedding = nn.Embedding(vocab_size, embed_size)7 self.rnn = rnn.GRU(num_hiddens, num_layers, dropout=dropout)8
9 def forward(self, X, *args):10 # [def] n 是批量11 # 输入 X 形状: (n, ns)12 X = self.embedding(X)13 # 输出'X'的形状:(batch_size,num_steps,embed_size)14
15 # 在循环神经网络模型中,第一个轴对应于时间步55 collapsed lines
16 X = X.swapaxes(0, 1)17 state = self.rnn.begin_state(batch_size=X.shape[1], ctx=X.ctx)18 output, state = self.rnn(X, state)19 # output的形状:(num_steps,batch_size,num_hiddens) 被舍弃20 # state的形状:(num_layers,batch_size,num_hiddens) 编码全部输入信息21 return output, state22
23class Seq2SeqDecoder(d2l.Decoder):24 def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,25 dropout=0, **kwargs):26 super(Seq2SeqDecoder, self).__init__(**kwargs)27 self.embedding = nn.Embedding(vocab_size, embed_size)28 self.rnn = nn.GRU(embed_size + num_hiddens, num_hiddens, num_layers,29 dropout=dropout)30 self.dense = nn.Linear(num_hiddens, vocab_size)31
32 def init_state(self, enc_outputs, *args):33 return enc_outputs[1] # 也就是 state34
35 def forward(self, X, state):36 # X: shape (n, ns)37 # state: shape (nl, n, nh),就是编码器对所有输入信息的编码.38 X = self.embedding(X).permute(1, 0, 2)39 # X: (ns, n, n_embed) ex (7, 4, 8)40
41 # 此处 state[-1] 是深度循环神经网络最后一层,即最靠近 output 的一层.42 # 将这一层复制 n_timestep 次. [这合理吗?]43 context = state[-1].repeat(X.shape[0], 1, 1)44 # context: (ns, n, nh) ex (7, 4, 16)45
46 X_and_context = torch.cat((X, context), 2)47 # X_and_context: (ns, n, n_embed + nh) ex (7, 4, 24)48 # 也即,每一时间步每一批量的输入为 n_embed + nh,即 embedding + 刚刚 state 最后一层.49
50 # [qm] 这里 rnn() 怎么又把 state 输入进去了,这不纯信息冗余吗51 # 注意 rnn 是一次性循环所有时间步.52 output, state = self.rnn(X_and_context, state)53 # 此时 output 形状为 (ns, n, nh) 即每时间步每批量输出一个神秘的 nh54
55 output = self.dense(output).permute(1, 0, 2)56 # 最终 output的形状:(n, ns, n_vocab) 这里 ns 中的有效长度和输入 X 不同57 # state的形状: (num_layers,batch_size,num_hiddens)58 return output, state59
60
61class EncoderDecoder(nn.Module):62 def __init__(self, encoder, decoder, **kwargs):63 super(EncoderDecoder, self).__init__(**kwargs)64 self.encoder = encoder65 self.decoder = decoder66
67 def forward(self, enc_X, dec_X, *args):68 enc_outputs = self.encoder(enc_X, *args)69 dec_state = self.decoder.init_state(enc_outputs, *args)70 return self.decoder(dec_X, dec_state)
手绘流程图
公式
- 解码时隐状态: $$s_(t^prime) = g(y_(t^prime - 1), bold(c), s_(t^prime - 1))$$
- 其中 $c$ 是编码器对整个输入序列 $x_1, … x_T$ 的编码.
- 这里 $t^prime$ 只是为了和输入序列时间区分.
1output, state = self.rnn(X_and_context, state)2output = self.dense(output).permute(1, 0, 2)
BLEU
用于评估预测序列的好坏,值域 $0 tilde 1$。给出预测序列中的 $n$ 元语法有多少出现在标签序列(答案)中。越长的语法权重越大。若预测的太短,会有惩罚。
1go . => va !, bleu 1.0002i lost . => j'ai perdu ., bleu 1.0003he's calm . => il est bon ?, bleu 0.5374i'm home . => je suis chez moi debout ., bleu 0.803