添加链接描述 transformer 形象链接的参考
本目录包含Seq2Seq的一个经典样例:机器翻译,带Attention机制的翻译模型。Seq2Seq翻译模型,模拟了人类在进行翻译类任务时的行为:先解析源语言,理解其含义,再根据该含义来写出目标语言的语句。
所有的模型所谓的智能都是模拟人的结构或者习惯而已
相较于此前 Seq2Seq 模型中广泛使用的循环神经网络(Recurrent Neural Network, RNN),使用Self Attention进行输入序列到输出序列的变换主要具有以下优势:
计算复杂度小
特征维度为 d 、长度为 n 的序列,在 RNN 中计算复杂度为 O(n * d * d) (n 个时间步,每个时间步计算 d 维的矩阵向量乘法),在 Self-Attention 中计算复杂度为 O(n * n * d) (n 个时间步两两计算 d 维的向量点积或其他相关度函数),n 通常要小于 d 。
计算并行度高
RNN 中当前时间步的计算要依赖前一个时间步的计算结果;Self-Attention 中各时间步的计算只依赖输入不依赖之前时间步输出,各时间步可以完全并行。
容易学习长距离依赖(long-range dependencies)
RNN 中相距为 n 的两个位置间的关联需要 n 步才能建立;Self-Attention 中任何两个位置都直接相连;路径越短信号传播越容易。 Transformer 中引入使用的基于 Self-Attention 的序列建模模块结构,已被广泛应用在 Bert 等语义表示模型中,取得了显著效果。
注意时间复杂度是降低了哦。从O(ndd)变成了O(nnd) 生产的字典是个类,包含了特殊字符,以及id转字符和字符转id的关系
def _map(self, fn, lazy=True, batched=False):
if batched:
self.new_data = fn(self.new_data)
elif lazy:
self._transform_pipline.append(fn)
else:
self.new_data = [
fn(self.new_data[idx]) for idx in range(len(self.new_data))
]
return self
# Truncate and convert example to ids
train_ds = train_ds.map(convert_example, lazy=False)
dev_ds = dev_ds.map(convert_example, lazy=False)
.map方法是内嵌你的方法,把所以的样本挨个做了转换
这就是为什么这个类,包含两个变量了,其中那个new就是专门存储转换后的id的
解码一般会定义三层,输入层,注意力层和输出层
Seq2SeqAttnModel(
(encoder): Seq2SeqEncoder(
(embedder): Embedding(17191, 512, sparse=False)
(lstm): LSTM(512, 512, num_layers=2, dropout=0.2
(0): RNN(
(cell): LSTMCell(512, 512)
)
(1): RNN(
(cell): LSTMCell(512, 512)
)
)
)
(decoder): Seq2SeqDecoder(
(embedder): Embedding(7709, 512, sparse=False)
(lstm_attention): RNN(
(cell): Seq2SeqDecoderCell(
(dropout): Dropout(p=0.2, axis=None, mode=upscale_in_train)
(lstm_cells): LayerList(
(0): LSTMCell(1024, 512)
(1): LSTMCell(512, 512)
)
(attention_layer): AttentionLayer(
(input_proj): Linear(in_features=512, out_features=512, dtype=float32)
(output_proj): Linear(in_features=1024, out_features=512, dtype=float32)
)
)
)
(output_layer): Linear(in_features=512, out_features=7709, dtype=float32)
)
)
模型的每一层都非常的清晰明了
def prepare_train_input(insts, bos_id, eos_id, pad_id):
# Add eos token id and bos token id.
insts = [([bos_id] + inst[0] + [eos_id], [bos_id] + inst[1] + [eos_id])
for inst in insts]
# Pad sequence using eos id.
src, src_length = Pad(pad_val=pad_id, ret_length=True)(
[inst[0] for inst in insts])
tgt, tgt_length = Pad(pad_val=pad_id, ret_length=True)(
[inst[1] for inst in insts])
tgt_mask = (tgt[:, :-1] != pad_id).astype("float32")
return src, src_length, tgt[:, :-1], tgt[:, 1:, np.newaxis], tgt_mask
最基本的,必须要特殊字符补充上,这个是一定不能少的,否则模型没法正常运行 另外,做填充,填充好了一定要知道哪些是填充的,要有mask矩阵 输入有两部分,编码的输入和解码的输入,生成句子的标签刚好和解码的输入错开一位 编码的时候实际的句子长度也要输入,底层模型内部会做处理 返回了每个时刻单词对应的神经元的输出,以及最后时刻的输出和状态值。一个神经元有输出的和隐藏层的,两部分
两层,每层两个
def forward(self, hidden, encoder_output, encoder_padding_mask):
encoder_output = self.input_proj(encoder_output)
attn_scores = paddle.matmul(
paddle.unsqueeze(hidden, [1]), encoder_output, transpose_y=True)
if encoder_padding_mask is not None:
attn_scores = paddle.add(attn_scores, encoder_padding_mask)
attn_scores = F.softmax(attn_scores)
attn_out = paddle.squeeze(
paddle.matmul(attn_scores, encoder_output), [1])
attn_out = paddle.concat([attn_out, hidden], 1)
attn_out = self.output_proj(attn_out)
return attn_out
通过跟mask矩阵相加,让padding部分不参与运算 一个时刻一个时刻的运算解码求注意力,然后这些所有时刻准备好了之后,然后统一做相关的矩阵运算 这个一般的注意力机制,通用也是多头的,统一也会乘以WK,WV做转换,只不过这是的Q是来自解码层的输出
既然我们已经涵盖了编码器方面的大多数概念,我们基本上都知道解码器的组件是如何工作的。但是让我们来看看它们如何协同工作。 编码器通过处理输入序列开始。然后将顶部编码器的输出变换成一组注意力向量K和V.这些将由每个解码器在其“编码器 - 解码器注意力”层中使用,这有助于解码器关注输入序列中的适当位置:既然我们已经涵盖了编码器方面的大多数概念,我们基本上都知道解码器的组件是如何工作的。但是让我们来看看它们如何协同工作。 编码器通过处理输入序列开始。然后将顶部编码器的输出变换成一组注意力向量K和V.这些将由每个解码器在其“编码器 - 解码器注意力”层中使用,这有助于解码器关注输入序列中的适当位置:解码器中的self-attention层以与编码器中的self-attention层略有不同的方式操作: 在解码器中,仅允许self-attention层关注输出序列中的较早位置。这是通过在self-attension计算中的softmax步骤之前屏蔽未来位置(将它们设置为-inf)来完成的。 “Encoder-Decoder Attention”层就像多头self-attention一样,除了它从它下面的层创建其查询矩阵,并从编码器堆栈的输出中获取键和值矩阵。
|