Spec-Zone.ru › Julia 1.9

Случайные числа

Генерация случайных чисел в Julia по умолчанию использует алгоритм Xoshiro256++ с состоянием на уровне Task. Другие типы генераторов случайных чисел (RNG) могут быть подключены путём наследования типа AbstractRNG; затем они могут использоваться для получения нескольких потоков случайных чисел.

Генераторы псевдослучайных чисел (PRNG), экспортированные пакетом Random, это:

  • TaskLocalRNG: маркер, представляющий использование текущего активного потока задач, детерминированно инициализированного из родительской задачи или путём RandomDevice (с системной случайностью) при запуске программы
  • Xoshiro: генерирует поток высококачественных случайных чисел с небольшим вектором состояния и высокой производительностью, используя алгоритм Xoshiro256++
  • RandomDevice: для энтропии, предоставляемой ОС. Может быть использован для криптографически безопасных случайных чисел (CS(P)RNG).
  • MersenneTwister: альтернативный высококачественный генератор PRNG, который был по умолчанию в старых версиях Julia, и также довольно быстрый, но требует больше места для хранения вектора состояния и генерации случайной последовательности.

Большинство функций, связанных с генерацией случайных чисел, принимают необязательный объект AbstractRNG в качестве первого аргумента. Некоторые также принимают спецификации размерности dims... (которые также могут быть заданы в виде кортежа) для генерации массивов случайных значений. В многопоточной программе, как правило, следует использовать разные объекты RNG для разных потоков или задач для обеспечения потоковой безопасности. Однако, начиная с Julia 1.3, генератор по умолчанию является потокобезопасным (используя генератор на уровне потока до версии 1.6, и на уровне задачи после этого).

Предоставленные 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.

Поскольку точный способ генерации случайных чисел считается деталью реализации, исправления ошибок и улучшения скорости могут изменить поток генерируемых чисел после изменения версии. Поэтому не рекомендуется полагаться на конкренный seed или сгенерированный поток чисел во время тестирования на единицу — вместо этого стоит проверять свойства самих методов.

Модуль случайных чисел

Random.RandomМодуль

Random

Поддержка генерации случайных чисел. Предоставляет rand, randn, AbstractRNG, MersenneTwister и RandomDevice.

Функции генерации случайных чисел

Base.randФункция

rand([rng=default_rng()], [S], [dims...])

Выбирает случайный элемент или массив случайных элементов из набора значений, указанного S; S может быть

  • индексируемой коллекцией (например, 1:9 или ('x', "y", :z) ),
  • объектом AbstractDict или AbstractSet ,
  • строкой (рассматривается как коллекция символов), или
  • типом: набор значений для выбора тогда эквивалентен typemin(S):typemax(S) для целых чисел (это не применимо к BigInt), к $[0, 1)$ для чисел с плавающей точкой и к $[0, 1)+i[0, 1)$ для комплексных чисел с плавающей точкой;

S по умолчанию равен Float64. Когда передаётся только один аргумент помимо необязательного rng и он является Tuple, он интерпретируется как коллекция значений (S ), а не как dims.

Поддержка S как кортежа требует по меньшей мере Julia 1.1.

Примеры

julia> rand(Int, 2)
2-element Array{Int64,1}:
 1339893410598768192
 1575814717733606317

julia> using Random

julia> rand(MersenneTwister(0), Dict(1=>2, 3=>4))
1=>2

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!Функция

rand!([rng=default_rng()], A, [S=eltype(A)])

Заполняет массив A случайными значениями. Если S задано (S может быть типом или коллекцией, см. rand для подробностей), значения выбираются случайным образом из S. Это эквивалентно copyto!(A, rand(rng, S, size(A))) , но без выделения нового массива.

Примеры

julia> rng = MersenneTwister(1234);

julia> rand!(rng, zeros(5))
5-element Vector{Float64}:
 0.5908446386657102
 0.7667970365022592
 0.5662374165061859
 0.4600853424625171
 0.7940257103317943

Random.bitrandФункция

bitrand([rng=default_rng()], [dims...])

Генерирует BitArray случайных булевых значений.

Примеры

julia> rng = MersenneTwister(1234);

julia> bitrand(rng, 10)
10-element BitVector:
 0
 0
 0
 0
 1
 0
 0
 0
 1
 1

Base.randnФункция

randn([rng=default_rng()], [T=Float64], [dims...])

Генерирует нормально распределённое случайное число типа T со средним значением 0 и стандартным отклонением 1. Необязательно, генерирует массив нормально распределённых случайных чисел. Модуль Base в настоящее время предоставляет реализацию для типов Float16, Float32 и Float64 (по умолчанию) и их комплексных аналогов. Когда тип аргумента является комплексным, значения выбираются из кругово-симметричного комплексного нормального распределения с дисперсией 1 (что соответствует тому, что действительная и мнимая части имеют независимое нормальное распределение со средним значением ноль и дисперсией 1/2).

Примеры

julia> using Random

julia> rng = MersenneTwister(1234);

julia> randn(rng, ComplexF64)
0.6133070881429037 - 0.6376291670853887im

julia> randn(rng, ComplexF32, (2, 3))
2×3 Matrix{ComplexF32}:
 -0.349649-0.638457im  0.376756-0.192146im  -0.396334-0.0136413im
  0.611224+1.56403im   0.355204-0.365563im  0.0905552+1.31012im

Random.randn!Функция

randn!([rng=default_rng()], A::AbstractArray) -> A

Заполняет массив A нормально распределёнными (среднее 0, стандартное отклонение 1) случайными числами. См. также функцию rand.

Примеры

julia> rng = MersenneTwister(1234);

julia> randn!(rng, zeros(5))
5-element Vector{Float64}:
  0.8673472019512456
 -0.9017438158568171
 -0.4944787535042339
 -0.9029142938652416
  0.8644013132535154

Random.randexpФункция

randexp([rng=default_rng()], [T=Float64], [dims...])

Генерирует случайное число типа T в соответствии с экспоненциальным распределением со масштабом 1. Необязательно, генерирует массив таких случайных чисел. Модуль Base в настоящее время предоставляет реализацию для типов Float16, Float32 и Float64 (по умолчанию).

Примеры

julia> rng = MersenneTwister(1234);

julia> randexp(rng, Float32)
2.4835055f0

julia> randexp(rng, 3, 3)
3×3 Matrix{Float64}:
 1.5167    1.30652   0.344435
 0.604436  2.78029   0.418516
 0.695867  0.693292  0.643644

Random.randexp!Функция

randexp!([rng=default_rng()], A::AbstractArray) -> A

Заполняет массив A случайными числами, следующими экспоненциальному распределению (со масштабом 1).

Примеры

julia> rng = MersenneTwister(1234);

julia> randexp!(rng, zeros(5))
5-element Vector{Float64}:
 2.4835053723904896
 1.516703605376473
 0.6044364871025417
 0.6958665886385867
 1.3065196315496677

Random.randstringФункция

randstring([rng=default_rng()], [chars], [len=8])

Создаёт случайную строку длины len, состоящую из символов из chars, по умолчанию — набор заглавных и строчных букв и цифр от 0 до 9. Необязательный аргумент rng задаёт генератор случайных чисел, см. Случайные числа.

Примеры

julia> Random.seed!(3); randstring()
"Lxz5hUwn"

julia> randstring(MersenneTwister(3), 'a':'z', 6)
"ocucay"

julia> randstring("ACGT")
"TGCTCCTC"

chars может быть любой коллекцией символов, типа Char или UInt8 (более эффективно), если rand может случайно выбирать символы из неё.

Подпоследовательности, перестановки и перемешивание

Random.randsubseqФункция

randsubseq([rng=default_rng(),] A, p) -> Vector

Возвращает вектор, состоящий из случайной подпоследовательности заданного массива A, где каждый элемент A включается (в порядке) с независимой вероятностью p. (Сложность линейна относительно p*length(A), поэтому эта функция эффективна даже если p мал, а A велик.) Технически, этот процесс известен как «выборка Бернулли» из A.

Примеры

julia> rng = MersenneTwister(1234);

julia> randsubseq(rng, 1:8, 0.3)
2-element Vector{Int64}:
 7
 8

Random.randsubseq!Функция

randsubseq!([rng=default_rng(),] S, A, p)

Подобно randsubseq, но результаты сохраняются в S (размер которого изменяется по мере необходимости).

Примеры

julia> rng = MersenneTwister(1234);

julia> S = Int64[];

julia> randsubseq!(rng, S, 1:8, 0.3)
2-element Vector{Int64}:
 7
 8

julia> S
2-element Vector{Int64}:
 7
 8

Random.randpermФункция

randperm([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(MersenneTwister(1234), 4)
4-element Vector{Int64}:
 2
 1
 4
 3

Random.randperm!Функция

randperm!([rng=default_rng(),] A::Array{<:Integer})

Строит в A случайную перестановку длины length(A). Необязательный аргумент rng указывает генератор случайных чисел (см. Случайные числа). Для случайной перестановки произвольного вектора, см. shuffle или shuffle!.

Примеры

julia> randperm!(MersenneTwister(1234), Vector{Int}(undef, 4))
4-element Vector{Int64}:
 2
 1
 4
 3

Random.randcycleФункция

randcycle([rng=default_rng(),] n::Integer)

Строит случайную циклическую перестановку длины n. Необязательный аргумент rng указывает генератор случайных чисел, см. Случайные числа. Тип элементов результата совпадает с типом n.

В Julia 1.1 randcycle возвращает вектор v с eltype(v) == typeof(n), в то время как в Julia 1.0 eltype(v) == Int.

Примеры

julia> randcycle(MersenneTwister(1234), 6)
6-element Vector{Int64}:
 3
 5
 4
 6
 1
 2

Random.randcycle!Функция

randcycle!([rng=default_rng(),] A::Array{<:Integer})

Строит в A случайную циклическую перестановку длины length(A). Необязательный аргумент rng указывает генератор случайных чисел, см. Случайные числа.

Примеры

julia> randcycle!(MersenneTwister(1234), Vector{Int}(undef, 6))
6-element Vector{Int64}:
 3
 5
 4
 6
 1
 2

Random.shuffleФункция

shuffle([rng=default_rng(),] v::AbstractArray)

Возвращает случайную перестановку копии v . Необязательный аргумент rng указывает генератор случайных чисел (см. Случайные числа). Для перестановки v на месте, см. shuffle!. Для получения случайно переставленных индексов, см. randperm.

Примеры

julia> rng = MersenneTwister(1234);

julia> shuffle(rng, Vector(1:10))
10-element Vector{Int64}:
  6
  1
 10
  2
  3
  9
  5
  7
  4
  8

Random.shuffle!Функция

shuffle!([rng=default_rng(),] v::AbstractArray)

Функция shuffle на месте: случайная перестановка v на месте, необязательно с указанием генератора случайных чисел rng.

Примеры

julia> rng = MersenneTwister(1234);

julia> shuffle!(rng, Vector(1:16))
16-element Vector{Int64}:
  2
 15
  5
 14
  1
  9
 10
  6
 11
  3
 16
  7
  4
 12
  8
 13

Генераторы (создание и инициализация)

Random.default_rngФункция

default_rng() -> rng

Возвращает глобальный генератор случайных чисел по умолчанию (RNG).

Генератор по умолчанию — это деталь реализации. В разных версиях Julia ожидайте, что генератор по умолчанию не всегда будет одинаковым, а также что он не будет возвращать одинаковую последовательность случайных чисел для заданного значения.

Эта функция была введена в Julia 1.3.

Random.seed!Функция

seed!([rng=default_rng()], seed) -> rng
seed!([rng=default_rng()]) -> rng

Переинициализирует генератор случайных чисел: rng даст воспроизводимую последовательность чисел только в том случае, если указано значение seed. Некоторые генераторы RNG не принимают значение seed, например RandomDevice. После вызова seed!, rng эквивалентен новому объекту, инициализированному тем же seed.

Если 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) # not reproducible
true

julia> rand(Random.seed!(rng), Bool) # not reproducible either
false

julia> rand(Xoshiro(), Bool) # not reproducible either
true

Random.AbstractRNGТип

AbstractRNG

Базовый тип для генераторов случайных чисел, таких как MersenneTwister и RandomDevice.

Random.TaskLocalRNGТип

TaskLocalRNG

Состояние TaskLocalRNG локализовано в задаче, а не в потоке. Оно инициализируется при создании задачи из состояния родительской задачи. Следовательно, создание задачи является событием, изменяющим состояние RNG родительской задачи.

Преимущества использования TaskLocalRNG заключается в высокой скорости и возможности воспроизводимых многопоточных симуляций (без гонок), независимо от решений планировщика задач. Пока количество потоков не используется для принятия решений о создании задач, результаты симуляции также независимы от доступного числа потоков/процессоров. Поток случайных чисел не должен зависеть от аппаратных особенностей, за исключением порядка байтов и, возможно, размера слова.

Использование или инициализация RNG любой задачи, отличной от возвращаемой current_task() — неопределенное поведение: оно будет работать в большинстве случаев и может иногда завершаться молчаливо.

Random.XoshiroТип

Xoshiro(seed)
Xoshiro()

Xoshiro256++ — быстрый псевдослучайный генератор, описанный Дэвидом Блэкманом и Себастьяно Вигна в «Scrambled Linear Pseudorandom Number Generators», ACM Trans. Math. Softw., 2021. Ссылка на реализацию доступна по адресу http://prng.di.unimi.it

Помимо высокой скорости, Xoshiro обладает небольшим объемом памяти, что делает его подходящим для приложений, где нужно хранить много разных состояний генератора на длительный срок.

Реализация Xoshiro в Julia имеет режим генерации больших блоков; это позволяет создавать новые виртуальные PRNG из родительского, и использует SIMD для параллельной генерации (т.е. поток блоков состоит из нескольких перекрывающихся экземпляров xoshiro). Виртуальные PRNG удаляются после обработки запроса на блок (и не должны вызывать выделение памяти).

Примеры

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Тип

MersenneTwister(seed)
MersenneTwister()

Создаёт объект генератора случайных чисел MersenneTwister . Разные объекты RNG могут иметь свои собственные значения seed, что может быть полезно для генерации разных потоков случайных чисел. Значение seed может быть целым неотрицательным числом или вектором целых чисел UInt32 . Если seed не указан, генерируется случайное значение (используя энтропию из системы). См. функцию seed! для переинициализации уже существующего объекта MersenneTwister.

Примеры

julia> rng = MersenneTwister(1234);

julia> x1 = rand(rng, 2)
2-element Vector{Float64}:
 0.5908446386657102
 0.7667970365022592

julia> rng = MersenneTwister(1234);

julia> x2 = rand(rng, 2)
2-element Vector{Float64}:
 0.5908446386657102
 0.7667970365022592

julia> x1 == x2
true

Random.RandomDeviceТип

RandomDevice()

Создаёт объект генератора случайных чисел RandomDevice. Два таких объекта всегда будут генерировать разные потоки случайных чисел. Энтропия извлекается из операционной системы.

Интеграция в API Random

Есть два, в основном ортогональных, способа расширения функциональности Random:

  1. генерация случайных значений пользовательских типов
  2. создание новых генераторов

API для 1) довольно функциональный, но относительно новый, поэтому в последующих версиях модуля Random он, возможно, потребует развития. Например, обычно достаточно реализовать один метод rand для того, чтобы все другие обычные методы работали автоматически.

