文章目錄
- 前言
- 加載數據集
- 一維卷積
- 最大時間匯聚層
- textCNN模型
- 定義模型
- 加載預訓練詞向量
- 訓練和評估模型
- 總結
前言
在之前的章節中,我們探討了如何使用循環神經網絡(RNN)來處理序列數據。今天,我們將探索另一種強大的模型——卷積神經網絡(CNN)——并將其應用于自然語言處理中的經典任務:情感分析。
你可能會覺得奇怪,CNN不是主要用于圖像處理的嗎?確實,CNN在計算機視覺領域取得了巨大的成功,它通過二維卷積核捕捉圖像的局部特征(如邊緣、紋理)。但如果我們換個角度思考,文本序列可以被看作是一維的“圖像”,其中每個詞元(token)就是一個“像素”。這樣,我們就可以使用一維卷積來捕捉文本中的局部模式,比如由相鄰單詞組成的n-gram。
本篇博客將詳細介紹如何使用 textCNN 模型,這是一種專為文本分類設計的CNN架構。我們將基于IMDb電影評論數據集,訓練一個能夠判斷評論是正面還是負面的模型。整個流程如下圖所示,我們將使用預訓練的GloVe詞向量作為輸入,將其送入textCNN模型,最終得到情感分類結果。
讓我們開始吧!首先,我們需要加載所需的數據集。
完整代碼:下載鏈接
加載數據集
我們仍然使用IMDb電影評論數據集。通過我們預先準備好的 utils_for_data.load_data_imdb
輔助函數,我們可以方便地加載訓練和測試數據迭代器,以及一個根據訓練數據構建好的詞匯表(vocab
)。
import torch
import utils_for_data
from torch import nnbatch_size = 64
train_iter, test_iter, vocab = utils_for_data.load_data_imdb(batch_size)
一維卷積
在深入textCNN模型之前,我們先來回顧一下一維卷積是如何工作的。它本質上是二維卷積在只有一個維度(時間或序列步長)上的特例。
如下圖所示,卷積窗口(或稱為卷積核)在一個一維輸入張量上從左到右滑動。在每個位置,輸入子張量與核張量進行逐元素相乘,然后求和,得到輸出張量中對應位置的一個標量值。例如,圖中第一個輸出值 2
是通過 0*1 + 1*2 = 2
計算得出的。
我們可以通過代碼來實現這個一維互相關(corr1d
)運算,代碼中詳盡的注釋解釋了每一步的維度變化和操作目的。
import torchdef corr1d(X, K):"""實現一維互相關(卷積)運算參數:X: 輸入張量,維度為 (n,) 其中 n 是輸入序列的長度K: 卷積核張量,維度為 (w,) 其中 w 是卷積核的長度返回:Y: 輸出張量,維度為 (n - w + 1,) 其中 n-w+1 是輸出序列的長度"""# 獲取卷積核的長度,維度: 標量w = K.shape[0]# 創建輸出張量,長度為輸入長度減去卷積核長度加1# Y的維度: (X.shape[0] - w + 1,)Y = torch.zeros((X.shape[0] - w + 1))# 遍歷輸出張量的每個位置for i in range(Y.shape[0]):# 在第i個位置進行卷積運算# X[i: i + w] 的維度: (w,) - 提取輸入序列的一個窗口# K 的維度: (w,) - 卷積核# 兩者逐元素相乘后求和得到標量結果Y[i] = (X[i: i + w] * K).sum()return Y# 測試代碼
# X: 輸入張量,維度 (7,) - 包含7個元素的一維張量
X = torch.tensor([0, 1, 2, 3, 4, 5, 6])# K: 卷積核張量,維度 (2,) - 包含2個元素的一維張量
K = torch.tensor([1, 2])# 調用函數進行一維卷積運算
# 輸出結果的維度: (7 - 2 + 1,) = (6,)
result = corr1d(X, K)
print(result)
輸出結果與預期一致:
tensor([ 2., 5., 8., 11., 14., 17.])
在NLP中,詞嵌入通常是多維的,這意味著我們的輸入有多個通道。一維卷積同樣可以處理多通道輸入。此時,卷積核也需要有相同數量的輸入通道。運算時,對每個通道分別執行一維互相關,然后將所有通道的結果相加,得到一個單通道的輸出。
下面是多輸入通道一維互相關的實現。
import torchdef corr1d_multi_in(X, K):"""實現多輸入通道的一維互相關(卷積)運算參數:X: 多通道輸入張量,維度為 (c, n) 其中 c 是輸入通道數,n 是每個通道的序列長度K: 多通道卷積核張量,維度為 (c, w) 其中 c 是輸入通道數,w 是卷積核的長度返回:result: 輸出張量,維度為 (n - w + 1,) 其中 n-w+1 是輸出序列的長度"""# 遍歷X和K的第0維(通道維),對每個通道分別進行一維卷積,然后求和# X的維度: (c, n) - c個通道,每個通道長度為n# K的維度: (c, w) - c個通道,每個通道的卷積核長度為w# zip(X, K) 將對應通道的輸入和卷積核配對# 每次corr1d(x, k)的結果維度: (n - w + 1,)# sum()將所有通道的結果相加,最終輸出維度: (n - w + 1,)return sum(corr1d(x, k) for x, k in zip(X, K))# 測試代碼
# X: 多通道輸入張量,維度 (3, 7) - 3個輸入通道,每個通道包含7個元素
X = torch.tensor([[0, 1, 2, 3, 4, 5, 6],[1, 2, 3, 4, 5, 6, 7],[2, 3, 4, 5, 6, 7, 8]])# K: 多通道卷積核張量,維度 (3, 2) - 3個通道,每個通道的卷積核長度為2
K = torch.tensor([[1, 2], [3, 4], [-1, -3]])# 調用函數進行多通道一維卷積運算
# 輸出結果的維度: (7 - 2 + 1,) = (6,)
result = corr1d_multi_in(X, K)
print(result)
輸出結果:
tensor([ 2., 8., 14., 20., 26., 32.])
有趣的是,多輸入通道的一維互相關等價于單輸入通道的二維互相關,只要將二維卷積核的高度設置為與輸入張量的高度相同即可,如下圖所示。
最大時間匯聚層
在卷積層之后,textCNN使用了一個稱為最大時間匯聚層(Max-over-time Pooling)的關鍵組件。卷積操作的輸出長度依賴于輸入序列和卷積核的寬度,導致不同卷積核產生的輸出序列長度不同。最大時間匯聚層的作用是在時間步(序列長度)維度上取最大值。這相當于從每個卷積核提取的特征圖中,只保留最強烈的信號。無論輸入序列多長,經過這個操作后,每個通道都只會輸出一個標量值,從而解決了不同卷積核輸出維度不一的問題,并生成了用于分類的固定長度的特征向量。
textCNN模型
理解了一維卷積和最大時間匯聚后,我們就可以構建textCNN模型了。整個模型的架構如下圖所示:
輸入是一個句子,每個詞元由一個多維向量表示。我們定義了多種不同寬度的卷積核(