十分钟搞定Keras序列到序列学习(附代码实现)

开发 开发工具
如何在 Keras 中实现 RNN 序列到序列学习?本文中,作者将尝试对这一问题做出简短解答。

如何在 Keras 中实现 RNN 序列到序列学习?本文中,作者将尝试对这一问题做出简短解答;本文预设你已有一些循环网络和 Keras 的使用经验。

GitHub:https://github.com/fchollet/keras/blob/master/examples/lstm_seq2seq.py

什么是序列到序列学习?

序列到序列学习(Seq2Seq)是指训练模型从而把一个域的序列(比如英语语句)转化为另一个域的序列(比如法语中的对应语句)。

  1. "the cat sat on the mat"->[Seq2Seqmodel]->"le chat etait assis sur le tapis" 

Seq2Seq 可用于机器翻译或者省去问题回答——通常来讲,它可以随时生成文本。完成这一任务有很多方式,比如 RNN 或一维卷积。本文只介绍 RNN。

次要案例:当输入序列和输出序列长度相同

当输入序列和输出序列长度相同时,你可以通过 Keras LSTM 或者 GRU 层(或者其中的堆栈)简单地实现模型。这一实例脚本中的案例展示了如何教会 RNN 学习添加被编码为字符串的数字:

一般案例:标准的 Seq2Seq

一般情况下,输入序列和输出序列有不同的长度(比如机器翻译)。这就需要一个更高级的设置,尤其在没有进一步语境的「序列到序列模型」时。下面是其工作原理:

  • 一个 RNN 层(或其中的堆栈)作为「编码器」:它处理输入序列并反馈其内部状态。注意我们抛弃了编码器 RNN 的输出,只恢复其状态。该状态在下一步中充当解码器的「语境」。
  • 另一个 RNN 层作为「解码器」:在给定目标序列先前字母的情况下,它被训练以预测目标序列的下一个字符。具体讲,它被训练把目标序列转化为相同序列,但接下来被一个时间步抵消,这一训练过程在语境中被称为「teacher forcing」。更重要的是,编码器把其状态向量用作初始状态,如此编码器获得了其将要生成的信息。实际上,在给定 targets[...t] 的情况下,解码器学习生成 targets[t+1...],前提是在输入序列上。

在推理模式中,即当要解码未知的输入序列,我们完成了一个稍微不同的处理:

  1. 把输入序列编码进状态向量
  2. 从大小为 1 的目标序列开始
  3. 馈送状态向量和 1 个字符的目标序列到解码器从而为下一字符生成预测
  4. 通过这些预测采样下一个字符(我们使用 argmax)
  5. 把采样的字符附加到目标序列
  6. 不断重复直至我们生成序列最后的字符或者达到字符的极限

相同的处理也可被用于训练没有「teacher forcing」的 Seq2Seq 网络,即把解码器的预测再注入到解码器之中。

Keras 实例

让我们用实际的代码演示一下这些想法。

对于实例实现,我们将使用一对英语语句及其法语翻译的数据集,你可以从

http://www.manythings.org/anki/下载它,文件的名称是 fra-eng.zip。我们将会实现一个字符级别的序列到序列模型,逐个字符地处理这些输入并生成输出。另一个选择是单词级别的模型,它对机器学习更常用。在本文最后,你会发现通过嵌入层把我们的模型转化为单词级别模型的一些注释。

这是实例的全部脚本:

https://github.com/fchollet/keras/blob/master/examples/lstm_seq2seq.py。

下面是这一过程的总结:

1. 把语句转化为 3 个 Numpy 数组 encoder_input_data、decoder_input_data、decoder_target_data:

  • encoder_input_data 是一个形态的 3D 数组(num_pairs, max_english_sentence_length, num_english_characters),包含一个英语语句的独热向量化。
  • decoder_input_data 是一个形态的 3D 数组(num_pairs, max_french_sentence_length, num_french_characters),包含一个法语语句的独热向量化。
  • decoder_target_data 与 decoder_input_data 相同,但是被一个时间步抵消。decoder_target_data[:, t, :] 与 decoder_input_data[:, t + 1, :] 相同。

2. 在给定 encoder_input_data 和 decoder_input_data 的情况下,训练一个基本的基于 LSTM 的 Seq2Seq 模型以预测 decoder_target_data。我们的模型使用 teacher forcing。

3. 解码一些语句以检查模型正在工作。

由于训练过程和推理过程(解码语句)相当不同,我们使用了不同的模型,虽然两者具有相同的内在层。这是我们的模型,它利用了 Keras RNN 的 3 个关键功能:

  • return_state 构造函数参数配置一个 RNN 层以反馈列表,其中第一个是其输出,下一个是内部的 RNN 状态。这被用于恢复编码器的状态。
  • inital_state 调用参数指定一个 RNN 的初始状态,这被用于把编码器状态作为初始状态传递至解码器。
  • return_sequences 构造函数参数配置一个 RNN 反馈输出的全部序列。这被用在解码器中。
  1. fromkeras.models importModel 
  2. fromkeras.layers importInput,LSTM,Dense 
  3. # Define an input sequence and process it. 
  4. encoder_inputs =Input(shape=(None,num_encoder_tokens)) 
  5. encoder =LSTM(latent_dim,return_state=True
  6. encoder_outputs,state_h,state_c =encoder(encoder_inputs) 
  7. # We discard `encoder_outputs` and only keep the states. 
  8. encoder_states =[state_h,state_c] 
  9. # Set up the decoder, using `encoder_states` as initial state. 
  10. decoder_inputs =Input(shape=(None,num_decoder_tokens)) 
  11. # We set up our decoder to return full output sequences, 
  12. # and to return internal states as well. We don't use the 
  13. # return states in the training model, but we will use them in inference. 
  14. decoder_lstm =LSTM(latent_dim,return_sequences=True,return_state=True
  15. decoder_outputs,_,_ =decoder_lstm(decoder_inputs, 
  16. initial_state=encoder_states
  17. decoder_dense =Dense(num_decoder_tokens,activation='softmax'
  18. decoder_outputs =decoder_dense(decoder_outputs) 
  19. # Define the model that will turn 
  20. # `encoder_input_data` & `decoder_input_data` into `decoder_target_data` 
  21. model =Model([encoder_inputs,decoder_inputs],decoder_outputs) 

我们用这两行代码训练模型,同时在 20% 样本的留存集中监测损失。

  1. # Run training 
  2. model.compile(optimizer='rmsprop',loss='categorical_crossentropy'
  3. model.fit([encoder_input_data,decoder_input_data],decoder_target_data, 
  4. batch_sizebatch_size=batch_size, 
  5. epochsepochs=epochs, 
  6. validation_split=0.2) 

大约 1 小时后在 MacBook CPU 上,我们已准备好做推断。为了解码测试语句,我们将重复:

编码输入语句,检索初始解码器状态。

用初始状态运行一步解码器,以「序列开始」为目标。输出即是下一个目标字符。

附加预测到的目标字符并重复。

这是我们的推断设置:

  1. encoder_model =Model(encoder_inputs,encoder_states) 
  2. decoder_state_input_h =Input(shape=(latent_dim,)) 
  3. decoder_state_input_c =Input(shape=(latent_dim,)) 
  4. decoder_states_inputs =[decoder_state_input_h,decoder_state_input_c] 
  5. decoder_outputs,state_h,state_c =decoder_lstm
  6. decoder_inputs,initial_state=decoder_states_inputs
  7. decoder_states =[state_h,state_c] 
  8. decoder_outputs =decoder_dense(decoder_outputs) 
  9. decoder_model =Model
  10. [decoder_inputs]+decoder_states_inputs, 
  11. [decoder_outputs]+decoder_states) 

我们使用它实现上述推断循环(inference loop):

  1. defdecode_sequence(input_seq): 
  2. # Encode the input as state vectors. 
  3. states_value =encoder_model.predict(input_seq) 
  4. # Generate empty target sequence of length 1. 
  5. target_seq =np.zeros((1,1,num_decoder_tokens)) 
  6. # Populate the first character of target sequence with the start character. 
  7. target_seq[0,0,target_token_index['t']]=1. 
  8. # Sampling loop for a batch of sequences 
  9. # (to simplify, here we assume a batch of size 1). 
  10. stop_condition =False 
  11. decoded_sentence ='' 
  12. whilenotstop_condition: 
  13. output_tokens,h,c =decoder_model.predict( 
  14. [target_seq]+states_value) 
  15. # Sample a token 
  16. sampled_token_index =np.argmax(output_tokens[0,-1,:]) 
  17. sampled_char =reverse_target_char_index[sampled_token_index] 
  18. decoded_sentence +=sampled_char 
  19. # Exit condition: either hit max length 
  20. # or find stop character. 
  21. if(sampled_char =='n'or 
  22. len(decoded_sentence)>max_decoder_seq_length): 
  23. stop_condition =True 
  24. # Update the target sequence (of length 1). 
  25. target_seq =np.zeros((1,1,num_decoder_tokens)) 
  26. target_seq[0,0,sampled_token_index]=1. 
  27. # Update states 
  28. states_value =[h,c] 
  29. returndecoded_sentence 

我们得到了一些不错的结果——这在意料之中,因为我们解码的样本来自训练测试。

  1. Inputsentence:Benice. 
  2. Decodedsentence:Soyezgentil ! 
  3. Inputsentence:Dropit! 
  4. Decodedsentence:Laisseztomber ! 
  5. Inputsentence:Getout! 
  6. Decodedsentence:Sortez ! 

这就是我们的十分钟入门 Keras 序列到序列模型教程。完整代码详见 GitHub:

https://github.com/fchollet/keras/blob/master/examples/lstm_seq2seq.py。

常见问题

1. 我想使用 GRU 层代替 LSTM,应该怎么做?

这实际上变简单了,因为 GRU 只有一个状态,而 LSTM 有两个状态。这是使用 GRU 层适应训练模型的方法:

  1. encoder_inputs =Input(shape=(None,num_encoder_tokens)) 
  2. encoder =GRU(latent_dim,return_state=True
  3. encoder_outputs,state_h =encoder(encoder_inputs) 
  4. decoder_inputs =Input(shape=(None,num_decoder_tokens)) 
  5. decoder_gru =GRU(latent_dim,return_sequences=True
  6. decoder_outputs =decoder_gru(decoder_inputs,initial_state=state_h
  7. decoder_dense =Dense(num_decoder_tokens,activation='softmax'
  8. decoder_outputs =decoder_dense(decoder_outputs) 
  9. model =Model([encoder_inputs,decoder_inputs],decoder_outputs) 

2. 我想使用整数序列的单词级别模型,应该怎么做?

如果你的输入是整数序列(如按词典索引编码的单词序列),你可以通过 Embedding 层嵌入这些整数标记。方法如下:

  1. # Define an input sequence and process it. 
  2. encoder_inputs =Input(shape=(None,)) 
  3. x =Embedding(num_encoder_tokens,latent_dim)(encoder_inputs) 
  4. x,state_h,state_c =LSTM(latent_dim, 
  5. return_state=True)(x) 
  6. encoder_states =[state_h,state_c] 
  7. # Set up the decoder, using `encoder_states` as initial state. 
  8. decoder_inputs =Input(shape=(None,)) 
  9. x =Embedding(num_decoder_tokens,latent_dim)(decoder_inputs) 
  10. x =LSTM(latent_dim,return_sequences=True)(x,initial_state=encoder_states
  11. decoder_outputs =Dense(num_decoder_tokens,activation='softmax')(x) 
  12. # Define the model that will turn 
  13. # `encoder_input_data` & `decoder_input_data` into `decoder_target_data` 
  14. model =Model([encoder_inputs,decoder_inputs],decoder_outputs) 
  15. # Compile & run training 
  16. model.compile(optimizer='rmsprop',loss='categorical_crossentropy'
  17. # Note that `decoder_target_data` needs to be one-hot encoded, 
  18. # rather than sequences of integers like `decoder_input_data`! 
  19. model.fit([encoder_input_data,decoder_input_data],decoder_target_data, 
  20. batch_sizebatch_size=batch_size, 
  21. epochsepochs=epochs, 
  22. validation_split=0.2) 

3. 如果我不想使用「teacher forcing」,应该怎么做?

一些案例中可能不能使用 teacher forcing,因为你无法获取完整的目标序列,比如,在线训练非常长的语句,则缓冲完成输入-目标语言对是不可能的。在这种情况下,你要通过将解码器的预测重新注入解码器输入进行训练,就像我们进行推断时所做的那样。

你可以通过构建硬编码输出再注入循环(output reinjection loop)的模型达到该目标:

  1. fromkeras.layers importLambda 
  2. fromkeras importbackend asK 
  3. # The first part is unchanged 
  4. encoder_inputs =Input(shape=(None,num_encoder_tokens)) 
  5. encoder =LSTM(latent_dim,return_state=True
  6. encoder_outputs,state_h,state_c =encoder(encoder_inputs) 
  7. states =[state_h,state_c] 
  8. # Set up the decoder, which will only process one timestep at a time. 
  9. decoder_inputs =Input(shape=(1,num_decoder_tokens)) 
  10. decoder_lstm =LSTM(latent_dim,return_sequences=True,return_state=True
  11. decoder_dense =Dense(num_decoder_tokens,activation='softmax'
  12. all_outputs =[] 
  13. inputs =decoder_inputs 
  14. for_ inrange(max_decoder_seq_length): 
  15. # Run the decoder on one timestep 
  16. outputs,state_h,state_c =decoder_lstm(inputs, 
  17. initial_state=states
  18. outputs =decoder_dense(outputs) 
  19. # Store the current prediction (we will concatenate all predictions later) 
  20. all_outputs.append(outputs) 
  21. # Reinject the outputs as inputs for the next loop iteration 
  22. # as well as update the states 
  23. inputs =outputs 
  24. states =[state_h,state_c] 
  25. # Concatenate all predictions 
  26. decoder_outputs =Lambda(lambdax:K.concatenate(x,axis=1))(all_outputs) 
  27. # Define and compile model as previously 
  28. model =Model([encoder_inputs,decoder_inputs],decoder_outputs) 
  29. model.compile(optimizer='rmsprop',loss='categorical_crossentropy'
  30. # Prepare decoder input data that just contains the start character 
  31. # Note that we could have made it a constant hard-coded in the model 
  32. decoder_input_data =np.zeros((num_samples,1,num_decoder_tokens)) 
  33. decoder_input_data[:,0,target_token_index['t']]=1. 
  34. # Train model as previously 
  35. model.fit([encoder_input_data,decoder_input_data],decoder_target_data, 
  36. batch_sizebatch_size=batch_size, 
  37. epochsepochs=epochs, 
  38. validation_split=0.2) 

原文:

https://blog.keras.io/a-ten-minute-introduction-to-sequence-to-sequence-learning-in-keras.html

【本文是51CTO专栏机构“机器之心”的原创译文,微信公众号“机器之心( id: almosthuman2014)”】

 

戳这里,看该作者更多好文

责任编辑:赵宁宁 来源: 51CTO专栏
相关推荐

2023-04-12 11:18:51

甘特图前端

2023-11-30 10:21:48

虚拟列表虚拟列表工具库

2023-12-08 13:19:00

前端Reactour流行库

2023-12-11 13:05:21

2020-12-17 06:48:21

SQLkafkaMySQL

2023-12-21 11:39:47

2023-12-13 13:26:41

2021-09-07 09:40:20

Spark大数据引擎

2022-06-16 07:31:41

Web组件封装HTML 标签

2019-04-01 14:59:56

负载均衡服务器网络

2019-09-16 09:14:51

2009-10-09 14:45:29

VB程序

2023-07-15 18:26:51

LinuxABI

2022-08-26 09:01:07

CSSFlex 布局

2020-08-14 07:58:03

代码库代码开发

2012-07-10 01:22:32

PythonPython教程

2015-09-06 09:22:24

框架搭建快速高效app

2024-01-29 00:20:00

GolangGo代码

2009-04-29 17:35:47

LinuxWebMail系统

2023-03-13 07:52:13

点赞
收藏

51CTO技术栈公众号