mahiyama/query-understanding-ja-4b
query-understanding-ja-4b
日本語の商品検索クエリを、検索システムで扱いやすい構造化 JSON に変換する Query Understanding モデルです。Qwen/Qwen3.5-4B を LoRA でファインチューニングし、アダプターをベースモデルへマージした完全なチェックポイントとして公開しています。
このモデルは、公開データセットである Amazon Shopping Queries Dataset(ESCI)の日本語クエリを使って、小規模なデータと LoRA で小型言語モデルを検索クエリ解釈に特化できるかを検証した PoC(概念実証)です。特定の EC サイトのクエリログや商品カタログに合わせて調整したものではなく、そのまま本番の検索システムに組み込むことを想定していません。実際のサービスに使う場合は、対象サイトのデータでの再学習と評価が必要です。
transformers で trustremotecode=True を付けて読み込むと parse メソッドが使えるようになり、クエリ文字列を渡すだけで構造化した結果(Python の dict)が返ります。カスタムコードを使わずに通常の Qwen3.5 モデルとして読み込むことも、vLLM でそのまま配信することもできます。
This model converts a Japanese e-commerce search query into a structured JSON object (product type, full-text search terms, inclusion filters, exclusion filters, and unresolved conditions). It is Qwen/Qwen3.5-4B fine-tuned with LoRA on 24,146 queries derived from the Japanese subset of the Amazon Shopping Queries Dataset (ESCI); the adapter is merged into the checkpoint. Load with trustremotecode=True and call model.parse(query). This is a proof of concept built on the public ESCI dataset, not a production-ready component; retrain and evaluate on your own query logs before using it in a real search system.
何をするモデルか
検索ボックスに入力される短い文字列には、商品種別、希望条件、除外条件、価格などの条件が混ざっています。これをそのまま全文検索へ渡すと、除外したいはずのブランド名まで通常の検索語として扱われるなど、意図が検索条件に反映されません。このモデルは、クエリを次の 5 項目に分解します。
入力:
ロジクール以外の静音ワイヤレスマウス 黒出力:
{
"product_type": "マウス",
"search_terms": ["静音", "ワイヤレス", "マウス"],
"include": {
"color": ["黒"],
"feature": ["静音", "ワイヤレス"]
},
"exclude": {
"brand": ["ロジクール"]
},
"unresolved": []
}検索システム側では、search_terms を全文検索へ、include をフィルターやブーストへ、exclude を除外や降格へ渡します。unresolved には、価格や送料のように商品属性のスキーマでは扱えない条件が原文のまま入るので、後段で処理の可否を判断できます。モデルは検索エンジン固有のクエリ構文を生成せず、用途に依存しない JSON を返すだけなので、Elasticsearch などのクエリへの変換はアプリケーション側の決定的なルールで行えます。
出力スキーマ
出力は常に次の 5 つのキーを持つ JSON オブジェクト 1 つです。
include と exclude で使える属性は次の 9 種類です。
主な規則は次のとおりです。
- クエリに明示された情報だけを抽出し、世界知識で補完しない(「iphone8 ケース」の brand に apple を入れない)
- include と exclude の値は必ずクエリ中に出現する語句を使う。表記は NFKC 正規化(全角英数字を半角に)だけを行い、言い換え、翻訳、正式名称への変換、誤字の修正はしない
- 同じ語を include の複数の属性に入れない。brand と color 以外の属性の値は search_terms にも含める
- 複数語からなる 1 つの名称(usb type c など)は分割しない
値はクエリ中の表記のままなので、ブランドの別名や色の表記揺れの統一は検索システム側の辞書で行う想定です。
使い方
transformers(parse メソッド)
import torch
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained(
"mahiyama/query-understanding-ja-4b",
trust_remote_code=True,
dtype=torch.bfloat16,
device_map="cuda",
)
result = model.parse("ロジクール以外の静音ワイヤレスマウス 黒")
print(result)
# {'product_type': 'マウス', 'search_terms': ['静音', 'ワイヤレス', 'マウス'],
# 'include': {'color': ['黒'], 'feature': ['静音', 'ワイヤレス']},
# 'exclude': {'brand': ['ロジクール']}, 'unresolved': []}
# 複数のクエリはリストで渡すとバッチ推論になり、同じ順序のリストが返る
results = model.parse([
"ナイキ スニーカー メンズ 27cm 白以外",
"子供が使える軽いテニスラケットを探しています 5000円以下",
])parse メソッドの仕様は次のとおりです。
- 引数は文字列 1 つ、または文字列のリスト。文字列なら dict を、リストなら dict のリストを返す
- 生成は貪欲デコード(dosample=False)で、既定の maxnew_tokens は 256。通常の出力は 30 から 40 トークン程度
- 返り値は正規化済み(NFKC、重複の除去、空の属性の省略、キー順の固定)。生成テキストが JSON として解釈できない場合、そのクエリの結果は None
- return_raw=True を指定すると、各要素が (dict または None, 生成テキスト) のタプルになる
- batch_size(既定 16)でバッチの大きさを変えられる。generate に渡す追加の引数はキーワード引数としてそのまま渡せる
- understand は parse の別名
- tokenizer はリポジトリから自動で読み込まれる。model.tokenizer に代入すると差し替えられる
学習時と同じ system プロンプトとチャットテンプレート(思考過程の生成を無効化した形式)を内部で組み立てるので、利用側でプロンプトを用意する必要はありません。
カスタムコードを使わない場合
このチェックポイントはベースモデル Qwen/Qwen3.5-4B と同じレイアウトなので、trustremotecode なしで通常の Qwen3.5 モデルとして読み込めます。その場合は、次の system プロンプトを付け、思考過程を無効化(enable_thinking=False)してチャットテンプレートを適用してください。
商品検索クエリを解析し、次のキーを持つJSONのみを出力してください: product_type, search_terms, include, exclude, unresolved (属性キー: brand, color, size, material, target, compatible_with, feature, quantity, model_number)import json
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
repo = "mahiyama/query-understanding-ja-4b"
tok = AutoTokenizer.from_pretrained(repo)
model = AutoModelForCausalLM.from_pretrained(repo, dtype=torch.bfloat16, device_map="cuda")
SYSTEM = ("商品検索クエリを解析し、次のキーを持つJSONのみを出力してください: "
"product_type, search_terms, include, exclude, unresolved "
"(属性キー: brand, color, size, material, target, compatible_with, feature, quantity, model_number)")
messages = [{"role": "system", "content": SYSTEM},
{"role": "user", "content": "ナイキ スニーカー メンズ 27cm 白以外"}]
inputs = tok.apply_chat_template(messages, add_generation_prompt=True, enable_thinking=False,
return_tensors="pt", return_dict=True).to(model.device)
out = model.generate(**inputs, max_new_tokens=256, do_sample=False)
text = tok.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
print(json.loads(text))vLLM
マージ済みの完全なチェックポイントなので、vLLM では通常の Qwen3.5 モデルとして配信できます。
vllm serve mahiyama/query-understanding-ja-4b --max-model-len 4096OpenAI 互換 API では、上記の system プロンプトを付け、temperature を 0 にし、chattemplatekwargs で enable_thinking を false にしてください。
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")
resp = client.chat.completions.create(
model="mahiyama/query-understanding-ja-4b",
messages=[{"role": "system", "content": SYSTEM},
{"role": "user", "content": "iphone15 pro ケース 手帳型 マグネットなし"}],
temperature=0.0,
max_tokens=256,
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
)
print(resp.choices[0].message.content)LoRA アダプター
マージ前の LoRA アダプター(PEFT 形式、rank 64、alpha 128)を lora-adapter フォルダに同梱しています。ベースモデルに後から適用する場合は次のようにします。
import torch
from transformers import AutoModelForCausalLM
from peft import PeftModel
base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3.5-4B", dtype=torch.bfloat16, device_map="cuda")
model = PeftModel.from_pretrained(base, "mahiyama/query-understanding-ja-4b", subfolder="lora-adapter")動作環境
- Qwen3.5 に対応した transformers(5.x 系)が必要です。学習と評価は transformers 5.16.1、公開前の動作確認は transformers 5.13.1 と PyTorch 2.11 で行いました
- bfloat16 で言語モデル部分を GPU に載せると約 8 GB のメモリを使います。GPU メモリが足りない場合は device_map="auto" で CPU と分割して読み込めます(速度は落ちます)
- Qwen3.5 は線形アテンション層(Gated DeltaNet)を含みます。flash-linear-attention と causal-conv1d がインストールされていれば高速なカーネルが使われ、なければ PyTorch 実装で動作します
学習
学習データ
学習データは 24,146 件で、すべて Amazon Shopping Queries Dataset(ESCI) の日本語サブセットに由来します。ESCI の test 分割は学習、合成、選別のいずれにも使わず、評価専用にしています。
- 実クエリ 12,043 件: ESCI 日本語 train 分割のユニークなクエリ(正規化した文字列が test と一致するものは除外)
- 合成クエリ 12,103 件: 日本語の実クエリは平均約 10 文字で 6 割以上が 1〜2 語のため、不足する検索意図を補うために合成した。train 側にだけ現れる商品のタイトル、ブランド、色を種にして、ブランド指定、色指定、ブランドや色や機能の否定、自然文、価格や送料などの未解決条件、対応機種、型番、3 条件以上の複合、表記揺れ(全角、かな、連結、大小文字、軽い誤字)、数量の 12 パターンを Qwen/Qwen3.5-27B に生成させた
正解となる構造化 JSON は Qwen/Qwen3.5-27B が作成しました。27B モデルには 5 項目と 9 属性の詳細な定義を含むガイドラインと 18 件の例を与え、思考過程なしの貪欲デコードで JSON を生成させています。生成した JSON は次の検証を通過したものだけを採用しました。
- JSON として解釈でき、必須キー、型、属性名がスキーマに合うこと
- include と exclude の値がクエリ中に存在すること
- ブランドと色の値を searchterms から外す、除外対象の語を searchterms から外すなど、決定的な規則を後処理で適用すること
学習例は system、user、assistant の 3 メッセージで、system は上記の短い固定プロンプト、user はクエリ、assistant は JSON だけです。詳細なガイドラインはラベル生成時にだけ使い、4B モデルにはその判断規則をファインチューニングで学習させたので、推論時に長い指示や例を送る必要はありません。
学習設定
評価
指標
- Exact Match: producttype、searchterms、include、exclude、unresolved の 5 項目すべてが正解と一致したクエリの割合。比較は NFKC 正規化、大文字小文字の同一視、空白の除去を行った上での完全一致で、search_terms の順序は問いません。JSON として解釈できない出力は不正解です
- Attribute F1: include と exclude に含まれる(属性名, 値)の組を全クエリで集計した micro F1。検索の絞り込みと除外に直接使う条件の抽出精度を表します
- JSON 有効率: 出力が JSON として解釈でき、スキーマに合致した割合
- grounding 率: 出力した include と exclude の値のうち、クエリ中に実在する語句の割合(条件の捏造がないこと)
評価セット
- 実クエリ 600 件: ESCI 日本語 test 分割のクエリから作成。Qwen3.5-27B にクエリごとに 3 回ラベルを生成させ、3 回とも一致した判断の安定した例 300 件と、一致しなかった判断の難しい例 300 件を選び、その全件を AI エージェントがガイドラインに照らして 1 件ずつ確認して 91 件を修正した。難しいクエリを意図的に半数含むため、この値は ESCI 全体の平均精度ではなく、モデル間の比較値として扱う
- 合成クエリ 711 件: 学習に使っていない評価側の商品情報から、否定表現、複数条件、対応機種、価格条件などの検索意図を狙って合成したクエリ。正解ラベルは Qwen3.5-27B が生成したものなので、同じ 27B モデルの評価には有利に働く
結果
実クエリ 600 件:
合成クエリ 711 件:
数値はすべて百分率です。このモデルの Exact Match の 95% 信頼区間(クエリ単位のブートストラップ)は、実クエリ 600 件で 65.5 から 72.8、合成クエリ 711 件で 64.8 から 71.7 です。実クエリでは、約 70 トークンの短い指示だけで、ガイドラインと 18 例を毎回与えた 27B モデルと同程度の精度になっています。
このモデルの項目別の値:
属性別の F1(括弧内は正解に含まれる組の数):
参考として、同じデータと設定で学習したベースモデルのサイズ違いの結果を示します。
推論速度
vLLM 0.29.0 の OpenAI 互換サーバー(NVIDIA RTX PRO 6000 Blackwell 96 GB 1 枚、bfloat16、同時実行数 1、貪欲デコード)に 200 件のクエリを 1 件ずつ送って測定した応答時間です。TTFT はリクエスト送信から最初のトークンを受け取るまで、E2E は生成完了までの時間です。
検索リクエストごとに毎回オンラインで推論するには、数百ミリ秒の応答時間は無視できません。実運用では、頻出クエリの解釈結果をオフラインで事前生成してキャッシュし、キャッシュにないクエリだけをオンラインで推論する構成を推奨します。多数のクエリをまとめてバッチ推論する場合のスループットは、単独リクエストの応答時間から想像するよりずっと高くなります。
制限事項
- 本モデルは公開データセット ESCI を使った PoC であり、本番運用を想定した品質保証や継続的な評価は行っていません。実際のサービスに使う場合は、対象サイトのクエリログでの再学習と評価、出力スキーマとガイドラインの見直し、検索側の正規化辞書との接続が必要です
- 値はクエリ中の表記をそのまま使うため、ブランドの別名や色の表記揺れの統一(ブラック、黒、black など)は検索システム側で行う必要があります
- 商品種別と属性の境界(対応機種かブランドか、機能か検索語か)、複合語の分割、「〜なし」の解釈には、ラベル作成時のガイドラインに由来する揺れがあり、Exact Match の上限はラベル自体の一致率に近い水準です
- 空白を入れずに語を連結したクエリでは語の切れ目が曖昧になり、空白区切りのクエリより精度が下がる傾向があります
- 学習データは Amazon の日本語検索クエリに基づくため、他の言語や、商品検索以外のドメインでは再学習が必要です
- 設計上、クエリに書かれていない情報は補完しません。ブランドから商品種別を推測したり、型番からメーカーを特定したりはしません
- 正解ラベルは大規模言語モデルが生成したもので、人手のアノテーションではありません。評価値も同じ性質のラベルに基づく比較値として扱ってください
ライセンスと出典
- このモデルのライセンスは Apache-2.0 です
- ベースモデル: Qwen/Qwen3.5-4B(Apache-2.0)
- ラベル生成と合成クエリの作成: Qwen/Qwen3.5-27B(Apache-2.0)
- データ: Amazon Shopping Queries Dataset(ESCI) の日本語サブセット(Apache-2.0、論文)
@article{reddy2022shopping,
title = {Shopping Queries Dataset: A Large-Scale {ESCI} Benchmark for Improving Product Search},
author = {Chandan K. Reddy and Lluís Màrquez and Fran Valero and Nikhil Rao and Hugo Zaragoza and Sambaran Bandyopadhyay and Arnab Biswas and Anlu Xing and Karthik Subbian},
journal = {arXiv preprint arXiv:2206.06588},
year = {2022}
}このモデルを引用する場合:
@misc{query-understanding-ja-4b,
title = {query-understanding-ja-4b: Japanese product search query understanding with Qwen3.5-4B and LoRA},
author = {mahiyama},
year = {2026},
url = {https://huggingface.co/mahiyama/query-understanding-ja-4b}
}