情感分析之卷積神經網絡(TextCNN)

sandag 發佈 2020-01-01T16:36:35+00:00

自然語言是一維數據,雖然經過word-embedding 生成了二維向量,但是對詞向量做從左到右滑動來進行卷積沒有意義. 比如 「今天」 對應的向量[0, 0, 0, 0, 1], 按窗口大小為 1* 2 從左到右滑動得到[0,0], [0,0], [0,0], [0, 1]這四

在「卷積神經網絡」中我們探究了如何使用二維卷積神經網絡來處理二維圖像數據。在之前的語言模型和文本分類任務中,我們將文本數據看作是只有一個維度的時間序列,並很自然地使用循環神經網絡來表征這樣的數據。其實,我們也可以將文本當作一維圖像,從而可以用一維卷積神經網絡來捕捉臨近詞之間的關聯。

TextCNN簡介

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 流程

Word Embedding 分詞構建詞向量

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的二維矩陣,作為最初的輸入。

Convolution 卷積

輸入一個句子,首先對這個句子進行切詞,假設有s個單詞。對每個詞,跟句嵌入矩陣M, 可以得到詞向量。假設詞向量一共有d維。那麼對於這個句子,便可以得到s行d列的矩陣

。我們可以把矩陣A看成是一幅圖像,使用卷積神經網絡去提取特徵。由於句子中相鄰的單詞關聯性總是很高的,因此可以使用一維卷積。卷積核的寬度就是詞向量的維度d,高度是超參數,可以設置。

假設有一個卷積核,是一個寬度為d,高度為h的矩陣w,那麼w有h∗d個參數需要被更新。對於一個句子,經過嵌入層之後可以得到矩陣

表示A的第i行到第j行,那麼卷積操作可以用如下公式表示:

疊加上偏置b,在使用激活函數f激活, 得到所需的特徵。公式如下:

對一個卷積核,可以得到特徵

, 總共個特徵。我們可以使用更多高度不同的卷積核,得到更豐富的特徵表達。

卷積是一種數學算子。我們用一個簡單的例子來說明一下

  • 1 將 「今天」/」天氣」/」很好」/」,」 對應的4*5 矩陣 與卷積核做一個point wise 的乘法然後求和, 便是卷積操作
  • 2 將窗口向下滑動一格(滑動的距離可以自己設置),」天氣」/」很好」/」,」/」出來」 對應的4*5 矩陣 與卷積核(權值不變) 繼續做point wise 乘法後求和
  • 3 將窗口向下滑動一格(滑動的距離可以自己設置) 「很好」/」,」/」出來」/」玩」 對應的4*5 矩陣 與卷積核(權值不變) 繼續做point wise 乘法後求和

feature_map 便是卷積之後的輸出, 通過卷積操作 將輸入的6*5 矩陣映射成一個 3*1 的矩陣,這個映射過程和特徵抽取的結果很像,於是便將最後的輸出稱作feature map。一般來說在卷積之後會跟一個激活函數,在這裡為了簡化說明需要,我們將激活函數設置為f(x) = x

channel

在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 的不同:

  • CNN-rand (單channel), 設計好 embedding_size 這個 Hyperparameter 後, 對不同單詞的向量作隨機初始化, 後續BP的時候作調整.
  • CNN-static(單channel), 拿 pre-trained vectors from word2vec, FastText or GloVe 直接用, 訓練過程中不再調整詞向量.
  • CNN-non-static(單channel), pre-trained vectors + fine tuning , 即拿word2vec訓練好的詞向量初始化, 訓練過程中再對它們微調.
  • CNN-multiple channel(多channels), 類比於圖像中的RGB通道, 這裡也可以用 static 與 non-static 搭兩個通道來做.

Pooling 池化

