接著我們看看LSTM網(wǎng)絡更復雜的運用,那就是用來預測氣溫。在這個例子中,我們可以使用很多高級數(shù)據(jù)處理功能,例如我們可以看到如何使用"recurrent dropout"來預防過度擬合,第二我們會把多個LTSM網(wǎng)絡層堆積起來,增強怎個網(wǎng)絡的解析能力,第三我們還會使用到雙向反復性網(wǎng)絡,它會把同一種信息以不同的形式在網(wǎng)絡內(nèi)傳遞,從而增加網(wǎng)絡對信息的理解。
我們首先要做的重要一步,那就是獲取數(shù)據(jù),打開迅雷,輸入下面URL以便下載原始數(shù)據(jù):
https://s3.amazonaws.com/keras-datasets/jena_climate_2009_2016.csv.zip
下載后解壓,我們就可以用代碼將其加載到內(nèi)存中:
import os
data_dir = '/Users/chenyi/Documents/人工智能/'
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))
上面代碼運行后結(jié)果如下:
['"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
數(shù)據(jù)總共有420551個條目,每個條目的內(nèi)容如上面顯示所示。接下來我們把所有條目轉(zhuǎn)換成可以被處理的數(shù)組格式:
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)
上面代碼轉(zhuǎn)換數(shù)據(jù)后,將數(shù)據(jù)描繪出來,結(jié)果如下:

從上圖可以看出,溫度呈現(xiàn)一種周期性的變化。由于數(shù)據(jù)記錄了2009年到2016年間每天的溫度變化,并且溫度記錄是每十分鐘完成一次,于是一天就有144個記錄點,我們截取10天的溫度信息,也就是總共1440個數(shù)據(jù)點,我們把這些數(shù)據(jù)圖繪制出來看看:
plt.plot(range(1440), temp[:1440])

我們將構(gòu)造一個網(wǎng)絡,分析當前的天氣數(shù)據(jù),然后預測未來天氣的可能狀況。當前數(shù)據(jù)格式比較理想,問題在于不同數(shù)據(jù)對應的單位不一樣,例如氣溫和氣壓采用不同的計量單位,因此我們需要對數(shù)據(jù)做歸一化處理。我們打算使用前200000個數(shù)據(jù)條目做訓練數(shù)據(jù),因此我們僅僅對這部分數(shù)據(jù)進行歸一化:
mean = float_data[:200000].mean(axis=0)
float_data -= mean
std = float_data[:200000].std(axis=0)
float_data /= std
接著我們用代碼把數(shù)據(jù)分成三組,一組用來訓練網(wǎng)絡,一組作為校驗數(shù)據(jù),最后一組作為測試數(shù)據(jù):
'''
假設現(xiàn)在是1點,我們要預測2點時的氣溫,由于當前數(shù)據(jù)記錄的是每隔10分鐘時的氣象數(shù)據(jù),1點到2點
間隔1小時,對應6個10分鐘,這個6對應的就是delay
要訓練網(wǎng)絡預測溫度,就需要將氣象數(shù)據(jù)與溫度建立起對應關(guān)系,我們可以從1點開始倒推10天,從過去
10天的氣象數(shù)據(jù)中做抽樣后,形成訓練數(shù)據(jù)。由于氣象數(shù)據(jù)是每10分鐘記錄一次,因此倒推10天就是從
當前時刻開始回溯1440條數(shù)據(jù),這個1440對應的就是lookback
我們無需把全部1440條數(shù)據(jù)作為訓練數(shù)據(jù),而是從這些數(shù)據(jù)中抽樣,每隔6條取一條,
因此有1440/6=240條數(shù)據(jù)會作為訓練數(shù)據(jù),這就是代碼中的lookback//step
于是我就把1點前10天內(nèi)的抽樣數(shù)據(jù)作為訓練數(shù)據(jù),2點是的氣溫作為數(shù)據(jù)對應的正確答案,由此
可以對網(wǎng)絡進行訓練
'''
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 samples, targets
有了上面的數(shù)據(jù)處理函數(shù),我們就可以調(diào)用它將原始數(shù)據(jù)分成三組,一組用于訓練,一組用于校驗,最后一組用于測試,代碼如下:
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=400000,
step=step, batch_size=batch_size)
val_steps = (300000 - 200001 - lookback) // batch_size
test_steps = (len(float_data) - 300001 - lookback) //batch_size
神經(jīng)網(wǎng)絡要有效,它就必須做的比人預測的準確度高。對于溫度預測而言,人本能直覺會認為溫度具有連續(xù)性,也就是下一刻的溫度應該會比當前時刻的溫度差別不了多少,根據(jù)人的經(jīng)驗,不可能現(xiàn)在溫度是20度,然后下一刻溫度里面變成60度,或零下20度,只有世界末日才會出現(xiàn)這樣的情形,于是如果要讓人來預測,他通常會認為接下24小時內(nèi)的溫度與當前溫度是一樣的,基于這種邏輯,我們計算一下這種預測方法的準確度:
def evaluate_naive_method():
batch_maes = []
for step in range(val_steps):
samples, targets = next(val_gen)
#preds是當前時刻溫度,targets是下一小時溫度
preds = samples[:, -1, 1]
mae = np.mean(np.abs(preds - targets))
batch_maes.append(mae)
print(np.mean(batch_maes))
evaluate_naive_method()
上面代碼運行后得到結(jié)果為0.29,注意到前面我們對數(shù)據(jù)的每一列都做了歸一化處理,因此0.29的解釋應該是0.29*std[1]=2.57,也就是說如果我們用前一個小時的溫度來預測下一個小時的溫度,那么誤差是2.57度,這個誤差不算小。如果我們的網(wǎng)絡要真有效,那么它預測的溫度誤差應該比2.57要小,小得越多就越有效。
為了比較不同網(wǎng)絡模型的效果,我們將分別構(gòu)造幾個不同類別的網(wǎng)絡然后分別看看他們的預測效果,首先我們先建立前面幾章講過的全連接網(wǎng)絡看看效果如何:
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))
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)
我們把上面代碼運行結(jié)果繪制出來看看:
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, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and Validation loss')
plt.show()
上面代碼運行后結(jié)果如下:

從上圖看,網(wǎng)絡預測的結(jié)果在0.4和0.3之間,也就是說網(wǎng)絡預測的精確度還不如人的直覺好。當前網(wǎng)絡存在一個問題,就是它把有時間次序的數(shù)據(jù)條目一下子碾平,從而使得數(shù)據(jù)之間的時間聯(lián)系消失了,可是時間信息對結(jié)果的預測非常重要,如果像上面做法,先把多條數(shù)據(jù)集合在一起傳入網(wǎng)絡,就會使得數(shù)據(jù)的時間特性消失,而時間是影響判斷結(jié)果的一個重要變量。
這回我們使用反復性神經(jīng)網(wǎng)絡,因為這樣的網(wǎng)絡能夠利用數(shù)據(jù)間存在的時間聯(lián)系來分析數(shù)據(jù)潛在規(guī)律進而提升預測的準確性,這次我們使用的反復性網(wǎng)絡叫GRU,它是LSTM的變種,兩者基本原理一樣,只不過前者是對后者的優(yōu)化,使得在訓練時效率能夠加快,我們看看相關(guān)代碼:
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)
代碼運行后,我們繪制出來的訓練效果如下圖:

從上圖我們看到,藍色實線在循環(huán)次數(shù)為4的時候,網(wǎng)絡對校驗數(shù)據(jù)判斷的誤差達到了接近0.26,這已經(jīng)遠遠好于由人的直覺猜測的0.29錯誤率,這次改進相當明顯。這次改進顯示出深度學習對數(shù)據(jù)模式的抽取能力比人的直覺要好很多,同時也表明反復性網(wǎng)絡對數(shù)據(jù)的識別能力要好于我們以前開發(fā)的全連接網(wǎng)絡。0.26轉(zhuǎn)換為錯誤值大概是2.3左右,也就是網(wǎng)絡對一小時后的溫度預測與實際值相差2.3度。
從上圖我們也看出,網(wǎng)絡對訓練數(shù)據(jù)的識別準確率不斷提升,對校驗數(shù)據(jù)的識別準確率越來越差,兩種分道揚鑣很明顯,也就是說網(wǎng)絡出現(xiàn)了過度擬合。以前我們處理過度擬合時的辦法是把權(quán)重隨機清零,但是這種方式不能直接使用到反復性網(wǎng)絡上,因為網(wǎng)絡中很多鏈路權(quán)重在用于記錄不同數(shù)據(jù)在時間上的內(nèi)在關(guān)聯(lián),如果隨機把這些權(quán)重清零,就會破壞網(wǎng)絡對數(shù)據(jù)在時間上關(guān)聯(lián)性的認識,從而造成準確率的下降。在2015年時研究貝葉斯深度學習的博士生Yarin Gal 發(fā)現(xiàn)了處理反復性網(wǎng)絡過度擬合的方法,那是每次都將同樣的若干比例權(quán)重清零,而不是隨機清零,而這種清零機制內(nèi)內(nèi)嵌在keras框架中。相關(guān)代碼如下:
model = Sequential()
model.add(layers.GRU(32, dropout=0.2, recurrent_dropout=0.2,
input_shape=(None, float_data.shape[-1])))
model.add(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)
上面代碼運行后,網(wǎng)絡的訓練效果如下:

從上圖實現(xiàn)和點線的發(fā)展趨勢不斷重合,也就是網(wǎng)絡對校驗數(shù)據(jù)的識別正確率跟訓練數(shù)據(jù)的正確率一樣不斷提高,因此過度擬合的現(xiàn)象消失了。至此我們就把LSTM和GRU這兩種反復性網(wǎng)絡在具體實例上的應用展示完成,如果你運行過上面代碼會發(fā)現(xiàn),普通CPU的機子運行代碼起來效率很慢,它再次證明了算力和數(shù)據(jù)是人工智能中兩道極難邁過去的坎兒。
更多技術(shù)信息,包括操作系統(tǒng),編譯器,面試算法,機器學習,人工智能,請關(guān)照我的公眾號: