[HN Gopher] Finding Mona Lisa in the Game of Life ___________________________________________________________________ Finding Mona Lisa in the Game of Life Author : mseri Score : 351 points Date : 2021-03-08 11:02 UTC (11 hours ago) (HTM) web link (avinayak.github.io) (TXT) w3m dump (avinayak.github.io) | jansan wrote: | It never fails to amaze me what an infinite number of monkeys | with keyboards are able to achieve if you give them enough time | :) | enriquto wrote: | > Running ~1000 iterations for a 483px wide Mona Lisa on the | google colab GPU runtime only takes around 40 seconds!. Compared | to the CPU version which takes several hours to do the same for a | smaller image | | Isn't this excruciatingly slow? I remember my old 486 computer | did run life at full-screen full-resolution in realtime (probably | 25fps at 800x600 ?) How it has come to that after 20 years? How | can a 20 year old CPU outperform a modern GPU by one order of | magnitude? Is it all fault of lots of useless intermediary | language layers? | xaedes wrote: | Regarding the speed of GoL itself: | | The JAX implementation of GoL he used should be able to do 10 | billion cells / second | | http://www.bnikolic.co.uk/blog/python/jax/2020/04/19/game-of... | | > Speed of execution | | > Looking into speed of execution was not a primary goal, but | in case people are interested initial: study suggests this code | will do about 10 billion cells / second on the Google Colab GPU | runtime. | | For comparision this simple numba optimized GoL propagation | function achieves roughly one billion cells per second on CPU | (i7-9700K): @numba.jit def | propagate_jit(arr, result): h, w = arr.shape | for y in range(1,h-1): for x in range(1,w-1): | result[y,x] = arr[y,x] num_neighbors = ( | arr[y-1,x-1] + arr[y-1,x] + arr[y-1,x+1] + | arr[y,x-1] + arr[y,x] + arr[y,x] + | arr[y+1,x-1] + arr[y+1,x] + arr[y+1,x+1] ) | if num_neighbors == 3: result[y,x] = 1 | elif num_neighbors < 2 or num_neighbors > 3: | result[y,x] = 0 arr = | np.random.randint(0,2,(700,480)) arr2 = arr.copy() | %timeit propagate_jit(arr, arr2) # 333 us +- 2.64 us | per loop (mean +- std. dev. of 7 runs, 1000 loops each) | 700*480*1e6/333 # 1009009009.009009 | | Btw: The not numba optimized implementation will run in 806ms | on my system. The optimization gives a speedup of over 2400. | UncleOxidant wrote: | Not entirely sure what @numba.jit is doing here as I haven't | used numba - is it compiling to native CPU code? Another | approach to speed up GoL would be to use a lookup table. 2^9 | entries. It's kind of like having the sum pre-calculated. | Using the lookup table approach with @numba.jit would | probably be even faster yet. | zbendefy wrote: | probably 99% of time shuffling data around, 1% of time | calculating | Marazan wrote: | That's not 1000 iterations of GoL. That's a thousand iterations | of trying to find the best seed. | enriquto wrote: | I stand corrected. The numbers were too way off to make sense | at all! | [deleted] | 7373737373 wrote: | For a similar challenge: https://en.wikipedia.org/wiki/Proof_game | xirbeosbwo1234 wrote: | This is of something I've often seen in GPU-accelerated code: | people get really amazing speedups _because they 're comparing to | something that started out dog slow_. Every example given is 10 | generations or fewer, so 1000 iterations is probably not more | than 10,000 generations. That should _not_ take several hours to | run. | | The code looks reasonable enough to me, so I assume this is just | Python being Python. | davguerrero wrote: | This could be useful using least significant bit steganography to | embed the start state and maybe a QR code as the end state, or | something else able to withstand the noisy output. | lubesGordi wrote: | So my understanding is that the Game of Life is an undecidable | system, so you basically can't write a solvable system of | equations that will tell you that your initial state will produce | a Mona Lisa. Even after reading the article I don't really | understand what he's doing to make this happen. And especially | after stating that most states are Garden of Eden states! Can | anyone ELI5? | OscarCunningham wrote: | It's a decidable problem to evolve the Game of Life fowrard a | fixed number of generations. The undecidable thing is to | determine the eventual fate of a pattern, e.g. 'does it ever | die out completely?'. Even then you can answer this question | for large classes of patterns, just not all of them. | aflag wrote: | Moreover, it is just like any other code you write. It's | undecidable whether a function you write will ever stop in | the general case, but I'm pretty sure you are able to easily | prove most of your functions are going to finish. | lubesGordi wrote: | Okay well you can 'simulate' the GoL forward and see what | it's going to be in a few fixed number of generations. That's | basically how you do anything with these undecidable systems. | I'm not clear on how he got some state that looks like a Mona | Lisa when most or almost all states that look like a Mona | Lisa are Garden of Eden states, and then from there worked | backwards (I'm not clear on if/how you can go backwards in | GoL). | ris wrote: | I suspect slightly better (or faster) results would be achieved | if instead of comparing against a specific dithering pattern | nominated by the author, they instead compared downscaled | candidate patterns against the greyscale target image. | sethbannon wrote: | This comment may be too meta but this post just made me | appreciate Hacker News so much! Classic creative hacking guided | by nothing but pure curiosity. Love it! | jphoward wrote: | My understanding is Conway's game of life can be done by simply | convolving a 3x3 array of all ones (except a central 0) through | every pixel, and then looking at the results of that convolution | to see if the pixel is dead or alive, e.g. | https://stackoverflow.com/questions/46196346/why-does-my-gam... | | If that's the case, would a nice solution be to model it as a | recurrent neural network, e.g. pytorch or jax, with a single | convolutional layer, with a single kernel, which you don't | backprop to, and then try to minimise to loss with reference to | the target image, by backpropagating to the input image? | | It then becomes highly analogous to Google's deepdream, but | instead of optimising the image for the output class, you | optimise it to the output image. | [deleted] | PartiallyTyped wrote: | What you are suggesting is the idea behind StyleTransfer. Given | two images, A, B, and a neural network F, you repeatedly modify | B using [?] L(F(A),F(B))/[?]B, where L is the loss function, A | contains the style and B is the input image. | andersource wrote: | That's an interesting approach. The problem is that the | function to be applied after the convolution is binary, hence | not differentiable. You could replace it with a soft variation, | my (wild) guess is it would still be difficult to converge: | | * If the transitions are too sharp it will behave like the | stepwise, non-differentiable original | | * If the transitions are too soft, the optimization will | converge to some middle-state compromise that will not behave | as desired when binarized | | But that's just speculation. Also interesting to note that in | the very relevant Kaggle competition [0], top solutions don't | mention differentiable approaches (as far as I've seen, I admit | I haven't looked too deeply). | | [0] https://www.kaggle.com/c/conways-reverse-game-of- | life-2020/d... | jpalomaki wrote: | As a next step: how simple can you make the initial state. Can | you have something that occupies much less space, but then grows | to something that resembles the target image. | fxj wrote: | Isn't this the goal of an image compression tool? Can we have | efficient lossless compression with GoL? That would work with | any data. Would it be better than bzip2 or png? | cnity wrote: | This is a fascinating idea. Using some kind of sufficiently | chaotic (and deterministic) process shared among two people, | one could simply reference a time and region of the state of | that process that contains the data to be sent. | npteljes wrote: | This is I think what cryptographic hashes are meant to | achieve. | PartiallyTyped wrote: | This is part of modern encryption. To be more specific, we | have blocks and a deterministic cryptographically secure | rng, we seed the rng with a public key that is broadcasted, | we then encrypt blocks and then combine them together with | the number produced by the rng and create the next block of | encrypted data. | | You may be wondering, why do we even do this? The answer is | simple, "the image is encrypted, but everyone can see the | penguin". In a less cryptic manner, encrypting blocks | simply maps them from one space to another, but is a | reversible operation and, as any function does, is | deterministic and returns the exact same value every time. | The consequence of this determinism, is that identical | blocks will always produce the same value and thus, the | content is still somewhat visible and in some cases it | could leak information. By combining blocks together along | with a PRNG, we hide the mapping by requiring that the | recipient solves the first block before decrypting further | data and we don't end up leaking information. | jmiskovic wrote: | This is same as giving starting digit in p, right? Not | usable for compression, and encryption folks would file it | under security-by-obscurity. | shawnz wrote: | Imagine the "sufficiently chaotic process" is simply | listing out all the possible data sequences in order, for | example 0 1 10 11 100 etc. | | Then, "referencing a region" of the state is basically the | same as just giving the value. The nth value is just n | itself. | | So clearly that doesn't give you any advantage. Why would | randomizing the data sequences make it any more efficient | on average? | | Although if you specifically choose the ordering of the | sequences so that common sequences are easy to represent | and uncommon sequences are hard to represent, then you | basically have Huffman coding/arithmetic coding. | cnity wrote: | Because you can bias the process to be more likely to | represent data relevant to your domain. | | Note: I'm not in any way a compression expert, so while | it may be obvious to you/others, it's a little | interesting for me to think of compression in this way | (sending parameters to a shared process, instead of | sharing the raw data). | 1-more wrote: | I've often thought of that, a compression thing that says | "start with this small board state, run for so many | generations, go sample this area of the board, and maybe | apply this run-length-encoded binary patch" but computing | that initial state is, uhhhhh, a lot. A huge amount. Too | freaky. But maybe there's a cellular automaton that is easier | to run in reverse to find such a thing? This becomes a | question for the real computer scientists. | altgoogler wrote: | The approach described here is very lossy, and very | inefficient since you have to search a huge number of | permutations to find reasonable results. | | It would be an interesting experiment to implement lossless | compression, but it likely wouldn't achieve high compression | ratios or be nearly as efficient as a purpose built | algorithm. | jerf wrote: | The problem in general with this approach is that the amount | of bits it takes to identify the correct decoded value | exceeds the amount of bits it takes to simply write down the | correct encoded value. | | People have been proposing similar algorithms for the last | few decades. It's really become the "Free energy is being | suppressed by the man!" of compression algorithms. One of the | more popular is "just identify where your value is in pi!", | which fails for that reason. | | One problem is that in order to analyze such algorithms you | really need to count your bits carefully. For instance in the | pi case people sometimes think "well, I just need two | numbers, the distance into pi and the length", but those "two | numbers" may need arbitrarily unbounded numbers of bits to | represent. | | GoL has its own problem which is that if you construct a | "random" state that didn't start "naturally" the odds of it | being a Garden of Eden state are essentially 1. Can you even | call it an "image compression" tool when it can't represent | an image of any pure color _at all_ , let alone in a | compressed format? | alpaca128 wrote: | Exactly, I know first hand that it's easy to believe in | such admittedly creative ideas. But thinking it through was | fun and cleared up my misunderstanding; in the end the | information itself cannot be compressed, one can only | reduce redundancies. And an arbitrarily chosen algorithm | tends to do a really bad job with that on average. | dcanelhas wrote: | Haha that's really creative though. Makes me wonder if | there is anything useful "stored" in pi that could be | represented with, say, 2 64-bit integers. | klyrs wrote: | Further "compression" -- you only want the index of the | first occurrence of a given string. Thus, you create a | table of offsets for each string length, where index 0 is | the first unique string of length n, index 1 is the | second, etc. This allows you to elide the issue that | indices can be arbitrarily large. Of course, the table is | hard to compute for large n, and the resulting | "compression" has indices at worst the size of the | original data... so it's gonna be super efficient at | compressing the bits of pi, and no worse than unity on | average. | ismail261 wrote: | https://bit.ly/3qq4iAX | atum47 wrote: | Some years ago I did a similar thing using genetic algorithm. I | was researching it's use in generative art. | | Here's a video of it in action: https://youtu.be/xgAigVfpIYc | sjg1729 wrote: | The parenthetical "(Consider a loaf of bread, with each slice | being dithered Mona Lisa)." is one of my favorites of any | technical article | matsemann wrote: | Gotta be used for some capture-the-flag or so, where after | running it a clue to the next step is revealed. | | I also wonder how effective this would be as a compression | algorithm (just for the fun of it). Are the black&white spots too | noisily distributed to make it compressable, or better than the | resulting image after running GoL? | | Also interesting that so little data is needed for my brain to | understand it's a picture of the David statue. | alpaca128 wrote: | > I also wonder how effective this would be as a compression | algorithm | | My guess is it would be the opposite of compression in many | cases, due to the number of required cells you need to store. | Using weird, creative ways of encoding data tends to be bad for | compression, and in the very best case you'd have something | that competes with JPEG but is much slower. | | It's an enticing idea, just like that concept of encoding | arbitrary data by looking for the exact same bit pattern in | decimal places of Pi and then storing that position. But | reality is often disappointing, and it doesn't really work | because more often than not you'll need more space to store the | decimal position than the original information. | lnyan wrote: | A similar post can be found here (2020, implemented with | backsearch): | | https://news.ycombinator.com/item?id=22552006 | | https://kevingal.com/blog/mona-lisa-gol.html | andersource wrote: | Kaggle recently hosted a very relevant competition - "Conway's | Reverse Game of Life" [0]. A write-up of the 1st place might be | an interesting read [1]. | | [0] https://www.kaggle.com/c/conways-reverse-game-of- | life-2020/o... | | [1] https://www.kaggle.com/c/conways-reverse-game-of- | life-2020/d... | bondarchuk wrote: | Very nice! Some states have multiple ancestors, some have none, | so these are "dead ends" when going backwards. But when you are | just looking for an approximation of the final state, as in | TFA, you can probably accept quite some perturbation, which | could make it significantly easier to go backwards in a naive | way and sometimes miss a few cells in the process. | andersource wrote: | Yeah, I think it's very interesting because there's a lot of | freedom in measuring the similarity and also other aspects of | the process, for example optimizing for a "simple" starting | configuration as mentioned in other comments. | UncleOxidant wrote: | > Some states have multiple ancestors, some have none | | Yeah, this is what I didn't understand about that particular | Kaggle competition. It doesn't seem possible that you could | learn some general set of rules that would allow you to | predict previous states with much accuracy. | UncleOxidant wrote: | I read the writeup where someone applied a genetic algorithm to | this problem and I guess what I found disappointing was that it | wasn't a general solution that could learn any general cases or | rules, it was just very specific for each example provided. | It's not like you could train it on a set of examples and then | give it a new example and it would be able to do it any quicker | or more accurately. | adenozine wrote: | Well, that's sort of the case for genetic algorithms. | | You might be aware of genetic programming? There may be | papers already out there, but the idea is representing a | programs source as a genome, and then performing the familiar | processes (mutation, crossover, selection) on those. | | Anyhow, that would produce something more general. A genetic | algorithm is traditionally a single shot thing. | UncleOxidant wrote: | > You might be aware of genetic programming? | | Actually been playing with Cartesian Genetic Programming | (CGP) recently. | | > Anyhow, that would produce something more general. | | Not really, at least as far as I can tell with CGP. I was | evolving a circuit to solve the 8-bit parity problem (kind | of the 'Hello world' of CGP - it solves this problem | surprisingly quickly). There wasn't any way to use that | information to evolve a circuit to solve the 16-bit parity | problem - you've gotta start again from scratch. There may | be some proposed solutions to this kind of issue in CGP - | I'm still reading papers. | ineiti wrote: | Did you consider something like the following to speed up the | calculation? This method can go forward _really_ fast. Not sur e | if it can also go backward... | | https://pzemtsov.github.io/2015/04/24/game-of-life-hash-tabl... | ineiti wrote: | In fact I wanted to point to the following... Googling 'hash' | put me to the wrong page. | | https://en.wikipedia.org/wiki/Hashlife | montebicyclelo wrote: | I did the same thing, but with gradient descent. You can create a | soft version of the game of life, that is differentiable. Here is | my messy collab notebook: [1] | | [1] | https://colab.research.google.com/drive/12CO3Y0JgCd3DVnQeNSB... | | Edit: only 1 step though, not 4, as in the OP. I couldn't get my | differentiable version to converge more than 1 step into the | past. | hardmath123 wrote: | See also, a post from mid-2020 that does something similar with | a "softened" Life: http://hardmath123.github.io/conways- | gradient.html | montebicyclelo wrote: | That's a really nice write up. It's insane how similar our | approaches are. | | Could it be a case of [1] (but on a non grand scale) :P? I | can list my sources of inspiration: [2] [3] [4]. | | I also tried training convolutional networks, using the soft | life set-up, but failed to get them to converge. | | [1] https://en.wikipedia.org/wiki/Multiple_discovery | | [2] https://kevingal.com/blog/mona-lisa-gol.html | | [3] https://arxiv.org/abs/1910.00935 | | [4] https://nicholasrui.com/2017/12/18/convolutions-and-the- | game... | montebicyclelo wrote: | Note, skip to the bottom to see the resulting plots. | | Here gradient descent is used to try and predict random game of | life games [1] | | [1] https://colab.research.google.com/drive/1NKWRarxM- | ar18x1ON71... | hardmath123 wrote: | Similar post from mid-2020, using PyTorch instead of JAX, and | using a "continuous" (gradient-based) hill-climbing: | http://hardmath123.github.io/conways-gradient.html | | And the HN discussion from the time: | https://news.ycombinator.com/item?id=23095190 ___________________________________________________________________ (page generated 2021-03-08 23:00 UTC)