Skip to content

Laya の使い方:pandas の DataFrame でテキストを分類する(Jev のオープンソース代替を4言語で実測)

更新日

pip で Laya をインストールし、pandas の DataFrame のテキスト列に choice・score・yes/no の質問でラベルを付ける手順を解説。英語・中国語・日本語・韓国語の問い合わせ40件で精度と速度(1件13 ms)を実測し、README に書かれていない落とし穴と回避策もまとめました。

結論から言うと: インストールして欲しいラベルを定義すれば、Laya が DataFrame に新しい列を埋めてくれます。

python -m pip install laya
import pandas as pd
from laya import Router
 
df = pd.DataFrame({"text": [
    "I was charged twice for my September invoice. Please refund the extra payment.",
    "The dashboard has been down since 9am, nobody can log in.",
]})
 
questions = {
    "department": {"type": "choice", "instructions": "Which team should handle this support ticket?",
                   "criteria": {"billing": "charges, invoices, refunds",
                                "technical": "bugs, errors, outages",
                                "account": "login, password, two-factor authentication",
                                "other": "everything else"}},
    "refund_requested": {"type": "noul", "instructions": "Does the customer ask for money back?"},
}
 
router = Router()
results = router.predict_batch([{"state": t, "questions": questions} for t in df["text"]])
df["department"] = [r["answers"]["department"]["choice"] for r in results]
df["refund_p"] = [r["answers"]["refund_requested"]["noul"] for r in results]
print(df)

初回実行時に約 800 MB のチェックポイントがダウンロードされます。2回目以降は1行あたり数ミリ秒で、テキスト生成を一切しないため、出力をパースする必要もありません。

実際に試した結果: 手作業でラベルを付けたサポート問い合わせ40件(英語・中国語・日本語・韓国語を各10件)を、Apple M4 Max、Laya 0.3.20 で2026年9月27日に検証しました。

質問結果
担当部署(4択の choice)全体で 75% 。英語 90%、韓国語 80%、日本語 70%、中国語 60%
返金を求めているか(yes/no の noul)全言語で 100%
緊急度(3段階の score)30% 。多言語チェックポイントは英語以外のチケットすべてに「最も緊急」と回答。yes/no 形式に変えると 85% まで改善
速度・バッチ処理(GPU / Apple MPS)質問3つで1件あたり 13 ms
速度・バッチ処理(CPU のみ)1件あたり 147 ms

結論として、Laya は明確なカテゴリ分けや yes/no フラグの一次ラベル付けには高速かつ無料で使えます。一方で、中国語・日本語・韓国語(CJK)の微妙なニュアンスの判定はまだ信頼できず、信頼度スコアも誤りを見分ける指標にはなりません。以下では、ワークフロー全体、計測値、そして各落とし穴の回避方法を順に紹介します。

Laya はどんなツールで、なぜ話題なのか

Laya は、テキストに対する型付きの質問に1回のフォワードパスで答えるオープンソース(Apache-2.0)の Python ライブラリです。 「state」(問い合わせ、メール、レビュー、JSON ドキュメントなど)と、次の3種類の質問を渡します。

  • choice:自分で定義したラベルの中から1つを選ぶ(「billing / technical / account / other」)
  • score:順序付きの尺度上に位置づける(「not urgent / soon / blocking」)
  • noul:yes/no の確率(「顧客は解約をほのめかしているか?」)

中身はチャットモデルではなく、エンコーダーモデル(ModernBERT または mmBERT)です。テキストを生成しないので、話が脱線したり、存在しないラベルをでっち上げたり、壊れた JSON を返したりすることがありません。その代わり、できるのは分類だけです。

注目を集めた理由はタイミングにあります。2026年9月15日、TypeSafe が同じ発想に基づくホスト型の「System One」判断モデル Jev を発表し、Hacker News で 1,984 ポイントを獲得しました。その4日後、Laya の作者が1年前に作っていたオープンな代替として Laya を投稿し、こちらも HN で 1,358 ポイントを集めています。GitHub のスター数は1週間で約 3k から 26k 超に増えました。Kev、OpenJev、「Jev 系モデル版の Ollama」をうたう Ollaya といった移植版やラッパーも登場し、日本では Zenn、韓国では GeekNews で連日記事が書かれています。

データを扱う立場から見ると、実務上の問いはもっと絞られます。テキスト列のラベル付けで、LLM の呼び出しや自作の分類器を置き換えられるのか? 以下のテストはこの問いに答えるためのものです。

ステップ1:インストール

Laya には Python 3.10 以上 が必要です。依存として PyTorch 2.14 と Transformers 5.x が入ります。既存の torch のバージョンと衝突しないよう、新しい仮想環境を使いましょう。

python3 -m venv .venv
.venv/bin/python -m pip install laya
.venv/bin/python -I -c "import laya; print(laya.__version__)"

Windows、CUDA 向けの torch ビルド、Intel GPU については README のインストールの節 (opens in a new tab) を参照してください。仮想環境に慣れていない場合は、Python 仮想環境ガイド から始めるのがおすすめです。

ディスク容量とダウンロード時間を見込んでおきましょう。 モデルの重みは初回利用時にダウンロードされます。

チェックポイントエンコーダー用途ディスク上のサイズ(実測)
layaModernBERT-large、421M パラメータ英語約 807 MB
laya-multilingualmmBERT-base、322M パラメータ100以上の言語この2つの合計で約 1.4 GB
laya-typed-decisionsModernBERT-large、421M パラメータ業務ワークフローの判断(上記に含む)
3つすべて2.2 GB

Hugging Face のトークンなしでは、私の回線で最初のチェックポイントのダウンロードに 5分35秒 かかりました。匿名アクセスのレート制限を避けるため、トークンを設定しておきましょう(export HF_TOKEN=...)。ホームディレクトリの空き容量が少ない場合は HF_HOME を設定します。

オプションの extras として、laya[serve](ローカルの Web プレイグラウンドと HTTP API)、laya[mcp](MCP サーバー)、laya[langchain]、laya[onnx] があります。

ステップ2:最初の予測を実行する

推奨されるエントリーポイントは Router です。入力ごとに言語を判定し、英語のテキストは英語チェックポイントへ、それ以外は多言語チェックポイントへ振り分けます。

from laya import Router
 
router = Router()
state = "Hi, we were billed twice for March. Please refund the duplicate today or we will cancel our plan."
questions = {
    "department": {"type": "choice", "instructions": "Which department should handle this?",
                   "criteria": {"billing": "invoices, payments, refunds",
                                "technical": "bugs, outages, system errors",
                                "other": "everything else"}},
    "churn_risk": {"type": "noul", "instructions": "Does the user threaten to cancel or leave?"},
}
 
result = router.predict(state, questions)
print(result["answers"]["department"]["choice"])   # billing
print(result["answers"]["churn_risk"]["noul"])     # 0.879
print(result["routing"]["model"])                  # english

各回答は dict です。よく使うフィールドは次のとおりです。

フィールド意味
choice(choice 質問)選ばれたラベル
probabilities各ラベル・各レベルの確率
noul(yes/no 質問)「yes」の確率
answer_confidence返された回答の確率
result["routing"]どのチェックポイントが実行されたか、その理由("English Latin text"、"non-Latin script…")

私の環境では、ウォームアップ後の2回目の呼び出しは 33 ms でした。

チェックポイントを初めて読み込むときに RuntimeWarning: laya: this checkpoint ships invalid temperatures… という警告が出ることがあります。一部の信頼度がキャリブレーションされていない、という意味です。予測自体は動きますが、数値を当てにする前に後述の信頼度の節を読んでください。

ステップ3:DataFrame の列全体にラベルを付ける

行ごとにループで predict() を呼ぶのは避けましょう。predict_batch() はまず全行をルーティングし、チェックポイントと質問セットごとに行をまとめ、フォワードパスを共有します。

import pandas as pd
from laya import Router
 
df = pd.read_csv("tickets.csv")          # テキスト列を持つ任意の DataFrame
 
questions = {
    "department": {"type": "choice", "instructions": "Which team should handle this support ticket?",
                   "criteria": {"billing": "charges, invoices, receipts, refunds, cancelling a paid plan",
                                "technical": "bugs, errors, outages, features not working",
                                "account": "login, password, two-factor authentication, profile settings",
                                "other": "sales questions, discounts, feedback, everything else"}},
    "blocking": {"type": "noul",
                 "instructions": "Is the customer blocked from working right now, or asking for an immediate fix?"},
    "refund_requested": {"type": "noul", "instructions": "Does the customer ask for money back?"},
}
 
router = Router(preload=True)            # ルーティング先の2つのチェックポイントを先に読み込む
results = router.predict_batch(
    [{"state": text, "questions": questions} for text in df["text"]],
    batch_size=32,
)
 
answers = [r["answers"] for r in results]
df["department"] = [a["department"]["choice"] for a in answers]
df["department_conf"] = [a["department"]["answer_confidence"] for a in answers]
df["blocking_p"] = [a["blocking"]["noul"] for a in answers]
df["refund_p"] = [a["refund_requested"]["noul"] for a in answers]
df["checkpoint"] = [r["routing"]["model"] for r in results]

結果は入力順で返ってくるので、リストをそのまま列に代入できます。行が dict の場合(件名と本文を持つメールなど)は、その dict を state として渡せます。Laya は JSON 形式の入力を受け付けます。

40件のチケット、各3問での実測速度(Apple M4 Max):

方法1件あたり
predict_batch()、自動ルーティング、ウォーム状態13 ms
predict_batch()、多言語チェックポイントのみ10 ms
predict_batch()、英語チェックポイントのみ34 ms
Python のループで predict()31 ms
CPU で predict_batch()(Router(device="cpu"))147 ms
チェックポイントのダウンロードが発生する最初のバッチ数分

1行 13 ms なら、10万行でもノート PC の GPU で約22分です。同じ行を LLM API でラベル付けすれば、相応のコストがかかり、時間も長くなります。CPU ではおよそ10倍遅くなりますが、数千行程度なら十分実用的です。

ステップ4:自分のデータで精度を確かめる(私の計測結果)

README のベンチマークは大規模な公開データセットを使っています。私が知りたかったのは、サポートチームやプロダクトチームが実際に扱う雑多で短いテキストを、このサイトの読者が使う言語で Laya がどこまで処理できるかです。そこで40件のチケットを自作し、手作業でラベルを付けました。各言語10件で、10の状況(二重請求、障害、メールアドレス変更、学割、エクスポートエラー、返金付きの解約、パスワードリセット、請求書の依頼、称賛、2FA ロックアウト)を全言語でそろえています。40行はあくまで動作確認であり、ベンチマークではありません。ここにある数値を信用する前に、自分のデータ50〜100行で同じ確認をしてください。

担当部署(4択の choice)のチェックポイント別精度:

言語Router(自動)英語チェックポイント固定多言語固定Typed-decisions 固定
英語90%90%90%90%
韓国語80%30%80%40%
日本語70%70%70%60%
中国語60%80%60%70%
全体75%68%75%65%

目立った点は3つです。

  1. ルーティングは Router に任せる。 韓国語に英語チェックポイントを強制すると精度は 30% まで落ち、中国語ではたまたま良くなりました。どこでも勝てる単一のチェックポイントはなく、全体では自動ルーティングが最も良い結果でした。
  2. 明確なカテゴリは簡単、「other」は難しい。 二重請求、障害、エクスポートエラーのチケットは全言語で正解しました。誤りが集中したのは「学割はありますか?」(zh / ja / ko で billing と予測)と「新しいデザイン、とても良いですね」(technical と予測)です。どちらも、製品に関する単語に反応するモデルならいかにもやりそうな間違いです。
  3. 2FA ロックアウトは中国語・日本語・韓国語で technical に分類されました。 account の説明文に two-factor authentication と明記しているにもかかわらずです。

最も強かったのは yes/no 質問です。 「Does the customer ask for money back?」は4言語すべてで 100% でした。

中国語の結果は独立した報告とも一致します。 issue #124 (opens in a new tab) で中国語のルーティング依頼20件を試したユーザーは、多言語チェックポイントで 70% という結果を得ています。

Laya と Jev の比較。 Issue #450 (opens in a new tab) では、結果で採点された実際の政府調達公告741件で英語チェックポイントを実行し、Laya の精度 0.780 に対して Jev は 0.919 でした。

落とし穴と回避策

落とし穴1:多言語チェックポイントの score 質問が最後の選択肢に張り付く

緊急度を3段階(not urgent、soon、blocking work right now)で尋ねたところ、多言語チェックポイントは40件すべてで blocking work right now を選びました。 「新しいチャート、すごく良いです。ありがとう」というチケットまで含めてです。自動ルーティングでは英語の10件だけがこれを免れ、緊急度の全体精度は 30% でした。

これは既知の未解決バグです。Issue #131 (opens in a new tab) によると、laya-multilingual は最初に並べたスコアレベルを一度も選ばず(英語で 290 件中 0 件、日本語で 300 件中 0 件)、確率はラベルではなくスロットの位置に従って動きます。

効果があった回避策: 尺度を yes/no 質問に置き換えます。緊急度の score を「Is the customer blocked from working right now, or asking for an immediate fix?」という1つの noul に置き換えたところ、85% (英語 90%、中国語 90%、韓国語 90%、日本語 70%)になりました。3段階が必要なら、2つの yes/no 質問(「今ブロックされているか」と「今日中に返信が必要か」)を投げて、pandas で組み合わせましょう。

落とし穴2:信頼度で正解と誤りを分けられない

まず思いつくのは、信頼度の高い行は自動で採用し、残りを人に回すという運用です。しかし私の実行では、このしきい値はあまり役に立ちませんでした。

answer_confidence がこれ以上の行を採用自動採用された行採用行の精度すり抜けた誤り
0.588%74%9
0.768%78%6
0.955%82%4

特にひどい誤りのうち2件は、信頼度 0.99 でした。中国語の称賛チケットが technical に、日本語の割引に関する質問が billing に分類されたものです。読み込み時の temperature に関する警告や、issue #124 の「confident wrong answers persist」も同じ現象を指しています。

対処法: 信頼度は弱いシグナルとして扱いましょう。信頼度の低い行だけでなく、ラベルごとにランダムサンプルを抜き取って確認します。重要なカテゴリ(法的な苦情、解約の示唆など)には専用の noul 質問を用意しましょう。yes/no 質問は4択の choice よりはるかに信頼できました。

落とし穴3:質問を翻訳しても安定して改善するわけではない

質問文と各ラベルの説明を中国語・日本語・韓国語に書き直し(ラベルのキーは英語のまま)、英語以外のチケットで再実行しました。中国語は 60% から 80% に上がりましたが、日本語は 70% から 60% に、韓国語は 80% から 70% に下がり、全体は 70% のまま変わりませんでした。自分のテストで別の結果が出ない限り、質問は英語のままにしておくのが無難です。

落とし穴4:短いラテン文字のテキストは英語チェックポイントに回される

Router が言語を判定するには、ある程度の長さのテキストが必要です。README によると、スペイン語やポルトガル語の非常に短い文字列("Esqueci minha senha")はデフォルトのチェックポイント、つまり英語に回されます。データの大半が英語以外なら、デフォルトを変更しましょう。

router = Router(default="multilingual")

モデルを実行せずに、ルーティングの判断だけを確認することもできます。

router.route({"body": "Der Kunde wurde zweimal belastet"}).reason

落とし穴5:長い文書は黙って切り捨てられる

多言語チェックポイントの上限は 1,024 トークン、英語チェックポイントは 512 トークンまでしか読みません。長いメールや文書では、max_len を渡してチェックポイントを明示します。README によると、約 4,000 トークンまでは精度が保たれ、それを超えるとばらつきが出ます。

result = router.predict(long_document, questions, model="multilingual", max_len=8192)

落とし穴6:言語が交互に現れるとモデルの入れ替えが頻発する

Router() はデフォルトで2つのチェックポイントをメモリに保持します。max_loaded=1 にすると、英語と英語以外の行が切り替わるたびにモデルが再読み込みされ、README の計測では1回の切り替えに7〜10秒かかります。多言語が混在する DataFrame では Router(preload=True) を使うかデフォルトのままにし、行がチェックポイントごとにまとめられるよう必ず predict_batch() を使いましょう。

Laya、Jev、LLM のどれを選ぶか

LayaJev(TypeSafe)汎用 LLM(API)
ローカル実行・無料はい、Apache-2.0いいえ、ホスト型 APIローカルモデルを使う場合のみ
1行あたりの速度GPU で約 10〜35 msホスト型数百 ms〜数秒
出力形式常に有効なラベルまたは確率型付きの回答パースや構造化出力が必要
ニュアンスのある文・CJK テキストの精度まちまち(私の4言語テストで 75%)#450 の比較ではより高い(英語で 0.919 対 0.780)たいてい最も高い
セットアップpip install、チェックポイントごとに約 800 MBアカウントと API キーAPI キー

現実的なパターンはこうです。まず列全体に Laya をかけます。そのうえで、苦手なカテゴリ(今回なら other や CJK テキストのアカウント関連)に入った行や、抜き取り確認で不合格だった行だけを LLM か人に回します。LLM の料金がかかるのはデータの一部だけで済みます。

ラベルを汎用的なものではなく ドメイン固有 の判断にしたい場合は、README にあるファインチューニング用ノートブックが次のステップです。作者は、typed-decisions のベンチマークでファインチューニング後に 0.766 対 0.362 の精度を報告しています。ファインチューニングは私は試していません。

ステップ5:ラベル付けしたデータを可視化して探索する

新しい列ができたら、次に知りたいのは分布です。どの部署にチケットが集中しているか、どの言語で信頼度の低いラベルが出ているか、返金依頼はどこに固まっているか。PyGWalker (opens in a new tab) を使うと、DataFrame を Jupyter 内のドラッグ&ドロップ式チャートビルダーに変えられます。

import pygwalker as pyg
 
pyg.walk(df[["text", "department", "department_conf", "blocking_p", "refund_p", "checkpoint"]])

department を x 軸、件数を y 軸に置いて checkpoint で色分けすれば、ルーティングの傾向が1枚のチャートで見えます。department_conf < 0.7 でフィルターすれば、人による確認用のキューが作れます。Jupyter の外では pyg.to_html(df) でスタンドアロンの HTML ページを書き出せます(pygwalker 0.5.0.1 で確認済み)。さらに詳しくは PyGWalker クイックスタート を参照してください。

ノートブックでこうしたラベル付けを日常的に行うなら、RunCell (opens in a new tab) が便利です。JupyterLab 上で実際の DataFrame を相手に動く AI エージェントで、「df に Laya のラベルを追加して、信頼度の低い行をチャートにして」といった依頼と相性が良く、推測ではなく実際の列の値を見て作業できます。

実際に試したこと、ドキュメント由来の情報

  • 実測したもの(2026年9月27日、macOS・Apple M4 Max、Python 3.12、laya 0.3.20、torch 2.14.0、transformers 5.17.0):
    • インストール
    • 最初の予測と出力形式
    • チェックポイントのダウンロード時間とディスクサイズ
    • MPS と CPU での predict() と predict_batch() の速度
    • 3つのチェックポイントと自動ルーティングでの40件チケットの精度表
    • score が張り付くバグと noul による回避策
    • 信頼度によるしきい値判定
    • 質問の翻訳
    • 結果に対する PyGWalker の to_html
  • README と GitHub の issue からの情報(ここでは未検証):
    • max_len=8192 での長文精度
    • T4 GPU のレイテンシ
    • default="multilingual" のルーティング挙動
    • モデル入れ替えの所要時間
    • ファインチューニングの結果
    • issue #450 の Jev との比較

このプロジェクトはほぼ毎日リリースされています。上記の score の挙動が今も当てはまるかは、リリースノート (opens in a new tab) と issue #131 を確認してから判断してください。

FAQ

Related Guides