{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":39272,"databundleVersionId":4629629,"sourceType":"competition"},{"sourceId":4639110,"sourceType":"datasetVersion","datasetId":2692826},{"sourceId":4675845,"sourceType":"datasetVersion","datasetId":2710646},{"sourceId":4696088,"sourceType":"datasetVersion","datasetId":2687741},{"sourceId":4698238,"sourceType":"datasetVersion","datasetId":2719281},{"sourceId":4709451,"sourceType":"datasetVersion","datasetId":2707371},{"sourceId":104036025,"sourceType":"kernelVersion"}],"dockerImageVersionId":30302,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<a id=\"section-one\"></a>\n# Training a fast.ai model\n","metadata":{"execution":{"iopub.status.busy":"2022-12-02T00:49:16.208242Z","iopub.execute_input":"2022-12-02T00:49:16.208915Z","iopub.status.idle":"2022-12-02T00:49:16.247884Z","shell.execute_reply.started":"2022-12-02T00:49:16.20876Z","shell.execute_reply":"2022-12-02T00:49:16.244677Z"}}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!unzip -q ../input/timm-with-dependencies/timm_all -d timm-with-dependencies\n!pip install --no-index --find-links timm-with-dependencies timm\n!pip install /kaggle/input/dicomsdl-offline-installer/dicomsdl-0.109.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl\n\nfrom fastai.vision.learner import *\nfrom fastai.data.all import *\nfrom fastai.vision.all import *\nfrom fastai.metrics import ActivationType\n\nfrom sklearn.model_selection import StratifiedKFold\nfrom collections import defaultdict\nimport pandas as pd\nimport numpy as np\nfrom pdb import set_trace","metadata":{"execution":{"iopub.status.busy":"2025-12-16T19:59:35.011407Z","iopub.execute_input":"2025-12-16T19:59:35.011785Z","iopub.status.idle":"2025-12-16T20:00:56.67626Z","shell.execute_reply.started":"2025-12-16T19:59:35.011697Z","shell.execute_reply":"2025-12-16T20:00:56.675167Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"NUM_EPOCHS = 4\nNUM_SPLITS = 4\n\nRESIZE_TO = (1024, 1024)\n\nDATA_PATH = '/kaggle/input/rsna-breast-cancer-detection'\nTRAIN_IMAGE_DIR = '/kaggle/input/rsna-mammography-images-as-pngs/images_as_pngs_cv2_512'\nTEST_DICOM_DIR = '/kaggle/input/rsna-breast-cancer-detection/test_images'\nMODEL_PATH = '/kaggle/input/rsna-trained-model-weights/tf_effv2_s_208_402/tf_effv2_s_208_402'\n\nlabel_smoothing_weights = torch.tensor([1,10]).float()\nif torch.cuda.is_available():\n    label_smoothing_weights = label_smoothing_weights.cuda()","metadata":{"execution":{"iopub.status.busy":"2025-12-16T20:02:31.906558Z","iopub.execute_input":"2025-12-16T20:02:31.907258Z","iopub.status.idle":"2025-12-16T20:02:36.262204Z","shell.execute_reply.started":"2025-12-16T20:02:31.907222Z","shell.execute_reply":"2025-12-16T20:02:36.261437Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Creating stratified splits for training","metadata":{}},{"cell_type":"code","source":"train_csv = pd.read_csv(f'{DATA_PATH}/train.csv')\npatient_id_any_cancer = train_csv.groupby('patient_id').cancer.max().reset_index()\nskf = StratifiedKFold(NUM_SPLITS, shuffle=True, random_state=42)\nsplits = list(skf.split(patient_id_any_cancer.patient_id, patient_id_any_cancer.cancer))","metadata":{"execution":{"iopub.status.busy":"2025-12-16T20:02:44.08046Z","iopub.execute_input":"2025-12-16T20:02:44.080805Z","iopub.status.idle":"2025-12-16T20:02:44.133476Z","shell.execute_reply.started":"2025-12-16T20:02:44.08077Z","shell.execute_reply":"2025-12-16T20:02:44.132758Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Defining some helper functions","metadata":{}},{"cell_type":"markdown","source":"I am defining some functionality here to make our life easier and to get us the last 10% of the way to a really good result.\n\nGenerally, none of this code is core to training or predicting, we could skip most of it and still be able to get a well trained model.\n\n\nBut here we want to push the boundaries of performance so let's get these things in 🙂","metadata":{}},{"cell_type":"code","source":"#https://www.kaggle.com/competitions/rsna-breast-cancer-detection/discussion/369267  \ndef pfbeta_torch(preds, labels, beta=1):\n    if preds.dim() != 2 or (preds.dim() == 2 and preds.shape[1] !=2): raise ValueError('Houston, we got a problem')\n    preds = preds[:, 1]\n    preds = preds.clip(0, 1)\n    y_true_count = labels.sum()\n    ctp = preds[labels==1].sum()\n    cfp = preds[labels==0].sum()\n    beta_squared = beta * beta\n    c_precision = ctp / (ctp + cfp)\n    c_recall = ctp / y_true_count\n    if (c_precision > 0 and c_recall > 0):\n        result = (1 + beta_squared) * (c_precision * c_recall) / (beta_squared * c_precision + c_recall)\n        return result\n    else:\n        return 0.0\n\n# https://www.kaggle.com/competitions/rsna-breast-cancer-detection/discussion/369886    \ndef pfbeta_torch_thresh(preds, labels):\n    optimized_preds = optimize_preds(preds, labels)\n    return pfbeta_torch(optimized_preds, labels)\n\ndef optimize_preds(preds, labels=None, thresh=None, return_thresh=False, print_results=False):\n    preds = preds.clone()\n    if labels is not None: without_thresh = pfbeta_torch(preds, labels)\n    \n    if not thresh and labels is not None:\n        threshs = np.linspace(0, 1, 101)\n        f1s = [pfbeta_torch((preds > thr).float(), labels) for thr in threshs]\n        idx = np.argmax(f1s)\n        thresh, best_pfbeta = threshs[idx], f1s[idx]\n\n    preds = (preds > thresh).float()\n\n    if print_results:\n        print(f'without optimization: {without_thresh}')\n        pfbeta = pfbeta_torch(preds, labels)\n        print(f'with optimization: {pfbeta}')\n        print(f'best_thresh = {thresh}')\n    if return_thresh:\n        return thresh\n    return preds\n\nfn2label = {fn: cancer_or_not for fn, cancer_or_not in zip(train_csv['image_id'].astype('str'), train_csv['cancer'])}\n\ndef splitting_func(paths):\n    train = []\n    valid = []\n    for idx, path in enumerate(paths):\n        if int(path.parent.name) in patient_id_any_cancer.iloc[splits[SPLIT][0]].patient_id.values:\n            train.append(idx)\n        else:\n            valid.append(idx)\n    return train, valid\n\ndef label_func(path):\n    return fn2label[path.stem]\n\ndef get_items(image_dir_path):\n    items = []\n    for p in get_image_files(image_dir_path):\n        items.append(p)\n        if p.stem in fn2label and int(p.parent.name) in patient_id_any_cancer.iloc[splits[SPLIT][0]].patient_id.values:\n            if label_func(p) == 1:\n                for _ in range(5):\n                    items.append(p)\n    return items","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-12-16T20:02:46.13968Z","iopub.execute_input":"2025-12-16T20:02:46.140061Z","iopub.status.idle":"2025-12-16T20:02:46.213419Z","shell.execute_reply.started":"2025-12-16T20:02:46.140029Z","shell.execute_reply":"2025-12-16T20:02:46.212515Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Wrapping getting data and getting a model into functions -- this way our logic for training will be cleaner to read.","metadata":{}},{"cell_type":"code","source":"from timm.models.layers.adaptive_avgmax_pool import SelectAdaptivePool2d\nfrom torch.nn import Flatten\n\ndef get_dataloaders():\n    train_image_path = TRAIN_IMAGE_DIR\n    batch_tfms = [\n    *aug_transforms(\n        flip_vert=False,\n        max_rotate=10,\n        max_zoom=1.1,\n        max_lighting=0.2\n    )\n    ]\n    dblock = DataBlock(\n        blocks    = (ImageBlock, CategoryBlock),\n        get_items = get_items,\n        get_y = label_func,\n        splitter  = splitting_func,\n        batch_tfms=batch_tfms\n    )\n    dsets = dblock.datasets(train_image_path)\n    return dblock.dataloaders(train_image_path, batch_size=32)\n\ndef get_learner(arch):\n    if arch in ['tf_efficientnet_b4_ns', 'tf_efficientnetv2_s']:\n        n_feats = 1792\n    else:\n        n_feats = 512\n\n    learner = vision_learner(\n        get_dataloaders(),\n        arch,\n        custom_head=nn.Sequential(\n            SelectAdaptivePool2d(pool_type='avg', flatten=Flatten()),\n            nn.Linear(n_feats, 2)\n        ),\n        metrics=[\n            error_rate,\n            AccumMetric(pfbeta_torch, activation=ActivationType.Softmax, flatten=False),\n            AccumMetric(pfbeta_torch_thresh, activation=ActivationType.Softmax, flatten=False)\n        ],\n       loss_func=CrossEntropyLossFlat(weight=torch.tensor([1,20]).float()),\n        pretrained=True,\n        normalize=True\n    ).to_fp16()\n\n    return learner\n","metadata":{"execution":{"iopub.status.busy":"2025-12-16T20:02:49.421201Z","iopub.execute_input":"2025-12-16T20:02:49.421554Z","iopub.status.idle":"2025-12-16T20:02:49.429033Z","shell.execute_reply.started":"2025-12-16T20:02:49.421523Z","shell.execute_reply":"2025-12-16T20:02:49.427983Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Creating the learner and training","metadata":{}},{"cell_type":"code","source":"# This is a dependency that is needed for reading DICOM images\n\ntry:\n    import pylibjpeg\nexcept:\n    !rm -rf /root/.cache/torch/hub/checkpoints/\n    !mkdir -p /root/.cache/torch/hub/checkpoints/\n    !pip install /kaggle/input/rsna-2022-whl/{pydicom-2.3.0-py3-none-any.whl,pylibjpeg-1.4.0-py3-none-any.whl,python_gdcm-3.0.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl}\n    !pip install /kaggle/input/rsna-2022-whl/{torch-1.12.1-cp37-cp37m-manylinux1_x86_64.whl,torchvision-0.13.1-cp37-cp37m-manylinux1_x86_64.whl}\n\n# copying the pretrained weights\n\nif not os.path.exists('/root/.cache/torch/hub/checkpoints/'):\n        os.makedirs('/root/.cache/torch/hub/checkpoints/')\n!cp '/kaggle/input/pretrained-model-weights-for-fastai/resnet18-f37072fd.pth' '/root/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth'\n!cp '/kaggle/input/pretrained-model-weights-for-fastai/tf_efficientnetv2_s-eb54923e.pth' '/root/.cache/torch/hub/checkpoints/tf_efficientnetv2_s-eb54923e.pth'","metadata":{"execution":{"iopub.status.busy":"2025-12-16T20:02:52.698683Z","iopub.execute_input":"2025-12-16T20:02:52.69964Z","iopub.status.idle":"2025-12-16T20:05:07.751247Z","shell.execute_reply.started":"2025-12-16T20:02:52.699596Z","shell.execute_reply":"2025-12-16T20:05:07.749861Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n\npreds, labels = [], []\n\nSPLIT = 0 # our learner needs this to construct its dataloaders...\nlearn = get_learner('tf_efficientnet_b4_ns')\nlearn.freeze()\nlearn.fit_one_cycle(3, 3e-4)\n\nlearn.unfreeze()\nlearn.fit_one_cycle(8, 1e-4)\n\n\n\n# instead of training, to conserve pipeline time, I am uploading models trained locally\n# uncomment the lines below for training\n  \n# for SPLIT in range(NUM_SPLITS):\n#     learn = get_learner()\n#     learn.unfreeze()\n#     learn.fit_one_cycle(NUM_EPOCHS, 1e-4, pct_start=0.1)\n#     learn.save(f'{MODEL_PATH}/{SPLIT}')\n        \n#     output = learn.get_preds()\n#     preds.append(output[0])\n#     labels.append(output[1])","metadata":{"execution":{"iopub.status.busy":"2025-12-16T20:05:28.051625Z","iopub.execute_input":"2025-12-16T20:05:28.052388Z","iopub.status.idle":"2025-12-16T20:07:38.693006Z","shell.execute_reply.started":"2025-12-16T20:05:28.052339Z","shell.execute_reply":"2025-12-16T20:07:38.691748Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# threshold = optimize_preds(torch.cat(preds), torch.cat(labels), return_thresh=True, print_results=True)\nthreshold = 0.402","metadata":{"execution":{"iopub.status.busy":"2025-12-16T20:07:43.940759Z","iopub.execute_input":"2025-12-16T20:07:43.941123Z","iopub.status.idle":"2025-12-16T20:07:43.945592Z","shell.execute_reply.started":"2025-12-16T20:07:43.941093Z","shell.execute_reply":"2025-12-16T20:07:43.94444Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Predicting on test<a id=\"section-two\">","metadata":{}},{"cell_type":"code","source":"# import pydicom\n# from pydicom.pixel_data_handlers.util import apply_voi_lut\nimport dicomsdl\n    \nfrom pathlib import Path\nimport multiprocessing as mp\nimport cv2\n\n!rm -rf test_resized_{RESIZE_TO[0]}\n\ndef dicom_file_to_ary(path):\n    dcm_file = dicomsdl.open(str(path))\n    data = dcm_file.pixelData()\n\n    data = (data - data.min()) / (data.max() - data.min())\n\n    if dcm_file.getPixelDataInfo()['PhotometricInterpretation'] == \"MONOCHROME1\":\n        data = 1 - data\n\n    data = cv2.resize(data, RESIZE_TO)\n    data = (data * 255).astype(np.uint8)\n    return data\n\ndirectories = list(Path(TEST_DICOM_DIR).iterdir())\n\ndef process_directory(directory_path):\n    parent_directory = str(directory_path).split('/')[-1]\n    !mkdir -p test_resized_{RESIZE_TO[0]}/{parent_directory}\n    for image_path in directory_path.iterdir():\n        processed_ary = dicom_file_to_ary(image_path)\n        cv2.imwrite(\n            f'test_resized_{RESIZE_TO[0]}/{parent_directory}/{image_path.stem}.png',\n            processed_ary\n        )\n\nwith mp.Pool(mp.cpu_count()) as p:\n    p.map(process_directory, directories)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-12-16T20:07:48.938737Z","iopub.execute_input":"2025-12-16T20:07:48.939748Z","iopub.status.idle":"2025-12-16T20:07:54.181349Z","shell.execute_reply.started":"2025-12-16T20:07:48.939694Z","shell.execute_reply":"2025-12-16T20:07:54.180149Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n\npreds_all = []\n\ntest_dl = learn.dls.test_dl(get_image_files(f'test_resized_{RESIZE_TO[0]}'))\nfor SPLIT in range(NUM_SPLITS):\n    learn.load(f'{MODEL_PATH}/{SPLIT}')\n    preds, _ = learn.get_preds(dl=test_dl)\n    preds_all.append(preds)","metadata":{"execution":{"iopub.status.busy":"2025-12-16T20:08:01.729998Z","iopub.execute_input":"2025-12-16T20:08:01.730382Z","iopub.status.idle":"2025-12-16T20:08:19.648573Z","shell.execute_reply.started":"2025-12-16T20:08:01.730343Z","shell.execute_reply":"2025-12-16T20:08:19.647473Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"preds = torch.zeros_like(preds_all[0])\nfor pred in preds_all:\n    preds += pred\n\npreds /= NUM_SPLITS\n\n\npreds = optimize_preds(preds, thresh=threshold)\nimage_ids = [path.stem for path in test_dl.items]\n\nimage_id2pred = defaultdict(lambda: 0)\nfor image_id, pred in zip(image_ids, preds[:, 1]):\n    image_id2pred[int(image_id)] = pred.item()","metadata":{"execution":{"iopub.status.busy":"2025-12-16T20:08:47.66744Z","iopub.execute_input":"2025-12-16T20:08:47.66778Z","iopub.status.idle":"2025-12-16T20:08:47.674905Z","shell.execute_reply.started":"2025-12-16T20:08:47.667751Z","shell.execute_reply":"2025-12-16T20:08:47.673852Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<a id=\"section-three\"></a>\n# Making a submission","metadata":{}},{"cell_type":"code","source":"test_csv = pd.read_csv('/kaggle/input/rsna-breast-cancer-detection/test.csv')\n\nprediction_ids = []\npreds = []\n\nfor _, row in test_csv.iterrows():\n    prediction_ids.append(row.prediction_id)\n    preds.append(image_id2pred[row.image_id])\n\nsubmission = pd.DataFrame(data={'prediction_id': prediction_ids, 'cancer': preds}).groupby('prediction_id').max().reset_index()\nsubmission.head()","metadata":{"execution":{"iopub.status.busy":"2025-12-16T20:08:50.303514Z","iopub.execute_input":"2025-12-16T20:08:50.304257Z","iopub.status.idle":"2025-12-16T20:08:50.338979Z","shell.execute_reply.started":"2025-12-16T20:08:50.304222Z","shell.execute_reply":"2025-12-16T20:08:50.337944Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission.to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2025-12-16T20:08:54.184478Z","iopub.execute_input":"2025-12-16T20:08:54.185282Z","iopub.status.idle":"2025-12-16T20:08:54.193985Z","shell.execute_reply.started":"2025-12-16T20:08:54.185237Z","shell.execute_reply":"2025-12-16T20:08:54.19301Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"And that's it! Thank you very much for reading! 🙂\n\n**If you enjoyed the notebook, please upvote! 🙏 Thank you, appreciate your support!**\n\nHappy Kaggling 🥳\n","metadata":{}}]}