{
  "id": 198141,
  "title": "38th Place Solution - Image Segmentation Solver",
  "url": "/competitions/conways-reverse-game-of-life-2020/discussion/198141",
  "author_name": "",
  "post_date": "2020-11-19T23:53:38.031212300Z",
  "votes": 5,
  "comment_count": 2,
  "views": 0,
  "content": "<p>Whilst my <a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction/\" target=\"_blank\">Z3 Constraint Satisfaction Solver</a> has gotten quite a bit of attention, despite its 10,000+ CPU hour runtime, I've discovered a new approach that produces an almost equivalent score.</p>\n<p>This is 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>",
  "messages": [
    {
      "id": "1084325",
      "postDate": "11/19/2020 23:53:38",
      "content": "<p>Whilst my <a href=\"https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction/\" target=\"_blank\">Z3 Constraint Satisfaction Solver</a> has gotten quite a bit of attention, despite its 10,000+ CPU hour runtime, I've discovered a new approach that produces an almost equivalent score.</p>\n<p>This is 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>",
      "rawMarkdown": "Whilst my [Z3 Constraint Satisfaction Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction/) has gotten quite a bit of attention, despite its 10,000+ CPU hour runtime, I've discovered a new approach that produces an almost equivalent score.\n\nThis is my [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.",
      "votes": null
    },
    {
      "id": "1084397",
      "postDate": "11/20/2020 02:12:37",
      "content": "<p>Nice solution, I had high hopes, but there are always limitations.</p>",
      "rawMarkdown": "Nice solution, I had high hopes, but there are always limitations.",
      "votes": null
    },
    {
      "id": "1097549",
      "postDate": "12/01/2020 05:08:16",
      "content": "<p>In the final leaderboard rankings, this notebook would have placed joint 38th </p>",
      "rawMarkdown": "In the final leaderboard rankings, this notebook would have placed joint 38th",
      "votes": null
    }
  ],
  "comments": [
    {
      "id": 1084397,
      "author_name": "eladwar",
      "author_url": "",
      "post_date": "11/20/2020 02:12:37",
      "content": "<p>Nice solution, I had high hopes, but there are always limitations.</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 1097549,
      "author_name": "jamesmcguigan",
      "author_url": "",
      "post_date": "12/01/2020 05:08:16",
      "content": "<p>In the final leaderboard rankings, this notebook would have placed joint 38th </p>",
      "votes": null,
      "replies": []
    }
  ],
  "raw_markdown_by_id": {
    "1084325": "Whilst my [Z3 Constraint Satisfaction Solver](https://www.kaggle.com/jamesmcguigan/game-of-life-z3-constraint-satisfaction/) has gotten quite a bit of attention, despite its 10,000+ CPU hour runtime, I've discovered a new approach that produces an almost equivalent score.\n\nThis is my [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.",
    "1084397": "Nice solution, I had high hopes, but there are always limitations.",
    "1097549": "In the final leaderboard rankings, this notebook would have placed joint 38th"
  },
  "source": "meta"
}