自然语言处理N天-从seq2seq到Transformer01

新建 Microsoft PowerPoint 演示文稿 (2).jpg

这个算是在课程学习之外的探索,不过希望能尽快用到项目实践中。在文章里会引用较多的博客,文末会进行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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容