Skip to content

程式架構_Gramambular

zonble edited this page Oct 30, 2024 · 7 revisions

Gramambular 是小麥注音當中的選字引擎。由於對於小麥的開發者來說,想要蒐集 Ngram 的資料相當困難,我們只整理出了 Unigram 的詞庫,Gramambular 的演算法也只支援 Unigram。

基本結構

Gramambular 的主要資料結構包括 Node、Span、Grid 等。

  • Node:用來代表一個字詞(平時大概會用 Phrase 或 Syllable 稱呼),包含這個詞的讀音、對應到的中文字詞、以及出現的頻率等資訊。
  • Span:代表相同長度的詞的集合,比方說,當我們有一個長度為 3 的 Span,代表這個 Span 下會有多個三字詞的 Node。
  • Grid:是 Span 的集合。比方說,我們現在在輸入緩衝區當中,輸入了十個注音,那麼這個 Grid 的大小就是 10,而在 1 到 10 的位置上。就會插入多個長度不一的 Span。

Grid 包含 Span,Span 內包含 Node。

比方說,如果我們想輸入下面這個句子:「在我平凡無奇的日子裡」,那麼 Grid 就會呈現成以下的結果:

注音 ㄗㄞ ˋ ㄨㄛ ˇ ㄆㄧㄥ ˊ ㄈㄢ ˊ ㄨ ˊ ㄑㄧ ˊ ㄉㄜ ˙ ㄖ ˋ ㄗ ˙ ㄌㄧ ˇ
Index 0 1 2 3 4 5 6 7 8 9
4 字 Span 平凡無奇
3 字 Span 日子裡
2 字 Span 平凡 凡無 日子 梓里
1 字 Span

Gramambular 當中還有另外一個叫做 Walker 的 class,則從這樣的結構中,找出各種可能的路徑,找出每個路徑上的分數,並且將分數最蒿的路徑當成最後的結果。以上面的例子來說,就會有以下結果:

分數 路徑
-18.65265033 在(-2.23651546) -> 我(-2.27257609) -> 平凡無奇(-6.67803313) -> 的(-1.62142821) -> 日子裡(-5.84409744)
-20.058111410000002 在(-2.23651546) -> 我(-2.27257609) -> 平凡無奇(-6.67803313) -> 的(-1.62142821) -> 日子(-4.31095122) -> 理(-2.9386073)
-21.45032334 在(-2.23651546) -> 我(-2.27257609) -> 平凡無奇(-6.67803313) -> 的(-1.62142821) -> 日(-2.91157897) -> 子(-2.79158418) -> 理(-2.9386073)
-23.07941185 在(-2.23651546) -> 我(-2.27257609) -> 平凡(-5.13953417) -> 無(-3.06378057) -> 其(-2.90147991) -> 的(-1.62142821) -> 日子裡(-5.84409744)
-24.48487293 在(-2.23651546) -> 我(-2.27257609) -> 平凡(-5.13953417) -> 無(-3.06378057) -> 其(-2.90147991) -> 的(-1.62142821) -> 日子(-4.31095122) -> 理(-2.9386073)
-25.34441624 在(-2.23651546) -> 我(-2.27257609) -> 平(-3.26688126) -> 繁(-4.1376573) -> 無(-3.06378057) -> 其(-2.90147991) -> 的(-1.62142821) -> 日子裡(-5.84409744)
-25.409617339999997 在(-2.23651546) -> 我(-2.27257609) -> 平(-3.26688126) -> 凡無(-7.26663897) -> 其(-2.90147991) -> 的(-1.62142821) -> 日子裡(-5.84409744)
-25.87708486 在(-2.23651546) -> 我(-2.27257609) -> 平凡(-5.13953417) -> 無(-3.06378057) -> 其(-2.90147991) -> 的(-1.62142821) -> 日(-2.91157897) -> 子(-2.79158418) -> 理(-2.9386073)
-26.749877320000003 在(-2.23651546) -> 我(-2.27257609) -> 平(-3.26688126) -> 繁(-4.1376573) -> 無(-3.06378057) -> 其(-2.90147991) -> 的(-1.62142821) -> 日子(-4.31095122) -> 理(-2.9386073)
-26.81507842 在(-2.23651546) -> 我(-2.27257609) -> 平(-3.26688126) -> 凡無(-7.26663897) -> 其(-2.90147991) -> 的(-1.62142821) -> 日子(-4.31095122) -> 理(-2.9386073)
-28.142089249999998 在(-2.23651546) -> 我(-2.27257609) -> 平(-3.26688126) -> 繁(-4.1376573) -> 無(-3.06378057) -> 其(-2.90147991) -> 的(-1.62142821) -> 日(-2.91157897) -> 子(-2.79158418) -> 理(-2.9386073)
-28.20729035 在(-2.23651546) -> 我(-2.27257609) -> 平(-3.26688126) -> 凡無(-7.26663897) -> 其(-2.90147991) -> 的(-1.62142821) -> 日(-2.91157897) -> 子(-2.79158418) -> 理(-2.9386073)

總共有 12 種組合。由於「在|我|平凡無奇|的|日子裡」的分數最高,所以我們就當成最後的計算結果。

Unigram 的限制

因為我們只有 Unigram 的資料,所以即使讀音是「ㄗㄞ ˋ」的字詞還包括「再」、「載」等,但我們就直接挑選分數最蒿的「在」,無法考慮在某些前後文當中更有可能是「再」或「載」。一些比較容易連續出現的字詞,我們直接簡單地合併成多字詞,像是上面例子中的「日子裡」。

分數的計算方式

每個字詞是這個字詞在整個語言模型,也就是小麥注音作者所能夠蒐集到的中文語料中,這個字詞出現可能性,是一個 Log 值。以「在」這個詞來看,分數是 -2.23651546,是 10 的 -2.23651546 次方,換算成十進位數字,大概是 0.005800755226466948。如果我們有一份一百萬字的語料,就是「在」出現了 5800 次。而整條路徑的最後分數,其實都是非常小的數字。

尋找最佳路徑的演算法

請參考我們之前在 X 上的說明

選字

當用戶進行選字之後,我們會把被選擇的 Node 的分數設定成正值,確保其他的 Node 不可能比被選擇的字詞的分數更高。當我們發現某個 index 有這樣的 Node 時,就直接採用這個 Node。

完整流程

輸入

  • 用戶先輸入一個正確的注音組合
  • 擴大 Grid 大小
  • 根據輸入的注音組合與鄰近的注音,從語言模型中,搜尋是否有符合的字詞
  • 根據不同長度的字詞建立 Span,然後在 Span 內插入 Node
  • 執行 Walker 計算最佳路徑

刪除

  • 清除特定位置的所有 Span
  • 縮小 Grid 大小
  • 執行 Walker 計算最佳路徑