線性同余發生器與偽隨機數


本文旨在簡單探索線性同余發生器的一些原理和特點,很多思路借鑒於TAOCP,如果想要深入的探索這方面的知識,建議直接閱讀原著。

一、公式化定義與線性同余序列的周期

離散數據及其應用中,如果

那么,稱a模m同余b(或者稱模m時,a等價於b),可以記為

 而線性同余式就可以這樣表示:

線性同余發生器與上面的線性同余式多少有一些關系。

2.1 公式化定義

按照The Art of Computer Programming,Volume 2[1]3.21. The Linear Congruential Method的思路,線性同余發生器(LCG:Linear Congruential Generators  )可以采用如下公式化定義:

其中:

模數m和系數a是這個公式中最重要的參數,如何合理的選擇這兩個參數決定了其產生的線性同余序列(LCS:Linear Congruential Scquence):<X>質量的優劣(<X>=X1,X2,X3..Xn...)。

常數c可以為0,也可以不為0。通常,如果c=0,那么(2)式也稱作乘法線性同余發生器(MCG:Multiplicative Congruential Generator),如果c非0,(2)式,則稱作混合線性同余發生器(Mixed Linear Congruential Generator)。

X0稱作初始值,也就是所謂的種子seed。

2.2 線性同余序列的周期

如上的線性同余發生器產生的線性同余序列必然會存在一個周期P。在TAOCP中,作者以一個練習的方式提出了這個問題(exercise 3.1-6)。以下通過一種簡單直白但不嚴謹的推理來解釋這個問題。

將上述線性同余公式抽象為一個函數f(將Xn映射為Xn+1),這個函數具有自封閉特性。不難發現,實際上存在以下已知條件:

定義兩個集合:S和T。初始狀態下,集合S包含了從0到m-1所有的m個元素,集合T是一個空集。

現模擬產生LCS的過程,以任意值X0為參數,產生第一個偽隨機數X1。其值必然屬於集合S,此時將X1從集合S移動到集合T。

以X1為參數,產生第二個偽隨機數X2=f(X1),此時,X2有可能屬於集合S,也有可能屬於集合T。

1)如果X2屬於集合S,那么此時還沒有產生一個周期;

2)如果X2屬於集合T,此處也就是剛好等於X1,那么此時一個周期產生,周期P=1。

更一般地,假設在生成X1到Xi-1過程中,每個數都是在集合S中找到的,則每個數都從集合S中移動到了集合T,此時兩個集合的狀態為:

 

然后生成Xi時,

1)如果Xi=f(Xi-1)在集合S中,則未能產生一個周期;

2)如果Xi=f(Xi-1)在集合T中,則一個周期產生,此時周期P<=i-1。

當然周期P也有可能等於m,也就是集合S最終為空集,集合T容納了0到m-1的所有元素,且f(Xm)=X1

因此,從以上推理不難得知,LCS必然存在一個周期P,且P<=m。

進而不難推斷:

1)如果某LCG產生的隨機序列的周期P小於m,則選取不同的初始值X0產生的LCS可能有不同的周期。

2)如果其周期P=m,則即使選取不同的X0,產生的這些LCS具有相同的周期且必定為P。

例如,對於下面發生器:

如果以初始值X0=12產生隨機序列為:

7,6,9,0,7,6,9,0,7,6,9,0,7,6,9,0......

如果以初始值X0=13產生隨機序列則為:

8,3,8,3,8,3,8,3,8,3,8,3,8,3......

但是,對於如下的發生器:

無論選取何值為種子產生的隨機序列,其周期都是16。

 二、關於參數選擇

構造一個表現良好的線性同余發生器並非易事,不但考慮其產生的隨機序列的周期、隨機分布特點,還要考慮計算的效率。

在參數的選擇方面,最關鍵的就是modulus和multiplier的選擇。

3.1 modulus選擇

modulus應該盡可能大,這樣才有可能產生較長的周期。如果計算機的字長為w位,TAOCP中推薦,應該取m=2^w,或者m=2^w+1,或者m=2^w-1,也可以取小於2^w的最大的素數,在論文TABLES OF LINEAR CONGRUENTIAL GENERATORS OF DIFFERENT SIZES AND GOOD LATTICE STRUCTURE[2] 中就采用了這種m值選取的方式。

通常而言,如果取m=2^w,則利用位運算往往會使計算過程更加方便和高效,但是會存在一個問題:產生的隨機序列中各元素的低比特位的隨機性並不是很好。

簡單的解釋是,當m=2^w時,對於一個s位的整數Z,Z模m的結果實際上就是Z的比特位中右邊的s-w位的結果。

作者在原文中用了比較形式化的描述來說明這個問題:

假設d是m的一個因子,q為某一整數,令Yn滿足如下關系:

然后進行如下變換:

不難發現由公式3-1-1實際就是一個線性同余發生器,產生的隨機序列也具有周期,但是其周期小於等於d。

