Mímisbrunnr知恵の泉

← シミュレーション 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 前提:対照変量法(アンチセティック) | 関連:シミュレーション最適化

要点(BLUF)

1. 問題:差を見たいのにノイズが邪魔

シミュレーションでよくあるのは「構成Aと構成B、どちらが良いか」という比較です(窓口1個 vs 2個、発注点 s=20 vs 30…)。知りたいのは差 Δ=E[YA]E[YB]\Delta = E[Y_A] - E[Y_B]。これを推定 Δ^=YˉAYˉB\hat{\Delta} = \bar{Y}_A - \bar{Y}_B するとき、分散は

Var(Δ^)=Var(YˉA)+Var(YˉB)2Cov(YˉA,YˉB)\text{Var}(\hat{\Delta}) = \text{Var}(\bar{Y}_A) + \text{Var}(\bar{Y}_B) - 2\,\text{Cov}(\bar{Y}_A, \bar{Y}_B)

各構成を独立な乱数でシミュすると共分散はゼロ、分散は単純な和。これだと、差 Δ\Delta が小さいとき、各構成のノイズに埋もれて差が判別できません。

2. アイデア:同じ乱数で正の相関を作る

CRN は、構成AとBを同じ乱数列で走らせます。すると両者が共通の「運」を共有するので、Y^A\hat{Y}_AY^B\hat{Y}_B正に相関Cov>0\text{Cov} > 0)し、上式の 2Cov-2\text{Cov} が効いて分散が下がる。

直観は明快——「同じ客の流れ・同じサービス時間で2つの窓口数を比べれば、条件が揃うので差が純粋に構成の違いだけを反映する」。乱数が違うと、Aが空いていたのは構成が良いのか、たまたま客が少なかったのか区別できません。対照変量(対照変量法(アンチセティック))がの相関を作って和の分散を下げたのに対し、CRN はの相関を作って差の分散を下げます。

3. 実装:2つの M/M/1 を比較する

λ=0.8\lambda=0.8 で、サービス率 μ=1.0\mu=1.0μ=1.05\mu=1.05 の M/M/1 の平均待ち時間差を推定します。CRN では、到着間隔と「基準サービス時間(レート1の指数)」を両系で共有し、μ\mu でスケールするだけにします。

import numpy as np

def mean_wait(inter, base_serv, mu):
    """共有した到着・基準サービスから平均待ち時間を計算"""
    serv = base_serv / mu                       # 基準Exp(1)をExp(mu)にスケール
    n = len(inter)
    arr = np.cumsum(inter)
    start = np.empty(n); depart = np.empty(n)
    start[0] = arr[0]; depart[0] = start[0] + serv[0]
    for i in range(1, n):
        start[i] = max(arr[i], depart[i-1]); depart[i] = start[i] + serv[i]
    return (start - arr).mean()

lam = 0.8; n = 2000; reps = 2000

# 共通乱数法:1複製内で両系が同じ乱数を共有
rng = np.random.default_rng(70)
diffs_crn = np.empty(reps)
for r in range(reps):
    inter = rng.exponential(1/lam, n)
    base_serv = rng.exponential(1.0, n)
    diffs_crn[r] = mean_wait(inter, base_serv, 1.0) - mean_wait(inter, base_serv, 1.05)

# 独立乱数:各系で別々の乱数
rng = np.random.default_rng(71)
diffs_ind = np.empty(reps)
for r in range(reps):
    iA = rng.exponential(1/lam, n); sA = rng.exponential(1.0, n)
    iB = rng.exponential(1/lam, n); sB = rng.exponential(1.0, n)
    diffs_ind[r] = mean_wait(iA, sA, 1.0) - mean_wait(iB, sB, 1.05)

print(f"平均待ち時間の差(理論 4.000 - 3.048 = 0.952)")
print(f"  CRN 推定差={diffs_crn.mean():.4f}  分散={diffs_crn.var(ddof=1):.5f}")
print(f"  独立 推定差={diffs_ind.mean():.4f}  分散={diffs_ind.var(ddof=1):.5f}")
print(f"  分散減少率 = {diffs_ind.var(ddof=1)/diffs_crn.var(ddof=1):.1f} 倍")

出力:

平均待ち時間の差(理論 4.000 - 3.048 = 0.952)
  CRN 推定差=0.9296  分散=0.12261
  独立 推定差=0.9457  分散=1.24445
  分散減少率 = 10.1 倍

出力の意味:理論的な待ち時間差は Wq(μ=1.0)Wq(μ=1.05)=4.0003.048=0.952W_q(\mu{=}1.0) - W_q(\mu{=}1.05) = 4.000 - 3.048 = 0.952。CRN・独立どちらの推定差もこれに近い(不偏性は保たれる)が、差の分散は CRN 0.123 に対し独立 1.244 で約10倍の改善。つまり同じ精度で2構成の優劣を判定するのに、CRN は独立法の約1/10のサンプルで済む。サービス率をわずか0.05上げる効果のような小さな差を検出したいときほど、CRN は強力です。

4. 実装の作法と注意

数式の直観的意味

CRN は「条件をそろえて、純粋に構成の違いだけを見る」操作です。差の分散 Var(Δ^)=VarA+VarB2Cov\text{Var}(\hat{\Delta}) = \text{Var}_A + \text{Var}_B - 2\text{Cov} で、Cov\text{Cov} を大きくすれば差の分散が縮む。同じ乱数を使うと、両系が「同じ客の波・同じ運」を浴びるので出力が一緒に上下し(正相関)、その共通成分が引き算で消える。残るのは構成の違いに起因する差だけ——だからノイズが小さい。対照変量(対照変量法(アンチセティック))の鏡像で、あちらは「和の分散を負相関で消す」、こちらは「差の分散を正相関で消す」。どちらも相関を設計してばらつきを相殺するという分散減少の共通原理の現れです。「Aが良かったのは運か実力か」を切り分けるために条件をそろえる、という実験計画の発想とも一致します。

⚠️ よくある誤解・落とし穴

対応シミュレーション参照

本文の2つの M/M/1 比較(CRN default_rng(70)/独立 default_rng(71)、差の分散10倍減)。

関連ノート