我应该如何从基于PRNG的生成转移到基于散列的程序生成?

问题描述:

我想用基于散列的数据生成器(以Python)替换现有的基于随机数的数据生成器,以便它不再需要按顺序生成所有内容,如this article的启发。我应该如何从基于PRNG的生成转移到基于散列的程序生成?

我可以通过取整数版本的哈希值并将其除以哈希值的最大值来创建一个从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生成哈希值,并使用该散列以某种方式建立的伪随机结果。

+0

你为什么要这么做? –

+0

您可以使用Box Muller转换将均匀分布的变量更改为普通变量。 https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform – WNG

+0

@ReblochonMasque因为我想让数据生成器对属性生成顺序的更改具有鲁棒性。 – PhilHibbs

首先,一个很大的警告:不要卷起自己的CRYPTO。 如果您为了安全目的而尝试这样做,请不要。

接下来,看看这个问题,其中列出了几种方法可以做到你想要什么,即改变一个随机变量统一到一个正常的: Converting a Uniform Distribution to a Normal Distribution

+0

我编辑了我的问题,使其与正常分布问题不同。 – PhilHibbs

除非你这样做是为自己的娱乐或作为学习练习,我很强烈的建议是不要这样做

PRNGs具有相同的总体结构,即使细节是非常不同的。它们的种子值小号映射到经由一些函数f的初始状态小号小号← F(小号);然后它们通过一些变换ħ迭代状态:小号 i + 1的← H(小号);最后他们经由一些函数g的状态映射到输出ûù←克(小号)。 (对于简单的PRNG,f()或g()通常是标识函数。对于更复杂的发电机,如Mersenne Twister,涉及更多。)

状态转换函数h()用于在状态空间中均匀分布新状态。换句话说,它已经是一个哈希函数,但是对于任何广泛接受的生成器而言,它已经被专家严格审查以获得良好的统计行为。

Mersenne Twister,Python的默认PRNG,已经在数学上证明有k元组共同均匀地分布在所有k中。我猜测你选择的任何散列函数都不能做出这样的声明。另外,崩溃函数g()应该保持结果的一致性。你已经建议你“可以使用整数版本的散列来创建一个平坦的数字范围,只需通过取模。”一般来说,这将引入modulo bias,所以你不会最终得到均匀分布的结果。

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


UPDATE

鉴于你在你的问题中提供的其他信息,我觉得你想要的答案被包含在Python的文件本节random:提供

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

听起来像是要用于每个personRandom单独实例,接种彼此独立地或与作为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)]) 
+0

所以我应该使用内置的随机模块,但每次使用散列作为新鲜的种子?这就说得通了。我希望每次构建一个新的Random实例的代价不是太大。 – PhilHibbs

+0

@PhilHibbs不!良好的分配属性来自PRNG内置的h()和g()转换,而不是播种。播种费用对于Mersenne Twister来说是非常昂贵的,反复重复的操作实际上可能会损害PRNG设计人员竭力为您提供的分配属性。 (做一个搜索所有的“为什么随机继续给予相同的价值”类型的帖子在SO上。)不要重新编入,除非你真的知道你在做什么,并有一个很好的理由这样做。 – pjs

+0

我的[“很好的理由”](https://blogs.unity3d.com/2015/01/07/a-primer-on-repeatable-random-numbers/)是我不想生成所有每次数据的顺序完全一样。看看我刚刚添加的示例代码 - 如何添加“中间名”属性,而没有随后的所有人为给定的随机种子命名完全不同? – PhilHibbs

我已经取得了进展,并创建了一个简单基于散列的替代一些在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