Keras教程,Keras-9 实现Seq2Seq

 2023-09-23 阅读 17 评论 0

摘要:A ten-minute introduction to sequence-to-sequence learning in Keras 简单介绍如何用Keras实现Seq2Seq模型原文链接 https://blog.keras.io/a-ten-minute-introduction-to-sequence-to-sequence-learning-in-keras.html该博客的完整代码在 这里 Sequence-to-sequence 学习是

A ten-minute introduction to sequence-to-sequence learning in Keras

  • 简单介绍如何用Keras实现Seq2Seq模型
  • 原文链接 https://blog.keras.io/a-ten-minute-introduction-to-sequence-to-sequence-learning-in-keras.html
  • 该博客的完整代码在 这里

Sequence-to-sequence 学习是什么?

Sequence-to-sequnce学习(Seq2Seq)大概就是将一个序列(Sequence)从一个域转换到另一个域,例如将一段英语通过翻译转换到法语

Keras教程?“the cat sat on the mat” -> [Seq2Seq model] -> “le chat etait assis sur le tapis”

Seq2Seq模型可以用于机器翻译,或者问答系统(给定一个问题,自动生成答案),一般情况下,Seq2Seq模型可以实现任意的文本生成

pythonkeras,处理Seq2Seq的方法很多种,可以使用RNN或者1D CNN,这里我们将重点讨论RNN

一种特殊的情况:当输入和输出的长度相同

当输入与输出的序列长度相同时,那么事情变得比较简单,我们可以用LSTM或者GRU就能实现Seq2Seq. 这里有个例子展示了如何教RNN去学习数字的加法(数字用字符串表示)
add numbers

一般情况:规范的Seq2Seq

通常情况下,输入序列和输出序列具有不同的长度(例如机器翻译),并且需要整个输入以便开始预测目标。这就需要一些更高级的设置,下面是Seq2Seq的工作原理

  • 用RNN层(一层或者多层)作为编码器(encoder):它处理输入序列并返回输入序列的内部状态。注意一点,我们抛弃了RNN层的输出,只需要状态(state).状态,可以理解为解码器需要的上下文(context),或者条件(conditioning)
  • 用另一个RNN层(一层或者多层)作为解码器(decoder):它被训练用来预测目标序列的下一个字符,当给定目标序列的前一个字符时。特殊的,它被训练成将目标序列转换成相同的序列,但是会有一个时间步长的偏移,在这种情况下称为"teacher forcing"的训练过程。重要的是,解码器使用来自编码器的状态向量作为初始状态,这使得解码器能够知道应该产生什么样的信息。给定targets[…t](前t个),并且在知晓输入的情况下(conditioned on the input sequence)解码器能够学习如何产生targets[t+1…]

seq2seq-teacher

在推断模式下,例如我们希望解码一组未知的序列,我们的流程有一丢丢不同:

  1. 将输入序列编码成状态向量 input->state vector
  2. 目标序列的大小从1开始 1-char
  3. 给定输入序列的状态向量以及1-char的目标序列,解码器生成下一个字符的预测
  4. 对下一个字符进行采样(在这里,简单的用argmax作为采样)
  5. 将采样的字符添加到目标序列中
  6. 重复以上操作直到生成了结束字符或者超过字符数量上限

seq2seq-inference

A Keras example

让我们动手实现下代码

在这里例子中,我们用到的数据集是一组常用英语短语以及其法语的翻译,叫 fra-eng.zip,这个数据集可以从 manythings.org/anki 下载

我们将实现字符级别的Seq2Seq模型,一个字符一个字符地处理输入序列,一个字符一个字符地生成序列。当然,我们也可以训练单词级别的模型,这样的模型在机器翻译中更为常见。在最后我们将说明一些如何用Embedding将我们字符级别的模型转换为单词级别

完整的代码在能够在github中找到

