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 AutoTokenizer

tokenizer = 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] += 1
    return word_freqs


def 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 alphabet

def compute_pair_freqs(splits, word_freqs):
	"""统计每一个对出现的频次"""
    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


def 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 = pair
            max_freq = freq
    return best_pair, max_freq


def merge_pair(a, b, splits, word_freqs):
    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

def 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 += 1
            splits[idx] = split

    return 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

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

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

相关文章

通义千问调用笔记

如何使用通义千问API_模型服务灵积(DashScope)-阿里云帮助中心 package com.ruoyi.webapp.utils;import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationOutput; import com.alibaba.dashscope.aigc.generation.G…

Element-UI - 解决el-table中图片悬浮被遮挡问题

在开发中&#xff0c;发现element-ui在el-table中添加图片悬浮显示时&#xff0c;会被单元格遮挡的问题。通过查询得到的解决办法&#xff0c;大多是修改.el-table类中相关样式属性&#xff0c;但经过验证发现会影响到其他正常功能的使用。对于此问题解决其实也并不难&#xff…

HTTP协议 快速入门

http概述 无状态性&#xff1a;HTTP是一个无状态协议&#xff0c;这意味着服务器不会在请求之间保存任何会话信息。每个请求都是独立的&#xff0c;服务器不会记住之前的请求。 请求-响应模型&#xff1a;HTTP通信是基于客户端发送请求和服务器返回响应的模型。客户端&#xf…

面向对象初级的内存分布图

1.一个对象的内存图 2.二个对象的内存图 3.二个引用指向同一个对象 4.this的内存布局图 创建了一个Student类的对象s1,因为有new,所有在堆区开辟了一些内存空间,比如把这些内存空间的地址值叫001 形参name是竹小玲, 也就是对象s调用method方法的地址值, this表示方法调用者的地…

MicroPython 环境下使用 ESP32 连接百度 AI 大模型

前言 在物联网领域&#xff0c;ESP32 由于其丰富的功能和低功耗性能成为了一种流行的选择。结合 MicroPython&#xff0c;它为开发者提供了一个高效的开发环量&#xff0c;让 Python 程序员也能轻松介入到嵌入式系统和 IoT 应用的开发之中。本文将介绍如何利用这些技术&#x…

内存-VSS、RSS、PSS、USS

一、 VSS 虚拟耗用内存大小&#xff0c;是进程可以访问的所有虚拟内存的总量&#xff0c;包括进程独自占用的物理内存、和其他进程共享的内存、分配但未使用的内存。 RSS 驻留内存大小&#xff0c;是进程当前实际占用的物理内存大小&#xff0c;包括进程独自占用的物理内存、…

【ROS里程计】中部分代码解释

bool OdomNodePub::Odom_Reset(ubt_odom::odomreset::Request& req, ubt_odom::odomreset::Response& res) {if(req.cmd "reset"){OdomResetFlag true;}else{OdomResetFlag false;}res.state "success";return true; } 该函数是一个ROS节点中…

如何判断一个js对象是否存在循环引用

一、背景 在前端JSON.stringfy是我们常用的一个方法&#xff0c;可以将一个对象序列化。 例如将如下对象序列化 const person { name: kalory, age:18}JSON.stringfy(person) // 结果 {"name":"kalory","age":18}将一个数组序列化const arr …

Modbus转Profibus网关接变频器:实现工业自动化无缝连接

一、背景 在工业自动化领域&#xff0c;Modbus和Profibus是两种常见的通讯协议&#xff0c;而变频器作为控制电机转速的重要设备。为了实现不同设备之间的无缝连接和数据传输&#xff0c;现场大多数采用Modbus转Profibus网关&#xff08;XD-MDPB100&#xff09;来解决Modbus设…

常见场景的业务逻辑漏洞以及安全设计

前言 目前常规漏洞的挖掘越来越困难&#xff0c;在这种情况下&#xff0c;我们可以多去看看业务逻辑方面的漏洞&#xff0c;也是复杂的系统&#xff0c;越有可能出现这方面的问题。本篇文章就来看看常见的一些场景下都有哪些业务漏洞。 由于本人水平有限&#xff0c;文章中可…

论文笔记:ATime-Aware Trajectory Embedding Model for Next-Location Recommendation

Knowledge and Information Systems, 2018 1 intro 1.1 背景 随着基于位置的社交网络&#xff08;LBSNs&#xff09;&#xff0c;如Foursquare和Facebook Places的日益流行&#xff0c;大量用户签到数据变得可用 这些大量签到数据的可用性带来了许多有用的应用&#xff0c;以…

【AI实践】Dify开发应用和对接微信

自定义应用 创建应用有2种&#xff0c; 从应用模板创建 空白应用&#xff0c;也就是自定义应用 选择翻译助手 Translation assistant模板创建一个应用 自定义应用&#xff0c;创建一个child_accompany_bot自定的应用&#xff0c;用来支持家长&#xff0c;如何解决低龄儿童的…

LLVM后端 td文件 tablegen 模式匹配 寄存器 指令集 calling convention

目录 一、寄存器 1.1 寄存器定义 1.2 寄存器分类 二、指令集 2.1 指令集定义 2.2 模式匹配 2.2.1 PatFrags与PatFrag 2.2.2 OutPatFrag 2.2.3 PatLeaf 2.2.4 ImmLeaf 2.2.5 IntImmLeaf和FPImmLeaf 2.2.6 Pat 2.2.7 ComplexPattern 2.3 指令合法化 2.3.1 Promote…

异常向量表的设置

1、Linux Kernel中对异常向量表的填充 linux/arch/arm64/kernel/entry.S kernel_ventry 是一个定义异常向量的宏&#xff1b; 在该宏中&#xff0c;程序跳转到了b el\el\ht()\regsize()\label; 以为异常向量的第6行为例&#xff0c;其实就是跳转到了bl el1h_64_irq; 然后你去搜…

YOLOv10涨点改进SPPF创新结构,重新设计全局平均池化层和全局最大池化层,增强全局视角信息和不同尺度大小的特征

本文改进:SPPF_improve利用全局平均池化层和全局最大池化层,加入一些全局背景信息和边缘信息,从而获取全局视角信息并减轻不同尺度大小所带来的影响,强烈推荐,适合直接使用,paper创新级。 目录 1,YOLOv10介绍 1.1 C2fUIB介绍 1.2 PSA介绍 1.3 SCDown 2.SPP &SP…

哇塞,超好吃的麻辣片,一口就爱上

最近&#xff0c;我发现了一款让人欲罢不能的美食——食家巷麻辣片&#xff01;&#x1f60d; 一打开包装&#xff0c;那浓郁的麻辣香气就扑鼻而来&#xff0c;瞬间刺激着我的嗅觉神经。&#x1f603;食家巷麻辣片的外观色泽鲜艳&#xff0c;红通通的一片&#xff0c;看着就特…

计算机组成原理之定点除法

文章目录 定点除法运算原码恢复余数法原码不恢复余数法&#xff08;加减交替法&#xff09;运算规则 习题 定点除法运算 注意 &#xff08;1&#xff09;被除数小于除数的时候&#xff0c;商0 &#xff08;2&#xff09;接下来&#xff0c;有一个除数再原来的基础上&#xff0c…

Linux2-系统自有服务防火墙与计划任务

一、什么是防火墙 防火墙主要用于防范网络攻击&#xff0c;防火墙一般分为软件防火墙、硬件防火墙 1、Windows中的防护墙设置 2、防火墙的作用 3、Linux中的防火墙分类 Centos6、Centos6>防火墙>iptables防火墙 防火墙系统管理工具 Centos7>防火墙>firewalld防火…

单目标应用:基于三角拓扑聚合优化算法TTAO的微电网优化(MATLAB代码)

一、微电网模型介绍 微电网多目标优化调度模型简介_vmgpqv-CSDN博客 参考文献&#xff1a; [1]李兴莘,张靖,何宇,等.基于改进粒子群算法的微电网多目标优化调度[J].电力科学与工程, 2021, 37(3):7 二、三角拓扑聚合优化算法求解微电网 2.1算法简介 三角拓扑聚合优化算法&…

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…