web-dev-qa-db-fra.com

Moyen rapide de générer des bits pseudo-aléatoires avec une probabilité donnée de 0 ou 1 pour chaque bit

Normalement, un générateur de nombres aléatoires renvoie un flux de bits pour lequel la probabilité d'observer un 0 ou un 1 dans chaque position est égale (c'est-à-dire 50%). Appelons cela un PRNG impartial.

J'ai besoin de générer une chaîne de bits pseudo-aléatoires avec la propriété suivante: la probabilité de voir un 1 dans chaque position est p (c'est-à-dire que la probabilité de voir un 0 est 1-p). Le paramètre p est un nombre réel compris entre 0 et 1; dans mon problème, il arrive qu'il ait une résolution de 0,5%, c'est-à-dire qu'il peut prendre les valeurs 0%, 0,5%, 1%, 1,5%, ..., 99,5%, 100%.

Notez que p est une probabilité et non une fraction exacte. Le nombre réel de bits mis à 1 dans un flux de n bits doit suivre la distribution binomiale B (n, p).

Il existe une méthode naïve qui peut utiliser un PRNG non biaisé pour générer la valeur de chaque bit (pseudocode):

generate_biased_stream(n, p):
  result = []
  for i in 1 to n:
    if random_uniform(0, 1) < p:
      result.append(1)
    else:
      result.append(0)
  return result

Une telle implémentation est beaucoup plus lente qu'une implémentation générant un flux non biaisé, car elle appelle la fonction de génération de nombres aléatoires une fois par chaque bit; tandis qu'un générateur de flux non biaisé l'appelle une fois par taille de mot (par exemple, il peut générer 32 ou 64 bits aléatoires avec un seul appel).

Je veux une implémentation plus rapide, même si cela sacrifie légèrement le hasard. Une idée qui vient à l'esprit est de précalculer une table de recherche: pour chacune des 200 valeurs possibles de p, calculer les valeurs C à 8 bits en utilisant l'algorithme plus lent et les enregistrer dans une table. Ensuite, l'algorithme rapide ne prendrait qu'un de ces éléments au hasard pour générer 8 bits asymétriques.

Un dos du calcul de l'enveloppe pour voir combien de mémoire est nécessaire: C devrait être au moins 256 (le nombre de valeurs 8 bits possibles), probablement plus pour éviter les effets d'échantillonnage; disons 1024. Peut-être que le nombre devrait varier en fonction de p, mais restons simples et disons que la moyenne est 1024. Puisqu'il y a 200 valeurs de p => l'utilisation totale de la mémoire est de 200 Ko. Ce n'est pas mauvais et pourrait tenir dans le cache L2 (256 Ko). J'ai encore besoin de l'évaluer pour voir s'il y a des effets d'échantillonnage qui introduisent des biais, auquel cas C devra être augmenté.

Une lacune de cette solution est qu'elle ne peut générer que 8 bits à la fois, même avec beaucoup de travail, tandis qu'un non biaisé PRNG peut générer 64 bits à la fois avec seulement quelques instructions arithmétiques.

Je voudrais savoir s'il existe une méthode plus rapide, basée sur des opérations de bits au lieu de tables de recherche. Par exemple, modifier directement le code de génération de nombres aléatoires pour introduire un biais pour chaque bit. Cela permettrait d'obtenir les mêmes performances qu'un PRNG impartial.


Modifier le 5 mars

Merci à tous pour vos suggestions, j'ai eu beaucoup d'idées et de suggestions intéressantes. Voici les meilleurs:

  • Modifiez les exigences du problème afin que p ait une résolution de 1/256 au lieu de 1/200. Cela permet d'utiliser les bits plus efficacement et offre également plus de possibilités d'optimisation. Je pense que je peux faire ce changement.
  • Utilisez le codage arithmétique pour consommer efficacement les bits d'un générateur non biaisé. Avec le changement de résolution ci-dessus, cela devient beaucoup plus facile.
  • Quelques personnes ont suggéré que les PRNG sont très rapides, donc l'utilisation du codage arithmétique pourrait en fait ralentir le code en raison de la surcharge introduite. Au lieu de cela, je devrais toujours consommer le nombre de bits le plus défavorable et optimiser ce code. Voir les repères ci-dessous.
  • @rici a suggéré d'utiliser SIMD. C'est une bonne idée, qui ne fonctionne que si nous consommons toujours un nombre fixe de bits.

Repères (sans décodage arithmétique)

Remarque: comme beaucoup d'entre vous l'ont suggéré, j'ai changé la résolution de 1/200 à 1/256.

J'ai écrit plusieurs implémentations de la méthode naïve qui prend simplement 8 bits aléatoires sans biais et génère 1 bit biaisé:

  • Sans SIMD
  • Avec SIMD utilisant la bibliothèque de classes vectorielles d'Agner Fog, comme suggéré par @rici
  • Avec SIMD utilisant intrinsèques

J'utilise deux générateurs de nombres pseudo-aléatoires impartiaux:

Je mesure également la vitesse de la non PRNG pour comparaison. Voici les résultats:


RNG: Ranvec1(Mersenne Twister for Graphics Processors + Multiply with Carry)

Method: Unbiased with 1/1 efficiency, SIMD=vectorclass (incorrect, baseline)
Gbps/s: 16.081 16.125 16.093 [Gb/s]
Number of ones: 536,875,204 536,875,204 536,875,204
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency
Gbps/s: 0.778 0.783 0.812 [Gb/s]
Number of ones: 104,867,269 104,867,269 104,867,269
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=vectorclass
Gbps/s: 2.176 2.184 2.145 [Gb/s]
Number of ones: 104,859,067 104,859,067 104,859,067
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=intrinsics
Gbps/s: 2.129 2.151 2.183 [Gb/s]
Number of ones: 104,859,067 104,859,067 104,859,067
Theoretical   : 104,857,600

SIMD augmente les performances d'un facteur 3 par rapport à la méthode scalaire. Il est 8 fois plus lent que le générateur non biaisé, comme prévu.

Le générateur polarisé le plus rapide atteint 2,1 Gb/s.


RNG: xorshift128plus

Method: Unbiased with 1/1 efficiency (incorrect, baseline)
Gbps/s: 18.300 21.486 21.483 [Gb/s]
Number of ones: 536,867,655 536,867,655 536,867,655
Theoretical   : 104,857,600

Method: Unbiased with 1/1 efficiency, SIMD=vectorclass (incorrect, baseline)
Gbps/s: 22.660 22.661 24.662 [Gb/s]
Number of ones: 536,867,655 536,867,655 536,867,655
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency
Gbps/s: 1.065 1.102 1.078 [Gb/s]
Number of ones: 104,868,930 104,868,930 104,868,930
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=vectorclass
Gbps/s: 4.972 4.971 4.970 [Gb/s]
Number of ones: 104,869,407 104,869,407 104,869,407
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=intrinsics
Gbps/s: 4.955 4.971 4.971 [Gb/s]
Number of ones: 104,869,407 104,869,407 104,869,407
Theoretical   : 104,857,600

Pour xorshift, SIMD augmente les performances d'un facteur 5 par rapport à la méthode scalaire. Il est 4 fois plus lent que le générateur non biaisé. Notez qu'il s'agit d'une implémentation scalaire de xorshift.

Le générateur polarisé le plus rapide atteint 4,9 Gb/s.


RNG: xorshift128plus_avx2

Method: Unbiased with 1/1 efficiency (incorrect, baseline)
Gbps/s: 18.754 21.494 21.878 [Gb/s]
Number of ones: 536,867,655 536,867,655 536,867,655
Theoretical   : 104,857,600

Method: Unbiased with 1/1 efficiency, SIMD=vectorclass (incorrect, baseline)
Gbps/s: 54.126 54.071 54.145 [Gb/s]
Number of ones: 536,874,540 536,880,718 536,891,316
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency
Gbps/s: 1.093 1.103 1.063 [Gb/s]
Number of ones: 104,868,930 104,868,930 104,868,930
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=vectorclass
Gbps/s: 19.567 19.578 19.555 [Gb/s]
Number of ones: 104,836,115 104,846,215 104,835,129
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=intrinsics
Gbps/s: 19.551 19.589 19.557 [Gb/s]
Number of ones: 104,831,396 104,837,429 104,851,100
Theoretical   : 104,857,600

Cette implémentation utilise AVX2 pour exécuter 4 générateurs xorshift non biaisés en parallèle.

Le générateur polarisé le plus rapide atteint 19,5 Gb/s.

Repères pour le décodage arithmétique

Des tests simples montrent que le code de décodage arithmétique est le goulot d'étranglement, pas le PRNG. Je ne fais donc que comparer le PRNG le plus cher.


RNG: Ranvec1(Mersenne Twister for Graphics Processors + Multiply with Carry)

Method: Arithmetic decoding (floating point)
Gbps/s: 0.068 0.068 0.069 [Gb/s]
Number of ones: 10,235,580 10,235,580 10,235,580
Theoretical   : 10,240,000

Method: Arithmetic decoding (fixed point)
Gbps/s: 0.263 0.263 0.263 [Gb/s]
Number of ones: 10,239,367 10,239,367 10,239,367
Theoretical   : 10,240,000

Method: Unbiased with 1/1 efficiency (incorrect, baseline)
Gbps/s: 12.687 12.686 12.684 [Gb/s]
Number of ones: 536,875,204 536,875,204 536,875,204
Theoretical   : 104,857,600

