EihiS

December 15, 2016

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

December 14, 2016

Neural nets,bases en C part 4, la fonction TANH(y)

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

Ou l’on arrive a la fonction TANH, nécéssaire dans le cas ou on s’attend à une sortie plus proche du ‘tout’ ou ‘rien’ logique que des valeurs linéaires précédentes :

Le programme de test suivant fait apprendre au réseau le déclenchement de la sortie à +1.0 si l’entrée 2 (_INB) est supérieure à +5.0, et -1.0 si elle est inférieure (ou égale) a +5.0 :

On ne change pas la structure du réseau, 3 entrées,une sortie. L’entrée inA est ‘inutilisée’, fixée à 0.01, inB est fixée entre 0.0 et +10.0 par un tirage aléatoire a chaque cycle, et inC est fixée a 2.5 fixe, servant d’entrée BIAS pour le réseau :

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

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;
	//
	uint32_t i;		// des variables d'utilité..
	//
	//
	RESO_init(net,3);// on initialise le reseau, avec 3 entrées
	//
	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, la sortie attendue par formule
	#define _INA	0.01
	#define _INB	NN_frand_ab(0.0,10.0)
	#define _INC	2.5
	//
	#define _REPON	(net->in[1]>5.0)? 1.0:-1.0
	//
	while((RMScompute>0.05)||(net->epoch<20))// jusqu'a RMS erreur total < 0.01
	{
		//
		net->in[0]=_INA;
		net->in[1]=_INB;
		net->in[2]=_INC;
		//
		expected=_REPON;
		//
		//	CALCUL : FORWARD
		//
		RESO_forward(net);
		// application tanh() a la sortie
		net->neuron=tanh(net->neuron);
		//
		//  CALCUL : BACKWARD ( pour obtenir l'erreur de la sortie n->neuron )
		//  expected contient la valeur attendue, fournie a la fonction
		RESO_backWARD(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,'%'); }
		//
		RESO_apply(net,0.05);
	}
	// seuil RMS 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 :
	// 'test run'
	net->internal=0;
	while(net->internal<80)
	{
		net->in[0]=_INA;
		net->in[1]=_INB;
		net->in[2]=_INC;
		expected=_REPON;				// uniquement pour comparer l'attendu a la sortie du reseau
		RESO_forward(net);				// forward uniquement
		net->neuron=tanh(net->neuron);	// application tanh()
		printf("\nCycle[%6.6lu] EXPECTED(%+6.6f) , ACTUAL{%5.5f} ",net->internal,expected,net->neuron);
	}
	//
}

.. on utilise les fonctions :

  • RESO_init()
  • RESO_forward()
  • RESO_backWARD()
  • RESO_apply()
  • une fonction NN_frand_ab(min,max)
  • et la structure reso

Telles que : ( je les ai listées a nouveau ici avec les modifications des précédents posts intégrées )

float NN_frand_ab(float a, float b)
{
    return (float) (( rand()/(float)RAND_MAX ) * (b-a) + a);
}
//
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
	//
	uint64_t epoch;
	uint64_t internal;
} reso;
void RESO_init(reso* n,uint32_t input_count)
{
	uint32_t k;
	float init_w=1.0;
	n->in=(float*) malloc(sizeof(float)*input_count);
	n->back=(float*) malloc(sizeof(float)*input_count);
	n->ward=(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, @1
		n->part[k]=n->w[k]/n->wtot;		// backed ready
	}
	n->neuron=0.0;
	n->error=0.0;
	//
	n->epoch=1;
	n->internal=1;

}
void RESO_forward(reso* n)
{
	uint32_t i;
	n->neuron=0.0;
	n->wtot=0.000001;// divide by zero ?
	for(i=0;i<n->synapses;i++)
	{
		n->neuron+=n->in[i]*n->w[i];
		n->wtot+=fabs(n->w[i]);//
	}
	n->internal++;
}
void RESO_backWARD(reso* n,float expected)
{
	uint32_t k;
	n->error=n->neuron-expected;	// actual - expected
	//
	for(k=0;k<n->synapses;k++)
	{
		n->part[k]=(fabs(n->w[k])/n->wtot);								// assume w always positive
		n->ward[k]=n->error*n->w[k];// erreur * poids
		n->back[k]=n->error*n->in[k];// erreur * input val
	}
	n->epoch++;
}
void RESO_apply(reso* n,float rate)
{
	uint32_t k;
	for(k=0;k<n->synapses;k++)
	{
		// commentée (voir plus tard ) : if(fabs(n->w[k])<0.001) n->w[k]=-n->w[k];
		n->w[k]-=n->back[k]*rate;//
	}
}

En suivant, un graphe de la sortie ‘neuron’, en fonction de l’entrée inB ( 0-10 ), et zoom (4.
125 à 6.0 )  - neuron et error utilisent l’axe Y gauche, ward et back, l’axe droite

