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

news2024/11/25 2:40:08

前面我们已经通过三篇文章,详细介绍了 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 os

from 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 种方式,让你的“儿童绘本”发声的实战教程

微信公众号:老牛同学

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

商品详情 API 接口的返回结果通常包含哪些信息?

商品详情 API 接口的返回结果通常包含以下几类信息: 一、商品基本信息: 商品 ID:唯一标识商品的编号,在电商平台的数据库中具有唯一性,用于区分不同的商品。商品标题:对商品的简要描述,通常包…

探索 Seata 分布式事务

Seata(Simple Extensible Autonomous Transaction Architecture)是阿里巴巴开源的一款分布式事务解决方案,旨在帮助开发者解决微服务架构下的分布式事务问题。它提供了高效且易于使用的分布式事务管理能力,支持多种事务模式&#…

AI写作(七)的核心技术探秘:情感分析与观点挖掘

一、AI 写作中的关键技术概述 情感分析与观点挖掘在 AI 写作中起着至关重要的作用。情感分析能够帮助 AI 理解文本中的情感倾向,无论是正面、负面还是中性。在当今信息时代,准确把握用户情绪对于提供个性化体验和做出明智决策至关重要。例如,…

容器化技术入门:Docker详解

💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 容器化技术入门:Docker详解 容器化技术入门:Docker详解 容器化技术入门:Docker详解 引言 Doc…

Flutter运行App时出现“Running Gradle task ‘assembleDebug“问题解决

在参考了众多解决办法中最有用并且最快的方法 Gradle Distributions 在这个地方下载对应你这个文件中的gradle版本 然后将 最后一行本来不是这样的,我们把下载好的zip包保存到本地,然后用这个代替网址,最后成功运行

Spark中的shuffle

Shuffle的本质基于磁盘划分来解决分布式大数据量的全局分组、全局排序、重新分区【增大】的问题。 1、Spark的Shuffle设计 Spark Shuffle过程也叫作宽依赖过程,Spark不完全依赖于内存计算,面临以上问题时,也需要Shuffle过程。 2、Spark中哪…

window11安装elasticsearch+Kibana

1、下载elasticsearch与elasticsearch 下载elasticsearch 查看elasticsearch对应的Kibana版本 下载elasticsearch解压后文件目录如下 可执行脚本文件,包括启动elasticsearch服务、插件管理、函数命令等 bin配置文件目录,如elasticsearch配置、角色配置、jvm配置等 conf 默认…

linux rocky 9.4部署和管理docker harbor私有源

文章目录 Harbor简介安装Harbor技术细节1.安装系统(略),设置主机名和IP2.安装docker3.安装docker-compose4.安装Harbor私有源仓库5 测试登录1.本机登录2.客户端登录Harbor服务器配置docker源1. 下载镜像2.把镜像上传到Harbor私有仓库源3.客户端下载镜像,并且启动容器linux …

【计算机网络五】HTTP协议!网站运行的奥秘!

目录 HTTP协议 1.HTTP是什么? 2.Fiddler抓包查看HTTP协议格式 3.HTTP请求 4.HTTP响应 HTTP协议 1.HTTP是什么? HTTP ( 全称为 " 超文本传输协议 ") 诞生与 1991 年 . 目前已经发展为最主流使用的一种应用层协议 . HTTP 的前几个版本…

嵌入式硬件实战基础篇(一)-STM32+DAC0832 可调信号发生器-产生方波-三角波-正弦波

引言:本内容主要用作于学习巩固嵌入式硬件内容知识,用于想提升下述能力,针对学习STM32与DAC0832产生波形以及波形转换,对于硬件的降压和对于前面硬件篇的实际运用,针对仿真的使用,具体如下: 设…

ubuntu 24.04运行chattts时cuda安装错误原因分析

使用ubuntu 24.04,按照2noise/ChatTTS官方流程安装依赖时报错。ChatTTShttps://github.com/2noise/ChatTTS 这是因为cuda版本不对,ChatTTS目前的版本,要求支持cuda 12.4及以上,但是如果nvidia显卡驱动版本较老,无法支…

【动态规划】斐波那契数列模型总结

一、第 N 个泰波那契数 题目链接: 第 N 个泰波那契数 题目描述: 题目分析: 1、状态表示: dp[i] 表示:第 i 个斐波那契数的值 2、状态转移方程: 由题意可知第 i 个数等于其前三个数之和 dp[i] dp[i-…

2024 第五次周赛

A: 直接遍历即可 #include<bits/stdc.h> using namespace std;typedef long long ll; typedef pair<ll, ll>PII; const int N 2e6 10; const int MOD 998244353; const int INF 0X3F3F3F3F;int n, m; int main() {cin >> n;int cnt 0;for(int i 0; i …

【数据库系列】postgresql链接详解

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Uniapp底部导航栏设置(附带PS填充图标教程)

首先需要注册和登录ifconfont官网&#xff0c;然后创建项目添加需要的图标 创建和添加图标库请参考&#xff1a;Uniapp在Vue环境中引入iconfont图标库&#xff08;详细教程&#xff09; 打开iconfont官网&#xff0c;找到之前添加的图标库&#xff0c;下载png图片 如果需要的…

提取神经网络数学表达式

来自《老饼讲解神经网络》 www..bbbdata.com 当我们在matlab训练好网络后&#xff0c;可以使用神经网络工具箱的sim(net,x)函数进行预测输出。但往往想提取出它的数学表达式&#xff0c;该怎么提取呢&#xff1f; 下面以《一个简单的神经网络例子》中的模型为例&#xff0c;提取…

【从零开始鸿蒙开发:01】自定义闪屏页

文章目录 大体介绍文件介绍各部分代码SplashPage.etsIndex.etsHomePage.etsroute_map.jsonmodule.json5 流程 大体介绍 文件介绍 其中&#xff1a; pages为我们的页面内容&#xff08;我个人理解功能性小于activity但是大于fragment&#xff09;route_map.json 为自定义的路由…

录制的音频听起来非常缓慢,声音很模糊

一、主题 录制的音频听起来非常缓慢&#xff0c;声音很模糊 二、问题背景 硬件&#xff1a;T113&#xff0c;R528等平台系列产品 软件&#xff1a;Tina5.0 三、问题描述 1、复现步骤 使用arecord进行录音。 arecord -Dhw:audiocodec -f S16_LE -r 16000 -c 2 -d 5 /tmp/t…

Android笔记(三十五):用责任链模式封装一个App首页Dialog管理工具

背景 项目需要在首页弹一系列弹窗&#xff0c;每个弹窗是否弹出都有自己的策略&#xff0c;以及哪个优先弹出&#xff0c;哪个在上一个关闭后再弹出&#xff0c;为了更好管理&#xff0c;于是封装了一个Dialog管理工具 效果 整体采用责任链模块设计&#xff0c;控制优先级及弹…

[CKS] K8S Dockerfile和yaml文件安全检测

最近准备花一周的时间准备CKS考试&#xff0c;在准备考试中发现有一个题目关于Dockerfile和yaml文件安全检测的题目。 ​ 专栏其他文章: [CKS] Create/Read/Mount a Secret in K8S-CSDN博客[CKS] Audit Log Policy-CSDN博客 -[CKS] 利用falco进行容器日志捕捉和安全监控-CSDN博…