不同尺寸的卷積核得到的特徵(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 從 三維變成了一維, 好處有如下兩點:

  • 降低了過擬合的風險, feature map = [1, 1, 2] 或者[1, 0, 2] 最後的輸出都是[2], 表明開始的輸入即使有輕微變形, 也不影響最後的識別。
  • 參數減少, 進一步加速計算。

pooling 本身無法帶來平移不變性(圖片有個字母A, 這個字母A 無論出現在圖片的哪個位置, 在CNN的網絡中都可以識別出來),卷積核的權值共享才能。max-pooling的原理主要是從多個值中取一個最大值,做不到這一點。cnn 能夠做到平移不變性,是因為在滑動卷積核的時候,使用的卷積核權值是保持固定的(權值共享), 假設這個卷積核被訓練的就能識別字母A, 當這個卷積核在整張圖片上滑動的時候,當然可以把整張圖片的A都識別出來。

使用softmax k分類

如圖所示, 我們將 max-pooling的結果拼接起來, 送入到softmax當中, 得到各個類別比如 label 為1 的機率以及label 為-1的機率。如果是預測的話,到這裡整個textCNN的流程遍結束了。如果是訓練的話,此時便會根據預測label以及實際label來計算損失函數, 計算出softmax 函數,max-pooling 函數, 激活函數以及卷積核函數 四個函數當中參數需要更新的梯度, 來依次更新這四個函數中的參數,完成一輪訓練。

總結

以上過程可以用下圖直觀表示:

  • 這裡word embedding的維度是5。對於句子 i like this movie very much。可以轉換成如上圖所示的矩陣
  • 有6個卷積核,尺寸為(2×5)(2×5), (3×5)(3×5), 4×54×5,每個尺寸各2個.
  • AA分別與以上卷積核進行卷積操作,再用激活函數激活。每個卷積核都得到了特徵向量(feature maps)
  • 使用1-max pooling提取出每個feature map的最大值,然後在級聯得到最終的特徵表達。
  • 將特徵輸入至softmax layer進行分類, 在這層可以進行正則化操作( l2-regulariation)

使用Keras 搭建卷積神經網絡來進行情感分析

在自然語言領域,卷積的作用在於利用文字的局部特徵。一個詞的前後幾個詞必然和這個詞本身相關,這組成該詞所代表的詞群。詞群進而會對段落文字的意思進行影響,決定這個段落到底是正向的還是負向的。對比傳統方法,利用詞包,和TF-IDF 等,其思想有相通之處。但最大的不同點在於,傳統方法是人為構造用於分類的特徵,而深度學習中的卷積讓神經網絡去構造特徵。以上便是卷積在自然語言處理中有著廣泛應用的原因。接下來介紹如何利用Keras 搭建卷積神經網絡來處理情感分析的分類問題。

下面的代碼構造了卷積神經網絡的結構:

from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv1D, MaxPooling1D
from keras.models import Sequential
from keras.layers.embeddings import Embedding
from keras.datasets import imdb
import numpy as np
from keras.preprocessing import sequence
 
(X_train, y_train), (X_test, y_test) = imdb.load_data()
 
max_word = 400
X_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 Shape              Param #   
=================================================================
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,001
Trainable params: 6,528,001
Non-trainable params: 0
_________________________________________________________________

TextCNN的超參數調參

在最簡單的僅一層卷積的TextCNN結構中,下面的超參數都對模型表現有影響:

  • 初始化詞向量。使用word2vec和golve都可以,不要使用one-hot vectors
  • 卷積核的尺寸。1-10之間,具體情況具體分析,對最終結果影響較大。一般來講,句子長度越長,卷積核的尺寸越大。另外,可以在尋找到了最佳的單個filter的大小後,嘗試在該filter的尺寸值附近尋找其他合適值來進行組合。實踐證明這樣的組合效果往往比單個最佳filter表現更出色
  • 每種尺寸卷積核的數量。100-600之間,對模型性能影響較大,需要注意的是增加卷積核的數量會增加訓練模型的實踐。主要考慮的是當增加特徵圖個數時,訓練時間也會加長,因此需要權衡好。當特徵圖數量增加到將性能降低時,可以加強正則化效果,如將dropout率提高過5
  • 激活函數的選擇。使用relu函數
  • drop out rate。0-0.5, 當增加卷積核的數量時,可以嘗試增加drop out rate,甚至可以大於0.5
  • 池化的選擇。1-max pooling表現最佳
  • 正則項。相對於其他超參數來說,影響較小點
關鍵字: