两副牌之间的相关性?

机器算法验证 相关性 皮尔逊-r 信息论
2022-03-23 18:17:29

我编写了一个程序来模拟上手洗牌。

每张牌都有编号,花色从俱乐部、钻石红心黑桃开始,等级从二到十,然后是杰克、皇后、国王和王牌。因此,梅花二的数字是 1,梅花三的数字是 2 ....梅花 A 是 13... 黑桃 A 是 52。

确定牌的洗牌程度的方法之一是将其与未洗牌的牌进行比较,并查看牌的顺序是否相关。

也就是说,我可能有这些卡片,与未洗牌的卡片进行比较:

未洗牌 洗牌 未洗牌号码 洗牌号码
两个俱乐部 俱乐部三 1 2
俱乐部三 两个俱乐部 2 1
俱乐部之四 俱乐部之五 3 4
俱乐部之五 俱乐部之四 4 3

Pearson 方法的相关性为:0.6

使用大量卡片(全部 52 张),您可能会看到模式出现。我的假设是,经过更多的洗牌,你会得到更少的相关性。

但是,有很多方法可以衡量相关性。

我已经尝试过 Pearson 的相关性,但我不确定这是否是在这种情况下使用的正确相关性。

这是一个合适的相关性度量吗?有更合适的衡量标准吗?

奖励积分我有时会在我的结果中看到此类数据:

样本卡相关性导致图形具有多条具有不同梯度的单独线

显然存在一些相关性,因为有多个具有不同梯度的单独线,但我不知道您如何测量单独的“趋势线”?

2个回答

您可以使用所有相邻卡片对之间面值差异的香农熵来测量相关性的相对水平(或更准确地说,随机性的增加水平) 。

下面是如何计算它,对于一副随机洗牌的 52 张牌。您首先在整个甲板上循环一次,然后构建一种直方图。对于每个卡片位置,计算面值差为了更具体,假设第位置的牌是黑桃 K,第位置的牌是梅花 4。然后我们有当您到达时,这是一种特殊情况;你再次循环回到牌组的开头并取i=1,2,...,52ΔFi=Fi+1Fi(i+1)iFi+1=51Fi=3ΔFi=513=48i=52ΔF52=F1F52. 的负数,则添加 52 以使面值差异回到 1-52 的范围内。ΔF

您最终会得到一组 52 对相邻卡片的面值差异,每对都落在 1-52 的允许范围内;使用具有 52 个元素的直方图(即一维数组)计算这些的相对频率。直方图记录了牌组的一种“观察到的概率分布”;您可以通过将每个 bin 中的计数除以 52 来标准化此分布。因此,您最终会得到一系列变量,其中每个变量都可能具有离散可能值的范围:{0、1/52、2/52、3/52 等},取决于有多少成对的面值差异随机地出现在直方图的特定 bin 中。p1,p2,...p52

获得直方图后,您可以计算特定 shuffle 迭代的香农熵

E=k=152pkln(pk)
我在 R 中编写了一个小型模拟来演示结果。第一个图显示了熵在 20 次 shuffle 迭代过程中如何演变。值 0 与完全有序的牌组相关联;较大的值表示逐渐变得更加无序或去相关的牌组。第二个图显示了一系列 20 个方面,每个方面都包含一个类似于最初包含在问题中的图,显示洗牌顺序与初始牌顺序。第二个图中的 20 个方面与第一个图中的 20 次迭代相同,并且它们的颜色编码也相同,这样您就可以直观地感受到什么级别的香农熵对应于多少随机性排序顺序。生成图的模拟代码附在最后。

香农信息熵与随机迭代

洗牌顺序与开始顺序的 20 次洗牌迭代,显示卡片的相关性逐渐降低,并且随着时间的推移更加随机分布。

library(ggplot2)

# Number of cards
ncard <- 52 
# Number of shuffles to plot
nshuffle <- 20
# Parameter between 0 and 1 to control randomness of the shuffle
# Setting this closer to 1 makes the initial correlations fade away
# more slowly, setting it closer to 0 makes them fade away faster
mixprob <- 0.985 
# Make data frame to keep track of progress
shuffleorder <- NULL
startorder <- NULL
iteration <- NULL
shuffletracker <- data.frame(shuffleorder, startorder, iteration)

# Initialize cards in sequential order
startorder <- seq(1,ncard)
shuffleorder <- startorder

entropy <- rep(0, nshuffle)
# Loop over each new shuffle
for (ii in 1:nshuffle) {
    # Append previous results to data frame
    iteration <- rep(ii, ncard)
    shuffletracker <- rbind(shuffletracker, data.frame(shuffleorder,
                            startorder, iteration))
    # Calculate pairwise value difference histogram
    freq <- rep(0, ncard)
    for (ij in 1:ncard) {
        if (ij == 1) {
            idx <- shuffleorder[1] - shuffleorder[ncard]
        } else {
            idx <- shuffleorder[ij] - shuffleorder[ij-1]
        }
        # Impose periodic boundary condition
        if (idx < 1) {
            idx <- idx + ncard
        }
        freq[idx] <- freq[idx] + 1
    }
    # Sum over frequency histogram to compute entropy
    for (ij in 1:ncard) {
        if (freq[ij] == 0) {
            x <- 0
        } else {
            p <- freq[ij] / ncard
            x <- -p * log(p, base=exp(1))
        }
        entropy[ii] <- entropy[ii] + x
    }
    # Shuffle the cards to prepare for the next iteration
    lefthand <- shuffleorder[floor((ncard/2)+1):ncard]
    righthand <- shuffleorder[1:floor(ncard/2)]
    ij <- 0
    ik <- 0
    while ((ij+ik) < ncard) {
        if ((runif(1) < mixprob) & (ij < length(lefthand))) {
            ij <- ij + 1
            shuffleorder[ij+ik] <- lefthand[ij]
        }
        if ((runif(1) < mixprob) & (ik < length(righthand))) {
            ik <- ik + 1
            shuffleorder[ij+ik] <- righthand[ik]
        }
    }
}
# Plot entropy vs. shuffle iteration
iteration <- seq(1, nshuffle)
output <- data.frame(iteration, entropy)
print(qplot(iteration, entropy, data=output, xlab="Shuffle Iteration", 
            ylab="Information Entropy", geom=c("point", "line"),
            color=iteration) + scale_color_gradient(low="#ffb000",
            high="red"))

# Plot gradually de-correlating sort order
dev.new()
print(qplot(startorder, shuffleorder, data=shuffletracker, color=iteration,
            xlab="Start Order", ylab="Shuffle Order") + facet_wrap(~ iteration,
            ncol=4) + scale_color_gradient(low="#ffb000", high="red"))

我知道这篇文章已经快 4 年了,但我是一名密码分析爱好者,并且一直在研究扑克牌密码因此,我一遍又一遍地回到这篇文章,解释牌组洗牌是随机键控牌组的熵源。最后,我决定通过手动洗牌并估计每次洗牌后的牌组熵来通过 stachyra 来验证答案。

TL;DR,最大化甲板熵:

  • 对于仅浅滩洗牌,您需要 11-12 次洗牌。
  • 对于先切牌然后洗牌,您只需要 6-7 次切牌和洗牌。

首先,stachyra 提到的用于计算香农熵的所有内容都是正确的。可以这样归结:

  1. 为一副牌中的 52 张卡片中的每一张数字分配一个唯一值。
  2. 洗牌。
  3. 对于 n=0 到 n=51,记录 (n - (n+1) mod 52) mod 52 的每个值
  4. 计算 0、1、2、...、49、50、51 的出现次数
  5. 通过将每个记录除以 52 来规范化这些记录
  6. 对于 i=1 到 i=52,计算 -p_i * log(p_i)/log(2)
  7. 对值求和

stachyra 做了一个微妙的假设,那就是在计算机程序中实现人类洗牌会带来一些包袱。使用纸质纸牌时,手上的油会转移到纸牌上。在很长一段时间内,由于油的积累,卡片会开始粘在一起,这最终会出现在你的洗牌中。牌组使用得越频繁,两张或多张相邻的牌就越有可能粘在一起,而且发生的频率也越高。

此外,假设俱乐部和红心杰克两者粘在一起。在你洗牌的过程中,它们最终可能会粘在一起,永远不会分开。这可以在计算机程序中进行模仿,但 stachyra 的 R 例程并非如此。

此外,stachyra 有一个操作变量“mixprob”。在没有完全理解这个变量的情况下,它有点像一个黑匣子。您可能会错误地设置它,从而影响结果。所以,我想确保他的直觉是正确的。所以我手动验证了它。

