您當前的位置:首頁 > 收藏

從零開始的機器學習實戰(十四):RNN

作者:由 Duduru 發表于 收藏時間:2019-01-29

當擊球手擊出棒球,你就開始跑,預測球的軌跡然後抓住他,你所做的就是預測未來。在這一章,我們討論的就是迴圈神經網路(RNN),一類預測未來的網路。它們可以分析時間序列資料,諸如股票價格,並告訴你什麼時候買入和賣出。或者預測行車軌跡,避免發生交通意外。一般地說,它們可在任意長度的序列上工作,而不是目前我們討論的只能在固定長度的輸入上工作的網路。它們可以把可變長的語句輸入,使得它們在諸如自動翻譯,語音到文字或者情感分析等自然語言處理系統中極為有用。

迴圈神經元

到目前為止,我們主要關注的是前饋神經網路,其中啟用僅從輸入層到輸出層流動。 RNN與其十分類似,除了它也有連線指向後方。如圖,迴圈神經元收到輸入

x_t

和本身上一個時間步的輸出

y_{t-1}

,我們可以沿著時間軸展開網路:

從零開始的機器學習實戰(十四):RNN

同理,你也可以很容易的建立一個迴圈神經元層,輸入

x_t

和本身上一個時間步的輸出

y_{t-1}

,都是向量而不是一個神經元下的標量

從零開始的機器學習實戰(十四):RNN

每個神經元有兩組權重,一組用於輸入

x_t

, 另一組用於上一個時間步的輸出

y_{t-1}

從零開始的機器學習實戰(十四):RNN

公式也可以向量化,對整個小批次進行計算

從零開始的機器學習實戰(十四):RNN

一般情況下,時間步t處的單元狀態,記為

h^{(t)}

(h代表“隱藏”),是該時間步的某些輸入和前一時間步的狀態的函式:

h^{(t)}=f(h^{(t-1)},x^{(t)})

。其在時間步t處的輸出,表示為

y^{(t)}

,我們前面的例子中,輸出等於單元狀態(y=h),但是在更復雜的單元中並不總是如此。

從零開始的機器學習實戰(十四):RNN

輸入和輸出序列

RNN 可以

輸入一系列,併產生一系列輸出,比如根據前面的情況預測每天股票的情況,根據前面n-1天情況預測第n天的,並向前移動(如左上圖)。

輸入一系列,並忽略除最後一個之外的所有輸出,比如分析一句話的情感是喜歡還是反感(如右上圖)

提供一個輸入(其他為零),輸出一個序列。 這是一個向量到序列的網路。 比如,輸入一幅影象,輸出該影象的標題(如左下角)

有一個序列到向量網路,稱為編碼器,後面跟著一個稱為解碼器的向量到序列網路(右下角)。 例如,這可以用於將句子從一種語言翻譯成另一種語言。聽完整個句子翻譯比一個個單詞翻譯準確的多

從零開始的機器學習實戰(十四):RNN

TensorFlow實現基本的RNN

我們可以試著不用高階的API,來完成一個簡單的RNN,這個RNN 只執行兩個時間步,每個時間步輸入大小為 3 的向量

import

numpy

as

np

import

tensorflow

as

tf

if

__name__

==

‘__main__’

n_inputs

=

3

n_neurons

=

5

X0

=

tf

placeholder

tf

float32

None

n_inputs

])

X1

=

tf

placeholder

tf

float32

None

n_inputs

])

Wx

=

tf

Variable

tf

random_normal

shape

=

n_inputs

n_neurons

],

dtype

=

tf

float32

))

Wy

=

tf

Variable

tf

random_normal

shape

=

n_neurons

n_neurons

],

dtype

=

tf

float32

))

b

=

tf

Variable

tf

zeros

([

1

n_neurons

],

dtype

=

tf

float32

))

Y0

=

tf

tanh

tf

matmul

X0

Wx

+

b

Y1

=

tf

tanh

tf

matmul

Y0

Wy

+

tf

matmul

X1

Wx

+

b

init

=

tf

global_variables_initializer

()

