<jupyter_start><jupyter_text>Basic Model Let's get a basic model up-and-running. After we've implemented it here, we can move to `src/main.py` for a proper K-folds training loop.<jupyter_code>%reload_ext autoreload
%autoreload 2
%matplotlib inline
#from fastai import
import math
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from PIL import Image
from functools import partial
from sklearn.model_selection import train_test_split

import torch

import fastai
from fastai.torch_core import grab_idx
from fastai.core import subplots, split_kwargs_by_func
from fastai.basic_train import RecordOnCPU
from fastai.layers import BCEWithLogitsFlat
from fastai.vision import unet_learner, models, DatasetType
from fastai.vision import lr_find
from fastai.vision import Path, get_image_files, open_image, open_mask, SegmentationItemList, get_transforms, imagenet_stats
from fastai.metrics import dice
from fastai.vision import ResizeMethod
from fastai.vision import ImageSegment, SegmentationLabelList, pil2tensor

from src.utils import multiclass_dice, multiclass_dice_threshold, get_training_image_size, rle_decode, overrideOpenMask<jupyter_output><empty_output><jupyter_text>Note that we're using images of size `350x525` but we're training with images of size `352x544`. This is because UNet has a [hard requirement](https://forums.fast.ai/t/in-dynamicunet-learner-export-will-fail-if-set-dataloader-transform-size-some-odd-number/54926) that its inputs be multiple of `32`.<jupyter_code>DATA = Path('data')
TRAIN = DATA/"train.csv"
TEST = DATA/"sample_submission.csv"

size = (350,525)
training_image_size = get_training_image_size(size)     #UNet requires that inputs are multiples of 32
#If we want to train on smaller images, we can add their suffix here
SUFFIX = "_" + str(size[0]) + "x" + str(size[1])        #eg. _350x525
batch_size=8

path = Path('data')
train = pd.read_csv(TRAIN)
test = pd.read_csv(TEST)

train['label'] = train['Image_Label'].apply(lambda x: x.split('_')[1])
train['im_id'] = train['Image_Label'].apply(lambda x: x.split('_')[0])
test['label'] = test['Image_Label'].apply(lambda x: x.split('_')[1])
test['im_id'] = test['Image_Label'].apply(lambda x: x.split('_')[0])<jupyter_output><empty_output><jupyter_text>Since every images is duplicated 4 times (once for each class) let's get a dataframe with only unique images.<jupyter_code>unique_images = train.iloc[::4, :]
unique_test_images = test.iloc[::4, :]
# Our masks were converted from four run-length encodings into 4-channel masks. 
# For the sake of convenience, I've saved these masks as RGBA .png images on disks

# Use a custom approach for opening masks as RGBA
overrideOpenMask()
def get_y_fn(x):
    # Given a path to a training image, build the corresponding mask path
    split = x.split('/')
    newPath = DATA/("train_images_annots" + SUFFIX)/split[-1].replace('.jpg','.png')
    return newPath

codes = np.array(['Fish', 'Flower', 'Gravel', 'Sugar'])
id_mask_count = train.loc[train['EncodedPixels'].isnull() == False, 'Image_Label'].apply(lambda x: x.split('_')[0]).value_counts().\
reset_index().rename(columns={'index': 'img_id', 'Image_Label': 'count'})
train_ids, valid_ids = train_test_split(id_mask_count['img_id'].values, random_state=42, stratify=id_mask_count['count'], test_size=0.1)
def isValid(img_id):
    filename = img_id.split('/')[-1]
    return str(filename) in valid_ids
src = (SegmentationItemList.from_df(unique_images[:3000], path/'train_images_350x525', cols='im_id')
       .split_by_valid_func(isValid)
       .label_from_func(get_y_fn, classes=codes))
transforms = get_transforms()
data = (src.transform(transforms, tfm_y=True, size=training_image_size, resize_method=ResizeMethod.PAD, padding_mode="zeros")
        .databunch(bs=batch_size)
        .normalize(imagenet_stats))<jupyter_output><empty_output><jupyter_text>We would like to plot the masks over the images to sanity check whether or not we've created them correctly. We'll create our own custom `show_batch()` method that also keeps track of the filepath of the images in a batch. Then we can compare these images to the original masks.<jupyter_code>def flattenImageSegment(imSegment):
    
    imTensor = imSegment.data
    
    imTensor[0,:] = imTensor[0,:] * 1
    imTensor[1,:] = imTensor[1,:] * 2
    imTensor[2,:] = imTensor[2,:] * 3
    imTensor[3,:] = imTensor[3,:] * 4
    
    flat = imTensor.float().sum(dim=0)
    flat = flat.unsqueeze(0)
    
    return ImageSegment(flat)

def custom_show_batch(data, imgsize=4, rows=2):
    n_items = 6
    rows = 2

    # Get the filenames 
    filenames = data.train_ds.items[:n_items]

    # Get the images after data augmentation
    items = data.train_ds[:n_items]
    xs, ys = list(zip(*items))

    axs = subplots(rows, rows, imgsize=imgsize)

    for fn,x,y,ax in zip(filenames, xs, ys, axs.flatten()): 
        flatY = flattenImageSegment(y)
        x.show(ax=ax, y=flatY, title=fn)

    for ax in axs.flatten()[len(xs):]: 
        ax.axis('off')

    plt.tight_layout()<jupyter_output><empty_output><jupyter_text>This is the original code to visualize the masks:<jupyter_code>def plotImage(im_id):

    fig = plt.figure(figsize=(25, 16))
    for i, (idx, row) in enumerate(train.loc[train['im_id'] == im_id].iterrows()):
        ax = fig.add_subplot(5, 4, i + 1, xticks=[], yticks=[])
        im = Image.open(f"data/train_images/{row['Image_Label'].split('_')[0]}")
        plt.imshow(im)
        mask_rle = row['EncodedPixels']
        if isinstance(mask_rle, str):
            mask = rle_decode(mask_rle)
        else:
            mask = np.zeros((1400, 2100))
        plt.imshow(mask, alpha=0.5, cmap='gray')
        ax.set_title(f"Image: {row['Image_Label'].split('_')[0]}. Label: {row['label']}")
custom_show_batch(data)<jupyter_output><empty_output><jupyter_text>We can compare these to the original images and masks to make sure everything is working:<jupyter_code>plotImage('0011165.jpg')
plotImage('002be4f.jpg')<jupyter_output><empty_output><jupyter_text>We are going to use two metrics to keep an eye on our model's performance during training:  - `multiclass_dice` - `multiclass_dice_threshold` with a threshold value of `0.5`<jupyter_code>dice_50 = partial(multiclass_dice_threshold, threshold=0.50)
learn = unet_learner(data, models.xresnet18, pretrained=False, metrics=[multiclass_dice, dice_50], loss_func=BCEWithLogitsFlat(), model_dir=path).mixup(stack_y=False)
lr_find(learn)
learn.recorder.plot()
lr=3e-3
learn.fit_one_cycle(3, lr)
learn.unfreeze()
learn.fit_one_cycle(10, lr)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12653618570778333, 'fraction_numerical': 0.01926869974207252, 'mean_word_length': 5.183864915572233, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 29, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '297885', 'license': 'MIT', 'n_tokens_mistral': 2371, 'n_tokens_neox': 2264, 'n_words': 557, 'path': '01_BasicModel.ipynb', 'repo_name': 'JoshVarty/KaggleClouds', 'size': '6591'}