【大模型实战篇】大模型分词算法BPE(Byte-Pair Encoding tokenization)及代码示例
词元化是针对自然语言处理任务的数据预处理中一个重要步骤,目的是将原始文本切分成模型可以识别和处理的词元序列。在大模型训练任务中,就是作为大模型的输入。传统的自然语言处理方法,如基于条件随机场的序列标注,主要采用基于词汇的分词方式,这与我们人类的语言认知更为契合。但是,这种分词方法在中文等语言中,可能会导致对同一输入产生不同的分词结果,从而生成包含大量低频词的庞大词表,并可能出现未登录词(OOV)的问题。因此,一些语言模型开始使用字符作为最小单位进行分词,例如,ELMo 使用了 CNN 词编码器。近年来,子词分词器在基于 Transformer 的语言模型中得到了广泛应用,常见的方法包括 BPE 分词、WordPiece 分词和 Unigram 分词,接下来我们将参考huggingface的材料【1】来分析这几类分词器,并展示每种模型使用的标记器类型的示例。本文首先对BPE进行分享。
1. 原理讲解
字节对编码(BPE, Byte-Pair Encoding tokenization)【2】最初是作为一种文本压缩算法开发的,随后被OpenAI用于预训练GPT模型时的分词。许多Transformer模型都使用了该算法,包括GPT、GPT-2、RoBERTa、BART和DeBERTa。BPE 算法从一组基本符号(如字母和边界字符)开始,迭代地寻找语料库中的两个相邻词元,并将它们替换为新的词元,这一过程被称为合并。合并的选择标准是计算两个连续词元的共现频率,也就是每次迭代中,最频繁出现的一对词元会被选择与合并。合并过程将一直持续达到预定义的词表大小【3】。
BPE训练开始于计算语料库中使用的唯一词汇集(在完成标准化和预分词步骤后),然后通过获取写这些词所用的所有符号来构建词汇表。举个非常简单的例子,假设我们的语料库使用了以下五个词:
"hug", "pug", "pun", "bun", "hugs"
基础词汇表将是["b", "g", "h", "n", "p", "s", "u"]。在实际情况下,该基础词汇表至少会包含所有ASCII字符,并可能包括一些Unicode字符。如果一个例子使用了在训练语料库中不存在的字符,该字符将被转换为未知标记。许多NLP模型在分析包含表情符号的内容时表现不佳的原因可能这个就是其中一种。
GPT-2和RoBERTa的分词器(相似性较高)有一种方法来处理这个问题:它们将词视为由字节组成,而不是Unicode字符。这样基础词汇表的大小就很小(256),但字符仍会被包含在内,而不会被转换为未知标记。这个技巧称为字节级BPE。 字节级别的 BPE(Byte-level BPE, B-BPE)是 BPE 算法的一种扩展,它将字节视为合并操作的基本符号,从而实现更精细的分割,并解决了未登录词的问题。代表性语言模型如 GPT-2、BART 和 LLaMA 都采用了这种分词方法。具体而言,如果将所有 Unicode 字符视为基本字符,基本词表的规模会非常庞大(例如,每个汉字都作为一个基本字符)。而使用字节作为基本字符,可以将词汇表的大小限制在 256,同时确保所有基本字符都包含在内。以 GPT-2 为例,其词表大小为 50,257,包含 256 个字节的基本词元、一个特殊的文末词元,以及通过 50,000 次合并学习到的词元。通过一些处理标点符号的附加规则,GPT-2 的分词器能够有效进行分词,而无需使用 “<UNK>” 符号。
在获得这个基础词汇表后,通过学习合并规则(将现有词汇表中的两个元素合并为一个新元素的规则)添加新标记,直到达到所需的词汇大小。因此,最开始这些合并将创建两个字符的标记,随着训练的进行,合并会生成更长的子词。在分词器训练的任何步骤中,BPE算法都会寻找最常见的现有标记对。最常见的那一对将被合并,然后重复下一步。
回到之前的例子,假设这些词的频率如下:
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
这意味着“hug”在语料库中出现了10次,“pug”5次,“pun”12次,“bun”4次,“hugs”5次。我们通过将每个词拆分为字符(构成我们初始词汇表的字符)开始训练,这样我们就可以将每个词视为标记的列表:
("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5)
然后查看标记对。标记对("h", "u")出现在“hug”和“hugs”中,因此在语料库中总共出现15次。但这不是最常见的标记对:最常见的对是("u", "g"),出现在“hug”、“pug”和“hugs”中,在词汇表中总共出现20次。因此,分词器学到的第一个合并规则是("u", "g") -> "ug",这意味着“ug”将被添加到词汇表中,并且在语料库中的所有词中都应合并该对。在这个阶段结束时,词汇表和语料库如下所示:
词汇表: ["b", "g", "h", "n", "p", "s", "u", "ug"]
语料库: ("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5)
现在有一些结果是超过两个字符的标记对:例如,标记对("h", "ug")(在语料库中出现15次)。然而,此时最常见的对是("u", "n"),在语料库中出现16次,因此第二个学习的合并规则是("u", "n") -> "un"。将其添加到词汇表并合并所有现有的出现结果如下:
词汇表: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"]
语料库: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5)
现在最常见的对是("h", "ug"),所以我们学习合并规则("h", "ug") -> "hug",这给我们带来了第一个三字符的标记。合并后,语料库如下:
词汇表: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"]
语料库: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5)
然后继续这样处理直到达到所需的词汇大小。
分词紧密跟随训练过程,新输入的分词通过以下步骤完成:
- 标准化
- 预分词
- 将词拆分为单个字符
- 在这些拆分上按顺序应用学习到的合并规则
以刚才使用的例子为例,三条学习到的合并规则为:
("u", "g") -> "ug"
("u", "n") -> "un"
("h", "ug") -> "hug"
单词“bug”将被分词为["b", "ug"]。然而,“mug”将被分词为["[UNK]", "ug"],因为字母“m”不在基础词汇表中。同样,“thug”将被分词为["[UNK]", "hug"]:字母“t”不在基础词汇表中,应用合并规则后首先会合并“u”和“g”,然后合并“h”和“ug”。
2. BPE代码实现
2.1 创建jupyter实验环境
为了快速方便验证代码,因此我们在服务器上安装了jupyter,来进行编程演示。这里记录下安装和部署的方式。
首先服务器上下载安装jupyter:
pip install jupyter
设置登录密码
jupyter notebook password
启动jupyter
jupyter notebook --no-browser --ip=0.0.0.0 --allow-root
访问jupyter-lab
一般域名就是:https://你的服务器ip地址:8888/lab
2.2 代码实现
示例语料库如下
corpus = ["This is the Hugging Face Course.","This chapter is about tokenization.","This section shows several tokenizer algorithms.","Hopefully, you will be able to understand how they are trained and generate tokens.",
]
我们需要将该语料库预分词为单词。这里将使用gpt2分词器进行预分词,国内的话,为了方便,我们依然采用model scope的模型库来操作, 下载gpt2模型到本地并加载,下载大概要花个几分钟的样子:
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
from transformers import AutoTokenizermodel_dir = snapshot_download('AI-ModelScope/gpt2', cache_dir='/root/autodl-tmp', revision='master')
mode_name_or_path = '/root/autodl-tmp/AI-ModelScope/gpt2'
tokenizer = AutoTokenizer.from_pretrained(mode_name_or_path, trust_remote_code=True)
然后,在预分词的同时,我们计算每个单词在语料库中的频率,并且计算基础词汇表,由语料库中使用的所有字符组成,将模型使用的特殊标记添加到词汇表的开头。对于GPT-2,唯一的特殊标记是"<|endoftext|>"。另外说明下:前面带有“Ġ”符号的词(例如“Ġis”、“Ġthe”)通常是在使用字节级别的BPE(Byte-level BPE)分词时生成的。这个符号的作用是标记一个单词是以空格开头的,即这个单词前面有一个空格。这种标记方式在处理文本时有助于保持词与词之间的分隔,使模型能够更好地理解上下文。
from collections import defaultdictword_freqs = defaultdict(int)for text in corpus:words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)new_words = [word for word, offset in words_with_offsets]for word in new_words:word_freqs[word] += 1print(word_freqs)
alphabet = []for word in word_freqs.keys():for letter in word:if letter not in alphabet:alphabet.append(letter)
alphabet.sort()print(alphabet)
将每个单词拆分为单个字符,以便开始训练。计算每个标记对的频率。找到出现次数最多的词对。
splits = {word: [c for c in word] for word in word_freqs.keys()}
def compute_pair_freqs(splits):pair_freqs = defaultdict(int)for word, freq in word_freqs.items():split = splits[word]if len(split) == 1:continuefor i in range(len(split) - 1):pair = (split[i], split[i + 1])pair_freqs[pair] += freqreturn pair_freqspair_freqs = compute_pair_freqs(splits)for i, key in enumerate(pair_freqs.keys()):print(f"{key}: {pair_freqs[key]}")if i >= 5:break
best_pair = ""
max_freq = Nonefor pair, freq in pair_freqs.items():if max_freq is None or max_freq < freq:best_pair = pairmax_freq = freqprint(best_pair, max_freq)
第一个合并学习的是 ('Ġ', 't')
-> 'Ġt'
,我们将 'Ġt'
添加到词汇表中。接下来,在分割字典中应用这个合并。
merges = {("Ġ", "t"): "Ġt"}
vocab.append("Ġt")
def merge_pair(a, b, splits):for word in word_freqs:split = splits[word]if len(split) == 1:continuei = 0while i < len(split) - 1:if split[i] == a and split[i + 1] == b:split = split[:i] + [a + b] + split[i + 2 :]else:i += 1splits[word] = splitreturn splits
splits = merge_pair("Ġ", "t", splits)
print(splits["Ġtrained"])
循环,直到学习到我们想要的所有合并。设置目标词汇表大小为 50。
vocab_size = 50while len(vocab) < vocab_size:pair_freqs = compute_pair_freqs(splits)best_pair = ""max_freq = Nonefor pair, freq in pair_freqs.items():if max_freq is None or max_freq < freq:best_pair = pairmax_freq = freqsplits = merge_pair(*best_pair, splits)merges[best_pair] = best_pair[0] + best_pair[1]vocab.append(best_pair[0] + best_pair[1])print(merges)
接下来为了对新文本进行分词,先进行预分词,再拆分,然后应用所有学习到的合并规则:
def tokenize(text):pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text)pre_tokenized_text = [word for word, offset in pre_tokenize_result]splits = [[l for l in word] for word in pre_tokenized_text]for pair, merge in merges.items():for idx, split in enumerate(splits):i = 0while i < len(split) - 1:if split[i] == pair[0] and split[i + 1] == pair[1]:split = split[:i] + [merge] + split[i + 2 :]else:i += 1splits[idx] = splitreturn sum(splits, [])
3. 参考材料
【1】Summary of the tokenizers
【2】Byte-Pair Encoding tokenization
【3】RUC AI BOX 大预言模型
相关文章:

【大模型实战篇】大模型分词算法BPE(Byte-Pair Encoding tokenization)及代码示例
词元化是针对自然语言处理任务的数据预处理中一个重要步骤,目的是将原始文本切分成模型可以识别和处理的词元序列。在大模型训练任务中,就是作为大模型的输入。传统的自然语言处理方法,如基于条件随机场的序列标注,主要采用基于词…...

低功耗4G模组LCD应用示例超全教程!不会的小伙伴看这篇就够了!
希望大家通过本文的介绍,学会LCD显示屏与Air780E开发板结合使用的方法。利用LCD显示屏,你可以为你的项目增加丰富的显示内容,提升用户体验。记住,实践出真知,赶快动手尝试吧!相信这篇教程对你有所帮助~ 本文…...

Java while语句练习 C语言的函数递归
1. /* public static void main(String[] args) {int[] arr {25, 24, 12, 98, 36, 45};int max arr[0];//不能写0for (int i 1; i < arr.length; i) {if (arr[i] > max) {max arr[i];}}System.out.println(max);}*//*public static void main(String[] args) {doubl…...

illustrator免费插件 截图识别文字插件 textOCR
随手可得的截图识别文字插件 textOCR,识别出来的文字可直接输入到illustrator的当前文档中: 执行条件 1、需截图软件支持,推荐笔记截图工具 2、截好图片直接拖入面板即可完成识别 ****后期可完成实现在illustrator选择图片对象完成文字识别。…...

提升数据管理效率:ETLCloud与达梦数据库的完美集成
达梦数据库的核心优势在于其强大的数据处理能力和高可用性设计。它采用先进的并行处理技术,支持大规模的数据操作,同时具备出色的事务处理能力和数据安全保障。此外,达梦数据库还提供了丰富的功能模块,如数据备份、恢复、监控等&a…...

头歌——人工智能(搜索策略)
文章目录 第1关:搜索策略第2关:盲目搜索第3关:启发式搜索 - 扫地机器人最短路径搜索第4关:搜索算法应用 - 四皇后问题 第1关:搜索策略 什么是搜索技术 人类的思维过程可以看作是一个搜索过程。从小学到现在࿰…...

gorm.io/sharding改造:赋能单表,灵活支持多分表策略(下)
背景 分表组件改造的背景,我在这篇文章《gorm.io/sharding改造:赋能单表,灵活支持多分表策略(上)》中已经做了详细的介绍——这个组件不支持单表多个分表策略,为了突破这个限制做的改造。 在上一篇文章中&…...

域渗透AD渗透攻击利用 MS14-068漏洞利用过程 以及域渗透中票据是什么 如何利用
目录 wmi协议远程执行 ptt票据传递使用 命令传递方式 明文口令传递 hash口令传递 票据分类 kerberos认证的简述流程 PTT攻击的过程 MS14-068 漏洞 执行过程 wmi协议远程执行 wmi服务是比smb服务高级一些的,在日志中是找不到痕迹的,但是这个主…...

C++进阶-->继承(inheritance)
1. 继承的概念及定义 1.1 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要手段,他允许我们在保证原有类的特性基础上还进行扩展,通过继承产生的类叫做派生类(子类),被继承的类叫做基类&a…...

可视化项目 gis 资源复用思路(cesium)
文章目录 可视化项目 gis 资源复用思路底图、模型替换思路具体操作 可视化项目 gis 资源复用思路 背景: A项目的底图、模型 是现在在做的 B项目所需要的,现在要把 B项目的底图之类的替换成 A系统的 底图、模型替换思路 观察可访问系统的 gis 相关网络请…...

SQL实战测试
SQL实战测试 (请写下 SQL 查询语句,不需要展示结果) 表 a DateSalesCustomerRevenue2019/1/1张三A102019/1/5张三A18 1. **用一条 ** SQL 语句写出每个月,每个销售有多少个客户收入多少 输出结果表头为“月”,“销…...
Java 基础教学:基础语法-变量与常量
变量 变量是程序设计中的基本概念,它用于存储信息,这些信息可以在程序执行过程中被读取和修改。 变量的声明 在Java中,声明变量需要指定变量的数据类型以及变量的名称。数据类型定义了变量可以存储的数据种类(例如整数、浮点数…...

vue3使用element-plus手动更改url后is-active和菜单的focus颜色不同步问题
在实习,给了个需求做个新的ui界面,遇到了一个非常烦人的问题 如下,手动修改url时,is-active和focus颜色不同步 虽然可以直接让el-menu-item:focus为白色能解决这个问题,但是我就是想要有颜色哈哈哈,有些执…...
每天五分钟深度学习框架pytorch:从底层实现一元线性回归模型
本文重点 本节课程我们继续搭建一元线性回归模型,不同的是这里我们不使用pytorch框架已经封装好的一些东西,我们做这个目的是为了更加清楚的看到pytorch搭建模型的本质,为了更好的理解,当然实际中我们还是使用pytorch封装好的一些东西,不要重复造轮子。 模型搭建 #定义…...
编辑器加载与AB包加载组合
解释: 这个 ABResMgr 类是一个资源加载管理器,它用于整合 AB包(Asset Bundle)资源加载和 编辑器模式资源加载。通过这个管理器,可以根据开发环境选择资源加载方式,既支持 运行时使用Asset Bundle加载&…...
【c++】vector中的back()函数
nums.back() 是 C 中 std::vector 类的一个成员函数,用于获取数组(向量)中的最后一个元素。以下是一些关于 nums.back() 的详细解释和示例使用: 1. 功能 nums.back() 返回数组 nums 中的最后一个元素。如果数组为空,…...

[分享] SQL在线编辑工具(好用)
在线SQL编写工具(无广告) - 在线SQL编写工具 - Web SQL - SQL在线编辑格式化 - WGCLOUD...
element-ui隐藏表单必填星号
// 必填星号在前显示 去掉 .el-form-item.is-required:not(.is-no-asterisk) > .el-form-item__label:before { content: !important; margin-right: 0px!important; } // 必填星号在结尾显示 .el-form-item.is-required:not(.is-no-asterisk) > .el-form-item__labe…...

自动驾驶系列—激光雷达点云数据在自动驾驶场景中的深度应用
🌟🌟 欢迎来到我的技术小筑,一个专为技术探索者打造的交流空间。在这里,我们不仅分享代码的智慧,还探讨技术的深度与广度。无论您是资深开发者还是技术新手,这里都有一片属于您的天空。让我们在知识的海洋中…...
C#删除dataGridView 选中行
关键在于:从最后一行开始删除。 从前往后删只能删除其中一半,我理解是再remove行的时候dataGridView内部行序列发生了变化,包含在选中行中的特定行会被忽略,从后往前删就可避免这个问题,最后一行的行号影响不到前面的…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
掌握 HTTP 请求:理解 cURL GET 语法
cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...

Mysql故障排插与环境优化
前置知识点 最上层是一些客户端和连接服务,包含本 sock 通信和大多数jiyukehuduan/服务端工具实现的TCP/IP通信。主要完成一些简介处理、授权认证、及相关的安全方案等。在该层上引入了线程池的概念,为通过安全认证接入的客户端提供线程。同样在该层上可…...
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ is not explicitly defined.
这个警告表明您在使用Vue的esm-bundler构建版本时,未明确定义编译时特性标志。以下是详细解释和解决方案: 问题原因: 该标志是Vue 3.4引入的编译时特性标志,用于控制生产环境下SSR水合不匹配错误的详细报告1使用esm-bundler…...
Git 命令全流程总结
以下是从初始化到版本控制、查看记录、撤回操作的 Git 命令全流程总结,按操作场景分类整理: 一、初始化与基础操作 操作命令初始化仓库git init添加所有文件到暂存区git add .提交到本地仓库git commit -m "提交描述"首次提交需配置身份git c…...
Qt学习及使用_第1部分_认识Qt---Qt开发基本流程
前言 学以致用,通过QT框架的学习,一边实践,一边探索编程的方方面面. 参考书:<Qt 6 C开发指南>(以下称"本书") 标识说明:概念用粗体倾斜.重点内容用(加粗黑体)---重点内容(红字)---重点内容(加粗红字), 本书原话内容用深蓝色标识,比较重要的内容用加粗倾…...

(12)-Fiddler抓包-Fiddler设置IOS手机抓包
1.简介 Fiddler不但能截获各种浏览器发出的 HTTP 请求,也可以截获各种智能手机发出的HTTP/ HTTPS 请求。 Fiddler 能捕获Android 和 Windows Phone 等设备发出的 HTTP/HTTPS 请求。同理也可以截获iOS设备发出的请求,比如 iPhone、iPad 和 MacBook 等苹…...

Gitlab + Jenkins 实现 CICD
CICD 是持续集成(Continuous Integration, CI)和持续交付/部署(Continuous Delivery/Deployment, CD)的缩写,是现代软件开发中的一种自动化流程实践。下面介绍 Web 项目如何在代码提交到 Gitlab 后,自动发布…...