BlessRNG və ya RNG-nin ədalətli olması üçün yoxlanılması

BlessRNG və ya RNG-nin ədalətli olması üçün yoxlanılması

Oyun inkişafında tez-tez təsadüfiliklə nəyisə bağlamaq lazımdır: Birlik bunun üçün öz Randomuna malikdir və bununla paralel olaraq System.Random var. Bir vaxtlar layihələrin birində məndə belə bir təəssürat yarandı ki, hər ikisi fərqli şəkildə işləyə bilər (baxmayaraq ki, onlar bərabər paylanmalıdırlar).

Sonra təfərrüatlara varmadılar - System.Random-a keçid bütün problemləri düzəltməsi kifayət idi. İndi biz bunu daha ətraflı nəzərdən keçirməyə və bir az araşdırma aparmağa qərar verdik: RNG-lərin nə qədər “qərəzli” və ya proqnozlaşdırıla bilən olması və hansını seçmək. Üstəlik, mən dəfələrlə onların "dürüstlüyü" ilə bağlı ziddiyyətli fikirlər eşitmişəm - gəlin real nəticələrin elan edilənlərlə necə müqayisə olunduğunu anlamağa çalışaq.

Qısa təhsil proqramı və ya RNG əslində RNG-dir

Təsadüfi ədəd generatorları ilə artıq tanışsınızsa, dərhal "Sınaq" bölməsinə keçə bilərsiniz.

Təsadüfi ədədlər (RN) entropiya mənbəyi olan bəzi təsadüfi (xaotik) prosesdən istifadə etməklə yaradılan nömrələr ardıcıllığıdır. Yəni bu, elementləri heç bir riyazi qanunla bir-birinə bağlı olmayan ardıcıllıqdır - onların səbəb-nəticə əlaqəsi yoxdur.

Təsadüfi ədədi yaradana təsadüfi ədəd generatoru (RNG) deyilir. Deyəsən, hər şey elementardır, amma nəzəriyyədən praktikaya keçsək, əslində belə bir ardıcıllıq yaratmaq üçün proqram alqoritmini həyata keçirmək o qədər də sadə deyil.

Səbəb müasir istehlak elektronikasında eyni xaosun olmamasıdır. Onsuz təsadüfi ədədlər təsadüfi olmağı dayandırır və onların generatoru açıq şəkildə müəyyən edilmiş arqumentlərin adi funksiyasına çevrilir. İT sahəsində bir sıra ixtisaslar üçün bu ciddi problemdir (məsələn, kriptoqrafiya), digərləri üçün isə tamamilə məqbul həll yolu var.

Həqiqətən təsadüfi ədədlər olmasa da, onlara mümkün qədər yaxın olan psevdo-təsadüfi nömrələr (PRN) deyilənləri qaytaracaq bir alqoritm yazmaq lazımdır. Bu vəziyyətdə alqoritm psevdor təsadüfi nömrə generatoru (PRNG) adlanır.

PRNG yaratmaq üçün bir neçə variant var, lakin aşağıdakılar hər kəs üçün uyğun olacaq:

  1. İlkin işə salınma ehtiyacı.

    PRNG-nin entropiya mənbəyi yoxdur, ona görə də istifadə etməzdən əvvəl ona ilkin vəziyyət verilməlidir. O, rəqəm (və ya vektor) kimi göstərilib və toxum (təsadüfi toxum) adlanır. Çox vaxt prosessor saat sayğacı və ya sistem vaxtının ədədi ekvivalenti toxum kimi istifadə olunur.

  2. Ardıcıllığın təkrar istehsalı.

    PRNG tamamilə deterministikdir, buna görə başlatma zamanı göstərilən toxum gələcək nömrələrin bütün ardıcıllığını unikal şəkildə müəyyənləşdirir. Bu o deməkdir ki, eyni toxumla (müxtəlif vaxtlarda, müxtəlif proqramlarda, müxtəlif cihazlarda) işə salınmış ayrıca PRNG eyni ardıcıllığı yaradacaqdır.

Siz həmçinin PRNG-ni xarakterizə edən ehtimal paylanmasını bilməlisiniz - onun hansı nömrələri yaradacağını və hansı ehtimalla. Çox vaxt bu normal paylanma və ya vahid paylamadır.
BlessRNG və ya RNG-nin ədalətli olması üçün yoxlanılması
Normal paylama (solda) və vahid paylama (sağda)

Deyək ki, 24 tərəfi olan ədalətli bir zar var. Əgər onu atsanız, bir əldə etmə ehtimalı 1/24-ə bərabər olacaq (hər hansı digər nömrənin alınma ehtimalı ilə eyni). Çoxlu atışlar etsəniz və nəticələri qeyd etsəniz, bütün kənarların təxminən eyni tezlikdə düşdüyünü görəcəksiniz. Əslində, bu kalıp vahid paylama ilə RNG hesab edilə bilər.

Bu zərdən 10-nu bir anda atıb ümumi xalları saysanız necə olacaq? Bunun üçün vahidlik qorunacaqmı? Yox. Çox vaxt məbləğ 125 bala, yəni bəzi orta dəyərə yaxın olacaqdır. Nəticədə, hətta atış etməzdən əvvəl, gələcək nəticəni təxminən təxmin edə bilərsiniz.

Səbəb orta xal almaq üçün ən çox birləşmələrin olmasıdır. Ondan nə qədər uzaq olsa, bir o qədər az birləşmələr - və müvafiq olaraq, itki ehtimalı bir o qədər azdır. Bu məlumatlar vizuallaşdırılarsa, o, qeyri-müəyyən bir şəkildə zəng şəklinə bənzəyəcəkdir. Buna görə də, bir qədər uzanmaqla, 10 zardan ibarət bir sistem normal paylanma ilə RNG adlandırıla bilər.

Başqa bir nümunə, yalnız bu dəfə bir təyyarədə - hədəfə atəş. Atıcı, qrafikdə göstərilən bir cüt ədəd (x, y) yaradan RNG olacaq.
BlessRNG və ya RNG-nin ədalətli olması üçün yoxlanılması
Razılaşın ki, soldakı seçim real həyata daha yaxındır - bu, normal paylamalı RNG-dir. Ancaq qaranlıq bir səmada ulduzları səpmək lazımdırsa, vahid paylama ilə RNG istifadə edərək əldə edilən düzgün seçim daha uyğundur. Ümumiyyətlə, tapşırıqdan asılı olaraq generator seçin.

İndi PNG ardıcıllığının entropiyasından danışaq. Məsələn, belə başlayan bir ardıcıllıq var:

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

İlk baxışdan bu rəqəmlər nə qədər təsadüfi görünür? Paylanmanı yoxlamağa başlayaq.
BlessRNG və ya RNG-nin ədalətli olması üçün yoxlanılması
Bu, uniformaya yaxın görünür, lakin iki ədədin ardıcıllığını oxusanız və onları təyyarədəki koordinatlar kimi şərh etsəniz, bunu alırsınız:
BlessRNG və ya RNG-nin ədalətli olması üçün yoxlanılması
Nümunələr aydın görünür. Ardıcıllıqdakı məlumatlar müəyyən bir şəkildə sıralandığından (yəni aşağı entropiyaya malikdir), bu, həmin “qərəz”ə səbəb ola bilər. Ən azı, belə bir PRNG təyyarədə koordinatlar yaratmaq üçün çox uyğun deyil.

Başqa bir ardıcıllıq:

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

Burada, hətta təyyarədə də hər şey qaydasındadır:
BlessRNG və ya RNG-nin ədalətli olması üçün yoxlanılması
Həcmə baxaq (bir anda üç rəqəm oxuyun):
BlessRNG və ya RNG-nin ədalətli olması üçün yoxlanılması
Və yenə nümunələr. Dörd ölçülü vizualizasiya qurmaq artıq mümkün deyil. Amma naxışlar bu ölçüdə və daha böyük ölçülərdə mövcud ola bilər.

PRNG-lərə ən sərt tələblərin qoyulduğu kriptoqrafiyada belə bir vəziyyət qəti şəkildə qəbuledilməzdir. Buna görə də onların keyfiyyətini qiymətləndirmək üçün xüsusi alqoritmlər hazırlanmışdır ki, biz indi bunlara toxunmayacağıq. Mövzu genişdir və ayrıca məqaləyə layiqdir.

Test

Əgər bir şeyi dəqiq bilmiriksə, onunla necə işləmək olar? Hansı işıqforun icazə verdiyini bilmirsinizsə, yolu keçməyə dəyərmi? Nəticələr fərqli ola bilər.

Eyni şey Unity-dəki bədnam təsadüfiliyə də aiddir. Sənədlər lazımi təfərrüatları üzə çıxarsa yaxşıdır, amma məqalənin əvvəlində qeyd olunan hekayə, istədiyiniz xüsusiyyətlərin olmaması səbəbindən baş verdi.

Və alətin necə işlədiyini bilmirsinizsə, ondan düzgün istifadə edə bilməyəcəksiniz. Ümumiyyətlə, ən azı paylama haqqında nəhayət əmin olmaq üçün yoxlamaq və təcrübə aparmaq vaxtı gəldi.

Həll sadə və effektiv idi - statistika toplamaq, obyektiv məlumat əldə etmək və nəticələrə baxmaq.

Tədqiqat mövzusu

Unity-də təsadüfi ədədlər yaratmağın bir neçə yolu var - beşini sınaqdan keçirdik.

  1. System.Random.Next(). Verilmiş dəyər diapazonunda tam ədədlər yaradır.
  2. System.Random.NextDouble(). [0-dan aralığında ikiqat dəqiqlikli ədədlər yaradır; 1).
  3. UnityEngine.Random.Range(). Verilmiş dəyər diapazonunda tək dəqiq nömrələr (üzənlər) yaradır.
  4. UnityEngine.Random.value. [0-dan diapazonda tək dəqiqlikli ədədlər (floats) yaradır; 1).
  5. Birlik.Riyaziyyat.Random.NextFloat(). Yeni Unity.Riyaziyyat kitabxanasının bir hissəsi. Verilmiş dəyər diapazonunda tək dəqiq nömrələr (üzənlər) yaradır.

UnityEngine.Random.value (burada paylanma göstərilməmişdi, lakin UnityEngine.Random.Range() ilə analoji olaraq uniforma da gözlənilirdi) və Unity.Mathematics.Random istisna olmaqla, sənədlərin demək olar ki, hər yerində vahid paylama göstərilmişdir. .NextFloat() (burada The bazası xorshift alqoritmidir, bu o deməkdir ki, yenidən vahid paylanmanı gözləmək lazımdır).

Varsayılan olaraq, gözlənilən nəticələr sənədlərdə göstərilənlər kimi qəbul edildi.

Texnika

Təqdim olunan metodların hər birini istifadə edərək təsadüfi ədədlərin ardıcıllığını yaradan və nəticələri sonrakı emal üçün saxlayan kiçik bir proqram yazdıq.

Hər ardıcıllığın uzunluğu 100 ədəddir.
Təsadüfi ədədlərin diapazonu [0, 100).

Məlumatlar bir neçə hədəf platformadan toplanıb:

  • Windows
    — Unity v2018.3.14f1, Redaktor rejimi, Mono, .NET Standard 2.0
  • MacOS
    — Unity v2018.3.14f1, Redaktor rejimi, Mono, .NET Standard 2.0
    — Unity v5.6.4p4, Redaktor rejimi, Mono, .NET Standard 2.0
  • Android
    — Unity v2018.3.14f1, hər cihaz üçün quruluş, Mono, .NET Standard 2.0
  • iOS
    — Unity v2018.3.14f1, hər cihaz üçün quruluş, il2cpp, .NET Standard 2.0

Tətbiq

Təsadüfi ədədlər yaratmaq üçün bir neçə fərqli yolumuz var. Onların hər biri üçün ayrıca bir sarğı sinfi yazacağıq, bu da təmin etməlidir:

  1. Dəyərlər diapazonunu təyin etmək imkanı [min/max). Konstruktor vasitəsilə təyin olunacaq.
  2. MF-nin qaytarılması üsulu. Növ olaraq float seçək, çünki daha ümumidir.
  3. Nəticələri qeyd etmək üçün nəsil metodunun adı. Rahatlıq üçün biz sinifin tam adını + MF yaratmaq üçün istifadə olunan metodun adını dəyər kimi qaytaracağıq.

Əvvəlcə IRandomGenerator interfeysi ilə təmsil olunacaq abstraksiyanı elan edək:

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

        float Generate();
    }
}

System.Random.Next() tətbiqi

Bu üsul sizə bir sıra dəyərlər təyin etməyə imkan verir, lakin tam ədədləri qaytarır, lakin üzənlər lazımdır. Siz sadəcə olaraq tam ədədi float kimi şərh edə bilərsiniz və ya dəyərlər diapazonunu orta diapazonun hər nəsli ilə kompensasiya edərək bir neçə böyüklük sırası ilə genişləndirə bilərsiniz. Nəticə müəyyən bir dəqiqlik sırasına malik sabit nöqtə kimi bir şey olacaq. Biz bu seçimdən istifadə edəcəyik, çünki o, real float dəyərinə yaxındır.

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() tətbiqi

Burada sabit dəyərlər diapazonu [0; 1). Onu konstruktorda göstərilən birinə proyeksiya etmək üçün sadə hesabdan istifadə edirik: 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() tətbiqi

UnityEngine.Random statik sinifinin bu üsulu sizə bir sıra dəyərlər təyin etməyə imkan verir və float növünü qaytarır. Heç bir əlavə transformasiya etmək lazım deyil.

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 tətbiqi

UnityEngine.Random statik sinifinin dəyər xassəsi sabit dəyərlər diapazonundan float növünü qaytarır [0; 1). Gəlin onu verilmiş diapazona System.Random.NextDouble() tətbiq edərkən olduğu kimi proyeksiya edək.

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() tətbiqi

Unity.Mathematics.Random sinifinin NextFloat() metodu float tipli üzən nöqtəni qaytarır və sizə bir sıra dəyərləri təyin etməyə imkan verir. Yeganə nüans ondan ibarətdir ki, Unity.Mathematics.Random-un hər bir nümunəsi müəyyən bir toxumla işə salınmalı olacaq - bu yolla biz təkrarlanan ardıcıllıqların yaranmasının qarşısını alacağıq.

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-in həyata keçirilməsi

IRandomGenerator-un bir neçə tətbiqi hazırdır. Sonra, ardıcıllıqlar yaratmalı və nəticədə əldə edilən məlumat dəstini emal üçün saxlamalısınız. Bunun üçün biz Unity-də bütün lazımi işləri görəcək və eyni zamanda UI ilə qarşılıqlı əlaqəyə cavabdeh olacaq səhnə və kiçik MainController skripti yaradacağıq.

Gəlin verilənlər bazasının ölçüsünü və MF dəyərlərinin diapazonunu təyin edək, həmçinin konfiqurasiya edilmiş və işə hazır olan generatorlar massivini qaytaran metodu əldə edək.

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

        ...
    }
}

İndi verilənlər bazası yaradaq. Bu halda, məlumatların yaradılması nəticələri mətn axınına (csv formatında) qeyd etməklə birləşdiriləcəkdir. Hər bir IRandomGenerator-un dəyərlərini saxlamaq üçün onun ayrıca sütunu ayrılır və birinci sətirdə generatorun adı var.

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

        ...
    }
}

Yalnız GenerateCsvDataSet metodunu çağırmaq və nəticəni faylda saxlamaq və ya məlumatları dərhal şəbəkə üzərindən son cihazdan qəbul edən serverə ötürmək qalır.

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

        ...
    }
}

Layihə mənbələri bu ünvandadır GitLab.

Tapıntılar

Heç bir möcüzə baş vermədi. Gözlədikləri şey əldə etdikləridir - bütün hallarda, sui-qəsd işarəsi olmadan bərabər paylama. Platformalar üçün ayrıca qrafiklər qoymağın mənasını görmürəm - hamısı təxminən eyni nəticələri göstərir.

Reallıq budur:
BlessRNG və ya RNG-nin ədalətli olması üçün yoxlanılması

Bütün beş nəsil üsullarından bir təyyarədə ardıcıllığın vizuallaşdırılması:
BlessRNG və ya RNG-nin ədalətli olması üçün yoxlanılması

Və 3D-də vizuallaşdırma. Bir dəstə eyni məzmun yaratmamaq üçün yalnız System.Random.Next() nəticəsini buraxacağam.
BlessRNG və ya RNG-nin ədalətli olması üçün yoxlanılması

Girişdə UnityEngine.Random-un normal paylanması haqqında danışılan hekayə təkrarlanmadı: ya əvvəlcə səhv idi, ya da sonradan mühərrikdə nəsə dəyişdi. Amma indi əminik.

Mənbə: www.habr.com

Добавить комментарий