CNN與文本分類

提到文本分類不能不說卷積神經(jīng)網(wǎng)絡(luò)(Convolutional Neural Network,CNN)。
本章將談?wù)刢nn與文本分類,詳細(xì)cnn原理就不講了,只梳理一些基礎(chǔ)概念,然后講講文本數(shù)據(jù)是怎么用cnn算法實(shí)現(xiàn)分類的。

一、CNN的大致框架

以下圖一個(gè)5層的卷積神經(jīng)網(wǎng)絡(luò)為例[1]。
1~4層為“Convolution - ReLU -(Pooling)”組合(Pooling層有時(shí)會(huì)被省略),第5層輸出層使用“Affine -Softmax”組合,輸出最終結(jié)果為概率。

1、基礎(chǔ)概念

什么是Conv層?什么是Pooling層?什么是Affine層?我們首先需要知道這些基礎(chǔ)概念,下面將一一解答。

(1)Convolution(卷積)層

卷積層的功能是對(duì)輸入數(shù)據(jù)進(jìn)行特征提取,通過 卷積運(yùn)算 提取特征 。
什么是卷積運(yùn)算?我們以一個(gè)簡單的卷積運(yùn)算為例,輸入數(shù)據(jù)是(4,4)的矩陣,引入一個(gè)濾波器(filter)(卷積核 kernal)(權(quán)重組合矩陣),運(yùn)算的結(jié)果是一個(gè)(2,2)的矩陣,再加偏置得到最終輸出結(jié)果。

具體是怎么計(jì)算的呢?

【步驟一】以一定間隔滑動(dòng)濾波器大小的窗口。
(4,4)和(3,3)的矩陣顯然是不能相乘的,所以從(4,4)的輸入數(shù)據(jù)中取出一個(gè)個(gè)(3,3)的矩陣來,這樣(3,3)和(3,3)的矩陣就可以相乘了。


【步驟二】乘積累加運(yùn)算
將輸入數(shù)據(jù)和對(duì)應(yīng)位置上濾波器的元素相乘,然后再求和。
第一個(gè)(3,3)數(shù)據(jù)矩陣:1*2+2*0+3*1+0*0+1*1+2*2+3*1+0*0+1*2=15
同理,第2,3,4個(gè)數(shù)據(jù)矩陣的計(jì)算結(jié)果為:
2*2+3*0+0*1+1*0+2*1+3*2+0*1+1*0+2*2=16
0*2+1*0+2*1+3*0+0*1+1*2+2*1+3*0+0*2=6
1*2+2*0+3*1+0*0+1*1+2*2+3*1+0*0+1*2=15
此時(shí)得到卷積運(yùn)算的輸出,結(jié)果為
\left[ \begin{matrix} 15 & 16\\ 6 & 15\end{matrix} \right]
【步驟三】加上偏置值
矩陣的每個(gè)值都加上偏置3
\left[ \begin{matrix} 18 & 19\\ 9 & 18\end{matrix} \right]

所謂卷積層的處理,其實(shí)是輸入數(shù)據(jù)的一種轉(zhuǎn)換方式。

CNN在圖像處理上應(yīng)用廣泛,對(duì)于一張圖片我們除了考慮長寬信息外,還需考慮顏色等信息,例如RGB三通道。

以三通道的數(shù)據(jù)為例,要求濾波器和輸入數(shù)據(jù)的通道上一致(也是3),這樣才可以一對(duì)一匹配起來,做上面的卷積運(yùn)算,算完以后的3個(gè)(2,2)的矩陣,對(duì)應(yīng)位置數(shù)值相加即可。



可以這樣處理方式輸出結(jié)果只有1張?zhí)卣鲌D,和我們經(jīng)??吹降男畔⑷杂谐鋈?。于是我們采用了多個(gè)濾波器(權(quán)重)。

濾波器有幾個(gè)對(duì)應(yīng)輸出的特征圖就有幾張,這樣一個(gè)三位的圖像信息在多個(gè)濾波器的專用下轉(zhuǎn)換成了(FN,OH,OW)的特征數(shù)據(jù)。


(2)ReLU層

神經(jīng)網(wǎng)絡(luò)中常用的激活函數(shù)有sigmoid、relu、tanh等,但由于relu的一些優(yōu)勢(shì)[2],CNN與一般與relu是捆綁在一起的。
relu層對(duì)數(shù)據(jù)矩陣做如下操作:如果值為負(fù)數(shù),relu將其轉(zhuǎn)變?yōu)?,否則為其本身。

relu函數(shù)


(3)Pooling(池化)層

relu層的操作會(huì)使矩陣一部分?jǐn)?shù)值為0,這樣就造成了網(wǎng)絡(luò)的稀疏性,因此引入池化層對(duì)矩陣進(jìn)行壓縮,特征降維。
池化層是通過什么樣的操作實(shí)現(xiàn)“特征降維”的呢?
常見的池化方式有Max池化、Average池化等。

