tokenization(二)子词切分方法

文章目录

    • 概述
    • BPE
      • 构建词表
      • 词元化
      • 代码实现
    • WordPiece
    • Unigram
      • 估算概率(E)
      • 删除词元(M)
    • 参考资料

概述

接上回,子词词元化(Subwords tokenization)是平衡字符级别和词级别的一种方法,也是目前用得最多的方法。
子词词元化的目标有2个:
● 常见词不应该切分为更小的单元
● 罕见词应该被分解为有意义的子词

BPE

BPE(Byte-Pair Encoding)最早用于数据压缩[3],后面由论文[4]将其应用于切词。模型词表通过统计出现频次最高的词或子词而构成,可以达到子词词元化的2个目标。BPE分为两步:
● 构建词表:根据预料构建词表,可理解为训练。
● 词元化:对文本利用上述词表进行词元化,可理解为推理。

字节级(Byte-level)BPE 通过将字节视为合并的基本符号,用来改善多语言语料库(例如包含非ASCII字符的文本)的分词质量。GPT-2、BART 和 LLaMA 等大语言模型都采用了这种分词方法

构建词表

最初,BPE按照所有单词的字符表作为初始词表,将每个单词切分成字符序列,然后每次迭代选取出现次数最多的字符对加入词表,直到没有可合并的字符或者词表到预设的大小为止。

这里具体构建过程以Huggingface上的例子说明,假设单词和出现的频次如下:

("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)

BPE构建词表过程如下图所示:
在这里插入图片描述

词元化

该过程可以理解为推理,应用上面的词表将新文本进行词元化。

代码实现

对Huggingface上的代码稍加整理、并增加了一些注释:

from collections import defaultdict
from transformers import AutoTokenizertokenizer = AutoTokenizer.from_pretrained("gpt2")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.",
]def stat_word_freqs():"""统计语料中的词频"""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] += 1return word_freqsdef stat_alphabet(word_freqs):"""获取所有的字符"""alphabet = []for word in word_freqs.keys():for letter in word:if letter not in alphabet:alphabet.append(letter)alphabet.sort()return alphabetdef compute_pair_freqs(splits, word_freqs):"""统计每一个对出现的频次"""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_freqsdef pick_best_pais(pair_freqs):best_pair = ""max_freq = None# 找到出现频次最多的对for pair, freq in pair_freqs.items():if max_freq is None or max_freq < freq:best_pair = pairmax_freq = freqreturn best_pair, max_freqdef merge_pair(a, b, splits, word_freqs):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 splitsdef make_vacab(vocab, merges, splits, word_freqs, vocab_size=20):"""制作词表"""while len(vocab) < vocab_size:pair_freqs = compute_pair_freqs(splits, word_freqs)best_pair, _ = pick_best_pais(pair_freqs)splits = merge_pair(*best_pair, splits, word_freqs)merges[best_pair] = best_pair[0] + best_pair[1]vocab.append(best_pair[0] + best_pair[1])def tokenize(text, merges):"""对文本进行词元切分"""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 += 1splits[idx] = splitreturn sum(splits, [])def main():# 1. 统计词频word_freqs = stat_word_freqs()print('word_freqs=', word_freqs)# 2. 统计字符表alphabet = stat_alphabet(word_freqs)vocab = ["<|endoftext|>"] + alphabet.copy()splits = {word: [c for c in word] for word in word_freqs.keys()}print('splits=', splits)print('alphabet=', alphabet)merges = {}# 3. 根据语料制作词表make_vacab(vocab, merges, splits, word_freqs)print('merges=', merges)print('vocab=', vocab)# 应用词元化res  = tokenize("This is not a token.", merges)# 输出:['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.']print(res)if __name__ == '__main__':main()

WordPiece

BERT中使用的WordPiece方法进行词元化,其思想和BPE类似。主要有以下不同点:

  1. 使用##代表非开始字符,如“word”按照字符切分为:
    w ##o ##r ##d
    
  2. 在合并字符对的时候,BPE使用的是出现最多的对,而“WordPiece”选择依据如下所示:
    s c o r e = # p a i r # f i r s t _ e l e m e n t × # s e c o n d _ e l e m e n t score=\frac{\#pair}{\#first\_element \times \#second\_element} score=#first_element×#second_element#pair
    使用BPE中的例子,切分后的语料如下所示:
    ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5)
    
    按照上述计算方式,应该合并“##g”和“##s”。
    • BPE选中的“ug”对得分为: s c o r e u g = 25 36 × 25 = 1 36 score_{ug}=\frac{25}{36 \times 25}=\frac{1}{36} scoreug=36×2525=361
    • “gs”对得分为: s c o r e g s = 5 20 × 5 = 1 20 score_{gs}=\frac{5}{20 \times 5}=\frac{1}{20} scoregs=20×55=201

Unigram

T5、XLNet等模型使用Unigram词元化方法。Unigram的思想和前两种词元化方法截然不同,最刚开始尽可能找到所有的子词,然后不断地删除,直到达到设定的词表大小为止。

Unigram方法本质上是一个基于词袋的统计语言模型。
使用之前的例子:

("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)

我们可以通过子串的方式得到最原始的词表:

["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"]

然后通过不断地迭代删除词元,直到达到设定的词表大小为止。采用期望最大化(EM)算法进行迭代。

估算概率(E)

该步骤找到最佳的切分方式,即需要计算每一种可能切分的概率,选取概率最大的切分。概率计算方式为每个次元概率相乘,如对于“pug”,其中一种切分方式的概率计算如下:
p ( “ p " , “ u " , “ g " ) = p ( “ p " ) × p ( “ u " ) × p ( “ g " ) = 5 210 × 36 210 × 20 210 = 0.000389 p(“p", “u", “g")=p(“p")\times p(“u") \times p(“g")=\frac{5}{210} \times \frac{36}{210} \times \frac{20}{210}=0.000389 p(p",u",g")=p(p")×p(u")×p(g")=2105×21036×21020=0.000389
同理,可以计算出其它2种切分的概率:

["p", "u", "g"]: 0.000389
["p", "ug"]: 0.0022676
["pu", "g"]: 0.0022676

从以上选取概率最大的切分方式,如果一样泽随机选。在实际使用中,所有可能切分方式可以使用维特比算法得到。

删除词元(M)

即一次计算每一个词元的损失,然后删除损失最小的词元。我们使用-logP计算得到语料中每个词的词元切分及得分:

"hug": ["hug"] (score 0.071428)
"pug": ["pu", "g"] (score 0.007710)
"pun": ["pu", "n"] (score 0.006168)
"bun": ["bu", "n"] (score 0.001451)
"hugs": ["hug", "s"] (score 0.001701)

假设删除“hug”,相关词得分变化:

"hug": ["hu", "g"] (score 0.006802)
"hugs": ["hu", "gs"] (score 0.001701)

可以计算出该词元删除后增加的损失为:

- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5

同理可以计算出在这次的迭代中应该删除词元pu,使其总体损失最小。

不断迭代E-M,直到词表到设定大小为止。

参考资料

  1. Huggingface NLP course
  2. 大规模语言模型:从理论到实践 – 张奇、桂韬、郑锐、黄萱菁
  3. A New Algorithm for Data Compression
  4. Neural Machine Translation of Rare Words with Subword Units

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

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

相关文章

网络通信架构

BS架构/CS架构 使用协议分别对应&#xff1a; TCP / HTTP 在计算机网络和软件开发中&#xff0c;CS架构&#xff08;Client-Server Architecture&#xff0c;客户端-服务器架构&#xff09;和BS架构&#xff08;Browser-Server Architecture&#xff0c;浏览器-服务器架构&am…

云和运维(SRE)的半生缘-深读实证02

这个标题不算太夸张&#xff0c;云计算和很多IT岗位都有缘&#xff0c;但是和运维&#xff08;SRE&#xff09;岗位的缘分最深。 “深读实证”系列文章都会结合一些外部事件&#xff0c;点明分析《云计算行业进阶指南》书中的内容。本次分享介绍了下列内容&#xff1a; 我以运维…

Matlab电话按键拨号器设计

前言 这篇文章是目前最详细的 Matlab 电话按键拨号器设计开源教程。如果您在做课程设计或实验时需要参考本文章&#xff0c;请注意避免与他人重复&#xff0c;小心撞车。博主做这个也是因为实验所需&#xff0c;我在这方面只是初学者&#xff0c;但实际上&#xff0c;从完全不…

ffmpeg学习

学习视频 帧率 帧率也叫做帧频率&#xff0c;帧率是视频文件中每一秒的帧数&#xff0c;肉眼想看到连续移动图像至少需要15帧。 码率 比特率&#xff08;也叫码率&#xff0c;数据率&#xff09;是一个确定整体视频/音频质量的参数&#xff0c;秒为单位处理的字节数&#x…

USB2.0高速转接芯片CH347应用开发手册

CH347应用开发手册 V1.3 一、简介 CH347是一款USB2.0高速转接芯片&#xff0c;以实现USB-UART(HID串口/VCP串口)、USB-SPI、USB-I2C、USB-JTAG以及USB-GPIO等接口&#xff0c;分别包含在芯片的四种工作模式中。 CH347DLL用于为CH347芯片提供操作系统端的UART/SPI/I2C/JTAG/B…

测试开发工程师<职业规划方向>

测试开发工程师的职业规划 一、引言 测试开发工程师是软件开发团队中不可或缺的角色&#xff0c;他们负责确保软件的质量和稳定性。随着软件行业的快速发展&#xff0c;测试开发工程师的职业前景也越来越广阔。本文将详细探讨测试开发工程师的职业规划&#xff0c;包括可能的职…

Python:线性查找法

什么是线性搜索算法&#xff1f; 线性搜索算法是一种基本的搜索技术&#xff0c;用于查找目标元素是否存在于一个集合&#xff08;通常是列表或数组&#xff09;中。该算法的工作原理非常简单&#xff1a;它从集合的第一个元素开始逐个检查&#xff0c;直到找到目标元素或遍历完…

Linux_应用篇(17) FrameBuffer 应用编程

本章学习 Linux 下的 Framebuffer 应用编程&#xff0c; 通过对本章内容的学习&#xff0c; 大家将会了解到 Framebuffer 设备究竟是什么&#xff1f;以及如何编写应用程序来操控 FrameBuffer 设备。 本章将会讨论如下主题。 ⚫ 什么是 Framebuffer 设备&#xff1f; ⚫ LCD 显…

git:切换到指定的commit

背景 今天合并人家代码的时候&#xff0c;报冲突了&#xff0c;然后解决完冲突以后&#xff0c;发现有个冲突干错了&#xff0c;但是都已经commit了&#xff0c;所以我就想那就回到这次merge之前的我的分支的commit 解决过程 取消合并 我先查了能否直接取消合并&#xff0c…

N32G031 ADC初始化

目录 1. ADC初始化概述 2. ADC初始化详细步骤 2.1 ADC配置 2.2 ADC初始化函数调用 2.3 DMA配置&#xff08;可选&#xff09; 3. 初始化结果验证 4. 注意事项 ADC采样注意事项 1. ADC初始化概述 在N32G031单片机中&#xff0c;ADC的初始化是确保ADC模块能够正常工作的…

JS中判断一个字符串中出现次数最多的字符,统计这个次数?

在JavaScript中&#xff0c;要判断一个字符串中出现次数最多的字符并统计这个次数&#xff0c;你可以通过创建一个对象来记录每个字符出现的次数&#xff0c;然后遍历这个对象以找到出现次数最多的字符。下面是一个简单的示例代码&#xff1a; function findMostFrequentChar(…

安卓在Fragment控制状态栏显示隐藏

废话不多上效果 隐藏 显示 核心代码 首先是Framgrent package com.zx.tab;import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button;impor…

【redis】Redis的经典使用场景

目录 1.最常见——缓存2.数据共享分布式3.分布式锁4.全局ID5.计数器6.限流7.位统计8.购物车9.用户消息时间线timeline10.消息队列11.抽奖点赞、签到、打卡13.商品标签14.商品筛选15.用户关注、推荐模型16排行榜 1.最常见——缓存 数据类型&#xff1a;string例如&#xff1a;热…

给Windows软件添加异常捕获模块生成dump文件(附源码)

软件在运行过程中会时常发生内存越界、内存访问为例、stack overflow线程栈溢出、空指针与野指针等异常崩溃,仅仅是依靠Debug和Release下的调试是远远不够的,因为有些崩溃不是必现的,或者是Debug下很难出现的。所以我们需要在软件中添加异常捕获的模块,在捕获到异常时生成包…

1、C++编程中的基本运算 - 课件

一、基础知识 1、C程序的基本框架 // 预处理器指令&#xff0c;引入需要的头文件 #include <iostream> // 使用标准命名空间 using namespace std; // 主函数&#xff0c;程序的入口 int main() {// 局部变量声明// 程序逻辑代码// 返回值&#xff0c;表示程序正常结束…

C 语言连接MySQL 数据库

前提条件 本机安装MySQL 8 数据库 整体步骤 第一步&#xff1a;开启Windows 子系统安装Ubuntu 22.04.4&#xff0c;安装MySQL 数据库第三方库执行 如下命令&#xff1a; sudo aptitude install libmysqlclient-dev wz2012LAPTOP-8R0KHL88:/mnt/e/vsCode/cpro$ sudo aptit…

鸿蒙求职面试内容总结——6月3日ZR的FS项目

最近接到了一些公司的入职面试邀约&#xff0c;这里略去公司的和项目的名字&#xff0c;做一些整理分享。 一、长列表如何实现部分渲染&#xff0c;使用的是哪一个API 在鸿蒙系统中&#xff0c;可以使用List组件来实现长列表的部分渲染。List组件支持使用条件渲染、循环渲染、…

docker一些常用命令以及镜像构建完后部署到K8s上

docker一些常用命令以及镜像构建完后部署到K8s上 1.创建文件夹2.删除文件3.复制现有文件内容到新建文件4.打开某个文件5.查看文件列表6.解压文件&#xff08;tar格式&#xff09;7.解压镜像8.查看镜像9.删除镜像10.查看容器11.删除容器12.停止运行容器13.构建镜像14.启动容器15…

英伟达开源最强通用模型Nemotron-4 340B

英伟达的通用大模型 Nemotron&#xff0c;开源了最新的 3400 亿参数版本。 本周五&#xff0c;英伟达宣布推出 Nemotron-4 340B。它包含一系列开放模型&#xff0c;开发人员可以使用这些模型生成合成数据&#xff0c;用于训练大语言模型&#xff08;LLM&#xff09;&#xff0…

Web开发技能树-HTML-class/id/name/tag

1 需求 需求1&#xff1a;CSS查找HTML元素 *tagclassid派生选择器 需求2&#xff1a;JavaScript查找HTML元素 通过id找到HTML元素&#xff1a;document.getElementById()通过标签名找到HTML元素&#xff1a;getElementsByTagName()通过类名找到HTML元素:document.getElemen…