自然语言处理(NLP)作为人工智能的核心领域,已广泛应用于情感分析、机器翻译、智能问答等场景。本文以中文文本情感分析为目标,从基础概念到实战代码,完整讲解基于 LSTM 的情感识别实现过程,涵盖数据预处理、模型构建、训练优化、预测部署全流程,所有代码均可直接运行。

一、NLP 基础与情感分析任务概述

1.1 什么是情感分析?

情感分析(Sentiment Analysis)是对文本中的主观信息进行提取和分类的技术,核心是判断文本的情绪倾向(如正面 / 负面 / 中性)。在中文场景中,由于语言的特殊性(无空格分词、语义丰富),需要针对性的处理方案。

1.2 应用场景

  • 电商平台:分析用户对商品的评价(如 "物流很快,推荐购买"→正面)
  • 舆情监控:跟踪社交媒体对事件的态度
  • 客服系统:自动识别用户反馈的情绪(如投诉→负面)

二、循环神经网络与 LSTM 原理

处理文本序列数据时,传统神经网络无法捕捉上下文依赖关系。循环神经网络(RNN) 通过 "记忆" 机制解决这一问题,而LSTM(长短期记忆网络) 则进一步解决了 RNN 的长期依赖缺陷。

2.1 LSTM 核心优势

  • 引入门控机制(遗忘门、输入门、输出门),控制信息的保留与遗忘
  • 解决 RNN 的 "梯度消失" 问题,能有效处理长文本序列
  • 适合情感分析等需要上下文理解的任务(如 "这家店不好,但服务不错" 需结合全句判断)

三、中文情感分析实战(基于 PyTorch)

3.1 环境准备

# 安装必要库
pip install torch pandas numpy jieba scikit-learn

3.2 数据集介绍

使用酒店评论数据集(5265 条),包含 "评论内容" 和 "情感标签"(1 = 正面,0 = 负面)。示例数据格式:

评论内容 标签
房间干净,服务热情,下次还来 1
卫生差,隔音不好,不推荐 0

3.3 数据预处理(关键步骤)

中文文本需经过分词、去停用词、向量化等处理,才能输入模型。

3.3.1 完整预处理代码
import re
import jieba
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from collections import Counter

# 1. 加载数据
def load_data(data_path):
    """加载数据集并简单清洗"""
    df = pd.read_csv(data_path)
    # 去除空值
    df = df.dropna(subset=['评论内容', '标签'])
    # 去除特殊符号和数字
    df['评论内容'] = df['评论内容'].apply(lambda x: re.sub(r'[^\u4e00-\u9fa5]', ' ', x))
    return df

# 2. 中文分词与去停用词
def tokenize(text, stopwords):
    """
    分词并过滤停用词
    :param text: 输入文本
    :param stopwords: 停用词列表
    :return: 分词后的词语列表
    """
    words = jieba.cut(text)  # 结巴分词
    return [word for word in words if word.strip() and word not in stopwords]

# 3. 构建词汇表
def build_vocab(tokenized_texts, min_freq=5):
    """
    从分词后的文本构建词汇表
    :param tokenized_texts: 分词后的文本列表
    :param min_freq: 词语最低出现频率(过滤低频词)
    :return: 词汇表(word→index)
    """
    # 统计词频
    word_counts = Counter()
    for words in tokenized_texts:
        word_counts.update(words)
    # 过滤低频词
    filtered_words = [word for word, count in word_counts.items() if count >= min_freq]
    # 构建词汇表(预留0给PAD,1给UNK)
    vocab = {'<PAD>': 0, '<UNK>': 1}
    vocab.update({word: i+2 for i, word in enumerate(filtered_words)})
    return vocab

# 4. 文本向量化
def text_to_sequence(text, vocab, max_len):
    """
    将文本转换为整数序列
    :param text: 分词后的文本
    :param vocab: 词汇表
    :param max_len: 序列最大长度(超过截断,不足补0)
    :return: 整数序列
    """
    # 词语→索引(未知词用1表示)
    sequence = [vocab.get(word, 1) for word in text]
    # 截断或补全
    if len(sequence) > max_len:
        sequence = sequence[:max_len]
    else:
        sequence += [0] * (max_len - len(sequence))
    return sequence

# 主函数:执行预处理
def preprocess_data(data_path, stopwords_path, max_len=50):
    # 加载停用词(中文常用停用词表)
    with open(stopwords_path, 'r', encoding='utf-8') as f:
        stopwords = set(f.read().splitlines())
    
    # 加载并清洗数据
    df = load_data(data_path)
    texts = df['评论内容'].tolist()
    labels = df['标签'].tolist()
    
    # 分词
    tokenized_texts = [tokenize(text, stopwords) for text in texts]
    print(f"示例分词结果:{tokenized_texts[0]}")  # 查看第一条分词结果
    
    # 构建词汇表
    vocab = build_vocab(tokenized_texts)
    print(f"词汇表大小:{len(vocab)}")
    
    # 文本向量化
    sequences = [text_to_sequence(words, vocab, max_len) for words in tokenized_texts]
    sequences = np.array(sequences)
    labels = np.array(labels)
    
    # 划分训练集和测试集(8:2)
    x_train, x_test, y_train, y_test = train_test_split(
        sequences, labels, test_size=0.2, random_state=42
    )
    
    return (x_train, y_train), (x_test, y_test), vocab, max_len

# 执行预处理(需准备数据集和停用词表)
# (train_data, train_labels), (test_data, test_labels), vocab, max_len = preprocess_data(
#     data_path='hotel_reviews.csv',
#     stopwords_path='stopwords.txt',
#     max_len=50
# )
3.3.2 关键说明
  • 停用词:过滤 "的"、"是" 等无实际意义的词(可从网上下载中文停用词表)
  • 词汇表:只保留出现次数≥5 的词,减少噪声
  • 序列长度:统一设为 50(根据数据分布调整,过长会增加计算量)

3.4 LSTM 模型构建

使用 PyTorch 实现情感分析模型,结构包括嵌入层、LSTM 层和分类层。

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader

# 1. 自定义数据集
class SentimentDataset(Dataset):
    def __init__(self, sequences, labels):
        self.sequences = sequences
        self.labels = labels
    
    def __len__(self):
        return len(self.sequences)
    
    def __getitem__(self, idx):
        # 转换为Tensor
        sequence = torch.tensor(self.sequences[idx], dtype=torch.long)
        label = torch.tensor(self.labels[idx], dtype=torch.float32)
        return sequence, label

# 2. LSTM模型
class LSTMSentimentClassifier(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim, num_layers=2, dropout=0.5):
        super(LSTMSentimentClassifier, self).__init__()
        # 嵌入层:将词索引转换为词向量
        self.embedding = nn.Embedding(
            num_embeddings=vocab_size,
            embedding_dim=embedding_dim,
            padding_idx=0  # PAD符号不参与训练
        )
        
        # LSTM层
        self.lstm = nn.LSTM(
            input_size=embedding_dim,
            hidden_size=hidden_dim,
            num_layers=num_layers,
            bidirectional=True,  # 双向LSTM,同时捕捉上下文
            dropout=dropout,
            batch_first=True  # 输入格式:(batch, seq_len, feature)
        )
        
        # 全连接层(双向LSTM输出需×2)
        self.fc = nn.Linear(hidden_dim * 2, output_dim)
        # 输出层(sigmoid用于二分类)
        self.sigmoid = nn.Sigmoid()
    
    def forward(self, x):
        # x shape: (batch_size, seq_len)
        embedded = self.embedding(x)  # (batch_size, seq_len, embedding_dim)
        
        # LSTM输出:output包含所有时间步的隐藏状态
        # hidden包含最后一个时间步的隐藏状态(用于分类)
        output, (hidden, cell) = self.lstm(embedded)
        
        # 拼接双向LSTM的最后一层隐藏状态
        hidden = torch.cat((hidden[-2,:,:], hidden[-1,:,:]), dim=1)  # (batch_size, hidden_dim*2)
        
        # 分类输出
        out = self.fc(hidden)
        out = self.sigmoid(out)  # 输出概率(0~1)
        return out.squeeze()  # 去除多余维度

3.5 模型训练与评估

# 1. 训练参数
vocab_size = len(vocab)
embedding_dim = 100  # 词向量维度
hidden_dim = 128     # LSTM隐藏层维度
output_dim = 1       # 输出维度(二分类)
num_layers = 2       # LSTM层数
batch_size = 32
epochs = 10
lr = 0.001
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')  # 优先使用GPU

# 2. 初始化模型、损失函数和优化器
model = LSTMSentimentClassifier(
    vocab_size=vocab_size,
    embedding_dim=embedding_dim,
    hidden_dim=hidden_dim,
    output_dim=output_dim,
    num_layers=num_layers
).to(device)

criterion = nn.BCELoss()  # 二元交叉熵损失(适合二分类)
optimizer = optim.Adam(model.parameters(), lr=lr)

# 3. 数据加载器
train_dataset = SentimentDataset(train_data, train_labels)
test_dataset = SentimentDataset(test_data, test_labels)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

# 4. 训练函数
def train(model, train_loader, criterion, optimizer, device):
    model.train()  # 训练模式
    total_loss = 0.0
    correct = 0
    total = 0
    
    for sequences, labels in train_loader:
        sequences, labels = sequences.to(device), labels.to(device)
        
        # 清零梯度
        optimizer.zero_grad()
        
        # 前向传播
        outputs = model(sequences)
        loss = criterion(outputs, labels)
        
        # 反向传播+参数更新
        loss.backward()
        optimizer.step()
        
        # 计算指标
        total_loss += loss.item() * sequences.size(0)
        preds = (outputs > 0.5).float()  # 概率>0.5视为正面
        correct += (preds == labels).sum().item()
        total += labels.size(0)
    
    # 平均损失和准确率
    avg_loss = total_loss / total
    acc = correct / total
    return avg_loss, acc

