Subword

[1] Unigram

[2] SentencePiece

Tokenizer

语言模型的第一步, 实际上是将句子 token 化, 然后向量化, 然后才有后面的一步步处理. 之前看论文的时候, 单纯的以为就是把每个单词作为一个 token 就好了. 比如 "My name is MTandHJ" 转换为: [My, name, is, MTandHJ]. 显然它有如下的问题:

  1. 对于中文可能还好一点, 对于英文它的词汇表将异常的庞大;
  2. 泛化性很差. 实际上, 你很难想象一个词汇表中恰好有 'MTandHJ' 这个词, 实际中, 'MTandHJ' 应当进一步分解为 [M, T, and, H, J].

另一种策略是将 [a-z, A-Z] 作为词汇表, 很显然绝大部分都能表示, 但是:

  1. 这样 token 化后的句子会非常的冗长;
  2. embeddings 几乎表达不了什么关系, 很难指望模型有很强的能力.

所以我们希望: 在限定的词汇表大小下, 尽可能'短'地 token 化句子.

我们知道, 在 encoding 方面, 大抵根据 '词频' 来分配编码长度, 一般来说, 出现频率越高, 编码长度越少. 放在这里, 可以这么理解, 对于那些高频次, 如 'we, you, love, peace' 等需要尽可能少拆分, 将原词作为 token; 而对于低频次, 比如 'compositionality' 往往就要被拆分多次, 如变成 [composition, al, ity].

Byte-Pair-Encoding (BPE)

  • BPE 初始化词汇表为 [a-z, A-Z] 等;
  • 然后依照词频, 将最有可能相邻的两个词拼在一起作为新词, 如 'a', 'n' -> 'an';
  • 重复直到达到词汇表的上限.

Unigram

  • Unigram 主要是为了解决如下的痛点:

  • 对于 'Hello world', 我们可能有多种编码方式. 作者采用采样的方式来解决.

  • 假设我们有语料库 D={X(s)}s=1|N|, 这里 X(s) 为一个句子. 我们需要涉及一个表将句子 token 化: X(s)T(s). 由于上述问题的存在, 我们的映射不是唯一的:

    P(T|X(s))

    不一定非零即一.

  • 故训练中, 作者希望:

    T(s)P(T|X(s))

    而非是固定的.

  • 现在的任务是, 如何估计 P(T|X(s)). 作者假设

    P(T|X(s))=TS(X(s))P(T)=TS(X(s))i=1|T|P(ti).

    其中 S(X(s)) 返回所有可能的编码方式. 故我们所要作的是估计 P(ti)

  • 作者用 EM 算法估计, 要求 P(t) 最大化:

    L=s=1|D|log(P(X(s)))=s=1|D|log(TS(X(s))i=1|T|P(ti)).

  • 有一个问题是, 词汇表我们是不知道的, 所以作者采用一种启发式的方法, 动态更新词汇表 (具体方法见 [1]).

SentencePiece

  • SentencePiece 是一个更加综合性的工具, 它的流程如下:

  • 和其它方法相比:

  • 一个比较特别地方是, sentencepiece 用 "_" 代表空格.

使用

pip install sentencepiece
  • Training: 首先我们需要给定语料库训练:

import sentencepiece as spm

spm.SentencePieceTrainer.train(input="botchan.txt", model_prefix="botchan_tokenizer", vocab_size=4000)

# 会生成:
# - botchan_tokenizer.model
# - botchan_tokenizer.vocab
  • 使用:
spp = spm.SentencePieceProcessor(model_file='botchan_tokenizer.model')

outs = spp.encode("Try it first now !")
# outs: [415, 586, 15, 174, 191, 17, 82]

spp.encode_as_pieces("Try it first now !")
# outs: ['▁T', 'ry', '▁it', '▁first', '▁now', '▁', '!']

# 输出 5 中最好的编码
spp.nbest_encode("Try it first now !", nbest_size=5, out_type=str)
# [['▁T', 'ry', '▁it', '▁first', '▁now', '▁', '!'],
#  ['▁T', 'r', 'y', '▁it', '▁first', '▁now', '▁', '!'],
#  ['▁', 'T', 'ry', '▁it', '▁first', '▁now', '▁', '!'],
#  ['▁T', 'ry', '▁it', '▁first', '▁no', 'w', '▁', '!'],
#  ['▁T', 'ry', '▁', 'it', '▁first', '▁now', '▁', '!']]

spp.decode([415, 586, 15, 174, 191, 17, 82])
# 'Try it first now !'

spp.get_piece_size()
# 4000

还有如: piece_to_id, id_to_piece, encode_as_immutable_proto 等方法.

见 [here].

posted @   馒头and花卷  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2019-07-23 ZFNet: Visualizing and Understanding Convolutional Networks
点击右上角即可分享
微信分享提示