Setting up our imported libraries.

In [1]:
from functools import reduce
import numpy as np
from keras.models import Sequential
from keras.layers import Dense
from os.path import join
Using TensorFlow backend.

Function definitions

In [2]:
# Used to format our input binary state.

def format_input(acc, elem):
    hex_elem = (elem - (elem >> 4 << 4))
    for x in range(16):
        if x == hex_elem:
            acc.append(1)
        else:
            acc.append(0)
    hex_elem = (elem >> 4) % 16
    for x in range(16):
        if x == hex_elem:
            acc.append(1)
        else:
            acc.append(0)
    return acc

# Calculate Manhattan distance between two points.

def man_dist(x, y):
    for a, b in zip(x, y):
        a_one, a_two = x
        b_one, b_two = y
    
    return (abs(a_one - b_one) + abs(a_two - b_two))
            
# Calculate Manhattan distance between each set of two points in a list.
    
def man_dist_state(x, y):
    return sum(man_dist(a, b) for a, b in zip(x, y))

# Used to format the positions we parsed from our binary input.

def format_pos(acc, elem):
    hex_elem = (elem[1] - (elem[1] >> 4 << 4))
    if hex_elem == 0:
        acc.append((hex_elem, (3,3)))
    else:
        acc.append((hex_elem, ((15 - ((elem[0]) * 2)) % 4,int((15 - ((elem[0]) * 2)) / 4))))
    hex_elem = (elem[1] >> 4) % 16
    if hex_elem == 0:
        acc.append((hex_elem, (3,3)))
    else:
        acc.append((hex_elem, ((15 - ((elem[0]) * 2 + 1)) % 4,int((15 - ((elem[0]) * 2 + 1)) / 4))))
    
    return acc

# The title of this function is slightly misleading.
# I'm simply generating a list of positions that each
# puzzle piece in the current parsed state SHOULD be at.
# I organize this in order of the pieces as they were
# parsed so the two lists line up perfectly.

def generate_pos(acc, elem):
    if(elem[0] == 0):
        acc.append((3,3))
    else:
        acc.append((((elem[0] - 1) % 4), (int((elem[0] - 1)/4))))
    
    return acc

# Used to format our ending Manhattan distance into a format
# that can be compared with our 29 output neurons.

def format_man_dist(elem):
    acc = []
    for x in range(28, -1, -1):
        if x == elem:
            acc.append(1)
        else:
            acc.append(0)
    return acc

Parsing input

Creating Target data

For this cell, I wanted to clearly explain how the script past this point works. Since I don't want to parse all of the binary states from each file that I'm going to use all at once and hold them in RAM, this instead parses 1 binary state at a time (8 bytes, meaning 64 bits, and we ignore the 1st 4 bits), does the calculations and input formatting needed, and appends the end result per state to a list to be used later.

In [3]:
target = []

for i in range(29):
    filename = join('data', str(i) + '.bin')
    
    # Debugging to print the current file from which states are being parsed.
    #print(i)
    temp = []
    
    with open(filename, 'rb') as f:
        data = f.read(8)
        counter = 0

        while(data and counter < 1000):
            temp.append(format_man_dist(i))
            
            data = f.read(8)
            counter += 1
        
        target.append(temp)
            
#print(target[28][500])

Parsing and Training the neural network

Setting up the neural network

Before we run our training/parsing, we need to make sure Keras has configured the neural network properly to our specifications.

In [14]:
# Sets up a Sequential model, Sequential is all
# that should need to be used for this project,
# considering that it will only be dealing with
# a linear stack of layers of neurons.

model = Sequential()

# Adding layers to the model.

model.add(Dense(units=240, activation='tanh', input_dim=240))
model.add(Dense(units=120, activation='tanh'))
model.add(Dense(units=60, activation='tanh'))
model.add(Dense(units=29, activation='sigmoid'))

# Configure the learning process.

model.compile(optimizer='sgd',
             loss='mean_squared_error',
             metrics=['accuracy'])

Training the neural network

Again, this part has the parsing and training in hand, since as we parse and format the states from our file input, we feed those batches to our neural network to train with.

