【大模型实战篇】大模型分词算法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)

        然后继续这样处理直到达到所需的词汇大小。

        分词紧密跟随训练过程,新输入的分词通过以下步骤完成:

  1. 标准化
  2. 预分词
  3. 将词拆分为单个字符
  4. 在这些拆分上按顺序应用学习到的合并规则

        以刚才使用的例子为例,三条学习到的合并规则为:

        ("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 AutoTokenizer

model_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 defaultdict

word_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] += 1

print(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:
            continue
        for i in range(len(split) - 1):
            pair = (split[i], split[i + 1])
            pair_freqs[pair] += freq
    return pair_freqs

pair_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 = None

for pair, freq in pair_freqs.items():
    if max_freq is None or max_freq < freq:
        best_pair = pair
        max_freq = freq

print(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:
            continue

        i = 0
        while i < len(split) - 1:
            if split[i] == a and split[i + 1] == b:
                split = split[:i] + [a + b] + split[i + 2 :]
            else:
                i += 1
        splits[word] = split
    return splits
splits = merge_pair("Ġ", "t", splits)
print(splits["Ġtrained"])

        循环,直到学习到我们想要的所有合并。设置目标词汇表大小为 50。

vocab_size = 50

while len(vocab) < vocab_size:
    pair_freqs = compute_pair_freqs(splits)
    best_pair = ""
    max_freq = None
    for pair, freq in pair_freqs.items():
        if max_freq is None or max_freq < freq:
            best_pair = pair
            max_freq = freq
    splits = 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 = 0
            while i < len(split) - 1:
                if split[i] == pair[0] and split[i + 1] == pair[1]:
                    split = split[:i] + [merge] + split[i + 2 :]
                else:
                    i += 1
            splits[idx] = split

    return sum(splits, [])

3. 参考材料

【1】Summary of the tokenizers

【2】Byte-Pair Encoding tokenization

【3】RUC AI BOX 大预言模型

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Nest.js 实战 (十五):前后端分离项目部署的最佳实践

☘️ 前言 本项目是一个采用现代前端框架 Vue3 与后端 Node.js 框架 Nest.js 实现的前后端分离架构的应用。Vue3 提供了高性能的前端组件化解决方案&#xff0c;而 Nest.js 则利用 TypeScript 带来的类型安全和模块化优势构建了一个健壮的服务端应用。通过这种技术栈组合&…

phpstorm中使用FTP功能和自动上传配置介绍

phpstorm中使用FTP功能和自动上传配置介绍 一、引言 PHPStorm 是一款强大的 PHP IDE&#xff0c;它提供了许多便捷的功能来提高开发效率。其中&#xff0c;内置的 FTP 功能允许开发者直接在 IDE 中上传文件到服务器&#xff0c;而自动上传配置则可以进一步简化这一过程。本文…

ISUP协议视频平台EasyCVR私有化视频平台视频汇聚/存储系统怎么选?

一、EasyCVR视频监控存储系统的核心优势 TSINGSEE青犀EasyCVR视频汇聚平台是一个具备高度集成化、智能化的视频监控汇聚管理平台&#xff0c;拥有远程视频监控、录像、存储、回放、语音对讲、云台控制、告警等多项核心功能。该系统采用先进的网络传输技术&#xff0c;支持高清…

Servlet(三)-------Cookie和session

一.Cookie和Session Cookie和Session都是用于在Web应用中跟踪用户状态的技术。Cookie是存储在用户浏览器中的小文本文件&#xff0c;由服务器发送给浏览器。当用户再次访问同一网站时&#xff0c;浏览器会把Cookie信息发送回服务器。例如&#xff0c;网站可以利用Cookie记住用…

轻松掌握Win10录屏技巧:四大神器推荐!

在Win10系统中&#xff0c;录屏功能的应用越来越广泛&#xff0c;无论是用于工作演示、在线教学还是游戏分享&#xff0c;一款好用的录屏软件都是必不可少的。今天&#xff0c;我们将推荐四款录屏工具&#xff01; 福昕录屏大师 直达链接&#xff1a;www.foxitsoftware.cn/RE…

字符串大小的比较

1.字符串中每一个字符都对应一个码值&#xff0c;字符串比较大小时从第一个字符开始比较出现结果时输出 如下图所示&#xff1a;

力扣之613.直线上的最近距离

文章目录 1. 613.直线上的最近距离1.1 题意1.2 准备数据1.3 题解1.4 结果截图 1. 613.直线上的最近距离 1.1 题意 表&#xff1a; Point ----------------- | Column Name | Type | ----------------- | x | int | ----------------- 在SQL中&#xff0c;x是该表的主键列。 …

《计算机视觉》—— 换脸

效果如下&#xff1a; 完整代码&#xff1a; import cv2 import dlib import numpy as npJAW_POINTS list(range(0, 17)) RIGHT_BROW_POINTS list(range(17, 22)) LEFT_BROW_POINTS list(range(22, 27)) NOSE_POINTS list(range(27, 35)) RIGHT_EYE_POINTS list(range(36…

PON架构(全光网络)

目前组网架构 世界上有一种最快的速度又是光&#xff0c;以前传统以太网络规划满足不了现在的需求。 有线网 无线网 全光网络方案 场景 全光网络分类 以太全光网络 PON&#xff08;Pas-sive-Optical Network 无源光网络&#xff09; 再典型的中大型高校网络中 推荐万兆入…

电脑技巧:Rufus——最佳USB启动盘制作工具指南

目录 一、功能强大&#xff0c;兼容性广泛 二、界面友好&#xff0c;操作简便 三、快速高效&#xff0c;高度可定制 四、安全可靠&#xff0c;社区活跃 在日常的电脑使用中&#xff0c;无论是为了安装操作系统、修复系统故障还是进行其他需要可引导媒体的任务&#xff0c;拥…

目前最新 Reflector V11.1.0.2067版本 .NET 反编译软件

目前最新 Reflector V11.1.0.2067版本 .NET 反编译软件 一、简介二、.NET Reflector的主要功能包括&#xff1a;1. **反编译**: 反编译是将已编译的.NET程序集&#xff08;如.dll或.exe文件&#xff09;转换回可读的源代码。这使得开发者可以查看和学习第三方库的实现细节&…

【分立元件】电阻的额定电压和最高电压

在文章:【分立元件】贴片电阻的额定功率中我们讲到使用电阻器时,不仅要注意额定功率,还要注意电压相关的一些项目。 本文我们将对与电阻基本参数关联的额定电压和元件最高电压这两个术语及其定义(包括它们之间的关系)进行解说。 额定电压 如下所示国巨片式电阻规…

排序算法 —— 希尔排序

目录 1.希尔排序的由来 2.希尔排序的思想 3.希尔排序的实现 实现分析 实现代码 代码优化 4.希尔排序的总结 1.希尔排序的由来 希尔排序是对直接插入排序的优化。在直接插入排序算法中&#xff0c;如果数据是有序or接近有序的时候&#xff0c;直接插入排序算法的时间复杂…

跨时钟域处理(单bit)_2024年10月21日

慢时钟域同步到快时钟域&#xff1a;打两拍 在快时钟域clk下对慢时钟域信号进行打两拍&#xff08;亚稳态概率很低&#xff09; 脉冲宽度改变&#xff0c;但不影响同步结果 快时钟域同步到慢时钟域&#xff08;两种方法&#xff09; ① 脉冲展宽同步 在快时钟域clk下对快时…

LeetCode199. 二叉树的右视图(2024秋季每日一题 47)

给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出: [1,3,4] 示例 2: 输入: [1,null,3] 输出: [1,3] 示例 3: 输入: [] 输出: [] 提示:…

软件压力测试如何进行?深圳软件测试机构分享

软件压力测试是每个重要软件测试工作的一部分&#xff0c;是一种基本的软件质量保证行为。压力测试不是在常规条件下运行手动或自动测试&#xff0c;而是在计算机数量较少或系统资源匮乏的条件下运行测试。通常要进行软件压力测试的资源包括内部内存、CPU 可用性、磁盘空间和网…

Libevent源码剖析-event

1 简介 本文来重点介绍下libevent中的event事件&#xff0c;在类unix系统中编写网络程序时&#xff0c;我们经常需要处理3类事件-IO事件&signal事件&timer事件&#xff0c;libevent通过reactor来注册&调度&处理IO事件&#xff0c;并且也将signal和timer事件借助…

ESP32开发__ESP-IDF, ESP-ADF官网下载,安装及环境配置

前言 不说废话&#xff0c;直面“干货”。最近公司项目涉及基于 ESP32 系列芯片开发&#xff0c;那我们新手小白如何准备相关工作及快速入门&#xff0c;本篇文章旨在&#xff1a;介绍ESP32&#xff0c;指导用户搭建 ESP32 硬件开发的软件环境&#xff08; ESP-IDF V5.2.1 和 …

解码专业术语——应用系统开发项目中的专业词汇解读

文章目录 引言站点设置管理具体要求包括&#xff1a; Footer管理基于URL的权限控制利用数据连接池优化数据库操作什么是数据连接池&#xff1f;优化的优势 利用反射改造后端代码&#xff0c;AJAX反射的作用及其在后端代码中的应用AJAX 实现前后端无刷新交互 引言 创新实践项目二…

Bash 中的 ${} 和 $() 有什么区别 ?

Bash (Bourne-Again SHell) 是一种流行的 Unix SHell&#xff0c;用于编写脚本。如果您使用 Bash 脚本&#xff0c;那么了解不同的语法元素对于提高脚本的效率和避免错误是很重要的。 在本文中&#xff0c;我们将解释 Bash 中 ${} 和 $() 语法之间的区别&#xff0c;并向您展示…