天天看點

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

前言

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

什麼是卷積

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

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

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

卷積公式如下,其中 S S S代表運算結果, I I I是原始圖檔, K K K是卷積核, m m m、 n n n是卷積核的高和寬,括号中的兩個值代表元素的位置:

S ( i , j ) = ( I ∗ K ) ( i , j ) = ∑ m ∑ n I ( 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) S(i,j)=(I∗K)(i,j)=m∑​n∑​I(i+m,j+n)K(m,n)

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

現在知道卷積運算了,那麼CNN呢?

CNN的結構:(卷積層+非線性激活函數(Relu或tanh)+池化層)× n + 幾個全連接配接層。

剛開始的卷積層提取低階特征,比如曲線等,層數越深提取的特征越進階,深層的卷積層提取的進階特征比如人臉識别中的鼻子、嘴巴等。

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

池化層的作用:

  1. 可以提将輸出矩陣的尺寸固定不變。
  2. 減少輸出次元,但是儲存重要特征。

Channels通道:通道是輸入資料的不同“視圖”。

比如圖檔中的RGB(紅色,綠色,藍色)是三個通道。在NLP中通道可以是不同的詞嵌入(word2vec和GloVe)或者是相同句子不同的語言。

通道

卷積層和全連接配接層有什麼本質上的差別:

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

CNN in NLP

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

cnn最适合做的是文本分類,由于卷積和池化會丢失句子局部字詞的排列關系,是以純cnn不太适合序列标注和命名實體識别之類的任務。

nlp任務的輸入都不再是圖檔像素,而是以矩陣表示的句子或者文檔。矩陣的每一行對應一個單詞或者字元。也即每行代表一個詞向量,通常是像 word2vec 或 GloVe 詞向量。在圖像問題中,卷積核滑過的是圖像的一“塊”區域,但在自然語言領域裡我們一般用卷積核滑過矩陣的一“行”(單詞)。

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

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

其計算公式如下,其中 ⊕ \oplus ⊕是按行拼接, f f f為非線性激活函數:

X 1 : n = x 1 ⊕ x 2 ⊕ . . . ⊕ x n X_{1:n}=x_1\oplus x_2\oplus ... \oplus x_n X1:n​=x1​⊕x2​⊕...⊕xn​

c i = f ( w ⋅ x i : i + h − 1 + b ) c_i=f(w \cdot x_{i:i+h-1}+b) ci​=f(w⋅xi:i+h−1​+b)

c = [ c 1 , c 2 , . . . , c n − h + 1 ] c=[c_1,c_2,...,c_{n-h+1}] c=[c1​,c2​,...,cn−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. 将資料集的所有詞标号,建構一個詞表,如下圖所示。
    CNN文本分類原理講解與實戰
    每個詞也對應一個詞向量,詞向量是預訓練好的,我們隻需要用它行了。
  2. 資料的格式如下圖。
    CNN文本分類原理講解與實戰
  3. 将積極樣本和消極樣本放在一起,并填充、打标簽,如下圖。
    CNN文本分類原理講解與實戰
  4. 打亂順序
    CNN文本分類原理講解與實戰
    打亂順序後參數不易陷入局部最優,模型能夠更容易達到收斂。

模型

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

這幅簡化的圖大緻能表現出模型的結構,下面的講解請結合圖檔一起看。

從左往右看,首先輸入資料(一句話),經過預處理,此處每個樣本填充為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

參數:

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

池化

參數和卷積很類似:

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官方文檔

繼續閱讀