{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Conway's Reverse Game of Life 2020\n\nThe Game of Life is a cellular automaton created by mathematician John Conway in 1970. The game consists of a board of cells that are either on or off. One creates an initial configuration of these on/off states and observes how it evolves. There are **four simple rules** to determine the next state of the game board, given the current state:\n\n- **Overpopulation:** if a living cell is surrounded by more than three living cells, it dies.\n\n- **Stasis:** if a living cell is surrounded by two or three living cells, it survives.\n\n- **Underpopulation:** if a living cell is surrounded by fewer than two living cells, it dies.\n\n- **Reproduction:** if a dead cell is surrounded by exactly three cells, it becomes a live cell."},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\nprint(\"hello world\")\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Initial Exploration - Data Set\n\nThe data consists of 50.000 games on a 25x25 board (625 locations). Each line is a game with the first column as game id and the second the delta (time steps between start set up and final/stop board). The next 625 columns represent each position in the start matrix and the last 625 each position in the stop one.  "},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"train_ds = pd.read_csv(\"../input/conways-reverse-game-of-life-2020/train.csv\")\ntrain_ds.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_ds.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_ds.describe()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"A sample of how the board looks like"},{"metadata":{"trusted":true},"cell_type":"code","source":"fig= plt.figure()\n\nstart = train_ds.iloc[5,2:625+2]\nmat_start = np.array(start).reshape(25,25)\nfig.add_subplot(1, 2, 1)\nplt.imshow(mat_start), plt.title('START MATRIX')\n\n\nstop = train_ds.iloc[5,625+2:]\nmat_stop= np.array(stop).reshape(25,25)\nfig.add_subplot(1, 2, 2)\nplt.imshow(mat_stop), plt.title('STOP MATRIX')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Looking for differences in the start and stop matrixes. We sum each matrix to get the total value of cells/ones and see if there is any difference.\nBoth distributions are fairly similar, but the stop board has slighly less cells/ones."},{"metadata":{"trusted":true},"cell_type":"code","source":"start = train_ds.iloc[:,:2]\nstart['Sum'] = train_ds.iloc[:,2:627].sum(axis=1)\n\nstop = train_ds.iloc[:,:2]\nstop['Sum'] = train_ds.iloc[:,627:].sum(axis=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print('START')\nprint('mean= ' + str(start['Sum'].mean()))\nprint('std= ' + str(start['Sum'].std()))\nprint('STOP')\nprint('mean= ' + str(stop['Sum'].mean()))\nprint('std= ' + str(stop['Sum'].std()))\n\nfig = plt.figure(figsize=(15,15))\n\n#START\nfig.add_subplot(3, 2, 1)\n#Histogram\nsns.distplot( start['Sum'], bins=15)\nplt.title('START')\n\n#Boxplot\nfig.add_subplot(3, 2, 3)\nsns.boxplot( y=start['Sum'] )\n\n#Violin\nfig.add_subplot(3, 2, 5)\nsns.violinplot( y=start['Sum'] )\n\n#STOP\nfig.add_subplot(3, 2, 2)\n#Histogram\nsns.distplot( stop['Sum'], bins=15)\nplt.title('STOP')\n\nfig.add_subplot(3, 2, 4)\n#Boxplot\nsns.boxplot( y=stop['Sum'] )\n\nfig.add_subplot(3, 2, 6)\n#Violin\nsns.violinplot( y=stop['Sum'] )","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now we look into the difference of cells between start and stop boards. \nWe clearly see again, that stop boards have less cells alive. In general we see three patterns:\n\n1) As we could expect, the start #cells highly influences the stop #cells\n\n2) The more the delta the more the variance on the relation start/stop #cells\n\n3) The more the start #cells the more the variance on the relation"},{"metadata":{"trusted":true},"cell_type":"code","source":"relation = start.copy()\nrelation = relation.rename(columns={'Sum':'Start_Sum'})\nrelation['Stop_Sum'] = stop['Sum']\nrelation['Diff'] = abs(relation['Start_Sum'] - relation['Stop_Sum'])\n\nplt.figure(figsize=(15,7))\nsns.violinplot( x= relation['delta'],y=relation['Diff']  )\ncmap = plt.cm.Spectral\nfig,axes = plt.subplots(nrows=1, ncols=5, figsize=(30, 10))\nfor i,delta in enumerate(np.sort(relation['delta'].unique())):\n    delta_condition=relation['delta']==delta\n    sns.regplot(x=relation['Start_Sum'][delta_condition], y=relation['Stop_Sum'][delta_condition],ax=axes[i], scatter=False)\n    sns.scatterplot(data=relation[delta_condition],x=relation['Start_Sum'][delta_condition], y=relation['Stop_Sum'][delta_condition],ax=axes[i], hue=relation['Diff'][delta_condition])\n    ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Deep Learning model\nNow we train a DL model to predict the start state given the stop board. (It is the reverse game of life)"},{"metadata":{"trusted":true},"cell_type":"code","source":"import tensorflow as tf\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras.layers import Dense, Flatten, Conv2D\nfrom tensorflow.keras import Model\n\nstart = train_ds.iloc[:,2:625+2]\nstop = train_ds.iloc[:,625+2:]\n\n#For now I am only considering delta=1\ny = np.array(start[train_ds['delta']==1])\nx = np.array(stop[train_ds['delta']==1])\n\nx= x.reshape(np.shape(x)[0],25,25,1)\n#y= y.reshape(np.shape(y)[0],25,25,1)\n\nx_train, x_test, y_train, y_test = train_test_split(x,y,test_size=0.3)\n\n#Some sanity checks\nprint(np.shape(x_train))\nprint(np.shape(y_train))\nprint(np.shape(y_test))\n\nprint(x_train[:3].reshape(3,25,25))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Simple model\nmodel = tf.keras.models.Sequential([\n    Conv2D(2, 3, activation='relu', dilation_rate=2, input_shape=(25,25,1), padding='same'),\n    Conv2D(1, 3, activation='relu', dilation_rate=2, input_shape=(25,25,1), padding='same'),\n    Flatten()#,\n    #Dense(625, input_dim=625, activation='sigmoid')\n  ])\n\nmodel.compile(optimizer='adam',\n              loss='binary_crossentropy'\n             )\n\nmodel.fit(x_train, y_train,validation_data=(x_test,y_test) , epochs=150, batch_size=32)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.summary()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Results data analysis\n\nWe plot some samples to check the results. In this case we define the treshold as 0.5 but we will explore further to get the best value.\nWe get an **accuracy of 83.4%**.\n\nWe will differentiate between accuracy on predicting alive and dead cells. As there many more dead cells that alive it is easy to predict an output of all dead cells and get fairly good results."},{"metadata":{"trusted":true},"cell_type":"code","source":"y_pred = model.predict(x_test)\n\npred = np.where(y_pred > 0.5, 1, 0)\n\nfig = plt.figure(figsize=(15,15))\n\nax1=fig.add_subplot(1,3,1)\nax1.imshow(x_test[0].reshape(25,25))\nplt.title('START')\n\nax2 = fig.add_subplot(1,3,2)\nax2.imshow(pred[0].reshape(25,25))\nplt.title('PREDICTION')\n\nax3 = fig.add_subplot(1,3,3)\nax3.imshow(y_test[0].reshape(25,25))\nplt.title('GROUND TRUTH')\n\nprint(np.shape(pred))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"pred = np.where(y_pred > 0.5, 1, 0)\n\naccuracy_1 = []\naccuracy_0 = []\nfor i,prediction in enumerate(pred):\n    accuracy_1.append(np.sum(prediction[y_test[i] == 1] == y_test[i][y_test[i] == 1])/len(y_test[i][y_test[i] == 1]))\n    accuracy_0.append(np.sum(prediction[y_test[i] == 0] == y_test[i][y_test[i] == 0])/len(y_test[i][y_test[i] == 0]))\n    acc = (np.sum(prediction[prediction == 1] == y_test[i][y_test[i] == 1])/len(y_test[i][y_test[i] == 1]))\n    \n    #print(str(len(y_test[i][y_test[i] == 1])) +' - ' + str(len(prediction[prediction == 1])) + ' - ' + str(acc))\n#print(str(prediction[y_test[i] == 1]) + ' - ' + str(y_test[i][y_test[i] == 1]))\nprint(sum(accuracy_1)/len(accuracy_1))\nprint(sum(accuracy_0)/len(accuracy_0))\n\nprint( (sum(accuracy_1)/len(accuracy_1) + sum(accuracy_0)/len(accuracy_0) )/2.0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Our output is a probability of [0,1] of a cell being alive. We plot the difference between our probability and the ground truth for dead and alive cells.\n\nWith this, we see that the treshold value for a cell being considerd alive is close to 0.2 not to the \"usual\" 0.5. There are also many more cells dead than alive."},{"metadata":{"trusted":true},"cell_type":"code","source":"diff = y_test-y_pred\n#print(sum(diff)/len(diff))\n\nsns.distplot( diff[y_test==1], label='Alive [1]')\n\nsns.distplot( abs(diff[y_test==0]), label='Dead [0]')\nplt.title('Diff of Ground truth - probability')\nplt.legend()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now we will se how changing the treshold changes the accuracy"},{"metadata":{"trusted":true},"cell_type":"code","source":"\ntresh = np.arange(0,1,0.01)\nacc1 = []\nacc0 = []\nacc = []\nfor treshold in tresh:\n    pred = np.where(y_pred > treshold, 1, 0)\n    \n    accuracy_1 = []\n    accuracy_0 = []\n    for i,prediction in enumerate(pred):\n        accuracy_1.append(np.sum(prediction[y_test[i] == 1] == y_test[i][y_test[i] == 1])/len(y_test[i][y_test[i] == 1]))\n        accuracy_0.append(np.sum(prediction[y_test[i] == 0] == y_test[i][y_test[i] == 0])/len(y_test[i][y_test[i] == 0]))\n\n    acc1.append(sum(accuracy_1)/len(accuracy_1))\n    acc0.append(sum(accuracy_0)/len(accuracy_0))\n\n    acc.append( (sum(accuracy_1)/len(accuracy_1) + sum(accuracy_0)/len(accuracy_0) )/2.0)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sns.scatterplot(x=tresh, y = acc1)\nsns.scatterplot(x=tresh, y = acc0)\nsns.lineplot(x=tresh, y = acc)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"pred = np.where(y_pred > 0.3, 1, 0)\n\naccuracy_1 = []\naccuracy_0 = []\nfor i,prediction in enumerate(pred):\n    accuracy_1.append(np.sum(prediction[y_test[i] == 1] == y_test[i][y_test[i] == 1])/len(y_test[i][y_test[i] == 1]))\n    accuracy_0.append(np.sum(prediction[y_test[i] == 0] == y_test[i][y_test[i] == 0])/len(y_test[i][y_test[i] == 0]))\n    acc = (np.sum(prediction[prediction == 1] == y_test[i][y_test[i] == 1])/len(y_test[i][y_test[i] == 1]))\n    \n    #print(str(len(y_test[i][y_test[i] == 1])) +' - ' + str(len(prediction[prediction == 1])) + ' - ' + str(acc))\n#print(str(prediction[y_test[i] == 1]) + ' - ' + str(y_test[i][y_test[i] == 1]))\nprint(sum(accuracy_1)/len(accuracy_1))\nprint(sum(accuracy_0)/len(accuracy_0))\n\nprint( (sum(accuracy_1)/len(accuracy_1) + sum(accuracy_0)/len(accuracy_0) )/2.0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We have increased our global accuracy by 12pp only with the treshold setting. "},{"metadata":{"trusted":true},"cell_type":"code","source":"test_ds = pd.read_csv('../input/conways-reverse-game-of-life-2020/test.csv')\ntest_ds.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_ds.iloc[:,2:].head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"predictions = np.array([])\nfor delta in np.flip(np.arange(1,6)):\n    print(delta)\n    if np.shape(predictions)[0] >0: \n        a = np.concatenate((predictions,np.array(test_ds.iloc[:,2:][test_ds['delta']==delta])),axis=0)\n    else:\n        a=np.array(test_ds.iloc[:,2:][test_ds['delta']==delta])\n    a = a.reshape(np.shape(a)[0],25,25,1)\n    print(np.shape(a))\n    predictions = model.predict(a)\n    predictions = np.where(predictions > 0.3, 1, 0)\n    \n\nsample_submission = pd.read_csv('../input/conways-reverse-game-of-life-2020/sample_submission.csv', index_col='id')\nsample_submission.iloc[:] = predictions\nsample_submission.to_csv('submission.csv')\nsample_submission.to_csv('submission3.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_submission","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}