Représenter un univers de probabilité et créer un générateur aléatoire en Python
par
, 28/02/2023 à 11h47 (2910 Affichages)
I. Introduction
Notre objectif est de créer une classe UniversProba pour représenter un univers de probabilité et dans laquelle on redéfinira les opérateurs de multiplication et de puissance pour ces nouveaux objets.
Nous ajouterons également à notre classe une méthode permettant de tirer au hasard des éléments dans un univers de probabilité.
Rappel important :
La surcharge d’opérateur permet de redéfinir un opérateur dans une classe.
Par exemple, en Python l’opérateur « + » est surchargé par la classe int et la classe str :
- On peut ainsi réaliser une addition classique entre deux entiers : print(1+2) affiche 3.
- Ou concaténer deux chaînes de caractères : print("bon"+"jour") renvoie "bonjour".
II. Univers de probabilité
En théorie des probabilités, un univers, souvent noté Ω, U ou S, est l'ensemble de toutes les issues (résultats) pouvant être obtenues au cours d'une expérience aléatoire.
À chaque élément e de l'univers Ω , c'est-à-dire à chacun des résultats possibles de l'expérience considérée, nous pouvons associer le sous-ensemble {e} constitué de cet élément, appelé événement élémentaire.
De manière plus générale, toute partie de l'univers Ω est appelée un événement.
Par exemple, si nous lançons une pièce, nous avons deux issues possibles : « pile » ou « face ».
L'expérience aléatoire considérée est alors : « un lancer de pièce ».
Nous pouvons définir l'univers Ω associé à cette expérience, qui regroupe tous les résultats possibles : Ω = {pile, face}.
Pour une expérience de lancer de dé, nous choisirions l'univers Ω = {1, 2, 3, 4, 5, 6}, chaque événement élémentaire étant équiprobable.
Enfin, si on tire une boule dans une urne contenant 2 boules blanches et 3 boules noires, on peut modéliser cette situation en considérant l'univers Ω = {b, n}, avec les probabilités :
p({b}) = 2/5
p({n}) = 3/5.
Pour un univers Ω nous avons donc toujours p(Ω)=1.
II-A. Produit cartésien de 2 univers
Soit deux univers :
Ω1 = {e1, e2}
Ω2 = {e3, e4}
Si on tire un élément de chaque, l'ensemble des résultats possibles peut être représenté par le produit cartésien :
Ω1 × Ω2 = {e1, e2} × {e3, e4} = {(e1,e3), (e1,e4), (e2,e3), (e2,e4)}
Considérons maintenant deux urnes U1 et U2 contenant des boules colorées :
U1 : 2 blanches et 3 noires.
U2 : 3 blanches et 1 noire.
Si on tire une boule dans U1, puis une boule dans U2 :
Au 1er tirage on peut associer l'univers Ω1 = {b, n} muni de la probabilité p1 définie par : p1(b)=2/5 et p1(n)=3/5.
Au 2nd tirage on peut associer l'univers Ω2 = {b, n} muni de la probabilité p2 définie par : p2(b)=3/4 et p2(n)=1/4.
L'ensemble des issues du tirage global peut ainsi être représenté par le produit cartésien :
Ω1 × Ω2 = {(b,b), (b,n), (n,b), (n,n)}
On peut également noter les points suivants :
- La probabilité de tirer 1 boule blanche dans U1, puis 1 boule blanche dans U2 est égale à (2/5)×(3/4).
- La probabilité de tirer 1 boule blanche dans U1, puis 1 boule noire dans U2 vaut (2/5)×(1/4).
- La probabilité de tirer 1 boule noire dans U1, puis 1 boule blanche dans U2 est égale à (3/5)×(3/4).
- La probabilité de tirer 1 boule noire dans U1, puis 1 boule noire dans U2 vaut (3/5)×(1/4).
On multiplie donc à chaque fois la probabilité p1(ei) du 1er événement, par la probabilité p2(ej) du 2nd événement, pour obtenir la probabilité p1(ei) × p2(ej) associée au couple (ei, ej).
On obtient ainsi l'univers :
Ω = {(b,b), (b,n), (n,b), (n,n)} avec les probabilités associées 0.3, 0.1, 0.45 et 0.15
Il représente donc un nouvel univers dont la somme des probabilités des événements élémentaires vaut également 1.
II-B. Produit cartésien de plus de 2 univers
Le produit de 3 univers Ω1, Ω2 et Ω3 :
Ω = Ω1 × Ω2 × Ω3
Peut également s'écrire en utilisant la propriété d'associativité du produit cartésien :
Ω = (Ω1 × Ω2) × Ω3
Le produit Ω × Ω × Ω est appelé cube cartésien de Ω et il est noté Ω3 :
Ω3 = Ω × Ω × Ω
Il peut également s'écrire :
Ω3 = (Ω × Ω) × Ω
On peut ainsi généraliser le produit cartésien à plus de deux univers.
Si vous souhaitez avoir plus d'information sur le sujet, je vous invite à consulter les pages wikipedia Univers (probabilité) et Produit cartésien.
III. Classe UniversProba
En Python, nous pouvons définir l'univers Ω associé à une expérience de lancer de pièce Ω = {pile , face} à l'aide d'une liste :
Ω = ["p", "f"]
Chaque élément de la liste correspond à une issue possible dans l'univers des probabilités.
De la même façon pour une expérience de lancer de dé, l'univers Ω = {1, 2, 3, 4, 5, 6} sera représenté par :
Ω = [1, 2, 3, 4, 5, 6]
Si maintenant on considère le tirage d'une boule dans une urne contenant 2 boules blanches et 3 boules noires, on pourra représenter l'univers de probabilité associé par la liste des éléments :
Ω = ["b", "n"]
Et la liste des probabilités associée :
probas = [0.4, 0.6]
Pour définir des univers de probabilité en Python et pouvoir réaliser des opérations entre eux, il nous faut créer une classe UniversProba.
Notre classe comportera un constructeur, c'est à dire une méthode particulière __init__() dont le code est exécuté quand la classe est instanciée.
Elle va nous permettre de définir la liste des éléments de l'univers (toutes les issues possibles) et la liste des probabilités associée au moment de la création de l'objet :
Code Python : 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
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 class UniversProba: def __init__(self, elements, probas=[]): # méthode constructeur de la classe if probas==[] and elements!=[]: # si aucune probabilité n'est passé en argument # on initialise la liste des probas. Exemple : [1/4, 1/4, 1/4, 1/4] -> [0.25, 0.25, 0.25, 0.25] probas = [1/len(elements)]*len(elements) if len(elements)==len(probas) and round(sum(probas),6)==1: # si le nombre d'éléments est égal au nombre de probas et si la somme des probas vaut 1 # on regroupe les éléments et les probas dans deux listes : ['e1', 'e2', 'e2', 'e3'] et [0.25, 0.25, 0.25, 0.25] -> (['e1', 'e2', 'e3'], [0.25, 0.5, 0.25]) (elements, probas) = self.grouper(elements,probas) self.elements = elements # on définit la liste des éléments de l'univers. Exemple : ["e1", "e2", "e3"] self.probas = probas # on définit la liste des probas associées. Exemple : [0.25, 0.5, 0.25] else: print ("Ce n'est pas un univers de probabilité !") def __str__(self): # permet d'afficher l'univers avec les probas associées : # Univers : Ω = {e1,e2,e3}, Probabilités : p({e1})=0.25, p({e2})=0.5, p({e3})=0.25 # initialisation des variables univers = ""; probas = "" # parcours simulané des éléments et des probas contenus dans les 2 listes de l'objet self for ei,pi in zip(self.elements,self.probas): # ajout de l'élément à l'univers univers = univers + str(ei) + ", " # ajout de sa probabilité : p({ei}) = pi probas = probas + "p({" + str(ei) + "}) = " + str(round(pi,6)) + "\n" univers_proba = "Univers : \nΩ = {" + univers[:-2] + "} \n\n" + "Probabilités : \n" + probas # retourne l'univers Ω avec la probabilité de chaque événement élémentaire return univers_proba.replace("'","")
La méthode __str__ permet d'afficher un univers de probabilité sous la forme :
Univers :
Ω = {e1, e2, e3}
Probabilités :
p({e1}) = 0.25
p({e2}) = 0.5
p({e3}) = 0.25
Pour tester ces méthodes, nous ajoutons simplement deux lignes au module :
Code Python : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 # création de l'objet UniversProba : univers Ω = {b, n} avec les probas associées 0.4 et 0.6 Ω = UniversProba(['b','n'],[0.4, 0.6]) print(Ω) # affiche l'univers de probabilité
Le code affiche :
Univers :
Ω = {b, n}
Probabilités :
p({b}) = 0.4
p({n}) = 0.6
III.A. Surcharge de l'opérateur « * »
Pour surcharger l'opérateur « * » et l'appliquer à 2 objets UniversProba, nous devons également ajouter une méthode __mul__() à la classe :
Code Python : 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 class UniversProba: .... def __mul__(self, other): # méthode permettant de redéfinir l'opérateur « * » pour 2 univers de probabilité : Ω1 * Ω2 = {e1, e2} * {e3, e4} = {(e1,e3), (e1,e4), (e2,e3), (e2,e4)} # initialisation des listes elements=[]; probas=[] # parcours simultané des listes contenant les éléments et les probas de self for ei,pi in zip(self.elements,self.probas): if not isinstance(ei, tuple): ei=(ei,) # si ei n'est pas un tuple on en crée un. # parcours simultané des listes contenant les éléments et les probas de other for ej,pj in zip(other.elements,other.probas): if not isinstance(ej, tuple): ej=(ej,) # si ej n'est pas un tuple on en crée un. elements = elements + [ei + ej] # ajout du couple d'éléments à la liste. Exemple : elements = elements + [(e1,e3)] probas = probas + [pi * pj] # multiplication des probas et ajout du résultat à la liste. Exemple : probas = probas + [0.2*0.5] # renvoie l'univers de proba. produit des 2 autres univers passés en argument return UniversProba(elements, probas)
Cette méthode permet donc de redéfinir le produit pour 2 univers de probabilité :
Ω1 * Ω2 = {e1, e2} * {e3, e4} = {(e1,e3), (e1,e4), (e2,e3), (e2,e4)}
Pour tester l'opérateur « * » portant sur 2 objets de la classe UniversProba, nous ajoutons simplement ces lignes :
Code Python : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9 # création du 1er objet UniversProba : univers Ω1 = {b, n} avec les probabilités associées 0.4 et 0.6 Ω1 = UniversProba(['b','n'],[0.4, 0.6]) # création du 2e objet UniversProba : univers Ω2 = {b, n} avec les probabilités associées 0.75 et 0.25 Ω2 = UniversProba(['b','n'],[0.75, 0.25]) Ω = Ω1 * Ω2 # produit des 2 univers : Ω = Ω1 × Ω2 print(Ω) # affiche le résultat
Le code affiche :
Univers :
Ω = {(b, b), (b, n), (n, b), (n, n)}
Probabilités :
p({(b, b)}) = 0.3
p({(b, n)}) = 0.1
p({(n, b)}) = 0.45
p({(n, n)}) = 0.15
III-B. Surcharge de l'opérateur de puissance
Pour surcharger l'opérateur « ** » et pouvoir ainsi obtenir l'univers de probabilité Ωn, nous devons ajouter une méthode __pow__() à la classe :
Code Python : 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 class UniversProba: ... def __pow__(self, n): # méthode permettant de redéfinir l'opérateur de puissance pour des objets UniversProba : self ** n if n>0: # si n est supérieur à zéro Ω = self # on multiplie n-1 fois Ω par self à l'aide de l'opérateur * for i in range(n-1): Ω = Ω*self # équivalent à : Ω = Ω.__mul__(self) elif n==0: # si n=0 Ω = UniversProba([()]) # on crée un univers contenant un seul élément vide : Ω = {()} # renvoie l'univers résultat de l'opération (self ** n) return Ω
Cette méthode permet donc de redéfinir l'opérateur « ** » pour les univers de probabilité :
Ω**n = Ω * Ω * ... * Ω
Ou encore :
Ω**3 = Ω * Ω * Ω = (Ω * Ω) * Ω
Pour tester cet opérateur portant sur un objet de la classe UniversProba, nous ajoutons simplement ces lignes de code :
Code Python : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 # création de l'objet UniversProba : univers équiprobable Ω1 = {"p", "f"} Ω1 = UniversProba(['p','f']) Ω = Ω1**3 # Ω1 au cube print(Ω) # affiche le résultat de Ω1 au cube
Le code affiche :
Univers :
Ω = {(f, f, f), (f, f, p), (f, p, f), (f, p, p), (p, f, f), (p, f, p), (p, p, f), (p, p, p)}
Probabilités :
p({(f, f, f)}) = 0.125
p({(f, f, p)}) = 0.125
p({(f, p, f)}) = 0.125
p({(f, p, p)}) = 0.125
p({(p, f, f)}) = 0.125
p({(p, f, p)}) = 0.125
p({(p, p, f)}) = 0.125
p({(p, p, p)}) = 0.125
Note importante : si vous souhaitez avoir une liste complète des opérateurs standards disponibles en Python, je vous invite à consulter cette documentation.
III-C. Tirage avec remise de n éléments dans l'univers Ω
Pour réaliser ce tirage il nous faut tout d'abord importer le module random à l'aide de la ligne de code :
Code Python : Sélectionner tout - Visualiser dans une fenêtre à part import random
Ensuite, pour tirer au hasard des éléments dans une liste Python, on peut faire :
Code Python : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 tirage_element = random.choice(['a', 'b', 'c']) # tirage d'un élément dans la liste ['a', 'b', 'c'] tirage_elements = random.choices(['a', 'b', 'c'], k = 10) # tirage avec remise de 10 valeurs dans ['a', 'b', 'c'] tirage_elements = random.choices(['a', 'b', 'c'], weights = [0.25, 0.5, 0.25], k = 10) # tirage avec remise de 10 valeurs, en attribuant des poids aux différents éléments
Nous ajoutons maintenant une méthode tirage() à notre classe qui va utiliser la fonction choices :
Code Python : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 class UniversProba: ... def tirage(self,n): # génère une liste de n éléments tirés au hasard dans l'univers de proba self # tirage avec remise de n éléments en fonction de leur poids return random.choices(self.elements, weights = self.probas, k=n)
Ensuite, on peut par exemple simuler une expérience consistant à lancer 10 fois de suite un dé en notant à chaque lancer le numéro de la face supérieure.
Il s'agit en fait de réaliser un tirage avec remise de 10 éléments dans un univers équiprobable :
Code Python : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 # création d'un objet UniversProba : Ω = {1, 2, 3, 4, 5, 6}, probas = [1/6, 1/6, 1/6, 1/6, 1/6] Ω = UniversProba([1,2,3,4,5,6]) tirage_elements = Ω.tirage(10) # on effectue un tirage avec remise de 10 éléments dans cet univers print(tirage_elements) # affiche une liste de 10 éléments tirés au hasard
Le code affiche :
[3, 4, 5, 4, 2, 1, 3, 4, 5, 1]
Note importante : cette méthode peut donc nous permettre de simuler une variable aléatoire suivant une loi de probabilité (loi uniforme, loi normale, etc.).
Pour avoir plus d'information sur ce module, je vous invite à consulter cette page.
Module complet de test :
Code Python : 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
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156 import random # import du module permettant de générer des nombres pseudo-aléatoires class UniversProba: def __init__(self, elements, probas=[]): # méthode constructeur de la classe if probas==[] and elements!=[]: # si aucune probabilité n'est passé en argument # on initialise la liste des probas. Exemple : [1/4, 1/4, 1/4, 1/4] -> [0.25, 0.25, 0.25, 0.25] probas = [1/len(elements)]*len(elements) if len(elements)==len(probas) and round(sum(probas),6)==1: # si le nombre d'éléments est égal au nombre de probas et si la somme des probas vaut 1 # on regroupe les éléments et les probas dans deux listes : ['e1', 'e2', 'e2', 'e3'] et [0.25, 0.25, 0.25, 0.25] -> (['e1', 'e2', 'e3'], [0.25, 0.5, 0.25]) (elements, probas) = self.grouper(elements,probas) self.elements = elements # on définit la liste des éléments de l'univers. Exemple : ["e1", "e2", "e3"] self.probas = probas # on définit la liste des probas associées. Exemple : [0.25, 0.5, 0.25] else: print ("Ce n'est pas un univers de probabilité !") def __str__(self): # permet d'afficher l'univers avec les probas associées : # Univers : Ω = {e1,e2,e3}, Probabilités : p({e1})=0.25, p({e2})=0.5, p({e3})=0.25 # initialisation des variables univers = ""; probas = "" # parcours simulané des éléments et des probas contenus dans les 2 listes de l'objet self for ei,pi in zip(self.elements,self.probas): # ajout de l'élément à l'univers univers = univers + str(ei) + ", " # ajout de sa probabilité : p({ei}) = pi probas = probas + "p({" + str(ei) + "}) = " + str(round(pi,6)) + "\n" univers_proba = "Univers : \nΩ = {" + univers[:-2] + "} \n\n" + "Probabilités : \n" + probas # retourne l'univers Ω avec la probabilité de chaque événement élémentaire return univers_proba.replace("'","") def grouper(self, elements, probas): # méthode pour regrouper les éléments et les probas dans deux listes : ['e1', 'e2', 'e2', 'e3'] et [0.2, 0.2, 0.2, 0.4] -> (['e1', 'e2', 'e3'], [0.2, 0.4, 0.4]) # réunion des éléments et des probas : ['e1','e2','e2','e3'] et [0.2, 0.2, 0.2, 0.4] -> [('e1',0.2), ('e2',0.2), ('e2',0.2), ('e3',0.4)] elements_probas =list(zip(elements,probas)) # on crée la liste des éléments uniques : ['e1','e2','e3'] elements = list(set(elements)) elements.sort() # tri de la liste des éléments uniques : ['e1', 'e2', 'e3'] probas = [] # initialisation de la liste des probas # parcours des éléments for ei in elements: p = sum([ej[1] for ej in elements_probas if ej[0]==ei]) # évaluation de la proba de chaque élément probas = probas + [p] # ajout de la proba à la liste # on retourne les 2 listes : (['e1', 'e2', 'e3'], [0.2, 0.4, 0.4]) return (elements, probas) def __mul__(self, other): # méthode permettant de redéfinir l'opérateur « * » pour 2 univers de probabilité : Ω1 * Ω2 = {e1, e2} * {e3, e4} = {(e1,e3), (e1,e4), (e2,e3), (e2,e4)} # initialisation des listes elements=[]; probas=[] # parcours simultané des listes contenant les éléments et les probas de self for ei,pi in zip(self.elements,self.probas): if not isinstance(ei, tuple): ei=(ei,) # si ei n'est pas un tuple on en crée un. # parcours simultané des listes contenant les éléments et les probas de other for ej,pj in zip(other.elements,other.probas): if not isinstance(ej, tuple): ej=(ej,) # si ej n'est pas un tuple on en crée un. elements = elements + [ei + ej] # ajout du couple d'éléments à la liste. Exemple : elements = elements + [(e1,e3)] probas = probas + [pi * pj] # multiplication des probas et ajout du résultat à la liste. Exemple : probas = probas + [0.2*0.5] # renvoie l'univers de proba. produit des 2 autres univers passés en argument return UniversProba(elements, probas) def __pow__(self, n): # méthode permettant de redéfinir l'opérateur de puissance pour des objets UniversProba : self ** n if n>0: # si n est supérieur à zéro Ω = self # on multiplie n-1 fois Ω par self à l'aide de l'opérateur * for i in range(n-1): Ω = Ω*self # équivalent à : Ω = Ω.__mul__(self) elif n==0: # si n=0 Ω = UniversProba([()]) # on crée un univers contenant un seul élément vide : Ω = {()} # renvoie l'univers résultat de l'opération (self ** n) return Ω def tirage(self,n): # génère une liste de n éléments tirés au hasard dans l'univers de proba self # tirage avec remise de n éléments en fonction de leur poids return random.choices(self.elements, weights = self.probas, k=n) def __eq__(self, other): # méthode permettant de redéfinir l'opérateur « == » pour 2 univers de proba return (self.elements==other.elements) # renvoie True si les 2 univers sont égaux print("I. Produit cartésien\n") # création du 1er objet UniversProba : univers Ω1 = {b, n} avec les probas respectives 0.4 et 0.6 Ω1 = UniversProba(['b','n'],[0.4, 0.6]) print("Ω1 = " + str(Ω1.elements)+ "\n") # création du 2e objet UniversProba : univers Ω2 = {b, n} avec les probas respectives 0.75 et 0.25 Ω2 = UniversProba(['b','n'],[0.75, 0.25]) print("Ω2 = " + str(Ω2.elements)+ "\n") Ω = Ω1 * Ω2 # produit des 2 univers : Ω = Ω1 × Ω2 print("Ω = Ω1 × Ω2 :\n") print(Ω) # affiche le résultat print("===========================================\n") print("II. Puissance de n\n") # création de l'objet UniversProba : univers équiprobable Ω1 = {"p", "f"} Ω1 = UniversProba(['p','f']) print("Ω1 = " + str(Ω1.elements)) print("Probas = " + str(Ω1.probas) + "\n") Ω = Ω1**3 # Ω1 au cube print("Ω = Ω1**3 :\n") print(Ω) # affiche le résultat de Ω1 au cube print("===========================================\n") print("III. Tirage de n éléments dans l'univers\n") # création d'un objet UniversProba : univers équiprobable Ω = {1, 2, 3, 4, 5, 6} Ω = UniversProba([1,2,3,4,5,6]) print(Ω) # affiche l'univers Ω print("Tirage avec remise de 10 éléments :\n") # on effectue un tirage avec remise de 10 éléments dans Ω tirage_elements = Ω.tirage(10) print(tirage_elements) # affiche une liste de 10 éléments tirés au hasard
IV. Conclusion
Après avoir défini le produit cartésien de plusieurs univers de probabilité, nous avons pu redéfinir les opérateurs « * » et « ** » pour ces nouveaux objets dans une classe Python.
Puis, nous avons ajouté à notre classe une méthode permettant de tirer au hasard une liste d'éléments dans un univers de probabilité.
Libre à chacun d'ajouter ensuite d'autres méthodes à cette classe, pour par exemple générer un arbre des probabilités.
Sources :
https://fr.wikipedia.org/wiki/Univer...abilit%C3%A9s)
https://fr.wikipedia.org/wiki/Produit_cart%C3%A9sien
https://docs.python.org/fr/3/library/random.html
https://docs.python.org/fr/3/library/operator.html