【论文复现】基于BERT的语义分析实现

📝个人主页🌹:Eternity._

🌹🌹期待您的关注 🌹🌹


❀ WRN: 宽度残差网络

概述


在之前的文章中,我们介绍了BERT模型。BERT作为一种预训练语言模型,它具有很好的兼容性,能够运用在各种下游任务中,本文的主要目的是利用数据集来对BERT进行训练,从而实现一个语义分类的模型。

本文所涉及的所有资源的获取方式:这里

语义分类


语义分类是自然语言处理任务中的一种,包含文本分类、情感分析

文本分类


文本分类是指给定文本a,将文本分类为n个类别中的一个或多个。常见的应用包括文本话题分类,情感分类,具体的分类方向有有二分类,多分类和多标签分类。

文本分类可以采用传统机器学习方法(贝叶斯,svm等)和深度学习方法(fastText,TextCNN等)实现。

举例而言,对于一个对话数据集,我们可以用1、2、3表示他们的话题,如家庭、学校、工作等,而文本分类的目的,则是把这些文本的话题划分到给定的三种类别中。

情感分类


情感分析是自然语言处理中常见的场景,比如商品评价等。通过情感分析,可以挖掘产品在各个维度的优劣。情感分类其实也是一种特殊的文本分类,只是他更聚焦于情感匹配词典。

举例而言,情感分类可以用0/1表示负面评价/正面评价,例子如下:

python 复制代码
0,不好的,319房间有故臭味。要求换房说满了,我是3月去的。在路上认识了一个上海人,他说他退房前也住的319,也是一股臭味。而且这个去不掉,特别是晚上,很浓。不知道是厕所的还是窗外的。服务一般,门前有绿皮公交去莫高窟,不过敦煌宾馆也有,下次住敦煌宾馆。再也不住这个酒店了,热水要放半个小时才有。
1,不错的酒店,大堂和餐厅的环境都不错。但由于给我的是一间走廊尽头的房间,所以房型看上去有点奇怪。客厅和卧室是连在一起的,面积偏小。服务还算到位,总的来说,性价比还是不错的。

本文将以情感二分类为例,实现如何利用BERT进行语义分析。

实现原理


首先,基于BERT预训练模型,能将一个文本转换成向量,作为模型的输入。

在BERT预训练模型的基础上,新增一个全连接层,将输入的向量通过训练转化成一个tensor作为输出,其中这个tensor的维度则是需要分类的种类,具体的值表示每个种类的概率。例如:

python 复制代码
[0.25,0.75] 

指代的是有0.25的概率属于第一类,有0.75的概率属于第二类,因此,理论输出结果是把该文本分为第二类。

核心逻辑


pre_deal.py


python 复制代码
import csv
import random
from datasets import load_dataset

def read_file(file_path):
    csv_reader = csv.reader(open(file_path, encoding='UTF-8'))
    num = 0
    data = []
    for row in csv_reader:
        if num == 0:
            num = 1
            continue
        comment_data = [row[1], int(row[0])]
        if len(comment_data[0]) > 500:
            text=comment_data[0]
            sub_texts, start, length = [], 0, len(text)
            while start < length:
                piecedata=[text[start: start + 500], comment_data[1]]
                data.append(piecedata)
                start += 500
        else:
             data.append(comment_data)
    random.shuffle(data)
    return data

对输入的csv文件进行处理,其中我们默认csv文件的格式是[label,text],将用于训练的内容读取出来,转化为numpy格式,其中,如果遇到有些文本过长(超过模型的输入),将其截断,分为多个文本段来输入。在最后,会通过shuffle函数进行打乱。

train.py


train.py定义了几个函数,用于训练。

首先是Bertmodel类,定义了基于Bert的训练模型:

python 复制代码
class Bertmodel(nn.Module):
    def __init__(self, output_dim, model_path):
        super(Bertmodel, self).__init__()
        # 导入bert模型
        self.bert = BertModel.from_pretrained(model_path)
        # 外接全连接层
        self.layer1 = nn.Linear(768, output_dim)

    def forward(self, tokens):
        res = self.bert(**tokens)
        res = self.layer1(res[1])
        res = res.softmax(dim=1)
        return res

该模型由Bert和一个全连接层组成,最后经过softmax激活函数。

其次是一个评估函数,用来计算模型结果的准确性

python 复制代码
def evaluate(net, comments_data, labels_data, device, tokenizer):
    ans = 0 # 输出结果
    i = 0
    step = 8 # 每轮一次读取多少条数据
    tot = len(comments_data)
    while i <= tot:
        print(i)
        comments = comments_data[i: min(i + step, tot)]
        tokens_X = tokenizer(comments, padding=True, truncation=True, return_tensors='pt').to(device=device)

        res = net(tokens_X)  # 获得到预测结果

        y = torch.tensor(labels_data[i: min(i + step, tot)]).reshape(-1).to(device=device)

        ans += (res.argmax(axis=1) == y).sum()
        i += step

    return ans / tot

原理就是,将文本转化为tokens,输入给模型,而后利用返回的结果,计算准确性

下面展示了开始训练的主函数,在训练的过程中,进行后向传播,储存checkpoints模型

python 复制代码
def training(net, tokenizer, loss, optimizer, train_comments, train_labels, test_comments, test_labels,
                          device, epochs):
    max_acc = 0.5  # 初始化模型最大精度为0.5

    for epoch in tqdm(range(epochs)):
        step = 8
        i, sum_loss = 0, 0
        tot=len(train_comments)
        while i < tot:
            comments = train_comments[i: min(i + step, tot)]
            tokens_X = tokenizer(comments, padding=True, truncation=True, return_tensors='pt').to(device=device)

            res = net(tokens_X)

            y = torch.tensor(train_labels[i: min(i + step, len(train_comments))]).reshape(-1).to(device=device)

            optimizer.zero_grad()  # 清空梯度
            l = loss(res, y)  # 计算损失
            l.backward()  # 后向传播
            optimizer.step()  # 更新梯度

            sum_loss += l.detach()  # 累加损失
            i += step

        train_acc = evaluate(net, train_comments, train_labels)
        test_acc = evaluate(net, test_comments, test_labels)

        print('\n--epoch', epoch + 1, '\t--loss:', sum_loss / (len(train_comments) / 8), '\t--train_acc:', train_acc,
              '\t--test_acc', test_acc)

        # 保存模型参数,并重设最大值
        if test_acc > max_acc:
            # 更新历史最大精确度
            max_acc = test_acc

            # 保存模型
            max_acc = test_acc
            torch.save({
                'epoch': epoch,
                'state_dict': net.state_dict(),
                'optimizer': optimizer.state_dict()
            }, 'model/checkpoint_net.pth')

训练结果表示如下:

python 复制代码
--epoch 0 	--train_acc: tensor(0.6525, device='cuda:1') 	--test_acc tensor(0.6572, device='cuda:1')

  0%|          | 0/20 [00:00<?, ?it/s]
  5%|▌         | 1/20 [01:48<34:28, 108.88s/it]
 10%|█         | 2/20 [03:38<32:43, 109.10s/it]
 15%|█▌        | 3/20 [05:27<30:56, 109.20s/it]
 20%|██        | 4/20 [07:15<29:02, 108.93s/it]
 25%|██▌       | 5/20 [09:06<27:23, 109.58s/it]
 30%|███       | 6/20 [10:55<25:29, 109.26s/it]
 35%|███▌      | 7/20 [12:44<23:40, 109.28s/it]
 40%|████      | 8/20 [14:33<21:51, 109.29s/it]
 45%|████▌     | 9/20 [16:23<20:04, 109.49s/it]
 50%|█████     | 10/20 [18:13<18:15, 109.59s/it]
 55%|█████▌    | 11/20 [20:03<16:27, 109.72s/it]
 60%|██████    | 12/20 [21:52<14:35, 109.45s/it]
 65%|██████▌   | 13/20 [23:41<12:45, 109.35s/it]
 70%|███████   | 14/20 [25:30<10:54, 109.14s/it]
 75%|███████▌  | 15/20 [27:19<09:05, 109.03s/it]
 80%|████████  | 16/20 [29:07<07:15, 108.84s/it]
 85%|████████▌ | 17/20 [30:56<05:26, 108.86s/it]
 90%|█████████ | 18/20 [32:44<03:37, 108.75s/it]
 95%|█████████▌| 19/20 [34:33<01:48, 108.73s/it]
