61 votes

Les mathématiques de l'optimisation en C#

J'ai été le profilage d'une application tout au long de la journée et, après avoir optimisé un couple de morceaux de code, je suis parti avec cette sur ma todo liste. C'est la fonction d'activation d'un réseau de neurones, qui est appelée à plus de 100 millions de fois. Selon dotTrace, il s'élève à environ 60% de l'ensemble de la fonction temps.

Comment voulez-vous optimiser ce?

    public static float Sigmoid(double value) {
        return (float) (1.0 / (1.0 + Math.Pow(Math.E, -value)));
    }

61voto

Ben Alpert Points 30381

Essayez:

public static float Sigmoid(double value) {
    return 1.0f / (1.0f + (float) Math.Exp(-value));
}

EDIT: j'ai fait un rapide test. Sur ma machine, le code ci-dessus est d'environ 43% plus rapide que votre méthode, et ce mathématiquement équivalent de code est la plus peu plus vite (de 46% plus rapide que l'original):

public static float Sigmoid(double value) {
    float k = Math.Exp(value);
    return k / (1.0f + k);
}

EDIT 2: je ne suis pas sûr de savoir comment beaucoup de frais généraux C# fonctions, mais si vous #include <math.h> dans votre code source, vous devriez être en mesure de l'utiliser, qui utilise un flotteur de la fonction exp. Il pourrait être un peu plus rapide.

public static float Sigmoid(double value) {
    float k = expf((float) value);
    return k / (1.0f + k);
}

Aussi, si vous êtes en train de faire des millions d'appels, l'appel de fonction surcharge pourrait être un problème. Essayez de faire une fonction en ligne et voir si cela aide.

30voto

Neil Coffey Points 13408

Si c'est pour une fonction d'activation, il importe terriblement bien, si le calcul de e^x est complètement précis?

Par exemple, si vous utilisez le rapprochement (1+x/256)^256, sur mon Pentium tests en Java (je suppose que C# essentiellement compile les mêmes instructions du processeur) c'est environ 7 à 8 fois plus rapide que e^x (Math.exp()), et est précis à 2 décimales jusqu'à x de +/-1.5, et dans le bon ordre de grandeur dans la plage que vous avez déclaré. (Évidemment, de s'élever jusqu'à 256, on place le nombre 8 fois -- ne pas utiliser les Maths.Pow!) En Java:

double eapprox = (1d + x / 256d);
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;

Continuez à doubler ou réduire de moitié 256 (et l'ajout/retrait d'une multiplication) selon le degré de précision que vous voulez le rapprochement de l'être. Même avec n=4, il donne environ 1,5 décimales de précision pour des valeurs de x entre -0.5 et 0.5 (et semble un bon 15 fois plus rapide que les Mathématiques.exp()).

P. S. j'ai oublié de mentionner -- vous ne devrait évidemment pas vraiment de division par 256: multiplier par une constante 1/256. Java compilateur JIT rend cette optimisation automatiquement (au moins, Hotspot), et j'ai été en supposant que C# doit faire trop.

24voto

martinus Points 6895

Jetez un oeil à ce post. il a une approximation de e^x écrit en Java, ce qui devrait être le code C# pour elle (non testé):

public static double Exp(double val) {  
    long tmp = (long) (1512775 * val + (1072693248 - 60801));  
    return BitConverter.Int64BitsToDouble(tmp << 32);  
}

Dans mes repères c'est plus de 5 fois plus rapide que les Mathématiques.exp() (en Java). Le rapprochement est basé sur le livre "Un Rapide, Compact Approximation de la Fonction Exponentielle", qui a été développé exactement pour être utilisé dans des réseaux de neurones. C'est fondamentalement le même que d'une table de recherche de 2048 entrées et approximation linéaire entre les entrées, mais tout cela avec virgule flottante IEEE de trucs.

EDIT: en Fonction de hb , ce qui accélère Henrik Sigmoid1/Sigmoid2 par ~50% (900ms vs 500ms). Merci!

14voto

Rinat Abdullin Points 13520
  1. Rappelez-vous, que des modifications dans cette fonction d'activation venir au coût de comportement différent. Cela inclut même la commutation à flotteur (et donc l'abaissement de la précision) ou à l'aide de l'activation des produits de substitution. Seulement à expérimenter avec votre cas d'utilisation va montrer le droit chemin.
  2. En plus de la simple optimisations de code, je recommande également d'envisager la parallélisation des calculs (c'est à dire: pour tirer parti des multiples cœurs de votre machine ou même des machines à Windows Azure Nuages) et l'amélioration de la formation des algorithmes.

Mise à JOUR: Post sur les tables de recherche pour l'ANN fonctions d'activation

UPDATE2: j'ai enlevé le point sur Lut depuis que je l'ai confondu avec le complet de hachage. Merci à Henrik Gustafsson pour me mettre sur la voie. Si la mémoire n'est pas un problème, bien que l'espace de recherche est encore foiré avec les extrema locaux un peu.

8voto

Shog9 Points 82052

À 100 millions d'appels, j'aimerais commencer à me demander si profiler de frais généraux n'est pas de biaiser les résultats. Remplacer le calcul avec un no-op et de voir si il est encore signalé à consommer 60% du temps d'exécution...

Ou, mieux encore, de créer des données de test et d'utiliser un chronomètre pour établir le profil d'un million d'appels.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X