1. 程式人生 > >python Deep learning 學習筆記(6)

python Deep learning 學習筆記(6)

本節介紹迴圈神經網路及其優化
迴圈神經網路(RNN,recurrent neural network)處理序列的方式是,遍歷所有序列元素,並儲存一個狀態(state),其中包含與已檢視內容相關的資訊。在處理兩個不同的獨立序列(比如兩條不同的 IMDB 評論)之間,RNN 狀態會被重置,因此,你仍可以將一個序列看作單個數據點,即網路的單個輸入。真正改變的是,資料點不再是在單個步驟中進行處理,相反,網路內部會對序列元素進行遍歷,RNN 的特徵在於其時間步函式

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 SimpleRNN

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適用於評論分析全域性的長期性結構

可以提高迴圈神經網路的效能和泛化能力的三種高階技巧

  • 迴圈 dropout(recurrent dropout)。這是一種特殊的內建方法,在迴圈層中使用 dropout 來降低過擬合
  • 堆疊迴圈層(stacking recurrent layers)。這會提高網路的表示能力(代價是更高的計算負荷)
  • 雙向迴圈層(bidirectional recurrent layer)。將相同的資訊以不同的方式呈現給迴圈網路,可以提高精度並緩解遺忘問題

門控迴圈單元(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(如果需要的話)

python Deep learning 學習筆記(5)