云计算百科
云计算领域专业知识百科平台

LSTM网络实战:实现微博评论情感分类

目录

一、任务明确

1. 任务目标

2. 核心要求

二、核心思路

三、流程设计

阶段1:原始数据处理(核心:字符→索引→批次张量,适配模型输入)

阶段2:LSTM模型构建(核心:词向量→时序特征→情感分类)

阶段3:训练与测试(核心:优化模型参数,评估模型效果)

四、代码实现

模块1:词表构建(build_vocab.py)

模块2:数据加载与迭代器(load_dataset.py)

模块3:LSTM模型构建(TextRNN.py)

模块4:训练与测试(train_eval_test.py)

模块5:主程序(main.py)

模块6:单个句子预测(predict.py)

五、关键修正与补充说明

1. 原始流程核心修正

六、总结与进阶方向

1. 实战总结


继上一篇学习了循环神经网络(RNN)及改进版长短期记忆网络(LSTM)的核心原理后,本文将从实战角度出发,基于LSTM网络完成微博评论的情感分类任务。整个过程涵盖数据处理、模型搭建、训练测试整个流程,包含完整代码解析。

一、任务明确

1. 任务目标

基于微博4类情绪数据集(simplifyweibo_4_moods.csv),搭建LSTM模型实现微博评论的情感分类,具体将评论分为4类情感标签,贴合真实微博场景:喜悦、愤怒、厌恶、低落。部分原始数据展示:

2. 核心要求

  • 数据层面:采用字符级处理方式,统一所有句子长度为70(多余部分截断、不足部分填充),构建字符级词表并过滤低频词,避免词表冗余。

  • 模型层面:使用腾讯预训练词向量初始化Embedding层并开启微调,将字符索引转化为200维词向量,传入双向LSTM提取文本时序特征,最终通过全连接层完成4类情感分类。

  • 评估层面:通过准确率(Accuracy)、分类报告(精确率、召回率、F1值)全面评估模型效果,加入早停策略(Early Stopping)避免模型过拟合,提升泛化能力。

  • 实操层面:代码模块化拆分(词表构建、数据加载、模型定义、训练测试),结构清晰,可直接复制复用,补充运行说明和常见问题解决方案,降低新手实操门槛。

二、核心思路

