EihiS

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)

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_958 } { } 2009-2015 EIhIS Powered by WordPress