基于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. 参考资料

相关推荐
靴子学长37 分钟前
基于字节大模型的论文翻译(含免费源码)
人工智能·深度学习·nlp
AI_NEW_COME2 小时前
知识库管理系统可扩展性深度测评
人工智能
海棠AI实验室2 小时前
AI的进阶之路:从机器学习到深度学习的演变(一)
人工智能·深度学习·机器学习
hunteritself2 小时前
AI Weekly『12月16-22日』:OpenAI公布o3,谷歌发布首个推理模型,GitHub Copilot免费版上线!
人工智能·gpt·chatgpt·github·openai·copilot
IT古董3 小时前
【机器学习】机器学习的基本分类-强化学习-策略梯度(Policy Gradient,PG)
人工智能·机器学习·分类
centurysee3 小时前
【最佳实践】Anthropic:Agentic系统实践案例
人工智能
mahuifa3 小时前
混合开发环境---使用编程AI辅助开发Qt
人工智能·vscode·qt·qtcreator·编程ai
四口鲸鱼爱吃盐3 小时前
Pytorch | 从零构建GoogleNet对CIFAR10进行分类
人工智能·pytorch·分类
蓝天星空3 小时前
Python调用open ai接口
人工智能·python
睡觉狂魔er3 小时前
自动驾驶控制与规划——Project 3: LQR车辆横向控制
人工智能·机器学习·自动驾驶