{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport matplotlib.pyplot as plt\nimport itertools\nimport numpy as np\nimport pandas as pd\nimport torch.optim as optim\nfrom torch.autograd import Variable\nimport torch\nfrom tqdm import tqdm\nimport sys\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Life Simulators"},{"metadata":{"trusted":true},"cell_type":"code","source":"def life_step(X):\n    \"\"\"Game of life step using generator expressions\"\"\"\n    nbrs_count = sum(np.roll(np.roll(X, i, 0), j, 1)\n                     for i in (-1, 0, 1) for j in (-1, 0, 1)\n                     if (i != 0 or j != 0))\n    return (nbrs_count == 3) | (X & (nbrs_count == 2))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def actual_forward(x): \n    torch.backends.cudnn.deterministic = True\n    \n    # Weights for layer 1\n    weight1 = torch.tensor([[[1, 1, 1], [1, 0.1, 1], [1, 1, 1]],\n                            [[1, 1, 1], [1, 1, 1], [1, 1, 1]]]).view(2, 1, 3, 3).float()\n    b1 = torch.tensor([-3, -2]).float()\n    # Weights for layer 2\n    weight2 = torch.tensor([-10, 1]).view(1, 2, 1, 1).float()\n    # Weights for layer 3\n    s = 20\n    weight3 = torch.tensor([2*s]).view(1, 1, 1, 1).float()\n    b3 = torch.tensor([-s]).float()\n    \n    if torch.cuda.is_available():\n        weight1 = weight1.cuda()\n        b1 = b1.cuda()\n        weight2 = weight2.cuda()\n        weight3 = weight3.cuda()\n        b3 = b3.cuda()\n\n    x = F.pad(x.float(), (1, 1, 1, 1), mode='circular')\n    x = F.relu(F.conv2d(x, weight1, b1))\n    x = F.relu(F.conv2d(x, weight2))\n    x = torch.sigmoid(F.conv2d(x, weight3, b3))\n    return x","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","collapsed":true,"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":false},"cell_type":"markdown","source":"# Data Generator"},{"metadata":{"trusted":true},"cell_type":"code","source":"def generate_inf_cases(train, seed, board_size=25, min_dens=0.01, max_dens=0.99, warm_up=5, min_delta=1, max_delta=5, dtype=np.int, return_one_but_last=False):\n    rs = np.random.RandomState(seed)\n    zer = np.zeros(shape=(board_size, board_size), dtype=dtype)\n    while True:\n        density = rs.uniform(min_dens, max_dens)\n        start = rs.choice([1, 0], size=(board_size, board_size), p=[density, 1.0-density])\n        for _ in range(warm_up):\n            start = life_step(start)\n\n        delta = rs.randint(min_delta, max_delta+1)\n        stop = start.copy()\n        one_but_last = None\n        for _ in range(delta):\n            one_but_last = stop\n            stop = life_step(stop)\n\n        if not (stop == zer).all():\n            if return_one_but_last:\n                yield delta, one_but_last, stop\n            elif train:\n                yield delta, start, stop\n            else:\n                yield delta, stop","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def generate_n_cases(train, set_size, seed, **kwargs):\n    return itertools.islice(\n        generate_inf_cases(train, seed, **kwargs),\n        set_size)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def generate_train_set(set_size, seed, **kwargs):\n    return generate_n_cases(True, set_size, seed, **kwargs)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"REGENERATE_DATA = True","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Generate or load training data\nN = 128000\n\nif REGENERATE_DATA:\n    train_data_gen = generate_train_set(N, 41, min_delta=1, max_delta=1)\n    deltas, start_boards, stop_boards = map(np.array, zip(*list(train_data_gen)))\n    # Save training data\n    np.save('/kaggle/working/training_start_boards', start_boards)\n    np.save('/kaggle/working/training_stop_boards', stop_boards)\nelse:\n    start_boards = np.load('/kaggle/working/data/training_start_boards.npy')\n    stop_boards = np.load('/kaggle/working/data/training_stop_boards.npy')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Generate or load validation data\nN_valid = 12800\n\nif REGENERATE_DATA:\n    valid_data_gen = generate_train_set(N_valid, 888, min_delta=1, max_delta=1)\n    deltas, valid_start_boards, valid_stop_boards = map(np.array, zip(*list(valid_data_gen)))\n    # Save validation data\n    np.save('/kaggle/working/valid_start_boards', valid_start_boards)\n    np.save('/kaggle/working/valid_stop_boards', valid_stop_boards)\nelse:\n    valid_start_boards = np.load('/kaggle/working/valid_start_boards.npy')\n    valid_stop_boards = np.load('/kaggle/working/valid_stop_boards.npy')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model Trainer"},{"metadata":{"trusted":true},"cell_type":"code","source":"def train(model, X, y, X_valid, y_valid, \n          optim, criterion, output_path, num_epochs=10, batch_size=128):\n    # Set optimizer\n    optimizer = optim(model.parameters())\n    \n    model.cuda()\n\n    # Best validation MAE\n    best_valid_mae = 1\n    \n    # Train\n    n_iter = 0\n    for epoch in range(num_epochs): \n        permutation = torch.randperm(X.size()[0])\n        running_loss = 0.0\n        pbar = tqdm(range(0, X.size()[0], batch_size))\n        for i in pbar:\n            n_iter += 1\n            indices = permutation[i:i+batch_size]\n            batch = X[indices].cuda()\n            target = y[indices].cuda()\n        \n            optimizer.zero_grad()\n            outputs = model(batch)\n            loss = criterion(outputs, target)\n            loss.backward()\n            optimizer.step()\n            \n            # Calculate MAE\n            if hasattr(model, \"reverse_net\"):\n                pred_start_boards = model.reverse_net(batch)\n                outputs = actual_forward((pred_start_boards > 0.5).int())\n            output_boards = (outputs > 0.5).int()   \n            mae = torch.sum(output_boards != target).float() / (batch_size * 25 * 25)    \n            pbar.set_description(\"[{:d}, {:5d}] loss: {:.6f} | train MAE {:.6f} | best MAE: {:.6f}\".format(epoch + 1, i + 1, loss.item(), mae, best_valid_mae))\n\n            # Write boards and validation results to Tensorboard every 50 batches\n            if n_iter % 50 == 0:\n                with torch.no_grad():\n                    model.eval()\n                    valid_loss = 0\n                    valid_mae = 0\n                    m = 0\n                    for j in range(0, X_valid.size()[0], batch_size):\n                        m += 1\n                        valid_batch = X_valid[j:j+batch_size].cuda()\n                        valid_target = y_valid[j:j+batch_size].cuda()\n                        valid_outputs = model(valid_batch)\n                        valid_loss += criterion(valid_outputs, valid_target)\n                        if hasattr(model, \"reverse_net\"):\n                            valid_start_boards = model.reverse_net(valid_batch)\n                            valid_outputs = actual_forward((valid_start_boards > 0.5).int())\n                        valid_boards = (valid_outputs > 0.5).int()\n                        valid_mae += torch.sum(valid_boards != valid_target).float()\n                    valid_loss /= m\n                    valid_mae /= (X_valid.size()[0] * 25 * 25)\n                    \n                if valid_mae < best_valid_mae:\n                    best_valid_mae = valid_mae\n                    # Save model if we have the lastest best MAE\n                    torch.save(model.state_dict(), output_path)\n    print(\"The best validation MAE: {}\".format(best_valid_mae))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Train Relaxed Forward Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Modify starting boards \ndef relax_boards(boards):\n    np.random.seed(41)\n    return np.abs(np.random.rand(*boards.shape) / 2 - boards)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class RelaxedForwardNet(nn.Module):\n    def __init__(self):\n        super(RelaxedForwardNet, self).__init__()\n        # in channels, out channels, kernel size\n        self.conv0 = nn.Conv2d(1, 8, (1, 1))\n        self.activ0 = nn.ReLU()\n        self.conv1 = nn.Conv2d(8, 16, (3, 3), padding=(1, 1), padding_mode='circular')\n        self.activ1 = nn.PReLU()\n        self.conv2 = nn.Conv2d(16, 8, (3, 3), padding=(1, 1), padding_mode='circular')\n        self.activ2 = nn.PReLU()\n        self.conv3 = nn.Conv2d(8, 4, (3, 3), padding=(1, 1), padding_mode='circular')\n        self.activ3 = nn.PReLU()\n        self.conv4 = nn.Conv2d(4, 1, (3, 3), padding=(1, 1), padding_mode='circular')\n        \n    def forward(self, x):\n        x = self.activ0(self.conv0(x))\n        x = self.activ1(self.conv1(x))\n        x = self.activ2(self.conv2(x))\n        x = self.activ3(self.conv3(x))\n        x = torch.sigmoid(self.conv4(x))\n        return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"relaxed_forward_net = RelaxedForwardNet()\ncriterion = nn.BCELoss()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Training data\nrelaxed_start_boards = relax_boards(start_boards)\nX_relaxed = Variable(torch.tensor(relaxed_start_boards).view(N, 1, 25, 25).float(), requires_grad=True)\ny = Variable(torch.tensor(stop_boards).view(N, 1, 25, 25).float())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Validation data\nrelaxed_valid_start_boards = relax_boards(valid_start_boards)\nX_valid_relaxed = Variable(torch.tensor(relaxed_valid_start_boards).view(N_valid, 1, 25, 25).float())\ny_valid = Variable(torch.tensor(valid_stop_boards).view(N_valid, 1, 25, 25).float())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"relaxed_forward_model_path = \"/kaggle/working/relaxed_forward.pkl\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train(relaxed_forward_net, X_relaxed, y, X_valid_relaxed, y_valid, optim.Adam, criterion, relaxed_forward_model_path, batch_size=512, num_epochs=25)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Train reverse-forward model"},{"metadata":{"trusted":true},"cell_type":"code","source":"class ReverseNet(nn.Module):\n    def __init__(self):\n        super(ReverseNet, self).__init__()\n        # in channels, out channels, kernel size\n        self.conv0 = nn.Conv2d(1, 8, (1, 1))\n        self.activ0 = nn.ReLU()\n        self.conv1 = nn.Conv2d(8, 16, (3, 3), padding=(1, 1), padding_mode='circular')\n        self.activ1 = nn.PReLU()\n        self.conv2 = nn.Conv2d(16, 8, (3, 3), padding=(1, 1), padding_mode='circular')\n        self.activ2 = nn.PReLU()\n        self.conv3 = nn.Conv2d(8, 4, (3, 3), padding=(1, 1), padding_mode='circular')\n        self.activ3 = nn.PReLU()\n        self.conv4 = nn.Conv2d(4, 1, (3, 3), padding=(1, 1), padding_mode='circular')\n\n    def forward(self, x):\n        x = self.activ0(self.conv0(x))\n        x = self.activ1(self.conv1(x))\n        x = self.activ2(self.conv2(x))\n        x = self.activ3(self.conv3(x))\n        x = torch.sigmoid(self.conv4(x))\n        return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class ReverseForwardNet(nn.Module):\n    def __init__(self, ForwardNet, forward_wt_path, ReverseNet):\n        super(ReverseForwardNet, self).__init__()\n        self.reverse_net = ReverseNet()\n        # freeze the weights of the forward net\n        self.forward_net = ForwardNet()\n        self.forward_net.load_state_dict(torch.load(forward_wt_path))\n        for param in self.forward_net.parameters():\n            param.requires_grad = False\n\n    def forward(self, x):\n        x = self.reverse_net(x)\n        x = self.forward_net(x)\n        return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"rf_net = ReverseForwardNet(RelaxedForwardNet, relaxed_forward_model_path, ReverseNet)\nprint(rf_net)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"criterion = nn.BCELoss()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Training data\nX_rf = Variable(torch.tensor(stop_boards).view(N, 1, 25, 25).float(), requires_grad=True)\ny_rf = y","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Validation data\nX_valid_rf = y_valid\ny_valid_rf = y_valid","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"rf_model_path = \"/kaggle/working/reverse_forward.pkl\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train(rf_net, X_rf, y_rf, X_valid_rf, y_valid_rf, optim.Adam, criterion, rf_model_path, batch_size=256, num_epochs=10)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"markdown","source":"# Make prediction and submission"},{"metadata":{"trusted":true},"cell_type":"code","source":"test_data_path = '/kaggle/input/conways-reverse-game-of-life-2020/test.csv'\nsubmission_path = '/kaggle/working/submission.csv'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_forward_mae(model, weight_path, input_boards, output_boards, n):\n    # Release CUDA memory\n    torch.cuda.empty_cache()\n    # Load model\n    model.load_state_dict(torch.load(weight_path))\n    model.cuda()\n    # Convert boards to tensor\n    input_boards_tensor = torch.tensor(input_boards[:n]).view(n, 1, 25, 25).float().cuda()\n    output_boards_tensor = torch.tensor(output_boards[:n]).view(n, 1, 25, 25)\n    with torch.no_grad():\n        model.eval()\n        # Make prediction\n        if hasattr(model, \"reverse_net\"):\n            predicted_start_board = model.reverse_net(input_boards_tensor)\n            predicted_output_board = (actual_forward((predicted_start_board > 0.5).int()) > 0.5).int()\n        else:\n            predicted_output_board = (model(input_boards_tensor) > 0.5).int()\n        error = torch.sum(predicted_output_board.cpu() != output_boards_tensor).float()\n        # print(predicted_stop_board)\n        # print(stop_boards_tensor)\n        return error / (n * 25 * 25)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def predict(df, model):\n    n = df.shape[0]\n    cols = [col for col in df.columns if col.startswith('stop')]\n    stop_boards = df[cols].to_numpy().reshape(n, 25, 25)\n    stop_boards_tensor = Variable(torch.tensor(stop_boards).float()).cuda()\n    deltas = df.delta.to_list()\n\n    predictions = np.zeros((n, 25, 25), dtype=int)\n    model.eval()\n    with torch.no_grad():\n        for i in tqdm(range(n)):\n            prediction = model(stop_boards_tensor[i].view(1, 1, 25, 25))            \n            for _ in range(deltas[i] - 1):\n                prediction = model((prediction > 0.5).float())\n            predictions[i] = (prediction > 0.5).view(25, 25).int().cpu()\n            \n    df_ = df[['id']].copy()\n    predictions = predictions.reshape(n, 625)\n    for i in range(625):\n        df_['start_{}'.format(i)] = predictions[:,i]\n    return df_","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df = pd.read_csv(test_data_path, sep=',')\ncols = [col for col in df.columns if col.startswith('stop')]\nstop_boards = df[cols].to_numpy().reshape(df.shape[0], 25, 25)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# print(\"MAE on test set: {:.8f}.\".format(get_forward_mae(rf_net, rf_model_path, stop_boards, stop_boards, df.shape[0])))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"predictions = predict(df, rf_net.reverse_net)\npredictions.to_csv(submission_path, index=False)","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}