深入解析文本词汇处理代码——如何用有限词表实现无限表达
一、代码概述
这段代码的核心目标是将输入文本转换为 token_id 列表,同时处理词汇表外的词。通过 替换词表(replace_voc) 和 特殊标记(special_voc),实现对超出主词表(voc)的词汇的灵活编码,甚至支持 大词汇量分层编码(如 2 亿词)。其设计灵感可能来源于雅思写作中词汇扩展的技巧,如使用同义词、分层结构等。
二、关键变量解析
-
主词表(voc)
存储常用词的 ID,例如:voc = {"a": 1, "b": 2}
-
替换词表(replace_voc)
记录未登录词的动态 ID,按 前一个词 分组存储。例如:replace_voc = {"a": {"1": 1}, "hello": {"a": 1, "b": 3}}
- 键是前一个词(如 “hello”),值是当前词的 ID 映射(如 “world” 的 ID 为 4)。
-
特殊标记表(special_voc)
定义替换标记和分层编码的起始符:special_voc = { "<|replace_voc_1|>": 1000, "<|count_2|>": 2000, # 两层编码 "<|count_3|>": 2001 # 三层编码 }
-
源词表(src_voc)
存储大词汇的 ID(如 2 亿词),例如:src_voc = {"hello": 100000000}
三、代码流程详解
以输入文本 ["hello", "world", "a", "test"]
为例,逐步解析:
1. 处理第一个词 “hello”
- 不在 voc 中 → 从 src_voc 获取 ID:
src_voc["hello"] = 100000000
。 - 分层编码:
- 分解 ID:
a = 100000000 // (8192**2) ≈ 1 b = (100000000 // 8192) % 8192 ≈ 4015 c = 100000000 % 8192 ≈ 16
- 使用特殊标记
<|count_3|>
(三层编码):token_id_list = [2001, 1, 4015, 16]
- 分解 ID:
2. 处理第二个词 “world”
- 不在 voc 中 → 查找前一个词 “hello” 的替换词表:
replace_voc["hello"].get("world", None) → 无 → 新增 ID 4
- 标记替换位置:
token_id_list.extend(["replace_voc", 4 + 3]) → [2001, 1, 4015, 16, "replace_voc", 7] replace_voc_count = 1
3. 处理第三个词 “a”
- 在 voc 中 → 直接获取 ID:
token_id = voc["a"] + 3 = 1 + 3 = 4
- 替换前一个标记:
将 "replace_voc" 替换为 special_voc["<|replace_voc_1|>"] → 1000 token_id_list = [2001,1,4015,16,1000,4] replace_voc_count = 0
4. 处理第四个词 “test”
- 不在 voc 中 → 查找前一个词 “a” 的替换词表:
replace_voc["a"].get("test", None) → 无 → 新增 ID 2
- 标记替换位置:
token_id_list.extend(["replace_voc", 2+3]) → [2001,1,4015,16,1000,4, "replace_voc",5] replace_voc_count = 1
5. 最终清理
- 遍历结束后,替换最后一个 “replace_voc”:
token_id_list[-2] = special_voc["<|replace_voc_1|>"] → 1000 最终输出: [2001,1,4015,16,1000,4,1000,5]
四、代码问题与改进
-
替换词表结构错误
- 问题:
replace_voc
的键是前一个词(如 “hello”),而非位置索引。- 当前一个词未在
replace_voc
中时(如 “world”),会导致KeyError
。
- 当前一个词未在
- 改进:
- 将
replace_voc
的键改为 前一个词的位置索引,例如:replace_voc = {0: {"world": 4}, 2: {"test": 2}}
- 将
- 问题:
-
分层编码的边界条件
- 问题:当
a=0
时,可能未正确处理两层编码。 - 改进:
- 确保
a
的计算逻辑正确,避免负数或溢出。
- 确保
- 问题:当
-
性能优化
- 使用
collections.defaultdict
替代普通字典,简化初始化逻辑。
- 使用
五、示例输出
输入文本:["hello", "world", "a", "test"]
输出 Token ID 列表:[2001, 1, 4015, 16, 1000, 4, 1000, 5]
更新后的 replace_voc:{
"hello": {"world": 4},
"a": {"test": 2}
}
六、应用与扩展
-
雅思写作中的启发
- 同义词替换:通过
replace_voc
动态扩展词汇,避免重复用词。 - 分层编码:类似雅思中用上义词/下义词扩展表达(如用 “screen” 替代 “computer”)。
- 同义词替换:通过
-
实际场景
- 处理长文本时,结合
replace_voc
和special_voc
,可显著减少词汇表大小,同时保持表达多样性。 - 在大模型中,类似技术用于处理超大规模词汇(如 2 亿词)的编码问题。
- 处理长文本时,结合
七、总结
这段代码巧妙结合了 动态替换词表 和 分层编码,实现了在有限词表下处理大词汇量的能力。尽管存在结构上的改进空间,但其核心思想——通过标记和分层扩展表达力——与雅思写作中的词汇策略不谋而合。通过优化代码结构和逻辑,可以进一步提升其在实际场景中的鲁棒性和效率。
希望这篇解析能帮助你理解代码逻辑,并在实际项目中灵活运用这些技巧! 🚀
源码
voc = {"a": 1, "b": 2}
replace_voc = {"a": {"1": 1},"hello":{"a":1,"b":3}}
special_voc = {
"<|replace_voc_1|>": 1000,
"<|count_2|>": 2000,
"<|count_3|>": 2001
}
special_voc_len = len(special_voc)
src_voc = {"hello": 100000000}
# replace_voc_initial = {"a": {"1": 1}}
text = ["hello", "world", "a", "test"]
token_id_list = []
replace_voc_count = 0
for idx, one in enumerate(text):
token_id = voc.get(one, None)
if token_id is None and idx != 0:
up_token = text[idx - 1]
token_id = replace_voc[up_token].get(one, None)
if token_id is None:
# 如果不存在就更新 replace_voc
token_id = max(replace_voc[up_token].values()) + 1
replace_voc[up_token].update({one: token_id})
# if replace_voc_pos == -1:
# replace_voc_pos = len(token_id_list)
replace_voc_count += 1
# 指代地方要先使用 指代 replace_voc 字符占据
token_id_list.append("replace_voc")
token_id_list.append(token_id + special_voc_len)
else:
if idx == 0:
if token_id is None:
token_id = src_voc.get(one, 3)
# src 中 查到
if token_id == 3:
token_id_list.append(3)
else:
c = token_id % 8192
b = token_id // 8192
a = token_id // (8192 ** 2)
if a == 0:
token_id_list.append(special_voc["<|count_2|>"])
token_id_list.append(b)
token_id_list.append(c)
else:
token_id_list.append(special_voc["<|count_3|>"])
token_id_list.append(a)
token_id_list.append(b)
token_id_list.append(c)
else:
token_id_list.append(token_id + special_voc_len)
else:
if replace_voc_count:
token_id_list[token_id_list.index("replace_voc")] = special_voc[
"<|replace_voc_{}|>".format(replace_voc_count)]
replace_voc_count = 0
token_id_list.append(token_id + special_voc_len)
if replace_voc_count:
token_id_list[token_id_list.index("replace_voc")] = special_voc[
"<|replace_voc_{}|>".format(replace_voc_count)]
replace_voc_count = 0
print(token_id_list,replace_voc_count,replace_voc)