{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Task overview/Game Rules\n\n*The game consists of a board of cells that are either on or off. One creates an initial configuration of these on/off states and observes how it evolves. There are four simple rules to determine the next state of the game board, given the current state:*\n\n* #### Overpopulation: if a living cell is surrounded by more than three living cells, it dies.\n* #### Stasis: if a living cell is surrounded by two or three living cells, it survives.\n* #### Underpopulation: if a living cell is surrounded by fewer than two living cells, it dies.\n* #### Reproduction: if a dead cell is surrounded by exactly three cells, it becomes a live cell.\n\n![](https://natureofcode.com/book/imgs/chapter07/ch07_01.png)","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Approach overview\n\n\n### Iterative model\nThe basic idea is to train a model that can predict start state from stop state(with delta=1) and apply this model delta times.\n\nThe model itself can be any (not only neural networks).\n\n\n![](https://i.ibb.co/y0fMSCg/simple-model-diagram.png)\n\n\n### Iterative model with Encoder/Decoder\n\nUsing an encoder and a decoder allows more information to be conveyed to the next step (not just one channel).\n\n![](https://i.ibb.co/MRvZbYj/model-diagram.png)\n\n\n### Iterative CNN model with Encoder/Decoder\n\nThe stop and start state can be viewed as single-channel images(so we can use CV approaches)\n\nInput - 1 channel image\n\nEncoder in - 1 channel image | out - N channel image\n\nReverseOneIterationModel - inp N channel image | out - N channel image\n\nDecoder in - N channel image | out - 1 channel image","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Model","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\n\nclass OneIterationReverseNet(nn.Module):\n    def __init__(self, info_ch, ch):\n        super().__init__()\n        self.relu = nn.ReLU()\n        self.conv1 = nn.Conv2d(info_ch, ch, 5, padding=4, padding_mode='circular')\n        self.conv2 = nn.Conv2d(ch, ch, 3, )\n        self.conv3 = nn.Conv2d(ch, info_ch, 3)\n        \n        \n    def forward(self, inp):\n        x = self.relu(self.conv1(inp))\n        x = self.relu(self.conv2(x))\n        x = self.relu(self.conv3(x))\n        return x\n      \n        \nclass ReverseModel(nn.Module):\n    def __init__(self, info_ch=64, ch=128):\n        super().__init__()\n        self.relu = nn.ReLU()\n        self.encoder = nn.Conv2d(1, info_ch, 7, padding=3, padding_mode='circular')# you can use other model\n        self.reverse_one_iter = OneIterationReverseNet(info_ch, ch)# you can use other model\n        self.decoder = nn.Conv2d(info_ch, 1, 3, padding=1, padding_mode='circular')# you can use other model\n        \n    \n    def forward(self, stop, delta):\n        x = self.relu(self.encoder(stop-0.5))\n        \n        for i in range(delta.max().item()):\n            y = self.reverse_one_iter(x)\n            \n            # this 2 lines allow use samples with different delta in one batch\n            mask = (delta > i).reshape(-1,1,1,1)\n            x = x*(~mask).float() + y*mask.float()\n        \n        x = self.decoder(x)\n        \n        return x ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Load data","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import pandas as pd\nfrom sklearn.model_selection import train_test_split","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_val = pd.read_csv('/kaggle/input/conways-reverse-game-of-life-2020/train.csv', index_col='id')\ntest = pd.read_csv('/kaggle/input/conways-reverse-game-of-life-2020/test.csv', index_col='id')\n\ntrain, val = train_test_split(train_val, test_size=0.2, shuffle=True, random_state=42, stratify=train_val['delta'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Dataset","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"from torch.utils.data import DataLoader, Dataset\nfrom torch import FloatTensor, LongTensor\n\n\ndef line2grid_tensor(data, device='cuda'):\n    grid = data.to_numpy().reshape((data.shape[0], 1, 25, 25))\n    return FloatTensor(grid).to(device)\n\n\nclass TaskDataset(Dataset):\n    def __init__(self, data, device='cuda'):\n        self.delta = LongTensor(data['delta'].to_numpy()).to(device)\n        if data.shape[1] == 1251: \n            self.start = line2grid_tensor(data.iloc[:,1:626], device)\n            self.stop = line2grid_tensor(data.iloc[:,626:], device)\n        else:\n            self.start = None\n            self.stop = line2grid_tensor(data.iloc[:,1:], device)\n        \n    def __len__(self):\n        return len(self.delta)\n\n    def __getitem__(self, idx):\n        if self.start is None:\n            return {'stop': self.stop[idx], 'delta': self.delta[idx]}\n        return {'start': self.start[idx], 'stop': self.stop[idx], 'delta': self.delta[idx]}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"dataset_train = TaskDataset(train)\ndataloader_train = DataLoader(dataset_train, batch_size=128, shuffle=True)\n\ndataset_val = TaskDataset(val)\ndataloader_val = DataLoader(dataset_val, batch_size=128, shuffle=False)\n\ndataset_test = TaskDataset(test)\ndataloader_test = DataLoader(dataset_test, batch_size=128, shuffle=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Train Loop","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"from catalyst.dl import SupervisedRunner\nfrom catalyst.dl.callbacks import CriterionCallback, EarlyStoppingCallback, AccuracyCallback\nfrom catalyst.contrib.nn.optimizers import RAdam, Lookahead\n\nimport collections\n\nrunner = SupervisedRunner(device='cuda', input_key=['stop', 'delta'], )\n\nloaders = {'train': dataloader_train, 'valid': dataloader_val}#collections.OrderedDict({'train': dataloader_train, 'valid': dataloader_val})\n\nmodel = ReverseModel()\n\noptimizer = Lookahead(RAdam(params=model.parameters(), lr=1e-3))\n\ncriterion = {\"bce\": nn.BCEWithLogitsLoss()}\n\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=0.25, patience=2)\n\ncallbacks = [\n        CriterionCallback(input_key='start', prefix=\"loss\", criterion_key=\"bce\"),\n        EarlyStoppingCallback(patience=5),\n    ]\n\nrunner.train(\n    model=model,\n    criterion=criterion,\n    optimizer=optimizer,\n    scheduler=scheduler,\n    loaders=loaders,\n    callbacks=callbacks,\n    logdir=\"./logs\",\n    num_epochs=999,\n    main_metric=\"loss\",\n    minimize_metric=True,\n    verbose=True,\n)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Prediction / LB estimate","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"### Load best model","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"best_model = ReverseModel().to('cuda')\nbest_model.load_state_dict(torch.load('logs/checkpoints/best.pth')['model_state_dict'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### LB estimate","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import numpy as np\n\ndef predict_batch(model, batch):\n    model.eval()\n    with torch.no_grad():\n        prediction = model(batch['stop'], batch['delta'])\n        prediction = torch.sigmoid(prediction).detach().cpu().numpy()\n        return prediction\n\n    \ndef predict_loader(model, loader):\n    predict = [predict_batch(model, batch) for batch in loader]\n    predict = np.concatenate(predict)\n    return predict\n\n\ndef validate_loader(model, loader, lb_delta=None, threshold=0.5):\n    prediction_val = predict_loader(best_model, loader)\n    y_val = loader.dataset.start.detach().cpu().numpy()\n    delta_val = loader.dataset.delta.detach().cpu().numpy()\n\n    score = ((prediction_val > threshold) == y_val).mean(axis=(1,2,3))\n    print(f'All data accuracy: {score.mean()}')\n        \n    delta_socre = {}\n    for i in range(1, 6):\n        delta_socre[i] = score[delta_val==i].mean()#print(f'delta={i} accuracy: {score[delta_val==i].mean()}')\n        print(f'delta={i} accuracy: {delta_socre[i]}')\n        \n    if lb_delta is not None:\n        lb_delta = lb_delta.value_counts(normalize=True)\n        test_score = sum([lb_delta[i]*delta_socre[i] for i in range(1,6)])\n        print(f'VAL score         : {1-score.mean()}')\n        print(f'LB  score estimate: {1-test_score}')\n    \n    \ndef make_submission(prediction, threshold=0.5, sample_submission_path='/kaggle/input/conways-reverse-game-of-life-2020/sample_submission.csv'):\n    prediction = (prediction > threshold).astype(int).reshape(-1, 625)\n    \n    sample_submission = pd.read_csv(sample_submission_path, index_col='id')\n    sample_submission.iloc[:] = prediction\n    return sample_submission","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"validate_loader(best_model, dataloader_val, test['delta'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### submission","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"prediction_test = predict_loader(best_model, dataloader_test)\nsubmission = make_submission(prediction_test)\nsubmission.to_csv('submission.csv')\nsubmission","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","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}