BlessRNG o cuntrollà u RNG per equità

BlessRNG o cuntrollà u RNG per equità

In u sviluppu di u ghjocu, spessu avete bisognu di ligà qualcosa cù l'aleatoriu: Unità hà u so propiu Random per questu, è in parallelu cù questu hè System.Random. Una volta, nantu à unu di i prughjetti, aghju avutu l'impressione chì i dui puderanu travaglià in modu diversu (ancu si deve avè una distribuzione uniforme).

Allora ùn anu micca andatu in i dettagli - era abbastanza chì a transizione à System.Random hà currettu tutti i prublemi. Avà avemu decisu di guardà in più in detail è di fà una ricerca pocu: quantu sò "pregiudiziati" o RNG prevedibili, è quale sceglite. Inoltre, aghju intesu più di una volta opinioni cunflittu nantu à a so "onestà" - pruvemu à capisce cumu i risultati veri paragunate cù quelli dichjarati.

U prugramma educativu brevi o RNG hè in realtà RNG

Sè avete digià familiarizatu cù generatori di numeri aleatorii, pudete saltà immediatamente à a sezione "Test".

I numeri aleatorii (RN) sò una sequenza di numeri generati cù qualchì prucessu aleatoriu (caòticu), una fonte d'entropia. Hè, questu hè una sequenza chì l'elementi ùn sò micca interconnessi da alcuna lege matematica - ùn anu micca una relazione di causa è effettu.

Ciò chì crea u numeru aleatoriu hè chjamatu generatore di numeri aleatorii (RNG). Sembra chì tuttu hè elementariu, ma se passemu da a teoria à a pratica, allora in fattu ùn hè micca cusì simplice per implementà un algoritmu di software per generà una sequenza cusì.

U mutivu si trova in l'absenza di quellu stessu caosu in l'elettronica di u cunsumu mudernu. Senza ellu, i numeri aleatorii cessanu di esse aleatorii, è u so generatore diventa una funzione ordinaria di argumenti ovviamente definiti. Per una quantità di specialità in u campu di l'informatica, questu hè un prublema seriu (per esempiu, criptografia), ma per altri ci hè una suluzione cumpletamente accettabile.

Hè necessariu di scrive un algoritmu chì vultà, ancu s'ellu ùn hè micca veramente numeri aleatorii, ma u più vicinu à elli - i chjamati numeri pseudo-aleatoriu (PRN). L'algoritmu in questu casu hè chjamatu un generatore di numeri pseudorandom (PRNG).

Ci hè parechje opzioni per creà un PRNG, ma i seguenti seranu pertinenti per tutti:

  1. A necessità di l'inizializazione preliminare.

    U PRNG ùn hà micca fonte d'entropia, cusì deve esse datu un statu iniziale prima di l'usu. Hè specificatu cum'è un numeru (o vettore) è hè chjamatu sumente (semente aleatoriu). Spessu, u cuntatore di clock di u processatore o l'equivalente numericu di u tempu di u sistema hè utilizatu cum'è una sumente.

  2. Riproducibilità di a sequenza.

    U PRNG hè cumpletamente deterministicu, cusì a sumente specificata durante l'inizializazione determina in modu unicu l'intera sequenza futura di numeri. Questu significa chì un PRNG separatu inizializatu cù a listessa sementa (in tempi diversi, in diversi prugrammi, in diversi dispositi) generarà a listessa sequenza.

Avete ancu bisognu di sapè a distribuzione di probabilità chì caratterizeghja u PRNG - chì numeri generarà è cù quale probabilità. A maiò spessu, questu hè una distribuzione normale o una distribuzione uniforme.
BlessRNG o cuntrollà u RNG per equità
Distribuzione normale (a manca) è a distribuzione uniforme (a diritta)

Diciamu chì avemu un fustu ghjustu cù 24 lati. Sè vo lanciate, a probabilità di ottene un unu serà uguali à 1/24 (u stessu cum'è a probabilità di ottene qualsiasi altru numeru). Se fate parechji tiri è arregistrate i risultati, vi vede chì tutti i bordi cascanu cù circa a listessa freccia. Essenzialmente, stu die pò esse cunsideratu un RNG cù una distribuzione uniforme.

E se tirassi 10 di sti dadi à una volta è cuntassi i punti totali? Serà mantenuta l'uniformità per questu? Innò. A maiò spessu, a quantità serà vicinu à i punti 125, vale à dì à qualchì valore mediu. È in u risultatu, ancu prima di fà un tirò, pudete apprussimatamente stima u risultatu futuru.

U mutivu hè chì ci hè u più grande numaru di cumminazzioni per ottene u puntu mediu. U più luntanu da ellu, menu cumminazzioni - è, per quessa, u più bassu a probabilità di perdita. Se sta dati hè visualizatu, s'assumiglia vagamente à a forma di una campana. Per quessa, cù qualchì stretchimentu, un sistema di 10 dadi pò esse chjamatu RNG cù una distribuzione normale.

Un altru esempiu, solu sta volta in un aviò - sparà à un mira. U tiratore serà un RNG chì genera un paru di numeri (x, y) chì si vede nantu à u graficu.
BlessRNG o cuntrollà u RNG per equità
Accordu chì l'opzione à a manca hè più vicinu à a vita reale - questu hè un RNG cù una distribuzione normale. Ma s'è avete bisognu di sparghje stelle in un celu scuru, allora l'opzione ghjusta, ottenuta cù RNG cù una distribuzione uniforme, hè megliu adattatu. In generale, sceglite un generatore secondu u compitu in manu.

Avà parlemu di l'entropia di a sequenza PNG. Per esempiu, ci hè una sequenza chì principia cusì:

89, 93, 33, 32, 82, 21, 4, 42, 11, 8, 60, 95, 53, 30, 42, 19, 34, 35, 62, 23, 44, 38, 74, 36, 52, 18, 58, 79, 65, 45, 99, 90, 82, 20, 41, 13, 88, 76, 82, 24, 5, 54, 72, 19, 80, 2, 74, 36, 71, 9, ...

Quantu sò casuali questi numeri à u primu sguardu? Accuminciamu per verificà a distribuzione.
BlessRNG o cuntrollà u RNG per equità
Sembra vicinu à l'uniforme, ma se leghjite una sequenza di dui numeri è interpretate cum'è coordenate in un pianu, avete questu:
BlessRNG o cuntrollà u RNG per equità
I mudelli diventanu chjaramente visibili. E postu chì i dati in a sequenza sò urdinati in un certu modu (vale à dì, hà una entropia bassa), questu pò dà origine à quellu "bias". À u minimu, un tali PRNG ùn hè micca assai adattatu per generà coordenate in un pianu.

Un'altra sequenza:

42, 72, 17, 0, 30, 0, 15, 9, 47, 19, 35, 86, 40, 54, 97, 42, 69, 19, 20, 88, 4, 3, 67, 27, 42, 56, 17, 14, 20, 40, 80, 97, 1, 31, 69, 13, 88, 89, 76, 9, 4, 85, 17, 88, 70, 10, 42, 98, 96, 53, ...

Tuttu pare esse bè quì ancu in l'aviò:
BlessRNG o cuntrollà u RNG per equità
Fighjemu in u voluminu (leghje trè numeri à un tempu):
BlessRNG o cuntrollà u RNG per equità
È dinò i mudelli. Ùn hè più pussibule di custruisce una visualizazione in quattru dimensioni. Ma i mudelli ponu esisti nantu à questa dimensione è in più grande.

In a criptografia, induve i requisiti più stretti sò imposti à i PRNG, una tale situazione hè categuricamente inacceptable. Per quessa, algoritmi spiciali sò stati sviluppati per valutà a so qualità, chì ùn avemu micca toccu avà. U tema hè largu è meriteghja un articulu separatu.

Prucessioni

Se ùn sapemu micca qualcosa di sicuru, allora cumu travaglià cun ellu? Vale a pena attraversà a strada s'ellu ùn sapete chì semaforu permette ? E cunsequenze pò esse diverse.

U stessu passa per a notoria casualità in Unità. Hè bonu se a documentazione revela i dettagli necessarii, ma a storia citata à u principiu di l'articulu hè accadutu precisamente per via di a mancanza di e specifiche desiderate.

È s'ellu ùn sapete micca cumu funziona u strumentu, ùn puderà micca aduprà bè. In generale, hè ghjuntu u tempu di cuntrollà è di fà un esperimentu per esse infine assicuratevi almenu di a distribuzione.

A suluzione era simplice è efficace - raccoglie statistiche, uttene dati oggettivi è fighjate i risultati.

Sugettu di studiu

Ci hè parechje manere di generà numeri aleatorii in Unity - avemu pruvatu cinque.

  1. System.Random.Next (). Genera numeri interi in una determinata gamma di valori.
  2. System.Random.NextDouble (). Genera numeri di doppia precisione in u intervallu da [0; 1).
  3. UnityEngine.Random.Range(). Genera numeri unichi di precisione (float) in una determinata gamma di valori.
  4. UnityEngine.Random.value. Genera numeri di precisione unichi (floats) in a gamma da [0; 1).
  5. Unity.Mathematics.Random.NextFloat(). Parte di a nova biblioteca Unity.Mathematics. Genera numeri unichi di precisione (float) in una determinata gamma di valori.

Quasi in ogni locu in a ducumentazione una distribuzione uniforme hè stata specificata, cù l'eccezzioni di UnityEngine.Random.value (induve a distribuzione ùn hè micca specificatu, ma per analogia cù l'uniforme UnityEngine.Random.Range() era ancu previstu) è Unity.Mathematics.Random. .NextFloat() (induve in A basa hè l'algoritmu di xorshift, chì significa chì di novu avete bisognu à aspittà per una distribuzione uniforme).

Per difettu, i risultati previsti sò stati presu cum'è quelli chì sò specificati in a documentazione.

Metodulugia

Avemu scrittu una piccula applicazione chì hà generatu sequenze di numeri aleatorii utilizendu ognuna di i metudi presentati è hà salvatu i risultati per un ulteriore prucessu.

A durata di ogni sequenza hè 100 numeri.
A gamma di numeri aleatorii hè [0, 100).

I dati sò stati raccolti da parechje piattaforme di destinazione:

  • Windows
    - Unity v2018.3.14f1, Modu Editore, Mono, .NET Standard 2.0
  • macOS
    - Unity v2018.3.14f1, Modu Editore, Mono, .NET Standard 2.0
    - Unity v5.6.4p4, Modu Editore, Mono, .NET Standard 2.0
  • Android
    - Unity v2018.3.14f1, build per device, Mono, .NET Standard 2.0
  • iOS
    - Unity v2018.3.14f1, custruisce per dispusitivu, il2cpp, .NET Standard 2.0

Реализация

Avemu parechje modi diffirenti per generà numeri aleatorii. Per ognunu di elli, scriveremu una classa di wrapper separata, chì deve furnisce:

  1. Possibilità di stabilisce a gamma di valori [min/max). Serà stabilitu via u custruttore.
  2. Metudu di ritornu MF. Sceglie u float cum'è u tipu, postu chì hè più generale.
  3. U nome di u metudu di generazione per marcà i risultati. Per comodità, vulteremu cum'è valore u nome cumpletu di a classa + u nome di u metudu utilizatu per generà l'MF.

Prima, dichjaremu una astrazione chì serà rapprisintata da l'interfaccia IRandomGenerator:

namespace RandomDistribution
{
    public interface IRandomGenerator
    {
        string Name { get; }

        float Generate();
    }
}

Implementazione di System.Random.Next ()

Stu metudu permette di stabilisce un intervallu di valori, ma torna interi, ma i floats sò necessarii. Pudete simpricimenti interpretà integer cum'è un float, o pudete espansione a gamma di valori da parechji ordini di grandezza, cumpensendu cù ogni generazione di u midrange. U risultatu serà qualcosa cum'è un puntu fissu cù un determinatu ordine di precisione. Adupremu sta opzione postu chì hè più vicinu à u valore reale di float.

using System;

namespace RandomDistribution
{
    public class SystemIntegerRandomGenerator : IRandomGenerator
    {
        private const int DefaultFactor = 100000;
        
        private readonly Random _generator = new Random();
        private readonly int _min;
        private readonly int _max;
        private readonly int _factor;


        public string Name => "System.Random.Next()";


        public SystemIntegerRandomGenerator(float min, float max, int factor = DefaultFactor)
        {
            _min = (int)min * factor;
            _max = (int)max * factor;
            _factor = factor;
        }


        public float Generate() => (float)_generator.Next(_min, _max) / _factor;
    }
}

Implementazione di System.Random.NextDouble ()

Quì a gamma fissa di valori [0; 1). Per prughjettà nantu à quellu specificatu in u custruttore, usemu aritmetica simplice: X * (max − min) + min.

using System;

namespace RandomDistribution
{
    public class SystemDoubleRandomGenerator : IRandomGenerator
    {
        private readonly Random _generator = new Random();
        private readonly double _factor;
        private readonly float _min;


        public string Name => "System.Random.NextDouble()";


        public SystemDoubleRandomGenerator(float min, float max)
        {
            _factor = max - min;
            _min = min;
        }


        public float Generate() => (float)(_generator.NextDouble() * _factor) + _min;
    }
}

Implementazione di UnityEngine.Random.Range()

Stu metudu di a classa statica UnityEngine.Random permette di stabilisce una gamma di valori è torna un tipu di float. Ùn avete micca fà alcuna trasfurmazioni supplementari.

using UnityEngine;

namespace RandomDistribution
{
    public class UnityRandomRangeGenerator : IRandomGenerator
    {
        private readonly float _min;
        private readonly float _max;


        public string Name => "UnityEngine.Random.Range()";


        public UnityRandomRangeGenerator(float min, float max)
        {
            _min = min;
            _max = max;
        }


        public float Generate() => Random.Range(_min, _max);
    }
}

Implementazione di UnityEngine.Random.value

A pruprietà di u valore di a classa statica UnityEngine.Random torna un tipu float da un intervallu fissu di valori [0; 1). Prughjettamu nantu à un intervallu determinatu in a listessa manera chì quandu implementa System.Random.NextDouble ().

using UnityEngine;

namespace RandomDistribution
{
    public class UnityRandomValueGenerator : IRandomGenerator
    {
        private readonly float _factor;
        private readonly float _min;


        public string Name => "UnityEngine.Random.value";


        public UnityRandomValueGenerator(float min, float max)
        {
            _factor = max - min;
            _min = min;
        }


        public float Generate() => (float)(Random.value * _factor) + _min;
    }
}

Implementazione di Unity.Mathematics.Random.NextFloat ()

U metudu NextFloat () di a classa Unity.Mathematics.Random torna una virgola flottante di tipu float è permette di specificà una gamma di valori. L'unica sfumatura hè chì ogni istanza di Unity.Mathematics.Random duverà esse inizializatu cù qualchì sumente - questu modu eviteremu di generà sequenze ripetute.

using Unity.Mathematics;

namespace RandomDistribution
{
    public class UnityMathematicsRandomValueGenerator : IRandomGenerator
    {
        private Random _generator;
        private readonly float _min;
        private readonly float _max;


        public string Name => "Unity.Mathematics.Random.NextFloat()";


        public UnityMathematicsRandomValueGenerator(float min, float max)
        {
            _min = min;
            _max = max;
            _generator = new Random();
            _generator.InitState(unchecked((uint)System.DateTime.Now.Ticks));
        }


        public float Generate() => _generator.NextFloat(_min, _max);
    }
}

Implementazione di MainController

Diversi implementazioni di IRandomGenerator sò pronti. Dopu, avete bisognu di generà sequenze è salvà u dataset resultanti per u processu. Per fà questu, creeremu una scena è un picculu script MainController in Unity, chì farà tuttu u travagliu necessariu è à u stessu tempu serà rispunsevule per l'interazzione cù l'UI.

Fighjemu a dimensione di u dataset è a gamma di valori MF, è ancu ottene un metudu chì torna una serie di generatori cunfigurati è pronti à travaglià.

namespace RandomDistribution
{
    public class MainController : MonoBehaviour
    {
        private const int DefaultDatasetSize = 100000;

        public float MinValue = 0f;
        public float MaxValue = 100f;

        ...

        private IRandomGenerator[] CreateRandomGenerators()
        {
            return new IRandomGenerator[]
            {
                new SystemIntegerRandomGenerator(MinValue, MaxValue),
                new SystemDoubleRandomGenerator(MinValue, MaxValue),
                new UnityRandomRangeGenerator(MinValue, MaxValue),
                new UnityRandomValueGenerator(MinValue, MaxValue),
                new UnityMathematicsRandomValueGenerator(MinValue, MaxValue)
            };
        }

        ...
    }
}

Avà criemu un dataset. In questu casu, a generazione di dati serà cumminata cù a registrazione di i risultati in un flussu di testu (in formatu csv). Per almacenà i valori di ogni IRandomGenerator, a so propria colonna separata hè attribuita, è a prima linea cuntene u Nome di u generatore.

namespace RandomDistribution
{
    public class MainController : MonoBehaviour
    {
        ...
		
        private void GenerateCsvDataSet(TextWriter writer, int dataSetSize, params IRandomGenerator[] generators)
        {
            const char separator = ',';
            int lastIdx = generators.Length - 1;

            // write header
            for (int j = 0; j <= lastIdx; j++)
            {
                writer.Write(generators[j].Name);
                if (j != lastIdx)
                    writer.Write(separator);
            }
            writer.WriteLine();

            // write data
            for (int i = 0; i <= dataSetSize; i++)
            {
                for (int j = 0; j <= lastIdx; j++)
                {
                    writer.Write(generators[j].Generate());
                    if (j != lastIdx)
                        writer.Write(separator);
                }

                if (i != dataSetSize)
                    writer.WriteLine();
            }
        }

        ...
    }
}

Tuttu ciò chì resta hè di chjamà u metudu GenerateCsvDataSet è salvà u risultatu in un schedariu, o trasfiriri immediatamente i dati nantu à a reta da u dispositivu finale à u servitore chì riceve.

namespace RandomDistribution
{
    public class MainController : MonoBehaviour
    {
        ...
		
        public void GenerateCsvDataSet(string path, int dataSetSize, params IRandomGenerator[] generators)
        {
            using (var writer = File.CreateText(path))
            {
                GenerateCsvDataSet(writer, dataSetSize, generators);
            }
        }


        public string GenerateCsvDataSet(int dataSetSize, params IRandomGenerator[] generators)
        {
            using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
            {
                GenerateCsvDataSet(writer, dataSetSize, generators);
                return writer.ToString();
            }
        }

        ...
    }
}

I fonti di u prugettu sò in GitLab.

Risultati

Nisun miraculu hè accadutu. Ciò chì aspittàvanu hè ciò chì anu ottenutu - in tutti i casi, una distribuzione uniforme senza un indizio di conspirazioni. Ùn vecu micca u puntu di mette grafici separati per e plataforme - tutti mostranu apprussimatamente i stessi risultati.

A realità hè:
BlessRNG o cuntrollà u RNG per equità

Visualizazione di sequenze nantu à un pianu da tutti i cinque metudi di generazione:
BlessRNG o cuntrollà u RNG per equità

È a visualizazione in 3D. Lascià solu u risultatu di System.Random.Next () per ùn pruduce un munzeddu di cuntenutu identicu.
BlessRNG o cuntrollà u RNG per equità

A storia contata in l'intruduzioni nantu à a distribuzione normale di UnityEngine.Random ùn si ripete micca: o era inizialmente erroneo, o qualcosa hà cambiatu in u mutore. Ma avà simu sicuri.

Source: www.habr.com

Add a comment