本文是對(duì)官方文檔 的學(xué)習(xí)筆記榛丢。
Keras 支持RNN 的理念:
- 好用: 內(nèi)建
keras.layers.RNN
,keras.layers.LSTM
,keras.layers.GRU
層,幫助你快速搭建 RNN - 易于定制: 可以迅速定制一個(gè)RNN Cell埠对, 并加入到現(xiàn)有架構(gòu)中, 快速驗(yàn)證idea。
內(nèi)建RNN Layer : 一個(gè)簡(jiǎn)單的例子
有三種內(nèi)建 RNN Layer:
-
keras.layers.SimpleRNN
, 內(nèi)部全連接,講輸入傳遞到輸出
-
keras.layers.GRU
, first proposed in Cho et al., 2014.keras.layers.LSTM
, first proposed in Hochreiter & Schmidhuber, 1997.
這是一個(gè)順序模型的簡(jiǎn)單示例棋恼,該模型處理整數(shù)序列,將每個(gè)整數(shù)嵌入到64維向量中锈玉,然后使用LSTM層處理向量序列爪飘。
model = keras.Sequential()
# Add an Embedding layer expecting input vocab of size 1000, and
# output embedding dimension of size 64.
model.add(layers.Embedding(input_dim=1000, output_dim=64))
# Add a LSTM layer with 128 internal units.
model.add(layers.LSTM(128))
# Add a Dense layer with 10 units.
model.add(layers.Dense(10))
model.summary()
內(nèi)建 RNN 支持一些列有用的 feature
- dropout ,and recurrent_dropout
- 以倒序的方式處理輸入 go_backwards
- unroll拉背, 可以在CPU 上大大加速對(duì)短輸入的訓(xùn)練
更多詳情师崎, RNN API documentation.
輸出和狀態(tài)
一般來(lái)說(shuō), RNN 會(huì)在接受整個(gè)序列后椅棺,輸出一個(gè) Vector (多對(duì)一)犁罩, 輸出Vector
維度在函數(shù)中只定齐蔽。 當(dāng)然,也可以通過(guò)設(shè)置 return_sequences=True 是的 RNN 對(duì)每一個(gè)輸入的Sample (序列中每個(gè)元素)輸出一個(gè)Vector(多對(duì)多)昼汗。 比如多層LSTM, 中間層LSTM 就會(huì)輸出序列鬼雀。
model = keras.Sequential()
model.add(layers.Embedding(input_dim=1000, output_dim=64))
# The output of GRU will be a 3D tensor of shape (batch_size, timesteps, 256)
model.add(layers.GRU(256, return_sequences=True))
# The output of SimpleRNN will be a 2D tensor of shape (batch_size, 128)
model.add(layers.SimpleRNN(128))
model.add(layers.Dense(10))
model.summary()
另外顷窒,RNN層可以返回其最終內(nèi)部狀態(tài)。返回的狀態(tài)可用于稍后恢復(fù)RNN執(zhí)行源哩,或初始化另一個(gè)RNN鞋吉。此設(shè)置通常在編碼器-解碼器序列到序列模型中使用,其中編碼器的最終狀態(tài)用作解碼器的初始狀態(tài)励烦。
要將RNN圖層配置為返回其內(nèi)部狀態(tài)谓着,請(qǐng)?jiān)趧?chuàng)建圖層時(shí)將return_state參數(shù)設(shè)置為T(mén)rue。請(qǐng)注意坛掠,LSTM具有2個(gè)狀態(tài) Tensor 赊锚,但GRU僅具有1個(gè)。
要配置圖層的初始狀態(tài)屉栓,只需使用其他關(guān)鍵字參數(shù)initial_state調(diào)用圖層即可舷蒲。請(qǐng)注意,狀態(tài)的形狀需要與圖層的單位大小匹配友多,如以下示例所示牲平。
encoder_vocab = 1000
decoder_vocab = 2000
encoder_input = layers.Input(shape=(None,))
encoder_embedded = layers.Embedding(input_dim=encoder_vocab, output_dim=64)(
encoder_input
)
# Return states in addition to output
output, state_h, state_c = layers.LSTM(64, return_state=True, name="encoder")(
encoder_embedded
)
encoder_state = [state_h, state_c]
decoder_input = layers.Input(shape=(None,))
decoder_embedded = layers.Embedding(input_dim=decoder_vocab, output_dim=64)(
decoder_input
)
# Pass the 2 states to a new LSTM layer, as initial state
decoder_output = layers.LSTM(64, name="decoder")(
decoder_embedded, initial_state=encoder_state
)
output = layers.Dense(10)(decoder_output)
model = keras.Model([encoder_input, decoder_input], output)
model.summary()
RNN Layer and RNN cells
對(duì)應(yīng) RNN Layer Tensorflow 2 還提供 RNN Cells。 與 Layer 每次處理一個(gè)序列不同域滥, 每個(gè) RNN Cell 每次只能處理一個(gè) Timestamp纵柿。 RNN Cell 的意義在于提供給開(kāi)發(fā)者供開(kāi)發(fā)者自己組裝 RNN Layer ,一般用于研究启绰。
內(nèi)置的 RNN Cell
keras.layers.SimpleRNNCell
corresponds to theSimpleRNN
layer.keras.layers.GRUCell
corresponds to theGRU
layer.keras.layers.LSTMCell
corresponds to theLSTM
layer.
跨批次狀態(tài)
跨批次狀態(tài) (cross-batch statefulness) 會(huì)用在處理非常長(zhǎng)的sequnce(甚至是無(wú)限長(zhǎng))昂儒。
RNN 會(huì)認(rèn)為每個(gè) Sample 都是獨(dú)立的, 所以在每次 Batch 結(jié)束后都會(huì)重置狀態(tài)委可。 但是如果處理非常長(zhǎng)的序列的時(shí)候荆忍, 有時(shí)候需要先把長(zhǎng)序列給分成一組一組的短序列,然后再把短序列送給 RNN處理撤缴。 如果每次batch 都清空狀態(tài)的話(huà)刹枉, 就無(wú)法實(shí)現(xiàn)用短序列拼接出來(lái)一個(gè)長(zhǎng)序列的目的了。
如果想讓 RNN在 batch 以后不重置狀態(tài)屈呕, 可以設(shè)置 stateful=True
微宝。
如果有個(gè)序列 s = [t0, t1, ... t1546, t1547], 可以將其分為
s1 = [t0, t1, ... t100]
s2 = [t101, ... t201]
...
s16 = [t1501, ... t1547]
處理的時(shí)候
lstm_layer = layers.LSTM(64, stateful=True)
for s in sub_sequences:
output = lstm_layer(s)
完整的例子
paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)
lstm_layer = layers.LSTM(64, stateful=True)
output = lstm_layer(paragraph1)
output = lstm_layer(paragraph2)
output = lstm_layer(paragraph3)
# reset_states() will reset the cached state to the original initial_state.
# If no initial_state was provided, zero-states will be used by default.
lstm_layer.reset_states()
RNN 狀態(tài)復(fù)用
如果想獲取 RNN 層的狀態(tài)虎眨, 并將其用在其他層中蟋软, 則需要從 layer.states 獲取狀態(tài)镶摘。(不在layer.weights() 中 )。 設(shè)置初始化狀態(tài)使用 new_layer(inputs, initial_state=layer.states)
paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)
lstm_layer = layers.LSTM(64, stateful=True)
output = lstm_layer(paragraph1)
output = lstm_layer(paragraph2)
existing_state = lstm_layer.states
new_lstm_layer = layers.LSTM(64)
new_output = new_lstm_layer(paragraph3, initial_state=existing_state)
雙向 RNN
某些序列岳守,比如文本凄敢, 雙向處理可以帶來(lái)更好的效果。 Keras 提供 keras.layers.Bidirectional
用來(lái)構(gòu)建雙向 RNN 網(wǎng)絡(luò)湿痢。
model = keras.Sequential()
model.add(
layers.Bidirectional(layers.LSTM(64, return_sequences=True), input_shape=(5, 10))
)
model.add(layers.Bidirectional(layers.LSTM(32)))
model.add(layers.Dense(10))
model.summary()
關(guān)于雙向 RNN 涝缝, 更多信息在 the API docs.
性能優(yōu)化與 CuDNN 核
TF2.0 中 當(dāng)發(fā)現(xiàn)有GPU的時(shí)候 LSTM , GRU 會(huì)自動(dòng)使用CuDNN . 但是如果改了他們的默認(rèn)設(shè)置譬重, 他們可能不會(huì)自動(dòng)使用 CuDNN 拒逮。 例如:
- 把 activation 從 tanh 修改成其他函數(shù)
- 把 recurrent_activation 從 sigmoid 換成函數(shù)
- recurrent_dropout > 0
- unroll = TRUE
- use_bias = False
- 當(dāng)輸入數(shù)據(jù)未嚴(yán)格右填充時(shí)使用屏蔽(如果掩碼對(duì)應(yīng)于嚴(yán)格右填充數(shù)據(jù),則仍可以使用CuDNN臀规。這是最常見(jiàn)的情況)滩援。
盡可能使用 CuDNN
建立一個(gè)簡(jiǎn)單的LSTM模型來(lái)演示性能差異。將MNIST數(shù)字的行序列(作為時(shí)間步長(zhǎng)處理每一行像素)用作輸入序列塔嬉,并預(yù)測(cè)該數(shù)字的標(biāo)簽玩徊。
batch_size = 64
# Each MNIST image batch is a tensor of shape (batch_size, 28, 28).
# Each input sequence will be of size (28, 28) (height is treated like time).
input_dim = 28
units = 64
output_size = 10 # labels are from 0 to 9
# Build the RNN model
def build_model(allow_cudnn_kernel=True):
# CuDNN is only available at the layer level, and not at the cell level.
# This means `LSTM(units)` will use the CuDNN kernel,
# while RNN(LSTMCell(units)) will run on non-CuDNN kernel.
if allow_cudnn_kernel:
# The LSTM layer with default options uses CuDNN.
lstm_layer = keras.layers.LSTM(units, input_shape=(None, input_dim))
else:
# Wrapping a LSTMCell in a RNN layer will not use CuDNN.
lstm_layer = keras.layers.RNN(
keras.layers.LSTMCell(units), input_shape=(None, input_dim)
)
model = keras.models.Sequential(
[
lstm_layer,
keras.layers.BatchNormalization(),
keras.layers.Dense(output_size),
]
)
return model
mnist = keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
sample, sample_label = x_train[0], y_train[0]
model = build_model(allow_cudnn_kernel=True)
model.compile(
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer="sgd",
metrics=["accuracy"],
)
model.fit(
x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1
)
可以將其和如下不用 CuDNN的比較一下性能
noncudnn_model = build_model(allow_cudnn_kernel=False)
noncudnn_model.set_weights(model.get_weights())
noncudnn_model.compile(
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer="sgd",
metrics=["accuracy"],
)
noncudnn_model.fit(
x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1
)
在安裝了NVIDIA GPU和CuDNN的計(jì)算機(jī)上運(yùn)行時(shí),與使用常規(guī)TensorFlow內(nèi)核的模型相比谨究,使用CuDNN構(gòu)建的模型的訓(xùn)練速度要快得多佣赖。
相同的支持CuDNN的模型也可以用于在僅CPU的環(huán)境中運(yùn)行。下面的tf.device注釋只是強(qiáng)制放置設(shè)備记盒。如果沒(méi)有可用的GPU憎蛤,默認(rèn)情況下該模型將在CPU上運(yùn)行。
import matplotlib.pyplot as plt
with tf.device("CPU:0"):
cpu_model = build_model(allow_cudnn_kernel=True)
cpu_model.set_weights(model.get_weights())
result = tf.argmax(cpu_model.predict_on_batch(tf.expand_dims(sample, 0)), axis=1)
print(
"Predicted result is: %s, target result is: %s" % (result.numpy(), sample_label)
)
plt.imshow(sample, cmap=plt.get_cmap("gray"))
將 list/dict 作為RNN的輸入(或嵌套輸入)
嵌套結(jié)構(gòu)允許實(shí)施者在單個(gè)時(shí)間步之內(nèi)包括更多信息纪吮。例如俩檬,一個(gè)視頻幀可以同時(shí)具有音頻和視頻輸入。在這種情況下碾盟,數(shù)據(jù)形狀可能是:
[batch, timestep, {"video": [height, width, channel], "audio": [frequency]}]
在另一個(gè)示例中棚辽,筆跡數(shù)據(jù)可以具有筆的當(dāng)前位置的坐標(biāo)x和y以及壓力信息。因此數(shù)據(jù)表示可以是:
[batch, timestep, {"location": [x, y], "pressure": [force]}]
以下代碼提供了一個(gè)示例冰肴,說(shuō)明如何構(gòu)建接受此類(lèi)結(jié)構(gòu)化輸入的自定義RNN單元屈藐。
定制化Cell 以支持嵌套輸入
class NestedCell(keras.layers.Layer):
def __init__(self, unit_1, unit_2, unit_3, **kwargs):
self.unit_1 = unit_1
self.unit_2 = unit_2
self.unit_3 = unit_3
self.state_size = [tf.TensorShape([unit_1]), tf.TensorShape([unit_2, unit_3])]
self.output_size = [tf.TensorShape([unit_1]), tf.TensorShape([unit_2, unit_3])]
super(NestedCell, self).__init__(**kwargs)
def build(self, input_shapes):
# expect input_shape to contain 2 items, [(batch, i1), (batch, i2, i3)]
i1 = input_shapes[0][1]
i2 = input_shapes[1][1]
i3 = input_shapes[1][2]
self.kernel_1 = self.add_weight(
shape=(i1, self.unit_1), initializer="uniform", name="kernel_1"
)
self.kernel_2_3 = self.add_weight(
shape=(i2, i3, self.unit_2, self.unit_3),
initializer="uniform",
name="kernel_2_3",
)
def call(self, inputs, states):
# inputs should be in [(batch, input_1), (batch, input_2, input_3)]
# state should be in shape [(batch, unit_1), (batch, unit_2, unit_3)]
input_1, input_2 = tf.nest.flatten(inputs)
s1, s2 = states
output_1 = tf.matmul(input_1, self.kernel_1)
output_2_3 = tf.einsum("bij,ijkl->bkl", input_2, self.kernel_2_3)
state_1 = s1 + output_1
state_2_3 = s2 + output_2_3
output = (output_1, output_2_3)
new_states = (state_1, state_2_3)
return output, new_states
def get_config(self):
return {"unit_1": self.unit_1, "unit_2": unit_2, "unit_3": self.unit_3}
RNN Model 以支持嵌套輸入/輸出
unit_1 = 10
unit_2 = 20
unit_3 = 30
i1 = 32
i2 = 64
i3 = 32
batch_size = 64
num_batches = 10
timestep = 50
cell = NestedCell(unit_1, unit_2, unit_3)
rnn = keras.layers.RNN(cell)
input_1 = keras.Input((None, i1))
input_2 = keras.Input((None, i2, i3))
outputs = rnn((input_1, input_2))
model = keras.models.Model([input_1, input_2], outputs)
model.compile(optimizer="adam", loss="mse", metrics=["accuracy"])
用隨機(jī)生成數(shù)據(jù)進(jìn)行訓(xùn)練
input_1_data = np.random.random((batch_size * num_batches, timestep, i1))
input_2_data = np.random.random((batch_size * num_batches, timestep, i2, i3))
target_1_data = np.random.random((batch_size * num_batches, unit_1))
target_2_data = np.random.random((batch_size * num_batches, unit_2, unit_3))
input_data = [input_1_data, input_2_data]
target_data = [target_1_data, target_2_data]
model.fit(input_data, target_data, batch_size=batch_size)