Maison >développement back-end >Tutoriel Python >Comment une quête de loterie m'a conduit aux pouvoirs de PyTorch

Comment une quête de loterie m'a conduit aux pouvoirs de PyTorch

Linda Hamilton
Linda Hamiltonoriginal
2025-01-01 04:19:16648parcourir

Introduction

À 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 :

  1. Vous pouvez essayer un numéro aléatoire à chaque fois.
  2. Vous pouvez essayer le même numéro à chaque fois.

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...
How a Lottery Quest Led Me to The Powers of PyTorch

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.
How a Lottery Quest Led Me to The Powers of PyTorch

La logique

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.

How a Lottery Quest Led Me to The Powers of PyTorch

Le problème

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.

Que devrait être « n » ?

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 :

  • Le temps n'est pas infini, et nous ne pouvons pas effectuer ces calculs indéfiniment, ce qui signifie qu'il y aura toujours une variation (aussi minime soit-elle) à chaque fois qu'il est exécuté, ce qui va à l'encontre de l'idée d'un « absolu ».
  • Les ressources informatiques sont limitées.
  • L'une des hypothèses de cette expérience est que le « caractère aléatoire » généré par les ordinateurs peut imiter avec précision la réalité.
  • Tout comme pour les temps d'exécution des algorithmes, les plus petites magnitudes cessent d'être aussi importantes que les plus grandes. Une variation d'environ 100 000 ne serait pas aussi significative lorsqu'il s'agit de valeurs supérieures à 13 000 000.

En gardant cela à l'esprit, j'ai testé "n" avec les valeurs : 10, 20, 30, 50, 100, 1000 et 5000 fois.

Où PyTorch intervient-il ?

À 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.

Refactorisation

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 type de données Python set() ne suffirait plus.
  • La fonction Python randint() serait remplacée par son équivalent PyTorch.
  • Étant donné que le type de données set() serait insuffisant, il y aurait un changement pour générer un tenseur de zéros qui correspond à la taille du lottery_players_count, avec un booléen pour indiquer si un numéro a déjà gagné ou non.

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.

Sortir

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 :

How a Lottery Quest Led Me to The Powers of PyTorch
Notez que l'utilisation du GPU est à 1 %

Après :

How a Lottery Quest Led Me to The Powers of PyTorch
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 :

How a Lottery Quest Led Me to The Powers of PyTorch

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.

Mises en garde

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 :

  1. J'ai fait l'hypothèse subtile que le hasard généré par ordinateur imite le hasard dans la vie réelle (le monde physique).
  2. Bien que j'ai changé un peu de logique pour utiliser PyTorch, le reste du code reposait toujours sur le CPU. Par exemple, dans la fonction Average_over_n_times(), il est possible que l'ajout dans la boucle et la moyenne aient bénéficié des équivalents PyTorch. Je soupçonne qu'il y aurait eu une amélioration des performances.
  3. Je ne suis pas sûr de l'impact du batch_size que j'ai utilisé sur la précision et les performances.
  4. Tous les tests CPU et GPU ont été effectués avec mon PC branché, pour permettre à la machine de fonctionner au mieux. Les exécuter avec un appareil alimenté par batterie peut entraîner des durées d'exécution plus longues.
  5. Le CUDA de PyTorch a peut-être l'avantage sur "XPU", ​​mais mon PC ne prend pas en charge le premier.
  6. J'ai évité de laisser mon PC "dormir" pendant les tests. L'exécution des tests peut potentiellement prendre plus de temps si votre ordinateur est en veille.

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.

Conclusion

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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn