EihiS

December 28, 2016

Neural nets,bases en C, part 9 : Features

Filed under: linux, neuron — Tags: , , , , — admin @ 7:42 pm

## ajout, le 25-03-2017 : les images de ce post proviennent de programmes compilés avec mes librairies ‘maison’ qui utilisent la sdl2.0. pour faciliter les exemples dans les posts qui vont suivre bientôt, j’ai mis a disposition ces librairies , ‘telles qu’elles sont’ , dans le répertoire ‘DATA’ de ce site ( INDEX )

Je vous invite a lire cette page pour les utiliser

Ou on on ré-apprend à faire un dessin :

Si on devait décomposer l’original ( un carré ) en éléments, on peut rapidement discerner qu’il est composé de :

  • traits verticaux
  • traits horizontaux
  • et éventuellement, d’angles ( notés c, c’ c” c”’ )

Ce travail de décomposition de l’image, c’est tout l’art de l’artiste peintre qui traduit sur sa toile une scène qu’il a sous les yeux : du peintre débutant au plus expérimenté, les deux sachant décomposer, de facon de plus en plus précise la scène en éléments importants et caractéristiques : géométrie, couleurs …
Mais la précision du rendu de la scène dépend de l’artiste : un voudra rendre le plus précisement possible la scene, un autre rendra une scene plus abstraite, voire completement abstraite , n’en retenant que l’essence .

On va prendre l’exemple ci dessus pour un réseau tels que ceux décrits dans les posts précédents, et utiliser le réseau pour analyser une image en entrée .

Comment faire apprendre au réseau les ‘features’ d’interet d’une image qu’on lui fournit en entrée ? ( et quels sont ces ‘features’ ? )

La réponse à la question , c’est un reseau de ce type :

N x Entrées -> R x Hidden -> N x Sorties

Le réseau à le même nombre d’entrées que de sorties, mais, dans la couche cachée, on limite la quantité de neurones à une valeur inférieure au nombre d’entrées.

Pour l’apprentissage, on demande au réseau de recomposer exactement l’information fournie en entrée, rien de plus : c’est un apprentissage sans supervision
Posons N=9 , R=3 :

Du fait que le nombre de neurones de la couche cachée et inférieur au nombre d’entrées , il va y avoir ‘compression’ des données par le réseau, au niveau de la couche cachée ( réduction d’une information à 9 variables en une information à 3 variables )
Mais, dans le même temps, l’apprentissage lui demande de restituer, sur ses sorties, la même information qu’a l’entrée ( ce qui équivaut à décompresser maintenant , une information composée de 3 variables vers une information composée de 9 variables , la sortie ) , on effectue l’opération : vecteur(9) vers vecteur(3) vers vecteur(9) pour l’information.

Ce reseau est auto-encodeur,
Et une fois l’apprentissage sans supervision terminé, il est capable d’extraire des éléments clé (features) de n’importe qu’elle sortes de données en entrée, et ceci, sans aucune supervision.

Pour traiter une image bitmap en entrée, un petit schéma descriptif :

Pour les besoins du post, on pose :

  • le bitmap est composé de valeurs 0.0 a 1.0 (float) , ou bien -1.0 / 1.0 ) , equivalent a la luminosité du pixel.
  • la grille (tile) d’entrée vers le réseau est composée de 3×3pixels = 9 entrées ( a,b,c,d,e,f,g,h,i)
  • La couche cachée est composée de 3 neurones, chacun entièrement connectés aux entrées ( il sont chacun 9 entrées + une de bias )
  • La couche de sortie comporte 9 neurones, entièrement connectés a la couche cachée ( + entrée bias pour chacun ) , notés A,B,C,D,E,F,G,H,I
  • Cette couche de sortie recompose l’entrée de 3×3 pixels , de sorte que, en terme de coordonnées spatiales (x,y)  : a correspond a A , b correspond à B etc
On fait donc l’apprentissage pour que :
A B C
D E F
G H I
reflète les valeurs d’entrées :
a b c
d e f
g h i
Pour mieux comprendre, une capture d’écran , en bas de ce post
  • IN MAP est le bitmap fourni en entrée au réseau. il est parcouru de haut en bas, et de gauche a droite, en faisant des incréments de 3 pixels a chaque itération. a chaque pas, on effectue un cycle d’apprentissage au reseau.
