100本ノック / 異常検知 / 異常検知100本ノック

製造業の異常検知入門|現場判断につなげる基礎10本ノック

製造現場の「いつもと違う」を意思決定につなげる:異常検知の基礎10本ノック

本記事では、架空の精密部品工場を題材に、異常検知の基本概念を 品質・設備保全・生産管理の判断へ結び付けて学びます。No.001〜No.010を通じて、異常の定義、データと手法の選び方、PoC(概念実証)の進め方までを一つの流れとして整理します。

この「異常検知100本ノック」は、基礎、前処理、統計、時系列、センサー、機械学習、多変量、深層学習、評価・アラート、実務運用の全10章で構成します。目的はアルゴリズムを覚えることではなく、検知結果を点検・停止・保全・改善の行動へ変える力を身につけることです。本記事はその入口となる第1章です。

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

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

製造現場では、温度の上昇、振動の増加、寸法の逸脱、歩留まり低下など、多様な「いつもと違う」が発生します。しかし、値が珍しいだけで設備を止めれば過剰対応となり、業務影響が大きい変化を見逃せば不良流出や突発停止につながります。

本記事が扱う問いは、何を異常と呼び、どのデータで捉え、誰がどのように対応するかです。分析担当者だけでなく、製造、品質、保全、生産管理が共通言語を持つことを重視します。

現場でよくある状況

  • 管理限界を超えた値は通知されるが、アラートが多く確認しきれない
  • 正常・異常ラベルが少なく、故障履歴とセンサーログの時刻も揃っていない
  • 統計的には珍しいが、段取り替え直後なので現場には想定内という値がある
  • 単一時点は正常でも、微小な変化が連続すると故障の前兆になる
  • モデル精度は報告されたものの、停止判断や保全指示の責任分界が未定である

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

「異常」はデータだけで一意に決まりません。発生頻度、工程条件、損失の大きさ、検知後に取れる行動を同時に考える必要があります。また、故障は少ないほど望ましいため、教師あり学習に必要な異常事例が本質的に集まりにくいという矛盾もあります。

統計的な珍しさを表す異常スコアを s(x)s(\mathbf{x})、通知しきい値を τ\tau とすれば、基本形は次式です。

alert(x)=1[s(x)>τ]\text{alert}(\mathbf{x}) = \mathbb{1}\left[s(\mathbf{x}) > \tau\right]

ただし、τ\tau は数学だけでなく、見逃し損失、誤報対応工数、安全基準、1日に処理できる件数を踏まえて決めます。

今回扱うノックの全体像

No.テーマ意思決定への接続
001異常検知とは何か監視から行動までを定義する
002異常値・外れ値・異常状態点検対象とデータ修正を混同しない
003統計的異常・業務的異常現場ルールと統計を併用する
004教師あり・教師なしラベルの成熟度で手法を選ぶ
005点・文脈・集合異常時刻や連続性を考慮する
006代表的なデータデータ粒度とキーを設計する
007製造業での活用品質・生産・安全へ展開する
008設備保全での活用予兆を保全優先度へ変換する
009業務データでの活用センサー外の兆候も捉える
010プロジェクトの進め方PoCから運用までを設計する

Python環境の準備

numpypandasmatplotlibだけを利用します。乱数シードを固定し、同じ結果を再現できるようにします。グラフ内は環境による日本語フォント差を避けるため英語表記とし、直後の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:異常検知とは何かを整理する

実務での意味

異常検知は「変な値を探す技術」ではなく、正常な操業パターンからの逸脱を早期に捉え、確認・調整・保全などの行動を促す仕組みです。検知対象、検知時点、担当者、一次対応まで定義して初めて業務価値が生まれます。

分析・モデル化の考え方

ここでは温度の通常範囲を平均 μ\mu と標準偏差 σ\sigma で要約し、μ+3σ\mu+3\sigma を超える点を候補にします。これは異常の確定ではなく、調査対象を絞る一次スクリーニングです。平均・標準偏差は既知の異常や欠損を除いた基準期間から推定するのが原則です。

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()}")

png

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スコア z=(xμ)/σz=(x-\mu)/\sigma と、夜勤時の上限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()

png

結果の読み取り

温度スパイクは即時確認、夜勤条件での温度上昇は運転条件確認、振動の連続上昇は計画保全の前倒し候補です。同じ「異常」でも必要な時間軸とアクションが異なるため、タイプを通知に含めます。

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

png

結果の読み取り

架空評価では品質ドリフトが最優先です。これは年間損失だけでなく、検査データが揃い、隔離・条件調整という行動が明確だからです。重みは経営方針に応じて合意し、感度分析も行います。

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

png

結果の読み取り

不良率上昇と報告行数低下は異なる異常です。前者は工程・材料の調査、後者は入力遅延や連携障害の確認につなげます。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では技術精度だけでなく、アラートから点検までの時間、実際に回避できた損失、運用工数を記録し、投資判断を更新します。

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

  1. 異常の定義を分ける:データ品質、統計的外れ、業務的異常、設備状態を一つのフラグに押し込めない。
  2. 文脈と時間を残す:品種、シフト、段取り、連続性がなければ、現場にとって重要な異常を説明できない。
  3. 手法より行動を先に決める:検知時の確認者、期限、点検内容、エスカレーションを定義する。
  4. 単純な基準をベースラインにする:高度なモデルは、現行ルールより損失や工数を改善できるかで評価する。
  5. レビュー結果を資産化する:アラートへの現場コメントを構造化し、将来のラベルと改善材料にする。

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

  • 対象損失とKPI:見逃し損失、誤報工数、リードタイム、対応率
  • データ基盤:設備・ロット・時刻のID統一、時刻同期、欠損監視
  • 運用設計:アラートレベル、SOP、責任分界、停止権限、監査証跡
  • 評価設計:シャドー運用、現場レビュー、期間・季節をまたぐ検証
  • 継続改善:工程変更、センサー交換、品種追加による分布変化の監視
  • ガバナンス:安全・品質基準をモデルより優先し、人が最終判断する範囲を明記

まとめ

異常検知の出発点は、アルゴリズムではなく「何が起きたら、誰が、何をするか」です。異常値・外れ値・異常状態、統計的異常・業務的異常、点・文脈・集合異常を区別すると、データ処理と現場対応を正しく設計できます。次章以降では、前処理、統計的手法、時系列、多変量、機械学習へ段階的に進みます。

法人向けのご相談

数理工房では、製造業の異常検知PoC、データ診断、モデル開発、現場運用設計、企業研修をご支援します。「データはあるがテーマを絞れない」「アラートが多く使われない」「PoCを本番運用につなげたい」といった段階からご相談いただけます。

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