這里的<Y>序列實際上對應了原線性同余序列<X>的低位字節,可以將序列<Y>理解為是將<X>的低位單獨抽取出來組成的一個序列。例如如果d=2^4,則序列<Y>的周期最大也就是16,對應了序列<X>中各個元素的低4位比特位的周期最大是16,顯然低位的隨機性並不是很好。正是由於這個原因,有些平台的實現會丟棄這些隨機性不好的低比特位,截取高比特位以取得一個比較好的隨機性效果。大多數應用場景中,低比特位並不會對最終用途產生影響,因此選取m=2^w基本能夠滿足要求,實際上很多平台也確實都是取m=2^w。

如果m取2^w+1或者m=2^w-1,則不會產生上述問題。

3.2 multiplier的選擇

multiplier的選擇推理過程比較復雜一些。一般來講,應該使LCS的周期盡量長(最長為m),然后只使用一個周期內的元素,但是周期長的序列可能並不具有很好的隨機性。

比如如下的線性同余發生器:

以初始值X0=3產生隨機序列的結果:

4,5,6,7,0,1,2,3,4,5,6,7,0,1,2,3,4,5,6,7,0,1,2,3......

可見,以上序列的隨機性表現很糟糕。

TAOCP中證明了如下定理,按照如下方式來選定系數a可以產生最大周期為m的LCS。

以上定理表明當c不等0時(c與m互質當然就不可能等於0),有可能產生周期為m的LCS。

另一方面,當c=0時,也即:

是否也有可能產生周期為m的LCS呢?答案是必然不可能。

 一個簡單的反證法:

如果c=0時,產生了周期為m的LCS,那么0必然在這個序列中,但是如果0在序列中,必然會導致LCS退化成全0的序列,因此原命題必然不成立。

從另一個角度考慮:

考慮d是m的一個因子,且Xn是d的倍數,也即Xn=kd,其中k為某整數。於是有如下推導:

由此可知,Xn+1也是d的倍數,同理,后面的Xn+2,Xn+2也是d的倍數,現在這樣的序列也不是一個號的序列。所以如果要求序列中各個元素分別與m形成互質關系,因此這樣的序列的元素個數實際就是歐拉函數對m求值,也即:

簡單總結幾點即是:

1)模數m應該盡可能大,通常至少大於2^30,為了計算效率,通常會結合計算機的字長考慮選取m的值。

2)如果m選取為2的冪,也即m=2^w,則選取的a通常應該滿足a模8等於5。

3)當參數m和a的選定比較合理時,對於c的選擇約束性不是很強烈,但要保證c與m互質。例如c可以選擇1或者11。

4)種子seed應該是隨機選取的,可以將時間戳作為種子。

因為Xn的低有效位的隨機性表現並不是很好,所以在對Xn的隨機特性比較敏感的應用場景中,應該盡量采用高比特位。事實上,更應該將值Xn/m視為0到1之間的均勻分布,而不是直接將Xn視為0到m-1之間的均勻分布。所以,如果希望產生0到k-1之間的均勻分布偽隨機數,應該采用kXn/M的方式。 在具體構造一個LCG時,不仿參考TABLES OF LINEAR CONGRUENTIAL GENERATORS OF DIFFERENT SIZES AND GOOD LATTICE STRUCTURE[2]中的表格來選取參數,該文中針對MLCG和LCG給出了若干可供選擇的參數m和a的值:針對MLCG,m取小於2^w的最大質數;針對LCG,m取2^w。

三、JDK中偽隨機數生成

很多平台都采用以上線性同余發生器實現了偽隨機數的生成機制,比如下面是常見的平台實現中對應的參數(from wiki):

代碼演示如下(from rosettacode):

package com.demo;

import java.util.stream.IntStream;
import static java.util.stream.IntStream.iterate;
 
public class LinearCongruentialGenerator {
    final static int mask = (1 << 31) - 1;
    
    static IntStream randBSD(int seed) {
        return iterate(seed, s -> (s * 1_103_515_245 + 12_345) & mask).skip(1);
    }
 
    static IntStream randMS(int seed) {
        return iterate(seed, s -> (s * 214_013 + 2_531_011) & mask).skip(1)
                .map(i -> i >> 16);
    }
    public static void main(String[] args) {
        System.out.println("BSD:");
        randBSD(0).limit(10).forEach(System.out::println);
        
        System.out.println("\nMS:");
        randMS(0).limit(10).forEach(System.out::println);
    }
}
View Code

java中java.util.Random類的實現中,發生器的關鍵代碼如下:

    private static final long multiplier = 0x5DEECE66DL;
    private static final long addend = 0xBL;
    private static final long mask = (1L << 48) - 1;


    protected int next(int bits) {
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
            oldseed = seed.get();
            nextseed = (oldseed * multiplier + addend) & mask;
        } while (!seed.compareAndSet(oldseed, nextseed));
        return (int)(nextseed >>> (48 - bits));//丟棄低比特位,保留高比特位
    }

    public int nextInt() {
        return next(32);
    }

同時可以看到,上滿實現中特意對比特位進行了截取,丟棄低比特位,保留高比特位。

四、References

1、Donald Knuth,The Art of Computer Programming, Volume 2

2、TABLES OF LINEAR CONGRUENTIAL GENERATORS OF DIFFERENT SIZES AND GOOD LATTICE STRUCTURE 

3、https://en.wikipedia.org/wiki/Linear_congruential_generator

完。

轉載請注明原文出處:http://www.cnblogs.com/qcblog/p/8450427.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM