BlessRNG edo RNG zuzentasuna egiaztatzea

BlessRNG edo RNG zuzentasuna egiaztatzea

Jokoen garapenean, askotan lotu behar duzu zerbait ausazkotasunarekin: Unity-k bere Random du horretarako, eta horrekin batera System.Random dago. Garai batean, proiekturen batean, biak ezberdin funtziona zezaketela iruditu zitzaidan (banaketa parekatua izan behar zuten arren).

Orduan ez ziren xehetasunetan sartu - nahikoa zen System.Random-era trantsizioak arazo guztiak zuzendu zituela. Orain zehatzago aztertzea eta ikerketa txiki bat egitea erabaki dugu: RNG zein "alboratuak" edo aurreikusten diren, eta zein aukeratu. Gainera, behin baino gehiagotan entzun ditut iritzi kontrajarriak haien "zintzotasunari buruz" - saia gaitezen egiazko emaitzak deklaratutakoekin nola alderatzen diren ulertzen.

Hezkuntza programa laburra edo RNG benetan RNG da

Ausazko zenbaki-sorgailuak ezagutzen badituzu, berehala joan zaitezke "Probak" atalera.

Ausazko zenbakiak (RN) ausazko prozesu (kaotiko) batzuk erabiliz sortutako zenbakien sekuentzia dira, entropia iturri bat. Hau da, sekuentzia bat da, zeinaren elementuak inolako lege matematikoren bidez elkarloturik ez dauden -ez dute kausa-ondorio erlaziorik.

Ausazko zenbakia sortzen duenari ausazko zenbaki-sorgailu (RNG) deitzen zaio. Dena oinarrizkoa dela dirudi, baina teoriatik praktikara pasatzen bagara, egia esan, ez da hain erraza sekuentzia hori sortzeko software-algoritmo bat ezartzea.

Arrazoia kontsumo elektroniko modernoan kaos bera ez egotean datza. Hori gabe, ausazko zenbakiek ausazko izateari uzten diote, eta haien sorgailua argi eta garbi definitutako argumentuen funtzio arrunt bihurtzen da. Informatika arloko espezialitate batzuentzat arazo larria da (adibidez, kriptografia), baina beste batzuentzat konponbide guztiz onargarria da.

Beharrezkoa da itzuliko litzatekeen algoritmo bat idatzi behar da, benetan ausazko zenbakiak ez badira ere, baina horietatik ahalik eta hurbilen - sasi-ausazko zenbakiak (PRN) deiturikoak. Kasu honetan algoritmoari zenbaki pseudo-ausazkoen sortzailea (PRNG) deitzen zaio.

PRNG bat sortzeko hainbat aukera daude, baina honako hau garrantzitsua izango da guztiontzat:

  1. Aurretiazko hasierako beharra.

    PRNG-k ez du entropia iturririk, beraz, erabili aurretik hasierako egoera bat eman behar zaio. Zenbaki (edo bektore) gisa zehazten da eta hazia (ausazko hazia) deritzo. Askotan, prozesadorearen erloju-kontagailua edo sistemaren denboraren baliokide numerikoa erabiltzen da hazi gisa.

  2. Sekuentziaren errepikagarritasuna.

    PRNG guztiz deterministikoa da, beraz, hasieratzerakoan zehaztutako haziak etorkizuneko zenbaki-sekuentzia osoa zehazten du modu bakarra. Horrek esan nahi du hazi berdinarekin (une ezberdinetan, programa ezberdinetan, gailu ezberdinetan) hasierako PRNG bereizi batek sekuentzia bera sortuko duela.

PRNG ezaugarritzen duen probabilitate-banaketa ere ezagutu behar duzu: zer zenbaki sortuko dituen eta zer probabilitaterekin. Gehienetan banaketa normala edo banaketa uniformea ​​da.
BlessRNG edo RNG zuzentasuna egiaztatzea
Banaketa normala (ezkerrean) eta banaketa uniformea ​​(eskuinean)

Demagun 24 alde dituen trokel bidezko bat dugula. Bozkatzen baduzu, bat lortzeko probabilitatea 1/24ren berdina izango da (beste edozein zenbaki lortzeko probabilitatearen berdina). Bota asko egin eta emaitzak erregistratzen badituzu, ertz guztiak gutxi gorabehera maiztasun berdinarekin erortzen direla ohartuko zara. Funtsean, trokel hau banaketa uniformea ​​duen RNGtzat har daiteke.

Zer gertatzen da aldi berean 10 dado hauetatik bota eta guztira puntuak zenbatzen badituzu? Uniformetasuna mantenduko al da horretarako? Ez. Gehienetan, zenbatekoa 125 puntutik hurbil egongo da, hau da, batez besteko balio batera. Eta, ondorioz, jaurtiketa bat egin aurretik ere, gutxi gorabehera etorkizuneko emaitza kalkulatu dezakezu.

Arrazoia da batez besteko puntuazioa lortzeko konbinazio kopuru handiena dagoela. Zenbat eta urrunago, orduan eta konbinazio gutxiago, eta, ondorioz, galtzeko probabilitatea txikiagoa da. Datu hauek bistaratzen badira, kanpai baten formaren antza lausoa izango da. Beraz, tarte pixka batekin, 10 dadoko sistema bati banaketa normala duen RNG dei daiteke.

Beste adibide bat, oraingoan bakarrik hegazkinean - helburu bati tiro egitea. Jaurtitzailea grafikoan bistaratzen den zenbaki pare bat (x, y) sortzen duen RNG bat izango da.
BlessRNG edo RNG zuzentasuna egiaztatzea
Ados ezkerreko aukera bizitza errealetik gertuago dagoela - hau banaketa normala duen RNG bat da. Baina izarrak zeru ilunean sakabanatu behar badituzu, orduan aukera egokia da, banaketa uniformeko RNG erabiliz lortutako aukera egokia. Oro har, aukeratu sorgailu bat esku artean duzun zereginaren arabera.

Orain hitz egin dezagun PNG sekuentziaren entropiari buruz. Adibidez, bada honela hasten den sekuentzia bat:

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, ...

Nola ausazkoak dira zenbaki hauek lehen begiratuan? Has gaitezen banaketa egiaztatzen.
BlessRNG edo RNG zuzentasuna egiaztatzea
Uniformetik gertu dirudi, baina bi zenbakiren segida irakurri eta plano batean koordenatu gisa interpretatzen badituzu, hau lortuko duzu:
BlessRNG edo RNG zuzentasuna egiaztatzea
Ereduak argi ikusten dira. Eta sekuentziako datuak modu jakin batean ordenatuta daudenez (hau da, entropia baxua dutenez), horrek oso “alborapen” hori sor dezake. Gutxienez, PRNG hori ez da oso egokia plano batean koordenatuak sortzeko.

Beste sekuentzia bat:

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, ...

Hemen dena ondo dagoela dirudi hegazkinean ere:
BlessRNG edo RNG zuzentasuna egiaztatzea
Ikus dezagun bolumena (irakurri hiru zenbaki aldi berean):
BlessRNG edo RNG zuzentasuna egiaztatzea
Eta berriro ere ereduak. Jada ezin da lau dimentsiotan bistaratzea eraiki. Baina ereduak dimentsio honetan eta handiagoetan egon daitezke.

Kriptografian, PRNGei baldintza zorrotzenak ezartzen zaizkienean, egoera hori guztiz onartezina da. Hori dela eta, haien kalitatea ebaluatzeko algoritmo bereziak garatu dira, orain ukituko ez ditugunak. Gaia zabala da eta aparteko artikulu bat merezi du.

Testing

Zerbait ziur ez badakigu, orduan nola lan egin? Merezi al du errepidea zeharkatzeak ez badakizu zein semaforo uzten duen? Ondorioak desberdinak izan daitezke.

Gauza bera gertatzen da Unity-en ausazkotasun sonatuarekin. Ondo dago dokumentazioak beharrezko xehetasunak agerian uzten baditu, baina artikuluaren hasieran aipatzen den istorioa, hain zuzen, nahi diren zehaztasun faltagatik gertatu da.

Eta tresnak nola funtzionatzen duen ez badakizu, ezin izango duzu behar bezala erabili. Orokorrean, esperimentu bat egiaztatzeko eta egiteko garaia iritsi da, azkenik, banaketari buruz gutxienez ziurtatzeko.

Irtenbidea sinplea eta eraginkorra zen: estatistikak bildu, datu objektiboak lortu eta emaitzak begiratu.

Ikasgaia

Unity-n ausazko zenbakiak sortzeko hainbat modu daude: bost probatu ditugu.

  1. Sistema.Ausazko.Hurrengoa(). Zenbaki osoak sortzen ditu balio sorta jakin batean.
  2. System.Random.NextDouble(). Zehaztasun bikoitzeko zenbakiak sortzen ditu [0tik aurrera; 1).
  3. UnityEngine.Random.Range(). Zehaztasun bakarreko zenbakiak (flotatzaileak) sortzen ditu balio sorta jakin batean.
  4. UnityEngine.Ausazko.balioa. Zehaztasun bakarreko zenbakiak (flotatzaileak) sortzen ditu [0tik; 1).
  5. Unity.Mathematics.Ausazko.NextFloat(). Unity.Matematika liburutegi berriaren parte. Zehaztasun bakarreko zenbakiak (flotatzaileak) sortzen ditu balio sorta jakin batean.

Dokumentazioan ia leku guztietan banaketa uniforme bat zehazten zen, UnityEngine.Random.value (non banaketa zehaztu ez zen, baina UnityEngine.Random.Range() uniformearekin analogiaz ere espero zen) eta Unity.Mathematics.Random izan ezik. .NextFloat() (non dagoen Oinarria xorshift algoritmoa da, hau da, berriro ere banaketa uniforme baten zain egon behar duzula).

Berez, aurreikusitako emaitzak dokumentazioan zehaztutakoak bezala hartu ziren.

teknika

Aurkeztutako metodo bakoitza erabiliz ausazko zenbakien sekuentziak sortu eta emaitzak prozesatzeko gorde zituen aplikazio txiki bat idatzi genuen.

Segida bakoitzaren luzera 100 zenbakikoa da.
Ausazko zenbakien barrutia [0, 100] da.

Hainbat helburu-plataformatatik bildu dira datuak:

  • Windows
    — Unity v2018.3.14f1, Editor modua, Mono, .NET Standard 2.0
  • MacOS
    — Unity v2018.3.14f1, Editor modua, Mono, .NET Standard 2.0
    — Unity v5.6.4p4, Editor modua, Mono, .NET Standard 2.0
  • Android
    — Unity v2018.3.14f1, gailu bakoitzeko eraiki, Mono, .NET Standard 2.0
  • IOS
    — Unity v2018.3.14f1, gailu bakoitzeko eraiki, il2cpp, .NET Standard 2.0

Inplementazioa

Ausazko zenbakiak sortzeko hainbat modu ditugu. Horietako bakoitzerako, bilgarri-klase bereizia idatziko dugu, eta horrek eman beharko luke:

  1. Balioen tartea ezartzeko aukera [min/max). Eraikitzailearen bidez ezarriko da.
  2. MF itzultzeko metodoa. Aukeratu dezagun float mota gisa, orokorragoa baita.
  3. Emaitzak markatzeko sortzeko metodoaren izena. Erosotasunerako, balio gisa itzuliko dugu klasearen izen osoa + MF sortzeko erabilitako metodoaren izena.

Lehenik eta behin, IRandomGenerator interfazeak irudikatuko duen abstrakzio bat deklaratu dezagun:

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

        float Generate();
    }
}

System.Random.Next() ezarpena

Metodo honek balio sorta bat ezartzeko aukera ematen du, baina osoak itzultzen ditu, baina flotatzaileak behar dira. Zenbaki osoa flotatzaile gisa interpretatu dezakezu, edo balio-sorta hainbat magnitude-agindutan zabal dezakezu, erdialdeko belaunaldi bakoitzarekin konpentsatuz. Emaitza puntu finko baten antzeko zerbait izango da zehaztasun-ordena jakin batekin. Aukera hau erabiliko dugu benetako flotazio-baliotik hurbilago dagoenez.

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;
    }
}

System.Random.NextDouble()-ren ezarpena

Hemen balioen sorta finkoa [0; 1). Eraikitzailean zehaztutakoaren gainean proiektatzeko, aritmetika sinplea erabiltzen dugu: 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;
    }
}

UnityEngine.Random.Range() ezartzea

UnityEngine.Random klase estatikoko metodo honek balio sorta bat ezartzeko aukera ematen du eta flotatzaile mota bat itzultzen du. Ez duzu eraldaketa gehigarririk egin behar.

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);
    }
}

UnityEngine.Random.value-ren ezarpena

UnityEngine klase estatikoko balio-propietateak [0] balio-tarte finko batetik flotatzaile mota bat itzultzen du. 1). Proiektatu dezagun barruti jakin batean System.Random.NextDouble() inplementatzen duzun modu berean.

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;
    }
}

Unity.Mathematics.Random.NextFloat()-ren ezarpena

Unity.Mathematics.Random klaseko NextFloat() metodoak float motako koma mugikorra itzultzen du eta balio sorta bat zehaztea ahalbidetzen du. Ñabardura bakarra da Unity.Mathematics.Random-en instantzia bakoitza hazi batekin hasieratu beharko dela; horrela, sekuentzia errepikakorrak sortzea saihestuko dugu.

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);
    }
}

MainController-en ezarpena

IRandomGenerator-en hainbat inplementazio prest daude. Ondoren, sekuentziak sortu eta lortutako datu-multzoa gorde behar duzu prozesatzeko. Horretarako, eszena bat eta MainController script txiki bat sortuko ditugu Unity-n, beharrezko lan guztia egingo duena eta, aldi berean, interfazearen interakzioaz arduratuko dena.

Ezar ditzagun datu-multzoaren tamaina eta MF balioen barrutia, eta lor ditzagun konfiguratutako eta lan egiteko prest dauden sorgailu sorta bat itzultzen duen metodo bat.

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)
            };
        }

        ...
    }
}

Orain sor dezagun datu multzo bat. Kasu honetan, datuak sortzea emaitzak testu-korronte batean grabatzearekin konbinatuko da (csv formatuan). IRandomGenerator bakoitzaren balioak gordetzeko, bere zutabe bereizia esleitzen da eta lehenengo lerroan sorgailuaren izena dago.

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();
            }
        }

        ...
    }
}

Geratzen dena da GenerateCsvDataSet metodoari deitzea eta emaitza fitxategi batean gordetzea edo berehala datuak sarearen bidez transferitzea amaierako gailutik zerbitzari hartzailera.

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();
            }
        }

        ...
    }
}

Proiektuaren iturriak hemen daude GitLab.

Findings

Ez zen miraririk gertatu. Espero zutena lortu zutena da: kasu guztietan, banaketa uniformea ​​konspirazio kutsurik gabe. Ez dut balio ikusten plataformetarako grafiko bereiziak jartzeari; denek gutxi gorabehera emaitza berdinak erakusten dituzte.

Errealitatea hau da:
BlessRNG edo RNG zuzentasuna egiaztatzea

Bost belaunaldiko metodoetatik sekuentziak plano batean bistaratzea:
BlessRNG edo RNG zuzentasuna egiaztatzea

Eta bistaratzea 3Dn. System.Random.Next()-ren emaitza bakarrik utziko dut, eduki berdin-pixka bat ez sortzeko.
BlessRNG edo RNG zuzentasuna egiaztatzea

Sarreran UnityEngine-ren banaketa normalari buruz kontatzen den istorioa.Random ez zen errepikatu: edo hasieran okerra zen, edo ordutik zerbait aldatu da motorrean. Baina orain ziur gaude.

Iturria: www.habr.com

Gehitu iruzkin berria