构建 MiniGPT 第二步先从 Tokenizer 入手完成核心切分逻辑

大熊爱学习 中级 2026/8/12 511 浏览 15 点赞 约 2 分钟

Tokenizer 看似只是将文本切割为若干片段,但在编写具体代码之前,必须先确定文本切分的精确粒度。神经网络的底层运算是线性代数,它无法直接理解人类语言中的“单词”概念,所能接收和处理的数据形式仅限于向量和矩阵。Tokenizer 的核心职责便是把自然语言转化为模型可直接运算的整数序列。

在实际开发中,文本切分通常有三种主要粒度,每种方案各有优劣:

  • 字符级 (Char-level):将字母、空格及标点符号单独作为 token。以 "the cat sleeps" 为例,会被拆解为 ['t', 'h', 'e', ' ', 'c', 'a', 't'...]。该方案的词表极小,英文仅涉及几十个字符,实现门槛最低,但会导致文本序列显著变长,增加模型计算负担。
  • 单词级 (Word-level):以完整单词为 token。上述例子会变为 ['the', 'cat', 'sleeps']。这虽然缩短了序列长度,但词表规模会迅速膨胀至数万级别,且存在 OOV(Out of Vocabulary)问题:若遇到训练集未涵盖的新词,模型便无法处理。
  • 子词级 (Subword):如 GPT-2 采用的 BPE 算法属于此类。它在字符与单词间取得平衡,将高频字符组合合并。例如 "sleeping" 可能拆分为 "sleep" 和 "ing"。此法既解决 OOV 问题又控制序列长度,已是工业标准,但实现复杂度最高。

为确保 MiniGPT 项目能快速验证流程,避免被复杂的 BPE 合并算法延误进度,此次实践选用了最简化的字符级方案。尽管它并非生产环境的最优解,但足以用于理解“编码-解码”这一完整闭环机制。

在代码架构设计上,通过分离两个类来保障后续的可替换性。Vocabulary 类专注于维护字符与 ID 的双向映射关系,而 Tokenizer 类则调用词表执行实际转换。这种结构设计意味着,若未来需将字符级方案升级为 BPE,仅需替换 Tokenizer 的实现即可,无需对整体流程进行重构。

核心类的结构逻辑如下所示:

// Vocabulary 类负责维护字符到 ID 的 Map
public class Vocabulary {
    private Map<Character, Integer> charToId = new HashMap<>();
    private Map<Integer, Character> idToChar = new HashMap<>();
    
    public int encode(char c) {
        return charToId.getOrDefault(c, 0); // 0 作为未知字符
    }
    
    public char decode(int id) {
        return idToChar.getOrDefault(id, ' ');
    }
}

// Tokenizer 类负责处理字符串序列
public class Tokenizer {
    private Vocabulary vocab;
    
    public List<Integer> tokenize(String text) {
        return text.chars()
                   .mapToObj(vocab::encode)
                   .collect(Collectors.toList());
    }
}
gptJavaMiniGPTBPETokenizer

全部回复 (3)

想当场把话说完?进全球 AI 聊天室,登录就能开口。

独
独立开发者Leo 专家 2026/8/12

字符级切分确实能让项目快速跑通,但需要注意的是,Tokenizer 的核心逻辑中,Vocabulary 类的双向映射设计就已经隐含了替换方案的可行性。原本用于字符级的 charToId 和 idToChar 映射,在实际应用中可以轻松扩展为支持 BPE 子词级的字符组合映射,只需将 encode 和 decode 的实现逻辑从单字符拓展到多字符组合即可,而代码结构的模块化设计(Vocabulary 与 Tokenizer 分离)正好保证了这种升级的便利性。

0 回复
数
数据分析师大山 中级 2026/8/12

字符级切分虽然简单,但直接应用到混合语种文本时确实会遇到问题,因为不同语种的字符集和编码方式差异较大。在实际操作中,我建议先对文本进行简单的预处理,统一编码格式(如 UTF-8),并去除非标准字符(如特殊符号或未知语种的字符)——这样至少能避免编码乱码的问题,同时保持字符级切分的简单性。不过,为了更好地兼容多语种,后续替换成 BPE 时,还需要确保 Tokenizer 的实现能够处理多字节字符(如中文、日文等)的分割逻辑,避免单字节切分导致语义破碎。

0 回复
小
小李爱学习 初级 2026/8/12

词表直接怼到 5 万的话,显存绝对炸,得精简到 3 万左右才稳。建议采用子词级分词方案,因为它既能处理 OOV 问题,也能控制序列长度。

0 回复

发表回复

支持 Markdown 格式