当前位置: 首页 > news >正文

NLP Subword 之 WordPiece 算法原理

本文将介绍以下 内容:

  • 1. WordPiece 与 BPE 的区别
  • 2. WordPiece 算法原理
  • 3. WordPiece 算法流程
  • 4. WordPiece 算法源码实现Demo

WordPiece 算法的核心思想是: WordPiece是基于最大化语料似然来选择扩展 token,每次从词表中选出两个子词合并成新的子词加入词表。

1. WordPiece 与 BPE 的区别:

WordPiece 是 Google 在 BERT 系列里用的分词方法,本质上和 BPE 类似,区别在于:

  • WordPiece:基于最大化语料似然来选择扩展 token;
  • BPE:基于合并 token 对的频率来扩展词表;

2. 算法原理:

假设句子 S=(t1,t2,…,tn)S = (t_{1}, t_{2}, \ldots, t_{n})S=(t1,t2,,tn) 由 n 个子词组成,tit_iti 表示子词,且假设各个子词之间是独立存在的,则句子
的语言模型似然值等价于所有子词概率的乘积:
log⁡P(S)=∑i=1nlog⁡P(ti) \log P(S) = \sum_{i=1}^{n} \log P(t_i) logP(S)=i=1nlogP(ti)
假设把相邻位置的x和y两个子词进行合并,合并后产生的子词记为z,此时句子 SSS 似然值的变化可表示为:

log⁡P(tz)−(log⁡P(tx)+log⁡P(ty))=log⁡(P(tz)P(tx)P(ty)) \log P(t_z) - \big( \log P(t_x) + \log P(t_y) \big) = \log \left( \frac{P(t_z)}{P(t_x) P(t_y)} \right) logP(tz)(logP(tx)+logP(ty))=log(P(tx)P(ty)P(tz))
从上面的公式,很容易发现,似然值的变化就是两个子词之间的互信息。简而言之,WordPiece每次选择合并的两个子词,他们具有最大的互信息值,也就是两子词在语言模型上具有较强的关联性,它们经常在语料中以相邻方式同时出现。

3. 算法流程:

