作者|Nechu BM
编译|VK
起源|Towards Data Science
基础知识:理解本文之前最好领有对于循环神经网络(RNN)和编解码器的常识。
本文是对于如何应用Python和Keras开发一个编解码器模型的实用教程,更准确地说是一个序列到序列(Seq2Seq)。在上一个教程中,咱们开发了一个多对多翻译模型,如下图所示:
这种构造有一个重要的限度,即序列长度。正如咱们在图像中看到的,输出序列和输入序列的长度必须雷同。如果咱们须要不同的长度呢?
例如,咱们想实现一个承受不同序列长度的模型,它接管一个单词序列并输入一个数字,或者是图像字幕模型,其中输出是一个图像,输入是一个单词序列。
如果咱们要开发的模型是输出和输入长度不同,咱们须要开发一个编解码器模型。通过本教程,咱们将理解如何开发模型,并将其利用于翻译练习。模型的示意如下所示。
咱们将模型分成两局部,首先,咱们有一个编码器,输出西班牙语句子并产生一个隐向量。编码器是用一个嵌入层将单词转换成一个向量而后用一个循环神经网络(RNN)来计算暗藏状态,这里咱们将应用长短期记忆(LSTM)层。
而后编码器的输入将被用作解码器的输出。对于解码器,咱们将再次应用LSTM层,以及预测英语单词的全连贯层。
实现
示例数据来自manythings.org。它是由语言的句子对组成的。在咱们的案例中,咱们将应用西班牙语-英语对。
建设模型首先须要对数据进行预处理,失去西班牙语和英语句子的最大长度。
1-预处理
先决条件:理解Keras中的类“tokenizer”和“pad_sequences”。如果你想具体回顾一下,咱们在上一个教程中探讨过这个主题。
首先,咱们将导入库,而后读取下载的数据。
import stringimport numpy as npfrom keras.preprocessing.text import Tokenizerfrom keras.preprocessing.sequence import pad_sequencesfrom keras.models import Modelfrom keras.layers import LSTM, Input, TimeDistributed, Dense, Activation, RepeatVector, Embeddingfrom keras.optimizers import Adamfrom keras.losses import sparse_categorical_crossentropy# 翻译文件的门路path_to_data = 'data/spa.txt'# 读文件translation_file = open(path_to_data,"r", encoding='utf-8') raw_data = translation_file.read()translation_file.close()# 解析数据raw_data = raw_data.split('\n')pairs = [sentence.split('\t') for sentence in raw_data]pairs = pairs[1000:20000]
一旦咱们浏览了数据,咱们将保留第一个例子,以便更快地进行训练。如果咱们想开发更高性能的模型,咱们须要应用残缺的数据集。而后咱们必须通过删除大写字母和标点符号来清理数据。
def clean_sentence(sentence): # 把这个句子小写 lower_case_sent = sentence.lower() # 删除标点 string_punctuation = string.punctuation + "¡" + '¿' clean_sentence = lower_case_sent.translate(str.maketrans('', '', string_punctuation)) return clean_sentence
接下来,咱们将句子标识化并剖析数据。
def tokenize(sentences): # 创立 tokenizer text_tokenizer = Tokenizer() # 利用到文本上 text_tokenizer.fit_on_texts(sentences) return text_tokenizer.texts_to_sequences(sentences), text_tokenizer
创立完函数后,咱们能够进行预处理:
# 清理句子english_sentences = [clean_sentence(pair[0]) for pair in pairs]spanish_sentences = [clean_sentence(pair[1]) for pair in pairs]# 标识化单词spa_text_tokenized, spa_text_tokenizer = tokenize(spanish_sentences)eng_text_tokenized, eng_text_tokenizer = tokenize(english_sentences)print('Maximum length spanish sentence: {}'.format(len(max(spa_text_tokenized,key=len))))print('Maximum length english sentence: {}'.format(len(max(eng_text_tokenized,key=len))))# 查看长度spanish_vocab = len(spa_text_tokenizer.word_index) + 1english_vocab = len(eng_text_tokenizer.word_index) + 1print("Spanish vocabulary is of {} unique words".format(spanish_vocab))print("English vocabulary is of {} unique words".format(english_vocab))
下面的代码打印以下后果
依据之前的代码,西班牙语句子的最大长度为12个单词,英语句子的最大长度为6个单词。在这里咱们能够看到应用编解码器模型的劣势。以前咱们解决等长句子有局限性,所以咱们须要对英语句子利用填充到12,当初只须要一半。因而,更重要的是,它还缩小了LSTM工夫步数,缩小了计算需要和复杂性。
咱们应用填充来使每种语言中句子的最大长度相等。
max_spanish_len = int(len(max(spa_text_tokenized,key=len)))max_english_len = int(len(max(eng_text_tokenized,key=len)))spa_pad_sentence = pad_sequences(spa_text_tokenized, max_spanish_len, padding = "post")eng_pad_sentence = pad_sequences(eng_text_tokenized, max_english_len, padding = "post")# 重塑spa_pad_sentence = spa_pad_sentence.reshape(*spa_pad_sentence.shape, 1)eng_pad_sentence = eng_pad_sentence.reshape(*eng_pad_sentence.shape, 1)
当初咱们曾经筹备好了数据,让咱们构建模型。
2.模型开发
在下一节中,咱们将创立模型,并在python代码中解释增加的每一层。
2.1-编码器
咱们定义的第一层是图像的嵌入层。为此,咱们首先必须增加一个输出层,这里惟一要思考的参数是“shape”,这是西班牙语句子的最大长度,在咱们的例子中是12。
而后咱们将其连贯到嵌入层,这里要思考的参数是“input_dim”(西班牙语词汇表的长度)和“output_dim”(嵌入向量的形态)。此层将把西班牙语单词转换为输入维度形态的向量。
这背地的概念是以空间示意的模式提取单词的含意,其中每个维度都是定义单词的特色。例如,“sol”将转换为形态为128的向量。输入维越高,从每个单词中提取的语义意义就越多,但所需的计算和解决工夫也就越高。咱们也须要在速度和性能之间找到均衡。
input_sequence = Input(shape=(max_spanish_len,))embedding = Embedding(input_dim=spanish_vocab, output_dim=128,)(input_sequence)
接下来,咱们将增加大小为64的LSTM层。即便LSTM的每一个工夫步都输入一个暗藏向量,咱们会把注意力集中在最初一个,因而参数return_sequences 是'False'。咱们将看到LSTM层如何在解码器的return_sequences=True的状况下工作。
input_sequence = Input(shape=(max_spanish_len,))embedding = Embedding(input_dim=spanish_vocab, output_dim=128,)(input_sequence)encoder = LSTM(64, return_sequences=False)(embedding)
当返回序列为'False'时,输入是最初一个暗藏状态。
2.2-解码器
编码器层的输入将是最初一个工夫步的暗藏状态。而后咱们须要把这个向量输出解码器。让咱们更准确地看一下解码器局部,并理解它是如何工作的。
正如咱们在图像中看到的,暗藏向量被反复n次,因而LSTM的每个工夫步都接管雷同的向量。为了使每个工夫步都有雷同的向量,咱们须要应用层RepeatVector,因为它的名字意味着它的作用是反复它接管的向量,咱们须要定义的惟一参数是n,反复次数。这个数字等于译码器局部的工夫步数,换句话说就是英语句子的最大长度6。
input_sequence = Input(shape=(max_spanish_len,))embedding = Embedding(input_dim=spanish_vocab, output_dim=128,)(input_sequence)encoder = LSTM(64, return_sequences=False)(embedding)r_vec = RepeatVector(max_english_len)(encoder)
一旦咱们筹备好输出,咱们将持续解码器。这也是用LSTM层构建的,区别在于参数return_sequences,在本例中为'True'。这个参数是用来做什么的?在编码器局部,咱们只冀望在最初一个工夫步中有一个向量,而疏忽了其余所有的向量,这里咱们冀望每个工夫步都有一个输入向量,这样全连贯层就能够进行预测。
input_sequence = Input(shape=(max_spanish_len,))embedding = Embedding(input_dim=spanish_vocab, output_dim=128,)(input_sequence)encoder = LSTM(64, return_sequences=False)(embedding)r_vec = RepeatVector(max_english_len)(encoder)decoder = LSTM(64, return_sequences=True, dropout=0.2)(r_vec)
咱们还有最初一步,预测翻译的单词。为此,咱们须要应用全连贯层。咱们须要定义的参数是单元数,这个单元数是输入向量的形态,它须要与英语词汇的长度雷同。为什么?这个向量的值都接近于零,除了其中一个单位靠近于1。而后咱们须要将输入1的单元的索引映射到字典中,在字典中咱们将每个单元映射到一个单词。
例如,如果输出是单词‘sun’,而输入是一个向量,其中所有都是零,而后单元472是1,那么咱们将该索引映射到蕴含英语单词的字典上,并失去值‘sun’。
咱们刚刚看到了如何利用全连贯层来预测一个单词,然而咱们如何对整个句子进行预测呢?因为咱们应用return_sequence=True,所以LSTM层在每个工夫步输入一个向量,所以咱们须要在每个工夫步利用后面解释过的全连贯层层,让其每次预测一个单词。
为此,Keras开发了一个称为TimeDistributed的特定层,它将雷同的全连贯层利用于每个工夫步。
input_sequence = Input(shape=(max_spanish_len,))embedding = Embedding(input_dim=spanish_vocab, output_dim=128,)(input_sequence)encoder = LSTM(64, return_sequences=False)(embedding)r_vec = RepeatVector(max_english_len)(encoder)decoder = LSTM(64, return_sequences=True, dropout=0.2)(r_vec)logits = TimeDistributed(Dense(english_vocab))(decoder)
最初,咱们创立模型并增加一个损失函数。
enc_dec_model = Model(input_sequence, Activation('softmax')(logits))enc_dec_model.compile(loss=sparse_categorical_crossentropy, optimizer=Adam(1e-3), metrics=['accuracy'])enc_dec_model.summary()
一旦咱们定义了模型,咱们就能够训练它了。
model_results = enc_dec_model.fit(spa_pad_sentence, eng_pad_sentence, batch_size=30, epochs=100)
当模型训练好后,咱们就能够进行第一次翻译了。你还能够找到函数“logits_to_sentence”,它将全连贯层的输入与英语词汇进行映射。
def logits_to_sentence(logits, tokenizer): index_to_words = {idx: word for word, idx in tokenizer.word_index.items()} index_to_words[0] = '<empty>' return ' '.join([index_to_words[prediction] for prediction in np.argmax(logits, 1)])index = 14print("The english sentence is: {}".format(english_sentences[index]))print("The spanish sentence is: {}".format(spanish_sentences[index]))print('The predicted sentence is :')print(logits_to_sentence(enc_dec_model.predict(spa_pad_sentence[index:index+1])[0], eng_text_tokenizer))
论断
编解码器构造容许不同的输出和输入序列长度。首先,咱们应用嵌入层来创立单词的空间示意,并将其输出LSTM层,因为咱们只关注最初一个工夫步的输入,咱们应用return_sequences=False。
这个输入向量须要反复的次数与解码器局部的工夫步数雷同,为此咱们应用RepeatVector层。解码器将应用LSTM,参数return_sequences=True,因而每个工夫步的输入都会传递到全连贯层。
只管这个模型曾经是上一个教程的一个很好的改良,咱们依然能够进步准确性。咱们能够在一层的编码器和解码器中减少一层。咱们也能够应用预训练的嵌入层,比方word2vec或Glove。最初,咱们能够应用留神机制,这是自然语言解决畛域的一个次要改良。咱们将在下一个教程中介绍这个概念。
附录:不应用反复向量的编解码器
在本教程中,咱们理解了如何应用RepeatVector层构建编码器-解码器。还有第二个选项,咱们应用模型的输入作为下一个工夫步骤的输出,而不是反复暗藏的向量,如图所示。
实现这个模型的代码能够在Keras文档中找到,它须要对Keras库有更深刻的了解,并且开发要简单得多:https://blog.keras.io/a-ten-m...
原文链接:https://towardsdatascience.co...
欢送关注磐创AI博客站:
http://panchuang.net/
sklearn机器学习中文官网文档:
http://sklearn123.com/
欢送关注磐创博客资源汇总站:
http://docs.panchuang.net/