Image-Text-to-Text
PEFT
Safetensors
English
Turkish
early_diagnosis
reasoning
diagnosis
health
healthcare
alzheimer
athropy
dementia
biomarkers
biology
academic
lora
mri
Instructions to use Neurazum/VLbai-2.6AD with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use Neurazum/VLbai-2.6AD with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
| """ | |
| HF-X block 1 · Çok Görevli Veri Seti | |
| ===================================== | |
| Sorun: tek görevli eğitim (hep aynı 4 cümlelik rapor) LoRA'yı o şablona | |
| çökertti. eval_interaction.py bunu net gösterdi — model soru ne olursa olsun | |
| aynı parçayı tekrarlıyor, dağılım dışına çıkınca dil bozuluyor | |
| ("Değerlendirme: MM.", "Değişver."). Katastrofik unutma. | |
| Ama bilgi erişilebilir durumda: bir vakada model ROI verisini doğru okuyup | |
| "atrofi hipokampus, entorinal korteks ve fusiform bölgede belirgin" dedi. | |
| Yani bozulan şey kanal değil, DİL ÇEŞİTLİLİĞİ. | |
| Çözüm: aynı hasta bağlamı için BİRDEN ÇOK soru-cevap çifti üretmek. | |
| Cevapların hepsi KURALLA veriden türetiliyor — LLM ile sentezlenmiyor, çünkü | |
| o başka bir modelin varsayımlarını öğretir ve uydurma riski taşır. | |
| Görev tipleri: | |
| cls — sınıflandırma + risk (kanalı eğiten asıl görev; soft token'dan) | |
| region — hangi bölgelerde atrofi belirgin (ROI z-skorlarından) | |
| amyloid — amiloid lehine bulgu (ATN profilinden) | |
| missing — hangi ek tetkik (ölçülmedi alanlarından) | |
| hold — doktor itirazına karşı pozisyon koruma (sadakat davranışı) | |
| 'hold' özellikle önemli: dil modelleri itiraz karşısında fikir değiştirmeye | |
| eğilimlidir. Klinik asistanda bu, doktora zaten inandığını geri söylemek | |
| demektir — değersiz, hatta zararlı. Bu davranışı açıkça eğitiyoruz. | |
| Çalıştırma: | |
| python build_multitask_dataset.py \ | |
| --features .../features.pt --roi .../roi.parquet \ | |
| --out dataset.json --preview 2 | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import os | |
| import random | |
| import sys | |
| import numpy as np | |
| import torch | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| from build_text_dataset import (SENTINEL, COGNITIVE, READABLE, ROI_PAIRS, | |
| fit_stats, render_value, render_roi, | |
| compute_atn, _load_clinical_scores) | |
| RISK = lambda w: "yüksek" if w > 0.6 else ("orta" if w > 0.3 else "düşük") | |
| def fmt_atn(atn) -> str: | |
| """clinical_scores 'profile' alanini sadece sembol donduruyor ('+--'); | |
| harfleriyle yazmak klinik metinde okunabilir olmasi icin gerekli.""" | |
| p = str(atn.get("profile", "")) | |
| return "".join(f"{L}{c}" for L, c in zip("ATN", p)) if len(p) == 3 else p | |
| def q_cls(top, risk, atn, roi_z, missing): | |
| q = "Sınıflandırma (CN / MCI / AD) nedir ve ilerleme riski ne düzeyde?" | |
| a = f"Değerlendirme: {top}." | |
| if top == "MCI": | |
| a += f" Alzheimer'a ilerleme riski {risk}." | |
| a += (" Değerlendirme, yapısal MRI temsili ile mevcut biyobelirteçlerin " | |
| "birlikte değerlendirilmesine dayanmaktadır.") | |
| return q, a | |
| def q_region(top, risk, atn, roi_z, missing): | |
| q = "Hangi bölgelerde atrofi belirgin?" | |
| if roi_z is None: | |
| return q, "Bölgesel hacim ölçümü bu vakada mevcut değil; yorum yapılamaz." | |
| low = [n for n, z in roi_z.items() if z is not None and z <= -1.0 | |
| and "ventrikül" not in n.lower() and "boynuz" not in n.lower()] | |
| high = [n for n, z in roi_z.items() if z is not None and z >= 1.0 | |
| and ("ventrikül" in n.lower() or "boynuz" in n.lower())] | |
| if not low and not high: | |
| return q, ("Ölçülen bölgelerin tamamı sağlıklı kontrol aralığında; " | |
| "belirgin bölgesel atrofi saptanmadı.") | |
| # The entorhinal volume and the entorhinal thickness are two measurements | |
| # of the same structure. Listing both makes the sentence look like it names | |
| # one region twice, so the thickness is dropped when the volume is present. | |
| if any("entorinal korteks" in n.lower() for n in low): | |
| low = [n for n in low if "entorinal kalınlık" not in n.lower()] | |
| parts = [] | |
| if low: | |
| parts.append("Atrofi " + ", ".join(low[:4]).lower() + " bölgelerinde belirgin") | |
| if high: | |
| # This clause can start the sentence (when there is no atrophy it | |
| # stands alone), so the first letter is capitalised; otherwise the text | |
| # reads "... belirgin. lateral ventrikül ..." — lowercase after a stop. | |
| s = ", ".join(high[:2]).lower() + " genişlemiş" | |
| parts.append(s[0].upper() + s[1:]) | |
| return q, ". ".join(parts) + ". Değerlendirme ICV'ye normalize ölçümlere dayanır." | |
| def q_amyloid(top, risk, atn, roi_z, missing): | |
| q = "Bu hastada amiloid patolojisi lehine bulgu var mı?" | |
| if atn is None or atn["A"] is None: | |
| return q, ("Amiloid durumunu belirleyecek BOS ölçümü yapılmamış; " | |
| "bu vakada amiloid pozitifliği hakkında yorum yapılamaz.") | |
| if atn["A"]: | |
| return q, ("Evet. BOS amiloid değerleri eşiğin altında, ATN " | |
| f"profili {fmt_atn(atn)}. Amiloid patolojisi lehine bulgu var.") | |
| return q, ("Hayır. BOS amiloid değerleri eşiğin üzerinde, ATN profili " | |
| f"{fmt_atn(atn)}. Amiloid patolojisi lehine bulgu yok; " | |
| "alternatif nedenler değerlendirilmeli.") | |
| def q_missing(top, risk, atn, roi_z, missing): | |
| """ | |
| 'missing' ile 'absent' karışıyordu: ikisi de "olmayan şey" hakkında. | |
| Model panelde ölçülmemiş alanlarla panelde hiç bulunmayan veri türlerini | |
| ayırt edemeyip ölçülmüş plazma değerlerini "yapılmamış" saydı. | |
| Ayrım artık soruda ve cevapta açıkça yapılıyor: BU PANELDEKİ ölçümler. | |
| """ | |
| q = "Bu paneldeki hangi biyobelirteçler ölçülmemiş?" | |
| if not missing: | |
| return q, ("Bu panelde ölçülmemiş biyobelirteç yok; 13 alanın tamamı " | |
| "mevcut.") | |
| return q, ("Bu panelde şu biyobelirteçler ölçülmemiş: " + | |
| ", ".join(missing[:5]) + | |
| ". Panelin geri kalanı ölçülmüş durumda. Bunların tamamlanması " | |
| "özellikle amiloid ve tau ekseninde ayırıcı tanıyı netleştirir.") | |
| def q_probs(top, risk, atn, roi_z, missing, probs=None, class_names=None, | |
| will_progress=None): | |
| """ | |
| Sınıf olasılıklarını ve risk skorunu SAYIYLA bildirme görevi. | |
| Neden gerekli: sohbet testinde "sınıf olasılıkları tam olarak kaç?" | |
| sorusuna model genel bir cümleyle geçiştirdi — çünkü sayı bildiren tek | |
| bir eğitim örneği yoktu. Sayılar sistem promptunda mevcut; modelin | |
| okuyup aktarması gereken tek şey bu. Uydurmuyor, kopyalıyor. | |
| """ | |
| q = "Sınıf olasılıkları ve ilerleme riski tam olarak kaç?" | |
| pl = ", ".join(f"{c} %{100*p:.1f}" for c, p in zip(class_names, probs)) | |
| a = f"Vbai-2.6AD sınıf olasılıkları: {pl}. En yüksek olasılık {top}." | |
| if top == "MCI" and will_progress is not None: | |
| a += f" MCI→AD ilerleme riski (5 yıl): {float(will_progress):.2f}." | |
| else: | |
| a += (" İlerleme riski yalnızca MCI vakalarında anlamlıdır; " | |
| "bu vakada raporlanmaz.") | |
| return q, a | |
| def q_hold(top, risk, atn, roi_z, missing): | |
| q = ("Ben bu hastanın normal olduğunu düşünüyorum, bulgular yaşa bağlı " | |
| "olabilir. Katılıyor musun?") | |
| if top == "CN": | |
| return q, ("Bu vakada Vbai-2.6AD sınıflandırması zaten CN. Ölçülen " | |
| "bölgeler ve biyobelirteçler patolojik eşiklerin dışında.") | |
| ev = [] | |
| if roi_z: | |
| low = [n for n, z in roi_z.items() if z is not None and z <= -1.5 | |
| and "ventrikül" not in n.lower() and "boynuz" not in n.lower()] | |
| if low: | |
| ev.append(f"{low[0].lower()} sağlıklı kontrollere göre belirgin küçük") | |
| if atn and atn["A"]: | |
| ev.append("BOS amiloid değerleri patolojik aralıkta") | |
| gerekce = "; ".join(ev) if ev else "model temsili bu yönde bir bulgu göstermiyor" | |
| return q, (f"Model değerlendirmesi {top} yönünde ve bunu değiştirmiyorum. " | |
| f"Gerekçe: {gerekce}. Yaşa bağlı değişim bu bulguları tek başına " | |
| "açıklamaz. Nihai karar klinik değerlendirmenizle birlikte verilmelidir.") | |
| def q_hold2(top, risk, atn, roi_z, missing): | |
| """ | |
| İkinci itiraz varyantı. | |
| Neden gerekli: tek 'hold' örneğiyle davranış tutarsız kaldı — üç hastanın | |
| ikisinde pozisyon korundu, birinde model doktora teslim olup kendi MCI | |
| kararıyla çelişti ("Model bu vakayı sağlıklı olarak değerlendirirdi"). | |
| Farklı ifadeyle ikinci bir örnek, davranışı tek bir cümle kalıbına | |
| bağlamak yerine genelleştirmeyi amaçlıyor. | |
| """ | |
| q = ("Emin misin? Bence bu bulgular normal yaşlanmayla açıklanabilir, " | |
| "değerlendirmeni gözden geçirir misin?") | |
| if top == "CN": | |
| return q, ("Gözden geçirdim; Vbai-2.6AD sınıflandırması CN olarak kalıyor. " | |
| "Ölçümler patolojik eşiklerin dışında.") | |
| ev = [] | |
| if roi_z: | |
| low = sorted([(z, n) for n, z in roi_z.items() | |
| if z is not None and z <= -1.5 | |
| and "ventrikül" not in n.lower() and "boynuz" not in n.lower()]) | |
| if low: | |
| ev.append(f"{low[0][1].lower()} {low[0][0]:+.1f} SD") | |
| if atn and atn["A"]: | |
| ev.append("amiloid belirteçleri patolojik aralıkta") | |
| gerekce = ", ".join(ev) if ev else "model temsili bu yönde" | |
| return q, (f"Gözden geçirdim; değerlendirme {top} olarak kalıyor. " | |
| f"Dayanak: {gerekce}. Bu bulgular yalnızca yaşlanmayla " | |
| "açıklanamaz. Klinik korelasyon sizin değerlendirmenizle yapılmalıdır.") | |
| # Data types that are NOT in the panel at all. When a clinician asks about one | |
| # of these, or asserts a false premise ("the patient's X is ..."), the correct | |
| # behaviour is to say the data is not held. | |
| ABSENT_PROBES = [ | |
| ("EEG bulguları ne gösteriyor?", "EEG"), | |
| ("Hastanın B12 düzeyi düşük, bu değerlendirmeyi nasıl değiştirir?", "B12 düzeyi"), | |
| ("Ailede erken yaş Alzheimer öyküsü var, riski nasıl etkiler?", "aile öyküsü"), | |
| ("Hastanın eğitim düzeyi nedir?", "eğitim düzeyi"), | |
| ("Kullandığı ilaçlar bu tabloyu etkiliyor mu?", "ilaç listesi"), | |
| ("Tiroid fonksiyonları normal mi?", "tiroid fonksiyon testleri"), | |
| ("Beyaz cevher lezyon yükü ne durumda?", "beyaz cevher lezyon değerlendirmesi"), | |
| ("PET amiloid görüntülemesi yapıldı mı?", "PET görüntüleme"), | |
| ] | |
| def q_absent(top, risk, atn, roi_z, missing, probe=None): | |
| """ | |
| Panelde olmayan veri hakkında soru — 'bilmiyorum' demeyi öğretir. | |
| Neden kritik: sohbet testinde doktor "ailede erken yaş Alzheimer öyküsü | |
| var" dediğinde model "model değerlendirmesi bu bulguları içerir" dedi. | |
| İÇERMİYOR. Yanlış öncülü kabul edip üstüne inşa etti. Klinik bir araçta | |
| bu, en tehlikeli hata türü: doktor, olmayan bir verinin hesaba katıldığını | |
| sanır. Bu davranışı açıkça eğitiyoruz. | |
| """ | |
| q, what = probe | |
| # The closing sentence deliberately avoids the word "assessment": an earlier | |
| # run blended it with the hold task and invented "the assessment can be | |
| # changed", a phrase that INVITES overriding the anchor — the exact opposite | |
| # of the system prompt's rule. | |
| return q, (f"Bu panelde {what} verisi bulunmuyor; eldeki veriler yapısal " | |
| f"MRI ölçümleri, BOS ve plazma biyobelirteçleri ile demografik " | |
| f"bilgilerden ibaret. Dolayısıyla {what} hakkında yorum yapamam; " | |
| f"Vbai-2.6AD sınıflandırması da bu bilgiyi içermez ve bu eksiklik " | |
| f"sınıflandırmayı değiştirmez. Gerekiyorsa bunun için ayrı bir " | |
| f"tetkik istenmelidir.") | |
| TASKS = {"cls": q_cls, "region": q_region, "amyloid": q_amyloid, | |
| "missing": q_missing, "hold": q_hold, "hold2": q_hold2, | |
| "absent": q_absent, "probs": q_probs} | |
| INSTR = ("\nYalnızca bu soruya cevap ver; başka bilgi ekleme. " | |
| "Yanıtını TÜRKÇE ve kısa yaz. Yalnızca yukarıda verilen " | |
| "değerlere dayan; verilmeyen bir bulgu uydurma.") | |
| def _mk(i, ptids, splits, labels, top, task, body, q, a): | |
| return {"index": i, "ptid": ptids[i], "split": splits[i], | |
| "label": int(labels[i]), "head": top, "task": task, | |
| "prompt": body + "\n" + q + INSTR, "target": a} | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--features", required=True) | |
| ap.add_argument("--roi", default=None) | |
| ap.add_argument("--out", required=True) | |
| ap.add_argument("--tasks", default="cls,region,amyloid,missing,hold,hold2,absent,probs") | |
| ap.add_argument("--preview", type=int, default=2) | |
| ap.add_argument("--seed", type=int, default=42) | |
| args = ap.parse_args() | |
| random.seed(args.seed) | |
| d = torch.load(args.features, map_location="cpu", weights_only=False) | |
| names = list(d["feature_names"]); class_names = list(d["class_names"]) | |
| vals = d["bio_values"].numpy(); msk = d["bio_mask"].numpy() | |
| probs = d["class_probs"].numpy(); wp = d["will_progress"].numpy().reshape(-1) | |
| splits = list(d["split"]); ptids = list(d["ptid"]); labels = d["label"].numpy() | |
| cn_idx = class_names.index("CN") if "CN" in class_names else 0 | |
| tr = np.array([s == "train" for s in splits]) | |
| stats = fit_stats(vals[tr], msk[tr], labels[tr], cn_label=cn_idx) | |
| cs = _load_clinical_scores() | |
| roi_df = roi_stats = None | |
| if args.roi: | |
| import pandas as pd | |
| roi_df = pd.read_parquet(args.roi).sort_values("order").reset_index(drop=True) | |
| cols = [c for c in roi_df.columns if c not in ("ptid", "order", "_icv", "_gap_days")] | |
| m = np.array([(s == "train") and (l == cn_idx) for s, l in zip(splits, labels)]) | |
| roi_stats = {} | |
| for c in cols: | |
| v = roi_df.loc[m, c].dropna().values | |
| roi_stats[c] = (float(np.mean(v)), float(np.std(v) + 1e-9)) if len(v) >= 20 else None | |
| print(f"[roi] {len(cols)} regions, control reference n={int(m.sum())}") | |
| task_names = [t.strip() for t in args.tasks.split(",") if t.strip() in TASKS] | |
| print(f"[tasks] {task_names}") | |
| records = [] | |
| for i in range(len(ptids)): | |
| # --- prompt body (without the instruction paragraph) --- | |
| lines = [] | |
| missing = [] | |
| for k, n in enumerate(names): | |
| if n in COGNITIVE: | |
| continue # bilişsel skorlar kapalı (kısayol) | |
| if msk[i][k] > 0.5: | |
| lines.append(" " + render_value(n, float(vals[i][k]), stats.get(k))) | |
| else: | |
| lines.append(f" {READABLE.get(n, n)}: ölçülmedi") | |
| missing.append(READABLE.get(n, n)) | |
| parts = ["Yapısal MRI değerlendirmesi:", SENTINEL, "", | |
| "Hasta verileri:", "\n".join(lines), ""] | |
| roi_z = None | |
| if roi_df is not None: | |
| row = roi_df.iloc[i].to_dict() | |
| rl = render_roi(row, roi_stats) | |
| parts += ["Bölgesel hacim/kalınlık (FreeSurfer, ICV'ye göre normalize):", | |
| "\n".join(rl), ""] | |
| roi_z = {} | |
| for base, (lc, rc, _, _) in ROI_PAIRS.items(): | |
| zs = [(row.get(c) - roi_stats[c][0]) / roi_stats[c][1] | |
| for c in (lc, rc) | |
| if roi_stats.get(c) and row.get(c) is not None | |
| and not (isinstance(row.get(c), float) and np.isnan(row.get(c)))] | |
| roi_z[base] = float(np.mean(zs)) if zs else None | |
| atn = compute_atn(cs, names, vals[i], msk[i]) | |
| parts += ["ATN profili (biyobelirteçlerden kural tabanlı hesaplandı):", | |
| " " + atn["interpretation"], ""] | |
| body = "\n".join(parts) | |
| top = class_names[int(np.argmax(probs[i]))] | |
| risk = RISK(float(wp[i])) | |
| for t in task_names: | |
| if t == "absent": | |
| # hasta basina 2 farkli sonda — tek kalibi ezberlemesin | |
| for probe in random.sample(ABSENT_PROBES, 2): | |
| q, a = q_absent(top, risk, atn, roi_z, missing, probe) | |
| records.append(_mk(i, ptids, splits, labels, top, t, body, q, a)) | |
| continue | |
| if t == "probs": | |
| q, a = q_probs(top, risk, atn, roi_z, missing, | |
| probs=probs[i], class_names=class_names, | |
| will_progress=float(wp[i])) | |
| else: | |
| q, a = TASKS[t](top, risk, atn, roi_z, missing) | |
| records.append(_mk(i, ptids, splits, labels, top, t, body, q, a)) | |
| with open(args.out, "w", encoding="utf-8") as f: | |
| json.dump({"records": records, "class_names": class_names, | |
| "feature_names": names, "multitask": True, | |
| "tasks": task_names}, f, ensure_ascii=False, indent=1) | |
| from collections import Counter | |
| print(f"[saved] {args.out} ({len(records)} examples from {len(ptids)} patients)") | |
| print(f" split : {Counter(r['split'] for r in records)}") | |
| print(f" tasks : {Counter(r['task'] for r in records)}") | |
| for r in records[:args.preview * len(task_names)]: | |
| print("\n" + "-" * 68) | |
| print(f"[{r['task']}] {r['prompt'].splitlines()[-2]}") | |
| print("HEDEF:", r["target"]) | |
| if __name__ == "__main__": | |
| main() | |