情感分析入门 1-Simple Sentiment Analysis

  最近开始着手毕设了,本来计划是先读懂一篇论文,复现(其实是跑通并理解)其代码,用作demo,后续在此基础上进行改进。我读的论文是 DialogueRNN An Attentive RNN for Emotion Detection in Conversations,在paper with code上有官方高赞代码。本来看起来应该很不错的一个计划,直到我打开了代码,发现自己很多地方并不理解,加之也没有注释,意识到这样的代码不适合入门。在知乎看到一个回答下,对于读论文的流程讲得挺有道理的,读一篇论文,首先读懂公式,然后是文章的重点,之后跑通代码,并理解其亮点部分。读论文的目的是找idea,而不是让论文叫你手把手入门。遂转向github 情感分析入门教程:pytorch-sentiment-analysis。话不多说,今天在这里分享一下Lesson 1 Simple Sentiment Analysis的内容。

  任务是使用电影评论数据集IMDb dataset,分析句子的情感是positive or negative的二分类问题。这里只是用简单的方法来了解的流程,效果的提升优化会在之后的章节讨论。

Introduction

  我们使用通常用于分析时序数据的 recurrent neural network(RNN)。RNN一次输入一组词X={{x_1,..x_T}},并且对于每个词都会输出一个隐藏状态h。我们通过输入当前单词 x_t 和之前单词的隐藏状态h_{t-1},循环使用RNN,来产生下一个隐藏状态h_t。我们把最后的隐藏状态h_T输入线性层 f,来得到我们预测的情感 \widehat{y} = f(h_T)

如下所示是一个例句,RNN的预测值为0,意味着negetive情绪。请注意,我们对于每个单词使用的是相同的RNN,意味着有相同的参数。

 Preparing Data

1.Field:数据应该被怎样处理

Text field:review应该被怎样处理;Label field : sentiment 应该被怎样处理

2.使用"en_core_web_sm"model前,需要先下载python -m spacy download en_core_web_sm

3.dtype=torch.float :TorchText默认设置tensors的类型为LongTensor,但是我们的criterion函数期望输入为FloatTensor。

import torch
from torchtext.legacy import data

#set the random seeds for reproducibility
SEED = 1234

torch.manual_seed(SEED)
#每次返回的卷积算法是固定的,即默认值
torch.backends.cudnn.deterministic = True

#使用spacy分词
#tokenizer_language指定为spacy model

TEXT = data.Field(tokenize = 'spacy',
                  tokenizer_language = 'en_core_web_sm')
LABEL = data.LabelField(dtype = torch.float)

划分训练集和测试集

from torchtext.legacy import datasets

train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)

#print(f'Number of training examples: {len(train_data)}')
#print(f'Number of testing examples: {len(test_data)}')
#check an example
#print(vars(train_data.examples[0]))

划分训练集和验证集

#random_sate = random.seed(SEED): 确保每次能得到相同的训练集和验证集的划分

import random

train_data, valid_data = train_data.split(random_state = random.seed(SEED))

#print(f'Number of training examples: {len(train_data)}')
#print(f'Number of validation examples: {len(valid_data)}')
#print(f'Number of testing examples: {len(test_data)}')

构建词典:数据集中每一个单词的索引表。我们把每个单词构建为一个独热编码(one-hot vector)。独热编码的维度是词典中总的单词的个数。我们训练集中的单词超过100,000个,意味着我们的独热编码超过了100,000维。

有两种方式可以有效减小我们的词典大小,我们可以取出top n个最常出现的单词,也可以忽视出现次数少于m次的单词。我们这里采用前者,只保留top 25,000个单词。对于那些出现在样本中,但是已经被从词典删去的单词,我们用 unkown or sepecial token 代替。

MAX_VOCAB_SIZE = 25_000

TEXT.build_vocab(train_data, max_size = MAX_VOCAB_SIZE)
LABEL.build_vocab(train_data)

#print(f"Unique tokens in TEXT vocabulary: {len(TEXT.vocab)}")
#print(f"Unique tokens in LABEL vocabulary: {len(LABEL.vocab)}")

我们这里只用训练集构造词典。Because when testing any machine learning system you do not want to look at the test set in any way. We do not include the validation set as we want it to reflect the test case as mush as possible.

数据准备的第一步是构造iterators。每次迭代会返回一个batch的数据(indexed and converted into tensors)。

因为一个batch中的所有句子都需要是相同长度,所以短于最长长度的句子会被padded。这里我们使用了BucketIterator。他返回的每个example的长度相似,是每个example的填充最小化。

batch的作用:批量梯度下降(BGD),计算所有样本损失的累加和,更新一次梯度;随机梯度下降(SGD),随机选取一个样本,计算损失,更新一次梯度;小批量梯度下降,选择一批样本,计算损失和,更新一次梯度。

BATCH_SIZE = 64
#使用GPU
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)

Build the Model

我们在__init__方法中定义我们module的layers。我们的三个layers是:an embedding layer,our RNN, and a linear layer。嵌入层用于将稀疏的独热向量(稀疏是因为大多数元素都是0)转换为密集的嵌入向量(密集是因为维度小得多,所有元素都是实数)。除了降低RNN输入的维度外,还有一种理论认为,在这个密集的向量空间中,对review的情绪有相似影响的词距离很近( are mapped close together)。

 最后,将最终的隐藏状态h_T输入全连接层。

text :[sentence length, batch size].是一批句子,句子中的每个词都被转换为了one-hot vector。PyTorch方便地将一个单热向量存储为它的索引值。即表示一个句子的张量只是该句子中每个标记的索引张量。

The input batch通过the embedding layer得到embedded,这为我们提供了句子的密集向量表示。

embedded 输入RNN ,返回两个tensor。output 是来自每个time step的 the hidden state 的拼接, 而hidden只是the final hidden state.我们使用assert语句来验证这一点。assert检验断言是否为真,若断言为假,则返回断言错误并停止运行程序。

最后,我们将最后的隐藏状态hidden 输入线性层f_c,来生成预测。

import torch.nn as nn

class RNN(nn.Module):
    def __init__(self, input_dim, embedding_dim, hidden_dim, output_dim):
        
        super().__init__()
        #输入为input_dim,输出位embedding_dim
        self.embedding = nn.Embedding(input_dim, embedding_dim)
        
        self.rnn = nn.RNN(embedding_dim, hidden_dim)
        
        self.fc = nn.Linear(hidden_dim, output_dim)
        
    def forward(self, text):

        #text = [sent len, batch size]
        
        embedded = self.embedding(text)
        
        #embedded = [sent len, batch size, emb dim]
        
        output, hidden = self.rnn(embedded)
        
        #output = [sent len, batch size, hid dim]
        #hidden = [1, batch size, hid dim]
        
        assert torch.equal(output[-1,:,:], hidden.squeeze(0))
        
        return self.fc(hidden.squeeze(0))

RNN实例化

INPUT_DIM = len(TEXT.vocab)
EMBEDDING_DIM = 100
HIDDEN_DIM = 256
OUTPUT_DIM = 1

model = RNN(INPUT_DIM, EMBEDDING_DIM, HIDDEN_DIM, OUTPUT_DIM)

Train the Model

The BCEWithLogitsLoss criterion carries out both the sigmoid and the binary cross entropy steps.

#create an optimizer
import torch.optim as optim

optimizer = optim.SGD(model.parameters(), lr=1e-3)

criterion = nn.BCEWithLogitsLoss()
#place the model and the criterion on the GPU
model = model.to(device)
criterion = criterion.to(device)

我们用binary_accuracy函数计算准确率。首先使用sigmoid函数将predictions的值挤压到0~1,之后对其进行四舍五入。这个四舍五入的值会将0.5~1转化为1(positive)

def binary_accuracy(preds, y):
    """
    Returns accuracy per batch, i.e. if you get 8/10 right, this returns 0.8, NOT 8
    """

    #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:
        
        #zero the gradients
        optimizer.zero_grad()
        
        #predictions=[batch size,1]
        predictions = model(batch.text).squeeze(1)

        #the loss being averaged over all examples in the batch.
        loss = criterion(predictions, batch.label)
        
        acc = binary_accuracy(predictions, batch.label)
        
        #calculate the gradient of each parameter
        loss.backward()
        
        #update the parameters
        optimizer.step()
        
        #.item() method is used to extract a scalar from a tensor
        epoch_loss += loss.item()
        epoch_acc += acc.item()
        
    return epoch_loss / len(iterator), epoch_acc / len(iterator)

evaluate和train相似,唯一的区别是不需要更新参数。PyTorch在with no_grad()块内的不会计算梯度。evaluate函数剩下的部分和train相同, 只是去除了 optimizer.zero_grad()loss.backward() and optimizer.step(), ,因为我们在计算时不更新模型的参数。

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

在每个epoch,如果valid loss是我们迄今为止看到的最好的,我们将保存模型的参数,然后在训练结束后,我们将在测试集中使用该模型。

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:
        best_valid_loss = valid_loss
        torch.save(model.state_dict(), 'tut1-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 loss and acc

model.load_state_dict(torch.load('tut1-model.pt'))

test_loss, test_acc = evaluate(model, test_iterator, criterion)

print(f'Test Loss: {test_loss:.3f} | Test Acc: {test_acc*100:.2f}%')

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Aspect-based sentiment analysis是一种文本分析技术,旨在识别文本中的不同方面(aspect)并对其情感进行分析。这种技术可以帮助企业了解消费者对其产品或服务的看法,从而改进其营销策略和产品设计。 ### 回答2: 方面情感分析是指一种自然语言处理技术,目的是从文本中提取文本中针对不同方面的情感,并分析和汇总这些情感。这种技术旨在帮助人们更好地理解用户对某个品牌、产品或服务的看法,并帮助企业更好地了解用户需求,提高用户满意度。 方面情感分析首先对文本进行标记化和词性标注等处理,然后从中提取涉及产品、服务或主题的关键词。分析这些关键词所涉及的方面,比如产品的性能、价格、设计等,然后将这些方面的情感值分析出来。 方面情感分析包括两个主要部分:方面分析和情感分析。方面分析着重于提取文本中的方面,包括主观方面(如感官体验、情感反应)和客观方面(如产品功能、价格等)。情感分析则通过对方面中的情感词进行分析,得出情感的极性和程度。 方面情感分析不仅可以帮助企业了解用户对其产品、服务或品牌的看法,还可以帮助企业识别用户需求和改进产品的缺陷,提高市场竞争力。与传统的情感分析相比,方面情感分析更加细致和准确,可以提供更精细的信息,有助于实现精细化营销。 ### 回答3: Aspect-Based Sentiment Analysis(ABSA)是指是一种基于自然语言处理的技术,旨在从观点或情感的角度分析文本中的不同方面。该技术在广告、品牌管理、社交媒体营销和舆情监测等方面具有广泛应用。 ABSA 的过程可以分为三个主要阶段: 首先,通过文本分析,将文本拆分成可独立分析的一系列方面,也被称为特征或属性。这些方面通常从主体产品或服务中提取,例如餐厅的食物、装修、服务、价格和位置等。 其次,对每个方面进行情感分析,以了解每个方面的观点或情感是否是积极、消极或中立的。当进行情感分析时,要考虑不同情感的标记和语义,以为每个方面赋予合适的情感极性。 最后,将每个方面的观点和情感结合起来,以得出文本整体的观点和情感。这可以通过对所有方面的评分进行汇总,基于特定产品或服务的整体观点或情感得出。 ABSA的成功关键在于结合自然语言处理技术和情感分析模型,以使计算机能够识别文本中的不同方面和情感。这项技术对于企业和组织来说非常有价值,因为它可以帮助他们更好地了解客户的需求、反馈和意见,进而提高产品和服务的质量和升级策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值