{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":10700,"databundleVersionId":503808,"sourceType":"competition"}],"dockerImageVersionId":25160,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# CNN 2D Basic Solution Powered by fast.ai\n\nThis kernel explains basic solution that I've used in the last competition and many of top competitors also.\nIt's CNN, even ImageNet pretrained model works fine with audio 2D image like data.\n\nWill show:\n\n- Converting audio to 2D image like array, so that we can simply exploit strong CNN classifier.\n- fast.ai to build fast and strong multi-label classifier model. Unlike normal use, we need to train from scratch to comply competition rule. (Though if we use ImageNet pretrained model, it converges super fast...)\n- With simple codes.\n\n## Update 30-Apr, 2019\n- Now fast.ai library ready to use lwlrap as metric: https://colab.research.google.com/drive/1AgPdhSp7ttY18O3fEoHOQKlt_3HJDLi8\n- And TTA! https://github.com/fastai/fastai/blob/master/fastai/vision/tta.py --> Oops, it might not be effective for this problem. Now planning to update one more...\n\n## Update 28-Apr, 2019\n\n- Removed EasyDict dependency.\n- Training steps improved, tuned by running `lr_find()` and `fit_one_cycle()` iteratively.","metadata":{}},{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load in \n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nfrom pathlib import Path\nimport matplotlib.pyplot as plt\nfrom tqdm import tqdm_notebook\nimport IPython\nimport IPython.display\nimport PIL\n\n# Input data files are available in the \"../input/\" directory.\n# For example, running this (by clicking run or pressing Shift+Enter) will list the files in the input directory\n\nimport os\nprint(os.listdir(\"../input\"))\n\n# Any results you write to the current directory are saved as output.","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:08.263568Z","iopub.execute_input":"2025-10-06T04:46:08.263744Z","iopub.status.idle":"2025-10-06T04:46:08.271052Z","shell.execute_reply.started":"2025-10-06T04:46:08.26371Z","shell.execute_reply":"2025-10-06T04:46:08.270258Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## File/folder definitions\n\n- `df` will handle training data.\n- `test_df` will handle test data.","metadata":{}},{"cell_type":"code","source":"DATA = Path('../input')\nCSV_TRN_CURATED = DATA/'train_curated.csv'\nCSV_TRN_NOISY = DATA/'train_noisy.csv'\nCSV_SUBMISSION = DATA/'sample_submission.csv'\nTRN_CURATED = DATA/'train_curated'\nTRN_NOISY = DATA/'train_noisy'\nTEST = DATA/'test'\n\nWORK = Path('work')\n\nTRN_CURATED_Working =  WORK/'trn_curated'\nTRN_CURATED = TRN_CURATED_Working\n\nTEST_working = WORK/'test'\n\n\nIMG_TRN_CURATED = WORK/'image/trn_curated'\nIMG_TRN_NOISY = WORK/'image/train_noisy'\nIMG_TEST = WORK/'image/test'\nfor folder in [WORK, IMG_TRN_CURATED, IMG_TRN_NOISY, IMG_TEST]: \n    Path(folder).mkdir(exist_ok=True, parents=True)\n\ndf = pd.read_csv(CSV_TRN_CURATED)\ntest_df = pd.read_csv(CSV_SUBMISSION)","metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true,"execution":{"iopub.status.busy":"2025-10-06T05:41:31.965612Z","iopub.status.idle":"2025-10-06T05:41:31.966056Z","shell.execute_reply":"2025-10-06T05:41:31.965749Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import zipfile","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T05:26:19.523974Z","iopub.execute_input":"2025-10-06T05:26:19.524282Z","iopub.status.idle":"2025-10-06T05:26:19.527521Z","shell.execute_reply.started":"2025-10-06T05:26:19.524227Z","shell.execute_reply":"2025-10-06T05:26:19.526682Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with zipfile.ZipFile('../input/train_curated.zip', 'r') as zip_ref:\n    zip_ref.extractall(TRN_CURATED_Working)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T05:34:57.48811Z","iopub.execute_input":"2025-10-06T05:34:57.488386Z","iopub.status.idle":"2025-10-06T05:35:26.186805Z","shell.execute_reply.started":"2025-10-06T05:34:57.488344Z","shell.execute_reply":"2025-10-06T05:35:26.186209Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with zipfile.ZipFile('../input/test.zip', 'r') as zip_ref:\n    zip_ref.extractall(TEST_working)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T05:41:31.96681Z","iopub.status.idle":"2025-10-06T05:41:31.967154Z","shell.execute_reply":"2025-10-06T05:41:31.966959Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Audio conversion to 2D\n\nAlmost copyed from my repository: https://github.com/daisukelab/ml-sound-classifier\n- Handle sampling rate 44.1kHz as is, no information loss.\n- Size of each file will be 128 x L, L is audio seconds x 128; `[128, 256]`  if sound is 2s long.\n- Convert to Mel-spectrogram, not MFCC. We are handling general sound rather than human voice. https://en.wikipedia.org/wiki/Spectrogram","metadata":{}},{"cell_type":"code","source":"import librosa\nimport librosa.display\n\ndef read_audio(conf, pathname, trim_long_data):\n    y, sr = librosa.load(pathname, sr=conf.sampling_rate)\n    # trim silence\n    if 0 < len(y): # workaround: 0 length causes error\n        y, _ = librosa.effects.trim(y) # trim, top_db=default(60)\n    # make it unified length to conf.samples\n    if len(y) > conf.samples: # long enough\n        if trim_long_data:\n            y = y[0:0+conf.samples]\n    else: # pad blank\n        padding = conf.samples - len(y)    # add padding at both ends\n        offset = padding // 2\n        y = np.pad(y, (offset, conf.samples - len(y) - offset), 'constant')\n    return y\n\ndef audio_to_melspectrogram(conf, audio):\n    spectrogram = librosa.feature.melspectrogram(audio, \n                                                 sr=conf.sampling_rate,\n                                                 n_mels=conf.n_mels,\n                                                 hop_length=conf.hop_length,\n                                                 n_fft=conf.n_fft,\n                                                 fmin=conf.fmin,\n                                                 fmax=conf.fmax)\n    spectrogram = librosa.power_to_db(spectrogram)\n    spectrogram = spectrogram.astype(np.float32)\n    return spectrogram\n\ndef show_melspectrogram(conf, mels, title='Log-frequency power spectrogram'):\n    librosa.display.specshow(mels, x_axis='time', y_axis='mel', \n                             sr=conf.sampling_rate, hop_length=conf.hop_length,\n                            fmin=conf.fmin, fmax=conf.fmax)\n    plt.colorbar(format='%+2.0f dB')\n    plt.title(title)\n    plt.show()\n\ndef read_as_melspectrogram(conf, pathname, trim_long_data, debug_display=False):\n    x = read_audio(conf, pathname, trim_long_data)\n    mels = audio_to_melspectrogram(conf, x)\n    if debug_display:\n        IPython.display.display(IPython.display.Audio(x, rate=conf.sampling_rate))\n        show_melspectrogram(conf, mels)\n    return mels\n\n\nclass conf:\n    # Preprocessing settings\n    sampling_rate = 44100\n    duration = 2\n    hop_length = 347*duration # to make time steps 128\n    fmin = 20\n    fmax = sampling_rate // 2\n    n_mels = 128\n    n_fft = n_mels * 20\n    samples = sampling_rate * duration\n\n# example\nx = read_as_melspectrogram(conf, TRN_CURATED/'0006ae4e.wav', trim_long_data=False, debug_display=True)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T05:35:31.512617Z","iopub.execute_input":"2025-10-06T05:35:31.512855Z","iopub.status.idle":"2025-10-06T05:35:32.611115Z","shell.execute_reply.started":"2025-10-06T05:35:31.512819Z","shell.execute_reply":"2025-10-06T05:35:32.610354Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Making 2D mel-spectrogram data as 2D 3ch images\n\nSo that normal CNN image classifier can handle.\nI wanted to put them into files, but kernel has restriction to keep files less than 500.\nWe need to keep the data on memory.\n\nOf course this has positive effect, training gets faster.","metadata":{}},{"cell_type":"code","source":"def mono_to_color(X, mean=None, std=None, norm_max=None, norm_min=None, eps=1e-6):\n    # Stack X as [X,X,X]\n    X = np.stack([X, X, X], axis=-1)\n\n    # Standardize\n    mean = mean or X.mean()\n    std = std or X.std()\n    Xstd = (X - mean) / (std + eps)\n    _min, _max = Xstd.min(), Xstd.max()\n    norm_max = norm_max or _max\n    norm_min = norm_min or _min\n    if (_max - _min) > eps:\n        # Scale to [0, 255]\n        V = Xstd\n        V[V < norm_min] = norm_min\n        V[V > norm_max] = norm_max\n        V = 255 * (V - norm_min) / (norm_max - norm_min)\n        V = V.astype(np.uint8)\n    else:\n        # Just zero\n        V = np.zeros_like(Xstd, dtype=np.uint8)\n    return V\n\ndef convert_wav_to_image(df, source, img_dest):\n    X = []\n    for i, row in tqdm_notebook(df.iterrows()):\n        x = read_as_melspectrogram(conf, source/str(row.fname), trim_long_data=False)\n        x_color = mono_to_color(x)\n        X.append(x_color)\n    return X\n\nX_train = convert_wav_to_image(df, source=TRN_CURATED, img_dest=IMG_TRN_CURATED)\nX_test = convert_wav_to_image(test_df, source=TEST, img_dest=IMG_TEST)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T05:45:15.935798Z","iopub.execute_input":"2025-10-06T05:45:15.936053Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Custom `open_image` for fast.ai library to load data from memory\n\n- Important note: Random cropping 1 sec, this is working like augmentation.","metadata":{}},{"cell_type":"code","source":"from fastai import *\nfrom fastai.vision import *\nfrom fastai.vision.data import *\nimport random\n\nCUR_X_FILES, CUR_X = list(df.fname.values), X_train\n\ndef open_fat2019_image(fn, convert_mode, after_open)->Image:\n    # open\n    idx = CUR_X_FILES.index(fn.split('/')[-1])\n    x = PIL.Image.fromarray(CUR_X[idx])\n    # crop\n    time_dim, base_dim = x.size\n    crop_x = random.randint(0, time_dim - base_dim)\n    x = x.crop([crop_x, 0, crop_x+base_dim, base_dim])    \n    # standardize\n    return Image(pil2tensor(x, np.float32).div_(255))\n\nvision.data.open_image = open_fat2019_image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.072599Z","iopub.status.idle":"2025-10-06T04:46:10.073111Z","shell.execute_reply":"2025-10-06T04:46:10.072746Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Follow multi-label classification \n\n- Almost following fast.ai course: https://nbviewer.jupyter.org/github/fastai/course-v3/blob/master/nbs/dl1/lesson3-planet.ipynb\n- But `pretrained=False`\n- With lwlrap as metric: https://colab.research.google.com/drive/1AgPdhSp7ttY18O3fEoHOQKlt_3HJDLi8","metadata":{}},{"cell_type":"code","source":"# from official code https://colab.research.google.com/drive/1AgPdhSp7ttY18O3fEoHOQKlt_3HJDLi8#scrollTo=cRCaCIb9oguU\ndef _one_sample_positive_class_precisions(scores, truth):\n    \"\"\"Calculate precisions for each true class for a single sample.\n\n    Args:\n      scores: np.array of (num_classes,) giving the individual classifier scores.\n      truth: np.array of (num_classes,) bools indicating which classes are true.\n\n    Returns:\n      pos_class_indices: np.array of indices of the true classes for this sample.\n      pos_class_precisions: np.array of precisions corresponding to each of those\n        classes.\n    \"\"\"\n    num_classes = scores.shape[0]\n    pos_class_indices = np.flatnonzero(truth > 0)\n    # Only calculate precisions if there are some true classes.\n    if not len(pos_class_indices):\n        return pos_class_indices, np.zeros(0)\n    # Retrieval list of classes for this sample.\n    retrieved_classes = np.argsort(scores)[::-1]\n    # class_rankings[top_scoring_class_index] == 0 etc.\n    class_rankings = np.zeros(num_classes, dtype=np.int)\n    class_rankings[retrieved_classes] = range(num_classes)\n    # Which of these is a true label?\n    retrieved_class_true = np.zeros(num_classes, dtype=np.bool)\n    retrieved_class_true[class_rankings[pos_class_indices]] = True\n    # Num hits for every truncated retrieval list.\n    retrieved_cumulative_hits = np.cumsum(retrieved_class_true)\n    # Precision of retrieval list truncated at each hit, in order of pos_labels.\n    precision_at_hits = (\n            retrieved_cumulative_hits[class_rankings[pos_class_indices]] /\n            (1 + class_rankings[pos_class_indices].astype(np.float)))\n    return pos_class_indices, precision_at_hits\n\n\ndef calculate_per_class_lwlrap(truth, scores):\n    \"\"\"Calculate label-weighted label-ranking average precision.\n\n    Arguments:\n      truth: np.array of (num_samples, num_classes) giving boolean ground-truth\n        of presence of that class in that sample.\n      scores: np.array of (num_samples, num_classes) giving the classifier-under-\n        test's real-valued score for each class for each sample.\n\n    Returns:\n      per_class_lwlrap: np.array of (num_classes,) giving the lwlrap for each\n        class.\n      weight_per_class: np.array of (num_classes,) giving the prior of each\n        class within the truth labels.  Then the overall unbalanced lwlrap is\n        simply np.sum(per_class_lwlrap * weight_per_class)\n    \"\"\"\n    assert truth.shape == scores.shape\n    num_samples, num_classes = scores.shape\n    # Space to store a distinct precision value for each class on each sample.\n    # Only the classes that are true for each sample will be filled in.\n    precisions_for_samples_by_classes = np.zeros((num_samples, num_classes))\n    for sample_num in range(num_samples):\n        pos_class_indices, precision_at_hits = (\n            _one_sample_positive_class_precisions(scores[sample_num, :],\n                                                  truth[sample_num, :]))\n        precisions_for_samples_by_classes[sample_num, pos_class_indices] = (\n            precision_at_hits)\n    labels_per_class = np.sum(truth > 0, axis=0)\n    weight_per_class = labels_per_class / float(np.sum(labels_per_class))\n    # Form average of each column, i.e. all the precisions assigned to labels in\n    # a particular class.\n    per_class_lwlrap = (np.sum(precisions_for_samples_by_classes, axis=0) /\n                        np.maximum(1, labels_per_class))\n    # overall_lwlrap = simple average of all the actual per-class, per-sample precisions\n    #                = np.sum(precisions_for_samples_by_classes) / np.sum(precisions_for_samples_by_classes > 0)\n    #           also = weighted mean of per-class lwlraps, weighted by class label prior across samples\n    #                = np.sum(per_class_lwlrap * weight_per_class)\n    return per_class_lwlrap, weight_per_class\n\n\n# Wrapper for fast.ai library\ndef lwlrap(scores, truth, **kwargs):\n    score, weight = calculate_per_class_lwlrap(to_np(truth), to_np(scores))\n    return torch.Tensor([(score * weight).sum()])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.073825Z","iopub.status.idle":"2025-10-06T04:46:10.074488Z","shell.execute_reply":"2025-10-06T04:46:10.074047Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tfms = get_transforms(do_flip=True, max_rotate=0, max_lighting=0.1, max_zoom=0, max_warp=0.)\nsrc = (ImageList.from_csv(WORK/'image', Path('../../')/CSV_TRN_CURATED, folder='trn_curated')\n       .split_by_rand_pct(0.2)\n       .label_from_df(label_delim=',')\n)\ndata = (src.transform(tfms, size=128)\n        .databunch(bs=64).normalize(imagenet_stats)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.075322Z","iopub.status.idle":"2025-10-06T04:46:10.075868Z","shell.execute_reply":"2025-10-06T04:46:10.07561Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data.show_batch(3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.076634Z","iopub.status.idle":"2025-10-06T04:46:10.077081Z","shell.execute_reply":"2025-10-06T04:46:10.07683Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learn = cnn_learner(data, models.resnet18, pretrained=False, metrics=[lwlrap])\nlearn.unfreeze()\n\nlearn.lr_find(); learn.recorder.plot()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.077921Z","iopub.status.idle":"2025-10-06T04:46:10.078386Z","shell.execute_reply":"2025-10-06T04:46:10.078132Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learn.fit_one_cycle(5, 1e-1)\nlearn.fit_one_cycle(10, 1e-2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.079166Z","iopub.status.idle":"2025-10-06T04:46:10.079835Z","shell.execute_reply":"2025-10-06T04:46:10.079571Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learn.lr_find(); learn.recorder.plot()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.080501Z","iopub.status.idle":"2025-10-06T04:46:10.081181Z","shell.execute_reply":"2025-10-06T04:46:10.080885Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learn.fit_one_cycle(20, 3e-3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.082054Z","iopub.status.idle":"2025-10-06T04:46:10.082543Z","shell.execute_reply":"2025-10-06T04:46:10.082301Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learn.lr_find(); learn.recorder.plot()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.083302Z","iopub.status.idle":"2025-10-06T04:46:10.08377Z","shell.execute_reply":"2025-10-06T04:46:10.08352Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learn.fit_one_cycle(20, 1e-3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.08441Z","iopub.status.idle":"2025-10-06T04:46:10.085037Z","shell.execute_reply":"2025-10-06T04:46:10.084737Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learn.lr_find(); learn.recorder.plot()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.08572Z","iopub.status.idle":"2025-10-06T04:46:10.086393Z","shell.execute_reply":"2025-10-06T04:46:10.086102Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learn.fit_one_cycle(50, slice(1e-3, 3e-3))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.087373Z","iopub.status.idle":"2025-10-06T04:46:10.088058Z","shell.execute_reply":"2025-10-06T04:46:10.087675Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learn.fit_one_cycle(10, slice(1e-4, 1e-3))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.088783Z","iopub.status.idle":"2025-10-06T04:46:10.089383Z","shell.execute_reply":"2025-10-06T04:46:10.089123Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Let's check how filters are","metadata":{}},{"cell_type":"code","source":"# https://discuss.pytorch.org/t/how-to-visualize-the-actual-convolution-filters-in-cnn/13850\nfrom sklearn.preprocessing import minmax_scale\n\ndef visualize_first_layer(learn, save_name=None):\n    conv1 = list(learn.model.children())[0][0]\n    if isinstance(conv1, torch.nn.modules.container.Sequential):\n        conv1 = conv1[0] # for some models, 1 layer inside\n    weights = conv1.weight.data.cpu().numpy()\n    weights_shape = weights.shape\n    weights = minmax_scale(weights.ravel()).reshape(weights_shape)\n    fig, axes = plt.subplots(8, 8, figsize=(8,8))\n    for i, ax in enumerate(axes.flat):\n        ax.imshow(np.rollaxis(weights[i], 0, 3))\n        ax.get_xaxis().set_visible(False)\n        ax.get_yaxis().set_visible(False)\n    if save_name:\n        fig.savefig(str(save_name))\n\nvisualize_first_layer(learn)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.089993Z","iopub.status.idle":"2025-10-06T04:46:10.090547Z","shell.execute_reply":"2025-10-06T04:46:10.090294Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learn.save('fat2019_fastai_cnn2d_stage-2')\nlearn.export()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.091264Z","iopub.status.idle":"2025-10-06T04:46:10.091852Z","shell.execute_reply":"2025-10-06T04:46:10.091619Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Test prediction and making submission file simple\n- Switch to test data.\n- Overwrite results to sample submission; simple way to prepare submission file.\n- Now using TTA (Test Time Augmentation)!","metadata":{}},{"cell_type":"code","source":"CUR_X_FILES, CUR_X = list(test_df.fname.values), X_test\n\ntest = ImageList.from_csv(WORK/'image', Path('../..')/CSV_SUBMISSION, folder='test')\nlearn = load_learner(WORK/'image', test=test)\npreds, _ = learn.TTA(ds_type=DatasetType.Test) # <== Simply replacing from learn.get_preds()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.09258Z","iopub.status.idle":"2025-10-06T04:46:10.09313Z","shell.execute_reply":"2025-10-06T04:46:10.092817Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df[learn.data.classes] = preds\ntest_df.to_csv('submission.csv', index=False)\ntest_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.093789Z","iopub.status.idle":"2025-10-06T04:46:10.094367Z","shell.execute_reply":"2025-10-06T04:46:10.094013Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"CUR_X_FILES, CUR_X = list(df.fname.values), X_train\nlearn = cnn_learner(data, models.resnet18, pretrained=False, metrics=[lwlrap])\nlearn.load('fat2019_fastai_cnn2d_stage-2');","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.095159Z","iopub.status.idle":"2025-10-06T04:46:10.09577Z","shell.execute_reply":"2025-10-06T04:46:10.095365Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Visualize by CAM","metadata":{}},{"cell_type":"code","source":"# Thanks to https://nbviewer.jupyter.org/github/fastai/course-v3/blob/master/nbs/dl1/lesson6-pets-more.ipynb\nfrom fastai.callbacks.hooks import *\n\ndef visualize_cnn_by_cam(learn, data_index):\n    x, _y = learn.data.valid_ds[data_index]\n    y = _y.data\n    if not isinstance(y, (list, np.ndarray)): # single label -> one hot encoding\n        y = np.eye(learn.data.valid_ds.c)[y]\n\n    m = learn.model.eval()\n    xb,_ = learn.data.one_item(x)\n    xb_im = Image(learn.data.denorm(xb)[0])\n    xb = xb.cuda()\n\n    def hooked_backward(cat):\n        with hook_output(m[0]) as hook_a: \n            with hook_output(m[0], grad=True) as hook_g:\n                preds = m(xb)\n                preds[0,int(cat)].backward()\n        return hook_a,hook_g\n    def show_heatmap(img, hm, label):\n        _,axs = plt.subplots(1, 2)\n        axs[0].set_title(label)\n        img.show(axs[0])\n        axs[1].set_title(f'CAM of {label}')\n        img.show(axs[1])\n        axs[1].imshow(hm, alpha=0.6, extent=(0,img.shape[0],img.shape[0],0),\n                      interpolation='bilinear', cmap='magma');\n        plt.show()\n\n    for y_i in np.where(y > 0)[0]:\n        hook_a,hook_g = hooked_backward(cat=y_i)\n        acts = hook_a.stored[0].cpu()\n        grad = hook_g.stored[0][0].cpu()\n        grad_chan = grad.mean(1).mean(1)\n        mult = (acts*grad_chan[...,None,None]).mean(0)\n        show_heatmap(img=xb_im, hm=mult, label=str(learn.data.valid_ds.y[data_index]))\n\nfor idx in range(10):\n    visualize_cnn_by_cam(learn, idx)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T04:46:10.09656Z","iopub.status.idle":"2025-10-06T04:46:10.096909Z","shell.execute_reply":"2025-10-06T04:46:10.096699Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}