{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Data preparation","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport json","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\n# prints cell time\n\n# Train data\nwith open('../input/herbarium-2020-fgvc7/nybg2020/train/metadata.json', \"r\", encoding=\"ISO-8859-1\") as file:\n    train = json.load(file)\n\n# remove image_id, license and region_id columns because they are unnecessary\ntrain_img = pd.DataFrame(train['images']).drop(columns='license')\ntrain_ann = pd.DataFrame(train['annotations']).drop(columns=['image_id', 'region_id'])\n# final data frame\ntrain_df = train_img.merge(train_ann, on='id')\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df['category_id'].value_counts()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Target of Train set is `category_id` with **32093** different values","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"CATEGORY_CLASSES = 32093\n\n# set all labels by category_id\nfrom sklearn import preprocessing\n\nle = preprocessing.LabelEncoder()\nle.fit(train_df['category_id'])\ntrain_df['category_id_le'] = le.transform(train_df['category_id'])\nclass_map = dict(sorted(train_df[['category_id_le', 'category_id']].values.tolist()))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\n\n# Test data\nwith open('../input/herbarium-2020-fgvc7/nybg2020/test/metadata.json', \"r\", encoding=\"ISO-8859-1\") as file:\n    test = json.load(file)\n\ntest_df = pd.DataFrame(test['images']).drop(columns='license')\ntest_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Processing","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import os\nimport random\nimport gc\ngc.enable()\nimport time\n\n# image loading\nimport cv2\nfrom PIL import Image\n\n# f1 score\nfrom sklearn.metrics import f1_score\n\n# progress bar\nfrom tqdm import tqdm\n\n# main library with neraul network for our image processing\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import DataLoader, Dataset\nimport torchvision.models as models\n\n# image transformations\nfrom albumentations import Compose, Normalize, Resize\nfrom albumentations.pytorch import ToTensorV2\n\n# set PyTorch processing with gpu if available\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# set seed for same result\nSEED = 999\n\nrandom.seed(SEED)\nos.environ['PYTHONHASHSEED'] = str(SEED)\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\ntorch.cuda.manual_seed(SEED)\n# necessary option for same behaviour\ntorch.backends.cudnn.deterministic = True","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"For any image size larger than *128x128* get **Memory error** shown below, so trained only for 128x128. I don’t know whether computing on my own computer will change the situation, because I have the same amount of RAM and even less GPU memory size.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# image transformation function\nHEIGHT = 200\nWIDTH = 200\n\ndef get_transforms():\n    \n    # Compose - use multiple transformations\n    return Compose([\n            Resize(HEIGHT, WIDTH),\n            Normalize(\n                mean=[0.485, 0.456, 0.406],\n                std=[0.229, 0.224, 0.225],\n            ),\n            ToTensorV2(),\n        ])\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Train","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"Train set was already computed","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# Train set class\nclass TrainDataset(Dataset):\n    # Train data frame, category labels, transform function\n    def __init__(self, df, labels, transform=None):\n        self.df = df\n        self.labels = labels\n        self.transform = transform\n        \n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        # load image and convert it to neccessary for 'albumentations' format\n        file_name = self.df['file_name'].values[idx]\n        file_path = f'../input/herbarium-2020-fgvc7/nybg2020/train/{file_name}'\n        image = cv2.imread(file_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        label = self.labels.values[idx]\n        \n        # transform and normalize image\n        if self.transform:\n            augmented = self.transform(image=image)\n            image = augmented['image']\n        \n        return image, label","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# create 0 and 1 folds to then split for Train and Validation sets\nfrom sklearn.model_selection import StratifiedKFold\n\n# folds = train_df.sample(n=200000, random_state=0).reset_index(drop=True).copy()\nfolds = train_df.copy()\ntrain_labels = folds['category_id'].values\nkf = StratifiedKFold(n_splits=2)\nfor fold, (train_index, val_index) in enumerate(kf.split(folds.values, train_labels)):\n    folds.loc[val_index, 'fold'] = int(fold)\nfolds['fold'] = folds['fold'].astype(int)\nfolds.to_csv('folds.csv', index=None)\nfolds.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"folds.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"FOLD = 0\n# Train and Validation indices\ntrn_idx = folds[folds['fold'] != FOLD].index\nval_idx = folds[folds['fold'] == FOLD].index\n\n# Train and Validation data sets\ntrain_dataset = TrainDataset(folds.loc[trn_idx].reset_index(drop=True), \n                             folds.loc[trn_idx]['category_id'], \n                             transform=get_transforms())\nvalid_dataset = TrainDataset(folds.loc[val_idx].reset_index(drop=True), \n                             folds.loc[val_idx]['category_id'], \n                             transform=get_transforms())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"batch_size = 512\n\n# decorators for data sets for easy iteration (must implement __len__ and __getitem__)\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\nvalid_loader = DataLoader(valid_dataset, batch_size=batch_size, shuffle=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# image recognition with Deep Residual Learning\nmodel = models.resnet18(pretrained=True)\n# max pooling\nmodel.avgpool = nn.AdaptiveAvgPool2d(1)\n# 2 layers classifier\nmodel.fc = nn.Linear(model.fc.in_features, CATEGORY_CLASSES)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Train proccess\nfrom torch.optim import Adam\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\n\n# number of epochs\nn_epochs = 1\n# learning rate\nlr = 4e-4\n\nmodel.to(device)\n\n# stochastic optimization\noptimizer = Adam(model.parameters(), lr=lr, amsgrad=False)\n# optimize learning rate\nscheduler = ReduceLROnPlateau(optimizer, 'min', factor=0.75, patience=5, verbose=True, eps=1e-6)\n\n# cross entroy loss criterion\ncriterion = nn.CrossEntropyLoss()\n\nbest_score = 0.\nbest_loss = np.inf\n\nfor epoch in range(n_epochs):\n\n    start_time = time.time()\n\n    model.train()\n    avg_loss = 0.\n    \n    # set gradients of model parameters to zero\n    optimizer.zero_grad()\n    \n    # training\n    for i, (images, labels) in tqdm(enumerate(train_loader)):\n\n        # load to device\n        images = images.to(device)\n        labels = labels.to(device)\n        \n        # compute output\n        y_preds = model(images)\n        loss = criterion(y_preds, labels)\n        \n        # backward propogation\n        loss.backward()\n        # adjust weights\n        optimizer.step()\n        optimizer.zero_grad()\n\n        avg_loss += loss.item() / len(train_loader)\n        \n    # enable prediction mode\n    model.eval()\n    avg_val_loss = 0.\n    preds = np.zeros((len(valid_dataset)))\n    \n    # validating\n    for i, (images, labels) in enumerate(valid_loader):\n\n        images = images.to(device)\n        labels = labels.to(device)\n        \n        with torch.no_grad():\n            y_preds = model(images)\n\n        preds[i * batch_size: (i+1) * batch_size] = y_preds.argmax(1).to('cpu').numpy()\n\n        loss = criterion(y_preds, labels)\n        avg_val_loss += loss.item() / len(valid_loader)\n    \n    # optimize learning rate\n    scheduler.step(avg_val_loss)\n    \n    # epoch score\n    score = f1_score(folds.loc[val_idx]['category_id'].values, preds, average='macro')\n\n    elapsed = time.time() - start_time\n\n    print(f'Epoch {epoch+1} avg_train_loss: {avg_loss:.4f}  avg_val_loss: {avg_val_loss:.4f}  F1: {score:.6f}  time: {elapsed:.0f}s')\n\n    if score>best_score:\n        best_score = score\n        print(f'Epoch {epoch+1} save best score: {best_score:.6f} Model')\n        torch.save(model.state_dict(), f'fold_{FOLD}_best_score.pth')\n\n    if avg_val_loss<best_loss:\n        best_loss = avg_val_loss\n        print(f'Epoch {epoch+1} save best loss: {best_loss:.4f} Model')\n        torch.save(model.state_dict(), f'fold_{FOLD}_best_loss.pth')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Test","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# Test set class\nclass TestDataset(Dataset):\n    # Test data frame and transformation function\n    def __init__(self, df, transform=None):\n        self.df = df\n        self.transform = transform\n        \n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        # load image and convert it to albumentations format\n        file_name = self.df['file_name'].values[idx]\n        file_path = f'../input/herbarium-2020-fgvc7/nybg2020/test/{file_name}'\n        image = cv2.imread(file_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        # transform and normalize image\n        if self.transform:\n            augmented = self.transform(image=image)\n            image = augmented['image']\n        \n        return image","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Test data set creation with specified batch size\nBATCH_SIZE = 1024\n\ntest_dataset = TestDataset(test_df, transform=get_transforms())\ntest_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# the same model\nmodel = models.resnet18(pretrained=False)\nmodel.avgpool = nn.AdaptiveAvgPool2d(1)\nmodel.fc = nn.Linear(model.fc.in_features, CATEGORY_CLASSES)\n\n# Trained weights path\nweights_path = '../input/herbarium-2020-pytorch-resnet18-train/fold0_best_score.pth'\nmodel.load_state_dict(torch.load(weights_path))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# testing\nmodel.to(device)\n\npreds = np.zeros((len(test_dataset)))\nfor i, images in tqdm(enumerate(test_loader)):\n    images = images.to(device)\n    \n    with torch.no_grad():\n        y_preds = model(images)\n        \n    preds[i * BATCH_SIZE: (i+1) * BATCH_SIZE] = y_preds.argmax(1).to('cpu').numpy()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Submission for competition","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# take example submission\nsample_submission = pd.read_csv('../input/herbarium-2020-fgvc7/sample_submission.csv')\n# rewrite with own predictions\ntest_df['preds'] = preds.astype(int)\nsubmission = sample_submission.merge(test_df.rename(columns={'id': 'Id'})[['Id', 'preds']], on='Id').drop(columns='Predicted')\nsubmission['Predicted'] = submission['preds'].map(class_map)\nsubmission = submission.drop(columns='preds')\nsubmission.to_csv('submission.csv', index=False)\nsubmission.head()","execution_count":null,"outputs":[]}],"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":4,"nbformat_minor":4}