1. 程式人生 > >keras RNN的高階用法

keras RNN的高階用法

本文主要介紹迴圈神經網路的高階用法。

本節將介紹三種用於改善迴圈神經網路的效能和泛化能力的高階技術。

 * *Recurrent dropout 遞迴丟失*,一種特定的內建方法,可以使用丟失來對抗復發層中的過度擬合。
* *Stacking recurrent layers 堆疊迴圈層*,以增加網路的代表性能力(以更高的計算負荷為代價)。
 * *Bidirectional recurrent layers 雙向迴圈層*,以不同方式向迴圈網路提供相同的資訊,提高準確性並減少遺忘問題。

溫度預測問題

將展示關於天氣預報問題的所有三個概念,我們可以訪問建築物屋頂上安裝的感測器的時間序列資料點,例如溫度,氣壓和溼度,用它來預測收集最後一個數據點後24小時的溫度。這是一個相當具有挑戰性的問題,它體現了使用時間序列時遇到的許多常見困難。

們將播放德國耶拿Max-Planck生物地球化學研究所氣象站記錄的天氣時間序列資料集:https://s3.amazonaws.com/keras-datasets/jena_climate_2009_2016.csv.zip
在該資料集中,在幾年內每十分鐘記錄十四種不同的量(例如空氣溫度,大氣壓力,溼度,風向等)。原始資料可以追溯到2003年,但本次測試僅限於2009 - 2016年的資料。該資料集非常適合學習使用數值時間序列。本節將使用它來構建一個模型,該模型將最近過去的一些資料(幾天的資料點)作為輸入,並預測未來24小時的空氣溫度。

下面是示例程式碼:


# coding: utf-8

# In[3]:


import os

fname = '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))

# 將所有420551行資料轉換為Numpy陣列

import numpy as np

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

# 溫度(以攝氏度為單位)隨時間變化的曲線圖:
from matplotlib import pyplot as plt

temp = float_data[:, 1]  #  溫度(以攝氏度為單位)
plt.plot(range(len(temp)), temp)
plt.show()

# 在圖中,可以清楚地看到溫度的年度週期。
# 這是前十天溫度資料的更窄的圖(由於資料每十分鐘記錄一次,每天可以得到144個數據點)
plt.plot(range(1440), temp[:1440])
plt.show()

'''
在圖中,可以看到每日週期,尤其是過去4天。還可以注意到,這十天的時間必須來自一個相當寒冷的冬季。
如果試圖在給定幾個月的過去資料的情況下預測下個月的平均溫度,由於資料的可靠年度週期性,問題將很容易。
但是,在幾天的時間內檢視資料,溫度看起來更加混亂。那麼這個時間序列是否可以在日常範圍內預測?。
準備資料
問題的確切表述如下:給定的資料可以追溯到“回顧”時間步長(時間步長為10分鐘)並對每個“步”時間步進行取樣,可以預測“延遲”時間步長的溫度嗎?
將使用以下引數值:
 *`lookback = 720`,即觀察將會回溯5天。
 *`steps = 6`,即觀察將在每小時一個數據點進行取樣。
 *`delay = 144`,即目標將來是24小時。
 首先,需要做兩件事:
 *將資料預處理為神經網路可以攝取的格式。這很簡單:資料已經是數字的,因此我們不需要進行任何向量化。
然而,資料中的每個時間序列處於不同的比例(例如,溫度通常在-20和+30之間,但是以mbar測量的壓力大約是1000)。因此,
將獨立地對每個時間序列進行標準化,以便它們都以相似的比例取小值。
 *編寫一個Python生成器,它接收當前的浮點資料陣列,併產生最近過去的批量資料,以及一個
#將來的目標溫度。由於資料集中的樣本是高度冗餘的(例如樣本“N”和樣本“N + 1”將具有共同的大部分時間步長),因此明確分配每個樣本將是非常浪費的。
相反,將使用原始資料動態生成樣本。
通過減去每個時間序列的平均值併除以標準偏差來預處理資料。我們計劃使用前200,000個時間步作為訓練資料,因此我們僅計算這部分資料的平均值和標準差:
'''
mean = float_data[: 200000].mean(axis=0)
float_data -= mean
std = float_data[: 200000].std(axis=0)
float_data /= std


