Rapide Exp calcul: possible pour améliorer la précision sans perdre trop de la performance?
Je suis en train de sortir de la fast Exp(x) la fonction qui, auparavant, a été décrit dans cette réponse à un DONC, la question sur l'amélioration de la vitesse de calcul en C#:
public static double Exp(double x)
{
var tmp = (long)(1512775 * x + 1072632447);
return BitConverter.Int64BitsToDouble(tmp << 32);
}
L'expression est l'utilisation de certaines virgule flottante IEEE "trucs et astuces" et est principalement conçu pour une utilisation dans des ensembles de neurones. La fonction est environ 5 fois plus rapide que le Math.Exp(x)
fonction.
Malheureusement, la précision numérique est seulement -4% -- +2% par rapport à la régulière Math.Exp(x)
fonction, idéalement, je voudrais avoir une précision à l'intérieur d'au moins la sous-gamme pour cent.
J'ai tracé le quotient entre l'approximatif et le régulier fonctions Exp, et comme on peut le voir dans le graphique de la différence relative semble être répété avec pratiquement constante de fréquence.
Est-il possible de profiter de cette régularité pour améliorer la précision de la "fast exp" fonction " sans réduire la vitesse de calcul, ou serait le calcul de la surcharge d'une précision d'amélioration de l'emporter sur le calcul du gain de l'expression d'origine?
(Comme une note de côté, j'ai aussi essayé de d'un autre approches proposées dans la même DONC, la question, mais cette approche ne semble pas être efficaces de calcul en C#, du moins pas pour le cas général.)
MISE À JOUR LE 14 MAI
À la demande de @Adriano, j'ai effectué un très simple indice de référence. J'ai réalisé 10 millions de calculs à l'aide de chacune des autres exp fonctions pour les valeurs à virgule flottante dans l'intervalle [-100, 100]. Depuis la plage de valeurs, je suis intéressé à s'étend de -20 à 0 j'ai aussi explicitement la valeur de la fonction en x = -5. Voici les résultats:
Math.Exp: 62.525 ms, exp(-5) = 0.00673794699908547
Empty function: 13.769 ms
ExpNeural: 14.867 ms, exp(-5) = 0.00675211846828461
ExpSeries8: 15.121 ms, exp(-5) = 0.00641270968867667
ExpSeries16: 32.046 ms, exp(-5) = 0.00673666189488182
exp1: 15.062 ms, exp(-5) = -12.3333325982094
exp2: 15.090 ms, exp(-5) = 13.708332516253
exp3: 16.251 ms, exp(-5) = -12.3333325982094
exp4: 17.924 ms, exp(-5) = 728.368055056781
exp5: 20.972 ms, exp(-5) = -6.13293614238501
exp6: 24.212 ms, exp(-5) = 3.55518353166184
exp7: 29.092 ms, exp(-5) = -1.8271053775984
exp7 +/-: 38.482 ms, exp(-5) = 0.00695945286970704
ExpNeural est l'équivalent de la Exp fonction spécifiée dans le début de ce texte. ExpSeries8 est le formulation que j'ai à l'origine revendiquée n'était pas très efficace .NET; lors de la mise en œuvre exactement comme Neil il était en fait très rapide. ExpSeries16 est l'analogue de la formule, mais avec 16 multiplications au lieu de 8. exp1 par exp7 sont les différentes fonctions de Adriano réponse ci-dessous. La dernière variante de exp7 est une variante où le signe de x est cochée; si le résultat est négatif, la fonction renvoie 1/exp(-x)
à la place.
Malheureusement, ni l'un ni l' expN fonctions énumérées par Adriano sont suffisantes dans la plus large de la valeur négative de la gamme que j'envisage. L'extension de la série approche par Neil Coffey semble être plus appropriée dans "ma" plage de valeurs, mais il est trop rapide divergentes avec les plus grandes négatif x, en particulier lors de l'utilisation de "seulement" 8 multiplications.
- je suis curieux de connaître votre référence à des "ensembles de neurones." actuellement, je suis à la simulation d'un réseau de neurones à l'aide de C++ et face à la même
exp
goulot d'étranglement des performances que vous avez relevé. existe-il des papiers dans les neurosciences computationnelles qui ont proposé approximativeexp
fonctions qui sont très vite?
Vous devez vous connecter pour publier un commentaire.
Au cas où quelqu'un veut reproduire l'erreur relative de la fonction de la question, voici un moyen à l'aide de Matlab (le "rapide" de l'exposant n'est pas très rapide dans Matlab, mais il est précis):
Maintenant, la période de l'erreur exactement coïncide avec le moment où la valeur binaire de
tmp
déborde de la mantisse en l'exposant. Brisons nos données dans des bacs en jetant les bits qui font l'exposant (ce qui périodique), et de ne garder que le haut de huit bits restants (pour faire de notre table de recherche d'une taille raisonnable):Maintenant nous calculons la moyenne d'ajustement requise:
L'erreur relative est réduite à +/- .0006. Bien sûr, d'autres tables de dimensions sont possibles (par exemple, un 6 bits de la table avec 64 entrées +/- .0025) et l'erreur est quasiment linéaire dans la taille de la table. L'interpolation linéaire entre les entrées de la table serait d'améliorer l'erreur encore plus loin, mais au détriment des performances. Puisque nous avons déjà rencontré la précision de l'objectif, il faut éviter toute nouvelle dégradation de leurs performances.
À ce point, c'est trivial éditeur de compétences pour prendre les valeurs calculées par MatLab et de créer une table de recherche en C#. Pour chaque calcul, nous avons ajouter un masque de bits, la lecture de la table, et de double précision multiplier.
L'accélération est très semblable à l'original du code-pour mon ordinateur, c'est environ 30% plus rapide compilé que les x86 et environ 3x plus rapide pour x64. Avec mono sur ideone, c'est une importante perte nette (mais c'est l'original).
Le code source complet et cas de test: http://ideone.com/UwNgx
memcpy
pour votre type beaucoup les jeux de mots. De toute façon, selon que votre cible a virgule flottante, vous pouvez utiliser en simple précision pour la table de recherche. Nous parlons d'une erreur relative de .0006, sorte de double précision n'aide pas.BitConverter
fonctions par unmemcpy
, et déplacer le[]
dans la définition de tableau. Le reste du code C# est valide C déjà.Essayez les solutions suivantes (
exp1
est plus rapide,exp7
est plus précis).Code
Précision
Crédits
Ces implémentations de
exp()
ont été calculés par "scoofy" à l'aide de la série de Taylor d'unetanh()
mise en œuvre de "fuzzpilz" (quels qu'ils soient, je viens d'avoir ces références sur mon code).De la série de Taylor des approximations (comme le
expX()
fonctions dans Adriano répondre) sont plus précises proche de zéro et peut avoir d'énormes erreurs à -20 ou même à -5. Si l'entrée a une aire de répartition connue, comme de -20 à 0, comme la question d'origine, vous pouvez utiliser une petite table et un autre se multiplier afin d'améliorer considérablement la précision.L'astuce est de reconnaître que exp() peut être séparé en entier et de fractions. Par exemple:
La partie fractionnaire sera toujours compris entre -1 et 1, donc un développement en série de Taylor rapprochement sera assez précis. La partie entière a seulement 21 valeurs possibles pour exp(-20) exp(0), de sorte que ceux-ci peuvent être stockés dans un petit look up table.
Le code suivant devrait répondre aux exigences de précision, comme pour les entrées dans [-87,88] les résultats ont erreur relative <= 1.73 e-3. Je ne sais pas C#, c'est donc le code en C, mais je suppose que la conversion devrait être assez simple.
Je suppose que, puisque l'exigence d'exactitude est faible, l'utilisation de la précision de calcul est très bien. Un algorithme classique est utilisé, dans lequel le calcul de exp() est appliquée pour le calcul de exp2(). Après l'argument de conversion par multiplication par log2(e), exponentation par la partie fractionnaire est gérée à l'aide d'un minimax polynôme de degré 2, alors que exponentation par la partie intégrante de l'argument est effectuée par la manipulation directe de l'exposant partie de la norme IEEE-754 simple précision nombre.
La volatilité de l'union facilite la ré-interprétation d'une séquence de bits comme un entier ou un flottant simple précision-nombre de point nécessaire pour l'exposant à la manipulation. Il ressemble à C# offre decidated ré-interprétation de fonctions pour ce, qui est beaucoup plus propre.
Les deux potentiels problèmes de performances de la fonction floor() et float->int conversion. Traditionnellement, les deux ont été lents sur x86 en raison de la nécessité de gérer processeur de dynamique de l'état. Mais l'ESS (en particulier SSE 4.1) fournit des instructions qui permettent à ces opérations pour être rapide. Je ne sais pas si C# peuvent faire usage de ces instructions.
memcpy
en C et C++, et l'optimiseur doit faire quelque chose de raisonnable, sans le briser, avec optimisation basée sur la stricte aliasing.__m128
)? Je Vous Remercie.expf()
SIMD-mise en œuvre et que je ne puis répondre.J'ai étudié la papier par Nicol Schraudolph où l'original C mise en œuvre de la fonction ci-dessus a été défini plus en détail maintenant. Il semble qu'il n'est probablement pas possible à la quasi-approuver l'exactitude de la exp calcul sans de graves impacts sur la performance. D'autre part, l'approximation n'est valable aussi pour les grandes amplitudes de x, jusqu'à +/- 700, ce qui est évidemment avantageux.
L'implémentation de la fonction ci-dessus est réglé pour obtenir le minimum d'erreur quadratique moyenne. Schraudolph décrit la façon dont le terme additif dans le tmp expression peut être modifiée pour obtenir des rapprochement propriétés.
Il souligne aussi que, à un "microscopique" niveau approximatif "exp" fonction des expositions d'escalier en cas de comportement depuis 32 bits sont jetés dans la conversion de long à double. Cela signifie que la fonction est la pièce sage constant sur une très petite échelle, mais la fonction est au moins de ne jamais diminuer avec l'augmentation de la x.