EihiS

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

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