Keras深度學(xué)習(xí)實(shí)踐7——循環(huán)卷積神經(jīng)網(wǎng)絡(luò)

內(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)行遍歷。


RNN原理

我們用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)
簡單RNN模型的精度

簡單RNN模型的損失

訓(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原理圖

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()
溫度數(shù)據(jù)曲線

解決這個(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)
密集連接網(wǎng)絡(luò)的損失

使用密集連接層的損失要比前面一種基準(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)
GRU訓(xùn)練損失

這一次的訓(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)
GRU結(jié)合dropout訓(xùn)練損失

過擬合現(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é)果入下:


stack_gru.png

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)還要差一些:


gru_bi.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容