本福特定律——原理、证明和伪造
昵称不想加后缀
2022年07月11日 07:16

不久前又看了本福特定律的介绍视频,一时兴起研究了一番。这里给出一些我的相关理解和证明,供于参考。

原视频:


原理

引用某百科的介绍,本福特定律指一堆从实际生活得出的数据中,以1为首位数字的数的出现概率约为总数的三成,而以2、3等开头的数则逐级向下递减。而实际上这些概率是可以被计算出来的。在n进制中,以数m起头的数出现的概率为 %5Clog_n%20%5Cfrac%7Bm%2B1%7D%7Bm%7D 。据此计算出10进制以1为首的概率即为 %5Clg%202%20%5Capprox%20%200.301 。

这里需要说明的是,本福特定律是一个经验性的定理,并不是所有生活中的数据都满足此条件。例如生活中极为常见的正态分布(如人身高体重的分布)、二项式分布(如扔硬币正面向上的次数)和均匀分布(骰子点数频率分布)都不满足本福特定律。

如视频所说,本福特定律往往只对跨越几个数量级的数据才有效,例如斐波那契数列的分布。当然这也不绝对,比如对于数列 a_n%3Dn%5Ek%20%20(k%5Cin%20N%5E%2B) 来说,本福特定律也都不满足。一般来说,最常见的满足本福特定律的概率分布满足以下概率密度函数

f(x)%20%3D%20%5Cleft%5C%7B%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%5Cbegin%7Barray%7D%7B**lr**%7D%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%200%20%2C%20x%20%5Cleq%20m%20%26%20%20%5C%5C%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%5Cfrac%7B1%7D%7B%5Clambda%20x%20%7D%20%2C%20m%20%3C%20x%20%5Cleq%20me%5E%5Clambda%20%20%5C%5C%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%200%20%2C%20x%20%3E%20me%5E%5Clambda%20%20%26%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%5Cend%7Barray%7D%20%20%0A%5Cright.%20%20%0A

其中,m,λ为任意正数。是不是一头雾水...?其实一些常见的序列都遵循这个概率分布。事实上,所有等比数列(指数函数)均符合此概率分布并满足本福特定律。第二部分中将给出具体证明。

让我们先假定上面的定理是正确的,那现实中的本福特定律就可以解释了。对于现实中的数据来说,如果能跨越多个数量级,那么它们一般都是以指数级增长的,它们的差往往也是指数级的。如果我们把常见的这些数据排序好绘制到坐标系上,我们往往都能发现指数函数能很好的拟合它们:

上证A股2000多条的股价数据

上图是上证A股2000多条的股价数据。对数图中,排除首尾极端数据后可使用一次函数拟合,这就意味着原始数据可使用指数函数拟合。经过统计,以1开头的数据共656条,总可用数据共2087条,约占0.314,也确实满足本福特定律。

所以总结来说,因为现实中能够跨越数量级的数据大多呈指数级增长,因此对其序列排序后大概率可以使用指数函数拟合,因此满足本福特定律。


证明

接下来就到证明的时候了!现在我们只需要证明等比数列满足本福特定律即可。不过在这之前,我们先证明一个引理:

引理1:对于任何能被 f(x) 拟合的序列,如果对于给定正整数m,n,其反函数 f%5E%7B-1%7D(x) 满足%5Cfrac%7Bf%5E%7B-1%7D(mx)%20-%20f%5E%7B-1%7D(x)%7D%7Bf%5E%7B-1%7D(nx)%20-%20f%5E%7B-1%7D(x)%7D%20%3D%20C 恒为常数,那么对应该序列的n进制表示中,其开头的数字小于m的数字占比应为C。

比如,对于f(x)%20%3D%20x%5E2 来说,给定m=2,n=10,因为对任意 x>0 有:

%5Cfrac%7B%5Csqrt%7B2x%7D%20%20-%20%5Csqrt%7Bx%7D%7D%7B%5Csqrt%7B10x%7D%20-%20%5Csqrt%7Bx%7D%7D%20%3D%20%5Cfrac%7B%5Csqrt%7B2%7D%20%20-%201%7D%7B%5Csqrt%7B10%7D%20-%201%7D%20%5Capprox%200.192 

所以 x%5E2 序列里以1开头的数大约占0.192。

接下来开始证明。我们先以十进制为例,对于所有的正数,我们可以把它们按如下规则划分到不同的区间:

%5B1%2C10)%20%2C%20%5B10%2C100)%2C%20%5B100%2C1000)%2C%20... 

