Downloads · 30 days
22
36% of all-time downloads
mogita/jev-decider-qwen3-4b
jev-decider-qwen3-4b is a text classification model from mogita. Use it when you need a label for a piece of text. It is set up for peft. The card lists the license as apache-2.0.
LoRA adapter that picks a budget category for a bank transaction in one forward pass. Categories are listed as lettered options; the answer is read from the logits at those letter tokens and softmaxed. Nothing is gene…
Downloads · 30 days
22
36% of all-time downloads
All-time downloads
61
Public
Repo size
132 MB
Likes
1
Public
Click a slice to open those files.
.safetensors132 MB · 93%
From the Hugging Face model README
LoRA adapter that picks a budget category for a bank transaction in one forward pass. Categories are listed as lettered options; the answer is read from the logits at those letter tokens and softmaxed. Nothing is generated.
Demo: mojev.mogita.rocks. Code: github.com/mogita/jev-decider.
Base is Qwen3-4B, no adapter, same rows and prompts. Verdict is McNemar's exact test on the rows where the two disagree, p < 0.05.
| Source | Base | This | Verdict |
|---|---|---|---|
sms_spam | 58.8% | 83.5% | better |
chaosnli | 59.2% | 66.2% | better |
measuring_hate_speech | 36.7% | 39.8% | better |
strategyqa_grounded | 81.1% | 83.1% | better |
boolq | 84.9% | 86.8% | better |
helpsteer2_helpfulness | 35.8% | 38.9% | better, p=0.049 |
sst5 | 47.6% | 50.4% | better, p=0.035 |
mnli | 81.8% | 81.8% | same |
fever_evidence | 91.5% | 91.6% | same |
mmlu | 65.9% | 66.4% | same |
helpsteer2_verbosity | 59.6% | 59.0% | same |
strategyqa_closed | 62.3% | 63.2% | same |
yelp5 | 63.2% | 64.0% | same |
paws | 77.8% | 77.2% | same |
arc_challenge | 87.1% | 87.8% | same |
civil_comments | 63.7% | 60.6% | worse |
stsb | 41.6% | 30.4% | worse |
5 better, 2 at p just under 0.05, 8 same, 2 worse.
| Macro | Base | This | Verdict |
|---|---|---|---|
| Accuracy | 64.6% | 66.5% | better |
Accuracy, minus sms_spam | 65.0% | 65.4% | same |
| ECE, uncalibrated | 32.9% | 21.4% | better |
| ECE, temperature fitted | 7.6% | 6.5% | better |
ECE, fitted, minus chaosnli + measuring_hate_speech | 4.8% | 5.7% | worse |
| TVD to human label distributions | 47.6% | 42.9% | better |
| Brier (0 to 2, not a percentage) | 0.671 | 0.524 | better |
Per-source verdicts are the test. Macro verdicts are direction only, except minus sms_spam, which is +0.45 points and inside the noise.
| Model | Accuracy | ECE | Brier |
|---|---|---|---|
| This adapter, temperature fitted | 66.5% | 6.5% | 0.428 |
| This adapter, uncalibrated | 66.5% | 21.4% | 0.524 |
| Jev 1.13.0 (commercial API) | 74.0% | 10.4% | 0.323 |
| Qwen3.5-4B, LoRA + residual heads | 74.7% | 9.7% | 0.324 |
| Qwen3.5-9B, frozen | 70.1% | 9.5% | 0.354 |
| Qwen3.5-4B, frozen | 67.0% | 8.7% | 0.373 |
chaosnli ships no validation split, so neither model was fitted on it.| Measure | Base | This |
|---|---|---|
| Dutch holdout, top-1 | 64.1% | 83.6% |
| Dutch holdout, top-3 | — | 94.5% |
| Dutch holdout, balanced | — | 69.7% |
| US test, top-1 | — | 96.3% |
| US test, balanced | — | 96.0% |
| ECE, 15 bins, uncalibrated | — | 11.7% |
| AG News, caller's own labels | — | 87.0% |
| dair-ai/emotion, caller's own labels | — | 46.3% |
Merchant-disjoint: no merchant is in both training and test. The Dutch holdout is 128 rows, so a few points is noise.
Latency: 201 ms median, batch 1, Apple M4 Max via MLX.
import string, torch
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
BASE = "Qwen/Qwen3-4B"
tokenizer = AutoTokenizer.from_pretrained(BASE)
model = PeftModel.from_pretrained(
AutoModelForCausalLM.from_pretrained(BASE, dtype=torch.bfloat16, device_map="auto"),
"mogita/jev-decider-qwen3-4b").eval()
CATEGORIES = ["🏥 Healthcare", "✈️ Travel", "🍽️ Restaurants", "🎓 Education",
"🎟️ Entertainment", "🏦 Fees", "🐾 Pets", "💡 Utilities", "💵 Income",
"🔄 Internal Transfer", "🔑 Rent, Loan", "🔔 Subscriptions",
"🚗 Transportation", "🛍️ Shopping", "🛒 Groceries", "🛡️ Insurance",
"🧴 Personal Care"]
PROMPT = """Below is a bank transaction.
{state}
Which budget category does it belong to?
{options}
Reply with the single letter only."""
def decide(record, categories=CATEGORIES):
options = "\n".join(f"{c}. {label}" for c, label in zip(string.ascii_uppercase, categories))
text = tokenizer.apply_chat_template(
[{"role": "user", "content": PROMPT.format(state=record, options=options)}],
tokenize=False, add_generation_prompt=True, enable_thinking=False)
ids = tokenizer(text, return_tensors="pt").to(model.device)
letters = [tokenizer.encode(c, add_special_tokens=False)[0]
for c in string.ascii_uppercase[:len(categories)]]
with torch.no_grad():
logits = model(**ids).logits[0, -1, letters].float()
probs = torch.softmax(logits, dim=-1)
return categories[probs.argmax()], probs.max().item()
print(decide("Payee: Albert Heijn\nAmount: -37.82 EUR\nDate: 2026-03-12"))
# ('🛒 Groceries', 0.996)
Pass any 2 to 26 labels for your own categories. That path rides the base model's ability, not anything taught here, and is not covered by the numbers above.