研究!rsc公司

关于编程的想法和链接,通过

RSS(RSS)

抽样的魔力及其局限性
发布于2023年2月4日,星期六。

假设我有大量M&M并想估计其中有多少彼得的脸在他们身上。正如人们所做的那样。

如果我懒得数一数,我可以通过采样来估计真实分数:随机选择N,数一数P有多少张彼得的脸,然后估计P/N的分数。

我可以编写围棋程序为我挑选37个M&M中的10个:27 30 1 13 36 5 33 7 10 19。(是的,我懒得数数,但我也不懒得为了使用Go程序而数数M&M。)

根据这一估计,我们可以估计3/10=30%的M&M长着彼得的脸。我们可以再做几次:

我们得到了一些新的估计:30%、40%、20%。实际分数为9/37=24.3%。这些估计可能没有那么令人印象深刻,但我们只使用了10个样本。如果没有太多的样本,我们可以得到更准确的估计,甚至对于更大的数据集。假设我们有更多的M&M,同样是24.3%的Peter脸,我们抽样100张,或者1000张,或者10000张。既然我们懒惰,那就写吧模拟该过程的程序.

$go运行sample.go10: 40.0% 20.0% 30.0%  0.0% 10.0% 30.0% 10.0% 20.0% 20.0%  0.0%100: 25.0% 26.0% 21.0% 26.0% 15.0% 25.0% 30.0% 30.0% 29.0% 20.0%1000: 24.7% 23.8% 21.0% 25.4% 25.1% 24.2% 25.7% 22.9% 24.0% 23.8%10000: 23.4% 24.6% 24.3% 24.3% 24.7% 24.6% 24.6% 24.7% 24.1% 25.0%$

准确性提高得相当快:

因为我们只估计了彼得脸的百分比,不是总数,而是准确度(也以百分比衡量)不取决于M&M的总数,只取决于样本数。因此,10000个样本足以获得大约1%的准确度10万M&M,100万M&Ms,甚至1000亿M&M!在最后一个场景中,尽管只抽样了0.00001%的M&Ms,但准确率为1%。

抽样的神奇之处在于我们可以得出准确的估计使用数量相对较少的样本来描述非常大的人群。

抽样将许多一次性估算转化为可用手工完成的工作。例如,假设我们正在考虑修改一个容易出错的API并希望估计该API被错误使用的频率。如果我们有办法随机抽样API的使用(也许grep-Rn包装。功能|洗牌-m 100),然后手动检查其中的100个将给我们一个估计准确度在5%左右。检查1000个,可能不超过一个小时如果它们很容易被看到,则将精确度提高到1.5%左右。决定重要问题的真实数据通常很值得少量手动操作。

对于我所关注的与围棋相关的决策类型,这种方法一直在出现:什么分数对于实代码中的循环具有循环范围错误?新警告的比例是多少 兽医检查是否为假阳性?哪些模块没有依赖项?这些都是从我的经验中提取的,因此它们可能对Go来说是特定的或语言发展,但一旦你意识到抽样使准确的估计很容易获得,各种用途都有。只要你有一个大数据集,

通过random()limit 1000从数据顺序中选择*;

是一种非常有效的获取数据集的方法,您可以手动分析并且仍然从中得出许多有用的结论。

准确性

让我们来计算一下这些估计的准确性。蛮力方法是运行许多给定大小的样本并计算每种方法的精度。这个节目进行1000次试验,每次试验100个样品,计算每个估计的观测误差然后按排序顺序打印出来。如果我们沿着x轴依次绘制这些点,我们得到这样的图片:

这个我在这个屏幕截图中使用的数据查看器已按缩放x轴标签1000倍(“x以千计”)。查看散点图,我们可以看到一半的时间是错误的小于3%,80%的时间误差小于5½%。

此时我们可能会想,错误是否取决于实际答案(到目前为止,我们的项目中有24.3%)。确实如此:当人口失衡时,误差会更低。显然,如果M&M是0%或100%的Peter面孔,我们的估计不会有任何误差。在稍微不那么退化的情况下,如果M&M是1%或99%的Peter脸,最可能的估计仅从几个样本中得到0%或100%的误差,只有1%。结果表明,一般来说,当实际分数为50%,所以我们会用的用于剩下的分析。

实际分数为50%,1000个排序错误通过抽样估计100个值如下:

误差有点大。现在一半的时间误差是4%,80%的时间误差为6%。放大绘图的尾部会产生:

我们可以看到,90%的试验具有8%或更低的误差,95%的试验误差不超过10%,99%的试验误差不超过12%。这些陈述的统计表达方式是“样本大小N=100产生8%的误差幅度,置信度为90%,10%,置信度95%,12%,置信度99%。”

我们可以不用目不转睛地看图表更新程序直接计算这些数字。

$go运行sample.goN=10:90%:30.00%95%:30.00%99%:40.00%N=100:90%:9.00%95%:11.00%99%:13.00%N=1000:90%:2.70%95%:3.20%99%:4.30%N=10000:90%:0.82%95%:0.98%99%:1.24%$

使用(试验的)抽样来估计引入的误差有点玄妙通过对实际分布进行抽样。通过对错误进行采样而引入的错误是什么?相反,我们可以编写一个程序来计算所有可能的结果并计算准确的误差分布,但对于较大的样本量,计数是行不通的。幸运的是,其他人已经为我们做了计算甚至实现了相关功能在Go的标准中数学软件包.给定置信水平的误差幅度样本大小为:

func-moe(置信浮点64,N int)浮点64{返回数学。埃尔芬夫(信心)/数学。平方(2*float64(N))}

这样我们就可以计算表格了更直接地.

$go运行sample.goN=10:90%:26.01%95%:30.99%99%:40.73%N=20:90%:18.39%95%:21.91%99%:28.80%N=50:90%:11.63%95%:13.86%99%:18.21%N=100:90%:8.22%95%:9.80%99%:12.88%N=200:90%:5.82%95%:6.93%99%:9.11%N=500:90%:3.68%95%:4.38%99%:5.76%N=1000:90%:2.60%95%:3.10%99%:4.07%N=2000:90%:1.84%95%:2.19%99%:2.88%N=5000:90%:1.16%95%:1.39%99%:1.82%N=10000:90%:0.82%95%:0.98%99%:1.29%N=20000:90%:0.58%95%:0.69%99%:0.91%N=50000:90%:0.37%95%:0.44%99%:0.58%N=100000:90%:0.26%95%:0.31%99%:0.41%$

我们也可以反转方程来计算必要的给定置信水平和误差幅度的样本量:

func N(置信度,moe浮点64)int{return int(math.Ceil(0.5*math.Pow(math.Erfinv(confidence)/moe,2))}

这让我们计算这个表.

$go运行sample.go摩尔当量=5%:90%:271 95%:385 99%:664摩尔当量=2%:90%:1691 95%:2401 99%:4147moe=1%:90%:6764 95%:9604 99%:16588$

限制

要准确估计项目的分数一个特定的财产,像M&M和Peter脸,每个项目必须有相同的被选中机会,就像每一次M&M一样。假设我们有十袋M&M:九个一磅重的袋子,每个500个M&M,还有一个小袋子,里面装着我们以前用过的37个M&M。如果我们想估计M&M的分数Peter面对现实,抽样调查是行不通的首先随机挑选一个袋子然后从袋子里随机挑选一个M&M。从一磅重的袋子中挑选任何特定M&M的机会将是1/10×1/500=1/5000,而概率从小包中挑选任何特定的M&M1/10 × 1/37 = 1/370.我们最终估计约9/370=2.4%的彼得脸,尽管实际答案是9/(9×500+37)=0.2%,但彼得面对。

这里的问题不是随机抽样误差我们在上一节中计算的。相反,它是由采样机制引起的系统误差这与所估计的统计数据不符。我们可以通过加权来恢复准确的估计在小包中发现的M&M,仅为M&M的37/500在任何估计的分子和分母中。例如,如果我们从每个包中挑选了100个M&M并替换在小袋子里发现了24张彼得的脸,然后,我们将计算24w/(900+100w)=0.2%,而不是24/1000=2.4%。

作为一个不那么做作的例子,Go的内存分析器目标是在分配的半MB中抽取大约一个分配然后导出有关程序在何处分配内存的统计信息。大致来说,为了做到这一点,分析器维护了一个采样触发器,初始化为0到100万之间的随机数。每次分配新对象时,探查器根据对象的大小减少触发器。当分配将触发器递减到零以下时,配置器采样然后将触发器重置为新的随机数在0到100万之间。

这种基于字节的采样意味着要估计给定函数中分配的字节的分数,分析器可以划分该函数中分配的总采样字节除以整个程序中分配的总采样字节数。使用相同的方法估计的分数物体在给定函数中分配这将是不准确的:它将高估大型对象而低估较小的,因为较大的对象更有可能被采样。为了恢复有关分配计数的准确统计信息,分析器应用基于大小的加权函数在计算过程中,就像M&M示例中一样。(这与M&M的情况相反:我们随机抽样分配内存的单个字节但现在需要有关他们“袋子”的统计数据。)

不可能总是撤消倾斜采样,倾斜使得计算误差幅度更难。确保采样与要计算的统计数据一致。