如何测试随机性(以洗牌为例)

47

首先,这个问题取自于这个问题。我这样做是因为我认为这部分比长问题的子部分更重要。如果有冒犯之处,请见谅。

假设你有一个生成随机数的算法。那么你如何测试它呢? 或者更直接地说——假设你有一个洗牌算法,那么怎么测试它是完全随机的算法呢?

为了给问题加点理论背景——一副牌可以按52!(52的阶乘)种不同的方式洗牌。拿一副牌,手动洗牌并记录所有牌的顺序。你得到刚好那个顺序的概率是多少?答案是:1 / 52!。

在洗牌后,你得到每个花色的A、K、Q、J……连续排列的机会是多少?答案是 1 / 52!。

因此,仅仅洗牌一次然后观察结果对于判断洗牌算法的随机性没有任何信息价值。两次洗牌获得更多信息,三次甚至更多……

你会如何对一个洗牌算法进行黑盒测试以确定其随机性?

11个回答

30

统计学。 在测试随机数生成器方面,事实上的标准是Diehard套件(最初在http://stat.fsu.edu/pub/diehard上提供)。 或者,Ent程序提供的测试更简单易懂,但不太全面。

至于洗牌算法,请使用众所周知的算法,如Fisher-Yates(又称“Knuth Shuffle”)。 只要底层随机数生成器是均匀随机的,洗牌就会均匀随机。 如果您使用的是Java,此算法可以在标准库中找到(请参见Collections.shuffle)。

对于大多数应用程序来说可能并不重要,但是请注意,大多数随机数生成器无法提供足够的自由度来产生52张纸牌的每个可能的排列(解释在此处)。


1
看起来FSU已经删除了Diehard网站。有一个叫做Dieharder的Duke GPL'd发行版类似工具。Dieharder - Matt
您可以查看存档版本 https://web.archive.org/web/20160125103112/http://stat.fsu.edu/pub/diehard/ - Ender

7
这里有一个简单的检验方法,可以使用生成的随机数来估计 Pi。它并不能证明随机性,但是差劲的 RNG 通常无法通过它(它们会返回类似于 2.5 或 3.8 而不是 ~3.14)。
理想情况下,这只是您运行检查随机性的许多测试之一。
还有另外一件事,您可以检查输出的标准偏差。在范围为 0..n 的均匀分布值的总体中,预期标准偏差接近 n/sqrt(12)。
/**
 * This is a rudimentary check to ensure that the output of a given RNG
 * is approximately uniformly distributed.  If the RNG output is not
 * uniformly distributed, this method will return a poor estimate for the
 * value of pi.
 * @param rng The RNG to test.
 * @param iterations The number of random points to generate for use in the
 * calculation.  This value needs to be sufficiently large in order to
 * produce a reasonably accurate result (assuming the RNG is uniform).
 * Less than 10,000 is not particularly useful.  100,000 should be sufficient.
 * @return An approximation of pi generated using the provided RNG.
 */
public static double calculateMonteCarloValueForPi(Random rng,
                                                   int iterations)
{
    // Assumes a quadrant of a circle of radius 1, bounded by a box with
    // sides of length 1.  The area of the square is therefore 1 square unit
    // and the area of the quadrant is (pi * r^2) / 4.
    int totalInsideQuadrant = 0;
    // Generate the specified number of random points and count how many fall
    // within the quadrant and how many do not.  We expect the number of points
    // in the quadrant (expressed as a fraction of the total number of points)
    // to be pi/4.  Therefore pi = 4 * ratio.
    for (int i = 0; i < iterations; i++)
    {
        double x = rng.nextDouble();
        double y = rng.nextDouble();
        if (isInQuadrant(x, y))
        {
            ++totalInsideQuadrant;
        }
    }
    // From these figures we can deduce an approximate value for Pi.
    return 4 * ((double) totalInsideQuadrant / iterations);
}

/**
 * Uses Pythagoras' theorem to determine whether the specified coordinates
 * fall within the area of the quadrant of a circle of radius 1 that is
 * centered on the origin.
 * @param x The x-coordinate of the point (must be between 0 and 1).
 * @param y The y-coordinate of the point (must be between 0 and 1).
 * @return True if the point is within the quadrant, false otherwise.
 */
private static boolean isInQuadrant(double x, double y)
{
    double distance = Math.sqrt((x * x) + (y * y));
    return distance <= 1;
}

我喜欢。虽然不是完美洗牌问题的解决方案,但是作为一个很好的起点。点个赞 :) - Tnilsson
isInQuadrant() 中不需要使用 Math.sqrt() - jfs
除了所有额外的处理之外,这与仅计算随机数范围的50%高/低有何不同? - JoeBloggs

6
首先,无法确定某个有限输出是否“真正随机”,因为如您所指出,任何输出都是可能的
可以做的是,获取一系列输出并检查该序列的各种测量结果与更可能的结果之间的差异。您可以得出一种置信度分数,表明生成算法正在很好地工作。
例如,您可以检查10个不同的洗牌输出。为每张牌分配一个0-51的数字,并取每次洗牌中位置6上的牌的平均值。收敛平均值为25.5,因此在这里看到1的值会令人惊讶。您可以使用中心极限定理来估计每个位置的每个平均值的可能性。
但我们不应该止步于此!因为这个算法可能会被一个只交替两个旨在给出每个位置精确平均值为25.5的洗牌的系统所欺骗。我们怎么能做得更好呢?
我们期望每个位置上的牌都有一个均匀分布(任何一张牌具有相等的可能性),在不同的洗牌中也是如此。因此,在这10个洗牌中,我们可以尝试验证选择是否“均匀”。这基本上只是原问题的简化版本。您可以检查标准差是否合理,最小值是否合理,以及最大值是否合理。您还可以检查其他值,例如最接近两张牌(按我们分配的数字),是否也合理。
但是我们也不能无限制地添加各种测量,因为在足够的统计数据下,任何特定的洗牌都会因某种原因看起来高度不可能(例如,这是其中很少几个洗牌之一,其中卡片X、Y、Z按顺序出现)。因此,重要的问题是:哪些是正确的测量集?在这里,我不得不承认我不知道最好的答案。然而,如果您有特定的应用程序,请选择一组好的属性/测量值进行测试,并与其一起工作-这似乎是密码学家处理事情的方式。

4
唯一测试随机性的方法是编写一个程序,尝试构建数据的预测模型,然后使用该模型尝试预测未来的数据,并显示其预测的不确定性或熵随时间趋于最大(即均匀分布)。当然,您始终无法确定您的模型是否捕获了所有必要的上下文;对于给定的模型,始终可以构建第二个模型,生成在第一个模型中看起来随机的非随机数据。但只要您接受冥王星的轨道对洗牌算法的结果影响微不足道,那么您应该能够满意地确认其结果是可接受的随机的。
当然,如果这样做,您可能会使用您的模型进行生成,以实际创建所需的数据。如果您这样做,那么您又回到了起点。

4

有很多关于测试随机性的理论。对于一个非常简单的卡牌洗牌算法测试,您可以进行大量的洗牌,然后运行卡方检验,以确保每张卡牌出现在任何位置的概率是均匀的。但这并不能测试连续的卡牌是否相关,因此您还需要进行相关测试。

Knuth的《计算机程序设计艺术》第2卷在3.3.2节(经验测试)和3.3.4节(频谱测试)中给出了许多可用的测试及其背后的理论。


2

大量洗牌,然后记录结果(如果我理解正确的话)。我记得看到过“随机数生成器”的比较。他们只是一遍又一遍地测试,然后绘制图表显示结果。

如果它真的是随机的,那么图表应该基本上是均匀的。


图表。使用大量的图表。使用散点图来确保没有模式,然后计算每个组合出现的次数,以确保它在时间上(几乎)均匀分布。使用数学更准确地确定没有模式,但数学很难。 - Andrew

0

目前还没有代码,因此我从我的回答中复制粘贴了一个测试部分到原始问题中。

  // ...
  int main() {
    typedef std::map<std::pair<size_t, Deck::value_type>, size_t> Map;
    Map freqs;    
    Deck d;
    const size_t ntests = 100000;

    // compute frequencies of events: card at position
    for (size_t i = 0; i < ntests; ++i) {
      d.shuffle();
      size_t pos = 0;
      for(Deck::const_iterator j = d.begin(); j != d.end(); ++j, ++pos) 
        ++freqs[std::make_pair(pos, *j)]; 
    }

    // if Deck.shuffle() is correct then all frequencies must be similar
    for (Map::const_iterator j = freqs.begin(); j != freqs.end(); ++j)
      std::cout << "pos=" << j->first.first << " card=" << j->first.second 
                << " freq=" << j->second << std::endl;    
  }

这段代码并不测试底层伪随机数生成器的随机性。测试PRNG的随机性是整个科学领域的一个分支。


0

对于快速测试,您可以尝试压缩它。一旦无法压缩,那么您可以进行其他测试。

我已经尝试了dieharder,但它拒绝为洗牌工作。所有测试都失败了。它也非常呆板,不允许您指定所需值的范围或类似的任何内容。


0

我不完全理解你的问题。你说:

假设您有一个生成随机性的算法。现在该如何测试它?

您是什么意思?如果您认为您可以生成随机性,那么就没有必要进行测试。

一旦您拥有了一个好的随机数生成器,创建随机排列就很容易了(例如:将您的牌命名为1-52。生成52个随机数,将每个随机数分配给一张牌,并按照您的52个随机数进行排序)。通过生成排列,您不会破坏您的好RNG的随机性。

困难的问题是您是否可以信任您的RNG。 这是一个讨论特定情境中这个问题的样例链接。


2
嘿,那么澄清一下。"假设您有一个算法,您相信它能生成随机性。" - Tnilsson
好的。我并不是想要表现得讽刺。我真的不知道你是在问“如何测试随机性”,这可以在不涉及洗牌的情况下询问,还是在问“如何测试我的洗牌算法是否破坏了我的良好随机数生成器”。 - Baltimark

0

当然,测试52!种可能性是不可能的。相反,尝试在较少的牌数上进行洗牌,比如3、5和10张牌。然后,您可以测试数十亿次洗牌,并使用直方图和卡方统计检验来证明每个排列出现的次数是“均匀”的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接