random – 疑似乱数の生成

目的:数種類の疑似乱数ジェネレータを提供する
利用できるバージョン:1.4 以上

random モジュールは、 Mersenne Twister アルゴリズムに基づく高速な擬似乱数ジェネレータを提供します。もともとはモンテカルロシミュレーションの入力値を生成するために開発された Mersenne Twister は、周期が長く、広範囲なアプリケーションに適した連続一様分布の数値を生成します。

乱数の生成

random() 関数は、生成されたシーケンスから次のランダムな浮動小数点数の値を返します。返り値の範囲は 0 <= n < 1.0 になります。

import random

for i in xrange(5):
    print '%04.3f' % random.random()

このプログラムを繰り返し実行すると、別の数値のシーケンスを生成します。

$ python random_random.py
0.688
0.096
0.717
0.924
0.329

$ python random_random.py
0.713
0.489
0.777
0.272
0.518

特定の範囲内の数値を生成するには uniform() を使用してください。

import random

for i in xrange(5):
    print '%04.3f' % random.uniform(1, 100)

uniform() に最小値と最大値を渡すと、 random() からの返り値を min + (max - min) * random() の数式で計算します。

$ python random_uniform.py
26.088
91.760
48.956
93.644
21.449

シード (種)

random() は呼び出される毎に違う値を生成し、任意の数が繰り返されるのにかなり長い周期があります。これは一意な値、またはそれに近い値を生成するには便利ですが、別の方法で処理するのに同じデータセットを利用できると便利なときもあります。1つのテクニックとしては、乱数を生成して、別の方法で処理されるように保存するプログラムを利用することです。しかし、このテクニックは巨大なデータに関しては実用的ではないかもしれません。そのため、 random は、予想される値セットを生成するために、疑似乱数ジェネレータを初期化する seed() を提供します。

import random

random.seed(1)

for i in xrange(5):
    print '%04.3f' % random.random()

シードの値は、擬似乱数を生成するために使用される数式によって生成された最初の値を制御します。その数式は決定性なので、シードが変更された後で生成された完全なシーケンスもセットします。 seed() の引数は、ハッシュ化できる任意のオブジェクトです。デフォルトでは、プラットフォーム固有な乱数のソースが利用できるのなら、それを使用します。それ以外の場合は現在時刻が使用されます。

$ python random_seed.py
0.134
0.847
0.764
0.255
0.495

$ python random_seed.py
0.134
0.847
0.764
0.255
0.495

状態を保存する

数値のシーケンスを制御するのに便利なもう1つのテクニックは、テスト実行間の乱数ジェネレータの内部状態を保存することです。処理を継続する前にその前の状態を復元することで、初期の入力値からの数値、または数値のシーケンスが繰り返される可能性が低くなります。 getstate() 関数は、次の実行時に setstate() で乱数ジェネレータを再初期化するデータを返します。

import random
import os
import cPickle as pickle

if os.path.exists('state.dat'):
    # 以前に保存された状態を復元する
    print 'Found state.dat, initializing random module'
    with open('state.dat', 'rb') as f:
        state = pickle.load(f)
    random.setstate(state)
else:
    # 良く知られた開始状態を使用する
    print 'No state.dat, seeding'
    random.seed(1)

# 乱数を生成する
for i in xrange(3):
    print '%04.3f' % random.random()

# 次回のために状態を保存する
with open('state.dat', 'wb') as f:
    pickle.dump(random.getstate(), f)

# さらに乱数を生成する
print '\nAfter saving state:'
for i in xrange(3):
    print '%04.3f' % random.random()

getstate() が返すデータは実装の詳細なので、このサンプルは pickle で変換してファイルにそのデータを保存します。それ以外の場合は、そのデータをブラックボックスとして扱います。プログラムの開始時にそのデータを保存したファイルが存在するなら、以前の古い状態を読み込んで処理を継続します。その状態を保存する前後でそれぞれ数回実行することで、状態を復元させるとその乱数ジェネレータは同じ値を生成することが分かります。

$ python random_state.py
No state.dat, seeding
0.134
0.847
0.764

After saving state:
0.255
0.495
0.449

$ python random_state.py
Found state.dat, initializing random module
0.255
0.495
0.449

After saving state:
0.652
0.789
0.094

ランダムな整数

random() は、浮動小数点数の数値を生成します。その結果を整数に変換するのも可能ですが、 randint() 関数を直接使用する方がもっと便利です。

import random

print '[1, 100]:'

for i in xrange(3):
    print random.randint(1, 100)

print
print '[-5, 5]:'
for i in xrange(3):
    print random.randint(-5, 5)

randint() の引数は、値が取りうる範囲の両端の値です。その引数は、正負どちらの数も受け取れますが、第一引数は第二引数より小さい必要があります。

$ python random_randint.py
[1, 100]:
35
41
53

[-5, 5]:
3
3
3

randrange() は、ある範囲から数値を選択する汎用的な関数です。

import random

for i in xrange(3):
    print random.randrange(0, 101, 5)

randrange() は、start と stop の引数に加えて step 引数もサポートします。そのため、 range(start, stop, step) から乱数を選択しているのと完全に等価です。この範囲は、実際には構築されないので、かなり効率的です。

$ python random_randrange.py
95
5
10

ランダムな要素を選ぶ

乱数ジェネレータの一般的な用途の1つは、数字でなくても、列挙型のシーケンスからランダムな要素を選択することです。 random は、シーケンスからランダムに選択する choice() 関数を提供します。このサンプルは、10,000 回コインを投げて、何回、表 (head) と裏 (tail) が出たかを数えるシミュレーションです。

import random
import itertools

outcomes = { 'heads':0,
             'tails':0,
             }
sides = outcomes.keys()

for i in range(10000):
    outcomes[ random.choice(sides) ] += 1

print 'Heads:', outcomes['heads']
print 'Tails:', outcomes['tails']

返り値は2つだけなので、数値を使用するよりも、 choice() を用いて “heads” と “tails” という単語を数値に変換します。この結果は、outcomes ディクショナリのキーの名前を使用してカウントされます。

$ python random_choice.py
Heads: 4949
Tails: 5051

順列

カードゲームのシミュレーションは、カードのデッキを必ず混ぜ合わせてから、同じカードを2回使用しないようにプレイヤーへカードを “配ります” 。 choice() を使用すると、同じカードを2回配ってしまいます。そのため、代わりにデッキを shuffle() で混ぜ合わせてから、カードを配るときにそのカードを削除します。

import random
import itertools

def new_deck():
    return list(itertools.product(
            itertools.chain(xrange(2, 11), ('J', 'Q', 'K', 'A')),
            ('H', 'D', 'C', 'S'),
            ))

def show_deck(deck):
    p_deck = deck[:]
    while p_deck:
        row = p_deck[:13]
        p_deck = p_deck[13:]
        for j in row:
            print '%2s%s' % j,
        print

# カードが順番に並んだ新しいデッキを取得する
deck = new_deck()
print 'Initial deck:'
show_deck(deck)

# ランダムな順番にカードをシャッフルする
random.shuffle(deck)
print '\nShuffled deck:'
show_deck(deck)

# 5つのカードのうち4つを配る
hands = [ [], [], [], [] ]

for i in xrange(5):
    for h in hands:
        h.append(deck.pop())

# 手札のカードを表示する
print '\nHands:'
for n, h in enumerate(hands):
    print '%d:' % (n+1),
    for c in h:
        print '%2s%s' % c,
    print
    
# デッキに残っているカードを表示する
print '\nRemaining deck:'
show_deck(deck)

カードは数とそのスート (マーク) を指す文字を含むタプルとして表現されます。配られた “手札” は、デッキから同じカードが2回配られないように削除すると同時に、それぞれ4種類のリストへ追加することで作成されます。