In [64]:
for i in range(29):
    filename = join('data', str(i) + '.bin')
    
    # Debugging to print the current file from which states are being parsed.
    print(i)
    
    with open(filename, 'rb') as f:
        data = f.read(8)
        counter = 0
        training = []

        while(data and counter < 2000):
            bin_data = reduce(format_input, list(data), [])
            bin_data.reverse()
            bin_data = bin_data[16:]
            
            training.append(bin_data)

            data = f.read(8)
            counter += 1

            #print(training[0])
    # Train the network.

    model.fit(np.array(training), np.array(target[i]), epochs=8, batch_size=2000)
    #model.train_on_batch(np.array(temp), np.array(target))
0
Epoch 1/8
1/1 [==============================] - 0s 2ms/step - loss: 0.1678 - acc: 0.0000e+00
Epoch 2/8
1/1 [==============================] - 0s 2ms/step - loss: 0.1671 - acc: 0.0000e+00
Epoch 3/8
1/1 [==============================] - 0s 1ms/step - loss: 0.1664 - acc: 0.0000e+00
Epoch 4/8
1/1 [==============================] - 0s 1ms/step - loss: 0.1657 - acc: 0.0000e+00
Epoch 5/8
1/1 [==============================] - 0s 1ms/step - loss: 0.1650 - acc: 0.0000e+00
Epoch 6/8
1/1 [==============================] - 0s 2ms/step - loss: 0.1643 - acc: 0.0000e+00
Epoch 7/8
1/1 [==============================] - 0s 1ms/step - loss: 0.1637 - acc: 0.0000e+00
Epoch 8/8
1/1 [==============================] - 0s 2ms/step - loss: 0.1630 - acc: 0.0000e+00
1
Epoch 1/8
2/2 [==============================] - 0s 1ms/step - loss: 0.1630 - acc: 0.0000e+00
Epoch 2/8
2/2 [==============================] - 0s 1ms/step - loss: 0.1625 - acc: 0.0000e+00
Epoch 3/8
2/2 [==============================] - 0s 1ms/step - loss: 0.1619 - acc: 0.0000e+00
Epoch 4/8
2/2 [==============================] - 0s 1ms/step - loss: 0.1613 - acc: 0.0000e+00
Epoch 5/8
2/2 [==============================] - 0s 1ms/step - loss: 0.1607 - acc: 0.0000e+00
Epoch 6/8
2/2 [==============================] - 0s 927us/step - loss: 0.1602 - acc: 0.0000e+00
Epoch 7/8
2/2 [==============================] - 0s 900us/step - loss: 0.1596 - acc: 0.0000e+00
Epoch 8/8
2/2 [==============================] - 0s 836us/step - loss: 0.1590 - acc: 0.0000e+00
2
Epoch 1/8
4/4 [==============================] - 0s 293us/step - loss: 0.1645 - acc: 0.0000e+00
Epoch 2/8
4/4 [==============================] - 0s 1ms/step - loss: 0.1640 - acc: 0.0000e+00
Epoch 3/8
4/4 [==============================] - 0s 384us/step - loss: 0.1636 - acc: 0.0000e+00
Epoch 4/8
4/4 [==============================] - 0s 360us/step - loss: 0.1631 - acc: 0.0000e+00
Epoch 5/8
4/4 [==============================] - 0s 342us/step - loss: 0.1627 - acc: 0.0000e+00
Epoch 6/8
4/4 [==============================] - 0s 378us/step - loss: 0.1622 - acc: 0.0000e+00
Epoch 7/8
4/4 [==============================] - 0s 611us/step - loss: 0.1618 - acc: 0.0000e+00
Epoch 8/8
4/4 [==============================] - 0s 471us/step - loss: 0.1613 - acc: 0.0000e+00
3
Epoch 1/8
10/10 [==============================] - 0s 185us/step - loss: 0.1725 - acc: 0.0000e+00
Epoch 2/8
10/10 [==============================] - 0s 148us/step - loss: 0.1721 - acc: 0.0000e+00
Epoch 3/8
10/10 [==============================] - 0s 303us/step - loss: 0.1717 - acc: 0.0000e+00
Epoch 4/8
10/10 [==============================] - 0s 182us/step - loss: 0.1712 - acc: 0.0000e+00
Epoch 5/8
10/10 [==============================] - 0s 170us/step - loss: 0.1708 - acc: 0.0000e+00
Epoch 6/8
10/10 [==============================] - 0s 164us/step - loss: 0.1704 - acc: 0.0000e+00
Epoch 7/8
10/10 [==============================] - 0s 162us/step - loss: 0.1700 - acc: 0.0000e+00
Epoch 8/8
10/10 [==============================] - 0s 206us/step - loss: 0.1695 - acc: 0.0000e+00
4
Epoch 1/8
24/24 [==============================] - 0s 172us/step - loss: 0.1579 - acc: 0.1250
Epoch 2/8
24/24 [==============================] - 0s 79us/step - loss: 0.1575 - acc: 0.1250
Epoch 3/8
24/24 [==============================] - 0s 93us/step - loss: 0.1570 - acc: 0.1250
Epoch 4/8
24/24 [==============================] - 0s 98us/step - loss: 0.1566 - acc: 0.1250
Epoch 5/8
24/24 [==============================] - 0s 74us/step - loss: 0.1562 - acc: 0.1250
Epoch 6/8
24/24 [==============================] - 0s 97us/step - loss: 0.1558 - acc: 0.1250
Epoch 7/8
24/24 [==============================] - 0s 92us/step - loss: 0.1553 - acc: 0.1250
Epoch 8/8
24/24 [==============================] - 0s 68us/step - loss: 0.1549 - acc: 0.1250
5
Epoch 1/8
54/54 [==============================] - 0s 33us/step - loss: 0.1632 - acc: 0.0000e+00
Epoch 2/8
54/54 [==============================] - 0s 37us/step - loss: 0.1628 - acc: 0.0000e+00
Epoch 3/8
54/54 [==============================] - 0s 40us/step - loss: 0.1624 - acc: 0.0000e+00
Epoch 4/8
54/54 [==============================] - 0s 66us/step - loss: 0.1620 - acc: 0.0000e+00
Epoch 5/8
54/54 [==============================] - 0s 39us/step - loss: 0.1616 - acc: 0.0000e+00
Epoch 6/8
54/54 [==============================] - 0s 42us/step - loss: 0.1612 - acc: 0.0000e+00
Epoch 7/8
54/54 [==============================] - 0s 36us/step - loss: 0.1608 - acc: 0.0000e+00
Epoch 8/8
54/54 [==============================] - 0s 45us/step - loss: 0.1603 - acc: 0.0000e+00
6
Epoch 1/8
107/107 [==============================] - 0s 19us/step - loss: 0.1639 - acc: 0.0000e+00
Epoch 2/8
107/107 [==============================] - 0s 16us/step - loss: 0.1636 - acc: 0.0000e+00
Epoch 3/8
107/107 [==============================] - 0s 16us/step - loss: 0.1632 - acc: 0.0000e+00
Epoch 4/8
107/107 [==============================] - 0s 19us/step - loss: 0.1628 - acc: 0.0000e+00
Epoch 5/8
107/107 [==============================] - 0s 15us/step - loss: 0.1625 - acc: 0.0000e+00
Epoch 6/8
107/107 [==============================] - 0s 26us/step - loss: 0.1621 - acc: 0.0000e+00
Epoch 7/8
107/107 [==============================] - 0s 26us/step - loss: 0.1618 - acc: 0.0000e+00
Epoch 8/8
107/107 [==============================] - 0s 19us/step - loss: 0.1614 - acc: 0.0000e+00
7
Epoch 1/8
222/222 [==============================] - 0s 10us/step - loss: 0.1609 - acc: 0.0135
Epoch 2/8
222/222 [==============================] - 0s 9us/step - loss: 0.1606 - acc: 0.0135
Epoch 3/8
222/222 [==============================] - 0s 10us/step - loss: 0.1603 - acc: 0.0135
Epoch 4/8
222/222 [==============================] - 0s 11us/step - loss: 0.1600 - acc: 0.0135
Epoch 5/8
222/222 [==============================] - 0s 9us/step - loss: 0.1597 - acc: 0.0135
Epoch 6/8
222/222 [==============================] - 0s 15us/step - loss: 0.1593 - acc: 0.0135
Epoch 7/8
222/222 [==============================] - 0s 10us/step - loss: 0.1590 - acc: 0.0135
Epoch 8/8
222/222 [==============================] - 0s 10us/step - loss: 0.1587 - acc: 0.0135
8
Epoch 1/8
496/496 [==============================] - 0s 5us/step - loss: 0.1634 - acc: 0.0000e+00
Epoch 2/8
496/496 [==============================] - 0s 6us/step - loss: 0.1631 - acc: 0.0000e+00
Epoch 3/8
496/496 [==============================] - 0s 6us/step - loss: 0.1628 - acc: 0.0000e+00
Epoch 4/8
496/496 [==============================] - 0s 6us/step - loss: 0.1625 - acc: 0.0000e+00
Epoch 5/8
496/496 [==============================] - 0s 5us/step - loss: 0.1622 - acc: 0.0000e+00
Epoch 6/8
496/496 [==============================] - 0s 4us/step - loss: 0.1619 - acc: 0.0000e+00
Epoch 7/8
496/496 [==============================] - 0s 6us/step - loss: 0.1616 - acc: 0.0000e+00
Epoch 8/8
496/496 [==============================] - 0s 5us/step - loss: 0.1613 - acc: 0.0000e+00
9
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-64-2d935fb7ef0f> in <module>()
     23     # Train the network.
     24 
