“Token→整数索引” 的完整实现步骤

这一步的核心是构建词汇表(Vocabulary/Vocab),再通过词汇表完成 Token 到索引的映射。整个流程分为 4 步,我们结合具体文本例子讲解。

前置条件:已完成Token 化

假设我们有一批文本数据,已经完成 Token 化(中文按字拆分):

复制代码
文本1:"我爱吃苹果" → Token序列:["我", "爱", "吃", "苹果"]
文本2:"我爱香蕉" → Token序列:["我", "爱", "香蕉"]
文本3:"苹果好吃" → Token序列:["苹果", "好", "吃"]

一、步骤 1:统计所有 Token,生成 Token 频率字典

遍历所有文本的 Token 序列,统计每个 Token 出现的次数 ------ 这一步是为了筛选高频 Token,构建核心词汇表

频率统计结果:

Token 出现次数
2
2
2
苹果 2
香蕉 1
1

二、步骤 2:定义特殊 Token,预留索引位置

在构建词汇表前,需要先为 <PAD> <UNK> <SOS> <EOS>特殊 Token 分配固定索引 ------ 这是工程上的约定,避免特殊 Token 的索引被普通 Token 占用。

通常的索引分配规则(优先级:特殊 Token > 普通 Token):

特殊 Token 索引值 作用
<PAD> 0 填充,统一序列长度
<UNK> 1 替换未登录词
<SOS> 2 标记序列开始
<EOS> 3 标记序列结束

三、步骤 3:构建词汇表(Vocab),分配普通 Token 索引

词汇表是一个 Python 字典,键是 Token,值是对应的整数索引。构建规则分两种场景:

场景 A:无词汇量限制(小数据集)

直接将所有普通 Token 加入词汇表,索引从 特殊 Token 的最大索引 + 1 开始分配。

  • 示例:特殊 Token 最大索引是 3 → 普通 Token 索引从 4 开始

    普通 Token 索引值
    4
    5
    6
    苹果 7
    香蕉 8
    9
  • 最终词汇表:

    复制代码
    vocab = {
        "<PAD>":0, "<UNK>":1, "<SOS>":2, "<EOS>":3,
        "我":4, "爱":5, "吃":6, "苹果":7, "香蕉":8, "好":9
    }

场景 B:有词汇量限制(大数据集)

真实场景中,文本数据的 Token 数量可能达数百万(如中文词汇量超 10 万),无法全部加入词汇表 ------ 此时需要按频率筛选 Top-N 个 Token ,低频 Token 会被替换为 <UNK>

  • 示例:设定词汇量 vocab_size=8(只要8个Token,包含 4 个特殊 Token + 4 个普通 Token)

    1.按频率排序普通 Token:我=2 爱=2 吃=2 苹果=2 香蕉=1 好=1

    2.取 Top-4 普通 Token:我、爱、吃、苹果

    3.分配索引:从 4 开始 → 我:4 爱:5 吃:6 苹果:7

    4.低频 Token 香蕉、好 被归为 <UNK>(索引 1)

  • 最终词汇表(大小 = 8):

    复制代码
    vocab = {
        "<PAD>":0, "<UNK>":1, "<SOS>":2, "<EOS>":3,
        "我":4, "爱":5, "吃":6, "苹果":7
    }

四、步骤 4:Token→整数索引映射(核心操作)

遍历每个文本的 Token 序列,通过词汇表字典的 get() 方法完成映射,规则如下:

若 Token 在词汇表中 → 取对应的索引;

若 Token 不在词汇表中 → 取 <UNK> 的索引(1)。

示例(基于场景 B 的词汇表)
原始 Token 序列 映射后的整数索引序列 说明
["我", "爱", "吃", "苹果"] [4,5,6,7] 所有 Token 都在词汇表中
["我", "爱", "香蕉"] [4,5,1] "香蕉" 不在词汇表 → 映射为 <UNK> 的索引 1
["苹果", "好", "吃"] [7,1,6] "好" 不在词汇表 → 映射为 <UNK> 的索引 1
关键代码(映射逻辑)
复制代码
token_seq = ["我", "爱", "香蕉"]
index_seq = [vocab.get(token, vocab["<UNK>"]) for token in token_seq]
print(index_seq)  # 输出:[4,5,1]

五、关键细节与工程优