明显的,每个区间内的前10%的数字开头均为1,前20%的数字开头均小于2,以此类推。同样的,对于n进制,这些区间可以统一表示为 %5Bk%2C%20nk) ,其中k为n的幂数倍。画个图:

f(x)

这里f(x)是拟合我们序列的函数,横坐标是序列编号,纵坐标为对应值。如图中用浅灰色线所示, %5Bk%2C%20nk) 会把整个函数的值域划分成一个个的区间。 %5Bk%2C%20nk) 内的数字中,数字开头小于m的数字均落在 %5Bk%2C%20mk) 区间内,如图中土黄色所示。那么这些数一共有多少个呢?我们知道,横坐标是连续的序列编号,因此这些数对应图中绿色区间,一共有 f%5E%7B-1%7D(mk)%20-%20f%5E%7B-1%7D(k) 个,所以在这个区间内,其开头的数字小于等于m的数字占比应为%5Cfrac%7Bf%5E%7B-1%7D(mk)%20-%20f%5E%7B-1%7D(k)%7D%7Bf%5E%7B-1%7D(nk)%20-%20f%5E%7B-1%7D(k)%7D%20 。剩下的所有区间同理,对应的占比也是此式。我们再根据引理的条件,既然对所有 x>0,都有%5Cfrac%7Bf%5E%7B-1%7D(mx)%20-%20f%5E%7B-1%7D(x)%7D%7Bf%5E%7B-1%7D(nx)%20-%20f%5E%7B-1%7D(x)%7D%20%3D%20C 恒为常数,自然就得出引理的结论了。至此,引理1证明完毕。

接下来证明指数函数满足引理1的条件:

定理1:指数函数 f(x)%20%3D%20ba%5Ex 对给定m,n,满足 %5Cfrac%7Bf%5E%7B-1%7D(mx)%20-%20f%5E%7B-1%7D(x)%7D%7Bf%5E%7B-1%7D(nx)%20-%20f%5E%7B-1%7D(x)%7D%20%3D%5Clog_n%20m 。

对此函数,可求反函数为 f%5E%7B-1%7D(x)%20%3D%20%5Clog_a%20%5Cfrac%7Bx%7D%7Bb%7D%20%20 ,则有:

%5Cfrac%7B%5Clog_a%5Cfrac%7Bmx%7D%7Bb%7D%20%20%20-%5Clog_a%5Cfrac%7Bx%7D%7Bb%7D%7D%7B%5Clog_a%5Cfrac%7Bnx%7D%7Bb%7D%20%20%20-%5Clog_a%5Cfrac%7Bx%7D%7Bb%7D%7D%20%3D%20%5Cfrac%7B%5Clog_a%5Cfrac%7Bmx%7D%7Bb%7D%20%2F%20%5Cfrac%7Bx%7D%7Bb%7D%7D%7B%5Clog_a%5Cfrac%7Bnx%7D%7Bb%7D%20%20%20%2F%20%5Cfrac%7Bx%7D%7Bb%7D%7D%20%3D%20%5Cfrac%7B%5Clog_a%20m%7D%7B%5Clog_a%20n%7D%20%20%3D%20%5Clog_n%20m 

得证。

至此“等比数列满足本福特定律”这个结论已经呼之欲出了。由定理1和引理1,我们可知对于任何指数函数,在n进制中,第一个数小于m的概率为 %5Clog_n%20m ,同理,第一个数小于m+1的概率为 %5Clog_n%20(m%2B1),两式相减,即可得出第一个数等于m的概率,即我们的本福特定律:%5Clog_n%20%5Cfrac%7Bm%2B1%7D%7Bm%7D。证毕!


伪造

本福特定律可用于检查各种数据是否有造假,但是如果造假者有心的话,满足本福特定律的数据也是可以伪造的。我们完全可以根据上文的内容来实现“造假”的过程。

方法很简单,我们既然已经知道指数函数满足本福特定律,那么我们就可以从一个指数函数里选择一小段,再用均匀分布的随机数映射过去就可以了。下面是一个生成本福特定律数据的示例代码:

代码块
Python
自动换行
复制代码
import math, random

def fake_benford(begin, end, n):
    """
    generate fake benford data.
    :param begin: min value
    :param end: max value
    :param n: total amount
    :return: generated array
    """
    def exp(v):
        return begin * (1.3 ** (v * math.log(end / begin, 1.3)))

    return [exp(random.random()) for _ in range(n)]

example = fake_benford(1, 100000, 1000000)
复制成功