---> 25     model.fit(np.array(training), np.array(target[i]), epochs=8, batch_size=2000)
     26     #model.train_on_batch(np.array(temp), np.array(target))

/usr/lib/python3.6/site-packages/keras/models.py in fit(self, x, y, batch_size, epochs, verbose, callbacks, validation_split, validation_data, shuffle, class_weight, sample_weight, initial_epoch, steps_per_epoch, validation_steps, **kwargs)
    958                               initial_epoch=initial_epoch,
    959                               steps_per_epoch=steps_per_epoch,
--> 960                               validation_steps=validation_steps)
    961 
    962     def evaluate(self, x, y, batch_size=32, verbose=1,

/usr/lib/python3.6/site-packages/keras/engine/training.py in fit(self, x, y, batch_size, epochs, verbose, callbacks, validation_split, validation_data, shuffle, class_weight, sample_weight, initial_epoch, steps_per_epoch, validation_steps, **kwargs)
   1572             class_weight=class_weight,
   1573             check_batch_axis=False,
-> 1574             batch_size=batch_size)
   1575         # Prepare validation data.
   1576         do_validation = False

/usr/lib/python3.6/site-packages/keras/engine/training.py in _standardize_user_data(self, x, y, sample_weight, class_weight, check_batch_axis, batch_size)
   1417                           for (ref, sw, cw, mode)
   1418                           in zip(y, sample_weights, class_weights, self._feed_sample_weight_modes)]