例如以 步幅 為2進(jìn)行2 × 2的Max池化


步幅為2的(2,2)窗口劃過去,每次取(2,2)矩陣的最大值(如果是Average池化就取均值),是不是超簡單的步驟。

池化層操作完以后,原本(4,4)的矩陣被壓縮成(2,2)的矩陣,且保留了矩陣的重要信息(窗口最大值)

(4)FC全連接層

全連接(fully connected),顧名思義指相鄰層的所有神經(jīng)元之間都有連接。在CNN起著分類決策的作用。

全連接層的每一個(gè)結(jié)點(diǎn)都與上一層的所有結(jié)點(diǎn)相連,用來把前邊提取到的特征綜合起來


所以a_i的取值是在x_1,x_2,x_3所有特征都參與(全連接)的情況下,共同決策出來來。
當(dāng)然如果全連接層有兩層及以上,這樣能解決非線性問題,原因參見BP神經(jīng)網(wǎng)絡(luò)的梳理:(6)如何解決非線性問題

如果說卷積層、池化層處理的數(shù)據(jù)還是多維數(shù)據(jù),那么到了全連接層,輸入數(shù)據(jù)就是一維的了,我們通過flattening(扁平化)把多維數(shù)據(jù)轉(zhuǎn)換成一維排排站好。

以一個(gè)簡單的矩陣為例,矩陣?yán)锩娴臄?shù)值一個(gè)一個(gè)排下來:


(5)Affine層

結(jié)構(gòu)圖上不是Affine層嗎?怎么變成全連接層了?
Affine層對(duì)輸入數(shù)據(jù)做仿射變換(Affine transformation)[3]
一個(gè)集合 X 的仿射變換為:
f(x) = Ax+b
Affine 仿射層, 又稱 Linear 線性變換層, 常用于神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)中的全連接層。就是說上述框架的全連接層采用了Affine這種數(shù)據(jù)處理方式。
全連接的概念是相鄰的結(jié)點(diǎn)都來參與決策,一個(gè)都不能落;Affine是指對(duì)輸入數(shù)據(jù)權(quán)重相乘加偏置的數(shù)據(jù)處理方式。

2、數(shù)據(jù)結(jié)構(gòu)變化

CNN一路運(yùn)算下來,數(shù)據(jù)結(jié)構(gòu)變化很大

例如一個(gè)輸入一個(gè)28*28,1通道的圖片
(1) 卷積層:在多個(gè)濾波器的作用下,提取到特征數(shù)據(jù)。特征數(shù)據(jù)的維度和濾波器的數(shù)量有關(guān)系,特征數(shù)據(jù)的長寬和濾波器的大小及滑動(dòng)窗的步長有關(guān)系。
(2) 池化層:壓縮特征,使特征圖長寬變小,維度不變。
(3) 到了FC全連接層,特征圖的數(shù)據(jù)排排站好,數(shù)據(jù)變成一維數(shù)據(jù),權(quán)重乘積求和、激活函數(shù)處理,做出分類決策。


二、CNN與文本分類

講了那么久CNN算法本身,來聊聊CNN與文本分類。

1、一維卷積處理

keras存在函數(shù)Conv1D、Conv2D、Conv3D用于支持一維卷積、二維卷積、三維卷積的處理,對(duì)于文本分類,使用Conv1D來處理。
不過,在Conv2D輸入通道為1的情況下,二者是沒有區(qū)別或者說是可以相互轉(zhuǎn)化的。

2、文本特征數(shù)值化

(1)中文文本

在傳統(tǒng)機(jī)器學(xué)習(xí)的中文文本分類中(SVM、隨機(jī)森林、XGBoost等),我們首先需要對(duì)中文文本分詞(例如jieba),然后采用tf-idf方式將文本信息轉(zhuǎn)換成數(shù)值信息。
但在CNN算法中,我們將每一個(gè)作為特征,為每一個(gè)字附上一個(gè)編號(hào),通過匹配字典的方式講文本信息轉(zhuǎn)換成數(shù)值信息。

(2)英文文本

在傳統(tǒng)機(jī)器學(xué)習(xí)的英文文本分類中,由于英文單詞與單詞之間是空格分開的,所以我們可以直接采用tf-idf方式,而不需要做分詞處理,將文本信息轉(zhuǎn)換成數(shù)值信息。
但在CNN算法中,我們將每一個(gè)單詞作為特征,為每一個(gè)單詞附上一個(gè)編號(hào),通過匹配字典的方式講文本信息轉(zhuǎn)換成數(shù)值信息。

