Bootstrap

【深度学习】基于BERT的文本情感分类模型---应用(附源代码+数据集+预训练模型)

【深度学习】基于BERT的文本情感分类模型—应用(附源代码+数据集+预训练模型)

一、BERT介绍

BERT的全称为Bidirectional Encoder Representation from Transformers,是一个预训练的语言表征模型,它强调不再像以往一样采用传统的单向语言模型或者把两个单向语言模型进行浅层拼接的方法进行预训练。它旨在通过在所有层中对左右上下文进行联合调节,采用新的masked language model(MLM),用于语言理解的深度双向转换器的预训练,从未标记的文本中预训练深度双向表示。因此,只需一个额外的输出层即可对预训练的 BERT 模型进行微调,从而为各种任务(例如问答和语言推理)创建较为先进的模型,而无需对特定于任务的架构进行大量修改。

二、基于BERT实现文本情感分类

2.1 文本情感分类任务

2.1.1 任务介绍

文本情感分类(Text Sentiment Classification)是一种自然语言处理(NLP)任务,其目标是确定给定文本的情感倾向。这种分类通常包括以下几类:

情感分类
正面情感(Positive Sentiment)文本表达了积极的情绪或观点,例如快乐、满意、赞扬等。
负面情感(Negative Sentiment)文本表达了消极的情绪或观点,例如愤怒、不满、批评等。
中性情感(Neutral Sentiment)文本既不表达强烈的正面情绪,也不表达强烈的负面情绪,通常是客观陈述。
其他情感类别(Optional)一些任务可能包括更多细分类别,例如愤怒、悲伤、惊喜等。
2.1.2 应用场景

文本情感分类在各种应用场景中有着广泛的应用,包括但不限于:

  • 产品评价分析:分析用户对产品或服务的评价,以了解用户满意度。
  • 社会媒体监控:监控社交媒体上的情感倾向,了解公众舆论。
  • 市场研究:评估市场反馈,帮助企业进行市场决策。
  • 客服自动化:识别客户情感,以便更好地提供支持和服务。

2.2 模型设计总体思路

本文提出一种基于BERT(Bidirectional Encoder Representations from Transformers)的深度学习模型来实现文本情感分类。以下是整体设计思路。

2.2.1 数据准备

2.2.1.1 读取数据

从CSV文件中读取评论和标签数据。使用pandas库进行数据处理,去除重复项和无效数据。打乱数据顺序,并只取前300条数据进行实验。

2.2.1.2 数据清理

将标签数据转换为整数类型,并去除无法转换的无效标签。打印唯一标签值,确保数据标签的有效性和多样性。

2.2.2模型设计

2.2.2.1 BERT 模型

  • 使用预训练的BERT模型进行文本表示,该模型能够捕捉文本中的复杂语义信息。
  • 加载BERT模型时,指定预训练模型的缓存路径以提高加载速度。

2.2.2.2 分类层

在BERT模型的输出之后添加一个全连接层(MLP),用于将BERT的输出转换为情感分类结果。

2.2.3 模型训练

2.2.3.1 数据集划分

将数据集划分为训练集和测试集,比例为80%:20%。

2.2.3.2 模型训练函数

  • 初始化BERT分类器、损失函数(交叉熵损失)和优化器(SGD优化器)。
  • 将模型和数据加载到GPU(若可用)或CPU。
  • 在训练开始前,评估未训练模型在训练集和测试集上的性能。
  • 进行多个训练周期(epoch),每个周期内对训练数据进行批次训练,计算损失,进行反向传播和优化。
  • 在每个周期结束时,评估模型在训练集和测试集上的性能,并保存性能最好的模型参数。

2.2.4.模型评估

2.2.4.1 评估函数

  • 定义evaluate函数,用于在评估模式下计算模型在数据集上的准确率。
  • 在评估过程中,关闭梯度计算,提高评估效率。
  • 对评估数据进行批次处理,计算预测结果与真实标签的匹配情况,输出模型的准确率。

2.2.4.2 预测与输出

使用训练好的模型对测试数据进行预测,并输出每条评论的预测结果和实际结果,比较预测是否正确。

2.3 模型模块化设计

2.3.1 导入必要的库

import csv
import pandas as pd
import random
from sklearn.model_selection import train_test_split
import torch
from torch import nn
from transformers import BertTokenizer, BertModel
from tqdm import tqdm
import os
import numpy as np

