{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":74631,"databundleVersionId":8138903,"sourceType":"competition"}],"dockerImageVersionId":30674,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"from torch.utils.data import Dataset, DataLoader\nimport torch.nn.functional as F\nimport torch.nn as nn\nimport torch\n\nfrom torchvision.models import feature_extraction\nimport torchvision\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score\nfrom tqdm import tqdm\nfrom PIL import Image\nimport pandas as pd\nimport numpy as np\nimport os","metadata":{"execution":{"iopub.status.busy":"2024-04-08T16:21:23.756318Z","iopub.execute_input":"2024-04-08T16:21:23.756627Z","iopub.status.idle":"2024-04-08T16:21:32.441254Z","shell.execute_reply.started":"2024-04-08T16:21:23.756602Z","shell.execute_reply":"2024-04-08T16:21:32.440282Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# set seeds for reproducibility\nSEED = 123\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\ntorch.cuda.manual_seed(SEED)\ntorch.cuda.manual_seed_all(SEED)","metadata":{"execution":{"iopub.status.busy":"2024-04-08T16:21:43.592986Z","iopub.execute_input":"2024-04-08T16:21:43.593520Z","iopub.status.idle":"2024-04-08T16:21:43.601909Z","shell.execute_reply.started":"2024-04-08T16:21:43.593492Z","shell.execute_reply":"2024-04-08T16:21:43.601165Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# set hyperparameters\nWORKDIR = \"/kaggle/input/copy-of-simpsons-faces-but-3-app\"\nVAL_SIZE = 0.2\nBATCH_SIZE = 128\nN_EPOCHS = 30\nLEARNING_RATE = 1e-4\nDEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\nBEST_MODEL_FILEPATH = 'best_model.pt'\n\n# feature extractor params\nWEIGHTS = torchvision.models.ViT_L_32_Weights.DEFAULT\nFEAT_EXTRACTOR = torchvision.models.vit_l_32\nFEAT_LAYER = 'getitem_5'\nFEAT_DIMS = 1024","metadata":{"execution":{"iopub.status.busy":"2024-04-08T16:21:44.596868Z","iopub.execute_input":"2024-04-08T16:21:44.597230Z","iopub.status.idle":"2024-04-08T16:21:44.627410Z","shell.execute_reply.started":"2024-04-08T16:21:44.597200Z","shell.execute_reply":"2024-04-08T16:21:44.626436Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# set filepaths of train and test csv files\ntrain_csv_filepath = os.path.join(WORKDIR, 'train.csv')\ntest_csv_filepath = os.path.join(WORKDIR, 'test.csv')\n\n# read train and test csv's as dataframe\ndf_train = pd.read_csv(train_csv_filepath)\ndf_test = pd.read_csv(test_csv_filepath)","metadata":{"execution":{"iopub.status.busy":"2024-04-08T16:21:45.699045Z","iopub.execute_input":"2024-04-08T16:21:45.699972Z","iopub.status.idle":"2024-04-08T16:21:45.736257Z","shell.execute_reply.started":"2024-04-08T16:21:45.699933Z","shell.execute_reply":"2024-04-08T16:21:45.735424Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# dict for class names and their corresponding IDs in the dataset\nid2name = {df_train.iloc[i]['classId']:df_train.iloc[i]['className'] for i in range(len(df_train))}\nn_class = len(id2name)\nprint(f\"Number of classes in the dataset = {n_class}\")\nprint(id2name)","metadata":{"execution":{"iopub.status.busy":"2024-04-08T16:21:46.112017Z","iopub.execute_input":"2024-04-08T16:21:46.112858Z","iopub.status.idle":"2024-04-08T16:21:46.722734Z","shell.execute_reply.started":"2024-04-08T16:21:46.112826Z","shell.execute_reply":"2024-04-08T16:21:46.721797Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"x_trainval = df_train['path'].values\ny_trainval = df_train['classId'].values\nx_train, x_val, y_train, y_val = train_test_split(x_trainval, y_trainval, test_size=VAL_SIZE, random_state=SEED)\n\nx_test = df_test['path'].values\n\nprint(x_train.shape, y_train.shape)\nprint(x_val.shape, y_val.shape)\nprint(x_test.shape)","metadata":{"execution":{"iopub.status.busy":"2024-04-08T16:21:46.724294Z","iopub.execute_input":"2024-04-08T16:21:46.724602Z","iopub.status.idle":"2024-04-08T16:21:46.735343Z","shell.execute_reply.started":"2024-04-08T16:21:46.724576Z","shell.execute_reply":"2024-04-08T16:21:46.734330Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"x_train = [os.path.join(WORKDIR, 'simpsons', 'train', fn) for fn in x_train]\nx_val = [os.path.join(WORKDIR, 'simpsons', 'train', fn) for fn in x_val]\nx_test = [os.path.join(WORKDIR, 'simpsons', 'test', fn) for fn in x_test]","metadata":{"execution":{"iopub.status.busy":"2024-04-08T16:21:47.105674Z","iopub.execute_input":"2024-04-08T16:21:47.106521Z","iopub.status.idle":"2024-04-08T16:21:47.130989Z","shell.execute_reply.started":"2024-04-08T16:21:47.106488Z","shell.execute_reply":"2024-04-08T16:21:47.129996Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# initialize the feature extractor and set it to evaluation mode\nfeat_extractor = feature_extraction.create_feature_extractor(\n    FEAT_EXTRACTOR(weights=WEIGHTS),\n    [FEAT_LAYER],\n).to(DEVICE)\nfeat_extractor.eval()\n\n# initialize corresponding image transformations (at preprocessing step)\ntransforms = WEIGHTS.transforms()","metadata":{"execution":{"iopub.status.busy":"2024-04-08T16:21:47.643829Z","iopub.execute_input":"2024-04-08T16:21:47.644761Z","iopub.status.idle":"2024-04-08T16:22:01.765016Z","shell.execute_reply.started":"2024-04-08T16:21:47.644727Z","shell.execute_reply":"2024-04-08T16:22:01.764081Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class SimpsonsFacesDataset(Dataset):\n    def __init__(self, filepaths, labels, feat_extractor, transforms):\n        super().__init__()\n        \n        self.filepaths = filepaths\n        self.labels = labels\n        \n        self.feat_extractor = feat_extractor\n        self.transforms = transforms\n        \n    def load_image(self, fp):\n        return Image.open(fp).convert(\"RGB\")\n    \n    def extract_feats(self, img):\n        # preprocess the image\n        x = self.transforms(img).unsqueeze(0).to(DEVICE)\n        with torch.inference_mode():\n            feats = self.feat_extractor(x)[FEAT_LAYER].view(-1)\n        return feats\n        \n    def __len__(self):\n        return len(self.filepaths)\n    \n    def __getitem__(self, idx):\n        filepath = self.filepaths[idx]\n        img = self.load_image(filepath)\n        feats = self.extract_feats(img)\n        label = torch.tensor(self.labels[idx], dtype=torch.long)\n        return feats, label","metadata":{"execution":{"iopub.status.busy":"2024-04-08T16:22:22.349490Z","iopub.execute_input":"2024-04-08T16:22:22.349863Z","iopub.status.idle":"2024-04-08T16:22:22.358696Z","shell.execute_reply.started":"2024-04-08T16:22:22.349834Z","shell.execute_reply":"2024-04-08T16:22:22.357727Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# initialize tran and val datasets\ntrain_dset = SimpsonsFacesDataset(x_train, y_train, feat_extractor, transforms)\nval_dset = SimpsonsFacesDataset(x_val, y_val, feat_extractor, transforms)\n\n# initialize tran and val iterators\ntrain_iterator = DataLoader(train_dset, batch_size=BATCH_SIZE, shuffle=True, drop_last=True)\nval_iterator = DataLoader(val_dset, batch_size=BATCH_SIZE, drop_last=True)","metadata":{"execution":{"iopub.status.busy":"2024-04-08T16:22:22.591876Z","iopub.execute_input":"2024-04-08T16:22:22.592244Z","iopub.status.idle":"2024-04-08T16:22:22.597757Z","shell.execute_reply.started":"2024-04-08T16:22:22.592213Z","shell.execute_reply":"2024-04-08T16:22:22.596860Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class SimpsonsImageClassifier(nn.Module):\n    def __init__(self, input_size, output_size):\n        super().__init__()\n        \n        self.net = nn.Sequential(\n            nn.Linear(input_size, 4*input_size),\n            nn.BatchNorm1d(4*input_size),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n            nn.Linear(4*input_size, output_size),\n        )\n        \n    def forward(self, x):\n        logits = self.net(x)\n        return logits","metadata":{"execution":{"iopub.status.busy":"2024-04-08T16:22:22.790529Z","iopub.execute_input":"2024-04-08T16:22:22.791225Z","iopub.status.idle":"2024-04-08T16:22:22.797245Z","shell.execute_reply.started":"2024-04-08T16:22:22.791195Z","shell.execute_reply":"2024-04-08T16:22:22.796239Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def evaluate(model, iterator):\n    \n    global DEVICE\n    \n    model.eval()\n    \n    loss_sum = 0.0\n    y_preds, y_gts = [], []    \n    \n    with torch.inference_mode():\n        \n        for iter_idx, (x, y) in tqdm(enumerate(iterator), total=len(iterator)):\n            x, y = x.to(DEVICE), y.to(DEVICE)\n            logits = model(x)\n            \n            preds = torch.argmax(logits, dim=1)\n            y_preds.append(preds.cpu())\n            y_gts.append(y.cpu())\n        \n            loss = F.cross_entropy(logits, y)\n            loss_sum += loss.item()\n    \n        y_preds, y_gts = torch.hstack(y_preds).numpy(), torch.hstack(y_gts).numpy()\n        acc = accuracy_score(y_gts, y_preds)\n        avg_loss = loss_sum / len(iterator)\n    return avg_loss, acc","metadata":{"execution":{"iopub.status.busy":"2024-04-08T16:22:23.029663Z","iopub.execute_input":"2024-04-08T16:22:23.030097Z","iopub.status.idle":"2024-04-08T16:22:23.041441Z","shell.execute_reply.started":"2024-04-08T16:22:23.030056Z","shell.execute_reply":"2024-04-08T16:22:23.040370Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# initialize classifier\nmodel = SimpsonsImageClassifier(input_size=FEAT_DIMS, output_size=n_class).to(DEVICE)\n# initialize optimizer\noptimizer = torch.optim.AdamW(model.parameters(), lr=LEARNING_RATE)\n\nbest_val_loss = float('inf')\nbest_streak = 0\n\nfor epoch_idx in range(1, N_EPOCHS + 1):\n    model.train()\n    train_loss_sum = 0.0\n    for iter_idx, (x, y) in tqdm(enumerate(train_iterator), total=len(train_iterator)):\n        x, y = x.to(DEVICE), y.to(DEVICE)\n        logits = model(x)\n        loss = F.cross_entropy(logits, y)\n        \n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n        \n        train_loss_sum += loss.item()\n        \n    avg_train_loss = train_loss_sum / len(train_iterator)\n    avg_val_loss, val_acc = evaluate(model, val_iterator)\n    print(f\"Epoch {epoch_idx} | train loss = {avg_train_loss:.3f}\\tval loss = {avg_val_loss:.3f}\\tval acc = {val_acc:.3f}\")\n    \n    if avg_val_loss < best_val_loss:\n        torch.save(model.state_dict(), BEST_MODEL_FILEPATH)\n        best_val_loss = avg_val_loss\n        best_streak = 0\n        print(f'The best model is found and saved. Current best validation loss = {best_val_loss:.3f}')\n    else:\n        best_streak += 1\n        \n    if best_streak == 5:\n        print('Best model has not been found in the last 5 epochs. Early stopping...')\n        break","metadata":{"scrolled":true,"execution":{"iopub.status.busy":"2024-04-08T16:22:23.443214Z","iopub.execute_input":"2024-04-08T16:22:23.444127Z","iopub.status.idle":"2024-04-08T17:06:11.604803Z","shell.execute_reply.started":"2024-04-08T16:22:23.444095Z","shell.execute_reply":"2024-04-08T17:06:11.603826Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# reinitialize the model\nbest_model = SimpsonsImageClassifier(input_size=FEAT_DIMS, output_size=n_class)\n# load best model's parameters\nbest_model.load_state_dict(torch.load(BEST_MODEL_FILEPATH))\nbest_model = best_model.to(DEVICE)","metadata":{"execution":{"iopub.status.busy":"2024-04-08T17:06:26.432250Z","iopub.execute_input":"2024-04-08T17:06:26.433065Z","iopub.status.idle":"2024-04-08T17:06:26.496265Z","shell.execute_reply.started":"2024-04-08T17:06:26.433032Z","shell.execute_reply":"2024-04-08T17:06:26.495390Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def predict(img, feat_extractor, model, transforms):\n    \n    global DEVICE\n    \n    model.eval()\n    \n    # preprocess the image\n    x = transforms(img).unsqueeze(0).to(DEVICE)\n    \n    with torch.inference_mode():\n        feats = feat_extractor(x)[FEAT_LAYER]\n        logits = model(feats)\n        probs = F.softmax(logits, dim=1)\n        pred = torch.argmax(probs).item()\n\n    return pred","metadata":{"execution":{"iopub.status.busy":"2024-04-08T17:11:08.812303Z","iopub.execute_input":"2024-04-08T17:11:08.813171Z","iopub.status.idle":"2024-04-08T17:11:08.820684Z","shell.execute_reply.started":"2024-04-08T17:11:08.813126Z","shell.execute_reply":"2024-04-08T17:11:08.819742Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"path_list, classId_list = [], []\n\nfor fp_test in tqdm(x_test):\n    img_test = Image.open(fp_test).convert(\"RGB\")\n    pred = predict(img_test, feat_extractor, best_model, transforms)\n    # append\n    path_list.append(os.path.basename(fp_test))    \n    classId_list.append(pred)","metadata":{"execution":{"iopub.status.busy":"2024-04-08T17:11:10.129643Z","iopub.execute_input":"2024-04-08T17:11:10.129997Z","iopub.status.idle":"2024-04-08T17:11:21.477045Z","shell.execute_reply.started":"2024-04-08T17:11:10.129969Z","shell.execute_reply":"2024-04-08T17:11:21.476111Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"columns = ['path', 'classId']\ndf_submission = pd.DataFrame(\n    {\n        'path': path_list,\n        'classId': classId_list,\n    }\n)\ndf_submission.to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2024-04-08T17:11:27.982566Z","iopub.execute_input":"2024-04-08T17:11:27.983260Z","iopub.status.idle":"2024-04-08T17:11:27.990801Z","shell.execute_reply.started":"2024-04-08T17:11:27.983231Z","shell.execute_reply":"2024-04-08T17:11:27.989959Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}