竟然被awk生成的随机数给整蒙了,也谈随机数生成种子

我们现在主要的一个业务是给科研单位等提供数据库构建服务,目前承接的数据库已经发表了 3 篇NAR文章,具体见你的数据也可以-三篇NAR的数据库。

这次一位老师需要做数据库,但数据还没做好,时间要求却比较急,我们只能随机生成一些数据来作为测试先把数据库搭起来,等完成后再替换为真实数据,稍加测试,也就完成了。

最开始是这么生成随机数字的,看上去没问题,每运行一次都会生成一个随机数,符合预期。

awk 'BEGIN{OFS=FS="\t";}{ $2=100 * rand(); print $0;}' 00232503-7e34-479a-b6fb-0b52e78b554e.txt | cut -f 1-3 | head
Rnu7-186P    99.4034    ACC-3
Rnu2-41P    24.6362    ACC-3
awk 'BEGIN{OFS=FS="\t";}{ $2=100 * rand(); print $0;}' 00232503-7e34-479a-b6fb-0b52e78b554e.txt | cut -f 1-3 | head
Rnu7-186P    24.3382    ACC-3
Rnu2-41P    87.6752    ACC-3

但当放到一个for循环中时 ,问题就出来了,每次循环的随机数都一样:

for i in *.txt; do awk 'BEGIN{OFS=FS="\t";}{ $2=100 * rand(); print $0;}' $i | cut -f 1-3 | head -n 2; echo "------------"; done
Rnu7-186P    99.0514    ACC-3
Rnu2-41P    82.4637    ACC-3
------------
Rnu7-186P    99.0514    ACC-3
Rnu2-41P    82.4637    ACC-3
------------
Rnu7-186P    99.0514    ACC-3
Rnu2-41P    82.4637    ACC-3
------------

这猜测是每次循环时随机数发生器给的种子都是一致的,导致随机数在每个循环都一致了,修改如下:每次循环单独给一个随机数的种子就好了。

for i in `seq 1 3`; do awk -v seed=$RANDOM 'BEGIN{OFS=FS="\t";srand(seed);}{ $2=100 * rand(); print $0;}' 00232503-7e34-479a-b6fb-0b52e78b554e.txt | cut -f 1-3 | head -n 2; echo "------------"; done
Rnu7-186P    38.0502    ACC-3
Rnu2-41P    76.7106    ACC-3
------------
Rnu7-186P    99.1498    ACC-3
Rnu2-41P    65.7196    ACC-3
------------
Rnu7-186P    92.9258    ACC-3
Rnu2-41P    24.0214    ACC-3
------------

这就是 awk 自己的坑了。

In most awk implementations, including gawk, rand() starts generating numbers from the same starting number, or seed, each time you run awk. Thus, a program generates the same results each time you run it. The numbers are random within one awk run but predictable from run to run. This is convenient for debugging, but if you want a program to do different things each time it is used, you must change the seed to a value that is different in each run. To do this, use srand() .

随机数生成器的种子

除了在显示生成随机数做测试时会用到随机数生成器,很多其它时候比如做 Kmeans聚类时 WGCNA分析时 , 随机森林分析时 也都会有随机过程,每次运行结果都有可能不同,为了保证结果的可重复性,这时就可以设置一个随机数种子。其原则是: 种子定了,每次运行结果也就不会变了

通常这个种子是一个整数, 任意整数 都可以。讲课时,我一般说大家可以选择自己的幸运数字来设置,在R中通常通过函数 set.seed 来设置:

通常,如果我们没有自己设置种子,大部分程序语言中会调用当前的时间戳作为随机数的种子,每次操作时间都不同,时间戳也就不同,获得的随机数序列也就不同。

下面是一个R中的示例,可以看到前面两次运行 rnorm(5) 获得的返回值都不同。而在设置 set.seed(10) 后,两次运行 rnorm(5) 获得的返回值完全一致。当然这个设置只是对最近的命令有效,下面再运行一次 rnorm(5) ,又是基于时间戳生成的完全不同的数据。

# 不设置
rnorm(5)
# [1]  1.1017795  0.7557815 -0.2382336  0.9874447  0.7413901
rnorm(5)
# [1]  0.08934727 -0.95494386 -0.19515038  0.92552126  0.48297852
set.seed(10)
rnorm(5)
# [1]  0.01874617 -0.18425254 -1.37133055 -0.59916772  0.29454513
set.seed(10)
rnorm(5)
# [1]  0.01874617 -0.18425254 -1.37133055 -0.59916772  0.29454513
rnorm(5)
[1]  0.3897943 -1.2080762 -0.3636760 -1.6266727 -0.2564784

关于随机数种子,虽然看上去比较简单,但每次课程,总会有多位老师问起,问起最多的就是 为什么你选择10作为随机数种子?依据是什么?我怎么选? 实际就记住两点:

  1. 同一个随机数种子获得的随机数序列是一致的,不管这个种子是 10 , 20 还是 30
  2. 随机数种子可以是任意值,看心情选择就好,课程中选哪个也都是随机的。

竟然被awk生成的随机数给整蒙了,也谈随机数生成种子