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

png

No.061:動的計画法とは何か

実務での意味

動的計画法は、多期間問題を「状態」と「行動」に分け、同じ部分問題を再利用する方法です。ここでは期首在庫を状態、生産量を行動として、3週間の生産・在庫費を最小化します。

分析・モデル化の考え方

需要を dtd_t、期首在庫を sts_t、生産量を ata_t とすると、状態は st+1=st+atdts_{t+1}=s_t+a_t-d_t で更新されます。価値関数 Vt(s)V_t(s) は、時点 tt 以降で達成できる最小累積費用です。

Vt(s)=mina{ct(a)+h(s+adt)+Vt+1(s+adt)}V_t(s)=\min_a\{c_t(a)+h(s+a-d_t)+V_{t+1}(s+a-d_t)\}

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:ベルマン方程式

実務での意味

ベルマン方程式は、現在の行動を「今すぐ発生する費用」と「次状態の将来費用」に分けます。この分解により、なぜその生産量を選んだのかを説明できます。

分析・モデル化の考え方

候補行動 aa の総評価値を Qt(s,a)=ct(s,a)+Vt+1(f(s,a))Q_t(s,a)=c_t(s,a)+V_{t+1}(f(s,a)) と置けば、Vt(s)=minaQt(s,a)V_t(s)=\min_a Q_t(s,a) です。最適性原理は、最適計画の残り部分も、その到達状態から見て最適であることを表します。

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

png

結果の読み取り

生産量を増やすほど当期費用は増えますが、高単価週を避けられるため将来費用は減ります。合計が最小の行動が採用されます。現場説明では、この内訳を提示すると「なぜ今、余分に作るのか」を共有しやすくなります。

No.063:最短経路と動的計画法

実務での意味

複数工程を通る製品のルート選択は、現在地点を状態、次に選ぶ工程を行動とする動的計画として表せます。工程時間、外注費、品質リスクを共通費用へ換算します。

分析・モデル化の考え方

有向非巡回グラフでは、終点から逆順に V(i)=minjN(i){cij+V(j)}V(i)=\min_{j\in N(i)}\{c_{ij}+V(j)\} を計算できます。負の辺がない一般グラフなら 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ナップサック問題で、案件ごとの採否を同時に決めます。

分析・モデル化の考え方

案件 ii の投資額を wiw_i、年間効果を viv_i、予算を BB とし、maxivixi\max\sum_i v_i x_iiwixiB\sum_i w_i x_i\le Bxi{0,1}x_i\in\{0,1\} とします。DPは「先頭 ii 件、残予算 bb」の価値を保存します。

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


png

結果の読み取り

採択集合は予算全体で最も効果が大きい組合せです。効果額の不確実性、案件間の依存、導入人員、同時実施不可などを含む場合は、シナリオ分析や整数計画へ拡張します。

No.065:在庫管理と動的計画法

実務での意味

需要が確定していない部品では、在庫費だけでなく欠品費も含めて発注量を決めます。欠品費には特急輸送、ライン停止、納期遅延などを反映します。

分析・モデル化の考え方

需要 DtD_t が確率変数なら、期待値を含むベルマン方程式

Vt(s)=mina{K1a>0+ca+E[h(s+aDt)++p(Dtsa)++Vt+1((s+aDt)+)]}V_t(s)=\min_a\left\{K\mathbf{1}_{a>0}+ca+\mathbb E[h(s+a-D_t)^++p(D_t-s-a)^++V_{t+1}((s+a-D_t)^+)]\right\}

を使います。ここでは未充足需要は当期の欠品として扱い、翌期へ持ち越しません。

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

png

結果の読み取り

在庫が少ない状態ではまとめて発注し、一定水準以上では発注しない方策になります。固定発注費があるため、単純な毎期補充ではありません。実務では需要分布の更新、発注リードタイム、ロット丸め、保管期限、サービス水準も検証します。

No.066:設備更新問題

実務での意味

設備更新は購入価格だけでなく、老朽化による保全費、停止損失、更新後の残存価値を複数年で比較します。

分析・モデル化の考え方

設備年齢を状態とし、「継続」なら年齢が1増え、「更新」なら翌期は新設備の年齢1になります。割引率 rr による現在価値で比較し、終端には残存価値を置きます。

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

png

結果の読み取り

若い設備は継続し、老朽化すると更新へ切り替わります。閾値は保全費、更新費、割引率、残存価値で変わるため、単一点の結論ではなく感度表を設備投資審議へ添えるべきです。

