[BPE]论文实现:Neural Machine Translation of Rare Words with Subword Units

文章探讨了神经机器翻译(NMT)中处理罕见词的方法,特别是通过字节对编码(BPE)和使用子词单位(subwordunits)。BPE通过迭代合并最频繁的字符对改善词汇表效率,而子词方法增强翻译性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

论文:Neural Machine Translation of Rare Words with Subword Units
作者:Rico Sennrich, Barry Haddow, Alexandra Birch
时间:2016

一、完整代码

这里我们使用python仅对BPE做一个简单的实现

import re, collections  
  
  
def get_stats(vocab):  
    pairs = collections.defaultdict(int)  
    for word, freq in vocab.items():  
        symbols = word.split()  
        for i in range(len(symbols) - 1):  
            pairs[symbols[i], symbols[i + 1]] += freq  
    return pairs  
  
  
def merge_vocab(pair, v_in):  
    v_out = {}  
    bigram = re.escape(' '.join(pair))  
    p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')  
    for word in v_in:  
        w_out = p.sub(''.join(pair), word)  
        v_out[w_out] = v_in[word]  
    return v_out  
  
  
if __name__ == '__main__':  
    vocab = {'l o w </w>': 5, 'l o w e r </w>': 2, 'n e w e s t </w>': 6, 'w i d e s t </w>': 3}  
    num_merges = 10  
    for i in range(num_merges):  
        pairs = get_stats(vocab)  
        best = max(pairs, key=pairs.get)  
        vocab = merge_vocab(best, vocab)  
      
    print(vocab)

二、论文解读

word-levelNMT任务上的不足:

  • a back-off to a dictionary look-up:用字典中相似且存在于vocabulary的词计算;
  • copy:对于名字来说处理有效,但是词的形态可能会发生改变,而翻译又需要词型的信息;

这篇文章主要介绍了两个方法:

  • 使用subwords而不是word,可以有效的提高NMT的能力;
  • byte pair encoding,利用压缩算法来构建词汇表;

2.1 模型架构

encoder:一个双向的GRU网络,隐藏层的结果合并作为最终的隐藏层;
decoder:RNN,利用前馈神经网络做对其模型输出结果;

2.2 BPE

字节对编码(BPE)(Gage,1994)是一种简单的数据压缩技术,它迭代地用一个未使用的字节替换序列中最频繁的字节对。我们将该算法用于分词。我们不合并频繁的字节对,而是合并字符或字符序列。

首先,我们用字符词汇表初始化字符词汇表,并将每个单词表示为一个字符序列,再加上一个特殊的词末字符“·”,这允许我们在翻译后恢复原始的序列。我们迭代地计算所有的字符对,并将每次出现的最频繁的字符对(“A”,“B”)替换为一个新的字符“AB”。每个合并操作都会产生一个新的字符,它表示一个字符n-gram。频繁的字符n-克(或整个单词)最终被合并成一个单一的字符,因此BPE不需要候选名单。最终的字符词汇表大小等于初始词汇表的大小,再加上合并操作的数量——后者是算法中唯一的超参数。

实现代码如下:

import re, collections  
  
  
def get_stats(vocab):  
    pairs = collections.defaultdict(int)  
    for word, freq in vocab.items():  
        symbols = word.split()  
        for i in range(len(symbols) - 1):  
            pairs[symbols[i], symbols[i + 1]] += freq  
    return pairs  
  
  
def merge_vocab(pair, v_in):  
    v_out = {}  
    bigram = re.escape(' '.join(pair))  
    p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')  
    for word in v_in:  
        w_out = p.sub(''.join(pair), word)  
        v_out[w_out] = v_in[word]  
    return v_out  
  
  
if __name__ == '__main__':  
    vocab = {'l o w </w>': 5, 'l o w e r </w>': 2, 'n e w e s t </w>': 6, 'w i d e s t </w>': 3}  
    num_merges = 10  
    for i in range(num_merges):  
        pairs = get_stats(vocab)  
        best = max(pairs, key=pairs.get)  
        vocab = merge_vocab(best, vocab)  
      
    print(vocab)

学习两种独立的编码,一种用于源句子,一种用于目标句子;可以使文本和词汇大小方面更紧凑,更能保证每个子词单元都在各自语言的训练文本中看到;

学习两个词汇的联合编码,提高了源句子和目标句子分割之间的一致性;

三、过程实现

论文整体比较简单,框架已经过时,没有实现的必要;

四、整体总结

这篇文章主要介绍了两个方法:

  • 使用subwords而不是word,可以有效的提高NMT的能力;
  • byte pair encoding,利用压缩算法来构建词汇表;
<think>好的,用户想了解如何实现大语言模型的token数统计,并且希望在C++ SDK中通用地支持多种模型。首先,我需要回忆一下tokenization的基本原理。大语言模型如GPT系列使用子词切分,比如BPE算法,所以token的划分可能因模型而异。比如“hello”可能被分成“he”和“llo”两个token。 接下来,用户的需求点有两个:一是了解token划分和计数的原理,二是用C++实现通用统计。针对第一点,我需要解释清楚不同的tokenization方法,比如BPE、WordPiece、SentencePiece等,以及它们如何影响token数量。例如,不同的模型使用不同的分词器,比如GPT用BPE,BERT用WordPiece,而一些多语言模型可能用SentencePiece。 然后是统计token数的实现。用户希望用C++ SDK支持多种模型,所以需要考虑如何适配不同的分词方式。一种通用的方法可能是集成各个模型官方的分词库,比如Hugging Face的tokenizers库,但C++环境下可能需要寻找对应的库或者自己实现。不过Hugging Face的tokenizers有C++绑定,这可能是一个方向。 另外,用户可能需要处理不同模型的特殊token,比如CLS、SEP等,这些会影响计数。所以统计的时候需要确保正确处理这些情况。比如在BERT中,每个输入会自动添加[CLS]和[SEP],统计时需要包含这些。 关于通用性,可能需要设计一个接口,根据不同的模型加载对应的分词器和词汇表。比如,为每个模型提供一个配置文件,指定分词方法和相关参数,这样在C++中动态加载这些配置。但这样实现起来可能比较复杂,需要考虑各个模型的分词差异。 推荐的资料方面,Hugging Face的文档是必须的,特别是tokenizers库的说明。另外,OpenAI的tiktoken库虽然是用Python的,但原理可以借鉴。论文方面,BPE和SentencePiece的原始论文会有帮助。还有,可能需要查看各个模型的官方文档,了解它们的分词细节,比如LLaMA、GPT、BERT等。 对于C++实现,可能需要用到现有的库,比如sentencepiece的C++版本,或者Hugging Face提供的C++分词器。另外,要注意编码问题,比如UTF-8的处理,不同语言字符的分词方式。性能也是一个考虑点,尤其是在处理大量文本时,分词速度要足够快。 总结下来,步骤可能是:1. 确定目标模型的分词方法;2. 集成对应的分词库到C++ SDK;3. 处理各模型的特殊token和配置;4. 设计统一的接口来统计不同模型的token数。同时,需要测试不同案例,确保统计准确,比如处理标点符号、合并子词等情况。</think>以下分三个部分为您解答大语言模型token统计的实现方法,并提供相关学习资料推荐: 一、Token划分原理及计算方法 1. 主流分词算法 - BPE (Byte-Pair Encoding):通过迭代合并高频字符对构建词汇表(GPT系列使用) - WordPiece:基于概率合并子词单元(BERT系列使用) - SentencePiece:支持Unicode的直接文本编码(LLaMA等使用) - Unigram:基于统计概率的分解方法 2. 典型token数量计算规则 ```text 英文文本:1 token ≈ 4字符(平均) 中文文本:1汉字 ≈ 1.5-2 tokens(因分词策略而异) 特殊符号:每个标点通常独立成token 换行符:\n 通常计为1 token ``` 二、C++通用实现方案 1. 架构设计要点 ```cpp class TokenCounter { public: // 加载不同模型的分词器 bool load_model(const std::string& model_type); // 通用计数接口 size_t count_tokens(const std::string& text); private: std::unique_ptr<TokenizerBase> tokenizer_; }; ``` 2. 推荐集成方案 ```bash # 第三方库推荐 git clone https://github.com/google/sentencepiece # 支持LLaMA等模型 git clone https://github.com/huggingface/tokenizers # 提供C++绑定 ``` 3. 性能优化技巧 - 预编译常用模型的tokenizer - 实现内存缓存机制 - 使用SIMD指令加速UTF-8解码 三、推荐学习资料 1. 核心文档 - OpenAI Tokenization 指南: https://platform.openai.com/tokenizer - HuggingFace Tokenizers 文档: https://huggingface.co/docs/tokenizers - SentencePiece 原理白皮书: https://arxiv.org/abs/1808.06226 2. 实用工具 - 在线可视化工具:https://tiktokenizer.vercel.app/ - 多模型对比工具库:https://github.com/openai/tiktoken 3. 进阶论文 - 《Neural Machine Translation of Rare Words with Subword Units》 (BPE算法原始论文) - 《Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates》 实际工程中建议结合具体模型SDK实现,例如LLaMA.cpp项目的token计数实现值得参考: https://github.com/ggerganov/llama.cpp/blob/master/common/common.cpp#L229-L249 不同模型的token数量差异示例: ```text 输入:"你好,世界!" - GPT-2: 6 tokens [你, 好, ,, 世, 界, !] - LLaMA: 5 tokens [你好, ,, 世界, !] - BERT: 7 tokens [[CLS], 你, 好, ,, 世, 界, !] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值