100本ノック / 数理最適化 / 数理最適化100本ノック
製造業の動的計画法入門|在庫・設備保全をPythonで最適化
需要変動と設備劣化に先回りする:動的計画法・逐次意思決定10本ノック
架空の精密部品工場を題材に、今週の判断が来週以降へ及ぼす影響を織り込んだ生産・在庫・設備保全を扱います。対象は No.061〜No.070(動的計画法・逐次意思決定) です。
[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。
掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。
はじめに:この記事で扱う製造業の実務課題
製造現場では、増産、在庫保有、予防保全、設備更新のどれも、その期だけでは評価できません。本記事では、将来費用を含めて現在の行動を選ぶ動的計画法(Dynamic Programming; DP)を、小さく追跡可能な例で確認します。
現場でよくある状況
- 欠品を避けて作りすぎると、在庫費と滞留リスクが増える
- 保全を先送りすると今週は稼働できるが、故障確率と停止損失が増える
- 老朽設備の延命は短期収支を改善する一方、将来の保全費を押し上げる
- 状態や需要が変わるため、固定ルールが常に最善とは限らない
なぜこの問題は判断が難しいのか
行動の効果が時間をまたぎ、不確実性もあるためです。逐次意思決定では「現在の費用」と「行動後の状態が持つ将来価値」を同じ尺度で比較します。ただし、状態・行動・期間が増えると組合せが急増するため、モデルの粒度も重要な設計判断です。
今回扱うノックの全体像
| No. | テーマ | 製造業での判断 |
|---|---|---|
| 061 | 動的計画法とは何か | 多期間の生産量を決める |
| 062 | ベルマン方程式 | 今期費用と将来価値を分ける |
| 063 | 最短経路と動的計画法 | 工程ルートを後ろから評価する |
| 064 | ナップサック問題と動的計画法 | 改善案件を予算内で選ぶ |
| 065 | 在庫管理と動的計画法 | 需要変動下の発注量を決める |
| 066 | 設備更新問題 | 修理・継続・更新を比較する |
| 067 | マルコフ決定過程 | 設備状態の確率遷移を扱う |
| 068 | 価値反復法 | 長期費用が小さい方策を求める |
| 069 | 方策反復法 | 方策評価と改善を分離する |
| 070 | 強化学習との関係 | 未知の遷移を運転データから学ぶ |
Python 環境の準備
外部データは使いません。乱数シードを固定し、同じ結果を再現できるようにします。金額は読みやすさのため 千円 単位とします。
import platform
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from IPython.display import display
rng = np.random.default_rng(42)
plt.rcParams["figure.figsize"] = (7.2, 4.5)
plt.rcParams["axes.unicode_minus"] = False
pd.DataFrame({
"package": ["Python", "NumPy", "pandas", "Matplotlib"],
"version": [platform.python_version(), np.__version__, pd.__version__, matplotlib.__version__],
})
| package | version | |
|---|---|---|
| 0 | Python | 3.13.1 |
| 1 | NumPy | 2.5.1 |
| 2 | pandas | 3.0.3 |
| 3 | Matplotlib | 3.11.0 |
架空データの作成
精密部品工場の12週間を想定します。需要には緩やかな季節性と偶然変動、設備には週ごとの劣化を持たせます。この観測表は状況理解用であり、後の各ノックでは意思決定の論点を分離するため、小さなモデルも併用します。
weeks = np.arange(1, 13)
demand = np.maximum(2, np.rint(6 + 1.5*np.sin(weeks/2) + rng.normal(0, 1.0, len(weeks)))).astype(int)
condition = np.maximum(45, np.rint(96 - 3.5*weeks + rng.normal(0, 2.5, len(weeks)))).astype(int)
factory = pd.DataFrame({"week": weeks, "demand_lots": demand, "equipment_condition": condition})
display(factory)
fig, ax1 = plt.subplots()
ax1.plot(weeks, demand, marker="o", label="Demand", color="tab:blue")
ax1.set_xlabel("Week"); ax1.set_ylabel("Demand (lots)", color="tab:blue")
ax2 = ax1.twinx(); ax2.plot(weeks, condition, marker="s", label="Condition", color="tab:red")
ax2.set_ylabel("Equipment condition score", color="tab:red")
ax1.set_title("Synthetic weekly demand and equipment condition")
ax1.grid(True, alpha=.3); fig.tight_layout(); plt.show()
| week | demand_lots | equipment_condition | |
|---|---|---|---|
| 0 | 1 | 7 | 93 |
| 1 | 2 | 6 | 92 |
| 2 | 3 | 8 | 87 |
| 3 | 4 | 8 | 80 |
| 4 | 5 | 5 | 79 |
| 5 | 6 | 5 | 73 |
| 6 | 7 | 6 | 74 |
| 7 | 8 | 5 | 68 |
| 8 | 9 | 5 | 64 |
| 9 | 10 | 4 | 59 |
| 10 | 11 | 6 | 61 |
| 11 | 12 | 6 | 54 |

No.061:動的計画法とは何か
実務での意味
動的計画法は、多期間問題を「状態」と「行動」に分け、同じ部分問題を再利用する方法です。ここでは期首在庫を状態、生産量を行動として、3週間の生産・在庫費を最小化します。
分析・モデル化の考え方
需要を 、期首在庫を 、生産量を とすると、状態は で更新されます。価値関数 は、時点 以降で達成できる最小累積費用です。
Pythonで確認する
dp_demand = [4, 7, 5]
capacity, max_inventory = 8, 8
unit_cost, holding_cost = [5, 8, 6], 1.2
V = {3: {s: 0.0 for s in range(max_inventory + 1)}}
policy = {}
for t in range(2, -1, -1):
V[t], policy[t] = {}, {}
for s in range(max_inventory + 1):
choices = []
for a in range(capacity + 1):
nxt = s + a - dp_demand[t]
if 0 <= nxt <= max_inventory:
choices.append((unit_cost[t]*a + holding_cost*nxt + V[t+1][nxt], a))
if choices:
V[t][s], policy[t][s] = min(choices)
s, plan = 0, []
for t in range(3):
a = policy[t][s]; nxt = s + a - dp_demand[t]
plan.append([t+1, s, dp_demand[t], a, nxt, unit_cost[t]*a + holding_cost*nxt])
s = nxt
display(pd.DataFrame(plan, columns=["week","opening_inventory","demand","production","closing_inventory","current_cost_kJPY"]))
print(f"Minimum 3-week cost: {V[0][0]:.1f} kJPY")
| week | opening_inventory | demand | production | closing_inventory | current_cost_kJPY | |
|---|---|---|---|---|---|---|
| 0 | 1 | 0 | 4 | 8 | 4 | 44.8 |
| 1 | 2 | 4 | 7 | 3 | 0 | 24.0 |
| 2 | 3 | 0 | 5 | 5 | 0 | 30.0 |
Minimum 3-week cost: 98.8 kJPY
結果の読み取り
第2週の単価が高いため、モデルは第1週に前倒し生産して在庫を持ちます。各週だけを最安にする判断では見えない効果です。一方、保管能力、歩留まり、段取り、欠品許容を省くと実行不能な計画になるため、状態遷移と制約の業務確認が必要です。
No.062:ベルマン方程式
実務での意味
ベルマン方程式は、現在の行動を「今すぐ発生する費用」と「次状態の将来費用」に分けます。この分解により、なぜその生産量を選んだのかを説明できます。
分析・モデル化の考え方
候補行動 の総評価値を と置けば、 です。最適性原理は、最適計画の残り部分も、その到達状態から見て最適であることを表します。
Pythonで確認する
t, s = 0, 0
rows = []
for a in range(capacity + 1):
nxt = s + a - dp_demand[t]
if nxt in V[t+1]:
immediate = unit_cost[t]*a + holding_cost*nxt
rows.append([a, nxt, immediate, V[t+1][nxt], immediate + V[t+1][nxt]])
bellman = pd.DataFrame(rows, columns=["production","next_inventory","immediate_cost","future_cost","total_Q"])
display(bellman.round(1))
bellman.set_index("production")[["immediate_cost","future_cost"]].plot(kind="bar", stacked=True)
plt.title("Bellman decomposition at week 1"); plt.xlabel("Production (lots)"); plt.ylabel("Cost (kJPY)")
plt.grid(True, axis="y", alpha=.3); plt.tight_layout(); plt.show()
| production | next_inventory | immediate_cost | future_cost | total_Q | |
|---|---|---|---|---|---|
| 0 | 4 | 0 | 20.0 | 86.0 | 106.0 |
| 1 | 5 | 1 | 26.2 | 78.0 | 104.2 |
| 2 | 6 | 2 | 32.4 | 70.0 | 102.4 |
| 3 | 7 | 3 | 38.6 | 62.0 | 100.6 |
| 4 | 8 | 4 | 44.8 | 54.0 | 98.8 |

結果の読み取り
生産量を増やすほど当期費用は増えますが、高単価週を避けられるため将来費用は減ります。合計が最小の行動が採用されます。現場説明では、この内訳を提示すると「なぜ今、余分に作るのか」を共有しやすくなります。
No.063:最短経路と動的計画法
実務での意味
複数工程を通る製品のルート選択は、現在地点を状態、次に選ぶ工程を行動とする動的計画として表せます。工程時間、外注費、品質リスクを共通費用へ換算します。
分析・モデル化の考え方
有向非巡回グラフでは、終点から逆順に を計算できます。負の辺がない一般グラフなら Dijkstra 法など、問題構造に合った解法を選びます。
Pythonで確認する
edges = {
"Material": [("Cut-A", 4.0), ("Cut-B", 5.0)],
"Cut-A": [("Heat-1", 7.0), ("Heat-2", 5.0)],
"Cut-B": [("Heat-1", 4.0), ("Heat-2", 6.0)],
"Heat-1": [("Inspect", 3.0)], "Heat-2": [("Inspect", 4.0)],
"Inspect": [("Ship", 2.0)], "Ship": []}
order = ["Ship","Inspect","Heat-2","Heat-1","Cut-B","Cut-A","Material"]
route_value, next_node = {"Ship": 0.0}, {}
for node in order[1:]:
route_value[node], next_node[node] = min((cost + route_value[j], j) for j, cost in edges[node])
path, node = ["Material"], "Material"
while node != "Ship": node = next_node[node]; path.append(node)
display(pd.DataFrame({"node": list(route_value), "minimum_remaining_cost": list(route_value.values())}).sort_values("minimum_remaining_cost"))
print("Best route:", " -> ".join(path), f" / cost={route_value['Material']:.1f}")
| node | minimum_remaining_cost | |
|---|---|---|
| 0 | Ship | 0.0 |
| 1 | Inspect | 2.0 |
| 3 | Heat-1 | 5.0 |
| 2 | Heat-2 | 6.0 |
| 4 | Cut-B | 9.0 |
| 5 | Cut-A | 11.0 |
| 6 | Material | 14.0 |
Best route: Material -> Cut-B -> Heat-1 -> Inspect -> Ship / cost=14.0
結果の読み取り
各工程から出荷までの最小残余費用が求まり、最適ルートを復元できます。費用係数に品質損失やリードタイムを加える場合は、金額換算の根拠と、通過禁止・能力上限を別途管理します。
No.064:ナップサック問題と動的計画法
実務での意味
改善予算が限られるとき、案件を効果額だけで順位付けすると、予算の組合せを無駄にすることがあります。0-1ナップサック問題で、案件ごとの採否を同時に決めます。
分析・モデル化の考え方
案件 の投資額を 、年間効果を 、予算を とし、、、 とします。DPは「先頭 件、残予算 」の価値を保存します。
Pythonで確認する
projects = pd.DataFrame({
"project": ["Vision inspection","Tool monitoring","Energy control","AGV routing","Predictive maintenance"],
"investment": [6, 4, 3, 5, 7], "annual_benefit": [11, 7, 5, 9, 13]})
B, n = 12, len(projects)
table = np.zeros((n+1, B+1), dtype=int)
for i in range(1, n+1):
w, v = projects.loc[i-1, ["investment","annual_benefit"]]
for b in range(B+1):
table[i,b] = table[i-1,b]
if w <= b: table[i,b] = max(table[i,b], table[i-1,b-w] + v)
b, chosen = B, []
for i in range(n, 0, -1):
if table[i,b] != table[i-1,b]: chosen.append(i-1); b -= projects.loc[i-1,"investment"]
display(projects.assign(selected=[i in chosen for i in range(n)]))
print(f"Investment={projects.loc[chosen,'investment'].sum()}, benefit={table[n,B]}")
plt.plot(range(B+1), table[n], marker="o"); plt.title("Best annual benefit by investment budget")
plt.xlabel("Budget"); plt.ylabel("Maximum annual benefit"); plt.grid(True, alpha=.3); plt.tight_layout(); plt.show()
| project | investment | annual_benefit | selected | |
|---|---|---|---|---|
| 0 | Vision inspection | 6 | 11 | False |
| 1 | Tool monitoring | 4 | 7 | False |
| 2 | Energy control | 3 | 5 | False |
| 3 | AGV routing | 5 | 9 | True |
| 4 | Predictive maintenance | 7 | 13 | True |
Investment=12, benefit=22

結果の読み取り
採択集合は予算全体で最も効果が大きい組合せです。効果額の不確実性、案件間の依存、導入人員、同時実施不可などを含む場合は、シナリオ分析や整数計画へ拡張します。
No.065:在庫管理と動的計画法
実務での意味
需要が確定していない部品では、在庫費だけでなく欠品費も含めて発注量を決めます。欠品費には特急輸送、ライン停止、納期遅延などを反映します。
分析・モデル化の考え方
需要 が確率変数なら、期待値を含むベルマン方程式
を使います。ここでは未充足需要は当期の欠品として扱い、翌期へ持ち越しません。
Pythonで確認する
dvals, probs = np.array([2,4,6,8]), np.array([.15,.35,.35,.15])
T, Smax, Amax = 3, 12, 10
fixed, purchase, hold, shortage = 6.0, 3.0, 1.0, 9.0
IV = {T: np.zeros(Smax+1)}; IP = {}
for t in range(T-1, -1, -1):
IV[t], IP[t] = np.zeros(Smax+1), np.zeros(Smax+1, dtype=int)
for s in range(Smax+1):
qvals = []
for a in range(Amax+1):
if s+a > Smax: continue
exp = fixed*(a>0) + purchase*a
for d, pr in zip(dvals, probs):
nxt=max(s+a-d,0); lost=max(d-s-a,0)
exp += pr*(hold*nxt + shortage*lost + IV[t+1][nxt])
qvals.append((exp,a))
IV[t][s], IP[t][s] = min(qvals)
inventory_policy = pd.DataFrame({"opening_inventory":range(Smax+1),"order_week1":IP[0],"expected_cost_to_go":IV[0]})
display(inventory_policy.round(2))
plt.step(inventory_policy.opening_inventory, inventory_policy.order_week1, where="mid")
plt.title("Inventory-dependent ordering policy"); plt.xlabel("Opening inventory (lots)"); plt.ylabel("Order quantity (lots)")
plt.grid(True, alpha=.3); plt.tight_layout(); plt.show()
| opening_inventory | order_week1 | expected_cost_to_go | |
|---|---|---|---|
| 0 | 0 | 8 | 73.07 |
| 1 | 1 | 7 | 70.07 |
| 2 | 2 | 6 | 67.07 |
| 3 | 3 | 5 | 64.07 |
| 4 | 4 | 0 | 60.80 |
| 5 | 5 | 0 | 55.30 |
| 6 | 6 | 0 | 49.80 |
| 7 | 7 | 0 | 46.58 |
| 8 | 8 | 0 | 43.07 |
| 9 | 9 | 0 | 40.66 |
| 10 | 10 | 0 | 37.33 |
| 11 | 11 | 0 | 35.03 |
| 12 | 12 | 0 | 31.33 |

結果の読み取り
在庫が少ない状態ではまとめて発注し、一定水準以上では発注しない方策になります。固定発注費があるため、単純な毎期補充ではありません。実務では需要分布の更新、発注リードタイム、ロット丸め、保管期限、サービス水準も検証します。
No.066:設備更新問題
実務での意味
設備更新は購入価格だけでなく、老朽化による保全費、停止損失、更新後の残存価値を複数年で比較します。
分析・モデル化の考え方
設備年齢を状態とし、「継続」なら年齢が1増え、「更新」なら翌期は新設備の年齢1になります。割引率 による現在価値で比較し、終端には残存価値を置きます。
Pythonで確認する
horizon, max_age, discount = 5, 8, .95
replace_cost = 65.0
maintenance = np.array([5 + 2*a + .8*a*a for a in range(max_age+2)])
EV = {horizon: {a: -max(3, 18-2*a) for a in range(max_age+1)}}
EP = {}
for t in range(horizon-1, -1, -1):
EV[t], EP[t] = {}, {}
for age in range(max_age+1):
keep = maintenance[age] + discount*EV[t+1][min(age+1,max_age)]
replace = replace_cost + maintenance[0] + discount*EV[t+1][1]
EV[t][age], EP[t][age] = min((keep,"Keep"),(replace,"Replace"))
eq = pd.DataFrame({"equipment_age":range(max_age+1),"decision_now":[EP[0][a] for a in range(max_age+1)],"discounted_cost":[EV[0][a] for a in range(max_age+1)]})
display(eq.round(1))
plt.plot(eq.equipment_age, eq.discounted_cost, marker="o"); plt.title("Five-year equipment cost by current age")
plt.xlabel("Current equipment age (years)"); plt.ylabel("Minimum discounted cost (kJPY)")
plt.grid(True, alpha=.3); plt.tight_layout(); plt.show()
| equipment_age | decision_now | discounted_cost | |
|---|---|---|---|
| 0 | 0 | Keep | 53.8 |
| 1 | 1 | Keep | 81.8 |
| 2 | 2 | Keep | 100.0 |
| 3 | 3 | Keep | 109.3 |
| 4 | 4 | Keep | 116.9 |
| 5 | 5 | Replace | 118.8 |
| 6 | 6 | Replace | 118.8 |
| 7 | 7 | Replace | 118.8 |
| 8 | 8 | Replace | 118.8 |

結果の読み取り
若い設備は継続し、老朽化すると更新へ切り替わります。閾値は保全費、更新費、割引率、残存価値で変わるため、単一点の結論ではなく感度表を設備投資審議へ添えるべきです。
No.067:マルコフ決定過程
実務での意味
設備の健全・劣化・故障を状態とし、運転・保全・交換によって次期状態の確率が変わる問題をマルコフ決定過程(MDP)で表します。
分析・モデル化の考え方
MDPは状態 、行動 、遷移確率 、費用 、割引率 で構成されます。マルコフ性は、必要な履歴情報が現在状態へ要約されていることを仮定します。
Pythonで確認する
states = ["Healthy","Degraded","Failed"]
actions = ["Run","Maintain","Replace"]
P = np.array([
[[.85,.14,.01],[.95,.05,0],[1,0,0]],
[[.25,.55,.20],[.75,.23,.02],[1,0,0]],
[[0,0,1],[.55,.40,.05],[1,0,0]]])
C = np.array([[2,8,25],[5,10,25],[45,35,25]], dtype=float)
rows=[]
for si,s in enumerate(states):
for ai,a in enumerate(actions): rows.append([s,a,C[si,ai],*P[si,ai]])
mdp_table=pd.DataFrame(rows,columns=["state","action","cost","P_healthy","P_degraded","P_failed"])
display(mdp_table)
print("Maximum transition-row error:", np.abs(P.sum(axis=2)-1).max())
| state | action | cost | P_healthy | P_degraded | P_failed | |
|---|---|---|---|---|---|---|
| 0 | Healthy | Run | 2.0 | 0.85 | 0.14 | 0.01 |
| 1 | Healthy | Maintain | 8.0 | 0.95 | 0.05 | 0.00 |
| 2 | Healthy | Replace | 25.0 | 1.00 | 0.00 | 0.00 |
| 3 | Degraded | Run | 5.0 | 0.25 | 0.55 | 0.20 |
| 4 | Degraded | Maintain | 10.0 | 0.75 | 0.23 | 0.02 |
| 5 | Degraded | Replace | 25.0 | 1.00 | 0.00 | 0.00 |
| 6 | Failed | Run | 45.0 | 0.00 | 0.00 | 1.00 |
| 7 | Failed | Maintain | 35.0 | 0.55 | 0.40 | 0.05 |
| 8 | Failed | Replace | 25.0 | 1.00 | 0.00 | 0.00 |
Maximum transition-row error: 0.0
結果の読み取り
同じ「運転」でも劣化状態では故障確率が高く、保全は費用を払って遷移を改善します。状態定義が粗すぎるとマルコフ性が崩れます。振動、累積稼働、直近保全など、将来予測に必要な情報を状態へ含めるか検証します。
No.068:価値反復法
実務での意味
価値反復法は、各状態の長期費用を繰り返し更新し、最小費用の行動を求めます。設備状態ごとの標準対応ルールを作る基礎になります。
分析・モデル化の考え方
割引率 の無限期間問題では、
を収束まで反復します。残差は数値収束の指標であり、モデル妥当性の保証ではありません。
Pythonで確認する
gamma = .95
value = np.zeros(len(states)); residuals=[]
for k in range(1000):
Q = C + gamma*np.einsum("sak,k->sa", P, value)
new_value = Q.min(axis=1)
residuals.append(np.max(np.abs(new_value-value)))
value = new_value
if residuals[-1] < 1e-10: break
vi_policy = Q.argmin(axis=1)
display(pd.DataFrame({"state":states,"long_run_cost":value,"recommended_action":[actions[i] for i in vi_policy]}).round(2))
plt.semilogy(residuals); plt.title("Convergence of value iteration"); plt.xlabel("Iteration"); plt.ylabel("Maximum Bellman residual")
plt.grid(True, alpha=.3); plt.tight_layout(); plt.show()
print("Iterations:", len(residuals))
| state | long_run_cost | recommended_action | |
|---|---|---|---|
| 0 | Healthy | 67.97 | Run |
| 1 | Degraded | 76.94 | Maintain |
| 2 | Failed | 89.57 | Replace |

Iterations: 475
結果の読み取り
健全時は運転、劣化時は保全、故障時は交換という状態依存方策が得られます。割引率を1に近づけるほど遠い将来を重視し、収束は遅くなります。費用・遷移確率を変えた方策安定性も確認します。
No.069:方策反復法
実務での意味
方策反復法は、現在の標準ルールがもたらす長期費用を評価し、改善可能な状態だけ行動を変えます。現行方策との比較が明確です。
分析・モデル化の考え方
方策 を固定して を解き、その価値を用いて各状態の行動を改善します。方策が変わらなくなれば終了します。
Pythonで確認する
pi = np.array([0,0,1]) # Healthy=Run, Degraded=Run, Failed=Maintain
history=[]
for it in range(20):
Ppi=P[np.arange(3),pi]; cpi=C[np.arange(3),pi]
vpi=np.linalg.solve(np.eye(3)-gamma*Ppi,cpi)
qpi=C+gamma*np.einsum("sak,k->sa",P,vpi)
improved=qpi.argmin(axis=1)
history.append([it,*[actions[x] for x in pi],*vpi])
if np.array_equal(improved,pi): break
pi=improved
display(pd.DataFrame(history,columns=["iteration","Healthy_action","Degraded_action","Failed_action","V_healthy","V_degraded","V_failed"]).round(2))
print("Stable policy:", dict(zip(states,[actions[x] for x in pi])))
| iteration | Healthy_action | Degraded_action | Failed_action | V_healthy | V_degraded | V_failed | |
|---|---|---|---|---|---|---|---|
| 0 | 0 | Run | Run | Maintain | 90.65 | 106.94 | 129.14 |
| 1 | 1 | Run | Maintain | Replace | 67.97 | 76.94 | 89.57 |
Stable policy: {'Healthy': 'Run', 'Degraded': 'Maintain', 'Failed': 'Replace'}
結果の読み取り
初期の現行ルールから、評価と改善を経て価値反復と同じ方策へ到達します。状態数が大きい場合は線形方程式の求解負荷が増えるため、反復評価や近似法を検討します。
No.070:強化学習との関係
実務での意味
DPやMDPは遷移確率と費用モデルが既知であることを前提にします。強化学習は、試行で得た「状態・行動・費用・次状態」から価値を推定します。ここでは同じ保全問題を Q-learning で学習します。
分析・モデル化の考え方
Q-learning の費用最小化版は、観測サンプルにより
と更新します。モデル不要ですが、安全で十分な探索が必要です。実機で無制限に探索することを意味しません。
Pythonで確認する
qrng=np.random.default_rng(42); Qlearn=np.zeros((3,3)); visits=np.zeros((3,3),int)
episodes, steps = 12000, 25
agreement=[]
for ep in range(episodes):
s=int(qrng.integers(0,3)); eps=max(.03, .8*(1-ep/episodes))
for _ in range(steps):
a=int(qrng.integers(0,3)) if qrng.random()<eps else int(np.argmin(Qlearn[s]))
sn=int(qrng.choice(3,p=P[s,a])); cost=C[s,a]; visits[s,a]+=1
alpha=max(.02, .5/(visits[s,a]**.55))
Qlearn[s,a]+=alpha*(cost+gamma*Qlearn[sn].min()-Qlearn[s,a]); s=sn
agreement.append(np.mean(Qlearn.argmin(axis=1)==vi_policy))
learned=Qlearn.argmin(axis=1)
display(pd.DataFrame({"state":states,"MDP_action":[actions[i] for i in vi_policy],"learned_action":[actions[i] for i in learned],"minimum_visits":visits.min(axis=1)}))
plt.plot(pd.Series(agreement).rolling(300,min_periods=1).mean()); plt.title("Q-learning policy agreement with model-based optimum")
plt.xlabel("Episode"); plt.ylabel("Share of states with matching action"); plt.ylim(-.05,1.05)
plt.grid(True, alpha=.3); plt.tight_layout(); plt.show()
| state | MDP_action | learned_action | minimum_visits | |
|---|---|---|---|---|
| 0 | Healthy | Run | Run | 33523 |
| 1 | Degraded | Maintain | Maintain | 5006 |
| 2 | Failed | Replace | Replace | 1222 |

結果の読み取り
十分な模擬経験により、学習方策は既知モデルから求めた方策と一致します。ただし実務では、低頻度故障の学習不足、設備変更による分布変化、安全制約、探索コストが課題です。まずシミュレータ、過去ログ、オフライン評価を使い、人の承認を残す段階導入が現実的です。
対象ノックを通して見える実務上の示唆
- 逐次意思決定では、今期KPIだけでなく次状態が持つ将来価値を評価する
- 状態は将来予測に必要十分な情報へ絞り、状態数の爆発を抑える
- 最適方策だけでなく、即時費用・将来費用・代替行動との差を説明資料にする
- 遷移確率、需要分布、費用係数は定期的に再推定し、方策の変化を監視する
- モデル既知ならDP/MDP、未知なら学習という単純な二択ではなく、既知情報とデータを組み合わせる
- 強化学習の探索はシミュレータや安全制約下で行い、実機へ直結させない
実務導入する場合に必要なこと
| 論点 | 確認事項 | 主な成果物 |
|---|---|---|
| 意思決定 | 誰が、いつ、何を選ぶか | 業務フロー・承認責任 |
| 状態 | 在庫、劣化、仕掛、納期をどう表すか | 状態定義・データ辞書 |
| 行動・制約 | 能力、安全、ロット、保全窓 | 制約一覧・根拠 |
| 費用 | 欠品、停止、品質、残存価値 | 費用換算表・感度分析 |
| 確率 | 需要・故障遷移の推定精度 | 推定手順・検証結果 |
| 運用 | 再計算、例外、モデル劣化 | SOP・監視ダッシュボード |
最初は状態数と行動数を限定し、現行ルールをベースラインとして過去データでバックテストします。推奨理由と代替案を人が確認する意思決定支援から始め、停止損失、サービス水準、作業負荷を並行監視します。
まとめ
多期間生産、工程ルート、改善案件、在庫、設備更新をDPとして表し、確率的な設備保全をMDP、価値反復、方策反復、Q-learningへ展開しました。重要なのは高度な解法そのものではなく、将来へ持ち越される影響を状態遷移として明文化し、現場が検証できる判断ルールにすることです。
法人向けのご相談
数理工房では、生産・在庫・設備保全の逐次意思決定について、課題整理、データ設計、シミュレーション、最適化PoC、運用設計までご支援します。
📩 お問い合わせ: surikobo.co.jp/contact
まずはお気軽にご相談ください。