# Mini-batch: instance 0,instance 1,instance 2,instance 3

X0_batch

=

np

array

([[

0

1

2

],

3

4

5

],

6

7

8

],

9

0

1

]])

# t = 0

X1_batch

=

np

array

([[

9

8

7

],

0

0

0

],

6

5

4

],

3

2

1

]])

# t = 1

with

tf

Session

()

as

sess

init

run

()

Y0_val

Y1_val

=

sess

run

([

Y0

Y1

],

feed_dict

=

{

X0

X0_batch

X1

X1_batch

})

print

Y0_val

\n

print

Y1_val

這個網路看起來很像一個雙層前饋神經網路,有一些改動:

兩個層共享相同的權重和偏差項,

每一層都有輸入,並從每個層獲得輸出。

時間上的靜態展開

static_rnn()

函式透過連結單元來建立一個展開的 RNN 網路,下面程式碼展示的和上文相同

X0

=

tf

placeholder

tf

float32

None

n_inputs

])

X1

=

tf

placeholder

tf

float32

None

n_inputs

])

basic_cell

=

tf

contrib

rnn

BasicRNNCell

num_units

=

n_neurons

output_seqs

states

=

tf

contrib

rnn

static_rnn

basic_cell

X0

X1

],

dtype

=

tf

float32

Y0

Y1

=

output_seqs

建立輸入佔位符和上文一樣。

建立一個

BasicRNNCell

,它類似一個工廠,建立單元的副本以構建展開的 RNN(每個時間步一個)。

呼叫

static_rnn()

,向它提供單元工廠和輸入張量,並告訴它輸入的資料型別(用來建立初始狀態矩陣,預設情況下是全零)

static_rnn()

函式返回兩個物件:

第一個是包含每個時間步的輸出張量的 Python 列表。

第二個是包含網路最終狀態的張量。 (基本單元時,狀態等於輸出)

如果有50個時間步長,你必須定義50個佔位符和50個輸出的張量,我們可以簡化一下:

X

=

tf

placeholder

tf

float32

None

n_steps

n_inputs

])

#增加了一維表示時間步

X_seqs

=

tf

unstack

tf

transpose

X

perm

=

1

0

2

]))

#首先使用transpose()函式交換前兩個維度,以便時間步驟現在是第一維度。

#然後, unstack()函式沿第一維(時間步)提取張量的 Python 列表

basic_cell

=

tf

contrib

rnn

BasicRNNCell

num_units

=

n_neurons

output_seqs

states

=

tf

contrib

rnn

static_rnn

basic_cell

X_seqs

dtype

=

tf

float32

outputs

=

tf

transpose

tf

stack

output_seqs

),

perm

=

1

0

2

])

X_batch

=

np

array

([

# t = 0 t = 1

[[

0

1

2

],

9

8

7

]],

# instance 1

[[

3

4

5

],

0

0

0

]],

# instance 2

[[

6

7

8

],

6

5

4

]],

# instance 3

[[

9

0

1

],

3

2

1

]],

# instance 4

])

with

tf

Session

()

as

sess

init

run

()

outputs_val

=

outputs

eval

feed_dict

=

{

X

X_batch

}

但是,這種方法仍然會建立一個每個時間步包含一個單元的圖。 如果有 50 個時間步,這個圖看起來會非常難看。 這有點像寫一個程式而沒有使用迴圈。而且可能會發生記憶體不足錯誤,因為它必須在正向傳遞期間儲存所有張量值,以便可以使用它們在反向傳播期間計算梯度。

下面介紹一種更好的方法

時間上的動態展開

dynamic_rnn()

函式使用

while_loop()

操作,在單元上執行適當的次數,如果要在反向傳播期間將 GPU內 存交換到 CPU 記憶體,可以設定

swap_memory = True

,以避免記憶體不足錯誤。 方便的是,還可以在每個時間步接受所有輸入的單個張量,並且在每個時間步上輸出所有輸出的單個張量。 沒有必要堆疊,拆散或轉置。

import

numpy

as

np

import

tensorflow

as

tf

import

pandas

as

pd

if

__name__

==

‘__main__’