No.067:マルコフ決定過程

実務での意味

設備の健全・劣化・故障を状態とし、運転・保全・交換によって次期状態の確率が変わる問題をマルコフ決定過程(MDP)で表します。

分析・モデル化の考え方

MDPは状態 SS、行動 AA、遷移確率 P(ss,a)P(s'|s,a)、費用 c(s,a)c(s,a)、割引率 γ\gamma で構成されます。マルコフ性は、必要な履歴情報が現在状態へ要約されていることを仮定します。

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:価値反復法

実務での意味

価値反復法は、各状態の長期費用を繰り返し更新し、最小費用の行動を求めます。設備状態ごとの標準対応ルールを作る基礎になります。

分析・モデル化の考え方

割引率 γ\gamma の無限期間問題では、

Vk+1(s)=mina{c(s,a)+γsP(ss,a)Vk(s)}V_{k+1}(s)=\min_a\{c(s,a)+\gamma\sum_{s'}P(s'|s,a)V_k(s')\}

を収束まで反復します。残差は数値収束の指標であり、モデル妥当性の保証ではありません。

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

png

Iterations: 475

結果の読み取り

健全時は運転、劣化時は保全、故障時は交換という状態依存方策が得られます。割引率を1に近づけるほど遠い将来を重視し、収束は遅くなります。費用・遷移確率を変えた方策安定性も確認します。

No.069:方策反復法

実務での意味

方策反復法は、現在の標準ルールがもたらす長期費用を評価し、改善可能な状態だけ行動を変えます。現行方策との比較が明確です。

分析・モデル化の考え方

方策 π\pi を固定して (IγPπ)Vπ=cπ(I-\gamma P_\pi)V_\pi=c_\pi を解き、その価値を用いて各状態の行動を改善します。方策が変わらなくなれば終了します。

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 の費用最小化版は、観測サンプルにより

Q(s,a)Q(s,a)+α{c+γminaQ(s,a)Q(s,a)}Q(s,a)\leftarrow Q(s,a)+\alpha\{c+\gamma\min_{a'}Q(s',a')-Q(s,a)\}

と更新します。モデル不要ですが、安全で十分な探索が必要です。実機で無制限に探索することを意味しません。

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

png

結果の読み取り

十分な模擬経験により、学習方策は既知モデルから求めた方策と一致します。ただし実務では、低頻度故障の学習不足、設備変更による分布変化、安全制約、探索コストが課題です。まずシミュレータ、過去ログ、オフライン評価を使い、人の承認を残す段階導入が現実的です。

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

  • 逐次意思決定では、今期KPIだけでなく次状態が持つ将来価値を評価する
  • 状態は将来予測に必要十分な情報へ絞り、状態数の爆発を抑える
  • 最適方策だけでなく、即時費用・将来費用・代替行動との差を説明資料にする
  • 遷移確率、需要分布、費用係数は定期的に再推定し、方策の変化を監視する
  • モデル既知ならDP/MDP、未知なら学習という単純な二択ではなく、既知情報とデータを組み合わせる
  • 強化学習の探索はシミュレータや安全制約下で行い、実機へ直結させない

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

論点確認事項主な成果物
意思決定誰が、いつ、何を選ぶか業務フロー・承認責任
状態在庫、劣化、仕掛、納期をどう表すか状態定義・データ辞書
行動・制約能力、安全、ロット、保全窓制約一覧・根拠
費用欠品、停止、品質、残存価値費用換算表・感度分析
確率需要・故障遷移の推定精度推定手順・検証結果
運用再計算、例外、モデル劣化SOP・監視ダッシュボード

最初は状態数と行動数を限定し、現行ルールをベースラインとして過去データでバックテストします。推奨理由と代替案を人が確認する意思決定支援から始め、停止損失、サービス水準、作業負荷を並行監視します。

まとめ

多期間生産、工程ルート、改善案件、在庫、設備更新をDPとして表し、確率的な設備保全をMDP、価値反復、方策反復、Q-learningへ展開しました。重要なのは高度な解法そのものではなく、将来へ持ち越される影響を状態遷移として明文化し、現場が検証できる判断ルールにすることです。

法人向けのご相談

数理工房では、生産・在庫・設備保全の逐次意思決定について、課題整理、データ設計、シミュレーション、最適化PoC、運用設計までご支援します。

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