【AI大模型舆情分析】微博舆情分析可视化系统(pytorch2+基于BERT大模型训练微调+flask+pandas+echarts+爬虫) 实战教程

70 阅读23分钟

大家好,我是锋哥。最近连载更新【AI大模型舆情分析】微博舆情分析可视化系统(pytorch2+基于BERT大模型训练微调+flask+pandas+echarts+爬虫) 》。image.png 本到课程基于前面的2026版【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts+爬虫)二次开发,前面课程舆情分析用得是snowNLP,我们现在改用基于BERT开源大模型微调,来实现舆情分析,提高舆情分析的准确率。重点讲解基本BERT大模型实现舆情分析,二分类问题。同时也配套视频教程 《【AI大模型舆情分析】微博舆情分析可视化系统(pytorch2+基于BERT大模型训练微调》

微博舆情分析可视化系统 - 项目准备

基于python微博舆情分析可视化系统(flask+pandas+echarts+爬虫)把课程项目源码本地运行下。

登录页面,我们改下小标题,加个LLM

image.png 登录后,进入系统主页面:

image.png

BERT大模型实现舆情分析功能

大模型开发环境准备-PyTorch和transformers库

首先新建model目录,把Bert-base-chinese粘贴到model目录下。

image.png

我们大模型训练尽可能的基于GPU的,所以大家最好大家可以看下 2027版 AI大模型应用开发入门-拥抱Hugging Face与Transformers生态 ,CUDA要安装下。

然后我们要安装Pytorch2和transformers库:

pip install torch torchvision --index-url https://download.pytorch.org/whl/cu126
pip install transformers -i http://mirrors.aliyun.com/pypi/simple/   --trusted-host mirrors.aliyun.com

环境测试代码:

import torch
from transformers import pipeline
​
print(torch.cuda.is_available())
​
# 通过pipeline 加载模型
model = pipeline(task="text-classification",  # 任务类型 二分类
                 model="Bert-base-chinese",  # 模型名称
                 device=0  # 使用GPU
                 )
# 调用模型预测
result = model("小明是好学生")
print(result)

运行结果:

image.png

BERT大模型情感分析功能封装(使用AutoModel自动模型方式)

新建llm目录,里面再新建weibo.py文件

代码实现:

import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
​
# 使用设备GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
​
# 加载分词器
tokenizer = AutoTokenizer.from_pretrained('../model/Bert-base-chinese')
​
# 加载模型
model = AutoModelForSequenceClassification.from_pretrained('../model/Bert-base-chinese')
print(model)
​
​
# 情感分析
def data_classfication(data):
    # 准备输入数据
    input_ids = tokenizer.encode(
        text=data,  # 输入文本
        return_tensors='pt',  # 返回PyTorch张量
        truncation=True,  # 截断超过长度的输入文本
        max_length=20,  # 最长长度
        padding="max_length"  # 填充短的输入文本
    ).to(device)
​
    # 评估模型
    model.eval()
​
    model.to(device)
​
    # 模型预测
    output = model(input_ids)
    print(output)
    # 获取预测结果
    logits = output.logits
    print(logits)
    predication = torch.argmax(logits, dim=-1)
    print(predication)
    sentiment = predication.item()  # 把张量转成数字
    print(sentiment)
    sentiment_label = "正面" if sentiment == 1 else "负面"
    return sentiment_label
​
​
if __name__ == '__main__':
    result = data_classfication("开心")
    print("预测结果:", result)

运行结果:

image.png

使用BERT大模型对微博热词进行情感分析

image.png

找到视图层业务逻辑代码:

image.png

我们修改下:

# 改成使用大模型进行舆情分析
sentiments = data_classfication(defaultHotWord)

image.png

启动就直接报错了

image.png

原因是加载模型用得是相对路径,我们调用封装方法的时候,相对路径又变了。所以我们企业级开发,一般不用相对路径,只用绝对路径,由于开发环境,测试环境,线上环境模型路径会变,所以我们一般都会把路径放到配置文件里面去,方便修改路径。

我们在项目根目录下新建config.py

image.png

配置下模型路径:

# 定义模型路径
model_path = 'D:\python_pro\weiboLLMProject2\model'

接下来,weibo.py里把相对路径,改成绝对路径,使用config.py里的model_path属性;

image.png

改成:

# 加载分词器
tokenizer = AutoTokenizer.from_pretrained(config.model_path + "/Bert-base-chinese")
​
# 加载模型
model = AutoModelForSequenceClassification.from_pretrained(config.model_path + "/Bert-base-chinese")
print(model)

运行测试系统,可以了。不过情感分析的准确率很有限,等后面VIP课程,我们使用增量微调训练后的模型,准确率能上到90%以上。

image.png

使用BERT大模型对微博文章内容进行情感分析

image.png

找到微博舆情分析后端视图业务逻辑代码:

image.png

改成:

# 改成使用大模型进行舆情分析
sentiments = data_classfication(article[1])

重启项目测试:

image.png

使用BERT大模型对微博舆情分析以及可视化操作

image.png

找到数据可视化微博舆情分析后端视图层业务逻辑代码,包括柱状图,树形图,饼状图。代码都需要修改;

image.png

代码修改:

@pb.route('sentimentAnalysis')
def sentimentAnalysis():
    """
    舆情数据分析
    :return:
    """
    xHotBarData = ['正面', '负面']
    yHotBarData = [0, 0]
    # 只读取前100条
    df = pd.read_csv('./fenci/comment_fre.csv', nrows=100)
    for value in df.values:
        # 情感分析
        # stc = SnowNLP(value[0]).sentiments
        # if stc > 0.6:
        #     yHotBarData[0] += 1
        # elif stc < 0.2:
        #     yHotBarData[2] += 1
        # else:
        #     yHotBarData[1] += 1
​
        # 使用大模型进行情感分析
        sentiment_label = data_classfication(value[0])
        if sentiment_label == '正面':
            yHotBarData[0] += 1
        else:
            yHotBarData[1] += 1
​
    hotTreeMapData = [{
        'name': xHotBarData[0],
        'value': yHotBarData[0]
    }, {
        'name': xHotBarData[1],
        'value': yHotBarData[1]
    }]
​
    commentPieData = [{
        'name': '正面',
        'value': 0
    },  {
        'name': '负面',
        'value': 0
    }]
    articlePieData = [{
        'name': '正面',
        'value': 0
    }, {
        'name': '负面',
        'value': 0
    }]
    commentList = commentDao.getAllComment()
    for comment in commentList:
        # 情感分析
        # stc = SnowNLP(comment[1]).sentiments
        # if stc > 0.6:
        #     commentPieData[0]['value'] += 1
        # elif stc < 0.2:
        #     commentPieData[2]['value'] += 1
        # else:
        #     commentPieData[1]['value'] += 1
        # 使用大模型进行情感分析
        sentiment_label = data_classfication(comment[1])
        if sentiment_label == '正面':
            commentPieData[0]['value'] += 1
        else:
            commentPieData[1]['value'] += 1
​
    articleList = articleDao.getAllArticle()
    for article in articleList:
        # 情感分析
        # stc = SnowNLP(article[1]).sentiments
        # if stc > 0.6:
        #     articlePieData[0]['value'] += 1
        # elif stc < 0.2:
        #     articlePieData[2]['value'] += 1
        # else:
        #     articlePieData[1]['value'] += 1
        # 使用大模型进行情感分析
        sentiment_label = data_classfication(article[1])
        if sentiment_label == '正面':
            articlePieData[0]['value'] += 1
        else:
            articlePieData[1]['value'] += 1
​
    df2 = pd.read_csv('./fenci/comment_fre.csv', nrows=15)
    xhotData15 = [x[0] for x in df2.values][::-1]
    yhotData15 = [x[1] for x in df2.values][::-1]
    return render_template('sentimentAnalysis.html',
                           xHotBarData=xHotBarData,
                           yHotBarData=yHotBarData,
                           hotTreeMapData=hotTreeMapData,
                           commentPieData=commentPieData,
                           articlePieData=articlePieData,
                           xhotData15=xhotData15,
                           yhotData15=yhotData15)

重启项目测试:

image.png

BERT大模型进行微调训练实现舆情分析功能

基于BERT模型进行增量微调训练

1,自定义数据集

我们首先要安装下datasets库。

pip install datasets -i http://mirrors.aliyun.com/pypi/simple/   --trusted-host mirrors.aliyun.com

然后我们准备下训练和测试数据。

image.png

8千多条的训练集数据,2千多条的测试集数据。

我们之所以要自定义数据集,是因为需要去适配训练模型需要的数据格式。

自定义数据集参考代码:

from datasets import load_dataset
from torch.utils.data import Dataset
​
​
# 自定义数据集
class MyDataset(Dataset):
​
    def __init__(self, split):
        # 加载训练集
        train_dataset = load_dataset(path="csv", data_files="weibo_senti_8k_train.csv")
        # 加载测试集
        test_dataset = load_dataset(path="csv", data_files="weibo_senti_2k_test.csv")
        if split == 'train':
            self.data = train_dataset['train']
        elif split == 'test':
            self.data = test_dataset['train']
​
    # 获取数据集大小
    def __len__(self):
        return len(self.data)
​
    # 获取数据集的某个元素
    def __getitem__(self, index):
        sentence = self.data[index]['sentence']
        label = self.data[index]['label']
        return sentence, label
​
​
if __name__ == '__main__':
    train_dataset = MyDataset('train')
    test_dataset = MyDataset('test')
    print(train_dataset[0])
    print(test_dataset[0])
    for data in test_dataset:
        print(data)

运行结果:

image.png

2,对训练输入文本进行编码

对传入的数据进行训练之前,我们需要对数据进行编码。

我们通过分词器的batch_encode_plus方法进行批量编码;

实例代码:

from transformers import BertTokenizer
​
import config
​
# 加载分词器
tokenizer = BertTokenizer.from_pretrained(config.model_path + '/Bert-base-chinese')
​
# 准备测试文本
sents = ['床前明月光,疑似地上霜,举头望明月,低头思故乡', '今天天气不错', '很开心']
​
# 批量编码句子
out = tokenizer.batch_encode_plus(
    batch_text_or_text_pairs=sents,  # 输入的文本
    add_special_tokens=True,  # 添加特殊标记
    max_length=10,  # 最大长度
    padding='max_length',  # 填充
    truncation=True,  # 截断
    return_tensors='pt',  # 返回pytorch张量
    return_token_type_ids=True,  # 返回token_type_ids  区分不同句子或段落的类型标识
    return_attention_mask=True,  # 返回attention_mask  标记有效token位置的掩码
    return_special_tokens_mask=True  # 返回special_tokens_mask 标识特殊token(如[CLS]、[SEP])的位置掩码
)
print(out)
for k, v in out.items():
    print(k, v)
​
# 解码文本数据
for i in range(len(sents)):
    print(sents[i] + "--解码后:", tokenizer.decode(out['input_ids'][i]))

运行输出:

{'input_ids': tensor([[ 101, 2414, 1184, 3209, 3299, 1045, 8024, 4542,  849,  102],
        [ 101,  791, 1921, 1921, 3698,  679, 7231,  102,    0,    0],
        [ 101, 2523, 2458, 2552,  102,    0,    0,    0,    0,    0]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'special_tokens_mask': tensor([[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 1, 1, 1],
        [1, 0, 0, 0, 1, 1, 1, 1, 1, 1]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
        [1, 1, 1, 1, 1, 0, 0, 0, 0, 0]])}
input_ids tensor([[ 101, 2414, 1184, 3209, 3299, 1045, 8024, 4542,  849,  102],
        [ 101,  791, 1921, 1921, 3698,  679, 7231,  102,    0,    0],
        [ 101, 2523, 2458, 2552,  102,    0,    0,    0,    0,    0]])
token_type_ids tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
special_tokens_mask tensor([[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 1, 1, 1],
        [1, 0, 0, 0, 1, 1, 1, 1, 1, 1]])
attention_mask tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
        [1, 1, 1, 1, 1, 0, 0, 0, 0, 0]])
床前明月光,疑似地上霜,举头望明月,低头思故乡--编码后: [CLS] 床 前 明 月 光 , 疑 似 [SEP]
今天天气不错--编码后: [CLS] 今 天 天 气 不 错 [SEP] [PAD] [PAD]
很开心--解码后: [CLS] 很 开 心 [SEP] [PAD] [PAD] [PAD] [PAD] [PAD]

3,定义增量模型,也就是下游任务

我们定义增量模型,主要是在Bert模型最后加一个全连接层,实现二分类任务。进行前向传播的时候,我们要使用torch.no_grad()冻结Bert模型参数,不需计算梯度,获取最后一层Bert隐藏层输出,调用自定义的全连接层,进行增量模型训练。

示例代码:

# 使用设备(GPU/CPU)
import torch
from transformers import BertModel
​
import config
​
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
​
# 加载预训练模型
pretrained_model = BertModel.from_pretrained(config.model_path + '/Bert-base-chinese').to(device)
​
​
# 定义下游任务(增量模型)
class DownStreamModel(torch.nn.Module):
​
    def __init__(self):
        super(DownStreamModel, self).__init__()
        # 下游加一个全连接层,实现二分类任务
        self.fc = torch.nn.Linear(768, 2)
​
    def forward(self, input_ids, attention_mask, token_type_ids):
        with torch.no_grad():  # 冻结Bert模型参数,不需计算梯度
            # 获取最后一层隐藏层输出
            output = pretrained_model(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)
        # 增量模型参与训练
        out = self.fc(output.last_hidden_state[:, 0, :])
        return out

output.last_hidden_state[:, 0, :] 是用来获取模型输出的隐藏状态(通常是进入模型的最后一层隐藏状态)的一个特定切片。让我们逐个参数进行详细解析:

1. output

output 通常是通过对输入数据(如文本、图像等)运行某个深度学习模型(例如 BERT、GPT、Transformers 等)后返回的对象,它包含多个属性,其中一个重要的属性是 last_hidden_state

2. last_hidden_state

last_hidden_state 是模型最后一层的隐藏状态,它的形状通常是 (batch_size, sequence_length, hidden_size),具体的含义如下:

  • batch_size:处理的样本数量。在一次前向传播中,模型可以并行处理多个输入样本。
  • sequence_length:输入序列的长度。对于处理文本来说,它表示词汇的数量,可以是句子中词的个数。
  • hidden_size:每个隐藏状态的维度,代表模型的输出特征维数。通常在模型架构中定义,例如对于 BERT-base 隐藏大小为 768。

3. [:, 0, :]

这里的切片操作用来访问 last_hidden_state 的一部分,具体解释如下:

  • : :表示选择所有的样本。由于 batch_size 是第一个维度,所以把 : 放在这个位置表示选取当前批次的所有样本。
  • 0:表示选择第一个时刻的隐藏状态。在 NLP 中,特别是像 BERT 这样的模型中,第一位置的隐藏状态通常对应于 [CLS] token(分类 token),它是用来进行句子级别的任务(例如分类)的。
  • : :表示选择所有的特征维度(hidden_size)。这意味着我们提取每个样本的第一个位置(即 [CLS] token)对应的全部隐藏状态特征。

综合

因此,output.last_hidden_state[:, 0, :] 表示提取所有输入样本的 [CLS] token 的隐藏状态向量,这通常用于下游任务,如文本分类、问答、情感分析等,因为 [CLS] token 的表示通常是整段文本的语义聚合。

例子

如果有一个输入批次大小为 4,并且每个输入序列的隐藏状态大小为 768,那么 output.last_hidden_state[:, 0, :] 的返回结果将是一个形状为 (4, 768) 的张量,其中每一行对应一个输入样本的 [CLS] token 的隐藏状态。

4,训练模型

前面我们已经定义好了数据集,以及文本编码处理,包括增量模型定义。接下来我们来进行增量模型微调训练。

实例代码:

import torch
from datasets import load_dataset
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertModel
​
import config
​
​
# 自定义数据集
class MyDataset(Dataset):
​
    def __init__(self, split):
        # 加载训练集
        train_dataset = load_dataset(path="csv", data_files="weibo_senti_8k_train.csv")
        # 加载测试集
        test_dataset = load_dataset(path="csv", data_files="weibo_senti_2k_test.csv")
        if split == 'train':
            self.data = train_dataset['train']
        elif split == 'test':
            self.data = test_dataset['train']
​
    # 获取数据集大小
    def __len__(self):
        return len(self.data)
​
    # 获取数据集的某个元素
    def __getitem__(self, index):
        sentence = self.data[index]['sentence']
        label = self.data[index]['label']
        return sentence, label
​
​
# 加载分词器
tokenizer = BertTokenizer.from_pretrained(config.model_path + '/Bert-base-chinese')
​
# 使用设备(GPU/CPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
​
# 加载预训练模型
pretrained_model = BertModel.from_pretrained(config.model_path + '/Bert-base-chinese').to(device)
​
​
# 定义下游任务(增量模型)
class DownStreamModel(torch.nn.Module):
​
    def __init__(self):
        super(DownStreamModel, self).__init__()
        # 下游加一个全连接层,实现二分类任务
        self.fc = torch.nn.Linear(768, 2)
​
    def forward(self, input_ids, attention_mask, token_type_ids):
        with torch.no_grad():  # 冻结Bert模型参数,不需计算梯度
            # 获取最后一层隐藏层输出
            output = pretrained_model(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)
        # 增量模型参与训练
        out = self.fc(output.last_hidden_state[:, 0, :])
        return out
​
​
# 对传入数据进行编码
def collate_fn(data):
    sents = [i[0] for i in data]
    labels = [i[1] for i in data]
    # 编码
    # 批量编码句子
    out = tokenizer.batch_encode_plus(
        batch_text_or_text_pairs=sents,  # 输入的文本
        add_special_tokens=True,  # 添加特殊标记
        max_length=80,  # 最大长度
        padding='max_length',  # 填充
        truncation=True,  # 截断
        return_tensors='pt',  # 返回pytorch张量
        return_token_type_ids=True,  # 返回token_type_ids  区分不同句子或段落的类型标识
        return_attention_mask=True,  # 返回attention_mask  标记有效token位置的掩码
        return_special_tokens_mask=True  # 返回special_tokens_mask 标识特殊token(如[CLS]、[SEP])的位置掩码
    )
    return out['input_ids'], out['attention_mask'], out['token_type_ids'], torch.tensor(labels)
​
​
# 创建数据集
train_dataset = MyDataset('train')  # 训练集
train_loader = DataLoader(
    dataset=train_dataset,  # 数据集
    batch_size=200,  # 批次大小
    shuffle=True,  # 是否打乱数据
    drop_last=True,  # 丢弃最后一个批次数据
    collate_fn=collate_fn  # 对加载的数据进行编码
)
​
test_dataset = MyDataset('test')  # 测试集
test_loader = DataLoader(
    dataset=test_dataset,  # 数据集
    batch_size=200,  # 批次大小
    shuffle=True,  # 是否打乱数据
    drop_last=True,  # 丢弃最后一个批次数据
    collate_fn=collate_fn  # 对加载的数据进行编码
)
​
if __name__ == '__main__':
    model = DownStreamModel().to(device)  # 创建模型
    optimizer = torch.optim.AdamW(model.parameters())  # 优化器
    criterion = torch.nn.CrossEntropyLoss()  # 定义损失函数
    best_val_acc = 0  # 保存最好的准确率
    EPOCH = 3  # 训练轮数
    for epoch in range(EPOCH):  # 训练轮数
        for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(train_loader):  # 批次数据
            out = model(input_ids=input_ids.to(device), attention_mask=attention_mask.to(device),
                        token_type_ids=token_type_ids.to(device))  # 模型输出
            loss = criterion(out, labels.to(device))  # 计算损失
            optimizer.zero_grad()  # 清空梯度
            loss.backward()  # 反向传播
            optimizer.step()  # 优化器更新参数
            # 每隔5个批次输出训练结果
            if i % 5 == 0:
                out = out.argmax(dim=1)  # 获取预测结果
                acc = (out == labels.to(device)).sum().item() / len(labels)  # 计算准确率
                print("EPOCH:{}--第{}批次--损失:{}--准确率:{}".format(epoch + 1, i + 1, loss.item(), acc))

运行结果:

cuda
EPOCH:1--第1批次--损失:0.5008983016014099--准确率:0.78
EPOCH:1--第6批次--损失:0.5052810907363892--准确率:0.75
EPOCH:1--第11批次--损失:0.4850313067436218--准确率:0.755
EPOCH:1--第16批次--损失:0.4301462471485138--准确率:0.83
EPOCH:1--第21批次--损失:0.39388778805732727--准确率:0.85
EPOCH:1--第26批次--损失:0.3695535361766815--准确率:0.855
EPOCH:1--第31批次--损失:0.35825812816619873--准确率:0.855
EPOCH:1--第36批次--损失:0.3288692533969879--准确率:0.875
EPOCH:2--第1批次--损失:0.31738394498825073--准确率:0.885
EPOCH:2--第6批次--损失:0.3121739625930786--准确率:0.87
EPOCH:2--第11批次--损失:0.30510687828063965--准确率:0.895
EPOCH:2--第16批次--损失:0.305753618478775--准确率:0.865
EPOCH:2--第21批次--损失:0.24456100165843964--准确率:0.92
EPOCH:2--第26批次--损失:0.2233615517616272--准确率:0.93
EPOCH:2--第31批次--损失:0.2816208302974701--准确率:0.89
EPOCH:2--第36批次--损失:0.24931633472442627--准确率:0.915
EPOCH:3--第1批次--损失:0.3053100109100342--准确率:0.885
EPOCH:3--第6批次--损失:0.2515011727809906--准确率:0.9
EPOCH:3--第11批次--损失:0.24241474270820618--准确率:0.915
EPOCH:3--第16批次--损失:0.2211739420890808--准确率:0.925
EPOCH:3--第21批次--损失:0.24276195466518402--准确率:0.91
EPOCH:3--第26批次--损失:0.27010777592658997--准确率:0.895
EPOCH:3--第31批次--损失:0.24304606020450592--准确率:0.9
EPOCH:3--第36批次--损失:0.26476508378982544--准确率:0.905

5,评估模型

模型训练好之后,我们要对模型进行性能评估,以及保存最优模型参数。

示例代码:

import torch
from datasets import load_dataset
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertModel
​
import config
​
​
# 自定义数据集
class MyDataset(Dataset):
​
    def __init__(self, split):
        # 加载训练集
        train_dataset = load_dataset(path="csv", data_files="weibo_senti_8k_train.csv")
        # 加载测试集
        test_dataset = load_dataset(path="csv", data_files="weibo_senti_2k_test.csv")
        if split == 'train':
            self.data = train_dataset['train']
        elif split == 'test':
            self.data = test_dataset['train']
​
    # 获取数据集大小
    def __len__(self):
        return len(self.data)
​
    # 获取数据集的某个元素
    def __getitem__(self, index):
        sentence = self.data[index]['sentence']
        label = self.data[index]['label']
        return sentence, label
​
​
# 加载分词器
tokenizer = BertTokenizer.from_pretrained(config.model_path + '/Bert-base-chinese')
​
# 使用设备(GPU/CPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
​
# 加载预训练模型
pretrained_model = BertModel.from_pretrained(config.model_path + '/Bert-base-chinese').to(device)
​
​
# 定义下游任务(增量模型)
class DownStreamModel(torch.nn.Module):
​
    def __init__(self):
        super(DownStreamModel, self).__init__()
        # 下游加一个全连接层,实现二分类任务
        self.fc = torch.nn.Linear(768, 2)
​
    def forward(self, input_ids, attention_mask, token_type_ids):
        with torch.no_grad():  # 冻结Bert模型参数,不需计算梯度
            # 获取最后一层隐藏层输出
            output = pretrained_model(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)
        # 增量模型参与训练
        out = self.fc(output.last_hidden_state[:, 0, :])
        return out
​
​
# 对传入数据进行编码
def collate_fn(data):
    sents = [i[0] for i in data]
    labels = [i[1] for i in data]
    # 编码
    # 批量编码句子
    out = tokenizer.batch_encode_plus(
        batch_text_or_text_pairs=sents,  # 输入的文本
        add_special_tokens=True,  # 添加特殊标记
        max_length=80,  # 最大长度
        padding='max_length',  # 填充
        truncation=True,  # 截断
        return_tensors='pt',  # 返回pytorch张量
        return_token_type_ids=True,  # 返回token_type_ids  区分不同句子或段落的类型标识
        return_attention_mask=True,  # 返回attention_mask  标记有效token位置的掩码
        return_special_tokens_mask=True  # 返回special_tokens_mask 标识特殊token(如[CLS]、[SEP])的位置掩码
    )
    return out['input_ids'], out['attention_mask'], out['token_type_ids'], torch.tensor(labels)
​
​
# 创建数据集
train_dataset = MyDataset('train')  # 训练集
train_loader = DataLoader(
    dataset=train_dataset,  # 数据集
    batch_size=200,  # 批次大小
    shuffle=True,  # 是否打乱数据
    drop_last=True,  # 丢弃最后一个批次数据
    collate_fn=collate_fn  # 对加载的数据进行编码
)
​
test_dataset = MyDataset('test')  # 测试集
test_loader = DataLoader(
    dataset=test_dataset,  # 数据集
    batch_size=200,  # 批次大小
    shuffle=True,  # 是否打乱数据
    drop_last=True,  # 丢弃最后一个批次数据
    collate_fn=collate_fn  # 对加载的数据进行编码
)
​
if __name__ == '__main__':
    model = DownStreamModel().to(device)  # 创建模型
    optimizer = torch.optim.AdamW(model.parameters())  # 优化器
    criterion = torch.nn.CrossEntropyLoss()  # 定义损失函数
    best_val_acc = 0  # 保存最好的准确率
    EPOCH = 3  # 训练轮数
    for epoch in range(EPOCH):  # 训练轮数
        for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(train_loader):  # 批次数据
            out = model(input_ids=input_ids.to(device), attention_mask=attention_mask.to(device),
                        token_type_ids=token_type_ids.to(device))  # 模型输出
            loss = criterion(out, labels.to(device))  # 计算损失
            optimizer.zero_grad()  # 清空梯度
            loss.backward()  # 反向传播
            optimizer.step()  # 优化器更新参数
            # 每隔5个批次输出训练结果
            if i % 5 == 0:
                out = out.argmax(dim=1)  # 获取预测结果
                acc = (out == labels.to(device)).sum().item() / len(labels)  # 计算准确率
                print("EPOCH:{}--第{}批次--损失:{}--准确率:{}".format(epoch + 1, i + 1, loss.item(), acc))
​
    # 验证模型
    model.eval()  # 评估模式 Dropout关闭
    with torch.no_grad():  # 评估模式,不需要计算梯度
        for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(test_loader):
            out = model(input_ids=input_ids.to(device), attention_mask=attention_mask.to(device),
                        token_type_ids=token_type_ids.to(device))
            out = out.argmax(dim=1)
            acc = (out == labels.to(device)).sum().item() / len(labels)
            if acc > best_val_acc:
                best_val_acc = acc
                torch.save(model.state_dict(), "best_model.pth")
                print(f"测试集准确率EPOCH:{epoch}-第{i}批次:模型保存,准确率:{acc}")
        # 保存最后一轮模型
        torch.save(model.state_dict(), "last_model.pth")
        print(f"最后一轮模型保存,:准确率:{acc}")

运行结果:

cuda
EPOCH:1--第1批次--损失:0.6369971632957458--准确率:0.695
EPOCH:1--第6批次--损失:0.6052521467208862--准确率:0.685
EPOCH:1--第11批次--损失:0.5286625623703003--准确率:0.77
EPOCH:1--第16批次--损失:0.4174182415008545--准确率:0.83
EPOCH:1--第21批次--损失:0.41344115138053894--准确率:0.81
EPOCH:1--第26批次--损失:0.3910037875175476--准确率:0.87
EPOCH:1--第31批次--损失:0.3680213987827301--准确率:0.85
EPOCH:1--第36批次--损失:0.3817676901817322--准确率:0.855
EPOCH:2--第1批次--损失:0.3963841199874878--准确率:0.84
EPOCH:2--第6批次--损失:0.3516421914100647--准确率:0.85
EPOCH:2--第11批次--损失:0.330654114484787--准确率:0.845
EPOCH:2--第16批次--损失:0.3345922529697418--准确率:0.865
EPOCH:2--第21批次--损失:0.3087370693683624--准确率:0.885
EPOCH:2--第26批次--损失:0.25769323110580444--准确率:0.92
EPOCH:2--第31批次--损失:0.2792946696281433--准确率:0.885
EPOCH:2--第36批次--损失:0.29899129271507263--准确率:0.905
EPOCH:3--第1批次--损失:0.3139827847480774--准确率:0.855
EPOCH:3--第6批次--损失:0.27809959650039673--准确率:0.895
EPOCH:3--第11批次--损失:0.2725857198238373--准确率:0.885
EPOCH:3--第16批次--损失:0.30161210894584656--准确率:0.885
EPOCH:3--第21批次--损失:0.26055067777633667--准确率:0.925
EPOCH:3--第26批次--损失:0.22951321303844452--准确率:0.895
EPOCH:3--第31批次--损失:0.2995443642139435--准确率:0.87
EPOCH:3--第36批次--损失:0.3246515691280365--准确率:0.87
测试集准确率EPOCH:2-第0批次:模型保存,准确率:0.975
最后一轮模型保存,:准确率:0.955

把训练好的模型参数进行业务功能封装

前面我们把模型参数训练好了,接下来,我们需要封装下一个接口方法,提供给应用调用。

llm目录下,新建weibo_train.py

import torch
from transformers import BertTokenizer, BertModel
​
import config
​
# 加载分词器
tokenizer = BertTokenizer.from_pretrained(config.model_path + '/Bert-base-chinese')
​
# 使用设备(GPU/CPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
​
# 加载预训练模型
pretrained_model = BertModel.from_pretrained(config.model_path + '/Bert-base-chinese').to(device)
​
​
# 定义下游任务(增量模型)
class DownStreamModel(torch.nn.Module):
​
    def __init__(self):
        super(DownStreamModel, self).__init__()
        # 下游加一个全连接层,实现二分类任务
        self.fc = torch.nn.Linear(768, 2)
​
    def forward(self, input_ids, attention_mask, token_type_ids):
        with torch.no_grad():  # 冻结Bert模型参数,不需计算梯度
            # 获取最后一层隐藏层输出
            output = pretrained_model(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)
        # 增量模型参与训练
        out = self.fc(output.last_hidden_state[:, 0, :])
        return out
​
​
# 对传入数据进行编码
def collate_fn(data):
    # 编码
    # 批量编码句子
    out = tokenizer.batch_encode_plus(
        batch_text_or_text_pairs=data,  # 输入的文本
        add_special_tokens=True,  # 添加特殊标记
        max_length=80,  # 最大长度
        padding='max_length',  # 填充
        truncation=True,  # 截断
        return_tensors='pt',  # 返回pytorch张量
        return_token_type_ids=True,  # 返回token_type_ids  区分不同句子或段落的类型标识
        return_attention_mask=True,  # 返回attention_mask  标记有效token位置的掩码
        return_special_tokens_mask=True  # 返回special_tokens_mask 标识特殊token(如[CLS]、[SEP])的位置掩码
    )
    return out['input_ids'], out['attention_mask'], out['token_type_ids']
​
​
model = DownStreamModel().to(device)  # 创建模型
model.load_state_dict(torch.load(config.model_path + "/best_model.pth"))
​
​
def check_data(data):
    # 验证模型
    model.eval()  # 评估模式 Dropout关闭
    with torch.no_grad():  # 评估模式,不需要计算梯度
        input_ids, attention_mask, token_type_ids = collate_fn(data)
        out = model(input_ids=input_ids.to(device), attention_mask=attention_mask.to(device),
                    token_type_ids=token_type_ids.to(device))
        output = out.argmax(dim=1)
        sentiment = output.item()
        sentiment_label = "正面" if sentiment == 1 else "负面"
        return sentiment_label
​
​
if __name__ == '__main__':
    result = check_data(["我非常难过"])
    print(result)

运行结果:

image.png

使用BERT大模型微调对微博热词进行情感分析

image.png

找到视图层业务逻辑代码:

image.png

改成:

# 改成使用大模型微调进行舆情分析
sentiments = check_data([defaultHotWord])

重启测试,明显比之前大模型微调前的准确率提高很多。

image.png

使用BERT大模型对微博文章内容进行情感分析

image.png

找到微博舆情分析后端视图业务逻辑代码:

image.png

改成:

# 改成使用大模型微调进行舆情分析
sentiments = check_data([article[1]])

重启运行,分析结果明显比大模型微调之前准确率高很多。

image.png

使用BERT大模型对微博舆情分析以及可视化操作

image.png

找到数据可视化微博舆情分析后端视图层业务逻辑代码,包括柱状图,树形图,饼状图。代码都需要修改;

image.png

修改代码后:

@pb.route('sentimentAnalysis')
def sentimentAnalysis():
    """
    舆情数据分析
    :return:
    """
    xHotBarData = ['正面', '负面']
    yHotBarData = [0, 0]
    # 只读取前100条
    df = pd.read_csv('./fenci/comment_fre.csv', nrows=100)
    for value in df.values:
        # 情感分析
        # stc = SnowNLP(value[0]).sentiments
        # if stc > 0.6:
        #     yHotBarData[0] += 1
        # elif stc < 0.2:
        #     yHotBarData[2] += 1
        # else:
        #     yHotBarData[1] += 1
​
        # 使用大模型进行情感分析
        # sentiment_label = data_classfication(value[0])
        # 使用大模型微调进行情感分析
        sentiment_label = check_data([value[0]])
        if sentiment_label == '正面':
            yHotBarData[0] += 1
        else:
            yHotBarData[1] += 1
​
    hotTreeMapData = [{
        'name': xHotBarData[0],
        'value': yHotBarData[0]
    }, {
        'name': xHotBarData[1],
        'value': yHotBarData[1]
    }]
​
    commentPieData = [{
        'name': '正面',
        'value': 0
    }, {
        'name': '负面',
        'value': 0
    }]
    articlePieData = [{
        'name': '正面',
        'value': 0
    }, {
        'name': '负面',
        'value': 0
    }]
    commentList = commentDao.getAllComment()
    for comment in commentList:
        # 情感分析
        # stc = SnowNLP(comment[1]).sentiments
        # if stc > 0.6:
        #     commentPieData[0]['value'] += 1
        # elif stc < 0.2:
        #     commentPieData[2]['value'] += 1
        # else:
        #     commentPieData[1]['value'] += 1
        # 使用大模型进行情感分析
        # sentiment_label = data_classfication(comment[1])
        # 使用大模型微调进行情感分析
        sentiment_label = check_data([comment[1]])
        if sentiment_label == '正面':
            commentPieData[0]['value'] += 1
        else:
            commentPieData[1]['value'] += 1
​
    articleList = articleDao.getAllArticle()
    for article in articleList:
        # 情感分析
        # stc = SnowNLP(article[1]).sentiments
        # if stc > 0.6:
        #     articlePieData[0]['value'] += 1
        # elif stc < 0.2:
        #     articlePieData[2]['value'] += 1
        # else:
        #     articlePieData[1]['value'] += 1
        # 使用大模型进行情感分析
        # sentiment_label = data_classfication(article[1])
        # 使用大模型微调进行情感分析
        sentiment_label = check_data([article[1]])
        if sentiment_label == '正面':
            articlePieData[0]['value'] += 1
        else:
            articlePieData[1]['value'] += 1
​
    df2 = pd.read_csv('./fenci/comment_fre.csv', nrows=15)
    xhotData15 = [x[0] for x in df2.values][::-1]
    yhotData15 = [x[1] for x in df2.values][::-1]
    return render_template('sentimentAnalysis.html',
                           xHotBarData=xHotBarData,
                           yHotBarData=yHotBarData,
                           hotTreeMapData=hotTreeMapData,
                           commentPieData=commentPieData,
                           articlePieData=articlePieData,
                           xhotData15=xhotData15,
                           yhotData15=yhotData15)

重启系统测试,准备率得到了质的提升。

image.png

应用系统进行【批量】调用大模型,提高系统响应速度

我们之前单个传入大模型进行情感分析,效率略低,我们一般建议批量的进行情感分析,但是传入的数据量也不能太大。我们之前封装的,可以传入一个列表,但是我们不可以传入一个非常大的列表,否则GPU内存会溢出。

我们如果在业务层进行控制传入的元素量,比较麻烦。所以我们封装一个批量导入的方法。

打开weibo_train.py

def check_data(data):
    # 验证模型
    model.eval()  # 评估模式 Dropout关闭
    with torch.no_grad():  # 评估模式,不需要计算梯度
        input_ids, attention_mask, token_type_ids = collate_fn(data)
        out = model(input_ids=input_ids.to(device), attention_mask=attention_mask.to(device),
                    token_type_ids=token_type_ids.to(device))
        output = out.argmax(dim=1)
        # sentiment = output.item()
        # sentiment_label = "正面" if sentiment == 1 else "负面"
        # print("预测结果:", sentiment_label)
        return output.tolist()
​
# 批量处理
def check_batch_datas(dataList, batch_num):
    outputList = []
    for i in range(0, len(dataList), batch_num):
        batch_data = dataList[i:i + batch_num]
        outputList.extend(check_data(batch_data))
    return outputList

我们以对微博文章进行大模型情感分析模块为例,修改成批量调用:

@pb.route('articleData')
def articleData():
    """
    微博舆情分析
    :return:
    """
    articleOldList = articleDao.getAllArticle()
    # 获取所有帖子标题
    articleTitleList = [article[1] for article in articleOldList]
    # 批量使用大模型进行舆情分析
    sentimentsList = check_batch_datas(articleTitleList, batch_num=10)
    # 追加情感分析属性
    articleNewList = []
    for i in range(len(articleOldList)):
        if sentimentsList[i] == 1:
            articleNewList.append(articleOldList[i] + ('正面',))
        else:
            articleNewList.append(articleOldList[i] + ('负面',))
​
        # for article in articleOldList:
        #     article = list(article)
        # 情感分析
        sentiments = ''
        # stc = SnowNLP(article[1]).sentiments
        # if stc > 0.6:
        #     sentiments = '正面'
        # elif stc < 0.2:
        #     sentiments = '负面'
        # else:
        #     sentiments = '中性'
        # 改成使用大模型进行舆情分析
        # sentiments = data_classfication(article[1])
        # 改成使用大模型微调进行舆情分析
        # sentiments = check_data([article[1]])
        # article.append(sentiments)
        # articleNewList.append(article)
    return render_template('articleData.html', articleList=articleNewList)

运行,效果还是有显著提高的。

image.png