n_steps

=

2

n_inputs

=

3

n_neurons

=

5

X

=

tf

placeholder

tf

float32

None

n_steps

n_inputs

])

basic_cell

=

tf

contrib

rnn

BasicRNNCell

num_units

=

n_neurons

outputs

states

=

tf

nn

dynamic_rnn

basic_cell

X

dtype

=

tf

float32

init

=

tf

global_variables_initializer

()

X_batch

=

np

array

([

[[

0

1

2

],

9

8

7

]],

# instance 1

[[

3

4

5

],

0

0

0

]],

# instance 2

[[

6

7

8

],

6

5

4

]],

# instance 3

[[

9

0

1

],

3

2

1

]],

# instance 4

])

with

tf

Session

()

as

sess

init

run

()

outputs_val

=

outputs

eval

feed_dict

=

{

X

X_batch

})

print

outputs_val

處理變長輸入序列

如果輸入序列具有可變長度(如句子)呢?

這種情況下,你應該在呼叫

dynamic_rnn()

設定

sequence_length

引數,如:

n_steps

=

2

n_inputs

=

3

n_neurons

=

5

reset_graph

()

X

=

tf

placeholder

tf

float32

None

n_steps

n_inputs

])

basic_cell

=

tf

contrib

rnn

BasicRNNCell

num_units

=

n_neurons

seq_length

=

tf

placeholder

tf

int32

None

])

outputs

states

=

tf

nn

dynamic_rnn

basic_cell

X

dtype

=

tf

float32

sequence_length

=

seq_length

X_batch

=

np

array

([

# step 0 step 1

[[

0

1

2

],

9

8

7

]],

# instance 1

[[

3

4

5

],

0

0

0

]],

# instance 2 (padded with zero vectors)

[[

6

7

8

],

6

5

4

]],

# instance 3

[[

9

0

1

],

3

2

1

]],

# instance 4

])

seq_length_batch

=

np

array

([

2

1

2

2

])

with

tf

Session

()

as

sess

init

run

()

outputs_val

states_val

=

sess

run

outputs

states

],

feed_dict

=

{

X

X_batch

seq_length

seq_length_batch

})

處理變長輸出序列

如果輸出序列長度不一樣呢? 如果事先知道長度,可以按照上面所述設定

sequence_length

引數。但是, 不幸的是通常這是不可能的:例如,翻譯後的句子的長度通常與輸入句子的長度不同。

這種情況下,最常見的解決方案是定義一個稱為序列結束標記(EOS 標記)的特殊輸出。 任何在 EOS 後面的輸出應該被忽略(稍後具體討論)。

訓練 RNN

訓練 RNN,訣竅是在時間上展開(如上所示),然後簡單地使用常規反向傳播(見圖 14-5)。 這個策略被稱為時間上的反向傳播(BPTT)。

從零開始的機器學習實戰(十四):RNN

和正常的反向傳播一樣,首先是沿著時間前向傳播(虛線),然後輸出部分的時間步被評估(隱藏部分不會計算)。損失函式的梯度透過展開的網路向後傳播(實線);最後使用在 BPTT 期間計算的梯度來更新模型引數。

和一般神經網路不同的是,梯度在損失函式所使用的所有輸出中反向流動,而不僅僅透過最終輸出(例如圖中,Y(2)-Y(4)都是輸出)

訓練序列分類器

我們訓練一個 RNN 來分類 MNIST 影象。 我們將使用 150 個迴圈神經元的單元,再加上一個全連線層,,然後是一個 softmax 層(見圖 14-6)。其他的程式碼和前面幾章介紹的相同。

從零開始的機器學習實戰(十四):RNN

n_steps

=

28

n_inputs

=

28

n_neurons

=

150

n_outputs

=

10

learning_rate

=

0。001

X

=

tf

placeholder

tf

float32

None

n_steps

n_inputs

])

y

=

tf

placeholder

tf

int32

None

])

basic_cell

=

tf

contrib

rnn

BasicRNNCell

num_units

=

n_neurons

outputs

states

=

tf

nn

dynamic_rnn

basic_cell

X

dtype

=

tf

float32

logits

=

tf

layers

dense

states

n_outputs

xentropy

=

tf

nn

sparse_softmax_cross_entropy_with_logits

labels

=

y

logits

=

logits

loss

=

tf

reduce_mean

xentropy

optimizer

=

tf

train

AdamOptimizer

learning_rate

=

learning_rate

training_op

=

optimizer

minimize

loss

correct

=

tf

nn

in_top_k

logits

y

1

accuracy

=

tf

reduce_mean

tf

cast

correct

tf

float32

))

init

=

tf

global_variables_initializer

()

from

tensorflow。examples。tutorials。mnist

import

input_data

mnist

=

input_data

read_data_sets

“/tmp/data/”

X_test

=

mnist

test

images

reshape

((

-

1

n_steps

n_inputs

))

y_test

=

mnist

test

labels

batch_size

=

150

with

tf

Session

()

as

sess

init

run

()

for

epoch

in

range

n_epochs

):

for

iteration

in

range

mnist

train

num_examples

//

batch_size

):

X_batch

y_batch

=

mnist

train

next_batch

batch_size

X_batch

=

X_batch

reshape

((

-

1

n_steps

n_inputs

))

sess

run

training_op

feed_dict

=

{

X

X_batch

y

y_batch

})

acc_train

=

accuracy

eval

feed_dict

=

{

X

X_batch

y

y_batch

})

acc_test

=

accuracy

eval

feed_dict

=

{

X

X_test

y

y_test

})

print

epoch

“Train accuracy:”

acc_train

“Test accuracy:”

acc_test

從零開始的機器學習實戰(十四):RNN

我們獲得了超過 98% 的準確性 ! 另外,透過調整超引數,使用 He 初始化初始化 RNN 權重,更長時間訓練或新增一些正則化(例如,droupout),你肯定會獲得更好的結果

訓練預測時間序列

現在來關注如何處理一個時間序列,比如,股票走勢,氣溫變化,腦電波的規律等。我們將訓練一個 RNN 來預測生成的時間序列中的下一個值。 每個訓練例項是從時間序列中隨機選取的 20 個連續值的序列,如圖:

從零開始的機器學習實戰(十四):RNN

程式碼與之前幾乎相同

n_steps

=

20

n_inputs

=

1

n_neurons

=

100

n_outputs

=

1

X

=

tf

placeholder

tf

float32

None

n_steps

n_inputs

])

y

=

tf

placeholder

tf

float32

None

n_steps

n_outputs

])

cell

=

tf

contrib

rnn

BasicRNNCell

num_units

=

n_neurons

activation

=

tf

nn

relu

outputs

states

=

tf

nn

dynamic_rnn

cell

X

dtype

=

tf

float32

一般來說,你將不只有一個輸入功能。 例如,預測股票價格時,可能有一些輔助資訊,如分析師的評價等。

每個時間步將會有100個輸出,但是我們只希望給出一個預測值。最簡單的解決方法是將單元包裝在

OutputProjectionWrapper

中。 單元包裝器就像一個普通的單元,但也增加了一些功能。

OutputProjectionWrapper

在每個輸出之上新增一個全連線的線性神經元層,如圖:

從零開始的機器學習實戰(十四):RNN

cell

=

tf

contrib

rnn

OutputProjectionWrapper

tf

contrib

rnn

BasicRNNCell

num_units

=

n_neurons

activation

=

tf

nn

relu

),

output_size

=

n_outputs

outputs

states

=

tf

nn

dynamic_rnn

cell

X

dtype

=

tf

float32

現在我們需要定義損失函式。 我們將使用均方誤差(MSE),建立一個 Adam 最佳化器,訓練操作和變數初始化操作:

learning_rate = 0。001

loss = tf。reduce_mean(tf。square(outputs - y)) # MSE

optimizer = tf。train。AdamOptimizer(learning_rate=learning_rate)

training_op = optimizer。minimize(loss)

init = tf。global_variables_initializer()

saver = tf。train。Saver()

n_iterations = 1500

batch_size = 50

with tf。Session() as sess:

