本節介紹循環神經網絡及其優化
循環神經網絡(RNN,recurrent neural network)處理序列的方式是,遍歷全部序列元素,並保存一個狀態(state),其中包含與已查看內容相關的信息。在處理兩個不一樣的獨立序列(好比兩條不一樣的 IMDB 評論)之間,RNN 狀態會被重置,所以,你仍能夠將一個序列看做單個數據點,即網絡的單個輸入。真正改變的是,數據點再也不是在單個步驟中進行處理,相反,網絡內部會對序列元素進行遍歷,RNN 的特徵在於其時間步函數
html
Keras 中的循環層數組
from keras.layers import SimpleRNN
它接收形狀爲 (batch_size, timesteps, input_features) 的輸入
與 Keras 中的全部循環層同樣,SimpleRNN 能夠在兩種不一樣的模式下運行:一種是返回每一個時間步連續輸出的完整序列,即形狀爲 (batch_size, timesteps, output_features)的三維張量;另外一種是隻返回每一個輸入序列的最終輸出,即形狀爲 (batch_size, output_features) 的二維張量。這兩種模式由return_sequences 這個構造函數參數來控制。爲了提升網絡的表示能力,將多個循環層逐個堆疊有時也是頗有用的。在這種狀況下,你須要讓全部中間層都返回完整的輸出序列,即將return_sequences設置爲True網絡
簡單Demo with SimpleRNNdom
from keras.datasets import imdb from keras.preprocessing import sequence from keras.layers import Dense, Embedding, SimpleRNN from keras.models import Sequential import matplotlib.pyplot as plt max_features = 10000 maxlen = 500 batch_size = 32 (input_train, y_train), (input_test, y_test) = imdb.load_data(num_words=max_features, path='E:\\study\\dataset\\imdb.npz') 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) # 用 Embedding 層和 SimpleRNN 層來訓練模型 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) acc = history.history['acc'] val_acc = history.history['val_acc'] loss = history.history['loss'] val_loss = history.history['val_loss'] epochs = range(1, len(acc) + 1) plt.plot(epochs, acc, 'bo', label='Training acc') plt.plot(epochs, val_acc, 'b', label='Validation acc') plt.title('Training and validation accuracy') plt.legend() plt.figure() plt.plot(epochs, loss, 'bo', label='Training loss') plt.plot(epochs, val_loss, 'b', label='Validation loss') plt.title('Training and validation loss') plt.legend() plt.show()
結果
機器學習
Keras同時還內置了另外兩個循環層:LSTM 和 GRU
SimpleRNN 的最大問題不能學到長期依賴,其緣由在於梯度消失問題。LSTM 層和 GRU 層都是爲了解決這個問題而設計的
LSTM(long short-term memory)層是 SimpleRNN 層的一種變體,它增長了一種攜帶信息跨越多個時間步的方法,保存信息以便後面使用,從而防止較早期的信號在處理過程當中逐漸消失函數
簡單Demo with LSTM性能
from keras.datasets import imdb from keras.preprocessing import sequence from keras.layers import Dense, Embedding, LSTM from keras.models import Sequential import matplotlib.pyplot as plt max_features = 10000 maxlen = 500 batch_size = 32 (input_train, y_train), (input_test, y_test) = imdb.load_data(num_words=max_features, path='E:\\study\\dataset\\imdb.npz') 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) 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) acc = history.history['acc'] val_acc = history.history['val_acc'] loss = history.history['loss'] val_loss = history.history['val_loss'] epochs = range(1, len(acc) + 1) plt.plot(epochs, acc, 'bo', label='Training acc') plt.plot(epochs, val_acc, 'b', label='Validation acc') plt.title('Training and validation accuracy') plt.legend() plt.figure() plt.plot(epochs, loss, 'bo', label='Training loss') plt.plot(epochs, val_loss, 'b', label='Validation loss') plt.title('Training and validation loss') plt.legend() plt.show()
結果
學習
可見這次結果比SimpleRNN網絡要好一些,主要是由於LSTM 受梯度消失問題的影響要小得多
LSTM適用於評論分析全局的長期性結構測試
能夠提升循環神經網絡的性能和泛化能力的三種高級技巧優化
門控循環單元(GRU,gated recurrent unit)層的工做原理與 LSTM 相同。但它作了一些簡化,所以運
行的計算代價更低(雖然表示能力可能不如 LSTM),GRU層一般更善於記住最近的數據,而不是久遠的數據
使用以上三種種方式來進行溫度預測
import os import numpy as np from matplotlib import pyplot as plt from keras.models import Sequential from keras import layers from keras.optimizers import RMSprop from keras import models data_dir = 'E:\\study\\dataset' 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)) # 將數據轉換成一個 Numpy 數組 float_data = np.zeros((len(lines), len(header) - 1)) for i, line in enumerate(lines): values = [float(x) for x in line.split(',')[1:]] float_data[i, :] = values # 溫度 temp = float_data[:, 1] plt.plot(range(len(temp)), temp) plt.show() # 前 10 天的溫度時間序列 plt.plot(range(1440), temp[:1440]) plt.show() # 數據標準化 # 將使用前200 000 個時間步做爲訓練數據 mean = float_data[:200000].mean(axis=0) float_data -= mean std = float_data[:200000].std(axis=0) float_data /= std # 生成時間序列樣本及其目標的生成器 def generator(data, lookback, delay, min_index, max_index, shuffle=False, batch_size=128, step=6): """ :param data: 浮點數數據組成的原始數組 :param lookback: 輸入數據應該包括過去多少個時間步 :param delay: 目標應該在將來多少個時間步以後 :param min_index: 數組中的索引 :param max_index: 數組中的索引 :param shuffle: 是打亂樣本,仍是按順序抽取樣本 :param batch_size: 每一個批量的樣本數 :param step: 數據採樣的週期 :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 # 準備訓練生成器、驗證生成器和測試生成器 lookback = 1440 step = 6 delay = 144 batch_size = 128 train_gen = generator(float_data, lookback=lookback, delay=delay, min_index=0, max_index=200000, shuffle=True, step=step, batch_size=batch_size) val_gen = generator(float_data, lookback=lookback, delay=delay, min_index=200001, max_index=300000, step=step, batch_size=batch_size) test_gen = generator(float_data, lookback=lookback, delay=delay, min_index=300001, max_index=None, step=step, batch_size=batch_size) # 查看,須要從 generate 中抽取多少次 val_steps = (300000 - 200001 - lookback) // batch_size test_steps = (len(float_data) - 300001 - lookback) // batch_size def get_base_model_history(): model = Sequential() model.add(layers.Flatten(input_shape=(lookback // step, float_data.shape[-1]))) model.add(layers.Dense(32, activation='relu')) model.add(layers.Dense(1)) model.compile(optimizer=RMSprop(), loss='mae', metrics=['acc']) history = model.fit_generator(train_gen, steps_per_epoch=500, epochs=20, validation_data=val_gen, validation_steps=val_steps) return history # 使用GRU 的模型 def get_gru_model_history(): 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', metrics=['acc']) history = model.fit_generator(train_gen, steps_per_epoch=500, epochs=20, validation_data=val_gen, validation_steps=val_steps) return history # 使用 dropout 正則化的基於 GRU 的模型 def get_gru_model_with_dropout_history(): 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', metrics=['acc']) history = model.fit_generator(train_gen, steps_per_epoch=500, epochs=40, validation_data=val_gen, validation_steps=val_steps) model.save('gru_model_with_dropout.h5') return history # 使用 dropout 正則化的堆疊 GRU 模型 def get_mul_gru_model_with_dropout_history(): model = Sequential() 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', metrics=['acc']) history = model.fit_generator(train_gen, steps_per_epoch=500, epochs=40, validation_data=val_gen, validation_steps=val_steps) model.save('mul_gru_model_with_dropout') return history def draw_loss(history): loss = history.history['loss'] val_loss = history.history['val_loss'] epochs = range(1, len(loss) + 1) plt.figure() plt.plot(epochs, loss, 'bo', label='Training loss') plt.plot(epochs, val_loss, 'b', label='Validation loss') plt.title('Training and validation loss') plt.legend() plt.show() draw_loss(get_base_model_history()) draw_loss(history=get_gru_model_history()) draw_loss(history=get_gru_model_with_dropout_history()) draw_loss(history=get_mul_gru_model_with_dropout_history())
結果
原始數據
十天數據
基準
只使用gru的預測Loss
由於第一個和其它兩個是分開訓練的,因此由於draw_acc_and_loss函數中的history參數寫成了'acc'獲得了報錯,而以前只保存了model,而沒有保存history,因此畫不出來,如下兩個將引用原書中結果圖,之後有空再補
使用 dropout 正則化的gru
使用 dropout 正則化的堆疊 GRU 模型
由以上可見,相對於基準模型,使用 GRU 稍微下降了 loss,可是很快過擬合了,而後使用帶有 dropout 的 GRU,再次下降了 loss,可是最後在0.28左右變得平緩,說明遇到了性能瓶頸,最後咱們使用帶有 dropout 正則化的堆疊 GRU 模型,性能再次提升,可是依舊不是很好
注意:想要在循環網絡中使用 dropout,你應該使用一個不隨時間變化的 dropout 掩碼與循環 dropout 掩碼。這兩者都內置於 Keras 的循環層中,因此你只須要使用循環層的 dropout 和 recurrent_dropout 參數便可
最後是雙向 RNN,它經常使用於天然語言處理
RNN是特別依賴順序或時間的,打亂時間步或反轉時間步會徹底改變RNN從序列中提取的表示。因此,若是順序對問題很重要,RNN的表現會很好。雙向RNN利用了RNN的順序敏感性:它包含兩個普通RNN,每一個RNN分別沿一個方向對輸入序列進行處理,而後將它們合併在一塊兒。經過沿這兩個方向處理序列,雙向RNN可以捕捉到可能被單向RNN忽略的模式
逆序數據,情感分類 Demo(用於性能比較)
from keras.datasets import imdb from keras.preprocessing import sequence from keras import layers from keras.models import Sequential import tools # 將畫圖的部分封裝到了tools裏面,依舊使用imdb數據(評論情感分類) max_features = 10000 maxlen = 500 (x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features, path='E:\\study\\dataset\\imdb.npz') # 逆序數據 x_train = [x[::-1] for x in x_train] x_test = [x[::-1] for x in x_test] x_train = sequence.pad_sequences(x_train, maxlen=maxlen) x_test = sequence.pad_sequences(x_test, maxlen=maxlen) model = Sequential() model.add(layers.Embedding(max_features, 128)) model.add(layers.LSTM(32)) model.add(layers.Dense(1, activation='sigmoid')) model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc']) history = model.fit(x_train, y_train, epochs=10, batch_size=128, validation_split=0.2) tools.draw_acc_and_loss(history)
結果
可見,逆序數據以後,模型的性能與正序幾乎沒有改變,這證實一個假設:雖然單詞順序對於理解語言很重要,但使用哪一種順序並不重要。重要的是,在逆序序列上訓練的RNN學到的表示不一樣於在原始序列上學到的表示。在機器學習中,若是一種數據表示不一樣但有用,那麼老是值得加以利用,這種表示與其餘表示的差別越大越好,它們提供了查看數據的全新角度,抓住了數據中被其餘方法忽略的內容,所以能夠提升模型在某個任務上的性能
雙向 RNN 正是利用這個想法來提升正序 RNN 的性能,它從兩個方向查看數據,從而獲得更加豐富的表示,並捕捉到僅使用正序 RNN 時可能忽略的一些模式
使用雙向LSTM和雙向GRU的方法
from keras.models import Sequential from keras import layers from keras.optimizers import RMSprop def get_bothway_lstm_history(max_features, x_train, y_train): model = Sequential() model.add(layers.Embedding(max_features, 32)) model.add(layers.Bidirectional(layers.LSTM(32))) model.add(layers.Dense(1, activation='sigmoid')) model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc']) history = model.fit(x_train, y_train, epochs=10, batch_size=128, validation_split=0.2) return history def get_bothway_gru_history(float_data, train_gen, val_gen, val_steps): 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) return history
向函數中填充對應數據便可開始訓練
書中給出的結果是: 雙向LSTM的表現比普通的LSTM略好,這是能夠理解的,畢竟情感分析與輸入順序是沒有什麼關係的,而使用雙向的LSTM比單向的LSTM參數多了一倍
當使用雙向GRU來預測溫度時,並無比普通的好,這也是能夠理解的,GRU對於近期的記憶要好一些,可是對於遠期的記憶表現的交叉,而溫度預測是與時間相關的,當改變輸入順序,GRU必然會出現很差的預測,所以,使用雙向GRU時,作出貢獻的幾乎都是正向的那個
在此,給一個建議,當你的model須要訓練的時間很長的話,能夠先使用只是一輪的訓練來測試程序是否徹底正確。而後,還能夠在每次或每幾回訓練以後就保存一下模型,順便保存一下history(若是須要的話)
Deep learning with Python 學習筆記(7)
Deep learning with Python 學習筆記(5)