一、构建字表
基于微博语料库构建中文字表,通过统计字频筛选有效字符,为每个字符分配唯一索引,并加入未知字符<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 任务中经典的数据集切分比例,兼顾训练集的数据量和验证 / 测试集的评估有效性