作者:Faizan Shaikh,翻譯:李文婧,轉(zhuǎn)自:數(shù)據(jù)派(ID:datapi)
引言
人類不會每聽到一個句子就對語言進行重新理解。看到一篇文章時,我們會根據(jù)之前對這些詞的理解來了解背景。我們將其定義為記憶力。
算法可以復(fù)制這種模式嗎?神經(jīng)網(wǎng)絡(luò)(NN)是最先被想到的技術(shù)。但令人遺憾的是傳統(tǒng)的神經(jīng)網(wǎng)絡(luò)還無法做到這一點。 舉個例子,如果讓傳統(tǒng)的神經(jīng)網(wǎng)絡(luò)預(yù)測一個視頻中接下來會發(fā)生什么,它很難有精確的結(jié)果。
這就是循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)發(fā)揮作用的地方。循環(huán)神經(jīng)網(wǎng)絡(luò)在深度學(xué)習(xí)領(lǐng)域非常熱門,因此,學(xué)習(xí)循環(huán)神經(jīng)網(wǎng)絡(luò)勢在必行。循環(huán)神經(jīng)網(wǎng)絡(luò)在現(xiàn)實生活中的一些實際應(yīng)用:
- 語音識別
- 機器翻譯
- 音樂創(chuàng)作
- 手寫識別
- 語法學(xué)習(xí)
在這篇文章中,我們首先對一個典型的循環(huán)神經(jīng)網(wǎng)絡(luò)模型的核心部分進行快速瀏覽。然后我們將設(shè)置問題陳述,最后我們將從零開始用Python構(gòu)建一個循環(huán)神經(jīng)網(wǎng)絡(luò)模型解決這些問題陳述。
我們總是習(xí)慣用高級Python庫編寫循環(huán)神經(jīng)網(wǎng)絡(luò)。那為什么還要從零開始編碼呢? 我堅信從頭學(xué)習(xí)是學(xué)習(xí)和真正理解一個概念的最佳方式。這就是我將在本教程中展示的內(nèi)容。
本文假設(shè)讀者已對循環(huán)神經(jīng)網(wǎng)絡(luò)有基本的了解。
一、快速回顧:循環(huán)神經(jīng)網(wǎng)絡(luò)概念回顧
讓我們快速回顧一下循環(huán)神經(jīng)網(wǎng)絡(luò)的核心概念。我們將以一家公司的股票的序列數(shù)據(jù)為例。一個簡單的機器學(xué)習(xí)模型或人工神經(jīng)網(wǎng)絡(luò)可以根據(jù)一些特征預(yù)測股票價格,比如股票的數(shù)量,開盤價值等。除此之外,該股票在之前的幾天和幾個星期的表現(xiàn)也影響著股票價格。對交易者來說,這些歷史數(shù)據(jù)實際上是進行預(yù)判的主要決定因素。
在傳統(tǒng)的前饋神經(jīng)網(wǎng)絡(luò)中,所有測試用例都被認為是獨立的。 在預(yù)測股價時,你能看出那不是一個合適的選擇嗎? 神經(jīng)網(wǎng)絡(luò)模型不會考慮之前的股票價格 – 這不是一個好想法!
面對時間敏感數(shù)據(jù)時,我們可以利用另一個概念 — 循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)!
典型的循環(huán)神經(jīng)網(wǎng)絡(luò)如下所示:
這剛開始看起來可能很嚇人。 但是如果我們展開來講,事情就開始變得更簡單:
現(xiàn)在,我們更容易想象出這些循環(huán)神經(jīng)網(wǎng)絡(luò)如何預(yù)測股票價格的走勢。這有助于我們預(yù)測當(dāng)天的價格。這里,有關(guān)時間t(h_t)的每個預(yù)測都需要依賴先前所有的預(yù)測和從它們那學(xué)習(xí)到的信息。相當(dāng)直截了當(dāng)吧?
循環(huán)神經(jīng)網(wǎng)絡(luò)可以在很大程度上幫助我們解決序列處理問題。
文本是序列數(shù)據(jù)的另一個好例子。一旦給定文本之后,循環(huán)神經(jīng)網(wǎng)絡(luò)就可以預(yù)測出接下來將會出現(xiàn)的單詞或短語,這可將是非常有用的資產(chǎn)。我們希望我們的循環(huán)神經(jīng)網(wǎng)絡(luò)可以寫出莎士比亞的十四行詩!
現(xiàn)在,循環(huán)神經(jīng)網(wǎng)絡(luò)在涉及短或小的環(huán)境時非常棒。 但是為了能夠構(gòu)建一個故事并記住它,我們的循環(huán)神經(jīng)網(wǎng)絡(luò)模型應(yīng)該能理解序列背后的背景,就像人腦一樣。
二、使用循環(huán)神經(jīng)網(wǎng)絡(luò)進行序列預(yù)測
在本文中,我們將使用循環(huán)神經(jīng)網(wǎng)絡(luò)處理序列預(yù)測問題。對此最簡單的例子之一是正弦波預(yù)測。序列包含可見趨勢,使用啟發(fā)式方式很容易解決。下面就是正弦波的樣子:
我們首先從零開始設(shè)計一個循環(huán)神經(jīng)網(wǎng)絡(luò)解決這個問題。 我們的循環(huán)神經(jīng)網(wǎng)絡(luò)模型也應(yīng)該得到很好地推廣,以便我們可以將其應(yīng)用于其他序列問題。 我們將像這樣制定我們的問題:給定一個屬于正弦波的50個數(shù)字的序列,預(yù)測系列中的第51個數(shù)字。 是時候打開你的Jupyter notebook(一個交互式筆記本,支持運行 40 多種編程語言)或你選擇的IDE(Integrated Development Environment,是一種編程軟件)!
三、使用Python編碼循環(huán)神經(jīng)網(wǎng)絡(luò)
第0步:數(shù)據(jù)準備
在做任何其他事情之前,數(shù)據(jù)準備是任何數(shù)據(jù)科學(xué)項目中不可避免的第一步。我們的網(wǎng)絡(luò)模型期望數(shù)據(jù)是什么樣的? 它將輸入長度為50的單個序列。所以輸入數(shù)據(jù)的形狀將是:
(number_of_records x length_of_sequence x types_of_sequences)
這里,types_of_sequence是1,因為我們只有一種類型的序列—正弦波。
另一方面,每次記錄的輸出只有一個值。那就是輸入序列中的第51個值。 所以它的形狀將是:
(number_of_records x types_of_sequences) #where types_of_sequences is 1
讓我們深入研究這個代碼。首先,導(dǎo)入必要的庫:
%pylab inline
import math
創(chuàng)建像數(shù)據(jù)一樣的正弦波,我們將使用Python數(shù)學(xué)庫中的正弦函數(shù):
sin_wave = np.array([math.sin(x) for x in np.arange(200)])
將剛剛生成的正弦波可視化:
plt.plot(sin_wave[:50])
我們現(xiàn)在將在下面的代碼塊中創(chuàng)建數(shù)據(jù):
X = []
Y = []
seq_len = 50
num_records = len(sin_wave) - seq_len
for i in range(num_records - 50):
X.append(sin_wave[i:i+seq_len])
Y.append(sin_wave[i+seq_len])
X = np.array(X)
X = np.expand_dims(X, axis=2)
Y = np.array(Y)
Y = np.expand_dims(Y, axis=1)
打印數(shù)據(jù)的形狀:
X.shape, Y.shape
((100, 50, 1), (100, 1))
請注意,我們循環(huán)(num_records - 50),是因為我們想要留出50條記錄作為驗證數(shù)據(jù)。現(xiàn)在我們可以創(chuàng)建這個驗證數(shù)據(jù):
X_val = []
Y_val = []
for i in range(num_records - 50, num_records):
X_val.append(sin_wave[i:i+seq_len])
Y_val.append(sin_wave[i+seq_len])
X_val = np.array(X_val)
X_val = np.expand_dims(X_val, axis=2)
Y_val = np.array(Y_val)
Y_val = np.expand_dims(Y_val, axis=1)
第1步:為我們的循環(huán)神經(jīng)網(wǎng)絡(luò)模型創(chuàng)建架構(gòu)
我們接來下的任務(wù)是將我們在循環(huán)神經(jīng)網(wǎng)絡(luò)模型中使用的所有必要變量和函數(shù)進行定義。我們的循環(huán)神經(jīng)網(wǎng)絡(luò)模型將接受輸入序列,通過100個單位的隱藏層處理它,并產(chǎn)生單值輸出:
learning_rate = 0.0001
nepoch = 25
T = 50 # length of sequence
hidden_dim = 100
output_dim = 1
bptt_truncate = 5
min_clip_value = -10
max_clip_value = 10
然后我們將定義網(wǎng)絡(luò)的權(quán)重:
U = np.random.uniform(0, 1, (hidden_dim, T))
W = np.random.uniform(0, 1, (hidden_dim, hidden_dim))
V = np.random.uniform(0, 1, (output_dim, hidden_dim))
其中:
U是輸入和隱藏圖層之間權(quán)重的權(quán)重矩陣
V是隱藏層和輸出層之間權(quán)重的權(quán)重矩陣
W是循環(huán)神經(jīng)網(wǎng)絡(luò)層(隱藏層)中共享權(quán)重的權(quán)重矩陣
最后,我們將定義在隱藏層中使用S型函數(shù):
def sigmoid(x):
return 1 / (1 + np.exp(-x))
第2步:訓(xùn)練模型
既然我們已經(jīng)定義了模型,最后我們就可以繼續(xù)訓(xùn)練我們的序列數(shù)據(jù)了。我們可以將訓(xùn)練過程細分為更小的步驟,即:
步驟2.1:檢查訓(xùn)練數(shù)據(jù)是否丟失
步驟2.1.1:前饋傳遞
步驟2.1.2:計算誤差
步驟2.2:檢查驗證數(shù)據(jù)是否丟失
步驟2.2.1前饋傳遞
步驟2.2.2:計算誤差
步驟2.3:開始實際訓(xùn)練
步驟2.3.1:正推法
步驟2.3.2:反向傳遞誤差
步驟2.3.3:更新權(quán)重
我們需要重復(fù)這些步驟直到數(shù)據(jù)收斂。 如果模型開始過擬合,請停止! 或者只是預(yù)先定義epoch的數(shù)量。
步驟2.1:檢查訓(xùn)練數(shù)據(jù)是否丟失
我們將通過我們的循環(huán)神經(jīng)網(wǎng)絡(luò)模型進行正推法,并計算所有記錄的預(yù)測的平方誤差,以獲得損失值。
for epoch in range(nepoch):
# check loss on train
loss = 0.0
# do a forward pass to get prediction
for i in range(Y.shape[0]):
x, y = X[i], Y[i] # get input, output values of each record
prev_s = np.zeros((hidden_dim, 1)) # here, prev-s is the value of the previous activation of hidden layer; which is initialized as all zeroes
for t in range(T):
new_input = np.zeros(x.shape) # we then do a forward pass for every timestep in the sequence
new_input[t] = x[t] # for this, we define a single input for that timestep
mulu = np.dot(U, new_input)
mulw = np.dot(W, prev_s)
add = mulw + mulu
s = sigmoid(add)
mulv = np.dot(V, s)
prev_s = s
# calculate error
loss_per_record = (y - mulv)**2 / 2
loss += loss_per_record
loss = loss / float(y.shape[0])
步驟2.2:檢查驗證數(shù)據(jù)是否丟失
我們將對計算驗證數(shù)據(jù)的損失做同樣的事情(在同一循環(huán)中):
# check loss on val
val_loss = 0.0
for i in range(Y_val.shape[0]):
x, y = X_val[i], Y_val[i]
prev_s = np.zeros((hidden_dim, 1))
for t in range(T):
new_input = np.zeros(x.shape)
new_input[t] = x[t]
mulu = np.dot(U, new_input)
mulw = np.dot(W, prev_s)
add = mulw + mulu
s = sigmoid(add)
mulv = np.dot(V, s)
prev_s = s
loss_per_record = (y - mulv)**2 / 2
val_loss += loss_per_record
val_loss = val_loss / float(y.shape[0])
print('Epoch: ', epoch + 1, ', Loss: ', loss, ', Val Loss: ', val_loss)
你應(yīng)該會得到以下輸出:
Epoch: 1 , Loss: [[101185.61756671]] , Val Loss: [[50591.0340148]]
...
...
步驟2.3:開始實際訓(xùn)練
現(xiàn)在我們開始對網(wǎng)絡(luò)進行實際訓(xùn)練。在這里,我們首先進行正推法計算誤差,然后使用逆推法來計算梯度并更新它們。讓我逐步向您展示這些內(nèi)容,以便您可以直觀地了解它的工作原理。
步驟2.3.1:正推法
正推法步驟如下:
1. 我們首先將輸入與輸入和隱藏層之間的權(quán)重相乘;
2. 在循環(huán)神經(jīng)網(wǎng)絡(luò)層中添加權(quán)重乘以此項,這是因為我們希望獲取前一個時間步的內(nèi)容;
3. 通過sigmoid 激活函數(shù)將其與隱藏層和輸出層之間的權(quán)重相乘;
4. 在輸出層,我們對數(shù)值進行線性激活,因此我們不會通過激活層傳遞數(shù)值;
5. 在字典中保存當(dāng)前圖層的狀態(tài)以及上一個時間步的狀態(tài)。
這是執(zhí)行正推法的代碼(請注意,它是上述循環(huán)的繼續(xù)):
# train model
for i in range(Y.shape[0]):
x, y = X[i], Y[i]
layers = []
prev_s = np.zeros((hidden_dim, 1))
dU = np.zeros(U.shape)
dV = np.zeros(V.shape)
dW = np.zeros(W.shape)
dU_t = np.zeros(U.shape)
dV_t = np.zeros(V.shape)
dW_t = np.zeros(W.shape)
dU_i = np.zeros(U.shape)
dW_i = np.zeros(W.shape)
# forward pass
for t in range(T):
new_input = np.zeros(x.shape)
new_input[t] = x[t]
mulu = np.dot(U, new_input)
mulw = np.dot(W, prev_s)
add = mulw + mulu
s = sigmoid(add)
mulv = np.dot(V, s)
layers.append({'s':s, 'prev_s':prev_s})
prev_s = s
步驟2.3.2:反向傳播誤差
在前向傳播步驟之后,我們計算每一層的梯度,并反向傳播誤差。 我們將使用截斷反向傳播時間(TBPTT),而不是vanilla backprop(反向傳播的非直觀效應(yīng)的一個例子)。這可能聽起來很復(fù)雜但實際上非常直接。
BPTT與backprop的核心差異在于,循環(huán)神經(jīng)網(wǎng)絡(luò)層中的所有時間步驟,都進行了反向傳播步驟。 因此,如果我們的序列長度為50,我們將反向傳播當(dāng)前時間步之前的所有時間步長。
如果你猜對了,那么BPTT在計算上看起來非常昂貴。 因此,我們不是反向傳播所有先前的時間步,而是反向傳播直到x時間步以節(jié)省計算能力??紤]這在概念上類似于隨機梯度下降,我們包括一批數(shù)據(jù)點而不是所有數(shù)據(jù)點。
以下是反向傳播誤差的代碼:
# derivative of pred
dmulv = (mulv - y)
# backward pass
for t in range(T):
dV_t = np.dot(dmulv, np.transpose(layers[t]['s']))
dsv = np.dot(np.transpose(V), dmulv)
ds = dsv
dadd = add * (1 - add) * ds
dmulw = dadd * np.ones_like(mulw)
dprev_s = np.dot(np.transpose(W), dmulw)
for i in range(t-1, max(-1, t-bptt_truncate-1), -1):
ds = dsv + dprev_s
dadd = add * (1 - add) * ds
dmulw = dadd * np.ones_like(mulw)
dmulu = dadd * np.ones_like(mulu)
dW_i = np.dot(W, layers[t]['prev_s'])
dprev_s = np.dot(np.transpose(W), dmulw)
new_input = np.zeros(x.shape)
new_input[t] = x[t]
dU_i = np.dot(U, new_input)
dx = np.dot(np.transpose(U), dmulu)
dU_t += dU_i
dW_t += dW_i
dV += dV_t
dU += dU_t
dW += dW_t
步驟2.3.3:更新權(quán)重
最后,我們使用計算的權(quán)重梯度更新權(quán)重。 有一件事我們必須記住,如果不對它們進行檢查,梯度往往會爆炸。這是訓(xùn)練神經(jīng)網(wǎng)絡(luò)的一個基本問題,稱為梯度爆炸問題。 所以我們必須將它們夾在一個范圍內(nèi),這樣它們就不會增長得太快。 我們可以這樣做:
if dU.max() > max_clip_value:
dU[dU > max_clip_value] = max_clip_value
if dV.max() > max_clip_value:
dV[dV > max_clip_value] = max_clip_value
if dW.max() > max_clip_value:
dW[dW > max_clip_value] = max_clip_value
if dU.min()
dU[dU
if dV.min()
dV[dV
if dW.min()
dW[dW
# update
U -= learning_rate * dU
V -= learning_rate * dV
W -= learning_rate * dW
在訓(xùn)練上述模型時,我們得到了這個輸出:
Epoch: 1 , Loss: [[101185.61756671]] , Val Loss: [[50591.0340148]]
Epoch: 2 , Loss: [[61205.46869629]] , Val Loss: [[30601.34535365]]
Epoch: 3 , Loss: [[31225.3198258]] , Val Loss: [[15611.65669247]]
Epoch: 4 , Loss: [[11245.17049551]] , Val Loss: [[5621.96780111]]
Epoch: 5 , Loss: [[1264.5157739]] , Val Loss: [[632.02563908]]
Epoch: 6 , Loss: [[20.15654115]] , Val Loss: [[10.05477285]]
Epoch: 7 , Loss: [[17.13622839]] , Val Loss: [[8.55190426]]
Epoch: 8 , Loss: [[17.38870495]] , Val Loss: [[8.68196484]]
Epoch: 9 , Loss: [[17.181681]] , Val Loss: [[8.57837827]]
Epoch: 10 , Loss: [[17.31275313]] , Val Loss: [[8.64199652]]
Epoch: 11 , Loss: [[17.12960034]] , Val Loss: [[8.54768294]]
Epoch: 12 , Loss: [[17.09020065]] , Val Loss: [[8.52993502]]
Epoch: 13 , Loss: [[17.17370113]] , Val Loss: [[8.57517454]]
Epoch: 14 , Loss: [[17.04906914]] , Val Loss: [[8.50658127]]
Epoch: 15 , Loss: [[16.96420184]] , Val Loss: [[8.46794248]]
Epoch: 16 , Loss: [[17.017519]] , Val Loss: [[8.49241316]]
Epoch: 17 , Loss: [[16.94199493]] , Val Loss: [[8.45748739]]
Epoch: 18 , Loss: [[16.99796892]] , Val Loss: [[8.48242177]]
Epoch: 19 , Loss: [[17.24817035]] , Val Loss: [[8.6126231]]
Epoch: 20 , Loss: [[17.00844599]] , Val Loss: [[8.48682234]]
Epoch: 21 , Loss: [[17.03943262]] , Val Loss: [[8.50437328]]
Epoch: 22 , Loss: [[17.01417255]] , Val Loss: [[8.49409597]]
Epoch: 23 , Loss: [[17.20918888]] , Val Loss: [[8.5854792]]
Epoch: 24 , Loss: [[16.92068017]] , Val Loss: [[8.44794633]]
Epoch: 25 , Loss: [[16.76856238]] , Val Loss: [[8.37295808]]
看起來不錯!是時候進行預(yù)測并繪制它們以獲得我們設(shè)計的視覺感受。
第3步:獲得預(yù)測
我們將通過訓(xùn)練的權(quán)重利用正推法獲得預(yù)測:
preds = []
for i in range(Y.shape[0]):
x, y = X[i], Y[i]
prev_s = np.zeros((hidden_dim, 1))
# Forward pass
for t in range(T):
mulu = np.dot(U, x)
mulw = np.dot(W, prev_s)
add = mulw + mulu
s = sigmoid(add)
mulv = np.dot(V, s)
prev_s = s
preds.append(mulv)
preds = np.array(preds)
將這些預(yù)測與實際值一起繪制:
plt.plot(preds[:, 0, 0], 'g')
plt.plot(Y[:, 0], 'r')
plt.show()
這是有關(guān)培訓(xùn)數(shù)據(jù)的。 我們怎么知道我們的模型是不是過擬合? 這就是我們之前創(chuàng)建的驗證集發(fā)揮作用的時候:
preds = []
for i in range(Y_val.shape[0]):
x, y = X_val[i], Y_val[i]
prev_s = np.zeros((hidden_dim, 1))
# For each time step...
for t in range(T):
mulu = np.dot(U, x)
mulw = np.dot(W, prev_s)
add = mulw + mulu
s = sigmoid(add)
mulv = np.dot(V, s)
prev_s = s
preds.append(mulv)
preds = np.array(preds)
plt.plot(preds[:, 0, 0], 'g')
plt.plot(Y_val[:, 0], 'r')
plt.show()
不錯。 預(yù)測看起來令人印象深刻。 驗證數(shù)據(jù)的均方根誤差分數(shù)也是可以接受的:
from sklearn.metrics import mean_squared_error
math.sqrt(mean_squared_error(Y_val[:, 0] * max_val, preds[:, 0, 0] * max_val))
0.127191931509431
總結(jié)
在處理序列數(shù)據(jù)時,我沒有足夠強調(diào)循環(huán)神經(jīng)網(wǎng)絡(luò)多么有用。 我懇請大家學(xué)習(xí)并將其應(yīng)用于數(shù)據(jù)集。 嘗試去解決NLP問題,看看是否可以找到解決方案。
在本文中,我們學(xué)習(xí)了如何使用numpy庫從零開始創(chuàng)建循環(huán)神經(jīng)網(wǎng)絡(luò)模型。 您也可以使用像Keras或Caffe這樣的高級庫,但了解您正在實施的概念至關(guān)重要。
-
神經(jīng)網(wǎng)絡(luò)
+關(guān)注
關(guān)注
42文章
4771瀏覽量
100778 -
深度學(xué)習(xí)
+關(guān)注
關(guān)注
73文章
5503瀏覽量
121175
發(fā)布評論請先 登錄
相關(guān)推薦
評論