..Avec le seuil RMS fixé a 0.05, on voit que le déclenchement de la sortie , passant de -1.0 a +1.0, se fait un peu avant 5 en entrée.

Si on descend le seuil RMS a 0.01 avant de terminer l’apprentissage, on obtient cette fois :

remarque : ne pas se fier a l’overshoot du neuron qui passe ‘au dela’ de 1.0 malgré la fonction tanh() . les courbes sont ‘lissées’ (spline) par le soft qui génère les graphes.

Sans smooth, et dans le range 4.9 - 5.1 , par pas de 0.025 :

La fonction tanh() sur la sortie permet de passer relativement vite d’un état a l’autre (-1.0 / +1.0 ), par une transition douce.

On peut essayer de modifer la partie du code :

commenté : //  net->neuron=tanh(net->neuron);
remplacé par : net->neuron=(net->neuron>0.0)? 1.0:-1.0;

Dans ces conditions, le réseau converge 2 fois plus vite , et son graphe devient :

.. radical..

On peut aussi essayer avec :

if(net->neuron>1.0) net->neuron=1.0;
if(net->neuron<-1.0) net->neuron=-1.0;

On obtient :

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

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

Filed under: linux, neuron — Tags: , , , , , , — admin @ 9:32 am

Si vous avez testé différentes combinaisons d’entrées / sortie attendue, vous aurez remarqué que certaines ne convergent pas vers une solution.

Pour ceux qui débuteraient, c’est ici qu’il faut aborder la notion de BIAS , tout d’abord.

Si on pose les conditions suivantes en entrée du réseau :

#define _INA	0.0 // était 3.0 sur la version d'origine
#define _INB	0.0+sin(ang) // inchangé
#define _INC	0.0+cos(ang) // inchangé
//
#define _REPON	1.0+2.0*cos(ang)+3*sin(ang) // inchangé

Si on execute le code, on voit que le reseau ne converge pas et ’stagne’ avec une erreur RMS de l’ordre 1.011,
après des époques dépassant les 1000000.

La modification de l’entrée A du reseau , passant de 3.0 a 0.0 , interdit au reseau de pouvoir générer la premiere partie de l’equation de sortie attendue, qui est une constant.

En effet, puisque la sortie (la valeur du neurone) est egale a : (inA*wA)+(inB*wB)+(inC*wC) , on peut voir que, quels  que soient wA,wB, ou wC (les poids) , il faut AU MOINS une des entrées qui ait une valeur autre que ZERO si l’on souhaite générer une sortie qui ait un ‘offset’ (valuer constante) non nul.

En mettant inA à zero, on a donc supprimé une entrée indispensable à n’importe quel reseau de ce type, qu’on appelle l’entrée de ‘BIAS’.

Cette entrée est la même qu’une entrée normale, mais sa valeur est fixée au démarrage de l’apprentissage. l’apprentissage (fonction BACKWARD + APPLY )  viendra modifier le poids de cette entrée, de manière à utiliser la valeur de ce BIAS comme nécéssaire.

On peut vérifier la chose en posant de nouveau ces conditions :

#define _INA	1.0
#define _INB	0.0+sin(ang)
#define _INC	0.0+cos(ang)
//
#define _REPON	1.0+2.0*cos(ang)+3*sin(ang)

le reseau converge de nouveau, avec au final les poids suivants :

Final Parts :
(+16.667%)(+50.000%)(+33.333%)
Final Weights:
(+1.000 )(+3.000 )(+2.000 )

la partie constant de la formule de sortie attendue est générée grace à inA*wA = (+1.0*+1.0) = +1.0

Mais  on peut aussi proposer en entrée :

#define _INA	-1.0
#define _INB	0.0+sin(ang)
#define _INC	0.0+cos(ang)
//
#define _REPON	1.0+2.0*cos(ang)+3*sin(ang)

On éxecute, s’attendant à ce que le poids final wA soit -1.0, de facon à obtenir le premier terme de la formule attendue en sortie ( qui est +1.0 =constante ) , avec inA*wA = (-1.0 * -1.0) = +1.0

Hors le réseau ne converge pas.
La répartition des poids montre que wA a été réduit à zero, et reste dans cet etat, interdisant au réseau de converger.

C’est là qu’il est temps de jeter un oeil sur la fonction :

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

Dans celle-ci, on multiplie le learning rate par la valeur de ‘back[] ‘, qui est la valeur de l’entrée calculée pour le réseau en le parcourant à l’envers (sortie vers entrées)

hors back[ ] est définie dans la fonction RESO_back() par le calcul suivant :

n->back[k]=n->neuron*n->part[k]*n->in[k];
avec :
n->part[k]=(fabs(n->w[k])/n->wtot);
et:
n->error=expected - n->neuron;
n->neuron=n->error;

On réécrit la fonction executée pour chaque entrée :
Remplacant neuron par sa valeur, qui est expected - neuron , c’est a dire l’erreur :

n->back[k]=n->neuron*n->part[k]*n->in[k];

