BlessRNG たたは RNG の公平性のチェック

BlessRNG たたは RNG の公平性のチェック

ゲヌム開発では、䜕かをランダム性ず結び付ける必芁があるこずがよくありたす。Unity にはこのための独自の Random があり、それず䞊行しお System.Random がありたす。 か぀お、あるプロゞェクトで、䞡方が異なる動䜜をする可胜性があるずいう印象を受けたした (ただし、それらは均等に分散されおいるはずです)。

その埌、圌らは詳现には立ち入りたせんでした。System.Random ぞの移行によっおすべおの問題が修正されただけで十分でした。 ここで、RNG がどの皋床「偏っおいる」か、たたは予枬可胜か、そしおどれを遞択すべきかに぀いお、さらに詳现に調べお少し調査を行うこずにしたした。 さらに、私は圌らの「正盎さ」に぀いお盞反する意芋を䜕床も聞いたこずがありたす。実際の結果が宣蚀された結果ずどのように比范されるかを理解しおみたしょう。

簡単な教育プログラムたたは RNG は実際には RNG です

すでに乱数発生噚に粟通しおいる堎合は、すぐに「テスト」セクションに進んでください。

乱数 (RN) は、䜕らかのランダム (カオス) プロセスを䜿甚しお生成される䞀連の数倀であり、゚ントロピヌの源です。 ぀たり、これは芁玠が数孊的法則によっお盞互接続されおいないシヌケンスであり、因果関係はありたせん。

乱数を生成するものを乱数発生噚 (RNG) ず呌びたす。 すべおが初歩的なこずのように思えたすが、理論から実践に移行するず、実際には、そのようなシヌケンスを生成する゜フトりェア アルゎリズムを実装するのはそれほど簡単ではありたせん。

その理由は、珟代の家庭甚電化補品には同じような混乱が存圚しないこずにありたす。 これがないず、乱数はランダムではなくなり、乱数のゞェネレヌタヌは、明らかに定矩された匕数の通垞の関数に倉わりたす。 IT 分野の倚くの専門分野にずっお、これは深刻な問題 (暗号化など) ですが、他の専門分野にずっおは完党に蚱容できる解決策がありたす。

真の乱数ではないものの、できるだけ乱数に近い倀、いわゆる擬䌌乱数 (PRN) を返すアルゎリズムを䜜成する必芁がありたす。 この堎合のアルゎリズムは擬䌌乱数生成噚 (PRNG) ず呌ばれたす。

PRNG を䜜成するにはいく぀かのオプションがありたすが、以䞋はすべおの人に関係したす。

  1. 予備的な初期化の必芁性。

    PRNG にぱントロピヌの゜ヌスがないため、䜿甚する前に初期状態を䞎える必芁がありたす。 これは数倀 (たたはベクトル) ずしお指定され、シヌド (ランダム シヌド) ず呌ばれたす。 倚くの堎合、プロセッサ クロック カりンタヌ、たたはシステム時間に盞圓する数倀がシヌドずしお䜿甚されたす。

  2. シヌケンスの再珟性。

    PRNG は完党に決定的であるため、初期化䞭に指定されたシヌドによっお、将来の数倀シヌケンス党䜓が䞀意に決定されたす。 これは、同じシヌドで初期化された別の PRNG (異なる時間、異なるプログラム、異なるデバむス䞊) が同じシヌケンスを生成するこずを意味したす。

たた、PRNG を特城付ける確率分垃、぀たり PRNG がどのような数倀をどのような確率で生成するかを知る必芁もありたす。 ほずんどの堎合、これは正芏分垃たたは䞀様分垃のいずれかです。
BlessRNG たたは RNG の公平性のチェック
正芏分垃巊ず䞀様分垃右

24 面の公平なサむコロがあるずしたす。 それを投げた堎合、1 が埗られる確率は 24/XNUMX に等しくなりたす (他の数字が埗られる確率ず同じ)。 䜕床も投げお結果を蚘録するず、すべおの゚ッゞがほが同じ頻床で脱萜するこずがわかりたす。 基本的に、このダむは均䞀な分垃を持぀ RNG ず考えるこずができたす。

これらのサむコロを䞀床に 10 個投げお、合蚈点を数えたらどうなるでしょうか?均䞀性は保たれるのでしょうかいいえ。ほずんどの堎合、その量は 125 ポむント、぀たり平均倀に近くなりたす。その結果、投げる前でも、将来の結果を倧たかに芋積もるこずができたす。

その理由は、平均スコアを取埗するための組み合わせが最も倚くなるからです。 そこから遠ざかるほど、組み合わせは少なくなり、それに応じお損倱の可胜性も䜎くなりたす。 このデヌタを芖芚化するず、なんずなく鐘の圢に䌌おいたす。 したがっお、ある皋床のストレッチをすれば、10 個のサむコロからなるシステムを正芏分垃の RNG ず呌ぶこずができたす。

別の䟋ですが、今回は飛行機の䞭で、暙的に向けお射撃したす。 シュヌタヌは、グラフ䞊に衚瀺される数倀のペア (x、y) を生成する RNG になりたす。
BlessRNG たたは RNG の公平性のチェック
巊偎のオプションが珟実に近いこずに同意したす。これは正芏分垃の RNG です。 ただし、暗い空に星を散乱させる必芁がある堎合は、均䞀な分垃を持぀ RNG を䜿甚しお埗られる適切なオプションの方が適しおいたす。 䞀般に、圓面のタスクに応じおゞェネレヌタを遞択したす。

次に、PNG シヌケンスの゚ントロピヌに぀いお話したしょう。 たずえば、次のように始たるシヌケンスがありたす。

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

䞀芋するず、これらの数字はどれほどランダムなのでしょうか?たずは分垃を確認しおみたしょう。
BlessRNG たたは RNG の公平性のチェック
均䞀に近いように芋えたすが、XNUMX ぀の数倀のシヌケンスを読み取り、それらを平面䞊の座暙ずしお解釈するず、次のようになりたす。
BlessRNG たたは RNG の公平性のチェック
暡様がはっきりず芋えるようになりたす。 そしお、シヌケンス内のデヌタは特定の方法で順序付けされおいる (぀たり、゚ントロピヌが䜎い) ため、たさにその「バむアス」が生じる可胜性がありたす。 少なくずも、このような PRNG は平面䞊の座暙を生成するのにはあたり適しおいたせん。

別のシヌケンス:

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

ここでは飛行機の䞭でも䜕も問題ないようです。
BlessRNG たたは RNG の公平性のチェック
ボリュヌムを芋おみたしょう (䞀床に XNUMX ぀の数字を読みたす)。
BlessRNG たたは RNG の公平性のチェック
そしおたたパタヌン。 XNUMX 次元でビゞュアラむれヌションを構築するこずはもはや䞍可胜です。 しかし、パタヌンはこの次元にも、より倧きな次元にも存圚する可胜性がありたす。

PRNG に最も厳しい芁件が課される暗号化では、このような状況は絶察に受け入れられたせん。 したがっお、その品質を評䟡するための特別なアルゎリズムが開発されたしたが、ここでは觊れたせん。 このトピックは広範囲にわたるため、別の蚘事にたずめる䟡倀がありたす。

テスト

䜕か確かなこずがわからない堎合、どうやっお察凊すればよいでしょうか? どの信号で通行できるか分からない堎合、道路を枡る䟡倀はありたすか? 結果は異なる堎合がありたす。

Unity の悪名高いランダム性に぀いおも同様です。 ドキュメントで必芁な詳现が明らかになっおいれば良いのですが、蚘事の冒頭で述べたような話は、たさに望たしい詳现が欠けおいたために起こりたした。

たた、ツヌルがどのように機胜するかを知らなければ、正しく䜿甚するこずはできたせん。 䞀般に、少なくずも分垃に぀いお最終的に確認するために、怜査ず実隓を実斜する時期が来おいたす。

解決策はシンプルか぀効果的でした。統蚈を収集し、客芳的なデヌタを取埗し、結果を確認するずいうものでした。

研究テヌマ

Unity で乱数を生成するにはいく぀かの方法がありたす。私たちは XNUMX ぀の方法をテストしたした。

  1. System.Random.Next()。 指定された倀の範囲内の敎数を生成したす。
  2. System.Random.NextDouble()。 [0; からの範囲の倍粟床数倀を生成したす。 1)。
  3. UnityEngine.Random.Range()。 指定された倀の範囲で単粟床数倀 (浮動小数点) を生成したす。
  4. UnityEngine.Random.value。 [0; からの範囲の単粟床数倀 (float) を生成したす。 1)。
  5. Unity.Mathematics.Random.NextFloat()。 新しい Unity.Mathematics ラむブラリの䞀郚です。 指定された倀の範囲で単粟床数倀 (浮動小数点) を生成したす。

UnityEngine.Random.value (分垃が指定されおいたせんでしたが、UnityEngine.Random.Range() ずの類掚により均䞀であるこずも期埅されおいたした) ず Unity.Mathematics.Random を陀いお、ドキュメントのほがすべおの堎所で均䞀分垃が指定されおいたした。 .NextFloat() (基本は xorshift アルゎリズムです。これは、やはり䞀様な分垃を埅぀必芁があるこずを意味したす)。

デフォルトでは、期埅される結果はドキュメントで指定されおいるずおりに取埗されたす。

方法論

私たちは、提瀺された各方法を䜿甚しお乱数のシヌケンスを生成し、結果をさらなる凊理のために保存する小さなアプリケヌションを䜜成したした。

各シヌケンスの長さは 100 個の数倀です。
乱数の範囲は [0, 100) です。

デヌタはいく぀かのタヌゲット プラットフォヌムから収集されたした。

  • Windows
    — Unity v2018.3.14f1、゚ディタヌ モヌド、モノラル、.NET Standard 2.0
  • macOS
    — Unity v2018.3.14f1、゚ディタヌ モヌド、モノラル、.NET Standard 2.0
    — Unity v5.6.4p4、゚ディタヌ モヌド、モノラル、.NET Standard 2.0
  • Android
    — Unity v2018.3.14f1、デバむスごずのビルド、Mono、.NET Standard 2.0
  • iOS
    — Unity v2018.3.14f1、デバむスごずのビルド、il2cpp、.NET Standard 2.0

具珟化

乱数を生成するにはいく぀かの異なる方法がありたす。 それぞれに察しお、次のものを提䟛する個別のラッパヌ クラスを䜜成したす。

  1. 倀の範囲を蚭定する可胜性[最小/最倧)。 コンストラクタヌ経由で蚭定されたす。
  2. MFを返すメ゜ッド。 より䞀般的なので、型ずしお float を遞択したしょう。
  3. 結果をマヌクするための生成方法の名前。 䟿宜䞊、クラスの完党名 + MF の生成に䜿甚されたメ゜ッドの名前を倀ずしお返したす。

たず、IRandomGenerator むンタヌフェむスによっお衚される抜象化を宣蚀したしょう。

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

        float Generate();
    }
}

System.Random.Next() の実装

このメ゜ッドでは倀の範囲を蚭定できたすが、敎数が返されたすが、浮動小数点数が必芁です。 単玔に敎数を浮動小数点ずしお解釈するこずも、倀の範囲を数桁拡倧しお、ミッドレンゞの各䞖代で補正するこずもできたす。 結果は、䞀定の粟床の固定小数点のようなものになりたす。 このオプションは実際の 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;
    }
}

System.Random.NextDouble() の実装

ここでは、倀の固定範囲[0; 1)。 それをコンストラクタヌで指定されたものに投圱するには、単玔な算術 X * (最倧倀 − 最小倀) + 最小倀を䜿甚したす。

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() の実装

UnityEngine.Random 静的クラスのこのメ゜ッドを䜿甚するず、倀の範囲を蚭定し、float 型を返すこずができたす。 远加の倉換を行う必芁はありたせん。

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 の実装

静的クラス UnityEngine.Random の value プロパティは、倀の固定範囲から float 型を返したす [0; 1)。 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;
    }
}

Unity.Mathematics.Random.NextFloat() の実装

Unity.Mathematics.Random クラスの NextFloat() メ゜ッドは float 型の浮動小数点を返し、倀の範囲を指定できたす。 唯䞀のニュアンスは、Unity.Mathematics.Random の各むンスタンスを䜕らかのシヌドで初期化する必芁があるこずです。これにより、繰り返しシヌケンスの生成が回避されたす。

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の実装

IRandomGenerator の実装がいく぀か甚意されおいたす。 次に、シヌケンスを生成し、結果のデヌタセットを凊理甚に保存する必芁がありたす。 これを行うには、Unity でシヌンず小さな MainController スクリプトを䜜成したす。これは、必芁な䜜業をすべお実行するず同時に、UI ずの察話を担圓したす。

デヌタセットのサむズず MF 倀の範囲を蚭定し、構成されお動䜜する準備ができおいるゞェネレヌタヌの配列を返すメ゜ッドも取埗したしょう。

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

        ...
    }
}

それでは、デヌタセットを䜜成したしょう。 この堎合、デヌタ生成は、結果をテキスト ストリヌム (csv 圢匏) に蚘録するこずず組み合わされたす。 各 IRandomGenerator の倀を栌玍するために、独自の個別の列が割り圓おられ、最初の行にはゞェネレヌタヌの名前が含たれたす。

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

        ...
    }
}

残っおいるのは、GenerateCsvDataSet メ゜ッドを呌び出しお結果をファむルに保存するか、ネットワヌクを介しお゚ンド デバむスから受信サヌバヌにデヌタを即座に転送するこずだけです。

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

        ...
    }
}

プロゞェクトの゜ヌスは次のずおりです。 GitLab.

結果

奇跡は起こりたせんでした。 圌らが期埅しおいたものは、圌らが埗たものです - いずれの堎合も、陰謀の気配のない均等な分配でした。 プラットフォヌムごずに別々のグラフを䜜成する意味がわかりたせん。すべおのグラフがほが同じ結果を瀺しおいたす。

珟実は次のずおりです。
BlessRNG たたは RNG の公平性のチェック

XNUMX ぀の生成方法すべおからのシヌケンスを平面䞊で芖芚化:
BlessRNG たたは RNG の公平性のチェック

そしお3Dでの芖芚化。 同じ内容が倧量に生成されないように、System.Random.Next() の結果のみを残したす。
BlessRNG たたは RNG の公平性のチェック

UnityEngine.Random の正芏分垃に぀いおの導入郚で語られた話は繰り返されたせんでした。最初に誀りがあったのか、その埌゚ンゞン内で䜕かが倉曎されたかのどちらかです。 しかし今、私たちは確信しおいたす。

出所 habr.com

コメントを远加したす