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

Python

litsea-pythonPyO3maturin を用いて Litsea を Python 3.10 以降へ公開するバインディングです。PyPI では litsea という名前で配布します。

インストール

pip install litsea

wheel は安定 ABI(abi3-py310)でビルドされるため、プラットフォームごとに 1 つの wheel がサポート対象の全 Python バージョンをカバーします。

モデルの入手

パッケージにモデルは含まれません。models/ ディレクトリから取得してパスを渡してください(事前学習済みモデルを参照)。

モデルの種別を指定するフラグはありません。ファイル自身が種別を持っており、読み込んだモデルで何ができるかは has_pos が示します。

分割

from litsea import Language, Segmenter

seg = Segmenter.open(Language.JAPANESE, "models/japanese.model")

seg.segment("これはテストです。")
# ['これ', 'は', 'テスト', 'です', '。']

Language を受け取る箇所では言語名も使えます。Segmenter.open("ja", ...)Segmenter.open("japanese", ...) は等価です。

空白区切りの言語では空白自体が 1 トークンとして返るため、トークンを連結すると常に入力が復元されます。

Segmenter.open("ko", "models/korean.model").segment("안녕하세요 반갑습니다")
# ['안녕하세요', ' ', '반갑습니다']

POS タグ付け

seg = Segmenter.open(Language.JAPANESE, "models/japanese_pos.model")

for token in seg.segment_with_pos("これはテストです。"):
    print(token.surface, token.pos.name, token.start, token.end)
# これ PRON 0 6
# は ADP 6 9
# テスト NOUN 9 18
# です AUX 18 24
# 。 PUNCT 24 27

startend は入力に対するバイトオフセットです。text.encode()[token.start:token.end].decode() で表層形が得られます。分割専用モデルに対して segment_with_pos を呼ぶと PosUnavailableError が送出されます。

API

呼び出し戻り値
Segmenter.open(language, path)ファイルから読み込んだセグメンタ
Segmenter.from_bytes(language, data)バイト列から読み込んだセグメンタ
Segmenter.from_uri(language, uri)パス・file://http(s):// から読み込んだセグメンタ
segment(text)list[str]
segment_batch(texts)list[list[str]]
segment_tokens(text)バイトオフセット付き list[Token]
segment_with_pos(text)タグとオフセット付き list[Token]
segment_with_pos_batch(texts)list[list[Token]]
Extractor(language).extract(...)特徴量ファイルを書き出す
Extractor(language).extract_two_stage(...).stage1 / .stage2 / .lexicon を書き出す
Trainer(threshold, iterations, features).train(model, cancel=None)BinaryMetrics
PerceptronTrainer(epochs, features).train(model, cancel=None)MulticlassMetrics
TwoStageTrainer(epochs, prefix, dominance=0.99).train(model, cancel=None)TwoStageMetrics

LanguageUposenum.Enum のサブクラスではなく PyO3 のクラスです。メンバーはクラス属性なので、列挙には for x in Language ではなく Language.all() / Upos.all() を使ってください。

学習

from litsea import Extractor, Language, Trainer

Extractor(Language.JAPANESE).extract("corpus.txt", "features.txt")
metrics = Trainer(0.01, 10_000, "features.txt").train("japanese.model")
print(f"accuracy: {metrics.accuracy:.2f}%")

TwoStageTrainer は 1 度しか実行できません。学習時に stage 1 が AdaBoost モデルへ collapse され、トレーナが消費されるためです。再利用可能かどうかは available が示し、2 回目の train()InvalidArgumentError を送出します。

キャンセル

学習中は GIL が解放されるため、別スレッドから停止できます。

import threading
from litsea import CancelToken, Trainer

cancel = CancelToken()
threading.Timer(60.0, cancel.cancel).start()
metrics = Trainer(0.01, 100_000, "features.txt").train("japanese.model", cancel=cancel)

キャンセルはエラーではありません。次のチェックポイントで停止し、部分的に学習されたモデルを保存してメトリクスを返します。バインディングはシグナルハンドラを登録しないため、Ctrl-C の扱いはアプリケーション側のままです。

エラー

すべての例外は LitseaError を継承します。

例外発生条件
InvalidArgumentError未知の言語名、未知の feature set、使用済みトレーナ
ModelErrorダウンロード失敗、または旧 joint POS モデル
IoErrorファイルの読み書き失敗
ParseErrorモデルまたは学習データの形式不正
UnsupportedErrorこのビルドでは利用できないスキームや操作
PosUnavailableError分割専用モデルに対する POS タグ付けの要求

スレッドと GIL

Segmenter はイミュータブルで、スレッド間で共有できます。segment_batchsegment_with_pos_batchextract・各 train は GIL を解放します。

単文の segment / segment_with_pos は GIL を保持します。GIL を解放するには入力文字列を所有する必要があり(PyO3 の Ungil 境界により、GIL 解放中に Python 所有のメモリへ触れられないため)、そのコピーのコストが 1 文の分割コストを上回るからです。大量処理にはバッチ版を使ってください。

開発

make setup-venv            # venv 作成と開発ツールのインストール
make test-litsea-python    # cargo test + maturin develop + pytest
make lint-litsea-python    # clippy + ruff
make build-litsea-python   # リリース wheel を litsea-python/dist へ出力

パリティテストは litsea CLI をビルドして、その出力とバインディングの出力を突き合わせます。何が正しいかを決めるのはハードコードした期待値ではなく参照実装です。