OpenCC
OpenCC copied to clipboard
演算法缺陷導致s2twp「正則表達式=>正規表示式」無作用
如題,雖然 TWPhrases* 有定義「正則表達式=>正規表示式」,但實際使用會把「正则表达式」轉成「正則表示式」。
我看不太懂C的程式碼,但就目前s2twp的配置檔看起來,程式似乎是先根據 STPhrases.ocd2 分詞,然後再對分詞後的片段做簡繁轉換和地區字詞轉換。由於 STPhrases 已經有「表达式=>表達式」一詞,原文會被切成「正则 表达式」,之後簡轉繁成為「正則 表達式」,由於已被切開,因此TWPhrases* 定義的「正則表達式=>正規表示式」無法匹配。
演算法可能需要改進,否則只有在STPhrases加入「正则表达式=>正則表達式」才能解決此問題。
目前程式的分詞和轉換都是正向最長匹配。
我目前使用的解決方法是將轉換拆成兩個步驟,類似於將
opencc -c s2twp
改為
opencc -c s2t | opencc -c t2twp
這樣兩個步驟分別使用兩個詞典分詞,就不會出現這個問題。
如果是這樣,那直接把 s2twp 裡面的 segmentation 拿掉就可以了。
但我猜原來加入這個分詞機制是有原因的,應該是為了避免 s2t 以後 t2twp 在不適當的地方分詞。比如 s2t 詞表有「万用字元=>萬用字元」、「数据=>數據」,t2twp 詞表有「數據=>資料」「元數據=>後設資料」,那麼「万用字元数据有问题」就會在轉成「萬用字元數據有問題」之後被轉成「萬用字後設資料有問題」。
所以才會先用 s2t 詞典切詞,讓原句變成「万用字元 数据 有问题」,簡轉繁後變成「萬用字元 數據 有問題」,以避免「元數據」被轉成「後設資料」的問題,結果輸出為「萬用字元資料有問題」。
更好的辦法應該是改進分詞算法,使之不再依賴單獨的詞典分詞。可以依據整個詞典鏈來。
更好的辦法應該是改進分詞算法,使之不再依賴單獨的詞典分詞。可以依據整個詞典鏈來。
魔鬼藏在細節,問題在於怎麼實做。
我想到的做法是先把詞典鏈裡的所有詞典合併成單一詞典,轉換時就用該合併詞典做轉換。
因為合併時是把各組的詞典串聯生成更大的詞典,因此可以避免不適當的斷句,也可以說就是「根據整個詞典鏈」分詞。
舉例來說,本來有 A 詞典組和 B 詞典組,首先先把各詞典組中的詞典「並聯」(全部載入及去重,類似目前的 merge.py 腳本)成 A 詞典和 B 詞典。
然後再把 A 詞典和 B 詞典「串聯」,具體做法是合併以下兩組運算結果,即可生成合併詞典:
- A 後聯 B:把 A 詞典的每個轉換結果用 B 轉換。例如 A 詞典有「为=>爲」,B 詞典有「爲=>為」,則轉換成「为=>為」。當然還要加上 B 本身的「爲=>為」。這主要處理 A 詞典輸出比 B 詞典輸入長的情況。
- B 前聯 A:對於 B 的每組 x => y1, y2, ..., yn,透過 A 詞典的轉換表列舉出「所有經過 A 詞典轉換後會形成 x 的詞 x1, x2, ..., xm」,個別生成 x1 => y1, y2, ..., yn; x2 => y1, y2, ..., yn; ...; xm => y1, y2, ..., yn 的轉換組合。比如 A 詞典有「数=>數」「据=>據据」,B 詞典有「元數據=>後設資料」,則生成「元數據=>後設資料」「元数据=>後設資料」「元数據=>後設資料」「元數据=>後設資料」。這主要處理 A 詞典輸出比 B 詞典輸入短的情況。
因為這個合併詞典是對「万用字元」「元数据」等詞直接做一次轉換,因此遇到像「万用字元数据…」的句子便不會像做兩次轉換的做法那樣把「万用字元」轉成「萬用字元」後又把「萬用字元數據」轉成「萬用字後設資料」。同理,也不會在遇到像「字符数据」這種句子時在轉成「字元數據」後又把「字元數據」轉成「字後設資料」。
除此之外,這樣做還有其他好處:
- 合併詞典可以預先儲存起來,轉換時只需載入一個詞典、做一次轉換,可以提高載入速度和轉換速度。
- 由於實際上只需要一次轉換,因此很容易處理一對多或對轉換過的內容加標記。
至於缺點……由於詞典串聯需要比較多運算,生成合併詞典速度會比較慢,而由於生成的詞組眾多,合併詞典檔案也會比較大,再加上每個轉換方案都要個別生成一個合併詞典,就會需要更多空間(如果即時組合,載入速度可能會慢到不能接受)。另外是這做法目前綁定「最大正向分詞」演算法,如果要改分詞演算法,則合併詞典的分詞演算法也會不同,則不能使用相同詞典,必須另外生成不同的合併詞典。
實做方面,我寫的 Python 程式已經做出來了,可以參考。不過用 Python 寫的速度畢竟會比較慢,目前實測轉換文本大約需要 OpenCC 3 倍左右的時間。