大模型微调实战:从数据准备到Tokenizer编码全流程解析
本文详细讲解大模型微调数据准备全流程,以电商智能客服意图识别为例。首先设计包含查物流、退换货等场景的样本数据,强调高质量数据的五大特性:领域匹配性、标签准确性、格式规范性、多样性和模型输入适配性。接着通过代码演示数据清洗步骤,包括去除重复数据、过滤敏感内容、剔除低质样本等关键操作。最终生成符合BERT-base-chinese模型输入要求的清洗数据,为后续微调奠定基础。
大模型微调的效果,七分看数据,三分看调参。高质量、结构化的数据是微调成功的核心前提。本文将以智能客服意图识别为场景,完整讲解大模型微调数据准备的全流程,包括样本数据设计、数据质量分析、数据清洗、格式标准化、Tokenizer编码等关键步骤,并通过可复现的代码案例,拆解每一步的技术细节和实现逻辑。
一、需求背景与样本数据设计
1.1 场景定义
本次案例聚焦电商智能客服意图识别场景,目标是微调一个轻量级大模型(如BERT-base-chinese或ChatGLM-6B),使其能准确识别用户咨询意图(如查物流、退换货、商品咨询、投诉)。
1.2 样本数据设计
首先生成符合该场景的原始样本数据,数据需具备「真实业务特征」「意图标签明确」「覆盖典型用户表达」等特点。
1.2.1 原始样本数据(JSON格式)
# 生成电商客服意图识别原始样本数据
import json
import random
# 定义意图标签和对应的用户表达模板
intent_templates = {
"查物流": [
"我的订单{order_id}到哪了?",
"订单{order_id}什么时候发货?",
"麻烦查一下{order_id}的物流状态",
"物流信息怎么查?订单号是{order_id}",
"为什么我的{order_id}还没送到?"
],
"退换货": [
"我要退货,订单{order_id},商品质量有问题",
"能换货吗?{order_id}收到的商品尺寸不对",
"申请退款,订单号{order_id},不想要了",
"退换货流程是什么?我想退{order_id}这个订单",
"商品破损,订单{order_id},要求退货"
],
"商品咨询": [
"这款{product}的保质期是多久?",
"{product}有黑色款吗?",
"请问{product}的使用方法?",
"{product}支持7天无理由吗?",
"买{product}送赠品吗?"
],
"投诉": [
"客服态度太差了,投诉!",
"等了3天还没发货,必须投诉",
"商品和描述不符,我要投诉",
"快递员把东西弄丢了,投诉维权",
"售后一直不处理,投诉你们平台"
]
}
# 生成模拟数据(包含部分脏数据)
def generate_sample_data(num_samples=100):
# 模拟订单号、商品名
order_ids = [f"JD{random.randint(10000, 99999)}" for _ in range(50)]
products = ["保温杯", "充电宝", "卫衣", "运动鞋", "蓝牙耳机", "面膜", "洗发水"]
samples = []
for i in range(num_samples):
# 随机选择意图
intent = random.choice(list(intent_templates.keys()))
# 随机选择模板并填充变量
template = random.choice(intent_templates[intent])
text = template.format(
order_id=random.choice(order_ids) if "{order_id}" in template else "",
product=random.choice(products) if "{product}" in template else ""
)
# 故意混入部分脏数据(重复、低质、敏感内容)
if i % 10 == 0: # 10%重复数据
text = samples[-1]["text"] if samples else text
if i % 15 == 0: # 低质数据(无意义内容)
text = "啊啊啊 不知道说啥 随便写写"
if i % 20 == 0: # 敏感内容
text = "XXX平台就是垃圾,去死吧"
samples.append({
"id": i,
"text": text,
"intent": intent
})
# 保存为JSON文件
with open("raw_customer_service_data.json", "w", encoding="utf-8") as f:
json.dump(samples, f, ensure_ascii=False, indent=2)
return samples
# 生成100条样本数据
raw_data = generate_sample_data(100)
print(f"生成原始数据条数:{len(raw_data)}")
print("前5条原始数据示例:")
for i in range(5):
print(json.dumps(raw_data[i], ensure_ascii=False, indent=2))
1.2.2 高质量数据的核心特性
针对本次微调场景,高质量数据需满足以下特性:
- 领域匹配性:数据内容严格围绕「电商客服意图识别」,覆盖查物流、退换货等核心业务场景,符合真实用户的表达习惯;
- 标签准确性:每条数据的「intent」标签与文本内容完全匹配,无标签错误;
- 格式规范性:基础JSON格式,包含唯一ID、文本、标签三要素,便于后续处理;
- 多样性:同一意图包含不同的用户表达方式(如查物流有「到哪了」「什么时候发货」等),避免表达单一;
- 模型输入适配性:文本长度控制在模型输入阈值内(如BERT-base支持512个token),无超长文本。
1.2.3 模型输入要求
本次选择BERT-base-chinese作为微调模型,其输入要求:
- 文本需经过Tokenizer编码,转换为
input_ids(token的数字编码)、attention_mask(注意力掩码)、token_type_ids(分句标识); - 标签需转换为数字编码(如查物流=0,退换货=1,商品咨询=2,投诉=3);
- 单条文本token数不超过512,超出部分需截断;
- 输入文本需为纯文本格式,无特殊字符、乱码、敏感内容。
二、数据清洗:剔除低质、敏感、重复数据
数据清洗是提升数据质量的核心步骤,目标是去除无效数据,保留符合要求的高质量样本。
2.1 清洗步骤与代码实现
import json
import re
from collections import Counter
# 1. 加载原始数据
with open("raw_customer_service_data.json", "r", encoding="utf-8") as f:
raw_data = json.load(f)
print(f"原始数据条数:{len(raw_data)}")
# 2. 定义清洗规则
## 2.1 敏感词列表(电商场景常见)
sensitive_words = ["垃圾", "去死", "傻逼", "操", "妈的", "智障"]
## 2.2 低质内容判定(无意义字符/短句)
low_quality_pattern = re.compile(r"^[\s啊哈额哦呀]+$|^随便写写$|^不知道说啥$")
# 3. 数据清洗函数
def clean_data(raw_data):
cleaned_data = []
text_set = set() # 用于去重
for sample in raw_data:
id_ = sample["id"]
text = sample["text"].strip() # 去除首尾空格
intent = sample["intent"]
# 跳过空文本
if not text:
print(f"跳过空文本:ID={id_}")
continue
# 3.1 去重:判断文本是否已存在
if text in text_set:
print(f"跳过重复数据:ID={id_},文本={text}")
continue
# 3.2 剔除敏感内容
if any(word in text for word in sensitive_words):
print(f"跳过敏感内容:ID={id_},文本={text}")
continue
# 3.3 剔除低质内容
if low_quality_pattern.match(text):
print(f"跳过低质内容:ID={id_},文本={text}")
continue
# 3.4 剔除超长文本(预筛,后续Tokenizer会再处理)
if len(text) > 100:
print(f"跳过超长文本:ID={id_},文本={text[:20]}...")
continue
# 符合要求的数据加入清洗列表
cleaned_data.append({
"id": id_,
"text": text,
"intent": intent
})
text_set.add(text)
# 保存清洗后的数据
with open("cleaned_customer_service_data.json", "w", encoding="utf-8") as f:
json.dump(cleaned_data, f, ensure_ascii=False, indent=2)
print(f"清洗后数据条数:{len(cleaned_data)}")
return cleaned_data
# 执行清洗
cleaned_data = clean_data(raw_data)
print("前5条清洗后数据示例:")
for i in range(5):
print(json.dumps(cleaned_data[i], ensure_ascii=False, indent=2))
2.2 清洗步骤说明
- 加载数据:读取原始JSON格式数据,确认数据条数;
- 去重处理:通过集合
text_set记录已出现的文本,跳过重复文本(重复数据会导致模型过拟合); - 敏感内容过滤:基于敏感词列表,剔除包含辱骂、攻击性词汇的文本(避免模型学习不良内容);
- 低质内容过滤:通过正则表达式匹配无意义内容(如「啊啊啊 随便写写」),剔除无效样本;
- 超长文本过滤:预筛超长文本(长度>100),减少后续Tokenizer处理压力;
- 结果保存:将清洗后的数据保存为新的JSON文件,便于后续标准化处理。
三、数据标准化:统一格式、标点、表达规则
标准化的目标是消除文本中的「噪音」,统一表达形式,提升数据的一致性,降低模型学习成本。
3.1 标准化步骤与代码实现
import json
import re
import jieba # 需提前安装:pip install jieba
# 1. 加载清洗后的数据
with open("cleaned_customer_service_data.json", "r", encoding="utf-8") as f:
cleaned_data = json.load(f)
# 2. 定义标准化规则
class DataStandardizer:
def __init__(self):
# 标点符号映射(统一全角/半角、中文/英文标点)
self.punctuation_map = {
",": ",", ".": "。", "?": "?", "!": "!",
";": ";", ":": ":", "(": "(", ")": ")",
"[": "【", "]": "】", "{": "{", "}": "}",
"'": "‘", "\"": "”", "-": "—"
}
# 冗余空格正则
self.space_pattern = re.compile(r"\s+")
# 重复字符正则(如「好好好」→「好」)
self.repeat_pattern = re.compile(r"(\w)\1{2,}")
def standardize_punctuation(self, text):
"""统一标点符号(全角、中文格式)"""
for en_punc, cn_punc in self.punctuation_map.items():
text = text.replace(en_punc, cn_punc)
return text
def remove_redundant_space(self, text):
"""去除冗余空格(多个空格→单个,首尾空格→去除)"""
text = self.space_pattern.sub(" ", text).strip()
return text
def normalize_repeat_chars(self, text):
"""归一化重复字符(如「啊啊啊」→「啊」)"""
text = self.repeat_pattern.sub(r"\1", text)
return text
def standardize_text(self, text):
"""完整文本标准化流程"""
# 步骤1:统一标点
text = self.standardize_punctuation(text)
# 步骤2:去除冗余空格
text = self.remove_redundant_space(text)
# 步骤3:归一化重复字符
text = self.normalize_repeat_chars(text)
# 步骤4:分词(可选,根据模型需求)
# 分词后拼接(保留空格分隔,便于模型识别)
seg_text = " ".join(jieba.cut(text))
return text, seg_text
# 3. 执行标准化
standardizer = DataStandardizer()
standardized_data = []
for sample in cleaned_data:
id_ = sample["id"]
raw_text = sample["text"]
intent = sample["intent"]
# 执行文本标准化
standard_text, seg_text = standardizer.standardize_text(raw_text)
standardized_data.append({
"id": id_,
"raw_text": raw_text, # 原始清洗后文本
"standard_text": standard_text, # 标准化文本(无分词)
"seg_text": seg_text, # 分词后文本(模型输入用)
"intent": intent
})
# 4. 保存标准化后数据
with open("standardized_customer_service_data.json", "w", encoding="utf-8") as f:
json.dump(standardized_data, f, ensure_ascii=False, indent=2)
print(f"标准化后数据条数:{len(standardized_data)}")
print("标准化示例对比:")
sample = standardized_data[0]
print(f"原始清洗文本:{sample['raw_text']}")
print(f"标准化文本:{sample['standard_text']}")
print(f"分词后文本:{sample['seg_text']}")
3.2 标准化步骤说明
- 标点统一:将半角标点(如
,?)转换为全角中文标点(如,?),消除标点格式差异; - 冗余空格去除:将多个连续空格替换为单个空格,去除首尾空格,避免空格干扰模型分词;
- 重复字符归一化:将连续重复3次以上的字符(如「啊啊啊」)归一化为单个字符,减少无意义噪音;
- 分词处理:使用jieba分词对文本进行分词(电商场景中文分词能提升模型意图识别准确率),分词后用空格分隔,便于Tokenizer处理。
四、数据编码:Tokenizer转换为模型可读取格式
大模型无法直接读取文本,需通过Tokenizer将文本转换为数字编码(input_ids、attention_mask等),这是数据准备的最后一步。
4.1 Tokenizer编码步骤与代码实现
import json
import torch
from transformers import BertTokenizer, BertForSequenceClassification
from sklearn.preprocessing import LabelEncoder
# 1. 加载标准化后的数据
with open("standardized_customer_service_data.json", "r", encoding="utf-8") as f:
standardized_data = json.load(f)
# 2. 初始化Tokenizer和标签编码器
## 加载预训练BERT中文Tokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
## 标签编码(将文本标签转换为数字)
label_encoder = LabelEncoder()
intents = [sample["intent"] for sample in standardized_data]
label_encoder.fit(intents)
print(f"标签映射关系:{dict(zip(label_encoder.classes_, label_encoder.transform(label_encoder.classes_))))}")
# 3. 定义编码函数
def encode_data(sample, tokenizer, label_encoder, max_length=64):
"""
将单条样本转换为模型可读取的张量格式
Args:
sample: 标准化后的单条样本
tokenizer: BERT Tokenizer
label_encoder: 标签编码器
max_length: 最大token长度(根据模型调整)
Returns:
编码后的字典(包含input_ids、attention_mask、label等)
"""
# 提取分词后文本和标签
text = sample["seg_text"]
intent = sample["intent"]
# Tokenizer编码(返回PyTorch张量)
encoding = tokenizer(
text,
max_length=max_length,
padding="max_length", # 不足max_length则补0
truncation=True, # 超过max_length则截断
return_tensors="pt" # 返回PyTorch张量
)
# 标签编码
label = torch.tensor(label_encoder.transform([intent])[0], dtype=torch.long)
# 整理结果(去除batch维度)
encoded_sample = {
"id": sample["id"],
"input_ids": encoding["input_ids"].squeeze(0), # 形状:(max_length,)
"attention_mask": encoding["attention_mask"].squeeze(0), # 形状:(max_length,)
"token_type_ids": encoding["token_type_ids"].squeeze(0), # 形状:(max_length,)
"label": label, # 形状:()
"text": sample["standard_text"],
"intent": intent
}
return encoded_sample
# 4. 批量编码数据
encoded_dataset = []
for sample in standardized_data:
encoded_sample = encode_data(sample, tokenizer, label_encoder, max_length=64)
encoded_dataset.append(encoded_sample)
# 5. 保存编码后的数据(保存为PT文件,便于PyTorch加载)
## 转换为字典格式(便于批量加载)
batch_encoded = {
"input_ids": torch.stack([sample["input_ids"] for sample in encoded_dataset]),
"attention_mask": torch.stack([sample["attention_mask"] for sample in encoded_dataset]),
"token_type_ids": torch.stack([sample["token_type_ids"] for sample in encoded_dataset]),
"labels": torch.stack([sample["label"] for sample in encoded_dataset]),
"metadata": [{"id": s["id"], "text": s["text"], "intent": s["intent"]} for s in encoded_dataset]
}
# 保存为PyTorch文件
torch.save(batch_encoded, "encoded_customer_service_data.pt")
print(f"编码后数据保存完成,数据维度:")
print(f"input_ids形状:{batch_encoded['input_ids'].shape}") # (样本数, max_length)
print(f"attention_mask形状:{batch_encoded['attention_mask'].shape}")
print(f"labels形状:{batch_encoded['labels'].shape}") # (样本数,)
# 6. 验证编码结果
sample_idx = 0
sample = encoded_dataset[sample_idx]
print("\n编码结果验证(第1条样本):")
print(f"原始文本:{sample['text']}")
print(f"Input IDs(前10个):{sample['input_ids'][:10]}")
print(f"Attention Mask(前10个):{sample['attention_mask'][:10]}")
print(f"标签(数字):{sample['label']},标签(文本):{sample['intent']}")
# 解码验证(将input_ids转回文本)
decoded_text = tokenizer.decode(sample["input_ids"], skip_special_tokens=True)
print(f"解码后文本:{decoded_text}")
4.2 Tokenizer编码步骤说明
- 加载Tokenizer:使用
transformers库加载预训练的bert-base-chineseTokenizer,该Tokenizer适配中文文本的分词和编码; - 标签编码:通过
LabelEncoder将文本标签(如「查物流」)转换为数字(如0),便于模型进行分类任务; - 文本编码:
max_length=64:设置最大token长度(根据业务场景调整,电商客服文本较短,64足够);padding="max_length":不足64个token的文本,在末尾补0;truncation=True:超过64个token的文本,截断到64个;return_tensors="pt":返回PyTorch张量格式,可直接输入模型;
- 张量整理:将所有样本的
input_ids、attention_mask等拼接为批量张量,形状为(样本数, max_length); - 结果保存:将编码后的数据保存为
.pt文件,便于后续微调时直接加载; - 编码验证:将
input_ids解码回文本,确认编码无错误,保证文本和编码的一致性。
五、完整流程总结
5.1 数据准备全流程
5.2 核心要点回顾
- 数据质量是核心:高质量数据需满足领域匹配、标签准确、表达多样,清洗步骤需重点剔除重复、敏感、低质内容;
- 标准化提升一致性:统一标点、去除冗余空格、分词等操作,能消除文本噪音,降低模型学习成本;
- 编码适配模型要求:Tokenizer编码需严格匹配模型的输入格式(如max_length、padding、truncation),确保编码后的张量可直接输入模型;
- 可复现性:每一步操作都需保存中间结果(如清洗后、标准化后的数据),便于问题排查和流程复用。
5.3 扩展建议
- 数据量扩展:实际微调时,样本数建议不少于1000条,可通过数据增强(如同义词替换、句式变换)扩充数据;
- 多模型适配:若使用ChatGLM、LLaMA等大模型,需替换对应的Tokenizer(如ChatGLMTokenizer),调整max_length等参数;
- 数据校验:可增加人工抽检环节,验证清洗、标准化、编码后的数据集质量,确保符合业务要求。
通过以上完整的流程,你可以将原始的非结构化文本数据,转换为大模型微调所需的高质量、结构化、可读取的格式,为后续的微调训练奠定坚实的基础。
更多推荐


所有评论(0)