EihiS

December 16, 2016

Neural nets, bases en C, part 8 , généralisation

Filed under: linux, neuron — Tags: , , , — admin @ 11:41 am

La fonction backWARD_CASCADE() est transformée afin d’être utilisable dans un réseau qui serait constitué de plusieurs couches, ou d’autres configurations moins conventionelles :

L’existante :

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++;
}

Un neurone d’une couche ‘cachée’ peut avoir plusieurs connections vers des entrées de la couche suivante :

ou, plus simplement représenté :

Il est nécéssaire de modifier la fonction pour cumuler, pour chaque neurone d’une couche ‘N’ du reseau (autre que la dernière couche , ‘de sortie ‘), l’erreur des neurones auquels il est connecté, et ceci dans la proportion à laquelle il aura participé à l’erreur de ces neurones.

on modifie donc dabord la structure du reseau, pour rendre ‘xb’ élément de 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 wtot;			// total input weights
	//
	float xb;
	//
	uint64_t epoch;
	uint64_t internal;
} reso;

Puis la fonction backWARD_CASCADE() ,dupliquée et modifiée devient :

void RESO_backWARD_CASCADE_ADDERROR(reso* n,float reverse_error)
{
	uint32_t k;
	n->xb=tanh(1.0-(n->neuron*n->neuron));
	n->xb*=reverse_error;
	n->error+=n->xb;	// on cumule maintenant la part d'erreur entrante
	for(k=0;k<n->synapses;k++)
	{
		n->part[k]=(fabs(n->w[k])/n->wtot);//
		n->ward[k]=n->xb*n->w[k];//
		n->back[k]+=n->xb*n->in[k];// on cumule les backs[] générés pour chaque entrées
	}
	n->epoch++;
}

Ensuite,une fonction pour préparer (initialiser) error et les back[] avant :

//
void RESO_prepare_CASCADE(reso* n)
{
	uint32_t k;
	for(k=0;k<n->synapses;k++) { n->back[k]=0.0;}
	n->error=0.0;
}

Avant de tester avec la nouvelle mouture de programme qui suit, quelques détails sur des ajouts :

  • ajout de macros pour modifier facilement le mode d’activation des neurones ( tanh , etc )
  • mise en #define des LR

Il y précisement 2 macros pour la fonction d’activation :

#define _OUTPUT_TRANSFORM(x) tanh(x)
ou bien :
#define _OUTPUT_TRANSFORM(x) (x>0.0)? tanh(x):0.0

La deuxième est dérivée de reLU , pour rectifier Linear Unit (google pour plus d’informations), limitée a +1.0 par le tanh, ce qui aurait pu etre fait par une autre méthode.

On définit 2 sorties , la sortie z_net et la sortie y_net attendues:

#define _REPON_Z ((ang>3.0)&&(ang<6.0))? +1.0:0.0 /* expected formula, Z output */
#define _REPON_Y (ang>3.0)?  0.0:1.0 /* expected formula, Y output */

Je vous invite à tester le programme avec l’une et l’autre de fonctions pour l’activation

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

