{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"6552014f","cell_type":"markdown","source":"# Revisi Konferensi - Eksperimen Tambahan (versi final, label English)\nFailure cases (R1.4) + Grad-CAM (R2.3, fix Keras 3) + Sensitivitas mask (R1.3).\n\n**Cara pakai:** attach input yang sama seperti notebook signifikansi (dataset kompetisi + cassava_cropped + 2 model .keras), lalu **Run All**. Output: `failure_cases.png`, `gradcam.png`, `mask_ablation.png` di `/kaggle/working`.","metadata":{}},{"id":"14bd43db","cell_type":"code","source":"# === CELL 1: Setup, load model, prediksi test set ===\nimport os, glob, numpy as np, pandas as pd, cv2\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom sklearn.model_selection import train_test_split\nimport matplotlib.pyplot as plt\n\nSEED=42; IMG_SIZE=299; BATCH=32; NUM_CLASSES=5\nCLASSES={0:\"CBB\",1:\"CBSD\",2:\"CGM\",3:\"CMD\",4:\"Healthy\"}\nnp.random.seed(SEED); tf.random.set_seed(SEED)\n\ndef find_first(patterns, kind):\n    for p in patterns:\n        hits=sorted(glob.glob(p, recursive=True))\n        if hits: return hits[0]\n    raise FileNotFoundError(\"Tidak menemukan \"+kind+\"\\nPola:\\n  \"+\"\\n  \".join(patterns))\n\nCKPT_BASE=find_first([\"/kaggle/input/datasets/farhanadityafauzi/xception-baseline/xception_baseline.keras\"],\"model baseline\")\nCKPT_UNET=find_first([\"/kaggle/input/datasets/farhanadityafauzi/xception-baseline/xception_baseline.keras\"],\"model cropped/U-Net\")\nORIG_CSV=find_first([\"/kaggle/input/cassava-leaf-disease-classification/train.csv\",\n                     \"/kaggle/input/competitions/cassava-leaf-disease-classification/train.csv\",\n                     \"/kaggle/input/**/cassava-leaf-disease-classification/train.csv\"],\"train.csv\")\nORIG_DIR=os.path.join(os.path.dirname(ORIG_CSV),\"train_images\")\nCROP_DIR=find_first([\"/kaggle/input/**/cassava_cropped/train_images\"],\"cassava_cropped/train_images\")\nprint(\"base:\",CKPT_BASE); print(\"unet:\",CKPT_UNET); print(\"orig:\",ORIG_DIR); print(\"crop:\",CROP_DIR)\n\ndf=pd.read_csv(ORIG_CSV); ids=df[\"image_id\"].values; labels=df[\"label\"].values\nids_tr,ids_tmp,y_tr,y_tmp=train_test_split(ids,labels,test_size=0.30,random_state=SEED,stratify=labels)\nids_val,ids_test,y_val,y_test=train_test_split(ids_tmp,y_tmp,test_size=0.5,random_state=SEED,stratify=y_tmp)\ny_test=np.array(y_test); print(\"Test:\",len(ids_test))\n\ndef load_img(img_dir,iid):\n    im=cv2.imread(os.path.join(img_dir,iid)); im=cv2.resize(im,(IMG_SIZE,IMG_SIZE))\n    return cv2.cvtColor(im,cv2.COLOR_BGR2RGB).astype(np.float32)/255.0\ndef make_ds(img_dir,id_list):\n    paths=[os.path.join(img_dir,i) for i in id_list]\n    def _f(pp):\n        im=cv2.imread(pp.decode()); im=cv2.resize(im,(IMG_SIZE,IMG_SIZE))\n        return cv2.cvtColor(im,cv2.COLOR_BGR2RGB).astype(np.float32)/255.0\n    def _load(p):\n        im=tf.numpy_function(_f,[p],tf.float32); im.set_shape([IMG_SIZE,IMG_SIZE,3]); return im\n    return tf.data.Dataset.from_tensor_slices(paths).map(_load,num_parallel_calls=tf.data.AUTOTUNE).batch(BATCH).prefetch(tf.data.AUTOTUNE)\n\nm_base=keras.models.load_model(CKPT_BASE,compile=False)\nm_unet=keras.models.load_model(CKPT_UNET,compile=False)\nyp_base=m_base.predict(make_ds(ORIG_DIR,ids_test),verbose=1).argmax(1)\nyp_unet=m_unet.predict(make_ds(CROP_DIR,ids_test),verbose=1).argmax(1)\nbase_ok=(yp_base==y_test); unet_ok=(yp_unet==y_test)\nprint(\"Acc baseline %.4f | Acc U-Net %.4f\"%(base_ok.mean(),unet_ok.mean()))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"e6e8d338","cell_type":"code","source":"# === CELL 2: FAILURE CASES (R1.4) - label English ===\nHURT=np.where(base_ok & ~unet_ok)[0]\nBOTH_WRONG=np.where(~base_ok & ~unet_ok)[0]\nprint(\"Removal hurts:\",len(HURT),\" | Both wrong:\",len(BOTH_WRONG))\n\ndef fg_frac(iid):\n    im=cv2.imread(os.path.join(CROP_DIR,iid),cv2.IMREAD_GRAYSCALE)\n    return float((im>10).mean()) if im is not None else 1.0\nfrac=np.array([fg_frac(ids_test[i]) for i in range(len(ids_test))])\nSEG_FAIL=np.argsort(frac)[:8]\nprint(\"Smallest foreground fraction:\",frac[SEG_FAIL[:5]].round(3))\n\ndef show_row(axrow, idxs, title):\n    for ax,i in zip(axrow, idxs):\n        ax.imshow(load_img(CROP_DIR, ids_test[i])); ax.axis('off')\n        ax.set_title(\"T:%s B:%s U:%s\"%(CLASSES[y_test[i]],CLASSES[yp_base[i]],CLASSES[yp_unet[i]]),fontsize=8)\n    axrow[0].text(-0.15,0.5,title,rotation=90,va='center',ha='center',transform=axrow[0].transAxes,fontsize=10,weight='bold')\n\nn=5\nfig,axes=plt.subplots(3,n,figsize=(3*n,9))\nshow_row(axes[0],HURT[:n],\"Removal hurts\")\nshow_row(axes[1],BOTH_WRONG[:n],\"Both wrong\")\nshow_row(axes[2],SEG_FAIL[:n],\"Poor segmentation\")\nplt.suptitle(\"Failure Cases (T=true, B=baseline, U=U-Net)\",y=1.01)\nplt.tight_layout(); plt.savefig(\"/kaggle/working/failure_cases.png\",dpi=150,bbox_inches='tight'); plt.show()\nprint(\"Saved failure_cases.png\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"b861dc58","cell_type":"code","source":"# === CELL 3: GRAD-CAM (R2.3) - Keras 3 robust ===\ndef last_conv_idx(model):\n    for i in range(len(model.layers)-1,-1,-1):\n        try:\n            if len(model.layers[i].output.shape)==4: return i\n        except Exception: pass\n    raise ValueError(\"Tidak ada layer konvolusi 4D\")\n\ndef gradcam(model, img, cls):\n    ci   = last_conv_idx(model)\n    feat = keras.Model(model.inputs, model.layers[ci].output)\n    tail = model.layers[ci+1:]\n    x    = tf.convert_to_tensor(img[None], dtype=tf.float32)\n    conv = feat(x)\n    with tf.GradientTape() as tape:\n        tape.watch(conv)\n        h = conv\n        for L in tail:\n            h = L(h)\n        loss = h[:, int(cls)]\n    grads = tape.gradient(loss, conv)\n    if grads is None:\n        raise RuntimeError(\"Grad masih None\")\n    conv = conv[0].numpy(); grads = grads[0].numpy()\n    w   = grads.mean(axis=(0,1))\n    cam = np.maximum((conv*w).sum(-1), 0.0)\n    cam = cam/(cam.max()+1e-8)\n    return cv2.resize(cam.astype(np.float32), (IMG_SIZE, IMG_SIZE))\n\ndef overlay(img,cam):\n    hm=cv2.applyColorMap(np.uint8(255*cam),cv2.COLORMAP_JET)[:,:,::-1].astype(np.float32)/255.0\n    return np.clip(0.55*img+0.45*hm,0,1)\n\nminor=[0,1]\nsel=[i for i in range(len(ids_test)) if unet_ok[i] and y_test[i] in minor][:4]\nif len(sel)<4: sel=[i for i in range(len(ids_test)) if unet_ok[i]][:4]\nfig,axes=plt.subplots(len(sel),4,figsize=(12,3*len(sel)))\ntitles=[\"Original\",\"Grad-CAM Baseline\",\"Cropped (no-bg)\",\"Grad-CAM U-Net\"]\nfor r,i in enumerate(sel):\n    ob=load_img(ORIG_DIR,ids_test[i]); oc=load_img(CROP_DIR,ids_test[i])\n    cb=gradcam(m_base,ob,int(y_test[i])); cc=gradcam(m_unet,oc,int(y_test[i]))\n    for ax,im,t in zip(axes[r],[ob,overlay(ob,cb),oc,overlay(oc,cc)],titles):\n        ax.imshow(im); ax.axis('off')\n        if r==0: ax.set_title(t,fontsize=10)\n    axes[r][0].text(-0.15,0.5,CLASSES[y_test[i]],rotation=90,va='center',ha='center',transform=axes[r][0].transAxes,weight='bold')\nplt.tight_layout(); plt.savefig(\"/kaggle/working/gradcam.png\",dpi=150,bbox_inches='tight'); plt.show()\nprint(\"Saved gradcam.png\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"cd4f4a83","cell_type":"code","source":"# === CELL 4: MASK SENSITIVITY (R1.3, proxy) - label English ===\ndef degrade(img, level):\n    out=img.copy(); black=(out.sum(2)<0.05)\n    if level>0:\n        noise=np.random.rand(*out.shape).astype(np.float32)\n        m=(np.random.rand(*black.shape)<level) & black\n        out[m]=noise[m]\n        k=int(3+level*10); ker=np.ones((k,k),np.uint8)\n        fg=cv2.erode((~black).astype(np.uint8),ker); out=out*fg[...,None]\n    return np.clip(out,0,1)\n\nrng=np.random.default_rng(SEED)\nsub=rng.choice(len(ids_test),size=min(1200,len(ids_test)),replace=False)\ncrops=[load_img(CROP_DIR,ids_test[i]) for i in sub]\nlevels=[0.0,0.1,0.2,0.3]; accs=[]\nfor lv in levels:\n    imgs=np.stack([degrade(c,lv) for c in crops])\n    yp=m_unet.predict(imgs,batch_size=BATCH,verbose=0).argmax(1)\n    a=float((yp==y_test[sub]).mean()); accs.append(a); print(\"degradation %.1f -> acc %.4f\"%(lv,a))\nplt.figure(figsize=(5,4)); plt.plot(levels,accs,'o-',color='C3',lw=2)\nplt.xlabel(\"Mask degradation level\"); plt.ylabel(\"Classification accuracy\")\nplt.grid(alpha=0.3)\nplt.savefig(\"/kaggle/working/mask_ablation.png\",dpi=150,bbox_inches='tight'); plt.show()\nprint(\"Saved mask_ablation.png  | accs:\",[round(a,4) for a in accs])","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}