API для 2) все еще примитивен и может потребовать от разработчика больше работы, чем строго необходимо, для поддержки обычных типов генерируемых значений.

Генерация случайных значений пользовательских типов

Генерация случайных значений для некоторых распределений может подразумевать различные компромиссы. Предвычисленные значения, такие как таблица алиасов для дискретных распределений или функции «сжатия» для одномерных распределений, могут значительно ускорить выборку. Сколько информации следует предварительно вычислить, может зависеть от количества значений, которые мы планируем извлечь из распределения. Кроме того, некоторые генераторы случайных чисел могут обладать определенными свойствами, которые различные алгоритмы могут использовать.

Модуль Random определяет настраиваемую структуру для получения случайных значений, которая может решить эти проблемы. Каждый вызов rand генерирует сэмплер, который можно настроить, учитывая вышеуказанные компромиссы, добавив методы к Sampler, которые, в свою очередь, могут обрабатывать генератор случайных чисел, объект, характеризующий распределение, и предложение о количестве повторений. В настоящее время для последнего используются Val{1} (для одного образца) и Val{Inf} (для произвольного количества), при этом Random.Repetition является псевдонимом для обоих.

Затем объект, возвращаемый Sampler, используется для генерации случайных значений. При реализации интерфейса случайной генерации для значения X, которое может быть сэмплировано, реализующий должен определить метод

rand(rng, sampler)

для конкретного sampler, возвращаемого Sampler(rng, X, repetition).

Сэмплеры могут быть произвольными значениями, реализующими rand(rng, sampler), но для большинства приложений могут быть достаточны следующие предопределённые сэмплеры:

  1. SamplerType{T}() может быть использован для реализации сэмплеров, которые берут образцы из типа T (например, rand(Int)). Это значение по умолчанию, возвращаемое Sampler для типов.

  2. SamplerTrivial(self) — это простой оболочечный класс для self, к которому можно получить доступ с помощью []. Это рекомендуемый сэмплер, когда не требуется предварительно вычисленная информация (например, rand(1:3)), и это значение по умолчанию, возвращаемое Sampler для значений.

  3. SamplerSimple(self, data) также содержит дополнительное поле data, которое может использоваться для хранения произвольных предварительно вычисленных значений, которые должны быть вычислены в специальном методе Sampler.

Мы предоставляем примеры для каждого из них. Мы предполагаем здесь, что выбор алгоритма не зависит от ГСЧ, поэтому в наших сигнатурах используется AbstractRNG.

Random.SamplerТип

Sampler(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Тип

SamplerType{T}()

Сэмплер для типов, не содержащий другой информации. Значение по умолчанию для Sampler при вызове с типами.

Random.SamplerTrivialТип

SamplerTrivial(x)

Создаёт сэмплер, который просто оборачивает заданное значение x. Это значение по умолчанию для значений. Значение eltype этого сэмплера равно eltype(x).

Рекомендуемый случай использования — сэмплирование значений без предварительно вычисленных данных.

Random.SamplerSimpleТип

SamplerSimple(x, data)

Создаёт сэмплер, который оборачивает заданное значение x и data. Значение eltype этого сэмплера равно eltype(x).

Рекомендуемый случай использования — сэмплирование значений с предварительно вычисленными данными.

Разделение предварительных вычислений и фактической генерации значений является частью API и также доступно пользователю. В качестве примера предположим, что rand(rng, 1:20) необходимо вызывать многократно в цикле: способ воспользоваться этим разделением следующий:

rng = MersenneTwister()
sp = Random.Sampler(rng, 1:20) # or Random.Sampler(MersenneTwister, 1:20)
for x in X
    n = rand(rng, sp) # similar to n = rand(rng, 1:20)
    # use n
end

Этот механизм также используется в стандартной библиотеке, например, в реализации генерации случайных массивов (как в rand(1:20, 10)).

Генерация значений из типа

Для типа T, в настоящее время предполагается, что если rand(T) определено, будет создан объект типа T. SamplerType — сэмплер по умолчанию для типов. Для определения случайной генерации значений типа T, необходимо определить метод rand(rng::AbstractRNG, ::Random.SamplerType{T}), который должен возвращать значения, которые ожидаются от rand(rng, T).

Рассмотрим следующий пример: мы реализуем тип Die, с переменным числом n сторон, пронумерованных с 1 до n. Мы хотим, чтобы rand(Die) генерировал Die со случайным числом сторон до 20 (и не менее 4):

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(MersenneTwister(0), Die)
Die(11)

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)

Простой сэмплер без предварительно вычисленных данных

Здесь мы определяем сэмплер для коллекции. Если предварительно вычисленные данные не требуются, его можно реализовать с помощью сэмплера SamplerTrivial, который, фактически, является значением по умолчанию для значений.

Для определения случайной генерации из объектов типа S, необходимо определить следующий метод: rand(rng::AbstractRNG, sp::Random.SamplerTrivial{S}). Здесь sp просто оборачивает объект типа S, к которому можно получить доступ через sp[]. Продолжая пример Die, мы теперь хотим, чтобы rand(d::Die) генерировал Int соответствующий одной из сторон d:

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.

Генерация значений для типа AbstractFloat

Типы AbstractFloat являются особым случаем, потому что по умолчанию случайные значения генерируются не во всей области типа, а в [0,1). Для T <: AbstractFloat необходимо реализовать следующий метод: Random.rand(::AbstractRNG, ::Random.SamplerTrivial{Random.CloseOpen01{T}})

Оптимизированный сэмплер с предварительно вычисленными данными

Рассмотрим дискретное распределение, где числа 1:n выбираются с заданными вероятностями, которые суммируются до единицы. Когда из этого распределения требуется много значений, самым быстрым методом является использование метода алиасов. Мы не будем здесь описывать алгоритм построения такой таблицы, но предположим, что он доступен в 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(disribution, make_alias_table(distribution.probabilities))
end

должны быть определены для возврата сэмплера с предварительно вычисленными данными, затем

function rand(rng::AbstractRNG, sp::SamplerSimple{<:DiscreteDistribution})
    draw_number(rng, sp.data)
end

будут использоваться для выбора значений.

Пользовательские типы сэмплеров

Тип 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), и использовать sp вместо die в любом вызове rand с участием rng. В упрощённом примере выше, 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 (рекомендуется использовать SamplerSimple как в примере Die, где x просто передаётся при определении метода Sampler). Аналогично, SamplerTrivial(x) isa Sampler{eltype(x)}.

В настоящее время доступен и другой вспомогательный тип Random.SamplerTag, но он считается внутренней частью API и может сломаться в любое время без надлежащих аннотаций об устаревании.

Использование различных алгоритмов для генерации скалярных или массивно-ориентированных данных

В некоторых случаях, если вы хотите сгенерировать только небольшое количество значений или большое количество значений, выбор алгоритма будет иметь значение. Это обрабатывается третьим параметром конструктора 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}}.

Создание новых генераторов

API пока не определён чётко, но в качестве правила:

  1. любой метод rand, производящий "базовые" типы (isbitstype целые и плавающие типы в Base), должен быть определён для этого конкретного генератора случайных чисел, если они необходимы;
  2. другие документированные методы rand , принимающие AbstractRNG, должны работать прямо из коробки (при условии, что реализованы методы из п. 1), на которых они опираются), но, конечно же, могут быть специализированы для этого генератора, если есть возможность оптимизации;
  3. copy для псевдогенераторов должен возвращать независимую копию, генерирующую точно такую же последовательность случайных чисел, что и оригинал, начиная с того момента, когда вызывается таким же способом. Если это не представляется возможным (например, для генераторов на основе аппаратного обеспечения), то copy не должен быть реализован.

Что касается п. 1), метод rand может случайно работать автоматически, но это не официально поддерживается и может сломаться без предупреждений в последующих выпусках.

Чтобы определить новый метод rand для гипотетического генератора MyRNG и спецификации значения s (например, s == Int, или s == 1:10) типа S==typeof(s) или S==Type{s}, если s — тип, должны быть определены те же два метода, что мы видели ранее:

  1. Sampler(::Type{MyRNG}, ::S, ::Repetition), который возвращает объект типа, скажем, SamplerS
  2. rand(rng::MyRNG, sp::SamplerS)

Может случиться, что Sampler(rng::AbstractRNG, ::S, ::Repetition) уже определён в модуле Random. В этом случае можно пропустить шаг 1) на практике (если нужно специализировать генерацию для этого конкретного типа генератора), но соответствующий тип SamplerS рассматривается как внутренняя деталь и может быть изменён без предупреждения.

Специализация генерации массивов

В некоторых случаях для данного типа генератора случайных чисел генерация массива случайных значений может быть более эффективной с помощью специализированного метода, чем просто использование описанной ранее техники разъединения. Это, например, относится к MersenneTwister, который напрямую записывает случайные значения в массив.

Для реализации этой специализации для MyRNG и спецификации s, производящей элементы типа S, может быть определён следующий метод: rand!(rng::MyRNG, a::AbstractArray{S}, ::SamplerS), где SamplerS — тип сэмплера, возвращаемого Sampler(MyRNG, s, Val(Inf)). Вместо AbstractArray, можно реализовать функциональность только для подтипа, например, Array{S}. Неизменяющий метод массива rand автоматически вызовет эту специализацию внутри.

Воспроизводимость

Используя параметр генератора случайных чисел, инициализированный заданным значением, можно воспроизвести ту же последовательность псевдослучайных чисел при многократном запуске программы. Однако незначительное обновление Julia (например, с 1.3 до 1.4) может изменить последовательность псевдослучайных чисел, сгенерированных из конкретного значения, особенно если используется MersenneTwister. (Даже если последовательность, производимая низкоуровневой функцией, как rand, не меняется, вывод функций более высокого уровня, таких как randsubseq, может измениться из-за обновлений алгоритмов.) Обоснование: гарантия, что потоки псевдослучайных чисел никогда не изменятся, препятствует многим улучшениям алгоритмов.

Если вам нужно гарантировать точную воспроизводимость случайных данных, рекомендуется просто сохранить данные (например, как дополнительный вложение в научной публикации). (Вы также можете, конечно, указать определённую версию Julia и манифест пакета, особенно если вам требуется точная воспроизводимость в битах.)

Тесты программного обеспечения, которые полагаются на конкретные "случайные" данные, также, как правило, должны либо сохранить данные, встроить их в код теста, либо использовать сторонние пакеты, такие как StableRNGs.jl. С другой стороны, тесты, которые должны пройти для большинства случайных данных (например, тестирование A \ (A*x) ≈ x для случайной матрицы A = randn(n,n) ), могут использовать генератор с фиксированным значением, чтобы гарантировать, что простой многократный запуск теста не вызовет сбой из-за очень маловероятных данных (например, чрезвычайно плохо обусловленной матрицы).

Статистическое распределение, из которого извлекаются случайные выборки, гарантируется неизменным в рамках любых незначительных обновлений Julia.

© 2009–2023 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.9/stdlib/Random/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API