CNN文本分類原理講解與實戰

前言

卷積神經網絡主要用來做圖片分類、目標檢測等圖像相關的任務,這篇文章介紹了它在NLP中的應用:文本分類。本文先介紹了CNN,然後分析了CNN爲什麼能用在NLP中,最後講解了Yoon Kim (2014)提出的CNN文本分類模型,代碼見github


什麼是卷積

簡單介紹一下卷積運算,卷積運算作用就是用濾波器來學習或者檢測圖片的特徵。

看上圖,左邊是一張5×5的黑白圖片,現在是矩陣的形式,每個格子代表一個像素點。中間的3×3的矩陣叫做濾波器,也可以叫做卷積核。 星號代表的就是卷積運算,用濾波器對左邊的圖片做卷積運算,得出3×3的矩陣。具體怎麼算呢? 先說結果的第一個元素:就是用濾波器,覆蓋在圖片的左上角,對應的每格元素相乘,得到9個數字,最後把這9個數字相加,就得到了第一個元素。 濾波器在圖片上左移一格,再計算就得到了第二個元素,之後的元素同理。

卷積公式如下,其中SS代表運算結果,II是原始圖片,KK是卷積核,mmnn是卷積核的高和寬,括號中的兩個值代表元素的位置:
S(i,j)=(IK)(i,j)=mnI(i+m,j+n)K(m,n)S(i,j)=(I*K)(i,j)=\sum_m\sum_nI(i+m,j+n)K(m,n)

其實該函數叫互相關函數(cross-correlation),和卷積函數幾乎一樣,只是沒有對卷積核進行翻轉,很多深度學習的庫實現的都是這個函數而並非真正的卷積函數。在深度學習中我們默認它爲卷積函數。

現在知道卷積運算了,那麼CNN呢?
CNN的結構:(卷積層+非線性激活函數(Relu或tanh)+池化層)× n + 幾個全連接層。
剛開始的卷積層提取低階特徵,比如曲線等,層數越深提取的特徵越高級,深層的卷積層提取的高級特徵比如人臉識別中的鼻子、嘴巴等。

池化層:對每個不重疊的nnn*n的區域進行降採樣,有最大池化、平均池化等。比如222*2最大池化層:從原矩陣每個不重疊的222*2的四個元素中選出一個最大值,組成一個矩陣。
池化層的作用:

  1. 可以提將輸出矩陣的尺寸固定不變。
  2. 減少輸出維度,但是保存重要特徵。

Channels通道:通道是輸入數據的不同“視圖”。
比如圖片中的RGB(紅色,綠色,藍色)是三個通道。在NLP中通道可以是不同的詞嵌入(word2vec和GloVe)或者是相同句子不同的語言。
通道

卷積層和全連接層有什麼本質上的區別

  1. 卷積層權重只有一個卷積核,是共享的,參數遠少於全連接層。
  2. 卷積層可以提取相對位置信息(相鄰的權重是相關的)。

CNN in NLP

以上特點使得cnn在計算機視覺方向應用很廣,但是在nlp中,cnn貌似並不適合使用。一句話說法有很多種,語義相關的詞中間可能會穿插不相關的詞,而且提取出來的語句的高階特徵也不如視覺方向那麼明顯。下面看看CNN如何應用在NLP中。

cnn最適合做的是文本分類,由於卷積和池化會丟失句子局部字詞的排列關係,所以純cnn不太適合序列標註和命名實體識別之類的任務。
nlp任務的輸入都不再是圖片像素,而是以矩陣表示的句子或者文檔。矩陣的每一行對應一個單詞或者字符。也即每行代表一個詞向量,通常是像 word2vec 或 GloVe 詞向量。在圖像問題中,卷積核滑過的是圖像的一“塊”區域,但在自然語言領域裏我們一般用卷積核滑過矩陣的一“行”(單詞)。

