{
  "id": 200608,
  "title": "22nd Place Solution - Z3 Constraint Satisfaction Solver",
  "url": "/competitions/conways-reverse-game-of-life-2020/discussion/200608",
  "author_name": "James McGuigan",
  "post_date": "2020-12-01T05:05:19.655000",
  "votes": 5,
  "comment_count": 0,
  "views": 0,
  "content": "<p>My approach to this competition was to try a variety of different techniques and publically publish all my ongoing research public throughout the competition, which resulted in a small tail of \"ensemble of public notebook\" behind me on the leaderboard.</p>\n<h1>Results</h1>\n<table>\n<thead>\n<tr>\n<th>score</th>\n<th>duration</th>\n<th>cores</th>\n<th>name</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0.08410</td>\n<td>5000h</td>\n<td>4</td>\n<td><a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction\" target=\"_blank\">Z3 Constraint Satisfaction</a></td>\n</tr>\n<tr>\n<td>0.08631</td>\n<td>8.25h</td>\n<td>4</td>\n<td><a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver\" target=\"_blank\">Image Segmentation Solver</a></td>\n</tr>\n<tr>\n<td>0.14502</td>\n<td>100h</td>\n<td>4</td>\n<td><a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver\" target=\"_blank\">Hashmap Solver</a></td>\n</tr>\n<tr>\n<td>0.14689</td>\n<td>0.5h</td>\n<td>4</td>\n<td><a href=\"https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-decision-tree?scriptVersionId=46840547\" target=\"_blank\">Random Forest</a></td>\n</tr>\n<tr>\n<td>0.14689</td>\n<td>0.5h</td>\n<td>GPU</td>\n<td><a href=\"https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-rnn\" target=\"_blank\">Recursive CNN</a></td>\n</tr>\n<tr>\n<td>0.31613</td>\n<td>1.5h</td>\n<td>GPU</td>\n<td><a href=\"https://www.kaggle.com/jamesmcguigan/ouroboroslife-function-reversal-gan\" target=\"_blank\">OuroborosLife Function Reversal GAN</a></td>\n</tr>\n</tbody>\n</table>\n<h1>Interactive Playable Game</h1>\n<p>I had previously written an interactive playable demo of the forward version of this game in React Javascript:</p>\n<ul>\n<li><a href=\"https://life.jamesmcguigan.com/\" target=\"_blank\">https://life.jamesmcguigan.com/</a></li>\n</ul>\n<h1>Z3 Constraint Satisfaction</h1>\n<ul>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction\" target=\"_blank\">https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction</a></li>\n</ul>\n<p>My first attempt was to use the Z3 SAT solver. </p>\n<p>Design-wise this was implemented as a 3d grid of cells going back delta steps. Game of Life forward rules were added as constraints, along with the stop board position and a no empty board constraint.</p>\n<p>As a performance optimization, an additional constraint was added that any dead cell with no neighbors was also dead in the previous time step. This helped prevent an infinite number of zero-point energy solutions of cells from appearing and disappearing in a sea of whitespace. However, this caused unsat on a few boards which required a distance of 2 whitespace. Z3 push and pop were used to check for the distance=1 constraint, then replace it with distance=2 upon unsat.</p>\n<p>I also discovered that using boolean rather than integer logic resulted in a 2-4x performance speedup.</p>\n<p>The main limitation of this approach was CPU runtime. Larger deltas and high cell counts (with less whitespace) would take exponentially longer. Even with pathos multiprocessing using all 4 CPUs it still took 20,000h of CPU runtime to reach a score of 0.08410 = 29,121/50,000 = 58%. </p>\n<p>Getting 20,000h of runtime on Kaggle required cluster compute with a dataset reimport loop. The codebase was hosted on github, and 9 forks of the notebook were created. The dataset divided by modulo % 9 on the index id. Each notebook fork imported the others as a datasource and the CSV files could be recombined, sorting to remove zeroed entries and awk to ensure unique indexes<br>\nusing:</p>\n<pre><code>!find ./ ../input/ -name 'submission.csv' | xargs cat | sort -nr | uniq | awk -F',' '!a[$1]++' | sort -n &gt; ./submission.csv\n</code></pre>\n<p>Many boards could not be individually solved within the 9h kaggle notebook time limit, so this also required logging timeout errors and skipping them on future runs.</p>\n<p>This process took quite a bit of effort, as I would need to manually rerun 9 notebook forks 2-3 timer per day to reach version 135 of the primary notebook. My estimate is that I used about 5000h x 4 CPUs of Kaggle resources on this competition.</p>\n<p>I did briefly experiment with making the stop position an optimization goal rather than a constraint but after the first execution kept running into memory and timeout errors.</p>\n<h1>Image Segmentation and Hashmap Solvers</h1>\n<ul>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/summable-primes\" target=\"_blank\">Summable Primes</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/geometric-invariant-hash-functions\" target=\"_blank\">Geometric Invariant Hash Functions</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-repeating-patterns\" target=\"_blank\">Game of Life - Repeating Patterns</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-hashmap-solver\" target=\"_blank\">Game of Life - Hashmap Solver</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver\" target=\"_blank\">Game of Life - Image Segmentation Solver</a></li>\n</ul>\n<p>My <a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver/\" target=\"_blank\">Image Segmentation Solver</a> which was inspired by my previous work on the <a href=\"https://www.kaggle.com/jamesmcguigan/arc-geometry-solvers\" target=\"_blank\">ARC Abstraction and Reasoning Corpus Geometry Solvers</a></p>\n<p>Scores are:</p>\n<ul>\n<li>0.08549 = 28,731/50,000 = 57.4% <a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction/\" target=\"_blank\">Z3 Constraint Satisfaction Solver</a> (still running)</li>\n<li>0.08631 = 28,446/50,000 = 56.9% <a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver/\" target=\"_blank\">Image Segmentation Solver</a></li>\n<li>0.14502 = 6,734/50,000 = 13.5% <a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-hashmap-solver/\" target=\"_blank\">Hashmap Solver</a></li>\n</ul>\n<p>The idea is that instead of trying to solve the reverse function, we can simply use forward play over millions of boards to generate a database of known 4d shapes, then create a solver using dictionary lookup based on hash key and delta.</p>\n<p>At first, you might consider the state space for a 25x25 game of life board might be too large (2^625 = 1.3*10^188), but it possible to exploit rotational, mirror, and translation symmetry to significantly reduce this. For this I developed <a href=\"https://www.kaggle.com/jamesmcguigan/geometric-invariant-hash-functions\" target=\"_blank\">Geometrically Invariant Hash Functions</a>. They work by taking a pixel-wise view of the board, extending out from each pixel in concentric circles, and using <a href=\"https://www.kaggle.com/jamesmcguigan/summable-primes\" target=\"_blank\">Summable Primes</a> to multiply the count the number of neighbouring pixels at each distance. The result of this is that the hash function remains unchanged after <code>np.roll()</code>, <code>np.flip()</code> and <code>np.rot90()</code> operations.</p>\n<p>It is also possible to create a translation-insensitive variant of this hash function that only remains unchanged by <code>np.roll()</code> but is affected by <code>np.flip()</code> and <code>np.rot90()</code>. </p>\n<p>For the test dataset, we hash the board and look it up in the database. If a match is found, then the problem is reduced to a much simpler geometry problem. <code>np.roll()</code> and <code>np.flip()</code> can be solved by brute force until the second translation-insensitive hash function aligns. <code>np.roll()</code> can be solved by taking <code>np.bincount()</code> of both axis, and repeatedly rolling it until they align. The result of which is saved in a function that apply the same operation to both the -delta history from the database.</p>\n<p>With enough runtime over millions of boards, this approach was able to achieve 13.5% success rate.</p>\n<p>This approach is wasteful as many boards contain small self-contained <a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-repeating-patterns\" target=\"_blank\">Repeating Patterns</a> with known timelines. If two or more of these shapes appear on the board, then the hashmap database would need to contain every possible permutation of combinations at every distance. </p>\n<p>Image segmentation simplifies this by being able to extract out such self-contained shapes and then solve them individually. To make <code>skimage.measure.label()</code> work with diagonal neighbours, it needs to be combined with both <code>scipy.ndimage.convolve()</code> as well as tessellation/de-tessellation to catch wraparound shapes on the image border.</p>\n<p>This approach, however, results in a respectable 56.9% accuracy and is mostly stumped by the high cell count boards, which it often reads as a single large cluster of cells that most likely has not seen before.</p>\n<p>The <a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver/\" target=\"_blank\">Image Segmentation Solver</a> also executes within a single notebook run, which is much quicker than my previous 10,000+ CPU hour Z3 solution.</p>\n<h1>Neural Networks - Forward Play</h1>\n<ul>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/pytorch-game-of-life-first-attempt\" target=\"_blank\">Pytorch Game of Life - First Attempt</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/pytorch-game-of-life-hardcoding-network-weights\" target=\"_blank\">Pytorch Game of Life - Hardcoding Network Weights</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/its-easy-for-neural-networks-to-learn-game-of-life\" target=\"_blank\">Its Easy for Neural Networks To Learn Game of Life</a></li>\n</ul>\n<p>I was able to fully solve the forward game of life problem using a neural network, and managed to train my first network to 100% accuracy. After reading the scientific paper <a href=\"https://arxiv.org/abs/2009.01398\" target=\"_blank\">It's Hard for Neural Networks To Learn the Game of Life</a> I wrote a tutorial on how to hardcode neural network weights and then wrote a response to their paper.</p>\n<p>Small neural networks are often hard or unreliable to train because there is limited dimensionality and it is easy to get stuck in a saddle point between different solutions. However I discovered that this effect is reversed if you create a neural network that has only a single mathematical solution. Such is the case with a 10 neuron network (9 inputs + 1 output) which trains with a 90% success rate compared to the 60% success rate to the 12 neuron network (9 inputs + 2 middle + 1 output) which actually has 3 mathematical solutions.  </p>\n<h1>Neural Networks - Reverse Problem</h1>\n<ul>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/ouroboroslife-function-reversal-gan\" target=\"_blank\">OuroborosLife - Function Reversal GAN</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-rnn\" target=\"_blank\">Reverse Game of Life RNN</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-inception-cnn\" target=\"_blank\">Reverse Game of Life Inception CNN</a></li>\n</ul>\n<p>I experimented with lots of different architectures and learned a lot about neural network design, however, this was somewhat frustrating as whilst I could see some shape patterns start to converge, I never achieved any high scores solving the reverse problem using this approach.</p>\n<p>Common problems included having the network converge on solutions such as all 0s, all 1s or all ~0.5s.</p>\n<p>At one point I did see 99% accuracy reported in log files training on a 5x5 board, but I forgot to save the model file and was never able to reproduce the results. Maybe this was a case of lottery ticket initialization.</p>\n<p>I had high hopes for my Ouroboros Function Reversal GAN. The network was designed to have multiple heads predicting past, present and future for a given input board state. The loss function then passed the output of the Past back into the network to compare accuracy against it's predicted Future. However, it scored 0.31613 on the leaderboard, which is twice as bad as the sample submission. </p>\n<p>Inception CNN combined convolutions of multiple sizes, along with a \"count neighbors\" convolutions with hardcoded weights. The RNN CNN had the network output an additional state vector, which could then be fed back into the network along with forward play of its output. </p>\n<p>Still unsure if neural networks are just fundamentally unsuited to this type of problem, or if I was doing something obviously wrong in my neural network design.</p>\n<h1>XGboost, Random Forest and Decision Trees</h1>\n<ul>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-decision-tree?scriptVersionId=46840547\" target=\"_blank\">https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-decision-tree?scriptVersionId=46840547</a></li>\n</ul>\n<p>I was quite impressed with what XGboost was able to do on the Abstraction and Reasoning Challenge.</p>\n<ul>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/arc-oo-framework-xgboost-multimodel-solvers\" target=\"_blank\">https://www.kaggle.com/jamesmcguigan/arc-oo-framework-xgboost-multimodel-solvers</a></li>\n</ul>\n<p>Unfortunately, XGBoost doesn't support multi-class outputs, so I switched it out for a Random Forest.</p>\n<p>The first nieve attempt of just giving it an entire board and asking it to predict the output scored 0.14689 which was better than the sample submission. </p>\n<p>I suspect the main problem here is that a Random Forest has no knowledge of rotational and translational symmetry, thus is dealing with very sparse training data for when a specific pattern has appeared in a given location.</p>\n<p>I thought about this for a bit, and came up with another idea. Reformat the dataset to give a pixelwise view of the board, N nearest neighbors for each cell, and then try to predict each cell individually. The prediction could then be run through forward play and fed back into the network (much like the RNN CNN design).</p>\n<p>I didn't get very far with this approach, mostly because it exceeded the 9 hour time limit for notebook execution, even when I replaced Random Forest with a much quicker Decision Tree and limited the dataset only to delta=1. Never got to submit this approach.</p>\n<h1>Summary</h1>\n<p>In theory, the Z3 solver should have been able to get a perfect score, but the runtime was far too excessive, some boards would have individually taken longer than 9 hours to solve, and I ran into my fair share of notebook timeouts and out of memory errors. </p>\n<p>Image Segmentation Solver scored an almost identical score, which mostly comprised the \"easy\" boards with small self-contained shapes. Geometrically Invariant Hash functions and Summable Primes were new mathematical inventions.</p>\n<p>Neural Networks could easily solve the forward problem, and this was great training in neural network design and implementation but wasn't able to make much progress on the reverse problem using this approach.</p>\n<p>Decision Trees didn't get fully explored.</p>\n<p>As I understand it, most of the top entries on the leaderboard were written in C, as was also the case in the ConnectX competition, so I probably need to start learning Rust if I want to be competitive on Kaggle.</p>\n<p>Overall I learned a huge amount during this competition and had lots of fun.</p>",
  "messages": [
    {
      "id": 1097547,
      "postDate": "2020-12-01T05:05:19.657Z",
      "content": "<p>My approach to this competition was to try a variety of different techniques and publically publish all my ongoing research public throughout the competition, which resulted in a small tail of \"ensemble of public notebook\" behind me on the leaderboard.</p>\n<h1>Results</h1>\n<table>\n<thead>\n<tr>\n<th>score</th>\n<th>duration</th>\n<th>cores</th>\n<th>name</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0.08410</td>\n<td>5000h</td>\n<td>4</td>\n<td><a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction\" target=\"_blank\">Z3 Constraint Satisfaction</a></td>\n</tr>\n<tr>\n<td>0.08631</td>\n<td>8.25h</td>\n<td>4</td>\n<td><a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver\" target=\"_blank\">Image Segmentation Solver</a></td>\n</tr>\n<tr>\n<td>0.14502</td>\n<td>100h</td>\n<td>4</td>\n<td><a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver\" target=\"_blank\">Hashmap Solver</a></td>\n</tr>\n<tr>\n<td>0.14689</td>\n<td>0.5h</td>\n<td>4</td>\n<td><a href=\"https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-decision-tree?scriptVersionId=46840547\" target=\"_blank\">Random Forest</a></td>\n</tr>\n<tr>\n<td>0.14689</td>\n<td>0.5h</td>\n<td>GPU</td>\n<td><a href=\"https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-rnn\" target=\"_blank\">Recursive CNN</a></td>\n</tr>\n<tr>\n<td>0.31613</td>\n<td>1.5h</td>\n<td>GPU</td>\n<td><a href=\"https://www.kaggle.com/jamesmcguigan/ouroboroslife-function-reversal-gan\" target=\"_blank\">OuroborosLife Function Reversal GAN</a></td>\n</tr>\n</tbody>\n</table>\n<h1>Interactive Playable Game</h1>\n<p>I had previously written an interactive playable demo of the forward version of this game in React Javascript:</p>\n<ul>\n<li><a href=\"https://life.jamesmcguigan.com/\" target=\"_blank\">https://life.jamesmcguigan.com/</a></li>\n</ul>\n<h1>Z3 Constraint Satisfaction</h1>\n<ul>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction\" target=\"_blank\">https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction</a></li>\n</ul>\n<p>My first attempt was to use the Z3 SAT solver. </p>\n<p>Design-wise this was implemented as a 3d grid of cells going back delta steps. Game of Life forward rules were added as constraints, along with the stop board position and a no empty board constraint.</p>\n<p>As a performance optimization, an additional constraint was added that any dead cell with no neighbors was also dead in the previous time step. This helped prevent an infinite number of zero-point energy solutions of cells from appearing and disappearing in a sea of whitespace. However, this caused unsat on a few boards which required a distance of 2 whitespace. Z3 push and pop were used to check for the distance=1 constraint, then replace it with distance=2 upon unsat.</p>\n<p>I also discovered that using boolean rather than integer logic resulted in a 2-4x performance speedup.</p>\n<p>The main limitation of this approach was CPU runtime. Larger deltas and high cell counts (with less whitespace) would take exponentially longer. Even with pathos multiprocessing using all 4 CPUs it still took 20,000h of CPU runtime to reach a score of 0.08410 = 29,121/50,000 = 58%. </p>\n<p>Getting 20,000h of runtime on Kaggle required cluster compute with a dataset reimport loop. The codebase was hosted on github, and 9 forks of the notebook were created. The dataset divided by modulo % 9 on the index id. Each notebook fork imported the others as a datasource and the CSV files could be recombined, sorting to remove zeroed entries and awk to ensure unique indexes<br>\nusing:</p>\n<pre><code>!find ./ ../input/ -name 'submission.csv' | xargs cat | sort -nr | uniq | awk -F',' '!a[$1]++' | sort -n &gt; ./submission.csv\n</code></pre>\n<p>Many boards could not be individually solved within the 9h kaggle notebook time limit, so this also required logging timeout errors and skipping them on future runs.</p>\n<p>This process took quite a bit of effort, as I would need to manually rerun 9 notebook forks 2-3 timer per day to reach version 135 of the primary notebook. My estimate is that I used about 5000h x 4 CPUs of Kaggle resources on this competition.</p>\n<p>I did briefly experiment with making the stop position an optimization goal rather than a constraint but after the first execution kept running into memory and timeout errors.</p>\n<h1>Image Segmentation and Hashmap Solvers</h1>\n<ul>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/summable-primes\" target=\"_blank\">Summable Primes</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/geometric-invariant-hash-functions\" target=\"_blank\">Geometric Invariant Hash Functions</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-repeating-patterns\" target=\"_blank\">Game of Life - Repeating Patterns</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-hashmap-solver\" target=\"_blank\">Game of Life - Hashmap Solver</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver\" target=\"_blank\">Game of Life - Image Segmentation Solver</a></li>\n</ul>\n<p>My <a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver/\" target=\"_blank\">Image Segmentation Solver</a> which was inspired by my previous work on the <a href=\"https://www.kaggle.com/jamesmcguigan/arc-geometry-solvers\" target=\"_blank\">ARC Abstraction and Reasoning Corpus Geometry Solvers</a></p>\n<p>Scores are:</p>\n<ul>\n<li>0.08549 = 28,731/50,000 = 57.4% <a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction/\" target=\"_blank\">Z3 Constraint Satisfaction Solver</a> (still running)</li>\n<li>0.08631 = 28,446/50,000 = 56.9% <a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver/\" target=\"_blank\">Image Segmentation Solver</a></li>\n<li>0.14502 = 6,734/50,000 = 13.5% <a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-hashmap-solver/\" target=\"_blank\">Hashmap Solver</a></li>\n</ul>\n<p>The idea is that instead of trying to solve the reverse function, we can simply use forward play over millions of boards to generate a database of known 4d shapes, then create a solver using dictionary lookup based on hash key and delta.</p>\n<p>At first, you might consider the state space for a 25x25 game of life board might be too large (2^625 = 1.3*10^188), but it possible to exploit rotational, mirror, and translation symmetry to significantly reduce this. For this I developed <a href=\"https://www.kaggle.com/jamesmcguigan/geometric-invariant-hash-functions\" target=\"_blank\">Geometrically Invariant Hash Functions</a>. They work by taking a pixel-wise view of the board, extending out from each pixel in concentric circles, and using <a href=\"https://www.kaggle.com/jamesmcguigan/summable-primes\" target=\"_blank\">Summable Primes</a> to multiply the count the number of neighbouring pixels at each distance. The result of this is that the hash function remains unchanged after <code>np.roll()</code>, <code>np.flip()</code> and <code>np.rot90()</code> operations.</p>\n<p>It is also possible to create a translation-insensitive variant of this hash function that only remains unchanged by <code>np.roll()</code> but is affected by <code>np.flip()</code> and <code>np.rot90()</code>. </p>\n<p>For the test dataset, we hash the board and look it up in the database. If a match is found, then the problem is reduced to a much simpler geometry problem. <code>np.roll()</code> and <code>np.flip()</code> can be solved by brute force until the second translation-insensitive hash function aligns. <code>np.roll()</code> can be solved by taking <code>np.bincount()</code> of both axis, and repeatedly rolling it until they align. The result of which is saved in a function that apply the same operation to both the -delta history from the database.</p>\n<p>With enough runtime over millions of boards, this approach was able to achieve 13.5% success rate.</p>\n<p>This approach is wasteful as many boards contain small self-contained <a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-repeating-patterns\" target=\"_blank\">Repeating Patterns</a> with known timelines. If two or more of these shapes appear on the board, then the hashmap database would need to contain every possible permutation of combinations at every distance. </p>\n<p>Image segmentation simplifies this by being able to extract out such self-contained shapes and then solve them individually. To make <code>skimage.measure.label()</code> work with diagonal neighbours, it needs to be combined with both <code>scipy.ndimage.convolve()</code> as well as tessellation/de-tessellation to catch wraparound shapes on the image border.</p>\n<p>This approach, however, results in a respectable 56.9% accuracy and is mostly stumped by the high cell count boards, which it often reads as a single large cluster of cells that most likely has not seen before.</p>\n<p>The <a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver/\" target=\"_blank\">Image Segmentation Solver</a> also executes within a single notebook run, which is much quicker than my previous 10,000+ CPU hour Z3 solution.</p>\n<h1>Neural Networks - Forward Play</h1>\n<ul>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/pytorch-game-of-life-first-attempt\" target=\"_blank\">Pytorch Game of Life - First Attempt</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/pytorch-game-of-life-hardcoding-network-weights\" target=\"_blank\">Pytorch Game of Life - Hardcoding Network Weights</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/its-easy-for-neural-networks-to-learn-game-of-life\" target=\"_blank\">Its Easy for Neural Networks To Learn Game of Life</a></li>\n</ul>\n<p>I was able to fully solve the forward game of life problem using a neural network, and managed to train my first network to 100% accuracy. After reading the scientific paper <a href=\"https://arxiv.org/abs/2009.01398\" target=\"_blank\">It's Hard for Neural Networks To Learn the Game of Life</a> I wrote a tutorial on how to hardcode neural network weights and then wrote a response to their paper.</p>\n<p>Small neural networks are often hard or unreliable to train because there is limited dimensionality and it is easy to get stuck in a saddle point between different solutions. However I discovered that this effect is reversed if you create a neural network that has only a single mathematical solution. Such is the case with a 10 neuron network (9 inputs + 1 output) which trains with a 90% success rate compared to the 60% success rate to the 12 neuron network (9 inputs + 2 middle + 1 output) which actually has 3 mathematical solutions.  </p>\n<h1>Neural Networks - Reverse Problem</h1>\n<ul>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/ouroboroslife-function-reversal-gan\" target=\"_blank\">OuroborosLife - Function Reversal GAN</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-rnn\" target=\"_blank\">Reverse Game of Life RNN</a></li>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-inception-cnn\" target=\"_blank\">Reverse Game of Life Inception CNN</a></li>\n</ul>\n<p>I experimented with lots of different architectures and learned a lot about neural network design, however, this was somewhat frustrating as whilst I could see some shape patterns start to converge, I never achieved any high scores solving the reverse problem using this approach.</p>\n<p>Common problems included having the network converge on solutions such as all 0s, all 1s or all ~0.5s.</p>\n<p>At one point I did see 99% accuracy reported in log files training on a 5x5 board, but I forgot to save the model file and was never able to reproduce the results. Maybe this was a case of lottery ticket initialization.</p>\n<p>I had high hopes for my Ouroboros Function Reversal GAN. The network was designed to have multiple heads predicting past, present and future for a given input board state. The loss function then passed the output of the Past back into the network to compare accuracy against it's predicted Future. However, it scored 0.31613 on the leaderboard, which is twice as bad as the sample submission. </p>\n<p>Inception CNN combined convolutions of multiple sizes, along with a \"count neighbors\" convolutions with hardcoded weights. The RNN CNN had the network output an additional state vector, which could then be fed back into the network along with forward play of its output. </p>\n<p>Still unsure if neural networks are just fundamentally unsuited to this type of problem, or if I was doing something obviously wrong in my neural network design.</p>\n<h1>XGboost, Random Forest and Decision Trees</h1>\n<ul>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-decision-tree?scriptVersionId=46840547\" target=\"_blank\">https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-decision-tree?scriptVersionId=46840547</a></li>\n</ul>\n<p>I was quite impressed with what XGboost was able to do on the Abstraction and Reasoning Challenge.</p>\n<ul>\n<li><a href=\"https://www.kaggle.com/jamesmcguigan/arc-oo-framework-xgboost-multimodel-solvers\" target=\"_blank\">https://www.kaggle.com/jamesmcguigan/arc-oo-framework-xgboost-multimodel-solvers</a></li>\n</ul>\n<p>Unfortunately, XGBoost doesn't support multi-class outputs, so I switched it out for a Random Forest.</p>\n<p>The first nieve attempt of just giving it an entire board and asking it to predict the output scored 0.14689 which was better than the sample submission. </p>\n<p>I suspect the main problem here is that a Random Forest has no knowledge of rotational and translational symmetry, thus is dealing with very sparse training data for when a specific pattern has appeared in a given location.</p>\n<p>I thought about this for a bit, and came up with another idea. Reformat the dataset to give a pixelwise view of the board, N nearest neighbors for each cell, and then try to predict each cell individually. The prediction could then be run through forward play and fed back into the network (much like the RNN CNN design).</p>\n<p>I didn't get very far with this approach, mostly because it exceeded the 9 hour time limit for notebook execution, even when I replaced Random Forest with a much quicker Decision Tree and limited the dataset only to delta=1. Never got to submit this approach.</p>\n<h1>Summary</h1>\n<p>In theory, the Z3 solver should have been able to get a perfect score, but the runtime was far too excessive, some boards would have individually taken longer than 9 hours to solve, and I ran into my fair share of notebook timeouts and out of memory errors. </p>\n<p>Image Segmentation Solver scored an almost identical score, which mostly comprised the \"easy\" boards with small self-contained shapes. Geometrically Invariant Hash functions and Summable Primes were new mathematical inventions.</p>\n<p>Neural Networks could easily solve the forward problem, and this was great training in neural network design and implementation but wasn't able to make much progress on the reverse problem using this approach.</p>\n<p>Decision Trees didn't get fully explored.</p>\n<p>As I understand it, most of the top entries on the leaderboard were written in C, as was also the case in the ConnectX competition, so I probably need to start learning Rust if I want to be competitive on Kaggle.</p>\n<p>Overall I learned a huge amount during this competition and had lots of fun.</p>",
      "rawMarkdown": "My approach to this competition was to try a variety of different techniques and publically publish all my ongoing research public throughout the competition, which resulted in a small tail of \"ensemble of public notebook\" behind me on the leaderboard.\n\n# Results\n\n| score | duration  | cores | name |\n| --- | --- | --- | --- |\n| 0.08410 | 5000h | 4 | [Z3 Constraint Satisfaction](https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction) |\n| 0.08631 | 8.25h | 4 | [Image Segmentation Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver)|\n| 0.14502 | 100h | 4 | [Hashmap Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver) | \n| 0.14689 | 0.5h | 4 | [Random Forest](https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-decision-tree?scriptVersionId=46840547) |\n| 0.14689 | 0.5h | GPU | [Recursive CNN](https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-rnn) |\n| 0.31613 | 1.5h | GPU | [OuroborosLife Function Reversal GAN](https://www.kaggle.com/jamesmcguigan/ouroboroslife-function-reversal-gan) | \n\n# Interactive Playable Game\n\nI had previously written an interactive playable demo of the forward version of this game in React Javascript:\n- https://life.jamesmcguigan.com/\n\n\n# Z3 Constraint Satisfaction\n- https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction\n\nMy first attempt was to use the Z3 SAT solver. \n\nDesign-wise this was implemented as a 3d grid of cells going back delta steps. Game of Life forward rules were added as constraints, along with the stop board position and a no empty board constraint.\n\nAs a performance optimization, an additional constraint was added that any dead cell with no neighbors was also dead in the previous time step. This helped prevent an infinite number of zero-point energy solutions of cells from appearing and disappearing in a sea of whitespace. However, this caused unsat on a few boards which required a distance of 2 whitespace. Z3 push and pop were used to check for the distance=1 constraint, then replace it with distance=2 upon unsat.\n\nI also discovered that using boolean rather than integer logic resulted in a 2-4x performance speedup.\n\nThe main limitation of this approach was CPU runtime. Larger deltas and high cell counts (with less whitespace) would take exponentially longer. Even with pathos multiprocessing using all 4 CPUs it still took 20,000h of CPU runtime to reach a score of 0.08410 = 29,121/50,000 = 58%. \n\nGetting 20,000h of runtime on Kaggle required cluster compute with a dataset reimport loop. The codebase was hosted on github, and 9 forks of the notebook were created. The dataset divided by modulo % 9 on the index id. Each notebook fork imported the others as a datasource and the CSV files could be recombined, sorting to remove zeroed entries and awk to ensure unique indexes\nusing:\n\n```\n!find ./ ../input/ -name 'submission.csv' | xargs cat | sort -nr | uniq | awk -F',' '!a[$1]++' | sort -n > ./submission.csv\n``` \n\nMany boards could not be individually solved within the 9h kaggle notebook time limit, so this also required logging timeout errors and skipping them on future runs.\n\nThis process took quite a bit of effort, as I would need to manually rerun 9 notebook forks 2-3 timer per day to reach version 135 of the primary notebook. My estimate is that I used about 5000h x 4 CPUs of Kaggle resources on this competition.\n\nI did briefly experiment with making the stop position an optimization goal rather than a constraint but after the first execution kept running into memory and timeout errors.\n\n# Image Segmentation and Hashmap Solvers\n- [Summable Primes](https://www.kaggle.com/jamesmcguigan/summable-primes)\n- [Geometric Invariant Hash Functions](https://www.kaggle.com/jamesmcguigan/geometric-invariant-hash-functions)\n- [Game of Life - Repeating Patterns](https://www.kaggle.com/jamesmcguigan/game-of-life-repeating-patterns)\n- [Game of Life - Hashmap Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-hashmap-solver)\n- [Game of Life - Image Segmentation Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver)\n\nMy [Image Segmentation Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver/) which was inspired by my previous work on the [ARC Abstraction and Reasoning Corpus Geometry Solvers](https://www.kaggle.com/jamesmcguigan/arc-geometry-solvers)\n\nScores are:\n- 0.08549 = 28,731/50,000 = 57.4% [Z3 Constraint Satisfaction Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction/) (still running)\n- 0.08631 = 28,446/50,000 = 56.9% [Image Segmentation Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver/)\n- 0.14502 = 6,734/50,000 = 13.5% [Hashmap Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-hashmap-solver/)\n\nThe idea is that instead of trying to solve the reverse function, we can simply use forward play over millions of boards to generate a database of known 4d shapes, then create a solver using dictionary lookup based on hash key and delta.\n\nAt first, you might consider the state space for a 25x25 game of life board might be too large (2^625 = 1.3*10^188), but it possible to exploit rotational, mirror, and translation symmetry to significantly reduce this. For this I developed [Geometrically Invariant Hash Functions](https://www.kaggle.com/jamesmcguigan/geometric-invariant-hash-functions). They work by taking a pixel-wise view of the board, extending out from each pixel in concentric circles, and using [Summable Primes](https://www.kaggle.com/jamesmcguigan/summable-primes) to multiply the count the number of neighbouring pixels at each distance. The result of this is that the hash function remains unchanged after `np.roll()`, `np.flip()` and `np.rot90()` operations.\n\nIt is also possible to create a translation-insensitive variant of this hash function that only remains unchanged by `np.roll()` but is affected by `np.flip()` and `np.rot90()`. \n\nFor the test dataset, we hash the board and look it up in the database. If a match is found, then the problem is reduced to a much simpler geometry problem. `np.roll()` and `np.flip()` can be solved by brute force until the second translation-insensitive hash function aligns. `np.roll()` can be solved by taking `np.bincount()` of both axis, and repeatedly rolling it until they align. The result of which is saved in a function that apply the same operation to both the -delta history from the database.\n\nWith enough runtime over millions of boards, this approach was able to achieve 13.5% success rate.\n\nThis approach is wasteful as many boards contain small self-contained [Repeating Patterns](https://www.kaggle.com/jamesmcguigan/game-of-life-repeating-patterns) with known timelines. If two or more of these shapes appear on the board, then the hashmap database would need to contain every possible permutation of combinations at every distance. \n\nImage segmentation simplifies this by being able to extract out such self-contained shapes and then solve them individually. To make `skimage.measure.label()` work with diagonal neighbours, it needs to be combined with both `scipy.ndimage.convolve()` as well as tessellation/de-tessellation to catch wraparound shapes on the image border.\n\nThis approach, however, results in a respectable 56.9% accuracy and is mostly stumped by the high cell count boards, which it often reads as a single large cluster of cells that most likely has not seen before.\n\nThe [Image Segmentation Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver/) also executes within a single notebook run, which is much quicker than my previous 10,000+ CPU hour Z3 solution.\n\n# Neural Networks - Forward Play\n- [Pytorch Game of Life - First Attempt](https://www.kaggle.com/jamesmcguigan/pytorch-game-of-life-first-attempt)\n- [Pytorch Game of Life - Hardcoding Network Weights](https://www.kaggle.com/jamesmcguigan/pytorch-game-of-life-hardcoding-network-weights)\n- [Its Easy for Neural Networks To Learn Game of Life](https://www.kaggle.com/jamesmcguigan/its-easy-for-neural-networks-to-learn-game-of-life)\n\nI was able to fully solve the forward game of life problem using a neural network, and managed to train my first network to 100% accuracy. After reading the scientific paper [It's Hard for Neural Networks To Learn the Game of Life](https://arxiv.org/abs/2009.01398) I wrote a tutorial on how to hardcode neural network weights and then wrote a response to their paper.\n\nSmall neural networks are often hard or unreliable to train because there is limited dimensionality and it is easy to get stuck in a saddle point between different solutions. However I discovered that this effect is reversed if you create a neural network that has only a single mathematical solution. Such is the case with a 10 neuron network (9 inputs + 1 output) which trains with a 90% success rate compared to the 60% success rate to the 12 neuron network (9 inputs + 2 middle + 1 output) which actually has 3 mathematical solutions.  \n\n# Neural Networks - Reverse Problem\n- [OuroborosLife - Function Reversal GAN](https://www.kaggle.com/jamesmcguigan/ouroboroslife-function-reversal-gan)\n- [Reverse Game of Life RNN](https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-rnn)\n- [Reverse Game of Life Inception CNN](https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-inception-cnn)\n\nI experimented with lots of different architectures and learned a lot about neural network design, however, this was somewhat frustrating as whilst I could see some shape patterns start to converge, I never achieved any high scores solving the reverse problem using this approach.\n\nCommon problems included having the network converge on solutions such as all 0s, all 1s or all ~0.5s.\n\nAt one point I did see 99% accuracy reported in log files training on a 5x5 board, but I forgot to save the model file and was never able to reproduce the results. Maybe this was a case of lottery ticket initialization.\n\nI had high hopes for my Ouroboros Function Reversal GAN. The network was designed to have multiple heads predicting past, present and future for a given input board state. The loss function then passed the output of the Past back into the network to compare accuracy against it's predicted Future. However, it scored 0.31613 on the leaderboard, which is twice as bad as the sample submission. \n\nInception CNN combined convolutions of multiple sizes, along with a \"count neighbors\" convolutions with hardcoded weights. The RNN CNN had the network output an additional state vector, which could then be fed back into the network along with forward play of its output. \n\nStill unsure if neural networks are just fundamentally unsuited to this type of problem, or if I was doing something obviously wrong in my neural network design.\n\n\n# XGboost, Random Forest and Decision Trees\n- https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-decision-tree?scriptVersionId=46840547\n\nI was quite impressed with what XGboost was able to do on the Abstraction and Reasoning Challenge.\n- https://www.kaggle.com/jamesmcguigan/arc-oo-framework-xgboost-multimodel-solvers\n\nUnfortunately, XGBoost doesn't support multi-class outputs, so I switched it out for a Random Forest.\n\nThe first nieve attempt of just giving it an entire board and asking it to predict the output scored 0.14689 which was better than the sample submission. \n\nI suspect the main problem here is that a Random Forest has no knowledge of rotational and translational symmetry, thus is dealing with very sparse training data for when a specific pattern has appeared in a given location.\n\nI thought about this for a bit, and came up with another idea. Reformat the dataset to give a pixelwise view of the board, N nearest neighbors for each cell, and then try to predict each cell individually. The prediction could then be run through forward play and fed back into the network (much like the RNN CNN design).\n\nI didn't get very far with this approach, mostly because it exceeded the 9 hour time limit for notebook execution, even when I replaced Random Forest with a much quicker Decision Tree and limited the dataset only to delta=1. Never got to submit this approach.\n\n# Summary\n\nIn theory, the Z3 solver should have been able to get a perfect score, but the runtime was far too excessive, some boards would have individually taken longer than 9 hours to solve, and I ran into my fair share of notebook timeouts and out of memory errors. \n\nImage Segmentation Solver scored an almost identical score, which mostly comprised the \"easy\" boards with small self-contained shapes. Geometrically Invariant Hash functions and Summable Primes were new mathematical inventions.\n\nNeural Networks could easily solve the forward problem, and this was great training in neural network design and implementation but wasn't able to make much progress on the reverse problem using this approach.\n\nDecision Trees didn't get fully explored.\n\nAs I understand it, most of the top entries on the leaderboard were written in C, as was also the case in the ConnectX competition, so I probably need to start learning Rust if I want to be competitive on Kaggle.\n\nOverall I learned a huge amount during this competition and had lots of fun.\n\n\n",
      "votes": 5
    }
  ],
  "comments": [],
  "raw_markdown_by_id": {
    "1097547": "My approach to this competition was to try a variety of different techniques and publically publish all my ongoing research public throughout the competition, which resulted in a small tail of \"ensemble of public notebook\" behind me on the leaderboard.\n\n# Results\n\n| score | duration  | cores | name |\n| --- | --- | --- | --- |\n| 0.08410 | 5000h | 4 | [Z3 Constraint Satisfaction](https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction) |\n| 0.08631 | 8.25h | 4 | [Image Segmentation Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver)|\n| 0.14502 | 100h | 4 | [Hashmap Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver) | \n| 0.14689 | 0.5h | 4 | [Random Forest](https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-decision-tree?scriptVersionId=46840547) |\n| 0.14689 | 0.5h | GPU | [Recursive CNN](https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-rnn) |\n| 0.31613 | 1.5h | GPU | [OuroborosLife Function Reversal GAN](https://www.kaggle.com/jamesmcguigan/ouroboroslife-function-reversal-gan) | \n\n# Interactive Playable Game\n\nI had previously written an interactive playable demo of the forward version of this game in React Javascript:\n- https://life.jamesmcguigan.com/\n\n\n# Z3 Constraint Satisfaction\n- https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction\n\nMy first attempt was to use the Z3 SAT solver. \n\nDesign-wise this was implemented as a 3d grid of cells going back delta steps. Game of Life forward rules were added as constraints, along with the stop board position and a no empty board constraint.\n\nAs a performance optimization, an additional constraint was added that any dead cell with no neighbors was also dead in the previous time step. This helped prevent an infinite number of zero-point energy solutions of cells from appearing and disappearing in a sea of whitespace. However, this caused unsat on a few boards which required a distance of 2 whitespace. Z3 push and pop were used to check for the distance=1 constraint, then replace it with distance=2 upon unsat.\n\nI also discovered that using boolean rather than integer logic resulted in a 2-4x performance speedup.\n\nThe main limitation of this approach was CPU runtime. Larger deltas and high cell counts (with less whitespace) would take exponentially longer. Even with pathos multiprocessing using all 4 CPUs it still took 20,000h of CPU runtime to reach a score of 0.08410 = 29,121/50,000 = 58%. \n\nGetting 20,000h of runtime on Kaggle required cluster compute with a dataset reimport loop. The codebase was hosted on github, and 9 forks of the notebook were created. The dataset divided by modulo % 9 on the index id. Each notebook fork imported the others as a datasource and the CSV files could be recombined, sorting to remove zeroed entries and awk to ensure unique indexes\nusing:\n\n```\n!find ./ ../input/ -name 'submission.csv' | xargs cat | sort -nr | uniq | awk -F',' '!a[$1]++' | sort -n > ./submission.csv\n``` \n\nMany boards could not be individually solved within the 9h kaggle notebook time limit, so this also required logging timeout errors and skipping them on future runs.\n\nThis process took quite a bit of effort, as I would need to manually rerun 9 notebook forks 2-3 timer per day to reach version 135 of the primary notebook. My estimate is that I used about 5000h x 4 CPUs of Kaggle resources on this competition.\n\nI did briefly experiment with making the stop position an optimization goal rather than a constraint but after the first execution kept running into memory and timeout errors.\n\n# Image Segmentation and Hashmap Solvers\n- [Summable Primes](https://www.kaggle.com/jamesmcguigan/summable-primes)\n- [Geometric Invariant Hash Functions](https://www.kaggle.com/jamesmcguigan/geometric-invariant-hash-functions)\n- [Game of Life - Repeating Patterns](https://www.kaggle.com/jamesmcguigan/game-of-life-repeating-patterns)\n- [Game of Life - Hashmap Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-hashmap-solver)\n- [Game of Life - Image Segmentation Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver)\n\nMy [Image Segmentation Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver/) which was inspired by my previous work on the [ARC Abstraction and Reasoning Corpus Geometry Solvers](https://www.kaggle.com/jamesmcguigan/arc-geometry-solvers)\n\nScores are:\n- 0.08549 = 28,731/50,000 = 57.4% [Z3 Constraint Satisfaction Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction/) (still running)\n- 0.08631 = 28,446/50,000 = 56.9% [Image Segmentation Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver/)\n- 0.14502 = 6,734/50,000 = 13.5% [Hashmap Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-hashmap-solver/)\n\nThe idea is that instead of trying to solve the reverse function, we can simply use forward play over millions of boards to generate a database of known 4d shapes, then create a solver using dictionary lookup based on hash key and delta.\n\nAt first, you might consider the state space for a 25x25 game of life board might be too large (2^625 = 1.3*10^188), but it possible to exploit rotational, mirror, and translation symmetry to significantly reduce this. For this I developed [Geometrically Invariant Hash Functions](https://www.kaggle.com/jamesmcguigan/geometric-invariant-hash-functions). They work by taking a pixel-wise view of the board, extending out from each pixel in concentric circles, and using [Summable Primes](https://www.kaggle.com/jamesmcguigan/summable-primes) to multiply the count the number of neighbouring pixels at each distance. The result of this is that the hash function remains unchanged after `np.roll()`, `np.flip()` and `np.rot90()` operations.\n\nIt is also possible to create a translation-insensitive variant of this hash function that only remains unchanged by `np.roll()` but is affected by `np.flip()` and `np.rot90()`. \n\nFor the test dataset, we hash the board and look it up in the database. If a match is found, then the problem is reduced to a much simpler geometry problem. `np.roll()` and `np.flip()` can be solved by brute force until the second translation-insensitive hash function aligns. `np.roll()` can be solved by taking `np.bincount()` of both axis, and repeatedly rolling it until they align. The result of which is saved in a function that apply the same operation to both the -delta history from the database.\n\nWith enough runtime over millions of boards, this approach was able to achieve 13.5% success rate.\n\nThis approach is wasteful as many boards contain small self-contained [Repeating Patterns](https://www.kaggle.com/jamesmcguigan/game-of-life-repeating-patterns) with known timelines. If two or more of these shapes appear on the board, then the hashmap database would need to contain every possible permutation of combinations at every distance. \n\nImage segmentation simplifies this by being able to extract out such self-contained shapes and then solve them individually. To make `skimage.measure.label()` work with diagonal neighbours, it needs to be combined with both `scipy.ndimage.convolve()` as well as tessellation/de-tessellation to catch wraparound shapes on the image border.\n\nThis approach, however, results in a respectable 56.9% accuracy and is mostly stumped by the high cell count boards, which it often reads as a single large cluster of cells that most likely has not seen before.\n\nThe [Image Segmentation Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-image-segmentation-solver/) also executes within a single notebook run, which is much quicker than my previous 10,000+ CPU hour Z3 solution.\n\n# Neural Networks - Forward Play\n- [Pytorch Game of Life - First Attempt](https://www.kaggle.com/jamesmcguigan/pytorch-game-of-life-first-attempt)\n- [Pytorch Game of Life - Hardcoding Network Weights](https://www.kaggle.com/jamesmcguigan/pytorch-game-of-life-hardcoding-network-weights)\n- [Its Easy for Neural Networks To Learn Game of Life](https://www.kaggle.com/jamesmcguigan/its-easy-for-neural-networks-to-learn-game-of-life)\n\nI was able to fully solve the forward game of life problem using a neural network, and managed to train my first network to 100% accuracy. After reading the scientific paper [It's Hard for Neural Networks To Learn the Game of Life](https://arxiv.org/abs/2009.01398) I wrote a tutorial on how to hardcode neural network weights and then wrote a response to their paper.\n\nSmall neural networks are often hard or unreliable to train because there is limited dimensionality and it is easy to get stuck in a saddle point between different solutions. However I discovered that this effect is reversed if you create a neural network that has only a single mathematical solution. Such is the case with a 10 neuron network (9 inputs + 1 output) which trains with a 90% success rate compared to the 60% success rate to the 12 neuron network (9 inputs + 2 middle + 1 output) which actually has 3 mathematical solutions.  \n\n# Neural Networks - Reverse Problem\n- [OuroborosLife - Function Reversal GAN](https://www.kaggle.com/jamesmcguigan/ouroboroslife-function-reversal-gan)\n- [Reverse Game of Life RNN](https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-rnn)\n- [Reverse Game of Life Inception CNN](https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-inception-cnn)\n\nI experimented with lots of different architectures and learned a lot about neural network design, however, this was somewhat frustrating as whilst I could see some shape patterns start to converge, I never achieved any high scores solving the reverse problem using this approach.\n\nCommon problems included having the network converge on solutions such as all 0s, all 1s or all ~0.5s.\n\nAt one point I did see 99% accuracy reported in log files training on a 5x5 board, but I forgot to save the model file and was never able to reproduce the results. Maybe this was a case of lottery ticket initialization.\n\nI had high hopes for my Ouroboros Function Reversal GAN. The network was designed to have multiple heads predicting past, present and future for a given input board state. The loss function then passed the output of the Past back into the network to compare accuracy against it's predicted Future. However, it scored 0.31613 on the leaderboard, which is twice as bad as the sample submission. \n\nInception CNN combined convolutions of multiple sizes, along with a \"count neighbors\" convolutions with hardcoded weights. The RNN CNN had the network output an additional state vector, which could then be fed back into the network along with forward play of its output. \n\nStill unsure if neural networks are just fundamentally unsuited to this type of problem, or if I was doing something obviously wrong in my neural network design.\n\n\n# XGboost, Random Forest and Decision Trees\n- https://www.kaggle.com/jamesmcguigan/reverse-game-of-life-decision-tree?scriptVersionId=46840547\n\nI was quite impressed with what XGboost was able to do on the Abstraction and Reasoning Challenge.\n- https://www.kaggle.com/jamesmcguigan/arc-oo-framework-xgboost-multimodel-solvers\n\nUnfortunately, XGBoost doesn't support multi-class outputs, so I switched it out for a Random Forest.\n\nThe first nieve attempt of just giving it an entire board and asking it to predict the output scored 0.14689 which was better than the sample submission. \n\nI suspect the main problem here is that a Random Forest has no knowledge of rotational and translational symmetry, thus is dealing with very sparse training data for when a specific pattern has appeared in a given location.\n\nI thought about this for a bit, and came up with another idea. Reformat the dataset to give a pixelwise view of the board, N nearest neighbors for each cell, and then try to predict each cell individually. The prediction could then be run through forward play and fed back into the network (much like the RNN CNN design).\n\nI didn't get very far with this approach, mostly because it exceeded the 9 hour time limit for notebook execution, even when I replaced Random Forest with a much quicker Decision Tree and limited the dataset only to delta=1. Never got to submit this approach.\n\n# Summary\n\nIn theory, the Z3 solver should have been able to get a perfect score, but the runtime was far too excessive, some boards would have individually taken longer than 9 hours to solve, and I ran into my fair share of notebook timeouts and out of memory errors. \n\nImage Segmentation Solver scored an almost identical score, which mostly comprised the \"easy\" boards with small self-contained shapes. Geometrically Invariant Hash functions and Summable Primes were new mathematical inventions.\n\nNeural Networks could easily solve the forward problem, and this was great training in neural network design and implementation but wasn't able to make much progress on the reverse problem using this approach.\n\nDecision Trees didn't get fully explored.\n\nAs I understand it, most of the top entries on the leaderboard were written in C, as was also the case in the ConnectX competition, so I probably need to start learning Rust if I want to be competitive on Kaggle.\n\nOverall I learned a huge amount during this competition and had lots of fun.\n\n\n"
  }
}