[深度学习] 自然语言处理 --- 文本分类模型总结
文本分類
包括基于word2vec預訓練的文本分類,與及基于最新的預訓練模型(ELMO,BERT等)的文本分類
一 fastText 模型
fastText模型架構和word2vec中的CBOW很相似, 不同之處是fastText預測標簽而CBOW預測的是中間詞,即模型架構類似但是模型的任務不同。
?
其中x1,x2,...,xN?1,xN表示一個文本中的n-gram向量,每個特征是詞向量的平均值。這和前文中提到的cbow相似,cbow用上下文去預測中心詞,而此處用全部的n-gram去預測指定類別。
?
import torch.nn as nn import torch.nn.functional as Fclass FastText(nn.Module):def __init__(self, vocab_size, embedding_dim, output_dim, pad_idx):super().__init__()self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=pad_idx)self.fc = nn.Linear(embedding_dim, output_dim)def forward(self, text):#text = [sent len, batch size]embedded = self.embedding(text)#embedded = [sent len, batch size, emb dim]embedded = embedded.permute(1, 0, 2)#embedded = [batch size, sent len, emb dim]pooled = F.avg_pool2d(embedded, (embedded.shape[1], 1)).squeeze(1) #pooled = [batch size, embedding_dim]return self.fc(pooled)?
二 TextCNN模型
TextCNN 是利用卷積神經網絡對文本進行分類的算法,由 Yoon Kim 在 “Convolutional Neural Networks for Sentence Classification” 一文? 中提出. 是2014年的算法.
將Text的詞向量拼接在一起,就好比一張圖,只不過這個圖只是一個channel的.這里使用的就是Conv1d.
模型的結構是:
?
三 CharCNN模型
在charCNN論文Character-level Convolutional Networks for Text Classification中提出了6層卷積層 + 3層全連接層的結構,
在此之前很多基于深度學習的模型都是使用更高層面的單元對文本或者語言進行建模,比如單詞(統計信息或者 n-grams、word2vec 等),短語(phrases),句子(sentence)層面,或者對語義和語法結構進行分析,但是CharCNN則提出了從字符層面進行文本分類,提取出高層抽象概念。
字符編碼層 為了實現 CharCNN,首先要做的就是構建字母表,本文中使用的字母標如下,共有 69 個字符,對其使用 one-hot 編碼,外加一個全零向量(用于處理不在該字符表中的字符),所以共 70 個,所以每個字符轉化為一個 70 維的向量。文中還提到要反向處理字符編碼,即反向讀取文本,這樣做的好處是最新讀入的字符總是在輸出開始的地方。:
?
模型卷積 - 池化層 文中提出了兩種規模的神經網絡–large 和 small。(kernel——size的不同)都由 6 個卷積層和 3 個全連接層共 9 層神經網絡組成。這里使用的是 1-D 卷積神經網絡。除此之外,在三個全連接層之間加入兩個 dropout 層以實現模型正則化。
import torch from torch import nn import numpy as np from utils import *class CharCNN(nn.Module):def __init__(self, config, vocab_size, embeddings):super(CharCNN, self).__init__()self.config = configembed_size = vocab_size# Embedding Layerself.embeddings = nn.Embedding(vocab_size, embed_size)self.embeddings.weight = nn.Parameter(embeddings, requires_grad=False)# This stackoverflow thread explains how conv1d works# https://stackoverflow.com/questions/46503816/keras-conv1d-layer-parameters-filters-and-kernel-size/46504997conv1 = nn.Sequential(nn.Conv1d(in_channels=embed_size, out_channels=self.config.num_channels, kernel_size=7),nn.ReLU(),nn.MaxPool1d(kernel_size=3)) # (batch_size, num_channels, (seq_len-6)/3)conv2 = nn.Sequential(nn.Conv1d(in_channels=self.config.num_channels, out_channels=self.config.num_channels, kernel_size=7),nn.ReLU(),nn.MaxPool1d(kernel_size=3)) # (batch_size, num_channels, (seq_len-6-18)/(3*3))conv3 = nn.Sequential(nn.Conv1d(in_channels=self.config.num_channels, out_channels=self.config.num_channels, kernel_size=3),nn.ReLU()) # (batch_size, num_channels, (seq_len-6-18-18)/(3*3))conv4 = nn.Sequential(nn.Conv1d(in_channels=self.config.num_channels, out_channels=self.config.num_channels, kernel_size=3),nn.ReLU()) # (batch_size, num_channels, (seq_len-6-18-18-18)/(3*3))conv5 = nn.Sequential(nn.Conv1d(in_channels=self.config.num_channels, out_channels=self.config.num_channels, kernel_size=3),nn.ReLU()) # (batch_size, num_channels, (seq_len-6-18-18-18-18)/(3*3))conv6 = nn.Sequential(nn.Conv1d(in_channels=self.config.num_channels, out_channels=self.config.num_channels, kernel_size=3),nn.ReLU(),nn.MaxPool1d(kernel_size=3)) # (batch_size, num_channels, (seq_len-6-18-18-18-18-18)/(3*3*3))# Length of output after conv6 conv_output_size = self.config.num_channels * ((self.config.seq_len - 96) // 27)linear1 = nn.Sequential(nn.Linear(conv_output_size, self.config.linear_size),nn.ReLU(),nn.Dropout(self.config.dropout_keep))linear2 = nn.Sequential(nn.Linear(self.config.linear_size, self.config.linear_size),nn.ReLU(),nn.Dropout(self.config.dropout_keep))linear3 = nn.Sequential(nn.Linear(self.config.linear_size, self.config.output_size),nn.Softmax())self.convolutional_layers = nn.Sequential(conv1,conv2,conv3,conv4,conv5,conv6)self.linear_layers = nn.Sequential(linear1, linear2, linear3)def forward(self, x):embedded_sent = self.embeddings(x).permute(1,2,0) # shape=(batch_size,embed_size,seq_len)conv_out = self.convolutional_layers(embedded_sent)conv_out = conv_out.view(conv_out.shape[0], -1)linear_output = self.linear_layers(conv_out)return linear_output?
四 Bi-LSTM模型
Bi-LSTM即雙向LSTM,較單向的LSTM,Bi-LSTM能更好地捕獲句子中上下文的信息。
雙向循環神經網絡(BRNN)的基本思想是提出每一個訓練序列向前和向后分別是兩個循環神經網絡(RNN),而且這兩個都連接著一個輸出層。這個結構提供給輸出層輸入序列中每一個點的完整的過去和未來的上下文信息。下圖展示的是一個沿著時間展開的雙向循環神經網絡。六個獨特的權值在每一個時步被重復的利用,六個權值分別對應:輸入到向前和向后隱含層(w1, w3),隱含層到隱含層自己(w2, w5),向前和向后隱含層到輸出層(w4, w6)。值得注意的是:向前和向后隱含層之間沒有信息流,這保證了展開圖是非循環的。
?
五 Bi-LSTM+Attention 模型
Bi-LSTM + Attention模型來源于論文Attention-Based Bidirectional Long Short-Term Memory Networks for Relation Classification。關于Attention的介紹見這篇。
Bi-LSTM + Attention 就是在Bi-LSTM的模型上加入Attention層,在Bi-LSTM中我們會用最后一個時序的輸出向量 作為特征向量,然后進行softmax分類。Attention是先計算每個時序的權重,然后將所有時序 的向量進行加權和作為特征向量,然后進行softmax分類。在實驗中,加上Attention確實對結果有所提升。其模型結構如下圖:
?
六 RCNN模型
Here, we have implemented Recurrent Convolutional Neural Network model for text classification, as proposed in the paper Recurrent Convolutional Neural Networks for Text Classification.
在文本表示方面,會有超過filter_size的上下文的語義缺失,因此本篇文章利用RNN來進行文本表示,中心詞左側和右側的詞設為trainable,然后將中心詞左側和右側的詞concat作為中心詞的表示。 當前step的中心詞不輸入lstm,僅僅與左側詞和右側詞在lstm的輸出concat。
先經過1層雙向LSTM,該詞的左側的詞正向輸入進去得到一個hidden state(從上往下),該詞的右側反向輸入進去得到一個hidden state(從下往上)。再結合該詞的詞向量,生成一個 1 * 3k 的向量。
再經過全連接層,tanh為非線性函數,得到y2。
再經過最大池化層,得出最大化向量y3.
再經過全連接層,sigmod為非線性函數,得到最終的多分類。
1、結合了中心詞窗口的輸入,其輸出的representation能很好的保留上下文語義信息
2、全連接層+pooling進行特征選擇,獲取全局最重要的特征
RCNN 整體的模型構建流程如下:
1)利用Bi-LSTM獲得上下文的信息,類似于語言模型。
2)將Bi-LSTM獲得的隱層輸出和詞向量拼接[fwOutput,wordEmbedding, bwOutput]。
3)將拼接后的向量非線性映射到低維。
4)向量中的每一個位置的值都取所有時序上的最大值,得到最終的特征向量,該過程類似于max-pool。
5)softmax分類。
七 Adversarial LSTM模型
模型來源于論文Adversarial Training Methods For Semi-Supervised Text Classification
??????? 上圖中左邊為正常的LSTM結構,右圖為Adversarial LSTM結構,可以看出在輸出時加上了噪聲。
Adversarial LSTM的核心思想是通過對word Embedding上添加噪音生成對抗樣本,將對抗樣本以和原始樣本 同樣的形式喂給模型,得到一個Adversarial Loss,通過和原始樣本的loss相加得到新的損失,通過優化該新 的損失來訓練模型,作者認為這種方法能對word embedding加上正則化,避免過擬合。
八 Transformer模型
創新之處在于使用了scaled Dot-Product Attention和Multi-Head Attention
Encoder部分
上面的Q,K和V,被作為一種抽象的向量,主要目的是用來做計算和輔助attention。根據文章我們知道Attention的計算公式如下:
接著是Multi-head Attention:
?
這里的positional encoding需要說明一下:
公式中pos就代表了位置index,然后i就是index所對應的向量值,是一個標量,然后dmodel就是512了。之所以選擇這個函數是因為作者假設它能夠讓模型通過相關的位置學習Attend。
(引入這個的原因就是因為模型里沒有用到RNN和CNN,不能編碼序列順序,因此需要顯示的輸入位置信息.之前用到的由position embedding,作者發現上述方法與這個方法差不多.位置特征在這里是一種重要特征。)
Decoder部分
對比單個encoder和decoder,可以看出,decoder多出了一個encoder-decoder Attention layer,接收encoder部分輸出的向量和decoder自身的self attention出來的向量,然后再進入到全連接的前饋網絡中去,最后向量輸出到下一個的decoder
最后一個decoder輸出的向量會經過Linear層和softmax層。Linear層的作用就是對decoder部分出來的向量做映射成一個logits向量,然后softmax層根據這個logits向量,將其轉換為了概率值,最后找到概率最大值的位置。這樣就完成了解碼的輸出了。
?
?
九? ELMO 預訓練模型
ELMo模型是利用BiLM(雙向語言模型)來預訓練詞的向量表示,可以根據我們的訓練集動態的生成詞的向量表示。ELMo預訓練模型來源于論文:Deep contextualized word representations。具體的ELMo模型的詳細介紹見ELMO模型(Deep contextualized word representation)。
ELMo的模型代碼發布在github上,我們在調用ELMo預訓練模型時主要使用到bilm中的代碼,因此可以將bilm這個文件夾拷貝到自己的項目路徑下,之后需要導入這個文件夾中的類和函數。此外,usage_cached.py,usage_character.py,usage_token.py這三個文件中的代碼是告訴你該怎么去調用ELMo模型動態的生成詞向量。在這里我們使用usage_token.py中的方法,這個計算量相對要小一些。
在使用之前我們還需要去下載已經預訓練好的模型參數權重,打開https://allennlp.org/elmo鏈接,在Pre-trained ELMo Models 這個版塊下總共有四個不同版本的模型,可以自己選擇,我們在這里選擇Small這個規格的模型,總共有兩個文件需要下載,一個"options"的json文件,保存了模型的配置參數,另一個是"weights"的hdf5文件,保存了模型的結構和權重值(可以用h5py讀取看看)。
?
十 BERT 預訓練模型
?? BERT 模型來源于論文BERT: Pre-training of Deep Bidirectional Transformers for?Language Understanding。BERT模型是谷歌提出的基于雙向Transformer構建的語言模型。BERT模型和ELMo有大不同,在之前的預訓練模型(包括word2vec,ELMo等)都會生成詞向量,這種類別的預訓練模型屬于domain transfer。而近一兩年提出的ULMFiT,GPT,BERT等都屬于模型遷移。
BERT 模型是將預訓練模型和下游任務模型結合在一起的,也就是說在做下游任務時仍然是用BERT模型,而且天然支持文本分類任務,在做文本分類任務時不需要對模型做修改。谷歌提供了下面七種預訓練好的模型文件。
BERT模型在英文數據集上提供了兩種大小的模型,Base和Large。Uncased是意味著輸入的詞都會轉變成小寫,cased是意味著輸入的詞會保存其大寫(在命名實體識別等項目上需要)。Multilingual是支持多語言的,最后一個是中文預訓練模型。
?
總結
以上是生活随笔為你收集整理的[深度学习] 自然语言处理 --- 文本分类模型总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何做服务监控
- 下一篇: [深度学习] 自然语言处理---Tran