{"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":"Many thanks to @nayuts for sharing his hard work with the community. All credit for this work goes to below notebooks and @nayuts. Kindly upvote and appreciate the original work.\n\n1. https://www.kaggle.com/nayuts/256-x-256-cropped-images\n2. https://www.kaggle.com/nayuts/efficientnet-with-undersampling\n\nI have increased the number of epochs and tuned the model optimizer paramaters to get slightly better results.","metadata":{}},{"cell_type":"markdown","source":"<img src=\"https://raw.githubusercontent.com/tasotasoso/kaggle_media/main/iwildcam2021/model_image.png\" width=\"***300***\">","metadata":{}},{"cell_type":"code","source":"import sys\nsys.path.append('../input/pytorch-image-models/pytorch-image-models-master')\n\nimport collections\nimport gc\nimport json\nimport os\nimport random\nimport time\nimport warnings\nwarnings.simplefilter(\"ignore\")\n\nfrom albumentations import *\nfrom albumentations.pytorch import ToTensor\nimport cv2\nfrom imblearn.under_sampling import RandomUnderSampler\nfrom matplotlib import pyplot as plt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image, ImageFilter\nfrom sklearn.model_selection import KFold\nfrom sklearn.model_selection import train_test_split\nimport tifffile as tiff\nimport timm\nimport torch\nimport torch.backends.cudnn as cudnn\nimport torch.nn as nn\nfrom torch.nn import functional as F\nimport torch.optim as optim\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\nfrom torch.utils.data import DataLoader, Dataset, sampler\nfrom tqdm import tqdm_notebook as tqdm\n\n%matplotlib inline","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### setting","metadata":{}},{"cell_type":"code","source":"!ls ../input/256-x-256-cropped-images","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"DATASET = \"../input/iwildcam2021-fgvc8\"\nCROPED_DATA = \"../input/256-x-256-cropped-images/\"\n\nTRAIN_CROPED_DATA = \"croped_images_train/\"\nTEST_CROPED_DATA = \"croped_images_test/\"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"BATCH_SIZE = 32\nDEVICE = ('cuda' if torch.cuda.is_available() else 'cpu')\nEPOCHS = 5000\nNUM_WORKERS = 4\nSEED = 2021","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def set_seed(seed=2**3):\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    np.random.seed(seed)\n    random.seed(seed)\n    torch.backends.cudnn.deterministic = True\nset_seed(SEED)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_croped_img_ids_train = pd.read_csv(CROPED_DATA + \"croped_train.csv\")\ndf_croped_img_ids_test = pd.read_csv(CROPED_DATA + \"croped_test.csv\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_croped_img_ids_train.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_croped_img_ids_test.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### create train dataframe","metadata":{}},{"cell_type":"code","source":"with open('../input/iwildcam2021-fgvc8/metadata/iwildcam2021_train_annotations.json', encoding='utf-8') as json_file:\n    train_annotations =json.load(json_file)\ndf_train_annotation = pd.DataFrame(train_annotations[\"annotations\"])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = df_croped_img_ids_train[[\"id\", \"idx\"]].merge(df_train_annotation[[\"image_id\", \"category_id\"]], \n                                      left_on='id', right_on='image_id')[[\"id\", \"idx\", \"category_id\"]]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_categories = pd.DataFrame(train_annotations[\"categories\"])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cat_idxs = df_categories[\"id\"]\n\ndef convert_cat_to_index(x):\n    return np.where(cat_idxs==x)[0][0]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train[\"category_id\"] = train[\"category_id\"].map(lambda x: convert_cat_to_index(x))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### unzip croped data","metadata":{}},{"cell_type":"code","source":"! unzip ../input/256-x-256-cropped-images/croped_images_train.zip ","metadata":{"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"! unzip ../input/256-x-256-cropped-images/croped_images_test.zip","metadata":{"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train","metadata":{}},{"cell_type":"markdown","source":"## Create dataset for training","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# Dataset for train\n# ====================================================\n\nmean = np.array([0.37087523, 0.370876, 0.3708759] )\nstd = np.array([0.21022698, 0.21022713, 0.21022706])\n\ndef img2tensor(img,dtype:np.dtype=np.float32):\n    if img.ndim==2 : img = np.expand_dims(img,2)\n    img = np.transpose(img,(2,0,1))\n    return torch.from_numpy(img.astype(dtype, copy=False))\n\nclass IWildcamTrainDataset(Dataset):\n    def __init__(self, df, tfms=None):\n        self.ids = df[\"id\"]\n        self.idxs = df[\"idx\"]\n        self.categories = df[\"category_id\"]\n        self.tfms = tfms\n        \n    def __len__(self):\n        return len(self.ids)\n    \n    def __getitem__(self, idx):\n        size = (256, 256)\n        image_id = self.ids[idx]\n        image_idx = self.idxs[idx]\n        iamge_categorie = self.categories[idx]\n        \n        image_path = TRAIN_CROPED_DATA + f\"{image_id}_{image_idx}.jpg\"\n        img = cv2.resize(cv2.cvtColor(cv2.imread(image_path), cv2.COLOR_BGR2RGB),size)\n\n        if self.tfms is not None:\n            augmented = self.tfms(image=img)\n            img = augmented['image']\n            \n        # we should normalize here\n        return img2tensor((img/255.0  - mean)/std), torch.tensor(iamge_categorie)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_aug(p=1.0):\n    return Compose([\n        HorizontalFlip(),\n        ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.2, rotate_limit=15, p=0.9, \n                         border_mode=cv2.BORDER_REFLECT),\n        VerticalFlip(),\n        RandomBrightnessContrast(p=0.9),\n    ], p=p)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Create model","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# EfficientNet Model\n# ====================================================\n\nclass enet_v2(nn.Module):\n\n    def __init__(self, backbone, out_dim, pretrained=False):\n        super(enet_v2, self).__init__()\n        self.enet = timm.create_model(backbone, pretrained=pretrained)\n        in_ch = self.enet.classifier.in_features\n        self.myfc = nn.Linear(in_ch, out_dim)\n        self.enet.classifier = nn.Identity()\n\n    def forward(self, x):\n        x = self.enet(x)\n        x = self.myfc(x)\n        return x","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = enet_v2(backbone=\"tf_efficientnet_b0\", out_dim=205)\nmodel.to(DEVICE)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## train setting","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# Optimizer and Loss\n# ====================================================\n\noptimizer = torch.optim.SGD(model.parameters(), lr=0.0001, momentum=0.9)\n#optimizer = torch.optim.SGD([\n#                {'params': model.parameters()},\n#               {'params': model.classifier.parameters(), 'lr': 1e-4}\n#            ], lr=1e-3, momentum=0.9)\ncriterion = nn.CrossEntropyLoss()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Train\n\nSince we know that [the training data is imbalanced](https://www.kaggle.com/nayuts/iwildcam-2021-overviewing-for-start#EDA), I undersampled it.","metadata":{}},{"cell_type":"code","source":"rus = RandomUnderSampler(random_state=SEED, replacement=True)\n\ndef generate_dataloders(train):\n    \n    train_resampled, _ = rus.fit_resample(train, train[\"category_id\"])\n    test_resampled, _ = rus.fit_resample(train, train[\"category_id\"])\n\n    train_resampled = train_resampled.reset_index(drop=True)\n    test_resampled = test_resampled.reset_index(drop=True)\n    \n    ds_train = IWildcamTrainDataset(train_resampled, tfms=get_aug())\n    dl_train = DataLoader(ds_train,batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS)\n    ds_test = IWildcamTrainDataset(test_resampled)\n    dl_test = DataLoader(ds_test,batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS)\n    \n    return dl_train, dl_test","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ====================================================\n# Train\n# ====================================================\n\nfor epoch in tqdm(range(EPOCHS)):\n    \n    dl_train, dl_test = generate_dataloders(train)\n    \n    ###Train\n    model.train()\n    train_loss = 0\n    \n    for data in dl_train:\n        optimizer.zero_grad()\n        imgs, categories = data\n        imgs = imgs.to(DEVICE)\n        categories = categories.to(DEVICE)\n        \n        outputs = model(imgs)\n    \n        loss = criterion(outputs, categories)\n        loss.backward()\n        optimizer.step()\n            \n        train_loss += loss.item()\n    train_loss /= len(dl_train)\n        \n    print(f\"EPOCH: {epoch + 1}, train_loss: {train_loss}\")\n        \n    ###Validation\n    model.eval()\n    valid_loss = 0\n        \n    for data in dl_test:\n        imgs, categories = data\n        imgs = imgs.to(DEVICE)\n        categories = categories.to(DEVICE)\n        \n        outputs = model(imgs)\n    \n        loss = criterion(outputs, categories)\n        \n        valid_loss += loss.item()\n    valid_loss /= len(dl_test)\n        \n    print(f\"EPOCH: {epoch + 1}, valid_loss: {valid_loss}\")\n        \n    \n    if (epoch+1)%50 == 0 or (epoch+1)%EPOCHS == 0:\n        ###Save model\n        torch.save(model.state_dict(), f\"{epoch+1}_.pth\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Inference","metadata":{}},{"cell_type":"markdown","source":"## Create dataset for test","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# Dataset for test\n# ====================================================\n\nmean = np.array([0.37087523, 0.370876, 0.3708759] )\nstd = np.array([0.21022698, 0.21022713, 0.21022706])\n\nclass IWildcamTestDataset(Dataset):\n    def __init__(self, df, tfms=None):\n        self.ids = df[\"id\"]\n        self.idx = df[\"idx\"]\n        self.tfms = tfms\n        \n    def __len__(self):\n        return len(self.ids)\n    \n    def __getitem__(self, idx):\n        size = (256, 256)\n        image_id = self.ids[idx]\n        image_idx = self.idx[idx]\n        \n        image_path = TEST_CROPED_DATA + f\"{image_id}_{image_idx}.jpg\"\n        \n        img = cv2.resize(cv2.cvtColor(cv2.imread(image_path), cv2.COLOR_BGR2RGB),size)\n\n        if self.tfms is not None:\n            augmented = self.tfms(image=img)\n            img = augmented['image']\n            \n        # we should normalize here\n        return img2tensor((img/255.0 - mean)/std), image_id","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ds_test = IWildcamTestDataset(df_croped_img_ids_test)\ndl_test = DataLoader(ds_test,batch_size=32,shuffle=False,num_workers=NUM_WORKERS)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Load trained model","metadata":{}},{"cell_type":"code","source":"model = enet_v2(backbone=\"tf_efficientnet_b0\", out_dim=205)\nmodel.to(DEVICE)\nmodel.load_state_dict(torch.load(f\"{epoch+1}_.pth\"))\nmodel.eval()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pred_categories = []\npred_img_ids = []","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## inference","metadata":{}},{"cell_type":"code","source":"with torch.no_grad():\n    for imgs, img_ids in tqdm(dl_test):\n        imgs = imgs.to(DEVICE)\n        \n        outputs = model(imgs)\n        output_labels = torch.argmax(outputs, dim=1).tolist()\n        pred_categories += output_labels\n        pred_img_ids += img_ids","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pred = collections.defaultdict(list)\nfor category, img_id in zip(pred_categories, pred_img_ids):\n    pred[img_id].append(category)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pred","metadata":{"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Create submit file","metadata":{}},{"cell_type":"code","source":"sub = pd.read_csv(\"../input/iwildcam2021-fgvc8/sample_submission.csv\")\ncol_Predicted = [col for col in sub.columns if \"Predicted\" in col]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"with open('../input/iwildcam2021-fgvc8/metadata/iwildcam2021_train_annotations.json', encoding='utf-8') as json_file:\n    train_annotations =json.load(json_file)\ndf_categories = pd.DataFrame.from_records(train_annotations[\"categories\"])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"For each image, count the number of each animal species and store them in the corresponding column.","metadata":{}},{"cell_type":"code","source":"results = []\n\nfor key in pred.keys():\n    c = collections.Counter(pred[key])\n    \n    res = []\n    cnts = [ 0 for i in range(205)]\n    for category, cnt in c.items():\n        cnts[category] = cnt\n    res += [key] + cnts[1:]\n    results.append(res)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Convert to pandas dataframe.","metadata":{}},{"cell_type":"code","source":"sub_tmp = pd.DataFrame(results, columns=sub.columns)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub_tmp.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub_tmp.to_csv(\"./sub_tmp.csv\", index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Add seq_id information to the counted results. iwildcam2021_test_information.json contains the mapping between the id of the image and the id of the sequence.","metadata":{}},{"cell_type":"code","source":"with open('../input/iwildcam2021-fgvc8/metadata/iwildcam2021_test_information.json', encoding='utf-8') as json_file:\n    test_information =json.load(json_file)\n    \ndf_test_info = pd.DataFrame(test_information[\"images\"])[[\"id\", \"seq_id\"]]\ndf_test_info.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Take right join on the image id.","metadata":{}},{"cell_type":"code","source":"sub_tmp = sub_tmp.merge(df_test_info, left_on=\"Id\", right_on=\"id\", how=\"right\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub_tmp.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Since there are multiple lines for the same sequence ID. We should aggregate them to single line. In this case, we will choose the image with the highest number of animals shown and submit the animal species and the number of animals shown in that image.","metadata":{}},{"cell_type":"code","source":"sum_counts = []\nfor i in range(len(sub_tmp)):\n    sum_counts.append(sum(sub_tmp.iloc[i][col_Predicted]))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub_tmp[\"total\"] =  sum_counts\nsub_tmp = sub_tmp.sort_values('total', ascending=False)\nsub_tmp = sub_tmp[~sub_tmp.duplicated(keep='first', subset='seq_id')].fillna(\"0\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub_tmp","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I'll match the result to the sample submission format. I was told that the order of the rows is not related to the score, but we will match it just in case.","metadata":{}},{"cell_type":"code","source":"# Since it was difficult to join the pandas series, I intentionally created an extra column.\nsub = sub.reset_index()\nsub = sub[[\"index\", \"Id\"]].merge(sub_tmp, left_on=\"Id\", right_on=\"seq_id\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub = sub[[\"Id_x\"] + col_Predicted].rename(columns={\"Id_x\": \"Id\"})\nsub.to_csv(\"sub.csv\", index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#If we don't delete them, csv files are buried and cannot be retrieved.\n!rm -r croped_images_train\n!rm -r croped_images_test","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}