1. 反向映射:索引→Token

训练完成后,若需要将模型输出的索引序列转回文本,需要构建反向词汇表(索引为键,Token 为值):

复制代码
reverse_vocab = {v: k for k, v in vocab.items()}
index_seq = [4,5,6,7]
token_seq = [reverse_vocab[idx] for idx in index_seq]
print(token_seq)  # 输出:["我", "爱", "吃", "苹果"]

2. 未登录词(OOV)的处理原则

训练阶段 :低频 Token 主动替换为 <UNK>,让模型学习 <UNK> 的语义;

推理阶段 :所有不在词汇表中的 Token 都替换为 <UNK>,避免索引错误;

优化方案 :使用子词分词(如 BPE、WordPiece) ,将未登录词拆分为子词(子词大概率在词汇表中),减少 <UNK> 的出现。(非常妙)

3. 索引值的分配原则

特殊 Token 索引必须固定 :尤其是 <PAD> 的索引(通常为 0),后续掩码(Mask)和损失计算需要依赖这个固定值;

普通 Token 索引无需连续 :但连续分配可以节省内存(嵌入层的权重矩阵大小为 [vocab_size, embedding_dim],索引不连续会浪费空间)。

4. 词汇表的保存与加载

真实项目中,词汇表需要保存为文件(如 JSON),避免每次训练都重新构建:

复制代码
import json
# 保存词汇表
with open("vocab.json", "w", encoding="utf-8") as f:
    json.dump(vocab, f, ensure_ascii=False, indent=2)
# 加载词汇表
with open("vocab.json", "r", encoding="utf-8") as f:
    vocab = json.load(f)

六、完整代码实现(PyTorch 实战)

我们结合真实流程,实现 "Token 化→构建词汇表→Token→索引映射" 的完整代码:

复制代码
import json
from collections import Counter

# ===================== 1. 准备数据 =====================
corpus = [
    "我爱吃苹果",
    "我爱香蕉",
    "苹果好吃"
]

# ===================== 2. Token 化(中文按字拆分) =====================
def tokenize(text):
    return list(text)  # 简单按字拆分,实际可用 jieba 分词

token_seqs = [tokenize(text) for text in corpus]
print("Token 序列列表:", token_seqs)
# 输出:[['我', '爱', '吃', '苹果'], ['我', '爱', '香蕉'], ['苹果', '好', '吃']]

# ===================== 3. 统计 Token 频率 =====================
all_tokens = []
for seq in token_seqs:
    all_tokens.extend(seq)
token_freq = Counter(all_tokens)
print("Token 频率:", token_freq)
# 输出:Counter({'我':2, '爱':2, '吃':2, '苹果':2, '香蕉':1, '好':1})

# ===================== 4. 构建词汇表 =====================
# 4.1 定义特殊 Token
special_tokens = ["<PAD>", "<UNK>", "<SOS>", "<EOS>"]
# 4.2 设定词汇量
vocab_size = 8  # 4个特殊 Token + 4个普通 Token
# 4.3 筛选 Top-N 普通 Token
top_tokens = [token for token, _ in token_freq.most_common(vocab_size - len(special_tokens))]
# 4.4 分配索引
vocab = {}
# 先分配特殊 Token 索引
for idx, token in enumerate(special_tokens):
    vocab[token] = idx
# 再分配普通 Token 索引
start_idx = len(special_tokens)
for idx, token in enumerate(top_tokens):
    vocab[token] = start_idx + idx

print("最终词汇表:", vocab)
# 输出:{'<PAD>':0, '<UNK>':1, '<SOS>':2, '<EOS>':3, '我':4, '爱':5, '吃':6, '苹果':7}

# ===================== 5. Token→整数索引映射 =====================
def token_to_index(token_seq, vocab):
    return [vocab.get(token, vocab["<UNK>"]) for token in token_seq]

index_seqs = [token_to_index(seq, vocab) for seq in token_seqs]
print("索引序列列表:", index_seqs)
# 输出:[[4,5,6,7], [4,5,1], [7,1,6]]

# ===================== 6. 保存词汇表 =====================
with open("vocab.json", "w", encoding="utf-8") as f:
    json.dump(vocab, f, ensure_ascii=False, indent=2)

七、常见误区与注意事项

