提到文本分類不能不說卷積神經(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ù)矩陣:
同理,第2,3,4個(gè)數(shù)據(jù)矩陣的計(jì)算結(jié)果為:
此時(shí)得到卷積運(yùn)算的輸出,結(jié)果為
【步驟三】加上偏置值
矩陣的每個(gè)值都加上偏置3
![]()
所謂卷積層的處理,其實(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)相連,用來把前邊提取到的特征綜合起來
所以的取值是在
所有特征都參與(全連接)的情況下,共同決策出來來。
當(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 的仿射變換為:
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/>填充;
最后將文本信息根據(jù)字典編號(hào)替換成數(shù)值信息。
然后創(chuàng)建詞典,為每個(gè)單詞附上一個(gè)整數(shù)編號(hào)
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










