Comptez les arrivées avec le processus de Poisson
Une API de prédiction reçoit en moyenne 4 requêtes par seconde. Mais « en moyenne » cache beaucoup de choses : certaines secondes, elle n’en reçoit aucune ; d’autres, elle en reçoit 8 ou 10. Pour dimensionner le service, il faut connaître la loi du nombre de requêtes, pas seulement sa moyenne.
Quand les arrivées proviennent d’un grand nombre d’utilisateurs indépendants, qui agissent chacun rarement, un modèle s’impose presque toujours : le processus de Poisson. C’est le processus d’arrivée de référence des files d’attente, et c’est aussi le plus simple des processus de naissance et de mort. Nous suivrons cette API tout au long du chapitre.
Compter des événements au fil du temps
Section intitulée « Compter des événements au fil du temps »Un processus de comptage compte le nombre d’événements survenus dans l’intervalle : ici, le nombre de requêtes reçues depuis l’instant 0. Il part de 0, ne prend que des valeurs entières et ne décroît jamais. Le nombre d’événements dans un intervalle est l’accroissement .
Un processus de comptage est un processus de Poisson de taux (ou d’intensité) si :
- ;
- ses accroissements sont indépendants : les nombres d’arrivées sur des intervalles disjoints sont des variables aléatoires indépendantes (ce qui arrive entre 10 h et 10 h 01 ne dit rien sur ce qui arrive entre 11 h et 11 h 01) ;
- ses accroissements sont stationnaires : la loi de ne dépend que de la durée , pas de l’instant ;
- sur un petit intervalle de durée , il arrive une requête avec probabilité et deux ou plus avec probabilité : les arrivées sont isolées.
La dernière condition dit exactement qu’on a un processus de naissance et de mort de naissance pure : pour tout et . Le graphe est une ligne où l’on ne fait que monter :
λ λ λ λ 0 ─────► 1 ─────► 2 ─────► 3 ··· n ─────► n+1 ···Le nombre d’arrivées suit une loi de Poisson
Section intitulée « Le nombre d’arrivées suit une loi de Poisson »Écrivons les équations du régime transitoire du chapitre 6 avec et :
La première donne, avec , . La deuxième, pour , est une équation linéaire dont la solution avec est . En continuant par récurrence (méthode de variation de la constante), on obtient
Le nombre d’arrivées sur une durée suit une loi de Poisson de paramètre . Grâce à la stationnarité des accroissements, c’est vrai sur n’importe quel intervalle de longueur : suit la même loi. On en déduit
Le taux est donc le nombre moyen d’événements par unité de temps, ce qui justifie son nom.
Les temps entre deux arrivées sont exponentiels
Section intitulée « Les temps entre deux arrivées sont exponentiels »Regardons maintenant le processus autrement : non plus « combien d’arrivées ? », mais « quand ? ». Notons l’instant de la première requête. Attendre plus de secondes, c’est n’avoir reçu aucune requête dans :
suit donc une loi exponentielle de paramètre . Grâce à l’indépendance et à la stationnarité des accroissements, le processus « repart à zéro » après chaque arrivée : les temps inter-arrivées successifs sont indépendants et de même loi , de moyenne (ici 0,25 seconde).
La réciproque est vraie et très utile : si l’on place des événements séparés par des durées indépendantes de loi , on obtient un processus de Poisson de taux . C’est ainsi qu’on le simule.
L’instant de la -ième arrivée est la somme de exponentielles indépendantes. Sa loi s’appelle la loi d’Erlang de paramètres (c’est une loi Gamma), de moyenne . Pour calculer sa fonction de répartition, on utilise l’équivalence : la -ième requête est arrivée avant si et seulement si on a reçu au moins requêtes dans .
Superposer et décomposer des flux
Section intitulée « Superposer et décomposer des flux »Deux propriétés rendent le processus de Poisson très commode pour modéliser des réseaux de files d’attente.
Superposition. Si deux flux indépendants sont des processus de Poisson de taux et , leur réunion est un processus de Poisson de taux . Supposons que l’API reçoive 3 requêtes par seconde du site web et 1 de l’application mobile, de façon indépendante : au total, c’est bien un Poisson de taux 4. C’est la même propriété que « le minimum d’exponentielles est exponentiel » (chapitre 5).
Décomposition. Inversement, si chaque arrivée d’un Poisson de taux est aiguillée vers la destination avec probabilité , indépendamment des autres, alors les flux obtenus sont des processus de Poisson indépendants de taux . Si l’API envoie 70 % des requêtes vers un modèle A et 30 % vers un modèle B (un test A/B, par exemple), le modèle A reçoit un Poisson de taux 2,8 et le modèle B un Poisson de taux 1,2. La probabilité que le modèle B ne reçoive aucune requête pendant une seconde vaut .
L’indépendance des deux flux décomposés est un résultat étonnant : savoir que le modèle A vient de recevoir un pic de requêtes ne dit rien sur ce qu’a reçu le modèle B.
Simuler et vérifier les hypothèses
Section intitulée « Simuler et vérifier les hypothèses »La simulation découle directement de la propriété des temps inter-arrivées :
import numpy as np
rng = np.random.default_rng(42)lam, duree = 4.0, 10_000.0 # 4 requêtes/s pendant 10 000 s
# Inter-arrivées exponentielles de moyenne 1/lam, puis instants d'arrivée cumulésinter = rng.exponential(1 / lam, size=int(lam * duree * 1.2))arrivees = np.cumsum(inter)arrivees = arrivees[arrivees < duree]
# Nombre de requêtes dans chaque secondecomptes = np.bincount(arrivees.astype(int), minlength=int(duree))print(comptes.mean(), comptes.var()) # tous deux proches de 4print((comptes == 0).mean(), (comptes >= 8).mean()) # proches de 0.018 et 0.051
# Décomposition : chaque requête va vers A avec probabilité 0.7vers_A = rng.random(arrivees.size) < 0.7print(vers_A.sum() / duree) # proche de 2.8 requêtes/sLa dernière vérification est aussi la plus utile sur des données réelles : pour un processus de Poisson, la variance du nombre d’arrivées par intervalle égale sa moyenne. Si vous observez une variance bien plus grande que la moyenne, les arrivées arrivent en rafales (un utilisateur qui envoie un lot de requêtes, un pic à la sortie d’une newsletter) et le modèle de Poisson sous-estimera les pics. De même, si le taux varie au cours de la journée, les accroissements ne sont plus stationnaires : on découpe alors la journée en tranches où le taux est à peu près constant.
En résumé
Section intitulée « En résumé »- Un processus de Poisson de taux est un processus de comptage avec , des accroissements indépendants et stationnaires, et des arrivées isolées (). C’est un processus de naissance pure.
- suit une loi de Poisson de paramètre : , avec moyenne et variance .
- Les inter-arrivées sont indépendantes de loi ; l’instant de la -ième arrivée suit une loi d’Erlang de moyenne . On simule un Poisson en cumulant des exponentielles.
- Superposition : Poisson() + Poisson() indépendants = Poisson(). Décomposition aléatoire avec probabilités : Poisson indépendants de taux .
- Sur des données réelles, vérifiez que la variance des comptes est proche de leur moyenne et que le taux est stable dans le temps.
Exercices
Section intitulée « Exercices »Exercice · Tickets au support
facileUn service de support reçoit des tickets selon un processus de Poisson de taux 6 par heure.
- Quelle est la probabilité de ne recevoir aucun ticket pendant 20 minutes ?
- Combien de tickets reçoit-on en moyenne sur une journée de 8 heures ? Avec quel écart-type ?
- Quelle est la durée moyenne entre deux tickets ?
Voir la solution
- Sur 20 minutes, soit d’heure, le nombre de tickets suit une loi de Poisson de paramètre . La probabilité de n’en recevoir aucun vaut .
- Sur 8 heures, le paramètre vaut : 48 tickets en moyenne, avec une variance de 48, donc un écart-type de .
- Les inter-arrivées sont exponentielles de moyenne d’heure, soit 10 minutes.
En code
import numpy as npfrom scipy.stats import poisson
taux = 6 # tickets par heureprint(poisson.pmf(0, taux / 3)) # 0.1353 : aucun ticket en 20 minutesjour = poisson(taux * 8) # journée de 8 heuresprint(jour.mean(), jour.std()) # 48.0 6.93print(60 / taux) # 10.0 minutes entre deux ticketstaux <- 6 # tickets par heuredpois(0, taux / 3) # 0.1353 : aucun ticket en 20 minutesc(moyenne = taux * 8, ecart_type = sqrt(taux * 8)) # 48 6.9360 / taux # 10 minutes entre deux ticketsExercice · Test A/B sur une API
moyenL’API du chapitre reçoit un Poisson de taux 4 requêtes par seconde, aiguillées à 70 % vers le modèle A et à 30 % vers le modèle B.
- Quelle est la probabilité que le modèle A reçoive au moins une requête pendant une seconde donnée ?
- Quelle est la probabilité que l’API reçoive au plus 5 requêtes en 2 secondes ?
- Pendant une seconde, le modèle A a reçu 6 requêtes, bien plus que sa moyenne. Quelle est la loi du nombre de requêtes reçues par B pendant cette même seconde ?
Voir la solution
- A reçoit un Poisson de taux 2,8 : .
- Sur 2 secondes, le nombre total suit une loi de Poisson de paramètre 8. .
- Par la propriété de décomposition, les flux vers A et vers B sont indépendants. Le nombre de requêtes de B suit donc toujours une loi de Poisson de paramètre 1,2, quoi qu’ait reçu A.
En code
import numpy as npfrom scipy.stats import poisson
print(1 - poisson.pmf(0, 0.7 * 4)) # 0.9392 : A reçoit au moins une requêteprint(poisson.cdf(5, 4 * 2)) # 0.1912 : au plus 5 requêtes en 2 s
# Indépendance des flux décomposés : simulation seconde par seconderng = np.random.default_rng(0)total = rng.poisson(4, 200_000)vers_A = rng.binomial(total, 0.7)vers_B = total - vers_Aprint(np.corrcoef(vers_A, vers_B)[0, 1]) # -0.0005, corrélation nulleprint(vers_B[vers_A == 6].mean(), vers_B.mean()) # 1.195 1.201 : B garde sa moyenne 1.21 - dpois(0, 0.7 * 4) # 0.9392 : A reçoit au moins une requêteppois(5, 4 * 2) # 0.1912 : au plus 5 requêtes en 2 s
# Indépendance des flux décomposés : simulation seconde par secondeset.seed(0)total <- rpois(2e5, 4)vers_A <- rbinom(length(total), total, 0.7)vers_B <- total - vers_Acor(vers_A, vers_B) # 0.0005, corrélation nullec(mean(vers_B[vers_A == 6]), mean(vers_B)) # 1.200 1.197 : B garde sa moyenne 1.2Exercice · L'instant de la troisième requête
difficileToujours avec requêtes par seconde, on note l’instant d’arrivée de la troisième requête.
- Quelle est sa moyenne ?
- Calculez , la probabilité que la troisième requête arrive dans la première seconde.
- Le serveur met 1 seconde à démarrer et ne commence à traiter qu’à partir de là. Quelle est la probabilité qu’au moins trois requêtes l’attendent à son démarrage ?
Voir la solution
- est la somme de trois inter-arrivées exponentielles de moyenne 0,25 s : s.
- On utilise , avec de loi de Poisson de paramètre 4 :
- C’est exactement la même question, formulée autrement : au moins trois requêtes attendent si et seulement si la troisième est arrivée avant 1 s. La probabilité vaut environ 0,762.
En code
import numpy as npfrom scipy.stats import gamma, poisson
lam = 4S3 = gamma(a=3, scale=1 / lam) # loi d'Erlang(3, lam)print(S3.mean()) # 0.75print(S3.cdf(1), 1 - poisson.cdf(2, lam), 1 - 13 * np.exp(-4)) # 0.7619 (trois fois)
# Simulation : somme de trois inter-arrivées exponentiellesrng = np.random.default_rng(0)s3 = rng.exponential(1 / lam, (500_000, 3)).sum(axis=1)print(s3.mean(), (s3 <= 1).mean()) # 0.7498 0.7621lam <- 43 / lam # 0.75, moyenne d'une Erlang(3, lam)c(pgamma(1, shape = 3, rate = lam), # P(S3 <= 1) 1 - ppois(2, lam), # = P(N1 >= 3) 1 - 13 * exp(-4)) # 0.7619 (trois fois)
# Simulation : somme de trois inter-arrivées exponentiellesset.seed(0)s3 <- rowSums(matrix(rexp(3 * 5e5, lam), ncol = 3))c(mean(s3), mean(s3 <= 1)) # 0.7506 0.7615