Text Classification
Transformers
Safetensors
Laya
English
decision-model
system-one
rlcd
typed-decisions
calibrated
Instructions to use winwinwinbb/soft-decider-421m with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use winwinwinbb/soft-decider-421m with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="winwinwinbb/soft-decider-421m")# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("winwinwinbb/soft-decider-421m", device_map="auto") - Laya
How to use winwinwinbb/soft-decider-421m with Laya:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
File size: 8,510 Bytes
ad67f6b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | """Gold-preserving data augmentation (open-jev recipe) -> train_items_aug.pt + calib_items.
The five augmentations, each label-preserving by construction, applied at p=0.7 like
com-kotobalabs/open-jev-deberta-v3-large (their in-domain 0.854 / OOD 0.690 gap is what
this family of tricks attacks):
1. option shuffle - permute option render order; target permuted identically
2. instruction paraphrase - deterministic templates, semantics untouched
3. distractor drop - choice only: drop non-gold options with p<0.05, renormalise
4. level relabel - score only: shift the "level N" numbers by a constant
5. noul negation - "It is NOT the case that ..." with flipped target
The calibration slice uses the SAME seed/order as train_single.py's internal split and is
kept CLEAN (never augmented, never trained on), so temperature fitting stays honest.
Run: D:\\venvs\\voxcpm-cuda\\Scripts\\python.exe d:\\QoderCN\\jev_finetune\\preprocess_aug.py
"""
from __future__ import annotations
import json
import os
import random
import sys
import time
os.environ.setdefault("HF_ENDPOINT", "https://hf-mirror.com")
os.environ.setdefault("USE_TF", "0")
sys.stdout.reconfigure(encoding="utf-8")
WORK = os.path.dirname(os.path.abspath(__file__))
TRAIN_PARQUET = os.path.join(WORK, "td_all_train.parquet")
OUT = os.path.join(WORK, "train_items_aug.pt")
CALIB_MAX = 400
CALIB_SEED = 20260922 # must match train_single.py so the calib slice is identical
P_AUG = 0.7
PARAPHRASE = [
"{ins}",
"Considering the state, {ins_lower}",
"Decide: {ins_lower}",
"Based only on the information given, {ins_lower}",
]
def variants_for(q, gold_q, rng):
"""Yield (question_dict, target_list) variants of one (question, gold) pair, always incl. the base."""
t = q["type"]
crit = q.get("criteria", {})
ins = q["instructions"]
# base distribution aligned to render order
if t == "choice":
keys = list(crit.keys())
base = [gold_q["probabilities"].get(k, 0.0) for k in keys]
opts = {"t": t, "crit": crit}
elif t == "noul":
base = [gold_q["probabilities"].get("false", 0.5), gold_q["probabilities"].get("true", 0.5)]
opts = {"t": t, "crit": crit}
else:
n = len(crit) if isinstance(crit, list) else 4
base = [gold_q["probabilities"].get(str(i), 0.0) for i in range(n)]
opts = {"t": t, "crit": crit}
s = sum(base)
base = [v / s for v in base] if s > 0 else [1.0 / len(base)] * len(base)
yield {"t": t, "ins": ins, "crit": opts["crit"]}, base
if rng.random() > P_AUG:
return
vs = [(ins, base, opts["crit"])]
# 1. option shuffle (choice only; score levels are ordinal and stay in level order)
if t == "choice" and len(base) >= 2 and rng.random() < 0.8:
perm = list(range(len(base)))
rng.shuffle(perm)
keys = list(crit.keys())
new_crit = {keys[perm[m]]: crit[keys[perm[m]]] for m in range(len(base))}
new_target = [base[perm[m]] for m in range(len(base))]
vs.append((ins, new_target, new_crit))
# 2. instruction paraphrase
if rng.random() < 0.6:
tpl = rng.choice(PARAPHRASE[1:])
new_ins = tpl.format(ins=ins, ins_lower=ins[0].lower() + ins[1:] if ins else ins)
vs.append((new_ins, vs[-1][1], vs[-1][2]))
# 3. distractor drop (choice, >=5 opts): drop non-gold with prob < 0.05
if t == "choice" and len(base) >= 5 and rng.random() < 0.5:
keys = list(crit.keys())
top = max(range(len(base)), key=lambda i: base[i])
drop = {i for i, v in enumerate(base) if i != top and v < 0.05}
drop -= {top}
if drop and len(base) - len(drop) >= 3:
keep = [i for i in range(len(base)) if i not in drop]
new_crit = {keys[i]: crit[keys[i]] for i in keep}
nt = [base[i] for i in keep]
vs.append((ins, [v / sum(nt) for v in nt], new_crit))
# 4. level relabel (score): shift the "level N" numbers; keys keep level order so the
# target is untouched; rendered through the choice path with pre-numbered option texts
if t == "score" and rng.random() < 0.5:
sh = rng.choice([1, 2])
new_crit = {str(i): "level %d: %s" % (i + sh, c) for i, c in enumerate(crit)}
vs.append((ins, base, new_crit))
# 5. noul negation with flipped gold
if t == "noul" and rng.random() < 0.5:
vs.append(("It is NOT the case that: " + ins[0].lower() + ins[1:], [base[1], base[0]], crit))
seen = set()
for ins2, tgt2, crit2 in vs[1:]:
key = (ins2, tuple(round(x, 6) for x in tgt2), json.dumps(crit2, sort_keys=True) if isinstance(crit2, dict) else str(crit2))
if key in seen:
continue
seen.add(key)
yield {"t": t, "ins": ins2, "crit": crit2}, tgt2
def to_item(tok, cfg, state, q, target):
from laya.common import build_sequence, render_options, QTYPES
t = q["t"]
# score relabelling arrives as a dict of pre-numbered strings -> render via the choice path
tv = "choice" if (t == "score" and isinstance(q["crit"], dict)) else t
kk = len(render_options({"t": tv, "crit": q["crit"]}))
seq, markers = build_sequence(tok, state, {"t": tv, "ins": q["ins"], "crit": q["crit"]},
cfg["max_len"], cfg["head_max_len"])
if len(markers) != kk:
return None
qt = QTYPES["score"] if t == "score" else QTYPES[t]
label = target.index(max(target))
return {"ids": seq, "markers": markers, "qtype": qt, "target": target, "label": label}
def main() -> None:
import torch
import pandas as pd
from transformers import AutoTokenizer
from huggingface_hub import snapshot_download
from laya.agent import _fix_tokenizer_config
model_dir = snapshot_download("convaiinnovations/laya")
_fix_tokenizer_config(model_dir)
tok = AutoTokenizer.from_pretrained(os.path.join(model_dir, "tokenizer"))
cfg = json.load(open(os.path.join(model_dir, "rl_agent_config.json")))
cfg["max_len"] = 1024
cfg["head_max_len"] = 256
df = pd.read_parquet(TRAIN_PARQUET)
rng = random.Random(20260924)
# Pass 1: base items in the exact same iteration order as preprocess.py (keeps the split identical)
print("[1/3] base items in canonical order ...")
base = []
row_ctx = []
for row in df.itertuples(index=False):
state = json.loads(row.state)
questions = json.loads(row.questions)
gold = json.loads(row.gold)
for qid, q in questions.items():
if qid in gold:
row_ctx.append((state, q, gold[qid]))
base.append(len(base))
# rebuild items canonically (identical call order to preprocess.py)
from preprocess import build_training_item # canonical builder, same output as preprocess.py run
items_base = [build_training_item(tok, cfg, *ctx) for ctx in row_ctx]
print(f" base: {len(items_base)} built, {sum(x is None for x in items_base)} skipped")
# NOTE: None entries can differ from preprocess.py only if builders diverge; they don't.
pairs = [(it, ctx) for it, ctx in zip(items_base, row_ctx) if it is not None]
n = len(pairs)
# Calibration slice: same shuffle & indices as train_single's internal split over canonical list
order = list(range(n))
random.Random(CALIB_SEED).shuffle(order)
n_calib = min(CALIB_MAX, n // 10)
calib_idx = set(order[:n_calib])
calib_items = [pairs[i][0] for i in sorted(calib_idx)]
train_idx = [i for i in range(n) if i not in calib_idx]
print(f"[2/3] split: train={len(train_idx)} calib(clean)={len(calib_items)}; augmenting ...")
t0 = time.time()
aug_items = []
for i in train_idx:
state, q, gold_q = pairs[i][1]
for v, tgt in variants_for(q, gold_q, rng):
it = to_item(tok, cfg, state, v, tgt)
if it:
aug_items.append(it)
rng.shuffle(aug_items)
print(f" augmented: {len(aug_items)} items in {time.time()-t0:.1f}s "
f"({len(aug_items)/max(1,len(train_idx)):.2f}x per source question)")
torch.save({"train": aug_items, "calib": calib_items}, OUT)
print(f"[3/3] saved -> {OUT}")
assert len(calib_items) == n_calib
if __name__ == "__main__":
main()
|