文本情感分类的核心是「将文本转化为可被模型识别的数值特征,再通过深度学习模型捕捉文本中的情感倾向」,结合LSTM的时序特征优势,本次实战核心思路分为3步,逻辑连贯且贴合LSTM特性:

  • 数据预处理:先构建字符级词表(过滤低频词、补充未知/填充字符),再将原始文本转化为词表索引,统一句子长度后拆分训练集、验证集、测试集(8:1:1),最后构建数据迭代器,将数据转为张量并按批次加载,适配模型输入格式。

  • 模型设计:采用「Embedding→双向LSTM→全连接」的经典文本分类架构——Embedding层解决文本向量化问题(复用预训练词向量提升效果),双向LSTM捕捉文本上下文双向时序特征(贴合中文句子前后文关联的特点),全连接层将LSTM输出的特征向量映射为4类情感的预测概率。

  • 训练与测试:使用Adam优化器、交叉熵损失函数训练模型,每100批次监控训练集和验证集效果,保存验证集损失最优的模型;训练结束后在测试集上评估模型性能,输出分类报告,同时加入训练可视化,方便监控训练过程中的损失和准确率变化。

  • 三、流程设计

    阶段1:原始数据处理(核心:字符→索引→批次张量,适配模型输入)

    数据处理是实战的基础,直接影响模型效果,核心目标是「将非结构化的文本数据,转化为结构化的数值张量」,具体分为3步:

  • 构建词表:

  • 采用字符级分词(逐个拆分中文汉字/符号),统计所有文本中每个字符的出现频率;

  • 过滤低频字符(低于最小词频的字符删除),设置词表最大容量为4762,避免词表过大导致模型冗余。这里4762是因为我么使用的腾讯预训练词向量矩阵是4762*200,我们还需要留两个词,分别表示空缺值和未知值。因为我们要保证输入到lstm网络的数据大小是统一的,这里我们设定为70个字,即每次像一个lstm网络中传入70个字,当一条评论不足70个字时,我们就用表示空缺的值<pad>来代替;因为我们腾讯的磁向量矩阵输入只有4762,也就是说在文本中我们只能使用出现频次最高的前4760个字,其他频率低的词我们使用未知值来代替,设定为<unk>。

  • 补充2个特殊字符:<unk>(未知字符,对应词表中未收录的字符)、<pad>(填充字符,用于统一句子长度);

  • 将字符映射为唯一索引(从0开始),保存词表到本地(vocab.pkl),供后续数据处理和预测时复用(仅需构建一次)。

  • 数据格式化:

  • 读取原始数据集,提取每条评论的「情感标签」(首字符,0-3分别对应4类情感)和「文本内容」(去掉标签后的评论内容);

  • 统一句子长度为70:句子长度超过70时,截断多余部分;不足70时,用<pad>字符填充至70;

  • 将文本中的每个字符,通过词表映射为对应的索引(未知字符映射为<unk>的索引),完成「文本→索引」的转化。

  • 数据打包与拆分:

  • 将处理后的「字符索引、情感标签、句子原始长度」打包为元组,随机打乱数据顺序(避免模型学习到数据顺序的冗余特征);

  • 按8:1:1的比例拆分训练集(用于模型训练)、验证集(用于监控模型效果、调整参数)、测试集(用于最终评估模型泛化能力);

  • 构建数据迭代器(DatasetIterater),将数据按批次(batch_size=128)转为PyTorch张量,并加载到指定设备(CPU/GPU),减少数据加载的时间开销,提升训练效率。

  • 阶段2:LSTM模型构建(核心:词向量→时序特征→情感分类)

    模型构建贴合LSTM的时序优势,适配文本分类场景,重点优化Embedding层和LSTM层的设计,具体结构分为3层,层层递进:

  • Embedding层(词向量层):

  • 用腾讯预训练200维词向量初始化,避免随机初始化导致的词向量语义偏差;

  • 设置padding_idx为词表最后一位(即<pad>的索引),让模型忽略填充字符的影响;

  • 开启微调(freeze=False),让预训练词向量在训练过程中适配当前微博情感分类任务,进一步提升模型效果;

  • 核心作用:将字符索引(如[12,34,56,…])转化为200维词向量,解决「文本无法直接被模型识别」的问题(这里更详细的解释是,每一个字对应一个索引张量,索引张量在embedding层中会自动转化为4762维度的独热编码,然后经过embedding层4762*200的矩阵转化为200维度的词向量)。

  • LSTM层(时序特征提取层):

  • 采用3层双向LSTM(BiLSTM),隐藏层维度为128,dropout=0.3(防止过拟合),这里dropout的防止过拟合策略是所有模型通用的,在其他网络如CNN同样适用。原理是模仿人脑的神经元结构,因为人脑也不是所有神经元都被同时使用的,这里dropout=0.3表示令百分之三十的神经元参数w为0。

  • 双向LSTM的优势:相比单向LSTM,能同时捕捉文本的正向(从左到右)和反向(从右到左)上下文信息,比如句子「这部电影开头很平淡,但结尾却非常精彩」,正向LSTM捕捉开头的平淡描述,反向LSTM捕捉结尾的精彩评价,两者融合能更精准判断情感倾向,更适合情感分类任务;

  • 核心作用:提取文本的时序特征,捕捉句子中字符之间的语义关联(如「开心」「愉快」等词的情感关联),输出维度为[batch_size, 70, 256](128×2,双向LSTM的正向和反向隐藏层输出拼接)。

  • 全连接层(分类层):

  • 输入维度为256(双向LSTM最后一步的输出,即[batch_size, 256]),输出维度为4(对应4类情感);

  • 核心作用:将LSTM提取的时序特征,映射为4类情感的预测概率,完成最终的分类任务。

  • 阶段3:训练与测试(核心:优化模型参数,评估模型效果)

    训练与测试的核心是「让模型学习到文本与情感的关联,同时避免过拟合,确保模型在新数据上的泛化能力」,具体分为2步,加入实用优化策略:

  • 训练环节:

  • 优化器:选用Adam优化器(学习率0.001),收敛速度快、适合深度学习任务,相比SGD能更好地避免局部最优;

  • 损失函数:选用交叉熵损失函数(F.cross_entropy),适配多分类任务,能有效衡量预测概率与真实标签之间的差距;

  • 监控策略:每100批次输出一次训练集损失、训练集准确率,以及验证集损失、验证集准确率,直观监控模型训练效果;

  • 早停策略:若连续10000批次验证集损失无提升,自动停止训练,避免模型过度训练导致过拟合;

  • 模型保存:保存验证集损失最优的模型(TextRNN.ckpt),后续测试和预测直接加载该模型,无需重新训练。

  • 训练可视化:加入TensorBoardX可视化工具,监控训练过程中的损失和准确率变化,解决「盲训」问题,直观判断模型收敛情况。

  • 测试环节:

  • 加载训练好的最优模型,切换为评估模式(model.eval()),关闭dropout和梯度计算,节省内存;

  • 在测试集上运行模型,计算测试集损失、测试集准确率;

  • 输出分类报告,包含4类情感的精确率、召回率、F1值,全面评估模型在每类情感上的分类效果(比如模型对「喜悦」类评论的识别准确率,对「低落」类评论的召回率等)。

  • 四、代码实现

    代码按「功能模块化」拆分,每个模块单独一个文件,结构清晰、便于维护和复用,同时添加详细注释,新手可逐模块理解,也可直接复制到对应文件中运行。所有代码基于Python 3.7+、PyTorch 1.10+编写,确保兼容性。

    模块1:词表构建(build_vocab.py)

    核心功能:构建字符级词表,过滤低频词,保存词表到本地(仅需运行一次)。

    from tqdm import tqdm
    import pickle as pkl # 用于保存/加载词表

    max_vocab_size = 4760 # 词表最大容量
    unk, pad = '<unk>', '<pad>' # 未知字符/填充字符,与后续数据处理保持一致

    def build_vocab(file_path, max_size, min_freq):
    """
    构建字符级词表:字符分词+频率统计+低频过滤+保存词表
    :param file_path: 数据集路径(simplifyweibo_4_moods.csv)
    :param max_size: 词表最大容量
    :param min_freq: 最小词频(低于该值的字符将被过滤)
    :return: 构建好的词表(字典:字符→索引)
    """
    # 字符级分词器:逐个拆分中文汉字/符号(适配中文微博评论场景)
    tokenizer = lambda x: [y for y in x]
    vocab_dict = {} # 用于统计字符频率(key:字符,value:频率)

    # 读取数据集,统计字符频率
    with open(file_path, 'r', encoding='utf-8') as f:
    i = 0
    for line in tqdm(f, desc="正在构建词表"):
    if i == 0: # 跳过数据集表头(第一行)
    i += 1
    continue
    lin = line[2:].strip() # 去掉标签(首字符),提取文本内容
    if not lin: # 跳过空行(避免无效数据)
    continue
    # 统计每个字符的频率
    for word in tokenizer(lin):
    vocab_dict[word] = vocab_dict.get(word, 0) + 1

    # 过滤低频词,并按频率降序排序(保留高频字符)
    vocab_list = sorted([_ for _ in vocab_dict.items() if _[1] >= min_freq],
    key=lambda x: x[1], reverse=True)[:max_size]
    # 将字符映射为索引(从0开始)
    vocab_dict = {word_count[0]: idx for idx, word_count in enumerate(vocab_list)}
    # 补充未知字符(unk)和填充字符(pad)的索引(放在词表最后)
    vocab_dict[unk] = len(vocab_dict)
    vocab_dict[pad] = len(vocab_dict)

    # 保存词表到本地(后续数据处理、预测时直接加载,无需重新构建)
    pkl.dump(vocab_dict, open('vocab.pkl', 'wb'))
    print(f"词表构建完成!词表总大小:{len(vocab_dict)}(含unk和pad)") # 预期大小:4762(4760+2)
    print(f"词表示例:{list(vocab_dict.items())}")
    return vocab_dict

    # 调用示例:运行该函数构建词表(仅需运行一次)
    # 请根据实际数据集路径修改file_path,min_freq建议设为5(过滤极低频字符)
    build_vocab("simplifyweibo_4_moods.csv", max_vocab_size, min_freq=5)

    运行结果:

    模块2:数据加载与迭代器(load_dataset.py)

    核心功能:加载词表,处理原始数据(统一长度、字符转索引),拆分数据集,构建数据迭代器,适配模型输入。

    from tqdm import tqdm
    import pickle as pkl
    import random
    import torch

    unk, pad = '<unk>', '<pad>' # 与词表构建时保持一致

    def load_dataset(path, pad_size=70):
    """
    处理原始数据:加载词表→字符转索引→统一句子长度→拆分数据集
    :param path: 数据集路径(simplifyweibo_4_moods.csv)
    :param pad_size: 句子统一长度(默认70,与任务要求一致)
    :return: 词表、训练集、验证集、测试集
    """
    contents = [] # 用于存储处理后的数据(每个元素:(字符索引列表, 标签, 原始句子长度))
    vocab = pkl.load(open('vocab.pkl', 'rb')) # 加载已构建好的词表
    tokenizer = lambda x: [y for y in x] # 字符级分词器(与词表构建一致)

    # 读取并处理原始数据
    with open(path, 'r', encoding='utf-8') as f:
    i = 0
    for line in tqdm(f, desc="正在处理数据"):
    if i == 0: # 跳过表头
    i += 1
    continue
    if not line: # 跳过空行
    continue
    # 提取标签(首字符,转为整数)和文本内容(去掉标签和换行符)
    label = int(line[0])
    content = line[2:].strip("\\n")

    # 字符分词→统一句子长度→字符转索引
    token = tokenizer(content) # 分词(逐个字符)
    seq_len = len(token) # 记录句子原始长度(备用)
    # 统一长度:截断或填充
    if seq_len >= pad_size:
    token = token[:pad_size] # 超过70,截断多余部分
    seq_len = pad_size
    else:
    token.extend([pad] * (pad_size – len(token))) # 不足70,填充pad

    # 将字符映射为索引(未知字符用unk的索引)
    words_line = [vocab.get(word, vocab[unk]) for word in token]
    contents.append((words_line, label, seq_len))

    # 随机打乱数据(设置随机种子可保证结果可复现)
    random.shuffle(contents)
    # 按8:1:1拆分训练集、验证集、测试集
    train_data = contents[:int(len(contents)*0.8)]
    dev_data = contents[int(len(contents)*0.8):int(len(contents)*0.9)]
    test_data = contents[int(len(contents)*0.9):]
    return vocab, train_data, dev_data, test_data

    class DatasetIterater(object):
    """数据迭代器:将处理好的数据转为PyTorch张量,并按批次返回,适配模型训练/测试"""
    def __init__(self, batches, batch_size, device):
    self.batch_size = batch_size # 批次大小(与main.py中一致)
    self.batches = batches # 输入数据(训练/验证/测试集)
    self.n_batches = len(batches) // batch_size # 总批次数量(整数部分)
    self.residue = len(batches) % self.n_batches != 0 # 是否有剩余数据(不足一个批次)
    self.index = 0 # 当前批次索引
    self.device = device # 数据加载到的设备(CPU/GPU)

    def _to_tensor(self, datas):
    """将单个批次的数据转为PyTorch张量,适配模型输入格式"""
    # x:字符索引张量([batch_size, pad_size])
    x = torch.LongTensor([_[0] for _ in datas]).to(self.device)
    # y:标签张量([batch_size])
    y = torch.LongTensor([_[1] for _ in datas]).to(self.device)
    # seq_len:句子原始长度张量(备用,模型未用到,仅保持输入格式统一)
    seq_len = torch.LongTensor([_[2] for _ in datas]).to(self.device)
    return (x, seq_len), y

    def __next__(self):
    """迭代器:返回下一个批次的数据,结束时抛出StopIteration"""
    if self.residue and self.index == self.n_batches:
    # 处理剩余数据(不足一个批次)
    batches = self.batches[self.index*self.batch_size:]
    self.index += 1
    return self._to_tensor(batches)
    elif self.index >= self.n_batches:
    # 所有批次迭代完成,重置索引并抛出停止信号
    self.index = 0
    raise StopIteration
    else:
    # 返回当前批次的数据,索引加1
    batches = self.batches[self.index*self.batch_size:(self.index+1)*self.batch_size]
    self.index += 1
    return self._to_tensor(batches)

    def __iter__(self):
    """返回迭代器自身(适配for循环迭代)"""
    return self

    def __len__(self):
    """返回总批次数量(含剩余批次)"""
    return self.n_batches + 1 if self.residue else self.n_batches

    模块3:LSTM模型构建(TextRNN.py)

    核心功能:定义LSTM模型结构,适配Embedding层、双向LSTM层、全连接层,贴合任务需求和数据格式。

    import torch.nn as nn

    class Model(nn.Module):
    def __init__(self, embedding_pretrained, n_vocab, embed, num_classes):
    """
    初始化LSTM模型
    :param embedding_pretrained: 预训练词向量(None表示随机初始化)
    :param n_vocab: 词表大小
    :param embed: 词向量维度(预训练词向量为200维)
    :param num_classes: 类别数(4类情感)
    """
    super(Model, self).__init__()
    # 1. Embedding层(词向量层)
    if embedding_pretrained is not None:
    # 用预训练词向量初始化,开启微调(freeze=False)
    self.embedding = nn.Embedding.from_pretrained(
    embedding_pretrained,
    padding_idx=n_vocab-1, # pad的索引为词表最后一位
    freeze=False
    )
    else:
    # 无预训练词向量时,随机初始化词向量
    self.embedding = nn.Embedding(n_vocab, embed, padding_idx=n_vocab-1)

    # 2. 双向LSTM层(时序特征提取)
    self.lstm = nn.LSTM(
    embed, 128, 3, # 输入维度(词向量维度)、隐藏层维度、层数
    bidirectional=True, # 开启双向
    batch_first=True, # 输入格式:[batch_size, seq_len, embed_dim]
    dropout=0.3 # dropout防止过拟合(仅中间层生效)
    )

    # 3. 全连接层(分类层):双向LSTM输出维度=128×2=256
    self.fc = nn.Linear(128*2, num_classes)

    def forward(self, x):
    """
    模型前向传播(输入→Embedding→LSTM→全连接→输出)
    :param x: 输入数据(元组:(字符索引张量, 句子长度张量))
    :return: 模型预测概率([batch_size, num_classes])
    """
    # x是元组(x_idx, seq_len),仅使用字符索引部分(seq_len备用)
    x, _ = x
    # Embedding层:[batch_size, 70] → [batch_size, 70, 200]
    out = self.embedding(x)
    # LSTM层:输出[batch_size, 70, 256],取最后一步输出[batch_size, 256]
    out, _ = self.lstm(out)
    out = out[:, -1, :] # 取最后一步输出,作为文本的整体特征
    # 全连接层:[batch_size, 256] → [batch_size, 4](4类情感的预测概率)
    out = self.fc(out)
    return out

    模块4:训练与测试(train_eval_test.py)

    核心功能:定义模型训练、评估、测试函数,加入早停策略、训练可视化,输出训练日志和测试报告。

    import torch
    import torch.nn.functional as F
    from sklearn import metrics
    import numpy as np
    import time
    from tensorboardX import SummaryWriter # 导入训练可视化工具

    # 初始化TensorBoardX日志写入器(日志保存在runs目录)
    writer = SummaryWriter('runs/lstm_weibo_sentiment')

    def train(model, train_iter, dev_iter, test_iter, class_list):
    """模型训练:含早停策略、模型保存、训练可视化"""
    model.train() # 切换为训练模式(开启dropout)
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # Adam优化器
    total_batch = 0 # 总批次计数器
    dev_best_loss = float('inf') # 验证集最优损失(初始设为无穷大)
    last_improve = 0 # 最后一次验证集损失提升的批次
    flag = False # 是否触发早停
    epochs = 20 # 总训练轮次

    for epoch in range(epochs):
    print(f"\\nEpoch [{epoch + 1}/{epochs}]")
    epoch_start_time = time.time()
    for i, (trains, labels) in enumerate(train_iter):
    # 前向传播:输入数据→模型输出→计算损失
    outputs = model(trains)
    loss = F.cross_entropy(outputs, labels)
    # 反向传播:清空梯度→计算梯度→更新参数
    model.zero_grad()
    loss.backward()
    optimizer.step()

    # 每100批次监控训练集/验证集效果,并写入TensorBoardX
    if total_batch % 100 == 0:
    # 计算训练集准确率
    predic = torch.max(outputs.data, 1)[1].cpu()
    train_acc = metrics.accuracy_score(labels.data.cpu(), predic)
    # 计算验证集准确率和损失(关闭梯度计算,节省内存)
    dev_acc, dev_loss = evaluate(class_list, model, dev_iter)

    # 保存验证集损失最优的模型
    if dev_loss < dev_best_loss:
    dev_best_loss = dev_loss
    torch.save(model.state_dict(), 'TextRNN.ckpt')
    last_improve = total_batch

    # 打印监控信息
    msg = "Iter: {0:>6}, Train Loss: {1:>5.2}, Train Acc: {2:>6.2%}, Val Loss: {3:>5.2}, Val Acc: {4:>6.2%}"
    print(msg.format(total_batch, loss.item(), train_acc, dev_loss, dev_acc))

    # 写入TensorBoardX(监控loss和accuracy)
    writer.add_scalar('train/loss', loss.item(), total_batch)
    writer.add_scalar('train/accuracy', train_acc, total_batch)
    writer.add_scalar('val/loss', dev_loss, total_batch)
    writer.add_scalar('val/accuracy', dev_acc, total_batch)

    model.train() # 回到训练模式(评估模式后切换回来)

    total_batch += 1
    # 早停策略:连续10000批次验证集损失无提升,自动停止训练
    if total_batch – last_improve > 10000:
    print("长时间无优化,自动停止训练…")
    flag = True
    break
    # 打印本轮训练耗时
    print(f"Epoch [{epoch+1}/{epochs}] 耗时:{time.time() – epoch_start_time:.2f}秒")
    if flag:
    break
    # 训练结束后,在测试集上评估模型效果
    test(model, test_iter, class_list)
    # 关闭TensorBoardX日志写入器
    writer.close()

    def evaluate(class_list, model, data_iter, test=False):
    """评估函数:计算准确率/损失,测试时返回分类报告"""
    model.eval() # 切换为评估模式(关闭dropout、批量归一化)
    loss_total = 0 # 总损失
    predict_all = np.array([], dtype=int) # 所有预测标签
    labels_all = np.array([], dtype=int) # 所有真实标签

    with torch.no_grad(): # 关闭梯度计算,节省内存,加快评估速度
    for texts, labels in data_iter:
    outputs = model(texts)
    loss = F.cross_entropy(outputs, labels)
    loss_total += loss

    # 收集预测结果和真实标签(转为numpy数组,便于计算评估指标)
    labels = labels.data.cpu().numpy()
    predic = torch.max(outputs.data, 1)[1].cpu().numpy()
    labels_all = np.append(labels_all, labels)
    predict_all = np.append(predict_all, predic)

    # 计算准确率(所有类别的整体准确率)
    acc = metrics.accuracy_score(labels_all, predict_all)
    if test:
    # 测试时,返回准确率、平均损失、分类报告(精确率、召回率、F1值)
    report = metrics.classification_report(
    labels_all, predict_all,
    target_names=class_list,
    digits=4 # 保留4位小数,提升精度
    )
    return acc, loss_total / len(data_iter), report
    # 验证时,仅返回准确率和平均损失
    return acc, loss_total / len(data_iter)

    def test(model, test_iter, class_list):
    """测试集评估:加载最优模型,输出测试结果和分类报告"""
    # 加载训练好的最优模型参数
    model.load_state_dict(torch.load('TextRNN.ckpt'))
    model.eval() # 切换为评估模式
    start_time = time.time()
    # 计算测试集准确率、平均损失、分类报告
    test_acc, test_loss, test_report = evaluate(class_list, model, test_iter, test=True)
    # 打印测试结果
    print("\\n" + "="*50)
    print("测试集评估结果:")
    msg = 'Test Loss: {0:>5.2}, Test Acc: {1:>6.2%}'
    print(msg.format(test_loss, test_acc))
    print("\\n分类报告(精确率/召回率/F1值):")
    print(test_report)
    print(f"测试耗时:{time.time() – start_time:.2f}秒")
    print("="*50)

    模块5:主程序(main.py)

    核心功能:整合所有模块,加载数据、初始化模型、启动训练,是整个实战的入口文件,新手只需修改少量参数即可运行。

    import torch
    import numpy as np
    import load_dataset # 导入数据加载模块
    import TextRNN # 导入模型模块
    from train_eval_test import train # 导入训练函数

    # 固定随机种子(保证模型训练结果可复现)
    np.random.seed(1)
    torch.manual_seed(1)
    torch.cuda.manual_seed_all(1)
    torch.backends.cudnn.deterministic = True

    # 设备选择:优先使用GPU(训练速度更快),无GPU则使用CPU
    device = "cuda" if torch.cuda.is_available() else "cpu"
    print(f"当前使用设备:{device}")

    # 1. 加载并处理数据(数据集路径请根据实际情况修改)
    vocab, train_data, dev_data, test_data = load_dataset.load_dataset("simplifyweibo_4_moods.csv")
    # 构建数据迭代器(batch_size=128,可根据GPU显存调整)
    train_iter = load_dataset.DatasetIterater(train_data, batch_size=128, device=device)
    dev_iter = load_dataset.DatasetIterater(dev_data, batch_size=128, device=device)
    test_iter = load_dataset.DatasetIterater(test_data, batch_size=128, device=device)

    # 2. 加载预训练词向量(腾讯词向量,200维)
    # 若没有预训练词向量,设为embedding_pretrained = None(随机初始化)
    try:
    embedding_pretrained = torch.tensor(np.load("embedding_Tencent.npz")["embeddings"].astype('float32'))
    except FileNotFoundError:
    raise Exception("未找到预训练词向量文件embedding_Tencent.npz,请确认文件路径正确!")

    # 3. 模型参数配置(与训练时保持一致)
    embed = embedding_pretrained.size(1) # 词向量维度(200维)
    class_list = ["喜悦", "愤怒", "厌恶", "低落"] # 情感类别列表(与标签0-3一一对应)
    num_classes = len(class_list) # 类别数(4)
    n_vocab = len(vocab) # 词表大小(4762)

    # 4. 初始化模型,并加载到指定设备(CPU/GPU)
    model = TextRNN.Model(
    embedding_pretrained,
    n_vocab,
    embed,
    num_classes
    ).to(device)

    # 5. 启动模型训练(训练完成后自动进行测试)
    print("\\n" + "="*50)
    print("开始模型训练…")
    train(model, train_iter, dev_iter, test_iter, class_list)

    模块6:单个句子预测(predict.py)

    核心功能:训练完成后,预测单个微博评论的情感,适配实战场景,输入句子即可得到预测结果。

    import torch
    import pickle as pkl

    # 全局配置(与训练时保持一致)
    unk, pad, PAD_SIZE = '<unk>', '<pad>', 70

    def predict(model, sentence, class_list):
    """
    预测单个句子的情感类别
    :param model: 训练好的LSTM模型
    :param sentence: 待预测的微博评论(字符串)
    :param class_list: 情感类别列表
    :return: 预测结果字典(含输入句子、预测类别、各类别概率)
    """
    model.eval() # 切换为评估模式
    # 加载词表
    try:
    vocab = pkl.load(open('vocab.pkl', 'rb'))
    except FileNotFoundError:
    raise Exception("未找到vocab.pkl文件,请确保该文件在当前目录下!")

    # 数据预处理(与训练时完全一致)
    token = [y for y in sentence][:PAD_SIZE] if len(sentence)>=PAD_SIZE else [y for y in sentence] + [pad]*(PAD_SIZE-len(sentence))
    idx = [vocab.get(w, vocab[unk]) for w in token]

    # 构建模型输入(适配模型输入格式)
    x = torch.LongTensor([idx]).to(next(model.parameters()).device)
    with torch.no_grad():
    out = model((x, None))
    pred_label = class_list[out.argmax(1).item()]
    probs = torch.softmax(out, 1).cpu().numpy()[0].round(4)

    # 返回预测结果
    return {
    "输入句子": sentence,
    "预测情感类别": pred_label,
    "各类别概率": dict(zip(class_list, probs))
    }

    # 调用示例(需先加载训练好的模型)
    if __name__ == "__main__":
    # 初始化模型(参数与训练时一致)
    embedding_pretrained = torch.tensor(np.load("embedding_Tencent.npz")["embeddings"].astype('float32'))
    vocab = pkl.load(open('vocab.pkl', 'rb'))
    model = TextRNN.Model(embedding_pretrained, len(vocab), 200, 4)
    # 加载训练好的模型参数
    model.load_state_dict(torch.load('TextRNN.ckpt', map_location=torch.device('cpu')))
    # 类别列表
    class_list = ["喜悦", "愤怒", "厌恶", "低落"]

    # 持续预测(输入q结束)
    print("===== 微博评论情感预测(输入q结束)=====")
    while True:
    s = input("请输入待预测的微博评论:").strip()
    if s.lower() == 'q':
    print("预测结束!")
    break
    if not s:
    print("输入不能为空,请重新输入!")
    continue
    # 执行预测并打印结果
    result = predict(model, s, class_list)
    print(f"\\n预测结果:{result['预测情感类别']}")
    print("各类别概率:", result['各类别概率'])
    print("-"*50)

    运行结果:

    五、关键修正与补充说明

    1. 原始流程核心修正

    • 修正「4762的独热编码」表述:实际是「字符索引→词向量」的转化,词表大小为4762(4760个高频字符+unk+pad),独热编码会导致维度爆炸(4762维),不适用于该任务,Embedding层可将其压缩为200维词向量,更适合模型训练。

    • 补充「早停策略」「训练可视化」:原始流程未提及过拟合解决方案和训练监控方法,补充后可避免模型过度训练,同时直观监控训练过程,解决「盲训」问题。

    • 明确「打包」含义:原始流程中「打包」表述模糊,修正为「构建数据迭代器,将处理好的数据转为张量并按批次加载」,适配模型的批量训练需求,提升训练效率。

    • 补充「单个句子预测功能」:原始流程仅包含训练测试,补充预测函数,实现实战落地,输入任意微博评论即可得到情感预测结果。

    六、总结与进阶方向

    1. 实战总结

    本次实战基于LSTM网络完成了微博评论情感分类任务,覆盖了文本分类的完整流程,核心要点如下:

    • 数据处理是基础:字符级处理适配中文微博场景,统一句子长度、构建词表并过滤低频词,能有效减少模型冗余,提升训练效率和模型效果;

    • 模型设计要贴合任务:双向LSTM能捕捉文本上下文双向特征,比单向LSTM更适合情感分类;预训练词向量的使用的能有效提升模型的语义理解能力,开启微调后能更好适配当前任务;

    • 训练策略是关键:早停策略避免过拟合,Adam优化器加快收敛,训练可视化能直观监控模型训练过程,这些技巧在实战中非常实用;

    • 模块化代码更易维护:将词表构建、数据加载、模型定义、训练测试拆分到不同模块,结构清晰,便于后续修改和复用。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » LSTM网络实战:实现微博评论情感分类
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!