2019年1月30日 星期三

深度學習 - 天氣預測練習 - 多種模型嘗試與優化

先上結論:以供後續方便複習
  1. 遇到新問題時,最好首先為你選擇的指標建立一個基於常識的基準(baseline)。藉由baseline 與手上訓練出來的模型比較,分辨模型建立的方向是否正確。
    ex.分辨MINST手寫數字的baseline=10%,分辨IMDb文章正反面的baseline=50%...等等
  2. 在嘗試計算代價較高的模型之前,先嘗試一些簡單的模型,以此證明增加計算代價是有意義的。有時簡單模型就是你的最佳選擇。
  3. 如果時間順序對數據很重要,那麼循環網絡是一種很適合的方法,先將時間數據展平的模型無法訓練出與時間順訊相關的特徵。
  4. 想要在循環網絡中使用dropout,應該使用一個不隨時間變化的dropout掩碼與循環dropout掩碼。
  5. 在處理語言翻譯等複雜問題時,與單個RNN層相比,堆疊RNN的表示能力更加強大。但它的計算代價也更高。堆疊RNN在較小,較簡單的問題上可能不一定有用。
  6. 雙向RNN從兩個方向查看一個序列,它對自然語言處理問題非常有用。但如果在序列數據中最近的數據比序列開頭包含更多的信息,那麼這種方法的效果就不明顯。

開始進入主題,來練習天氣預測的問題,先下載數據
 https://s3.amazonaws.com/kerasdatasets/jena_climate_2009_2016.csv.zip
解壓縮後得到一個.csv檔,打開後可以看到資料大概如下。

此資料檔是德國耶拿的馬克思• 普朗克生物地球化學研究所的氣象站紀錄,在這個數據集中,每10 分鐘記錄14 個不同的量(比如氣壓、溫度、濕度...等)
接著用編碼來練習讀取數據
import os
data_dir = 'C:\\Users\\lido_lee\\Downloads\\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))

['"Date Time"', '"p (mbar)"', '"T (degC)"', '"Tpot (K)"', '"Tdew (degC)"', '"rh (%)"', '"VPmax (mbar)"', '"VPact (mbar)"', '"VPdef (mbar)"', '"sh (g/kg)"', '"H2OC (mmol/mol)"', '"rho (g/m**3)"', '"wv (m/s)"', '"max. wv (m/s)"', '"wd (deg)"']
420551
總共有420551筆資料,420551*10/60/24/365 ~= 8 ,總共8年的data。
將溫度的data完整畫出
import matplotlib.pyplot as plt
temp = float_data[:, 1] # Temperature
plt.plot(range(len(temp)), temp,'g')
plt.xlabel('10 min /step')
plt.ylabel('Temperature')
plt.show()

我們的目的是要練習訓練模型可以用來預測溫度,"在得到10天的天氣變化資料,預測24小時候的溫度。"
  • 首先,先將數據標準化,因為要使用前200000個data做訓練集,故標準化前200000個data即可。
    標準化後的圖如下:
  • 我們需要一個生成器 (generator),可以用來產出訓練集、驗證集、測試集,有點類似先前練習的Imagedatagenerator ,只是這邊需要完整的練習寫一遍。

    這個生成器會有的參數:
    1.'data',輸入data 要分成[0:200000],[200001:300000],[300001:]分別是訓練集、驗證集、測試集。
    2.'min_index','max_index',呈上,選取資料範圍由'min_index','max_index'來定義。
    3.'delay',因為是預測問題,要預測24小時候的溫度,所以要delay=144個data。
    4.'lockback',得到10天的天氣變化的'lockback=10天的數據量',這邊lockback=1440。
    5.'samples',生成器的輸出,用來訓練模型的資料。
    6.'targets',生成器的輸出,用來訓練模型預測的溫度。
    7.'shuffle',是打亂樣本,還是按順序抽取樣本。
    8.'batch_size',每個批量的樣本數。
    9.'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 #考慮到最後一個data要給模型預測用
        i = min_index + lookback
        while 1:
            if shuffle:
                """給定一組數列,來決定之後的samples,shuffle=True則給定random數列。
                np.random.radint(1,10,5) => (1,4,5,4,8) 在1~10之間隨意抓取五次數值
                所以len(rows) 由最後的size決定,len(rows) = batch_size"""
                rows = np.random.randint( min_index + lookback, max_index, size=batch_size)
            else:
                if i + batch_size >= max_index:  #防止i+batch_size後停止while loop
                    i = min_index + lookback    #讓i回到原點
                rows = np.arange(i, min(i + batch_size, max_index))
                i += len(rows)
            samples = np.zeros((len(rows), lookback // step, data.shape[-1]))
            #產生訓練samples 形狀為(128,1440/6=240,14)
            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)

    來看看generator 的批量產出
    >>>next(train_gen)
    generator 每次產出128批資料,每一批含240個(10天的資料)數據list,每組資料共14個不同的量,也就是形狀(128,240,14)的numpy array。

    接著定義一下驗證&測試批量
    val_steps = (300000 - 200001 - lookback) //batch_size
    test_steps = (len(float_data) - 300001 - lookback) //batch_size
  • 在定義模型之前,先假設baseline,再定義模型看效能是否優於baseline。
    這邊假設,由於天氣變化的連續性,隔天的溫度近乎今天的溫度,所以我們可以將未來24小時候的溫度等於現在的溫度來做預測,我們使用平均絕對誤差(MAE)指標來評估這種方法,np.mean(np.abs(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()
    得到 0.2897359729905486
    轉換為攝氏  celsius_mae = 0.2897359729905486 * std[1] = 2.564887434980494
    誤差約為2.56度C。
  • 定義模型,先練習全連接層
    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))
  • 選擇優化器、損失函數,丟進fit_gernerator訓練。
    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)
  • 畫出訓練過程
    import matplotlib.pyplot as plt
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    epochs = range(1, len(loss) + 1)
    plt.figure()
    plt.plot(epochs, loss, 'go', label='Training loss')
    plt.plot(epochs, val_loss, 'g+', label='Validation loss')
    plt.title('Training and validation loss')
    plt.legend()
    plt.show()
    val_loss 大概在0.35左右,明顯得比 baseline 差許多,這是因為將一個有時間依賴性的序列用全連接層攤平訓練是一個不明智的做法,也許我們應該在這個問題上使用循環神經網絡來訓練模型。

  • 使用循環網絡之前,先嘗試使用運算代價較低的GRU而不是直接使用LSTM來定義模型,來看看初步的GRU是否有比baseline來的好。
    修改模型:
    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))
    利用GRU後,明顯的比使用全連接層來的好,MAE誤差最好有0.26左右,但在第五輪後開始出現過擬合現象,我們需要來嘗試降低過擬合。

  • 在循環層中加入Dropout ,Keras的每個循環層都有兩個與dropout 相關的參數,一個是dropout:是一個浮點數,指定該層輸入單元的dropout 比率;另一個是recurrent_dropout,指定循環單元的 dropout 比率。
    這邊要注意的是,在循環層單元中加入的dropout 是對每個時間步應該使用相同的dropout 掩碼(dropout mask,相同模式的捨棄單元),而不是讓dropout 掩碼隨著時間步的增加而隨機變化,因為每個時間步使用相同的dropout 掩碼,可以讓網絡沿著時間正確地傳播其學習誤差,而隨時間隨機變化的dropout 掩碼則會破壞這個誤差信號,並且不利於學習過程 Yarin Gal.al,"Uncertainty in deep learning"
    在Keras中簡單地就能加入循環層dropout:(1060的GPU跑了約1個小時...)
    from keras.models import Sequential
    from keras import layers
    from keras.optimizers import RMSprop
    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))
    加入dropout後,成功降低過擬合的現象,MAE誤差也大概在0.26左右,接著嘗試使用其他優化方法。
  • 使用堆疊循環層,我們可以考慮增加網絡容量,只要過擬合的問題不是很嚴重的話,可以嘗試增加網絡容量來增加模型性能。
    在Keras中增加堆疊循環層時,所有中間層都應該返回完整的輸出序列(一個 3D 張量),而不是只返回最後一個時間步的輸出。這可以通過指定 return_sequences=True 來實現:
    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))
    添加一層後模型並沒有顯著改進,提高網絡能力的回報在逐漸減小。
  • 嘗試其它優化 ex.雙向RNN(GRU, LSTM...),這邊因為跑太久,直接給結論,在此例中因為最靠近預測溫度的時間的資料包含的訊息可能較為重要,所以在反向RNN的表現會非常的差()。
    在使用雙向RNN時,較多精準預測的權重偏向正向RNN,即反向RNN是沒什麼參考價值,甚至可以說是浪費運算資源。
    但是,在自然語言工程(NLP)使用RNN是非常有幫助的,就好像文順字序不響讀影閱一樣。














沒有留言:

張貼留言