init。run()

for iteration in range(n_iterations):

X_batch, y_batch = next_batch(batch_size, n_steps)

sess。run(training_op, feed_dict={X: X_batch, y: y_batch})

if iteration % 100 == 0:

mse = loss。eval(feed_dict={X: X_batch, y: y_batch})

print(iteration, “\tMSE:”, mse)

saver。save(sess, “。/my_time_series_model”) # not shown in the book

RNN生成新序列

既然我們的模型可以預測未來,就可以生成一些新的序列,正如之前討論的那樣。

提供長度為

n_steps

的種子序列,然後透過模型預測下一時刻的值;把該預測值新增到種子序列的末尾,用最後面長度為

n_steps

的序列做為新的種子序列,做下一次預測,以此類推,如圖:

從零開始的機器學習實戰(十四):RNN

sequence

=

0。

*

n_steps

for

iteration

in

range

300

):

X_batch

=

np

array

sequence

-

n_steps

:]

reshape

1

n_steps

1

y_pred

=

sess

run

outputs

feed_dict

=

{

X

X_batch

}

sequence

append

y_pred

0

-

1

0

])

如果你試圖把約翰·列儂的唱片塞給一個 RNN 模型,看它能不能生成下一張《想象》專輯。

約翰·列儂 有一張專輯《Imagine》(1971),這裡取雙關的意思

深度RNN

一個簡單的想法是,把一層層神經元堆疊起來,如圖:

從零開始的機器學習實戰(十四):RNN

在TF中,可先建立一些神經單元,然後堆疊進

MultiRNNCell

n_neurons

=

100

n_layers

=

3

basic_cell

=

tf

contrib

rnn

BasicRNNCell

num_units

=

n_neurons

multi_layer_cell

=

tf

contrib

rnn

MultiRNNCell

