深入解析 Transformers 框架(四):Qwen2.5/GPT 分词流程与 BPE 分词算法技术细节详解

前面我们已经通过三篇文章,详细介绍了 Qwen2.5 大语言模型在 Transformers 框架中的技术细节,包括包和对象加载、模型初始化和分词器技术细节:

  1. 深入解析 Transformers 框架(一):包和对象加载中的设计巧思与实用技巧
  2. 深入解析 Transformers 框架(二):AutoModel 初始化及 Qwen2.5 模型加载全流程
  3. 深入解析 Transformers 框架(三):Qwen2.5 大模型的 AutoTokenizer 技术细节

在第 3 篇文章中,我们简要介绍了 Qwen2.5 使用的 Byte Pair Encoding (BPE) 分词算法,并用英文序列 "Hello World." 举例说明了分词过程。然而,这只是 BPE 分词的一部分内容。今天,我们将继续深入探讨 Qwen2.5 的分词流程,重点介绍 Qwen2.5 如何处理中英文混合的文本序列,并解析 BPE 分词算法的具体实现。

分词配置文件

一、中英文分词示例与初步分析

为深入理解 Qwen2.5 的分词过程,我们以一段中英文混合的文本序列“Transformers分词:台风又双叒叕来了!”为例进行剖析。以下是相关的代码示例:

import osfrom transformers import AutoTokenizer# 初始化分词器,从本地文件加载模型
model_dir = os.path.join('D:', os.path.sep, 'ModelSpace', 'Qwen2.5', 'Qwen2.5-1.5B-Instruct')
tokenizer = AutoTokenizer.from_pretrained(model_dir,local_files_only=True,
)text = 'Transformers分词:台风又双叒叕来了!'
tokens = tokenizer.tokenize(text)print(tokens)# 输出:['Transform', 'ers', 'åĪĨ', 'è¯į', 'ï¼ļ', 'åı°é£İ', 'åıĪ', 'åıĮ', 'åı', 'Ĵ', 'åıķ', 'æĿ¥äºĨ', 'ï¼ģ']ids = tokenizer.convert_tokens_to_ids(tokens)print(ids)# 输出:[8963, 388, 17177, 99689, 5122, 108118, 99518, 99493, 5758, 240, 122378, 101161, 6313]

我们先看看tokenizer.convert_tokens_to_ids()方法:它的实现比较简单,就是从词表中获取Token对应的ID;而词表其实就是一个字典,即vocab.json文件内容,每个 Token 都对应一个数字,即 Token ID。

如下随便挑选了上面结果几个 Token,在vocab.json文件中都可以找到他们的对应关系:

"Transform": 8963,
"åıķ": 122378,
"æĿ¥äºĨ": 101161,

第 1 个问题:为什么英文序列分词之后仍可以明确地识别出来,而中文分词之后看起来像是乱码呢?

实际上,这并非真正的乱码,而是字节 Byte 的 Unicode 字符表示。这也揭示了 BPE 算法的核心特性——基于 Byte 字节进行分词。无论是英文、中文,还是其他任何语言的字符序列,在计算机的存储与计算体系中,均以字节为基本单元进行处理。这一特性使得 BPE 算法具备了天然的通用性,能够跨越不同语言的界限,为大语言模型的多语言处理能力奠定了坚实基础。

二、Tokenize 分词流程详解

接下来,我们重点聚焦于tokenizer.tokenize()方法,深入探究其如何实现分词操作,其中 BPE 算法的具体实现过程尤为关键。通过对 Transformers 框架源代码的逐步解读,可以将其分词流程分解为以下几个关键步骤:

  1. 执行PreTrainedTokenizer.tokenize()父类方法:在这一初始步骤中,借助 Python 内置的unicodedata.normalize()函数对输入的文本序列进行规范化处理。其主要目的在于消除因字符编码差异可能引发的后续处理问题,确保文本序列在字符编码层面的一致性与规范性。在当前示例中,由于输入的文本序列本身已经是规范化的文本,因此该步骤直接返回原文本序列,未进行实质性的修改操作。

  2. 处理特殊 Token:特殊 Token 在分词过程中具有特殊的处理方式,它们将直接作为分词结果的一部分被返回,无需经过复杂的分词逻辑处理。

  3. 执行Qwen2Tokenizer._tokenize()方法:这一步骤构成了分词的核心逻辑部分,其中 BPE 算法将被实际应用于文本序列的分词操作。从该方法的注释Copied from transformers.models.gpt2.tokenization_gpt2.GPT2Tokenizer._tokenize 可以清晰地看出,Qwen2.5 的分词逻辑实现,是复制GPT2的实现代码。

