Comment simplifier et accélérer le calcul d'un réseau neuronal à propagation directe?

Bonjour chers lecteurs. Beaucoup a été écrit et dit sur les réseaux de neurones, principalement sur la manière et la raison pour laquelle ils peuvent être appliqués. De plus, en quelque sorte, peu d'attention est accordée à deux questions importantes: a) comment simplifier et calculer rapidement un réseau de neurones (un calcul de l'exposant est réalisé par les fonctions de bibliothèque des langages de programmation, généralement pas moins de 15 à 20 instructions de processeur), b) quoi, au moins en partie, la logique du réseau construit - en fait, les énormes matrices de valeurs de poids et de déplacements obtenues après la formation du réseau n'aident pas vraiment à comprendre les modèles que ce réseau a trouvés (ils restent cachés et la tâche de les déterminer est la tâche des saules tion - parfois très important). Je vais parler de l'une de mes approches pour résoudre ces problèmes pour les réseaux de neurones à distribution directe ordinaire, tout en essayant de me débrouiller avec un minimum de mathématiques.

Un peu de théorie


Le réseau de distribution directe, d'un point de vue mathématique, est une très grande fonction, qui comprend les valeurs des entrées du réseau, les coefficients de pondération et les déplacements des neurones. Dans chaque neurone de la couche, les valeurs des entrées de la couche (vecteur X) sont multipliées par le poids du neurone (vecteur Wi), additionnez avec un décalage Bi

si=WiX+Bi


et entrez les fonctions d'activation A(si)formant les sorties des neurones de couche.

Les fonctions d'activation peuvent ne pas être très simples à calculer, par exemple, elles contiennent souvent des exponentielles (sigmoïde exponentielle, tangente hyperbolique). Si vous regardez le code assembleur qui implémente les exposants, vous pouvez trouver, premièrement, de nombreuses vérifications différentes qui ne sont pas toujours nécessaires, et deuxièmement, le calcul de l'exposant lui-même se fait généralement en au moins deux opérations:

exp(v)=2vlog2(e)


Par conséquent, si nous voulons accélérer le calcul du réseau, la première tâche sera de simplifier le calcul de la fonction d'activation. Vous pouvez essayer de sacrifier un peu de qualité en raison d'un gain de vitesse, en remplaçant approximativement le calcul de la fonction d'activation classique par le calcul d'une fonction plus simple qui (sur les données d'entrée disponibles) donne approximativement les mêmes résultats. De manière générale, il s'agit d'un problème d'interpolation classique: nous avons un ensemble de valeurs calculées par la fonction d'origine A (s), et nous sélectionnons une fonction plus simple qui donne des valeurs très similaires. Une telle fonction simple a (s) peut être un polynôme ordinaire, ou un polynôme avec des pouvoirs négatifs, ou quelque chose comme ça. J'ai utilisé quatre types de telles fonctions:

a(s)=b0+b1s+b2s2+...+bnsn;
a(s)=b0+b1/s+b2/s2+...+bn/sn;
a(s)=b0+b1s0,5+b2s1+b3s1,5+...+bns0,5n;
a(s)=b0+b1/s0,5+b2/s1+b3/s1,5+...+bn/s0,5n;

Supposons que pour chaque neurone, nous ayons réussi à remplacer la fonction d'activation par une fonction légèrement plus simple - cela peut être fait, par exemple, en appliquant la méthode des moindres carrés. Une telle substitution en soi ne donnera probablement pas un très gros gain. Mais ici, vous pouvez essayer une autre astuce:

  1. Écrire la fonction analytiquement énorme NET (X) calculée par le réseau dans son ensemble;
  2. Remplacez les fonctions d'origine A (s) dans NET (X) par les fonctions de remplacement a (s) obtenues pour elles;
  3. Simplifiez le NET (X) obtenu algébriquement (ou plutôt, utilisez du code prêt à l'emploi pour la simplification symbolique des expressions). Ceci est déjà possible (au moins, beaucoup plus facile que nous essaierions de simplifier le réseau avec les fonctions originales, par exemple, avec des exposants).

En conséquence, nous obtenons quelque chose de plus simple et, peut-être, un peu plus évident mathématiquement - ici, vous pouvez déjà essayer de comprendre quel type de fonction le réseau implémente.

C'est la possibilité d'expliquer la logique du réseau construit.

La tâche décrite, bien sûr, uniquement en mots, semble simple. Pour une utilisation dans mes programmes, j'avais besoin d'écrire mon propre code pour la simplification symbolique des expressions. De plus, j'ai résolu un problème plus complexe, en supposant que chaque neurone avec la fonction A (s) peut avoir plusieurs options pour une fonction d'activation alternative ak(s)par conséquent, la tâche générale se résumait également à énumérer les options pour de telles fonctions et à simplifier symboliquement le réseau pour chacune de ces options. Ici, seule la parallélisation des calculs a aidé.

Résultat


Le résultat m'a plu. J'ai accéléré un réseau à trois couches (avec trois entrées) de huit neurones (avec poids d'entrée et déplacements) avec les fonctions d'activation «sigmoïde exponentielle». Comme le montrent les mesures du temps, il a été possible d'obtenir un gain d'environ 40% dans le temps sans perte significative de qualité.

J'illustre. Voici les données du réseau source:





Et dans la troisième couche de sortie:


Si les entrées sont désignées comme a, b et c, alors, après remplacements et simplifications, la fonction réseau NET est considérée comme suit:

double a2 = a*a; double b2 = b*b; double c2 = c*c; double a3 = a2*a; double b3 = b2*b; double c3 = c2*c; double z01 = sqrt(-1.6302e-02+7.9324e-01*a+9.65149e-01*b+5.64151e-01*c); double z06 = sqrt(1.583708e+00-8.907654e-01*a-2.844379e-01*a2+1.050942e+00*a3+1.178096e+01*b-1.865618e+00*b*a-3.145465e+00*b*a2-5.777153e+00*b2+3.138123e+00*b2*a-1.043599e+00*b3+1.32778e+00*c+5.849582e-01*c*a-3.440382e+00*c*a2+1.838371e+00*c*b+6.864703e+00*c*b*a-3.42434e+00*c*b2-3.013361e-01*c2+3.754167e+00*c2*a-3.745404e+00*c2*b-1.365524e+00*c3+1.014237e-01*z01); double NET = (-1.477593e+00)/(z06)+1.370237e+00-6.303167e-02*a-1.495051e-03*a2+2.33748e-02*a3+5.558024e-02*b+1.178189e-02*b*a-6.996071e-02*b*a2+1.837937e-02*b2+6.97974e-02*b2*a-2.321149e-02*b3+7.924241e-02*c+3.392287e-03*c*a-7.652018e-02*c*a2-1.214263e-02*c*b+1.526831e-01*c*b*a-7.616337e-02*c*b2-1.915279e-03*c2+8.349931e-02*c2*a-8.33044e-02*c2*b-3.037166e-02*c3+1.949161e-02*z01; 

Gagner - je le répète, 40% du temps, sans trop nuire à la qualité. Je pense que cette approche peut être appliquée dans les cas où la vitesse de calcul d'un réseau de neurones est critique - par exemple, si elle est calculée à plusieurs reprises, dans un cycle double ou triple. Un exemple d'un tel problème : une solution numérique du problème d'aérodynamique sur une grille, et dans chacun de ses nœuds, le réseau neuronal calcule des prévisions utiles, par exemple, pour un calcul plus précis de la viscosité turbulente. Ensuite, nous avons un cycle externe dans le temps, un cycle double ou triple en coordonnées y est intégré, et déjà là, à l'intérieur, il y a un calcul d'un réseau neuronal. Dans ce cas, la simplification est plus que appropriée et utile.

Source: https://habr.com/ru/post/fr442478/


All Articles