import tensorflow as tf
tf.logging.set_verbosity(tf.logging.ERROR)
print(tf.__version__)

"""
Explanation: <a href="https://colab.research.google.com/github/DJCordhose/ai/blob/master/notebooks/2019_tf/autoencoders_tabular.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a>
Autoencoders on Tabular Data

Normalizaing data: https://www.tensorflow.org/api_docs/python/tf/keras/utils/normalize
Keras functional API for more flexible models: https://keras.io/getting-started/functional-api-guide/
Batch Size should not matter too much, but larger batch sizes might require more training epochs: https://ieeexplore.ieee.org/document/8323035
End of explanation
"""


!curl -O https://raw.githubusercontent.com/DJCordhose/deep-learning-crash-course-notebooks/master/data/insurance-customers-1500.csv

import pandas as pd

df = pd.read_csv('./insurance-customers-1500.csv', sep=';')

df.describe()

df.head()

import seaborn as sns

sample_df = df.sample(n=100, random_state=42)
sns.pairplot(sample_df, 
             hue="group", palette={0: '#AA4444', 1: '#006000', 2: '#EEEE44'},
#              kind='reg',
             size=5,
             diag_kind='kde',
             vars=['age', 'speed', 'miles'])

"""
Explanation: Loading and exploring our data set
This is a database of customers of an insurance company. Each data point is one customer. The group represents the number of accidents the customer has been involved with in the past

0 - red: many accidents
1 - green: few or no accidents
2 - yellow: in the middle
End of explanation
"""


from tensorflow import keras
from tensorflow.keras.layers import Input, Flatten, GlobalAveragePooling1D, Dense
from tensorflow.keras.models import Sequential, Model

encoding_dim = 32

# https://keras.io/getting-started/functional-api-guide/
input_data = Input(shape=(4,))
encoded = Dense(units=encoding_dim, activation='relu', name="encoder")(input_data)
decoded = Dense(units=4, activation='linear', name="decoder")(encoded)

autoencoder = Model(inputs=input_data, outputs=decoded)
autoencoder.compile(optimizer='adam', loss='mse')
autoencoder.summary();

"""
Explanation: The embedding network
Network Architecture
End of explanation
"""


X = df
BATCH_SIZE = 1 # larger batch size might force more epochs
%time history = autoencoder.fit(X, X, epochs=10, batch_size=BATCH_SIZE, verbose=1)

"""
Explanation: Training
End of explanation
"""


import matplotlib.pyplot as plt

plt.yscale('log')
plt.plot(history.history['loss'])

loss = autoencoder.evaluate(X, X, batch_size=BATCH_SIZE)
loss

samples = df.sample(10).reset_index(drop=True)
samples

predictions = pd.DataFrame(autoencoder.predict(samples), columns=["speed",	"age",	"miles",	"group"])
predictions

samples.subtract(predictions)

"""
Explanation: Evaluation
End of explanation
"""


from tensorflow.keras.initializers import glorot_normal

encoding_dim = 2
seed = 13 # make training results more deterministic

input_data = Input(shape=(4,))
# notice tanh
encoded = Dense(units=encoding_dim, activation='tanh', name="encoder", kernel_initializer=glorot_normal(seed=seed))(input_data)
decoded = Dense(units=4, activation='linear', name="decoder", kernel_initializer=glorot_normal(seed=seed))(encoded)

autoencoder = Model(inputs=input_data, outputs=decoded)
# this does not get much better than 210 as a loss (bad), so we can just as well get there a bit faster (10 instead of 50 epochs with standard lr)
adam = keras.optimizers.Adam(lr=0.01)
# adam = keras.optimizers.Adam()
autoencoder.compile(optimizer=adam, loss='mse')

X = df
BATCH_SIZE = 1
%time history = autoencoder.fit(X, X, epochs=10, batch_size=BATCH_SIZE, verbose=1)

plt.yscale('log')
plt.plot(history.history['loss'])

autoencoder.evaluate(X, X, batch_size=BATCH_SIZE)

# just average over all of them :D
predictions = pd.DataFrame(autoencoder.predict(samples), columns=["speed",	"age",	"miles",	"group"])
predictions

samples.describe()

"""
Explanation: Looks quite nice, but how impressed are you by abstracting 4 variables into 32 / Let us try dimension of 2
End of explanation
"""


from tensorflow.keras.initializers import glorot_normal

encoding_dim = 2
seed = 13 # if it trains still depens on initialization

input_data = Input(shape=(4,))
# notice relu
encoded = Dense(units=encoding_dim, activation='relu', name="encoder", kernel_initializer=glorot_normal(seed=seed))(input_data)
decoded = Dense(units=4, activation='linear', name="decoder", kernel_initializer=glorot_normal(seed=seed))(encoded)

autoencoder = Model(inputs=input_data, outputs=decoded)
# adam = keras.optimizers.Adam(lr=0.01)
adam = keras.optimizers.Adam()
autoencoder.compile(optimizer=adam, loss='mse')

X = df
BATCH_SIZE = 1
%time history = autoencoder.fit(X, X, epochs=10, batch_size=BATCH_SIZE, verbose=1)

plt.yscale('log')
plt.plot(history.history['loss'])

samples

"""
Explanation: relu makes it train
End of explanation
"""


predictions = pd.DataFrame(autoencoder.predict(samples), columns=["speed",	"age",	"miles",	"group"])
predictions

samples.subtract(predictions)

"""
Explanation: not too bad, but all groups close to 1, all off as do not add much to loss
End of explanation
"""


df = pd.read_csv('./insurance-customers-1500.csv', sep=';')
normalized_df = (df - df.mean()) / df.std()
normalized_df.head()

X = normalized_df

encoding_dim = 2
seed = 13 # make results determinisitic

input_data = Input(shape=(4,))
encoded = Dense(units=encoding_dim, activation='relu', name="encoder", kernel_initializer=glorot_normal(seed=seed))(input_data)
decoded = Dense(units=4, activation='linear', name="decoder", kernel_initializer=glorot_normal(seed=seed))(encoded)

autoencoder = Model(inputs=input_data, outputs=decoded)
# adam = keras.optimizers.Adam(lr=0.01)
adam = keras.optimizers.Adam()
autoencoder.compile(optimizer=adam, loss='mse')

BATCH_SIZE = 1
%time history = autoencoder.fit(X, X, epochs=10, batch_size=BATCH_SIZE, verbose=1)

plt.yscale('log')
plt.plot(history.history['loss'])

"""
Explanation: Let's try normalizing the data first (caution: losses do not compare any more)
End of explanation
"""


encoder = Model(inputs=input_data, outputs=encoded)
latent_representation = encoder.predict(X)

latent_representation.shape

"""
Explanation: This makes it very hard to estimate how well we did our job, but we can still plot the embedding
Could be interpreted as a fancy version of dimensionality reduction
End of explanation
"""


latent_x = latent_representation[:, 0]
latent_y = latent_representation[:, 1]
plt.scatter(latent_x, latent_y, alpha=0.5)

"""
Explanation: Look, ma, clusters
End of explanation
"""


from matplotlib.colors import ListedColormap

# * 0 - red: many accidents
# * 1 - green: few or no accidents
# * 2 - yellow: in the middle
colors = X['group']
color_map = ListedColormap(['#AA4444', '#006000', '#EEEE44'])

plt.scatter(latent_x, latent_y, alpha=0.5, s=100, marker='o', edgecolors='w', cmap=color_map, c=colors)

"""
Explanation: Clusters have actual meaning
End of explanation
"""


