G1c FINO-style adapter: metadata guidance offsets SSL-globalization directionally (+0.014) but no CI-significant recovery; F1b not de-risked at adapter scale (honest)
Browse files- RESEARCH_OVERVIEW.md +9 -7
- jobs/g1c_fino_adapter_job.py +193 -0
- research_specs/RESEARCH_SPEC_v4.md +17 -0
- research_v4/g1c_fino_adapter.json +26 -0
RESEARCH_OVERVIEW.md
CHANGED
|
@@ -59,13 +59,15 @@ geometry — no training, no metadata. It addresses none of covtoken's contribut
|
|
| 59 |
localization, the rank-vs-concentration law, the conformal certificate, rare-structure focus).
|
| 60 |
Three implications:
|
| 61 |
- **Differentiate** in related work: frozen geometric probe + law + certificate, not adaptation.
|
| 62 |
-
- **
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
| 69 |
|
| 70 |
## Reproduce
|
| 71 |
All jobs in `jobs/` (PEP-723 uv scripts) run via `hf jobs uv run --flavor <t4-medium|t4-small|cpu>
|
|
|
|
| 59 |
localization, the rank-vs-concentration law, the conformal certificate, rare-structure focus).
|
| 60 |
Three implications:
|
| 61 |
- **Differentiate** in related work: frozen geometric probe + law + certificate, not adaptation.
|
| 62 |
+
- **F1b lever — TESTED at adapter scale (G1c), not de-risked.** No FINO checkpoint is released, so we
|
| 63 |
+
trained a label-free metadata-guided adapter (the trained counterpart to G1a). Result: SSL-only
|
| 64 |
+
adaptation *erodes* localizability (globalization, reproduced by a trained objective), and metadata
|
| 65 |
+
guidance *offsets* it directionally (+0.014 mean vs SSL) — but the gap is not CI-significant and it
|
| 66 |
+
does **not recover** the depth-eroded concentration subspace toward the mid-layer (0.866). So at
|
| 67 |
+
adapter scale with anatomical metadata, FINO's lever moves the needle in the right direction but
|
| 68 |
+
does not solve the erosion. F1b would need full-backbone capacity AND lesion-relevant metadata.
|
| 69 |
+
- **Complementary experiment, if a FINO checkpoint ships**: run the probe on a *full* FINO-adapted
|
| 70 |
+
backbone (more capacity than our adapter) — the open question G1c could only lower-bound.
|
| 71 |
|
| 72 |
## Reproduce
|
| 73 |
All jobs in `jobs/` (PEP-723 uv scripts) run via `hf jobs uv run --flavor <t4-medium|t4-small|cpu>
|
jobs/g1c_fino_adapter_job.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# /// script
|
| 2 |
+
# requires-python = ">=3.10"
|
| 3 |
+
# dependencies = [
|
| 4 |
+
# "torch", "torchvision", "numpy", "pillow", "scikit-learn", "scipy",
|
| 5 |
+
# "huggingface_hub>=0.34", "dinov3 @ git+https://github.com/facebookresearch/dinov3",
|
| 6 |
+
# ]
|
| 7 |
+
# ///
|
| 8 |
+
"""G1c — FINO-style metadata-guided adaptation: the TRAINED counterpart to G1a. HF Job (GPU).
|
| 9 |
+
|
| 10 |
+
FINO (Gardes et al. 2026, arXiv:2606.05107) adapts FMs label-free via metadata-guided SSL TRAINING;
|
| 11 |
+
no adapted checkpoint is released, so we test the MECHANISM at adapter scale. G1a showed the eroded
|
| 12 |
+
late-layer concentration subspace is NOT recoverable training-free. Here, with MedDINOv3 FROZEN, we
|
| 13 |
+
train a small residual-MLP adapter g (NO lesion labels) at a late layer under three conditions:
|
| 14 |
+
RAW : no adapter (baseline late-layer localizability)
|
| 15 |
+
SSL_only : augmentation-invariance objective only (the self-distillation pressure that globalizes)
|
| 16 |
+
FINO_style: SSL + METADATA guidance -- keep slice-position (continuous) and scan-id (discrete)
|
| 17 |
+
linearly decodable from pooled g(z) (preserve informative factors)
|
| 18 |
+
Probe: lesion density-AUROC on g(z). FINO-direction supported if FINO_style > SSL_only and recovers
|
| 19 |
+
toward the mid-layer target -> metadata-guided ADAPTATION succeeds where training-free steering
|
| 20 |
+
failed, de-risking F1b. Honest proxy: adapter-scale, anatomical metadata; not a FINO reproduction.
|
| 21 |
+
Labels eval-only. Emits G1C_RESULT.
|
| 22 |
+
"""
|
| 23 |
+
from __future__ import annotations
|
| 24 |
+
import json, os, sys, time
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
import numpy as np, torch, torch.nn as nn, torch.nn.functional as F
|
| 27 |
+
from PIL import Image
|
| 28 |
+
from scipy import stats
|
| 29 |
+
from sklearn.neighbors import NearestNeighbors
|
| 30 |
+
from huggingface_hub import hf_hub_download
|
| 31 |
+
sys.path.insert(0,"/mnt/processed/covtoken_code")
|
| 32 |
+
from dinov3.models.vision_transformer import vit_base # noqa: E402
|
| 33 |
+
|
| 34 |
+
BACKBONE_REPO="ricklisz123/MedDINOv3-ViTB-16-CT-3M"; MNT=Path("/mnt")
|
| 35 |
+
RAW_LIDC=MNT/"raw"/"lidc"; MASK_ROOT=MNT/"processed"/"lidc_v2"; OUT=MNT/"processed"/"covtoken"
|
| 36 |
+
N_PATCH,CLS_OFF,GRID=196,5,14
|
| 37 |
+
LAYERS=[int(x) for x in os.environ.get("LAYERS","8,11").split(",")] # blocks 9, 12 (eroded)
|
| 38 |
+
MID_TARGET=0.866 # block-3 multi-seed peak
|
| 39 |
+
BANK_SLICES=int(os.environ.get("BANK_SLICES","700")); EVAL_SLICES=int(os.environ.get("EVAL_SLICES","600"))
|
| 40 |
+
STEPS=int(os.environ.get("STEPS","400")); LMETA=float(os.environ.get("LMETA","1.0"))
|
| 41 |
+
CT_MEAN=np.array([0.485,0.456,0.406],np.float32); CT_STD=np.array([0.229,0.224,0.225],np.float32)
|
| 42 |
+
def log(m): print(f"[g1c] {m}", flush=True)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def load_backbone(device):
|
| 46 |
+
ck=hf_hub_download(BACKBONE_REPO,"model.pth",token=os.environ.get("HF_TOKEN"))
|
| 47 |
+
m=vit_base(drop_path_rate=0.0,layerscale_init=1e-5,n_storage_tokens=4,qkv_bias=False,mask_k_bias=True)
|
| 48 |
+
raw=torch.load(ck,map_location="cpu"); sd=raw.get("teacher",raw)
|
| 49 |
+
sd={(k[9:] if k.startswith("backbone.") else k):v for k,v in sd.items()}
|
| 50 |
+
m.load_state_dict(sd,strict=False); m.eval().to(device)
|
| 51 |
+
for p in m.parameters(): p.requires_grad_(False)
|
| 52 |
+
feats={}
|
| 53 |
+
for i,blk in enumerate(m.blocks):
|
| 54 |
+
def mk(i):
|
| 55 |
+
def h(_m,_i,out):
|
| 56 |
+
while isinstance(out,(list,tuple)): out=out[0]
|
| 57 |
+
feats[i]=out.detach()
|
| 58 |
+
return h
|
| 59 |
+
blk.register_forward_hook(mk(i))
|
| 60 |
+
return m,feats
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def to_t(pil):
|
| 64 |
+
a=(np.asarray(pil.resize((224,224),Image.BILINEAR),np.float32)/255.0-CT_MEAN)/CT_STD
|
| 65 |
+
return torch.from_numpy(a).permute(2,0,1)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
@torch.inference_mode()
|
| 69 |
+
def feats_at(model,feats,imgs,device):
|
| 70 |
+
model.forward_features(imgs.to(device,torch.float32))
|
| 71 |
+
return {L:feats[L][:,CLS_OFF:CLS_OFF+N_PATCH,:].float() for L in LAYERS} # GPU tensors
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class Adapter(nn.Module):
|
| 75 |
+
def __init__(self,d=768,h=256):
|
| 76 |
+
super().__init__(); self.n=nn.LayerNorm(d); self.a=nn.Linear(d,h); self.b=nn.Linear(h,d)
|
| 77 |
+
nn.init.zeros_(self.b.weight); nn.init.zeros_(self.b.bias)
|
| 78 |
+
def forward(self,z): return z + self.b(F.gelu(self.a(self.n(z))))
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def auroc(s,y):
|
| 82 |
+
s=np.asarray(s,float); y=np.asarray(y,int); pos,neg=y.sum(),len(y)-y.sum()
|
| 83 |
+
if pos==0 or neg==0: return float("nan")
|
| 84 |
+
r=stats.rankdata(s); return float((r[y==1].sum()-pos*(pos+1)/2)/(pos*neg))
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def boot_ci(s,y,n=1000,seed=0):
|
| 88 |
+
rng=np.random.default_rng(seed); s=np.asarray(s); y=np.asarray(y); idx=np.arange(len(y)); out=[]
|
| 89 |
+
for _ in range(n):
|
| 90 |
+
b=rng.choice(idx,len(idx),replace=True); out.append(auroc(s[b],y[b]))
|
| 91 |
+
return float(np.nanpercentile(out,2.5)),float(np.nanpercentile(out,97.5))
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def main():
|
| 95 |
+
t0=time.time(); device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 96 |
+
model,feats=load_backbone(device); rng=np.random.default_rng(0)
|
| 97 |
+
scan_split=json.load(open(hf_hub_download("Chucks90/eryon-data-pipelines","manifests/lidc/splits_v1.0.0.json",repo_type="dataset",token=os.environ.get("HF_TOKEN"))))["splits"]
|
| 98 |
+
# train images with metadata: zpos (continuous), scanid (discrete) -- NO lesion labels
|
| 99 |
+
scans=[];
|
| 100 |
+
for b in sorted(RAW_LIDC.glob("batch_*")):
|
| 101 |
+
for sd in b.iterdir():
|
| 102 |
+
if sd.is_dir() and scan_split.get(sd.name)=="train": scans.append(sd)
|
| 103 |
+
train=[] # (path, zpos, scanid)
|
| 104 |
+
for sid,sd in enumerate(scans):
|
| 105 |
+
sl=sorted(sd.glob("slice_*.png")); n=len(sl)
|
| 106 |
+
for j,p in enumerate(sl): train.append((p, j/max(1,n-1), sid))
|
| 107 |
+
sel=rng.choice(len(train),min(BANK_SLICES,len(train)),replace=False); train=[train[i] for i in sel]
|
| 108 |
+
n_scans=len(scans)
|
| 109 |
+
ev=[]
|
| 110 |
+
for cd in sorted((MASK_ROOT/"test").iterdir()):
|
| 111 |
+
npz=cd/"patch_masks.npz"
|
| 112 |
+
if cd.is_dir() and npz.exists():
|
| 113 |
+
pm=np.load(npz)["patch_masks"]
|
| 114 |
+
for idx in range(len(pm)): ev.append((cd/f"slice_{idx:04d}.png", pm[idx]))
|
| 115 |
+
ev=[ev[i] for i in rng.choice(len(ev),min(EVAL_SLICES,len(ev)),replace=False)]
|
| 116 |
+
log(f"device={device.type}; train={len(train)} ({n_scans} scans) eval={len(ev)} layers={[L+1 for L in LAYERS]} steps={STEPS}")
|
| 117 |
+
|
| 118 |
+
# precompute frozen features once: orig + intensity-jitter + hflip, per layer; + metadata
|
| 119 |
+
flip_idx=torch.tensor((np.arange(N_PATCH).reshape(GRID,GRID)[:,::-1]).reshape(-1).copy(),device=device)
|
| 120 |
+
Zo={L:[] for L in LAYERS}; Zj={L:[] for L in LAYERS}; Zf={L:[] for L in LAYERS}; ZP=[]; ID=[]
|
| 121 |
+
for i in range(0,len(train),48):
|
| 122 |
+
ch=train[i:i+48]; pil=[Image.open(p).convert("RGB") for p,_,_ in ch]
|
| 123 |
+
o=feats_at(model,feats,torch.stack([to_t(im) for im in pil]),device)
|
| 124 |
+
jit=[Image.fromarray(np.clip(np.asarray(im,np.float32)*rng.uniform(0.85,1.15)+rng.uniform(-12,12),0,255).astype(np.uint8)) for im in pil]
|
| 125 |
+
j=feats_at(model,feats,torch.stack([to_t(im) for im in jit]),device)
|
| 126 |
+
fl=feats_at(model,feats,torch.stack([to_t(im.transpose(Image.FLIP_LEFT_RIGHT)) for im in pil]),device)
|
| 127 |
+
for L in LAYERS: Zo[L].append(o[L].cpu()); Zj[L].append(j[L].cpu()); Zf[L].append(fl[L].cpu())
|
| 128 |
+
ZP+=[z for _,z,_ in ch]; ID+=[s for _,_,s in ch]
|
| 129 |
+
Zo={L:torch.cat(v).to(device) for L,v in Zo.items()}; Zj={L:torch.cat(v).to(device) for L,v in Zj.items()}; Zf={L:torch.cat(v).to(device) for L,v in Zf.items()}
|
| 130 |
+
zpos=torch.tensor(ZP,dtype=torch.float32,device=device); sid=torch.tensor(ID,dtype=torch.long,device=device)
|
| 131 |
+
log(f"precomputed features in {time.time()-t0:.0f}s")
|
| 132 |
+
# eval features (orig only) per layer + labels
|
| 133 |
+
Ze={L:[] for L in LAYERS}; lab=[]
|
| 134 |
+
for i in range(0,len(ev),48):
|
| 135 |
+
ch=ev[i:i+48]; o=feats_at(model,feats,torch.stack([to_t(Image.open(p).convert("RGB")) for p,_ in ch]),device)
|
| 136 |
+
for L in LAYERS: Ze[L].append(o[L].cpu())
|
| 137 |
+
lab.append(np.stack([pm for _,pm in ch]).reshape(-1))
|
| 138 |
+
Ze={L:torch.cat(v).to(device) for L,v in Ze.items()}; lab=np.concatenate(lab)
|
| 139 |
+
|
| 140 |
+
def train_adapter(L, use_meta):
|
| 141 |
+
g=Adapter().to(device); pos_h=nn.Linear(768,1).to(device); id_h=nn.Linear(768,n_scans).to(device)
|
| 142 |
+
params=list(g.parameters())+(list(pos_h.parameters())+list(id_h.parameters()) if use_meta else [])
|
| 143 |
+
opt=torch.optim.Adam(params,lr=2e-3,weight_decay=1e-4); N=Zo[L].shape[0]
|
| 144 |
+
for step in range(STEPS):
|
| 145 |
+
bi=torch.randint(0,N,(64,),device=device)
|
| 146 |
+
zo=g(Zo[L][bi]); zj=g(Zj[L][bi]); zf=g(Zf[L][bi])
|
| 147 |
+
# SSL: invariance to jitter (same positions) + equivariance to flip (mirrored positions)
|
| 148 |
+
l_ssl=(1-F.cosine_similarity(zo,zj,dim=2)).mean() + (1-F.cosine_similarity(zo,zf[:,flip_idx,:],dim=2)).mean()
|
| 149 |
+
loss=l_ssl
|
| 150 |
+
if use_meta:
|
| 151 |
+
pool=zo.mean(1)
|
| 152 |
+
l_meta=F.mse_loss(pos_h(pool).squeeze(-1),zpos[bi]) + F.cross_entropy(id_h(pool),sid[bi])
|
| 153 |
+
loss=l_ssl+LMETA*l_meta
|
| 154 |
+
opt.zero_grad(); loss.backward(); opt.step()
|
| 155 |
+
g.eval(); return g
|
| 156 |
+
|
| 157 |
+
def probe(L, g):
|
| 158 |
+
with torch.no_grad():
|
| 159 |
+
ref=(g(Zo[L]) if g else Zo[L]).reshape(-1,768).cpu().numpy()
|
| 160 |
+
ref=ref[rng.choice(len(ref),min(50000,len(ref)),replace=False)]
|
| 161 |
+
nn_=NearestNeighbors(n_neighbors=11).fit(ref)
|
| 162 |
+
ze=(g(Ze[L]) if g else Ze[L]).reshape(-1,768).cpu().numpy()
|
| 163 |
+
d,_=nn_.kneighbors(ze); sc=d[:,1:].mean(1)
|
| 164 |
+
return auroc(sc,lab), boot_ci(sc,lab)
|
| 165 |
+
|
| 166 |
+
res={"backbone":"MedDINOv3","mid_layer_target":MID_TARGET,"steps":STEPS,"lambda_meta":LMETA,"by_layer":{}}
|
| 167 |
+
for L in LAYERS:
|
| 168 |
+
raw_a,raw_ci=probe(L,None)
|
| 169 |
+
g_ssl=train_adapter(L,use_meta=False); ssl_a,ssl_ci=probe(L,g_ssl)
|
| 170 |
+
g_fino=train_adapter(L,use_meta=True); fin_a,fin_ci=probe(L,g_fino)
|
| 171 |
+
row={"raw":{"auroc":round(raw_a,4),"ci":[round(x,4) for x in raw_ci]},
|
| 172 |
+
"ssl_only":{"auroc":round(ssl_a,4),"ci":[round(x,4) for x in ssl_ci]},
|
| 173 |
+
"fino_style":{"auroc":round(fin_a,4),"ci":[round(x,4) for x in fin_ci]},
|
| 174 |
+
"fino_minus_ssl":round(fin_a-ssl_a,4),"fino_minus_raw":round(fin_a-raw_a,4)}
|
| 175 |
+
res["by_layer"][str(L+1)]=row
|
| 176 |
+
log(f" block {L+1}: raw {raw_a:.4f} | ssl_only {ssl_a:.4f} | FINO {fin_a:.4f} | FINO-ssl {fin_a-ssl_a:+.4f} | FINO-raw {fin_a-raw_a:+.4f}")
|
| 177 |
+
|
| 178 |
+
# verdict
|
| 179 |
+
deltas=[res["by_layer"][str(L+1)]["fino_minus_ssl"] for L in LAYERS]
|
| 180 |
+
fino_beats_ssl=all(res["by_layer"][str(L+1)]["fino_style"]["ci"][0] > res["by_layer"][str(L+1)]["ssl_only"]["auroc"] for L in LAYERS)
|
| 181 |
+
res["verdict"]={
|
| 182 |
+
"fino_beats_ssl_all_layers_ci": bool(fino_beats_ssl),
|
| 183 |
+
"mean_fino_minus_ssl": round(float(np.mean(deltas)),4),
|
| 184 |
+
"recovers_toward_mid": bool(any(res["by_layer"][str(L+1)]["fino_style"]["auroc"]>=res["by_layer"][str(L+1)]["raw"]["auroc"]+0.03 for L in LAYERS)),
|
| 185 |
+
"interpretation": ("FINO-style metadata-guided adaptation beats pure-SSL adaptation"
|
| 186 |
+
if np.mean(deltas)>0 else "metadata guidance does NOT beat SSL-only at adapter scale")
|
| 187 |
+
+ f" (mean FINO-ssl {np.mean(deltas):+.4f}); contrast with G1a (training-free steering, no recovery)."}
|
| 188 |
+
res["elapsed_s"]=round(time.time()-t0,1)
|
| 189 |
+
OUT.mkdir(parents=True,exist_ok=True); (OUT/"g1c_fino_adapter.json").write_text(json.dumps(res,indent=2))
|
| 190 |
+
print("G1C_RESULT "+json.dumps(res),flush=True)
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
if __name__=="__main__": main()
|
research_specs/RESEARCH_SPEC_v4.md
CHANGED
|
@@ -26,6 +26,23 @@ like mid. A deployable steering module for heads forced to use late features.
|
|
| 26 |
|
| 27 |
---
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
## G2 — Universality: the principle is not about lesions
|
| 30 |
|
| 31 |
**Question.** Does the label-free concentration-subspace readout localize RARE structure in a
|
|
|
|
| 26 |
|
| 27 |
---
|
| 28 |
|
| 29 |
+
## G1c — FINO-style metadata-guided adaptation (the trained counterpart to G1a) ★ built
|
| 30 |
+
|
| 31 |
+
**Context.** FINO (Gardès et al. 2026, arXiv:2606.05107) adapts FMs label-free via metadata-guided
|
| 32 |
+
SSL *training*. No adapted checkpoint is released (FINO branch = code/recipes only), so full FINO is
|
| 33 |
+
a pretraining run, not a job. We test the *mechanism* at adapter scale: G1a showed the eroded late-
|
| 34 |
+
layer subspace is NOT recoverable training-free. Does a TRAINED, label-free, metadata-guided adapter
|
| 35 |
+
recover it — and does FINO's metadata lever beat pure-SSL adaptation?
|
| 36 |
+
|
| 37 |
+
**G1c.** Freeze MedDINOv3. At a late layer, train a small residual-MLP adapter g_theta (no lesion
|
| 38 |
+
labels) under three conditions: (RAW) none; (SSL-only) augmentation-invariance objective — the pure
|
| 39 |
+
self-distillation pressure that globalizes; (FINO-style) SSL + metadata guidance (keep slice-
|
| 40 |
+
position [continuous] and scan-id [discrete] linearly decodable from pooled g(z) — preserve
|
| 41 |
+
informative factors). Probe lesion density-AUROC on g(z). PASS-for-FINO-direction if FINO-style >
|
| 42 |
+
SSL-only (CI excludes 0) and recovers toward the mid-layer target -> metadata-guided adaptation
|
| 43 |
+
succeeds where training-free steering failed, de-risking F1b. Honest proxy: adapter-scale, anatomical
|
| 44 |
+
metadata; not a FINO reproduction.
|
| 45 |
+
|
| 46 |
## G2 — Universality: the principle is not about lesions
|
| 47 |
|
| 48 |
**Question.** Does the label-free concentration-subspace readout localize RARE structure in a
|
research_v4/g1c_fino_adapter.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"study": "G1c — FINO-style metadata-guided adaptation (trained counterpart to G1a)",
|
| 3 |
+
"status": "MEASURED / WEAK-NEGATIVE (directional mechanism signal; strong claim not supported)",
|
| 4 |
+
"context": "FINO (arXiv:2606.05107) adapts FMs label-free via metadata-guided SSL training; no checkpoint released, so tested at adapter scale. Question: does a TRAINED, label-free, metadata-guided adapter recover the eroded late-layer concentration subspace that training-free steering (G1a) could not?",
|
| 5 |
+
"mid_layer_target": 0.866,
|
| 6 |
+
"by_layer": {
|
| 7 |
+
"block9": {"raw": 0.7145, "ssl_only": 0.6890, "fino_style": 0.7080, "fino_minus_ssl": 0.0190, "fino_minus_raw": -0.0065},
|
| 8 |
+
"block12": {"raw": 0.6513, "ssl_only": 0.6465, "fino_style": 0.6545, "fino_minus_ssl": 0.0079, "fino_minus_raw": 0.0032}
|
| 9 |
+
},
|
| 10 |
+
"verdict": {
|
| 11 |
+
"fino_beats_ssl_ci_significant": false,
|
| 12 |
+
"mean_fino_minus_ssl": 0.0135,
|
| 13 |
+
"recovers_toward_mid": false
|
| 14 |
+
},
|
| 15 |
+
"findings": [
|
| 16 |
+
"MECHANISM CONFIRMED DIRECTIONALLY: pure-SSL adaptation (augmentation-invariance only) HURT localizability at block 9 (0.715 -> 0.689) -- the globalization/invariance erosion, now reproduced by a TRAINED objective. FINO-style metadata guidance OFFSET this (+0.019 vs ssl), recovering back toward raw.",
|
| 17 |
+
"STRONG CLAIM NOT SUPPORTED: the FINO-vs-SSL gap is not CI-significant, and neither layer recovers toward the mid-layer concentration subspace (0.866). Metadata guidance at adapter scale mostly AVOIDS the SSL-induced loss rather than recovering the depth-eroded subspace.",
|
| 18 |
+
"CONSISTENT WITH G1a: the depth-erosion is robust even to a trained signal at adapter scale -- reinforcing that globalization is an entangling transformation, and that the deployment answer remains 'read the mid layer' (S2)."
|
| 19 |
+
],
|
| 20 |
+
"honest_caveats": [
|
| 21 |
+
"Adapter-scale (residual MLP at one layer) is NOT full-backbone FINO pretraining; FINO adapts the whole backbone with far more capacity to reshape representations -- this is a lower bound on FINO's potential.",
|
| 22 |
+
"Anatomical metadata (slice-position, scan-id) captures anatomy, not pathology; FINO's premise needs metadata that captures the TASK-relevant factor. Lesion-relevant weak signal could do better."
|
| 23 |
+
],
|
| 24 |
+
"implication_for_F1b": "NOT de-risked at this scale. F1b (concentration-preserving adaptation) would need (a) full-backbone capacity (not an adapter) AND (b) lesion-relevant metadata/weak supervision, not anatomical metadata. The bar for F1b is now better understood and higher than the FINO abstract suggested for rare-pathology localization specifically.",
|
| 25 |
+
"human_signoff": null
|
| 26 |
+
}
|