Method: Unbiased with 1/1 efficiency, SIMD=vectorclass (incorrect, baseline)
Gbps/s: 14.536 14.536 14.536 [Gb/s]
Number of ones: 536,875,204 536,875,204 536,875,204
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency
Gbps/s: 0.754 0.754 0.754 [Gb/s]
Number of ones: 104,867,269 104,867,269 104,867,269
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=vectorclass
Gbps/s: 2.094 2.095 2.094 [Gb/s]
Number of ones: 104,859,067 104,859,067 104,859,067
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=intrinsics
Gbps/s: 2.094 2.094 2.095 [Gb/s]
Number of ones: 104,859,067 104,859,067 104,859,067
Theoretical   : 104,857,600

La méthode simple à virgule fixe atteint 0,25 Gb/s, tandis que la méthode scalaire naïve est 3 fois plus rapide et la méthode naïve SIMD est 8 fois plus rapide. Il pourrait y avoir des moyens d'optimiser et/ou de paralléliser davantage la méthode de décodage arithmétique, mais en raison de sa complexité, j'ai décidé de m'arrêter ici et de choisir l'implémentation SIMD naïve.

Merci à tous pour l'aide.

57
o9000

Si vous êtes prêt à approximer p sur la base de 256 valeurs possibles, et que vous avez un PRNG qui peut générer des valeurs uniformes dans lesquelles les bits individuels sont indépendants les uns des autres, alors vous pouvez utiliser la comparaison vectorisée pour produire plusieurs bits biaisés à partir d'un seul nombre aléatoire.

Cela ne vaut que si (1) vous vous inquiétez de la qualité des nombres aléatoires et (2) vous avez probablement besoin d'un grand nombre de bits avec le même biais. La deuxième exigence semble être impliquée par la question initiale, qui critique une solution proposée, comme suit: "Une lacune de cette solution est qu’elle ne peut générer que 8 bits à la fois, même avec beaucoup de travail, alors qu’un biais = PRNG peut générer 64 à la fois avec seulement quelques instructions arithmétiques. "Ici, l'implication semble être utile pour générer un grand bloc de bits polarisés en un seul appel.

La qualité des nombres aléatoires est un sujet difficile. C'est difficile, voire impossible à mesurer, et donc différentes personnes proposeront différentes métriques qui mettent l'accent et/ou dévalorisent différents aspects du "hasard". Il est généralement possible d'échanger la vitesse de génération de nombres aléatoires pour une "qualité" inférieure; si cela vaut la peine dépend de votre application précise.

Les tests les plus simples possibles de la qualité des nombres aléatoires impliquent la distribution des valeurs individuelles et la durée du cycle du générateur. Les implémentations standard des fonctions Rand et Posix random de la bibliothèque C passeront généralement le test de distribution, mais les longueurs de cycle ne sont pas adéquates pour les applications de longue durée.

Ces générateurs sont généralement extrêmement rapides, cependant: l'implémentation glibc de random ne nécessite que quelques cycles, tandis que le générateur linéaire congruentiel linéaire (LCG) nécessite une multiplication et une addition. (Ou, dans le cas de l'implémentation de la glibc, trois des éléments ci-dessus pour générer 31 bits.) Si cela suffit pour vos exigences de qualité, il est inutile d'essayer d'optimiser, en particulier si la probabilité de biais change fréquemment.

Gardez à l'esprit que la durée du cycle doit être beaucoup plus longue que le nombre d'échantillons attendu; idéalement, il devrait être supérieur au carré de ce nombre, donc un générateur linéaire-congruentiel (LCG) avec une durée de cycle de 231 n'est pas approprié si vous prévoyez de générer des gigaoctets de données aléatoires. Même le générateur de rétroaction additive non linéaire trinomial Gnu, dont la durée de cycle est d'environ 235, ne doit pas être utilisé dans des applications qui nécessiteront des millions d'échantillons.

Un autre problème de qualité, beaucoup plus difficile à tester, concerne l'indépendance sur des échantillons consécutifs. Les courtes durées de cycle échouent complètement sur cette métrique, car une fois la répétition démarrée, les nombres aléatoires générés sont précisément corrélés avec les valeurs historiques. L'algorithme trinomial Gnu, bien que son cycle soit plus long, a une corrélation claire du fait que le i e nombre aléatoire généré, r i , est toujours l'une des deux valeurs r i - 3+ r i - 31 ou r i - 3+ r i - 31+1. Cela peut avoir des conséquences surprenantes ou du moins déroutantes , en particulier avec les expériences de Bernoulli.

Voici une implémentation utilisant l'utile d'Agner Fog bibliothèque de classes vectorielles , qui résume beaucoup de détails ennuyeux dans SSE intrinsèque, et est également utile avec un rapide générateur de nombres aléatoires vectorisés (trouvé dans special.Zip à l'intérieur de vectorclass.Zip archive), ce qui nous permet de générer 256 bits à partir de huit appels vers le PRNG 256 bits. Vous pouvez lire l'explication du Dr Fog sur les raisons pour lesquelles il trouve que même le twister de Mersenne a des problèmes de qualité, et sa solution proposée; Je ne suis pas qualifié pour commenter, vraiment, mais cela semble au moins donner les résultats attendus dans les expériences de Bernoulli que j'ai essayées avec.

#include "vectorclass/vectorclass.h"
#include "vectorclass/ranvec1.h"

class BiasedBits {
  public:
    // Default constructor, seeded with fixed values
    BiasedBits() : BiasedBits(1)  {}
    // Seed with a single seed; other possibilities exist.
    BiasedBits(int seed) : rng(3) { rng.init(seed); }

    // Generate 256 random bits, each with probability `p/256` of being 1.
    Vec8ui random256(unsigned p) {
      if (p >= 256) return Vec8ui{ 0xFFFFFFFF };
      Vec32c output{ 0 };
      Vec32c threshold{ 127 - p };
      for (int i = 0; i < 8; ++i) {
        output += output;
        output -= Vec32c(Vec32c(rng.uniform256()) > threshold);
      }
      return Vec8ui(output);
    }

  private:
    Ranvec1 rng;
};

Dans mon test, cela a produit et compté 268435456 bits en 260 ms, soit un bit par nanoseconde. La machine de test est un i5, donc elle n'a pas AVX2; YMMV.

Dans le cas d'utilisation réel, avec 201 valeurs possibles pour p, le calcul des valeurs de seuil à 8 bits sera extrêmement imprécis. Si cette imprécision n'est pas souhaitée, vous pouvez adapter ce qui précède pour utiliser des seuils de 16 bits, au prix de générer deux fois plus de nombres aléatoires.

Alternativement, vous pouvez effectuer manuellement une vectorisation basée sur des seuils de 10 bits, ce qui vous donnerait une très bonne approximation des incréments de 0,5%, en utilisant le hack standard de manipulation de bits pour effectuer la comparaison de seuil vectorisée en vérifiant l'emprunt tous les 10 bits de la soustraction du vecteur de valeurs et du seuil répété. Combiné avec, disons, std::mt19937_64, cela vous donnerait en moyenne six bits par nombre aléatoire de 64 bits.

25
rici

Une chose que vous pouvez faire consiste à échantillonner plusieurs fois à partir du générateur non biaisé sous-jacent, à obtenir plusieurs mots de 32 ou 64 bits, puis à effectuer une arithmétique booléenne au niveau du bit. Par exemple, pour 4 mots b1,b2,b3,b4, vous pouvez obtenir les distributions suivantes:

 expression | p (le bit est 1) 
 ----------------------- + ------------- 
 b1 & b2 & b3 & b4 | 6,25% 
 B1 & b2 & b3 | 12,50% 
 B1 & b2 & (b3 | b4) | 18,75% 
 B1 & b2 | 25,00% 
 B1 | (b2 et (b3 | b4)) | 31,25% 
 B1 & (b2 | b3) | 37,50% 
 B1 & (b2 | b3 | b4)) | 43,75% 
 B1 | 50,00% 

Des constructions similaires peuvent être faites pour des résolutions plus fines. Cela devient un peu fastidieux et nécessite toujours plus d'appels de générateur, mais au moins pas un par bit. Ceci est similaire à la réponse de a3f, mais est probablement plus facile à implémenter et, je suppose, plus rapide que la recherche de mots pour 0xF nybbles.

Notez que pour la résolution souhaitée de 0,5%, vous aurez besoin de 8 mots non biaisés pour un mot biaisé, ce qui vous donnera une résolution de (0,5 ^ 8) = 0,390625%.

28
mindriot

D'un point de vue théorique de l'information, un flux de bits biaisé (avec p != 0.5) Contient moins d'informations qu'un flux sans biais , donc en théorie, cela devrait prendre (en moyenne) moins que 1 bit de l'entrée non biaisée pour produire un seul bit du flux de sortie biaisé. Par exemple, la entropie d'une variable aléatoire de Bernoulli avec p = 0.1 Est de -0.1 * log2(0.1) - 0.9 * log2(0.9) bits, qui est d'environ 0.469 Bits. Cela suggère que dans le cas p = 0.1, Nous devrions être en mesure de produire un peu plus de deux bits du flux de sortie par bit d'entrée non biaisé.

Ci-dessous, je donne deux méthodes pour produire les bits biaisés. Tous deux atteignent une efficacité presque optimale, dans le sens où ils nécessitent le moins de bits sans biais d'entrée possible.

Méthode 1: codage (dé) arithmétique

Une méthode pratique consiste à décoder votre flux d'entrée non biaisé en utilisant (dé) codage arithmétique , comme déjà décrit dans le réponse d'Alexis . Pour ce cas simple, il n'est pas difficile de coder quelque chose. Voici un pseudocode non optimisé ( toux, Python ) qui fait cela:

import random

def random_bits():
    """
    Infinite generator generating a stream of random bits,
    with 0 and 1 having equal probability.
    """
    global bit_count  # keep track of how many bits were produced
    while True:
        bit_count += 1
        yield random.choice([0, 1])

def bernoulli(p):
    """
    Infinite generator generating 1-bits with probability p
    and 0-bits with probability 1 - p.
    """
    bits = random_bits()

    low, high = 0.0, 1.0
    while True:
        if high <= p:
            # Generate 1, rescale to map [0, p) to [0, 1)
            yield 1
            low, high = low / p, high / p
        Elif low >= p:
            # Generate 0, rescale to map [p, 1) to [0, 1)
            yield 0
            low, high = (low - p) / (1 - p), (high - p) / (1 - p)
        else:
            # Use the next random bit to halve the current interval.
            mid = 0.5 * (low + high)
            if next(bits):
                low = mid
            else:
                high = mid

Voici un exemple d'utilisation:

import itertools
bit_count = 0

# Generate a million deviates.
results = list(itertools.islice(bernoulli(0.1), 10**6))

print("First 50:", ''.join(map(str, results[:50])))
print("Biased bits generated:", len(results))
print("Unbiased bits used:", bit_count)
print("mean:", sum(results) / len(results))

Ce qui précède donne l'exemple de sortie suivant:

First 50: 00000000000001000000000110010000001000000100010000
Biased bits generated: 1000000
Unbiased bits used: 469036
mean: 0.100012

Comme promis, nous avons généré 1 million de bits de notre flux biaisé en sortie en utilisant moins de cinq cent mille à partir du flux non biaisé source.

À des fins d'optimisation, lors de la traduction en C/C++, il peut être judicieux de coder cela en utilisant une arithmétique à virgule fixe basée sur des nombres entiers plutôt que virgule flottante.

Méthode 2: algorithme basé sur des nombres entiers

Plutôt que d'essayer de convertir la méthode de décodage arithmétique pour utiliser directement des entiers, voici une approche plus simple. Ce n'est plus un décodage arithmétique, mais ce n'est pas totalement indépendant, et il atteint presque le même rapport bit de sortie/bit sans biais d'entrée que la version à virgule flottante ci-dessus. Il est organisé de sorte que toutes les quantités tiennent dans un entier 32 bits non signé, donc devrait être facile à traduire en C/C++. Le code est spécialisé dans le cas où p est un multiple exact de 1/200, Mais cette approche fonctionnerait pour tout p qui peut être exprimé comme un nombre rationnel avec raisonnablement petit dénominateur.

def bernoulli_int(p):
    """
    Infinite generator generating 1-bits with probability p
    and 0-bits with probability 1 - p.

    p should be an integer multiple of 1/200.
    """
    bits = random_bits()
    # Assuming that p has a resolution of 0.05, find p / 0.05.
    p_int = int(round(200*p))

    value, high = 0, 1
    while True:
        if high < 2**31:
            high = 2 * high
            value = 2 * value + next(bits)
        else:
            # Throw out everything beyond the last multiple of 200, to
            # avoid introducing a bias.
            discard = high - high % 200
            split = high // 200 * p_int
            if value >= discard:  # rarer than 1 time in 10 million
                value -= discard
                high -= discard
            Elif value >= split:
                yield 0
                value -= split
                high = discard - split
            else:
                yield 1
                high = split

L'observation clé est que chaque fois que nous atteignons le début de la boucle while, value est uniformément répartie entre tous les entiers dans [0, high), Et est indépendante de tous les bits précédemment sortis . Si vous vous souciez de la vitesse plus que de l'exactitude parfaite, vous pouvez vous débarrasser de discard et de la branche value >= discard: C'est juste là pour nous assurer que nous sortons 0 Et 1 avec exactement les bonnes probabilités. Laissez cette complication de côté et vous obtiendrez presque les bonnes probabilités à la place. De plus, si vous définissez la résolution de p égale à 1/256 Plutôt qu'à 1/200, Les opérations de division et de module potentiellement chronophages peuvent être remplacées par des opérations binaires.

Avec le même code de test qu'auparavant, mais en utilisant bernoulli_int À la place de bernoulli, j'obtiens les résultats suivants pour p=0.1:

First 50: 00000010000000000100000000000000000000000110000100
Biased bits generated: 1000000
Unbiased bits used: 467997
mean: 0.099675
17
Mark Dickinson

Vous obtiendrez un comportement théoriquement optimal, c'est-à-dire que vous utiliserez une utilisation vraiment minimale du générateur de nombres aléatoires et pourrez modéliser toute probabilité p exactement, si vous vous en approchez en utilisant codage arithmétique .

Le codage arithmétique est une forme de compression de données qui représente le message comme un sous-intervalle d'une plage de nombres. Il fournit un codage théoriquement optimal et peut utiliser un nombre fractionnaire de bits pour chaque symbole d'entrée.

L'idée est la suivante: imaginez que vous avez une séquence de bits aléatoires, qui sont 1 avec une probabilité p. Pour plus de commodité, j'utiliserai plutôt q pour la probabilité que le bit soit nul. ( q = 1-p). Le codage arithmétique attribue à chaque partie binaire de la plage numérique. Pour le premier bit, affectez l'intervalle [0, q) si l'entrée est 0 et l'intervalle [q, 1) si l'entrée est 1. Les bits suivants affectent des sous-intervalles proportionnels de la plage actuelle. Par exemple, supposons que q = 1/3 L'entrée 1 0 0 sera encodée comme ceci:

Initially       [0, 1),             range = 1
After 1         [0.333, 1),         range = 0.6666        
After 0         [0.333, 0.5555),    range = 0.2222   
After 0         [0.333, 0.407407),  range = 0.074074

Le premier chiffre, 1, sélectionne les deux tiers supérieurs (1-q) de la plage; le deuxième chiffre, 0, sélectionne le tiers inférieur de ça, et ainsi de suite. Après la première et la deuxième étape, l'intervalle chevauche le point médian; mais après la troisième étape, il est entièrement en dessous du point médian, donc le premier chiffre compressé peut être sorti: 0. Le processus se poursuit et un symbole spécial EOF est ajouté comme terminateur.

Qu'est-ce que cela a à voir avec votre problème? La sortie compressée aura des zéros aléatoires et des uns avec une probabilité égale. Donc, pour obtenir des bits avec une probabilité p, faites comme si la sortie de votre RNG est le résultat d'un codage arithmétique comme ci-dessus, et appliquez le processus du décodeur à Autrement dit, lisez les bits comme s'ils subdivisaient l'intervalle de ligne en morceaux de plus en plus petits. Par exemple, après avoir lu 01 du RNG, nous serons dans la plage [0,25, 0,5). Continuez à lire les bits jusqu'à ce que suffisamment de sortie soit "décodée". Puisque vous imitez la décompression, vous obtiendrez plus de bits aléatoires que vous n'en placez. Parce que le codage arithmétique est théoriquement optimal, il n'y a aucun moyen possible de transformez la sortie RNG en bits plus biaisés sans sacrifier l'aléatoire: vous obtenez le vrai maximum.

Le hic, c'est que vous ne pouvez pas le faire en quelques lignes de code, et je ne connais pas de bibliothèque sur laquelle je puisse vous indiquer (bien qu'il doit y en avoir certaines que vous pouvez utiliser). Pourtant, c'est assez simple. Le article ci-dessus fournit du code pour un encodeur et un décodeur à usage général, en C. C'est assez simple, et il prend en charge plusieurs symboles d'entrée avec des probabilités arbitraires; dans votre cas, une implémentation beaucoup plus simple est possible (comme la réponse de Mark Dickinson le montre maintenant), car le modèle de probabilité est trivial. Pour une utilisation étendue, un peu plus de travail serait nécessaire pour produire une implémentation robuste qui ne fasse pas beaucoup de calcul en virgule flottante pour chaque bit.

Wikipedia a également une discussion intéressante sur le codage arithmétique considéré comme un changement de radix, qui est une autre façon de voir votre tâche.

9
alexis

Disons que la probabilité d'apparition d'un 1 est de 6,25% (1/16). Il existe 16 modèles de bits possibles pour un nombre de 4 bits: 0000,0001, ..., 1110,1111.

Maintenant, il suffit de générer un nombre aléatoire comme vous le faisiez auparavant et de remplacer chaque 1111 à une limite de quartet avec un 1 et transformez tout le reste en 0.

Ajustez en conséquence pour les autres probabilités.

9
a3f

Euh, les générateurs de nombres pseudo-aléatoires sont généralement assez rapides. Je ne sais pas de quelle langue il s'agit (Python, peut-être), mais "result.append" (qui contient presque certainement l'allocation de mémoire) est probablement plus lent que "random_uniform" (qui fait juste un peu de calcul).

