在「卷積神經網絡」中我們探究了如何使用二維卷積神經網絡來處理二維圖像數據。在之前的語言模型和文本分類任務中,我們將文本數據看作是只有一個維度的時間序列,並很自然地使用循環神經網絡來表征這樣的數據。其實,我們也可以將文本當作一維圖像,從而可以用一維卷積神經網絡來捕捉臨近詞之間的關聯。
TextCNN 是利用卷積神經網絡對文本進行分類的算法,由 Yoon Kim 在 《 Convolutional Neural Networks for Sentence Classification 》中提出。
TextCNN結構圖:
第一層將單詞嵌入到低維矢量中。下一層使用多個過濾器大小對嵌入的單詞向量執行卷積。例如,一次滑動3,4或5個單詞。接下來,將卷積層的結果最大池化為一個長特徵向量,添加dropout正則,並使用softmax對結果進行分類。與傳統圖像的CNN網絡相比, textCNN 在網絡結構上沒有任何變化(甚至更加簡單了), 從圖中可以看出textCNN 其實只有一層卷積,一層max-pooling, 最後將輸出外接softmax 來n分類。
與圖像當中CNN的網絡相比,textCNN 最大的不同便是在輸入數據的不同:圖像是二維數據, 圖像的卷積核是從左到右, 從上到下進行滑動來進行特徵抽取。自然語言是一維數據, 雖然經過word-embedding 生成了二維向量,但是對詞向量做從左到右滑動來進行卷積沒有意義. 比如 「今天」 對應的向量[0, 0, 0, 0, 1], 按窗口大小為 1* 2 從左到右滑動得到[0,0], [0,0], [0,0], [0, 1]這四個向量, 對應的都是」今天」這個詞彙, 這種滑動沒有幫助.
TextCNN的成功, 不是網絡結構的成功, 而是通過引入已經訓練好的詞向量來在多個數據集上達到了超越benchmark 的表現,進一步證明了構造更好的embedding, 是提升nlp 各項任務的關鍵能力。
TextCNN最大優勢網絡結構簡單 ,在模型網絡結構如此簡單的情況下,通過引入已經訓練好的詞向量依舊有很不錯的效果,在多項數據數據集上超越benchmark。 網絡結構簡單導致參數數目少, 計算量少, 訓練速度快,在單機單卡的v100機器上,訓練165萬數據, 疊代26萬步,半個小時左右可以收斂。
textcnn使用預先訓練好的詞向量作embedding layer。對於數據集裡的所有詞,因為每個詞都可以表征成一個向量,因此我們可以得到一個嵌入矩陣M, M里的每一行都是詞向量。這個M可以是靜態(static)的,也就是固定不變。可以是非靜態(non-static)的,也就是可以根據反向傳播更新。
如圖所示, textCNN 首先將 「今天天氣很好,出來玩」 分詞成」今天/天氣/很好/,/出來/玩, 通過word2vec或者GLOV 等embedding 方式將每個詞成映射成一個5維(維數可以自己指定)詞向量, 如 「今天」 -> [0,0,0,0,1], 「天氣」 ->[0,0,0,1,0], 「很好」 ->[0,0,1,0,0]等等。
這樣做的好處主要是將自然語言數值化,方便後續的處理。從這裡也可以看出不同的映射方式對最後的結果是會產生巨大的影響, nlp 當中目前最火熱的研究方向便是如何將自然語言映射成更好的詞向量。我們構建完詞向量後,將所有的詞向量拼接起來構成一個6*5的二維矩陣,作為最初的輸入。
輸入一個句子,首先對這個句子進行切詞,假設有s個單詞。對每個詞,跟句嵌入矩陣M, 可以得到詞向量。假設詞向量一共有d維。那麼對於這個句子,便可以得到s行d列的矩陣
。我們可以把矩陣A看成是一幅圖像,使用卷積神經網絡去提取特徵。由於句子中相鄰的單詞關聯性總是很高的,因此可以使用一維卷積。卷積核的寬度就是詞向量的維度d,高度是超參數,可以設置。
假設有一個卷積核,是一個寬度為d,高度為h的矩陣w,那麼w有h∗d個參數需要被更新。對於一個句子,經過嵌入層之後可以得到矩陣
。
表示A的第i行到第j行,那麼卷積操作可以用如下公式表示:
疊加上偏置b,在使用激活函數f激活, 得到所需的特徵。公式如下:
對一個卷積核,可以得到特徵
, 總共個特徵。我們可以使用更多高度不同的卷積核,得到更豐富的特徵表達。
卷積是一種數學運算元。我們用一個簡單的例子來說明一下
feature_map 便是卷積之後的輸出, 通過卷積操作 將輸入的6*5 矩陣映射成一個 3*1 的矩陣,這個映射過程和特徵抽取的結果很像,於是便將最後的輸出稱作feature map。一般來說在卷積之後會跟一個激活函數,在這裡為了簡化說明需要,我們將激活函數設置為f(x) = x
在CNN 中常常會提到一個詞channel, 圖中深紅矩陣與淺紅矩陣 便構成了兩個channel 統稱一個卷積核, 從這個圖中也可以看出每個channel 不必嚴格一樣, 每個4*5 矩陣與輸入矩陣做一次卷積操作得到一個feature map. 在計算機視覺中,由於彩色圖像存在 R, G, B 三種顏色, 每個顏色便代表一種channel。根據原論文作者的描述, 一開始引入channel 是希望防止過擬合(通過保證學習到的vectors 不要偏離輸入太多)來在小數據集合獲得比單channel更好的表現,後來發現其實直接使用正則化效果更好。不過使用多channel 相比與單channel, 每個channel 可以使用不同的word embedding, 比如可以在no-static(梯度可以反向傳播) 的channel 來fine tune 詞向量,讓詞向量更加適用於當前的訓練。 對於channel在textCNN 是否有用, 從論文的實驗結果來看多channels並沒有明顯提升模型的分類能力, 七個數據集上的五個數據集 單channel 的textCNN 表現都要優於 多channels的textCNN。
我們在這裡也介紹一下論文中四個model 的不同:
不同尺寸的卷積核得到的特徵(feature map)大小也是不一樣的,因此我們對每個feature map使用池化函數,使它們的維度相同。最常用的就是1-max pooling,提取出feature map照片那個的最大值。這樣每一個卷積核得到特徵就是一個值,對所有卷積核使用1-max pooling,再級聯起來,可以得到最終的特徵向量,這個特徵向量再輸入softmax layer做分類。這個地方可以使用drop out防止過擬合。
得到feamap = [1,1,2] 後, 從中選取一個最大值[2] 作為輸出, 便是max-pooling。max-pooling 在保持主要特徵的情況下, 大大降低了參數的數目, 從圖中可以看出 feature map 從 三維變成了一維, 好處有如下兩點:
pooling 本身無法帶來平移不變性(圖片有個字母A, 這個字母A 無論出現在圖片的哪個位置, 在CNN的網絡中都可以識別出來),卷積核的權值共享才能。max-pooling的原理主要是從多個值中取一個最大值,做不到這一點。cnn 能夠做到平移不變性,是因為在滑動卷積核的時候,使用的卷積核權值是保持固定的(權值共享), 假設這個卷積核被訓練的就能識別字母A, 當這個卷積核在整張圖片上滑動的時候,當然可以把整張圖片的A都識別出來。
如圖所示, 我們將 max-pooling的結果拼接起來, 送入到softmax當中, 得到各個類別比如 label 為1 的機率以及label 為-1的機率。如果是預測的話,到這裡整個textCNN的流程遍結束了。如果是訓練的話,此時便會根據預測label以及實際label來計算損失函數, 計算出softmax 函數,max-pooling 函數, 激活函數以及卷積核函數 四個函數當中參數需要更新的梯度, 來依次更新這四個函數中的參數,完成一輪訓練。
以上過程可以用下圖直觀表示:
在自然語言領域,卷積的作用在於利用文字的局部特徵。一個詞的前後幾個詞必然和這個詞本身相關,這組成該詞所代表的詞群。詞群進而會對段落文字的意思進行影響,決定這個段落到底是正向的還是負向的。對比傳統方法,利用詞包,和TF-IDF 等,其思想有相通之處。但最大的不同點在於,傳統方法是人為構造用於分類的特徵,而深度學習中的卷積讓神經網絡去構造特徵。以上便是卷積在自然語言處理中有著廣泛應用的原因。接下來介紹如何利用Keras 搭建卷積神經網絡來處理情感分析的分類問題。
下面的代碼構造了卷積神經網絡的結構:
from keras.layers import Dense, Dropout, Activation, Flattenfrom keras.layers import Conv1D, MaxPooling1Dfrom keras.models import Sequentialfrom keras.layers.embeddings import Embeddingfrom keras.datasets import imdbimport numpy as npfrom keras.preprocessing import sequence(X_train, y_train), (X_test, y_test) = imdb.load_data()max_word = 400X_train = sequence.pad_sequences(X_train, maxlen=max_word)X_test = sequence.pad_sequences(X_test, maxlen=max_word)vocab_size = np.max([np.max(X_train[i]) for i in range(X_train.shape[0])]) + 1# 這裡1 代表空格,其索引被認為是0。model = Sequential()model.add(Embedding(vocab_size, 64, input_length=max_word))model.add(Conv1D(filters=64, kernel_size=3, padding='same', activation='relu'))model.add(MaxPooling1D(pool_size=2))model.add(Dropout(0.25))model.add(Conv1D(filters=128, kernel_size=3, padding='same', activation='relu'))model.add(MaxPooling1D(pool_size=2))model.add(Dropout(0.25))model.add(Flatten())model.add(Dense(64, activation='relu'))model.add(Dense(32, activation='relu'))model.add(Dense(1, activation='sigmoid'))model.compile(loss='binary_crossentropy', optimizer='rmsprop', metrics=['accuracy'])print(model.summary())model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=20, batch_size=100)scores = model.evaluate(X_test, y_test, verbose=1)print(scores)
整個模型的結構如下:
Model: "sequential_1"_________________________________________________________________Layer (type) Output ShapeParam # =================================================================embedding_1 (Embedding)(None, 400, 64) 5669568 _________________________________________________________________conv1d_1 (Conv1D)(None, 400, 64) 12352 _________________________________________________________________max_pooling1d_1 (MaxPooling1 (None, 200, 64) 0 _________________________________________________________________dropout_1 (Dropout)(None, 200, 64) 0 _________________________________________________________________conv1d_2 (Conv1D)(None, 200, 128)24704 _________________________________________________________________max_pooling1d_2 (MaxPooling1 (None, 100, 128)0 _________________________________________________________________dropout_2 (Dropout)(None, 100, 128)0 _________________________________________________________________flatten_1 (Flatten)(None, 12800) 0 _________________________________________________________________dense_1 (Dense)(None, 64)819264_________________________________________________________________dense_2 (Dense)(None, 32)2080_________________________________________________________________dense_3 (Dense)(None, 1) 33=================================================================Total params: 6,528,001Trainable params: 6,528,001Non-trainable params: 0_________________________________________________________________
在最簡單的僅一層卷積的TextCNN結構中,下面的超參數都對模型表現有影響: