Ceci est une ancienne révision du document !
Table des matières
L'intelligence du sémaphore
Les images à reconnaître
La Reconnaissance faciale, c'est cool !
Des maths !
Relu Rectifier neural networks
- Rectifier (neural networks) sur Wikipedia en
def relu(x): """Rectified Linear Unit: In the context of artificial neural networks, the rectifier is an activation function defined as the positive part of its argument. Rectifie les négatifs à 0: -1 > 0 1 > 1 """ return np.maximum(0, x)
Relu_prime
- Fonction de Heaviside sur Wikipedia fr
- Fonction caractéristique (théorie des ensembles) sur Wikipedia fr
def relu_prime(z): """La fonction de Heaviside (également fonction échelon unité, fonction marche d'escalier) est la fonction indicatrice de R. Une fonction fonction indicatrice, est une fonction définie sur un ensemble E qui explicite l’appartenance ou non à un sous-ensemble F de E de tout élément de E. C'est donc la fonction H (discontinue en 0) prenant la valeur 1 pour tous les réels positifs et la valeur 0 pour les réels strictement négatifs. """ return np.asarray(z > 0, dtype=np.float32)
Sigmoïd
- Sigmoïde sur Wikipedia fr
Elle représente la fonction de répartition de la loi logistique. Elle est souvent utilisée dans les réseaux de neurones parce qu'elle est dérivable, ce qui est une contrainte pour l'algorithme de rétropropagation de Werbos. La forme de la dérivée de sa fonction inverse est extrêmement simple et facile à calculer, ce qui améliore les performances des algorithmes.
def sigmoid(x): """la fonction sigmoïde est une courbe en S: https://fr.wikipedia.org/wiki/Sigmo%C3%AFde_(math%C3%A9matiques)""" return 1 / (1 + np.exp(-x))
Sigmoïd prime
def sigmoid_prime(z): """La dérivée de la fonction sigmoid.""" return z * (1 - z)
Algorithme du gradient stochastique
L'algorithme du gradient stochastique est une méthode de descente de gradient (itérative) utilisée pour la minimisation d'une fonction objectif qui est écrite comme une somme de fonctions différentiables.
Diagonale de 1
numpy.eye(N, M=None, k=0, dtype=<class 'float'>, order='C')\\ Return a 2-D array with ones on the diagonal and zeros elsewhere.
Matrice ou la sortie est idéale: le 1 correspond à entée[i] = sortie[i], et entée[j],sortie[k] =0 si j différent de k
Initialisation de X. Glorot et He
X = Xavier = prénom
Réseau de neurones Perceptron multicouches
Un réseau de neurones Perceptron multicouches est un type de réseau dont l'information circule dans un unique sens, de la couche d'entrée vers la couche de sortie. Ont dit qu'il est un réseau “à propagation directe” (feedforward).
Notre réseau
Réseau de neurones: Une colonne de 1600 en entrée, 2 nodes de 100, une sortie de 27 caractères.
Réseau de neurones Convolutif
Un réseau de neurones Convolutif est un type de réseau de neurones artificiels dans lequel le motif de connexion entre les neurones est inspiré par le cortex visuel des animaux. Actuellement, il est très utilisé pour l'analyse des images, des vidéos et du langage naturel.
Du code expliqué avec beaucoup d'amour
Enfin, là c'est de l'intelligence qu'on cherche, pas de l'amour.
La totalité du projet est à Semaphore sur Github, et Jeu du sémaphore dans le Blender Game Engine pour la création des images.
<file ia.py python> #!/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 60 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 de Glorot He Xavier 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 # TODO: mettre ça dans un truc à l'ext de cette méthode 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 neuronne 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 proportionnellemnt # 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: # TODO: mettre ça dans un truc à l'ext de cette méthode 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): print("Petit test de l'influence du random dans la liste des poids") learningrate = 0.022 failed = 0 sia = SemaphoreIA(root, learningrate, failed) sia.training() resp = sia.testing() print("Learningrate: {} Résultat {}".format(learningrate, round(resp, 1)))
</code>
La reconnaissance seule
L'image en entrée doit être:
- 40 x 40 pixels
- en noir et blanc convertit en 0 et 1, que des 0 et des 1 dans le array
- Floutée entre 5 et 7
- Convertie en vecteur ligne: img = img.reshape(1600)
Les poids
Le fichier weights.npy doit être dans le dossier du script. weights.npy téléchargeable sur GitHub.
La class Reconnaissance
import numpy as np import cv2 def sigmoid(x): return 1 / (1 + np.exp(-x)) def relu(x): return np.maximum(0, x) class Reconnaissance: def __init__(self): self.weight = np.load('weights.npy') def testing(self, img): vecteur_ligne = img layers = [1600, 100, 100, 27] activations = [relu, relu, sigmoid] for k in range(len(layers)-1): vecteur_ligne = activations[k](np.dot(self.weight[k], vecteur_ligne)) reconnu = np.argmax(vecteur_ligne) return reconnu
Adaptation de l'image
# frame peut être la capture d'une webcam cv2.imshow('RGB Input', frame) # Application d'un seuil pour extraire le sémaphore du fond # Le seuil est à adapter en fonction de la couleur du sémaphore hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) lower = np.array([120, 80, 80]) upper = np.array([255, 255, 255]) img = cv2.inRange(hsv, lower, upper) # Flou: GaussianBlur semble mieux que Averaging=cv2.blur() img = cv2.GaussianBlur(img, (5, 5), 0) # Resize img = cv2.resize(img, (40, 40), interpolation=cv2.INTER_AREA) # Noir et blanc, sans gris ret, nb = cv2.threshold(img, 2, 255, cv2.THRESH_BINARY) # Valeur 0 ou 1 nb = nb / 255 # Reshape pour avoir un vecteur ligne vect = nb.reshape(40*40) reco = Reconnaissance() reconnu = reco.testing(vect) print("Caractère reconnu:", reconnu)
Les fichiers pour un test avec Webcam et un sémaphore en carton
Etape suivante: Mask R-CNN
Solution déposée en 2017 par un chercheur chez F…B…k. Computer Vision and Pattern Recognition Mask R-CNN
Idées de choses à faire par Max
- Essayer d'implémenter un petit réseau de neurones convolutif pas trop compliqué.
- Essayer de reconnaître les coordonnées de la position du sémaphore (un quadrilatère entourant le sémaphore sur l'image) en plus de sa valeur.
- Essayer de reconnaître la forme du sémaphore plutôt que l'associé à un symbole : Reconnaître la position du régulateur et des 2 indicateurs. Les buts étant de pouvoir changer l'alphabet sans réapprendre, et de pouvoir dessiner un sémaphore schématique tel que reconnu par l'IA.
- Essayer de reconnaître plusieurs sémaphores simultanément. Le but est de pouvoir choisir lequel on veut pouvoir imiter.
Idée directrice
Réaliser 2 apprentissages indépendants pour faciliter l'apprentissage et ajouter du filtrage intermédiaire. Je propose de réaliser un premier apprentissage qui reconnaîtrait la position d'un sémaphore dans une image. Il retournerait ses coordonnées. Le second apprentissage reconnaîtrait la forme d'un sémaphore dans un cadre particulier.
Je vois plusieurs avantages :
- Nous savons que dans une vidéo, un sémaphore ne peut pas se promener d'un bout à l'autre d'une frame de manière discontinue. On peut donc facilement filtrer les faux positifs de détection d'un sémaphore du premier apprentissage qui se déplaceraient de manière discontinue.
- Une fois la première reconnaissance réalisé il est facile de recadrer l'image en entrée et la présenter dans un format particulier au second apprentissage (ex: 40px x 40px).
- Il est plus facile d'apprendre à détecter plusieurs positions de sémaphore simultanés que de reconnaitre la forme de plusieurs sémaphore simultanément.
- Si nous reconnaissons plusieurs sémaphores simultanément, nous pourrons lancer un traitement de reconnaissance de leur forme pour chaque détection indépendament.
- Un filtrage intelligent pourrait comprendre qu'un sémaphore est occulté par un obstacle mouvant avant d'etre de nouveau visible.
Petit réseau convolutif
- En entré, il faudrait une première couche identique, et une deuxième couche qui n'est pas relié à tous les neurones de la première couche, mais uniquement à un ensemble représentant une tuile. Il faut donc modifier l'algo actuel qui relie toujours tous les neurones de la couche N-1 à tous les neurones de la couche N.
- Comment paver l'image de tuiles ? Est ce que les tuiles doivent se chevaucher ? Il serait interessant de variabiliser la taille des tuiles et leur chevauchement.
Reconnaître les coordonnées du sémaphore
- Il faut lors de la génération des images ajouter une méta information dans l'image indiquant dans les coordonnées d'un quadrilataire inscrivant le sémaphore.
- Quels types de coordonnées ? Les coordonées de 2 angles opposés ? les coordonées d'un angle et 2 distances ? autre ?
- Comment modifier la sortie du réseau de neurones pour reconnaître le couple (position du sémaphore, valeur) ?
Reconnaître la forme du sémaphore
- Plutôt que de tagger l'image avec un symbole, il faudrait la tagger avec la forme du sémaphore.
- Quels informations pour coder la forme ? Les 3 Angles absolue du régulateur et des indicateurs ?
- Comment modifier la sortie du réseau de neurones pour reconnaître le couple (position du sémaphore, forme du sémaphore) ?
Reconnaître plusieurs sémaphores
- Comment modifier la sortie du réseau de neurones pour reconnaître plusieurs couples (position, forme) ?