Si vous souhaitez optimiser les performances de ce code:

  1. Vérifiez qu'il s'agit d'un problème. Les optimisations sont un peu de travail et rendent le code plus difficile à maintenir. Ne les faites pas sauf si nécessaire.
  2. Profilez-le. Exécutez des tests pour déterminer quelles parties du code sont réellement les plus lentes. Ce sont les pièces dont vous avez besoin pour accélérer.
  3. Apportez vos modifications et vérifiez qu'elles sont réellement plus rapides. Les compilateurs sont assez intelligents; souvent, un code clair se compilera en un meilleur code que quelque chose de complexe qui pourrait apparaître plus rapidement.

Si vous travaillez dans un langage compilé (même compilé JIT), vous prenez un coup de performance pour chaque transfert de contrôle (if, while, appel de fonction, etc.). Éliminez ce que vous pouvez. L'allocation de mémoire est également (généralement) assez coûteuse.

Si vous travaillez dans une langue interprétée, tous les paris sont désactivés. Le code le plus simple est probablement le meilleur. Les frais généraux de l'interpréteur éclipseront tout ce que vous faites, donc réduisez son travail autant que possible.

Je ne peux que deviner où sont vos problèmes de performances:

  1. Allocation de mémoire. Pré-allouez le tableau à sa taille maximale et remplissez les entrées plus tard. Cela garantit que la mémoire n'aura pas besoin d'être réallouée pendant que vous ajoutez les entrées.
  2. Branches. Vous pourrez peut-être éviter le "si" en lançant le résultat ou quelque chose de similaire. Cela dépendra beaucoup du compilateur. Vérifiez l'assemblage (ou le profil) pour vérifier qu'il fait ce que vous voulez.
  3. Types numériques. Découvrez le type que votre générateur de nombres aléatoires utilise nativement et effectuez votre arithmétique dans ce type. Par exemple, si le générateur renvoie naturellement des entiers non signés 32 bits, mettez d'abord "p" à l'échelle, puis utilisez-le pour la comparaison.

Soit dit en passant, si vous voulez vraiment utiliser le moins de bits possibles, utilisez le "codage arithmétique" pour décoder votre flux aléatoire. Ce ne sera pas rapide.

8
Dalias

Une façon qui donnerait un résultat précis est de générer d'abord de manière aléatoire pour un bloc de k bits le nombre de 1 bits suivant la distribution binomiale, puis de générer un mot de k bits avec exactement autant de bits en utilisant l'une des méthodes ici . Par exemple, la méthode de mic006 ne nécessite que des nombres aléatoires log k k bits, et la mienne n'en a besoin que d'un.

7
Falk Hüffner

En supposant que vous avez accès à un générateur de bits aléatoires, vous pouvez générer une valeur à comparer avec p bit par bit, et abandonner dès que vous pouvez prouver que la valeur générée est inférieure ou supérieure ou -égal à p.

Procédez comme suit pour créer un élément dans un flux avec une probabilité donnée p:

  1. Commencer avec 0. en binaire
  2. Ajoutez un bit aléatoire; en supposant qu'un 1 a été dessiné, vous obtiendrez 0.1
  3. Si le résultat (en notation binaire) est plus petit que p, affichez 1
  4. Si le résultat est manifestement supérieur ou égal à p, affichez un 0
  5. Sinon (si aucun des deux ne peut être exclu), passez à l'étape 2.

Supposons que p en notation binaire soit 0.1001101...; si ce processus génère l'un des 0.0, 0.1000, 0.10010, ..., la valeur ne peut plus devenir supérieure ou égale à p; si l'un des 0.11, 0.101, 0.100111, ... est généré, la valeur ne peut pas devenir inférieure à p.

Pour moi, il semble que cette méthode utilise environ deux bits aléatoires dans l'attente. Le codage arithmétique (comme le montre la réponse de Mark Dickinson) consomme au plus un bit aléatoire par bit biaisé (en moyenne) pour _ p fixe; le coût de la modification de p n'est pas clair.

6
krlmlr

Si p est proche de 0, vous pouvez calculer la probabilité que le nième bit soit le premier bit égal à 1; puis vous calculez un nombre aléatoire entre 0 et 1 et choisissez n en conséquence. Par exemple, si p = 0,005 (0,5%) et que le nombre aléatoire est 0,638128, vous pouvez calculer (je suppose ici) n = 321, vous remplissez donc avec 321 0 bits et un ensemble de bits.

Si p est proche de 1, utilisez 1-p au lieu de p et définissez 1 bits plus un 0 bit.

Si p n'est pas proche de 1 ou 0, faites un tableau des 256 séquences de 8 bits, calculez leurs probabilités cumulatives, puis obtenez un nombre aléatoire, effectuez une recherche binaire dans le tableau des probabilités cumulatives, et vous pouvez définir 8 bits .

6
gnasher729

Ce qu'il fait

