十分鐘搞定Keras序列到序列學(xué)習(xí)(附代碼實(shí)現(xiàn))
如何在 Keras 中實(shí)現(xiàn) RNN 序列到序列學(xué)習(xí)?本文中,作者將嘗試對這一問題做出簡短解答;本文預(yù)設(shè)你已有一些循環(huán)網(wǎng)絡(luò)和 Keras 的使用經(jīng)驗(yàn)。
GitHub:https://github.com/fchollet/keras/blob/master/examples/lstm_seq2seq.py
什么是序列到序列學(xué)習(xí)?
序列到序列學(xué)習(xí)(Seq2Seq)是指訓(xùn)練模型從而把一個域的序列(比如英語語句)轉(zhuǎn)化為另一個域的序列(比如法語中的對應(yīng)語句)。
- "the cat sat on the mat"->[Seq2Seqmodel]->"le chat etait assis sur le tapis"
Seq2Seq 可用于機(jī)器翻譯或者省去問題回答——通常來講,它可以隨時(shí)生成文本。完成這一任務(wù)有很多方式,比如 RNN 或一維卷積。本文只介紹 RNN。
次要案例:當(dāng)輸入序列和輸出序列長度相同
當(dāng)輸入序列和輸出序列長度相同時(shí),你可以通過 Keras LSTM 或者 GRU 層(或者其中的堆棧)簡單地實(shí)現(xiàn)模型。這一實(shí)例腳本中的案例展示了如何教會 RNN 學(xué)習(xí)添加被編碼為字符串的數(shù)字:
一般案例:標(biāo)準(zhǔn)的 Seq2Seq
一般情況下,輸入序列和輸出序列有不同的長度(比如機(jī)器翻譯)。這就需要一個更高級的設(shè)置,尤其在沒有進(jìn)一步語境的「序列到序列模型」時(shí)。下面是其工作原理:
- 一個 RNN 層(或其中的堆棧)作為「編碼器」:它處理輸入序列并反饋其內(nèi)部狀態(tài)。注意我們拋棄了編碼器 RNN 的輸出,只恢復(fù)其狀態(tài)。該狀態(tài)在下一步中充當(dāng)解碼器的「語境」。
- 另一個 RNN 層作為「解碼器」:在給定目標(biāo)序列先前字母的情況下,它被訓(xùn)練以預(yù)測目標(biāo)序列的下一個字符。具體講,它被訓(xùn)練把目標(biāo)序列轉(zhuǎn)化為相同序列,但接下來被一個時(shí)間步抵消,這一訓(xùn)練過程在語境中被稱為「teacher forcing」。更重要的是,編碼器把其狀態(tài)向量用作初始狀態(tài),如此編碼器獲得了其將要生成的信息。實(shí)際上,在給定 targets[...t] 的情況下,解碼器學(xué)習(xí)生成 targets[t+1...],前提是在輸入序列上。
在推理模式中,即當(dāng)要解碼未知的輸入序列,我們完成了一個稍微不同的處理:
- 把輸入序列編碼進(jìn)狀態(tài)向量
- 從大小為 1 的目標(biāo)序列開始
- 饋送狀態(tài)向量和 1 個字符的目標(biāo)序列到解碼器從而為下一字符生成預(yù)測
- 通過這些預(yù)測采樣下一個字符(我們使用 argmax)
- 把采樣的字符附加到目標(biāo)序列
- 不斷重復(fù)直至我們生成序列最后的字符或者達(dá)到字符的極限
相同的處理也可被用于訓(xùn)練沒有「teacher forcing」的 Seq2Seq 網(wǎng)絡(luò),即把解碼器的預(yù)測再注入到解碼器之中。
Keras 實(shí)例
讓我們用實(shí)際的代碼演示一下這些想法。
對于實(shí)例實(shí)現(xiàn),我們將使用一對英語語句及其法語翻譯的數(shù)據(jù)集,你可以從
http://www.manythings.org/anki/下載它,文件的名稱是 fra-eng.zip。我們將會實(shí)現(xiàn)一個字符級別的序列到序列模型,逐個字符地處理這些輸入并生成輸出。另一個選擇是單詞級別的模型,它對機(jī)器學(xué)習(xí)更常用。在本文最后,你會發(fā)現(xiàn)通過嵌入層把我們的模型轉(zhuǎn)化為單詞級別模型的一些注釋。
這是實(shí)例的全部腳本:
https://github.com/fchollet/keras/blob/master/examples/lstm_seq2seq.py。
下面是這一過程的總結(jié):
1. 把語句轉(zhuǎn)化為 3 個 Numpy 數(shù)組 encoder_input_data、decoder_input_data、decoder_target_data:
- encoder_input_data 是一個形態(tài)的 3D 數(shù)組(num_pairs, max_english_sentence_length, num_english_characters),包含一個英語語句的獨(dú)熱向量化。
- decoder_input_data 是一個形態(tài)的 3D 數(shù)組(num_pairs, max_french_sentence_length, num_french_characters),包含一個法語語句的獨(dú)熱向量化。
- decoder_target_data 與 decoder_input_data 相同,但是被一個時(shí)間步抵消。decoder_target_data[:, t, :] 與 decoder_input_data[:, t + 1, :] 相同。
2. 在給定 encoder_input_data 和 decoder_input_data 的情況下,訓(xùn)練一個基本的基于 LSTM 的 Seq2Seq 模型以預(yù)測 decoder_target_data。我們的模型使用 teacher forcing。
3. 解碼一些語句以檢查模型正在工作。
由于訓(xùn)練過程和推理過程(解碼語句)相當(dāng)不同,我們使用了不同的模型,雖然兩者具有相同的內(nèi)在層。這是我們的模型,它利用了 Keras RNN 的 3 個關(guān)鍵功能:
- return_state 構(gòu)造函數(shù)參數(shù)配置一個 RNN 層以反饋列表,其中第一個是其輸出,下一個是內(nèi)部的 RNN 狀態(tài)。這被用于恢復(fù)編碼器的狀態(tài)。
- inital_state 調(diào)用參數(shù)指定一個 RNN 的初始狀態(tài),這被用于把編碼器狀態(tài)作為初始狀態(tài)傳遞至解碼器。
- return_sequences 構(gòu)造函數(shù)參數(shù)配置一個 RNN 反饋輸出的全部序列。這被用在解碼器中。
- fromkeras.models importModel
- fromkeras.layers importInput,LSTM,Dense
- # Define an input sequence and process it.
- encoder_inputs =Input(shape=(None,num_encoder_tokens))
- encoder =LSTM(latent_dim,return_state=True)
- encoder_outputs,state_h,state_c =encoder(encoder_inputs)
- # We discard `encoder_outputs` and only keep the states.
- encoder_states =[state_h,state_c]
- # Set up the decoder, using `encoder_states` as initial state.
- decoder_inputs =Input(shape=(None,num_decoder_tokens))
- # We set up our decoder to return full output sequences,
- # and to return internal states as well. We don't use the
- # return states in the training model, but we will use them in inference.
- decoder_lstm =LSTM(latent_dim,return_sequences=True,return_state=True)
- decoder_outputs,_,_ =decoder_lstm(decoder_inputs,
- initial_state=encoder_states)
- decoder_dense =Dense(num_decoder_tokens,activation='softmax')
- decoder_outputs =decoder_dense(decoder_outputs)
- # Define the model that will turn
- # `encoder_input_data` & `decoder_input_data` into `decoder_target_data`
- model =Model([encoder_inputs,decoder_inputs],decoder_outputs)
我們用這兩行代碼訓(xùn)練模型,同時(shí)在 20% 樣本的留存集中監(jiān)測損失。
- # Run training
- model.compile(optimizer='rmsprop',loss='categorical_crossentropy')
- model.fit([encoder_input_data,decoder_input_data],decoder_target_data,
- batch_sizebatch_size=batch_size,
- epochsepochs=epochs,
- validation_split=0.2)
大約 1 小時(shí)后在 MacBook CPU 上,我們已準(zhǔn)備好做推斷。為了解碼測試語句,我們將重復(fù):
編碼輸入語句,檢索初始解碼器狀態(tài)。
用初始狀態(tài)運(yùn)行一步解碼器,以「序列開始」為目標(biāo)。輸出即是下一個目標(biāo)字符。
附加預(yù)測到的目標(biāo)字符并重復(fù)。
這是我們的推斷設(shè)置:
- encoder_model =Model(encoder_inputs,encoder_states)
- 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)
我們使用它實(shí)現(xiàn)上述推斷循環(huán)(inference loop):
- defdecode_sequence(input_seq):
- # Encode the input as state vectors.
- states_value =encoder_model.predict(input_seq)
- # Generate empty target sequence of length 1.
- target_seq =np.zeros((1,1,num_decoder_tokens))
- # Populate the first character of target sequence with the start character.
- target_seq[0,0,target_token_index['t']]=1.
- # Sampling loop for a batch of sequences
- # (to simplify, here we assume a batch of size 1).
- stop_condition =False
- decoded_sentence =''
- whilenotstop_condition:
- output_tokens,h,c =decoder_model.predict(
- [target_seq]+states_value)
- # Sample a token
- sampled_token_index =np.argmax(output_tokens[0,-1,:])
- sampled_char =reverse_target_char_index[sampled_token_index]
- decoded_sentence +=sampled_char
- # Exit condition: either hit max length
- # or find stop character.
- if(sampled_char =='n'or
- len(decoded_sentence)>max_decoder_seq_length):
- stop_condition =True
- # Update the target sequence (of length 1).
- target_seq =np.zeros((1,1,num_decoder_tokens))
- target_seq[0,0,sampled_token_index]=1.
- # Update states
- states_value =[h,c]
- returndecoded_sentence
我們得到了一些不錯的結(jié)果——這在意料之中,因?yàn)槲覀兘獯a的樣本來自訓(xùn)練測試。
- Inputsentence:Benice.
- Decodedsentence:Soyezgentil !
- -
- Inputsentence:Dropit!
- Decodedsentence:Laisseztomber !
- -
- Inputsentence:Getout!
- Decodedsentence:Sortez !
這就是我們的十分鐘入門 Keras 序列到序列模型教程。完整代碼詳見 GitHub:
https://github.com/fchollet/keras/blob/master/examples/lstm_seq2seq.py。
常見問題
1. 我想使用 GRU 層代替 LSTM,應(yīng)該怎么做?
這實(shí)際上變簡單了,因?yàn)?GRU 只有一個狀態(tài),而 LSTM 有兩個狀態(tài)。這是使用 GRU 層適應(yīng)訓(xùn)練模型的方法:
- encoder_inputs =Input(shape=(None,num_encoder_tokens))
- encoder =GRU(latent_dim,return_state=True)
- encoder_outputs,state_h =encoder(encoder_inputs)
- decoder_inputs =Input(shape=(None,num_decoder_tokens))
- decoder_gru =GRU(latent_dim,return_sequences=True)
- decoder_outputs =decoder_gru(decoder_inputs,initial_state=state_h)
- decoder_dense =Dense(num_decoder_tokens,activation='softmax')
- decoder_outputs =decoder_dense(decoder_outputs)
- model =Model([encoder_inputs,decoder_inputs],decoder_outputs)
2. 我想使用整數(shù)序列的單詞級別模型,應(yīng)該怎么做?
如果你的輸入是整數(shù)序列(如按詞典索引編碼的單詞序列),你可以通過 Embedding 層嵌入這些整數(shù)標(biāo)記。方法如下:
- # Define an input sequence and process it.
- encoder_inputs =Input(shape=(None,))
- x =Embedding(num_encoder_tokens,latent_dim)(encoder_inputs)
- x,state_h,state_c =LSTM(latent_dim,
- return_state=True)(x)
- encoder_states =[state_h,state_c]
- # Set up the decoder, using `encoder_states` as initial state.
- decoder_inputs =Input(shape=(None,))
- x =Embedding(num_decoder_tokens,latent_dim)(decoder_inputs)
- x =LSTM(latent_dim,return_sequences=True)(x,initial_state=encoder_states)
- decoder_outputs =Dense(num_decoder_tokens,activation='softmax')(x)
- # Define the model that will turn
- # `encoder_input_data` & `decoder_input_data` into `decoder_target_data`
- model =Model([encoder_inputs,decoder_inputs],decoder_outputs)
- # Compile & run training
- model.compile(optimizer='rmsprop',loss='categorical_crossentropy')
- # Note that `decoder_target_data` needs to be one-hot encoded,
- # rather than sequences of integers like `decoder_input_data`!
- model.fit([encoder_input_data,decoder_input_data],decoder_target_data,
- batch_sizebatch_size=batch_size,
- epochsepochs=epochs,
- validation_split=0.2)
3. 如果我不想使用「teacher forcing」,應(yīng)該怎么做?
一些案例中可能不能使用 teacher forcing,因?yàn)槟銦o法獲取完整的目標(biāo)序列,比如,在線訓(xùn)練非常長的語句,則緩沖完成輸入-目標(biāo)語言對是不可能的。在這種情況下,你要通過將解碼器的預(yù)測重新注入解碼器輸入進(jìn)行訓(xùn)練,就像我們進(jìn)行推斷時(shí)所做的那樣。
你可以通過構(gòu)建硬編碼輸出再注入循環(huán)(output reinjection loop)的模型達(dá)到該目標(biāo):
- fromkeras.layers importLambda
- fromkeras importbackend asK
- # The first part is unchanged
- encoder_inputs =Input(shape=(None,num_encoder_tokens))
- encoder =LSTM(latent_dim,return_state=True)
- encoder_outputs,state_h,state_c =encoder(encoder_inputs)
- states =[state_h,state_c]
- # Set up the decoder, which will only process one timestep at a time.
- decoder_inputs =Input(shape=(1,num_decoder_tokens))
- decoder_lstm =LSTM(latent_dim,return_sequences=True,return_state=True)
- decoder_dense =Dense(num_decoder_tokens,activation='softmax')
- all_outputs =[]
- inputs =decoder_inputs
- for_ inrange(max_decoder_seq_length):
- # Run the decoder on one timestep
- outputs,state_h,state_c =decoder_lstm(inputs,
- initial_state=states)
- outputs =decoder_dense(outputs)
- # Store the current prediction (we will concatenate all predictions later)
- all_outputs.append(outputs)
- # Reinject the outputs as inputs for the next loop iteration
- # as well as update the states
- inputs =outputs
- states =[state_h,state_c]
- # Concatenate all predictions
- decoder_outputs =Lambda(lambdax:K.concatenate(x,axis=1))(all_outputs)
- # Define and compile model as previously
- model =Model([encoder_inputs,decoder_inputs],decoder_outputs)
- model.compile(optimizer='rmsprop',loss='categorical_crossentropy')
- # Prepare decoder input data that just contains the start character
- # Note that we could have made it a constant hard-coded in the model
- decoder_input_data =np.zeros((num_samples,1,num_decoder_tokens))
- decoder_input_data[:,0,target_token_index['t']]=1.
- # Train model as previously
- model.fit([encoder_input_data,decoder_input_data],decoder_target_data,
- batch_sizebatch_size=batch_size,
- epochsepochs=epochs,
- validation_split=0.2)
原文:
https://blog.keras.io/a-ten-minute-introduction-to-sequence-to-sequence-learning-in-keras.html
【本文是51CTO專欄機(jī)構(gòu)“機(jī)器之心”的原創(chuàng)譯文,微信公眾號“機(jī)器之心( id: almosthuman2014)”】