如上圖,如果一個句子有6個詞,每個詞的詞向量長度爲7,那輸入矩陣就是6x7,這就是我們的“圖像”。可以理解爲通道爲1的圖片。

其計算公式如下,其中\oplus是按行拼接,ff爲非線性激活函數:
X1:n=x1x2...xnX_{1:n}=x_1\oplus x_2\oplus ... \oplus x_n

ci=f(wxi:i+h1+b)c_i=f(w \cdot x_{i:i+h-1}+b)

c=[c1,c2,...,cnh+1]c=[c_1,c_2,...,c_{n-h+1}]

卷積核的“寬度”就是輸入矩陣的寬度(詞向量維度),“高度”可能會變(句子長度),但一般是每次掃過2-5個單詞,是整行整行的進行的。這有點像n-gram語言模型
也稱爲n元語言模型。n元語言模型簡單來說就是:一個詞出現的概率只與它前面的n-1個詞有關,考慮到了詞與詞之間的關聯。一般來說,在大的詞表中計算3元語言模型就會很吃力,更別說4甚至5了。由於參數量少,CNN做這種類似的計算卻很輕鬆。
卷積核的大小:由於在卷積的時候是整行整行的卷積的,因此只需要確定每次卷積的行數即可,這個行數也就是n-gram模型中的n。一般來講,n一般按照2,3,4這樣來取值,這也和n-gram模型中n的取值相照應。


實例:

從頭開始實現一個CNN文本分類的模型,對影評進行二分類,分爲消極和積極兩類。
分預處理和模型兩部分:


一. 預處理

  1. 將數據集的所有詞標號,構建一個詞表,如下圖所示。

    每個詞也對應一個詞向量,詞向量是預訓練好的,我們只需要用它行了。

  2. 數據的格式如下圖。

  3. 將積極樣本和消極樣本放在一起,並填充、打標籤,如下圖。

  4. 打亂順序

    打亂順序後參數不易陷入局部最優,模型能夠更容易達到收斂。


模型

這幅簡化的圖大致能表現出模型的結構,下面的講解請結合圖片一起看。
從左往右看,首先輸入數據(一句話),經過預處理,此處每個樣本填充爲7個詞,進入embedding層,將句子中每個詞按照詞表轉爲索引,再轉變成詞向量,此處詞向量維度爲5,輸入到模型中。需要注意的是這裏的embedding矩陣可以設置爲**trainable=True,也可設置爲trainable=False**,前者代表embedding矩陣可以微調,後者表示不對其進行訓練。

注:這裏可以設置爲多個通道。

下一層是卷積層,一共有三種尺寸的卷積核:2,3,4,分別對應2-gram,3-gram和4-gram,這個簡化的圖中每種尺寸的卷積核的數量都是2,實際會設置爲幾十上百個,我們不會只用一個卷積覈對輸入圖像進行過濾,因爲一個核提取的特徵是單一的。這就有點像是我們平時如何客觀看待事物,必須要從多個角度分析事物,這樣才能儘可能地避免對該事物產生偏見。接下來用三個尺寸的卷積覈對輸入矩陣做卷積運算,每個卷積操作都會得到一個feature map。

然後是池化層:我們對每一個feature map,進行最大池化,就是簡單地從feature map中提取最大的值,這裏最大的值也就代表着最重要的特徵信息,將每個Feature Map的維度全部下降爲1,這樣,句子填充對結果就沒有影響了,因爲不管feature map中有多少個值,只取最大的值,即最重要的特徵。
接着將所有池化得到的特徵值拼接到一起,形成單個feature map
下一層將這個feature map通過全連接的方式連接到一個softmax層,進行分類。

訓練的時候,在全連接的部分使用dropout,並對全連接層的權值參數進行L2正則化的限制。用來防止隱藏單元自適應,從而減輕過擬合程度。
其中L2正則化使每個參數不會很大。對於Dropout,它會使神經網絡的隱藏單元按照一定的概率暫時從網絡中丟棄,它使的所有神經單元變得不那麼重要,因爲沒準下次迭代它就被shuts down了。不依賴於任何一個特徵,因爲該單元的輸入可能隨時被清除。Dropout提高了2%-4%的性能。


