先に結論
Laya Multilingual と Open-Jev-DeBERTa-v3-large を、公開されている日本語感情分類データセットで比較しました。
今回の条件では、精度は Open-Jev-DeBERTa-v3-large が上でした。
一方で、推論速度は Laya Multilingual がかなり速いです。
| モデル | Accuracy | Macro F1 | Brier | ECE(15) | 平均処理時間/件 | Throughput |
|---|---|---|---|---|---|---|
| Open-Jev-DeBERTa-v3-large | 95.92% | 95.61% | 0.0334 | 0.0420 | 18.32 ms | 54.58/s |
| Laya Multilingual | 92.36% | 91.94% | 0.0673 | 0.0526 | 3.09 ms | 323.50/s |
ざっくり言うと、こうです。
- 精度優先なら Open-Jev-DeBERTa-v3-large
- 速度・多言語・軽さ優先なら Laya Multilingual
- どちらもテキスト生成モデルではなく、選択肢に対する確率分布を返す decision model
- 日本語で使うなら、モデルカードの説明だけではなく、自分のタスクで実測した方がよい
特に今回面白かったのは、Open-Jev-DeBERTa-v3-large はモデルカード上では English model なのに、日本語レビュー感情分類ではかなり強かったことです。
もちろん、これをもって「日本語汎用に強い」とは言えません。
ただ、binary sentiment のような比較的単純なタスクでは、DeBERTa-v3-large の表現力と sentiment 系の学習データが効いている可能性があります。
今回やったこと
やったことは単純です。
- Laya Multilingual をローカルにロードする
- Open-Jev-DeBERTa-v3-large をローカルにロードする
- 同じ日本語レビューを渡す
- 同じ
negative/positiveの二択で答えさせる - Accuracy、Macro F1、Brier、ECE、速度を比較する
評価データは Hugging Face の sbintuitions/JMTEB に含まれる japanese_sentiment_classification の test split を使いました。
JMTEB のデータセットカードでは、この Japanese Sentiment Classification は train 9,831 / dev 1,677 / test 2,552 件とされています。
今回使った test split は次の分布でした。
| label | 件数 |
|---|---|
| negative | 934 |
| positive | 1,618 |
| 合計 | 2,552 |
モデルの位置づけ
Laya Multilingual
Laya Multilingual は、非自己回帰型の System 1 decision model です。
通常の LLM のように文章を生成するのではなく、
- state
- typed question
- options / criteria
を渡すと、選択肢ごとの確率を返します。
モデルカードでは、100+ languages を対象にした checkpoint と説明されています。
Laya family の中では、英語以外の入力には convaiinnovations/laya-multilingual を使う、という位置づけです。
今回のような二値分類なら、Laya には次のように渡します。
import laya
agent = laya.load("convaiinnovations/laya-multilingual", device="cuda")
state = "毎回使って居て綺麗に仕上がるし、値段も安く買えてお得です。"
questions = {
"sentiment": {
"type": "choice",
"instructions": (
"この日本語レビューを感情分類してください。"
"選択肢: negative = 否定的、不満、低評価。"
"positive = 肯定的、満足、高評価。"
),
"criteria": ["negative", "positive"],
}
}
result = agent.predict(state, questions)
print(result["answers"]["sentiment"])
出力はだいたいこういう形です。
{
"type": "choice",
"choice": "positive",
"probabilities": {
"negative": 0.0008,
"positive": 0.9992,
},
"confidence": 0.9909,
}
ここで大事なのは、生成結果をパースしているのではないことです。
最初から choice と確率分布が返ります。
Open-Jev-DeBERTa-v3-large
Open-Jev-DeBERTa-v3-large も、Jev-shaped typed-decision model です。
こちらも文章生成ではなく、
choicescorenoul
という typed question に対して、選択肢ごとの確率分布を返します。
モデルカードによると、backbone は DeBERTa-v3-large で、choice は最大 255 options、score は 2〜10 ordered levels、noul は yes/no を扱います。
最小コードは次です。
from typed_decisions.open_jev import OpenJev
model = OpenJev.from_pretrained(
"com-kotobalabs/open-jev-deberta-v3-large",
device="cuda",
)
state = "毎回使って居て綺麗に仕上がるし、値段も安く買えてお得です。"
questions = [
{
"type": "choice",
"instructions": (
"この日本語レビューを感情分類してください。"
"選択肢: negative = 否定的、不満、低評価。"
"positive = 肯定的、満足、高評価。"
),
"options": ["negative", "positive"],
}
]
result = model.decide(state, questions)
print(result[0])
出力例です。
{
"choice": "positive",
"probabilities": {
"negative": 0.0077,
"positive": 0.9923,
},
"confidence": 0.9923,
}
こちらも同じく、自由生成ではありません。
業務システムから見ると、これはかなり扱いやすいです。
検証環境
今回の実行環境です。
| 項目 | 内容 |
|---|---|
| GPU | NVIDIA GeForce RTX 5080 Laptop GPU |
| PyTorch | 2.14.0+cu130 |
| CUDA | 13.0 |
| Python | 3.13.2 |
| transformers | 4.57.6 |
| datasets | 5.0.1 |
| laya | 0.3.4 |
| typed-decisions | GitHub 版 |
| 評価データ |
sbintuitions/JMTEB / japanese_sentiment_classification / test
|
| 評価件数 | 2,552 |
| batch size | 16 |
RTX 50 系はまだ環境差が出やすいので、古い PyTorch を使うと CUDA が見えない、またはカーネル周りで落ちる可能性があります。
今回は uv の --torch-backend cu130 で CUDA 13.0 対応の PyTorch を入れました。
セットアップ
uv で環境を作る
まず仮想環境を作ります。
mkdir laya-openjev-ja-bench
cd laya-openjev-ja-bench
env UV_CACHE_DIR="$PWD/.uv-cache" uv venv .venv
依存関係を入れます。
env UV_CACHE_DIR="$PWD/.uv-cache" \
uv pip install -p .venv/bin/python --torch-backend cu130 \
torch torchvision datasets laya \
git+https://github.com/kotoba-lang/typed-decisions
手元では torchvision も明示的に入れました。
理由は、transformers が内部で vision 系の import を踏むことがあり、環境によってはシステム側の torchvision と PyTorch の組み合わせがずれて落ちるからです。
Hugging Face のキャッシュを固定する
WSL、Docker、社内環境、Kaggle などでは、デフォルトのキャッシュディレクトリに書けないことがあります。
そのため、最初から作業ディレクトリ配下に固定しておく方が安全です。
export USE_TF=0
export HF_HOME="$PWD/.hf-cache"
export HF_HUB_CACHE="$PWD/.hf-cache/hub"
export HF_DATASETS_CACHE="$PWD/.hf-cache/datasets"
USE_TF=0 も入れています。
Laya のモデルカードにも注意がありますが、TensorFlow が入っている環境で transformers の import が余計な経路を踏むことがあります。
今回の用途では TensorFlow は不要なので、切っておくのが無難です。
Kaggle で動かす場合
Kaggle Notebook なら、最初のセルはだいたいこうです。
!pip install -U torch torchvision datasets laya
!pip install git+https://github.com/kotoba-lang/typed-decisions
Python 側では、キャッシュと TensorFlow 無効化を先に入れます。
import os
os.environ["USE_TF"] = "0"
os.environ["HF_HOME"] = "/kaggle/working/.hf-cache"
os.environ["HF_HUB_CACHE"] = "/kaggle/working/.hf-cache/hub"
os.environ["HF_DATASETS_CACHE"] = "/kaggle/working/.hf-cache/datasets"
Kaggle は GPU の種類が毎回同じとは限らないので、最初に確認します。
import torch
print(torch.__version__)
print(torch.cuda.is_available())
print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else "no cuda")
評価コードの要点
データセットはこれで読めます。
from datasets import load_dataset
ds = load_dataset(
"sbintuitions/JMTEB",
"japanese_sentiment_classification",
split="test",
)
print(ds)
print(ds[0])
ラベルは今回のデータでは次のように扱いました。
LABELS = {
0: "negative",
1: "positive",
}
両モデルに渡した質問文は同じです。
QUESTION = (
"この日本語レビューを感情分類してください。"
"選択肢: negative = 否定的、不満、低評価。"
"positive = 肯定的、満足、高評価。"
)
ここは意外と重要です。
モデルの比較でよくある罠は、片方だけに丁寧なプロンプトを渡してしまうことです。
今回は、Laya 側は criteria、Open-Jev 側は options という API 差分はありますが、ラベル名と説明は揃えました。
結果
改めて結果です。
| モデル | Accuracy | Macro F1 | Positive F1 | Negative F1 | Brier | Log loss | ECE(15) |
|---|---|---|---|---|---|---|---|
| Open-Jev-DeBERTa-v3-large | 95.92% | 95.61% | 96.78% | 94.44% | 0.0334 | 0.1345 | 0.0420 |
| Laya Multilingual | 92.36% | 91.94% | 93.78% | 90.10% | 0.0673 | 0.3124 | 0.0526 |
混同行列は次です。
| モデル | TP | TN | FP | FN |
|---|---|---|---|---|
| Open-Jev-DeBERTa-v3-large | 1564 | 884 | 50 | 54 |
| Laya Multilingual | 1470 | 887 | 47 | 148 |
Open-Jev は false negative がかなり少ないです。
今回の test set は positive が多いので、ここが Accuracy と Macro F1 の差に効いています。
一方で速度は Laya が圧倒的です。
| モデル | 推論時間 | 平均処理時間/件 | Throughput |
|---|---|---|---|
| Laya Multilingual | 7.89 sec | 3.09 ms | 323.50/s |
| Open-Jev-DeBERTa-v3-large | 46.76 sec | 18.32 ms | 54.58/s |
ここでの平均処理時間/件は、batch size 16 のバッチ処理時間を件数で割った値です。
API の単発リクエストの p95 latency とは別物です。
オンライン API 的に使うなら、batch size 1 でも測った方がよいです。
食い違いを少し見る
両モデルの正誤関係はこうでした。
| パターン | 件数 |
|---|---|
| 両方正解 | 2,306 |
| 両方不正解 | 53 |
| Open-Jev のみ正解 | 142 |
| Laya のみ正解 | 51 |
| 予測ラベルが食い違い | 193 |
Open-Jev のみ正解だった例には、短くて少し婉曲な positive review が多めに見えました。
例:
ちょうど旅行に行ったのでこれくらいないと印刷ができない。いっぱい入ってておとくです
gold は positive ですが、Laya は negative、Open-Jev は positive でした。
Laya のみ正解だった例もあります。
安かったかどうか良くわかりませんが、まあこんなもんでしょうね。印刷問題なしです。
これは gold positive で、Laya は positive、Open-Jev は negative でした。
このあたりを見ると、単純な正解率だけではなく、自分のドメインに多い文体で見る必要があります。
EC レビュー、問い合わせ、社内チケット、監査コメントでは、同じ sentiment でも表現がかなり違います。
踏みやすい地雷
1. RTX 50 系では PyTorch のバージョンを疑う
RTX 5080 Laptop は新しい GPU なので、古い PyTorch だと CUDA が見えないことがあります。
まずこれを確認します。
import torch
print(torch.__version__)
print(torch.version.cuda)
print(torch.cuda.is_available())
print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else "no cuda")
torch.cuda.is_available() が False のままなら、モデル比較以前の問題です。
2. Hugging Face の cache path は固定した方がいい
今回、デフォルトの cache path が root 配下になり、通常実行では lock file を作れずに落ちました。
最初からこうしておくのが安全です。
export HF_HOME="$PWD/.hf-cache"
export HF_HUB_CACHE="$PWD/.hf-cache/hub"
export HF_DATASETS_CACHE="$PWD/.hf-cache/datasets"
Docker や CI でも同じです。
「一度ダウンロードしたのに、次の実行でなぜか落ちる」という事故を避けられます。
3. Laya は USE_TF=0 を入れておく
TensorFlow が入っている環境では、transformers が不要な TensorFlow 側の probe を踏むことがあります。
今回のタスクでは使わないので、切っておく方が安全です。
export USE_TF=0
4. torchvision の不整合で落ちることがある
手元では Laya ロード時に torchvision::nms 周りのエラーが出ました。
原因は、venv 側の PyTorch と、システム側の torchvision の組み合わせがずれていたことです。
venv 内に対応する torchvision を入れると解消しました。
uv pip install -p .venv/bin/python --torch-backend cu130 torchvision
モデルの問題に見えて、実は import 経路の問題というやつです。
5. 確率はそのまま信用しすぎない
Laya のモデルカードにも calibration の注意があります。
Open-Jev も Brier / ECE は悪くありませんでしたが、それでも本番で確率を閾値制御に使うなら、自分のデータで calibration を見た方がよいです。
分類ラベルだけを見るのか。
確率を使って human review に回すのか。
ここで必要な評価指標が変わります。
どちらを使うべきか
今回の結果だけで判断するなら、次のようになります。
Open-Jev-DeBERTa-v3-large が向いているケース
- 日本語感情分類で精度を優先したい
- 多少遅くてもよい
- 二択、多択、score、yes/no を同じ API で扱いたい
- 確率分布つきの structured output が欲しい
- DeBERTa-v3-large クラスの重さを許容できる
今回の benchmark では、精度、Macro F1、Brier、ECE のすべてで Open-Jev が上でした。
Laya Multilingual が向いているケース
- 多言語入力を扱う
- throughput を重視する
- 1 GPU に軽く載せたい
- 大量の問い合わせやチケットを高速に振り分けたい
- 多少の精度差より処理量が重要
Laya は速いです。
今回の条件では Open-Jev の約 6 倍の throughput が出ました。
日本語・英語・その他言語が混ざる業務で、まず高速に route したいなら Laya はかなり扱いやすい候補です。
最小の評価スクリプト
記事用に短くすると、評価ループはこういう形になります。
from datasets import load_dataset
ds = load_dataset(
"sbintuitions/JMTEB",
"japanese_sentiment_classification",
split="test",
)
correct = 0
for row in ds:
text = row["text"]
gold = "positive" if row["label"] == 1 else "negative"
pred, probs = predict_one(text) # Laya または Open-Jev の wrapper
if pred == gold:
correct += 1
print(correct / len(ds))
実際には、次も保存した方がよいです。
- gold label
- predicted label
prob_negativeprob_positive- latency
- text
あとから「どちらだけが間違えたのか」を見られるからです。
モデル比較で一番おいしいのは、平均値ではなく disagreement の中身です。
まとめ
今回の日本語感情分類では、Open-Jev-DeBERTa-v3-large が精度面で勝ちました。
ただし、Laya Multilingual はかなり速いです。
精度で 3.6 points ほど負けていますが、throughput は約 6 倍でした。
なので、結論は単純な「どちらが上か」ではありません。
- 精度重視: Open-Jev-DeBERTa-v3-large
- 速度・多言語・軽量運用重視: Laya Multilingual
- 本番投入前: 必ず自分の文体、自分のラベル、自分の閾値で再評価
特に decision model は、生成 AI と違って出力形式が安定しています。
これは業務システムに組み込みやすいです。
一方で、確率が返るからといって、その確率がそのまま業務上の信頼度になるとは限りません。
分類の正解率、Brier、ECE、そして disagreement の実例まで見て、使う場所を決めるのが良いと思います。