Pythonでランダムな数を作ろう!乱数生成の基本と活用例

はい、承知いたしました。Pythonでの乱数生成について、基本から応用、そして活用例までを詳細に解説した記事を記述します。約5000語を目指します。


Pythonでランダムな数を作ろう!乱数生成の基本と活用例

はじめに:なぜランダムな数が必要なのか?

コンピュータの世界では、様々な場面で「ランダムな数」が必要とされます。ゲームにおける敵キャラクターの動き、シミュレーションにおける不確実性の表現、統計分析におけるデータのサンプリング、あるいはセキュリティにおけるパスワードや暗号鍵の生成など、その用途は多岐にわたります。ランダム性を取り入れることで、プログラムの予測不可能性を高めたり、現実世界のランダムな事象を模倣したり、統計的な手法を実行したりすることが可能になります。

Pythonは、この「ランダムな数」、つまり乱数を生成するための強力かつ使いやすい機能を提供しています。標準ライブラリに含まれるrandomモジュールをはじめ、科学技術計算でよく使われるnumpyライブラリのnumpy.randomモジュール、そしてセキュリティ用途に特化したsecretsモジュールなど、目的に応じて最適なツールを選択できます。

この記事では、Pythonでの乱数生成の基本から始め、主要なモジュールの使い方、様々な分布からの乱数生成、シードの概念、そして実際の活用例までを詳細に解説します。乱数生成の原理的な話から、具体的なコード例までを網羅し、読者の皆様がPythonで自在に乱数を扱えるようになることを目指します。

さあ、Pythonで「ランダム」な世界を探求していきましょう!

乱数とは何か?:真の乱数と擬似乱数

「ランダムな数」と一言で言っても、実はいくつかの種類があります。コンピュータが生成する乱数は、一般的に「擬似乱数(Pseudo-Random Number)」と呼ばれます。これに対して、「真の乱数(True Random Number)」または物理乱数と呼ばれるものも存在します。

真の乱数(True Random Number)

真の乱数は、予測不可能な物理的な現象(例えば、放射性崩壊、大気ノイズ、熱雑音、マウスの動きやキーボードの入力タイミングなど)を観測し、それを数値データに変換することで生成されます。これらの現象は原理的に予測不可能であるため、生成される乱数も真にランダムであるとみなされます。真の乱数は、暗号学的に非常に強力な乱数が必要とされる場面(例えば、暗号鍵の生成など)で利用されることがあります。ただし、物理的なプロセスに依存するため、生成速度は比較的遅い傾向があります。

擬似乱数(Pseudo-Random Number)

一方、擬似乱数は、コンピュータのアルゴリズムによって生成される乱数です。特定の初期値(「シード(Seed)」と呼ばれます)から出発し、数学的な計算を繰り返し行うことで、あたかもランダムに見える数列を生成します。重要なのは、同じシードを使えば、全く同じ数列が再現されるという点です。

なぜ「擬似」なのかというと、アルゴリズムに基づいて生成されるため、原理的には予測可能だからです。シードとアルゴリズムが分かれば、次にどのような数が出てくるかを正確に計算できます。しかし、生成される数列は統計的にランダムな性質(例えば、均等な分布、相関がないことなど)を持っているため、多くの用途では十分なランダム性があるとみなされます。

コンピュータで一般的に利用される乱数は、この擬似乱数です。擬似乱数のメリットは、物理的な装置が不要で、高速に大量の乱数を生成できることです。また、シードを固定することで、プログラムの動作を再現できるため、デバッグや実験の再現性確保に役立ちます。

Pythonの標準ライブラリrandomモジュールやnumpy.randomモジュールが生成するのは、この擬似乱数です。セキュリティ用途に特化したsecretsモジュールは、オペレーティングシステムが提供する暗号学的に安全な乱数源を利用しており、これはより真の乱数に近い性質を持ちますが、厳密には「暗号学的擬似乱数(Cryptographically Secure Pseudo-Random Number)」と呼ばれることが多いです。これもアルゴリズムに基づきますが、その内部状態を推測することが計算上非常に困難になるように設計されています。

シード(Seed)の重要性

擬似乱数生成において、「シード」は非常に重要な概念です。シードは乱数生成アルゴズムの「種」となる初期値です。

  • 同じシード → 同じ乱数列: シードが同じであれば、何度プログラムを実行しても全く同じ擬似乱数のシーケンスが生成されます。
  • 異なるシード → 異なる乱数列: シードが異なれば、生成される乱数列も異なります。
  • シードを指定しない場合: Pythonのrandomモジュールなどでは、シードを指定しない場合、システムの現在時刻など、実行ごとに異なる値がデフォルトのシードとして使われます。これにより、実行ごとに異なる乱数列が得られます。

デバッグや実験の再現性が必要な場合は、明示的にシードを指定することが不可欠です。逆に、毎回異なる結果を得たい場合は、シードを指定しないか、またはシステム時刻などによって変化する値をシードとして利用します。

Pythonのrandomモジュール入門

Python標準ライブラリのrandomモジュールは、様々な種類の擬似乱数を生成するための基本的な機能を提供します。ゲーム、シミュレーション、統計モデリングなど、幅広い用途で利用できます。

まず、randomモジュールを使うには、以下のようにインポートします。

python
import random

最も基本的な乱数生成:random.random()

random.random()関数は、0.0以上1.0未満の範囲で一様な浮動小数点数を生成します。

