从零实现循环神经网络:中文情感分析的完整实践指南

1. 引言:循环神经网络在情感分析中的应用价值

在当今大数据时代,情感分析已成为自然语言处理(NLP)领域中最重要的应用之一。无论是电商平台的产品评论分析,还是社交媒体的舆情监控,准确识别用户情感倾向都具有巨大商业价值。然而,传统的机器学习方法在处理序列数据时面临着巨大挑战,特别是对于具有上下文依赖的文本数据。

循环神经网络(RNN)作为一种专门处理序列数据的神经网络架构,在情感分析任务中展现出独特优势。与传统方法不同,RNN能够捕捉文本中的时序信息和长期依赖关系,这对于理解自然语言的语义至关重要。例如,在中文情感分析中,"这个产品虽然贵但是质量很好"这样的句子,只有理解整个句子上下文才能准确判断其积极情感倾向。

本文将从零开始,详细讲解如何实现一个完整的RNN模型,并将其应用于中文情感分析任务。通过这个实践项目,您不仅将深入理解RNN的工作原理,还能掌握从数据处理到模型训练的完整流程。

2. 环境准备与理论基础

2.1. 核心库介绍

构建RNN模型需要多个Python库的支持,每个库都有其特定作用:

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特别适合处理序列数据,如文本、语音、时间序列等。

  1. 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模型的初始化涉及多个重要参数的设置:

python 复制代码
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. 激活函数实现

激活函数是神经网络的非线性来源,使网络能够学习复杂的模式:

python 复制代码
"""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的核心,负责计算网络输出:

python 复制代码
"""前向传播"""
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训练的关键:

python 复制代码
"""反向传播"""
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. 权重更新与训练

python 复制代码
"""权重更新"""
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. 数据集创建

python 复制代码
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. 文本预处理

预处理步骤直接影响模型性能:

python 复制代码
# 文本预处理
def preprocess_text(text):
    # 分词
    seg_list = jb.cut(text)
    # 合并分词结果
    return " ".join(seg_list)

中文分词的重要性,中文没有自然的分隔符,分词是将连续字符转换为有意义的词语的关键步骤。准确的分词有助于模型理解语义。

4.3. 序列化与编码

将文本转换为数值表示是NLP任务的核心:

python 复制代码
# 将文本转换为序列
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. 完整实验流程

主函数整合了从数据准备到模型评估的完整流程:

python 复制代码
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. 训练过程观察

在训练过程中,损失函数应呈现下降趋势。如果损失波动较大或下降缓慢,可能需要调整学习率或检查数据预处理过程。

  1. 准确率评估

模型在测试集上的准确率反映了其泛化能力。对于二分类问题,准确率超过随机猜测(50%)即表示模型学到了一些模式。

6. 模型优化与改进方向

6.1. 当前实现局限性

当前的SimpleRNN实现存在一些局限性:

  • 梯度消失/爆炸:在处理长序列时容易出现

  • 计算效率:One-hot编码导致高维稀疏问题

  • 模型容量:简单RNN难以捕捉复杂模式

6.2. 改进方案

  1. 使用高级RNN变体
  • LSTM:通过门控机制解决长依赖问题

  • GRU:简化版LSTM,计算效率更高

  • 双向RNN:同时考虑前后文信息

  1. 词嵌入技术

用词向量(Word2Vec, GloVe, FastText)代替One-hot编码:

  • 降低维度

  • 捕捉语义相似性

  • 处理未登录词

  1. 注意力机制

引入注意力机制使模型能够关注输入序列中的重要部分:

  • 提高模型解释性

  • 改善长序列处理能力

  1. 数据增强
  • 同义词替换

  • 回译技术

  • 随机插入/删除

7. 实际应用建议

7.1. 部署注意事项

  • 性能优化:考虑使用批量推理和模型量化

  • 实时性要求:优化预测延迟,支持高并发

  • 模型更新:建立定期更新机制,适应语言变化

7.2. 领域适应

当将模型应用于特定领域时:

  • 领域词典:构建领域专业词汇表

  • 迁移学习:使用预训练模型进行微调

  • 领域数据:收集领域特定训练数据

8. 完整代码示例

以下是完整的RNN中文情感分析实现代码:

python 复制代码
# 关键点说明:
# 这是一个简单的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的工作原理,对于掌握更复杂的序列模型具有重要意义。

相关推荐
Master_oid2 小时前
机器学习30:神经网络压缩(Network Compression)①
人工智能·神经网络·机器学习
xinyuan_1234562 小时前
不止于提速:德州数智招标采购交易平台,重塑采购生态新效率
大数据·人工智能
沃达德软件2 小时前
智能车辆检索系统解析
人工智能·深度学习·神经网络·目标检测·机器学习·计算机视觉·目标跟踪
java1234_小锋2 小时前
【专辑】AI大模型应用开发入门-拥抱Hugging Face与Transformers生态 - 使用datasets库加载Huggingface数据集
人工智能·深度学习
喵手2 小时前
Python爬虫零基础入门【第九章:实战项目教学·第1节】通用新闻采集器:从零打造可复用的静态站模板!
爬虫·python·爬虫实战·python爬虫工程化实战·零基础python爬虫教学·新闻采集器·静态站模版
kkk_皮蛋2 小时前
作为一个学生,如何用免费 AI 工具手搓了一款 Android AI 日记 App
android·人工智能
摸鱼仙人~2 小时前
从 Gunicorn 到 FastAPI:Python Web 生产环境架构演进与实战指南
python·fastapi·gunicorn
TTGGGFF2 小时前
从零到一:五分钟快速部署轻量化 AI 知识库模型(GTE + SeqGPT)
人工智能
凤希AI伴侣2 小时前
凤希AI积分系统上线与未来工作模式畅想-2026年1月25日
人工智能·凤希ai伴侣