Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

はじめに

Litsea は、TinySegmenter および TinySegmenterMaker に触発されて開発された、Rust で実装された極めてコンパクトな単語分割ライブラリです。

MeCabLindera などの従来の形態素解析器とは異なり、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 DependenciesUPOS タグセット(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 は以下のプラットフォームでテストされています:

OSArchitecture
Linuxx86_64, aarch64
macOSx86_64 (Intel), aarch64 (Apple Silicon)
Windowsx86_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(())
}

次のステップ

アーキテクチャ概要

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)"]
  1. コーパスの準備 – 単語をスペースで区切ったテキストを準備
  2. 特徴量抽出Extractor がコーパスを読み込み、文字を種別に分類し、ラベル付き特徴量ベクトルを出力
  3. モデル学習Trainer が特徴量を AdaBoost に入力し、最も情報量の多い特徴量を反復的に選択してコンパクトなモデルを生成

分割パイプライン

flowchart LR
    F["Raw text"] --> G["Segmenter (AdaBoost)"]
    H["Model file"] --> G
    G --> I["Segmented words"]
  1. モデル読み込み – 学習済みモデルを読み込み(ファイルまたは URL から)
  2. 文字分類 – 入力の各文字について、言語固有のパターンに基づいて文字種コードを決定
  3. 特徴量抽出 – スライディングウィンドウを使用して各文字位置の特徴量セットを構築
  4. 予測 – 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 のすべての機能を提供します。

DependencyVersion用途
thiserror2.0エラー型の導出
reqwest0.13HTTP/HTTPS モデル読み込み(rustls)
tokio1.49リモートモデル読み込み用非同期ランタイム
criterion0.8ベンチマーク(開発依存)
tempfile3.25テスト用一時ファイル(開発依存)

litsea-cli(CLI バイナリ)

CLI は Litsea の機能へのコマンドラインインターフェースを提供します。

DependencyVersion用途
clap4.5コマンドライン引数の解析
ctrlc3.5学習中の Ctrl+C のグレースフルハンドリング
tokio1.49非同期ランタイム
litsea0.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 と文字種分類を定義します。

  • LanguageJapaneseChineseKorean のバリアントを持つ enum
    • FromStr を実装("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 – 単語分割と品詞付与

主要なユーザー向けモジュールです。

  • SegmenterLanguageAdaBoost 学習器、オプションの 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 アルゴリズム

単語境界の判定に使う二値分類器です。

  • AdaBoost
    • new(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

分割+品詞付与に使う多クラス分類器です。

  • AveragedPerceptron
    • add_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品詞タグ(NOUNVERB、…)
  • SegmentLabel – 文字位置ごとの分割+品詞の複合ラベル(B(Upos) または O)。"B-NOUN" / "O" 文字列形式の Display/FromStr を実装

extractor.rs – 特徴量抽出

モデル学習用にコーパスから特徴量を抽出します。

  • ExtractorSegmenter をラップしてコーパスファイルを処理
    • 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(IoInvalidDataInvalidInputUnsupportedremote_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() の学習ループは以下のように動作します:

初期化

  1. 学習ファイルから特徴量とインスタンスを読み込み
  2. インスタンスの重みを均一に初期化(後に初期スコアに基づいて調整)
  3. すべてのモデルの重みをゼロで初期化

反復ブースティング

各イテレーション 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
  • 各行には特徴量名とその重み(タブ区切り)が含まれる
  • 重みがゼロの特徴量は省略される
  • 最終行にはバイアス項(単一の数値)が含まれる

詳細はモデルファイル形式を参照してください。

ハイパーパラメータ

ParameterDefault説明
threshold0.01早期停止の閾値。低い値はより多くのイテレーションを許可し、精度が向上する可能性がある
num_iterations100ブースティングの最大ラウンド数。高い値は学習時間とモデルサイズを犠牲にして精度を向上させる可能性がある

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 との比較

項目AdaBoostAveraged Perceptron
分類方式二値分類(+1 / -1)多クラス分類(18クラス)
出力単語境界のみ単語境界 + 品詞タグ
弱学習器特徴量の決定株なし(線形分類器)
重みの管理特徴量ごとに1つの重みクラス×特徴量の重み行列
汎化手法アンサンブル重みの平均化
学習方式サンプル再重み付けによる反復ブースティング重み平均化によるオンライン学習
モデルサイズ数 KB約 11 MB(品詞特徴量含む)
ハイパーパラメータthreshold, num_iterationsnum_epochs

ハイパーパラメータ

パラメータデフォルト値説明
num_epochs10学習エポック数。高い値は精度を向上させる可能性があるが、過学習のリスクがある

特徴量抽出

Litsea は、各単語境界候補の周辺の局所的なコンテキストを捉えるために文字 n-gram 特徴量を使用します。本章ではすべての特徴量タイプをカタログ化します。

特徴量カテゴリ

入力の各文字位置 i について、セグメンタは文字、その種別コード、および前回の境界判定からなるスライディングウィンドウから特徴量を抽出します。

基本特徴量(38 個)

CategoryIDs説明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 個、日本語と中国語のみ)

CategoryIDs説明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 特徴量は有用な信号ではなくノイズを追加してしまいます。

特徴量の総数

LanguageBaseWCTotal
Japanese38442
Chinese38442
Korean38038

特徴量の形式

各特徴量は 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、非境界の場合 -1
  2. タブ区切りの特徴量文字列

文字種分類

Litsea の各言語は、個々の文字を言語学的に意味のあるカテゴリに分類する文字種クラスのセットを定義します。これらの種別コードは AdaBoost 分類器の特徴量として使用されます。

仕組み

Language::char_type(c: char) -> &'static str は、Unicode 文字範囲に対する match 式で文字を直接分類します(正規表現・アロケーションなし)。matchアームは上から順に評価され、最初にマッチしたアームが種別コードを決定します。どのアームにもマッチしない場合、その文字は "O"(Other)に分類されます。

言語ごとに分類関数(japanese_char_typechinese_char_typekorean_char_type)があり、全言語で共通のクラス "P"(句読点)・"A"(ラテン文字)・"N"(数字)は、言語固有クラスの後に評価される共有ヘルパー punct_latin_digit() にまとめられています。単純な範囲を超えるロジックはmatchガードで表現します(例: 韓国語のハングル音節構造)。

日本語の文字種

Code名称パターン / 範囲
M漢数字[一二三四五六七八九十百千万億兆]一, 千, 億
H漢字 / CJK 統合漢字[一-龠々〆ヵヶ]漢, 字, 学
Iひらがな[ぁ-ん]あ, い, う
Kカタカナ[ァ-ヴーア-ン゙゚]ア, カ, ー
P句読点CJK 記号(U+3000-303F)、全角(U+FF01-FF65)。, 、, 「
AASCII / ラテン文字[a-zA-Za-zA-Z]A, z, B
N数字[0-90-9]0, 5
Oその他フォールバック@, #

注意: “M”(漢数字)は “H”(一般漢字)よりも先にチェックされるため、一や百などの文字は一般的な漢字ではなく数字として分類されます。

中国語の文字種

Code名称パターン / 範囲
F機能語高頻度の文法語的, 了, 在, 是
CCJK 統合漢字U+4E00–U+9FFF中, 国, 人
XCJK 拡張 AU+3400–U+4DBF希少な文字
RCJK 部首U+2E80–U+2FDF康熙部首
P句読点CJK 記号 + 全角。, ,, 《
B注音符号U+3100–U+312F, U+31A0–U+31BF注音記号
AASCII / ラテン文字[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+9FFFCJK 統合漢字
P句読点CJK 記号 + 全角。, ,
AASCII / ラテン文字[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、「받침」)

この区別は、終声(パッチム/받침)の有無が韓国語の単語境界パターンと助詞の接続に影響するため重要です。

言語間の比較

FeatureJapaneseChineseKorean
種別の総数8910
固有の種別M, H, I, KF, C, X, R, BE, SN, SF, J, G
共有する種別P, A, N, OP, A, N, OP, 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 で、セグメンタは以下を実行します:

  1. 特徴量の抽出get_attributes(i, tags, chars, types) を呼び出し、38-42 個の特徴量からなる HashSet<String> を構築

  2. スコアの計算 – AdaBoost 学習器がマッチするすべての特徴量のモデル重みとバイアスを合計:

    score = bias + sum(model[feature] for feature in attributes)
    
  3. 判定score >= 0 の場合、その文字は新しい単語を開始(境界)、そうでなければ現在の単語を継続

  4. タグの更新 – 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つの言語の単語分割をサポートしています。

サポート言語

LanguageEnum VariantCLI ValuesFeature CountPre-trained Model Accuracy
日本語Language::Japanesejapanese, ja4294.15%
中国語Language::Chinesechinese, zh4280.72%
韓国語Language::Koreankorean, ko3885.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分類器の特徴量として使用されます。

AspectJapaneseChineseKorean
文字タイプ数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個追加)なし
総特徴量数424238
マッチング方式正規表現のみ正規表現のみ正規表現 + クロージャ

韓国語の特徴量が少ない理由

韓国語のハングル音節は、SN(받침/終声なし)とSF(받침あり)の2種類にのみ分類されます。この二値的な区別では、WC特徴量(単語+文字タイプの組み合わせ)は冗長な情報を生成し、識別力がほとんどありません。これらを除外することで、ノイズを低減し、モデルをコンパクトに保ちます。

日本語

日本語はLitseaのデフォルト言語です。

文字タイプ

CodeNamePatternExamples
M漢数字[一二三四五六七八九十百千万億兆]一, 三, 千, 億
H漢字 / CJK[一-龠々〆ヵヶ]漢, 字, 学, 々
Iひらがな[ぁ-ん]あ, い, う, を
Kカタカナ[ァ-ヴーア-ン゙゚]ア, カ, ー, ハ
P句読点CJK記号 + 全角。, 、, 「, 」
AASCII/ラテン文字[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は簡体字・繁体字の両方を対象とした中国語の単語分割をサポートしています。

文字タイプ

CodeNamePatternExamples
F機能語高頻度の文法語的, 了, 在, 是, 和
CCJK統合漢字U+4E00–U+9FFF中, 国, 人
XCJK拡張AU+3400–U+4DBF稀少文字
RCJK部首U+2E80–U+2FDF康熙部首
P句読点CJK記号 + 全角。, ,, 《, 》
B注音符号U+3100–U+312F, U+31A0–U+31BF注音記号
AASCII/ラテン文字[a-zA-Za-zA-Z]A, z
N数字[0-90-9]0, 5, 5
Oその他フォールバック@, #, $

中国語の機能語(虚词)

「F」タイプは、分割において重要な高頻度の文法語を捉えます。

CategoryCharacters
構造助詞的, 地, 得
アスペクト・語気助詞了, 着, 过, 吗, 呢, 吧, 啊, 嘛
接続詞和, 与, 或, 但, 而, 且, 及
前置詞在, 从, 到, 把, 被, 对, 向, 给
文法動詞・副詞是, 有, 不, 也, 都, 就, 要, 会, 能, 可

これらの文字は圧倒的に文法的な役割で出現し、内容語とは異なる形で語境界を示します。

学習済みモデル

chinese.model

  • 学習コーパス: UD Chinese-GSD
  • 精度(Accuracy): 80.72%

使用例

echo "中文分词测试。" | litsea segment -l chinese ./models/chinese.model

韓国語

Litseaは、ハングル文字タイプの特殊な検出機能を備えた韓国語の単語分割をサポートしています。

文字タイプ

CodeNamePatternExamples
E助詞/語尾[은는을를의에]은, 는, 을, 를, 의, 에
SNハングル(받침なし)コードポイント演算가, 나, 하, 모
SFハングル(받침あり)コードポイント演算한, 글, 각, 붙
Jハングル字母U+1100–U+11FF個別の子音/母音
G互換字母U+3130–U+318Fㄱ, ㅏ, ㅎ
H漢字U+4E00–U+9FFFCJK統合漢字
P句読点CJK記号 + 全角。, ,
AASCII/ラテン文字[a-zA-Za-zA-Z]A, z
N数字[0-90-9]0, 5, 5
Oその他フォールバック@, #, $

韓国語の助詞(조사)

「E」タイプは、6つの高頻度文法助詞を捉えます。

CharacterRoleName
은/는主題マーカー주격 조사
을/를目的語マーカー목적격 조사
所有格관형격 조사
場所格부사격 조사

これらの助詞は語境界に頻繁に出現するため、分割精度を向上させるために独自のタイプコードが割り当てられています。

ハングル音節構造(받침検出)

韓国語では、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の多言語フレームワークは、容易に拡張できるよう設計されています。本ガイドでは、新しい言語のサポートを追加する方法を説明します。

手順の概要

  1. Language 列挙型にバリアントを追加
  2. Display および FromStr のmatchアームを実装
  3. 文字タイプパターン関数を作成
  4. パターン関数を登録
  5. WC特徴量の有無を決定
  6. 学習コーパスを用意してモデルを学習
  7. テストを追加

手順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.rsget_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: コーパスを用意してモデルを学習

  1. コーパスを用意します(単語をスペースで区切った形式)。

    word1 word2 word3 word4
    
  2. 特徴量を抽出します。

    litsea extract -l thai ./corpus.txt ./features.txt
    
  3. モデルを学習します。

    litsea train -t 0.005 -i 1000 ./features.txt ./models/thai.model
    

手順7: テストを追加

language.rssegmenter.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::segmenterSegmenter単語分割、品詞推定付き分割
litsea::adaboostAdaBoost二値分類、モデルの入出力
litsea::perceptronAveragedPerceptron多クラス分類(品詞推定)、モデルの入出力
litsea::uposUpos, SegmentLabelUPOS 品詞タグ、セグメントラベル
litsea::languageLanguage言語定義、文字分類
litsea::extractorExtractorコーパスからの特徴量抽出
litsea::trainerTrainer, PosTrainer学習パイプラインの制御
litsea::errorLitseaError, Resultエラー型と Result エイリアス
litsea::metricsBinaryMetrics, 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 は以下の処理を行います:

  1. コーパスファイルから各行を読み込む
  2. Segmenter::add_corpus_with_writer() を呼び出して各行を処理する
  3. 各文字位置のラベルと特徴量セットを出力ファイルに書き込む

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/modelremote_model フィーチャーが必要)
  • HTTPS: https://example.com/modelremote_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 の学習ループを実行します。runningfalse に設定すると早期終了します。

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>)
}

指定されたエポック数でモデルを学習します。runningfalse に設定すると早期終了します。学習終了時に重みの平均化が自動的に行われます。

#![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/modelremote_model フィーチャーが必要)
  • HTTPS: https://example.com/modelremote_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,
}
}

トレイト

  • DefaultLanguage::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_typechinese_char_typekorean_char_type)にディスパッチします。全言語に共通のクラス – "P"(句読点)、"A"(ラテン文字)、"N"(数字) – は、言語固有のクラスの後にチェックされる共通ヘルパーで処理されます。

CLIリファレンス概要

litsea CLIは、単語分割、モデル学習、テキスト処理のためのコマンドを提供します。

使い方

litsea <COMMAND> [OPTIONS] [ARGS]

コマンド一覧

CommandDescription
extract学習用にコーパスから特徴量を抽出
train単語分割モデルを学習
segment学習済みモデルを使用してテキストを単語に分割

グローバルオプション

OptionDescription
-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"]
  1. UD Treebank をダウンロードする: conllu_file=$(bash scripts/download_udtreebank.sh -l ja -o /tmp)
  2. コーパスを準備する: bash scripts/corpus_udtreebank.sh "$conllu_file" corpus.txt
  3. 特徴量を抽出する: litsea extract -l japanese corpus.txt features.txt
  4. モデルを学習する: litsea train -t 0.005 -i 1000 features.txt model.model
  5. テキストを分割する: 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"]
  1. UD Treebank をダウンロードする: conllu_file=$(bash scripts/download_udtreebank.sh -l ja -o /tmp)
  2. 品詞付きコーパスを準備する: bash scripts/corpus_udtreebank.sh -p "$conllu_file" pos_corpus.txt
  3. 品詞付き特徴量を抽出する: litsea extract --pos -l japanese pos_corpus.txt features_pos.txt
  4. POS モデルを学習する: litsea train --pos --num-epochs 10 features_pos.txt model_pos.model
  5. 品詞推定付き分割: echo "text" | litsea segment --pos -l japanese model_pos.model

extract

モデル学習用にコーパスファイルから特徴量を抽出します。

使い方

litsea extract [OPTIONS] <CORPUS_FILE> <FEATURES_FILE>

引数

ArgumentDescription
CORPUS_FILE入力コーパスファイルのパス(単語をスペースで区切り、1行に1文)
FEATURES_FILE出力特徴量ファイルのパス

オプション

OptionDefaultDescription
-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

出力形式

品詞付き特徴量ファイルでは、ラベルが SegmentLabelB-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>

引数

ArgumentDescription
FEATURES_FILE入力特徴量ファイルのパス(extract の出力)
MODEL_FILE出力モデルファイルのパス

オプション

OptionDefaultDescription
-t, --threshold <THRESHOLD>0.01早期停止のための弱分類器精度の閾値。値を小さくするとより多くの反復が可能になる
-i, --num-iterations <NUM_ITERATIONS>100ブースティング反復の最大回数
-m, --load-model-uri <LOAD_MODEL_URI>None学習を再開するための既存モデルのURI(ファイルパスまたはHTTP/HTTPS URL)
--posoff品詞(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 学習固有のオプション

OptionDefaultDescription
--posoff品詞推定モデル(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>

引数

ArgumentDescription
MODEL_URI学習済みモデルファイルのパスまたはURL。サポート形式: ローカルファイルパス, file://, http://, https://

オプション

OptionDefaultDescription
-l, --language <LANGUAGE>japanese文字タイプ分類に使用する言語。指定可能な値: japanese / ja, chinese / zh, korean / ko
--posoff品詞推定付き分割を有効にします。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)

  1. UD Treebank をダウンロードしてコーパスを準備: conllu_file=$(bash scripts/download_udtreebank.sh -l ja -o /tmp) && bash scripts/corpus_udtreebank.sh "$conllu_file" corpus.txt
  2. コーパスから特徴量を抽出する
  3. AdaBoost でモデルを訓練する

品詞推定(Averaged Perceptron)

  1. UD Treebank をダウンロードして品詞付きコーパスを準備: conllu_file=$(bash scripts/download_udtreebank.sh -l ja -o /tmp) && bash scripts/corpus_udtreebank.sh -p "$conllu_file" pos_corpus.txt
  2. 品詞付き特徴量を抽出: litsea extract --pos -l japanese corpus.txt features.txt
  3. 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-GSDUD_Japanese-GSD
中国語UD Chinese-GSDUD_Chinese-GSD
韓国語UD Korean-GSDUD_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 は以下の処理を行います:

  1. 指定された言語に対応する UD Treebank リポジトリをクローン
  2. 学習用 CoNLL-U ファイルのパスを標準出力に出力

scripts/corpus_udtreebank.sh は以下の処理を行います:

  1. CoNLL-U ファイルを単語分割用コーパス形式に変換(デフォルト)
  2. -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, zhja
-n max_lines処理する最大文数(0 = 無制限)100000

文章フィルタリング

スクリプトは以下の2つのフィルタを適用し、整形された文章のみを抽出します:

  1. 文末記号 – 行が .!? で終わる必要があります。これにより、セクション見出し(例: 「参考文献」)、箇条書き項目、メタデータが除外されます。
  2. 最小文字数 – 行は20文字以上である必要があります。これにより、短い断片や孤立したラベルが除外されます。

トークナイザー辞書

言語辞書トークンフィルタ
日本語 (ja)embedded://unidicjapanese_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"]
  1. Extractor がコーパスの各行を読み込む
  2. 各文に対して、文字配列・文字種配列・タグ配列を持つ Segmenter コンテキストを作成する
  3. 各文字位置(先頭を除く)について特徴量を抽出し、正しいラベルとともに書き込む

特徴量ファイルの形式

各行は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-NOUNO
  • 残りの列: 特徴量(タブ区切り)

ファイルサイズの目安

特徴量ファイルは、各文字位置が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"]
  1. 特徴量の初期化 – 特徴量ファイルを読み込み、特徴量インデックスを構築する
  2. インスタンスの初期化 – 再度読み込み、ラベル付きインスタンスと初期重みをロードする
  3. 学習ループ – 最適な特徴量を反復的に選択し、モデルの重みを更新し、インスタンスの重みを調整する
  4. モデルの保存 – 非ゼロの特徴量の重みをモデルファイルに書き込む
  5. メトリクスの出力 – 正解率、適合率、再現率、混同行列を表示する

ハイパーパラメータ

パラメータフラグデフォルト値ガイダンス
閾値-t0.010.005 から開始することを推奨。値を低くすると反復回数が増えるが、学習時間も増加する
反復回数-i1001000 から開始することを推奨。学習停止時にまだ精度が向上している場合は増やす

出力の解釈

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, -1B-NOUN, B-VERB, …, O
ハイパーパラメータ閾値、反復回数エポック数
モデルサイズ約 1-22 KB約 11 MB

品詞学習のハイパーパラメータ

パラメータフラグデフォルト値ガイダンス
エポック数--num-epochs10学習データ全体を何回繰り返すかを指定。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.model94.15%95.57%94.36%UD Japanese-GSD
korean.model85.08%UD Korean-GSD
chinese.model80.72%UD Chinese-GSD

モデル品質の改善

精度が不十分な場合は、以下を検討してください:

  1. より多くの学習データ – より大規模で多様なコーパスを用意する
  2. 閾値を下げる-t 0.001 を試して、より多くのブースティング反復を許可する
  3. 反復回数を増やす-i 5000 以上を試す
  4. コーパスの品質向上 – 一貫したトークン化とクリーンなテキストを確保する
  5. 再学習 – 既存のモデルから開始し、追加データで学習する(モデルの再学習を参照)

モデルの再学習

既存のモデルに新しいデータで学習を再開することで、モデルを改善できます。

コマンド

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)"]
  1. Trainer が新しい特徴量ファイルから特徴量とインスタンスを初期化する
  2. -m オプションで既存のモデルの重みを読み込む
  3. 読み込まれた重みを出発点として学習を継続する
  4. 新しいモデルは、学習済みのパターンをすべて引き継ぎつつ、新しいデータで改良される

ユースケース

  • ドメイン適応 – 汎用モデルをドメイン固有のテキスト(医療、法律など)でファインチューニングする
  • 段階的な改善 – ゼロから再学習せずに、より多くの学習データを追加する
  • エラー修正 – 現在のモデルが誤りを犯す例を使って学習する

注意事項

  • 出力モデルのパスは入力モデルと同じパスを指定できます(上書き)
  • -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 KBWikipedia で学習
korean.model約 1.8 KBWikipedia で学習
chinese.model約 1.3 KBWikipedia で学習
RWCP.model約 22 KBオリジナルの TinySegmenter
JEITA_Genpaku_ChaSen_IPAdic.model約 17 KBJEITA コーパス

コンパクトなサイズは Litsea の主要な利点の一つです。モデルはアプリケーションに直接埋め込んだり、最小限のオーバーヘッドで HTTP 経由で配信したりできます。

互換性

  • モデルファイルはエンコーディング非依存です(特徴量名はそのまま保存されます)
  • 形式は決定的です(特徴量は BTreeMap により整列されます)
  • モデルは前方互換性があります。入力に含まれるがモデルにない新しい特徴量は、予測時に単純に無視されます

リモートモデルの読み込み

Litsea は、ローカルファイルに加えて HTTP/HTTPS URL からのモデル読み込みに対応しています。

対応する URI スキーム

スキーム説明
(なし)./model.modelローカルファイルパス(デフォルト)
file://file:///path/to/model明示的な File URI
http://http://example.com/modelHTTP URL
https://https://example.com/modelHTTPS 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.modeljapanese_pos.modelchinese_pos.modelkorean_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 ファイルで確認できます。