rime / librime

Rime Input Method Engine, the core library
https://rime.im
BSD 3-Clause "New" or "Revised" License
3.48k stars 563 forks source link

[寻求建议] 处理跨音节的注音生成规律 #187

Open ztl8702 opened 6 years ago

ztl8702 commented 6 years ago

背景

我在参与制作一个基于 Rime 的福州话的输入方案,其中遇到的一个问题是,福州话有比较复杂的(跨音节的)语流音变,导致使用script_translator时感到有些限制。因为(如果我理解正确的话):

而福州话的跨音节变化规律主要包括:

  1. 连读变调 (Tone sandhi) 即两个字分开单念的时候声调可能是 甲(声调a) 和 乙字(声调b),但是连成一个词汇之后,前一个字的声调会发生变化,所以组成的词汇是甲(声调a')乙(声调b)。例子:

    教 kau212 + 员 uong53 => 教员 kau44 uong53

    虽然说作为输入方案不需要用户输入声调,但是因为 1) 声调变化对下一点(韵母交替)有影响,2) 我们希望在 candidate 列表提供声调 hint 给用户参考。所以在词表中保留声调,通过 algebra xform/^([a-z]+)([1-5]+)$/$1/ 来使得输入时不带声调。

  2. 韵母交替/变韵 (Vowel alternation) 即福州话的单字声调 (citation tone) 可分为两个集合 Set A 和 Set B。若一个字的声调,由于连读变调的关系,从 Set A 声调之一变到 Set B 声调之一,其韵母发生对应的(规律性)改变。变韵是受连读变调直接影响的。换句话说,韵母的变化是声调变化的函数。例子:

    耳 ngei242 + 机 gi44 => 耳机 ngi44 gi44

  3. 声母类化 (Consonantal sandhi) 即两个字连成词汇的时候,前一个字的韵尾和后一个字的声母会条件性地发生改变。独立于前两种变化。例子:

    下 a242 + 昼 dau212 => 下昼 a53 lau212

以及一个三种变化都有的例子:

裤 kou212 + 头 tau53 => 裤头 ku44 lau53

以上用的罗马化方案参见此处。只涵盖了2个字/音节组成词汇的情况,3、4个字的情况亦有相应的变化规律,此处略去。福州话中的连读音变限于4字组,4字以上的词组会断开成多个4字以内的词组。

值得注意的是,并不是所有情况下,福州话的词汇都是适用连读后的音,有时(根据上下文、重音位置、词汇本身的常用程度等等因素)会用单个字的发音(即没有发生上述的连读音变)。福州话的使用者并非总是能分辨出一个词汇中,每个音节“本来的发音”是什么(特别是一些方言词或与官话差别较远的词汇),而有时是将连读后的多音节发音整体认读的。因此从使用者的角度,输入法需要兼容 “单个字的发音” 和 “连读后的发音”。即一个词汇,无论用单个字发音,抑或是根据上述规则连读后的发音,来录入,都能打得出来。

目前的解决方案

目前我们的一个原型其实是用了一个很粗暴的解决方案,就是给一个词汇在dict.yaml里注多个发音,比如:

...
南風  nang44 hung44
南風  nang44 ngung44
南風  nang44 ung44
搓螺紋 cuo44 loey44 ung53
搓螺風 cuo44 loey44 hung44
搓螺風 cuo44 loey44 ung44
龍捲風 lyng21 guong21 hung44
龍捲風 lyng21 nguong21 ngung44
龍捲風 lyng21 nguong21 ung44
卷螺風 guong21 loey44 hung44
卷螺風 guong21 loey44 ung44
順風  sung44 hung44
順風  sung44 ngung44
...

有时一个词汇不止需要2行的注音,比如说可能有 {都不变(单字音), 只变声母, 只变韵母和声调, 韵母声调和声母都变} 四种组合,对应不同语境下的发音,或者对使用者本身的混淆进行容错处理。

理论上我们可以用程序来生成一个词汇的多种可能发音,但是这样做,感觉是在“滥用”词典文件,同时我担心可能对词频会有影响,而且没法用在内置的“八股文”上面。

最新的想法

稍微研究了 librime 的代码之后,我目前的另一个想法是定制一个专门的 Encoder,将上述的福州话连读规律考虑进去。具体而言,当这个 Encoder 遇到未注音的词(可能来自“八股文”或者我们自己的词典文件):

似乎只要在 encoder 成功完成匹配、即将保存结果的之前,插入上述逻辑,在 RawCode 上做改动即可。大约就是这个位置: https://github.com/rime/librime/blob/9e1114e58cbcd5c0da8b3786148984cca9a3fc09/src/rime/algo/encoder.cc#L269

我觉得挑战主要集中在:

因为我刚刚开始熟悉 librime 的代码,有许多东西还不太了解。把这些写出来,一方面自己理清思路,另一方面也想请教各位:基于我描述的问题,有没有更好的解决方案?欢迎任何想法和建议。谢谢!

lotem commented 6 years ago

跨音節拼寫運算的問題的確值得研究。 現在 Rime 沒有支持這項功能,因爲我對這個問題的認識有限,不夠自信能作出足夠通用、能解決一類問題的程序。

變調在漢語方言中普遍存在。輕聲也許可以看作類似性質的問題。 官話等方言的兒化韻(還包括子變韻、合音詞)屬於另一種情況,涉及音節數目的增減。 還有人提到其他語言的,如泰文,也有跨音節處理的需求。

可以肯定的是,以平臺支持這項功能,比實現單獨一例難度要高。 作用於單個音節的拼寫運算,也不算很簡單,但至少定義和用法已經很明確了。 跨音節要怎麼做,我還沒有想周全。

另一番討論 https://github.com/rime/librime/issues/85 ,提出一種不同思路,即將輸入串變形後再查詢。這種思路難點在於應對輸入串到字典編碼的轉換有歧義的情況。

Encoder 固然是用來註音的,但必須用他的場合其實是在打字的時候爲用戶輸入的新詞編碼。 而預先準備好的詞典,我們有時間,可以利用各種手段完成註音。未必要用 Rime 的編碼器——這種場合他只爲詞典製作者提供些許方便,然而是可替代的——如果詞典文件已經完成編碼,就不需要動用。

我想說明:使用外部工具或腳本生成註音,未必構成「濫用」。無論在內存中完成編碼(註音)還是在外存(詞典文件)完成,最終結果是一致的,即寫入 .bin 文件的是全部已註音詞條。 惟一可能構成問題的用法是,人工標記的詞條與工具生成的數據混同在一個文件裏。這可以通過引入額外的處理步驟解決:詞典源文件→用外部工具生成註音→結果存入中間詞典文件→ 由 Rime 讀取並轉換爲固態詞典。添加這個中間步驟另有好處:可以不受 Rime 功能限制,實現任意有針對性的轉換規則。 總之,凡是可以前期處理的數據,我傾向於在 Rime 之外做預處理。這也符合讓程序專注於一項任務、組合多個程序完成複雜功能的 UNIX 哲學。

決定實現用程序註音之前,必須考察:規則是否普遍適用?舉一個「兒化韻」的例子: 由「猴兒」(h)ou+er = (h)our 總結出的規則並不能用於「幼兒」等不讀作兒化韻的詞,因此簡單地對匹配該形式的音節序列施加轉換並不嚴謹。這時候在詞典文件中列明詞條的註音,就關係到最終詞典的質量了,類似於爲詞語中的多音字註音的消除歧義、防止生成錯誤註音的作用。 或當先以外部工具批量按規則生成註音,再人工審查編輯定稿。分離出一個前期步驟爲我們提供了這樣的靈活性。