#!/usr/bin/env python3 # -*- coding: UTF-8 -*- import shutil import numpy as np import cv2 from pymultilame import MyTools def sigmoid(x): return 1 / (1 + np.exp(-x)) def sigmoid_prime(z): return z * (1 - z) def relu(x): return np.maximum(0, x) def relu_prime(z): return np.asarray(z > 0, dtype=np.float32) class SemaphoreIA: def __init__(self, root, learningrate, failed=0): self.root = root self.learningrate = learningrate self.failed = failed self.tools = MyTools() # Dossier des ratés if self.failed: # Suppression du dossier failed et recréation pour le vider try: shutil.rmtree(self.root + 'failed') except: print('Pas de dossier failed') self.tools.create_directory(self.root + 'failed') # Réseau de neurones: colonne 1600 en entrée, 2 nodes de 100, sortie de 27 caractères self.layers = [1600, 100, 100, 27] # Fonction d'activation: imite l'activation d'un neuronne self.activations = [relu, relu, sigmoid] fichier = np.load(self.root + 'semaphore.npz') self.x_train, self.y_train = fichier['x_train'], fichier['y_train'] self.x_train = 1 - self.x_train self.x_test, self.y_test = self.x_train[50000:,:], self.y_train[50000:] self.x_train, self.y_train = self.x_train[:50000,:], self.y_train[:50000] # Affichage des images pour distraire cv2.namedWindow('img') def training(self): """Apprentissage avec 50 000 images. Poids enregistré dans weights.npy""" print("Training...") # Matrice diagonale de 1 diagonale = np.eye(27, 27) # globals() Return a dictionary representing the current global symbol table. self.activations_prime = [globals()[fonction.__name__ + '_prime'] for fonction in self.activations] node_dict = {} # Liste des poids # Initialisation des poids des nodes, pour ne pas à être à 0 # Construit 3 matrices (100x1600, 100x100, 27x100) # /np.sqrt() résultat expérimental de l'initialisation d'un gars qui s'appelle Xavier Glorot et d'un autre qui s'appelle He ! weight_list = [np.random.randn(self.layers[k+1], self.layers[k]) / \ np.sqrt(self.layers[k]) for k in range(len(self.layers)-1)] # vecteur_ligne = image en ligne à la 1ère itération # nombre_lettre = nombre correspondant à la lettre de l'image # i pour itération, vecteur_colonne = x_train de i, nombre_lettre = y_train de i for i, (vecteur_ligne, nombre_lettre) in enumerate(zip(self.x_train, self.y_train)): # Affichage pour distraire les mangalore if i % 10000 == 0: print(i, nombre_lettre) img = vecteur_ligne.reshape(40,40) * 255 img = cv2.resize(img, (600, 600), interpolation=cv2.INTER_AREA) cv2.imshow("img", img) cv2.waitKey(1) # la ligne devient colonne vecteur_colonne = np.array(vecteur_ligne, ndmin=2).T # Forward propagation node_dict[0] = vecteur_colonne for k in range(len(self.layers)-1): # weight_list[k] (100x1600, 100x100 27x100) vecteur_colonne (1600,) # z de format 100 x 1 z = np.dot(weight_list[k], vecteur_colonne) # self.activations = non linéaire sinon sortie fonction linéaire de l'entrée # imite le seuil d'activation électrique du neurone vecteur_colonne = self.activations[k](z) node_dict[k+1] = vecteur_colonne # Retro propagation, delta_a = écart entre la sortie réelle et attendue delta_a = vecteur_colonne - diagonale[:,[nombre_lettre]] # Parcours des nodes en sens inverse pour corriger proportionnellement # les poids en fonction de l'erreur par rapport à la valeur souhaitée # Descente du Gradient stochastique for k in range(len(self.layers)-2, -1, -1): delta_z = delta_a * self.activations_prime[k](node_dict[k+1]) delta_w = np.dot(delta_z, node_dict[k].T) delta_a = np.dot(weight_list[k].T, delta_z) # Pour converger vers le minimum d'erreur weight_list[k] -= self.learningrate * delta_w # Dans un fichier np.save(self.root + 'weights.npy', weight_list) print('weights.npy enregistré') cv2.destroyAllWindows() def testing(self): """Teste avec 10 000 images, retourne le ratio de bon résultats""" print("Testing...") weight_list = np.load(self.root + 'weights.npy') # Nombre de bonnes reconnaissance success = 0 # Dict avec le nombre d'erreurs par lettre failed_dict = {} for vecteur_ligne, nombre_lettre in zip(self.x_test, self.y_test): # image en ligne au 1er passage pour les failed img = vecteur_ligne.copy() for k in range(len(self.layers)-1): vecteur_ligne = self.activations[k](np.dot(weight_list[k], vecteur_ligne)) reconnu = np.argmax(vecteur_ligne) if reconnu == nombre_lettre: success += 1 else: if self.failed: self.write_failed(img, nombre_lettre, reconnu, success) if nombre_lettre in failed_dict: failed_dict[nombre_lettre] += 1 else: if self.failed: self.tools.create_directory(self.root + 'failed' + '/bad_' + str(nombre_lettre)) failed_dict[nombre_lettre] = 1 if self.failed: sorted_by_value = sorted(failed_dict.items(), key=lambda kv: kv[1], reverse=True) print(sorted_by_value) resp = 100.0 * success / len(self.x_test) return resp def write_failed(self, img, nombre_lettre, reconnu, S): """Les images avec erreur de reconnaisance sont copiées dans /semaphore/failed/bad_11/11_6_9067.png 11 est la lettre k, donc dans le dossier il ny a que la lettre k et le 2ème nombre est la lettre reconnue fausse """ name = str(nombre_lettre) + '_' + str(reconnu) + '_' + str(S) + '.png' fichier = self.root + 'failed' + '/bad_' + str(nombre_lettre) + '/' + name img = img.reshape(40,40) * 255 cv2.imwrite(fichier, img) if __name__ == "__main__": print(MyTools().get_absolute_path(__file__)) root = MyTools().get_absolute_path(__file__)[:-28] print("Current directory:", root) for i in range(5): learningrate = 0.022 failed = 0 sia = SemaphoreIA(root, learningrate, failed) sia.training() resp = sia.testing() print("Learningrate: {} Résultat {}".format(learningrate, round(resp, 1)))