-> 1419         _check_array_lengths(x, y, sample_weights)
   1420         _check_loss_and_target_compatibility(y,
   1421                                              self._feed_loss_fns,

/usr/lib/python3.6/site-packages/keras/engine/training.py in _check_array_lengths(inputs, targets, weights)
    248                          'the same number of samples as target arrays. '
    249                          'Found ' + str(list(set_x)[0]) + ' input samples '
--> 250                          'and ' + str(list(set_y)[0]) + ' target samples.')
    251     if len(set_w) > 1:
    252         raise ValueError('All sample_weight arrays should have '

ValueError: Input arrays should have the same number of samples as target arrays. Found 1151 input samples and 1000 target samples.

Testing the neural network

Creating the testing target data

Similarly, here is a separate cell that just parses and creates the necessary target and testing arrays that we will need to test the neural network using Keras.

In [68]:
# Used for testing data
for i in range(11,29):
    filename = join('data', str(i) + '.bin')

    # Debugging to print the current file from which states are being parsed.
    print(i)

    with open(filename, 'rb') as f:

        for i in range(2000):
            data = f.read(8)

        data = f.read(8)

        counter = 0

        testing = []

        testing_target = []

        while(data and counter < 10000):
            bin_data = reduce(format_input, list(data), [])
            bin_data.reverse()
            bin_data = bin_data[16:]

            testing.append(bin_data)

            pos_data = reduce(format_pos, enumerate(list(data)), [])
            pos_data.reverse()
            pos_data = pos_data[1:]

            state_pos = []

            for p in pos_data:
                state_pos.append(p[1])

            testing_target_pos = reduce(generate_pos, pos_data, [])

            testing_target.append(format_man_dist(man_dist_state(state_pos, testing_target_pos)))

            counter += 1
            data = f.read(8)
        
        # Evaluate accuracy

        loss_and_metrics = model.evaluate(np.array(testing),np.array(testing_target), batch_size=1000)

        # Generating predictions:

        predictions = model.predict(np.array(testing), batch_size=1000)
        
        output = []

        for p in range(len(predictions)):
            if np.argmax(testing_target[p]) < 18:
                output.append(100*((18 - (28 - np.argmax(predictions[p]))) / (18 - np.argmax(testing_target[p]))))
            else:
                output.append(0)

        #for i in range(len(output)):
        #    print(output[i])

        print("Percentage possible improvement: ", np.array(output).mean())

        print(model.metrics_names[0], loss_and_metrics[0])

        print(model.metrics_names[1], loss_and_metrics[1])
11
1938/1938 [==============================] - 0s 2us/step
Percentage possible improvement:  131.991744066
loss 0.167936483834
acc 0.0175438595376
12
5808/5808 [==============================] - 0s 2us/step
Percentage possible improvement:  153.83953168
loss 0.169155473757
acc 0.0118801652392
13
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  91.605
loss 0.167931690812
acc 0.0303999996744
14
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  115.414166667
loss 0.166890135407
acc 0.0186000001617
15
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  98.1758333333
loss 0.168728539348
acc 0.0131000000983
16
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  34.4588333333
loss 0.168766576052
acc 0.010100000212
17
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  -53.8332380952
loss 0.17060739994
acc 0.0102000000887
18
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  -0.692416666667
loss 0.169354042411
acc 0.00440000006929
19
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  -29.2496706349
loss 0.171234123409
acc 0.00790000013076
20
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  23.3521944444
loss 0.170418299735
acc 0.007200000179
21
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  -31.1126269841
loss 0.171738886833
acc 0.0229000000283
22
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  9.1381010101
loss 0.173329897225
acc 0.00890000014333
23
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  -43.6177330447
loss 0.174675036967
acc 0.0130999999936
24
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  -3.36664033189
loss 0.176320441067
acc 0.0120000001742
25
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  -1.81612357087
loss 0.177039775252
acc 0.00450000000419
26
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  33.477459596
loss 0.174290961027
acc 0.017100000754
27
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  4.31057944833
loss 0.173010015488
acc 0.00779999999795
28
10000/10000 [==============================] - 0s 2us/step
Percentage possible improvement:  30.1904823787
loss 0.174648806453
acc 0.024500000081

Evaluating our test data

In [69]:
# Evaluate accuracy
    
loss_and_metrics = model.evaluate(np.array(testing),np.array(testing_target), batch_size=1000)
        
# Generating predictions:
    
predictions = model.predict(np.array(testing), batch_size=1000)
10000/10000 [==============================] - 0s 2us/step
In [34]:
output = []

for p in range(len(predictions)):
    if np.argmax(testing_target[p]) < 18:
        output.append(100*((18 - (28 - np.argmax(predictions[p]))) / (18 - np.argmax(testing_target[p]))))
    else:
        output.append(0)
    
#for i in range(len(output)):
#    print(output[i])

print(np.array(output).mean())

print(loss_and_metrics)

print(model.metrics_names)
104.35049058
[0.19274216511868517, 0.022359536046598016]
['loss', 'acc']