tf.contrib.distributions.SeedStream
Локальный генератор псевдослучайных чисел для усиления энтропии начального значения в семена для базовых операций.
tf.contrib.distributions.SeedStream(
seed, salt
)
Написание кода для выборки, который правильно устанавливает начальное значение генератора псевдослучайных чисел (ГПСЧ), оказывается неожиданно сложной задачей. Этот класс служит помощником для шаблона кодирования TensorFlow Probability, разработанного для предотвращения распространённых ошибок.
Пример
Типичная реализация генератора для распределения бета состоит в вычислении отношения гамма-распределения к самому себе плюс другое гамма-распределение. Этот фрагмент кода пытается это сделать, но содержит неожиданно распространённую ошибку:
def broken_beta(shape, alpha, beta, seed): x = tf.random.gamma(shape, alpha, seed=seed) y = tf.random.gamma(shape, beta, seed=seed) return x / (x + y)
Ошибка заключается в том, что два гамма-вычисления используют одно и то же начальное значение. Это приводит к тому, что они всегда производят одинаковые результаты, что, в свою очередь, заставляет этот фрагмент кода всегда возвращать 0.5. Поскольку это может происходить между уровнями абстракции, этот тип ошибки неожиданно легко допустить при работе с неизменяемыми начальными значениями.
Цели
TensorFlow Probability использует стиль кодирования, призванный устранить вышеописанный тип ошибок, не усугубляя другие. Цели этого стиля кодирования:
Поддержка воспроизводимости результатов (путём стимулирования начальных значений для всех операций с псевдослучайными числами).
Избегание общего состояния с одновременной записью (путем отказа от глобального ГПСЧ).
Предотвращение случайного повторного использования начального значения разработчиками TF Probability. Эта цель реализуется с помощью локального генератора псевдослучайных чисел, предоставляемого в этом модуле.
Смягчение потенциального случайного повторного использования начального значения клиентами TF Probability (с помощью схемы добавления соли).
Предотвращение случайных совпадений с ГПСЧ на более низких уровнях (путём хэширования результата).
Нецели
Реализация высокопроизводительного ГПСЧ для генерации больших объёмов энтропии. Это работа лежащего в основе TensorFlow ГПСЧ, которое мы используем.
Избегание столкновений начальных значений, т.е. "атаки дня рождения".
Шаблон кода
def random_beta(shape, alpha, beta, seed): # (a) seed = SeedStream(seed, salt="random_beta") # (b) x = tf.random.gamma(shape, alpha, seed=seed()) # (c) y = tf.random.gamma(shape, beta, seed=seed()) # (c) return x / (x + y)
Элементы этого шаблона:
-
Принимать явное начальное значение (строка а) в качестве аргумента во всех открытых функциях и писать функцию так, чтобы она была детерминированной (в пределах любых числовых проблем) для фиксированного начального значения.
- Обоснование: Это предоставляет клиенту возможность воспроизвести результаты. Принятие неизменяемого начального значения вместо изменяемого объекта ГПСЧ снижает связность кода, позволяя различным разделам быть воспроизводимыми независимо.
-
Использовать это начальное значение только для инициализации локального
SeedStreamэкземпляра (строка б).- Обоснование: Избегает случайного повторного использования начального значения.
Указывать имя реализуемой функции в качестве соли для экземпляра
SeedStream(строка б). Это служит для поддержания уникальности соли; уникальная соль гарантирует, что клиенты TF Probability будут видеть, что разные функции всегда производят независимые результаты, даже если вызываются с одинаковыми начальными значениями.Начинать каждую операцию вызываемого объекта с результата уникального вызова экземпляра
SeedStream(строки с). Это обеспечивает воспроизводимость результатов, одновременно предотвращая повторное использование начального значения в вызовах вызываемого объекта.
Зачем использовать соль?
Использование соли в экземплярах SeedStream (с уникальными солями) — это защитный программирование против случайной ошибки клиента, подобной нашему примеру. Рассмотрим следующую ситуацию, которая может возникнуть без добавления соли:
def tfp_foo(seed): seed = SeedStream(seed, salt="") foo_stuff = tf.random.normal(seed=seed()) ... def tfp_bar(seed): seed = SeedStream(seed, salt="") bar_stuff = tf.random.normal(seed=seed()) ... def client_baz(seed): foo = tfp_foo(seed=seed) bar = tfp_bar(seed=seed) ...
Клиент должен был использовать различные начальные значения в качестве входных данных для foo и bar. Однако, поскольку этого не было сделано, *и поскольку foo и bar оба внутренне используют выборку гауссова распределения в качестве своей первой операции*, внутренние foo_stuff и bar_stuff будут одинаковыми, а возвращаемые foo и bar не будут независимыми, что приведёт к несколько неверным ответам в симуляции клиента. Такая ошибка особенно коварна для клиента, потому что она зависит от реализации деталей распределений, а именно от порядка, в котором foo и bar вызывают зависящие от них генераторы. В частности, член команды Bayesflow может ввести такую ошибку в ранее (случайно) правильный клиентский код, выполнив внутреннюю рефакторинг, который приводит к выравниванию порядка этих операций.
Дисциплина использования соли устраняет эту проблему, гарантируя, что семена, увиденные вызываемыми объектами foo, будут отличаться от семян, увиденных вызываемыми объектами bar, даже если foo и bar вызываются с одним и тем же начальным значением.
| Аргументы | |
|---|---|
seed |
Любой Python-объект, преобразуемый в строку, обеспечивающий начальную энтропию. Если None, операции, начальные значения которых взяты из этого SeedStream, будут следовать семантике TensorFlow, не имея начального значения. |
salt |
Любой Python-объект, преобразуемый в строку, обеспечивающий дополнительную энтропию. Должен быть уникальным во всей базе кода Distributions и TensorFlow Probability. См. строку документации класса для обоснования. |
| Атрибуты | |
|---|---|
original_seed |
|
salt |
|
Методы
__call__
__call__()
Возвращает свежее целое число, пригодное для использования в качестве начального значения в последующих операциях.
Если этот SeedStream был инициализирован с seed=None, возвращает None. Это приводит к тому, что последующие операции (как вызовы SeedStream и примитивные операции TensorFlow) будут вести себя так, как будто они не были инициализированы начальным значением.
Возвращаемое целое число неотрицательно и равномерно распределено в полуоткрытом интервале [0, 2**512). Это согласуется с TensorFlow, поскольку операции TensorFlow внутренне используют остаток от деления заданного начального значения на 2**31 - 1 (см. tensorflow/python/framework/random_seed.py).
| Возвращаемое значение | |
|---|---|
seed |
Свежее целое число, пригодное для использования в качестве начального значения в последующих операциях, или None. |
© 2020 The TensorFlow Authors. All rights reserved.
Licensed under the Creative Commons Attribution License 3.0.
Code samples licensed under the Apache 2.0 License.
https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/contrib/distributions/SeedStream