TF2 Keras (9) : Recurrent Neural Networks (RNN)

本文是對(duì)官方文檔 的學(xué)習(xí)筆記榛丢。


Keras 支持RNN 的理念:

內(nèi)建RNN Layer : 一個(gè)簡(jiǎn)單的例子

有三種內(nèi)建 RNN Layer:

  1. keras.layers.GRU, first proposed in Cho et al., 2014.

  2. 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

跨批次狀態(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 以支持嵌套輸入

參考 用類(lèi)的方法創(chuàng)建新的層和模型

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)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市熙尉,隨后出現(xiàn)的幾起案子联逻,更是在濱河造成了極大的恐慌,老刑警劉巖检痰,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件包归,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡铅歼,警方通過(guò)查閱死者的電腦和手機(jī)公壤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)换可,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人厦幅,你說(shuō)我怎么就攤上這事沾鳄。” “怎么了确憨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵译荞,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我缚态,道長(zhǎng)磁椒,這世上最難降的妖魔是什么堤瘤? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任玫芦,我火速辦了婚禮,結(jié)果婚禮上本辐,老公的妹妹穿的比我還像新娘桥帆。我一直安慰自己,他們只是感情好慎皱,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布老虫。 她就那樣靜靜地躺著,像睡著了一般茫多。 火紅的嫁衣襯著肌膚如雪祈匙。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,806評(píng)論 1 290
  • 那天天揖,我揣著相機(jī)與錄音夺欲,去河邊找鬼。 笑死今膊,一個(gè)胖子當(dāng)著我的面吹牛些阅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播斑唬,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼市埋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了恕刘?” 一聲冷哼從身側(cè)響起缤谎,我...
    開(kāi)封第一講書(shū)人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎褐着,沒(méi)想到半個(gè)月后弓千,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡献起,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年洋访,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了镣陕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡姻政,死狀恐怖呆抑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情汁展,我是刑警寧澤鹊碍,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站食绿,受9級(jí)特大地震影響侈咕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜器紧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一耀销、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧铲汪,春花似錦熊尉、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至齿梁,卻和暖如春催植,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背勺择。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工创南, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酵幕。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓扰藕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親芳撒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子邓深,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容