([

basic_cell

*

n_layers

outputs

states

=

tf

nn

dynamic_rnn

multi_layer_cell

X

dtype

=

tf

float32

status

變數包含了每層的一個張量,這個張量就代表了該層神經單元的最終狀態(維度為[batch_size, n_neurons])。如果在建立

MultiRNNCell

時設定了

state_is_tuple=False

,那麼

status

變數就變成了單個張量,它包含了每一層的狀態,其在列的方向上進行了聚合,維度為 [batch_size, n_layers*n_neurons]

在多個 GPU 上分散式部署深度 RNN 網路

如果你嘗試在不同的

device()

塊中建立每個單元格,它將無法工作:

with tf。device(“/gpu:0”): # BAD! This is ignored。

layer1 = tf。contrib。rnn。BasicRNNCell(num_units=n_neurons)

with tf。device(“/gpu:1”): # BAD! Ignored again。

layer2 = tf。contrib。rnn。BasicRNNCell(num_units=n_neurons)

失敗是因為

BasicRNNCell

是一個單元工廠,而不是一個單元本身。建立工廠時不會建立單元格,因此也沒有變數。裝置塊被簡單地忽略了。 單元實際上是後來建立的。

當你呼叫

dynamic_rnn()

時,

dynamic_rnn()

呼叫

MultiRNNCell

,呼叫每個單獨的

BasicRNNCell

BasicRNNCell

建立實際的單元格。

秘訣是建立自己的cell Wrapper,你需要一個

DeviceCellWrapper

,這個包裝器只是代理每個方法呼叫到另一個單元格,除了它在裝置塊中包裝

__call __()

函式 。 現在,你可以在不同的GPU上分發每個層:

import tensorflow as tf

class DeviceCellWrapper(tf。contrib。rnn。RNNCell):

def __init__(self, device, cell):

self。_cell = cell

self。_device = device

@property

def state_size(self):

return self。_cell。state_size

@property

def output_size(self):

return self。_cell。output_size

def __call__(self, inputs, state, scope=None):

with tf。device(self。_device):

return self。_cell(inputs, state, scope)

n_inputs = 5

n_steps = 20

n_neurons = 100

X = tf。placeholder(tf。float32, shape=[None, n_steps, n_inputs])

devices = [“/cpu:0”, “/cpu:0”, “/cpu:0”]

# replace with [“/gpu:0”, “/gpu:1”, “/gpu:2”] if you have 3 GPUs

cells = [DeviceCellWrapper(dev,tf。contrib。rnn。BasicRNNCell(num_units=n_neurons))

for dev in devices]

multi_layer_cell = tf。contrib。rnn。MultiRNNCell(cells)

outputs, states = tf。nn。dynamic_rnn(multi_layer_cell, X, dtype=tf。float32)

或者,從TensorFlow 1。1開始,你可以使用

tf。contrib。rnn。DeviceWrapper

類(自TF 1。2以來別名

tf。nn。rnn_cell。DeviceWrapper

)。

應用Dropout

對於深層深度 RNN,在訓練集上很容易過擬合。Dropout 是防止過擬合的常用技術。可以簡單的在 RNN 層之前或之後新增一層 Dropout 層,但如果需要在 RNN 層之間應用 Dropout 技術就需要

DropoutWrapper

但是問題在於,Dropout 不管是在訓練還是測試時都起作用了,而我們想要的僅僅是在訓練時應用 Dropout。很不幸的是

DropoutWrapper

不支援

is_training

這樣一個設定選項。因此必須自己寫 Dropout 包裝類,或者建立兩個計算圖,一個用來訓練,一個用來測試。後者可透過如下面程式碼這樣實現:

import sys

is_training = (sys。argv[-1] == “train”)

X = tf。placeholder(tf。float32, [None, n_steps, n_inputs])

y = tf。placeholder(tf。float32, [None, n_steps, n_outputs])

cell = tf。contrib。rnn。BasicRNNCell(num_units=n_neurons)

if is_training:

cell = tf。contrib。rnn。DropoutWrapper(cell, input_keep_prob=keep_prob)

multi_layer_cell = tf。contrib。rnn。MultiRNNCell([cell]*n_layers)

rnn_outpus, status = tf。nn。dynamic_rnn(multi_layer_cell, X, dtype=tf。float32)

[。。。] # bulid the rest of the graph

init = tf。global_variables_initializer()

saver = tf。train。Saver()

with tf。Session() as sess:

if is_training:

init。run()

for iteration in range(n_iterations):

[。。。] # train the model

save_path = saver。save(sess, “/tmp/my_model。ckpt”)

else:

saver。restore(sess, “/tmp/my_model。ckpt”)

[。。。] # use the model

長期訓練的困難

要處理很長的序列,你的RNN要經過許多時間步,就像普通DNN一樣,可能會遭受梯度爆炸/消失的問題,同樣許多解決這些問題的策略也適用於RNN:好的引數初始化方式,非飽和的啟用函式(如 ReLU),批次規範化, 梯度截斷,更快的最佳化器。但即便如此, RNN 在處理適中的長序列(如 100 輸入序列)也在訓練時表現的很慢。

最簡單和常見的方法是僅僅展開限定時間步長的 RNN 網路,可以透過截斷輸入序列來簡單實現這種功能。如減小

n_steps

來實現截斷。當然這種方法會限制模型在長期模式的學習能力。一種折衷方案是包含舊資料和新資料,從而使模型獲得兩者資訊,但是也很難保證從細分類中獲取的資料有效性,這種方案存在著明顯的不足

第二個問題是記憶會在長時間執行的 RNN 網路中逐漸淡去。比如說,你想要分析長篇幅的影評的情感,以

“I love this movie”

開篇,並輔以各種缺點的建議。但如果 RNN 網路逐漸忘記了開頭的幾個詞,RNN 網路的判斷完全有可能完全相反。

為了解決這個問題,各種能夠攜帶長時記憶的神經單元的變體被提出。這些變體是有效的,往往基本形式RNN不怎麼被使用了。

LSTM單元

長短時記憶單元

(LSTM)在 1997 年Sepp Hochreiter和JürgenSchmidhuber於1997年提出,多年來由Alex Graves,HaşimSak,Wojciech Zaremba等研究人員逐漸改進。如果把 LSTM 單元看作一個黑盒,他與基本的RNN單元很相似,但 LSTM 單元會比基本單元效能更好,收斂更快,能夠感知資料的長時依賴。TensorFlow 中透過

BasicLSTMCell

實現 LSTM 單元。

lstm_cell = tf。contrib。rnn。BasicLSTMCell(num_units=n_neurons)

LSTM 單元的工作機制如圖:

從零開始的機器學習實戰(十四):RNN

LSTM和普通單元的不同在於,狀態分為

h_t,c_t

兩個向量,h表示短期記憶,c表示長期記憶

長期記憶

c_{(t-1)}

從左向右傳播,依次經過遺忘門遺忘一些記憶,然後經過輸入門加上一些新的記憶。新的長期記憶

c_t

直接輸出,不再經過任何轉化。另一方面,長期記憶經過tanh變化再透過輸入門,得到短期記憶

h_t

,和這時的輸出

y_t

輸入

x_t

和前一時間的短期記憶

h_{(t-1)}

輸入到了四個不同的門中:

最重要的是輸出是

g_t

,它分析輸入和上一時間的短期記憶,和基本RNN 一樣,直接輸出到了y和h,不同點在於LSTM 單元會將一部分g儲存在長期記憶

其它三個為

門控制器

。採用 Logistic 作為啟用函式,當輸入為 0 時門關閉,輸出為 1 時門開啟。分別為:

遺忘門

由 f 控制,決定哪些長期記憶需要被擦除;

輸入門

由 i 控制,決定處理哪部分應該被新增到長期記憶中。

輸出門

由 o 控制,決定從長時狀態中讀取哪些記憶到這時的輸出中。

簡要來說,LSTM 單元能夠學習到識別重要輸入(輸入門作用),儲存進長時狀態,並儲存必要的時間(遺忘門功能),並學會提取當前輸出所需要的記憶。這也解釋了 LSTM 單元能夠在提取長時序列,長文字,錄音等資料中的長期模式的驚人成功的原因。

從零開始的機器學習實戰(十四):RNN

窺孔連線

基本形式的 LSTM 單元中,門的控制僅有當前的輸入x和前一時刻的短期記憶

h_{t-1}

。有一種思路是讓各個控制門窺視一下長時狀態,獲取一些上下文資訊。該想法由 Felix Gers和Jürgen Schmidhuber於2000年提出,這種LSTM 的變體擁有叫做窺孔連線的額外連線:把前一時刻的長時狀態 加入遺忘門和輸入門控制的輸入,當前時刻的長時狀態加入輸出門的控制輸入。

TensorFlow 中由

LSTMCell

設定

use_peepholes=True

可以實現這種。

lstm_cell = tf。contrib。rnn。LSTMCell(num_units=n_neurons, use_peepholes=True)

GRU單元

GRU由Kyunghyun Cho等人在2014年的論文中提出。GRU單元是LSTM單元的簡化版本,它似乎表現得同樣好(這解釋了它越來越受歡迎)。

從零開始的機器學習實戰(十四):RNN

GRU的主要簡化是:

長期記憶c和短期記憶h狀態向量合併為單個向量h

單個門控制器控制遺忘門門和輸入門。如果門控制器輸出1,則輸入門開啟,遺忘門關閉。反之亦然。

沒有輸出門,狀態就是輸出。與此同時,增加了一個控制門 r 來控制哪部分前一時間步的狀態在該時刻的單元內呈現。

從零開始的機器學習實戰(十四):RNN

在 TensoFlow 中建立 GRU 單元很簡單:

gru_cell = tf。contrib。rnn。GRUCell(n_units=n_neurons)

自然語言處理中的應用

詞嵌入

表示一個單詞,一種選擇是one-hot向量。假設你的詞彙表包含 5 萬個單詞,那麼第n個單詞將被表示為 50,000 維的向量,除了第n個位置為 1 之外,其它全部為 0。 然而,你希望相似的單詞具有相似的表示形式,這樣學習到的內容可以推廣到相似的單詞。

最常見的解決方案是,用一個相當小且密集的向量(例如 150 維)表示詞彙表中的每個單詞,稱為嵌入,並讓神經網路在訓練過程中,為每個單詞學習一個良好的嵌入。 在訓練開始時,嵌入只是隨機選擇的,但在訓練過程中,反向傳播會自動更新嵌入,來幫助神經網路執行任務。 通常這意味著,相似的詞會逐漸彼此靠近。

你應該首先對句子進行預處理並將其分解成已知單詞的列表。 例如,你刪除不必要的字元,替換未知單詞,替換數字值等。應用embedding_lookup()函式來獲取相應的嵌入:

vocabulary_size

=

50000

embedding_size

=

150

embeddings

=

tf

Variable

tf

random_uniform

([

vocabulary_size

embedding_size

],

-

1。0

1。0

))

train_inputs

=

tf

placeholder

tf

int32

shape

=

None

])

# from ids。。。

embed

=

tf

nn

embedding_lookup

embeddings

train_inputs

# 。。。to embeddings

一旦你的模型習得了良好的詞嵌入,它們實際上可以在任何 NLP 應用中高效複用:milk和water總是相近的,不論什麼情況,都不可能和shoes相近。 實際上,你可能需要下載預訓練的單詞嵌入,而不是訓練自己的單詞嵌入。 就像複用預訓練一樣,你可以選擇凍結預訓練嵌入(例如,使用

trainable=False

建立嵌入變數),或者讓反向傳播為你的應用調整它們。 第一種選擇將加速訓練,但第二種選擇可能會產生稍高的效能。

對於表示可能擁有大量不同值的類別屬性,嵌入也很有用,特別是當值之間存在複雜的相似性的時候。 例如,考慮職業,愛好,菜品,物種,品牌等。

用於機器翻譯的編解碼器網路

從零開始的機器學習實戰(十四):RNN

如圖,顯示了一個英語-法語翻譯,英語句子被送進編碼器,解碼器輸出法語翻譯。 注意,法語翻譯也被用作解碼器的輸入,不過向後推了一個時刻(第一個時刻的輸入是,第二個時刻的輸入才是Je)。 換句話說,解碼器的輸入是它應該在前一步輸出的字(不管它實際輸出的是什麼)。

注意,英語句子在送入編碼器之前會反轉。 例如,

“I drink milk”

“milk drink I”

相反。這確保了英語句子的開頭將會最後送到編碼器,這很有用,因為這通常是解碼器需要翻譯的第一個東西。

每個單詞最初由簡單整數識別符號表示, 接下來,嵌入查詢返回詞的嵌入。 這些詞的嵌入是實際送到編碼器和解碼器的內容。然後,解碼器輸出輸出詞彙表(法語)中每個詞的得分, 機率最高的詞會輸出。

從零開始的機器學習實戰(十四):RNN

注意,在預測時期,你不再將目標句子送入解碼器。 相反,只需向解碼器提供它在上一步輸出的單詞,如圖所示(這將需要嵌入查詢,它未在圖中顯示)。

上面大概介紹了Encoder-Decoder過程,但如果檢視

rnn/translate/seq2seq_model。py

中的程式碼(在 TensorFlow 模型中),你會注意到一些重要的區別:

首先,我們已經假定所有輸入序列具有定長。但顯然可能會有所不同。可以採用類似前面介紹的

sequence_length

引數。然而,該程式碼中使用了另一種方法(大概是出於效能原因):句子分到長度相似的桶中,並且使用特殊的填充標記(例如

)來填充較短的句子。例如,

“I drink milk”

變成

milk drink I”

,翻譯成

“Je bois du lait

。然後忽略任何 EOS 標記之後的輸出。

其次,當輸出詞彙表很大時,softmax過程將變得很慢,實際採用了sampled softmax 技術(Sébastien Jean 2015 )在 TensorFlow 中,你可以使用

sampled_softmax_loss()

函式。

第三,該程式碼使用了一種注意力機制,你可以閱讀相關文獻來了解

最後,該程式碼的實現使用了

tf。nn。legacy_seq2seq

模組,提供了輕鬆構建各種編解碼器模型的工具。

標簽: tf  RNN  batch  Cell