# 5. 评估函数
def evaluate(model, test_loader, criterion, device):
    model.eval()  # 评估模式(关闭dropout等)
    total_loss = 0.0
    correct = 0
    total = 0
    
    with torch.no_grad():  # 不计算梯度,节省内存
        for sequences, labels in test_loader:
            sequences, labels = sequences.to(device), labels.to(device)
            
            outputs = model(sequences)
            loss = criterion(outputs, labels)
            
            total_loss += loss.item() * sequences.size(0)
            preds = (outputs > 0.5).float()
            correct += (preds == labels).sum().item()
            total += labels.size(0)
    
    avg_loss = total_loss / total
    acc = correct / total
    return avg_loss, acc

# 6. 执行训练
best_acc = 0.0
for epoch in range(epochs):
    # 训练
    train_loss, train_acc = train(model, train_loader, criterion, optimizer, device)
    # 评估
    test_loss, test_acc = evaluate(model, test_loader, criterion, device)
    
    print(f"Epoch {epoch+1}/{epochs}")
    print(f"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f}")
    print(f"Test Loss: {test_loss:.4f}, Test Acc: {test_acc:.4f}\n")
    
    # 保存最优模型
    if test_acc > best_acc:
        best_acc = test_acc
        torch.save(model.state_dict(), 'best_lstm_model.pth')
        print(f"保存最优模型(准确率:{best_acc:.4f})")

3.6 模型预测(实战应用)

使用训练好的模型对新评论进行情感预测:

def predict_sentiment(text, model, vocab, max_len, device):
    """
    预测单条文本的情感
    :param text: 输入中文文本
    :param model: 训练好的模型
    :param vocab: 词汇表
    :param max_len: 序列最大长度
    :param device: 设备(cpu/gpu)
    :return: 情感标签(1=正面,0=负面)和概率
    """
    # 1. 预处理(与训练时一致)
    # 加载停用词
    with open('stopwords.txt', 'r', encoding='utf-8') as f:
        stopwords = set(f.read().splitlines())
    # 分词
    words = tokenize(text, stopwords)
    # 转换为序列
    sequence = text_to_sequence(words, vocab, max_len)
    # 转换为Tensor并增加batch维度
    sequence = torch.tensor(sequence, dtype=torch.long).unsqueeze(0).to(device)
    
    # 2. 预测
    model.eval()
    with torch.no_grad():
        output = model(sequence)
        prob = output.item()  # 正面概率
        label = 1 if prob > 0.5 else 0
    
    return label, prob

# 加载最优模型
model = LSTMSentimentClassifier(
    vocab_size=vocab_size,
    embedding_dim=embedding_dim,
    hidden_dim=hidden_dim,
    output_dim=output_dim
).to(device)
model.load_state_dict(torch.load('best_lstm_model.pth'))

# 测试预测
test_texts = [
    "房间宽敞明亮,服务态度很好,性价比高",
    "卫生太差,床品有异味,不会再来了",
    "位置不错,交通方便,但隔音效果一般"
]

for text in test_texts:
    label, prob = predict_sentiment(text, model, vocab, max_len, device)
    sentiment = "正面" if label == 1 else "负面"
    print(f"文本:{text}")
    print(f"预测结果:{sentiment}(概率:{prob:.4f})\n")
预测结果示例

plaintext

文本:房间宽敞明亮,服务态度很好,性价比高
预测结果:正面(概率:0.9723)

文本:卫生太差,床品有异味,不会再来了
预测结果:负面(概率:0.0315)

文本:位置不错,交通方便,但隔音效果一般
预测结果:正面(概率:0.6218)  # 模型捕捉到正面词汇更多

四、模型优化与进阶方向

4.1 提升模型性能的方法

  1. 使用预训练词向量:如 Word2Vec、GloVe 的中文预训练模型,替换随机初始化的嵌入层
  2. 调整模型结构:增加 LSTM 层数、隐藏层维度,或改用 GRU(计算量更小)
  3. 正则化:添加 L2 正则化、增大 dropout 率,防止过拟合
  4. 数据增强:对文本进行同义词替换、随机插入 / 删除词语,扩充训练数据

4.2 进阶模型选择

  • BiLSTM+Attention:通过注意力机制让模型关注情感关键词(如 "好"、"差")
  • BERT 等预训练模型:使用中文 BERT(如 bert-base-chinese),通过微调实现更高准确率
    # BERT微调示例(需安装transformers库)
    from transformers import BertTokenizer, BertForSequenceClassification
    tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
    model = BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=2)
    

五、总结

本文完整实现了基于 LSTM 的中文情感分析流程,核心步骤包括:

  1. 数据预处理:解决中文分词、文本向量化等关键问题
  2. 模型构建:利用 LSTM 捕捉文本上下文依赖关系
  3. 训练优化:通过合理的参数设置和评估指标提升模型性能
  4. 实际应用:实现对新文本的情感预测

对于追求更高准确率的场景,建议尝试 BERT 等预训练模型,但 LSTM 因其简单高效,仍是中小数据集的首选方案。通过本文代码,读者可快速搭建中文情感分析系统,并根据实际需求进行扩展优化

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