基於LSTM和TextCNN的文本情感分析

Pipeline

讀取評論數據和情感標籤
預處理文本數據
創建數據迭代器
建立神經網絡模型
加載預訓練詞向量
訓練模型
模型評價

LSTM

在這裏插入圖片描述
對於給定序列{X1,X2,...,XT}\{ \boldsymbol X_1,\boldsymbol X_2,...,\boldsymbol X_T\},其中,XtRn×d\boldsymbol X_t \in R^{n \times d}爲時間步tt的輸入,nn爲批量大小,dd爲輸入維度。在雙向神經網絡的架構中,時間步tt上的正向隱藏狀態爲HtRn×h\overrightarrow {\boldsymbol H_t} \in R^{n \times h},正向隱藏狀態維度爲hh,反向隱藏狀態爲HtRn×h\overleftarrow{\boldsymbol H_t} \in R^{n \times h},則正向和反向隱藏狀態的計算如下:
Ht=ϕ(XtWxhf+Ht1Whhf+bhf) \overrightarrow {\boldsymbol H_t} = \phi(X_tW_{xh}^f+\overrightarrow {\boldsymbol H_{t-1}}W_{hh}^f +b_h^f)

Ht=ϕ(XtWxhb+Ht+1Whhb+bhf) \overleftarrow {\boldsymbol H_t} = \phi(X_t W_{xh}^b+\overleftarrow {\boldsymbol H_{t+1}}W_{hh}^b +b_h^f)

其中,WxhfRd×hW_{xh}^f \in R^{d \times h}WhhfRh×hW_{hh}^f \in R^{h \times h}WxhbRd×hW_{xh}^b \in R^{d \times h}WhhbRh×hW_{hh}^b \in R^{h \times h}bhbR1×hb_h^b \in R^{1 \times h}ϕ\phi爲激活函數。

然後連接同一時間步上兩個方向的隱藏狀態$ \overrightarrow{ \boldsymbol H_t}\overleftarrow {\boldsymbol H_t} 得到H_t \in R^{n \times 2h},並將其輸入到輸出層,輸出層計算出\boldsymbol O_t \in R^{ n \times q}(輸出維度爲h$):

Ot=HtWhq+bq \boldsymbol O_t=\boldsymbol H_t \boldsymbol W_{hq}+ \boldsymbol b_q

其中,WhqR2h×q\boldsymbol W_{hq} \in R^{2h \times q}和偏差bqR1×q\boldsymbol b_q \in R^{1 \times q}

TextCNN

序列數據的卷積操作

詞向量的維度可以看成是輸入通道數,序列方向可以看成這句話的特徵。多輸入通道的一維互相關運算可以看作單輸入通道的二維互相關運算。反過來說,對於文本數據,當二維卷積核的高度等於輸入的高度時才成立。

在這裏插入圖片描述

序列數據的池化操作(時序最大池化層)

TextCNN 中使用的時序最大池化(max-over-time pooling)層實際上對應一維全局最大池化層:假設輸入包含多個通道,各通道由不同時間步上的數值組成,各通道的輸出即該通道所有時間步中最大的數值。因此,時序最大池化層的輸入在各個通道上的時間步數可以不同。

在這裏插入圖片描述

TextCNN的數據流變化過程

TextCNN 模型主要使用了一維卷積層和時序最大池化層。假設輸入的文本序列由 nn 個詞組成,每個詞用 dd 維的詞向量表示。那麼輸入樣本的寬爲 nn,輸入通道數爲 dd。TextCNN 的計算主要分爲以下幾步。

  1. 定義多個一維卷積核,並使用這些卷積覈對輸入分別做卷積計算。寬度不同的卷積核可能會捕捉到不同個數的相鄰詞的相關性。
  2. 對輸出的所有通道分別做時序最大池化,再將這些通道的池化輸出值連結爲向量。
  3. 通過全連接層將連結後的向量變換爲有關各類別的輸出。這一步可以使用丟棄層應對過擬合。

在這裏插入圖片描述

代碼如下:

class TextCNN(nn.Module):
    def __init__(self, vocab, embed_size, kernel_sizes, num_channels):
        '''
        @params:
            vocab: 在數據集上創建的詞典,用於獲取詞典大小
            embed_size: 嵌入維度大小
            kernel_sizes: 卷積核大小列表
            num_channels: 卷積通道數列表
        '''
        super(TextCNN, self).__init__()
        self.embedding = nn.Embedding(len(vocab), embed_size) # 參與訓練的嵌入層
        self.constant_embedding = nn.Embedding(len(vocab), embed_size) # 不參與訓練的嵌入層
        
        self.pool = GlobalMaxPool1d() # 時序最大池化層沒有權重,所以可以共用一個實例
        self.convs = nn.ModuleList()  # 創建多個一維卷積層
        for c, k in zip(num_channels, kernel_sizes):
            self.convs.append(nn.Conv1d(in_channels = 2*embed_size, 
                                        out_channels = c, 
                                        kernel_size = k))
            
        self.decoder = nn.Linear(sum(num_channels), 2)
        self.dropout = nn.Dropout(0.5) # 丟棄層用於防止過擬合

    def forward(self, inputs):
        '''
        @params:
            inputs: 詞語下標序列,形狀爲 (batch_size, seq_len) 的整數張量
        @return:
            outputs: 對文本情感的預測,形狀爲 (batch_size, 2) 的張量
        '''
        embeddings = torch.cat((
            self.embedding(inputs), 
            self.constant_embedding(inputs)), dim=2) # (batch_size, seq_len, 2*embed_size)
        # 根據一維卷積層要求的輸入格式,需要將張量進行轉置
        embeddings = embeddings.permute(0, 2, 1) # (batch_size, 2*embed_size, seq_len)
        
        encoding = torch.cat([
            self.pool(F.relu(conv(embeddings))).squeeze(-1) for conv in self.convs], dim=1)
        # encoding = []
        # for conv in self.convs:
        #     out = conv(embeddings) # (batch_size, out_channels, seq_len-kernel_size+1)
        #     out = self.pool(F.relu(out)) # (batch_size, out_channels, 1)
        #     encoding.append(out.squeeze(-1)) # (batch_size, out_channels)
        # encoding = torch.cat(encoding) # (batch_size, out_channels_sum)
        
        # 應用丟棄法後使用全連接層得到輸出
        outputs = self.decoder(self.dropout(encoding))
        return outputs

embed_size, kernel_sizes, nums_channels = 100, [3, 4, 5], [100, 100, 100]
net = TextCNN(vocab, embed_size, kernel_sizes, nums_channels)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章