(3)操作案例
a = [['the', 'rock'], ['is', 'destined', 'to', 'be'],['continuation', 'of', 'the', 'lord', 'of', 'the', 'rings']]
b = pad_sentences(a, padding_word="<PAD/>")  # pad_sentences為自建函數(shù),用來將短文本處理成同一長度
vocabulary_a = build_vocab(b)  # 創(chuàng)建詞典
x = np.array([[vocabulary_a[word] for word in sentence] for sentence in b])
print(x)

有3段文本 'the rock','is destined to be','continuation of the lord of the rings',我們把短文本以空格分隔存儲(chǔ)在list里。首先將每段短文本處理為同一長度,缺失部分用<PAD/>填充;
然后創(chuàng)建詞典,為每個(gè)單詞附上一個(gè)整數(shù)編號(hào)

最后將文本信息根據(jù)字典編號(hào)替換成數(shù)值信息。

3、embedding嵌入層

(1) 轉(zhuǎn)換數(shù)據(jù)格式
在構(gòu)建文本分類的cnn模型中,embedding嵌入層被定義為網(wǎng)絡(luò)的第一個(gè)隱藏層,數(shù)據(jù)經(jīng)過embedding層之后就方便地轉(zhuǎn)換為了可以由CNN進(jìn)一步處理的格式。
如果沒有embedding的處理,單單根據(jù)字典由文本信息轉(zhuǎn)換成數(shù)字信息的數(shù)據(jù)格式是不符合CNN的輸入要求的。

(2) 提取上下文信息
那么embedding除了轉(zhuǎn)換數(shù)據(jù)格式外還有什么作用呢?
我們已經(jīng)將文本信息轉(zhuǎn)換成數(shù)字信息了,但是一段文本包含語義信息、上下文關(guān)系、詞匯含義等,如何讓計(jì)算機(jī)獲取這些信息呢?通過embedding的處理使數(shù)據(jù)攜帶上下文信息。(具體原理暫放)

例如輸入上面[9,8,0,0,0,0,0],相關(guān)詞典涉及詞匯11個(gè),設(shè)置embedding空間長度(例如30),那么輸入數(shù)據(jù)轉(zhuǎn)換后變成縱向?yàn)樵~典的詞匯數(shù)量,橫向?yàn)樵O(shè)置的embedding長度的空間表(11,30)。

4、cnn與文本分類原理

以經(jīng)典的CNN與文本分類為例[8]

embedding層——>輸入數(shù)據(jù)在第一層embedding layer轉(zhuǎn)換成固定長度,設(shè)置embedding長度為5,詞典涉及詞匯7個(gè),因此embedding層處理后數(shù)據(jù)格式為(7,5)。這樣就是一張大表了,可以讓窗口在這張大表上滑動(dòng),和濾波器(filter)做卷積運(yùn)算;
卷積層——>下圖設(shè)置6個(gè)濾波器,寬度設(shè)置與數(shù)據(jù)寬度一致,高度(region sizes)分別設(shè)置為2,3,4各2個(gè)。當(dāng)步長為1,分別輸出結(jié)果為(6,1),(5,1),(4,1)的特征圖;
池化層——>采用max的方式對(duì)卷積層輸出結(jié)果做池化處理,壓縮數(shù)據(jù)。
全連接層——>池化層的輸出結(jié)果進(jìn)入全連接層,先flattening扁平化排排站好,然后乘以權(quán)重求和加偏置,激活函數(shù)處理,梯度下降優(yōu)化權(quán)重,softmax輸出分類概率。

5、cnn與文本分類算法搭建

以下為文本的CNN分類實(shí)現(xiàn)部分代碼[9]。

# 這將返回tensor
print("創(chuàng)建CNN文本分類模型...")
inputs = Input(shape=(56,), dtype='int32') 
embedding = Embedding(input_dim=18765,output_dim=80,input_length= 56)(inputs)
reshape = Reshape((56, 80, 1))(embedding)
# 三層卷積:filter過濾器都是128個(gè),寬度和輸入數(shù)據(jù)寬度一致為80,不做填充操作,激活函數(shù)relu
conv_0 = Conv2D(128, kernel_size=(1, 80), padding='valid',kernel_initializer='normal', activation='relu')(reshape) 
conv_1 = Conv2D(128, kernel_size=(2,80), padding='valid',kernel_initializer='normal', activation='relu')(reshape)
conv_2 = Conv2D(128, kernel_size=(3,80), padding='valid',kernel_initializer='normal', activation='relu')(reshape)
#添加dropout層,防止過擬合
conv_0 = Dropout(drop)(conv_0)
conv_1 = Dropout(drop)(conv_1)
conv_2 = Dropout(drop)(conv_2)
# 每個(gè)卷積層對(duì)應(yīng)一個(gè)池化層
maxpool_0 = MaxPool2D(pool_size=(56-1+1,1),strides=(1,1),padding='valid')(conv_0) 
maxpool_1 = MaxPool2D(pool_size=(56-2+1,1),strides=(1,1),padding='valid')(conv_1)
maxpool_2 = MaxPool2D(pool_size=(56-3+1,1),strides=(1,1),padding='valid')(conv_2)
concatenated_tensor = Concatenate(axis=1)([maxpool_0, maxpool_1, maxpool_2])
# 扁平化排排站好,便于喂進(jìn)全連接層
flatten = Flatten()(concatenated_tensor)
dropout = Dropout(drop)(flatten)
output = Dense(units=2, activation='softmax')(dropout)  # 2個(gè)神經(jīng)元,softmax為激活函數(shù)
model = Model(inputs=inputs, outputs=output)
print("模型創(chuàng)建成功!")

checkpoint = ModelCheckpoint('weights.{epoch:03d}-{val_acc:.4f}.hdf5',monitor='val_acc',verbose=1,save_best_only=True,mode='auto')
earlyStopping = EarlyStopping(monitor='val_acc',patience=4,verbose=1,mode='max')
callbacks_list = [checkpoint, earlyStopping, metrics]
adam = Adam(lr=1e-4, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)
model.compile(optimizer=adam, loss='binary_crossentropy', metrics=['accuracy'])
print("開始訓(xùn)練模型...")
history = model.fit(X_train,y_train,batch_size=batch_size,epochs=epochs,verbose=1,callbacks=callbacks_list,validation_data=(X_test, y_test))
print('Training has completed!')

6、過擬合與欠擬合

(1)過擬合

謝天謝地,終于搭建好模型了,準(zhǔn)確率高的不要不要的,可是等運(yùn)行完發(fā)現(xiàn)算法的判斷效果不是很好,這是算法過擬合了[10]。

下圖為模型訓(xùn)練隨著訓(xùn)練數(shù)據(jù)增大,loss值的變化曲線。A點(diǎn)時(shí)訓(xùn)練誤差很小,可是驗(yàn)證集誤差比較大,這就會(huì)發(fā)生模型準(zhǔn)確率高的不要不要的,但實(shí)際使用效果不好的情況,所以我們需要增大模型的訓(xùn)練樣本。

隨著訓(xùn)練數(shù)據(jù)的增加,train數(shù)據(jù)上的loss越來越大,而驗(yàn)證集val data上的loss越來越小,Jtrain 和 Jval 越來越接近但始終保持 Jval > Jtrain。

解決過擬合可以嘗試:

  • 增大訓(xùn)練數(shù)據(jù);
  • 導(dǎo)致過擬合的一個(gè)原因也有可能是數(shù)據(jù)不純導(dǎo)致的,重新清洗下數(shù)據(jù);
  • 采用dropout方法
(2)欠擬合

在train數(shù)據(jù)表現(xiàn)差,在val數(shù)據(jù)表現(xiàn)也很差,反正在哪個(gè)數(shù)據(jù)集準(zhǔn)確率都不高,這可能是模型欠擬合導(dǎo)致。
解決過擬合可以嘗試:

  • 增大模型復(fù)雜度
  • 增加更多的特征信息

7、數(shù)據(jù)決定了模型最終的高度

最后,提一提樣本數(shù)據(jù)的選擇,數(shù)據(jù)決定了模型最終的高度,不斷優(yōu)化的模型只不過是為了不斷逼近這個(gè)高度而已,要保障樣本的典型性、均衡性等要求。

參考資料

[1] 《深度學(xué)習(xí)入門:基于python的理論與實(shí)現(xiàn)》
[2] 為什么在CNNs中激活函數(shù)選用ReLU,而不用sigmoid或tanh函數(shù):https://blog.csdn.net/benniaofei18/article/details/79868689
[3] 仿射變換(Affine transformation):https://blog.csdn.net/robert_chen1988/article/details/80498805
[4] 為什么要有最后一層全連接:https://blog.csdn.net/qq_39521554/article/details/81385159
[5] 卷積、池化、全連接關(guān)系蠻有意思的解釋:https://blog.csdn.net/m0_37407756/article/details/80904580
[6] 深度學(xué)習(xí)中Keras中的Embedding層的理解與使用:https://blog.csdn.net/sinat_22510827/article/details/90727435
[7] CNN與文本分類經(jīng)典論文一:https://arxiv.org/pdf/1408.5882.pdf
[8] CNN與文本分類經(jīng)典論文二:https://arxiv.org/pdf/1510.03820.pdf
[9] 基于CNN的文本分類代碼實(shí)現(xiàn):https://www.kesci.com/home/project/5b6bd1279889570010cbf9c7
[10] 欠擬合、過擬合及其解決方法:https://www.cnblogs.com/alan666/p/8311809.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容