IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

Apprendre à réaliser des simulations aléatoires en Python


Sujet :

Python

  1. #1
    Rédacteur/Modérateur

    Avatar de User
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    8 581
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 8 581
    Billets dans le blog
    67
    Par défaut Apprendre à réaliser des simulations aléatoires en Python
    Bonjour,

    Je vous présente un nouvel article :


    Le module random met en œuvre des générateurs de nombres pseudo-aléatoires pour différentes distributions (loi uniforme, loi normale, etc.).

    Après avoir présenté quelques fonctions importantes de ce module, notre objectif sera de les utiliser pour réaliser différentes simulations (jeu de pile ou face, simulation de variable aléatoire, etc.).

    Pour conclure, nous proposerons de créer notre propre générateur de nombres pseudo-aléatoires et de le tester à l'aide d'une méthode de Monte-Carlo.
    Nom : alea.png
Affichages : 32439
Taille : 8,3 Ko

    Bonne lecture
    Vous trouverez dans la FAQ, les sources ou les tutoriels, de l'information accessible au plus grand nombre, plein de bonnes choses à consulter sans modération

    Des tutoriels pour apprendre à créer des formulaires de planning dans vos applications Access :
    Gestion sur un planning des présences et des absences des employés
    Gestion des rendez-vous sur un calendrier mensuel


    Importer un fichier JSON dans une base de données Access :
    Import Fichier JSON

  2. #2
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 716
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 716
    Par défaut
    Salut,

    Bel effort... mais 3 remarques sur la dernière partie (le PRNG).
    D'abord un petit bug, vous intialisez vos paramètres par défaut via:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
            def __init__(self, m=2147483648, a=1103515245, c=12345, s=int(time.time())):
    L'intention semble être d'initialiser, par défaut, X0 avec le retour de time.time à l'instant de l'appel.
    Dit autrement, si j'écris:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    >>> u = Random()
    >>> v = Random()
    la suite de nombres aléatoires générée par u et v devrait être différente.
    Hélas, elle sera ici identique.
    La valeur par défaut sera calculée lorsque l'interpréteur définit la fonction (et non lorsqu'elle sera appelée).

    La qualité du générateur dépend des valeurs de m, a et c comme mentionné dans l'article de wikipédia sur ce type de générateurs.

    Il est préférable d'utilisée des valeurs "bien construites" et déjà testées.. et si on ne teste pas la différence d'aléas entre plusieurs valeurs, on ne proposera peut être pas de les changer n'importe comment.

    Du coup, on teste le code et non le "degré d'aléatoire" du générateur.
    A partir du moment, où on doit choisir des valeurs déjà testées (m, a, c,...) pour un tel générateur, on testera que le code et non le générateur (ce qui est beaucoup plus compliqué...).

    Enfin, utiliser un générateur à la place d'une classe serait plus concis et donnerait (compte tenu des remarques précédentes):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    def random(s=None):
        m=2147483648
        a=1103515245
        c=12345
        s = s or int(time.time())
        while True:
            s = (a * s + c) % m
            yield s/m

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  3. #3
    Rédacteur/Modérateur

    Avatar de User
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    8 581
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 8 581
    Billets dans le blog
    67
    Par défaut
    Bonjour,

    Citation Envoyé par wiztricks
    ...
    la suite de nombres aléatoires générée par u et v devrait être différente.
    Hélas, elle sera ici identique.
    La valeur par défaut sera calculée lorsque l'interpréteur définit la fonction (et non lorsqu'elle sera appelée).
    En effet, ça m'a surpris. Je vais corriger avec un code de ce genre inspiré du vôtre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
        def __init__(self, m=2147483648, a=1103515245, c=12345, s=None):
            # m : module ; a : multiplicateur; c : incrément; s : valeur initiale ou graine (seed)
            # Si les arguments de la fonction sont omis, on passe les mêmes valeurs que pour le générateur d'UNIX
     
            # mise à jour des attributs avec les valeurs passées en argument
            self.m = m
            self.a = a
     
            # self.x = s or int(time.time())
            self.x = s if s else int(time.time())
    pour être un peu plus lisible, merci à vous.

    ------------------------------------------------

    J'ai aussi une autre méthode init_rand() pour réinitialiser le générateur :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
        def init_rand(self, s=None):
            # méthode permettant d'initialiser le générateur de nombres aléatoires
     
            # mise à jour de x avec la valeur initiale ou la graine s
            self.x = s if s else int(time.time())
    Citation Envoyé par wiztricks
    La qualité du générateur dépend des valeurs de m, a et c comme mentionné dans l'article de wikipédia sur ce type de générateurs.

    Il est préférable d'utilisée des valeurs "bien construites" et déjà testées.. et si on ne teste pas la différence d'aléas entre plusieurs valeurs, on ne proposera peut être pas de les changer n'importe comment.
    Oui, je laisse cette possibilité. J'ai pensé que ça donnerait plus de souplesse pour changer les paramètres, mais je précise bien :

    Dans notre cas, on va choisir de créer un générateur congruentiel linéaire, car il est facile à mettre en œuvre et généralement de bonne qualité sous réserve bien sûr de choisir les bons paramètres.
    ...
    ...on connaît les critères sur les nombres a, c et m qui vont permettre d’obtenir une période maximale (égale à m) :
    https://fr.wikipedia.org/wiki/G%C3%A...incr%C3%A9ment

    et plus loin je précise aussi :

    Les valeurs par défaut de m, a et c sont également celles utilisées par le générateur d'UNIX : 231, 1103515245 et 12345.
    avec d'autres liens :

    https://fr.wikipedia.org/wiki/G%C3%A...9aire#Exemples
    https://en.wikipedia.org/wiki/Linear..._in_common_use

    ---------------------

    Pour la partie test du générateur, comme je le mentionne, je suis aussi conscient des limites de la méthode. j'essaierai de vous répondre prochainement plus en détail sur ce point.


    Citation Envoyé par wiztricks
    Enfin, utiliser un générateur à la place d'une classe serait plus concis et donnerait (compte tenu des remarques précédentes):
    J'ai choisi d'utiliser une classe car comme je le propose elle permet de créer simplement d'autre méthodes basées sur le générateur rand() comme rand_int(lower,upper) (un peu comme pour le module random) :

    La classe Random contient également une méthode init_rand() pour initialiser le générateur de nombres aléatoires une fois l'objet créé, et une méthode rand_int(lower,upper) permettant de générer un entier au hasard. Libre à chacun d'ajouter d'autres méthodes à cette classe.
    D'autre part personnellement je trouve le code plus lisible.

    En tous cas merci pour vos remarques.
    Vous trouverez dans la FAQ, les sources ou les tutoriels, de l'information accessible au plus grand nombre, plein de bonnes choses à consulter sans modération

    Des tutoriels pour apprendre à créer des formulaires de planning dans vos applications Access :
    Gestion sur un planning des présences et des absences des employés
    Gestion des rendez-vous sur un calendrier mensuel


    Importer un fichier JSON dans une base de données Access :
    Import Fichier JSON

  4. #4
    Expert confirmé Avatar de papajoker
    Homme Profil pro
    Développeur Web
    Inscrit en
    Septembre 2013
    Messages
    2 322
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nièvre (Bourgogne)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Septembre 2013
    Messages : 2 322
    Par défaut
    bonjour

    De, sans intéret à plus ...

    Puisque c'est une classe , un petit moyen de gagner une nano seconde et quelques octets
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    __slots__ = ("x", "a", "c", "m")
    Puisque c'est une classe, (pourquoi pas ...) j'ai testé :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ...
        def __enter__(self):
            return self.rand
     
        def __exit__(self, exception_type, exception_value, exception_traceback):
            pass
     
    with Random() as rand:
        print(rand())
        print(rand())
    On pousse le bouchon
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
        def __call__(self):
            return self.rand()
     
    rand = Random()
    print(rand())
    print(rand())
    Puisque c'est une classe (et que le mot "générateur" est dans le titre), en faire un véritable en langue python
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
        def __iter__(self):
            return self
     
        def __next__(self):
            return self.rand()
    Puisque l'on parle de "test" et que maintenant j'ai un "vrai" générateur, j'ai (quand même) testé si c'est bien si aléaloire que cela (on peut rêver avec ces valeurs)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    MAX = 100_000
    tests = [-1] * MAX
     
    for i, value in enumerate(Random()):
        if i >= MAX:
            break
        if value in tests:
            raise Exception("BAD Random, at", i, value)
        tests[i] = value
        if i % 5_000 == 0:     # je n'aime pas dormir devant une console vide
            print(i, value)
    Même pas vu si mon raise fonctionne

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    for i, value in enumerate(Random(m=3648, a=245, c=45)):
    ...
    Exception: ('BAD Random, at', 576, 0.15158991228070176)
    yes
    $moi= (:nono: !== :oops:) ? :king: : :triste: ;

  5. #5
    Rédacteur/Modérateur

    Avatar de User
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    8 581
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 8 581
    Billets dans le blog
    67
    Par défaut
    Bonjour,

    Ah oui, merci

    Comme on parle de réaliser des simulations l'aspect "sécurité" est moins primordial mais pour finir j'ai quand même choisi de définir les paramètres du générateur en dur dans le code :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Random():
        # classe permettant de définir le générateur congruentiel linéaire (LCG)
     
        def __init__(self, s=None):
            # s : valeur initiale ou graine (seed)
     
            # self.x = s or int(time.time())
            self.x = s if s else int(time.time())
     
        def rand(self):
            # méthode permettant de générer un nombre aléatoire entre 0 et 1
     
            # paramètres du générateur :  m : module ; a : multiplicateur; c : incrément
            m=2147483648; a=1103515245; c=12345
     
            # formule de récurrence pour le LCG : Xn+1 = (aXn + c) mod m
            self.x = (a * self.x + c) % m
     
            # renvoie la nouvelle valeur pour x divisé par le module m
            return self.x/m
    Cdlt
    Vous trouverez dans la FAQ, les sources ou les tutoriels, de l'information accessible au plus grand nombre, plein de bonnes choses à consulter sans modération

    Des tutoriels pour apprendre à créer des formulaires de planning dans vos applications Access :
    Gestion sur un planning des présences et des absences des employés
    Gestion des rendez-vous sur un calendrier mensuel


    Importer un fichier JSON dans une base de données Access :
    Import Fichier JSON

  6. #6
    Membre prolifique
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 827
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 827
    Billets dans le blog
    1
    Par défaut
    Bonjour
    Citation Envoyé par User Voir le message
    En effet, ça m'a surpris.
    C'est parce que, contrairement à ce que laisse sous-entendre l'intuition, un paramètre par défaut est créé non pas à l'appel de la fonction mais à la définition de la fonction.

    Démonstration
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class toto:
    	def __init__(self): print("Création toto")
     
    def fct(param=toto()): pass
     
    print("Début")
    fct()
    print("Fin")
    Si le paramètre était créé à l'appel de la fonction, on aurait "Début" puis "Création toto" puis "Fin". Mais dans les faits, on a "Création toto" puis "Début" puis "Fin" montrant que l'objet est créé au maximum avant le premier print. Rajouter d'autres print avant et après la définition de la fonction te montrera que cela se passe bel et bien à la définition de la fonction.

    Dans la plupart des cas, ce comportement (qui est fait pour optimiser la vitesse) ne pose pas de souci. Les soucis n'arrivent que quand le paramètre par défaut est soit un objet mutable (ie une liste) soit un évènement calculé (ton cas).

    D'où la solution pour créer le comportement désiré : positionner l'argument par défaut à une valeur particulière immuable (ie None) puis tester cette valeur dans la fonction.
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  7. #7
    Expert confirmé Avatar de papajoker
    Homme Profil pro
    Développeur Web
    Inscrit en
    Septembre 2013
    Messages
    2 322
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nièvre (Bourgogne)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Septembre 2013
    Messages : 2 322
    Par défaut
    Citation Envoyé par User Voir le message
    quand même choisi de définir les paramètres du générateur en dur dans le code :
    Je serais plus pour un compromis.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class Random:
        MODULE = 2147483648
        MULTIPLICATEUR = 1103515245
        INCREMENT = 12345
     
        def rand(self):
            self.m, self.a, self.c = self.MODULE, self.MULTIPLICATEUR, self.INCREMENT
            # ou bien sûr, plus logique : utiliser directement ces constantes au lieu de créer des variables m, a et c
    De plus, le nom des constantes est plus parlant.
    Un dev python doit comprendre que se sont des constantes donc pas de raison d'y toucher. Et on laisse une possibilité...


    -----------------------------
    EDIT
    -----------------------------

    Puisque j'ai la possibilité de modifier, autre test complémentaire, ici je ne cherche plus l'unicité mais l'uniformité sur un "grand" nombre de tirages.

    J'ai écrit ce test sur uniquement Random.MODULE (et j'ai laissé le code pour le transformer en itérateur)
    ps: sous réserve d'un matheux (car je ne le suis pas), ma logique est peut-être fausse ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    count = 0
    PAS_BON = 40    # normalement un pourcentage
    for n in range(1000):
        MAX = 1000
        tests = [0] * 10
     
        for i, value in enumerate(Random()):
            if i >= MAX:
                break
            val = int((value * 10) // 1)
            tests[val] += 1
        v = max(tests) - min(tests)
        if v > PAS_BON:
            count += 1
            print(f"  {Random.MODULE:<14} {str(tests):60} {v}", "*" * 5)
        Random.MODULE = random.randint(5_000_000, 2_147_483_648) # OUI random.randint() !
    print(count, "/", n + 1, "au dessus de ", PAS_BON)
    résultat, j'ai à peu près toujours environ +10% avec un trop grand écart. Et généralement + de 1% pour écart de 50 max
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
      1822770248     [112, 92, 97, 128, 95, 110, 78, 73, 109, 106]                55 *****
      994122736      [113, 82, 146, 75, 104, 104, 93, 94, 124, 65]                81 *****
      1104348338     [107, 100, 127, 97, 88, 108, 64, 111, 103, 95]               63 *****
      165707073      [38, 78, 129, 117, 169, 52, 78, 104, 91, 144]                131 *****
    ...
     
    121 / 1000 au dessus de  40
    15 / 1000 au dessus de  50
    Je n'ai pas la moindre idée que nous pouvons donner à PAS_BON A partir de quand peut-on dire que nous avons un mauvais aléatoire ??? (si ma logique n'est pas trop mauvaise...)
    $moi= (:nono: !== :oops:) ? :king: : :triste: ;

  8. #8
    Rédacteur/Modérateur

    Avatar de User
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    8 581
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 8 581
    Billets dans le blog
    67
    Par défaut
    Bonjour,

    Citation Envoyé par Sve@r Voir le message
    Bonjour

    C'est parce que, contrairement à ce que laisse sous-entendre l'intuition, un paramètre par défaut est créé non pas à l'appel de la fonction mais à la définition de la fonction.

    Démonstration
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class toto:
    	def __init__(self): print("Création toto")
     
    def fct(param=toto()): pass
     
    print("Début")
    fct()
    print("Fin")
    Si le paramètre était créé à l'appel de la fonction, on aurait "Début" puis "Création toto" puis "Fin". Mais dans les faits, on a "Création toto" puis "Début" puis "Fin" montrant que l'objet est créé au maximum avant le premier print. Rajouter d'autres print avant et après la définition de la fonction te montrera que cela se passe bel et bien à la définition de la fonction.

    Dans la plupart des cas, ce comportement (qui est fait pour optimiser la vitesse) ne pose pas de souci. Les soucis n'arrivent que quand le paramètre par défaut est soit un objet mutable (ie une liste) soit un évènement calculé (ton cas).

    D'où la solution pour créer le comportement désiré : positionner l'argument par défaut à une valeur particulière immuable (ie None) puis tester cette valeur dans la fonction.
    Merci pour ces précisions éclairantes, l'intuition peut en effet nous induire en erreur. J'ai fait un raccourci dans ma tête sans vraiment analyser le problème.

    @papajoker

    Je pense que je vais laisser comme ça pour les variables pour me baser sur la formule de récurrence.

    Merci pour tes propositions.
    Vous trouverez dans la FAQ, les sources ou les tutoriels, de l'information accessible au plus grand nombre, plein de bonnes choses à consulter sans modération

    Des tutoriels pour apprendre à créer des formulaires de planning dans vos applications Access :
    Gestion sur un planning des présences et des absences des employés
    Gestion des rendez-vous sur un calendrier mensuel


    Importer un fichier JSON dans une base de données Access :
    Import Fichier JSON

Discussions similaires

  1. Réponses: 2
    Dernier message: 01/05/2023, 12h55
  2. trouver des ports libre avec python
    Par xxiemeciel dans le forum Réseau/Web
    Réponses: 2
    Dernier message: 30/07/2007, 16h41

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo