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

WebAssembly

litsea-wasm は wasm-bindgen を用いて、ブラウザ・Deno・各種バンドラで Litsea を動かすバインディングです。npm では litsea-wasm として配布します。

Node.js ではネイティブバインディングの litsea を使ってください。速度が上で、モデルの学習にも対応しています。

インストール

npm install litsea-wasm

モジュールサイズは 178KB(gzip 82KB)(リリースビルドでの実測値)です。モデルは別途ダウンロードします。

使い方

import init, { Segmenter } from 'litsea-wasm'

await init()

const bytes = new Uint8Array(await (await fetch('/models/japanese.model')).arrayBuffer())
const seg = Segmenter.fromBytes('japanese', bytes)

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

seg.free()

言語名と ISO 639-1 コードのどちらも使えます。モデルファイル自身が種別を持つため、読み込んだモデルで何ができるかは hasPos が示します。

POS タグ付け

seg.segmentWithPos('これはテストです。')
// [ Token { surface: 'これ', pos: 'PRON', start: 0, end: 6 }, ... ]

start と end は UTF-8 でのバイトオフセットです。JavaScript の文字列インデックスは UTF-16 コードユニットなので、切り出しには TextEncoder / TextDecoder を使ってください。

const bytes = new TextEncoder().encode(text)
new TextDecoder().decode(bytes.subarray(token.start, token.end))   // === token.surface

ユーザー辞書

fromBytes は、Node.js バインディングと同じ名前の、省略可能な末尾の引数を 2 つ受け取ります(#263)。userWords は、現れた場所で必ず 1 トークンとして出力する語の配列、userWordsGate は、モデルの margin が十分小さい場所にだけ語を適用する flip ratio(0〜1)です。

const seg = Segmenter.fromBytes('japanese', bytes, ['雪だるま', '新幹線'])

seg.segment('雪だるまを作る')
// [ '雪だるま', 'を', '作る' ]   (辞書なしでは [ '雪', 'だる', 'ま', 'を', '作る' ])

登録語には両端に境界が置かれ、内側には置かれません。重なった出現箇所は leftmost-longest で解決し、1 文字の語は無視します。照合は文脈を見ないため、短い断片を語として登録すると、それを含む他の語をすべて壊します。userWordsGate を 0 にすると、モデルがすでに同意している場所だけに語を適用します。意味論の全体は Segmenter を参照してください。

ホストの制約で提供しない機能

5 つのバインディングの中で最も制約が強く、いずれも推測ではなく実測に基づいて判断しています。

無い機能理由
fromUricargo check --target wasm32-unknown-unknown --features remote_model が失敗する。reqwest の wasm バックエンドには connect_timeout が無く、litsea::model_io はそれを設定しているため。代わりにページ側で fetch する(キャッシュ・CORS・進捗の制御もページ側に残る)
学習技術的な制約ではなく方針判断。下記を参照
CancelToken学習が無い以上、キャンセル対象が存在しない

学習を提供しない理由

#218 で litsea にファイルシステム非依存の extract/train API が入り、wasm32 でもコンパイルできるため、このバインディングでも学習を公開すること自体は可能です。それでも提供しないのは次の 2 点によります(#221)。

  • ブラウザは学習を行う場所として適さない。 タブがコーパス・そこから抽出した特徴量(コーパスよりかなり大きい)・モデルを同時に保持することになります。API の形を決めるにはまずそのメモリ実測が必要で、実用的なコーパス規模では成立しないという結論も十分あり得ます。
  • 参照実装も持たない。 lindera-python・lindera-nodejs・lindera-php・lindera-ruby はいずれもトレーナを備えますが、lindera-wasm は備えていません。litsea のバインディング群もこの形に揃えています。

学習は CLI かネイティブバインディングで行い、生成したモデルをここで読み込んでください。

メモリ

Segmenter はコンパイル済みモデル(POS モデルなら数 MB)を保持し、WebAssembly のオブジェクトは GC されません。不要になったら free() を呼んでください。

モデルのキャッシュ

モデルは 86KB〜8.3MB あり、訪問者ごとにネットワークを通ります。これはネイティブ版には無い、このバインディング固有のコストです。補助モジュールを同梱しています。

import { fetchModel, clearModelCache } from 'litsea-wasm/js/cache.js'

const bytes = await fetchModel('/models/japanese.model')

URL をキーに Cache Storage へ保存し、利用できない環境(非セキュアコンテキストなど)では通常の fetch にフォールバックするため、呼び出し側での分岐は不要です。wasm モジュール外の素の JavaScript なので、使わないページには一切コストがかかりません。

エラー

すべてのエラーは Node.js バインディングと同じ code を持ち、2 つの JavaScript バインディングで扱いが揃います。

err.code発生条件
invalid_argument未知の言語名、0〜1 の外の userWordsGate
model旧 joint POS モデル
parseモデルの形式不正、または UTF-8 でない
pos_unavailable分割専用モデルに対する POS タグ付けの要求

開発

make test-litsea-wasm    # cargo test + ヘッドレスブラウザテスト
make lint-litsea-wasm    # wasm32 での clippy
make build-litsea-wasm   # wasm-pack build --target web

ブラウザテストはプロセスを起動できないため、tests/generate_fixtures.sh が先に litsea CLI を実行して出力を書き出し、テストはそれとの一致を検証します。他のバインディングと同じく、正解は常に参照実装が決めます。

ブラウザは make test-litsea-wasm WASM_BROWSER=chrome で切り替えられます。全テストが成功した直後に PermissionDenied で失敗する場合、PATH 上の geckodriver が snap 制約下にあります(テスト自体は実行済みです)。