NLP入门-情感分析系列教程- Simple Sentiment Analysis学习笔记

本系列来源Github自学使用翻译
原文地址:pytorch-sentiment-analysis

Simple Sentiment Analysis

在第一篇教程中不关心实验结果好坏,只介绍基本概念,是读者对情感分析有初步了解。

使用PyTorch和TorchText构建模型用来检测一句话情感(检测句子是持1肯定或0否定态度)本文使用IMDB电影评论数据集。

1 - 介绍

RNN网络简单介绍

输入:一句话(单词序列)X={x1,x2,......xt}该序列依次输入模型(一次输入一个)得到响应隐藏层输出H={h1,h2,......ht }

例:当前单词x2及上一个单词的隐藏层输出h1同时输入RNN得到h2 (本文中h0初始化为一个全零向量)

递推公式:ht = RNN( xt , ht-1 )
输出:得到ht将其送入全连接层,得到所预测的情感y = f ( ht )
结果:下图所示,输入句子 I hate this film经过模型得到输出0,表明本句话持否定态度
在这里插入图片描述

2 - 数据预处理

TorchText中有一个重要的概念Field,它了数据应该怎样被处理(如:分词等)pytorch使用TEXT处理评论字段,使用LABEL处理情感标签(即"pos" 或 “neg”.)

torchtext 认为一个样本是由多个字段(文本字段,标签字段)组成,不同的字段可能会有不同的处理方式。(想要了解更多Field相关内容go here

  • tokenize = 'spacy'完成了将一句话拆分为多个单词的分词操作(默认以空格拆分)
#设置分词函数
TEXT = data.Field(tokenize = 'spacy')
LABEL = data.LabelField(dtype = torch.float)
  • TorchText中内含许多NLP通用数据集,本文是用的IMDB数据集包含50,000个电影评论,每个评论都标记为正面或负面评论。
#加载语料库,并划分训练集和测试集
from torchtext import datasets
train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)
  • 可查看默认划分的测试集和训练集大小
print(f'训练集中样本数: {len(train_data)}') #25000
print(f'测试集中样本数: {len(test_data)}')  #25000
#查看第一个样本的格式
print(vars(train_data[0]))
#vars() 函数返回对象的属性和属性值的字典对象
  • 使用.spilt()划分验证集
#从训练集中划分出一部分作为验证集
import random
#为了使用相同的随即初始化数据,使用初始化种子
#1234是随即数字的位数
SEED =1234
#每次得到相同的随即数字
torch.manual_seed(SEED)
#保证实验的可重复性
torch.backends.cudnn.deterministic = True
# 在同一份数据集上,相同的种子产生相同的结果,不同的种子产生不同的划分结果,split默认7:3
train_data,valid_data = train_data.split(random_state = random.seed(SEED))
#random_state(int),int是随机数的种子
  • 查看划分结果
#查验数据集划分情况
print(f'训练集中样本数: {len(train_data)}')#17500
print(f'验证集中样本数: {len(valid_data)}')#7500
print(f'测试集中样本数: {len(test_data)}') #25000
  • 构建词表,采用one-hot向量,由于词向量过多所以设置只使用出现频率最高的25000个单词构建
#构建词汇表:给每一个单词对应为向量,词汇表中不包含的单词被显示为<unk>
#这个横杠只是为了便于分辨 没有实际意义
MAX_VOCAB_SIZE = 25_000
#只在训练集上构建词表,以保护测试集的隐秘
TEXT.build_vocab(train_data, max_size = MAX_VOCAB_SIZE)
LABEL.build_vocab(train_data)
#查看词表长度
print(f"TEXT vocabulary: {len(TEXT.vocab)}")
#TEXT.vocab为25002,因为包含<unk> <pad>填充,pad是为了保持句子长度的统一
print(f"LABEL vocabulary: {len(LABEL.vocab)}") # 2
#查看词表中出现频率较高的单词及其出现频率
print(TEXT.vocab.freqs.most_common(20))
#也可以用下面的方式只查看频率较高词汇
print(TEXT.vocab.itos[:10])
#可以查看标签所对应数字化向量
print(LABEL.vocab.stoi)
print(TEXT.vocab.stoi)
  • 构建迭代器
#处理数据的最后一步是构建迭代器
BATCH_SIZE = 64
#选择是否使用GPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
#使用Buketlterator迭代器,他返回一个Batch,最大程度的减少了每个实例的填充量
train_iterator, valid_iterator, test_iterator = data.BucketIterator.splits(
    (train_data, valid_data, test_data),
    batch_size = BATCH_SIZE,
    device = device)

3 - 构建模型

  • Pytorch中nn.Module.Embedding使向量降维
    在这里插入图片描述
  • 句子的输入纬度为[sent len, batch size]

在这里可能会产生困惑为什么不是[sent len,词向量维度, batch size],因为 pytorch使用index value代表句子的词向量维度

import torch.nn as nn

class RNN(nn.Module):
    def __init__(self, input_dim, embedding_dim, hidden_dim, output_dim):
        super().__init__()

        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]
        #首先输入的是one-hot向量,和词汇表大小一样
        embedded = self.embedding(text)
        #经过embedding层后变为稠密的向量
        # embedded = [sent len, batch size, emb dim]
        #隐藏层的维度取决于隐藏层的cell数目
        output, hidden = self.rnn(embedded)
        # output = [sent len, batch size, hid dim]
        # hidden = [1, batch size, hid dim]
        #squeeze()方法将第一列维度删去
        assert torch.equal(output[-1, :, :], hidden.squeeze(0))
        #返回最后一个隐藏层输出
        return self.fc(hidden.squeeze(0))

4 - 训练模型

import torch.optim as optim
#创建优化器,采用梯度下降,学习率为e-3
#优化器就是需要根据网络反向传播的梯度信息来更新网络的参数,以起到降低loss函数计算值的作用
#第一个参数是可训练参数,第二个参数是学习率
optimizer = optim.SGD(model.parameters(), lr=1e-3)
#损失函数
criterion = nn.BCEWithLogitsLoss()
#定义精度计算函数
#返回每个batch的精确度
def binary_accuracy(preds, y):
    #round预测出距离最近的整数,比如0.34 距离0和1选出更近的
    rounded_preds = torch.round(torch.sigmoid(preds))
    #转换为浮点数进行除法
    correct = (rounded_preds == y).float()
    acc = correct.sum() / len(correct)
    return acc


def train(model, iterator, optimizer, criterion):
    #初始化损失函数和准确度
    epoch_loss = 0
    epoch_acc = 0
    #将模型设置为train模式
    model.train()

    for batch in iterator:
        #将梯度置零
        optimizer.zero_grad()
        #predictions通常初始化为[batch size, 1]
        # 但criterion要求[batch]输入,所以移除维度为1的维度
        predictions = model(batch.text).squeeze(1)
        #计算损失
        loss = criterion(predictions, batch.label)
        #计算准确率
        acc = binary_accuracy(predictions, batch.label)
        #计算每个参数的梯度
        loss.backward()
        #更新参数
        optimizer.step()
        #item()方法从仅包含单个值的张量中提取标量
        epoch_loss += loss.item()
        epoch_acc += acc.item()

    return epoch_loss / len(iterator), epoch_acc / len(iterator)


5 - 模型评估

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)

6 - 迭代训练


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}%')

7 - 总结

第一篇文章仅简单介绍使用TorchText处理一个分类模型的方法,不关注准确率及损失函数的改进。第二篇进行如下优化:

  • 打包填充序列
  • 使用词嵌入预训练模型
  • 不同的的RNN架构
  • 双向及多层RNN
  • 改进正则化及不同的优化器
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值