从 NLP 基础到 LSTM 情感分析:中文文本情感识别全流程实践(附完整代码)
情感分析(Sentiment Analysis)是对文本中的主观信息进行提取和分类的技术,核心是判断文本的情绪倾向(如正面 / 负面 / 中性)。在中文场景中,由于语言的特殊性(无空格分词、语义丰富),需要针对性的处理方案。使用酒店评论数据集(5265 条),包含 "评论内容" 和 "情感标签"(1 = 正面,0 = 负面)。评论内容标签房间干净,服务热情,下次还来1卫生差,隔音不好,不推荐0数据
自然语言处理(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 提升模型性能的方法
- 使用预训练词向量:如 Word2Vec、GloVe 的中文预训练模型,替换随机初始化的嵌入层
- 调整模型结构:增加 LSTM 层数、隐藏层维度,或改用 GRU(计算量更小)
- 正则化:添加 L2 正则化、增大 dropout 率,防止过拟合
- 数据增强:对文本进行同义词替换、随机插入 / 删除词语,扩充训练数据
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 的中文情感分析流程,核心步骤包括:
- 数据预处理:解决中文分词、文本向量化等关键问题
- 模型构建:利用 LSTM 捕捉文本上下文依赖关系
- 训练优化:通过合理的参数设置和评估指标提升模型性能
- 实际应用:实现对新文本的情感预测
对于追求更高准确率的场景,建议尝试 BERT 等预训练模型,但 LSTM 因其简单高效,仍是中小数据集的首选方案。通过本文代码,读者可快速搭建中文情感分析系统,并根据实际需求进行扩展优化
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)