learn-corpus 改善実験
背景
learn-corpus コマンドの学習ロジックを調査した結果、以下の問題が判明した。
問題点
- コメントとコードの不一致: コメントは「頻度を増やす」だが、コードは
cost - delta(減算) - u32 アンダーフロー依存:
costが 0 のとき0 - deltaは release ビルドでラップアラウンドし、非常に大きな値になる。これが「たまたま」スコアを下げる方向に作用している - debug ビルドでパニック: debug ビルドでは u32 のオーバーフローチェックが有効なため、パニックする
- 予測パスのカウント放置: 不正解時に正解パスのカウントのみ操作し、予測(間違った)パスのカウントは変更しない
現在のコード(問題箇所)
#![allow(unused)]
fn main() {
// 正解じゃないときには出現頻度の確率が正しくないということだと思いますんで
// 頻度を増やす。
if result != surface {
// learn unigram
for i in 0..teacher.nodes.len() {
let key = teacher.nodes[i].key();
let (_, cost) = self.system_unigram_lm
.find_cnt(&key.to_string())
.unwrap_or((-1, 0_u32));
self.system_unigram_lm.update(key.as_str(), cost - delta); // ← 減算
}
// bigram, skip-bigram, BOS/EOS も同様に `v - delta`
}
}
実験計画
実験1: 減算 → 加算 (saturating_add)
5箇所の cost - delta / v - delta を cost.saturating_add(delta) / v.saturating_add(delta) に変更。
コメント「頻度を増やす」の意図どおりの動作にする。
実験2: 加算 + 予測パス減算
実験1に加えて、予測パス(不正解)のカウントを saturating_sub(delta) で減算。
正解パスと予測パスで共通する単語はスキップ。
評価指標
| 指標 | 説明 |
|---|---|
| Good | Top-1 exact match 数 |
| Top-5 | Top-5 に正解あり(Top-1 にはない)の数 |
| Bad | Top-5 にも正解なし |
| 再現率 | LCS ベースの文字レベル再現率 |
| 学習時間 | learn-corpus の wall clock 時間 |
実験条件
- コーパス: anthy-corpus (corpus.0〜3, 5.txt, 全 11,065 件)
- 辞書: SKK-JISYO.L + SKK-JISYO.akaza
- モデル: akaza-default-model
- learn-corpus パラメータ: delta=2000, may-epochs=10, should-epochs=100, must-epochs=10000
- k-best: 5 (デフォルト)
実験結果
結果一覧
| 手法 | Good | Top-5 | Bad | 再現率 | 学習時間 |
|---|---|---|---|---|---|
| ベースライン(現行モデル) | 6719 | 416 | 3930 | 93.267% | 16:48 |
| 実験1: 減算→加算 | 6719 | 416 | 3930 | 93.267% | 18:04 |
| 実験2: 加算+予測パス減算 | 6719 | 416 | 3930 | 93.267% | 17:25 |
ベースライン(現行モデル)
学習済みモデル(data/)でそのまま evaluate を実行。
Good=6719, Top-5=416, Bad=3930, 再現率=93.26721
学習時間: 16:48 (wall clock)
実験1: 減算 → 加算
cost - delta → cost.saturating_add(delta) に全5箇所を変更。
Good=6719, Top-5=416, Bad=3930, 再現率=93.26721
学習時間: 18:04 (wall clock)
ベースラインと完全に同一のスコア。
実験2: 加算 + 予測パス減算
実験1に加えて、予測パスの unigram/bigram/skip-bigram/BOS-EOS を saturating_sub(delta) で減算。
正解パスと共通する単語はスキップ。
Good=6719, Top-5=416, Bad=3930, 再現率=93.26721
学習時間: 17:25 (wall clock)
ベースラインと完全に同一のスコア。
考察
全手法でスコアが同一だった理由
3手法とも evaluate スコアが完全に一致した。以下の原因が考えられる。
-
学習コーパスと評価コーパスの重複が少ない: 学習コーパス(training-corpus/)は手作業で作成した少量のデータであり、評価コーパス(anthy-corpus)の 11,065 件とはドメインが異なる。学習による重み変更が、評価コーパスの変換に影響する単語・bigram にヒットしていない可能性が高い。
-
delta が小さすぎる: delta=2000 は統計的な頻度カウント(数十万〜数百万オーダー)に対して微小であり、LM コストの変化が変換結果を変えるほどの影響を持たない。
-
learn-corpus の学習対象が限定的: 学習は不正解時のみ発火するが、must コーパスの全10件中大半は数エポックで正解に到達するため、実際の重み更新回数が少ない。
学習時間の差
- 実験1は baseline より約1分16秒遅い(+7.5%)
- 実験2は baseline より約37秒遅い(+3.7%)
- いずれもオーバーヘッドは軽微
結論
今回の実験では、learn-corpus の学習ロジック変更が evaluate スコアに影響を与えないことが判明した。
コードの正しさ(コメントとの整合性、u32 アンダーフロー排除、debug ビルド対応)の観点からは saturating_add への変更が望ましいが、スコア改善は確認できなかった。
今後の方向性:
- 評価コーパスに含まれる単語を学習コーパスに追加し、学習効果を測定する
- delta の値を大きくして影響を調べる
- 学習前後のモデルの差分を直接比較し、重み変更が実際に発生しているか確認する
追記: 評価方法の問題
この実験のベースラインは data/(learn-corpus 適用済みモデル)であり、「学習済み vs 再学習済み」の比較だった。後の構造化パーセプトロン評価で未学習モデル(learn-corpus を実行しないモデル)と比較した結果、旧 learn-corpus(delta=2000)は +11 文(+0.028%)のわずかな改善効果があることが判明した。全手法でスコアが同一だったのは、再学習しても同様のモデルが生成されていたためである。