【中文分词系列】 5. 基于语言模型的无监督分词
By 苏剑林 | 2016-09-12 | 148804位读者 |迄今为止,前四篇文章已经介绍了分词的若干思路,其中有基于最大概率的查词典方法、基于HMM或LSTM的字标注方法等。这些都是已有的研究方法了,笔者所做的就只是总结工作而已。查词典方法和字标注各有各的好处,我一直在想,能不能给出一种只需要大规模语料来训练的无监督分词模型呢?也就是说,怎么切分,应该是由语料来决定的,跟语言本身没关系。说白了,只要足够多语料,就可以告诉我们怎么分词。
看上去很完美,可是怎么做到呢?《2.基于切分的新词发现》中提供了一种思路,但是不够彻底。那里居于切分的新词发现方法确实可以看成一种无监督分词思路,它就是用一个简单的凝固度来判断某处该不该切分。但从分词的角度来看,这样的分词系统未免太过粗糙了。因此,我一直想着怎么提高这个精度,前期得到了一些有意义的结果,但都没有得到一个完整的理论。而最近正好把这个思路补全了。因为没有查找到类似的工作,所以这算是笔者在分词方面的一点原创工作了。
语言模型 #
首先简单谈一下语言模型。
很多数据挖掘的读者都已经听说过Word2Vec,知道它是一个能够生成词向量的工具,很多人也知道将词向量作为模型的特征来进行输入。但相信不少读者不知道为什么会有词向量,为什么Word2Vec能生成词向量。Word2Vec本身的光芒(Google出品、速度快、效果也不错、在Python中有很好实现等)已经把同类产品以及背后的原理都给掩盖下去了。事实上,词向量的初衷,是为了更好地生成语言模型,最经典的文章应该是深度学习的鼻祖之一——Bengio——的《A Neural Probabilistic Language Model》。这一段的重点是谈语言模型,不是词向量。关于词向量,有兴趣的读者可以参考下面的文章:
Deep Learning in NLP (一)词向量和语言模型:
http://licstar.net/archives/328火光摇曳的《我们是这样理解语言的》系列:
http://www.flickering.cn/?s=我们是这样理解语言的
语言模型是计算条件概率
$$p(w_n|w_1,w_2,\dots,w_{n-1})$$
的模型,其中$w_1,w_2,\dots,w_{n-1}$是句子中的前$n-1$个词(或字),$w_n$是第$n$个词(或字)。语言模型在很多方面都有应用,比如说分词、语音识别、机器翻译等。为了得到语言模型,有很多方法,比如说最简单的是“统计+平滑”的方法,还有最大熵语言模型、CRF语言模型等,而当前深度学习框架下研究得很多的是“神经网络语言模型”,它的大概思路是:$p(w_n|w_1,w_2,\dots,w_{n-1})$是关于$w_1,w_2,\dots,w_n$的一个函数,这个函数的具体形式我不知道,所以利用神经网络去拟合它,为了更好地拟合,并且减少模型参数,还把词语“嵌入”到实数空间中,用短向量来表示词语,跟语言模型一起训练。从这个角度看,词向量只是语言模型的副产品。
语言模型生成的词向量能够较好地表示语义,这是很有趣的,却也是在情理之中。什么是语义?对人类来说,语义是一种推理和理解的过程,而我们的语言模型,就是从前$n-1$个字推测下一个字,这也是一个推理的过程。既然包含了推理成分在里边,就有可能捕捉到语义了。
无监督分词 #
说语言模型似乎说得有点多了,不过,本文要介绍的分词方法,就是以“基于字的语言模型”为基础的。
我们从最大概率法出发,如果一个长度为$l$的字符串$s_1, s_2, \dots, s_l$,最优分词结果为$w_1, w_2, \dots, w_m$,那么它应该是所有切分中,概率乘积
$$p(w_1)p(w_2)\dots p(w_m)$$
最大的一个。
假如没有词表,自然也就不存在$w_1, w_2, \dots, w_m$这些词了。但是,我们可以用贝叶斯公式,将词的概率转化为字的组合概率:
$$p(w)=p(c_1)p(c_2|c_1)p(c_3|c_1 c_2)\dots p(c_k|c_1 c_2 \dots c_{k-1})$$
其中$w$是一个$k$字词,$c_1,c_2,\dots,c_k$分别是$w$的第$1,2,\dots,k$个字。可以发现,$p(c_k|c_1 c_2 \dots c_{k-1})$就是我们前面提到过的字的语言模型。
当然,对于很大的$k$,$p(c_k|c_1 c_2 \dots c_{k-1})$还是不容易估算的,不过幸好按照我们的经验,词的平均长度不会很大,因此,我们只需要用n-gram语言模型就够了,其中$n$为4时效果就挺不错了。
那分词具体又是怎么操作呢?假如字符串$s_1, s_2, s_3\dots, s_l$,如果不进行切分,那么它的路径概率应该是
$$p(s_1)p(s_2)p(s_3)\dots p(s_l)$$
如果$s_1, s_2$应该合并为一个词,那么它的路径概率是
$$p(s_1 s_2)p(s_3)\dots p(s_l)=p(s_1)p(s_2|s_1)p(s_3)\dots p(s_l)$$
如果$s_2, s_3$应该合并为一个词,那么它的路径概率是
$$p(s_1)p(s_2 s_3)\dots p(s_l)=p(s_1)p(s_2)p(s_3|s_2)\dots p(s_l)$$
如果$s_1, s_2, s_3$应该合并为一个词,那么它的路径概率是
$$p(s_1 s_2 s_3)\dots p(s_l)=p(s_1)p(s_2|s_1)p(s_3|s_1 s_2)\dots p(s_l)$$
看到特点了吗?每一种切分方式,事实上都对应着$l$个条件概率的相乘,我们就是从这些条件概率的相乘模式中,找出结果最大的那个。而同样的,如果我们知道了最优的相乘模式,就可以对应地写出分词结果来。
更系统地看,其实就是将分词转化为了标注问题,如果字语言模型取到4-gram,那么它相当于做了如下的字标注:
b:单字词或者多字词的首字
c:多字词的第二字
d:多字词的第三字
e:多字词的其余部分
对于句子中的一个字$s_k$来说,就有
$$\begin{aligned}&p(b)=p(s_k)\\
&p(c)=p(s_k|s_{k-1})\\
&p(d)=p(s_k|s_{k-2} s_{k-1})\\
&p(e)=p(s_k|s_{k-3} s_{k-2} s_{k-1})
\end{aligned}$$
这就是将分词问题变成了一种字标注问题,而每个标签的概率由语言模型给出。而且,显然b后面只能接b或者c,类似地,就得到非零的转移概率只有:
$$p(b|b),\,p(c|b),\,p(b|c),\,p(d|c),\,p(b|d),\,p(e|d),\,p(b|e),\,p(e|e)$$
这些转移概率的值,决定了划分出来的是长词还是短词。最后找最优路径,依旧由viterbi算法完成。
到这里,问题就变成了语言模型的训练了,这是无监督的。我们只需要花心思优化语言模型,而这方面不论是理论还是实战都已经很成熟了,有不少现成的工具可以用。简单地可以只用传统的“统计+平滑”模型,如果要从语义来做,那么就可以用最新的神经语言模型。总而言之,分词的效果,取决于语言模型的质量。
实践:训练 #
首先来训练语言模型。这里文本数据是50万微信公众号的文章,约2GB大小,训练语言模型用的是传统的“统计+平滑”的方法,使用kenlm这个工具来训练。
kenlm是一个C++编写的语言模型工具,具有速度快、占用内存小的特点,也提供了Python接口。首先下载编译它:
wget -O - http://kheafield.com/code/kenlm.tar.gz |tar xz
cd kenlm
./bjam -j4
python setup.py install
接着训练语言模型。kenlm的输入很灵活,不用预先生成语料文本,而可以通过管道的方式传递。比如先编写一个p.py
import pymongo
db = pymongo.MongoClient().weixin.text_articles
for text in db.find(no_cursor_timeout=True).limit(500000):
print ' '.join(text['text']).encode('utf-8')
我的文章放在MongoDB中,所以是上面的格式,如果你的数据放在其他地方,请做相应修改,其实很简单,就是把你要训练的文本分好词(用空格隔开,如果你是做基于字的模型,就把模型的每个字用空格隔开),然后逐一print出来。
然后就可以训练语言模型了,这里训练一个4-gram的语言模型:
python p.py|./kenlm/bin/lmplz -o 4 > weixin.arpa
./kenlm/bin/build_binary weixin.arpa weixin.klm
arpa是通用的语言模型格式,klm是kenlm定义的二进制格式,klm格式占用空间更少。最后我们就可以在Python中载入了
import kenlm
model = kenlm.Model('weixin.klm')
model.score('微 信', bos=False, eos=False)
'''
score函数输出的是对数概率,即log10(p('微 信')),其中字符串可以是gbk,也可以是utf-8
bos=False, eos=False意思是不自动添加句首和句末标记符
'''
实践:分词 #
有了上述基础,就可以来做一个分词系统了。
import kenlm
model = kenlm.Model('weixin.klm')
from math import log10
#这里的转移概率是人工总结的,总的来说,就是要降低长词的可能性。
trans = {'bb':1, 'bc':0.15, 'cb':1, 'cd':0.01, 'db':1, 'de':0.01, 'eb':1, 'ee':0.001}
trans = {i:log10(j) for i,j in trans.iteritems()}
def viterbi(nodes):
paths = nodes[0]
for l in range(1, len(nodes)):
paths_ = paths
paths = {}
for i in nodes[l]:
nows = {}
for j in paths_:
if j[-1]+i in trans:
nows[j+i]= paths_[j]+nodes[l][i]+trans[j[-1]+i]
k = nows.values().index(max(nows.values()))
paths[nows.keys()[k]] = nows.values()[k]
return paths.keys()[paths.values().index(max(paths.values()))]
def cp(s):
return (model.score(' '.join(s), bos=False, eos=False) - model.score(' '.join(s[:-1]), bos=False, eos=False)) or -100.0
def mycut(s):
nodes = [{'b':cp(s[i]), 'c':cp(s[i-1:i+1]), 'd':cp(s[i-2:i+1]), 'e':cp(s[i-3:i+1])} for i in range(len(s))]
tags = viterbi(nodes)
words = [s[0]]
for i in range(1, len(s)):
if tags[i] == 'b':
words.append(s[i])
else:
words[-1] += s[i]
return words
实践:效果 #
语言模型的大小有近3G,因此就不放出来了,有需要的读者可以联系我。下面看一下一些例子。
水 是 生命 的 源泉 , 是 人类 赖以生存 且无 可 替代 的 营养 物质 。 为 使 队员们 更加 了解 水 对 生命 的 至关重要 性 , 提高 队员们 对 水 更 科学 的 认识 与 理解 , 倡导 节水 爱 水 的 环保 意识 , 青少年 环境 知识 科普 课堂 走进 大 金 小学 , 为 五、 六年级 近 300 余 名 队员 开展 了 一场 《 水 与 生命 》为主题 的 科普 知识 讲座 。 此次 活动 共分为三 场 进行 , 宣讲 人 祝 老师 结合 PPT , 图文并茂 、 生动 地 从 水 的 特性 、 水 与 生命 、 水 与 生活 以及 节水 技巧 四个 方面 与 队员们 进行 了 交流 。 祝 老师 告诉 队员们 水 对人体 的 重要 性 , 详细 说明 了 水 的 营养 组成 , 同 时 提醒 队员们 要 学会 健康 科学 的 饮水 方法 , 并 分享 了 节水 小窍门, 希望 队员们 都能 以 自己 为 榜样 , 努力 承担 “ 小 小 节水 宣传 员 ”的 职责 , 积极 带动 身边的人 一起 参与 节约用水 。 PH 试纸 检测 水 的 酸碱度 ,队员们 都 表现 了 浓厚的兴趣 , 纷纷 取了 试纸 回家 测试 水质 。 讲座 结束后, 队员们 都 领到 了 “ 小 小 节水 宣传 员 ” 培训 课 程 的 结业证书 。 从 队员们 兴奋 的 表情 中 能够 感受 到 队员们 节水 爱 水 的 决心 。 保护 水 环境 , 珍惜 水 资源 , 从点滴做起 , 从 自己 做起 , 只要 每个人都 做到 了 保护 生态 、 爱护 环境 , 那么 碧水蓝天 就会 离我们 越来越 近 ! 打赏小编 的 最好 方式 就是 —— 点赞 ↓↓ 长按二维码 , 关注 我们 吧! ↓↓
可以看到,效果还是不错的,对长词的识别效果都挺好。但是,有些情况可能不符合我们的习惯认识,比如“队员们”作为一个词了,还有“且无可替代”错误地分为了“且无 可 替代”,因为“且无”太频繁了。
区 志愿者 协会 在 前几日 得知 芦林街道 三官殿居 有 一 居民 家庭 特别 困难 的 情况 , 12月 12 日 下午 , 招募 了 7 名 志愿者 来到 芦林三官 殿周全禄 老人 家 , 送去 了 一袋大米 和 一床棉被 。 此次 助 养 慰问 品 是 由 广丰区 志愿者 协会 公益 基金 提供 , “ 暖冬行动 ” 作为 志愿者 协会 帮困 项目 的 其中 重要 一 项 , 由 参与 暖冬行动 的 志愿者们 负责 执行 发 放到 走访 核实 的 困境 家庭 手中 。 志愿者 现场 和 周 全 禄 老人 交谈 , 从 他 本人 和 周边 群众 了解 到 他的 基本 家庭 状况 , 他 本人 今年 62 岁 , 娶了一个 患有 精神 疾病 的 妻子 , 生 了 2个 儿子 , 小孩 大 的 14 岁 , 小 的 12 岁 , 妻子 在 十年 前 也 离家出走 , 至今 未 回 , 留下 他 和 2个 儿子 共同 生活 , 由于 儿子 遗传 了 母 亲 的 精神 疾病 , 大 儿子 的 种种 不 正常 表现 , 不能 在 学校 正常 上 学 , 只能 整天 跟着 小 儿子 两个 人 无所事事 , 游手好闲 , 什么 事 也 做 不 了。 周 老 本身就是 一个 老实巴交 的 农民 , 今年 不慎 干农活 时 摔了一跤 , 医药费 2万多 元 , 都是 村里 和 亲戚 邻居 帮忙 筹集 的 。 他 住的 房子 也是 亲戚 筹集 盖的 一层 瓦房 。 凌乱的 客厅 , 衣服 基本 上 就是 没有 什么 换洗 , 湿了 就 随意 搭着 晾干 , 然后 接着 穿 我们 在 他 家 看到 做的 饭菜 , 这 就是 一 家人 赖以 生存 的 厨房 。 这 就是 卧室 , 床铺被褥 都是 破旧不堪 , 我们 带去 的 一 床 新 棉被 他的 外甥女 偶尔 帮他 整理 下 卫生 , 做些家务 赠人玫瑰 , 手有余香 ; 扶困助弱 , 千古 美德 ; 能力 不 分 大 小 , 善举 不 分 先后 , 真情 重 在 付出 。 众人 拾柴火焰高 , 我们 将 把所有 爱心 力量 汇集 在 一起 , 传递 社会 大 家庭 的 温暖 , 传递 社会 正能量 , 放 飞 困境儿童 的 未来 梦想 ! 伸出 您的 双手 , 奉献 您的 爱心 , 让我们 行动 起来 , 共同 关爱 困境 家庭 , 让 所 他们 同 在 蓝天 下 健康 快乐 成长 ! 如果 您 或 您身边的 人 有 12 - 15 岁 男 孩子 的 衣物 , 棉被 等 暖冬 物质 可以 捐赠 , 请伸出您 充满 爱心 的 双手 , 给 这个 特殊 家庭 一个 暖暖的 冬日 ! !! 暖冬 物质 接收 地址: 广丰区 志愿者 协会 暖冬 物质 接收 联系人: 18 6 07 03 48 18 ( 段 先生 ) 13 8 70 32 70 03 ( 陈女士 ) 供稿: 段 建 波 图片 : 段 建 波 编辑: 周 小 飞
可以看到,即使对“拾柴火焰高”这样的长词也有不错的识别效果。当然,错误的例子也不少,比如“把所有”、“让我们”、“请伸出您”成为了一个词。
根据 业务 发展 需要 , 现 将 我 公司 20 16 年 招聘 应届高校 毕业 生 公告 如下 : 一 、 招聘 岗位 20 16 年 我 公司 拟招聘 应届高校 毕业 生 20 名 。 招聘 岗位 和 学历 、 专业 要求 见下表 。 二、 报名 条件 1. 列入国家 招生 计划 、 具备 派遣 资格 、 处于 毕业 学 年 的 全日制 普通 高等院校 在 校 生 , 以及 经 教育 部 留学 服务 中心 认证 并 具备 派遣 资 格 的 归国留学 生 ; 2. 遵守 国家 法律法规 和 学校 规章制度 , 具有 良好 的 思想 品质 和 道德 素质 , 无 刑事 犯罪 和 严重 违反 校纪校规 记录 ; 3. 专业 对 口 , 符合 工作 岗位要求 , 热爱 铁路 集装箱 事业 ; 4. 学习 成绩 优良 , 取得 相应 的 大学 本科 及以上学历 和 学位证书 ; 应聘 在 京 单位 岗位 毕业 生 需 取得 国家 大学 外语 四级考试 合格 证书 ( 主 修其他语 种 除外 ); 5. 身心 健康 , 近期 医院 健康 体检合格 , 能够 适应 应聘 岗位 工作 要求 。 三、 报名 方法 应 聘者 需 登录 " 中国 铁路 人才 招聘 网 — 个人 中心 " 栏目 按照 流程 进行 报名 应聘 ( 首次 登录 须 进行 网上 注册 )。 报名 截止日期 为 20 16 年 1月 10 日 。 每人 限报一个 岗位 。 四、 招聘 流程 1. 资格 确认 。 根据 资格审查 和 初步 筛选 情况 , 于201 6年 2月 28 日前 , 择优 以 邮件 、 短信 或 电话 方式 通知 毕业 生 参加 招聘 考试 。 2. 招聘 考试 。 参加 招聘 考试 的 毕业 生 应 携带 在 中国 铁路 人才 招聘 网 打印 的 毕业 生 应聘 登记 表 , 本人 身份证 、 学生 证、 所在 学校 盖章 的 就业 推荐 表 、 成绩 单 、 外语 证书 等 材料 的 原件及复印件 。 招聘 考试 在 20 16 年 4月 15 日前 完成 , 具体 时间 、 地点 另行通知 。 3. 人员 公示 。 拟录用人 选 将 统一 在 中国 铁路 人才 招聘 网 和 公司 官网 进行 公示 。 招聘 过程中, 对 未 进入 下一 环节 的 毕业 生 不再 另行通知 。 五、 其他 事项 1. 公司 不 委托 第三 方 招聘 , 也不 在 招聘 过程中 向 应聘者 收 任何 费用 。 2. 应聘者 的 报名 材料 概不退回 , 在 招聘 过程中 公司 对 应聘者 的 相关 信息 予以 保密 。 毕业 生 应对 招聘 各环节 所提供的 材料 的 真实 性 负责 , 凡 弄虚作假 的 , 一 经 发现 , 取消 聘用 资格 。 3. 单位 地址: 北京 市 西城区 鸭子 桥路 24 号 中 铁 商务大厦 邮政编码 : 10 00 55 联系 电话:0 10 - 51 89 27 23
总的来说 #
总的来说,这种无监督的分词方式,事实上是对我们的用字习惯做了总结,把我们常见的用字模式提取了出来。因此,它对于不少长词,尤其是固定搭配的成语,有着很好的识别效果。同时,我们也有一些频繁的用字组合,比如前面说的“让我们”之类的,也被视为单个词语了。可能我们会觉得这是一个不合理的情况,但反过来想想,既然我们经常说“让我们”,那么为什么不把“让我们”就作为一个“词”呢?
换句话说,我们做分词,事实上就是事先提取出固定的用语模式罢了,这个固定的用语模式,不一定是我们认识中的“词”,也有可能是习惯用语等。当然,这里边有个相互矛盾的地方,就是分词的粒度太细,则词表的词数不会过多,但单个句子的长度则会变长;分词的粒度太粗,则词表的词数可能暴增,但好处是单个句子的长度会减少。而本文所提供的分词方式,可以通过转移概率的调整,来实现对分词粒度的调整,以适应不同的任务。
同时,前面已经说了,分词的效果取决于语言模型的质量,这使得我们只需要优化语言模型,而且语言模型可以无监督地训练,这是一个明显的好处。比如,如果我们希望能够实现具有语义理解能力的分词模型,那么用神经网络之类的方法训练语言模型即可,如果我们考虑速度,那么传统的统计方法就不错了(用kenlm从50万文本中得到语言模型,只用了10分钟不到)。总而言之,提供了最大的自由度。
转载到请包括本文地址:https://kexue.fm/archives/3956
更详细的转载事宜请参考:《科学空间FAQ》
如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。
如果您觉得本文还不错,欢迎分享/打赏本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!
如果您需要引用本文,请参考:
苏剑林. (Sep. 12, 2016). 《【中文分词系列】 5. 基于语言模型的无监督分词 》[Blog post]. Retrieved from https://kexue.fm/archives/3956
@online{kexuefm-3956,
title={【中文分词系列】 5. 基于语言模型的无监督分词},
author={苏剑林},
year={2016},
month={Sep},
url={\url{https://kexue.fm/archives/3956}},
}
August 4th, 2019
感谢苏神!
请教一个问题,这里用这种方法生成的分词模型,与结巴的效果对比,有无什么可比较的方面呢? 这样理解的话kenlm能够因应不同语料的统计,生成对应的分词模型;但感觉对比加了对应场景词库的jieba模型,应该还是jieba的效果要好点吧。
无监督算法本身就是主要来解决“场景词库哪里来”的问题,你都说有了场景词库还要将它加入到jieba了,那有什么好比较的,肯定jieba好啊
August 10th, 2019
请问训练数据可否提供,是什么样子的呢?
不可提供,无固定格式,只需看懂全文,然后自行修改,如不愿意学习,请勿参考本文。
August 12th, 2019
看了文章想到一个问题,无监督分词是不是无法统计准确率?应该采取何种方式来评判模型的性能呢?
可以用现成的标注语料作为测试集来评估性能
August 12th, 2019
苏神您好, 请问如果使用LSTM作为语言模型, 例如要计算`中文分词`中的P(词|中文分), 是不是用`中文分`作为输入得到的`词`的概率?
另外对于单字的概率, 网络是没法得到的吧, 是不是还是需要用统计平滑的方式?
1、是;
2、语言模型一般都会引入一个标记,p(字|)就是单字概率。
明白了, 感谢解答
苏神你好,看到这个问题很感兴趣,追问一个问题:
这里你说的标记,指的是开始符号吗,?所以单字概率是不是P(字|)?
September 17th, 2019
苏神,借楼问一下,我最近在看词向量的文章,在看A Neural Probabilistic Language Model这篇的时候,关于他词向量的处理那部分不太理解。
里面说We can then incorporate j in V and recompute probabilities for this slightly larger set (which only requires a renormalization for all the words, except for word i, which requires a pass through the neural network). 这里为什么要进行recompute probabilities,以及renormalization for all the words啊,原本输出的词向量矩阵本也没有normalization这个操作吧。只有输出的softmax才有normalization的操作。(我理解的normalization的操作就是概率归一化)所以如果要进行renormalization,是一个怎样的操作?
此外,在初始化oov词j的时候的公式:$C(j) \leftarrow \sum_{i \in V}C(i)\hat{P}(i|w^{t-1}_{t-n+1})$的时候,也利用oov词j的词向量吧,因为oov词j在i的上下文中。是哪里理解的有问题吗。
这文章我不大清楚,没时间去读了,抱歉。
September 23rd, 2019
苏神,你好。请问这个模型涉及监督学习吗?因为我觉得无监督学习得到的语言模型的每个输出是m(词典大小)维的向量,但具体在做4-gram分词的时候,最后的输出应该是4维的向量吧?那么得到这个4维向量全连接层是需要训练的吧。这是我的一点疑问,我不知道是不是我没看懂你的方法。。望解答,谢谢!
确实是你没看懂。
最后的4维向量是$[p(c_n),p(c_n|c_{n-1}),p(c_n|c_{n-2},c_{n-1}), p(c_n|c_{n-3},c_{n-2},c_{n-1})]$,也就是当前字$c_n$在1gram、2gram、3gram、4gram语言模型中的概率,其中$c_{n-1}$是$c_n$的前第1个字,$c_{n-2}$是$c_n$的前第2个字,,$c_{n-2}$是$c_n$的前第3个字。
March 10th, 2021
大佬,是不是有点类似sentencepiece?