1. 引言:循环神经网络在情感分析中的应用价值
在当今大数据时代,情感分析已成为自然语言处理(NLP)领域中最重要的应用之一。无论是电商平台的产品评论分析,还是社交媒体的舆情监控,准确识别用户情感倾向都具有巨大商业价值。然而,传统的机器学习方法在处理序列数据时面临着巨大挑战,特别是对于具有上下文依赖的文本数据。
循环神经网络(RNN)作为一种专门处理序列数据的神经网络架构,在情感分析任务中展现出独特优势。与传统方法不同,RNN能够捕捉文本中的时序信息和长期依赖关系,这对于理解自然语言的语义至关重要。例如,在中文情感分析中,"这个产品虽然贵但是质量很好"这样的句子,只有理解整个句子上下文才能准确判断其积极情感倾向。
本文将从零开始,详细讲解如何实现一个完整的RNN模型,并将其应用于中文情感分析任务。通过这个实践项目,您不仅将深入理解RNN的工作原理,还能掌握从数据处理到模型训练的完整流程。
2. 环境准备与理论基础
2.1. 核心库介绍
构建RNN模型需要多个Python库的支持,每个库都有其特定作用:
# 导入数值计算库
import numpy as np
# 导入数据处理库
import pandas as pd
# 导入中文分词库
import jieba as jb
# 导入数据集划分工具
from sklearn.model_selection import train_test_split
# 导入模型评估工具,用于计算模型的准确率
from sklearn.metrics import accuracy_score
# 导入警告过滤工具
import warnings
# 忽略警告信息
warnings.filterwarnings("ignore")
-
NumPy:提供高效的数组操作,是神经网络计算的基础
-
Pandas:简化数据处理流程,方便数据探索和分析
-
Jieba:准确高效的中文分词工具
-
scikit-learn:提供数据集划分和评估功能
-
warnings:控制警告信息输出,保持代码整洁
2.2. RNN理论基础
1. RNN的核心思想
循环神经网络通过引入循环连接,使网络能够"记住"之前的信息。这种记忆能力使RNN特别适合处理序列数据,如文本、语音、时间序列等。
2. RNN的基本结构
RNN在时间步t的计算公式如下:
-
隐藏状态:h_t = tanh(W_xh·x_t + W_hh·h_{t-1} + b_h)
-
输出:y_t = softmax(W_hy·h_t + b_y)
其中:
-
x_t:时间步t的输入
-
h_t:时间步t的隐藏状态
-
y_t:时间步t的输出
-
W_xh, W_hh, W_hy:权重矩阵
-
b_h, b_y:偏置项
3. SimpleRNN类的设计与实现
3.1. 类初始化
RNN模型的初始化涉及多个重要参数的设置:
class SimpleRNN:
"""简单RNN模型初始化"""
def __init__(self, input_size, hidden_size, output_size, learning_rate=0.01):
# 参数说明:
# input_size: 输入特征大小
# hidden_size: 隐藏层大小
# output_size: 输出层大小
# learning_rate: 学习率,默认值为0.01
self.input_size = input_size
self.hidden_size = hidden_size
self.output_size = output_size
self.learning_rate = learning_rate
# 初始化权重矩阵
self.W_xh = np.random.randn(input_size, hidden_size) * 0.01
self.W_hh = np.random.randn(hidden_size, hidden_size) * 0.01
self.W_hy = np.random.randn(hidden_size, output_size) * 0.01
# 初始化偏置项
self.b_h = np.zeros((1, hidden_size))
self.b_y = np.zeros((1, output_size))
权重初始化采用小随机数策略(乘以0.01),这种策略有助于避免梯度爆炸或消失问题。偏置项初始化为零,这是神经网络中的常见做法。
3.2. 激活函数实现
激活函数是神经网络的非线性来源,使网络能够学习复杂的模式:
"""Tanh激活函数"""
def tanh(self, x):
return np.tanh(x) # 隐藏层激活函数
"""Softmax激活函数"""
def softmax(self, x):
exp_x = np.exp(x – np.max(x, axis=1, keepdims=True)) # 防止溢出
return exp_x / np.sum(exp_x, axis=1, keepdims=True) # 输出层激活函数
激活函数选择依据:
-
Tanh:输出范围在[-1, 1],适合作为隐藏层激活函数
-
Softmax:将输出转换为概率分布,适合多分类问题
3.3. 前向传播实现
前向传播是RNN的核心,负责计算网络输出:
"""前向传播"""
def forward(self, X):
# 获取输入序列长度、批次大小
seq_len, batch_size, _ = X.shape
# 初始化隐藏层状态
self.h = np.zeros((seq_len + 1, batch_size, self.hidden_size))
# 初始化输出层状态
self.y = np.zeros((seq_len, batch_size, self.output_size))
for t in range(seq_len):
# 计算隐藏层状态
# h_t = tanh(W_xh * x_t + W_hh * h_t-1 + b_h)
self.h[t + 1] = self.tanh(np.dot(X[t], self.W_xh) +
np.dot(self.h[t], self.W_hh) + self.b_h)
# 计算输出层状态
# y_t = softmax(W_hy * h_t + b_y)
self.y[t] = self.softmax(np.dot(self.h[t + 1], self.W_hy) + self.b_y)
return self.y
时间步计算流程:
-
输入准备:接收三维张量(序列长度×批次大小×特征维度)
-
状态初始化:隐藏状态初始化为零向量
-
循环计算:按时间步顺序计算每个时间步的隐藏状态和输出
3.4. 反向传播实现
反向传播通过时间(BPTT)是RNN训练的关键:
"""反向传播"""
def backward(self, X, y):
seq_len, batch_size, _ = X.shape
self.dh = np.zeros((seq_len + 2, batch_size, self.hidden_size))
self.dy = np.zeros((seq_len, batch_size, self.output_size))
self.dx = np.zeros((seq_len, batch_size, self.input_size))
# 初始化梯度
self.dW_hy = np.zeros_like(self.W_hy)
self.dW_hh = np.zeros_like(self.W_hh)
self.dW_xh = np.zeros_like(self.W_xh)
self.db_h = np.zeros_like(self.b_h)
self.db_y = np.zeros_like(self.b_y)
# 从最后一个时间步开始反向传播
for t in reversed(range(seq_len)):
# 计算输出层状态梯度
self.dy[t] = self.y[t] – y[t]
# 计算隐藏层到输出层的权重矩阵梯度
self.dW_hy += np.dot(self.h[t + 1].T, self.dy[t])
# 计算输出层偏置项梯度
self.db_y += np.sum(self.dy[t], axis=0, keepdims=True)
# 计算当前输出误差引起的隐藏层梯度
dh_from_output = np.dot(self.dy[t], self.W_hy.T)
# 计算下一时间步的隐藏层梯度
dh_from_next = np.dot(self.dh[t + 2], self.W_hh.T) if (t + 1) < seq_len else 0
# 合并梯度并应用tanh的导数
self.dh[t + 1] = (dh_from_output + dh_from_next) * (1 – self.h[t + 1] ** 2)
# 计算权重梯度
self.dW_hh += np.dot(self.h[t].T, self.dh[t + 1])
self.dW_xh += np.dot(X[t].T, self.dh[t + 1])
# 计算偏置梯度
self.db_h += np.sum(self.dh[t + 1], axis=0, keepdims=True)
# 计算输入梯度
self.dx[t] = np.dot(self.dh[t + 1], self.W_xh.T)
return self.dx
BPTT算法要点:
-
时间反向传播:从最后一个时间步开始,向前传播梯度
-
梯度链式法则:通过链式法则计算每个参数的梯度
-
梯度累积:RNN中同一参数在不同时间步共享,梯度需要累加
3.5. 权重更新与训练
"""权重更新"""
def update_weights(self):
# 使用梯度下降法更新所有权重和偏置
self.W_xh -= self.learning_rate * self.dW_xh
self.W_hh -= self.learning_rate * self.dW_hh
self.W_hy -= self.learning_rate * self.dW_hy
self.b_h -= self.learning_rate * self.db_h
self.b_y -= self.learning_rate * self.db_y
"""模型训练"""
def train(self, X, y, epochs=100):
# 训练模型
for epoch in range(epochs):
# 前向传播
y_pred = self.forward(X)
# 反向传播
self.backward(X, y)
# 更新权重
self.update_weights()
# 计算损失
loss = -np.mean(np.sum(y * np.log(y_pred + 1e-8), axis=1))
# 打印损失
if epoch % 10 == 0:
print(f"Epoch {epoch}, Loss: {loss:.4f}")
4. 数据处理与预处理
4.1. 数据集创建
def create_dataset():
"""创建简单的情感分析数据集"""
# 扩展训练数据集
data = {
'text': [
'这个产品很好', '质量太差了', '服务不错', '价格太贵',
'物流很快', '包装精美', '客服耐心', '商品有瑕疵',
'性价比高', '使用不方便', '颜色很好看', '尺寸不合适',
'材质不错', '味道不好闻', '价格合理', '物流太慢',
'包装破损', '客服态度好', '这个东西很好用', '质量非常差',
'服务态度很好', '价格真的贵', '物流速度很快', '包装很用心',
'客服非常耐心', '商品质量有问题', '性价比非常高', '使用起来不方便'
],
'label': [1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0]
}
# 转换为DataFrame
df = pd.DataFrame(data)
return df
数据集设计原则:
-
类别平衡:确保正面和负面样本数量接近
-
多样性:覆盖不同主题和表达方式
-
真实性:模拟真实场景中的评论内容
4.2. 文本预处理
预处理步骤直接影响模型性能:
# 文本预处理
def preprocess_text(text):
# 分词
seg_list = jb.cut(text)
# 合并分词结果
return " ".join(seg_list)
中文分词的重要性,中文没有自然的分隔符,分词是将连续字符转换为有意义的词语的关键步骤。准确的分词有助于模型理解语义。
4.3. 序列化与编码
将文本转换为数值表示是NLP任务的核心:
# 将文本转换为序列
def text_to_sequence(text, vocab, max_len=5):
words = text.split() # 按空格分词
sequence = [] # 初始化序列
for word in words[:max_len]: # 只取前max_len个单词
if word in vocab:
sequence.append(vocab[word]) # 获取词对应的ID
else:
sequence.append(0) # 未知词用0表示
# 填充序列到固定长度(不足的部分用0填充)
while len(sequence) < max_len:
sequence.append(0)
return sequence
One-hot编码原理:One-hot编码将每个词表示为长度为词汇表大小的向量,其中只有一个位置为1,其余为0。虽然简单直观,但在词汇表较大时会产生高维稀疏问题。
5. 实验流程与结果分析
5.1. 完整实验流程
主函数整合了从数据准备到模型评估的完整流程:
def main():
# 1.数据准备
print("===数据准备===")
df = create_dataset()
print(f"数据集大小:{len(df)}")
# 2.文本预处理
print("\\n===文本预处理===")
df['processed'] = df['text'].apply(preprocess_text)
# 3.构建词汇表
print("\\n===构建词汇表===")
vocab = {'<PAD>': 0}
for text in df['processed']:
for word in text.split():
if word not in vocab:
vocab[word] = len(vocab)
vocab_size = len(vocab)
print(f"词汇表大小:{vocab_size}")
# 4.转换为序列
print("\\n===序列转换===")
max_len = 5
sequences = []
for text in df['processed']:
seq = text_to_sequence(text, vocab, max_len)
sequences.append(seq)
# 转换为独热编码
X = np.zeros((len(sequences), max_len, vocab_size))
for i, seq in enumerate(sequences):
for j, word_index in enumerate(seq):
X[i, j, word_index] = 1
# 标签编码
y = df['label'].values
y_onehot = np.zeros((len(y), 2))
y_onehot[np.arange(len(y)), y] = 1
# 5.数据集分割
X_train, X_test, y_train, y_test = train_test_split(
X, y_onehot, test_size=0.25, random_state=42
)
# 调整维度为(seq_len, batch_size, input_size)
X_train = X_train.transpose(1, 0, 2)
X_test = X_test.transpose(1, 0, 2)
# 6.训练RNN模型
print("\\n===训练RNN模型===")
hidden_size = 32
output_size = 2
model = SimpleRNN(vocab_size, hidden_size, output_size, learning_rate=0.01)
model.train(X_train, y_train, epochs=200)
# 7.模型评估
print("\\n===模型评估===")
y_pred = model.predict(X_test)
y_test_labels = np.argmax(y_test, axis=1)
accuracy = accuracy_score(y_test_labels, y_pred)
print(f"测试集准确率:{accuracy:.4f}")
# 8.预测新文本
print("\\n===预测新文本===")
new_texts = [
'这个商品很有价值',
'服务糟糕',
'物流很及时',
'包装很精美',
'客服很耐心'
]
for text in new_texts:
processed = preprocess_text(text)
seq = text_to_sequence(processed, vocab, max_len)
x = np.zeros((max_len, 1, vocab_size))
for j, word_id in enumerate(seq):
x[j, 0, word_id] = 1
result = model.predict(x)[0]
sentiment = "正面" if result == 1 else "负面"
print(f"文本: {text}")
print(f"预测: {sentiment}")
print()
5.2. 实验结果分析
1. 训练过程观察
在训练过程中,损失函数应呈现下降趋势。如果损失波动较大或下降缓慢,可能需要调整学习率或检查数据预处理过程。
2. 准确率评估
模型在测试集上的准确率反映了其泛化能力。对于二分类问题,准确率超过随机猜测(50%)即表示模型学到了一些模式。
6. 模型优化与改进方向
6.1. 当前实现局限性
当前的SimpleRNN实现存在一些局限性:
-
梯度消失/爆炸:在处理长序列时容易出现
-
计算效率:One-hot编码导致高维稀疏问题
-
模型容量:简单RNN难以捕捉复杂模式
6.2. 改进方案
1. 使用高级RNN变体
-
LSTM:通过门控机制解决长依赖问题
-
GRU:简化版LSTM,计算效率更高
-
双向RNN:同时考虑前后文信息
2. 词嵌入技术
用词向量(Word2Vec, GloVe, FastText)代替One-hot编码:
-
降低维度
-
捕捉语义相似性
-
处理未登录词
3. 注意力机制
引入注意力机制使模型能够关注输入序列中的重要部分:
-
提高模型解释性
-
改善长序列处理能力
4. 数据增强
-
同义词替换
-
回译技术
-
随机插入/删除
7. 实际应用建议
7.1. 部署注意事项
-
性能优化:考虑使用批量推理和模型量化
-
实时性要求:优化预测延迟,支持高并发
-
模型更新:建立定期更新机制,适应语言变化
7.2. 领域适应
当将模型应用于特定领域时:
-
领域词典:构建领域专业词汇表
-
迁移学习:使用预训练模型进行微调
-
领域数据:收集领域特定训练数据
8. 完整代码示例
以下是完整的RNN中文情感分析实现代码:
# 关键点说明:
# 这是一个简单的RNN实现,用于中文文本情感分析(正面/负面)
# 数据预处理包括:中文分词、构建词汇表、序列化和one-hot编码
# RNN的核心是前向传播部分,计算隐藏状态和输出
# 最后展示了如何用训练好的模型预测新文本的情感倾向
# 这个代码主要目的是展示RNN的基本原理和工作流程,实际应用中可能需要:
# 实现完整的训练过程
# 使用更复杂的RNN变体(如LSTM、GRU)
# 使用词嵌入代替one-hot编码
# 增加更多的训练数据
# 导入数值计算库
import numpy as np
# 导入数据处理库
import pandas as pd
# 导入中文分词库
import jieba as jb
# 导入数据集划分工具
from sklearn.model_selection import train_test_split
# 导入模型评估工具,用于计算模型的准确率
from sklearn.metrics import accuracy_score
# 导入警告过滤工具
import warnings
# 忽略警告信息
warnings.filterwarnings("ignore")
# 实现简单的RNN模型
class SimpleRNN:
"""简单RNN模型初始化"""
def __init__(self, input_size, hidden_size, output_size, learning_rate=0.01):
# 参数说明:
# input_size: 输入特征大小
# hidden_size: 隐藏层大小
# output_size: 输出层大小
# learning_rate: 学习率,默认值为0.01
self.input_size = input_size
self.hidden_size = hidden_size
self.output_size = output_size
self.learning_rate = learning_rate
# 初始化输入层到隐藏层的权重矩阵
self.W_xh = np.random.randn(input_size, hidden_size) * 0.01
# 初始化隐藏层到隐藏层的权重矩阵
self.W_hh = np.random.randn(hidden_size, hidden_size) * 0.01
# 初始化隐藏层到输出层的权重矩阵
self.W_hy = np.random.randn(hidden_size, output_size) * 0.01
# 初始化隐藏层偏置项
self.b_h = np.zeros((1, hidden_size))
# 初始化输出层偏置项
self.b_y = np.zeros((1, output_size))
"""Tanh激活函数"""
def tanh(self, x):
return np.tanh(x) # 隐藏层激活函数
"""Softmax激活函数"""
def softmax(self, x):
exp_x = np.exp(x – np.max(x, axis=1, keepdims=True)) # 防止溢出
return exp_x / np.sum(exp_x, axis=1, keepdims=True) # 输出层激活函数
"""前向传播"""
def forward(self, X):
# 获取输入序列长度、批次大小
seq_len, batch_size, _ = X.shape
# 初始化隐藏层状态
self.h = np.zeros((seq_len + 1, batch_size, self.hidden_size))
# 初始化输出层状态
self.y = np.zeros((seq_len, batch_size, self.output_size))
for t in range(seq_len):
# 计算隐藏层状态
# h_t = tanh(W_xh * x_t + W_hh * h_t-1 + b_h)
self.h[t + 1] = self.tanh(np.dot(X[t], self.W_xh) +
np.dot(self.h[t], self.W_hh) + self.b_h)
# 计算输出层状态
# y_t = softmax(W_hy * h_t + b_y)
self.y[t] = self.softmax(np.dot(self.h[t + 1], self.W_hy) + self.b_y)
return self.y
"""反向传播"""
def backward(self, X, y):
# 获取输入序列长度、批次大小
seq_len, batch_size, _ = X.shape
# 初始化隐藏层状态梯度
self.dh = np.zeros((seq_len + 2, batch_size, self.hidden_size)) # 增加一个位置用于最后一个隐藏状态的梯度
# 初始化输出层状态梯度
self.dy = np.zeros((seq_len, batch_size, self.output_size))
# 初始化输入层状态梯度
self.dx = np.zeros((seq_len, batch_size, self.input_size))
# 初始化隐藏层到输出层的权重矩阵梯度
self.dW_hy = np.zeros_like(self.W_hy)
# 初始化隐藏层到隐藏层的权重矩阵梯度
self.dW_hh = np.zeros_like(self.W_hh)
# 初始化输入层到隐藏层的权重矩阵梯度
self.dW_xh = np.zeros_like(self.W_xh)
# 初始化隐藏层偏置项梯度
self.db_h = np.zeros_like(self.b_h)
# 初始化输出层偏置项梯度
self.db_y = np.zeros_like(self.b_y)
# 从最后一个时间步开始反向传播(BPTT核心)
for t in reversed(range(seq_len)):
# 计算输出层状态梯度 (softmax+交叉熵的联合梯度)
self.dy[t] = self.y[t] – y[t]
# 计算隐藏层到输出层的权重矩阵梯度
self.dW_hy += np.dot(self.h[t + 1].T, self.dy[t])
# 计算输出层偏置项梯度
self.db_y += np.sum(self.dy[t], axis=0, keepdims=True)
# 计算当前输出误差引起的隐藏层梯度
dh_from_output = np.dot(self.dy[t], self.W_hy.T)
# 计算下一时间步的隐藏层梯度通过W_hh传递的部分
# 注意:t+1是当前隐藏层的索引,下一个隐藏层是t+2
dh_from_next = np.dot(self.dh[t + 2], self.W_hh.T) if (t + 1) < seq_len else 0
# 合并梯度并应用tanh的导数
self.dh[t + 1] = (dh_from_output + dh_from_next) * (1 – self.h[t + 1] ** 2)
# 计算隐藏层到隐藏层的权重矩阵梯度
self.dW_hh += np.dot(self.h[t].T, self.dh[t + 1])
# 计算输入层到隐藏层的权重矩阵梯度
self.dW_xh += np.dot(X[t].T, self.dh[t + 1])
# 计算隐藏层偏置项梯度
self.db_h += np.sum(self.dh[t + 1], axis=0, keepdims=True)
# 计算输入层状态梯度
self.dx[t] = np.dot(self.dh[t + 1], self.W_xh.T)
return self.dx
"""权重更新"""
def update_weights(self):
# 使用梯度下降法更新所有权重和偏置
self.W_xh -= self.learning_rate * self.dW_xh
self.W_hh -= self.learning_rate * self.dW_hh
self.W_hy -= self.learning_rate * self.dW_hy
self.b_h -= self.learning_rate * self.db_h
self.b_y -= self.learning_rate * self.db_y
# 重置梯度(可选,避免梯度累积)
self.dW_xh *= 0
self.dW_hh *= 0
self.dW_hy *= 0
self.db_h *= 0
self.db_y *= 0
"""模型训练"""
def train(self, X, y, epochs=100):
# 训练模型
for epoch in range(epochs):
# 前向传播
y_pred = self.forward(X)
# 反向传播
self.backward(X, y)
# 更新权重
self.update_weights()
# 计算损失
loss = -np.mean(np.sum(y * np.log(y_pred + 1e-8), axis=1))
# 打印损失
if epoch % 10 == 0:
print(f"Epoch {epoch}, Loss: {loss:.4f}")
"""预测模型"""
def predict(self, X):
y_pred = self.forward(X)
# 取最后一个时间步的输出,并返回概率最大的类别
return np.argmax(y_pred[-1], axis=1)
# 数据处理函数
def create_dataset():
"""创建简单的情感分析数据集"""
# 扩展训练数据集
data = {
'text':[
'这个产品很好', '质量太差了', '服务不错', '价格太贵',
'物流很快', '包装精美', '客服耐心', '商品有瑕疵',
'性价比高', '使用不方便', '颜色很好看', '尺寸不合适',
'材质不错', '味道不好闻', '价格合理', '物流太慢',
'包装破损', '客服态度好', '这个东西很好用', '质量非常差',
'服务态度很好', '价格真的贵', '物流速度很快', '包装很用心',
'客服非常耐心', '商品质量有问题', '性价比非常高', '使用起来不方便'
],
'label':[1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0]
}
# 转换为DataFrame
df = pd.DataFrame(data)
return df
# 文本预处理
def preprocess_text(text):
# 分词
seg_list = jb.cut(text)
# 合并分词结果
return " ".join(seg_list)
# 将文本转换为序列
def text_to_sequence(text, vocab, max_len=5):
words = text.split() # 按空格分词
sequence = [] # 初始化序列
for word in words[:max_len]: # 只取前max_len个单词
if word in vocab:
sequence.append(vocab[word]) # 获取词对应的ID
else:
sequence.append(0) # 未知词用0表示
# 填充序列到固定长度(不足的部分用0填充)
while len(sequence) < max_len:
sequence.append(0)
return sequence
# 主函数
def main():
# 1.数据准备
print("===数据准备===")
df = create_dataset()
print(f"数据集大小:{len(df)}")
# 2.文本预处理
print("\\n===文本预处理===")
df['processed'] = df['text'].apply(preprocess_text)
# 3.构建词汇表
print("\\n===构建词汇表===")
all_words = []
for text in df['processed']:
all_words.extend(text.split()) # 收集所有分词后的词
# 创建词汇表,<PAD>表示填充词
vocab = {'<PAD>': 0}
for word in set(all_words): # 去重并遍历所有词
if word not in vocab: # 如果词不在词汇表中
vocab[word] = len(vocab) # 加入词汇表
vocab_size = len(vocab)
print(f"词汇表大小:{vocab_size}")
# 4.转换为序列
print("\\n===序列转换===")
max_len = 5 # 最大序列长度
sequences = [] # 初始化序列列表
for text in df['processed']: # 遍历所有文本
seq = text_to_sequence(text, vocab, max_len) # 将文本转换为序列
sequences.append(seq) # 加入序列列表
# 转换为独热编码
X = np.zeros((len(sequences), max_len, vocab_size))
for i, seq in enumerate(sequences):
for j, word_index in enumerate(seq):
X[i, j, word_index] = 1
# 将标签转换为独热编码格式
y = df['label'].values
y_onehot = np.zeros((len(y), 2))
y_onehot[np.arange(len(y)), y] = 1
# 5.数据集分割
X_train, X_test, y_train, y_test = train_test_split(
X, y_onehot, test_size=0.25, random_state=42
)
# 调整维度为(seq_len, batch_size, input_size)
X_train = X_train.transpose(1, 0, 2)
X_test = X_test.transpose(1, 0, 2)
# 6.训练RNN模型
print("\\n===训练RNN模型===")
hidden_size = 32 # 隐藏层大小
output_size = 2 # 输出层大小(二分类)
model = SimpleRNN(vocab_size, hidden_size, output_size, learning_rate=0.01)
model.train(X_train, y_train, epochs=200)
# 7.模型评估
print("\\n===模型评估===")
y_pred = model.predict(X_test)
y_test_labels = np.argmax(y_test, axis=1)
accuracy = accuracy_score(y_test_labels, y_pred)
print(f"测试集准确率:{accuracy:.4f}")
# 8.预测新文本
print("\\n===预测新文本===")
new_texts = [
'这个商品很有价值',
'服务糟糕',
'物流很及时',
'包装很精美',
'客服很耐心'
]
for text in new_texts:
processed = preprocess_text(text) # 预处理
seq = text_to_sequence(processed, vocab, max_len) # 转换为序列
# 转换为独热编码
x = np.zeros((max_len, 1, vocab_size))
for j, word_id in enumerate(seq):
x[j, 0, word_id] = 1
# 预测
result = model.predict(x)[0]
sentiment = "正面" if result == 1 else "负面"
print(f"文本: {text}")
print(f"预测: {sentiment}")
print()
if __name__ == "__main__":
main()
9. 总结与展望
通过本文的完整实践,我们从零开始实现了一个基于RNN的中文情感分析系统。这个系统虽然简单,但涵盖了RNN的核心概念、数据预处理、模型训练和评估的完整流程。
未来发展方向包括:
-
模型优化:使用LSTM、GRU等高级RNN变体
-
词嵌入:采用Word2Vec、BERT等预训练词向量
-
多任务学习:同时进行情感分析和主题分类
-
实时系统:构建低延迟的在线情感分析服务
RNN作为序列建模的基础架构,虽然在某些任务上已被Transformer等新架构超越,但其核心思想仍然深刻影响了现代深度学习的发展。理解RNN的工作原理,对于掌握更复杂的序列模型具有重要意义。
网硕互联帮助中心



评论前必须登录!
注册