【问题标题】:How should I move from PRNG based generation to hash-based procedural generation?我应该如何从基于 PRNG 的生成转移到基于散列的程序生成?
【发布时间】:2017-10-19 13:52:33
【问题描述】:

我想将现有的基于随机数的数据生成器(在 Python 中)替换为基于哈希的数据生成器,这样它就不再需要按照 this article 的启发按顺序生成所有内容。

我可以通过取散列的整数版本并将其除以散列的最大值来创建一个从 0 到 1 的浮点数。

我可以通过取浮点数并乘以平面范围来创建一个平面整数范围。我可能会使用模数并忍受偏差,因为哈希范围很大,而我的平面范围很小。

如何使用哈希来创建高斯或正态分布的浮点值?

对于所有这些情况,我最好将我的哈希用作新 random.Random 对象的种子并使用该类中的函数来生成我的数字并依靠它们来获得正确的分布特征?

目前,我的代码结构如下:

num_people = randint(1,100)
people = [dict() for x in range(num_people)]
for person in people:
    person['surname'] = choice(surname_list)
    person['forename'] = choice(forename_list)

问题在于,要使给定的种子保持一致,我必须以相同的顺序生成所有人,并且必须先生成姓氏然后再生成名字。如果我在两者之间添加一个中间名,那么生成的名字将会改变,所有后续人的名字也会改变。

我想这样构造代码:

h1_groupseed=1

h2_peoplecount=1
h2_people=2

h4_surname=1
h4_forename=2

num_people = pghash([h1_groupseed,h2_peoplecount]).hashint(1,100)
people = [dict() for x in range(num_people)]
for h3_index, person in enumerate(people,1):
    person['surname'] = surname_list[pghash([h1_groupseed,h2_people,h3_index,h4_surname]).hashint(0, num_of_surnames - 1)]
    person['forename'] = forename_list[pghash([h1_groupseed,h2_people,h3_index,h4_forename]).hashint(0, num_of_forenames - 1)]

这将使用传递给 pghash 的值来生成散列,并使用该散列以某种方式创建伪随机结果。

【问题讨论】:

  • 你为什么要这样做?
  • 您可以使用 Box Muller 变换将均匀分布的变量变为正态变量。 en.wikipedia.org/wiki/Box%E2%80%93Muller_transform
  • @ReblochonMasque 因为我想让数据生成器对属性生成顺序的更改具有鲁棒性。
  • 仍然不清楚原因(我无法将第一句话映射到关于原因的最后一条评论;更不用说理解其中任何一个了)。如果做对了(位 -> 浮动),这也可能比您想做的工作更多。
  • 您能否展示一些简单的代码来演示如何使用此功能?

标签: python random hash


【解决方案1】:

除非您这样做是为了自娱自乐或作为学习练习,否则我非常强烈的建议是不要这样做

PRNG 具有相同的总体结构,即使细节大相径庭。他们通过一些函数 f 将种子值 s 映射到初始状态 SS←f(s );然后他们通过一些变换 h 来迭代状态:Si+1←h(Si);最后他们通过某个函数 g 将状态映射到输出 UUi←g(S )。 (对于简单的 PRNG,f() 或 g() 通常是恒等函数。对于更复杂的生成器,例如 Mersenne Twister,涉及更多。)

状态转换函数 h() 旨在将新状态均匀地分布在状态空间中。换句话说,它已经是一个散列函数,但还有一个额外的好处是,对于任何被广泛接受的生成器,它都经过专家的严格审查,具有良好的统计行为。

Mersenne Twister 是 Python 的默认 PRNG,已在数学上证明对于所有 k ≤ 623,k 元组是联合均匀分布的。我猜你选择的任何散列函数都不能做出这样的声明。此外,折叠函数 g() 应保持结果的一致性。您建议您“可以使用哈希的整数版本来创建一个平坦的数字范围,只需取模即可。”一般来说,这会引入modulo bias,因此您最终不会得到均匀分布的结果。

如果您坚持使用内置 PRNG,则没有理由不使用内置高斯生成器。如果您想为自己的娱乐而这样做,有很多资源会告诉您如何将制服映射到高斯。众所周知的方法包括Box-Muller 方法、Marsaglia's polar methodziggurat 方法。


更新

鉴于您在问题中提供的其他信息,我认为您想要的答案包含在 Python 文档的这部分random

这个模块提供的函数实际上是一个绑定的方法 random.Random 类的隐藏实例。你可以实例化你的 拥有 Random 实例以获取不共享状态的生成器。这个 对于多线程程序特别有用,可以创建不同的 每个线程的 Random 实例,并使用 jumpahead() 方法 使每个线程可能看到生成的序列 不要重叠。

