v6 から v7 への移行
Lindera v7.0.0 では lindera クレートがファサード(facade)になりました。
形態素セグメンター(lindera-segmenter として公開)を再エクスポートし、
新しくデフォルト有効になった analysis feature を通じて lindera-analysis の
分析チェーンを lindera::analysis として提供します。依存 1 行で
Segmenter・Tokenizer・すべてのフィルタが使えます。あわせて
lindera-binding-core を lindera-binding に改名し、v5.0.0 で予告していた
LINDERA_DICTIONARIES_PATH のフォールバックを削除し、IPADIC のスキーマで
conjugation_type と conjugation_form の名前が逆になっていた誤りを
修正し、表層形が空白だけの見出し語や先頭・末尾が空白の見出し語を保持し、
MeCab と同様に空白をラティス上で読み飛ばし(Sudachi と同じく空白を残す SudachiDict
を除く)、char.def の重複した行を MeCab と同じく解決し、MeCab と同じく行の中では
、・。 をまたいで文脈を引き継ぎ、
N-best の各結果が入力全体を覆うようにし、MeCab と同じくどの位置からも未知語が
始まるようにし、MeCab と同じく文字の既定カテゴリから未知語を作ってカテゴリを
共有する文字をまとめるようにし、辞書の見出し語のダッシュとチルダを書かれたとおりに保持して、
それを書き換えていた設定 normalize_details を削除し、MeCab と同じく同点の
見出し語からは CSV で先の行を出力するようにし、コストが同じ 2 つの分割からは
MeCab と同じく最後の語が後から始まる方を選ぶようにし、lindera-dictionary の
ラティスのバックトレースが各トークンの終了オフセットも返すようにしました。
lindera train は MeCab の学習器と同じく、種辞書の語とランタイムが作る未知語で
ラティスを作り、unk.def のすべてのエントリを保持するため、以前のバージョンで
学習したモデルは学習し直す必要があります。lindera build は、文脈 ID が接続コスト
行列の範囲外にある辞書を拒否し、トークナイザーの構築もそうしたユーザー辞書を
拒否します。
v6.2.0 以前にビルドまたはダウンロードした辞書ディレクトリは読み込めなくなるため、
再ビルドか再ダウンロードが必要です(辞書の形式バージョン 3)。Lindera の
ビルドには Rust 1.89 以降が必要になりました。
このガイドでは、すべての破壊的変更とその対処方法を説明します。
概要
| 変更 | 影響範囲 | 対処方法 |
|---|---|---|
lindera がファサードになり、lindera::analysis が lindera-analysis を再エクスポート(analysis feature、デフォルト有効) | lindera と lindera-analysis の両方に依存する Rust ユーザー | 両方を同時に "7" に上げるか、lindera-analysis を外して lindera::analysis::… から import する |
default-features = false は「セグメンターのみ」の意味になり、analysis(と従来どおり mmap)が無効になる | default-features = false で lindera::analysis を使いたい Rust ユーザー | features に "analysis" を追加する(メモリマップ読み込みを維持するなら "mmap" も列挙したままにする) |
従来の lindera クレートは lindera-segmenter として公開 | 直接依存する人はいない | 対応不要 — lindera = "7" が再エクスポートする |
生の lindera-dictionary モジュールを lindera::dictionary 配下から参照できる | lindera-dictionary を直接使うユーザー | 対応不要 — 任意で直接依存を外し、lindera::dictionary::core::…・::builder・::viterbi などを使う |
暗黙の feature lindera-ipadic・lindera-ipadic-neologd・lindera-unidic・lindera-sudachidict・lindera-ko-dic・lindera-cc-cedict・lindera-jieba の削除 | features = [...] に辞書クレート名を書いて有効化していたユーザー | 代わりに embed-* feature を使う |
lindera-binding-core を lindera-binding に改名 | バインディング用ヘルパークレートを使う Rust ユーザー | lindera-binding に依存し、lindera_binding_core:: を lindera_binding:: に置き換える |
LINDERA_DICTIONARIES_PATH の削除 | 非推奨のビルドキャッシュ変数をまだ設定しているユーザー | LINDERA_BUILD_DICTIONARY_CACHE_DIR を設定する。旧名は無視される |
| Rust 1.89 以降が必要 | Rust 1.88 で Lindera をビルドするユーザー(Python・Ruby パッケージのソースからのビルドを含む) | ツールチェーンを更新する(rustup update stable) |
| 辞書の形式バージョン 3: v6.2.0 以前にビルドまたはダウンロードした辞書ディレクトリは読み込めない | ファイルから辞書を読み込むユーザー(パスを指定した load_dictionary、lindera tokenize --dict <dir>、バインディングのパスやバイト列からの読み込み、WASM バインディングの OPFS に保存した辞書)。埋め込み辞書とユーザー辞書は対象外 | 7.0.0 の lindera build で再ビルドするか、7.0.0 の lindera download やリリースアセットから再ダウンロードする |
IPADIC・IPADIC-NEologd の conjugation_type と conjugation_form が正しい列を指すように修正 | IPADIC・IPADIC-NEologd でこの 2 フィールドを名前で読むユーザー(Token::get・Token::as_value・lindera tokenize -o json・バインディングのスキーマ) | 2 つの値が入れ替わることを前提にする。v6.2.0 以前に作った辞書は再ビルドまたは再ダウンロードする |
| 空白を含む見出し語を保持 | U+3000 を含むテキスト(IPADIC・IPADIC-NEologd・UniDic)、空白を含むテキスト(SudachiDict)、末尾が空白の一部の語 | U+3000 が 記号,空白・空白 になり、SudachiDict の分割が Sudachi にずっと近くなることを前提にする。v6.2.0 以前に作った辞書は再ビルドまたは再ダウンロードする |
| 空白をラティス上で読み飛ばし、MeCab と同様に空白の前後の語を直接接続する | keep_whitespace が false(デフォルト)で空白を含むテキストを分割するユーザー。ko-dic で特に顕著。Sudachi と同じく空白をラティスに残す SudachiDict は対象外 | そのようなテキストは MeCab と同じ分割になることを前提にする。skip_whitespace(false)("skip_whitespace": false、--disable-skip-whitespace)で空白の扱いを v6 に戻せる(このガイドの他の出力の変化は戻らない) |
char.def の重複した行は最後の行が決める | Ð(U+00D0、ko-dic 以外のすべての辞書)・々(U+3005)・〇(U+3007)を含むテキスト、単一コードポイントの行や重複した行を持つ char.def で lindera train を使うユーザー | Ð が文字として出力に残ることを前提にする。v6.2.0 以前に作った辞書は再ビルドまたは再ダウンロードする |
MeCab と同じく、行の中では 、・。 をまたいで文脈を引き継ぐ | 行の中に 、 や 。 を含む日本語のテキストを分割するユーザー | 、 や 。 の直後の語の解析が一部変わり(多くは MeCab と同じ解析になる)、そうした行の N-best のコストが変わることを前提にする。v6 の動作に戻す設定はない |
| N-best: 各結果が入力全体を覆う | 複数の文を含む入力で N-best(segment_nbest・tokenize_nbest・lindera tokenize -N・バインディングの N-best メソッド)を使うユーザー | 2 件目以降が入力全体のコストの小さい分割になり、空の入力がトークンのない結果を 1 件返すことを前提にする。コストの閾値は入力全体に対して適用される |
| MeCab と同じく、どの位置からも未知語が始まる | カタカナ・記号・英字が続くテキストを通常モードで分割するユーザー。特に ・ でつないだカタカナ、." のような韓国語の文末の句読点、CC-CEDICT の英単語 | そうした連続が MeCab と同じ位置で分かれること、カタカナや英字の多いテキストで命令数が 13% 程度増えることを前提にする。v6 の動作に戻す設定はない |
| MeCab と同じく、未知語は文字の既定カテゴリから作り、カテゴリを共有する文字をまとめる | 複数のカテゴリを持つ文字がほかの文字と並ぶテキストを分割するユーザー。同梱の辞書では、ko-dic の漢数字に続く漢字と、ko-dic・CC-CEDICT・Jieba の 〇(U+3007)。CharacterDefinition::lookup_categories の順序に依存する Rust コード | そうしたテキストが MeCab と同じ分割になることを前提にする(ko-dic の 三國史記 は 1 つの SH、Jieba の 二〇〇八年北京奧運會 は 二 / 〇〇八年北京奧運會)。v6 の動作に戻す設定はない |
| IPADIC・IPADIC-NEologd: MeCab と同じく、見出し語のダッシュとチルダを書かれたとおりに保持する | IPADIC・IPADIC-NEologd で ―(U+2015)・—(U+2014)・~(U+FF5E)・〜(U+301C)を含むテキストを分割するユーザー | ― や ~ で書かれた語が同じ綴りのテキストに一致し、— や 〜 で書いたテキストからは、以前の書き換えでしか引けなかった語(IPADIC 12 語、IPADIC-NEologd 1,085 語)が引けなくなることを前提にする(mapping 文字フィルタで綴りをそろえられる)。v6.2.0 以前に作った辞書は再ビルドまたは再ダウンロードする |
| MeCab と同じく、表層形・文脈 ID・コストが同じ見出し語からは CSV で先の行を出力する | IPADIC・IPADIC-NEologd・UniDic・ko-dic、またはそうした見出し語を持つユーザー辞書で、読みや原形などのトークンの詳細情報を読むユーザー。N-best を使うユーザー | そうした語の詳細情報が MeCab と同じになり(IPADIC の 狡い の読みは コスイ ではなく ズルイ)、ユーザー辞書の見出し語が引き続きシステム辞書の同点の見出し語に勝ち、N-best の 1 件目が常に 1-best と同じになることを前提にする。v6 の動作に戻す設定はない |
| MeCab と同じく、コストが同じ分割からは最後の語が後から始まる方を選ぶ | テキストを解析するすべてのユーザー。現れるのはまれ(比較したテキストでは約 54,000 行のうち 9 行) | そうした箇所が MeCab と同じ分割になることを前提にする(IPADIC の 腸窒扶斯 は 腸 / 窒 / 扶斯 ではなく 腸 / 窒扶 / 斯)。v6 の動作に戻す設定はない |
lindera tokenize が各入力行から改行コードだけを取り除き、すべての行に結果を出す | 行頭・行末に空白がある入力を CLI に渡すユーザー、トークンのない行の wakati や N-best の出力を読むユーザー | オフセットが入力行の中の位置を指し、行頭・行末の U+3000 がトークンになり(IPADIC では 記号,空白)、--keep-whitespace でそこの空白も出力され、空行や空白だけの行が wakati では空行に、N-best では結果 1 件になることを前提にする。v6 の動作に戻す設定はない |
WASM: 辞書の設定方法にかかわらず TokenizerBuilder の設定が適用される | setDictionaryInstance() と setKeepWhitespace() やフィルタ、または setDictionary() と setUserDictionaryInstance() を組み合わせる WASM のユーザー | それらの設定とユーザー辞書が効くことを前提にする。char.def に SPACE カテゴリがない辞書インスタンスでは、URI で設定した辞書と同じく setKeepWhitespace(true) が必要になる |
| PHP: 省略可能な引数に型違いの値を渡すと、既定値を使わずに例外を投げる | 省略可能な引数に別の型の値を渡す PHP のコード。例えば Lindera\Trainer::train() の $max_iter に数字の文字列を、tokenizeNbest() の $unique に 1 を渡すもの | 宣言された型の値を渡す。$argv の値のような文字列は (int) などでキャストする。null を渡すか引数を省略すれば、これまでどおり既定値になる |
| PHP・Node.js・WASM: 設定の範囲外の整数の引数は、折り返さずに例外を投げる | そのような値を渡すコード。例えば Metadata の既定の単語コストに 100000 を渡すもの(v6 は -31072 を保持した)や、N-best の件数に -1 を渡すもの(v6 はすべての分割を列挙した)。getFieldName と PHP の getDetail への負の位置。Node.js と WASM では、1.5 や NaN のような整数でない数値 | 範囲内の整数を渡す。エラーは引数の名前と範囲を示す |
バインディング: decompose モードが set_mode / setMode と Tokenizer のコンストラクタで Penalty を受け取り、TokenizerBuilder::set_segmenter_mode が Mode::Decompose のペナルティを保つ | decompose モードのペナルティを変えたいバインディングのユーザー。Node.js の型 JsPenalty を名指しする TypeScript のコード。set_segmenter_mode に既定でないペナルティを渡す Rust のコード。CoreTokenizerBuilder::set_mode や CoreTokenizer::from_segmenter_with_space_penalty を呼ぶバインディングの作者 | ペナルティを変えるには新しい引数に Penalty を渡す。JsPenalty を Penalty に改名する。lindera-binding の関数の新しい引数には None を渡す |
lindera train は MeCab の学習器と同じくラティスを作り、unk.def のすべてのエントリを保持する。model.dat の形式バージョンは 2.0.0 | lindera train・lindera export と、Python・Node.js・Ruby・PHP のバインディングの学習関数を使うユーザー | 7.0.0 ですべてのモデルを学習し直し、エクスポートと辞書のビルドもやり直す(v6.2.0 以前に書いた model.dat は拒否される)。unk.def のすべての行に 5 つ以上のフィールドを書く。種辞書からコメント行を消す |
lindera build は matrix.def の範囲外の文脈 ID を拒否する | lex.csv や unk.def に matrix.def の大きさを超える文脈 ID があるシステム辞書をビルドするユーザー(そうした辞書はビルドでき、解析時にパニックしていた)。MeCab の mecab-dict-index が受け付ける辞書は対象外。また、組み合わせるシステム辞書に対してそうした文脈 ID を持つユーザー辞書を使うユーザー(解析時のパニックではなく、トークナイザーの構築時のエラーになる) | ID か行列を直すか、metadata.json の skip_invalid_cost_or_id でそうした lex.csv の行を取り除く。ユーザー辞書ではそうした ID を直す |
lindera build と lindera train は char.def と unk.def を照らし合わせる | それより前の行で定義していないカテゴリを指す範囲行や、2 回定義したカテゴリが char.def にあるユーザー、char.def と unk.def でカテゴリが一致しないユーザー(そうした辞書はビルドでき、その後パニックしたり、別のカテゴリの設定を使ったり、出力を途中で打ち切ったり、unk.def の行を無視したりしていた)。MeCab の mecab-dict-index が受け入れる辞書は影響を受けない | 各カテゴリを、それを指す範囲行より前で 1 回だけ定義する。すべてのカテゴリに unk.def の行を書き、未定義のカテゴリの unk.def の行を削除する |
lindera build は、MeCab と同じく matrix.def にないセルのコストを 0 にする | セルを省いた matrix.def からシステム辞書をビルドするユーザー。すべてのセルを列挙している同梱の辞書と、lindera export が書き出す matrix.def は対象外 | そうしたセルのコストは 32767 ではなく 0 になる。接続を高いコストのままにするには、そのセルをコスト付きで列挙する |
| CLI はライブラリの警告を標準エラー出力に書く | 標準エラー出力を読む CLI のユーザー。たとえば、そこに何か出力があれば失敗とみなすスクリプト。目立つのは、文の区切り文字のないテキストが 32 KiB を超える入力と、--context-id-freq を指定しない ko-dic・UniDic の lindera build | 標準エラー出力に [WARN …] の行が出ることを前提にする。RUST_LOG=error で警告を消せる。標準出力は変わらない |
設定 normalize_details の削除: Metadata::new と CoreMetadata::new の引数は 10 個、Ruby の Metadata.new の引数は 8 個になる | 辞書のメタデータを作成・参照するコード。Rust(Metadata・lindera_binding::CoreMetadata・PrefixDictionaryBuilderOptions)と、Python・Ruby・PHP・Node.js のバインディングの Metadata クラス | 引数・オプション・プロパティを削除する。このキーを含む metadata.json もそのまま読み込め、キーは無視される |
Lattice::tokens_offset・tokens_offset_into・nbest_tokens_offset・NBestGenerator::next が (start, end, WordId) を返す | これらの lindera_dictionary の関数を直接呼び出す Rust コード(lindera::dictionary::viterbi::…・lindera::dictionary::Lattice・lindera::dictionary::nbest::… 経由を含む) | 3 つの要素に分解し、次のトークンの開始位置の代わりに返された終了位置を使う |
Lattice::tokens_offset_into が最良パスの BOS の添字(Option<usize>)を返す | tokens_offset_into の戻り値 () を値として使う Rust コード | 文として呼び出しているなら対応不要。それ以外は新しい戻り値を無視する |
UnknownDictionary::gen_unk_words・UnknownDictionary::compatible_unk_index・UnkWord・WordIdx の削除 | これらを lindera_dictionary::dictionary::unknown_dictionary(lindera::dictionary::core::unknown_dictionary 経由を含む)から使う Rust コード | ランタイムが作る未知語を列挙する Lattice::unknown_word_candidates を使う |
lindera_trainer: SerializableModel::unk_category_names と unk_categories は unk_entries に置き換わり、Trainer::max_grouping_len はランタイムと同じ意味になる | lindera-trainer(lindera::dictionary::trainer 経由を含む)を使う Rust コード | ファイル順の unk.def のエントリである unk_entries を読む。max_grouping_len には辞書を使うときの上限を渡す |
言語バインディング(Python・Node.js・Ruby・PHP・WASM)と CLI のパッケージ名は
変わりません。API も 1 つの設定を除いて変わりません。Python・Node.js・Ruby・PHP の
バインディングの Metadata クラスは、normalize_details を受け取らず、公開も
しなくなりました(設定 normalize_details の削除
を参照)。PHP バインディングは、省略可能な引数に渡された型違いの値も拒否します。
v6 はこの値を null として扱っていました
(PHP: 省略可能な引数は型違いの値を拒否するを参照)。
PHP・Node.js・WASM のバインディングは、設定の範囲外の整数の引数も拒否します。
v6 はこの値を折り返していました
(バインディング: 範囲外の整数の引数を拒否するを参照)。
Node.js の TypeScript の型 JsPenalty は Penalty に改名しました
(バインディング: decompose モードが Penalty を受け取るを参照)。
バージョン番号は 7.0.0 になります。Rust クレートと同じく、v6.2.0 以前に
ビルドまたはダウンロードした辞書ディレクトリは読み込めなくなります
(辞書ディレクトリの再ビルドが必要を参照)。v6.2.0 以前にバインディングの
学習関数や lindera train で書いたモデルファイルは、学習し直す必要があります
(lindera train は MeCab の学習器と同じくラティスを作るを参照)。出力の違いは 13 個あり、いずれも
後述します(最後の 2 つは CLI だけ、WASM バインディングだけの変化です)。IPADIC・IPADIC-NEologd では、
conjugation_type と
conjugation_form という名前で返る値が入れ替わります。表層形が空白だけの
見出し語や先頭・末尾が空白の見出し語が辞書に入るため、そうした空白を含む
テキストの分割が変わります。空白を含むテキストは MeCab と同じように分割されます(Sudachi と同じく空白を
ラティスに残す SudachiDict を除く)。
Ð・々・〇 が MeCab と同じ文字カテゴリになるため、これらを含むテキストの分割が
変わります。行の中では 、・。 の直後の語が前の語の文脈を引き継いで解析されるため、
そうしたテキストの一部で分割が変わり、、・。 を含む行の N-best のコストは
すべて変わります。複数の文を含む入力では、N-best の 2 件目以降が入力全体で
コストの小さい分割になり、空の入力は N-best でトークンのない結果を 1 件返します。
さらに通常モードでは、同じ文字種が続く範囲の内側からも
MeCab と同じく未知語が始まるため、カタカナ・記号・英字の連続の一部が分かれます。
未知語は MeCab と同じく文字の既定カテゴリから作り、グルーピングした未知語は
どの文字も直前の文字とカテゴリを共有する間続くため、ko-dic では漢数字と後ろの
漢字が 1 語になり、ko-dic・CC-CEDICT・Jieba では 〇 が記号になります。
IPADIC・IPADIC-NEologd では、― や ~ で書かれた見出し語を MeCab と同じく
その綴りで引けるようになるため、―・—・~・〜 を含むテキストの分割が
変わります。表層形・文脈 ID・コストが同じ見出し語からは、MeCab と同じく CSV で
最後の行ではなく最初の行を出力するため、そうした語の読みや原形などの詳細情報が
変わり、N-best の 1 件目は常に 1-best と同じになります。コストがまったく同じ
2 つの分割からは、MeCab と同じく最後の語が後から始まる方を選ぶため、わずかな行の
分割が変わります。そして lindera tokenize は、各入力行の両端の空白をすべて
取り除くのをやめて改行コードだけを取り除くため、行頭・行末の U+3000 が残り、
オフセットが入力行の中の位置を指すようになり、wakati 形式や -N でもすべての行に
結果を出します。また、WASM バインディングの TokenizerBuilder は、v6 では辞書の
設定方法によって一部の設定を無視していましたが、どちらの方法でもすべての設定と
ユーザー辞書を適用するようになります。それ以外は、同じ入力と
辞書に対して、v7.0.0 は v6.2.0 と同じトークンを、同じ位置ベースの詳細情報
(details)とともに出力します。
lindera クレートはファサードに
v6 までの Rust API は、lindera(セグメンターと辞書読み込み API)と
lindera-analysis(character filter・token filter・Tokenizer)の 2 クレートに
分かれており、それぞれに依存する必要がありました。v7 では lindera クレートが
両者を束ねる薄いファサードになります:
lindera ──┬─▶ lindera-segmenter ──┬─▶ lindera-dictionary
│ ├─▶ 辞書クレート [features: embed-*]
│ └─▶ lindera-trainer [feature: train]
└─▶ lindera-analysis [feature: analysis、デフォルト有効] ──▶ lindera-segmenter
公開ツリーは v6 のすべての lindera::… パスを維持し、lindera::analysis を
追加します:
lindera
├── LinderaResult, get_version()
├── dictionary, error, mode, segmenter, space_penalty, token, worker (lindera-segmenter から。v6 と同じ)
└── analysis [feature: analysis、デフォルト有効] (lindera-analysis: character_filter, token_filter, tokenizer, worker)
依存は 1 つに
# v6
[dependencies]
lindera = "6"
lindera-analysis = "6"
# v7
[dependencies]
lindera = "7"
#![allow(unused)] fn main() { // v6 use lindera_analysis::tokenizer::Tokenizer; use lindera_analysis::token_filter::japanese_stop_tags::JapaneseStopTagsTokenFilter; // v7 use lindera::analysis::tokenizer::Tokenizer; use lindera::analysis::token_filter::japanese_stop_tags::JapaneseStopTagsTokenFilter; }
lindera-analysis = "7" への直接依存を残しても動作します。lindera::analysis は
同じクレートなので、lindera_analysis::tokenizer::Tokenizer と
lindera::analysis::tokenizer::Tokenizer は同一の型であり、既存の use パスを
変える必要はありません。
lindera と lindera-analysis は同時に上げる
両方の依存を残す場合は、同じタイミングで両方を上げてください。
lindera-analysis 6.x は lindera 6.x(旧セグメンター)に依存するため、
lindera の行だけを "7" にして lindera-analysis = "6" を残すと、
セグメンターが 2 バージョン同時にビルドされます:
# Segmenter 型が 2 つになる: lindera 7(lindera-segmenter 経由)と lindera 6(lindera-analysis 6 経由)
[dependencies]
lindera = "7"
lindera-analysis = "6"
この状態では lindera::segmenter::Segmenter は v7 の型、
lindera_analysis::tokenizer::Tokenizer::new が受け取るのは v6 の型となり、
型不一致でコンパイルに失敗します。両方の行を同時に "7" にするか、
前述のとおり lindera-analysis を外してください。
default-features = false は「セグメンターのみ」の意味に
ファサードのデフォルト feature は mmap と analysis です。
default-features = false にすると純粋なセグメンターになり、両方が無効に
なります。lindera::analysis は存在せず、ファイルシステム辞書のメモリマップ
読み込みもデフォルトではなくなります(v6 でも default-features = false は
mmap を無効にしていました)。必要なものを個別に有効にしてください:
# メモリマップ読み込み付きのセグメンター(v6 のデフォルトビルドと同じ)
lindera = { version = "7", default-features = false, features = ["mmap"] }
# セグメンターと分析チェーン。メモリマップ読み込みはデフォルトにしない
lindera = { version = "7", default-features = false, features = ["analysis"] }
memmap2 自体は lindera-dictionary のデフォルト feature を通じてどちらの場合も
リンクされます。ファサードの mmap feature は load_dictionary などの
use_mmap のデフォルト値を決めるだけで、
load_dictionary_with_options(uri, true) は feature なしでも動作します。
セグメンターは lindera-segmenter として公開
v6 までの lindera クレートは、同じモジュール(dictionary・error・mode・
segmenter・space_penalty・token・worker)と同じ API を持つ
lindera-segmenter 7.0.0 として続いています。ファサードはそのすべてを
ルートで再エクスポートするため、v6 でコンパイルできた lindera::… パスは
v7 でもそのままコンパイルできます。このクレートは、lindera-analysis が
ファサードに依存せずにセグメンターを利用できるようにするために存在します。
lindera-segmenter に直接依存する必要はなく、アプリケーションコードは
引き続き lindera に依存してください。
lindera::dictionary から生の辞書クレートを参照できる
lindera-dictionary の構成部品(辞書のデータ構造・辞書ビルダー・Viterbi
ラティスなど)を使うコードは、従来 lindera-dictionary への直接依存が
必要でした。v7 ではこれらのモジュールが、高レベルの読み込み API と並んで
lindera::dictionary の中に再エクスポートされます:
lindera::dictionary
├── Dictionary, Metadata, Schema, load_dictionary, load_user_dictionary, DictionaryKind, … (変更なし)
├── core lindera_dictionary::dictionary(prefix_dictionary, character_definition, connection_cost_matrix, unknown_dictionary, …)
├── builder, error, loader, mode, nbest, space_penalty, util, viterbi
├── embedded_dictionary!, include_bytes_aligned!
└── trainer lindera_trainer(`train` feature。変更なし)
既存の lindera::dictionary::* パスはすべて有効なままで、再エクスポートは同じ
アイテムを指します。lindera::dictionary::Dictionary と
lindera::dictionary::core::Dictionary は同一の型です。lindera-dictionary への
直接依存も引き続き動作しますが、必須ではなくなりました:
#![allow(unused)] fn main() { // v6: Cargo.toml に lindera-dictionary が必要 use lindera_dictionary::dictionary::prefix_dictionary::PrefixDictionary; use lindera_dictionary::viterbi::Lattice; // v7: ファサード経由で参照できる use lindera::dictionary::core::prefix_dictionary::PrefixDictionary; use lindera::dictionary::viterbi::Lattice; }
[!NOTE]
lindera::dictionary::coreは Rust のcoreクレートと同じ名前です。use lindera::dictionary::*;のような glob import はこのモジュールをスコープに 持ち込み、そのモジュール内でcoreクレートを隠してしまうため、core::fmt::Debugのようなパスが標準ライブラリを指さなくなります。 必要なアイテムを名前で個別に import してください。
暗黙の辞書 feature の削除
v6 では lindera の optional な辞書依存が Cargo の feature としても機能しており、
features = ["lindera-ipadic"] という指定が受け付けられていました(辞書を
埋め込まずにクレートだけを取り込む動作でした)。ファサードは辞書クレートに
依存しないため、次の 7 つの暗黙の feature は存在しなくなります:
lindera-ipadic・lindera-ipadic-neologd・lindera-unidic・
lindera-sudachidict・lindera-ko-dic・lindera-cc-cedict・lindera-jieba。
これらを指定したマニフェストは Cargo がエラーにします。辞書の埋め込みには、
従来から正式な方法である embed-* feature を使ってください:
# v6
[dependencies]
lindera = { version = "6", features = ["lindera-ipadic"] }
# v7
[dependencies]
lindera = { version = "7", features = ["embed-ipadic"] }
lindera-binding-core は lindera-binding に改名
共通ヘルパークレートの上に独自の言語バインディングを構築している場合のみ
関係します。lindera-binding 7.0.0 は lindera と lindera-analysis の代わりに
lindera ファサード(analysis feature 付き)に依存します。クレート自身の
API(CoreTokenizerBuilder・CoreTokenizer・TokenView、および argument・
metadata・schema の各ヘルパー)は、CoreMetadata から normalize_details が
なくなったこと(設定 normalize_details の削除
を参照)を除いて変わりません。公開済みの lindera-binding-core
(4.0.0〜6.2.0)は crates.io にそのまま残り、yank もされませんが、
以降のバージョンは公開されません。
# v6
[dependencies]
lindera-binding-core = "6"
# v7
[dependencies]
lindera-binding = "7"
#![allow(unused)] fn main() { // v6 use lindera_binding_core::tokenizer::CoreTokenizer; // v7 use lindera_binding::tokenizer::CoreTokenizer; }
LINDERA_DICTIONARIES_PATH の削除
v5.0.0 でビルド時の辞書キャッシュ変数は LINDERA_BUILD_DICTIONARY_CACHE_DIR に
改名され、旧名 LINDERA_DICTIONARIES_PATH はビルド警告付きの非推奨
フォールバックとして残されていました(v4 から v5 への移行
を参照)。v7.0.0 ではこのフォールバックを削除します。旧名だけを設定した
ビルドはキャッシュを使わず、警告も出しません。辞書クレートはキャッシュ
未設定の場合と同じように辞書をダウンロード・ビルドします。設定している
すべての場所(シェルのプロファイル・CI 設定・コンテナイメージ)で変数名を
変更してください:
# v6
export LINDERA_DICTIONARIES_PATH=/path/to/cache
# v7
export LINDERA_BUILD_DICTIONARY_CACHE_DIR=/path/to/cache
Rust 1.89 以降が必要
v7.0.0 は rust-version = "1.89" を宣言します(v6.2.0 は 1.88)。辞書クレートの
build script は、書き込む LINDERA_BUILD_DICTIONARY_CACHE_DIR のエントリを
Rust 1.89 で安定化した File::lock でロックするようになり、キャッシュを共有する
ビルドが互いのファイルを消さなくなりました。古いツールチェーンでは、cargo が
必要なバージョンを示して止まります。ツールチェーンを更新してください:
rustup update stable
辞書ディレクトリの再ビルドが必要
v7.0.0 は辞書を形式バージョン 3 で書き出し、v6.2.0 以前にビルドまたはダウンロード した辞書ディレクトリ(バージョン 2)を読み込むときに拒否します:
Dictionary 'ipadic' has format version 2, but this build of Lindera reads format version 3. To fix this, rebuild it with `lindera build`, or download a matching prebuilt dictionary with `lindera download`.
バージョン 3 では、char_def.bin に各コードポイントの既定カテゴリを先頭に
並べます。未知語をそのカテゴリだけから作るようになったためです(MeCab と同じく、未知語は既定カテゴリから作るを参照)。
バージョン 2 のファイルも配置は同じなので、検査がなければそのまま読み込まれ、
一部の文字の既定カテゴリを黙って取り違えます。このバージョンには、v6.2.0 の辞書には
ない、辞書ビルダーの出力のほかの変更も含まれます:
IPADIC の活用フィールド名、
空白を含む見出し語、
char.def の重複した行、
ダッシュとチルダの綴り。
| 辞書 | 対処方法 |
|---|---|
埋め込み辞書(embed-* feature) | 対応不要。クレートのコンパイル時にビルドされる |
lindera download でダウンロードした辞書 | 7.0.0 の CLI で再ダウンロードする。CLI は辞書を自身のバージョンのディレクトリに保存する |
lindera build でビルドした辞書 | 7.0.0 の CLI で再ビルドする |
| リリースアセットからダウンロードした辞書 | 7.0.0 のアセットをダウンロードする |
| WASM バインディングの OPFS に保存した辞書 | removeDictionary() で削除し、downloadDictionary() で 7.0.0 のアセットをダウンロードする。hasDictionary() で確かめるだけでは古い辞書が残る |
ユーザー辞書(CSV と .bin) | 対応不要。char_def.bin も形式バージョンも持たない |
IPADIC の活用フィールド名の修正
IPADIC は 8 列目に活用型(例: 五段・カ行イ音便)、9 列目に活用形(例:
連用タ接続)を格納しています。v1.0.0 から v6.2.0 までの lindera-ipadic と
lindera-ipadic-neologd のスキーマは、この 2 列の名前を逆に付けていたため、
名前で引くと常にもう一方の値が返っていました。v7.0.0 で名前を修正しました。
UniDic と SudachiDict は元から正しい順序です。
書いた の 書い の場合:
| フィールド名 | v6 | v7 |
|---|---|---|
conjugation_type | 連用タ接続 | 五段・カ行イ音便 |
conjugation_form | 五段・カ行イ音便 | 連用タ接続 |
変わるのは名前によるアクセスだけです。Token::get("conjugation_type") と
Token::get("conjugation_form")、Token::as_value() と
lindera tokenize -o json の JSON、バインディングが公開する辞書スキーマの
フィールド名が該当します。位置によるアクセス(details、MeCab 形式と wakati
形式の出力)は変わりません。値そのものは元から正しい列に入っていたためです。
これらのフィールドを名前で読んでいるコードや、回避策として 2 つを入れ替えて
読んでいたコードは修正してください。
フィールド名はライブラリではなく、ビルド済み辞書ごとに保存される
metadata.json から読み込まれます。埋め込み辞書(embed-ipadic・
embed-ipadic-neologd)と、7.0.0 の CLI が lindera download で取得する辞書は
修正後の名前になります。v6.2.0 以前にビルドまたはダウンロードした辞書
ディレクトリは読み込めなくなるため(辞書ディレクトリの再ビルドが必要を参照)、
v7.0.0 の lindera-ipadic/metadata.json(または
lindera-ipadic-neologd/metadata.json)で再ビルドするか、7.0.0 のリリース
アセットをダウンロードしてください。
IPADIC の metadata.json の独自のコピーで辞書をビルドしている場合は、
そのコピーの dictionary_schema.fields で "conjugation_form" と
"conjugation_type" を入れ替えてください。
空白を含む見出し語を保持する
v6.2.0 までは、辞書ビルダーがすべての見出し語の表層形を trim していました。そのため、 表層形が空白だけの語は辞書から落ち、先頭や末尾が空白の語は空白を削った表層形で 登録され、コストの低い空白付きの語が空白のない語を乗っ取ることがありました。 v7.0.0 は、MeCab や Sudachi と同じく表層形を書いたとおりに読みます。
| 辞書 | 変化 |
|---|---|
| IPADIC・IPADIC-NEologd | U+3000(全角空白)は未知語の 名詞,サ変接続 ではなく辞書の 記号,空白 になり、後ろの語も MeCab と同じに解析されます(東京 都 の 都 は 名詞,接尾 ではなく 名詞,一般)。ルーマニア の原形は、末尾に U+3000 の付いた形ではなく ルーマニア になります |
| UniDic | U+3000 は未知語の 名詞,普通名詞,サ変可能 ではなく辞書の 空白 になります |
| SudachiDict | U+0020 には SPACE の未知語ではなく辞書の半角スペースの語(空白)が使われ、空白を含む文の分割が Sudachi にずっと近くなります(すべての語の間に空白を入れた 600 文で、Sudachi との一致が 201 文から 597 文に増加)。keep_whitespace(true) では空白が 1 つずつトークンになります。SudachiDict には U+3000 の語がないため、U+3000 は変わりません |
| ko-dic | 単独の 에듀 と 캘리 は、末尾に空白の付いた語の NNG ではなく NNP(人名)になります |
| CC-CEDICT・Jieba | 変化なし |
末尾が空白の語(IPADIC-NEologd 55 語、ko-dic 4 語、SudachiDict 4 語)は、MeCab と
同じく、テキストにその空白があるときだけ一致するようになります。IPADIC-NEologd では、
末尾に半角スペースの付いた GeForce GTX Titan X の語が、GeForce GTX Titan X です には
一致し、GeForce GTX Titan Xです には一致しなくなります。空白がラティスに残るとき
(keep_whitespace(true)、または SudachiDict のデフォルトである
skip_whitespace(false))も、空白を読み飛ばすとき(ほかの辞書のデフォルト。次の節を
参照)も、MeCab と同じくトークンは末尾の空白を含みます。読み飛ばすのは語の後ろの
空白だけで、GeForce GTX Titan X です のように空白が 2 つ続く場合、トークンは
1 つ目の空白で終わります。IPADIC-NEologd の 55 語のうち 50 語は、ko-dic と
SudachiDict の語と同じく半角スペースで終わります。残りの 5 語は U+3000 か U+00A0 で
終わり、これらは SPACE の文字ではないため読み飛ばされません。
Decompose の長さペナルティは、空白がラティスに残るときと同じく、語自身の空白も
数えます。unique を指定した N-best では、この空白だけが異なる結果が 1 つに
まとめられなくなります(에듀 센터 に対する ko-dic の、半角スペースの付いた
에듀(NNG)と 에듀(NNP)など)。このような語の直後の語は、mecab-ko と
同じく左側空白ペナルティ(ko-dic)を受けません。mecab-ko のペナルティは、前の語の
表層に含まれない空白の後にだけかかるためです。에듀 이 は、半角スペースの付いた
에듀 と 이/VCP になります。
U+3000 は SPACE の文字カテゴリではないため、keep_whitespace(false) でも MeCab と
同じくトークンとして出力されます。取り除くには、japanese_stop_tags トークンフィルタで
記号,空白(IPADIC)や 空白(UniDic)を除くか、unicode_normalize 文字フィルタ
(NFKC)で U+0020 に変換してください。
変更は辞書ビルダーにあります。埋め込み辞書と、7.0.0 の CLI が lindera download
で取得する辞書には、これらの語が含まれます。v6.2.0 以前にビルドまたはダウンロード
した辞書ディレクトリは読み込めなくなります(辞書ディレクトリの再ビルドが必要を参照)。
空白をラティス上で読み飛ばす
keep_whitespace が false(デフォルト)のとき、v7.0.0 は MeCab と同様に空白を
Viterbi ラティス上で読み飛ばします。空白の後ろの語は空白の前の語に直接
接続します。v6 は空白を出力から除外していましたが、ラティスには SPACE の
未知語として残していたため、空白の前後の語は互いにではなくそのノードに接続して
いました。その影響の大きさは、辞書がそのノードに与える連接コストによります。
ko-dic ではすべて 0 なので、空白のたびに前後の語の文脈が途切れていました。
IPADIC と UniDic では 0 ではなく、語どうしの連接の代わりにそのノードのコストが
使われていました。
出力が変わるのは空白を含む文だけです。分割の区切りになった \n や \t で
終わる文(。 で終わらない行など)もこれに含まれ、その文の最後の語は文末に
直接接続するようになります。
| 辞書 | 影響 | 例 |
|---|---|---|
| ko-dic | 空白を含む韓国語が mecab-ko と同じ解析になる | 2년 전 대회 の 전 は 저/NP + ㄴ/JX ではなく NNG、하고 있다 の 있 は VV ではなく VX |
| IPADIC・IPADIC-NEologd・UniDic | MeCab と同様に、半角空白の後ろの語が空白の前の語に直接接続する | IPADIC の Google が 新しい の が は接続詞ではなく格助詞、東京 都 の 都 は MeCab と同様に接尾 |
| SudachiDict | 変わらない。Sudachi は空白をラティスに残し、コストもそれを前提にしているため、metadata.json で skip_whitespace を false にしている | — |
| CC-CEDICT・Jieba | 最良パスは変わらない(連接コストを持たない辞書のため)。N-best のコストに空白ノードの分が含まれなくなる | — |
デフォルトは辞書の metadata.json で決まり、skip_whitespace を設定していない
辞書は読み飛ばします。Lindera 6.x でビルドした SudachiDict にはこの設定が
ありませんが、そもそも読み込めなくなります。再ビルドした辞書にはこの設定があります。
トークンの表層形とオフセットには読み飛ばした空白が含まれません。ただし、見出し語の
一部である空白は、語の途中にあっても末尾にあっても、MeCab と同じくその語のトークンに
残ります(前の節を参照)。読み飛ばしは空白を含まないテキストの分割を変えません。
keep_whitespace(true) では空白がラティスに残るため、出力は v6 と同じです。
ただし、空白トークン以外のトークンも、空白を読み飛ばす v7 のデフォルトの出力と
変わることがあります。
空白の扱いを v6 に戻すには、読み飛ばしを無効にします。空白は引き続き出力から
除外されます。ただし、MeCab と同じく、どの位置からも未知語が始まる
など、このガイドの他の出力の変化は戻りません。設定で skip_whitespace を省略するか
null にすると、辞書のデフォルトのままになります:
#![allow(unused)] fn main() { // Segmenter let segmenter = Segmenter::new(Mode::Normal, dictionary, None).skip_whitespace(false); // SegmentWorker worker.set_skip_whitespace(false); }
# 設定ファイル / TokenizerBuilder::set_segmenter_skip_whitespace(false)
segmenter:
skip_whitespace: false
lindera tokenize --disable-skip-whitespace
言語バインディングでは TokenizerBuilder で設定します。Python では
set_skip_whitespace(False)、Ruby では set_skip_whitespace(false)、Node.js・
PHP・WASM では setSkipWhitespace(false) です。null(Python では None、
Ruby では nil)を渡すと辞書のデフォルトに戻ります。
char.def の重複した行を MeCab と同じく解決する
辞書の char.def では、同じコードポイントを複数の行に書くことができます。v6.2.0
までは、辞書ビルダーがそのコードポイントに、覆うすべての行のカテゴリを与えていました。
v7.0.0 は MeCab・vibrato・Kuromoji と同じく最後の行だけを使います。同梱の辞書で
変わるのは次の 3 文字です(漢字の
カテゴリ名は、ko-dic では HANJA・HANJANUMERIC、CC-CEDICT と Jieba では
CHINESE・CHINESENUMERIC です)。
| 文字 | v6 のカテゴリ | v7 のカテゴリ | 影響 |
|---|---|---|---|
Ð(U+00D0) | SPACE・ALPHA | ALPHA | ko-dic 以外のすべての辞書は、ALPHA の範囲より前に U+00D0 を SPACE に割り当てています(mecab-ipadic から引き継がれた U+000D のタイポ)。このため Ð は空白として出力から消え、前後の語も分断されていました。GUÐMUNDUR さん は GU・MUNDUR・さん ではなく GUÐMUNDUR・さん になります |
々(U+3005) | KANJI・SYMBOL | SYMBOL | 人々 や 佐々木 のような辞書の語は変わりません。辞書にない漢字の後ろでは、MeCab と同じく 々 が単独のトークンになります(龘々 は 1 つの未知語ではなく 龘・々)。ko-dic では 々 の品詞が SH ではなく SY になります |
〇(U+3007) | KANJI・SYMBOL・KANJINUMERIC | SYMBOL・KANJINUMERIC | 数字の連続のまとまり方が変わることがあります。ko-dic では 二〇二六年 が 二・〇・二六・年 ではなく 1 つの未知語(SH)になります(MeCab と同じく、未知語は既定カテゴリから作るも参照) |
漢数字 一〜九・十・百・千・万・億・兆 も 2 つの行に書かれていますが、
カテゴリは変わりません。
lindera train は char.def を辞書ビルダーで読むようになりました。従来は各行の
先頭のカテゴリだけを使い、単一コードポイントの行(0x0020 SPACE など)を読み飛ばし、
重複は範囲の開始位置で解決していました。素性テンプレートの %t は、その文字を覆う
最後の行の先頭カテゴリ(MeCab の既定カテゴリ)になります。同梱の学習用ファイルには
どちらの種類の行もないため、それらから学習したモデルは変わりません。
カテゴリのない範囲行は、MeCab と同じく lindera build と lindera train でエラーに
なります。そうしないと、それより前の行を DEFAULT に戻してしまうためです。
変更は辞書ビルダーにあります。埋め込み辞書と、7.0.0 の CLI が lindera download
で取得する辞書は、新しいカテゴリを持ちます。v6.2.0 以前にビルドまたはダウンロード
した辞書ディレクトリは読み込めなくなります(辞書ディレクトリの再ビルドが必要を参照)。
、・。 をまたいで文脈を引き継ぐ
セグメンターは、各ラティスを小さく保つために、入力を \n・\t・。・、 で
文に区切り、区切り文字のないまま 32 KiB を超えたところでも区切ります。v6 までは、
どの文も文頭(BOS)から始まり、文末(EOS)への連接で終わっていたため、、 や 。 の
直後の語は、テキストの先頭にあるものとして解析されていました。MeCab は行の中では
区切りません。v7.0.0 は、、・。 と強制的な区切りをまたいで文脈を引き継ぎます。
次の文は前の文の最後の語から始まり、EOS への連接は \n・\t・入力の終わりでだけ
払います。そこまでの文の並びが 1 つの区間(segment)になり、区間どうしは引き続き
独立しています。区間の最良のパスとそのコストは、区間全体を 1 つのラティスで解いた
場合と同じになり、MeCab が 1 行に対して返す結果と一致します。
| 入力(辞書) | v6 | v7(MeCab と同じ) |
|---|---|---|
彼は、ああ言った(IPADIC) | ああ が感動詞 | ああ が副詞 |
だから、こんなに答える(IPADIC) | こんな(連体詞)と に | こんなに(副詞) |
選手が泳ぎ、さらにカヌーで進んだ(UniDic) | さらに が接続詞 | さらに が副詞 |
言語としては、Javaのオブジェクト(UniDic) | J・a・v・a(それぞれ 記号,文字) | Java(名詞,普通名詞) |
UD Japanese GSD の 、 を含む文では、UniDic で 50 行の出力が変わり、そのうち
27 行は正解の分割と品詞に近づき、7 行は離れ(符号検定 p = 0.0008)、16 行は
同点でした。『坊っちゃん』の 505 段落のうち、MeCab と分割・品詞が完全に一致する
段落は、IPADIC で 352 から 438 に、UniDic で 428 から 503 に増えました。、・。・
強制的な区切りを含まないテキスト(\n と \t だけで区切られるテキストなど)は
影響を受けません。文脈の引き継ぎで、1-best の分割の命令数は 1% 程度増えます。
次の場合は、区間全体を 1 つのラティスで解いた結果と異なることがあります。
- 区切りをまたぐ語はできません。
、や。を含む見出し語はマッチしません。 UniDic の一、二塁を、MeCab は 1 つの名詞として、Lindera は一・、・二塁として解析します。未知語も区切りをまたいでまとめられず、強制的な区切りを またぐはずの語はそこで分かれます。 - 強制的な区切りの位置から始まる語には、区切りの直前が空白でも左側空白ペナルティ (ko-dic)がかかりません。
N-best の結果も同じモデルに従います。区間の中では、結果は区間全体を 1 つの
ラティスで解いたときの上位 N 本のパスと厳密に一致します。unique は区間全体の
単語境界で比較し、コストの閾値は 1 件目の結果から測ります。区間をまたぐ結果は、
区間ごとに 1 つのパスを選んだ組み合わせのうちコストの小さいものです(次の節を
参照)。1 件目の結果は 1-best の分割と同じです。、 や 。 を含む行のコストは、
区切りをまたぐ連接を含み、EOS への連接を 1 回だけ含むようになるため、1 件目の
結果も含めて変わります。
v6 の動作に戻す設定はありません。
N-best の結果が入力全体を覆う
N-best のメソッド(Segmenter::segment_nbest・Tokenizer::tokenize_nbest、それらの
_with_lattice 版と worker 版、lindera tokenize -N、バインディングの N-best
メソッド)は、1-best の分割と同じく入力を文に区切ります。v6 までは文ごとに探索し、
k 件目の結果は各文の k 番目のパスをつないだものでした。2 件目以降はすべての文が
同時に変わるため、結果は入力全体でコストの小さい分割には
ならず、コストが下がることもありました。また、パスが k 個未満の文は k 件目の結果から
抜けていました。v7.0.0 は入力を区間ごとに探索し(前の節を参照)、区間ごとに 1 つの
パスを選んだ組み合わせのうちコストの小さい N 件を合計コストの昇順に返します。どの結果も
入力全体を覆います。
入力(IPADIC、-N 3) | v6 | v7 |
|---|---|---|
東京、です | 3079 東京 、 です、23850 東 京 、 で す、24249 東 京 、 で す | 3357 東京 、 です、12059 東京 、 です(、 が 名詞,数)、13633 東京 、 で す |
。東京 | 13 。 東京、30433 。 東 京、13520 東 京(。 がない) | 852 。 東京、13569 。 東 京、13636 。 東 京 |
どちらの行も 1 つの区間なので、v7 の結果は、行全体を 1 つのラティスで解いたときの
上位 3 本のパスで、MeCab が返すものと同じです。1 件目の結果は 1-best の分割と同じで、
文が 1 つだけの入力の出力は変わりません。コストの閾値
(cost_threshold・--nbest-cost-threshold)は入力全体で測るようになりました。
合計コストが 1 件目の結果のコストから閾値以内の結果を残します。v6 は閾値を文ごとに
適用していました。unique を指定した場合も、結果の単語境界は互いに異なります。
空の入力は、MeCab と同じく、また空白だけの入力がすでにそうだったように、BOS から
EOS への連接コストを持つトークンのない結果を 1 件返すようになりました。v6 は、空の
入力には文がないため結果を返しませんでした。segment_nbest・tokenize_nbest、
それらのワーカー版、バインディングの N-best のメソッド、lindera tokenize -N が
対象で、lindera tokenize -N は空行にも結果を出すようになります。
MeCab と同じく、どの位置からも未知語が始まる
辞書にない語は未知語として扱われ、辞書の char.def の文字種(カタカナ・英字・
数字・記号など)をもとに作られます。多くの文字種はグルーピングする設定で、
カタカナの連続のように文字種を共有する文字が続く範囲が 1 つの候補になります。これを
「グルーピングした未知語」と呼びます。v6 までの通常モードは、直前にグルーピング
した未知語の内側の位置では未知語を作りませんでした。Kuromoji から引き継いだ
近道で、MeCab にはないものです。そのため、こうした範囲の途中で終わる辞書語の
後ろに未知語を続けられず、辞書語を飲み込んだグルーピングした未知語しか通り道が
ないことがよくありました。v7.0.0 は、経路が到達するすべての位置で、どのモードでも、
MeCab と同じ条件で未知語候補を作ります。条件は、候補を作る文字種が常に未知語を作る
設定(char.def の INVOKE)であるか、その位置から始まる辞書語がないことです。
Decompose モードはもともとこの動作で、出力は変わりません。
| 入力(辞書) | v6 | v7(MeCab と同じ) |
|---|---|---|
それが「⁂⁂第一だ(IPADIC) | 「⁂⁂(辞書の記号 「 を飲み込んだ 1 つの未知語の名詞) | 「(記号,括弧開)と ⁂⁂ |
ジョン・レノンが歌う(IPADIC) | ジョン・レノン(1 つの未知語の名詞) | ジョン、・、レノン |
ホテル・コルテシアに泊まる(UniDic) | ホテル・コルテシア(1 つの未知語の名詞) | ホテル、・、コルテシア |
"좋아."(ko-dic) | 아(EC)と ."(SY) | 아(EF)、.(SF)、"(SY) |
The(CC-CEDICT) | The | T と he |
- 日本語: IPADIC・IPADIC-NEologd・UniDic の
char.defは・をカタカナの 範囲に含めています。辞書語と・の後ろからはカタカナの未知語を始められなかった ため、ジョン・レノンは 1 つの未知語になっていました。v7 では MeCab と同じく、 その後ろから未知語が始まります。レノン・ジョンのように未知語で始まる範囲は、 MeCab でも 1 つの未知語のままです。同じように、辞書の記号がその後ろの未知の記号と まとめられることもなくなります。 - 韓国語: 文末の
."・?"・!"・.'は 1 つの記号(SY)になっていました。 v7 では MeCab と同じく.・?・!が文末の句読点(SF)になり、"좋아."の ように、その前の語尾の解析も変わることがあります。 - 中国語: CC-CEDICT では、
A・B・P・Q・Tで始まる英単語がその文字の 後ろで分かれ(TheはTとhe、TBSはT、B、S)、日本語の仮名の 連続は 1 文字ずつに分かれます。同じ辞書を使う MeCab と同じ分け方です。原因は 辞書にあります。未知語のコストは長さによらず −3,200、連接表は 1 マスだけで コストは 0、これらの英字 1 文字はコスト −400 の辞書語なので、語の数が多い経路ほど 安くなります。たとえばTとhe(−3,600)はThe(−3,200)より安くなります。 これまでは近道がこの分割を隠していました。Jieba は影響を受けません。 - SudachiDict でも、同じような範囲で出力が変わることがあります。
- N-best: 1 件目の結果は 1-best の分割と同じように変わります。2 件目以降には、
GoogleをGとoogleに分ける(UniDic)ような結果も現れるようになります。
同じソースからビルドした辞書で、Lindera の分割と品詞が MeCab と完全に一致する
行は、このガイドの他の変更に加えてさらに増えます。IPADIC では日本語 600 文のうち
589 から 596 に、『坊っちゃん』の 505 段落のうち 438 から 459 に、IPADIC-NEologd
では同じく 590 から 596 に、441 から 459 に、ko-dic では韓国語 1,500 文のうち 877 から
1,498 に増えました(MeCab には左側空白ペナルティがないため、ko-dic はペナルティを
無効にして比較)。一致していた行が一致しなくなった例はなく、UniDic はこれらの
テキストでは変わりません。UD Japanese GSD を UniDic で解析すると 64 文が変わり、
53 文は正解の分割と品詞に近づき、10 文は離れ(符号検定 p = 3.4 × 10⁻⁸)、1 文は
同点でした。離れた 10 文はいずれも MeCab と同じ出力で、GSD が ・ でつないだ名前や
数字の一部を 1 語としているためです。
同じ文字種が続く範囲の内側のすべての位置で、MeCab と同じく候補を作るため、
カタカナや英字の連続が多いテキストでは処理が増えます。この変更を除いた同じ
リリースと比べると、あわせて入れた高速化を含めて、命令数は UD Japanese GSD
(Wikipedia の文章)で 13%(N-best では 14〜17%)増え、『坊っちゃん』と韓国語で
0.8%、Decompose モードで 14〜16% 減ります。区切り文字のないカタカナ 10,240 文字の
行では、命令数が 1-best で 3〜5 倍、N-best で 3〜15 倍になり、その行に対する
lindera tokenize -N 3 の最大メモリは IPADIC で 12 MiB から 56 MiB に、UniDic で
18 MiB から 127 MiB に増えます。
v6 の動作に戻す設定はありません。unknown_word_ladder(false)
(--disable-unknown-word-ladder)は引き続き短い未知語候補を無効にしますが、
v6 より前の Lindera の出力は再現しなくなりました。
MeCab と同じく、未知語は既定カテゴリから作る
char.def では、1 つの文字が複数のカテゴリを持つことがあります。漢数字は
IPADIC と UniDic では KANJINUMERIC KANJI、ko-dic では HANJANUMERIC HANJA、CC-CEDICT と
Jieba では CHINESENUMERIC CHINESE で、〇(U+3007)は SYMBOL と数字の
カテゴリを持ちます。その文字のカテゴリを決める行の先頭のカテゴリを、その文字の
「既定カテゴリ」と呼びます。v6.2.0 までの Lindera は、文字のカテゴリごとに未知語
候補を作り、グルーピングした未知語は、次の文字がカテゴリの並びの同じ位置に同じ
カテゴリを持つ間だけ続けていました。v7.0.0 は MeCab に合わせます:
- 未知語は既定カテゴリだけから、その
INVOKE・GROUP・LENGTHの設定とunk.defのエントリで作る。 - グルーピングした未知語は、どの文字も直前の文字とカテゴリを共有する間続く。
- 候補ラダーの短い候補は、どの文字も先頭の文字とカテゴリを共有する間続く。
| 入力(辞書) | v6 | v7(MeCab と同じ) |
|---|---|---|
三國史記(ko-dic) | 三(SH)・國史(NNG)・記(NNG) | 三國史記(SH) |
十人十色(ko-dic) | 十(SH)・人(NNG)・十(SH)・色(NNG) | 十人十色(SH) |
二十歲(ko-dic) | 二十(SH)・歲(SH) | 二十歲(SH) |
〇〇〇(ko-dic) | 〇〇〇(SH) | 〇〇〇(SY) |
二〇〇八年北京奧運會(Jieba) | 二・〇〇・八・年・北京奧運・會 | 二・〇〇八年北京奧運會(w) |
二〇二六年(CC-CEDICT) | 二・〇・二・六・年 | 二・〇二六年 |
- 韓国語: ko-dic では
三や十など多くの漢数字が辞書語でなく、その既定 カテゴリHANJANUMERICはグルーピングするため、MeCab と同じく漢数字と後ろの 漢字が 1 つのSHになります。 〇: 既定カテゴリのSYMBOLは辞書語が始まる位置でも未知語を作るため、 記号になります(ko-dic はSY、Jieba はw)。CC-CEDICT と Jieba では、MeCab でも 同じく、そのグループが後ろの数字と漢字まで続きます。この 2 つの辞書のchar.defの修正は #1162 で扱います。- 日本語: IPADIC・IPADIC-NEologd・UniDic では漢数字と
〇が辞書語で、比較した テキストでは 1 行も変わりません。SudachiDict は比較していません。
同じソースからビルドした辞書で、Lindera が MeCab とまったく同じに分割・品詞付け する行は、このガイドのほかの変更に加えて、ko-dic の『상록수』で空行を除く 3,919 行のうち 3,913 行から 3,917 行に増えます(MeCab にはないため左側空白ペナルティは無効)。 複数のカテゴリを持つ文字とほかの文字を組み合わせて作った文は、IPADIC・ IPADIC-NEologd・UniDic・ko-dic・CC-CEDICT・Jieba のすべてで MeCab と一致するように なります。以前に一致していた行で一致しなくなる行は ありません。命令数は 0.3〜4.8% 減ります。
CharacterDefinition::lookup_categories(lindera_dictionary::dictionary::character_definition)
は、既定カテゴリを先頭に、残りのカテゴリをカテゴリ ID 順に返します。7.0.0 の
char_def.bin はこの順に保存するため、辞書の形式バージョンが変わります
(辞書ディレクトリの再ビルドが必要を参照)。
v6 の動作に戻す設定はありません。
ダッシュとチルダを辞書に書かれたとおりに保持する
日本語のテキストでは、ダッシュとチルダにそれぞれ 2 通りの文字が使われます。
ダッシュは ―(U+2015、HORIZONTAL BAR)か —(U+2014、EM DASH)、チルダは
~(U+FF5E、FULLWIDTH TILDE)か 〜(U+301C、WAVE DASH)です。v6.2.0 までは、
辞書ビルダーが IPADIC と IPADIC-NEologd のすべての見出し語で、― を — に、~ を
〜 に書き換えていました。この 2 つの辞書の metadata.json が、設定
normalize_details で書き換えを有効にしていたためです。書き換えの対象は、表層形
(セグメンターがテキストの中でその語を探すときの綴り)と、原形や読みなどの詳細情報の
欄です。解析するテキストは書き換えないため、辞書のソースで ― や ~ を使って
書かれた語は、その文字を — や 〜 で書いたテキストにしか一致しませんでした。
この書き換えは、古い EUC-JP の変換に対する回避策でした。いまの辞書のソースは
UTF-8 です。また、Shift_JIS や EUC-JP からの主な変換が出力するダッシュも、
青空文庫のテキストが使うダッシュも ― です。v7.0.0 は MeCab と同じく、すべての
見出し語を辞書の CSV に書かれたとおりに保持します。
| 入力(辞書) | v6 | v7(MeCab と同じ) |
|---|---|---|
CD―ROM(IPADIC) | CD、―、ROM | CD―ROM(名詞,一般) |
――(IPADIC) | 未知語の名詞(名詞,サ変接続) | 記号,一般 |
あいいれなぁ~い(IPADIC-NEologd) | あいいれなぁ、~、い | あいいれなぁ~い(形容詞,自立) |
CD―ROM(IPADIC-NEologd) | CD、―、ROM | CD―ROM(名詞,一般) |
CD—ROM(IPADIC) | CD—ROM(名詞,一般) | CD、—、ROM |
——(IPADIC) | 記号,一般 | 未知語の名詞(名詞,サ変接続) |
詳細情報の欄も CSV の綴りのままです。見出し語 CD―ROM の原形は、v6 の
CD—ROM ではなく CD―ROM になります。
同じソースからビルドした辞書で、Lindera の分割と品詞が MeCab と完全に一致する 行は、このガイドの他の変更に加えてさらに増えます。『坊っちゃん』の 505 段落の うち 459 から 503 に(2,746 文のうち 2,690 から 2,744 に)、同じく青空文庫の 『こころ』と『吾輩は猫である』の 3,587 段落のうち 3,377 から 3,581 に増えました。 IPADIC と IPADIC-NEologd で同じ数で、一致していた行が一致しなくなった例は ありません。
— や 〜 で書いたテキストからは、書き換えによってしか引けなかった語が引けなく
なります。IPADIC では 12 語(すべて ― で書かれた語)、IPADIC-NEologd では
1,085 語(ほとんどが ~ で書かれた語)です。上の表のとおり、CD—ROM は
分かれ、—— は未知語の名詞になります。どちらも MeCab と同じです。
IPADIC-NEologd はチルダを含む語のほとんどを両方の綴りで持っているため、どちらの
チルダで書いたテキストからも引けます。
両方の綴りから語を引けるようにするには、mapping 文字フィルタでテキストを辞書の
綴りにそろえます。IPADIC はダッシュを含む語をすべて ― で、チルダを含む語を
すべて 〜 で書いているので、— を ― に、~ を 〜 に置き換えます:
lindera tokenize --dict ipadic --char-filter 'mapping:{"mapping":{"—":"―","~":"〜"}}'
# 設定ファイル
character_filters:
- kind: mapping
args:
mapping:
"—": "―"
"~": "〜"
このフィルタを使うと、CD—ROM は再び 1 語になり、—— は 記号,一般 に
戻ります。さらに、~ で書いたテキストからも、v6 でも引けなかった IPADIC の
〜 の語を引けるようになります。トークンの表層形は置き換え後の文字
(CD―ROM など)になり、バイト位置は元のテキストを指します。チルダを逆向きに
(〜 を ~ に)置き換えると、IPADIC の 〜 の語が引けなくなります。
IPADIC-NEologd では、置き換えは語の入れ替えになります。片方の綴りにしかない語が
どちらの側にもあるためです(― だけの語が 129、— だけの語が 85、~ だけの
語が 957、〜 だけの語が 1,813)。一方の綴りにそろえると、その綴りの語が引ける
かわりに、もう一方の綴りの語が引けなくなります。NFKC 正規化
(unicode_normalize 文字フィルタ)では綴りはそろいません。~ は ~ になり、
―・—・〜 はそのまま残ります。
ユーザー辞書はもともと書き換えていないため、変わりません。変更は辞書ビルダーに
あります。埋め込み辞書と、7.0.0 の CLI が lindera download で取得する辞書は、
書かれたとおりの綴りを持ちます。v6.2.0 以前にビルドまたはダウンロードした辞書
ディレクトリは読み込めなくなります(辞書ディレクトリの再ビルドが必要を参照)。
UniDic・ko-dic・CC-CEDICT・Jieba・SudachiDict はこの書き換えを有効にしていません
でした。
設定 normalize_details の削除
normalize_details はこの書き換えを切り替えるだけの設定だったため、v7.0.0 では
同梱の metadata.json と、辞書のメタデータを作成・公開する API から削除しました。
| API | 変更 |
|---|---|
| Rust | Metadata::new の引数が 11 個から 10 個になり(9 番目の normalize_details を削除)、Metadata::normalize_details フィールドと PrefixDictionaryBuilderOptions::normalize_details メソッドを削除。バインディングの作者向けの lindera_binding::CoreMetadata と CoreMetadata::new も同じように変わる。lindera_dictionary の Rust API 変更を参照 |
| Python | Metadata のキーワード引数 normalize_details、プロパティ、to_dict() のキーを削除。キーワード引数を渡すと TypeError になる |
| Ruby | Metadata.new の位置引数が 9 個から 8 個になり、9 個渡すと ArgumentError になる。normalize_details の読み取りメソッドと to_h のキーを削除 |
| PHP | Metadata のコンストラクタの最後の引数 normalize_details、プロパティ、toArray() のキーを削除。この引数を渡すと、名前付き引数なら Error、9 番目の位置引数なら ArgumentCountError になる |
| Node.js | Metadata のオプション normalizeDetails、プロパティ、toObject() のキーを削除。TypeScript ではこのオプションがエラーになり、実行時には無視される |
| WASM | 変更なし(Metadata はこの設定を公開していなかった) |
normalize_details を含む metadata.json(v6 が書き出したものなど)は、ビルド
済みの辞書に付いているものも、lindera build に渡すものも、バインディングで
読み込むものも、これまでどおり読み込めます。キーは無視され、書き出すときには
含まれません。
MeCab と同じく、同点の見出し語は CSV で先の行を選ぶ
辞書には、表層形・左右の文脈 ID・コストがすべて同じで、読みや原形などの詳細情報 だけが違う見出し語が複数あることがあります。このガイドでは、これを同点の見出し語と 呼びます。どれを使っても経路のコストは同じなので、どれを出力するかは、セグメンターが それらを候補にする順で決まります。v6.2.0 までの Lindera は、ある位置から始まる辞書の 見出し語を逆の順でラティスに入れていました。探索はコストが同じなら先に入った候補を 残すため、同点の見出し語のうち CSV で最後の行が選ばれていました。逆の順になったのは v2.1.0 からで、意図しない変更だったとみられます。v7.0.0 は MeCab と同じく最初の 行を出力します。
| 入力(辞書) | v6 | v7(MeCab と同じ) |
|---|---|---|
あいつは狡い(IPADIC) | 狡い の読みが コスイ | 狡い の読みが ズルイ |
IPADIC の Adj.csv には、この 2 行がこの順で並んでいます。
狡い,19,19,4682,形容詞,自立,*,*,形容詞・アウオ段,基本形,狡い,ズルイ,ズルイ
狡い,19,19,4682,形容詞,自立,*,*,形容詞・アウオ段,基本形,狡い,コスイ,コスイ
同点の見出し語を持つ表層形は、IPADIC に 5,637、IPADIC-NEologd に 90,991、UniDic に
3,324、ko-dic に 175 あります。日本語の辞書では、同点の行どうしで品詞の欄が違う
ことはなく、違うのは読みや原形などの欄だけです。ko-dic では 5 組で品詞の欄も
違います。同点の見出し語の表層形を 1 行に 1 つずつ解析すると、詳細情報まで
MeCab 0.996 と一致する表層形は、IPADIC で 5,637 のうち 1,607 から 5,637 に、
UniDic で 3,324 のうち 524 から 3,324 に、IPADIC-NEologd で 86,464 のうち 8,205 から
86,319 に増えます(IPADIC-NEologd は空白や制御文字を含む表層形を除いて比較)。
IPADIC-NEologd の残りの 145 は、、 を含む表層形(区切りをまたぐ語はできません。
、・。 をまたいで文脈を引き継ぐを参照)か、MeCab が
別の順で行を読む組(後述)です。ko-dic では、MeCab の辞書を CSV ファイルの名前の順に
ビルドすると、175 すべてが一致します。
文章では、詳細情報まで MeCab と一致する行が、このガイドの他の変更に加えてさらに
増えます。IPADIC では UD Japanese GSD の 8,100 文のうち 7,989 から 8,096 に、
『こころ』と『吾輩は猫である』の 3,587 段落のうち 3,278 から 3,583 に、UniDic では
同じ 3,587 段落のうち 3,394 から 3,571 に増えました。分割が変わった行はありません。
比較したすべてのテキストで、品詞が変わったトークンは 15 個あり、どれも MeCab と
同じ品詞になりました。その多くは、文脈 ID もコストも違う同じ表層形の見出し語どうしで、
その文の中でたまたま経路のコストが同じになったものです。どちらを選ぶかは同じ順で
決まります。たとえば UniDic の 御前 は、MeCab と同じく、普通名詞
(名詞,普通名詞,一般)ではなく代名詞(代名詞)になります。
ユーザー辞書も同じ規則に従います。同点のユーザー辞書の見出し語からは、最後の行では
なく最初の行が選ばれるようになりました。ユーザー辞書の見出し語とシステム辞書の
見出し語が同点の場合は、v6 と同じくユーザー辞書の見出し語が選ばれます。MeCab は
システム辞書の見出し語を選ぶので、意図して MeCab と変えているのはこの点だけです。
このような同点になるのは、詳細形式のユーザー辞書で、システム辞書の見出し語の文脈 ID と
コストをそのまま使った場合(たとえば語に別の読みを与える場合)です。3 列の簡易形式の
見出し語には、辞書の metadata.json のコストと文脈 ID(default_word_cost・
default_left_context_id・default_right_context_id。IPADIC では -10000・0・
0)が使われるため、システム辞書の見出し語と同点になることはまずありません。
N-best の探索も同じ順を使います。さらに、N-best の 1 件目の結果は、常に 1-best の
分割(segment の出力)と同じになりました。v6 までは、コストがまったく同じパスが
複数あると、別のパスになることがありました。N-best の結果のコストは変わりません。
入れ替わるのはコストが同じ結果どうしの順だけで、そのため n 件目で切られる結果が
変わることもあります。N-best の命令数は 3〜4% 程度増えます(-N 3)。1-best の
分割の速度は変わりません(差は 0.2% 以内)。
unique(--nbest-unique)を指定した場合、各結果はその単語境界のパスのうち最も
コストの低いもので、同点の見出しは、1-best の分割と同じく、どの結果でも CSV の先の行に
なりました。v6 までの unique な N-best は、パスをコストの順にたどって各区切りの最初の
パスを残していたため、同点の組のどちらの行にもなりえました。また、次の区切りに
届くまでに 1 つの区切りのパスを
すべてたどっていたため、単語が多くの見出しを持つ短い行で、数十秒・数 GB かかることが
ありました。探索は単語境界を単位に進むようになり、パスの数に左右されません。
ここでも、入れ替わるのはコストが同じ結果だけです。
MeCab の mecab-dict-index は CSV ファイルをファイルシステムが返す順(readdir の順)に
読むため、行が複数のファイルにまたがる同点の組(IPADIC-NEologd と ko-dic にあります)
では、MeCab がどの行を選ぶかはファイルシステムによって変わります(Lindera は
ファイル名の順に読みます)。
変更はセグメンターにあり、辞書のファイルは変わりません。v6 の動作に戻す設定は ありません。
MeCab と同じく、コストが同じ分割からは最後の語が後から始まる方を選ぶ
2 つの分割が、同じ位置までまったく同じコストで届き、しかも最後の語の始まる位置が 違うことがあります。MeCab は、最後の語が後から始まる方を残します。MeCab は語を、 その語が終わる位置で終わる語の一覧の先頭に入れるので、後から始まる語ほど一覧の 前に来ます。そして、コストが同じ語からは先頭のものを残します。v6.2.0 までの Lindera は、最後の語が先に始まる方を残していました。v7.0.0 は MeCab と同じ方を 選びます。
| 入力(辞書) | v6 | v7(MeCab と同じ) |
|---|---|---|
腸窒扶斯(IPADIC) | 腸 / 窒 / 扶斯 | 腸 / 窒扶 / 斯 |
にフリーホイールダイオードや(IPADIC) | フリーホイールダイオード(1 つの未知語) | フリー / ホイール / ダイオード |
家系譜(UniDic) | 家 / 系譜 | 家系 / 譜 |
차나 마셔(ko-dic) | 차나 / 마셔 | 차 / 나 / 마셔 |
腸窒扶斯 の 2 つの分割は、どちらもコストが 28,382 です。このような同点はまれです。
比較したテキストは、『坊っちゃん』(1 ファイル・段落ごと・文ごと)、『こころ』と
『吾輩は猫である』の 3,587 段落、UD Japanese GSD の 8,100 文をそれぞれ IPADIC・
IPADIC-NEologd・UniDic で解析したものと、『상록수』の 7,759 行を ko-dic で解析した
もので、合わせて約 54,000 行です。そのうち変わったのは 9 行で、どれも MeCab と同じ
分割になり、MeCab から離れた行はありませんでした。MeCab にない Decompose モード
でも、テキストごとに 0〜2 行が同じように変わります。
空白を読み飛ばすとき(空白をラティス上で読み飛ばす を参照)は、MeCab と同じく、まず後で終わる語が勝ちます。MeCab は、語が終わる各位置 から次の語を引き直すためです。空白で終わる見出し語は、空白の前で終わる語に勝ちます。 理論上は、MeCab との違いが 1 つ残ります。空白を読み飛ばした後、MeCab は前の語が 終わりうる位置ごとに別のノードを持ちますが、Lindera は 1 つにまとめます。そのため、 同じ空白の直後から始まる 2 つの語が、別の位置で終わる前の語を通って同点になると、 MeCab と違う方を選ぶことがあります。空白で終わる見出し語とまったくの同点が重なる 必要があり、比較したテキストでは起きていません。
同じ位置から始まる語どうしでは、
MeCab と同じく、同点の見出し語は CSV で先の行を選ぶ
の順がそのまま使われ、N-best の 1 件目も引き続き 1-best の分割と同じです。、・。 を
またいで文脈を引き継ぐときも、前の文の終わりの語どうしの同点は、行全体を 1 つの
ラティスで解析したときと同じく、後から始まる語が選ばれます
(、・。 をまたいで文脈を引き継ぐを参照)。
1-best の分割の命令数は、IPADIC・IPADIC-NEologd・UniDic で 0.6〜0.9% 増えますが、
時間の差は測れない程度です。ko-dic では命令数が約 2%、時間が 2〜5% 増えます。
N-best(-N 3)の命令数は約 1% 増えます。
変更はセグメンターにあり、辞書のファイルは変わりません。v6 の動作に戻す設定は ありません。
lindera tokenize は行末の改行コードだけを取り除き、すべての行に結果を出す
lindera tokenize は入力を 1 行ずつ読み込みます。v6.2.0 までは、各行の両端の空白
(半角空白・タブ・U+3000・U+00A0 などの Unicode の空白、str::trim)をすべて
取り除いてから解析していました。ライブラリ・言語バインディング・MeCab は取り除きません。
v7.0.0 は行末の改行コード(\n または \r\n)だけを取り除き、残りをライブラリと
同じように解析します。
| 入力行(IPADIC) | v6 | v7(MeCab と同じ) |
|---|---|---|
半角空白 2 つ、東京、半角空白 1 つ | 東京 が 0-6 バイト目 | 東京 が 2-8 バイト目 |
U+3000、東京 | 東京 が 0-6 | U+3000(記号,空白)が 0-3、東京 が 3-9 |
東京、U+3000 | 東京 が 0-6 | 東京 が 0-6、U+3000(記号,空白)が 6-9 |
U+00A0、東京、U+00A0 | 東京 が 0-6 | U+00A0(未知語、記号,一般)が 0-2、東京 が 2-8、U+00A0 が 8-10 |
- 半角空白とタブは
SPACEの文字で、Segmenter が読み飛ばして出力から除くため、 これまでどおりトークンにはなりません。変わるのはその後のトークンのバイトオフセット だけで、ライブラリのオフセットと同じく入力行の中の位置を指すようになります。 - 行頭・行末の U+3000 は、行の途中と同じく、IPADIC・IPADIC-NEologd(
記号,空白)と UniDic(空白)ではトークンになります (空白を含む見出し語を保持するを参照)。 1 ファイルにまとめた『坊っちゃん』の 538 行のうち、202 行は U+3000 の字下げで 始まり、このトークンが計 206 個現れ、ほかのトークンは変わりません。 UD Japanese GSD の 8,100 文や『こころ』と『吾輩は猫である』の 3,587 段落の ように、行頭・行末に空白のないテキストの出力は変わりません。 --keep-whitespaceでは、行頭・行末の半角空白もトークンになります。半角空白 2 つ、東京、半角空白 1 つの行は 3 つのトークンになります。-Nでは、半角空白やタブだけの行と空行が、結果なしではなく、MeCab と同じく トークンのない結果を 1 件(NBEST 1とEOS)返します(空の入力については N-best の結果が入力全体を覆うを参照)。 行頭・行末に U+3000 がある行のコストには、そのトークンが含まれます。- wakati 形式は、入力 1 行につき(
-Nでは結果 1 件につき)1 行を出します。空行や 空白だけの行のようにトークンのない行は、MeCab と同じく空行になります。v6 はこの 行に何も出さなかったため、出力の行数が入力より少なくなっていました(1 ファイルに まとめた『坊っちゃん』では、空行 33 行を含む入力 538 行に対して出力 505 行)。 \r\nで終わる行の\rは取り除きます(取り除くのはその 1 つだけです)。MeCab は\nだけを取り除き、\rを未知語として出力しますが、Lindera は Windows の改行の テキストですべての行の末尾に\rのトークンが出ないように、これを取り除きます。
ライブラリと言語バインディングは、もともと入力を trim していないため、空の入力の
N-best の結果を除いて変わりません。変更は CLI にあり、辞書のファイルは
変わりません。v6 の動作に戻す設定はありません。v6 の
オフセットとトークンが必要なら、テキストを lindera tokenize に渡す前に各行の
両端の空白を取り除いてください。
WASM: 辞書の設定方法にかかわらずビルダーの設定が適用される
v6.2.0 までの WASM バインディングの TokenizerBuilder.build() は、辞書の設定方法に
よって、ビルダーの設定の一部を無視していました:
| 辞書の設定方法 | v6.2.0 まで無視されていたもの |
|---|---|
setDictionaryInstance()(loadDictionaryFromBytes() で OPFS から読み込んだ辞書など) | setKeepWhitespace()・appendCharacterFilter()・appendTokenFilter() |
setDictionary()(embedded:// の辞書など) | setUserDictionaryInstance() で設定したユーザー辞書 |
v7.0.0 は、Tokenizer API のページのとおり、どちらの
方法でもすべての設定とユーザー辞書を適用します。たとえばバイト列から読み込んだ
IPADIC で setKeepWhitespace(true) と unicode_normalize(NFKC)・lowercase の
フィルタを設定すると、Lindera 東京 は lindera・空白・東京 になります。
v6 では Lindera と 東京 でした。
辞書インスタンスに対しても、build() は URI で設定した辞書と同じ確認を行うように
なります。空白を保持しない場合、辞書の char.def に SPACE カテゴリが定義されて
いる必要があります。同梱の辞書はすべて定義していますが、定義していない独自の辞書では
setKeepWhitespace(true) が必要になります。
ほかのバインディング・CLI・ライブラリは変わらず、辞書のファイルも変わりません。 v6 の動作に戻す設定はありません。必要なら、v6 が無視していた設定を外してください。
PHP: 省略可能な引数は型違いの値を拒否する
v6.2.0 までの PHP バインディングは、省略可能な引数に渡された型違いの値を null と
して扱い、strict_types の有無にかかわらず、黙って既定値を使っていました。例えば
$tokenizer->tokenizeNbest('東京都', 2, 'yes') は重複を除かない結果を返し、
Lindera\Trainer::train(..., max_iter: $argv[1]) はコマンドラインの指定に
かかわらず既定の 100 回で学習していました。必須の引数は、以前からこのような値を
拒否していました。
v7.0.0 では、省略可能な引数もこのような値を拒否します。対象は、Mode・Penalty・
Metadata・FieldDefinition・Tokenizer のコンストラクタと、
Tokenizer::tokenizeNbest()・Trainer::train()・Trainer::export()・
Dictionary::buildUser() の省略可能な引数です。呼び出しは TypeError
(Invalid value given for argument に続けて引数の名前)を投げます。必須の引数も、
このような値には同じ TypeError を投げるようになりました。v6 は同じメッセージの
Exception を投げていました。
null を渡すか引数を省略すれば、これまでどおり既定値になります。
Trainer::train() の $lambda は、PHP の float の引数と同じく、float に
加えて int も受け付けるようになりました。v6 はここでも int を null として
扱っていました。
各引数には宣言された型の値を渡し、文字列で届く値は (int) $argv[1] のように
キャストしてください(引数の型を参照)。このような値を
catch (Exception $e) で扱っていたコードは、TypeError を捕まえるように
書き換えてください。TypeError は Error の子クラスで、Exception の子クラスでは
ありません。mixed と宣言した引数、ほかのバインディング・CLI・ライブラリは
変わりません。v6 の動作に戻す設定はありません。
バインディング: 範囲外の整数の引数を拒否する
v6.2.0 までの PHP・Node.js・WASM のバインディングは、整数の引数を設定の型に
変換するときに値を折り返していたため、範囲外の値が黙って別の値になっていました。
PHP バインディングは、すべての int を 64 ビットの整数として受け取り、狭い型に
キャストしていました。Node.js と WASM では、JS の数値を 32 ビットの整数に変換する
処理(ECMAScript の ToUint32 か ToInt32)が、バインディングに届く前に値を
折り返していました。WASM の BigInt は 2^64 を法として扱われていました。
| 呼び出し | v6.2.0 | v7.0.0 |
|---|---|---|
PHP new Lindera\Metadata(default_word_cost: 100000) | コスト -31072 | ValueError |
Node.js new Metadata({ defaultLeftContextId: 2 ** 32 + 5 }) | 文脈 ID 5 | code が InvalidArg の Error |
Node.js schema.getFieldName(2 ** 32) | 先頭のフィールド "surface" | null |
WASM new Penalty(-1) | しきい値 4294967295 | 文字列の例外 |
PHP・Node.js・WASM の tokenizeNbest('東京', -1) | 33 個の分割すべて | バインディングのエラー |
v7.0.0 では、このような値を拒否します。PHP バインディングは ValueError を、
Node.js バインディングは code が InvalidArg の Error を、WASM バインディングは
ほかのエラーと同じく文字列を投げます。メッセージは引数の名前と範囲を示します。例えば
default_word_cost must be an integer between -32768 and 32767, got 100000
です。対象の引数は、Metadata の整数の設定(既定の単語コストと文脈 ID)と
Penalty の設定、FieldDefinition・getFieldName・PHP の Token::getDetail の
位置、N-best の件数と Node.js・WASM の N-best のコストのしきい値、学習関数の
反復回数とスレッド数です。検査は、コンストラクタとプロパティの setter(Node.js の
Metadata、WASM の Penalty と FieldDefinition)に適用されます。
これに伴い、v6 が受け付けていた次の値も例外を投げるようになります。
- 負の位置。v6 は
null(PHP・Node.js)かundefined(WASM)を返していました。 末尾より先の位置は、これまでどおりnullかundefinedを返します。 - Node.js と WASM で、
1.5・NaN・Infinityのような整数でない数値。v6 は 切り捨てるか 0 にしていました。 - WASM で、64 ビットの範囲外の BigInt のコストのしきい値と、BigInt でないコストの しきい値。
範囲内の整数を渡してください。Node.js と WASM のバインディングの TypeScript の
型定義は変わりません。Python と Ruby のバインディングは、以前からこれらの値を
OverflowError と RangeError で拒否しており、変わりません。ただし、Python の
プロパティの setter の OverflowError は、注記でプロパティの名前を示すように
なりました('cost' ではなく while processing 'default_word_cost')。v6 の動作に
戻す設定はありません。
バインディング: decompose モードが Penalty を受け取る
v6.2.0 までは、どのバインディングにも Penalty がありましたが、それを受け取る関数も
コンストラクタもありませんでした。set_mode / setMode と Tokenizer の
コンストラクタはモードの文字列だけを受け取り、decompose モードは常に既定の
ペナルティを使っていました。既定でないペナルティを使えたのは、from_file /
fromFile で読み込む設定ファイル経由だけでした。Rust の
TokenizerBuilder::set_segmenter_mode も、設定にモードの名前しか書かなかったため、
Mode::Decompose のペナルティを同じように失っていました。
v7.0.0 では、set_mode / setMode と Tokenizer のコンストラクタが、省略可能な
引数でペナルティを受け取り、set_segmenter_mode はペナルティを保ちます。
| バインディング | ビルダー | Tokenizer のコンストラクタ |
|---|---|---|
| Python | set_mode("decompose", penalty) | Tokenizer(dictionary, "decompose", penalty=penalty) |
| Ruby | set_mode('decompose', penalty: penalty) | Lindera::Tokenizer.new(dictionary, 'decompose', penalty: penalty) |
| PHP | $builder->setMode('decompose', $penalty) | new Lindera\Tokenizer($dictionary, 'decompose', penalty: $penalty) |
| Node.js | builder.setMode("decompose", penalty) | new Tokenizer(dictionary, "decompose", null, null, penalty) |
| WASM | builder.setMode("decompose", penalty) | new Tokenizer(dictionary, "decompose", null, null, penalty) |
引数を省略すると既定のペナルティになるため、既存の呼び出しは変わりません。
"normal" とペナルティを一緒に渡すと、不正なモードと同じくエラーになります。
IPADIC で長さのペナルティを 2 つとも 0 にすると、decompose モードは
関西国際空港限定トートバッグ を 関西 / 国際 / 空港 / 限定 / トートバッグ ではなく
関西国際空港 / 限定 / トートバッグ と分割します。
対応が必要になりうる変更は 3 つです。
- Node.js: TypeScript の型
JsPenaltyはPenaltyに改名し、フィールドは 省略できるようになりました。省略したフィールドは既定値になります。JsPenaltyを 名指しする TypeScript のコードはPenaltyを使ってください。この型に対応する値は パッケージからエクスポートされないため、JavaScript のコードは変わりません。 - Rust:
TokenizerBuilder::set_segmenter_modeに既定でないペナルティのMode::Decomposeを渡すと、そのペナルティが適用され、出力が変わります。CLI の--modeのように"decompose"から作ったモードは既定のペナルティを持つため、 影響はありません。 - バインディングの作者:
CoreTokenizerBuilder::set_modeとCoreTokenizer::from_segmenter_with_space_penaltyは、2 番目の引数で ペナルティを受け取ります。既定のままにするにはNoneを渡してください。
lindera train は MeCab の学習器と同じくラティスを作る
v6.2.0 までの lindera train は、MeCab の学習器とは異なる学習用ラティスを
作っていました。
- 種辞書がラティスに入っていなかったため、正解のトークンは未知語とだけ競合し、 ほかの種辞書の語とは競合しませんでした。
- 未知語候補は
char.defを無視していました。固定の分類(ASCII の数字、ASCII の 英字、漢字、カタカナ、ひらがな、その他)が先頭の文字の種類を決め、候補はすべて 1〜3 文字で、種類の番号をunk.defのエントリとして使っていました。そのため、 種類ごとに 1 エントリをDEFAULT・HIRAGANA・KATAKANA・KANJI・ALPHA・NUMERICの順に並べたunk.defでしか正しく動きませんでした。 char.defのカテゴリごとに、そのカテゴリの最後のunk.defの行の素性文字列を 持つラベルを 1 つ作り、lindera exportもカテゴリごとにunk.defを 1 行書き 出していました。mecab-ipadic のunk.defは 11 カテゴリに 40 行ありますが、 これが 11 行になり、ALPHA・HIRAGANA・KATAKANAの未知語はすべて、これらの カテゴリの最後の行の素性である感動詞になっていました。- 種辞書の語でない正解トークンには、IPADIC の素性(
名詞,一般など)向けに書かれた 判定で未知語のラベルを付けるか、素性のないラベルを付けていました。フィールドが 5 つ未満のunk.defの行は、何も知らせずに読み飛ばしていました。 - 正則化で一部の文脈 ID のバイグラムの重みが残らなかった場合、
lindera exportは エントリの ID より小さいmatrix.def(重みが 1 つも残らなければ0 0)を書き出し、 ビルドした辞書は解析時にパニックしていました。metadata.jsonのmodel_info.connection_matrix_sizeは、大きい方の ID の正方形でした。 lindera exportは、学習した重みがない連接(バイグラム素性が学習中に隣り合わな かったか、正則化で重みが 0 になったもの)に、matrix.defで32767(i16::MAX)を書き出していました。学習ではそうした連接のコストは 0 で、MeCab のmecab-dict-genも 0 を書き出すため、ビルドした辞書は、モデルが良くも悪くもない と学習した遷移を避けていました。mecab-ipadic のファイルと既定の L1 正則化で 5 分割の交差検証をすると、0 を書き出すことで境界 F1 がすべての分割で上がり、 0.9552 から 0.9572 になりました (#1168)。metadata.jsonのmodel_info.training_iterationsは、実際に回った反復回数では なく、--max-iterationsの値でした。- Python・Node.js・Ruby・PHP バインディングのエクスポート関数は、
lex.csv、matrix.def、unk.def、metadata.jsonと、カテゴリDEFAULT・HIRAGANA・KATAKANA・KANJI・ALPHA・NUMERICを持つ固定のchar.defを書き出して いました。学習に使ったchar.defではなく、feature.def・rewrite.def・left-id.def・right-id.defも書き出していませんでした。 - 種辞書を 1 行ずつ
,で分割して読み、#で始まる行をコメントとして読み飛ばして いました。mecab-ko-dic の引用符付きのカンマの表層形","は表層形"になり、lindera exportがそれを引用符なしで書き出したため、lindera buildはlex.csvの残りを 1 つのフィールドとして読み、その後ろの行をすべて何も知らせずに 捨てていました。UniDic・SudachiDict・IPADIC NEologd の#の行は学習されません でした。 - 書き換え規則・素性テンプレート・未知語の照合・ユーザー辞書の語の文脈 ID では、
素性を
,で分割していました。,を含む引用付きの列は複数の列になり、後ろの列が ずれていました。英語の定義を引用符で囲み、その後ろに 4 つの列を持つ mecab-jieba の学習用の種辞書では、19,955 行で%F[6]〜%F[9]が定義の断片になっていました。 ?のない%F[n]・%L[n]・%R[n]が素性の最後の列より後ろを読むと、そこを*として読んだため、テンプレートはU09:*のような素性を作り、列の少ない素性 がすべてそれを共有していました。MeCab の学習器はそのテンプレートを飛ばします。 mecab-jieba のunk.defの素性は 5 列で、そのfeature.defは%F[6]〜%F[9]を読むため、11 個の未知語のラベルは、どの種辞書の行も持たない 4 つの 素性U06:*〜U09:*を持っていました。Model::write_dictionaryは、Model::read_user_lexiconが読むユーザー辞書の うち文脈 ID とコストが0,0,0のエントリに、別のユーザー辞書のエントリの文脈 ID を付けていました。素性がそのエントリに最も近い種辞書の行の添字にあるエントリか、 それがなければ最初のエントリです。どの種辞書の行とも共通の列を持たない素性の エントリには、先頭の文字のカテゴリのchar.defでの添字を両方の ID として 付けていました。これは文脈 ID ではありません。Model::write_dictionaryは、そうした0,0,0のエントリすべてに、素性に よらず同じコストtocost(0.8, cost_factor)を付けていました。Model::read_user_lexiconは、空の文脈 ID やコストと、数でない値を0と して読み、負の文脈 ID を 16 ビットに折り返して読んでいました(-1は65535)。パラメータを埋めるのは 3 つとも0のときだけでした。そのため、 コストが空で文脈 ID を与えたエントリはコスト0に、文脈 ID が空でコストを 与えたエントリは BOS/EOS 用の ID0,0になっていました。
v7.0.0 は MeCab の学習器と同じく学習します (学習のしくみを参照)。
- ラティスには、各位置から始まる種辞書の語と、ランタイムがその位置に同じコードで
作る未知語が入ります。未知語は、文字の既定カテゴリのものを、その
INVOKE・GROUP・LENGTHの設定で、カテゴリのunk.defのエントリそれぞれについて 作ります。 unk.defの各行はそれぞれ 1 つのエントリで 1 つの CRF ラベルになり、その%tはカテゴリの ID です。lindera exportはすべての行をファイル順に書き出します。 コメント行と空行は読み飛ばし、フィールドが 5 つ未満の行はエラーになります。- 正解のトークンには、範囲と素性文字列全体が同じ最初の種辞書の行のラベルを
付けます。なければ、範囲が同じ未知語候補の
unk.defエントリのうち、*以外の 素性の列がすべて正解トークンの列と一致する最初のエントリのラベルを付けます。 それもなければ、MeCab の学習器が仮想ノードを追加するのと同じく、学習はされる もののエクスポートされない仮想ラベルを付けます。lindera trainはこれらの数をLattice statistics:の行に出力します。 matrix.defはすべての文脈 ID と BOS/EOS 用の 0 を覆い、model_info.connection_matrix_sizeはその大きさを行数 x 列数で示します。- 学習した重みがない連接の
matrix.defのコストは、学習中や MeCab と同じく 0 です。 model_info.training_iterationsは実際に回った L-BFGS の反復回数です。--max-iterationsで学習が止まったときはその値、それより前に収束したときは それより少ない値になります。- Python・Node.js・Ruby・PHP バインディングのエクスポート関数は、
lindera exportと同じコードで同じファイルを書き出します。char.def・feature.def・rewrite.defは学習に使ったもので、left-id.defとright-id.defも書き出し ます。 model.datの形式バージョンは 2.0.0 です。lindera exportとバインディングの エクスポート関数は、v6.2.0 以前に書いたものを含め、ほかのバージョンのmodel.datを「re-runlindera trainto regenerate it」で終わるエラーで拒否 します。- 種辞書は、
lindera buildがlex.csvを読むのと同じく CSV として読みます (種辞書を参照)。 フィールドは引用符で囲め、#で始まる行も 1 つの行として読み、素性の列は書かれた まま保ちます。複数の行にまたがる引用付きのフィールドはエラーになります。lindera exportは、,・"・改行を含む表層形を引用符で囲みます。Model::read_user_lexiconが読むユーザー辞書も同じく読みます。 - 素性は MeCab の学習器と同じく列に分けます
(種辞書を参照)。
引用付きの列は 1 つの列で、列の前の空白とタブは読み飛ばし、閉じる引用符から
次の
,までのテキストは捨てます。書き換え規則は、書き換えた列を,でつなぎ、 MeCab と同じく,か"を含む列を引用符で囲みます。 - 素性の最後の列より後ろを読むテンプレートは、
?の有無にかかわらず、MeCab の 学習器と同じく何も作りません (素性テンプレートを参照)。 mecab-ipadic・mecab-ipadic-neologd・unidic-mecab・mecab-ko-dic・mecab-jieba・resources/training/の学習用のファイルのうち、テンプレートが最後の列より後ろを 読む素性があるのは mecab-jieba のunk.defの行だけで、変わるのは mecab-jieba のファイルから学習したモデルだけです。 Model::write_dictionaryは、mecab-dict-indexと同じく、ユーザー辞書の エントリのパラメータを別々に埋めます。空のコストは計算し、文脈 ID のどちらかが 空か負の数なら、両方を推定します。0,0,0はこれまでどおり 3 つとも埋めます。 空でも範囲内の数でもない文脈 ID やコストは、そのエントリを示すエラーになります。- 計算するコストは、MeCab の
calcCostと同じく、エントリの unigram 素性の 学習済みの重みの和を、lex.csvのコストと同じく変換したものです。そのため、 素性と先頭の文字のカテゴリが種辞書のある行と同じエントリは、その行のコストを 受け取ります。 - 推定する文脈 ID は、素性がそのエントリに最も近い種辞書の行に
lex.csvが付ける 文脈 ID です。どの種辞書の行とも共通の列を持たない素性のエントリは、mecab-dict-indexが文脈 ID のない素性のユーザー辞書のエントリで失敗するのと 同じくエラーになり、何も書き出しません。
すべてのラティスに種辞書の語が入るため、学習には以前より時間がかかります。学習は
MeCab の学習器より遅く、収束する前に --max-iterations で
止まることがあります(#1167)。
resources/training/ のファイルは、char.def に SPACE カテゴリを、unk.def
に SPACE の行を持つようになりました。そのため、これらから学習した辞書は、v6 では
必要だった --keep-whitespace なしで解析できます
(#1166)。
対処方法: 7.0.0 の lindera train かバインディングの学習関数ですべてのモデルを
学習し直し、エクスポートと辞書のビルドもやり直してください。unk.def のすべての
行に 5 つ以上のフィールドを書いてください。mecab-ipadic のようにカテゴリごとに
複数のエントリを持つ unk.def も、そのまま学習・エクスポートできるように
なりました。種辞書からコメント行を消してください。# で始まる行は 1 つの行として
読むようになりました。どの種辞書の行とも共通の列を持たない素性のユーザー辞書の
エントリには、文脈 ID を書いてください。ユーザー辞書のエントリの文脈 ID とコストは、
それぞれ数で書くか、埋めさせるなら空にしてください。v6 の動作に戻す設定は
ありません。
lindera build は接続コスト行列の範囲外の文脈 ID を拒否する
v6.2.0 までの lindera build は、文脈 ID が matrix.def の範囲外にある lex.csv
の行や unk.def の行を受け付け、ビルドした辞書は、解析中にそうした語を引いた
ときにパニックしていました。ID が軸の範囲を超える matrix.def の行も受け付け、
そのコストは別のセルに入っていました。v7.0.0 は MeCab の mecab-dict-index と
同じく、これらをすべて拒否します。lex.csv の各行と unk.def の各行で、右文脈
ID は matrix.def のヘッダの 1 つ目の値より小さく、左文脈 ID は 2 つ目の値より
小さくなければなりません。エラーはファイル・行・ID・行列の大きさを示します。
Error: LinderaError { kind: Content, source: ./dict-source/lex.csv: line 2: right context id 2 is out of range for the connection matrix (matrix.def size 2 2; right context ids must be below 2) }
- 辞書の
metadata.jsonでskip_invalid_cost_or_idを設定している場合は、 そうしたlex.csvの行を取り除き、警告を出します。CLI はこの警告を標準エラー 出力に書きます(CLI はライブラリの警告を標準エラー出力に書くを参照)。unk.defの行は常に確認します。 - コメント行や空行など、フィールドが 4 つ未満の
unk.defの行は、パニックでは なくエラーになります。lindera_dictionaryのDictionaryBuilder::build_unknown_dictionaryとparse_unkでも同じです。 エラーは行を示します。これまで行を示さなかった、文脈 ID やコストが数値でない 行のエラーも、行を示すようになります。 - ユーザー辞書は、システム辞書と組み合わせるときに確認します。
Segmenter::try_new、設定からの構築、lindera tokenize --user-dict、 各言語バインディングのTokenizerのコンストラクタとビルダーは、行列の範囲外に ある最初の見出し語を示すエラーを返します (#1171。 文脈 ID を参照)。Segmenter::newはこれまでどおり確認しません。新しい--system-dictオプションでシステム辞書を指定すると、lindera build --userの時点で各行を 確認し、行列の範囲外にある最初の行を CSV の行番号と表層形で示します (Rust API ではDictionaryBuilder::with_system_matrix。 #1182)。設定からの構築、lindera tokenize --user-dict、バインディングのTokenizerBuilderがパスで 読み込む.csvのユーザー辞書と、Rust API に新しく加わったload_user_dictionary_forで読み込むユーザー辞書も、システム辞書に合わせて ビルドするときに同じように確認し、CSV の行番号と表層形を示します (#1187)。各言語バインディングにも、 読み込み済みのシステム辞書に合わせてユーザー辞書を読み込む関数が加わりました。 Python・Ruby のload_user_dictionary_for、Node.js のloadUserDictionaryFor、 PHP のLindera\Dictionary::loadUserFor、WebAssembly のloadUserDictionaryFromBytesForです (#1193)。メタデータを受け取る これまでの関数は変わりません。これらのバインディングのユーザー辞書のビルド関数にも、 読み込み済みのシステム辞書と照らして CSV の各行を確認しながらビルドするものが 加わりました。Python・Ruby のbuild_user_dictionary_for、Node.js のbuildUserDictionaryFor、PHP のLindera\Dictionary::buildUserForです (#1188)。メタデータを受け取る これまでのビルド関数も変わりません。そのoutput_dirは、これまでどおり ディレクトリではなく、書き出すファイルのパスです。 - ユーザー辞書の行のコストや文脈 ID が数値でないときのエラーは、
lindera build --userでも.csvのユーザー辞書の読み込みでも、その行の CSV の行番号を示します(at line 1)。これまでは、表層形の順に並べた行を 数えていました(1 行目にあり、表層形の順で 2 番目になる行はat row 2)。
mecab-dict-index は同じ範囲を確認するため、それでビルドできる辞書は確認を
通ります。v6.2.0 以前の lindera export で書き出した辞書は、matrix.def が
文脈 ID より小さいことがあるため、失敗する場合があります。7.0.0 で学習と
エクスポートをやり直してください(前の節を参照)。それ以外の辞書が失敗した場合は、
lex.csv や unk.def の ID か、matrix.def の大きさを直してください。
lindera build と lindera train は char.def と unk.def を照らし合わせる
v6.2.0 までの lindera build と lindera train は、MeCab の mecab-dict-index が
拒否する char.def と unk.def を受け入れていました
(#1195)。
- どの行も定義していないカテゴリを指す
char.defの範囲行。ビルドした辞書は、 そのカテゴリの文字でパニックしました。学習も、コーパスにその文字があると パニックしました。 - カテゴリの定義より前にある範囲行や、2 回定義したカテゴリ。一部のカテゴリが
別のカテゴリの
INVOKE・GROUP・LENGTHの設定を使い、未知語が変わりました。 unk.defの行がないchar.defのカテゴリ。そのカテゴリの文字のうち、 どの単語も覆わない文字には未知語ができないため、解析はエラーを出さずに その手前で出力を打ち切り、学習用ラティスにはその文字を通る経路が ありませんでした。char.defが定義していないカテゴリを指すunk.defの行。その行は使われず、lindera exportはそれをそのまま書き出していました。
v7.0.0 は、mecab-dict-index と同じく、これらをすべて拒否します。エラーは
ファイルとカテゴリを示し、unk.def の行がないカテゴリ以外では行も示します。
たとえば、どの行も定義していないカテゴリを指す 0x3005 KANJI_ITERATION が
char.def の 8 行目にある場合は、次のようになります:
Error: LinderaError { kind: Content, source: ./dict-source/char.def: line 8: category KANJI_ITERATION is not defined; a range line can only name a category defined on an earlier line }
lindera train とバインディングの学習関数も、学習の前に入力を同じように
確認します。unk.def の行番号は、コメント行と空行も数えたファイル上の番号です。
同梱の辞書と、mecab-dict-index でビルドできる辞書は、すべて確認を通ります。
それ以外の辞書が失敗した場合は、各カテゴリを、それを指す範囲行より前で 1 回だけ
定義し、すべてのカテゴリに unk.def の行を 1 行以上書き、char.def が定義して
いないカテゴリの unk.def の行を削除してください。
lindera build は matrix.def にないセルのコストを 0 にする
v6.2.0 までの lindera build は、matrix.def が列挙していないセルのコストを
32767(i16::MAX)にしていたため、その接続は非常に高いコストになっていました。
MeCab の mecab-dict-index はそうしたセルのコストを 0 にし、v7.0.0 も同じく
0 にします(#1211。
matrix.def にないセルを参照)。
同梱の辞書の matrix.def はすべてのセルを列挙しており、lindera export が
書き出す matrix.def も同じなので、どちらも変わりません。セルを省いた
matrix.def からビルドした辞書は、そうしたセルを通る接続のコストが 0 になるため、
分割が変わることがあります。接続を高いコストのままにするには、そのセルを
コスト付きで列挙してください。
CLI はライブラリの警告を標準エラー出力に書く
v6.2.0 までの lindera CLI はロガーを組み込んでいなかったため、Lindera の
ライブラリが log クレートで出す警告は捨てられていました
(#1107)。v7.0.0 は、既定で
警告とエラーを標準エラー出力に書きます。標準出力は変わりません。たとえば:
lindera tokenizeは、文の区切り文字のないテキストが 32 KiB を超えて文を 区切ったときに警告します。lindera buildは、skip_invalid_cost_or_idによってlex.csvの行を 取り除いたときと、辞書のmetadata.jsonがconnection_id_mappingを設定して いて(ko-dic・UniDic)、--context-id-freqを指定していないときに警告します。 ビルドの例と同じく、辞書クレートのcontext_id_freq.txtを渡してください。
[WARN lindera_segmenter::segmenter] no sentence delimiter (\n, \t, 。, 、) found within 32768 bytes from offset 0; forcing a sentence boundary to bound lattice size (see https://github.com/lindera/lindera/issues/871)
環境変数 RUST_LOG で、出すメッセージを選べます。RUST_LOG=error は警告を
消し、RUST_LOG=debug はデバッグ用のメッセージも出します
(警告とログメッセージを参照)。
lindera build と lindera train は、左文脈 ID と右文脈 ID が異なる unk.def
の行を警告しなくなりました。mecab-dict-index はこうした行を受け入れ、
mecab-ko-dic の unk.def はすべての行がこうした行です。ロガーを組み込んだ Rust
のプログラムにも、この警告は出なくなります。
lindera_dictionary の Rust API 変更
以下は、ラティスのバックトレースを直接呼び出すコード、Metadata::new で辞書の
メタデータを作成するコード、辞書の文字カテゴリを読むコード、UnknownDictionary
で未知語を生成するコード、mecab-dict-index が拒否する char.def と unk.def
から辞書をビルドするコード(前の節を参照)
にのみ影響します。対象は
lindera_dictionary::viterbi::Lattice と lindera_dictionary::nbest::NBestGenerator
(lindera::dictionary::viterbi::…・lindera::dictionary::Lattice・
lindera::dictionary::nbest::… からも参照できます)、および
lindera_dictionary::dictionary::metadata::Metadata(lindera::dictionary::Metadata
からも参照できます)、lindera_dictionary::dictionary::character_definition::CharacterDefinition、
lindera_dictionary::dictionary::unknown_dictionary(lindera::dictionary::core::unknown_dictionary
からも参照できます)、lindera_dictionary::builder(lindera::dictionary::builder
からも参照できます)です。Segmenter・Tokenizer・SegmentWorker・
AnalysisWorker の API と CLI は影響を受けません。言語バインディングで変わるのは
Metadata クラスだけです(設定 normalize_details の削除
を参照)。
| v6 | v7 |
|---|---|
Lattice::tokens_offset() -> Vec<(usize, WordId)> | Lattice::tokens_offset() -> Vec<TokenOffset> |
Lattice::tokens_offset_into(&mut Vec<(usize, WordId)>) | Lattice::tokens_offset_into(&mut Vec<TokenOffset>) -> Option<usize> |
Lattice::nbest_tokens_offset(n, unique, cost_threshold) -> Vec<(Vec<(usize, WordId)>, i64)> | Lattice::nbest_tokens_offset(n, unique, cost_threshold) -> Vec<NBestPath> |
NBestGenerator::next() -> Option<(Vec<(usize, WordId)>, i64)> | NBestGenerator::next() -> Option<NBestPath> |
| — | viterbi に新しい型エイリアス TokenOffset = (usize, usize, WordId) と NBestPath = (Vec<TokenOffset>, i64) を追加 |
| — | viterbi に BosContext・LatticeOptions::bos・LatticeExit・Lattice::exits_into・Lattice::exit_tokens_offset_into を追加 |
| — | nbest に NBestGenerator::from_exit・NBestGenerator::next_with_bos を追加 |
| — | nbest に UniqueNBestGenerator を追加 |
Metadata::new(name, encoding, simple_word_cost, default_left_context_id, default_right_context_id, default_field_value, flexible_csv, skip_invalid_cost_or_id, normalize_details, schema, userdic_schema) | Metadata::new(name, encoding, simple_word_cost, default_left_context_id, default_right_context_id, default_field_value, flexible_csv, skip_invalid_cost_or_id, schema, userdic_schema) |
Metadata::normalize_details | 削除 |
PrefixDictionaryBuilderOptions::normalize_details(value) | 削除 |
| — | 公開フィールド Metadata::skip_whitespace: Option<bool> を追加(lindera_binding::CoreMetadata::skip_whitespace も) |
CharacterDefinition::lookup_categories(c) は c を覆うすべての行のカテゴリを返す | c を覆う最後の行のカテゴリを、既定カテゴリを先頭に、残りをカテゴリ ID 順に返す |
DICTIONARY_FORMAT_VERSION は 2 | 3 |
UnknownDictionary::gen_unk_words(sentence, start_pos, has_matched, max_grouping_len, callback) | 削除 |
UnknownDictionary::compatible_unk_index(sentence, start, end, feature) -> Option<WordIdx> | 削除 |
unknown_dictionary::UnkWord と unknown_dictionary::WordIdx | 削除 |
| — | viterbi に Lattice::unknown_word_candidates と UnknownWordCandidate を追加 |
CharacterDefinitionBuilder::build_from_str と build は、それより前の行で定義していないカテゴリを指す範囲行と、2 回定義したカテゴリを受け入れる | 行とカテゴリを示すエラーを返す |
DictionaryBuilder::build_dictionary と build_unknown_dictionary は、char.def が定義していないカテゴリを指す unk.def の行と、unk.def の行がない char.def のカテゴリを受け入れる | ファイルとカテゴリを示すエラーを返す |
| — | unknown_dictionary に check_unknown_word_categories を追加。ビルダーと学習器が実行する、char.def のカテゴリと unk.def の照合 |
各トークンは (start, word_id) ではなく (start, end, word_id) になりました。
オフセットは文内のバイト位置です。空白を読み飛ばすとき(v7 で追加された
LatticeOptions::skip_whitespace)は、トークンの終わりが次のトークンの開始位置と
一致するとは限りません。間に読み飛ばした空白があると、そのうちどこまでが語に
属するかはラティスにしかわからないためです。返される終了位置は、読み飛ばした空白を含まず、
語自身の末尾の空白を含みます(空白を含む見出し語を保持する
を参照)。set_text や set_text_nbest のように空白をラティスに残す場合、終了位置は
これまでどおり次のトークンの開始位置(最後のトークンでは文の終わり)です。unique を
指定した nbest_tokens_offset は、パスの開始位置ではなく (start, end) の組を
比較するようになりました。
#![allow(unused)] fn main() { use lindera::dictionary::viterbi::TokenOffset; // v6: トークンは次のトークンの開始位置で終わる let offsets = lattice.tokens_offset(); for (i, &(start, word_id)) in offsets.iter().enumerate() { let end = offsets.get(i + 1).map_or(sentence.len(), |&(next, _)| next); handle(&sentence[start..end], word_id); } // v7: 終了位置が返される let offsets: Vec<TokenOffset> = lattice.tokens_offset(); for &(start, end, word_id) in &offsets { handle(&sentence[start..end], word_id); } }
nbest_tokens_offset と NBestGenerator::next が返すパスも、同じ 3 つ組を持ちます。
tokens_offset_into は、最良パスの BOS の添字も返すようになりました。パスが始まる
BOS の辺の、LatticeOptions::bos での添字です。デフォルトの単一の BOS では
Some(0)、ラティスに完全なパスがないときは None です。文として呼び出している
コードはそのままコンパイルできます。for_each に渡すクロージャの本体のように () を
期待される場所で使っている場合は、; を付けてください。
新しい項目は、セグメンターが 、・。 をまたいで文脈を引き継ぐために使うものです
(、・。 をまたいで文脈を引き継ぐを参照)。
既存のコードには影響しません。
BosContextとLatticeOptions::bos: 文を複数の BOS の辺から始められます。 各辺は右文脈 ID とコストを持ちます。デフォルトの空のスライスは、v6 と同じ単一の BOS です。LatticeExit・Lattice::exits_into・Lattice::exit_tokens_offset_into: 文の最後の 語の右文脈 ID ごとに、その語までの最良のパス(EOS への連接を含まない)と、その トークンを返します。NBestGenerator::from_exitとNBestGenerator::next_with_bos: ある出口の右文脈 ID で終わるパスを、EOS なしでコストの昇順に返し、各パスとともにそのパスが始まる BOS の 添字を返します。nextは、next_with_bosのパスを BOS の添字なしで返します。
UniqueNBestGenerator は NBestGenerator と同じメソッド(new・from_exit・
next・next_with_bos)を持ち、単語境界と BOS の辺の組ごとに最もコストの低いパスを
コストの昇順に返します。unique を指定した nbest_tokens_offset とセグメンターは
これを使います。
nbest_tokens_offset はデフォルトの単一の BOS を前提にしています。BOS が複数ある
場合は、パスがどの BOS から始まるかがわからないため、NBestGenerator::next_with_bos
を使ってください。
Metadata::new から 9 番目の引数 normalize_details がなくなったので、呼び出し
から削除してください。ビルダーがすべての見出し語を書かれたとおりに保持するように
なったため(ダッシュとチルダを辞書に書かれたとおりに保持する
を参照)、Metadata からはこのフィールドが、PrefixDictionaryBuilderOptions
(lindera_dictionary::builder::prefix_dictionary)からはこのメソッドがなくなり
ました。Metadata は、このキーを含む metadata.json も読み込み、キーを無視します。
バインディングの作者向けの lindera_binding::CoreMetadata からも同じくフィールドが
なくなり、CoreMetadata::new の引数は 11 個から 10 個になります。
Metadata には、空白の読み飛ばしの辞書のデフォルトを表す公開フィールド
skip_whitespace: Option<bool> が加わりました(空白をラティス上で読み飛ばす
を参照)。lindera_binding::CoreMetadata も同じです。どちらの構造体も
#[non_exhaustive] ではないため、すべてのフィールドを .. なしで並べた構造体
リテラルはコンパイルできなくなります。skip_whitespace: None を加えるか、
リテラルの最後を ..Default::default() にしてください。Metadata::new と
CoreMetadata::new はこれを None(読み飛ばす)にします。
CharacterDefinition::lookup_categories のシグネチャは変わりません。先頭のカテゴリ
が未知語を作るカテゴリです(MeCab と同じく、未知語は既定カテゴリから作るを参照)。文字があるカテゴリを持つかどうかだけを
調べるコードには影響しません。ビルダーが書き出し、読み込み時に受け付ける
DICTIONARY_FORMAT_VERSION は 3 になりました(辞書ディレクトリの再ビルドが必要を参照)。
UnknownDictionary::gen_unk_words と UnknownDictionary::compatible_unk_index は、
これらが使う UnkWord 型・WordIdx 型とともに削除しました。呼び出していたのは
lindera train だけで、char.def を無視していました
(lindera train は MeCab の学習器と同じくラティスを作るを参照)。
ランタイムが作る未知語を列挙するには、
Lattice::unknown_word_candidates(dict, char_definitions, unknown_dictionary, text, max_grouping_len, out)
を呼び出します。text の各開始位置の各候補について、文字単位の位置と unk.def の
エントリ ID を持つ UnknownWordCandidate { start_char, end_char, word_id } を
out に入れます。候補は set_text_with_options がデフォルトの設定と指定した
max_grouping_len で作るものと同じで、どのパスも届かない位置の候補も含みます。
解析の処理は変わりません。
lindera_trainer の Rust API 変更
以下は、lindera-trainer を直接、または lindera::dictionary::trainer(train
feature)経由で使う Rust コードにのみ影響します
(lindera train は MeCab の学習器と同じくラティスを作るを参照)。
| v6 | v7 |
|---|---|
SerializableModel::unk_category_names: Vec<String> と SerializableModel::unk_categories: BTreeMap<String, String> | SerializableModel::unk_entries: Vec<(String, String)>。ファイル順の unk.def の (カテゴリ, 素性文字列) の組 |
ModelMetadata::version は "1.0.0" で、Model::read_model はこれを確認しない | "2.0.0"。Model::read_model はほかのバージョンのモデルを拒否する |
| — | model に MODEL_FORMAT_VERSION を追加 |
| — | lindera export のファイルをディレクトリに書き出す SerializableModel::export を追加 |
TrainerConfig::dict() は空の辞書とプレースホルダーの未知語辞書を持つ | 種辞書(system_lexicon())と unk.def のエントリ(unk_handler())を持つ |
TrainerConfig::from_readers はフィールドが 5 つ未満の unk.def の行を読み飛ばす | エラーを返す |
TrainerConfig::from_readers は mecab-dict-index が拒否する char.def と unk.def を受け入れる | ファイルとカテゴリ、行があればその行を示すエラーを返す(lindera build と lindera train は char.def と unk.def を照らし合わせるを参照) |
Trainer::max_grouping_len(len) は未知語候補の長さを制限し、候補は 3 文字を超えない | ランタイムと同じ意味(先頭の 1 文字を除いた文字数)で未知語のグルーピングに上限を設ける。デフォルトの None は無制限 |
Model::num_labels() と ModelMetadata::label_count は char.def のカテゴリごとに 1 つの未知語ラベルを数える | unk.def のエントリごとに 1 つを数える。仮想ラベルは数えない |
Model::write_left_id_def / write_right_id_def はバイグラム素性の文字列の ID を書き出す。lex.csv や matrix.def の文脈 ID とは別の ID 空間 | 文脈 ID を書き出す。Model::write_model が書き出すモデルについて SerializableModel::write_left_id_def / write_right_id_def が書き出すものと同じ |
ModelMetadata::iterations(と model_info.training_iterations)は max_iter の設定値 | 実際に回った L-BFGS の反復回数 |
Model::write_connection_costs と SerializableModel::write_connection_costs は、学習した重みがない連接に i16::MAX を書き出す | 0 を書き出す。学習中や MeCab でのその連接のコスト |
モデルは MODEL_FORMAT_VERSION を metadata.version として書き出され、
Model::read_model は rkyv 形式でもレガシーの JSON 形式でもこれを確認するため、
v6.2.0 以前に書かれたモデルは学習し直す必要があります。unk_category_names や
unk_categories を読むコードは、代わりに unk_entries を読んでください。
unk.def の 1 行につき 1 組で、カテゴリごとに複数ある場合も含み、エクスポート
される unk.def と同じ順序です。Trainer::max_grouping_len を設定している場合は、
辞書を使うときの上限(Segmenter の max_grouping_len、CLI の
--max-grouping-len)を渡してください。
対応が不要なケース
- 言語バインディングと CLI のユーザー: Python・Node.js・Ruby・PHP・WASM の
各パッケージと
lindera-cliのパッケージ名は変わらず、API もMetadataの設定normalize_detailsと、PHP の省略可能な引数の型違いの値と、PHP・Node.js・WASM の 範囲外の整数の引数と、Node.js の TypeScript の型JsPenalty(Penaltyに改名)を 除いて変わりません (設定normalize_detailsの削除、 PHP: 省略可能な引数は型違いの値を拒否する、 バインディング: 範囲外の整数の引数を拒否する、 バインディング: decompose モードがPenaltyを受け取るを参照)。 今回の構成変更は Rust クレート内部のものです。v6.2.0 以前にビルドまたは ダウンロードした辞書ディレクトリは、再ビルドか再ダウンロードが必要です (辞書ディレクトリの再ビルドが必要を参照)。埋め込み辞書は対応不要です。 v6.2.0 以前に学習したモデルファイルは学習し直す必要があります (lindera trainは MeCab の学習器と同じくラティスを作るを参照)。出力の変化は、この 2 フィールドを 名前で読む場合にのみ影響する IPADIC の活用フィールドの修正、U+3000 を含む テキスト(IPADIC・IPADIC-NEologd・UniDic)や空白を含むテキスト(SudachiDict)に のみ影響する空白の見出し語、空白を含むテキストの分割、Ð・々・〇を含む テキストにのみ影響するchar.defの修正、行の中に、や。を含むテキストに のみ影響する、・。をまたぐ文脈の引き継ぎ、複数の文を含む入力や空の入力の N-best にのみ影響する N-best の修正、通常モードでのカタカナ・記号・英字の連続に 影響する、どの位置からも始まる未知語、ko-dic の漢数字に続く漢字と、ko-dic・ CC-CEDICT・Jieba の〇に影響する、既定カテゴリから作る未知語、IPADIC・IPADIC-NEologd で―・—・~・〜を含むテキストにのみ影響する、ダッシュとチルダの綴りの 保持、読みや原形などのトークンの詳細情報や、コストが同じ N-best の結果の 順を読む場合にのみ影響する、同点の見出し語の選び方、わずかな行の分割を 変える、コストが同じ分割の選び方、CLI だけの変化として、行頭・行末に 空白がある行と、空行や空白だけの行の wakati・N-best の出力にのみ影響する、入力行の 両端の空白の扱いとトークンのない行の出力、標準エラー出力を読むスクリプトにのみ 影響する、標準エラー出力の警告、そして WASM バインディングだけの変化 として、v6 が辞書の設定方法によって無視していたビルダーの設定です。 lindera = "6"のまま使い続けるプロジェクト:linderaとlindera-analysisの 6.x は crates.io に残り、組み合わせて動作し続けます。メジャーバージョンを 上げるまで何も変わりません。- セグメンター API だけを使うユーザー:
Segmenter・load_dictionary・Modeなど v6 のlindera::…アイテムを使うコードは、lindera = "7"でそのまま コンパイルできます(lindera::dictionary::Latticeのバックトレースを呼び出す 場合と、lindera::dictionary::Metadata::newでメタデータを作成する場合を除く。lindera_dictionaryの Rust API 変更 を参照)。パスから読み込む辞書ディレクトリは再ビルドが必要です。ただしデフォルトビルドではlindera-analysisとその依存 (kanaria・regex・serde_yaml_ng・unicode-blocks・unicode-normalization・ unicode-segmentation)もコンパイルされるため、v6 の依存ツリーを維持したい 場合はdefault-features = false, features = ["mmap"]を指定してください。
アップグレードチェックリスト
Rust クレートのユーザー:
linderaを"7"に上げ、同じコミットでlindera-analysisも"7"に上げるか、 取り除いてlindera::analysis::…から import する。- 任意で
useパスのlindera_analysis::をlindera::analysis::に置き換える (どちらも同じ型を指す)。 default-features = falseを指定している場合は、必要に応じてfeaturesに"analysis"(Tokenizerとフィルタ)と"mmap"(メモリマップ読み込み)を 追加する。features = ["lindera-<辞書>"]をfeatures = ["embed-<辞書>"]に置き換える。- 任意で
lindera-dictionaryへの直接依存を外し、lindera::dictionary::core・::builder・::viterbiなどの再エクスポートに切り替える。 Lattice::tokens_offset・tokens_offset_into・nbest_tokens_offset・NBestGenerator::nextを呼び出している場合は、(start, end, word_id)に分解し、 次のトークンの開始位置の代わりに返された終了位置を使う。Lattice::tokens_offset_intoの戻り値()をクロージャの本体などで値として 使っている場合は、;を付ける。最良パスの BOS の添字を返すようになったため。Metadata::newを呼び出している場合は、9 番目の引数normalize_detailsを 削除し、Metadata::normalize_detailsフィールドとPrefixDictionaryBuilderOptions::normalize_detailsを使わないようにする。- すべてのフィールドを並べた構造体リテラルで
Metadataを作っている場合は、skip_whitespace: Noneを加えるか、最後を..Default::default()にする。 CharacterDefinition::lookup_categoriesを読んでいる場合は、既定カテゴリが先頭で、 残りがカテゴリ ID 順になることを前提にする。UnknownDictionary::gen_unk_wordsやUnknownDictionary::compatible_unk_indexを 呼び出しているか、UnkWordやWordIdxを使っている場合は、代わりにLattice::unknown_word_candidatesで未知語を列挙する。SerializableModel::unk_category_namesやunk_categoriesを読んでいる場合は 代わりにunk_entriesを読み、Trainer::max_grouping_lenを設定している場合は 辞書を使うときの上限を渡す。TokenizerBuilder::set_segmenter_modeに既定でないペナルティのMode::Decomposeを渡している場合は、そのペナルティが適用されることを前提にする。
バインディングの作者:
lindera-binding-coreの代わりにlindera-bindingに依存し、lindera_binding_core::をlindera_binding::に置き換える。CoreMetadata::newから引数normalize_detailsを削除し(引数は 10 個になる)、CoreMetadata::normalize_detailsフィールドを使わないようにする。- すべてのフィールドを並べた構造体リテラルで
CoreMetadataを作っている場合は、skip_whitespace: Noneを加えるか、最後を..Default::default()にする。 CoreTokenizerBuilder::set_modeとCoreTokenizer::from_segmenter_with_space_penaltyの新しい 2 番目の引数に、 ペナルティかNoneを渡す。
ビルド環境:
- Rust 1.89 以降を使う。
- シェルのプロファイル・CI 設定・コンテナイメージで
LINDERA_DICTIONARIES_PATHをLINDERA_BUILD_DICTIONARY_CACHE_DIRに改名する。
ファイルから辞書を読み込むすべてのユーザー:
- v6.2.0 以前に作った辞書ディレクトリは、WASM バインディングの OPFS に保存した
ものも含め、
lindera buildで再ビルドするか再ダウンロードする。v7.0.0 はそれらを 拒否する(形式バージョン 3)。埋め込み辞書とユーザー辞書は対応不要。
IPADIC・IPADIC-NEologd のユーザー:
conjugation_typeやconjugation_formを名前で読んでいる場合(CLI の JSON 出力を含む)は、2 つの値が入れ替わることを前提にする。- IPADIC の
metadata.jsonの独自のコピーで辞書をビルドしている場合は、そのコピーの 2 つの名前を入れ替える。
U+3000 や空白を含むテキストを IPADIC・IPADIC-NEologd・UniDic・SudachiDict で 解析するユーザー:
- U+3000 が
記号,空白(IPADIC)や空白(UniDic)のトークンになり、SudachiDict の 空白を含む文の分割が Sudachi にずっと近くなることを前提にする。U+3000 が不要ならjapanese_stop_tagsか NFKC 正規化で取り除く。
空白を含むテキストを分割するすべてのユーザー:
- そのようなテキストは MeCab と同じ分割になることを前提にする(ko-dic で特に
顕著。Sudachi と同じく空白をラティスに残す SudachiDict は対象外)。空白の扱いを v6 に戻したい場合は
skip_whitespace(false)、"skip_whitespace": false、--disable-skip-whitespaceを指定する。このガイドの 他の出力の変化は戻らない。 - 末尾が半角スペースの見出し語(IPADIC-NEologd・ko-dic・SudachiDict)のトークンは、 MeCab と同じくその空白を含むことを前提にする。
Ð・々・〇 を含むテキストを同梱の辞書で解析するユーザー:
Ðが出力に残り、未知語の漢字に続く々が単独のトークンになることを前提にする。
lindera train のユーザー:
char.defのすべての範囲行に 1 つ以上のカテゴリを書く。char.defに単一コードポイントの行や重複した行がある場合は、再学習する。%t素性が MeCab と同じくそれらの行に従うようになったため。- 7.0.0 の
lindera train(またはバインディングの学習関数)ですべてのモデルを 学習し直し、エクスポートと辞書のビルドもやり直す。lindera exportは v6.2.0 以前に 書いたmodel.datを拒否し、学習用ラティス・未知語のラベル・エクスポートするunk.defとmatrix.defも変わったため。 unk.defのすべての行に 5 つ以上のフィールド(カテゴリ、2 つの文脈 ID、コスト、 1 列以上の素性)を書く。resources/training/からコピーしたchar.defとunk.defにSPACEカテゴリが なければ、いまのそれらのファイルと同じく追加する。学習した辞書を--keep-whitespaceなしで解析できるようにするため (#1166)。- 学習で
char.defやunk.defのカテゴリのエラーが出た場合は、各カテゴリを、 それを指す範囲行より前で 1 回だけ定義し、すべてのカテゴリにunk.defの行を 書き、未定義のカテゴリのunk.defの行を削除する (#1195)。
lindera build でシステム辞書をビルドするすべてのユーザー:
- 文脈 ID が接続コスト行列の範囲外だというエラーが出た場合は、
lex.csvやunk.defの ID かmatrix.defの大きさを直すか、skip_invalid_cost_or_idでそうしたlex.csvの行を取り除く。そうした辞書は、以前はビルドでき、解析時に パニックしていた。v6.2.0 以前にエクスポートした辞書は、学習とエクスポートを やり直すのがよい。 - ビルドで
char.defやunk.defのカテゴリのエラーが出た場合は、各カテゴリを、 それを指す範囲行より前で 1 回だけ定義し、すべてのカテゴリにunk.defの行を 書き、未定義のカテゴリのunk.defの行を削除する (#1195)。
詳細形式の見出し語を含むユーザー辞書を使うすべてのユーザー:
- トークナイザーの構築で、文脈 ID が接続コスト行列の範囲外だというユーザー辞書の
見出し語のエラーが出た場合は、使うシステム辞書に合わせてユーザー辞書の CSV の
ID を直し、
.binを使う場合はビルドし直す。そうしたユーザー辞書は、以前は その見出し語が入力に一致したときに解析中にパニックしていた。
行の中に 、 や 。 を含む日本語のテキストを解析するユーザー:
、や。の直後の語の一部が、前の語の文脈を引き継いで解析されることを前提に する(多くは MeCab と同じ解析になる)。v6 の出力に戻す設定はない。
N-best を使うユーザー:
- どの結果も入力全体を覆い、複数の文を含む入力では 2 件目以降の結果が変わることを 前提にする。
、や。を含む行では、1 件目の結果も含めてすべてのコストが変わることを前提に する。コストは行全体を 1 つのラティスで解いたときの値になる。- コストの閾値を指定している場合、閾値は文ごとではなく入力全体の合計コストに 適用される。
- 空の入力が、空白だけの入力と同じく、結果なしではなくトークンのない結果を 1 件 返すことを前提にする。
- コストがまったく同じパスが複数あっても 1 件目の結果が 1-best の分割と同じになり、 コストが同じ結果どうしの順が入れ替わることがあると前提にする。
カタカナ・記号・英字が続くテキストを通常モードで解析するユーザー:
- そうした連続が MeCab と同じ位置で分かれることを前提にする。
・でつないだ カタカナ(IPADIC・IPADIC-NEologd・UniDic)、辞書の記号の直後の未知の記号、."のような韓国語の文末の句読点、CC-CEDICT でA・B・P・Q・Tで始まる 英単語と日本語の仮名の連続が該当する。v6 の出力に戻す設定はなく、unknown_word_ladder(false)でも v6 より前の Lindera の出力にはならない。 - カタカナや英字の多いテキストで命令数が 13% 程度増え、カタカナの非常に長い連続の N-best では時間とメモリが大きく増えることを前提にする。
漢数字や 〇 を含むテキストを ko-dic・CC-CEDICT・Jieba で解析するユーザー:
- ko-dic の漢数字と後ろの漢字が 1 つの未知語になり(
三國史記は 1 つのSH)、〇が記号になり(ko-dic はSY、Jieba はw)、CC-CEDICT と Jieba ではその グループが後ろの数字と漢字まで続くことを、MeCab と同じく前提にする。v6 の出力に 戻す設定はない。
―・—・~・〜 を含むテキストを IPADIC・IPADIC-NEologd で解析するユーザー:
―や~で書かれた語が MeCab と同じく同じ綴りのテキストに一致し、—や〜で 書いたテキストからは、以前の書き換えでしか引けなかった語(IPADIC 12 語、 IPADIC-NEologd 1,085 語)が引けなくなることを前提にする。IPADIC で両方の綴りから 語を引きたい場合は、mapping文字フィルタで—を―に、~を〜に 置き換える。
読みや原形などのトークンの詳細情報を読むユーザー:
- 同点の見出し語を持つ語が、MeCab と同じく CSV で先の行の詳細情報になることを
前提にする(IPADIC の
狡いの読みはコスイではなくズルイ)。v6 の出力に 戻す設定はない。 - ユーザー辞書では、同点の行のうち先の行が選ばれることを前提にする。ユーザー辞書の 見出し語は、MeCab と異なり、引き続きシステム辞書の同点の見出し語に勝つ。
分割を v6 や MeCab と比べるすべてのユーザー:
- コストがまったく同じ 2 つの分割がある、まれな箇所が、MeCab と同じく最後の語が
後から始まる分割になることを前提にする(IPADIC の
腸窒扶斯は腸 / 窒扶 / 斯)。v6 の出力に戻す設定はない。
言語バインディングと CLI:
Metadataを作成するときのnormalize_detailsを削除する(Python のキーワード 引数、Ruby のMetadata.newの 9 番目の位置引数、PHP のMetadataの コンストラクタの最後の引数、Node.js のオプションnormalizeDetails)。 プロパティと、to_dict()・to_h・toArray()・toObject()の同名のキーも 読まないようにする。lindera tokenizeでは、行頭に空白がある行のオフセットが入力行の中の位置を指し、 行頭・行末の U+3000 がトークンになり(IPADIC・IPADIC-NEologd・UniDic)、--keep-whitespaceでそこの空白も出力され、-Nでは空行や空白だけの行が 1 件の 結果を返し、wakati 形式ではトークンのない行が空行になることを前提にする。v6 の オフセットとトークンが必要なら、先に各行の両端の空白を取り除く。- WASM の
TokenizerBuilderでは、setKeepWhitespace()と追加したフィルタがsetDictionaryInstance()で設定した辞書にも、setUserDictionaryInstance()で 設定したユーザー辞書がsetDictionary()で設定した辞書にも適用されることを 前提にする。 - PHP では、各引数に宣言された型の値を渡す。省略可能な引数に渡した型違いの値は、
v6 では
nullとして扱われたが、Exceptionを投げるようになる。$argvの値の ような文字列は(int)や(float)でキャストする。 - PHP・Node.js・WASM では、整数の引数に設定の範囲内の値を渡す。範囲外の値は、v6 では 折り返されたが、例外を投げるようになる。負の位置と、Node.js・WASM の整数でない 数値も同様。
- Node.js の TypeScript では、型
JsPenaltyをPenaltyに改名する。decompose モードを調整するには、set_mode/setModeかTokenizerのコンストラクタにPenaltyを渡す。 - CLI では、文の区切り文字なしに 32 KiB を超えて文を区切ったときなどに、標準エラー
出力に警告が出ることを前提にする。
RUST_LOG=errorで警告を消せる。 - それ以外は、7.0.0 リリースを取り込む以外に対応は不要(上記の辞書の再ビルド・辞書・
空白・
、と。・N-best・未知語・既定カテゴリ・ダッシュとチルダ・同点の見出し語・ コストが同じ分割の項目、lindera trainとlindera buildの項目、lindera tokenizeと WASM と PHP と整数の引数とJsPenaltyと CLI の警告の項目を除く)。