The data were collected as the SCITOS G5 navigates through the room following the wall in a clockwise direction, for 4 rounds. To navigate, the robot uses 24 ultrasound sensors arranged circularly around its "waist". The numbering of the ultrasound sensors starts at the front of the robot and increases in clockwise direction.
In [59]:
# modules
from keras.layers import Input, Dense, Dropout
from keras.models import Model
from keras.datasets import mnist
from keras.models import Sequential, load_model
from keras.optimizers import RMSprop
from keras.callbacks import TensorBoard
from __future__ import print_function
from keras.utils import plot_model
from IPython.display import SVG
from keras.utils.vis_utils import model_to_dot
from sklearn import preprocessing
from keras import layers
from keras import initializers
from matplotlib import axes
from matplotlib import rc
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
import keras
import matplotlib.pyplot as plt
import numpy as np
import math
import pydot
import graphviz
import pandas as pd
import IPython
import itertools
In [60]:
%matplotlib inline
font = {'family' : 'monospace',
'weight' : 'bold',
'size' : 20}
rc('font', **font)
In [61]:
def plot_confusion_matrix(cm, classes,
normalize=False,
title='Confusion matrix',
cmap=plt.cm.Blues):
"""
This function prints and plots the confusion matrix.
Normalization can be applied by setting `normalize=True`.
"""
if normalize:
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
print("Normalized confusion matrix")
else:
print('Confusion matrix, without normalization')
print(cm)
plt.imshow(cm, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes, rotation=45)
plt.yticks(tick_marks, classes)
fmt = '.2f' if normalize else 'd'
thresh = cm.max() / 2.
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
plt.text(j, i, format(cm[i, j], fmt),
horizontalalignment="center",
color="white" if cm[i, j] > thresh else "black")
plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('Predicted label')
In [62]:
# import
data_raw = pd.read_csv('data/sensor_readings_24.csv', sep=",", header=None)
data = data_raw.copy()
The dataframe consists of only positive values and the classes are encoded as strings in the variable with index 24
In [63]:
data.head()
Out[63]:
Whats the distribution of the classes?
In [64]:
df_tab = data_raw
df_tab[24] = df_tab[24].astype('category')
tab = pd.crosstab(index=df_tab[24], columns="frequency")
tab.index.name = 'Class/Direction'
tab/tab.sum()
Out[64]:
The Move_Forward
and the Sharp-Right-Turn
Class combine nearly 80% of all observated classes. So it might happen, that the accuracy may still be high with around 75% although most of the features are eliminated.
0. Mapping integer values to the classes.
In [65]:
mapping = {key: value for (key, value) in zip(data[24].unique(), range(len(data[24].unique())))}
print(mapping)
data.replace({24:mapping}, inplace=True)
In [66]:
data[24].unique()
Out[66]:
1. Take a random sample of 90% of the rows from the dataframe. To ensure reproducability the random_state
variable is set. The other 10% are placed aside for validation after training. The last column is the class column and is stored in the y variables respectively.
In [67]:
data_train = data.sample(frac=0.9, random_state=42)
data_val = data.drop(data_train.index)
df_x_train = data_train.iloc[:,:-1]
df_y_train = data_train.iloc[:,-1]
df_x_val = data_val.iloc[:,:-1]
df_y_val = data_val.iloc[:,-1]
2. Normalization between 0 and 1
In [68]:
x_train = df_x_train.values
x_train = (x_train - x_train.min()) / (x_train.max() - x_train.min())
y_train = df_y_train.values
y_train_cat = y_train
x_val = df_x_val.values
x_val = (x_val - x_val.min()) / (x_val.max() - x_val.min())
y_val = df_y_val.values
y_eval = y_val
3. Make useful categorical variables out of the single column data by one-hot encoding it.
In [10]:
y_train = keras.utils.to_categorical(y_train, 4)
y_val = keras.utils.to_categorical(y_val, 4)
4. Set Global Parameters
In [11]:
epochsize = 150
batchsize = 24
shuffle = False
dropout = 0.1
num_classes = 4
input_dim = x_train.shape[1]
hidden1_dim = 30
hidden2_dim = 30
class_names = mapping.keys()
Due to a tight schedule we will not perform any cross validation. So it might happen that our accuracy estimators lack a little bit in potential of generalization. We shall live with that. Another setup of experiments would be, that we loop over some different dataframes samples up in the preprocessing steps and repeat all the steps below to finally average the results.
The dimension of the hidden layers are set arbitrarily but some runs have shown that 30 is a good number. The input_dim
Variable is set to 24 because initially there are 24 features. The aim is to build the best possible neural net.
RMSprop is a mini batch gradient descent algorithm which divides the gradient by a running average of the learning rate. More information: http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf
The weights are initialized by a normal distribution with mean 0 and standard deviation of 0.05.
In [15]:
input_data = Input(shape=(input_dim,), dtype='float32', name='main_input')
hidden_layer1 = Dense(hidden1_dim, activation='relu', input_shape=(input_dim,), kernel_initializer='normal')(input_data)
dropout1 = Dropout(dropout)(hidden_layer1)
hidden_layer2 = Dense(hidden2_dim, activation='relu', input_shape=(input_dim,), kernel_initializer='normal')(dropout1)
dropout2 = Dropout(dropout)(hidden_layer2)
output_layer = Dense(num_classes, activation='softmax', kernel_initializer='normal')(dropout2)
model = Model(inputs=input_data, outputs=output_layer)
model.compile(loss='categorical_crossentropy',
optimizer=RMSprop(),
metrics=['accuracy'])
In [13]:
plot_model(model, to_file='images/robo1_nn.png', show_shapes=True, show_layer_names=True)
In [14]:
IPython.display.Image("images/robo1_nn.png")
Out[14]:
In [16]:
model.fit(x_train, y_train,
batch_size=batchsize,
epochs=epochsize,
verbose=0,
shuffle=shuffle)
nn_score = model.evaluate(x_val, y_val)[1]
print(nn_score)
In [18]:
# Compute confusion matrix
cnf_matrix = confusion_matrix(y_eval, model.predict(x_val).argmax(axis=-1))
np.set_printoptions(precision=2)
In [20]:
# Plot normalized confusion matrix
plt.figure(figsize=(20,10))
plot_confusion_matrix(cnf_matrix, classes=class_names, normalize=True,
title='Normalized confusion matrix')
The following data is from a paper published in March 2017. You can find that here: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5375835/
In [21]:
IPython.display.Image("images/2018-01-25 18_44_01-PubMed Central, Table 2_ Sensors (Basel). 2017 Mar; 17(3)_ 549. Published online.png")
Out[21]:
One can easily see that our results are better. So we go further with that result and check how good our SAW might become.
For this dataset we decided to go with a 24-16-8-16-24 architecture.
In [31]:
input_img = Input(shape=(input_dim,))
encoded1 = Dense(16, activation='relu')(input_img)
decoded1 = Dense(input_dim, activation='relu')(encoded1)
class1 = Dense(num_classes, activation='softmax')(decoded1)
autoencoder1 = Model(input_img, class1)
autoencoder1.compile(optimizer=RMSprop(), loss='binary_crossentropy', metrics=['accuracy'])
encoder1 = Model(input_img, encoded1)
encoder1.compile(optimizer=RMSprop(), loss='binary_crossentropy')
In [32]:
autoencoder1.fit(x_train
, y_train
, epochs=50
, batch_size=24
, shuffle=True
, verbose=False
)
Out[32]:
In [33]:
score1 = autoencoder1.evaluate(x_val, y_val, verbose=0)
print('Test accuracy:', score1[1])
In [34]:
first_layer_code = encoder1.predict(x_train)
encoded_2_input = Input(shape=(16,))
encoded2 = Dense(8, activation='relu')(encoded_2_input)
decoded2 = Dense(16, activation='relu')(encoded2)
class2 = Dense(num_classes, activation='softmax')(decoded2)
autoencoder2 = Model(encoded_2_input, class2)
autoencoder2.compile(optimizer=RMSprop(), loss='binary_crossentropy', metrics=['accuracy'])
encoder2 = Model(encoded_2_input, encoded2)
encoder2.compile(optimizer=RMSprop(), loss='binary_crossentropy')
In [36]:
autoencoder2.fit(first_layer_code
, y_train
, epochs=50
, batch_size=24
, shuffle=True
, verbose=False
)
Out[36]:
In [37]:
first_layer_code_val = encoder1.predict(x_val)
score2 = autoencoder2.evaluate(first_layer_code_val, y_val, verbose=0)
print('Test loss:', score2[0])
print('Test accuracy:', score2[1])
In [41]:
sae_encoded1 = Dense(16, activation='relu')(input_img)
sae_encoded2 = Dense(8, activation='relu')(sae_encoded1)
sae_decoded1 = Dense(16, activation='relu')(sae_encoded2)
sae_decoded2 = Dense(24, activation='sigmoid')(sae_decoded1)
sae = Model(input_img, sae_decoded2)
sae.layers[1].set_weights(autoencoder1.layers[1].get_weights())
sae.layers[2].set_weights(autoencoder2.layers[1].get_weights())
sae.compile(loss='binary_crossentropy', optimizer=RMSprop())
In [42]:
sae.fit(x_train
, x_train
, epochs=50
, batch_size=24
, shuffle=True
, verbose=False
)
Out[42]:
In [43]:
score4 = sae.evaluate(x_val, x_val, verbose=0)
print('Test loss:', score4)
In [48]:
input_img = Input(shape=(input_dim,))
sae_classifier_encoded1 = Dense(16, activation='relu')(input_img)
sae_classifier_encoded2 = Dense(8, activation='relu')(sae_classifier_encoded1)
class_layer = Dense(num_classes, activation='softmax')(sae_classifier_encoded2)
sae_classifier = Model(inputs=input_img, outputs=class_layer)
sae_classifier.layers[1].set_weights(autoencoder1.layers[1].get_weights())
sae_classifier.layers[2].set_weights(autoencoder2.layers[1].get_weights())
sae_classifier.compile(loss='binary_crossentropy', optimizer=RMSprop(), metrics=['accuracy'])
In [49]:
sae_classifier.fit(x_train, y_train
, epochs=50
, verbose=True
, batch_size=24
, shuffle=True)
Out[49]:
In [50]:
score5 = sae_classifier.evaluate(x_val, y_val)
print('Test accuracy:', score5[1])
In [54]:
third_layer_code = encoder2.predict(encoder1.predict(x_train))
encoded_4_input = Input(shape=(8,))
encoded4 = Dense(2, activation='sigmoid')(encoded_4_input)
decoded4 = Dense(8, activation='sigmoid')(encoded4)
class4 = Dense(num_classes, activation='softmax')(decoded4)
autoencoder4 = Model(encoded_4_input, class4)
autoencoder4.compile(optimizer=RMSprop(), loss='binary_crossentropy', metrics=['accuracy'])
encoder4 = Model(encoded_4_input, encoded4)
encoder4.compile(optimizer=RMSprop(), loss='binary_crossentropy')
In [55]:
autoencoder4.fit(third_layer_code
, y_train
, epochs=100
, batch_size=24
, shuffle=True
, verbose=True
)
Out[55]:
In [57]:
third_layer_code_val = encoder2.predict(encoder1.predict(x_val))
score4 = autoencoder4.evaluate(third_layer_code_val, y_val, verbose=0)
print('Test loss:', score4[0])
print('Test accuracy:', score4[1])
In [58]:
fourth_layer_code = encoder4.predict(encoder2.predict(encoder1.predict(x_train)))
In [69]:
value1 = [x[0] for x in fourth_layer_code]
value2 = [x[1] for x in fourth_layer_code]
y_classes = y_train_cat
In [70]:
data = {'value1': value1, 'value2': value2, 'class' : y_classes}
data = pd.DataFrame.from_dict(data)
data.head()
Out[70]:
In [71]:
groups = data.groupby('class')
# Plot
fig, ax = plt.subplots(figsize=(20,10))
# plt.figure(figsize=(20,10))
ax.margins(0.05) # Optional, just adds 5% padding to the autoscaling
for name, group in groups:
ax.plot(group.value1, group.value2, marker='o', linestyle='', ms=3, label=name, alpha=0.7)
ax.legend()
plt.show()