{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"* V1 --> Base model\n* V2 --> Increased dataset size (thumbnails + slices)\n* V3 --> Customizable fine-tuning and Custmom images augmentation","metadata":{}},{"cell_type":"code","source":"from fastai.vision.all import *\nfrom fastai.torch_core import set_seed\nfrom fastai.vision.learner import _update_first_layer\n\nimport os\nimport re\nimport torch\nimport numpy\nimport random\nimport pandas as pd\nimport albumentations\nfrom timm import create_model\nfrom sklearn.utils.class_weight import compute_class_weight\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Fix random seed\nSEED = 123\nset_seed(SEED, reproducible=True)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<h4>Dataset Creation and Preparation:","metadata":{}},{"cell_type":"code","source":"# Create a dataframe containing thumbnail + slices paths\nimages_path = '/kaggle/input/tiles-of-cancer-2048px-scale-0-25'\n\nimages_list = []\nlabels_list = []\ndirs_list = []\n\ndf = pd.read_csv('/kaggle/input/UBC-OCEAN/train.csv')\n\nfor root, subdirs, files in os.walk(images_path): \n    for name in files:\n        if name != 'train.csv':\n            foo = os.path.join(root, name)\n            images_list.append(foo)\n\nimages_list = sorted(images_list)\n\nkeys_ = sorted(os.listdir(images_path))\nvalues_ = df['label']\nbind = {keys_[i]: values_[i] for i in range(len(keys_)) if keys_[i]!='train.csv'}\n\nfor p in images_list:\n    normalized_path = os.path.normpath(p)\n    path_components = normalized_path.split(os.sep)\n    k = path_components[4] \n    dirs_list.append(k)\n    label = bind[k]\n    labels_list.append(label)\n    \ntrain_df = pd.DataFrame({'image':images_list, 'directory':dirs_list,'label':labels_list})","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Create a dataframe containing thumbnail + slices paths\nthub_df = pd.read_csv('/kaggle/input/UBC-OCEAN/train.csv')\n\nbase_path = '/kaggle/input/UBC-OCEAN'\nclasses = np.unique(df['label'])\n\ndef get_file_train_path(image_id):\n    \n    thubs_path = f\"{base_path}/train_thumbnails/{image_id}_thumbnail.png\"\n    alternative_path = f\"{base_path}/train_images/{image_id}.png\"\n    \n    if os.path.exists(thubs_path):\n        return thubs_path\n    else:\n        return alternative_path\n    \ndef extract_numbers(text):\n    numbers = re.findall(r'\\d+', text)\n    numbers = [int(number) for number in numbers]\n    return numbers[0]\n    \nthub_df['image'] = thub_df['image_id'].apply(get_file_train_path)\nthub_df = thub_df.loc[:, ['image','label']]\nthub_df['directory'] = thub_df['image'].apply(extract_numbers)\n\ntrain_df = pd.concat([train_df, thub_df])\ntrain_df.reset_index(drop=True, inplace=True)\n\n#train_df.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Validation-set perc. calculation\ndef calc_perc_val(perc:float):\n    dirs = os.listdir(images_path)\n    n_elem = round(len(dirs) * perc) \n    validation_elems = random.sample(dirs, n_elem)\n    return validation_elems\n\nval_elems = calc_perc_val(0.23)\n\ntrain_df['is_valid'] = False\ntrain_df.loc[train_df['directory'].isin(val_elems), 'is_valid'] = True\n\ncount = train_df['is_valid'].value_counts()\nprint(f'Actual validation/training ratio: {round((count[1]/count[0])*100,1)}%')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<h4>Model Definition:","metadata":{}},{"cell_type":"code","source":"# Image Augmentation\nclass AlbumentationsTransform(RandTransform):\n    \"A transform handler for multiple `Albumentation` transforms\"\n    split_idx,order=None,2\n    def __init__(self, train_aug, valid_aug): store_attr()\n    \n    def before_call(self, b, split_idx):\n        self.idx = split_idx\n    \n    def encodes(self, img: PILImage):\n        if self.idx == 0:\n            aug_img = self.train_aug(image=np.array(img))['image']\n        else:\n            aug_img = self.valid_aug(image=np.array(img))['image']\n        return PILImage.create(aug_img)","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Image Augmentation\nRESIZED_DIM = 224\n\ndef get_train_aug(): return albumentations.Compose([\n            albumentations.CenterCrop(RESIZED_DIM,RESIZED_DIM,p=0.5),\n            albumentations.Transpose(p=0.5),\n            albumentations.HorizontalFlip(p=0.5),\n            albumentations.VerticalFlip(p=0.5),\n            albumentations.ShiftScaleRotate(p=0.5),\n])\n\ndef get_valid_aug(): return albumentations.Compose([\n    albumentations.Resize(RESIZED_DIM,RESIZED_DIM),\n    albumentations.HorizontalFlip(p=0.5),\n    albumentations.VerticalFlip(p=0.5),\n], p=1.)\n\nitem_tfms = [Resize(RESIZED_DIM), AlbumentationsTransform(get_train_aug(), get_valid_aug())]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"BS = 64\n\ndblock = DataBlock(\n    blocks = (ImageBlock, CategoryBlock),\n    get_x=ColReader('image'),\n    get_y = ColReader('label'),\n    splitter = ColSplitter (col='is_valid'),\n    item_tfms = item_tfms,\n    #batch_tfms=[Normalize.from_stats(*imagenet_stats)],\n)\n\ndls = dblock.dataloaders(train_df, bs=BS)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dls.show_batch()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Weighted cross-entropy loss\nclasses = np.unique(train_df['label'])\ny = train_df['label']\n\nweights = compute_class_weight(class_weight='balanced',classes=classes,y=y)\nweights = torch.tensor(weights).float()\n\nwce = torch.nn.CrossEntropyLoss(weight=weights)\n\ndict(zip(classes, weights.numpy()))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#TODO: Experimental model based on self-attention\n'''\nfrom wwf.vision.timm import timm_learner\n\nlearn = timm_learner(\n    dls, 'maxvit_tiny_rw_224', \n    loss_func=wce,\n    opt_func=ranger, \n    metrics=[accuracy,RocAuc()], \n    cbs=[\n        EarlyStoppingCallback(\n             monitor='valid_loss',\n             patience=5,\n         ),\n        #SaveModelCallback(),\n    ],\n).to_fp16()\n'''","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Layers selection for fine-tuning\ndef freeze_layers(model, N=0):\n    ct = 0\n    for child in model.children():\n        ct += 1\n        if ct <= N:\n            for param in child.parameters():\n                param.requires_grad = False\n\ndef create_timm_body(arch:str, pretrained=True, cut=None, n_in=3, N_freeze=0):\n    \"Creates a body from any model in the `timm` library.\"\n    model = create_model(arch, pretrained=pretrained, num_classes=0, global_pool='')\n    _update_first_layer(model, n_in, pretrained)\n    if cut is None:\n        ll = list(enumerate(model.children()))\n        cut = next(i for i,o in reversed(ll) if has_pool_type(o))\n        \n        # Freeze the first N+1 layers\n        freeze_layers(model, N=N_freeze)\n        print(f'Total number of children: {len(list(model.children()))} \\nNumber of frozen children: {N_freeze + 1}')\n        \n    if isinstance(cut, int): return nn.Sequential(*list(model.children())[:cut])\n    elif callable(cut): return cut(model)\n    else: raise NamedError(\"cut must be either integer or function\")\n\nhead = create_head(1280, dls.c)        \nbody = create_timm_body('efficientnet_b0', N_freeze=4)\n\nmodel = nn.Sequential(body, head)\napply_init(model[1], nn.init.kaiming_normal_)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learn = Learner(\n    dls, model,\n    loss_func=wce,\n    opt_func=ranger, \n    metrics=[accuracy,RocAuc()], \n    cbs=[\n        EarlyStoppingCallback(\n             monitor='valid_loss',\n             patience=5,\n         ),\n        #SaveModelCallback(),\n    ],\n).to_fp16()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<h4>Training","metadata":{}},{"cell_type":"code","source":"#Plot the suggested learning-rate\n\nlearn.lr_find()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"LR = 2e-3\n\nlearn.fit_flat_cos(n_epoch=1, lr=slice(LR))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#1.426394\t1.765182\t0.186315\t0.508547\t21:12 --> bs=128, N_freeze=4\n#1.413216\t1.728935\t0.197939\t0.519116\t17:33 --> bs=64, N_freeze=4","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}