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 16, 2016

Neural nets, bases en C, part 7 , backward_CASCADE()

Filed under: linux, neuron — Tags: , , , — admin @ 10:40 am

Avant de modifier la fonction backWARD_CASCADE() pour l’adapter progressivement à l’utilisation dans un reseau a plusieurs couches,une explication sur un de ses éléments.

Rappel de la fonction :

void RESO_backWARD_CASCADE(reso* n,float reverse_error)
{
	uint32_t k;
	float xb;
	//
	xb=tanh(1.0-(n->neuron*n->neuron));
	xb*=reverse_error;
	n->error=xb;	//
	for(k=0;k<n->synapses;k++)
	{
		n->part[k]=(fabs(n->w[k])/n->wtot);//
		n->ward[k]=n->error*n->w[k];//
		n->back[k]=n->error*n->in[k];//
	}
	n->epoch++;
}

Il convient d’expliquer ce qu’est le xb=tanh(1.0-(n->neuron*n->neuron)) :
Dans le post sur le même sujet, part 5 , en annexe, j’ai posté un graphe de la fonction.
Son but est de moduler le learning rate selon la valeur du neuron : si celle ci est très grande, on le réduit, et on tend vers un coef. de 1.0 pour le LR  lorsque ‘neuron’ tend vers zéro (limite).
Cette fonction optimise la vitesse d’apprentissage, et,  les pivots autour de valeurs 0.0 des neurones.
On peut se passer du tanh() et conserver 1.0-(neuron*neuron) , si la totalité du reseau exploite des neurones dont les valeurs min/max ne dépassent pas 1.0 : dans ce cas, un valeur absolue de neuron de 1.0 engage un xb=0, qui annule à 100% la modfication future du poids du neurone, puisque ‘reverse_error’ est mutltiplié par xb pour générer ‘error’

Pour l’heure, on ne l’applique qu’aux neurones des couches autres que la dernière (outputs).
On verra ensuite que cette fonction peut servir à beaucoup de choses utiles.

314159265358979323846264338327950288
419716939937510582097494459230781640
628620899862803482534211706798214808

December 15, 2016

Neural nets, bases en C , part 6

Filed under: linux, neuron — Tags: , , , , — admin @ 6:08 pm

De l’importance du ‘training set’

Le reseau décrit précédement aboutit a une erreur RMS en sortie inférieure a 0.05 au bout d’approximativement  cycles (epochs) d’apprentissage (epoch = 7.27Millions)

On modifie la façon dont ‘ang’ est générée, dans la boucle d’apprentissage :

de :

  • ang=NN_frand_ab(0.0,10);
en :
  • ang=(float) ((int32_t)NN_frand_ab(0.0,10.0));

On relance le programme :

  • -> il atteint le seuil RMS voulu en seulement 180000 epochs
