9.6. random — 擬似乱数を生成する

ソースコード: Lib/random.py


このモジュールでは様々な分布をもつ擬似乱数生成器を実装しています。

整数用に、ある範囲からの一様な選択があります。シーケンス用には、シーケンスからのランダムな要素の一様な選択、リストのランダムな置換をインプレースに生成する関数、順列を置換せずにランダムサンプリングする関数があります。

実数用としては、一様分布、正規分布 (ガウス分布)、対数正規分布、負の指数分布、ガンマおよびベータ分布を計算する関数があります。角度分布の生成用には、フォンミーゼス分布が利用できます。

ほとんど全てのモジュール関数は、基礎となる関数 random() に依存します。この関数はランダムな浮動小数点数を半開区間 [0.0, 1.0) 内に一様に生成します。Python は中心となる乱数生成器としてメルセンヌツイスタを使います。これは 53 ビット精度の浮動小数点を生成し、周期は 2**19937-1 です、本体は C で実装されていて、高速でスレッドセーフです。メルセンヌツイスタは、現存する中で、最も広範囲にテストされた乱数生成器のひとつです。しかし、完全に決定論的であるため、この乱数生成器は全ての目的に合致しているわけではなく、暗号化の目的には全く向いていません。

このモジュールで提供されている関数は、実際には random.Random クラスの隠蔽されたインスタンスのメソッドに束縛されています。内部状態を共有しない生成器を取得するため、自分で Random のインスタンスを生成することができます。

自分で考案した基本乱数生成器を使いたい場合、クラス Random をサブクラス化することもできます。この場合、メソッド random()seed()getstate()setstate() をオーバライドしてください。オプションとして、新しいジェネレータは getrandbits() メソッドを提供することができます。これにより randrange() メソッドが任意に大きな範囲から選択を行えるようになります。

random モジュールは SystemRandom クラスも提供していて、このクラスは OS が提供している乱数発生源を利用して乱数を生成するシステム関数 os.urandom() を使うものです。

警告

このモジュールの擬似乱数生成器をセキュリティ目的に使用してはいけません。

保守(bookkeeping)関数:

random.seed(a=None, version=2)

乱数生成器を初期化します。

a が省略されるか None の場合、現在のシステム時刻が使用されます。乱数のソースがオペレーティングシステムによって提供される場合、システム時刻の代わりにそれが使用されます (利用可能性についての詳細は os.urandom() 関数を参照)。

a が int の場合、それが直接使われます。

バージョン2 (デフォルト) では、 str, bytes, bytearray オブジェクトは int に変換され、そのビットがすべて使用されます。

バージョン1 (Python の古いバージョンでのランダムなシーケンスを再現するために提供される) では、 str:str:`bytes` に対して適用されるアルゴリズムは、より狭い範囲のシードを生成します。

バージョン 3.2 で変更: 文字列シードのすべてのビットを使うバージョン2スキームに移行。

random.getstate()

乱数生成器の現在の内部状態を記憶したオブジェクトを返します。このオブジェクトを setstate() に渡して内部状態を復帰することができます。

random.setstate(state)

state は予め getstate() を呼び出して得ておかなくてはなりません。 setstate()getstate() が呼び出された時の乱数生成器の内部状態を復帰します。

random.getrandbits(k)

k 桁の乱数ビットで Python の整数を生成し、返します。このメソッドはメルセンヌツイスタ生成器で提供されており、その他の乱数生成器でもオプションの API として提供されている場合があります。randrange() メソッドを使用できる場合、getrandbits() はそのメソッドを有効にし、任意の大きな範囲を扱えるようになります。

整数用の関数:

random.randrange(stop)
random.randrange(start, stop[, step])

range(start, stop, step) からランダムに選ばれた要素を返します。この関数は choice(range(start, stop, step)) と等価ですが、実際には range オブジェクトを生成しません。

位置引数のパターンは range() のそれと一致します。キーワード引数は、この関数に望まれない方法で使われるかもしれないので、使うべきではありません。

バージョン 3.2 で変更: 一様に分布した値の生成に関して randrange() がより洗練されました。以前は int(random()*n) のようなやや一様でない分布を生成するスタイルを使用していました。

random.randint(a, b)

a <= N <= b であるようなランダムな整数 N を返します。randrange(a, b+1) のエイリアスです。

シーケンス用の関数:

random.choice(seq)

空でないシーケンス seq からランダムに要素を返します。 seq が空のときは、 IndexError が送出されます。

random.shuffle(x[, random])

シーケンス x をインプレースにシャッフルします。オプションの引数 random は、引数を持たず、 [0.0, 1.0) のランダムな浮動小数点数を返すような関数です。デフォルトでは、これは関数 random() です。

やや小さい len(x) であっても、x の順列の総数はほとんどの乱数生成器の周期よりも大きくなるので注意してください; このことは長いシーケンスに対してはほとんどの順列は生成されないことを意味します。

random.sample(population, k)

母集団のシーケンスまたは集合から選ばれた長さ k の一意な要素からなるリストを返します。置換を行わないランダムサンプリングに用いられます。

母集団自体を変更せずに、母集団内の要素を含む新たなリストを返します。返されたリストは選択された順に並んでいるので、このリストの部分スライスもランダムなサンプルになります。これにより、くじの当選者 (サンプル) を1等賞と2等賞(の部分スライス)に分けることも可能です。

母集団の要素はハッシュ可能 (hashable) でなくても、ユニークでなくてもかまいません。母集団が繰り返しを含む場合、出現するそれぞれがサンプルに選択されえます。

整数のある範囲からサンプルを選ぶには、引数に range() オブジェクトを使いましょう。特に、巨大な母集団からサンプルを取るとき、速度と空間効率が上がります: sample(range(10000000), 60)

サンプルの大きさが母集団の大きさより大きいなら、 ValueError が送出されます。

以下の関数は特定の実数値分布を生成します。関数の引数の名前は、一般的な数学の慣例で使われている分布の公式の対応する変数から取られています; これらの公式のほとんどはどんな統計学のテキストにも載っています。

random.random()

範囲 [0.0, 1.0) の次のランダムな浮動小数点数を返します。

random.uniform(a, b)

a <= b であれば a <= N <= bb < a であれば b <= N <= a であるようなランダムな浮動小数点数 N を返します。

端点の値 b が範囲に含まれるかどうかは、等式 a + (b-a) * random() における浮動小数点の丸めに依存します。

random.triangular(low, high, mode)

low <= N <= high でありこれら境界値の間に指定された最頻値 mode を持つようなランダムな浮動小数点数 N を返します。境界 lowhigh のデフォルトは 0 と 1 です。最頻値 mode 引数のデフォルトは両境界値の中点になり、対称な分布を与えます。

random.betavariate(alpha, beta)

ベータ分布です。引数の満たすべき条件は alpha > 0 および beta > 0 です。範囲 0 から 1 の値を返します。

random.expovariate(lambd)

指数分布です。lambd は平均にしたい値の逆数です。(この引数は “lambda” と呼ぶべきなのですが、Python の予約語なので使えません。) 返される値の範囲は lambd が正なら 0 から正の無限大、lambd が負なら負の無限大から 0 です。

random.gammavariate(alpha, beta)

ガンマ分布です (ガンマ関数 ではありません !)。引数の満たすべき条件は alpha > 0 および beta > 0 です。

確率分布関数は:

          x ** (alpha - 1) * math.exp(-x / beta)
pdf(x) =  --------------------------------------
            math.gamma(alpha) * beta ** alpha
random.gauss(mu, sigma)

ガウス分布です。 mu は平均であり、 sigma は標準偏差です。この関数は後で定義する関数 normalvariate() より少しだけ高速です。

random.lognormvariate(mu, sigma)

対数正規分布です。この分布を自然対数を用いた分布にした場合、平均 mu で標準偏差 sigma の正規分布になります。mu は任意の値を取ることができ、sigma はゼロより大きくなければなりません。

random.normalvariate(mu, sigma)

正規分布です。mu は平均で、sigma は標準偏差です。

random.vonmisesvariate(mu, kappa)

mu は平均の角度で、0 から 2*pi までのラジアンで表されます。kappa は濃度パラメタで、ゼロ以上でなければなりません。kappa がゼロに等しい場合、この分布は範囲 0 から 2*pi の一様でランダムな角度の分布に退化します。

random.paretovariate(alpha)

パレート分布です。alpha は形状パラメタです。

random.weibullvariate(alpha, beta)

ワイブル分布です。alpha は尺度パラメタで、beta は形状パラメタです。

代替の生成器:

class random.SystemRandom([seed])

オペレーティングシステムの提供する発生源によって乱数を生成する os.urandom() 関数を使うクラスです。すべてのシステムで使えるメソッドではありません。ソフトウェアの状態に依存してはいけませんし、一連の操作は再現不能です。従って、 seed() メソッドは何の影響も及ぼさず、無視されます。 getstate()setstate() メソッドが呼び出されると、例外 NotImplementedError が送出されます。

参考

M. Matsumoto and T. Nishimura, “Mersenne Twister: A 623-dimensionally equidistributed uniform pseudorandom number generator”, ACM Transactions on Modeling and Computer Simulation Vol. 8, No. 1, January pp.3–30 1998.

Complementary-Multiply-with-Carry recipe 長い周期と比較的シンプルな更新操作を備えた互換性のある別の乱数生成器。

9.6.1. 再現性について

疑似乱数生成器から与えられたシーケンスを再現できると便利なことがあります。シード値を再利用することで、複数のスレッドが実行されていない限り、実行ごとに同じシーケンスが再現できます。

random モジュールのアルゴリズムやシード処理関数のほとんどは、Python バージョン間で変更される対象となりますが、次の二点は変更されないことが保証されています:

  • 新しいシード処理メソッドが追加されたら、後方互換なシード処理器が提供されます。

  • 生成器の random() メソッドは、互換なシード処理器に同じシードが与えられた場合、引き続き同じシーケンスを生成します。

9.6.2. 例とレシピ

基本的な使用例:

>>> random.random()                      # Random float x, 0.0 <= x < 1.0
0.37444887175646646

>>> random.uniform(1, 10)                # Random float x, 1.0 <= x < 10.0
1.1800146073117523

>>> random.randrange(10)                 # Integer from 0 to 9
7

>>> random.randrange(0, 101, 2)          # Even integer from 0 to 100
26

>>> random.choice('abcdefghij')          # Single random element
'c'

>>> items = [1, 2, 3, 4, 5, 6, 7]
>>> random.shuffle(items)
>>> items
[7, 3, 2, 5, 6, 4, 1]

>>> random.sample([1, 2, 3, 4, 5],  3)   # Three samples without replacement
[4, 1, 5]

ありがちな作業として、重み付けされた確率の random.choice() を作ることがあります。

重み付けが整数比率なら、簡単なテクニックはサンプル母集団を繰り返しで作ることです:

>>> weighted_choices = [('Red', 3), ('Blue', 2), ('Yellow', 1), ('Green', 4)]
>>> population = [val for val, cnt in weighted_choices for i in range(cnt)]
>>> population
['Red', 'Red', 'Red', 'Blue', 'Blue', 'Yellow', 'Green', 'Green', 'Green', 'Green']

>>> random.choice(population)
'Green'

より一般的なアプローチは、重みを itertools.accumulate() で累積分布に並べ、ランダム値を bisect.bisect() で位置づけることです:

>>> choices, weights = zip(*weighted_choices)
>>> cumdist = list(itertools.accumulate(weights))
>>> cumdist            # [3, 3+2, 3+2+1, 3+2+1+4]
[3, 5, 6, 10]

>>> x = random.random() * cumdist[-1]
>>> choices[bisect.bisect(cumdist, x)]
'Blue'