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去学习数字的加法(数字用字符串表示)
通常情况下,输入序列和输出序列具有不同的长度(例如机器翻译),并且需要整个输入以便开始预测目标。这就需要一些更高级的设置,下面是Seq2Seq的工作原理
在推断模式下,例如我们希望解码一组未知的序列,我们的流程有一丢丢不同:
让我们动手实现下代码
在这里例子中,我们用到的数据集是一组常用英语短语以及其法语的翻译,叫 fra-eng.zip,这个数据集可以从 manythings.org/anki 下载
我们将实现字符级别的Seq2Seq模型,一个字符一个字符地处理输入序列,一个字符一个字符地生成序列。当然,我们也可以训练单词级别的模型,这样的模型在机器翻译中更为常见。在最后我们将说明一些如何用Embedding将我们字符级别的模型转换为单词级别
完整的代码在能够在github中找到
下面是代码的总结:
decoder_target_data[:, t, :]
与decoder_input_data[:, t+1, :]
相同encoder_input_data
和decoder_input_data
是,预测 decoder_target_data
,我们的模型利用了teacher forcing下面我们还是来看代码吧
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 '
# 定义 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模型,关键在于构建编码器和解码器,并且要认识到训练阶段与推断阶段工作的流程是不一样的
原文最后还提到了如何将我们这个字符级别的模型改成单词级别,有兴趣的同学可以了解了解
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态