将单词拆分成多个前缀符号(比如BERT中的##)最小单元,再通过子词合并规则将最小单元进行合并为字词级别。

(1)计算初始词表:通过训练语料获得或者最初的英文中的26个字母加上各种符号以及常见中文字符,这些作为初始词表。
(2)计算合并分数:对训练语料拆分的多个子词单元通过合并规则计算合并分数。
(3)执行合并:选择分数最高的子词对,将它们合并成一个新的子词单元,并更新词表。
(4)重复合并步骤:不断进行重复步骤(2)和步骤(3),直到达到预定的词表大小。
(5)分词:使用训练得到的词汇表对文本进行分词。

4. 算法源码实现Demo:

import math
import collections
from typing import List, Dictclass WordPieceTokenizerMLE:def __init__(self, unk_token="[UNK]"):self.vocab = {unk_token: 0}self.unk_token = unk_tokendef tokenize_chars(self, text: str) -> List[str]:"""基础分词:逐字切分"""return list(text.strip())def corpus_to_tokenized(self, corpus: List[str]) -> List[List[str]]:return [self.tokenize_chars(sent) for sent in corpus]def compute_likelihood(self, corpus_tokenized: List[List[str]], vocab: Dict[str, int]):"""计算 log-likelihood"""token_counts = collections.Counter()for sent in corpus_tokenized:for tok in sent:token_counts[tok] += 1total = sum(token_counts.values())loglik = 0.0for tok, c in token_counts.items():p = c / totalloglik += c * math.log(p + 1e-12)return loglikdef train(self, corpus: List[str], vocab_size: int = 50000):"""基于最大似然的 WordPiece 训练"""# 初始语料corpus_tok = self.corpus_to_tokenized(corpus)# 初始 vocab = 单字符for sent in corpus_tok:for ch in sent:if ch not in self.vocab:self.vocab[ch] = len(self.vocab)while len(self.vocab) < vocab_size:# 1. 统计相邻 pair 频率pair_counts = collections.Counter()for sent in corpus_tok:for i in range(len(sent) - 1):pair = (sent[i], sent[i + 1])pair_counts[pair] += 1if not pair_counts:break# 2. 计算所有 pair 的 ΔLbase_likelihood = self.compute_likelihood(corpus_tok, self.vocab)best_pair, best_gain = None, -1e9for pair, _ in pair_counts.items():# 尝试合并new_token = "".join(pair)temp_corpus = []for sent in corpus_tok:new_sent = []i = 0while i < len(sent):if i < len(sent) - 1 and (sent[i], sent[i+1]) == pair:new_sent.append(new_token)i += 2else:new_sent.append(sent[i])i += 1temp_corpus.append(new_sent)# 计算新的 log-likelihoodnew_ll = self.compute_likelihood(temp_corpus, self.vocab)delta = new_ll - base_likelihoodif delta > best_gain:best_gain = deltabest_pair = pairbest_new_corpus = temp_corpusif best_pair is None:break# 3. 接受增益最大的合并new_token = "".join(best_pair)self.vocab[new_token] = len(self.vocab)corpus_tok = best_new_corpusprint(f"Add token: {new_token}, ΔL={best_gain:.4f}")self.corpus_tok = corpus_tok  # 保存最后的分词表示def tokenize(self, text: str) -> List[str]:"""贪心匹配 WordPiece (不强制加##,因为训练没加过)"""tokens = []chars = self.tokenize_chars(text)i = 0while i < len(chars):match = Nonefor j in range(len(chars), i, -1):  # 尝试最长匹配substr = "".join(chars[i:j])if substr in self.vocab:match = substri = jbreakif match is None:tokens.append(self.unk_token)i += 1else:tokens.append(match)return tokensif __name__ == "__main__":corpus = ["我爱中国","中国人友好","I love China",]tokenizer = WordPieceTokenizerMLE()tokenizer.train(corpus, vocab_size=30)print("Vocab:", tokenizer.vocab)print("Tokenize 示例:", tokenizer.tokenize("我爱中国人"))

文章转载自:

http://qMu81fAS.wpsfc.cn
http://07rSaVA3.wpsfc.cn
http://oyqg20xu.wpsfc.cn
http://PIfr6SIU.wpsfc.cn
http://QpWgUgL9.wpsfc.cn
http://0OcImCTV.wpsfc.cn
http://1YgoRmXs.wpsfc.cn
http://SRCVz2WN.wpsfc.cn
http://wPqjr3PO.wpsfc.cn
http://AWsv2zC9.wpsfc.cn
http://kTM54wQY.wpsfc.cn
http://0u7ycLV6.wpsfc.cn
http://NZfLHPLN.wpsfc.cn
http://jQaCbsTY.wpsfc.cn
http://WD2Quh4k.wpsfc.cn
http://YtyViZ8e.wpsfc.cn
http://gprREBCB.wpsfc.cn
http://ndzgDbe7.wpsfc.cn
http://velxjULW.wpsfc.cn
http://KiODHaJF.wpsfc.cn
http://v16ejJGT.wpsfc.cn
http://2QcvyAeU.wpsfc.cn
http://Fq8EUAVA.wpsfc.cn
http://sXxk66fu.wpsfc.cn
http://6hLuw7e2.wpsfc.cn
http://1NGur5VI.wpsfc.cn
http://t8rEr3mv.wpsfc.cn
http://2rLTLaGB.wpsfc.cn
http://7LJQIpu8.wpsfc.cn
http://rJ0IGK7T.wpsfc.cn
http://www.dtcms.com/a/384725.html

相关文章:

  • 【SQL】MySQL中空值处理COALESCE函数
  • Kafka实时数据管道:ETL在流式处理中的应用
  • VBA数据结构深度解析:字典对象与集合对象的性能终极对决
  • 查看当前虚拟环境中安装的 PyTorch 版本
  • 布尔运算-区间dp
  • WWW‘25一通读 |图Anomaly/OOD检测相关文章(1)
  • 视频分类 pytorchvideo
  • RabbitMQ 基础概念与原理
  • 专题:2025中国消费市场趋势与数字化转型研究报告|附360+份报告PDF、数据仪表盘汇总下载
  • 预制菜行业新风向:企业运营与商家协同发展的实践启示
  • 晶台光耦 KL6N137 :以精密光电技术驱动智能开关性能提升
  • 贪心算法应用:最短作业优先(SJF)调度问题详解
  • javaee初阶 文件IO
  • 如何调整滚珠丝杆的反向间隙?
  • Python项目中的包添加后为什么要进行可编辑安装?
  • daily notes[45]
  • 基于51单片机的蓝牙体温计app设计
  • Git版本控制完全指南
  • 【CSS】一个自适应大小的父元素,如何让子元素的宽高比一直是2:1
  • 前端通过地址生成自定义二维码实战(带源码)
  • Android Doze低电耗休眠模式 与 WorkManager
  • 用 Go 重写 adbkit:原理、架构与实现实践
  • 通过Magisk service.d 脚本实现手机开机自动开启无线 ADB
  • NineData社区版 V4.5.0 正式发布!运维中心新增细粒度任务权限管理,新增MySQL至Greenplum全链路复制对比
  • centos配置环境变量jdk
  • 基于“能量逆流泵“架构的220V AC至20V DC 300W高效电源设计
  • 归一化实现原理
  • 云原生安全如何构建
  • 条件生成对抗网络(cGAN)详解与实现
  • Mysql杂志(十六)——缓存池