Maison >développement back-end >Tutoriel Python >Comment une quête de loterie m'a conduit aux pouvoirs de PyTorch
À un moment donné, vous avez probablement entendu dire que vos chances de gagner une loterie sont très minces. Comme pour tout ce qui concerne les probabilités, plusieurs essais peuvent faire pencher la balance en votre faveur. Maintenant, si vous deviez participer à plusieurs loteries, vos chances d'en gagner une seraient un peu meilleures, en fonction du nombre de loteries supplémentaires auxquelles vous avez participé. Cela ne garantit en aucun cas que vous finirez par gagner, mais avec une répartition uniforme. , et en suivant la loi des grands nombres (dans ce cas, c'est-à-dire un grand nombre de loteries), nous pouvons arriver à des possibilités relativement plus probables.
Il est important de comprendre que chaque nouvelle loterie est indépendante des autres, et que le même « numéro de ticket » de loterie pourrait gagner de nombreuses loteries différentes (suivant la loi des grands nombres). Vous pourriez également ne pas avoir de chance et choisir le mauvais numéro à chaque loterie, quel que soit le nombre de fois que vous avez essayé. Vous avez maintenant deux options :
Théoriquement (et mathématiquement), les deux scénarios ont la même probabilité de se produire. Cependant, le scénario 2 vous donnera un léger avantage. À mesure que le nombre de fois tend vers l’infini, chaque nombre finira par être sélectionné. Le problème est qu'avec le scénario 1, vous devez essayer plusieurs fois en espérant que le numéro que vous avez choisi à ce moment-là correspond au numéro gagnant. Avec le scénario 2, vous êtes sûr que comme les épreuves tendent vers l'infini, votre numéro finira par « gagner ». Pour cet article de blog, nous utiliserons le scénario 2.
Alors, pensez-vous pouvoir répondre à cette question avant que je vous donne la réponse ?
"Si toutes les loteries autour de vous avaient des machines à sous pour exactement 1 million de personnes et que vous choisissiez le même ticket [x] pour tous ceux auxquels vous jouiez, à combien de loteries faudrait-il jouer pour enfin être gagnant ?" (N'hésitez pas à commenter votre réponse initiale)
La réponse est...
Environ 14,4 millions de fois.
Le reste de cet article de blog expliquera comment je suis arrivé à cette valeur, comment les simulations ont été effectuées et quelques mises en garde. Les choses deviendront plus techniques à partir d’ici.
Les numéros de billets d'une loterie de 1 million de personnes seraient compris entre 1 et 1 000 000 (ou entre 0 et 999 999). Les joueurs ne peuvent choisir qu'un numéro compris dans cette plage pour chaque loterie, et le ticket gagnant ne peut provenir que de cette plage. Essentiellement, nous pouvons dire que nous aurons un ensemble de 1 million de nombres.
Compte tenu du fait qu'un utilisateur peut choisir n'importe quel nombre dans cette plage, nous devons satisfaire la condition selon laquelle chaque élément de l'ensemble est touché au moins une fois. En effet, si chaque numéro a été appelé au moins une fois, il couvrirait n'importe quel numéro de ticket possible qu'un joueur aurait pu choisir. Cela signifie également que nous ne nous soucions pas du nombre de fois où chaque nombre est exécuté, ce qui fait d'un « ensemble » la structure de données Python idéale à utiliser pour notre simulation. Nous commencerons avec un ensemble vide et le remplirons avec un nombre généré aléatoirement à chaque itération jusqu'à ce que l'ensemble contienne tous les nombres dans la plage spécifiée. Puisque les ensembles Python ne répètent pas les nombres, nous n'avons pas à nous soucier de garantir l'unicité.
def calculate_lottery_chances(lottery_players_count): number_set = set() count = 0 while len(number_set) < lottery_players_count: gen_number = random.randint(1, lottery_players_count) number_set.add(gen_number) count += 1 return count
Pour une loterie de 1 000 000 de personnes, l'appel de fonction ressemblerait à : calculate_lottery_chances(1000000), et il renverrait le nombre de tentatives de loterie avant de gagner. Organiser le code de cette manière le rend très extensible.
En un mot, la cause profonde du problème est la « variation ». La première fois que j'ai exécuté la fonction, j'ai obtenu "13,1 millions" de fois comme valeur. Je l'ai rediffusé et j'ai obtenu quelque chose de l'ordre de 13,9 millions. Je l'ai fait encore plus de fois et j'ai obtenu des réponses très variées – à un moment donné, j'ai obtenu 15 millions. Il était clair que je devais faire cela et trouver une moyenne. En suivant le modèle existant jusqu'à présent, j'ai pensé que comme le nombre d'itérations pour faire la moyenne tendait vers l'infini, je serais plus proche d'avoir une réponse fiable. Il y avait un besoin pour quelque chose qui puisse faire cela, et le faire rapidement, et cela m'a amené à écrire cette fonction :
def average_over_n_times(function, function_arg, n): """ This returns the average of the returned value of a function when it is called n times, with its (one) arg """ total = 0 for x in range(0, n): total += function(function_arg) return round(total/n)
Par la suite, tout serait alors rafistolé comme :
num_of_trials = average_over_n_times(calculate_lottery_chances, lottery_players_count, n)
Où "n" représenterait le nombre de fois où faire la moyenne des résultats. Ceci soulève cependant un autre problème qui sera abordé dans la section suivante.
Plus la valeur de n est grande, plus le résultat se rapproche d'un "cas moyen". Cependant, étant donné qu’il n’y a toujours pas d’absolu ni de certitude, effectuer cette série de tâches trop souvent cesse d’être productif. Je dis cela pour les raisons suivantes :
En gardant cela à l'esprit, j'ai testé "n" avec les valeurs : 10, 20, 30, 50, 100, 1000 et 5000 fois.
À ce stade, vous vous demandez probablement pourquoi le mot « PyTorch » dans le titre de l'article de blog n'a même pas été mentionné. Eh bien, même si j'ai mentionné tester n avec des valeurs différentes, ce n'était pas le même code que j'ai utilisé pour tous les tests.
Il s'agissait d'expériences lourdes en termes de calcul, et mon processeur m'a dit un mot. Les extraits de code que j'ai partagés plus tôt ont été écrits dans un fichier qui n'avait aucune dépendance de package externe, et le fichier a été exécuté dans le shell bash avec la commande time ajoutée pour suivre les temps d'exécution. Voici à quoi ressemblaient les temps d'exécution en utilisant uniquement le CPU :
n | Time (min and sec) |
---|---|
10 | 1m34.494s |
20 | 3m2.591s |
30 | 5m19.903s |
50 | 10m58.844s |
100 | 14m56.157s |
À 1000, je n'arrivais plus à faire fonctionner le programme. Je ne savais pas s'il s'était cassé à mi-chemin et n'avait pas réussi à arrêter l'exécution, mais je l'ai annulé après 4 heures et 57 minutes. Il y a quelques facteurs qui, selon moi, ont influencé cela, et dont je discuterai dans la section « mises en garde ». Quoi qu'il en soit, le bruit de mon ventilateur était strident et je savais que j'avais peut-être un peu trop poussé le processeur modestement alimenté de mon ordinateur portable. J'ai refusé d'accepter la défaite, et en réfléchissant à ce que je pourrais faire pour exécuter au moins des itérations à 4 chiffres, je me suis souvenu de quelque chose qu'un de mes amis qui travaillait avec PyTorch m'a dit :
"Les GPU sont généralement plus efficaces en termes de calculs intensifs que les CPU"
PyTorch utilise le GPU, ce qui en fait l'outil parfait pour ce travail.
PyTorch serait utilisé pour les calculs à nos fins, donc refactoriser le code calculate_lottery_chances() existant signifierait modifier les opérations numériques dépendant du processeur et passer à des structures de données PyTorch appropriées. En un mot :
Le refactor de calculate_lottery_chances ressemblerait à :
def calculate_lottery_chances(lottery_players_count): number_set = set() count = 0 while len(number_set) < lottery_players_count: gen_number = random.randint(1, lottery_players_count) number_set.add(gen_number) count += 1 return count
J'ai défini mon appareil sur "xpu" car mon ordinateur utilise un GPU Intel Graphics, pris en charge par PyTorch.
Pour m'assurer que mon GPU était utilisé pendant l'exécution, j'ai ouvert mon gestionnaire de tâches Windows et accédé à la section « performances » avant d'exécuter. Lors de l'exécution, j'ai constaté une augmentation notable de l'utilisation des ressources GPU.
Pour le contexte, voici un avant vs après :
Avant :
Notez que l'utilisation du GPU est à 1 %
Après :
Notez que l'utilisation du GPU est à 49 %
Pour les temps d'exécution pour différentes valeurs de n, le GPU était plusieurs fois plus rapide. Il a exécuté des valeurs de n inférieures à 100 de manière constante en moins d'une minute et a été capable de calculer une valeur de n à 5 000 (cinq mille !)
Voici un tableau des environnements d'exécution utilisant le GPU :
n | Time (min and sec) |
---|---|
10 | 0m13.920s |
20 | 0m18.797s |
30 | 0m24.749s |
50 | 0m34.076s |
100 | 1m12.726s |
1000 | 16m9.831s |
Pour avoir une idée visuelle de l'ampleur de l'écart de performances entre les opérations du GPU et du CPU pour cette expérience, voici une visualisation des données à laquelle réfléchir :
L'axe X a été plafonné à 100 car je ne pouvais plus obtenir une sortie réaliste "en temps opportun" du CPU, ne laissant ainsi aucune place à la comparaison avec le GPU. Réaliser les expériences avec des nombres compris entre 1 000 et 5 000 m'a donné environ « 14,4 millions de fois », le plus souvent. C'est ainsi que j'ai obtenu la réponse plus tôt.
Cette expérience faisait des hypothèses et s'appuyait sur certaines façons de faire les choses. De plus, mon inexpérience avec PyTorch signifie potentiellement qu'il existe peut-être une approche plus efficace. Voici quelques facteurs à considérer qui peuvent avoir influencé soit l'exactitude de mes conclusions, soit les délais d'exécution :
Enfin, je voudrais souligner que c'était la première fois que j'utilisais PyTorch pour quoi que ce soit, et j'ai été assez impressionné par les performances.
Quand je suis descendu dans le terrier du lapin avec ça, je ne m'attendais pas à voir de tels gains de performances. J'ai appris l'idée derrière les tenseurs et quelques éléments sur les mécanismes de support derrière des tâches encore plus complexes sur le plan informatique. Vous avez la liberté d'utiliser, de reproduire ou de modifier les extraits de code à votre guise.
Merci de m'avoir fait plaisir et j'espère que vous avez passé une lecture amusante.
À la prochaine fois,
Bravo. ?
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!