$ python random_shuffle.py
Initial deck:
 2H  2D  2C  2S  3H  3D  3C  3S  4H  4D  4C  4S  5H
 5D  5C  5S  6H  6D  6C  6S  7H  7D  7C  7S  8H  8D
 8C  8S  9H  9D  9C  9S 10H 10D 10C 10S  JH  JD  JC
 JS  QH  QD  QC  QS  KH  KD  KC  KS  AH  AD  AC  AS

Shuffled deck:
 2D  3H  KD  2H  8D  8C  QS  5H  KC  7H  AD  AH  7S
 4S  9H  7D  JD  6C  5D 10S  9S 10C  4D  6S  JS  KH
 5C  3D 10H  4H  4C  2S  QH  6D  AS  7C  KS  JH  6H
 JC  8H  9D  9C  3S  AC  5S  2C  QD 10D  3C  QC  8S

Hands:
1:  8S  QD  3S  JC  7C
2:  QC  2C  9C  6H  AS
3:  3C  5S  9D  JH  6D
4: 10D  AC  8H  KS  QH

Remaining deck:
 2D  3H  KD  2H  8D  8C  QS  5H  KC  7H  AD  AH  7S
 4S  9H  7D  JD  6C  5D 10S  9S 10C  4D  6S  JS  KH
 5C  3D 10H  4H  4C  2S

多くのシミュレーションは、入力値の母集団からのランダム・サンプルを必要とします。 sample() 関数は、入力値のシーケンスを変更せず、取り出した値を繰り返さずにサンプル値を生成します。このサンプルは、システム辞書から単語のランダム・サンプルを表示します。

import random

with open('/usr/share/dict/words', 'rt') as f:
    words = f.readlines()
words = [ w.rstrip() for w in words ]

for w in random.sample(words, 5):
    print w
    

結果セットを生成するためのアルゴリズムは、その入力値のサイズを考慮して、できるだけ効率的に結果を生成するようにサンプル値を指定しました。

$ python random_sample.py

pleasureman
consequency
docibility
youdendrift
Ituraean

$ python random_sample.py

jigamaree
readingdom
sporidium
pansylike
foraminiferan

同時に複数生成する

モジュールレベルの関数に加え、 random は、複数の乱数ジェネレータのために内部状態を管理する Random クラスも提供します。これまでに説明した全ての関数は、 Random インスタンスのメソッドとして利用できます。そして、それぞれのインスタンスは、他のインスタンスが返す値に影響を受けずに個別に初期化して使用できます。

import random
import time

print 'Default initializiation:\n'

r1 = random.Random()
r2 = random.Random()

for i in xrange(3):
    print '%04.3f  %04.3f' % (r1.random(), r2.random())

print '\nSame seed:\n'

seed = time.time()
r1 = random.Random(seed)
r2 = random.Random(seed)

for i in xrange(3):
    print '%04.3f  %04.3f' % (r1.random(), r2.random())

ネイティブな乱数のシードをもつシステムでは、そのインスタンスが一意な状態を保持して利用されます。しかし、プラットフォームに適切な乱数のシードがない場合、そのインスタンスは現在時刻でシードの値を生成しているので、同じ値を生成します。

$ python random_random_class.py
Default initializiation:

0.697  0.425
0.128  0.093
0.188  0.009

Same seed:

0.571  0.571
0.054  0.054
0.620  0.620

乱数ジェネレータがランダムな周期の別の部分から値を生成するのを保証するには、その初期状態を変更する jumpahead() を使用してください。

import random
import time

r1 = random.Random()
r2 = random.Random()

# r1 から r2 を強制的に別の乱数周期にする
r2.setstate(r1.getstate())
r2.jumpahead(1024)

for i in xrange(3):
    print '%04.3f  %04.3f' % (r1.random(), r2.random())

jumpahead() の引数は、それぞれの乱数ジェネレータから必要な数値に基づく正の整数にします。乱数ジェネレータの内部状態は、その入力値に基づいて混ぜられますが、引数に渡された値から入力値を増加させるという単純なものではありません。