peut s’écrire : back[k] = (expected-neuron) * ( FABS( w[k] ) / wtot ) * in [k] ;

le ‘problème‘ vient du terme w[k] / wtot

Dans le cas d’un poids qui évolue vers une valeur positive croissante (avec w initial positif), tout ce passe bien.
Mais dans le cas d’une évolution décroissante, on va rapidement stagner, avec un terme w[k]/wtot qui va ressembler de plus en plus à zero (tendant vers zero pour etre précis) ,  divisé par wtot ( qui au passage ne doit pas etre egal a zero sous peine d’exception ‘DIVIDE BY ZERO’ du programme) , ce qui donne une valeur tendant vers zéro.

Hors ce terme w[k]/wtot est multiplié par in[k] et ‘error’
L’ensemble va donc tendre vers ZERO, et , de ce fait, plus le poids va s’approcher de zéro, moins il sera modifié par la fonction ‘APPLY’.

Dans le cas de l’exemple donné, on a inA=-1.0 , hors le terme constant de la formule attendue en sortie est +1.0+(…)

Le poids de inA devrait donc être -1.0, pour obtenir cette valeur constante :

inA * wA =  (-1.0* -1.0) = 1.0

Donc, puisque le poids wA au départ vaut +1.0 , il faut qu’il décroisse vers -1.0 : c’est ce qui se produit lors de l’apprentissage, et,à l’approche de 0.0, l’apprentissage se verrouille puisque le poids sera de moins en moins modifié, plus il tendra vers ZERO.
Le cap du ‘ZERO’ semble etre un point de blocage infranchissable avec la méthode actuelle de calcul et la réciproque ( poids initial négatif, et croissant vers le positif du fait de l’apprentissage), et aussi valable.
On va pour l’heure modifier la fonction, pour passer ce CAP , de la facon suivante :

 void RESO_apply(reso* n,float rate)
{
	uint32_t k;
	for(k=0;k<n->synapses;k++)
	{
		n->w[k]+=n->back[k]*rate;// inchangé
		if(fabs(n->w[k])<0.001) { n->w[k]=-n->w[k]; } // switch : évite le ZERO fatidique...
	}
}

On relance avec les même valeurs d’entrée que précédement, et miracle, le réseau converge, avec au final :

Final Parts :
(+25.000%)(+75.000%)(+50.000%)
Final Weights:
(-1.000 )(+3.000 )(+2.000 )

On essaye une modification sur une autre entrée par exemple inA, en inversant sa valuer de sin() mais pas la formule attendue en sortie :

#define _INA	-1.0
#define _INB	0.0-sin(ang) // inversée->  -sin(ang)
#define _INC	0.0+cos(ang)
//
#define _REPON	1.0+2.0*cos(ang)+3*sin(ang) // terme +3.0*sin(ang) ..

Le reseau converge, avec comme valeurs finales (ainsi que les 20 essais de vérification ) :

Final Parts :
(+16.667%)(+50.000%)(+33.333%)
Final Weights:
(-1.000 )(-3.000 )(+2.000 )

Cycle[000001] EXPECTED(-2.269685) , ACTUAL{-2.26969}
Cycle[000002] EXPECTED(+2.999692) , ACTUAL{2.99969}
Cycle[000003] EXPECTED(+0.855610) , ACTUAL{0.85561}
Cycle[000004] EXPECTED(-0.753175) , ACTUAL{-0.75318}
Cycle[000005] EXPECTED(+4.137586) , ACTUAL{4.13759}
Cycle[000006] EXPECTED(-2.603627) , ACTUAL{-2.60363}
Cycle[000007] EXPECTED(+4.014887) , ACTUAL{4.01489}
Cycle[000008] EXPECTED(-0.543690) , ACTUAL{-0.54369}
Cycle[000009] EXPECTED(+0.620656) , ACTUAL{0.62066}
Cycle[000010] EXPECTED(+3.191344) , ACTUAL{3.19134}
Cycle[000011] EXPECTED(-2.361939) , ACTUAL{-2.36194}
Cycle[000012] EXPECTED(+4.548495) , ACTUAL{4.54849}
Cycle[000013] EXPECTED(-1.696408) , ACTUAL{-1.69641}
Cycle[000014] EXPECTED(+2.055083) , ACTUAL{2.05508}
Cycle[000015] EXPECTED(+1.895064) , ACTUAL{1.89506}
Cycle[000016] EXPECTED(-1.583227) , ACTUAL{-1.58323}
Cycle[000017] EXPECTED(+4.515279) , ACTUAL{4.51528}
Cycle[000018] EXPECTED(-2.418411) , ACTUAL{-2.41841}
Cycle[000019] EXPECTED(+3.320974) , ACTUAL{3.32097}
Cycle[000020] EXPECTED(+0.455810) , ACTUAL{0.45581}
314159265358979323846264338327950288
419716939937510582097494459230781640
628620899862803482534211706798214808
« Newer PostsOlder Posts »

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