內(nèi)容參考以及代碼整理自“深度學(xué)習(xí)四大名“著之一《Python深度學(xué)習(xí)》
查看完整代碼,請(qǐng)看: https://github.com/ubwshook/MachineLearning
密集連接網(wǎng)絡(luò)和卷積神經(jīng)網(wǎng)絡(luò)都有一個(gè)主要特點(diǎn),那就是它們都沒有記憶。它們單獨(dú)處理每個(gè)輸入,在輸入與輸入之間沒有保存任何狀態(tài),對(duì)于這樣的網(wǎng)絡(luò),要想處理數(shù)據(jù)點(diǎn)的序列或時(shí)間序列,你需要向網(wǎng)絡(luò)同時(shí)展示整個(gè)序列,即將序列轉(zhuǎn)換成單個(gè)數(shù)據(jù)點(diǎn)。
與此相反,當(dāng)你在閱讀的時(shí)候,你是一個(gè)詞一個(gè)詞地閱讀,同時(shí)會(huì)記住之前的內(nèi)容。這讓你能夠動(dòng)態(tài)理解這個(gè)句子所傳達(dá)的含義。生物智能以漸進(jìn)的方式處理信息,同時(shí)保存一個(gè)關(guān)于所處理內(nèi)容的內(nèi)部模型,這個(gè)模型是根據(jù)過去的信息構(gòu)建的,并隨著新信息的進(jìn)去而不斷更新。
循環(huán)神經(jīng)網(wǎng)絡(luò)的原理與此相同: 它處理序列的方式,遍歷所有序列元素,并保存一個(gè)狀態(tài)(state), 起重工包含與已查看內(nèi)容相關(guān)的信息。實(shí)際上,RNN是一類具有內(nèi)部環(huán)的神經(jīng)網(wǎng)絡(luò)。在處理兩個(gè)不同的獨(dú)立序列之間,RNN狀態(tài)會(huì)被重置,你仍可以將一個(gè)序列看作單個(gè)數(shù)據(jù)點(diǎn),即網(wǎng)絡(luò)的單個(gè)輸入。真正改變的是,數(shù)據(jù)點(diǎn)不再是在單個(gè)步驟中進(jìn)行處理,相反,網(wǎng)絡(luò)內(nèi)部會(huì)對(duì)序列元素進(jìn)行遍歷。

我們用Numpy來實(shí)現(xiàn)一個(gè)簡單RNN的前向傳遞。這個(gè)RNN的輸入是一個(gè)張量序列,我們將其編碼為大小為(timesteps, input_features)的二維張量。
"""
純python實(shí)現(xiàn)一個(gè)RNN的原理
"""
timesteps = 100 # 輸入序列的時(shí)間步
input_features = 32 # 輸入特征空間維度
output_features = 64 # 輸出特征空間維度
inputs = np.random.random((timesteps, input_features)) # 輸入數(shù)據(jù),此處僅為示意
state_t = np.zeros((output_features,)) # 出事狀態(tài)為0
# 創(chuàng)建隨機(jī)的權(quán)重矩陣
W = np.random.random((output_features, input_features))
U = np.random.random((output_features, output_features))
b = np.random.random((output_features,))
successive_outputs = []
for input_t in inputs: # 輸入形狀為(input_features,)的向量
# 由輸入和當(dāng)前狀態(tài)計(jì)算得到輸出
output_t = np.tanh(np.dot(W, input_t) + np.dot(U, state_t) + b)
successive_outputs.append(output_t) # 將輸出保存到列表中
state_t = output_t # 更新網(wǎng)絡(luò)狀態(tài)
final_output_sequence = np.concatenate(successive_outputs, axis=0)
一、Keras實(shí)現(xiàn)簡單RNN
keras中簡單RNN層使用下面語句:
from keras.layers import SimpleRNN
SimpleRNN層可以批量處理輸入序列,接收形狀為(batch_size, timesteps, input_feature)。與所有循環(huán)層一樣SimpleRNN可以再兩種模式下運(yùn)行:一種是返回每個(gè)時(shí)間步連續(xù)輸出的完整序列,即形狀為(batch_size, timesteps, output_features)的三維張量;另一種是只返回每個(gè)輸入序列的最終輸出,即形狀為(batch_size, output_features)的二維張量。這兩種模型由return_sequences這個(gè)構(gòu)造參數(shù)來控制。下面是一個(gè)簡單RNN的例子。
1. 數(shù)據(jù)準(zhǔn)備
這個(gè)例子使用的是imdb數(shù)據(jù)集,這個(gè)我們?cè)谥笆褂妹芗B接網(wǎng)絡(luò)解決過,如果需要回顧請(qǐng)點(diǎn)擊這里。
max_features = 10000 # 作為特征的單詞數(shù)量,也就是高頻出現(xiàn)的10000個(gè)詞語
maxlen = 500 # 評(píng)論500詞以上截?cái)?batch_size = 32 # 每個(gè)批次的數(shù)據(jù)個(gè)數(shù)
print('Loading data...')
(input_train, y_train), (input_test, y_test) = load_local(num_words=max_features)
print(len(input_train), 'train sequences')
print(len(input_test), 'test sequences')
print('Pad sequences (samples x time)')
input_train = sequence.pad_sequences(input_train, maxlen=maxlen) # 填充序列是所有序列都是相同長度
input_test = sequence.pad_sequences(input_test, maxlen=maxlen)
print('input_train shape:', input_train.shape)
print('input_test shape:', input_test.shape)
2.訓(xùn)練簡單RNN網(wǎng)絡(luò)
這個(gè)模型第一層是詞嵌入層獲得詞向量(詞嵌入回顧),第二層使用簡單RNN循環(huán)處理數(shù)據(jù),第三層使用密集連接構(gòu)成分類器。
model = Sequential()
model.add(Embedding(max_features, 32)) # 詞嵌入
model.add(SimpleRNN(32))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(input_train, y_train, epochs=10, batch_size=128, validation_split=0.2)
show_acc_results(history)