$ python random_jumpahead.py
0.064  0.903
0.930  0.790
0.325  0.815

SystemRandom

一部のオペレーティングシステムは、乱数ジェネレータ内部に導入されるエントロピーのより多くのソースにアクセスする乱数ジェネレータを提供します。 random は、 Random と同じ API を持つ SystemRandom クラスを通して、この機能を公開します。しかし、その他の全てアルゴリズムの基本となる値を生成するには os.urandom() を使用します。

import random
import time

print 'Default initializiation:\n'

r1 = random.SystemRandom()
r2 = random.SystemRandom()

for i in xrange(3):
    print '%04.3f  %04.3f' % (r1.random(), r2.random())

print '\nSame seed:\n'

seed = time.time()
r1 = random.SystemRandom(seed)
r2 = random.SystemRandom(seed)

for i in xrange(3):
    print '%04.3f  %04.3f' % (r1.random(), r2.random())

SystemRandom が生成するシーケンスを再現できない理由は、その乱数がソフトウェアの状態というよりもそのシステムからきているからです (実際に seed()setstate() は全く影響を受けません) 。

$ python random_system_random.py
Default initializiation:

0.590  0.867
0.069  0.935
0.640  0.052

Same seed:

0.560  0.701
0.926  0.808
0.068  0.465

不均一分布

random() が生成する値の連続一様分布は多くの用途に便利ではあるものの、その他に特定の状況により正確なモデルもあります。 random モジュールは、そういった分布の値を生成する関数も提供します。ここで紹介しますが、その詳細については、より複雑なサンプルを必要とし、その用途が特化されたものなので説明しません。

正規分布

正規 分布は、一般的に非一様な連続性をもつ成績、身長、体重といった値に使用されます。分布曲線は、独特の形状をもち、通称 “ベルカーブ” と呼ばれます。 random は、正規分布の値を生成する2つの関数 normalvariate() と わずかに高速な gauss() を提供します (正規分布はガウス分布とも呼ばれる) 。

関連する関数 lognormvariate() は、正規分布の値の対数で疑似乱数を生成します。対数正規分布は、お互いに影響しない複数の乱数を集めた値に便利です。

近似

三角 分布は、小さなサンプルサイズの近似分布として使用されます。三角分布の “曲線” は、最小値と最大値の低位置の座標、高位置の座標、そのモードをもちます。そのモードは “最も近い” 結果 (triangular() のモードの引数で反映される) に基づいて見積もられます。

指数関数

expovariate() は、Web サーバへ到着するリクエストや放射線減衰率といったポアソン分布のために、到着するものや間隔の時間の値に便利です。

パレート、またはべき乗則の分布は多くの実際に目に見える現象と一致し、Chris Anderon の著書 ロングテール で広まりました。 paretovariate() 関数は、目に見えないリソース (人々の豊かさ、音楽家の需要、ブログへのこだわりなど) を割り当てるシミュレーションに便利です。

角度

(vonmisesvariate() で生成される) フォン・ミーゼス分布、または円周上の正規分布は、角度、カレンダーの日付、時間といった周期的な確率を計算するのに使用されます。

サイズ

betavariate() は、モデリング間のタスクといったアプリケーションや一般的にベイズ統計で利用されるベータ分布の値を生成します。

gammavariate() が生成するガンマ分布は、待ち時間、降雨量、計算エラーといったサイズをモデリングするために使用されます。

weibullvariate() が生成するワイブル分布は、不良解析、工業エンジニアリング、天気予報に使用されます。これは粒子または離散物体のサイズの分布を説明します。

See also

random
本モジュールの標準ライブラリドキュメント
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
Wikipedia: Mersenne Twister
Python で利用できる疑似乱数生成に関する記事
Wikipedia: Uniform distribution
統計における連続一様分布に関する記事
Bookmark and Share