“`python
import random

0.0から1.0未満の浮動小数点数を生成

random_float = random.random()
print(f”生成された浮動小数点数: {random_float}”)

複数回実行してみる

print(“連続して生成:”)
for _ in range(5):
print(random.random())
“`

実行するたびに異なる0.0から1.0未満の値が出力されます。これは、デフォルトでシステム時刻などがシードに使われているためです。

整数を生成する:random.randint()

指定した範囲内の整数(両端を含む)を生成するには、random.randint(a, b)関数を使います。これはa <= N <= bとなる整数Nを返します。

“`python
import random

1から6までのサイコロの目をシミュレート

dice_roll = random.randint(1, 6)
print(f”サイコロの目: {dice_roll}”)

0から100までの乱数を生成

percentage = random.randint(0, 100)
print(f”ランダムなパーセンテージ: {percentage}%”)
“`

randint(a, b)ab含む点に注意が必要です。これは他の言語やライブラリの乱数関数と異なる場合があるため、特に注意が必要です。例えば、0から9までの乱数が欲しい場合はrandom.randint(0, 9)とします。

範囲指定で浮動小数点数を生成する:random.uniform()

random.random()は0.0から1.0未満の範囲でしたが、任意の範囲aからbまでの浮動小数点数を生成したい場合は、random.uniform(a, b)を使います。これはa <= N <= b または b <= N <= a となる浮動小数点数Nを返します。どちらの端を含むかは、浮動小数点数の丸め処理によって決まるため、基本的には両端を含む可能性があると考えて良いでしょう。

“`python
import random

10.0から20.0の間の浮動小数点数を生成

temp = random.uniform(10.0, 20.0)
print(f”ランダムな気温: {temp}°C”)

a > b の場合でも機能する

another_float = random.uniform(5.0, -5.0)
print(f”逆順の範囲で生成: {another_float}”) # -5.0 から 5.0 の間になる
“`

uniform(a, b)は、a < bの場合もa > bの場合も正しく機能します。

randomモジュールのさらに便利な関数

randomモジュールには、これら基本的な関数以外にも、様々な便利な機能が用意されています。

シーケンスからの選択

リストやタプルなどのシーケンスから要素をランダムに選択する関数は、非常に頻繁に使われます。

  • random.choice(seq): シーケンスseqから要素を1つランダムに選択します。

“`python
import random

選択肢のリスト

colors = [“赤”, “青”, “緑”, “黄”, “黒”, “白”]

リストから一つ選択

chosen_color = random.choice(colors)
print(f”選ばれた色: {chosen_color}”)

空のシーケンスを渡すと IndexError が発生するので注意

random.choice([]) # -> IndexError: Cannot choose from an empty sequence

“`

  • random.choices(population, weights=None, cum_weights=None, k=1): シーケンスpopulationから要素をランダムにk個選択します。重複を許します(復元抽出)weightsまたはcum_weightsを指定することで、要素ごとの選択確率を制御できます。

“`python
import random

選択肢のリスト

fruits = [“りんご”, “バナナ”, “オレンジ”]

重複を許して3つ選択

chosen_fruits = random.choices(fruits, k=3)
print(f”重複ありで選ばれた果物 (k=3): {chosen_fruits}”)

重み(確率)を指定して選択

りんご: 50%, バナナ: 30%, オレンジ: 20% の確率で選択

weighted_fruits = random.choices(fruits, weights=[0.5, 0.3, 0.2], k=5)
print(f”重み付きで選ばれた果物 (k=5): {weighted_fruits}”)
“`

weightsは各要素に対応する非負の重みのリストです。重みの合計は1である必要はありません(内部で正規化されます)。cum_weightsは累積重みのリストで、weightsよりも効率が良い場合があります。

  • random.sample(population, k): シーケンスpopulationから要素をランダムにk個選択します。重複を許しません(非復元抽出)kpopulationの長さより小さくまたは等しくなければなりません。

“`python
import random

0から9までの数字

numbers = list(range(10))

重複なしで5つ選択

sample_numbers = random.sample(numbers, k=5)
print(f”重複なしで選ばれた数字 (k=5): {sample_numbers}”)

リストの要素数を超えるkを指定すると ValueError が発生

random.sample(numbers, k=12) # -> ValueError: Sample size larger than population or is negative

“`

random.sample()は、抽選やカードゲームのように、一度選んだものは二度と選ばない場合に適しています。

リストのシャッフル

リストの要素をランダムな順序に並べ替える(シャッフルする)には、random.shuffle(x)関数を使います。この関数はリストをインプレースで(元のリスト自体を変更して)シャッフルします。タプルや文字列などのイミュータブルなシーケンスには使えません。

“`python
import random

カードの山を表現するリスト

cards = [“A”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”, “10”, “J”, “Q”, “K”]

print(f”シャッフル前: {cards}”)

リストをシャッフル

random.shuffle(cards)

print(f”シャッフル後: {cards}”)

もう一度シャッフル

random.shuffle(cards)
print(f”もう一度シャッフル後: {cards}”)
“`

shuffle()はリスト自体を変更するので、元の順序を残したい場合はリストのコピーを作成してからシャッフルする必要があります。

