3、数据准备与预处理:数据收集策略、数据清洗与去重、数据标注格式(JSONL、CSV)、分词器(Tokenizer)的使用、数据集划分(训练/验证/测试)
数据准备,说白了就是大模型微调的「食材采购与清洗」环节。我见过太多团队模型架构选得挺好,训练代码也没问题,最后效果差——一查,数据烂了。嗯,这步要是没做好,后面再怎么调参都是白搭。
3.1 数据收集策略:从哪儿找「好料子」
数据收集不是简单的「爬就完事了」。我个人习惯先问自己三个问题:
- 任务需要什么知识? 比如做法律问答,你得有法条、判例、司法解释。
- 数据量级够不够? 领域微调一般几千到几万条高质量数据就够了,别贪多。
- 数据来源是否可靠? 网上随便扒的,你敢用吗?
常见的收集渠道我列一下:
- 公开数据集:Hugging Face Datasets、天池、Kaggle。记得先看数据协议,有些不能商用。
- 业务日志/用户反馈:这是最宝贵的「真实数据」。我在项目中遇到过,用户对话日志里藏着大量真实需求,清洗后效果比人工标注还好。
- 知识库/文档:企业内部文档、技术手册、产品说明书。格式乱?没关系,后面会清洗。
- 合成数据:用GPT-4或文心一言生成。注意!合成数据容易有「AI味」,需要人工抽检。
我的小技巧:收集时多留一个心眼——记录数据来源。万一模型出问题了,你能快速定位是哪个批次的数据「有毒」。
3.2 数据清洗与去重:别让垃圾数据毁了模型
数据清洗,说白了就是「去杂质」。我刚开始做微调时,觉得这步可有可无,直到有一次模型老是输出乱码——查了半天,发现训练数据里有几万条HTML标签没去掉。
清洗流程我一般这么走:
- 去噪:去掉HTML标签、特殊符号、不可见字符。用正则表达式一把梭。
- 去重:重复数据会让模型「死记硬背」,泛化能力变差。我常用MinHash或SimHash做近似去重。
- 过滤低质量:太短的(<10字符)、太长的(>2048 token)、乱码的、全是标点的,统统扔掉。
- 敏感信息脱敏:手机号、身份证、银行卡号,用正则替换成占位符。
# 一个简单的清洗示例
import re
def clean_text(text):
# 去HTML标签
text = re.sub(r'<[^>]+>', '', text)
# 去特殊符号(保留中文、英文、数字、基本标点)
text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9,。!?、:;""''()\n]', '', text)
# 合并多余换行
text = re.sub(r'\n{3,}', '\n\n', text)
return text.strip()
注意:去重时别太狠。有些领域(比如法律条文)天然就有相似内容,过度去重会丢失关键知识。我曾经吃过这个亏,把法条去重后,模型连「故意杀人罪」和「过失致人死亡罪」都分不清了。
3.3 数据标注格式:JSONL 还是 CSV?
微调数据格式,说白了就两种主流:JSONL 和 CSV。我个人更偏爱 JSONL,原因后面说。
| 格式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JSONL | 每行一个独立JSON,支持嵌套结构,字段灵活 | 文件体积稍大 | 对话数据、多轮问答、指令微调 |
| CSV | 轻量、Excel可直接打开查看 | 不支持嵌套,字段值里不能有逗号 | 简单分类、单轮问答 |
以对话微调为例,JSONL 长这样:
{"instruction": "解释什么是注意力机制", "input": "", "output": "注意力机制是一种让模型关注输入中重要部分的技术..."}
{"instruction": "翻译成英文", "input": "今天天气真好", "output": "The weather is nice today."}
CSV 版本:
instruction,input,output
解释什么是注意力机制,,注意力机制是一种...
翻译成英文,今天天气真好,The weather is nice today.
为什么我推荐 JSONL? 因为微调时经常需要加字段,比如加个「system_prompt」或「history」。JSONL 直接加个键就行,CSV 就得改表结构,麻烦。
3.4 分词器(Tokenizer)的使用:别小看这一步
Tokenizer 说白了就是把文本切成模型能理解的「数字块」。你想想看,模型不认识汉字,只认识数字,所以得靠 Tokenizer 做翻译。
使用 Tokenizer 时,我踩过两个大坑:
- 坑一:忘记设置 max_length。训练时一条数据太长,直接爆显存。我建议统一截断到 2048 或 4096。
- 坑二:padding 策略不对。训练时用动态 padding(按 batch 内最长序列),推理时用固定 padding。混用会出问题。
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2-7B-Instruct")
# 编码示例
text = "请解释一下大模型微调的原理"
tokens = tokenizer(
text,
max_length=512,
padding="max_length", # 训练时建议用 "longest"
truncation=True,
return_tensors="pt"
)
print(tokens["input_ids"].shape) # torch.Size([1, 512])
避坑指南:我曾经在微调时忘了加
add_special_tokens=True,结果模型把 [CLS] [SEP] 这些特殊标记当成了普通文本,训练出来的模型一开口就是乱码。嗯,从那以后我每次都会检查 tokenizer 的输出。
3.5 数据集划分:训练/验证/测试,比例怎么定?
数据集划分,说白了就是「分考场」。训练集是平时做题,验证集是模拟考,测试集是最终大考。
我常用的划分比例:
- 小数据集(<1万条):80% 训练,10% 验证,10% 测试
- 大数据集(>10万条):98% 训练,1% 验证,1% 测试
为什么验证集和测试集可以这么少?因为大模型微调时,验证集主要看 loss 是否收敛,不需要太多样本。但测试集一定要保证覆盖所有任务类型。
from sklearn.model_selection import train_test_split
# 假设 data 是 List[dict]
train, temp = train_test_split(data, test_size=0.2, random_state=42)
val, test = train_test_split(temp, test_size=0.5, random_state=42)
print(f"训练集: {len(train)}, 验证集: {len(val)}, 测试集: {len(test)}")
重要提醒:划分时一定要做 分层采样。比如你的数据里「法律问答」占 60%、「医疗问答」占 40%,那划分后每个子集里比例也要保持 6:4。否则验证集里全是法律,医疗的 loss 你根本看不到。
嗯,数据准备这块就这些。说白了,数据质量决定了微调的天花板,模型架构只是帮你接近这个天花板。下一章我们聊聊「模型选择与加载」,到时候见。