Ci dessous, IN MAP , zoomé : la grille rouge représent les surfaces de 3×3 pixels qui sont parcourues lors de l’apprentissage.elle ne se recouvrent pas et sont contigues :
  • OUT MAP est la réponse en sortie du réseau, au bout de l’apprentissage (erreur RMS <0.01)
  • La sortie est faite sur le canal rouge
Pour afficher les éléments clé utilisés (les features), il suffit d’utiliser les 3 neurones de la couche cachée. en effet, leur déclenchement est combiné par la couche de sortie pour refabriquer l’image a l’identique que l’original :
Il suffit donc de visualiser les poids déclencheurs de ces 3 neurones pour afficher l’aspect du ‘feature’ utilisé pour chacun des 3 neurones.
  • C’est ce qui est visible sur la capture d’écran, noté ‘FEATURE FEATURE FEATURE’
  • Chaque feature a été colorisé Rouge,Vert,Bleu afin de pouvoir le distinguer dans la zone ‘F USAGE’
La zone ‘FEAT GROUPS’ permet ,grace a une SOM (Self Organizing Map) , de créer des groupes de déclenchements pour ces 3 features et de les visualiser. La carte SOM présente des groupes purs de BLEU , VERT , ROUGE, qui correspondent aux usages d’une feature unique pour fabriquer une sortie determinée, tandis que les autres groupes représentent différents mixages des quantitées des 3 neurones (rouge,vert,bleu) pour certains autres états de sortie a générer : c’est le mixage des features , utilisé par le réseau
  • F USAGE est la carte d’utilisation des features : chaque couleur de cette carte traduit le mixage qui a été fait entre les 3 features (préléve de la carte SOM), pour recréer la sortie le plus correctement possible, grace a l’apprentissage.ils sont repositionnés aux coordonnées x,y correspondantes au bitmap d’entrée, afin de mettre en évidence OU ils ont été utilisés, par rapport a l’image d’entrée :

Pour faciliter la compréhension, un overlay :
Les couleurs représentent chaque mixage de features utilisé, en superposition sur le bitmap original.
(En blanc, les pixels a 1.0 du bitmap d’entrée)

Une capture sur un bitmap différent :

314159265358979323846264338327950288
419716939937510582097494459230781640
628620899862803482534211706798214808

December 11, 2016

Neural net,deep learning,bases en C, part 3

Filed under: linux, neuron — Tags: , , , , , , — admin @ 7:55 pm
Pour continuer avec les posts précédents, et en gardant la configuration entrées/ expected formula ,

On peut tracer la sortie du reseau, et les états des variables back[ ] pour chaque entrées :

en abcisses, l’angle ‘ang’ , axe Y gauche pour ‘back A,B,C’ , axe Y droit pour ‘neuron’

Si, on modifie la valeur de inA (qui est le bias) de -1.0 , à -2.0 , sans modifier le reseau existant. il répond alors de la façon suivante :

