先把语言对和数据格式定下来——Index-Translate 微调前的数据核对

文章导读
用 Index-Translate 微调自己的领域数据,脚本能跑通不等于训练目标对得上。动手前建议先核对三处:数据文件的格式(jsonl、tsv 还是平行文本目录)、语言对的标注写法、训练集与验证集的切分方式。这三处里任何一处和仓库的数据加载代码不一致,常见表现是训练不报错但 loss 不下降,或者验证分数看着正常,却对应不到你真正想要的语言方向。
📋 目录
  1. 壹 在仓库的数据加载代码里确认字段顺序与分隔方式
  2. 贰 把语言对标识统一成模型接受的写法
  3. 叁 写脚本统计每个语言对的样本条数
  4. 肆 切分训练集与验证集并检查交叉泄漏
  5. 伍 用一次小步数训练验证数据通路
A A

用 Index-Translate 微调自己的领域数据,脚本能跑通不等于训练目标对得上。动手前建议先核对三处:数据文件的格式(jsonl、tsv 还是平行文本目录)、语言对的标注写法、训练集与验证集的切分方式。这三处里任何一处和仓库的数据加载代码不一致,常见表现是训练不报错但 loss 不下降,或者验证分数看着正常,却对应不到你真正想要的语言方向。

处理方向:先在仓库的数据集类或数据加载脚本里抄下实际字段名与分隔方式,不要凭记忆写文件;再把所有语言对统一成同一套短代码与固定分隔符,并让文本里的语言标记与配置保持一致;接着用统计脚本看每个语言对的条数和句长分布,再做训练/验证切分与跨集重复句对检查;最后用极小步数跑一次训练,从日志确认数据被正确读取。适用于自建领域数据微调前的准备,不适用于直接复用已处理好的公开数据集。

在仓库的数据加载代码里确认字段顺序与分隔方式

第一步不是打开自己的数据文件,而是打开仓库里读数据的那个文件。通常是数据集类(带 __getitem__ 的定义)、load_dataset 调用处,或者名字里含 data/dataset 的脚本。在仓库根目录先定位:

grep -rn `--include`='*.py' -E 'load_dataset|jsonl|tsv|train_file|validation_file|def __getitem__' .

命中之后逐个打开,确认三件事:读的是哪种文件;字段是按键取值还是按列顺序取值;有没有默认语言对字段。jsonl 一般按键取值,字段顺序无关;tsv 常按列顺序取值,列插错位置不会报错但语义就反了;平行文本目录结构则靠文件后缀配对,两边的行数必须严格对齐。

# 下面是三种常见结构的示意,字段名以本地脚本实际写法为准
# jsonl
{"source": "...", "target": "...", "src_lang": "zh", "tgt_lang": "en"}
# tsv(无表头)
中文句子\t英文句子\tzh\ten
# 平行文本目录
data/train.zh
data/train.en

tsv 还要检查列数是否统一,用 awk -F'\t' '{print NF}' data.tsv | sort | uniq -c 看有没有某几行列数不一致。如果仓库脚本里硬编码了 source/target 之类的键名,你的文件就必须用同名键,不要自己另起一套命名再去改脚本。

把语言对标识统一成模型接受的写法

语言标识在数据里通常出现在三个位置:独立字段(如 src_lang/tgt_lang)、拼在文本开头的特殊前缀标记、或者文件名后缀。混用 zh、zh-CN、zh_CN、ZH 这类写法,会让模型把它们当成不同语言,训练目标被摊薄,多语言方向尤其容易出问题。

建议统一成小写 ISO 639-1 短码,方向固定为“源→目标”,并用同一个分隔符,例如统一写成 zh-en 而不是 en_zh 和 zh-EN 混排。检查用:cut -f3 data.tsv | sort | uniq -c(列号换成语言字段实际所在列),比逐个翻文件快。若语言对写在前缀标记里,前缀标记本身也要和模型配置里的语言标签对齐。

先把语言对和数据格式定下来——Index-Translate 微调前的数据核对

不确定模型接受哪种写法时,可以借助分词器行为反推,把配置里的标记名替换成实际值:

python -c "from transformers import AutoTokenizer; t=AutoTokenizer.from_pretrained('本地模型目录'); print(t.tokenize('<PAIR>'), t.convert_tokens_to_ids('<PAIR>'))"

