【问题标题】:"Deterministic" pseudorandom number generation“确定性”伪随机数生成
【发布时间】:2018-05-20 14:05:05
【问题描述】:

我处于需要生成具有一定随机稀疏模式的非常大(~10^16 个元素)随机矩阵的情况。显然,存储所有这些元素是完全不可行的。在任何给定时间只需要少数元素,因此可以按需绘制它们 - 但是,一旦存储了元素,以后可能需要它,并且使用相同的值很重要。即,元素不能被丢弃并随机重绘 - 一旦随机元素被绘制,就需要保存。

根据问题本身,可能有一些聪明的方法可以解决这个问题,我不会深入探讨。但是,一位同事说,应该可以根据需要使用伪随机数生成器来确定性地生成这些随机数,该生成器的种子由矩阵中的索引给出,即使用i + N*j 作为矩阵的元素(i, j),其中矩阵大小为N*N。这不会调用rand() 函数,而是使用带有特定参数的底层伪随机函数来确定性地生成先前绘制的随机值。这样就不需要保存任何数字,并且可以根据需要确定性地重新绘制它们。

我对 PRG 的理解是,对于出现随机的数字序列,您必须修复种子。上述方法有意义吗?在我看来,这就像反复重新播种 PRG 并只取第一个元素。

【问题讨论】:

  • 评论不用于扩展讨论;这个对话是moved to chat
  • 你为什么不试试呢?看起来很简单。
  • 这听起来很宽泛而且不清楚。对我来说最重要的问题是:你想用这个矩阵做什么?需要哪些操作(仅索引/查找;算术)?考虑通常的稀疏矩阵格式,如 lil、dok、coo、csr 和 co。它们在允许(或者我们称之为:高效)操作以及处理重复值方面是不同的。
  • 您的用例非常专业,如果是我,我会设计自己的元素生成器。您的最后一段总结了这个问题:设计师希望通过在播种后获取一长串数字来使用他们的 RNG,而不是很多种子,每个种子一个数字。
  • 为此使用哈希。根据矩阵索引播种随机生成器,然后获得一个随机数是一个坏主意(即使它不是第一个随机数,而是第 1000 个)。通常,随机生成器产生的数字与初始种子有很强的相关性。

标签: python c++ matrix random


【解决方案1】:

不是一个精确的答案,但有一些尝试。

散列函数似乎是实现目标的一种简单而有效的方法。

Here 是关于整数到整数散列函数的一些好主意。

从这篇文章中我尝试了:

from numba import uint64, njit 
import pylab as pl

@njit(uint64(uint64,uint64))    
def hash64(i,j) :
    x= i + (j << 32)
    x = (x ^ (x >> 30)) * (0xbf58476d1ce4e5b9);
    x = (x ^ (x >> 27)) * (0x94d049bb133111eb);
    x = x ^ (x >> 31);
    return x;  

n=1000    
im=[[hash64(i,j) for i in range(n)] for j in range(n)]
pl.subplot(121)
pl.imshow(im)
pl.colorbar()
pl.subplot(122)
pl.hist(np.array(im).ravel(),bins=100)
pl.show()  

这个 numba hash64 函数在约 200 ns 内计算哈希码。

这个图(即使它什么也没显示)表明这个函数可能是一个很好的候选者。

相比之下,python 哈希函数 (hash((i,j)) on tuple) 没有通过测试:

这里是 Knuth 生成器:

还有一些基准测试:

In [61]: %timeit hash64(0,1)
215 ns ± 9.11 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [62]: %timeit np.random.seed(0+1<<30);a=np.random.randint(2**30)
4.18 µs ± 126 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [63]:%timeit  hash((0,1))
102 ns ± 19.5 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each) 

【讨论】:

  • 没有稀疏矩阵的链接。假设我们不想做涉及稀疏矩阵的最简单的任务之一:获取所有非零值(在#nnz 中是时间线性的):你会怎么做? (外部随机二元决策索引可能会生成一个稀疏矩阵,但找到 nnz 的成本与尝试所有 n*m 索引一样昂贵)。
  • @sascha 当我称它为“稀疏矩阵”时可能有点误导,因为这意味着我想使用这个矩阵来执行典型的稀疏矩阵运算。从形式上讲,它是一个矩阵,但它在我的问题中出现的唯一一次是带有向量的二次形式,并且一次只需要计算该二次形式的一部分。因此,虽然它是一个稀疏矩阵,但任何典型的矩阵运算都是不必要的(没有乘法)。我只需要能查到元素,元素(i,j)在问题中就有意义。
  • 这个散列是伪随机的吗?从某种意义上说,我可以决定绘制元素的分布?
  • @TheWind-UpBird 当然。但这也意味着需要一些操作。想象一个 coo 格式。对于您的非显式 PRNG 类存储任务来说,这可能是最简单的。在此四边形中,您将需要查询矩阵此行/列中有哪些条目(仅在此行/列中的 nnzs 的线性时间中)。例如,这对 coo 来说是一件很糟糕的事情。它不允许有效的索引(需要遍历整个矩阵的所有 nnz;即使在查找索引 i、v 时)。 (计算元素的一些基于分布的位置/值对我来说似乎是最不重要的问题)
  • @TheWind-UpBird 关于你的伪随机问题:目前还不清楚你想要什么。我从来没有见过一些分布模型。基本概念是:通过访问 [0,1) 中的统一数字(良好的哈希提供;在最好的情况下,加密哈希很慢),您始终可以生成任何分布,代价是使用超过一个统一的数字(例如,wiki 的采样高斯的文章用于专门的变体;逆变换采样用于给定 cdf 的一般较慢的方法)。
【解决方案2】:

基本上,您需要 1016 ~ 253 个由矩阵元素线性索引唯一确定的随机数。

最简单的随机数生成器是 64 位 Linear Congruential one。基本上任何 具有 63-64 位长度和完整周期的合理的将起作用。下面的代码 使用等于矩阵元素线性索引的种子实现 Knuth LCG。 LCG 的另一个优点是具有对数复杂度的超前跳跃 - jump ~ log2(N)

代码

import numpy as np

A = np.uint64(6364136223846793005) # multiplier
C = np.uint64(1442695040888963407) # increment
K = np.float64(5.421010862427522170037E-20) # 1/2^64

def K64(seed):
    """
    Knuth 64bit MMIX LCG, return uint64 in the range [0...2^64)
    """
    return A*np.uint64(seed) + C

def KLCG64(seed):
    """
    Knuth 64bit MMIX LCG, return float64 in the range [0.0...1.0)
    """
    #return np.float64(K64(seed))*K # here is proper implementation, but slower than inlined code below
    return np.float64(A*np.uint64(seed) + C)*K

def randomMat(i, j, N):
    """
    return random element of the matrix
    """
    return KLCG64(np.uint64(i) + np.uint64(j)*np.uint64(N))

np.seterr(over='ignore') # important! LCG relies on unsigned overflow for mod operation

print(randomMat(123738, 9276, 735111))

【讨论】:

  • 两个问题。 1)就生成的随机数之间的相关性而言,使用矩阵元素索引播种是否没有问题? 2) 超前复杂性是指在对数时间内产生第 N 个数字的能力?仅使用该属性并将整个事物视为一个顺序会更好吗?
  • @TheWind-UpBird 2. 跳转复杂度意味着给定当前种子(例如,位置 #0),您可以跳过 N 个数字的生成而不是 O(N) 时间(基本上,调用 RNG N次)使用 O(log2(N)) 算法并到达位置#N。它是种子独立算法。PCG random 具有更好的随机属性,但仍然保留了从 LCG 继承的这种特性。 Would it be better to just use that property and consider the whole thing one sequence? 当然,由你决定,但基本上你会放慢自己的速度 - 而不是单个 RNG 调用 (O(1)) 现在你有 O(log2N)) 调用。但应该可以工作,我相信
  • @TheWind-UpBird 1. 存在相关性,对此毫无疑问。但是存在的每个算法都存在相关性 - 毕竟,对于每个索引,都有一些预定且固定的指令集要执行,仅此而已。所以唯一的问题是你能容忍多少相关性。 LCG - PCG random (pcg-random.org) 的进一步发展是强化 LCG 但保留 log2(N) 跳转功能。 Python代码:github.com/rkern/pcg-python
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-07
  • 1970-01-01
  • 1970-01-01
  • 2014-05-18
  • 2012-02-12
  • 1970-01-01
  • 2012-09-01
相关资源
最近更新 更多