基于BERT的语义分析实现

news/2024/12/14 18:05:26 标签: python, 人工智能


✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨

🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。

我是Srlua小谢,在这里我会分享我的知识和经验。🎥

希望在这里,我们能一起探索IT世界的奥妙,提升我们的技能。🔮

记得先点赞👍后阅读哦~ 👏👏

📘📚 所属专栏:传知代码论文复现

欢迎访问我的主页:Srlua小谢 获取更多信息和资源。✨✨🌙🌙

​​

​​

目录

概述

语义分类

文本分类

情感分类

实现原理

核心逻辑

pre_deal.py

train.py

test_demo.py

实现方式&演示效果

训练阶段

测试阶段


本文所有资源均可在该地址处获取。

概述

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

语义分类

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

文本分类

文本分类是指给定文本a,将文本分类为n个类别中的一个或多个。常见的应用包括文本话题分类,情感分类,具体的分类方向有有二分类,多分类和多标签分类。
文本分类可以采用传统机器学习方法(贝叶斯,svm等)和深度学习方法(fastText,TextCNN等)实现。
举例而言,对于一个对话数据集,我们可以用1、2、3表示他们的话题,如家庭、学校、工作等,而文本分类的目的,则是把这些文本的话题划分到给定的三种类别中。

情感分类

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

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

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

实现原理

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

[0.25,0.75] 

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

核心逻辑

pre_deal.py

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的训练模型:

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激活函数。
其次是一个评估函数,用来计算模型结果的准确性

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模型

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

训练结果表示如下:

--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,而后输入给模型,返回输出结果。

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,测试输入文本的分类结果
输入

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

输出

tensor([[0.3191, 0.6809]], grad_fn=<SoftmaxBackward0>)
[[1]]
positive

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

​​

希望对你有帮助!加油!

若您认为本文内容有益,请不吝赐予赞同并订阅,以便持续接收有价值的信息。衷心感谢您的关注和支持!


http://www.niftyadmin.cn/n/5768312.html

相关文章

go的math/rand随机数生成器

伪随机数生成器&#xff0c;默认情况下随机数种子是固定的&#xff0c; **注意&#xff1a;**固定的随机数种子每次生成的随机数都是相同的随机数序列 一、基础用法 math/rand 包提供了随机数生成的方法。常用的函数包括&#xff1a; rand.Int()&#xff1a;返回一个伪随机…

Secured Finance与GLIF建立战略合作关系,为Filecoin FVM赋能

“DeFi 巨头强强联合增强流动性质押&#xff0c;并开启全新收益机会。” Secured Finance 是一个专注于促进固定收益市场和跨链借贷服务的 DeFi 协议&#xff0c;允许用户在多个区块链生态系统之间高效、安全地执行借贷、债券交易和利率互换等金融操作。通过智能合约和去中心化…

Elasticsearch与NLP的深度融合:文本嵌入与向量搜索实战指南

Elasticsearch与NLP的深度融合:文本嵌入与向量搜索实战指南 引言 在当今信息爆炸的时代,如何从海量文本数据中快速准确地检索出相关信息,成为了一个迫切需要解决的问题。自然语言处理(NLP)技术的发展为这一挑战提供了新的解决方案。Elasticsearch,作为一个强大的搜索引…

open-instruct - 训练开放式指令跟随语言模型

文章目录 关于 open-instruct设置训练微调偏好调整RLVR 污染检查开发中仓库结构 致谢 关于 open-instruct github : https://github.com/allenai/open-instruct 这个仓库是我们对在公共数据集上对流行的预训练语言模型进行指令微调的开放努力。我们发布这个仓库&#xff0c;并…

三十:在Web当中什么样的响应才会被缓存?

Web缓存是提高网站性能、减轻服务器负担和优化用户体验的重要技术之一。然而&#xff0c;并非所有的HTTP响应都适合缓存。在Web开发中&#xff0c;理解什么样的响应应该被缓存&#xff0c;对于制定高效的缓存策略至关重要。本文将深入探讨哪些类型的响应适合缓存&#xff0c;以…

Pytorch使用手册-What is torch.nn really?(专题九)

我们建议将本教程作为 notebook 而不是脚本运行。要下载 notebook(.ipynb)文件,请点击页面顶部的链接。 PyTorch 提供了精心设计的模块和类,如 torch.nn、torch.optim、Dataset 和 DataLoader,帮助你创建和训练神经网络。为了充分利用这些工具的强大功能并根据你的问题进…

Java 单例模式:深度解析与应用

在软件开发领域&#xff0c;设计模式是解决常见设计问题的有效方案&#xff0c;而单例模式作为创建型设计模式中的一员&#xff0c;其重要性不容小觑。它能够确保一个类仅有一个实例&#xff0c;并提供全局访问点&#xff0c;这一特性在资源管理、配置信息读取、线程池管理以及…

window.structuredClone 深拷贝

概述&#xff1a; structuredClone 是一种新的 JavaScript 原生方法&#xff0c;用于创建一个对象的深拷贝。与传统的浅拷贝方法&#xff08;如 Object.assign 或数组的 slice&#xff09;不同&#xff0c;structuredClone 可以递归地拷贝对象&#xff0c;包括其中的嵌套对象、…