“`python
import random

original_list = [1, 2, 3, 4, 5]
list_copy = original_list[:] # リストのコピーを作成

print(f”元のリスト: {original_list}”)

random.shuffle(list_copy) # コピーをシャッフル

print(f”シャッフルされたコピー: {list_copy}”)
print(f”元のリストは変更されない: {original_list}”)
“`

異なる確率分布からの乱数生成

randomモジュールは、一様分布以外の様々な確率分布に従う乱数を生成する関数も提供しています。これらの関数は、統計モデリングやシミュレーションで特定の確率分布を模倣したい場合に非常に役立ちます。

  • 正規分布 (Gaussian Distribution): 釣鐘型の分布です。自然界の様々なデータ(身長、測定誤差など)によく現れます。random.gauss(mu, sigma) または random.normalvariate(mu, sigma) を使います。muは平均、sigmaは標準偏差です。

“`python
import random
import matplotlib.pyplot as plt # 分布を可視化するためにmatplotlibを使用

平均0、標準偏差1の正規分布に従う乱数を1000個生成

gaussian_numbers = [random.gauss(0, 1) for _ in range(1000)]

ヒストグラムで分布を確認

plt.hist(gaussian_numbers, bins=50, density=True, alpha=0.6, color=’g’)
plt.title(‘Gaussian Distribution’)
plt.xlabel(‘Value’)
plt.ylabel(‘Frequency’)
plt.grid(True)
plt.show()
“`

  • 対数正規分布 (Log-normal Distribution): その対数が正規分布に従う確率変数によって生成される分布です。株価や生物のサイズなど、値が正であり、大きい値の出現頻度が低いデータによく適合します。random.lognormvariate(mu, sigma)を使います。musigmaは、対応する正規分布の平均と標準偏差です。

“`python
import random
import matplotlib.pyplot as plt

log(X)が平均0、標準偏差0.5の正規分布に従うような対数正規分布乱数を1000個生成

lognormal_numbers = [random.lognormvariate(0, 0.5) for _ in range(1000)]

plt.hist(lognormal_numbers, bins=50, density=True, alpha=0.6, color=’b’)
plt.title(‘Log-normal Distribution’)
plt.xlabel(‘Value’)
plt.ylabel(‘Frequency’)
plt.grid(True)
plt.show()
“`

  • 指数分布 (Exponential Distribution): ある事象が発生するまでの待機時間などに使われる分布です。過去の出来事とは独立に、将来のいつの時点でも一定の確率で事象が発生する場合に適用されます(無記憶性)。random.expovariate(lambd)を使います。lambdはレートパラメータ(1/平均)です。

“`python
import random
import matplotlib.pyplot as plt

レートパラメータ lambd = 0.5 (平均待機時間 = 1/0.5 = 2) の指数分布乱数を1000個生成

exponential_numbers = [random.expovariate(0.5) for _ in range(1000)]

plt.hist(exponential_numbers, bins=50, density=True, alpha=0.6, color=’r’)
plt.title(‘Exponential Distribution (lambda=0.5)’)
plt.xlabel(‘Value’)
plt.ylabel(‘Frequency’)
plt.grid(True)
plt.show()
“`

  • ベータ分布 (Beta Distribution): 0と1の間の値を取る連続確率分布です。統計学で確率の事前分布や事後分布としてよく使われます。random.betavariate(alpha, beta)を使います。alphabetaは形状パラメータで、どちらも0より大きい必要があります。

“`python
import random
import matplotlib.pyplot as plt

alpha=10, beta=10 のベータ分布乱数を1000個生成 (中央付近にピーク)

beta_numbers_1 = [random.betavariate(10, 10) for _ in range(1000)]

alpha=1, beta=5 のベータ分布乱数を1000個生成 (0に近い方にピーク)

beta_numbers_2 = [random.betavariate(1, 5) for _ in range(1000)]

plt.figure(figsize=(10, 5))

plt.subplot(1, 2, 1)
plt.hist(beta_numbers_1, bins=50, density=True, alpha=0.6, color=’purple’)
plt.title(‘Beta Distribution (alpha=10, beta=10)’)
plt.xlabel(‘Value’)
plt.ylabel(‘Frequency’)
plt.grid(True)

plt.subplot(1, 2, 2)
plt.hist(beta_numbers_2, bins=50, density=True, alpha=0.6, color=’orange’)
plt.title(‘Beta Distribution (alpha=1, beta=5)’)
plt.xlabel(‘Value’)
plt.ylabel(‘Frequency’)
plt.grid(True)

plt.tight_layout()
plt.show()
“`

  • ガンマ分布 (Gamma Distribution): 指数分布を一般化した分布です。複数の独立した指数分布に従う待ち時間の和などに使われます。random.gammavariate(alpha, beta)を使います。alpha(形状パラメータ)とbeta(尺度パラメータ)は0より大きい必要があります。

他にも、三角分布 (random.triangular(low, high, mode))、フォン・ミーゼス分布 (random.vonmisesvariate(mu, kappa))、パレート分布 (random.paretovariable(alpha))、ワイブル分布 (random.weibullvariate(alpha, beta)) など、様々な分布からの乱数生成関数が用意されています。これらの関数を使うことで、特定の統計的性質を持つデータをシミュレートすることが可能です。

シードと状態:再現性とデバッグ

前述したように、Pythonのrandomモジュールが生成するのは擬似乱数です。同じシードを使えば同じ乱数列が生成されます。この性質は、デバッグや再現可能な実験を行う際に非常に重要になります。

シードの設定:random.seed()

random.seed(a=None, version=2)関数を使って、乱数生成器の初期シードを設定できます。

  • a: シードとして使用する値です。Noneの場合は、システムの現在時刻やオペレーティングシステムが提供する乱数源など、システム固有の値が使用されます。整数、浮動小数点数、文字列、バイト列、またはその他のハッシュ可能なオブジェクトを指定できます。デフォルトはNoneです。
  • version: シード値を内部状態に変換する方法を指定します。バージョン2(デフォルト)は、すべてのオブジェクト型をサポートする最新の方法です。バージョン1は古い方法で、互換性のために残されています。通常はデフォルトのままにしておけば問題ありません。

例を見てみましょう。

“`python
import random

シードを設定しない場合(実行ごとに異なる)

print(“— シードなし —“)
print(random.random())
print(random.randint(1, 10))
print(random.choice([10, 20, 30]))

print(“— シード固定 (整数) —“)

同じ整数シードを設定

random.seed(42)
print(random.random())
print(random.randint(1, 10))
print(random.choice([10, 20, 30]))

もう一度同じシードを設定して実行

random.seed(42)
print(“— シード固定 (整数) 再現 —“)
print(random.random())
print(random.randint(1, 10))
print(random.choice([10, 20, 30]))

print(“— シード固定 (文字列) —“)

文字列シードを設定

random.seed(“my_seed”)
print(random.random())
print(random.randint(1, 10))
print(random.choice([10, 20, 30]))

もう一度同じ文字列シードを設定して実行

random.seed(“my_seed”)
print(“— シード固定 (文字列) 再現 —“)
print(random.random())
print(random.randint(1, 10))
print(random.choice([10, 20, 30]))
“`

上記のコードを実行すると、「シードなし」の部分は実行ごとに結果が変わりますが、「シード固定 (整数)」の2回目と「シード固定 (文字列)」の2回目は、それぞれのシードで初めて実行したときと全く同じ結果が出力されることがわかります。

デバッグ中に特定のバグが乱数によってのみ発生する場合、問題が発生したときのシード値を特定し、そのシードを使ってプログラムを実行することで、バグを再現しやすくなります。

乱数生成器の状態の保存と復元

randomモジュールは、乱数生成器の現在の内部状態を取得し、後でその状態を復元する機能も提供します。これは、特定の時点での乱数生成器の状態を保存しておき、後で全く同じ乱数列をその時点から再開したい場合に便利です。

  • random.getstate(): 現在の乱数生成器の内部状態を表すオブジェクトを返します。
  • random.setstate(state): getstate()によって返された状態オブジェクトを使って、乱数生成器の状態を復元します。

“`python
import random

初期の乱数をいくつか生成

random.seed(100)
print(“— 初期状態 —“)
print(random.random())
print(random.randint(1, 100))

現在の状態を保存

saved_state = random.getstate()
print(“状態を保存しました。”)

さらに乱数を生成 (状態は進む)

print(“— 状態が進んだ後 —“)
print(random.random())
print(random.randint(1, 100))

状態を保存した時点に戻す

random.setstate(saved_state)
print(“状態を復元しました。”)

復元後の乱数生成 (保存した時点から同じ乱数列が続く)

print(“— 状態復元後 —“)
print(random.random()) # 状態が進んだ後の1回目の値と同じになるはず
print(random.randint(1, 100)) # 状態が進んだ後の2回目の値と同じになるはず
“`

この機能は、複雑なシミュレーションなどで、計算の途中経過を保存し、後で全く同じ状態から再開したい場合に役立ちます。

セキュリティのための乱数生成:secretsモジュール

randomモジュールが生成する擬似乱数は、統計的にはランダムに見えますが、アルゴリズムに基づいており、シードが分かれば数列が予測できてしまいます。このような擬似乱数は、シミュレーションやゲームなどの用途には十分ですが、パスワード、トークン、暗号鍵などのセキュリティに関わる生成物には絶対に使用してはいけません。予測可能な乱数を使ってしまうと、攻撃者によって容易に推測され、システムが危険にさらされる可能性があります。

セキュリティ用途には、Python 3.6以降で標準ライブラリに追加されたsecretsモジュールを使用する必要があります。secretsモジュールは、オペレーティングシステムが提供する、暗号学的に安全な乱数源を利用します。これは、内部状態を外部から推測することが極めて困難になるように設計されています。

secretsモジュールもrandomモジュールと同様にインポートして使用します。

python
import secrets

secretsモジュールが提供する主な関数を紹介します。

  • secrets.randbelow(n): 0以上n未満の範囲で、暗号学的に安全な整数を生成します。secrets.randint(0, n-1)と同等ですが、偏りがないことが保証されます。

“`python
import secrets

0から99までの安全な整数を生成

secure_int = secrets.randbelow(100)
print(f”安全な整数 (0-99): {secure_int}”)

1から6までの安全なサイコロの目をシミュレート (1から7未満なので 1-6)

secure_dice_roll = secrets.randbelow(6) + 1
print(f”安全なサイコロの目 (1-6): {secure_dice_roll}”)
“`

  • secrets.randbits(k): kビットの安全な整数を生成します。

“`python
import secrets

128ビットの安全な整数を生成 (セッショントークンなどに利用可能)

secure_bits = secrets.randbits(128)
print(f”128ビットの安全な整数: {secure_bits}”)
print(f”その値のビット長: {secure_bits.bit_length()}”)
“`

  • secrets.choice(seq): シーケンスseqから暗号学的に安全な方法で要素を1つ選択します。random.choice()のセキュリティ強化版です。

