技术同事电话打过来的时候,我正盯着屏幕发呆。他说微调后的模型输出全是乱码,根本没法用。我当时心里一沉,这项目已经拖了两周,再出问题估计得挨骂。我赶紧登录服务器,一看训练日志,果然,数据格式错了——模型压根没看懂输入。
业务场景
我们团队当时接了个活儿,要把公司内部的大量非结构化文档(比如PDF报告、Word文档)转换成大模型微调可用的训练数据。客户想用这些数据微调一个Llama 3模型,做智能问答。听起来挺简单,对吧?其实坑多得要命。数据来源五花八门,有扫描的PDF、老旧的Word 2003文件,还有一堆Excel表格。我得把这些东西统一成文本,再标注成问答对,最后转成模型能吃的格式。
数据说明
说实话,数据量真不小。原始文档大概有5000多个文件,包括2000多份PDF(其中30%是扫描件)、1500个Word文档(有些还是.doc老格式)、剩下的是一堆Excel和PPT。文本提取出来,原始大小接近50GB。清洗后,我们得到了10万条文本片段,每条平均500字符。标注目标是把这些转换成问答对,最终我们生成了8万条,用于Llama 3微调。数据分布上,技术文档占60%,业务报告30%,其他杂项10%。这个比例是客户定的,但我觉得技术部分可能标得不够细,后面会说到。
一开始,我们直接用Python的pdfplumber和python-docx库提取文本。代码写起来挺快,但跑起来就出问题了。有些PDF是扫描件,pdfplumber读出来全是乱码。当时的做法错了,以为换个库就行,试了PyPDF2和pdfminer,结果差不多。后来才发现,得先用OCR处理。接着装了pytesseract,配合pdf2image把PDF转成图片再识别。这步花了大概40分钟调试参数,因为默认的OCR精度不够,识别出来的文本错别字一堆。
# 用pytesseract做OCR,提取扫描PDF的文本
import pytesseract
from pdf2image import convert_from_path
def extract_text_from_scanned_pdf(pdf_path):
images = convert_from_path(pdf_path, dpi=300) # dpi调高,识别更准
text = ''
for img in images:
# 这里我试过不同配置,默认的英语识别效果一般
custom_config = r'--oem 3 --psm 6' # OEM 3用LSTM引擎,PSM 6假设统一区块
page_text = pytesseract.image_to_string(img, config=custom_config)
text += page_text + '\n'
return text
# 实际跑的时候,一个100页的PDF花了5分钟,CPU飙到80%
# 错误日志片段:TesseractError: Error opening data file /usr/share/tesseract-ocr/4.00/tessdata/eng.traineddata
# 解决:得先安装tesseract-ocr和语言包,sudo apt-get install tesseract-ocr tesseract-ocr-eng
文本提取完了,下一步是清洗。这步更头疼。原始文档里有大量无关内容,比如页眉、页脚、表格线,还有重复段落。我们写了个清洗脚本,用正则表达式匹配和删除。但正则这东西,写起来容易,调试起来要命。差点没把自己绕进去,因为有些文档的格式不统一,一个正则覆盖不了所有情况。比如,删除空行,一开始用^\s*$,结果把一些缩进的代码段也删了。后来改成更保守的方法,只删纯空行。
清洗完的数据,大概有10万条文本片段。接下来是标注成问答对。客户提供了一些示例问题,但不够用。我们团队手动标了1000条,然后想用大模型自动扩展。我用GPT-4 API生成了更多问答对,但这里有个大坑:生成的数据有偏见,模型容易重复相似的问题。我试了不同的提示词,比如“生成多样化的问答对”,效果还是不行。最后,我结合规则和模型,先提取关键实体,再围绕实体生成问题。
参数说明
这步的代码我放在下面,参数调了好几次。我用的是GPT-4 API,具体配置如下:
| 参数 | 值 | 说明 |
|---|---|---|
| model | gpt-4-turbo | 我用这个版本,生成质量稳定点,虽然贵但值 |
| temperature | 0.7 | 调高了点,避免输出太死板,试过0.5和0.9,0.7折中 |
| max_tokens | 500 | 限制长度,不然API贵死,实际平均输出300 token |
| top_p | 1.0 | 默认值,没动过,我觉得够用了 |
| frequency_penalty | 0.0 | 没加惩罚,怕影响多样性 |
标注数据攒到5万条,我开始做质量检查。第一次没成功,因为有些问答对不匹配——问题问的是A,答案讲的是B。我写了个检查脚本,用句子相似度计算(比如用sentence-transformers库)过滤低分对。但计算量太大,5万条数据跑下来要2小时。我优化了一下,只抽样检查,然后人工复核。这里我一般不用全自动,容易漏掉上下文相关的错误。
调用方式
整个流水线,封装成了几个Python脚本,通过命令行调用。我们一般用模块化设计,每个步骤一个脚本,这样好调试。主流程大概这样调用:
# 1. 提取文本
python extract_text.py --input_dir ./raw_docs --output_dir ./extracted_text
# 输出:每个文件一个.txt,放在extracted_text目录
# 2. 清洗数据
python clean_text.py --input_dir ./extracted_text --output_dir ./cleaned_text
# 这里会删掉页眉页脚,处理编码问题
# 3. 标注问答对(半自动)
python annotate_qa.py --input_dir ./cleaned_text --output_file ./qa_pairs.jsonl --use_gpt true
# 用GPT-4生成,结合手动标注,输出JSONL格式
# 4. 质量检查
python quality_check.py --input_file ./qa_pairs.jsonl --output_file ./filtered_qa.jsonl
# 基于相似度过滤,阈值设0.7,低于的丢到review目录人工看
# 5. 转训练格式
python convert_to_training.py --input_file ./filtered_qa.jsonl --output_file ./train_data.jsonl
# 转成Llama 3需要的instruction/input/output格式
每个脚本都有--help看参数,习惯这么写,免得自己忘了。跑全流程的话,用个shell脚本串起来,但建议分步跑,因为中间可能出错要回滚。
数据准备好了,得转成训练格式。我们微调的是Llama 3模型,需要JSONL格式,每条记录包含instruction、input、output。我一开始用简单的字典转JSON,但发现编码问题——有些文本包含特殊字符,JSON序列化失败。错误日志长这样:UnicodeEncodeError: 'ascii' codec can't encode character '\u2019' in position 42。解决方法是强制用UTF-8编码。
# 换数据为JSONL格式,用于Llama 3微调
import json
def convert_to_jsonl(data_list, output_path):
with open(output_path, 'w', encoding='utf-8') as f:
for item in data_list:
# 这里得处理特殊字符,不然写入会报错
record = {
"instruction": item["question"],
"input": "", # 我们场景不需要额外输入
"output": item["answer"]
}
json_line = json.dumps(record, ensure_ascii=False) # 关键:ensure_ascii=False
f.write(json_line + '\n')
# 跑的时候,一个文件大概1GB,我分了5个批次处理,避免内存爆掉
整个流水线跑完,我生成了最终的数据集,大概8万条问答对。训练前,我做了个简单统计:数据长度分布、问题类型比例。然后开始微调。训练过程中,我盯着loss曲线——前几个epoch降得很快,但到第10轮左右就平了。我当时以为过拟合了,赶紧停了训练,用验证集检查。结果发现,模型在训练集上表现很好,但新问题答得一般。复盘时发现,数据多样性还是不够,可能得加更多来源。
上线后评估
模型上线后,我们设了几个观察指标:回答准确率、响应时间、用户反馈率。头两天还行,准确率大概85%,响应时间平均2秒。但第三天有个用户反馈,模型对技术术语解释不清,比如“Kubernetes Pod网络策略”这种,回答很模糊。我一看日志,那部分数据我们标得少,训练集里相关问答对不到100条。所以赶紧补数据,重新微调了一版。
评估时,我用了一个测试集500条问题,人工打分。第一版得分70/100,主要扣在技术细节上。第二版补数据后,提到85/100。但说实话,这分数可能虚高,因为测试集没覆盖所有场景。我现在每周跑一次抽样检查,持续优化数据。
常见坑
踩了这么多坑,我总结几个常见的,你遇到了可能省点时间:
- 编码问题:文本里的特殊字符(比如弯引号、emoji)处理不好,JSON序列化直接崩。一定要用
ensure_ascii=False和UTF-8编码。 - OCR精度:扫描PDF别指望一次搞定,dpi调高、语言包装全,还得后处理纠错。我试过用
spellchecker库,但效果一般,后来主要靠规则过滤。 - 标注偏见:用大模型生成数据,容易重复相似模式。我现在的做法是混合来源——手动标一些,规则生成一些,模型生成一些,再打乱。
- 数据量太大:一次性处理所有数据,内存和CPU都扛不住。我分了批次,用
multiprocessing并行,但调试更麻烦。建议先小规模跑通。 - 格式转换错误:不同模型要不同格式(比如Alpaca、Llama、ChatGLM),转换脚本得测试透。我第一次就把
input字段漏了,模型训练报错。
工具上,pytesseract看着土但好用,sentence-transformers做质量检查挺顺手。如果你要做类似的事情,我的建议是:先小规模试跑,别一次性处理所有数据;多留点时间给清洗和标注,这步最容易出错;格式转换时,编码问题得提前想到。大概就这样,可能要看你的具体数据情况。