{"id":1493,"date":"2023-09-26T21:02:34","date_gmt":"2023-09-26T19:02:34","guid":{"rendered":"http:\/\/blog.schmoigl-online.de\/?p=1493"},"modified":"2023-09-26T23:21:04","modified_gmt":"2023-09-26T21:21:04","slug":"tic-tac-toe-and-ai-a-winning-board","status":"publish","type":"post","link":"http:\/\/blog.schmoigl-online.de\/?p=1493","title":{"rendered":"Tic-Tac-Toe and AI: A Winning Board (Part 2)"},"content":{"rendered":"\n<p>As a first learning task for using AI with Tic-Tac-Toe, let us take the task of determining whether a board is a winning one or not (i.e. either X or O has won the game). We cannot directly tell the neural network what the rules are for an assignment to be winning. Instead, we need to train it by examples. For that we already have prepared some data in a <a href=\"http:\/\/blog.schmoigl-online.de\/?p=1475\">previous post<\/a>.<\/p>\n\n\n\n<p>The idea is a to train a TensorFlow model with several Dense layers. By varying the model&#8217;s configuration, we want to determine how complex (e.g. how many parameters we need) such a model needs to be to fulfill this requirement. Also looking at the accuracy will be an interesting topic.<\/p>\n\n\n\n<p>As an information in advance: The computation were done using TensorFlow 2.13.1 on a Windows WSL2 machine having an NVIDIA Geforce RTX 4060 (8GB) installed. <a href=\"https:\/\/www.tensorflow.org\/guide\/mixed_precision\">Mixed Precision<\/a> was not enabled.<\/p>\n\n\n\n<p>As usual you may download the entire example using the following link:<\/p>\n\n\n<p><img decoding=\"async\" src=\"http:\/\/blog.schmoigl-online.de\/wp-content\/plugins\/wp-downloadmanager\/images\/ext\/zip.gif\" alt=\"\" title=\"\" style=\"vertical-align: middle;\" \/>&nbsp;&nbsp;<strong><a href=\"http:\/\/blog.schmoigl-online.de\/?dl_id=15\">tictactoetf.zip<\/a><\/strong> (9.6 KiB, 1,132 hits)<\/p>\n\n\n\n<p>Let&#8217;s get started&#8230;<\/p>\n\n\n\n<p>[continued on the next page]<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">Reading Data for Training and Evaluation<\/h2>\n\n\n\n<p>First of all, we need to read the data for training and evaluation. In the preparation blog post, we already created the test data file <code>tictactoe_valid.txt<\/code> for that. We will reuse the data loader for further cases as well, that is why we build it a little generic using <a href=\"https:\/\/pythonhosted.org\/pyrecord\/index.html\">Pyrecord<\/a>s.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import gzip\nfrom pyrecord import Record # https:\/\/pythonhosted.org\/pyrecord\/\n\nimport itertools<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>TTTRecord = Record.create_type(\"TTTRecord\", \"vector\", \"valid\", \"winning\", \"winner\", \"move\")<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>def tttRecordGenerator():\n    with open(\"tictactoe_valid.txt\", \"rt\") as f:\n        line = f.readline()\n        while line:\n            # print (line)\n            vector = &#091;int(line&#091;0]), int(line&#091;1]), int(line&#091;2]), int(line&#091;3]), int(line&#091;4]), int(line&#091;5]), int(line&#091;6]), int(line&#091;7]), int(line&#091;8])]\n            valid = line&#091;10] == \"1\"\n            winning = int(line&#091;11])\n            winner = int(line&#091;12])\n            move = int(line&#091;13])\n            record = TTTRecord(vector, valid, winning, winner, move)\n            yield record\n            line = f.readline()<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>validtttRecords = filter(lambda o : o.valid, tttRecordGenerator())<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code># Warning: will take a couple of seconds!\nvalidtttRecordsList = list(validtttRecords)\nlen(validtttRecordsList)<\/code><\/pre>\n\n\n\n<p>Having set up TensorFlow with<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import os\nos.environ&#091;\"TF_CPP_MIN_LOG_LEVEL\"] = \"2\"\n\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.layers.experimental import preprocessing\n\nimport pandas as pd\nimport numpy as np\nimport datetime<\/code><\/pre>\n\n\n\n<p>we then transform or Pyrecord data into a Pandas DataFrame.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>allDataFrame = pd.DataFrame(list(zip(&#091;x.vector&#091;0] for x in validtttRecordsList], \n                                     &#091;x.vector&#091;1] for x in validtttRecordsList],\n                                     &#091;x.vector&#091;2] for x in validtttRecordsList],\n                                     &#091;x.vector&#091;3] for x in validtttRecordsList],\n                                     &#091;x.vector&#091;4] for x in validtttRecordsList],\n                                     &#091;x.vector&#091;5] for x in validtttRecordsList],\n                                     &#091;x.vector&#091;6] for x in validtttRecordsList],\n                                     &#091;x.vector&#091;7] for x in validtttRecordsList],\n                                     &#091;x.vector&#091;8] for x in validtttRecordsList],\n                                     &#091;x.winning for x in validtttRecordsList])), \n             columns =&#091;'pos1', 'pos2', 'pos3','pos4','pos5','pos6','pos7','pos8','pos9', 'winning'])\n\nprint(allDataFrame.tail())<\/code><\/pre>\n\n\n\n<p>This gives us a first glimpse of the data:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>        pos1  pos2  pos3  pos4  pos5  pos6  pos7  pos8  pos9  winning\n362875     9     8     7     6     5     4     1     3     2        1\n362876     9     8     7     6     5     4     2     1     3        0\n362877     9     8     7     6     5     4     2     3     1        0\n362878     9     8     7     6     5     4     3     1     2        1\n362879     9     8     7     6     5     4     3     2     1        1<\/code><\/pre>\n\n\n\n<p>Essentially, what we are doing is that we unpack the vector into columns (this will be our features later on), having it located next to the winning information (which will be our labels later).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Training (Basic Model)<\/h2>\n\n\n\n<p>For training, we take the usual 80% random cut. The remainder will serve as test for evaluation later. Moreover, we take the usual statistical information for our training set.<br><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>train_dataset = allDataFrame.sample(frac=0.8, random_state=42)\ntest_dataset = allDataFrame.drop(train_dataset.index)\n\nprint(allDataFrame.shape, train_dataset.shape, test_dataset.shape)\ntrain_dataset.describe().transpose()<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>(362880, 10) (290304, 10) (72576, 10)<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th><\/th><th>count<\/th><th>mean<\/th><th>std<\/th><th>min<\/th><th>25%<\/th><th>50%<\/th><th>75%<\/th><th>max<\/th><\/tr><\/thead><tbody><tr><th>pos1<\/th><td>290304.0<\/td><td>5.004037<\/td><td>2.582516<\/td><td>1.0<\/td><td>3.0<\/td><td>5.0<\/td><td>7.0<\/td><td>9.0<\/td><\/tr><tr><th>pos2<\/th><td>290304.0<\/td><td>4.999249<\/td><td>2.580750<\/td><td>1.0<\/td><td>3.0<\/td><td>5.0<\/td><td>7.0<\/td><td>9.0<\/td><\/tr><tr><th>pos3<\/th><td>290304.0<\/td><td>4.996063<\/td><td>2.580538<\/td><td>1.0<\/td><td>3.0<\/td><td>5.0<\/td><td>7.0<\/td><td>9.0<\/td><\/tr><tr><th>pos4<\/th><td>290304.0<\/td><td>5.003014<\/td><td>2.581781<\/td><td>1.0<\/td><td>3.0<\/td><td>5.0<\/td><td>7.0<\/td><td>9.0<\/td><\/tr><tr><th>pos5<\/th><td>290304.0<\/td><td>4.996579<\/td><td>2.582223<\/td><td>1.0<\/td><td>3.0<\/td><td>5.0<\/td><td>7.0<\/td><td>9.0<\/td><\/tr><tr><th>pos6<\/th><td>290304.0<\/td><td>5.000279<\/td><td>2.581773<\/td><td>1.0<\/td><td>3.0<\/td><td>5.0<\/td><td>7.0<\/td><td>9.0<\/td><\/tr><tr><th>pos7<\/th><td>290304.0<\/td><td>4.999029<\/td><td>2.583215<\/td><td>1.0<\/td><td>3.0<\/td><td>5.0<\/td><td>7.0<\/td><td>9.0<\/td><\/tr><tr><th>pos8<\/th><td>290304.0<\/td><td>4.998505<\/td><td>2.581489<\/td><td>1.0<\/td><td>3.0<\/td><td>5.0<\/td><td>7.0<\/td><td>9.0<\/td><\/tr><tr><th>pos9<\/th><td>290304.0<\/td><td>5.003245<\/td><td>2.583642<\/td><td>1.0<\/td><td>3.0<\/td><td>5.0<\/td><td>7.0<\/td><td>9.0<\/td><\/tr><tr><th>winning<\/th><td>290304.0<\/td><td>0.448692<\/td><td>0.497361<\/td><td>0.0<\/td><td>0.0<\/td><td>0.0<\/td><td>1.0<\/td><td>1.0<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>We can easily see that there is a uniform distribution on all positions, and that the winning value behaves like a boolean for categorization.<\/p>\n\n\n\n<p>For easier access, we can now split features and labels:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># split features from labels\ntrain_features = train_dataset.copy()\ntest_features = test_dataset.copy()\n\ntrain_labels = train_features.pop('winning')\ntest_labels = test_features.pop('winning')<\/code><\/pre>\n\n\n\n<p>Obviously our features are not normalized yet. That is why we prepare a Keras Normalization Layer.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>normalizer = preprocessing.Normalization()\nnormalizer.adapt(np.array(train_features))\nprint(normalizer.mean.numpy())<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>&#091;&#091;5.004032  4.9992476 4.9960785 5.0030107 4.9965568 5.0002723 4.999036\n  4.9984956 5.0032406]]<\/code><\/pre>\n\n\n\n<p>Soon we will make this the first layer of our model.<\/p>\n\n\n\n<p>Apropos talking about the model: For a starter, let&#8217;s take the following model:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>model = keras.models.Sequential(&#091;\n    normalizer,\n    layers.Dense(units=64, activation='relu'), #1\n    layers.Dense(units=64,activation='relu'), #2 \n    layers.Dense(units=128,activation='relu'), #3\n    layers.Dense(units=1)\n])\nprint(model.summary())<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>Model: \"sequential\"\n_________________________________________________________________\n Layer (type)                Output Shape              Param #   \n=================================================================\n normalization (Normalizati  (None, 9)                 19        \n on)                                                             \n                                                                 \n dense (Dense)               (None, 64)                640       \n                                                                 \n dense_1 (Dense)             (None, 64)                4160      \n                                                                 \n dense_2 (Dense)             (None, 128)               8320      \n                                                                 \n dense_3 (Dense)             (None, 1)                 129       \n                                                                 \n=================================================================\nTotal params: 13268 (51.83 KB)\nTrainable params: 13249 (51.75 KB)\nNon-trainable params: 19 (80.00 Byte)\n_________________________________________________________________\nNone<\/code><\/pre>\n\n\n\n<p>As you can see, after the normalizer, we have two Dense layers with 64 units each, followed by a Dense layer with 128 units. As we want to have a single boolean-like result (&#8220;winning or not&#8221;), the result layer is a Dense layer with a single unit. As we go with the logit approach, there is no activation function on the result layer. Let&#8217;s see how far we get with this. <\/p>\n\n\n\n<p>To ensure that we don&#8217;t overfit our model, let&#8217;s make sure that we will stop fitting at latest, if we have an accuracy of 1.0. For that we define a brief custom fitting callback:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class Accuracy1Stopping(keras.callbacks.Callback):\n    def __init():\n        super.__init__()\n\n    def on_epoch_end(self, epoch, logs=None):\n        if round(logs.get('accuracy'), 4) == 1.0:\n            self.model.stop_training = True<\/code><\/pre>\n\n\n\n<p>Then let&#8217;s compile and fit the model right away:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>model.compile(loss=keras.losses.BinaryCrossentropy(from_logits=True),\n              optimizer=keras.optimizers.Adam(learning_rate=0.01), \n              metrics = &#091;\"accuracy\"])<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>history = model.fit(train_features, train_labels, \n          batch_size=512, \n          epochs=20, \n          shuffle=True,\n          callbacks=&#091;\n              tf.keras.callbacks.EarlyStopping(monitor='accuracy', mode=\"max\", restore_best_weights=True, patience=5, verbose=1), \n              Accuracy1Stopping(),\n              tf.keras.callbacks.ReduceLROnPlateau(monitor='loss', factor=0.2, patience=2, min_lr=0.002)\n          ], \n          verbose=1)<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>Epoch 1\/50\n567\/567 &#091;==============================] - 6s 7ms\/step - loss: 0.5521 - accuracy: 0.6821 - lr: 0.0100\nEpoch 2\/50\n567\/567 &#091;==============================] - 3s 5ms\/step - loss: 0.3881 - accuracy: 0.7986 - lr: 0.0100\nEpoch 3\/50\n567\/567 &#091;==============================] - 3s 5ms\/step - loss: 0.2644 - accuracy: 0.8709 - lr: 0.0100\nEpoch 4\/50\n567\/567 &#091;==============================] - 3s 5ms\/step - loss: 0.1828 - accuracy: 0.9161 - lr: 0.0100\nEpoch 5\/50\n567\/567 &#091;==============================] - 3s 5ms\/step - loss: 0.1332 - accuracy: 0.9435 - lr: 0.0100\nEpoch 6\/50\n567\/567 &#091;==============================] - 3s 5ms\/step - loss: 0.0902 - accuracy: 0.9631 - lr: 0.0100\nEpoch 7\/50\n567\/567 &#091;==============================] - 3s 5ms\/step - loss: 0.0713 - accuracy: 0.9716 - lr: 0.0100\nEpoch 8\/50\n567\/567 &#091;==============================] - 3s 5ms\/step - loss: 0.0696 - accuracy: 0.9740 - lr: 0.0100\nEpoch 9\/50\n567\/567 &#091;==============================] - 3s 5ms\/step - loss: 0.0528 - accuracy: 0.9808 - lr: 0.0100\nEpoch 10\/50\n567\/567 &#091;==============================] - 3s 5ms\/step - loss: 0.0445 - accuracy: 0.9841 - lr: 0.0100\nEpoch 11\/50\n567\/567 &#091;==============================] - 3s 5ms\/step - loss: 0.0496 - accuracy: 0.9836 - lr: 0.0100\nEpoch 12\/50\n567\/567 &#091;==============================] - 3s 5ms\/step - loss: 0.0285 - accuracy: 0.9907 - lr: 0.0100\nEpoch 13\/50\n567\/567 &#091;==============================] - 3s 5ms\/step - loss: 0.0433 - accuracy: 0.9860 - lr: 0.0100\nEpoch 14\/50\n567\/567 &#091;==============================] - 3s 5ms\/step - loss: 0.0181 - accuracy: 0.9955 - lr: 0.0100\nEpoch 15\/50\n567\/567 &#091;==============================] - 3s 6ms\/step - loss: 0.0552 - accuracy: 0.9852 - lr: 0.0100\nEpoch 16\/50\n567\/567 &#091;==============================] - 3s 6ms\/step - loss: 0.0022 - accuracy: 0.9999 - lr: 0.0100\nEpoch 17\/50\n567\/567 &#091;==============================] - 3s 6ms\/step - loss: 0.0032 - accuracy: 0.9995 - lr: 0.0100\nEpoch 18\/50\n567\/567 &#091;==============================] - 3s 6ms\/step - loss: 0.0914 - accuracy: 0.9779 - lr: 0.0100\nEpoch 19\/50\n567\/567 &#091;==============================] - 3s 5ms\/step - loss: 0.0030 - accuracy: 0.9999 - lr: 0.0020\nEpoch 20\/50\n567\/567 &#091;==============================] - 3s 6ms\/step - loss: 0.0022 - accuracy: 1.0000 - lr: 0.0020\n\n<\/code><\/pre>\n\n\n\n<p>Note that the training was through in less than 63s &#8211; and we achieved a mind-blowing accuracy of 1.0 after already 20 epochs!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Evaluation (Basic Model)<\/h2>\n\n\n\n<p>Let&#8217;s check whether the model has not tried to trick us and use the test dataset:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>evaluationResult = model.evaluate(test_features, test_labels, batch_size=256, verbose=1)\nprint(evaluationResult)<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>284\/284 &#091;==============================] - 4s 10ms\/step - loss: 0.0021 - accuracy: 1.0000\n&#091;0.002135399729013443, 1.0]<\/code><\/pre>\n\n\n\n<p>Also for test dataset, the accuracy is 1.0! That impressively confirms that the neural network was able to learn what a winning board is.<\/p>\n\n\n\n<p>BTW: This Keras neural network can be downloaded here.<\/p>\n\n\n<p><img decoding=\"async\" src=\"http:\/\/blog.schmoigl-online.de\/wp-content\/plugins\/wp-downloadmanager\/images\/ext\/zip.gif\" alt=\"\" title=\"\" style=\"vertical-align: middle;\" \/>&nbsp;&nbsp;<strong><a href=\"http:\/\/blog.schmoigl-online.de\/?dl_id=12\">tic-tac-toe-Winning-Model.zip<\/a><\/strong> (134.8 KiB, 861 hits)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Sensitive Analysis<\/h2>\n\n\n\n<p>Now, let&#8217;s play around a little with the model and see how sensitive our model configuration is. For that, we automate the evaluation in a function, which reads like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import time\ndef runTrainingAndMeasureTestAccuracy(model, learning_rate): \n    model.compile(loss=keras.losses.BinaryCrossentropy(from_logits=True),\n              optimizer=keras.optimizers.Adam(learning_rate=learning_rate), \n              metrics = &#091;\"accuracy\"])\n    start = time.time()\n    history = model.fit(train_features, train_labels, \n          batch_size=512, \n          epochs=50, \n          shuffle=True, \n          callbacks=&#091;\n              tf.keras.callbacks.EarlyStopping(monitor='accuracy', mode=\"max\", patience=5, verbose=1), \n              Accuracy1Stopping(),\n              tf.keras.callbacks.ReduceLROnPlateau(monitor='loss', factor=0.2, patience=2, min_lr=0.002)\n          ],\n          verbose=1)\n    stop = time.time()\n    print(f\"Elapsed Training time: {stop-start}\")\n\n    print(\"Evaluating...\")\n    evaluationResult = model.evaluate(test_features, test_labels, batch_size=256, verbose=1)\n    print(evaluationResult)<\/code><\/pre>\n\n\n\n<p>The training &#8220;loop&#8221; then itself calls this method like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>model = keras.models.Sequential(&#091;\n     normalizer,\n     # here goes the model definition.\n     layers.Dense(units=1)\n])\nrunTrainingAndMeasureTestAccuracy(model, 0.01)<\/code><\/pre>\n\n\n\n<p>Running a set of measurements, this results in the following data points:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td>Learning Rate<\/td><td>Dense Layer 1<\/td><td>Dense Layer 2<\/td><td>Dense Layer 3<\/td><td>Dense Layer 4<\/td><td>Dense Layer 5<\/td><td>Dense Layer 6<\/td><td>Dense Layer 7<\/td><td>Dense Layer 8<\/td><td>Dense Layer 9<\/td><td>Epochs<\/td><td>Training Accuracy<\/td><td>Test Accuracy<\/td><td>Training Duration [s]<\/td><td>Avg Training Time\/Epoch<\/td><\/tr><tr><td>0,01<\/td><td>64<\/td><td>64<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>27<\/td><td>1<\/td><td>0,99994<\/td><td>41,5<\/td><td>1,54 s<\/td><\/tr><tr><td>0,01<\/td><td>64<\/td><td>64<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>18<\/td><td>1<\/td><td>1<\/td><td>28,5<\/td><td>1,58 s<\/td><\/tr><tr><td>0,01<\/td><td>64<\/td><td>64<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>15<\/td><td>1<\/td><td>1<\/td><td>23,3<\/td><td>1,55 s<\/td><\/tr><tr><td>0,01<\/td><td>32<\/td><td>32<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>34<\/td><td>1<\/td><td>0,9997<\/td><td>50,6<\/td><td>1,49 s<\/td><\/tr><tr><td>0,01<\/td><td>32<\/td><td>32<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>50<\/td><td>0,9963<\/td><td>0,9957<\/td><td>77,7<\/td><td>1,55 s<\/td><\/tr><tr><td>0,01<\/td><td>32<\/td><td>32<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>50<\/td><td>0,9934<\/td><td>0,9966<\/td><td>74,6<\/td><td>1,49 s<\/td><\/tr><tr><td>0,01<\/td><td>128<\/td><td>128<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>12<\/td><td>1<\/td><td>1<\/td><td>20,9<\/td><td>1,74 s<\/td><\/tr><tr><td>0,01<\/td><td>128<\/td><td>128<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>20<\/td><td>1<\/td><td>0,9999<\/td><td>41,4<\/td><td>2,07 s<\/td><\/tr><tr><td>0,01<\/td><td>128<\/td><td>128<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>9<\/td><td>1<\/td><td>1<\/td><td>15<\/td><td>1,67 s<\/td><\/tr><tr><td>0,01<\/td><td>128<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>16<\/td><td>1<\/td><td>0,9999<\/td><td>22,7<\/td><td>1,42 s<\/td><\/tr><tr><td>0,01<\/td><td>128<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>38<\/td><td>0,9993<\/td><td>0,9993<\/td><td>51,8<\/td><td>1,36 s<\/td><\/tr><tr><td>0,01<\/td><td>128<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>50<\/td><td>0,9998<\/td><td>0,9998<\/td><td>71,6<\/td><td>1,43 s<\/td><\/tr><tr><td>0,01<\/td><td>256<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>50<\/td><td>0,9961<\/td><td>0,9969<\/td><td>69,8<\/td><td>1,40 s<\/td><\/tr><tr><td>0,01<\/td><td>256<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>44<\/td><td>1<\/td><td>0,9999<\/td><td>82,3<\/td><td>1,87 s<\/td><\/tr><tr><td>0,01<\/td><td>256<\/td><td>128<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>34<\/td><td>1<\/td><td>1<\/td><td>49<\/td><td>1,44 s<\/td><\/tr><tr><td>0,01<\/td><td>128<\/td><td>128<\/td><td>64<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>13<\/td><td>1<\/td><td>0,9999<\/td><td>20,7<\/td><td>1,59 s<\/td><\/tr><tr><td>0,01<\/td><td>128<\/td><td>128<\/td><td>64<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>11<\/td><td>1<\/td><td>1<\/td><td>17,7<\/td><td>1,61 s<\/td><\/tr><tr><td>0,01<\/td><td>128<\/td><td>128<\/td><td>64<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>16<\/td><td>1<\/td><td>1<\/td><td>26,1<\/td><td>1,63 s<\/td><\/tr><tr><td>0,01<\/td><td>384<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>50<\/td><td>0,9503<\/td><td>0,9477<\/td><td>91,7<\/td><td>1,83 s<\/td><\/tr><tr><td>0,01<\/td><td>384<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>50<\/td><td>0,9472<\/td><td>0,9458<\/td><td>108,2<\/td><td>2,16 s<\/td><\/tr><tr><td>0,01<\/td><td>384<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>50<\/td><td>0,9417<\/td><td>0,9414<\/td><td>93,4<\/td><td>1,87 s<\/td><\/tr><tr><td>0,01<\/td><td>1024<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>50<\/td><td>0,9506<\/td><td>0,9429<\/td><td>64,8<\/td><td>1,30 s<\/td><\/tr><tr><td>0,01<\/td><td>1024<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>50<\/td><td>0,9556<\/td><td>0,9603<\/td><td>82,2<\/td><td>1,64 s<\/td><\/tr><tr><td>0,01<\/td><td>1024<\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td><\/td><td>50<\/td><td>0,9513<\/td><td>0,9554<\/td><td>65,9<\/td><td>1,32 s<\/td><\/tr><tr><td>0,01<\/td><td>32<\/td><td>32<\/td><td>32<\/td><td>32<\/td><td>32<\/td><td>32<\/td><td><\/td><td><\/td><td><\/td><td>40<\/td><td>0,9997<\/td><td>0,9998<\/td><td>80,4<\/td><td>2,01 s<\/td><\/tr><tr><td>0,01<\/td><td>32<\/td><td>32<\/td><td>32<\/td><td>32<\/td><td>32<\/td><td>32<\/td><td><\/td><td><\/td><td><\/td><td>23<\/td><td>1<\/td><td>1<\/td><td>45,6<\/td><td>1,98 s<\/td><\/tr><tr><td>0,01<\/td><td>32<\/td><td>32<\/td><td>32<\/td><td>32<\/td><td>32<\/td><td>32<\/td><td><\/td><td><\/td><td><\/td><td>30<\/td><td>1<\/td><td>1<\/td><td>59,1<\/td><td>1,97 s<\/td><\/tr><tr><td>0,01<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>50<\/td><td>0,9808<\/td><td>0,9591<\/td><td>133,5<\/td><td>2,67 s<\/td><\/tr><tr><td>0,01<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>50<\/td><td>0,9478<\/td><td>0,9506<\/td><td>137,3<\/td><td>2,75 s<\/td><\/tr><tr><td>0,01<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>16<\/td><td>47<\/td><td>0,9361<\/td><td>0,9417<\/td><td>118,6<\/td><td>2,52 s<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>It is to be admitted that this small series of measurement are way too small to allow concluding general statements, but the following hypothesizes may appear worthwhile having a closer look at:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>More nodes in the layers does not automatically mean better results.<\/li>\n\n\n\n<li>Also the number of layers do not yield better results automatically.<\/li>\n\n\n\n<li>Reduction of the number of nodes &#8220;in the base layers&#8221; may mean slower training progress.<\/li>\n\n\n\n<li>There seems to be a lower limit of units in the dense layer that are necessary to achieve an accuracy value of 1.<\/li>\n\n\n\n<li>Reducing the number of hidden layers below 2 does not allow anymore to reach an accuracy value of 1 &#8211; even if the number of units is increased drastically.<\/li>\n\n\n\n<li>A higher learning rate (0.01 -&gt; 0.05) may lead to training problems.<\/li>\n<\/ul>\n\n\n\n<p>By looking at which neural networks were able to achieve an accuracy of 1, and mentally comparing those, then it two more aspects become apparent:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Models, which consist of only layers with 32 nodes, were able to achieve an accuracy of 1.<\/li>\n\n\n\n<li>A large number of layers with only small amounts of units (16) does not guarantee to also achieve an accuracy value of 1. Moreover, models with many layers take longer to train.<\/li>\n<\/ul>\n\n\n\n<p>In short, there seems to be an optimum between number of layers and units in the layers: two to three layers seem to be most efficient, plus going below 64 units per Dense layer also is not very promising. <\/p>\n\n\n\n<p>Perhaps with a later blog post we may further analyze these aspects.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As a first learning task for using AI with Tic-Tac-Toe, let us take the task of determining whether a board is a winning one or not (i.e. either X or O has won the game). We cannot directly tell the neural network what the rules are for an assignment to be winning. Instead, we need &#8230;<\/p>\n<p><a href=\"http:\/\/blog.schmoigl-online.de\/?p=1493\" class=\"more-link\">Continue reading &lsquo;Tic-Tac-Toe and AI: A Winning Board (Part 2)&rsquo; &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[38],"tags":[48,50],"class_list":["post-1493","post","type-post","status-publish","format-standard","hentry","category-machine-learning","tag-tensorflow","tag-tic-tac-toe"],"_links":{"self":[{"href":"http:\/\/blog.schmoigl-online.de\/index.php?rest_route=\/wp\/v2\/posts\/1493","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/blog.schmoigl-online.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.schmoigl-online.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.schmoigl-online.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.schmoigl-online.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1493"}],"version-history":[{"count":29,"href":"http:\/\/blog.schmoigl-online.de\/index.php?rest_route=\/wp\/v2\/posts\/1493\/revisions"}],"predecessor-version":[{"id":1581,"href":"http:\/\/blog.schmoigl-online.de\/index.php?rest_route=\/wp\/v2\/posts\/1493\/revisions\/1581"}],"wp:attachment":[{"href":"http:\/\/blog.schmoigl-online.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1493"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.schmoigl-online.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1493"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.schmoigl-online.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1493"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}