“`python
import secrets

使用可能な文字セット

alphabet = “abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()”

上記の文字セットからランダムに1文字選択

secure_char = secrets.choice(alphabet)
print(f”安全に選ばれた文字: {secure_char}”)
“`

secretsモジュールには、さらにパスワードやトークンを簡単に生成するための関数も用意されています。

  • secrets.token_bytes(nbytes=None): nbytesのバイト数を持つ安全なバイト列を生成します。nbytesを指定しない場合、適当なデフォルト値が使用されます。
  • secrets.token_hex(nbytes=None): nbytesのバイト数に対応する、安全な16進数文字列を生成します。
  • secrets.token_urlsafe(nbytes=None): nbytesのバイト数に対応する、URL-safeなテキスト文字列を生成します。これはウェブアプリケーションのトークンなどに適しています。

“`python
import secrets

32バイトの安全なバイト列トークンを生成

token_bytes = secrets.token_bytes(32)
print(f”安全なバイト列トークン (32 bytes): {token_bytes.hex()}”)

16バイトに対応する安全な16進数トークンを生成 (長さ32文字)

token_hex = secrets.token_hex(16)
print(f”安全な16進数トークン (16 bytes equivalent): {token_hex}”)

32バイトに対応する安全なURL-safeトークンを生成 (長さはnbytesに依存)

token_urlsafe = secrets.token_urlsafe(32)
print(f”安全なURL-safeトークン (32 bytes equivalent): {token_urlsafe}”)
“`

これらの関数は、ユーザー認証後のセッショントークン、パスワードリセットリンクのトークン、APIキーなど、予測されては困る重要な識別子やキーを生成するのに最適です。

重要な注意点: セキュリティ目的以外で通常のシミュレーションなどにsecretsモジュールを使用する必要はありません。secretsモジュールはシステムの乱数源に依存するため、randomモジュールよりも若干遅い場合があります。

科学技術計算のための乱数生成:numpy.random

データ分析、機械学習、物理シミュレーションなど、科学技術計算の分野では、大量の乱数を高速に生成したり、多次元配列として扱ったり、様々な統計分布からの乱数を生成したりする必要が頻繁に発生します。このような用途には、Pythonの科学技術計算ライブラリであるNumPyが提供するnumpy.randomモジュールが非常に強力で便利です。

NumPyの乱数機能を使うには、まずNumPyをインストールし、インポートします。

bash
pip install numpy

python
import numpy as np

numpy.randomモジュールは、randomモジュールと似た機能を提供しますが、NumPyの配列 (ndarray) を効率的に扱える点が大きな違いです。

NumPyの基本的な乱数生成

NumPyの乱数関数は、通常、生成したい乱数の「形状 (shape)」を指定するsize引数を持っています。これにより、一度に多数の乱数を配列として生成できます。

  • np.random.rand(d0, d1, ..., dn): 0.0以上1.0未満の一様分布に従う浮動小数点数の配列を生成します。引数は配列の次元サイズを指定します。

“`python
import numpy as np

0.0から1.0未満の浮動小数点数1つ

print(np.random.rand())

3つの要素を持つ1次元配列

print(np.random.rand(3))

2×3の2次元配列

print(np.random.rand(2, 3))

2x3x4の3次元配列

print(np.random.rand(2, 3, 4))
“`

  • np.random.randn(d0, d1, ..., dn): 標準正規分布(平均0、標準偏差1)に従う浮動小数点数の配列を生成します。これも引数で形状を指定します。

“`python
import numpy as np

標準正規分布に従う浮動小数点数1つ

print(np.random.randn())

5つの要素を持つ1次元配列

print(np.random.randn(5))

3×3の2次元配列

print(np.random.randn(3, 3))
“`

  • np.random.randint(low, high=None, size=None, dtype=int): low以上high未満の範囲の整数を生成します。highが指定されない場合は、0以上low未満の範囲となります。sizeで形状を指定できます。

“`python
import numpy as np

1から6未満 (つまり1から5) の整数を1つ

print(np.random.randint(1, 6))

0から10未満の整数を5つ持つ1次元配列

print(np.random.randint(10, size=5))

1から100未満の整数を持つ 2×4 の2次元配列

print(np.random.randint(1, 100, size=(2, 4)))
“`

  • np.random.random_sample(size=None): random.random()np.random.rand()と同様に、0.0以上1.0未満の一様分布に従う浮動小数点数の配列を生成します。こちらはsize引数で形状を指定するのが一般的です。

“`python
import numpy as np

0.0から1.0未満の浮動小数点数を 3×2 の配列で生成

print(np.random.random_sample(size=(3, 2)))
“`

  • np.random.uniform(low=0.0, high=1.0, size=None): 指定した範囲lowからhighまでの、一様分布に従う浮動小数点数の配列を生成します。random.uniform()のNumPy版で、配列生成に対応しています。

“`python
import numpy as np

10.0から20.0の間の浮動小数点数を 4つの要素を持つ配列で生成

print(np.random.uniform(10.0, 20.0, size=4))
“`

  • np.random.normal(loc=0.0, scale=1.0, size=None): 正規分布に従う浮動小数点数の配列を生成します。locは平均(mean)、scaleは標準偏差(standard deviation)です。random.gauss()random.normalvariate()のNumPy版で、配列生成に対応しています。