如果标记被切成多个子词、或者 id 落在 unk 上,说明模型没见过这个标记,需要换用配置里已有的标记,或在允许的情况下把它加为特殊 token 并重训。这一步的结论要结合本地模型配置确认,不要只凭猜测。

写脚本统计每个语言对的样本条数

语言对写对了,还要看够不够。统计脚本的骨架如下,先改字段名常量和文件路径:

import json
from collections import defaultdict

SRC_KEY, TGT_KEY = 'source', 'target'   # 换成仓库实际字段名
SL_KEY, TL_KEY = 'src_lang', 'tgt_lang'

def load_jsonl(path):
    with open(path, encoding='utf-8') as f:
        for i, line in enumerate(f, 1):
            line = line.strip()
            if not line:
                continue
            try:
                yield i, json.loads(line)
            except json.JSONDecodeError as e:
                print('bad json at line', i, e)

stat = defaultdict(lambda: [0, 0, 0])
for i, obj in load_jsonl('train.jsonl'):
    pair = (obj[SL_KEY], obj[TL_KEY])   # 语言写在前缀里的,改成解析前缀
    stat[pair][0] += 1
    stat[pair][1] += len(obj[SRC_KEY])
    stat[pair][2] += len(obj[TGT_KEY])

for pair, (n, s, t) in sorted(stat.items(), key=lambda x: x[1][0]):
    print(pair, n, round(s / n, 1), round(t / n, 1))

输出表格字段建议为:语言对、条数、源句平均字符数、目标句平均字符数。用字符数只是为了快速看分布,要更准就把 len() 换成 tokenizer 编码后的长度。按条数升序打印,长尾一眼就能看到。

长尾怎么处理要看用途:想覆盖多语言就保留并适当提高采样权重;只关心少数方向就把其余方向裁掉,但把裁掉的语言对和条数记录下来,方便复现。条数极少的方向(个位数)通常不适合放进训练集,放进去也容易过拟合,建议挪到验证集或直接剔除,具体阈值结合你的数据集规模定。

切分训练集与验证集并检查交叉泄漏

切分规则有两种常见做法:按行切分时,同一句对只能进一侧,不能拆分;如果数据是文档级抓取的,建议按文档或按来源分组切分,同一文档的所有句对整体进训练集或整体进验证集。按行随机切分看似平稳,但同一段落的相似句子分居两侧,验证分数会虚高。

先把语言对和数据格式定下来——Index-Translate 微调前的数据核对

切分前先去重,切分后必须检查交叉泄漏:

def key_of(obj):
    return (obj[SRC_KEY].strip(), obj[TGT_KEY].strip())

def dedup(rows):
    seen, out = set(), []
    for r in rows:
        k = key_of(r)
        if k not in seen:
            seen.add(k)
            out.append(r)
    return out

train, valid = dedup(train), dedup(valid)
leak = {key_of(r) for r in train} & {key_of(r) for r in valid}
print('cross-split duplicates:', len(leak))
for k in list(leak)[:5]:
    print(k)

去重键默认是“源句+目标句”完全一致。如果数据里存在大小写、全半角、多余空格的差异,可以在 key_of 里先做归一化,但要清楚归一化会顺带合并掉一些本来不同的样本。检查出跨集重复时,处理方式通常是:从验证集删掉,或把这些样本回退到训练集,不要同时保留。验证集抽取比例按数据集规模定,小数据集宁可少抽一些。

用一次小步数训练验证数据通路

数据核对完成后,别直接开长训练。先覆写几个参数跑几十步:数据路径(train_file/validation_file 或数据集目录)、最大长度(源端与目标端各自的 max length,或统一 max_length)、步数(max_steps 设小值)、per_device_train_batch_size=1、logging_steps=1,并打开一次评估。目的是确认数据能被读进来、能构造成训练样本,不关心效果。

看日志时,几类报错指向不同问题:出现 KeyError 一般是字段名和脚本预期不一致;出现列数解包错误(too many values to unpack 一类)说明 tsv 列数或顺序不对;出现索引越界、或 tokenizer 提示输入类型不对,多是某一行字段为空、把 None 或列表传进了编码函数;loss 恒定为 nan,要优先怀疑数据里有空串或长度超限后标签被全部置为忽略。若日志显示样本数极少、几十步就跑完一轮,先回头确认数据路径指向的文件是不是空的或只有表头。

确认这些日志都干净、能正常打印出 loss 和评估结果之后,再换回真实步数正式训练。顺序上宁可多花十分钟做这次空跑,也不要等训练几小时才发现语言方向反了。