""" 设置缓存目录 """
os.environ['TRANSFORMERS_CACHE'] = 'D:/Python Project_2024_7_20_BERT/cache'  # 用于缓存预训练的BERT模型

2.3.2 读取和清理数据

""" 设置数据集 """
def read_file(file_name):
    print("开始读取文件...")
    comments_data = None
    with open(file_name, 'r', encoding='UTF-8') as f:
        reader = csv.reader(f)
        comments_data = []
        for line in reader:
            if len(line[0]) > 0:
                comments_data.append([line[0], line[1]])

    # 只取前300条数据
    comments_data = comments_data[:300]
    # 打乱数据集,并删除重复项
    random.shuffle(comments_data)
    data = pd.DataFrame(comments_data, columns=['Comment', 'Label'])
    data = data.drop_duplicates()
    data = clean_labels(data)
    print("文件读取完成.")
    return data
""" 定义清理标签函数 """
def clean_labels(df):
    df['Label'] = pd.to_numeric(df['Label'], errors='coerce')
    df = df.dropna(subset=['Label'])
    df['Label'] = df['Label'].astype(int)
    print("清理后的标签:")
    print(df['Label'].unique())  # 打印唯一标签值以确认
    return df

2.3.3 BERT 分类器定义

""" 设置 BERT 分类器类 """
# 将预训练的 BERT 模型和一个用于分类的全连接层结合在一起,提供了一个简单的接口来使用 BERT 进行文本分类任务。
class BERT_Classifier(nn.Module):

    # 加载预训练的 BERT 模型
    # 连接全连接层(分类层)
    def __init__(self, output_dim, pretrained_path='D:/Python Project_2024_7_20_BERT/cache'):
        super(BERT_Classifier, self).__init__()
        print("加载 BERT 模型...")
        self.BERT = BertModel.from_pretrained(pretrained_path)
        self.MLP = nn.Linear(768, output_dim)
        print("BERT 模型加载完成.")

    # 定义前向传播过程
    def forward(self, tokens_X):
        res = self.BERT(**tokens_X)
        return self.MLP(res.pooler_output)  # 将 BERT 模型的输出结果进行池化
                                            # 模型输出结果输入全连接层得到分类结果

2.3.4 评估函数

""" 设置评估函数 """
def evaluate(net, comments_data, labels_data, batch_size, device):

    # 设置模型为评估模式
    net.eval()

    sum_correct = 0
    total_samples = len(comments_data)

    if total_samples == 0:
        print("数据集中没有样本")
        return 0.0

    # 关闭梯度计算
    with torch.no_grad():
        for i in tqdm(range(0, total_samples, batch_size), desc="Evaluating"):
            comments = comments_data[i: i + batch_size]
            comments = list(map(str, comments))
            tokens_x = tokenizer(comments, padding=True, truncation=True, return_tensors='pt')
            tokens_x = {key: value.to(device) for key, value in tokens_x.items()}

            # 获取模型预测
            try:
                res = net(tokens_x)

                # 处理标签
                labels_slice = labels_data[i: i + batch_size].to_numpy()
                labels_slice = [label for label in labels_slice if pd.notna(label)]

                if len(labels_slice) == 0:
                    continue

                labels_slice = np.array(labels_slice, dtype=int)
                real_labels = torch.tensor(labels_slice, dtype=torch.long).to(device)

                # 确保模型输出维度与标签维度匹配
                correct_preds = (res.argmax(dim=1) == real_labels).sum().item()
                sum_correct += correct_preds

            except Exception as e:
                print(f"评估过程中出错: {e}")

    accuracy = sum_correct / total_samples if total_samples > 0 else 0
    print(f"评估完成,准确率: {accuracy}")
    return accuracy

2.3.5 训练函数