“`python
import numpy as np

平均10、標準偏差2の正規分布に従う乱数を 2×2 の配列で生成

print(np.random.normal(loc=10.0, scale=2.0, size=(2, 2)))
“`

NumPyでの様々な分布からの乱数生成

NumPyのnumpy.randomモジュールは、標準ライブラリのrandomモジュールよりもはるかに多くの確率分布からの乱数生成に対応しています。二項分布、ポアソン分布、ベータ分布、ガンマ分布、指数分布、ロジスティック分布、対数正規分布、多項分布など、統計学や確率論でよく使われる様々な分布を利用できます。

例えば、ポアソン分布(一定時間内に稀に発生する事象の回数をモデル化するのに使われます)に従う乱数を生成するにはnp.random.poisson()を使います。

“`python
import numpy as np
import matplotlib.pyplot as plt

平均発生率 lambda=5 のポアソン分布に従う乱数を1000個生成

poisson_numbers = np.random.poisson(lam=5, size=1000)

ヒストグラムで分布を確認

plt.hist(poisson_numbers, bins=np.arange(poisson_numbers.max()+1)-0.5, density=True, alpha=0.6, color=’cyan’, edgecolor=’black’)
plt.title(‘Poisson Distribution (lambda=5)’)
plt.xlabel(‘Number of Events’)
plt.ylabel(‘Frequency’)
plt.grid(True)
plt.show()
“`

NumPyでのシャッフルと選択

NumPyでも、配列のシャッフルや要素の選択を行う関数が提供されています。これらはNumPy配列を効率的に処理できます。

  • np.random.shuffle(x): 配列xをインプレースで(元の配列を変更して)シャッフルします。random.shuffle()と同様ですが、NumPy配列に対応しています。多次元配列の場合、最初の軸に沿ってシャッフルされます。

“`python
import numpy as np

1次元配列のシャッフル

arr1d = np.array([1, 2, 3, 4, 5])
print(f”シャッフル前 (1D): {arr1d}”)
np.random.shuffle(arr1d)
print(f”シャッフル後 (1D): {arr1d}”)

2次元配列のシャッフル (行がシャッフルされる)

arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(f”シャッフル前 (2D):\n{arr2d}”)
np.random.shuffle(arr2d)
print(f”シャッフル後 (2D):\n{arr2d}”)
“`

  • np.random.permutation(x): shuffleと似ていますが、配列をインプレースで変更するのではなく、シャッフルされた新しい配列を返します。引数に整数nを指定すると、0からn-1までの整数の順列を返します。

“`python
import numpy as np

1次元配列の順列 (新しい配列が返る)

arr1d = np.array([1, 2, 3, 4, 5])
permuted_arr1d = np.random.permutation(arr1d)
print(f”元の配列 (permutation): {arr1d}”)
print(f”順列された新しい配列: {permuted_arr1d}”)

0から4までの整数の順列

print(f”0から4までの順列: {np.random.permutation(5)}”)
“`

  • np.random.choice(a, size=None, replace=True, p=None): 与えられた1次元配列aから、ランダムに要素を選択します。
    • size: 選択する要素の数(配列の形状)。
    • replace: 重複を許すか(True)許さないか(False)。デフォルトはTrue
    • p: 各要素が選択される確率を指定する配列。指定しない場合は一様確率。

“`python
import numpy as np

elements = np.array([“A”, “B”, “C”, “D”])

重複を許して3つ選択

chosen_replace = np.random.choice(elements, size=3, replace=True)
print(f”重複ありで選択: {chosen_replace}”)

重複なしで3つ選択 (sizeはaの長さ以下である必要あり)

chosen_no_replace = np.random.choice(elements, size=3, replace=False)
print(f”重複なしで選択: {chosen_no_replace}”)

確率を指定して選択

A:10%, B:20%, C:30%, D:40% の確率

probabilities = [0.1, 0.2, 0.3, 0.4]
chosen_weighted = np.random.choice(elements, size=5, replace=True, p=probabilities)
print(f”確率を指定して選択: {chosen_weighted}”)
“`

np.random.choice(a, size=k, replace=False)は、random.sample(a, k)のNumPy版に相当します。np.random.choice(a, size=k, replace=True)は、random.choices(a, k=k)のNumPy版に相当します。

NumPyの乱数生成器オブジェクト (新しいAPI)

NumPy 1.17以降では、乱数生成の推奨される新しい方法として、乱数生成器オブジェクト (Generator) を利用するAPIが導入されました。以前のnp.random.から直接関数を呼び出す方法はレガシーAPIと位置づけられています(後方互換性のために残されています)。

新しいAPIでは、まず乱数生成器オブジェクトを作成し、そのオブジェクトのメソッドとして乱数生成関数を呼び出します。これにより、複数の独立した乱数ストリームを管理しやすくなります。

“`python
import numpy as np

推奨される方法で乱数生成器オブジェクトを作成

rng = np.random.default_rng()

Generatorオブジェクトを使って乱数を生成

print(rng.random(3)) # 0.0-1.0の一様乱数3個
print(rng.integers(1, 10, size=5)) # 1以上10未満の整数乱数5個
print(rng.normal(loc=0, scale=1, size=(2, 2))) # 正規分布乱数 2×2配列
“`

シードを設定したい場合は、default_rng()の引数として指定します。

“`python
import numpy as np

シードを指定してGeneratorオブジェクトを作成

rng1 = np.random.default_rng(seed=123)
rng2 = np.random.default_rng(seed=123) # 同じシードで別のオブジェクトを作成

print(“— rng1 —“)
print(rng1.random(3))
print(rng1.integers(1, 10, size=5))

print(“— rng2 (同じシード) —“)
print(rng2.random(3)) # rng1と全く同じ乱数が出力される
print(rng2.integers(1, 10, size=5))
“`

新しいAPI (Generator) は、より統計的に優れた乱数生成アルゴリズム(PCG64など)をデフォルトで使用しており、並列処理との相性も良いため、特別な理由がない限りこちらを使用することが推奨されます。

レガシーAPI (np.random.) は、内部で単一のグローバルな乱数生成器インスタンスを使用しています。複数の場所でnp.random.seed()を呼び出すと、他の部分の乱数生成に影響を与えてしまう可能性があります。一方、新しいAPIでは、必要に応じて複数のGeneratorオブジェクトを作成し、それぞれに独立したシードを設定して使用できるため、コードの見通しが良くなり、意図しない乱数系列の干渉を防ぐことができます。

乱数生成の活用例

Pythonの乱数生成機能は、非常に多くの分野で活用されています。代表的な例をいくつか紹介します。

1. シミュレーション

  • モンテカルロ法: 乱数を使って確率的な事象を多数シミュレートすることで、複雑な問題の近似解を求める手法です。例えば、円周率の計算、金融商品の価格変動予測、物理システムの挙動予測などに使われます。
    “`python
    import random
    import matplotlib.pyplot as plt

    モンテカルロ法による円周率の推定

    num_points = 10000 # 試行回数
    points_in_circle = 0 # 円内に入った点の数
    x_coords = []
    y_coords = []
    colors = []

    for _ in range(num_points):
    # -1から1の範囲でランダムな座標 (正方形内)
    x = random.uniform(-1, 1)
    y = random.uniform(-1, 1)
    x_coords.append(x)
    y_coords.append(y)

    # 原点からの距離が1以下なら円内
    if x**2 + y**2 <= 1:
        points_in_circle += 1
        colors.append('red')
    else:
        colors.append('blue')
    

    推定される円周率

    (円の面積)/(正方形の面積) = (πr^2)/( (2r)^2 ) = π/4

    よって、π = 4 * (円内の点の数) / (全点の数)

    estimated_pi = 4 * points_in_circle / num_points
    print(f”試行回数: {num_points}”)
    print(f”円周率の推定値: {estimated_pi}”)

    可視化 (NumPyとMatplotlibが必要)

    if num_points <= 10000: # 点が多いと描画が重くなるため制限
    plt.figure(figsize=(6, 6))
    plt.scatter(x_coords, y_coords, c=colors, s=1) # sは点のサイズ
    circle = plt.Circle((0, 0), 1, color=’black’, fill=False)
    ax = plt.gca()
    ax.add_patch(circle)
    ax.set_aspect(‘equal’, adjustable=’box’)
    plt.title(‘Monte Carlo Simulation for Pi’)
    plt.xlim(-1.1, 1.1)
    plt.ylim(-1.1, 1.1)
    plt.show()
    “`
    * 物理シミュレーション: 分子のブラウン運動、粒子の拡散、気体の運動などを確率的にモデル化する際に乱数が使われます。
    * ゲーム開発: キャラクターの動き、アイテムの出現率、イベントの発生、カードゲームのデッキシャッフルなど、ゲームにランダム性を取り入れるために必須です。

2. データ分析と機械学習

  • データのサンプリング: 大量のデータから一部をランダムに抽出(サンプリング)して分析を行う場合にrandom.samplenp.random.choiceが使われます。
  • データセットの分割: 機械学習モデルの訓練、検証、テストのためにデータセットを分割する際に、データをランダムにシャッフルしてから分割することが一般的です(例: トレーニングセットとテストセットへの分割)。random.shufflenp.random.shuffle、あるいはscikit-learnのtrain_test_split関数(内部で乱数を使用)が利用されます。
  • ブートストラップ法: 観測されたデータから重複を許して何度もサンプリング(リサンプリング)を行い、統計量のばらつきを推定する手法です。random.choicesnp.random.choice(..., replace=True)が使われます。
  • ニューラルネットワークの初期値: ニューラルネットワークの重みをランダムに初期化する際に乱数が使われます。

3. アルゴリズム

  • ランダム化アルゴリズム: アルゴリズムの決定過程に乱数を取り入れることで、問題を効率的に解いたり、最悪ケースの発生確率を低くしたりする手法です。例えば、クイックソートのピボット選択、ラビン-ミラー素数判定法などがあります。
    “`python
    import random

    def randomized_quicksort(data):
    if len(data) <= 1:
    return data
    # ランダムにピボットを選択
    pivot = random.choice(data)
    # ピボットより小さい要素、等しい要素、大きい要素に分割
    less = [x for x in data if x < pivot]
    equal = [x for x in data if x == pivot]
    greater = [x for x in data if x > pivot]
    # 再帰的にソート
    return randomized_quicksort(less) + equal + randomized_quicksort(greater)

    my_list = [9, 3, 1, 4, 6, 8, 7, 5, 2]
    sorted_list = randomized_quicksort(my_list)
    print(f”元のリスト: {my_list}”)
    print(f”ソートされたリスト: {sorted_list}”)
    “`

4. セキュリティ

  • パスワード生成: 推測されにくいランダムなパスワードを生成する際にsecretsモジュールが使われます。
    “`python
    import secrets
    import string

    def generate_secure_password(length=12):
    characters = string.ascii_letters + string.digits + string.punctuation
    # 各カテゴリから少なくとも1文字含めるようにする (より安全なパスワードのために)
    password = [
    secrets.choice(string.ascii_lowercase),
    secrets.choice(string.ascii_uppercase),
    secrets.choice(string.digits),
    secrets.choice(string.punctuation)
    ]
    # 残りの文字をランダムに選択
    password += [secrets.choice(characters) for _ in range(length – 4)]
    # リストをシャッフルして並べ替える
    secrets.SystemRandom().shuffle(password) # secretsモジュールにはshuffleがないため、SystemRandomインスタンスを使う
    return ”.join(password)

    print(f”安全なパスワード: {generate_secure_password(16)}”)
    ``
    secretsモジュール自体にはshuffleがありませんが、secrets.SystemRandomクラスはrandom.Randomクラスと同様のメソッドを持ち、OSの安全な乱数源を使用します。通常はこちらを使ってシャッフルします。
    * **トークン生成**: セッションID、パスワードリセットトークン、APIキーなど、推測困難な一時的な識別子を生成する際に
    secrets.token_hexsecrets.token_urlsafeが使われます。
    * **暗号鍵の生成**: 非常に高いランダム性が要求される暗号鍵の生成には、
    secretsモジュールや、より専門的な暗号ライブラリ(例:cryptography`ライブラリなど、OSの安全な乱数源を利用するもの)が使われます。

5. 教育

  • 統計学のデモンストレーション: 中心極限定理の確認や、様々な確率分布の形状を視覚的に示す際に、大量の乱数生成と可視化(Matplotlibなどを使用)が行われます。

注意点とベストプラクティス

Pythonで乱数を使う際に注意すべき点と、より良いコードを書くためのベストプラクティスをまとめます。

  1. 擬似乱数であることを理解する: randomモジュールやnumpy.randomが生成するのは擬似乱数であり、真の乱数ではありません。予測可能であることを常に意識し、用途に応じて適切なツールを選びましょう。
  2. セキュリティ用途にはsecretsを使う: パスワード、トークン、暗号鍵など、セキュリティ上重要な乱数が必要な場合は、必ずsecretsモジュールを使用してください。randomモジュールは絶対に避けるべきです。
  3. 科学技術計算にはNumPyを使う: 大量の乱数が必要な場合、配列として扱いたい場合、特定の統計分布から生成したい場合は、numpy.randomモジュールが効率的で多機能です。
  4. シードの管理:
    • デバッグ時や再現可能な実験(論文の結果再現など)が必要な場合は、必ず明示的にシードを設定しましょう (random.seed() または np.random.seed(), 新しいNumPy APIでは np.random.default_rng(seed=...))。
    • 毎回異なる結果を得たい場合は、シードを設定しないか、実行ごとに変化する値(例: システム時刻)をシードに使いましょう(デフォルトの動作)。
    • 複数の独立した乱数ストリームが必要な場合は、NumPyの新しいGeneratorオブジェクトを利用することを検討しましょう。
  5. 効率: 大量の乱数を生成する場合は、リスト内包表記でrandomモジュールの関数をループで呼び出すよりも、NumPyの乱数関数でsize引数を使って一度に配列として生成する方がはるかに高速です。
  6. NumPy乱数APIの選択: NumPy 1.17以降では、新しい乱数生成器API (np.random.default_rng()) の使用が推奨されています。特別な理由がなければ新しいAPIを使うようにしましょう。古いAPI (np.random.) は将来的に変更される可能性があります。
  7. 分布のパラメータを理解する: randomnumpy.randomの分布関数(正規分布、指数分布など)を使う際は、引数(平均、標準偏差、レートなど)がそれぞれどのような統計的意味を持つのかを理解しておくことが重要です。

まとめ

Pythonは乱数生成のための強力で多様なツールを提供しています。

  • randomモジュール: 一般的な擬似乱数生成(シミュレーション、ゲーム、サンプリングなど)に便利です。様々な分布からの生成や、リストのシャッフル、シーケンスからの選択が可能です。シード管理により再現性を確保できます。
  • secretsモジュール: パスワードやトークンなど、暗号学的に安全な乱数が必要なセキュリティ用途に特化しています。OSが提供する安全な乱数源を利用します。
  • numpy.randomモジュール: 科学技術計算やデータ分析で大量の乱数を効率的に生成し、NumPy配列として扱いたい場合に最適です。より多くの統計分布に対応しており、新しいGeneratorAPIはシード管理や並列処理に優れています。

それぞれのモジュールの特徴を理解し、目的に応じて適切に使い分けることが重要です。乱数は、プログラムにランダム性や不確実性をもたらし、シミュレーションからセキュリティまで、幅広い応用を可能にします。

この記事で紹介した基本的な使い方や様々な関数、活用例、注意点を参考に、ぜひPythonでの乱数生成をマスターし、様々なプロジェクトに役立ててください。乱数の奥深さとその応用範囲の広さを体験できるはずです。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール