为了给出一些背景信息,我一直在用Java编写一个基本的Perlin噪声实现,当实现播种时,我遇到了一个我无法解释的错误。
每次为同一种子生成相同的随机权重向量,无论哪一组坐标是什么?我们根据原始种子和权重向量坐标的组合生成了一个新的种子(newSeed
),并将其用作权重随机化的种子,查询噪声级别。矢量运行:
rnd.setSeed(newSeed);
weight = new NVector(2);
weight.setElement(0, rnd.nextDouble() * 2 - 1);
weight.setElement(1, rnd.nextDouble() * 2 - 1);
weight.normalize()
NVector
是一个自制的矢量数学类。
然而,在运行时,程序产生了非常糟糕的噪音:
经过一番挖掘后,我发现每个向量的第一个元素非常相似(因此每个nextDouble()
调用后的第一个setSeed()
调用)导致向量中每个向量的第一个元素网格相似。
这可以通过运行来证明:
long seed = Long.valueOf(args[0]);
int loops = Integer.valueOf(args[1]);
double avgFirst = 0.0, avgSecond = 0.0, avgThird = 0.0;
double lastfirst = 0.0, lastSecond = 0.0, lastThird = 0.0;
for(int i = 0; i<loops; i++)
{
ran.setSeed(seed + i);
double first = ran.nextDouble();
double second = ran.nextDouble();
double third = ran.nextDouble();
avgFirst += Math.abs(first - lastfirst);
avgSecond += Math.abs(second - lastSecond);
avgThird += Math.abs(third - lastThird);
lastfirst = first;
lastSecond = second;
lastThird = third;
}
System.out.println("Average first difference.: " + avgFirst/loops);
System.out.println("Average second Difference: " + avgSecond/loops);
System.out.println("Average third Difference.: " + avgSecond/loops);
在程序参数指定的种子范围内调用setSeed()
方法后,生成第一,第二和第三随机数之间的平均差异;这对我来说是这些结果:
C:\java Test 462454356345 10000
Average first difference.: 7.44638117976783E-4
Average second Difference: 0.34131692827329957
Average third Difference.: 0.34131692827329957
C:\java Test 46245445 10000
Average first difference.: 0.0017196011123287126
Average second Difference: 0.3416750057190849
Average third Difference.: 0.3416750057190849
C:\java Test 1 10000
Average first difference.: 0.0021601598225344998
Average second Difference: 0.3409914232342002
Average third Difference.: 0.3409914232342002
在这里你可以看到第一个平均差异明显小于其余的差异,并且随着种子数量的增加似乎在减少。
因此,通过在设置权重向量之前向nextDouble()
添加一个简单的虚拟调用,我能够修复我的perlin噪声实现:
rnd.setSeed(newSeed);
rnd.nextDouble();
weight.setElement(0, rnd.nextDouble() * 2 - 1);
weight.setElement(1, rnd.nextDouble() * 2 - 1);
导致:
我想知道为什么第一次调用nextDouble()
(我没有检查其他类型的随机性)的这种不良变化发生和/或提醒人们注意这个问题。
当然,这可能只是我的实施错误,如果向我指出,我会很高兴。
答案 0 :(得分:29)
Random
类旨在成为伪随机数的低开销源。但是低开销的结果是#34;实现是从统计角度来看,数字流具有很长的完美特性。你遇到了一个不完美的地方。 Random
被记录为线性同余生成器,这些生成器的属性是众所周知的。
有多种方法可以解决这个问题。例如,如果你小心,你可以隐藏一些最明显的&#34;穷人&#34;特点。 (但是你会被建议进行一些统计测试。你不能看到第二张图像中添加的噪声中的非随机性,但它仍然存在。)
或者,如果您想要保证良好统计属性的伪随机数,那么您应该使用 SecureRandom
而不是 Random
。它具有明显更高的管理费用,但你可以放心,许多聪明的人&#34;将花费大量时间进行算法的设计,测试和分析。
最后,创建一个使用替代算法生成数字的Random
子类相对简单;见link。问题是您必须选择(或设计)并实施适当的算法。
将此称为&#34; 问题&#34;值得商榷。这是LCG的众所周知和理解的属性,并且LCG的使用是一种有意义的工程选择。人们想要低开销的PRNG,但低开销的PRNG具有较差的属性。 TANSTAAFL。
当然,这不是Oracle在Random
中考虑改变的事情。实际上,Random
类的javadoc清楚地说明了不改变的原因。
&#34;为了保证此属性,为类
Random
指定了特定的算法。为了Java代码的绝对可移植性,Java实现必须使用此处显示的所有算法Random
。&#34;
答案 1 :(得分:11)
这是已知问题。类似的种子将产生类似的少数第一个值。随机并没有真正被设计成以这种方式使用。您应该使用良好的种子创建实例,然后生成中等大小的序列&#34;随机&#34;号。
您目前的解决方案还可以 - 只要它看起来不错并且速度足够快。您还可以考虑使用散列/混合函数来解决您的问题(然后,可选地,使用输出作为种子)。例如,请参阅:Parametric Random Function For 2D Noise Generation
答案 2 :(得分:7)
将setSeed
移出循环。 Java的PRNG是一个线性同余生成器,因此保证用连续值对其进行播种,可以得到在循环迭代中相关的结果。
<强>附录强>
在跑出会议大门之前我把它打破了,现在有时间说明我上面说的话。
我写了一个Ruby脚本,它实现了Schrage的便携式素数模乘法线性同余生成器。我实例化了两个LCG副本,两个都是值为1.但是,在输出循环的每次迭代中,我根据循环索引重新设置了第二个副本。这是代码:
# Implementation of a Linear Congruential Generator (LCG)
class LCG
attr_reader :state
M = (1 << 31) - 1 # Modulus = 2**31 - 1, which is prime
# constructor requires setting a seed value to use as initial state
def initialize(seed)
reseed(seed)
end
# users can explicitly reset the seed.
def reseed(seed)
@state = seed.to_i
end
# Schrage's portable prime modulus multiplicative LCG
def value
@state = 16807 * @state % M
# return the generated integer value AND its U(0,1) mapping as an array
[@state, @state.to_f / M]
end
end
if __FILE__ == $0
# create two instances of LCG, both initially seeded with 1
mylcg1 = LCG.new(1)
mylcg2 = LCG.new(1)
puts " default progression manual reseeding"
10.times do |n|
mylcg2.reseed(1 + n) # explicitly reseed 2nd LCG based on loop index
printf "%d %11d %f %11d %f\n", n, *mylcg1.value, *mylcg2.value
end
end
以及它产生的输出:
default progression manual reseeding
0 16807 0.000008 16807 0.000008
1 282475249 0.131538 33614 0.000016
2 1622650073 0.755605 50421 0.000023
3 984943658 0.458650 67228 0.000031
4 1144108930 0.532767 84035 0.000039
5 470211272 0.218959 100842 0.000047
6 101027544 0.047045 117649 0.000055
7 1457850878 0.678865 134456 0.000063
8 1458777923 0.679296 151263 0.000070
9 2007237709 0.934693 168070 0.000078
列是迭代次数,后跟LCG生成的基础整数,以及缩放到范围(0,1)时的结果。左边的列显示了LCG在自己进行时的自然进展,而右边的集显示了每次迭代重新设置时会发生什么。