下面是代码的总结:

  1. 将句子转换为3个numpy arrays, encoder_input_data, decoder_input_data, decoder_target_data:
    • encoder_input_data 是一个 3D 数组,大小为 (num_pairs, max_english_sentence_length, num_english_characters),包含英语句子的one-hot向量
    • decoder_input_data 是一个 3D 数组,大小为 (num_pairs, max_fench_sentence_length, num_french_characters) 包含法语句子的one-hot向量
    • decoder_target_datadecoder_input_data 相同,但是有一个时间的偏差。 decoder_target_data[:, t, :]decoder_input_data[:, t+1, :]相同
  2. 训练一个基于LSTM的Seq2Seq模型,在给定 encoder_input_datadecoder_input_data是,预测 decoder_target_data,我们的模型利用了teacher forcing
  3. 解码一些语言用来验证模型事有效的

下面我们还是来看代码吧

from keras.models import Model
from keras.layers import Input, LSTM, Dense
from keras import callbacks
import numpy as np
Using TensorFlow backend.
# 基本参数
batch_size = 64
epochs = 100
latent_dim = 256 # LSTM 的单元个数
num_samples = 10000 # 训练样本的大小# 数据集路径
data_path = 'fra-eng/fra.txt'

数据向量化

input_texts = []
target_texts = []
input_characters = set()
target_characters = set()with open(data_path, 'r', encoding='utf-8') as f:lines = f.read().split('\n')
# 显示部分数据
lines[:20]
['Go.\tVa !','Run!\tCours\u202f!','Run!\tCourez\u202f!','Wow!\tÇa alors\u202f!','Fire!\tAu feu !',"Help!\tÀ l'aide\u202f!",'Jump.\tSaute.','Stop!\tÇa suffit\u202f!','Stop!\tStop\u202f!','Stop!\tArrête-toi !','Wait!\tAttends !','Wait!\tAttendez !','Go on.\tPoursuis.','Go on.\tContinuez.','Go on.\tPoursuivez.','I see.\tJe comprends.',"I try.\tJ'essaye.","I won!\tJ'ai gagné !","I won!\tJe l'ai emporté !",'Oh no!\tOh non !']
for line in lines[: min(num_samples, len(lines) - 1)]:# 分割输入序列和目标序列input_text, target_text = line.split('\t')# 用'tab'作为 一个序列的开始字符# 用 '\n' 作为 序列的结束字符target_text = '\t' + target_text + '\n'input_texts.append(input_text)target_texts.append(target_text)# 计算 input_text 中的 tokensfor char in input_text:if char not in input_characters:input_characters.add(char)# 计算 target_text 中的 tokensfor char in target_text:if char not in target_characters:target_characters.add(char)input_characters = sorted(list(input_characters))
target_characters = sorted(list(target_characters))
num_encoder_tokens = len(input_characters)
num_decoder_tokens = len(target_characters)
max_encoder_seq_length = max([ len(txt) for txt in input_texts])
max_decoder_seq_length = max([ len(txt) for txt in target_texts])print('Nunmber of samples:', len(input_texts))
print('Number of unique input tokens:', num_encoder_tokens)
print('Number of unique output tokens:', num_decoder_tokens)
print('Max sequence length of input:', max_encoder_seq_length)
print('Max sequence length of outputs:', max_decoder_seq_length)
Nunmber of samples: 10000
Number of unique input tokens: 71
Number of unique output tokens: 94
Max sequence length of input: 16
Max sequence length of outputs: 59
# 建立 字符->数字 字典,用于字符的向量化
input_token_index = dict( [(char, i)for i, char in enumerate(input_characters)] )
target_token_index = dict( [(char, i) for i, char in enumerate(target_characters)] )
# 创建数组 
encoder_input_data = np.zeros((len(input_texts), max_encoder_seq_length, num_encoder_tokens), dtype=np.float32)
decoder_input_data = np.zeros((len(input_texts), max_decoder_seq_length, num_decoder_tokens), dtype=np.float32)
decoder_target_data = np.zeros((len(input_texts), max_decoder_seq_length, num_decoder_tokens), dtype=np.float32)# 填充数据, 对每一个字符做one-hot
for i, (input_text, target_text) in enumerate(zip(input_texts, target_texts)):# 对编码器的输入序列做one-hotfor t, char in enumerate(input_text):encoder_input_data[i, t, input_token_index[char]] = 1.0# 对解码器的输入与输出做序列做one-hotfor t, char in enumerate(target_text):decoder_input_data[i, t, target_token_index[char]] = 1.0if t > 0:# decoder_target_data 不包含开始字符,并且比decoder_input_data提前一步decoder_target_data[i, t-1, target_token_index[char]] = 1.0

