大模型微调的效果,七分看数据,三分看调参。高质量、结构化的数据是微调成功的核心前提。本文将以智能客服意图识别为场景,完整讲解大模型微调数据准备的全流程,包括样本数据设计、数据质量分析、数据清洗、格式标准化、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 高质量数据的核心特性

针对本次微调场景,高质量数据需满足以下特性:

  1. 领域匹配性:数据内容严格围绕「电商客服意图识别」,覆盖查物流、退换货等核心业务场景,符合真实用户的表达习惯;
  2. 标签准确性:每条数据的「intent」标签与文本内容完全匹配,无标签错误;
  3. 格式规范性:基础JSON格式,包含唯一ID、文本、标签三要素,便于后续处理;
  4. 多样性:同一意图包含不同的用户表达方式(如查物流有「到哪了」「什么时候发货」等),避免表达单一;
  5. 模型输入适配性:文本长度控制在模型输入阈值内(如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 清洗步骤说明

  1. 加载数据:读取原始JSON格式数据,确认数据条数;
  2. 去重处理:通过集合text_set记录已出现的文本,跳过重复文本(重复数据会导致模型过拟合);
  3. 敏感内容过滤:基于敏感词列表,剔除包含辱骂、攻击性词汇的文本(避免模型学习不良内容);
  4. 低质内容过滤:通过正则表达式匹配无意义内容(如「啊啊啊 随便写写」),剔除无效样本;
  5. 超长文本过滤:预筛超长文本(长度>100),减少后续Tokenizer处理压力;
  6. 结果保存:将清洗后的数据保存为新的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 标准化步骤说明

  1. 标点统一:将半角标点(如, ?)转换为全角中文标点(如 ),消除标点格式差异;
  2. 冗余空格去除:将多个连续空格替换为单个空格,去除首尾空格,避免空格干扰模型分词;
  3. 重复字符归一化:将连续重复3次以上的字符(如「啊啊啊」)归一化为单个字符,减少无意义噪音;
  4. 分词处理:使用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编码步骤说明

  1. 加载Tokenizer:使用transformers库加载预训练的bert-base-chinese Tokenizer,该Tokenizer适配中文文本的分词和编码;
  2. 标签编码:通过LabelEncoder将文本标签(如「查物流」)转换为数字(如0),便于模型进行分类任务;
  3. 文本编码
    • max_length=64:设置最大token长度(根据业务场景调整,电商客服文本较短,64足够);
    • padding="max_length":不足64个token的文本,在末尾补0;
    • truncation=True:超过64个token的文本,截断到64个;
    • return_tensors="pt":返回PyTorch张量格式,可直接输入模型;
  4. 张量整理:将所有样本的input_idsattention_mask等拼接为批量张量,形状为(样本数, max_length)
  5. 结果保存:将编码后的数据保存为.pt文件,便于后续微调时直接加载;
  6. 编码验证:将input_ids解码回文本,确认编码无错误,保证文本和编码的一致性。

五、完整流程总结

5.1 数据准备全流程

原始样本数据生成

数据清洗:去重/去敏感/去低质

数据标准化:统一标点/分词/格式

Tokenizer编码:文本→数字张量

模型微调输入

5.2 核心要点回顾

  1. 数据质量是核心:高质量数据需满足领域匹配、标签准确、表达多样,清洗步骤需重点剔除重复、敏感、低质内容;
  2. 标准化提升一致性:统一标点、去除冗余空格、分词等操作,能消除文本噪音,降低模型学习成本;
  3. 编码适配模型要求:Tokenizer编码需严格匹配模型的输入格式(如max_length、padding、truncation),确保编码后的张量可直接输入模型;
  4. 可复现性:每一步操作都需保存中间结果(如清洗后、标准化后的数据),便于问题排查和流程复用。

5.3 扩展建议

  • 数据量扩展:实际微调时,样本数建议不少于1000条,可通过数据增强(如同义词替换、句式变换)扩充数据;
  • 多模型适配:若使用ChatGLM、LLaMA等大模型,需替换对应的Tokenizer(如ChatGLMTokenizer),调整max_length等参数;
  • 数据校验:可增加人工抽检环节,验证清洗、标准化、编码后的数据集质量,确保符合业务要求。

通过以上完整的流程,你可以将原始的非结构化文本数据,转换为大模型微调所需的高质量、结构化、可读取的格式,为后续的微调训练奠定坚实的基础。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