Keras編解碼模型序列預測
編解碼模型提供了一種使用循環神經網絡來解決諸如機器翻譯這樣的序列預測問題的模式。
編解碼模型可以用Keras Python深度學習庫來進行開發,使用該模型開發的神經網絡機器翻譯係統的示例在Keras博客上也有描述,示例代碼與Keras項目一起分發。該示例為用戶開發自己的編解碼LSTM模型提供了基礎。
在本教程中,你將學會如何用Keras為序列預測問題開發複雜的編解碼循環神經網絡,包括:
- 如何在Keras中為序列預測定義一個複雜的編解碼模型。
- 如何定義一個可用於評估編解碼LSTM模型的可伸縮序列預測問題。
- 如何在Keras中應用編解碼LSTM模型來解決可伸縮的整數序列預測問題。
教程概述
本教程分為三部分:
- Keras中的編解碼模型
- 可伸縮的序列問題
- 用於序列預測的編解碼LSTM
Python環境
- 需安裝Python SciPy,可以使用Python 2/3進行開發。
- 必須安裝Keras(2.0或更高版本),並且使用TensorFlow或Theano作為後端。
- 需安裝scikit-learn、Pandas、NumPy和Matplotlib。
這篇文章對搭建環境有一定的幫助:
Keras中的編解碼模型
編解碼模型是針對序列預測問題組織循環神經網絡的一種方法。它最初是為機器翻譯問題而開發的,並且在相關的序列預測問題(如文本摘要和問題回答)中已被證明是有效的。
該方法涉及到兩個循環神經網絡,一個用於對源序列進行編碼,稱為編碼器,另一個將編碼的源序列解碼為目標序列,稱為解碼器。
Keras深度學習Python庫提供了一個機器翻譯編解碼的例子(lstm_seq2seq.py),作者在文章“十分鍾簡介:在Keras中進行序列學習”中進行了講解。
以該示例的代碼為基礎,我們可以開發一個通用函數來定義編解碼循環神經網絡。下麵是define_models()
函數。
# returns train, inference_encoder and inference_decoder models
def define_models(n_input, n_output, n_units):
# define training encoder
encoder_inputs = Input(shape=(None, n_input))
encoder = LSTM(n_units, return_state=True)
encoder_outputs, state_h, state_c = encoder(encoder_inputs)
encoder_states = [state_h, state_c]
# define training decoder
decoder_inputs = Input(shape=(None, n_output))
decoder_lstm = LSTM(n_units, return_sequences=True, return_state=True)
decoder_outputs, _, _ = decoder_lstm(decoder_inputs, initial_state=encoder_states)
decoder_dense = Dense(n_output, activation='softmax')
decoder_outputs = decoder_dense(decoder_outputs)
model = Model([encoder_inputs, decoder_inputs], decoder_outputs)
# define inference encoder
encoder_model = Model(encoder_inputs, encoder_states)
# define inference decoder
decoder_state_input_h = Input(shape=(n_units,))
decoder_state_input_c = Input(shape=(n_units,))
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)
# return all models
return model, encoder_model, decoder_model
該函數有3個參數:
- n_input:輸入序列的基數,例如每個時間步長的特征、字或字符的個數。
- n_output:輸出序列的基數,例如每個時間步長的特征、字或字符的個數。
- n_units:在編碼器和解碼器模型中創建的單元的數量,例如128或256。
該函數創建並返回3個模型:
- train:給定源、目標和偏移目標序列進行訓練的模型。
- inference_encoder:對新的源序列進行預測時使用的編碼器模型。
- inference_decoder:對新的源序列進行預測時使用的解碼器模型。
該模型對給定的源序列和目標序列進行訓練,其中模型以源序列和目標序列的偏移作為輸入,對整個目標序列進行預測。
該模型對源和目標序列進行訓練,其中模型將目標序列的源和位移版本作為輸入,並預測整個目標序列。
例如,源序列可能是[1,2,3],目標序列是[4,5,6],則訓練時模型的輸入和輸出將是:
Input1: ['1', '2', '3']
Input2: ['_', '4', '5']
Output: ['4', '5', '6']
當為新的源序列生成目標序列時,該模型將會被遞歸調用。
源序列會被編碼,同時,目標序列生成一個元素,使用類似於“_”這樣的起始符來啟動這個過程。因此,在上述情況下,訓練過程中會生成以下這樣的輸入輸出對:
t, Input1, Input2, Output
1, ['1', '2', '3'], '_', '4'
2, ['1', '2', '3'], '4', '5'
3, ['1', '2', '3'], '5', '6'
這裏,你可以看到遞歸是如何使用模型來構建輸出序列。在預測過程中,inference_encoder
模型用於對輸入序列進行編碼。然後,inference_decoder
模型用於逐步生成預測。
下麵這個predict_sequence()
函數可以在模型訓練完成之後根據給定的源序列生成目標序列。
# generate target given source sequence
def predict_sequence(infenc, infdec, source, n_steps, cardinality):
# encode
state = infenc.predict(source)
# start of sequence input
target_seq = array([0.0 for _ in range(cardinality)]).reshape(1, 1, cardinality)
# collect predictions
output = list()
for t in range(n_steps):
# predict next char
yhat, h, c = infdec.predict([target_seq] + state)
# store prediction
output.append(yhat[0,0,:])
# update state
state = [h, c]
# update target sequence
target_seq = yhat
return array(output)
此函數需要5個參數:
- infenc:對新的源序列進行預測時使用的編碼器模型。
- infdec:對新的源序列進行預測時使用的解碼器模型。
- source:已編碼的源序列。
- n_steps:目標序列中的時間步長數。
- cardinality:輸出序列的基數,例如每個時間步長的特征、單詞或字符的數量。
該函數返回包含目標序列的列表。
可伸縮序列問題
在本章節中,我們將提出一個可伸縮的序列預測問題。
源序列是一係列隨機生成的整數值,例如[20, 36, 40, 10, 34, 28],目標序列是輸入序列的反向預定義子集,例如前3個元素倒序排列[40, 36, 20]。源序列的長度可配置,輸入和輸出序列的基數以及目標序列的長度也可配置。我們將使用的源序列元素個數是6,基數是50,目標序列元素個數是3。
下麵是具體的例子。
Source, Target
[13, 28, 18, 7, 9, 5] [18, 28, 13]
[29, 44, 38, 15, 26, 22] [38, 44, 29]
[27, 40, 31, 29, 32, 1] [31, 40, 27]
...
首先定義一個函數來生成隨機整數序列。我們將使用0值作為序列字符的填充或起始,因此0是保留字符,不能在源序列中使用。要實現這一點,把1添加配置的基數,以確保獨熱編碼足夠大。
例如:
n_features = 50 + 1
可以使用randint()
函數生成1和-1之間的隨機整數。下麵的generate_sequence()
生成了一個隨機整數序列。
# generate a sequence of random integers
def generate_sequence(length, n_unique):
return [randint(1, n_unique-1) for _ in range(length)]
接下來,創建一個與給定源序列相對應的輸出序列。為了方便起見,把源序列的前n個元素作為目標序列並將其逆序排列。
# define target sequence
target = source[:n_out]
target.reverse()
我們還需要一個輸出序列向前移動一個時間步長的版本,把它作為生成的模擬目標序列。可以直接由目標序列進行創建。
# create padded input target sequence
target_in = [0] + target[:-1]
現在,所有的序列已經定義好了,下麵可以對它們進行獨熱編碼了,即將它們轉換為二進製向量序列。可以使用Keras內置的to_categorical()
函數來實現這個。
可以將所有這些操作都放到get_dataset()
這個產生指定數量序列的函數中。
# prepare data for the LSTM
def get_dataset(n_in, n_out, cardinality, n_samples):
X1, X2, y = list(), list(), list()
for _ in range(n_samples):
# generate source sequence
source = generate_sequence(n_in, cardinality)
# define target sequence
target = source[:n_out]
target.reverse()
# create padded input target sequence
target_in = [0] + target[:-1]
# encode
src_encoded = to_categorical([source], num_classes=cardinality)
tar_encoded = to_categorical([target], num_classes=cardinality)
tar2_encoded = to_categorical([target_in], num_classes=cardinality)
# store
X1.append(src_encoded)
X2.append(tar2_encoded)
y.append(tar_encoded)
return array(X1), array(X2), array(y)
最後,對獨熱編碼序列進行解碼,以使其可以再次讀取。這不僅對於打印生成的目標序列是必需的,而且也可用於比較完全預測目標序列是否與預期目標序列相匹配。 one_hot_decode()
函數將對已編碼的序列進行解碼。
# decode a one hot encoded string
def one_hot_decode(encoded_seq):
return [argmax(vector) for vector in encoded_seq]
我們可以將所有這些結合在一起並進行測試。下麵列出了一個完整的代碼示例。
from random import randint
from numpy import array
from numpy import argmax
from keras.utils import to_categorical
# generate a sequence of random integers
def generate_sequence(length, n_unique):
return [randint(1, n_unique-1) for _ in range(length)]
# prepare data for the LSTM
def get_dataset(n_in, n_out, cardinality, n_samples):
X1, X2, y = list(), list(), list()
for _ in range(n_samples):
# generate source sequence
source = generate_sequence(n_in, cardinality)
# define target sequence
target = source[:n_out]
target.reverse()
# create padded input target sequence
target_in = [0] + target[:-1]
# encode
src_encoded = to_categorical([source], num_classes=cardinality)
tar_encoded = to_categorical([target], num_classes=cardinality)
tar2_encoded = to_categorical([target_in], num_classes=cardinality)
# store
X1.append(src_encoded)
X2.append(tar2_encoded)
y.append(tar_encoded)
return array(X1), array(X2), array(y)
# decode a one hot encoded string
def one_hot_decode(encoded_seq):
return [argmax(vector) for vector in encoded_seq]
# configure problem
n_features = 50 + 1
n_steps_in = 6
n_steps_out = 3
# generate a single source and target sequence
X1, X2, y = get_dataset(n_steps_in, n_steps_out, n_features, 1)
print(X1.shape, X2.shape, y.shape)
print('X1=%s, X2=%s, y=%s' % (one_hot_decode(X1[0]), one_hot_decode(X2[0]), one_hot_decode(y[0])))
運行示例,首先打印生成的數據集的形狀,確保訓練模型所需的3D形狀符合我們的期望。
然後將生成的序列解碼並打印到屏幕上,展示一下源和目標序列是否符合我們的本意,以及正在進行的解碼操作。
(1, 6, 51) (1, 3, 51) (1, 3, 51)
X1=[32, 16, 12, 34, 25, 24], X2=[0, 12, 16], y=[12, 16, 32]
下麵將開發一個針對該序列預測問題的模型。
用於序列預測的編解碼LSTM
在本章節中,我們將把第一節開發的編LSTM模型應用到第二節開發的序列預測問題上。
第一步是配置這個問題。
# configure problem
n_features = 50 + 1
n_steps_in = 6
n_steps_out = 3
接下來,定義模型並編譯訓練模型。
# define model
train, infenc, infdec = define_models(n_features, n_features, 128)
train.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc'])
接下來,生成包含10萬個樣本的訓練數據集並訓練模型。
# generate training dataset
X1, X2, y = get_dataset(n_steps_in, n_steps_out, n_features, 100000)
print(X1.shape,X2.shape,y.shape)
# train model
train.fit([X1, X2], y, epochs=1)
模型訓練完成之後,就可以對其進行評估了。評估的辦法是對100個源序列進行預測並計算目標序列預測正確的個數。可以在解碼的序列上使用numpy的array_equal()
函數來檢查是否相等。
# evaluate LSTM
total, correct = 100, 0
for _ in range(total):
X1, X2, y = get_dataset(n_steps_in, n_steps_out, n_features, 1)
target = predict_sequence(infenc, infdec, X1, n_steps_out, n_features)
if array_equal(one_hot_decode(y[0]), one_hot_decode(target)):
correct += 1
print('Accuracy: %.2f%%' % (float(correct)/float(total)*100.0))
最後,示例將產生一些預測並打印出解碼的源、目標和預測目標序列,以檢查模型是否按預期的那樣運行。
將上麵所有的代碼片段合在一起,完整的代碼示例如下所示。
from random import randint
from numpy import array
from numpy import argmax
from numpy import array_equal
from keras.utils import to_categorical
from keras.models import Model
from keras.layers import Input
from keras.layers import LSTM
from keras.layers import Dense
# generate a sequence of random integers
def generate_sequence(length, n_unique):
return [randint(1, n_unique-1) for _ in range(length)]
# prepare data for the LSTM
def get_dataset(n_in, n_out, cardinality, n_samples):
X1, X2, y = list(), list(), list()
for _ in range(n_samples):
# generate source sequence
source = generate_sequence(n_in, cardinality)
# define padded target sequence
target = source[:n_out]
target.reverse()
# create padded input target sequence
target_in = [0] + target[:-1]
# encode
src_encoded = to_categorical([source], num_classes=cardinality)
tar_encoded = to_categorical([target], num_classes=cardinality)
tar2_encoded = to_categorical([target_in], num_classes=cardinality)
# store
X1.append(src_encoded)
X2.append(tar2_encoded)
y.append(tar_encoded)
return array(X1), array(X2), array(y)
# returns train, inference_encoder and inference_decoder models
def define_models(n_input, n_output, n_units):
# define training encoder
encoder_inputs = Input(shape=(None, n_input))
encoder = LSTM(n_units, return_state=True)
encoder_outputs, state_h, state_c = encoder(encoder_inputs)
encoder_states = [state_h, state_c]
# define training decoder
decoder_inputs = Input(shape=(None, n_output))
decoder_lstm = LSTM(n_units, return_sequences=True, return_state=True)
decoder_outputs, _, _ = decoder_lstm(decoder_inputs, initial_state=encoder_states)
decoder_dense = Dense(n_output, activation='softmax')
decoder_outputs = decoder_dense(decoder_outputs)
model = Model([encoder_inputs, decoder_inputs], decoder_outputs)
# define inference encoder
encoder_model = Model(encoder_inputs, encoder_states)
# define inference decoder
decoder_state_input_h = Input(shape=(n_units,))
decoder_state_input_c = Input(shape=(n_units,))
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)
# return all models
return model, encoder_model, decoder_model
# generate target given source sequence
def predict_sequence(infenc, infdec, source, n_steps, cardinality):
# encode
state = infenc.predict(source)
# start of sequence input
target_seq = array([0.0 for _ in range(cardinality)]).reshape(1, 1, cardinality)
# collect predictions
output = list()
for t in range(n_steps):
# predict next char
yhat, h, c = infdec.predict([target_seq] + state)
# store prediction
output.append(yhat[0,0,:])
# update state
state = [h, c]
# update target sequence
target_seq = yhat
return array(output)
# decode a one hot encoded string
def one_hot_decode(encoded_seq):
return [argmax(vector) for vector in encoded_seq]
# configure problem
n_features = 50 + 1
n_steps_in = 6
n_steps_out = 3
# define model
train, infenc, infdec = define_models(n_features, n_features, 128)
train.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc'])
# generate training dataset
X1, X2, y = get_dataset(n_steps_in, n_steps_out, n_features, 100000)
print(X1.shape,X2.shape,y.shape)
# train model
train.fit([X1, X2], y, epochs=1)
# evaluate LSTM
total, correct = 100, 0
for _ in range(total):
X1, X2, y = get_dataset(n_steps_in, n_steps_out, n_features, 1)
target = predict_sequence(infenc, infdec, X1, n_steps_out, n_features)
if array_equal(one_hot_decode(y[0]), one_hot_decode(target)):
correct += 1
print('Accuracy: %.2f%%' % (float(correct)/float(total)*100.0))
# spot check some examples
for _ in range(10):
X1, X2, y = get_dataset(n_steps_in, n_steps_out, n_features, 1)
target = predict_sequence(infenc, infdec, X1, n_steps_out, n_features)
print('X=%s y=%s, yhat=%s' % (one_hot_decode(X1[0]), one_hot_decode(y[0]), one_hot_decode(target)))
運行示例,首先打印準備好的數據集的形狀。
(100000, 6, 51) (100000, 3, 51) (100000, 3, 51)
接下來,顯示一個進度條,在一台現代多核CPU上的運行時間不會超過一分鍾。
100000/100000 [==============================] - 50s - loss: 0.6344 - acc: 0.7968
再接下來,評估模型並打印準確度。 可以看到,該模型在新的隨機生成的樣本上實現了100%的準確度。
Accuracy: 100.00%
最後,生成10個新的例子,然後預測目標序列。 可以看到,模型正確地預測了每種情況下的輸出序列,並且期望值與源序列顛倒的前3個元素相匹配。
X=[22, 17, 23, 5, 29, 11] y=[23, 17, 22], yhat=[23, 17, 22]
X=[28, 2, 46, 12, 21, 6] y=[46, 2, 28], yhat=[46, 2, 28]
X=[12, 20, 45, 28, 18, 42] y=[45, 20, 12], yhat=[45, 20, 12]
X=[3, 43, 45, 4, 33, 27] y=[45, 43, 3], yhat=[45, 43, 3]
X=[34, 50, 21, 20, 11, 6] y=[21, 50, 34], yhat=[21, 50, 34]
X=[47, 42, 14, 2, 31, 6] y=[14, 42, 47], yhat=[14, 42, 47]
X=[20, 24, 34, 31, 37, 25] y=[34, 24, 20], yhat=[34, 24, 20]
X=[4, 35, 15, 14, 47, 33] y=[15, 35, 4], yhat=[15, 35, 4]
X=[20, 28, 21, 39, 5, 25] y=[21, 28, 20], yhat=[21, 28, 20]
X=[50, 38, 17, 25, 31, 48] y=[17, 38, 50], yhat=[17, 38, 50]
現在,你就擁有了編解碼器LSTM模型的模板了,你可以將其應用到你自己的序列預測問題上了。
總結
在本教程中,你學會了如何用Keras為序列預測問題開發複雜的編解碼循環神經網絡,具體一點說,包括以下幾個方麵:
- 如何在Keras中為序列預測定義一個複雜的編解碼模型。
- 如何定義一個可用於評估編解碼LSTM模型的可伸縮序列預測問題。
- 如何在Keras中應用編LSTM模型來解決可伸縮的整數序列預測問題。
文章原標題《How to Develop an Encoder-Decoder Model for Sequence-to-Sequence Prediction in Keras》,作者: Jason Brownlee,譯者:夏天,審校:主題曲。
文章為簡譯,更為詳細的內容請查看原文。
本文由北郵@愛可可-愛生活老師推薦,阿裏雲雲棲社區組織翻譯。
最後更新:2017-11-08 20:35:06