On voit que la réponse a été translatée de +1.0 sur Y , ce qui est cohérent avec la valeur modifiée du bias , de -1.0 a -2.0 (elle est multipliée par approximative -1.0 (poids de l’entrée A )

Mais ce qui est remarquable, c’est la modifcation de la variable back pour A : elle est passée d’une valeur négative a une valeur positive différente.
On va faire effectuer de nouveau une suite de calculs avec le reseau, en gardant les poids calculés par l’apprentissage, et faire varier l’entrée A ( le bias) par des valeurs comprises en -4.0 et +4.0 ( tout en gardant en tete que la valeur originale du bias , pour ce reseau, est de -1.0 )

Ce qui donne :

On voit que back A , décroit jusqu’a un minima, puis croit de nouveau .
Pour être précis, on réduit la variation de l’entrée A entre -1.5 et -0.5 :

Le minima pour back A est situé entre deux passage par 0 de cette variable back[ ].
On change l’axe X pour qu’il représente la variable input A , au lieu des angles pour les cos/sin :

… clairement, BACK pour A passe par zéro en 2 points :

  • pour une valeur inputA de -1.0
  • pour une valeur inputA de 0.0
la première est la valeur de bias initiale ( -1.0) qui est solution de l’apprentissage qu’on a appliqué au reseau
la deuxième est le résultat d’un passage à ZERO de l’entrée A , puisque back est calculé selon la formule vue précédement :
n->back[k]=n->neuron*n->part[k]*n->in[k];

…Si in[k] passe par zéro, back[k] est nul, quelle que soit la valeur du reste de sa formule.

Pour cette raison, on va modifier la fonction BACK() de façon à utiliser une partie seulement de la fonction existante :

n->part[k]=(fabs(n->w[k])/n->wtot);	// proportion							// assume w always positive
n->ward[k]=n->error*n->w[k];// erreur * poids
n->back[k]=n->error*n->in[k];// erreur * valeur d'entree

Pour ça, on modifie la structure du reseau, et on crée une nouvelle fonction backWARD :

// la structure
typedef struct {
	uint32_t synapses;
	float* in;			// forward compute inputs
	float* back;		// backward computed inputs
	float* ward;
	float* w;			// forward/backward weight for each inputs
	float* part;
	float neuron;			// forward computed output, backward compute input
	float error;
	//float* delta;
	//
	//uint32_t genre;
	//
	float wtot;			// total input weights
	//
	uint64_t epoch;
	uint64_t internal;
} reso;

nouvelle fonction backward:

void RESO_backWARD(reso* n,float expected)
{
	uint32_t k;
	n->error=n->neuron-expected;	// actual_value - expected_value
	//
	for(k=0;k<n->synapses;k++)
	{
		n->part[k]=(fabs(n->w[k])/n->wtot);	// proportion							// assume w always positive
		n->ward[k]=n->error*n->w[k];// erreur * poids
		n->back[k]=n->error*n->in[k];// erreur * valeur d'entree
	}
	n->epoch++;
}

Pour terminer, un autre graphe d’un réseau pour lequel la valeur de BIAS était fixée à -2.5.

  • On la fait varier de -10.0 a 0.0, en fixant les 2 autres entrées à un état déterminé et connu

Sur le graphe, on peut voir back , ward et error (en abcisse, la valeur d’entrée du bias (nominal -2.5), les valeurs de ward sont sur l’axe Y de droite, celles de back et error sur l’axe Y de gauche
Dans le cas ou les valeurs des 2 autres entrées provoquent une sortie attendue à +1.0 :

Puis dans le cas ou les valeurs des 2 autres entrées provoquent une sortie attendue à -1.0 :

314159265358979323846264338327950288
419716939937510582097494459230781640
628620899862803482534211706798214808

December 10, 2016

Neural nets , deep learning, bases en C - part 1

Filed under: linux, neuron — Tags: , , , , , , — admin @ 7:15 pm

Les reseaux composés de plus d’une couche cachée sont plus difficiles que les autres à entrainer.
On trouve ça-et-la des articles sur le sujet , bourrés de maths, qui ont de quoi rebuter.

Les arXiv publiés sont nombreux..

Pour un réseau type “perceptron” à une couche ( N entrées vers une sortie unique) , partons sur la base suivante ( code C )
Attention: j’utilise des fonctions ‘maison’ qui peuvent différer des formes ‘canoniques’ si tant est qu’elles existent.
Je suis un codeur plus qu’un matheux. les programmes originaux sont souvent ‘mathéifiés’ par la suite, et non l’inverse.

Le calcul du reseau inverse, justement, est , si l’on pousse la reflection sur son mode de calcul, analogue aux phénomènes de mouvements oculaires rapides,et mouvements inconscients pendant les phases de sommeil, lors desquelles il est supposé (ou prouvé?) que le cerveau ‘renforce’ les liens neuronaux en ‘répétant’ des stimulis de la periode d’eveil. les vagues EEG mettent en evidence des mouvements de va-et-vient de l’avant a l’arrière du cortex des signaux electriques dans les reseaux de neurones cérébraux.

Les fonctions décrites ci-après agissent sur cette structure simple, qui décrit le reseau (désolé pour les ‘mix’ entre anglais,français. mes codes sont tjs commentés en anglais, j’ai étoffé en français pour cet post )  :

//
typedef struct {
	uint32_t synapses;// nombre d'entrées du neurone
	float* in;	// forward compute inputs
	float* back;	// backward computed inputs
	float* w;	// forward/backward weight for each inputs
	float* part;    // proportion rapportée a 1.0 pour chaque poids entrant
	float neuron;	// forward computed output, backward compute input
	float error;    // erreur en sortie
	float wtot;			// total input weights
	//
	uint64_t epoch;	// incrémenté a chaque function d'apprentissage appliquée (modification des poids)
	uint64_t internal;// incrémenté a chaque calcul de la sortie en fonction des entrées
} reso;

Une fonction qui initialise cette structure :

void RESO_init(reso* n,uint32_t input_count)
{
	uint32_t k;
	float init_w=1.0;// todo: use setup, default var instead
	n->in=(float*) malloc(sizeof(float)*input_count);
	n->back=(float*) malloc(sizeof(float)*input_count);
	n->w=(float*) malloc(sizeof(float)*input_count);
	n->part=(float*) malloc(sizeof(float)*input_count);
	//
	n->synapses=input_count;
	n->wtot=init_w*input_count;	//prepare startup total weights
	for(k=0;k<n->synapses;k++)
	{
		n->in[k]=0.0;
		n->w[k]=init_w;	// default...
		n->part[k]=n->w[k]/n->wtot;// back ready
	}
	n->neuron=0.0;
        n->error=0.0;
	//
	n->epoch=1;
	n->internal=1;
}

Maintenant, la fonction de calcul ‘FORWARD’ : Calcule la sortie en fonction des entrées

void RESO_forward(reso* n)
{
	uint32_t i;
	n->neuron=0.0;
	n->wtot=0.0;
	for(i=0;i<n->synapses;i++)
	{
	  n->neuron+=n->in[i]*n->w[i]; // somme des entrées*poids de cette entrée
	  n->wtot+=fabs(n->w[i]);      // cumule au passage les poids pour calculer le total des poids entrants
	}
	n->internal++;
}

On passe a deux fonctions essentielles dans l’apprentissage:

la fonction ‘BACKWARD’ qui effectue un calcul inverse (de la sortie vers les entrées), mais ne modifie aucun des poids du réseau :

//
void RESO_back(reso* n,float expected)
{
	uint32_t k;
	n->error=expected - n->neuron;	// AKA 'expected - what_i_got'
	//
	n->neuron=n->error;// prepare for back compute
	//
	for(k=0;k<n->synapses;k++)
	{
		n->part[k]=fabs(n->w[k])/n->wtot;	 // weight part always positive
		n->back[k]=n->neuron*n->part[k]*n->in[k];// back[] is the backward equivalent of in[]'s for the net
	}
	n->epoch++;
}

.. je reviendrai plus tard sur celle ci ainsi que sur la suivante : la fonction de modification du reseau ,qui s’execute après le calcul inverse (fonction précédente) :

void RESO_apply(reso* n,float rate)
{
	uint32_t k;
	for(k=0;k<n->synapses;k++)
	{
		n->w[k]+=n->back[k]*rate;
	}
}

En pratique, on peut tester tout ça avec un programme simple :

#include "../common/classics.h" // check for a post on the website about it
// ..les librairies classiques requises
#include "../common/neuron/RESO_library.c"
// c'est le nom que j'utilise
// pour le fichier ou sont les fonctions précédentes et la definition de la structure
int main(int argc, char **argv)
{
	// le reseau
	reso snet;
	reso* net=&snet;			// pointeur vers la structure
	//
	float expected;				// utile pour stocker ce qu'on attend en reponse
	float RMSerror,RMScompute;
	float ang;					// pour un angle, sinus/cosinus cf while()

	//
	uint32_t i;		// des variables d'utilité..
	//
	//
	RESO_init(net,3);// on initialise le reseau, avec 3 entrées
	// tous les poids a 1.0 pour tester
	net->w[0]=1.0;
	net->w[1]=1.0;
	net->w[2]=1.0;
	//
	RMSerror=0.0;
	RMScompute=1.0;
	// on définit ici pour les essais : les 3 entrées,et la sortie attendue par formule
	#define _INA	3.0
	#define _INB	0.0+sin(ang)
	#define _INC	0.0+cos(ang)
	// on pond une formule qui utilise les 3 entrées, pour tester le reseau
	#define _REPON	1.0+2.0*cos(ang)+3.0*sin(ang)
	//
	ang=0.0;
	while((RMScompute>0.01)||(net->epoch<2))// jusqu'a RMS erreur total <= 0.01
	{
		//
		net->in[0]=_INA;
		net->in[1]=_INB;
		net->in[2]=_INC;
		//
		expected=_REPON;
		ang+=2.6;// incrém. pour le prochain cycle du while, 2.6 est choisi comme ça..
		// *** CALCUL : FORWARD
		RESO_forward(net);
		// *** CALCUL : BACKWARD ( pour obtenir l'erreur de la sortie n->neuron )
		//  expected contient la valeur attendue, fournie a la fonction
		RESO_back(net,expected);
		// au passage, on fait un cumul d'erreur RMS pour superviser l'apprentissage du reseau
		RMSerror+=fabs(net->error);
		RMScompute=(RMSerror/(float)net->epoch);
		// epoque de l'apprentissage, erreur immédiate dela sortie, et erreur RMS total depuis le début
		printf("\nepoch[%6.6lu] e(%+6.6f) , RMS{%5.5f} ",net->epoch,net->error,RMScompute);
		printf("wtot[%+3.3f] --",net->wtot);	// au passage, le poids total des entrées a ce cycle
		// les 'n' entrées (ici 3) , leur part en % dans le résultat que fournit la sortie
		for(i=0;i<net->synapses;i++) { printf("(%+3.3f%c)",net->part[i]*100.0,'%'); }
		// final : on modifie le reseau, et rebouclage while()
		// on fournit la 'learning_rate' a la fonction ( vitesse d'apprentissage ) ,
		// c'est a dire en quelle quantité elle va venir modifier les poids.
		// le learning rate agit en dosage sur la modification.
		RESO_apply(net,0.05); // *** APPLIQUER modification aux poids ***

	}
	// si le seuil RMS est ok, affiche les infos finales poids/ part dans le résultat
	printf("\nFinal Parts :\n");
	for(i=0;i<net->synapses;i++) { printf("(%+3.3f%c)",net->part[i]*100.0,'%'); }
	printf("\nFinal Weights:\n");
	for(i=0;i<net->synapses;i++) { printf("(%+3.3f )",net->w[i]);}
	printf("\n");
	// on execute un cycle de calcul FORWARD uniquement, pour vérifier que
	// tout est OK , 20 fois :
	// 'test run'
	net->internal=0;
	while(net->internal<20)
	{
		net->in[0]=_INA;
		net->in[1]=_INB;
		net->in[2]=_INC;
		expected=_REPON;// uniquement pour afficher l'attendu VS la sortie du reseau
		ang+=2.6;//
		RESO_forward(net);
		printf("\nCycle[%6.6lu] EXPECTED(%+6.6f) , ACTUAL{%5.5f} ",net->internal,expected,net->neuron);

	}
// TODO : malloc cleanups !
 return 0;// :)
}

Le reseau, dans son état actuel,  converge , et c’est le but.

On constate que les parts de chacune des entrées est exactement proportionnées tel que nécéssaire , a savoir :

Final Parts :
(+6.250%)(+56.250%)(+37.500%)
Final Weights:
(+0.333 )(+3.000 )(+2.000 )
A) On a in[0]=3.0 , poids=0.33333 ce qui fait  3.0*0.33333333 = 1.0
B) On a in[1]=sin(angle)  , poids = 3.0 ce qui fait 3.0*sin(angle)
C) On a in[2]=cos(angle) , poids = 2.0  ce qui fait 2.0*cos(angle)
La valeur du neurone etant la somme des entrées * leur poids respectifs,
on a donc neuron = A+B+C = 1.0 + 3.0*sin(angle) + 2.0*cos(angle)
.. c’est ce qu’on voulait, ça tombe bien… (expected : 1.0+2.0*cos(ang)+3.0*sin(ang) )

Suite au prochain numéro. ( si vous avez VRAIMENT besoin d’aide, ou des remarques constructives, contact : admin(AT)eihis.com)

314159265358979323846264338327950288
419716939937510582097494459230781640
628620899862803482534211706798214808

cat{ } { post_958 } { } 2009-2015 EIhIS Powered by WordPress