设计模型

编码器

# 定义编码器的输入
# encoder_inputs (None, num_encoder_tokens), None表示可以处理任意长度的序列
encoder_inputs = Input(shape=(None, num_encoder_tokens))# 编码器,要求其返回状态
encoder = LSTM(latent_dim, return_state=True)# 调用编码器,得到编码器的输出(输入其实不需要),以及状态信息 state_h 和 state_c
encoder_outpus, state_h, state_c = encoder(encoder_inputs)# 丢弃encoder_outputs, 我们只需要编码器的状态
encoder_state = [state_h, state_c]

解码器

# 定义解码器的输入
# 同样的,None表示可以处理任意长度的序列
decoder_inputs = Input(shape=(None, num_decoder_tokens))# 接下来建立解码器,解码器将返回整个输出序列
# 并且返回其中间状态,中间状态在训练阶段不会用到,但是在推理阶段将是有用的
decoder_lstm = LSTM(latent_dim, return_sequences=True, return_state=True)# 将编码器输出的状态作为初始解码器的初始状态
decoder_outputs, _, _ = decoder_lstm(decoder_inputs, initial_state=encoder_state)# 添加全连接层
decoder_dense = Dense(num_decoder_tokens, activation='softmax')
decoder_outputs = decoder_dense(decoder_outputs)

训练模型

# 定义整个模型
model = Model([encoder_inputs, decoder_inputs], decoder_outputs)# 定义回调函数
#callback_list = [callbacks.EarlyStopping(patience=10)]
# 编译模型
model.compile(optimizer='rmsprop', loss='categorical_crossentropy')# 训练
model.fit([encoder_input_data, decoder_input_data], decoder_target_data,batch_size=batch_size,epochs = epochs,validation_split=0.2)# 保存模型
model.save('s2s_2.h5')
Train on 8000 samples, validate on 2000 samples
Epoch 1/100
8000/8000 [==============================] - 21s 3ms/step - loss: 0.9184 - val_loss: 0.9529
Epoch 2/100
8000/8000 [==============================] - 19s 2ms/step - loss: 0.7240 - val_loss: 0.8046
Epoch 3/100
8000/8000 [==============================] - 18s 2ms/step - loss: 0.6161 - val_loss: 0.6959
Epoch 4/100
8000/8000 [==============================] - 18s 2ms/step - loss: 0.5613 - val_loss: 0.6610
Epoch 5/100
8000/8000 [==============================] - 19s 2ms/step - loss: 0.5225 - val_loss: 0.6264
Epoch 6/100
8000/8000 [==============================] - 19s 2ms/step - loss: 0.4901 - val_loss: 0.5925
Epoch 7/100
8000/8000 [==============================] - 19s 2ms/step - loss: 0.4640 - val_loss: 0.5756
Epoch 8/100
8000/8000 [==============================] - 19s 2ms/step - loss: 0.4415 - val_loss: 0.5536
Epoch 9/100
8000/8000 [==============================] - 19s 2ms/step - loss: 0.4218 - val_loss: 0.5432
Epoch 10/100
8000/8000 [==============================] - 19s 2ms/step - loss: 0.4047 - val_loss: 0.5353
Epoch 11/100
8000/8000 [==============================] - 19s 2ms/step - loss: 0.3890 - val_loss: 0.5224
Epoch 12/100
8000/8000 [==============================] - 19s 2ms/step - loss: 0.3750 - val_loss: 0.5085
Epoch 13/100
8000/8000 [==============================] - 19s 2ms/step - loss: 0.3618 - val_loss: 0.5004
Epoch 14/100
8000/8000 [==============================] - 19s 2ms/step - loss: 0.3489 - val_loss: 0.4961
.....
Epoch 100/100
8000/8000 [==============================] - 19s 2ms/step - loss: 0.0563 - val_loss: 0.7883/home/nls3/anaconda2/envs/keras/lib/python3.6/site-packages/keras/engine/topology.py:2344: UserWarning: Layer lstm_2 was passed non-serializable keyword arguments: {'initial_state': [<tf.Tensor 'lstm_1/while/Exit_2:0' shape=(?, 256) dtype=float32>, <tf.Tensor 'lstm_1/while/Exit_3:0' shape=(?, 256) dtype=float32>]}. They will not be included in the serialized model (and thus will be missing at deserialization time).str(node.arguments) + '. They will not be included '

建立推断模型

  • 将推断模型与训练模型分开,这样比较清楚,但是他们两内部所用到的结构是一样的
  • 推断的步骤如下
    1. 将输入编码,得到解码器所需要的初始状态
    2. 结合初始状态,对一个size=1的序列(其中只包含开始字符)做模型推断,得到的输出作为下一个size=1序列的内容
    3. 结合当前的输出以及状态,重复以上步骤
# 定义 sampling 模型
# 定义 encoder 模型,得到输出encoder_states
encoder_model = Model(encoder_inputs, encoder_state)decoder_state_input_h = Input(shape=(latent_dim,))
decoder_state_input_c = Input(shape=(latent_dim,))
decoder_states_inputs = [decoder_state_input_h, decoder_state_input_c]# 得到解码器的输出以及中间状态
decoder_outputs, state_h, state_c = decoder_lstm(decoder_inputs, initial_state=decoder_states_inputs)
decoder_states = [state_h, state_c]
decoder_outputs = decoder_dense(decoder_outputs)decoder_model = Model([decoder_inputs] + decoder_states_inputs, [decoder_outputs]+decoder_states)
# 建立 数字->字符 的字典,用于恢复
reverse_input_char_index = dict([(i, char) for char, i in input_token_index.items()])
reverse_target_char_index = dict([(i, char) for char, i in target_token_index.items()])
def decode_sequence(input_seq):# 将输入序列进行编码states_value = encoder_model.predict(input_seq)# 生成一个size=1的空序列target_seq = np.zeros((1, 1, num_decoder_tokens))# 将这个空序列的内容设置为开始字符target_seq[0, 0, target_token_index['\t']] = 1.# 进行字符恢复# 简单起见,假设batch_size = 1stop_condition = Falsedecoded_sentence = ''while not stop_condition:output_tokens, h, c = decoder_model.predict([target_seq] + states_value)# sample a tokensampled_token_index = np.argmax(output_tokens[0, -1, :])sampled_char = reverse_target_char_index[sampled_token_index]decoded_sentence += sampled_char# 退出条件:生成 \n 或者 超过最大序列长度if sampled_char == '\n' or len(decoded_sentence) > max_decoder_seq_length :stop_condition = True# 更新target_seqtarget_seq = np.zeros((1, 1, num_decoder_tokens))target_seq[0, 0, sampled_token_index] = 1.# 更新中间状态states_value = [h, c]return decoded_sentence
# 检验成果的时候到了,从训练集中选取一些句子做测试
# 效果还行(废话,从训练集里挑的数据)
for seq_index in range(1000, 1100):# batch_size = 1input_seq = encoder_input_data[seq_index:seq_index+1]decoded_sentence = decode_sequence(input_seq)print('-')print('Input sentence:', input_texts[seq_index])print('Decoded sentence:', decoded_sentence)
-
Input sentence: Come alone.
Decoded sentence: Venez seuls !-
Input sentence: Come alone.
Decoded sentence: Venez seuls !-
Input sentence: Come along.
Decoded sentence: Venez seul !-
Input sentence: Come on in!
Decoded sentence: Arrête de te li peis travant.-
Input sentence: Come on in.
Decoded sentence: Entre.-
Input sentence: Come quick!
Decoded sentence: Dépêche-toi de venir !-
Input sentence: Come quick!
Decoded sentence: Dépêche-toi de venir !......-
Input sentence: I am tired.
Decoded sentence: Je suis chez lui.-
Input sentence: I broke it.
Decoded sentence: Je l'ai cassé.-
Input sentence: I broke it.
Decoded sentence: Je l'ai cassé.

总结

我们了解了如何用Keras实现Seq2Seq模型,关键在于构建编码器和解码器,并且要认识到训练阶段与推断阶段工作的流程是不一样的

原文最后还提到了如何将我们这个字符级别的模型改成单词级别,有兴趣的同学可以了解了解

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/5/92671.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息