如何配合UTF-8编码正确使用ByteLevelBPETokenizer解决分词乱码问题
问题原因
乱码是ByteLevelBPETokenizer的固有逻辑导致,不是文件编码读取错误:
- 字节级BPE的最小处理单位是单字节,而非UTF-8字符。UTF-8编码下非ASCII字符会占用2-4个字节,这类字符会在预处理阶段被拆成多个独立字节,再参与BPE合并。
- 为了让所有字节都以可见字符串形式展示,
ByteLevel逻辑会把0x80~0xFF范围的字节映射到可打印Unicode字符区间,你看到的Ìģ就是拆分后的单字节映射后的结果。 - 你单独对单个token调用解码方法必然得到乱码:多字节UTF-8字符的字节可能被拆分到不同token中,单个token仅包含字符的部分字节,不满足UTF-8解码的完整字节要求,根本无法还原为正确字符。
解决方案
方案1:正确使用ByteLevelBPE解码(工业场景推荐)
不需要更换分词器实现,只要避免单独解码单个token,对完整的token id序列调用内置解码方法,分词器会自动把拆分的字节重新拼接为合法UTF-8字符:
# 错误写法:单独解码单个token,必然乱码 # print(decoder.decode(tokens.tokens[1])) # 正确写法:对完整编码结果解码 line = "ítwa" tokens = tokenizer.encode(line) print(tokenizer.decode(tokens.ids)) # 输出原始正确文本
如果需要做token和原文的位置对齐,使用tokens.offsets获取每个token对应原文的字符区间即可,不要直接读取tokens.tokens的原始映射字符串。
方案2:实现以UTF-8字符为原子单位的BPE
如果你明确要求分词最小单位是单个UTF-8字符,不要使用字节级BPE实现,改用基础BPE搭配字符级预分词即可:
from tokenizers import Tokenizer from tokenizers.models import BPE from tokenizers.trainers import BpeTrainer from tokenizers.pre_tokenizers import CharLevel # 初始化标准BPE模型 tokenizer = Tokenizer(BPE(unk_token="<unk>")) # 设置预分词规则为字符级,保证最小拆分单元是单个UTF-8字符 tokenizer.pre_tokenizer = CharLevel() trainer = BpeTrainer( vocab_size=52_000, min_frequency=2, special_tokens=["<s>", "<pad>", "</s>", "<unk>", "<mask>"] ) # 训练分词器 tokenizer.train(files=["my_corpus.txt"], trainer=trainer) # 测试编码 line = "ítwa" tokens = tokenizer.encode(line) print(tokens.tokens) # 所有token均为合法UTF-8字符/合并后的字符组合,无乱码 print(tokenizer.decode(tokens.ids)) # 解码输出原始文本
注意:字符级BPE相比字节级BPE词表利用率更低,且无法处理训练语料中未出现过的生僻字符,通用场景下优先选择方案1。
内容的提问来源于stack exchange,提问作者kloop
相关产品推荐
相关产品推荐

