Random Numbers
Juliaにおける乱数生成は、デフォルトでXoshiro256++アルゴリズムを使用し、Task
ごとに状態を持ちます。他のRNGタイプはAbstractRNG
タイプを継承することでプラグインでき、その後、複数の乱数ストリームを取得するために使用できます。
Random
パッケージによってエクスポートされるPRNG(擬似乱数生成器)は次のとおりです:
TaskLocalRNG
: 現在アクティブなタスクローカルストリームの使用を表すトークンで、親タスクから決定的にシードされるか、プログラム開始時にRandomDevice
(システムのランダム性を使用)によってシードされます。Xoshiro
: Xoshiro256++アルゴリズムを使用して、小さな状態ベクトルと高いパフォーマンスで高品質の乱数ストリームを生成します。RandomDevice
: OS提供のエントロピー用。これは暗号的に安全な乱数(CS(P)RNG)に使用される場合があります。MersenneTwister
: 古いバージョンのJuliaでデフォルトだった代替の高品質PRNGで、非常に高速ですが、状態ベクトルを保存しランダムシーケンスを生成するためにはるかに多くのスペースを必要とします。
ほとんどのランダム生成に関連する関数は、最初の引数としてオプションの AbstractRNG
オブジェクトを受け入れます。また、ランダム値の配列を生成するために、次元指定 dims...
(タプルとしても指定可能)を受け入れるものもあります。マルチスレッドプログラムでは、一般的にスレッドセーフであるために、異なるスレッドやタスクから異なるRNGオブジェクトを使用するべきです。ただし、デフォルトのRNGはJulia 1.3以降スレッドセーフであり(バージョン1.6まではスレッドごとのRNGを使用し、それ以降はタスクごとに使用します)。
提供されたRNGは、以下のタイプの均一なランダム数を生成できます: Float16
、 Float32
、 Float64
、 BigFloat
、 Bool
、 Int8
、 UInt8
、 Int16
、 UInt16
、 Int32
、 UInt32
、 Int64
、 UInt64
、 Int128
、 UInt128
、 BigInt
(またはそれらのタイプの複素数)。ランダムな浮動小数点数は $[0, 1)$ の範囲で均一に生成されます。 BigInt
は無制限の整数を表すため、区間を指定する必要があります(例: rand(big.(1:6))
)。
さらに、いくつかの AbstractFloat
および Complex
タイプに対して、正規分布と指数分布が実装されています。詳細については、 randn
および randexp
を参照してください。
他の分布からランダムな数を生成するには、Distributions.jl パッケージを参照してください。
乱数が生成される正確な方法は実装の詳細と見なされるため、バグ修正や速度改善により、バージョン変更後に生成される数のストリームが変わる可能性があります。ユニットテスト中に特定のシードや生成された数のストリームに依存することは推奨されません - 代わりに、問題のメソッドの特性をテストすることを検討してください。
Random numbers module
Random.Random
— Moduleランダム
ランダム数を生成するためのサポート。rand
、randn
、AbstractRNG
、MersenneTwister
、およびRandomDevice
を提供します。
Random generation functions
Base.rand
— Functionrand([rng=default_rng()], [S], [dims...])
指定された値の集合 S
からランダムな要素またはランダムな要素の配列を選択します。S
は以下のいずれかであることができます。
インデックス可能なコレクション(例えば
1:9
や('x', "y", :z)
)AbstractDict
またはAbstractSet
オブジェクト文字列(文字のコレクションと見なされる)、または
指定された値の集合に対応する以下のリストからの型
- 具体的な整数型は
typemin(S):typemax(S)
からサンプリングします(BigInt
はサポートされていません) - 具体的な浮動小数点型は
[0, 1)
からサンプリングします - 具体的な複素数型
Complex{T}
は、T
がサンプリング可能な型であれば、実部と虚部をそれぞれT
に対応する値の集合から独立して取得しますが、T
がサンプリング不可能な場合はサポートされません。 - すべての
<:AbstractChar
型は有効なUnicodeスカラーの集合からサンプリングします - ユーザー定義型と値の集合; 実装ガイダンスについては Hooking into the
Random
API を参照してください - 知られたサイズのタプル型で、
S
の各パラメータがサンプリング可能な型である場合; 型S
の値を返します。Tuple{Vararg{T}}
(サイズ不明)やTuple{1:2}
(値でパラメータ化された)などのタプル型はサポートされていないことに注意してください Pair
型、例えばPair{X, Y}
で、rand
がX
とY
に対して定義されている場合、ランダムなペアが生成されます。
- 具体的な整数型は
S
のデフォルトは Float64
です。オプションの rng
以外に1つの引数が渡され、かつそれが Tuple
の場合、それは値のコレクション(S
)として解釈され、dims
とは見なされません。
通常分布の数値については randn
を、インプレースの同等物については rand!
および randn!
を参照してください。
タプルとしての S
のサポートには少なくとも Julia 1.1 が必要です。
Tuple
型としての S
のサポートには少なくとも Julia 1.11 が必要です。
例
julia> rand(Int, 2)
2-element Array{Int64,1}:
1339893410598768192
1575814717733606317
julia> using Random
julia> rand(Xoshiro(0), Dict(1=>2, 3=>4))
3 => 4
julia> rand((2, 3))
3
julia> rand(Float64, (2, 3))
2×3 Array{Float64,2}:
0.999717 0.0143835 0.540787
0.696556 0.783855 0.938235
rand(rng, s::Union{AbstractDict,AbstractSet})
の複雑さは s
の長さに対して線形ですが、定数の複雑さを持つ最適化されたメソッドが利用可能な場合(Dict
、Set
および密な BitSet
の場合)、その限りではありません。数回以上の呼び出しには、代わりに rand(rng, collect(s))
を使用するか、適切に rand(rng, Dict(s))
または rand(rng, Set(s))
を使用してください。
Random.rand!
— Functionrand!([rng=default_rng()], A, [S=eltype(A)])
配列 A
をランダムな値で埋めます。S
が指定されている場合(S
は型またはコレクションであり、詳細は rand
を参照)、値は S
からランダムに選ばれます。これは copyto!(A, rand(rng, S, size(A)))
と同等ですが、新しい配列を割り当てることはありません。
例
julia> rand!(Xoshiro(123), zeros(5))
5-element Vector{Float64}:
0.521213795535383
0.5868067574533484
0.8908786980927811
0.19090669902576285
0.5256623915420473
Random.bitrand
— Functionbitrand([rng=default_rng()], [dims...])
ランダムなブール値の BitArray
を生成します。
例
julia> bitrand(Xoshiro(123), 10)
10-element BitVector:
0
1
0
1
0
1
0
0
1
1
Base.randn
— Functionrandn([rng=default_rng()], [T=Float64], [dims...])
平均0、標準偏差1の正規分布に従う型T
の乱数を生成します。オプションのdims
引数を指定すると、そのサイズの配列を生成します。Juliaの標準ライブラリは、rand
を実装している任意の浮動小数点型に対してrandn
をサポートしています。例えば、Base
型のFloat16
、Float32
、Float64
(デフォルト)、およびBigFloat
と、それらのComplex
対応型です。
(T
が複素数の場合、値は分散1の円対称複素正規分布から引き出され、実部と虚部は平均0、分散1/2
の独立した正規分布を持ちます)。
インプレースで動作するrandn!
も参照してください。
例
単一の乱数を生成する(デフォルトのFloat64
型を使用):
julia> randn()
-0.942481877315864
正規乱数の行列を生成する(デフォルトのFloat64
型を使用):
julia> randn(2,3)
2×3 Matrix{Float64}:
1.18786 -0.678616 1.49463
-0.342792 -0.134299 -1.45005
ユーザー定義のシードで乱数生成器rng
を設定し(再現可能な数値のため)、それを使用して乱数Float32
またはComplexF32
の乱数行列を生成する:
julia> using Random
julia> rng = Xoshiro(123);
julia> randn(rng, Float32)
-0.6457307f0
julia> randn(rng, ComplexF32, (2, 3))
2×3 Matrix{ComplexF32}:
-1.03467-1.14806im 0.693657+0.056538im 0.291442+0.419454im
-0.153912+0.34807im 1.0954-0.948661im -0.543347-0.0538589im
Random.randn!
— Functionrandn!([rng=default_rng()], A::AbstractArray) -> A
配列 A
を平均 0、標準偏差 1 の正規分布に従うランダムな数値で埋めます。 rand
関数も参照してください。
例
julia> randn!(Xoshiro(123), zeros(5))
5-element Vector{Float64}:
-0.6457306721039767
-1.4632513788889214
-1.6236037455860806
-0.21766510678354617
0.4922456865251828
Random.randexp
— Functionrandexp([rng=default_rng()], [T=Float64], [dims...])
スケール1の指数分布に従って、型 T
のランダムな数を生成します。オプションで、そのようなランダムな数の配列を生成します。Base
モジュールは現在、Float16
、Float32
、および Float64
(デフォルト)の型に対する実装を提供しています。
例
julia> rng = Xoshiro(123);
julia> randexp(rng, Float32)
1.1757717f0
julia> randexp(rng, 3, 3)
3×3 Matrix{Float64}:
1.37766 0.456653 0.236418
3.40007 0.229917 0.0684921
0.48096 0.577481 0.71835
Random.randexp!
— Functionrandexp!([rng=default_rng()], A::AbstractArray) -> A
配列 A
をスケール1の指数分布に従うランダムな数値で埋めます。
例
julia> randexp!(Xoshiro(123), zeros(5))
5-element Vector{Float64}:
1.1757716836348473
1.758884569451514
1.0083623637301151
0.3510644315565272
0.6348266443720407
Random.randstring
— Functionrandstring([rng=default_rng()], [chars], [len=8])
長さ len
のランダムな文字列を作成します。文字列は chars
の文字から構成され、デフォルトでは大文字と小文字のアルファベットおよび数字 0-9 のセットになります。オプションの rng
引数は乱数生成器を指定します。詳細は Random Numbers を参照してください。
例
julia> Random.seed!(3); randstring()
"Lxz5hUwn"
julia> randstring(Xoshiro(3), 'a':'z', 6)
"iyzcsm"
julia> randstring("ACGT")
"TGCTCCTC"
chars
は Char
または UInt8
型の任意の文字コレクションであり、rand
がそこからランダムに文字を選択できる限り、使用可能です。
Subsequences, permutations and shuffling
Random.randsubseq
— Functionrandsubseq([rng=default_rng(),] A, p) -> Vector
与えられた配列 A
のランダム部分列からなるベクターを返します。A
の各要素は、独立した確率 p
で(順序を保ったまま)含まれます。(複雑さは p*length(A)
に対して線形であるため、この関数は p
が小さく A
が大きい場合でも効率的です。)技術的には、このプロセスは A
の「ベルヌーイサンプリング」として知られています。
例
julia> randsubseq(Xoshiro(123), 1:8, 0.3)
2-element Vector{Int64}:
4
7
Random.randsubseq!
— Functionrandsubseq!([rng=default_rng(),] S, A, p)
randsubseq
と同様ですが、結果はS
に格納されます(必要に応じてサイズが変更されます)。
例
julia> S = Int64[];
julia> randsubseq!(Xoshiro(123), S, 1:8, 0.3)
2-element Vector{Int64}:
4
7
julia> S
2-element Vector{Int64}:
4
7
Random.randperm
— Functionrandperm([rng=default_rng(),] n::Integer)
長さ n
のランダムな順列を構築します。オプションの rng
引数は乱数生成器を指定します(乱数を参照)。結果の要素型は n
の型と同じです。
任意のベクトルをランダムに並べ替えるには、shuffle
または shuffle!
を参照してください。
Julia 1.1 では randperm
は eltype(v) == typeof(n)
のベクトル v
を返しますが、Julia 1.0 では eltype(v) == Int
です。
例
julia> randperm(Xoshiro(123), 4)
4-element Vector{Int64}:
1
4
2
3
Random.randperm!
— Functionrandperm!([rng=default_rng(),] A::Array{<:Integer})
A
の中に長さlength(A)
のランダムな順列を構築します。オプションのrng
引数は乱数生成器を指定します(乱数を参照)。任意のベクトルをランダムに順列するには、shuffle
またはshuffle!
を参照してください。
例
julia> randperm!(Xoshiro(123), Vector{Int}(undef, 4))
4-element Vector{Int64}:
1
4
2
3
Random.randcycle
— Functionrandcycle([rng=default_rng(),] n::Integer)
長さ n
のランダムな循環置換を構築します。オプションの rng
引数は乱数生成器を指定します。詳細は Random Numbers を参照してください。結果の要素型は n
の型と同じです。
ここで「循環置換」とは、すべての要素が単一のサイクル内にあることを意味します。n > 0
の場合、可能な循環置換は $(n-1)!$ 個あり、均等にサンプリングされます。n == 0
の場合、randcycle
は空のベクターを返します。
randcycle!
はこの関数のインプレースバリアントです。
Julia 1.1 以降では、randcycle
は eltype(v) == typeof(n)
のベクター v
を返しますが、Julia 1.0 では eltype(v) == Int
です。
例
julia> randcycle(Xoshiro(123), 6)
6-element Vector{Int64}:
5
4
2
6
3
1
Random.randcycle!
— Functionrandcycle!([rng=default_rng(),] A::Array{<:Integer})
A
に長さn = length(A)
のランダムな循環置換を構築します。オプションのrng
引数は乱数生成器を指定します。詳細はRandom Numbersを参照してください。
ここで「循環置換」とは、すべての要素が単一のサイクル内にあることを意味します。A
が空でない場合(n > 0
)、可能な循環置換は$(n-1)!$個あり、これらは均等にサンプリングされます。A
が空の場合、randcycle!
はそれを変更しません。
randcycle
は、新しいベクトルを割り当てるこの関数のバリアントです。
例
julia> randcycle!(Xoshiro(123), Vector{Int}(undef, 6))
6-element Vector{Int64}:
5
4
2
6
3
1
Random.shuffle
— Functionshuffle([rng=default_rng(),] v::AbstractArray)
v
のランダムに順序を入れ替えたコピーを返します。オプションのrng
引数は乱数生成器を指定します(乱数を参照)。v
をインプレースで順序を入れ替えるには、shuffle!
を参照してください。ランダムに順序を入れ替えたインデックスを取得するには、randperm
を参照してください。
例
julia> shuffle(Xoshiro(123), Vector(1:10))
10-element Vector{Int64}:
5
4
2
3
6
10
8
1
9
7
Random.shuffle!
— Functionshuffle!([rng=default_rng(),] v::AbstractArray)
shuffle
のインプレースバージョン:v
をインプレースでランダムに順序を入れ替え、オプションで乱数生成器 rng
を指定します。
例
julia> shuffle!(Xoshiro(123), Vector(1:10))
10-element Vector{Int64}:
5
4
2
3
6
10
8
1
9
7
Generators (creation and seeding)
Random.default_rng
— FunctionRandom.default_rng() -> rng
デフォルトのグローバル乱数生成器 (RNG) を返します。これは、明示的な RNG が提供されていない場合に rand
関連の関数によって使用されます。
Random
モジュールがロードされると、デフォルトの RNG は ランダムに シードされます。これは Random.seed!()
を介して行われます。つまり、新しい Julia セッションが開始されるたびに、最初の rand()
の呼び出しは異なる結果を生成します。最初に seed!(seed)
が呼ばれない限りです。
これはスレッドセーフです:異なるスレッドは安全に default_rng()
に対して rand
関連の関数を同時に呼び出すことができます。例えば、rand(default_rng())
のようにです。
デフォルトの RNG の型は実装の詳細です。異なるバージョンの Julia において、デフォルトの RNG が常に同じ型を持つとは限らず、特定のシードに対して同じ乱数のストリームを生成するとは限りません。
この関数は Julia 1.3 で導入されました。
Random.seed!
— Functionseed!([rng=default_rng()], seed) -> rng
seed!([rng=default_rng()]) -> rng
乱数生成器を再シードします:rng
は、seed
が提供される場合に限り、再現可能な数列を生成します。一部のRNGはシードを受け付けません(例:RandomDevice
)。seed!
の呼び出し後、rng
は同じシードで初期化された新しく作成されたオブジェクトと同等になります。受け入れられるシードの型はrng
の型によって異なりますが、一般的に整数シードは機能するはずです。
rng
が指定されていない場合、共有タスクローカルジェネレーターの状態をシードすることがデフォルトとなります。
例
julia> Random.seed!(1234);
julia> x1 = rand(2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> Random.seed!(1234);
julia> x2 = rand(2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> x1 == x2
true
julia> rng = Xoshiro(1234); rand(rng, 2) == x1
true
julia> Xoshiro(1) == Random.seed!(rng, 1)
true
julia> rand(Random.seed!(rng), Bool) # 再現性なし
true
julia> rand(Random.seed!(rng), Bool) # 再現性なし
false
julia> rand(Xoshiro(), Bool) # 再現性なし
true
Random.AbstractRNG
— TypeAbstractRNG
MersenneTwister
やRandomDevice
などの乱数生成器のスーパークラスです。
Random.TaskLocalRNG
— TypeTaskLocalRNG
TaskLocalRNG
は、そのスレッドではなくタスクにローカルな状態を持っています。これはタスクの作成時に親タスクの状態からシードされますが、親のRNGの状態を進めることはありません。
利点として、TaskLocalRNG
は非常に高速で、スケジューラの決定に依存せず、レースコンディションを除けば再現可能なマルチスレッドシミュレーションを許可します。タスクの作成にスレッド数が使用されない限り、シミュレーション結果は利用可能なスレッド/CPUの数にも依存しません。ランダムストリームは、エンディアンやおそらくワードサイズを除いて、ハードウェアの特性に依存するべきではありません。
current_task()
によって返されるタスク以外のタスクのRNGを使用したりシードしたりすることは未定義の動作です:ほとんどの場合は機能しますが、時には静かに失敗することがあります。
TaskLocalRNG()
をseed!
でシードする際に渡されるシードは、あれば任意の整数であることができます。
負の整数シードでTaskLocalRNG()
をシードするには、少なくともJulia 1.11が必要です。
Julia 1.10以降、タスクの作成は親タスクのRNG状態を進めなくなりました。
Random.Xoshiro
— TypeXoshiro(seed::Union{Integer, AbstractString})
Xoshiro()
Xoshiro256++は、David BlackmanとSebastiano Vignaによって「Scrambled Linear Pseudorandom Number Generators」で説明された高速擬似乱数生成器です。参照実装は https://prng.di.unimi.it で入手可能です。
高速度に加えて、Xoshiroは小さなメモリフットプリントを持ち、多くの異なる乱数状態を長時間保持する必要があるアプリケーションに適しています。
JuliaのXoshiro実装にはバルク生成モードがあり、親から新しい仮想PRNGをシードし、SIMDを使用して並列に生成します(つまり、バルクストリームは複数のインタリーブされたxoshiroインスタンスで構成されます)。仮想PRNGは、バルクリクエストが処理されると破棄され(ヒープアロケーションを引き起こすことはありません)、
シードが提供されない場合は、ランダムに生成されたものが作成されます(システムからのエントロピーを使用)。既存のXoshiro
オブジェクトの再シードについては、seed!
関数を参照してください。
負の整数シードを渡すには、少なくともJulia 1.11が必要です。
例
julia> using Random
julia> rng = Xoshiro(1234);
julia> x1 = rand(rng, 2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> rng = Xoshiro(1234);
julia> x2 = rand(rng, 2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> x1 == x2
true
Random.MersenneTwister
— TypeMersenneTwister(seed)
MersenneTwister()
MersenneTwister
RNGオブジェクトを作成します。異なるRNGオブジェクトはそれぞれ独自のシードを持つことができ、異なるランダム数のストリームを生成するのに役立ちます。seed
は整数、文字列、またはUInt32
整数のベクターである可能性があります。シードが提供されない場合、ランダムに生成されたものが作成されます(システムからのエントロピーを使用)。既存のMersenneTwister
オブジェクトの再シードについては、seed!
関数を参照してください。
負の整数シードを渡すには、少なくともJulia 1.11が必要です。
例
julia> rng = MersenneTwister(123);
julia> x1 = rand(rng, 2)
2-element Vector{Float64}:
0.37453777969575874
0.8735343642013971
julia> x2 = rand(MersenneTwister(123), 2)
2-element Vector{Float64}:
0.37453777969575874
0.8735343642013971
julia> x1 == x2
true
Random.RandomDevice
— TypeRandomDevice()
RandomDevice
RNGオブジェクトを作成します。このような2つのオブジェクトは常に異なるランダム数のストリームを生成します。エントロピーはオペレーティングシステムから取得されます。
Hooking into the Random
API
Random
機能を拡張する主に直交する2つの方法があります:
- カスタムタイプのランダム値を生成する
- 新しいジェネレーターの作成
The API for 1) is quite functional, but is relatively recent so it may still have to evolve in subsequent releases of the Random
module. For example, it's typically sufficient to implement one rand
method in order to have all other usual methods work automatically.
The API for 2) is still rudimentary, and may require more work than strictly necessary from the implementor, in order to support usual types of generated values.
Generating random values of custom types
ランダム値を生成するためのいくつかの分布には、さまざまなトレードオフが関与する場合があります。事前計算された 値、例えば離散分布のための alias table や、単変量分布のための “squeezing” functions は、サンプリングを大幅に高速化することができます。事前に計算すべき情報の量は、分布から引き出す予定の値の数によって異なる場合があります。また、いくつかの乱数生成器には、さまざまなアルゴリズムが利用したい特定の特性がある場合があります。
Random
モジュールは、これらの問題に対処するためのランダム値を取得するカスタマイズ可能なフレームワークを定義します。rand
の各呼び出しは、上記のトレードオフを考慮してカスタマイズ可能な サンプラー を生成します。これは、Sampler
にメソッドを追加することで実現され、ランダム数生成器、分布を特徴づけるオブジェクト、および繰り返し回数の提案に基づいてディスパッチできます。現在、後者については、Val{1}
(単一サンプル用)と Val{Inf}
(任意の数用)が使用されており、Random.Repetition
は両者のエイリアスです。
Sampler
によって返されるオブジェクトは、ランダムな値を生成するために使用されます。サンプリング可能な値X
のためのランダム生成インターフェースを実装する際、実装者はメソッドを定義する必要があります。
rand(rng, sampler)
Sampler(rng, X, repetition)
によって返される特定のsampler
について。
サンプラーは rand(rng, sampler)
を実装する任意の値である可能性がありますが、ほとんどのアプリケーションでは以下の定義済みサンプラーで十分かもしれません:
SamplerType{T}()
は、型T
からサンプリングを行うサンプラーを実装するために使用できます(例:rand(Int)
)。これは、型 に対してSampler
が返すデフォルトです。SamplerTrivial(self)
はself
のシンプルなラッパーで、[]
を使ってアクセスできます。これは、事前に計算された情報が必要ない場合(例:rand(1:3)
)に推奨されるサンプラーであり、valuesのためにSampler
が返すデフォルトです。SamplerSimple(self, data)
は、任意の事前計算された値を格納するために使用できる追加のdata
フィールドも含まれています。これらの値はSampler
の カスタムメソッド で計算されるべきです。
これらの各例を提供します。ここでは、アルゴリズムの選択がRNGとは独立していると仮定し、署名にはAbstractRNG
を使用します。
Random.Sampler
— TypeSampler(rng, x, repetition = Val(Inf))
rng
からx
のランダムな値を生成するために使用できるサンプラーオブジェクトを返します。
sp = Sampler(rng, x, repetition)
のとき、rand(rng, sp)
がランダムな値を引き出すために使用され、適切に定義されるべきです。
repetition
は Val(1)
または Val(Inf)
であり、適用可能な場合の前計算の量を決定するための提案として使用されるべきです。
Random.SamplerType
と Random.SamplerTrivial
はそれぞれ 型 と 値 のデフォルトのフォールバックです。 Random.SamplerSimple
は、この目的のためだけに追加の型を定義することなく、前計算された値を保存するために使用できます。
Random.SamplerType
— TypeSamplerType{T}()
型のためのサンプラーで、他の情報は含まれていません。型で呼び出されたときのSampler
のデフォルトフォールバックです。
Random.SamplerTrivial
— TypeSamplerTrivial(x)
与えられた値 x
を単にラップするサンプラーを作成します。これは値のデフォルトのフォールバックです。このサンプラーの eltype
は eltype(x)
と等しいです。
推奨される使用ケースは、事前に計算されたデータなしで値からサンプリングすることです。
Random.SamplerSimple
— TypeSamplerSimple(x, data)
与えられた値 x
と data
をラップするサンプラーを作成します。このサンプラーの eltype
は eltype(x)
と等しくなります。
推奨される使用ケースは、事前に計算されたデータから値をサンプリングすることです。
事前計算を実際に値を生成することから切り離すことはAPIの一部であり、ユーザーにも利用可能です。例えば、rand(rng, 1:20)
をループ内で繰り返し呼び出す必要があると仮定します。この切り離しを活用する方法は次のとおりです:
rng = Xoshiro()
sp = Random.Sampler(rng, 1:20) # or Random.Sampler(Xoshiro, 1:20)
for x in X
n = rand(rng, sp) # similar to n = rand(rng, 1:20)
# use n
end
これは、標準ライブラリでも使用されるメカニズムであり、例えば、ランダム配列生成のデフォルト実装(rand(1:20, 10)
のような)によって使用されます。
Generating values from a type
与えられた型 T
に対して、rand(T)
が定義されている場合、型 T
のオブジェクトが生成されると仮定されています。SamplerType
は 型のデフォルトサンプラー です。型 T
の値のランダム生成を定義するために、rand(rng::AbstractRNG, ::Random.SamplerType{T})
メソッドを定義する必要があり、これは rand(rng, T)
が返すことが期待される値を返すべきです。
次の例を考えてみましょう:Die
型を実装します。これは、1
からn
までの番号が付けられた可変数n
の面を持ちます。rand(Die)
が最大20面(少なくとも4面)のランダムな数のDie
を生成することを望みます。
struct Die
nsides::Int # number of sides
end
Random.rand(rng::AbstractRNG, ::Random.SamplerType{Die}) = Die(rand(rng, 4:20))
# output
Die
のスカラーおよび配列メソッドは、期待通りに動作します:
julia> rand(Die)
Die(5)
julia> rand(Xoshiro(0), Die)
Die(10)
julia> rand(Die, 3)
3-element Vector{Die}:
Die(9)
Die(15)
Die(14)
julia> a = Vector{Die}(undef, 3); rand!(a)
3-element Vector{Die}:
Die(19)
Die(7)
Die(17)
A simple sampler without pre-computed data
ここではコレクションのサンプラーを定義します。事前に計算されたデータが必要ない場合、SamplerTrivial
サンプラーを使用して実装できます。これは実際には値のデフォルトフォールバックです。
S
型のオブジェクトからのランダム生成を定義するために、次のメソッドを定義する必要があります: rand(rng::AbstractRNG, sp::Random.SamplerTrivial{S})
。ここで、sp
は単にS
型のオブジェクトをラップしており、sp[]
を介してアクセスできます。Die
の例を続けると、d
の側面の1つに対応するInt
を生成するためにrand(d::Die)
を定義したいと思います:
julia> Random.rand(rng::AbstractRNG, d::Random.SamplerTrivial{Die}) = rand(rng, 1:d[].nsides);
julia> rand(Die(4))
1
julia> rand(Die(4), 3)
3-element Vector{Any}:
2
3
3
与えられたコレクションタイプ S
において、rand(::S)
が定義されている場合、eltype(S)
の型のオブジェクトが生成されると仮定されています。最後の例では、Vector{Any}
が生成されました。その理由は、eltype(Die) == Any
だからです。解決策は、Base.eltype(::Type{Die}) = Int
を定義することです。
Generating values for an AbstractFloat
type
AbstractFloat
型は特別扱いされます。なぜなら、デフォルトではランダムな値は全体の型ドメインではなく、むしろ [0,1)
の範囲で生成されるからです。次のメソッドは T <: AbstractFloat
に対して実装されるべきです: Random.rand(::AbstractRNG, ::Random.SamplerTrivial{Random.CloseOpen01{T}})
An optimized sampler with pre-computed data
離散分布を考えます。ここで、数値 1:n
が与えられた確率で引かれ、その合計が1になります。この分布から多くの値が必要な場合、最も速い方法は alias table を使用することです。このようなテーブルを構築するためのアルゴリズムはここでは提供しませんが、代わりに make_alias_table(probabilities)
で利用可能であり、draw_number(rng, alias_table)
を使用してそこからランダムな数を引くことができます。
分布が次のように説明されていると仮定します。
struct DiscreteDistribution{V <: AbstractVector}
probabilities::V
end
そして、私たちは常にエイリアステーブルを構築したいと考えています。必要な値の数に関係なく(以下でカスタマイズ方法を学びます)。メソッド
Random.eltype(::Type{<:DiscreteDistribution}) = Int
function Random.Sampler(::Type{<:AbstractRNG}, distribution::DiscreteDistribution, ::Repetition)
SamplerSimple(distribution, make_alias_table(distribution.probabilities))
end
サンプラーを事前に計算されたデータで返すように定義されるべきです、その後
function rand(rng::AbstractRNG, sp::SamplerSimple{<:DiscreteDistribution})
draw_number(rng, sp.data)
end
値を描画するために使用されます。
Custom sampler types
SamplerSimple
型は、事前に計算されたデータを使用するほとんどのユースケースに対して十分です。しかし、カスタムサンプラータイプの使用方法を示すために、ここではSamplerSimple
に似たものを実装します。
Die
の例に戻ると、rand(::Die)
は範囲からのランダム生成を使用するため、この最適化の機会があります。私たちはカスタムサンプラーをSamplerDie
と呼びます。
import Random: Sampler, rand
struct SamplerDie <: Sampler{Int} # generates values of type Int
die::Die
sp::Sampler{Int} # this is an abstract type, so this could be improved
end
Sampler(RNG::Type{<:AbstractRNG}, die::Die, r::Random.Repetition) =
SamplerDie(die, Sampler(RNG, 1:die.nsides, r))
# the `r` parameter will be explained later on
rand(rng::AbstractRNG, sp::SamplerDie) = rand(rng, sp.sp)
sp = Sampler(rng, die)
を使ってサンプラーを取得し、rng
に関わる任意のrand
呼び出しでdie
の代わりにsp
を使用することが可能になりました。上記の単純な例では、die
をSamplerDie
に保存する必要はありませんが、実際にはこれがよくあるケースです。
もちろん、このパターンは非常に頻繁に使用されるため、上記で使用したヘルパータイプである Random.SamplerSimple
が利用可能であり、SamplerDie
の定義を省略できます。私たちは次のようにデカップリングを実装することができました:
Sampler(RNG::Type{<:AbstractRNG}, die::Die, r::Random.Repetition) =
SamplerSimple(die, Sampler(RNG, 1:die.nsides, r))
rand(rng::AbstractRNG, sp::SamplerSimple{Die}) = rand(rng, sp.data)
ここで、sp.data
はSamplerSimple
コンストラクタへの呼び出しの第二引数を指します(この場合、Sampler(rng, 1:die.nsides, r)
に等しいです)。一方、Die
オブジェクトはsp[]
を介してアクセスできます。
SamplerDie
のように、カスタムサンプラーはすべてSampler{T}
のサブタイプでなければなりません。ここで、T
は生成される値の型です。SamplerSimple(x, data) isa Sampler{eltype(x)}
であることに注意してください。これにより、SamplerSimple
の最初の引数が何であるべきかが制約されます(x
が単に転送されるSampler
メソッドを定義するDie
の例のように、SamplerSimple
を使用することをお勧めします)。同様に、SamplerTrivial(x) isa Sampler{eltype(x)}
です。
別のヘルパータイプが他のケース用に現在利用可能であり、Random.SamplerTag
ですが、内部APIと見なされており、適切な非推奨なしにいつでも壊れる可能性があります。
Using distinct algorithms for scalar or array generation
場合によっては、少数の値を生成したいのか、大量の値を生成したいのかがアルゴリズムの選択に影響を与えることがあります。これは Sampler
コンストラクタの第3引数で処理されます。ここでは、少数のランダム値を生成するために使用される SamplerDie1
と、多くの値のための SamplerDieMany
という2つのヘルパータイプを定義したと仮定します。これらのタイプは次のように使用できます:
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{1}) = SamplerDie1(...)
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{Inf}) = SamplerDieMany(...)
もちろん、rand
はこれらの型でも定義されている必要があります(すなわち、rand(::AbstractRNG, ::SamplerDie1)
およびrand(::AbstractRNG, ::SamplerDieMany)
)。通常通り、カスタム型が必要ない場合は、SamplerTrivial
およびSamplerSimple
を使用できます。
注意: Sampler(rng, x)
は単に Sampler(rng, x, Val(Inf))
の省略形であり、Random.Repetition
は Union{Val{1}, Val{Inf}}
のエイリアスです。
Creating new generators
APIはまだ明確に定義されていませんが、一般的な指針として:
- 任意の
rand
メソッドは、この特定のRNGのために定義されるべきであり、"基本的な" 型(isbitstype
整数および浮動小数点型)を生成する場合に必要です; - 他の文書化された
rand
メソッドは、AbstractRNG
を受け入れる場合、(1) で依存しているメソッドが実装されていれば)そのまま動作するはずですが、最適化の余地があれば、この RNG に特化させることももちろん可能です。 copy
は擬似乱数生成器(pseudo-RNG)に対して、同じ方法で呼び出されたときに元のものと同じ乱数列を生成する独立したコピーを返すべきです。これが実現不可能な場合(例:ハードウェアベースの乱数生成器)、copy
は実装されてはなりません。
Concerning 1), a rand
method may happen to work automatically, but it's not officially supported and may break without warnings in a subsequent release.
新しい rand
メソッドを仮想の MyRNG
ジェネレーターのために定義し、値の仕様 s
(例:s == Int
または s == 1:10
)を S==typeof(s)
または S==Type{s}
(s
が型の場合)として、前に見たのと同じ2つのメソッドを定義する必要があります:
Sampler(::Type{MyRNG}, ::S, ::Repetition)
は、型SamplerS
のオブジェクトを返します。rand(rng::MyRNG, sp::SamplerS)
Sampler(rng::AbstractRNG, ::S, ::Repetition)
がRandom
モジュール内ですでに定義されていることがあります。その場合、特定のRNGタイプに対して生成を特化したい場合は、実際にはステップ1)をスキップすることが可能ですが、対応するSamplerS
タイプは内部の詳細と見なされ、予告なしに変更される可能性があります。
Specializing array generation
場合によっては、特定のRNGタイプに対して、ランダム値の配列を生成することが、前述のデカップリング技術を単に使用するよりも、特化した方法を用いる方が効率的であることがあります。これは、MersenneTwister
の場合のように、ネイティブにランダム値を配列に書き込むことができる場合です。
MyRNG
のこの特化を実装し、仕様s
に対して、型S
の要素を生成するために、次のメソッドを定義できます: rand!(rng::MyRNG, a::AbstractArray{S}, ::SamplerS)
。ここで、SamplerS
はSampler(MyRNG, s, Val(Inf))
によって返されるサンプラーの型です。AbstractArray
の代わりに、機能をサブタイプ、例えばArray{S}
のみに実装することも可能です。rand
の非変異配列メソッドは、内部でこの特化を自動的に呼び出します。
Reproducibility
与えられたシードで初期化されたRNGパラメータを使用することで、プログラムを複数回実行した際に同じ擬似乱数列を再現することができます。しかし、Juliaのマイナーリリース(例:1.3から1.4)では、特定のシードから生成される擬似乱数の列が変更される可能性があります。特にMersenneTwister
が使用されている場合です。(低レベルの関数rand
によって生成される列が変更されない場合でも、randsubseq
のような高レベルの関数の出力はアルゴリズムの更新により変更される可能性があります。)理由:擬似乱数ストリームが決して変更されないことを保証することは、多くのアルゴリズムの改善を禁止します。
ランダムデータの正確な再現性を保証する必要がある場合は、単にデータを保存することをお勧めします(例えば、科学出版物の補足資料として)。 (もちろん、特定のJuliaバージョンやパッケージマニフェストを指定することもできます。特にビット再現性が必要な場合は。)
特定の「ランダム」データに依存するソフトウェアテストは、一般的にデータを保存するか、テストコードに埋め込むか、または StableRNGs.jl のようなサードパーティパッケージを使用するべきです。一方で、ほとんどのランダムデータに対して合格すべきテスト(例:ランダム行列 A = randn(n,n)
に対して A \ (A*x) ≈ x
をテストする)は、固定シードを持つRNGを使用して、テストを何度も実行しても非常にありえないデータ(例:極端に条件の悪い行列)による失敗が発生しないようにすることができます。
統計的な分布からランダムサンプルが抽出されることは、マイナーなJuliaリリース間で同じであることが保証されています。