rostcm6情感分析案例分析_卷积情感分析
這是一個面向小白(比如,本人)的關于情感分析的系列教程 [1]。老鴿子整理了“4 - Convolutional Sentiment Analysis.ipynb”中的內容。
本文任務:使用卷積神經網絡(CNN)來實現句子分類。
簡介
CNN用于分析圖像,包含一個或多個卷積層,緊跟一個或多個線性層。在卷積層中,使用濾波器掃描圖像。掃描后的圖像送入另一個卷積層或線性層。每個濾波器都有大小,如:每次使用大小的濾波器來掃描大小的圖像區域,濾波器有9個對應的權重。
類似于使用濾波器查看圖像區域,使用大小的濾波器來查看文本中兩個連續的詞(bi-gram)。
準備數據
不同于FastText模型,不用明確創建bi-grams并添加到句尾。
因為卷積層的第一個維度是batch,所以可以設置field中的batch_first為True。
劃分訓練集和驗證集;構建詞匯表;創建迭代器。
import?torchfrom?torchtext?import?data
from?torchtext?import?datasets
import?random
import?numpy?as?np
SEED?=?1234
random.seed(SEED)
np.random.seed(SEED)
torch.manual_seed(SEED)
torch.backends.cudnn.deterministic?=?True
TEXT?=?data.Field(tokenize='spacy',?batch_first=True)
LABEL?=?data.LabelField(dtype=torch.float)
train_data,?test_data?=?datasets.IMDB.splits(TEXT,?LABEL)
train_data,?valid_data?=?train_data.split(random_state?=?random.seed(SEED))
#-----------------------------
MAX_VOCAB_SIZE?=?25_000
TEXT.build_vocab(
????train_data,?
????max_size?=?MAX_VOCAB_SIZE,?
????vectors?=?"glove.6B.100d",?
????unk_init?=?torch.Tensor.normal_
????)
LABEL.build_vocab(train_data)
#-----------------------------
BATCH_SIZE?=?64
device?=?torch.device('cuda'?if?torch.cuda.is_available()?else?'cpu')
train_iterator,?valid_iterator,?test_iterator?=?data.BucketIterator.splits(
????(train_data,?valid_data,?test_data),?
????batch_size=BATCH_SIZE,?
????device=device
????)
構建模型
在二維中可視化句中的詞。每個詞沿一個軸,嵌入沿另一個軸。
使用一個大小的濾波器,每次掃描n個詞。
下圖有4個詞,每個詞有5維嵌入,因此得到一個大小的“圖像”張量。使用一個大小的濾波器,每次掃描兩個詞(黃色)。濾波器的每個元素包含一個權重,它的輸出為10個嵌入元素的加權和。濾波器向下掃描(跨句)到下一個bi-gram,輸出另一個加權和。濾波器再次向下掃描,計算最后的加權和。
濾波器的寬度等于“圖像”的寬度,輸出一個向量,它的長度等于“圖像”的高度減濾波器的高度加1:。
本文所用的模型將有不同大小的濾波器,它們的高度分別為3、4和5,每個大小有100個,從而能夠尋找與評論的情感相關的不同的tri-grams、4-grams和5-grams。
為了確定與情緒相關的最重要的n-gram,最大池化卷積層的輸出。
因為模型有300個不同且重要的n-grams,所以可以認為全連接層用于加權這些n-grams,從而給出最終的判斷。
應用細節
使用nn.Conv2d來實現卷積層:in_channels為輸入通道數;圖像的通道數為3(紅-綠-藍),而文本的通道數為1;out_channels為輸出通道數,kernel_size為濾波器大小:,其中的為n-grams的大小。
RNN的batch位于第二維,而CNN的batch位于第一維。CNN的第二維為通道數(為嵌入添加大小為1的新維度,與卷積層的in_channels=1一致)。
卷積后的激活函數為ReLU。池化層可以處理不同長度的句子,卷積層的輸出取決于它的輸入,不同的batches包含了不同長度的句子。當沒有最大池化層時,線性層的輸入取決于輸入句子的大小。為了解決該問題,可以修剪或填充所有句子到等長。當有最大池化層時,線性層的輸入數等于濾波器個數。
當句子比最大的濾波器還短時,必須填充句子到最大的濾波器長度。因為IMDb數據中的評論都大于5個字長,所以不用擔心該情況。
對級聯的濾波器輸出使用Dropout,再經過線性層得到預測結果。
import?torch.nn?as?nnimport?torch.nn.functional?as?F
class?CNN(nn.Module):
????def?__init__(self,?vocab_size,?embedding_dim,?n_filters,?filter_sizes,?output_dim,?dropout,?pad_idx):
????????super().__init__()
????????
????????self.embedding?=?nn.Embedding(
????????????vocab_size,?
????????????embedding_dim,?
????????????padding_idx=pad_idx
????????????)
????????self.conv_0?=?nn.Conv2d(
????????????in_channels=1,?
????????????out_channels=n_filters,?
????????????kernel_size=(filter_sizes[0],?embedding_dim)
????????????)
????????self.conv_1?=?nn.Conv2d(
????????????in_channels=1,?
????????????out_channels=n_filters,?
????????????kernel_size=(filter_sizes[1],?embedding_dim)
????????????)
????????self.conv_2?=?nn.Conv2d(
????????????in_channels=1,?
????????????out_channels=n_filters,?
????????????kernel_size=(filter_sizes[2],?embedding_dim)
????????????)
????????self.fc?=?nn.Linear(len(filter_sizes)?*?n_filters,?output_dim)
????????self.dropout?=?nn.Dropout(dropout)
????def?forward(self,?text):??????
????????#?text:?????[batch?size,?sent?len]
????????#?embedded:?[batch?size,?sent?len,?emb?dim]
????????embedded?=?self.embedding(text)
????????#?embedded:?[batch?size,?1,?sent?len,?emb?dim]
????????embedded?=?embedded.unsqueeze(1)
????????
????????#?conved_n:?[batch?size,?n_filters,?sent?len?-?filter_sizes[n]?+?1]
????????conved_0?=?F.relu(self.conv_0(embedded).squeeze(3))
????????conved_1?=?F.relu(self.conv_1(embedded).squeeze(3))
????????conved_2?=?F.relu(self.conv_2(embedded).squeeze(3))
????????#?pooled_n:?[batch?size,?n_filters]
????????pooled_0?=?F.max_pool1d(conved_0,?conved_0.shape[2]).squeeze(2)
????????pooled_1?=?F.max_pool1d(conved_1,?conved_1.shape[2]).squeeze(2)
????????pooled_2?=?F.max_pool1d(conved_2,?conved_2.shape[2]).squeeze(2)
????????#?cat:??[batch?size,?n_filters?*?len(filter_sizes)]
????????cat?=?self.dropout(torch.cat((pooled_0,?pooled_1,?pooled_2),?dim=1))
????????return?self.fc(cat)
CNN模型僅用了3種不同大小的濾波器。為了使用任意數量的濾波器,將所有的卷積層放入nn.ModuleList。
class?CNN2d(nn.Module):????def?__init__(self,?vocab_size,?embedding_dim,?n_filters,?filter_sizes,?output_dim,?dropout,?pad_idx):
????????super().__init__()
????????????????
????????self.embedding?=?nn.Embedding(
????????????vocab_size,?
????????????embedding_dim,?
????????????padding_idx=pad_idx
????????????)
????????self.convs?=?nn.ModuleList([
????????????nn.Conv2d(
????????????????in_channels=1,?
????????????????out_channels=n_filters,?
????????????????kernel_size=(fs,?embedding_dim)
????????????????)?
????????????????for?fs?in?filter_sizes
????????????])
????????self.fc?=?nn.Linear(len(filter_sizes)?*?n_filters,?output_dim)
????????self.dropout?=?nn.Dropout(dropout)
????????
????def?forward(self,?text):
????????#?text:?????[batch?size,?sent?len]
????????#?embedded:?[batch?size,?sent?len,?emb?dim]
????????embedded?=?self.embedding(text)
????????#?embedded:?[batch?size,?1,?sent?len,?emb?dim]
????????embedded?=?embedded.unsqueeze(1)
????????
????????#?conved_n:?[batch?size,?n_filters,?sent?len?-?filter_sizes[n]?+?1]
????????conved?=?[F.relu(conv(embedded)).squeeze(3)?for?conv?in?self.convs]
????????#?pooled_n:?[batch?size,?n_filters]
????????pooled?=?[F.max_pool1d(conv,?conv.shape[2]).squeeze(2)?for?conv?in?conved]
????????
????????#?cat:??????[batch?size,?n_filters?*?len(filter_sizes)]
????????cat?=?self.dropout(torch.cat(pooled,?dim=1))
????????return?self.fc(cat)
使用一維卷積層,濾波器的“深度”和寬分別為嵌入的維度和句中的詞數。
class?CNN1d(nn.Module):????def?__init__(self,?vocab_size,?embedding_dim,?n_filters,?filter_sizes,?output_dim,?dropout,?pad_idx):
????????super().__init__()
????????
????????self.embedding?=?nn.Embedding(
????????????vocab_size,?
????????????embedding_dim,?
????????????padding_idx=pad_idx
????????????)
????????self.convs?=?nn.ModuleList([
????????????nn.Conv1d(
????????????????in_channels=embedding_dim,?
????????????????out_channels=n_filters,?
????????????????kernel_size=fs
????????????????)
????????????????for?fs?in?filter_sizes
????????????])
????????self.fc?=?nn.Linear(len(filter_sizes)?*?n_filters,?output_dim)
????????self.dropout?=?nn.Dropout(dropout)
????????
????def?forward(self,?text):
????????#?text:?????[batch?size,?sent?len]
????????#?embedded:?[batch?size,?sent?len,?emb?dim]
????????embedded?=?self.embedding(text)
????????#?embedded:?[batch?size,?emb?dim,?sent?len]
????????embedded?=?embedded.permute(0,?2,?1)
????????
????????#?conved_n:?[batch?size,?n_filters,?sent?len?-?filter_sizes[n]?+?1]
????????conved?=?[F.relu(conv(embedded))?for?conv?in?self.convs]
????????#?pooled_n:?[batch?size,?n_filters]
????????pooled?=?[F.max_pool1d(conv,?conv.shape[2]).squeeze(2)?for?conv?in?conved]
????????
????????#?cat:??[batch?size,?n_filters?*?len(filter_sizes)]
????????cat?=?self.dropout(torch.cat(pooled,?dim=1))
????????return?self.fc(cat)
創建CNN2d(或CNN、CNN1d)類的實例;打印模型中可訓練的參數量;清0“”和“”的初始權重。
INPUT_DIM?=?len(TEXT.vocab)EMBEDDING_DIM?=?100
N_FILTERS?=?100
FILTER_SIZES?=?[3,?4,?5]
OUTPUT_DIM?=?1
DROPOUT?=?0.5
PAD_IDX?=?TEXT.vocab.stoi[TEXT.pad_token]
model?=?CNN2d(INPUT_DIM,?EMBEDDING_DIM,?N_FILTERS,?FILTER_SIZES,?OUTPUT_DIM,?DROPOUT,?PAD_IDX)
#-----------------------------
def?count_parameters(model):
????return?sum(p.numel()?for?p?in?model.parameters()?if?p.requires_grad)
print(f'The?model?has?{count_parameters(model):,}?trainable?parameters')
#-----------------------------
pretrained_embeddings?=?TEXT.vocab.vectors
model.embedding.weight.data.copy_(pretrained_embeddings)
UNK_IDX?=?TEXT.vocab.stoi[TEXT.unk_token]
model.embedding.weight.data[UNK_IDX]?=?torch.zeros(EMBEDDING_DIM)
model.embedding.weight.data[PAD_IDX]?=?torch.zeros(EMBEDDING_DIM)
訓練模型、測試和用戶輸入
該部分與前面幾節對應的代碼相同。當測試模型時,損失為0.337,準確率為85.69%。
用戶的預測結果分別為0.11990132927894592(負面情緒)和0.9671174883842468(正面情緒)。
import?torch.optim?as?optimoptimizer?=?optim.Adam(model.parameters())
criterion?=?nn.BCEWithLogitsLoss()
model?=?model.to(device)
criterion?=?criterion.to(device)
def?binary_accuracy(preds,?y):
????#?round?predictions?to?the?closest?integer
????rounded_preds?=?torch.round(torch.sigmoid(preds))
????correct?=?(rounded_preds?==?y).float()?#?convert?into?float?for?division?
????acc?=?correct.sum()?/?len(correct)
????return?acc
#-----------------------------
def?train(model,?iterator,?optimizer,?criterion):
????epoch_loss?=?0
????epoch_acc?=?0
????
????model.train()
????for?batch?in?iterator:
????????optimizer.zero_grad()
????????predictions?=?model(batch.text).squeeze(1)
????????loss?=?criterion(predictions,?batch.label)
????????acc?=?binary_accuracy(predictions,?batch.label)
????????
????????loss.backward()
????????optimizer.step()
????????
????????epoch_loss?+=?loss.item()
????????epoch_acc?+=?acc.item()
????????
????return?epoch_loss?/?len(iterator),?epoch_acc?/?len(iterator)
#-----------------------------
def?evaluate(model,?iterator,?criterion):
????epoch_loss?=?0
????epoch_acc?=?0
????
????model.eval()
????
????with?torch.no_grad():
????????for?batch?in?iterator:
????????????predictions?=?model(batch.text).squeeze(1)
????????????loss?=?criterion(predictions,?batch.label)
????????????acc?=?binary_accuracy(predictions,?batch.label)
????????????epoch_loss?+=?loss.item()
????????????epoch_acc?+=?acc.item()
????????
????return?epoch_loss?/?len(iterator),?epoch_acc?/?len(iterator)
#-----------------------------
import?time
def?epoch_time(start_time,?end_time):
????elapsed_time?=?end_time?-?start_time
????elapsed_mins?=?int(elapsed_time?/?60)
????elapsed_secs?=?int(elapsed_time?-?(elapsed_mins?*?60))
????return?elapsed_mins,?elapsed_secs
#-----------------------------
N_EPOCHS?=?5
best_valid_loss?=?float('inf')
for?epoch?in?range(N_EPOCHS):
????start_time?=?time.time()
????train_loss,?train_acc?=?train(model,?train_iterator,?optimizer,?criterion)
????valid_loss,?valid_acc?=?evaluate(model,?valid_iterator,?criterion)
????end_time?=?time.time()
????epoch_mins,?epoch_secs?=?epoch_time(start_time,?end_time)
????
????if?valid_loss?????????best_valid_loss?=?valid_loss
????????torch.save(model.state_dict(),?'tut4-model.pt')
????
????print(f'Epoch:?{epoch+1:02}?|?Epoch?Time:?{epoch_mins}m?{epoch_secs}s')
????print(f'\tTrain?Loss:?{train_loss:.3f}?|?Train?Acc:?{train_acc*100:.2f}%')
????print(f'\t?Val.?Loss:?{valid_loss:.3f}?|??Val.?Acc:?{valid_acc*100:.2f}%')
#=============================
#?Test.
model.load_state_dict(torch.load('tut4-model.pt'))
test_loss,?test_acc?=?evaluate(model,?test_iterator,?criterion)
print(f'Test?Loss:?{test_loss:.3f}?|?Test?Acc:?{test_acc*100:.2f}%')
#=============================
#?User?input.
import?spacy
nlp?=?spacy.load('en')
def?predict_sentiment(model,?sentence,?min_len=5):
????model.eval()
????tokenized?=?[tok.text?for?tok?in?nlp.tokenizer(sentence)]
????if?len(tokenized)?????????tokenized?+=?['']?*?(min_len?-?len(tokenized))
????indexed?=?[TEXT.vocab.stoi[t]?for?t?in?tokenized]
????tensor?=?torch.LongTensor(indexed).to(device)
????tensor?=?tensor.unsqueeze(0)
????prediction?=?torch.sigmoid(model(tensor))
????return?prediction.item()
print(predict_sentiment(model,?"This?film?is?terrible"))
print(predict_sentiment(model,?"This?film?is?great"))
[1]https://github.com/bentrevett/pytorch-sentiment-analysis
總結
以上是生活随笔為你收集整理的rostcm6情感分析案例分析_卷积情感分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 拉进路由器黑名单怎么拉出来TP路由器如何
- 下一篇: opencv python 多帧降噪算法