diff --git a/TP5/.gitignore b/TP5/.gitignore new file mode 100644 index 0000000..f1b747d --- /dev/null +++ b/TP5/.gitignore @@ -0,0 +1,2 @@ +*.csv +*.png \ No newline at end of file diff --git a/TP5/README.md b/TP5/README.md new file mode 100644 index 0000000..008059f --- /dev/null +++ b/TP5/README.md @@ -0,0 +1,15 @@ +# TP5 + +## 72.27 - Sistemas de Inteligencia Artificial - 2º cuatrimestre 2022 + +### Instituto Tecnológico de Buenos Aires (ITBA) + +## Autores + +- [Sicardi, Julián Nicolas](https://github.com/Jsicardi) - Legajo 60347 +- [Quintairos, Juan Ignacio](https://github.com/juaniq99) - Legajo 59715 +- [Zavalia Pángaro, Salustiano Jose](https://github.com/szavalia) - Legajo 60312 + +## Índice +- [Autores](#autores) +- [Índice](#índice) \ No newline at end of file diff --git a/TP5/TP5 - Presentacion.pdf b/TP5/TP5 - Presentacion.pdf new file mode 100644 index 0000000..60ce700 Binary files /dev/null and b/TP5/TP5 - Presentacion.pdf differ diff --git a/TP5/algorithms/autoencoder.py b/TP5/algorithms/autoencoder.py new file mode 100644 index 0000000..103e9c9 --- /dev/null +++ b/TP5/algorithms/autoencoder.py @@ -0,0 +1,139 @@ +from models import Autoencoder, Observables, Properties +import numpy as np +import scipy.optimize as sco +from algorithms.noiser import noise_font +from io_parser import generate_output_file,generate_samples_file +from autograd.misc.optimizers import adam +import numdifftools as nd + +def execute(properties:Properties): + # Create autoencoder + autoencoder:Autoencoder = build_autoencoder(properties) + + # Train + trained_weigths = train_autoencoder(autoencoder,properties) + + autoencoder.weights = autoencoder.unflatten_weights(trained_weigths) + + # After training: + # Get outputs for inputs + unflattened = autoencoder.unflatten_weights(trained_weigths) + output = autoencoder.get_output(properties.training_set, unflattened) + + # If mode was DAE, get new noised font and see how well it denoises + noised_font = None + noised_output = None + if properties.mode == "DAE": + noised_font = noise_font(properties.orig_training_set, properties.noise_prob) + noised_output = [] + for letter in noised_font: + noised_output.append(autoencoder.get_output(letter,unflattened)) + # print error for noised font + print("\nError for new noised font: " + str(autoencoder.error_given_sets(trained_weigths, noised_font))) + + generate_output_file(properties.mode,properties.training_set,output,noise_font,noised_output) + latent_outputs = get_latent_outputs(autoencoder,properties) + + if properties.mode == "DEFAULT" and properties.neurons_per_layer[autoencoder.latent_index] == 2: + distances = get_distances(latent_outputs) + pairs = [distances[0][:2],distances[int(len(distances)/2)][:2],distances[-1][:2]] + + samples = [] + char_pairs = [] + for pair_indexes in pairs: + value1 = latent_outputs[pair_indexes[0]] + value2 = latent_outputs[pair_indexes[1]] + char_pairs.append([properties.font_chars[pair_indexes[0]],properties.font_chars[pair_indexes[1]]]) + samples.append(get_samples(value1,value2,autoencoder)) + + generate_samples_file(char_pairs,samples) + + return Observables(autoencoder.errors_per_step,latent_outputs) + +def build_autoencoder(properties:Properties): + weights = [] + + # Add layers + for layer_index, neurons_count in enumerate(properties.neurons_per_layer): + weights.append([]) + for index in range(0, neurons_count): + if layer_index == 0: + w = np.random.randn(len(properties.training_set[0])) + else: + w = np.random.randn(len(weights[-2])) + weights[-1].append(w) + + # Add output layer of decoder + # Number of neurons in output layer depends on number of camps in expected output values + weights.append([]) + for i in range(len(properties.output_set[0])): + w = np.random.randn(len(weights[-2])) + weights[-1].append(w) + + # Convert to ndarray + for (i, layer) in enumerate(weights): + weights[i] = np.array(layer, dtype=float) + + return Autoencoder(np.array(weights, dtype=object),int((len(properties.neurons_per_layer))/2),properties.training_set,properties.output_set,properties.neurons_per_layer) + +def flatten_weights(weigths): + flattened_weights = np.array([]) + for (i,layer) in enumerate(weigths): + flattened_weights = np.append(flattened_weights,layer.flatten()) + return flattened_weights.flatten() + +def train_autoencoder(autoencoder:Autoencoder,properties:Properties): + flattened_weights = flatten_weights(autoencoder.weights) + + # Optimize + if properties.minimizer == "powell": + trained_weights = sco.minimize( + autoencoder.error, flattened_weights, method='Powell', callback=autoencoder.callback, + options={'maxiter': properties.epochs} + ).x + elif properties.minimizer == "adam": + trained_weights = adam(nd.Gradient(autoencoder.error), flattened_weights, callback=autoencoder.callback, num_iters=properties.epochs, step_size=0.1, b1=0.9, b2=0.999, eps=10**-2) + + return trained_weights + +def get_latent_outputs(autoencoder:Autoencoder,properties:Properties): + return autoencoder.get_latent_output(properties.training_set) + +def get_decoder_outputs(autoencoder:Autoencoder,samples): + return autoencoder.get_decoder_output(samples) + +def get_distances(points): + distances = [] + for (i,value) in enumerate(points): + for j in range(i+1, len(points)): + distances.append(np.array([int(i),int(j),np.linalg.norm(value - points[j])], dtype = object)) + distances = np.array(distances, dtype = object) + return distances[distances[:, 2].argsort()] + +def line(m,b,x): + return m*x - b + +def get_samples(point1,point2,autoencoder:Autoencoder): + m = (point1[1]-point2[1]) / (point1[0]-point2[0]) + b = (point1[0]*point2[1] - point2[0]*point1[1])/(point1[0]-point2[0]) + + min_x = min(point1[0],point2[0]) + max_x = max(point1[0],point2[0]) + inputs = [] + + print(np.linspace(min_x,max_x,5)) + for x in np.linspace(min_x,max_x,5): + y = line(m,b,x) + inputs.append(np.array([x,y])) + + inputs = np.array(inputs) + + #print(inputs) + + outputs = autoencoder.get_decoder_output(inputs) + + return outputs + + + + diff --git a/TP5/algorithms/noiser.py b/TP5/algorithms/noiser.py new file mode 100644 index 0000000..b2fd3ae --- /dev/null +++ b/TP5/algorithms/noiser.py @@ -0,0 +1,19 @@ +import random + +# Adds noise to all elements in font, with probability prob to switch binary value +def noise_font(font, prob): + new_font = [] + for letter in font: + new_font.append(noise_elem(letter, prob)) + return new_font + + +# Adds noise to single character image +def noise_elem(elem, prob): + new_elem = [] + for bit in elem: + if random.random() < prob: + new_elem.append(1 - bit) + else: + new_elem.append(bit) + return new_elem \ No newline at end of file diff --git a/TP5/algorithms/vae.py b/TP5/algorithms/vae.py new file mode 100644 index 0000000..ff77f1a --- /dev/null +++ b/TP5/algorithms/vae.py @@ -0,0 +1,130 @@ +from models import Properties, VAEObservables +from keras.layers import Input, Dense, Lambda, Reshape +from keras.models import Model +from keras import backend as K +from keras import metrics +import tensorflow as tf +import numpy as np +from keras.datasets import mnist,fashion_mnist +import matplotlib.pyplot as plt +from scipy.stats import norm + +from tensorflow.python.framework.ops import disable_eager_execution +disable_eager_execution() + +class VAE: + def __init__(self,latent_neurons,dim,intermediate_layers): + self.latent_neurons = latent_neurons + self.dim = dim + self.intermediate_layers = intermediate_layers + self.set_vae() + + def train(self,training_set,epochs,batch_size): + self.model.fit(training_set,training_set,epochs=epochs) + + def set_vae(self): + # input to our encoder + x = Input(shape=(self.dim,), name="input") + self.encoder = self.set_encoder(x) + # print out summary of what we just did + self.encoder.summary() + self.decoder = self.set_decoder() + self.decoder.summary() + # grab the output. Recall, that we need to grab the 3rd element our sampling z + output_combined = self.decoder(self.encoder(x)[2]) + # link the input and the overall output + self.model = Model(x, output_combined) + # print out what the overall model looks like + self.model.summary() + self.model.compile(loss=self.vae_loss) + + def set_encoder(self,x): + # intermediate layers + if(len(self.intermediate_layers) != 0): + aux_h = x + for (i,neurons) in enumerate(self.intermediate_layers): + h = Dense(neurons,activation='relu', name="encoding_{0}".format(i))(aux_h) + aux_h = h + # defining the mean of the latent space + self.z_mean = Dense(self.latent_neurons, name="mean")(h) + # defining the log variance of the latent space + self.z_log_var = Dense(self.latent_neurons, name="log-variance")(h) + # note that "output_shape" isn't necessary with the TensorFlow backend + z = Lambda(self.get_samples, output_shape=(self.latent_neurons,))([self.z_mean, self.z_log_var]) + # defining the encoder as a keras model + encoder = Model(x, [self.z_mean, self.z_log_var, z], name="encoder") + return encoder + + def set_decoder(self): + # Input to the decoder + input_decoder = Input(shape=(self.latent_neurons,), name="decoder_input") + # intermediate layers + reversed_layers = self.intermediate_layers.copy() + reversed_layers.reverse() + if(len(self.intermediate_layers) != 0): + aux_h = input_decoder + for (i,neurons) in enumerate(reversed_layers): + h = Dense(neurons,activation='relu',name="encoding_{0}".format(i))(aux_h) + aux_h = h + #getting the mean from the original dimension + x_decoded = Dense(self.dim, activation='sigmoid', name="flat_decoded")(h) + # defining the decoder as a keras model + decoder = Model(input_decoder, x_decoded, name="decoder") + return decoder + + def get_samples(self,args: tuple): + # we grab the variables from the tuple + z_mean, z_log_var = args + print(z_mean) + print(z_log_var) + epsilon = K.random_normal(shape=(K.shape(z_mean)[0], self.latent_neurons), mean=0.,stddev=1.0) + return z_mean + K.exp(z_log_var / 2) * epsilon # h(z) + + def vae_loss(self,x: tf.Tensor, x_decoded_mean: tf.Tensor): + # Aca se computa la cross entropy entre los "labels" x que son los valores 0/1 de los pixeles, y lo que salió al final del Decoder. + xent_loss = self.dim * metrics.binary_crossentropy(x, x_decoded_mean) # x-^X + kl_loss = - 0.5 * K.sum(1 + self.z_log_var - K.square(self.z_mean) - K.exp(self.z_log_var), axis=-1) + vae_loss = K.mean(xent_loss + kl_loss) + return vae_loss + +def generate_samples(dataset,vae:VAE): + n = 15 # figure with 15x15 digits + digit_size = 28 + figure = np.zeros((digit_size * n, digit_size * n)) + # linearly spaced coordinates on the unit square were transformed through the inverse CDF (ppf) of the Gaussian + # to produce values of the latent variables z, since the prior of the latent space is Gaussian + grid_x = norm.ppf(np.linspace(0.05, 0.95, n)) + grid_y = norm.ppf(np.linspace(0.05, 0.95, n)) + + for i, yi in enumerate(grid_x): + for j, xi in enumerate(grid_y): + z_sample = np.array([[xi, yi]]) + x_decoded = vae.decoder.predict(z_sample) + digit = x_decoded[0].reshape(digit_size, digit_size) + figure[i * digit_size: (i + 1) * digit_size, + j * digit_size: (j + 1) * digit_size] = digit + + plt.figure(figsize=(10, 10)) + plt.imshow(figure, cmap='Greys_r') + if(dataset == "mnist"): + plt.savefig("mnist.png") + else: + plt.savefig("fashion_mnist.png") + +def execute(properties:Properties): + if(properties.VAE_dataset == "mnist"): + (x_train, y_train), (x_test, y_test) = mnist.load_data() + else: + (x_train, y_train), (x_test, y_test) = fashion_mnist.load_data() + x_train = x_train.astype('float32') / 255. + x_test = x_test.astype('float32') / 255. + x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:]))) + x_test = x_test.reshape((len(x_test), np.prod(x_test.shape[1:]))) + vae = VAE(2,28*28,[256]) + vae.train(x_train,properties.epochs,100) + latent_outputs = vae.encoder.predict(x_test, batch_size=100)[0] + generate_samples(properties.VAE_dataset,vae) + return VAEObservables(latent_outputs,y_test) + +def flatten_set(training_set): + return np.array(training_set).reshape((len(training_set), np.prod(np.array(training_set).shape[1:]))) diff --git a/TP5/config.json b/TP5/config.json new file mode 100644 index 0000000..3d34a88 --- /dev/null +++ b/TP5/config.json @@ -0,0 +1,12 @@ +{ + "mode": "DEFAULT", + "vae_dataset" : "fashion_mnist", + "minimizer": "powell", + "noise_probability": 0.1, + "neurons_per_layer": [20], + "latent_layer_neurons": 2, + "font_set": "font2", + "font_subset_size": 10, + "beta": 1, + "epochs": 10 +} \ No newline at end of file diff --git a/TP5/fonts.py b/TP5/fonts.py new file mode 100644 index 0000000..b93352b --- /dev/null +++ b/TP5/fonts.py @@ -0,0 +1,141 @@ +font_sets = { + "font1": [ + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # 0x20, space + [0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04], # 0x21, ! + [0x09, 0x09, 0x12, 0x00, 0x00, 0x00, 0x00], # 0x22, " + [0x0a, 0x0a, 0x1f, 0x0a, 0x1f, 0x0a, 0x0a], # 0x23, # + [0x04, 0x0f, 0x14, 0x0e, 0x05, 0x1e, 0x04], # 0x24, $ + [0x19, 0x19, 0x02, 0x04, 0x08, 0x13, 0x13], # 0x25, % + [0x04, 0x0a, 0x0a, 0x0a, 0x15, 0x12, 0x0d], # 0x26, & + [0x04, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00], # 0x27, ' + [0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02], # 0x28, ( + [0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08], # 0x29, ) + [0x04, 0x15, 0x0e, 0x1f, 0x0e, 0x15, 0x04], # 0x2a, * + [0x00, 0x04, 0x04, 0x1f, 0x04, 0x04, 0x00], # 0x2b, + + [0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x08], # 0x2c, , + [0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00], # 0x2d, - + [0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0c], # 0x2e, . + [0x01, 0x01, 0x02, 0x04, 0x08, 0x10, 0x10], # 0x2f, / + [0x0e, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0e], # 0x30, 0 + [0x04, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0e], # 0x31, 1 + [0x0e, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1f], # 0x32, 2 + [0x0e, 0x11, 0x01, 0x06, 0x01, 0x11, 0x0e], # 0x33, 3 + [0x02, 0x06, 0x0a, 0x12, 0x1f, 0x02, 0x02], # 0x34, 4 + [0x1f, 0x10, 0x1e, 0x01, 0x01, 0x11, 0x0e], # 0x35, 5 + [0x06, 0x08, 0x10, 0x1e, 0x11, 0x11, 0x0e], # 0x36, 6 + [0x1f, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08], # 0x37, 7 + [0x0e, 0x11, 0x11, 0x0e, 0x11, 0x11, 0x0e], # 0x38, 8 + [0x0e, 0x11, 0x11, 0x0f, 0x01, 0x02, 0x0c], # 0x39, 9 + [0x00, 0x0c, 0x0c, 0x00, 0x0c, 0x0c, 0x00], # 0x3a, : + [0x00, 0x0c, 0x0c, 0x00, 0x0c, 0x04, 0x08], # 0x3b, ; + [0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02], # 0x3c, < + [0x00, 0x00, 0x1f, 0x00, 0x1f, 0x00, 0x00], # 0x3d, = + [0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08], # 0x3e, > + [0x0e, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04] # 0x3f, ? + ], + + "font2": [ + [0x0e, 0x11, 0x17, 0x15, 0x17, 0x10,0x0f], # 0x40, @ + [0x04, 0x0a, 0x11, 0x11, 0x1f, 0x11, 0x11], # 0x41, A + [0x1e, 0x11, 0x11, 0x1e, 0x11, 0x11, 0x1e], # 0x42, B + [0x0e, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0e], # 0x43, C + [0x1e, 0x09, 0x09, 0x09, 0x09, 0x09, 0x1e], # 0x44, D + [0x1f, 0x10, 0x10, 0x1c, 0x10, 0x10, 0x1f], # 0x45, E + [0x1f, 0x10, 0x10, 0x1f, 0x10, 0x10, 0x10], # 0x46, F + [0x0e, 0x11, 0x10, 0x10, 0x13, 0x11, 0x0f], # 0x37, G + [0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11], # 0x48, H + [0x0e, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e], # 0x49, I + [0x1f, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0c], # 0x4a, J + [0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11], # 0x4b, K + [0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f], # 0x4c, L + [0x11, 0x1b, 0x15, 0x11, 0x11, 0x11, 0x11], # 0x4d, M + [0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11], # 0x4e, N + [0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e], # 0x4f, O + [0x1e, 0x11, 0x11, 0x1e, 0x10, 0x10, 0x10], # 0x50, P + [0x0e, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0d], # 0x51, Q + [0x1e, 0x11, 0x11, 0x1e, 0x14, 0x12, 0x11], # 0x52, R + [0x0e, 0x11, 0x10, 0x0e, 0x01, 0x11, 0x0e], # 0x53, S + [0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04], # 0x54, T + [0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e], # 0x55, U + [0x11, 0x11, 0x11, 0x11, 0x11, 0x0a, 0x04], # 0x56, V + [0x11, 0x11, 0x11, 0x15, 0x15, 0x1b, 0x11], # 0x57, W + [0x11, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x11], # 0x58, X + [0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04], # 0x59, Y + [0x1f, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1f], # 0x5a, Z + [0x0e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0e], # 0x5b, [ + [0x10, 0x10, 0x08, 0x04, 0x02, 0x01, 0x01], # 0x5c, \ + [0x0e, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0e], # 0x5d, ] + [0x04, 0x0a, 0x11, 0x00, 0x00, 0x00, 0x00], # 0x5e, ^ + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f] # 0x5f, _ + ], + + "font3": [ + [0x04, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00], # 0x60, ` + [0x00, 0x0e, 0x01, 0x0d, 0x13, 0x13, 0x0d], # 0x61, a + [0x10, 0x10, 0x10, 0x1c, 0x12, 0x12, 0x1c], # 0x62, b + [0x00, 0x00, 0x00, 0x0e, 0x10, 0x10, 0x0e], # 0x63, c + [0x01, 0x01, 0x01, 0x07, 0x09, 0x09, 0x07], # 0x64, d + [0x00, 0x00, 0x0e, 0x11, 0x1f, 0x10, 0x0f], # 0x65, e + [0x06, 0x09, 0x08, 0x1c, 0x08, 0x08, 0x08], # 0x66, f + [0x0e, 0x11, 0x13, 0x0d, 0x01, 0x01, 0x0e], # 0x67, g + [0x10, 0x10, 0x10, 0x16, 0x19, 0x11, 0x11], # 0x68, h + [0x00, 0x04, 0x00, 0x0c, 0x04, 0x04, 0x0e], # 0x69, i + [0x02, 0x00, 0x06, 0x02, 0x02, 0x12, 0x0c], # 0x6a, j + [0x10, 0x10, 0x12, 0x14, 0x18, 0x14, 0x12], # 0x6b, k + [0x0c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04], # 0x6c, l + [0x00, 0x00, 0x0a, 0x15, 0x15, 0x11, 0x11], # 0x6d, m + [0x00, 0x00, 0x16, 0x19, 0x11, 0x11, 0x11], # 0x6e, n + [0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e], # 0x6f, o + [0x00, 0x1c, 0x12, 0x12, 0x1c, 0x10, 0x10], # 0x70, p + [0x00, 0x07, 0x09, 0x09, 0x07, 0x01, 0x01], # 0x71, q + [0x00, 0x00, 0x16, 0x19, 0x10, 0x10, 0x10], # 0x72, r + [0x00, 0x00, 0x0f, 0x10, 0x0e, 0x01, 0x1e], # 0x73, s + [0x08, 0x08, 0x1c, 0x08, 0x08, 0x09, 0x06], # 0x74, t + [0x00, 0x00, 0x11, 0x11, 0x11, 0x13, 0x0d], # 0x75, u + [0x00, 0x00, 0x11, 0x11, 0x11, 0x0a, 0x04], # 0x76, v + [0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0a], # 0x77, w + [0x00, 0x00, 0x11, 0x0a, 0x04, 0x0a, 0x11], # 0x78, x + [0x00, 0x11, 0x11, 0x0f, 0x01, 0x11, 0x0e], # 0x79, y + [0x00, 0x00, 0x1f, 0x02, 0x04, 0x08, 0x1f], # 0x7a, z + [0x06, 0x08, 0x08, 0x10, 0x08, 0x08, 0x06], # 0x7b, { + [0x04, 0x04, 0x04, 0x00, 0x04, 0x04, 0x04], # 0x7c, | + [0x0c, 0x02, 0x02, 0x01, 0x02, 0x02, 0x0c], # 0x7d, } + [0x08, 0x15, 0x02, 0x00, 0x00, 0x00, 0x00], # 0x7e, ~ + [0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f] # 0x7f, DEL + ] +} + +font_names = { + "font1" : ["space", "!","\"\"", "#","$","%","&","'","(",")","*","+",",","-",".","/","0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?"], + "font2" : ["@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_"], + "font3": ["`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","{","|","}","~","DEL"] +} + +# From hex array to flattened binary array discarding 3 first bits +# e.g.: [0x04, 0x04, ...] -> [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, ...] +def font_char_to_bin_arr(hex_arr): + bin_arr = [] + for hex in hex_arr: + transformed = f'{hex:0>5b}' # To binary and drops 3 most significant bits + for char in transformed: + bin_arr.append(int(char)) + return bin_arr + + +# From full flattened 0/1s to matrix for graphication +# e.g.: [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, ...] -> [[0, 0, 1, 0, 0], [0, 0, 1, 0, 0, ], ...] +def bin_arr_to_bin_mat(bin_arr): + bin_mat = [] + for char in bin_arr: + char_mat = [] + for i in range(0, len(char), 5): + char_mat.append(char[i:i+5]) + bin_mat.append(char_mat) + + return bin_mat + + +# Prints a matrix of 0/1s in a readable way +def print_bin_mat(bin_mat): + for row in bin_mat: + print(row) diff --git a/TP5/io_parser.py b/TP5/io_parser.py new file mode 100644 index 0000000..a963dba --- /dev/null +++ b/TP5/io_parser.py @@ -0,0 +1,166 @@ +import json +import numpy as np +from models import Properties,Observables, VAEObservables +import fonts +from algorithms.noiser import noise_font + +def generate_output(properties:Properties, observables:Observables): + print("Mode: {0}".format(properties.mode)) + print("Neurons per layer: {0}".format(properties.neurons_per_layer)) + print("Font: {0}".format(properties.font)) + print("Epochs: {0}".format(properties.epochs)) + generate_latent_outputs(observables.latent_outputs,properties.font_chars) + generate_errors_output(observables.errors_per_step) + +def generate_output_file(mode,training_set,output,noised_font,noised_output): + f = open("outputs.csv", "w") + f.write("Calculated,Letter,Row,Column,Value\n") + for (k,letter) in enumerate(training_set): + for i in range(7): + for j in range(5): + f.write(str(0) + "," + str(k) + "," + str(i) + "," + str(j) + "," + str(letter[i*5+j]) + "\n") + for (k,letter) in enumerate(output): + for i in range(7): + for j in range(5): + f.write(str(1) + "," + str(k) + "," + str(i) + "," + str(j) + "," + str(letter[i*5+j]) + "\n") + if mode == "DAE": + for (k,letter) in enumerate(noised_font): + for i in range(7): + for j in range(5): + f.write(str(2) + "," + str(k) + "," + str(i) + "," + str(j) + "," + str(letter[i*5+j]) + "\n") + for (k,letter) in enumerate(noised_output): + for i in range(7): + for j in range(5): + f.write(str(3) + "," + str(k) + "," + str(i) + "," + str(j) + "," + str(letter[i*5+j]) + "\n") + f.close() + +def generate_samples_file(char_pairs,samples): + f = open("samples.csv", "w") + f.write("Char1,Char2,Sample,Row,Column,Value\n") + for (h,char_pair) in enumerate(char_pairs): + for (k,sample) in enumerate(samples[h]): + for i in range(7): + for j in range(5): + f.write("{0},{1},{2},{3},{4},{5}\n".format(char_pair[0], char_pair[1], k, i, j,sample[i*5+j])) + f.close() + + +def generate_latent_outputs(latent_outputs, font_chars): + with open("latent.csv", "w") as f: + f.write("Char,X,Y\n") + for (i,output) in enumerate(latent_outputs): + f.write("{0},{1},{2}\n".format(font_chars[i], latent_outputs[i][0], latent_outputs[i][1])) + +def generate_errors_output(errors): + with open("errors.csv", "w") as f: + f.write("Step,Error\n") + for (i,error) in enumerate(errors): + f.write("{0},{1}\n".format(i+1, error)) + +def generate_VAE_output(properties:Properties,observables:VAEObservables): + print("Mode: {0}".format(properties.mode)) + print("Neurons per layer: {0}".format(properties.neurons_per_layer)) + print("Epochs: {0}".format(properties.epochs)) + generate_VAE_latent_outputs(observables) + +def generate_VAE_latent_outputs(observables:VAEObservables): + with open("VAE_latent.csv", "w") as f: + f.write("X,Y,Value\n") + for (i,output) in enumerate(observables.latent_outputs): + f.write("{0},{1},{2}\n".format(output[0], output[1],observables.colors[i])) + +def parse_properties(): + with open("config.json") as json_file: + json_values = json.load(json_file) + + + neurons_per_layer = get_hidden_layer_neurons(json_values.get("neurons_per_layer"), json_values.get("latent_layer_neurons")) + + training_set = None + font = None + font_chars = None + output_set = None + noise_prob = 0 + orig_training_set = None + mode = json_values.get("mode") + if(mode == "DAE" or mode == "DEFAULT"): + beta = json_values.get("beta") + if beta == None or beta <= 0: + print("Positive beta required") + exit(-1) + Properties.beta = beta + font = json_values.get("font_set") + font_chars = get_font_characters(font) + font_subset_size = json_values.get("font_subset_size") + training_set = get_training_set(font, font_subset_size) + orig_training_set = training_set + output_set = training_set + noise_prob = 0 + if (mode == "DAE"): + orig_training_set = training_set.copy() + orig_output_set = output_set.copy() + noise_prob = json_values.get("noise_probability") + training_set = noise_font(orig_training_set, noise_prob) + for i in range(4): + output_set += orig_output_set + training_set += noise_font(orig_training_set, noise_prob) + + epochs = json_values.get("epochs") + if epochs == None or epochs <= 0: + print("Positive epochs required") + exit(-1) + + dataset = None + if(mode == "VAE"): + dataset = json_values.get("vae_dataset") + if(dataset == None or (dataset != "mnist" and dataset != "fashion_mnist")): + print("Valid dataset for VAE required") + exit(-1) + + minimizer = json_values.get("minimizer") + if minimizer == None or (minimizer != "adam" and minimizer != "powell"): + print("Valid minimizer required: adam or powell") + exit(-1) + + return Properties(neurons_per_layer,font,font_chars,epochs,training_set,output_set,mode,noise_prob,orig_training_set,dataset, minimizer) + +# Assembles a decoder with the given neurons_per_layer, a latent layer with the given latent_layer_neurons, and a decoder reversing the encoder. +def get_hidden_layer_neurons(neurons_per_layer, latent_layer_neurons): + if neurons_per_layer == None or latent_layer_neurons == None: + return None + if len(neurons_per_layer) < 0 or latent_layer_neurons <= 0: + return None + + hidden_layer_neurons = neurons_per_layer.copy() + hidden_layer_neurons.append(latent_layer_neurons) + for neurons in reversed(neurons_per_layer): + hidden_layer_neurons.append(neurons) + + return hidden_layer_neurons + +# Sets the training set of the given properties to the given font set +def get_training_set(font_set, font_subset_size=None): + if font_set == None: + return None + if font_set not in fonts.font_sets: + return None + + training_set = [] + font_set = fonts.font_sets.get(font_set) + for letter in font_set: + binary_array = fonts.font_char_to_bin_arr(letter) + training_set.append(binary_array) + + + if font_subset_size != None and font_subset_size < len(training_set): + training_set = training_set[:font_subset_size] + + return training_set + +def get_font_characters(font_set): + return fonts.font_names.get(font_set) + + + + + diff --git a/TP5/main.py b/TP5/main.py new file mode 100644 index 0000000..f51f91b --- /dev/null +++ b/TP5/main.py @@ -0,0 +1,19 @@ +from models import Observables, Properties, VAEObservables +from io_parser import parse_properties,generate_output,generate_VAE_output +import algorithms.autoencoder as autoencoder +import algorithms.vae as vae +def __main__(): + + #Parse parameters + properties:Properties = parse_properties() + + if(properties.mode == "DEFAULT" or properties.mode == "DAE"): + observables:Observables = autoencoder.execute(properties) + generate_output(properties,observables) + elif(properties.mode == "VAE"): + observables:VAEObservables = vae.execute(properties) + generate_VAE_output(properties,observables) + + +if __name__ == "__main__": + __main__() \ No newline at end of file diff --git a/TP5/models.py b/TP5/models.py new file mode 100644 index 0000000..6e2c322 --- /dev/null +++ b/TP5/models.py @@ -0,0 +1,126 @@ +import numpy as np +import scipy.optimize as sco +import warnings +warnings.filterwarnings(action='ignore', category=RuntimeWarning) + +class Properties: + beta = 0 + def __init__(self,neurons_per_layer,font,font_chars,epochs,training_set,output_set,mode,noise_prob,orig_training_set,VAE_dataset,minimizer): + self.neurons_per_layer = neurons_per_layer + self.font = font + self.font_chars = font_chars + self.epochs = epochs + self.training_set = training_set + self.output_set = output_set + self.mode = mode + self.noise_prob = noise_prob + self.orig_training_set = orig_training_set + self.VAE_dataset = VAE_dataset + self.minimizer = minimizer + +class Observables: + def __init__(self,errors_per_step,latent_outputs): + self.errors_per_step = errors_per_step + self.latent_outputs = latent_outputs + +class VAEObservables: + def __init__(self,latent_outputs,colors): + self.latent_outputs = latent_outputs + self.colors = colors + +class Autoencoder: + def __init__(self,weights,latent_index,training_set,output_set,neurons_per_layer): + self.weights = weights + self.latent_index = latent_index + self.training_set = training_set + self.output_set = output_set + self.neurons_per_layer = neurons_per_layer + self.curr_epoch = 0 + self.functions = [] + self.set_functions() + self.errors_per_step = [] + + def set_functions(self): + for i in range(0,len(self.weights)): + if i <= self.latent_index: + self.functions.append(self.linear) + else: + self.functions.append(self.logistic) + + def callback(self,x,i=None,g=None): # Optional parameters needed for ADAM callback + self.errors_per_step.append(self.error(x)) + self.curr_epoch += 1 + print("Epoch: {1}. Error: {0}".format(self.errors_per_step[-1], self.curr_epoch)) + + def relu(self,x): + return np.where(x <= 0, 0, x) + + def logistic(self,x): + return 1 / (1 + np.exp(-2*Properties.beta * x)) + + def linear(self,x): + return x + + def get_output(self, input, weights): + for (i,layer) in enumerate(weights): + h = np.dot(input, layer.T) + # Transform dot products into activations + input = self.functions[i](h) + + # Return results of last layer (network outputs) + return input + + def get_latent_output(self,input): + for(i,layer) in enumerate(self.weights[:self.latent_index+1]): + h = np.dot(input, layer.T) + + # Transform dot products into activations + input = self.functions[i](h) + + # Return results of latent layer + return input + + def get_decoder_output(self,input): + for(i,layer) in enumerate(self.weights[self.latent_index+1:]): + h = np.dot(input, layer.T) + + # Transform dot products into activations + input = self.functions[i+self.latent_index+1](h) + + # Return results of latent layer + return input + + def error(self, weights, step=None): + error = 0 + unflattened_weights = self.unflatten_weights(weights) + expected = np.array(self.output_set) + output = self.get_output(self.training_set,unflattened_weights) + error = np.sum(np.power(output-expected,2)) + + return error*(1/2) + + + # For DAE where we need to pass new noised data + def error_given_sets(self, weights, input_set): + error = 0 + unflattened_weights = self.unflatten_weights(weights) + for (i,entry) in enumerate(input_set): + expected = np.array(self.output_set[i]) + output = np.array(self.get_output(entry, unflattened_weights)) + + for (idx,output_value) in enumerate(output): + error += (output_value - expected[idx])**2 + + return error*(1/2) + + # Converts from 1D array back to 3D structure + def unflatten_weights(self, array): + new_arr = [] + i = 0 + for layer in self.weights: + curr_size = layer.size + flatted = np.array(array[i:i+curr_size]) + new_arr.append(flatted.reshape(layer.shape)) + i += curr_size + new_arr = np.array(new_arr, dtype=object) + return new_arr