Random Numbers
在 Julia 中,随机数生成默认使用 Xoshiro256++ 算法,并具有每个 Task
的状态。其他 RNG 类型可以通过继承 AbstractRNG
类型进行插入;然后可以使用它们来获取多个随机数流。
Random
包导出的 PRNG(伪随机数生成器)有:
TaskLocalRNG
:一个表示使用当前活动任务局部流的令牌,该流是从父任务确定性地播种的,或者在程序启动时由RandomDevice
(使用系统随机性)播种。Xoshiro
:使用 Xoshiro256++ 算法生成高质量的随机数流,具有小状态向量和高性能。RandomDevice
:用于操作系统提供的熵。这可以用于加密安全的随机数(CS(P)RNG)。MersenneTwister
:一种替代的高质量伪随机数生成器(PRNG),在旧版本的Julia中是默认的,速度也相当快,但需要更多的空间来存储状态向量和生成随机序列。
大多数与随机生成相关的函数接受一个可选的 AbstractRNG
对象作为第一个参数。有些函数还接受维度规格 dims...
(也可以作为元组给出)来生成随机值的数组。在多线程程序中,通常应该使用来自不同线程或任务的不同 RNG 对象,以确保线程安全。然而,从 Julia 1.3 开始,默认的 RNG 是线程安全的(在 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
之外只传递一个参数并且是 Tuple
时,它被解释为值集合(S
),而不是 dims
。
另请参见 randn
以获取正态分布的数字,以及 rand!
和 randn!
以获取就地等效项。
将 S
作为元组的支持至少需要 Julia 1.1。
将 S
作为 Tuple
类型的支持至少需要 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
参数,生成一个大小为dims
的数组。Julia的标准库支持对任何实现了rand
的浮点类型使用randn
,例如Base
类型的Float16
、Float32
、Float64
(默认)和BigFloat
,以及它们的Complex
对应类型。
(当T
为复数时,值是从方差为1的圆对称复正态分布中抽取的,实部和虚部具有独立的正态分布,均值为零,方差为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
用正态分布(均值为0,标准差为1)的随机数填充数组 A
。另请参见 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...])
根据指数分布生成类型为 T
的随机数,规模为 1。可选地生成这样的随机数数组。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
用遵循指数分布(尺度为1)的随机数填充数组 A
。
示例
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
返回一个向量 v
,其 eltype(v) == typeof(n)
,而在 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
参数指定一个随机数生成器(参见 Random Numbers)。要随机排列任意向量,请参见 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
返回一个向量 v
,其 eltype(v) == typeof(n)
,而在 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 的数量。随机流不应依赖于硬件特性,直到字节序和可能的字长。
使用或初始化任何其他任务的 RNG,而不是由 current_task()
返回的任务,是未定义的行为:它大多数时候会正常工作,但有时可能会静默失败。
当使用 seed!
初始化 TaskLocalRNG()
时,传递的种子(如果有的话)可以是任何整数。
使用负整数种子初始化 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" 中描述,发表于 ACM Trans. Math. Softw., 2021。参考实现可在 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
整数的向量。如果没有提供种子,将创建一个随机生成的种子(使用系统的熵)。请参见 seed!
函数以重新为已存在的 MersenneTwister
对象设置种子。
传递一个负整数种子需要至少 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 对象。两个这样的对象将始终生成不同的随机数流。熵来自操作系统。
Hooking into the Random
API
有两种主要正交的方式来扩展 Random
的功能:
- 生成自定义类型的随机值
- 创建新生成器
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,可以根据上述权衡进行定制,通过向 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)
),这是推荐的采样器,并且是Sampler
为 values 返回的默认值。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
类型,具有可变数量 n
的面,编号从 1
到 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
为例,我们现在想要定义 rand(d::Die)
以生成一个对应于 d
的某一面的 Int
:
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
以给定的概率抽取,这些概率的总和为一。当需要从该分布中获取许多值时,最快的方法是使用 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
调用中使用 sp
代替 die
。在上面的简单示例中,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
的第一个参数可以是什么(建议像在 Die
示例中那样使用 SamplerSimple
,其中 x
在定义 Sampler
方法时被简单地转发)。同样,SamplerTrivial(x) isa Sampler{eltype(x)}
。
另一个帮助器类型目前可用于其他情况,Random.SamplerTag
,但被视为内部 API,可能会在没有适当弃用的情况下随时中断。
Using distinct algorithms for scalar or array generation
在某些情况下,是否希望生成少量值或大量值将影响算法的选择。这通过 Sampler
构造函数的第三个参数来处理。假设我们定义了两个 Die
的辅助类型,分别是 SamplerDie1
,用于生成少量随机值,以及 SamplerDieMany
,用于生成大量值。我们可以如下使用这些类型:
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 尚未明确定义,但作为经验法则:
- 任何产生“基本”类型(
isbitstype
整数和浮点类型在Base
中)的rand
方法都应该为这个特定的随机数生成器定义,如果它们是需要的; - 其他文档中记录的
rand
方法接受AbstractRNG
应该可以开箱即用(前提是依赖的 1) 中的方法已实现),但当然可以针对这个 RNG 进行特化,如果有优化的空间; copy
对于伪随机数生成器应返回一个独立的副本,该副本在以相同方式调用时从该点开始生成与原始生成的完全相同的随机序列。当这不可行时(例如,基于硬件的随机数生成器),copy
不得实现。
关于 1),rand
方法可能会自动工作,但它并没有正式支持,并且在后续版本中可能会在没有警告的情况下中断。
要为假设的 MyRNG
生成器定义一个新的 rand
方法,以及一个值规范 s
(例如 s == Int
,或 s == 1:10
),类型为 S==typeof(s)
或 S==Type{s}
如果 s
是一个类型,则必须定义与之前看到的相同的两个方法:
Sampler(::Type{MyRNG}, ::S, ::Repetition)
,返回一个类型为SamplerS
的对象。rand(rng::MyRNG, sp::SamplerS)
可能会发生 Sampler(rng::AbstractRNG, ::S, ::Repetition)
已经在 Random
模块中定义。在这种情况下,如果想要针对这个特定的 RNG 类型专门化生成,可以在实践中跳过第 1 步,但相应的 SamplerS
类型被视为内部细节,可能会在没有警告的情况下更改。
Specializing array generation
在某些情况下,对于给定的随机数生成器类型,生成随机值数组使用专门的方法可能比仅仅使用之前解释的解耦技术更有效。例如,MersenneTwister
就是这种情况,它本地将随机值写入数组。
要为 MyRNG
实现此特化,并为规范 s
生成类型为 S
的元素,可以定义以下方法:rand!(rng::MyRNG, a::AbstractArray{S}, ::SamplerS)
,其中 SamplerS
是由 Sampler(MyRNG, s, Val(Inf))
返回的采样器的类型。可以仅为子类型实现该功能,例如 Array{S}
,而不是 AbstractArray
。rand
的非变异数组方法将自动在内部调用此特化。
Reproducibility
通过使用用给定种子初始化的RNG参数,您可以在多次运行程序时重现相同的伪随机数序列。然而,Julia的一个小版本更新(例如从1.3到1.4)可能会改变从特定种子生成的伪随机数序列,特别是如果使用MersenneTwister
。 (即使低级函数如rand
生成的序列没有变化,高级函数如randsubseq
的输出可能会因算法更新而变化。)理由:保证伪随机流永不改变会阻止许多算法改进。
如果您需要保证随机数据的精确可重现性,建议您简单地 保存数据(例如,作为科学出版物中的补充附件)。 (当然,您也可以指定特定的 Julia 版本和包清单,特别是如果您需要位级可重现性。)
依赖于特定“随机”数据的软件测试通常也应该保存数据,将其嵌入到测试代码中,或使用第三方包,如 StableRNGs.jl。另一方面,应该对大多数随机数据通过的测试(例如,测试 A \ (A*x) ≈ x
对于随机矩阵 A = randn(n,n)
)可以使用具有固定种子的随机数生成器,以确保简单地多次运行测试不会由于非常不可能的数据(例如,极度病态的矩阵)而遇到失败。
从中抽取随机样本的统计分布在任何小版本的Julia发布中是保证相同的。