深度学习系列28:BPE分词模型

这个是当前英文分词的标准方法了。

1. 简介

在NLP模型中,输入通常是一个句子,例如I went to New York last week.。传统做法:空格分隔,例如[‘i’, ‘went’, ‘to’, ‘New’, ‘York’, ‘last’, ‘week’]。
BPE算法通过训练,能够把[‘loved’, ‘loving’, ‘loves’]拆分成[“lov”, “ed”, “ing”, “es”]几个部分,这样可以把词的本身的意思和时态分开,有效的减少了此表的数量。算法流程如下:

1)设定最大subwords个数
2)将所有单词拆分为单个字符,并且在最后添加一个停止符,同时标记处该单词出现的次数。例如,"low"这个单词出现了5次,那么它将会被处理为{‘l o w ’: 5}
3)统计每一个连续字节对的出现频率,选择最高频者合成新的subword
4)重复第3步直到达到第1步设定的subwords词表大小或下一个最高频的字节对出现频率为1

2. 例子

{‘l o w ’: 5, ‘l o w e r ’: 2, ‘n e w e s t ’: 6, ‘w i d e s t ’: 3}

出现最频繁的字节对是e和s,共出现了 6+3 = 9次,因此将它们合并

{‘l o w ’: 5, ‘l o w e r ’: 2, ‘n e w es t ’: 6, ‘w i d es t ’: 3}

出现最频繁的字节对是es和t,共出现了6+3=9次,所以将它们合并

{‘l o w ’: 5, ‘l o w e r ’: 2, ‘n e w est ’: 6, ‘w i d est ’: 3}

出现最频繁的字节对是est和,共出现了6+3=9次,因此将它们合并

{‘l o w ’: 5, ‘l o w e r ’: 2, ‘n e w est’: 6, ‘w i d est’: 3}

出现最频繁的字节对是l和o,共出现了5+2 = 7 次,因此将它们合并

{‘lo w ’: 5, ‘lo w e r ’: 2, ‘n e w est’: 6, ‘w i d est’: 3}

3. 简单的实现

import re, collections

# 生成初始词汇字典
def get_vocab(filename):
    vocab = collections.defaultdict(int)
    with open(filename, 'r', encoding='utf-8') as fhand:
        for line in fhand:
            words = line.strip().split()
            for word in words:
                vocab[' '.join(list(word)) + ' </w>'] += 1
    return vocab

# 获取所有字节对的频率(找出最大的进行合并)
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

def get_tokens(vocab):
    tokens = collections.defaultdict(int)
    for word, freq in vocab.items():
        word_tokens = word.split()
        for token in word_tokens:
            tokens[token] += freq
    return tokens

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}

# Get free book from Gutenberg
# wget http://www.gutenberg.org/cache/epub/16457/pg16457.txt
# vocab = get_vocab('pg16457.txt')

print('==========')
print('Tokens Before BPE')
tokens = get_tokens(vocab)
print('Tokens: {}'.format(tokens))
print('Number of tokens: {}'.format(len(tokens)))
print('==========')

num_merges = 5
for i in range(num_merges):
    pairs = get_stats(vocab)
    if not pairs:
        break
    best = max(pairs, key=pairs.get)
    vocab = merge_vocab(best, vocab)
    print('Iter: {}'.format(i))
    print('Best pair: {}'.format(best))
    tokens = get_tokens(vocab)
    print('Tokens: {}'.format(tokens))
    print('Number of tokens: {}'.format(len(tokens)))
    print('==========')

4. 编码和解码

在之前的算法中,我们已经得到了 subword 的词表,对该词表按照字符个数由多到少排序。编码时,对于每个单词,遍历排好序的子词词表寻找是否有 token 是当前单词的子字符串,如果有,则该 token 是表示单词的 tokens 之一

我们从最长的token迭代到最短的token,尝试将每个单词中的子字符串替换为token。最终,我们将迭代所有的tokens,并将所有子字符串替换为tokens。 如果仍然有子字符串没被替换但所有token都已迭代完毕,则将剩余的子词替换为特殊token,如

例如

# 给定单词序列
["the</w>", "highest</w>", "mountain</w>"]

# 排好序的subword表
# 长度 6         5           4        4         4       4          2
["errrr</w>", "tain</w>", "moun", "est</w>", "high", "the</w>", "a</w>"]

# 迭代结果
"the</w>" -> ["the</w>"]
"highest</w>" -> ["high", "est</w>"]
"mountain</w>" -> ["moun", "tain</w>"]

解码
将所有的tokens拼在一起即可,例如

# 编码序列
["the</w>", "high", "est</w>", "moun", "tain</w>"]

# 解码序列
"the</w> highest</w> mountain</w>"
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值