[脑洞]用神经网络可以玩生命游戏吗?

[脑洞]用神经网络可以玩生命游戏吗?

2020.4.11 Conway教授已经因新冠肺炎离开了人世,感谢您对生命游戏的付出,您的灵魂将在生命游戏中永垂不朽,或许在生命游戏复杂度大海的角落,您还在默默注视着这个世界。


一 脑洞

前几日观看有关“生命游戏”的视频突发奇想,有关生命游戏规则的简介像极了CNN的运作方式,因此在这里开个脑洞来看看,用神经网络是否可以拟合生命游戏的规则,从而自动完成游戏的推演。

有关生命游戏的规则如下:

尺寸为N的空间就有N*N个格子。而每一个格子都可以看成是一个生命体,每个生命都有 两种状态,每一个格子旁边都有邻居格子存在,如果我们把3*3的9个格子构成的正方形看成一个基本单位的话,那么这个正方形中心的格子的邻居就是它旁边的8个格子。

每个格子的生死遵循下面的原则:

1. 如果一个细胞周围有3个细胞为生(一个细胞周围共有8个细胞),则该细胞为生(即该细胞若原先为死,则转为生,若原先为生,则保持不变) 。

2. 如果一个细胞周围有2个细胞为生,则该细胞的生死状态保持不变;

3. 在其它情况下,该细胞为死(即该细胞若原先为生,则转为死,若原先为死,则保持不变)


二 和CNN的联系

这个规则和 卷积层 如出一辙。从我的直觉来看,这个游戏用CNN的语言描述如下:

对于每个位置(stride=1),进行一次3x3的卷积,卷积核类似于下面的情况,用于计算周围8个细胞的生死,每个位置上,输出的值实际上就是邻居的细胞个数。

1 1 1
1 0 1
1 1 1

随后根据邻居个数,得到下一时刻细胞的状态。

具体过程如下:

邻居个数<=1      死
邻居个数==2      不变
邻居个数==3      生
邻居个数>=4      死

这实际上就是一个非线性激活函数。死用-1表示,不变用0表示,生用1表示,长得像一种奇怪的钟形曲线

生命游戏的激活函数


三 上CNN

训练: 网络的训练采用的是5000对生命游戏的图片(20*20),分别是进行生命游戏规则推演前后的图片对,有50次随机开局,每次开局都迭代100代,以得到训练集。希望网络模型可以替代程序,进行生命游戏的推演。

经过一段时间的实验后,我为生命游戏设计了如下的网络结构:

1.总体采用类似于Res Net的形式,因为生命游戏每推演一次,前后变化不大,希望网络学习到残差,这样可能比较好。

2.自己设定了一种新的激活函数,(其实就是sigmoid 的导数,计算方式就是sigmoid*(1-sigmoid)),其形状类似于钟形曲线,模仿原来的“激活函数”。

我的激活函数

3.卷积核一律采用3x3,stride=1,pad=same

4.最后一层用sigmoid激活函数,因为细胞的生死用01表示,这样的激活函数在实验中表现最好。


四 效果

拟合的时候,训练loss下降的相当好,最终我使用程序(原有的生命游戏) 和 神经网络(训练出来的),两个使用同一开局,分别迭代100代后看看结果。

但是和原来的生命游戏还是有微妙的差别.....

左侧为CNN推演的,右侧为程序推演的(答案)

令人震惊,CNN推演中出现了“滑翔机”
有些微妙的不同.....
出现了原来游戏中不可能出现的稳定结构

五 总结

好玩


六 源代码拿去玩

from __future__ import print_function
import keras
from keras.models import Sequential, Model
from keras.layers import Dense, Dropout, Flatten, Input, Activation,add
from keras.layers import Conv2D, MaxPooling2D, Convolution2D,AveragePooling2D
from keras import backend as K
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
import copy
np.set_printoptions(threshold=np.nan)
np.random.seed(42)
session_conf = tf.ConfigProto(intra_op_parallelism_threads=1, inter_op_parallelism_threads=1)
tf.set_random_seed(42)
sess = tf.Session(graph=tf.get_default_graph(), config=session_conf)
K.set_session(sess)
size = 20
# 生命游戏的函数
def life_game(table):
    new_table = copy.copy(table)
    for i in range(0,table.shape[0]):
        for j in range(0,table.shape[1]):
            n = np.sum(table[max(0,i-1):min(table.shape[0]-1,i+2),max(0,j-1):min(table.shape[1]-1,j+2)]) - table[i,j]
            if n==3:
                new_table[i,j] = 1
            elif n==2:
            else:
                new_table[i, j] = 0
    return new_table
def new_game(size,n):
    table = np.zeros([size,size]).astype(np.float32)
    for i in range(n):
        x,y = np.random.randint(size),np.random.randint(size)
        table[x,y] = 1
    return table
def train_data(starts=10,iter=100):
    train_x,train_y = np.zeros([starts*iter,size,size,1]).astype(np.float32),np.zeros([starts*iter,size,size,1]).astype(np.float32)
    for start in range(starts):
        count=0
        game_table = new_game(size, n=100)
        for i in range(iter):
            train_x[count, :, :, 0] = game_table
            game_table = life_game(game_table)
            train_y[count, :, :, 0] = game_table
            count += 1
    return train_x,train_y
def my_activation(x):
    return K.sigmoid(x)*(1-K.sigmoid(x))
train_x,train_y = train_data(starts=50,iter=100)
conv_size = 3
model = Sequential()
input = Input(shape=[size,size,1])
x = Convolution2D(8,1,strides=1,padding="same")(input)
for i in range(5):
    input_x = x
    x = Convolution2D(8,conv_size,strides=1,padding="same",activation=my_activation)(x)
    x = Convolution2D(8, 1, strides=1, padding="same", activation="relu")(x)
    x = add([x,input_x])
out = Convolution2D(1, 1, strides=1, padding="same", activation=keras.activations.sigmoid)(x)
model = Model(input=input,outputs=out)
model.compile(loss=keras.losses.mean_squared_error, optimizer=keras.optimizers.Adam(lr=0.005))
model.summary()
# 训练网络
epochs = 200
a = model.fit(train_x,train_y,batch_size=32,epochs=epochs,verbose=1,shuffle=True)
hist_loss = np.log10(a.history["loss"])
plt.subplot(111)
plt.plot(range(epochs),hist_loss,"-")
plt.savefig("loss.png")
plt.clf()
model.save("best_model")
keras.activations.my_activation = my_activation
model = keras.models.load_model("best_model")
print(model.get_weights())
game_table = new_game(size,n=100)
game_table_model = copy.deepcopy(np.reshape(game_table, [1, size, size, 1]))
for i in range(100):
    print(i)
    fig = plt.figure()
    plt.title(str(i))
    ax = fig.add_subplot(121)
    cax = ax.matshow(game_table_model[0,:,:,0], vmin=0, vmax=1, cmap="gray")