在两种不同的情况下(总共 40 次洗牌),我手动洗牌了 20 次。在第一种情况下,我只是洗牌,保持左右切口接近均匀。在第二种情况下,我故意将牌组从牌组的中间(1/3、2/5、1/4 等)剪开,然后再进行均匀剪裁以进行 riffle shuffle。在第二种情况下,我的直觉是,通过在洗牌前切割牌组,并远离中间,我可以比普通洗牌更快地将扩散引入牌组。

这是结果。首先,直式洗牌:

每张卡的熵与浅滩洗牌

这里是切牌结合浅滩洗牌:

每张卡的熵与切割和浅滩洗牌

似乎熵在 stachyra 声称的大约 1/2 时间内最大化。此外,我的直觉是正确的,首先故意将甲板从中间切开,然后再洗牌确实将更多的扩散引入甲板。然而,在大约 5 次洗牌之后,它不再那么重要了。您可以看到,在大约 6-7 次洗牌后,熵最大化,而 10-12 次是我声称的 stachyra。有没有可能 7 次洗牌就足够了,还是我被蒙蔽了?

您可以在 Google 表格中查看我的数据有可能我记错了一两张扑克牌,所以我不能保证数据 100% 准确。

您的发现也必须经过独立验证,这一点很重要。哈佛大学数学系的 Brad Mann 研究了在牌组中任何一张牌的可预测性完全不可预测(香农熵最大化)之前,将一副牌洗牌需要多少次。他的结果可以在这个 33 页的 PDF中找到。

他的发现有趣的是,他实际上是在独立验证Persi Diaconis 1990 年《纽约时报》的一篇文章,该文章声称 7 次洗牌足以通过 riffle shuffle 彻底混合一副扑克牌。

Brad Mann 浏览了几个不同的洗牌数学模型,包括马尔可夫链,并得出以下结论:

对于 n=52,这大约是 11.7,这意味着,根据这个观点,我们预计平均需要 11 或 12 次洗牌才能随机化一副​​真正的纸牌。请注意,这大大大于 7。

Brad Mann 只是独立验证了 stachyra 的结果,而不是我的。所以,我仔细查看了我的数据,我发现为什么 7 次随机播放是不够的。首先,对于牌组中的任何卡片,理论上的最大香农熵(以位为单位)为 log(52)/log(2) ~= 5.7 bits。但我的数据从未真正突破超过 5 位。好奇的是,我在 Python 中创建了一个包含 52 个元素的数组,并对该数组进行了洗牌:

>>> import random
>>> r = random.SystemRandom()
>>> d = [x for x in xrange(1,52)]
>>> r.shuffle(d)
>>> print d
[20, 51, 42, 44, 16, 5, 18, 27, 8, 24, 23, 13, 6, 22, 19, 45, 40, 30, 10, 15, 25, 37, 52, 34, 12, 46, 48, 3, 26, 4, 1, 38, 32, 14, 43, 7, 31, 50, 47, 41, 29, 36, 39, 49, 28, 21, 2, 33, 35, 9, 17, 11]

计算其每张卡的熵产生大约 4.8 位。这样做十几次显示类似的结果在 5.2 位和 4.6 位之间变化,平均为 4.8 到 4.9。所以只看我数据的原始熵值是不够的,否则我可以称之为擅长 5 次随机播放。

当我仔细查看我的数据时,我注意到“零桶”的数量。这些存储桶中没有该数字的卡面之间的增量数据。例如,当减去相邻两张牌的值时,在计算完所有 52 个 delta 之后,没有“15”的结果。

我看到它最终在 11-12 次洗牌时解决了 17-18 个“零桶”。果然,我通过 Python 洗牌的牌组平均有 17-18 个“零桶”,最高为 21,最低为 14。为什么 17-18 是结算结果,我还无法解释...... 但是,似乎我想要约 4.8 位熵和 17 个“零桶”。

随着我的股票浅滩洗牌,那是 11-12 洗牌。用我的剪切和洗牌,那是6-7。所以,当谈到游戏时,我会推荐随机播放。这不仅保证了每次洗牌时顶部和底部的牌都混入牌库,而且比 11-12 次洗牌要快得多。我不了解你,但是当我和家人朋友玩纸牌游戏时,他们没有足够的耐心让我进行 12 次洗牌。