使用RNN和CNN混合的’雞尾酒療法’,提升網絡對文本的識別正確率

前幾節我們詳細研究了GRU和LSTM網絡層,這兩者特點是能夠抓取輸入數據在時間上的邏輯聯繫,因此這兩種網絡特別容易從文本中抓取規律,因爲文本是有一個個單詞依據前後次序連接起來的整體,單詞與單詞之間的連接可以看做是時間上前後相連的組合,因此使用GRU和LSTM構成的網絡來進行文本的情緒分析時,正確率能高達90%。

然而GRU和LSTM網絡存在一個非常嚴重的問題,因爲他們要計算輸入數據的前後關聯,因此運行這兩種網絡時需要消耗大量內存,同時也損耗大量的運算時間,特別是當他們運行在CPU上時,時間損耗更是令人難以接受,這一節我們看看,能否在不嚴重降低準確率的情況下,有效的提升網絡的處理速度。

在前幾節我們提到過卷積網絡,它專門用於識別圖片。它的做法是將二維圖片切成若干個3*3的小塊,分別識別這些小塊,最後把這些識別結果綜合起來得到對正個圖片的識別。其實我們也可以使用卷積網絡來識別文本序列,文本相對於圖片是一維的,但識別方法可以沿用,那就是把一維的文本序列切割成多個小片,讓網絡分別識別每個小片,然後再把多個小片段的識別結果綜合起來得到對整篇文本的識別。Keras框架提供了識別2維數據的卷積網絡層Con2D和MaxPooling2D,它同時也提供了識別1維數據的卷積網絡層Conv1D和MaxPooling1D,我們使用1維的卷積網絡來識別文本序列,看看能得到什麼效果。首先我們先把文本數據加載進來,代碼如下:

from keras.datasets import imdb
from keras.preprocessing import sequence

max_features = 10000
max_len = 500
print('Loading data...')
(x_train, y_train),(x_test, y_test)=imdb.load_data(num_words=max_features)
print(len(x_train), 'train sequences')
print(len(x_test), 'test sequences')

print('Pad sequences (samples x time)')
x_train = sequence.pad_sequences(x_train, maxlen=max_len)
x_test = sequence.pad_sequences(x_test, maxlen=max_len)
print('x_train shape:', x_train.shape)
print('x_test shape:', x_test.shape)

上面代碼把keras框架自帶的影評文本數據加載到內存中,接下來我們可以構造一個含有1維卷積層的網絡來識別影評文本中的情緒,代碼如下:

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

model = Sequential()
model.add(layers.Embedding(max_features, 128, input_length=maxlen))
model.add(layers.Conv1D(32, 7, activation='relu'))
model.add(layers.MaxPooling1D(5))
model.add(layers.Conv1D(32, 7, activation='relu'))
model.add(layers.GlobalMaxPooling1D())
model.add(layers.Dense(1))

model.summary()

上面代碼需要注意的是語句:layers.Conv1D(32, 7, activation=’relu’),其中的7表示把文本序列中,每7個單詞當做一個切片進行識別,這與前面我們把二維圖片中美3*3個像素作爲識別單位是同一個道理,同時識別的結果用含有32個元素的一維向量表示,代碼構建的網絡含有兩個卷積層,最後一層值含有一個節點,用來輸出文本屬於哪種情緒,運行上面代碼後,我們把識別結果繪製出來,代碼如下:

import matplotlib.pyplot as plt

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.legend()
plt.figure()
plt.show()

上面代碼運行後,結果如下:

從上圖看出,使用1維卷積網絡識別文本所得準確率爲85%,上一節我們使用LSTM得到的準確率是89%,這次的準確率有所下降,但是程序的運行速度相比於LSTM可是有幾十倍的提升!由此看來使用一維卷積網絡來識別文本使得準確率有所下降,但隨着程序運行速度的極大提升,從而帶來了非常有吸引力的性價比。

既然CNN能提升速度,RNN能提升準確率,如果把兩者混合起來是不是能獲得兩者的效用呢。前面使用的CNN之所以準確率不如LSTM,原因在於它對數據沒有“記憶性”,它把單詞序列切成多個小塊分別處理,但是它無法抽取出這些小塊之間存在的邏輯聯繫,顯然這些有文本構成的小塊之間是存在很強的邏輯聯繫的,後抽取這些邏輯聯繫是LSTM這裏”記憶性“網絡的特長。

爲了能夠兩者兼備,我們可以構造一種混合動力型網絡,首先我們把網絡分成兩個層次,第一層是CNN層,它先將輸入數據切分成多個小段,然後對這些小段進行識別,由於前面輸入CNN的數據足夠長,CNN即使把輸入數據切分成多個小段後,”小段“的數量依然不少。此時爲了能夠把握“小段”之間存在的邏輯聯繫,我們把這些小段輸入到LSTM網絡,讓後者抓取他們之間存在的關聯。我們把這種網絡結構應用到上一節描述的天氣預測數據上看看效果如何。構造網絡的相應代碼如下:

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

model = Sequential()
#在頂層加1個卷積網絡
model.add(layers.Conv1D(32, 5, activation='relu', 
                        input_shape=(None, float_data.shape[-1])))
model.add(layers.MaxPooling1D(3))
#添加一個有記憶性的GRU層
model.add(layers.GRU(32, dropout=0.1, recurrent_dropout=0.5))
model.add(layers.Dense(1))
model.summary()

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)

上面代碼運行後,我們將結果繪製出來如下:

從上圖看,網絡對預測的誤差率最好時差不多是0.265左右,比上一節使用LSTM網絡的誤差率0.26稍微差了那麼一點點,但是速度快了不止幾十倍,由此看來使用兩種類型的網絡混合所得結果的性價比非常划算。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章