100%|██████████| 20/20 [36:22<00:00, 108.71s/it]
100%|██████████| 20/20 [36:22<00:00, 109.11s/it]

--epoch 1 	--loss: tensor(1.2426, device='cuda:1') 	--train_acc: tensor(0.6759, device='cuda:1') 	--test_acc tensor(0.6789, device='cuda:1')

--epoch 2 	--loss: tensor(1.0588, device='cuda:1') 	--train_acc: tensor(0.8800, device='cuda:1') 	--test_acc tensor(0.8708, device='cuda:1')

--epoch 3 	--loss: tensor(0.8543, device='cuda:1') 	--train_acc: tensor(0.8988, device='cuda:1') 	--test_acc tensor(0.8887, device='cuda:1')

--epoch 4 	--loss: tensor(0.8208, device='cuda:1') 	--train_acc: tensor(0.9111, device='cuda:1') 	--test_acc tensor(0.8990, device='cuda:1')

--epoch 5 	--loss: tensor(0.8024, device='cuda:1') 	--train_acc: tensor(0.9206, device='cuda:1') 	--test_acc tensor(0.9028, device='cuda:1')

--epoch 6 	--loss: tensor(0.7882, device='cuda:1') 	--train_acc: tensor(0.9227, device='cuda:1') 	--test_acc tensor(0.9024, device='cuda:1')

--epoch 7 	--loss: tensor(0.7749, device='cuda:1') 	--train_acc: tensor(0.9288, device='cuda:1') 	--test_acc tensor(0.9036, device='cuda:1')

--epoch 8 	--loss: tensor(0.7632, device='cuda:1') 	--train_acc: tensor(0.9352, device='cuda:1') 	--test_acc tensor(0.9061, device='cuda:1')

--epoch 9 	--loss: tensor(0.7524, device='cuda:1') 	--train_acc: tensor(0.9421, device='cuda:1') 	--test_acc tensor(0.9090, device='cuda:1')

--epoch 10 	--loss: tensor(0.7445, device='cuda:1') 	--train_acc: tensor(0.9443, device='cuda:1') 	--test_acc tensor(0.9103, device='cuda:1')

--epoch 11 	--loss: tensor(0.7397, device='cuda:1') 	--train_acc: tensor(0.9480, device='cuda:1') 	--test_acc tensor(0.9128, device='cuda:1')

--epoch 12 	--loss: tensor(0.7321, device='cuda:1') 	--train_acc: tensor(0.9505, device='cuda:1') 	--test_acc tensor(0.9123, device='cuda:1')

--epoch 13 	--loss: tensor(0.7272, device='cuda:1') 	--train_acc: tensor(0.9533, device='cuda:1') 	--test_acc tensor(0.9140, device='cuda:1')

--epoch 14 	--loss: tensor(0.7256, device='cuda:1') 	--train_acc: tensor(0.9532, device='cuda:1') 	--test_acc tensor(0.9111, device='cuda:1')

--epoch 15 	--loss: tensor(0.7186, device='cuda:1') 	--train_acc: tensor(0.9573, device='cuda:1') 	--test_acc tensor(0.9123, device='cuda:1')

--epoch 16 	--loss: tensor(0.7135, device='cuda:1') 	--train_acc: tensor(0.9592, device='cuda:1') 	--test_acc tensor(0.9136, device='cuda:1')

--epoch 17 	--loss: tensor(0.7103, device='cuda:1') 	--train_acc: tensor(0.9601, device='cuda:1') 	--test_acc tensor(0.9128, device='cuda:1')