Cette implémentation fait appel unique au module de noyau de périphérique aléatoire via l'interface du fichier de caractères spéciaux "/ dev/urandom" pour obtenir le nombre de données aléatoires nécessaires pour représenter tout valeurs dans une résolution donnée. La résolution maximale possible est de 1/256 ^ 2, de sorte que 0,005 peut être représenté par:

328/256 ^ 2,

c'est à dire:

résolution: 256 * 256

x: 328

avec l'erreur 0,000004883.

Comment ça fait ça

L'implémentation calcule le nombre de bits bits_per_byte qui est le nombre de bits uniformément répartis nécessaires pour gérer une résolution donnée, c'est-à-dire représenter tous les @resolution valeurs. Il effectue ensuite un seul appel vers le dispositif de randomisation ("/ dev/urandom" si URANDOM_DEVICE est défini, sinon il utilisera du bruit supplémentaire des pilotes de périphériques via l'appel à "/ dev/random" qui peut bloquer s'il n'y a pas assez d'entropie en bits) pour obtenir le nombre requis d'octets uniformément distribués et remplit le tableau rnd_bytes d'octets aléatoires. Enfin, il lit le nombre de bits nécessaires pour chaque échantillon Bernoulli de chaque octet_per_byte octets du tableau rnd_bytes et compare la valeur entière de ces bits à la probabilité de succès dans le résultat Bernoulli unique donné par x/resolution. Si la valeur atteint, c'est-à-dire qu'elle tombe dans le segment de x/resolution longueur que nous choisissons arbitrairement comme segment [0, x/résolution) puis nous notons le succès et insérons 1 dans le tableau résultant.


Lire à partir d'un appareil aléatoire:

/* if defined use /dev/urandom (will not block),
 * if not defined use /dev/random (may block)*/
#define URANDOM_DEVICE 1

/*
 * @brief   Read @outlen bytes from random device
 *          to array @out.
 */
int
get_random_samples(char *out, size_t outlen)
{
    ssize_t res;
#ifdef URANDOM_DEVICE
    int fd = open("/dev/urandom", O_RDONLY);
    if (fd == -1) return -1;
    res = read(fd, out, outlen);
    if (res < 0) {
        close(fd);
        return -2;
    }
#else
    size_t read_n;
    int fd = open("/dev/random", O_RDONLY);
    if (fd == -1) return -1;
    read_n = 0;
    while (read_n < outlen) {
       res = read(fd, out + read_n, outlen - read_n);
       if (res < 0) {
           close(fd);
           return -3;
       }
       read_n += res;
    }
#endif /* URANDOM_DEVICE */
    close(fd);
    return 0;
}

Remplissez le vecteur d'échantillons de Bernoulli:

/*
 * @brief   Draw vector of Bernoulli samples.
 * @details @x and @resolution determines probability
 *          of success in Bernoulli distribution
 *          and accuracy of results: p = x/resolution.
 * @param   resolution: number of segments per sample of output array 
 *          as power of 2: max resolution supported is 2^24=16777216
 * @param   x: determines used probability, x = [0, resolution - 1]
 * @param   n: number of samples in result vector
 */
int
get_bernoulli_samples(char *out, uint32_t n, uint32_t resolution, uint32_t x)
{
    int res;
    size_t i, j;
    uint32_t bytes_per_byte, Word;
    unsigned char *rnd_bytes;
    uint32_t uniform_byte;
    uint8_t bits_per_byte;

    if (out == NULL || n == 0 || resolution == 0 || x > (resolution - 1))
        return -1;

    bits_per_byte = log_int(resolution);
    bytes_per_byte = bits_per_byte / BITS_PER_BYTE + 
                        (bits_per_byte % BITS_PER_BYTE ? 1 : 0);
    rnd_bytes = malloc(n * bytes_per_byte);
    if (rnd_bytes == NULL)
        return -2;
    res = get_random_samples(rnd_bytes, n * bytes_per_byte);
    if (res < 0)
    {
        free(rnd_bytes);
        return -3;
    }

    i = 0;
    while (i < n)
    {
        /* get Bernoulli sample */
        /* read byte */
        j = 0;
        Word = 0;
        while (j < bytes_per_byte)
        {
            Word |= (rnd_bytes[i * bytes_per_byte + j] << (BITS_PER_BYTE * j));
            ++j;
        }
        uniform_byte = Word & ((1u << bits_per_byte) - 1);
        /* decision */
        if (uniform_byte < x)
            out[i] = 1;
        else
            out[i] = 0;
        ++i;
    }

    free(rnd_bytes);    
    return 0;
}

Usage:

int
main(void)
{
    int res;
    char c[256];

    res = get_bernoulli_samples(c, sizeof(c), 256*256, 328); /* 328/(256^2) = 0.0050 */
    if (res < 0) return -1;

    return 0;
}

Code complet , résultats .

5
4pie0