下面是Yoon Kim (2014)提出的四種模型:

論文提出的模型 在數據集Movie Review的準確率
CNN-rand 76.1%
CNN-static 81.0%
CNN-non-static 81.5%
CNN-multichannel 81.1%

四種模型的區別在embedding層:
CNN-rand:baseline模型,所有詞隨機初始化然後隨着訓練不斷修改。
CNN-static:使用經過word2vec預訓練的詞向量,而未覆蓋在內的詞則隨機初始化。訓練過程中,預訓練的詞向量不變,隨機初始化的詞向量不斷修改。
CNN-non-static:同上,但預訓練的詞向量會在訓練過程中微調(fine-tune)
CNN-multichannel:用上面兩種詞向量,每一種詞向量一個通道,一共兩個通道。


TensorFlow實現CNN文本分類

代碼見github,預訓練詞向量下載地址:https://nlp.stanford.edu/projects/glove/, 我用的是400,000個詞彙的300維Glove詞向量。

下面是tensorflow中卷積和池化的函數,拿出來講解一下。

tf.nn.conv2d

tf.nn.conv2d(input,filter,strides,padding,use_cudnn_on_gpu=None,name=None)

參數:

input:

一個Tensor,每個元素的數據類型必須爲float32或float64。
input的形狀:[batch, in_height, in_width, in_channels],
batch爲訓練過程中每迭代一次迭代數據條數。
in_height, in_width分別爲矩陣(圖片)的高和寬
in_channels爲矩陣(圖片)的通道,比如圖片可以有RGB三通道。

輸入:(句子個數, 句子長度, embedding尺寸, 通道數)

filter:
卷積核,也是一個Tensor,元素類型和input類型一致。
filter的形狀:[filter_height, filter_width, in_channels, out_channels]
(其中out_channels也是該卷積核的個數)。
參數分別爲卷積核的高,寬,輸入的channels和輸出的channels

卷積核:(卷積核尺寸, embedding尺寸, 通道數, 卷積核個數)

stride:
步長,長度爲4的list,元素類型爲int。表示每一維度滑動的步長。其中strides[0] = strides[3] = 1。strides[1]和strides[2]分別表示在hight和width方向的步長。

padding:
可選參數爲"SAME", “VALID”。
SAME表示填充,VALID不填充。

use_cudnn_on_gpu:
bool類型,有True和False兩種選擇。

name:
此操作的名字

tf.nn.max_pool

池化

tf.nn.max_pool(value, ksize, strides, padding, name=None)

參數和卷積很類似:
value:需要池化的輸入,池化層通常跟在卷積層後面,所以輸入的shape依然是[batch, hei ght, width, channels]。

ksize:池化窗口的大小,四維向量,一般是[1, height, width, 1],前後兩個1對應的是batch和channels,都不池化所以爲1

strides:步長,和卷積一樣,前後都爲1,中間兩個分表表示窗口在每一個維度上滑動的步長,shape:[1, stride,stride, 1]

padding:‘VALID’ 或’SAME’

返回:一個Tensor,類型不變,shape仍然是[batch, height, width, channels]這種形式


總結

Yoon Kim (2014)的實驗證明了CNN在NLP中的實用性,同時也可以看出預訓練詞向量對結果的巨幅提升,最近Google提出了BERT,它在11個NLP任務中刷新了成績,需要好好研究研究,繼續加油!


References:

[1] Yoon Kim (2014) Convolutional Neural Networks for Sentence Classification
[2] Implementing a CNN for Text Classification in TensorFlow
[3] Understanding Convolutional Neural Networks for NLP
[4] DNN/LSTM/Text-CNN情感分類實戰與分析
[5] Tensorflow官方文檔

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