[脑洞]用神经网络可以玩生命游戏吗?
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推演的,右侧为程序推演的(答案)
五 总结
好玩
六 源代码拿去玩
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")