{"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":"# Pytorch using only 5 column","metadata":{}},{"cell_type":"markdown","source":"\ncopy and edit from [this notebook](https://www.kaggle.com/code/chrisqiu/0-681-pytorch-using-only-1-column)\n\nonly add input 4 columns(\"room_coor_x\", \"room_coor_y\", \"screen_coor_x\", \"screen_coor_y\" ) to original notebook\n\nIt's for training, submission notebook is [this notebook](https://www.kaggle.com/code/daeseung/pytorch-using-5-column-submission)\n","metadata":{}},{"cell_type":"markdown","source":"\n# Data","metadata":{}},{"cell_type":"code","source":"import pandas as pd \nimport numpy as np\nimport torch\n\ndf = pd.read_csv(\n    '/kaggle/input/predict-student-performance-from-game-play/train.csv',\n    usecols = ['elapsed_time', 'session_id', 'level_group', \"room_coor_x\", \"room_coor_y\", \"screen_coor_x\", \"screen_coor_y\" ],\n    low_memory = True\n)\ndf = df.fillna(-1)\n\n# seed everything\ntorch.manual_seed(101)\nnp.random.seed(101)","metadata":{"execution":{"iopub.status.busy":"2023-04-16T14:20:30.157649Z","iopub.execute_input":"2023-04-16T14:20:30.158744Z","iopub.status.idle":"2023-04-16T14:21:44.052953Z","shell.execute_reply.started":"2023-04-16T14:20:30.158661Z","shell.execute_reply":"2023-04-16T14:21:44.051881Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Build 'event_duration'","metadata":{}},{"cell_type":"markdown","source":"`Event_duration` is just the difference between two adjacent `elapsed_time`. Lots of folks have already done this. \n\nThe reason why I do this is because `elapsed_time` has big values and contains even bigger outliers. They are bad for training. The number of `event_duration` is much smaller than `elapsed_time` and we can remove the outliers by clipping the large values.","metadata":{}},{"cell_type":"code","source":"upper = 3.6e6 # 1 hour. events longer than 1 hour are outliers.\n\ngps = []\n\n# event_duration for every session of every group\n# this code is slow but I think it is necessary because it simulates the test condition\nfor _, session in df.groupby('session_id'):\n    for _, gp in session.groupby('level_group'):\n        event_duration = gp.elapsed_time.diff().fillna(0).clip(0, upper)\n        gp['event_duration'] = event_duration\n        gps.append(gp)\n        \ndf = pd.concat(gps)\nFEATURES = ['event_duration', \"room_coor_x\", \"room_coor_y\", \"screen_coor_x\", \"screen_coor_y\"]","metadata":{"execution":{"iopub.status.busy":"2023-04-16T14:21:44.059148Z","iopub.execute_input":"2023-04-16T14:21:44.059531Z","iopub.status.idle":"2023-04-16T14:24:08.956110Z","shell.execute_reply.started":"2023-04-16T14:21:44.059491Z","shell.execute_reply":"2023-04-16T14:24:08.955031Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"targets = pd.read_csv('/kaggle/input/predict-student-performance-from-game-play/train_labels.csv')\n\nspilt =  targets.session_id.str.split(\"_\", expand = True)\ntargets['session_id'] = spilt[0].astype(np.int64)\ntargets['q'] = spilt[1].str.slice(1).astype(int)","metadata":{"execution":{"iopub.status.busy":"2023-04-16T14:24:08.957758Z","iopub.execute_input":"2023-04-16T14:24:08.958146Z","iopub.status.idle":"2023-04-16T14:24:10.802679Z","shell.execute_reply.started":"2023-04-16T14:24:08.958106Z","shell.execute_reply":"2023-04-16T14:24:10.801656Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Trim\n\nPytorch requires the sequences inside a batch to have equal length, but different sessions have different lengths. That's why we have to trim the longer sequence and pad the shorter sequence. \n\nBut please notice that you don't have to do this. If you train a network with 1 batch_size, the conv1d can absolutely handle it. However, there are lots of drawbacks of using 1 sample per batch, such as long training time and bad gradient estimation.\n\nAnother thing to consider is how long per sequence. I choose 1000 for simplicity. You could also try 95% quantile of every level_group.","metadata":{}},{"cell_type":"code","source":"def trim(X, trim_steps):\n    data_steps = X.shape[0]\n    if data_steps == trim_steps: return X\n    \n    if data_steps < trim_steps:\n        shortage = trim_steps - data_steps\n        return np.pad(X, ((0,shortage),(0,0)),'constant') \n    \n    start = int(np.random.random() * (data_steps - trim_steps)) \n    return X[start:start+trim_steps]   ","metadata":{"execution":{"iopub.status.busy":"2023-04-16T14:24:10.806512Z","iopub.execute_input":"2023-04-16T14:24:10.806913Z","iopub.status.idle":"2023-04-16T14:24:10.814034Z","shell.execute_reply.started":"2023-04-16T14:24:10.806872Z","shell.execute_reply":"2023-04-16T14:24:10.812418Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Data Into Level Groups","metadata":{}},{"cell_type":"code","source":"GP1 = '0-4'\nGP2 = '5-12'\nGP3 = '13-22'\n\ndef group_session_id(df): \n    return np.stack([gp.correct.values for _,gp in df.groupby('session_id')])\n\nGROUP_DATA = {\n    GP1: {\n        'targets': group_session_id(targets[(targets.q >= 1) & (targets.q <= 3)]),\n        'pos_weight': 1/2\n    },\n    GP2: {\n        'targets': group_session_id(targets[(targets.q >= 4) & (targets.q <= 13)]),\n        'pos_weight': 1\n    },\n    GP3: {\n        'targets': group_session_id(targets[(targets.q >= 14) & (targets.q <= 18)]),\n        'pos_weight': 1/2   \n    }\n    \n}\n\nTIMESTEPS = 1000\n\nfor GP in [GP1, GP2, GP3]: \n    gdf = df[df.level_group == GP]\n    \n    curr = np.zeros(shape = (gdf.session_id.nunique(), TIMESTEPS, 5))\n    \n    for i, (session_id, sdf) in enumerate(gdf.groupby('session_id')):\n        curr[i] = trim(sdf[FEATURES].values, TIMESTEPS)[None, ...]\n    \n    GROUP_DATA[GP]['data'] = curr\n    \n    \nfor GP in GROUP_DATA.keys():\n    data, targets = GROUP_DATA[GP]['data'], GROUP_DATA[GP]['targets']\n    print(f'group {GP}')\n    print(f'data shape: {data.shape}, target shape: {targets.shape}')\n    print()","metadata":{"execution":{"iopub.status.busy":"2023-04-16T14:24:10.815866Z","iopub.execute_input":"2023-04-16T14:24:10.816260Z","iopub.status.idle":"2023-04-16T14:25:11.000402Z","shell.execute_reply.started":"2023-04-16T14:24:10.816225Z","shell.execute_reply":"2023-04-16T14:25:10.998322Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import gc \ndel df\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-04-16T14:25:11.001812Z","iopub.execute_input":"2023-04-16T14:25:11.002876Z","iopub.status.idle":"2023-04-16T14:25:11.884455Z","shell.execute_reply.started":"2023-04-16T14:25:11.002836Z","shell.execute_reply":"2023-04-16T14:25:11.883444Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model\n\nThe model contains an encoder and a classifier. \n\nAn encoder is the feature extractor. It aggregates the points of all time steps and produce a vector with n features (an embedding). The way of aggregation is to use 1d convolution. \n\nThe encoder can be shared across different level_group if you fix the kernel_size, channels, and other hyperparameters. For instance, if we are training on group 5-12, weights learned from group 0-4 can be reused.   \n\n![image.png](attachment:d6ca0079-9f1d-4333-9901-be34e89a3782.png)","metadata":{},"attachments":{"d6ca0079-9f1d-4333-9901-be34e89a3782.png":{"image/png":"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"}}},{"cell_type":"markdown","source":"Batches of embeddings are fit into a classifier. A classifier can be anything trainable, but usually an MLP is a good choice.\n\n![image.png](attachment:0a044029-2610-4763-9b7e-cd2365121b36.png)","metadata":{},"attachments":{"0a044029-2610-4763-9b7e-cd2365121b36.png":{"image/png":"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"}}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\n\nclass Encoder(nn.Module):\n    def __init__(self, nembed, nfeatures = 128):\n        \n        super(Encoder, self).__init__()\n        \n        self.nfeatures = nfeatures\n        \n        self.convs = nn.Sequential(\n            nn.Conv1d(5, 64,  kernel_size = 5),\n            nn.BatchNorm1d(64),\n            nn.LeakyReLU(inplace = True),\n            nn.MaxPool1d(2),\n            \n            nn.Conv1d(64, 128,  kernel_size = 5),\n            nn.BatchNorm1d(128),\n            nn.LeakyReLU(inplace = True),\n            nn.MaxPool1d(2),\n            \n            nn.Conv1d(128, self.nfeatures,  kernel_size = 3),\n            nn.BatchNorm1d(self.nfeatures),\n            nn.LeakyReLU(inplace = True),\n        ) \n        \n\n        self.fcs = nn.Sequential(\n                # 2* because in the pooling layer we take mean AND the std.\n                nn.Linear(2*self.nfeatures, nembed),\n            )\n\n    \n    def forward(self,x):        \n        x = torch.transpose(x, 1,2)\n        x = self.convs(x)        \n        std = torch.std(x, dim = 2)\n        mean = torch.mean(x, dim = 2)\n        x = torch.cat([std, mean], dim = 1)\n        x = self.fcs(x)\n        return x\n    \nclass Model(nn.Module):\n    def __init__(self,nout, nembed, nfeatures):\n        super(Model, self).__init__()\n        self.encoder = Encoder(nembed, nfeatures)\n        self.clf = nn.Sequential(\n            \n            nn.LeakyReLU(inplace = True),\n            nn.Dropout(0.2),\n            \n            nn.Linear(nembed, nembed // 2),\n            nn.LeakyReLU(inplace = True),\n            nn.Dropout(0.2),\n            \n            nn.Linear(nembed // 2, nout),\n\n        ) \n        \n        self.nout = nout\n        self.nembed = nembed\n        self.nfeatures = nfeatures\n                \n    \n    def forward(self, x):\n        x = self.encoder(x)\n        x = self.clf(x)\n        return x","metadata":{"execution":{"iopub.status.busy":"2023-04-16T14:25:11.886461Z","iopub.execute_input":"2023-04-16T14:25:11.887064Z","iopub.status.idle":"2023-04-16T14:25:11.901564Z","shell.execute_reply.started":"2023-04-16T14:25:11.887023Z","shell.execute_reply":"2023-04-16T14:25:11.900562Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model_params = {}\nmodel_params[GP1] = dict(\n    nout = 3,\n    nembed = 128,\n    nfeatures = 128,\n)\nmodel_params[GP2] = dict(\n    nout = 10,\n    nembed = 128,\n    nfeatures = 128,\n)\nmodel_params[GP3] = dict(\n    nout = 5,\n    nembed = 128,\n    nfeatures = 128,\n)\n\n# test model\nfor GP in model_params:    \n    model = Model(**model_params[GP])\n    print(f'group {GP}')\n    print(f'total params: {sum(p.numel() for p in model.parameters())}')\n    print(f'output shape: {model(torch.ones(32, 1000, 5)).shape}')\n    print()","metadata":{"execution":{"iopub.status.busy":"2023-04-16T14:25:11.903182Z","iopub.execute_input":"2023-04-16T14:25:11.903593Z","iopub.status.idle":"2023-04-16T14:25:12.373344Z","shell.execute_reply.started":"2023-04-16T14:25:11.903557Z","shell.execute_reply":"2023-04-16T14:25:12.372199Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Trainer","metadata":{}},{"cell_type":"code","source":"import torch.optim as optim\n\nfrom torch.utils.tensorboard import SummaryWriter\nfrom sklearn.metrics import f1_score\nfrom datetime import datetime\n\nDEVICE = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\n\nclass Trainer():\n    def __init__(self, \n                 model, \n                 save_path, \n                 pos_weight, \n                 num_labels,\n                 learning_rate\n                ):\n        \n        self.model = model\n        \n        self.optimizer = optim.NAdam(self.model.parameters(), lr = learning_rate) \n        \n        self.loss_fn = nn.BCEWithLogitsLoss(pos_weight = torch.ones(num_labels) * pos_weight).to(DEVICE)\n        \n        self.scheduler = optim.lr_scheduler.StepLR(self.optimizer, step_size = 10, gamma = 0.95)\n        \n        self.cos_scheduler = optim.lr_scheduler.CosineAnnealingWarmRestarts(\n            self.optimizer, T_0 = 4, eta_min = 1e-5, T_mult = 1)\n        \n        self.save_path = save_path\n        self.num_labels = num_labels\n        \n    \n    def train_network(self, train_loader, test_loader):        \n        EPOCHS = Config.EPOCHS\n        TEST_INTERVAL = Config.TEST_INTERVAL\n        \n        max_score = 0\n        losses = []\n        f1s = []\n        \n        prev_score = 0\n        not_improve = 0\n        \n        pbar = tqdm(range(1, EPOCHS+1))\n        for it in pbar:\n            lr = self.optimizer.param_groups[0]['lr']\n            loss = self.train_one_epoch(train_loader)\n\n            f1 = self.evaluate(test_loader)\n            f1_train = self.evaluate(train_loader)\n\n            if f1 > max_score:\n                max_score = f1\n                self.save_model(self.save_path)\n                \n            if f1 > prev_score:\n                not_improve = 0\n            else:\n                not_improve += 1\n                if not_improve > Config.EARLY_STOP:\n                    pbar.set_description(f'epoch: {it}, early stop; max_f1: {max_score}')\n                    break\n                \n\n            desc = f'epoch: {it}' + ' '\\\n                + f'lr: {lr:e}' + ' '\\\n                + f'avg_loss: {loss / len(train_loader) :e}'+' '\\\n                + f'avg_f1: {f1 :.4f}' + ' '\\\n                + f'max_f1: {max_score :.4f}' + ' '\\\n                + f'f1_train: {f1_train :.4f}'\n\n            pbar.set_description(desc)\n            self.cos_scheduler.step()\n\n\n            losses.append(loss / len(train_loader))\n            f1s.append(f1)\n            prev_score = f1\n\n        return {'bce': losses, 'f1': f1s}\n    \n    \n    def train_one_epoch(self, loader):\n        self.model.train()\n        self.model.to(DEVICE)\n\n        loss = 0\n        for X,y in loader:\n            X = X.to(DEVICE)\n            y = y.to(DEVICE)\n            \n            self.optimizer.zero_grad()\n            outp = self.model(X)\n            nloss = self.loss_fn(outp,y)\n            nloss.backward()\n            self.optimizer.step()\n\n            loss += nloss.detach().cpu().item()\n\n\n        return loss\n    \n    def predict(self, loader):\n        self.model.eval()\n        self.model.to(DEVICE)\n        \n        preds = []\n        labels = []\n        for X, y in loader:\n            X = X.to(DEVICE)            \n            with torch.no_grad():\n                outp = torch.sigmoid(self.model(X))\n                        \n            preds.append(outp.detach().cpu().numpy())\n            labels.append(y.detach().cpu().numpy())\n        \n        return np.concatenate(preds, axis = 0), np.concatenate(labels, axis = 0)\n    \n    def evaluate(self, loader):\n        preds, labels = self.predict(loader)\n        return f1_score((preds.reshape(-1) > 0.5).astype(int), labels.reshape(-1), average = \"macro\")\n    \n    def generate_prediction(self, loader):\n        self.model.load_state_dict(torch.load(self.save_path, map_location = DEVICE))\n        return self.predict(loader)[0]\n    \n    def save_model(self, path):\n        torch.save(self.model.state_dict(), path)\n        torch.save(self.model.encoder.state_dict(), path + '-encoder')\n        \n    def load_encoder(self, path):\n        self.model.encoder.load_state_dict(torch.load(path))\n        \n    def load_model(self,path):\n        self.model.load_state_dict(torch.load(path))\n    ","metadata":{"execution":{"iopub.status.busy":"2023-04-16T14:25:12.375599Z","iopub.execute_input":"2023-04-16T14:25:12.376030Z","iopub.status.idle":"2023-04-16T14:25:13.403404Z","shell.execute_reply.started":"2023-04-16T14:25:12.375963Z","shell.execute_reply":"2023-04-16T14:25:13.402404Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Dataset","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import DataLoader, Dataset\n\nclass Ds(Dataset):\n    def __init__(self, data, targets):\n        self.data = data\n        self.targets = targets\n    \n    def __len__(self):\n        return len(self.data)\n    \n    def __getitem__(self, idx):\n                \n        X = torch.tensor(self.data[idx], dtype = torch.float32)\n        y = torch.tensor(self.targets[idx], dtype = torch.float32)\n        \n        return X,y\n","metadata":{"execution":{"iopub.status.busy":"2023-04-16T14:25:13.404910Z","iopub.execute_input":"2023-04-16T14:25:13.405603Z","iopub.status.idle":"2023-04-16T14:25:13.412874Z","shell.execute_reply.started":"2023-04-16T14:25:13.405561Z","shell.execute_reply":"2023-04-16T14:25:13.411752Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training/Evaluation Loop","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import GroupKFold\nfrom tqdm import tqdm\n\nclass Config:\n    BATCH_SIZE = 128\n    LEARNING_RATE = 1e-3\n    TEST_INTERVAL = 10\n    EPOCHS = 100\n    EARLY_STOP = 5\n\noofs = []\nwriter = SummaryWriter(f'logs/{datetime.now().strftime(\"%H:%M:%S\")}')\n\nfor level_group in [GP1,GP2,GP3]:\n    \n    print(f'*** training group {level_group} ***')\n    \n    torch.cuda.empty_cache()\n    \n    data = GROUP_DATA[level_group]['data']\n    labels = GROUP_DATA[level_group]['targets']\n    pos_weight = GROUP_DATA[level_group]['pos_weight']\n    \n    model_param = model_params[level_group]\n    \n    oof = pd.DataFrame(data=np.zeros((targets.shape[0], model_param['nout'])))\n    gkf = GroupKFold(n_splits=10)\n    \n    for nfold, (train_index, test_index) in enumerate(gkf.split(X = oof, groups = oof.index)):\n        print(f'--- fold {nfold+1} ---')\n        \n        save_path = f'model-{level_group}-fold-{nfold}'\n        \n        model = Model(**model_param)\n        \n        trainer = Trainer(\n            model,\n            save_path = save_path,\n            num_labels = model.nout,\n            pos_weight = pos_weight,\n            learning_rate = Config.LEARNING_RATE\n        )\n\n        train_data, train_label = data[train_index], labels[train_index]\n        valid_data, valid_label = data[test_index], labels[test_index]\n        \n        train_loader = DataLoader(\n            Ds(train_data, train_label), batch_size=Config.BATCH_SIZE, shuffle=True, num_workers = 2)\n        test_loader = DataLoader(\n            Ds(valid_data, valid_label), batch_size=Config.BATCH_SIZE, shuffle=False, num_workers = 2)\n\n        if level_group == GP2:\n            trainer.load_encoder(f'model-{GP1}-fold-{nfold}-encoder')\n        elif level_group == GP3:\n            trainer.load_encoder(f'model-{GP2}-fold-{nfold}-encoder')\n\n        scalars = trainer.train_network(train_loader, test_loader)        \n        for key in scalars:\n            for i in range(len(scalars[key])):\n                writer.add_scalar(f'{level_group}/{nfold}/{key}', scalars[key][i], i)\n        preds = trainer.generate_prediction(test_loader)\n    \n        oof.iloc[test_index] = preds\n      \n    oofs.append(oof)","metadata":{"execution":{"iopub.status.busy":"2023-04-16T14:25:13.414440Z","iopub.execute_input":"2023-04-16T14:25:13.415023Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Evaluate","metadata":{}},{"cell_type":"markdown","source":"The evaluation pipeline is copied from [this](https://www.kaggle.com/code/cdeotte/xgboost-baseline-0-676) this notebook. This is amazing because the CV score is very close to the real submission. ","metadata":{}},{"cell_type":"code","source":"oof = pd.concat(oofs, axis = 1)\noof.to_csv('./oof.csv')\noof.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"true = np.concatenate([GROUP_DATA[GP]['targets'] for GP in [GP1, GP2, GP3]], axis = 1)\ntrue.shape","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# FIND BEST THRESHOLD TO CONVERT PROBS INTO 1s AND 0s\nfrom sklearn.metrics import f1_score\nscores = []; thresholds = []\nbest_score = 0; best_threshold = 0\n\nfor threshold in np.arange(0.1,0.9,0.01):\n    print(f'{threshold:.02f}, ',end='')\n    m = f1_score((oof.values.reshape(-1) > threshold).astype(int), true.reshape(-1), average='macro')  \n    scores.append(m)\n    thresholds.append(threshold)\n    if m>best_score:\n        best_score = m\n        best_threshold = threshold","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# PLOT THRESHOLD VS. F1_SCORE\nplt.figure(figsize=(20,5))\nplt.plot(thresholds,scores,'-o',color='blue')\nplt.scatter([best_threshold], [best_score], color='blue', s=300, alpha=1)\nplt.xlabel('Threshold',size=14)\nplt.ylabel('Validation F1 Score',size=14)\nplt.title(f'Threshold vs. F1_Score with Best F1_Score = {best_score:.3f} at Best Threshold = {best_threshold:.3}',size=18)\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Download everything","metadata":{}},{"cell_type":"code","source":"!zip weights.zip model-*\n!zip logs.zip ./logs/*/*\n\nimport IPython.display as ipd\n\nipd.display(ipd.FileLink('./weights.zip'))\nipd.display(ipd.FileLink('./logs.zip'))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Future Works\n\n1. Embeds the categorical data into the network\n2. Use the past data, not only weights.\n3. Use the embeddings with other model such as XGBoost","metadata":{}}]}