navspec-lora-0.8b-v0.2 (Experimental)
艦艇の文脈(国籍・年・艦種・船体寸法・排水量)と主砲構成案を入力とし、
兵装ルールへの適合判定と違反理由コードを navspec.armament_verdict.v1 形式の JSON で返す実験的 LoRA アダプタです。
v0.1(Qwen2.5-0.5B)の作り直しです。v0.1 は判定を学習できておらず(全入力に ok を返す)、
公開していた PEFT 重みにも変換不具合があったため非公開にしました。
[!NOTE] 免責
- 研究・実験段階のプロトタイプです。判定の正確性や実務・ゲーム運用での信頼性は保証しません。
- 学習データはルールエンジンが生成した合成データで、架空の設計ルール体系に基づきます。現実・史実の兵装仕様や工学的制約とは一致しない場合があります。
- 下記の評価値は test 142 件での観測値です。分布外の入力では誤った判定や理由コードを出す可能性があります。
出力
{"schema_version": "navspec.armament_verdict.v1", "status": "impossible", "reason_codes": ["armament.mount_unavailable_for_nation", "armament.weapon_mount_incompatible"]}
status は ok / impossible。reason_codes はエラー理由のみ(ok のときは空配列)。
評価(test 142 件, greedy, thinking 無効)
| 指標 | 値 |
|---|---|
| JSON として読める | 1.000 |
| 出力契約を満たす | 1.000 |
| status 正解率 | 1.000(多数派ベースライン 0.570) |
| reason code precision / recall / F1 | 0.975 / 0.841 / 0.903 |
| reason code 集合の完全一致 | 0.894 |
| 定義外の reason code | 0 |
同じ設定で seed を変えた 3 本では、status は 3 本とも 1.000、reason F1 は 0.903 / 0.882 / 0.867 でした(公開物は seed 0)。
reason code 別(test):
| reason code | support | recall | 備考 |
|---|---|---|---|
| weapon_mount_incompatible | 36 | 1.00 | |
| firing_arc_conflict | 36 | 1.00 | |
| mount_unavailable_for_nation | 21 | 1.00 | |
| fire_control_unavailable | 9 | 1.00 | |
| mount_unavailable_for_era | 18 | 0.78 | 砲塔の就役年が入力に無い |
| turret_weight_exceeds_hull_limit | 9 | 0.00 | 砲塔重量が入力に無い |
| armament_displacement_conflict | 9 | 0.00 | 兵装重量が入力に無い |
入力に含まれない DB 値(就役年・重量)を要する理由コードは、ほとんど判定できません。 最終判定には、ルールベースの validator を併用してください。
学習
- ベース:
Qwen/Qwen3.5-0.8B(テキストのみ。AutoModelForCausalLMでQwen3_5ForCausalLMとして読み込み) - LoRA: r=16, alpha=32, dropout=0.05, 全 24 層
- full attention 層:
q_proj, k_proj, v_proj, o_proj - Gated DeltaNet 層:
in_proj_qkv, in_proj_z, in_proj_b, in_proj_a, out_proj - MLP:
gate_proj, up_proj, down_proj
- full attention 層:
- データ: 合成の兵装批評データ(train 679 / valid 159 / test 142)。assistant 部分のみで損失を計算
- lr 2e-4 cosine(warmup 5%), 5 epoch(424 step, 実効バッチ 8), bf16, RTX 3060
- best checkpoint は valid の status トークン正解率で選択
- torch 2.9.1, transformers 5.18.0, peft 0.21.2
使い方
thinking は無効(enable_thinking=False)にしてください。 学習も評価も、この設定で行っています。
CUDA GPU を前提にした例です(bf16 で約 2GB の VRAM)。flash-linear-attention / causal-conv1d を入れると Gated DeltaNet 層が高速になりますが、
causal-conv1d を入れた環境で device_map="auto" が層を CPU に逃がすと、その層でエラーになります。
import json, torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
base = "Qwen/Qwen3.5-0.8B"
tok = AutoTokenizer.from_pretrained(base)
model = AutoModelForCausalLM.from_pretrained(base, dtype=torch.bfloat16).to("cuda")
model = PeftModel.from_pretrained(model, "j-llm/navspec-lora-0.8b-v0.2")
system = ("You are navspec-armament-judge. Given the ship context and proposed main battery, "
"return only JSON matching navspec.armament_verdict.v1. The top-level keys must be "
"schema_version,status,reason_codes. Use status=impossible when any armament rule is violated; "
"otherwise use status=ok. reason_codes must contain only the error reason codes, as a JSON string array.")
request = {
"task": "judge_navspec_armament", "nation": "FR", "year": 1944,
"ship_type": "destroyer", "role": "long_range_scout",
"hull": {"beam_m": 10.73, "displacement_t": 1913.7},
"candidate": {
"schema_version": "navspec.armament_candidate.v1",
"design_rationale": "2 x single mount of 139mm 138.6 mm/40 Mle 1923 (Bison/Pluton) (FR, 1929)",
"confidence": 0.85,
"main_batteries": [{
"battery_id": "main_1", "weapon_id": "FR_139MM_M1923", "mount_id": "AH_305MM_TRIPLE_SKODA_K10",
"mount_count": 2, "barrels_per_mount": 3, "total_barrels": 6,
"positions": ["A", "X"], "fire_control_id": "FR_M1922_FC", "ammo_per_gun": 100}],
},
}
messages = [{"role": "system", "content": system},
{"role": "user", "content": json.dumps(request, ensure_ascii=False, separators=(",", ":"), sort_keys=True)}]
prompt = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True, enable_thinking=False)
inputs = tok(prompt, return_tensors="pt", add_special_tokens=False).to(model.device)
out = model.generate(**inputs, max_new_tokens=64, do_sample=False)
print(tok.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True))
weapon_id / mount_id / fire_control_id は学習データの兵装 DB(11 か国, 1910–1949)にある ID を前提にしています。
GGUF 版(マージ済み): j-llm/qwen35-0.8b-navspec-gguf