100本ノック / システム開発 / システム開発100本ノック
製造業のAIシステム開発入門|需要予測・異常検知・最適化・LLMを実装
製造現場でAIを「使える仕組み」にする — 予測・異常検知・最適化・LLM実装10本ノック
製造現場の生産計画、設備保全、品質管理を題材に、機械学習モデルのAPI化から需要予測、異常検知、ダッシュボード、最適化結果、LLM活用、運用設計、全体アーキテクチャまでを一続きで整理します。精度の高いモデルを作るだけでなく、誰が、いつ、どの根拠を見て、どの行動を選ぶかまで設計することが本稿の主題です。
[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。
掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。
はじめに:この記事で扱う製造業の実務課題
製造業でAI活用を進めると、PoCでは良好だった予測モデルが現場で参照されない、異常アラートが多すぎて無視される、最適化結果の理由が分からず計画担当者が採用できない、といった壁に直面します。原因はモデル精度だけではありません。入力データの鮮度、APIの応答、画面上の説明、承認権限、障害時の代替手順、モデル劣化の監視が一つの業務システムとして設計されていないことが大きく影響します。
本稿では架空の精密部品工場を想定し、月次需要、設備センサー、ライン能力、AIサービスの運用ログを生成します。予測値や異常スコアを、発注量、保全点検、日次生産計画という具体的な意思決定につなげます。
現場でよくある状況
- 予測値だけが表示され、予測区間や前提条件が分からない
- 異常スコアが高い理由を保全担当者へ説明できない
- AI APIが停止すると、作業指示画面まで利用できなくなる
- 推奨計画が設備能力、段取り、在庫などの制約を満たしているか確認できない
- LLMへ機密情報や個人情報をそのまま送信してしまう
- モデル更新後に精度が悪化しても、誰も気づかない
- 現場による採用・却下とその理由が記録されず、改善につながらない
なぜこの問題は判断が難しいのか
AIの出力には不確実性があります。需要予測を点予測 だけで示すと、予測誤差が意思決定へ与える影響を評価できません。実務では下限 、上限 とともに示し、安全在庫や残業判断へつなげます。
また、異常検知には誤報と見逃しのトレードオフがあります。閾値を下げると再現率は上がりますが、点検件数も増えます。最適な閾値はモデル指標だけでなく、見逃し損失、点検工数、設備停止リスクで決まります。さらにAI、API、画面、業務手順のどこか一つが欠けても、現場の判断は完結しません。
今回扱うノックの全体像
| No. | テーマ | 実務上の確認点 |
|---|---|---|
| 091 | 機械学習モデルのAPI化 | 入出力契約、版管理、応答性能 |
| 092 | 需要予測API | 予測区間と発注判断 |
| 093 | 異常検知API | 閾値、誤報、見逃し |
| 094 | 予測ダッシュボード | 例外中心の比較表示 |
| 095 | 異常アラート画面 | 優先順位と対応期限 |
| 096 | 最適化結果の表示 | 制約、根拠、採否記録 |
| 097 | LLMによる業務要約 | 根拠、秘匿化、人の確認 |
| 098 | チャット問い合わせ | 意図、権限、監査ログ |
| 099 | モデル・API・画面の運用 | SLO、ドリフト、切り戻し |
| 100 | AI活用システム設計 | 課題からKPI・構成・導入計画へ |
Python環境の準備
numpyとpandasで架空データを生成・集計し、matplotlibで可視化します。日本語表示にはjapanize_matplotlibを利用します。外部データや外部AI APIには接続しません。乱数シードを固定し、同じ結果を再現できるようにします。
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
import sys
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import japanize_matplotlib
rng = np.random.default_rng(20260712)
pd.set_option("display.max_columns", 20)
pd.set_option("display.width", 140)
print(f"Python : {sys.version.split()[0]}")
print(f"numpy : {np.__version__}")
print(f"pandas : {pd.__version__}")
print(f"matplotlib : {matplotlib.__version__}")
Python : 3.13.1
numpy : 2.5.1
pandas : 3.0.3
matplotlib : 3.11.0
架空データの作成
3製品の週次需要156週分、6台の設備から得たセンサー記録1,200件、3ラインの翌週計画候補、AI APIの運用ログ2,000件を生成します。需要には季節性と販促効果を、センサーには劣化に伴う温度・振動上昇を含めます。以下のデータは説明用の架空データであり、実測精度を示すものではありません。
weeks = pd.date_range("2023-07-03", periods=156, freq="W-MON")
products = ["精密ギアA", "精密ギアB", "駆動軸C"]
rows = []
for p, base, amp in zip(products, [520, 390, 300], [75, 45, 35]):
for i, week in enumerate(weeks):
promo = int(i % 26 in [4, 5])
demand = base + amp * np.sin(2 * np.pi * i / 52) + 65 * promo + rng.normal(0, 32)
rows.append([week, p, promo, max(0, round(demand))])
demand = pd.DataFrame(rows, columns=["week", "product", "promotion", "actual_qty"])
n_sensor = 1200
sensors = pd.DataFrame({
"timestamp": pd.date_range("2026-06-01", periods=n_sensor, freq="15min"),
"machine": rng.choice([f"MC-{i:02d}" for i in range(1, 7)], n_sensor),
"load_pct": rng.uniform(45, 98, n_sensor),
})
sensors["degradation"] = np.clip(np.arange(n_sensor) / n_sensor + rng.normal(0, .08, n_sensor), 0, 1)
sensors["temperature_c"] = 48 + .16*sensors["load_pct"] + 9*sensors["degradation"] + rng.normal(0, 2, n_sensor)
sensors["vibration_mm_s"] = 1.1 + .012*sensors["load_pct"] + 2.1*sensors["degradation"] + rng.normal(0, .25, n_sensor)
sensors["true_anomaly"] = (sensors["temperature_c"] > 69) | (sensors["vibration_mm_s"] > 3.8)
api_logs = pd.DataFrame({
"service": rng.choice(["需要予測", "異常検知", "最適化"], 2000, p=[.45, .4, .15]),
"model_version": rng.choice(["v2.3", "v2.4"], 2000, p=[.35, .65]),
"latency_ms": np.round(rng.lognormal(5.35, .48, 2000)),
"success": rng.random(2000) > .018,
"input_missing": rng.random(2000) < .025,
})
print("需要データ:", demand.shape, "設備センサーデータ:", sensors.shape, "APIログ:", api_logs.shape)
display(demand.tail(6), sensors.head(5))
需要データ: (468, 4) 設備センサーデータ: (1200, 7) APIログ: (2000, 5)
| week | product | promotion | actual_qty | |
|---|---|---|---|---|
| 462 | 2026-05-18 | 駆動軸C | 0 | 324 |
| 463 | 2026-05-25 | 駆動軸C | 0 | 249 |
| 464 | 2026-06-01 | 駆動軸C | 0 | 375 |
| 465 | 2026-06-08 | 駆動軸C | 0 | 277 |
| 466 | 2026-06-15 | 駆動軸C | 0 | 245 |
| 467 | 2026-06-22 | 駆動軸C | 0 | 274 |
| timestamp | machine | load_pct | degradation | temperature_c | vibration_mm_s | true_anomaly | |
|---|---|---|---|---|---|---|---|
| 0 | 2026-06-01 00:00:00 | MC-01 | 94.184648 | 0.019594 | 65.098978 | 2.324577 | False |
| 1 | 2026-06-01 00:15:00 | MC-06 | 83.966129 | 0.000000 | 60.966094 | 2.301255 | False |
| 2 | 2026-06-01 00:30:00 | MC-02 | 97.884000 | 0.134691 | 64.597830 | 2.489244 | False |
| 3 | 2026-06-01 00:45:00 | MC-01 | 77.891433 | 0.079755 | 59.151385 | 2.118854 | False |
| 4 | 2026-06-01 01:00:00 | MC-01 | 49.893098 | 0.062595 | 57.250918 | 1.531000 | False |
No.091:機械学習モデルをAPI化する
実務での意味
モデルをAPI化すると、生産管理画面、保全画面、バッチ処理から同じ推論機能を利用できます。ただし、学習済みモデルを読み込んで数値を返すだけでは不十分です。入力項目、単位、欠損時の扱い、モデル版、推論時刻、エラー形式を契約として定義します。
分析・モデル化の考え方
APIの運用品質を、成功率と応答時間のパーセンタイルで確認します。95パーセンタイルは、呼び出しの95%がその時間以内に完了する境界です。平均値だけでは一部の極端に遅い応答を見落とします。モデル読み込みは起動時に行い、リクエストごとの再読み込みを避ける設計が基本です。
Pythonで確認する
api_summary = (api_logs.groupby("service")
.agg(呼出件数=("success", "size"), 成功率_pct=("success", "mean"),
中央値_ms=("latency_ms", "median"),
p95_ms=("latency_ms", lambda x: x.quantile(.95)),
入力欠損率_pct=("input_missing", "mean")))
api_summary[["成功率_pct", "入力欠損率_pct"]] *= 100
display(api_summary.round(1))
sample_response = {"prediction": 548, "unit": "個/週", "model_version": "v2.4",
"predicted_at": "2026-07-12T09:00:00+09:00", "request_id": "REQ-091-001"}
sample_response
| 呼出件数 | 成功率_pct | 中央値_ms | p95_ms | 入力欠損率_pct | |
|---|---|---|---|---|---|
| service | |||||
| 最適化 | 277 | 98.6 | 203.0 | 431.4 | 3.6 |
| 異常検知 | 821 | 98.7 | 220.0 | 486.0 | 2.9 |
| 需要予測 | 902 | 97.3 | 205.5 | 475.3 | 2.2 |
{'prediction': 548,
'unit': '個/週',
'model_version': 'v2.4',
'predicted_at': '2026-07-12T09:00:00+09:00',
'request_id': 'REQ-091-001'}
結果の読み取り
サービス別に成功率とp95を分けると、画面のタイムアウト値や性能改善の優先順位を決められます。レスポンスには予測値だけでなく単位、モデル版、時刻、問い合わせに使うリクエストIDを含めます。本番では認証、入力スキーマ検証、最大件数、冪等性、監査ログも定義し、失敗時には前回確定値や手計算へ切り替えられるようにします。
No.092:需要予測APIを作成する
実務での意味
需要予測APIは、製品と予測期間を受け取り、将来需要を返します。利用者が必要なのは単一の予測値ではなく、その幅を踏まえた発注・能力調整の判断材料です。
分析・モデル化の考え方
ここでは直近8週平均に季節指数を掛けた簡易予測を用います。説明を単純にするための例で、実務では時系列交差検証で候補モデルを比較します。予測誤差は平均絶対誤差
で評価し、直近残差の標準偏差から概算の80%予測区間を作ります。
Pythonで確認する
forecast_rows = []
for product in products:
d = demand[demand["product"] == product].copy()
d["forecast"] = d["actual_qty"].shift(1).rolling(8).mean()
valid = d.dropna(subset=["forecast"])
sigma = (valid["actual_qty"] - valid["forecast"]).tail(52).std()
next_point = d["actual_qty"].tail(8).mean()
forecast_rows.append([product, round(next_point), round(next_point-1.28*sigma),
round(next_point+1.28*sigma), round(np.mean(abs(valid["actual_qty"]-valid["forecast"])), 1)])
forecast = pd.DataFrame(forecast_rows, columns=["product", "forecast_qty", "lower_80", "upper_80", "backtest_MAE"])
display(forecast)
plot_d = demand[demand["product"] == "精密ギアA"].tail(30).copy()
plot_d["8週移動平均予測"] = plot_d["actual_qty"].shift(1).rolling(8).mean()
ax = plot_d.plot(x="week", y=["actual_qty", "8週移動平均予測"], figsize=(9, 4), marker="o")
ax.set_title("精密ギアA:実績と簡易需要予測")
ax.set_xlabel("週")
ax.set_ylabel("需要数量(個/週)")
ax.grid(True, alpha=.3)
plt.tight_layout()
plt.show()
| product | forecast_qty | lower_80 | upper_80 | backtest_MAE | |
|---|---|---|---|---|---|
| 0 | 精密ギアA | 470 | 407 | 533 | 36.3 |
| 1 | 精密ギアB | 380 | 334 | 425 | 32.5 |
| 2 | 駆動軸C | 283 | 232 | 334 | 30.7 |
結果の読み取り
予測区間の上限は欠品回避を重視する場合の能力・材料確認、下限は過剰在庫リスクの確認に使えます。APIでは予測対象期間、予測作成時点、使用データ最終日、区間の信頼水準も返します。MAEは製品の数量規模に依存するため、製品間比較にはMAEを平均需要で割った指標や、欠品・余剰の非対称コストも併用します。
No.093:異常検知APIを作成する
実務での意味
異常検知APIはセンサー値を受け取り、異常度と判定理由を返します。目的はアラートを増やすことではなく、故障前に点検対象を絞り込むことです。
分析・モデル化の考え方
温度と振動を標準化し、正の逸脱を合成したスコアを用います。閾値ごとに適合率 、再現率 、アラート件数を比較します。見逃しが高額な設備では再現率を優先し、点検能力が限られる場合は適合率との均衡を取ります。
Pythonで確認する
for col in ["temperature_c", "vibration_mm_s"]:
sensors[f"z_{col}"] = (sensors[col] - sensors[col].mean()) / sensors[col].std()
sensors["anomaly_score"] = np.sqrt(np.maximum(sensors["z_temperature_c"], 0)**2 +
np.maximum(sensors["z_vibration_mm_s"], 0)**2)
metrics = []
for threshold in [1.0, 1.5, 2.0, 2.5]:
pred = sensors["anomaly_score"] >= threshold
truth = sensors["true_anomaly"]
tp, fp, fn = (pred & truth).sum(), (pred & ~truth).sum(), (~pred & truth).sum()
metrics.append([threshold, pred.sum(), tp/(tp+fp) if tp+fp else 0, tp/(tp+fn) if tp+fn else 0, fn])
thresholds = pd.DataFrame(metrics, columns=["閾値", "アラート件数", "適合率", "再現率", "見逃し件数"])
display(thresholds.round(3))
| 閾値 | アラート件数 | 適合率 | 再現率 | 見逃し件数 | |
|---|---|---|---|---|---|
| 0 | 1.0 | 351 | 0.735 | 1.000 | 0 |
| 1 | 1.5 | 200 | 0.995 | 0.771 | 59 |
| 2 | 2.0 | 80 | 1.000 | 0.310 | 178 |
| 3 | 2.5 | 31 | 1.000 | 0.120 | 227 |
結果の読み取り
閾値を上げるとアラート件数は減りますが、見逃しが増える可能性があります。したがって「スコア2以上なら異常」とモデル担当だけで決めず、1件の見逃し損失、1回の点検工数、日次対応可能件数を保全担当と確認します。APIは判定、スコア、閾値、主要な寄与項目を返し、センサー欠損や学習範囲外の値は通常判定と分けて通知します。
No.094:予測結果をダッシュボードに表示する
実務での意味
ダッシュボードの目的は全予測値を並べることではなく、計画とAI予測の差が大きく、対応が必要な製品を早く見つけることです。
分析・モデル化の考え方
販売・生産計画と予測を比較し、差分率と予測区間外判定を作ります。差分率は
とし、差分の絶対値が10%以上、または計画が80%予測区間外なら要確認とします。
Pythonで確認する
dashboard = forecast.copy()
dashboard["plan_qty"] = [500, 430, 270]
dashboard["gap_pct"] = 100 * (dashboard["forecast_qty"] - dashboard["plan_qty"]) / dashboard["plan_qty"]
dashboard["outside_interval"] = ((dashboard["plan_qty"] < dashboard["lower_80"]) |
(dashboard["plan_qty"] > dashboard["upper_80"]))
dashboard["status"] = np.where((dashboard["gap_pct"].abs() >= 10) | dashboard["outside_interval"], "要確認", "範囲内")
display(dashboard[["product", "plan_qty", "forecast_qty", "lower_80", "upper_80", "gap_pct", "status"]].round(1))
ax = dashboard.plot.bar(x="product", y=["plan_qty", "forecast_qty"], figsize=(8, 4), rot=0)
ax.set_title("製品別:計画数量とAI予測の比較")
ax.set_xlabel("製品")
ax.set_ylabel("数量(個/週)")
ax.grid(True, axis="y", alpha=.3)
plt.tight_layout()
plt.show()
| product | plan_qty | forecast_qty | lower_80 | upper_80 | gap_pct | status | |
|---|---|---|---|---|---|---|---|
| 0 | 精密ギアA | 500 | 470 | 407 | 533 | -6.0 | 範囲内 |
| 1 | 精密ギアB | 430 | 380 | 334 | 425 | -11.6 | 要確認 |
| 2 | 駆動軸C | 270 | 283 | 232 | 334 | 4.8 | 範囲内 |
結果の読み取り
差が大きい製品を先頭に表示し、計画変更、材料確認、営業確認などの次の操作へつなげます。グラフには計画と予測だけでなく予測区間、実績推移、データ最終更新時刻を表示します。赤色だけに依存せず「要確認」と理由を併記し、利用者が予測を採用・却下した理由を記録できる設計にします。
No.095:異常アラート画面を作成する
実務での意味
アラート画面は異常の一覧ではなく、限られた保全要員が対応順を決める作業台です。設備、発生時刻、異常理由、影響、確認期限、担当、対応状態を一画面で結びます。
分析・モデル化の考え方
異常スコア、設備重要度、未確認時間を合成して優先度を算出します。スコアは優先順位付けの補助であり、安全基準による強制停止を上書きしてはいけません。類似アラートを一定時間で束ね、通知疲れも抑えます。
Pythonで確認する
alerts = sensors[sensors["anomaly_score"] >= 1.5].copy()
importance = {"MC-01": 3, "MC-02": 2, "MC-03": 3, "MC-04": 1, "MC-05": 2, "MC-06": 1}
alerts["設備重要度"] = alerts["machine"].map(importance)
alerts["未確認時間_min"] = rng.integers(0, 91, len(alerts))
alerts["優先度スコア"] = alerts["anomaly_score"]*2 + alerts["設備重要度"] + alerts["未確認時間_min"]/30
alerts["主な理由"] = np.where(alerts["z_temperature_c"] >= alerts["z_vibration_mm_s"], "温度上昇", "振動上昇")
alert_view = alerts.nlargest(10, "優先度スコア")[["timestamp", "machine", "主な理由", "anomaly_score", "設備重要度", "未確認時間_min", "優先度スコア"]]
display(alert_view.round(2))
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_48148/2855950023.py:8: UserWarning: obj.round has no effect with datetime, timedelta, or period dtypes. Use obj.dt.round(...) instead.
display(alert_view.round(2))
| timestamp | machine | 主な理由 | anomaly_score | 設備重要度 | 未確認時間_min | 優先度スコア | |
|---|---|---|---|---|---|---|---|
| 1173 | 2026-06-13 05:15:00 | MC-01 | 温度上昇 | 3.28 | 3 | 57 | 11.45 |
| 1106 | 2026-06-12 12:30:00 | MC-03 | 温度上昇 | 2.57 | 3 | 88 | 11.07 |
| 1030 | 2026-06-11 17:30:00 | MC-03 | 振動上昇 | 2.88 | 3 | 68 | 11.03 |
| 929 | 2026-06-10 16:15:00 | MC-01 | 温度上昇 | 2.57 | 3 | 83 | 10.90 |
| 1113 | 2026-06-12 14:15:00 | MC-03 | 温度上昇 | 2.60 | 3 | 71 | 10.56 |
| 1169 | 2026-06-13 04:15:00 | MC-01 | 温度上昇 | 3.34 | 3 | 26 | 10.56 |
| 1196 | 2026-06-13 11:00:00 | MC-05 | 温度上昇 | 2.78 | 2 | 89 | 10.52 |
| 1157 | 2026-06-13 01:15:00 | MC-01 | 振動上昇 | 2.31 | 3 | 85 | 10.45 |
| 832 | 2026-06-09 16:00:00 | MC-05 | 温度上昇 | 2.86 | 2 | 79 | 10.35 |
| 1161 | 2026-06-13 02:15:00 | MC-02 | 振動上昇 | 2.99 | 2 | 70 | 10.31 |
結果の読み取り
異常度だけでなく設備重要度と経過時間を含めると、事業影響に沿った対応順を作れます。画面では「確認」「点検指示」「誤報」「保留」を記録し、重大アラートは担当者が受領するまで段階的に通知します。誤報理由はモデル改善に利用できますが、現場の評価指標に直結させると入力が歪むため、改善目的を明確に共有します。
No.096:最適化計算の結果を画面に表示する
実務での意味
生産最適化の結果は、数量だけでは採用できません。能力、在庫、納期、段取りなどの制約を満たすことと、現行計画より何が改善するかを説明する必要があります。
分析・モデル化の考え方
簡略化した配分問題として、製品 の生産量 を需要上限まで利益の高い順に割り当てます。目的は
です。ここで は限界利益、 は1個当たり工数、 は総能力です。
Pythonで確認する
plan = pd.DataFrame({"product": products, "demand": forecast["forecast_qty"],
"margin_yen": [1300, 1050, 1600], "minutes_per_unit": [2.4, 1.8, 3.2]})
capacity = 2450
plan["利益/分"] = plan["margin_yen"] / plan["minutes_per_unit"]
remaining = capacity
qty = []
for i in plan.sort_values("利益/分", ascending=False).index:
q = min(plan.loc[i, "demand"], int(remaining // plan.loc[i, "minutes_per_unit"]))
qty.append((i, q)); remaining -= q * plan.loc[i, "minutes_per_unit"]
plan["推奨生産量"] = 0
for i, q in qty: plan.loc[i, "推奨生産量"] = q
plan["不足量"] = plan["demand"] - plan["推奨生産量"]
plan["使用工数_min"] = plan["推奨生産量"] * plan["minutes_per_unit"]
display(plan.round(1))
print(f"能力使用: {plan['使用工数_min'].sum():,.1f}/{capacity:,}分、残余能力: {remaining:,.1f}分")
| product | demand | margin_yen | minutes_per_unit | 利益/分 | 推奨生産量 | 不足量 | 使用工数_min | |
|---|---|---|---|---|---|---|---|---|
| 0 | 精密ギアA | 470 | 1300 | 2.4 | 541.7 | 470 | 0 | 1128.0 |
| 1 | 精密ギアB | 380 | 1050 | 1.8 | 583.3 | 380 | 0 | 684.0 |
| 2 | 駆動軸C | 283 | 1600 | 3.2 | 500.0 | 199 | 84 | 636.8 |
能力使用: 2,448.8/2,450分、残余能力: 1.2分
結果の読み取り
画面には推奨数量とともに、能力使用量、不足量、目的関数、主要制約を表示します。担当者が数量を上書きした場合は制約を再計算し、違反をその場で示します。この例は連続的な能力配分を単純化したものです。実務ではロット、段取り順、設備適合、材料、要員、保全予定を含む整数最適化が必要で、最適解だけでなく実行可能な代替案も有用です。
No.097:LLMを使って業務データを要約する
実務での意味
LLMは日報や会議資料の下書きを短時間で作れます。一方、数値の取り違えや根拠のない説明が混ざる可能性があるため、集計はPythonやSQLで確定し、LLMには確定値を文章化させる役割分担が安全です。
分析・モデル化の考え方
「検索・集計」と「文章生成」を分離します。入力には対象期間、KPI、比較基準、根拠レコードIDを含め、出力は要約、要因候補、推奨確認事項の構造化形式にします。個人名や取引先名は送信前に秘匿化し、人が承認してから配信します。
Pythonで確認する
facts = {
"対象期間": "2026-06-01〜2026-06-13",
"センサー件数": len(sensors),
"真の異常件数_検証用": int(sensors["true_anomaly"].sum()),
"閾値1.5のアラート件数": int((sensors["anomaly_score"] >= 1.5).sum()),
"最高スコア設備": sensors.loc[sensors["anomaly_score"].idxmax(), "machine"],
"最高異常スコア": round(sensors["anomaly_score"].max(), 2),
}
prompt_template = (
"あなたは設備保全会議の記録支援者です。\n"
"以下の確定済みJSONだけを根拠に、事実、要確認事項、次の行動を分けて日本語で要約してください。\n"
"根拠のない原因を断定せず、数値には単位を付けてください。\n"
f"入力: {facts}"
)
print(prompt_template)
あなたは設備保全会議の記録支援者です。
以下の確定済みJSONだけを根拠に、事実、要確認事項、次の行動を分けて日本語で要約してください。
根拠のない原因を断定せず、数値には単位を付けてください。
入力: {'対象期間': '2026-06-01〜2026-06-13', 'センサー件数': 1200, '真の異常件数_検証用': 258, '閾値1.5のアラート件数': 200, '最高スコア設備': 'MC-01', '最高異常スコア': np.float64(3.34)}
結果の読み取り
LLMへ自由にデータを探索させるのではなく、確定済みの事実と明示的な指示を渡すことで、数値誤りと過剰な断定を抑えられます。生成文には根拠データへのリンク、生成時刻、モデル情報、未確認表示を付けます。品質は文章の流暢さだけでなく、数値一致率、根拠提示率、修正率、承認時間で評価します。本notebookでは外部LLMを呼び出していません。
No.098:チャット形式で業務データを問い合わせる
実務での意味
チャット形式なら、利用者は「今週、点検を優先すべき設備は?」のように業務用語で質問できます。しかし、曖昧な期間や権限外データをそのまま検索すると、もっともらしい誤回答や情報漏えいにつながります。
分析・モデル化の考え方
質問を、意図、対象、期間、指標へ変換し、許可された検索テンプレートへ割り当てます。SQLを直接自由生成・実行するのではなく、読み取り専用ビュー、行レベル権限、件数上限を適用します。回答には実行した条件と根拠件数を返します。
Pythonで確認する
questions = pd.DataFrame({
"質問": ["今週の需要予測を製品別に見せて", "異常が多い設備は?", "田中さんの評価を教えて", "来月の最適生産量は?"],
"意図": ["forecast", "anomaly_rank", "personnel", "optimization"],
"必要権限": ["生産計画閲覧", "保全閲覧", "人事機密", "生産計画編集"],
"利用者権限あり": [True, True, False, True],
"期間明確": [True, False, True, True],
})
questions["判定"] = np.select(
[~questions["利用者権限あり"], ~questions["期間明確"]],
["拒否:権限不足", "確認:期間を指定"], default="実行可能")
display(questions)
| 質問 | 意図 | 必要権限 | 利用者権限あり | 期間明確 | 判定 | |
|---|---|---|---|---|---|---|
| 0 | 今週の需要予測を製品別に見せて | forecast | 生産計画閲覧 | True | True | 実行可能 |
| 1 | 異常が多い設備は? | anomaly_rank | 保全閲覧 | True | False | 確認:期間を指定 |
| 2 | 田中さんの評価を教えて | personnel | 人事機密 | False | True | 拒否:権限不足 |
| 3 | 来月の最適生産量は? | optimization | 生産計画編集 | True | True | 実行可能 |
結果の読み取り
権限不足の質問は回答内容を生成する前に拒否し、期間が曖昧なら「直近7日でよいですか」と確認します。回答画面には対象期間、フィルター、集計時刻、参照件数を表示し、元データへ遷移できるようにします。質問、変換された検索条件、実行結果、回答、利用者評価を監査ログへ残しますが、不要な個人情報は保存しません。
No.099:モデル・API・画面の運用設計を整理する
実務での意味
AIシステムは公開後にデータ分布、設備、品種、業務ルールが変わります。モデル、API、画面を別々に監視すると、利用者が困っているのに各担当の指標は正常という状態が起こります。
分析・モデル化の考え方
モデル品質、サービス品質、業務成果の3層でKPIと責任者を定義します。データドリフトの簡易例として、基準期間と直近期間の温度平均差を基準標準偏差で割った標準化平均差を確認します。閾値超過は自動再学習ではなく、調査開始の合図とします。
Pythonで確認する
baseline = sensors.iloc[:400]
recent = sensors.iloc[-400:]
drift = (recent["temperature_c"].mean() - baseline["temperature_c"].mean()) / baseline["temperature_c"].std()
ops = pd.DataFrame({
"層": ["モデル", "モデル", "API", "API", "画面・業務", "画面・業務"],
"指標": ["需要予測MAE", "温度ドリフト", "成功率", "p95応答時間", "推奨採用率", "アラート確認時間"],
"現在値": [forecast["backtest_MAE"].mean(), drift,
100*api_logs["success"].mean(), api_logs["latency_ms"].quantile(.95), 68.0, 14.0],
"監視基準": ["製品別基準比+20%", "|値| >= 0.5", ">= 99.0%", "<= 800ms", "理由別に監視", "<= 15分"],
"一次責任者": ["分析担当", "分析担当", "システム担当", "システム担当", "生産管理", "保全責任者"],
})
display(ops.round(2))
| 層 | 指標 | 現在値 | 監視基準 | 一次責任者 | |
|---|---|---|---|---|---|
| 0 | モデル | 需要予測MAE | 33.17 | 製品別基準比+20% | 分析担当 |
| 1 | モデル | 温度ドリフト | 1.96 | |値| >= 0.5 | 分析担当 |
| 2 | API | 成功率 | 98.05 | >= 99.0% | システム担当 |
| 3 | API | p95応答時間 | 476.00 | <= 800ms | システム担当 |
| 4 | 画面・業務 | 推奨採用率 | 68.00 | 理由別に監視 | 生産管理 |
| 5 | 画面・業務 | アラート確認時間 | 14.00 | <= 15分 | 保全責任者 |
結果の読み取り
標準化平均差が基準を超えた場合、センサー校正、負荷構成、季節、設備劣化などを調査します。モデル版とデータ版を記録し、影響の小さい範囲で新旧版を比較してから展開します。API障害時の縮退運転、旧版への切り戻し、連絡網、判断期限も事前に演習します。再学習は精度だけでなく公平性、安全性、業務ルールとの整合を承認してから実施します。
No.100:業務課題からAI活用システムを設計する
実務での意味
最後に、技術からではなく業務課題から構想します。「AIを導入する」ではなく、「材料欠品による計画変更を減らす」「突発停止を早期発見する」のように、対象判断と期待成果を定義します。
分析・モデル化の考え方
課題、利用者、意思決定、入力、AI出力、行動、KPI、失敗時対応を一本の因果仮説にします。導入効果は、対象件数 × 現状損失 × 改善率から概算し、開発・運用費と比較します。AI精度が高くても、利用率が低ければ成果は出ないため、業務効果を次のように分解します。
Pythonで確認する
scenarios = pd.DataFrame({
"活用テーマ": ["需要予測と材料手配", "設備異常の早期点検", "生産計画最適化"],
"年間判断機会": [156, 240, 52],
"採用率": [.72, .60, .80],
"正しい行動率": [.78, .70, .85],
"1行動の価値_万円": [18, 35, 28],
"年間運用費_万円": [420, 500, 380],
})
scenarios["期待粗効果_万円"] = (scenarios["年間判断機会"] * scenarios["採用率"] *
scenarios["正しい行動率"] * scenarios["1行動の価値_万円"])
scenarios["運用費控除後_万円"] = scenarios["期待粗効果_万円"] - scenarios["年間運用費_万円"]
display(scenarios.round(1))
ax = scenarios.plot.bar(x="活用テーマ", y=["期待粗効果_万円", "年間運用費_万円"], figsize=(9, 4), rot=0)
ax.set_title("AI活用テーマ別:期待粗効果と年間運用費(仮定)")
ax.set_xlabel("活用テーマ")
ax.set_ylabel("金額(万円/年)")
ax.grid(True, axis="y", alpha=.3)
plt.tight_layout()
plt.show()
| 活用テーマ | 年間判断機会 | 採用率 | 正しい行動率 | 1行動の価値_万円 | 年間運用費_万円 | 期待粗効果_万円 | 運用費控除後_万円 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 需要予測と材料手配 | 156 | 0.7 | 0.8 | 18 | 420 | 1577.0 | 1157.0 |
| 1 | 設備異常の早期点検 | 240 | 0.6 | 0.7 | 35 | 500 | 3528.0 | 3028.0 |
| 2 | 生産計画最適化 | 52 | 0.8 | 0.8 | 28 | 380 | 990.1 | 610.1 |
結果の読み取り
試算は仮定に強く依存するため、投資効果の確定値ではありません。ただし採用率を明示すると、モデル精度の改善だけでなく、説明表示、教育、業務手順の改善が価値を左右することが見えます。まず1ライン・1判断で現状KPIを測り、手動承認付きで試行し、安全性と効果を確認してから対象を広げます。AIを使わないルールベースや業務改善が適切な場合も比較対象に含めます。
対象ノックを通して見える実務上の示唆
- AI出力ではなく意思決定を設計単位にする
予測、異常スコア、最適解が、担当者の確認・承認・行動へどうつながるかを先に定義します。 - 不確実性と根拠を画面に残す
予測区間、判定理由、制約、データ時点、モデル版が、利用者の過信と不信の双方を抑えます。 - モデル指標と業務KPIを分けて結ぶ
MAEや再現率だけでなく、欠品、停止、点検工数、採用率、確認時間を追います。 - 失敗を通常状態として設計する
欠損、範囲外入力、API停止、誤報、LLMの誤生成に対し、縮退運転と人の確認を用意します。 - 運用データを次の改善へ戻す
採否理由、誤報理由、問い合わせ、版情報を監査可能な形で記録し、再学習と画面改善へ利用します。
実務導入する場合に必要なこと
1. 業務と責任分界の定義
対象となる判断、判断期限、承認者、AIが提案できる範囲、人が必ず確認する条件を定義します。安全・品質に関する法令、社内標準、顧客要求を優先します。
2. データとセキュリティ
データ所有者、品質基準、更新頻度、保持期間、アクセス権限を決めます。LLM利用では送信可能データ、契約上の学習利用、保存先、機密情報の秘匿化を確認します。
3. API・画面・運用の非機能要件
可用性、応答時間、ピーク件数、監査ログ、バックアップ、切り戻し、障害時の手順を定義します。モデル版、特徴量版、入力、出力を追跡可能にします。
4. 段階導入と効果検証
現状値を測定し、過去データ検証、シャドー運用、限定利用、段階展開の順でリスクを抑えます。精度、業務成果、利用率、安全性を定期レビューし、停止基準も設けます。
まとめ
No.091〜No.100では、機械学習モデルのAPI化から需要予測、異常検知、ダッシュボード、アラート、最適化、LLM、チャット問い合わせ、運用設計、AI活用システム全体の構想までを確認しました。
製造業でAIを価値へ変える鍵は、モデル単体の精度競争ではありません。入力データの品質、不確実性の表示、現場の承認、障害時の代替、継続監視を含めた意思決定システムとして設計することです。架空データで行った試算は、現場データと業務制約を確認するための出発点として利用します。
法人向けのご相談
数理工房では、製造業の業務整理、データ分析、需要予測・異常検知・数理最適化・LLMのモデル開発から、API・業務画面・監視基盤への実装まで一貫してご支援しています。
- AI活用テーマの選定と投資効果を整理したい
- PoCで作ったモデルを現場システムへ組み込みたい
- 予測・異常・最適化結果を説明可能な画面にしたい
- LLMを機密性と監査性に配慮して業務利用したい
- モデル劣化やAPI障害を含む運用設計を整備したい
📩 お問い合わせ: surikobo.co.jp/contact
まずはお気軽にご相談ください。