Lindera
Rust製の形態素解析ライブラリです。Linderaは kuromoji-rs からフォークされ、複数言語のテキストトークナイズに対して、簡単なインストールと簡潔なAPIの提供を目指しています。
主な機能
| 機能 | 説明 |
|---|---|
| 形態素解析 | Viterbiベースの分割と品詞タグ付け |
| 多言語サポート | 日本語(IPADIC、IPADIC NEologd、UniDic)、韓国語(ko-dic)、中国語(CC-CEDICT、Jieba) |
| 辞書システム | ビルド済み辞書、ユーザー辞書、カスタム辞書学習 |
| テキスト処理パイプライン | 柔軟なテキスト正規化のための組み合わせ可能なキャラクターフィルターとトークンフィルター |
| CRF学習 | 辞書コスト推定のためのカスタムCRFモデルの学習 |
| Pythonバインディング | PyO3を介してPythonからLinderaを利用可能 |
| WebAssembly | wasm-bindgenを介してブラウザでLinderaを実行可能 |
| Pure Rust | C/C++依存なし。Rustがサポートするあらゆるプラットフォームで動作 |
トークナイズの流れ
graph LR
subgraph Your Application
T["Text"]
end
subgraph Lindera
CF["Character Filters"]
SEG["Segmenter\n(Dictionary + Viterbi)"]
TF["Token Filters"]
end
T --> CF --> SEG --> TF --> R["Tokens"]
ドキュメントマップ
| セクション | 説明 |
|---|---|
| はじめに | インストール、クイックスタート、サンプル |
| 辞書 | 利用可能な辞書とその使い方 |
| 設定 | YAMLベースのトークナイザー設定 |
| ユーザー辞書 | カスタムユーザー辞書の作成と利用 |
| フィルター | キャラクターフィルターとトークンフィルターのリファレンス |
| CRF学習 | 辞書コスト推定モデルのカスタム学習 |
| CLI | コマンドラインインターフェースリファレンス |
| アーキテクチャ | クレート構成と設計の概要 |
| APIリファレンス | Rust APIドキュメント |
| コントリビュート | Linderaへの貢献方法 |
パフォーマンス
Linderaは日本語テキスト(IPADIC)を、シングルスレッドで概ね10〜20 MB/sの速度でトークナイズします(速度はハードウェアや、Viterbiラティスを呼び出しをまたいで再利用するかどうかに依存します)。ベンチマークスイート(lindera/benches/)を自分のマシンで再現するにはmake benchを実行してください。詳細な計測方法と比較データは#875を参照してください。
クイック例
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 = "関西国際空港限定トートバッグ"; let mut tokens = tokenizer.tokenize(text)?; println!("text:\t{}", text); for token in tokens.iter_mut() { let details = token.details().join(","); println!("token:\t{}\t{}", token.surface.as_ref(), details); } Ok(()) }
上記の例は以下のように実行できます:
cargo run -p lindera-analysis --features=embed-ipadic --example=tokenize
実行結果は以下のようになります:
text: 関西国際空港限定トートバッグ
token: 関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
token: 限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
token: トートバッグ 名詞,一般,*,*,*,*,*,*,*
ライセンス
Linderaは MITライセンス の下で公開されています。
アーキテクチャ
Linderaは複数のクレートで構成されるCargo workspaceとして構成されています。各クレートは、低レベルのCRF計算から高レベルのCLIや言語バインディングまで、それぞれ明確な責務を持っています。
クレート依存関係図
graph TB
CRF["lindera-crf\n(CRF Engine)"]
DICT["lindera-dictionary\n(Dictionary Base)"]
TRAINER["lindera-trainer\n(CRF Training)"]
IPADIC["lindera-ipadic"]
UNIDIC["lindera-unidic"]
SUDACHIDICT["lindera-sudachidict"]
KODIC["lindera-ko-dic"]
CCCEDICT["lindera-cc-cedict"]
JIEBA["lindera-jieba"]
NEOLOGD["lindera-ipadic-neologd"]
SEGMENTER["lindera-segmenter\n(Segmenter)"]
ANALYSIS["lindera-analysis\n(Analysis Chain)"]
LIB["lindera\n(Facade)"]
CLI["lindera-cli\n(CLI)"]
BINDING["lindera-binding"]
PY["lindera-python"]
NODEJS["lindera-nodejs"]
RUBY["lindera-ruby"]
PHP["lindera-php"]
WASM["lindera-wasm"]
CRF --> TRAINER
DICT --> TRAINER
TRAINER -.->|"train feature"| SEGMENTER
DICT --> IPADIC
DICT --> UNIDIC
DICT --> SUDACHIDICT
DICT --> KODIC
DICT --> CCCEDICT
DICT --> JIEBA
DICT --> NEOLOGD
DICT --> SEGMENTER
IPADIC --> SEGMENTER
UNIDIC --> SEGMENTER
SUDACHIDICT --> SEGMENTER
KODIC --> SEGMENTER
CCCEDICT --> SEGMENTER
JIEBA --> SEGMENTER
NEOLOGD --> SEGMENTER
SEGMENTER --> ANALYSIS
SEGMENTER --> LIB
ANALYSIS -.->|"analysis feature"| LIB
LIB --> CLI
LIB --> BINDING
BINDING --> PY
BINDING --> NODEJS
BINDING --> RUBY
BINDING --> PHP
BINDING --> WASM
クレート一覧
| クレート | 種類 | 説明 |
|---|---|---|
lindera-crf | コア | Pure RustによるCRF(条件付き確率場)実装。no_stdサポート。シリアライゼーションにrkyvを使用。 |
lindera-dictionary | コア | 辞書ベースライブラリ。辞書の読み込みとビルドを提供。 |
lindera-trainer | コア | CRFベースの辞書学習パイプライン。lindera-crfとlindera-dictionaryの上に構築され、直接、またはlinderafacadeのtrain feature経由で利用される。 |
lindera-segmenter | コア | 純粋な形態素セグメンター(v6 以前の lindera クレート)。辞書クレートを統合し、Segmenter APIを提供。 |
lindera-analysis | コア | lindera-segmenterの上に構築されたLucene風の分析チェーン。文字フィルタ、トークンフィルタ、およびそれらをSegmenterの周りで組み合わせるTokenizerを提供。lindera::analysisとして参照できる。 |
lindera | コア | ファサードクレート。Rustユーザーが追加する唯一の依存で、lindera-segmenterを再エクスポートし、デフォルトで有効なanalysis featureによりlindera-analysisをlindera::analysisとして提供。 |
lindera-cli | アプリケーション | トークナイズ、辞書ビルド、CRF学習のためのコマンドラインインターフェース。 |
lindera-binding | コア | 以下の5つの言語バインディングが共有するFFI非依存のヘルパー。linderaファサードに依存。 |
lindera-ipadic | 辞書 | IPADICベースの日本語辞書。 |
lindera-ipadic-neologd | 辞書 | IPADIC NEologdベースの日本語辞書(新語対応)。 |
lindera-unidic | 辞書 | UniDicベースの日本語辞書。 |
lindera-sudachidict | 辞書 | SudachiDictベースの日本語辞書。 |
lindera-ko-dic | 辞書 | ko-dicベースの韓国語辞書。 |
lindera-cc-cedict | 辞書 | CC-CEDICTベースの中国語辞書。 |
lindera-jieba | 辞書 | Jiebaベースの中国語辞書。 |
lindera-python | バインディング | PyO3を利用したPythonバインディング。 |
lindera-nodejs | バインディング | NAPI-RSを利用したNode.jsバインディング。 |
lindera-ruby | バインディング | Magnus + rb-sysを利用したRubyバインディング。 |
lindera-php | バインディング | ext-php-rsを利用したPHPバインディング。 |
lindera-wasm | バインディング | wasm-bindgenを利用したWebAssemblyバインディング。 |
トークナイズパイプライン
Linderaは複数段階のパイプラインでテキストを処理します:
Input Text
|
v
Character Filters -- Normalize characters (e.g., Unicode normalization, mapping)
|
v
Segmenter -- Segment text into tokens using a dictionary and the Viterbi algorithm
|
v
Token Filters -- Transform tokens (e.g., POS filtering, stop words, stemming)
|
v
Output Tokens
Segmenterがコアコンポーネントです。辞書から候補トークンのラティスを構築し、Viterbiアルゴリズムを適用して最小コストのパスを見つけ、最も適切な分割結果を生成します。辞書検索は、所有権を持つ構造体へのデシリアライズを行わず、dict.trieのシリアライズ済みバイト列上を直接走査する文字単位のダブル配列トライ(crawdadでビルド)を使用します。繰り返しトークナイズを行う場合は、Segmenter::new_worker()が返すSegmentWorkerがラティスとスクラッチバッファの割り当てを呼び出しをまたいで再利用し、保持メモリを自動収縮ポリシーで制限します。
Featureフラグ
| Feature | 説明 | デフォルト |
|---|---|---|
analysis | lindera-analysisをlindera::analysisとして再エクスポート(文字フィルタ、トークンフィルタ、Tokenizer) | 有効(default-features = falseで無効) |
mmap | ファイルシステム辞書読み込みのためのメモリマップドファイルサポート(--mmap/use_mmapでオプトイン。トライ・単語リストファイル・接続コスト行列はいずれもマップしたバイト列上で直接参照され、遅延読み込みされる) | 有効 |
train | CRFベースの辞書学習機能(lindera-crfに依存) | CLI + Python/Node.js/Ruby/PHPバインディング(デフォルト有効)/linderaライブラリクレートではデフォルト無効(オプトイン)/lindera-wasmでは利用不可 |
embed-ipadic | IPADIC辞書をバイナリに埋め込み | 無効 |
embed-ipadic-neologd | IPADIC NEologd辞書をバイナリに埋め込み | 無効 |
embed-unidic | UniDic辞書をバイナリに埋め込み | 無効 |
embed-sudachidict | SudachiDict辞書をバイナリに埋め込み | 無効 |
embed-ko-dic | ko-dic辞書をバイナリに埋め込み | 無効 |
embed-cc-cedict | CC-CEDICT辞書をバイナリに埋め込み | 無効 |
embed-jieba | Jieba辞書をバイナリに埋め込み | 無効 |
embed-cjk | IPADIC + ko-dic + Jieba辞書を埋め込み | 無効 |
embed-cjk2 | UniDic + ko-dic + Jieba辞書を埋め込み | 無効 |
embed-cjk3 | IPADIC NEologd + ko-dic + Jieba辞書を埋め込み | 無効 |
embed-cjk4 | SudachiDict + ko-dic + Jieba辞書を埋め込み | 無効 |
詳細
- はじめに -- インストールと最初のステップ
- 基本概念 -- 辞書、トークナイズ、フィルター
- Linderaライブラリ --
linderaファサードとセグメンターAPI - Lindera Analysis -- 文字フィルタ、トークンフィルタ、
Tokenizer - Lindera Dictionary -- 辞書の読み込みとビルド
- Lindera Trainer -- CRFベースの辞書学習
- Lindera CRF -- CRFエンジン
- Lindera CLI -- コマンドラインインターフェース
- 開発ガイド -- ビルド、テスト、コントリビュート
はじめに
このセクションでは、Linderaのインストールから最初の形態素解析の実行までをガイドします。
- インストール -- プロジェクトへのLinderaの追加と環境変数の設定
- クイックスタート -- わずか数行のコードで最初のテキストをトークナイズ
- サンプル -- 一般的なユースケースのサンプルプログラムを探索
インストール
動作要件
Rust 1.89 以降。ワークスペースが rust-version として宣言しているため、
古いツールチェインではコンパイルエラーではなく cargo のエラーとして報告されます。
依存関係の追加
Cargo.tomlに以下を追加してください:
[dependencies]
lindera = "7"
[!NOTE] この 1 つの依存だけで、分析チェーン(character filter・token filter・
Tokenizer)もlindera::analysisとして利用できます。セグメンターだけが必要な場合はdefault-features = falseを指定してください。 v6 からアップグレードする場合はv6 から v7 への移行を参照してください。
辞書のセットアップ
Linderaの実行にはビルド済み辞書が必要です。GitHub Releases から辞書をダウンロードし、読み込み時にそのパスを指定してください:
#![allow(unused)] fn main() { let dictionary = load_dictionary("/path/to/ipadic")?; }
[!TIP] 辞書をバイナリに直接埋め込みたい場合(上級者向け)は、対応する
embed-*feature フラグを有効にしてビルドし、embedded://スキームでロードしてください:#![allow(unused)] fn main() { // Cargo.toml: lindera = { version = "7", features = ["embed-ipadic"] } let dictionary = load_dictionary("embedded://ipadic")?; }詳細は Feature フラグ を参照してください。
環境変数
LINDERA_BUILD_DICTIONARY_CACHE_DIR
LINDERA_BUILD_DICTIONARY_CACHE_DIR 環境変数は、埋め込み辞書ビルドパイプラインのビルド時キャッシュディレクトリを指定します。辞書クレートの build script のみが読み取り、実行時の動作には影響しません。
設定すると、各ビルドは $LINDERA_BUILD_DICTIONARY_CACHE_DIR/<version>-fmt<format>/(<version> は辞書クレートのバージョン、<format> は辞書フォーマットバージョン)配下に 2 種類のファイルを保存します:
- ダウンロードした配布アーカイブ(MD5 で検証。無効なファイルは自動的に再ダウンロード)
- クレートに埋め込まれるビルド済みバイナリ辞書と、その辞書を何から作ったかを記録する
build_inputs.json
フォーマットバージョンをパスに含めているのは、オンディスクレイアウトが異なるビルドが書いたキャッシュを「古いまま再利用」ではなく「キャッシュミス」にするためです。キャッシュした辞書は、次のすべてが今回のビルドと一致するときだけ再利用し、そうでなければ作り直します。
- 辞書フォーマットバージョンと、クレートの
metadata.jsonの内容 - 辞書を作る
lindera-dictionaryクレートのバージョンとソースのダイジェスト。辞書ビルダーを変更すると、コミット前の変更でも辞書を作り直します - ソースアーカイブ(ファイル名と MD5)と、ビルドがその中で読むディレクトリ
connection_id_mappingが有効なとき、ビルドが読む文脈 ID の頻度表(context_id_freq.txt、またはLINDERA_CTX_FREQ_FILEが指すファイル)
中断されたビルドが残した辞書や、build_inputs.json より前のリリースがキャッシュした辞書は作り直します。ビルダーが依存するクレートのバージョンは比較しません。フォーマットバージョンを変えずに lindera-dictionary の依存クレートを更新したときは、ビルドした辞書が変わりうるならキャッシュを削除してください。古いフォーマットバージョンのディレクトリは自動削除されないため、キャッシュが肥大化する場合は手動で削除してください。
cargo build -vv を実行すると、辞書クレートごとに、キャッシュした辞書を使ったかどうかとその理由が lindera dictionary cache: で始まる行に表示されます。
これにより以下のメリットがあります:
- オフラインビルド: 一度キャッシュされれば、以降のビルドにネットワークアクセスは不要です
- ビルドの高速化: 有効なキャッシュがあればダウンロードと辞書ビルドがスキップされます
- 再現可能なビルド: ビルド間での辞書バージョンの一貫性を保ちます
使用方法:
export LINDERA_BUILD_DICTIONARY_CACHE_DIR=/path/to/cache
cargo build --features=embed-ipadic
注意点:
- このディレクトリは自動管理されており、削除しても安全です(必要に応じて再ダウンロード・再ビルドされます)
- バージョンごとのサブディレクトリはアップグレードのたびに蓄積され、自動削除されません。古いものは自由に削除できます
- この変数を設定すると、
embed-*feature が無効でも辞書クレートはダウンロードとビルドを実行します(キャッシュの事前準備に便利です)。このビルドは辞書を埋め込まないので、キャッシュにすでにある辞書は確かめずにそのまま残します。embed-*feature を有効にしたビルドが確かめ、必要なら作り直します - キャッシュディレクトリは checkout ごとに 1 つ使ってください。辞書ビルダーのコードが異なる 2 つの checkout が同じディレクトリを共有すると、互いの辞書を作り直し合います。さらに、build script が再実行されなかったクレートは、もう一方の checkout が作った辞書を埋め込むことがあります。このリポジトリでは相対パスがワークスペースのルートを基準に解決されるので、checkout ごとに別のキャッシュになります
- このディレクトリを共有するビルド(たとえば
cargo buildと、別のターゲットディレクトリを使う rust-analyzer)は、辞書を 1 つずつ書き込みます。同じ辞書を別のビルドが書き込んでいれば、それが終わるのを待ち、そのビルドが残した辞書を使います(cargo build -vvにlindera dictionary cache: waiting for another buildと表示されます)。ロックは辞書の隣にある空の<辞書>.lockファイルで、ビルドが辞書を書き込む間だけ保持します。ロックが効くのは 1 台のマシンの中のビルド同士で、ネットワークファイルシステムでは、そのファイルシステムがファイルロックに対応する範囲に限られます。ファイルをロックできないときは、ビルドは警告を出し、7.0.0 より前と同じくロックなしで続けるので、そうしたビルド同士は互いのファイルを消すことがあります DOCS_RSを設定したビルドは、この変数を無視します(後述のDOCS_RSを参照)
LINDERA_CONFIG_PATH
LINDERA_CONFIG_PATH 環境変数は、トークナイザーの設定ファイル(YAML形式)へのパスを指定します。これにより、Rustコードを変更せずにトークナイザーの動作を設定できます。
export LINDERA_CONFIG_PATH=./resources/config/lindera.yml
設定フォーマットの詳細は 設定 セクションを参照してください。
DOCS_RS
DOCS_RS 環境変数は、docs.rsでドキュメントをビルドする際に自動的に設定されます。この変数が検出されると、Linderaは実際の辞書データをダウンロードする代わりにダミーの辞書ファイルを作成します。これにより、ネットワークアクセスや大容量ファイルのダウンロードなしでドキュメントをビルドできます。
このビルドは LINDERA_BUILD_DICTIONARY_CACHE_DIR を無視します。ダミーの辞書は build script の出力ディレクトリに作られ、キャッシュには入らないので、後で DOCS_RS なしにビルドしても埋め込まれません。
これは主にdocs.rs内部で使用されるものであり、通常ユーザーが設定する必要はありません。
LINDERA_WORKDIR
LINDERA_WORKDIR 環境変数は、ビルドプロセス中に lindera-dictionary クレートによって自動的に設定されます。これはビルドされた辞書データファイルを含むディレクトリを指し、辞書クレートがデータファイルの場所を特定するために内部で使用されます。
この変数は自動的に設定されるため、ユーザーが変更する必要はありません。
クイックスタート
この例では、Linderaの基本的な使い方を説明します。
以下の処理を行います:
- Normalモードでセグメンターを作成
- 入力テキストを分割(形態素解析)
- トークンを出力
この例では embed-ipadic feature を使用します。この feature はビルド時にIPADIC辞書を自動的にダウンロードしてバイナリに埋め込むため、辞書を手動でダウンロードする必要はありません。
use std::borrow::Cow; use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera::LinderaResult; fn main() -> LinderaResult<()> { let dictionary = load_dictionary("embedded://ipadic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let text = "関西国際空港限定トートバッグ"; let mut tokens = segmenter.segment(Cow::Borrowed(text))?; println!("text:\t{}", text); for token in tokens.iter_mut() { let details = token.details().join(","); println!("token:\t{}\t{}", token.surface.as_ref(), details); } Ok(()) }
上記の例は以下のように実行できます:
% cargo run --features embed-ipadic --example=segment
[!TIP] 辞書をバイナリに埋め込みたくない場合は、GitHub Releases からビルド済みIPADIC辞書をダウンロードしてローカルディレクトリ(例:
/path/to/ipadic)に展開し、代わりにload_dictionary("/path/to/ipadic")を呼び出してください。この場合、embed-ipadicfeature は不要です。詳細は Feature フラグ を参照してください。
実行結果は以下のようになります:
text: 関西国際空港限定トートバッグ
token: 関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
token: 限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
token: トートバッグ 名詞,一般,*,*,*,*,*,*,*
[!NOTE] character filter・token filter・
TokenizerAPI はlindera::analysis(デフォルトで有効なanalysisfeature)として利用できるため、lindera = "7"だけで分析チェーン全体を利用できます。追加の依存は不要です。default-features = falseにするとセグメンターのみになります。詳細は Lindera Analysis を、v6 からアップグレードする場合は v6 から v7 への移行 を参照してください。
サンプル
Linderaには、一般的なユースケースを示すいくつかのサンプルプログラムが含まれています。ソースコードはGitHubの examplesディレクトリ で確認できます(tokenize* のサンプルは lindera-analysis/examples にあります)。
tokenize* のサンプルは lindera::analysis の Tokenizer とフィルタ API を使います。ソースは lindera-analysis クレートにあるため、以下では -p lindera-analysis を指定します。
以下のサンプルはすべて embed-ipadic feature を有効にして実行します。この feature はビルド時にIPADIC辞書を自動的にダウンロードしてバイナリに埋め込むため、辞書を手動でダウンロードする必要はありません。
利用可能なサンプル
segment
Segmenter API による基本的な形態素分割です。セグメンターだけで足りるため、default-features = false でも動作します。
cargo run -p lindera --features=embed-ipadic --example=segment
tokenize
外部IPADIC辞書を使用した基本的なトークナイズです。入力テキストを分割し、各トークンの品詞情報を表示します。
cargo run -p lindera-analysis --features=embed-ipadic --example=tokenize
tokenize_with_user_dict
ユーザー辞書を使用したトークナイズです。ドメイン固有の用語のために、辞書をカスタムエントリで補完する方法を示します。
cargo run -p lindera-analysis --features=embed-ipadic --example=tokenize_with_user_dict
tokenize_with_filters
キャラクターフィルターとトークンフィルターを使用したトークナイズです。Unicode正規化、品詞フィルタリングなどの変換を含むテキスト処理パイプラインを実演します。
cargo run -p lindera-analysis --features=embed-ipadic --example=tokenize_with_filters
tokenize_with_config
YAML設定ファイルを使用したトークナイズです。プログラムではなく宣言的にトークナイザーを設定する方法を示します。
cargo run -p lindera-analysis --features=embed-ipadic --example=tokenize_with_config
基本概念
このセクションでは、Linderaの形態素解析システムの基本的な概念について説明します。
- 形態素解析 - Linderaがテキストを分割・解析する仕組み。
- 辞書 - Linderaがサポートする辞書フォーマット。
- トークナイズ - トークナイズモードとN-Best解析。
- ユーザー辞書 - ユーザー辞書によるカスタム単語の追加。
- フィルタ - 文字フィルタによるテキスト前処理とトークンフィルタによるトークン後処理。
形態素解析
形態素解析とは
形態素解析とは、テキストを最小の意味単位(形態素)に分割し、その文法的特性を同定する処理です。日本語、中国語、韓国語のように単語がスペースで区切られない言語では、形態素解析は検索インデックス作成、テキスト分類、機械翻訳などの自然言語処理タスクにおいて不可欠な最初のステップです。
Linderaの仕組み
Linderaは辞書ベースの形態素解析器です。既知の単語とそのコストを含む事前コンパイル済みのシステム辞書を使用し、Viterbiアルゴリズムを適用して入力テキストの最適な分割を見つけます。
解析処理は以下のように動作します:
- 文分割: ラティスを構築する前に、Linderaは入力テキストを区切り文字(
\n、\t、。、、)で文単位に分割し、1文ずつ処理します。文の先頭からおよそ 32 KiB 以内に区切り文字が見つからない場合、Linderaはその位置で強制的に文の境界を区切り、警告をログに出力します。これはラティスのメモリ・CPU コストを制限するためです。次の\nや\tまでは、各文が前の文の最後の語から続くため、わずかな例外を除いて、そのテキスト全体を 1 つのラティスで解いた場合と同じ結果になります(文分割を参照)。区切り文字を含まない病的な入力(minify されたテキストや base64 エンコードされたデータなど)に対しては、強制的な分割位置でトークナイズ結果に影響が出ることがあります。 - ラティス構築: Linderaは各文をスキャンし、各位置で辞書内のすべての候補単語を検索して、候補分割の有向非巡回グラフ(ラティス)を構築します。
- コスト割り当て: 各候補単語には(辞書からの)単語コストが関連付けられており、隣接する単語のペアには(連接コスト行列からの)連接コストが関連付けられています。
- 最適パス探索: Viterbiアルゴリズムがラティスを通る最小総コストのパスを見つけ、最適な分割結果を生成します。
主な用語
| 用語 | 説明 |
|---|---|
| 表層形 | 入力テキストに実際に現れるテキスト(例:"食べ")。 |
| 品詞(POS) | 単語の文法的カテゴリ(例:名詞、動詞、助詞)。Linderaの辞書は最大4階層のサブカテゴリを持つ階層的な品詞タグを提供します。 |
| 読み | 単語の発音。日本語辞書では通常カタカナで表記されます。 |
| 原形 | 単語の非活用形(辞書形)(例:表層形"食べ"に対して"食べる")。 |
| 活用 | 活用する単語の語形変化情報。活用型と活用形で構成されます。 |
コストベースの分割
Viterbiアルゴリズムは総コストが最小となる分割パスを選択します。パスの総コストは以下の合計です:
- 単語コスト: 辞書内の各単語に関連付けられたコスト。コストが低いほど、その単語が出現する可能性が高いことを意味します。一般的な単語はコストが低く、まれな単語はコストが高い傾向があります。
- 連接コスト: 隣接する2つの単語を接続するコスト。左側の単語の右文脈IDと右側の単語の左文脈IDによって決定されます。
アルゴリズムは以下を計算します:
Total cost = sum of word costs + sum of connection costs
この総コストを最小化することで、Linderaは入力テキストの最も自然な分割を見つけます。
連接コスト行列
連接コスト行列は、ある単語から別の単語への遷移コストを格納します。以下でインデックスされる2次元行列です:
- 先行する単語の右文脈ID
- 後続する単語の左文脈ID
これらの文脈IDは、単語境界に関する文法的情報をエンコードしています。例えば、名詞と助詞の間の連接コストは通常低く(自然な並び)、2つの動詞の基本形の間の連接コストは高くなります(不自然な並び)。
連接コスト行列は辞書ビルドプロセスの一部としてバイナリ形式にコンパイルされ、実行時に効率的な参照のために読み込まれます。
辞書
Linderaは、日本語・韓国語・中国語の形態素解析のための様々な辞書をサポートしています。各辞書は個別のクレートとして提供されます。
| 辞書 | 言語 | クレート | 説明 |
|---|---|---|---|
| IPADIC | 日本語 | lindera-ipadic | 日本語で最も一般的な辞書 |
| IPADIC NEologd | 日本語 | lindera-ipadic-neologd | 新語に対応したIPADIC |
| UniDic | 日本語 | lindera-unidic | 均一な単語単位定義を持つ辞書 |
| SudachiDict | 日本語 | lindera-sudachidict | 活発にメンテナンスされている語彙(上流で年数回更新) |
| ko-dic | 韓国語 | lindera-ko-dic | 韓国語の形態素解析 |
| CC-CEDICT | 中国語 | lindera-cc-cedict | 中英辞書 |
| Jieba | 中国語 | lindera-jieba | Jiebaベースの中国語辞書 |
辞書の入手方法
ビルド済み辞書は GitHub Releases からダウンロードできます。対象言語の辞書アーカイブをダウンロードし、ローカルディレクトリに展開してください。
#![allow(unused)] fn main() { // ローカルパスから外部辞書を読み込む let dictionary = load_dictionary("/path/to/ipadic")?; }
[!TIP] 外部辞書ファイルなしの自己完結型バイナリが必要な場合は、
embed-*feature フラグを使って辞書を埋め込み、embedded://スキームでロードできます:#![allow(unused)] fn main() { let dictionary = load_dictionary("embedded://ipadic")?; }詳細は Feature フラグ を参照してください。
各辞書クレートのドキュメントで、フォーマット詳細、ビルド手順、使用例を参照してください。
トークナイズ
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 | トークナイズするテキスト。 |
n | usize | 返すN-best結果の数。 |
unique | bool | trueの場合、同じ単語境界位置を生成する結果を重複排除します。各結果は、その単語境界のパスのうち最もコストの低いものです。 |
cost_threshold | Option<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 のページを参照してください。
ユーザー辞書
ユーザー辞書は、システム辞書と併用してカスタム単語を登録できる補助辞書です。ドメイン固有の用語、ブランド名、固有名詞、またはデフォルトのシステム辞書に含まれていない単語を登録する場合に有用です。
CSVフォーマット
最もシンプルなユーザー辞書フォーマットは、3つのカラムを持つCSVファイルです:
<surface>,<part_of_speech>,<reading>
CSV内容の例
東京スカイツリー,カスタム名詞,トウキョウスカイツリー
東武スカイツリーライン,カスタム名詞,トウブスカイツリーライン
とうきょうスカイツリー駅,カスタム名詞,トウキョウスカイツリーエキ
表層形(1 列目)は書いたとおりに使われます。先頭や末尾の空白も語の一部になり、空白だけの表層形も登録できます(IPADIC は U+3000 をこの形で登録しています)。読み飛ばされるのは空の表層形だけです。lindera build でビルドするシステム辞書の見出し語の CSV も同じです。
末尾が空白の語は、空白をラティス上で読み飛ばすとき(SudachiDict を除く同梱辞書のデフォルト。空白文字の扱い を参照)も、MeCab と同じくその空白をトークンに含みます。ただし読み飛ばすときは、辞書の SPACE カテゴリの空白で始まる語や、その空白だけの語は一致しません。読み飛ばした空白から始まる語はないためです。U+3000 は SPACE カテゴリではないため、IPADIC の U+3000 の語は引き続き一致します。
各辞書タイプ(IPADIC、UniDic、ko-dicなど)は、文脈ID、コスト、すべての素性フィールドを完全に制御できる詳細CSVフォーマットもサポートしています。各辞書タイプの詳細フォーマットについては 辞書 セクションを参照してください。
同点の見出し語
表層形・左右の文脈 ID・コストがすべて同じ 2 つの見出し語は同点です。どちらを使っても経路のコストは同じになります。ユーザー辞書の見出し語は、同点のシステム辞書の見出し語より優先して選ばれます。そのため、システム辞書の見出し語の表層形・文脈 ID・コストをそのまま使った詳細形式の見出し語(たとえば語に別の読みを与えるもの)は、システム辞書の見出し語の代わりに出力されます。MeCab はシステム辞書の見出し語を選びます。同点のユーザー辞書の見出し語どうしでは、MeCab と同じく先の行が選ばれます(v7 より前は後の行でした。v6 から v7 への移行を参照)。簡易形式の見出し語には、辞書の metadata.json のコストと文脈 ID(default_word_cost・default_left_context_id・default_right_context_id。IPADIC では -10000・0・0)が使われるため、システム辞書の見出し語と同点になることはまずありません。
文脈 ID
詳細形式の見出し語の左右の文脈 ID は、組み合わせるシステム辞書の接続コスト行列のセルを指していなければなりません。左文脈 ID はそのシステム辞書の matrix.def のヘッダの 2 つ目の値より小さく、右文脈 ID は 1 つ目の値より小さくなければなりません。lindera build がシステム辞書自体に課すのと同じ範囲です。たとえば IPADIC の行列は 1316 x 1316 です。
CSV のユーザー辞書をシステム辞書と一緒にビルドするときは、ビルドしながら各行を確認し、行列の範囲外にある最初の行で、その CSV の行番号と表層形を示すエラーを出して失敗します。
--system-dictでシステム辞書を指定したlindera build --user(Rust API ではDictionaryBuilder::with_system_matrix。文脈 ID の確認 を参照)- 設定(
Segmenter::from_config、TokenizerBuilder)やlindera tokenize --user-dictで指定した.csvのユーザー辞書と、Python・Node.js・Ruby・PHP のバインディングのTokenizerBuilderにパスで渡した.csvのユーザー辞書 - 読み込み済みのシステム辞書に合わせてユーザー辞書を読み込む関数(Rust API・Python・Ruby の
load_user_dictionary_for、Node.js のloadUserDictionaryFor、PHP のLindera\Dictionary::loadUserFor、WebAssembly のloadUserDictionaryFromBytesFor) - 読み込み済みのシステム辞書に合わせてユーザー辞書をビルドする関数(Python・Ruby の
build_user_dictionary_for、Node.js のbuildUserDictionaryFor、PHP のLindera\Dictionary::buildUserFor)
たとえば、1 行目が IPADIC の行列の範囲内にあり、2 行目の左文脈 ID が 60000 の userdic.csv では次のようになります。
$ echo 東京スカイツリーへ行く | lindera tokenize -d embedded://ipadic -u userdic.csv
Error: LinderaError { kind: Args, source: LinderaError(kind=Parse, source=failed to build tokenizer: LinderaError(kind=Content, source=user dictionary line 2 (surface: '東京スカイツリー'): left context id 60000 is out of range for the connection matrix (matrix.def size 1316 1316; left context ids must be below 1316)))
それ以外のユーザー辞書は、システム辞書なしでビルドされます。--system-dict を指定せずに lindera build --user で書き出した .bin、バインディングのビルド関数でメタデータだけを使って書き出した .bin(Python・Ruby の build_user_dictionary、Node.js の buildUserDictionary、PHP の Lindera\Dictionary::buildUser)、メタデータだけで読み込んだユーザー辞書(Rust API・Python・Ruby の load_user_dictionary、Node.js の loadUserDictionary、PHP の Lindera\Dictionary::loadUser、WebAssembly の loadUserDictionaryFromBytes)です。この場合、文脈 ID は 2 つの辞書を組み合わせるときに確認します。Segmenter::try_new、設定からの構築、lindera tokenize --user-dict、各言語バインディングの Tokenizer のコンストラクタとビルダーは、行列の範囲外にある最初の見出し語についてエラーを返します。エラーは、見出し語をその単語 ID(表層形の順に並べた行を 0 から数えた番号)と詳細フィールドで示します。上の userdic.csv からビルドした .bin では次のようになります。
$ echo 東京スカイツリーへ行く | lindera tokenize -d embedded://ipadic -u user_dict/userdic.bin
Error: LinderaError { kind: Args, source: LinderaError(kind=Parse, source=failed to build tokenizer: LinderaError(kind=Content, source=user dictionary entry 1 (名詞,固有名詞,一般,*,*,*,東京スカイツリー,トウキョウスカイツリー,トウキョウスカイツリー): left context id 60000 is out of range for the connection matrix (matrix.def size 1316 1316; left context ids must be below 1316)))
Segmenter::new はこの ID を確認しません。範囲外の見出し語があると、その見出し語が入力に一致したときに解析中にパニックします。v7.0.0 より前は、どの経路もこの動作でした。簡易形式の見出し語も確認します。簡易形式の見出し語には、ビルドに使う metadata.json の文脈 ID(default_left_context_id と default_right_context_id。同梱のどの辞書でも 0)が使われます。
Rust APIの例
use std::fs::File; use std::path::PathBuf; use lindera::dictionary::{Metadata, load_dictionary, load_user_dictionary}; use lindera::error::LinderaErrorKind; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera::analysis::tokenizer::Tokenizer; use lindera::LinderaResult; fn main() -> LinderaResult<()> { let user_dict_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../resources") .join("user_dict") .join("ipadic_simple_userdic.csv"); let metadata_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../lindera-ipadic") .join("metadata.json"); let metadata: Metadata = serde_json::from_reader( File::open(metadata_file) .map_err(|err| LinderaErrorKind::Io.with_error(anyhow::anyhow!(err))) .unwrap(), ) .map_err(|err| LinderaErrorKind::Io.with_error(anyhow::anyhow!(err))) .unwrap(); let dictionary = load_dictionary("embedded://ipadic")?; let user_dictionary = load_user_dictionary(user_dict_path.to_str().unwrap(), &metadata)?; // `try_new` checks the user dictionary's context IDs against the // dictionary's connection matrix (see "Context IDs" above). let segmenter = Segmenter::try_new( Mode::Normal, dictionary, Some(user_dictionary), // Using the loaded user dictionary )?; // Create a tokenizer. let tokenizer = Tokenizer::new(segmenter); // Tokenize a text. let text = "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です"; let mut tokens = tokenizer.tokenize(text)?; // Print the text and tokens. println!("text:\t{}", text); for token in tokens.iter_mut() { let details = token.details().join(","); println!("token:\t{}\t{}", token.surface.as_ref(), details); } Ok(()) }
実行結果は以下のようになります:
text: 東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です
token: 東京スカイツリー カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリー,*
token: の 助詞,連体化,*,*,*,*,の,ノ,ノ
token: 最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
token: は 助詞,係助詞,*,*,*,*,は,ハ,ワ
token: とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリーエキ,*
token: です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
base_formフィールド(CSV/詳細情報の7番目のフィールド)が表層形ではなく*になっている点に注意してください。
シンプルな3カラムのユーザー辞書スキーマでは surface、part_of_speech、reading の3つしか指定できず、
base_form を含むそれ以外のフィールドはすべて辞書の metadata.default_field_value
(IPADICの場合は*)で埋められます。
CLIでのユーザー辞書ビルド
CLIを使用して、CSVからバイナリ形式のユーザー辞書をビルドできます:
lindera build --src <user_dictionary.csv> --dest <dest_dir> --metadata <metadata.json> --user [--system-dict <system_dictionary>]
--system-dict を指定すると、組み合わせるシステム辞書と照らし合わせて文脈 ID を確認します(文脈 ID を参照)。
バイナリとCSVのユーザー辞書
- CSVフォーマット: 実行時に読み込み・パースされます。開発時や小規模な辞書に便利です。
- バイナリフォーマット: 高速な読み込みのために事前コンパイルされます。大規模なユーザー辞書を本番環境で使用する場合に推奨されます。
どちらのフォーマットもSegmenterの作成時に指定できます。バイナリフォーマットはCSVのパースステップをスキップするため、起動時間が短縮されます。
フィルタ
Linderaの解析パイプライン(lindera-analysisクレートが提供し、lindera::analysisとして使うTokenizer)には、Segmenterの前後に2つの拡張ポイントがあります。トークナイズ前の生テキストを変換する文字フィルタと、トークナイズ後のトークン列を変換するトークンフィルタです。
Input Text
--> Character Filters (preprocessing)
--> Tokenization
--> Token Filters (postprocessing)
--> Output Tokens
文字フィルタ
文字フィルタは、入力テキストがSegmenterに渡される前に前処理を行います。全角文字を半角に変換する、Unicode表現を正準化する、日本語の踊り字を展開形に変換するなど、トークナイズの一貫性を高めるためのテキスト正規化に主に使われます。文字フィルタはテキストの長さを変えることがあるため、Linderaはすべての変換を記録し、各トークンのバイトオフセットを元の(フィルタ適用前の)テキストにおける位置へ補正します。
トークンフィルタ
トークンフィルタは、Segmenterが生成したトークン列に対して後処理を行います。トークンを基本形(辞書形)に置き換える、トークンの表層形をひらがな・カタカナ間で変換する、品詞タグでトークンを除去する、ストップワードを除去するなど、検索や解析のためにトークン列を正規化・削減する目的で主に使われます。
フィルタの設定方法
文字フィルタ・トークンフィルタのいずれも、kind文字列で識別され、フィルタ固有のパラメータを持つJSONのargsオブジェクトで設定します。フィルタは追加された順序で実行され、追加方法は2通りあります。
-
YAML設定ファイル:
character_filtersキーとtoken_filtersキーの下にフィルタを列挙する。character_filters: - kind: unicode_normalize args: kind: nfkc token_filters: - kind: japanese_base_form -
Rust API:
append_character_filterとappend_token_filterでTokenizerに順番にフィルタを追加する。#![allow(unused)] fn main() { // Character filters run first, transforming the raw input text; // token filters run last, transforming the resulting token list. tokenizer .append_character_filter(BoxCharacterFilter::from(unicode_normalize_char_filter)) .append_token_filter(BoxTokenFilter::from(japanese_base_form_filter)); }
利用可能なフィルタ
Linderaは4種類の文字フィルタと21種類のトークンフィルタを提供しており、日本語・韓国語・汎用的なテキスト正規化をカバーしています。
| カテゴリ | フィルタ |
|---|---|
| 文字フィルタ | unicode_normalize, japanese_iteration_mark, mapping, regex |
| トークンフィルタ -- 正規化 | japanese_base_form, japanese_reading_form, korean_reading_form, japanese_kana, japanese_katakana_stem, japanese_number, korean_number, mapping, remove_diacritical_mark, lowercase, uppercase |
| トークンフィルタ -- 品詞によるフィルタリング | japanese_keep_tags, japanese_stop_tags, korean_keep_tags, korean_stop_tags |
| トークンフィルタ -- 単語によるフィルタリング | keep_words, stop_words |
| トークンフィルタ -- 構造の変換 | japanese_compound_word, korean_compound_word, korean_decompound, length |
各フィルタの説明・パラメータ・実行可能なYAML/Rust APIの例はフィルターリファレンスを参照してください。
Lindera CRF
Lindera CRFは、rucrfからフォークされたConditional Random Fields(CRF)のpure Rust実装です。ラティス構造をサポートしたCRFの学習器と推定器を提供します。
主な特徴
- 可変長エッジを持つラティス構造
- L1、L2、およびElastic Net正則化
- マルチスレッド学習
- rkyvによるゼロコピーデシリアライゼーション
--no-default-features --features allocによるno_stdサポート
目次
rucrfからの変更点
- シリアライゼーションバックエンド: ゼロコピーデシリアライゼーションのため、
bincodeからrkyvに変更 - Elastic Net正則化: L1とL2のペナルティを組み合わせた
Regularization::ElasticNetを追加 - Rust 2024 edition: Rust 2024 editionに更新
- 依存クレートの更新:
argmin、argmin-math、hashbrownなどを更新
アーキテクチャ
モジュール構成
lindera-crf/src/
├── lib.rs # パブリックAPIの再エクスポート
├── feature.rs # FeatureSet, FeatureProvider
├── lattice.rs # Edge, Node, Lattice
├── model.rs # RawModel, MergedModel, Model trait
├── trainer.rs # Trainer, Regularization enum
├── errors.rs # エラー型
├── forward_backward.rs # 前向き・後向きアルゴリズム
├── math.rs # 数学ユーティリティ (logsumexp)
├── optimizers.rs # optimizersモジュールの宣言
├── optimizers/
│ └── lbfgs.rs # L-BFGS最適化
└── utils.rs # ユーティリティtrait
主要コンポーネント
FeatureProvider / FeatureSet
ラベルごとの素性セットを管理します。各FeatureSetは、指定されたラベルのユニグラム素性と左右のバイグラム素性を保持します(保持するのは素性IDのみで、重みは持ちません)。FeatureProviderはラベルIDをFeatureSetインスタンスにマッピングします。重みはRawModel側で別途保持されます(weights、unigram_weight_indices、bigram_weight_indices)。
Lattice / Edge / Node
系列ラベリングのための可変長エッジを持つラティス構造です。Edgeはラベル付きの候補スパンを表し、Nodeは特定の位置にあるエッジを集約します。Latticeは入力データから構築され、モデルが最適パスを探索するために使用されます。
Trainer
設定可能な正則化を用いたL-BFGS最適化によりCRFモデルを学習します。Trainerはラベル付きラティスの例を受け取り、前向き・後向きアルゴリズムで勾配を計算し、反復的にモデルの重みを更新します。Trainer::trainは学習したRawModelを返します。Trainer::train_with_statsは、実際に回ったイテレーション数を持つTrainingStatsも返します。この数は、Trainer::max_iterで設定した最大値で学習が止まったときはその値で、収束などでそれより前に最適化が止まったときはそれより少なくなります。
Regularization
設定可能な正則化戦略:
- L1: L1ペナルティによるスパースモデル
- L2: L2ペナルティによる滑らかなモデル
- ElasticNet: L1とL2を設定可能な
l1_ratioで組み合わせ
Model (trait)
ラティスを通じて最適パスを探索するためのインターフェースです。2つの実装が提供されています:
- RawModel: 素性IDでインデックスされたフラットベクトルに重みを格納
- MergedModel: 推論に最適化され、素性の重みをrkyvでシリアライズ可能なコンパクトな表現にマージ。ラベルごとの要素型として
MergedFeatureSetを使用
MergedFeatureSetは、ラベルの事前合計済みユニグラムweightと、MergedModelのバイグラム重み行列を参照するleft_id/right_id接続IDを保持します。
RawModel::unigram_weightは、ユニグラム素性IDの並びについてこの和を計算し、mergeはラベルごとにこれを呼びます。重みのない素性は何も足さないため、学習でラベルでなかったエントリの素性の重みも計算できます。lindera-trainerはユーザー辞書のエントリのコストにこれを使います。
前向き・後向きアルゴリズム
ラティス上でアルファ(前向き)とベータ(後向き)の値を計算します。学習時に期待素性カウントと勾配の計算に使用されます。
Feature フラグ
| Feature | 説明 | デフォルト |
|---|---|---|
alloc | no_std向けのallocサポート | No* |
std | 標準ライブラリサポート(allocを含む) | No* |
train | 学習機能(L-BFGS、マルチスレッド、ログ出力) | Yes |
* defaultに直接列挙されているわけではありませんが、デフォルトで有効なtrainがstdを有効化し、stdがさらにallocを有効化するため、実際にはデフォルトビルドでalloc/stdは有効になっています。
APIリファレンス
APIリファレンスは以下で公開されています:
Lindera Dictionary
Lindera Dictionaryは、形態素解析辞書のベースライブラリです。辞書の読み込み、ビルド、Viterbiベースのセグメンテーションを提供します。CRFベースの辞書学習機能は、別クレートのlindera-trainerが提供します。lindera ファサードからは lindera::dictionary 配下(辞書データ構造の lindera::dictionary::core、lindera::dictionary::builder、lindera::dictionary::viterbi など)で参照できるため、lindera-dictionary への直接依存はほとんど不要です。
主な特徴
- ファイルシステムまたは埋め込みデータからの辞書読み込み
- MeCab形式のCSVソースファイルからの辞書ビルド
- 最適なセグメンテーションのためのViterbiアルゴリズム
- N-bestパス生成(Forward-DP Backward-A*)
- メモリマップドファイルサポート
目次
アーキテクチャ
モジュール構成
lindera-dictionary/src/
├── lib.rs # パブリックAPI
├── dictionary.rs # Dictionary, UserDictionary
├── builder.rs # DictionaryBuilder
├── loader.rs # DictionaryLoader trait, FSDictionaryLoader
├── viterbi.rs # Lattice, Edge, Viterbiセグメンテーション
├── nbest.rs # NBestGenerator, UniqueNBestGenerator (Forward-DP Backward-A*)
├── mode.rs # Mode (Normal/Decompose), Penalty
├── error.rs # LinderaError, LinderaErrorKind
├── assets.rs # ダウンロードとファイル管理
├── macros.rs # 辞書クレート共通の embedded_dictionary! マクロ
├── dictionary/
│ ├── character_definition.rs # 文字種定義
│ ├── connection_cost_matrix.rs # 連接コスト行列
│ ├── context_id_map.rs # ContextIdMap(連接コストのコンテキストID再割り当て)
│ ├── prefix_dictionary.rs # 前方一致辞書(crawdad トライをバイト列上で直接走査)
│ ├── unknown_dictionary.rs # 未知語処理
│ ├── metadata.rs # 辞書メタデータ
│ └── schema.rs # スキーマ定義
主要コンポーネント
Dictionary / UserDictionary
コンパイル済み辞書データを保持する主要データ構造です。Dictionaryは文字種定義、連接コスト行列、前方一致辞書(文字単位のダブル配列トライ)、および未知語辞書を含みます。システム辞書のトライはビルド時にcrawdadで構築され、実行時はdict.trieのシリアライズ済みバイト列上を直接走査します(デシリアライズなしのゼロコピー。ロードは O(1) のヘッダ検査のみ)。UserDictionaryを使用すると、システム辞書の上にカスタム語彙を追加できます。ユーザー辞書の前方一致検索は従来どおり rkyv アーカイブ内のdaachorseオートマトンを使用します。
DictionaryBuilder
ソースCSVファイルから辞書をビルドするためのFluent APIです。MeCab形式の辞書ソースを、実行時に使用されるバイナリ形式にコンパイルします。4つのビルドステージ(メタデータ、未知語辞書、前方一致辞書、連接コスト行列)は、wasm以外のターゲットではスコープ付きスレッド上で並行実行され、OSスレッドを持たないwasmでは逐次ビルドにフォールバックします。並行実行パスでは4ステージすべての作業データを同時に保持するため、ピークメモリ使用量が大きくなります。
DictionaryLoader / FSDictionaryLoader
DictionaryLoaderはコンパイル済み辞書を読み込むためのtraitです。FSDictionaryLoaderはファイルシステムベースの実装で、ディレクトリから辞書ファイルを読み込みます。オプションでメモリマップドファイルをサポートします。
埋め込み辞書マクロ
embedded_dictionary!マクロ(lindera-dictionary/src/macros.rs、#[macro_export])は、各辞書クレートがコンパイル済み辞書をバイナリに埋め込むために必要なボイラープレートを生成します。具体的には、include_bytes!経由で辞書コンポーネントを読み込むload()関数と、DictionaryLoaderを実装するローダー構造体です。各辞書クレート(lindera-ipadic、lindera-ipadic-neologd、lindera-unidic、lindera-ko-dic、lindera-cc-cedict、lindera-jieba)のembedded.rsは、読み込みロジックを重複実装する代わりにこのマクロを呼び出します。
Viterbi (Lattice, Edge)
入力テキストから候補トークンのラティスを構築し、Viterbiアルゴリズムを使用して最適なセグメンテーションパスを探索します。ラティス内の各Edgeは、関連するコスト(単語コスト + 連接コスト)を持つ候補トークンを表します。
NBestGenerator
Forward-DP Backward-A*アルゴリズムを使用してN-bestセグメンテーションパスを生成します。これにより、アプリケーションは単一の最適パスを超えた代替セグメンテーションを検討できます。
UniqueNBestGenerator
区切り(単語境界の並び)の異なる N-best を生成します。区切りごとに最もコストの低いパスを、コストの昇順に返します。unique な N-best(unique、--nbest-unique)はこれを使います。1 つの区切りには、各単語の辞書エントリの選び方の数だけパスがあり、短い行でも数百万本になることがあります。そのため、パスではなく単語境界の並びの末尾部分を単位に、後ろ向きに探索します。1 つの探索ノードが 1 つの範囲の候補をすべてまとめて持ち、そのコストは正確なので、各区切りはちょうど 1 回現れ、処理量はパスの数ではなく、結果の数と行の長さに比例します。1 件目は 1-best のパスで、同じ表層・同じ文脈 ID・同じコストのエントリは、1-best と同じく、どの結果でも CSV の先の行になります。
Mode
トークナイゼーションの動作を制御します:
- Normal: 最適なViterbiパスを使用した標準的なトークナイゼーション
- Decompose: 設定可能な
Penalty閾値に基づいて複合名詞をさらに分割
辞書フォーマットバージョン
ビルド済み辞書ディレクトリは、自身が書かれたオンディスクレイアウトを metadata.json の format_version として記録します。値は DictionaryBuilder::build_metadata が DICTIONARY_FORMAT_VERSION(lindera-dictionary/src/dictionary/metadata.rs)から刻印します。いま何を書いたのかを正しく主張できるのはビルダだけなので、ソース metadata.json 側の値は無視されます。辞書ディレクトリのロード時には記録されたバージョンを検証し、一致しなければ対処方法を含むエラーを返します。
この検証が重要なのは、ほとんどの成果物が自前のヘッダを持たないためです。matrix.mtx・dict.vals・dict.words は生の配列なので、古いレイアウトの辞書でも長さが辻褄の合う範囲ならエラーにならず異常な値として解釈されます。各辞書クレートに手書きでチェックインされているソース metadata.json はビルドの入力を記述するもので、format_version を持たず、フォーマット検証の対象にもなりません。
ビルド済み成果物のバイト列が変わる変更では必ず DICTIONARY_FORMAT_VERSION を上げてください。シリアライズ形式をそのまま書き出している依存クレートの更新(dict.trie の crawdad、char_def.bin / unk.bin の rkyv、ユーザー辞書 .bin 内の daachorse)も対象で、本クレートのコードが 1 行も変わらないため見落としやすい箇所です。ビルドキャッシュがフォーマットバージョンをキーに含めているのは、まさにこのためです。
現在のフォーマットバージョンは 3 です。バージョン 2(v6.0.0)では、システム前方一致辞書のバイト単位 daachorse Aho-Corasick オートマトン(dict.da)が、文字単位のダブル配列トライ(dict.trie)と u32 の累積和インデックス(dict.valsidx)に置き換えられました。バージョン 3(v7.0.0)では、char_def.bin の各行に、コードポイントの既定カテゴリを先頭に、残りのカテゴリをカテゴリ ID 順に並べます。MeCab と同じく未知語は既定カテゴリだけから作るためで、バージョン 2 の行を読むと既定カテゴリを黙って取り違えます。バージョン 3 には、それより前の 7.0.0 でのビルダーの出力の変更も含まれます(v6 から v7 への移行 を参照)。バージョン 2 以前の辞書はロード時に対処方法を含むエラーで拒否されます。ユーザー辞書の .bin はこの変更の影響を受けませんが、daachorse によって書き出されているため daachorse の更新では無効化されます。上記のリストに daachorse が含まれているのはそのためです。
コンテキストIDリマッピング
辞書メタデータ(lindera-dictionary/src/dictionary/metadata.rs)は、connection_id_mapping: boolフラグとオプションのcontext_id_map: Option<ContextIdMap>を持ちます。辞書クレート(例: lindera-unidic)がconnection_id_mappingを有効にすると、DictionaryBuilderはビルド時にアクセス頻度に基づいて連接行列の左右コンテキストIDを再割り当てし、頻繁に使用される連接コストのセルが近くに集まるようにしてキャッシュ局所性を高めます。DictionaryBuilder::with_context_id_freqは、IDのランク付けに使用するバンドル済み頻度ヒストグラムを(任意で)アタッチするためのメソッドで、再割り当て自体はlindera-dictionary/src/builder/context_id_remap.rsで計算されます。ContextIdMap(lindera-dictionary/src/dictionary/context_id_map.rs)は結果として得られるleft/rightの置換を保持し、同じマッピングを後から再適用できるようにビルド済みのmetadata.jsonに永続化されます。ユーザー辞書は常に元の(再割り当てされていない)ID空間でコンパイルされるため、UserDictionary::remap_context_ids(lindera-dictionary/src/dictionary.rs)は永続化されたContextIdMapを使って、ユーザー辞書のコンテキストIDを、それが紐付くシステム辞書と同じ空間に再割り当てします。この再割り当ては全単射(bijective)な付け替えであるため、トークナイゼーションの出力は変わらず、ルックアップの局所性のみが変化します。
学習
CRFベースの辞書学習パイプラインは、本クレートのランタイム型の上に構築された独立クレート lindera-trainer に含まれます。詳細は学習パイプラインのドキュメントを参照してください。
Featureフラグ
| Feature | 説明 | デフォルト |
|---|---|---|
mmap | ファイルシステム辞書読み込みのためのメモリマップドファイルサポート(トライ・単語リストファイル・接続コスト行列はいずれもマップしたバイト列上で直接参照され、遅延読み込みされる。ロード時に全体展開されるコンポーネントはない) | Yes |
build_rs | 辞書ソースのHTTPダウンロード | No |
ctxfreq | 実験的機能: 連接行列のアクセス頻度プロファイリングを計測し、コンテキストID頻度リマップの構築に使用 | No |
APIリファレンス
APIリファレンスは以下で公開されています:
Lindera Trainer
Lindera Trainerは、CRFベースの辞書学習パイプライン(lindera train)を実装するクレートです。アノテーション付きコーパスと種辞書から学習済みモデルを生成し、そのモデルをMeCab形式の辞書ソースファイルにエクスポートしてバイナリ辞書としてビルドできるようにします。内部ではlindera-dictionaryのランタイム型とlindera-crfのCRFコアを利用しています。このクレートは直接利用することも、linderaファサードのtrain feature経由(lindera::dictionary::trainerとして再エクスポート)で利用することもできます。
主な特徴
lindera-crfによるCRFベースの重み学習(L1、L2、Elastic Net正則化に対応)- MeCab互換の素性テンプレート解析(
feature.def:%F[n]、%L[n]、%R[n]、%w、%u、%l、%r、およびそれぞれの?付きオプション形式) - MeCab互換の3セクション形式による素性書き換え(
rewrite.def: ユニグラム/左文脈/右文脈の書き換えルール) - 辞書形式に依存しない設計:
surface,left_id,right_id,cost,feature...の列構成に従う辞書であれば任意のもの(IPADIC、UniDic、ko-dic、CC-CEDICTなど)を扱える - MeCabの学習器と同じ学習用ラティス: 各位置から始まる種辞書の語と、ランタイムが
char.defとunk.defから作る未知語を候補にし、unk.defのエントリごとに1つのCRFラベルを割り当てる - 学習されたCRFの重みから連接コスト行列を生成し、MeCab互換のコスト変換(
tocost)を適用 - ゼロコピーの
rkyvバイナリ形式による、形式バージョン付きのモデルのシリアライズ(読み込み時はレガシーJSON形式へのフォールバックにも対応) - Lindera/MeCab辞書ソースファイル(
lex.csv、matrix.def、unk.def、char.def、feature.def、rewrite.def、left-id.def、right-id.def)の直接エクスポート
目次
アーキテクチャ
モジュール構成
lindera-trainer/src/
├── lib.rs # Trainer: CRF学習の実行制御。パブリックAPIの再エクスポート
├── config.rs # TrainerConfig: 種辞書・char.def・unk.def・feature.def・rewrite.defのパース
├── corpus.rs # Corpus, Example, Word: 学習データの表現
├── feature_extractor.rs # FeatureExtractor: 素性テンプレート解析と素性ID管理
├── feature_rewriter.rs # DictionaryRewriter: MeCab互換の3セクション書き換え
└── model.rs # Model, SerializableModel: 学習済みモデルの保持・シリアライズ・辞書出力
主要コンポーネント
TrainerConfig
学習に必要な5つの入力ファイル――種辞書(lex.csv)、文字定義(char.def)、未知語定義(unk.def)、素性テンプレート(feature.def)、書き換えルール(rewrite.def)――をパースし、Trainerが利用する設定情報にまとめます。TrainerConfig::from_readers(またはファイルパスを直接渡せるラッパーのfrom_paths)は、種辞書から表層形と素性の語彙を抽出し、パースした素性テンプレートからFeatureExtractorを構築し、書き換えルールからDictionaryRewriterを構築し、さらに種辞書・文字定義・未知語定義をもとにインメモリのlindera_dictionary::dictionary::Dictionaryを組み立てます。char.defは辞書ビルダー(CharacterDefinitionBuilder::build_from_str)でパースするため、学習時の文字カテゴリはビルドされる辞書と同じになります。%t素性に使う各文字の既定カテゴリは、ビルダーのbuild_default_category_tableから得ます。このDictionary(dict())は学習用ラティスの材料を持ちます。prefix_dictionary(system_lexicon())は種辞書で、種辞書の表層形を収めたprefix dictionaryの単語IDは行番号です。unknown_dictionary(unk_handler())は、辞書ビルダーのparse_unkで読んだunk.defのエントリを、ファイル順に1行1エントリで持ちます。これらのコストと文脈IDは学習では使わず、Dictionaryの連接行列はプレースホルダーです。TrainerConfigは、種辞書の素性文字列と、unk.defのエントリを(カテゴリ, 素性文字列)の組としてファイル順にも保持し、ラベルとエクスポートするunk.defはこれらから作ります。unk.defでは、#で始まる行と空行を読み飛ばし、フィールドが5つ未満の行と、文脈IDやコストが数値でない行は、その行を示すエラーになります。from_readersは、mecab-dict-indexやlindera buildと同じく、それより前の行で定義していないカテゴリを指すchar.defの範囲行、2回定義したカテゴリ、char.defにないカテゴリを指すunk.defの行、unk.defの行がないchar.defのカテゴリも拒否します(lindera_dictionary::dictionary::unknown_dictionary::check_unknown_word_categories)。そのため、こうした入力は、エクスポートしたファイルのビルドではなく、学習の前に失敗します。また、surfaces()、surface_features()、get_features()に加え、user_lexicon() / add_user_lexicon_entry() / load_user_lexicon_from_content()によるユーザー辞書へのアクセス、metadata()も提供します。
Corpus / Example / Word
アノテーション付き学習データを表現します。Wordは表層形とその素性文字列(カンマ区切り)のペアです。Exampleは1つの学習用の文であり、Vec<Word>から構築され、各語の表層形を連結して元の文を復元します。CorpusはExampleの集合です。Corpus::from_readerはタブ区切りのsurface<TAB>features形式の行をパースし、空行またはEOSのみの行を文の区切りとして扱います。
FeatureExtractor
MeCab互換の素性テンプレートを解析し、生成された素性文字列から内部で採番されるNonZeroU32型の素性IDへのマッピングを管理します。サポートされるテンプレートのプレースホルダは、%F[n] / %F?[n](インデックスnの素性フィールド。?付きは値が*の場合にスキップ)、%t(文字カテゴリ)、%w(表層形、ユニグラムのみ)、%u / %l / %r(書き換え後のユニグラム/左文脈/右文脈の素性文字列全体)、およびバイグラムの左右文脈フィールド用の%L[n] / %L?[n] / %R[n] / %R?[n]です。%F[n]・%L[n]・%R[n]が最後の列より後ろを読むテンプレートは、?の有無にかかわらず、MeCabの学習器と同じく素性を作りません。extract_unigram_feature_ids[_with_ctx]、extract_left_feature_ids[_with_ctx]、extract_right_feature_ids[_with_ctx]は、パース済みテンプレートを素性の配列(バイグラム抽出の場合は表層形/ufeature/lfeature/rfeatureを保持するオプションのTemplateContextも併せて)に適用し、対応する素性IDを返します。初回利用時には新しいIDが割り当てられます。
DictionaryRewriter
MeCabの3セクション形式rewrite.def([unigram rewrite]、[left rewrite]、[right rewrite])を実装します。各セクションはpattern<TAB>replacement形式のルールの並びを持ち、内部のFeatureRewriterBuilderによってFeatureRewriterというprefix trie(前方一致木)に構築されます。DictionaryRewriter::from_readerは3セクションすべてをパースしますが、セクションヘッダのないファイルは後方互換性のため(右文脈書き換えのみのレガシー形式として)扱います。rewrite()は素性文字列に3つの書き換え器すべてを適用し、(ufeature, lfeature, rfeature)を返します。マッチするルールがないセクションは入力をそのまま通過させます。MeCabと同じく、素性と各ルールのパターン・置換はMeCabのtokenizeCSVの規則(クレート内部のfeature_csv::split_feature)で列に分けるので、,を含む引用付きの列は1つの列です。rewrite()は書き換えた列を,でつなぎ、,か"を含む列は引用符で囲みます。学習器は、素性テンプレートのために3つの結果を同じく列に分けます。(かつてのrewrite_cached()は素性文字列全体をキーにメモ化していましたが、実辞書の素性は行ごとにほぼ一意でキャッシュが一度もヒットしないため削除されました — #975。)
Trainer
Trainer::newはCRFのラベルを作ります。まず種辞書の行ごとに行順で1つ(ラベル行番号 + 1。単語IDが行番号である学習用辞書での一致を、そのままラベルに対応付けるため)、続いてunk.defのエントリごとにファイル順で1つ作り、その%tはエントリのカテゴリのIDです。Trainer::trainは、MeCabの学習器と同じく、例文ごとにlindera_crf::Latticeを1つ作ります。
- 各位置の候補は、その位置から始まる種辞書の語と、ランタイムがその位置に作る未知語です。未知語は
lindera_dictionary::viterbi::Lattice::unknown_word_candidatesが列挙します。文字の既定カテゴリの未知語を、そのINVOKE・GROUP・LENGTHの設定で、カテゴリのunk.defのエントリそれぞれについて作ります。 - 正解の各トークンには、範囲と素性文字列全体が同じ最初の種辞書の行のラベルを付けます。なければ、範囲が同じ未知語候補の
unk.defエントリのうち、*以外の素性の列がすべて正解トークンの列と一致するファイル順で最初のエントリのラベルを付けます(どちらの列もrewrite()と同じく分けます)。それもなければ、MeCabの学習器が仮想ノードを追加するのと同じく、正解の素性文字列から作る仮想ラベル(%tは0、%wなし、素性文字列ごとに1つ)を付けます。仮想ラベルはほかのラベルの後に並び、学習されますが、エクスポートされることはありません。 - それ以外の候補はすべて負例の辺になります。
ラティスを作り終えると、Lattice statistics:の行を出力します。各方法でラベルを付けた正解トークンの数(gold_lexicon・gold_unknown・gold_virtual)、仮想ラベルの数(virtual_labels)、種辞書と未知語候補それぞれから作った負例の辺の数(negative_lexicon・negative_unknown)です。Trainer::max_grouping_lenは、ランタイムと同じ意味(先頭の1文字を除いた文字数。デフォルトのNoneは無制限)で未知語のグルーピングの長さに上限を設けます。ほかのビルダーメソッドは、正則化・イテレーション数の上限・スレッド数を設定します。
Model / SerializableModel
ModelはTrainer::trainが生成する学習済みモデルです。学習されたlindera_crf::RawModel、学習に用いたTrainerConfig、抽出済みの素性の重みとラベル、仮想ラベルの素性文字列、そして学習後にread_user_lexiconで追加されたユーザー辞書エントリを保持します。学習されたCRFの重みは、MeCab互換の整数コストに変換されます(tocost(weight, cost_factor) = clamp(round(-weight * cost_factor), i16::MIN, i16::MAX)、すなわち[-32768, 32767]の範囲にクランプされます)。この際のコストファクターは、i16の値域を最大限活用できるよう計算されます(calculate_cost_factor)。Modelは自身をシリアライズでき(write_model)、辞書ソースファイルを直接エクスポートすることもできます。
SerializableModelは、Model::read_modelがリーダーから以前学習したモデルを読み込んだ際に返される、rkyvでシリアライズ可能な素のデータ型です(lindera export CLIコマンドで使用されます)。Modelと同じ学習結果の情報――素性の重み、ラベル、品詞情報、連接コスト行列、unk.defのエントリ(unk_entries。ファイル順の(カテゴリ, 素性文字列)の組)、保存されているchar.def / feature.def / rewrite.defの内容、コストファクター、左右文脈IDマッピング――をTrainerConfigを持たない所有データとして保持し、辞書エクスポートファイルを生成するための独自のライターメソッド群を提供します。
Modelのパブリックメソッド
| メソッド | 説明 |
|---|---|
read_user_lexicon<R: Read>(&mut self, rdr: R) -> Result<()> | ユーザー定義辞書(種辞書と同じsurface,left_id,right_id,cost,feature...のCSV形式)をモデルに読み込みます。これにより、エントリが与えていないパラメータをwrite_dictionaryが埋められるようになります。MeCabのmecab-dict-indexと同じく、コストと連接IDは別々に埋めます。空のコストは計算し、連接IDのどちらかが空か負の数なら、両方を推定します。パラメータが0,0,0のエントリは、Vibratoの学習器と同じく3つとも埋めます(mecab-dict-indexはそのまま使います)。空でも範囲内の数でもない連接IDやコストは、そのエントリを示すエラーになり、そのときはエントリを1つも追加しません。mecab-dict-indexはそうした値をatoiで数として読みますが、lindera buildは拒否します。ユーザー辞書が必要な場合は、辞書を書き出す前に呼び出す必要があります。 |
write_model<W: Write>(&self, writer: &mut W) -> Result<()> | 学習済みモデル全体(素性の重み、ラベル、品詞情報、連接行列、unk.defのエントリ、保存された定義ファイルの内容、コストファクター、左右IDマッピング)をrkyvバイナリ形式でシリアライズします。metadata.versionはmodel::MODEL_FORMAT_VERSION(2.0.0)です。保存する行列の大きさは、重みが残ったかどうかにかかわらず、モデルのすべての文脈IDを覆います。出力はバイト単位で再現可能です。連接行列は順序付きマップ、unk.defのエントリはVecであるため、バイト列はモデルの内容だけで決まります。 |
read_model<R: Read>(reader: R) -> Result<SerializableModel> | 関連関数。write_modelが書き出したデータからSerializableModelをデシリアライズします。まずrkyv形式を試み、ペイロードが実際にJSONオブジェクトで始まる場合にのみレガシーのJSON形式へフォールバックします。いずれでもない場合は、形式の不一致として再学習を促すエラーを返します。どちらの形式でも、metadata.versionがMODEL_FORMAT_VERSIONでないモデルは同じエラーで拒否するため、7.0.0より前に書かれたモデルはすべて再学習が必要です。feature_setsフィールドが存在しない旧形式のモデルについては、feature_weightsから補完します。 |
write_dictionary<W1, W2, W3, W4>(&self, lexicon_wtr, connector_wtr, unk_handler_wtr, user_lexicon_wtr) -> Result<()> | レキシコン、連接コスト行列、未知語辞書、ユーザー辞書を一度にまとめて書き出す便利メソッドです。ユーザー辞書はread_user_lexiconで読み込んだエントリを入力順で書き出します。連接IDのないエントリには、素性がそのエントリに最も近い種辞書の行にlex.csvが付ける連接IDを割り当てます。コストのないエントリには、MeCabのcalcCostと同じく、そのエントリのunigram素性の学習済みの重みの和を、lex.csvのコストファクターでtocostにより変換したコストを割り当てます。素性は種辞書の行と同じく、書き換えた素性、先頭の文字の既定カテゴリ(%t)、表層形(%w。MeCabはunigramテンプレートの%wを受け付けません)から作ります。そのため、素性と先頭の文字のカテゴリが種辞書のある行と同じエントリは、lex.csvがその行に付けるコストを受け取ります。モデルに重みのない素性は何も足しません。エントリが与えたパラメータはそのまま書き出します。最も近い行は、エントリと同じ位置で一致する列ごとに、前の列ほど高い点を足した合計が最も高い行で、同点なら最初の行です。どの種辞書の行とも共通の列を持たない素性の、連接IDのないエントリは、MeCabのmecab-dict-indexと同じくエラーになり、そのときは何も書き出しません。 |
write_lexicon<W: Write>(&self, writer: &mut W) -> Result<()> | マージされたCRFモデルから得た連接IDとコストを用いて、種辞書の各語彙エントリについてlex.csv形式のエントリ(surface,left_id,right_id,cost,features...)を書き出します。 |
write_connection_costs<W: Write>(&self, writer: &mut W) -> Result<()> | マージしたモデルの文脈IDのすべての(right_id, left_id)の組み合わせ(BOS/EOS用の0を含む)を網羅する密なmatrix.def形式の連接コスト行列を書き出します。各組み合わせのコストは、学習した重みをtocostで変換したものです。学習した重みがない組み合わせ(バイグラム素性が学習中に隣り合わなかったか、正則化で重みが0になったもの)は0になります。これは学習中のその遷移のコストであり、MeCabのmecab-dict-genが書き出す値でもあります。write_modelが書き出すモデルについてSerializableModel::write_connection_costsが書き出す行列と同じです。 |
write_unknown_dictionary<W: Write>(&self, writer: &mut W) -> Result<()> | 学習に使ったunk.defのエントリごとに、ファイル順でunk.defの行を1行ずつ書き出します。各行は、エントリのカテゴリ名と素性文字列、学習された連接IDとコストを持ちます。 |
get_unknown_word_cost(&self, category: usize) -> i32 | char.defのカテゴリのインデックスに対する入力のコストを返します。学習した値ではなくunk.defのコスト列の値で、エントリが複数あるカテゴリでは最後の行のコストです。設定がない場合はデフォルト値2000を返します。 |
num_features(&self) -> usize | 学習済みモデルが保持する素性の重みの数を返します。 |
num_labels(&self) -> usize | 学習済みモデルのエクスポートされるラベル(種辞書の行とunk.defのエントリの合計)の数を返します。仮想ラベルは数えません。 |
raw_model(&self) -> &lindera_crf::RawModel | 高度な操作や低レベルの処理のために、内部のlindera-crfのraw modelへアクセスします。 |
write_bigram_details<L: Write, R: Write, C: Write>(&self, left_wtr, right_wtr, cost_wtr) -> Result<()> | バイグラム素性とコストを記述した3つの診断用ファイル(左側素性一覧、右側素性一覧、生のバイグラム素性ペアごとのコスト行。id 0 はBOS/EOS、名前は素性抽出器由来)を書き出します。3ファイルとも同じモデルに対してバイト単位で再現可能です。 |
evaluate(&self, test_lattices: &[lindera_crf::Lattice]) -> f64 | raw modelの素性の重みの絶対値の平均を簡易的な評価スコアとして返します。引数test_latticesは現時点では未使用であり、保留データに対するモデルの評価はまだ行われません。 |
write_dictionary_buffers(&self, lexicon, connector, unk_handler, user_lexicon: &mut Vec<u8>) -> Result<()> | ラベル、素性の重み、ユーザーエントリ数、表層形を、それぞれ生のrkyvバイトバッファへシリアライズします。CSVベースのwrite_dictionaryに対する、より低レベルな代替エクスポート手段です。 |
write_left_id_def<W: Write>(&self, writer: &mut W) -> Result<()> | left-id.defを書き出します。先頭行は0 BOS/EOSで、続いてマージ済みモデルの左文脈ID(write_dictionaryがlex.csv・unk.def・matrix.defに書き出すID)ごとに、そのIDを持つ最初のラベルの左素性を書き出します。write_modelが書き出すモデルについてSerializableModel::write_left_id_defが書き出すファイルと同じです(両者は1つの対応づけを使います)。 |
write_right_id_def<W: Write>(&self, writer: &mut W) -> Result<()> | right-id.defを書き出します。write_left_id_defと同じく、先頭行の0 BOS/EOSに続いて、マージ済みモデルの右文脈IDごとに、そのIDを持つ最初のラベルの右素性を書き出します。write_modelが書き出すモデルについてSerializableModel::write_right_id_defが書き出すファイルと同じです。 |
SerializableModelのパブリックメソッド
Model::read_modelが返す値に対して利用できるメソッド群で、以前に学習・シリアライズされたモデルから辞書ソースファイルを再エクスポートするために使用します(lindera export CLIコマンドと、Python・Node.js・Ruby・PHPバインディングのエクスポート関数で利用されます)。
| メソッド | 説明 |
|---|---|
export(&self, output_dir: &Path, base_metadata: Option<&Path>) -> Result<Vec<PathBuf>> | 以下のメソッドで、辞書ソースファイルをoutput_dirに書き出します(output_dirがなければ作成します): lex.csv、matrix.def、unk.def、char.def、feature.def、rewrite.def、left-id.def、right-id.defと、base_metadataを指定したときはmetadata.json(update_metadata_json)です。書き出したファイルのパスをこの順序で返します。lindera exportとバインディングのエクスポート関数はこのメソッドを呼ぶため、同じファイルを書き出します。metadata.jsonを最初に作るため、ベースのmetadata.jsonを読めないときはファイルを1つも書き出さずに失敗します。エラーはファイルを示します。 |
write_lexicon<W: Write>(&self, writer: &mut W) -> Result<()> | 保存されているfeature_sets、labels、pos_infoから、種辞書の行の後に続くunk.defのエントリのラベルを除いてlex.csv形式のエントリを書き出します。 |
write_connection_costs<W: Write>(&self, writer: &mut W) -> Result<()> | 保存されているconnection_matrix、max_left_id、max_right_idから、密なmatrix.def形式の連接コスト行列を書き出します。大きさはmax_right_id + 1行×max_left_id + 1列で、学習でバイグラムの重みが1つも残らなかった場合もモデルのすべての文脈IDを覆います。connection_matrixにないセル(学習した重みがない連接)は、Model::write_connection_costsと同じく0になります。 |
write_unknown_dictionary<W: Write>(&self, writer: &mut W) -> Result<()> | 保存されているunk_entriesのエントリごとに、ファイル順でunk.defの行を1行ずつ書き出します。各行は、カテゴリ名、学習された連接IDとコスト、素性文字列を持ちます。 |
write_char_def<W: Write>(&self, writer: &mut W) -> Result<()> | 元の学習入力からそのまま保存されているchar.defの内容を書き出します。 |
write_feature_def<W: Write>(&self, writer: &mut W) -> Result<()> | 元の学習入力からそのまま保存されているfeature.defの内容を書き出します。 |
write_rewrite_def<W: Write>(&self, writer: &mut W) -> Result<()> | 元の学習入力からそのまま保存されているrewrite.defの内容を書き出します。 |
write_left_id_def<W: Write>(&self, writer: &mut W) -> Result<()> | 保存されているleft_id_mapからleft-id.defを書き出します。先頭行は0 BOS/EOSです。 |
write_right_id_def<W: Write>(&self, writer: &mut W) -> Result<()> | 保存されているright_id_mapからright-id.defを書き出します。先頭行は0 BOS/EOSです。 |
update_metadata_json<W: Write>(&self, base_metadata_path: &Path, writer: &mut W) -> Result<()> | ベースとなるmetadata.jsonを読み込み、学習結果に基づく値(素性の重みの中央値から推定したdefault_word_cost、および素性・ラベル数や文脈IDの範囲・connection_matrix_sizeとしてのmatrix.defの大きさ({行数}x{列数})・学習メタデータを含むmodel_infoセクション)で更新した結果を書き出します。タイムスタンプは含まれず、同じモデルに対してバイト単位で再現可能です。 |
ModelMetadataとFeatureSetInfo
ModelMetadataは学習実行に関する要約情報を保持します: version(モデルの形式バージョンMODEL_FORMAT_VERSION)、regularization(使用した正則化コスト係数)、iterations(実際に回ったL-BFGSのイテレーション数。Trainer::max_iterで設定した最大値以下。model_info.training_iterationsとしてエクスポートされます)、feature_count、label_countです。SerializableModel::metadataに格納されます。
FeatureSetInfoは、学習後にマージされたCRFモデルから抽出された、ラベルごとの連接情報を保持します: left_id、right_id、weightです。SerializableModel::feature_setsには、CRFのラベルごとに1エントリが格納されます。labelsの各要素に対応するエントリが同じ順序で並び、その後にエクスポートされない仮想ラベルのエントリが続きます。
APIリファレンス
APIリファレンスは以下で公開されています:
Lindera ライブラリ
lindera クレートは Rust API のファサードです。lindera-segmenter の形態素セグメンター(lindera::segmenter・lindera::dictionary・lindera::mode など)を再エクスポートし、デフォルトで有効な analysis feature により lindera-analysis の分析チェーンを lindera::analysis として提供します。必要な依存は lindera = "7" の 1 行だけで、default-features = false にするとセグメンターのみになります。このセクションでは、セグメンテーション、エラーハンドリング、APIリファレンスについて説明します。
Tokenizer や文字フィルタ・トークンフィルタ(Segmenter の上に構築されたLucene風の分析チェーン)が必要な場合は lindera::analysis を使います。Lindera Analysis(設定・フィルタページを含む)を参照してください。
- Segmenter - Viterbi アルゴリズムを使用するコアセグメンテーションコンポーネント
- エラーハンドリング - エラー型とハンドリングパターン
- APIリファレンス - 生成されたAPIドキュメントへのリンク
Segmenter
Segmenter は形態素解析を実行するコアコンポーネントです。辞書とコストモデルに基づいて、入力テキストの最適な分割を Viterbi アルゴリズムで探索します。表層形・文脈 ID・コストがすべて同じ見出し語(同点の見出し語)からは、MeCab と同じく CSV で先の行を選び、ユーザー辞書の見出し語とシステム辞書の見出し語が同点ならユーザー辞書の見出し語を選びます(同点の見出し語を参照)。同じ位置で終わる 2 つの分割のコストが同じなら、MeCab と同じく最後の語が後から始まる方を選びます。
Segmenter の作成
Segmenter には以下の3つのコンポーネントが必要です:
- Mode - トークナイズ戦略(
NormalまたはDecompose) - Dictionary - 形態素解析用のシステム辞書
- UserDictionary(オプション) - カスタム単語用の補助辞書
#![allow(unused)] fn main() { use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; let dictionary = load_dictionary("embedded://ipadic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); }
Segmenter::new はユーザー辞書をシステム辞書と照らし合わせて確認しません。Segmenter::try_new は同じ引数を取り、見出し語の文脈 ID がシステム辞書の接続コスト行列の範囲外にあるとエラーを返します。Segmenter::new はそうした見出し語を受け付け、その見出し語が入力に一致したときに解析中にパニックします(文脈 ID を参照)。設定からの構築も確認します。load_user_dictionary_for は、読み込み済みのシステム辞書に合わせてユーザー辞書を読み込み、.csv はビルドする時点で確認するため、エラーはその行の CSV の行番号と表層形を示します。.bin や、load_user_dictionary でメタデータだけを使って読み込んだユーザー辞書では、try_new が見出し語を単語 ID で示します。
#![allow(unused)] fn main() { use lindera::dictionary::{load_dictionary, load_user_dictionary_for}; use lindera::mode::Mode; use lindera::segmenter::Segmenter; let dictionary = load_dictionary("embedded://ipadic")?; let user_dictionary = load_user_dictionary_for("userdic.csv", &dictionary)?; let segmenter = Segmenter::try_new(Mode::Normal, dictionary, Some(user_dictionary))?; }
トークナイズモード
Mode::Normal
辞書に登録されたエントリに基づく標準的なトークナイズです。辞書に登録された単語に忠実に分割します。
#![allow(unused)] fn main() { use lindera::mode::Mode; let mode = Mode::Normal; }
Mode::Decompose
複合名詞を構成要素に分解します。このモードでは、長い複合語にペナルティを適用し、Segmenter がより短い構成要素に分割するよう促します。
例えば、「関西国際空港限定トートバッグ」という文中の複合語「関西国際空港」は、Mode::Normal では1つのトークンの一部のままですが、Mode::Decompose では「関西」「国際」「空港」に分割されます。
#![allow(unused)] fn main() { use lindera::mode::Mode; let mode = Mode::Decompose(Default::default()); }
辞書の読み込み
Lindera は様々なソースから辞書を読み込むための load_dictionary 関数を提供しています。
埋め込み辞書
適切な Feature フラグ(例: embed-ipadic)を指定してビルドすると、バイナリから直接辞書を読み込むことができます:
#![allow(unused)] fn main() { use lindera::dictionary::load_dictionary; let dictionary = load_dictionary("embedded://ipadic")?; }
利用可能な埋め込み辞書URI:
embedded://ipadic- IPADIC(日本語)embedded://ipadic-neologd- IPADIC NEologd(日本語)embedded://unidic- UniDic(日本語)embedded://ko-dic- ko-dic(韓国語)embedded://cc-cedict- CC-CEDICT(中国語)embedded://jieba- Jieba(中国語)
外部辞書
ビルド済みの辞書ディレクトリをファイルシステムから読み込むことができます:
#![allow(unused)] fn main() { use lindera::dictionary::load_dictionary; let dictionary = load_dictionary("/path/to/dictionary")?; }
Tokenizer との連携
Segmenter は通常、Character Filter と Token Filter のサポートを追加する Tokenizer を通じて使用されます:
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 = "日本語の形態素解析を行うことができます。"; let tokens = tokenizer.tokenize(text)?; for mut token in tokens { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } Ok(()) }
token を mut で束縛している点に注意してください。Token::details は &mut self を取るため、単純な for token in tokens ではコンパイルエラー(E0596: 可変として借用できません)になります。
Config からの構築
Segmenter::from_config は、Tokenizer/TokenizerBuilder と同じ設定フォーマット(設定を参照)のうち segmenter: セクション相当を受け取り、SegmenterConfig(serde_json::Value)から Segmenter を構築します:
#![allow(unused)] fn main() { use serde_json::json; use lindera::segmenter::{Segmenter, SegmenterConfig}; let config: SegmenterConfig = json!({ "mode": "normal", "dictionary": "embedded://ipadic", "keep_whitespace": false, "use_mmap": false }); let segmenter = Segmenter::from_config(&config)?; }
注: ここでは use_mmap を明示的に示すためにあえて false を指定していますが、省略した場合も後述のデフォルト(true)と同じ挙動になります。
Segmenter::from_config_with_dictionaries は、バイト列から読み込んだ辞書など、読み込み済みの辞書に設定を適用します。Some で渡した辞書は dictionary キーが指す辞書の代わりに使われ、このときキーは省略できます(use_mmap は関係しません)。Some で渡したユーザー辞書は、user_dictionary キーが指すユーザー辞書の代わりに使われます。None の場合、辞書は from_config と同じく設定から読み込まれ、設定にあるユーザー辞書は、load_user_dictionary_for で使う辞書に合わせて読み込まれます(.csv はその辞書のメタデータでビルドし、接続コスト行列と照らし合わせて確認します)。それ以外のキーは from_config と同じように適用されます:
#![allow(unused)] fn main() { use serde_json::json; use lindera::dictionary::load_dictionary; use lindera::segmenter::{Segmenter, SegmenterConfig}; let dictionary = load_dictionary("embedded://ipadic")?; let config: SegmenterConfig = json!({ "mode": "normal", "keep_whitespace": true }); let segmenter = Segmenter::from_config_with_dictionaries(&config, Some(dictionary), None)?; }
Tokenizer::from_config_with_dictionaries と TokenizerBuilder::build_with_dictionaries は、フィルタを含むトークナイザーの設定に対して同じことを行います(アーキテクチャを参照)。
メモリマップド読み込み
ファイルシステム辞書(embedded:// ではない辞書)に対しては、mmap cargo
feature がコンパイルに含まれている場合(デフォルトで含まれます)、
use_mmap はデフォルトで true になり、メモリマップド読み込みが自動的に
使用されます。単純なファイル読み込み(非メモリマップド)を強制したい場合は
false を指定してください。
辞書フォーマットバージョン 2 以降、トライ・単語リストファイル・接続コスト
行列はいずれもマップしたバイト列上で直接参照されるため、大きなコンポーネント
はすべて遅延読み込みされ、ロード時に所有メモリへ全体展開されるものは
ありません。
use_mmap は embedded:// 辞書に対しては無視されます(埋め込みデータは
既に静的なゼロコピーバイトスライスであるため)。mmap cargo feature
(デフォルトで有効)が必要です。
空白文字の扱い
デフォルトでは、MeCab と同様に空白をラティス上で読み飛ばします。空白文字から始まる語はなく、空白の後ろの語は空白の前の語に直接接続し、辞書が学習した連接コストが使われます。読み飛ばした空白は出力に現れず、トークンの表層形とオフセットにも含まれません。ただし、見出し語の一部である空白は、語の途中(IPADIC-NEologd の GeForce GTX、ユーザー辞書の 해운대 해수욕장)でも末尾(ko-dic の、半角スペースの付いた 에듀)でも、MeCab と同じくその語のトークンに残ります。「空白」とは辞書の SPACE 文字カテゴリ(char.def)のことで、ko-dic では U+0020、U+0009、U+000A、U+000B、U+000D、IPADIC・IPADIC-NEologd・UniDic・SudachiDict・CC-CEDICT・Jieba では U+0020、U+0009、U+000A、U+000B です。これらの char.def は U+00D0(Ð)も SPACE に割り当てています(U+000D のタイポ)が、後続の 0x00C0..0x00FF ALPHA 行が優先されるため、MeCab と同じく Ð は英字として扱われます。
デフォルトは辞書の metadata.json(skip_whitespace)で決まります。SudachiDict はこれを false にしています。Sudachi は空白をラティスに残して 空白 トークンとして出力し、SudachiDict のコストもそれに合わせて調整されているため、読み飛ばすと、たとえば caramel man という項目が 2 つの普通名詞に分割されてしまいます。そのため SudachiDict では、デフォルトで空白が独立したノード(U+0020 の項目のような 空白 の見出し語か、SPACE の未知語)としてラティスに残ります(出力からは除外されます)。他の同梱辞書はこの設定を持たず、その場合は読み飛ばします。設定が導入される前にビルドされた辞書(再ビルドするまでの Lindera 6.x 製の SudachiDict を含む)も同様です。
Segmenter に対して keep_whitespace(true) を呼び出すと、空白のトークンを出力します。このとき空白はラティスにも独立したノード(IPADIC や UniDic の U+3000 の項目のような空白の見出し語か、SPACE の未知語)として残るため、他のトークンも、空白を読み飛ばすデフォルトの出力と変わることがあります:
#![allow(unused)] fn main() { let segmenter = Segmenter::new(Mode::Normal, dictionary, None).keep_whitespace(true); }
v7 より前の Lindera は、空白を出力から除外する場合でもラティスにはノードとして残していました。そのため空白の前後の語は、互いにではなく空白のノードに接続していました。その影響の大きさは、辞書の SPACE の未知語の連接コストによります。ko-dic では連接表のその行と列がすべて 0 なので、空白のたびに文脈が途切れ、空白に挟まれた語は単語コストだけで選ばれていました。ko-dic は 2년 전 대회 の 전 を名詞 NNG ではなく 저/NP + ㄴ/JX と解析していました。IPADIC と UniDic ではこの連接コストは 0 ではなく(IPADIC の SPACE の未知語は全角空白の項目 記号,空白 と文脈 ID を共有します)、IPADIC は Google が 新しい の が を接続詞と解析していました。CC-CEDICT と Jieba には連接コスト自体がないため、分割はこの違いに左右されませんが、N-best が返す経路のコストは変わります。skip_whitespace は辞書のデフォルトを上書きします。skip_whitespace(false) は従来どおり空白ノードをラティスに残し(出力からは除外)、skip_whitespace(true) は SudachiDict でも読み飛ばしを有効にします。SegmenterConfig の skip_whitespace キーには true または false を指定し、省略するか null にすると辞書のデフォルトのままになります。CLI では --disable-skip-whitespace で読み飛ばしを無効にします:
#![allow(unused)] fn main() { let segmenter = Segmenter::new(Mode::Normal, dictionary, None).skip_whitespace(false); }
\n と \t は引き続き区間の終わりになるため(文分割 を参照)、改行やタブをまたいで文脈は引き継がれません。mecab-ko は 1 行ずつ解析し、行内のタブは他の空白と同様に読み飛ばします。
未知語のグルーピング
辞書にない語は「未知語」として扱われます。未知語は、辞書の char.def が文字に割り当てる文字種(カタカナ・英字・数字・記号など)をもとに作られます。漢数字(IPADIC では KANJINUMERIC KANJI)のように、1 つの文字が複数の文字種を持つこともあります。その文字の文字種を決める char.def の行の先頭の文字種を、その文字の「既定の文字種」と呼びます。Lindera は、ラティス上の経路が到達するすべての位置で、その位置の文字の既定の文字種から未知語候補を作ります。作るのは、その文字種が常に未知語を作る設定(char.def の INVOKE)の場合か、その位置から始まる辞書語がない場合で、MeCab と同じ条件です。グルーピングする設定(GROUP)の文字種では、カタカナの連続のように、その位置から「どの文字も直前の文字と文字種を 1 つ以上共有する」間続くランを 1 つの候補にします。これを「グルーピングした未知語」と呼びます。
ko-dic では、数字の 三 は HANJANUMERIC HANJA(既定の文字種はグルーピングする HANJANUMERIC)、ほかの漢字は HANJA です。そのため 三 から始まるグルーピングした未知語は後ろの漢字まで続き、三國史記 は MeCab と同じく 1 つの未知語(SH)になります。v7 より前の Lindera は、文字の文字種ごとに候補を作り、次の文字が文字種の並びの同じ位置に同じ文字種を持つ間だけランを続けていたため、三 / 國史 / 記 を返していました。
未知語は、前の位置からグルーピングした未知語が覆うランの内側も含め、こうしたどの位置からも始まります。IPADIC の char.def は ・ をカタカナの範囲に含めるため、ジョン・レノンが歌う の先頭の位置では ジョン・レノン がグルーピングした未知語になります。一方、辞書語の ジョン と ・ をたどる経路が レ の位置に届き、そこから未知語 レノン が始まるので、結果は MeCab と同じく ジョン / ・ / レノン / が / 歌う になります。v7 より前の通常モードは、Kuromoji から引き継いだ近道として、直前にグルーピングした未知語の内側の位置では未知語を作らなかったため、ジョン・レノン を 1 つの未知語として返していました。Decompose モードにはこの近道はありませんでした。
未知語のグルーピングはデフォルトでは無制限です。max_grouping_len(Some(n)) で MeCab の max-grouping-size と同じ意味論(先頭を除いた文字数で数え、MeCab のデフォルトは 24)の上限を設定できます:
#![allow(unused)] fn main() { let segmenter = Segmenter::new(Mode::Normal, dictionary, None).max_grouping_len(Some(24)); }
上限はラン全体ではなく、ラティスの各位置で判定されます。ある位置から始まるランが上限を超える場合、その位置ではグルーピング候補を出さず、1 文字候補(および後述の候補ラダーと辞書語)が残ります。残りの末尾は上限に収まった時点で再びグルーピングされます。そのため未知語トークンは最長で n + 1 文字になりますが、上限を超えるランが全体として 1 文字ずつになるわけではありません。IPADIC で辞書外のカタカナ列 ヷヸヹヺヷ を max_grouping_len(Some(2)) で分割すると、候補ラダーを無効にした場合は ヷ / ヸ / ヹヺヷ になります。先頭 2 つの位置ではランの長さが 5 文字・4 文字で上限を超えるため 1 文字候補だけが残り、末尾 3 文字は上限に収まるのでグルーピングされます。候補ラダーが有効(デフォルト)の場合は先頭位置で 2 文字のラダー候補が選ばれ、ヷヸ / ヹヺヷ になります。
Some(0) は 2 文字以上のランを一切グルーピングしません。設定キー max_grouping_len と CLI の --max-grouping-len では 0 は無制限を意味します。
グルーピングとは独立に、Lindera はデフォルトで MeCab/Vibrato 由来の
「候補ラダー(length ladder)」も生成します。1 文字から、既定の文字種の
char.def の LENGTH フィールドまでの短い未知語候補を段階的に生成し、
Viterbi 探索が最もコストの低い長さを選べるようにします。候補が伸びるのは、
先頭の文字と文字種を共有する文字が続く間です。MeCab と同じく、各文字は
直前の文字ではなく先頭の文字と比べます。unknown_word_ladder(false) で
無効にすると、各位置で作る候補は、v6 より前と同じくグルーピング候補か
1 文字候補だけになります。ただし、ランの内側から始まる未知語など、その後の変更に
よる出力の違いは残るため、v6 より前の出力とは一致しません(v6 から v7 への移行を参照):
#![allow(unused)] fn main() { let segmenter = Segmenter::new(Mode::Normal, dictionary, None).unknown_word_ladder(false); }
左側空白ペナルティ(韓国語)
MeCab ベースの韓国語解析器(mecab-ko + mecab-ko-dic、Lucene の nori)は、直前に空白がある候補の品詞が、本来は前の語に空白なしで付く品詞(助詞 J*、語尾 E*、指定詞 VCP、派生接尾辞 XS*)である場合にコストを加算します。これがないと 검색 이 잘 된다 の 이 は感動詞 IC ではなく主格助詞 JKS と解析されます。Lindera は、ルールを metadata.json に同梱する辞書(ko-dic)ではこのペナルティをデフォルトで適用します。他の辞書には影響しません。Segmenter::space_penalty(None)、設定の "space_penalty": false、CLI の --disable-space-penalty でオフにできます。skip_whitespace(false) と併用すると、空白まわりの韓国語の解析について v6.0 以降に入った 2 つの変更(このペナルティと空白の読み飛ばし)がどちらもオフになります。ただし、." のような記号の連続が分かれるなど、それ以外の出力の変化は残ります(v6 から v7 への移行を参照)。
SpacePenaltyConfig は「先頭品詞タグの一覧とコスト」の組(ルール)のリストです。候補は品詞タグの最初の + より前の部分(ko-dic の Inflect 行では first_part_of_speech 列)で照合され、最初に一致したルールが適用されます。一覧にないタグのコストは 0 です。mecab-ko-dic の dicrc のルールは次のように書けます:
#![allow(unused)] fn main() { use lindera::space_penalty::{SpacePenaltyConfig, SpacePenaltyRule}; let rules = SpacePenaltyConfig::new(vec![ SpacePenaltyRule::new(["EC", "EF", "EP", "ETM", "ETN", "VCP", "XSA", "XSN", "XSV"], 3000), SpacePenaltyRule::new(["JC", "JKB", "JKC", "JKG", "JKO", "JKQ", "JKS", "JKV", "JX"], 6000), ]); let segmenter = Segmenter::new(Mode::Normal, dictionary, None).space_penalty(Some(rules))?; }
「空白」とは辞書の SPACE カテゴリ(char.def)に属する文字のことで、ラティス上で読み飛ばす文字集合、keep_whitespace が除外する文字集合と同じです。SPACE カテゴリを持たない辞書では Unicode の White_Space にフォールバックします。ペナルティは両モードと N-best 探索で適用され、システム辞書・ユーザー辞書・未知語のいずれのエントリにも効きます。space_penalty は単語 ID ごとの参照表を一度だけ構築し(ko-dic で数十ミリ秒)、辞書スキーマに part_of_speech_tag も part_of_speech もない場合はエラーを返します。
辞書は metadata.json の space_penalty にデフォルトのルールを同梱できます。ko-dic は上記とまったく同じルールを同梱しており、Segmenter::new がそれを自動的に適用します。space_penalty_from_dictionary() はオフにした後に再び有効化するためのもので、ルールを同梱しない辞書ではエラーを返します。同梱ルールを適用できない辞書(スキーマに品詞列が無い辞書)では、Segmenter::new は警告を出してオフのままにします:
#![allow(unused)] fn main() { let segmenter = Segmenter::new(Mode::Normal, dictionary, None).space_penalty_from_dictionary()?; }
このルールを同梱しているのは、Lindera 6.1.0 以降でビルドした ko-dic だけです。v6.0.0 リリースからダウンロードした ko-dic にはルールがないため、その辞書ではペナルティは黙ってオフのままになり、space_penalty_from_dictionary()(および "space_penalty": true)は辞書を再ビルドするまでエラーを返します。明示的なルール(space_penalty、--space-penalty-rules)はどの ko-dic でも使えます。
SegmenterConfig の space_penalty キーには、明示的なルールのオブジェクト、オフにする false、辞書のルールを要求する true(同梱しない辞書ではエラー)を指定できます。キーを省略するか null にするとデフォルト、つまり辞書がルールを同梱していればそのルールが使われます:
{
"dictionary": "embedded://ko-dic",
"space_penalty": {
"rules": [
{ "pos": ["EC", "EF", "EP", "ETM", "ETN", "VCP", "XSA", "XSN", "XSV"], "cost": 3000 },
{ "pos": ["JC", "JKB", "JKC", "JKG", "JKO", "JKQ", "JKS", "JKV", "JX"], "cost": 6000 }
]
}
}
候補は、前の語との間にその語に含まれない空白があるときにペナルティを受けます。これは mecab-ko の判定(空白を読み飛ばしたノード、つまり rlength > length のノードに加算する)と同じです。そのため、文頭の空白の後の語はペナルティを受け、表層が空白で終わる見出し語の直後の語は受けません。ko-dic にはこのような見出し語が 4 件あり(내셔날、에듀、캘리、콜라비 のそれぞれに空白が続くもの)、에듀 이 は mecab-ko と同じくこの見出し語と 이/VCP になります。見出し語が含まない 2 つ目の空白がある 에듀 이 では 이/NR になります。判定は、空白をラティス上で読み飛ばす場合(デフォルト。空白文字の扱い を参照)でもノードとして残す場合でも同じで、残す場合は空白のノードの後の語がペナルティを受けます。空白を読み飛ばすと、ペナルティの対象となる読みの一部は連接コストだけで排除されます(서울 시 에서 の 시 はペナルティなしでも NNG)が、すべてではありません(검색 이 잘 된다 の 이)。
N-Best セグメンテーション
segment_nbest は、コストの合計で並べた上位 n 件の分割結果を、それぞれのコストと共に返します。unique を指定すると、単語境界は同じで品詞タグのみ異なる結果を重複排除できます(各結果はその単語境界のパスのうち最もコストの低いものです。探索は他のパスをたどらないので、行の単語が多くのエントリを持つ場合も速く終わります)。cost_threshold を指定すると、best_cost + threshold を超えるコストのパスを除外できます:
#![allow(unused)] fn main() { let results = segmenter.segment_nbest(Cow::Borrowed("すもももももももものうち"), 3, false, None)?; for (tokens, cost) in results { println!("cost={cost}"); for token in tokens { println!(" {}", token.surface.as_ref()); } } }
どの結果も入力全体の分割です。入力は文分割のとおりに区間に分けられ、区間ごとに探索されます。区間の中では 、・。・強制的な区切りをまたいで文脈を引き継ぐため、区間のパスは、区間全体を 1 つのラティスで解いたときのコストの小さい n 本のパスです(例外は文分割を参照)。unique は区間全体の単語境界で比較します。各結果は区間ごとに 1 つのパスを選んだ組み合わせで、そのコストは選んだパスのコストの合計です。結果は、これらの組み合わせのうちコストの小さい n 件です。そのため、1 つの区間だけが最良の結果と異なる結果もあります。best_cost は 1 件目の結果のコストなので、cost_threshold は入力全体に対して適用されます。1 件目の結果は常に segment の出力と同じです。語のない入力は、MeCab と同じく、BOS から EOS への連接コストを持つトークンのない結果を 1 件返します。読み飛ばす空白だけの入力と、v7 からは空の入力(以前は結果なし)がこれにあたります。
segment_nbest_with_lattice は同じ処理を行いますが、呼び出しごとの Lattice バッファの再確保を避けるために、再利用可能な Lattice を自分で渡すことができます。
文分割
ラティスを構築する前に、Linderaは入力テキストを区切り文字(\n、\t、。、、)で文単位に分割し、文ごとに Viterbi ラティスを構築します。文の先頭からおよそ 32 KiB 以内に区切り文字が見つからない場合、Linderaはその位置で強制的に文の境界を区切り、警告をログに出力します。こうして区切ることで、各ラティスのサイズ(ひいてはメモリ・CPU コスト)を制限しています。
\n・\t・入力の終わりは、区間(segment)の終わりになります。区間とは、そこまでの文の並びのことで、区間どうしは互いに独立しています。区間の中では、、・。 と強制的な区切りをまたいで文脈を引き継ぎます。次の文は文頭(BOS)からではなく前の文の最後の語から始まり(その右文脈 ID ごとに、そこまでの最良のパスのコストを引き継ぎます)、文末(EOS)への連接コストは区間の終わりでだけ払います。そのため、区間の最良のパスとそのコストは、区間全体を 1 つのラティスで解いた場合と同じになり、行の中で区切らない MeCab の結果と一致します。v7 より前は、どの文も BOS から始まり EOS で終わっていたため、、 の直後の語は文脈を失っていました。たとえば IPADIC では、彼は、ああ言った の ああ が感動詞になっていましたが、現在は MeCab と同じく副詞になります。
次の場合は、区間全体を 1 つのラティスで解いた結果と異なることがあります。
- 区切りをまたぐ語はできません。UniDic の
一、二塁のように、や。を含む見出し語はマッチせず、未知語も区切りをまたいでまとめられず、強制的な区切りをまたぐはずの語はそこで分かれます。 - 強制的な区切りの位置から始まる語には、区切りの直前が空白でも左側空白ペナルティ(左側空白ペナルティ(韓国語)を参照)がかかりません。
- 経路のない文(未知語の候補を作らない文字がある辞書でだけ起きます)があると、その前で区間を終えます。区切りまでの最良の経路を、そこで終わる経路として(文末の連接コストを含めて)確定し、その文は単独で解析します。N-best の 1 件目も同じ経路になります。
通常のテキストが強制的な区切りに達することはありませんが、区切り文字を含まない病的な入力(例えば minify されたテキストや base64 エンコードされたデータなど)に対しては、この人為的な分割位置でトークナイズ結果に影響が出ることがあります。
N-best の探索も同じ区間を使います。N-Best セグメンテーションを参照してください。
再利用可能な Worker
SegmentWorker は、Viterbi ラティスと分割処理用のスクラッチバッファを所有する再利用可能なセグメンテーションセッションです。呼び出しのたびに segment が支払うアロケーションを回避できます。new_worker(セグメンターを clone)または into_worker(セグメンターを消費し、ユーザー辞書のコピーを回避)で作成し、segment/segment_nbest を呼び出します:
#![allow(unused)] fn main() { let mut worker = segmenter.new_worker(); for line in lines { let tokens = worker.segment(line)?; for token in &tokens { println!("{}", token.surface.as_ref()); } } }
返されるトークンは worker を借用するため、次の呼び出しの前に消費する必要があります(上記のような行単位のループはそのままコンパイルできます)。set_mode・set_keep_whitespace・set_skip_whitespace・set_max_grouping_len・set_unknown_word_ladder・set_space_penalty・set_space_penalty_from_dictionary で呼び出しごとに設定を切り替えられます。segmenter() は内部の Segmenter への共有参照を返します。&mut でのアクセサは意図的に提供されていません。再利用中のラティスの下で辞書を差し替えてしまうと、この worker が保証する辞書とラティスの対応関係が壊れてしまうためです。
Worker は保持メモリも制限します。区切り文字のない最大長の文を 1 回処理するとラティスは数 MB まで成長し(32 KiB の ASCII 文で約 18 MB、32 KiB の CJK 文では文字索引ラティスのスロット数が 1/3 のため約 6 MB)、素の Lattice はそれを保持し続けます。Worker は一定回数の呼び出し窓で容量が過大と判明した場合に、自動でラティスを縮小してスクラッチバッファを解放し、shrink_to(text_len_hint) で即時に縮小することもできます。reset() は内部バッファを破棄して新しいものに置き換えます。これは、例えばパニックによって worker を保持する Mutex がポイズニングされた場合など、バッファが不整合な中間状態を保持している可能性がある回復経路のために用意されています。Segmenter の設定(mode や空白の扱いなど)自体は保持されます。
Worker は作成元セグメンターの辞書に恒久的に紐付けられ、稼働中の worker の辞書を差し替える手段はありません。これにより、ラティス再利用に起因するバグの一群を構造的に排除しています。マルチスレッドで使う場合は、共有の Segmenter からスレッドごとに worker を 1 つ作成してください。
エラーハンドリング
Lindera はライブラリ全体で使いやすいエラーハンドリングを実現するため、anyhow と thiserror に基づく構造化されたエラーシステムを使用しています。
LinderaResult
LinderaResult<T> 型エイリアスは、Lindera における失敗する可能性のある操作の標準的な戻り値型です:
#![allow(unused)] fn main() { pub type LinderaResult<T> = Result<T, LinderaError>; }
LinderaError
LinderaError はメインのエラー型で、エラー種別と完全なコンテキストを持つソースエラーを含みます:
#![allow(unused)] fn main() { pub struct LinderaError { pub kind: LinderaErrorKind, source: anyhow::Error, } }
add_context メソッドを使用して、エラーに追加のコンテキストを付与できます:
#![allow(unused)] fn main() { let error = error.add_context("failed to load dictionary from /path/to/dict"); }
LinderaErrorKind
LinderaErrorKind はエラーを分類する列挙型です:
| Kind | 説明 |
|---|---|
Io | I/Oエラー(ファイルの読み書き、ネットワーク) |
Parse | パースエラー(無効な入力形式) |
Serialize | シリアライズエラー |
Deserialize | デシリアライズエラー |
Content | 無効なコンテンツまたはデータのエラー |
Args | 無効な引数のエラー |
Decode | デコードエラー |
NotFound | リソースが見つからない(例: 辞書ファイルの欠落) |
Build | 辞書ビルドエラー |
Dictionary | 辞書関連のエラー |
Mode | 無効なトークナイズモードのエラー |
FeatureDisabled | 有効化されていない機能を使用しようとした |
エラーの作成
LinderaErrorKind::with_error を使用して、種別とソースからエラーを作成します:
#![allow(unused)] fn main() { use lindera::error::LinderaErrorKind; let error = LinderaErrorKind::Io.with_error(anyhow::anyhow!("file not found: config.yml")); }
? 演算子の使用
Lindera の関数は LinderaResult を返すため、? 演算子で自然にエラーを伝播できます:
#![allow(unused)] fn main() { use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera::analysis::tokenizer::Tokenizer; use lindera::LinderaResult; fn analyze(text: &str) -> LinderaResult<Vec<String>> { let dictionary = load_dictionary("embedded://ipadic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let tokens = tokenizer.tokenize(text)?; Ok(tokens.iter().map(|t| t.surface.as_ref().to_string()).collect()) } }
エラーハンドリングパターン
エラー種別によるマッチング
#![allow(unused)] fn main() { use lindera::dictionary::load_dictionary; use lindera::error::LinderaErrorKind; match load_dictionary("/path/to/dictionary") { Ok(dict) => { /* 辞書を使用 */ } Err(e) if e.kind() == LinderaErrorKind::NotFound => { eprintln!("Dictionary not found: {}", e); } Err(e) if e.kind() == LinderaErrorKind::Io => { eprintln!("I/O error loading dictionary: {}", e); } Err(e) => { eprintln!("Unexpected error: {}", e); } } }
外部エラーからの変換
#![allow(unused)] fn main() { use lindera::error::LinderaErrorKind; let content = std::fs::read_to_string("config.yml") .map_err(|err| LinderaErrorKind::Io.with_error(anyhow::anyhow!(err)))?; }
APIリファレンス
APIリファレンスは以下で公開されています:
Lindera Analysis
Lindera Analysisは、lindera-segmenterクレートが提供する形態素セグメンター(Segmenter)の上に、Lucene流のテキスト解析チェーンを重ねるクレートです。通常はlinderaファサード経由でlindera::analysisとして使います(デフォルトで有効なanalysis feature)。文字フィルタ、Segmenter、トークンフィルタを1つのTokenizerパイプラインとして組み合わせ、Rustコードから組み立てることも、YAML設定ファイルだけで組み立てることもできます。
主な特徴
- 文字フィルタ: セグメンテーション前に入力テキストを変換し、バイトオフセットは元のテキストに対して自動的に補正される
- トークンフィルタ: Segmenterが生成したトークンの変換・結合・除去・並べ替えを行う
Tokenizer/TokenizerBuilder: Rustコード、またはYAMLファイル(LINDERA_CONFIG_PATH)から解析パイプライン全体を組み立てる- 日本語・韓国語・汎用のテキスト正規化をカバーする豊富な組み込みフィルタ
依存関係
ファサード経由(推奨):
[dependencies]
lindera = "7"
#![allow(unused)] fn main() { use lindera::analysis::tokenizer::Tokenizer; }
直接依存にする場合。同じクレートなので、どちらのパスも同じ型を指します。linderaとlindera-analysisのメジャーバージョンは揃えてください(v6 から v7 への移行を参照):
[dependencies]
lindera = "7"
lindera-analysis = "7"
#![allow(unused)] fn main() { use lindera_analysis::tokenizer::Tokenizer; }
目次
- 設定 --
TokenizerのYAML設定ファイル形式 - フィルタ -- すべての文字フィルタ・トークンフィルタのリファレンス
- アーキテクチャ -- 内部構造と主要コンポーネント
- APIリファレンス -- APIドキュメント
設定
LinderaはYAML形式の設定ファイルを読み込むことができます。
環境変数 LINDERA_CONFIG_PATH にファイルのパスを指定してください。Rustコードでトークナイザーの動作をコーディングすることなく、簡単に利用できます。
segmenter:
mode: "normal"
dictionary: "embedded://ipadic"
# user_dictionary: "./resources/user_dict/ipadic_simple_userdic.csv"
# keep_whitespace: false
# skip_whitespace: false # MeCab と同様に空白をラティス上で読み飛ばす。SudachiDict 以外はデフォルトでオン。false で空白ノードを残す(v7 より前の動作)
# use_mmap: false # ファイルシステム辞書(embedded:// ではない辞書)にのみ意味がある
# max_grouping_len: 24 # 未知語のグルーピング長の上限。省略または 0 で無制限
# unknown_word_ladder: true # 短い未知語候補も生成する(デフォルト: true)
# space_penalty: false # 左側空白ペナルティ(韓国語)。ルールを同梱する辞書(ko-dic)ではデフォルトでオン。false でオフ、オブジェクトで明示的なルールを指定
character_filters:
- kind: "unicode_normalize"
args:
kind: "nfkc"
- kind: "japanese_iteration_mark"
args:
normalize_kanji: true
normalize_kana: true
- kind: mapping
args:
mapping:
リンデラ: Lindera
token_filters:
- kind: "japanese_compound_word"
args:
# Merge the numeral tokens only. A counter merged into the number token keeps japanese_number from converting it.
tags:
- "名詞,数"
new_tag: "名詞,数"
- kind: "japanese_number"
args:
tags:
- "名詞,数"
- kind: "japanese_stop_tags"
args:
tags:
- "接続詞"
- "助詞"
- "助詞,格助詞"
- "助詞,格助詞,一般"
- "助詞,格助詞,引用"
- "助詞,格助詞,連語"
- "助詞,係助詞"
- "助詞,副助詞"
- "助詞,間投助詞"
- "助詞,並立助詞"
- "助詞,終助詞"
- "助詞,副助詞/並立助詞/終助詞"
- "助詞,連体化"
- "助詞,副詞化"
- "助詞,特殊"
- "助動詞"
- "記号"
- "記号,一般"
- "記号,読点"
- "記号,句点"
- "記号,空白"
- "記号,括弧閉"
- "その他,間投"
- "フィラー"
- "非言語音"
- kind: "japanese_katakana_stem"
args:
min: 3
- kind: "remove_diacritical_mark"
args:
japanese: false
Segmenter のオプション
| キー | 型 | デフォルト | 説明 |
|---|---|---|---|
mode | string | "normal" | 分割モード。"normal" または "decompose" |
dictionary | string | (必須) | 辞書 URI。例: "embedded://ipadic" |
user_dictionary | string | (なし) | ユーザー辞書のパス |
keep_whitespace | bool | false | 空白トークンを出力する。このとき空白はラティスにも残るため、他のトークンも、MeCab と同様に空白を読み飛ばすデフォルトの出力と変わることがある |
skip_whitespace | bool | (辞書の設定: true、SudachiDict は false) | keep_whitespace が false のとき、MeCab と同様に空白をラティス上で読み飛ばし、前後の語を直接接続する。false で空白を独立したノード(空白の見出し語か SPACE の未知語)としてラティスに残す(v7 より前の動作)。そのノードの連接コストがすべて 0 の ko-dic では、空白のたびに連接の文脈が途切れる。省略または null で辞書の metadata.json のデフォルトを使う。Segmenter を参照 |
use_mmap | bool | mmap feature が有効なとき on(デフォルト) | 辞書をメモリマップする。ファイルシステム辞書(embedded:// ではない辞書)にのみ意味がある |
max_grouping_len | integer | (無制限) | 未知語グルーピングの上限(先頭の 1 文字を超えた文字数)。MeCab の max-grouping-size に相当する(MeCab のデフォルトは 24)。上限は各位置で判定され、上限を超えるランはその位置ではグルーピングされず、1 文字候補(および候補ラダーと辞書語)が残る。残りの末尾は上限に収まった時点で再びグルーピングされるため、未知語トークンは最長で上限 + 1 文字になる。キーを省略するか 0 を指定すると無制限になる |
unknown_word_ladder | bool | true | MeCab や Vibrato と同様に、文字の既定の文字種の char.def の LENGTH フィールドまでの短い未知語候補も生成する。false を指定すると v6 より前と同じくこれらの候補を作らない。ただし、グルーピングした未知語の内側から始まる未知語など、その後の変更による出力の違いは残る。Segmenter を参照 |
space_penalty | bool または object | (辞書同梱のルール、あれば) | 左側空白ペナルティ(mecab-ko の left-space-penalty-factor)。直前に空白があり、先頭品詞タグが一覧にある候補にコストを加算する。省略または null で辞書が metadata.json に同梱するルールを使用(ko-dic は mecab-ko-dic のルールを同梱するため ko-dic ではデフォルトでオン。他の同梱辞書はルールを持たない)、false でオフ、true で辞書のルールを要求(同梱しない辞書ではエラー)、オブジェクト {"rules": [{"pos": [...], "cost": n}, ...]} で明示的なルールを指定。Segmenter を参照 |
% export LINDERA_CONFIG_PATH=./resources/config/lindera.yml
use std::path::PathBuf; use lindera::analysis::tokenizer::TokenizerBuilder; use lindera::LinderaResult; fn main() -> LinderaResult<()> { // 設定ファイルからトークナイザーの設定を読み込む let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../resources") .join("config") .join("lindera.yml"); let builder = TokenizerBuilder::from_file(&path)?; let tokenizer = builder.build()?; let text = "Linderaは形態素解析エンジンです。ユーザー辞書も利用可能です。".to_string(); println!("text: {text}"); let tokens = tokenizer.tokenize(&text)?; for token in tokens { println!( "token: {:?}, start: {:?}, end: {:?}, details: {:?}", token.surface, token.byte_start, token.byte_end, token.details ); } Ok(()) }
フィルタ
文字フィルタ(Character Filter)とトークンフィルタ(Token Filter)は、lindera-analysisのTokenizerパイプラインにおける前処理・後処理の2つの段階です。
- 文字フィルタはセグメンテーションの前に入力テキストを変換します。バイトオフセットは自動的に補正されるため、変換後のテキストに対して生成されたトークンでも、元のフィルタ前のテキストにおける位置が正しく報告されます。
- トークンフィルタはSegmenterが生成したトークンのリストをセグメンテーションの後に変換します。
どちらのフィルタも設定方法は共通で、kind文字列(CLIの--character-filter / --token-filterフラグでもkind:{"json": "args"}という形式で使用されます)と、フィルタ固有のパラメータを持つJSONのargsオブジェクトで構成されます。
文字フィルタ
文字フィルタはYAML設定ファイルのcharacter_filtersキーで設定します。各エントリは順番に適用され、あるフィルタの出力が次のフィルタの入力になります。
unicode_normalize
4種類の標準的なUnicode正規化形式のいずれかを使って入力テキストを正規化します。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
kind | string | はい | nfc、nfd、nfkc、nfkdのいずれか |
例:
{
"kind": "unicode_normalize",
"args": {
"kind": "nfkc"
}
}
japanese_iteration_mark
日本語の踊り字(繰り返し記号)である々、ゝ、ゞ、ヽ、ヾを、それぞれが繰り返す文字に置き換えて正規化します。ひらがな・カタカナの繰り返し記号については、必要に応じて濁点の付与・除去も行います。
パラメータ:
| パラメータ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
normalize_kanji | bool | いいえ | false | 漢字の踊り字々を正規化する |
normalize_kana | bool | いいえ | false | ひらがな・カタカナの踊り字ゝ、ゞ、ヽ、ヾを正規化する |
例:
{
"kind": "japanese_iteration_mark",
"args": {
"normalize_kanji": true,
"normalize_kana": true
}
}
mapping(文字フィルタ)
mappingのキーに一致する部分を対応する値に置き換えます。入力テキスト全体に対して、Aho-Corasickオートマトンによる最長一致検索を行います。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
mapping | object(string to string) | はい | 置換対象の部分文字列と、その置換先の対応表 |
例:
{
"kind": "mapping",
"args": {
"mapping": {
"リンデラ": "Lindera"
}
}
}
regex
正規表現にマッチした箇所をすべて、リテラルな置換文字列で置き換えます。キャプチャグループの内容は置換文字列に展開されません。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
pattern | string | はい | 正規表現(regexクレートの構文) |
replacement | string | はい | patternにマッチした箇所すべてを置き換えるリテラル文字列 |
例:
{
"kind": "regex",
"args": {
"pattern": "\\s{2,}",
"replacement": " "
}
}
トークンフィルタ
トークンフィルタはYAML設定ファイルのtoken_filtersキーで設定します。各フィルタは、Segmenterが生成したトークンリストに対して順番に適用されます。
japanese_base_form
トークンの表層形を、辞書のbase_form(またはorthographic_base_form)フィールドに登録された原形(辞書形)に置き換えます。動詞・形容詞のレンマ化(見出し語化)として機能します。未知語処理によって生成されたトークン(token.word_id.is_unknown())は変更されません。
このフィルタに設定パラメータはありません。
例:
{
"kind": "japanese_base_form"
}
japanese_compound_word
品詞タグがtagsのいずれかに一致する連続したトークンを、1つの複合語トークンに結合します。
結合後のトークンの詳細には、辞書スキーマから解決した品詞の位置に new_tag が入ります。それより前の品詞以外のフィールド(SudachiDict の display_surface)と残りのフィールドはすべて * になります。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
tags | array<string> | はい | 結合対象となるトークンを示す品詞タグ(カンマ区切りで最大4階層) |
new_tag | string | いいえ | 結合後のトークンに付与する品詞タグ。省略した場合は複合語が付与される |
例:
{
"kind": "japanese_compound_word",
"args": {
"tags": [
"名詞,数",
"名詞,接尾,助数詞"
],
"new_tag": "名詞,数"
}
}
japanese_kana
トークンテキストをひらがなとカタカナの間で相互変換します。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
kind | string | はい | "hiragana"はカタカナをひらがなに、"katakana"はひらがなをカタカナに変換する |
例:
{
"kind": "japanese_kana",
"args": {
"kind": "hiragana"
}
}
japanese_katakana_stem
カタカナのトークンの末尾にある長音記号(ー、U+30FC)を除去します。ただし、トークンの文字数がminより大きい場合のみ除去されます。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
min | 正の整数 | はい | 末尾の長音記号をステミングする対象となる、カタカナトークンの最小文字数 |
例:
{
"kind": "japanese_katakana_stem",
"args": {
"min": 3
}
}
japanese_keep_tags
品詞タグがtagsのいずれかに一致するトークンのみを保持し、それ以外を除去します。
タグは 4 階層のカンマ区切りに正規化され(不足分は * で補完)、各トークンの品詞詳細 4 つ(辞書スキーマで part_of_speech という名前のフィールドから始まる 4 つ。スキーマにそのフィールドが無い場合は先頭の詳細から)と完全一致で比較されます。IPADIC の助詞トークンは必ず 助詞,係助詞 のようにサブカテゴリを持つため、助詞 単独では一致しません。一方、助動詞はサブカテゴリを持たないため(助動詞,*,*,*)、助動詞 単独で一致します。この動作は japanese_stop_tags、japanese_compound_word、および japanese_number の tags も同様です。位置はスキーマ経由で解決されるため、SudachiDict の display_surface のように品詞の前に列があるスキーマでも同じ規則で一致します。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
tags | array<string> | はい | 保持する品詞タグ(カンマ区切りで最大4階層) |
例:
{
"kind": "japanese_keep_tags",
"args": {
"tags": [
"名詞,一般"
]
}
}
japanese_number
トークンの表層テキストに含まれる日本語の数値表現(漢数字、大字、全角数字)をアラビア数字に変換します。
トークンは、その全ての文字が数詞である場合にのみ変換されます(Lucene の JapaneseNumberFilter と同じ規則)。そのため、数詞で始まるだけの通常の語は変換されません。一部は1部にならず一部のままです。
japanese_compound_wordの後段で使う場合は、名詞,数のトークンだけを結合してください。助数詞(名詞,接尾,助数詞)を数値トークンに結合すると、そのトークンは数詞ではなくなるため、一万円は変換されずそのまま残ります。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
tags | array<string>またはnull | いいえ | 変換対象を限定する品詞タグ(カンマ区切りで最大4階層)。japanese_keep_tags と同じ規則で一致判定される。省略またはnullの場合はすべてのトークンが変換対象になる |
例:
{
"kind": "japanese_number",
"args": {
"tags": [
"名詞,数"
]
}
}
japanese_reading_form
トークンの表層テキストを、辞書のreadingフィールドに登録された読み(カタカナ)に置き換えます。未知語処理によって生成されたトークン(token.word_id.is_unknown())は変更されません。
このフィルタに設定パラメータはありません。
例:
{
"kind": "japanese_reading_form"
}
japanese_stop_tags
品詞タグがtagsのいずれかに一致するトークンを除去します。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
tags | array<string> | はい | 除去する品詞タグ(カンマ区切りで最大4階層) |
例:
{
"kind": "japanese_stop_tags",
"args": {
"tags": [
"助詞,格助詞,一般",
"助詞,係助詞",
"助詞,連体化",
"助動詞"
]
}
}
keep_words
表層テキストがwordsのいずれかに完全一致するトークンのみを保持します。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
words | array<string> | はい | 保持する表層形の一覧 |
例:
{
"kind": "keep_words",
"args": {
"words": [
"すもも",
"もも"
]
}
}
korean_compound_word
最初の品詞タグがtagsのいずれかに一致する連続した韓国語トークンを、1つの複合語トークンに結合します。結合後のトークンは先頭トークンのbyte_startとposition、末尾トークンのbyte_endを持ち、position_lengthは結合したトークン数になります。隣接トークンが一致しない単独のトークンはそのままです。
ko-dicは漢数詞を形態素単位に分割するため、이/NR 천/NR 이/NR 십/NR 육/NRをkorean_numberで変換する前に1トークンへ戻すのがこのフィルタの役割です。
| 入力 | ko-dicのトークン | korean_compound_word適用後(tags: ["SN", "NR"]、new_tag: "NR") | korean_number適用後 |
|---|---|---|---|
이천이십육년 | 이/NR 천/NR 이/NR 십/NR 육/NR 년/NNBC | 이천이십육/NR 년/NNBC | 2026 년 |
10만 | 10/SN 만/NR | 10만/NR | 100000 |
2천26 | 2/SN 천/NR 26/SN | 2천26/NR | 2026 |
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
tags | array<string> | はい | 結合対象となるトークンを示す品詞タグ(NRやSNなどko-dicの単一タグ) |
new_tag | string | いいえ | 結合後のトークンに付与する品詞タグ。他のdetailは*になる。省略した場合は복합어が付与されるが、これはko-dicのタグではないため、後続のkorean_numberやkorean_keep_tagsに結合後のトークンを渡す場合はNRなどのタグを指定する |
例:
{
"kind": "korean_compound_word",
"args": {
"tags": [
"SN",
"NR"
],
"new_tag": "NR"
}
}
既定の設定では、ko-dicは만 원や십만 원の만を数詞NRと判定するため、십만 원は십만/NR 원/NNBCになります。空白の読み飛ばしを無効にする(skip_whitespace: false)と、만の後ろの語は空白のノードに接続するため、ko-dicはそこでの만を助詞JXと判定し(만/JX 원/NNG)、["SN", "NR"]では結合されません。
korean_decompound
ko-dicの複合語・活用形・事前解析済み(Preanalysis)トークンを、expressionフィールドが示す形態素に分割します。Lucene noriのDecompoundModeと同じ動作で、隣接トークンを結合するkorean_compound_wordの逆操作にあたります。LinderaのMode::Decomposeは日本語の漢字向けに調整された長さペナルティであり、これらのフィールドを読まないため、このフィルタなしでは무궁화はどのモードでも分割されません。
| 入力 | ko-dicのトークン | mode: "discard" | mode: "mixed" |
|---|---|---|---|
무궁화꽃이 | 무궁화/NNG 꽃/NNG 이/JKS | 무궁 화 꽃 이 | 무궁화 무궁 화 꽃 이 |
가곡역 | 가곡역/NNP | 가곡 역 | 가곡역 가곡 역 |
断片のオフセットはnoriに合わせています。自身のバイト範囲を持つのはCompoundの断片だけで、各断片は自身が表すテキストの範囲を持ちます。InflectとPreanalysisの断片はトークン全体の範囲を共有します。活用形の断片は元の表層形の部分文字列とは限らないためです(갔は가 + 았に分解されます)。
Compoundトークンを分割するのは、断片をつなげると表層形とちょうど一致する場合だけで、それ以外は範囲を共有します。これには、断片が表層形そのものの文字列ではない同梱ko-dicの12項目(안지랑역 = 안지 + 역、고춧값 = 고추 + 값)が含まれます。これらではnoriのオフセットが一部の断片を別の文字列に向けてしまいます。프린터3D = 프린터 + 쓰리디のようなユーザー辞書の項目や、先行フィルタが表層形を書き換えた場合も同様です。同梱辞書のそれ以外の項目ではオフセットはnoriと一致し、どのオフセットでテキストを切り出しても安全です。
discardでは断片が複合語自身の位置から連番で並び、mixedでは複合語がその位置にposition_lengthを断片数として残り、断片が同じ位置から続きます。展開後のトークンは増えた位置の分だけ後ろにずれ、それ以外はずれないため、korean_stop_tagsなど先行フィルタが残した位置の隙間は保たれます。
展開するのは位置を1つだけ占めるトークンに限るため、mixedが残した複合語が再び分割されることはありません。それでもフィルタはチェーンに一度だけ入れてください。mixedが残した断片1つの項目は位置を1つしか占めないためです。korean_compound_wordと組み合わせる場合は結合を先に置いてください。結合はバイト範囲の重なるトークンを繋がないため逆順でも壊れませんが、結合を先にするとセグメンターが出したトークンそのものを対象にできます。
断片は自身の品詞タグと意味分類を持ち、複合語自体を説明するフィールド(type、expression、first_part_of_speech、last_part_of_speech、reading、presence_absence)は空になります。読みを空にするのはnoriと同じ挙動で、noriのDecompoundToken.getReading()はnullを返します。korean_reading_formは*をスキップするため、断片は自身の表層形を保ちます。
スキーマにtype/expressionフィールドがない辞書に適用した場合、フィルタは設定されたフィルタごとに一度だけ警告を出し、すべてのトークンをそのまま通過させます。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
mode | string | いいえ | "discard"(デフォルト、noriと同じ)は複合語を断片で置き換え、"mixed"は複合語を残したうえで断片も出力します |
types | array<string> | いいえ | 分解対象のtype値(大文字と小文字を区別)。デフォルトは["Compound", "Inflect", "Preanalysis"]。空のリストやそれ以外の値はエラーになります |
例:
{
"kind": "korean_decompound",
"args": {
"mode": "discard",
"types": [
"Compound",
"Inflect",
"Preanalysis"
]
}
}
korean_keep_tags
最初の品詞タグがtagsのいずれかに一致する韓国語トークンのみを保持します。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
tags | array<string> | はい | 保持する品詞タグ |
例:
{
"kind": "korean_keep_tags",
"args": {
"tags": [
"NNG"
]
}
}
korean_number
トークンの表層テキストに含まれる漢数詞由来の韓国語の数値表現(ハングル数字、漢数字、全角数字)をアラビア数字に変換します。固有語数詞(하나、둘、열、스물など)は位取りの構造を持たないため変換しません。
変換はトークンごとに独立して行われます。ko-dicは漢数詞を形態素単位に分割するため、1つの数を構成するトークン群が1つのアラビア数字にまとまるわけではありません。
| 入力 | ko-dicのトークン | korean_number適用後 |
|---|---|---|
이천이십육년 | 이/NR 천/NR 이/NR 십/NR 육/NR 년/NNBC | 2 1000 2 10 6 년 |
10만 | 10/SN 만/NR | 10 10000 |
IPADICに対するjapanese_compound_wordのように、変換前にトークンを結合するにはkorean_compound_wordをこのフィルタの前に置きます。結合後のトークンを変換対象にするため、new_tagにはこのフィルタのtagsに含まれるタグを指定します。
[
{"kind": "korean_compound_word", "args": {"tags": ["SN", "NR"], "new_tag": "NR"}},
{"kind": "korean_number", "args": {"tags": ["SN", "NR"]}}
]
このチェーンで이천이십육년は2026 년、10만は100000、2천26は2026になります。漢字表記の二千二十六/SHのように数値全体が1トークンの場合は、このフィルタ単体でそのまま変換されます。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
tags | array<string>またはnull | いいえ | 変換対象を限定する品詞タグ。省略した場合は["SN", "NR"]、nullの場合はすべてのトークンが変換対象になる |
トークンは、その全ての文字が数詞である場合にのみ変換されます(Lucene の KoreanNumberFilter と同じ規則)。これにより固有語数詞が保護されます。일곱(7)は漢数詞の일で始まるため、1文字ずつ変換すると1곱になってしまいます。
ハングルの数詞は他の形態素とも同形で(이は主格助詞、만は補助詞)、これらは1文字なので全文字一致の規則では区別できません。そのため既定では数詞の品詞タグに限定しています。
漢数字も変換する場合はSHを追加してください。対応は2つの意味で部分的です。ko-dicは漢字語全体をNNGと判定するため參加や萬歲は変換されず、一方で単語に結び付かない単独の漢字はSHとなるため、十字架は10 字 架、百貨店は100 貨 店になります。
例:
{
"kind": "korean_number",
"args": {
"tags": [
"SN",
"NR"
]
}
}
korean_reading_form
トークンの表層テキストを、辞書のreadingフィールドに登録された読みに置き換えます。未知語処理によって生成されたトークン(token.word_id.is_unknown())は変更されません。
このフィルタに設定パラメータはありません。
例:
{
"kind": "korean_reading_form"
}
korean_stop_tags
最初の品詞タグがtagsのいずれかに一致する韓国語トークンを除去します。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
tags | array<string> | はい | 除去する品詞タグ |
例:
{
"kind": "korean_stop_tags",
"args": {
"tags": [
"EP",
"EF",
"JKG"
]
}
}
length
表層テキストの文字数が[min, max]の範囲に収まるトークンのみを保持します。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
min | 符号なし整数 | いいえ | 最小文字数(この値を含む) |
max | 符号なし整数 | いいえ | 最大文字数(この値を含む) |
例:
{
"kind": "length",
"args": {
"min": 2,
"max": 3
}
}
lowercase
トークンの表層テキストを小文字に変換します。
このフィルタに設定パラメータはありません。
例:
{
"kind": "lowercase"
}
mapping(トークンフィルタ)
mappingのキーに一致する部分を、各トークンの表層テキスト内で対応する値に置き換えます。Aho-Corasickオートマトンによる最長一致検索を使用します。文字フィルタのmappingのトークン版に相当します。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
mapping | object(string to string) | はい | 置換対象の部分文字列と、その置換先の対応表 |
例:
{
"kind": "mapping",
"args": {
"mapping": {
"籠": "篭"
}
}
}
remove_diacritical_mark
トークンの表層テキストからダイアクリティカルマーク(発音区別符号)を除去し、その後テキストの元のUnicode正規化形式を再適用します。
パラメータ:
| パラメータ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
japanese | bool | いいえ | false | 日本語の濁点・半濁点の結合文字(分解済みの濁音・半濁音仮名に含まれるものなど)も除去する |
例:
{
"kind": "remove_diacritical_mark",
"args": {
"japanese": false
}
}
stop_words
表層テキストがwordsのいずれかに完全一致するトークンを除去します。
パラメータ:
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
words | array<string> | はい | 除去する表層形の一覧 |
例:
{
"kind": "stop_words",
"args": {
"words": [
"も",
"の"
]
}
}
uppercase
トークンの表層テキストを大文字に変換します。
このフィルタに設定パラメータはありません。
例:
{
"kind": "uppercase"
}
YAML設定
文字フィルタとトークンフィルタは、Segmenterと一緒に1つのYAMLファイルで設定します。ファイル全体の形式は設定を参照してください。関連する部分だけを抜粋すると次のようになります。
character_filters:
- kind: "unicode_normalize"
args:
kind: "nfkc"
- kind: "japanese_iteration_mark"
args:
normalize_kanji: true
normalize_kana: true
token_filters:
- kind: "japanese_stop_tags"
args:
tags:
- "助詞,格助詞,一般"
- "助詞,係助詞"
- "助詞,連体化"
- "助動詞"
- kind: "japanese_katakana_stem"
args:
min: 3
- kind: "lowercase"
- kind: "length"
args:
min: 2
Rust API
文字フィルタとトークンフィルタは、プログラムから作成・適用することもできます。
use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera::analysis::character_filter::BoxCharacterFilter; use lindera::analysis::character_filter::unicode_normalize::{ UnicodeNormalizeCharacterFilter, UnicodeNormalizeKind, }; use lindera::analysis::token_filter::BoxTokenFilter; use lindera::analysis::token_filter::japanese_stop_tags::JapaneseStopTagsTokenFilter; use lindera::analysis::token_filter::japanese_katakana_stem::JapaneseKatakanaStemTokenFilter; 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 mut tokenizer = Tokenizer::new(segmenter); // 文字フィルタを追加 let normalize_filter = UnicodeNormalizeCharacterFilter::new(UnicodeNormalizeKind::NFKC); tokenizer.append_character_filter(BoxCharacterFilter::from(normalize_filter)); // トークンフィルタを追加 let stop_tags_filter = JapaneseStopTagsTokenFilter::new( vec![ "助詞,格助詞,一般".to_string(), "助詞,係助詞".to_string(), "助詞,連体化".to_string(), "助動詞".to_string(), ] .into_iter() .collect(), ); tokenizer.append_token_filter(BoxTokenFilter::from(stop_tags_filter)); let katakana_stem_filter = JapaneseKatakanaStemTokenFilter::new(std::num::NonZeroUsize::new(3).unwrap()); tokenizer.append_token_filter(BoxTokenFilter::from(katakana_stem_filter)); // フィルタを適用してトークナイズ let tokens = tokenizer.tokenize("Linderaは形態素解析エンジンです。")?; for token in tokens { println!( "token: {:?}, details: {:?}", token.surface, token.details ); } Ok(()) }
append_character_filterとappend_token_filterメソッドは、フィルタを追加した順番で登録します。文字フィルタはセグメンテーション前のテキストに対して順次適用され、トークンフィルタはセグメンテーション後のトークンリストに対して順次適用されます。
アーキテクチャ
モジュール構成
lindera-analysis/src/
├── lib.rs # パブリックAPIの再エクスポート、CLIフラグ解析ヘルパー
├── character_filter.rs # CharacterFilter trait、OffsetMapping、CharacterFilterLoader
├── character_filter/
│ ├── unicode_normalize.rs # Unicode正規化(NFC/NFD/NFKC/NFKD)
│ ├── japanese_iteration_mark.rs # 日本語の踊り字(繰り返し記号)正規化
│ ├── mapping.rs # マッピングによるテキスト置換
│ └── regex.rs # 正規表現によるテキスト置換
├── token_filter.rs # TokenFilter trait、TokenFilterLoader
├── token_filter/
│ ├── compound.rs # 複合語フィルタが共有する結合ヘルパー(非公開)
│ ├── japanese_base_form.rs
│ ├── japanese_compound_word.rs
│ ├── japanese_kana.rs
│ ├── japanese_katakana_stem.rs
│ ├── japanese_keep_tags.rs
│ ├── japanese_number.rs
│ ├── japanese_reading_form.rs
│ ├── japanese_stop_tags.rs
│ ├── keep_words.rs
│ ├── korean_compound_word.rs
│ ├── korean_decompound.rs
│ ├── korean_keep_tags.rs
│ ├── korean_number.rs
│ ├── korean_reading_form.rs
│ ├── korean_stop_tags.rs
│ ├── length.rs
│ ├── lowercase.rs
│ ├── mapping.rs
│ ├── numeral.rs # 数値フィルタが共有する変換エンジン(非公開)
│ ├── remove_diacritical_mark.rs
│ ├── stop_words.rs
│ ├── tags.rs # keep/stop系タグフィルタが共有するヘルパー(非公開)
│ └── uppercase.rs
└── tokenizer.rs # Tokenizer、TokenizerBuilder
主要コンポーネント
CharacterFilter
セグメンテーション前にテキストを前処理するフィルタのtraitです。各実装はname()と、textをその場で書き換えて実施した変換内容をOffsetMappingとして返すapply(&self, text: &mut String) -> LinderaResult<OffsetMapping>を提供します。
OffsetMapping(Transformationレコードのリストから構築される)により、複数のフィルタを順に適用した後でも、Tokenizerはフィルタ後のテキストに対して計算されたトークンのバイトオフセットを、元の入力テキストにおけるバイトオフセットへ変換できます。BoxCharacterFilterは任意のCharacterFilter実装をボックス化・クローン可能なトレイトオブジェクトとしてラップし、CharacterFilterLoaderはkind文字列とserde_json::Valueの引数からフィルタを構築します(YAML設定の読み込みとCLIフラグ解析の両方で利用されます)。
TokenFilter
Segmenterが生成したトークンを後処理するフィルタのtraitです。各実装はname()と、トークンをその場で変換・結合・並べ替え・除去するapply(&self, tokens: &mut Vec<Token<'_>>) -> LinderaResult<()>を提供します。BoxTokenFilterは任意のTokenFilter実装をボックス化・クローン可能なトレイトオブジェクトとしてラップし、TokenFilterLoaderはCharacterFilterLoaderと同様に、kind文字列とserde_json::Valueの引数からフィルタを構築します。
Tokenizer / TokenizerBuilder
Tokenizerは、文字フィルタ、lindera::segmenter::Segmenter、トークンフィルタを1つの解析パイプラインとして組み合わせます。tokenizeを呼び出すと、入力テキストに文字フィルタを適用し、フィルタ後のテキストをセグメンテーションし、得られたトークンにトークンフィルタを適用したうえで、記録済みのOffsetMappingを使って各トークンのバイトオフセットを元のテキストに対する値へ補正します。
TokenizerBuilderはTokenizerConfig(serde_json::Value)からTokenizerを組み立てます。この設定はプログラムから直接構築することも、YAMLファイルから読み込むこと(TokenizerBuilder::from_file、または環境変数LINDERA_CONFIG_PATH経由で自動的に読み込むTokenizerBuilder::new)も、set_segmenter_mode・set_segmenter_dictionary・append_character_filter・append_token_filterで段階的に組み立てることもできます。YAMLファイルの形式は設定を、フィルタの完全なリファレンスはフィルタを参照してください。
TokenizerBuilder::buildは、設定が指す辞書を読み込みます。TokenizerBuilder::build_with_dictionaries(とTokenizer::from_config_with_dictionaries)は、代わりにバイト列から読み込んだ辞書などの読み込み済みの辞書を受け取り、フィルタを含む設定のほかの項目をすべてその辞書に適用します。渡した辞書が設定の辞書をどう置き換えるかはConfig からの構築を参照してください。WASMバインディングのTokenizerBuilderは、setDictionaryInstance()とsetUserDictionaryInstance()で設定した辞書を渡して、この方法でトークナイザーをビルドします。
AnalysisWorker
AnalysisWorker(Tokenizer::new_workerまたはTokenizer::into_workerで作成)は、解析チェーン全体に対する再利用可能なセッションです。呼び出しごとのバッファ — Viterbiラティスとバックトレース用スクラッチ(SegmentWorker経由)、文字フィルタが操作する正規化テキストバッファ、オフセットマッピング用スクラッチ — をすべて所有するため、tokenizeを繰り返し呼び出してもTokenizer::tokenizeが支払う呼び出しごとのアロケーションを回避できます。文字フィルタが設定されている場合、トークンのsurfaceはトークンごとのStringにコピーされる代わりにワーカーのバッファを借用します。返されるトークンはワーカーを借用するため、次の呼び出しの前に消費する必要があります。マルチスレッドで使う場合はスレッドごとにワーカーを作成してください(あるいはlindera-bindingのようにMutexで保護します)。基盤となるSegmentWorkerと自動メモリ縮小ポリシーについてはSegmenterのページを参照してください。
AnalysisWorkerの主なpublicメソッド:
tokenize(&mut self, text: &str)— ワーカーの内部バッファを再利用しながら、解析チェーン全体を通してtextをトークナイズします。同じ入力・設定であればTokenizer::tokenizeとまったく同じトークンを返します。tokenize_nbest(&mut self, text: &str, n, unique, cost_threshold)— ワーカーの内部バッファを再利用しながら、コスト付きの上位N件の結果をトークナイズして返します。Tokenizer::tokenize_nbestとまったく同じ結果を返します。set_mode(&mut self, mode: Mode)— 以降の呼び出しで使用するセグメンテーションモードを設定します。set_keep_whitespace(&mut self, keep: bool)— 以降の呼び出しで空白トークンを出力に残すかどうかを設定します。残した空白はラティスにも残るため、他のトークンも変わることがあります。set_skip_whitespace(&mut self, skip: bool)— 空白トークンを残さないとき、以降の呼び出しで空白をラティス上で読み飛ばすかどうかを設定します(デフォルトは辞書の設定)。set_max_grouping_len(&mut self, max_grouping_len: Option<usize>)・set_unknown_word_ladder(&mut self, unknown_word_ladder: bool)— 以降の呼び出しで使う未知語グルーピングの上限と候補ラダーを設定します。set_space_penalty(&mut self, space_penalty: Option<SpacePenaltyConfig>)・set_space_penalty_from_dictionary(&mut self)— 以降の呼び出しで使う左側空白ペナルティのルールを設定するか、辞書のルールに戻します。shrink_to(&mut self, text_len_hint: usize)— ワーカーの内部バッファを、text_len_hintバイトの入力に必要なサイズまで直ちに縮小します。reset(&mut self)— すべての内部バッファを破棄し、新しいバッファに置き換えます。(例えばワーカーを保持するMutexがパニックでpoisonedになった場合の)リカバリ用途を想定しており、設定(辞書・フィルタ・モード)は保持されます。
Feature フラグ
| Feature | 説明 | デフォルト |
|---|---|---|
embed-ipadic | IPADIC辞書をバイナリに埋め込む(lindera/embed-ipadicへ委譲) | No |
embed-ipadic-neologd | IPADIC-NEologd辞書をバイナリに埋め込む(lindera/embed-ipadic-neologdへ委譲) | No |
embed-unidic | UniDic辞書をバイナリに埋め込む(lindera/embed-unidicへ委譲) | No |
embed-sudachidict | SudachiDict辞書をバイナリに埋め込む(lindera/embed-sudachidictへ委譲) | No |
embed-ko-dic | ko-dic辞書をバイナリに埋め込む(lindera/embed-ko-dicへ委譲) | No |
embed-cc-cedict | CC-CEDICT辞書をバイナリに埋め込む(lindera/embed-cc-cedictへ委譲) | No |
embed-jieba | Jieba辞書をバイナリに埋め込む(lindera/embed-jiebaへ委譲) | No |
APIリファレンス
APIリファレンスは以下で公開されています:
Lindera CLI
Lindera のための形態素解析コマンドラインインターフェースです。
- インストール - CLIのインストールまたはビルド
- コマンド - list、tokenize、build、download、train、export のコマンドリファレンス
- チュートリアル - はじめの一歩のステップバイステップガイド
インストール
cargo経由でインストール
cargo経由でバイナリをインストールできます:
% cargo install lindera-cli
GitHub Releasesからダウンロード
または、以下のリリースページからビルド済みバイナリをダウンロードすることもできます:
辞書の入手
Lindera はバイナリに辞書を同梱していません。最も簡単な入手方法は download サブコマンドで、CLI と同じバージョンのビルド済み辞書を取得して OS 標準のアプリケーションデータディレクトリにインストールします:
% lindera download ipadic
ダウンロード後は辞書名で参照できます:
% echo "関西国際空港限定トートバッグ" | lindera tokenize --dict ipadic
利用可能な辞書名と保存場所についてはコマンドを参照してください。
または、GitHub Releases ページからビルド済み辞書を手動でダウンロードすることもできます:
# 例: IPADIC 辞書のダウンロードと展開
% curl -LO https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip
% unzip lindera-ipadic-<version>.zip -d /path/to/ipadic
CLI 使用時に展開した辞書パスを指定します(アーカイブには lindera-ipadic ディレクトリが含まれる点に注意):
% echo "関西国際空港限定トートバッグ" | lindera tokenize --dict /path/to/ipadic/lindera-ipadic
ソースからビルド
辞書なしでビルド(デフォルト)
辞書を埋め込まず、トークナイザーとトレーナーのみを含むバイナリをビルドします:
% cargo build --release
全機能を含めてビルド
% cargo build --release --all-features
辞書埋め込みビルド(上級者向け)
辞書をバイナリに直接埋め込みたい上級者向けに、embed-* feature フラグを使用できます。実行時の外部辞書ファイルが不要になりますが、バイナリサイズが増加します。
IPADIC(日本語辞書)
% cargo build --release --features=embed-ipadic
IPADIC NEologd(日本語辞書)
% cargo build --release --features=embed-ipadic-neologd
UniDic(日本語辞書)
% cargo build --release --features=embed-unidic
SudachiDict(日本語辞書)
% cargo build --release --features=embed-sudachidict
ko-dic(韓国語辞書)
% cargo build --release --features=embed-ko-dic
CC-CEDICT(中国語辞書)
% cargo build --release --features=embed-cc-cedict
Jieba(中国語辞書)
% cargo build --release --features=embed-jieba
[!TIP]
embed-*feature フラグ付きでビルドした後、embedded://スキームで埋め込み辞書をロードできます:% echo "関西国際空港限定トートバッグ" | lindera tokenize --dict embedded://ipadic詳細は Feature フラグ を参照してください。
コマンド
Lindera CLI は6つのメインコマンドを提供します:
- list - 形態素解析辞書の一覧と状態を表示
- tokenize - テキストに対して形態素解析を実行
- build - ソースCSVファイルから辞書をビルド
- download - GitHub リリースページから学習済み辞書をダウンロード
- train - 注釈付きコーパスデータからCRFモデルを学習
- export - 学習済みモデルを辞書フォーマットにエクスポート
警告とログメッセージ
どのコマンドも、結果を標準出力に、Lindera のライブラリが出す警告を標準エラー出力に書きます。たとえば tokenize は、文の区切り文字のないテキストが 32 KiB を超えると、そこで文を区切り(文分割を参照)、警告を出します:
% head -c 40000 /dev/zero | tr '\0' a | lindera tokenize --dict embedded://ipadic -o wakati > /dev/null
[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 で、出すメッセージを選べます。書式は env_logger のものです:
- 指定なし: 警告とエラー
RUST_LOG=error: エラーだけ。警告は出しません。RUST_LOG=offはログメッセージを一切出しませんRUST_LOG=debug: デバッグ用のメッセージも出します。たとえばlindera buildが読み書きするファイル
この設定で標準出力は変わりません。また、失敗したコマンドのエラーは設定にかかわらず出します。標準エラー出力が端末のときはメッセージに色を付けます。RUST_LOG_STYLE=never で色を消せます。
list
既知の形態素解析辞書の一覧を表示し、各辞書について(embed-* feature フラグ経由で)バイナリに埋め込まれているか、および lindera download で学習済み辞書がローカルにダウンロード済みかを表示します。
list パラメータ
このコマンドは引数を取りません。環境変数 LINDERA_DATA_DIR を設定すると、ダウンロード済み辞書を探すアプリケーションデータディレクトリを上書きできます(lindera download と同じ挙動です)。
list の使用方法
% lindera list
NAME EMBEDDED DOWNLOADED PATH
ipadic yes yes /home/user/.local/share/lindera/dictionaries/5.1.0/lindera-ipadic
ipadic-neologd no no -
unidic no incomplete /home/user/.local/share/lindera/dictionaries/5.1.0/lindera-unidic
sudachidict no no -
ko-dic no no -
cc-cedict no no -
jieba no no -
出力は1行につき1辞書です:
EMBEDDEDは、ビルド時に辞書が埋め込まれている場合(例:--features=embed-ipadic)にyesになります。DOWNLOADEDは、実行中の CLI バージョンに対応するlindera downloadでインストールされた完全な辞書が存在する場合にyes、インストールディレクトリは存在するが必要なファイルが欠けている場合にincompleteになります(lindera download <name> --forceで再ダウンロードしてください)。PATHは、インストールディレクトリが存在する場合はそのパス、存在しない場合は-を表示します。
tokenize
様々な辞書を使用して、日本語、中国語、または韓国語のテキストに対して形態素解析(トークナイズ)を行います。
パラメータ
--dict/-d: 辞書のパス、URI、またはダウンロード済み辞書名(必須)- ファイルパス:
/path/to/dictionary - 埋め込み:
embedded://ipadic,embedded://unidic, etc. - ダウンロード済み辞書名:
ipadic,unidicなど。lindera downloadでインストールした辞書に解決されます。同名のファイルパスが実在する場合はパスが優先されます。
- ファイルパス:
--output/-o: 出力形式 (デフォルト: mecab)mecab: 品詞情報を含むMeCab互換形式wakati: スペース区切りのトークンのみ。入力 1 行につき 1 行(トークンのない行は空行)json: すべてのトークン情報を含む詳細なJSON形式
--user-dict/-u: ユーザー辞書のパス(オプション)。文脈 ID がシステム辞書の接続コスト行列の範囲外にある見出し語はエラーになります(文脈 ID を参照)。--mode/-m: トークナイズモード (デフォルト: normal)normal: 標準的なトークナイズdecompose: 複合語を分解する
--char-filter/-c: 文字フィルタ設定 (JSON)--token-filter/-t: トークンフィルタ設定 (JSON)--keep-whitespace: 空白文字のトークンを出力に含める。このとき空白はラティスにも残るため、他のトークンもデフォルトの出力と変わることがある(デフォルトでは空白は出力から除去され、辞書のmetadata.jsonで無効にしていない限り(SudachiDict は無効)、MeCab と同様にラティス上で読み飛ばされる)--disable-skip-whitespace: 空白を読み飛ばさず、独立したノード(空白の見出し語かSPACEの未知語)としてラティスに残す(出力からは引き続き除去する。v7 より前の動作)。そのノードの連接コストがすべて 0 の ko-dic では、空白のたびに連接の文脈が途切れる。読み飛ばしは、metadata.jsonで無効にしていない辞書(SudachiDict は無効)ではデフォルトで有効。--keep-whitespaceと併用した場合は効果がない。Segmenter のドキュメントを参照--max-grouping-len: 未知語グルーピングの上限(先頭を除いた文字数。MeCab のmax-grouping-sizeに相当し、MeCab のデフォルトは 24)。上限は各位置で判定され、ラン(MeCab と同じく、どの文字も直前の文字とchar.defの文字種を共有する連続)が上限を超える位置ではグルーピング候補を出さず、1 文字候補(および候補ラダーと辞書語)が残ります。残りの末尾は上限に収まった時点で再びグルーピングされるため、未知語トークンは最長で上限 + 1 文字になります。0または省略: 無制限。例は Segmenter のドキュメントを参照--disable-unknown-word-ladder: MeCab/Vibrato 由来の未知語候補ラダー(char.defのLENGTHフィールド)を無効化する。デフォルトで有効。無効にすると、v6 より前と同じくこれらの候補を作らない。ただし、グルーピングした未知語の内側から始まる未知語など、その後の変更による出力の違いは残る。Segmenter のドキュメントを参照--disable-space-penalty: 辞書のmetadata.jsonに同梱されたルールでデフォルト有効になる左側空白ペナルティ(mecab-ko のleft-space-penalty-factor。ko-dic は mecab-ko-dic のルールを同梱)を無効にする。直前に空白があり、先頭品詞タグが一覧にある候補にコストを加算する機能で、--disable-skip-whitespaceと併せて無効にすると、空白まわりの韓国語の解析について v6.0 以降に入った 2 つの変更(このペナルティと空白の読み飛ばし)がどちらもオフになる。ただし、."のような記号の連続が分かれるなど、それ以外の出力の変化は残る。Segmenter のドキュメントを参照--space-penalty-rules: 辞書のルールの代わりに、明示的な左側空白ペナルティのルールを JSON で指定する。例:'{"rules":[{"pos":["JKS","JX"],"cost":6000}]}'。--disable-space-penaltyとは同時に指定できない--mmap: 辞書ディレクトリの単語リストにメモリマップドファイル読み込みを使用する。embedded://辞書、およびmmapfeature が無効な場合は無視される。プロセスがマップを保持している間に辞書ディレクトリを再ビルド・切り詰めると、次回のルックアップで SIGBUS を引き起こす可能性がある。--nbest/-N: 返す N-best 結果の数(デフォルト: 1)。2以上に設定すると N-best 出力が有効になります。--nbest-unique: 同じ分割を生成するパスの重複を排除します。各結果は、その単語境界のパスのうち最もコストの低いものです。--nbest-cost-threshold: 最良の結果からの最大コスト差。best_cost + threshold以内のコストを持つ結果のみが返されます。どちらのコストも入力行全体の合計です。- 入力ファイル: オプションのファイルパス (デフォルト: 標準入力)。入力は 1 行ずつ読み込まれ、行ごとに解析されます。取り除くのは行末の改行コード(
\nまたは\r\n)だけで、行頭・行末の空白も行の途中と同じに扱います(SPACEの文字である半角空白とタブは読み飛ばして出力から除き、U+3000 は IPADIC・IPADIC-NEologd・UniDic ではトークンになります)。JSON 出力のバイトオフセットは入力行の中の位置を指します。MeCab は CRLF の行の\rも未知語として出力しますが、Lindera は取り除きます
基本的な使用方法
# 辞書ディレクトリを指定してトークナイズ
echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict /path/to/dictionary
# 埋め込み辞書を指定してトークナイズ
echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://ipadic
# 出力形式を指定してトークナイズ
echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://ipadic \
--output json
# ファイルからテキストを読み込んでトークナイズ
lindera tokenize \
--dict /path/to/dictionary \
--output wakati \
input.txt
外部辞書を使用した例
外部IPADIC(日本語辞書)を使用したトークナイズ
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict /tmp/lindera-ipadic-2.7.0-20250920
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素 名詞,一般,*,*,*,*,形態素,ケイタイソ,ケイタイソ
解析 名詞,サ変接続,*,*,*,*,解析,カイセキ,カイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
外部IPADIC NEologd(日本語辞書)を使用したトークナイズ
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict /tmp/lindera-ipadic-neologd-0.0.7-20200820
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素解析 名詞,固有名詞,一般,*,*,*,形態素解析,ケイタイソカイセキ,ケイタイソカイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
外部UniDic(日本語辞書)を使用したトークナイズ
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict /tmp/lindera-unidic-2.1.2
日本 名詞,固有名詞,地名,国,*,*,ニッポン,日本,日本,ニッポン,日本,ニッポン,固,*,*,*,*
語 名詞,普通名詞,一般,*,*,*,ゴ,語,語,ゴ,語,ゴ,漢,*,*,*,*
の 助詞,格助詞,*,*,*,*,ノ,の,の,ノ,の,ノ,和,*,*,*,*
形態 名詞,普通名詞,一般,*,*,*,ケイタイ,形態,形態,ケータイ,形態,ケータイ,漢,*,*,*,*
素 接尾辞,名詞的,一般,*,*,*,ソ,素,素,ソ,素,ソ,漢,*,*,*,*
解析 名詞,普通名詞,サ変可能,*,*,*,カイセキ,解析,解析,カイセキ,解析,カイセキ,漢,*,*,*,*
を 助詞,格助詞,*,*,*,*,ヲ,を,を,オ,を,オ,和,*,*,*,*
行う 動詞,一般,*,*,五段-ワア行,連体形-一般,オコナウ,行う,行う,オコナウ,行う,オコナウ,和,*,*,*,*
こと 名詞,普通名詞,一般,*,*,*,コト,事,こと,コト,こと,コト,和,コ濁,基本形,*,*
が 助詞,格助詞,*,*,*,*,ガ,が,が,ガ,が,ガ,和,*,*,*,*
でき 動詞,非自立可能,*,*,上一段-カ行,連用形-一般,デキル,出来る,でき,デキ,できる,デキル,和,*,*,*,*
ます 助動詞,*,*,*,助動詞-マス,終止形-一般,マス,ます,ます,マス,ます,マス,和,*,*,*,*
。 補助記号,句点,*,*,*,*,,。,。,,。,,記号,*,*,*,*
EOS
外部ko-dic(韓国語辞書)を使用したトークナイズ
% echo "한국어의형태해석을실시할수있습니다." | lindera tokenize \
--dict /tmp/lindera-ko-dic-2.1.1-20180720
한국어 NNG,*,F,한국어,Compound,*,*,한국/NNG/*+어/NNG/*
의 JKG,*,F,의,*,*,*,*
형태 NNG,*,F,형태,*,*,*,*
해석 NNG,행위,T,해석,*,*,*,*
을 JKO,*,T,을,*,*,*,*
실시 NNG,행위,F,실시,*,*,*,*
할 XSV+ETM,*,T,할,Inflect,XSV,ETM,하/XSV/*+ᆯ/ETM/*
수 NNB,*,F,수,*,*,*,*
있 VV,*,T,있,*,*,*,*
습니다 EF,*,F,습니다,*,*,*,*
. SF,*,*,*,*,*,*,*
EOS
外部CC-CEDICT(中国語辞書)を使用したトークナイズ
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict /tmp/lindera-cc-cedict-0.1.0-20200409
可以 *,*,*,*,ke3 yi3,可以,可以,can/may/possible/able to/not bad/pretty good/
进行 *,*,*,*,jin4 xing2,進行,进行,to advance/to conduct/underway/in progress/to do/to carry out/to carry on/to execute/
中文 *,*,*,*,Zhong1 wen2,中文,中文,Chinese language/
形态学 *,*,*,*,xing2 tai4 xue2,形態學,形态学,morphology (in biology or linguistics)/
分析 *,*,*,*,fen1 xi1,分析,分析,to analyze/analysis/CL:個|个[ge4]/
。 *,*,*,*,*,*,*,*
EOS
外部Jieba(中国語辞書)を使用したトークナイズ
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict /tmp/lindera-jieba-0.1.1
埋め込み辞書を使用した例
Linderaは、特定の機能フラグを指定してビルドすることで、バイナリに辞書を直接含めることができます。これにより、外部辞書ファイルなしでトークナイズが可能になります。
埋め込みIPADIC(日本語辞書)を使用したトークナイズ
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://ipadic
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素 名詞,一般,*,*,*,*,形態素,ケイタイソ,ケイタイソ
解析 名詞,サ変接続,*,*,*,*,解析,カイセキ,カイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
注意: IPADIC辞書をバイナリに含めるには、--features=embed-ipadic オプションを使用してビルドする必要があります。
埋め込みUniDic(日本語辞書)を使用したトークナイズ
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://unidic
日本 名詞,固有名詞,地名,国,*,*,ニッポン,日本,日本,ニッポン,日本,ニッポン,固,*,*,*,*
語 名詞,普通名詞,一般,*,*,*,ゴ,語,語,ゴ,語,ゴ,漢,*,*,*,*
の 助詞,格助詞,*,*,*,*,ノ,の,の,ノ,の,ノ,和,*,*,*,*
形態 名詞,普通名詞,一般,*,*,*,ケイタイ,形態,形態,ケータイ,形態,ケータイ,漢,*,*,*,*
素 接尾辞,名詞的,一般,*,*,*,ソ,素,素,ソ,素,ソ,漢,*,*,*,*
解析 名詞,普通名詞,サ変可能,*,*,*,カイセキ,解析,解析,カイセキ,解析,カイセキ,漢,*,*,*,*
を 助詞,格助詞,*,*,*,*,ヲ,を,を,オ,を,オ,和,*,*,*,*
行う 動詞,一般,*,*,五段-ワア行,連体形-一般,オコナウ,行う,行う,オコナウ,行う,オコナウ,和,*,*,*,*
こと 名詞,普通名詞,一般,*,*,*,コト,事,こと,コト,こと,コト,和,コ濁,基本形,*,*
が 助詞,格助詞,*,*,*,*,ガ,が,が,ガ,が,ガ,和,*,*,*,*
でき 動詞,非自立可能,*,*,上一段-カ行,連用形-一般,デキル,出来る,でき,デキ,できる,デキル,和,*,*,*,*
ます 助動詞,*,*,*,助動詞-マス,終止形-一般,マス,ます,ます,マス,ます,マス,和,*,*,*,*
。 補助記号,句点,*,*,*,*,,。,。,,。,,記号,*,*,*,*
EOS
注意: UniDic辞書をバイナリに含めるには、--features=embed-unidic オプションを使用してビルドする必要があります。
埋め込みIPADIC NEologd(日本語辞書)を使用したトークナイズ
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://ipadic-neologd
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素解析 名詞,固有名詞,一般,*,*,*,形態素解析,ケイタイソカイセキ,ケイタイソカイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
注意: IPADIC NEologd辞書をバイナリに含めるには、--features=embed-ipadic-neologd オプションを使用してビルドする必要があります。
埋め込みko-dic(韓国語辞書)を使用したトークナイズ
% echo "한국어의형태해석을실시할수있습니다." | lindera tokenize \
--dict embedded://ko-dic
한국어 NNG,*,F,한국어,Compound,*,*,한국/NNG/*+어/NNG/*
의 JKG,*,F,의,*,*,*,*
형태 NNG,*,F,형태,*,*,*,*
해석 NNG,행위,T,해석,*,*,*,*
을 JKO,*,T,을,*,*,*,*
실시 NNG,행위,F,실시,*,*,*,*
할 XSV+ETM,*,T,할,Inflect,XSV,ETM,하/XSV/*+ᆯ/ETM/*
수 NNB,*,F,수,*,*,*,*
있 VV,*,T,있,*,*,*,*
습니다 EF,*,F,습니다,*,*,*,*
. SF,*,*,*,*,*,*,*
EOS
注意: ko-dic辞書をバイナリに含めるには、--features=embed-ko-dic オプションを使用してビルドする必要があります。
埋め込みCC-CEDICT(中国語辞書)を使用したトークナイズ
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict embedded://cc-cedict
可以 *,*,*,*,ke3 yi3,可以,可以,can/may/possible/able to/not bad/pretty good/
进行 *,*,*,*,jin4 xing2,進行,进行,to advance/to conduct/underway/in progress/to do/to carry out/to carry on/to execute/
中文 *,*,*,*,Zhong1 wen2,中文,中文,Chinese language/
形态学 *,*,*,*,xing2 tai4 xue2,形態學,形态学,morphology (in biology or linguistics)/
分析 *,*,*,*,fen1 xi1,分析,分析,to analyze/analysis/CL:個|个[ge4]/
。 *,*,*,*,*,*,*,*
EOS
注意: CC-CEDICT辞書をバイナリに含めるには、--features=embed-cc-cedict オプションを使用してビルドする必要があります。
埋め込みJieba(中国語辞書)を使用したトークナイズ
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict embedded://jieba
注意: Jieba辞書をバイナリに含めるには、--features=embed-jieba オプションを使用してビルドする必要があります。
ユーザー辞書の例
Linderaは、システム辞書と一緒にカスタム単語を追加するためのユーザー辞書をサポートしています。ユーザー辞書はCSVまたはバイナリ形式にすることができます。
ユーザー辞書の使用(CSV形式)
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict embedded://ipadic \
--user-dict ./resources/user_dict/ipadic_simple_userdic.csv
東京スカイツリー カスタム名詞,*,*,*,*,*,東京スカイツリー,トウキョウスカイツリー,*
の 助詞,連体化,*,*,*,*,の,ノ,ノ
最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,とうきょうスカイツリー駅,トウキョウスカイツリーエキ,*
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
EOS
ユーザー辞書の使用(バイナリ形式)
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict /tmp/lindera-ipadic-2.7.0-20250920 \
--user-dict ./resources/user_dict/ipadic_simple_userdic.bin
東京スカイツリー カスタム名詞,*,*,*,*,*,東京スカイツリー,トウキョウスカイツリー,*
の 助詞,連体化,*,*,*,*,の,ノ,ノ
最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,とうきょうスカイツリー駅,トウキョウスカイツリーエキ,*
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
EOS
トークナイズモード
Linderaは2つのトークナイズモードを提供します:normal と decompose です。
Normal モード(デフォルト)
辞書に登録された単語に基づいて忠実にトークナイズします:
% echo "関西国際空港限定トートバッグ" | lindera tokenize \
--dict embedded://ipadic \
--mode normal
関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ 名詞,一般,*,*,*,*,*,*,*
EOS
Decompose モード
複合語をさらに分解してトークナイズします:
% echo "関西国際空港限定トートバッグ" | lindera tokenize \
--dict embedded://ipadic \
--mode decompose
関西 名詞,固有名詞,地域,一般,*,*,関西,カンサイ,カンサイ
国際 名詞,一般,*,*,*,*,国際,コクサイ,コクサイ
空港 名詞,一般,*,*,*,*,空港,クウコウ,クーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ 名詞,一般,*,*,*,*,*,*,*
EOS
出力形式
Linderaは3つの出力形式を提供します:mecab, wakati, json。
MeCab 形式(デフォルト)
品詞情報を含むMeCab互換形式で結果を出力します:
% echo "お待ちしております。" | lindera tokenize \
--dict embedded://ipadic \
--output mecab
お待ち 名詞,サ変接続,*,*,*,*,お待ち,オマチ,オマチ
し 動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
て 助詞,接続助詞,*,*,*,*,て,テ,テ
おり 動詞,非自立,*,*,五段・ラ行,連用形,おる,オリ,オリ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
Wakati 形式
トークンテキストのみをスペース区切りで、入力 1 行につき 1 行(-N では結果 1 件につき 1 行)出力します。空行や空白だけの行のようにトークンのない行は、MeCab と同じく空行になります:
% echo "お待ちしております。" | lindera tokenize \
--dict embedded://ipadic \
--output wakati
お待ち し て おり ます 。
JSON 形式
すべてのトークン情報を含む詳細なJSON形式で出力します:
% echo "お待ちしております。" | lindera tokenize \
--dict embedded://ipadic \
--output json
[
{
"base_form": "お待ち",
"byte_end": 9,
"byte_start": 0,
"conjugation_form": "*",
"conjugation_type": "*",
"part_of_speech": "名詞",
"part_of_speech_subcategory_1": "サ変接続",
"part_of_speech_subcategory_2": "*",
"part_of_speech_subcategory_3": "*",
"pronunciation": "オマチ",
"reading": "オマチ",
"surface": "お待ち",
"word_id": 14698
},
...
]
N-Best トークナイズ
Linderaは N-Best トークナイズをサポートしており、コスト順(低コスト=高精度)に上位 N 件のトークナイズ候補を返します。これは MeCab の N-Best 実装と互換性のある Forward-DP Backward-A* アルゴリズムに基づいています。
行は \t・。・、 で文に区切られますが、、 と 。 をまたいで文脈を引き継ぐため、タブを含まない行の候補は、行全体の分割のうちコストの小さい N 件です(わずかな例外は文分割を参照)。タブは区間の終わりになります。タブを含む行は区間ごとに探索され、候補は区間ごとに 1 つのパスを選んだ組み合わせのうちコストの小さい N 件です。どの候補も行全体の分割です。
基本的な N-Best の例
% echo "すもももももももものうち" | lindera tokenize \
--dict embedded://ipadic \
-N 3
NBEST 1 (cost=21245)
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS
NBEST 2 (cost=24541)
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS
NBEST 3 (cost=24541)
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS
2 位と 3 位は、分割は異なりますがコストは同じ(24541)です。
ユニーク結果を使用した N-Best
同じ分割が複数のパスに現れる場合(各単語の品詞など、辞書エントリだけが異なる場合)、--nbest-unique を使用して重複を排除します。各結果は、その単語境界のパスのうち最もコストの低いものになります。探索は他のパスをたどりません(短い行でも数百万本になることがあります):
% echo "営業部長谷川です" | lindera tokenize \
--dict embedded://ipadic \
-N 5 --nbest-unique -o wakati
NBEST 1 (cost=15760)
営業 部長 谷川 です
NBEST 2 (cost=17747)
営業 部 長谷川 です
NBEST 3 (cost=18365)
営業 部 長谷 川 です
NBEST 4 (cost=21125)
営業 部 長 谷川 です
NBEST 5 (cost=22259)
営業 部長 谷 川 です
コスト閾値を使用した N-Best
--nbest-cost-threshold を使用して、最良パスから一定のコスト範囲内の結果に制限します:
% echo "営業部長谷川です" | lindera tokenize \
--dict embedded://ipadic \
-N 10 --nbest-unique --nbest-cost-threshold 5000 -o wakati
NBEST 1 (cost=15760)
営業 部長 谷川 です
NBEST 2 (cost=17747)
営業 部 長谷川 です
NBEST 3 (cost=18365)
営業 部 長谷 川 です
上の例の 4 位のコスト 21125 が 15760 + 5000 = 20760 を超えるため、3 件の結果のみが返されます。
フィルタを使用した高度なトークナイズ
Linderaは、文字フィルタ、トークナイザー、トークンフィルタを組み合わせた分析フレームワークを提供します。フィルタはJSONを使用して構成します。
% echo "すもももももももものうち" | lindera tokenize \
--dict embedded://ipadic \
--char-filter 'unicode_normalize:{"kind":"nfkc"}' \
--token-filter 'japanese_keep_tags:{"tags":["名詞,一般"]}'
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
EOS
build
Linderaで使用するための形態素解析辞書をCSVソースファイルからビルド(コンパイル)します。
ビルドパラメータ
--src/-s: 辞書CSVファイルを含むソースディレクトリ(ユーザー辞書の場合は単一CSVファイル)--dest/-d: コンパイルされた辞書の出力先ディレクトリ--metadata/-m: 辞書構造を定義するメタデータ設定ファイル (metadata.json)--user/-u: システム辞書の代わりにユーザー辞書をビルドする(オプションフラグ)--system-dict: ユーザー辞書を組み合わせるシステム辞書。ディレクトリのパス、URI、ダウンロード済みの辞書名のいずれかで、tokenize --dictと同じ形で指定する(オプション。--userのときだけ)。指定すると、各行の文脈 ID をそのシステム辞書の接続コスト行列と照らし合わせて確認します(文脈 ID の確認 を参照)--context-id-freq/-f: 接続コストIDの並び替えに使用するコンテキストID アクセス頻度ファイル(オプション。辞書のmetadata.jsonでconnection_id_mapping: trueが設定されている場合にのみ意味を持ちます)
辞書の種類
システム辞書 (System dictionary)
以下を含む完全な形態素解析辞書です:
- 語彙エントリ(単語定義)
- 接続コスト行列
- 未知語処理ルール
- 文字種定義
ユーザー辞書 (User dictionary)
システム辞書と一緒に動作する、カスタム単語のための補助辞書です。
文脈 ID の確認
システム辞書をビルドするとき、lindera build は MeCab の mecab-dict-index と同じく、すべての文脈 ID が接続コスト行列の範囲内にあることを確認します。lex.csv の各行と unk.def の各行で、右文脈 ID は matrix.def のヘッダの 1 つ目の値より小さく、左文脈 ID は 2 つ目の値より小さくなければなりません。これに反する辞書はビルドに失敗し、エラーはファイル・行・ID・行列の大きさを示します。たとえば 2 x 2 の matrix.def で、lex.csv の 2 行目の右文脈 ID が 2 の場合は次のとおりです:
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がtrueの場合は、そうしたlex.csvの行を取り除き、標準エラー出力に警告を出します(警告とログメッセージを参照)。unk.defの行は常に確認します。 - ID が軸の範囲外にある
matrix.defの行もエラーになり、フィールドが 4 つ未満のunk.defの行もエラーになります。 - ユーザー辞書(
--user)を確認するのは、組み合わせるシステム辞書を--system-dictで指定したときだけです(次の段落を参照)。
--system-dict を指定すると、lindera build --user はユーザー辞書の各行を、そのシステム辞書の接続コスト行列と同じ範囲で照らし合わせて確認します。簡易形式の行は、metadata.json の既定の文脈 ID(default_left_context_id と default_right_context_id)で確認します。行列の範囲外にある最初の行でビルドは失敗し、エラーはその行の CSV の行番号と表層形を示します。.bin ファイルは書き出しません。たとえば IPADIC の 1316 x 1316 の行列に対して、userdic.csv の 2 行目の左文脈 ID が 60000 の場合は次のとおりです:
% lindera build --src ./userdic.csv --dest ./user_dict --metadata ./lindera-ipadic/metadata.json --user --system-dict embedded://ipadic
Error: LinderaError { kind: Content, source: user dictionary line 2 (surface: '東京スカイツリー'): left context id 60000 is out of range for the connection matrix (matrix.def size 1316 1316; left context ids must be below 1316) }
--system-dict を指定しないと、ビルドの時点では組み合わせるシステム辞書が分からないため、ユーザー辞書はそのままビルドされます。その後 lindera tokenize --user-dict がシステム辞書と照らし合わせて確認し、行列の範囲外にある最初の見出し語を単語 ID で示すエラーで失敗します(文脈 ID を参照)。lindera tokenize --user-dict に .csv を渡すと、読み込むときに --dict のシステム辞書に合わせてビルドするため、--system-dict と同じく CSV の行番号と表層形を示すエラーになります。
matrix.def にないセル
matrix.def は、すべてのセルを列挙しなくてもかまいません。lindera build は、matrix.def が列挙していないセルのコストを、MeCab の mecab-dict-index と同じく 0 にします。v6.2.0 までは、そうしたセルのコストは 32767(i16::MAX)で、その接続は非常に高いコストになっていました。たとえば次の matrix.def では、右文脈 ID 1 から左文脈 ID 0 と 1 への接続のコストが 10 と 20 になり、残りの 7 つのセルのコストは 0 になります。
3 3
1 0 10
1 1 20
同梱の辞書と、lindera export が書き出す matrix.def は、すべてのセルを列挙しているため、この規則で変わることはありません。
char.def と unk.def の確認
システム辞書をビルドするとき、lindera build は mecab-dict-index と同じく、char.def と unk.def を互いに照らし合わせて確認します:
char.defの範囲行が指すカテゴリは、それより前の行で定義されていること- 各カテゴリの定義は 1 回だけであること
unk.defの各行が、char.defで定義したカテゴリを指すことchar.defの各カテゴリに、unk.defの行が 1 行以上あること
どれかに反する辞書はビルドに失敗し、エラーはファイルとカテゴリを示します。unk.def の行がないカテゴリ以外では、行も示します。以前はこうした辞書もビルドでき、その後の lindera tokenize は、未定義のカテゴリの文字で panic したり、一部のカテゴリに別のカテゴリの設定を使ったり、どの単語にも未知語にも覆われない文字の手前で出力を打ち切ったり、unk.def の行を無視したりしていました。たとえば、char.def が SPACE カテゴリを定義し、unk.def に SPACE の行がない場合は、次のようになります:
Error: LinderaError { kind: Content, source: ./dict-source/unk.def: no line for category SPACE, which char.def defines; every char.def category needs at least one unk.def line }
lindera train も、学習の前に --char-def と --unk-def を同じように確認します(学習パイプラインを参照)。
例
IPADIC(日本語辞書)のビルド
# IPADICソースファイルのダウンロードと展開
% curl -L -o /tmp/mecab-ipadic-2.7.0-20250920.tar.gz "https://Lindera.dev/mecab-ipadic-2.7.0-20250920.tar.gz"
% tar zxvf /tmp/mecab-ipadic-2.7.0-20250920.tar.gz -C /tmp
# 辞書のビルド
% lindera build \
--src /tmp/mecab-ipadic-2.7.0-20250920 \
--dest /tmp/lindera-ipadic-2.7.0-20250920 \
--metadata ./lindera-ipadic/metadata.json
IPADIC NEologd(日本語辞書)のビルド
% curl -L -o /tmp/mecab-ipadic-neologd-0.0.7-20200820.tar.gz "https://lindera.dev/mecab-ipadic-neologd-0.0.7-20200820.tar.gz"
% tar zxvf /tmp/mecab-ipadic-neologd-0.0.7-20200820.tar.gz -C /tmp
% lindera build \
--src /tmp/mecab-ipadic-neologd-0.0.7-20200820 \
--dest /tmp/lindera-ipadic-neologd-0.0.7-20200820 \
--metadata ./lindera-ipadic-neologd/metadata.json
UniDic(日本語辞書)のビルド
% curl -L -o /tmp/unidic-mecab-2.1.2.tar.gz "https://Lindera.dev/unidic-mecab-2.1.2.tar.gz"
% tar zxvf /tmp/unidic-mecab-2.1.2.tar.gz -C /tmp
% lindera build \
--src /tmp/unidic-mecab-2.1.2 \
--dest /tmp/lindera-unidic-2.1.2 \
--metadata ./lindera-unidic/metadata.json \
--context-id-freq ./lindera-unidic/context_id_freq.txt
CC-CEDICT(中国語辞書)のビルド
% curl -L -o /tmp/CC-CEDICT-MeCab-0.1.0-20200409.tar.gz "https://lindera.dev/CC-CEDICT-MeCab-0.1.0-20200409.tar.gz"
% tar zxvf /tmp/CC-CEDICT-MeCab-0.1.0-20200409.tar.gz -C /tmp
% lindera build \
--src /tmp/CC-CEDICT-MeCab-0.1.0-20200409 \
--dest /tmp/lindera-cc-cedict-0.1.0-20200409 \
--metadata ./lindera-cc-cedict/metadata.json
Jieba(中国語辞書)のビルド
% curl -L -o /tmp/mecab-jieba-0.1.1.tar.gz "https://lindera.dev/mecab-jieba-0.1.1.tar.gz"
% tar zxvf /tmp/mecab-jieba-0.1.1.tar.gz -C /tmp
% lindera build \
--src /tmp/mecab-jieba-0.1.1/dict-src \
--dest /tmp/lindera-jieba-0.1.1 \
--metadata ./lindera-jieba/metadata.json
ko-dic(韓国語辞書)のビルド
% curl -L -o /tmp/mecab-ko-dic-2.1.1-20180720.tar.gz "https://Lindera.dev/mecab-ko-dic-2.1.1-20180720.tar.gz"
% tar zxvf /tmp/mecab-ko-dic-2.1.1-20180720.tar.gz -C /tmp
% lindera build \
--src /tmp/mecab-ko-dic-2.1.1-20180720 \
--dest /tmp/lindera-ko-dic-2.1.1-20180720 \
--metadata ./lindera-ko-dic/metadata.json \
--context-id-freq ./lindera-ko-dic/context_id_freq.txt
ユーザー辞書のビルド
IPADICユーザー辞書(日本語)のビルド
ユーザー辞書フォーマットの詳細については、以下のURLを参照してください:
% lindera build \
--src ./resources/user_dict/ipadic_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-ipadic/metadata.json \
--user
--system-dict embedded://ipadic(またはビルド済みの IPADIC のパス)を加えると、各行の文脈 ID を IPADIC の接続コスト行列と照らし合わせて確認します(文脈 ID の確認 を参照)。
UniDicユーザー辞書(日本語)のビルド
ユーザー辞書フォーマットの詳細については、以下のURLを参照してください:
% lindera build \
--src ./resources/user_dict/unidic_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-unidic/metadata.json \
--user
SudachiDictユーザー辞書(日本語)のビルド
ユーザー辞書フォーマットの詳細については、以下のURLを参照してください:
% lindera build \
--src ./resources/user_dict/sudachidict_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-sudachidict/metadata.json \
--user
CC-CEDICTユーザー辞書(中国語)のビルド
ユーザー辞書フォーマットの詳細については、以下のURLを参照してください:
% lindera build \
--src ./resources/user_dict/cc-cedict_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-cc-cedict/metadata.json \
--user
Jiebaユーザー辞書(中国語)のビルド
ユーザー辞書フォーマットの詳細については、以下のURLを参照してください:
% lindera build \
--src ./resources/user_dict/jieba_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-jieba/metadata.json \
--user
ko-dicユーザー辞書(韓国語)のビルド
ユーザー辞書フォーマットの詳細については、以下のURLを参照してください:
% lindera build \
--src ./resources/user_dict/ko-dic_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-ko-dic/metadata.json \
--user
download
CLI と同じバージョンの学習済み辞書アーカイブを GitHub リリースページからダウンロードし、OS 標準のアプリケーションデータディレクトリにインストールします。
download パラメータ
- 辞書名(必須):
ipadic,ipadic-neologd,unidic,sudachidict,ko-dic,cc-cedict,jiebaのいずれか --force: 既存の辞書を再ダウンロードして置き換える
保存場所
辞書はバージョン付きレイアウト <データディレクトリ>/dictionaries/<バージョン>/lindera-<辞書名>/ にインストールされます。OS ごとのデフォルトのベースディレクトリ:
| OS | デフォルトの場所 |
|---|---|
| Linux | ~/.local/share/lindera |
| macOS | ~/Library/Application Support/lindera |
| Windows | %LOCALAPPDATA%\lindera |
環境変数 LINDERA_DATA_DIR を設定するとベースディレクトリを上書きできます。この変数はビルド時のソースキャッシュ用変数 LINDERA_BUILD_DICTIONARY_CACHE_DIR とは無関係です。
download の使用方法
% lindera download ipadic
Downloading https://github.com/lindera/lindera/releases/download/v5.1.0/lindera-ipadic-5.1.0.zip
100% (15.1/15.1 MiB)
Downloaded dictionary 'ipadic'
/home/user/.local/share/lindera/dictionaries/5.1.0/lindera-ipadic
インストール先のパスは標準出力に出力されるため(進捗や通知は標準エラー出力)、スクリプトで取得できます:
% DICT_DIR=$(lindera download ipadic)
ダウンロード後は tokenize で辞書名を指定して参照できます:
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize --dict ipadic
注意事項:
- アーカイブのバージョンは常に CLI のバージョンと一致します。リリースが公開されていない開発版のバージョンで実行すると HTTP 404 で失敗します。
- 整合性は HTTPS トランスポートと ZIP アーカイブ内蔵のファイルごとの CRC32 チェックサムで検証されます。
- 最大の辞書(
ipadic-neologd)は約 305 MB のダウンロードで、展開時にはその約 2 倍の空きディスク容量が必要です。
train
注釈付きコーパスデータから新しい形態素解析モデルを学習します。この機能を使用するには、train 機能フラグを有効にしてビルドする必要があります。(train 機能フラグはデフォルトで有効になっています。)
学習パラメータ
--seed/-s: 重み付けを行うシード語彙ファイル(CSV形式)--corpus/-c: 学習用コーパス(注釈付きテキスト)--char-def/-C: 文字定義ファイル (char.def)--unk-def/-u: 未知語定義ファイル (unk.def) - 重み付けの対象--feature-def/-f: 素性定義ファイル (feature.def)--rewrite-def/-r: 書換えルール定義ファイル (rewrite.def)--output/-o: 出力モデルファイル--lambda/-l: 正則化係数 (0.0-1.0) (デフォルト: 0.01)--regularization/-R: 正則化の種類:l1,l2, またはelasticnet(デフォルト:l1)--elastic-net-l1-ratio: Elastic Net 正則化時のL1比率 (0.0-1.0)。--regularization elasticnetを指定した場合のみ使用されます (デフォルト: 0.5)--max-iterations/-i: 学習の最大反復回数 (デフォルト: 100)--max-threads/-t: 最大スレッド数 (デフォルトはCPUコア数。速度のみに影響し、学習結果は変わりません)
基本的なワークフロー
1. 学習用ファイルの準備
シード語彙ファイル (seed.csv):
シード語彙ファイルは、CRFモデルの学習に使用される初期辞書エントリを含みます。各行はカンマ区切りのフィールドを持つ単語エントリを表します:
- 表層形
- 左文脈ID
- 右文脈ID
- 単語コスト
- 品詞タグ(複数のフィールド)
- 原形
- 読み(カタカナ)
- 発音
注意: 正確なフィールド定義は辞書フォーマット(IPADIC, UniDic, ko-dic, CC-CEDICT)によって異なります。詳細は各辞書のフォーマット仕様を参照してください。
外国,0,0,0,名詞,一般,*,*,*,*,外国,ガイコク,ガイコク
人,0,0,0,名詞,接尾,一般,*,*,*,人,ジン,ジン
このファイルは、lindera build が lex.csv を読むのと同じく CSV として読みます。フィールドは引用符で囲め(",",0,0,0,...)、# で始まる行はコメントではなくエントリです。詳しくは種辞書を参照してください。
学習用コーパス (corpus.txt):
学習用コーパスファイルは、CRFモデルの学習に使用される注釈付きテキストデータを含みます。各行は以下で構成されます:
- 表層形(単語)とそれに続くタブ文字
- カンマ区切りの形態素素性(品詞タグ、原形、読み、発音)
- 文は "EOS" (End Of Sentence) マーカーで区切られます
外国 名詞,一般,*,*,*,*,外国,ガイコク,ガイコク
人 名詞,接尾,一般,*,*,*,人,ジン,ジン
参政 名詞,サ変接続,*,*,*,*,参政,サンセイ,サンセイ
権 名詞,接尾,一般,*,*,*,権,ケン,ケン
EOS
ファイルフォーマットや高度な機能の詳細については、学習パイプライン を参照してください。
2. モデルの学習
lindera train \
--seed ./resources/training/seed.csv \
--corpus ./resources/training/corpus.txt \
--unk-def ./resources/training/unk.def \
--char-def ./resources/training/char.def \
--feature-def ./resources/training/feature.def \
--rewrite-def ./resources/training/rewrite.def \
--output /tmp/lindera/training/model.dat \
--lambda 0.01 \
--max-iterations 100
3. 学習結果
lindera train はモデルを --output のファイルに書き出します。学習用のラティスを作り終えると、コーパスの正解トークンのラベルの付き方を出力します(学習のしくみを参照)。./resources/training/ のファイルでは次のとおりです:
Lattice statistics: gold_lexicon=14 gold_unknown=0 gold_virtual=0 virtual_labels=0 negative_lexicon=0 negative_unknown=17
学習済みモデルには以下が含まれます:
- シード辞書の語: シード辞書のすべてのレコード(学習された重みと文脈 ID 付き)
- 未知語:
unk.defのすべてのエントリをファイル順に(学習された重みと文脈 ID 付き)
シード辞書にないコーパスの語は、語として追加されません。それらは unk.def のエントリか、学習されるもののエクスポートされない仮想ラベルを通じて学習されます。
export
学習済みモデルファイルをLindera辞書フォーマットのファイルにエクスポートします。この機能を使用するには、train 機能フラグを有効にしてビルドする必要があります。
エクスポートパラメータ
--model/-m: 学習済みモデルファイル(.dat形式)のパス--output/-o: 辞書ファイルの出力先ディレクトリ--metadata: オプションの metadata.json ファイル(学習済みモデル情報で更新されます)--cost-factor: 重みからコストへの変換係数を上書き(デフォルト: 学習済みモデルの値、通常は700)
出力ファイル
エクスポートコマンドは出力ディレクトリに以下の辞書ファイルを作成します:
lex.csv: 学習された重みを持つ語彙ファイル(MeCab互換のtocost()によるコスト変換)matrix.def: モデルの文脈 ID と BOS/EOS 用の 0 の全 (right_id, left_id) ペアを網羅する密な接続コスト行列。学習した重みがないペアのコストは、学習中や MeCab と同じく 0unk.def: 未知語定義。学習の入力のunk.defのエントリごとに 1 行を、同じ順序で書き出すchar.def: 文字種定義feature.def: 素性テンプレート定義(学習済みモデルからコピー)rewrite.def: 素性リライトルール(学習済みモデルからコピー)left-id.def: 左文脈IDから素性文字列へのマッピングright-id.def: 右文脈IDから素性文字列へのマッピングmetadata.json: 更新されたメタデータファイル(--metadataオプションが指定された場合)
モデルの形式バージョン
lindera export が読み込めるのは、現在の形式バージョン(2.0.0)のモデルだけです。それより前のバージョンで書かれた model.dat(7.0.0 より前に書かれたものはすべて該当)は拒否されるため、lindera train で学習し直す必要があります。6.1.0 の CLI で学習したモデルでは次のとおりです:
Error: LinderaError { kind: Deserialize, source: failed to deserialize model: failed without error information; enable debug assertions and the `alloc` feature in rancor for error information. The file is not a legacy JSON model either, so it was most likely written by a different version of lindera-trainer; re-run `lindera train` to regenerate it. }
完全なワークフロー例
1. モデルの学習
lindera train \
--seed ./resources/training/seed.csv \
--corpus ./resources/training/corpus.txt \
--unk-def ./resources/training/unk.def \
--char-def ./resources/training/char.def \
--feature-def ./resources/training/feature.def \
--rewrite-def ./resources/training/rewrite.def \
--output /tmp/lindera/training/model.dat \
--lambda 0.01 \
--max-iterations 100
2. 辞書フォーマットへのエクスポート
lindera export \
--model /tmp/lindera/training/model.dat \
--metadata ./resources/training/metadata.json \
--output /tmp/lindera/training/dictionary
3. 辞書のビルド
lindera build \
--src /tmp/lindera/training/dictionary \
--dest /tmp/lindera/training/compiled_dictionary \
--metadata /tmp/lindera/training/dictionary/metadata.json
4. 学習済み辞書の使用
echo "これは外国人参政権です。" | lindera tokenize -d /tmp/lindera/training/compiled_dictionary
これ 名詞,代名詞,一般,*,*,*,これ,コレ,コレ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
外国 名詞,一般,*,*,*,*,外国,ガイコク,ガイコク
人 名詞,接尾,一般,*,*,*,人,ジン,ジン
参政 名詞,サ変接続,*,*,*,*,参政,サンセイ,サンセイ
権 名詞,接尾,一般,*,*,*,権,ケン,ケン
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。 記号,句点,*,*,*,*,。,。,。
EOS
メタデータ更新機能
--metadata オプションが指定されると、エクスポートコマンドは以下の処理を行います:
- ベースとなる metadata.json ファイルを読み込み、既存の設定を保持します
- 特定のフィールドを学習済みモデルの値で更新します:
default_word_cost: 素性重みの中央値から計算された値model_info: 素性数、ラベル数、行列サイズ(connection_matrix_size。matrix.defの大きさを行数 x 列数で表す)、実際に回った学習の反復回数(training_iterations。--max-iterations以下)、正則化、モデルの形式バージョンを含む学習統計情報。タイムスタンプは書き込まれないため、同じモデルを 2 回エクスポートすると同一の出力になります
- 既存の設定を保持します(辞書名、文字エンコード設定、スキーマ定義、その他のユーザー定義設定など)
チュートリアル
このチュートリアルでは、Lindera CLIの基本的な使い方を、インストールから高度なテキスト処理まで順を追って説明します。
1. CLIのインストール
Lindera CLIをインストールします:
% cargo install lindera-cli
インストールの確認:
% lindera --help
2. 辞書のダウンロード
GitHub リリースページからビルド済みの IPADIC 辞書をダウンロードします。辞書は OS 標準のアプリケーションデータディレクトリにインストールされます:
% lindera download ipadic
利用可能な辞書名と保存場所についてはコマンドを参照してください。
3. 基本的なトークナイズ
ダウンロードした IPADIC 辞書を辞書名で参照して、日本語テキストをトークナイズします:
% echo "東京は日本の首都です。" | lindera tokenize \
--dict ipadic
期待される出力:
東京 名詞,固有名詞,地域,一般,*,*,東京,トウキョウ,トーキョー
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
日本 名詞,固有名詞,地域,国,*,*,日本,ニホン,ニホン
の 助詞,連体化,*,*,*,*,の,ノ,ノ
首都 名詞,一般,*,*,*,*,首都,シュト,シュト
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。 記号,句点,*,*,*,*,。,。,。
EOS
4. 異なる出力形式を試す
Wakati 形式(分かち書きのみ)
% echo "東京は日本の首都です。" | lindera tokenize \
--dict ipadic \
--output wakati
期待される出力:
東京 は 日本 の 首都 です 。
JSON 形式(詳細情報)
% echo "東京は日本の首都です。" | lindera tokenize \
--dict ipadic \
--output json
バイトオフセット、品詞タグ、読みなどの詳細なトークン情報を含むJSON配列が出力されます。
5. Decompose モードの使用
Decompose モードは複合名詞を構成要素に分解します:
% echo "関西国際空港限定トートバッグ" | lindera tokenize \
--dict ipadic \
--mode decompose
期待される出力:
関西 名詞,固有名詞,地域,一般,*,*,関西,カンサイ,カンサイ
国際 名詞,一般,*,*,*,*,国際,コクサイ,コクサイ
空港 名詞,一般,*,*,*,*,空港,クウコウ,クーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ 名詞,一般,*,*,*,*,*,*,*
EOS
Normal モードと比較すると、「関西国際空港」が1つのトークンのままになる点が異なります。
6. 文字フィルタとトークンフィルタの適用
Unicode正規化を行い、一般名詞のみを保持します:
% echo "Linderaは形態素解析エンジンです。" | lindera tokenize \
--dict ipadic \
--char-filter 'unicode_normalize:{"kind":"nfkc"}' \
--token-filter 'japanese_keep_tags:{"tags":["名詞,一般","名詞,サ変接続","名詞,固有名詞,組織"]}'
期待される出力:
Lindera 名詞,固有名詞,組織,*,*,*,*,*,*
形態素 名詞,一般,*,*,*,*,形態素,ケイタイソ,ケイタイソ
解析 名詞,サ変接続,*,*,*,*,解析,カイセキ,カイセキ
エンジン 名詞,一般,*,*,*,*,エンジン,エンジン,エンジン
EOS
Unicode正規化により全角文字が半角に変換され、Token Filter により指定した品詞タグに一致するトークンのみが保持されます。
複数のフィルタを組み合わせることもできます:
% echo "すもももももももものうち" | lindera tokenize \
--dict ipadic \
--token-filter 'japanese_stop_tags:{"tags":["助詞,格助詞,一般","助詞,係助詞","助詞,連体化","助動詞"]}'
7. ユーザー辞書の使用
カスタム単語エントリを含むCSVファイル(例: my_dict.csv)を作成します:
東京スカイツリー,カスタム名詞,トウキョウスカイツリー
ユーザー辞書を使用してトークナイズします:
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict ipadic \
--user-dict ./my_dict.csv
ユーザー辞書がない場合、「東京スカイツリー」は複数のトークンに分割されます。ユーザー辞書を使用すると、1つのトークンとして認識されます。
ビルド済みのユーザー辞書の例については、以下を参照してください:
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict ipadic \
--user-dict ./resources/user_dict/ipadic_simple_userdic.csv
期待される出力:
東京スカイツリー カスタム名詞,*,*,*,*,*,東京スカイツリー,トウキョウスカイツリー,*
の 助詞,連体化,*,*,*,*,の,ノ,ノ
最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,とうきょうスカイツリー駅,トウキョウスカイツリーエキ,*
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
EOS
Lindera Python
Lindera Python は、PyO3 を使用して構築された Lindera 形態素解析エンジンの Python バインディングです。Python 3.10 以降をサポートし、Lindera の高性能なトークナイズ機能を Python エコシステムに提供します。
特徴
- 多言語対応: 日本語(IPADIC、IPADIC NEologd、UniDic)、韓国語(ko-dic)、中国語(CC-CEDICT、Jieba)のテキストをトークナイズ
- テキスト処理パイプライン: 文字フィルタとトークンフィルタを組み合わせて、柔軟な前処理・後処理が可能
- CRF ベースの辞書学習: アノテーション付きコーパスからカスタム形態素解析モデルを学習(
trainfeature が必要) - 複数のトークナイズモード: 解析粒度に応じた Normal モードと Decompose モード
- N-best トークナイズ: コスト順にランク付けされた複数のトークナイズ候補を取得
- ユーザー辞書: システム辞書をカスタム語彙で拡張
ドキュメント
- インストール -- 前提条件、ビルド手順、feature フラグ
- クイックスタート -- 最小限の使用例
- Tokenizer API --
TokenizerBuilder、Tokenizer、Tokenクラスリファレンス - 辞書管理 -- 辞書の読み込み、ビルド、管理
- テキスト処理パイプライン -- 文字フィルタとトークンフィルタ
- 学習 -- カスタム CRF モデルの学習と辞書のエクスポート
インストール
PyPI からのインストール
ビルド済みホイールが PyPI で公開されています:
pip install lindera
ホイールの対象は、CPython 3.10〜3.15 の Linux(x86_64 と aarch64、glibc)、macOS(x86_64 と arm64)、Windows(x64 と arm64)です。それ以外の環境(musl ベースの Linux(Alpine)、ほかの CPU アーキテクチャ、free-threaded 版 CPython など)では、pip install lindera がソース配布物(sdist)からパッケージをビルドします。このビルドには Rust ツールチェーン 1.89 以降(rustup でインストール)と cc などの C リンカが必要で、数分かかります。
[!NOTE] PyPI パッケージには辞書が含まれていません。下記の辞書の入手を参照してください。
辞書の入手
Lindera はパッケージに辞書を同梱していません。ビルド済み辞書を別途入手する必要があります。
GitHub Releases からのダウンロード
ビルド済み辞書は GitHub Releases ページから入手できます。辞書アーカイブをダウンロードしてローカルディレクトリに展開してください:
# 例: IPADIC 辞書のダウンロードと展開
curl -LO https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip
unzip lindera-ipadic-<version>.zip -d /path/to/ipadic
ソースからのビルド
特定の feature フラグを有効にする必要がある場合など、ソースからビルドするには以下の前提条件が必要です:
- Python 3.10 以降(3.15 まで)
- Rust ツールチェーン -- rustup 経由でインストール
- maturin -- Rust ベースの Python 拡張をビルドするための Python パッケージ
maturin を pip でインストールします:
pip install maturin
開発ビルド
lindera-python を開発モードでビルドしてインストールします:
cd lindera-python
maturin develop
または、プロジェクトの Makefile を使用します:
make python-develop
学習機能付きビルド
train feature を有効にすると、CRF ベースの辞書学習機能が利用可能になります。デフォルトで有効になっています:
maturin develop --features train
Feature フラグ
| Feature | 説明 | デフォルト |
|---|---|---|
train | CRF 学習機能 | 有効 |
embed-ipadic | 日本語辞書(IPADIC)をバイナリに埋め込み | 無効 |
embed-unidic | 日本語辞書(UniDic)をバイナリに埋め込み | 無効 |
embed-sudachidict | 日本語辞書(SudachiDict)をバイナリに埋め込み | 無効 |
embed-ipadic-neologd | 日本語辞書(IPADIC NEologd)をバイナリに埋め込み | 無効 |
embed-ko-dic | 韓国語辞書(ko-dic)をバイナリに埋め込み | 無効 |
embed-cc-cedict | 中国語辞書(CC-CEDICT)をバイナリに埋め込み | 無効 |
embed-jieba | 中国語辞書(Jieba)をバイナリに埋め込み | 無効 |
embed-cjk | 全 CJK 辞書をバイナリに埋め込み(IPADIC、ko-dic、Jieba) | 無効 |
複数の feature を組み合わせることができます:
maturin develop --features "train,embed-ipadic,embed-ko-dic"
[!TIP] 辞書をバイナリに直接埋め込みたい場合(上級者向け)は、対応する
embed-*feature フラグを有効にしてビルドし、embedded://スキームでロードしてください:dictionary = load_dictionary("embedded://ipadic")詳細は Feature フラグ を参照してください。
インストールの確認
インストール後、Python で lindera が利用可能であることを確認します:
import lindera
print(lindera.version())
クイックスタート
このガイドでは、lindera-python を使用してテキストをトークナイズする方法を紹介します。
基本的なトークナイズ
トークナイザーの作成には TokenizerBuilder の使用を推奨します:
from lindera import TokenizerBuilder
builder = TokenizerBuilder()
builder.set_mode("normal")
builder.set_dictionary("/path/to/ipadic")
tokenizer = builder.build()
tokens = tokenizer.tokenize("関西国際空港限定トートバッグ")
for token in tokens:
print(f"{token.surface}\t{','.join(token.details)}")
注意: ビルド済み辞書を GitHub Releases からダウンロードし、展開したディレクトリのパスを指定してください。
期待される出力:
関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ UNK
メソッドチェーン
TokenizerBuilder は簡潔な設定のためにメソッドチェーンをサポートしています:
from lindera import TokenizerBuilder
tokenizer = (
TokenizerBuilder()
.set_mode("normal")
.set_dictionary("/path/to/ipadic")
.build()
)
tokens = tokenizer.tokenize("すもももももももものうち")
for token in tokens:
print(f"{token.surface}\t{token.get_detail(0)}")
トークンプロパティへのアクセス
各トークンは以下のプロパティを公開しています:
from lindera import TokenizerBuilder
tokenizer = TokenizerBuilder().set_dictionary("/path/to/ipadic").build()
tokens = tokenizer.tokenize("東京タワー")
for token in tokens:
print(f"Surface: {token.surface}")
print(f"Byte range: {token.byte_start}..{token.byte_end}")
print(f"Position: {token.position}")
print(f"Word ID: {token.word_id}")
print(f"Unknown: {token.is_unknown}")
print(f"Details: {token.details}")
print()
N-best トークナイズ
コスト順にランク付けされた複数のトークナイズ候補を取得します:
from lindera import TokenizerBuilder
tokenizer = TokenizerBuilder().set_dictionary("/path/to/ipadic").build()
results = tokenizer.tokenize_nbest("すもももももももものうち", n=3)
for tokens, cost in results:
surfaces = [t.surface for t in tokens]
print(f"Cost {cost}: {' / '.join(surfaces)}")
Tokenizer API
TokenizerBuilder
TokenizerBuilder はビルダーパターンを使用して Tokenizer インスタンスを設定・構築します。
コンストラクタ
TokenizerBuilder()
デフォルト設定で新しいビルダーを作成します。
from lindera import TokenizerBuilder
builder = TokenizerBuilder()
TokenizerBuilder().from_file(file_path)
YAML ファイルから設定を読み込み、新しいビルダーを返します。segmenter、
character_filters、token_filters を含む完全な例は
lindera-python/resources/lindera.yml
を参照してください。
builder = TokenizerBuilder().from_file("lindera.yml")
設定メソッド
すべてのセッターメソッドはメソッドチェーンのために self を返します。
set_mode(mode, penalty=None)
トークナイズモードを設定します。
"normal"-- 標準的なトークナイズ(デフォルト)"decompose"-- 複合語をより小さな単位に分解
penalty は "decompose" モードが使う Penalty です。None(デフォルト)では既定のペナルティを使います。"normal" とペナルティを一緒に渡すと ValueError が、Penalty でない値を渡すと TypeError が送出されます。
builder.set_mode("normal")
builder.set_mode("decompose", Penalty(kanji_penalty_length_penalty=0))
set_dictionary(path)
システム辞書のパスまたは URI を設定します。
# 埋め込み辞書を使用
builder.set_dictionary("embedded://ipadic")
# 外部辞書を使用
builder.set_dictionary("/path/to/dictionary")
set_user_dictionary(uri)
ユーザー辞書の URI を設定します。
builder.set_user_dictionary("/path/to/user_dictionary.csv")
set_keep_whitespace(keep)
出力に空白トークンを含めるかどうかを制御します。残した空白はラティスにも残るため、他のトークンも、MeCab と同様に空白をラティス上で読み飛ばすデフォルトの出力と変わることがあります。空白を出力せずにラティスにだけ残すには、代わりに set_skip_whitespace(False) を使います。
builder.set_keep_whitespace(True)
set_skip_whitespace(skip)
空白をラティス上で読み飛ばすかどうかを設定します。skip の意味は設定ファイルの segmenter.skip_whitespace と同じです:
True-- 空白を読み飛ばし、MeCab と同様に空白の前後の語を直接接続します。False-- 空白を独立したノード(空白の見出し語かSPACEの未知語)としてラティスに残します(v7 より前の動作)。空白は引き続き出力から除外されます。None-- 辞書のmetadata.jsonのデフォルトです。SudachiDict 以外では読み飛ばします。以前の呼び出しを取り消すときに使います。
ここで設定した値は、辞書のデフォルトと、from_file で読み込んだ skip_whitespace より優先されます。空白トークンを残すとき(set_keep_whitespace(True))は効果がありません。残した空白は常にラティスに残るためです。それ以外の値では TypeError が送出されます。
たとえば IPADIC では、Google が 新しい の が は、デフォルトでは助詞(助詞)、set_skip_whitespace(False) では接続詞(接続詞)と解析されます。詳しくは Segmenter を参照してください。
# v7 より前と同じく、空白をラティスに残す
builder.set_skip_whitespace(False)
set_space_penalty(value)
韓国語向けの左側空白ペナルティ(left-space penalty)を設定します。直前に空白がある候補のうち、本来は前の語に空白なしで付く品詞(助詞、語尾など)の候補にコストを加算します。value の意味は設定ファイルの segmenter.space_penalty と同じです:
None-- デフォルト。辞書がmetadata.jsonに同梱するルールがあればそれを使います(ko-dic は mecab-ko-dic のルールを同梱し、他の同梱辞書はルールを持ちません)。以前の呼び出しを取り消すときにも使います。False-- ペナルティをオフにします。True-- 辞書のルールを要求します。ルールを同梱しない辞書ではbuild()がValueErrorを送出します。{"rules": [{"pos": [...], "cost": n}, ...]}形式のdict-- 辞書のルールの代わりにこのルールを適用します。先頭品詞タグがposに含まれる候補にcostを加算し、最初に一致したルールが使われます。
それ以外の値では ValueError が送出されます(任意のオブジェクトなど JSON に対応しない値は、append_token_filter と同様に TypeError になります)。
# ペナルティをオフにする
builder.set_space_penalty(False)
# 明示的なルール
builder.set_space_penalty({
"rules": [
{"pos": ["EC", "EF", "EP", "ETM", "ETN", "VCP", "XSA", "XSN", "XSV"], "cost": 3000},
{"pos": ["JC", "JKB", "JKC", "JKG", "JKO", "JKQ", "JKS", "JKV", "JX"], "cost": 6000},
]
})
[!NOTE] v6.1.0 から、ko-dic では mecab-ko と同じく左側空白ペナルティがデフォルトで適用されます。たとえば
검색 이 잘 된다の이は感動詞ICと解析されます(ペナルティがなければ主格助詞JKS)。オフにするにはbuilder.set_space_penalty(False)を呼び出してください。ルールを同梱しているのは Lindera 6.1.0 以降でビルドした ko-dic だけです。v6.0.0 リリースの ko-dic ではペナルティはオフのままで、辞書を再ビルドするまでTrueはエラーになります。詳しくは Segmenter を参照してください。
append_character_filter(kind, args=None)
前処理パイプラインに文字フィルタを追加します。
builder.append_character_filter("unicode_normalize", {"kind": "nfkc"})
append_token_filter(kind, args=None)
後処理パイプラインにトークンフィルタを追加します。
builder.append_token_filter("lowercase", {})
set_space_penalty、append_character_filter、append_token_filter の引数は、list と dict の入れ子が 128 段までです。それより深い値や、自分自身を含む値を渡すと ValueError が送出されます。
ビルド
build()
設定された内容で Tokenizer をビルドして返します。
tokenizer = builder.build()
Tokenizer
Tokenizer はテキストに対して形態素解析を行います。
Tokenizer の作成
Tokenizer(dictionary, mode="normal", user_dictionary=None, space_penalty=None, penalty=None)
読み込み済みの辞書から直接トークナイザーを作成します。
space_penalty には TokenizerBuilder.set_space_penalty と同じ値を渡せます。None(デフォルト)では辞書が同梱する左側空白ペナルティのルールを使うため、ko-dic ではペナルティがオンになります。オフにするには False を渡します。不正な設定では、set_space_penalty や build() と同じエラーが送出されます。
penalty には、TokenizerBuilder.set_mode と同じく "decompose" モードの Penalty を渡せます。エラーも set_mode と同じです。
from lindera import Tokenizer, load_dictionary
dictionary = load_dictionary("embedded://ipadic")
tokenizer = Tokenizer(dictionary, mode="normal")
# 左側空白ペナルティなしの ko-dic
ko_tokenizer = Tokenizer(load_dictionary("embedded://ko-dic"), space_penalty=False)
Tokenizer メソッド
tokenize(text)
入力テキストをトークナイズし、Token オブジェクトのリストを返します。
tokens = tokenizer.tokenize("形態素解析")
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
text | str | トークナイズするテキスト |
戻り値: list[Token]
tokenize_surfaces(text)
入力テキストをトークナイズし、トークンの surface のみを文字列のリストとして返します。分かち書き用途の高速パスです。Token オブジェクトを生成せず、形態素の詳細情報もロードしないため、surface 文字列だけが必要な場合は tokenize より大幅に高速です。結果は [t.surface for t in tokenizer.tokenize(text)] と一致します。
surfaces = tokenizer.tokenize_surfaces("形態素解析")
# ["形態素", "解析"]
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
text | str | トークナイズするテキスト |
戻り値: list[str]
tokenize_nbest(text, n, unique=False, cost_threshold=None)
N-best トークナイズ結果を返します。各結果はトータルパスコストとペアになっています。
results = tokenizer.tokenize_nbest("すもももももももものうち", n=3)
for tokens, cost in results:
print(cost, [t.surface for t in tokens])
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
text | str | トークナイズするテキスト |
n | int | 返す結果の数 |
unique | bool | 結果の重複を排除(デフォルト: False) |
cost_threshold | int または None | 最良パスからの最大コスト差(デフォルト: None) |
戻り値: list[tuple[list[Token], int]]
Mode
Mode はトークナイズモードを表します。モードの確認や比較のためのスタンドアロンな
ヘルパーとして提供されています。TokenizerBuilder.set_mode() と Tokenizer の
コンストラクタは、Mode インスタンスではなく単純なモード文字列
("normal" または "decompose")を受け取ります。decompose モードのペナルティは、
それぞれの penalty 引数で渡します(下記 Penalty を参照)。
Mode の作成
Mode(mode_str=None)
Mode を作成します。"normal" / "Normal"(省略時のデフォルト)または
"decompose" / "Decompose" を受け付けます。それ以外の値を指定すると
ValueError が発生します。
from lindera import Mode
mode = Mode("normal")
mode = Mode("decompose")
mode = Mode() # デフォルトは "normal"
メソッド
| メソッド | 戻り値 | 説明 |
|---|---|---|
__str__() | str | "normal" または "decompose" |
__repr__() | str | 例: "Mode.Normal" |
is_normal() | bool | モードが "normal" の場合 True |
is_decompose() | bool | モードが "decompose" の場合 True |
mode = Mode("decompose")
str(mode) # "decompose"
repr(mode) # "Mode.Decompose"
mode.is_normal() # False
mode.is_decompose() # True
Penalty
Penalty は "decompose" モードのセグメンテーションで使用される、長さに基づく
ペナルティの閾値を設定します。
Penalty の作成
Penalty(kanji_penalty_length_threshold=2, kanji_penalty_length_penalty=3000, other_penalty_length_threshold=7, other_penalty_length_penalty=1700)
すべての引数は省略可能で、上記の値がデフォルトとして使用されます。
from lindera import Penalty
penalty = Penalty(
kanji_penalty_length_threshold=2,
kanji_penalty_length_penalty=3000,
other_penalty_length_threshold=7,
other_penalty_length_penalty=1700,
)
Penalty のプロパティ
4 つのフィールドはすべて取得・設定の両方をサポートしています:
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
kanji_penalty_length_threshold | int | 2 | ペナルティが適用される、漢字のみの表層形の長さの閾値 |
kanji_penalty_length_penalty | int | 3000 | 閾値を超える漢字のみの表層形に加算されるコストペナルティ |
other_penalty_length_threshold | int | 7 | 漢字のみでない表層形にペナルティが適用される長さの閾値 |
other_penalty_length_penalty | int | 1700 | 閾値を超える漢字のみでない表層形に加算されるコストペナルティ |
penalty.kanji_penalty_length_threshold = 3
print(penalty.kanji_penalty_length_threshold) # 3
Penalty の使い方
Penalty は "decompose" と一緒に TokenizerBuilder.set_mode() か Tokenizer のコンストラクタ に渡します。長さのペナルティをなくすと、decompose モードでも長い語が分割されずに残ります。
from lindera import Penalty, Tokenizer, TokenizerBuilder, load_dictionary
text = "関西国際空港限定トートバッグ"
penalty = Penalty(kanji_penalty_length_penalty=0, other_penalty_length_penalty=0)
builder = TokenizerBuilder().set_dictionary("embedded://ipadic")
tokenizer = builder.set_mode("decompose").build()
print([token.surface for token in tokenizer.tokenize(text)])
# ['関西', '国際', '空港', '限定', 'トートバッグ']
tokenizer = builder.set_mode("decompose", penalty).build()
print([token.surface for token in tokenizer.tokenize(text)])
# ['関西国際空港', '限定', 'トートバッグ']
tokenizer = Tokenizer(load_dictionary("embedded://ipadic"), mode="decompose", penalty=penalty)
print([token.surface for token in tokenizer.tokenize(text)])
# ['関西国際空港', '限定', 'トートバッグ']
"normal" モードと一緒に Penalty を渡すと ValueError が送出されます。
Token
Token は単一の形態素トークンを表します。
プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
surface | str | トークンの表層形 |
byte_start | int | 元テキストでの開始バイト位置 |
byte_end | int | 元テキストでの終了バイト位置 |
position | int | トークンの位置インデックス |
word_id | int | 辞書の単語 ID |
is_unknown | bool | 辞書に登録されていない単語の場合 True |
details | list[str] または None | 形態素の詳細情報(品詞、読みなど) |
Token メソッド
get_detail(index)
指定されたインデックスの詳細文字列を返します。インデックスが範囲外の場合は None を返します。
token = tokenizer.tokenize("東京")[0]
pos = token.get_detail(0) # 例: "名詞"
subpos = token.get_detail(1) # 例: "固有名詞"
reading = token.get_detail(7) # 例: "トウキョウ"
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
index | int | details リストへのゼロベースインデックス |
戻り値: str または None
to_dict()
トークンをプレーンな dict として返します。各フィールドは Python の自然な型を
保つため、独自のエンコーダなしでシリアライズできます。
import json
data = tokenizer.tokenize("東京")[0].to_dict()
# {'surface': '東京', 'byte_start': 0, 'byte_end': 6, 'position': 0,
# 'word_id': 12345, 'is_unknown': False, 'details': [...]}
json.dumps([token.to_dict() for token in tokens])
戻り値: surface、byte_start、byte_end、position、word_id、
is_unknown、details をキーに持つ dict。
details の構造は辞書によって異なります:
- IPADIC:
[品詞, 品詞細分類1, 品詞細分類2, 品詞細分類3, 活用型, 活用形, 原形, 読み, 発音] - UniDic: UniDic 仕様に準拠した詳細な形態素情報
- ko-dic / CC-CEDICT / Jieba: 各辞書固有の詳細フォーマット
エラーハンドリング
Lindera Python の関数は、カスタムの例外型ではなく標準的な Python の例外を送出します:
IOError(OSErrorのエイリアス) -- ファイルが存在しない、読み込めないなどの I/O 関連の失敗ValueError-- それ以外のすべてのケース(不正な設定、パースエラー、 トークナイズの失敗など)
from lindera import load_dictionary
try:
dictionary = load_dictionary("/path/that/does/not/exist")
except ValueError as e:
print(f"Failed to load dictionary: {e}")
lindera.LinderaError クラスも登録されていますが、このクレート内のどの関数からも
送出されることはありません -- 手動で構築・送出する場合にのみ使用できます。この
ライブラリのエラーを処理する際は、LinderaError ではなく IOError /
ValueError(または一般的な Exception)をキャッチしてください。
辞書管理
Lindera Python は、形態素解析で使用する辞書の読み込み、ビルド、管理のための関数を提供します。
辞書の読み込み
システム辞書
load_dictionary(uri) を使用してシステム辞書を読み込みます。GitHub Releases からビルド済み辞書をダウンロードし、展開したディレクトリのパスを指定してください:
from lindera import load_dictionary
dictionary = load_dictionary("/path/to/ipadic")
埋め込み辞書(上級者向け) -- embed-* feature フラグ付きでビルドした場合、埋め込み辞書を使用できます:
dictionary = load_dictionary("embedded://ipadic")
読み込んだ Dictionary は、自身のメタデータも公開しています:
print(dictionary.metadata_name()) # 例: "ipadic"
print(dictionary.metadata_encoding()) # 例: "UTF-8"
metadata = dictionary.metadata() # Metadata オブジェクト全体
ユーザー辞書は、組み合わせるシステム辞書と同じメタデータ(スキーマ、
エンコーディングなど)を共有する必要があります。load_user_dictionary_for は
システム辞書を受け取り、そのメタデータを使います。
lindera-python/examples/tokenize_with_userdict.py
とユーザー辞書を参照してください。
ユーザー辞書
ユーザー辞書はシステム辞書にカスタム語彙を追加します。組み合わせるシステム辞書に 合わせて読み込みます:
from lindera import load_dictionary, load_user_dictionary_for
dictionary = load_dictionary("/path/to/ipadic")
user_dict = load_user_dictionary_for("/path/to/user_dictionary.csv", dictionary)
.csv はシステム辞書のメタデータでビルドし、ビルドしながら接続コスト行列と
照らして確認します。文脈 ID が行列の範囲外にある行は、その CSV の行番号と表層形を
示す ValueError になります(文脈 ID
を参照)。.bin はビルドされたとおりに読み込みます。
load_user_dictionary(uri, metadata) は、メタデータだけでユーザー辞書を
読み込みます。この方法で読み込んだ .csv はシステム辞書と組み合わせるときに
確認し、エラーは見出し語をその単語 ID で示します:
from lindera import load_user_dictionary, Metadata
metadata = Metadata()
user_dict = load_user_dictionary("/path/to/user_dictionary.csv", metadata)
トークナイザーのビルド時にユーザー辞書を渡します:
from lindera import Tokenizer, load_dictionary, load_user_dictionary_for
dictionary = load_dictionary("/path/to/ipadic")
user_dict = load_user_dictionary_for("/path/to/user_dictionary.csv", dictionary)
tokenizer = Tokenizer(dictionary, mode="normal", user_dictionary=user_dict)
または、ビルダー経由で設定します:
from lindera import TokenizerBuilder
tokenizer = (
TokenizerBuilder()
.set_dictionary("/path/to/ipadic")
.set_user_dictionary("/path/to/user_dictionary.csv")
.build()
)
辞書のビルド
システム辞書のビルド
ソースファイルからシステム辞書をビルドします:
from lindera import build_dictionary, Metadata
metadata = Metadata(name="custom", encoding="UTF-8")
build_dictionary("/path/to/input_dir", "/path/to/output_dir", metadata)
入力ディレクトリには辞書のソースファイル(CSV レキシコン、matrix.def など)が含まれている必要があります。
Metadata.from_json_file で読み込んだメタデータは metadata.json の space_penalty ルールを保持し、ビルドした辞書にもそのルールが同梱されます。そのため ko-dic をビルドした場合も、左側空白ペナルティはデフォルトでオンのままです。トークナイザーごとにオフにするには TokenizerBuilder.set_space_penalty(False) を使います(Tokenizer API を参照)。コンストラクタで作成した Metadata はルールを持ちません。
ユーザー辞書のビルド
組み合わせるシステム辞書に合わせて、CSV ファイルからユーザー辞書をビルドします:
from lindera import build_user_dictionary_for, load_dictionary, load_user_dictionary_for
dictionary = load_dictionary("/path/to/ipadic")
build_user_dictionary_for("user_words.csv", "/path/to/user_dictionary.bin", dictionary)
user_dict = load_user_dictionary_for("/path/to/user_dictionary.bin", dictionary)
CSV はシステム辞書のメタデータでビルドし、接続コスト行列と照らして確認します。
文脈 ID が行列の範囲外にある行は、その CSV の行番号と表層形を示す ValueError
になり、ファイルは書き出しません(文脈 ID
を参照)。2 番目の引数は書き出すファイルのパスです。ローダーは拡張子で
ユーザー辞書の読み込み方を決めるので、.bin で終えてください。
build_user_dictionary(kind, input_file, output_dir, metadata) は、メタデータ
だけでユーザー辞書をビルドします。output_dir も、名前に反してディレクトリでは
なく、書き出すファイルのパスです。各行はシステム辞書と組み合わせるときに確認し、
エラーは見出し語をその単語 ID で示します:
from lindera import build_user_dictionary, Metadata
metadata = Metadata()
build_user_dictionary("ipadic", "user_words.csv", "/path/to/user_dictionary.bin", metadata)
metadata パラメータは省略可能です。省略した場合はデフォルトのメタデータ値が使用されます:
build_user_dictionary("ipadic", "user_words.csv", "/path/to/user_dictionary.bin")
注意: 最初の引数(上記の "ipadic")は現在未使用で、将来のために予約されて
いるものです -- ビルドの選択や設定には一切影響しません。ビルドの挙動は
metadata(特に metadata.user_dictionary_schema)によってのみ制御されます。
現時点では任意の文字列を渡すことができます。システム辞書に合わせてビルドするには
build_user_dictionary_for を使います。
Metadata
Metadata クラスは辞書のパラメータを設定します。
Metadata の作成
from lindera import Metadata
# デフォルトのメタデータ
metadata = Metadata()
# カスタムメタデータ
metadata = Metadata(
name="my_dictionary",
encoding="UTF-8",
default_word_cost=-10000,
)
JSON からの読み込み
metadata = Metadata.from_json_file("metadata.json")
プロパティ
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | str | "default" | 辞書名 |
encoding | str | "UTF-8" | 文字エンコーディング |
default_word_cost | int | -10000 | 未知語のデフォルトコスト |
default_left_context_id | int | 1288 | デフォルトの左文脈 ID |
default_right_context_id | int | 1288 | デフォルトの右文脈 ID |
default_field_value | str | "*" | 欠損フィールドのデフォルト値 |
flexible_csv | bool | False | 柔軟な CSV パースを許可 |
skip_invalid_cost_or_id | bool | False | 無効なコストまたは ID のエントリーをスキップ |
dictionary_schema | Schema | IPADIC スキーマ | メイン辞書のスキーマ |
user_dictionary_schema | Schema | 最小スキーマ | ユーザー辞書のスキーマ |
すべてのプロパティは取得と設定の両方をサポートしています:
metadata = Metadata()
metadata.name = "custom_dict"
metadata.encoding = "EUC-JP"
print(metadata.name) # "custom_dict"
to_dict()
メタデータを文字列の dict として返します。上記のプロパティを含み、2 つのスキーマは dictionary_schema_fields と user_dictionary_schema_fields(カンマ区切りのフィールド名)として格納されます:
metadata = Metadata(name="test")
print(metadata.to_dict())
ko-dic のようにメタデータが左側空白ペナルティのルールを持つ場合、dict にはそのルールを JSON 文字列で保持する space_penalty エントリも含まれます。ルールがない場合、このキーはありません:
metadata = Metadata.from_json_file("/path/to/ko-dic/metadata.json")
metadata.to_dict()["space_penalty"]
# '{"rules":[{"pos":["EC","EF",...],"cost":3000},{"pos":["JC","JKB",...],"cost":6000}]}'
Schema
Schema、FieldDefinition、FieldType は、辞書エントリーのフィールド構成を
表します。スキーマは Metadata.dictionary_schema と
Metadata.user_dictionary_schema(上記の表を参照)で使用されます。
FieldType
FieldType は単一フィールドの種別を列挙します:
FieldType.Surface-- 表層形(単語のテキスト)FieldType.LeftContextId-- 左文脈 IDFieldType.RightContextId-- 右文脈 IDFieldType.Cost-- 単語コストFieldType.Custom-- その他の辞書固有フィールド
FieldDefinition
FieldDefinition はスキーマ内の単一フィールドを表します。
FieldDefinition(index, name, field_type, description=None)
from lindera import FieldDefinition, FieldType
field = FieldDefinition(0, "surface", FieldType.Surface, "Surface form")
プロパティ(読み取り専用):
| プロパティ | 型 | 説明 |
|---|---|---|
index | int | スキーマ内でのフィールドの位置(0 始まり) |
name | str | フィールド名 |
field_type | FieldType | フィールドの種別 |
description | str または None | 任意の説明文 |
Schema の作成
Schema はフィールド名の順序付きリストを保持し、フィールド名とインデックス間の
相互参照を提供します。
Schema(fields)
フィールド名のリストからスキーマを作成します。
from lindera import Schema
schema = Schema([
"surface",
"left_context_id",
"right_context_id",
"cost",
"major_pos",
"reading",
])
Schema.create_default()
組み込みのデフォルトスキーマを返す静的メソッドです。13 個のフィールドから成り、
IPADIC 形式のレイアウトに対応します(surface, left_context_id,
right_context_id, cost, major_pos, pos_detail_1, pos_detail_2,
pos_detail_3, conjugation_type, conjugation_form, base_form, reading,
pronunciation)。
schema = Schema.create_default()
Schema のメソッドとプロパティ
| メンバー | 戻り値 | 説明 |
|---|---|---|
fields(プロパティ) | list[str] | すべてのフィールド名(順序どおり) |
field_count() | int | フィールドの総数 |
get_field_index(name) | int または None | name という名前のフィールドのインデックス |
get_field_name(index) | str または None | index のフィールド名 |
get_custom_fields() | list[str] | 4 つの固定フィールド(surface, left_context_id, right_context_id, cost)以降のフィールド名 |
get_field_by_name(name) | FieldDefinition または None | name の完全なフィールド定義 |
validate_record(record) | None | record がスキーマと一致しない場合 ValueError を送出 |
__len__() | int | field_count() と同じ |
schema = Schema.create_default()
schema.field_count() # 13
schema.get_field_index("cost") # 3
schema.get_field_name(0) # "surface"
schema.get_custom_fields() # ["major_pos", "pos_detail_1", ..., "pronunciation"]
len(schema) # 13
field = schema.get_field_by_name("surface")
print(field.index, field.name, field.field_type) # 0 surface FieldType.Surface
schema.validate_record([
"東京", "1288", "1288", "100",
"名詞", "固有名詞", "地域", "一般", "*", "*",
"東京", "トウキョウ", "トーキョー",
])
テキスト処理パイプライン
Lindera Python は、トークナイズ前に文字フィルタを適用し、トークナイズ後にトークンフィルタを適用する、組み合わせ可能なテキスト処理パイプラインをサポートしています。フィルタは TokenizerBuilder に追加され、追加された順序で実行されます。
Input Text
--> Character Filters (preprocessing)
--> Tokenization
--> Token Filters (postprocessing)
--> Output Tokens
文字フィルタ
文字フィルタはトークナイズ前に入力テキストを変換します。
unicode_normalize
入力テキストに Unicode 正規化を適用します。
from lindera import TokenizerBuilder
tokenizer = (
TokenizerBuilder()
.set_dictionary("embedded://ipadic")
.append_character_filter("unicode_normalize", {"kind": "nfkc"})
.build()
)
サポートされる正規化形式: "nfc"、"nfkc"、"nfd"、"nfkd"。
mapping
マッピングテーブルに従って文字や文字列を置換します。
tokenizer = (
TokenizerBuilder()
.set_dictionary("embedded://ipadic")
.append_character_filter("mapping", {
"mapping": {
"\u30fc": "-",
"\uff5e": "~",
}
})
.build()
)
japanese_iteration_mark
日本語の踊り字(繰り返し記号)を完全な形に展開します。
tokenizer = (
TokenizerBuilder()
.set_dictionary("embedded://ipadic")
.append_character_filter("japanese_iteration_mark", {
"normalize_kanji": True,
"normalize_kana": True,
})
.build()
)
トークンフィルタ
トークンフィルタはトークナイズ後にトークンを変換または除去します。
lowercase
トークンの表層形を小文字に変換します。
tokenizer = (
TokenizerBuilder()
.set_dictionary("embedded://ipadic")
.append_token_filter("lowercase", {})
.build()
)
japanese_base_form
辞書の形態素情報を使用して、活用形を基本形(辞書形)に置換します。
tokenizer = (
TokenizerBuilder()
.set_dictionary("embedded://ipadic")
.append_token_filter("japanese_base_form", {})
.build()
)
japanese_stop_tags
指定されたタグに一致する品詞のトークンを除去します。
tokenizer = (
TokenizerBuilder()
.set_dictionary("embedded://ipadic")
.append_token_filter("japanese_stop_tags", {
"tags": ["助詞,格助詞,一般", "助詞,係助詞", "助詞,連体化", "助動詞"],
})
.build()
)
タグは 4 階層のカンマ区切りに正規化され(不足分は * で補完)、各トークンの品詞詳細 4 つ(辞書スキーマで part_of_speech という名前のフィールドから始まる 4 つ。スキーマにそのフィールドが無い場合は先頭の詳細から)と完全一致で比較されます。IPADIC の助詞トークンは必ず 助詞,係助詞 のようにサブカテゴリを持つため、助詞 単独では一致しません。一方、助動詞はサブカテゴリを持たないため(助動詞,*,*,*)、助動詞 単独で一致します。
japanese_keep_tags
指定されたタグに一致する品詞のトークンのみを保持します。その他のトークンはすべて除去されます。
tokenizer = (
TokenizerBuilder()
.set_dictionary("embedded://ipadic")
.append_token_filter("japanese_keep_tags", {
"tags": ["名詞,一般"],
})
.build()
)
パイプラインの完全な例
以下の例では、複数の文字フィルタとトークンフィルタを1つのパイプラインに組み合わせています:
from lindera import TokenizerBuilder
tokenizer = (
TokenizerBuilder()
.set_mode("normal")
.set_dictionary("embedded://ipadic")
# Preprocessing
.append_character_filter("unicode_normalize", {"kind": "nfkc"})
.append_character_filter("japanese_iteration_mark", {
"normalize_kanji": True,
"normalize_kana": True,
})
# Postprocessing
.append_token_filter("japanese_base_form", {})
.append_token_filter("japanese_stop_tags", {
"tags": ["助詞,格助詞,一般", "助詞,係助詞", "助詞,連体化", "助動詞", "記号,句点", "記号,読点"],
})
.append_token_filter("lowercase", {})
.build()
)
tokens = tokenizer.tokenize("Linderaは形態素解析を行うライブラリです。")
for token in tokens:
print(f"{token.surface}\t{','.join(token.details)}")
このパイプラインでは:
unicode_normalizeが全角文字を半角に変換(NFKC 正規化)japanese_iteration_markが踊り字を展開japanese_base_formが活用形のトークンを基本形に変換japanese_stop_tagsが助詞(格助詞・係助詞・連体化)・助動詞・句読点を除去lowercaseがアルファベットを小文字に正規化
学習
Lindera Python は、アノテーション付きコーパスからカスタム CRF ベースの形態素解析モデルを学習する機能をサポートしています。この機能には train feature が必要です。
前提条件
train feature を有効にして lindera-python をビルドします(デフォルトで有効):
maturin develop --features train
モデルの学習
lindera.train() を使用して、種辞書とアノテーション付きコーパスから CRF モデルを学習します:
import lindera
lindera.train(
seed="resources/training/seed.csv",
corpus="resources/training/corpus.txt",
char_def="resources/training/char.def",
unk_def="resources/training/unk.def",
feature_def="resources/training/feature.def",
rewrite_def="resources/training/rewrite.def",
output="/tmp/model.dat",
lambda_=0.01,
max_iter=100,
max_threads=4,
)
学習パラメータ
| パラメータ | 型 | デフォルト | 説明 |
|---|---|---|---|
seed | str | 必須 | 種辞書ファイルのパス(CSV 形式) |
corpus | str | 必須 | アノテーション付き学習コーパスのパス |
char_def | str | 必須 | 文字定義ファイルのパス(char.def) |
unk_def | str | 必須 | 未知語定義ファイルのパス(unk.def) |
feature_def | str | 必須 | 素性定義ファイルのパス(feature.def) |
rewrite_def | str | 必須 | 書き換えルール定義ファイルのパス(rewrite.def) |
output | str | 必須 | 学習済みモデルファイルの出力パス |
lambda_ | float | 0.01 | L1 正則化コスト(0.0--1.0) |
max_iter | int | 100 | 最大学習イテレーション数 |
max_threads | int または None | None | スレッド数(None = CPU コア数を自動検出) |
学習済みモデルのエクスポート
学習後、lindera.export() を使用してモデルを辞書ソースファイルにエクスポートします:
import lindera
lindera.export(
model="/tmp/model.dat",
output="/tmp/dictionary_source",
metadata="resources/training/metadata.json",
)
エクスポートパラメータ
| パラメータ | 型 | デフォルト | 説明 |
|---|---|---|---|
model | str | 必須 | 学習済みモデルファイルのパス(.dat) |
output | str | 必須 | 辞書ソースファイルの出力ディレクトリ |
metadata | str または None | None | ベースとなる metadata.json ファイルのパス |
エクスポートにより、出力ディレクトリに以下のファイルが作成されます。lindera export が作成するファイルと同じです:
lex.csv-- 学習済みコスト付きのレキシコンエントリーmatrix.def-- 連接コスト行列unk.def-- 未知語定義char.def-- 文字カテゴリ定義(学習済みモデルからコピー)feature.def-- 素性テンプレート定義(学習済みモデルからコピー)rewrite.def-- 素性リライトルール(学習済みモデルからコピー)left-id.def-- 左文脈IDから素性文字列へのマッピングright-id.def-- 右文脈IDから素性文字列へのマッピングmetadata.json-- 更新されたメタデータ(metadataパラメータ指定時)
モデルファイルは、このバージョンの学習で作ったものである必要があります。それより前のバージョンで書かれたモデルファイル(7.0.0 より前に書かれたものはすべて該当)は拒否され、lindera.export() は学習のやり直しを求めるエラーで失敗します。そうしたモデルは lindera.train() で学習し直してください。
完全なワークフロー
カスタム辞書の学習と使用の完全なワークフロー:
import lindera
# Step 1: Train the CRF model
lindera.train(
seed="resources/training/seed.csv",
corpus="resources/training/corpus.txt",
char_def="resources/training/char.def",
unk_def="resources/training/unk.def",
feature_def="resources/training/feature.def",
rewrite_def="resources/training/rewrite.def",
output="/tmp/model.dat",
lambda_=0.01,
max_iter=100,
)
# Step 2: Export to dictionary source files
lindera.export(
model="/tmp/model.dat",
output="/tmp/dictionary_source",
metadata="resources/training/metadata.json",
)
# Step 3: Build the dictionary from exported source files
metadata = lindera.Metadata.from_json_file("/tmp/dictionary_source/metadata.json")
lindera.build_dictionary("/tmp/dictionary_source", "/tmp/dictionary", metadata)
# Step 4: Use the trained dictionary
tokenizer = (
lindera.TokenizerBuilder()
.set_dictionary("/tmp/dictionary")
.set_mode("normal")
.build()
)
tokens = tokenizer.tokenize("形態素解析のテスト")
for token in tokens:
print(f"{token.surface}\t{','.join(token.details)}")
Lindera Node.js
Lindera Node.js は、NAPI-RS を使用して構築された Lindera 形態素解析エンジンの Node.js バインディングです。Node.js 18 以降をサポートし、Lindera の高性能なトークナイズ機能を Node.js エコシステムに提供します。
特徴
- 多言語対応: 日本語(IPADIC、IPADIC NEologd、UniDic)、韓国語(ko-dic)、中国語(CC-CEDICT、Jieba)のテキストをトークナイズ
- テキスト処理パイプライン: 文字フィルタとトークンフィルタを組み合わせて、柔軟な前処理・後処理が可能
- CRF ベースの辞書学習: アノテーション付きコーパスからカスタム形態素解析モデルを学習(
trainfeature が必要) - 複数のトークナイズモード: 解析粒度に応じた Normal モードと Decompose モード
- N-best トークナイズ: コスト順にランク付けされた複数のトークナイズ候補を取得
- ユーザー辞書: システム辞書をカスタム語彙で拡張
- TypeScript サポート: 完全な型定義を同梱
ドキュメント
- インストール -- 前提条件、ビルド手順、feature フラグ
- クイックスタート -- 最小限の使用例
- Tokenizer API --
TokenizerBuilder、Tokenizer、Tokenクラスリファレンス - 辞書管理 -- 辞書の読み込み、ビルド、管理
- テキスト処理パイプライン -- 文字フィルタとトークンフィルタ
- 学習 -- カスタム CRF モデルの学習と辞書のエクスポート
インストール
npm からのインストール
ビルド済みパッケージが npm で公開予定です:
npm install lindera
[!NOTE] npm パッケージには辞書が含まれていません。下記の辞書の入手を参照してください。 ブラウザ/WASM での利用には lindera-wasm を参照してください。
ソースからのビルド
前提条件
- Node.js 18 以降(LTS バージョン推奨)
- Rust ツールチェーン -- rustup 経由でインストール
- NAPI-RS CLI -- Rust で Node.js ネイティブアドオンをビルドするための CLI ツール
NAPI-RS CLI をグローバルにインストールします:
npm install -g @napi-rs/cli
辞書の入手
Lindera はパッケージに辞書を同梱していません。ビルド済み辞書を別途入手する必要があります。
GitHub Releases からのダウンロード
ビルド済み辞書は GitHub Releases ページから入手できます。辞書アーカイブをダウンロードしてローカルディレクトリに展開してください:
# 例: IPADIC 辞書のダウンロードと展開
curl -LO https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip
unzip lindera-ipadic-<version>.zip -d /path/to/ipadic
開発ビルド
lindera-nodejs を開発モードでビルドします:
cd lindera-nodejs
npm install
npm run build
または、プロジェクトの Makefile を使用します:
make nodejs-develop
学習機能付きビルド
train feature を有効にすると、CRF ベースの辞書学習機能が利用可能になります。デフォルトで有効になっています:
npm run build -- --features train
Feature フラグ
| Feature | 説明 | デフォルト |
|---|---|---|
train | CRF 学習機能 | 有効 |
embed-ipadic | 日本語辞書(IPADIC)をバイナリに埋め込み | 無効 |
embed-unidic | 日本語辞書(UniDic)をバイナリに埋め込み | 無効 |
embed-sudachidict | 日本語辞書(SudachiDict)をバイナリに埋め込み | 無効 |
embed-ipadic-neologd | 日本語辞書(IPADIC NEologd)をバイナリに埋め込み | 無効 |
embed-ko-dic | 韓国語辞書(ko-dic)をバイナリに埋め込み | 無効 |
embed-cc-cedict | 中国語辞書(CC-CEDICT)をバイナリに埋め込み | 無効 |
embed-jieba | 中国語辞書(Jieba)をバイナリに埋め込み | 無効 |
embed-cjk | 全 CJK 辞書をバイナリに埋め込み(IPADIC、ko-dic、Jieba) | 無効 |
複数の feature を組み合わせることができます:
npm run build -- --features "train,embed-ipadic,embed-ko-dic"
[!TIP] 辞書をバイナリに直接埋め込みたい場合(上級者向け)は、対応する
embed-*feature フラグを有効にしてビルドし、embedded://スキームでロードしてください:const dictionary = loadDictionary("embedded://ipadic");詳細は Feature フラグ を参照してください。
インストールの確認
インストール後、Node.js で lindera が利用可能であることを確認します:
const lindera = require("lindera");
console.log(lindera.version());
[!NOTE] npm パッケージ
linderaのexportsマップはtypes/import/requireの各条件を宣言しているため、 CommonJS(require("lindera"))からも ES モジュール(import { TokenizerBuilder } from "lindera")からも そのまま読み込めます。
最小の ESM の例:
import { TokenizerBuilder } from "lindera";
console.log(typeof TokenizerBuilder); // "function"
クイックスタート
このガイドでは、lindera-nodejs を使用してテキストをトークナイズする方法を紹介します。
基本的なトークナイズ
トークナイザーの作成には TokenizerBuilder の使用を推奨します:
const { TokenizerBuilder } = require("lindera");
const builder = new TokenizerBuilder();
builder.setMode("normal");
builder.setDictionary("/path/to/ipadic");
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("関西国際空港限定トートバッグ");
for (const token of tokens) {
console.log(`${token.surface}\t${token.details.join(",")}`);
}
注意: ビルド済み辞書を GitHub Releases からダウンロードし、展開したディレクトリのパスを指定してください。
期待される出力:
関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ 名詞,一般,*,*,*,*,*,*,*
メソッドチェーン
すべてのセッターはビルダー自身(this)を返すため、設定をチェーンして記述できます:
const { TokenizerBuilder } = require("lindera");
const tokenizer = new TokenizerBuilder()
.setMode("normal")
.setDictionary("/path/to/ipadic")
.build();
const tokens = tokenizer.tokenize("すもももももももものうち");
for (const token of tokens) {
console.log(`${token.surface}\t${token.details[0]}`);
}
1 文ずつセッターを呼び出すスタイルもそのまま使えます。どちらのスタイルでも同じビルダーを設定します。
トークンプロパティへのアクセス
各トークンは以下のプロパティを公開しています:
const { TokenizerBuilder } = require("lindera");
const builder = new TokenizerBuilder();
builder.setDictionary("/path/to/ipadic");
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("東京タワー");
for (const token of tokens) {
console.log(`Surface: ${token.surface}`);
console.log(`Byte range: ${token.byteStart}..${token.byteEnd}`);
console.log(`Position: ${token.position}`);
console.log(`Word ID: ${token.wordId}`);
console.log(`Unknown: ${token.isUnknown}`);
console.log(`Details: ${token.details}`);
console.log();
}
N-best トークナイズ
コスト順にランク付けされた複数のトークナイズ候補を取得します:
const { TokenizerBuilder } = require("lindera");
const builder = new TokenizerBuilder();
builder.setDictionary("/path/to/ipadic");
const tokenizer = builder.build();
const results = tokenizer.tokenizeNbest("すもももももももものうち", 3);
for (const { tokens, cost } of results) {
const surfaces = tokens.map((t) => t.surface);
console.log(`Cost ${cost}: ${surfaces.join(" / ")}`);
}
TypeScript
Lindera Node.js には TypeScript の型定義が同梱されています。すべてのクラスと関数に完全な型が付いています。npm パッケージ lindera の exports マップは require / import の両方の条件を宣言しているため、CommonJS からも ES モジュールからもそのまま読み込めます。以下のサンプルは moduleResolution: node16 + strict でコンパイルできることを確認済みです:
import type { Token } from "lindera";
import { TokenizerBuilder } from "lindera";
const builder = new TokenizerBuilder();
builder.setMode("normal");
builder.setDictionary("/path/to/ipadic");
const tokenizer = builder.build();
const tokens: Token[] = tokenizer.tokenize("形態素解析");
for (const token of tokens) {
console.log(`${token.surface}: ${token.details.join(",")}`);
}
Tokenizer API
TokenizerBuilder
TokenizerBuilder はビルダーパターンを使用して Tokenizer インスタンスを設定・構築します。
コンストラクタ
new TokenizerBuilder()
デフォルト設定で新しいビルダーを作成します。
const { TokenizerBuilder } = require("lindera");
const builder = new TokenizerBuilder();
new TokenizerBuilder().fromFile(filePath)
YAML ファイルから設定を読み込み、新しいビルダーを返します。
const builder = new TokenizerBuilder().fromFile("lindera.yml");
辞書、文字フィルタ、トークンフィルタの設定を含む完全な設定ファイルの例は
lindera-nodejs/resources/lindera.yml
を参照してください。
設定メソッド
すべてのセッターメソッドはビルダー自身(this)を返すため、メソッドチェーンでも 1 文ずつの呼び出しでも設定できます。
setMode(mode, penalty?)
トークナイズモードを設定します。
"normal"-- 標準的なトークナイズ(デフォルト)"decompose"-- 複合語をより小さな単位に分解
penalty は "decompose" モードが使う Penalty オブジェクトです。null または undefined(デフォルト)では既定のペナルティを使います。"normal" とペナルティを一緒に渡すと、不正なモードと同じくエラーを投げます。オブジェクトでない値や、範囲内の整数でないフィールドを渡すと、InvalidArg のエラーを投げます。
builder.setMode("normal");
builder.setMode("decompose", { kanjiPenaltyLengthPenalty: 0 });
setDictionary(path)
システム辞書のパスまたは URI を設定します。
// 埋め込み辞書を使用
builder.setDictionary("embedded://ipadic");
// 外部辞書を使用
builder.setDictionary("/path/to/dictionary");
setUserDictionary(uri)
ユーザー辞書の URI を設定します。
builder.setUserDictionary("/path/to/user_dictionary.csv");
setKeepWhitespace(keep)
出力に空白トークンを含めるかどうかを制御します。残した空白はラティスにも残るため、他のトークンも、MeCab と同様に空白をラティス上で読み飛ばすデフォルトの出力と変わることがあります。空白を出力せずにラティスにだけ残すには、代わりに setSkipWhitespace(false) を使います。
builder.setKeepWhitespace(true);
setSkipWhitespace(skip)
空白をラティス上で読み飛ばすかどうかを設定します。値の意味は設定ファイルの segmenter.skip_whitespace と同じです。
| 値 | 効果 |
|---|---|
true | 空白を読み飛ばし、MeCab と同様に空白の前後の語を直接接続する |
false | 空白を独立したノード(空白の見出し語か SPACE の未知語)としてラティスに残す(v7 より前の動作)。空白は引き続き出力から除外する |
null または undefined | デフォルト。辞書の metadata.json の設定に従う(SudachiDict 以外では読み飛ばす) |
ここで設定した値は、辞書のデフォルトと、fromFile で読み込んだ skip_whitespace より優先されます。空白トークンを残すとき(setKeepWhitespace(true))は効果がありません。残した空白は常にラティスに残るためです。引数は省略できず、それ以外の値を渡すと Error を投げます。
たとえば IPADIC では、Google が 新しい の が は、デフォルトでは助詞(助詞)、setSkipWhitespace(false) では接続詞(接続詞)と解析されます。詳しくは Segmenter を参照してください。
// v7 より前と同じく、空白をラティスに残す
builder.setSkipWhitespace(false);
// 辞書のデフォルトに戻す
builder.setSkipWhitespace(null);
setSpacePenalty(value)
韓国語向けの左側空白ペナルティ(mecab-ko の left-space-penalty-factor)を設定します。直前に空白があり、先頭品詞タグがルールの一覧にある候補に、そのルールのコストを加算します。値の意味は設定ファイルの segmenter.space_penalty と同じです。
| 値 | 効果 |
|---|---|
null または undefined | デフォルト。辞書が metadata.json に同梱するルールがあればそれを使用 |
false | ペナルティをオフにする |
true | 辞書のルールを要求する。辞書がルールを同梱していない場合は build() が例外を投げる |
{ rules: [{ pos: [...], cost: n }, ...] } | 辞書のルールの代わりにこのルールを適用する |
それ以外の値を渡すと Error を投げます。
// ペナルティをオフにする
builder.setSpacePenalty(false);
// 明示的なルール(ko-dic が同梱するルールと同じ)
builder.setSpacePenalty({
rules: [
{ pos: ["EC", "EF", "EP", "ETM", "ETN", "VCP", "XSA", "XSN", "XSV"], cost: 3000 },
{ pos: ["JC", "JKB", "JKC", "JKG", "JKO", "JKQ", "JKS", "JKV", "JX"], cost: 6000 },
],
});
// 辞書のデフォルトに戻す
builder.setSpacePenalty(null);
[!NOTE] v6.1.0 以降、ko-dic では左側空白ペナルティがデフォルトで適用されます。ko-dic の
metadata.jsonが mecab-ko-dic のルールを同梱しているためです。これにより韓国語の出力が変わります(例:검색 이 잘 된다の이は、ペナルティがあると感動詞IC、なければ主格助詞JKSになる)。オフにするにはsetSpacePenalty(false)を呼び出してください。直接作成するTokenizerでは、4 番目の引数にfalseを渡します(new Tokenizer(dictionary, "normal", null, false))。v6.0.0 リリースからダウンロードした ko-dic はルールを同梱していないため、その辞書ではペナルティはオフのままで、setSpacePenalty(true)を指定するとbuild()が例外を投げます。詳細は Segmenter を参照してください。
appendCharacterFilter(kind, args?)
前処理パイプラインに文字フィルタを追加します。
builder.appendCharacterFilter("unicode_normalize", { kind: "nfkc" });
appendTokenFilter(kind, args?)
後処理パイプラインにトークンフィルタを追加します。
builder.appendTokenFilter("lowercase", {});
setSpacePenalty、appendCharacterFilter、appendTokenFilter の引数は、配列とオブジェクトの入れ子が 128 段までです。それより深い値や、自分自身を含む値を渡すと Error を投げます。
ビルド
build()
設定された内容で Tokenizer をビルドして返します。
const tokenizer = builder.build();
Tokenizer
Tokenizer はテキストに対して形態素解析を行います。
Tokenizer の作成
new Tokenizer(dictionary, mode?, userDictionary?, spacePenalty?, penalty?)
読み込み済みの辞書から直接トークナイザーを作成します。
spacePenalty には setSpacePenalty と同じ値を渡せます。null または undefined(デフォルト)では、辞書が同梱する左側空白ペナルティのルールを使います。不正な設定では、setSpacePenalty や build() と同じくエラーを投げます。
penalty には、setMode と同じく "decompose" モードの Penalty を渡せます。エラーも setMode と同じです。
const { Tokenizer, loadDictionary } = require("lindera");
const dictionary = loadDictionary("embedded://ipadic");
const tokenizer = new Tokenizer(dictionary, "normal");
// 左側空白ペナルティなしの ko-dic
const koTokenizer = new Tokenizer(loadDictionary("embedded://ko-dic"), "normal", null, false);
Tokenizer メソッド
tokenize(text)
入力テキストをトークナイズし、プレーンなトークンオブジェクトの配列を返します。
const tokens = tokenizer.tokenize("形態素解析");
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
text | string | トークナイズするテキスト |
戻り値: Token[]
トークンはクラスのインスタンスではなくプレーンな JavaScript オブジェクトです。そのため通常の GC で回収され、JSON.stringify、structuredClone、worker への転送を変換なしで通過します。詳細は Token を参照してください。
tokenizeSurfaces(text)
入力テキストをトークナイズし、トークンの surface のみを文字列の配列として返します。分かち書き用途の高速パスです。トークンオブジェクトを構築せず、形態素の詳細情報もロードしないため、surface 文字列だけが必要な場合は tokenize より大幅に高速です。結果は tokenizer.tokenize(text).map((t) => t.surface) と一致します。
const surfaces = tokenizer.tokenizeSurfaces("形態素解析");
// ["形態素", "解析"]
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
text | string | トークナイズするテキスト |
戻り値: string[]
tokenizeNbest(text, n, unique?, costThreshold?)
N-best トークナイズ結果を返します。各結果はトークン配列とトータルパスコストを含みます。
const results = tokenizer.tokenizeNbest("すもももももももものうち", 3);
for (const { tokens, cost } of results) {
console.log(cost, tokens.map((t) => t.surface));
}
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
text | string | トークナイズするテキスト |
n | number | 返す結果の数。0 以上の整数 |
unique | boolean | 結果の重複を排除(デフォルト: false) |
costThreshold | number | undefined | 最良パスからの最大コスト差(デフォルト: undefined) |
戻り値: NbestResult[]。各 NbestResult は { tokens: Token[], cost: number } です。
Token
Token は単一の形態素トークンを表すプレーンオブジェクトです。クラスではなく
TypeScript の interface であり、メソッドもプロトタイプも持ちません。
各フィールドは直接読み出します。
プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
surface | string | トークンの表層形 |
byteStart | number | 元テキストでの開始バイト位置 |
byteEnd | number | 元テキストでの終了バイト位置 |
position | number | トークンの位置インデックス |
wordId | number | 辞書の単語 ID |
isUnknown | boolean | 辞書に登録されていない単語の場合 true |
details | string[] | 形態素の詳細情報(品詞、読みなど) |
詳細情報の読み出し
details を直接インデックスします。範囲外のインデックスは undefined になります。
const token = tokenizer.tokenize("東京")[0];
const pos = token.details[0]; // 例: "名詞"
const subpos = token.details[1]; // 例: "固有名詞"
const reading = token.details[7]; // 例: "トウキョウ"
details の構造は辞書によって異なります:
- IPADIC:
[品詞, 品詞細分類1, 品詞細分類2, 品詞細分類3, 活用型, 活用形, 原形, 読み, 発音] - UniDic: UniDic 仕様に準拠した詳細な形態素情報
- ko-dic / CC-CEDICT / Jieba: 各辞書固有の詳細フォーマット
Mode と Penalty
TokenizerBuilder.setMode() と Tokenizer のコンストラクタは、モードを単純な文字列
("normal" または "decompose")で、decompose モードのペナルティを Penalty
オブジェクトで受け取ります。
Mode
2つの値を持つ文字列列挙型です:
Mode.Normal-- 辞書コストに基づく標準的なトークナイズMode.Decompose-- ペナルティベースの複合語分解
Penalty
decompose モードのペナルティパラメータを持つプレーンなオブジェクト(TypeScript の型は Penalty)です。フィールドはすべて省略でき、省略したフィールドはデフォルト値になります:
| プロパティ | 型 | 説明 |
|---|---|---|
kanjiPenaltyLengthThreshold | number | ペナルティを適用する前の漢字列の長さの閾値。0 以上の整数(デフォルト: 2) |
kanjiPenaltyLengthPenalty | number | 長い漢字列に対するペナルティ値。32 ビット符号付き整数(デフォルト: 3000) |
otherPenaltyLengthThreshold | number | ペナルティを適用する前のその他の文字列の長さの閾値。0 以上の整数(デフォルト: 7) |
otherPenaltyLengthPenalty | number | 長いその他の文字列に対するペナルティ値。32 ビット符号付き整数(デフォルト: 1700) |
"decompose" と一緒に setMode() か Tokenizer のコンストラクタ に渡します。長さのペナルティをなくすと、decompose モードでも長い語が分割されずに残ります:
const { Tokenizer, TokenizerBuilder, loadDictionary } = require("lindera");
const text = "関西国際空港限定トートバッグ";
const penalty = { kanjiPenaltyLengthPenalty: 0, otherPenaltyLengthPenalty: 0 };
const surfaces = (tokenizer) => tokenizer.tokenize(text).map((token) => token.surface);
const builder = new TokenizerBuilder().setDictionary("embedded://ipadic");
console.log(surfaces(builder.setMode("decompose").build()));
// [ '関西', '国際', '空港', '限定', 'トートバッグ' ]
console.log(surfaces(builder.setMode("decompose", penalty).build()));
// [ '関西国際空港', '限定', 'トートバッグ' ]
const tokenizer = new Tokenizer(loadDictionary("embedded://ipadic"), "decompose", null, null, penalty);
console.log(surfaces(tokenizer));
// [ '関西国際空港', '限定', 'トートバッグ' ]
"normal" モードと一緒にペナルティを渡すとエラーを投げます。
辞書管理
Lindera Node.js は、形態素解析で使用する辞書の読み込み、ビルド、管理のための関数を提供します。
辞書の読み込み
システム辞書
loadDictionary(uri) を使用してシステム辞書を読み込みます。GitHub Releases からビルド済み辞書をダウンロードし、展開したディレクトリのパスを指定してください:
const { loadDictionary } = require("lindera");
const dictionary = loadDictionary("/path/to/ipadic");
埋め込み辞書(上級者向け) -- embed-* feature フラグ付きでビルドした場合、埋め込み辞書を使用できます:
const dictionary = loadDictionary("embedded://ipadic");
Dictionary には、読み込んだ辞書のメタデータを参照するための読み取り専用アクセサがいくつか用意されています:
console.log(dictionary.metadataName()); // 例: "ipadic"
console.log(dictionary.metadataEncoding()); // 例: "UTF-8"
const metadata = dictionary.metadata(); // Metadata オブジェクト全体
console.log(metadata.defaultWordCost);
ユーザー辞書
ユーザー辞書はシステム辞書にカスタム語彙を追加します。組み合わせるシステム辞書に 合わせて読み込みます:
const { loadDictionary, loadUserDictionaryFor } = require("lindera");
const dictionary = loadDictionary("/path/to/ipadic");
const userDict = loadUserDictionaryFor("/path/to/user_dictionary.csv", dictionary);
.csv はシステム辞書のメタデータでビルドし、ビルドしながら接続コスト行列と
照らして確認します。文脈 ID が行列の範囲外にある行は、その CSV の行番号と表層形を
示すエラーになります(文脈 ID
を参照)。.bin はビルドされたとおりに読み込みます。
loadUserDictionary(uri, metadata) は、メタデータだけでユーザー辞書を
読み込みます。この方法で読み込んだ .csv はシステム辞書と組み合わせるときに
確認し、エラーは見出し語をその単語 ID で示します:
const { loadUserDictionary, Metadata } = require("lindera");
const metadata = new Metadata();
const userDict = loadUserDictionary("/path/to/user_dictionary.csv", metadata);
トークナイザーのビルド時にユーザー辞書を渡します:
const { Tokenizer, loadDictionary, loadUserDictionaryFor } = require("lindera");
const dictionary = loadDictionary("/path/to/ipadic");
const userDict = loadUserDictionaryFor("/path/to/user_dictionary.csv", dictionary);
const tokenizer = new Tokenizer(dictionary, "normal", userDict);
または、ビルダー経由で設定します:
const { TokenizerBuilder } = require("lindera");
const builder = new TokenizerBuilder();
builder.setDictionary("/path/to/ipadic");
builder.setUserDictionary("/path/to/user_dictionary.csv");
const tokenizer = builder.build();
辞書のビルド
システム辞書のビルド
ソースファイルからシステム辞書をビルドします:
const { buildDictionary, Metadata } = require("lindera");
const metadata = new Metadata({ name: "custom", encoding: "UTF-8" });
buildDictionary("/path/to/input_dir", "/path/to/output_dir", metadata);
入力ディレクトリには辞書のソースファイル(CSV レキシコン、matrix.def など)が含まれている必要があります。
辞書の metadata.json に含まれる設定のままビルドするには、Metadata.fromJsonFile() でファイルを読み込み、その結果を渡します。ファイルの space_penalty ルールは保持されるため、この方法でビルドした ko-dic では左側空白ペナルティがデフォルトで適用されます(setSpacePenalty を参照)。new Metadata(...) で作成した Metadata はルールを持ちません。
const metadata = Metadata.fromJsonFile("/path/to/lindera-ko-dic/metadata.json");
buildDictionary("/path/to/mecab-ko-dic", "/path/to/output_dir", metadata);
ユーザー辞書のビルド
組み合わせるシステム辞書に合わせて、CSV ファイルからユーザー辞書をビルドします:
const { buildUserDictionaryFor, loadDictionary, loadUserDictionaryFor } = require("lindera");
const dictionary = loadDictionary("/path/to/ipadic");
buildUserDictionaryFor("user_words.csv", "/path/to/user_dictionary.bin", dictionary);
const userDict = loadUserDictionaryFor("/path/to/user_dictionary.bin", dictionary);
CSV はシステム辞書のメタデータでビルドし、接続コスト行列と照らして確認します。
文脈 ID が行列の範囲外にある行は、その CSV の行番号と表層形を示すエラーを投げ、
ファイルは書き出しません(文脈 ID
を参照)。2 番目の引数は書き出すファイルのパスです。ローダーは拡張子で
ユーザー辞書の読み込み方を決めるので、.bin で終えてください。
buildUserDictionary(kind, inputFile, outputDir, metadata) は、メタデータだけで
ユーザー辞書をビルドします。outputDir も、名前に反してディレクトリではなく、
書き出すファイルのパスです。各行はシステム辞書と組み合わせるときに確認し、
エラーは見出し語をその単語 ID で示します:
const { buildUserDictionary, Metadata } = require("lindera");
const metadata = new Metadata();
buildUserDictionary("ipadic", "user_words.csv", "/path/to/user_dictionary.bin", metadata);
metadata パラメータは省略可能です。省略した場合はデフォルトのメタデータ値が使用されます:
buildUserDictionary("ipadic", "user_words.csv", "/path/to/user_dictionary.bin");
[!NOTE] 第一引数(
kind)は現状未使用です -- 将来の拡張のために予約されており、ビルド結果には 影響しません。現時点では任意の文字列を渡しても問題ありません。システム辞書に合わせて ビルドするにはbuildUserDictionaryForを使います。
Metadata
Metadata クラスは辞書のパラメータを設定します。
Metadata の作成
const { Metadata } = require("lindera");
// デフォルトのメタデータ
const metadata = new Metadata();
// カスタムメタデータ
const metadata = new Metadata({
name: "my_dictionary",
encoding: "UTF-8",
defaultWordCost: -10000,
});
JSON からの読み込み
const metadata = Metadata.fromJsonFile("metadata.json");
プロパティ
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | string | "default" | 辞書名 |
encoding | string | "UTF-8" | 文字エンコーディング |
defaultWordCost | number | -10000 | 未知語のデフォルトコスト。-32768 から 32767 の整数 |
defaultLeftContextId | number | 1288 | デフォルトの左文脈 ID。0 から 65535 の整数 |
defaultRightContextId | number | 1288 | デフォルトの右文脈 ID。0 から 65535 の整数 |
defaultFieldValue | string | "*" | 欠損フィールドのデフォルト値 |
flexibleCsv | boolean | false | 柔軟な CSV パースを許可 |
skipInvalidCostOrId | boolean | false | 無効なコストまたは ID のエントリーをスキップ |
[!NOTE] このバインディングの
Metadataオブジェクトには、スキーマ情報(辞書/ユーザー辞書の フィールド構成)は公開されていません --dictionarySchema/userDictionarySchemaプロパティは存在しません。代わりに独立したSchemaクラスを使用してください。
すべてのプロパティは取得と設定の両方をサポートしています:
const metadata = new Metadata();
metadata.name = "custom_dict";
metadata.encoding = "EUC-JP";
console.log(metadata.name); // "custom_dict"
toObject()
メタデータのオブジェクト表現を返します。値はすべて文字列です:
const metadata = new Metadata({ name: "test" });
console.log(metadata.toObject());
メタデータが左側空白ペナルティのルールを持つ場合(ko-dic の metadata.json から読み込んだ場合など)、オブジェクトにはルールを JSON 文字列で保持する spacePenalty エントリーも含まれます。ルールを持たない場合、このキーはありません。
const koDic = Metadata.fromJsonFile("/path/to/lindera-ko-dic/metadata.json");
const { rules } = JSON.parse(koDic.toObject().spacePenalty);
console.log(rules[1].pos); // ["JC", "JKB", "JKC", "JKG", "JKO", "JKQ", "JKS", "JKV", "JX"]
Schema
Schema クラスは辞書エントリーのフィールド構造を定義します。
Schema の作成
const { Schema } = require("lindera");
// デフォルトの IPADIC 互換スキーマ
const schema = Schema.createDefault();
// カスタムスキーマ
const custom = new Schema(["surface", "left_id", "right_id", "cost", "pos", "reading"]);
Schema メソッド
| メソッド | 戻り値 | 説明 |
|---|---|---|
getFieldIndex(name) | number | null | フィールド名からインデックスを取得 |
fieldCount() | number | フィールドの総数 |
getFieldName(index) | string | null | インデックスからフィールド名を取得 |
getCustomFields() | string[] | インデックス 4 以降のフィールド(形態素素性) |
getAllFields() | string[] | すべてのフィールド名 |
getFieldByName(name) | FieldDefinition | null | フィールド定義の完全な情報を取得 |
validateRecord(record) | void | CSV レコードをスキーマに対して検証 |
const schema = Schema.createDefault();
console.log(schema.fieldCount()); // 13(IPADIC フォーマット)
console.log(schema.getFieldIndex("pos1")); // 例: 4
console.log(schema.getAllFields()); // ["surface", "left_id", ...]
console.log(schema.getCustomFields()); // インデックス 4 以降のフィールド
FieldDefinition
| プロパティ | 型 | 説明 |
|---|---|---|
index | number | フィールドの位置インデックス |
name | string | フィールド名 |
fieldType | FieldType | フィールド型の列挙値 |
description | string | undefined | 任意の説明 |
FieldType
| 値 | 説明 |
|---|---|
FieldType.Surface | 単語の表層形 |
FieldType.LeftContextId | 左文脈 ID |
FieldType.RightContextId | 右文脈 ID |
FieldType.Cost | 単語コスト |
FieldType.Custom | 形態素素性フィールド |
テキスト処理パイプライン
Lindera Node.js は、トークナイズ前に文字フィルタを適用し、トークナイズ後にトークンフィルタを適用する、組み合わせ可能なテキスト処理パイプラインをサポートしています。フィルタは TokenizerBuilder に追加され、追加された順序で実行されます。
Input Text
--> Character Filters (preprocessing)
--> Tokenization
--> Token Filters (postprocessing)
--> Output Tokens
[!NOTE] このページではよく使われる一部のフィルタのみを例として紹介しています。これが全リストではありません。
lindera-analysisが提供する全フィルタ(パラメータ・使用例を含む)の正式なカタログについてはフィルタを参照してください。
文字フィルタ
文字フィルタはトークナイズ前に入力テキストを変換します。
unicode_normalize
入力テキストに Unicode 正規化を適用します。
const { TokenizerBuilder } = require("lindera");
const builder = new TokenizerBuilder();
builder.setDictionary("embedded://ipadic");
builder.appendCharacterFilter("unicode_normalize", { kind: "nfkc" });
const tokenizer = builder.build();
サポートされる正規化形式: "nfc"、"nfkc"、"nfd"、"nfkd"。
mapping
マッピングテーブルに従って文字や文字列を置換します。
const builder = new TokenizerBuilder();
builder.setDictionary("embedded://ipadic");
builder.appendCharacterFilter("mapping", {
mapping: {
"\u30fc": "-",
"\uff5e": "~",
},
});
const tokenizer = builder.build();
japanese_iteration_mark
日本語の踊り字(繰り返し記号)を完全な形に展開します。
const builder = new TokenizerBuilder();
builder.setDictionary("embedded://ipadic");
builder.appendCharacterFilter("japanese_iteration_mark", {
normalize_kanji: true,
normalize_kana: true,
});
const tokenizer = builder.build();
トークンフィルタ
トークンフィルタはトークナイズ後にトークンを変換または除去します。
lowercase
トークンの表層形を小文字に変換します。
const builder = new TokenizerBuilder();
builder.setDictionary("embedded://ipadic");
builder.appendTokenFilter("lowercase", {});
const tokenizer = builder.build();
japanese_base_form
辞書の形態素情報を使用して、活用形を基本形(辞書形)に置換します。
const builder = new TokenizerBuilder();
builder.setDictionary("embedded://ipadic");
builder.appendTokenFilter("japanese_base_form", {});
const tokenizer = builder.build();
japanese_stop_tags
指定されたタグに一致する品詞のトークンを除去します。
const builder = new TokenizerBuilder();
builder.setDictionary("embedded://ipadic");
builder.appendTokenFilter("japanese_stop_tags", {
tags: ["助詞,格助詞,一般", "助詞,係助詞", "助詞,連体化", "助動詞"],
});
const tokenizer = builder.build();
[!NOTE] タグは 4 階層のカンマ区切りに正規化され(不足分は
*で補完)、各トークンの品詞詳細 4 つ(辞書スキーマでpart_of_speechという名前のフィールドから始まる 4 つ。スキーマにそのフィールドが無い場合は先頭の詳細から)と完全一致で比較されます。 IPADIC の助詞トークンは必ず助詞,係助詞のようにサブカテゴリを持つため、助詞単独では一致しません。 一方、助動詞はサブカテゴリを持たないため(助動詞,*,*,*)、助動詞単独で一致します。
japanese_keep_tags
指定されたタグに一致する品詞のトークンのみを保持します。その他のトークンはすべて除去されます。
const builder = new TokenizerBuilder();
builder.setDictionary("embedded://ipadic");
builder.appendTokenFilter("japanese_keep_tags", {
tags: ["名詞,一般"],
});
const tokenizer = builder.build();
パイプラインの完全な例
以下の例では、複数の文字フィルタとトークンフィルタを1つのパイプラインに組み合わせています:
const { TokenizerBuilder } = require("lindera");
const builder = new TokenizerBuilder();
builder.setMode("normal");
builder.setDictionary("embedded://ipadic");
// Preprocessing
builder.appendCharacterFilter("unicode_normalize", { kind: "nfkc" });
builder.appendCharacterFilter("japanese_iteration_mark", {
normalize_kanji: true,
normalize_kana: true,
});
// Postprocessing
builder.appendTokenFilter("japanese_base_form", {});
builder.appendTokenFilter("japanese_stop_tags", {
tags: ["助詞,格助詞,一般", "助詞,係助詞", "助詞,連体化", "助動詞", "記号,句点", "記号,読点"],
});
builder.appendTokenFilter("lowercase", {});
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("Linderaは形態素解析を行うライブラリです。");
for (const token of tokens) {
console.log(`${token.surface}\t${token.details.join(",")}`);
}
このパイプラインでは:
unicode_normalizeが全角文字を半角に変換(NFKC 正規化)japanese_iteration_markが踊り字を展開japanese_base_formが活用形のトークンを基本形に変換japanese_stop_tagsが助詞(格助詞・係助詞・連体化)・助動詞・句読点を除去lowercaseがアルファベットを小文字に正規化
学習
Lindera Node.js は、アノテーション付きコーパスからカスタム CRF ベースの形態素解析モデルを学習する機能をサポートしています。この機能には train feature が必要です。
前提条件
train feature を有効にして lindera-nodejs をビルドします(デフォルトで有効):
npm run build -- --features train
モデルの学習
[!NOTE] 以下で示すファイルパス(
resources/training/*.csv、*.def)はあくまで例示用のプレースホルダーであり、 このリポジトリにこれらのファイルが実際に同梱されているわけではありません。種辞書・コーパス・各種定義ファイルを その場で生成し、実際に学習・エクスポート・辞書ビルドまで一気通貫で行う完全な実行可能サンプルはlindera-nodejs/examples/train_and_export.jsを参照してください。
train() を使用して、種辞書とアノテーション付きコーパスから CRF モデルを学習します:
const { train } = require("lindera");
train({
seed: "resources/training/seed.csv",
corpus: "resources/training/corpus.txt",
charDef: "resources/training/char.def",
unkDef: "resources/training/unk.def",
featureDef: "resources/training/feature.def",
rewriteDef: "resources/training/rewrite.def",
output: "/tmp/model.dat",
lambda: 0.01,
maxIter: 100,
maxThreads: 4,
});
学習パラメータ
| パラメータ | 型 | デフォルト | 説明 |
|---|---|---|---|
seed | string | 必須 | 種辞書ファイルのパス(CSV 形式) |
corpus | string | 必須 | アノテーション付き学習コーパスのパス |
charDef | string | 必須 | 文字定義ファイルのパス(char.def) |
unkDef | string | 必須 | 未知語定義ファイルのパス(unk.def) |
featureDef | string | 必須 | 素性定義ファイルのパス(feature.def) |
rewriteDef | string | 必須 | 書き換えルール定義ファイルのパス(rewrite.def) |
output | string | 必須 | 学習済みモデルファイルの出力パス |
lambda | number | 0.01 | L1 正則化コスト(0.0--1.0) |
maxIter | number | 100 | 最大学習イテレーション数 |
maxThreads | number | undefined | undefined | スレッド数(undefined = CPU コア数を自動検出) |
学習済みモデルのエクスポート
学習後、exportModel() を使用してモデルを辞書ソースファイルにエクスポートします:
const { exportModel } = require("lindera");
exportModel({
model: "/tmp/model.dat",
output: "/tmp/dictionary_source",
metadata: "resources/training/metadata.json",
});
エクスポートパラメータ
| パラメータ | 型 | デフォルト | 説明 |
|---|---|---|---|
model | string | 必須 | 学習済みモデルファイルのパス(.dat) |
output | string | 必須 | 辞書ソースファイルの出力ディレクトリ |
metadata | string | undefined | undefined | ベースとなる metadata.json ファイルのパス |
エクスポートにより、出力ディレクトリに以下のファイルが作成されます。lindera export が作成するファイルと同じです:
lex.csv-- 学習済みコスト付きのレキシコンエントリーmatrix.def-- 連接コスト行列unk.def-- 未知語定義char.def-- 文字カテゴリ定義(学習済みモデルからコピー)feature.def-- 素性テンプレート定義(学習済みモデルからコピー)rewrite.def-- 素性リライトルール(学習済みモデルからコピー)left-id.def-- 左文脈IDから素性文字列へのマッピングright-id.def-- 右文脈IDから素性文字列へのマッピングmetadata.json-- 更新されたメタデータ(metadataパラメータ指定時)
モデルファイルは、このバージョンの学習で作ったものである必要があります。それより前のバージョンで書かれたモデルファイル(7.0.0 より前に書かれたものはすべて該当)は拒否され、exportModel() は学習のやり直しを求めるエラーで失敗します。そうしたモデルは train() で学習し直してください。
完全なワークフロー
カスタム辞書の学習と使用の完全なワークフロー:
const {
train,
exportModel,
buildDictionary,
Metadata,
TokenizerBuilder,
} = require("lindera");
// Step 1: Train the CRF model
train({
seed: "resources/training/seed.csv",
corpus: "resources/training/corpus.txt",
charDef: "resources/training/char.def",
unkDef: "resources/training/unk.def",
featureDef: "resources/training/feature.def",
rewriteDef: "resources/training/rewrite.def",
output: "/tmp/model.dat",
lambda: 0.01,
maxIter: 100,
});
// Step 2: Export to dictionary source files
exportModel({
model: "/tmp/model.dat",
output: "/tmp/dictionary_source",
metadata: "resources/training/metadata.json",
});
// Step 3: Build the dictionary from exported source files
const metadata = Metadata.fromJsonFile("/tmp/dictionary_source/metadata.json");
buildDictionary("/tmp/dictionary_source", "/tmp/dictionary", metadata);
// Step 4: Use the trained dictionary
const builder = new TokenizerBuilder();
builder.setDictionary("/tmp/dictionary");
builder.setMode("normal");
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("形態素解析のテスト");
for (const token of tokens) {
console.log(`${token.surface}\t${token.details.join(",")}`);
}
Lindera Ruby
Lindera Ruby は、Magnus と rb-sys を使用して構築された Lindera 形態素解析エンジンの Ruby バインディングです。Ruby 3.1 以降をサポートし、Lindera の高性能なトークナイズ機能を Ruby エコシステムに提供します。
特徴
- 多言語対応: 日本語(IPADIC、IPADIC NEologd、UniDic)、韓国語(ko-dic)、中国語(CC-CEDICT、Jieba)のテキストをトークナイズ
- テキスト処理パイプライン: 文字フィルタとトークンフィルタを組み合わせて、柔軟な前処理・後処理が可能
- CRF ベースの辞書学習: アノテーション付きコーパスからカスタム形態素解析モデルを学習(
trainfeature が必要) - 複数のトークナイズモード: 解析粒度に応じた Normal モードと Decompose モード
- N-best トークナイズ: コスト順にランク付けされた複数のトークナイズ候補を取得
- ユーザー辞書: システム辞書をカスタム語彙で拡張
ドキュメント
- インストール -- 前提条件、ビルド手順、feature フラグ
- クイックスタート -- 最小限の使用例
- Tokenizer API --
TokenizerBuilder、Tokenizer、Tokenクラスリファレンス - 辞書管理 -- 辞書の読み込み、ビルド、管理
- テキスト処理パイプライン -- 文字フィルタとトークンフィルタ
- 学習 -- カスタム CRF モデルの学習と辞書のエクスポート
インストール
v6.1.0 以降の lindera-ruby は、RubyGems から lindera としてインストールできます。ソース gem のため、gem install の実行時に同梱の Rust ソースからネイティブ拡張をコンパイルします(数分かかります)。
前提条件
- Ruby 3.1 以降と、ネイティブ gem のビルドに必要なツール(C コンパイラと
make) - Rust ツールチェーン 1.89 以降 -- rustup 経由でインストール
- libclang -- Debian/Ubuntu は
libclang-dev、macOS は Xcode コマンドラインツール - Bundler -- ソースからビルドする場合のみ(
gem install bundler)
RubyGems からのインストール
gem install lindera
または Gemfile に追加します:
gem "lindera"
[!NOTE] lindera v6.1.0 以降を使用してください。v6.1.0 より前に公開された
linderagem は、インストール時のコンパイルに失敗します。それらのバージョンは、以下の手順でソースからビルドしてください。
gem は crates.io 上の同じバージョンの lindera と lindera-binding に対してコンパイルされます(この 2 つはバージョンを完全に固定しています)。gem には Cargo.lock が含まれないため、それ以外の依存(ほかの lindera-* クレートを含む)は、インストール時に互換性のある最新のリリースに解決されます。デフォルトの feature(train)のみを有効にし、辞書は埋め込みません。辞書は辞書の入手を参照してください。その他の Feature フラグを有効にするには、インストール時に LINDERA_FEATURES を指定します:
LINDERA_FEATURES="embed-ipadic" gem install lindera
辞書の入手
Lindera はパッケージに辞書を同梱していません。ビルド済み辞書を別途入手する必要があります。
GitHub Releases からのダウンロード
ビルド済み辞書は GitHub Releases ページから入手できます。辞書アーカイブをダウンロードしてローカルディレクトリに展開してください:
# 例: IPADIC 辞書のダウンロードと展開
curl -LO https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip
unzip lindera-ipadic-<version>.zip -d /path/to/ipadic
開発ビルド
lindera-ruby を開発モードでビルドしてインストールします:
cd lindera-ruby
bundle install
bundle exec rake compile
または、プロジェクトの Makefile を使用します:
make build-lindera-ruby
make test-lindera-ruby を実行すると、Rust のユニットテストと Ruby の minitest
スイートの両方が実行されます。
gem のビルド
bundle exec rake build(または make package-lindera-ruby)は、ソース gem を
lindera-ruby/pkg/ に出力します。cargo package が正規化したクレートを同梱するため、
gem はこのリポジトリの外でも、crates.io 上の同じバージョンの lindera と
lindera-binding に対してコンパイルできます。gem build を直接実行しないで
ください。クレート自身の Cargo.toml は Cargo ワークスペースの中でしか解決できません。
make test-lindera-ruby-gem(Docker が必要)は、gem をビルドし、クリーンな ruby
コンテナで gem install を実行して、テキストをトークナイズします。このテストは
lindera クレートをチェックアウトからコンパイルするため、crates.io に未公開の
バージョンでも実行できます。
学習機能付きビルド
train feature は、CRF ベースの辞書学習機能を有効にします。デフォルトで有効です:
LINDERA_FEATURES="train" bundle exec rake compile
Feature フラグ
Feature は環境変数 LINDERA_FEATURES にカンマ区切りリストで指定します。bundle exec rake compile と gem install のどちらでも使えます。
| Feature | 説明 | デフォルト |
|---|---|---|
train | CRF 学習機能 | 有効 |
embed-ipadic | 日本語辞書(IPADIC)をバイナリに埋め込み | 無効 |
embed-unidic | 日本語辞書(UniDic)をバイナリに埋め込み | 無効 |
embed-sudachidict | 日本語辞書(SudachiDict)をバイナリに埋め込み | 無効 |
embed-ipadic-neologd | 日本語辞書(IPADIC NEologd)をバイナリに埋め込み | 無効 |
embed-ko-dic | 韓国語辞書(ko-dic)をバイナリに埋め込み | 無効 |
embed-cc-cedict | 中国語辞書(CC-CEDICT)をバイナリに埋め込み | 無効 |
embed-jieba | 中国語辞書(Jieba)をバイナリに埋め込み | 無効 |
embed-cjk | 全 CJK 辞書をバイナリに埋め込み(IPADIC、ko-dic、Jieba) | 無効 |
複数の feature を組み合わせることができます:
LINDERA_FEATURES="train,embed-ipadic,embed-ko-dic" bundle exec rake compile
[!TIP] 辞書をバイナリに直接埋め込みたい場合(上級者向け)は、対応する
embed-*feature フラグを有効にしてビルドし、embedded://スキームでロードしてください:dictionary = Lindera.load_dictionary("embedded://ipadic")詳細は Feature フラグ を参照してください。
インストールの確認
インストール後、Ruby で lindera が利用可能であることを確認します:
require 'lindera'
puts Lindera.version
クイックスタート
このガイドでは、lindera-ruby を使用してテキストをトークナイズする方法を紹介します。
基本的なトークナイズ
トークナイザーの作成には Lindera::TokenizerBuilder の使用を推奨します:
require 'lindera'
builder = Lindera::TokenizerBuilder.new
builder.set_mode('normal')
builder.set_dictionary('/path/to/ipadic')
tokenizer = builder.build
tokens = tokenizer.tokenize('関西国際空港限定トートバッグ')
tokens.each do |token|
puts "#{token.surface}\t#{token.details.join(',')}"
end
注意: ビルド済み辞書を GitHub Releases からダウンロードし、展開したディレクトリのパスを指定してください。
期待される出力:
関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ 名詞,一般,*,*,*,*,*,*,*
逐次的な設定
TokenizerBuilder は逐次的なメソッド呼び出しで設定します:
require 'lindera'
builder = Lindera::TokenizerBuilder.new
builder.set_mode('normal')
builder.set_dictionary('/path/to/ipadic')
tokenizer = builder.build
tokens = tokenizer.tokenize('すもももももももものうち')
tokens.each do |token|
puts "#{token.surface}\t#{token.get_detail(0)}"
end
トークンプロパティへのアクセス
各トークンは以下のプロパティを公開しています:
require 'lindera'
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('/path/to/ipadic')
tokenizer = builder.build
tokens = tokenizer.tokenize('東京タワー')
tokens.each do |token|
puts "Surface: #{token.surface}"
puts "Byte range: #{token.byte_start}..#{token.byte_end}"
puts "Position: #{token.position}"
puts "Word ID: #{token.word_id}"
puts "Unknown: #{token.unknown?}"
puts "Details: #{token.details}"
puts
end
N-best トークナイズ
コスト順にランク付けされた複数のトークナイズ候補を取得します:
require 'lindera'
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('/path/to/ipadic')
tokenizer = builder.build
results = tokenizer.tokenize_nbest('すもももももももものうち', 3, false, nil)
results.each do |tokens, cost|
surfaces = tokens.map(&:surface)
puts "Cost #{cost}: #{surfaces.join(' / ')}"
end
Tokenizer API
TokenizerBuilder
Lindera::TokenizerBuilder はビルダーパターンを使用して Tokenizer インスタンスを設定・構築します。
コンストラクタ
Lindera::TokenizerBuilder.new
デフォルト設定で新しいビルダーを作成します。
require 'lindera'
builder = Lindera::TokenizerBuilder.new
Lindera::TokenizerBuilder.from_file(file_path)
JSON ファイルから設定を読み込み、新しいビルダーを返します。これはクラスメソッドであり、既存のインスタンスに対してチェーンするものではありません。
builder = Lindera::TokenizerBuilder.from_file('config.json')
設定メソッド
set_mode(mode, penalty: nil)
トークナイズモードを設定します。
"normal"-- 標準的なトークナイズ(デフォルト)"decompose"-- 複合語をより小さな単位に分解
キーワード引数 penalty: には、"decompose" モードが使う Lindera::Penalty を渡します。nil(デフォルト)では既定のペナルティを使います。"normal" とペナルティを一緒に渡すと、不正なモードと同じく RuntimeError が、Lindera::Penalty でない値を渡すと TypeError が発生します。
builder.set_mode('normal')
builder.set_mode('decompose', penalty: Lindera::Penalty.new(nil, 0, nil, nil))
set_dictionary(path)
システム辞書のパスまたは URI を設定します。
# 埋め込み辞書を使用
builder.set_dictionary('embedded://ipadic')
# 外部辞書を使用
builder.set_dictionary('/path/to/dictionary')
set_user_dictionary(uri)
ユーザー辞書の URI を設定します。
builder.set_user_dictionary('/path/to/user_dictionary.csv')
set_keep_whitespace(keep)
出力に空白トークンを含めるかどうかを制御します。残した空白はラティスにも残るため、他のトークンも、MeCab と同様に空白をラティス上で読み飛ばすデフォルトの出力と変わることがあります。空白を出力せずにラティスにだけ残すには、代わりに set_skip_whitespace(false) を使います。
builder.set_keep_whitespace(true)
set_skip_whitespace(skip)
空白をラティス上で読み飛ばすかどうかを設定します。skip の意味は設定ファイルの segmenter.skip_whitespace と同じです:
true-- 空白を読み飛ばし、MeCab と同様に空白の前後の語を直接接続します。false-- 空白を独立したノード(空白の見出し語かSPACEの未知語)としてラティスに残します(v7 より前の動作)。空白は引き続き出力から除外されます。nil-- 辞書のmetadata.jsonのデフォルトです。SudachiDict 以外では読み飛ばします。以前の呼び出しを取り消すときに使います。
ここで設定した値は、辞書のデフォルトと、TokenizerBuilder.from_file で読み込んだ skip_whitespace より優先されます。空白トークンを残すとき(set_keep_whitespace(true))は効果がありません。残した空白は常にラティスに残るためです。それ以外の値は、set_keep_whitespace と同じく Ruby の真偽値の規則で扱われ、false と nil 以外は読み飛ばしを有効にします。
たとえば IPADIC では、Google が 新しい の が は、デフォルトでは助詞(助詞)、set_skip_whitespace(false) では接続詞(接続詞)と解析されます。詳しくは Segmenter を参照してください。
# v7 より前と同じく、空白をラティスに残す
builder.set_skip_whitespace(false)
set_space_penalty(value)
韓国語向けの左側空白ペナルティ(left-space penalty)を設定します。直前に空白がある候補のうち、本来は前の語に空白なしで付く品詞(助詞、語尾など)の候補にコストを加算します。value の意味は設定ファイルの segmenter.space_penalty と同じです:
nil-- デフォルト。辞書がmetadata.jsonに同梱するルールがあればそれを使います(ko-dic は mecab-ko-dic のルールを同梱し、他の同梱辞書はルールを持ちません)。以前の呼び出しを取り消すときにも使います。false-- ペナルティをオフにします。true-- 辞書のルールを要求します。ルールを同梱しない辞書ではbuildがRuntimeErrorを発生させます。- 文字列キーのハッシュ
{ 'rules' => [{ 'pos' => [...], 'cost' => n }, ...] }-- 辞書のルールの代わりにこのルールを適用します。先頭品詞タグがposに含まれる候補にcostを加算し、最初に一致したルールが使われます。
これらの形式に当てはまらない値(1、'false'、{ 'rules' => 'JKS' } など)では RuntimeError が発生します。変換できない値では TypeError が発生します。該当するのは Symbol(ハッシュのキーや要素に含まれる場合も含むため、%i[JKS] ではなく %w[JKS] と書きます)、有限でない Float(NaN や Infinity)、64 ビットの範囲を超える Integer、および nil・true・false・Integer・Float・String・Array・Hash 以外のオブジェクトです。
# ペナルティをオフにする
builder.set_space_penalty(false)
# 明示的なルール
builder.set_space_penalty({
'rules' => [
{ 'pos' => %w[EC EF EP ETM ETN VCP XSA XSN XSV], 'cost' => 3000 },
{ 'pos' => %w[JC JKB JKC JKG JKO JKQ JKS JKV JX], 'cost' => 6000 }
]
})
[!NOTE] v6.1.0 から、ko-dic では mecab-ko と同じく左側空白ペナルティがデフォルトで適用されます。たとえば
검색 이 잘 된다の이は感動詞ICと解析されます(ペナルティがなければ主格助詞JKS)。オフにするにはbuilder.set_space_penalty(false)を呼び出してください。Lindera::Tokenizer.new(dictionary, ...)で直接作成したトークナイザーは常にデフォルトを使うため、オフにするにはTokenizerBuilderを使ってください。ルールを同梱しているのは Lindera 6.1.0 以降でビルドした ko-dic だけです。v6.0.0 リリースの ko-dic ではペナルティはオフのままで、辞書を再ビルドするまでtrueはエラーになります。詳しくは Segmenter を参照してください。
append_character_filter(kind, args)
前処理パイプラインに文字フィルタを追加します。
builder.append_character_filter('unicode_normalize', { 'kind' => 'nfkc' })
append_token_filter(kind, args)
後処理パイプラインにトークンフィルタを追加します。args が不要な場合は nil を渡します。
builder.append_token_filter('lowercase', nil)
set_space_penalty、append_character_filter、append_token_filter の引数は、配列と hash の入れ子が 128 段までです。それより深い値や、自分自身を含む値を渡すと ArgumentError が発生します。
ビルド
build
設定された内容で Tokenizer をビルドして返します。
tokenizer = builder.build
Tokenizer
Lindera::Tokenizer はテキストに対して形態素解析を行います。
Tokenizer の作成
Lindera::Tokenizer.new(dictionary, mode = nil, user_dictionary = nil, space_penalty: nil, penalty: nil)
読み込み済みの辞書から直接トークナイザーを作成します。mode のデフォルトは 'normal' で、user_dictionary は省略できます。
キーワード引数 space_penalty: には TokenizerBuilder#set_space_penalty と同じ値を渡せます。nil(デフォルト)では辞書が同梱する左側空白ペナルティのルールを使うため、ko-dic ではペナルティがオンになります。オフにするには false を渡します。不正な設定では、set_space_penalty や build と同じエラーが発生します。
キーワード引数 penalty: には、TokenizerBuilder#set_mode と同じく "decompose" モードの Lindera::Penalty を渡せます。エラーも set_mode と同じです。
require 'lindera'
dictionary = Lindera.load_dictionary('embedded://ipadic')
tokenizer = Lindera::Tokenizer.new(dictionary)
# 左側空白ペナルティなしの ko-dic
ko_tokenizer = Lindera::Tokenizer.new(Lindera.load_dictionary('embedded://ko-dic'), space_penalty: false)
ユーザー辞書を使用する場合:
dictionary = Lindera.load_dictionary('embedded://ipadic')
user_dict = Lindera.load_user_dictionary_for('/path/to/user_dictionary.csv', dictionary)
tokenizer = Lindera::Tokenizer.new(dictionary, 'normal', user_dict)
Tokenizer メソッド
tokenize(text)
入力テキストをトークナイズし、Token オブジェクトの配列を返します。
tokens = tokenizer.tokenize('形態素解析')
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
text | String | トークナイズするテキスト |
戻り値: Array<Token>
tokenize_surfaces(text)
入力テキストをトークナイズし、トークンの surface のみを文字列の配列として返します。分かち書き用途の高速パスです。Token オブジェクトを生成せず、形態素の詳細情報もロードしないため、surface 文字列だけが必要な場合は tokenize より大幅に高速です。結果は tokenizer.tokenize(text).map(&:surface) と一致します。
surfaces = tokenizer.tokenize_surfaces('形態素解析')
# ["形態素", "解析"]
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
text | String | トークナイズするテキスト |
戻り値: Array<String>
tokenize_nbest(text, n, unique, cost_threshold)
N-best トークナイズ結果を返します。各結果はトータルパスコストとペアになっています。
results = tokenizer.tokenize_nbest('すもももももももものうち', 3, false, nil)
results.each do |tokens, cost|
puts "#{cost}: #{tokens.map(&:surface).join(' / ')}"
end
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
text | String | トークナイズするテキスト |
n | Integer | 返す結果の数 |
unique | Boolean | 結果の重複を排除(false で無効) |
cost_threshold | Integer または nil | 最良パスからの最大コスト差(nil で無制限) |
戻り値: Array<[Array<Token>, Integer]>
Mode
Lindera::Mode はトークナイズモードを表します。モードの確認・比較のためのスタンドアロンヘルパーとして提供されています。TokenizerBuilder#set_mode と Tokenizer.new は、Mode インスタンスではなく単純なモード文字列("normal" または "decompose")を受け取ります。decompose モードのペナルティは、それぞれのキーワード引数 penalty: で渡します(下記の Penalty を参照してください)。
Mode の作成
Lindera::Mode.new(mode_str)
Mode を作成します。引数は必須ですが nil を渡すこともできます。"normal" / "Normal"(mode_str が nil の場合に使用)または "decompose" / "Decompose" を受け付けます。それ以外の値を渡すと ArgumentError が発生します。
require 'lindera'
mode = Lindera::Mode.new('normal')
mode = Lindera::Mode.new('decompose')
mode = Lindera::Mode.new(nil) # デフォルトの "normal" になる
Mode メソッド
| メソッド | 戻り値 | 説明 |
|---|---|---|
to_s | String | "normal" または "decompose" |
name | String | to_s と同じ |
inspect | String | 例: "#<Lindera::Mode: decompose>" |
normal? | Boolean | モードが "normal" の場合 true |
decompose? | Boolean | モードが "decompose" の場合 true |
mode = Lindera::Mode.new('decompose')
mode.to_s # "decompose"
mode.normal? # false
mode.decompose? # true
Penalty
Lindera::Penalty は "decompose" モードのセグメンテーションで使用される、長さに基づくペナルティのしきい値を設定します。
Penalty の作成
Lindera::Penalty.new(kanji_penalty_length_threshold, kanji_penalty_length_penalty, other_penalty_length_threshold, other_penalty_length_penalty)
4つの位置引数はすべて必須ですが、それぞれ nil を渡すとデフォルト値(下記参照)にフォールバックします。
require 'lindera'
penalty = Lindera::Penalty.new(2, 3000, 7, 1700)
penalty = Lindera::Penalty.new(nil, nil, nil, nil) # すべてデフォルト値を使用
Penalty プロパティ
すべてのプロパティは読み取り専用です(setter メソッドはありません):
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
kanji_penalty_length_threshold | Integer | 2 | ペナルティが適用される漢字のみの表層形の長さのしきい値 |
kanji_penalty_length_penalty | Integer | 3000 | しきい値を超える漢字のみの表層形に加算されるコストペナルティ |
other_penalty_length_threshold | Integer | 7 | 漢字のみでない表層形にペナルティが適用される長さのしきい値 |
other_penalty_length_penalty | Integer | 1700 | しきい値を超える漢字のみでない表層形に加算されるコストペナルティ |
penalty = Lindera::Penalty.new(nil, nil, nil, nil)
penalty.kanji_penalty_length_threshold # 2
Penalty の使い方
Penalty は "decompose" と一緒に、TokenizerBuilder#set_mode か Tokenizer.new のキーワード引数 penalty: で渡します。長さのペナルティをなくすと、decompose モードでも長い語が分割されずに残ります。
require 'lindera'
text = '関西国際空港限定トートバッグ'
penalty = Lindera::Penalty.new(nil, 0, nil, 0)
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.set_mode('decompose')
p builder.build.tokenize(text).map(&:surface)
# => ["関西", "国際", "空港", "限定", "トートバッグ"]
builder.set_mode('decompose', penalty: penalty)
p builder.build.tokenize(text).map(&:surface)
# => ["関西国際空港", "限定", "トートバッグ"]
dictionary = Lindera.load_dictionary('embedded://ipadic')
tokenizer = Lindera::Tokenizer.new(dictionary, 'decompose', penalty: penalty)
p tokenizer.tokenize(text).map(&:surface)
# => ["関西国際空港", "限定", "トートバッグ"]
"normal" モードと一緒に Penalty を渡すと RuntimeError が発生します。
Token
Token は単一の形態素トークンを表します。
プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
surface | String | トークンの表層形 |
byte_start | Integer | 元テキストでの開始バイト位置 |
byte_end | Integer | 元テキストでの終了バイト位置 |
position | Integer | トークンの位置インデックス |
word_id | Integer | 辞書の単語 ID |
details | Array<String> | 形態素の詳細情報(品詞、読みなど) |
さらに、述語メソッド unknown? は辞書に登録されていない単語の場合 true を返します:
token.unknown? # => false
Token メソッド
get_detail(index)
指定されたインデックスの詳細文字列を返します。インデックスが範囲外の場合は nil を返します。
token = tokenizer.tokenize('東京')[0]
pos = token.get_detail(0) # 例: "名詞"
subpos = token.get_detail(1) # 例: "固有名詞"
reading = token.get_detail(7) # 例: "トウキョウ"
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
index | Integer | details 配列へのゼロベースインデックス |
戻り値: String または nil
to_h / to_hash
トークンを Symbol をキーとするプレーンな Hash として返します。各フィールドは
Ruby の自然な型を保つため、独自のエンコーダなしでシリアライズできます。
to_h と to_hash は同一のメソッドです。
require 'json'
data = tokenizer.tokenize('東京')[0].to_h
# {surface: "東京", byte_start: 0, byte_end: 6, position: 0,
# word_id: 12345, is_unknown: false, details: [...]}
JSON.generate(tokens.map(&:to_h))
戻り値: :surface、:byte_start、:byte_end、:position、:word_id、
:is_unknown、:details をキーに持つ Hash。
details の構造は辞書によって異なります:
- IPADIC:
[品詞, 品詞細分類1, 品詞細分類2, 品詞細分類3, 活用型, 活用形, 原形, 読み, 発音] - UniDic: UniDic 仕様に準拠した詳細な形態素情報
- ko-dic / CC-CEDICT / Jieba: 各辞書固有の詳細フォーマット
Schema
Lindera::Schema はフィールド名の順序付きリストを保持し、フィールド名とインデックスの間の相互変換を提供します。Metadata#dictionary_schema と Metadata#user_dictionary_schema で使用されます(辞書管理 を参照)。
Schema の作成
Lindera::Schema.new(fields)
フィールド名の配列からスキーマを作成します。
require 'lindera'
schema = Lindera::Schema.new(%w[
surface
left_context_id
right_context_id
cost
major_pos
reading
])
Lindera::Schema.create_default
組み込みのデフォルトスキーマを返します。IPADIC 形式に対応する13フィールド(surface、left_context_id、right_context_id、cost、major_pos、pos_detail_1、pos_detail_2、pos_detail_3、conjugation_type、conjugation_form、base_form、reading、pronunciation)です。
schema = Lindera::Schema.create_default
Schema メソッド
| メソッド | 戻り値 | 説明 |
|---|---|---|
fields | Array<String> | すべてのフィールド名(順序どおり) |
get_all_fields | Array<String> | fields と同じ |
field_count | Integer | フィールドの総数 |
get_field_index(name) | Integer または nil | name という名前のフィールドのインデックス |
get_field_name(index) | String または nil | index にあるフィールド名 |
get_custom_fields | Array<String> | 固定の4フィールド(surface、left_context_id、right_context_id、cost)以降のフィールド名 |
get_field_by_name(name) | FieldDefinition または nil | name の完全なフィールド定義 |
validate_record(record) | nil | record がスキーマと一致しない場合 ArgumentError を発生 |
to_s | String | 例: "Schema(fields=13)" |
inspect | String | フィールドの全リスト |
schema = Lindera::Schema.create_default
schema.field_count # 13
schema.get_field_index('cost') # 3
schema.get_field_name(0) # "surface"
schema.get_custom_fields # ["major_pos", "pos_detail_1", ..., "pronunciation"]
field = schema.get_field_by_name('surface')
puts "#{field.index} #{field.name} #{field.field_type}" # 0 surface surface
schema.validate_record([
'東京', '1288', '1288', '100',
'名詞', '固有名詞', '地域', '一般', '*', '*',
'東京', 'トウキョウ', 'トーキョー'
])
FieldDefinition
Lindera::FieldDefinition は Schema 内の単一フィールドを表します。インスタンスは Schema#get_field_by_name からのみ取得でき、公開コンストラクタはありません(Lindera::FieldDefinition.new は TypeError を発生させます)。
FieldDefinition プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
index | Integer | スキーマ内でのフィールドの位置(ゼロベース) |
name | String | フィールド名 |
field_type | FieldType | フィールドタイプ |
description | String または nil | 説明(任意) |
schema = Lindera::Schema.create_default
field = schema.get_field_by_name('surface')
field.index # 0
field.name # "surface"
field.field_type # #<Lindera::FieldType: surface>
field.description # nil(デフォルトスキーマでは説明は設定されていない)
FieldType
Lindera::FieldType はフィールドの種別を列挙します。FieldDefinition と同様、インスタンスは Schema(FieldDefinition#field_type 経由)からのみ取得でき、公開コンストラクタはありません。
to_s(および inspect)は次のいずれかを返します:
"surface"-- 表層形(単語のテキスト)"left_context_id"-- 左文脈 ID"right_context_id"-- 右文脈 ID"cost"-- 単語コスト"custom"-- その他の辞書固有フィールド
field = Lindera::Schema.create_default.get_field_by_name('surface')
field.field_type.to_s # "surface"
辞書管理
Lindera Ruby は、形態素解析で使用する辞書の読み込み、ビルド、管理のためのメソッドを提供します。
辞書の読み込み
システム辞書
Lindera.load_dictionary(uri) を使用してシステム辞書を読み込みます。GitHub Releases からビルド済み辞書をダウンロードし、展開したディレクトリのパスを指定してください:
require 'lindera'
dictionary = Lindera.load_dictionary('/path/to/ipadic')
埋め込み辞書(上級者向け) -- embed-* feature フラグ付きでビルドした場合、埋め込み辞書を使用できます:
dictionary = Lindera.load_dictionary('embedded://ipadic')
ユーザー辞書
ユーザー辞書はシステム辞書にカスタム語彙を追加します。組み合わせるシステム辞書に 合わせて読み込みます:
require 'lindera'
dictionary = Lindera.load_dictionary('/path/to/ipadic')
user_dict = Lindera.load_user_dictionary_for('/path/to/user_dictionary.csv', dictionary)
.csv はシステム辞書のメタデータでビルドし、ビルドしながら接続コスト行列と
照らして確認します。文脈 ID が行列の範囲外にある行は、その CSV の行番号と表層形を
示す RuntimeError になります(文脈 ID
を参照)。.bin はビルドされたとおりに読み込みます。
Lindera.load_user_dictionary(uri, metadata) は、メタデータだけでユーザー辞書を
読み込みます。この方法で読み込んだ .csv はシステム辞書と組み合わせるときに
確認し、エラーは見出し語をその単語 ID で示します:
require 'lindera'
dictionary = Lindera.load_dictionary('/path/to/ipadic')
metadata = dictionary.metadata
user_dict = Lindera.load_user_dictionary('/path/to/user_dictionary.csv', metadata)
トークナイザーの作成時にユーザー辞書を渡します:
require 'lindera'
dictionary = Lindera.load_dictionary('/path/to/ipadic')
user_dict = Lindera.load_user_dictionary_for('/path/to/user_dictionary.csv', dictionary)
tokenizer = Lindera::Tokenizer.new(dictionary, 'normal', user_dict)
または、ビルダー経由で設定します:
require 'lindera'
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('/path/to/ipadic')
builder.set_user_dictionary('/path/to/user_dictionary.csv')
tokenizer = builder.build
辞書のビルド
システム辞書のビルド
ソースファイルからシステム辞書をビルドします:
require 'lindera'
metadata = Lindera::Metadata.from_json_file('metadata.json')
Lindera.build_dictionary('/path/to/input_dir', '/path/to/output_dir', metadata)
入力ディレクトリには辞書のソースファイル(CSV レキシコン、matrix.def など)が含まれている必要があります。
Lindera::Metadata.from_json_file で読み込んだメタデータは metadata.json の space_penalty ルールを保持し、ビルドした辞書にもそのルールが同梱されます。そのため ko-dic をビルドした場合も、左側空白ペナルティはデフォルトでオンのままです。トークナイザーごとにオフにするには TokenizerBuilder#set_space_penalty(false) を使います(Tokenizer API を参照)。
ユーザー辞書のビルド
組み合わせるシステム辞書に合わせて、CSV ファイルからユーザー辞書をビルドします:
require 'lindera'
dictionary = Lindera.load_dictionary('/path/to/ipadic')
Lindera.build_user_dictionary_for('user_words.csv', '/path/to/user_dictionary.bin', dictionary)
user_dict = Lindera.load_user_dictionary_for('/path/to/user_dictionary.bin', dictionary)
CSV はシステム辞書のメタデータでビルドし、接続コスト行列と照らして確認します。
文脈 ID が行列の範囲外にある行は、その CSV の行番号と表層形を示す RuntimeError
になり、ファイルは書き出しません(文脈 ID
を参照)。2 番目の引数は書き出すファイルのパスです。ローダーは拡張子で
ユーザー辞書の読み込み方を決めるので、.bin で終えてください。
Lindera.build_user_dictionary(kind, input_file, output_dir, metadata) は、
メタデータだけでユーザー辞書をビルドします。output_dir も、名前に反して
ディレクトリではなく、書き出すファイルのパスです。各行はシステム辞書と
組み合わせるときに確認し、エラーは見出し語をその単語 ID で示します:
require 'lindera'
metadata = Lindera::Metadata.from_json_file('metadata.json')
Lindera.build_user_dictionary('ipadic', 'user_words.csv', '/path/to/user_dictionary.bin', metadata)
metadata パラメータは省略可能です。省略した場合はデフォルトのメタデータ値が使用されます:
Lindera.build_user_dictionary('ipadic', 'user_words.csv', '/path/to/user_dictionary.bin', nil)
[!NOTE] 第一引数(
kind、上の例では'ipadic')は現時点では未使用です -- 将来の利用のために予約 されているだけで、ビルドには影響しません。現時点では任意の文字列を渡すことができます。 システム辞書に合わせてビルドするにはLindera.build_user_dictionary_forを使います。
Metadata
Lindera::Metadata クラスは辞書のパラメータを設定します。
Metadata の作成
require 'lindera'
# 標準設定でデフォルトのメタデータを作成
metadata = Lindera::Metadata.create_default
Lindera::Metadata.new は8つのプロパティすべてを必須の位置引数として受け取ります
(それぞれ nil を渡すとデフォルト値にフォールバックします)。特定の値を上書きしたい
場合にのみ使用してください:
metadata = Lindera::Metadata.new(
'my_dict', # name
'UTF-8', # encoding
-10_000, # default_word_cost
1288, # default_left_context_id
1288, # default_right_context_id
'*', # default_field_value
false, # flexible_csv
false # skip_invalid_cost_or_id
)
JSON ファイルからの読み込み
metadata = Lindera::Metadata.from_json_file('metadata.json')
辞書からのメタデータ取得
読み込み済みの辞書からメタデータを取得できます:
dictionary = Lindera.load_dictionary('/path/to/ipadic')
metadata = dictionary.metadata
プロパティ
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | String | "default" | 辞書名 |
encoding | String | "UTF-8" | 文字エンコーディング |
default_word_cost | Integer | -10000 | 未知語のデフォルトコスト |
default_left_context_id | Integer | 1288 | デフォルトの左文脈 ID |
default_right_context_id | Integer | 1288 | デフォルトの右文脈 ID |
default_field_value | String | "*" | 欠損フィールドのデフォルト値 |
flexible_csv | Boolean | false | 柔軟な CSV パースを許可 |
skip_invalid_cost_or_id | Boolean | false | 無効なコストまたは ID のエントリーをスキップ |
to_h(別名 to_hash)は、これらのプロパティに dictionary_schema_fields と user_dictionary_schema_fields(カンマ区切りのフィールド名)を加えた、値がすべて文字列の Hash を返します。ko-dic のようにメタデータが左側空白ペナルティのルールを持つ場合は、そのルールを JSON 文字列で保持する space_penalty エントリも含まれます:
metadata = Lindera::Metadata.from_json_file('/path/to/ko-dic/metadata.json')
metadata.to_h['space_penalty']
# '{"rules":[{"pos":["EC","EF",...],"cost":3000},{"pos":["JC","JKB",...],"cost":6000}]}'
テキスト処理パイプライン
Lindera Ruby は、トークナイズ前に文字フィルタを適用し、トークナイズ後にトークンフィルタを適用する、組み合わせ可能なテキスト処理パイプラインをサポートしています。フィルタは Lindera::TokenizerBuilder に追加され、追加された順序で実行されます。
Input Text
--> Character Filters (preprocessing)
--> Tokenization
--> Token Filters (postprocessing)
--> Output Tokens
[!NOTE] このページではよく使われる一部のフィルタのみを例として紹介しています。これが全リストではありません。
lindera-analysisが提供する全フィルタ(パラメータ・使用例を含む)の正式なカタログについてはフィルタを参照してください。
文字フィルタ
文字フィルタはトークナイズ前に入力テキストを変換します。
unicode_normalize
入力テキストに Unicode 正規化を適用します。
require 'lindera'
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_character_filter('unicode_normalize', { 'kind' => 'nfkc' })
tokenizer = builder.build
サポートされる正規化形式: "nfc"、"nfkc"、"nfd"、"nfkd"。
mapping
マッピングテーブルに従って文字や文字列を置換します。
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_character_filter('mapping', {
'mapping' => {
"\u30fc" => '-',
"\uff5e" => '~'
}
})
tokenizer = builder.build
japanese_iteration_mark
日本語の踊り字(繰り返し記号)を完全な形に展開します。
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_character_filter('japanese_iteration_mark', {
'normalize_kanji' => true,
'normalize_kana' => true
})
tokenizer = builder.build
トークンフィルタ
トークンフィルタはトークナイズ後にトークンを変換または除去します。
lowercase
トークンの表層形を小文字に変換します。
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_token_filter('lowercase', nil)
tokenizer = builder.build
japanese_base_form
辞書の形態素情報を使用して、活用形を基本形(辞書形)に置換します。
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_token_filter('japanese_base_form', nil)
tokenizer = builder.build
japanese_stop_tags
指定されたタグに一致する品詞のトークンを除去します。
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_token_filter('japanese_stop_tags', {
'tags' => ['助詞,格助詞,一般', '助詞,係助詞', '助詞,連体化', '助動詞']
})
tokenizer = builder.build
[!NOTE] タグは 4 階層のカンマ区切りに正規化され(不足分は
*で補完)、各トークンの品詞詳細 4 つ(辞書スキーマでpart_of_speechという名前のフィールドから始まる 4 つ。スキーマにそのフィールドが無い場合は先頭の詳細から)と完全一致で比較されます。 IPADIC の助詞トークンは必ず助詞,係助詞のようにサブカテゴリを持つため、助詞単独では一致しません。 一方、助動詞はサブカテゴリを持たないため(助動詞,*,*,*)、助動詞単独で一致します。
japanese_keep_tags
指定されたタグに一致する品詞のトークンのみを保持します。その他のトークンはすべて除去されます。
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_token_filter('japanese_keep_tags', {
'tags' => ['名詞,一般']
})
tokenizer = builder.build
japanese_katakana_stem
カタカナ語の末尾にある長音記号を除去します。最小文字数を指定できます。
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_token_filter('japanese_katakana_stem', { 'min' => 3 })
tokenizer = builder.build
パイプラインの完全な例
以下の例では、複数の文字フィルタとトークンフィルタを1つのパイプラインに組み合わせています:
require 'lindera'
builder = Lindera::TokenizerBuilder.new
builder.set_mode('normal')
builder.set_dictionary('embedded://ipadic')
# Preprocessing
builder.append_character_filter('unicode_normalize', { 'kind' => 'nfkc' })
builder.append_character_filter('japanese_iteration_mark', {
'normalize_kanji' => true,
'normalize_kana' => true
})
# Postprocessing
builder.append_token_filter('japanese_base_form', nil)
builder.append_token_filter('japanese_stop_tags', {
'tags' => ['助詞,格助詞,一般', '助詞,係助詞', '助詞,連体化', '助動詞', '記号,句点', '記号,読点']
})
builder.append_token_filter('lowercase', nil)
tokenizer = builder.build
tokens = tokenizer.tokenize('Linderaは形態素解析を行うライブラリです。')
tokens.each do |token|
puts "#{token.surface}\t#{token.details.join(',')}"
end
このパイプラインでは:
unicode_normalizeが全角文字を半角に変換(NFKC 正規化)japanese_iteration_markが踊り字を展開japanese_base_formが活用形のトークンを基本形に変換japanese_stop_tagsが助詞(格助詞・係助詞・連体化)・助動詞・句読点を除去lowercaseがアルファベットを小文字に正規化
学習
Lindera Ruby は、アノテーション付きコーパスからカスタム CRF ベースの形態素解析モデルを学習する機能をサポートしています。この機能には train feature が必要です。
前提条件
train feature を有効にして lindera-ruby をビルドします:
LINDERA_FEATURES="embed-ipadic,train" bundle exec rake compile
モデルの学習
Lindera.train を使用して、種辞書とアノテーション付きコーパスから CRF モデルを学習します:
require 'lindera'
Lindera.train(
'resources/training/seed.csv',
'resources/training/corpus.txt',
'resources/training/char.def',
'resources/training/unk.def',
'resources/training/feature.def',
'resources/training/rewrite.def',
'/tmp/model.dat',
0.01, # lambda (L1 regularization)
100, # max_iter
nil # max_threads (nil = auto-detect)
)
学習パラメータ
Lindera.train の引数は位置引数として順番に渡します:
| 順番 | パラメータ | 型 | 説明 |
|---|---|---|---|
| 1 | seed | String | 種辞書ファイルのパス(CSV 形式) |
| 2 | corpus | String | アノテーション付き学習コーパスのパス |
| 3 | char_def | String | 文字定義ファイルのパス(char.def) |
| 4 | unk_def | String | 未知語定義ファイルのパス(unk.def) |
| 5 | feature_def | String | 素性定義ファイルのパス(feature.def) |
| 6 | rewrite_def | String | 書き換えルール定義ファイルのパス(rewrite.def) |
| 7 | output | String | 学習済みモデルファイルの出力パス |
| 8 | lambda | Float | L1 正則化コスト(0.0--1.0) |
| 9 | max_iter | Integer | 最大学習イテレーション数 |
| 10 | max_threads | Integer または nil | スレッド数(nil = CPU コア数を自動検出) |
学習済みモデルのエクスポート
学習後、Lindera.export を使用してモデルを辞書ソースファイルにエクスポートします:
require 'lindera'
Lindera.export('/tmp/model.dat', '/tmp/dictionary_source', 'resources/training/metadata.json')
エクスポートパラメータ
| 順番 | パラメータ | 型 | 説明 |
|---|---|---|---|
| 1 | model | String | 学習済みモデルファイルのパス(.dat) |
| 2 | output | String | 辞書ソースファイルの出力ディレクトリ |
| 3 | metadata | String または nil | ベースとなる metadata.json ファイルのパス |
エクスポートにより、出力ディレクトリに以下のファイルが作成されます。lindera export が作成するファイルと同じです:
lex.csv-- 学習済みコスト付きのレキシコンエントリーmatrix.def-- 連接コスト行列unk.def-- 未知語定義char.def-- 文字カテゴリ定義(学習済みモデルからコピー)feature.def-- 素性テンプレート定義(学習済みモデルからコピー)rewrite.def-- 素性リライトルール(学習済みモデルからコピー)left-id.def-- 左文脈IDから素性文字列へのマッピングright-id.def-- 右文脈IDから素性文字列へのマッピングmetadata.json-- 更新されたメタデータ(metadataパラメータ指定時)
モデルファイルは、このバージョンの学習で作ったものである必要があります。それより前のバージョンで書かれたモデルファイル(7.0.0 より前に書かれたものはすべて該当)は拒否され、Lindera.export は学習のやり直しを求めるエラーで失敗します。そうしたモデルは Lindera.train で学習し直してください。
完全なワークフロー
カスタム辞書の学習と使用の完全なワークフロー:
require 'lindera'
# Step 1: Train the CRF model
Lindera.train(
'resources/training/seed.csv',
'resources/training/corpus.txt',
'resources/training/char.def',
'resources/training/unk.def',
'resources/training/feature.def',
'resources/training/rewrite.def',
'/tmp/model.dat',
0.01, # lambda
100, # max_iter
nil # max_threads
)
# Step 2: Export to dictionary source files
Lindera.export('/tmp/model.dat', '/tmp/dictionary_source', 'resources/training/metadata.json')
# Step 3: Build the dictionary from exported source files
metadata = Lindera::Metadata.from_json_file('/tmp/dictionary_source/metadata.json')
Lindera.build_dictionary('/tmp/dictionary_source', '/tmp/dictionary', metadata)
# Step 4: Use the trained dictionary
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('/tmp/dictionary')
builder.set_mode('normal')
tokenizer = builder.build
tokens = tokenizer.tokenize('形態素解析のテスト')
tokens.each do |token|
puts "#{token.surface}\t#{token.details.join(',')}"
end
Lindera PHP
Lindera PHP は、ext-php-rs を使用して構築された Lindera 形態素解析エンジンの PHP バインディングです。PHP 8.1 以降をサポートし、Lindera の高性能なトークナイズ機能を PHP エコシステムに提供します。
特徴
- 多言語対応: 日本語(IPADIC、IPADIC NEologd、UniDic)、韓国語(ko-dic)、中国語(CC-CEDICT、Jieba)のテキストをトークナイズ
- テキスト処理パイプライン: 文字フィルタとトークンフィルタを組み合わせて、柔軟な前処理・後処理が可能
- CRF ベースの辞書学習: アノテーション付きコーパスからカスタム形態素解析モデルを学習(
trainfeature が必要) - 複数のトークナイズモード: 解析粒度に応じた Normal モードと Decompose モード
- N-best トークナイズ: コスト順にランク付けされた複数のトークナイズ候補を取得
- ユーザー辞書: システム辞書をカスタム語彙で拡張
引数の型
型を宣言した引数は、declare(strict_types=1) の有無にかかわらず、その型の値だけを受け付けます。int の引数は int を受け付け、数字の文字列や float は受け付けません。?Metadata の引数は Lindera\Metadata を受け付けます。float の引数は int も受け付けます。?int $cost_threshold = null のような省略可能な引数では、null を渡すか引数を省略すると既定値になります。それ以外の値は、引数の名前を含む TypeError を投げます。コマンドライン引数のように文字列で届く値は、宣言された型にキャストしてから渡してください。
<?php
$dictionary = Lindera\Dictionary::load('embedded://ipadic');
$tokenizer = new Lindera\Tokenizer($dictionary);
$threshold = '5000'; // 例えば $argv や設定ファイルから
try {
$tokenizer->tokenizeNbest('東京都', 2, cost_threshold: $threshold);
} catch (TypeError $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
// TypeError: Invalid value given for argument `cost_threshold`.
}
// 宣言された型にキャストする
$results = $tokenizer->tokenizeNbest('東京都', 2, cost_threshold: (int) $threshold);
// null を渡すか引数を省略すると既定値になる
$results = $tokenizer->tokenizeNbest('東京都', 2, cost_threshold: null);
new Lindera\Tokenizer(...) の $space_penalty やフィルタのメソッドの $args など、mixed と宣言した引数は、この規則の対象外です。どの PHP の値も受け取り、値を自分で検査します。
int の引数は、その設定が保持できる値だけを受け付けます。ほとんどの設定の範囲は、PHP の 64 ビットの int より狭いです。Lindera\Metadata の $default_word_cost は -32768 から 32767、文脈 ID は 0 から 65535、Lindera\Penalty のペナルティは 32 ビットの整数を受け付けます。個数や位置は 0 以上を受け付けます。該当するのは、Lindera\Penalty のしきい値、new Lindera\FieldDefinition(...)・Schema::getFieldName()・Token::getDetail() の $index、tokenizeNbest() の $n、Lindera\Trainer::train() の $max_iter と $max_threads です。範囲外の値は、引数の名前と範囲を含む ValueError を投げます。末尾より先の位置は、これまでどおり null を返します。
<?php
try {
new Lindera\Metadata(default_word_cost: 100000);
} catch (ValueError $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
// ValueError: default_word_cost must be an integer between -32768 and 32767, got 100000
}
$dictionary = Lindera\Dictionary::load('embedded://ipadic');
$tokenizer = new Lindera\Tokenizer($dictionary);
try {
$tokenizer->tokenizeNbest('東京都', -1);
} catch (ValueError $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
// ValueError: n must be an integer between 0 and 18446744073709551615, got -1
}
ドキュメント
- インストール -- 前提条件、ビルド手順、feature フラグ
- クイックスタート -- 最小限の使用例
- トークナイザー API --
TokenizerBuilder、Tokenizer、Tokenクラスリファレンス - 辞書管理 -- 辞書の読み込み、ビルド、管理
- テキスト処理パイプライン -- 文字フィルタとトークンフィルタ
- 学習 -- カスタム CRF モデルの学習と辞書のエクスポート
インストール
lindera-php は PHP 拡張です。Packagist に lindera/lindera として公開されており、PHP 拡張インストーラーの PIE でインストールします。拡張名は lindera です。
前提条件
- PHP 8.1 以降(Linux または macOS)。Windows は非対応です
- PIE 1.4 以降と
unzip-- PIE のインストール方法を参照。それより古い PIE はこのパッケージのメタデータを読めないため、pie self-updateで更新してください - PIE がソースからのビルドにフォールバックする場合のみ、Rust ツールチェーン(rustup)、libclang(Debian/Ubuntu は
libclang-dev、macOS は Xcode コマンドラインツールまたはbrew install llvm)、および PHP 拡張の標準的なビルドツール(autoconf、libtool、make)が必要です
PIE でのインストール
pie install lindera/lindera
[!NOTE]
lindera/linderaは lindera v6.1.0 以降で Packagist から利用できます。それ以前のバージョンは、以下の手順でソースからビルドしてください。
使用中の PHP とプラットフォームに対応するビルド済み lindera.so が GitHub Release にあれば、PIE はそれをダウンロードして拡張ディレクトリにインストールし、有効化します。コンパイルは行わず、Rust ツールチェーンも不要です。ビルド済みバイナリの対象は次のとおりです:
- PHP 8.1〜8.5、NTS と ZTS
- glibc 2.35 以降の Linux x86_64 / arm64(例: Ubuntu 22.04、Debian 12)
- macOS(Apple silicon)
それ以外のプラットフォーム(Alpine などの musl 環境、Intel Mac)では、同じコマンドが検出した PHP 向けのソースビルドにフォールバックします。別の PHP を対象にする場合は --with-php-config=/path/to/php-config を指定してください。Composer 自体は php-ext パッケージをインストールしないため、composer require lindera/lindera ではインストールできません。
[!WARNING] PIE は glibc のバージョンを確認しません。glibc 2.35 未満の環境(例: Ubuntu 20.04、Debian 11、RHEL 8)でもビルド済みバイナリをインストールし、その拡張は読み込みに失敗します。その場合はソースからビルドしてください。PIE 1.5 以降なら
pie install --suppress-download-url-method=pre-packaged-binary lindera/linderaを実行するか、後述の手順で手動ビルドします。
パッケージには辞書は埋め込まれていません。辞書の入手を参照してください。
辞書の入手
Lindera はパッケージに辞書を同梱していません。ビルド済み辞書を別途入手する必要があります。
GitHub Releases からのダウンロード
ビルド済み辞書は GitHub Releases ページから入手できます。辞書アーカイブをダウンロードしてローカルディレクトリに展開してください:
# 例: IPADIC 辞書のダウンロードと展開
curl -LO https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip
unzip lindera-ipadic-<version>.zip -d /path/to/ipadic
ソースからのビルド
lindera-php をビルドします:
cargo build -p lindera-php
または、プロジェクトの Makefile を使用します:
make build-lindera-php
学習機能付きビルド
train feature を有効にすると、CRF ベースの辞書学習機能が利用可能になります。デフォルトで有効になっています:
cargo build -p lindera-php --features train
PHP 拡張の読み込み
ビルド後、-d extension= オプションでビルドされた共有ライブラリを指定して PHP を実行します:
php -d extension=target/debug/liblindera_php.so script.php
リリースビルドの場合:
cargo build -p lindera-php --release
php -d extension=target/release/liblindera_php.so script.php
php.ini に追加して常に読み込むこともできます:
extension=/absolute/path/to/liblindera_php.so
macOS では cargo は liblindera_php.dylib を生成します。どの方法で読み込んでも拡張名は lindera です。php -m には lindera と表示され、extension_loaded('lindera') で確認できます。
Feature フラグ
| Feature | 説明 | デフォルト |
|---|---|---|
train | CRF 学習機能 | 有効 |
embed-ipadic | 日本語辞書(IPADIC)をバイナリに埋め込み | 無効 |
embed-unidic | 日本語辞書(UniDic)をバイナリに埋め込み | 無効 |
embed-sudachidict | 日本語辞書(SudachiDict)をバイナリに埋め込み | 無効 |
embed-ipadic-neologd | 日本語辞書(IPADIC NEologd)をバイナリに埋め込み | 無効 |
embed-ko-dic | 韓国語辞書(ko-dic)をバイナリに埋め込み | 無効 |
embed-cc-cedict | 中国語辞書(CC-CEDICT)をバイナリに埋め込み | 無効 |
embed-jieba | 中国語辞書(Jieba)をバイナリに埋め込み | 無効 |
embed-cjk | 全 CJK 辞書をバイナリに埋め込み(IPADIC、ko-dic、Jieba) | 無効 |
複数の feature を組み合わせることができます:
cargo build -p lindera-php --features "train,embed-ipadic,embed-ko-dic"
ビルド済みバイナリと PIE のソースビルドは、デフォルトの feature(train のみ、辞書の埋め込みなし)を使います。
[!TIP] 辞書をバイナリに直接埋め込みたい場合(上級者向け)は、対応する
embed-*feature フラグを有効にしてビルドし、embedded://スキームでロードしてください:$dictionary = Lindera\Dictionary::load('embedded://ipadic');詳細は Feature フラグ を参照してください。
インストールの確認
インストール後、PHP で lindera が利用可能であることを確認します:
<?php
$version = Lindera\Dictionary::version();
echo "Lindera version: {$version}\n";
実行方法:
# PIE でインストールした場合
php script.php
# 自分でビルドした場合
php -d extension=target/debug/liblindera_php.so script.php
静的解析用スタブ
lindera-php/stubs/lindera.stubs.php は、拡張の全クラスを IDE・PHPStan・Psalm 向けに宣言したスタブです。ビルドした拡張から生成され、内容が古くなるとテストが失敗します。ファイルのコピーを解析ツールに指定してください。拡張がクラスを宣言済みのため、実行時に include してはいけません。
# phpstan.neon
parameters:
stubFiles:
- path/to/lindera.stubs.php
クイックスタート
このガイドでは、lindera-php を使用してテキストをトークナイズする方法を紹介します。
基本的なトークナイズ
辞書を読み込み、トークナイザーを作成してテキストをトークナイズします:
<?php
// Load the dictionary
$dictionary = Lindera\Dictionary::load('/path/to/ipadic');
// Create a tokenizer
$tokenizer = new Lindera\Tokenizer($dictionary, 'normal');
// Tokenize the text
$tokens = $tokenizer->tokenize('関西国際空港限定トートバッグ');
foreach ($tokens as $token) {
echo $token->surface . "\t" . implode(',', $token->details) . "\n";
}
注意: ビルド済み辞書を GitHub Releases からダウンロードし、展開したディレクトリのパスを指定してください。
期待される出力:
関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ 名詞,一般,*,*,*,*,*,*,*
TokenizerBuilder の使用
TokenizerBuilder を使用すると、より柔軟にトークナイザーを設定できます:
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setMode('normal');
$builder->setDictionary('/path/to/ipadic');
$tokenizer = $builder->build();
$tokens = $tokenizer->tokenize('すもももももももものうち');
foreach ($tokens as $token) {
echo $token->surface . "\t" . $token->getDetail(0) . "\n";
}
トークンプロパティへのアクセス
各トークンは以下のプロパティを公開しています:
<?php
$dictionary = Lindera\Dictionary::load('/path/to/ipadic');
$tokenizer = new Lindera\Tokenizer($dictionary, 'normal');
$tokens = $tokenizer->tokenize('東京タワー');
foreach ($tokens as $token) {
echo "Surface: {$token->surface}\n";
echo "Byte range: {$token->byte_start}..{$token->byte_end}\n";
echo "Position: {$token->position}\n";
echo "Word ID: {$token->word_id}\n";
echo "Unknown: " . ($token->is_unknown ? 'true' : 'false') . "\n";
echo "Details: " . implode(',', $token->details) . "\n";
echo "\n";
}
N-best トークナイズ
コスト順にランク付けされた複数のトークナイズ候補を取得します:
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('/path/to/ipadic');
$tokenizer = $builder->build();
$results = $tokenizer->tokenizeNbest('東京都', 3);
foreach ($results as $result) {
$surfaces = array_map(fn($t) => $t->surface, $result->tokens);
echo "Cost {$result->cost}: " . implode(' / ', $surfaces) . "\n";
}
Decompose モード
Decompose モードでは、複合語をより小さな単位に分解します:
<?php
$dictionary = Lindera\Dictionary::load('/path/to/ipadic');
$tokenizer = new Lindera\Tokenizer($dictionary, 'decompose');
$tokens = $tokenizer->tokenize('関西国際空港限定トートバッグ');
foreach ($tokens as $token) {
echo $token->surface . "\n";
}
トークナイザー API
TokenizerBuilder
TokenizerBuilder はビルダーパターンを使用して Tokenizer インスタンスを設定・構築します。
コンストラクタ
new Lindera\TokenizerBuilder()
デフォルト設定で新しいビルダーを作成します。
<?php
$builder = new Lindera\TokenizerBuilder();
設定メソッド
setMode($mode, $penalty)
トークナイズモードを設定します。
"normal"-- 標準的なトークナイズ(デフォルト)"decompose"-- 複合語をより小さな単位に分解
$penalty は "decompose" モードが使う Lindera\Penalty です。null(デフォルト)では既定のペナルティを使います。"normal" とペナルティを一緒に渡すと、不正なモードと同じく ValueError をスローします。型の違う値を渡すと、デフォルトを選ぶのではなく例外をスローします。
$builder->setMode('normal');
$builder->setMode('decompose', new Lindera\Penalty(kanji_penalty_length_penalty: 0));
setDictionary($uri)
システム辞書のパスまたは URI を設定します。
// 埋め込み辞書を使用
$builder->setDictionary('embedded://ipadic');
// 外部辞書を使用
$builder->setDictionary('/path/to/dictionary');
setUserDictionary($uri)
ユーザー辞書の URI を設定します。
$builder->setUserDictionary('/path/to/user_dictionary.csv');
setKeepWhitespace($keep)
出力に空白トークンを含めるかどうかを制御します。残した空白はラティスにも残るため、他のトークンも、MeCab と同様に空白をラティス上で読み飛ばすデフォルトの出力と変わることがあります。空白を出力せずにラティスにだけ残すには、代わりに setSkipWhitespace(false) を使います。
$builder->setKeepWhitespace(true);
setSkipWhitespace($skip)
空白をラティス上で読み飛ばすかどうかを設定します。$skip の意味は設定ファイルの segmenter.skip_whitespace と同じです:
true-- 空白を読み飛ばし、MeCab と同様に空白の前後の語を直接接続します。false-- 空白を独立したノード(空白の見出し語かSPACEの未知語)としてラティスに残します(v7 より前の動作)。空白は引き続き出力から除外されます。null-- 辞書のmetadata.jsonのデフォルトです。SudachiDict 以外では読み飛ばします。以前の呼び出しを取り消すときに使います。
ここで設定した値は、辞書のデフォルトと、fromFile で読み込んだ skip_whitespace より優先されます。空白トークンを残すとき(setKeepWhitespace(true))は効果がありません。残した空白は常にラティスに残るためです。引数は省略できず、それ以外の型の値では TypeError をスローします。
たとえば IPADIC では、Google が 新しい の が は、デフォルトでは助詞(助詞)、setSkipWhitespace(false) では接続詞(接続詞)と解析されます。詳しくは Segmenter を参照してください。
// v7 より前と同じく、空白をラティスに残す
$builder->setSkipWhitespace(false);
setSpacePenalty($value)
韓国語向けの左側空白ペナルティ(left-space penalty)を設定します。直前に空白がある候補のうち、本来は前の語に空白なしで付く品詞(助詞、語尾など)の候補にコストを加算します。$value の意味は設定ファイルの segmenter.space_penalty と同じです:
null-- デフォルト。辞書がmetadata.jsonに同梱するルールがあればそれを使います(ko-dic は mecab-ko-dic のルールを同梱し、他の同梱辞書はルールを持ちません)。以前の呼び出しを取り消すときにも使います。false-- ペナルティをオフにします。true-- 辞書のルールを要求します。ルールを同梱しない辞書ではbuild()がValueErrorをスローします。- 連想配列
['rules' => [['pos' => [...], 'cost' => n], ...]]-- 辞書のルールの代わりにこのルールを適用します。先頭品詞タグがposに含まれる候補にcostを加算し、最初に一致したルールが使われます。
rules と各 pos はキーが 0, 1, 2, ... のリストでなければなりません(array_filter() や array_unique() の後は array_values() を適用してください)。cost は 32 ビットの範囲の int でなければならず、6000.0 のような float は拒否されます。それ以外の値では、オブジェクト(stdClass や true を渡さない json_decode() の結果)や NAN、INF も含めて ValueError がスローされます。
$builder->setDictionary('embedded://ko-dic');
// ペナルティをオフにする
$builder->setSpacePenalty(false);
// 明示的なルール
$builder->setSpacePenalty([
'rules' => [
['pos' => ['EC', 'EF', 'EP', 'ETM', 'ETN', 'VCP', 'XSA', 'XSN', 'XSV'], 'cost' => 3000],
['pos' => ['JC', 'JKB', 'JKC', 'JKG', 'JKO', 'JKQ', 'JKS', 'JKV', 'JX'], 'cost' => 6000],
],
]);
// 辞書のデフォルトに戻す
$builder->setSpacePenalty(null);
[!NOTE] v6.1.0 から、ko-dic では mecab-ko と同じく左側空白ペナルティがデフォルトで適用されます。たとえば
검색 이 잘 된다の이は感動詞ICと解析されます(ペナルティがなければ主格助詞JKS)。オフにするには$builder->setSpacePenalty(false)を呼び出してください。new Lindera\Tokenizer(...)で作成するトークナイザーではspace_penalty: falseを渡します。ルールを同梱しているのは Lindera 6.1.0 以降でビルドした ko-dic だけです。v6.0.0 リリースの ko-dic ではペナルティはオフのままで、辞書を再ビルドするまでtrueはエラーになります。詳しくは Segmenter を参照してください。
appendCharacterFilter($kind, $args)
前処理パイプラインに文字フィルタを追加します。
$builder->appendCharacterFilter('unicode_normalize', ['kind' => 'nfkc']);
appendTokenFilter($kind, $args)
後処理パイプラインにトークンフィルタを追加します。
$builder->appendTokenFilter('lowercase');
$builder->appendTokenFilter('japanese_stop_tags', [
'tags' => ['助詞,格助詞,一般', '助詞,係助詞', '助詞,連体化', '助動詞'],
]);
setSpacePenalty、appendCharacterFilter、appendTokenFilter の引数は、配列の入れ子が 128 段までです。それより深い配列を渡すと ValueError をスローします。
ビルド
build()
設定された内容で Tokenizer をビルドして返します。
$tokenizer = $builder->build();
Tokenizer
Tokenizer はテキストに対して形態素解析を行います。
Tokenizer の作成
new Lindera\Tokenizer($dictionary, $mode, $user_dictionary, $space_penalty, $penalty)
読み込み済みの辞書から直接トークナイザーを作成します。$mode、$user_dictionary、$space_penalty、$penalty は省略でき、デフォルトは null です($mode の null は 'normal' を意味します)。
$space_penalty には setSpacePenalty() と同じ値を渡せます。null では辞書が同梱する左側空白ペナルティのルール(ko-dic は同梱しています)を使います。オフにするには false を渡します。名前付き引数で渡せば、ほかの省略可能な引数を飛ばせます。不正な設定では、setSpacePenalty() や build() と同じく ValueError をスローします。
$penalty には、setMode() と同じく "decompose" モードの Lindera\Penalty を渡せます。エラーも setMode() と同じです。
<?php
$dictionary = Lindera\Dictionary::load('embedded://ipadic');
$tokenizer = new Lindera\Tokenizer($dictionary, 'normal');
// 左側空白ペナルティなしの ko-dic
$koTokenizer = new Lindera\Tokenizer(Lindera\Dictionary::load('embedded://ko-dic'), space_penalty: false);
ユーザー辞書を指定する場合:
<?php
$dictionary = Lindera\Dictionary::load('embedded://ipadic');
$userDictionary = Lindera\Dictionary::loadUserFor('/path/to/user_dictionary.csv', $dictionary);
$tokenizer = new Lindera\Tokenizer($dictionary, 'normal', $userDictionary);
Tokenizer メソッド
tokenize($text)
入力テキストをトークナイズし、Token オブジェクトの配列を返します。
$tokens = $tokenizer->tokenize('形態素解析');
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
$text | string | トークナイズするテキスト |
戻り値: array<Token>
tokenizeSurfaces($text)
入力テキストをトークナイズし、トークンの surface のみを文字列の配列として返します。分かち書き用途の高速パスです。Token オブジェクトを生成せず、形態素の詳細情報もロードしないため、surface 文字列だけが必要な場合は tokenize より大幅に高速です。結果は array_map(fn ($t) => $t->surface, $tokenizer->tokenize($text)) と一致します。
<?php
$surfaces = $tokenizer->tokenizeSurfaces('形態素解析');
// ["形態素", "解析"]
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
$text | string | トークナイズするテキスト |
戻り値: array<string>
tokenizeNbest($text, $n, $unique, $cost_threshold)
N-best トークナイズ結果を返します。各結果は NbestResult オブジェクトで、トークン配列とトータルパスコストを含みます。
$results = $tokenizer->tokenizeNbest('すもももももももものうち', 3);
foreach ($results as $result) {
echo "Cost: {$result->cost}\n";
foreach ($result->tokens as $token) {
echo " {$token->surface}\n";
}
}
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
$text | string | トークナイズするテキスト |
$n | int | 返す結果の数。0 以上 |
$unique | bool|null | 重複する結果を除く(デフォルト: false) |
$cost_threshold | int|null | 最良パスとのコストの差の上限(デフォルト: null) |
戻り値: array<NbestResult>
NbestResult
NbestResult は N-best トークナイズの個別の結果を表します。
NbestResult プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
tokens | array<Token> | トークンの配列 |
cost | int | トータルパスコスト |
Token
Token は単一の形態素トークンを表します。
Token プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
surface | string | トークンの表層形 |
byte_start | int | 元テキストでの開始バイト位置 |
byte_end | int | 元テキストでの終了バイト位置 |
position | int | トークンの位置インデックス |
word_id | int | 辞書の単語 ID |
is_unknown | bool | 辞書に登録されていない単語の場合 true |
details | array<string> | 形態素の詳細情報(品詞、読みなど) |
Token メソッド
getDetail($index)
指定されたインデックスの詳細文字列を返します。その位置に詳細がない場合は null を返します。負のインデックスは ValueError を投げます。
$token = $tokenizer->tokenize('東京')[0];
$pos = $token->getDetail(0); // 例: "名詞"
$subpos = $token->getDetail(1); // 例: "固有名詞"
$reading = $token->getDetail(7); // 例: "トウキョウ"
パラメータ:
| 名前 | 型 | 説明 |
|---|---|---|
$index | int | details 配列へのゼロベースインデックス |
戻り値: string または null
toArray()
トークンを連想配列として返します。各フィールドは PHP の自然な型を保つため、 独自のエンコーダなしでシリアライズできます。
$data = $tokenizer->tokenize('東京')[0]->toArray();
// ['surface' => '東京', 'byte_start' => 0, 'byte_end' => 6, 'position' => 0,
// 'word_id' => 12345, 'is_unknown' => false, 'details' => [...]]
echo json_encode(array_map(fn($t) => $t->toArray(), $tokens));
戻り値: surface、byte_start、byte_end、position、word_id、
is_unknown、details をキーに持つ array。
[!NOTE]
Lindera\TokenはJsonSerializableを実装していないため、トークンオブジェクトを そのままjson_encodeに渡してもこれらのフィールドは出力されません。先にtoArray()を呼んでください。この拡張は ext-php-rs でビルドされており、その#[php_class]はまだ実装インターフェースを宣言できません(上流の ext-php-rs#326)。
details の構造は辞書によって異なります:
- IPADIC:
[品詞, 品詞細分類1, 品詞細分類2, 品詞細分類3, 活用型, 活用形, 原形, 読み, 発音] - UniDic: UniDic 仕様に準拠した詳細な形態素情報
- ko-dic / CC-CEDICT / Jieba: 各辞書固有の詳細フォーマット
Mode
Mode はトークナイズの動作モードを表します。
Mode の作成
new Lindera\Mode($name)
$mode = new Lindera\Mode('normal');
$mode = new Lindera\Mode('decompose');
$mode = new Lindera\Mode(); // デフォルト: 'normal'
Mode プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
name | string | モード名("normal" または "decompose") |
Mode メソッド
| メソッド | 戻り値 | 説明 |
|---|---|---|
isNormal() | bool | Normal モードの場合 true |
isDecompose() | bool | Decompose モードの場合 true |
Penalty
Penalty は、文字種と長さのしきい値に基づいて、decompose モードが複合語をどの程度積極的に分割するかを設定します。
<?php
// すべてのパラメータは省略可能で、decompose モードが使用するデフォルト値になります
$penalty = new Lindera\Penalty(
kanji_penalty_length_threshold: 2,
kanji_penalty_length_penalty: 3000,
other_penalty_length_threshold: 7,
other_penalty_length_penalty: 1700,
);
Penalty プロパティ
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
$kanji_penalty_length_threshold | int | 2 | 漢字連続のしきい値 |
$kanji_penalty_length_penalty | int | 3000 | しきい値を超えた漢字連続に適用されるペナルティ |
$other_penalty_length_threshold | int | 7 | その他の文字連続のしきい値 |
$other_penalty_length_penalty | int | 1700 | しきい値を超えたその他の文字連続に適用されるペナルティ |
Penalty の使い方
Penalty は 'decompose' と一緒に setMode() か Tokenizer のコンストラクタ に渡します。長さのペナルティをなくすと、decompose モードでも長い語が分割されずに残ります。
<?php
$text = '関西国際空港限定トートバッグ';
$penalty = new Lindera\Penalty(kanji_penalty_length_penalty: 0, other_penalty_length_penalty: 0);
$surfaces = static fn (array $tokens): string => implode(' / ', array_map(static fn ($token) => $token->surface, $tokens));
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->setMode('decompose');
echo $surfaces($builder->build()->tokenize($text)), "\n";
// 関西 / 国際 / 空港 / 限定 / トートバッグ
$builder->setMode('decompose', $penalty);
echo $surfaces($builder->build()->tokenize($text)), "\n";
// 関西国際空港 / 限定 / トートバッグ
$tokenizer = new Lindera\Tokenizer(Lindera\Dictionary::load('embedded://ipadic'), 'decompose', penalty: $penalty);
echo $surfaces($tokenizer->tokenize($text)), "\n";
// 関西国際空港 / 限定 / トートバッグ
'normal' モードと一緒に Penalty を渡すと ValueError をスローします。
辞書管理
Lindera PHP は、形態素解析で使用する辞書の読み込み、ビルド、管理のためのクラスを提供します。
辞書の読み込み
システム辞書
Lindera\Dictionary::load($uri) を使用してシステム辞書を読み込みます。GitHub Releases からビルド済み辞書をダウンロードし、展開したディレクトリのパスを指定してください:
<?php
$dictionary = Lindera\Dictionary::load('/path/to/ipadic');
埋め込み辞書(上級者向け) -- embed-* feature フラグ付きでビルドした場合、埋め込み辞書を使用できます:
<?php
$dictionary = Lindera\Dictionary::load('embedded://ipadic');
ユーザー辞書
ユーザー辞書はシステム辞書にカスタム語彙を追加します。Lindera\Dictionary::loadUserFor() を使用して、組み合わせるシステム辞書に合わせて読み込みます。
<?php
$dictionary = Lindera\Dictionary::load('/path/to/ipadic');
$userDictionary = Lindera\Dictionary::loadUserFor('/path/to/user_dictionary.csv', $dictionary);
.csv はシステム辞書のメタデータでビルドし、ビルドしながら接続コスト行列と
照らして確認します。文脈 ID が行列の範囲外にある行は、その CSV の行番号と表層形を
示す ValueError になります(文脈 ID
を参照)。.bin はビルドされたとおりに読み込みます。
Lindera\Dictionary::loadUser($uri, $metadata) は、メタデータだけでユーザー辞書を
読み込みます。この方法で読み込んだ .csv はシステム辞書と組み合わせるときに
確認し、エラーは見出し語をその単語 ID で示します:
<?php
$dictionary = Lindera\Dictionary::load('/path/to/ipadic');
$metadata = $dictionary->metadata();
$userDictionary = Lindera\Dictionary::loadUser('/path/to/user_dictionary.csv', $metadata);
トークナイザーの作成時にユーザー辞書を渡します:
<?php
$dictionary = Lindera\Dictionary::load('/path/to/ipadic');
$userDictionary = Lindera\Dictionary::loadUserFor('/path/to/user_dictionary.csv', $dictionary);
$tokenizer = new Lindera\Tokenizer($dictionary, 'normal', $userDictionary);
または、ビルダー経由で設定します:
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('/path/to/ipadic');
$builder->setUserDictionary('/path/to/user_dictionary.csv');
$tokenizer = $builder->build();
Dictionary メソッド
| メソッド | 戻り値 | 説明 |
|---|---|---|
Dictionary::load($uri) | Dictionary | システム辞書を読み込む |
Dictionary::loadUser($path, $metadata) | UserDictionary | ユーザー辞書を読み込む |
Dictionary::loadUserFor($path, $dictionary) | UserDictionary | システム辞書に合わせてユーザー辞書を読み込む |
Dictionary::version() | string | Lindera のバージョン文字列を返す |
Dictionary::build($source, $dest, $metadata) | void | 辞書をビルドする |
Dictionary::buildUser($kind, $source, $dest, $metadata) | void | ユーザー辞書をビルドする |
Dictionary::buildUserFor($source, $dest, $dictionary) | void | システム辞書に合わせてユーザー辞書をビルドする |
$dictionary->metadata() | Metadata | 辞書のメタデータを返す |
$dictionary->metadataName() | string | 辞書名を返す |
$dictionary->metadataEncoding() | string | 辞書のエンコーディングを返す |
辞書のビルド
システム辞書のビルド
ソースファイルからシステム辞書をビルドします:
<?php
$metadata = Lindera\Metadata::fromJsonFile('/path/to/metadata.json');
Lindera\Dictionary::build('/path/to/input_dir', '/path/to/output_dir', $metadata);
入力ディレクトリには辞書のソースファイル(CSV レキシコン、matrix.def など)が含まれている必要があります。
Lindera\Metadata::fromJsonFile で読み込んだメタデータは metadata.json の space_penalty ルールを保持し、ビルドした辞書にもそのルールが同梱されます。そのため ko-dic をビルドした場合も、左側空白ペナルティはデフォルトでオンのままです。トークナイザーごとにオフにするには TokenizerBuilder::setSpacePenalty(false) を使います(Tokenizer API を参照)。コンストラクタで作成した Metadata はルールを持ちません。
以下は IPADIC 辞書をダウンロードしてビルドする例です:
<?php
$url = 'https://lindera.dev/mecab-ipadic-2.7.0-20070801.tar.gz';
$filename = '/tmp/mecab-ipadic-2.7.0-20070801.tar.gz';
// Download and extract dictionary source
file_put_contents($filename, file_get_contents($url));
$phar = new PharData($filename);
$phar->extractTo('/tmp/', null, true);
// Load metadata and build
$metadata = Lindera\Metadata::fromJsonFile('resources/ipadic_metadata.json');
Lindera\Dictionary::build(
'/tmp/mecab-ipadic-2.7.0-20070801',
'/tmp/lindera-ipadic',
$metadata
);
ユーザー辞書のビルド
組み合わせるシステム辞書に合わせて、CSV ファイルからユーザー辞書をビルドします:
<?php
$dictionary = Lindera\Dictionary::load('/path/to/ipadic');
Lindera\Dictionary::buildUserFor('user_words.csv', '/path/to/user_dictionary.bin', $dictionary);
$userDict = Lindera\Dictionary::loadUserFor('/path/to/user_dictionary.bin', $dictionary);
CSV はシステム辞書のメタデータでビルドし、接続コスト行列と照らして確認します。
文脈 ID が行列の範囲外にある行は、その CSV の行番号と表層形を示す ValueError
になり、ファイルは書き出しません(文脈 ID
を参照)。2 番目の引数は書き出すファイルのパスです。ローダーは拡張子で
ユーザー辞書の読み込み方を決めるので、.bin で終えてください。
Lindera\Dictionary::buildUser($kind, $input_file, $output_dir, $metadata) は、
メタデータだけでユーザー辞書をビルドします。$output_dir も、名前に反して
ディレクトリではなく、書き出すファイルのパスです。各行はシステム辞書と
組み合わせるときに確認し、エラーは見出し語をその単語 ID で示します:
<?php
$metadata = new Lindera\Metadata();
Lindera\Dictionary::buildUser('ipadic', 'user_words.csv', '/path/to/user_dictionary.bin', $metadata);
第一引数($kind、上の例では 'ipadic')は現時点では未使用で、将来のために
予約されています。システム辞書に合わせてビルドするには
Lindera\Dictionary::buildUserFor を使います。
Metadata
Metadata クラスは辞書のパラメータを設定します。
Metadata の作成
<?php
// デフォルトのメタデータ
$metadata = Lindera\Metadata::createDefault();
// カスタムメタデータ
$metadata = new Lindera\Metadata('my_dictionary', 'UTF-8', -10000);
JSON からの読み込み
$metadata = Lindera\Metadata::fromJsonFile('metadata.json');
プロパティ
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | string | "default" | 辞書名 |
encoding | string | "UTF-8" | 文字エンコーディング |
default_word_cost | int | -10000 | 未知語のデフォルトコスト |
toArray()
メタデータを連想配列(値はすべて文字列)で返します。ko-dic のようにメタデータが左側空白ペナルティのルールを持つ場合は、そのルールを JSON 文字列で保持する space_penalty エントリも含まれます。ルールがなければこのキーはありません:
<?php
$metadata = Lindera\Metadata::fromJsonFile('/path/to/ko-dic/metadata.json');
$rules = json_decode($metadata->toArray()['space_penalty'], true);
// ['rules' => [['pos' => ['EC', 'EF', ...], 'cost' => 3000], ...]]
Schema
Schema クラスは辞書のフィールド構造を定義します。
Schema の作成
<?php
// デフォルトスキーマ(IPADIC 互換)
$schema = Lindera\Schema::createDefault();
// カスタムスキーマ
$schema = new Lindera\Schema(['surface', 'pos']);
メソッド
| メソッド | 戻り値 | 説明 |
|---|---|---|
fieldCount() | int | フィールド数を返す |
getFieldIndex($name) | int | フィールドのインデックスを返す(見つからない場合は -1) |
getFieldByName($name) | FieldDefinition または null | フィールド情報を返す |
getCustomFields() | array<string> | カスタムフィールド名の配列を返す |
validateRecord($record) | void | レコードがスキーマに適合するか検証する |
Schema プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
fields | array<string> | フィールド名の配列 |
テキスト処理パイプライン
Lindera PHP は、トークナイズ前に文字フィルタを適用し、トークナイズ後にトークンフィルタを適用する、組み合わせ可能なテキスト処理パイプラインをサポートしています。フィルタは TokenizerBuilder に追加され、追加された順序で実行されます。
Input Text
--> Character Filters (preprocessing)
--> Tokenization
--> Token Filters (postprocessing)
--> Output Tokens
[!NOTE] このページではよく使われる一部のフィルタのみを例として紹介しています。これが全リストではありません。
lindera-analysisが提供する全フィルタ(パラメータ・使用例を含む)の正式なカタログについてはフィルタを参照してください。
文字フィルタ
文字フィルタはトークナイズ前に入力テキストを変換します。
unicode_normalize
入力テキストに Unicode 正規化を適用します。
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendCharacterFilter('unicode_normalize', ['kind' => 'nfkc']);
$tokenizer = $builder->build();
サポートされる正規化形式: "nfc"、"nfkc"、"nfd"、"nfkd"。
mapping
マッピングテーブルに従って文字や文字列を置換します。
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendCharacterFilter('mapping', [
'mapping' => [
'リンデラ' => 'lindera',
],
]);
$tokenizer = $builder->build();
japanese_iteration_mark
日本語の踊り字(繰り返し記号)を完全な形に展開します。
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendCharacterFilter('japanese_iteration_mark', [
'normalize_kanji' => 'true',
'normalize_kana' => 'true',
]);
$tokenizer = $builder->build();
トークンフィルタ
トークンフィルタはトークナイズ後にトークンを変換または除去します。
lowercase
トークンの表層形を小文字に変換します。
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendTokenFilter('lowercase');
$tokenizer = $builder->build();
japanese_base_form
辞書の形態素情報を使用して、活用形を基本形(辞書形)に置換します。
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendTokenFilter('japanese_base_form', []);
$tokenizer = $builder->build();
japanese_katakana_stem
カタカナ語の語尾の長音記号を除去してステミングを行います。
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendTokenFilter('japanese_katakana_stem', ['min' => 3]);
$tokenizer = $builder->build();
japanese_stop_tags
指定されたタグに一致する品詞のトークンを除去します。
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendTokenFilter('japanese_stop_tags', [
'tags' => ['助詞,格助詞,一般', '助詞,係助詞', '助詞,連体化', '助動詞'],
]);
$tokenizer = $builder->build();
タグは 4 階層のカンマ区切りに正規化され(不足分は * で補完)、各トークンの品詞詳細 4 つ(辞書スキーマで part_of_speech という名前のフィールドから始まる 4 つ。スキーマにそのフィールドが無い場合は先頭の詳細から)と完全一致で比較されます。IPADIC の助詞トークンは必ず 助詞,係助詞 のようにサブカテゴリを持つため、助詞 単独では一致しません。一方、助動詞はサブカテゴリを持たないため(助動詞,*,*,*)、助動詞 単独で一致します。
japanese_keep_tags
指定されたタグに一致する品詞のトークンのみを保持します。その他のトークンはすべて除去されます。
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendTokenFilter('japanese_keep_tags', [
'tags' => ['名詞,一般'],
]);
$tokenizer = $builder->build();
パイプラインの完全な例
以下の例では、複数の文字フィルタとトークンフィルタを1つのパイプラインに組み合わせています:
<?php
$builder = new Lindera\TokenizerBuilder();
// Set mode and dictionary
$builder->setMode('normal');
$builder->setDictionary('embedded://ipadic');
// Preprocessing
$builder->appendCharacterFilter('unicode_normalize', ['kind' => 'nfkc']);
$builder->appendCharacterFilter(
'japanese_iteration_mark',
['normalize_kanji' => 'true', 'normalize_kana' => 'true']
);
$builder->appendCharacterFilter('mapping', ['mapping' => ['リンデラ' => 'lindera']]);
// Postprocessing
$builder->appendTokenFilter('japanese_katakana_stem', ['min' => 3]);
$builder->appendTokenFilter('japanese_stop_tags', [
'tags' => [
'接続詞',
'助詞,格助詞,一般',
'助詞,格助詞,一般',
'助詞,係助詞',
'助詞,副助詞',
'助詞,終助詞',
'助詞,連体化',
'助動詞',
'記号',
'記号,一般',
'記号,読点',
'記号,句点',
'記号,空白',
],
]);
$builder->appendTokenFilter('lowercase');
// Build the tokenizer
$tokenizer = $builder->build();
// Tokenize
$text = 'Linderaは形態素解析エンジンです。';
$tokens = $tokenizer->tokenize($text);
foreach ($tokens as $token) {
echo $token->surface . "\t" . implode(',', $token->details) . "\n";
}
このパイプラインでは:
unicode_normalizeが全角文字を半角に変換(NFKC 正規化)japanese_iteration_markが踊り字を展開mappingが指定された文字列を置換japanese_katakana_stemがカタカナ語をステミングjapanese_stop_tagsが助詞、助動詞、記号を除去lowercaseがアルファベットを小文字に正規化
学習
Lindera PHP は、アノテーション付きコーパスからカスタム CRF ベースの形態素解析モデルを学習する機能をサポートしています。この機能には train feature が必要です。
前提条件
train feature を有効にして lindera-php をビルドします(デフォルトで有効):
cargo build -p lindera-php --features train
モデルの学習
Lindera\Trainer::train() を使用して、種辞書とアノテーション付きコーパスから CRF モデルを学習します:
<?php
Lindera\Trainer::train(
'/path/to/seed.csv', // seed: 種辞書ファイル
'/path/to/corpus.txt', // corpus: アノテーション付きコーパス
'/path/to/char.def', // char_def: 文字定義ファイル
'/path/to/unk.def', // unk_def: 未知語定義ファイル
'/path/to/feature.def', // feature_def: 素性定義ファイル
'/path/to/rewrite.def', // rewrite_def: 書き換えルール定義ファイル
'/tmp/model.dat', // output: 学習済みモデルの出力パス
0.01, // lambda: L1 正則化コスト
100, // max_iter: 最大イテレーション数
null // max_threads: スレッド数(null = 自動検出)
);
学習パラメータ
| パラメータ | 型 | デフォルト | 説明 |
|---|---|---|---|
$seed | string | 必須 | 種辞書ファイルのパス(CSV 形式) |
$corpus | string | 必須 | アノテーション付き学習コーパスのパス |
$charDef | string | 必須 | 文字定義ファイルのパス(char.def) |
$unkDef | string | 必須 | 未知語定義ファイルのパス(unk.def) |
$featureDef | string | 必須 | 素性定義ファイルのパス(feature.def) |
$rewriteDef | string | 必須 | 書き換えルール定義ファイルのパス(rewrite.def) |
$output | string | 必須 | 学習済みモデルファイルの出力パス |
$lambda | float | 0.01 | L1 正則化コスト(0.0--1.0) |
$maxIter | int | 100 | 最大学習イテレーション数 |
$maxThreads | int または null | null | スレッド数(null = CPU コア数を自動検出) |
学習済みモデルのエクスポート
学習後、Lindera\Trainer::export() を使用してモデルを辞書ソースファイルにエクスポートします:
<?php
Lindera\Trainer::export(
'/tmp/model.dat', // model: 学習済みモデルファイル
'/tmp/dictionary_source', // output: 出力ディレクトリ
'/path/to/metadata.json' // metadata: メタデータファイル(省略可)
);
エクスポートパラメータ
| パラメータ | 型 | デフォルト | 説明 |
|---|---|---|---|
$model | string | 必須 | 学習済みモデルファイルのパス(.dat) |
$output | string | 必須 | 辞書ソースファイルの出力ディレクトリ |
$metadata | string または null | null | ベースとなる metadata.json ファイルのパス |
エクスポートにより、出力ディレクトリに以下のファイルが作成されます。lindera export が作成するファイルと同じです:
lex.csv-- 学習済みコスト付きのレキシコンエントリーmatrix.def-- 連接コスト行列unk.def-- 未知語定義char.def-- 文字カテゴリ定義(学習済みモデルからコピー)feature.def-- 素性テンプレート定義(学習済みモデルからコピー)rewrite.def-- 素性リライトルール(学習済みモデルからコピー)left-id.def-- 左文脈IDから素性文字列へのマッピングright-id.def-- 右文脈IDから素性文字列へのマッピングmetadata.json-- 更新されたメタデータ($metadataパラメータ指定時)
モデルファイルは、このバージョンの学習で作ったものである必要があります。それより前のバージョンで書かれたモデルファイル(7.0.0 より前に書かれたものはすべて該当)は拒否され、Lindera\Trainer::export() は学習のやり直しを求めるエラーで失敗します。そうしたモデルは Lindera\Trainer::train() で学習し直してください。
完全なワークフロー
カスタム辞書の学習と使用の完全なワークフロー:
<?php
// Step 1: Train the CRF model
Lindera\Trainer::train(
'resources/training/seed.csv',
'resources/training/corpus.txt',
'resources/training/char.def',
'resources/training/unk.def',
'resources/training/feature.def',
'resources/training/rewrite.def',
'/tmp/model.dat',
0.01, // lambda
100, // max_iter
null // max_threads
);
// Step 2: Export to dictionary source files
Lindera\Trainer::export(
'/tmp/model.dat',
'/tmp/dictionary_source',
'resources/training/metadata.json'
);
// Step 3: Build the dictionary from exported source files
$metadata = Lindera\Metadata::fromJsonFile('/tmp/dictionary_source/metadata.json');
Lindera\Dictionary::build('/tmp/dictionary_source', '/tmp/dictionary', $metadata);
// Step 4: Use the trained dictionary
$dictionary = Lindera\Dictionary::load('/tmp/dictionary');
$tokenizer = new Lindera\Tokenizer($dictionary, 'normal');
$tokens = $tokenizer->tokenize('形態素解析のテスト');
foreach ($tokens as $token) {
echo $token->surface . "\t" . implode(',', $token->details) . "\n";
}
Lindera WASM
Lindera WASM は、wasm-bindgen を使用して構築された Lindera 形態素解析エンジンの WebAssembly バインディングです。Web ブラウザ、Node.js、バンドラー環境で日本語、韓国語、中国語のテキストトークナイズを直接実行できます。
配布フォーマット
Lindera WASM は wasm-pack を通じて複数の配布フォーマットをサポートしています:
| ターゲット | 用途 | モジュールシステム |
|---|---|---|
web | ブラウザ ESM | ES Modules |
bundler | Webpack、Vite、Rollup | ES Modules(バンドラー解決) |
辞書パッケージ
各パッケージはオフライン使用のために特定の辞書を埋め込みます:
| Feature フラグ | 辞書 | 言語 |
|---|---|---|
| (なし) | 埋め込み辞書なし | -- |
embed-ipadic | IPADIC | 日本語 |
embed-unidic | UniDic | 日本語 |
embed-ko-dic | ko-dic | 韓国語 |
embed-cc-cedict | CC-CEDICT | 中国語 |
embed-jieba | Jieba | 中国語 |
embed-cjk | IPADIC + ko-dic + Jieba | CJK |
セクション
- インストール -- lindera-wasm パッケージのビルドとインストール
- クイックスタート -- 最小限の動作例
- Tokenizer API -- JavaScript/TypeScript の完全な API リファレンス
- 辞書管理 -- 辞書の読み込みとビルド
- ブラウザでの使用 -- Web アプリケーションとの統合
- OPFS 辞書ストレージ -- OPFS による辞書の永続キャッシュ
インストール
前提条件
辞書の入手
Lindera WASM はデフォルトで辞書を同梱しません。ブラウザ環境では、OPFS(Origin Private File System)API を使用して辞書を実行時にダウンロードする方法を推奨します。
GitHub Releases からのダウンロード
ビルド済み辞書は GitHub Releases ページから入手できます。ブラウザ環境では、OPFS ヘルパーを使用して辞書をダウンロードしてキャッシュします:
import { downloadDictionary, hasDictionary } from 'lindera-wasm/opfs';
if (!await hasDictionary("ipadic")) {
await downloadDictionary(
"https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip",
"ipadic",
);
}
詳細は OPFS 辞書ストレージ を参照してください。
wasm-pack によるビルド
公開されている npm パッケージと同じ構成でビルドするには、web ターゲットを使用します:
wasm-pack build --target web
出力は lindera-wasm クレート内の pkg/ ディレクトリに書き込まれます。
web ターゲットのビルドは、ブラウザからネイティブ ES モジュールとして直接利用できるほか、モダンなバンドラー(Vite、Webpack 5 の asyncWebAssembly など)からもそのまま利用できます。バンドラー専用のビルドを別途用意する必要はありません。
なぜ web ターゲットのみなのか(bundler ではなく)
wasm-pack のデフォルトの --target は bundler ですが、公開されている lindera-wasm パッケージは意図的に --target web でビルドしています。ターゲット名はやや誤解を招きやすいため、それぞれの実態を説明します:
--target bundler(wasm-pack のデフォルト)は、.wasmファイルを ES モジュールとして直接importする JavaScript を生成します。これはまだ標準化が完了していない WebAssembly ESM integration 提案に依存しており、実際にこの出力を解釈できるのは事実上asyncWebAssemblyexperiment を有効にした Webpack だけです。このデフォルトは Webpack が支配的だった時代の名残であり、「bundler 汎用」という名前に反して実態は Webpack 専用に近い出力です。たとえば Vite で利用するには追加プラグイン(vite-plugin-wasmと top-level-await 対応)が必要です。--target webは標準技術のみで構成されたコードを生成します。.wasmファイルはnew URL('lindera_wasm_bg.wasm', import.meta.url)で解決され、明示的な非同期 init 関数の中でfetch+WebAssembly.instantiateStreamingにより読み込まれます。ビルドステップなしでブラウザのネイティブ ES モジュールとして動作するほか、モダンなバンドラー(Vite、Webpack 5、Rollup)はnew URL(..., import.meta.url)パターンを認識して.wasmファイルをアセットとして配置します。唯一のコストは、API を使う前に一度await __wbg_init()を呼ぶ必要があることです。
まとめると、bundler ターゲットは Webpack に限ればゼロコンフィグですが、web ターゲットは明示的な init 呼び出し 1 回のコストでどこでも動きます。v5 までは両方のビルド(lindera-wasm-web と lindera-wasm-bundler)を公開していましたが、v6 からは可搬性の高い単一ビルドに統合しました。lindera-wasm-bundler からの移行は v5 から v6 への移行ガイドを参照してください。
利用可能な Feature フラグ(上級者向け)
辞書を WASM バイナリに直接埋め込みたい上級者向けに、以下の feature フラグが利用できます。バイナリサイズが大幅に増加しますが、実行時の辞書ダウンロードが不要になります。
| Feature | 辞書 | 言語 |
|---|---|---|
embed-ipadic | IPADIC | 日本語 |
embed-unidic | UniDic | 日本語 |
embed-sudachidict | SudachiDict | 日本語(注意: 約570MB、WASMでは通常非現実的) |
embed-ko-dic | ko-dic | 韓国語 |
embed-cc-cedict | CC-CEDICT | 中国語 |
embed-jieba | Jieba | 中国語 |
embed-cjk | IPADIC + ko-dic + Jieba | CJK(全言語) |
複数の feature フラグを有効にして複数の辞書を組み合わせることができます:
wasm-pack build --target web --features embed-ipadic,embed-ko-dic
npm パッケージの命名規則
スコープなしの lindera-wasm-* プレフィックスは Lindera プロジェクトの名前空間です。辞書を埋め込んだ独自ビルドを npm に公開する場合は、公式パッケージとの誤認を避けるため、必ず自分のスコープ配下の名前で公開してください:
@your-scope/lindera-wasm-{dict}
例:
@your-scope/lindera-wasm-ipadic@your-scope/lindera-wasm-unidic@your-scope/lindera-wasm-cjk
公開前にパッケージ名を設定するには、生成された pkg/package.json の name フィールドを編集します。なお、辞書を埋め込んだパッケージは辞書の再配布にあたるため、各辞書のライセンス条件(帰属表示など)にも従ってください。
[!NOTE] 本プロジェクトのリリースワークフロー(
.github/workflows/release.yml)が実際にビルドして npm に公開しているのは、embed-*feature を一切使わずにwebターゲットでビルドしたlindera-wasmという汎用パッケージのみです。lindera-wasm-ipadicのようなスコープなしの辞書名付きパッケージ名は、v3 以前のリリースの名残としてプロジェクトが確保しているもので、現在は更新されていません。第三者がこれらの名前で公開することはできません(しないでください)。本ドキュメント内でのこれらの名前は、対応するembed-*feature(上記の利用可能な Feature フラグを参照)でローカルビルドした場合の説明用の例に過ぎません。
npm からのインストール
ビルド済みパッケージが npm で公開されています:
npm install lindera-wasm
または yarn で:
yarn add lindera-wasm
[!NOTE] npm パッケージには辞書が含まれていません。OPFS ヘルパーを使用して辞書を実行時にダウンロードしてください。詳細は OPFS 辞書ストレージ を参照してください。
クイックスタート
Web(ブラウザ) -- OPFS 辞書ロード
推奨される方法は、OPFS ヘルパーを使用して辞書を実行時にダウンロードすることです:
import __wbg_init, { TokenizerBuilder, loadDictionaryFromBytes } from 'lindera-wasm';
import { downloadDictionary, loadDictionaryFiles, hasDictionary } from 'lindera-wasm/opfs';
async function main() {
await __wbg_init();
// キャッシュされていない場合は辞書をダウンロード
if (!await hasDictionary("ipadic")) {
await downloadDictionary(
"https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip",
"ipadic",
);
}
// OPFS から辞書を読み込み
const files = await loadDictionaryFiles("ipadic");
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictTrie, files.dictValsIdx, files.dictVals,
files.dictWordsIdx, files.dictWords, files.matrixMtx, files.charDef,
files.unk,
);
// トークナイザーを構築
const builder = new TokenizerBuilder();
builder.setDictionaryInstance(dictionary);
builder.setMode("normal");
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("関西国際空港限定トートバッグ");
tokens.forEach(token => {
console.log(`${token.surface}\t${token.details.join(',')}`);
});
}
main();
注意: ビルド済み辞書を GitHub Releases からダウンロードしてください。完全なワークフローは OPFS 辞書ストレージ を参照してください。
期待される出力:
関西国際空港 名詞,固有名詞,一般,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ 名詞,一般,*,*,*,*,*,*,*
埋め込み辞書の使用(上級者向け)
embed-* feature フラグ付きでビルドした場合、埋め込み辞書を使用できます:
[!NOTE] ここでの
lindera-wasm-ipadicは説明用のパッケージ名であり、npm に公開されているものではありません。実際に公開されているのはlindera-wasmのみです。このようなパッケージを自分でビルド・命名する方法は npm パッケージの命名規則 を参照してください。
import __wbg_init, { TokenizerBuilder } from 'lindera-wasm-ipadic';
async function main() {
await __wbg_init();
const builder = new TokenizerBuilder();
builder.setDictionary("embedded://ipadic");
builder.setMode("normal");
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("関西国際空港限定トートバッグ");
tokens.forEach(token => {
console.log(`${token.surface}\t${token.details.join(',')}`);
});
}
main();
フィルタの使用
トークナイズパイプラインに文字フィルタやトークンフィルタを追加できます:
import __wbg_init, { TokenizerBuilder, loadDictionaryFromBytes } from 'lindera-wasm';
import { loadDictionaryFiles } from 'lindera-wasm/opfs';
async function main() {
await __wbg_init();
// OPFS にキャッシュ済みの辞書を読み込み
const files = await loadDictionaryFiles("ipadic");
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictTrie, files.dictValsIdx, files.dictVals,
files.dictWordsIdx, files.dictWords, files.matrixMtx, files.charDef,
files.unk,
);
const builder = new TokenizerBuilder();
builder.setDictionaryInstance(dictionary);
builder.setMode("normal");
// Add Unicode NFKC normalization
builder.appendCharacterFilter("unicode_normalize", { kind: "nfkc" });
// Add a stop-tags filter to remove particles and auxiliary verbs
builder.appendTokenFilter("japanese_stop_tags", {
tags: ["助詞,格助詞,一般", "助詞,係助詞", "助詞,連体化", "助動詞"]
});
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("Linderaは形態素解析エンジンです");
tokens.forEach(token => {
console.log(`${token.surface}\t${token.details.join(',')}`);
});
}
main();
N-Best トークナイズ
コスト順にランク付けされた複数のトークナイズ候補を取得します:
const results = tokenizer.tokenizeNbest("すもももももももものうち", 3);
results.forEach((result, rank) => {
console.log(`--- NBEST ${rank + 1} (cost=${result.cost}) ---`);
result.tokens.forEach(token => {
console.log(`${token.surface}\t${token.details.join(',')}`);
});
});
Tokenizer API
このページでは、lindera-wasm が公開する JavaScript/TypeScript API について説明します。
TokenizerBuilder
設定済みの Tokenizer インスタンスを作成するためのビルダークラスです。
コンストラクタ
const builder = new TokenizerBuilder();
デフォルト設定で新しいビルダーを作成します。
メソッド
すべてのセッターは同じ設定を共有するビルダーハンドルを返すため、
builder.setMode("normal").setDictionary(...) のようにチェーンでも、1 文ずつでも記述できます。
返されるハンドルは新しいオブジェクトですが、設定先は同一のビルダーです。
setMode(mode, penalty?)
トークナイズモードを設定します。
- パラメータ:
mode(string) --"normal"または"decompose"penalty(Penalty, 省略可) --"decompose"モードが使うPenalty。nullまたはundefined(デフォルト)では既定のペナルティを使います。渡したPenaltyはそのまま使えるので、同じものを何度でも渡せます。
- 戻り値:
TokenizerBuilder
"normal" とペナルティを一緒に渡すと、不正なモードと同じくエラー文字列が投げられます。Penalty でない値を渡したときも同じです。
builder.setMode("normal");
builder.setMode("decompose", new Penalty(undefined, 0));
setDictionary(uri)
トークナイズに使用する辞書を設定します。setDictionaryInstance() で設定した辞書は、この辞書に置き換わります。
- パラメータ:
uri(string) -- 辞書の URI(例:"embedded://ipadic") - 戻り値:
TokenizerBuilder
builder.setDictionary("embedded://ipadic");
setDictionaryInstance(dictionary)
読み込み済みの辞書インスタンスをトークナイズに設定します。
URI の代わりにバイトデータから読み込んだ辞書(例: loadDictionaryFromBytes() 経由)を使用する場合に使います。
setDictionary() で設定した辞書は、この辞書に置き換わります(2 つのうち最後に呼んだ方の辞書が使われます)。ビルダーのほかの設定(モード、setUserDictionaryInstance()、setKeepWhitespace()、setSkipWhitespace()、setSpacePenalty()、追加したフィルタ)は、URI で設定した辞書と同じようにこの辞書にも適用されます。
- パラメータ:
dictionary(Dictionary) -- 読み込み済みの辞書オブジェクト - 戻り値:
TokenizerBuilder
import { loadDictionaryFromBytes } from 'lindera-wasm';
import { loadDictionaryFiles } from 'lindera-wasm/opfs';
const files = await loadDictionaryFiles("ipadic");
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictTrie, files.dictValsIdx, files.dictVals,
files.dictWordsIdx, files.dictWords, files.matrixMtx, files.charDef,
files.unk,
);
builder.setDictionaryInstance(dictionary);
setUserDictionaryInstance(userDictionary)
読み込み済みのユーザー辞書インスタンスを設定します。loadUserDictionaryFromBytesFor() か loadUserDictionaryFromBytes()(CSV)、または loadUserDictionaryBinFromBytes()(ビルド済み .bin)でバイト列から読み込んでください。WebAssembly では URI ベースのユーザー辞書は使用できません。ユーザー辞書は、setDictionary() と setDictionaryInstance() のどちらで設定した辞書にも適用されます。
- パラメータ:
userDictionary(UserDictionary) -- 読み込み済みのユーザー辞書オブジェクト - 戻り値:
TokenizerBuilder
setKeepWhitespace(keep)
出力に空白トークンを保持するかどうかを設定します。保持した空白はラティスにも残るため、他のトークンも、MeCab と同様に空白をラティス上で読み飛ばすデフォルトの出力と変わることがあります。空白を出力せずにラティスにだけ残すには、代わりに setSkipWhitespace(false) を使います。
- パラメータ:
keep(boolean) --trueで空白トークンを保持 - 戻り値:
TokenizerBuilder
builder.setKeepWhitespace(true);
setSkipWhitespace(skip)
空白をラティス上で読み飛ばすかどうかを設定します。意味は設定ファイルの segmenter.skip_whitespace と同じです。true では空白を読み飛ばし、MeCab と同様に空白の前後の語を直接接続します。false では空白を独立したノード(空白の見出し語か SPACE の未知語)としてラティスに残します(v7 より前の動作。空白は引き続き出力から除外されます)。null または undefined では、辞書の metadata.json のデフォルトに戻ります(SudachiDict 以外では読み飛ばします)。
ここで設定した値は、setDictionary() と setDictionaryInstance() のどちらで設定した辞書でも、辞書のデフォルトより優先されます。空白トークンを保持するとき(setKeepWhitespace(true))は効果がありません。保持した空白は常にラティスに残るためです。たとえば IPADIC では、Google が 新しい の が は、デフォルトでは助詞(助詞)、setSkipWhitespace(false) では接続詞(接続詞)と解析されます。詳しくは Segmenter を参照してください。
- パラメータ:
skip(boolean | null | undefined) -- 設定値。nullまたはundefinedで辞書のデフォルト。TypeScript の宣言では引数を省略できません。それ以外の値は JavaScript の真偽値の規則で扱われます - 戻り値:
TokenizerBuilder
// v7 より前と同じく、空白をラティスに残す
builder.setSkipWhitespace(false);
setSpacePenalty(value)
韓国語向けの左側空白ペナルティ(left-space penalty)を設定します。直前に空白がある候補のうち、本来は前の語に空白なしで付く品詞(助詞、語尾など)の候補にコストを加算します。
- パラメータ:
value(boolean | object | null | undefined) -- 設定値。意味は設定ファイルのsegmenter.space_penaltyと同じです - 戻り値:
TokenizerBuilder
value は次のいずれかの形式をとります。それ以外の値ではエラー文字列が投げられます:
nullまたはundefined-- デフォルト。辞書がmetadata.jsonに同梱するルールがあればそれを使います(ko-dic は mecab-ko-dic のルールを同梱し、他の同梱辞書はルールを持ちません)。以前の呼び出しを取り消すときにも使います。false-- ペナルティをオフにします。true-- 辞書のルールを要求します。ルールを同梱しない辞書ではbuild()がエラーを投げます。{ rules: [{ pos: [...], cost: n }, ...] }形式のオブジェクト -- 辞書のルールの代わりにこのルールを適用します。先頭品詞タグがposに含まれる候補にcostを加算し、最初に一致したルールが使われます。
この設定は、setDictionary() で設定した辞書にも、setDictionaryInstance() で設定した辞書(たとえば loadDictionaryFromBytes() で OPFS から読み込んだ ko-dic)にも適用されます。辞書オブジェクト自体は変更されません。Tokenizer コンストラクタは、同じ設定を引数 spacePenalty で受け取ります。
// ペナルティをオフにする
builder.setSpacePenalty(false);
// 明示的なルール
builder.setSpacePenalty({
rules: [
{ pos: ["EC", "EF", "EP", "ETM", "ETN", "VCP", "XSA", "XSN", "XSV"], cost: 3000 },
{ pos: ["JC", "JKB", "JKC", "JKG", "JKO", "JKQ", "JKS", "JKV", "JX"], cost: 6000 },
],
});
[!NOTE] v6.1.0 から、ko-dic では mecab-ko と同じく左側空白ペナルティがデフォルトで適用されます。たとえば
검색 이 잘 된다の이は感動詞ICと解析されます(ペナルティがなければ主格助詞JKS)。オフにするにはbuilder.setSpacePenalty(false)を呼び出してください。ルールを同梱しているのは Lindera 6.1.0 以降でビルドした ko-dic だけです。v6.0.0 リリースの ko-dic ではペナルティはオフのままで、辞書を再ビルドするまでtrueはエラーになります。詳しくは Segmenter を参照してください。
appendCharacterFilter(name, args)
前処理パイプラインに文字フィルタを追加します。
- パラメータ:
name(string) -- フィルタ名(例:"unicode_normalize"、"japanese_iteration_mark")args(object, 省略可) -- フィルタの設定
- 戻り値:
TokenizerBuilder
builder.appendCharacterFilter("unicode_normalize", { kind: "nfkc" });
appendTokenFilter(name, args)
後処理パイプラインにトークンフィルタを追加します。
- パラメータ:
name(string) -- フィルタ名(例:"japanese_stop_tags"、"lowercase")args(object, 省略可) -- フィルタの設定
- 戻り値:
TokenizerBuilder
builder.appendTokenFilter("japanese_stop_tags", {
tags: ["助詞,格助詞,一般", "助詞,係助詞", "助詞,連体化", "助動詞", "記号,句点", "記号,読点"]
});
setSpacePenalty、appendCharacterFilter、appendTokenFilter の引数は、配列、オブジェクト、Map の入れ子が 128 段までです。それより深い値や、自分自身を含む値を渡すと、エラー文字列が投げられます。
build()
設定済みの Tokenizer インスタンスをビルドして返します。辞書を setDictionary() と setDictionaryInstance() のどちらで設定しても、すべての設定が適用されます。ビルド後もビルダーはそのまま使えるため、同じ設定から複数のトークナイザーをビルドできます。
- 戻り値:
Tokenizer
const tokenizer = builder.build();
Tokenizer
メインのトークナイザークラスです。TokenizerBuilder.build() またはコンストラクタ経由で作成できます。
Tokenizer コンストラクタ
const tokenizer = new Tokenizer(dictionary, mode, userDictionary, spacePenalty, penalty);
- パラメータ:
dictionary(Dictionary) -- 読み込み済みの辞書オブジェクトmode(string, 省略可) -- トークナイズモード("normal"または"decompose"、デフォルト:"normal")userDictionary(UserDictionary, 省略可) -- 読み込み済みのユーザー辞書spacePenalty(boolean | object, 省略可) -- 左側空白ペナルティ。setSpacePenalty()と同じ値を渡せます。nullまたはundefined(デフォルト)では、辞書が同梱するルールを使います。不正な設定では、setSpacePenalty()やbuild()と同じくエラー文字列が投げられます。penalty(Penalty, 省略可) --"decompose"モードのPenalty。setMode()と同じものを渡せ、エラーも同じです。
Tokenizer メソッド
tokenize(text)
入力テキストをトークナイズします。
- パラメータ:
text(string) -- トークナイズするテキスト - 戻り値:
Token[]-- トークンオブジェクトの配列
const tokens = tokenizer.tokenize("関西国際空港");
tokenizeSurfaces(text)
入力テキストをトークナイズし、トークンの surface のみを返します。分かち書き用途の高速パスです。トークンオブジェクトを生成せず、形態素の詳細情報もロードしないため、surface 文字列だけが必要な場合は tokenize より大幅に高速です。結果は tokenizer.tokenize(text).map((t) => t.surface) と一致します。(Web Worker とは無関係です。)
- パラメータ:
text(string) -- トークナイズするテキスト - 戻り値:
string[]-- surface 文字列の配列
const surfaces = tokenizer.tokenizeSurfaces("関西国際空港");
// ["関西国際空港"]
tokenizeNbest(text, n, unique?, costThreshold?)
トータルパスコスト順に N-best トークナイズ結果を返します。
- パラメータ:
text(string) -- トークナイズするテキストn(number) -- 返す結果の数。0 以上の整数unique(boolean, 省略可) -- 同一のセグメンテーション結果を重複排除(デフォルト:false)costThreshold(bigint, 省略可) --bestCost + threshold以内のパスのみ返す。64 ビットに収まる値
- 戻り値:
{ tokens: Token[], cost: number }の配列
const results = tokenizer.tokenizeNbest("すもももももももものうち", 3);
// コスト閾値を指定する場合 -- bigint リテラルとして渡す必要がある点に注意
const resultsWithThreshold = tokenizer.tokenizeNbest("すもももももももものうち", 3, false, 100n);
Token
トークナイザーが生成する単一のトークンで、プレーンな JavaScript オブジェクトです。
プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
surface | string | トークンの表層形 |
byteStart | number | 元テキストでの開始バイトオフセット |
byteEnd | number | 元テキストでの終了バイトオフセット |
position | number | トークンの位置インデックス |
wordId | number | 辞書内の単語 ID |
isUnknown | boolean | 未知語かどうか |
details | string[] | 形態素の詳細フィールド |
[!NOTE] トークンは
wasm_bindgenのクラスインスタンスではなくプレーンオブジェクトです。クラスインスタンスは JavaScript 側が解放するまでデータを Rust ヒープ上に保持するため、yield しない同期ループでトークナイズするとメモリが蓄積します。プレーンオブジェクトなら Rust 側に確保されるものが無く、結果はJSON.stringify、structuredClone、worker への転送を変換なしで通過します。フィールド名はlindera-nodejsバインディングと同じ camelCase です。
詳細情報の読み出し
details を直接インデックスします。範囲外のインデックスは undefined になります。
const pos = token.details[0]; // 例: "名詞"
const reading = token.details[7]; // 例: "トウキョウ"
トークンのシリアライズ
トークンは既にプレーンオブジェクトなので、そのままシリアライズできます。
console.log(JSON.stringify(token, null, 2));
ヘルパー関数
[!NOTE] 以下の例は
lindera-wasm-ipadicからインポートしていますが、これはembed-ipadicfeature を使ってローカルビルドした場合の説明用パッケージ名であり、npm に公開されているものではありません。実際に公開されているのはlindera-wasmのみです。詳細は npm パッケージの命名規則 を参照してください。
loadDictionary(uri)
指定された URI から辞書を読み込みます。
- パラメータ:
uri(string) -- 辞書の URI(例:"embedded://ipadic") - 戻り値:
Dictionary
import { loadDictionary } from 'lindera-wasm-ipadic';
const dict = loadDictionary("embedded://ipadic");
loadUserDictionaryFromBytes(csv, metadata)
CSV バイト列(UTF-8)からユーザー辞書をビルドします。バイト列は fetch・ファイル入力・OPFS から取得します。
- パラメータ:
csv(Uint8Array) -- ユーザー辞書 CSV の内容metadata(Metadata) -- 組み合わせるシステム辞書のメタデータ(例:dictionary.metadata)
- 戻り値:
UserDictionary
loadUserDictionaryFromBytesFor(csv, dictionary)
読み込み済みのシステム辞書に合わせて、CSV バイト列(UTF-8)からユーザー辞書をビルドします。CSV はその辞書のメタデータでビルドし、接続コスト行列と照らして確認するので、文脈 ID が行列の範囲外にある行は、その CSV の行番号と表層形を示すエラーになります。辞書は借用するだけで、その後も使えます。
- パラメータ:
csv(Uint8Array) -- ユーザー辞書 CSV の内容dictionary(Dictionary) -- 組み合わせるシステム辞書
- 戻り値:
UserDictionary
loadUserDictionaryBinFromBytes(bytes)
ビルド済みユーザー辞書(lindera build --user の出力)をバイト列から読み込みます。
- パラメータ:
bytes(Uint8Array) --.binの内容 - 戻り値:
UserDictionary
version() / getVersion()
lindera-wasm パッケージのバージョン文字列を返します。
- 戻り値:
string
import { version } from 'lindera-wasm-ipadic';
console.log(version()); // 例: "7.0.0"
列挙型とユーティリティクラス
Mode
トークナイズモードの列挙型です。
| 値 | 説明 |
|---|---|
Mode.Normal | 辞書コストに基づく標準的なトークナイズ |
Mode.Decompose | ペナルティベースのセグメンテーションによる複合語分解 |
Penalty
decompose モードの設定です。複合語をどの程度積極的に分解するかを制御します。
const penalty = new Penalty(
kanjiThreshold?, // 漢字の長さ閾値(デフォルト: 2)
kanjiPenalty?, // 漢字の長さペナルティ(デフォルト: 3000)
otherThreshold?, // その他の文字の長さ閾値(デフォルト: 7)
otherPenalty?, // その他の文字の長さペナルティ(デフォルト: 1700)
);
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
kanji_penalty_length_threshold | number | 2 | 漢字複合語分割の長さ閾値 |
kanji_penalty_length_penalty | number | 3000 | 閾値を超える漢字複合語のペナルティコスト |
other_penalty_length_threshold | number | 7 | 非漢字複合語分割の長さ閾値 |
other_penalty_length_penalty | number | 1700 | 閾値を超える非漢字複合語のペナルティコスト |
"decompose" と一緒に setMode() か Tokenizer のコンストラクタ に渡します。Penalty は読み取られるだけで消費されないので、同じものを何度でも渡せます。長さのペナルティをなくすと、decompose モードでも長い語が分割されずに残ります。
import { Penalty, Tokenizer, TokenizerBuilder, loadDictionary } from 'lindera-wasm-ipadic';
const text = "関西国際空港限定トートバッグ";
const penalty = new Penalty(undefined, 0, undefined, 0);
const surfaces = (tokenizer) => tokenizer.tokenize(text).map((token) => token.surface);
const builder = new TokenizerBuilder();
builder.setDictionary("embedded://ipadic");
console.log(surfaces(builder.setMode("decompose").build()));
// [ '関西', '国際', '空港', '限定', 'トートバッグ' ]
console.log(surfaces(builder.setMode("decompose", penalty).build()));
// [ '関西国際空港', '限定', 'トートバッグ' ]
const tokenizer = new Tokenizer(loadDictionary("embedded://ipadic"), "decompose", null, null, penalty);
console.log(surfaces(tokenizer));
// [ '関西国際空港', '限定', 'トートバッグ' ]
"normal" モードと一緒にペナルティを渡すと、エラー文字列が投げられます。
LinderaError
Lindera 操作のエラー型です。
const error = new LinderaError("message");
console.log(error.message); // "message"
console.log(error.toString()); // "message"
| プロパティ / メソッド | 型 | 説明 |
|---|---|---|
message | string | エラーメッセージ |
toString() | string | エラーメッセージを返す |
[!NOTE]
LinderaErrorはユーティリティクラスとしてエクスポートされていますが、TokenizerBuilder・Tokenizer・辞書読み込み関数(lindera-wasm/src/tokenizer.rs、lindera-wasm/src/dictionary.rs)の実際のエラーパスはすべてJsValue::from_str(...)で reject しており、JsLinderaError/LinderaErrorのインスタンスではありません。そのため、これらの API が投げるエラーは JavaScript 側では単なる文字列として現れます。catch (e) { ... }で捕捉する際は、eをLinderaErrorインスタンスではなくstringとして扱ってください。
snake_case エイリアス
Python API との一貫性のため、すべてのメソッドは snake_case 形式でも利用可能です:
| camelCase | snake_case |
|---|---|
setMode() | set_mode() |
setDictionary() | set_dictionary() |
setDictionaryInstance() | set_dictionary_instance() |
setUserDictionaryInstance() | set_user_dictionary_instance() |
setKeepWhitespace() | set_keep_whitespace() |
setSkipWhitespace() | set_skip_whitespace() |
setSpacePenalty() | set_space_penalty() |
appendCharacterFilter() | append_character_filter() |
appendTokenFilter() | append_token_filter() |
tokenizeSurfaces() | tokenize_surfaces() |
tokenizeNbest() | tokenize_nbest() |
loadDictionary() | load_dictionary() |
loadDictionaryFromBytes() | load_dictionary_from_bytes() |
loadUserDictionaryFromBytes() | load_user_dictionary_from_bytes() |
loadUserDictionaryFromBytesFor() | load_user_dictionary_from_bytes_for() |
loadUserDictionaryBinFromBytes() | load_user_dictionary_bin_from_bytes() |
辞書管理
OPFS からの辞書読み込み
WASM で辞書を使用する推奨方法は、GitHub Releases からダウンロードし、OPFS 経由で読み込むことです。これにより、WASM バイナリに大きな辞書を埋め込む必要がなくなります。
バイトデータからの読み込み
OPFS やその他のブラウザストレージに保存された辞書を loadDictionaryFromBytes() で読み込みます。
loadDictionaryFromBytes(metadata, dictTrie, dictValsIdx, dictVals, dictWordsIdx, dictWords, matrixMtx, charDef, unk)
- パラメータ:
metadata(Uint8Array) --metadata.jsonの内容dictTrie(Uint8Array) --dict.trieの内容(文字単位のダブル配列トライ)dictValsIdx(Uint8Array) --dict.valsidxの内容(単語値インデックス)dictVals(Uint8Array) --dict.valsの内容(単語値データ)dictWordsIdx(Uint8Array) --dict.wordsidxの内容(単語詳細インデックス)dictWords(Uint8Array) --dict.wordsの内容(単語詳細)matrixMtx(Uint8Array) --matrix.mtxの内容(連接コスト行列)charDef(Uint8Array) --char_def.binの内容(文字定義)unk(Uint8Array) --unk.binの内容(未知語辞書)
- 戻り値:
Dictionary
import { loadDictionaryFromBytes, TokenizerBuilder } from 'lindera-wasm';
import { loadDictionaryFiles } from 'lindera-wasm/opfs';
// OPFS から辞書ファイルを読み込む
const files = await loadDictionaryFiles("ipadic");
// バイトデータから Dictionary を作成
const dictionary = loadDictionaryFromBytes(
files.metadata,
files.dictTrie,
files.dictValsIdx,
files.dictVals,
files.dictWordsIdx,
files.dictWords,
files.matrixMtx,
files.charDef,
files.unk,
);
// TokenizerBuilder で使用
const builder = new TokenizerBuilder();
builder.setDictionaryInstance(dictionary);
builder.setMode("normal");
const tokenizer = builder.build();
ダウンロードとキャッシュを含む完全なワークフローは OPFS 辞書ストレージ を参照してください。
埋め込み辞書(上級者向け)
embed-* feature フラグ付きでビルドした場合、embedded:// URI スキームで埋め込み辞書を読み込めます。WASM バイナリのサイズが大幅に増加します。
[!NOTE] 以下の例の
lindera-wasm-ipadicはembed-ipadicfeature でローカルビルドした場合の説明用パッケージ名であり、npm に公開されているものではありません。実際に公開されているのはlindera-wasmのみです。詳細は npm パッケージの命名規則 を参照してください。
埋め込み辞書の読み込み
import { loadDictionary } from 'lindera-wasm-ipadic';
const dictionary = loadDictionary("embedded://ipadic");
利用可能な埋め込み辞書の URI(ビルド時に有効にした feature に依存):
| URI | Feature フラグ |
|---|---|
embedded://ipadic | embed-ipadic |
embedded://unidic | embed-unidic |
embedded://ko-dic | embed-ko-dic |
embedded://cc-cedict | embed-cc-cedict |
embedded://jieba | embed-jieba |
TokenizerBuilder での使用
const builder = new TokenizerBuilder();
builder.setDictionary("embedded://ipadic");
builder.setMode("normal");
const tokenizer = builder.build();
Tokenizer コンストラクタでの使用
import { loadDictionary, Tokenizer } from 'lindera-wasm-ipadic';
const dictionary = loadDictionary("embedded://ipadic");
const tokenizer = new Tokenizer(dictionary, "normal");
Dictionary クラス
Dictionary クラスは、読み込み済みの形態素解析辞書を表します。
プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
name | string | 辞書名(例: "ipadic") |
encoding | string | 辞書の文字エンコーディング |
metadata | Metadata | 完全なメタデータオブジェクト |
console.log(dictionary.name); // "ipadic"
console.log(dictionary.encoding); // "utf-8"
ユーザー辞書
ユーザー辞書を使用すると、システム辞書にないカスタム語彙を追加できます。
WebAssembly にはファイルシステムが無いため、ユーザー辞書は JavaScript 側で
取得したバイト列(fetch・<input type="file">・OPFS)から読み込みます。
CSV バイト列からのユーザー辞書の読み込み
ユーザー辞書を組み合わせるシステム辞書を渡してください。CSV はそのスキーマで 解釈し、その接続コスト行列と照らして確認します。CSV の内容は UTF-8 である必要が あります。
import { loadUserDictionaryFromBytesFor } from 'lindera-wasm';
const response = await fetch('/dictionaries/user_dict.csv');
const csvBytes = new Uint8Array(await response.arrayBuffer());
const userDict = loadUserDictionaryFromBytesFor(csvBytes, dictionary);
文脈 ID が行列の範囲外にある行は、その CSV の行番号と表層形を示すエラーになります
(文脈 ID を参照)。辞書は借用する
だけなので、その後も Tokenizer に渡せます。
loadUserDictionaryFromBytes(csv, metadata) は、メタデータ(例:
dictionary.metadata)だけで CSV をビルドします。文脈 ID はシステム辞書と
組み合わせるときに確認し、エラーは見出し語をその単語 ID で示します。
OPFS 上のファイルも同じ形で使えます:
const root = await navigator.storage.getDirectory();
const handle = await root.getFileHandle('user_dict.csv');
const csvBytes = new Uint8Array(await (await handle.getFile()).arrayBuffer());
const userDict = loadUserDictionaryFromBytesFor(csvBytes, dictionary);
ビルド済みユーザー辞書(.bin)の読み込み
lindera build --user でコンパイルしたユーザー辞書はそのまま読み込めます:
import { loadUserDictionaryBinFromBytes } from 'lindera-wasm';
const response = await fetch('/dictionaries/user_dict.bin');
const binBytes = new Uint8Array(await response.arrayBuffer());
const userDict = loadUserDictionaryBinFromBytes(binBytes);
Tokenizer でのユーザー辞書の使用
import { loadDictionaryFromBytes, loadUserDictionaryFromBytesFor, Tokenizer } from 'lindera-wasm';
import { loadDictionaryFiles } from 'lindera-wasm/opfs';
const files = await loadDictionaryFiles("ipadic");
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictTrie, files.dictValsIdx, files.dictVals,
files.dictWordsIdx, files.dictWords, files.matrixMtx, files.charDef,
files.unk,
);
const response = await fetch('/dictionaries/user_dict.csv');
const csvBytes = new Uint8Array(await response.arrayBuffer());
const userDict = loadUserDictionaryFromBytesFor(csvBytes, dictionary);
const tokenizer = new Tokenizer(dictionary, "normal", userDict);
ユーザー辞書の CSV フォーマット
ユーザー辞書の CSV は Lindera ユーザー辞書と同じフォーマットに準拠します。 IPADIC のシンプル形式は以下のとおりです:
東京スカイツリー,カスタム名詞,トウキョウスカイツリー
東武スカイツリーライン,カスタム名詞,トウブスカイツリーライン
各行の構成: surface,part_of_speech,reading。辞書のフル形式
(IPADIC では 13 フィールド以上)の行も併用できます。内容は UTF-8 で
ある必要があります。
辞書のビルド
WebAssembly では辞書のビルドはできません。ビルドはソースディレクトリを
読み出力ディレクトリへ書き込みますが、wasm32-unknown-unknown には
ファイルシステムが無いためです。辞書は lindera CLI(またはネイティブ
バインディング)でビルドし、結果をバイト列としてここに読み込んでください。
ビルド済み辞書のダウンロードは OPFS 辞書管理 を参照してください。
space_penalty ルールを同梱する metadata.json からビルドした辞書はその
ルールを保持し、loadDictionaryFromBytes() はほかのメタデータと一緒に
ルールも読み込みます。ko-dic の metadata.json は mecab-ko-dic のルールを
同梱しているため、ko-dic を使うトークナイザーでは左側空白ペナルティが
デフォルトで適用されます。オフにする方法は
setSpacePenalty() を参照してください。
Metadata
Metadata クラスは辞書のパラメータを設定します。
コンストラクタ
const metadata = new Metadata(name?, encoding?);
- パラメータ:
name(string, 省略可) -- 辞書名(デフォルト:"default")encoding(string, 省略可) -- 文字エンコーディング(デフォルト:"UTF-8")
静的メソッド
Metadata.createDefault()
デフォルト値で Metadata インスタンスを作成します。
const metadata = Metadata.createDefault();
Metadata プロパティ
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | string | "default" | 辞書名 |
encoding | string | "UTF-8" | 文字エンコーディング |
dictionary_schema | Schema | IPADIC スキーマ | メイン辞書のスキーマ |
user_dictionary_schema | Schema | 最小スキーマ | ユーザー辞書のスキーマ |
すべてのプロパティは取得と設定の両方に対応しています:
const metadata = Metadata.createDefault();
metadata.name = "custom_dict";
metadata.encoding = "EUC-JP";
console.log(metadata.name); // "custom_dict"
[!NOTE] Python・Node.js・Ruby・PHP の各バインディングと異なり、WASM の
Metadataクラスはdefault_word_cost、default_left_context_id、default_right_context_id、default_field_value、flexible_csv、skip_invalid_cost_or_idを取得・設定可能なプロパティとして公開していません(lindera-wasm/src/metadata.rs参照)。これらは常にバインディング共通のデフォルト値(コスト-10000、文脈 ID1288、フィールド値"*"、フラグはすべてfalse)にフォールバックし、JavaScript から変更することはできません。space_penaltyルールも公開していません。ルールが効くのは、そのルールと一緒に読み込んだ辞書だけです(setSpacePenalty()を参照)。dictionary.metadataをloadUserDictionaryFromBytes()などに渡しても、ほかの場所でルールが適用されることはありません。
読み込み済み辞書のメタデータには dictionary.metadata からアクセスできます。
Schema
Schema クラスは辞書エントリのフィールド構造を定義します。
Schema コンストラクタ
const schema = new Schema(["surface", "left_id", "right_id", "cost", "pos", "reading"]);
Schema 静的メソッド
Schema.create_default()-- IPADIC のレイアウトを緩やかに踏襲した組み込みの 13 フィールドスキーマを作成する。内訳は 4 つのシステムフィールド(surface、left_context_id、right_context_id、cost)に続く 9 つの汎用素性フィールド(major_pos、pos_detail_1〜pos_detail_3、conjugation_type、conjugation_form、base_form、reading、pronunciation)。このうち品詞の 4 フィールド名は、実際のlindera-ipadic辞書スキーマ(part_of_speech、part_of_speech_subcategory_1〜_3)とは異なる。残りのフィールドは名前も順序も一致する。実際の IPADIC 辞書のスキーマに合わせたい場合は、読み込み済み辞書のdictionary.metadata.dictionary_schemaを使用すること
Schema メソッド
| メソッド | 戻り値 | 説明 |
|---|---|---|
get_field_index(name) | number | undefined | フィールド名からインデックスを取得 |
field_count() | number | フィールドの総数 |
get_field_name(index) | string | undefined | インデックスからフィールド名を取得(末尾より先は undefined、負や整数でないインデックスは例外) |
get_custom_fields() | string[] | インデックス 3 以降のフィールド(形態素素性) |
get_all_fields() | string[] | すべてのフィールド名 |
get_field_by_name(name) | FieldDefinition | undefined | フィールド定義の完全な情報を取得 |
FieldDefinition
| プロパティ | 型 | 説明 |
|---|---|---|
index | number | フィールドの位置インデックス |
name | string | フィールド名 |
field_type | FieldType | フィールド型の列挙値 |
description | string | undefined | 説明(省略可) |
FieldType
| 値 | 説明 |
|---|---|
FieldType.Surface | 単語の表層形テキスト |
FieldType.LeftContextId | 左文脈 ID |
FieldType.RightContextId | 右文脈 ID |
FieldType.Cost | 単語コスト |
FieldType.Custom | 形態素素性フィールド |
ブラウザでの使用
ES Module インポート
ブラウザ環境では、Lindera の関数を使用する前に WASM モジュールを初期化する必要があります。デフォルトエクスポートの __wbg_init がこの初期化を処理します。
推奨される方法は、辞書を WASM バイナリに埋め込むのではなく、OPFS から読み込むことです:
import __wbg_init, { TokenizerBuilder, loadDictionaryFromBytes } from 'lindera-wasm';
import { downloadDictionary, loadDictionaryFiles, hasDictionary } from 'lindera-wasm/opfs';
async function main() {
// WASM モジュールを初期化する(いずれかの API を使用する前に一度だけ呼び出す必要がある)
await __wbg_init();
// キャッシュされていない場合は辞書をダウンロード
if (!await hasDictionary("ipadic")) {
await downloadDictionary(
"https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip",
"ipadic",
);
}
// OPFS から辞書を読み込み
const files = await loadDictionaryFiles("ipadic");
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictTrie, files.dictValsIdx, files.dictVals,
files.dictWordsIdx, files.dictWords, files.matrixMtx, files.charDef,
files.unk,
);
const builder = new TokenizerBuilder();
builder.setDictionaryInstance(dictionary);
builder.setMode("normal");
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("形態素解析を行います");
tokens.forEach(token => {
console.log(`${token.surface}: ${token.details.join(',')}`);
});
}
main();
埋め込み辞書の使用(上級者向け)
embed-* feature フラグ付きでビルドした場合、OPFS の代わりに埋め込み辞書を使用できます:
[!NOTE] ここでの
lindera-wasm-ipadicは説明用のパッケージ名であり、npm に公開されているものではありません。実際に公開されているのはlindera-wasmのみです。このようなパッケージを自分でビルド・命名する方法は npm パッケージの命名規則 を参照してください。
import __wbg_init, { TokenizerBuilder } from 'lindera-wasm-ipadic';
async function main() {
await __wbg_init();
const builder = new TokenizerBuilder();
builder.setDictionary("embedded://ipadic");
builder.setMode("normal");
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("形態素解析を行います");
tokens.forEach(token => {
console.log(`${token.surface}: ${token.details.join(',')}`);
});
}
main();
HTML の例
OPFS 辞書ロードを使用した lindera-wasm の最小限の HTML ページ:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lindera WASM Demo</title>
</head>
<body>
<textarea id="input" rows="4" cols="50">関西国際空港限定トートバッグ</textarea>
<br>
<button id="tokenize" disabled>Tokenize</button>
<pre id="output">Loading dictionary...</pre>
<script type="module">
import __wbg_init, { TokenizerBuilder, loadDictionaryFromBytes } from './pkg/lindera_wasm.js';
import { downloadDictionary, loadDictionaryFiles, hasDictionary } from './pkg/opfs.js';
let tokenizer;
async function init() {
await __wbg_init();
// キャッシュされていない場合は辞書をダウンロード
if (!await hasDictionary("ipadic")) {
document.getElementById('output').textContent = 'Downloading dictionary...';
await downloadDictionary(
"https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip",
"ipadic",
);
}
// OPFS から辞書を読み込み
const files = await loadDictionaryFiles("ipadic");
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictTrie, files.dictValsIdx, files.dictVals,
files.dictWordsIdx, files.dictWords, files.matrixMtx, files.charDef,
files.unk,
);
const builder = new TokenizerBuilder();
builder.setDictionaryInstance(dictionary);
builder.setMode("normal");
tokenizer = builder.build();
document.getElementById('tokenize').disabled = false;
document.getElementById('output').textContent = 'Ready!';
}
document.getElementById('tokenize').addEventListener('click', () => {
const text = document.getElementById('input').value;
const tokens = tokenizer.tokenize(text);
const output = tokens.map(t =>
`${t.surface}\t${t.details.join(',')}`
).join('\n');
document.getElementById('output').textContent = output;
});
init();
</script>
</body>
</html>
Webpack の設定
Webpack 5 を使用する場合は、asyncWebAssembly experiment を有効にします:
// webpack.config.js
module.exports = {
experiments: {
asyncWebAssembly: true,
},
module: {
rules: [
{
test: /\.wasm$/,
type: "webassembly/async",
},
],
},
};
次に、lindera-wasm パッケージをインポートします。web ターゲットのビルドでは、使用前にデフォルトエクスポートの __wbg_init() を呼び出す必要があります:
import __wbg_init, { TokenizerBuilder, loadDictionaryFromBytes } from 'lindera-wasm';
import { loadDictionaryFiles } from 'lindera-wasm/opfs';
// WASM モジュールを初期化
await __wbg_init();
// OPFS から辞書を読み込み(セットアップは OPFS 辞書ストレージを参照)
const files = await loadDictionaryFiles("ipadic");
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictTrie, files.dictValsIdx, files.dictVals,
files.dictWordsIdx, files.dictWords, files.matrixMtx, files.charDef,
files.unk,
);
const builder = new TokenizerBuilder();
builder.setDictionaryInstance(dictionary);
builder.setMode("normal");
const tokenizer = builder.build();
Vite / Rollup のセットアップ
Vite は web ターゲットの WASM をそのままサポートしています。npm からインストールした lindera-wasm を直接インポートできます:
import __wbg_init, { TokenizerBuilder, loadDictionaryFromBytes } from 'lindera-wasm';
import { loadDictionaryFiles } from 'lindera-wasm/opfs';
await __wbg_init();
// OPFS から辞書を読み込み、上記のように TokenizerBuilder を使用
Vite では optimizeDeps でこのパッケージを除外してください:
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
optimizeDeps: {
exclude: ["lindera-wasm"],
},
});
ローカルでビルドした場合は、生成された pkg/ ディレクトリをプロジェクトに配置し、./pkg/lindera_wasm.js を直接インポートすることもできます。
Chrome 拡張機能に関する注意事項
Manifest V3 を使用する Chrome 拡張機能では、デフォルトで WebAssembly.compile と WebAssembly.instantiate が制限されています。拡張機能で lindera-wasm を使用するには、Content Security Policy に wasm-unsafe-eval を追加する必要があります:
{
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
}
}
wasm-unsafe-eval は WebAssembly の実行のみを許可し、任意の JavaScript eval() は許可しません。
パフォーマンスのヒント
- 初期化は一度だけ:
__wbg_init()はアプリケーション起動時に一度だけ呼び出し、トークナイズリクエストごとには呼び出さないでください。 - トークナイザーの再利用:
Tokenizerインスタンスは一度作成し、複数のtokenize()呼び出しで再利用してください。 - Web Workers: 大量のトークナイズ処理を行う場合は、メインスレッドのブロックを避けるため、Web Worker での Lindera の実行を検討してください。
OPFS 辞書ストレージ
Lindera WASM は、Web ブラウザでの辞書の永続キャッシュのために OPFS(Origin Private File System)ヘルパーユーティリティを提供します。辞書を一度ダウンロードすれば、WASM バイナリに埋め込むことなく、セッションをまたいで再利用できます。
概要
OPFS ヘルパーは、WASM パッケージとともに別の JavaScript モジュール(opfs.js)として配布されます。ブラウザの Origin Private File System を使用して、辞書のダウンロード、保存、読み込み、管理を行う関数を提供します。
辞書は OPFS パス lindera/dictionaries/<name>/ に保存されます。
インポート
import { downloadDictionary, loadDictionaryFiles, removeDictionary,
listDictionaries, hasDictionary } from 'lindera-wasm/opfs';
関数
downloadDictionary(url, name, options?)
辞書の zip アーカイブをダウンロードし、展開して、ファイルを OPFS に保存します。
アーカイブは、必要な 9 つの辞書ファイルを含む zip ファイルである必要があります。サブディレクトリにネストされていても構いません。
- 引数:
url(string) -- 辞書 zip アーカイブの URLname(string) -- 辞書の保存名(例:"ipadic")options(object, 省略可):onProgress(function) -- 進捗コールバックfetchInit(RequestInit, 省略可) --fetch()にそのまま渡す追加オプション(カスタムヘッダー、認証情報、AbortSignalなど)
- 戻り値:
Promise<void>
await downloadDictionary(
"https://example.com/ipadic.zip",
"ipadic",
{
onProgress: (progress) => {
switch (progress.phase) {
case "downloading":
console.log(`Downloading: ${progress.loaded}/${progress.total} bytes`);
break;
case "extracting":
console.log("Extracting archive...");
break;
case "storing":
console.log("Storing in OPFS...");
break;
case "complete":
console.log("Done!");
break;
}
},
},
);
進捗コールバック
onProgress コールバックは以下の形式のオブジェクトを受け取ります:
| プロパティ | 型 | 説明 |
|---|---|---|
phase | string | "downloading"、"extracting"、"storing"、または "complete" |
loaded | number | undefined | ダウンロード済みバイト数("downloading" フェーズのみ) |
total | number | undefined | 合計バイト数(判明している場合、"downloading" フェーズのみ) |
loadDictionaryFiles(name)
OPFS から辞書ファイルを Uint8Array 値のオブジェクトとして読み込みます。
返されたオブジェクトは loadDictionaryFromBytes() にそのまま渡すことができます。
- 引数:
name(string) -- 辞書名(例:"ipadic") - 戻り値:
Promise<DictionaryFiles>
const files = await loadDictionaryFiles("ipadic");
DictionaryFiles
| プロパティ | 型 | ソースファイル |
|---|---|---|
metadata | Uint8Array | metadata.json |
dictTrie | Uint8Array | dict.trie(文字単位のダブル配列トライ) |
dictValsIdx | Uint8Array | dict.valsidx(単語値インデックス) |
dictVals | Uint8Array | dict.vals(単語値データ) |
dictWordsIdx | Uint8Array | dict.wordsidx(単語詳細インデックス) |
dictWords | Uint8Array | dict.words(単語詳細) |
matrixMtx | Uint8Array | matrix.mtx(連接コスト行列) |
charDef | Uint8Array | char_def.bin(文字定義) |
unk | Uint8Array | unk.bin(未知語辞書) |
removeDictionary(name)
OPFS から辞書を削除します。
- 引数:
name(string) -- 削除する辞書名 - 戻り値:
Promise<void>
await removeDictionary("ipadic");
listDictionaries()
OPFS に保存されているすべての辞書を一覧表示します。
- 戻り値:
Promise<string[]>-- 辞書名の配列
const names = await listDictionaries();
console.log(names); // 例: ["ipadic", "unidic"]
hasDictionary(name)
OPFS に辞書が存在するかどうかを確認します。
- 引数:
name(string) -- 確認する辞書名 - 戻り値:
Promise<boolean>
if (await hasDictionary("ipadic")) {
console.log("Dictionary is cached");
}
完全なワークフロー
OPFS ベースの辞書を使用する一般的なワークフロー:
import __wbg_init, { TokenizerBuilder, loadDictionaryFromBytes } from 'lindera-wasm';
import { downloadDictionary, loadDictionaryFiles, hasDictionary } from 'lindera-wasm/opfs';
async function main() {
await __wbg_init();
const DICT_NAME = "ipadic";
const DICT_URL = "https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip";
// キャッシュされていない場合は辞書をダウンロード
if (!await hasDictionary(DICT_NAME)) {
await downloadDictionary(DICT_URL, DICT_NAME, {
onProgress: ({ phase, loaded, total }) => {
if (phase === "downloading" && total) {
console.log(`${(loaded / total * 100).toFixed(1)}%`);
}
},
});
}
// OPFS から辞書を読み込み
const files = await loadDictionaryFiles(DICT_NAME);
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictTrie, files.dictValsIdx, files.dictVals,
files.dictWordsIdx, files.dictWords, files.matrixMtx, files.charDef,
files.unk,
);
// トークナイザーを構築
const builder = new TokenizerBuilder();
builder.setDictionaryInstance(dictionary);
builder.setMode("normal");
const tokenizer = builder.build();
// トークナイズ
const tokens = tokenizer.tokenize("形態素解析を行います");
tokens.forEach(token => {
console.log(`${token.surface}\t${token.details.join(',')}`);
});
}
main();
必要な辞書ファイル
有効な辞書アーカイブには以下の 9 ファイルが含まれている必要があります:
| ファイル | 説明 |
|---|---|
metadata.json | 辞書メタデータ(名前、エンコーディング、スキーマなど) |
dict.trie | 文字単位のダブル配列トライ構造 |
dict.valsidx | 単語値インデックス |
dict.vals | 単語値データ |
dict.wordsidx | 単語詳細インデックス |
dict.words | 単語詳細(形態素素性) |
matrix.mtx | 連接コスト行列 |
char_def.bin | 文字カテゴリ定義 |
unk.bin | 未知語辞書 |
ブラウザ互換性
OPFS は安全なコンテキスト(HTTPS または localhost)が必要で、以下のブラウザでサポートされています:
- Chrome 86+
- Edge 86+
- Firefox 111+
- Safari 15.2+
zip の展開には DecompressionStream API を使用しており、以下が必要です:
- Chrome 80+
- Edge 80+
- Firefox 113+
- Safari 16.4+
Lindera IPADIC
Lindera IPADIC は、IPADIC に基づく日本語辞書クレートです。IPADIC は、日本語の形態素解析で最も広く使われている辞書です。
目次
API リファレンス
Lindera IPADIC
辞書バージョン
このリポジトリには mecab-ipadic が含まれています。
辞書フォーマット
IPADIC の辞書フォーマットおよび品詞タグの詳細については、マニュアルを参照してください。
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 左文脈ID | Left context ID | |
| 2 | 右文脈ID | Right context ID | |
| 3 | コスト | Cost | |
| 4 | 品詞 | Part-of-speech | |
| 5 | 品詞細分類1 | Part-of-speech subcategory 1 | |
| 6 | 品詞細分類2 | Part-of-speech subcategory 2 | |
| 7 | 品詞細分類3 | Part-of-speech subcategory 3 | |
| 8 | 活用型 | Conjugation type | |
| 9 | 活用形 | Conjugation form | |
| 10 | 原形 | Base form | |
| 11 | 読み | Reading | |
| 12 | 発音 | Pronunciation |
ユーザー辞書フォーマット (CSV)
簡易版
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 品詞 | Part-of-speech | |
| 2 | 読み | Reading |
この簡易スキーマに含まれないフィールド (base_form や pronunciation など) には、辞書の default_field_value (IPADIC の場合は *) が設定されます。
詳細版
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 左文脈ID | Left context ID | |
| 2 | 右文脈ID | Right context ID | |
| 3 | コスト | Cost | |
| 4 | 品詞 | Part-of-speech | |
| 5 | 品詞細分類1 | Part-of-speech subcategory 1 | |
| 6 | 品詞細分類2 | Part-of-speech subcategory 2 | |
| 7 | 品詞細分類3 | Part-of-speech subcategory 3 | |
| 8 | 活用型 | Conjugation type | |
| 9 | 活用形 | Conjugation form | |
| 10 | 原形 | Base form | |
| 11 | 読み | Reading | |
| 12 | 発音 | Pronunciation | |
| 13 | - | - | 13 以降は自由に拡張可能です。 |
API リファレンス
API リファレンスは以下の URL から参照できます:
ビルド
このページでは、ソースファイルから IPADIC 辞書をビルドする方法を説明します。
システム辞書のビルド
IPADIC のソースファイルをダウンロードし、辞書をビルドします:
# IPADIC ソースファイルのダウンロードと展開
% curl -L -o /tmp/mecab-ipadic-2.7.0-20250920.tar.gz "https://Lindera.dev/mecab-ipadic-2.7.0-20250920.tar.gz"
% tar zxvf /tmp/mecab-ipadic-2.7.0-20250920.tar.gz -C /tmp
# 辞書のビルド
% lindera build \
--src /tmp/mecab-ipadic-2.7.0-20250920 \
--dest /tmp/lindera-ipadic-2.7.0-20250920 \
--metadata ./lindera-ipadic/metadata.json
ユーザー辞書のビルド
CSV ファイルからユーザー辞書をビルドします:
% lindera build \
--src ./resources/user_dict/ipadic_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-ipadic/metadata.json \
--user
ユーザー辞書フォーマットの詳細については、辞書フォーマットを参照してください。
バイナリへの埋め込み
IPADIC 辞書をバイナリに直接埋め込むには、以下のようにビルドします:
cargo build --features=embed-ipadic
これにより、外部辞書ファイルなしで embedded://ipadic を辞書パスとして使用できるようになります。
使用例
このページでは、IPADIC 辞書を使用したトークナイズの例を示します。
外部 IPADIC でトークナイズ
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict /tmp/lindera-ipadic-2.7.0-20250920
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素 名詞,一般,*,*,*,*,形態素,ケイタイソ,ケイタイソ
解析 名詞,サ変接続,*,*,*,*,解析,カイセキ,カイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
埋め込み IPADIC でトークナイズ
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://ipadic
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素 名詞,一般,*,*,*,*,形態素,ケイタイソ,ケイタイソ
解析 名詞,サ変接続,*,*,*,*,解析,カイセキ,カイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
注意: IPADIC 辞書をバイナリに含めるには、--features=embed-ipadic オプションを付けてビルドする必要があります。
ユーザー辞書を使用したトークナイズ (CSV フォーマット)
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict embedded://ipadic \
--user-dict ./resources/user_dict/ipadic_simple_userdic.csv
東京スカイツリー カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリー,*
の 助詞,連体化,*,*,*,*,の,ノ,ノ
最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリーエキ,*
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
EOS
ユーザー辞書を使用したトークナイズ (バイナリフォーマット)
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict /tmp/lindera-ipadic-2.7.0-20250920 \
--user-dict ./resources/user_dict/ipadic_simple_userdic.bin
東京スカイツリー カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリー,*
の 助詞,連体化,*,*,*,*,の,ノ,ノ
最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリーエキ,*
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
EOS
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 = "日本語の形態素解析を行うことができます。"; let mut tokens = tokenizer.tokenize(text)?; for token in tokens.iter_mut() { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } Ok(()) }
Lindera IPADIC NEologd
Lindera IPADIC NEologd は、新語を含む IPADIC NEologd に基づく日本語辞書クレートです。標準の IPADIC 辞書を拡張し、最新の用語や固有名詞を追加した語彙を提供します。
目次
API リファレンス
Lindera IPADIC NEologd
辞書バージョン
このリポジトリには mecab-ipadic-neologd が含まれています。
辞書フォーマット
IPADIC の辞書フォーマットおよび品詞タグの詳細については、マニュアルを参照してください。
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 左文脈ID | Left context ID | |
| 2 | 右文脈ID | Right context ID | |
| 3 | コスト | Cost | |
| 4 | 品詞 | Part-of-speech | |
| 5 | 品詞細分類1 | Part-of-speech subcategory 1 | |
| 6 | 品詞細分類2 | Part-of-speech subcategory 2 | |
| 7 | 品詞細分類3 | Part-of-speech subcategory 3 | |
| 8 | 活用型 | Conjugation type | |
| 9 | 活用形 | Conjugation form | |
| 10 | 原形 | Base form | |
| 11 | 読み | Reading | |
| 12 | 発音 | Pronunciation |
ユーザー辞書フォーマット (CSV)
簡易版
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 品詞 | Part-of-speech | |
| 2 | 読み | Reading |
詳細版
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 左文脈ID | Left context ID | |
| 2 | 右文脈ID | Right context ID | |
| 3 | コスト | Cost | |
| 4 | 品詞 | Part-of-speech | |
| 5 | 品詞細分類1 | Part-of-speech subcategory 1 | |
| 6 | 品詞細分類2 | Part-of-speech subcategory 2 | |
| 7 | 品詞細分類3 | Part-of-speech subcategory 3 | |
| 8 | 活用型 | Conjugation type | |
| 9 | 活用形 | Conjugation form | |
| 10 | 原形 | Base form | |
| 11 | 読み | Reading | |
| 12 | 発音 | Pronunciation | |
| 13 | - | - | 13 以降は自由に拡張可能です。 |
API リファレンス
API リファレンスは以下の URL から参照できます:
ビルド
このページでは、ソースファイルから IPADIC NEologd 辞書をビルドする方法を説明します。
システム辞書のビルド
IPADIC NEologd のソースファイルをダウンロードし、辞書をビルドします:
% curl -L -o /tmp/mecab-ipadic-neologd-0.0.7-20200820.tar.gz "https://lindera.dev/mecab-ipadic-neologd-0.0.7-20200820.tar.gz"
% tar zxvf /tmp/mecab-ipadic-neologd-0.0.7-20200820.tar.gz -C /tmp
% lindera build \
--src /tmp/mecab-ipadic-neologd-0.0.7-20200820 \
--dest /tmp/lindera-ipadic-neologd-0.0.7-20200820 \
--metadata ./lindera-ipadic-neologd/metadata.json
ユーザー辞書のビルド
CSV ファイルからユーザー辞書をビルドします:
% lindera build \
--src ./resources/user_dict/ipadic_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-ipadic-neologd/metadata.json \
--user
ユーザー辞書フォーマットの詳細については、辞書フォーマットを参照してください。
バイナリへの埋め込み
IPADIC NEologd 辞書をバイナリに直接埋め込むには、以下のようにビルドします:
cargo build --features=embed-ipadic-neologd
これにより、外部辞書ファイルなしで embedded://ipadic-neologd を辞書パスとして使用できるようになります。
使用例
このページでは、IPADIC NEologd 辞書を使用したトークナイズの例を示します。
外部 IPADIC NEologd でトークナイズ
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict /tmp/lindera-ipadic-neologd-0.0.7-20200820
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素解析 名詞,固有名詞,一般,*,*,*,形態素解析,ケイタイソカイセキ,ケイタイソカイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
NEologd では「形態素解析」が単一の複合名詞として扱われますが、標準の IPADIC では「形態素」と「解析」に分割されます。
埋め込み IPADIC NEologd でトークナイズ
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://ipadic-neologd
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素解析 名詞,固有名詞,一般,*,*,*,形態素解析,ケイタイソカイセキ,ケイタイソカイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
注意: IPADIC NEologd 辞書をバイナリに含めるには、--features=embed-ipadic-neologd オプションを付けてビルドする必要があります。
ユーザー辞書を使用したトークナイズ (CSV フォーマット)
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict embedded://ipadic-neologd \
--user-dict ./resources/user_dict/ipadic_simple_userdic.csv
東京スカイツリー カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリー,*
の 助詞,連体化,*,*,*,*,の,ノ,ノ
最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリーエキ,*
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
EOS
ユーザー辞書を使用したトークナイズ (バイナリフォーマット)
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict /tmp/lindera-ipadic-neologd-0.0.7-20200820 \
--user-dict ./resources/user_dict/ipadic_simple_userdic.bin
東京スカイツリー カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリー,*
の 助詞,連体化,*,*,*,*,の,ノ,ノ
最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリーエキ,*
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
EOS
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-neologd")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "日本語の形態素解析を行うことができます。"; let mut tokens = tokenizer.tokenize(text)?; for token in tokens.iter_mut() { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } Ok(()) }
Lindera UniDic
Lindera UniDic は、統一的な単語単位の定義を使用する UniDic に基づく日本語辞書クレートです。UniDic は IPADIC よりも詳細な形態素情報を提供し、1エントリあたり 21 フィールドを持ちます。
目次
API リファレンス
Lindera UniDic
辞書バージョン
このリポジトリには unidic-mecab が含まれています。
辞書フォーマット
unidic-mecab の辞書フォーマットおよび品詞タグの詳細については、マニュアルを参照してください。
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 左文脈ID | Left context ID | |
| 2 | 右文脈ID | Right context ID | |
| 3 | コスト | Cost | |
| 4 | 品詞大分類 | Part-of-speech | |
| 5 | 品詞中分類 | Part-of-speech subcategory 1 | |
| 6 | 品詞小分類 | Part-of-speech subcategory 2 | |
| 7 | 品詞細分類 | Part-of-speech subcategory 3 | |
| 8 | 活用型 | Conjugation type | |
| 9 | 活用形 | Conjugation form | |
| 10 | 語彙素読み | Reading | |
| 11 | 語彙素(語彙素表記 + 語彙素細分類) | Lexeme | |
| 12 | 書字形出現形 | Orthographic surface form | |
| 13 | 発音形出現形 | Phonological surface form | |
| 14 | 書字形基本形 | Orthographic base form | |
| 15 | 発音形基本形 | Phonological base form | |
| 16 | 語種 | Word type | |
| 17 | 語頭変化型 | Initial mutation type | |
| 18 | 語頭変化形 | Initial mutation form | |
| 19 | 語末変化型 | Final mutation type | |
| 20 | 語末変化形 | Final mutation form |
ユーザー辞書フォーマット (CSV)
簡易版
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 品詞大分類 | Part-of-speech | |
| 2 | 語彙素読み | Reading |
詳細版
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 左文脈ID | Left context ID | |
| 2 | 右文脈ID | Right context ID | |
| 3 | コスト | Cost | |
| 4 | 品詞大分類 | Part-of-speech | |
| 5 | 品詞中分類 | Part-of-speech subcategory 1 | |
| 6 | 品詞小分類 | Part-of-speech subcategory 2 | |
| 7 | 品詞細分類 | Part-of-speech subcategory 3 | |
| 8 | 活用型 | Conjugation type | |
| 9 | 活用形 | Conjugation form | |
| 10 | 語彙素読み | Reading | |
| 11 | 語彙素(語彙素表記 + 語彙素細分類) | Lexeme | |
| 12 | 書字形出現形 | Orthographic surface form | |
| 13 | 発音形出現形 | Phonological surface form | |
| 14 | 書字形基本形 | Orthographic base form | |
| 15 | 発音形基本形 | Phonological base form | |
| 16 | 語種 | Word type | |
| 17 | 語頭変化型 | Initial mutation type | |
| 18 | 語頭変化形 | Initial mutation form | |
| 19 | 語末変化型 | Final mutation type | |
| 20 | 語末変化形 | Final mutation form | |
| 21 | - | - | 21 以降は自由に拡張可能です。 |
API リファレンス
API リファレンスは以下の URL から参照できます:
ビルド
このページでは、ソースファイルから UniDic 辞書をビルドする方法を説明します。
システム辞書のビルド
UniDic のソースファイルをダウンロードし、辞書をビルドします:
% curl -L -o /tmp/unidic-mecab-2.1.2.tar.gz "https://Lindera.dev/unidic-mecab-2.1.2.tar.gz"
% tar zxvf /tmp/unidic-mecab-2.1.2.tar.gz -C /tmp
% lindera build \
--src /tmp/unidic-mecab-2.1.2 \
--dest /tmp/lindera-unidic-2.1.2 \
--metadata ./lindera-unidic/metadata.json \
--context-id-freq ./lindera-unidic/context_id_freq.txt
[!TIP]
lindera-unidic/metadata.jsonはconnection_id_mapping: trueを設定しているため、ビルダーは 連接コスト行列の文脈 ID を使用頻度順に付け替え、連接コスト参照時のキャッシュ局所性を改善します。--context-id-freq/-fに同梱のcontext_id_freq.txtヒストグラムを渡すことで、この付け替えに 実際のコーパス頻度データを与えて ID をランク付けできます。このフラグを省略してもビルドは失敗せず、 精度の低いエントリ数ベースのフォールバックに黙って切り替わるだけです。いずれの場合もトークン化の 結果には影響しません -- この付け替えはコストを保つ全単射な再ラベル付けであり、ビルド時の最適化のみに 関わるものであって正確性には影響しません。
ユーザー辞書のビルド
CSV ファイルからユーザー辞書をビルドします:
% lindera build \
--src ./resources/user_dict/unidic_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-unidic/metadata.json \
--user
ユーザー辞書フォーマットの詳細については、辞書フォーマットを参照してください。
バイナリへの埋め込み
UniDic 辞書をバイナリに直接埋め込むには、以下のようにビルドします:
cargo build --features=embed-unidic
これにより、外部辞書ファイルなしで embedded://unidic を辞書パスとして使用できるようになります。
使用例
このページでは、UniDic 辞書を使用したトークナイズの例を示します。
外部 UniDic でトークナイズ
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict /tmp/lindera-unidic-2.1.2
日本 名詞,固有名詞,地名,国,*,*,ニッポン,日本,日本,ニッポン,日本,ニッポン,固,*,*,*,*
語 名詞,普通名詞,一般,*,*,*,ゴ,語,語,ゴ,語,ゴ,漢,*,*,*,*
の 助詞,格助詞,*,*,*,*,ノ,の,の,ノ,の,ノ,和,*,*,*,*
形態 名詞,普通名詞,一般,*,*,*,ケイタイ,形態,形態,ケータイ,形態,ケータイ,漢,*,*,*,*
素 接尾辞,名詞的,一般,*,*,*,ソ,素,素,ソ,素,ソ,漢,*,*,*,*
解析 名詞,普通名詞,サ変可能,*,*,*,カイセキ,解析,解析,カイセキ,解析,カイセキ,漢,*,*,*,*
を 助詞,格助詞,*,*,*,*,ヲ,を,を,オ,を,オ,和,*,*,*,*
行う 動詞,一般,*,*,五段-ワア行,連体形-一般,オコナウ,行う,行う,オコナウ,行う,オコナウ,和,*,*,*,*
こと 名詞,普通名詞,一般,*,*,*,コト,事,こと,コト,こと,コト,和,コ濁,基本形,*,*
が 助詞,格助詞,*,*,*,*,ガ,が,が,ガ,が,ガ,和,*,*,*,*
でき 動詞,非自立可能,*,*,上一段-カ行,連用形-一般,デキル,出来る,でき,デキ,できる,デキル,和,*,*,*,*
ます 助動詞,*,*,*,助動詞-マス,終止形-一般,マス,ます,ます,マス,ます,マス,和,*,*,*,*
。 補助記号,句点,*,*,*,*,,。,。,,。,,記号,*,*,*,*
EOS
UniDic では「日本語」が「日本」と「語」に、「形態素」が「形態」と「素」に分割されます。これは UniDic の統一的な単語単位の定義を反映しています。
埋め込み UniDic でトークナイズ
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://unidic
日本 名詞,固有名詞,地名,国,*,*,ニッポン,日本,日本,ニッポン,日本,ニッポン,固,*,*,*,*
語 名詞,普通名詞,一般,*,*,*,ゴ,語,語,ゴ,語,ゴ,漢,*,*,*,*
の 助詞,格助詞,*,*,*,*,ノ,の,の,ノ,の,ノ,和,*,*,*,*
形態 名詞,普通名詞,一般,*,*,*,ケイタイ,形態,形態,ケータイ,形態,ケータイ,漢,*,*,*,*
素 接尾辞,名詞的,一般,*,*,*,ソ,素,素,ソ,素,ソ,漢,*,*,*,*
解析 名詞,普通名詞,サ変可能,*,*,*,カイセキ,解析,解析,カイセキ,解析,カイセキ,漢,*,*,*,*
を 助詞,格助詞,*,*,*,*,ヲ,を,を,オ,を,オ,和,*,*,*,*
行う 動詞,一般,*,*,五段-ワア行,連体形-一般,オコナウ,行う,行う,オコナウ,行う,オコナウ,和,*,*,*,*
こと 名詞,普通名詞,一般,*,*,*,コト,事,こと,コト,こと,コト,和,コ濁,基本形,*,*
が 助詞,格助詞,*,*,*,*,ガ,が,が,ガ,が,ガ,和,*,*,*,*
でき 動詞,非自立可能,*,*,上一段-カ行,連用形-一般,デキル,出来る,でき,デキ,できる,デキル,和,*,*,*,*
ます 助動詞,*,*,*,助動詞-マス,終止形-一般,マス,ます,ます,マス,ます,マス,和,*,*,*,*
。 補助記号,句点,*,*,*,*,,。,。,,。,,記号,*,*,*,*
EOS
注意: UniDic 辞書をバイナリに含めるには、--features=embed-unidic オプションを付けてビルドする必要があります。
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://unidic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "日本語の形態素解析を行うことができます。"; let mut tokens = tokenizer.tokenize(text)?; for token in tokens.iter_mut() { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } Ok(()) }
Lindera SudachiDict
Lindera SudachiDict は、Sudachi 形態素解析器が使用する、活発にメンテナンスされている辞書 SudachiDict に基づく日本語辞書クレートです。上流で年に数回更新されているため、最近の語彙(令和、スマホ、推し活、...)が語彙に含まれます。各エントリは 19 フィールドを持ち、正規化表記・分割情報・同義語グループ ID といった SudachiDict 固有のカラムを含みます。
辞書のソースファイルは lindera/sudachidict にミラーされています。同梱バージョンは 20260723(small + core + notcore レキシコン)です。
[!NOTE] この辞書は SudachiDict の語彙とラティスの挙動を再現しますが、Sudachi のエンジンプラグイン(カタカナ・数詞の連結、入力正規化、A/B/C 分割モード)は再現しません。詳細は Sudachi との挙動の違いを参照してください。
目次
API リファレンス
Lindera SudachiDict
辞書バージョン
このリポジトリには SudachiDict 20260723(small + core + notcore レキシコン)が含まれています。
辞書フォーマット
辞書フォーマットおよび品詞タグの詳細については、SudachiDict のドキュメントを参照してください。
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 左文脈ID | Left context ID | |
| 2 | 右文脈ID | Right context ID | |
| 3 | コスト | Cost | |
| 4 | 見出し(解析結果表示用) | Display surface | |
| 5 | 品詞大分類 | Part-of-speech | |
| 6 | 品詞中分類 | Part-of-speech subcategory 1 | |
| 7 | 品詞小分類 | Part-of-speech subcategory 2 | |
| 8 | 品詞細分類 | Part-of-speech subcategory 3 | |
| 9 | 活用型 | Conjugation type | |
| 10 | 活用形 | Conjugation form | |
| 11 | 読み | Reading | |
| 12 | 正規化表記 | Normalized form | |
| 13 | 辞書形ID | Dictionary form word ID | |
| 14 | 分割タイプ | Split mode | A/B/C |
| 15 | A単位分割情報 | Split references (A) | |
| 16 | B単位分割情報 | Split references (B) | |
| 17 | 語構成 | Word structure | |
| 18 | 同義語グループID | Synonym group IDs |
[!NOTE] 見出し(解析結果表示用)が品詞カラムより前のインデックス 4 に位置するため、 トークン詳細(details)の先頭は、IPADIC や UniDic のような品詞ではなく見出しに なります。details を位置ベースで読み取るコードはこの点を考慮してください。
token.get("part_of_speech")のようなスキーマを認識するアクセスは品詞を直接 返します。品詞ベースのトークンフィルタ(japanese_stop_tags、japanese_keep_tags、japanese_compound_word、およびtagsを指定したjapanese_number)は辞書スキーマ経由で品詞カラムを解決するため、IPADIC や UniDic と同様にこの辞書でも一致します。詳細は lindera/lindera#997 を参照してください。
ユーザー辞書フォーマット (CSV)
簡易版
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 品詞大分類 | Part-of-speech | |
| 2 | 読み | Reading |
詳細版
詳細版は上記の辞書フォーマット(19 カラム)に従います。
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0-18 | - | Same as the dictionary format | |
| 19 | - | - | 19 以降は自由に拡張可能です。 |
API リファレンス
API リファレンスは以下の URL から参照できます:
ビルド
このページでは、ソースファイルから SudachiDict 辞書をビルドする方法を説明します。
システム辞書のビルド
SudachiDict のソースアーカイブをダウンロードし、辞書をビルドします:
% curl -L -o /tmp/sudachidict-20260723.tar.gz "https://lindera.dev/sudachidict-20260723.tar.gz"
% tar zxvf /tmp/sudachidict-20260723.tar.gz -C /tmp
% lindera build \
--src /tmp/sudachidict-20260723 \
--dest /tmp/lindera-sudachidict \
--metadata ./lindera-sudachidict/metadata.json
アーカイブには raw レキシコン(small + core + notcore)、連接コスト行列、および位置合わせ済みの char.def / unk.def が同梱されているため、手動の前処理は不要です。ビルドされた辞書のサイズは約 570MB です。
[!TIP] 同梱バージョンより新しい上流の SudachiDict リリースからビルドするには、 上流の raw 配布物から直接ビルドする SudachiDict(カスタムビルド)を参照してください。
ユーザー辞書のビルド
CSV ファイルからユーザー辞書をビルドします:
% lindera build \
--src ./resources/user_dict/sudachidict_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-sudachidict/metadata.json \
--user
ユーザー辞書フォーマットの詳細については、辞書フォーマットを参照してください。
バイナリへの埋め込み
SudachiDict 辞書をバイナリに直接埋め込むには、以下のようにビルドします:
cargo build --features=embed-sudachidict
これにより、外部辞書ファイルなしで embedded://sudachidict を辞書パスとして使用できるようになります。
[!NOTE] 埋め込み辞書はバイナリサイズを約 570MB 増加させます。バイナリサイズが重要な場合は、 上記のように辞書をディレクトリにビルドし、パス指定で読み込むことを推奨します。
使用例
このページでは、SudachiDict 辞書を使用したトークナイズの例を示します。
外部 SudachiDict でトークナイズ
% echo "令和五年に始まった。推し活が楽しい。" | lindera tokenize \
--dict /tmp/lindera-sudachidict
令和 令和,名詞,固有名詞,一般,*,*,*,レイワ,令和,*,A,*,*,*,*
五 五,名詞,数詞,*,*,*,*,ゴ,五,*,A,*,*,*,017040
年 年,名詞,普通名詞,助数詞可能,*,*,*,ネン,年,*,A,*,*,*,*
に に,助詞,格助詞,*,*,*,*,ニ,に,*,A,*,*,*,*
始まっ 始まっ,動詞,一般,*,*,五段-ラ行,連用形-促音便,ハジマッ,始まる,380965,A,*,*,*,000378
た た,助動詞,*,*,*,助動詞-タ,終止形-一般,タ,た,83558,A,*,*,*,*
。 。,補助記号,句点,*,*,*,*,。,。,*,A,*,*,*,*
推し活 推し活,名詞,普通名詞,一般,*,*,*,オシカツ,推し活,*,A,*,*,*,*
が が,助詞,格助詞,*,*,*,*,ガ,が,*,A,*,*,*,*
楽しい 楽しい,形容詞,一般,*,*,形容詞,終止形-一般,タノシイ,楽しい,519727,A,*,*,*,*
。 。,補助記号,句点,*,*,*,*,。,。,*,A,*,*,*,*
EOS
令和 が単一の固有名詞になり(IPADIC と UniDic 2.1.2 は 令 と 和 に分割します)、推し活 が語彙に含まれている点に注目してください。詳細(details)の先頭カラムは見出し(解析結果表示用)で、その後に品詞カラムと SudachiDict 固有のカラム(正規化表記、分割タイプ、同義語グループID)が続きます。
埋め込み SudachiDict でトークナイズ
% echo "令和五年に始まった。推し活が楽しい。" | lindera tokenize \
--dict embedded://sudachidict
出力は上記と同じです。
注意: SudachiDict 辞書をバイナリに含めるには、--features=embed-sudachidict オプションを付けてビルドする必要があります。
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://sudachidict")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "令和五年に始まった。推し活が楽しい。"; let mut tokens = tokenizer.tokenize(text)?; for token in tokens.iter_mut() { // Schema-aware access: works regardless of column positions let pos = token.get("part_of_speech").unwrap_or_default().to_string(); let details = token.details().join(","); println!("{}\t{}\t{}", token.surface.as_ref(), pos, details); } Ok(()) }
Lindera ko-dic
Lindera ko-dic は、mecab-ko-dic に基づく韓国語辞書クレートです。
目次
API リファレンス
Lindera ko-dic
辞書バージョン
このリポジトリには mecab-ko-dic が含まれています。
辞書フォーマット
mecab-ko-dic で使用される辞書フォーマットおよび品詞タグの情報は、mecab-ko-dic のリポジトリ README からリンクされているこの Google スプレッドシートに記載されています。
ko-dic は NAIST JDIC よりもフィールド列が 1 つ少なく、全体的に異なる情報セットを持っています(例: 単語の「原形」を提供しません)。
タグは世宗(Sejong)で規定されたものを若干修正したものです。世宗から mecab-ko-dic のタグ名へのマッピングは、上記スプレッドシートの 태그 v2.0 タブに記載されています。
辞書フォーマットの完全な仕様は(韓国語で)スプレッドシートの 사전 형식 v2.0 タブに記載されています。空の値はデフォルトで * になります。
| Index | Name (Korean) | Name (English) | Notes |
|---|---|---|---|
| 0 | 표면 | Surface | |
| 1 | 왼쪽 문맥 ID | Left context ID | |
| 2 | 오른쪽 문맥 ID | Right context ID | |
| 3 | 비용 | Cost | |
| 4 | 품사 태그 | Part-of-speech tag | スプレッドシートの 태그 v2.0 タブを参照 |
| 5 | 의미 부류 | Meaning | (確信するには例が少なすぎます) |
| 6 | 종성 유무 | Presence or absence | T は true、F は false、それ以外は * |
| 7 | 읽기 | Reading | 通常は表層形と一致しますが、外来語(例: 漢字語)では異なる場合があります |
| 8 | 타입 | Type | Inflect(活用)、Compound(複合名詞)、Preanalysis(事前解析済み)のいずれか |
| 9 | 첫번째 품사 | First part-of-speech | 例: 品詞タグが "VV+EM+VX+EP" の場合、VV を返します |
| 10 | 마지막 품사 | Last part-of-speech | 例: 品詞タグが "VV+EM+VX+EP" の場合、EP を返します |
| 11 | 표현 | Expression | 활용, 복합명사, 기분석이 어떻게 구성되는지 알려주는 필드 -- 活用、複合名詞、事前解析済みの語がどのように構成されるかを示すフィールド |
左側空白ペナルティ
mecab-ko(mecab-ko-dic が対象とする MeCab のフォーク)は、語の左側に読み飛ばした空白(前の語の表層に含まれない空白)がある場合、その語の pos-id.def の ID が dicrc に列挙されていれば連接コストを加算します:
left-space-penalty-factor = 100,3000,120,6000,172,3000,183,3000,184,3000,185,3000,200,3000,210,6000,220,3000,221,3000,222,3000,230,3000
Lindera は pos-id.def の ID を保持しませんが、列挙されている ID はすべてエントリの先頭品詞タグ(Inflect 行では「最初の品詞」列)に対応するため、同じルールをタグで記述できます。ko-dic はこのルールを metadata.json(space_penalty)に同梱しているので、Segmenter::new がデフォルトで適用します。オフにするには Segmenter::space_penalty(None)、セグメンター設定の "space_penalty": false、または lindera tokenize --disable-space-penalty を使います。明示的なルールは Segmenter::space_penalty、space_penalty オブジェクト、または --space-penalty-rules で指定します。同梱ルールは Lindera 6.1.0 以降でビルドした ko-dic にしか含まれません。v6.0.0 リリースの ko-dic にはルールがないため、辞書を再ビルドするまでペナルティは黙ってオフのままになりますが、--space-penalty-rules はどの ko-dic でも動作します。
| pos-id.def の ID | 先頭品詞タグ | コスト |
|---|---|---|
100, 200 (Inflect) | EC, EF, EP, ETM, ETN | 3000 |
172, 230 (Inflect) | VCP | 3000 |
183, 184, 185, 220, 221, 222 (Inflect) | XSA, XSN, XSV | 3000 |
120, 210 (Inflect) | JC, JKB, JKC, JKG, JKO, JKQ, JKS, JKV, JX | 6000 |
{
"rules": [
{ "pos": ["EC", "EF", "EP", "ETM", "ETN", "VCP", "XSA", "XSN", "XSV"], "cost": 3000 },
{ "pos": ["JC", "JKB", "JKC", "JKG", "JKO", "JKQ", "JKS", "JKV", "JX"], "cost": 6000 }
]
}
echo "검색 이 잘 된다" | lindera tokenize --dict embedded://ko-dic
空白の後ろの 이 は主格助詞 JKS ではなく(mecab-ko と同様に)感動詞 IC と解析されます。--disable-space-penalty を付けると JKS になります。意味論については Segmenter のドキュメントを参照してください。
空白
mecab-ko はラティスを構築する際に空白を読み飛ばすため、空白の後ろの語は空白の前の語に接続します。そのため SPACE の未知語項目(unk.def: SPACE,1798,3563,996)は学習時にどの語とも接続されず、matrix.def のその行と列はすべて 0 です。Lindera もデフォルトで同じように空白を読み飛ばします。--disable-skip-whitespace(Segmenter::skip_whitespace(false)、"skip_whitespace": false)を指定すると、空白はこの項目としてラティスに残り、空白のたびに文脈が途切れます。その場合、2년 전 대회 の 전 は名詞 NNG ではなく 저/NP + ㄴ/JX、하고 있다 の 있 は補助用言 VX ではなく本動詞と解析されます。
ユーザー辞書フォーマット (CSV)
簡易版
| Index | Name (Korean) | Name (English) | Notes |
|---|---|---|---|
| 0 | 표면 | Surface | |
| 1 | 품사 태그 | part-of-speech tag | スプレッドシートの 태그 v2.0 タブを参照 |
| 2 | 읽기 | reading | 通常は表層形と一致しますが、外来語(例: 漢字語)では異なる場合があります |
詳細版
| Index | Name (Korean) | Name (English) | Notes |
|---|---|---|---|
| 0 | 표면 | Surface | |
| 1 | 왼쪽 문맥 ID | Left context ID | |
| 2 | 오른쪽 문맥 ID | Right context ID | |
| 3 | 비용 | Cost | |
| 4 | 품사 태그 | part-of-speech tag | スプレッドシートの 태그 v2.0 タブを参照 |
| 5 | 의미 부류 | meaning | (確信するには例が少なすぎます) |
| 6 | 종성 유무 | presence or absence | T は true、F は false、それ以外は * |
| 7 | 읽기 | reading | 通常は表層形と一致しますが、外来語(例: 漢字語)では異なる場合があります |
| 8 | 타입 | type | Inflect(活用)、Compound(複合名詞)、Preanalysis(事前解析済み)のいずれか |
| 9 | 첫번째 품사 | first part-of-speech | 例: 品詞タグが "VV+EM+VX+EP" の場合、VV を返します |
| 10 | 마지막 품사 | last part-of-speech | 例: 品詞タグが "VV+EM+VX+EP" の場合、EP を返します |
| 11 | 표현 | expression | 활용, 복합명사, 기분석이 어떻게 구성되는지 알려주는 필드 -- 活用、複合名詞、事前解析済みの語がどのように構成されるかを示すフィールド |
| 12 | - | - | 12 以降は自由に拡張可能です。 |
API リファレンス
API リファレンスは以下の URL から参照できます:
ビルド
システム辞書のビルド
mecab-ko-dic のソースファイルをダウンロード・展開し、辞書をビルドします:
% curl -L -o /tmp/mecab-ko-dic-2.1.1-20180720.tar.gz "https://Lindera.dev/mecab-ko-dic-2.1.1-20180720.tar.gz"
% tar zxvf /tmp/mecab-ko-dic-2.1.1-20180720.tar.gz -C /tmp
% lindera build \
--src /tmp/mecab-ko-dic-2.1.1-20180720 \
--dest /tmp/lindera-ko-dic-2.1.1-20180720 \
--metadata ./lindera-ko-dic/metadata.json \
--context-id-freq ./lindera-ko-dic/context_id_freq.txt
[!TIP]
lindera-ko-dic/metadata.jsonはconnection_id_mapping: trueを設定しているため、ビルダーは 連接コスト行列の文脈 ID を使用頻度順に付け替え、連接コスト参照時のキャッシュ局所性を改善します。--context-id-freq/-fに同梱のcontext_id_freq.txtヒストグラムを渡すことで、この付け替えに 実際のコーパス頻度データを与えて ID をランク付けできます。このフラグを省略してもビルドは失敗せず、 精度の低いエントリ数ベースのフォールバックに黙って切り替わるだけです。いずれの場合もトークン化の 結果には影響しません -- この付け替えはコストを保つ全単射な再ラベル付けであり、ビルド時の最適化のみに 関わるものであって正確性には影響しません。
ユーザー辞書のビルド
% lindera build \
--src ./resources/user_dict/ko-dic_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-ko-dic/metadata.json \
--user
辞書の埋め込み
ko-dic 辞書をバイナリに直接埋め込むには、以下の feature フラグを付けてビルドします:
% cargo build --features=embed-ko-dic
使用例
外部 ko-dic でトークナイズ
% echo "한국어의형태해석을실시할수있습니다." | lindera tokenize \
--dict /tmp/lindera-ko-dic-2.1.1-20180720
한국어 NNG,*,F,한국어,Compound,*,*,한국/NNG/*+어/NNG/*
의 JKG,*,F,의,*,*,*,*
형태 NNG,*,F,형태,*,*,*,*
해석 NNG,행위,T,해석,*,*,*,*
을 JKO,*,T,을,*,*,*,*
실시 NNG,행위,F,실시,*,*,*,*
할 XSV+ETM,*,T,할,Inflect,XSV,ETM,하/XSV/*+ᆯ/ETM/*
수 NNB,*,F,수,*,*,*,*
있 VV,*,T,있,*,*,*,*
습니다 EF,*,F,습니다,*,*,*,*
. SF,*,*,*,*,*,*,*
EOS
埋め込み ko-dic でトークナイズ
% echo "한국어의형태해석을실시할수있습니다." | lindera tokenize \
--dict embedded://ko-dic
한국어 NNG,*,F,한국어,Compound,*,*,한국/NNG/*+어/NNG/*
의 JKG,*,F,의,*,*,*,*
형태 NNG,*,F,형태,*,*,*,*
해석 NNG,행위,T,해석,*,*,*,*
을 JKO,*,T,을,*,*,*,*
실시 NNG,행위,F,실시,*,*,*,*
할 XSV+ETM,*,T,할,Inflect,XSV,ETM,하/XSV/*+ᆯ/ETM/*
수 NNB,*,F,수,*,*,*,*
있 VV,*,T,있,*,*,*,*
습니다 EF,*,F,습니다,*,*,*,*
. SF,*,*,*,*,*,*,*
EOS
注意: ko-dic 辞書をバイナリに含めるには、--features=embed-ko-dic オプションを付けてビルドする必要があります。
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://ko-dic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "한국어의형태해석을실시할수있습니다."; let mut tokens = tokenizer.tokenize(text)?; for token in tokens.iter_mut() { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } Ok(()) }
Lindera CC-CEDICT
Lindera CC-CEDICT は、CC-CEDICT-MeCab に基づく中国語辞書クレートです。
目次
API リファレンス
Lindera CC-CE-DICT
辞書バージョン
このリポジトリには CC-CEDICT-MeCab が含まれています。
辞書フォーマット
| Index | Name (Chinese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表面形式 | Surface | |
| 1 | 左语境ID | Left context ID | |
| 2 | 右语境ID | Right context ID | |
| 3 | 成本 | Cost | |
| 4 | 词类 | Part-of-speech | |
| 5 | 词类1 | Part-of-speech subcategory 1 | |
| 6 | 词类2 | Part-of-speech subcategory 2 | |
| 7 | 词类3 | Part-of-speech subcategory 3 | |
| 8 | 併音 | Pinyin | |
| 9 | 繁体字 | Traditional | |
| 10 | 簡体字 | Simplified | |
| 11 | 定义 | Definition |
ユーザー辞書フォーマット (CSV)
簡易版
| Index | Name (Chinese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表面形式 | Surface | |
| 1 | 词类 | Part-of-speech | |
| 2 | 併音 | Pinyin |
詳細版
| Index | Name (Chinese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表面形式 | Surface | |
| 1 | 左语境ID | Left context ID | |
| 2 | 右语境ID | Right context ID | |
| 3 | 成本 | Cost | |
| 4 | 词类 | Part-of-speech | |
| 5 | 词类1 | Part-of-speech subcategory 1 | |
| 6 | 词类2 | Part-of-speech subcategory 2 | |
| 7 | 词类3 | Part-of-speech subcategory 3 | |
| 8 | 併音 | Pinyin | |
| 9 | 繁体字 | Traditional | |
| 10 | 簡体字 | Simplified | |
| 11 | 定义 | Definition | |
| 12 | - | - | 12 以降は自由に拡張可能です。 |
API リファレンス
API リファレンスは以下の URL から参照できます:
ビルド
システム辞書のビルド
CC-CEDICT-MeCab のソースファイルをダウンロード・展開し、辞書をビルドします:
% curl -L -o /tmp/CC-CEDICT-MeCab-0.1.0-20200409.tar.gz "https://lindera.dev/CC-CEDICT-MeCab-0.1.0-20200409.tar.gz"
% tar zxvf /tmp/CC-CEDICT-MeCab-0.1.0-20200409.tar.gz -C /tmp
% lindera build \
--src /tmp/CC-CEDICT-MeCab-0.1.0-20200409 \
--dest /tmp/lindera-cc-cedict-0.1.0-20200409 \
--metadata ./lindera-cc-cedict/metadata.json
ユーザー辞書のビルド
% lindera build \
--src ./resources/user_dict/cc-cedict_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-cc-cedict/metadata.json \
--user
辞書の埋め込み
CC-CEDICT 辞書をバイナリに直接埋め込むには、以下の feature フラグを付けてビルドします:
% cargo build --features=embed-cc-cedict
使用例
外部 CC-CEDICT でトークナイズ
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict /tmp/lindera-cc-cedict-0.1.0-20200409
可以 *,*,*,*,ke3 yi3,可以,可以,can/may/possible/able to/not bad/pretty good/
进行 *,*,*,*,jin4 xing2,進行,进行,to advance/to conduct/underway/in progress/to do/to carry out/to carry on/to execute/
中文 *,*,*,*,Zhong1 wen2,中文,中文,Chinese language/
形态学 *,*,*,*,xing2 tai4 xue2,形態學,形态学,morphology (in biology or linguistics)/
分析 *,*,*,*,fen1 xi1,分析,分析,to analyze/analysis/CL:個|个[ge4]/
。 *,*,*,*,*,*,*,*
EOS
埋め込み CC-CEDICT でトークナイズ
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict embedded://cc-cedict
可以 *,*,*,*,ke3 yi3,可以,可以,can/may/possible/able to/not bad/pretty good/
进行 *,*,*,*,jin4 xing2,進行,进行,to advance/to conduct/underway/in progress/to do/to carry out/to carry on/to execute/
中文 *,*,*,*,Zhong1 wen2,中文,中文,Chinese language/
形态学 *,*,*,*,xing2 tai4 xue2,形態學,形态学,morphology (in biology or linguistics)/
分析 *,*,*,*,fen1 xi1,分析,分析,to analyze/analysis/CL:個|个[ge4]/
。 *,*,*,*,*,*,*,*
EOS
注意: CC-CEDICT 辞書をバイナリに含めるには、--features=embed-cc-cedict オプションを付けてビルドする必要があります。
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://cc-cedict")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "可以进行中文形态学分析。"; let mut tokens = tokenizer.tokenize(text)?; for token in tokens.iter_mut() { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } Ok(()) }
Lindera Jieba
Lindera Jieba は、mecab-jieba に基づく中国語辞書クレートです。
目次
API リファレンス
Lindera Jieba
辞書バージョン
このリポジトリには mecab-jieba が含まれています。
辞書フォーマット
| Index | Name (Chinese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表面形式 | Surface | |
| 1 | 左语境ID | Left context ID | |
| 2 | 右语境ID | Right context ID | |
| 3 | 成本 | Cost | |
| 4 | 词类 | Part-of-speech | |
| 5 | 字符类型 | Character type | |
| 6 | 併音 | Pinyin | |
| 7 | 繁体字 | Traditional | |
| 8 | 簡体字 | Simplified | |
| 9 | 定义 | Definition | |
| 10 | 字符数 | Character count | |
| 11 | 首字符 | First character | |
| 12 | 末字符 | Last character | |
| 13 | 频率等级 | Frequency band |
ユーザー辞書フォーマット (CSV)
簡易版
| Index | Name (Chinese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表面形式 | Surface | |
| 1 | 词类 | Part-of-speech | |
| 2 | 併音 | Pinyin |
詳細版
| Index | Name (Chinese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表面形式 | Surface | |
| 1 | 左语境ID | Left context ID | |
| 2 | 右语境ID | Right context ID | |
| 3 | 成本 | Cost | |
| 4 | 词类 | Part-of-speech | |
| 5 | 字符类型 | Character type | |
| 6 | 併音 | Pinyin | |
| 7 | 繁体字 | Traditional | |
| 8 | 簡体字 | Simplified | |
| 9 | 定义 | Definition | |
| 10 | 字符数 | Character count | |
| 11 | 首字符 | First character | |
| 12 | 末字符 | Last character | |
| 13 | 频率等级 | Frequency band | |
| 14 | - | - | 14 以降は自由に拡張可能です。 |
API リファレンス
API リファレンスは以下の URL から参照できます:
ビルド
システム辞書のビルド
mecab-jieba のソースファイルをダウンロード・展開し、辞書をビルドします:
% curl -L -o /tmp/mecab-jieba-0.1.1.tar.gz "https://lindera.dev/mecab-jieba-0.1.1.tar.gz"
% tar zxvf /tmp/mecab-jieba-0.1.1.tar.gz -C /tmp
% lindera build \
--src /tmp/mecab-jieba-0.1.1/dict-src \
--dest /tmp/lindera-jieba-0.1.1 \
--metadata ./lindera-jieba/metadata.json
ユーザー辞書のビルド
% lindera build \
--src ./resources/user_dict/jieba_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-jieba/metadata.json \
--user
辞書の埋め込み
Jieba 辞書をバイナリに直接埋め込むには、以下の feature フラグを付けてビルドします:
% cargo build --features=embed-jieba
使用例
外部 Jieba でトークナイズ
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict /tmp/lindera-jieba-0.1.1
可以 c,CHINESE,ke3 yi3,可以,可以,can/may/possible/able to/not bad/pretty good,2,可,以,high
进行 v,CHINESE,jin4 xing2,進行,进行,(of a process etc) to proceed; to be in progress; to be underway/(of people) to carry out; to conduct (an investigation or discussion etc)/(of an army etc) to be on the march; to advance,2,进,行,high
中文 nz,CHINESE,Zhong1 wen2,中文,中文,Chinese language,2,中,文,high
形态 n,CHINESE,xing2 tai4,形態,形态,shape/form/pattern/morphology,2,形,态,high
学 n,CHINESE,xue2,學,学,to learn/to study/to imitate/science/-ology,1,学,学,high
分析 vn,CHINESE,fen1 xi1,分析,分析,to analyze/analysis/CL:個|个[ge4],2,分,析,high
。 w,*,*,*,*,*,*,*,*,*
EOS
埋め込み Jieba でトークナイズ
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict embedded://jieba
可以 c,CHINESE,ke3 yi3,可以,可以,can/may/possible/able to/not bad/pretty good,2,可,以,high
进行 v,CHINESE,jin4 xing2,進行,进行,(of a process etc) to proceed; to be in progress; to be underway/(of people) to carry out; to conduct (an investigation or discussion etc)/(of an army etc) to be on the march; to advance,2,进,行,high
中文 nz,CHINESE,Zhong1 wen2,中文,中文,Chinese language,2,中,文,high
形态 n,CHINESE,xing2 tai4,形態,形态,shape/form/pattern/morphology,2,形,态,high
学 n,CHINESE,xue2,學,学,to learn/to study/to imitate/science/-ology,1,学,学,high
分析 vn,CHINESE,fen1 xi1,分析,分析,to analyze/analysis/CL:個|个[ge4],2,分,析,high
。 w,*,*,*,*,*,*,*,*,*
EOS
注意: Jieba 辞書をバイナリに含めるには、--features=embed-jieba オプションを付けてビルドする必要があります。
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://jieba")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "可以进行中文形态学分析。"; let mut tokens = tokenizer.tokenize(text)?; for token in tokens.iter_mut() { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } Ok(()) }
SudachiDict(カスタムビルド)
このページでは、SudachiDict — Sudachi が使用する、活発にメンテナンスされている日本語辞書 — を、上流の raw 配布物から、汎用辞書ビルダーとメタデータファイルのみを使って Lindera 辞書として 直接ビルドする方法を説明します。エンジン側の変更は不要です。
[!TIP] SudachiDict は公式辞書クレート
lindera-sudachidictとしても利用できます。通常の用途では クレートの利用を推奨します(--features embed-sudachidict、lindera download sudachidict、 またはビルド済みリリースアーカイブ)。同梱バージョンより新しい上流リリースを取り込みたい 場合や、カスタムバリアント(例: small + core のみ)をビルドしたい場合に、このページを 利用してください。
関連 Issue: lindera/lindera#487
[!NOTE] このレシピで生成される辞書は語彙とラティスの挙動が Sudachi と一致しますが、 Sudachi のエンジンプラグインは再現しません。利用する前に、下記の Sudachi との挙動の違い を確認してください。
なぜ SudachiDict か
従来の MeCab フォーマットの辞書(IPADIC、UniDic 2.1.2)は、語彙の更新が
何年も前に止まっています。SudachiDict は年に数回更新されており、MeCab 互換の raw CSV
フォーマットで配布されているため、汎用ビルダーでそのままコンパイルできます。
公式の lindera-sudachidict クレートは 20260723 リリースを同梱しています。この
レシピを使って上流の raw 配布物からビルドすれば、より新しいリリースを自分で
取り込むことができます。20260723 リリースでは:
令和が単一の固有名詞になります(IPADIC と UniDic 2.1.2 は令|和に分割します)スマホ、テレワーク、推し活、コロナ禍が語彙に含まれます- 1905 年の 321K 文字の小説では、未知語トークンの割合が 2.00%(IPADIC)から 0.51% に下がります
ソースファイル
SudachiDict の raw 辞書ファイル(最新の日付は 一覧 を確認してください):
% mkdir -p /tmp/sudachidict-src
% cd /tmp/sudachidict-src
% for f in small_lex core_lex notcore_lex; do
curl -LO "http://sudachi.s3-website-ap-northeast-1.amazonaws.com/sudachidict-raw/20260723/${f}.zip"
unzip -o "${f}.zip" && rm "${f}.zip"
done
連接コスト行列。SudachiDict 自身のビルドがダウンロードするファイルで、UniDic 2.1.2 の 行列と同一です(SudachiDict のエントリは UniDic の文脈 ID を使用しています):
% curl -LO "https://d2ej7fkh96fzlu.cloudfront.net/sudachidict-raw/matrix.def.zip"
% unzip -o matrix.def.zip && rm matrix.def.zip
文字種定義と未知語定義は UniDic 2.1.2 のソース(lindera-unidic がビルドに使う
アーカイブと同じもの)から取得します:
% curl -L -o /tmp/unidic-mecab-2.1.2.tar.gz "https://Lindera.dev/unidic-mecab-2.1.2.tar.gz"
% tar zxf /tmp/unidic-mecab-2.1.2.tar.gz -C /tmp
% cp /tmp/unidic-mecab-2.1.2/char.def /tmp/unidic-mecab-2.1.2/unk.def .
unk.def を SudachiDict のカラムレイアウトに揃える
SudachiDict の辞書行は 19 カラムで、カラム 4(0 始まり)は表示用の表層形(display
surface)です。そのため形態素の詳細情報はカラム 5 から始まります。一方 UniDic の
unk.def は品詞がカラム 4 にあります。プレースホルダの表示カラムを挿入して、
未知語の詳細情報が辞書語と同じインデックスに来るようにします:
% awk -F, 'BEGIN{OFS=","} {out=$1 OFS $2 OFS $3 OFS $4 OFS "*"; for(i=5;i<=NF;i++) out=out OFS $i; print out}' unk.def > unk.def.tmp && mv unk.def.tmp unk.def
メタデータ
sudachidict-metadata.json として保存します。先頭 4 フィールドはビルダーの予約
カラムで、残りは SudachiDict のカラムを順に列挙したものです:
{
"name": "sudachidict",
"encoding": "UTF-8",
"default_word_cost": -10000,
"default_left_context_id": 0,
"default_right_context_id": 0,
"default_field_value": "*",
"flexible_csv": true,
"skip_invalid_cost_or_id": true,
"dictionary_schema": {
"fields": [
"surface",
"left_context_id",
"right_context_id",
"cost",
"display_surface",
"part_of_speech",
"part_of_speech_subcategory_1",
"part_of_speech_subcategory_2",
"part_of_speech_subcategory_3",
"conjugation_type",
"conjugation_form",
"reading",
"normalized_form",
"dictionary_form_id",
"split_mode",
"split_a",
"split_b",
"word_structure",
"synonym_group_ids"
]
},
"user_dictionary_schema": { "fields": ["surface", "part_of_speech", "reading"] },
"skip_whitespace": false
}
skip_whitespace: false は、Sudachi と同様に、また SudachiDict のコストが前提と
しているとおりに、空白をラティスに残します。指定しないと Lindera は MeCab と
同様に空白を読み飛ばします(Segmenter を参照)。
ビルドと確認
% lindera build \
--src /tmp/sudachidict-src \
--dest /tmp/lindera-sudachidict \
--metadata ./sudachidict-metadata.json
small + core + notcore(2026-07-23)のビルドは Lindera v6 で約 10 秒、生成される辞書は 約 570MB です。なお、Lindera のリリース間でオンディスク辞書フォーマットのバージョンが 変わった場合、ビルド済み辞書は再ビルドが必要です (v5 から v6 への移行 を参照)。
% echo "令和五年に始まった。推し活が楽しい。" | lindera tokenize --dict /tmp/lindera-sudachidict
令和 令和,名詞,固有名詞,一般,*,*,*,レイワ,令和,*,A,*,*,*,*
五 五,名詞,数詞,*,*,*,*,ゴ,五,*,A,*,*,*,017040
年 年,名詞,普通名詞,助数詞可能,*,*,*,ネン,年,*,A,*,*,*,*
...
推し活 推し活,名詞,普通名詞,一般,*,*,*,オシカツ,推し活,*,A,*,*,*,*
詳細情報には SudachiDict の追加カラム(正規化形、分割参照、同義語グループ ID)が 含まれるため、後段の処理から利用できます。
[!NOTE]
display_surfaceが品詞カラムの前にあるため、details[0]は IPADIC や UniDic の ような品詞ではなく表示用表層形になります。details を位置ベースで読むコードはこの点を 考慮してください。token.get("part_of_speech")のようなスキーマ経由のアクセスは 品詞を直接返します。品詞ベースのトークンフィルタ(japanese_stop_tags、japanese_keep_tags、japanese_compound_word、およびtagsを指定したjapanese_number)は辞書スキーマ経由で品詞カラムを解決するため、IPADIC や UniDic と同様にこの辞書でも一致します。
Sudachi との挙動の違い
ラティス層は等価であることを検証済みです: 行列ファイルは SudachiDict の公式ビルドが 使用するものと byte 単位で同一(sha256)であり、Sudachi のパス書き換え・入力正規化 プラグインを無効にすると、Sudachi エンジンはこの辞書を使う Lindera と同じ最小コスト パスを生成します。残る違いは辞書データではなく Sudachi のエンジンプラグインです:
| Sudachi の機能 | 効果 | Lindera での状況 |
|---|---|---|
JoinKatakanaOovPlugin | カタカナ連続を結合するパス書き換え(例: よりコストの低い サブ+スク のパスではなく サブスク) | 未対応 |
JoinNumericPlugin | 数値列の結合 | 未対応 |
DefaultInputTextPlugin | 検索前の NFKC + 小文字化による正規化(例: 半角の AI が正規化済みエントリにヒット) | Lindera の character filter(unicode_normalize)で部分的に代替可能 |
| A/B/C 分割モード | split_a / split_b 参照による辞書エントリの分割 | 未適用。エントリは分割せずそのまま使用されます。分割参照は詳細情報に保持されるため、後処理で適用できます |
ライセンス
SudachiDict は Apache-2.0 です。matrix.def、char.def、unk.def は UniDic の一部です
(BSD/GPL/LGPL のトリプルライセンス —
SudachiDict の LEGAL notice
を参照)。このレシピでビルドした辞書は両方を含みます。再配布の前にライセンスを
確認してください。
v3 から v4 への移行
Lindera v4.0.0 は、v3 系で意図的に先送りしていた破壊的変更をまとめて取り込んだメジャー リリースです。個々の変更は小さいものですが、安心してアップグレードできるよう、すべての変更を 本ガイドに列挙します。
破壊的変更は、v3.0.7 と v4 の公開サーフェスを cargo public-api で機械的に差分比較して検証
しています。
概要
| 変更 | 対象 | 対応 |
|---|---|---|
デフォルトスキーマのフィールド名が pos_detail_* に統一 | Python, Node.js, Ruby, PHP | middle_pos / small_pos / fine_pos を pos_detail_1 / pos_detail_2 / pos_detail_3 に変更 |
Token.details が常にリスト | Python, Node.js, Ruby | null / None / nil の処理を削除 |
バインディングの Segmenter を削除 | Python, WASM | 代わりに tokenizer を使用 |
LINDERA_CACHE 環境変数を削除 | Rust ビルド, CLI | LINDERA_DICTIONARIES_PATH を使用 |
| ユーザー辞書のバイナリ形式が変更 | すべて(ビルド済み .bin) | ユーザー辞書を CSV から再ビルド |
lindera-dictionary の viterbi 内部をカプセル化 | Rust クレート利用者 | 新しいアクセサを使用 |
最上位の lindera クレートの公開 Rust API は v3.0.7 と v4 で変わりません。
デフォルト辞書スキーマのフィールド名
Schema.create_default()(およびデフォルト辞書スキーマ)は、3 つの品詞詳細フィールドを
middle_pos / small_pos / fine_pos ではなく pos_detail_1 / pos_detail_2 /
pos_detail_3(インデックス 5, 6, 7)と命名するようになりました。これにより、すべての
バインディングが、すでに pos_detail_* を使用していたコアの
lindera::dictionary::Schema::default() と一致します。
対象は Python・Node.js・Ruby・PHP バインディングです。WASM バインディングはすでに
pos_detail_* を使用していたため変更ありません。
Python の例:
schema = Schema.create_default()
# v3: schema.fields[5] == "middle_pos"
# v4: schema.fields[5] == "pos_detail_1"
# v3
index = schema.get_field_index("middle_pos")
# v4
index = schema.get_field_index("pos_detail_1")
これらのフィールド名を文字列で参照している箇所(ルックアップ、カスタムスキーマ、シリアライズ
された設定など)があれば、pos_detail_* 形式に更新してください。
Token.details は常にリスト
Token.details は常に文字列のリストになり、null / None / nil を返さなくなりました。
詳細を持たないトークンは空リストで表現されます。以前は Python・Node.js・Ruby バインディングが
(実際には常に値が入っているにもかかわらず)nullable 型でラップしていました。PHP と WASM
バインディングはもともと非 nullable でした。
Python では型が list[str] | None から list[str] に変わります:
# v3 — 型の都合で null チェックが必要だった
if token.details is not None:
pos = token.details[0]
# v4 — details は常にリスト
pos = token.details[0]
Node.js では Array<string> | null から Array<string> に、Ruby では Array | nil から
Array に変わります。null / nil チェックを削除してください。
バインディングの Segmenter を削除
使われていなかった Segmenter ラッパーをバインディングから削除しました。コンストラクタが無く
使用できないもので、形態素分割は常に tokenizer から利用できます。
- Python:
lindera.segmenterサブモジュールとlindera.segmenter.Segmenterが無くなりました。 - WASM:
Segmenterクラスのエクスポートが無くなりました。
代わりに tokenizer でトークン化してください:
from lindera import Tokenizer, TokenizerBuilder
tokenizer = TokenizerBuilder().build()
tokens = tokenizer.tokenize("関西国際空港")
LINDERA_CACHE 環境変数を削除
非推奨だったビルド時環境変数 LINDERA_CACHE を削除しました。数リリース前から正式に
サポートされている LINDERA_DICTIONARIES_PATH を使用してください:
# v3(非推奨)
export LINDERA_CACHE=/path/to/dicts
# v4
export LINDERA_DICTIONARIES_PATH=/path/to/dicts
ユーザー辞書のバイナリ形式が変更
ユーザー辞書は、システム辞書と同じ 8-bit のバリアント数エンコーディングを使うようになりました
(1 表層あたり最大 255 バリアント、以前は 31)。そのため、v3 でビルドしたユーザー辞書の .bin
ファイルは v4 では誤ってデコードされます。形式バージョンのガードが無いため、失敗はサイレントで
す(トークンは生成されますが、誤った詳細になります)。
v4 でユーザー辞書を CSV から再ビルドしてください:
lindera build --user \
--src user_dict.csv \
--dest ./build \
--metadata lindera-ipadic/metadata.json
ユーザー辞書をビルド済み .bin ではなく .csv から読み込んでいる場合は、読み込み時に再ビルド
されるため対応は不要です。
Rust ライブラリ: lindera-dictionary の viterbi 内部
これは lindera-dictionary クレートを直接利用している場合にのみ影響します。lindera クレートの
API は変わりません。
内部の viterbi 構造体は公開フィールドを持たなくなりました。代わりにアクセサを使用してください:
#![allow(unused)] fn main() { // v3 — フィールドへ直接アクセス let id = word_id.id; let cost = word_entry.word_cost; // v4 — アクセサ let id = word_id.id(); let cost = word_entry.word_cost(); }
lindera_dictionary::viterbi のその他の変更:
EdgeTypeを削除しました。WordEntryにnew()/word_cost()/word_id()を、WordIdにid()を追加しました。WordEntry::serialize/WordEntry::deserialize/WordEntry::SERIALIZED_LENを非公開に しました。util::read_aligned_fileとembedded_dictionary!マクロを追加しました。
この一覧は、lindera-dictionary クレートの v3.0.7 と v4 リリース間で機械生成された
完全な cargo public-api 差分から作成しています。
アップグレードチェックリスト
middle_pos/small_pos/fine_posをpos_detail_1/pos_detail_2/pos_detail_3に置き換える(Python, Node.js, Ruby, PHP)。Token.detailsのnull/None/nilチェックを削除する(Python, Node.js, Ruby)。- バインディングの
Segmenterの使用を tokenizer に置き換える(Python, WASM)。 LINDERA_CACHEをLINDERA_DICTIONARIES_PATHに置き換える。- ビルド済みユーザー辞書
.binを CSV から再ビルドする。 lindera-dictionaryの viterbi フィールド直接アクセスを新しいアクセサに切り替える(Rust)。
v4 から v5 への移行
Lindera v5.0.0 では、ワークスペースをリーンなコアを中心に再構成しました。
lindera クレートのデフォルトビルドは純粋な形態素分割器(セグメンター)となり、
辞書学習パイプラインは独立したクレートに移動しました。このガイドでは、
すべての破壊的変更とその対処方法を説明します。
概要
| 変更 | 影響範囲 | 対処方法 |
|---|---|---|
分析チェーンが新クレート lindera-analysis へ移動 | Tokenizer・character filter・token filter を使う Rust ユーザー | lindera-analysis に依存し import パスを更新 |
lindera-dictionary の train feature 廃止 | lindera-dictionary --features train を直接使うユーザー | lindera-trainer(または lindera facade の train feature)に依存 |
| ビルドキャッシュ変数の改名 | LINDERA_DICTIONARIES_PATH を設定しているユーザー | LINDERA_BUILD_DICTIONARY_CACHE_DIR に改名(旧名は v7.0.0 で削除済み) |
言語バインディング(Python・Node.js・Ruby・PHP・WASM)と CLI は影響を 受けません。必要な feature はそれぞれが有効化しており、API と出力も 変わりません。トークナイズ結果も不変です — 同じ入力と辞書に対して、 v5 は v4 とバイト単位で同一のトークンを出力します。
分析チェーンは lindera-analysis クレートへ
v5.0 では lindera クレートは純粋な形態素分割器になりました。
character_filter・token_filter・tokenizer モジュールは新クレート
lindera-analysis に移動しています
(Lucene のトークナイザーコアとアナライザーモジュールの分離と同じ構成です)。
Tokenizer やフィルタを使用している場合は、新クレートに依存し import パスを
更新してください。API 自体は変更ありません:
# v4
[dependencies]
lindera = "4.0"
# v5
[dependencies]
lindera = "5.0"
lindera-analysis = "5.0"
#![allow(unused)] fn main() { // v4 use lindera::tokenizer::Tokenizer; use lindera::token_filter::japanese_stop_tags::JapaneseStopTagsTokenFilter; // v5 use lindera_analysis::tokenizer::Tokenizer; use lindera_analysis::token_filter::japanese_stop_tags::JapaneseStopTagsTokenFilter; }
[!NOTE] v7.0.0 以降、
linderaクレートはlindera-analysisをlindera::analysisとして 再エクスポートするため、上記の別依存は不要になり、同じアイテムをlindera::analysis::…から参照できます。 v6 から v7 への移行 を参照してください。
テキストの分割だけを行う場合、コードの変更は不要です。むしろ依存ツリーが 軽量化されます(kanaria・unicode-normalization・unicode-segmentation・ unicode-blocks・serde_yaml_ng がビルドされなくなります):
#![allow(unused)] fn main() { use std::borrow::Cow; use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; let dictionary = load_dictionary("/path/to/ipadic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokens = segmenter.segment(Cow::Borrowed("関西国際空港限定トートバッグ"))?; }
学習機能は lindera-trainer クレートへ
CRF 学習パイプライン(TrainerConfig・Trainer・Corpus・Model・
SerializableModel)は、lindera-dictionary の train feature 配下の
trainer モジュールから、新しい lindera-trainer クレートに移動しました。
これにより lindera-dictionary は lindera-crf と regex に依存しなく
なりました。
lindera facade 経由の利用は変更不要です — train feature が
lindera-trainer を取り込み、同じパスで再エクスポートします:
#![allow(unused)] fn main() { // v4 でも v5 でも動作します(train feature 有効時): use lindera::dictionary::trainer::{Corpus, Trainer, TrainerConfig}; }
lindera-dictionary --features train を直接使っていた場合のみ、
切り替えが必要です:
# v4
[dependencies]
lindera-dictionary = { version = "4.0", features = ["train"] }
# v5
[dependencies]
lindera-dictionary = "5.0"
lindera-trainer = "5.0"
#![allow(unused)] fn main() { // v4 use lindera_dictionary::trainer::{Corpus, Trainer, TrainerConfig}; // v5 use lindera_trainer::{Corpus, Trainer, TrainerConfig}; }
CLI の lindera train → lindera export → lindera build ワークフローに
変更はありません。
ビルドキャッシュ環境変数の改名
ビルド時の辞書キャッシュ変数 LINDERA_DICTIONARIES_PATH は
LINDERA_BUILD_DICTIONARY_CACHE_DIR に改名されました。この変数は辞書クレートの
build script だけがビルド時に読み取るもので、ダウンロードした辞書アーカイブと
ビルド済みバイナリ辞書を保持する自動管理のキャッシュを指定します。新しい名前は
その契約(ビルド時専用・辞書・キャッシュ)を明示します。
旧名は v6.x の間は非推奨のフォールバックとして動作し(両方設定時は新名が優先)、 v7.0.0 で削除されました。
# v4
export LINDERA_DICTIONARIES_PATH=/path/to/cache
# v5
export LINDERA_BUILD_DICTIONARY_CACHE_DIR=/path/to/cache
v5 から v6 への移行
Lindera v6.0.0 では、ビルド済みユーザー辞書の再ビルドが必要になり、
トークナイズ結果が 2 点(Decompose モードの精度修正と、新しいデフォルト
有効な未知語機能)で変わり、JavaScript バインディングのトークン型が変わり、
lindera-dictionary の一部 Rust API が改名されます。ほとんどのユーザーは
辞書の再ビルドだけで済みますが、lindera_dictionary::viterbi/mode の型を
直接使っている場合、JavaScript を使っている場合、未知語(辞書外の文字列)に
対する厳密な旧出力に依存している場合は、追加で確認すべき点があります。
v5.2 以前からアップグレードする場合は、後述のシステム辞書フォーマット 変更も対象になります。この変更は v5.3.0 で既にリリース済みのため、v5.3.0 からのアップグレードではシステム辞書の再ビルドは不要です。ただしユーザー 辞書の再ビルドは、これとは別の理由で必要です。
概要
| 変更 | 影響範囲 | 対処方法 |
|---|---|---|
ビルド済みユーザー辞書 .bin が読み込めなくなった | .bin からユーザー辞書を読み込むすべてのユーザー | CSV から lindera build --user で再ビルド |
学習済み model.dat が読み込めなくなった | 以前のビルドで生成した model.dat を再利用しているユーザー | lindera train を再実行 |
| Decompose モードの長さペナルティが非 3 バイト文字に対して正確になった | 1・2・4 バイトの UTF-8 文字を含むテキストに対する Mode::Decompose の出力 | 対応不要(正確性修正)。出力を厳密に固定している場合は Decompose 出力を再確認 |
| 未知語の候補ラダー(length ladder)がデフォルトで有効に | 辞書外テキストに対する Normal・Decompose 両モードの出力 | v5 と同一の出力が必要な場合は unknown_word_ladder(false) / --disable-unknown-word-ladder を設定 |
max_grouping_len オプションの新設(--max-grouping-len、config キー、builder/worker setter) | MeCab の max-grouping-size 相当の上限を使いたいユーザー | 対応不要(デフォルトは従来どおり無制限) |
JS バインディングがプレーンオブジェクトを返すようになり Token クラスが廃止 | Node.js・WASM ユーザー | token.getDetail(i) を token.details[i] に置換。WASM ユーザーはフィールド名の camelCase 化にも対応 |
npm・PyPI のパッケージ名が変更(lindera-nodejs → lindera、lindera-python → lindera、WASM は lindera-wasm に統合) | npm・PyPI からバインディングをインストールしているユーザー | 依存関係と import のパッケージ名を更新(下記参照) |
lindera_dictionary::viterbi/mode の一部項目が改名・削除、Lattice::set_text/set_text_nbest に引数 2 つ追加 | Lattice/Edge/Penalty/Mode を直接利用するコード(lindera クレートの Segmenter/Tokenizer/Worker API 利用者は対象外) | 下記の表に従い呼び出し箇所を更新 |
ビルド済み辞書フォーマットがバージョン 2(dict.da → dict.trie + dict.valsidx)— v5.3.0 でリリース済み | v5.2 以前で作成した自前ビルドのシステム辞書 | lindera build で再ビルド、または lindera download で再取得 |
loadDictionaryFromBytes() が 9 引数 — v5.3.0 でリリース済み | v5.2 以前からアップグレードする、バイトデータから辞書を読み込む WASM ユーザー | dictDa の代わりに dictTrie と dictValsIdx を渡す |
OPFS の DictionaryFiles が dictDa を dictTrie + dictValsIdx に置き換え — v5.3.0 でリリース済み | v5.2 以前からアップグレードする、opfs ヘルパーを使う WASM ユーザー | OPFS にキャッシュ済みの辞書を再ダウンロード |
npm・PyPI・crates.io のパッケージ名変更
v6.0.0 から、公開パッケージ名がサフィックス付きの名前から素の名前 (bare name)に統一されます:
| レジストリ | 旧パッケージ名 | 新パッケージ名 |
|---|---|---|
| npm(Node.js 本体) | lindera-nodejs | lindera |
| npm(プラットフォーム別) | lindera-nodejs-<platform>(例: lindera-nodejs-darwin-arm64) | lindera-<platform>(例: lindera-darwin-arm64) |
| npm(WASM) | lindera-wasm-web / lindera-wasm-bundler | lindera-wasm(単一パッケージ) |
| PyPI | lindera-python | lindera |
RubyGems の lindera と、crates.io のコアクレート(lindera・
lindera-dictionary など)は変更ありません。
Node.js(npm)
package.json の依存関係と require/import のパッケージ名を更新してください:
// v5
const { TokenizerBuilder } = require("lindera-nodejs");
// v6
const { TokenizerBuilder } = require("lindera");
プラットフォーム別パッケージ(optionalDependencies 経由で自動的に
解決されます)も lindera-nodejs-<platform> から lindera-<platform> に
変わりますが、通常は明示的に依存指定していないため対応は不要です。
旧 lindera-nodejs 系パッケージは npm 上で deprecated としてマークされます。
公開済みの旧バージョンはそのまま残ります。
Python(PyPI)
インストールコマンドと依存関係を更新してください:
# v5
pip install lindera-python
# v6
pip install lindera
Python の import 名は変わりません — 以前から import lindera であり、
コードの変更は不要です。変わるのは PyPI 上の配布名だけです。
移行を助けるため、lindera-python の最終リリースとして、新しい lindera
パッケージに依存するだけの移行スタブ(transition stub)が PyPI に公開されます。
pip install lindera-python は当面動作しますが、依存関係は lindera に
切り替えてください。
WASM(npm)
lindera-wasm-web と lindera-wasm-bundler の 2 パッケージは、
wasm-pack --target web でビルドされた単一の lindera-wasm パッケージに
統合されます。
lindera-wasm-web を使っていた場合は、パッケージ名の置き換えのみです:
// v5
import __wbg_init, { TokenizerBuilder } from "lindera-wasm-web";
await __wbg_init();
// v6
import init, { TokenizerBuilder } from "lindera-wasm";
await init();
lindera-wasm-bundler を使っていた場合も、同じ lindera-wasm パッケージを
インストールします。ただし web ターゲットのビルドでは初期化が自動では
行われないため、使用前にデフォルトエクスポートの非同期初期化関数を必ず
呼び出してください:
// v5(bundler ターゲット: 初期化はバンドラーが処理)
import { TokenizerBuilder } from "lindera-wasm-bundler";
// v6(web ターゲット: 明示的な初期化が必要)
import init, { TokenizerBuilder } from "lindera-wasm";
await init();
モダンなバンドラー(Vite、Webpack 5 の asyncWebAssembly など)は
web ターゲットのビルドをそのまま扱えます。設定例は
ブラウザでの使用 を参照してください。
crates.io
バインディングクレート(lindera-python・lindera-nodejs・lindera-ruby・
lindera-wasm)は crates.io への公開を終了します。公開済みの 5.3.0 までの
バージョンはそのまま残ります。これらは各言語のレジストリ(PyPI・npm・
RubyGems)経由で利用するものであり、Rust クレートとして依存する用途は
想定されていません。コアクレートは引き続き crates.io に公開されます。
[!NOTE] v7.0.0 以降、
linderaクレートはlindera-segmenterとlindera-analysisを 束ねるファサードになり、lindera-binding-coreはlindera-bindingとして 公開されています。v6 から v7 への移行 を参照してください。
ビルド済みユーザー辞書の再ビルドが必要
Lindera v6 では daachorse を 4.x から 5.0 に更新しており、ユーザー辞書が
内部に持つ Aho-Corasick オートマトンのシリアライズ形式が変わりました。
そのため、v5 系でビルドした .bin ファイルはロードに失敗します:
LinderaError(kind=Deserialize, source=InvalidAutomatonError: invalid serialized automaton)
システム辞書と異なり、ユーザー辞書の .bin には format_version が無いため
バージョン検査ができず、より親切なメッセージを出せません。上記の
デシリアライズエラーとして表面化します。
CSV から v6 で再ビルドしてください:
lindera build --user \
--src ./user_dict.csv \
--dest ./build \
--metadata ./lindera-ipadic/metadata.json
.bin ではなく .csv からユーザー辞書を読み込んでいる場合は、ロード時に
コンパイルされるため対応は不要です。
学習済みモデルファイルの再生成が必要
lindera train が書き出す model.dat がバイト単位で再現可能になりました。
同じ入力・同じフラグで 2 回学習するとバイト列が一致するため、
チェックサムの取得・キャッシュ・差分比較ができます。
これを実現するため、連接行列と未知語カテゴリをハッシュマップではなく
順序付きマップとして保持するようにしました。rkyv は archived HashMap を
ソース走査順にレイアウトし、その順序はインスタンスごとにシードされるためです。
ディスク上のレイアウトが変わるので、以前のビルドが書き出した model.dat は
読み込みに失敗します。
failed to deserialize model: ... re-run `lindera train` to regenerate it.
model.dat は lindera train と lindera export の間の中間ファイルであり、
配布物ではありません。lindera train を再実行して生成し直してください。
旧モデルからすでにエクスポート済みの辞書には影響しません。また、
学習された重みはどちらでも同一です。変わったのは直列化の順序だけです。
関連する注意点が 2 つあります。
SerializableModel::connection_matrixとSerializableModel::unk_categoriesの型がHashMapからBTreeMapに変わりました。これらの公開フィールドを直接読んでいる 場合にのみ影響します。ライターメソッド群は変更ありません。- 再現性は
--max-threadsの値によらず成り立ちます。勾配と損失は学習データの 固定分割を固定順で加算するため、スレッド数は所要時間だけを変え、 学習結果は変えません。 lindera exportの出力もバイト単位で再現可能になりました。metadata.jsonはupdated_atタイムスタンプを持たなくなり、各エクスポートライターは決定的な 順序でエントリを書き出します。これに伴い Rust API からModelInfo.updated_atフィールドを削除しました(lindera-dictionary)。 このキーを含む既存のmetadata.jsonは引き続き読み込めます。DictionaryRewriter::rewrite_cachedとclear_cacheを削除しました。 キャッシュは素性文字列全体をキーにしており、実辞書では行ごとにほぼ一意で 一度もヒットせず、メモリを保持するだけだったためです。代わりにrewriteを呼んでください(&mutも不要になりました)。FeatureExtractor::extract_*6 メソッドのfeatures引数を&[String]から&[&str]に変更しました。呼び出し側がフィールドごとにStringを確保する必要がなくなります。&[String]を持っている場合は&v.iter().map(|s| s.as_str()).collect::<Vec<_>>()で渡せます。lindera-wasmからファイルシステム依存のエクスポートloadUserDictionary/buildDictionary/buildUserDictionary/TokenizerBuilder.setUserDictionary(uri)(と snake_case エイリアス)を 削除しました。wasm32-unknown-unknownにはファイルシステムが無く、 これらは常に実行時に失敗していました。システム辞書はloadDictionaryFromBytes()(またはembedded://)で、ユーザー辞書は 新設のloadUserDictionaryFromBytes()/loadUserDictionaryBinFromBytes()とsetUserDictionaryInstance()で 読み込んでください。loadDictionary()はファイル URI・パスを bytes API を案内するエラーで即座に拒否するようになりました。- 固定分割の導入で加算順が変わったため、この変更前に学習した重みと
変更後に学習した重みはバイト単位では一致しません(
--max-threads 1を 含むすべてのスレッド数で)。以後の成果物を比較可能にするにはlindera trainを再実行してください。
辞書フォーマットバージョン 2(v5.3.0 でリリース済み)
この変更は v6.0.0 ではなく v5.3.0 でリリース済みです。v5.3.0 から アップグレードする場合、システム辞書は既にフォーマットバージョン 2 で あり、そのままロードできます。この節は v5.2 以前からアップグレード する場合にのみ従ってください。
システム前方一致辞書は、シリアライズされた daachorse Aho-Corasick オートマトン
(dict.da)として保存されなくなりました。ビルダはビルド時に crawdad で文字単位の
ダブル配列トライを構築して dict.trie として書き出し、単語値への u32 累積和
インデックス(dict.valsidx)を併せて出力します。実行時にはトライをシリアライズ済み
バイト列上で直接走査するため、ロードはデシリアライズではなく O(1) のヘッダ検査で済み、
mmap 下では他の大きなコンポーネントと同様に遅延ページングされたままになります。
ビルド済み辞書ディレクトリは以下の 9 ファイルで構成されます:
| ファイル | 説明 |
|---|---|
metadata.json | 辞書メタデータ(format_version を含むようになった) |
dict.trie | 文字単位のダブル配列トライ構造(新規) |
dict.valsidx | 単語値インデックス(新規) |
dict.vals | 単語値データ |
dict.wordsidx | 単語詳細インデックス |
dict.words | 単語詳細(形態素素性) |
matrix.mtx | 連接コスト行列 |
char_def.bin | 文字カテゴリ定義 |
unk.bin | 未知語辞書 |
dict.trie と dict.valsidx 以外のファイルは v5 から変更ありません。dict.da は
存在しなくなりました。
古い辞書のロードは明確なエラーで失敗する
ビルド済み辞書の metadata.json は format_version: 2 を記録し、ローダーがこれを検証
します。v5.2 以前でビルドした辞書のロードは、ヘッダを持たないバイナリファイルを
誤読する代わりに、対処方法を含むエラーで失敗します:
Dictionary 'ipadic' has format version 1, but this build of Lindera reads format version 2. To fix this, rebuild it with `lindera build`, or download a matching prebuilt dictionary with `lindera download`.
トークナイズ結果の変更
上記のフォーマット変更とは異なり、以下の 2 点は同じ入力・同じ辞書でも Lindera が出力するトークン自体を変える可能性があります。
Decompose モードのペナルティ精度修正
Mode::Decompose の長さペナルティは、スパンの文字数を
(終了バイト位置 - 開始バイト位置) / 3 で近似していました。これは全体が
3 バイト UTF-8 文字(一般的な漢字・かな)で構成されたテキストに対してのみ
正確です。Lindera の Viterbi ラティスは内部的に文字単位で索引されるように
なったため、ペナルティは実際の文字数を使うようになりました。この変更が
影響するのは 1 バイト(ASCII)・2 バイト・4 バイト(絵文字や一部の CJK
拡張文字など)の UTF-8 文字を含むスパンの Decompose 出力のみで、純粋な
日本語の Decompose 出力には影響しません。これは挙動選択ではなくバグ修正の
ため、旧来の(不正確な)挙動に戻すオプションはありません。
未知語の候補ラダー(新規・デフォルト有効)
辞書に無い文字が連続する箇所に対して、MeCab や Vibrato は候補となる未知語の
長さを 1..=LENGTH(LENGTH は辞書の char.def にあるカテゴリごとの
フィールド)まで段階的に(梯子=ladder のように)生成し、Viterbi 探索に
コスト最小の長さを選ばせます。Lindera は char.def から LENGTH を
パースして保存はしていましたが、実行時には一切参照しておらず、常に
1 文字の候補のみ(グルーピング対象カテゴリの場合はラン全体を覆う 1 候補のみ)
しか生成していませんでした。
v6.0.0 からは、このラダーがデフォルトで生成されるようになります。IPADIC で
影響を受けるのは KANJI・HIRAGANA・KATAKANA(LENGTH=2)カテゴリで、
それ以外の大半のカテゴリは LENGTH=0 のため影響を受けません。実際の効果と
しては、辞書に無い漢字・かなが連続する箇所で、1 文字ずつに分割するより
複数文字でまとめたほうがコストが低い場合に、複数文字の未知語としてまとめて
トークナイズされるようになります — 例えば、未知の 2 字熟語が従来は 2 つの
未知語トークンに分かれていたのが、1 つの未知語トークンになります。
これは後述の新オプション max_grouping_len(ラン全体のグルーピング上限)
とは独立した、加算的な機能です。
辞書外の漢字・かな連続を含むテキストで v5 と同一の出力を再現するには、 ラダーを無効化してください:
let segmenter = Segmenter::new(mode, dictionary, None)
.unknown_word_ladder(false);
lindera tokenize -d ipadic --disable-unknown-word-ladder input.txt
AnalysisWorker/SegmentWorker は set_unknown_word_ladder(bool)、
TokenizerBuilder は set_segmenter_unknown_word_ladder(bool) で同じ
切り替えができます。
新オプション: max_grouping_len(デフォルトは従来どおり)
v6 では MeCab の max-grouping-size と同じ意味論を追加しました。各位置で、
そこから始まる辞書外文字のグルーピング可能なランが先頭を除いて
max_grouping_len 文字を超える場合、その位置ではグループ候補を出さず、
1 文字候補(および候補ラダーと辞書語)が残ります。残りの末尾は上限に
収まった時点で再びグルーピングされるため、未知語トークンは最長で
max_grouping_len + 1 文字になります。デフォルトは無制限で、これは v5 と
同じ挙動です。つまり 明示的に設定しない限り何も変わりません:
let segmenter = Segmenter::new(mode, dictionary, None)
.max_grouping_len(Some(24)); // MeCab 自体のデフォルト値
lindera tokenize -d ipadic --max-grouping-len 24 input.txt
SegmentWorker/AnalysisWorker の set_max_grouping_len(Option<usize>)、
TokenizerBuilder の set_segmenter_max_grouping_len(usize)、および
segmenter 設定の max_grouping_len キーでも指定できます。
lindera_dictionary の Rust API 変更
以下は lindera_dictionary::viterbi::{Lattice, Edge} や
lindera_dictionary::mode::{Mode, Penalty} を直接利用するコード(独自の
ラティス検査や代替トークナイザーフロントエンドなど)にのみ影響します。
lindera クレートの Segmenter・Tokenizer・SegmentWorker・
AnalysisWorker API は影響を受けません — 上記の
unknown_word_ladder/max_grouping_len オプションが追加されただけで、
シグネチャの変更はありません。
| v5 | v6 | 理由 |
|---|---|---|
Lattice::text_len() | Lattice::char_len() | ラティスがバイト索引から文字索引に変わったため。改名により、バイト前提のコードは黙って誤動作せずコンパイルエラーになる |
Lattice::edges_at(pos) | Lattice::edges_at_char(pos) | 同上(pos が文字位置になった) |
Lattice::paths_at(pos) | Lattice::paths_at_char(pos) | 同上 |
Lattice::capacity() / shrink_to(n) | 名前は同じだが単位がバイトから文字(スロット)に | バイト長は依然として shrink_to の有効な(安全側に倒れる)引数として使える(文はバイト数より文字数の方が少ないため) |
Edge::num_chars() | 削除 | パック済み Edge は終了位置を保持しなくなった(常にラティススロットの添字と一致するため)ので、エッジ単体からは計算できなくなった |
Penalty::penalty(&Edge) | Penalty::penalty(&Edge, num_chars: usize) | 呼び出し側が正確な文字数スパンを渡すようになった(前述の Decompose 精度修正を参照) |
Mode::penalty_cost(&Edge) | 削除 | コードベース内に呼び出し元が無かった。必要であれば Penalty::penalty で再実装可能 |
Lattice::set_text(..., search_mode) | Lattice::set_text(..., search_mode, max_grouping_len, unknown_word_ladder) | 新オプション用に引数 2 つが末尾に追加。v6 のデフォルトに合わせるなら None と true、v5 の挙動にするなら None と false を渡す。set_text_nbest も同様に変更 |
| — | Lattice::take_max_char_len()(新規) | 加算的な追加。処理した最大の文長を返してリセットする(worker の縮小判定用) |
set_text と set_text_nbest には、文が u16::MAX 文字未満であることを
検査する panic 契約も追加されました(エッジが開始位置を u16 で保持する
ようになったため)。Segmenter を経由する経路は MAX_SENTENCE_BYTES で
文を分割するためすべて安全です。Lattice を直接駆動するコードだけが、
自分で入力を分割する必要があります。
JavaScript バインディング: トークンがプレーンオブジェクトに
これまで両 JS バインディングは Token クラスのインスタンスを返していました。
これらのインスタンスは JavaScript ヒープの外にメモリを保持し、ホストが
ファイナライザを実行して初めて解放されます。napi はそのファイナライザを
イベントループへ遅延させ、wasm-bindgen は解放を呼び出し側に委ねるため、
大きな入力を yield せずに同期ループでトークナイズすると、強制 GC を
挟んでもメモリが蓄積していました。v6 では両バインディングともプレーン
オブジェクトを返すようになり、メモリは JavaScript の GC が完全に管理します。
実用上の利点: バッチループでメモリがフラットに保たれ、結果を
JSON.stringify・structuredClone・worker への転送にそのまま使えます。
getDetail(i) の廃止(Node.js・WASM 共通)
プレーンオブジェクトにメソッドは無いため、配列を直接参照してください:
// v5
const pos = token.getDetail(0);
// v6
const pos = token.details[0];
範囲外の読み出しは null ではなく undefined になります。
Node.js: tokenizeObjects の廃止
tokenizeObjects は上記のメモリ問題に対する v5 限定の回避策としてのみ
存在していました。tokenize が同じプレーンオブジェクトを返すように
なったため、呼び出しを置き換えてください:
// v5
const tokens = tokenizer.tokenizeObjects(text);
// v6
const tokens = tokenizer.tokenize(text);
tokenizeNbest もプレーンオブジェクトを返すようになったため、v5 で
既知の制約として記載していた蓄積の問題は解消されています。NbestResult も
Token と同様にクラスではなくなり、同じ tokens・cost プロパティを
持つプレーンオブジェクトになりました。
どちらもクラスではなくなったため、パッケージから export されなくなりました。
Token・JsToken・NbestResult・JsNbestResult はすべて index.js から
削除され、require("lindera").Token は undefined になります。
instanceof による判定もできません:
// v5
const { Token } = require("lindera-nodejs");
if (tokens[0] instanceof Token) { /* ... */ }
// v6 — プレーンオブジェクトなので、プロパティで判定する
if (typeof tokens[0].surface === "string") { /* ... */ }
TypeScript 利用者へ: トークンを表すインターフェース名は Token になりました
(Token がクラスに使われていた間は JsTokenData という名前でした)。
WASM: フィールド名が camelCase に
廃止された Token クラスは snake_case のフィールドを公開する一方、
同じクラスの toJSON() は既に camelCase を出力しており不整合がありました。
v6 では Node.js バインディングに揃えて camelCase に統一します:
// v5
token.byte_start, token.byte_end, token.word_id, token.is_unknown
// v6
token.byteStart, token.byteEnd, token.wordId, token.isUnknown
surface・position・details は変更ありません。既に toJSON() を
呼んでその結果を読んでいたコードは、これらの名前を使っていたため
そのまま動作します。ただし toJSON() 自体は、トークンが既にプレーン
オブジェクトであるため廃止されました:
// v5
const data = token.toJSON();
// v6
const data = token; // 既にプレーンオブジェクト
Token はモジュールから export されなくなったため、
import { Token } from 'lindera-wasm-...' は失敗します。代わりに import
すべきものはありません — tokenize が直接プレーンオブジェクトを返します。
WASM の tokenizeNbest は v5 の時点で既にプレーンオブジェクトを
返していたため、変更ありません。
対応が不要なケース
- Python・Ruby・PHP バインディング: 上記の JavaScript の変更の影響を
受けません。これらは解放が決定的であり、JS 側の作り直しの動機となった
メモリ問題が元々無かったため、
Tokenクラスをそのまま維持しています。 それぞれプレーンデータへの変換メソッド(to_dict()、to_h(to_hash)、toArray())が追加されましたが、削除・改名されたものはありません。 .csvから読み込むユーザー辞書: ロード時にコンパイルされるため、 新しいフォーマットが自動的に反映されます。(ビルド済みの.binは 再ビルドが必要です — 冒頭の節を参照してください。)linderaクレートの公開 API:Segmenter・Tokenizer・SegmentWorker・AnalysisWorkerのシグネチャは変わっていません(加算的なunknown_word_ladder/max_grouping_lenオプションを除く)。パスや URI から 辞書をロードするコードは、辞書自体を再ビルドまたは再ダウンロードすればそのまま コンパイル・動作します。embed-*の埋め込み辞書は各辞書クレートのビルド スクリプトがコンパイルするため、常に実行中のバージョンと一致します。
アップグレードチェックリスト
全員:
- ビルド済みユーザー辞書
.binを CSV から再ビルドする (lindera build --user)。どの v5 からのアップグレードでも必要です。 - 辞書外・非日本語テキストに対するトークナイズ結果を厳密に固定している場合は
再確認する — Decompose ペナルティ修正と未知語ラダー(デフォルト有効)の
両方が結果を変える可能性がある。v5 と同一の未知語出力が必要なら
unknown_word_ladder(false)を設定する。
JavaScript(Node.js・WASM):
- Node.js:
package.jsonの依存をlindera-nodejsからlinderaに変更し、require/importのパッケージ名を更新する。 - WASM: 依存を
lindera-wasm-web/lindera-wasm-bundlerからlindera-wasmに変更する。lindera-wasm-bundlerを使っていた場合は、使用前に デフォルトエクスポートの初期化関数(await init())を呼び出すコードを 追加する。 token.getDetail(i)をtoken.details[i]に置き換える。- Node.js:
tokenizeObjects(text)をtokenize(text)に置き換え、Token/NbestResultの import をやめる(export されなくなったため)。 TypeScript 利用者は型名JsTokenDataをTokenに変更する。 - WASM: トークンのフィールド参照を camelCase に変更し(
byteStart・byteEnd・wordId・isUnknown)、toJSON()の呼び出しを削除し、Tokenの import をやめる。
Python:
- 依存を
lindera-pythonからlinderaに変更する(pip install lindera)。import linderaはそのままで、コードの変更は不要。
lindera_dictionary を直接利用している場合:
- 上記の Rust API 表に従い呼び出し箇所を更新する(
set_text/set_text_nbestの追加引数 2 つを含む)。
v5.2 以前からアップグレードする場合(以下は v5.3.0 でリリース済み):
- 自前ビルドのシステム辞書を
lindera buildで再ビルドするか、lindera downloadでビルド済み辞書を再取得する。 - WASM:
loadDictionaryFromBytes()の呼び出しを 9 引数のシグネチャに更新する (dictDaの代わりにdictTrieとdictValsIdx)。 - WASM: v5 世代の辞書を OPFS から削除し、v6 のアーカイブをダウンロードする。
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 の警告の項目を除く)。
開発ガイド
このセクションでは、Lindera のビルド、テスト、貢献に関する情報を提供します。
- ビルドとテスト -- ビルドコマンド、テスト実行、品質チェック
- Feature フラグ -- 利用可能な feature フラグとその効果
- プロジェクト構成 -- クレートの配置とモジュール構成
- 学習パイプライン -- CRF ベースの辞書学習ワークフロー
- 貢献ガイド -- コントリビューター向けガイドライン
ビルドとテスト
ビルド
デフォルトビルド
デフォルトの feature(mmap)でワークスペースをビルドします:
cargo build
学習機能付きビルド
CRF ベースの辞書学習機能を含めてビルドします:
cargo build --features train
CLI のみビルド
cargo build -p lindera-cli
CLI ではデフォルトで train feature が有効になっています。
テスト
単一テスト
クレート内の特定のテストを実行します(開発時はこちらを推奨):
cargo test -p <crate> <test_name>
学習機能のテスト
cargo test -p lindera-trainer
クレート単位の全機能テスト
単一クレートの全テストスイートを実行します:
cargo test -p <crate> --all-features
注意: CI では
--all-featuresは使用されません。各クレートに対してキュレートされた個別の feature の組み合わせでテストを実行します(.github/workflows/regression.ymlを参照)。Makefile のクレート別パターンターゲット(make test-<crate>、make lint-<crate>)は CI と同じ feature の組み合わせを適用するため、ローカルでの実行方法としては最も近い代替手段です。CI はさらに、lindera-segmenterとlinderaをすべての辞書を埋め込んだ状態でもう一度テストし(Linux のみ)、ほかの辞書の feature でだけ有効になるテストも実行します。ローカルで同じことをするには、cargo test -p lindera --features embed-ipadic,embed-ipadic-neologd,embed-unidic,embed-sudachidict,embed-ko-dic,embed-cc-cedict,embed-jiebaを実行します(lindera-segmenterも同様)。
ワークスペース全体のテスト
cargo test
品質チェック
フォーマットチェック
コードのフォーマットがプロジェクトのスタイルに一致しているか確認します:
cargo fmt --all -- --check
フォーマットを自動修正するには:
cargo fmt --all
リント
Clippy を警告をエラーとして扱うモードで実行します:
cargo clippy -- -D warnings
注意: CI は各クレートに対して、そのクレートのテストと同じ feature で
cargo clippy --all-targets -- -D warningsを実行します(.github/workflows/test-crate.ymlと、.github/workflows/regression.ymlの言語バインディングのジョブを参照)。そのため、テストのコードも lint されます。make lint-<crate>は同じ clippy のコマンドを実行します。PR を開く前にローカルで(例えばmake lint経由で)clippy を実行してください。
ドキュメント
API ドキュメント
Rust の API ドキュメントを生成して開きます:
cargo doc --no-deps --open
各クレートの API ドキュメントに rustdoc の警告(壊れた intra-doc link や、公開項目から private 項目へのリンクなど)がないかを確認します:
make check-docs
注意: CI は
make check-docsを実行し、rustdoc の警告が 1 つでもあれば失敗します。言語バインディング以外のクレートを、すべての feature を有効にして文書化します。ライブラリは 2 回文書化し、1 回目は docs.rs が公開するのと同じく公開項目だけ、2 回目は private 項目も含めます。private 項目の doc コメントを検査するのは 2 回目だけです。続けて、CLI のコードがあるlindera-cliのバイナリを private 項目も含めて文書化します。ダミーの辞書(DOCS_RS=1)を使い、専用の target ディレクトリtarget/check-docsでビルドするので、辞書をダウンロードせず、target/にも影響しません。
言語バインディングは、ビルドにそれぞれの言語のツールチェーンが必要なため、1 つずつ検査します:
make check-docs-lindera-python # -nodejs、-ruby、-php、-wasm も同様
注意: 各バインディングの CI ジョブがそれぞれのターゲットを実行し、rustdoc の警告が 1 つでもあれば失敗します。ターゲットはバインディングを 2 回文書化します。1 回目は公開項目だけ、2 回目は private 項目も含めます。feature はデフォルトのままです。項目の有無を切り替える feature はすべてデフォルトで有効なので、これで足ります。辞書はビルドしません。
lindera-rubyはmake test-lindera-rubyと同じ RbConfig のラッパーを通して実行し、lindera-wasmはwasm32-unknown-unknown向けに文書化します。
mdBook ドキュメント
ユーザー向けドキュメントをビルドします:
mdbook build docs
http://localhost:3000 でローカルプレビュー:
mdbook serve docs
Markdown リント
ドキュメントの Markdown スタイルの問題をチェックします:
markdownlint-cli2 "docs/src/**/*.md"
ルールはリポジトリルートの .markdownlint.json で設定されています。
Feature フラグ
Lindera は Cargo の feature フラグを使用して、オプション機能と辞書の埋め込みを制御します。
コア Feature
| Feature | 説明 | デフォルト |
|---|---|---|
analysis | 分析チェーン(lindera::analysis: character filter・token filter・Tokenizer)。lindera-analysis を再エクスポート | 有効 |
mmap | ファイルシステム辞書のメモリマップ読み込みをデフォルトにする(use_mmap)。memmap2 自体は常にリンクされる | 有効 |
train | CRF ベースの辞書学習(lindera-trainer に依存。lindera::dictionary::trainer) | 下記参照 |
analysisとmmapはlinderaファサードでどちらもデフォルト有効です。default-features = falseにすると両方とも無効になり、純粋なセグメンター (lindera-segmenterクレート)だけになります。必要に応じてfeatures = ["mmap"]やfeatures = ["analysis"]で個別に有効にしてください。- v7.0 以降、分析チェーンは再び
linderaの一部です。lindera::analysisがlindera-analysisクレートを再エクスポートします。v5.x・v6.x では別の依存として 追加する必要がありました。 trainはlindera-cli・lindera-python・lindera-nodejs・lindera-ruby・lindera-phpではデフォルトで有効です。linderaライブラリクレートではデフォルトで無効なので、ライブラリとして使用する場合はfeatures = ["train"]で明示的に有効にしてください。lindera-wasmでは利用できません。
外部辞書の使用(推奨)
推奨される方法は、ビルド済み辞書を外部ファイルとして使用することです。GitHub Releases から辞書をダウンロードし、実行時にそのパスを指定してください:
#![allow(unused)] fn main() { let dictionary = load_dictionary("/path/to/ipadic")?; }
この使用方法では、追加の feature フラグは不要です。
辞書埋め込み Feature(上級者向け)
これらの feature はビルド済み辞書をバイナリに直接埋め込み、実行時に外部辞書ファイルを不要にします。自己完結型バイナリが必要な上級者向けの機能です。
| Feature | 辞書 | 言語 |
|---|---|---|
embed-ipadic | IPADIC | 日本語 |
embed-ipadic-neologd | IPADIC NEologd | 日本語 |
embed-unidic | UniDic | 日本語 |
embed-sudachidict | SudachiDict | 日本語 |
embed-ko-dic | ko-dic | 韓国語 |
embed-cc-cedict | CC-CEDICT | 中国語 |
embed-jieba | Jieba | 中国語 |
いずれもデフォルトでは無効です。必要に応じて有効にしてください:
[dependencies]
lindera = { version = "7", features = ["embed-ipadic"] }
埋め込みを有効にした場合、以下のように辞書を読み込めます:
#![allow(unused)] fn main() { let dictionary = load_dictionary("embedded://ipadic")?; }
[!NOTE] 辞書データには Lindera 本体とは別のライセンスが適用されます。辞書を埋め込んだ バイナリを配布する場合(またはビルド済み辞書を再配布する場合)は、対応する 辞書クレートの
NOTICE.txt(例:lindera-unidic/NOTICE.txt)に記載された 帰属表示を、配布物に付属するドキュメントや資料に転載してください。
組み合わせ Feature
多言語アプリケーション向けに、複数の辞書を一度に有効にするメタ Feature です。
| Feature | 含まれる辞書 |
|---|---|
embed-cjk | IPADIC + ko-dic + Jieba |
embed-cjk2 | UniDic + ko-dic + Jieba |
embed-cjk3 | IPADIC NEologd + ko-dic + Jieba |
embed-cjk4 | SudachiDict + ko-dic + Jieba |
Feature フラグの組み合わせ
複数の feature フラグを組み合わせることができます。例えば、日本語と韓国語の辞書を両方埋め込む場合:
[dependencies]
lindera = { version = "7", features = ["embed-ipadic", "embed-ko-dic"] }
またはコマンドラインから:
cargo build --features embed-ipadic,embed-ko-dic
注意事項
- 辞書の埋め込みはバイナリサイズを大幅に増加させます。実際に必要な辞書のみを埋め込んでください。
trainfeature はlindera-trainer(を通じてlindera-crf)への依存を追加し、コンパイル時間が増加します。トークナイズのみのユースケースでは不要です。mmapfeature はファイルシステム辞書に対するメモリマップド読み込みを有効にします(CLIの--mmapまたはsegmenter設定のuse_mmapキーで要求)。辞書フォーマットバージョン 2 以降、トライ(dict.trie/dict.valsidx)もシリアライズ済みバイト列上を直接走査するため、単語リストファイル(dict.vals/dict.wordsidx/dict.words)・接続コスト行列(matrix.mtx)と合わせて、大きなコンポーネントはすべて遅延読み込みされ、匿名メモリを消費しません。ロード時に全体がメモリに展開されるコンポーネントはありません。埋め込み辞書には影響しませんが、埋め込み辞書のトライと接続コスト行列もバイナリ内から直接参照されます。
プロジェクト構成
Lindera は複数のクレートで構成される Cargo ワークスペースとして組織されています。
ディレクトリ構成
lindera/
├── lindera-crf/ # CRF エンジン(pure Rust、no_std)
├── lindera-dictionary/ # 辞書ベースライブラリ
├── lindera-trainer/ # CRF ベースの辞書学習
├── lindera/ # ファサードクレート(lindera-segmenter と lindera-analysis を再エクスポート)。tests・examples・benches を含む
├── lindera-segmenter/ # 形態素分割ライブラリ(Segmenter API)
├── lindera-analysis/ # 分析チェーン(character/token filter・tokenizer)
├── lindera-cli/ # CLI ツール
├── lindera-binding/ # 言語バインディングが共有するFFI非依存のヘルパー
├── lindera-ipadic/ # IPADIC 辞書(日本語)
├── lindera-ipadic-neologd/ # IPADIC NEologd 辞書(日本語)
├── lindera-unidic/ # UniDic 辞書(日本語)
├── lindera-sudachidict/ # SudachiDict 辞書(日本語)
├── lindera-ko-dic/ # ko-dic 辞書(韓国語)
├── lindera-cc-cedict/ # CC-CEDICT 辞書(中国語)
├── lindera-jieba/ # Jieba 辞書(中国語)
├── lindera-python/ # Python バインディング(PyO3)
├── lindera-nodejs/ # Node.js バインディング(NAPI-RS)
├── lindera-ruby/ # Ruby バインディング(Magnus + rb-sys)
├── lindera-php/ # PHP バインディング(ext-php-rs)
├── lindera-wasm/ # WebAssembly バインディング(wasm-bindgen)
├── resources/ # テストリソースとサンプルデータ
└── docs/ # ドキュメント(mdBook)
クレートの説明
コアクレート
lindera-crf
条件付き確率場(CRF)の pure Rust 実装です。no_std 環境をサポートします。高速なゼロコピーシリアライゼーションに rkyv を使用します。辞書学習で使用される統計学習エンジンを提供します。
lindera-dictionary
辞書のベースライブラリです。辞書の読み込み、ビルド、クエリ機能を提供します。
lindera-trainer
カスタム辞書作成のための CRF 学習パイプラインです。lindera-dictionary のランタイム型と lindera-crf エンジンの上に構築されています。通常は lindera facade の train feature 経由で利用します(lindera::dictionary::trainer として再エクスポート)。
| モジュール | 役割 |
|---|---|
config.rs | 設定管理(種辞書、char.def、feature.def、rewrite.def) |
corpus.rs | 学習コーパスの処理 |
feature_extractor.rs | 素性テンプレートの解析と素性 ID 管理 |
feature_rewriter.rs | MeCab 互換の素性書き換え(3セクション形式) |
model.rs | 学習済みモデルの保存、シリアライゼーション、辞書出力 |
lindera-segmenter
形態素分割ライブラリです(v6 までは lindera として公開)。辞書クレートを統合し、Segmenter API を提供します。dictionary モジュールからは lindera-dictionary の生モジュール(builder・core・loader・viterbi など)も再エクスポートされます。
lindera
ファサードクレートで、Rust ユーザーが追加する唯一の依存です。lindera-segmenter の全モジュールを再エクスポートし、デフォルトで有効な analysis feature により lindera-analysis を lindera::analysis として提供します。このディレクトリには公開パスを検証する統合テスト・サンプル・ベンチマークも置かれています。
lindera-analysis
lindera-segmenter の上に構築されたLucene風の分析チェーンです(ファサード経由では lindera::analysis として利用します)。文字フィルタ、トークンフィルタ、およびそれらを Segmenter の周りで組み合わせる Tokenizer を提供します。
lindera-cli
トークナイズ、辞書学習、エクスポート、ビルドのためのコマンドラインインターフェースです。デフォルトで train feature が有効です。
lindera-binding
5つの言語バインディング(lindera-python、lindera-nodejs、lindera-ruby、lindera-php、lindera-wasm)すべてが共有するFFI非依存のヘルパーです。各バインディングがそれぞれの言語のネイティブAPIでラップするコアのトークナイザー・スキーマ・メタデータ層を提供します。lindera ファサードに依存します。
辞書クレート
各辞書クレートには、特定の言語と辞書ソースのビルド済み辞書データが含まれます。
| クレート | 言語 | 辞書ソース |
|---|---|---|
lindera-ipadic | 日本語 | IPADIC |
lindera-ipadic-neologd | 日本語 | IPADIC NEologd(拡張語彙) |
lindera-unidic | 日本語 | UniDic |
lindera-sudachidict | 日本語 | SudachiDict |
lindera-ko-dic | 韓国語 | ko-dic |
lindera-cc-cedict | 中国語 | CC-CEDICT |
lindera-jieba | 中国語 | Jieba |
バインディング
lindera-python
PyO3 で構築された Python バインディングです。Lindera のトークナイザー API を Python アプリケーションに公開します。
lindera-nodejs
NAPI-RS で構築された Node.js バインディングです。Lindera のトークナイザー API を Node.js アプリケーションに公開します。
lindera-ruby
Magnus と rb-sys で構築された Ruby バインディングです。Lindera のトークナイザー API を Ruby gem として公開します。
lindera-php
ext-php-rs で構築された PHP バインディングです。Lindera のトークナイザー API を PHP 拡張として公開します。拡張名は lindera で、PIE 向けに Packagist へ lindera/lindera として公開します。パッケージの composer.json はリポジトリのルートにあり、PIE を lindera-php/ に向けます。そこで config.m4 と Makefile.frag が phpize ビルドの中で cargo build を実行します。
lindera-wasm
wasm-bindgen で構築された WebAssembly バインディングです。ブラウザと Node.js でのトークナイズを可能にします。
その他のディレクトリ
resources/
テストスイートで使用されるサンプル辞書、ユーザー辞書、テストコーパスなどのテストリソースです。
docs/
mdBook で構築されたユーザー向けドキュメントです。目次は docs/src/SUMMARY.md で定義されています。日本語翻訳は docs/ja/ 配下にあります。
lindera/examples/ と lindera-analysis/examples/
一般的な使用パターンを示す実行可能なサンプルプログラムです。以下のコマンドで実行できます:
cargo run -p lindera --features=embed-ipadic --example=segment
cargo run -p lindera-analysis --features=embed-ipadic --example=tokenize
学習パイプライン
Lindera は、カスタム形態素解析モデルを作成するための CRF ベースの辞書学習機能を提供します。この機能には train feature フラグが必要です。
概要
学習パイプラインは3つのステージで構成されます:
lindera train --> model.dat --> lindera export --> dictionary files --> lindera build --> compiled dictionary
- Train: アノテーション付きコーパスと種辞書から CRF の重みを学習し、バイナリモデルファイルを生成します。
- Export: 学習済みモデルを Lindera 辞書ソースファイルに変換します。
- Build: ソースファイルを Lindera が実行時に読み込めるバイナリ辞書にコンパイルします。
学習のしくみ
lindera train は、MeCab の学習器と同じく、コーパスの文ごとにラティスを 1 つ作ります。ラティスには、各位置から始まる種辞書の語と、ランタイムが char.def と unk.def からその位置に作る未知語が入ります(文字定義と未知語定義を参照)。CRF は、アノテーションされたパス(正解のトークン)が最もコストの低いパスになるように重みを学習します。正解の各トークンには、次のうち最初に当てはまるもののラベルを付けます。
- 範囲(span)と素性文字列全体が同じ種辞書の行のうち、ファイル順で最初の行。MeCab の学習器は先頭の
eval-size列だけを比べますが、Lindera はすべての列が一致する必要があるため、読みなどの列が種辞書の行と異なるトークンは次の段階に進みます。 - 範囲が同じ未知語候補の
unk.defエントリのうち、*以外の素性の列がすべて正解トークンの列と一致する、ファイル順で最初のエントリ。 - 正解の素性文字列から作る仮想ラベル。MeCab の学習器が仮想ノードを追加するのと同じです。素性文字列が同じ正解トークンは 1 つの仮想ラベルを共有します。仮想ラベルは学習されますが、エクスポートされません。
それ以外の候補はすべて負例になります。ラティスを作り終えると、lindera train は正解トークンのラベルの付き方を出力します。resources/training/ のファイルでは次のとおりです。
Lattice statistics: gold_lexicon=14 gold_unknown=0 gold_virtual=0 virtual_labels=0 negative_lexicon=0 negative_unknown=17
gold_lexicon・gold_unknown・gold_virtual は上の 3 つの段階それぞれでラベルを付けた正解トークンの数、virtual_labels は仮想ラベルの数、negative_lexicon と negative_unknown は種辞書と未知語候補それぞれから作った負例の辺の数です。
必要な入力ファイル
1. 種辞書 (seed.csv)
MeCab CSV 形式のベース語彙辞書です。
外国,0,0,0,名詞,一般,*,*,*,*,外国,ガイコク,ガイコク
人,0,0,0,名詞,接尾,一般,*,*,*,人,ジン,ジン
参政,0,0,0,名詞,サ変接続,*,*,*,*,参政,サンセイ,サンセイ
各行の構成: surface,left_id,right_id,cost,pos,pos_detail1,pos_detail2,pos_detail3,inflection_type,inflection_form,base_form,reading,pronunciation
種辞書では left_id、right_id、cost フィールドは 0 に設定されます。学習器が CRF モデルから適切な値を計算します。
種辞書は、lindera build が lex.csv を読むのと同じく CSV として読みます。
- フィールドは引用符で囲めます。mecab-ko-dic のカンマの表層形がその例です:
",",1792,3558,788,SC,*,*,*,*,*,*,*。 #で始まる行はコメントではなく、1 つの行として読みます。UniDic の#の表層形がその例です。空行と、フィールドが 5 つ未満の行は読み飛ばします。- 行の素性は、4 つ目のフィールドより後ろのテキストを、引用符も含めて書かれたまま使います。
lindera exportはそれをそのまま書き出し、,・"・改行を含む表層形は引用符で囲みます。 - 複数の行にまたがる引用付きのフィールドは、その行を示すエラーになります。閉じていない引用符である可能性が高く、そのままでは後ろの行を飲み込んでしまうためです。
- 行の順序はそのまま保たれ、その順序がラベルの ID になります。
書き換え規則・素性テンプレート・未知語の照合で素性の列を読むとき、lindera train は素性を、上の CSV の読み方ではなく、MeCab の学習器と同じく(tokenizeCSV)列に分けます。コーパスの素性も同じく分けます。
- 引用符で囲んだ列は 1 つの列で、
""は"と読みます。mecab-jieba の4S店の定義"authorized full-service car dealership (offering sales, spare parts, after-sales service and customer support)"は 1 つの列です。 - 列の前の空白とタブは読み飛ばします。CC-CEDICT の
COVID-19, the coronavirus disease identified in 2019/は、COVID-19とthe coronavirus disease identified in 2019/の 2 つの列です。 - 閉じる引用符から次の
,までのテキストは捨てます。"bamboo" radical in Chinese characters (Kangxi radical 118)/はbambooという列です。 - 素性の末尾の
,の後ろに空の列はできません。
2. 学習コーパス (corpus.txt)
タブ区切り形式のアノテーション付きテキストデータです。各行は surface<TAB>pos_info で、文は EOS で区切られます。
外国 名詞,一般,*,*,*,*,外国,ガイコク,ガイコク
人 名詞,接尾,一般,*,*,*,人,ジン,ジン
参政 名詞,サ変接続,*,*,*,*,参政,サンセイ,サンセイ
権 名詞,接尾,一般,*,*,*,権,ケン,ケン
EOS
これ 連体詞,*,*,*,*,*,これ,コレ,コレ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
テスト 名詞,サ変接続,*,*,*,*,テスト,テスト,テスト
EOS
学習の品質はこのコーパスの量と質に大きく依存します。
3. 文字定義 (char.def)
文字タイプのカテゴリと Unicode コードポイント範囲を定義します。
# Category definition: category_name compatibility_flag continuity_flag length
DEFAULT 0 1 0
SPACE 0 1 0
HIRAGANA 1 1 0
KATAKANA 1 1 0
KANJI 0 0 2
ALPHA 1 1 0
NUMERIC 1 1 0
# Character range mapping
0x0020 SPACE # Space (reserved for SPACE)
0x0009 SPACE # Tab
0x000A SPACE # Line feed
0x000B SPACE # Vertical tab
0x000D SPACE # Carriage return
0x3041..0x3096 HIRAGANA # Hiragana
0x30A1..0x30F6 KATAKANA # Katakana
0x4E00..0x9FAF KANJI # Kanji
0x0030..0x0039 NUMERIC # Numbers
0x0041..0x005A ALPHA # Uppercase letters
0x0061..0x007A ALPHA # Lowercase letters
パラメータは、文字の既定カテゴリが作る未知語を制御します。辞書語が始まる位置でも未知語を作るか(INVOKE)、どの文字も直前の文字とカテゴリを共有する連続を 1 つのトークンにするか(GROUP)、どの長さまで短い候補を作るか(LENGTH)を指定します。
範囲行には複数のカテゴリを書くことができ(例: 0x4E00 KANJINUMERIC KANJI)、少なくとも 1 つのカテゴリが必要です。同じコードポイントを複数の行が覆う場合は、MeCab と同じく最後の行がそのカテゴリを決めます。その行の先頭のカテゴリが文字の既定カテゴリです。未知語候補を作るのは既定カテゴリだけで、素性テンプレートの %t もこれを使います。
MeCab と同じく、範囲行が指せるのはそれより前の行で定義したカテゴリだけで、各カテゴリの定義は 1 回だけです。lindera train と lindera build は、これに反する char.def を、行とカテゴリを示すエラーで拒否します。たとえば、KATAKANA を定義する行より前に、char.def の 1 行目として 0x30FC KATAKANA を挿入して lindera train を実行すると、次のようになります:
Error: LinderaError { kind: Args, source: failed to parse char.def: LinderaError(kind=Content, source=char.def: line 1: category KATAKANA is not defined; a range line can only name a category defined on an earlier line) }
SPACE カテゴリと、unk.def のその行は残してください。MeCab は、すべての char.def に DEFAULT と SPACE を求めます。また、すべてのカテゴリに unk.def の行が 1 つ以上必要です(次の節を参照)。Segmenter は SPACE の文字を空白として扱い、デフォルトでは MeCab と同じく読み飛ばして出力しません。char.def に SPACE カテゴリがない辞書は、--keep-whitespace を指定しないと使えず、lindera tokenize は SPACE category is not defined in the dictionary (char.def) で失敗します。
学習は、ランタイムと同じコード(Lattice::unknown_word_candidates)で未知語候補を作ります。そのため、ビルドした辞書が作る候補に対して重みを学習します。
- 各位置では、文字の既定カテゴリが候補を作ります。
INVOKEが 0 で、その位置から種辞書の語が始まる場合は作りません。 GROUPが 1 なら、どの文字も直前の文字とカテゴリを共有する連続を 1 つの候補にします。LENGTHが n なら、どの文字も先頭の文字とカテゴリを共有する間、1〜n 文字の候補も作ります(候補ラダー)。上の例のKANJI 0 0 2では、種辞書の語が始まらない漢字の位置に、漢字 1 文字と 2 文字の候補ができます。- カテゴリの
unk.defのエントリそれぞれが、これらすべての範囲の候補を持ちます。
グルーピングの長さは、ランタイムのデフォルトと同じく無制限です。Rust API の Trainer::max_grouping_len で、ランタイムと同じ意味(先頭の 1 文字を除いた文字数)の上限を設定できます。CLI にはこのオプションはありません。
4. 未知語定義 (unk.def)
文字カテゴリごとに、そのカテゴリが作る未知語を定義します。各行は category,left_id,right_id,cost,feature... で、category は char.def のカテゴリ名です。種辞書と同じく ID とコストは 0 にしておきます。学習した値はエクスポートで書き出されます。
# Unknown word definitions
DEFAULT,0,0,0,名詞,一般,*,*,*,*,*,*,*
SPACE,0,0,0,記号,空白,*,*,*,*,*,*,*
HIRAGANA,0,0,0,名詞,一般,*,*,*,*,*,*,*
KATAKANA,0,0,0,名詞,一般,*,*,*,*,*,*,*
KANJI,0,0,0,名詞,一般,*,*,*,*,*,*,*
ALPHA,0,0,0,名詞,固有名詞,一般,*,*,*,*,*,*
NUMERIC,0,0,0,名詞,数,*,*,*,*,*,*,*
- 各行がそれぞれ 1 つのエントリで、1 つのカテゴリが複数のエントリを持てます。未知語がとりうる品詞ごとに 1 行です。たとえば mecab-ipadic の
unk.defは 11 カテゴリに 40 行あり、そのうち 6 行がKANJI(名詞,一般・名詞,サ変接続・4 種類の名詞,固有名詞)です。 - 各エントリはそれぞれ 1 つの CRF ラベルになり、その
%tはエントリのカテゴリの ID です。カテゴリの候補はエントリごとに作られるため、どのエントリがどこに合うかを学習します。 - 行の順序は保たれます。
lindera exportはエントリごとに 1 行を同じ順序で書き出します。 #で始まる行と空行は読み飛ばします。フィールドが 5 つ未満(カテゴリ、2 つの ID、コスト、1 列以上の素性)の行はエラーになります。
Error: LinderaError { kind: Args, source: unk.def line 1: expected at least 5 fields (category, left id, right id, cost, feature), got 4 }
- MeCab と同じく、各行は
char.defで定義したカテゴリを指し、char.defの各カテゴリには 1 行以上が必要です。lindera trainとlindera buildは、これに反するunk.defを拒否します。エラーはカテゴリを示し、char.defが定義していないカテゴリの行では、コメント行と空行も数えた行番号も示します。たとえば、char.defにSYMBOLカテゴリがないまま、上のunk.defの 9 行目にSYMBOL,0,0,0,記号,一般,*,*,*,*,*,*,*を追加してlindera trainを実行すると、次のようになります:
Error: LinderaError { kind: Args, source: failed to check unk.def: LinderaError(kind=Content, source=unk.def: line 9: category SYMBOL is not defined in char.def) }
5. 素性テンプレート (feature.def)
CRF モデルが学習に使用する情報を定義する MeCab 互換の素性抽出パターンです。
# Unigram features (word-level)
UNIGRAM U00:%F[0] # POS
UNIGRAM U01:%F[0],%F?[1] # POS + POS detail (%F?[n] = optional, skipped if *)
UNIGRAM U02:%F[6] # Base form
UNIGRAM U03:%w # Surface form
# Bigram features (context combination)
BIGRAM B00:%L[0]/%R[0] # Left POS / Right POS
BIGRAM B01:%L[0],%L[1]/%R[0],%R[1] # Left POS detail / Right POS detail
テンプレート変数:
| 変数 | 説明 |
|---|---|
%F[n] / %F?[n] | インデックス n の素性フィールド(? = オプション、値が * の場合はスキップ) |
%L[n] | 左文脈の素性フィールド(rewrite.def の左セクションから) |
%R[n] | 右文脈の素性フィールド(rewrite.def の右セクションから) |
%w | 単語の表層形 |
%u | ユニグラム書き換え後の素性文字列 |
%l | 左書き換え後の素性文字列 |
%r | 右書き換え後の素性文字列 |
%F[n]・%L[n]・%R[n] のフィールドは、書き換え後の素性を種辞書で説明したとおりに分けた列です。%u・%l・%r は、rewrite.def が書き出したままの書き換え後の素性で、, か " を含む列は引用符で囲まれています。
素性の最後の列より後ろを読むテンプレートは、? の有無にかかわらず、MeCab の学習器と同じく、その素性からは何も作りません。たとえば UNIGRAM U06:%F[6] は、5 列の unk.def の素性 n,*,*,*,* には何も作りません。バイグラムのテンプレートは、%L[n] と %R[n] のどちらかが最後の列より後ろを読むと何も作りません。
6. 素性書き換えルール (rewrite.def)
MeCab 互換の3セクション形式による素性正規化ルールです。セクションは空行で区切られます。
# Section 1: Unigram rewrite rules
名詞,固有名詞,* 名詞,固有名詞
助動詞,*,*,*,特殊・デス 助動詞
* *
# Section 2: Left context rewrite rules
名詞,固有名詞,* 名詞,固有名詞
助詞,* 助詞
* *
# Section 3: Right context rewrite rules
名詞,固有名詞,* 名詞,固有名詞
助詞,* 助詞
* *
各行は pattern<TAB>replacement です。パターンはワイルドカードとして * を使用し、前方一致で照合されます。各セクションで最初に一致したルールが適用されます。ユニグラム、左文脈、右文脈に対して異なるルールを独立して適用できるため、スパース性を低減するきめ細かい素性正規化が可能です。
パターン・置換・素性は種辞書で説明したとおりに列に分けるので、, を含む引用付きの列は 1 つの列です。MeCab と同じく、書き換えた列は , でつなぎ、, か " を含む列は引用符で囲み、中の " を二重にします。たとえば左の規則 * $5 は、素性 n,dian4,店,店,"shop, store",1 を "shop, store" に書き換えます。
学習パラメータ
| パラメータ | 説明 | デフォルト |
|---|---|---|
lambda | 正則化係数(過学習の制御) | 0.01 |
regularization | 正則化の種類: l1、l2、elasticnet のいずれか | l1 |
elastic-net-l1-ratio | Elastic Net 正則化の L1 比率(0.0-1.0、--regularization elasticnet の場合のみ使用) | 0.5 |
max-iterations | 最大学習イテレーション数 | 100 |
max-threads | 並列処理スレッド数 | CPU コア数 |
学習は MeCab の学習器より遅く、収束する前に max-iterations で止まることがあります(#1167)。
CLI の使用
Train
lindera train \
--seed seed.csv \
--corpus corpus.txt \
--char-def char.def \
--unk-def unk.def \
--feature-def feature.def \
--rewrite-def rewrite.def \
--lambda 0.01 \
--max-iterations 100 \
--max-threads 4 \
--output model.dat
Export
学習済みモデルを辞書ソースファイルに変換します:
lindera export \
--model model.dat \
--metadata metadata.json \
--output ./dict-source
以下のファイルが生成されます:
| ファイル | 説明 |
|---|---|
lex.csv | 学習済みコスト付きレキシコン |
matrix.def | すべての文脈 ID と BOS/EOS 用の 0 を覆う連接コスト行列。正則化でバイグラムの重みが 1 つも残らない場合も同じ。学習した重みがない連接のコストは、学習中や MeCab と同じく 0 |
unk.def | 未知語定義。入力の unk.def のエントリごとに 1 行を入力と同じ順序で書き、カテゴリ・学習した ID とコスト・素性を持つ |
char.def | 文字定義 |
feature.def | 素性テンプレート |
rewrite.def | 素性書き換えルール |
left-id.def | 左文脈 ID マッピング |
right-id.def | 右文脈 ID マッピング |
metadata.json | 辞書メタデータ。model_info.connection_matrix_size は matrix.def の大きさ(行(右文脈 ID)× 列(左文脈 ID)) |
たとえば上の unk.def で、最初の KANJI の行の後に 2 つ目の KANJI の行 KANJI,0,0,0,名詞,サ変接続,*,*,*,*,*,*,* を加えると、エクスポートした unk.def は両方の行を入力と同じ順序で持ちます。
DEFAULT,1,1,32761,名詞,一般,*,*,*,*,*,*,*
SPACE,1,1,0,記号,空白,*,*,*,*,*,*,*
HIRAGANA,1,1,32761,名詞,一般,*,*,*,*,*,*,*
KATAKANA,1,1,32761,名詞,一般,*,*,*,*,*,*,*
KANJI,1,1,32761,名詞,一般,*,*,*,*,*,*,*
KANJI,1,1,0,名詞,サ変接続,*,*,*,*,*,*,*
ALPHA,1,1,0,名詞,固有名詞,一般,*,*,*,*,*,*
NUMERIC,1,1,0,名詞,数,*,*,*,*,*,*,*
Build
エクスポートされたソースファイルをバイナリ辞書にコンパイルします:
lindera build \
--src ./dict-source \
--dest ./dict-compiled \
--metadata ./dict-source/metadata.json
lindera build は、MeCab の mecab-dict-index と同じく、lex.csv の各行と unk.def の各行の文脈 ID が matrix.def の範囲内にあることを確認します(文脈 ID の確認を参照)。lindera export が書き出したファイルはこの確認を通ります。
Tokenize
ビルドした辞書でテキストを解析します:
echo "これは外国人参政権です。" | lindera tokenize -d ./dict-compiled
上の入力ファイルに resources/training/ のファイルを使うと、出力は次のとおりです。
これ 名詞,代名詞,一般,*,*,*,これ,コレ,コレ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
外国 名詞,一般,*,*,*,*,外国,ガイコク,ガイコク
人 名詞,接尾,一般,*,*,*,人,ジン,ジン
参政 名詞,サ変接続,*,*,*,*,参政,サンセイ,サンセイ
権 名詞,接尾,一般,*,*,*,権,ケン,ケン
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。 記号,句点,*,*,*,*,。,。,。
EOS
これは 外国人参政権です。 のように入力に空白があっても、空白は読み飛ばされ、出力は同じです。--keep-whitespace を指定すると、空白は SPACE の未知語として出力されます。
これ 名詞,代名詞,一般,*,*,*,これ,コレ,コレ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
記号,空白,*,*,*,*,*,*,*
外国 名詞,一般,*,*,*,*,外国,ガイコク,ガイコク
人 名詞,接尾,一般,*,*,*,人,ジン,ジン
参政 名詞,サ変接続,*,*,*,*,参政,サンセイ,サンセイ
権 名詞,接尾,一般,*,*,*,権,ケン,ケン
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。 記号,句点,*,*,*,*,。,。,。
EOS
出力モデルの形式
学習済みモデルは高速読み込みのために rkyv バイナリ形式でシリアライズされます。以下を含みます:
- CRF で学習された素性の重み
- ラベルセット(種辞書の行、続いて
unk.defのエントリ) - 品詞情報
- ファイル順の
unk.defのエントリと、char.def・feature.def・rewrite.defの内容 - 学習メタデータ(形式バージョン、正則化、イテレーション数、素性・ラベル数)
形式バージョンは 2.0.0(lindera_trainer::model::MODEL_FORMAT_VERSION)で、モデルの構造や意味が変わるたびに変わります。lindera export は、別のバージョンで書かれた model.dat(7.0.0 より前に書かれたものはすべて該当)を、「re-run lindera train to regenerate it」で終わるエラーで拒否します。そうしたモデルは現在のバージョンで学習し直してください。
出力はバイト単位で再現可能です。同じ入力・同じフラグで 2 回学習すると
model.dat のバイト列が一致するため、チェックサムの取得や差分比較ができます。
これは --max-threads の値によらず成り立ちます。勾配と損失は学習データの
固定分割を固定順で加算するため、スレッド数は所要時間だけを変え、
結果は変えません。
API の使用
lindera-trainer の完全な API については、Lindera Trainer アーキテクチャとAPIリファレンスを参照してください。
#![allow(unused)] fn main() { use std::fs::File; use lindera_trainer::{Corpus, Trainer, TrainerConfig}; // Load configuration from files let seed_file = File::open("resources/training/seed.csv")?; let char_file = File::open("resources/training/char.def")?; let unk_file = File::open("resources/training/unk.def")?; let feature_file = File::open("resources/training/feature.def")?; let rewrite_file = File::open("resources/training/rewrite.def")?; let config = TrainerConfig::from_readers( seed_file, char_file, unk_file, feature_file, rewrite_file )?; // Initialize and configure trainer let trainer = Trainer::new(config)? .regularization_cost(0.01) .max_iter(100) .num_threads(4); // Load corpus let corpus_file = File::open("resources/training/corpus.txt")?; let corpus = Corpus::from_reader(corpus_file)?; // Execute training let model = trainer.train(corpus)?; // Save model (binary format) let mut output = File::create("trained_model.dat")?; model.write_model(&mut output)?; // Output in Lindera dictionary format. output_user.csv には read_user_lexicon で // 読み込んだエントリが入力順で書かれる(呼ばなければ空になる)。 let mut lex_out = File::create("output_lex.csv")?; let mut conn_out = File::create("output_conn.dat")?; let mut unk_out = File::create("output_unk.def")?; let mut user_out = File::create("output_user.csv")?; model.write_dictionary(&mut lex_out, &mut conn_out, &mut unk_out, &mut user_out)?; Ok::<(), Box<dyn std::error::Error>>(()) }
推奨コーパス仕様
実用的なアプリケーション向けの辞書を生成するための推奨事項:
コーパスサイズ
| レベル | 文数 | 用途 |
|---|---|---|
| 最小 | 100以上 | 基本的な動作確認 |
| 推奨 | 1,000以上 | 実用的なアプリケーション |
| 理想 | 10,000以上 | 商用品質 |
品質ガイドライン
- 語彙の多様性: さまざまな品詞のバランスの取れた分布、活用形・接尾語のカバー、専門用語・固有名詞の適切な含有。
- 一貫性: コーパス全体で分析基準を一貫して適用すること。
- 検証: 形態素解析結果を手動で検証すること。エラー率を5%以下に維持すること。
貢献ガイド
Lindera への貢献に興味をお持ちいただきありがとうございます。このページでは、貢献を始めるためのガイドラインを紹介します。
はじめに
-
GitHub でリポジトリをフォークします。
-
フォークをローカルにクローンします:
git clone https://github.com/<your-username>/lindera.git cd lindera -
feature ブランチを作成します:
git checkout -b feature/my-feature -
変更を行い、すべてのチェックに通ることを確認します:
cargo fmt --all -- --check cargo clippy -- -D warnings cargo test make check-docs注意: CI はこの 4 つをすべて検査します。フォーマットを確認し、各クレートに対してそのクレートのテストと同じ feature で clippy(警告はエラー扱い)とテストを実行し、どのクレートでも rustdoc に警告があれば失敗します。
make check-docsは、ビルドにそれぞれの言語のツールチェーンが必要な言語バインディングを対象にしません。バインディングを変更した場合は、そのバインディングの CI ジョブと同じくmake check-docs-lindera-<binding>も実行してください。make lintとmake testはクレートごとに同じ feature を使います。詳しくはビルドとテストを参照してください。 -
変更をコミットしてプッシュし、プルリクエストを開きます。
コードスタイル
- リポジトリの既存のコードスタイルに従ってください。
- コミット前に
cargo fmtを実行してください。 - すべての public および private アイテム(型、関数、モジュール、フィールド、定数、型エイリアス)にドキュメントコメント(
///)を記述してください。 - trait 実装メソッドにも、実装固有の振る舞いを説明するドキュメントコメントを記述してください。
- 関数・メソッドのドキュメントには、該当する場合
# Argumentsと# Returnsセクションを含めてください。これらのセクションを日本語の見出し# 引数・# 戻り値で書くと、CI が失敗します。 - コードコメント、ドキュメントコメント、コミットメッセージ、ログメッセージ、エラーメッセージは英語で記述してください。
- 本番コードでは
unwrap()やexpect()を避けてください(テストコードでは使用可)。 unsafeブロックは必要な場合にのみ使用し、必ず// SAFETY: ...コメントを付けてください。- モジュールは
mod.rsスタイルではなく、ファイルベースのスタイル(src/tokenizer.rs)を使用してください。
言語バインディングの整合性
Lindera は Python・Node.js・Ruby・PHP・WASM のバインディングを提供しています。バインディングの機能を追加・レビューする際は次の方針に従ってください。
- 機能セットを揃える。 トークナイズ(
tokenize/tokenize_surfaces/tokenize_nbest)、ユーザー辞書、文字フィルタ、トークンフィルタは全バインディングで利用できるようにします。あるバインディングに機能を追加したら、他のバインディングの計画(または追跡 Issue)を添えてください。 - 表現と寿命管理は各言語に従う。 トークンをクラスにするかプレーンデータにするか、命名規則(camelCase / snake_case)、変換ヘルパー(
to_dict/to_h/toArray/toJSON)は言語ごとの判断で、バインディング間で一致させる必要はありません。JS バインディングはホストがクラスのファイナライザを遅延させるためプレーンオブジェクトを返し、Python・Ruby・PHP は解放が決定的なのでクラスを保ちます。
共通ロジックは lindera-binding(例: TokenView)に置き、各バインディングはコアのデータを自身の FFI 型に写すだけにします。
テスト
-
すべての新機能にユニットテストを作成してください。
-
開発中は迅速なフィードバックのために関連するテストのみを実行してください:
cargo test -p <crate> <test_name> -
学習パイプライン機能に関連する作業では、
lindera-trainerクレートのテストを実行してください:cargo test -p lindera-trainer
コミットメッセージ
Conventional Commits の仕様に従ってください。コミットメッセージは英語で記述してください。
例:
feat: add Korean dictionary supportfix: correct character category ID in trainerdocs: update installation instructionsrefactor: split large training method into smaller functions
ドキュメント
-
変更がユーザー向けドキュメントに影響する場合は、
docs/src/配下の関連ファイルを更新してください。 -
Markdown ファイルの編集後は、リントエラーがないことを確認してください:
markdownlint-cli2 "docs/src/**/*.md" -
ルールはリポジトリルートの
.markdownlint.jsonで設定されています。
依存関係
新しい依存関係を追加する際は、ライセンスの互換性を確認してください。Lindera は MIT / Apache-2.0 デュアルライセンスを使用しています。
Feature フラグ
学習関連コードの条件コンパイルには #[cfg(feature = "train")] を使用してください。完全なリストは Feature フラグ を参照してください。
問題の報告
バグを報告する際は、以下の情報を含めてください:
- Lindera のバージョン(
lindera --versionまたはCargo.tomlを確認) - Rust のバージョン(
rustc --version) - オペレーティングシステム
- 問題の再現手順
- 期待される動作と実際の動作