基于BERT的情感分析

news2024/11/18 17:42:49

基于BERT的情感分析

1. 项目背景

情感分析(Sentiment Analysis)是自然语言处理的重要应用之一,用于判断文本的情感倾向,如正面、负面或中性。随着深度学习的发展,预训练语言模型如BERT在各种自然语言处理任务中取得了显著的效果。本项目利用预训练语言模型BERT,构建一个能够对文本进行情感分类的模型。


2. 项目结构

sentiment-analysis/
├── data/
│   ├── train.csv        # 训练数据集
│   ├── test.csv         # 测试数据集
├── src/
│   ├── preprocess.py    # 数据预处理模块
│   ├── train.py         # 模型训练脚本
│   ├── evaluate.py      # 模型评估脚本
│   ├── inference.py     # 模型推理脚本
│   ├── utils.py         # 工具函数(可选)
├── models/
│   ├── bert_model.pt    # 保存的模型权重
├── logs/
│   ├── training.log     # 训练日志(可选)
├── README.md            # 项目说明文档
├── requirements.txt     # 依赖包列表
└── run.sh               # 一键运行脚本

3. 环境准备

3.1 系统要求

  • Python 3.6 或以上版本
  • GPU(可选,但建议使用以加速训练)

3.2 安装依赖

建议在虚拟环境中运行。安装所需的依赖包:

pip install -r requirements.txt

requirements.txt内容:

torch>=1.7.0
transformers>=4.0.0
pandas
scikit-learn
tqdm

4. 数据准备

4.1 数据格式

数据文件train.csvtest.csv的格式如下:

textlabel
I love this product.1
This is a bad movie.0
  • text:输入文本
  • label:目标标签,1为正面情感,0为负面情感

将数据文件保存至data/目录下。

4.2 数据集划分

可以使用train_test_split将数据划分为训练集和测试集。


5. 代码实现

5.1 数据预处理 (src/preprocess.py)

import pandas as pd
from transformers import BertTokenizer
from torch.utils.data import Dataset
import torch

class SentimentDataset(Dataset):
    """
    自定义的用于情感分析的Dataset。
    """
    def __init__(self, data_path, tokenizer, max_len=128):
        """
        初始化Dataset。

        Args:
            data_path (str): 数据文件的路径。
            tokenizer (BertTokenizer): BERT的分词器。
            max_len (int): 最大序列长度。
        """
        self.data = pd.read_csv(data_path)
        self.tokenizer = tokenizer
        self.max_len = max_len

    def __len__(self):
        """
        返回数据集的大小。
        """
        return len(self.data)

    def __getitem__(self, idx):
        """
        根据索引返回一条数据。

        Args:
            idx (int): 数据索引。

        Returns:
            dict: 包含input_ids、attention_mask和label的字典。
        """
        text = str(self.data.iloc[idx]['text'])
        label = int(self.data.iloc[idx]['label'])
        encoding = self.tokenizer(
            text, 
            padding='max_length', 
            truncation=True, 
            max_length=self.max_len, 
            return_tensors="pt"
        )
        return {
            'input_ids': encoding['input_ids'].squeeze(0),  # shape: [seq_len]
            'attention_mask': encoding['attention_mask'].squeeze(0),  # shape: [seq_len]
            'label': torch.tensor(label, dtype=torch.long)  # shape: []
        }

5.2 模型训练 (src/train.py)

import torch
from torch.utils.data import DataLoader
from transformers import BertForSequenceClassification, AdamW, BertTokenizer, get_linear_schedule_with_warmup
from preprocess import SentimentDataset
import argparse
import os
from tqdm import tqdm

def train_model(data_path, model_save_path, batch_size=16, epochs=3, lr=2e-5, max_len=128):
    """
    训练BERT情感分析模型。

    Args:
        data_path (str): 训练数据的路径。
        model_save_path (str): 模型保存的路径。
        batch_size (int): 批次大小。
        epochs (int): 训练轮数。
        lr (float): 学习率。
        max_len (int): 最大序列长度。
    """
    # 初始化分词器和数据集
    tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
    dataset = SentimentDataset(data_path, tokenizer, max_len=max_len)

    # 划分训练集和验证集
    train_size = int(0.8 * len(dataset))
    val_size = len(dataset) - train_size
    train_dataset, val_dataset = torch.utils.data.random_split(dataset, [train_size, val_size])

    # 数据加载器
    train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
    val_loader = DataLoader(val_dataset, batch_size=batch_size)

    # 初始化模型
    model = BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=2)

    # 优化器和学习率调度器
    optimizer = AdamW(model.parameters(), lr=lr)
    total_steps = len(train_loader) * epochs
    scheduler = get_linear_schedule_with_warmup(
        optimizer, 
        num_warmup_steps=0, 
        num_training_steps=total_steps
    )

    # 设备设置
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model.to(device)

    # 训练循环
    for epoch in range(epochs):
        model.train()
        total_loss = 0
        progress_bar = tqdm(train_loader, desc=f"Epoch {epoch + 1}/{epochs}")
        for batch in progress_bar:
            optimizer.zero_grad()
            input_ids = batch['input_ids'].to(device)  # shape: [batch_size, seq_len]
            attention_mask = batch['attention_mask'].to(device)  # shape: [batch_size, seq_len]
            labels = batch['label'].to(device)  # shape: [batch_size]

            outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
            loss = outputs.loss

            loss.backward()
            optimizer.step()
            scheduler.step()

            total_loss += loss.item()
            progress_bar.set_postfix(loss=loss.item())

        avg_train_loss = total_loss / len(train_loader)
        print(f"Epoch {epoch + 1}/{epochs}, Average Loss: {avg_train_loss:.4f}")

        # 验证模型
        model.eval()
        val_loss = 0
        correct = 0
        total = 0
        with torch.no_grad():
            for batch in val_loader:
                input_ids = batch['input_ids'].to(device)
                attention_mask = batch['attention_mask'].to(device)
                labels = batch['label'].to(device)

                outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
                loss = outputs.loss
                logits = outputs.logits

                val_loss += loss.item()
                preds = torch.argmax(logits, dim=1)
                correct += (preds == labels).sum().item()
                total += labels.size(0)
        avg_val_loss = val_loss / len(val_loader)
        val_accuracy = correct / total
        print(f"Validation Loss: {avg_val_loss:.4f}, Accuracy: {val_accuracy:.4f}")

    # 保存模型
    os.makedirs(os.path.dirname(model_save_path), exist_ok=True)
    torch.save(model.state_dict(), model_save_path)
    print(f"Model saved to {model_save_path}")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Train BERT model for sentiment analysis")
    parser.add_argument('--data_path', type=str, default='data/train.csv', help='Path to training data')
    parser.add_argument('--model_save_path', type=str, default='models/bert_model.pt', help='Path to save the trained model')
    parser.add_argument('--batch_size', type=int, default=16, help='Batch size')
    parser.add_argument('--epochs', type=int, default=3, help='Number of training epochs')
    parser.add_argument('--lr', type=float, default=2e-5, help='Learning rate')
    parser.add_argument('--max_len', type=int, default=128, help='Maximum sequence length')
    args = parser.parse_args()

    train_model(
        data_path=args.data_path,
        model_save_path=args.model_save_path,
        batch_size=args.batch_size,
        epochs=args.epochs,
        lr=args.lr,
        max_len=args.max_len
    )

5.3 模型评估 (src/evaluate.py)

import torch
from sklearn.metrics import accuracy_score, precision_recall_fscore_support
from preprocess import SentimentDataset
from torch.utils.data import DataLoader
from transformers import BertForSequenceClassification, BertTokenizer
import argparse
from tqdm import tqdm

def evaluate_model(data_path, model_path, batch_size=16, max_len=128):
    """
    评估BERT情感分析模型。

    Args:
        data_path (str): 测试数据的路径。
        model_path (str): 训练好的模型的路径。
        batch_size (int): 批次大小。
        max_len (int): 最大序列长度。
    """
    # 初始化分词器和数据集
    tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
    dataset = SentimentDataset(data_path, tokenizer, max_len=max_len)
    loader = DataLoader(dataset, batch_size=batch_size)

    # 加载模型
    model = BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=2)
    model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu')))
    model.eval()

    # 设备设置
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model.to(device)

    all_preds = []
    all_labels = []

    with torch.no_grad():
        for batch in tqdm(loader, desc="Evaluating"):
            input_ids = batch['input_ids'].to(device)
            attention_mask = batch['attention_mask'].to(device)
            labels = batch['label'].to(device)

            outputs = model(input_ids, attention_mask=attention_mask)
            logits = outputs.logits
            preds = torch.argmax(logits, dim=1)

            all_preds.extend(preds.cpu().numpy())
            all_labels.extend(labels.cpu().numpy())

    accuracy = accuracy_score(all_labels, all_preds)
    precision, recall, f1, _ = precision_recall_fscore_support(all_labels, all_preds, average='binary')
    print(f"Accuracy: {accuracy:.4f}")
    print(f"Precision: {precision:.4f}, Recall: {recall:.4f}, F1-score: {f1:.4f}")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Evaluate BERT model for sentiment analysis")
    parser.add_argument('--data_path', type=str, default='data/test.csv', help='Path to test data')
    parser.add_argument('--model_path', type=str, default='models/bert_model.pt', help='Path to the trained model')
    parser.add_argument('--batch_size', type=int, default=16, help='Batch size')
    parser.add_argument('--max_len', type=int, default=128, help='Maximum sequence length')
    args = parser.parse_args()

    evaluate_model(
        data_path=args.data_path,
        model_path=args.model_path,
        batch_size=args.batch_size,
        max_len=args.max_len
    )

5.4 推理 (src/inference.py)

import torch
from transformers import BertTokenizer, BertForSequenceClassification
import argparse

def predict_sentiment(text, model_path, max_len=128):
    """
    对输入的文本进行情感预测。

    Args:
        text (str): 输入的文本。
        model_path (str): 训练好的模型的路径。
        max_len (int): 最大序列长度。

    Returns:
        str: 预测的情感类别。
    """
    # 初始化分词器和模型
    tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
    model = BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=2)
    model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu')))
    model.eval()

    # 设备设置
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model.to(device)

    # 数据预处理
    inputs = tokenizer(text, return_tensors="pt", truncation=True, padding='max_length', max_length=max_len)
    inputs = {key: value.to(device) for key, value in inputs.items()}

    # 模型推理
    with torch.no_grad():
        outputs = model(**inputs)
        logits = outputs.logits
        prediction = torch.argmax(logits, dim=1).item()
        sentiment = "Positive" if prediction == 1 else "Negative"
        return sentiment

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Inference script for sentiment analysis")
    parser.add_argument('--text', type=str, required=True, help='Input text for sentiment prediction')
    parser.add_argument('--model_path', type=str, default='models/bert_model.pt', help='Path to the trained model')
    parser.add_argument('--max_len', type=int, default=128, help='Maximum sequence length')
    args = parser.parse_args()

    sentiment = predict_sentiment(
        text=args.text,
        model_path=args.model_path,
        max_len=args.max_len
    )
    print(f"Input Text: {args.text}")
    print(f"Predicted Sentiment: {sentiment}")

6. 项目运行

6.1 一键运行脚本 (run.sh)

#!/bin/bash

# 训练模型
python src/train.py --data_path=data/train.csv --model_save_path=models/bert_model.pt

# 评估模型
python src/evaluate.py --data_path=data/test.csv --model_path=models/bert_model.pt

# 推理示例
python src/inference.py --text="I love this movie!" --model_path=models/bert_model.pt

6.2 单独运行

6.2.1 训练模型
python src/train.py --data_path=data/train.csv --model_save_path=models/bert_model.pt --epochs=3 --batch_size=16
6.2.2 评估模型
python src/evaluate.py --data_path=data/test.csv --model_path=models/bert_model.pt
6.2.3 模型推理
python src/inference.py --text="This product is great!" --model_path=models/bert_model.pt

7. 结果展示

7.1 训练结果

  • 损失下降曲线:可以使用matplotlibtensorboard绘制训练过程中的损失变化。
  • 训练日志:在logs/training.log中记录训练过程。

7.2 模型评估

  • 准确率(Accuracy):模型在测试集上的准确率。
  • 精确率、召回率、F1-score:更全面地评估模型性能。

7.3 推理示例

示例:

python src/inference.py --text="I absolutely love this!" --model_path=models/bert_model.pt

输出:

Input Text: I absolutely love this!
Predicted Sentiment: Positive

8. 注意事项

  • 模型保存与加载:确保模型保存和加载时的路径正确,特别是在使用相对路径时。
  • 设备兼容性:代码中已考虑CPU和GPU的兼容性,确保设备上安装了相应的PyTorch版本。
  • 依赖版本:依赖的库版本可能会影响代码运行,建议使用requirements.txt中指定的版本。

9. 参考资料

  • BERT论文
  • Hugging Face Transformers文档
  • PyTorch官方文档

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android Osmdroid + 天地图 (一)

Osmdroid 天地图 前言正文一、配置build.gradle二、配置AndroidManifest.xml三、获取天地图的API Key① 获取开发版SHA1② 获取发布版SHA1 四、请求权限五、显示地图六、源码 前言 Osmdroid是一款完全开源的地图基本操作SDK,我们可以通过这个SDK去加一些地图API&am…

️️一篇快速上手 AJAX 异步前后端交互

AJAX 1. AJAX1.1 AJAX 简介1.2 AJAX 优缺点1.3 AJAX 前后端准备1.4 AJAX 请求基本操作1.5 AJAX 发送 POST 请求1.6 设置请求头1.7 响应 JSON 数据1.8 AJAX 请求超时与网络异常处理1.9 取消请求1.10 Fetch 发送 Ajax 请求 2. jQuery-Ajax2.1 jQuery 发送 Ajax 请求(G…

2024年11月16日 星期六 重新整理Go技术

今日格言 坚持每天进步一点点~ 一个人也可以是一个团队~ 学习全栈开发, 做自己喜欢的产品~~ 简介 大家好, 我是张大鹏, 今天是2024年11月16日星期六, 很高兴在这里给大家分享技术. 今天又是休息的一天, 做了很多的思考, 整理了自己掌握的技术, 比如Java, Python, Golang,…

炼码LintCode--数据库题库(级别:简单;数量:55道)--刷题笔记_02

目录 炼码LintCode--数据库题库(级别:简单;数量:55道)--刷题笔记_023618 耗时前三的任务(日期差)题:sql:解释:DATEDIFF 天数差order by 别名TIMESTAMPDIFF 月…

洛谷刷题日记||基础篇8

#include <iostream> #include <vector> using namespace std;int N, M; // N为行数&#xff0c;M为列数 vector<vector<char>> field; // 表示田地的网格&#xff0c;每个元素是W或. vector<vector<bool>> visited; // 用来记录网格是否访…

在Ubuntu22.04上源码构建ROS noetic环境

Ubuntu22.04上源码构建ROS noetic 起因准备环境创建工作目录并下载源码安装编译依赖包安装ros_comm和rosconsole包的两个补丁并修改pluginlib包的CMakeLists的编译器版本编译安装ROS noetic和ros_test验证 起因 最近在研究VINS-Mono从ROS移植到ROS2&#xff0c;发现在编写feat…

从dos上传shell脚本文件到Linux、麒麟执行报错“/bin/bash^M:解释器错误:没有那个文件或目录”

[rootkylin tmp]#./online_update_wars-1.3.0.sh ba51:./online_update_wars-1.3.0.sh:/bin/bash^M:解释器错误:没有那个文件或目录 使用scp命令上传文件到麒麟系统&#xff0c;执行shell脚本时报错 “/bin/bash^M:解释器错误:没有那个文件或目录” 解决方法&#xff1a; 执行…

react+hook+vite项目使用eletron打包成桌面应用+可以热更新

使用Hooks-Admin的架构 Hooks-Admin: &#x1f680;&#x1f680;&#x1f680; Hooks Admin&#xff0c;基于 React18、React-Router V6、React-Hooks、Redux、TypeScript、Vite2、Ant-Design 开源的一套后台管理框架。https://gitee.com/HalseySpicy/Hooks-Adminexe桌面应用…

华东师范大学数学分析第五版PDF习题答案上册及下册

“数学分析”是数学专业最重要的一门基础课程&#xff0c;也是报考数学类专业硕士研究生的专业考试科目。为了帮助、指导广大读者学好这门课程&#xff0c;编者编写了与华东师范大学数学科学学院主编的《数学分析》(第五版)配套的辅导用书&#xff0c;以帮助读者加深对基本概念…

FineBI漏斗图分析转化率计算,需要获取当前节点和上一节点的转化率,需要获取错行值实现方案

FineBI漏斗图分析转化率计算&#xff0c;当前节点和上一节点的转化率&#xff0c;需要获取错行值 下面这张图大家很熟悉吧&#xff0c;非常经典的漏斗转化率分析。 从漏斗图看到需要计算转化率&#xff0c;都需要获取上一步漏斗的值&#xff0c;比如计算上一个省份的门店数量…

Solana 区块链的技术解析及未来展望 #dapp开发#公链搭建

随着区块链技术的不断发展和应用场景的扩展&#xff0c;性能和可拓展性成为各大公链竞争的关键因素。Solana&#xff08;SOL&#xff09;因其高吞吐量、低延迟和低成本的技术特性&#xff0c;在众多区块链项目中脱颖而出&#xff0c;被誉为“以太坊杀手”之一。本文将从技术层面…

FPGA开发-逻辑分析仪的应用-数字频率计的设计

目录 逻辑分析仪的应用 数字频率计的设计 -基于原理图方法 主控电路设计 分频器设计 顶层电路设计 数字系统开发不但需要进行仿真分析&#xff0c;更重要的是需要进行实际测试。 逻辑分析仪的应用 测试方式&#xff1a;&#xff08;1&#xff09;传统的测试方式&#…

基于python Django的boss直聘数据采集与分析预测系统,爬虫可以在线采集,实时动态显示爬取数据,预测基于技能匹配的预测模型

本系统是基于Python Django框架构建的“Boss直聘”数据采集与分析预测系统&#xff0c;旨在通过技能匹配的方式对招聘信息进行分析与预测&#xff0c;帮助求职者根据自身技能找到最合适的职位&#xff0c;同时为招聘方提供更精准的候选人推荐。系统的核心预测模型基于职位需求技…

kubesphere环境-本地Harbor仓库+k8s集群(单master 多master)+Prometheus监控平台部署

前言&#xff1a;半月前在公司生产环境上离线部署了k8s集群Victoria Metrics(二开版)自研版夜莺 监控平台的搭建&#xff0c;下面我租用3台华为云服务器演示部署kubesphere环境-本地Harbor仓库k8s集群&#xff08;单master节点 & 单master节点&#xff09;Prometheus监控部…

车载诊断框架 --- UDS小白入门篇

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 所有人的看法和评价都是暂时的&#xff0c;只有自己的经历是伴随一生的&#xff0c;几乎所有的担忧和畏惧…

强大的正则表达式——Easy

进入题目界面输入难度1后&#xff0c;让我们输入正则表达式&#xff08;regex&#xff09;&#xff1a; 目前不清楚题目要求&#xff0c;先去下载附件查看情况&#xff1a; import re import random# pip install libscrc import libscrcallowed_chars "0123456789()|*&q…

字节青训-小C的外卖超时判断、小C的排列询问

目录 一、小C的外卖超时判断 问题描述 测试样例 解题思路&#xff1a; 问题理解 数据结构选择 算法步骤 最终代码&#xff1a; 运行结果&#xff1a; 二、小C的排列询问 问题描述 测试样例 最终代码&#xff1a; 运行结果&#xff1a; ​编辑 一、小C的外卖超时判断…

游戏引擎学习第13天

视频参考:https://www.bilibili.com/video/BV1QQUaYMEEz/ 改代码的地方尽量一张图说清楚吧,懒得浪费时间 game.h #pragma once #include <cmath> #include <cstdint> #include <malloc.h>#define internal static // 用于定义内翻译单元内部函数 #…

C++11(五)----lambda表达式

文章目录 lambda表达式 lambda表达式 lambda表达式可以看作一个匿名函数 语法 [capture-list] (parameters) mutable -> return-type { statement } auto func1 [](int a, int b) mutable -> int {return a b; }; *capture-list&#xff1a;捕捉列表。编译器根据[]来 判…

CSS基础知识05(弹性盒子、布局详解,动画,3D转换,calc)

目录 0、弹性盒子、布局 0.1.弹性盒子的基本概念 0.2.弹性盒子的主轴和交叉轴 0.3.弹性盒子的属性 flex-direction row row-reverse column column-reverse flex-wrap nowrap wrap wrap-reverse flex-dirction和flex-wrap的组合简写模式 justify-content flex-s…