用Python标准库给JSONL做导入前体检:BOM、重复字段与行号定位
在把 JSONL 文件交给训练平台之前,可以先在本地做一次只读检查。本文给出一个不依赖第三方库的小工具,检查范围包括逐行 JSON 解析、顶层对象类型、重复字段、非标准常量,以及一组自定义的 Alpaca 风格字段要求。
它是导入前的辅助工具,不是任何云平台的完整数据规范实现。字段要求需要按实际任务调整。本文使用自建测试数据运行验证,不涉及真实训练样本。
1. 先约定检查范围
本例采用以下规则:
- 使用 UTF-8 读取文件,并兼容文件开头的 UTF-8 BOM。
- 逐行处理;空白行单独计数,不算有效样本。
- 每条非空记录的顶层必须是 JSON 对象。
- instruction 和 output 必须是非空字符串;input 可省略,存在时必须是字符串。
- 拒绝重复字段,以及 NaN、Infinity 等非标准 JSON 常量。
- 不修改原文件,只返回计数及前10条错误示例。
这里的“有效”仅表示满足上述局部规则,并不代表样本语义正确、回答质量合格或一定能通过平台全量校验。
2. 为什么不只调用一次 json.loads
JSONL 由多条 JSON 记录组成,按行检查能保留原文件中的行号。有一行失败时,也可以继续统计后面的数据。
Python 的默认 JSON 解码会接受一些不适合严格数据交换的输入。例如,重复字段通常会留下后一个值,NaN 也会被默认解析器接受。因此,本例使用 object_pairs_hook 检查重复字段,并通过 parse_constant 显式拒绝非标准常量。
utf-8-sig 用于兼容文件开头的 BOM,不会自动处理任意位置插入的 BOM;这类异常仍应回到原文件排查。
3. 可直接保存运行的代码
把下面代码保存为 jsonl_precheck.py:
"""只读检查 Alpaca 风格 JSONL 样例,不修改文件。"""
import json
from pathlib import Path
def unique_object(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValueError(f"重复字段:{key}")
result[key] = value
return result
def reject_constant(name):
raise ValueError(f"不接受非标准 JSON 常量:{name}")
def validate_jsonl(path):
valid = invalid = blank = 0
examples = []
with Path(path).open(encoding="utf-8-sig") as stream:
for line_number, line in enumerate(stream, 1):
if not line.strip():
blank += 1
continue
try:
item = json.loads(line, object_pairs_hook=unique_object,
parse_constant=reject_constant)
if not isinstance(item, dict):
raise ValueError("记录必须是 JSON 对象")
for key in ("instruction", "output"):
if not isinstance(item.get(key), str) or not item[key].strip():
raise ValueError(f"{key} 必须是非空字符串")
if "input" in item and not isinstance(item["input"], str):
raise ValueError("input 必须是字符串")
except (ValueError, TypeError) as error:
invalid += 1
if len(examples) < 10:
examples.append({"line": line_number, "reason": str(error)})
else:
valid += 1
return {"valid": valid, "invalid": invalid, "blank": blank,
"error_examples": examples}
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("path")
args = parser.parse_args()
print(json.dumps(validate_jsonl(args.path), ensure_ascii=False, indent=2))
运行方式:
python jsonl_precheck.py sample.jsonl
工具只读取文件。错误原因里记录行号、字段名或解析错误,不会主动打印整条样本。不过,如果数据包含敏感字段名,日志仍需要按自己的数据管理要求保存。
4. 用8行自建数据核对结果
本次在 Python 3.11 中创建了一份带 UTF-8 BOM 的临时文件,8个物理行依次为:
| 行号 | 内容类型 | 本例预期 |
|---|---|---|
| 1 | instruction/output 均为字符串的合法对象 | 有效 |
| 2 | 空行 | 计入 blank |
| 3 | 不完整 JSON:{broken |
语法错误 |
| 4 | 顶层数组:[] |
类型错误 |
| 5 | 只有 instruction,缺少 output | 字段错误 |
| 6 | output 为数字7 | 字段类型错误 |
| 7 | 同一对象重复写了两次 output | 重复字段错误 |
| 8 | output 为 NaN | 非标准常量错误 |
本地实际返回的计数是:
{"valid":1,"invalid":6,"blank":1}
错误示例中的行号依次是3、4、5、6、7、8。程序没有因为第3行失败而终止,后续各类问题都被单独记录。演示结束后临时文件被清理,原有文件未被修改。
5. 把规则和解析分开,后续更好维护
如果业务允许空 output,或者字段名称并非 instruction/input/output,就应该调整字段检查部分。不要为了让统计数字变好看而自动填入空字符串、删除报错样本或改写原文件。修正前应先明确任务的数据约定。
重复字段检查会作用于嵌套对象,而不只检查顶层。若需要输出完整的嵌套字段路径,可以在后续版本中单独设计路径跟踪;当前示例仅报告重复的字段名。
本工具还没有检查重复样本、token长度、训练集与验证集泄漏、个人信息残留或样本质量,也没有限制单行大小、嵌套深度。逐行读取减少了整文件一次载入的需求,但不能据此认为它适合处理任意大小的不可信输入。文件编码或磁盘读取异常也会直接抛出,需由调用方处理。
对于一个小型训练数据准备流程,这种“先解析、再校验字段、保留行号、最后汇总”的组织方式,能让问题定位更具体,也方便把后续规则逐项加入,而无需一开始就引入完整的数据处理框架。
本文示例代码为本次编写并验证,文章使用 AI 辅助整理。
- 点赞
- 收藏
- 关注作者
评论(0)