100本ノック / 確率統計 / 確率・統計マーケティング応用100本ノック

製造業の意思決定科学をPythonで学ぶ|期待値・ベイズ・ロバスト最適化10本ノック

不確実性下で利益と供給責任を両立する:製造業の意思決定科学10本ノック

本記事では、架空の産業用センサメーカーを題材に、期待値最大化、リスク最小化、効用理論、意思決定木、ベイズ意思決定、MDP、POMDP、シナリオ分析、感度分析、ロバスト最適化を一続きの意思決定として扱います。

設備増強、外注、保全、調達、生産配分では、将来の需要や故障を完全には予測できません。そこで、単一の予測値を当てにいくのではなく、起こり得る状態、確率、損失、観測できない状態、想定外への耐性を分けて考えます。掲載データは Python で生成し、外部データには依存しません。

[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。
掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。

はじめに:この記事で扱う製造業の実務課題

対象企業は、受注変動の大きい産業用センサを生産しています。需要が強ければ設備増強が利益を生みますが、需要が弱ければ固定費が残ります。外注は需要への追随性を高める一方、単価と納期の変動を招きます。また、設備の劣化状態は完全には見えず、調達先の寸断や加工時間の悪化も起こり得ます。

本記事の目的は「最も当たりそうな予測を選ぶ」ことではありません。選択肢・不確実な状態・結果・価値基準・情報更新を明文化し、意思決定者が判断理由と見直し条件を説明できる状態をつくることです。

現場でよくある状況

  • 需要予測の平均値だけで設備投資を決め、下振れ時の固定費負担を見落とす
  • 期待利益が高い案を採用したものの、資金繰り上許容できない損失が発生する
  • センサ警報を故障確定とみなし、誤報による過剰保全が増える
  • 新情報が入っても、計画会議では古い確率や前提のまま議論が続く
  • 為替、材料高、物流寸断を個別に検討し、複合シナリオを比較していない
  • 最適解の数字だけが共有され、どの前提が変わると結論が逆転するか分からない

これらに共通するのは、不確実性を一つの安全係数へ押し込め、意思決定の構造を見えなくしていることです。

なぜこの問題は判断が難しいのか

同じデータを見ても、株主は期待利益、工場長は供給停止、財務部門は最大損失を重視するかもしれません。さらに、需要・設備状態・調達環境は時間とともに変化し、追加情報によって確率も更新されます。

したがって、単一KPIだけでは十分ではありません。本記事では、期待値、下方リスク、確実性等価、情報の価値、将来費用、事後確率、最悪ケース、結論が逆転する境界を併記します。モデルは意思決定を自動的に正当化するものではなく、前提とトレードオフを監査可能にする道具として使います。

今回扱うノックの全体像

No.テーマ主な問い主な出力
071期待値最大化確率を加味した平均利益が最大の案はどれか期待利益、機会損失
072リスク最小化悪い結果をどこまで抑えるべきか下方分位点、CVaR
073効用理論リスク許容度を選択へどう反映するか確実性等価
074意思決定木調査してから投資する価値はあるか枝別判断、期待価値
075ベイズ意思決定需要シグナルで判断をどう更新するか事後確率、推奨案
076MDP設備状態を見ながら保全をいつ行うか状態別方策、将来費用
077POMDP劣化状態が直接見えないときどう判断するか信念確率、点検閾値
078シナリオ分析複合的な外部環境に調達案が耐えるかシナリオ別利益
079感度分析どの前提で結論が逆転するか切替境界
080ロバスト最適化加工時間が悪化しても実行可能な計画は何か頑健な生産配分

Python 環境の準備

NumPy は数値計算と乱数生成、pandas は意思決定表、matplotlib は可視化に利用します。乱数 seed は 42 に固定します。japanize_matplotlib はグラフ内の日本語表示を安定させるために読み込みます。

import platform

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import japanize_matplotlib
from IPython.display import display

SEED = 42
rng = np.random.default_rng(SEED)
pd.set_option("display.max_columns", 20)
pd.set_option("display.float_format", lambda x: f"{x:,.2f}")

print(f"Python: {platform.python_version()}")
print(f"NumPy: {np.__version__} / pandas: {pd.__version__}")
print(f"乱数seed: {SEED}")
Python: 3.13.1
NumPy: 2.5.1 / pandas: 3.0.3
乱数seed: 42

架空データの作成

翌年度の能力方針として「現有設備を継続」「外注で柔軟化」「設備を増強」の3案を比較します。需要状態は弱気・標準・強気の3段階、利益の単位は百万円です。利益は売上から変動費、外注費、固定費、欠品ペナルティを控除した意思決定用の限界利益として定義します。

実務では、確率と利益を同じ担当者だけで決めず、営業・生産・調達・財務が根拠を持ち寄ります。また「利益」に含める費目と評価期間を揃えなければ、案同士を公平に比較できません。

states = ["弱気需要", "標準需要", "強気需要"]
plans = ["現有設備を継続", "外注で柔軟化", "設備を増強"]
probability = pd.Series([0.25, 0.50, 0.25], index=states, name="事前確率")
profit = pd.DataFrame(
    [[42, 58, 64], [35, 66, 86], [10, 70, 118]],
    index=plans,
    columns=states,
)

likelihood = pd.DataFrame(
    [[0.65, 0.25, 0.10], [0.25, 0.50, 0.25], [0.10, 0.25, 0.65]],
    index=["弱い受注シグナル", "通常シグナル", "強い受注シグナル"],
    columns=states,
)

print("意思決定用利益(百万円)")
display(profit)
print("需要状態の事前確率")
display(probability.to_frame().T)
print("各需要状態で受注シグナルが観測される確率")
display(likelihood)
意思決定用利益(百万円)
弱気需要 標準需要 強気需要
現有設備を継続 42 58 64
外注で柔軟化 35 66 86
設備を増強 10 70 118
需要状態の事前確率
弱気需要 標準需要 強気需要
事前確率 0.25 0.50 0.25
各需要状態で受注シグナルが観測される確率
弱気需要 標準需要 強気需要
弱い受注シグナル 0.65 0.25 0.10
通常シグナル 0.25 0.50 0.25
強い受注シグナル 0.10 0.25 0.65

No.071:期待値最大化 — 需要確率から能力方針を選ぶ

実務での意味

期待値最大化は、繰り返し可能な意思決定や、会社全体でリスクを分散できる投資案件の基準になります。設備増強・外注・現状維持を「最もありそうな需要」だけで比較せず、全状態を確率加重します。

分析・モデル化の考え方

行動 aa、状態 ss、利益 V(a,s)V(a,s)、状態確率 p(s)p(s) に対する期待利益は

E[V(a,S)]=sp(s)V(a,s)\mathbb{E}[V(a,S)]=\sum_s p(s)V(a,s)

です。さらに、各状態で事後的に最良だった利益との差をリグレット R(a,s)=maxaV(a,s)V(a,s)R(a,s)=\max_{a'}V(a',s)-V(a,s) として示します。期待値が近い案では、平均リグレットも判断材料になります。

Pythonで確認する

expected_profit = profit.mul(probability, axis=1).sum(axis=1)
regret = profit.max(axis=0) - profit
decision_071 = pd.DataFrame({
    "期待利益(百万円)": expected_profit,
    "期待リグレット(百万円)": regret.mul(probability, axis=1).sum(axis=1),
}).sort_values("期待利益(百万円)", ascending=False)
display(decision_071)

ax = decision_071["期待利益(百万円)"].sort_values().plot(
    kind="barh", color=["#9ecae1", "#6baed6", "#2171b5"], figsize=(8, 4)
)
ax.set_title("能力方針別の期待利益")
ax.set_xlabel("期待利益(百万円)")
ax.set_ylabel("能力方針")
ax.grid(axis="x", alpha=0.3)
plt.tight_layout()
plt.show()

print(f"期待値基準の推奨案: {expected_profit.idxmax()}")
期待利益(百万円) 期待リグレット(百万円)
設備を増強 67.00 8.00
外注で柔軟化 63.25 11.75
現有設備を継続 55.50 19.50

png

期待値基準の推奨案: 設備を増強

結果の読み取り

事前確率のもとでは「設備を増強」の期待利益が最大です。ただし、弱気需要では利益が10百万円まで下がり、事後的な最良案との差も大きくなります。期待値は長期平均の基準であり、単年度の資金制約や供給責任を自動的に反映しません。投資決裁では、次のノックの下方リスクとセットで提示します。

No.072:リスク最小化 — 平均利益だけでなく下振れを測る

実務での意味

利益の平均が高くても、一定確率で大きな損失が出る案は、借入条件や雇用維持の制約に合わない場合があります。ここでは需要誤差、立上げ遅延、外注単価変動を含む仮想年度を発生させます。

分析・モデル化の考え方

利益 YY の5%分位点は「20回に1回程度の悪い水準」を表します。下側10%の平均である下方CVaRを

CVaR10%lower(Y)=E[YYq0.1(Y)]\mathrm{CVaR}_{10\%}^{\mathrm{lower}}(Y)=\mathbb{E}[Y\mid Y\le q_{0.1}(Y)]

として計算します。利益なので値が大きいほど望ましく、損失表現のCVaRとは符号が逆である点に注意します。

Pythonで確認する

risk_rng = np.random.default_rng(SEED)
n_sim = 5000
simulated_profit = pd.DataFrame({
    "現有設備を継続": risk_rng.normal(56, 7, n_sim),
    "外注で柔軟化": risk_rng.normal(64, 12, n_sim),
    "設備を増強": risk_rng.normal(69, 21, n_sim)
        - risk_rng.binomial(1, 0.05, n_sim) * risk_rng.uniform(12, 28, n_sim),
})

risk_rows = []
for plan in simulated_profit:
    values = simulated_profit[plan]
    q10 = values.quantile(0.10)
    risk_rows.append({
        "能力方針": plan,
        "平均利益": values.mean(),
        "5%分位点": values.quantile(0.05),
        "下方CVaR 10%": values[values <= q10].mean(),
        "利益40未満の確率": (values < 40).mean(),
    })
risk_table = pd.DataFrame(risk_rows).set_index("能力方針")
display(risk_table)

fig, ax = plt.subplots(figsize=(9, 4.5))
for plan in simulated_profit:
    ax.hist(simulated_profit[plan], bins=45, alpha=0.35, density=True, label=plan)
ax.axvline(40, color="red", linestyle="--", label="警戒水準 40")
ax.set_title("能力方針別の利益分布(5,000回シミュレーション)")
ax.set_xlabel("年間利益(百万円)")
ax.set_ylabel("確率密度")
ax.grid(alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
平均利益 5%分位点 下方CVaR 10% 利益40未満の確率
能力方針
現有設備を継続 55.86 44.19 43.50 0.01
外注で柔軟化 63.99 44.07 42.66 0.02
設備を増強 67.76 32.93 30.47 0.10

png

結果の読み取り

設備増強は平均利益が高い一方、分布の左裾が長く、利益40百万円未満の確率も高くなります。現有設備案は上振れが小さい代わりに下方CVaRが良好です。「平均利益を最大化する案」と「厳しい年でも耐えやすい案」が異なることを、経営会議で明示できます。許容下限は分析者が決めず、財務制約や事業継続条件から設定します。

No.073:効用理論 — リスク許容度を確実性等価へ変換する

実務での意味

同じ利益分布でも、財務余力のある企業と、単年度赤字を避ける必要がある企業では選択が変わります。効用理論は、その違いを「慎重・積極的」という言葉だけでなく数式へ落とします。

分析・モデル化の考え方

絶対的リスク回避度を α>0\alpha>0 とする指数効用 U(y)=eαyU(y)=-e^{-\alpha y} を使います。確実性等価(CE)は

CE=1αlog(E[eαY])\mathrm{CE}=-\frac{1}{\alpha}\log\left(\mathbb{E}[e^{-\alpha Y}]\right)

です。これは「不確実な利益 YY と同じ効用をもつ確実な利益」を意味します。α\alpha が大きいほど下振れを強く嫌います。

Pythonで確認する

alphas = [0.01, 0.03, 0.06]
ce = pd.DataFrame(index=simulated_profit.columns)
for alpha in alphas:
    ce[f"α={alpha:.02f}"] = [
        -np.log(np.mean(np.exp(-alpha * simulated_profit[plan]))) / alpha
        for plan in simulated_profit
    ]
display(ce)

ax = ce.plot(marker="o", figsize=(8, 4.5))
ax.set_title("リスク回避度による確実性等価の変化")
ax.set_xlabel("能力方針")
ax.set_ylabel("確実性等価(百万円)")
ax.grid(alpha=0.3)
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()
α=0.01 α=0.03 α=0.06
現有設備を継続 55.62 55.13 54.39
外注で柔軟化 63.25 61.77 59.53
設備を増強 65.46 60.88 53.93

png

結果の読み取り

リスク回避度が小さいと平均利益の高い案が評価されやすい一方、リスク回避度を上げると分散の大きな設備増強案の確実性等価が低下します。重要なのは、α\alpha を恣意的に選ばないことです。過去の投資選択、許容損失、資金余力との整合を確認し、複数の α\alpha を並べて結論の安定性を見ます。

No.074:意思決定木 — 市場テスト後に投資する価値を測る

実務での意味

大型投資を直ちに決める代わりに、先行顧客へのテスト販売や試作ラインで情報を得てから規模を選べる場合があります。意思決定木は「調査する/しない」「結果を見て投資する/しない」の順序を明示します。

分析・モデル化の考え方

終端利益から後ろ向きに計算します。意思決定ノードでは最大値、確率ノードでは期待値を取るロールバックを行います。テスト費用を CC とすると、情報付き戦略の価値は

Vtest=C+zP(z)maxaE[V(a,S)z]V_{\mathrm{test}}=-C+\sum_z P(z)\max_a\mathbb{E}[V(a,S)\mid z]

です。比較対象は、情報なしで今すぐ最善行動を選ぶ価値です。

Pythonで確認する

pilot = pd.DataFrame({
    "テスト結果": ["好感触", "慎重反応"],
    "発生確率": [0.55, 0.45],
    "本格増強の成功確率": [0.75, 0.30],
})
expand_success, expand_failure = 105, 5
small_scale_profit, test_cost = 52, 2
pilot["増強の条件付期待利益"] = (
    pilot["本格増強の成功確率"] * expand_success
    + (1 - pilot["本格増強の成功確率"]) * expand_failure
)
pilot["結果観測後の選択"] = np.where(
    pilot["増強の条件付期待利益"] > small_scale_profit, "本格増強", "小規模改善"
)
pilot["選択後価値"] = pilot[["増強の条件付期待利益"]].iloc[:, 0].clip(lower=small_scale_profit)

prior_success = (pilot["発生確率"] * pilot["本格増強の成功確率"]).sum()
no_test_value = max(
    prior_success * expand_success + (1 - prior_success) * expand_failure,
    small_scale_profit,
)
test_value = (pilot["発生確率"] * pilot["選択後価値"]).sum() - test_cost
display(pilot)
display(pd.DataFrame({
    "戦略": ["直ちに最善案を実行", "市場テスト後に分岐"],
    "期待価値(百万円)": [no_test_value, test_value],
}).set_index("戦略"))

ax = pilot.set_index("テスト結果")[["増強の条件付期待利益", "選択後価値"]].plot(
    kind="bar", figsize=(8, 4.5), color=["#9ecae1", "#2171b5"]
)
ax.axhline(small_scale_profit, color="red", linestyle="--", label="小規模改善")
ax.set_title("市場テスト結果ごとの条件付価値")
ax.set_xlabel("市場テスト結果")
ax.set_ylabel("利益(百万円)")
ax.grid(axis="y", alpha=0.3)
ax.legend()
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()
テスト結果 発生確率 本格増強の成功確率 増強の条件付期待利益 結果観測後の選択 選択後価値
0 好感触 0.55 0.75 80.00 本格増強 80.00
1 慎重反応 0.45 0.30 35.00 小規模改善 52.00
期待価値(百万円)
戦略
直ちに最善案を実行 59.75
市場テスト後に分岐 65.40

png

結果の読み取り

好感触なら本格増強、慎重反応なら小規模改善へ切り替える戦略が選ばれます。テスト費用を差し引いても、結果を見て行動を変えられるため、直ちに一案へ固定するより期待価値が高くなります。情報は集めるだけでは価値を生みません。結果ごとの行動ルール、テスト期間中の機会損失、競合参入の影響まで事前に定めます。

No.075:ベイズ意思決定 — 受注シグナルで需要確率を更新する

実務での意味

展示会反応、引合件数、先行受注などが入ったとき、需要予測を感覚的に上書きすると過剰反応が起こります。ベイズ更新は、元の見通しと新しいシグナルの信頼度を一貫して統合します。

分析・モデル化の考え方

シグナル zz を観測した後の状態確率は

P(sz)=P(zs)P(s)sP(zs)P(s)P(s\mid z)=\frac{P(z\mid s)P(s)}{\sum_{s'}P(z\mid s')P(s')}

です。各シグナルについて事後確率を求め、その確率で期待利益が最大になる行動を選びます。シグナルの識別力は尤度 P(zs)P(z\mid s) に現れます。

Pythonで確認する

posterior_rows = []
decision_rows = []
for signal, row in likelihood.iterrows():
    joint = row * probability
    posterior = joint / joint.sum()
    posterior_rows.append(posterior.rename(signal))
    conditional_ev = profit.mul(posterior, axis=1).sum(axis=1)
    decision_rows.append({
        "シグナル": signal,
        "推奨案": conditional_ev.idxmax(),
        "条件付期待利益": conditional_ev.max(),
        "シグナル発生確率": joint.sum(),
    })
posterior_table = pd.DataFrame(posterior_rows)
bayes_decisions = pd.DataFrame(decision_rows).set_index("シグナル")
display(posterior_table)
display(bayes_decisions)

ax = posterior_table.plot(kind="bar", stacked=True, figsize=(8, 4.5), colormap="Blues")
ax.set_title("受注シグナル観測後の需要状態確率")
ax.set_xlabel("観測したシグナル")
ax.set_ylabel("事後確率")
ax.grid(axis="y", alpha=0.3)
ax.legend(title="需要状態", bbox_to_anchor=(1.02, 1), loc="upper left")
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()
弱気需要 標準需要 強気需要
弱い受注シグナル 0.52 0.40 0.08
通常シグナル 0.17 0.67 0.17
強い受注シグナル 0.08 0.40 0.52
推奨案 条件付期待利益 シグナル発生確率
シグナル
弱い受注シグナル 外注で柔軟化 51.48 0.31
通常シグナル 設備を増強 68.00 0.38
強い受注シグナル 設備を増強 90.16 0.31

png

結果の読み取り

弱いシグナルでは弱気需要の確率が上がり、強いシグナルでは強気需要の確率が上がります。その結果、推奨案もシグナルに応じて変わります。実務では、尤度を過去の予測精度から更新し、営業担当者によるシグナル定義のばらつきを監視します。事後確率と採用判断を保存すると、後から予測と意思決定を分けて評価できます。

No.076:MDP — 設備状態を踏まえて保全時期を最適化する

実務での意味

設備保全は「今期の保全費を抑える」と「将来の故障費を防ぐ」の時間的トレードオフです。状態を正常・劣化・危険に分類し、運転継続または予防保全を毎期選ぶ問題として扱います。

分析・モデル化の考え方

マルコフ意思決定過程(MDP)では、状態 ss、行動 aa、遷移確率 P(ss,a)P(s'\mid s,a)、即時費用 c(s,a)c(s,a) を定めます。残り tt 期の最小期待費用はベルマン方程式

Jt(s)=mina[c(s,a)+γsP(ss,a)Jt1(s)]J_t(s)=\min_a\left[c(s,a)+\gamma\sum_{s'}P(s'\mid s,a)J_{t-1}(s')\right]

で後ろ向きに計算します。ここでは6期、割引率 γ=0.97\gamma=0.97 とします。

Pythonで確認する

machine_states = ["正常", "劣化", "危険"]
actions = ["運転継続", "予防保全"]
transition = {
    "運転継続": np.array([[0.78, 0.20, 0.02], [0.08, 0.65, 0.27], [0.02, 0.18, 0.80]]),
    "予防保全": np.array([[0.94, 0.06, 0.00], [0.82, 0.16, 0.02], [0.68, 0.25, 0.07]]),
}
immediate_cost = {
    "運転継続": np.array([3, 12, 45]),
    "予防保全": np.array([15, 18, 25]),
}
horizon, gamma = 6, 0.97
value = np.zeros((horizon + 1, len(machine_states)))
policy = []
for t in range(1, horizon + 1):
    q = np.column_stack([
        immediate_cost[a] + gamma * transition[a] @ value[t - 1]
        for a in actions
    ])
    value[t] = q.min(axis=1)
    policy.append([actions[i] for i in q.argmin(axis=1)])

policy_table = pd.DataFrame(
    policy, index=[f"残り{t}期" for t in range(1, horizon + 1)], columns=machine_states
)
display(policy_table)
display(pd.DataFrame(value[1:], index=policy_table.index, columns=machine_states).tail(1))

ax = pd.DataFrame(value[1:], columns=machine_states, index=range(1, horizon + 1)).plot(
    marker="o", figsize=(8, 4.5)
)
ax.set_title("残存期間と状態別の最小期待累積費用")
ax.set_xlabel("残り期間(期)")
ax.set_ylabel("期待累積費用")
ax.grid(alpha=0.3)
plt.tight_layout()
plt.show()
正常 劣化 危険
残り1期 運転継続 運転継続 予防保全
残り2期 運転継続 予防保全 予防保全
残り3期 運転継続 予防保全 予防保全
残り4期 運転継続 予防保全 予防保全
残り5期 運転継続 予防保全 予防保全
残り6期 運転継続 予防保全 予防保全
正常 劣化 危険
残り6期 31.14 45.58 54.99

png

結果の読み取り

正常状態では運転継続、危険状態では予防保全が基本方策になります。劣化状態の判断は残り期間と将来費用に依存し、目先の保全費だけでは決まりません。本番では、故障による安全・品質・納期影響を費用へ含め、保全後の回復確率を実績から推定します。状態分類が粗すぎると同じ「劣化」に異なる設備挙動が混在します。

No.077:POMDP — 見えない劣化を信念確率で管理する

実務での意味

現実には設備の真の劣化状態を直接観測できません。振動センサの警報には誤報と見逃しがあり、警報の有無だけで保全を決めると、過剰保全または突発停止が増えます。

分析・モデル化の考え方

部分観測マルコフ意思決定過程(POMDP)では、状態そのものではなく、劣化している確率 b=P(D)b=P(D) という信念状態を使います。警報 AA に対する更新は

P(DA)=P(AD)P(D)P(AD)P(D)+P(AH)P(H)P(D\mid A)=\frac{P(A\mid D)P(D)}{P(A\mid D)P(D)+P(A\mid H)P(H)}

です。簡略化のため、点検費8、点検しない場合の期待故障費 40b40b を比較し、b>8/40=0.20b>8/40=0.20 なら点検します。

Pythonで確認する

prior_degraded = 0.25
p_alarm_given_degraded = 0.80
p_alarm_given_healthy = 0.15

def posterior_degraded(alarm):
    p_obs_d = p_alarm_given_degraded if alarm else 1 - p_alarm_given_degraded
    p_obs_h = p_alarm_given_healthy if alarm else 1 - p_alarm_given_healthy
    return (p_obs_d * prior_degraded) / (
        p_obs_d * prior_degraded + p_obs_h * (1 - prior_degraded)
    )

beliefs = pd.Series({
    "観測前": prior_degraded,
    "警報あり": posterior_degraded(True),
    "警報なし": posterior_degraded(False),
}, name="劣化の信念確率")
pomdp_table = beliefs.to_frame()
pomdp_table["点検しない期待費用"] = 40 * pomdp_table["劣化の信念確率"]
pomdp_table["推奨行動"] = np.where(
    pomdp_table["点検しない期待費用"] > 8, "点検する", "運転継続"
)
display(pomdp_table)

belief_grid = np.linspace(0, 1, 101)
fig, ax = plt.subplots(figsize=(8, 4.5))
ax.plot(belief_grid, 40 * belief_grid, label="点検しない期待費用")
ax.plot(belief_grid, np.full_like(belief_grid, 8), label="点検費用")
ax.axvline(0.20, color="red", linestyle="--", label="点検閾値")
ax.scatter(beliefs.values, 40 * beliefs.values, color="black", zorder=3)
ax.set_title("劣化の信念確率と点検判断")
ax.set_xlabel("劣化している信念確率")
ax.set_ylabel("期待費用")
ax.grid(alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
劣化の信念確率 点検しない期待費用 推奨行動
観測前 0.25 10.00 点検する
警報あり 0.64 25.60 点検する
警報なし 0.07 2.91 運転継続

png

結果の読み取り

警報ありでは劣化確率が点検閾値を上回り、警報なしでは下回ります。警報を状態そのものと見なさず、「警報精度と事前劣化率から更新された確率」として扱う点が重要です。実務では、連続する観測、劣化遷移、点検後の情報、停止時間を含む多期間POMDPへ拡張します。閾値は安全基準を下回るよう最適化してはならず、法令・品質上の強制点検を優先します。

No.078:シナリオ分析 — 調達戦略を複合ショックで比較する

実務での意味

調達先を選ぶ際、通常時の購入単価だけを見ると、材料高・円安・物流寸断が重なったときの供給損失を見落とします。シナリオ分析では、整合的な外部環境を複数作り、各戦略の弱点を可視化します。

分析・モデル化の考え方

シナリオは予測値の上下幅ではなく、因果的に整合する変数の組です。ここでは国内中心、二社分散、海外集中を、通常、材料高、円安、物流寸断の利益で比較します。確率加重値に加え、最悪利益と最大リグレットも示します。シナリオ確率の信頼性が低い場合は、期待値より順位の安定性を重視します。

Pythonで確認する

sourcing_profit = pd.DataFrame(
    [[56, 45, 52, 50], [64, 53, 43, 38], [72, 48, 25, 12]],
    index=["国内中心", "二社分散", "海外集中"],
    columns=["通常", "材料高", "円安", "物流寸断"],
)
scenario_prob = pd.Series([0.45, 0.20, 0.20, 0.15], index=sourcing_profit.columns)
scenario_summary = pd.DataFrame({
    "確率加重利益": sourcing_profit.mul(scenario_prob, axis=1).sum(axis=1),
    "最悪利益": sourcing_profit.min(axis=1),
    "最大リグレット": (sourcing_profit.max(axis=0) - sourcing_profit).max(axis=1),
})
display(sourcing_profit)
display(scenario_summary)

ax = sourcing_profit.T.plot(marker="o", figsize=(9, 4.5))
ax.set_title("調達戦略別のシナリオ利益")
ax.set_xlabel("外部環境シナリオ")
ax.set_ylabel("年間利益(百万円)")
ax.grid(alpha=0.3)
ax.legend(title="調達戦略")
plt.tight_layout()
plt.show()
通常 材料高 円安 物流寸断
国内中心 56 45 52 50
二社分散 64 53 43 38
海外集中 72 48 25 12
確率加重利益 最悪利益 最大リグレット
国内中心 52.10 45 16
二社分散 53.70 38 12
海外集中 48.80 12 38

png

結果の読み取り

海外集中は通常時の利益が最大ですが、円安と物流寸断で大きく下がります。国内中心は上振れが小さい代わりに最悪利益が高く、二社分散は中間的です。採用案は確率加重利益だけで決めず、供給停止が顧客ラインへ与える損失、代替認定に要する期間、BCP上の最低供給量を制約として確認します。

No.079:感度分析 — 強気需要確率の切替境界を知る

実務での意味

意思決定表の確率は固定された真実ではありません。強気需要の確率を営業部門が25%から20%へ見直しただけで結論が変わるなら、その投資案は前提に敏感です。感度分析は、再決裁が必要になる境界を示します。

分析・モデル化の考え方

弱気需要確率を0.25に固定し、強気需要確率 pHp_H を0〜0.50で動かします。標準需要確率は 0.75pH0.75-p_H です。各案の期待利益は pHp_H の一次関数となり、二案の直線が交差する点が選択の切替境界です。複数パラメータが不確かな場合は、二次元感度分析や確率的感度分析へ拡張します。

Pythonで確認する

p_high_grid = np.linspace(0, 0.50, 101)
sensitivity = pd.DataFrame(index=p_high_grid)
for plan in plans:
    sensitivity[plan] = (
        0.25 * profit.loc[plan, "弱気需要"]
        + (0.75 - p_high_grid) * profit.loc[plan, "標準需要"]
        + p_high_grid * profit.loc[plan, "強気需要"]
    )
sensitivity.index.name = "強気需要確率"
sensitivity["推奨案"] = sensitivity.idxmax(axis=1)
switches = sensitivity["推奨案"].ne(sensitivity["推奨案"].shift())
display(sensitivity.loc[switches, [*plans, "推奨案"]])

fig, ax = plt.subplots(figsize=(8, 4.5))
for plan in plans:
    ax.plot(sensitivity.index, sensitivity[plan], label=plan)
ax.axvline(0.25, color="black", linestyle="--", label="基準確率 0.25")
ax.set_title("強気需要確率に対する期待利益の感度")
ax.set_xlabel("強気需要の確率")
ax.set_ylabel("期待利益(百万円)")
ax.grid(alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
現有設備を継続 外注で柔軟化 設備を増強 推奨案
強気需要確率
0.00 54.00 58.25 55.00 外注で柔軟化
0.12 54.72 60.65 60.76 設備を増強

png

結果の読み取り

強気需要確率が低い領域では柔軟な外注案、高くなると設備増強案が有利になり、切替境界を表から確認できます。基準値25%が境界から十分離れていれば、小さな予測修正で結論は変わりません。境界に近い場合は、段階投資、解約可能な契約、追加調査など「後から変えられる選択肢」に価値があります。

No.080:ロバスト最適化 — 加工時間悪化にも耐える生産配分を作る

実務での意味

名目標準時間だけで製品ミックスを最適化すると、工具摩耗や作業者差で加工時間が悪化した日に計画が実行不能になります。ロバスト最適化は、あらかじめ定めた不確実性集合の全ケースで制約を守る計画を求めます。

分析・モデル化の考え方

製品A・Bの数量を xA,xBx_A,x_B、限界利益を 8,118,11、利用可能時間を480分とします。加工時間シナリオ集合 U\mathcal U に対して

maxxA,xB 8xA+11xB\max_{x_A,x_B}\ 8x_A+11x_B

tA(u)xA+tB(u)xB480(uU)t_A^{(u)}x_A+t_B^{(u)}x_B\le480\quad(\forall u\in\mathcal U)

を満たします。全ケースを守るため保守的になりますが、計画変更・残業・納期遅延を抑えられます。ここでは整数格子を全列挙します。

Pythonで確認する

time_scenarios = pd.DataFrame(
    [[2.0, 3.0], [2.4, 3.0], [2.0, 3.8]],
    index=["名目", "製品A加工悪化", "製品B加工悪化"],
    columns=["製品A", "製品B"],
)
candidates = pd.DataFrame(
    [(a, b) for a in range(161) for b in range(121)],
    columns=["製品A", "製品B"],
)
candidates["限界利益"] = 8 * candidates["製品A"] + 11 * candidates["製品B"]
for scenario, times in time_scenarios.iterrows():
    candidates[f"使用時間_{scenario}"] = candidates[["製品A", "製品B"]] @ times

nominal_feasible = candidates["使用時間_名目"] <= 480
robust_feasible = candidates[[f"使用時間_{s}" for s in time_scenarios.index]].max(axis=1) <= 480
nominal_best = candidates.loc[candidates.loc[nominal_feasible, "限界利益"].idxmax()]
robust_best = candidates.loc[candidates.loc[robust_feasible, "限界利益"].idxmax()]
comparison = pd.DataFrame([nominal_best, robust_best], index=["名目最適", "ロバスト最適"])
display(time_scenarios)
display(comparison[["製品A", "製品B", "限界利益", *[f"使用時間_{s}" for s in time_scenarios.index]]])

fig, ax = plt.subplots(figsize=(8, 5))
sample = candidates.iloc[::25]
ax.scatter(sample.loc[robust_feasible.iloc[::25], "製品A"],
           sample.loc[robust_feasible.iloc[::25], "製品B"],
           s=8, alpha=0.25, label="全シナリオで実行可能")
ax.scatter(nominal_best["製品A"], nominal_best["製品B"], marker="x", s=120,
           color="red", label="名目最適")
ax.scatter(robust_best["製品A"], robust_best["製品B"], marker="*", s=180,
           color="green", label="ロバスト最適")
ax.set_title("加工時間の不確実性を考慮した生産配分")
ax.set_xlabel("製品A 生産数量")
ax.set_ylabel("製品B 生産数量")
ax.grid(alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
製品A 製品B
名目 2.00 3.00
製品A加工悪化 2.40 3.00
製品B加工悪化 2.00 3.80
製品A 製品B 限界利益 使用時間_名目 使用時間_製品A加工悪化 使用時間_製品B加工悪化
名目最適 159.00 54.00 1,866.00 480.00 543.60 523.20
ロバスト最適 125.00 60.00 1,660.00 430.00 480.00 478.00

png

結果の読み取り

名目最適は利益が高い一方、製品Bの加工時間が悪化すると480分を超えます。ロバスト最適は限界利益を一部手放し、全シナリオで実行可能な配分を選びます。この差は「頑健性の保険料」です。不確実性集合を過度に広げると計画が保守的になるため、過去実績、設備条件、改善余地から範囲を定期更新し、超過確率を許す確率制約とも比較します。

対象ノックを通して見える実務上の示唆

  1. 期待値と下方リスクを分ける:平均利益が最大の案と、悪い年に耐える案は一致しないことがあります。
  2. 価値基準を明文化する:効用や許容損失を使い、「慎重に判断する」を再現可能なルールへ変えます。
  3. 情報取得後の行動まで決める:市場テストやセンサ導入は、観測結果で意思決定が変わるときに価値を持ちます。
  4. 確率は更新する:ベイズ更新により、事前見通しと新情報を一貫して統合し、判断履歴を残せます。
  5. 時間と状態を扱う:MDPでは、今の費用だけでなく、行動が将来状態へ与える影響を評価します。
  6. 見えない状態を確率で持つ:POMDPの信念状態により、警報の誤報・見逃しを判断へ織り込めます。
  7. 前提の境界を共有する:シナリオ分析と感度分析は、結論が変わる条件を部門間の共通言語にします。
  8. 実行可能性を守る:ロバスト最適化は、名目利益と計画変更リスクの交換条件を数値化します。

実務導入する場合に必要なこと

1. 意思決定台帳を作る

誰が、いつ、何を決め、選択肢・状態・評価期間・KPI・制約をどう置いたかを記録します。予測精度と意思決定品質を混同せず、「当時の情報では妥当だったか」を後から評価できるようにします。

2. 確率と損失の根拠を管理する

需要確率、故障遷移、警報精度、停止損失はデータ期間と推定方法を紐づけます。件数が少ない場合は点推定を過信せず、専門家判断の幅や信用区間も残します。安全・法令・品質の強制条件は、利益との交換対象にしません。

3. 複数KPIと承認境界を合意する

期待利益、CVaR、供給遵守率、最大停止時間などを併記し、どの水準を下回ると案を却下・再審査するか決めます。効用係数やシナリオ確率は経営判断を隠すための数字ではなく、判断を説明するための仮定として扱います。

4. 小規模な意思決定から閉ループ化する

一つの設備群や製品群で「予測→提案→承認→実行→結果→確率更新」を回します。モデル提案の採否と理由を残し、再現性を確認した後にERP・MES・設備データとの連携や自動化範囲を広げます。

5. モデル監視と例外手順を設計する

確率校正、予測誤差、状態遷移、制約違反、手動介入を監視します。災害、新製品、設備改造など学習範囲外では自動判断を停止し、人が代替手順へ切り替えられる責任分界を定めます。

まとめ

No.071〜No.080では、期待利益から始め、下方リスクと効用、段階的な意思決定、ベイズ更新、設備状態の時間変化、部分観測、複合シナリオ、前提感度、頑健な生産配分まで扱いました。

製造業の意思決定では、予測精度を上げるだけで不確実性は消えません。重要なのは、何が不確かか、どの損失を避けるか、情報が入ったらどう行動を変えるか、どの範囲まで計画を守るかを明文化することです。小さな意思決定で前提と実績を記録し、継続的に確率・損失・制約を更新することで、分析を会議資料から運用可能な意思決定基盤へ進められます。

法人向けのご相談

数理工房では、製造業における不確実性下の意思決定、需要・故障リスク評価、シミュレーション、数理最適化、意思決定基盤の構想策定から実装・内製化支援までご相談を承ります。

  • 設備投資・外注・在庫・調達戦略の定量比較
  • 期待値、CVaR、シナリオ、感度分析を用いた経営判断支援
  • 設備保全のMDP/POMDPモデル、センサ情報を用いたベイズ更新
  • 需要・加工時間の不確実性を考慮したロバスト最適化
  • ERP・MES・設備データをつなぐ意思決定基盤の設計
  • Python・統計・シミュレーション・最適化を扱う法人研修

📩 お問い合わせ: surikobo.co.jp/contact
まずはお気軽にご相談ください。