情感分析
隨著在線社交媒體和評論平臺的快速發(fā)展,大量評論的數據被記錄下來。這些數據具有支持決策過程的巨大潛力。 情感分析(sentiment analysis)研究人們在文本中 (如產品評論、博客評論和論壇討論等)“隱藏”的情緒。 它在廣泛應用于政治(如公眾對政策的情緒分析)、 金融(如市場情緒分析)和營銷(如產品研究和品牌管理)等領域。
由于情感可以被分類為離散的極性或尺度(例如,積極的和消極的),我們可以將情感分析看作一項文本分類任務,它將可變長度的文本序列轉換為固定長度的文本類別。在本章中,我們將使用斯坦福大學的大型電影評論數據集(large movie review dataset)進行情感分析。它由一個訓練集和一個測試集組成,其中包含從IMDb下載的25000個電影評論。在這兩個數據集中,“積極”和“消極”標簽的數量相同,表示不同的情感極性。
文章內容來自李沐大神的《動手學深度學習》并加以我的理解,感興趣可以去https://zh-v2.d2l.ai/查看完整書籍
數據集
讀取數據集
讀取訓練和測試數據集。每個樣本都是一個評論及其標簽:1表示“積極”,0表示“消極”。
import os
import torch
from torch import nn
from d2l import torch as d2l
#@save
d2l.DATA_HUB['aclImdb'] = (
'http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz',
'01ada507287d82875905620988597833ad4e0903')
data_dir = d2l.download_extract('aclImdb', 'aclImdb')
#@save
def read_imdb(data_dir, is_train):
"""讀取IMDb評論數據集文本序列和標簽"""
data, labels = [], []
for label in ('pos', 'neg'):
folder_name = os.path.join(data_dir, 'train' if is_train else 'test',
label)
for file in os.listdir(folder_name):
with open(os.path.join(folder_name, file), 'rb') as f:
review = f.read().decode('utf-8').replace('\n', '')
data.append(review)
labels.append(1 if label == 'pos' else 0)
return data, labels
train_data = read_imdb(data_dir, is_train=True)
print('訓練集數目:', len(train_data[0]))
for x, y in zip(train_data[0][:3], train_data[1][:3]):
print('標簽:', y, 'review:', x[0:60])
預處理數據集
將每個單詞作為一個詞元,過濾掉出現不到5次的單詞,我們從訓練數據集中創(chuàng)建一個詞表。
train_tokens = d2l.tokenize(train_data[0], token='word')
vocab = d2l.Vocab(train_tokens, min_freq=5, reserved_tokens=['<pad>'])
在詞元化之后,讓我們繪制評論詞元長度的直方圖。
d2l.set_figsize()
d2l.plt.xlabel('# tokens per review')
d2l.plt.ylabel('count')
d2l.plt.hist([len(line) for line in train_tokens], bins=range(0, 1000, 50));
正如我們所料,評論的長度各不相同。為了每次處理一小批量這樣的評論,我們通過截斷和填充將每個評論的長度設置為500。
def truncate_pad(line, num_steps, padding_token):
if len(line) > num_steps:
return line[:num_steps] # Truncate
return line + [padding_token] * (num_steps - len(line)) # Pad
如果長度大于num_steps的,我們將其截斷,小于num_steps的,我們將其填充為<pad>
num_steps = 500 # 序列長度
train_features = torch.tensor([truncate_pad(
vocab[line], num_steps, vocab['<pad>']) for line in train_tokens])
print(train_features.shape)
創(chuàng)建數據迭代器
現在我們可以創(chuàng)建數據迭代器了。在每次迭代中,都會返回一小批量樣本。
train_iter = d2l.load_array((train_features,torch.tensor(train_data[1])), 64)
for X, y in train_iter:
print('X:', X.shape, ', y:', y.shape)
break
print('小批量數目:', len(train_iter))
整合代碼
#@save
def load_data_imdb(batch_size, num_steps=500):
"""返回數據迭代器和IMDb評論數據集的詞表"""
data_dir = d2l.download_extract('aclImdb', 'aclImdb')
train_data = read_imdb(data_dir, True)
test_data = read_imdb(data_dir, False)
train_tokens = d2l.tokenize(train_data[0], token='word')
test_tokens = d2l.tokenize(test_data[0], token='word')
vocab = d2l.Vocab(train_tokens, min_freq=5)
train_features = torch.tensor([d2l.truncate_pad(
vocab[line], num_steps, vocab['<pad>']) for line in train_tokens])
test_features = torch.tensor([d2l.truncate_pad(
vocab[line], num_steps, vocab['<pad>']) for line in test_tokens])
train_iter = d2l.load_array((train_features, torch.tensor(train_data[1])),
batch_size)
test_iter = d2l.load_array((test_features, torch.tensor(test_data[1])),
batch_size,
is_train=False)
return train_iter, test_iter, vocab
使用循環(huán)神經網絡實現
與詞相似度和類比任務一樣,我們也可以將預先訓練的詞向量應用于情感分析。由于IMDb評論數據集不是很大,使用在大規(guī)模語料庫上預訓練的文本表示可以減少模型的過擬合。作為圖中所示的具體示例,我們將使用預訓練的GloVe模型來表示每個詞元,并將這些詞元表示送入多層雙向循環(huán)神經網絡以獲得文本序列表示,該文本序列表示將被轉換為情感分析輸出 (Maas et al., 2011)。對于相同的下游應用,我們稍后將考慮不同的架構選擇。
import torch
from torch import nn
from d2l import torch as d2l
batch_size = 64
train_iter, test_iter, vocab = d2l.load_data_imdb(batch_size)
使用循環(huán)神經網絡表示單個文本
在文本分類任務(如情感分析)中,可變長度的文本序列將被轉換為固定長度的類別。在下面的BiRNN類中,雖然文本序列的每個詞元經由嵌入層(self.embedding)獲得其單獨的預訓練GloVe表示,但是整個序列由雙向循環(huán)神經網絡(self.encoder)編碼。更具體地說,雙向長短期記憶網絡在初始和最終時間步的隱狀態(tài)(在最后一層)被連結起來作為文本序列的表示。然后,通過一個具有兩個輸出(“積極”和“消極”)的全連接層(self.decoder),將此單一文本表示轉換為輸出類別。
class BiRNN(nn.Module):
def __init__(self, vocab_size, embed_size, num_hiddens,
num_layers, **kwargs):
super(BiRNN, self).__init__(**kwargs)
self.embedding = nn.Embedding(vocab_size, embed_size)
# 將bidirectional設置為True以獲取雙向循環(huán)神經網絡
self.encoder = nn.LSTM(embed_size, num_hiddens, num_layers=num_layers,
bidirectional=True)
self.decoder = nn.Linear(4 * num_hiddens, 2)
def forward(self, inputs):
# inputs的形狀是(批量大小,時間步數)
# 因為長短期記憶網絡要求其輸入的第一個維度是時間維,
# 所以在獲得詞元表示之前,輸入會被轉置。
# 輸出形狀為(時間步數,批量大小,詞向量維度)
embeddings = self.embedding(inputs.T)
self.encoder.flatten_parameters()
# 返回上一個隱藏層在不同時間步的隱狀態(tài),
# outputs的形狀是(時間步數,批量大小,2*隱藏單元數)
outputs, _ = self.encoder(embeddings)
# 連結初始和最終時間步的隱狀態(tài),作為全連接層的輸入,
# 其形狀為(批量大小,4*隱藏單元數)
encoding = torch.cat((outputs[0], outputs[-1]), dim=1)
outs = self.decoder(encoding)
return outs
讓我們構造一個具有兩個隱藏層的雙向循環(huán)神經網絡來表示單個文本以進行情感分析。
embed_size, num_hiddens, num_layers = 100, 100, 2
devices = d2l.try_all_gpus()
net = BiRNN(len(vocab), embed_size, num_hiddens, num_layers)
def init_weights(m):
if type(m) == nn.Linear:
nn.init.xavier_uniform_(m.weight)
if type(m) == nn.LSTM:
for param in m._flat_weights_names:
if "weight" in param:
nn.init.xavier_uniform_(m._parameters[param])
net.apply(init_weights);
加載預訓練的詞向量
下面,我們?yōu)樵~表中的單詞加載預訓練的100維(需要與embed_size一致)的GloVe嵌入。
glove_embedding = d2l.TokenEmbedding('glove.6b.100d')
打印詞表中所有詞元向量的形狀。
embeds = glove_embedding[vocab.idx_to_token]
embeds.shape
我們使用這些預訓練的詞向量來表示評論中的詞元,并且在訓練期間不要更新這些向量。
net.embedding.weight.data.copy_(embeds)
net.embedding.weight.requires_grad = False
訓練和評估模型
lr, num_epochs = 0.01, 5
trainer = torch.optim.Adam(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss(reduction="none")
d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs,
devices)
我們定義以下函數來使用訓練好的模型net預測文本序列的情感。
#@save
def predict_sentiment(net, vocab, sequence):
"""預測文本序列的情感"""
sequence = torch.tensor(vocab[sequence.split()], device=d2l.try_gpu())
label = torch.argmax(net(sequence.reshape(1, -1)), dim=1)
return 'positive' if label == 1 else 'negative'
最后,讓我們使用訓練好的模型對簡單的句子進行情感預測。
predict_sentiment(net, vocab, 'this movie is so great')
使用卷積神經網絡實現
雖然卷積神經網絡最初是為計算機視覺設計的,但它也被廣泛用于自然語言處理。簡單地說,只要將任何文本序列想象成一維圖像即可。通過這種方式,一維卷積神經網絡可以處理文本中的局部特征,例如 n n n元語法。
本節(jié)將使用textCNN模型來演示如何設計一個表示單個文本 (Kim, 2014)的卷積神經網絡架構。
一維卷積
在介紹該模型之前,讓我們先看看一維卷積是如何工作的。請記住,這只是基于互相關運算的二維卷積的特例。
如圖中所示,在一維情況下,卷積窗口在輸入張量上從左向右滑動。在滑動期間,卷積窗口中某個位置包含的輸入子張量和核張量按元素相乘。這些乘法的總和在輸出張量的相應位置給出單個標量值
我們在下面的corr1d函數中實現了一維互相關。給定輸入張量X和核張量K,它返回輸出張量Y。
def corr1d(X, K):
w = K.shape[0]
Y = torch.zeros((X.shape[0] - w + 1))
for i in range(Y.shape[0]):
Y[i] = (X[i: i + w] * K).sum()
return Y
X, K = torch.tensor([0, 1, 2, 3, 4, 5, 6]), torch.tensor([1, 2])
corr1d(X, K)
對于任何具有多個通道的一維輸入,卷積核需要具有相同數量的輸入通道。然后,對于每個通道,對輸入的一維張量和卷積核的一維張量執(zhí)行互相關運算,將所有通道上的結果相加以產生一維輸出張量。 下圖演示了具有3個輸入通道的一維互相關操作。
我們可以實現多個輸入通道的一維互相關運算
def corr1d_multi_in(X, K):
# 首先,遍歷'X'和'K'的第0維(通道維)。然后,把它們加在一起
return sum(corr1d(x, k) for x, k in zip(X, K))
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 = torch.tensor([[1, 2], [3, 4], [-1, -3]])
corr1d_multi_in(X, K)
注意,多輸入通道的一維互相關等同于單輸入通道的二維互相關。
最大時間匯聚層
類似地,我們可以使用匯聚層從序列表示中提取最大值,作為跨時間步的最重要特征。textCNN中使用的最大時間匯聚層的工作原理類似于一維全局匯聚 (Collobert et al., 2011)。對于每個通道在不同時間步存儲值的多通道輸入,每個通道的輸出是該通道的最大值。請注意,最大時間匯聚允許在不同通道上使用不同數量的時間步。
textCNN模型
使用一維卷積和最大時間匯聚,textCNN模型將單個預訓練的詞元表示作為輸入,然后獲得并轉換用于下游應用的序列表示。
對于具有由 d d d維向量表示的 n n n個詞元的單個文本序列,輸入張量的寬度、高度和通道數分別為 n n n、 1 1 1和 d d d。textCNN模型將輸入轉換為輸出,如下所示:
-
定義多個一維卷積核,并分別對輸入執(zhí)行卷積運算。具有不同寬度的卷積核可以捕獲不同數目的相鄰詞元之間的局部特征。
-
在所有輸出通道上執(zhí)行最大時間匯聚層,然后將所有標量匯聚輸出連結為向量。
-
使用全連接層將連結后的向量轉換為輸出類別。Dropout可以用來減少過擬合。
定義模型
我們在下面的類中實現textCNN模型。除了用卷積層代替循環(huán)神經網絡層外,我們還使用了兩個嵌入層:一個是可訓練權重,另一個是固定權重。
class TextCNN(nn.Module):
def __init__(self, vocab_size, embed_size, kernel_sizes, num_channels,
**kwargs):
super(TextCNN, self).__init__(**kwargs)
self.embedding = nn.Embedding(vocab_size, embed_size)
# 這個嵌入層不需要訓練
self.constant_embedding = nn.Embedding(vocab_size, embed_size)
self.dropout = nn.Dropout(0.5)
self.decoder = nn.Linear(sum(num_channels), 2)
# 最大時間匯聚層沒有參數,因此可以共享此實例
self.pool = nn.AdaptiveAvgPool1d(1)
self.relu = nn.ReLU()
# 創(chuàng)建多個一維卷積層
self.convs = nn.ModuleList()
for c, k in zip(num_channels, kernel_sizes):
self.convs.append(nn.Conv1d(2 * embed_size, c, k))
def forward(self, inputs):
# 沿著向量維度將兩個嵌入層連結起來,
# 每個嵌入層的輸出形狀都是(批量大小,詞元數量,詞元向量維度)連結起來
embeddings = torch.cat((
self.embedding(inputs), self.constant_embedding(inputs)), dim=2)
# 根據一維卷積層的輸入格式,重新排列張量,以便通道作為第2維
embeddings = embeddings.permute(0, 2, 1)
# 每個一維卷積層在最大時間匯聚層合并后,獲得的張量形狀是(批量大小,通道數,1)
# 刪除最后一個維度并沿通道維度連結
encoding = torch.cat([
torch.squeeze(self.relu(self.pool(conv(embeddings))), dim=-1)
for conv in self.convs], dim=1)
outputs = self.decoder(self.dropout(encoding))
return outputs
讓我們創(chuàng)建一個textCNN實例。它有3個卷積層,卷積核寬度分別為3、4和5,均有100個輸出通道。
embed_size, kernel_sizes, nums_channels = 100, [3, 4, 5], [100, 100, 100]
devices = d2l.try_all_gpus()
net = TextCNN(len(vocab), embed_size, kernel_sizes, nums_channels)
def init_weights(m):
if type(m) in (nn.Linear, nn.Conv1d):
nn.init.xavier_uniform_(m.weight)
net.apply(init_weights);
加載預訓練詞向量
glove_embedding = d2l.TokenEmbedding('glove.6b.100d')
embeds = glove_embedding[vocab.idx_to_token]
net.embedding.weight.data.copy_(embeds)
net.constant_embedding.weight.data.copy_(embeds)
net.constant_embedding.weight.requires_grad = False
訓練和評估模型
現在我們可以訓練textCNN模型進行情感分析。
lr, num_epochs = 0.001, 5
trainer = torch.optim.Adam(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss(reduction="none")
d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)
下面,我們使用訓練好的模型來預測兩個簡單句子的情感。文章來源:http://www.zghlxwxcb.cn/news/detail-706035.html
d2l.predict_sentiment(net, vocab, 'this movie is so great')
文章來源地址http://www.zghlxwxcb.cn/news/detail-706035.html
到了這里,關于自然語言處理應用(一):情感分析的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!