{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.13.15","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Imports","metadata":{}},{"cell_type":"code","source":"import os, json, shutil, zipfile\nfrom pathlib import Path\nfrom datetime import datetime\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom PIL import Image\n\nimport torch\nfrom fastai.vision.all import *","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-10-14T10:48:08.147839Z","iopub.execute_input":"2026-10-14T10:48:08.148579Z","iopub.status.idle":"2026-10-14T10:48:27.21777Z","shell.execute_reply.started":"2026-10-14T10:48:08.148536Z","shell.execute_reply":"2026-10-14T10:48:27.217072Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# GPU check\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Device: {device}\")\nif device.type == 'cuda':\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n    print(f\"Memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB\")\n# print(f\"fastai version: {fastai.__version__}\")\nprint(f\"torch version: {torch.__version__}\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-10-14T10:48:27.2192Z","iopub.execute_input":"2026-10-14T10:48:27.219778Z","iopub.status.idle":"2026-10-14T10:48:27.545096Z","shell.execute_reply.started":"2026-10-14T10:48:27.219745Z","shell.execute_reply":"2026-10-14T10:48:27.544451Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data","metadata":{}},{"cell_type":"code","source":"COMP_DIR = Path('/kaggle/input/competitions/dogs-vs-cats-redux-kernels-edition')\nWORK_DIR = Path('/kaggle/working/dogscats')\nWORK_DIR.mkdir(exist_ok=True)\n\n# Extract only if not already extracted (saves time on re-runs)\nif not (WORK_DIR / 'train').exists():\n    with zipfile.ZipFile(COMP_DIR / 'train.zip', 'r') as z:\n        z.extractall(WORK_DIR / 'train')\nif not (WORK_DIR / 'test').exists():\n    with zipfile.ZipFile(COMP_DIR / 'test.zip', 'r') as z:\n        z.extractall(WORK_DIR / 'test')\n\nprint(f\"Train images: {len(list((WORK_DIR/'train').glob('*.jpg')))}\")\nprint(f\"Test images:  {len(list((WORK_DIR/'test').glob('*.jpg')))}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T10:48:27.546064Z","iopub.execute_input":"2026-10-14T10:48:27.546337Z","iopub.status.idle":"2026-10-14T10:48:39.847377Z","shell.execute_reply.started":"2026-10-14T10:48:27.54631Z","shell.execute_reply":"2026-10-14T10:48:39.84679Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## DataBlocks and DataLoaders","metadata":{}},{"cell_type":"code","source":"def label_func(x):\n    return 'cat' if x.name.startswith('cat') else 'dog'\n\ndblock = DataBlock(\n    blocks=(ImageBlock, CategoryBlock),\n    get_items=get_image_files,\n    splitter=RandomSplitter(valid_pct=0.2, seed=42),\n    get_y=label_func,\n    item_tfms=Resize(224),\n)\n\ndls = dblock.dataloaders(WORK_DIR / 'train', bs=64)\nprint(f\"Train batches: {len(dls.train)}, Val batches: {len(dls.valid)}\")\nprint(f\"Classes: {dls.vocab}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T10:48:39.848977Z","iopub.execute_input":"2026-10-14T10:48:39.849309Z","iopub.status.idle":"2026-10-14T10:48:40.906628Z","shell.execute_reply.started":"2026-10-14T10:48:39.84928Z","shell.execute_reply":"2026-10-14T10:48:40.905675Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dls.show_batch(max_n=6, nrows=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T10:48:40.907797Z","iopub.execute_input":"2026-10-14T10:48:40.90823Z","iopub.status.idle":"2026-10-14T10:48:41.866584Z","shell.execute_reply.started":"2026-10-14T10:48:40.908164Z","shell.execute_reply":"2026-10-14T10:48:41.865675Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"xb, yb = dls.one_batch()\nprint(f\"Batch shape: {xb.shape}, labels: {yb.shape}\")\nprint(f\"Label values: {yb[:8]}\")\nprint(f\"Pixel range: [{xb.min().item():.2f}, {xb.max().item():.2f}]\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T10:48:41.867783Z","iopub.execute_input":"2026-10-14T10:48:41.868049Z","iopub.status.idle":"2026-10-14T10:48:42.192897Z","shell.execute_reply.started":"2026-10-14T10:48:41.86802Z","shell.execute_reply":"2026-10-14T10:48:42.192179Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Experiment Logging","metadata":{}},{"cell_type":"code","source":"RUNS_DIR = Path('/kaggle/working/runs')\nRUNS_DIR.mkdir(exist_ok=True)\nLOG_PATH = Path('/kaggle/working/experiments.csv')\n\ndef log_run(**kwargs):\n    \"\"\"Append a row to experiments.csv. Creates the file if it doesn't exist.\"\"\"\n    row = {'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M'), **kwargs}\n    if LOG_PATH.exists():\n        df = pd.read_csv(LOG_PATH)\n        df = pd.concat([df, pd.DataFrame([row])], ignore_index=True)\n    else:\n        df = pd.DataFrame([row])\n    df.to_csv(LOG_PATH, index=False)\n    print(f\"Logged run. Total rows: {len(df)}\")\n    return df\n\ndef save_run_artifacts(learn, run_id, config):\n    \"\"\"Save loss curve, confusion matrix, top losses, and config for a run.\"\"\"\n    run_dir = RUNS_DIR / run_id\n    run_dir.mkdir(parents=True, exist_ok=True)\n\n    # Loss curve\n    try:\n        learn.recorder.plot_loss()\n        plt.savefig(run_dir / 'loss_curve.png', dpi=100, bbox_inches='tight')\n        plt.close()\n    except Exception as e:\n        print(f\"  loss_curve failed: {e}\")\n\n    # Confusion matrix + top losses\n    try:\n        interp = ClassificationInterpretation.from_learner(learn)\n        interp.plot_confusion_matrix()\n        plt.savefig(run_dir / 'confusion_matrix.png', dpi=100, bbox_inches='tight')\n        plt.close()\n\n        interp.plot_top_losses(9, nrows=3)\n        plt.savefig(run_dir / 'top_losses.png', dpi=100, bbox_inches='tight')\n        plt.close()\n    except Exception as e:\n        print(f\"  interpretation failed: {e}\")\n\n    # Config JSON\n    with open(run_dir / 'config.json', 'w') as f:\n        json.dump(config, f, indent=2, default=str)\n\n    print(f\"Artifacts saved to {run_dir}:\")\n    for p in sorted(run_dir.iterdir()):\n        print(f\"  {p.name}\")\n\nprint(\"Tracking setup ready.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T10:48:42.194025Z","iopub.execute_input":"2026-10-14T10:48:42.194348Z","iopub.status.idle":"2026-10-14T10:48:42.203985Z","shell.execute_reply.started":"2026-10-14T10:48:42.194319Z","shell.execute_reply":"2026-10-14T10:48:42.20326Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Run Config","metadata":{}},{"cell_type":"code","source":"run_id = 'dvc_001'\n\nconfig = {\n    'run_id': run_id,\n    'description': 'baseline resnet34 no aug default lr',\n    'arch': 'resnet34',\n    'img_size': 224,\n    'batch_size': 64,\n    'augmentation': 'none',\n    'lr': 'default (fine_tune)',\n    'epochs': 3,\n    'notes': 'first logged run',\n}\n\nprint(json.dumps(config, indent=2))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T10:48:42.205014Z","iopub.execute_input":"2026-10-14T10:48:42.20534Z","iopub.status.idle":"2026-10-14T10:48:42.225645Z","shell.execute_reply.started":"2026-10-14T10:48:42.205311Z","shell.execute_reply":"2026-10-14T10:48:42.224854Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learn = vision_learner(\n    dls,\n    resnet34,\n    metrics=[accuracy, error_rate],\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T10:48:42.226614Z","iopub.execute_input":"2026-10-14T10:48:42.226962Z","iopub.status.idle":"2026-10-14T10:48:44.651767Z","shell.execute_reply.started":"2026-10-14T10:48:42.226924Z","shell.execute_reply":"2026-10-14T10:48:44.651085Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learn.lr_find()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T10:48:44.654198Z","iopub.execute_input":"2026-10-14T10:48:44.65465Z","iopub.status.idle":"2026-10-14T10:49:06.447763Z","shell.execute_reply.started":"2026-10-14T10:48:44.654574Z","shell.execute_reply":"2026-10-14T10:49:06.446914Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learn.fine_tune(3, base_lr=1e-3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T10:50:58.345715Z","iopub.execute_input":"2026-10-14T10:50:58.346146Z","iopub.status.idle":"2026-10-14T10:58:10.924743Z","shell.execute_reply.started":"2026-10-14T10:50:58.346113Z","shell.execute_reply":"2026-10-14T10:58:10.923849Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Run Log","metadata":{}},{"cell_type":"code","source":"# Validation metrics (order matches the metrics= list, with loss first)\nval_loss, val_acc, val_err = learn.validate()\nprint(f\"val_loss={val_loss:.4f}  val_acc={val_acc:.4f}  val_err={val_err:.4f}\")\n\n# Save graphs + config\nsave_run_artifacts(learn, run_id, config)\n\n# Log the run (config first, then metrics override any duplicates)\nlog_run(\n    **{k: v for k, v in config.items() if k != 'run_id'},\n    run_id=run_id,\n    val_loss=round(float(val_loss), 4),\n    val_acc=round(float(val_acc), 4),\n    lb_score=None,  # fill in after submitting\n)\n\n# Show the current table\npd.read_csv(LOG_PATH)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T10:58:10.926496Z","iopub.execute_input":"2026-10-14T10:58:10.926881Z","iopub.status.idle":"2026-10-14T10:58:45.866093Z","shell.execute_reply.started":"2026-10-14T10:58:10.926852Z","shell.execute_reply":"2026-10-14T10:58:45.865205Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Probabality Submission","metadata":{}},{"cell_type":"code","source":"test_files = get_image_files(WORK_DIR / 'test').sorted()\ntest_dl = dls.test_dl(test_files, with_labels=False)\n\npreds, _ = learn.get_preds(dl=test_dl)\nprobs = preds[:, 1]  # probability of class 1 = 'dog'\n\nsubmission = pd.read_csv(COMP_DIR / 'sample_submission.csv')\nsubmission['label'] = probs.numpy()\nsubmission.to_csv('/kaggle/working/submission_prob.csv', index=False)\n\nprint(submission.head())\nprint(f\"Shape: {submission.shape}\")\nprint(f\"Prob range: [{submission['label'].min():.4f}, {submission['label'].max():.4f}]\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T10:58:45.867452Z","iopub.execute_input":"2026-10-14T10:58:45.867838Z","iopub.status.idle":"2026-10-14T10:59:12.630668Z","shell.execute_reply.started":"2026-10-14T10:58:45.867808Z","shell.execute_reply":"2026-10-14T10:59:12.629711Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Zip the runs folder for download\nif (RUNS_DIR).exists():\n    shutil.make_archive('/kaggle/working/runs', 'zip', RUNS_DIR)\n\nfrom IPython.display import FileLink\nprint(\"Download these files and upload them to your Kaggle Dataset:\")\nFileLink('/kaggle/working/experiments.csv')\nFileLink('/kaggle/working/runs.zip')\nFileLink('/kaggle/working/submission_prob.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T10:59:49.123449Z","iopub.execute_input":"2026-10-14T10:59:49.124236Z","iopub.status.idle":"2026-10-14T10:59:49.155733Z","shell.execute_reply.started":"2026-10-14T10:59:49.124201Z","shell.execute_reply":"2026-10-14T10:59:49.155053Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}