{"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":"# Whale/Dolphin Identification with CNNS and Pytorch_lightning","metadata":{}},{"cell_type":"markdown","source":"## Intro","metadata":{"execution":{"iopub.status.busy":"2022-02-05T01:58:19.779631Z","iopub.execute_input":"2022-02-05T01:58:19.780038Z","iopub.status.idle":"2022-02-05T01:58:19.804747Z","shell.execute_reply.started":"2022-02-05T01:58:19.779937Z","shell.execute_reply":"2022-02-05T01:58:19.803949Z"}}},{"cell_type":"markdown","source":"<p>\n    Although this competition asks us to identify individual animals, there are 2 other coarse-grained layers of classes:\n\n    Whale or Dolphin\n    Species\n</p>\n\n<p>\nIn this notebook, I will attempt to categorize images into Whale/Dolphin classes and hopefully find out more about the dataset\n    \nAugmentations are taken from this notebook: https://www.kaggle.com/kohjiahng/whaleeda-aug\n</p>","metadata":{}},{"cell_type":"markdown","source":"## Imports","metadata":{}},{"cell_type":"code","source":"!pip install https://github.com/ufoym/imbalanced-dataset-sampler/archive/master.zip","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-02-08T11:10:45.674506Z","iopub.execute_input":"2022-02-08T11:10:45.674996Z","iopub.status.idle":"2022-02-08T11:11:04.273214Z","shell.execute_reply.started":"2022-02-08T11:10:45.674869Z","shell.execute_reply":"2022-02-08T11:11:04.271992Z"},"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\n\nimport matplotlib.pyplot as plt\nplt.rcParams.update({'font.size': 15})\n\n# DL\nimport torch\nfrom torch import nn\nimport pytorch_lightning as pl\nfrom torchvision import transforms\n\nfrom torchsampler import ImbalancedDatasetSampler\n\n# Image reading\nimport cv2\n\n# Splitting data\nfrom sklearn.model_selection import train_test_split\n\n# Augmentations\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\n# Progress bar\nfrom tqdm import tqdm\n\nimport wandb\nfrom pytorch_lightning.loggers import WandbLogger","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:04.277874Z","iopub.execute_input":"2022-02-08T11:11:04.278157Z","iopub.status.idle":"2022-02-08T11:11:14.532800Z","shell.execute_reply.started":"2022-02-08T11:11:04.278118Z","shell.execute_reply":"2022-02-08T11:11:14.531323Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Global variables\nTRAIN_IMAGE_PATH = '../input/w-d-224x224-fast-dataset/train_images'\nTRAIN_CSV_PATH = '../input/w-d-224x224-fast-dataset/train.csv'","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:14.535024Z","iopub.execute_input":"2022-02-08T11:11:14.535410Z","iopub.status.idle":"2022-02-08T11:11:14.547835Z","shell.execute_reply.started":"2022-02-08T11:11:14.535363Z","shell.execute_reply":"2022-02-08T11:11:14.543725Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Weights and Biases","metadata":{}},{"cell_type":"code","source":"try:\n    from kaggle_secrets import UserSecretsClient\n    user_secrets = UserSecretsClient()\n    api_key = user_secrets.get_secret(\"wandb_api\")\n    wandb.login(key=api_key)\n    anony = None\nexcept:\n    anony = \"must\"\n    print('If you want to use your W&B account, go to Add-ons -> Secrets and provide your W&B access token. Use the Label name as wandb_api. \\nGet your W&B access token from here: https://wandb.ai/authorize')","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:14.549278Z","iopub.execute_input":"2022-02-08T11:11:14.549702Z","iopub.status.idle":"2022-02-08T11:11:16.282735Z","shell.execute_reply.started":"2022-02-08T11:11:14.549655Z","shell.execute_reply":"2022-02-08T11:11:16.281775Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Config","metadata":{}},{"cell_type":"code","source":"CONFIG = {\n    \"seed\": 2021,\n    \"epochs\": 10,\n    \"img_size\": 224,\n    \"model_name\": \"resnet50\",\n    \"batch_size\": 128,\n    'train_size': 0.8,\n    \"learning_rate\": 1e-5,\n    \"optimizer\": \"adam\",\n    \"device\": torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n}","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:16.286265Z","iopub.execute_input":"2022-02-08T11:11:16.286562Z","iopub.status.idle":"2022-02-08T11:11:16.341093Z","shell.execute_reply.started":"2022-02-08T11:11:16.286520Z","shell.execute_reply":"2022-02-08T11:11:16.339829Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Seeding","metadata":{}},{"cell_type":"code","source":"def set_seed(seed=42):\n    '''Sets the seed of the entire notebook so results are the same every time we run.\n    This is for REPRODUCIBILITY.'''\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    # When running on the CuDNN backend, two further options must be set\n#     torch.backends.cudnn.deterministic = True\n#     torch.backends.cudnn.benchmark = False\n    # Set a fixed value for the hash seed\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    \nset_seed(CONFIG['seed'])","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:16.342624Z","iopub.execute_input":"2022-02-08T11:11:16.344358Z","iopub.status.idle":"2022-02-08T11:11:16.356225Z","shell.execute_reply.started":"2022-02-08T11:11:16.344309Z","shell.execute_reply":"2022-02-08T11:11:16.355237Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Utility Functions","metadata":{}},{"cell_type":"code","source":"def read_image(path)->np.array: #reads image as array of shape (H,W,3)\n    arr = cv2.cvtColor(cv2.imread(path), cv2.COLOR_BGR2RGB) / 255\n    return arr","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:16.357848Z","iopub.execute_input":"2022-02-08T11:11:16.359797Z","iopub.status.idle":"2022-02-08T11:11:16.367065Z","shell.execute_reply.started":"2022-02-08T11:11:16.359766Z","shell.execute_reply":"2022-02-08T11:11:16.365985Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Data Loading and Cleaning","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(TRAIN_CSV_PATH)\ndf['path'] = TRAIN_IMAGE_PATH+'/'+df['image']","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:16.368858Z","iopub.execute_input":"2022-02-08T11:11:16.372335Z","iopub.status.idle":"2022-02-08T11:11:16.492904Z","shell.execute_reply.started":"2022-02-08T11:11:16.372292Z","shell.execute_reply":"2022-02-08T11:11:16.491801Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Fixing misspellings\ndf['species'] = df['species'].replace({\n    'kiler_whale': 'killer_whale',\n    'bottlenose_dolpin': 'bottlenose_dolphin',\n    'globis': 'short_finned_pilot_whale',\n    'pilot_whale': 'short_finned_pilot_whale'\n})","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:16.494436Z","iopub.execute_input":"2022-02-08T11:11:16.495365Z","iopub.status.idle":"2022-02-08T11:11:16.515944Z","shell.execute_reply.started":"2022-02-08T11:11:16.495324Z","shell.execute_reply":"2022-02-08T11:11:16.514839Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"WHALE_SPECIES = [\n    'blue_whale',\n    'brydes_whale',\n    'cuviers_beaked_whale',\n    'fin_whale',\n    'gray_whale',\n    'humpback_whale',\n    'killer_whale',\n    'long_finned_pilot_whale',\n    'melon_headed_whale',\n    'minke_whale',\n    'pygmy_killer_whale',\n    'sei_whale',\n    'short_finned_pilot_whale',\n    'southern_right_whale',\n    'beluga'\n]\nDOLPHIN_SPECIES = [\n    'false_killer_whale',\n    'bottlenose_dolphin',\n    'commersons_dolphin',\n    'common_dolphin',\n    'dusky_dolphin',\n    'frasiers_dolphin',\n    'pantropic_spotted_dolphin',\n    'rough_toothed_dolphin',\n    'spinner_dolphin',\n    'spotted_dolphin',\n    'white_sided_dolphin'\n]","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:16.518010Z","iopub.execute_input":"2022-02-08T11:11:16.518426Z","iopub.status.idle":"2022-02-08T11:11:16.525869Z","shell.execute_reply.started":"2022-02-08T11:11:16.518363Z","shell.execute_reply":"2022-02-08T11:11:16.524441Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def species2type(species):\n    if species in WHALE_SPECIES:\n        return 'whale'\n    elif species in DOLPHIN_SPECIES:\n        return 'dolphin'\n    else:\n        raise Exception(f'{species} not in whale or dolphin lists')\n\ndf['type'] = df['species'].apply(species2type)","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:16.528113Z","iopub.execute_input":"2022-02-08T11:11:16.529359Z","iopub.status.idle":"2022-02-08T11:11:16.560155Z","shell.execute_reply.started":"2022-02-08T11:11:16.529312Z","shell.execute_reply":"2022-02-08T11:11:16.559133Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## A bit of EDA","metadata":{}},{"cell_type":"code","source":"n_species_by_type = df.groupby('type')['species'].agg(lambda x: x.nunique()).loc[['whale', 'dolphin']]\nn_species_by_type.plot(kind='bar', figsize=(20,10))","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:16.563542Z","iopub.execute_input":"2022-02-08T11:11:16.563887Z","iopub.status.idle":"2022-02-08T11:11:16.854941Z","shell.execute_reply.started":"2022-02-08T11:11:16.563852Z","shell.execute_reply":"2022-02-08T11:11:16.853987Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"n_images_by_type = df['type'].value_counts().loc[['whale', 'dolphin']]\n\nanimal_types = df.groupby('individual_id')['type'].agg(lambda x: x.iloc[0])\nn_animals_by_type = animal_types.value_counts().loc[['whale', 'dolphin']]\n\ntype_numbers_df = pd.DataFrame({\n    'Image': n_images_by_type,\n    'Animal': n_animals_by_type,\n})\n\ntype_numbers_df.plot(kind='bar',figsize = (20, 10))\n\nplt.title('Number of images/animals of each type')\nplt.ylabel('Frequency')\nplt.xlabel('Type')","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:16.856343Z","iopub.execute_input":"2022-02-08T11:11:16.857442Z","iopub.status.idle":"2022-02-08T11:11:17.412121Z","shell.execute_reply.started":"2022-02-08T11:11:16.857379Z","shell.execute_reply":"2022-02-08T11:11:17.411124Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<p>\n    Whales are the majority, oversampling will be used later to balance the dataset\n</p>","metadata":{}},{"cell_type":"markdown","source":"## Dataset","metadata":{}},{"cell_type":"code","source":"data_transforms = {\n    'train': A.Compose([\n        A.Resize(CONFIG['img_size'], CONFIG['img_size']),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        A.HorizontalFlip(p=0.5),\n        A.RandomBrightness(0.5),\n        A.RandomFog(),\n        A.Rotate((0, 45)),\n        ToTensorV2()\n    ]),\n    'val': A.Compose([\n        A.Resize(CONFIG['img_size'], CONFIG['img_size']),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2()\n    ])\n}","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:17.416406Z","iopub.execute_input":"2022-02-08T11:11:17.416916Z","iopub.status.idle":"2022-02-08T11:11:17.432264Z","shell.execute_reply.started":"2022-02-08T11:11:17.416831Z","shell.execute_reply":"2022-02-08T11:11:17.431174Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class WhaleDataset(torch.utils.data.Dataset):\n    def __init__(self, df, transforms):\n        super().__init__()\n        self.df = df\n        self.transforms = transforms\n\n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        image = read_image(self.df['path'].iloc[idx])\n        \n        aug_image = self.transforms(image=image)['image']\n        \n        label = self.df['token_type'].iloc[idx]\n        return aug_image, label\n    \n    def get_labels(self): # For ImbalancedDatasetSampler\n        return self.df['token_type']","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:17.434020Z","iopub.execute_input":"2022-02-08T11:11:17.434660Z","iopub.status.idle":"2022-02-08T11:11:17.445019Z","shell.execute_reply.started":"2022-02-08T11:11:17.434604Z","shell.execute_reply":"2022-02-08T11:11:17.443883Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class WhaleDataModule(pl.LightningDataModule):\n    def __init__(self, df, batch_size):\n        super().__init__()\n        self.df = df.copy()\n        self.batch_size = batch_size\n        \n    def prepare_data(self):\n        self.df = self.df[['path', 'type']]\n        self.df['token_type'] = (self.df['type'] == 'whale').astype(int)\n        \n    def setup(self, stage):\n        self.train_df, self.val_df = train_test_split(self.df, train_size = CONFIG['train_size'], random_state=0)\n        \n        self.train_ds = WhaleDataset(self.train_df, data_transforms['train'])\n        self.val_ds = WhaleDataset(self.val_df, data_transforms['val'])\n    \n    def train_dataloader(self):\n        return torch.utils.data.DataLoader(self.train_ds, \n                                           batch_size=self.batch_size,\n                                           sampler = ImbalancedDatasetSampler(self.train_ds),\n                                           pin_memory=True)\n    def val_dataloader(self):\n        return torch.utils.data.DataLoader(self.val_ds, \n                                           batch_size=self.batch_size,\n                                          shuffle=False,\n                                          pin_memory=True)\nwhale_datamodule = WhaleDataModule(df, batch_size=CONFIG['batch_size'])","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:17.447378Z","iopub.execute_input":"2022-02-08T11:11:17.448531Z","iopub.status.idle":"2022-02-08T11:11:17.468960Z","shell.execute_reply.started":"2022-02-08T11:11:17.448487Z","shell.execute_reply":"2022-02-08T11:11:17.467819Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Finetuning ResNet","metadata":{}},{"cell_type":"code","source":"class ResNet(pl.LightningModule):\n    def __init__(self):\n        super().__init__()\n        self.net = torch.hub.load('pytorch/vision:v0.10.0', CONFIG['model_name'], pretrained=True)\n        self.net.fc = nn.Sequential(\n            nn.Linear(self.net.fc.in_features, 1),\n            nn.Sigmoid()\n        )\n        self.loss_fn = nn.BCELoss()\n        \n    def forward(self, X):\n        return self.net(X).squeeze(-1)\n    \n    def training_step(self, batch, batch_idx):\n        x, y = batch\n        y = y.float()\n        out = self.forward(x)\n        \n        loss = self.loss_fn(out, y)\n        acc = ((out.detach() > 0.5) == y).float().mean()\n        self.log('loss', {'train': loss}, logger=True)\n        self.log('acc', {'train': acc}, logger=True)\n\n        return loss\n    \n    def validation_step(self, batch, batch_idx):\n        x, y = batch\n        y = y.float()\n        \n        out = self.forward(x)\n        \n        loss = self.loss_fn(out, y)\n        acc = ((out > 0.5) == y).float().mean()\n        self.log('loss', {'val': loss}, logger=True)\n        self.log('acc',{'val': acc}, logger=True)\n        return loss\n    \n    def configure_optimizers(self):\n        optimizer = torch.optim.Adam(self.parameters(), lr = CONFIG['learning_rate'])\n        return optimizer","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:17.470929Z","iopub.execute_input":"2022-02-08T11:11:17.471718Z","iopub.status.idle":"2022-02-08T11:11:17.486146Z","shell.execute_reply.started":"2022-02-08T11:11:17.471672Z","shell.execute_reply":"2022-02-08T11:11:17.485208Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = ResNet()","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:17.488624Z","iopub.execute_input":"2022-02-08T11:11:17.489474Z","iopub.status.idle":"2022-02-08T11:11:25.570107Z","shell.execute_reply.started":"2022-02-08T11:11:17.489415Z","shell.execute_reply":"2022-02-08T11:11:25.569163Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"run = wandb.init(project=\"HappyWhale-WhaleorDolphin\",\n                 config = CONFIG,\n                 entity=\"jiahng\")","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:25.571721Z","iopub.execute_input":"2022-02-08T11:11:25.572379Z","iopub.status.idle":"2022-02-08T11:11:32.637241Z","shell.execute_reply.started":"2022-02-08T11:11:25.572292Z","shell.execute_reply":"2022-02-08T11:11:32.636278Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"wandb.watch(model, log_freq = 100)\nlogger = WandbLogger()\ntrainer = pl.Trainer(gpus=1,max_epochs=CONFIG['epochs'],profiler='simple',logger = logger, log_every_n_steps = 10)\ntrainer.fit(model, datamodule = whale_datamodule)","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:11:32.642773Z","iopub.execute_input":"2022-02-08T11:11:32.645284Z","iopub.status.idle":"2022-02-08T11:12:31.934622Z","shell.execute_reply.started":"2022-02-08T11:11:32.645235Z","shell.execute_reply":"2022-02-08T11:12:31.933413Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"torch.save(model.state_dict(), 'model.pth')\n\nfinal_model_artifact = wandb.Artifact('model', type='model')\nfinal_model_artifact.add_file('model.pth')\nrun.log_artifact(final_model_artifact)","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:12:31.936722Z","iopub.execute_input":"2022-02-08T11:12:31.937057Z","iopub.status.idle":"2022-02-08T11:12:33.307575Z","shell.execute_reply.started":"2022-02-08T11:12:31.937008Z","shell.execute_reply":"2022-02-08T11:12:33.306380Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"wandb.finish()","metadata":{"execution":{"iopub.status.busy":"2022-02-08T11:12:33.309153Z","iopub.execute_input":"2022-02-08T11:12:33.309813Z","iopub.status.idle":"2022-02-08T11:12:43.032037Z","shell.execute_reply.started":"2022-02-08T11:12:33.309767Z","shell.execute_reply":"2022-02-08T11:12:43.031142Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Results\n\n<p> 94% validated accuracy, 0.1557 validated BCE loss </p>\n<p> Train accuracy and loss are worse than validated accuracy and loss, more training might be done </p>\n<p> Full Logs and Final model: https://wandb.ai/jiahng/HappyWhale-WhaleorDolphin/runs/3p812n8p </p>","metadata":{}}]}