はじめに
Litsea は、TinySegmenter および TinySegmenterMaker に触発されて開発された、Rust で実装された極めてコンパクトな単語分割ライブラリです。
MeCab や Lindera などの従来の形態素解析器とは異なり、Litsea は大規模な辞書に依存しません。代わりに、AdaBoost 二値分類アルゴリズムに基づくコンパクトな学習済みモデルを使用して単語分割を行います。また、Averaged Perceptron 多クラス分類器と Universal POS (UPOS) タグセットを用いた単語分割と品詞推定の同時実行もサポートしています。
主な特徴
- 高速かつ安全な Rust 実装 – Rust の安全性保証とパフォーマンスを活用
- コンパクトな学習済みモデル – モデルファイルはわずか数キロバイト
- 辞書不要 – 統計モデルのみで分割を実行
- 品詞推定(POS Tagging) – Averaged Perceptron 多クラス分類により、単語分割と同時に UPOS 品詞タグを推定
- 多言語対応 – 日本語、中国語(簡体字/繁体字)、韓国語
- モデル学習機能 – AdaBoost または Averaged Perceptron を使用して独自のコーパスからカスタムモデルを学習可能
- リモートモデル読み込み – HTTP/HTTPS URL またはローカルファイルからモデルを読み込み
- シンプルで拡張性の高い API – Rust プロジェクトへのライブラリとしての統合が容易
仕組み
Litsea は単語分割を二値分類問題として扱います。文中の各文字位置について、モデルがその位置が単語境界(+1)か非境界(-1)かを予測します。分類器は、各言語固有の文字 n-gram 特徴量と文字種情報を使用します。
Input: "LitseaはRust製です"
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
O O O O B O B O B ← boundary predictions
Output: ["Litsea", "は", "Rust製", "です"]
品詞推定(POS Tagging)
Litsea は単語分割に加えて、品詞推定(Part-of-Speech Tagging)もサポートしています。Averaged Perceptron 多クラス分類器を使用し、単語分割と品詞推定を同時に(Joint方式で)行います。
各文字位置に対して、18 クラスの SegmentLabel を予測します:
B-NOUN,B-VERB, …,B-X(17 品詞の境界ラベル)O(非境界 = 単語の継続)
品詞タグには Universal Dependencies の UPOS タグセット(17 品詞)を採用しています。
Input: "今日はいい天気ですね。"
Output: 今日/X は/ADP いい/ADJ 天気/NOUN です/AUX ね/PART 。/PUNCT
名前の由来
クスノキ科には Lindera(クロモジ)と同じ科に属する Litsea cubeba(アオモジ)という小さな植物があります。これが Litsea という名前の由来です。
現在のバージョン
Litsea v0.4.0 – Rust Edition 2024、最低 Rust バージョン 1.87。
リンク
導入
Litsea へようこそ! このセクションでは、すぐに使い始められるようガイドします。
Litsea は、単語分割(AdaBoost)と品詞推定付き単語分割(Averaged Perceptron)の両方をサポートする Rust 製コンパクト単語分割ライブラリです。
次のステップ
インストール
前提条件
- Rust 1.87 以降(stable チャンネル)– rust-lang.org から入手
- Cargo(Rust のパッケージマネージャ、Rust に同梱)
CLI ツールのインストール
crates.io から
cargo install litsea-cli
ソースから
git clone https://github.com/mosuka/litsea.git
cd litsea
cargo build --release
バイナリは ./target/release/litsea に生成されます。
インストールの確認:
./target/release/litsea --help
ライブラリとしての利用
プロジェクトの Cargo.toml に Litsea を追加します:
[dependencies]
litsea = "0.4.0"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
注意: モデルの読み込み(
load_model)は HTTP/HTTPS URL をサポートする非同期操作のため、tokioが必要です。
サポートプラットフォーム
Litsea は以下のプラットフォームでテストされています:
| OS | Architecture |
|---|---|
| Linux | x86_64, aarch64 |
| macOS | x86_64 (Intel), aarch64 (Apple Silicon) |
| Windows | x86_64, aarch64 |
クイックスタート
CLI クイックスタート
テキストの分割
Litsea には models/ ディレクトリに学習済みモデルが同梱されています。テキストを segment コマンドにパイプで渡します:
日本語:
echo "LitseaはTinySegmenterを参考に開発された、Rustで実装された極めてコンパクトな単語分割ソフトウェアです。" \
| litsea segment -l japanese ./models/japanese.model
出力:
Litsea は TinySegmenter を 参考 に 開発 さ れ た 、 Rust で 実装 さ れ た 極めて コンパクト な 単語 分割 ソフトウェア です 。
中国語:
echo "中文分词测试。" | litsea segment -l chinese ./models/chinese.model
韓国語:
echo "한국어 단어 분할 테스트입니다." | litsea segment -l korean ./models/korean.model
品詞推定付き分割
--pos フラグを付けると、単語分割と同時に UPOS 品詞タグを推定します:
echo "今日はいい天気ですね。" \
| litsea segment --pos -l japanese ./models/japanese_pos.model
出力:
今日/X は/ADP いい/ADJ 天気/NOUN です/AUX ね/PART 。/PUNCT
ライブラリ クイックスタート
モデルを読み込みテキストを分割する最小限の Rust プログラムです:
use std::path::Path;
use litsea::adaboost::AdaBoost;
use litsea::language::Language;
use litsea::segmenter::Segmenter;
fn main() -> litsea::Result<()> {
// 学習済みモデルを読み込み
let mut learner = AdaBoost::new(0.01, 100);
learner.load_model_from_path(Path::new("./models/japanese.model"))?;
// Create a segmenter
let segmenter = Segmenter::new(Language::Japanese, Some(learner));
// Segment text
let tokens = segmenter.segment("これはテストです。");
println!("{}", tokens.join(" "));
// Output: これ は テスト です 。
Ok(())
}
ライブラリ クイックスタート(品詞推定)
品詞推定付きモデルを読み込み、単語分割と品詞推定を同時に行う例です:
use std::path::Path;
use litsea::language::Language;
use litsea::perceptron::AveragedPerceptron;
use litsea::segmenter::Segmenter;
fn main() -> litsea::Result<()> {
// POS モデルを読み込み
let mut pos_learner = AveragedPerceptron::new();
pos_learner.load_model_from_path(Path::new("./models/japanese_pos.model"))?;
// POS 対応 Segmenter を作成
let segmenter = Segmenter::with_pos_learner(Language::Japanese, pos_learner);
// 品詞推定付き分割
let tokens = segmenter.segment_with_pos("これはテストです。");
for (word, pos) in &tokens {
print!("{}/{} ", word, pos);
}
println!();
Ok(())
}
次のステップ
- CLI リファレンス – すべての CLI コマンドとオプションの詳細
- 学習ガイド – 独自モデルの学習方法
- アーキテクチャ – Litsea の内部動作の理解
アーキテクチャ概要
Litsea は、コンパクトで辞書不要の単語分割システムとして設計されています。単語分割を二値分類問題として扱い、文字レベルの特徴量から単語境界パターンを学習するために AdaBoost を使用します。
高レベルデータフロー
Litsea には学習と分割の 2 つの主要なワークフローがあります。
学習パイプライン
flowchart LR
A["Corpus (text)"] --> B["Extractor"]
B --> C["Features File (.txt)"]
C --> D["Trainer (AdaBoost)"]
D --> E["Model File (.model)"]
- コーパスの準備 – 単語をスペースで区切ったテキストを準備
- 特徴量抽出 –
Extractorがコーパスを読み込み、文字を種別に分類し、ラベル付き特徴量ベクトルを出力 - モデル学習 –
Trainerが特徴量を AdaBoost に入力し、最も情報量の多い特徴量を反復的に選択してコンパクトなモデルを生成
分割パイプライン
flowchart LR
F["Raw text"] --> G["Segmenter (AdaBoost)"]
H["Model file"] --> G
G --> I["Segmented words"]
- モデル読み込み – 学習済みモデルを読み込み(ファイルまたは URL から)
- 文字分類 – 入力の各文字について、言語固有のパターンに基づいて文字種コードを決定
- 特徴量抽出 – スライディングウィンドウを使用して各文字位置の特徴量セットを構築
- 予測 – AdaBoost が各位置が単語境界かどうかを予測
設計原則
- 辞書不要 – MeCab や Lindera とは異なり、Litsea は文字パターンから学習した統計モデルのみに依存
- コンパクトなモデル – モデルファイルは通常 1-22 KB で、重要な特徴量の重みのみを含む
- 言語非依存のフレームワーク – コアアルゴリズムはすべての言語で共通であり、文字種パターンのみが異なる
- 簡単な拡張性 – 新しい言語の追加には、文字種パターンの定義とモデルの学習のみが必要
ワークスペース構成
Litsea は 2 つのクレートとサポートディレクトリで構成される Cargo ワークスペースとして組織されています。
ディレクトリ構成
litsea/
├── Cargo.toml # Workspace manifest
├── Cargo.lock # Dependency lock file
├── Makefile # Build convenience targets
├── rustfmt.toml # Rust formatting configuration
├── LICENSE # MIT
├── README.md # Project overview
├── litsea/ # Core library crate
│ ├── Cargo.toml
│ ├── src/
│ │ ├── lib.rs # Module declarations and version
│ │ ├── adaboost.rs # AdaBoost algorithm
│ │ ├── segmenter.rs # Word segmentation
│ │ ├── extractor.rs # Feature extraction from corpus
│ │ ├── trainer.rs # Training orchestration
│ │ ├── language.rs # Language definitions and char patterns
│ │ └── util.rs # URI scheme utilities
│ └── benches/
│ └── bench.rs # Criterion benchmarks
├── litsea-cli/ # CLI binary crate
│ ├── Cargo.toml
│ └── src/
│ └── main.rs # CLI entry point
├── models/ # Pre-trained models
│ ├── japanese.model
│ ├── chinese.model
│ ├── korean.model
│ ├── RWCP.model
│ └── JEITA_Genpaku_ChaSen_IPAdic.model
├── resources/ # Sample data and test fixtures
│ └── bocchan.txt # Sample corpus
├── scripts/ # Corpus preparation utilities
│ ├── download_udtreebank.sh # Download UD Treebanks and output CoNLL-U file path
│ └── corpus_udtreebank.sh # Convert CoNLL-U files to Litsea corpus format
├── docs/ # mdbook documentation (this book)
└── .github/
└── workflows/ # CI/CD pipelines
├── regression.yml # Test on push/PR
├── release.yml # Release builds and publishing
└── periodic.yml # Weekly stability tests
クレートの詳細
litsea(コアライブラリ)
コアライブラリは、分割、学習、モデル I/O のすべての機能を提供します。
| Dependency | Version | 用途 |
|---|---|---|
thiserror | 2.0 | エラー型の導出 |
reqwest | 0.13 | HTTP/HTTPS モデル読み込み(rustls) |
tokio | 1.49 | リモートモデル読み込み用非同期ランタイム |
criterion | 0.8 | ベンチマーク(開発依存) |
tempfile | 3.25 | テスト用一時ファイル(開発依存) |
litsea-cli(CLI バイナリ)
CLI は Litsea の機能へのコマンドラインインターフェースを提供します。
| Dependency | Version | 用途 |
|---|---|---|
clap | 4.5 | コマンドライン引数の解析 |
ctrlc | 3.5 | 学習中の Ctrl+C のグレースフルハンドリング |
tokio | 1.49 | 非同期ランタイム |
litsea | 0.4 | コアライブラリ(ワークスペースメンバー) |
ワークスペース設定
ワークスペースは Cargo resolver バージョン 3(Rust Edition 2024)を使用します:
[workspace]
resolver = "3"
members = ["litsea", "litsea-cli"]
[workspace.package]
version = "0.4.0"
edition = "2024"
rust-version = "1.87"
共有依存関係はワークスペースレベルの [workspace.dependencies] で定義され、各クレートから { workspace = true } で参照されます。
モジュール設計
litsea ライブラリクレートは、それぞれ明確な責務を持つモジュールで構成されています。
モジュール依存関係グラフ
graph TD
language["language.rs<br/>文字種分類"]
segmenter["segmenter.rs<br/>分割 + 品詞付与"]
adaboost["adaboost.rs<br/>AdaBoost(境界判定)"]
perceptron["perceptron.rs<br/>Averaged Perceptron(品詞)"]
upos["upos.rs<br/>UPOSタグとラベル"]
extractor["extractor.rs<br/>特徴量抽出"]
trainer["trainer.rs<br/>学習オーケストレーション"]
model_io["model_io.rs(非公開)<br/>モデルURI読み込み"]
error["error.rs<br/>LitseaError / Result"]
metrics["metrics.rs<br/>評価指標"]
language --> segmenter
upos --> segmenter
adaboost --> segmenter
perceptron --> segmenter
segmenter --> extractor
adaboost --> trainer
perceptron --> trainer
model_io --> adaboost
model_io --> perceptron
error --> adaboost
error --> perceptron
metrics --> trainer
モジュール詳細
language.rs – 言語定義
Language enum と文字種分類を定義します。
Language–Japanese・Chinese・Koreanのバリアントを持つ enumFromStrを実装("japanese"・"ja"・"chinese"・"zh"・"korean"・"ko"をパース)Displayを実装(小文字名を出力)char_type(c: char) -> &'static str– 文字範囲に対するmatchで文字を直接分類(アロケーションなし・正規表現不使用)。言語別関数(japanese_char_typeなど)が、共通の"P"/"A"/"N"クラス用のpunct_latin_digit()ヘルパーを共有します。
segmenter.rs – 単語分割と品詞付与
主要なユーザー向けモジュールです。
Segmenter–Language、AdaBoost学習器、オプションのAveragedPerceptron品詞学習器を保持(フィールドは非公開。language()・learner()・learner_mut()・pos_learner()・pos_learner_mut()を使用)new(language, learner)– 学習済みモデル(任意)付きでセグメンターを作成with_pos_learner(language, pos_learner)– 分割+品詞付与用のセグメンターを作成segment(sentence)– テキストを単語に分割しVec<String>を返すsegment_with_pos(sentence)– 分割と品詞付与を行いVec<(String, Upos)>を返すchar_type(ch)– 1文字を種別コードに分類add_corpus(corpus)/add_corpus_with_pos(corpus)– 学習データを追加add_corpus_with_writer(corpus, callback)/add_corpus_with_pos_writer(corpus, callback)– カスタムコールバックでコーパスを処理
adaboost.rs – AdaBoost アルゴリズム
単語境界の判定に使う二値分類器です。
AdaBoostnew(threshold, num_iterations)– 学習パラメータを指定して作成initialize_features(path)/initialize_instances(path)– 学習データを読み込みtrain(running)– AdaBoost の学習ループを実行predict(&attributes)– 境界(+1)か非境界(-1)かを予測load_model(uri)(async)/load_model_from_path(path)/load_model_from_reader(reader)– モデルの読み込みsave_model(path)– モデルをファイルに保存metrics()– 正解率・適合率・再現率を計算(BinaryMetrics)bias()– モデルのバイアス項を取得
perceptron.rs – Averaged Perceptron
分割+品詞付与に使う多クラス分類器です。
AveragedPerceptronadd_instance(features, label)– 学習インスタンスを追加train(num_epochs, running)– 重み平均化付きで学習predict(&features)– 最良クラスのラベルを予測load_model(uri)(async)/load_model_from_path(path)/load_model_from_reader(reader)– モデルの読み込みsave_model(path)– モデルを保存metrics()– マクロ平均の評価指標(MulticlassMetrics)
- 重みは高速な推論のため「特徴 → クラス別ベクトル」レイアウトで保持します。
upos.rs – Universal POS タグ
Upos– Universal Dependencies の17品詞タグ(NOUN、VERB、…)SegmentLabel– 文字位置ごとの分割+品詞の複合ラベル(B(Upos)またはO)。"B-NOUN"/"O"文字列形式のDisplay/FromStrを実装
extractor.rs – 特徴量抽出
モデル学習用にコーパスから特徴量を抽出します。
Extractor–Segmenterをラップしてコーパスファイルを処理new(language)– 言語を指定して作成extract(corpus_path, features_path)– コーパスを読み、特徴量ファイルを書き出すextract_with_pos(corpus_path, features_path)– 品詞付きコーパス版
trainer.rs – 学習オーケストレーション
高レベルの学習ワークフローです。
Trainer– 分割モデルの学習(AdaBoost)new(threshold, num_iterations, features_path)– 特徴量ファイルから初期化load_model(uri)– 増分学習用に既存モデルを読み込み(async・任意)train(running, model_path)– 学習・保存してBinaryMetricsを返す
PosTrainer– 品詞モデルの学習(Averaged Perceptron)new(num_epochs, features_path)/load_model(uri)/train(running, model_path)(MulticlassMetricsを返す)
error.rs – エラー処理
LitseaError– エラー enum(Io・InvalidData・InvalidInput・Unsupported、remote_modelフィーチャー時はDownloadも)Result<T>– すべての失敗しうるAPIが使うエイリアス
metrics.rs – 評価指標
BinaryMetrics– 正解率・適合率・再現率・混同行列(AdaBoost)MulticlassMetrics– 正解率とマクロ平均適合率/再現率(Averaged Perceptron)
model_io.rs – モデル読み込みI/O(非公開)
モデルURI(プレーンパス、file://、remote_model フィーチャー時の http(s)://)を解決して生のモデルバイト列を返す内部モジュールです。公開APIには含まれません。
公開エクスポート
ライブラリの lib.rs は公開モジュールと主要型の再エクスポートを提供します:
#![allow(unused)]
fn main() {
pub mod adaboost;
pub mod error;
pub mod extractor;
pub mod language;
pub mod metrics;
mod model_io;
pub mod perceptron;
pub mod segmenter;
pub mod trainer;
pub mod upos;
pub use adaboost::AdaBoost;
pub use error::{LitseaError, Result};
pub use extractor::Extractor;
pub use language::Language;
pub use metrics::{BinaryMetrics, MulticlassMetrics};
pub use perceptron::AveragedPerceptron;
pub use segmenter::Segmenter;
pub use trainer::{PosTrainer, Trainer};
pub use upos::{SegmentLabel, Upos};
pub fn version() -> &'static str { ... }
}
AdaBoost 二値分類
Litsea は、単語境界を判定するために AdaBoost(Adaptive Boosting)アルゴリズムによる二値分類を使用します。本章では、Litsea に実装されているアルゴリズムについて説明します。
概要
AdaBoost は、多数の弱学習器(単純な分類器)を組み合わせて強力なアンサンブル分類器を構築します。Litsea では:
- 正ラベル(+1) = 単語境界
- 負ラベル(-1) = 非境界(現在の単語の継続)
- 弱学習器 = 個々の特徴量(各特徴量は二値の「切り株」– 存在するか否か)
学習アルゴリズム
AdaBoost::train() の学習ループは以下のように動作します:
初期化
- 学習ファイルから特徴量とインスタンスを読み込み
- インスタンスの重みを均一に初期化(後に初期スコアに基づいて調整)
- すべてのモデルの重みをゼロで初期化
反復ブースティング
各イテレーション t(最大 num_iterations 回)について:
ステップ 1: 重み付き誤差の計算
各特徴量 h について、全インスタンスに対する重み付き誤差を計算します:
error[h] -= D[i] * y[i] (for each instance i that has feature h)
ここで D[i] はインスタンスの重み、y[i] は真のラベルです。
ステップ 2: 最良の弱学習器の選択
重み付き誤差率が最も低い特徴量を選択します:
error_rate(h) = (error[h] + positive_weight_sum) / instance_weight_sum
h_best = argmax_h |0.5 - error_rate(h)|
基準となる競合対象は「全て負」分類器(常に -1 を予測)であり、その誤差率は正のインスタンスの割合に等しくなります。実際の特徴量はこの基準を上回る必要があります。
ステップ 3: 収束判定
|0.5 - best_error_rate| < threshold の場合、早期停止します – どの特徴量もモデルを大幅に改善できないためです。
ステップ 4: 弱学習器の重みの計算
alpha = 0.5 * ln((1 - error_rate) / error_rate)
model[h_best] += alpha
誤差率が低いほど alpha が高くなり、より良い特徴量により大きな影響力を与えます。
ステップ 5: インスタンスの重みの更新
For each instance i:
prediction = +1 if h_best in features(i), else -1
if y[i] * prediction < 0: (misclassified)
D[i] *= exp(alpha) (increase weight)
else: (correctly classified)
D[i] /= exp(alpha) (decrease weight)
Normalize: D[i] /= sum(D)
これにより、後続のイテレーションは分類が困難なインスタンスに集中するようになります。
予測
入力された特徴量(属性)のセットに対して、予測は以下のように行われます:
score = bias + sum(model[feature] for each feature in attributes)
prediction = +1 if score >= 0, else -1
バイアス項
バイアスは以下のように計算されます:
bias = -sum(all model weights) / 2.0
これにより決定境界が中心化されます。空文字列特徴量("")は学習中のバイアスバケットとして機能します。
モデルファイル形式
学習されたモデルはシンプルなテキストファイルとして保存されます:
feature1\tweight1
feature2\tweight2
...
bias_value
- 各行には特徴量名とその重み(タブ区切り)が含まれる
- 重みがゼロの特徴量は省略される
- 最終行にはバイアス項(単一の数値)が含まれる
詳細はモデルファイル形式を参照してください。
ハイパーパラメータ
| Parameter | Default | 説明 |
|---|---|---|
threshold | 0.01 | 早期停止の閾値。低い値はより多くのイテレーションを許可し、精度が向上する可能性がある |
num_iterations | 100 | ブースティングの最大ラウンド数。高い値は学習時間とモデルサイズを犠牲にして精度を向上させる可能性がある |
Averaged Perceptron
Litsea は、単語分割と品詞推定を同時に行うために Averaged Perceptron アルゴリズムによる多クラス分類を使用します。本章では、Litsea に実装されているアルゴリズムについて説明します。
概要
AdaBoost が二値分類(境界 / 非境界)を行うのに対し、Averaged Perceptron は 18 クラスの多クラス分類を行い、各文字位置に対してセグメントラベルを予測します:
- 17 個の境界ラベル:
B-ADJ,B-ADP,B-ADV,B-AUX,B-CCONJ,B-DET,B-INTJ,B-NOUN,B-NUM,B-PART,B-PRON,B-PROPN,B-PUNCT,B-SCONJ,B-SYM,B-VERB,B-X - 1 個の非境界ラベル:
O(単語の継続)
これらのラベルは Universal Dependencies プロジェクトの 17 個の Universal POS (UPOS) タグに対応し、B- プレフィックスで単語境界を示します。これにより、単語境界の検出と品詞の推定を 1 つの分類ステップで同時に行えます。
アルゴリズム
重み表現
各クラスは独立した重みベクトルを持ちます。重みは疎なマップとして格納されます:
weights: HashMap<Feature, HashMap<Class, f64>>
例:
weights["UW4:猫"]["B-NOUN"] = 2.5
weights["UC4:H"]["B-NOUN"] = 1.8
weights["UW4:猫"]["O"] = -0.3
...
特徴量の集合に対し、各クラスのスコアは特徴量の重みを合算して計算し、最大スコアのクラスを予測ラベルとします:
score(class) = sum(weights[feature][class] for feature in input_features)
prediction = argmax(score(class) for all classes)
更新規則
各学習インスタンスについて、予測が正解と異なる場合に重みを更新します:
For each training instance (features, truth):
guess = predict(features)
if guess != truth:
For each feature f in features:
weights[f][truth] += 1.0 # 正解クラスの重みを増加
weights[f][guess] -= 1.0 # 誤予測クラスの重みを減少
この単純な更新規則により、正解クラスの特徴量が強化され、誤予測クラスの特徴量が弱められます。これにより、将来の類似入力に対して正しい予測がより起こりやすくなります。
平均化
基本的なパーセプトロンに対する重要な改善点が重みの平均化です。最終的な重みは学習データの末尾に過適合する傾向があるため、学習中に観測されたすべての重みベクトルの平均を最終モデルとして使用します。これにより、未知データへの汎化性能が向上します。
実装では効率のために累積和アプローチを使用します:
# 各ステップの重みを累積
cumulative[feature][class] += weights[feature][class] * elapsed_steps
# 学習終了時に平均化
averaged[feature][class] = cumulative[feature][class] / total_steps
これにより、すべての中間重みベクトルを保存することなく同じ結果が得られます。この平均化により、学習データの順序への依存が軽減され、汎化性能が向上します。
エポックによる学習
学習は指定されたエポック数だけ学習データを繰り返します。各エポックでは、すべての学習インスタンスを順に処理します:
For each epoch (1 to num_epochs):
For each instance in training data:
features = extract_features(instance)
predicted = argmax(score(class) for all classes)
if predicted != correct_label:
update weights
accumulate weights for averaging
AtomicBool フラグにより、Ctrl+C などで学習を中断し、その時点でのモデルを保存することも可能です。
#![allow(unused)]
fn main() {
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use litsea::perceptron::AveragedPerceptron;
let mut perceptron = AveragedPerceptron::new();
// ... インスタンスを追加 ...
let running = Arc::new(AtomicBool::new(true));
perceptron.train(10, running); // 10エポック
}
モデルファイル形式
学習されたモデルはテキストファイルとして保存されます:
18
B-ADJ
B-ADP
B-ADV
...
O
UW4:猫 B-NOUN 2.5
UC4:H B-NOUN 1.8
UW4:猫 O -0.3
...
- 1行目: クラス数(18)
- 続くN行: クラス名(1行に1つ)
- 残りの行: 特徴量の重み、タブ区切り
特徴名\tクラス名\t重み - 重みがゼロの特徴量は省略
AdaBoost との比較
| 項目 | AdaBoost | Averaged Perceptron |
|---|---|---|
| 分類方式 | 二値分類(+1 / -1) | 多クラス分類(18クラス) |
| 出力 | 単語境界のみ | 単語境界 + 品詞タグ |
| 弱学習器 | 特徴量の決定株 | なし(線形分類器) |
| 重みの管理 | 特徴量ごとに1つの重み | クラス×特徴量の重み行列 |
| 汎化手法 | アンサンブル | 重みの平均化 |
| 学習方式 | サンプル再重み付けによる反復ブースティング | 重み平均化によるオンライン学習 |
| モデルサイズ | 数 KB | 約 11 MB(品詞特徴量含む) |
| ハイパーパラメータ | threshold, num_iterations | num_epochs |
ハイパーパラメータ
| パラメータ | デフォルト値 | 説明 |
|---|---|---|
num_epochs | 10 | 学習エポック数。高い値は精度を向上させる可能性があるが、過学習のリスクがある |
特徴量抽出
Litsea は、各単語境界候補の周辺の局所的なコンテキストを捉えるために文字 n-gram 特徴量を使用します。本章ではすべての特徴量タイプをカタログ化します。
特徴量カテゴリ
入力の各文字位置 i について、セグメンタは文字、その種別コード、および前回の境界判定からなるスライディングウィンドウから特徴量を抽出します。
基本特徴量(38 個)
| Category | IDs | 説明 | Window |
|---|---|---|---|
| UW (Unary Word) | UW1–UW6 | 位置 i-3 から i+2 の個々の文字 | 6 |
| BW (Bigram Word) | BW1–BW3 | 隣接する文字ペア | 3 |
| UC (Unary Char-type) | UC1–UC6 | 位置 i-3 から i+2 の文字種コード | 6 |
| BC (Bigram Char-type) | BC1–BC3 | 隣接する種別コードペア | 3 |
| TC (Trigram Char-type) | TC1–TC4 | 種別コードのトリプル | 4 |
| UP (Unary Previous-tag) | UP1–UP3 | 直前 3 つの境界判定 | 3 |
| BP (Bigram Previous-tag) | BP1–BP2 | 境界判定のペア | 2 |
| UQ (Unary tag+type) | UQ1–UQ3 | 境界判定と種別コードの組み合わせ | 3 |
| BQ (Bigram tag+type) | BQ1–BQ4 | 判定と種別コードのバイグラム組み合わせ | 4 |
| TQ (Trigram tag+type) | TQ1–TQ4 | 判定と種別コードのトライグラム組み合わせ | 4 |
言語固有の特徴量(4 個、日本語と中国語のみ)
| Category | IDs | 説明 | Count |
|---|---|---|---|
| WC (Word+Char-type) | WC1–WC4 | 文字と種別コードの混合特徴量 | 4 |
WC1: 位置 i-1 の文字 + 位置 i の種別コードWC2: 位置 i-1 の種別コード + 位置 i の文字WC3: 位置 i-1 の文字 + 位置 i-1 の種別コードWC4: 位置 i の文字 + 位置 i の種別コード
韓国語に WC がない理由: 韓国語のハングル音節は 2 種類(SN と SF)にのみ分類されるため、WC 特徴量は有用な信号ではなくノイズを追加してしまいます。
特徴量の総数
| Language | Base | WC | Total |
|---|---|---|---|
| Japanese | 38 | 4 | 42 |
| Chinese | 38 | 4 | 42 |
| Korean | 38 | 0 | 38 |
特徴量の形式
各特徴量は PREFIX:VALUE の形式の文字列として表現されます:
UW4:は ← The character at position i is "は"
UC4:I ← The type code at position i is "I" (Hiragana)
BW2:はテ ← The bigram at position i-1..i is "はテ"
BC2:IK ← The type bigram is Hiragana + Katakana
UP3:B ← The previous boundary decision was "B" (boundary)
WC1:はK ← Character "は" combined with type "K"
スライディングウィンドウの配置
セグメンタは入力をセンチネル文字でパディングします:
Index: 0 1 2 3 4 5 ... n+2 n+3 n+4 n+5
Chars: B3 B2 B1 c1 c2 c3 ... cn E1 E2 E3
Types: O O O t1 t2 t3 ... tn O O O
Tags: U U U U ? ? ... ?
- B3, B2, B1 – 開始センチネル(パディング)
- E1, E2, E3 – 終了センチネル(パディング)
- O – パディング位置の「Other」種別
- U – 初期位置の「Unknown」タグ
- B – 「Boundary」タグ(単語の開始)
- O – 「Other」タグ(継続)
特徴量は位置 4 から len-3 まで抽出され、i-3 から i+2 の完全なウィンドウが利用可能です。
学習データの形式
extract コマンドは以下の形式で特徴量をファイルに書き出します:
1 UW1:B2 UW2:B1 UW3:L UW4:i UW5:t UC1:O UC2:O UC3:A UC4:A ...
-1 UW1:B1 UW2:L UW3:i UW4:t UW5:s UC1:O UC2:A UC3:A UC4:A ...
各行には以下が含まれます:
- ラベル(境界の場合
1、非境界の場合-1) - タブ区切りの特徴量文字列
文字種分類
Litsea の各言語は、個々の文字を言語学的に意味のあるカテゴリに分類する文字種クラスのセットを定義します。これらの種別コードは AdaBoost 分類器の特徴量として使用されます。
仕組み
Language::char_type(c: char) -> &'static str は、Unicode 文字範囲に対する match 式で文字を直接分類します(正規表現・アロケーションなし)。matchアームは上から順に評価され、最初にマッチしたアームが種別コードを決定します。どのアームにもマッチしない場合、その文字は "O"(Other)に分類されます。
言語ごとに分類関数(japanese_char_type・chinese_char_type・korean_char_type)があり、全言語で共通のクラス "P"(句読点)・"A"(ラテン文字)・"N"(数字)は、言語固有クラスの後に評価される共有ヘルパー punct_latin_digit() にまとめられています。単純な範囲を超えるロジックはmatchガードで表現します(例: 韓国語のハングル音節構造)。
日本語の文字種
| Code | 名称 | パターン / 範囲 | 例 |
|---|---|---|---|
| M | 漢数字 | [一二三四五六七八九十百千万億兆] | 一, 千, 億 |
| H | 漢字 / CJK 統合漢字 | [一-龠々〆ヵヶ] | 漢, 字, 学 |
| I | ひらがな | [ぁ-ん] | あ, い, う |
| K | カタカナ | [ァ-ヴーア-ン゙゚] | ア, カ, ー |
| P | 句読点 | CJK 記号(U+3000-303F)、全角(U+FF01-FF65) | 。, 、, 「 |
| A | ASCII / ラテン文字 | [a-zA-Za-zA-Z] | A, z, B |
| N | 数字 | [0-90-9] | 0, 5 |
| O | その他 | フォールバック | @, # |
注意: “M”(漢数字)は “H”(一般漢字)よりも先にチェックされるため、一や百などの文字は一般的な漢字ではなく数字として分類されます。
中国語の文字種
| Code | 名称 | パターン / 範囲 | 例 |
|---|---|---|---|
| F | 機能語 | 高頻度の文法語 | 的, 了, 在, 是 |
| C | CJK 統合漢字 | U+4E00–U+9FFF | 中, 国, 人 |
| X | CJK 拡張 A | U+3400–U+4DBF | 希少な文字 |
| R | CJK 部首 | U+2E80–U+2FDF | 康熙部首 |
| P | 句読点 | CJK 記号 + 全角 | 。, ,, 《 |
| B | 注音符号 | U+3100–U+312F, U+31A0–U+31BF | 注音記号 |
| A | ASCII / ラテン文字 | [a-zA-Za-zA-Z] | A, z |
| N | 数字 | [0-90-9] | 0, 5 |
| O | その他 | フォールバック | @, # |
中国語の機能語には以下が含まれます:
- 構造助詞: 的, 地, 得
- アスペクト / モーダル助詞: 了, 着, 过, 吗, 呢, 吧, 啊, 嘛
- 接続詞: 和, 与, 或, 但, 而, 且, 及
- 前置詞: 在, 从, 到, 把, 被, 对, 向, 给
- よく使われる文法動詞 / 副詞: 是, 有, 不, 也, 都, 就, 要, 会, 能, 可
韓国語の文字種
| Code | 名称 | パターン / 範囲 | 例 |
|---|---|---|---|
| E | 助詞 / 語尾 | 高頻度の文法助詞 | 은, 는, 을, 를, 의, 에 |
| SN | ハングル(パッチムなし) | 終声のないハングル音節 | 가, 나, 하 |
| SF | ハングル(パッチムあり) | 終声のあるハングル音節 | 한, 글, 각 |
| J | ハングル字母 | U+1100–U+11FF | 個別の子音 / 母音 |
| G | 互換字母 | U+3130–U+318F | ㄱ, ㅏ, ㅎ |
| H | 漢字 | U+4E00–U+9FFF | CJK 統合漢字 |
| P | 句読点 | CJK 記号 + 全角 | 。, , |
| A | ASCII / ラテン文字 | [a-zA-Za-zA-Z] | A, z |
| N | 数字 | [0-90-9] | 0, 5 |
| O | その他 | フォールバック | @, # |
韓国語ハングル音節の検出
韓国語では SN と SF の種別にmatchガードを使用します。これは Unicode の体系的なハングルエンコーディングを活用しています:
- ハングル音節は U+AC00–U+D7AF を占有
- 各音節は
(初声 * 21 + 中声) * 28 + 終声 + 0xAC00としてエンコード (codepoint - 0xAC00) % 28 == 0の場合、音節に終声がない(SN)- それ以外の場合、終声がある(SF、「받침」)
この区別は、終声(パッチム/받침)の有無が韓国語の単語境界パターンと助詞の接続に影響するため重要です。
言語間の比較
| Feature | Japanese | Chinese | Korean |
|---|---|---|---|
| 種別の総数 | 8 | 9 | 10 |
| 固有の種別 | M, H, I, K | F, C, X, R, B | E, SN, SF, J, G |
| 共有する種別 | P, A, N, O | P, A, N, O | P, A, N, O(H は日本語と共通) |
| マッチング方法 | 範囲match | 範囲match | 範囲match + ガード |
| WC 特徴量の使用 | あり | あり | なし |
予測パイプライン
本章では、Segmenter::segment() が入力テキストを処理する手順をステップごとに解説します。
例: “これはテストです。” の分割
ステップ 1: パディングで配列を初期化
chars: ["B3", "B2", "B1"]
types: ["O", "O", "O" ]
tags: ["U", "U", "U", "U"]
tags 配列には “U” が 1 つ余分に追加されます。これは tags[3] が最初の実際の文字のタグを表し、先行する境界判定がないため “Unknown” に設定されるためです。
ステップ 2: 入力文字のスキャン
入力の各文字について、言語固有のパターンを使用して種別を決定し、配列に追加します:
chars: ["B3","B2","B1", "こ","れ","は","テ","ス","ト","で","す","。"]
types: ["O", "O", "O", "I", "I", "I", "K", "K", "K", "I", "I", "P"]
ステップ 3: 終了センチネルの追加
chars: [..., "。", "E1", "E2", "E3"]
types: [..., "P", "O", "O", "O" ]
ステップ 4: 反復と予測
位置 i を 4 から len(chars) - 3 まで繰り返します:
i=4 (れ): Extract features → predict → label=-1 (O) → word="これ"
i=5 (は): Extract features → predict → label=+1 (B) → push "これ", word="は"
i=6 (テ): Extract features → predict → label=+1 (B) → push "は", word="テ"
i=7 (ス): Extract features → predict → label=-1 (O) → word="テス"
i=8 (ト): Extract features → predict → label=-1 (O) → word="テスト"
i=9 (で): Extract features → predict → label=+1 (B) → push "テスト", word="で"
i=10(す): Extract features → predict → label=-1 (O) → word="です"
i=11(。): Extract features → predict → label=+1 (B) → push "です", word="。"
ステップ 5: 最後の単語をプッシュ
残りの単語 “。” を結果に追加します。
結果
["これ", "は", "テスト", "です", "。"]
各位置での予測の仕組み
各位置 i で、セグメンタは以下を実行します:
-
特徴量の抽出 –
get_attributes(i, tags, chars, types)を呼び出し、38-42 個の特徴量からなるHashSet<String>を構築 -
スコアの計算 – AdaBoost 学習器がマッチするすべての特徴量のモデル重みとバイアスを合計:
score = bias + sum(model[feature] for feature in attributes) -
判定 –
score >= 0の場合、その文字は新しい単語を開始(境界)、そうでなければ現在の単語を継続 -
タグの更新 – tags 配列に “B” または “O” をプッシュし、後続の位置での特徴量抽出に影響を与える
学習と予測の比較
| 観点 | 学習(process_corpus) | 予測(segment) |
|---|---|---|
| タグの情報源 | アノテーション済みコーパスから事前計算 | モデルにより動的に生成 |
| 最初のタグ | “U”(位置 3 の “B” を上書き) | “U”(先行する判定なし) |
| ラベル | コーパスから既知(+1 または -1) | AdaBoost による予測 |
| 特徴量 | コールバックを通じてファイルに書き出し | predict() に直接渡される |
学習時は、タグが正解のコーパス分割から導出されるため、モデルは正しい境界判定から学習します。予測時は、タグがその場で生成されるため、各判定は過去のすべての予測に依存します – これは左から右への貪欲法(Left-to-right Greedy)アプローチです。
パフォーマンス特性
分割アルゴリズムは入力長に対して線形です:
- 各文字位置を 1 回ずつ訪問: O(n)
- 各位置での特徴量抽出: O(1)(固定数の特徴量)
- 各位置での予測: O(f)、f はアクティブな特徴量の数(約 38-42)
- 合計: O(n * f)、実質的に O(n)
言語サポート概要
Litseaは、Language 列挙型に基づく統一的なフレームワークを通じて、3つの言語の単語分割をサポートしています。
サポート言語
| Language | Enum Variant | CLI Values | Feature Count | Pre-trained Model Accuracy |
|---|---|---|---|---|
| 日本語 | Language::Japanese | japanese, ja | 42 | 94.15% |
| 中国語 | Language::Chinese | chinese, zh | 42 | 80.72% |
| 韓国語 | Language::Korean | korean, ko | 38 | 85.08% |
Language 列挙型
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Language {
#[default]
Japanese,
Chinese,
Korean,
}
}
- デフォルトは
Japanese FromStrを実装 – 完全な言語名またはISO 639-1コードからパース可能(大文字小文字を区別しない)Displayを実装 – 小文字の完全な言語名を出力
パース例
#![allow(unused)]
fn main() {
use litsea::language::Language;
let ja: Language = "japanese".parse().unwrap();
let zh: Language = "zh".parse().unwrap();
let ko: Language = "Korean".parse().unwrap(); // case-insensitive
let err = "french".parse::<Language>(); // Err(...)
}
言語間の違い
各言語は独自の文字タイプパターンを定義しており、文字をタイプコードに分類します。これらのタイプコードはAdaBoost分類器の特徴量として使用されます。
| Aspect | Japanese | Chinese | Korean |
|---|---|---|---|
| 文字タイプ数 | 8 (M, H, I, K, P, A, N, O) | 9 (F, C, X, R, P, B, A, N, O) | 10 (E, SN, SF, J, G, H, P, A, N, O) |
| WC特徴量 | あり(4個追加) | あり(4個追加) | なし |
| 総特徴量数 | 42 | 42 | 38 |
| マッチング方式 | 正規表現のみ | 正規表現のみ | 正規表現 + クロージャ |
韓国語の特徴量が少ない理由
韓国語のハングル音節は、SN(받침/終声なし)とSF(받침あり)の2種類にのみ分類されます。この二値的な区別では、WC特徴量(単語+文字タイプの組み合わせ)は冗長な情報を生成し、識別力がほとんどありません。これらを除外することで、ノイズを低減し、モデルをコンパクトに保ちます。
日本語
日本語はLitseaのデフォルト言語です。
文字タイプ
| Code | Name | Pattern | Examples |
|---|---|---|---|
| M | 漢数字 | [一二三四五六七八九十百千万億兆] | 一, 三, 千, 億 |
| H | 漢字 / CJK | [一-龠々〆ヵヶ] | 漢, 字, 学, 々 |
| I | ひらがな | [ぁ-ん] | あ, い, う, を |
| K | カタカナ | [ァ-ヴーア-ン゙゚] | ア, カ, ー, ハ |
| P | 句読点 | CJK記号 + 全角 | 。, 、, 「, 」 |
| A | ASCII/ラテン文字 | [a-zA-Za-zA-Z] | A, z, B |
| N | 数字 | [0-90-9] | 0, 5, 5 |
| O | その他 | フォールバック | @, #, $ |
パターンの優先順位
パターンは順番に評価されます。特に注意すべき点は以下の通りです。
- MはHより先に評価: 一や百などの文字は、汎用的な「漢字」(H)ではなく「漢数字」(M)に分類されます
- この区別により、モデルは数字特有の境界パターンを学習できます
学習済みモデル
japanese.model
- 学習コーパス: UD Japanese-GSD
- 精度(Accuracy): 94.15%
- 適合率(Precision): 95.57%
- 再現率(Recall): 94.36%
RWCP.model
- 出典: オリジナルのTinySegmenterから抽出
- ライセンス: BSD 3-Clause(工藤拓)
- サイズ: 約22 KB
JEITA_Genpaku_ChaSen_IPAdic.model
- 学習コーパス: JEITA杉田玄白プロジェクトのコーパス
- トークナイザ: ChaSen(IPAdic辞書使用)
- サイズ: 約17 KB
使用例
echo "LitseaはTinySegmenterを参考に開発された、Rustで実装された極めてコンパクトな単語分割ソフトウェアです。" \
| litsea segment -l japanese ./models/japanese.model
出力:
Litsea は TinySegmenter を 参考 に 開発 さ れ た 、 Rust で 実装 さ れ た 極めて コンパクト な 単語 分割 ソフトウェア です 。
中国語
Litseaは簡体字・繁体字の両方を対象とした中国語の単語分割をサポートしています。
文字タイプ
| Code | Name | Pattern | Examples |
|---|---|---|---|
| F | 機能語 | 高頻度の文法語 | 的, 了, 在, 是, 和 |
| C | CJK統合漢字 | U+4E00–U+9FFF | 中, 国, 人 |
| X | CJK拡張A | U+3400–U+4DBF | 稀少文字 |
| R | CJK部首 | U+2E80–U+2FDF | 康熙部首 |
| P | 句読点 | CJK記号 + 全角 | 。, ,, 《, 》 |
| B | 注音符号 | U+3100–U+312F, U+31A0–U+31BF | 注音記号 |
| A | ASCII/ラテン文字 | [a-zA-Za-zA-Z] | A, z |
| N | 数字 | [0-90-9] | 0, 5, 5 |
| O | その他 | フォールバック | @, #, $ |
中国語の機能語(虚词)
「F」タイプは、分割において重要な高頻度の文法語を捉えます。
| Category | Characters |
|---|---|
| 構造助詞 | 的, 地, 得 |
| アスペクト・語気助詞 | 了, 着, 过, 吗, 呢, 吧, 啊, 嘛 |
| 接続詞 | 和, 与, 或, 但, 而, 且, 及 |
| 前置詞 | 在, 从, 到, 把, 被, 对, 向, 给 |
| 文法動詞・副詞 | 是, 有, 不, 也, 都, 就, 要, 会, 能, 可 |
これらの文字は圧倒的に文法的な役割で出現し、内容語とは異なる形で語境界を示します。
学習済みモデル
chinese.model
- 学習コーパス: UD Chinese-GSD
- 精度(Accuracy): 80.72%
使用例
echo "中文分词测试。" | litsea segment -l chinese ./models/chinese.model
韓国語
Litseaは、ハングル文字タイプの特殊な検出機能を備えた韓国語の単語分割をサポートしています。
文字タイプ
| Code | Name | Pattern | Examples |
|---|---|---|---|
| E | 助詞/語尾 | [은는을를의에] | 은, 는, 을, 를, 의, 에 |
| SN | ハングル(받침なし) | コードポイント演算 | 가, 나, 하, 모 |
| SF | ハングル(받침あり) | コードポイント演算 | 한, 글, 각, 붙 |
| J | ハングル字母 | U+1100–U+11FF | 個別の子音/母音 |
| G | 互換字母 | U+3130–U+318F | ㄱ, ㅏ, ㅎ |
| H | 漢字 | U+4E00–U+9FFF | CJK統合漢字 |
| P | 句読点 | CJK記号 + 全角 | 。, , |
| A | ASCII/ラテン文字 | [a-zA-Za-zA-Z] | A, z |
| N | 数字 | [0-90-9] | 0, 5, 5 |
| O | その他 | フォールバック | @, #, $ |
韓国語の助詞(조사)
「E」タイプは、6つの高頻度文法助詞を捉えます。
| Character | Role | Name |
|---|---|---|
| 은/는 | 主題マーカー | 주격 조사 |
| 을/를 | 目的語マーカー | 목적격 조사 |
| 의 | 所有格 | 관형격 조사 |
| 에 | 場所格 | 부사격 조사 |
これらの助詞は語境界に頻繁に出現するため、分割精度を向上させるために独自のタイプコードが割り当てられています。
ハングル音節構造(받침検出)
韓国語では、SNとSFタイプに正規表現ではなくクロージャベースのマッチングを使用します。これはUnicodeハングルエンコーディングの体系的な構造を活用しています。
- ハングル音節: U+AC00–U+D7AF(11,172音節)
- 各音節 =
(初声 * 21 + 中声) * 28 + 終声 + 0xAC00 - SN(받침なし):
(codepoint - 0xAC00) % 28 == 0 - SF(받침あり):
(codepoint - 0xAC00) % 28 != 0
받침(終声子音)の有無の区別は言語学的に重要であり、助詞が語にどのように接続するか、また境界がどこに生じるかに影響します。
WC特徴量なし
韓国語ではWC(単語+文字タイプ)特徴量を使用しません。ほとんどのハングル音節はSNとSFの2つのタイプにしか分類されないため、WC特徴量は低エントロピーでノイズの多い組み合わせを生成し、モデルの精度を低下させます。
学習済みモデル
korean.model
- 学習コーパス: UD Korean-GSD
- 精度(Accuracy): 85.08%
使用例
echo "한국어 단어 분할 테스트입니다." | litsea segment -l korean ./models/korean.model
新しい言語の追加
Litseaの多言語フレームワークは、容易に拡張できるよう設計されています。本ガイドでは、新しい言語のサポートを追加する方法を説明します。
手順の概要
Language列挙型にバリアントを追加DisplayおよびFromStrのmatchアームを実装- 文字タイプパターン関数を作成
- パターン関数を登録
- WC特徴量の有無を決定
- 学習コーパスを用意してモデルを学習
- テストを追加
手順1: Language にバリアントを追加
litsea/src/language.rs で、Language 列挙型に新しいバリアントを追加します。
#![allow(unused)]
fn main() {
pub enum Language {
#[default]
Japanese,
Chinese,
Korean,
Thai, // ← new language
}
}
手順2: Display と FromStr を実装
新しい言語のmatchアームを追加します。
#![allow(unused)]
fn main() {
// In Display impl
Language::Thai => write!(f, "thai"),
// In FromStr impl
"thai" | "th" => Ok(Language::Thai),
}
手順3: 文字タイプパターンを作成
新しい言語の文字を種別コードに分類する関数を定義します。分類は文字範囲に対する match 式で直接行います(正規表現は使いません)。最初にマッチしたアームが種別を決定します。
#![allow(unused)]
fn main() {
fn thai_char_type(c: char) -> &'static str {
match c {
// タイ文字の子音・順行母音 (U+0E01-U+0E3A)
'\u{0E01}'..='\u{0E3A}' => "T",
// タイ文字の母音・声調記号 (U+0E40-U+0E4E)
'\u{0E40}'..='\u{0E4E}' => "V",
// タイ数字 (U+0E50-U+0E59)
'\u{0E50}'..='\u{0E59}' => "N",
// 共通クラス: "P"(句読点)、"A"(ラテン文字)、"N"(数字)
_ => punct_latin_digit(c).unwrap_or("O"),
}
}
}
文字タイプ設計のヒント
- 語境界パターンと相関する言語学的に異なるカテゴリを特定する
- 順序は重要 – 最初にマッチしたものが優先されるため、より具体的なパターンを汎用的なパターンの前に配置する
- 中国語の「F」のように、高頻度の機能語を別のタイプとして検討する
- 単一の正規表現では表現できない複雑なロジックにはクロージャを使用する
手順4: パターン関数を登録
Language::char_type() にmatchアームを追加します。
#![allow(unused)]
fn main() {
pub fn char_type(&self, c: char) -> &'static str {
match self {
Language::Japanese => japanese_char_type(c),
Language::Chinese => chinese_char_type(c),
Language::Korean => korean_char_type(c),
Language::Thai => thai_char_type(c), // ← new
}
}
}
手順5: WC特徴量の有無を決定
segmenter.rs の get_attributes() では、WC特徴量を含めるかどうかを言語に基づいて match で判定しています。
#![allow(unused)]
fn main() {
match self.language {
Language::Japanese | Language::Chinese => {
// Include WC features
attrs.insert(format!("WC1:{}{}", w3, c4));
attrs.insert(format!("WC2:{}{}", c3, w4));
attrs.insert(format!("WC3:{}{}", w3, c3));
attrs.insert(format!("WC4:{}{}", w4, c4));
}
_ => {}
}
}
対象言語の文字タイプに十分な多様性があり、WC特徴量が有益である場合は、matchアームに追加してください。韓国語のSN/SFのようにタイプ体系が低エントロピーの場合は、WC特徴量を除外する方が適切です。
手順6: コーパスを用意してモデルを学習
-
コーパスを用意します(単語をスペースで区切った形式)。
word1 word2 word3 word4 -
特徴量を抽出します。
litsea extract -l thai ./corpus.txt ./features.txt -
モデルを学習します。
litsea train -t 0.005 -i 1000 ./features.txt ./models/thai.model
手順7: テストを追加
language.rs と segmenter.rs の両方にテストを追加します。
#![allow(unused)]
fn main() {
// In language.rs tests
#[test]
fn test_thai_patterns() {
let lang = Language::Thai;
assert_eq!(lang.char_type('ก'), "T"); // Thai consonant
assert_eq!(lang.char_type('A'), "A"); // ASCII
assert_eq!(lang.char_type('@'), "O"); // Other
}
// In segmenter.rs tests
#[test]
fn test_char_type_thai() {
let segmenter = Segmenter::new(Language::Thai, None);
assert_eq!(segmenter.char_type("ก"), "T");
}
}
全テストを実行して検証します。
cargo test --workspace
ライブラリ API 概要
litsea クレートは、単語分割、モデル学習、特徴量抽出のための Rust API を提供します。
インストール
[dependencies]
litsea = "0.5.0"
ローカルファイルからのモデル読み込みは同期 API(load_model_from_path)で行えるため、tokio などの非同期ランタイムは不要です。HTTP(S) からのリモートモデル取得など async API(load_model)を使う場合のみ、非同期ランタイムを追加してください。
モジュール構成
graph LR
A["litsea::segmenter"] --- B["Segmenter"]
C["litsea::adaboost"] --- D["AdaBoost"]
E["litsea::language"] --- F["Language"]
G["litsea::extractor"] --- H["Extractor"]
I["litsea::trainer"] --- J["Trainer, PosTrainer"]
K["litsea::error"] --- L["LitseaError, Result"]
M["litsea::perceptron"] --- N["AveragedPerceptron"]
O["litsea::upos"] --- P["Upos, SegmentLabel"]
Q["litsea::metrics"] --- R["BinaryMetrics, MulticlassMetrics"]
| モジュール | 主要な型 | 用途 |
|---|---|---|
litsea::segmenter | Segmenter | 単語分割、品詞推定付き分割 |
litsea::adaboost | AdaBoost | 二値分類、モデルの入出力 |
litsea::perceptron | AveragedPerceptron | 多クラス分類(品詞推定)、モデルの入出力 |
litsea::upos | Upos, SegmentLabel | UPOS 品詞タグ、セグメントラベル |
litsea::language | Language | 言語定義、文字分類 |
litsea::extractor | Extractor | コーパスからの特徴量抽出 |
litsea::trainer | Trainer, PosTrainer | 学習パイプラインの制御 |
litsea::error | LitseaError, Result | エラー型と Result エイリアス |
litsea::metrics | BinaryMetrics, MulticlassMetrics | 学習結果の評価指標 |
主要な型はすべてクレートルートから再エクスポートされているため、use litsea::{AdaBoost, Language, Segmenter}; のように短いパスで利用できます。
クイックスタート
use std::path::Path;
use litsea::{AdaBoost, Language, Segmenter};
fn main() -> litsea::Result<()> {
let mut learner = AdaBoost::new(0.01, 100);
learner.load_model_from_path(Path::new("./models/japanese.model"))?;
let segmenter = Segmenter::new(Language::Japanese, Some(learner));
let tokens = segmenter.segment("これはテストです。");
assert_eq!(tokens, vec!["これ", "は", "テスト", "です", "。"]);
Ok(())
}
クイックスタート(品詞推定)
use std::path::Path;
use litsea::language::Language;
use litsea::perceptron::AveragedPerceptron;
use litsea::segmenter::Segmenter;
fn main() -> litsea::Result<()> {
let mut pos_learner = AveragedPerceptron::new();
pos_learner.load_model_from_path(Path::new("./models/japanese_pos.model"))?;
let segmenter = Segmenter::with_pos_learner(Language::Japanese, pos_learner);
let tokens = segmenter.segment_with_pos("これはテストです。");
for (word, pos) in &tokens {
print!("{}/{} ", word, pos);
}
println!();
Ok(())
}
API ドキュメント
完全な API ドキュメントは docs.rs/litsea で参照できます。
Segmenter
Segmenter 構造体は、単語分割のための主要なインターフェースです。
定義
#![allow(unused)]
fn main() {
pub struct Segmenter {
// private: language: Language,
// private: learner: AdaBoost,
// private: pos_learner: Option<AveragedPerceptron>,
}
}
フィールドは非公開です。アクセサメソッド language()、learner()、learner_mut()、pos_learner()、pos_learner_mut() を使ってアクセスしてください。
コンストラクタ
Segmenter::new
#![allow(unused)]
fn main() {
pub fn new(language: Language, learner: Option<AdaBoost>) -> Self
}
新しい Segmenter を作成します。
language– 文字種分類に使用する言語learner– 学習済みのAdaBoostモデル(オプション)。Noneの場合、デフォルト(未学習)のインスタンスが作成されます。
#![allow(unused)]
fn main() {
use litsea::language::Language;
use litsea::segmenter::Segmenter;
// 学習済みモデルを使用する場合
let segmenter = Segmenter::new(Language::Japanese, Some(learner));
// モデルなし(学習や特徴量抽出用)
let segmenter = Segmenter::new(Language::Japanese, None);
}
メソッド
segment
#![allow(unused)]
fn main() {
pub fn segment(&self, sentence: &str) -> Vec<String>
}
文を単語に分割します。空の入力に対しては空のベクターを返します。
#![allow(unused)]
fn main() {
let tokens = segmenter.segment("これはテストです。");
// ["これ", "は", "テスト", "です", "。"]
}
char_type
#![allow(unused)]
fn main() {
pub fn char_type(&self, ch: &str) -> &str
}
言語固有のルールを使用して、1文字をその文字種コードに分類します。&str の先頭の文字が分類対象になります。空文字列の場合は "O" を返します。
#![allow(unused)]
fn main() {
let segmenter = Segmenter::new(Language::Japanese, None);
assert_eq!(segmenter.char_type("あ"), "I"); // ひらがな
assert_eq!(segmenter.char_type("漢"), "H"); // 漢字
assert_eq!(segmenter.char_type("A"), "A"); // ASCII
}
add_corpus
#![allow(unused)]
fn main() {
pub fn add_corpus(&mut self, corpus: &str)
}
スペース区切りのコーパスを処理し、内部の AdaBoost 学習器にインスタンスを追加します。
#![allow(unused)]
fn main() {
let mut segmenter = Segmenter::new(Language::Japanese, None);
segmenter.add_corpus("テスト です");
}
add_corpus_with_writer
#![allow(unused)]
fn main() {
pub fn add_corpus_with_writer<F>(&self, corpus: &str, writer: F)
where
F: FnMut(HashSet<String>, i8),
}
コーパスを処理し、各文字位置の特徴量セットとラベルをコールバックに渡します。
#![allow(unused)]
fn main() {
segmenter.add_corpus_with_writer("テスト です", |attrs, label| {
println!("Features: {:?}, Label: {}", attrs, label);
});
}
アクセサ
#![allow(unused)]
fn main() {
pub fn language(&self) -> Language
pub fn learner(&self) -> &AdaBoost
pub fn learner_mut(&mut self) -> &mut AdaBoost
pub fn pos_learner(&self) -> Option<&AveragedPerceptron>
pub fn pos_learner_mut(&mut self) -> Option<&mut AveragedPerceptron>
}
Segmenter の言語と内部の学習器へのアクセスを提供します。
文字位置ごとの特徴量抽出(韓国語では38個、日本語・中国語では42個の特徴量)は内部実装の詳細です。以前の
get_attributesメソッドは非公開になりました。
Extractor
Extractor 構造体は、モデル学習用にコーパスファイルから特徴量を抽出します。
定義
#![allow(unused)]
fn main() {
pub struct Extractor {
segmenter: Segmenter,
}
}
コンストラクタ
Extractor::new
#![allow(unused)]
fn main() {
pub fn new(language: Language) -> Self
}
指定した言語に対応する新しい Extractor を作成します。内部的に、学習済みモデルを持たない Segmenter を作成します。
#![allow(unused)]
fn main() {
use litsea::extractor::Extractor;
use litsea::language::Language;
let mut extractor = Extractor::new(Language::Japanese);
}
メソッド
extract
#![allow(unused)]
fn main() {
pub fn extract(
&mut self,
corpus_path: &Path,
features_path: &Path,
) -> litsea::Result<()>
}
コーパスファイル(スペース区切りの単語、1行1文)を読み込み、抽出した特徴量を出力ファイルに書き込みます。
#![allow(unused)]
fn main() {
use std::path::Path;
extractor.extract(
Path::new("./corpus.txt"),
Path::new("./features.txt"),
)?;
}
パイプライン
flowchart LR
A["corpus.txt<br/>(space-separated words)"] --> B["Extractor::extract()"]
B --> C["features.txt<br/>(label + features per position)"]
Extractor は以下の処理を行います:
- コーパスファイルから各行を読み込む
Segmenter::add_corpus_with_writer()を呼び出して各行を処理する- 各文字位置のラベルと特徴量セットを出力ファイルに書き込む
Trainer
Trainer 構造体は、モデル学習パイプライン全体を制御します。
定義
#![allow(unused)]
fn main() {
pub struct Trainer {
learner: AdaBoost,
}
}
コンストラクタ
Trainer::new
#![allow(unused)]
fn main() {
pub fn new(
threshold: f64,
num_iterations: usize,
features_path: &Path,
) -> litsea::Result<Self>
}
Trainer を作成し、特徴量ファイルから初期化します。内部で AdaBoost::initialize_features() と AdaBoost::initialize_instances() を呼び出します。
#![allow(unused)]
fn main() {
use std::path::Path;
use litsea::trainer::Trainer;
let mut trainer = Trainer::new(
0.005, // 閾値
1000, // 最大反復回数
Path::new("./features.txt"), // 特徴量ファイル
)?;
}
メソッド
load_model
#![allow(unused)]
fn main() {
pub async fn load_model(&mut self, uri: &str) -> litsea::Result<()>
}
再学習用に既存のモデルを読み込みます。ファイルパス、file://、および(remote_model フィーチャー有効時)http://、https:// URI に対応しています。
Trainer::new の後に呼び出すと、読み込んだ重みは特徴名をキーとして、初期化済みの学習データにマージされます。そのため、特徴量インデックスを壊すことなく、既存モデルから増分学習を開始できます。
#![allow(unused)]
fn main() {
trainer.load_model("./models/japanese.model").await?;
}
train
#![allow(unused)]
fn main() {
pub fn train(
&mut self,
running: Arc<AtomicBool>,
model_path: &Path,
) -> litsea::Result<BinaryMetrics>
}
モデルを学習し、指定したパスに保存します。評価メトリクスを返します。
running フラグにより、学習の途中停止が可能です。false に設定すると学習を早期終了します。
#![allow(unused)]
fn main() {
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::path::Path;
let running = Arc::new(AtomicBool::new(true));
let metrics = trainer.train(running, Path::new("./model.model"))?;
println!("Accuracy: {:.2}%", metrics.accuracy);
}
学習の完全な例
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::path::Path;
use litsea::trainer::Trainer;
#[tokio::main]
async fn main() -> litsea::Result<()> {
let mut trainer = Trainer::new(
0.005,
1000,
Path::new("./features.txt"),
)?;
// 必要に応じて既存モデルから再開
// trainer.load_model("./models/japanese.model").await?;
let running = Arc::new(AtomicBool::new(true));
let metrics = trainer.train(running, Path::new("./model.model"))?;
println!("Accuracy: {:.2}%", metrics.accuracy);
println!("Precision: {:.2}%", metrics.precision);
println!("Recall: {:.2}%", metrics.recall);
Ok(())
}
AdaBoost
AdaBoost 構造体は、単語境界検出のための二値分類を実装しています。
定義
#![allow(unused)]
fn main() {
pub struct AdaBoost {
pub threshold: f64,
pub num_iterations: usize,
// internal fields: model weights, features, instances, etc.
}
}
コンストラクタ
AdaBoost::new
#![allow(unused)]
fn main() {
pub fn new(threshold: f64, num_iterations: usize) -> Self
}
指定したハイパーパラメータで新しい AdaBoost インスタンスを作成します。
#![allow(unused)]
fn main() {
use litsea::adaboost::AdaBoost;
let mut learner = AdaBoost::new(0.01, 100);
}
モデルの読み込み
load_model_from_path
#![allow(unused)]
fn main() {
pub fn load_model_from_path(&mut self, path: &Path) -> litsea::Result<()>
}
ローカルファイルからモデルの重みを同期的に読み込みます。ローカルファイルにはこのメソッドが推奨されます – 非同期ランタイムは不要です。
#![allow(unused)]
fn main() {
use std::path::Path;
learner.load_model_from_path(Path::new("./models/japanese.model"))?;
}
load_model_from_reader
#![allow(unused)]
fn main() {
pub fn load_model_from_reader<R: BufRead>(&mut self, reader: R) -> litsea::Result<()>
}
メモリ上のバッファや既に開いているファイルなど、任意の BufRead ソースからモデルの重みを読み込みます。
load_model
#![allow(unused)]
fn main() {
pub async fn load_model(&mut self, uri: &str) -> litsea::Result<()>
}
URI からモデルの重みを読み込みます。以下の形式に対応しています:
- ローカルファイルパス:
./models/japanese.model - File URI:
file:///path/to/model - HTTP:
http://example.com/model(remote_modelフィーチャーが必要) - HTTPS:
https://example.com/model(remote_modelフィーチャーが必要)
#![allow(unused)]
fn main() {
learner.load_model("https://example.com/model").await?;
}
save_model
#![allow(unused)]
fn main() {
pub fn save_model(&self, filename: &Path) -> litsea::Result<()>
}
モデルの重みをファイルに保存します。モデルが空の場合はエラーを返します。
学習メソッド
initialize_features
#![allow(unused)]
fn main() {
pub fn initialize_features(&mut self, filename: &Path) -> litsea::Result<()>
}
特徴量ファイルを読み込み、特徴量インデックスを構築します。initialize_instances の前に呼び出す必要があります。
initialize_instances
#![allow(unused)]
fn main() {
pub fn initialize_instances(&mut self, filename: &Path) -> litsea::Result<()>
}
同じ特徴量ファイルを読み込み、ラベル付きインスタンスとその重みを初期化します。
train
#![allow(unused)]
fn main() {
pub fn train(&mut self, running: Arc<AtomicBool>)
}
AdaBoost の学習ループを実行します。running を false に設定すると早期終了します。
add_instance
#![allow(unused)]
fn main() {
pub fn add_instance(&mut self, attributes: HashSet<String>, label: i8)
}
特徴量セットとラベルを持つ単一の学習インスタンスを追加します。
予測
predict
#![allow(unused)]
fn main() {
pub fn predict(&self, attributes: &HashSet<String>) -> i8
}
与えられた特徴量セットに対してラベルを予測します。+1(境界)または -1(非境界)を返します。
#![allow(unused)]
fn main() {
use std::collections::HashSet;
let mut attrs = HashSet::new();
attrs.insert("UW4:は".to_string());
attrs.insert("UC4:I".to_string());
// ... その他の特徴量
let label = learner.predict(&attrs);
// label == 1 (境界) or -1 (非境界)
}
bias
#![allow(unused)]
fn main() {
pub fn bias(&self) -> f64
}
バイアス項を返します: -sum(all model weights) / 2.0
評価
metrics
#![allow(unused)]
fn main() {
pub fn metrics(&self) -> BinaryMetrics
}
学習データに対する評価メトリクスを算出します。
BinaryMetrics
litsea::metrics で定義されています(litsea::BinaryMetrics としても再エクスポートされます):
#![allow(unused)]
fn main() {
pub struct BinaryMetrics {
pub accuracy: f64, // 正解率(パーセント)
pub precision: f64, // 適合率(パーセント)
pub recall: f64, // 再現率(パーセント)
pub num_instances: usize,
pub true_positives: usize,
pub false_positives: usize,
pub false_negatives: usize,
pub true_negatives: usize,
}
}
Averaged Perceptron
AveragedPerceptron 構造体は、品詞推定のための多クラス分類を実装しています。
定義
#![allow(unused)]
fn main() {
pub struct AveragedPerceptron {
// internal fields: weights, accumulated, timestamps, step, classes, instances
}
}
コンストラクタ
AveragedPerceptron::new
#![allow(unused)]
fn main() {
pub fn new() -> Self
}
新しい Averaged Perceptron インスタンスを作成します。
#![allow(unused)]
fn main() {
use litsea::perceptron::AveragedPerceptron;
let mut learner = AveragedPerceptron::new();
}
インスタンスの追加
add_instance
#![allow(unused)]
fn main() {
pub fn add_instance(&mut self, features: HashSet<String>, label: String)
}
特徴量セットとラベルを持つ学習インスタンスを追加します。未知のクラスは自動的に登録されます。
#![allow(unused)]
fn main() {
use std::collections::HashSet;
use litsea::perceptron::AveragedPerceptron;
let mut learner = AveragedPerceptron::new();
let mut feats = HashSet::new();
feats.insert("UW4:猫".to_string());
feats.insert("UC4:H".to_string());
learner.add_instance(feats, "B-NOUN".to_string());
}
学習
train
#![allow(unused)]
fn main() {
pub fn train(&mut self, num_epochs: usize, running: Arc<AtomicBool>)
}
指定されたエポック数でモデルを学習します。running を false に設定すると早期終了します。学習終了時に重みの平均化が自動的に行われます。
#![allow(unused)]
fn main() {
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
let running = Arc::new(AtomicBool::new(true));
learner.train(10, running);
}
予測
predict
#![allow(unused)]
fn main() {
pub fn predict(&self, features: &HashSet<String>) -> String
}
与えられた特徴量セットに対してラベルを予測します。各クラスのスコアを計算し、最大スコアのクラス名を返します。クラスが未登録の場合は空文字列を返します。
#![allow(unused)]
fn main() {
use std::collections::HashSet;
let mut attrs = HashSet::new();
attrs.insert("UW4:猫".to_string());
attrs.insert("UC4:H".to_string());
let label = learner.predict(&attrs);
// label == "B-NOUN" など
}
モデルの入出力
save_model
#![allow(unused)]
fn main() {
pub fn save_model(&self, path: &Path) -> litsea::Result<()>
}
モデルをファイルに保存します。モデルが空の場合はエラーを返します。
load_model_from_path
#![allow(unused)]
fn main() {
pub fn load_model_from_path(&mut self, path: &Path) -> litsea::Result<()>
}
ローカルファイルからモデルの重みを同期的に読み込みます。ローカルファイルにはこのメソッドが推奨されます – 非同期ランタイムは不要です。
#![allow(unused)]
fn main() {
use std::path::Path;
learner.load_model_from_path(Path::new("./models/japanese_pos.model"))?;
}
load_model_from_reader
#![allow(unused)]
fn main() {
pub fn load_model_from_reader<R: BufRead>(&mut self, reader: R) -> litsea::Result<()>
}
メモリ上のバッファや既に開いているファイルなど、任意の BufRead ソースからモデルの重みを読み込みます。
load_model
#![allow(unused)]
fn main() {
pub async fn load_model(&mut self, uri: &str) -> litsea::Result<()>
}
URI からモデルを読み込みます。以下の形式に対応しています:
- ローカルファイルパス:
./models/japanese_pos.model - File URI:
file:///path/to/model - HTTP:
http://example.com/model(remote_modelフィーチャーが必要) - HTTPS:
https://example.com/model(remote_modelフィーチャーが必要)
#![allow(unused)]
fn main() {
learner.load_model("https://example.com/models/japanese_pos.model").await?;
}
評価
metrics
#![allow(unused)]
fn main() {
pub fn metrics(&self) -> MulticlassMetrics
}
学習データに対する評価メトリクスを算出します。
MulticlassMetrics
litsea::metrics で定義されています(litsea::MulticlassMetrics としても再エクスポートされます):
#![allow(unused)]
fn main() {
pub struct MulticlassMetrics {
pub accuracy: f64, // 正解率(パーセント)
pub macro_precision: f64, // マクロ平均適合率(パーセント)
pub macro_recall: f64, // マクロ平均再現率(パーセント)
pub num_instances: usize, // インスタンス数
pub correct_per_class: HashMap<String, usize>, // クラスごとの正解数
pub predicted_per_class: HashMap<String, usize>, // クラスごとの予測数
pub gold_per_class: HashMap<String, usize>, // クラスごとの正解ラベル数
}
}
UPOS
upos モジュールは、品詞タグ付けに使用する Universal POS (UPOS) タグセットと分割ラベル型を定義します。
Upos
定義
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Upos {
ADJ, // 形容詞 (Adjective)
ADP, // 接置詞 (Adposition)
ADV, // 副詞 (Adverb)
AUX, // 助動詞 (Auxiliary)
CCONJ, // 等位接続詞 (Coordinating conjunction)
DET, // 限定詞 (Determiner)
INTJ, // 間投詞 (Interjection)
NOUN, // 名詞 (Noun)
NUM, // 数詞 (Numeral)
PART, // 助詞・小辞 (Particle)
PRON, // 代名詞 (Pronoun)
PROPN, // 固有名詞 (Proper noun)
PUNCT, // 句読点 (Punctuation)
SCONJ, // 従属接続詞 (Subordinating conjunction)
SYM, // 記号 (Symbol)
VERB, // 動詞 (Verb)
X, // その他 (Other)
}
}
Litsea は Universal Dependencies プロジェクトの全 17 UPOS タグをサポートしています:
| タグ | 説明 | 例(日本語) |
|---|---|---|
ADJ | 形容詞 | いい, 大きい |
ADP | 接置詞 | は, が, を, に |
ADV | 副詞 | とても, まだ |
AUX | 助動詞 | です, ます, た |
CCONJ | 等位接続詞 | と, や |
DET | 限定詞 | この, その |
INTJ | 間投詞 | ああ, はい |
NOUN | 名詞 | 天気, 本 |
NUM | 数詞 | 一, 二, 100 |
PART | 助詞・小辞 | ね, よ |
PRON | 代名詞 | これ, それ |
PROPN | 固有名詞 | 東京, 太郎 |
PUNCT | 句読点 | 。, 、 |
SCONJ | 従属接続詞 | ので, から |
SYM | 記号 | %, $ |
VERB | 動詞 | 読む, 書く |
X | その他 | (未分類トークン) |
定数
Upos::ALL
#![allow(unused)]
fn main() {
pub const ALL: [Upos; 17]
}
全 17 品詞の配列を返します。
トレイト実装
Display:"NOUN","VERB"などの文字列に変換FromStr: 文字列からUposにパース。不正な文字列にはエラーを返す
#![allow(unused)]
fn main() {
use litsea::upos::Upos;
let pos: Upos = "NOUN".parse().unwrap();
assert_eq!(pos.to_string(), "NOUN");
}
SegmentLabel
定義
SegmentLabel 型は単語境界検出と品詞タグ付けを組み合わせます。各文字位置に 18 ラベルのいずれかが割り当てられます:
B(Upos)(17 ラベル): 指定された UPOS タグを持つ単語境界(例:B-NOUN,B-VERB)O(1 ラベル): 非境界(現在の単語の継続)
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SegmentLabel {
B(Upos), // 単語の先頭文字(境界)。品詞情報を持つ
O, // 単語の継続文字(非境界)
}
}
#![allow(unused)]
fn main() {
use litsea::upos::SegmentLabel;
// "今日は" の分割ラベル
// 今 → B-NOUN ("今日" の先頭、NOUN としてタグ付け)
// 日 → O ("今日" の継続)
// は → B-ADP ("は" の先頭、ADP としてタグ付け)
}
メソッド
all_labels
#![allow(unused)]
fn main() {
pub fn all_labels() -> Vec<SegmentLabel>
}
全 18 ラベル(B-ADJ, B-ADP, …, B-X, O)の一覧を返します。
is_boundary
#![allow(unused)]
fn main() {
pub fn is_boundary(&self) -> bool
}
境界ラベル(B-*)かどうかを返します。
pos
#![allow(unused)]
fn main() {
pub fn pos(&self) -> Option<Upos>
}
品詞タグを返します。非境界(O)の場合は None。
トレイト実装
Display:"B-NOUN","O"などの文字列に変換FromStr: 文字列からSegmentLabelにパース
#![allow(unused)]
fn main() {
use litsea::upos::{SegmentLabel, Upos};
let label: SegmentLabel = "B-NOUN".parse().unwrap();
assert!(label.is_boundary());
assert_eq!(label.pos(), Some(Upos::NOUN));
let label_o: SegmentLabel = "O".parse().unwrap();
assert!(!label_o.is_boundary());
assert_eq!(label_o.pos(), None);
}
Language
Language 列挙型は、文字種分類を含む言語固有の動作を定義します。
Language 列挙型
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Language {
#[default]
Japanese,
Chinese,
Korean,
}
}
トレイト
Default–Language::Japaneseを返すDisplay– 小文字の名前を返す("japanese"、"chinese"、"korean")FromStr– 完全名または ISO 639-1 コードから解析(大文字・小文字を区別しない)
パース
#![allow(unused)]
fn main() {
use litsea::language::Language;
// 完全名
let ja: Language = "japanese".parse().unwrap();
let zh: Language = "chinese".parse().unwrap();
let ko: Language = "korean".parse().unwrap();
// ISO 639-1 コード
let ja: Language = "ja".parse().unwrap();
let zh: Language = "zh".parse().unwrap();
let ko: Language = "ko".parse().unwrap();
// 大文字・小文字を区別しない
let ko: Language = "KOREAN".parse().unwrap();
// 無効な値
assert!("french".parse::<Language>().is_err());
}
char_type
#![allow(unused)]
fn main() {
pub fn char_type(&self, c: char) -> &'static str
}
文字をその言語固有の文字種コードに分類します。どのクラスにも属さない文字には "O"(その他)を返します。
分類は文字範囲に対する直接の match で行われます – アロケーション不要、O(1) で、正規表現は使用しません。
#![allow(unused)]
fn main() {
use litsea::language::Language;
let lang = Language::Japanese;
assert_eq!(lang.char_type('あ'), "I");
assert_eq!(lang.char_type('漢'), "H");
assert_eq!(lang.char_type('@'), "O");
}
内部的には、char_type は言語ごとの非公開関数(japanese_char_type、chinese_char_type、korean_char_type)にディスパッチします。全言語に共通のクラス – "P"(句読点)、"A"(ラテン文字)、"N"(数字) – は、言語固有のクラスの後にチェックされる共通ヘルパーで処理されます。
CLIリファレンス概要
litsea CLIは、単語分割、モデル学習、テキスト処理のためのコマンドを提供します。
使い方
litsea <COMMAND> [OPTIONS] [ARGS]
コマンド一覧
グローバルオプション
| Option | Description |
|---|---|
-h, --help | ヘルプ情報を表示 |
-V, --version | バージョン番号を表示 |
一般的なワークフロー
AdaBoost ワークフロー(単語分割のみ)
flowchart LR
A["1. scripts/download_udtreebank.sh"] --> B["2. scripts/corpus_udtreebank.sh"]
B --> C["3. litsea extract"]
C --> D["4. litsea train"]
D --> E["5. litsea segment"]
- UD Treebank をダウンロードする:
conllu_file=$(bash scripts/download_udtreebank.sh -l ja -o /tmp) - コーパスを準備する:
bash scripts/corpus_udtreebank.sh "$conllu_file" corpus.txt - 特徴量を抽出する:
litsea extract -l japanese corpus.txt features.txt - モデルを学習する:
litsea train -t 0.005 -i 1000 features.txt model.model - テキストを分割する:
echo "text" | litsea segment -l japanese model.model
POS ワークフロー(品詞推定付き単語分割)
flowchart LR
A["1. scripts/download_udtreebank.sh"] --> B["2. scripts/corpus_udtreebank.sh -p"]
B --> C["3. litsea extract --pos"]
C --> D["4. litsea train --pos"]
D --> E["5. litsea segment --pos"]
- UD Treebank をダウンロードする:
conllu_file=$(bash scripts/download_udtreebank.sh -l ja -o /tmp) - 品詞付きコーパスを準備する:
bash scripts/corpus_udtreebank.sh -p "$conllu_file" pos_corpus.txt - 品詞付き特徴量を抽出する:
litsea extract --pos -l japanese pos_corpus.txt features_pos.txt - POS モデルを学習する:
litsea train --pos --num-epochs 10 features_pos.txt model_pos.model - 品詞推定付き分割:
echo "text" | litsea segment --pos -l japanese model_pos.model
extract
モデル学習用にコーパスファイルから特徴量を抽出します。
使い方
litsea extract [OPTIONS] <CORPUS_FILE> <FEATURES_FILE>
引数
| Argument | Description |
|---|---|
CORPUS_FILE | 入力コーパスファイルのパス(単語をスペースで区切り、1行に1文) |
FEATURES_FILE | 出力特徴量ファイルのパス |
オプション
| Option | Default | Description |
|---|---|---|
-l, --language <LANGUAGE> | japanese | 文字タイプ分類に使用する言語。指定可能な値: japanese / ja, chinese / zh, korean / ko |
コーパスの形式
入力コーパスは、単語をスペースで区切り、1行に1文とする形式です。
Litsea は TinySegmenter を 参考 に 開発 さ れ た 。
Rust で 実装 さ れ た コンパクト な 単語 分割 ソフトウェア です 。
出力形式
特徴量ファイルには、文字位置ごとに1行が含まれます。
1 UW1:B2 UW2:B1 UW3:L UW4:i UW5:t UC1:O UC2:O UC3:A UC4:A ...
-1 UW1:B1 UW2:L UW3:i UW4:t UW5:s UC1:O UC2:A UC3:A UC4:A ...
1= 語境界-1= 非境界- 特徴量はタブ区切り
使用例
# Japanese
litsea extract -l japanese ./corpus.txt ./features.txt
# Chinese
litsea extract -l zh ./corpus_zh.txt ./features_zh.txt
# Korean
litsea extract -l ko ./corpus_ko.txt ./features_ko.txt
成功時のstderr出力:
Feature extraction completed successfully.
品詞付き特徴量抽出(--pos)
--pos フラグを指定すると、品詞付きコーパスから Averaged Perceptron 用の特徴量を抽出します。
品詞付きコーパスの形式
品詞付きコーパスは、単語/品詞 をスペースで区切り、1行に1文とする形式です。品詞タグは UPOS タグセット(ADJ, ADP, ADV, AUX, CCONJ, DET, INTJ, NOUN, NUM, PART, PRON, PROPN, PUNCT, SCONJ, SYM, VERB, X)を使用します。
これ/PRON は/PART テスト/NOUN です/AUX 。/PUNCT
私/PRON の/PART 猫/NOUN は/PART 可愛い/ADJ 。/PUNCT
ヒント:
scripts/download_udtreebank.shで UD Treebank をダウンロードし、scripts/corpus_udtreebank.sh -pで CoNLL-U ファイルからこの形式に自動変換できます。詳しくはコーパスの準備を参照してください。
使い方
litsea extract --pos -l japanese ./corpus_pos.txt ./features_pos.txt
出力形式
品詞付き特徴量ファイルでは、ラベルが SegmentLabel(B-NOUN, B-VERB, …, B-X, O)の18クラスとなります。
B-NOUN UW1:B2 UW2:B1 UW3:テ UW4:ス UC1:O UC2:O UC3:K UC4:K ...
O UW1:B1 UW2:テ UW3:ス UW4:ト UC1:O UC2:K UC3:K UC4:K ...
B-AUX UW1:ト UW2:で UW3:す UW4:。 UC1:K UC2:I UC3:I UC4:P ...
train
AdaBoostを使用して単語分割モデルを学習します。
使い方
litsea train [OPTIONS] <FEATURES_FILE> <MODEL_FILE>
引数
| Argument | Description |
|---|---|
FEATURES_FILE | 入力特徴量ファイルのパス(extract の出力) |
MODEL_FILE | 出力モデルファイルのパス |
オプション
| Option | Default | Description |
|---|---|---|
-t, --threshold <THRESHOLD> | 0.01 | 早期停止のための弱分類器精度の閾値。値を小さくするとより多くの反復が可能になる |
-i, --num-iterations <NUM_ITERATIONS> | 100 | ブースティング反復の最大回数 |
-m, --load-model-uri <LOAD_MODEL_URI> | None | 学習を再開するための既存モデルのURI(ファイルパスまたはHTTP/HTTPS URL) |
--pos | off | 品詞(POS)学習モードを有効にする(Averaged Perceptron を使用) |
-e, --num-epochs <NUM_EPOCHS> | 10 | 学習エポック数(POS モードのみ) |
出力
学習メトリクスはstderrに出力されます。
Result Metrics:
Accuracy: 94.15% ( 564133 / 599198 )
Precision: 95.57% ( 330454 / 345758 )
Recall: 94.36% ( 330454 / 350215 )
Confusion Matrix:
True Positives: 330454
False Positives: 15304
False Negatives: 19761
True Negatives: 233679
Ctrl+C のハンドリング
学習は優雅な中断をサポートしています。
- 1回目のCtrl+C: 学習を停止し、現在の状態でモデルを保存する
- 2回目のCtrl+C: 保存せずに即座に終了する
これにより、長時間の学習セッションを進捗を失うことなく停止できます。
使用例
基本的な学習:
litsea train -t 0.005 -i 1000 ./features.txt ./models/japanese.model
高精度な学習(低い閾値、多い反復回数):
litsea train -t 0.001 -i 5000 ./features.txt ./model.model
既存モデルからの再学習:
litsea train -t 0.005 -i 1000 -m ./models/japanese.model \
./new_features.txt ./models/japanese_v2.model
ハイパーパラメータの調整
| Parameter | 値を小さくした場合の効果 | 値を大きくした場合の効果 |
|---|---|---|
threshold | 反復回数が増加、精度が向上する可能性あり、学習時間が長くなる | 反復回数が減少、学習が高速化、アンダーフィットの可能性あり |
num_iterations | ブースティングラウンドが減少、モデルが小さくなる、アンダーフィットの可能性あり | ラウンドが増加、モデルが大きくなる、精度が向上する可能性あり |
品詞モデルの学習(--pos)
--pos フラグを指定すると、AdaBoost の代わりに Averaged Perceptron アルゴリズムを使用します。単語分割と品詞タグ付けを同時に行うマルチクラス分類器を学習します。
使い方
litsea train --pos [OPTIONS] <FEATURES_FILE> <MODEL_FILE>
POS 学習固有のオプション
| Option | Default | Description |
|---|---|---|
--pos | off | 品詞推定モデル(Averaged Perceptron)を学習する |
-e, --num-epochs <NUM_EPOCHS> | 10 | 学習エポック数 |
使用例
# 品詞モデルの学習(10エポック)
litsea train --pos -e 10 ./pos_features.txt ./models/japanese_pos.model
出力
学習メトリクスはstderrに出力されます(マクロ平均の適合率・再現率)。
Result Metrics:
Accuracy: 98.34%
Macro Precision: 97.87%
Macro Recall: 91.67%
Ctrl+C のハンドリング
AdaBoost と同様に、品詞モデルの学習も優雅な中断をサポートしています。1回目の Ctrl+C で学習を停止し、現在の状態でモデルを保存します。
POS ハイパーパラメータ
| Parameter | 値を小さくした場合の効果 | 値を大きくした場合の効果 |
|---|---|---|
num_epochs | 学習が高速化、アンダーフィットの可能性あり | 精度が向上、学習時間が長くなる、オーバーフィットの可能性あり |
segment
学習済みモデルを使用してテキストを単語に分割します。
使い方
echo "text" | litsea segment [OPTIONS] <MODEL_URI>
引数
| Argument | Description |
|---|---|
MODEL_URI | 学習済みモデルファイルのパスまたはURL。サポート形式: ローカルファイルパス, file://, http://, https:// |
オプション
| Option | Default | Description |
|---|---|---|
-l, --language <LANGUAGE> | japanese | 文字タイプ分類に使用する言語。指定可能な値: japanese / ja, chinese / zh, korean / ko |
--pos | off | 品詞推定付き分割を有効にします。train --pos で学習したPOSモデルが必要です |
入力 / 出力
- 入力: stdinから読み取り、1行に1文。空行はスキップされます。
- 出力: stdoutに書き込み、スペース区切りのトークン、入力行ごとに1行。
使用例
日本語:
echo "LitseaはTinySegmenterを参考に開発された。" \
| litsea segment -l japanese ./models/japanese.model
Litsea は TinySegmenter を 参考 に 開発 さ れ た 。
中国語:
echo "中文分词测试。" | litsea segment -l chinese ./models/chinese.model
韓国語:
echo "한국어 단어 분할 테스트입니다." \
| litsea segment -l korean ./models/korean.model
ファイルの処理:
cat input.txt | litsea segment -l japanese ./models/japanese.model > output.txt
URLからモデルを読み込み:
echo "テスト文です。" \
| litsea segment -l japanese https://example.com/models/japanese.model
品詞推定付き分割(--pos)
--pos フラグを指定すると、Averaged Perceptron モデルを使用して単語分割と品詞推定を同時に行います。
使い方
echo "text" | litsea segment --pos [OPTIONS] <MODEL_URI>
出力形式
各単語が 単語/品詞 の形式で出力されます。品詞は UPOS タグセットに準拠します。
echo "今日はいい天気ですね。" \
| litsea segment --pos -l japanese ./models/japanese_pos.model
今日/X は/ADP いい/ADJ 天気/NOUN です/AUX ね/PART 。/PUNCT
ファイルの処理
cat input.txt | litsea segment --pos -l japanese ./models/japanese_pos.model > output.txt
注意事項
--languageフラグは、モデルが学習された言語と一致する必要があります- モデルの読み込みは非同期で行われ、TLS(rustls)を使用したHTTP/HTTPSをサポートしています
- モデルURIはファイルパスに限定されません – 有効なURLであれば使用可能です
--posを使用する場合、モデルはtrain --posで学習したPOSモデルである必要があります
トレーニングガイド
このガイドでは、Litsea で独自の単語分割モデルと品詞推定モデルを学習する手順を説明します。
両方のワークフローとも、データソースとして Universal Dependencies (UD) Treebanks を使用します。
単語分割(AdaBoost)
- UD Treebank をダウンロードしてコーパスを準備:
conllu_file=$(bash scripts/download_udtreebank.sh -l ja -o /tmp) && bash scripts/corpus_udtreebank.sh "$conllu_file" corpus.txt - コーパスから特徴量を抽出する
- AdaBoost でモデルを訓練する
品詞推定(Averaged Perceptron)
- UD Treebank をダウンロードして品詞付きコーパスを準備:
conllu_file=$(bash scripts/download_udtreebank.sh -l ja -o /tmp) && bash scripts/corpus_udtreebank.sh -p "$conllu_file" pos_corpus.txt - 品詞付き特徴量を抽出:
litsea extract --pos -l japanese corpus.txt features.txt - POS モデルを訓練:
litsea train --pos --num-epochs 10 features.txt model.txt
その他のトピック
コーパスの準備
良質な学習コーパスは、モデルの精度にとって不可欠です。このガイドでは、Universal Dependencies (UD) Treebanks を使用したコーパスの準備方法を説明します。
データソース: UD Treebanks
Litsea は単語分割と品詞推定の両方のデータソースとして UD Treebanks を使用します。UD Treebanks は、多くの言語に対して CoNLL-U 形式の高品質な手動アノテーション済みデータを提供しています。
利用可能な Treebanks
| 言語 | ツリーバンク | リポジトリ |
|---|---|---|
| 日本語 | UD Japanese-GSD | UD_Japanese-GSD |
| 中国語 | UD Chinese-GSD | UD_Chinese-GSD |
| 韓国語 | UD Korean-GSD | UD_Korean-GSD |
ステップ 1: UD Treebank のダウンロード
scripts/download_udtreebank.sh を使用して UD Treebank をダウンロードします。スクリプトは学習用 CoNLL-U ファイルのパスを標準出力に出力します:
conllu_file=$(bash scripts/download_udtreebank.sh -l ja -o /tmp)
オプション:
-l: 言語コード(ja,ko,zh。デフォルト:ja)-o: 出力ディレクトリ(デフォルト: カレントディレクトリ)
手動でクローンする場合:
git clone https://github.com/UniversalDependencies/UD_Japanese-GSD
単語分割用コーパス
単語分割(AdaBoost)用のコーパスは以下の条件を満たすプレーンテキストファイルである必要があります:
- 1行1文
- 単語をスペースで区切る
太郎 は 走っ た 。
Litsea は コンパクト な 単語 分割 ソフトウェア です 。
CoNLL-U から単語分割用コーパスに変換
scripts/corpus_udtreebank.sh を使用して、CoNLL-U ファイルからスペース区切りの単語を抽出します:
conllu_file=$(bash scripts/download_udtreebank.sh -l ja -o /tmp)
bash scripts/corpus_udtreebank.sh "$conllu_file" corpus.txt
scripts/corpus_udtreebank.sh は CoNLL-U ファイルから品詞タグを除去し、スペース区切りの単語のみを1行1文で出力します。
品詞推定用コーパス
品詞推定(Averaged Perceptron)を行う場合、各単語に品詞タグを付与した形式のコーパスを使用します。
品詞付きコーパスの形式
1行1文で、各単語を 単語/品詞 の形式でスペース区切りに記述します:
これ/PRON は/ADP テスト/NOUN です/AUX 。/PUNCT
Litsea/PROPN は/ADP 単語/NOUN 分割/NOUN ソフトウェア/NOUN です/AUX 。/PUNCT
品詞タグは Universal POS (UPOS) タグセットに準拠し、17カテゴリで構成されます: ADJ, ADP, ADV, AUX, CCONJ, DET, INTJ, NOUN, NUM, PART, PRON, PROPN, PUNCT, SCONJ, SYM, VERB, X。
CoNLL-U から品詞付きコーパスに変換
scripts/corpus_udtreebank.sh に -p オプションを指定して、CoNLL-U ファイルを 単語/品詞 形式に変換します:
conllu_file=$(bash scripts/download_udtreebank.sh -l ja -o /tmp)
bash scripts/corpus_udtreebank.sh -p "$conllu_file" pos_corpus.txt
-p オプションにより品詞付き形式で出力されます。複合語トークンや空ノードは変換時に自動的に処理されます。
コーパスの自動作成
Litsea には、UD Treebank のダウンロードと変換を自動化するヘルパースクリプトが scripts/ ディレクトリに用意されています:
# UD Treebank をダウンロードして CoNLL-U ファイルのパスを取得
conllu_file=$(bash scripts/download_udtreebank.sh -l ja -o /tmp)
# 単語分割用コーパスを生成
bash scripts/corpus_udtreebank.sh "$conllu_file" corpus.txt
# 品詞付きコーパスを生成
bash scripts/corpus_udtreebank.sh -p "$conllu_file" pos_corpus.txt
scripts/download_udtreebank.sh は以下の処理を行います:
- 指定された言語に対応する UD Treebank リポジトリをクローン
- 学習用 CoNLL-U ファイルのパスを標準出力に出力
scripts/corpus_udtreebank.sh は以下の処理を行います:
- CoNLL-U ファイルを単語分割用コーパス形式に変換(デフォルト)
-pオプション指定時は品詞付きコーパス形式に変換
サポート言語: ja(日本語)、ko(韓国語)、zh(中国語)。
Wikipedia ダンプからのコーパス作成
大規模な学習データが必要な場合、Wikipedia の全文ダンプから scripts/corpus_wikidump.sh を使ってコーパスを作成できます。このスクリプトは wicket でプレーンテキストを抽出し、文章のみをフィルタリングした後、lindera でトークナイズします。
使い方
# 日本語(デフォルト)
bash scripts/corpus_wikidump.sh jawiki-latest-pages-articles.xml.bz2 corpus_ja.txt
# 韓国語
bash scripts/corpus_wikidump.sh -l ko kowiki-latest-pages-articles.xml.bz2 corpus_ko.txt
# 中国語
bash scripts/corpus_wikidump.sh -l zh zhwiki-latest-pages-articles.xml.bz2 corpus_zh.txt
オプション
| オプション | 説明 | デフォルト |
|---|---|---|
-l lang | 言語コード: ja, ko, zh | ja |
-n max_lines | 処理する最大文数(0 = 無制限) | 100000 |
文章フィルタリング
スクリプトは以下の2つのフィルタを適用し、整形された文章のみを抽出します:
- 文末記号 – 行が
。、.、!、?で終わる必要があります。これにより、セクション見出し(例: 「参考文献」)、箇条書き項目、メタデータが除外されます。 - 最小文字数 – 行は20文字以上である必要があります。これにより、短い断片や孤立したラベルが除外されます。
トークナイザー辞書
| 言語 | 辞書 | トークンフィルタ |
|---|---|---|
日本語 (ja) | embedded://unidic | japanese_compound_word(数詞複合語) |
韓国語 (ko) | embedded://ko-dic | なし |
中国語 (zh) | embedded://cc-cedict | なし |
コーパスサイズのガイドライン
推奨されるコーパスサイズは用途によって異なります:
| サイズ(文数) | 用途 |
|---|---|
| ~10,000 | プロトタイピングや動作確認の最小限 |
| 50,000 – 100,000 | モデル学習の実用的な範囲 |
| 100,000 – 500,000 | 高品質でロバストなモデル |
| 無制限 | 最大精度を目指す場合は全量使用 |
corpus_wikidump.sh のデフォルト max_lines=100000 は、実用〜高品質の範囲を想定しています。
コーパスの品質に関するヒント
- 多様性 – さまざまな分野のテキストを含める(ニュース、文学、ウェブなど)
- データ量 – 推奨サイズはコーパスサイズのガイドラインを参照
- 一貫性 – コーパス全体で一貫したトークン化を確保する
- 重複排除 – 偏りを避けるために重複文を除去する
- クリーニング – HTML タグ、特殊なフォーマット、非テキストコンテンツを除去する
特徴量の抽出
コーパスの準備ができたら、次のステップはモデル学習用の特徴量を抽出することです。
コマンド
litsea extract -l <LANGUAGE> <CORPUS_FILE> <FEATURES_FILE>
使用例
litsea extract -l japanese ./corpus.txt ./features.txt
出力:
Feature extraction completed successfully.
内部処理の仕組み
flowchart TD
A["Read corpus line by line"] --> B["Split line into words"]
B --> C["Build chars, types, and tags arrays"]
C --> D["For each character position"]
D --> E["Extract 38-42 features"]
E --> F["Write label + features to file"]
Extractorがコーパスの各行を読み込む- 各文に対して、文字配列・文字種配列・タグ配列を持つ
Segmenterコンテキストを作成する - 各文字位置(先頭を除く)について特徴量を抽出し、正しいラベルとともに書き込む
特徴量ファイルの形式
各行は1つの文字位置を表します:
1 UP1:U UP2:U UP3:U BP1:UU BP2:UU UW1:B2 UW2:B1 UW3:は ...
-1 UP1:U UP2:U UP3:B BP1:UB BP2:BU UW1:B1 UW2:は UW3:テ ...
- 最初の列: ラベル(
1= 境界、-1= 非境界) - 残りの列: 特徴量(タブ区切り)
品詞付き特徴量の抽出
品詞推定モデル用には、--pos フラグを使用して、二値境界ラベルの代わりに品詞ラベル付きの特徴量を抽出します。
コマンド
litsea extract --pos -l <LANGUAGE> <CORPUS_FILE> <FEATURES_FILE>
使用例
litsea extract --pos -l japanese ./corpus.txt ./features.txt
品詞ラベル
品詞特徴量を抽出する場合、各文字位置には二値の 1/-1 ラベルではなく、18種類のセグメントラベルのいずれかが付与されます:
- B-NOUN, B-VERB, B-ADJ, B-ADP, B-ADV, B-AUX, B-CCONJ, B-DET, B-INTJ, B-NUM, B-PART, B-PRON, B-PROPN, B-PUNCT, B-SCONJ, B-SYM, B-X – 対応する品詞タグを持つ単語境界
- O – 非境界(単語の内部)
特徴量テンプレート(文字 n-gram、文字種 n-gram など)は標準の分割と同じで、ラベル体系のみが異なります。
品詞特徴量ファイルの形式
B-NOUN UP1:U UP2:U UP3:U BP1:UU BP2:UU UW1:B2 UW2:B1 UW3:は ...
O UP1:U UP2:U UP3:B BP1:UB BP2:BU UW1:B1 UW2:は UW3:テ ...
B-VERB UP1:U UP2:U UP3:U BP1:UU BP2:UU UW1:B2 UW2:B1 UW3:い ...
- 最初の列: セグメントラベル(例:
B-NOUN、O) - 残りの列: 特徴量(タブ区切り)
ファイルサイズの目安
特徴量ファイルは、各文字位置が38-42個の特徴量文字列を生成するため、コーパスよりも大幅に大きくなります。1 MB のコーパスに対して、特徴量ファイルはおよそ 50-100 MB になることが見込まれます。
モデルの学習
特徴量の抽出が完了したら、AdaBoost を使用してモデルを学習します。
コマンド
litsea train [OPTIONS] <FEATURES_FILE> <MODEL_FILE>
基本的な使用例
litsea train -t 0.005 -i 1000 ./features.txt ./models/japanese.model
学習プロセス
flowchart TD
A["Initialize features<br/>(read feature names)"] --> B["Initialize instances<br/>(read labels + features)"]
B --> C["AdaBoost training loop"]
C --> D{"Converged or<br/>max iterations?"}
D -->|No| C
D -->|Yes| E["Save model"]
E --> F["Output metrics"]
- 特徴量の初期化 – 特徴量ファイルを読み込み、特徴量インデックスを構築する
- インスタンスの初期化 – 再度読み込み、ラベル付きインスタンスと初期重みをロードする
- 学習ループ – 最適な特徴量を反復的に選択し、モデルの重みを更新し、インスタンスの重みを調整する
- モデルの保存 – 非ゼロの特徴量の重みをモデルファイルに書き込む
- メトリクスの出力 – 正解率、適合率、再現率、混同行列を表示する
ハイパーパラメータ
| パラメータ | フラグ | デフォルト値 | ガイダンス |
|---|---|---|---|
| 閾値 | -t | 0.01 | 0.005 から開始することを推奨。値を低くすると反復回数が増えるが、学習時間も増加する |
| 反復回数 | -i | 100 | 1000 から開始することを推奨。学習停止時にまだ精度が向上している場合は増やす |
出力の解釈
Result Metrics:
Accuracy: 94.15% ( 564133 / 599198 )
Precision: 95.57% ( 330454 / 345758 )
Recall: 94.36% ( 330454 / 350215 )
Confusion Matrix:
True Positives: 330454
False Positives: 15304
False Negatives: 19761
True Negatives: 233679
- Accuracy(正解率) – 正しい予測の割合(境界と非境界の両方を含む)
- Precision(適合率) – 境界と予測されたもののうち、実際に正しかった割合
- Recall(再現率) – 実際の境界のうち、検出できた割合
- True Positives(真陽性) – 正しく予測された境界
- False Positives(偽陽性) – 境界がないのに境界と予測されたもの
- False Negatives(偽陰性) – 見逃された実際の境界
- True Negatives(真陰性) – 正しく予測された非境界
途中停止
学習中に Ctrl+C を1回押すと、現在の状態でモデルを保存して停止します。Ctrl+C を2回押すと、保存せずに即時終了します。
品詞推定モデルの学習
品詞推定モデルを学習する場合は、--pos フラグを使用します。品詞モデルは AdaBoost(二値分類器)の代わりに Averaged Perceptron(多クラス分類器)アルゴリズムを使用します。
品詞学習コマンド
litsea train --pos --num-epochs 10 <FEATURES_FILE> <MODEL_FILE>
品詞学習の使用例
litsea train --pos --num-epochs 10 ./features.txt ./models/japanese_pos.model
Averaged Perceptron と AdaBoost の比較
| 観点 | AdaBoost(分割) | Averaged Perceptron(品詞) |
|---|---|---|
| 分類 | 二値(境界 / 非境界) | 多クラス(18 セグメントラベル) |
| ラベル | 1, -1 | B-NOUN, B-VERB, …, O |
| ハイパーパラメータ | 閾値、反復回数 | エポック数 |
| モデルサイズ | 約 1-22 KB | 約 11 MB |
品詞学習のハイパーパラメータ
| パラメータ | フラグ | デフォルト値 | ガイダンス |
|---|---|---|---|
| エポック数 | --num-epochs | 10 | 学習データ全体を何回繰り返すかを指定。10 から開始し、メトリクスに基づいて調整 |
品詞学習の出力
Result Metrics:
Accuracy: 98.34%
Macro Precision: 97.87%
Macro Recall: 91.67%
- Accuracy(正解率) – 全クラスにわたる正しい予測の割合
- Macro Precision(マクロ適合率) – 全品詞クラスの適合率の平均
- Macro Recall(マクロ再現率) – 全品詞クラスの再現率の平均
品詞学習の途中停止
品詞学習中に Ctrl+C を1回押すと、現在の状態でモデルを保存して停止します。Ctrl+C を2回押すと、保存せずに即時終了します。
モデルの評価
モデルの品質を理解することは、良好な分割結果を得るために不可欠です。
メトリクス
train コマンドは学習後に3つの主要なメトリクスを出力します:
Accuracy(正解率)
Accuracy = (TP + TN) / Total Instances
すべての文字位置のうち、正しく分類された割合(境界と非境界の両方を含む)です。モデル品質の最も広範な指標です。
Precision(適合率)
Precision = TP / (TP + FP)
モデルが予測した境界のうち、正しかった割合です。高い適合率は、誤った境界(過分割)が少ないことを意味します。
Recall(再現率)
Recall = TP / (TP + FN)
実際の境界のうち、モデルが検出した割合です。高い再現率は、見逃された境界(不足分割)が少ないことを意味します。
混同行列
| 境界と予測 (+1) | 非境界と予測 (-1) | |
|---|---|---|
| 実際の境界 | True Positive (TP) | False Negative (FN) |
| 実際の非境界 | False Positive (FP) | True Negative (TN) |
事前学習済みモデルのベンチマーク
| モデル | 正解率 | 適合率 | 再現率 | 学習コーパス |
|---|---|---|---|---|
| japanese.model | 94.15% | 95.57% | 94.36% | UD Japanese-GSD |
| korean.model | 85.08% | – | – | UD Korean-GSD |
| chinese.model | 80.72% | – | – | UD Chinese-GSD |
モデル品質の改善
精度が不十分な場合は、以下を検討してください:
- より多くの学習データ – より大規模で多様なコーパスを用意する
- 閾値を下げる –
-t 0.001を試して、より多くのブースティング反復を許可する - 反復回数を増やす –
-i 5000以上を試す - コーパスの品質向上 – 一貫したトークン化とクリーンなテキストを確保する
- 再学習 – 既存のモデルから開始し、追加データで学習する(モデルの再学習を参照)
モデルの再学習
既存のモデルに新しいデータで学習を再開することで、モデルを改善できます。
コマンド
litsea train -t 0.005 -i 1000 -m <EXISTING_MODEL> <NEW_FEATURES_FILE> <OUTPUT_MODEL>
使用例
# 新しいコーパスから特徴量を抽出
litsea extract -l japanese ./new_corpus.txt ./new_features.txt
# 既存モデルから再学習
litsea train -t 0.005 -i 1000 \
-m ./models/japanese.model \
./new_features.txt \
./models/japanese_v2.model
仕組み
flowchart LR
A["Existing model<br/>(weights)"] --> C["Trainer"]
B["New features"] --> C
C --> D["Retrained model<br/>(updated weights)"]
- Trainer が新しい特徴量ファイルから特徴量とインスタンスを初期化する
-mオプションで既存のモデルの重みを読み込む- 読み込まれた重みを出発点として学習を継続する
- 新しいモデルは、学習済みのパターンをすべて引き継ぎつつ、新しいデータで改良される
ユースケース
- ドメイン適応 – 汎用モデルをドメイン固有のテキスト(医療、法律など)でファインチューニングする
- 段階的な改善 – ゼロから再学習せずに、より多くの学習データを追加する
- エラー修正 – 現在のモデルが誤りを犯す例を使って学習する
注意事項
- 出力モデルのパスは入力モデルと同じパスを指定できます(上書き)
-mフラグはファイルパス、file://、http://、https://URI に対応しています- 再学習は既存の重みから開始するため、必要な反復回数が少なくなる場合があります
モデルファイル形式
Litsea のモデルは、シンプルなプレーンテキストファイルとして保存されます。
形式の仕様
<feature_name>\t<weight>
<feature_name>\t<weight>
...
<bias>
- 最終行を除く各行は、タブ文字で区切られた特徴量名と重みを含む
- 重みがゼロの特徴量は、ファイルをコンパクトに保つために省略される
- 最終行はバイアス項を単一の数値として含む
例
BC1:IK 0.3456
BC2:KI -0.1234
UW4:は 0.5678
UC4:I 0.2345
...
-0.0891
バイアスの復元
モデルの読み込み時に、バイアスは以下の式で復元されます:
bias_bucket_weight = -bias_value * 2 - sum(all_feature_weights)
予測時:
bias = -sum(all_model_weights) / 2.0
score = bias + sum(model[feature] for feature in input_attributes)
ファイルサイズ
モデルファイルは非常にコンパクトです:
| モデル | サイズ | 特徴量 |
|---|---|---|
| japanese.model | 約 2.9 KB | Wikipedia で学習 |
| korean.model | 約 1.8 KB | Wikipedia で学習 |
| chinese.model | 約 1.3 KB | Wikipedia で学習 |
| RWCP.model | 約 22 KB | オリジナルの TinySegmenter |
| JEITA_Genpaku_ChaSen_IPAdic.model | 約 17 KB | JEITA コーパス |
コンパクトなサイズは Litsea の主要な利点の一つです。モデルはアプリケーションに直接埋め込んだり、最小限のオーバーヘッドで HTTP 経由で配信したりできます。
互換性
- モデルファイルはエンコーディング非依存です(特徴量名はそのまま保存されます)
- 形式は決定的です(特徴量は BTreeMap により整列されます)
- モデルは前方互換性があります。入力に含まれるがモデルにない新しい特徴量は、予測時に単純に無視されます
リモートモデルの読み込み
Litsea は、ローカルファイルに加えて HTTP/HTTPS URL からのモデル読み込みに対応しています。
対応する URI スキーム
| スキーム | 例 | 説明 |
|---|---|---|
| (なし) | ./model.model | ローカルファイルパス(デフォルト) |
file:// | file:///path/to/model | 明示的な File URI |
http:// | http://example.com/model | HTTP URL |
https:// | https://example.com/model | HTTPS URL |
CLI での使用
echo "テスト" | litsea segment -l japanese https://example.com/japanese.model
ライブラリでの使用
#![allow(unused)]
fn main() {
let mut learner = AdaBoost::new(0.01, 100);
// ローカルファイル
learner.load_model_from_path(Path::new("./models/japanese.model"))?; // ローカルは同期
// HTTP URL
learner.load_model("https://example.com/models/japanese.model").await?;
}
実装の詳細
- HTTP クライアント: reqwest + rustls(OpenSSL 依存なし)
- カスタム User-Agent:
Litsea/<version> load_modelメソッドが**非同期(async)**なのは、HTTP 読み込みに非同期ランタイムが必要なため- CLI では
tokioが非同期ランタイムを提供
WASM に関する注意事項
wasm32 ターゲットでは:
- ローカルファイルパスは非対応 – ファイルシステムへのアクセスが利用できない
file://スキームは非対応- HTTP/HTTPS の読み込みは動作する – ブラウザの fetch API 経由(reqwest の WASM サポート)
WASM 環境で実行する場合、ファイルパスの代わりに URL を使用するようエラーメッセージが案内します。
ベンチマーク
Litsea には、パフォーマンス測定のための Criterion ベンチマークスイートが含まれています。
ベンチマークの実行
cargo bench --bench bench
または Makefile を使用:
make bench
ベンチマークスイート
ベンチマークは litsea/benches/bench.rs で定義されています:
| ベンチマーク | 説明 |
|---|---|
segment_short/adaboost/{ja,zh,ko} | 短い文の分割(AdaBoost) |
segment_short/averaged_perceptron/{ja,zh,ko} | 短い文の分割+品詞付与 |
segment_long_japanese/{adaboost,averaged_perceptron} | 坊っちゃん全文の処理(約 300 KB) |
get_type_hiragana | 文字種分類 |
add_corpus | 学習用コーパスの取り込み |
predict_adaboost | 単一の AdaBoost 予測 |
モデルは load_model_from_path で同期的に読み込まれます。ベンチマークに非同期ランタイムは関与しません。
HTML レポート
Criterion は、統計情報と比較グラフを含む詳細な HTML レポートを以下の場所に生成します:
target/criterion/report/index.html
ベンチマーク実行後にこのファイルをブラウザで開くと、以下を確認できます:
- 信頼区間付きの反復時間
- スループット測定
- 前回実行との比較(自動回帰検出)
結果の解釈
パフォーマンスに影響する主な要因:
- 分割処理は入力長に対して線形(O(n))
- 文字種分類は文字範囲に対する
matchで直接行われる(数ナノ秒、セットアップコストなし) - 各位置での予測は特徴量の数に依存(38-42個、定数)
- モデル読み込み時間はモデルファイルサイズに比例
事前学習済みモデル
Litsea は models/ ディレクトリに複数の事前学習済みモデルを同梱しています。
モデルカタログ
japanese.model
| プロパティ | 値 |
|---|---|
| 言語 | 日本語 |
| 学習コーパス | UD Japanese-GSD |
| 正解率 | 94.15% |
| 適合率 | 95.57% |
| 再現率 | 94.36% |
| ファイルサイズ | 約 2.9 KB |
korean.model
| プロパティ | 値 |
|---|---|
| 言語 | 韓国語 |
| 学習コーパス | UD Korean-GSD |
| 正解率 | 85.08% |
| ファイルサイズ | 約 1.8 KB |
chinese.model
| プロパティ | 値 |
|---|---|
| 言語 | 中国語(簡体字・繁体字) |
| 学習コーパス | UD Chinese-GSD |
| 正解率 | 80.72% |
| ファイルサイズ | 約 1.3 KB |
RWCP.model
| プロパティ | 値 |
|---|---|
| 言語 | 日本語 |
| ソース | オリジナルの TinySegmenter から抽出 |
| ライセンス | BSD 3-Clause (Taku Kudo) |
| ファイルサイズ | 約 22 KB |
JEITA_Genpaku_ChaSen_IPAdic.model
| プロパティ | 値 |
|---|---|
| 言語 | 日本語 |
| 学習コーパス | JEITA プロジェクト 杉田玄白コーパス |
| トークナイザ | ChaSen with IPAdic |
| ファイルサイズ | 約 17 KB |
品詞推定モデル
japanese_pos.model
| プロパティ | 値 |
|---|---|
| 言語 | 日本語 |
| アルゴリズム | Averaged Perceptron |
| 学習コーパス | UD Japanese-GSD(7,050 文) |
| エポック数 | 10 |
| 正解率 | 98.34% |
| マクロ適合率 | 97.87% |
| マクロ再現率 | 91.67% |
| ファイルサイズ | 約 11 MB |
chinese_pos.model
| プロパティ | 値 |
|---|---|
| 言語 | 中国語(簡体字・繁体字) |
| アルゴリズム | Averaged Perceptron |
| 学習コーパス | UD Chinese-GSD(3,997 文) |
| エポック数 | 10 |
| 正解率 | 97.09% |
| マクロ適合率 | 97.31% |
| マクロ再現率 | 96.23% |
| ファイルサイズ | 約 19 MB |
korean_pos.model
| プロパティ | 値 |
|---|---|
| 言語 | 韓国語 |
| アルゴリズム | Averaged Perceptron |
| 学習コーパス | UD Korean-GSD(4,400 文) |
| エポック数 | 10 |
| 正解率 | 95.33% |
| マクロ適合率 | 95.30% |
| マクロ再現率 | 87.69% |
| ファイルサイズ | 約 8.4 MB |
使用方法
echo "これはテストです。" | litsea segment --pos -l japanese models/japanese_pos.model
出力:
これ/PRON は/ADP テスト/NOUN です/AUX 。/PUNCT
モデルの選択
- 日本語には、最高精度を求める場合は
japanese.modelを、オリジナルの TinySegmenter との互換性を重視する場合はRWCP.modelを使用 - 中国語には
chinese.modelを使用 - 韓国語には
korean.modelを使用 - 品詞推定には、対応する
*_pos.model(japanese_pos.model、chinese_pos.model、korean_pos.model)を使用して単語分割と品詞推定を同時実行 - ドメイン固有の用途には、独自モデルの学習または既存モデルの再学習を検討
サンプルデータ
resources/ ディレクトリには以下も含まれています:
- bocchan.txt – 夏目漱石の小説「坊っちゃん」のサンプル日本語コーパス(約 307 KB)。ベンチマークに使用されます。
ライセンス
Litsea はデュアルライセンスで配布されています。
MIT License
Litsea のメインコードベースは MIT License の下でライセンスされています:
MIT License
Copyright (c) 2025 Minoru OSUKA
Copyright (c) 2022 ICHINOSE Shogo
BSD 3-Clause License
Taku Kudo 氏が開発したオリジナルのコード(TinySegmenter)は BSD 3-Clause License の下でライセンスされています:
Copyright (c) 2008, Taku Kudo
All rights reserved.
ライセンス全文
完全なライセンス文はリポジトリ内の LICENSE ファイルで確認できます。