トークナイズ

Linderaは複数のトークナイズモードを提供し、代替の分割候補を列挙するためのN-Best解析をサポートしています。

トークナイズモード

Normalモード

Normalモードは辞書エントリに基づく標準的なトークナイズを実行します。辞書に単一エントリとして存在する複合語はそのまま保持されます。

例 -- "関西国際空港限定トートバッグ" をNormalモードでトークナイズ:

関西国際空港 | 限定 | トートバッグ

複合名詞 "関西国際空港"(Kansai International Airport)は辞書に1つのエントリとして存在するため、単一トークンとして保持されます。

Decomposeモード

Decomposeモードは、複合語が辞書エントリとして存在する場合でも、さらに構成要素に分解します。

例 -- "関西国際空港限定トートバッグ" をDecomposeモードでトークナイズ:

関西 | 国際 | 空港 | 限定 | トートバッグ

複合語 "関西国際空港" は "関西"、"国際"、"空港" に分解されます。

モードの選択

Rustでは、Segmenterの作成時にモードを指定します:

#![allow(unused)]
fn main() {
use lindera::mode::Mode;
use lindera::segmenter::Segmenter;
use lindera::dictionary::load_dictionary;

let dictionary = load_dictionary("embedded://ipadic")?;

// Normal mode
let segmenter = Segmenter::new(Mode::Normal, dictionary, None);

// Decompose mode
let segmenter = Segmenter::new(Mode::Decompose(Default::default()), dictionary, None);
}

CLIでは、--modeフラグを使用します:

echo "関西国際空港限定トートバッグ" | lindera tokenize --dict embedded://ipadic --mode normal
echo "関西国際空港限定トートバッグ" | lindera tokenize --dict embedded://ipadic --mode decompose

[!NOTE] embedded:// スキームを使用するには、lindera-cli を対応する embed-* feature 付きでビルドする必要があります(例: cargo install lindera-cli --features=embed-ipadic)。デフォルトビルドの lindera-cli はどの embed-* feature も有効化していないため、 embed-* feature なしで --dict embedded://ipadic を指定すると Invalid dictionary scheme: embedded エラーになります。

N-Bestトークナイズ

N-Bestトークナイズは、総パスコスト順(低コスト = より良い分割)に上位N件のトークナイズ候補を列挙します。最良の結果が曖昧な場合や、入力テキストの代替解釈を探索したい場合に有用です。

アルゴリズム

N-Bestトークナイズは**Forward-DP Backward-A***アルゴリズムに基づいており、MeCabのN-Best実装と互換性があります。フォワードパスは動的計画法で最適コストを計算し、バックワードパスはA*探索を使用して総コストの昇順にパスを列挙します。

1-best のトークナイズと同じく、入力はまず文に分けられ、\n や \t までの文の並びが 1 つの区間になります(文分割を参照)。区間ごとに探索され、区間の中では 、・。 をまたいで文脈を引き継ぐため、区間のパスは、区間全体を 1 つのラティスで解いたときのコストの小さい N 本のパスです。どの候補も入力全体の分割で、区間ごとに 1 つのパスを選び、そのコストは選んだパスのコストの合計です。候補は、これらの組み合わせのうちコストの小さい N 件です。

unique を指定すると、後ろ向きの探索はパスではなく単語境界を単位に進みます。1 つの区切りには各単語の辞書エントリの選び方の数だけパスがあり、短い行でも数百万本になることがありますが、探索は各区切りを最もコストの低いパスで 1 回だけ取り出し、他のパスはたどりません。

パラメータ

tokenize_nbestメソッドは以下のパラメータを受け付けます:

パラメータ型説明
text&strトークナイズするテキスト。
nusize返すN-best結果の数。
uniquebooltrueの場合、同じ単語境界位置を生成する結果を重複排除します。各結果は、その単語境界のパスのうち最もコストの低いものです。
cost_thresholdOption<i64>Some(threshold)の場合、best_cost + threshold以内のコストの候補のみを返します。best_cost は 1 件目の候補のコストです(どちらも入力全体の合計)。

Rust APIの例

use lindera::dictionary::load_dictionary;
use lindera::mode::Mode;
use lindera::segmenter::Segmenter;
use lindera::analysis::tokenizer::Tokenizer;
use lindera::LinderaResult;

fn main() -> LinderaResult<()> {
    let dictionary = load_dictionary("embedded://ipadic")?;
    let segmenter = Segmenter::new(Mode::Normal, dictionary, None);
    let tokenizer = Tokenizer::new(segmenter);

    let text = "すもももももももものうち";

    // Get top 3 tokenization results
    let results = tokenizer.tokenize_nbest(text, 3, false, None)?;

    for (rank, (tokens, cost)) in results.iter().enumerate() {
        println!("--- NBEST {} (cost={}) ---", rank + 1, cost);
        for token in tokens {
            let details = token.details().join(",");
            println!("{}\t{}", token.surface.as_ref(), details);
        }
    }

    Ok(())
}

実行結果は以下のようになります:

--- NBEST 1 (cost=21245) ---
すもも  名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も      助詞,係助詞,*,*,*,*,も,モ,モ
もも    名詞,一般,*,*,*,*,もも,モモ,モモ
も      助詞,係助詞,*,*,*,*,も,モ,モ
もも    名詞,一般,*,*,*,*,もも,モモ,モモ
の      助詞,連体化,*,*,*,*,の,ノ,ノ
うち    名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
--- NBEST 2 (cost=24541) ---
...

CLIの例

echo "すもももももももものうち" | lindera tokenize --dict embedded://ipadic -N 3

Latticeの再利用

繰り返しトークナイズを行う場合、Latticeを再利用してメモリ割り当てを削減できます:

#![allow(unused)]
fn main() {
use lindera::dictionary::Lattice;

let mut lattice = Lattice::default();
let results = tokenizer.tokenize_nbest_with_lattice(text, &mut lattice, 3, false, None)?;
}

セグメンターのレベルでは、SegmentWorker(Segmenter::new_worker で作成)がこのパターンをラティスとスクラッチバッファを所有するセッションオブジェクトとしてまとめており、さらに自動縮小ポリシーで保持メモリも制限します。詳細は Segmenter のページを参照してください。