'''
將使用的資料生成器。 它產生一個元組`(樣本,目標)`其中`samples`是一批輸入資料和
  `targets`是相應的目標溫度陣列。 它需要以下引數:
*`data`:浮點資料的原始陣列,已經在上面的程式碼片段中對其進行了規範化。
*`lookback`:輸入資料應該返回多少次。
  *`delay`:未來我們的目標應該是多少次。
  *`min_index`和`max_index`:`data`陣列中的索引,用於分隔要繪製的時間步長。 這對於保留細分非常有用
  用於驗證的資料和用於測試的另一個數據。
  *`shuffle`:是否按照時間順序對樣品進行洗牌或繪製。
  *`batch_size`:每批樣本數。
*`step`:取樣資料的時間段,在時間步長。 將其設定為6以便每小時繪製一個數據點。
'''
def generator(data, lookback, delay, min_index, max_index,
              shuffle=False, batch_size=128, step=6):
    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的功效理解為暫停和播放。
        # 在一個函式中,程式執行到yield語句的時候,程式暫停,返回yield後面表示式的值,
        # 在下一次呼叫的時候,從yield語句暫停的地方繼續執行,如此迴圈,直到函式執行完
        yield samples, targets


# In[5]:


# 現在讓我們使用抽象生成器函式來例項化三個生成器,一個用於訓練,一個用於驗證,一個用於測試。
# 每個都將檢視原始資料的不同時間段:訓練生成器檢視前200,000個步驟,驗證生成器檢視以下100,000個,
# 並且測試生成器檢視剩餘部分。
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)


# In[6]:


# 從`val_gen`中抽取多少步驟以檢視整個驗證集:
val_steps = (300000 - 200001 - lookback) // batch_size


# In[7]:


# 從`test_gen`中抽取多少步驟以檢視整個測試集:
test_steps = (len(float_data) - 300001 - lookback) // batch_size


# In[11]:


'''
常識,非機器學習基線
在開始利用黑盒深度學習模型來解決溫度預測問題之前,先嚐試一種簡單的常識方法
。它將作為一個健全性檢查,它將建立一個必須擊敗的基線,以證明更先進的機器
學習模型的有用性。當接近尚未知解決方案的新問題時,這種常識基線可能非常有用。
一個典型的例子是不平衡的分類任務,其中一些類可能比其他類更常見。如果資料集
包含90%的A類例項和10%B類例項,那麼分類任務的常識方法是在呈現新樣本時始終
預測“A”。這樣的分類器總體上將是90%準確,因此任何基於學習的方法都應該超過
這個90%的分數以證明有用性。有時,這種基本基線可能難以擊敗。
在溫度預測的例子中,可以安全地假設溫度時間序列是連續的(明天的溫度可能接近
今天的溫度)以及每日期間的週期。因此,常識方法是始終預測從現在起24小時後的溫度
將等於現在的溫度。讓我們使用平均絕對誤差度量(MAE)來評估這種方法.
Mean Absolute Error簡單地等於:
'''
# np.mean(np.preds - targets)
def evaluate_naive_method():
    batch_maes = []
    for step in range(val_steps):
        samples, targets = next(val_gen)
        preds = samples[:, -1, 1]
        mae = np.mean(np.abs(preds - targets))
        batch_maes.append(mae)
    print(np.mean(batch_maes))


evaluate_naive_method()


# In[14]:


'''
它產生的MAE為0.29。由於溫度資料已經標準化為以0為中心並且標準偏差為1,因此
該數字不能立即解釋。它轉換為平均絕對誤差“0.29 * temperature_std”攝氏度,
即2.57˚C。這是一個相當大的平均絕對誤差 - 

接下來是進行改進:
一種基本的機器學習方法
以在嘗試機器學習方法之前建立常識基線有用的方式相同,在研究複雜且計算量大的
模型之前嘗試簡單且廉價的機器學習模型(例如小型密集連線網路)是有用的。 RNNs。
這是確保我們以後針對該問題提出的任何進一步複雜性是合法的並提供真正好處的最佳方法。
這是一個簡單的完全連線模型,首先展平資料,然後通過兩個“密集”層執行它。
請注意最後一個'Dense`圖層缺少啟用功能,這是迴歸問題的典型特徵。使用MAE作為損失。
由於正在評估完全相同的資料並使用與我們的常識方法完全相同的指標,因此結果將直接具有可比性。
'''
from keras.models import Sequential
from keras import layers
from keras.optimizers import RMSprop

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))


# In[15]:


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)


# In[16]:


import matplotlib.pyplot as plt

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(loss))

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()


# In[18]:


'''
一些驗證損失接近無學習基線,但不是非常可靠。這表明首先有這個基線的優點:
事實證明並不那麼容易。
如果存在一個簡單,表現良好的模型,從資料到目標(我們的常識基線),為什麼
正在訓練的模型不能找到並改進它?簡單地說:因為這個簡單的解決方案不是訓練
設定所需要的。尋找解決方案的模型空間,即假設空間,是具有定義的配置的所有
可能的2層網路的空間。這些網路已經相當複雜。當尋找具有複雜模型空間的解決
方案時,簡單的良好效能基線可能是無法獲得的,即使它在技術上是假設空間的
一部分。這通常是機器學習的一個非常重要的限制:除非學習演算法被硬編碼以尋
找特定型別的簡單模型,否則引數學習有時無法找到簡單問題的簡單解決方案。

第一個復發基線
第一個完全連線的方法並不是那麼好,但這並不意味著機器學習不適用於問題。
上面的方法包括首先展平時間序列,從輸入資料中刪除時間概念。資料是什麼:
一個序列,因果關係和秩序很重要。接下來將嘗試一種迴圈序列處理模型 - 
它應該是這種序列資料的完美擬合,正是因為它確實利用了資料點的時間排序,
這與第一種方法不同。
接下來將使用由Cho等人開發的`GRU`層,而不是上一節中介紹的`LSTM`層。在2014
年,`GRU`層(代表“門控迴圈單元”)通過利用與LSTM相同的原理工作,但它們
有些精簡,因此執行起來更便宜,儘管它們可能沒有LSTM那麼多的代表性能力。
計算代價和代表性能力之間的這種權衡在機器學習中隨處可見。
'''
from keras.models import Sequential
from keras import layers
from keras.optimizers import RMSprop

model = Sequential()
model.add(layers.GRU(32, input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))


# In[20]:


model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen,
                             steps_per_epoch=500,
                             epochs=5,  # 最好多訓練幾輪,比如20輪,為了展示方便我只訓練了5輪
                             validation_data=val_gen,
                             validation_steps=val_steps)


# In[21]:


loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(loss))

# 繪製損失曲線
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.legend()
plt.show()


# In[23]:


'''
上述效果比第一種方式好了很多!能夠顯著擊敗基準基線,這樣就證明了機器學習的價值,
以及與此類任務中的序列扁平化密集網路相比,迴圈網路的優越性。
新的驗證MAE約為0.265(訓練20輪左右,在我們開始顯著過度擬合之前)轉換為去標準化後的平均絕對誤差2.35˚C。
這是我們初始誤差2.57˚C的穩固增長,但我們可能還有一些改進餘地。

使用recurrent dropout來削弱過度擬合
從訓練和驗證曲線可以看出,模型過度擬合:訓練和驗證損失在幾個時期之後開始顯著不同。
解決的方式:‘dorpout’丟失,包括隨機清零圖層的輸入單元,以打破圖層暴露的訓練資料中的偶然相關性。
但是,如何在經常性網路中正確應用dropout並不是一個微不足道的問題。人們早就知道,在recurrent layer(迴圈層)
之前應用輟學會阻礙學習而不是幫助正規化。
2015年,Yarin Gal,作為博士學位的一部分。關於貝葉斯深度學習的論文,確定了使用丟失與迴圈網路的正確方法
:應該在每個時間步應用相同的丟失掩碼(相同的丟棄單位模式),而不是從時間步長到時間步長隨機變化的丟失掩碼。
更重要的是:為了規範由GRU和LSTM等層的迴圈門形成的表示,應該對層的內部迴圈啟用應用時間上恆定的丟失掩模
(“迴圈”丟失掩碼)。在每個時間步使用相同的丟失掩碼允許網路在時間上正確地傳播其學習錯誤;一個暫時的
隨機丟失掩碼會破壞這個錯誤訊號並對學習過程有害。
Yarin Gal使用Keras進行了他的研究,並幫助將這種機制直接構建到Keras recurrent layer。 Keras中的每個迴圈層都有兩個
與dropout相關的引數:`dropout`,一個指定圖層輸入單位的丟失率的浮點數,以及`recurrent_dropout`,
指定迴圈單位的丟失率。讓我們在我們的GRU層新增丟失和重複丟失,看看它如何影響過度擬合。由於網路正常化
與丟失總是需要更長時間才能完全收斂,因此我們訓練網路的時間是原來的兩倍。

'''
from keras.models import Sequential
from keras import layers
from keras.optimizers import RMSprop

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))


# In[25]:


model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen,
                             steps_per_epoch=500,
                             epochs=5,  # 最好多訓練幾輪,比如40輪,為了展示方便我只訓練了5輪
                             validation_data=val_gen,
                             validation_steps=val_steps)


# In[26]:


loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(loss))

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()


# In[37]:


'''
可以看到,增加的層確實提升了效果,雖然不是很顯著。可以得出兩個結論:
*由於我們仍然沒有過度擬合,可以安全地增加層的大小,以尋求一點驗證損失改進。
但是,這確實具有不可忽略的計算成本。
*由於新增一個層並沒有產生重大影響,因此可能會看到此時增加網路容量的收益遞減。
使用雙向RNN | Using bidirectional RNNs
將在本節中介紹的最後一種技術稱為“bidirectional RNNs”。雙向RNN是常見的RNN變體,
其在某些任務上可以提供比常規RNN更高的效能。它經常用於自然語言處理 - 可以把它稱為NLP的深度學習的瑞士軍刀。
RNN特別依賴於順序或時間依賴性:它們按順序處理其輸入序列的時間步長,並且改組或反轉時間步長可以
完全改變RNN將從序列中提取的表示。這正是他們在訂單有意義的問題上表現良好的原因,例如溫度預測問題。
雙向RNN利用RNN的順序靈敏度:它只包含兩個常規RNN,例如您已經熟悉的GRU或LSTM層,每個都在一個方向上處理
輸入序列(按時間順序和反時間順序),然​​後合併它們的表示。通過雙向處理序列,雙向RNN能夠捕獲可能被單向R
NN忽略的模式。
值得注意的是,本節中的RNN層到目前為止按時間順序處理序列(較早的時間步長)可能是一個隨意的決定。
。如果按照反時間順序處理輸入序列,需要做的就是編寫一個數據生成器的變體,其中輸入序列沿時間維度被恢復(
用`yield samples [:,:: - 1,:],targets`替換最後一行)。培訓與本節第一個實驗中使用的相同的一個GRU層網路,
可以得到以下結果:
'''
def reverse_order_generator(data, lookback, delay, min_index, max_index, 
                           shuffle=False, batch_size=128, step=6):
    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[:, ::-1, :], targets


# In[38]:


train_gen_reverse = reverse_order_generator(float_data,
                                           lookback=lookback,
                                           delay=delay,
                                           min_index=0,
                                           max_index=200000,
                                           shuffle=True,
                                           step=step,
                                           batch_size=batch_size)
val_gen_reverse = reverse_order_generator(float_data,
                                         lookback=lookback,
                                         delay=delay,
                                         min_index=200001,
                                         max_index=300000,
                                         step=step,
                                         batch_size=batch_size)

model = Sequential()
model.add(layers.GRU(32, input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))


# In[40]:


model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen_reverse,
                             steps_per_epoch=500,
                             epochs=5,  # 最好多訓練幾輪,比如20輪,為了展示方便我只訓練了5輪
                             validation_data=val_gen_reverse,
                             validation_steps=val_steps)


# In[41]:


loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(loss))

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()


# In[46]:


'''
逆序GRU甚至在常識基線上也表現不佳,這表明在案例中按時間順序處理對於方法的成功非常重要。 
這是完全合理的:基礎GRU層通常會更好地記住最近的過去而不是遙遠的過去,並且自然地,最近的
天氣資料點比問題中的舊資料點更具預測性(這正是使得常識的原因) 基線相當強的基線)。 因
此,層的時間順序版本必然優於逆序版本。 重要的是,這通常不適用於許多其他問題,包括自然語
言:直覺上,單詞在理解句子時的重要性通常不取決於其在句子中的位置。 
接下來在上一節的LSTM IMDB示例中嘗試相同的技巧:
'''
from keras.datasets import imdb
from keras.preprocessing import sequence
from keras import layers
from keras.models import Sequential

# 考慮作為特徵的單詞數
max_features = 10000

maxlen = 500

# 載入資料
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
                     
#  reverse sequences
x_train = [x[::-1] for x in x_train]
x_test = [x[::-1] for x in x_test]
# Pad sequences
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)      


# In[ ]:


'''。
值得注意的是,在這樣的文字資料集中,逆序處理與按時間順序處理一樣有效,證實了我們的假設
,儘管詞序*在理解語言方面起作用*,*您使用的*順序並不重要。重要的是,訓練有關反向序列的
RNN將學習與原始序列訓練不同的表徵,就像在現實世界中時間倒流時你會有完全不同的心理模型一樣
- 如果你過著與你生活相同的生活在你的第一天去世,你出生在你的最後一天。在機器學習中,*不同
*但有用*的表示總是值得利用,它們越多越好:它們提供了一個新的角度來檢視您的資料,捕獲其他
人遺漏的資料的方面方法,因此它們可以提高任務的效能。
 雙向RNN利用這一想法來改進按時間順序的RNN的效能:它以兩種方式檢視其輸入序列,獲得可能更豐
 富的表示並捕獲可能僅由時間順序版本遺漏的模式。

'''
from keras import backend as K
K.clear_session()


# In[5]:


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)


# In[ ]:


from keras.models import Sequential
from keras import layers
from keras.optimizers import RMSprop

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)

執行結果: