BPE、Wordpiece、Unigram、SpanBERT等Tokenizer细节总结

news2025/3/11 17:06:14

BPE(Byte Pair Encoding)

GPT-2和Roberta用的是这种,不会产生[UNK]这个unknown字符

这部分部分摘录自https://martinlwx.github.io/zh-cn/the-bpe-tokenizer/

看以下code例子就足够理解了,核心是维护self.merges(维护一个pair->str的字典)和self.vocab(每次挑最高频的pair加入self.vocab)做训练,每次刷新一遍最新的self.splits,具体格式参考注释:

from collections import defaultdict, Counter
from pprint import pprint
from typing import List

class BPE:
    def __init__(self, corpus: List[str], vocab_size: int, max_iter: int, debug: bool, ):
        self.corpus = corpus
        self.vocab_size = vocab_size
        self.vocab = []
        self.word_freq = Counter()
        self.splits = {}  # 格式:highest: [high, est</w>]
        self.merges = {}  # 格式:[high, est</w>]: highest
        self.max_iter = max_iter
        self.debug = debug

    def train(self):
        """Train a BPE Tokenizer"""
        # count the word frequency
        for document in self.corpus:
            words = document.split() #按照空格等whitespae进行split
            self.word_freq += Counter(words)

        # initialize the self.splits
        for word in self.word_freq:
            self.splits[word] = list(word) + ["</w>"]
        if self.debug:
            print(f"Init splits: {self.splits}")
        alphabet = set()
        for word in self.word_freq:
            alphabet |= set(list(word))
        alphabet.add("</w>")
        self.vocab = list(alphabet)
        self.vocab.sort()

        cnt = 0
        while len(self.vocab) < self.vocab_size:
            if self.max_iter and cnt >= self.max_iter:
                break
            pair_freq = self.get_pairs_freq() #格式为 {('a','b'):3,('c','d'),5}
            if len(pair_freq) == 0:
                print("No pair available")
                break
            pair = max(pair_freq, key=pair_freq.get) #输出值最大的key
            self.update_splits(pair[0], pair[1])
            if self.debug:
                print(f"Updated splits: {self.splits}")
            self.merges[pair] = pair[0] + pair[1]
            self.vocab.append(pair[0] + pair[1])
            if self.debug:
                print(f"Most frequent pair({max(pair_freq.values())} times) "
                    f"is : {pair[0]}, {pair[1]}. Vocab size: {len(self.vocab)}"
                )
            cnt += 1

    def update_splits(self, lhs: str, rhs: str):
        """If we see lhs and rhs appear consecutively, we merge them"""
        for word, word_split in self.splits.items():
            new_split = []
            cursor = 0
            while cursor < len(word_split):
                if (word_split[cursor] == lhs and cursor + 1 < len(word_split) and word_split[cursor + 1] == rhs):
                    new_split.append(lhs + rhs)
                    cursor += 2
                else:
                    new_split.append(word_split[cursor])
                    cursor += 1
            self.splits[word] = new_split

    def get_pairs_freq(self) -> dict:
        """Compute the pair frequency"""
        pairs_freq = defaultdict(int)
        for word, freq in self.word_freq.items():
            split = self.splits[word]
            for i in range(len(split)):
                if i + 1 < len(split):
                    pairs_freq[(split[i], split[i + 1])] += freq
        return pairs_freq

    def tokenize(self, s: str) -> List[str]:
        splits = [list(t) + ["</w>"] for t in s.split()]
        for lhs, rhs in self.merges:
            for idx, split in enumerate(splits):
                new_split = []
                cursor = 0
                while cursor < len(split):
                    if (cursor + 1 < len(split) and split[cursor] == lhs and split[cursor + 1] == rhs):
                        new_split.append(lhs + rhs)
                        cursor += 2
                    else:
                        new_split.append(split[cursor])
                        cursor += 1
                assert "".join(new_split) == "".join(split)
                splits[idx] = new_split
        # splits是二维数组,最终拼成一维
        return sum(splits, [])

corpus = ["highest", "higher", "lower", "lowest", "cooler", "coolest"]

bpe = BPE(corpus, vocab_size=17, debug=True, max_iter=100)
bpe.train()
print('---------------output of tokenize---------------')
print(bpe.tokenize(" ". join(corpus)))
'''
Init splits: {'highest': ['h', 'i', 'g', 'h', 'e', 's', 't', '</w>'], 'higher': ['h', 'i', 'g', 'h', 'e', 'r', '</w>'], 'lower': ['l', 'o', 'w', 'e', 'r', '</w>'], 'lowest': ['l', 'o', 'w', 'e', 's', 't', '</w>'], 'cooler': ['c', 'o', 'o', 'l', 'e', 'r', '</w>'], 'coolest': ['c', 'o', 'o', 'l', 'e', 's', 't', '</w>']}
Updated splits: {'highest': ['h', 'i', 'g', 'h', 'es', 't', '</w>'], 'higher': ['h', 'i', 'g', 'h', 'e', 'r', '</w>'], 'lower': ['l', 'o', 'w', 'e', 'r', '</w>'], 'lowest': ['l', 'o', 'w', 'es', 't', '</w>'], 'cooler': ['c', 'o', 'o', 'l', 'e', 'r', '</w>'], 'coolest': ['c', 'o', 'o', 'l', 'es', 't', '</w>']}
Most frequent pair(3 times) is : e, s. Vocab size: 13
Updated splits: {'highest': ['h', 'i', 'g', 'h', 'est', '</w>'], 'higher': ['h', 'i', 'g', 'h', 'e', 'r', '</w>'], 'lower': ['l', 'o', 'w', 'e', 'r', '</w>'], 'lowest': ['l', 'o', 'w', 'est', '</w>'], 'cooler': ['c', 'o', 'o', 'l', 'e', 'r', '</w>'], 'coolest': ['c', 'o', 'o', 'l', 'est', '</w>']}
Most frequent pair(3 times) is : es, t. Vocab size: 14
Updated splits: {'highest': ['h', 'i', 'g', 'h', 'est</w>'], 'higher': ['h', 'i', 'g', 'h', 'e', 'r', '</w>'], 'lower': ['l', 'o', 'w', 'e', 'r', '</w>'], 'lowest': ['l', 'o', 'w', 'est</w>'], 'cooler': ['c', 'o', 'o', 'l', 'e', 'r', '</w>'], 'coolest': ['c', 'o', 'o', 'l', 'est</w>']}
Most frequent pair(3 times) is : est, </w>. Vocab size: 15
Updated splits: {'highest': ['h', 'i', 'g', 'h', 'est</w>'], 'higher': ['h', 'i', 'g', 'h', 'er', '</w>'], 'lower': ['l', 'o', 'w', 'er', '</w>'], 'lowest': ['l', 'o', 'w', 'est</w>'], 'cooler': ['c', 'o', 'o', 'l', 'er', '</w>'], 'coolest': ['c', 'o', 'o', 'l', 'est</w>']}
Most frequent pair(3 times) is : e, r. Vocab size: 16
Updated splits: {'highest': ['h', 'i', 'g', 'h', 'est</w>'], 'higher': ['h', 'i', 'g', 'h', 'er</w>'], 'lower': ['l', 'o', 'w', 'er</w>'], 'lowest': ['l', 'o', 'w', 'est</w>'], 'cooler': ['c', 'o', 'o', 'l', 'er</w>'], 'coolest': ['c', 'o', 'o', 'l', 'est</w>']}
Most frequent pair(3 times) is : er, </w>. Vocab size: 17
['h', 'i', 'g', 'h', 'est</w>', 'h', 'i', 'g', 'h', 'er</w>', 'l', 'o', 'w', 'er</w>', 'l', 'o', 'w', 'est</w>', 'c', 'o', 'o', 'l', 'er</w>', 'c', 'o', 'o', 'l', 'est</w>']
'''

Wordpiece

这部分摘录自huggingface的教程 https://huggingface.co/learn/nlp-course/chapter6/6?fw=pt,Bert,DistilBERT, MobileBERT用的是这种

训练过程

Wordpiece和BPE的训练过程很像,区别在于两点:

  1. Wordpiece不再使用出现最高频的pair,而是用下面的score来筛选每一个pair
score=(freq_of_pair)/(freq_of_first_element×freq_of_second_element)
  1. Wordpiece不是在结尾填充</w>,而是把中间字符前填充##,例如“word”这个词会被分割成w,##o,##r,##d

Tokenize过程

  1. wordpiece没有像BPE一样存self.merge,而是只存了self.vocab,每次都是最长匹配
  2. wordpiece会给填充[UNK]的token,同时还有"[PAD]", “[UNK]”, “[CLS]”, “[SEP]”, "[MASK]"这些特殊token

Unigram

这部分摘录自https://huggingface.co/learn/nlp-course/chapter6/7?fw=pt,Unigram的基本思路用下面例子比较明显,其实就是把句子理解成了unigram的language model
在这里插入图片描述

HuggingFace的tokenizer梳理

这部分摘录自https://huggingface.co/docs/tokenizers/components#models,HF的Tokenizer分为以下几个components:
在这里插入图片描述

  • Normalization: 比如unicode转换、大小写转换
  • Pre-tokenizers:作用是splitting the input into words,比如ByteLevel, this technique as been introduced by OpenAI with GPT-2, a tokenizer using this only requires 256 characters as initial alphabet (the number of values a byte can have), as opposed to the 130,000+ Unicode characters.
  • Models:WordLevel、BPE、WordPiece和Unigram
  • post-processing:adding the special tokens of the tokenizer, generating the attention mask and token type IDs

Tokenizer的mask策略

部分转载自https://zhuanlan.zhihu.com/p/360982134

静态mask

输入时,随机遮盖或替换一句话里面任意字或词, 然后让模型通过上下文的理解预测那一个被遮盖或替换的部分, 之后做 的时候只计算被遮盖部分的 。

随机把一句话中 15% 的 替换成以下内容:

  1. 这些 有 80% 的几率被替换成 [ ];
  2. 有 10% 的几率被替换成任意一个其他的 ;
  3. 有 10% 的几率原封不动。

动态mask

RoBERTa中引入了动态mask的策略,原论文中将原始数据复制n份,每份都进行随机的静态mask,从而每份数据的mask结果都不太一样。huggingface中data collator使用的是动态mask,但不是复制数据,而是每一个epoch的mask策略都不同,这样就可以达到动态mask的效果了,从而使得每一个epoch的mask的情况都不同,更方便更胜内存。

whole word mask (wwm)和ernie

对于原始的 BERT,训练时,会随机选取整句中的最小输入单元 token 来进行遮盖。因为用到 Byte Pair Encoding (BPE)技术,所以也可以把这些最小单元当作是子词(subword),比如说superman,分成 super+man 两个子词。

但这样会让本来应该有强相关的一些连在一起的字词,在训练时是割裂开来的。

因此我们就会想到,那能不能遮盖掉这样连在一起的片段训练呢?当然可以。

首先想到的做法,既然现在遮盖子词,那能不能直接遮盖整个词,比如说对于 super + man,只要遮盖就两个同时遮盖掉,这便是 Google 放出的 BERT WWM 模型所做的。

ERNIE类似的思路做了一些改进:
– Basic-Level Masking: 跟bert一样对单字进行mask,很难学习到高层次的语义信息;
– Phrase-Level Masking: 输入仍然是单字级别的,mask连续短语;
– Entity-Level Masking:首先进行实体识别,然后将识别出的实体进行mask。

n-gram mask

使用n-gram(uni-gram,bi-gram, tri-gram)来做MLM任务,,即以不同的概率使用n-gram,其中 uni-gram的概率最大,bi-gram其次,tri-gram概率最小。和下面的span有一些类似。

random span mask

在这里插入图片描述

来自于论文SpanBERT: Improving Pre-training by Representing and Predicting Spans
大体过程是,对一个句子X = (x1, x2, . . . , xn), 我们选取它的一个子序列(span)进行mask。通过不断地选取span,直到选够了X中15%的token。选取的方法是,首先通过几何分布选取span的长度L,(均匀分布采样应该大家都比较熟悉,对于不均匀的分布进行采样,简单的方式是将概率进行展平然后转化为均匀分布采样的问题,例如 0.6,0.3,0.2,0.1这样的分布,可以统一切割为6,3,2,1个0.1,然后均匀采样即可,当然按照0.01之类的来进行切割也是可以的)然后,均匀随机地选取span的起始位置。选取长度时,官方的设置是 L ∼Geo(0.2),同时裁剪L使Lmax=10,于是span长度的分布如下,平均值为3.8。span masking,指的是对span中的每一个token都替换成[MASK]。

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

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

相关文章

基于数据挖掘的斗鱼直播数据可视化分析系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 随着网络直播平台的兴起&#xff0c;斗鱼直播作为其中的佼佼者&#xff0c;吸引了大量用户和观众。为了更好地理解和分析斗鱼直播中的数据&#xff0c;本项目介绍了一个基于数据挖掘的斗鱼直播数据…

无人机GB42590接收端 +接收端模组,同时支持2.4G与5.8G双频

严格按照GB42590的协议开发的发射端&#xff0c;通过串口和模块通讯&#xff0c;默认波特率 921600。 http://www.doit.am/深圳四博智联科技有限公司https://shenzhendoit.taobao.com/category-1734422372.htm?spma1z10.1-c-s.0.0.560c74d77eT01G&searchy&catNameGB4…

MySQL数据库安装——zip压缩包形式

安装压缩包zip形式的 MySQL 8数据库 一 、先进入官网下载 https://dev.mysql.com/downloads/mysql/ 二、解压到某个文件夹 我解压到了D:\mysql\mysql8 下面 然后在这个文件夹下手动创建 my.ini 文件和 data 文件夹 my.ini 内容如下&#xff1a; 注意 basedir 和 datadi…

Docker数据管理、网络通信和Dockerfile

一.数据管理 数据卷是一个供容器使用的特殊目录&#xff0c;位于容器中。可将宿主机的目录挂载到数据卷上&#xff0c;对数据卷的修改操作立刻可见&#xff0c;并且更新数据不会影响镜像&#xff0c;从而实现数据在宿主机与容器之间的迁移。数据卷的使用类似于 Linux 下对目录…

面试算法题精讲:最长回文子串

面试算法题精讲&#xff1a;最长回文子串 题目来源&#xff1a;5. 最长回文子串 题目描述&#xff1a; 给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同&#xff0c;则该字符串称为回文字符串。 解法1&#xff1a;动态规划 对于…

ubuntu22.04安装TensorRT(过程记录)

重要说明&#xff1a;此贴经过多次修改。第一次安装的的为trt8.6.1版本。第二次安装的10.0.0.6版本。有些地方可能没改过来&#xff0c;比如链接向导&#xff0c;我懒得改了&#xff0c;但是流程是对的。 cuda和cudnn版本对应关系 tensorRT历史发行版本 CUDA历史发行版本 cudn…

【Godot4.2】有序和无序列表函数库 - myList

概述 在打印输出或其他地方可能需要构建有序或无序列表。本质就是构造和维护一个纯文本数组。并用格式化文本形式&#xff0c;输出带序号或前缀字符的多行文本。 为此我专门设计了一个类myList&#xff0c;来完成这项任务。 代码 以下是myList类的完整代码&#xff1a; # …

Android 设置头像 - 相册拍照

Android开发在个人信息管理中&#xff0c;如果设置头像&#xff0c;一般都提供了从相册选择和拍照两种方式。下午将针对设置用户头像相册和拍照两种方式的具体实现进行详细说明。 在实际实现过程中需要使用到权限管理&#xff0c;新版本的Android需要动态申请权限&#xff0c;权…

【JAVA】一文掌握Java并发编程

Java 开发中&#xff0c;并发编程属于相当重要的一个知识点&#xff0c;可以说&#xff0c;Java 的并发能力&#xff0c;是成就今日 Java 地位的因素之一。Java 的并发编程由浅入深实质上是包含 Java&#xff08;API&#xff09;层、JVM&#xff08;虚拟机&#xff09;层、内核…

Mac下使用homebrew管理多版本mysql同时启动

Mac下使用homebrew管理多版本mysql同时启动 思路 给每个版本分配不同的数据目录和配置文件即可 本文尝试了使用 brew 安装管理多个MySQL版本&#xff0c;同时运行、直接切换 安装 如果已有数据文件请自行备份以及使用 安装 mysql 5.7 brew install mysql5.7在 /opt/home…

运维笔记:基于阿里云跨地域服务器通信(上)

运维笔记 阿里云&#xff1a;跨地域服务器通信&#xff08;上&#xff09; - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this a…

C语言编译的优化等级应该选哪个?O0、O1、O2还是O3

在使用IDE开发STM32程序时&#xff0c;IDE一般都会提供优化等级设置的选项&#xff0c;例如下图中KEIL软件优化等级的设置。 从上图中也可以看出&#xff0c;设置不同的优化等级&#xff0c;实际上是修改了编译器的编译参数。这个编译器是由ARM公司提供的C/C编译器armclang或者…

opencv4.8 系列一环境搭搭建

open 运行环境&#xff1a; vs2017 下载地址&#xff1a;https://www.123pan.com/s/cVyRVv-ydPWh.html 一&#xff1a;新建项目 二&#xff1a;核心代码&#xff1a; 在这里插入代码片 #include<opencv2/opencv.hpp>int main(int argc,char** argv) {cv::Mat src cv…

【软考高项】二十六、范围管理基础内容

一、管理基础 产品范围和项目范围 产品范围强调结果&#xff0c;项目范围强调结果 管理的新实践 &#xff1a;需求一直是项目管理的关注重点&#xff0c;需求管理过程结束于需求关闭&#xff0c;即把产品、服务或成果移交给接收方&#xff0c;以便长期测量、监控、实现并维持收…

ptyhon画图显示中文

import matplotlib.pyplot as plt import matplotlib# 设置中文字体 matplotlib.rcParams[font.sans-serif] [SimHei] matplotlib.rcParams[font.family]sans-serifplt.plot([1, 2, 3, 4]) plt.xlabel(这是x轴) plt.ylabel(这是y轴) plt.title(这是标题) plt.show()用这个代码…

anaconda安装python 3.8环境

打开anaconda命令行窗口 在命令行窗口中&#xff0c;输入命令&#xff1a;conda create -n py38 python3.8 执行命令后&#xff0c;显示conda版本、安装路径和安装的包 然后提醒是否安装&#xff0c;输入y 等待安装完成。然后进入python3.8&#xff0c;执行命令&#xff1a;con…

收藏:什么是协程的通俗解析

不错的视频&#xff1a;到底该怎么理解协程&#xff1f;_哔哩哔哩_bilibili 重点的要点&#xff1a; 比如这个函数&#xff1a; python中&#xff0c;使用yield关键字来做协程&#xff0c;就是暂停可以去执行其他东西&#xff0c;然后其他东西执行完后&#xff0c;继续执行yiel…

抓包理解协议

用的Wireshark 抓包 1.抓包网卡选择 - WLAN 无线网卡&#xff0c;其他是本地虚拟机的网卡 这里分别是开始捕获、停止捕获、重新捕获、网卡选择&#xff0c;下面是可以过滤选择 过滤tcp包 3次握手&#xff1a; source是源地址&#xff0c; destination是目标地址&#xff0c;in…

Mysql用语句创建表/插入列【示例】

一、 创建表 COMMENT表示字段或列的注释 -- 新建student表 CREATE TABLE student (id BIGINT NOT NULL COMMENT 学生id, enroll_date DATE NOT NULL COMMENT 注册时间, NAME VARCHAR(18) DEFAULT NOT NULL COMMENT 学生姓名, deal_flag TINYINT(1) DEFAULT 0 NOT NULL COMM…

创新入门|从点击到转化:AI个性化登陆页助力潜在客户转化

在数字营销的竞争格局中&#xff0c;采用先进技术对于旨在区分自己并吸引受众的企业至关重要。人工智能 &#xff08;AI&#xff09; 成为一项关键技术&#xff0c;尤其是在制作个性化登录页面的艺术方面。这些页面不仅仅是品牌与其潜在客户之间的第一个接触点;它们是吸引兴趣、…