基于BERT的情感分析

基于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 安装依赖

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

bash 复制代码
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的格式如下:

text label
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)

python 复制代码
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)

python 复制代码
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)

python 复制代码
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)

python 复制代码
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)

bash 复制代码
#!/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 训练模型
bash 复制代码
python src/train.py --data_path=data/train.csv --model_save_path=models/bert_model.pt --epochs=3 --batch_size=16
6.2.2 评估模型
bash 复制代码
python src/evaluate.py --data_path=data/test.csv --model_path=models/bert_model.pt
6.2.3 模型推理
bash 复制代码
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 推理示例

示例:

bash 复制代码
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. 参考资料

相关推荐
lijianhua_97128 小时前
国内某顶级大学内部用的ai自动生成论文的提示词
人工智能
EDPJ8 小时前
当图像与文本 “各说各话” —— CLIP 中的模态鸿沟与对象偏向
深度学习·计算机视觉
蔡俊锋8 小时前
用AI实现乐高式大型可插拔系统的技术方案
人工智能·ai工程·ai原子能力·ai乐高工程
自然语8 小时前
人工智能之数字生命 认知架构白皮书 第7章
人工智能·架构
大熊背8 小时前
利用ISP离线模式进行分块LSC校正的方法
人工智能·算法·机器学习
eastyuxiao9 小时前
如何在不同的机器上运行多个OpenClaw实例?
人工智能·git·架构·github·php
诸葛务农9 小时前
AGI 主要技术路径及核心技术:归一融合及未来之路5
大数据·人工智能
光影少年9 小时前
AI Agent智能体开发
人工智能·aigc·ai编程
ai生成式引擎优化技术9 小时前
TSPR-WEB-LLM-HIC (TWLH四元结构)AI生成式引擎(GEO)技术白皮书
人工智能
帐篷Li9 小时前
9Router:开源AI路由网关的架构设计与技术实现深度解析
人工智能