int main(int argc, char **argv)
{
	// le reseau S (layer1)
	reso stnet;
	reso* snet=&stnet;			// pointeur vers la structure
	//
	// le reseau T (layer1)
	reso ttnet;
	reso* tnet=&ttnet;			// pointeur vers la structure
	//
	// le reseau Y (layer 2)
	reso ytnet;
	reso* y_net=&ytnet;			// pointeur vers la structure
	// le reseau Z (layer 2)
	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_z,expected_y;// commode
	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(y_net,3);// on initialise le reseau Z, avec 3 entrées
	RESO_init(z_net,3);// on initialise le reseau Z, avec 3 entrées
	//
	snet->w[0]=0.5;
	snet->w[1]=0.39;
	snet->w[2]=0.41;
	//
	tnet->w[0]=0.5;
	tnet->w[1]=0.45;
	tnet->w[2]=0.43;
	//
	//
	z_net->w[0]=0.49;
	z_net->w[1]=0.40;
	z_net->w[2]=0.5;
	//
	y_net->w[0]=0.48;
	y_net->w[1]=0.42;
	y_net->w[2]=0.5;
	//
	RMSerror=0.0;
	RMScompute=0.0;
	//
	#define _OUTPUT_TRANSFORM(x) tanh(x)
	//#define _OUTPUT_TRANSFORM(x) (x>0.0)? tanh(x):0.0
	//
	#define _LR_OUT		0.01
	#define _LR_HIDDEN	0.01
	//
	// on définit ici pour les essais, les 3 entrées, la sortie attendue par formule
	//
	#define _INA	1.0 /* un bias */
	#define _INB 	NN_frand_ab(0.0,2.0) /* inutilisé */
	#define _INC	ang /* la variable d'entrée */
	//
	#define _REPON_Z ((ang>3.0)&&(ang<6.0))? +1.0:0.0 /* expected formula, Z output */
	#define _REPON_Y (ang>3.0)?  0.0:1.0 /* expected formula, Y output */
	//
	//
	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_z=_REPON_Z;
		expected_y=_REPON_Y;
		//
		//	CALCUL : FORWARD
		RESO_forward(snet);
		RESO_forward(tnet);
		//
		snet->neuron=_OUTPUT_TRANSFORM(snet->neuron);
		tnet->neuron=_OUTPUT_TRANSFORM(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
		//
		y_net->in[0]=snet->neuron;
		y_net->in[1]=tnet->neuron;
		y_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=_OUTPUT_TRANSFORM(z_net->neuron);
		RESO_forward(y_net);
		y_net->neuron=_OUTPUT_TRANSFORM(y_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_z);
		RESO_backWARD(y_net,expected_y);
		//
		//
		RESO_prepare_CASCADE(snet);// remise a zéro du cumul d'erreur pour ce neurone
		RESO_prepare_CASCADE(tnet);// idem
		// on reporte LES ERREURS sur les reseaux précédents S,T , et ce ,
		// pour chaque liaison du neurone vers la couche suivante
		RESO_backWARD_CASCADE_ADDERROR(snet,y_net->ward[0]);// dans quelle 'proportion' snet a participé à l'erreur de znet,A
		RESO_backWARD_CASCADE_ADDERROR(snet,z_net->ward[0]);//idem pour znet, A
		// meme chose pour tnet
		RESO_backWARD_CASCADE_ADDERROR(tnet,y_net->ward[1]);// tnet, pour ynet entrée B
		RESO_backWARD_CASCADE_ADDERROR(tnet,z_net->ward[1]);//tnet pour znet B
		// cumul RMS pour controle convergence
		RMSerror+=(fabs(z_net->error)+fabs(y_net->error))/2.0; // basé sur une moyenne des erreurs RMS des 2 sorties
		RMScompute=(RMSerror/(float)z_net->epoch);
		// appliquer correction, inverse : derniere couche vers premiere
		RESO_apply(z_net,_LR_OUT);
		RESO_apply(y_net,_LR_OUT);
		//
		RESO_apply(snet,_LR_HIDDEN);//
		RESO_apply(tnet,_LR_HIDDEN);//
		// .. 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");
	printf("\n\tY_NET Final Parts  :");
	for(i=0;i<y_net->synapses;i++) { printf("(%+3.3f%c)",y_net->part[i]*100.0,'%'); }
	printf("\n\tY_NET Final Weights:");
	for(i=0;i<y_net->synapses;i++) { printf("(%+3.3f )",y_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_z=_REPON_Z;
		expected_y=_REPON_Y;
		//	CALCUL : FORWARD
		RESO_forward(snet);
		RESO_forward(tnet);
		//
		snet->neuron=_OUTPUT_TRANSFORM(snet->neuron);
		tnet->neuron=_OUTPUT_TRANSFORM(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;
		//
		y_net->in[0]=snet->neuron;
		y_net->in[1]=tnet->neuron;
		y_net->in[2]=_INA;
		// on calcule ce dernier noeud:
		RESO_forward(z_net);
		z_net->neuron=_OUTPUT_TRANSFORM(z_net->neuron);
		RESO_forward(y_net);
		y_net->neuron=_OUTPUT_TRANSFORM(y_net->neuron);
		// output version human readable
		#define _OUT_TEXT 	"cycle[%lu] B[%f] B[%f] A[%f] EXP_Z[%f] OUT_Z[%f] || EXP_Y[%f] OUT_Y[%f]\n"
		// ou pour export CSV :
		//#define _OUT_TEXT	"%lu,%f,%f,%f,%f,%f,%f,%f\n"
		printf(_OUT_TEXT,
			z_net->internal,
			_INC,
			_INB,
			_INA,
			_REPON_Z,
			z_net->neuron,
			_REPON_Y,
			y_net->neuron);
		//
		ang+=0.10;
	}
	//
	// todo : malloc releases
}

La sortie, en version TANH() * seuil atteint en 1100000 epochs

Le même , avec activation en mode ‘reLU’ , seuil atteint en 140000 epochs :

En descendant le seuil RMS a 0.01, et reLU en activation :

Dans cette situation , un résumé des parts de chaque élément :

Z_NET Final Parts  :(+45.641%)(+46.988%)(+7.372%)
Z_NET Final Weights:(+4.510 )(-4.643 )(-0.728 )

Y_NET Final Parts  :(+63.674%)(+6.939%)(+29.387%)
Y_NET Final Weights:(-5.336 )(-0.581 )(+2.463 )

SNET Final Parts  :(+74.550%)(+0.064%)(+25.386%)
SNET Final Weights:(-16.639 )(+0.014 )(+5.666 )

TNET Final Parts  :(+84.045%)(+0.552%)(+15.403%)
TNET Final Weights:(-8.278 )(-0.054 )(+1.517 )

SNET utilise A et C ( BIAS, et la variable ang ) : il détecte un des ’seuils’ ,celui a 3.0 ou celui a 6.0
TNET utilise A et C egalement : il detecte une des deux seuils , celui a 3.0 ou celui a 6.0
ZNET utilise majoritairement ses entrées A et B , qui sont les sorties de SNET et TNET, respectivement , pour générer la réponse attendue, ainsi que son entrée de BIAS (comparaison)
YNET, utilise majoritairement  son entrée A , qui est la sortie de SNET, et son BIAS , hors, comme on sait que ZNET et YNET , en sortie, doivent communément déclencher sur 3.0, on peut en conclure que SNET detecte le passage par 3.0 de la variable en ‘ang’ , puisqu’il est utilisé par YNET ET ZNET.

Pour finir :

Même si le reseau et les fonctions sont, pour l’instant, plutot ‘lourdes’, vous avez les bases pour constituer des réseau personnalisés beaucoup plus compacts, et j’ai volontairement découpé les fonctions et phases de l’apprentissage, afin de clarifier chacunes des étapes.
On va optimiser et rendre synthétiques les choses dans les prochains posts

No Comments

No comments yet.

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

314159265358979323846264338327950288
419716939937510582097494459230781640
628620899862803482534211706798214808

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