100本ノック / 異常検知 / 異常検知100本ノック
製造業の異常検知入門|現場判断につなげる基礎10本ノック
製造現場の「いつもと違う」を意思決定につなげる:異常検知の基礎10本ノック
本記事では、架空の精密部品工場を題材に、異常検知の基本概念を 品質・設備保全・生産管理の判断へ結び付けて学びます。No.001〜No.010を通じて、異常の定義、データと手法の選び方、PoC(概念実証)の進め方までを一つの流れとして整理します。
この「異常検知100本ノック」は、基礎、前処理、統計、時系列、センサー、機械学習、多変量、深層学習、評価・アラート、実務運用の全10章で構成します。目的はアルゴリズムを覚えることではなく、検知結果を点検・停止・保全・改善の行動へ変える力を身につけることです。本記事はその入口となる第1章です。
[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。
掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。
はじめに:この記事で扱う製造業の実務課題
製造現場では、温度の上昇、振動の増加、寸法の逸脱、歩留まり低下など、多様な「いつもと違う」が発生します。しかし、値が珍しいだけで設備を止めれば過剰対応となり、業務影響が大きい変化を見逃せば不良流出や突発停止につながります。
本記事が扱う問いは、何を異常と呼び、どのデータで捉え、誰がどのように対応するかです。分析担当者だけでなく、製造、品質、保全、生産管理が共通言語を持つことを重視します。
現場でよくある状況
- 管理限界を超えた値は通知されるが、アラートが多く確認しきれない
- 正常・異常ラベルが少なく、故障履歴とセンサーログの時刻も揃っていない
- 統計的には珍しいが、段取り替え直後なので現場には想定内という値がある
- 単一時点は正常でも、微小な変化が連続すると故障の前兆になる
- モデル精度は報告されたものの、停止判断や保全指示の責任分界が未定である
なぜこの問題は判断が難しいのか
「異常」はデータだけで一意に決まりません。発生頻度、工程条件、損失の大きさ、検知後に取れる行動を同時に考える必要があります。また、故障は少ないほど望ましいため、教師あり学習に必要な異常事例が本質的に集まりにくいという矛盾もあります。
統計的な珍しさを表す異常スコアを 、通知しきい値を とすれば、基本形は次式です。
ただし、 は数学だけでなく、見逃し損失、誤報対応工数、安全基準、1日に処理できる件数を踏まえて決めます。
今回扱うノックの全体像
| No. | テーマ | 意思決定への接続 |
|---|---|---|
| 001 | 異常検知とは何か | 監視から行動までを定義する |
| 002 | 異常値・外れ値・異常状態 | 点検対象とデータ修正を混同しない |
| 003 | 統計的異常・業務的異常 | 現場ルールと統計を併用する |
| 004 | 教師あり・教師なし | ラベルの成熟度で手法を選ぶ |
| 005 | 点・文脈・集合異常 | 時刻や連続性を考慮する |
| 006 | 代表的なデータ | データ粒度とキーを設計する |
| 007 | 製造業での活用 | 品質・生産・安全へ展開する |
| 008 | 設備保全での活用 | 予兆を保全優先度へ変換する |
| 009 | 業務データでの活用 | センサー外の兆候も捉える |
| 010 | プロジェクトの進め方 | PoCから運用までを設計する |
Python環境の準備
numpy、pandas、matplotlibだけを利用します。乱数シードを固定し、同じ結果を再現できるようにします。グラフ内は環境による日本語フォント差を避けるため英語表記とし、直後のMarkdownで業務上の意味を日本語で説明します。
import platform
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from IPython.display import display
SEED = 42
rng = np.random.default_rng(SEED)
plt.rcParams.update({"figure.figsize": (10, 4.5), "axes.grid": True})
print("Python :", platform.python_version())
print("NumPy :", np.__version__)
print("pandas :", pd.__version__)
print("matplotlib :", matplotlib.__version__)
Python : 3.13.1
NumPy : 2.5.1
pandas : 3.0.3
matplotlib : 3.11.0
架空データの作成
5分間隔で稼働する加工設備M-01のログを2日分作成します。温度、振動、主軸負荷、製品寸法に加え、シフト、品種、段取り状態、保全ラベルを持たせます。意図的に単発スパイク、夜勤時だけ問題となる温度、振動の連続上昇、センサー欠損を埋め込みます。
n = 576
ts = pd.date_range("2026-04-01", periods=n, freq="5min")
t = np.arange(n)
shift = np.where((ts.hour >= 8) & (ts.hour < 20), "day", "night")
product = np.where((t // 72) % 2 == 0, "A", "B")
setup = (t % 72 < 5)
temperature = 68 + 2.2 * np.sin(2 * np.pi * t / 288) + rng.normal(0, 0.7, n)
vibration = 1.8 + 0.12 * np.sin(2 * np.pi * t / 60) + rng.normal(0, 0.08, n)
load = 62 + np.where(product == "B", 7, 0) + rng.normal(0, 3.0, n)
dimension = 20 + np.where(product == "B", 0.015, 0) + rng.normal(0, 0.012, n)
temperature[430] = 82.0 # 単発の点異常
temperature[40] = 74.5 # 夜勤という文脈で問題
vibration[300:312] += np.linspace(0.3, 1.5, 12) # 集合異常・故障前兆
dimension[145] = 20.11 # 規格逸脱
temperature[210] = np.nan # 通信欠損
maintenance_label = np.zeros(n, dtype=int)
maintenance_label[300:312] = 1
df = pd.DataFrame({
"timestamp": ts, "equipment": "M-01", "shift": shift,
"product": product, "setup": setup, "temperature_c": temperature,
"vibration_mm_s": vibration, "spindle_load_pct": load,
"dimension_mm": dimension, "maintenance_label": maintenance_label,
})
display(df.head())
print(f"rows={len(df):,}, period={df.timestamp.min()} - {df.timestamp.max()}")
| timestamp | equipment | shift | product | setup | temperature_c | vibration_mm_s | spindle_load_pct | dimension_mm | maintenance_label | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2026-04-01 00:00:00 | M-01 | night | A | True | 68.213302 | 1.906895 | 62.817936 | 20.000879 | 0 |
| 1 | 2026-04-01 00:05:00 | M-01 | night | A | True | 67.320004 | 1.912379 | 60.315551 | 20.000450 | 0 |
| 2 | 2026-04-01 00:10:00 | M-01 | night | A | True | 68.621278 | 1.804748 | 64.093451 | 19.998910 | 0 |
| 3 | 2026-04-01 00:15:00 | M-01 | night | A | True | 68.802282 | 1.866158 | 62.331839 | 19.999630 | 0 |
| 4 | 2026-04-01 00:20:00 | M-01 | night | A | True | 66.826018 | 1.656015 | 62.004020 | 19.997427 | 0 |
rows=576, period=2026-04-01 00:00:00 - 2026-04-02 23:55:00
No.001:異常検知とは何かを整理する
実務での意味
異常検知は「変な値を探す技術」ではなく、正常な操業パターンからの逸脱を早期に捉え、確認・調整・保全などの行動を促す仕組みです。検知対象、検知時点、担当者、一次対応まで定義して初めて業務価値が生まれます。
分析・モデル化の考え方
ここでは温度の通常範囲を平均 と標準偏差 で要約し、 を超える点を候補にします。これは異常の確定ではなく、調査対象を絞る一次スクリーニングです。平均・標準偏差は既知の異常や欠損を除いた基準期間から推定するのが原則です。
Pythonで確認する
baseline = df.loc[:287, "temperature_c"].dropna()
mu, sigma = baseline.mean(), baseline.std()
threshold = mu + 3 * sigma
candidate = df["temperature_c"] > threshold
fig, ax = plt.subplots()
ax.plot(df["timestamp"], df["temperature_c"], label="Temperature", linewidth=1)
ax.axhline(threshold, color="tab:red", linestyle="--", label="Baseline mean + 3 SD")
ax.scatter(df.loc[candidate, "timestamp"], df.loc[candidate, "temperature_c"], color="tab:red", label="Candidates", zorder=3)
ax.set_title("Temperature anomaly screening")
ax.set_xlabel("Timestamp"); ax.set_ylabel("Temperature (C)")
ax.grid(True, alpha=0.3); ax.legend(); fig.tight_layout()
plt.show()
print(f"threshold={threshold:.2f} C, candidates={candidate.sum()}")

threshold=73.14 C, candidates=2
結果の読み取り
しきい値超過は複数抽出されますが、すべてが停止対象とは限りません。時刻、品種、段取り、他センサーを併せて確認し、重大度を決めます。モデルの出力は判断材料であり、判断そのものではないという役割分担が重要です。
No.002:異常値・外れ値・異常状態の違いを理解する
実務での意味
- 異常値:測定・入力・通信の誤りを含む、妥当性に疑いがある値
- 外れ値:他の観測から統計的に離れた値。正しい測定の場合もある
- 異常状態:設備・工程が望ましくない状態。単一の外れ値がなくても成立する
これらを混同すると、故障兆候を欠損扱いで消したり、通信エラーで不要な保全を実施したりします。
分析・モデル化の考え方
最初に物理的妥当性と欠損を検査し、次に統計的な外れを抽出し、最後に業務ルールや複数変数で状態を評価します。データ品質フラグと設備状態フラグは別列で管理します。
Pythonで確認する
check = df.copy()
check["data_quality_issue"] = check["temperature_c"].isna() | ~check["temperature_c"].between(0, 150)
q1, q3 = check["dimension_mm"].quantile([0.25, 0.75])
iqr = q3 - q1
check["statistical_outlier"] = ~check["dimension_mm"].between(q1 - 1.5 * iqr, q3 + 1.5 * iqr)
check["abnormal_state"] = (check["vibration_mm_s"].rolling(6, min_periods=6).mean() > 2.35)
summary = pd.Series({
"data_quality_issue": int(check["data_quality_issue"].sum()),
"statistical_outlier": int(check["statistical_outlier"].sum()),
"abnormal_state": int(check["abnormal_state"].sum()),
}, name="count").to_frame()
display(summary)
display(check.loc[check[["data_quality_issue", "statistical_outlier", "abnormal_state"]].any(axis=1),
["timestamp", "temperature_c", "dimension_mm", "vibration_mm_s",
"data_quality_issue", "statistical_outlier", "abnormal_state"]].head(10))
| count | |
|---|---|
| data_quality_issue | 1 |
| statistical_outlier | 2 |
| abnormal_state | 10 |
| timestamp | temperature_c | dimension_mm | vibration_mm_s | data_quality_issue | statistical_outlier | abnormal_state | |
|---|---|---|---|---|---|---|---|
| 145 | 2026-04-01 12:05:00 | 67.661257 | 20.110000 | 1.950242 | False | True | False |
| 187 | 2026-04-01 15:35:00 | 65.368694 | 19.963428 | 1.931248 | False | True | False |
| 210 | 2026-04-01 17:30:00 | NaN | 19.991873 | 1.803537 | True | False | False |
| 305 | 2026-04-02 01:25:00 | 68.060335 | 19.995237 | 2.781267 | False | False | True |
| 306 | 2026-04-02 01:30:00 | 68.607784 | 20.001365 | 2.736191 | False | False | True |
| 307 | 2026-04-02 01:35:00 | 69.796074 | 20.008842 | 2.850174 | False | False | True |
| 308 | 2026-04-02 01:40:00 | 69.337619 | 19.994449 | 2.974439 | False | False | True |
| 309 | 2026-04-02 01:45:00 | 70.185653 | 20.011959 | 3.202014 | False | False | True |
| 310 | 2026-04-02 01:50:00 | 69.840035 | 19.988076 | 3.394428 | False | False | True |
| 311 | 2026-04-02 01:55:00 | 69.365536 | 20.003535 | 3.375118 | False | False | True |
結果の読み取り
欠損、寸法外れ、振動の継続異常は別の件数として現れます。欠損は計装・通信の確認、寸法外れは品質隔離、振動継続は保全点検というように、フラグごとに対応先を変える設計が必要です。
No.003:統計的異常と業務的異常を区別する
実務での意味
統計的異常は「珍しい」、業務的異常は「規格、安全、納期、コスト上の問題がある」を意味します。珍しくても許容できる試作条件がある一方、統計的にはありふれていても顧客規格をわずかに外れれば不適合です。
分析・モデル化の考え方
統計ルールと業務ルールを別々に評価し、重なりを確認します。ここでは温度のZスコア と、夜勤時の上限72℃という業務ルールを比較します。
Pythonで確認する
temp_mean = df["temperature_c"].mean()
temp_std = df["temperature_c"].std()
df["statistical_anomaly"] = ((df["temperature_c"] - temp_mean) / temp_std).abs() > 3
df["business_anomaly"] = (df["shift"] == "night") & (df["temperature_c"] > 72)
comparison = pd.crosstab(df["statistical_anomaly"], df["business_anomaly"],
rownames=["Statistical"], colnames=["Business"])
display(comparison)
display(df.loc[df["statistical_anomaly"] | df["business_anomaly"],
["timestamp", "shift", "temperature_c", "statistical_anomaly", "business_anomaly"]])
| Business | False | True |
|---|---|---|
| Statistical | ||
| False | 574 | 0 |
| True | 1 | 1 |
| timestamp | shift | temperature_c | statistical_anomaly | business_anomaly | |
|---|---|---|---|---|---|
| 40 | 2026-04-01 03:20:00 | night | 74.5 | True | True |
| 430 | 2026-04-02 11:50:00 | day | 82.0 | True | False |
結果の読み取り
2種類のフラグは完全には一致しません。統計モデルだけでは夜勤固有の運用条件を取りこぼし、業務ルールだけでは未知の極端値を見逃す可能性があります。両者を併用し、理由コードを通知に添えると現場が確認しやすくなります。
No.004:教師あり異常検知と教師なし異常検知を比較する
実務での意味
教師あり手法は過去の故障ラベルを学習し、既知の異常を高精度に再現しやすい一方、ラベル作成と未知故障への対応が課題です。教師なし手法はラベルなしで珍しいパターンを探せますが、抽出結果が業務上重要とは限りません。
分析・モデル化の考え方
手法選定は流行ではなく、ラベル量、ラベル信頼性、未知異常の重要性、説明責任で決めます。ここでは候補手法を意思決定表で比較し、保全ラベルの偏りも確認します。
Pythonで確認する
method_table = pd.DataFrame({
"approach": ["Supervised", "Unsupervised", "Rule + score hybrid"],
"label_requirement": ["Many reliable labels", "Not required", "A few reviewed cases"],
"strength": ["Known failure recognition", "Unknown pattern discovery", "Explainability and gradual rollout"],
"main_risk": ["Misses unseen failure modes", "Many irrelevant alerts", "Rules require maintenance"],
"recommended_phase": ["Mature operation", "Early exploration", "PoC to early operation"],
})
display(method_table)
label_counts = df["maintenance_label"].value_counts().rename(index={0: "normal", 1: "anomaly"})
display(label_counts.to_frame("records"))
print(f"anomaly prevalence={df['maintenance_label'].mean():.2%}")
| approach | label_requirement | strength | main_risk | recommended_phase | |
|---|---|---|---|---|---|
| 0 | Supervised | Many reliable labels | Known failure recognition | Misses unseen failure modes | Mature operation |
| 1 | Unsupervised | Not required | Unknown pattern discovery | Many irrelevant alerts | Early exploration |
| 2 | Rule + score hybrid | A few reviewed cases | Explainability and gradual rollout | Rules require maintenance | PoC to early operation |
| records | |
|---|---|
| maintenance_label | |
| normal | 564 |
| anomaly | 12 |
anomaly prevalence=2.08%
結果の読み取り
異常ラベルは全体のごく一部で、不均衡です。この段階では教師なしスコアと現場ルールで候補を提示し、保全担当者の確認結果を蓄積するハイブリッド運用が現実的です。蓄積したレビューを将来の教師あり学習に利用できます。
No.005:点異常・文脈異常・集合異常を理解する
実務での意味
点異常は単一観測の極端値、文脈異常はシフトや品種を考慮すると不自然な値、集合異常は個々の値が軽微でも連続・集団として不自然な状態です。製造現場の予兆は集合異常として現れることが少なくありません。
分析・モデル化の考え方
点異常には全体しきい値、文脈異常には条件別基準、集合異常には移動平均や連続回数を使います。時間順序を無視して行を独立に扱うと、集合異常の情報が失われます。
Pythonで確認する
point_flag = df["temperature_c"] > 80
context_flag = (df["shift"] == "night") & (df["temperature_c"] > 72)
rolling_vib = df["vibration_mm_s"].rolling(6, min_periods=6).mean()
collective_flag = rolling_vib > 2.35
fig, axes = plt.subplots(2, 1, figsize=(10, 7), sharex=True)
axes[0].plot(df["timestamp"], df["temperature_c"], linewidth=1)
axes[0].scatter(df.loc[point_flag | context_flag, "timestamp"],
df.loc[point_flag | context_flag, "temperature_c"], color="tab:red")
axes[0].set_title("Point and contextual anomalies"); axes[0].set_ylabel("Temperature (C)")
axes[0].grid(True, alpha=0.3)
axes[1].plot(df["timestamp"], rolling_vib, label="6-point rolling mean")
axes[1].axhline(2.35, color="tab:red", linestyle="--", label="Collective threshold")
axes[1].fill_between(df["timestamp"], 0, rolling_vib, where=collective_flag, color="tab:red", alpha=0.25)
axes[1].set_title("Collective anomaly in vibration"); axes[1].set_xlabel("Timestamp")
axes[1].set_ylabel("Vibration (mm/s)"); axes[1].grid(True, alpha=0.3); axes[1].legend()
fig.tight_layout(); plt.show()

結果の読み取り
温度スパイクは即時確認、夜勤条件での温度上昇は運転条件確認、振動の連続上昇は計画保全の前倒し候補です。同じ「異常」でも必要な時間軸とアクションが異なるため、タイプを通知に含めます。
No.006:異常検知で扱う代表的なデータを整理する
実務での意味
異常検知はセンサーだけでは完結しません。検査、稼働、アラーム、保全、作業条件、環境、受発注などをつなぐことで原因候補と業務影響を判断できます。
分析・モデル化の考え方
重要なのはデータ量より、設備ID・品番・ロット・時刻などの結合キー、粒度、取得遅延、欠損の意味です。まずデータ台帳を作り、1行が何を表すかを明確にします。
Pythonで確認する
data_catalog = pd.DataFrame([
["Sensor", "5 minutes", "equipment + timestamp", "Temperature, vibration", "Condition monitoring"],
["Inspection", "product", "lot + serial", "Dimension, pass/fail", "Quality containment"],
["Production", "lot", "equipment + lot", "Count, cycle time", "Loss detection"],
["Maintenance", "work order", "equipment + work time", "Part, symptom, action", "Cause validation"],
["Master", "revision", "equipment/product ID", "Spec, location, model", "Context enrichment"],
], columns=["data_type", "grain", "join_key", "examples", "decision_use"])
display(data_catalog)
quality = df.isna().sum().to_frame("missing_count")
quality["missing_rate"] = df.isna().mean()
display(quality.query("missing_count > 0"))
| data_type | grain | join_key | examples | decision_use | |
|---|---|---|---|---|---|
| 0 | Sensor | 5 minutes | equipment + timestamp | Temperature, vibration | Condition monitoring |
| 1 | Inspection | product | lot + serial | Dimension, pass/fail | Quality containment |
| 2 | Production | lot | equipment + lot | Count, cycle time | Loss detection |
| 3 | Maintenance | work order | equipment + work time | Part, symptom, action | Cause validation |
| 4 | Master | revision | equipment/product ID | Spec, location, model | Context enrichment |
| missing_count | missing_rate | |
|---|---|---|
| temperature_c | 1 | 0.001736 |
結果の読み取り
今回のログでも温度に欠損があります。欠損をゼロで埋めると偽の急低下を作るため、通信断として別途監視すべきです。本番では設備ID体系と時刻同期を先に整備すると、分析の手戻りを大きく減らせます。
No.007:製造業における異常検知の活用例を整理する
実務での意味
活用先は予知保全だけではありません。品質異常、サイクルタイム遅延、エネルギー原単位悪化、安全逸脱など、意思決定と損失が定義できる領域が対象です。
分析・モデル化の考え方
候補テーマは、年間損失、検知可能性、検知後の行動可能性、データ準備度で評価します。精度が高くても対応できないテーマより、多少単純でも現場がすぐ動けるテーマの方が価値を出しやすい場合があります。
Pythonで確認する
use_cases = pd.DataFrame({
"use_case": ["Quality drift", "Bearing degradation", "Cycle-time delay", "Energy loss"],
"annual_loss_mjpy": [24, 40, 18, 12],
"detectability": [4, 3, 4, 3],
"actionability": [5, 4, 3, 4],
"data_readiness": [5, 3, 4, 2],
})
use_cases["priority_score"] = (
0.4 * use_cases["annual_loss_mjpy"] / use_cases["annual_loss_mjpy"].max() * 5
+ 0.2 * use_cases["detectability"]
+ 0.25 * use_cases["actionability"]
+ 0.15 * use_cases["data_readiness"]
)
display(use_cases.sort_values("priority_score", ascending=False).round(2))
fig, ax = plt.subplots()
ax.bar(use_cases["use_case"], use_cases["priority_score"], color="tab:blue")
ax.set_title("Anomaly-detection use-case priority")
ax.set_xlabel("Use case"); ax.set_ylabel("Priority score")
ax.grid(True, axis="y", alpha=0.3); ax.tick_params(axis="x", rotation=15); fig.tight_layout()
plt.show()
| use_case | annual_loss_mjpy | detectability | actionability | data_readiness | priority_score | |
|---|---|---|---|---|---|---|
| 1 | Bearing degradation | 40 | 3 | 4 | 3 | 4.05 |
| 0 | Quality drift | 24 | 4 | 5 | 5 | 4.00 |
| 2 | Cycle-time delay | 18 | 4 | 3 | 4 | 3.05 |
| 3 | Energy loss | 12 | 3 | 4 | 2 | 2.50 |

結果の読み取り
架空評価では品質ドリフトが最優先です。これは年間損失だけでなく、検査データが揃い、隔離・条件調整という行動が明確だからです。重みは経営方針に応じて合意し、感度分析も行います。
No.008:設備保全における異常検知の活用例を整理する
実務での意味
設備保全では、異常検知を「故障を当てる」だけでなく、点検順位と実施時期を決めるために使います。突発停止損失、部品納期、安全リスクを加味して優先度を付けます。
分析・モデル化の考え方
振動の基準値からの乖離を簡易リスクスコアとし、一定時間継続した場合のみ点検候補にします。単発ノイズを抑えるため、連続性とヒステリシスを設けることが実務では重要です。
Pythonで確認する
vib_base = df.loc[:287, "vibration_mm_s"]
vib_z = (df["vibration_mm_s"] - vib_base.mean()) / vib_base.std()
df["maintenance_risk"] = vib_z.clip(lower=0)
df["inspection_candidate"] = df["maintenance_risk"].rolling(3, min_periods=3).min() > 3
top_risk = df.nlargest(8, "maintenance_risk")[
["timestamp", "vibration_mm_s", "maintenance_risk", "inspection_candidate"]
]
display(top_risk.round(2))
print("first inspection candidate:",
df.loc[df["inspection_candidate"], "timestamp"].min())
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_19197/885776230.py:9: UserWarning: obj.round has no effect with datetime, timedelta, or period dtypes. Use obj.dt.round(...) instead.
display(top_risk.round(2))
| timestamp | vibration_mm_s | maintenance_risk | inspection_candidate | |
|---|---|---|---|---|
| 310 | 2026-04-02 01:50:00 | 3.39 | 13.49 | True |
| 311 | 2026-04-02 01:55:00 | 3.38 | 13.33 | True |
| 309 | 2026-04-02 01:45:00 | 3.20 | 11.86 | True |
| 308 | 2026-04-02 01:40:00 | 2.97 | 9.94 | True |
| 307 | 2026-04-02 01:35:00 | 2.85 | 8.89 | True |
| 305 | 2026-04-02 01:25:00 | 2.78 | 8.30 | True |
| 306 | 2026-04-02 01:30:00 | 2.74 | 7.92 | True |
| 304 | 2026-04-02 01:20:00 | 2.60 | 6.80 | True |
first inspection candidate: 2026-04-02 01:15:00
結果の読み取り
連続条件により、振動上昇の途中から点検候補が立ち上がります。実運用では、停止までの猶予、予備品在庫、生産計画を組み合わせ、「今すぐ停止」「次の休止で点検」「経過観察」に振り分けます。
No.009:業務データにおける異常検知の活用例を整理する
実務での意味
日報、実績工数、在庫、購買、納期、検査件数などの業務データにも異常は現れます。設備ログがなくても、歩留まり悪化や入力停止、工程滞留を早期に把握できます。
分析・モデル化の考え方
日次の生産数と不良数を集計し、不良率とデータ完全性を監視します。件数が少ない日の率は変動しやすいため、分母も同時に表示します。業務カレンダー、休業日、棚卸日などの文脈も必要です。
Pythonで確認する
daily = pd.DataFrame({
"date": pd.date_range("2026-04-01", periods=14, freq="D"),
"production_qty": rng.integers(900, 1100, 14),
"defect_qty": rng.poisson(11, 14),
"report_rows": rng.integers(18, 23, 14),
})
daily.loc[8, "defect_qty"] = 46
daily.loc[11, "report_rows"] = 4
daily["defect_rate"] = daily["defect_qty"] / daily["production_qty"]
daily["quality_alert"] = daily["defect_rate"] > 0.025
daily["data_delay_alert"] = daily["report_rows"] < 15
display(daily.loc[daily["quality_alert"] | daily["data_delay_alert"]].round(4))
fig, ax = plt.subplots()
ax.plot(daily["date"], daily["defect_rate"] * 100, marker="o")
ax.axhline(2.5, color="tab:red", linestyle="--", label="Review threshold")
ax.set_title("Daily defect-rate monitoring")
ax.set_xlabel("Date"); ax.set_ylabel("Defect rate (%)")
ax.grid(True, alpha=0.3); ax.legend(); fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_19197/2887811494.py:12: UserWarning: obj.round has no effect with datetime, timedelta, or period dtypes. Use obj.dt.round(...) instead.
display(daily.loc[daily["quality_alert"] | daily["data_delay_alert"]].round(4))
| date | production_qty | defect_qty | report_rows | defect_rate | quality_alert | data_delay_alert | |
|---|---|---|---|---|---|---|---|
| 8 | 2026-04-09 | 1077 | 46 | 18 | 0.0427 | True | False |
| 11 | 2026-04-12 | 930 | 11 | 4 | 0.0118 | False | True |

結果の読み取り
不良率上昇と報告行数低下は異なる異常です。前者は工程・材料の調査、後者は入力遅延や連携障害の確認につなげます。KPI異常とデータパイプライン異常を分けることで、責任部署へのルーティングが明確になります。
No.010:異常検知プロジェクトの進め方を整理する
実務での意味
異常検知プロジェクトの成果はモデルではなく、損失削減、停止回避、確認工数削減などの業務成果です。PoC開始前に、検知後の行動と効果測定方法まで合意します。
分析・モデル化の考え方
進め方は、課題定義 → データ監査 → ベースライン → オフライン評価 → シャドー運用 → 限定運用 → 本番・改善です。モデル指標に加え、1日当たりアラート数、検知リードタイム、対応率、回避損失をKPIにします。
Pythonで確認する
project_plan = pd.DataFrame([
[1, "Problem framing", "Target loss and action defined", "Plant manager"],
[2, "Data audit", "Keys, gaps, labels reviewed", "Data/OT owner"],
[3, "Baseline", "Rules and simple score compared", "Analytics"],
[4, "Offline evaluation", "Recall, false alerts, lead time", "Analytics + maintenance"],
[5, "Shadow operation", "Alerts reviewed without control", "Operators"],
[6, "Limited rollout", "SOP and escalation validated", "Operations"],
[7, "Production", "Value and drift monitored", "Cross-functional owner"],
], columns=["step", "phase", "exit_criterion", "primary_owner"])
display(project_plan)
expected = pd.Series({
"avoided_failures_per_year": 3,
"loss_per_failure_mjpy": 8.0,
"annual_operation_cost_mjpy": 6.0,
})
expected["expected_net_value_mjpy"] = (
expected["avoided_failures_per_year"] * expected["loss_per_failure_mjpy"]
- expected["annual_operation_cost_mjpy"]
)
display(expected.to_frame("assumption"))
| step | phase | exit_criterion | primary_owner | |
|---|---|---|---|---|
| 0 | 1 | Problem framing | Target loss and action defined | Plant manager |
| 1 | 2 | Data audit | Keys, gaps, labels reviewed | Data/OT owner |
| 2 | 3 | Baseline | Rules and simple score compared | Analytics |
| 3 | 4 | Offline evaluation | Recall, false alerts, lead time | Analytics + maintenance |
| 4 | 5 | Shadow operation | Alerts reviewed without control | Operators |
| 5 | 6 | Limited rollout | SOP and escalation validated | Operations |
| 6 | 7 | Production | Value and drift monitored | Cross-functional owner |
| assumption | |
|---|---|
| avoided_failures_per_year | 3.0 |
| loss_per_failure_mjpy | 8.0 |
| annual_operation_cost_mjpy | 6.0 |
| expected_net_value_mjpy | 18.0 |
結果の読み取り
架空試算では年間純効果は18百万円ですが、回避件数や損失額は不確実です。PoCでは技術精度だけでなく、アラートから点検までの時間、実際に回避できた損失、運用工数を記録し、投資判断を更新します。
対象ノックを通して見える実務上の示唆
- 異常の定義を分ける:データ品質、統計的外れ、業務的異常、設備状態を一つのフラグに押し込めない。
- 文脈と時間を残す:品種、シフト、段取り、連続性がなければ、現場にとって重要な異常を説明できない。
- 手法より行動を先に決める:検知時の確認者、期限、点検内容、エスカレーションを定義する。
- 単純な基準をベースラインにする:高度なモデルは、現行ルールより損失や工数を改善できるかで評価する。
- レビュー結果を資産化する:アラートへの現場コメントを構造化し、将来のラベルと改善材料にする。
実務導入する場合に必要なこと
- 対象損失とKPI:見逃し損失、誤報工数、リードタイム、対応率
- データ基盤:設備・ロット・時刻のID統一、時刻同期、欠損監視
- 運用設計:アラートレベル、SOP、責任分界、停止権限、監査証跡
- 評価設計:シャドー運用、現場レビュー、期間・季節をまたぐ検証
- 継続改善:工程変更、センサー交換、品種追加による分布変化の監視
- ガバナンス:安全・品質基準をモデルより優先し、人が最終判断する範囲を明記
まとめ
異常検知の出発点は、アルゴリズムではなく「何が起きたら、誰が、何をするか」です。異常値・外れ値・異常状態、統計的異常・業務的異常、点・文脈・集合異常を区別すると、データ処理と現場対応を正しく設計できます。次章以降では、前処理、統計的手法、時系列、多変量、機械学習へ段階的に進みます。
法人向けのご相談
数理工房では、製造業の異常検知PoC、データ診断、モデル開発、現場運用設計、企業研修をご支援します。「データはあるがテーマを絞れない」「アラートが多く使われない」「PoCを本番運用につなげたい」といった段階からご相談いただけます。
📩 お問い合わせ: surikobo.co.jp/contact
まずはお気軽にご相談ください。