这个算是在课程学习之外的探索,不过希望能尽快用到项目实践中。在文章里会引用较多的博客,文末会进行reference。
搜索Transformer机制,会发现高分结果基本上都源于一篇论文Jay Alammar的《The Illustrated Transformer》(图解Transformer),提到最多的Attention是Google的《Attention Is All You Need》。
- 对于Transformer的运行机制了解即可,所以会基于这篇论文来学习Transformer,结合《Sklearn+Tensorflow》中Attention注意力机制一章完成基本的概念学习;
- 找一个基于Transformer的项目练手
0.引言
Transformer由论文《Attention is All You Need》提出。Transformer和传统RNN的主要区别
1.传统RNN是通过不断循环完成学习,通过每次迭代后的输出实现对上下文的记忆功能,这样才有了LSTM和GRU模型,因为它们能够较好处理RNN梯度爆炸和梯度消失问题,通过对各类门的操作实现“记忆”。但是问题是RNN的训练非常缓慢,之前实现作诗软件的那篇文章,一个2层的LSTM我用I7处理器跑了将近8个小时……
2.Transformer不需要循环,通过构建self-attention机制来完成对上下文和距离较远词汇的结合,并且通过并行进行处理,让每个单词在多个处理步骤中注意到句子中的其他单词,Transformer 的训练速度比 RNN 快很多,而且其翻译结果也比 RNN 好得多。但是在处理小型结构化的语言理解任务或是简单算法任务时表现不如传统模型。
现在Transformer已经被扩展为一个通用模型
研究者将该模型建立在 Transformer 的并行结构上,以保持其快速的训练速度。但是他们用单一、时间并行循环的变换函数的多次应用代替了 Transformer 中不同变换函数的固定堆叠(即,相同的学习变换函数在多个处理步骤中被并行应用于所有符号,其中每个步骤的输出馈入下一个)。RNN 逐个符号(从左到右)处理序列,而 Universal Transformer 同时处理所有符号(像 Transformer 一样),然后使用自注意力机制在循环处理步骤(步骤数量可变)上,对每个符号的解释进行改进。这种时间并行循环机制比 RNN 中使用的顺序循环(serial recurrence)更快,也使得 Universal Transformer 比标准前馈 Transformer 更强大。
Transformer模型能做什么?
目前最火的那个帖子认为Transformer已经可以完成大多数的任务。这个也是这几天我要探究的。
1.从Encoder到Decoder实现Seq2Seq模型
本节主要记录了seq2seq模型基本概念和encoder和decoder层的构建
本文来源《从Encoder到Decoder实现Seq2Seq模型》
采用seq2seq框架来实现MT(机器翻译)现在已经是一个非常热点的研究方向,各种花式设计归根离不开RNN、CNN同时辅以attention。但是正如上文所说的,对于NLP来讲不论是RNN还是CNN都存在其固有的缺陷,即使attention可以缓解长距依赖的问题。
因此我觉得可以先从seq2seq开始,在这里观察一个简单的Seq2Seq,使用TensorFlow来实现一个基础版本的Seq2Seq,主要帮助理解Seq2Seq中的基础架构。
最基础的Seq2Seq模型包含三个部分:Encoder、Decoder以及连接两者固定大小的State Vector。
Encoder通过学习输入,将其编码成一个固定大小的状态向量S,继而将S传给Decoder,Decoder再通过对状态向量S的学习来进行输出。
下面利用TensorFlow来构建一个基础的Seq2Seq模型,通过向我们的模型输入一个单词(字母序列),例如hello,模型将按照字母顺序排序输出,即输出ehllo。
1)数据集
数据集包括两个部分,sorce_data和target_data,放置在datasets/seq2seqdata文件夹下
- sorce_data:每一行是一个单词
- target_data:: 每一行是经过字母排序后的“单词”,它的每一行与source_data中每一行一一对应
2)读取数据和预处理
在神经网络中,对于文本的数据预处理无非是将文本转化为模型可理解的数字。
在这里加入以下四种字符
- <PAD>主要用来进行字符补全
- <EOS>和<GO>都是用在Decoder端的序列中,告诉解码器句子的起始与结束
- <UNK>则用来替代一些未出现过的词或者低频词。
import numpy as np
import time
import tensorflow as tf
with open(r'C:\\Users\\01\\Desktop\\机器学习作业\\sklearn+tensorflow\\datasets\\seq2seqdata\\letters_source.txt', 'r',
encoding='utf-8') as f:
source_data = f.read()
with open(r'C:\\Users\\01\\Desktop\\机器学习作业\\sklearn+tensorflow\\datasets\\seq2seqdata\\letters_target.txt', 'r',
encoding='utf-8') as f:
target_data = f.read()
print(source_data.split('\\n')[:10])
print(target_data.split('\\n')[:10])
def extract_character_vocab(data):
special_words = ['<PAD>', '<UNK>', '<GO>', '<EOS>']
set_words = list(set([character for line in data.split('\n') for character in line]))
int_to_vocab = {idx: word for idx, word in enumerate(special_words + set_words)}
vocab_to_int = {word: idx for idx, word in int_to_vocab.items()}
return int_to_vocab, vocab_to_int
# 构造映射表,就是将source和target的每一个字母转为数值
source_int_to_letter, source_letter_to_int = extract_character_vocab(source_data)
target_int_to_letter, target_letter_to_int = extract_character_vocab(target_data)
source_int = [[source_letter_to_int.get(letter, source_letter_to_int['<UNK>']) for letter in line] for line in
source_data.split('\n')]
target_int = [[target_letter_to_int.get(letter, target_letter_to_int['<UNK>']) for letter in line] for line in
target_data.split('\n')]
print(source_int[:10])
print(target_int[:10])
3)模型构建
如上文所述,模型包括两个部分,Encoder和Decoder。
在Encoder层,首先需要对定义输入的tensor,同时要对字母进行Embedding,再输入到RNN层。使用TensorFlow中的tf.contrib.layers.embed_sequence来对输入进行embedding。
输入层
def get_inputs():
'''
模型输入tensor
'''
inputs = tf.placeholder(tf.int32, [None, None], name='inputs')
targets = tf.placeholder(tf.int32, [None, None], name='targets')
learning_rate = tf.placeholder(tf.float32, name='learning_rate')
# 定义target序列最大长度(之后target_sequence_length和source_sequence_length会作为feed_dict的参数)
target_sequence_length = tf.placeholder(tf.int32, (None,), name='target_sequence_length')
max_target_sequence_length = tf.reduce_max(target_sequence_length, name='max_target_len')
source_sequence_length = tf.placeholder(tf.int32, (None,), name='source_sequence_length')
return inputs, targets, learning_rate, target_sequence_length, max_target_sequence_length, source_sequence_length
Encoder
参数说明:
- input_data: 输入tensor
- rnn_size: rnn隐层结点数量
- num_layers: 堆叠的rnn cell数量
- source_sequence_length: 源数据的序列长度
- source_vocab_size: 源数据的词典大小
- encoding_embedding_size: embedding的大小
def get_encoder_layer(input_data, rnn_size, num_layers,
source_sequence_length, source_vocab_size,
encoding_embedding_size):
encoder_embed_input = tf.contrib.layers.embed_sequence(input_data, source_vocab_size, encoding_embedding_size)
# RNN cell
def get_lstm_cell(rnn_size):
lstm_cell = tf.contrib.rnn.LSTMCell(rnn_size, initializer=tf.random_uniform_initializer(-0.1, 0.1, seed=2))
return lstm_cell
cell = tf.contrib.rnn.MultiRNNCell([get_lstm_cell(rnn_size) for _ in range(num_layers)])
encoder_output, encoder_state = tf.nn.dynamic_rnn(cell, encoder_embed_input, sequence_length=source_sequence_length,
dtype=tf.float32)
return encoder_output, encoder_state
Decoder
在Decoder端主要要完成以下几件事情:
- 对target数据进行处理
- 构造Decoder
- Embedding
- 构造Decoder
- 构造输出层,输出层会告诉我们每个时间序列的RNN输出结果
- 训练Decoder
- 预测Decoder
target数据进行处理
target数据有两个作用:
- 在训练过程中,需要将的target序列作为输入传给Decoder端RNN的每个阶段,而不是使用前一阶段预测输出,这样会使得模型更加准确。(这就是为什么构建Training和Predicting两个Decoder的原因,下面还会有对这部分的解释)。
- 需要用target数据来计算模型的loss。
对target数据的处理时要将每一行最后一个标记处理掉,同时在每一行第一个元素前添加起始标记。"h,o,w,<EOS>"->"<go>,h,o,w"。
使用tf.strided_slice()来进行这一步处理。
def process_decoder_input(data, vocab_to_int, batch_size):
ending = tf.strided_slice(data, [0, 0], [batch_size, -1], [1, 1])
decoder_input = tf.concat([tf.fill([batch_size, 1], vocab_to_int['<GO>']), ending], 1)
return decoder_input
构造Decoder
注意,这里将decoder分为了training和predicting,这两个encoder实际上是共享参数的,也就是通过training decoder学得的参数,predicting会拿来进行预测。那么为什么我们要分两个呢,这里主要考虑模型的robust(鲁棒性)。
在training阶段,为了能够让模型更加准确,我们并不会把t-1的预测输出作为t阶段的输入,而是直接使用target data中序列的元素输入到Encoder中。而在predict阶段,我们没有target data,有的只是t-1阶段的输出和隐层状态。
在training过程中,不会把每个阶段的预测输出作为下一阶段的输入,下一阶段的输入我们会直接使用target data,这样能够保证模型更加准确。
代码中做了一些更新,另外好像batch_size参数丢失了……
def decoding_layer(target_letter_to_int, decoding_embedding_size, num_layers, rnn_size,
target_sequence_length, max_target_sequence_length, encoder_state, decoder_input,batch_size):
'''
构造Decoder层
参数:
- target_letter_to_int: target数据的映射表
- decoding_embedding_size: embed向量大小
- num_layers: 堆叠的RNN单元数量
- rnn_size: RNN单元的隐层结点数量
- target_sequence_length: target数据序列长度
- max_target_sequence_length: target数据序列最大长度
- encoder_state: encoder端编码的状态向量
- decoder_input: decoder端输入
'''
# 1. Embedding
target_vocab_size = len(target_letter_to_int)
decoder_embeddings = tf.Variable(tf.random_uniform([target_vocab_size, decoding_embedding_size]))
decoder_embed_input = tf.nn.embedding_lookup(decoder_embeddings, decoder_input)
# 2. 构造Decoder中的RNN单元
def get_decoder_cell(rnn_size):
decoder_cell = tf.nn.rnn_cell.LSTMCell(rnn_size, initializer=tf.random_uniform_initializer(-0.1, 0.1, seed=2))
return decoder_cell
cell = tf.nn.rnn_cell.MultiRNNCell([get_decoder_cell(rnn_size) for _ in range(num_layers)])
# 3. Output全连接层
output_layer = Dense(target_vocab_size, kernel_initializer=tf.truncated_normal_initializer(mean=0.0, stddev=0.1))
# 4. Training decoder
with tf.variable_scope("decode"):
# 得到help对象
training_helper = tf.contrib.seq2seq.TrainingHelper(inputs=decoder_embed_input,
sequence_length=target_sequence_length,
time_major=False)
# 构造decoder
training_decoder = tf.contrib.seq2seq.BasicDecoder(cell,
training_helper,
encoder_state,
output_layer)
training_decoder_output, _ = tf.contrib.seq2seq.dynamic_decode(training_decoder,
impute_finished=True,
maximum_iterations=max_target_sequence_length)
# 5. Predicting decoder
# 与training共享参数
with tf.variable_scope("decode", reuse=True):
# 创建一个常量tensor并复制为batch_size的大小
start_tokens = tf.tile(tf.constant([target_letter_to_int['<GO>']], dtype=tf.int32), [batch_size],
name='start_tokens')
predicting_helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(decoder_embeddings,
start_tokens,
target_letter_to_int['<EOS>'])
predicting_decoder = tf.contrib.seq2seq.BasicDecoder(cell,
predicting_helper,
encoder_state,
output_layer)
predicting_decoder_output, _ = tf.contrib.seq2seq.dynamic_decode(predicting_decoder,
impute_finished=True,
maximum_iterations=max_target_sequence_length)
return training_decoder_output, predicting_decoder_output