Bert 实现情感分析任务

BERT

Bert (Bidirectional Encoder Representations from Transformers)预训练模型是 Google 2018开源的自然语言模型,主要有以下特点。

  1. 像它名字一样,BERT最显著的特点是其能够为文本中的每个标记考虑双向上下文。与传统的基于先前标记预测标记的语言模型(从左到右或单向模型)不同,它查看前后标记(一次查看整个序列),以理解和预测单词的上下文。
  2. 采用了 Transformer 架构,通过自注意力机制关注自身与句子中其他单词的关系。
  3. 通过 MLM 和 NSP 两个任务进行预训练,MLM 在预训练期间,随机遮蔽句子的词,模型的目标是仅基于其上下文预测掩蔽字的原始值。在NSP中,模型给出句子对,并必须预测第二句是否是原始文档中的后续句子。
  4. BERT 可以添加一个额外的输出层进行微调,而无需进行大量的任务特定修改。这包括问答、情感分析和语言推理等任务。微调步骤略微调整预训练参数,以专门为手头的特定任务定制模型,利用在预训练期间学习到的丰富表示。

本文通过微调 BERT 实现情感分析。

Transformer 模型中的QKV

开始代码之前,再回顾一下 Q、K、V,这是三个 Tansformer 中最重要的公式,

查询(Q) Q 在自注意力层中代表当前词,Q 帮助模型理解在特定上下文中哪些信息是重要的,可以理解为:问那一个词在句子中是需要关注的。

键(K) 用于与Q(查询)进行匹配,键的作用是作为一种标识,最终生成相关性得分。

值(V) 表示与每个Token关联的实际内容,当 Q 对应的Token被认为是重要的,相应的值就会在输出中获得更多的关注,V决定了自注意力的输出。

公式如下:
在这里插入图片描述

  1. 首先计算注意力得分:Q和 K 之间的点积,得到注意力得分。这里可以简单理解一下,两个向量的乘法,也是两个向量的内积,内积越大,说明其2个向量相似度越高。
  2. Softmax函数:将这些得分通过softmax函数进行标准化,这样得分就转换为概率形式,表明每个值(V)的相对重要性(权重)。
  3. 最后,模型将这些标准化的得分与对应的值(V)相乘,加权输出。

Pytorch 实现情感分析

安装依赖
pip install addict
数据准备
import random
import time
import numpy as np
from tqdm import tqdm
import torch 
from torch import nn
import torch.optim as optim
import torchtext

# 设定随机数的种子,
torch.manual_seed(1234)
np.random.seed(1234)
random.seed(1234)

#生成预处理和单词分割的函数
import re
import string
from utils.bert import BertTokenizer
#来自文件夹“utils”的bert.py


def preprocessing_text(text):
    '''IMDb预处理'''
    # 删除换行代码
    text = re.sub('<br />', '', text)

    #将逗号和句号以外的标点符号全部替换成空格
    for p in string.punctuation:
        if (p == ".") or (p == ","):
            continue
        else:
            text = text.replace(p, " ")

    #在句号和逗号前后插入空格
    text = text.replace(".", " . ")
    text = text.replace(",", " , ")
    return text


#将逗号和句号以外的标点符号全部替换成空格
tokenizer_bert = BertTokenizer(
    vocab_file="./vocab/bert-base-uncased-vocab.txt", do_lower_case=True)


#定义同时负责预处理和分词处理的函数
#指定分词处理的函数,注意不要使用tokenizer_bert,而应指定使tokenizer_bert.tokenize
def tokenizer_with_preprocessing(text, tokenizer=tokenizer_bert.tokenize):
    text = preprocessing_text(text)
    ret = tokenizer(text)  # tokenizer_bert
    return ret


#定义在读入数据时,对读到的内容应做的处理
max_length = 256

TEXT = torchtext.data.Field(sequential=True, tokenize=tokenizer_with_preprocessing, use_vocab=True,
                            lower=True, include_lengths=True, batch_first=True, fix_length=max_length, init_token="[CLS]", eos_token="[SEP]", pad_token='[PAD]', unk_token='[UNK]')
LABEL = torchtext.data.Field(sequential=False, use_vocab=False)

# (注释):再次确认各个参数
# sequential:  数据长度是否可变?由于文章长度是不固定的,因此指定True,标签则指定False
# tokenize:  用于指定读入文章时所需执行的预处理和分词处理函数
# use_vocab:指定是否将单词添加到词汇表中
# lower:指定是否将英文字母转换为小写字母
# include_length: 指定是否返回文章的单词数量
# batch_first:指定是否在开头处生成批次的维度信息
# fix_length::指定是否确保所有文章都为相同长度,长度不足的填充处理
# init_token, eos_token, pad_token, unk_token:指定使用什么单词来表示、文章开头、文章结尾、填充和未知单词

#从data文件夹中读取各个tsv文件
#使用BERT进行处理,执行时间大约为10分钟
train_val_ds, test_ds = torchtext.data.TabularDataset.splits(
    path='./data/', train='IMDb_train.tsv',
    test='IMDb_test.tsv', format='tsv',
    fields=[('Text', TEXT), ('Label', LABEL)])

#使用torchtext.data.Dataset的split函数将数据划分为训练数据和验证数据
train_ds, val_ds = train_val_ds.split(
    split_ratio=0.8, random_state=random.seed(1234))

#BERT是使用BERT掌握的所有单词来创建BertEmbedding模块的,因此将使用全部单词作为词汇表
# 为此不会使用训练数据来生成词汇表

# #首先为BERT准备字典型变量
from utils.bert import BertTokenizer, load_vocab

vocab_bert, ids_to_tokens_bert = load_vocab(
    vocab_file="./vocab/bert-base-uncased-vocab.txt")


#虽然很想写成TEXT.vocab.stoi= vocab_bert (stoi意为string_to_ID,将单词转换为 ID 的字典的形式
#但是如果不执行一次bulild_vocab,TEXT对象就不会初始化vocab的成员变量
#程序会产生“'Field' object has no attribute 'vocab'”这一错误信息

#首先调用build_vocab创建词汇表,然后替换BERT的词汇表
TEXT.build_vocab(train_ds, min_freq=1)
TEXT.vocab.stoi = vocab_bert

#创建DataLoader(在torchtext中被称为iterater)
batch_size = 32   #BERT中经常使用16和32

train_dl = torchtext.data.Iterator(
    train_ds, batch_size=batch_size, train=True)

val_dl = torchtext.data.Iterator(
    val_ds, batch_size=batch_size, train=False, sort=False)

test_dl = torchtext.data.Iterator(
    test_ds, batch_size=batch_size, train=False, sort=False)

#集中保存到字典对象中
dataloaders_dict = {"train": train_dl, "val": val_dl}

#确认执行结果,使用验证数据的数据集进行确认
batch = next(iter(val_dl))
print(batch.Text)
print(batch.Label)

#确认小批次中第一句话的内容
text_minibatch_1 = (batch.Text[0][1]).numpy()

#将ID还原成单词
text = tokenizer_bert.convert_ids_to_tokens(text_minibatch_1)

print(text)


准备模型

class BertForIMDb(nn.Module):
    '''在BERT模型中增加了IMDb的正面/负面分析功能的模型'''

    def __init__(self, net_bert):
        super(BertForIMDb, self).__init__()

       #BERT模块
        self.bert = net_bert  # BERTモデル

        #在head中添加正面 / 负面预测
        #输入是BERT输出的特征量的维度,输出是正面和负面这两种
        self.cls = nn.Linear(in_features=768, out_features=2)

        #权重初始化处理
        nn.init.normal_(self.cls.weight, std=0.02)
        nn.init.normal_(self.cls.bias, 0)

    def forward(self, input_ids, token_type_ids=None, attention_mask=None, output_all_encoded_layers=False, attention_show_flg=False):
        '''
        input_ids: 形状为[batch_size, sequence_length]的文章的单词ID序列
        token_type_ids:形状为[batch_size, sequence_length],表示每个单词是属于第一句还是第二句的id
        attention_mask:与Transformer的掩码作用相同的掩码
        output_all_encoded_layers:用于指定是返回全部12个Transformer的列表还是只返回最后一项的标识
        attention_show_flg:指定是否返回Self−Attention的权重的标识
        '''

        #BERT的基础模型部分的正向传播
        #进行正向传播处理る
        if attention_show_flg == True:
           '''指定attention_show时,也同时返回attention_probs'''
           encoded_layers, pooled_output, attention_probs = self.bert(
                input_ids, token_type_ids, attention_mask, output_all_encoded_layers, attention_show_flg)
        elif attention_show_flg == False:
            encoded_layers, pooled_output = self.bert(
                input_ids, token_type_ids, attention_mask, output_all_encoded_layers, attention_show_flg)

        #使用输入文章的第一个单词[CLS]的特征量进行正面/负面分类处理
        vec_0 = encoded_layers[:, 0, :]
        vec_0 = vec_0.view(-1, 768)  # 将size转换为batch size、hidden size
        out = self.cls(vec_0)

       #指定attention_show时,也同时返回attention_probs(位于最后一位的)
        if attention_show_flg == True:
            return out, attention_probs
        elif attention_show_flg == False:
            return out

#网络设置完毕
net = BertForIMDb(net_bert)

#设置为训练模式
net.train()

print('网络设置完毕')

#只处理位于最后的BertLayer模块的梯度计算和添加的分类适配器

#1.首先,将所有的梯度计算设置为False
for name, param in net.named_parameters():
    param.requires_grad = False

#2.设置对位于最后的BertLayer模块进行梯度计算
for name, param in net.bert.encoder.layer[-1].named_parameters():
    param.requires_grad = True

#3.设置打开识别器的梯度计算
for name, param in net.cls.named_parameters():
    param.requires_grad = True

#设置最优化算法

#BERT的原有部分作为精调
optimizer = optim.Adam([
    {'params': net.bert.encoder.layer[-1].parameters(), 'lr': 5e-5},
    {'params': net.cls.parameters(), 'lr': 5e-5}
], betas=(0.9, 0.999))

#设置损失函数
criterion = nn.CrossEntropyLoss()
# nn.LogSoftmax()を計算してからnn.NLLLoss(negative log likelihood loss)を計算

#创建用于训练模型的函数


def train_model(net, dataloaders_dict, criterion, optimizer, num_epochs):

    #确认GPU是否可用
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("使用的设备:", device)
    print('-----start-------')

    #将网络载入GPU中
    net.to(device)

    #如果网络结构比较固定,则开启硬件加速
    torch.backends.cudnn.benchmark = True

    #小批次的尺寸
    batch_size = dataloaders_dict["train"].batch_size

   #epoch的循环
    for epoch in range(num_epochs):
        #每轮epoch的训练和验证循环
        for phase in ['train', 'val']:
            if phase == 'train':
                net.train()  #将模型设置为训练模式
            else:
                net.eval()  #将模型设置为验证模式

            epoch_loss = 0.0  #epoch的损失总和
            epoch_corrects = 0  #epoch的正确答案数量
            iteration = 1

            #保存开始时间
            t_epoch_start = time.time()
            t_iter_start = time.time()

            #以minibatch为单位从数据加载器中读取数据的循环
            for batch in (dataloaders_dict[phase]):
                #batch是Text和Lable的字典型变量

                #如果能使用GPU,则将数据送入GPU中
                inputs = batch.Text[0].to(device)  # 文章
                labels = batch.Label.to(device)  # 标签

                #初始化optimizer
                optimizer.zero_grad()

              #计算正向传播
                with torch.set_grad_enabled(phase == 'train'):

                    #输入BertForIMDb中
                    outputs = net(inputs, token_type_ids=None, attention_mask=None,
                                  output_all_encoded_layers=False, attention_show_flg=False)

                    loss = criterion(outputs, labels)  #计算损失

                    _, preds = torch.max(outputs, 1)  #对标签进行预测

                    #训练时执行反向传播
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()

                        if (iteration % 10 == 0): #每10个iter显示一次loss
                            t_iter_finish = time.time()
                            duration = t_iter_finish - t_iter_start
                            acc = (torch.sum(preds == labels.data)
                                   ).double()/batch_size
                            print('迭代 {} || Loss: {:.4f} || 10iter: {:.4f} sec. ||本次迭代的准确率:{}'.format(
                                iteration, loss.item(), duration, acc))
                            t_iter_start = time.time()

                    iteration += 1

                   #更新损失和正确答案数量的合计值
                    epoch_loss += loss.item() * batch_size
                    epoch_corrects += torch.sum(preds == labels.data)

            #每轮epoch的loss和准确率
            t_epoch_finish = time.time()
            epoch_loss = epoch_loss / len(dataloaders_dict[phase].dataset)
            epoch_acc = epoch_corrects.double(
            ) / len(dataloaders_dict[phase].dataset)

            print('Epoch {}/{} | {:^5} |  Loss: {:.4f} Acc: {:.4f}'.format(epoch+1, num_epochs,
                                                                           phase, epoch_loss, epoch_acc))
            t_epoch_start = time.time()

    return net


开始训练

#执行学习和验证处理
num_epochs = 2
net_trained = train_model(net, dataloaders_dict,
                          criterion, optimizer, num_epochs=num_epochs)

#对完成学习的网络参数进行保存
save_path = './weights/bert_fine_tuning_IMDb.pth'
torch.save(net_trained.state_dict(), save_path)

测试

#对使用测试数据时模型的准确率进行求解
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

net_trained.eval()   #将模型设置为验证模式
net_trained.to(device) #如果GPU可用,则将数据送入GPU中

#记录epoch的正确答案数量的变量
epoch_corrects = 0

for batch in tqdm(test_dl):  #test数据的DataLoader
    #test数据的DataLoader
    #如果GPU可用,则将数据送入GPU中
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    inputs = batch.Text[0].to(device)  # 文章
    labels = batch.Label.to(device)  #标签

    #计算正向传播
    with torch.set_grad_enabled(False):

        #输入BertForIMDb中
        outputs = net_trained(inputs, token_type_ids=None, attention_mask=None,
                              output_all_encoded_layers=False, attention_show_flg=False)

        loss = criterion(outputs, labels) #计算损失
        _, preds = torch.max(outputs, 1)  #进行标签预测
        epoch_corrects += torch.sum(preds == labels.data) #更新正确答案数量的合计

#准确率
epoch_acc = epoch_corrects.double() / len(test_dl.dataset)

print('处理 {} 个测试数据的准确率:{:.4f}'.format(len(test_dl.dataset), epoch_acc))

正确率达到 90%
在这里插入图片描述
Bert 模型比之前的 Transformer 模型实现的情感分析效果要好,但是BERT 只是实现了 Encoder Layer,如果需要做更复杂的任务还需要 Decoder Layer,例如翻译,对话等等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/606815.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32G030C8T6:EEPROM读写实验(I2C通信)

本专栏记录STM32开发各个功能的详细过程&#xff0c;方便自己后续查看&#xff0c;当然也供正在入门STM32单片机的兄弟们参考&#xff1b; 本小节的目标是&#xff0c;系统主频64 MHZ,采用高速外部晶振&#xff0c;实现PB11,PB10 引脚模拟I2C 时序&#xff0c;对M24C08 的EEPRO…

面试常见 | 项目上没有亮点,如何包装?

很多技术人在公司用的老技术&#xff0c;而且很多都是搬业务代码且做枯燥乏味的CRUD&#xff0c;在面试提交简历或做自我介绍的时候并不突出&#xff0c;这种情况&#xff0c;如何破局&#xff1f; 首先不管你做的啥项目&#xff0c;全世界不可能只有你自己在做&#xff0c;比…

【MATLAB源码-第52期】基于matlab的4用户DS-CDMA误码率仿真,对比不同信道以及不同扩频码。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 DS-CDMA (Direct Sequence Code Division Multiple Access) 是一种多址接入技术&#xff0c;其基本思想是使用伪随机码序列来调制发送信号。DS-CDMA的特点是所有用户在同一频率上同时发送和接收信息&#xff0c;但每个用户使…

Leetcode—1396. 设计地铁系统【中等】

2024每日刷题&#xff08;127&#xff09; Leetcode—1396. 设计地铁系统 实现代码 class UndergroundSystem { public:typedef struct Checkin {string startStation;int time;} Checkin;typedef struct Checkout{int tripNum;int totalTime;} Checkout;UndergroundSystem()…

ANSI转义序列

一、ASCII码 ASCII&#xff08;American Standard Code for Information Interchange&#xff0c;美国信息交换标准代码&#xff09;最初的设计是一个7位的字符编码&#xff0c;使用了从0到127的数字来表示字符。这意味着它总共可以表示128个不同的字符。这包括了英文大小写字…

vue+ant-design+formBuiler表单构建器——技能提升——form design——亲测有效

最近看到后端同事在弄一个后台管理系统&#xff0c;额&#xff0c;前端真的是夹缝中生存啊&#xff0c;AI抢饭碗&#xff0c;后端也想干前端的活儿。。。 他用到了表单构建器&#xff0c;具体效果如下: 网上有很多适用于ElementUi和ant-design的form design插件&#xff0c;下…

深度学习Day-16:实现天气预测

&#x1f368; 本文为&#xff1a;[&#x1f517;365天深度学习训练营] 中的学习记录博客 &#x1f356; 原作者&#xff1a;[K同学啊 | 接辅导、项目定制] 要求&#xff1a;根据提供的数据集对RainTomorrow进行预测 一、 基础配置 语言环境&#xff1a;Python3.7编译器选择…

CSS伪类选择器

目录 前言&#xff1a; 链接伪类&#xff1a; 用户行为伪类&#xff1a; 元素状态伪类&#xff1a; 结构化伪类&#xff1a; 否定伪类&#xff1a; 目标伪类&#xff1a; 输入伪类&#xff1a; 前言&#xff1a; 在CSS中有一种特殊的选择器&#xff1a;伪类选择器&…

3D翻页电子画册制作零基础制作

随着科技的不断发展&#xff0c;3D翻页电子画册逐渐成为了一种流行的展示方式。它不仅具有丰富的视觉冲击力&#xff0c;还能带来更好的用户体验。如果你是零基础&#xff0c;不用担心&#xff0c;我将为你详细介绍如何制作3D翻页电子画册。让你轻松入门&#xff0c;创作出属于…

DUX 主题 版本:8.2 WordPress主题优化版

主题下载地址&#xff1a;DUX 主题优化版.zip 支持夜间模式、快讯、专题、百度收录、人机验证、多级分类筛选&#xff0c;适用于垂直站点、科技博客、个人站&#xff0c;扁平化设计、简洁白色、超多功能配置、会员中心、直达链接、自动缩略图

【qt】QString字符串

前言&#xff1a; 这节很轻松&#xff0c;大家可以放心食用 ♪(&#xff65;ω&#xff65;)&#xff89; QString目录 一.与cString的区别二.隐式共享三.初始化四.判断是否为空串五.字符串的长度六.添加字符串1.尾加2.任意位置加 七.替换字符串八.修改字符串九.删除字符串1.清…

Elastic 基于 RAG 的 AI 助手:利用 LLM 和私有 GitHub 问题分析应用程序问题

作者&#xff1a;来自 Elastic Bahubali Shetti 作为 SRE&#xff0c;分析应用程序比以往更加复杂。 你不仅必须确保应用程序以最佳状态运行以确保良好的客户体验&#xff0c;而且还必须了解某些情况下的内部工作原理以帮助排除故障。 分析基于生产的服务中的问题是一项团队运动…

EOCR-DS3T-05S电动机保护器 施耐德 EOCR-DS3系列

EOCR-DS3T-05S电动机保护器 施耐德 EOCR-DS3系列型号&#xff1a; EOCR-DS3-05S EOCR-DS3-30S EOCR-DS3-60S EOCR-DS3T-05S EOCR-DS3T-30S EOCR-DS3T-60S 基于MCU&#xff08;微处理器&#xff09;的2CT型产品 ■ 实时处理/高精度 ■ 电流设定范围&#xff1a;05型&#xff1…

【SpringBoot整合系列】SpringBoot整合Thymeleaf

目录 背景Thymeleaf简介Thymeleaf的特征模板引擎是什么 代码示例1.引入依赖2.修改配置文件&#xff0c;添加Thymeleaf的配置信息3.编写HTML模板文件4.编写控制器&#xff0c;返回ModelAndView&#xff0c;进行视图渲染 Thymeleaf语法1.常用标签/属性1.1 th:action1.2 th:method…

SpringBoot 使用Outlook邮箱发送邮件

目录 一、开启Outlook设置 二、依赖 三、配置文件 四、代码调用 一、开启Outlook设置 开启设置如图&#xff1a; 二、依赖 <!-- 邮箱依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mai…

Java -- (part25)

一.Junit单元测试 1.概述 是一个单元测试框架,可以代替main方法去执行其他的方法 2.使用 a.导入jar包 b.注解 Test:单独执行一个方法 Before:在Test之前执行,有几个Test就有几个Before After:在Test之后执行,有几个Test就有几个After 二.类的加载机制 1.类的加载时机…

七款好用的上网行为管理软件推荐 |有没有好用的上网行为管理系统

七款好用的上网行为管理软件推荐 |有没有好用的上网行为管理系统 员工上班刷视频&#xff01; 员工上班炒股&#xff01; 员工上班干副业&#xff01; 碰见这种情况怎么办&#xff1f;当然是用电脑监控软件来监视员工的一举一动了&#xff0c;那么这样的软件有哪些呢&#…

充电桩测试:选择适合的负载箱

随着电动汽车的普及&#xff0c;充电桩的需求也在不断增加。为了保证充电桩的性能和安全&#xff0c;对其进行测试是非常重要的。在充电桩测试过程中&#xff0c;选择合适的负载箱是关键。本文将介绍如何选择合适的负载箱进行充电桩测试。 在进行充电桩测试之前&#xff0c;首先…

思腾合力受邀参加VALSE 2024视觉与学习青年学者研讨会

在充满学术氛围的五月&#xff0c;思腾合力荣幸受邀参加了于2024年5月5-7日在重庆举行的第十四届VALSE大会。作为视觉与学习领域的顶级交流平台&#xff0c;VALSE大会每年都吸引着全国专家与学者的目光。 本次大会不仅延续了往届的高水平学术研讨&#xff0c;还进一步拓宽了研究…

【CTF-Crypto】RSA-选择明密文攻击 一文通

RSA&#xff1a;选择明密文攻击 关于选择明/密文攻击&#xff0c;其实这一般是打一套组合拳的&#xff0c;在网上找到了利用的思路&#xff0c;感觉下面这个题目是真正将这个问题实现了&#xff0c;所以还是非常棒的一道题&#xff0c;下面先了解一下该知识点&#xff1a;(来自…