误区 1:索引值越大,Token 越重要 → 否:索引值只是 Token 的 "编码",和 Token 的语义重要性无关。语义的重要性由后续嵌入层的向量值决定
误区 2:词汇表越大越好 → 否:词汇表过大会导致嵌入层权重矩阵过大(如 vocab_size=10万embedding_dim=512 → 权重矩阵大小为 10万×512=5120万 参数),增加训练难度和内存消耗。
注意事项:特殊 Token 的索引必须固定 → 尤其是 <PAD> 的索引(通常为 0),后续使用 CrossEntropyLoss(ignore_index=0) 时,必须保证这个索引对应 <PAD>,否则会计算填充位置的损失,干扰模型训练。

八、总结

Token→整数索引 的核心是构建词汇表 ,流程可简化为:Token 化 → 统计频率 → 定义特殊 Token → 筛选普通 Token → 分配索引 → 映射转换

这一步的本质是将人类可读的符号系统,转化为机器可读的数值系统,是文本数据进入神经网络的必经之路。

九、词汇表构建的进阶技巧清单

词汇表(Vocab)是文本与神经网络之间的核心桥梁,基础构建方法适用于小数据集,而在大规模、复杂文本任务(如大语言模型预训练、多语言处理)中,需要结合工程优化、语义增强、效率提升的进阶技巧,以下是全面且可落地的方法清单:

一、 分词策略优化:从 "字 / 词级" 到 "子词级",减少未登录词

基础的字 / 词级分词会产生大量未登录词(OOV),而子词分词 能将未登录词拆分为模型见过的子词,大幅降低 <UNK> 比例,是工业界的主流方案。

1. 核心子词算法(按落地难度排序)

算法名称 核心原理 适用场景 优势 工具实现
BPE(字节对编码) 1. 统计词汇中字符对的频率;2. 迭代合并频率最高的字符对,生成新子词;3. 直到达到预设子词数量 英文 / 中文(拼音)、大规模语料 简单高效,适合预训练模型 Hugging Face Tokenizers、SentencePiece
WordPiece 类似 BPE,但合并规则改为 "使训练集的对数似然最大" 英文(如 BERT 的分词) 子词语义更连贯 TensorFlow Text、Hugging Face Transformers
Unigram 1. 初始化包含所有可能子词的集合;2. 迭代删除对模型损失影响最小的子词;3. 保留最优子词集合 多语言处理、对 OOV 要求高的场景 子词粒度更灵活,支持多语言 SentencePiece

2. 中文子词分词的特殊处理

中文无天然空格分隔,直接用子词算法效果有限,需结合预处理:

  • 方案 1:拼音子词 → 将汉字转为拼音(如 "苹果"→ping guo),再用 BPE 分词;

  • 方案 2:汉字笔画子词 → 将汉字拆为笔画(如 "苹"→艹 平),适合生僻字多的场景;

  • 方案 3:预分词 + 子词 → 先用 jieba 等工具做粗粒度分词,再对分词结果做子词拆分(如 "自动驾驶"→自动 驾驶→再拆为子词)。

3. 实战优势对比

分词方式 OOV 比例 语义保留度 计算效率 适用模型规模
字级分词 低(但语义粒度细) 低(单个字无完整语义) 小模型(如文本分类)
词级分词 高(生僻词多) 高(完整词语义) 中等模型(如情感分析)
子词分词 极低(几乎无 OOV) 中高(子词兼顾粒度和语义) 大模型(如预训练 LLM)

二、 词汇表大小优化:平衡 "覆盖度" 与 "计算成本"

词汇表大小(vocab_size)是核心超参数:过大增加嵌入层参数规模,过小导致 OOV 增多。

1. 最优词汇表大小的选择原则

  • 小任务(如文本分类)vocab_size=1万~5万 → 足够覆盖核心词汇,参数规模小;

  • 中等任务(如机器翻译)vocab_size=5万~30万 → 平衡 OOV 和计算量;

  • 大模型预训练(如 LLM)vocab_size=30万~100万 → 覆盖更多生僻词和子词,提升模型泛化能力。

2. 动态词汇表:按需扩展词汇

固定词汇表无法适配新领域文本(如医疗、法律),动态词汇表方案可解决:

  1. 领域词汇注入 → 从领域语料中提取专业词汇(如医疗领域的 "CT 扫描""靶向药"),直接加入核心词汇表;

  2. 增量子词训练 → 用新领域语料继续训练 BPE 模型,生成新子词并合并到原词汇表;

  3. 注意事项 → 新增词汇的嵌入向量需初始化(可采用 "平均嵌入法":类似语义词汇的向量平均值),再通过微调更新。

3. 减少词汇表冗余的技巧

  • 低频 Token 过滤:删除训练集中出现次数 < N(如 N=5)的 Token,这些 Token 对模型贡献极小;

  • Token 去重:合并同义 Token(如 "新冠" 和 "新型冠状病毒"),减少词汇表冗余;

  • 特殊 Token 精简 :仅保留任务必需的特殊 Token(如分类任务无需 <SOS>/<EOS>)。

三、 嵌入层与词汇表的协同优化:提升语义表示能力

词汇表的最终价值是通过嵌入层转化为语义向量,以下技巧可提升向量质量:

1. 特殊 Token 的嵌入向量初始化策略

特殊 Token 无实际语义,需针对性初始化,避免干扰模型:

特殊 Token 初始化方法 核心目的
<PAD> 全 0 向量(torch.zeros(embedding_dim) 确保填充位置无语义信息
<UNK> 随机初始化 + 训练中学习 让模型学习未登录词的通用语义
<SOS>/<EOS> 均值初始化(所有普通 Token 向量的平均值) 赋予 "序列边界" 的基础语义
<MASK>(BERT 用) 随机初始化 + 掩码任务微调 适配掩码语言模型的预训练目标

2. 预训练词向量融合:冷启动词汇表语义

直接随机初始化嵌入层,模型需要大量数据才能学习到好的语义;融合预训练词向量可实现 "冷启动":

  • 步骤 1:用 Word2Vec、GloVe 等工具预训练词向量(基于大规模通用语料);

  • 步骤 2:构建词汇表时,若 Token 存在预训练词向量,直接赋值给嵌入层权重;

  • 步骤 3:未匹配的 Token(如特殊 Token、生僻词)随机初始化;

  • 步骤 4:训练时选择 "冻结预训练向量" 或 "微调预训练向量":

    • 小数据集任务 → 冻结(避免过拟合);

    • 大数据集任务 → 微调(让向量适配任务语义)。

3. 词汇表的分层设计:适配多粒度语义

复杂任务(如阅读理解)需要同时捕捉 "字、词、短语" 的语义,可采用分层词汇表:

  • 底层:字级 Token(如 "我""爱""吃")→ 捕捉细粒度语义;

  • 中层:词级 Token(如 "苹果""香蕉")→ 捕捉基础语义;

  • 高层:短语级 Token(如 "爱吃苹果")→ 捕捉组合语义;

  • 实现方式 → 嵌入层输出不同粒度 Token 的向量,再通过注意力机制融合为最终表示。

四、 工程化技巧:词汇表的保存、加载与跨平台兼容

在真实项目中,词汇表的复用性和兼容性至关重要,以下是工程化最佳实践:

1. 标准化存储格式

避免自定义格式,优先选择跨平台的存储格式:

存储格式 优点 适用场景 工具
JSON 人类可读,支持字符串键值对 中小规模词汇表(<50 万) Python json
Protocol Buffers(PB) 体积小,序列化 / 反序列化速度快 大规模词汇表(>50 万) Google Protobuf
SentencePiece Model 内置分词 + 词汇表,支持跨语言 子词分词场景 SentencePiece

存储内容必须包含

  • 正向映射:token → index

  • 反向映射:index → token

  • 元信息:词汇表大小、特殊 Token 列表、分词算法版本。

2. 跨框架兼容方案

不同深度学习框架(PyTorch/TensorFlow)的嵌入层输入要求一致,但词汇表加载需适配:

  • 方案:定义统一的词汇表加载类,输出框架兼容的索引张量;

  • 示例

    复制代码
    class VocabLoader:
        def __init__(self, vocab_path):
            self.vocab = self._load_vocab(vocab_path)
            self.reverse_vocab = {v:k for k,v in self.vocab.items()}
        
        def _load_vocab(self, path):
            # 支持JSON/PB格式加载
            pass
        
        def token_to_idx(self, token_seq, framework="pytorch"):
            idx_seq = [self.vocab.get(t, self.vocab["<UNK>"]) for t in token_seq]
            if framework == "pytorch":
                return torch.tensor(idx_seq, dtype=torch.long)
            elif framework == "tensorflow":
                return tf.convert_to_tensor(idx_seq, dtype=tf.int32)

3. 词汇表的版本管理

当语料更新或分词算法调整时,词汇表会发生变化,需做好版本管理:

  • 命名规范vocab_<任务>_<分词算法>_<vocab_size>_<版本号>.json(如vocab_chatbot_bpe_30w_v1.0.json);

  • 版本日志:记录每个版本的变更(如 "v1.1:新增医疗领域词汇 1000 个");

  • 回滚机制:保存历史版本,避免新版本引入问题无法回退。

五、 特殊场景的词汇表定制技巧

1. 多语言任务词汇表

  • 方案 1:共享词汇表 → 用 Unigram 算法训练多语言语料,生成统一子词词汇表(如 XLM 模型),优势是支持跨语言语义对齐;

  • 方案 2:独立词汇表 + 共享嵌入层 → 每种语言构建独立词汇表,但共享同一嵌入层权重,适合语言差异大的场景(如中文 + 英文)。

2. 低资源语言任务词汇表

低资源语言(如少数民族语言)语料少,直接训练词汇表效果差:

  • 迁移学习 → 用高资源语言(如中文)的词汇表和嵌入层,微调适配低资源语言;

  • 跨语言子词 → 将低资源语言与高资源语言混合训练 BPE 模型,利用高资源语言的子词提升覆盖度。

3. 生成式任务词汇表

文本生成任务(如续写、翻译)对词汇表的要求更高:

  • 必须包含 <SOS>/<EOS> 标记序列边界;

  • 词汇表需覆盖高频生成词汇(如标点符号、连接词);

  • 可加入控制 Token (如 <POSITIVE> <NEGATIVE>),引导模型生成特定情感的文本。

六、 避坑指南:词汇表构建的常见错误

  1. 错误 1:词汇表过大 / 过小→ 解决:通过 "语料覆盖率曲线" 选择最优大小(覆盖率 = 词汇表覆盖的 Token 数 / 总 Token 数,通常选择覆盖率 99% 时的最小词汇表大小);

  2. 错误 2:特殊 Token 索引不固定 → 解决:特殊 Token 必须放在词汇表最前面,索引固定(如 <PAD>=0 <UNK>=1),避免训练和推理时索引不一致;

  3. 错误 3:忽略词汇表的领域适配→ 解决:通用词汇表在特定领域(如医疗)效果差,需注入领域词汇并微调嵌入层;

  4. 错误 4:子词分词后未做还原 → 解决:生成任务中,子词序列需通过 "反向分词" 还原为完整文本(如子词 ping@@ guopingguo苹果)。

相关推荐
deephub1 小时前
多智能体强化学习(MARL)核心概念与算法概览
人工智能·机器学习·强化学习·多智能体
张小凡vip2 小时前
数据挖掘(五) -----JupyterHub 使用gitlab的账号体系进行认证
人工智能·数据挖掘·gitlab
叫我:松哥2 小时前
基于神经网络算法的多模态内容分析系统,采用Flask + Bootstrap + ECharts + LSTM-CNN + 注意力机制
前端·神经网络·算法·机器学习·flask·bootstrap·echarts
阿杰 AJie2 小时前
Java Stream API详细用法
java·windows·python
AI街潜水的八角2 小时前
基于keras框架的LeNet/AlexNet/Vgg16深度学习神经网络花卉/花朵分类识别系统源码
深度学习·神经网络·keras
蒜香拿铁2 小时前
【第五章】python判断语句if
java·服务器·python
vx_bisheyuange2 小时前
基于SpringBoot的知识竞赛系统
大数据·前端·人工智能·spring boot·毕业设计
Ryan老房2 小时前
从LabelImg到TjMakeBot-标注工具的进化史
人工智能·yolo·目标检测·计算机视觉·ai
Coding茶水间2 小时前
基于深度学习的吸烟检测系统演示与介绍(YOLOv12/v11/v8/v5模型+Pyqt5界面+训练代码+数据集)
开发语言·人工智能·深度学习·yolo·目标检测·机器学习