On trace le graphe de sa sortie :
Le réseau a généralisé : la simplification du nombre d’élements dans le train-set accélère l’apprentissage, mais entraine aussi le réseau à faire une approximation des cas intermédiaires, non présents dans le train-set.
Au niveau du seuil du 3.0 en valeur d’entrée par exemple, le réseau n’a eu que deux exemples de cas en apprentissage :
  • pour 3.0 en entrée, sortie attendue = -1 ( le test est : +1 si strictement supérieur a 3.0)
  • pour 4.0 en entrée, sortie attendue = +1
  • On constate que la sortie OUTPUT bascule de -1.0 vers +1.0 à la moitié de ces 2 valeurs, 3.5 ( la fonction tanh ‘arrondi’ les angles aux extrèmes -1.0/+1.0
Peut-on faire apprendre le reseau encore plus vite ?
Il est temps de parler de la fonction d’activation :
  • pour l’heure c’est tanh()
on va changer l’attendu en sortie du reseau , de :
  • #define _REPON ((ang>3.0)&&(ang<8.0)) +1.0:-1.0
en :
  • #define _REPON ((ang>3.0)&&(ang<8.0))? +1.0:0.0
On relance le programme sans autre modifications :
Le seuil est atteint en environ 70000 ( l’affichage est fait en %10000 .. )
C’est deux fois moins de temps pour descendre vers le seuil RMS voulu.
Son graphe en sortie :
314159265358979323846264338327950288
419716939937510582097494459230781640
628620899862803482534211706798214808

Neural nets, bases en C, part 5

Filed under: linux, neuron — Tags: , , , , — admin @ 8:58 am

..Ou l’on arrive à l’ajout nécéssaire d’une couche supplémentaire de neurones :

Si on garde le réseau (plutot le neurone daillleurs ) , à 3 entrées, on constatera que tout tentative de le faire converger vers une solution est impossible, lorsqu’on pose par exemple, un sortie attendue de la forme  :

#define _REPON	((net->in[1]>3.0)&&(net->in[1]<8.0))? +1.0:-1.0

Il va falloir créer un vrai réseau, en connectant entre eux les neurones :

J’ai gardé volontairement les 3 entrées pour chaque neurone même si elles ne sont pas nécéssaire pour résoudre le problème, mais pour un aspect pratique afin de modifier facilement les entrées du réseau constitué a fins de tests.

La littérature sur le sujet de la backpropagation étant largement diffusée et ‘mathéifiée’ , on reviendra plus tard sur une portion de la nouvelle fonction ‘backWARD_CASCADE()‘ qui sera, pour l’instant:

void RESO_backWARD_CASCADE(reso* n,float reverse_error)
{
	uint32_t k;
	float xb;
	//
	xb=tanh(1.0-(n->neuron*n->neuron));
	xb*=reverse_error;
	n->error=xb;	//
	for(k=0;k<n->synapses;k++)
	{
		n->part[k]=(fabs(n->w[k])/n->wtot);//
		n->ward[k]=n->error*n->w[k];//
		n->back[k]=n->error*n->in[k];//
	}
	n->epoch++;
}

A la différence de backWARD() , on n’introduit plus une valeur attendue , mais (l’erreur*poids)=ward[] du neurone situé après celui qui est en cours de traitement ( et pour l’instant, on en aura qu’1 seul , celui de z_net )

On peut tester la configuration à trois neurones, avec le programme suivant :

#include "../common/classics.h"
#include "../common/neuron/RESO_library.c"

int main(int argc, char **argv)
{
	// le reseau S
	reso stnet;
	reso* snet=&stnet;			// pointeur vers la structure
	//
	// le reseau T
	reso ttnet;
	reso* tnet=&ttnet;			// pointeur vers la structure
	//
	// le reseau Z
	reso ztnet;
	reso* z_net=&ztnet;			// pointeur vers la structure
	//
	//float s_expected,t_expected,u_expected;				// utile pour stocker ce qu'on attend en reponse
	float expected;
	float RMSerror,RMScompute;
	float ang;					// pour un angle, sinus/cosinus
	//
	uint32_t i;		// une variable d'utilité..
	//
	//
	RESO_init(snet,3);// on initialise le reseau S, avec 3 entrées
	RESO_init(tnet,3);// on initialise le reseau T, avec 3 entrées
	//RESO_init(unet,3);// on initialise le reseau U, avec 3 entrées
	//
	RESO_init(z_net,3);// on initialise le reseau Z, avec 3 entrées
	//
	// mise a 0.5,0.4,0.3 de tous les poids , pour S T et Z cf POST
	snet->w[0]=0.5;
	snet->w[1]=0.5;
	snet->w[2]=0.5;
	//
	tnet->w[0]=0.4;
	tnet->w[1]=0.4;
	tnet->w[2]=0.4;
	//
	//
	z_net->w[0]=0.3;
	z_net->w[1]=0.3;
	z_net->w[2]=0.3;
	//
	RMSerror=0.0;
	RMScompute=0.0;
	//
	// on définit ici pour les essais, les 3 entrées, la sortie attendue par formule
	//
	#define _INA	5.0 /* un bias */
	#define _INB 	0.0 /* inutilisé */
	#define _INC	ang /* la variable d'entrée */
	//
	#define _REPON	((ang>3.0)&&(ang<8.0))? +1.0:-1.0 /* expected formula */
	//
	// _________--------________
	// 0        3       8        10
	//
	ang=0.0;
	//
	while((RMScompute>0.05)||(z_net->epoch<200))// jusqu'a RMS erreur total < 0.01
	{
		//
		snet->in[0]=_INA; //
		snet->in[1]=_INB;
		snet->in[2]=_INC;

		tnet->in[0]=_INA;
		tnet->in[1]=_INB;
		tnet->in[2]=_INC;
		//
		//
		expected=_REPON;
		//
		//	CALCUL : FORWARD
		RESO_forward(snet);
		RESO_forward(tnet);
		//
		snet->neuron=tanh(snet->neuron);
		tnet->neuron=tanh(tnet->neuron);
		// on transmet les 2 sorties de snet,tnet ver z_net, neuron de sortie, qui a 3 entrées :
		z_net->in[0]=snet->neuron;
		z_net->in[1]=tnet->neuron;
		z_net->in[2]=_INA;// entrée BIAS pour ce neurone, est INA pour les 2 neurones de la couche précédente
		// on calcule ce dernier noeud:
		RESO_forward(z_net);
		z_net->neuron=tanh(z_net->neuron);
		//
		if(z_net->epoch%10000==0)
		{
			printf("\n\nepoch[%6.6lu]\nZNET\t e(%+5.5f) , RMS{%8.8f}\n",z_net->epoch,z_net->error,RMScompute);
			printf("\t\t wtot[%+3.3f] --",z_net->wtot);	// au passage, le poids total des entrées a ce cycle
			for(i=0;i<z_net->synapses;i++) { printf("(w=%+5.5f / ward=%+7.7f]",z_net->w[i],z_net->ward[i]); }
			//
			// affiche snet valeurs
				printf("\nSNET\t e(%+5.5f) out=[%5.5f]\t",snet->error,snet->neuron);
				printf("wtot[%+3.3f] --",snet->wtot);	//
				for(i=0;i<snet->synapses;i++) { printf("(%+3.3f)",snet->w[i]); }
			// affiche tnet valeurs
				printf("\nTNET\t e(%+5.5f) out=[%5.5f]\t",tnet->error,tnet->neuron);
				printf("wtot[%+3.3f] --",tnet->wtot);	//
				for(i=0;i<tnet->synapses;i++) { printf("(%+3.3f)",tnet->w[i]); }
		}
		//  CALCUL : BACKWARD pour Z_NET ( pour obtenir l'erreur de la sortie n->neuron )
		//  expected contient la valeur attendue, fournie a la fonction
		RESO_backWARD(z_net,expected);
		//
		// on reporte ** l'ERREUR ** sur sur les reseaux précédents S,T
		RESO_backWARD_CASCADE(snet,z_net->ward[0]);// <- on transmet ward[0] de z_net
		RESO_backWARD_CASCADE(tnet,z_net->ward[1]);// <- idem pour ward[1]
		// cumul RMS pour controle convergence
		RMSerror+=fabs(z_net->error);
		RMScompute=(RMSerror/(float)z_net->epoch);
		// appliquer correction, inverse : derniere couche vers premiere
		RESO_apply(z_net,0.001);
		//
		RESO_apply(snet,0.1);//
		RESO_apply(tnet,0.1);//
		// .. valeur entre 0/10.0 pour le prochain cycle
		ang=NN_frand_ab(0.0,10.0);
	}
	// seuil RMS ok, affiche les infos finales poids/ part dans le résultat
	printf("\n\tZ_NET Final Parts  :");
	for(i=0;i<z_net->synapses;i++) { printf("(%+3.3f%c)",z_net->part[i]*100.0,'%'); }
	printf("\n\tZ_NET Final Weights:");
	for(i=0;i<z_net->synapses;i++) { printf("(%+3.3f )",z_net->w[i]);}
	printf("\n");
	// infos snet
	printf("\n\tSNET Final Parts  :");
	for(i=0;i<snet->synapses;i++) { printf("(%+3.3f%c)",snet->part[i]*100.0,'%'); }
	printf("\n\tSNET Final Weights:");
	for(i=0;i<snet->synapses;i++) { printf("(%+3.3f )",snet->w[i]);}
	printf("\n");
	// infos tnet
	printf("\n\tTNET Final Parts  :");
	for(i=0;i<tnet->synapses;i++) { printf("(%+3.3f%c)",tnet->part[i]*100.0,'%'); }
	printf("\n\tTNET Final Weights:");
	for(i=0;i<tnet->synapses;i++) { printf("(%+3.3f )",tnet->w[i]);}
	printf("\n");
	// 'test run'
	z_net->internal=0;
	ang=0.00;
	while(z_net->internal<100)
	{
		//
		snet->in[0]=_INA; // diffuses constant 5.0
		snet->in[1]=_INB;
		snet->in[2]=ang;
		//
		tnet->in[0]=_INA;
		tnet->in[1]=_INB;
		tnet->in[2]=ang;
		//
		expected=_REPON;
		//	CALCUL : FORWARD
		RESO_forward(snet);
		RESO_forward(tnet);
		//
		snet->neuron=tanh(snet->neuron);
		tnet->neuron=tanh(tnet->neuron);
		//unet->neuron=tanh(unet->neuron);
		// on transmet les 3 sorties de snet,tnet,unet ver z_net, neuron de sorties, qui a 3 entrées :
		z_net->in[0]=snet->neuron;
		z_net->in[1]=tnet->neuron;
		z_net->in[2]=_INA;
		// on calcule ce dernier noeud:
		RESO_forward(z_net);
		z_net->neuron=tanh(z_net->neuron);
		// output version human readable
		#define _OUT_TEXT 	"cycle[%lu] A[%f] B[%f] C[%f] EXP[%f] OUT[%f]\n"
		// ou pour export CSV :
		// #define _OUT_TEXT	"%lu,%f,%f,%f,%f,%f\n"
		printf(_OUT_TEXT,
			z_net->internal,
			_INA,
			_INB,
			_INC,
			_REPON,
			z_net->neuron);
		//
		ang+=0.10;
	}
	//
	// todo : malloc releases
}

L’apprentissage converge vers une solution, et on obtient en résultat (sur ma machine):

Z_NET Final Parts  :(+48.571%)(+42.593%)(+8.837%)
Z_NET Final Weights:(+2.709 )(+2.375 )(-0.493 )

SNET Final Parts  :(+37.337%)(+0.258%)(+62.405%)
SNET Final Weights:(-72.378 )(+0.500 )(+120.971 )

TNET Final Parts  :(+61.343%)(+0.139%)(+38.517%)
TNET Final Weights:(+176.048 )(+0.400 )(-110.540 )

Comme on peut le constater, l’entrée ‘B’ de snet, et tnet, qui est a 0.0 (cf. programme) , est quasiment complètement rejetée ( 0.258% et 0.139% de parts, respectivement ), ou , plus précisement, son poids n’a pas été changé, hors comparé aux poids des autres entrées, leur parts sont devenues minimes dans la résultat final.

  • snet et tnet ont utilisé les entrées A (bias) et C (la variable) en majorité
  • znet, à utilisé les 3 entrées : A qui est la sortie de snet, B qui est la sortie de tnet et C qui est son entrée BIAS, la même que A pour snet et tnet.
Plusieurs remarques s’imposent :
  • Les poids de départ ne sont plus fixés à la même valeur. La situation de départ du réseau constitué (les valeurs des poids) influe sur la rapidité de la convergence, et peut aboutir des echecs ( cf. littérature sur le sujet )
  • Les learning rates : faire des essais , cycles de comparaisons avec même situation (poids) de départ VS différents learning rates sur z_net et snet/tnet
Avant d’aborder une partie d’explication sur la fonction backWARD_CASCADE() , un graphique complet des entrées/sorties du réseau :

Annexe, détail de fonctions utiles (ou pas)

314159265358979323846264338327950288
419716939937510582097494459230781640
628620899862803482534211706798214808
Older Posts »

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