{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Reverse Game of Life - Inception CNN"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Download git repository and copy to local directory\n!rm -rf /ai-games/\n!git clone https://github.com/JamesMcGuigan/ai-games/ /ai-games/\n# !cd /ai-games/; git checkout ad2f8cc94865f1be6083ca699d4b62b0cc039435\n!cp -rf /ai-games/puzzles/game_of_life/* ./   # copy code to kaggle notebook\n!cd /ai-games/; git log -n1 ","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"from numba import njit, prange\nfrom scipy.signal import convolve2d\nfrom typing import Union, List, Tuple, Dict, Callable\nfrom itertools import chain, product\n\nimport humanize\nimport itertools\nimport math\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport os\nimport pandas as pd\nimport scipy\nimport scipy.sparse\nimport sys\nimport time\nimport skimage\nimport skimage.measure\nimport pydash\n\nimport torch\nimport numpy as np\nfrom utils.util import *\nfrom utils.plot import *\nfrom utils.game import *\nfrom utils.datasets import *\nfrom utils.tuplize import *\nfrom hashmaps.crop import *\nfrom hashmaps.hash_functions import *\nfrom hashmaps.translation_solver import *\nfrom hashmaps.repeating_patterns import *\nfrom constraint_satisfaction.fix_submission import *\nfrom neural_networks.train import train\n\n\n\ndevice   = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n__file__ = './notebook.py'\nnotebook_start = time.perf_counter()\n\n### Don't wrap console output text\nfrom IPython.display import display, HTML\ndisplay(HTML(\"\"\"<style>\ndiv.output_area pre {\n    width: 10000px;\n}\n</style>\"\"\"))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"from abc import ABCMeta\nfrom typing import TypeVar\n\nimport numpy as np\nimport torch\nimport torch as pt\nimport torch.nn.functional as F\nfrom torch import nn\n\nfrom neural_networks.GameOfLifeBase import GameOfLifeBase\nfrom neural_networks.hardcoded.GameOfLifeHardcodedReLU1_21 import GameOfLifeHardcodedReLU1_21\nfrom neural_networks.modules.ReLUX import ReLU1\n\n# noinspection PyTypeChecker\nT = TypeVar('T', bound='GameOfLifeFeatures')\nclass GameOfLifeFeatures(nn.Module):\n    \"\"\" Count the number of neighbouring cells (excluding self)\"\"\"\n    def __init__(self):\n        super().__init__()\n        self.forward_play = GameOfLifeHardcodedReLU1_21()\n        self.conv2d       = nn.Conv2d(in_channels=1, out_channels=4, bias=False,\n                                      kernel_size=(3,3), padding=1, padding_mode='circular')\n        self.conv2d.weight.data = torch.tensor([\n            # 11N Solution\n            [[[ -1.0, -1.0, -1.0 ],\n              [ -1.0, -0.7, -1.0 ],\n              [ -1.0, -1.0, -1.0 ]]],\n            # neighbours\n            [[[  1.0,  1.0,  1.0 ],\n              [  1.0,  0.0,  1.0 ],\n              [  1.0,  1.0,  1.0 ]]],\n            # # corners\n            # [[[  1.0,  0.0,  1.0 ],\n            #   [  0.0,  0.0,  0.0 ],\n            #   [  1.0,  0.0,  1.0 ]]],\n            # # edges\n            # [[[  0.0,  1.0,  0.0 ],\n            #   [  1.0,  0.0,  1.0 ],\n            #   [  0.0,  1.0,  0.0 ]]],\n        ])\n        self.conv2d.weight.requires_grad_(False)  # Weights are hardcoded\n\n\n    def forward(self, x):\n        shape = x.shape\n        x = x.reshape(-1, 1, x.shape[2], x.shape[3])\n        features = self.conv2d(x)\n        x = torch.cat([\n            x,                     # +1 channels\n            self.forward_play(x),  # +1 channels\n            features,              # +4 channels\n        ], dim=1)\n        x = x.reshape(shape[0], -1, shape[2], shape[3])\n        return x\n\n\n\n\nclass GameOfLifeReverseInception(GameOfLifeBase, metaclass=ABCMeta):\n    \"\"\"\n    \"\"\"\n    def __init__(self):\n        super().__init__()\n\n        # self.criterion = FocalLoss()\n        self.criterion = nn.MSELoss()\n        # self.criterion  = nn.BCELoss()\n        self.activation = nn.PReLU()\n        self.relu1      = ReLU1()\n\n        # discriminator must be at top-level for autograd to work, its weights are added to the savefile\n        self.discriminator = GameOfLifeHardcodedReLU1_21()\n        for name, parameter in self.discriminator.named_parameters(): parameter.requires_grad = False\n\n        self.features = GameOfLifeFeatures()\n\n        self.layers = nn.ModuleList([\n\n            # self.features()\n            nn.ModuleList([\n                nn.Conv2d(in_channels=4, out_channels=9, kernel_size=(1,1), padding=0, padding_mode='circular'),\n                nn.Conv2d(in_channels=4, out_channels=9, kernel_size=(3,3), padding=1, padding_mode='circular'),\n                nn.Conv2d(in_channels=4, out_channels=9, kernel_size=(5,5), padding=2, padding_mode='circular'),\n                nn.Conv2d(in_channels=4, out_channels=9, kernel_size=(7,7), padding=3, padding_mode='circular'),\n                nn.Conv2d(in_channels=4, out_channels=9, kernel_size=(9,9), padding=4, padding_mode='circular'),\n            ]),\n            nn.Conv2d(in_channels=9*5, out_channels=9, kernel_size=(1,1), padding=0, padding_mode='circular'),\n            nn.Conv2d(in_channels=9,   out_channels=9, kernel_size=(1,1), padding=0, padding_mode='circular'),\n\n            # self.features()\n            nn.ModuleList([\n                nn.Conv2d(in_channels=(9+1)*4, out_channels=9, kernel_size=(1,1), padding=0, padding_mode='circular'),\n                nn.Conv2d(in_channels=(9+1)*4, out_channels=9, kernel_size=(3,3), padding=1, padding_mode='circular'),\n                nn.Conv2d(in_channels=(9+1)*4, out_channels=9, kernel_size=(5,5), padding=2, padding_mode='circular'),\n                nn.Conv2d(in_channels=(9+1)*4, out_channels=9, kernel_size=(7,7), padding=3, padding_mode='circular'),\n                nn.Conv2d(in_channels=(9+1)*4, out_channels=9, kernel_size=(9,9), padding=4, padding_mode='circular'),\n            ]),\n            nn.Conv2d(in_channels=9*5, out_channels=9, kernel_size=(1,1), padding=0, padding_mode='circular'),\n            nn.Conv2d(in_channels=9,   out_channels=9, kernel_size=(1,1), padding=0, padding_mode='circular'),\n\n            # self.features()\n            nn.ModuleList([\n                nn.Conv2d(in_channels=(9+1)*4, out_channels=9, kernel_size=(1,1), padding=0, padding_mode='circular'),\n                nn.Conv2d(in_channels=(9+1)*4, out_channels=9, kernel_size=(3,3), padding=1, padding_mode='circular'),\n                nn.Conv2d(in_channels=(9+1)*4, out_channels=9, kernel_size=(5,5), padding=2, padding_mode='circular'),\n                nn.Conv2d(in_channels=(9+1)*4, out_channels=9, kernel_size=(7,7), padding=3, padding_mode='circular'),\n                nn.Conv2d(in_channels=(9+1)*4, out_channels=9, kernel_size=(9,9), padding=4, padding_mode='circular'),\n            ]),\n            nn.Conv2d(in_channels=9*5, out_channels=9, kernel_size=(1,1), padding=0, padding_mode='circular'),\n            nn.Conv2d(in_channels=9,   out_channels=9, kernel_size=(1,1), padding=0, padding_mode='circular'),\n\n            nn.Conv2d(in_channels=9,   out_channels=1, kernel_size=(1,1), padding=0, padding_mode='circular'),\n        ])\n\n\n    def forward(self, x):\n        x = self.cast_inputs(x)\n        x = self.relu1(x)\n        input = self.features(x)\n        for n, layer in enumerate(self.layers):\n            if isinstance(layer, nn.ModuleList):\n                x = self.features(x)\n                x = torch.cat([ x, input ], dim=1) if n != 0 else x\n                channels = [ sublayer(x) for sublayer in layer ]\n                x = torch.cat(channels, dim=1)\n            else:\n                x = layer(x)\n            x = self.activation(x)\n        x = self.relu1(x)\n        return x\n\n\n    def loss(self, outputs: pt.tensor, expected: pt.tensor, inputs: pt.tensor):\n        \"\"\"\n        GameOfLifeReverseOneGAN() computes the backwards timestep\n        discriminator GameOfLifeHardcodedReLU1_21() replays the board again forwards\n        forward_loss is the MSE difference between the backwards prediction and forward play\n        classic_loss biases the network towards the exact solution, but reduces to zero as forward_loss approaches zero\n        cell_count_loss is a heuristic to guide solution towards the correct cell count and avoid all 0 or all 1 solutions\n        binary_loss penalizes non-binary output\n        \"\"\"\n        forwards         = self.discriminator(outputs)\n        forward_loss     = self.criterion(forwards, inputs)                      # loss==0 if forward play matches input\n        classic_loss     = self.criterion(outputs, expected)                     # loss==0 if dataset matches output\n        cell_count_loss1 = ( torch.mean(forwards) - torch.mean(inputs)   ) ** 2  # loss==0 if cell count is the same\n        cell_count_loss2 = ( torch.mean(outputs)  - torch.mean(expected) ) ** 2  # loss==0 if cell count is the same\n        binary_loss1     = torch.mean( 0.5**2 - ( forwards - 0.5 )**2 )          # loss==0 if all values are 1 or 0\n        binary_loss2     = torch.mean( 0.5**2 - ( outputs  - 0.5 )**2 )          # loss==0 if all values are 1 or 0\n        loss = (\n            forward_loss + cell_count_loss1 + binary_loss1 + binary_loss2\n            + F.relu( ( classic_loss + cell_count_loss2 ) * (forward_loss - 0.01) * 10 )\n        )\n        return loss\n\n    # noinspection PyTypeChecker\n    def accuracy(self, outputs, expected, inputs):\n        \"\"\" Accuracy here is based upon if the output matches the input after forward play \"\"\"\n        # return super().accuracy(outputs, expected, inputs)\n        forwards = self.discriminator(self.cast_int(outputs))\n        return pt.sum( self.cast_bool(forwards) == self.cast_bool(inputs) ).cpu().numpy() / np.prod(outputs.shape)\n\n\n    def unfreeze(self: T) -> T:\n        if not self.loaded: self.load()\n        excluded = { 'discriminator', 'features' }\n        for name, parameter in self.named_parameters():\n            if not set( name.split('.') ) & excluded:\n                parameter.requires_grad = True\n        return self\n    \n    def weights_init(self, layer):\n        pass","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# %%time\n# from neural_networks.train import train\n\n# # Keep trying to retrain the network until we get 90% accuracy after 300 epochs\n# def lottery_ticket_initialization( model_class, epochs=100, min_acc=0.9, timeout=30*60, verbose=False ):\n#     time_start = time.perf_counter()\n#     accs     = []\n#     expected = generate_random_boards(1_000)\n#     inputs   = [ life_step(board) for board in expected ]\n#     best_model = None\n#     best_acc   = -1\n#     while True:\n#         model = model_class()\n#         if os.path.exists(model.filename): os.remove(model.filename)\n#         model.load(verbose=verbose)\n        \n#         # small grids have less whitespace\n#         train(model, grid_size=5, reverse_input_output=True, epochs=epochs, verbose=verbose)  \n        \n#         acc = np.mean( model.predict(inputs) == expected ) \n#         accs.append( acc )\n#         if acc > best_acc: \n#             best_model = model\n#             best_acc   = acc\n#         if acc > min_acc:  \n#             model.save() \n#             break\n#         if timeout and timeout < time.perf_counter() - time_start: \n#             break\n#     print(f'attempts: {len(accs)} | accuracies: {accs} | min: {np.min(accs)} | mean: {np.mean(accs)} | max: {np.max(accs)} | std: {np.std(accs)}')\n#     return best_model\n\n# model = lottery_ticket_initialization(GameOfLifeReverseInception)\n\nmodel = GameOfLifeReverseInception()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"timeout = 110 * 60 - ( time.perf_counter() - notebook_start )\ntrain(model, grid_size=3, reverse_input_output=True, timeout=timeout)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"timeout = 110 * 60 - ( time.perf_counter() - notebook_start )\ntrain(model, grid_size=5, reverse_input_output=True, timeout=timeout)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"timeout = 110 * 60 - ( time.perf_counter() - notebook_start )\ntrain(model, grid_size=7, reverse_input_output=True, timeout=timeout)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"timeout = 110 * 60 - ( time.perf_counter() - notebook_start )\ntrain(model, grid_size=11, reverse_input_output=True, timeout=timeout)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"timeout = 110 * 60 - ( time.perf_counter() - notebook_start )\ntrain(model, grid_size=25, reverse_input_output=True, timeout=timeout)","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}