Qwen2.5分词逻辑实现

第一行代码:re.findall(self.pat, text) 通过正则表达式对文本序列进行初步分割:(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+,该正则表达式从基础语法层面进行初步分割,包括英文缩写词、字母数字、空白符和换行符等。

如,对于我们的输入序列Transformers分词:台风又双叒叕来了!,会被分割成[Transformers分词, :台风又双叒叕来了, !]三个文本片段。

第二行代码:token = "".join(self.byte_encoder[b] for b in token.encode("utf-8")) 对正则表达式分割的文本片段进行处理

  • 首先,通过token.encode("utf-8")方法编码为 UTF-8 格式的字节序列,对于 ASCII 字符(0-127)字符,UTF-8 编码与其相同,这也是为什么分词结果,英文单词我们可以明确识别出来;而对于中文等非 ASCII 字符(128 及以上),UTF-8 会编码使用 2 到 4 个字节,如果 BPE 按照字节分词的话,一个汉字会被拆分几个部分,因此看起来就像是乱码了。如:Transformers分词字节序列的 16 进展表示为Transformers\xe5\x88\x86\xe8\xaf\x8d

  • 然后,通过self.byte_encoder[b]字典映射成字符,然后把每个 Byte 的字符表示拼接起来。

那么,self.byte_encoder字典又是如何实现的呢?

字节Unicode表示字典

寥寥几行代码,Qwen2.5 继续复制GPT2的实现代码:

  • 对于!~¡¬®ÿ这些可见字符,字典键就是这些字符,值为对应的 Byte 值。

  • 对于其他的非可见字符(空白和控制字符),字典键为256+序号,值为对应的 Byte 值。

第 2 个问题:为什么可见字符和非可见字符需要分开处理?其实原因也比较好理解:因为 BPE 代码无法处理的空白和控制字符,所以需要特殊处理。

因此,第 2 行代码的结果是,通过正则表达式初步分割的序列片段,把他们的每个字节按照其对应的 Unicode 字符拼接起来了,这也是为什么最终结果看起来像乱码的原因了。

第三行代码:bpe_tokens.extend(bpe_token for bpe_token in self.bpe(token).split(" ")) 对于 Unicode 字符串,执行 BPE 算法,进行分词操作。

BPE 分词算法的核心逻辑,都在这个方法里面,下面老牛同学进行详细介绍!

三、BPE 分词算法核心逻辑解析

bpe()方法作为 BPE 分词算法的核心枢纽,其代码实现主要借鉴了 GPT2 的相关代码:

BPE分词算法实现

在深入探讨其核心代码之前,有必要对 BPE 算法的基本思想进行简要回顾(可参考本系列文章的第三篇:深入解析 Transformers 框架(三):Qwen2.5 大模型的 AutoTokenizer 技术细节)。

首先,算法会检查缓存中是否已经存在当前 Unicode 字符串的分词结果。若缓存命中,则直接返回缓存中的分词后的 Token 列表,从而避免重复计算,提高分词效率。

若缓存未命中,则进入以下核心逻辑处理流程:

  1. 第 1 行代码:word = tuple(token):将输入的 Unicode 字符串拆分为单个 Unicode 字符的元组形式。这一操作将原始字符串打散为最基本的Unicode字符单元,为后续基于字符对的合并操作奠定了基础。

  2. 第 2 行代码:pairs = get_pairs(word):该方法同样源自 GPT2 的代码实现,其功能是获取给定 Unicode 字符串中所有可能的字符对组合。例如,对于“ABCD”这样的 Unicode 字符串,其返回的结果将是[(A,B),(B,C),(C,D)],即包含了字符串中相邻两个字符组成的所有字符对。

  3. 第 3 行代码:bigram = min(pairs, key=lambda pair: self.bpe_ranks.get(pair, float("inf"))) 对于pairsUnicode字符对集合中的每个元素,取bpe_ranks的最小值,如果不存在,则返回float("inf")无限大值。

那么,self.bpe_ranks又是什么呢?它也是一个字典,它的键是merges.txt文件内容去掉第 1 行版本号行后的每一行内容,值就是对应的行号。

也就是说,bigram的内容是pairs中的所有的 Unicode 字符对从merges.txt匹配到的最先出现的行号(即:最小值)。

第 3 个问题:为什么首先合并的 Unicode 字符对要取merges.txt文件中最小行号的字符对?

这需要我们理解merges.txt文件中存储的是什么内容:它存储的就是 GPT2/Qwen2.5 等大语言模型在预训练时,使用 BPE 算法进行分词时 Unicode 字符对的合并顺序。最小行号,意味着最先合并的 Unicode 字符对,也意味着出现频次最高的字符对。在我们推理时,保持同样的合并顺序,就可以保证合并结果和预训练时一致,同样保证了词表中有我们合并的Token!

BPE 算法在while True:循环中,持续进行字符对的合并操作:若在当前循环中成功合并了字符对,则将原有的单个 Unicode 字符对从待处理列表中移除,并将合并后的结果添加到待合并 Unicode 字符列表中。例如,对于初始字符列表:['T','r','a','n','s','f','o','r','m','e','r','s'],最小行号 Unicode 字符对是('e','r'),则在本次循环中会将“er”进行合并,下一轮循环时word的内容将更新为['T','r','a','n','s','f','o','r','m','er','s']。循环将持续进行,直到无法再找到可合并的字符对,或者所有字符对在merges.txt中均无法匹配到有效行号(即:bigram=float("inf"))时,循环终止,此时word中的内容即为最终的 Token 列表,代表了经过 BPE 算法分词后的结果。

至此,Qwen2.5 分词流程和 BPE 分词算法实现逻辑基本完成,下面进行简单总结。

总结:Qwen2.5 分词流程与 BPE 算法要点

Qwen2.5 的分词流程可以概括为以下几个关键步骤:

  1. 规范化预处理:对输入的文本序列运用规范化处理手段,消除因字符编码差异可能带来的干扰因素,确保文本序列在进入分词流程前的一致性与规范性。

  2. 正则表达式初步分割:借助精心设计的正则表达式,依据常见的语义和语法规则,将文本序列初步分割为多个片段序列,为后续的精细化分词操作提供基础框架。

  3. BPE 算法分词:针对每个片段序列,运用 BPE 算法进行深度分词处理,将片段序列进一步分解为 Token 列表,从而完成整个文本序列的分词任务。

BPE 分词算法的核心要点包括:

  1. 文本序列转换:将输入的文本序列通过 UTF-8 编码转换为 Byte 字节序列,再将每个字节映射为 Unicode 字符,实现文本序列在字符表示层面的转换,为基于字节的分词操作提供基础数据格式。

  2. 预训练阶段字符对合并与记录:在 Qwen2.5/GPT2 等大模型的预训练阶段,对语料中的 Unicode 字符进行逐对分析与合并操作。按照字符对在语料中出现的频次高低,逐步合并出现频次最高的 Unicode 字符对,并将每次合并的字符对信息逐行记录到“merges.txt”文件中,形成预训练阶段的字符对合并顺序知识库。

  3. 推理阶段字符对匹配与合并:在模型推理阶段,将每个 Unicode 字符转换为字符对形式,然后依据“merges.txt”文件中的合并顺序信息,每次仅合并最小行号(即最先合并)的字符对,持续进行合并操作直至无法再进行合并为止。最终得到的合并结果即为分词后的 Token 列表,完成对输入文本序列的分词任务,并确保推理阶段的分词结果与预训练阶段保持高度一致,从而为大语言模型在多语言文本处理中的准确性与稳定性提供有力保障。


往期推荐文章:

基于 Qwen2.5-Coder 模型和 CrewAI 多智能体框架,实现智能编程系统的实战教程

vLLM CPU 和 GPU 模式署和推理 Qwen2 等大语言模型详细教程

基于 Qwen2/Lllama3 等大模型,部署团队私有化 RAG 知识库系统的详细教程(Docker+AnythingLLM)

使用 Llama3/Qwen2 等开源大模型,部署团队私有化 Code Copilot 和使用教程

基于 Qwen2 大模型微调技术详细教程(LoRA 参数高效微调和 SwanLab 可视化监控)

ChatTTS 长音频合成和本地部署 2 种方式,让你的“儿童绘本”发声的实战教程

深入解析 Transformers 框架(一):包和对象加载中的设计巧思与实用技巧

深入解析 Transformers 框架(二):AutoModel 初始化及 Qwen2.5 模型加载全流程

深入解析 Transformers 框架(三):Qwen2.5 大模型的 AutoTokenizer 技术细节

微信公众号:老牛同学

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/829149.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

37 机器人

机器人由计算机控制的机器,可编程机器人

鸿蒙项目实战(三):自定义弹窗开发实践

自定义弹窗选型 合理选择不同的系统能力实现弹窗,有利于提升应用开发效率,实现更好的功能需求,因此了解自定义弹窗的选型和差异非常重要。在应用开发中,为了选择出合适的弹窗选型,从使用场景上,需要重点关注以下两点:弹窗与界面代码解耦 在开发业务逻辑时,例如遇到一些…

换根 DP

树形 DP 中的换根 DP 问题又被称为二次扫描,通常需要求以每个点为根时某个式子的答案。 这一类问题通常需要遍历两次树,第一次遍历先求出以某个点(如 \(1\))为根时的答案,在第二次遍历时考虑由根为 \(u\) 转化为根为 \(v\) 时答案的变化(换根)。这个变化往往分为两部分,…

学习笔记(三十):ArkUi-UIContext.getPromptAction(弹窗)

概述: 基于promptAction弹窗演进而来,支持全局自定义弹窗,不依赖UI组件,依赖UIContext, 支持在非页面文件中使用,弹窗内容支持动态修改,支持自定义弹窗圆角半径、大小和位置, 适合在与页面解耦的全局弹窗、自定义弹窗显示和退出动画等场景下使用。 注意: 需先使用UICo…

MudBlazor:基于Material Design风格开源且强大的Blazor组件库

项目介绍 MudBlazor是一个基于Material Design风格开源、免费(MIT License)、功能强大的Blazor组件框架,注重易用性和清晰的结构。它非常适合想要快速构建Web应用程序的 .NET 开发人员,无需费力地处理 CSS 和 JavaScript。由于MudBlazor完全使用C#编写,因此你可以自由地调…

读数据工程之道:设计和构建健壮的数据系统32序列化和云网络

序列化和云网络1. 序列化 1.1. 仅仅通过从CSV转换到Parquet序列化,任务性能就提高了上百倍 1.2. 基于行的序列化1.2.1. 基于行的序列化是按行来组织数据1.2.2. 对于那些半结构化的数据(支持嵌套和模式变化的数据对象)​,基于行的序列化需要将每个对象作为一个单元来存储1.2…

入门龙芯旧世界汇编指令

我是龙芯汇编指令新手,本文是我学习龙芯汇编的笔记我借到了一台宝贵的龙芯 3A6000 设备,我期望在这台设备上面学习龙芯汇编指令。这台设备上的是龙芯旧世界的麒麟系统,由于这台设备很宝贵,我不能随意玩。为了防止弄坏设备,我将在此设备上面搭建 docker 环境,进入到 docke…

促进通用跨域检索中广义知识的模拟

促进通用跨域检索中广义知识的模拟ProS:促进通用跨域检索中广义知识的模拟通用跨域检索(UCDR)的目标是在广义测试场景中实现稳健的性能,其中数据在训练过程中可能属于严格未知的域和类别。最近,具有快速调整的预训练模型显示出很强的泛化能力,并在各种下游任务中取得了显著…

ParamISP:使用相机参数学习正向和反向ISP

ParamISP:使用相机参数学习正向和反向ISPRAW图像很少被共享,主要是因为与相机ISP获得的sRGB图像相比,RAW图像的数据量过大。最近已经证明,学习相机ISP的正向和反向过程,可以对输入的sRGB图像进行具有物理意义的RAW级图像处理。然而,现有的基于学习的ISP方法,无法处理ISP…

thinkphp console 命令行打印错误调用堆栈

在think\Console源文件里找到 run() 方法,加上内容: $output->error($e->getTraceAsString()); 然后当执行命令报错的时候就会有详细的错误信息,方便排查具体是哪行引起的问题!本文来自博客园,作者:imzhi,转载请注明原文链接:https://www.cnblogs.com/imzhi/p/18…

信道的极限容量

我们可以简单地将带通信道理解为无线传输信道,低通信道理解为有线传输信道,记忆公式时应该记住乘2的那个调制速度就是波特率

JD 商品詳情頁解析

https://item.jd.com/100036218692.html 以这个商品链接为例,分析详情图接口抓包拿到接口入参出参构建代码headers = {cookie:"",accept: application/json, text/javascript, */*; q=0.01,accept-language: zh-CN,zh;q=0.9,origin: https://item.jd.com,priority: …