--epoch 18 	--loss: tensor(0.7091, device='cuda:1') 	--train_acc: tensor(0.9590, device='cuda:1') 	--test_acc tensor(0.9086, device='cuda:1')

--epoch 19 	--loss: tensor(0.7084, device='cuda:1') 	--train_acc: tensor(0.9626, device='cuda:1') 	--test_acc tensor(0.9123, device='cuda:1')

--epoch 20 	--loss: tensor(0.7038, device='cuda:1') 	--train_acc: tensor(0.9628, device='cuda:1') 	--test_acc tensor(0.9107, device='cuda:1')

最终训练结果,在训练集上达到了96.28%的准确率,在测试集上达到了91.07%的准确率

test_demo.py


这个函数提供了一个调用我们储存的checkpoint模型来进行预测的方式,将input转化为berttokens,而后输入给模型,返回输出结果。

python 复制代码
input_text=['这里环境很好,风光美丽,下次还会再来的。']
Bert_model_path = 'xxxx'
output_path='xxxx'
device = torch.device('cpu')
checkpoint = torch.load(output_path,map_location='cpu')

model = Bertmodel(output_dim=2,model_path=Bert_model_path)
model.load_state_dict(checkpoint,False)
# print(model)
tokenizer = BertTokenizer.from_pretrained(Bert_model_path,model_max_length=512)

tokens_X = tokenizer(input_text, padding=True, truncation=True, return_tensors='pt').to(device='cpu')
model.eval()
output=model(tokens_X)
print(output)
out = torch.unsqueeze(output.argmax(dim=1), dim=1)
result = out.numpy()
print(result)
if result[0][0]==1:
    print("positive")
else:
    print("negative")

实现方式&演示效果


训练阶段


首先找到能够拿来训练的数据,运行pre_deal.py进行预处理,而后可以在main.py修改模型的相关参数,运行main.py开始训练。

这个过程,可能会收到硬件条件的影响,推荐使用cuda进行训练。如果实在训练不了,可以直接调用附件中对应的训练好的模型来进行预测。

测试阶段


运行test_demo.py,测试输入文本的分类结果

输入

python 复制代码
input_text=['这里环境很好,风光美丽,下次还会再来的。']

输出

python 复制代码
tensor([[0.3191, 0.6809]], grad_fn=<SoftmaxBackward0>)
[[1]]
positive

得出,这句话的情感分类是positive(正面)


编程未来,从这里启航!解锁无限创意,让每一行代码都成为你通往成功的阶梯,帮助更多人欣赏与学习!

更多内容详见:这里

相关推荐
@小匠3 小时前
Read Frog:一款开源的 AI 驱动浏览器语言学习扩展
人工智能·学习
山间小僧4 小时前
「AI学习笔记」RNN
机器学习·aigc·ai编程
网教盟人才服务平台6 小时前
“方班预备班盾立方人才培养计划”正式启动!
大数据·人工智能
芯智工坊6 小时前
第15章 Mosquitto生产环境部署实践
人工智能·mqtt·开源
菜菜艾6 小时前
基于llama.cpp部署私有大模型
linux·运维·服务器·人工智能·ai·云计算·ai编程
TDengine (老段)6 小时前
TDengine IDMP 可视化 —— 分享
大数据·数据库·人工智能·时序数据库·tdengine·涛思数据·时序数据
小真zzz6 小时前
搜极星:第三方多平台中立GEO洞察专家全面解析
人工智能·搜索引擎·seo·geo·中立·第三方平台
Dxy12393102167 小时前
Python基于BERT的上下文纠错详解
开发语言·python·bert
GreenTea7 小时前
从 Claw-Code 看 AI 驱动的大型项目开发:2 人 + 10 个自治 Agent 如何产出 48K 行 Rust 代码
前端·人工智能·后端
火山引擎开发者社区7 小时前
秒级创建实例,火山引擎 Milvus Serverless 让 AI Agent 开发更快更省
人工智能