訓(xùn)練結(jié)果看看起來不是很理想,有兩個(gè)原因:1.輸入只考慮了500個(gè)單詞; 2.SimpleRNN不擅長處理長序列,比如文本。所以我們要引入更高級(jí)的循環(huán)層
二、LSTM和GRU
Keras中還有兩個(gè)循環(huán)層,LSTM和GRU。SimpleRNN的最大問題是,不能學(xué)習(xí)長期依賴,存在梯度消失問題。隨著層數(shù)的增加,網(wǎng)絡(luò)最終無法訓(xùn)練。
LSTM層是SimpleRNN層的一種變體,它增加了一種攜帶信息跨越多個(gè)時(shí)間步的方法。假設(shè)有一條傳送帶,其運(yùn)行方向平行于你所處理的序列。序列中的信息可以在任意位置跳上傳送帶,然后被傳送到更晚的時(shí)間步,并在需要的時(shí)候原封不動(dòng)的跳回來。它保存信息以便后面使用,從而防止較早期的信號(hào)在處理中逐漸消失。

LSTM的理解推薦[一篇文章](http://m.itdecent.cn/p/4b4701beba92),這里就不展開去講述。下面我們來看一下keras怎么去實(shí)現(xiàn)LSTM,非常簡單就是使用LSTM層:
model = Sequential()
model.add(Embedding(max_features, 32))
model.add(LSTM(32))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(input_train, y_train, epochs=10, batch_size=128, validation_split=0.2)
show_acc_results(history)
三、循環(huán)神經(jīng)網(wǎng)絡(luò)的高級(jí)用法
為了提高循環(huán)神經(jīng)網(wǎng)絡(luò)的性能和泛化能力,我們將依托耶拿氣溫預(yù)測問題來實(shí)踐3種提高循環(huán)神經(jīng)網(wǎng)絡(luò)的技巧:
- 循環(huán)dropout: 使用dropout來降低過擬合
- 堆疊循環(huán)層: 提高循環(huán)神經(jīng)網(wǎng)絡(luò)的表示能力
- 雙向循環(huán)層: 將相同的信息以不同的方式呈現(xiàn)給循環(huán)網(wǎng)絡(luò),可以提高精度并緩解遺忘問題。
1.問題描述和數(shù)據(jù)準(zhǔn)備
數(shù)據(jù)集是一個(gè)天氣事件序列數(shù)據(jù)集,它是德國耶拿地區(qū)的氣象數(shù)據(jù)。在這個(gè)數(shù)據(jù)集中,每10分鐘記錄14個(gè)不同的量,比如氣溫、氣壓、濕度、風(fēng)向等。我們將使用這個(gè)數(shù)據(jù)集構(gòu)造模型,輸入最近的一些數(shù)據(jù),可以預(yù)測24小時(shí)之后的氣溫。
數(shù)據(jù)集下載地址:https://s3.amazonaws.com/keras-datasets/jena_climate_2009_2016.csv.zip
首先我們解析數(shù)據(jù),將各項(xiàng)指標(biāo)的浮點(diǎn)數(shù)數(shù)據(jù)存放在張量中:
def get_temperature_data():
"""
對(duì)原始數(shù)據(jù)處理
:return: 返回標(biāo)準(zhǔn)化后的浮點(diǎn)數(shù)據(jù),和標(biāo)準(zhǔn)差
"""
data_dir = 'E:/git_code/data/jena_climate'
fname = os.path.join(data_dir, 'jena_climate_2009_2016.csv')
f = open(fname)
data = f.read()
f.close()
lines = data.split('\n')
header = lines[0].split(',')
lines = lines[1:]
print(header)
print(len(lines))
float_data = np.zeros((len(lines), len(header) - 1))
for index, line in enumerate(lines):
values = [float(x) for x in line.split(',')[1:]]
float_data[index, :] = values
mean = float_data[:200000].mean(axis=0)
float_data -= mean
std = float_data[:200000].std(axis=0)
float_data /= std
return float_data, std
繪制溫度曲線:
def show_sequence(float_data):
"""
展示序列圖像
:param float_data: 數(shù)據(jù)序列
:return: 返回浮點(diǎn)數(shù)據(jù)
"""
temp = float_data[:, 1]
plt.plot(range(len(temp)), temp)
plt.show()
plt.plot(range(1440), temp[:1440])
plt.show()

解決這個(gè)問題一些數(shù)據(jù)需要制定下來:
float_data, std = get_temperature_data() # 獲取處理后的數(shù)據(jù)和標(biāo)準(zhǔn)差
LOOK_BACK = 1440 # 輸入數(shù)據(jù)應(yīng)該包括過去的多少個(gè)時(shí)間步
STEP = 6 # 數(shù)據(jù)采樣周期。 設(shè)置為6,每小時(shí)抽取一個(gè)數(shù)據(jù)點(diǎn)
DELAY = 144 # 目標(biāo)應(yīng)該在未來的多少個(gè)時(shí)間步
BATCH_SIZE = 128 # 每批數(shù)據(jù)的樣本數(shù)
VAL_STEPS = (300000 - 200001 - LOOK_BACK) // BATCH_SIZE # 驗(yàn)證數(shù)據(jù)的批次數(shù)量
TEST_STEPS = (len(float_data) - 300001 - LOOK_BACK) // BATCH_SIZE # 測試數(shù)據(jù)的批次數(shù)量
構(gòu)造數(shù)據(jù)生成器,來組織數(shù)據(jù)以便可以給神經(jīng)網(wǎng)絡(luò)使用:
def generator(data, lookback, delay, min_index, max_index, shuffle=False, batch_size=128, step=6):
"""
根據(jù)參數(shù)生成數(shù)據(jù),用于進(jìn)行訓(xùn)練
:param data: 浮點(diǎn)數(shù)據(jù)組成的原始數(shù)據(jù)
:param lookback: 輸入數(shù)據(jù)應(yīng)該包括過去的多少個(gè)時(shí)間步
:param delay: 目標(biāo)應(yīng)該在未來的多少個(gè)時(shí)間步
:param min_index: data中的索引,用于確定抽取數(shù)據(jù)的范圍
:param max_index: data中的索引,用于確定抽取數(shù)據(jù)的范圍
:param shuffle: 是否打亂樣本
:param batch_size: 每批數(shù)據(jù)的樣本數(shù)
:param step: 數(shù)據(jù)采樣周期。 設(shè)置為6,每小時(shí)抽取一個(gè)數(shù)據(jù)點(diǎn)
:return:
"""
if max_index is None:
max_index = len(data) - delay - 1
i = min_index + lookback
while 1:
if shuffle:
rows = np.random.randint(min_index + lookback, max_index, size=batch_size)
else:
if i + batch_size >= max_index:
i = min_index + lookback
rows = np.arange(i, min(i + batch_size, max_index))
i += len(rows)
samples = np.zeros((len(rows), lookback // step, data.shape[-1]))
targets = np.zeros((len(rows),))
for j, row in enumerate(rows):
indices = range(rows[j] - lookback, rows[j], step)
samples[j] = data[indices]
targets[j] = data[rows[j] + delay][1]
yield samples, targets
2.確定訓(xùn)練的比較基準(zhǔn)
在開始神經(jīng)網(wǎng)絡(luò)的訓(xùn)練之前,我們先可以確定一些基準(zhǔn),更高級(jí)的神經(jīng)網(wǎng)絡(luò)起碼要比這個(gè)基準(zhǔn)要好。有時(shí)候這個(gè)基準(zhǔn)也是很難被打敗的。
這里我們假設(shè)24小時(shí)后的溫度與現(xiàn)在相同,這樣的假設(shè)預(yù)測的平均絕對(duì)誤差(mae)可以作為基準(zhǔn):
def evaluate_naive_method():
"""
計(jì)算一個(gè)基準(zhǔn)精度,始終預(yù)測24小時(shí)后的溫度與現(xiàn)在的溫度相同,以下代碼就計(jì)算局方絕對(duì)誤差(MAE)指標(biāo)來評(píng)估這種方法。
:return:
"""
batch_maes = []
for step in range(VAL_STEPS):
samples, targets = next(val_gen)
preds = samples[:, -1, 1] # 使用最后一個(gè)采樣點(diǎn)的溫度作為預(yù)測值
mae = np.mean(np.abs(preds - targets)) # 計(jì)算誤差
batch_maes.append(mae)
print(np.mean(batch_maes))
計(jì)算的MAE為0.29, 絕對(duì)誤差為0.29*std,即2.57攝氏度。
另一個(gè)基準(zhǔn)是使用密集連接神經(jīng)網(wǎng)絡(luò)來處理:
def train_dense(float_data):
"""
密集連接網(wǎng)絡(luò)預(yù)測問題
:param float_data: 標(biāo)準(zhǔn)化后溫度數(shù)據(jù)
:return:
"""
model = Sequential()
model.add(layers.Flatten(input_shape=(LOOK_BACK // STEP, float_data.shape[-1])))
model.add(layers.Dense(32, activation='relu'))
model.add(layers.Dense(1))
model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen, steps_per_epoch=500, epochs=20,
validation_data=val_gen, validation_steps=VAL_STEPS)
plt_loss(history)

使用密集連接層的損失要比前面一種基準(zhǔn)大,可以看出基準(zhǔn)并不容易超越。
第三種基準(zhǔn)是循環(huán)網(wǎng)絡(luò)基準(zhǔn),使用一層GRU層的循環(huán)神經(jīng)網(wǎng)絡(luò)。GRU層比LSTM更加簡化計(jì)算代價(jià)更低。
def train_gru(float_data):
"""
使用GRU循環(huán)網(wǎng)絡(luò)預(yù)測溫度
:param float_data: 標(biāo)準(zhǔn)化后溫度數(shù)據(jù)
:return:
"""
model = Sequential()
model.add(layers.GRU(32, input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))
model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen, steps_per_epoch=500, epochs=20,
validation_data=val_gen, validation_steps=VAL_STEPS)
plt_loss(history)

這一次的訓(xùn)練結(jié)果約為0.265,比基準(zhǔn)好好多了,但訓(xùn)練很快出現(xiàn)了過擬合,仍然可以改進(jìn)。
3.使用循環(huán)dropout來降低擬合
dropout即將某一層輸入單元隨機(jī)設(shè)置為0,目的是打破該層訓(xùn)練數(shù)據(jù)中的偶然相關(guān)性。dropout參數(shù)用于設(shè)置被dropout的單元的比例。
def train_gru_dropout(float_data):
"""
使用GRU循環(huán)網(wǎng)絡(luò)并使用dropout來降低過擬合
:param float_data: 標(biāo)準(zhǔn)化后溫度數(shù)據(jù)
:return:
"""
model = Sequential()
model.add(layers.GRU(32, dropout=0.2, recurrent_dropout=0.2, input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))
model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen, steps_per_epoch=500, epochs=40,
validation_data=val_gen, validation_steps=VAL_STEPS)
plt_loss(history)

過擬合現(xiàn)象比之前要好的多,但是結(jié)果提升并不大。
4.循環(huán)層堆疊
增加了dropout模型不再擬合,但是性能高似乎遇到了瓶頸,我們可以嘗試增加網(wǎng)絡(luò)容量,增加循環(huán)層:
def train_stacking_gru(float_data):
"""
使用GRU循環(huán)網(wǎng)絡(luò) + dropout + 堆疊
:param float_data: 標(biāo)準(zhǔn)化后溫度數(shù)據(jù)
:return:
"""
model = Sequential(float_data)
model.add(layers.GRU(32, dropout=0.1, recurrent_dropout=0.5, return_sequences=True,
input_shape=(None, float_data.shape[-1])))
model.add(layers.GRU(64, activation='relu', dropout=0.1, recurrent_dropout=0.5))
model.add(layers.Dense(1))
model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen, steps_per_epoch=500, epochs=40, validation_data=val_gen,
validation_steps=VAL_STEPS)
plt_loss(history)
訓(xùn)練結(jié)果入下:

4.雙向RNN
雙向RNN在某些任務(wù)上比普通的RNN要好。他會(huì)使用輸入的正向和反向都進(jìn)行訓(xùn)練:
def train_bidirectional_gru(float_data):
"""
使用雙向GRU來訓(xùn)練網(wǎng)絡(luò)
:return:
"""
model = Sequential()
model.add(layers.Bidirectional(
layers.GRU(32), input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))
model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen, steps_per_epoch=500, epochs=40,
validation_data=val_gen, validation_steps=VAL_STEPS)
plt_loss(history)
訓(xùn)練結(jié)果入下,這個(gè)結(jié)果不是很好,比基準(zhǔn)還要差一些:
