循环网络RNN--评论内容情感分析

一、构建字表

基于微博语料库构建中文字表,通过统计字频筛选有效字符,为每个字符分配唯一索引,并加入未知字符<UNK>和填充字符<PAD>,最终将词表保存为 pickle 文件

代码:

python 复制代码
from tqdm import tqdm
import pickle as pkl

MAX_VOCAB_SIZE=4760
UNK,PAD='<UNK>','<PAD>'

def build_vocab(file_path,max_size,min_frep):
    '''功能:基于文本内容建立词表vocab,vocab中包含语料库中的字
    参数file_path:需要读取的语料库的路径
    max_size:获取词频最高的前max_size个词
    min_freq:剔除字频低于min_freq个的词 '''
    tokenizer = lambda x: [y for y in x]#  简单的函数定义了一个函数tokenizer,功能为分字
    vocab_dic = {}
    with open(file_path, 'r',encoding='UTF-8') as f:
        i = 0
        for line in tqdm(f):# 用来显示循环的进度条
            if i == 0:
            # 跳过文件种第1行表头内容
                i += 1
                continue
            lin = line[2:].strip()  # 获取评论内容,剔除标签。不用split分割,因为评论内容中可能会存在逗号。
            if not lin:
                continue
            for word in tokenizer(lin):
                vocab_dic[word] = vocab_dic.get(word,0)+1
        vocab_list = sorted([_ for _ in vocab_dic.items() if _[1] > min_frep],key = lambda x: x[1],reverse = True)[:max_size]
        vocab_dic = {word_count[0]: idx for idx,word_count in enumerate(vocab_list)}
        vocab_dic.update({UNK: len(vocab_dic),PAD: len(vocab_dic) + 1})  # {"<UNK>':4760,"<PAD>':4761}
        print(vocab_dic)
        pkl.dump(vocab_dic,open('simplifyweibo_4_moods.pkl','wb'))  # 现在我完成:统计了所有的文字,并将每一个独一无二的文
        print(f"Vocabsize:{len(vocab_dic)}")
    return vocab_dic

if __name__=="__main__":
    vocab=build_vocab('simplifyweibo_4_moods.csv',MAX_VOCAB_SIZE,3)

1. 分字器tokenizer = lambda x: [y for y in x]

作用:中文分字(因中文无空格分隔单词,NLP 基础任务常按字符处理)

原理:匿名函数lambda接收字符串x,通过列表推导式将字符串拆分为单个字符的列表

示例:输入"我喜欢NLP",输出["我", "喜", "欢", "N", "L", "P"]

2. 字频统计vocab_dic.get(word, 0) + 1

核心:利用字典get方法实现高效字频统计,无需先判断字符是否在字典中

如果word已在vocab_dic中,取其当前值(已出现次数)加 1

如果word不在vocab_dic中,默认返回 0,加 1 后即为首次出现(次数 = 1)

3. 词表排序与截取

python 复制代码
sorted([_ for _ in vocab_dic.items() if _[1] > min_freq], key=lambda x: x[1], reverse=True)[:max_size]

筛选:[1] > min_freq 保留字频超过阈值的字符([1]为元组中的字频值)

排序:key=lambda x: x[1] 按字频排序,reverse=True 降序(高频在前)

截取:[:max_size] 仅保留前MAX_VOCAB_SIZE个高频字符,控制词表容量

4. UNK 和 PAD 的作用

<UNK>(未知字符):词表中未收录的生僻字、外来字、特殊符号,统一映射到该索引,避免模型因未见过的字符报错

<PAD>(填充字符):NLP 模型要求输入文本长度一致,需用该字符将短文本补全到指定长度,长文本截断,保证输入维度统一

索引分配:在基础词表后连续分配,确保索引唯一且无冲突(示例:基础词表 4760 个字符→UNK=4760,PAD=4761,总词表大小 4762)

5. pickle 保存词表pkl.dump(vocab_dic, open('xxx.pkl', 'wb'))

原因:pickle 是 Python 专用的序列化格式,能完整保留字典的键值对结构,且二进制写入节省存储空间

使用:在模型训练 / 推理时,可通过pkl.load(open('xxx.pkl', 'rb'))快速加载词表,无需重新统计

二、数据集加载与预处理

1.函数功能:

  • 加载之前构建的字表文件,实现字符→数值索引的映射;

  • 读取原始 CSV 语料,提取文本标签和评论内容,并对文本进行中文分字;

  • 对所有文本做长度统一处理(短文本填充、长文本截断),适配模型固定输入长度要求;

  • 将字符序列转换为数值索引序列(模型可处理的输入格式);

  • 对所有数据随机打乱,按8:1:1比例切分训练集、验证集、测试集;

  • 输出字表和切分后的三类数据集,为后续模型训练做准备。

代码:

python 复制代码
from tqdm import tqdm
import pickle as pkl
import random
import torch
UNK,PAD='<UNK>','<PAD>'
def load_dataset(path, pad_size=70):
    contents =[]  # 用来存储转换为数值标号的句子
    vocab = pkl.load(open('simplifyweibo_4_moods.pkl','rb'))  # 读取vocab文件
    tokenizer = lambda x: [y for y in x]
    with open(path,'r',encoding='UTF-8') as f:
        i = 0
        for line in tqdm(f):
            if i == 0:
                i += 1
                continue
            if not line:
                continue
            label = int(line[0])
            content = line[2:].strip('\n')
            words_line = []
            token = tokenizer(content)  # 将每一行的内容进行分字
            seq_len = len(token) # 获取一行实际内容的长度
            if pad_size:  # 判断每条评论是否超过70个字
                if len(token) < pad_size:  # 如果一行的字少于70,则补充<PAD>
                    token.extend([PAD] * (pad_size - len(token)))
                else :  # 如果一行的字大于70,则只取前70个字
                    token = token[:pad_size]# 如果一条评论种的字大于或等于70个字,索引的切分
                    seq_len = pad_size# 当前评论的长度
            # word to id
            for word in token:
                words_line.append(vocab.get(word,vocab.get(UNK)))  # 把每一条评论转换为独热编码
            contents.append((words_line,int(label),seq_len))
        random.shuffle(contents)# 打乱顺序
        train_data = contents[:int(len(contents) * 0.8)]  # 前80%的评论数据作为训练集
        dev_data = contents[int(len(contents) * 0.8):int(len(contents) * 0.9)]  # 把80%~90%的评论数据集作为验证数据
        test_data = contents[int(len(contents)*0.9):]  # 90%~最后的数据作为测试数据集
        return vocab, train_data, dev_data, test_data

1.文本长度统一(填充 / 截断)

python 复制代码
pad_size=70  # 目标固定长度:所有文本最终统一为70个字符
token = tokenizer(content)  # 对评论内容分字,得到字符列表
seq_len = len(token)        # 记录文本**原始实际长度**(后续模型可利用该信息,避免填充字符干扰)
if pad_size:  # 若指定了固定长度,则执行填充/截断
    if len(token) < pad_size:  # 短文本:填充<PAD>
        token.extend([PAD] * (pad_size - len(token)))  # 末尾补充PAD,直到长度为70
    else:  # 长文本:截断
        token = token[:pad_size]  # 只取前70个字符,截断多余部分
        seq_len = pad_size        # 截断后实际有效长度为pad_size

2.字符→数值索引

python 复制代码
words_line = []
for word in token:
    # 核心映射逻辑:存在则取字符索引,不存在则取UNK的索引
    words_line.append(vocab.get(word, vocab.get(UNK)))
  • 若字符在字表中(高频基础字符),直接取其对应的唯一索引

  • 若字符不在字表中(生僻字、特殊符号、外来字),统一映射为<UNK>的索引

3.将单条数据的「数值序列、标签、原始长度」封装为元组,存入列表统一管理、

python 复制代码
contents.append((words_line,int(label),seq_len))

4.数据集打乱与切分

python 复制代码
random.shuffle(contents)  # 随机打乱所有数据的顺序,破坏原始数据的分布规律
# 按8:1:1比例切分,无重叠、全覆盖
train_data = contents[:int(len(contents) * 0.8)]  # 前80%:训练集(模型参数学习)
dev_data = contents[int(len(contents) * 0.8):int(len(contents) * 0.9)]  # 80%-90%:验证集(超参数调优、早停)
test_data = contents[int(len(contents)*0.9):]  # 后10%:测试集(模型最终性能评估)

8:1:1是 NLP 任务中经典的数据集切分比例,兼顾训练集的数据量和验证 / 测试集的评估有效性

相关推荐
波动几何2 小时前
Skill 构建指南:从零打造 AI 智能体扩展包
人工智能
穿过锁扣的风2 小时前
从原理到实战:决策树三大算法(ID3、C4.5、CART)深度解析
大数据·深度学习·神经网络·机器学习
2501_947908202 小时前
2026年如何打造理想的沉浸式声学空间,选择合适的吸顶音响至关重要
大数据·人工智能
deephub2 小时前
分类数据 EDA 实战:如何发现隐藏的层次结构
人工智能·python·机器学习·数据分析·数据可视化
Godspeed Zhao2 小时前
从零开始学AI8——机器学习1
人工智能·机器学习
samoyan2 小时前
agent 开发中,压缩历史信息常用策略
人工智能
海绵宝宝de派小星2 小时前
图像处理基础概念与常用操作
图像处理·人工智能·ai
@鱼香肉丝没有鱼2 小时前
Transformer底层原理—Encoder结构
人工智能·深度学习·transformer
发哥来了2 小时前
主流Sora2相关商用服务公司可靠性对比
大数据·人工智能