泰语分词,是在词与词之间没有空格的文本里,找出一个词在哪里结束、下一个词从哪里开始的步骤。所有处理泰语的系统,无论是朗读、翻译、建索引,还是给文本标注时间,都必须先完成这一步,才能开始做自己的工作。
在英语里看不到这一步,因为空格已经把活干完了。泰语也有空格,但它表示的是另一回事。这篇文章先讲泰语的空格标记什么,再列出可以有两种切法的字符串。然后跟着一处切错,看它在朗读的句子、译文、字幕文件和搜索索引里分别造成什么后果。
为什么泰语的词与词之间没有空格#
泰文在一个短语之内把词连在一起写。ผมกินข้าวที่ร้านอาหารไทย 在泰语母语者看来是六个词,在任何按空白字符切分的函数看来却只是一个 token。空格留给更大的单位使用。它标记一个短语或分句的结束,相当于英语里逗号或句号的作用,出现的频率也远低于英语读者的预期。
这一书写习惯由来已久,而且始终如一。数字、人名和外来词有时两侧各留一个空格,句末语气词有时也会单独隔开,但一个泰语短语内部的词从不分开写。读者靠词汇和上下文找出边界,就像英语读者判断句子里的 lead 用的是哪个意思一样。
泰语的词很短。在 BEST2010 语料库中,泰语词的平均长度是 3.855 个字符(COLING (2014)),本站把词数换算成计费字符数时,将其取整为 4。因此,一段一百个词的段落是一串约四百个字符的文本,其中也许只有三个空格,而这一百处边界每一处都得靠推断得出。
能切出两种结果的字符串#
只要掌握了词汇,大多数泰语文本只有一种切法。难的是这样一类字符串:两种不同的切法切出来的都是真实存在的词。词典解决不了它们,因为两边的每个词词典里都有。
| 字符串 | 一种切法 | 另一种切法 |
|---|---|---|
| ตากลม | ตา กลม(圆眼睛) | ตาก ลม(晾干) |
| ไปหามเหสี | ไป หา มเหสี(去见王后) | ไป หาม เห สี(去抬,后接两个不成词的音节) |
| มากกว่า | มาก กว่า(多于) | มา กกว่า(来,后接一个不成词的片段) |
| ขนมอบ | ขนม อบ(烘焙点心) | ขน มอบ(毛,交给) |
前两行是读者在实际中会遇到的情况。ตากลม 是真正的歧义,只有它周围的句子才能决定取哪种读法。ไปหามเหสี 对机器来说有歧义,对人来说却从来没有,因为第二种切法留下的音节组不成词。后两行是同一种情形:一种切法是人们真会说的短语,另一种是一串没人会这样写的词典词条。
ตากลม ตา กลม 圆眼睛 ตาก ลม 晾干 เด็กคนนี้ตากลม 这个孩子眼睛圆圆的 เอาผ้าไปตากลม 把布拿去晾干 同样的五个字母,由前面的词决定读法
整个问题都浓缩在这一行里。ตากลม 内部没有任何东西能决定读法。决定读法的是 เด็กคนนี้(这个孩子)或 เอาผ้าไป(把布拿去),而它们都在逐词匹配器所看的那个字符串之外。
切错一处,译文会怎样#
先分词、后翻译的系统,会把每一处切错都原样继承下来。เอาผ้าไปตากลม 如果被切成 ตา กลม,译出来就是一句关于布和圆眼睛的话。译文通顺、合乎语法,却是错的,不懂泰语的审校人员也收不到任何出了问题的信号。
专有名词是最常见的情况。用泰文书写的品牌名,比如聊天应用的 ไลน์、打车服务的 แกร็บ,本身就是一串普通音节。切错了,ไลน์ 就变成表示“线”的词,品牌也就从译文里消失了。翻译 API 正是为此支持术语表。按发布时的写法列入其中的术语,会作为一个整体匹配,并按调用方写的样子输出。
标记(markup)是切分出错的另一个地方。按字符偏移量重新插回的行内标签,只要偏移量差一位,就会落在一个词的中间;在泰语里,这意味着落在辅音和属于它的元音之间。format 字段的作用,就是让模型直接收到标记,并把标签保持在它能看到的词边界上。
切错一处,语音会怎样#
语音模型需要知道词在哪里,才能确定两件事:在哪里换气,以及字母该怎么读。停顿跟着词和分句走。停顿落在词中间,听起来像口吃;整句一口气读完不停顿,听起来像机器。空格和标点都不会告诉模型词在哪里。
音节怎么读,同样取决于词。同样的字母在不同的词里切成不同的音节,只有词本身能决定。ปลา 读作一个音节 plaa,开头是复辅音,而 ปลัด 以同样两个字母开头,却读作两个音节 pa-lat。จักรยาน 读作 jak-kra-yaan,จักรี 读作 jak-kri,两者共有的 จักร 之后切分位置不同。双写的 รร 在词尾读作 un,如 สรร(san);在尾辅音之前读作 um,如 กรรม(kam)。
| 词 | 读音 | 为什么由词决定 |
|---|---|---|
| ปลา · ปลัด | plaa · pa-lat | 开头字母相同,一个是复辅音,另一个读作两个音节 |
| จักรยาน · จักรี | jak-kra-yaan · jak-kri | 前四个字母相同,音节切分不同 |
| สรร · กรรม | san · kam | 双写的 รร 在词尾读 un,在尾辅音前读 um |
找到了词的模型,能把上面每个词都读对。按顺序逐个读字母的模型,每个词都会在第一个音节上读错。文本转语音页面列出了更多这类读音规则,并附上泰语母语者的实际读法。
切错一处,词级时间戳会怎样#
词级时间戳是附在一个词上的一段音频区间。在英语里,词是现成的,对齐就是全部工作。在泰语里,模型得先把词切出来,时间戳再继承它切出的结果。切错一处,就会得到一个覆盖一个半词的区间,用这些区间生成的字幕会高亮错误的音节。
切错一处,搜索索引会怎样#
搜索索引存储的是词项,一个词项就是一个词。按空格切分泰语建出的索引,会把整个短语当成一个词项存下来,查询短语里的某个词就什么也查不到。用分词器建出的索引能找到这个词,但只能按分词器选定的那种切法找到。查询也必须用同一个分词器、按同样的方式切分,否则两边就对不上。
实际的后果是:泰语搜索的质量受限于分词的质量,更换分词器就意味着重建索引。商品目录最先感受到这一点,因为商品名恰恰是分词器从未见过的字符串。
词典匹配与上下文模型#
分词方法分为两大类。词典方法沿着字符串逐位扫描,在每个位置取词典中收录的最长词,剩余部分无法切分时再回溯。这类方法速度快、过程透明,在大多数文本上都能切对。但遇到上面那些有歧义的字符串就会出错,因为两种切法都在词典里,而这种方法无从参考周围的句子。凡是词典没收录的词它也会切错,其中包括大多数新出现的名称。
上下文模型读取每个位置周围的字符,预测那里是否存在边界。它们学会了 เด็กคนนี้ 后面跟的是描述,เอาผ้าไป 后面跟的是动作,并据此切分 ตากลม。遇到没见过的词,它们按词形来处理,因为泰语词由辅音、元音和声调符号构成,结构有规律可循。代价在于它们本身就是模型,随之而来的是训练数据的需求和模型特有的失败情形。
说明
这两类方法常常结合使用:词典给出容易确定的边界,其余的由上下文模型判断。无论处理流程采用哪种方法,它得出的分词结果都会被后面的每个环节继承下来。