""" 设置训练函数,对模型进行具体分类任务训练 """
def train_BERT_Classifier(net, tokenizer, loss, optimizer, comment_train, comment_test, label_train, label_test, batch_size, epochs, device):
    max_acc = 0.3
    net.to(device)

    # 评估模型的初始性能
    print("测试未训练前的模型精确度...")
    pre_train_acc = evaluate(net, comment_train, label_train, batch_size, device)
    pre_test_acc = evaluate(net, comment_test, label_test, batch_size, device)
    print('--epoch', 0, '\t--pre_train_acc:', pre_train_acc, '\t--pre_test_acc:', pre_test_acc)

    # 训练循环
    for epoch in tqdm(range(epochs), desc="Training"):
        net.train()
        sum_loss = 0
        for i in tqdm(range(0, len(comment_train), batch_size), desc=f"Epoch {epoch+1}/{epochs}"):
            comments = comment_train[i: i + batch_size]
            comments = list(map(str, comments))
            tokens_x = tokenizer(comments, padding=True, truncation=True, return_tensors='pt')
            tokens_x = {key: value.to(device) for key, value in tokens_x.items()}
            real_labels = torch.tensor(label_train[i: i + batch_size].to_numpy(), dtype=torch.long).to(device)

            optimizer.zero_grad()
            res = net(tokens_x)
            l = loss(res, real_labels)
            l.backward()
            optimizer.step()

            sum_loss += l.detach()

        train_acc = evaluate(net, comment_train, label_train, batch_size, device)
        test_acc = evaluate(net, comment_test, label_test, batch_size, device)

        print('--epoch', epoch + 1, '\t--train_acc:', train_acc, '\t--test_acc:', test_acc)

        if test_acc > max_acc:
            max_acc = test_acc
            torch.save(net.state_dict(), 'D:/Python Project_2024_7_20_BERT/best_model.pth')
            print(f'New best model saved with accuracy: {test_acc}')

2.3.6 主函数

""" 主函数 """
if __name__ == "__main__":
    try:
        file_path = "D:/Python Project_2024_7_20_BERT/comments.csv"
        comments_data = read_file(file_path)

        Comment = comments_data['Comment']
        Label = comments_data['Label']
        Comment_train, Comment_test, Label_train, Label_test = train_test_split(Comment, Label, test_size=0.2, random_state=42)

        pretrained_path = 'D:/Python Project_2024_7_20_BERT/cache'
        net = BERT_Classifier(output_dim=3, pretrained_path=pretrained_path)
        tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
        loss = nn.CrossEntropyLoss()
        optimizer = torch.optim.SGD(net.parameters(), lr=1e-4)

        device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        train_BERT_Classifier(net, tokenizer, loss, optimizer, Comment_train, Comment_test, Label_train, Label_test, 8, 1, device)

        # 加载测试数据
        test_comments = list(Comment_test)
        test_labels = list(Label_test)

        # 定义模型
        net = BERT_Classifier(output_dim=3, pretrained_path=pretrained_path)
        net = net.to(device)

        # 加载训练好的模型参数
        model_path = 'D:/Python Project_2024_7_20_BERT/best_model.pth'
        if os.path.exists(model_path):
           net.load_state_dict(torch.load(model_path))
        else:
           print(f"模型路径 {model_path} 不存在,请检查路径是否正确。")
           exit()

        start = 0
        while start < len(test_comments) and start < 200:
            comment = test_comments[start]
            token_X = tokenizer(comment, padding=True, truncation=True, return_tensors='pt').to(device)
            label = test_labels[start]  # 实际结果
            result = net(token_X).argmax(axis=1).item()  # 得到预测结果

            # 打印评论语句
            print(comment)

            # 输出预测结果
            if result == 0:
               print('预测结果: ', 0, '----》差评', end='\t')
            elif result == 1:
               print('预测结果: ', 1, '----》中评', end='\t')
            else:
               print('预测结果: ', 2, '----》好评', end='\t')

            # 输出实际结果
            if label == 0:
               print('实际结果: ', 0, '----》差评', end='\t')
            elif label == 1:
               print('实际结果: ', 1, '----》中评', end='\t')
            else:
               print('实际结果: ', 2, '----》好评', end='\t')

            if result == label:
               print('预测正确')
            else:
               print('预测错误')

            start += 1

    except Exception as e:
        print(f"程序运行时发生了错误: {e}")

2.4 模型实现完整代码及运行结果

** 完整代码 **

import csv
import pandas as pd
import random
from sklearn.model_selection import train_test_split
import torch
from torch import nn
from transformers import BertTokenizer, BertModel
from tqdm import tqdm
import os
import numpy as np

""" 设置缓存目录 """
os.environ['TRANSFORMERS_CACHE'] = 'D:/Python Project_2024_7_20_BERT/cache'  # 用于缓存预训练的BERT模型

""" 设置数据集 """
def read_file(file_name):
    print("开始读取文件...")
    comments_data = None
    with open(file_name, 'r', encoding='UTF-8') as f:
        reader = csv.reader(f)
        comments_data = []
        for line in reader:
            if len(line[0]) > 0:
                comments_data.append([line[0], line[1]])

    # 只取前300条数据
    comments_data = comments_data[:300]
    # 打乱数据集,并删除重复项
    random.shuffle(comments_data)
    data = pd.DataFrame(comments_data, columns=['Comment', 'Label'])
    data = data.drop_duplicates()
    data = clean_labels(data)
    print("文件读取完成.")
    return data

""" 定义清理标签函数 """
def clean_labels(df):
    df['Label'] = pd.to_numeric(df['Label'], errors='coerce')
    df = df.dropna(subset=['Label'])
    df['Label'] = df['Label'].astype(int)
    print("清理后的标签:")
    print(df['Label'].unique())  # 打印唯一标签值以确认
    return df

""" 设置 BERT 分类器类 """
# 将预训练的 BERT 模型和一个用于分类的全连接层结合在一起,提供了一个简单的接口来使用 BERT 进行文本分类任务。
class BERT_Classifier(nn.Module):

    # 加载预训练的 BERT 模型
    # 连接全连接层(分类层)
    def __init__(self, output_dim, pretrained_path='D:/Python Project_2024_7_20_BERT/cache'):
        super(BERT_Classifier, self).__init__()
        print("加载 BERT 模型...")
        self.BERT = BertModel.from_pretrained(pretrained_path)
        self.MLP = nn.Linear(768, output_dim)
        print("BERT 模型加载完成.")

    # 定义前向传播过程
    def forward(self, tokens_X):
        res = self.BERT(**tokens_X)
        return self.MLP(res.pooler_output)  # 将 BERT 模型的输出结果进行池化
                                            # 模型输出结果输入全连接层得到分类结果

""" 设置评估函数 """
def evaluate(net, comments_data, labels_data, batch_size, device):

    # 设置模型为评估模式
    net.eval()

    sum_correct = 0
    total_samples = len(comments_data)

    if total_samples == 0:
        print("数据集中没有样本")
        return 0.0

    # 关闭梯度计算
    with torch.no_grad():
        for i in tqdm(range(0, total_samples, batch_size), desc="Evaluating"):
            comments = comments_data[i: i + batch_size]
            comments = list(map(str, comments))
            tokens_x = tokenizer(comments, padding=True, truncation=True, return_tensors='pt')
            tokens_x = {key: value.to(device) for key, value in tokens_x.items()}

            # 获取模型预测
            try:
                res = net(tokens_x)

                # 处理标签
                labels_slice = labels_data[i: i + batch_size].to_numpy()
                labels_slice = [label for label in labels_slice if pd.notna(label)]

                if len(labels_slice) == 0:
                    continue

                labels_slice = np.array(labels_slice, dtype=int)
                real_labels = torch.tensor(labels_slice, dtype=torch.long).to(device)

                # 确保模型输出维度与标签维度匹配
                correct_preds = (res.argmax(dim=1) == real_labels).sum().item()
                sum_correct += correct_preds

            except Exception as e:
                print(f"评估过程中出错: {e}")

    accuracy = sum_correct / total_samples if total_samples > 0 else 0
    print(f"评估完成,准确率: {accuracy}")
    return accuracy

""" 设置训练函数,对模型进行具体分类任务训练 """
def train_BERT_Classifier(net, tokenizer, loss, optimizer, comment_train, comment_test, label_train, label_test, batch_size, epochs, device):
    max_acc = 0.3
    net.to(device)

    # 评估模型的初始性能
    print("测试未训练前的模型精确度...")
    pre_train_acc = evaluate(net, comment_train, label_train, batch_size, device)
    pre_test_acc = evaluate(net, comment_test, label_test, batch_size, device)
    print('--epoch', 0, '\t--pre_train_acc:', pre_train_acc, '\t--pre_test_acc:', pre_test_acc)

    # 训练循环
    for epoch in tqdm(range(epochs), desc="Training"):
        net.train()
        sum_loss = 0
        for i in tqdm(range(0, len(comment_train), batch_size), desc=f"Epoch {epoch+1}/{epochs}"):
            comments = comment_train[i: i + batch_size]
            comments = list(map(str, comments))
            tokens_x = tokenizer(comments, padding=True, truncation=True, return_tensors='pt')
            tokens_x = {key: value.to(device) for key, value in tokens_x.items()}
            real_labels = torch.tensor(label_train[i: i + batch_size].to_numpy(), dtype=torch.long).to(device)

            optimizer.zero_grad()
            res = net(tokens_x)
            l = loss(res, real_labels)
            l.backward()
            optimizer.step()

            sum_loss += l.detach()

        train_acc = evaluate(net, comment_train, label_train, batch_size, device)
        test_acc = evaluate(net, comment_test, label_test, batch_size, device)

        print('--epoch', epoch + 1, '\t--train_acc:', train_acc, '\t--test_acc:', test_acc)

        if test_acc > max_acc:
            max_acc = test_acc
            torch.save(net.state_dict(), 'D:/Python Project_2024_7_20_BERT/best_model.pth')
            print(f'New best model saved with accuracy: {test_acc}')

""" 主函数 """
if __name__ == "__main__":
    try:
        file_path = "D:/Python Project_2024_7_20_BERT/comments.csv"
        comments_data = read_file(file_path)

        Comment = comments_data['Comment']
        Label = comments_data['Label']
        Comment_train, Comment_test, Label_train, Label_test = train_test_split(Comment, Label, test_size=0.2, random_state=42)

        pretrained_path = 'D:/Python Project_2024_7_20_BERT/cache'
        net = BERT_Classifier(output_dim=3, pretrained_path=pretrained_path)
        tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
        loss = nn.CrossEntropyLoss()
        optimizer = torch.optim.SGD(net.parameters(), lr=1e-4)

        device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        train_BERT_Classifier(net, tokenizer, loss, optimizer, Comment_train, Comment_test, Label_train, Label_test, 8, 1, device)

        # 加载测试数据
        test_comments = list(Comment_test)
        test_labels = list(Label_test)

        # 定义模型
        net = BERT_Classifier(output_dim=3, pretrained_path=pretrained_path)
        net = net.to(device)

        # 加载训练好的模型参数
        model_path = 'D:/Python Project_2024_7_20_BERT/best_model.pth'
        if os.path.exists(model_path):
           net.load_state_dict(torch.load(model_path))
        else:
           print(f"模型路径 {model_path} 不存在,请检查路径是否正确。")
           exit()

        start = 0
        while start < len(test_comments) and start < 200:
            comment = test_comments[start]
            token_X = tokenizer(comment, padding=True, truncation=True, return_tensors='pt').to(device)
            label = test_labels[start]  # 实际结果
            result = net(token_X).argmax(axis=1).item()  # 得到预测结果

            # 打印评论语句
            print(comment)

            # 输出预测结果
            if result == 0:
               print('预测结果: ', 0, '----》差评', end='\t')
            elif result == 1:
               print('预测结果: ', 1, '----》中评', end='\t')
            else:
               print('预测结果: ', 2, '----》好评', end='\t')

            # 输出实际结果
            if label == 0:
               print('实际结果: ', 0, '----》差评', end='\t')
            elif label == 1:
               print('实际结果: ', 1, '----》中评', end='\t')
            else:
               print('实际结果: ', 2, '----》好评', end='\t')

            if result == label:
               print('预测正确')
            else:
               print('预测错误')

            start += 1

    except Exception as e:
        print(f"程序运行时发生了错误: {e}")

** 运行结果截图 **

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、附件:代码运行参考指南

3.1 相关预训练模型下载连接

由于加载预训练的BERT模型时,可能会出现网络连接中断,导致模型文件下载不完整,无法运行代码。建议手动下载 Hugging Face 预训练模型并将其放置在缓存目录中,可以按照以下步骤操作:

步骤 1: 手动下载模型文件

  • 打开你的浏览器并访问 Hugging Face 模型库,比如 bert-base-chinese 的页面:BERT Chinese
  • 下载以下文件到你的本地目录(你可以创建一个特定的目录,比如 D:/Python Project_2024_7_20_BERT/cache/bert-base-chinese):
  1. pytorch_model.bin
  2. config.json
  3. vocab.txt

步骤 2: 将模型文件放置在指定缓存目录中

  • 假设你已经下载了上述文件并将其放置在 D:/PythonProject_2024_7_20_BERT/cache/bert-base-chinese 目录中。

步骤 3: 加载本地缓存的模型文件

  • 你可以使用本地目录路径来加载模型和分词器,而不是从 Hugging Face Hub 下载。

3.2 数据集文件

准备18多万条手机评论信息和对应的情感标签," 0、1、2 "代表差中好评。(文件下载地址在评论区)

3.3 代码补充说明

有任何问题可以联系我:[email protected]

引用:Gaolw1102— https://blog.csdn.net/weixin_43479947/article/details/127885471

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;