SentencePiece
SentencePiece 是一个无监督的文本分词器和去分词器,主要用于基于神经网络的文本生成系统,其中词汇表大小在神经模型训练之前预先确定。SentencePiece 实现了子词单元(例如,字节对编码(BPE)[Sennrich et al.])和unigram 语言模型[Kudo.]),并扩展了直接从原始句子训练的功能。SentencePiece 允许我们构建一个纯粹的端到端系统,不依赖于特定语言的预处理/后处理。
这不是 Google 的官方产品。
技术亮点
- 纯数据驱动:SentencePiece 从句子中训练分词和去分词模型。不总是需要预分词(Moses 分词器/MeCab/KyTea)。
- 语言无关:SentencePiece 将句子仅视为 Unicode 字符序列。没有语言相关的逻辑。
- 多种子词算法:支持 BPE [Sennrich et al.] 和 unigram 语言模型 [Kudo.]。
- 子词正则化:SentencePiece 实现了子词采样,用于子词正则化和 BPE-dropout,有助于提高 NMT 模型的鲁棒性和准确性。
- 快速且轻量:分割速度约为 50k 句/秒,内存占用约为 6MB。
- 自包含:只要使用相同的模型文件,就能获得相同的分词/去分词结果。
- 直接生成词汇 id:SentencePiece 管理词汇到 id 的映射,可以直接从原始句子生成词汇 id 序列。
- 基于 NFKC 的规范化:SentencePiece 执行基于 NFKC 的文本规范化。
对于不熟悉 SentencePiece 作为软件/算法的人,可以阅读这里的温和介绍。
与其他实现的比较
特性 | SentencePiece | subword-nmt | WordPiece |
---|---|---|---|
支持的算法 | BPE, unigram, char, word | BPE | BPE* |
开源? | 是 | 是 | Google 内部 |
子词正则化 | 是 | 否 | 否 |
Python 库 (pip) | 是 | 否 | 不适用 |
C++ 库 | 是 | 否 | 不适用 |
需要预分割? | 否 | 是 | 是 |
可自定义规范化(如 NFKC) | 是 | 否 | 不适用 |
直接 id 生成 | 是 | 否 | 不适用 |
注意,WordPiece 使用的 BPE 算法与原始 BPE 略有不同。
概述
什么是 SentencePiece?
SentencePiece 是子词单元的重新实现,这是一种缓解神经机器翻译中开放词汇问题的有效方法。SentencePiece 支持两种分割算法,字节对编码(BPE) [Sennrich et al.] 和 unigram 语言模型 [Kudo.]。以下是与其他实现的主要区别。
唯一标记的数量是预先确定的
神经机器翻译模型通常使用固定的词汇表。与大多数假设无限词汇表的无监督词分割算法不同,SentencePiece 训练分割模型时确保最终词汇表大小是固定的,例如 8k、16k 或 32k。
请注意,SentencePiece 为训练指定最终词汇表大小,这与 subword-nmt 使用合并操作次数不同。合并操作次数是 BPE 特有的参数,不适用于其他分割算法,包括 unigram、word 和 character。
从原始句子训练
以前的子词实现假设输入句子已经预先分词。这种限制是为了高效训练而必需的,但使得预处理变得复杂,因为我们必须事先运行特定语言的分词器。
SentencePiece的实现足够快,可以直接从原始句子训练模型。这对于训练中文和日语的分词器和去分词器特别有用,因为这些语言中单词之间没有明确的空格。
将空白作为基本符号处理
自然语言处理的第一步是文本分词。例如,标准的英语分词器会将文本"Hello world."分割成以下三个标记:
[Hello] [World] [.]
一个观察结果是,原始输入和分词后的序列是不可逆转换的。例如,"World"和"."之间没有空格的信息在分词序列中丢失了,因为Tokenize("World.") == Tokenize("World .")
SentencePiece将输入文本仅视为Unicode字符序列。空白也被视为普通符号处理。为了明确地将空白作为基本标记处理,SentencePiece首先用元符号"▁"(U+2581)转义空白,如下所示:
Hello▁World.
然后,将此文本分割成小片段,例如:
[Hello] [▁Wor] [ld] [.]
由于空白在分段文本中得以保留,我们可以无歧义地进行去分词。
detokenized = ''.join(pieces).replace('▁', ' ')
这一特性使得无需依赖特定语言资源就能执行去分词成为可能。
请注意,当使用标准词分割器分割句子时,我们无法应用相同的无损转换,因为它们将空白视为特殊符号。分词后的序列不保留恢复原始句子所需的必要信息。
- (英) Hello world. → [Hello] [World] [.] (Hello和World之间有空格)
- (日) こんにちは世界。 → [こんにちは] [世界] [。] (こんにちは和世界之间没有空格)
子词正则化和BPE-dropout
子词正则化[Kudo.]和BPE-dropout Provilkov等人是简单的正则化方法,通过即时子词采样虚拟增强训练数据,有助于提高神经机器翻译模型的准确性和鲁棒性。
要启用子词正则化,您需要将SentencePiece库(C++/Python)集成到神经机器翻译系统中,以便在每次参数更新时采样一种分割方式,这与标准的离线数据准备不同。以下是Python库的示例。您可以发现'New York'在每次调用SampleEncode (C++)
或encode with enable_sampling=True (Python)
时都被不同地分割。采样参数的详细信息可以在sentencepiece_processor.h中找到。
>>> import sentencepiece as spm
>>> s = spm.SentencePieceProcessor(model_file='spm.model')
>>> for n in range(5):
... s.encode('New York', out_type=str, enable_sampling=True, alpha=0.1, nbest_size=-1)
...
['▁', 'N', 'e', 'w', '▁York']
['▁', 'New', '▁York']
['▁', 'New', '▁Y', 'o', 'r', 'k']
['▁', 'New', '▁York']
['▁', 'New', '▁York']
安装
Python模块
SentencePiece提供Python包装器,支持SentencePiece训练和分割。 您可以使用以下命令安装SentencePiece的Python二进制包:
pip install sentencepiece
更多详情,请参阅Python模块
从C++源代码构建和安装SentencePiece命令行工具
构建SentencePiece需要以下工具和库:
- cmake
- C++11编译器
- gperftools库(可选,可获得10-40%的性能提升)
在Ubuntu上,可以使用apt-get安装构建工具:
% sudo apt-get install cmake build-essential pkg-config libgoogle-perftools-dev
然后,您可以按如下方式构建和安装命令行工具:
% git clone https://github.com/google/sentencepiece.git
% cd sentencepiece
% mkdir build
% cd build
% cmake ..
% make -j $(nproc)
% sudo make install
% sudo ldconfig -v
在OSX/macOS上,将最后一条命令替换为sudo update_dyld_shared_cache
使用vcpkg构建和安装
您可以使用vcpkg依赖管理器下载和安装sentencepiece:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install sentencepiece
vcpkg中的sentencepiece端口由Microsoft团队成员和社区贡献者保持最新。如果版本过时,请在vcpkg仓库上创建问题或拉取请求。
从签名发布的wheel下载并安装SentencePiece
您可以从GitHub发布页面下载wheel。 我们在发布过程中使用OpenSSF的slsa-framework/slsa-github-generator生成SLSA3签名。要验证发布二进制文件:
- 从slsa-framework/slsa-verifier#installation安装验证工具。
- 从GitHub发布页面下载来源文件
attestation.intoto.jsonl
。 - 运行验证器:
slsa-verifier -artifact-path <the-wheel> -provenance attestation.intoto.jsonl -source github.com/google/sentencepiece -tag <the-tag>
pip install wheel_file.whl
使用说明
训练SentencePiece模型
% spm_train --input=<输入文件> --model_prefix=<模型名称> --vocab_size=8000 --character_coverage=1.0 --model_type=<类型>
--input
: 每行一个句子的原始语料文件。无需运行分词器、规范化器或预处理器。默认情况下,SentencePiece使用Unicode NFKC对输入进行规范化。您可以传入以逗号分隔的文件列表。--model_prefix
: 输出模型名称前缀。将生成<模型名称>.model
和<模型名称>.vocab
文件。--vocab_size
: 词汇表大小,例如8000、16000或32000。--character_coverage
: 模型覆盖的字符比例,良好的默认值为:对于具有丰富字符集的语言(如日语或中文)使用0.9995
,对于具有较小字符集的其他语言使用1.0
。--model_type
: 模型类型。可选择unigram
(默认)、bpe
、char
或word
。使用word
类型时,输入句子必须预先分词。
使用--help
标志显示所有训练参数,或查看此处获取概览。
将原始文本编码为句子片段/ID
% spm_encode --model=<模型文件> --output_format=piece < 输入 > 输出
% spm_encode --model=<模型文件> --output_format=id < 输入 > 输出
使用--extra_options
标志插入BOS/EOS标记或反转输入序列。
% spm_encode --extra_options=eos (仅添加</s>)
% spm_encode --extra_options=bos:eos (添加<s>和</s>)
% spm_encode --extra_options=reverse:bos:eos (反转输入并添加<s>和</s>)
SentencePiece支持N-best分割和分割采样,使用--output_format=(nbest|sample)_(piece|id)
标志。
% spm_encode --model=<模型文件> --output_format=sample_piece --nbest_size=-1 --alpha=0.5 < 输入 > 输出
% spm_encode --model=<模型文件> --output_format=nbest_id --nbest_size=10 < 输入 > 输出
将句子片段/ID解码为原始文本
% spm_decode --model=<模型文件> --input_format=piece < 输入 > 输出
% spm_decode --model=<模型文件> --input_format=id < 输入 > 输出
使用--extra_options
标志以相反顺序解码文本。
% spm_decode --extra_options=reverse < 输入 > 输出
端到端示例
% spm_train --input=data/botchan.txt --model_prefix=m --vocab_size=1000
unigram_model_trainer.cc(494) LOG(INFO) 开始训练,参数为:
input: "../data/botchan.txt"
... <省略>
unigram_model_trainer.cc(529) LOG(INFO) EM子迭代=1 大小=1100 目标值=10.4973 词元数=37630 每片段词元数=34.2091
trainer_interface.cc(272) LOG(INFO) 保存模型:m.model
trainer_interface.cc(281) LOG(INFO) 保存词汇表:m.vocab
% echo "I saw a girl with a telescope." | spm_encode --model=m.model
▁I ▁saw ▁a ▁girl ▁with ▁a ▁ te le s c o pe .
% echo "I saw a girl with a telescope." | spm_encode --model=m.model --output_format=id
9 459 11 939 44 11 4 142 82 8 28 21 132 6
% echo "9 459 11 939 44 11 4 142 82 8 28 21 132 6" | spm_decode --model=m.model --input_format=id
I saw a girl with a telescope.
您可以发现原始输入句子从词汇ID序列中恢复出来。
导出词汇表列表
% spm_export_vocab --model=<模型文件> --output=<输出文件>
<输出文件>
存储词汇表列表和发射对数概率。词汇ID对应于此文件中的行号。
重新定义特殊元标记
默认情况下,SentencePiece使用未知(<unk>)、BOS(<s>)和EOS(</s>)标记,它们的ID分别为0、1和2。我们可以在训练阶段重新定义这个映射,如下所示:
% spm_train --bos_id=0 --eos_id=1 --unk_id=5 --input=... --model_prefix=... --character_coverage=...
当设置-1 ID时,例如bos_id=-1
,这个特殊标记将被禁用。请注意,未知ID不能被禁用。我们可以使用--pad_id=3
为填充(<pad>)定义一个ID。
如果您想分配其他特殊标记,请参阅使用自定义符号。
词汇限制
spm_encode
接受--vocabulary
和--vocabulary_threshold
选项,这样spm_encode
只会生成在词汇表中也出现的符号(至少有一定频率)。这个特性的背景在subword-nmt页面中有描述。
使用方法基本与subword-nmt
相同。假设L1和L2是两种语言(源语言/目标语言),训练共享的spm模型,并为每种语言获取结果词汇:
% cat {训练文件}.L1 {训练文件}.L2 | shuffle > train
% spm_train --input=train --model_prefix=spm --vocab_size=8000 --character_coverage=0.9995
% spm_encode --model=spm.model --generate_vocabulary < {训练文件}.L1 > {词汇文件}.L1
% spm_encode --model=spm.model --generate_vocabulary < {训练文件}.L2 > {词汇文件}.L2
使用shuffle
命令是为了以防万一,因为spm_train
默认加载语料库的前1000万行。
然后使用--vocabulary
选项对训练/测试语料进行分割
% spm_encode --model=spm.model --vocabulary={词汇文件}.L1 --vocabulary_threshold=50 < {测试文件}.L1 > {测试文件}.seg.L1
% spm_encode --model=spm.model --vocabulary={词汇文件}.L2 --vocabulary_threshold=50 < {测试文件}.L2 > {测试文件}.seg.L2