听起来您希望为每个 person 提供单独的 Random 实例,彼此独立播种或具有同步但广泛分离的状态,如 random.jumpahead() 文档中所述。这是仿真建模师自 1950 年代初以来使用的方法之一,因此他们可以保持配置之间的可重复性,从而以公平的方式直接比较两个或多个系统。查看this article第二页关于“同步”的讨论,或者从this book chapter第8页开始,或者从大多数大学图书馆提供的数十种仿真教科书中选择任何一本,阅读关于“常见随机数字。” (我没有将您指向维基百科,因为它几乎没有提供有关此主题的详细信息。)

这是一个显示创建多个Random 实例的显式示例:

import random as rnd

print("two PRNG instances with identical seeding produce identical results:")
r1 = rnd.Random(12345)
r2 = rnd.Random(12345)
for _ in range(5):
    print([r1.normalvariate(0, 1), r2.normalvariate(0, 1)])

print("\ndifferent seeding yields distinct but reproducible results:")
r1 = rnd.Random(12345)
r2 = rnd.Random(67890)
for _ in range(3):
    print([r1.normalvariate(0, 1), r2.normalvariate(0, 1)])
print("\nresetting, different order of operations")
r1 = rnd.Random(12345)
r2 = rnd.Random(67890)
print("r1: ", [r1.normalvariate(0, 1) for _ in range(3)])
print("r2: ", [r2.normalvariate(0, 1) for _ in range(3)])

【讨论】:

  • 所以我应该使用内置的随机模块,但每次都使用哈希作为新鲜种子?那讲得通。我希望每次构建一个新的 Random 实例的成本不会太大。
  • @PhilHibbs 不!良好的分布特性来自于 PRNG 中内置的 h() 和 g() 转换,而不是来自播种。对于 Mersenne Twister 来说,播种是昂贵的,而且重复这样做实际上会损害 PRNG 设计师辛辛苦苦为您提供的分布属性。 (在 SO 上搜索所有“为什么随机不断给出相同值”类型的帖子。)不要重新播种,除非你真的很清楚自己在做什么并且有充分的理由这样做。
  • 我的"good reason" 是我不想每次都以完全相同的顺序生成所有数据。查看我刚刚添加的示例代码 - 您如何添加“中间名”属性,而所有后续人员对于给定的随机种子都具有完全不同的名称?
  • 我过去的处理方式是,每次添加一些新属性时,我都会在 Random 对象上使用 getstate/jumpahead/setstate,以免干扰后续的随机值。
  • 对于遇到此问题的任何人,请完全忽略此帖子,它是在不考虑上下文的​​情况下提出建议。超高质量的 PRNG 对于像这样的程序生成来说非常糟糕,尝试使用它们会带来比它解决的问题更多的问题。查找如何改用 xxHash 之类的东西。
【解决方案2】:

首先,一个重要的警告:不要滚动你自己的加密货币。 如果您出于安全目的尝试这样做,请不要这样做。

接下来,看看这个问题,它列出了几种方法来做你想做的事,即将随机统一变量转换为正常变量: Converting a Uniform Distribution to a Normal Distribution

【讨论】:

  • 我已经编辑了我的问题,使其与正态分布问题不同。
【解决方案3】:

我已经为 random.Random 类中的一些函数创建了一个简单的基于哈希的替换:

from __future__ import division
import xxhash
from numpy import sqrt, log, sin, cos, pi

def gaussian(u1, u2):
    z1 = sqrt(-2*log(u1))*cos(2*pi*u2)
    z2 = sqrt(-2*log(u1))*sin(2*pi*u2)
    return z1,z2

class pghash:
    def __init__(self, tuple, seed=0, sep=','):
        self.hex = xxhash.xxh64(sep.join(tuple), seed=seed).hexdigest()

    def pgvalue(self):
        return int(self.hex, 16)

    def pghalves(self):
        return self.hex[:8], self.hex[8:]

    def pgvalues(self):
        return int(self.hex[:8], 16), int(self.hex[8:], 16)

    def random(self):
        return self.value() / 2**64

    def randint(self, min, max):
        return int(self.random() * max + min)

    def gauss(self, mu, sigma):
        xx = self.pgvalues()
        uu = [xx[0]/2**32, xx[1]/2**32]
        return gaussian(uu[0],uu[1])[0]

下一步是检查我的代码并将所有对 random.Random 方法的调用替换为 pghash 对象。

我已经把它做成了一个模块,我希望在某个时候上传到 pypi: https://github.com/UKHomeOffice/python-pghash

【讨论】:

    猜你喜欢
    • 2018-08-31
    • 2020-02-27
    • 2011-10-01
    • 2017-11-12
    • 1970-01-01
    • 2019-12-19
    • 2011-04-10
    • 1970-01-01
    相关资源
    最近更新 更多