FastChat数据预处理:文本清洗与格式标准化
在大语言模型训练过程中,数据质量直接决定了模型性能的上限。FastChat作为业界领先的开源对话模型训练平台,提供了一套完整的数据预处理流水线,本文将深入解析其文本清洗与格式标准化技术细节。
数据预处理的核心挑战
大语言模型训练数据面临的主要挑战包括:
| 挑战类型 | 具体问题 | FastChat解决方案 |
|---|---|---|
| 格式不一致 | HTML标签残留、代码块格式错误 | HTML转Markdown标准化 |
| 内容质量问题 | 低质量对话、重复内容 | 多级过滤机制 |
| 语言混杂 | 多语言混合影响训练效果 | 语言检测与过滤 |
| 长度不匹配 | 对话过长超出模型上下文 | 智能分割算法 |
| 隐私与合规 | 敏感信息泄露风险 | 关键词过滤机制 |
HTML到Markdown的精准转换
FastChat使用clean_sharegpt.py模块处理HTML格式的原始数据,其转换流程包含多个关键步骤:
正则表达式模式匹配
# 核心正则表达式模式
div_pattern = re.compile("<div.*?>")
span_pattern = re.compile("<span.*?>")
code_lang_pattern = re.compile(
"```\s*" + "(.*?)" + "(?:Copy code)+" + "(.+?)" + "\s*?```", re.DOTALL
)
code_lang_format = "```\g<1>\n\g<2>\n```"
代码块格式化处理
原始HTML中的代码块通常包含冗余信息,FastChat通过以下步骤进行清理:
def reformat_code(val: str) -> str:
# 输入格式示例:
# ```
# $<language>Copy code$<exact_code_here>
# ```
# 转换为标准Markdown格式
return re.sub(code_lang_pattern, code_lang_format, val)
def html_to_markdown(val: str) -> str:
val = re.sub(div_pattern, "", val) # 移除所有<div>标签
val = re.sub(span_pattern, "", val) # 移除所有<span>标签
val = markdownify.markdownify(val).strip() # 转换为Markdown
val = reformat_code(val) # 重新格式化代码块
# 移除噪声内容
val = re.sub(regenerate_pattern, "", val)
val = re.sub(copy_chars_pattern, "", val)
val = re.sub(copy_code_pattern, "", val)
return val.replace("\n\n\n", "\n").strip()
多级数据过滤机制
1. 格式验证过滤
def clean_html_one_sample(sample):
roles = ["human", "gpt"]
# 对话轮次验证
if len(sample["conversations"]) <= 1:
return (sample, 1) # 跳过过短对话
# 起始角色验证(必须从human开始)
if sample["conversations"][0]["from"] != "human":
sample["conversations"] = sample["conversations"][1:]
# 终止角色验证(必须以gpt结束)
if sample["conversations"][-1]["from"] == "human":
sample["conversations"] = sample["conversations"][:-1]
2. 关键词黑名单过滤
def contain_blocked_words(val: str) -> bool:
blocked_words = ["openai", "chatgpt"]
for w in blocked_words:
if w in val.lower():
return True
return False
def contain_blocked_responses(role: str, val: str) -> bool:
if role == "gpt":
blocked_responses = [
"Too many requests in 1 hour. Try again later.",
"!Too many requests in 1 hour. Try again later.",
]
for w in blocked_responses:
if val.startswith(w):
return True
return False
3. 语言检测与过滤
def skip(conv, args):
# 多语言检测与过滤
if args.keep_lang != "all" or args.skip_lang is not None:
text = "\n".join([x["value"] for x in conv["conversations"]])
try:
lang_code = Detector(text).language.code
except (pycld2.error, polyglot.detect.base.UnknownLanguage):
lang_code = "unknown"
# 保留指定语言或跳过指定语言
if args.keep_lang != "all" and lang_code != args.keep_lang:
return True
if lang_code == args.skip_lang:
return True
# 重复数字模式过滤
if args.reduce_rep:
for sentence in conv["conversations"]:
val = sentence["value"]
sub = re.search(r"(\d)\1{8}", val) # 检测连续9个相同数字
if sub is not None:
return True
return False
长对话智能分割算法
FastChat采用基于token长度的智能分割算法:
def split_one_sample(sample):
tokenized_lens = []
conversations = sample["conversations"]
conversations = conversations[: len(conversations) // 2 * 2] # 确保偶数轮次
# 计算每轮对话的token长度
for c in conversations:
length = len(tokenizer(c["value"]).input_ids) + 6 # 添加特殊token开销
tokenized_lens.append(length)
start_idx = 0
cur_len = 0
new_samples = []
# 滑动窗口分割算法
for i in range(0, len(conversations), 2):
tmp_len = tokenized_lens[i] + tokenized_lens[i + 1] # 一轮完整对话长度
if cur_len + tmp_len > max_length: # 超过最大长度限制
new_samples.append(make_sample(sample, start_idx, i))
start_idx = i
cur_len = 0
elif i == len(conversations) - 2: # 最后一轮对话
new_samples.append(make_sample(sample, start_idx, i + 2))
cur_len += tmp_len
return new_samples
数据质量统计与分析
FastChat提供详细的数据统计功能,帮助用户了解数据集特征:
def compute_stats(content):
sample_lens = [] # 样本总长度
sample_turns = [] # 对话轮次
prompt_lens = [] # 提示词长度
res_lens = [] # 回复长度
for c in content:
sample_len = 0
sample_turns.append(len(c["conversations"]) // 2)
for i in range(len(c["conversations"]) // 2):
p = c["conversations"][i * 2]["value"] # 用户提问
r = c["conversations"][i * 2 + 1]["value"] # 模型回复
turn_len = len(p) + len(r)
sample_len += turn_len
prompt_lens.append(len(p))
res_lens.append(len(r))
sample_lens.append(sample_len)
return sample_lens, sample_turns, prompt_lens, res_lens
统计输出示例:
#sequence: 125.34 K
#tokens: 1.25 M
avg. turns: 2.45
avg. prompt length: 156.78
avg. response length: 245.32
完整数据处理流水线
# 步骤1: HTML转Markdown基础清洗
python3 -m fastchat.data.clean_sharegpt --in sharegpt_html.json --out sharegpt_clean.json
# 步骤2: 语言过滤(可选)
python3 -m fastchat.data.optional_clean --in sharegpt_clean.json --out sharegpt_clean_en.json --keep-lang en
# 步骤3: 格式错误过滤
python3 -m fastchat.data.filter_wrong_format --in sharegpt_clean_en.json --out sharegpt_clean_en_filtered.json
# 步骤4: 长对话分割
python3 -m fastchat.data.split_long_conversation \
--in sharegpt_clean_en_filtered.json \
--out sharegpt_final.json \
--model-name-or-path meta-llama/Llama-2-7b-chat-hf
# 步骤5: 数据统计分析
python3 -m fastchat.data.get_stats --in sharegpt_final.json
最佳实践与性能优化
并行处理加速
FastChat利用多进程并行处理大幅提升清洗效率:
def clean_html_all(content, begin, end):
processed = []
with ProcessPoolExecutor() as executor:
for result in tqdm(
executor.map(clean_html_one_sample, content), total=len(content)
):
processed.append(result)
# ...后续处理逻辑
内存优化策略
对于超大规模数据集,建议采用分块处理:
def split_all(content, begin, end, tokenizer_, max_length_):
# 将数据分块处理,每块1000个样本
chunks = [content[i : i + 1000] for i in range(0, len(content), 1000)]
with ProcessPoolExecutor() as executor:
for result in tqdm(executor.map(worker, chunks), total=len(chunks)):
new_content.extend(result)
return new_content
总结
FastChat的数据预处理流水线体现了现代大语言模型训练数据处理的先进理念:
- 标准化优先:通过HTML到Markdown的转换确保格式统一
- 质量至上:多级过滤机制保障数据纯净度
- 智能分割:基于token长度的自适应分割算法
- 可扩展架构:并行处理支持大规模数据集
- 透明统计:详细的数据分析帮助优化训练策略
这套数据处理方案不仅适用于Vicuna模型训练,也可作为其他对话模型数据预处理的参考标准,为构建高质量对话数据集提供了完整的技术路径。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



