{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ๐ŸŽฏ [MNT04] ๊ณ ์žฅ์‹ค์ ยท์ˆ˜๋ฆฌ๋ถ€์† ์†Œ๋ชจ ๊ธฐ๋ฐ˜ PBL ํ’ˆ๋ชฉ๊ด€๋ฆฌ\n", "### Google Colab ์‹ค์Šต ํ™˜๊ฒฝ (์žฅ๋น„์ •๋น„ยท์ˆ˜๋ฆฌ๋ถ€์† / Level 3)\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/MNT04/MNT04_solution.ipynb)\n", "\n", "---\n", "\n", "## ๐Ÿ“Œ 1. ํ”„๋กœ์ ํŠธ ๊ฐœ์š” ๋ฐ ๋ฐฐ๊ฒฝ\n", "\n", "| ํ•ญ๋ชฉ | ๋‚ด์šฉ |\n", "| :--- | :--- |\n", "| **๊ณผ์ • ๋ถ„๋ฅ˜** | ์žฅ๋น„์ •๋น„ยท์ˆ˜๋ฆฌ๋ถ€์† (Level 3) |\n", "| **ํ•™์Šต ์†Œ์š” ์‹œ๊ฐ„** | 10๊ฐœ ๋ฏธ์…˜ (5์ผ / ์ด 28.5์‹œ๊ฐ„) |\n", "| **ํ™˜๊ฒฝ ์‚ฌ์–‘** | Python 3.10+, numpy, pandas, matplotlib, scipy, scikit-learn |\n", "\n", "### ๐Ÿ“– ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ\n", "ํ•ญ๊ณตยท๊ธฐ๊ฐ‘ ์žฅ๋น„์˜ ์—”์ง„๊ณผ ๊ตฌ๋™๊ณ„๋Š” ๊ณ ์žฅ์ด ๋‚˜๊ธฐ ์ „์— ์ง„๋™, ์˜จ๋„, ์••๋ ฅ์— ๋ฏธ์„ธํ•œ ๋ณ€ํ™”๊ฐ€ ๋จผ์ € ๋‚˜ํƒ€๋‚˜์ง€๋งŒ, ๊ณ ์ • ์ž„๊ณ„๊ฐ’๋งŒ์œผ๋กœ๋Š” ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ์„ ์ด์ƒ์œผ๋กœ ์˜ค์ธํ•˜๊ฑฐ๋‚˜ ์ดˆ๊ธฐ ์—ดํ™”๋ฅผ ๋†“์ณ ์ˆ˜๋ฆฌ๋ถ€์†์„ ์ œ๋•Œ ํ™•๋ณดํ•˜์ง€ ๋ชปํ•œ๋‹ค. ๋ณธ ํ”„๋กœ์ ํŠธ๋Š” ๊ณ ์žฅ ์‹ค์ ๊ณผ ์ˆ˜๋ฆฌ๋ถ€์† ์†Œ๋ชจ๋ฅผ ์—ฐ๊ฒฐํ•ด ์ •๋น„์™€ ๋ถ€ํ’ˆ ๊ด€๋ฆฌ์˜ ์—ฐ๊ณ„ ๋ฌธ์ œ๋ฅผ ํ•™์Šตํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ, ๊ต์œก์šฉ ๊ฐ€์ƒ ๊ธฐ๋™์ฒด 48๋Œ€์˜ ์„ผ์„œ ์‹œ๊ณ„์—ด๊ณผ ๋ถ€ํ’ˆ ์†Œ๋ชจ๋ฅผ ์ฝ”๋“œ๋กœ ๋งŒ๋“ค์–ด ๋ถ„์„ํ•œ๋‹ค. ์„ผ์„œ ๋ฐ์ดํ„ฐ์…‹ ์ •์˜, ์žก์Œ ์ œ๊ฑฐ์™€ FFT ํŠน์ง•๋Ÿ‰ ์ถ”์ถœ, ์ž„๊ณ„๊ฐ’ Baseline๊ณผ ๋ถ€ํ’ˆ ์†Œ๋ชจ ํŒŒ๋ ˆํ†  ๋ถ„์„, ์˜คํ† ์ธ์ฝ”๋” ๊ธฐ๋ฐ˜ ๋ฏธ์„ธ ๋ณ€ํ™” ๊ฐ์ง€์™€ ํŒŒ์ƒ ํ”ผ์ฒ˜ ๋ชจ๋ธ, ์ž”์กด์ˆ˜๋ช…(RUL) ์˜ˆ์ธก๊ณผ ์ด์ƒ ์‹ ๋ขฐ๋„ ์ ์ˆ˜, ์„ค๋ช…๊ฐ€๋Šฅํ•œ ๊ฒฝ๋ณด ํ™”๋ฉด, ๊ฒ€์‚ฌ๊ด€ ์Šน์ธ(HITL) ๊ธฐ๋ฐ˜ ์กฐ๊ธฐ ์ž…๊ณ  ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, ๋Œ€์šฉ๋Ÿ‰ ์ŠคํŠธ๋ฆผ ์ŠคํŠธ๋ ˆ์Šค ์‹œํ—˜, AAR ์˜ค๊ฒฝ๋ณด ๋ถ„์„๊ณผ ์ž„๊ณ„ ์žฌ๋ณด์ •, ์žฌํ•™์Šต์„ ํฌํ•จํ•œ ํ†ตํ•ฉ ๋„๊ตฌ๊นŒ์ง€ ๋‹จ๊ณ„๋ณ„๋กœ ๊ตฌํ˜„ํ•œ๋‹ค. ์ตœ์ข… ๋ชฉํ‘œ๋Š” ๊ณ ์žฅ ์ „์— ์ด์ƒ ์ง•ํ›„๋ฅผ ์ผ์ฐ ์žก์•„ ์ •๋น„ ์‹œ์ ๊ณผ ์ˆ˜๋ฆฌ๋ถ€์† ์†Œ์š”๋ฅผ ์˜ˆ๊ณ ํ•˜๋˜, ์กฐ๊ธฐ ์ž…๊ณ  ๊ฒฐ์ •์€ ๊ฒ€์‚ฌ๊ด€์ด ๊ทผ๊ฑฐ๋ฅผ ๋ณด๊ณ  ๋‚ด๋ฆฌ๋„๋ก ์ง€์›ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.\n", "\n", "> **โš ๏ธ [๊ต์œก์šฉ ๊ฐ€์ƒ ๋ฐ์ดํ„ฐ์…‹ ์›์น™]**\n", "> - ๋ณธ ๋…ธํŠธ๋ถ์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ(์ œ์›, ๋กœ๊ทธ, ์‹œ๋‚˜๋ฆฌ์˜ค ๋“ฑ)๋Š” ๋ณด์•ˆ ๊ทœ์ •์„ ์ค€์ˆ˜ํ•˜์—ฌ **์ฝ”๋“œ ๋‚ด ๋‚œ์ˆ˜ ๊ธฐ๋ฐ˜ ๊ฐ€์ƒ ๋ฐ์ดํ„ฐ๋กœ ์ƒ์„ฑ**๋ฉ๋‹ˆ๋‹ค.\n", "> - ์‹ค์ œ ๋ถ€๋Œ€ ์œ„์น˜, ์ž‘์ „ ์ธ๊ฐ€, ์‹ค์ œ ์ „์žฅ ์ •๋ณด, ๊ตฐ์‚ฌ ๊ธฐ๋ฐ€ ๋ฐ์ดํ„ฐ๋Š” ์ผ์ฒด ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.\n", "\n", "---\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ============================================================================\n", "# [Google Colab ํ™˜๊ฒฝ ์„ค์ •] ํ•„์ˆ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ ๋ฐ ์ž„ํฌํŠธ\n", "# ============================================================================\n", "!pip install -q numpy pandas matplotlib scipy scikit-learn\n", "\n", "import os\n", "import sys\n", "import time\n", "import math\n", "import json\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "import warnings\n", "warnings.filterwarnings('ignore')\n", "\n", "# ๊ทธ๋ž˜ํ”„ ์˜๋ฌธ ํฐํŠธ ๊ธฐ๋ณธ ์„ค์ • (ํ™˜๊ฒฝ ๋…๋ฆฝ์„ฑ ๋ณด์žฅ)\n", "plt.rcParams['font.sans-serif'] = 'DejaVu Sans'\n", "plt.rcParams['axes.unicode_minus'] = False\n", "\n", "print(f\"์‹คํ–‰ ํ™˜๊ฒฝ ์ค€๋น„ ์™„๋ฃŒ (Python {sys.version.split()[0]})\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 01. ํ—ฌ๊ธฐ/๊ธฐ๊ฐ‘ ์—”์ง„ ๋ฐ ๊ตฌ๋™๊ณ„ ์ง„๋™, ์˜จ๋„, ์••๋ ฅ ์„ผ์„œ ์‹œ๊ณ„์—ด ๋ฐ์ดํ„ฐ์…‹ ์ •์˜\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 1.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "ํ—ฌ๊ธฐ/๊ธฐ๊ฐ‘ ์—”์ง„ ์ง„๋™, ์˜จ๋„, ์••๋ ฅ ์„ผ์„œ ์‹œ๊ณ„์—ด ๋ฐ์ดํ„ฐ์…‹์„ ์ •์˜ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **ํ—ฌ๊ธฐ/๊ธฐ๊ฐ‘ ์—”์ง„ ๋ฐ ๊ตฌ๋™๊ณ„์˜ ์ง„๋™, ์˜จ๋„, ์••๋ ฅ ์„ผ์„œ ์‹œ๊ณ„์—ด๊ณผ ๊ณ ์žฅยท์ˆ˜๋ฆฌ๋ถ€์† ์†Œ๋ชจ ์‹ค์ ์˜ ๋ฐ์ดํ„ฐ์…‹ ์Šคํ‚ค๋งˆ, ๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ, ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต์„ ์ •์˜ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์„ผ์„œ ์ˆ˜์ง‘๋ถ€ํ„ฐ ์ „์ฒ˜๋ฆฌ, ํŠน์ง• ์ถ”์ถœ, ์ด์ƒ ๊ฐ์ง€, RUL ์˜ˆ์ธก, ๊ฒ€์‚ฌ๊ด€ ์Šน์ธ, ์ •๋น„ยท๋ถ€ํ’ˆ ์†Œ๋ชจ, ์‚ฌํ›„๊ฒ€ํ† ๊นŒ์ง€ ์—…๋ฌด ํ๋ฆ„์„ ๋‹จ๊ณ„๋ณ„๋กœ ์ •๋ฆฌํ•˜๊ณ , ์„ผ์„œยทํŠน์ง•ยท๊ณ ์žฅยท๋ถ€ํ’ˆ ๋ฌถ์Œ์œผ๋กœ ํ”ผ์ฒ˜ 20๊ฐœ ์ด์ƒ ์ •์˜ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/mnt04_logistics_domain_spec.md ๋ฐ ๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ์‹ค๋ฌด ์—…๋ฌด ํ”„๋กœ์„ธ์Šค์™€์˜ ์ผ์น˜์„ฑ ๋ฐ ๋ถ„์„ ๋ชฉํ‘œ์˜ ๋ช…ํ™•์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต ์ž‘์„ฑ ์™„๋ฃŒ, ํ•„์š” ํ”ผ์ฒ˜(Feature) ๋ชฉ๋ก 20๊ฐœ ์ด์ƒ ์ •์˜\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ๋„๋ฉ”์ธ ๋ถ„์„์„œ ๋ฐ ๋ฐ์ดํ„ฐ ์š”๊ตฌ๋ช…์„ธ์„œ ์Šน์ธ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๊ตฐ์ˆ˜ ๋‹ด๋‹น ์‹ค๋ฌด์ž ์ธํ„ฐ๋ทฐ ๋ฐ ๊ธฐ์กด ์ „์‚ฐ์ฒด๊ณ„ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ๋Œ€์กฐ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"ํ—ฌ๊ธฐ/๊ธฐ๊ฐ‘ ์—”์ง„ ๋ฐ ๊ตฌ๋™๊ณ„์˜ ์ง„๋™ยท์˜จ๋„ยท์••๋ ฅ ์„ผ์„œ ์‹œ๊ณ„์—ด, ๊ณ ์žฅ ์‹ค์ , ์ˆ˜๋ฆฌ๋ถ€์† ์†Œ๋ชจ ๊ธฐ๋ก์˜ ๋ฐ์ดํ„ฐ์…‹ ์Šคํ‚ค๋งˆยท๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ(ํ”ผ์ฒ˜ 20๊ฐœ ์ด์ƒ)์™€ ์˜ˆ์ง€์ •๋น„ยทPBL ํ’ˆ๋ชฉ๊ด€๋ฆฌ ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต์„ ์ •์˜ํ•˜๊ณ , ๊ฐ€์ƒ ์„ผ์„œ ์ž๋ฃŒ๋กœ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import json\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "from scipy.stats import kurtosis\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๊ธฐ๋™์ฒด 48๋Œ€์˜ ์—”์ง„ยท๊ตฌ๋™๊ณ„ ์ง„๋™ยท์˜จ๋„ยท์••๋ ฅ ์„ผ์„œ ์ž๋ฃŒ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๊ฐœ์ฒด๋งˆ๋‹ค ์ •์ƒ -> ์—ดํ™” -> ๊ณ ์žฅ(์ˆ˜๋ช…์ข…๋ฃŒ)๊นŒ์ง€ ํ•˜๋ฃจ 1ํšŒ ์ธก์ •(์ง„๋™ ํŒŒํ˜• 256ํ‘œ๋ณธ + ์˜จ๋„ + ์••๋ ฅ + ๋ถ€ํ•˜)์„ ๋งŒ๋“ ๋‹ค.\n", "# ์—ดํ™” ์œ ํ˜• 4๊ฐ€์ง€: bearing(๋ฒ ์–ด๋ง), gear(๊ธฐ์–ด), oil_pump(์˜ค์ผ ํŽŒํ”„), cooling(๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ)\n", "# ---------------------------------------------------------------------------\n", "FS, NS = 1024, 256 # ๊ฐ€์ƒ ํ‘œ๋ณธํ™” ์ฃผํŒŒ์ˆ˜(Hz), ํŒŒํ˜• 1๊ฐœ๋‹น ํ‘œ๋ณธ ์ˆ˜\n", "TT = np.arange(NS) / FS\n", "MODES = { # ์—ดํ™” ์œ ํ˜•: (๋ฐœ์ƒ ํ™•๋ฅ , ํ‰๊ท  ์ˆ˜๋ช…(์ผ), ๊ต์ฒด ์ˆ˜๋ฆฌ๋ถ€์†, ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜)\n", " \"bearing\": (0.40, 160, \"์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ\", 6.0), \"gear\": (0.25, 175, \"๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ\", 9.0),\n", " \"oil_pump\": (0.20, 150, \"์˜ค์ผ ํŽŒํ”„\", 4.0), \"cooling\": (0.15, 140, \"๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ\", 3.0)}\n", "KIT_COST = 1.0 # ๋ชจ๋“  ์ •๋น„์— ํ•จ๊ป˜ ์†Œ๋ชจ๋˜๋Š” ์”ฐยทํ•„ํ„ฐ ํ‚คํŠธ ๋‹จ๊ฐ€์ง€์ˆ˜\n", "BANDS = {\"b50\": (40, 60), \"b100\": (90, 110), \"b150\": (140, 156), \"b165\": (156, 176), \"b200\": (190, 215)}\n", "\n", "\n", "def make_runs(n=48, seed=1):\n", " \"\"\"๋ฐ˜ํ™˜: units(๊ฐœ์ฒด ์ •๋ณด), snap(์ผ๋ณ„ ์ธก์ • ์š”์•ฝ), W(์ง„๋™ ํŒŒํ˜• [์ธก์ • ์ˆ˜, 256])\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(MODES)\n", " mode = rng.choice(names, n, p=[MODES[m][0] for m in names])\n", " life = np.clip(np.array([rng.normal(MODES[m][1], 18) for m in mode]), 90, 230).astype(int)\n", " start = np.sort(rng.integers(0, 240, n))\n", " units = pd.DataFrame({\"unit\": [f\"U{i:02d}\" for i in range(n)], \"mode\": mode, \"life\": life, \"start\": start, \"base_load\": rng.uniform(0.4, 0.75, n)})\n", " rows = []\n", " for u in units.itertuples():\n", " d = np.arange(u.life + 1)\n", " onset = 0.45 * u.life\n", " deg = np.clip((d - onset) / (u.life - onset), 0, 1) ** 1.5\n", " load = np.clip(u.base_load + rng.normal(0, 0.06, len(d)), 0.2, 1.0)\n", " hi = rng.random(len(d)) < 0.07 # ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ(์ •์ƒ์ด์ง€๋งŒ ์ง„๋™ยท์˜จ๋„ ์ƒ์Šน)\n", " load = np.where(hi, rng.uniform(0.95, 1.1, len(d)), load)\n", " rows.append(pd.DataFrame({\"unit\": u.unit, \"day\": d, \"cal\": u.start + d, \"rul\": u.life - d, \"deg\": deg, \"load\": load, \"hi_load\": hi, \"mode\": u.mode}))\n", " snap = pd.concat(rows, ignore_index=True)\n", " S = len(snap)\n", " snap[\"ambient\"] = 15 + 12 * np.sin(2 * np.pi * snap[\"cal\"] / 365) + rng.normal(0, 2, S)\n", " dg, ld, md = snap[\"deg\"].values, snap[\"load\"].values, snap[\"mode\"].values\n", " isb, isg, iso, isc = (md == \"bearing\"), (md == \"gear\"), (md == \"oil_pump\"), (md == \"cooling\")\n", " snap[\"temp\"] = 55 + 22 * ld + 0.5 * snap[\"ambient\"] + 9 * dg * iso + 26 * dg * isc + 3 * dg * (isb | isg) + rng.normal(0, 1.5, S)\n", " snap[\"press\"] = 450 - 12 * ld - 85 * dg * iso - 15 * dg * isc + rng.normal(0, 6, S)\n", " ph = rng.uniform(0, 2 * np.pi, (S, 3))\n", " a1, a2 = 1.0 * (0.5 + ld), 0.4 * (0.5 + ld)\n", " W = (a1[:, None] * np.sin(2 * np.pi * 50 * TT + ph[:, :1]) + a2[:, None] * np.sin(2 * np.pi * 100 * TT + ph[:, 1:2])\n", " + rng.normal(0, 0.35, (S, NS)))\n", " W += (0.9 * dg * isb)[:, None] * np.sin(2 * np.pi * 165 * TT + ph[:, 2:3])\n", " W += (0.8 * dg * isg)[:, None] * (np.sin(2 * np.pi * 150 * TT + ph[:, 2:3]) + 0.8 * np.sin(2 * np.pi * 100 * TT))\n", " W += (0.5 * dg * iso)[:, None] * np.sin(2 * np.pi * 200 * TT + ph[:, 2:3])\n", " k = rng.poisson(10 * dg * isb) # ๋ฒ ์–ด๋ง ์—ดํ™”: ์ถฉ๊ฒฉ์„ฑ ํŽ„์Šค(์ฒจ๋„ ์ƒ์Šน)\n", " pos, amp = rng.integers(0, NS, (S, 30)), rng.normal(0, 3.0, (S, 30)) * dg[:, None]\n", " amp = amp * (np.arange(30)[None, :] < k[:, None])\n", " np.add.at(W, (np.repeat(np.arange(S), 30), pos.ravel()), amp.ravel())\n", " return units, snap, W\n", "\n", "\n", "def extract(W):\n", " \"\"\"์ง„๋™ ํŒŒํ˜• -> ํŠน์ง•๋Ÿ‰: RMS, ์ฒจ๋„, ํŒŒ๊ณ ์œจ, ์ตœ๋Œ€-์ตœ์†Œ, FFT ๋Œ€์—ญ ์—๋„ˆ์ง€(50/100/150/165/200Hz ๋ถ€๊ทผ)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ํ—ฌ๊ธฐ/๊ธฐ๊ฐ‘ ์—”์ง„ ๋ฐ ๊ตฌ๋™๊ณ„ ์ง„๋™, ์˜จ๋„, ์••๋ ฅ ์„ผ์„œ ์‹œ๊ณ„์—ด ๋ฐ์ดํ„ฐ์…‹ ์ •์˜ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "PROCESS = [(\"์„ผ์„œ ์ˆ˜์ง‘\", \"๊ฐœ์ฒด ID, ์ธก์ •์ผ, ์ง„๋™ ํŒŒํ˜•, ์˜จ๋„, ์••๋ ฅ, ๋ถ€ํ•˜\"), (\"์ „์ฒ˜๋ฆฌ\", \"์žก์Œ ์ œ๊ฑฐ, ๋‹จ์œ„ยทํ‘œ๊ธฐ ํ†ต์ผ, ๊ฒฐ์ธก ๋ณด์ •\"),\n", " (\"ํŠน์ง• ์ถ”์ถœ\", \"RMS, ์ฒจ๋„, ํŒŒ๊ณ ์œจ, FFT ๋Œ€์—ญ ์—๋„ˆ์ง€\"), (\"์ด์ƒ ๊ฐ์ง€\", \"์ด์ƒ ์ ์ˆ˜, ๊ฒฝ๋ณด ๋‹จ๊ณ„\"),\n", " (\"์ž”์กด์ˆ˜๋ช…(RUL) ์˜ˆ์ธก\", \"๊ณ ์žฅ ์ž„๋ฐ• ์‹œ์ , ์‹ ๋ขฐ ๊ตฌ๊ฐ„\"), (\"๊ฒ€์‚ฌ๊ด€ ์Šน์ธ\", \"์กฐ๊ธฐ ์ž…๊ณ  ์ œ์•ˆ, ์Šน์ธยท๋ฐ˜๋ ค ์‚ฌ์œ \"),\n", " (\"์ •๋น„ยท๋ถ€ํ’ˆ ์†Œ๋ชจ\", \"๊ต์ฒด ๋ถ€ํ’ˆ, ์ˆ˜๋Ÿ‰, ์ •๋น„ ์†Œ์š”์‹œ๊ฐ„\"), (\"๊ณ ์žฅ ์‹ค์  ์—ฐ๊ฒฐ\", \"์ •๋น„ ๊ฑด ID๋กœ ์„ผ์„œ ์ด๋ ฅ๊ณผ ๊ณ ์žฅ ์‹ค์  ์—ฐ๊ฒฐ\"),\n", " (\"๋ถ€ํ’ˆ ์†Œ์š”ยท๊ฐ€๋™๋ฅ  ์ง‘๊ณ„\", \"ํ’ˆ๋ชฉ๋ณ„ ์†Œ๋ชจ, ๊ฐ€์šฉ๋„(PBL ์ง€ํ‘œ)\"), (\"์‚ฌํ›„๊ฒ€ํ† (AAR)\", \"์˜ค๊ฒฝ๋ณด ์›์ธ, ์ž„๊ณ„ ํŒŒ๋ผ๋ฏธํ„ฐ ์กฐ์ •\")]\n", "\n", "# 2) ์Šคํ‚ค๋งˆ(๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ): ๊ทธ๋ฃน / ํ•„๋“œ / ์ž๋ฃŒํ˜• / ๊ทœ์น™ / ์„ค๋ช…\n", "S = {\n", " \"sensor\": {\n", " \"unit_id\": (str, {\"pattern\": r\"^U\\d{2}$\"}, \"๊ฐœ์ฒด ID(๊ฐ€์ƒ ๊ธฐ๋™์ฒด)\"),\n", " \"day\": (int, {\"min\": 0, \"max\": 260}, \"์šด์šฉ ๊ฒฝ๊ณผ์ผ(์ธก์ •์ผ)\"),\n", " \"vib_rms\": (float, {\"min\": 0, \"max\": 10}, \"์ง„๋™ RMS(๊ฐ€์ƒ ๋‹จ์œ„)\"),\n", " \"vib_kurt\": (float, {\"min\": 1, \"max\": 30}, \"์ง„๋™ ์ฒจ๋„(์ถฉ๊ฒฉ์„ฑ ์ง€ํ‘œ)\"),\n", " \"temp\": (float, {\"min\": -30, \"max\": 150}, \"์—”์ง„ ์˜จ๋„(์„ญ์”จ)\"),\n", " \"press\": (float, {\"min\": 0, \"max\": 700}, \"์˜ค์ผ ์••๋ ฅ(kPa)\"),\n", " \"load\": (float, {\"min\": 0, \"max\": 1.2}, \"์šด์šฉ ๋ถ€ํ•˜์œจ\"),\n", " },\n", " \"feature\": {\n", " \"band_50\": (float, {\"min\": 0}, \"50Hz ๋ถ€๊ทผ FFT ๋Œ€์—ญ ์—๋„ˆ์ง€\"),\n", " \"band_165\": (float, {\"min\": 0}, \"165Hz ๋ถ€๊ทผ(๋ฒ ์–ด๋ง ๊ฒฐํ•จ ๋Œ€์—ญ) ์—๋„ˆ์ง€\"),\n", " \"crest\": (float, {\"min\": 1, \"max\": 30}, \"ํŒŒ๊ณ ์œจ(์ตœ๋Œ€๊ฐ’/RMS)\"),\n", " \"hf_ratio\": (float, {\"min\": 0}, \"๊ณ ์ฃผํŒŒ/๊ธฐ๋ณธ์ฃผํŒŒ ์—๋„ˆ์ง€ ๋น„\"),\n", " \"temp_res\": (float, {\"min\": -60, \"max\": 80}, \"๋ถ€ํ•˜ยท์™ธ๊ธฐ ๋Œ€๋น„ ์˜จ๋„ ์ž”์ฐจ\"),\n", " },\n", " \"failure\": {\n", " \"event_id\": (str, {\"pattern\": r\"^F\\d{3}$\"}, \"๊ณ ์žฅยท์ •๋น„ ๊ฑด ID(์„ผ์„œ ์ด๋ ฅ๊ณผ ์—ฐ๊ฒฐํ•˜๋Š” ํ‚ค)\"),\n", " \"mode\": (str, {\"enum\": list(MODES)}, \"์—ดํ™”(๊ณ ์žฅ) ์œ ํ˜•\"),\n", " \"life\": (int, {\"min\": 60, \"max\": 260}, \"๊ณ ์žฅ๊นŒ์ง€ ์šด์šฉ์ผ์ˆ˜\"),\n", " \"unplanned\": (bool, {}, \"๋น„๊ณ„ํš ์ •๋น„ ์—ฌ๋ถ€\"),\n", " },\n", " \"parts\": {\n", " \"part_name\": (str, {\"enum\": [v[2] for v in MODES.values()]}, \"์†Œ๋ชจ ์ˆ˜๋ฆฌ๋ถ€์†\"),\n", " \"qty\": (int, {\"min\": 1, \"max\": 10}, \"์†Œ๋ชจ ์ˆ˜๋Ÿ‰\"),\n", " \"unit_cost\": (float, {\"min\": 0, \"max\": 50}, \"๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜\"),\n", " },\n", " \"target\": {\n", " \"rul\": (float, {\"min\": 0, \"max\": 260}, \"์ž”์กด์ˆ˜๋ช…(์ผ) - ์˜ˆ์ธก๋Œ€์ƒ 1\"),\n", " \"anomaly_score\": (float, {\"min\": 0}, \"์ด์ƒ ์ ์ˆ˜(์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ) - ์˜ˆ์ธก๋Œ€์ƒ 2\"),\n", " \"availability\": (float, {\"min\": 0, \"max\": 1}, \"๊ฐ€์šฉ๋„(๊ฐ€๋™์ผ/์ „์ฒด์ผ) - PBL ์„ฑ๊ณผ ์ง€ํ‘œ\"),\n", " },\n", "}\n", "\n", "\n", "def check(name, v, typ, rule):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ํ—ฌ๊ธฐ/๊ธฐ๊ฐ‘ ์—”์ง„ ๋ฐ ๊ตฌ๋™๊ณ„ ์ง„๋™, ์˜จ๋„, ์••๋ ฅ ์„ผ์„œ ์‹œ๊ณ„์—ด ๋ฐ์ดํ„ฐ์…‹ ์ •์˜ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def validate(rec):\n", " errs = []\n", " for g, fields in S.items():\n", " for k, (typ, rule, _) in fields.items():\n", " if k not in rec.get(g, {}):\n", " errs.append(f\"{g}.{k}: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\")\n", " else:\n", " errs += check(f\"{g}.{k}\", rec[g][k], typ, rule)\n", " return errs\n", "\n", "\n", "# 3) [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์ƒ์„ฑ ์ž๋ฃŒ์—์„œ 20๊ฑด์„ ๋ ˆ์ฝ”๋“œ๋กœ ๋งŒ๋“ค์–ด ๊ฒ€์ฆ\n", "units, snap, W = make_runs()\n", "F = extract(W)\n", "snap = pd.concat([snap, F], axis=1)\n", "snap[\"temp_res\"] = snap[\"temp\"] - (55 + 22 * snap[\"load\"] + 0.5 * snap[\"ambient\"])\n", "snap[\"hf_ratio\"] = (snap[\"b150\"] + snap[\"b165\"] + snap[\"b200\"]) / (snap[\"b50\"] + 1e-6)\n", "ui = units.set_index(\"unit\")\n", "recs = []\n", "for i, r in enumerate(snap.sample(20, random_state=0).itertuples()):\n", " m = r.mode\n", " recs.append({\n", " \"sensor\": {\"unit_id\": r.unit, \"day\": int(r.day), \"vib_rms\": float(round(r.rms, 3)), \"vib_kurt\": float(round(r.kurt, 2)),\n", " \"temp\": float(round(r.temp, 1)), \"press\": float(round(r.press, 1)), \"load\": float(round(r.load, 2))},\n", " \"feature\": {\"band_50\": float(round(r.b50, 4)), \"band_165\": float(round(r.b165, 4)), \"crest\": float(round(r.crest, 2)),\n", " \"hf_ratio\": float(round(r.hf_ratio, 3)), \"temp_res\": float(round(r.temp_res, 2))},\n", " \"failure\": {\"event_id\": f\"F{int(r.unit[1:]):03d}\", \"mode\": m, \"life\": int(ui.loc[r.unit, \"life\"]), \"unplanned\": bool(r.rul < 5)},\n", " \"parts\": {\"part_name\": MODES[m][2], \"qty\": 1, \"unit_cost\": float(MODES[m][3])},\n", " \"target\": {\"rul\": float(r.rul), \"anomaly_score\": float(round(r.deg, 3)), \"availability\": 0.92}})\n", "ok = sum(not validate(r) for r in recs)\n", "print(f\"๊ฐ€์ƒ ์„ผ์„œ ์ž๋ฃŒ {len(snap)}๊ฑด(๊ฐœ์ฒด {len(units)}๋Œ€), ํ‘œ๋ณธ {len(recs)}๊ฑด ์ค‘ ๊ฒ€์ฆ ํ†ต๊ณผ {ok}๊ฑด, ์˜ค๋ฅ˜ {len(recs) - ok}๊ฑด\")\n", "print(f\"์ „์ฒด ํŒŒํ˜• {W.shape[0]}๊ฐœ x {W.shape[1]}ํ‘œ๋ณธ, ์˜จ๋„ยท์••๋ ฅ ๊ฒฐ์ธก {int(snap[['temp', 'press']].isna().sum().sum())}๊ฑด\")\n", "\n", "# 4) ์˜ค๋ฅ˜ ์ฃผ์ž… ์‹œํ—˜\n", "bad = json.loads(json.dumps(recs[0]))\n", "bad[\"sensor\"][\"unit_id\"] = \"ENGINE-3\" # ID ํ˜•์‹ ์˜ค๋ฅ˜\n", "bad[\"sensor\"][\"temp\"] = 480.0 # ์„ผ์„œ ๋ฒ”์œ„ ์ดˆ๊ณผ(๋‹จ์œ„ ํ˜ผ๋™ ์˜์‹ฌ)\n", "bad[\"failure\"][\"mode\"] = \"turbine\" # ์ •์˜๋˜์ง€ ์•Š์€ ์—ดํ™” ์œ ํ˜•\n", "del bad[\"parts\"][\"qty\"] # ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\n", "bad[\"target\"][\"rul\"] = \"30์ผ\" # ์ž๋ฃŒํ˜• ์˜ค๋ฅ˜\n", "print(\"\\n[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ]\")\n", "for e in validate(bad):\n", " print(\" -\", e)\n", "\n", "# 5) ํ”„๋กœ์„ธ์Šค ๋งต๊ณผ ๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ\n", "print(\"\\n[์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต]\")\n", "print(\" -> \".join(f\"{i}.{p}\" for i, (p, _) in enumerate(PROCESS, 1)))\n", "for p, d in PROCESS:\n", " print(f\" {p:12s}: {d}\")\n", "rows = [(g, k, t.__name__, d) for g, fl in S.items() for k, (t, r, d) in fl.items()]\n", "dd = pd.DataFrame(rows, columns=[\"๊ทธ๋ฃน\", \"ํ•„๋“œ\", \"์ž๋ฃŒํ˜•\", \"์„ค๋ช…\"])\n", "print(f\"\\n[๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ] ํ”ผ์ฒ˜ {len(dd)}๊ฐœ (๋ชฉํ‘œ 20๊ฐœ ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(dd) >= 20 else '๋ฏธ๋‹ฌ'})\")\n", "print(dd.to_string(index=False))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 01 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "๊ฐ€์ƒ ์„ผ์„œ ์ž๋ฃŒ 7733๊ฑด(๊ฐœ์ฒด 48๋Œ€), ํ‘œ๋ณธ 20๊ฑด ์ค‘ ๊ฒ€์ฆ ํ†ต๊ณผ 20๊ฑด, ์˜ค๋ฅ˜ 0๊ฑด\n", "์ „์ฒด ํŒŒํ˜• 7733๊ฐœ x 256ํ‘œ๋ณธ, ์˜จ๋„ยท์••๋ ฅ ๊ฒฐ์ธก 0๊ฑด\n", "\n", "[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ]\n", " - sensor.unit_id: ํ˜•์‹ ๋ถˆ์ผ์น˜ 'ENGINE-3'\n", " - sensor.temp: ์ตœ๋Œ€๊ฐ’ 150 ์ดˆ๊ณผ\n", " - failure.mode: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๊ฐ’ 'turbine'\n", " - parts.qty: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\n", " - target.rul: ์ž๋ฃŒํ˜• ์˜ค๋ฅ˜(str)\n", "\n", "[์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต]\n", "1.์„ผ์„œ ์ˆ˜์ง‘ -> 2.์ „์ฒ˜๋ฆฌ -> 3.ํŠน์ง• ์ถ”์ถœ -> 4.์ด์ƒ ๊ฐ์ง€ -> 5.์ž”์กด์ˆ˜๋ช…(RUL) ์˜ˆ์ธก -> 6.๊ฒ€์‚ฌ๊ด€ ์Šน์ธ -> 7.์ •๋น„ยท๋ถ€ํ’ˆ ์†Œ๋ชจ -> 8.๊ณ ์žฅ ์‹ค์  ์—ฐ๊ฒฐ -> 9.๋ถ€ํ’ˆ ์†Œ์š”ยท๊ฐ€๋™๋ฅ  ์ง‘๊ณ„ -> 10.์‚ฌํ›„๊ฒ€ํ† (AAR)\n", " ์„ผ์„œ ์ˆ˜์ง‘ : ๊ฐœ์ฒด ID, ์ธก์ •์ผ, ์ง„๋™ ํŒŒํ˜•, ์˜จ๋„, ์••๋ ฅ, ๋ถ€ํ•˜\n", " ์ „์ฒ˜๋ฆฌ : ์žก์Œ ์ œ๊ฑฐ, ๋‹จ์œ„ยทํ‘œ๊ธฐ ํ†ต์ผ, ๊ฒฐ์ธก ๋ณด์ •\n", " ํŠน์ง• ์ถ”์ถœ : RMS, ์ฒจ๋„, ํŒŒ๊ณ ์œจ, FFT ๋Œ€์—ญ ์—๋„ˆ์ง€\n", " ์ด์ƒ ๊ฐ์ง€ : ์ด์ƒ ์ ์ˆ˜, ๊ฒฝ๋ณด ๋‹จ๊ณ„\n", " ์ž”์กด์ˆ˜๋ช…(RUL) ์˜ˆ์ธก: ๊ณ ์žฅ ์ž„๋ฐ• ์‹œ์ , ์‹ ๋ขฐ ๊ตฌ๊ฐ„\n", " ๊ฒ€์‚ฌ๊ด€ ์Šน์ธ : ์กฐ๊ธฐ ์ž…๊ณ  ์ œ์•ˆ, ์Šน์ธยท๋ฐ˜๋ ค ์‚ฌ์œ \n", " ์ •๋น„ยท๋ถ€ํ’ˆ ์†Œ๋ชจ : ๊ต์ฒด ๋ถ€ํ’ˆ, ์ˆ˜๋Ÿ‰, ์ •๋น„ ์†Œ์š”์‹œ๊ฐ„\n", " ๊ณ ์žฅ ์‹ค์  ์—ฐ๊ฒฐ : ์ •๋น„ ๊ฑด ID๋กœ ์„ผ์„œ ์ด๋ ฅ๊ณผ ๊ณ ์žฅ ์‹ค์  ์—ฐ๊ฒฐ\n", " ๋ถ€ํ’ˆ ์†Œ์š”ยท๊ฐ€๋™๋ฅ  ์ง‘๊ณ„: ํ’ˆ๋ชฉ๋ณ„ ์†Œ๋ชจ, ๊ฐ€์šฉ๋„(PBL ์ง€ํ‘œ)\n", " ์‚ฌํ›„๊ฒ€ํ† (AAR) : ์˜ค๊ฒฝ๋ณด ์›์ธ, ์ž„๊ณ„ ํŒŒ๋ผ๋ฏธํ„ฐ ์กฐ์ •\n", "\n", "[๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ] ํ”ผ์ฒ˜ 22๊ฐœ (๋ชฉํ‘œ 20๊ฐœ ์ด์ƒ: ๋‹ฌ์„ฑ)\n", " ๊ทธ๋ฃน ํ•„๋“œ ์ž๋ฃŒํ˜• ์„ค๋ช…\n", " sensor unit_id str ๊ฐœ์ฒด ID(๊ฐ€์ƒ ๊ธฐ๋™์ฒด)\n", " sensor day int ์šด์šฉ ๊ฒฝ๊ณผ์ผ(์ธก์ •์ผ)\n", " sensor vib_rms float ์ง„๋™ RMS(๊ฐ€์ƒ ๋‹จ์œ„)\n", " sensor vib_kurt float ์ง„๋™ ์ฒจ๋„(์ถฉ๊ฒฉ์„ฑ ์ง€ํ‘œ)\n", " sensor temp float ์—”์ง„ ์˜จ๋„(์„ญ์”จ)\n", " sensor press float ์˜ค์ผ ์••๋ ฅ(kPa)\n", " sensor load float ์šด์šฉ ๋ถ€ํ•˜์œจ\n", "feature band_50 float 50Hz ๋ถ€๊ทผ FFT ๋Œ€์—ญ ์—๋„ˆ์ง€\n", "feature band_165 float 165Hz ๋ถ€๊ทผ(๋ฒ ์–ด๋ง ๊ฒฐํ•จ ๋Œ€์—ญ) ์—๋„ˆ์ง€\n", "feature crest float ํŒŒ๊ณ ์œจ(์ตœ๋Œ€๊ฐ’/RMS)\n", "feature hf_ratio float ๊ณ ์ฃผํŒŒ/๊ธฐ๋ณธ์ฃผํŒŒ ์—๋„ˆ์ง€ ๋น„\n", "feature temp_res float ๋ถ€ํ•˜ยท์™ธ๊ธฐ ๋Œ€๋น„ ์˜จ๋„ ์ž”์ฐจ\n", "failure event_id str ๊ณ ์žฅยท์ •๋น„ ๊ฑด ID(์„ผ์„œ ์ด๋ ฅ๊ณผ ์—ฐ๊ฒฐํ•˜๋Š” ํ‚ค)\n", "failure mode str ์—ดํ™”(๊ณ ์žฅ) ์œ ํ˜•\n", "failure life int ๊ณ ์žฅ๊นŒ์ง€ ์šด์šฉ์ผ์ˆ˜\n", "... (์ดํ•˜ ์ƒ๋žต)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 02. ๊ณ ์ฃผํŒŒ ์„ผ์„œ ์‹ ํ˜ธ ์žก์Œ ์ œ๊ฑฐ, FFT ๋ณ€ํ™˜ ๋ฐ ์ฃผ์š” ํŠน์ง•๋Ÿ‰(RMS, Kurtosis) ์ „์ฒ˜๋ฆฌ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์„ผ์„œ ์žก์Œ ์ œ๊ฑฐ, FFT ๋ณ€ํ™˜ ๋ฐ ์ฃผ์š” ํŠน์ง•๋Ÿ‰(RMS ๋“ฑ) ์ถ”์ถœ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๊ณ ์ฃผํŒŒ ์„ผ์„œ ์‹ ํ˜ธ์˜ ์žก์Œ์„ ์ œ๊ฑฐํ•˜๊ณ  FFT ๋ณ€ํ™˜์œผ๋กœ RMS, ์ฒจ๋„ ๋“ฑ ์ฃผ์š” ํŠน์ง•๋Ÿ‰์„ ์ถ”์ถœํ•˜๋Š” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **ํ‘œ๊ธฐยท๋‹จ์œ„(์˜จ๋„ F/C, ์••๋ ฅ psi/kPa) ํ†ต์ผ, ์ค‘๋ณต ์ œ๊ฑฐ, ๋ถˆ๊ฐ€๋Šฅ๊ฐ’ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ, ํŒŒํ˜• ๋‹จ๋ฐœ ์ŠคํŒŒ์ดํฌ์˜ ์ค‘์•™๊ฐ’ ํ•„ํ„ฐ ์ œ๊ฑฐ, ๊ฐ„์„ญ ์žก์Œ ์ฐฝ์˜ ์ €์—ญํ†ต๊ณผ ํ•„ํ„ฐ, KNN ๊ฒฐ์ธก ๋ณด์ • ๋“ฑ ์ •์ œ ๊ทœ์น™ 10์ข…์„ ์ ์šฉ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/etl/mnt04_data_cleansing.py ๋ฐ ์ •์ œ ํ’ˆ์งˆ ๋ณด๊ณ ์„œ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ํŠน์ˆ˜ ์˜ˆ์™ธ(๋น„๊ณ„ํš ์ •๋น„, ๊ธด๊ธ‰ ๋ถˆ์ถœ ๋“ฑ) ๋ฐ์ดํ„ฐ ๋ณด์กด์˜ ํƒ€๋‹น์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๋ฐ์ดํ„ฐ ์ •์ œ ๊ทœ์น™ 10์ข… ์ ์šฉ, ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ ํ†ต๊ณผ์œจ 100%\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ETL ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌํ˜„ ๋ฐ ์ •์ œ๋œ ๋งˆ์Šคํ„ฐ ๋ฐ์ดํ„ฐ์…‹ ๊ตฌ์ถ•\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ์ „ํ›„ ๋ฐ์ดํ„ฐ ํ†ต๊ณ„๋Ÿ‰(๊ธฐ์ˆ ํ†ต๊ณ„) ๋น„๊ต ๋ฐ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"ํ‘œ๊ธฐ ๋ถˆ์ผ์น˜ยท์ค‘๋ณตยท๋‹จ์œ„ ํ˜ผ์žฌยท๋ถˆ๊ฐ€๋Šฅ๊ฐ’ยท๊ฒฐ์ธกยท๋‹จ๋ฐœ ์ŠคํŒŒ์ดํฌ๊ฐ€ ์„ž์ธ ๊ฐ€์ƒ ์„ผ์„œ ์›์ฒœ ์ž๋ฃŒ์— ์ •์ œ ๊ทœ์น™ 10์ข…(ํŒŒํ˜• ์ŠคํŒŒ์ดํฌ ์ œ๊ฑฐ, ์ €์—ญํ†ต๊ณผ ์žก์Œ ์ œ๊ฑฐ, KNN ๊ฒฐ์ธก ๋ณด์ • ํฌํ•จ)์„ ์ ์šฉํ•˜๊ณ , FFT ๋ณ€ํ™˜์œผ๋กœ RMSยท์ฒจ๋„ ๋“ฑ ์ฃผ์š” ํŠน์ง•๋Ÿ‰์„ ์ถ”์ถœํ•œ๋‹ค. ๊ณ ์žฅ ์ž„๋ฐ• ์‹œ์ ์˜ ๊ธด๊ธ‰ ์ ๊ฒ€ ์ธก์ •์€ ๋ณด์กดํ•˜๋ฉฐ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ ํ†ต๊ณผ์œจ๊ณผ ์ •์ œ ์ „ํ›„ ํ†ต๊ณ„๋ฅผ ํ™•์ธํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.stats import kurtosis\n", "from scipy.signal import medfilt, butter, sosfiltfilt\n", "from sklearn.impute import KNNImputer\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๊ธฐ๋™์ฒด 48๋Œ€์˜ ์—”์ง„ยท๊ตฌ๋™๊ณ„ ์ง„๋™ยท์˜จ๋„ยท์••๋ ฅ ์„ผ์„œ ์ž๋ฃŒ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๊ฐœ์ฒด๋งˆ๋‹ค ์ •์ƒ -> ์—ดํ™” -> ๊ณ ์žฅ(์ˆ˜๋ช…์ข…๋ฃŒ)๊นŒ์ง€ ํ•˜๋ฃจ 1ํšŒ ์ธก์ •(์ง„๋™ ํŒŒํ˜• 256ํ‘œ๋ณธ + ์˜จ๋„ + ์••๋ ฅ + ๋ถ€ํ•˜)์„ ๋งŒ๋“ ๋‹ค.\n", "# ์—ดํ™” ์œ ํ˜• 4๊ฐ€์ง€: bearing(๋ฒ ์–ด๋ง), gear(๊ธฐ์–ด), oil_pump(์˜ค์ผ ํŽŒํ”„), cooling(๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ)\n", "# ---------------------------------------------------------------------------\n", "FS, NS = 1024, 256 # ๊ฐ€์ƒ ํ‘œ๋ณธํ™” ์ฃผํŒŒ์ˆ˜(Hz), ํŒŒํ˜• 1๊ฐœ๋‹น ํ‘œ๋ณธ ์ˆ˜\n", "TT = np.arange(NS) / FS\n", "MODES = { # ์—ดํ™” ์œ ํ˜•: (๋ฐœ์ƒ ํ™•๋ฅ , ํ‰๊ท  ์ˆ˜๋ช…(์ผ), ๊ต์ฒด ์ˆ˜๋ฆฌ๋ถ€์†, ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜)\n", " \"bearing\": (0.40, 160, \"์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ\", 6.0), \"gear\": (0.25, 175, \"๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ\", 9.0),\n", " \"oil_pump\": (0.20, 150, \"์˜ค์ผ ํŽŒํ”„\", 4.0), \"cooling\": (0.15, 140, \"๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ\", 3.0)}\n", "KIT_COST = 1.0 # ๋ชจ๋“  ์ •๋น„์— ํ•จ๊ป˜ ์†Œ๋ชจ๋˜๋Š” ์”ฐยทํ•„ํ„ฐ ํ‚คํŠธ ๋‹จ๊ฐ€์ง€์ˆ˜\n", "BANDS = {\"b50\": (40, 60), \"b100\": (90, 110), \"b150\": (140, 156), \"b165\": (156, 176), \"b200\": (190, 215)}\n", "\n", "\n", "def make_runs(n=48, seed=1):\n", " \"\"\"๋ฐ˜ํ™˜: units(๊ฐœ์ฒด ์ •๋ณด), snap(์ผ๋ณ„ ์ธก์ • ์š”์•ฝ), W(์ง„๋™ ํŒŒํ˜• [์ธก์ • ์ˆ˜, 256])\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(MODES)\n", " mode = rng.choice(names, n, p=[MODES[m][0] for m in names])\n", " life = np.clip(np.array([rng.normal(MODES[m][1], 18) for m in mode]), 90, 230).astype(int)\n", " start = np.sort(rng.integers(0, 240, n))\n", " units = pd.DataFrame({\"unit\": [f\"U{i:02d}\" for i in range(n)], \"mode\": mode, \"life\": life, \"start\": start, \"base_load\": rng.uniform(0.4, 0.75, n)})\n", " rows = []\n", " for u in units.itertuples():\n", " d = np.arange(u.life + 1)\n", " onset = 0.45 * u.life\n", " deg = np.clip((d - onset) / (u.life - onset), 0, 1) ** 1.5\n", " load = np.clip(u.base_load + rng.normal(0, 0.06, len(d)), 0.2, 1.0)\n", " hi = rng.random(len(d)) < 0.07 # ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ(์ •์ƒ์ด์ง€๋งŒ ์ง„๋™ยท์˜จ๋„ ์ƒ์Šน)\n", " load = np.where(hi, rng.uniform(0.95, 1.1, len(d)), load)\n", " rows.append(pd.DataFrame({\"unit\": u.unit, \"day\": d, \"cal\": u.start + d, \"rul\": u.life - d, \"deg\": deg, \"load\": load, \"hi_load\": hi, \"mode\": u.mode}))\n", " snap = pd.concat(rows, ignore_index=True)\n", " S = len(snap)\n", " snap[\"ambient\"] = 15 + 12 * np.sin(2 * np.pi * snap[\"cal\"] / 365) + rng.normal(0, 2, S)\n", " dg, ld, md = snap[\"deg\"].values, snap[\"load\"].values, snap[\"mode\"].values\n", " isb, isg, iso, isc = (md == \"bearing\"), (md == \"gear\"), (md == \"oil_pump\"), (md == \"cooling\")\n", " snap[\"temp\"] = 55 + 22 * ld + 0.5 * snap[\"ambient\"] + 9 * dg * iso + 26 * dg * isc + 3 * dg * (isb | isg) + rng.normal(0, 1.5, S)\n", " snap[\"press\"] = 450 - 12 * ld - 85 * dg * iso - 15 * dg * isc + rng.normal(0, 6, S)\n", " ph = rng.uniform(0, 2 * np.pi, (S, 3))\n", " a1, a2 = 1.0 * (0.5 + ld), 0.4 * (0.5 + ld)\n", " W = (a1[:, None] * np.sin(2 * np.pi * 50 * TT + ph[:, :1]) + a2[:, None] * np.sin(2 * np.pi * 100 * TT + ph[:, 1:2])\n", " + rng.normal(0, 0.35, (S, NS)))\n", " W += (0.9 * dg * isb)[:, None] * np.sin(2 * np.pi * 165 * TT + ph[:, 2:3])\n", " W += (0.8 * dg * isg)[:, None] * (np.sin(2 * np.pi * 150 * TT + ph[:, 2:3]) + 0.8 * np.sin(2 * np.pi * 100 * TT))\n", " W += (0.5 * dg * iso)[:, None] * np.sin(2 * np.pi * 200 * TT + ph[:, 2:3])\n", " k = rng.poisson(10 * dg * isb) # ๋ฒ ์–ด๋ง ์—ดํ™”: ์ถฉ๊ฒฉ์„ฑ ํŽ„์Šค(์ฒจ๋„ ์ƒ์Šน)\n", " pos, amp = rng.integers(0, NS, (S, 30)), rng.normal(0, 3.0, (S, 30)) * dg[:, None]\n", " amp = amp * (np.arange(30)[None, :] < k[:, None])\n", " np.add.at(W, (np.repeat(np.arange(S), 30), pos.ravel()), amp.ravel())\n", " return units, snap, W\n", "\n", "\n", "def extract(W):\n", " \"\"\"์ง„๋™ ํŒŒํ˜• -> ํŠน์ง•๋Ÿ‰: RMS, ์ฒจ๋„, ํŒŒ๊ณ ์œจ, ์ตœ๋Œ€-์ตœ์†Œ, FFT ๋Œ€์—ญ ์—๋„ˆ์ง€(50/100/150/165/200Hz ๋ถ€๊ทผ)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] ๊ณ ์ฃผํŒŒ ์„ผ์„œ ์‹ ํ˜ธ ์žก์Œ ์ œ๊ฑฐ, FFT ๋ณ€ํ™˜ ๋ฐ ์ฃผ์š” ํŠน์ง•๋Ÿ‰(RMS, Kurtosis) ์ „์ฒ˜๋ฆฌ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "units, snap, W0 = make_runs()\n", "rng = np.random.default_rng(2)\n", "S = len(snap)\n", "W = W0.copy()\n", "raw = pd.DataFrame({\"unit\": snap[\"unit\"].values, \"date\": [(pd.Timestamp(\"2026-01-01\") + pd.Timedelta(days=int(c))).strftime(\"%Y-%m-%d\") for c in snap[\"cal\"]],\n", " \"temp\": snap[\"temp\"].round(1).values, \"temp_unit\": \"C\", \"press\": snap[\"press\"].round(1).values, \"press_unit\": \"kPa\",\n", " \"load\": snap[\"load\"].round(2).values, \"emergency\": snap[\"rul\"].values <= 3})\n", "idx = rng.choice(S, 1500, replace=False)\n", "a, b, c, d_, e, f, g, h_ = np.split(idx, [150, 350, 600, 750, 800, 850, 1050])\n", "raw.loc[a, \"unit\"] = rng.choice([\"u\", \" U\", \"unit-\"], len(a)) + raw.loc[a, \"unit\"].str[1:] # ID ํ‘œ๊ธฐ ๋ถˆ์ผ์น˜\n", "raw.loc[b, \"date\"] = pd.to_datetime(raw.loc[b, \"date\"]).dt.strftime(\"%Y/%m/%d\") # ๋‚ ์งœ ํ˜•์‹ ํ˜ผ์žฌ\n", "raw.loc[c, \"temp\"] = (raw.loc[c, \"temp\"] * 9 / 5 + 32).round(1); raw.loc[c, \"temp_unit\"] = \"F\" # ์˜จ๋„ ๋‹จ์œ„ ํ˜ผ์žฌ\n", "raw.loc[d_, \"press\"] = (raw.loc[d_, \"press\"] / 6.895).round(1); raw.loc[d_, \"press_unit\"] = \"psi\" # ์••๋ ฅ ๋‹จ์œ„ ํ˜ผ์žฌ\n", "raw.loc[e, \"temp\"] = -999.0; raw.loc[f, \"press\"] = -5.0 # ๋ถˆ๊ฐ€๋Šฅ ๊ฐ’\n", "raw.loc[g, [\"temp\", \"press\"]] = np.nan # ๊ฒฐ์ธก\n", "sp = h_[:200] # ํŒŒํ˜• ๋‹จ๋ฐœ ์ŠคํŒŒ์ดํฌ(์„ผ์„œ ๊ธ€๋ฆฌ์น˜)\n", "for i in sp:\n", " W[i, rng.integers(0, NS, 2)] += rng.choice([-9.0, 9.0], 2)\n", "nz = h_[200:] # ์ „์ž๊ธฐ ๊ฐ„์„ญ: ํŒŒํ˜• ์ „์ฒด ์žก์Œ ์ฆ๊ฐ€\n", "W[nz] += rng.normal(0, 0.9, (len(nz), NS))\n", "raw = pd.concat([raw, raw.sample(80, random_state=1)], ignore_index=True) # ์ค‘๋ณต 80๊ฑด\n", "print(f\"์›์ฒœ ์ž๋ฃŒ {len(raw)}ํ–‰ (์ •์ƒ {S}ํ–‰ + ์ค‘๋ณต 80ํ–‰, ์˜ค๋ฅ˜ ์ฃผ์ž… {len(idx)}ํ–‰, ํŒŒํ˜• ์ŠคํŒŒ์ดํฌ {len(sp)}๊ฐœยท๊ฐ„์„ญ {len(nz)}๊ฐœ ํฌํ•จ)\")\n", "\n", "# 2) ์ •์ œ ๊ทœ์น™ 10์ข… (ํ‘œ ๊ทœ์น™ R1~R6, ๊ฒฐ์ธก R9, ์˜ˆ์™ธ R10 / ํŒŒํ˜• ๊ทœ์น™ R7~R8)\n", "df, applied = raw.iloc[:S].copy(), []\n", "def rule(name, n):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] ๊ณ ์ฃผํŒŒ ์„ผ์„œ ์‹ ํ˜ธ ์žก์Œ ์ œ๊ฑฐ, FFT ๋ณ€ํ™˜ ๋ฐ ์ฃผ์š” ํŠน์ง•๋Ÿ‰(RMS, Kurtosis) ์ „์ฒ˜๋ฆฌ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "rule(\"R1 ์ค‘๋ณต ํ–‰ ์ œ๊ฑฐ\", raw.duplicated().sum())\n", "uid = \"U\" + df[\"unit\"].str.extract(r\"(\\d+)\")[0]; rule(\"R2 ๊ฐœ์ฒด ID ํ‘œ๊ธฐ ํ†ต์ผ\", (uid != df[\"unit\"]).sum()); df[\"unit\"] = uid\n", "t1 = pd.to_datetime(df[\"date\"], format=\"%Y-%m-%d\", errors=\"coerce\"); t2 = pd.to_datetime(df[\"date\"], format=\"%Y/%m/%d\", errors=\"coerce\")\n", "rule(\"R3 ์ธก์ •์ผ ํ˜•์‹ ํ†ต์ผ\", t1.isna().sum()); df[\"date\"] = t1.fillna(t2)\n", "fm = df[\"temp_unit\"] == \"F\"; df.loc[fm, \"temp\"] = ((df.loc[fm, \"temp\"] - 32) * 5 / 9).round(1); df[\"temp_unit\"] = \"C\"; rule(\"R4 ์˜จ๋„ ๋‹จ์œ„ ํ†ต์ผ(F -> C)\", fm.sum())\n", "pm = df[\"press_unit\"] == \"psi\"; df.loc[pm, \"press\"] = (df.loc[pm, \"press\"] * 6.895).round(1); df[\"press_unit\"] = \"kPa\"; rule(\"R5 ์••๋ ฅ ๋‹จ์œ„ ํ†ต์ผ(psi -> kPa)\", pm.sum())\n", "bt, bp = ~df[\"temp\"].between(-30, 150) & df[\"temp\"].notna(), (df[\"press\"] < 0) & df[\"press\"].notna()\n", "df.loc[bt, \"temp\"] = np.nan; df.loc[bp, \"press\"] = np.nan; rule(\"R6 ๋ถˆ๊ฐ€๋Šฅ ๊ฐ’ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ\", bt.sum() + bp.sum())\n", "Wc = W.copy()\n", "glitch = np.abs(Wc - np.median(Wc, axis=1, keepdims=True)).max(axis=1) > 8 # ๋‹จ๋ฐœ ์ŠคํŒŒ์ดํฌ ์ฐฝ\n", "for i in np.where(glitch)[0]:\n", " Wc[i] = medfilt(Wc[i], 3)\n", "rule(\"R7 ํŒŒํ˜• ๋‹จ๋ฐœ ์ŠคํŒŒ์ดํฌ ์ œ๊ฑฐ(์ค‘์•™๊ฐ’ ํ•„ํ„ฐ)\", glitch.sum())\n", "sos = butter(4, 250, fs=FS, output=\"sos\")\n", "hf = (Wc - sosfiltfilt(sos, Wc, axis=1)).std(axis=1) # 250Hz ์ด์ƒ ์„ฑ๋ถ„์˜ ํฌ๊ธฐ\n", "noisy = hf > 0.4 # ๊ฐ„์„ญ ์žก์Œ ์ฐฝ\n", "Wc[noisy] = sosfiltfilt(sos, Wc[noisy], axis=1); rule(\"R8 ๊ฐ„์„ญ ์žก์Œ ์ฐฝ ์ €์—ญํ†ต๊ณผ(250Hz) ํ•„ํ„ฐ\", noisy.sum())\n", "miss = df[[\"temp\", \"press\"]].isna().sum().sum()\n", "X = df.assign(un=df[\"unit\"].str[1:].astype(int) / 50)[[\"temp\", \"press\", \"load\", \"un\"]]\n", "df[[\"temp\", \"press\"]] = KNNImputer(n_neighbors=5).fit_transform(X)[:, :2]; rule(\"R9 ์„ผ์„œ ๊ฒฐ์ธก KNN ๋ณด์ •\", miss)\n", "df[\"rms\"] = np.sqrt(((Wc - Wc.mean(axis=1, keepdims=True)) ** 2).mean(axis=1)); lim = df[\"rms\"].quantile(0.99)\n", "df[\"outlier_flag\"] = (df[\"rms\"] > lim) & ~df[\"emergency\"]; rule(\"R10 ๊ธด๊ธ‰ ์ ๊ฒ€ ์ธก์ • ๋ณด์กด(์ผ๋ฐ˜ ์ด์ƒ๊ฐ’์€ ํ‘œ์‹œ๋งŒ)\", (df[\"rms\"] > lim).sum() - df[\"outlier_flag\"].sum())\n", "print(\"\\n[์ ์šฉ ๊ทœ์น™๊ณผ ์ฒ˜๋ฆฌ ๊ฑด์ˆ˜]\")\n", "for nme, n in applied:\n", " print(f\" {nme:36s} {n:5d}๊ฑด\")\n", "\n", "# 3) ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ\n", "checks = {\"์ค‘๋ณต 0๊ฑด\": not df.duplicated([\"unit\", \"date\"]).any(), \"ID ํ˜•์‹\": df[\"unit\"].str.match(r\"^U\\d{2}$\").all(),\n", " \"๋‚ ์งœ ๋ณ€ํ™˜\": df[\"date\"].notna().all(), \"์˜จ๋„ ๋‹จ์œ„ C\": (df[\"temp_unit\"] == \"C\").all(), \"์••๋ ฅ ๋‹จ์œ„ kPa\": (df[\"press_unit\"] == \"kPa\").all(),\n", " \"์˜จ๋„ ๋ฒ”์œ„\": df[\"temp\"].between(-30, 150).all(), \"์••๋ ฅ 0 ์ด์ƒ\": (df[\"press\"] >= 0).all(), \"๊ฒฐ์ธก 0๊ฑด\": df[[\"temp\", \"press\"]].notna().all().all(),\n", " \"ํŒŒํ˜• ์ŠคํŒŒ์ดํฌ ์ž”์กด 0๊ฐœ\": not (np.abs(Wc - np.median(Wc, axis=1, keepdims=True)).max(axis=1) > 8).any(),\n", " f\"๊ธด๊ธ‰ ์ ๊ฒ€ {int(df['emergency'].sum())}๊ฑด ๋ณด์กด\": int(df[\"emergency\"].sum()) == int(snap[\"rul\"].le(3).sum())}\n", "rate = np.mean(list(checks.values()))\n", "print(f\"\\n์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ {sum(checks.values())}/{len(checks)} ํ†ต๊ณผ, ํ†ต๊ณผ์œจ {rate:.0%} (๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if rate == 1 else '๋ฏธ๋‹ฌ'})\")\n", "for k, v in checks.items():\n", " if not v:\n", " print(\" ์‹คํŒจ:\", k)\n", "\n", "# 4) FFT ํŠน์ง•๋Ÿ‰: ์ •์ œ ํšจ๊ณผ(๊นจ๋—ํ•œ ์›๋ณธ ํŒŒํ˜• ๋Œ€๋น„ ์˜ค์ฐจ)\n", "def feats(Wx):\n", " x = Wx - Wx.mean(axis=1, keepdims=True)\n", " return np.sqrt((x ** 2).mean(axis=1)), kurtosis(x, axis=1, fisher=False)\n", "r0, k0 = feats(W0); r1, k1 = feats(W); r2, k2 = feats(Wc)\n", "bad = glitch | noisy\n", "print(\"\\n[ํŠน์ง•๋Ÿ‰ ์ •์ œ ํšจ๊ณผ] ์ŠคํŒŒ์ดํฌยท๊ฐ„์„ญ์ด ์žˆ๋˜ ์ฐฝ \" + f\"{int(bad.sum())}๊ฐœ์˜ ๊นจ๋—ํ•œ ์›๋ณธ ๋Œ€๋น„ ํ‰๊ท  ์˜ค์ฐจ\")\n", "print(f\" RMS ์ •์ œ ์ „ {np.abs(r1 - r0)[bad].mean():.3f} -> ์ •์ œ ํ›„ {np.abs(r2 - r0)[bad].mean():.3f}\")\n", "print(f\" ์ฒจ๋„ ์ •์ œ ์ „ {np.abs(k1 - k0)[bad].mean():.2f} -> ์ •์ œ ํ›„ {np.abs(k2 - k0)[bad].mean():.2f}\")\n", "cmp = pd.DataFrame({\"์ •์ œ ์ „\": [len(raw), pd.to_numeric(raw[\"temp\"], errors=\"coerce\").mean(), raw[\"temp\"].min(), raw[\"press\"].min(), raw[\"press\"].isna().sum()],\n", " \"์ •์ œ ํ›„\": [len(df), df[\"temp\"].mean(), df[\"temp\"].min(), df[\"press\"].min(), df[\"press\"].isna().sum()]},\n", " index=[\"ํ–‰ ์ˆ˜\", \"์˜จ๋„ ํ‰๊ท \", \"์˜จ๋„ ์ตœ์†Œ\", \"์••๋ ฅ ์ตœ์†Œ\", \"์••๋ ฅ ๊ฒฐ์ธก\"]).round(1)\n", "print(\"\\n[์ •์ œ ์ „ํ›„ ๊ธฐ์ˆ ํ†ต๊ณ„]\"); print(cmp.to_string())\n", "\n", "i = int(np.where(glitch)[0][0])\n", "fig, ax = plt.subplots(1, 2, figsize=(10, 3.5))\n", "ax[0].plot(W[i], label=\"raw\", alpha=0.6); ax[0].plot(Wc[i], label=\"cleaned\"); ax[0].set_title(\"Waveform with glitch\"); ax[0].legend()\n", "f = np.fft.rfftfreq(NS, 1 / FS); ax[1].plot(f, np.abs(np.fft.rfft(Wc[i])) / NS); ax[1].set_title(\"FFT spectrum (cleaned)\"); ax[1].set_xlabel(\"Hz\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 02 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "์›์ฒœ ์ž๋ฃŒ 7813ํ–‰ (์ •์ƒ 7733ํ–‰ + ์ค‘๋ณต 80ํ–‰, ์˜ค๋ฅ˜ ์ฃผ์ž… 1500ํ–‰, ํŒŒํ˜• ์ŠคํŒŒ์ดํฌ 200๊ฐœยท๊ฐ„์„ญ 250๊ฐœ ํฌํ•จ)\n", "\n", "[์ ์šฉ ๊ทœ์น™๊ณผ ์ฒ˜๋ฆฌ ๊ฑด์ˆ˜]\n", " R1 ์ค‘๋ณต ํ–‰ ์ œ๊ฑฐ 80๊ฑด\n", " R2 ๊ฐœ์ฒด ID ํ‘œ๊ธฐ ํ†ต์ผ 150๊ฑด\n", " R3 ์ธก์ •์ผ ํ˜•์‹ ํ†ต์ผ 200๊ฑด\n", " R4 ์˜จ๋„ ๋‹จ์œ„ ํ†ต์ผ(F -> C) 250๊ฑด\n", " R5 ์••๋ ฅ ๋‹จ์œ„ ํ†ต์ผ(psi -> kPa) 150๊ฑด\n", " R6 ๋ถˆ๊ฐ€๋Šฅ ๊ฐ’ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ 100๊ฑด\n", " R7 ํŒŒํ˜• ๋‹จ๋ฐœ ์ŠคํŒŒ์ดํฌ ์ œ๊ฑฐ(์ค‘์•™๊ฐ’ ํ•„ํ„ฐ) 207๊ฑด\n", " R8 ๊ฐ„์„ญ ์žก์Œ ์ฐฝ ์ €์—ญํ†ต๊ณผ(250Hz) ํ•„ํ„ฐ 347๊ฑด\n", " R9 ์„ผ์„œ ๊ฒฐ์ธก KNN ๋ณด์ • 500๊ฑด\n", " R10 ๊ธด๊ธ‰ ์ ๊ฒ€ ์ธก์ • ๋ณด์กด(์ผ๋ฐ˜ ์ด์ƒ๊ฐ’์€ ํ‘œ์‹œ๋งŒ) 11๊ฑด\n", "\n", "์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ 10/10 ํ†ต๊ณผ, ํ†ต๊ณผ์œจ 100% (๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ)\n", "\n", "[ํŠน์ง•๋Ÿ‰ ์ •์ œ ํšจ๊ณผ] ์ŠคํŒŒ์ดํฌยท๊ฐ„์„ญ์ด ์žˆ๋˜ ์ฐฝ 554๊ฐœ์˜ ๊นจ๋—ํ•œ ์›๋ณธ ๋Œ€๋น„ ํ‰๊ท  ์˜ค์ฐจ\n", " RMS ์ •์ œ ์ „ 0.267 -> ์ •์ œ ํ›„ 0.116\n", " ์ฒจ๋„ ์ •์ œ ์ „ 8.31 -> ์ •์ œ ํ›„ 0.86\n", "\n", "[์ •์ œ ์ „ํ›„ ๊ธฐ์ˆ ํ†ต๊ณ„]\n", " ์ •์ œ ์ „ ์ •์ œ ํ›„\n", "ํ–‰ ์ˆ˜ 7813.0 7733.0\n", "์˜จ๋„ ํ‰๊ท  73.2 77.3\n", "์˜จ๋„ ์ตœ์†Œ -999.0 61.5\n", "์••๋ ฅ ์ตœ์†Œ -5.0 342.8\n", "์••๋ ฅ ๊ฒฐ์ธก 201.0 0.0\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 03. ์ž„๊ณ„๊ฐ’ ์ดˆ๊ณผ ๊ธฐ๋ฐ˜ 1์ฐจ ์„ผ์„œ ์ด์ƒ ์ง•ํ›„ ๊ฐ์ง€ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ž„๊ณ„๊ฐ’ ์ดˆ๊ณผ ๊ธฐ๋ฐ˜ 1์ฐจ ์„ผ์„œ ์ด์ƒ ์ง•ํ›„ ๊ฐ์ง€ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์ž„๊ณ„๊ฐ’ ์ดˆ๊ณผ ๊ธฐ๋ฐ˜ 1์ฐจ ์„ผ์„œ ์ด์ƒ ์ง•ํ›„ ๊ฐ์ง€ Baseline์„ ๊ตฌํ˜„ํ•˜๊ณ , ํƒ์ƒ‰์  ๋ถ„์„์œผ๋กœ ๊ณ ์ • ์ž„๊ณ„๊ฐ’์˜ ํ•œ๊ณ„์™€ ์ˆ˜๋ฆฌ๋ถ€์† ์†Œ๋ชจ์˜ ํ•ต์‹ฌ ๊ด€๋ฆฌ ๋Œ€์ƒ์„ ์ฐพ๋Š”๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ •์ƒ ์ž๋ฃŒ ๋ถ„ํฌ๋กœ RMS, ์ฒจ๋„, ์˜จ๋„, ์••๋ ฅ ์ž„๊ณ„๊ฐ’์„ ์ •ํ•˜๊ณ  ํ•˜๋‚˜๋ผ๋„ ๋„˜์œผ๋ฉด ์ด์ƒ์œผ๋กœ ๋ณด๋Š” Baseline์˜ ํƒ์ง€์œจ, ์ •๋ฐ€๋„, ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ ์˜ค๊ฒฝ๋ณด์œจ, ๊ณ ์žฅ ์ „ ํ™•๋ณด ์‹œ๊ฐ„์„ ์ธก์ • ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `notebooks/mnt04_eda_analysis.ipynb ๋ฐ EDA ์š”์•ฝ ๋ฆฌํฌํŠธ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ์˜์‚ฌ๊ฒฐ์ •์— ์ง๊ด€์ ์ธ ์ธ์‚ฌ์ดํŠธ ์ œ๊ณต ์ˆ˜์ค€\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: EDA ์‹œ๊ฐํ™” ์ฐจํŠธ 8์ข… ์ด์ƒ ์ƒ์„ฑ, ์œ ์˜๋ฏธํ•œ ์ƒ๊ด€๊ด€๊ณ„ ํ”ผ์ฒ˜ 5๊ฐœ ์ด์ƒ ๋„์ถœ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: EDA ๋…ธํŠธ๋ถ ์‹คํ–‰ ์™„๋ฃŒ ๋ฐ ํ•ต์‹ฌ ๋ฐœ๊ฒฌ์ (Key Findings) ๋ฌธ์„œํ™”\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ํ†ต๊ณ„์  ๊ฐ€์„ค ๊ฒ€์ •(p-value, ์ƒ๊ด€๊ณ„์ˆ˜)์„ ํ†ตํ•œ ์œ ์˜์„ฑ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์ž„๊ณ„๊ฐ’ ์ดˆ๊ณผ(์ง„๋™ RMSยท์ฒจ๋„ยท์˜จ๋„ยท์••๋ ฅ) ๊ธฐ๋ฐ˜ 1์ฐจ ์„ผ์„œ ์ด์ƒ ์ง•ํ›„ ๊ฐ์ง€ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•ด ํƒ์ง€์œจยท์˜ค๊ฒฝ๋ณด์œจยท๊ณ ์žฅ ์ „ ํ™•๋ณด ์‹œ๊ฐ„์„ ํ™•์ธํ•˜๊ณ , ํƒ์ƒ‰์  ๋ถ„์„(EDA ์ฐจํŠธ 8์ข…, ํ”ผ์ฒ˜์™€ ์ž”์กด์ˆ˜๋ช…์˜ ์ƒ๊ด€๊ณ„์ˆ˜ยทp-value, ์ˆ˜๋ฆฌ๋ถ€์† ์†Œ๋ชจ ํŒŒ๋ ˆํ†  ABC ๋ถ„๋ฅ˜)์œผ๋กœ ํ•ต์‹ฌ ๊ด€๋ฆฌ ๋Œ€์ƒ์„ ์‹๋ณ„ํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.stats import kurtosis, spearmanr\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๊ธฐ๋™์ฒด 48๋Œ€์˜ ์—”์ง„ยท๊ตฌ๋™๊ณ„ ์ง„๋™ยท์˜จ๋„ยท์••๋ ฅ ์„ผ์„œ ์ž๋ฃŒ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๊ฐœ์ฒด๋งˆ๋‹ค ์ •์ƒ -> ์—ดํ™” -> ๊ณ ์žฅ(์ˆ˜๋ช…์ข…๋ฃŒ)๊นŒ์ง€ ํ•˜๋ฃจ 1ํšŒ ์ธก์ •(์ง„๋™ ํŒŒํ˜• 256ํ‘œ๋ณธ + ์˜จ๋„ + ์••๋ ฅ + ๋ถ€ํ•˜)์„ ๋งŒ๋“ ๋‹ค.\n", "# ์—ดํ™” ์œ ํ˜• 4๊ฐ€์ง€: bearing(๋ฒ ์–ด๋ง), gear(๊ธฐ์–ด), oil_pump(์˜ค์ผ ํŽŒํ”„), cooling(๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ)\n", "# ---------------------------------------------------------------------------\n", "FS, NS = 1024, 256 # ๊ฐ€์ƒ ํ‘œ๋ณธํ™” ์ฃผํŒŒ์ˆ˜(Hz), ํŒŒํ˜• 1๊ฐœ๋‹น ํ‘œ๋ณธ ์ˆ˜\n", "TT = np.arange(NS) / FS\n", "MODES = { # ์—ดํ™” ์œ ํ˜•: (๋ฐœ์ƒ ํ™•๋ฅ , ํ‰๊ท  ์ˆ˜๋ช…(์ผ), ๊ต์ฒด ์ˆ˜๋ฆฌ๋ถ€์†, ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜)\n", " \"bearing\": (0.40, 160, \"์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ\", 6.0), \"gear\": (0.25, 175, \"๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ\", 9.0),\n", " \"oil_pump\": (0.20, 150, \"์˜ค์ผ ํŽŒํ”„\", 4.0), \"cooling\": (0.15, 140, \"๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ\", 3.0)}\n", "KIT_COST = 1.0 # ๋ชจ๋“  ์ •๋น„์— ํ•จ๊ป˜ ์†Œ๋ชจ๋˜๋Š” ์”ฐยทํ•„ํ„ฐ ํ‚คํŠธ ๋‹จ๊ฐ€์ง€์ˆ˜\n", "BANDS = {\"b50\": (40, 60), \"b100\": (90, 110), \"b150\": (140, 156), \"b165\": (156, 176), \"b200\": (190, 215)}\n", "\n", "\n", "def make_runs(n=48, seed=1):\n", " \"\"\"๋ฐ˜ํ™˜: units(๊ฐœ์ฒด ์ •๋ณด), snap(์ผ๋ณ„ ์ธก์ • ์š”์•ฝ), W(์ง„๋™ ํŒŒํ˜• [์ธก์ • ์ˆ˜, 256])\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(MODES)\n", " mode = rng.choice(names, n, p=[MODES[m][0] for m in names])\n", " life = np.clip(np.array([rng.normal(MODES[m][1], 18) for m in mode]), 90, 230).astype(int)\n", " start = np.sort(rng.integers(0, 240, n))\n", " units = pd.DataFrame({\"unit\": [f\"U{i:02d}\" for i in range(n)], \"mode\": mode, \"life\": life, \"start\": start, \"base_load\": rng.uniform(0.4, 0.75, n)})\n", " rows = []\n", " for u in units.itertuples():\n", " d = np.arange(u.life + 1)\n", " onset = 0.45 * u.life\n", " deg = np.clip((d - onset) / (u.life - onset), 0, 1) ** 1.5\n", " load = np.clip(u.base_load + rng.normal(0, 0.06, len(d)), 0.2, 1.0)\n", " hi = rng.random(len(d)) < 0.07 # ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ(์ •์ƒ์ด์ง€๋งŒ ์ง„๋™ยท์˜จ๋„ ์ƒ์Šน)\n", " load = np.where(hi, rng.uniform(0.95, 1.1, len(d)), load)\n", " rows.append(pd.DataFrame({\"unit\": u.unit, \"day\": d, \"cal\": u.start + d, \"rul\": u.life - d, \"deg\": deg, \"load\": load, \"hi_load\": hi, \"mode\": u.mode}))\n", " snap = pd.concat(rows, ignore_index=True)\n", " S = len(snap)\n", " snap[\"ambient\"] = 15 + 12 * np.sin(2 * np.pi * snap[\"cal\"] / 365) + rng.normal(0, 2, S)\n", " dg, ld, md = snap[\"deg\"].values, snap[\"load\"].values, snap[\"mode\"].values\n", " isb, isg, iso, isc = (md == \"bearing\"), (md == \"gear\"), (md == \"oil_pump\"), (md == \"cooling\")\n", " snap[\"temp\"] = 55 + 22 * ld + 0.5 * snap[\"ambient\"] + 9 * dg * iso + 26 * dg * isc + 3 * dg * (isb | isg) + rng.normal(0, 1.5, S)\n", " snap[\"press\"] = 450 - 12 * ld - 85 * dg * iso - 15 * dg * isc + rng.normal(0, 6, S)\n", " ph = rng.uniform(0, 2 * np.pi, (S, 3))\n", " a1, a2 = 1.0 * (0.5 + ld), 0.4 * (0.5 + ld)\n", " W = (a1[:, None] * np.sin(2 * np.pi * 50 * TT + ph[:, :1]) + a2[:, None] * np.sin(2 * np.pi * 100 * TT + ph[:, 1:2])\n", " + rng.normal(0, 0.35, (S, NS)))\n", " W += (0.9 * dg * isb)[:, None] * np.sin(2 * np.pi * 165 * TT + ph[:, 2:3])\n", " W += (0.8 * dg * isg)[:, None] * (np.sin(2 * np.pi * 150 * TT + ph[:, 2:3]) + 0.8 * np.sin(2 * np.pi * 100 * TT))\n", " W += (0.5 * dg * iso)[:, None] * np.sin(2 * np.pi * 200 * TT + ph[:, 2:3])\n", " k = rng.poisson(10 * dg * isb) # ๋ฒ ์–ด๋ง ์—ดํ™”: ์ถฉ๊ฒฉ์„ฑ ํŽ„์Šค(์ฒจ๋„ ์ƒ์Šน)\n", " pos, amp = rng.integers(0, NS, (S, 30)), rng.normal(0, 3.0, (S, 30)) * dg[:, None]\n", " amp = amp * (np.arange(30)[None, :] < k[:, None])\n", " np.add.at(W, (np.repeat(np.arange(S), 30), pos.ravel()), amp.ravel())\n", " return units, snap, W\n", "\n", "\n", "def extract(W):\n", " \"\"\"์ง„๋™ ํŒŒํ˜• -> ํŠน์ง•๋Ÿ‰: RMS, ์ฒจ๋„, ํŒŒ๊ณ ์œจ, ์ตœ๋Œ€-์ตœ์†Œ, FFT ๋Œ€์—ญ ์—๋„ˆ์ง€(50/100/150/165/200Hz ๋ถ€๊ทผ)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 3] ์ž„๊ณ„๊ฐ’ ์ดˆ๊ณผ ๊ธฐ๋ฐ˜ 1์ฐจ ์„ผ์„œ ์ด์ƒ ์ง•ํ›„ ๊ฐ์ง€ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "BASE = [\"rms\", \"kurt\", \"crest\", \"p2p\", \"b50\", \"b100\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "ROLL = [\"rms\", \"kurt\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "\n", "\n", "def build_features(snap, W):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 3] ์ž„๊ณ„๊ฐ’ ์ดˆ๊ณผ ๊ธฐ๋ฐ˜ 1์ฐจ ์„ผ์„œ ์ด์ƒ ์ง•ํ›„ ๊ฐ์ง€ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "DERIVED = [\"temp_res\", \"press_res\", \"rms_n\", \"hf_ratio\"] + [c + \"_m3\" for c in ROLL] + [\"rms_n_d\", \"temp_res_d\", \"press_res_d\"]\n", "FEATS = BASE + DERIVED\n", "AE_COLS = [\"rms_n\", \"kurt\", \"b150\", \"b165\", \"b200\", \"hf_ratio\", \"temp_res\", \"press_res\"]\n", "\n", "\n", "def split_units(units, n_train=24):\n", " return list(units[\"unit\"][:n_train]), list(units[\"unit\"][n_train:])\n", "\n", "\n", "class AE:\n", " \"\"\"์˜คํ† ์ธ์ฝ”๋”(์ž…๋ ฅ 8 -> 10 -> 4 -> 10 -> 8): ์ •์ƒ ์ž๋ฃŒ๋งŒ ์žฌ๊ตฌ์„ฑํ•˜๋„๋ก ํ•™์Šตํ•˜๊ณ , ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ๋ฅผ ์ด์ƒ ์ ์ˆ˜๋กœ ์“ด๋‹ค.\"\"\"\n", "\n", " def __init__(self, cols=AE_COLS, q=0.99, seed=0):\n", " self.cols, self.q, self.seed = cols, q, seed\n", "\n", " def fit(self, healthy):\n", " self.sc = StandardScaler().fit(healthy[self.cols])\n", " Z = self.sc.transform(healthy[self.cols])\n", " self.net = MLPRegressor(hidden_layer_sizes=(10, 4, 10), max_iter=800, random_state=self.seed).fit(Z, Z)\n", " self.thr = float(np.quantile(self.err(healthy), self.q))\n", " return self\n", "\n", " def contrib(self, d):\n", " Z = self.sc.transform(d[self.cols])\n", " return (Z - self.net.predict(Z)) ** 2 # ์ž…๋ ฅ ํ•ญ๋ชฉ๋ณ„ ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ(์ด์ƒ ๊ธฐ์—ฌ๋„)\n", "\n", " def err(self, d):\n", " return self.contrib(d).mean(axis=1)\n", "\n", "\n", "def latch(d, e, thr, k=2):\n", " \"\"\"๊ฐ™์€ ๊ฐœ์ฒด์—์„œ ์ด์ƒ ์ ์ˆ˜๊ฐ€ k์ผ ์—ฐ์† ๊ธฐ์ค€์„ ๋„˜์œผ๋ฉด ๊ฒฝ๋ณด๋ฅผ ์ผœ๊ณ  ์œ ์ง€(๋ž˜์น˜)ํ•œ๋‹ค.\"\"\"\n", " out, e = np.zeros(len(d), bool), np.asarray(e)\n", " for idx in d.reset_index(drop=True).groupby(\"unit\").indices.values():\n", " run, on = 0, False\n", " for i in idx:\n", " run = run + 1 if e[i] > thr else 0\n", " on = on or run >= k\n", " out[i] = on\n", " return out\n", "\n", "units, snap, W = make_runs()\n", "D = build_features(snap, W)\n", "\n", "# 1) Baseline: ๋‹จ์ˆœ ์ž„๊ณ„๊ฐ’(ํ•˜๋‚˜๋ผ๋„ ๋„˜์œผ๋ฉด ์ด์ƒ). ์ž„๊ณ„๊ฐ’์€ ์ •์ƒ(์—ดํ™” ์ „) ์ž๋ฃŒ์˜ ๋ถ„ํฌ๋กœ ์ •ํ•œ๋‹ค.\n", "healthy = D[D[\"deg\"] == 0]\n", "TH = {\"rms\": 1.25, \"kurt\": 3.0, \"temp\": 92.0, \"press_low\": 405.0}\n", "D[\"alarm\"] = (D[\"rms\"] > TH[\"rms\"]) | (D[\"kurt\"] > TH[\"kurt\"]) | (D[\"temp\"] > TH[\"temp\"]) | (D[\"press\"] < TH[\"press_low\"])\n", "D[\"anomaly\"] = D[\"deg\"] >= 0.1 # ์ •๋‹ต: ์—ดํ™”๊ฐ€ ์ผ์ • ์ˆ˜์ค€ ์ด์ƒ ์ง„ํ–‰๋œ ์ธก์ •\n", "tp, fp = (D[\"alarm\"] & D[\"anomaly\"]).sum(), (D[\"alarm\"] & ~D[\"anomaly\"]).sum()\n", "fn = (~D[\"alarm\"] & D[\"anomaly\"]).sum()\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์ธก์ • {len(D)}๊ฑด, ๊ฐœ์ฒด {len(units)}๋Œ€, ์—ดํ™” ๊ตฌ๊ฐ„ {int(D['anomaly'].sum())}๊ฑด\")\n", "print(f\"Baseline ์ž„๊ณ„๊ฐ’ {TH}\")\n", "print(f\" ํƒ์ง€์œจ {tp / (tp + fn):.1%}, ์ •๋ฐ€๋„ {tp / (tp + fp):.1%}, ์ •์ƒ ์ธก์ • ์˜ค๊ฒฝ๋ณด์œจ {fp / (~D['anomaly']).sum():.1%} (๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ ์˜ค๊ฒฝ๋ณด์œจ {D.loc[D['hi_load'] & ~D['anomaly'], 'alarm'].mean():.1%})\")\n", "lead = []\n", "for u, g in D.groupby(\"unit\"):\n", " a = g[g[\"alarm\"] & g[\"anomaly\"]]\n", " lead.append(a[\"rul\"].max() if len(a) else 0)\n", "units[\"lead\"] = lead\n", "print(f\" ๊ณ ์žฅ ์ „ ์ฒซ ๊ฒฝ๋ณด ํ™•๋ณด ์‹œ๊ฐ„ ์ค‘์•™๊ฐ’ {np.median(lead):.0f}์ผ, ๊ฒฝ๋ณด ๋ชป ๋‚ธ ๊ฐœ์ฒด {int((units['lead'] == 0).sum())}๋Œ€/{len(units)}๋Œ€\")\n", "print(\" ์—ดํ™” ์œ ํ˜•๋ณ„ ํƒ์ง€์œจ: \" + \", \".join(f\"{m} {D.loc[D['anomaly'] & (D['mode'] == m), 'alarm'].mean():.0%}\" for m in MODES))\n", "\n", "# 2) ์ƒ๊ด€๋ถ„์„: ํ”ผ์ฒ˜์™€ ์ž”์กด์ˆ˜๋ช…(RUL) (์Šคํ”ผ์–ด๋งŒ)\n", "rows = []\n", "for c in [\"rms\", \"kurt\", \"crest\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\", \"hf_ratio\", \"temp_res\", \"press_res\", \"load\", \"ambient\"]:\n", " r, p = spearmanr(D[c], D[\"rul\"]); rows.append((c, r, p))\n", "cor = pd.DataFrame(rows, columns=[\"ํ”ผ์ฒ˜\", \"rho\", \"p\"]); cor[\"์œ ์˜\"] = (cor[\"p\"] < 0.05) & (cor[\"rho\"].abs() >= 0.2)\n", "print(\"\\n[์ƒ๊ด€๋ถ„์„] ํ”ผ์ฒ˜ | ์Šคํ”ผ์–ด๋งŒ rho | p-value | ์œ ์˜(p<0.05, |rho|>=0.2)\")\n", "for x in cor.sort_values(\"rho\", key=abs, ascending=False).head(8).itertuples():\n", " print(f\" {x.ํ”ผ์ฒ˜:10s} {x.rho:+.2f} {x.p:.4f} {'์œ ์˜' if x.์œ ์˜ else '-'}\")\n", "nsig = int(cor[\"์œ ์˜\"].sum())\n", "\n", "# 3) ํŒŒ๋ ˆํ† (ABC): ๊ณ ์žฅ๋ณ„ ์ˆ˜๋ฆฌ๋ถ€์† ์†Œ๋ชจ ๋น„์šฉ(๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜)\n", "cons = {}\n", "for m in units[\"mode\"]:\n", " cons[MODES[m][2]] = cons.get(MODES[m][2], 0) + MODES[m][3]\n", " cons[\"์”ฐยทํ•„ํ„ฐ ํ‚คํŠธ\"] = cons.get(\"์”ฐยทํ•„ํ„ฐ ํ‚คํŠธ\", 0) + KIT_COST\n", "par = pd.Series(cons).sort_values(ascending=False).to_frame(\"์†Œ๋ชจ ๋น„์šฉ์ง€์ˆ˜\")\n", "par[\"๋ˆ„์ ๋น„์œจ\"] = par[\"์†Œ๋ชจ ๋น„์šฉ์ง€์ˆ˜\"].cumsum() / par[\"์†Œ๋ชจ ๋น„์šฉ์ง€์ˆ˜\"].sum()\n", "par[\"๋“ฑ๊ธ‰\"] = np.where(par[\"๋ˆ„์ ๋น„์œจ\"] <= 0.8, \"A\", np.where(par[\"๋ˆ„์ ๋น„์œจ\"] <= 0.95, \"B\", \"C\")); par.iloc[0, 2] = \"A\"\n", "print(\"\\n[ํŒŒ๋ ˆํ† ] ์ˆ˜๋ฆฌ๋ถ€์† ์†Œ๋ชจ ๋น„์šฉ(๊ณ ์žฅ 1ํšŒ ๊ต์ฒด ๊ธฐ์ค€)\")\n", "print(par.assign(๋ˆ„์ ๋น„์œจ=lambda d: d[\"๋ˆ„์ ๋น„์œจ\"].map(\"{:.0%}\".format)).round(0).to_string())\n", "top20 = par.head(int(np.ceil(len(par) * 0.2)))\n", "print(f\"์ƒ์œ„ 20% ํ’ˆ๋ชฉ({', '.join(top20.index)})์ด ์ „์ฒด ์†Œ๋ชจ ๋น„์šฉ์˜ {top20['์†Œ๋ชจ ๋น„์šฉ์ง€์ˆ˜'].sum() / par['์†Œ๋ชจ ๋น„์šฉ์ง€์ˆ˜'].sum():.0%}\")\n", "\n", "# 4) EDA ์ฐจํŠธ 8์ข…\n", "fig, ax = plt.subplots(2, 4, figsize=(16, 7))\n", "ax[0, 0].scatter(D[\"rul\"], D[\"rms\"], s=3); ax[0, 0].set_title(\"1 RMS vs RUL\"); ax[0, 0].invert_xaxis()\n", "for m in MODES:\n", " g = D[D[\"mode\"] == m]; ax[0, 1].scatter(g[\"deg\"], g[\"kurt\"], s=2, label=m)\n", "ax[0, 1].set_title(\"2 Kurtosis vs degradation\"); ax[0, 1].legend(fontsize=6)\n", "for m in MODES:\n", " g = D[D[\"mode\"] == m]; ax[0, 2].scatter(g[\"deg\"], g[\"temp_res\"], s=2)\n", "ax[0, 2].set_title(\"3 Temp residual vs degradation\")\n", "for m in MODES:\n", " g = D[D[\"mode\"] == m]; ax[0, 3].scatter(g[\"deg\"], g[\"press_res\"], s=2)\n", "ax[0, 3].set_title(\"4 Pressure residual vs degradation\")\n", "f = np.fft.rfftfreq(NS, 1 / FS); bm = D[\"mode\"] == \"bearing\"\n", "ax[1, 0].plot(f, np.abs(np.fft.rfft(W[(bm & (D[\"deg\"] == 0)).values], axis=1)).mean(axis=0) / NS, label=\"healthy\")\n", "ax[1, 0].plot(f, np.abs(np.fft.rfft(W[(bm & (D[\"deg\"] > 0.6)).values], axis=1)).mean(axis=0) / NS, label=\"degraded\"); ax[1, 0].legend(); ax[1, 0].set_title(\"5 Mean spectrum (bearing)\")\n", "ax[1, 1].hist([units.loc[units[\"mode\"] == m, \"life\"] for m in MODES], bins=8, stacked=True, label=list(MODES)); ax[1, 1].set_title(\"6 Life by failure mode\")\n", "ax[1, 2].scatter(healthy[\"load\"], healthy[\"rms\"], s=2); ax[1, 2].set_title(\"7 Load vs RMS (healthy)\")\n", "ax[1, 3].bar(range(len(par)), par[\"์†Œ๋ชจ ๋น„์šฉ์ง€์ˆ˜\"]); ax[1, 3].set_title(\"8 Spare-part consumption (Pareto)\")\n", "plt.tight_layout(); plt.show()\n", "\n", "print(f\"\\nEDA ์ฐจํŠธ 8์ข… ์ƒ์„ฑ(๋ชฉํ‘œ 8์ข… ์ด์ƒ: ๋‹ฌ์„ฑ), ์œ ์˜ํ•œ ํ”ผ์ฒ˜ {nsig}๊ฐœ (๋ชฉํ‘œ 5๊ฐœ ์ด์ƒ: {'๋‹ฌ์„ฑ' if nsig >= 5 else '๋ฏธ๋‹ฌ'})\")\n", "print(\"ํ•ต์‹ฌ ๋ฐœ๊ฒฌ: 1) ๊ณ ์ • ์ž„๊ณ„๊ฐ’์€ ๊ณ ๋ถ€ํ•˜์ผ ์˜ค๊ฒฝ๋ณด์™€ ์˜จ๋„ยท์••๋ ฅ ์œ ํ˜• ๋†“์นจ์ด ์žˆ์Œ 2) ๋ถ€ํ•˜ยท์™ธ๊ธฐ๋ฅผ ๋ณด์ •ํ•œ ์ž”์ฐจ ํ”ผ์ฒ˜๊ฐ€ ์—ดํ™”์™€ ๋” ๋šœ๋ ทํ•จ 3) ๋ฒ ์–ด๋งยท๊ธฐ์–ด ๋ถ€ํ’ˆ์ด ์†Œ๋ชจ ๋น„์šฉ ๋Œ€๋ถ€๋ถ„\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 03 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์ธก์ • 7733๊ฑด, ๊ฐœ์ฒด 48๋Œ€, ์—ดํ™” ๊ตฌ๊ฐ„ 3336๊ฑด\n", "Baseline ์ž„๊ณ„๊ฐ’ {'rms': 1.25, 'kurt': 3.0, 'temp': 92.0, 'press_low': 405.0}\n", " ํƒ์ง€์œจ 25.3%, ์ •๋ฐ€๋„ 90.3%, ์ •์ƒ ์ธก์ • ์˜ค๊ฒฝ๋ณด์œจ 2.1% (๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ ์˜ค๊ฒฝ๋ณด์œจ 28.6%)\n", " ๊ณ ์žฅ ์ „ ์ฒซ ๊ฒฝ๋ณด ํ™•๋ณด ์‹œ๊ฐ„ ์ค‘์•™๊ฐ’ 40์ผ, ๊ฒฝ๋ณด ๋ชป ๋‚ธ ๊ฐœ์ฒด 0๋Œ€/48๋Œ€\n", " ์—ดํ™” ์œ ํ˜•๋ณ„ ํƒ์ง€์œจ: bearing 29%, gear 9%, oil_pump 54%, cooling 31%\n", "\n", "[์ƒ๊ด€๋ถ„์„] ํ”ผ์ฒ˜ | ์Šคํ”ผ์–ด๋งŒ rho | p-value | ์œ ์˜(p<0.05, |rho|>=0.2)\n", " hf_ratio -0.63 0.0000 ์œ ์˜\n", " temp_res -0.53 0.0000 ์œ ์˜\n", " b165 -0.38 0.0000 ์œ ์˜\n", " b150 -0.37 0.0000 ์œ ์˜\n", " rms -0.32 0.0000 ์œ ์˜\n", " ambient +0.31 0.0000 ์œ ์˜\n", " kurt -0.29 0.0000 ์œ ์˜\n", " b200 -0.21 0.0000 ์œ ์˜\n", "\n", "[ํŒŒ๋ ˆํ† ] ์ˆ˜๋ฆฌ๋ถ€์† ์†Œ๋ชจ ๋น„์šฉ(๊ณ ์žฅ 1ํšŒ ๊ต์ฒด ๊ธฐ์ค€)\n", " ์†Œ๋ชจ ๋น„์šฉ์ง€์ˆ˜ ๋ˆ„์ ๋น„์œจ ๋“ฑ๊ธ‰\n", "๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ 162.0 46% A\n", "์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ 90.0 71% A\n", "์”ฐยทํ•„ํ„ฐ ํ‚คํŠธ 48.0 85% B\n", "์˜ค์ผ ํŽŒํ”„ 32.0 94% B\n", "๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ 21.0 100% C\n", "์ƒ์œ„ 20% ํ’ˆ๋ชฉ(๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ)์ด ์ „์ฒด ์†Œ๋ชจ ๋น„์šฉ์˜ 46%\n", "\n", "EDA ์ฐจํŠธ 8์ข… ์ƒ์„ฑ(๋ชฉํ‘œ 8์ข… ์ด์ƒ: ๋‹ฌ์„ฑ), ์œ ์˜ํ•œ ํ”ผ์ฒ˜ 9๊ฐœ (๋ชฉํ‘œ 5๊ฐœ ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "ํ•ต์‹ฌ ๋ฐœ๊ฒฌ: 1) ๊ณ ์ • ์ž„๊ณ„๊ฐ’์€ ๊ณ ๋ถ€ํ•˜์ผ ์˜ค๊ฒฝ๋ณด์™€ ์˜จ๋„ยท์••๋ ฅ ์œ ํ˜• ๋†“์นจ์ด ์žˆ์Œ 2) ๋ถ€ํ•˜ยท์™ธ๊ธฐ๋ฅผ ๋ณด์ •ํ•œ ์ž”์ฐจ ํ”ผ์ฒ˜๊ฐ€ ์—ดํ™”์™€ ๋” ๋šœ๋ ทํ•จ 3) ๋ฒ ์–ด๋งยท๊ธฐ์–ด ๋ถ€ํ’ˆ์ด ์†Œ๋ชจ ๋น„์šฉ ๋Œ€๋ถ€๋ถ„\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 04. ์˜คํ† ์ธ์ฝ”๋”(Autoencoder)/LSTM ๊ธฐ๋ฐ˜ ๋ฏธ์„ธ ์ง„๋™ ๋ณ€ํ™” ๊ฐ์ง€ ์˜ˆ์ง€์ •๋น„(PdM) ๋ชจ๋ธ ๊ณ ๋„ํ™”\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์˜คํ† ์ธ์ฝ”๋” ๊ธฐ๋ฐ˜ ๋ฏธ์„ธ ์ง„๋™ ๋ณ€ํ™” ๊ฐ์ง€ ์˜ˆ์ง€์ •๋น„(PdM) ๋ชจ๋ธ์„ ๊ณ ๋„ํ™”ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์˜คํ† ์ธ์ฝ”๋” ๊ธฐ๋ฐ˜ ๋ฏธ์„ธ ์ง„๋™ยท์˜จ๋„ ๋ณ€ํ™” ๊ฐ์ง€ ์˜ˆ์ง€์ •๋น„(PdM) ๋ชจ๋ธ์„ ๊ณ ๋„ํ™”ํ•˜๊ณ , ํŒŒ์ƒ ํ”ผ์ฒ˜๋กœ ์ž”์กด์ˆ˜๋ช… ๊ธฐ์ค€ ๋ชจ๋ธ์„ ๋งŒ๋“ ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ •์ƒ ์ž๋ฃŒ๋งŒ์œผ๋กœ ์˜คํ† ์ธ์ฝ”๋”(MLP)๋ฅผ ํ•™์Šตํ•ด ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ๋ฅผ ์ด์ƒ ์ ์ˆ˜๋กœ ์“ฐ๊ณ  2์ผ ์—ฐ์† ์ดˆ๊ณผ ์‹œ ๊ฒฝ๋ณด๋ฅผ ์ผ ๋‹ค ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 5๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/features/mnt04_feature_engineering.py ๋ฐ ๊ธฐ์ค€ ๋ชจ๋ธ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ์‹ค๋ฌด์—์„œ ๋‚ฉ๋“ ๊ฐ€๋Šฅํ•œ ํ”ผ์ฒ˜ ์„ค๊ณ„์˜ ๋…ผ๋ฆฌ์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ํŒŒ์ƒ ํ”ผ์ฒ˜ 10๊ฐœ ์ด์ƒ ์ƒ์„ฑ, ๊ธฐ์ค€ ๋ชจ๋ธ MAPE 25% ์ดํ•˜ ๋‹ฌ์„ฑ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ํ”ผ์ฒ˜ ํŒŒ์ดํ”„๋ผ์ธ ๋ฐ ๋ฒ ์ด์Šค๋ผ์ธ ๋ชจ๋ธ ์„ฑ๋Šฅ ์ง€ํ‘œ ๋„์ถœ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๊ต์ฐจ ๊ฒ€์ฆ(Time-series Cross Validation)์„ ํ†ตํ•œ ๊ณผ์ ํ•ฉ ๋ฐฉ์ง€\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์ •์ƒ ์ž๋ฃŒ๋งŒ์œผ๋กœ ํ•™์Šตํ•œ ์˜คํ† ์ธ์ฝ”๋”์˜ ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ๋กœ ๋ฏธ์„ธ ์ง„๋™ยท์˜จ๋„ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€(2์ผ ์—ฐ์† ์‹œ ๊ฒฝ๋ณด)ํ•ด ์ž„๊ณ„๊ฐ’ Baseline๊ณผ ๋น„๊ตํ•˜๊ณ , ํŒŒ์ƒ ํ”ผ์ฒ˜ 14๊ฐœ๋กœ ์ž”์กด์ˆ˜๋ช…(RUL) ๊ธฐ์ค€ ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด ๊ฐœ์ฒด ๋‹จ์œ„ ์‹œ๊ณ„์—ด ๊ต์ฐจ๊ฒ€์ฆ MAPE๋ฅผ ํ‰๊ฐ€ํ•˜๋ฉฐ ์ˆœ์—ด ์ค‘์š”๋„๋กœ ํ”ผ์ฒ˜๋ฅผ ์ค„์ด๋Š” ์˜ˆ์ง€์ •๋น„(PdM) ๋ชจ๋ธ์„ ๊ตฌํ˜„ํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "from scipy.stats import kurtosis\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "from sklearn.inspection import permutation_importance\n", "from sklearn.neural_network import MLPRegressor\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๊ธฐ๋™์ฒด 48๋Œ€์˜ ์—”์ง„ยท๊ตฌ๋™๊ณ„ ์ง„๋™ยท์˜จ๋„ยท์••๋ ฅ ์„ผ์„œ ์ž๋ฃŒ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๊ฐœ์ฒด๋งˆ๋‹ค ์ •์ƒ -> ์—ดํ™” -> ๊ณ ์žฅ(์ˆ˜๋ช…์ข…๋ฃŒ)๊นŒ์ง€ ํ•˜๋ฃจ 1ํšŒ ์ธก์ •(์ง„๋™ ํŒŒํ˜• 256ํ‘œ๋ณธ + ์˜จ๋„ + ์••๋ ฅ + ๋ถ€ํ•˜)์„ ๋งŒ๋“ ๋‹ค.\n", "# ์—ดํ™” ์œ ํ˜• 4๊ฐ€์ง€: bearing(๋ฒ ์–ด๋ง), gear(๊ธฐ์–ด), oil_pump(์˜ค์ผ ํŽŒํ”„), cooling(๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ)\n", "# ---------------------------------------------------------------------------\n", "FS, NS = 1024, 256 # ๊ฐ€์ƒ ํ‘œ๋ณธํ™” ์ฃผํŒŒ์ˆ˜(Hz), ํŒŒํ˜• 1๊ฐœ๋‹น ํ‘œ๋ณธ ์ˆ˜\n", "TT = np.arange(NS) / FS\n", "MODES = { # ์—ดํ™” ์œ ํ˜•: (๋ฐœ์ƒ ํ™•๋ฅ , ํ‰๊ท  ์ˆ˜๋ช…(์ผ), ๊ต์ฒด ์ˆ˜๋ฆฌ๋ถ€์†, ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜)\n", " \"bearing\": (0.40, 160, \"์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ\", 6.0), \"gear\": (0.25, 175, \"๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ\", 9.0),\n", " \"oil_pump\": (0.20, 150, \"์˜ค์ผ ํŽŒํ”„\", 4.0), \"cooling\": (0.15, 140, \"๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ\", 3.0)}\n", "KIT_COST = 1.0 # ๋ชจ๋“  ์ •๋น„์— ํ•จ๊ป˜ ์†Œ๋ชจ๋˜๋Š” ์”ฐยทํ•„ํ„ฐ ํ‚คํŠธ ๋‹จ๊ฐ€์ง€์ˆ˜\n", "BANDS = {\"b50\": (40, 60), \"b100\": (90, 110), \"b150\": (140, 156), \"b165\": (156, 176), \"b200\": (190, 215)}\n", "\n", "\n", "def make_runs(n=48, seed=1):\n", " \"\"\"๋ฐ˜ํ™˜: units(๊ฐœ์ฒด ์ •๋ณด), snap(์ผ๋ณ„ ์ธก์ • ์š”์•ฝ), W(์ง„๋™ ํŒŒํ˜• [์ธก์ • ์ˆ˜, 256])\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(MODES)\n", " mode = rng.choice(names, n, p=[MODES[m][0] for m in names])\n", " life = np.clip(np.array([rng.normal(MODES[m][1], 18) for m in mode]), 90, 230).astype(int)\n", " start = np.sort(rng.integers(0, 240, n))\n", " units = pd.DataFrame({\"unit\": [f\"U{i:02d}\" for i in range(n)], \"mode\": mode, \"life\": life, \"start\": start, \"base_load\": rng.uniform(0.4, 0.75, n)})\n", " rows = []\n", " for u in units.itertuples():\n", " d = np.arange(u.life + 1)\n", " onset = 0.45 * u.life\n", " deg = np.clip((d - onset) / (u.life - onset), 0, 1) ** 1.5\n", " load = np.clip(u.base_load + rng.normal(0, 0.06, len(d)), 0.2, 1.0)\n", " hi = rng.random(len(d)) < 0.07 # ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ(์ •์ƒ์ด์ง€๋งŒ ์ง„๋™ยท์˜จ๋„ ์ƒ์Šน)\n", " load = np.where(hi, rng.uniform(0.95, 1.1, len(d)), load)\n", " rows.append(pd.DataFrame({\"unit\": u.unit, \"day\": d, \"cal\": u.start + d, \"rul\": u.life - d, \"deg\": deg, \"load\": load, \"hi_load\": hi, \"mode\": u.mode}))\n", " snap = pd.concat(rows, ignore_index=True)\n", " S = len(snap)\n", " snap[\"ambient\"] = 15 + 12 * np.sin(2 * np.pi * snap[\"cal\"] / 365) + rng.normal(0, 2, S)\n", " dg, ld, md = snap[\"deg\"].values, snap[\"load\"].values, snap[\"mode\"].values\n", " isb, isg, iso, isc = (md == \"bearing\"), (md == \"gear\"), (md == \"oil_pump\"), (md == \"cooling\")\n", " snap[\"temp\"] = 55 + 22 * ld + 0.5 * snap[\"ambient\"] + 9 * dg * iso + 26 * dg * isc + 3 * dg * (isb | isg) + rng.normal(0, 1.5, S)\n", " snap[\"press\"] = 450 - 12 * ld - 85 * dg * iso - 15 * dg * isc + rng.normal(0, 6, S)\n", " ph = rng.uniform(0, 2 * np.pi, (S, 3))\n", " a1, a2 = 1.0 * (0.5 + ld), 0.4 * (0.5 + ld)\n", " W = (a1[:, None] * np.sin(2 * np.pi * 50 * TT + ph[:, :1]) + a2[:, None] * np.sin(2 * np.pi * 100 * TT + ph[:, 1:2])\n", " + rng.normal(0, 0.35, (S, NS)))\n", " W += (0.9 * dg * isb)[:, None] * np.sin(2 * np.pi * 165 * TT + ph[:, 2:3])\n", " W += (0.8 * dg * isg)[:, None] * (np.sin(2 * np.pi * 150 * TT + ph[:, 2:3]) + 0.8 * np.sin(2 * np.pi * 100 * TT))\n", " W += (0.5 * dg * iso)[:, None] * np.sin(2 * np.pi * 200 * TT + ph[:, 2:3])\n", " k = rng.poisson(10 * dg * isb) # ๋ฒ ์–ด๋ง ์—ดํ™”: ์ถฉ๊ฒฉ์„ฑ ํŽ„์Šค(์ฒจ๋„ ์ƒ์Šน)\n", " pos, amp = rng.integers(0, NS, (S, 30)), rng.normal(0, 3.0, (S, 30)) * dg[:, None]\n", " amp = amp * (np.arange(30)[None, :] < k[:, None])\n", " np.add.at(W, (np.repeat(np.arange(S), 30), pos.ravel()), amp.ravel())\n", " return units, snap, W\n", "\n", "\n", "def extract(W):\n", " \"\"\"์ง„๋™ ํŒŒํ˜• -> ํŠน์ง•๋Ÿ‰: RMS, ์ฒจ๋„, ํŒŒ๊ณ ์œจ, ์ตœ๋Œ€-์ตœ์†Œ, FFT ๋Œ€์—ญ ์—๋„ˆ์ง€(50/100/150/165/200Hz ๋ถ€๊ทผ)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] ์˜คํ† ์ธ์ฝ”๋”(Autoencoder)/LSTM ๊ธฐ๋ฐ˜ ๋ฏธ์„ธ ์ง„๋™ ๋ณ€ํ™” ๊ฐ์ง€ ์˜ˆ์ง€์ •๋น„(PdM) ๋ชจ๋ธ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "BASE = [\"rms\", \"kurt\", \"crest\", \"p2p\", \"b50\", \"b100\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "ROLL = [\"rms\", \"kurt\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "\n", "\n", "def build_features(snap, W):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] ์˜คํ† ์ธ์ฝ”๋”(Autoencoder)/LSTM ๊ธฐ๋ฐ˜ ๋ฏธ์„ธ ์ง„๋™ ๋ณ€ํ™” ๊ฐ์ง€ ์˜ˆ์ง€์ •๋น„(PdM) ๋ชจ๋ธ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "DERIVED = [\"temp_res\", \"press_res\", \"rms_n\", \"hf_ratio\"] + [c + \"_m3\" for c in ROLL] + [\"rms_n_d\", \"temp_res_d\", \"press_res_d\"]\n", "FEATS = BASE + DERIVED\n", "AE_COLS = [\"rms_n\", \"kurt\", \"b150\", \"b165\", \"b200\", \"hf_ratio\", \"temp_res\", \"press_res\"]\n", "\n", "\n", "def split_units(units, n_train=24):\n", " return list(units[\"unit\"][:n_train]), list(units[\"unit\"][n_train:])\n", "\n", "\n", "class AE:\n", " \"\"\"์˜คํ† ์ธ์ฝ”๋”(์ž…๋ ฅ 8 -> 10 -> 4 -> 10 -> 8): ์ •์ƒ ์ž๋ฃŒ๋งŒ ์žฌ๊ตฌ์„ฑํ•˜๋„๋ก ํ•™์Šตํ•˜๊ณ , ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ๋ฅผ ์ด์ƒ ์ ์ˆ˜๋กœ ์“ด๋‹ค.\"\"\"\n", "\n", " def __init__(self, cols=AE_COLS, q=0.99, seed=0):\n", " self.cols, self.q, self.seed = cols, q, seed\n", "\n", " def fit(self, healthy):\n", " self.sc = StandardScaler().fit(healthy[self.cols])\n", " Z = self.sc.transform(healthy[self.cols])\n", " self.net = MLPRegressor(hidden_layer_sizes=(10, 4, 10), max_iter=800, random_state=self.seed).fit(Z, Z)\n", " self.thr = float(np.quantile(self.err(healthy), self.q))\n", " return self\n", "\n", " def contrib(self, d):\n", " Z = self.sc.transform(d[self.cols])\n", " return (Z - self.net.predict(Z)) ** 2 # ์ž…๋ ฅ ํ•ญ๋ชฉ๋ณ„ ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ(์ด์ƒ ๊ธฐ์—ฌ๋„)\n", "\n", " def err(self, d):\n", " return self.contrib(d).mean(axis=1)\n", "\n", "\n", "def latch(d, e, thr, k=2):\n", " \"\"\"๊ฐ™์€ ๊ฐœ์ฒด์—์„œ ์ด์ƒ ์ ์ˆ˜๊ฐ€ k์ผ ์—ฐ์† ๊ธฐ์ค€์„ ๋„˜์œผ๋ฉด ๊ฒฝ๋ณด๋ฅผ ์ผœ๊ณ  ์œ ์ง€(๋ž˜์น˜)ํ•œ๋‹ค.\"\"\"\n", " out, e = np.zeros(len(d), bool), np.asarray(e)\n", " for idx in d.reset_index(drop=True).groupby(\"unit\").indices.values():\n", " run, on = 0, False\n", " for i in idx:\n", " run = run + 1 if e[i] > thr else 0\n", " on = on or run >= k\n", " out[i] = on\n", " return out\n", "\n", "units, snap, W = make_runs()\n", "D = build_features(snap, W)\n", "tr_u, va_u = split_units(units)\n", "tr, va = D[D[\"unit\"].isin(tr_u)].reset_index(drop=True), D[D[\"unit\"].isin(va_u)].reset_index(drop=True)\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•™์Šต {len(tr_u)}๋Œ€({len(tr)}๊ฑด), ๊ฒ€์ฆ {len(va_u)}๋Œ€({len(va)}๊ฑด), ํŒŒ์ƒ ํ”ผ์ฒ˜ {len(DERIVED)}๊ฐœ (๋ชฉํ‘œ 10๊ฐœ ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(DERIVED) >= 10 else '๋ฏธ๋‹ฌ'})\")\n", "print(\" ํŒŒ์ƒ ํ”ผ์ฒ˜:\", \", \".join(DERIVED))\n", "\n", "# 1) ์˜คํ† ์ธ์ฝ”๋” ์ด์ƒ ๊ฐ์ง€ vs ์ž„๊ณ„๊ฐ’ Baseline (๊ฒ€์ฆ ๊ฐœ์ฒด)\n", "ae = AE().fit(tr[tr[\"deg\"] == 0])\n", "e = ae.err(va)\n", "va[\"anomaly\"] = va[\"deg\"] >= 0.1\n", "va[\"ae_alarm\"] = latch(va, e, ae.thr, k=2)\n", "va[\"th_alarm\"] = (va[\"rms\"] > 1.25) | (va[\"kurt\"] > 3.0) | (va[\"temp\"] > 92.0) | (va[\"press\"] < 405.0)\n", "va[\"ae_raw\"] = e > ae.thr\n", "\n", "\n", "def report(name, col):\n", " a = va[col]\n", " tpr, far = (a & va[\"anomaly\"]).sum() / va[\"anomaly\"].sum(), (a & ~va[\"anomaly\"]).sum() / (~va[\"anomaly\"]).sum()\n", " hi = a[va[\"hi_load\"] & ~va[\"anomaly\"]].mean()\n", " lead = [g.loc[g[col] & g[\"anomaly\"], \"rul\"].max() if (g[col] & g[\"anomaly\"]).any() else 0 for _, g in va.groupby(\"unit\")]\n", " print(f\" {name:22s} ํƒ์ง€์œจ {tpr:5.1%} ์˜ค๊ฒฝ๋ณด์œจ {far:5.1%} ๊ณ ๋ถ€ํ•˜์ผ ์˜ค๊ฒฝ๋ณด {hi:5.1%} ๊ณ ์žฅ ์ „ ํ™•๋ณด ์ค‘์•™๊ฐ’ {np.median(lead):.0f}์ผ\")\n", " return tpr, far\n", "\n", "\n", "print(f\"\\n[์˜คํ† ์ธ์ฝ”๋” ์ž…๋ ฅ {len(AE_COLS)}๊ฐœ, ์ •์ƒ ์ž๋ฃŒ ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ 99๋ถ„์œ„ {ae.thr:.2f}๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์‚ฌ์šฉ]\")\n", "report(\"์ž„๊ณ„๊ฐ’ Baseline\", \"th_alarm\")\n", "report(\"์˜คํ† ์ธ์ฝ”๋”(1ํšŒ ์ดˆ๊ณผ)\", \"ae_raw\")\n", "tpr, far = report(\"์˜คํ† ์ธ์ฝ”๋”(2์ผ ์—ฐ์†)\", \"ae_alarm\")\n", "print(f\" ๋ฏธ์„ธ ๋ณ€ํ™” ๊ตฌ๊ฐ„(์—ดํ™” 0.1~0.3) ํƒ์ง€์œจ: Baseline {va.loc[va['deg'].between(0.1, 0.3), 'th_alarm'].mean():.1%} -> ์˜คํ† ์ธ์ฝ”๋” {va.loc[va['deg'].between(0.1, 0.3), 'ae_alarm'].mean():.1%}\")\n", "\n", "# 2) RUL ๊ธฐ์ค€ ๋ชจ๋ธ: ํ•™์Šต ๊ฐœ์ฒด ์ผ๋ถ€๋กœ ํ•™์Šต, ๊ฐœ์ฒด ๋‹จ์œ„ ์‹œ๊ณ„์—ด ๊ต์ฐจ๊ฒ€์ฆ(์•ž์„  ๊ฐœ์ฒด๋กœ ํ•™์Šต -> ๋’ค ๊ฐœ์ฒด ๊ฒ€์ฆ)\n", "CAP = 120 # RUL ์ƒํ•œ(์ผ): ์—ดํ™” ์ดˆ๊ธฐ์—๋Š” 120์ผ ์ด์ƒ์€ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š๋Š”๋‹ค\n", "def fit_rul(d, cols):\n", " return GradientBoostingRegressor(n_estimators=100, max_depth=3, learning_rate=0.1, subsample=0.8, random_state=0).fit(d[cols], np.minimum(d[\"rul\"], CAP))\n", "\n", "\n", "def mape(d, p):\n", " m = (d[\"deg\"] >= 0.1) & (d[\"rul\"] >= 10) # ์—ดํ™”๊ฐ€ ์‹œ์ž‘๋œ ๊ตฌ๊ฐ„, ๊ณ ์žฅ ์ง์ „ 10์ผ ๋ฏธ๋งŒ์€ ๋ถ„๋ชจ ๋ฌธ์ œ๋กœ ์ œ์™ธ\n", " y = np.minimum(d[\"rul\"], CAP)[m]\n", " return float(np.mean(np.abs(y - p[m]) / y))\n", "\n", "\n", "order = list(units[\"unit\"])\n", "folds, cut = [], [12, 24, 36]\n", "for i, c in enumerate(cut):\n", " trn, val = D[D[\"unit\"].isin(order[:c])], D[D[\"unit\"].isin(order[c:c + 12])]\n", " if len(val):\n", " folds.append(mape(val, fit_rul(trn, FEATS).predict(val[FEATS])))\n", "base_only = fit_rul(tr, BASE)\n", "full = fit_rul(tr, FEATS)\n", "m_base, m_full = mape(va, base_only.predict(va[BASE])), mape(va, full.predict(va[FEATS]))\n", "print(f\"\\n[RUL ๋ชจ๋ธ] ๊ฐœ์ฒด ๋‹จ์œ„ ์‹œ๊ณ„์—ด ๊ต์ฐจ๊ฒ€์ฆ ํด๋“œ๋ณ„ MAPE: {', '.join(f'{f:.1%}' for f in folds)} -> ํ‰๊ท  {np.mean(folds):.1%} (๋ชฉํ‘œ 25% ์ดํ•˜: {'๋‹ฌ์„ฑ' if np.mean(folds) <= 0.25 else '๋ฏธ๋‹ฌ'})\")\n", "print(f\" ๊ฒ€์ฆ 24๋Œ€ MAPE: ์›๋ณธ ์„ผ์„œ {len(BASE)}๊ฐœ {m_base:.1%} -> ํŒŒ์ƒ ํฌํ•จ {len(FEATS)}๊ฐœ {m_full:.1%} (์—ดํ™” ๊ตฌ๊ฐ„, ์ž”์กด 10์ผ ์ด์ƒ)\")\n", "\n", "# 3) ์ˆœ์—ด ์ค‘์š”๋„๋กœ ์ฐจ์› ์ถ•์†Œ\n", "pi = permutation_importance(full, va[FEATS], np.minimum(va[\"rul\"], CAP), n_repeats=3, random_state=0, scoring=\"neg_mean_absolute_error\")\n", "imp = pd.Series(pi.importances_mean, FEATS).sort_values(ascending=False)\n", "print(\"ํ”ผ์ฒ˜ ์ค‘์š”๋„ ์ƒ์œ„ 5: \" + \", \".join(f\"{k}({v:.1f})\" for k, v in imp.head(5).items()))\n", "top = list(imp.index[:8])\n", "m_top = mape(va, fit_rul(tr, top).predict(va[top]))\n", "print(f\"์ฐจ์› ์ถ•์†Œ {len(FEATS)}๊ฐœ -> {len(top)}๊ฐœ: ๊ฒ€์ฆ MAPE {m_full:.1%} -> {m_top:.1%}\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 04 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•™์Šต 24๋Œ€(3847๊ฑด), ๊ฒ€์ฆ 24๋Œ€(3886๊ฑด), ํŒŒ์ƒ ํ”ผ์ฒ˜ 14๊ฐœ (๋ชฉํ‘œ 10๊ฐœ ์ด์ƒ: ๋‹ฌ์„ฑ)\n", " ํŒŒ์ƒ ํ”ผ์ฒ˜: temp_res, press_res, rms_n, hf_ratio, rms_m3, kurt_m3, b150_m3, b165_m3, b200_m3, temp_m3, press_m3, rms_n_d, temp_res_d, press_res_d\n", "\n", "[์˜คํ† ์ธ์ฝ”๋” ์ž…๋ ฅ 8๊ฐœ, ์ •์ƒ ์ž๋ฃŒ ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ 99๋ถ„์œ„ 1.27๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์‚ฌ์šฉ]\n", " ์ž„๊ณ„๊ฐ’ Baseline ํƒ์ง€์œจ 22.7% ์˜ค๊ฒฝ๋ณด์œจ 1.7% ๊ณ ๋ถ€ํ•˜์ผ ์˜ค๊ฒฝ๋ณด 22.8% ๊ณ ์žฅ ์ „ ํ™•๋ณด ์ค‘์•™๊ฐ’ 40์ผ\n", " ์˜คํ† ์ธ์ฝ”๋”(1ํšŒ ์ดˆ๊ณผ) ํƒ์ง€์œจ 80.1% ์˜ค๊ฒฝ๋ณด์œจ 1.2% ๊ณ ๋ถ€ํ•˜์ผ ์˜ค๊ฒฝ๋ณด 0.6% ๊ณ ์žฅ ์ „ ํ™•๋ณด ์ค‘์•™๊ฐ’ 66์ผ\n", " ์˜คํ† ์ธ์ฝ”๋”(2์ผ ์—ฐ์†) ํƒ์ง€์œจ 79.2% ์˜ค๊ฒฝ๋ณด์œจ 0.0% ๊ณ ๋ถ€ํ•˜์ผ ์˜ค๊ฒฝ๋ณด 0.0% ๊ณ ์žฅ ์ „ ํ™•๋ณด ์ค‘์•™๊ฐ’ 56์ผ\n", " ๋ฏธ์„ธ ๋ณ€ํ™” ๊ตฌ๊ฐ„(์—ดํ™” 0.1~0.3) ํƒ์ง€์œจ: Baseline 2.0% -> ์˜คํ† ์ธ์ฝ”๋” 32.4%\n", "\n", "[RUL ๋ชจ๋ธ] ๊ฐœ์ฒด ๋‹จ์œ„ ์‹œ๊ณ„์—ด ๊ต์ฐจ๊ฒ€์ฆ ํด๋“œ๋ณ„ MAPE: 26.7%, 24.2%, 17.1% -> ํ‰๊ท  22.7% (๋ชฉํ‘œ 25% ์ดํ•˜: ๋‹ฌ์„ฑ)\n", " ๊ฒ€์ฆ 24๋Œ€ MAPE: ์›๋ณธ ์„ผ์„œ 11๊ฐœ 43.3% -> ํŒŒ์ƒ ํฌํ•จ 25๊ฐœ 20.4% (์—ดํ™” ๊ตฌ๊ฐ„, ์ž”์กด 10์ผ ์ด์ƒ)\n", "ํ”ผ์ฒ˜ ์ค‘์š”๋„ ์ƒ์œ„ 5: hf_ratio(11.8), temp_res(7.0), b150_m3(3.8), b165_m3(2.8), press_m3(2.5)\n", "์ฐจ์› ์ถ•์†Œ 25๊ฐœ -> 8๊ฐœ: ๊ฒ€์ฆ MAPE 20.4% -> 20.8%\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 05. ๋ถ€ํ’ˆ ๊ณ ์žฅ ์ž„๋ฐ• ์‹œ์ (RUL) ์˜ˆ์ธก ๋ฐ ์ƒํƒœ ๊ธฐ๋ฐ˜ ์ด์ƒ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์—”์ง„ ๊ฐœ๋ฐœ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋ถ€ํ’ˆ ๊ณ ์žฅ ์ž„๋ฐ• ์‹œ์ (RUL) ์˜ˆ์ธก ๋ฐ ์ƒํƒœ ์ด์ƒ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ถ€ํ’ˆ ๊ณ ์žฅ ์ž„๋ฐ• ์‹œ์ (RUL)์„ ์˜ˆ์ธกํ•˜๋Š” ์•™์ƒ๋ธ” ๋ชจ๋ธ๊ณผ ์ƒํƒœ ์ด์ƒ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ทธ๋ž˜๋””์–ธํŠธ ๋ถ€์ŠคํŒ…, ๋žœ๋ค ํฌ๋ ˆ์ŠคํŠธ, ๋ฆฟ์ง€๋ฅผ ํ‰๊ท ํ•˜๋Š” ์•™์ƒ๋ธ”์„ ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ ํƒ์ƒ‰์œผ๋กœ ๋งŒ๋“ค์–ด RMS์™€ ์˜จ๋„๋งŒ ์“ฐ๋Š” ์„ ํ˜• ๊ธฐ์ค€ ๋ชจ๋ธ๊ณผ Hold-out ๊ฒ€์ฆ ๊ฐœ์ฒด์—์„œ MAE, RMSE, R2๋ฅผ ๋น„๊ต ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/mnt04_advanced_model.py` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๋น„์ •ํ˜•/๋ถˆ๊ทœ์น™ ์ˆ˜์š”์— ๋Œ€ํ•œ ๋ชจ๋ธ์˜ ์ ์‘๋ ฅ ๋ฐ ์˜ˆ์ธก ์•ˆ์ •์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๊ธฐ์ค€ ๋ชจ๋ธ ๋Œ€๋น„ ์˜ค์ฐจ์œจ(MAE/RMSE) 20% ์ด์ƒ ๊ฐœ์„ , R2 Score 0.85 ์ด์ƒ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ๋ชจ๋ธ ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ์™„๋ฃŒ ๋ฐ ์ตœ์  ๋ชจ๋ธ ๊ฐ€์ค‘์น˜ ์ €์žฅ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: Hold-out ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ์…‹ ๋Œ€์ƒ ์ตœ์ข… ์„ฑ๋Šฅ ํ‰๊ฐ€\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์ง„๋™ยท์˜จ๋„ยท์••๋ ฅ ํŠน์ง•์œผ๋กœ ๋ถ€ํ’ˆ ๊ณ ์žฅ ์ž„๋ฐ• ์‹œ์ (RUL)์„ ์˜ˆ์ธกํ•˜๋Š” ์•™์ƒ๋ธ”(๊ทธ๋ž˜๋””์–ธํŠธ ๋ถ€์ŠคํŒ…+๋žœ๋ค ํฌ๋ ˆ์ŠคํŠธ+๋ฆฟ์ง€) ๋ชจ๋ธ์„ ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ ํƒ์ƒ‰์œผ๋กœ ๋งŒ๋“ค์–ด ๋‹จ์ˆœ ์„ ํ˜• ๊ธฐ์ค€ ๋ชจ๋ธ๊ณผ MAEยทRMSEยทR2๋ฅผ ๋น„๊ตํ•˜๊ณ , ์˜คํ† ์ธ์ฝ”๋” ์ด์ƒ ์ ์ˆ˜์™€ ์˜ˆ์ธก ๊ตฌ๊ฐ„์œผ๋กœ ์ƒํƒœ ์ด์ƒ ์‹ ๋ขฐ๋„ ์ ์ˆ˜๋ฅผ ์‚ฐ์ถœํ•ด ์‹ ๋ขฐ๋„ ๊ตฌ๊ฐ„๋ณ„ ์‹ค์ œ ์ •ํ™•๋„์™€ ์ ๊ฒ€ ์นด๋“œ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "from scipy.stats import kurtosis\n", "from sklearn.ensemble import GradientBoostingRegressor, RandomForestRegressor\n", "from sklearn.linear_model import LinearRegression, Ridge\n", "from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score\n", "from sklearn.neural_network import MLPRegressor\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๊ธฐ๋™์ฒด 48๋Œ€์˜ ์—”์ง„ยท๊ตฌ๋™๊ณ„ ์ง„๋™ยท์˜จ๋„ยท์••๋ ฅ ์„ผ์„œ ์ž๋ฃŒ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๊ฐœ์ฒด๋งˆ๋‹ค ์ •์ƒ -> ์—ดํ™” -> ๊ณ ์žฅ(์ˆ˜๋ช…์ข…๋ฃŒ)๊นŒ์ง€ ํ•˜๋ฃจ 1ํšŒ ์ธก์ •(์ง„๋™ ํŒŒํ˜• 256ํ‘œ๋ณธ + ์˜จ๋„ + ์••๋ ฅ + ๋ถ€ํ•˜)์„ ๋งŒ๋“ ๋‹ค.\n", "# ์—ดํ™” ์œ ํ˜• 4๊ฐ€์ง€: bearing(๋ฒ ์–ด๋ง), gear(๊ธฐ์–ด), oil_pump(์˜ค์ผ ํŽŒํ”„), cooling(๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ)\n", "# ---------------------------------------------------------------------------\n", "FS, NS = 1024, 256 # ๊ฐ€์ƒ ํ‘œ๋ณธํ™” ์ฃผํŒŒ์ˆ˜(Hz), ํŒŒํ˜• 1๊ฐœ๋‹น ํ‘œ๋ณธ ์ˆ˜\n", "TT = np.arange(NS) / FS\n", "MODES = { # ์—ดํ™” ์œ ํ˜•: (๋ฐœ์ƒ ํ™•๋ฅ , ํ‰๊ท  ์ˆ˜๋ช…(์ผ), ๊ต์ฒด ์ˆ˜๋ฆฌ๋ถ€์†, ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜)\n", " \"bearing\": (0.40, 160, \"์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ\", 6.0), \"gear\": (0.25, 175, \"๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ\", 9.0),\n", " \"oil_pump\": (0.20, 150, \"์˜ค์ผ ํŽŒํ”„\", 4.0), \"cooling\": (0.15, 140, \"๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ\", 3.0)}\n", "KIT_COST = 1.0 # ๋ชจ๋“  ์ •๋น„์— ํ•จ๊ป˜ ์†Œ๋ชจ๋˜๋Š” ์”ฐยทํ•„ํ„ฐ ํ‚คํŠธ ๋‹จ๊ฐ€์ง€์ˆ˜\n", "BANDS = {\"b50\": (40, 60), \"b100\": (90, 110), \"b150\": (140, 156), \"b165\": (156, 176), \"b200\": (190, 215)}\n", "\n", "\n", "def make_runs(n=48, seed=1):\n", " \"\"\"๋ฐ˜ํ™˜: units(๊ฐœ์ฒด ์ •๋ณด), snap(์ผ๋ณ„ ์ธก์ • ์š”์•ฝ), W(์ง„๋™ ํŒŒํ˜• [์ธก์ • ์ˆ˜, 256])\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(MODES)\n", " mode = rng.choice(names, n, p=[MODES[m][0] for m in names])\n", " life = np.clip(np.array([rng.normal(MODES[m][1], 18) for m in mode]), 90, 230).astype(int)\n", " start = np.sort(rng.integers(0, 240, n))\n", " units = pd.DataFrame({\"unit\": [f\"U{i:02d}\" for i in range(n)], \"mode\": mode, \"life\": life, \"start\": start, \"base_load\": rng.uniform(0.4, 0.75, n)})\n", " rows = []\n", " for u in units.itertuples():\n", " d = np.arange(u.life + 1)\n", " onset = 0.45 * u.life\n", " deg = np.clip((d - onset) / (u.life - onset), 0, 1) ** 1.5\n", " load = np.clip(u.base_load + rng.normal(0, 0.06, len(d)), 0.2, 1.0)\n", " hi = rng.random(len(d)) < 0.07 # ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ(์ •์ƒ์ด์ง€๋งŒ ์ง„๋™ยท์˜จ๋„ ์ƒ์Šน)\n", " load = np.where(hi, rng.uniform(0.95, 1.1, len(d)), load)\n", " rows.append(pd.DataFrame({\"unit\": u.unit, \"day\": d, \"cal\": u.start + d, \"rul\": u.life - d, \"deg\": deg, \"load\": load, \"hi_load\": hi, \"mode\": u.mode}))\n", " snap = pd.concat(rows, ignore_index=True)\n", " S = len(snap)\n", " snap[\"ambient\"] = 15 + 12 * np.sin(2 * np.pi * snap[\"cal\"] / 365) + rng.normal(0, 2, S)\n", " dg, ld, md = snap[\"deg\"].values, snap[\"load\"].values, snap[\"mode\"].values\n", " isb, isg, iso, isc = (md == \"bearing\"), (md == \"gear\"), (md == \"oil_pump\"), (md == \"cooling\")\n", " snap[\"temp\"] = 55 + 22 * ld + 0.5 * snap[\"ambient\"] + 9 * dg * iso + 26 * dg * isc + 3 * dg * (isb | isg) + rng.normal(0, 1.5, S)\n", " snap[\"press\"] = 450 - 12 * ld - 85 * dg * iso - 15 * dg * isc + rng.normal(0, 6, S)\n", " ph = rng.uniform(0, 2 * np.pi, (S, 3))\n", " a1, a2 = 1.0 * (0.5 + ld), 0.4 * (0.5 + ld)\n", " W = (a1[:, None] * np.sin(2 * np.pi * 50 * TT + ph[:, :1]) + a2[:, None] * np.sin(2 * np.pi * 100 * TT + ph[:, 1:2])\n", " + rng.normal(0, 0.35, (S, NS)))\n", " W += (0.9 * dg * isb)[:, None] * np.sin(2 * np.pi * 165 * TT + ph[:, 2:3])\n", " W += (0.8 * dg * isg)[:, None] * (np.sin(2 * np.pi * 150 * TT + ph[:, 2:3]) + 0.8 * np.sin(2 * np.pi * 100 * TT))\n", " W += (0.5 * dg * iso)[:, None] * np.sin(2 * np.pi * 200 * TT + ph[:, 2:3])\n", " k = rng.poisson(10 * dg * isb) # ๋ฒ ์–ด๋ง ์—ดํ™”: ์ถฉ๊ฒฉ์„ฑ ํŽ„์Šค(์ฒจ๋„ ์ƒ์Šน)\n", " pos, amp = rng.integers(0, NS, (S, 30)), rng.normal(0, 3.0, (S, 30)) * dg[:, None]\n", " amp = amp * (np.arange(30)[None, :] < k[:, None])\n", " np.add.at(W, (np.repeat(np.arange(S), 30), pos.ravel()), amp.ravel())\n", " return units, snap, W\n", "\n", "\n", "def extract(W):\n", " \"\"\"์ง„๋™ ํŒŒํ˜• -> ํŠน์ง•๋Ÿ‰: RMS, ์ฒจ๋„, ํŒŒ๊ณ ์œจ, ์ตœ๋Œ€-์ตœ์†Œ, FFT ๋Œ€์—ญ ์—๋„ˆ์ง€(50/100/150/165/200Hz ๋ถ€๊ทผ)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] ๋ถ€ํ’ˆ ๊ณ ์žฅ ์ž„๋ฐ• ์‹œ์ (RUL) ์˜ˆ์ธก ๋ฐ ์ƒํƒœ ๊ธฐ๋ฐ˜ ์ด์ƒ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์—”์ง„ ๊ฐœ๋ฐœ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "BASE = [\"rms\", \"kurt\", \"crest\", \"p2p\", \"b50\", \"b100\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "ROLL = [\"rms\", \"kurt\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "\n", "\n", "def build_features(snap, W):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] ๋ถ€ํ’ˆ ๊ณ ์žฅ ์ž„๋ฐ• ์‹œ์ (RUL) ์˜ˆ์ธก ๋ฐ ์ƒํƒœ ๊ธฐ๋ฐ˜ ์ด์ƒ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์—”์ง„ ๊ฐœ๋ฐœ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "DERIVED = [\"temp_res\", \"press_res\", \"rms_n\", \"hf_ratio\"] + [c + \"_m3\" for c in ROLL] + [\"rms_n_d\", \"temp_res_d\", \"press_res_d\"]\n", "FEATS = BASE + DERIVED\n", "AE_COLS = [\"rms_n\", \"kurt\", \"b150\", \"b165\", \"b200\", \"hf_ratio\", \"temp_res\", \"press_res\"]\n", "\n", "\n", "def split_units(units, n_train=24):\n", " return list(units[\"unit\"][:n_train]), list(units[\"unit\"][n_train:])\n", "\n", "\n", "class AE:\n", " \"\"\"์˜คํ† ์ธ์ฝ”๋”(์ž…๋ ฅ 8 -> 10 -> 4 -> 10 -> 8): ์ •์ƒ ์ž๋ฃŒ๋งŒ ์žฌ๊ตฌ์„ฑํ•˜๋„๋ก ํ•™์Šตํ•˜๊ณ , ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ๋ฅผ ์ด์ƒ ์ ์ˆ˜๋กœ ์“ด๋‹ค.\"\"\"\n", "\n", " def __init__(self, cols=AE_COLS, q=0.99, seed=0):\n", " self.cols, self.q, self.seed = cols, q, seed\n", "\n", " def fit(self, healthy):\n", " self.sc = StandardScaler().fit(healthy[self.cols])\n", " Z = self.sc.transform(healthy[self.cols])\n", " self.net = MLPRegressor(hidden_layer_sizes=(10, 4, 10), max_iter=800, random_state=self.seed).fit(Z, Z)\n", " self.thr = float(np.quantile(self.err(healthy), self.q))\n", " return self\n", "\n", " def contrib(self, d):\n", " Z = self.sc.transform(d[self.cols])\n", " return (Z - self.net.predict(Z)) ** 2 # ์ž…๋ ฅ ํ•ญ๋ชฉ๋ณ„ ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ(์ด์ƒ ๊ธฐ์—ฌ๋„)\n", "\n", " def err(self, d):\n", " return self.contrib(d).mean(axis=1)\n", "\n", "\n", "def latch(d, e, thr, k=2):\n", " \"\"\"๊ฐ™์€ ๊ฐœ์ฒด์—์„œ ์ด์ƒ ์ ์ˆ˜๊ฐ€ k์ผ ์—ฐ์† ๊ธฐ์ค€์„ ๋„˜์œผ๋ฉด ๊ฒฝ๋ณด๋ฅผ ์ผœ๊ณ  ์œ ์ง€(๋ž˜์น˜)ํ•œ๋‹ค.\"\"\"\n", " out, e = np.zeros(len(d), bool), np.asarray(e)\n", " for idx in d.reset_index(drop=True).groupby(\"unit\").indices.values():\n", " run, on = 0, False\n", " for i in idx:\n", " run = run + 1 if e[i] > thr else 0\n", " on = on or run >= k\n", " out[i] = on\n", " return out\n", "\n", "CAP = 120\n", "units, snap, W = make_runs()\n", "D = build_features(snap, W)\n", "D[\"hf_max\"] = D.groupby(\"unit\")[\"hf_ratio\"].cummax() # ์ง€๊ธˆ๊นŒ์ง€ ๊ฐ€์žฅ ๋†’์•˜๋˜ ๊ณ ์ฃผํŒŒ ๋น„(์—ดํ™”๋Š” ๋˜๋Œ์•„๊ฐ€์ง€ ์•Š๋Š”๋‹ค)\n", "D[\"temp_res_max\"] = D.groupby(\"unit\")[\"temp_res\"].cummax()\n", "FEATS = FEATS + [\"day\", \"hf_max\", \"temp_res_max\"] # ์šด์šฉ์ผ์ˆ˜(day)๋Š” ์ •๋น„ ์ด๋ ฅ์—์„œ ์•„๋Š” ๊ฐ’\n", "tr_u, va_u = split_units(units)\n", "tr, va = D[D[\"unit\"].isin(tr_u)].reset_index(drop=True), D[D[\"unit\"].isin(va_u)].reset_index(drop=True)\n", "ytr, yva = np.minimum(tr[\"rul\"], CAP), np.minimum(va[\"rul\"], CAP)\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•™์Šต {len(tr_u)}๋Œ€ {len(tr)}๊ฑด, Hold-out ๊ฒ€์ฆ {len(va_u)}๋Œ€ {len(va)}๊ฑด (RUL์€ {CAP}์ผ ์ƒํ•œ)\")\n", "\n", "# 1) ๊ธฐ์ค€ ๋ชจ๋ธ: ์ง„๋™ RMS์™€ ์˜จ๋„๋งŒ ์“ฐ๋Š” ์„ ํ˜• ํšŒ๊ท€\n", "base = LinearRegression().fit(tr[[\"rms\", \"temp\"]], ytr)\n", "pb = base.predict(va[[\"rms\", \"temp\"]])\n", "\n", "# 2) ๊ณ ๋„ํ™”: ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ ํƒ์ƒ‰(ํ•™์Šต ๊ฐœ์ฒด์˜ ๋’ค์ชฝ 6๋Œ€๋ฅผ ๋‚ด๋ถ€ ๊ฒ€์ฆ) ํ›„ ์•™์ƒ๋ธ”\n", "inner_tr, inner_va = tr[tr[\"unit\"].isin(tr_u[:18])], tr[tr[\"unit\"].isin(tr_u[18:])]\n", "\n", "\n", "def ens(d_tr, y, d_va, depth, alpha):\n", " ms = [GradientBoostingRegressor(n_estimators=100, max_depth=depth, learning_rate=0.1, subsample=0.8, random_state=0),\n", " RandomForestRegressor(n_estimators=40, max_depth=10, min_samples_leaf=5, n_jobs=-1, random_state=0),\n", " make_pipeline(StandardScaler(), Ridge(alpha=alpha))]\n", " for m in ms:\n", " m.fit(d_tr[FEATS], y)\n", " return ms, np.mean([m.predict(d_va[FEATS]) for m in ms], axis=0)\n", "\n", "\n", "score = {(dp, al): mean_absolute_error(np.minimum(inner_va[\"rul\"], CAP), ens(inner_tr, np.minimum(inner_tr[\"rul\"], CAP), inner_va, dp, al)[1])\n", " for dp in (3, 4) for al in (1, 10)}\n", "dp, al = min(score, key=score.get)\n", "models, pe = ens(tr, ytr, va, dp, al)\n", "names = [\"GBR\", \"RF\", \"Ridge\"]\n", "print(f\"์„ ํƒ ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ: GBR depth={dp}, ridge alpha={al} (๋‚ด๋ถ€ ๊ฒ€์ฆ MAE {score[(dp, al)]:.1f}์ผ)\")\n", "\n", "\n", "def metrics(p):\n", " return mean_absolute_error(yva, p), mean_squared_error(yva, p) ** 0.5, r2_score(yva, p)\n", "\n", "\n", "mb, me = metrics(pb), metrics(pe)\n", "print(\"\\n[RUL ์˜ˆ์ธก ์„ฑ๋Šฅ] ๋ชจ๋ธ | MAE(์ผ) | RMSE(์ผ) | R2\")\n", "print(f\" ๊ธฐ์ค€(RMS+์˜จ๋„ ์„ ํ˜•) {mb[0]:6.2f} {mb[1]:6.2f} {mb[2]:.3f}\")\n", "for n, m in zip(names, models):\n", " x = metrics(m.predict(va[FEATS]))\n", " print(f\" ๋‹จ์ผ {n:12s} {x[0]:6.2f} {x[1]:6.2f} {x[2]:.3f}\")\n", "print(f\" ๊ณ ๋„ํ™”(์•™์ƒ๋ธ”) {me[0]:6.2f} {me[1]:6.2f} {me[2]:.3f}\")\n", "imp_mae, imp_rmse = 1 - me[0] / mb[0], 1 - me[1] / mb[1]\n", "print(f\"๊ฐœ์„ ์œจ MAE {imp_mae:.1%}, RMSE {imp_rmse:.1%} (๋ชฉํ‘œ 20% ์ด์ƒ: {'๋‹ฌ์„ฑ' if min(imp_mae, imp_rmse) >= 0.2 else '๋ฏธ๋‹ฌ'}), R2 {me[2]:.3f} (๋ชฉํ‘œ 0.85 ์ด์ƒ: {'๋‹ฌ์„ฑ' if me[2] >= 0.85 else '๋ฏธ๋‹ฌ'})\")\n", "for lo, hi, nm in [(0, 30, \"๊ณ ์žฅ 30์ผ ์ด๋‚ด\"), (30, 90, \"30~90์ผ\"), (90, 9999, \"90์ผ ์ด์ƒ\")]:\n", " m = (va[\"rul\"] >= lo) & (va[\"rul\"] < hi)\n", " print(f\" ๊ตฌ๊ฐ„ {nm:10s} MAE {mean_absolute_error(yva[m], pe[m]):5.1f}์ผ (๊ธฐ์ค€ {mean_absolute_error(yva[m], pb[m]):5.1f}์ผ)\")\n", "\n", "# 3) ์ƒํƒœ ์ด์ƒ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ = ์˜คํ† ์ธ์ฝ”๋” ์ด์ƒ ์ ์ˆ˜(์ •์ƒ ๋Œ€๋น„ ๋ฐฐ์ˆ˜)์™€ ์•™์ƒ๋ธ” ๊ตฌ์„ฑ ๋ชจ๋ธ ๊ฐ„ ์˜๊ฒฌ ์ผ์น˜๋„๋ฅผ ๊ฒฐํ•ฉ\n", "ae = AE().fit(tr[tr[\"deg\"] == 0])\n", "e = ae.err(va)\n", "z = np.log(e / ae.thr)\n", "conf = 1 / (1 + np.exp(-2.5 * z)) # ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ๊ฐ€ ๊ธฐ์ค€๊ณผ ๊ฐ™์œผ๋ฉด 0.5\n", "spread = np.std([m.predict(va[FEATS]) for m in models], axis=0)\n", "agree = np.clip(1 - spread / 40, 0, 1) # ๋ชจ๋ธ ๊ฐ„ RUL ์˜๊ฒฌ ์ฐจ์ด๊ฐ€ ์ž‘์„์ˆ˜๋ก ๋†’์Œ\n", "score_c = conf * (0.7 + 0.3 * agree)\n", "truth = va[\"deg\"] >= 0.1\n", "print(\"\\n[์ƒํƒœ ์ด์ƒ ์‹ ๋ขฐ๋„] ๊ตฌ๊ฐ„ | ๊ฑด์ˆ˜ | ํ‰๊ท  ์ ์ˆ˜ | ์‹ค์ œ ์—ดํ™” ๋น„์œจ\")\n", "bins = [0, 0.2, 0.5, 0.8, 1.01]\n", "for a, b in zip(bins[:-1], bins[1:]):\n", " m = (score_c >= a) & (score_c < b)\n", " print(f\" {a:.1f}~{min(b, 1):.1f} {m.sum():5d} {score_c[m].mean():.2f} {truth[m].mean():.2f}\")\n", "ece = sum(((score_c >= a) & (score_c < b)).mean() * abs(score_c[(score_c >= a) & (score_c < b)].mean() - truth[(score_c >= a) & (score_c < b)].mean())\n", " for a, b in zip(bins[:-1], bins[1:]) if ((score_c >= a) & (score_c < b)).any())\n", "print(f\"๊ธฐ๋Œ€ ๋ณด์ • ์˜ค์ฐจ(ECE) {ece:.3f}\")\n", "\n", "# 4) ์ ๊ฒ€ ์นด๋“œ ์˜ˆ์‹œ(๊ฐœ์ฒด ํ•˜๋‚˜์˜ ์ตœ๊ทผ ์ƒํƒœ)\n", "print(\"\\n[์ ๊ฒ€ ์นด๋“œ ์˜ˆ์‹œ] ๊ฐœ์ฒด | ์ธก์ •์ผ | ์ด์ƒ ์‹ ๋ขฐ๋„ | ์˜ˆ์ธก RUL(์ผ) | ์‹ค์ œ RUL(์ผ)\")\n", "va[\"conf\"], va[\"rul_hat\"] = score_c, pe\n", "for u in va_u[:3]:\n", " g = va[va[\"unit\"] == u]\n", " r = g[g[\"rul\"] == 40].iloc[0] if (g[\"rul\"] == 40).any() else g.iloc[-1]\n", " print(f\" {u} {int(r['day']):3d}์ผ์งธ {r['conf']:.2f} {r['rul_hat']:5.1f} {int(r['rul'])} -> {'์ •๋น„ ๊ณ„ํš ์ˆ˜๋ฆฝ ๊ถŒ๊ณ ' if r['conf'] >= 0.8 else '์ถ”๊ฐ€ ๊ด€์ฐฐ'}(๊ฒ€์‚ฌ๊ด€ ํ™•์ธ)\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 05 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•™์Šต 24๋Œ€ 3847๊ฑด, Hold-out ๊ฒ€์ฆ 24๋Œ€ 3886๊ฑด (RUL์€ 120์ผ ์ƒํ•œ)\n", "์„ ํƒ ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ: GBR depth=4, ridge alpha=1 (๋‚ด๋ถ€ ๊ฒ€์ฆ MAE 6.8์ผ)\n", "\n", "[RUL ์˜ˆ์ธก ์„ฑ๋Šฅ] ๋ชจ๋ธ | MAE(์ผ) | RMSE(์ผ) | R2\n", " ๊ธฐ์ค€(RMS+์˜จ๋„ ์„ ํ˜•) 35.67 41.43 -0.078\n", " ๋‹จ์ผ GBR 9.45 13.37 0.888\n", " ๋‹จ์ผ RF 8.00 12.31 0.905\n", " ๋‹จ์ผ Ridge 9.98 12.78 0.897\n", " ๊ณ ๋„ํ™”(์•™์ƒ๋ธ”) 8.56 12.05 0.909\n", "๊ฐœ์„ ์œจ MAE 76.0%, RMSE 70.9% (๋ชฉํ‘œ 20% ์ด์ƒ: ๋‹ฌ์„ฑ), R2 0.909 (๋ชฉํ‘œ 0.85 ์ด์ƒ: ๋‹ฌ์„ฑ)\n", " ๊ตฌ๊ฐ„ ๊ณ ์žฅ 30์ผ ์ด๋‚ด MAE 5.7์ผ (๊ธฐ์ค€ 44.8์ผ)\n", " ๊ตฌ๊ฐ„ 30~90์ผ MAE 10.2์ผ (๊ธฐ์ค€ 16.9์ผ)\n", " ๊ตฌ๊ฐ„ 90์ผ ์ด์ƒ MAE 8.4์ผ (๊ธฐ์ค€ 47.5์ผ)\n", "\n", "[์ƒํƒœ ์ด์ƒ ์‹ ๋ขฐ๋„] ๊ตฌ๊ฐ„ | ๊ฑด์ˆ˜ | ํ‰๊ท  ์ ์ˆ˜ | ์‹ค์ œ ์—ดํ™” ๋น„์œจ\n", " 0.0~0.2 2242 0.04 0.10\n", " 0.2~0.5 282 0.31 0.41\n", " 0.5~0.8 161 0.65 0.86\n", " 0.8~1.0 1201 0.95 1.00\n", "๊ธฐ๋Œ€ ๋ณด์ • ์˜ค์ฐจ(ECE) 0.065\n", "\n", "[์ ๊ฒ€ ์นด๋“œ ์˜ˆ์‹œ] ๊ฐœ์ฒด | ์ธก์ •์ผ | ์ด์ƒ ์‹ ๋ขฐ๋„ | ์˜ˆ์ธก RUL(์ผ) | ์‹ค์ œ RUL(์ผ)\n", " U24 94์ผ์งธ 0.01 56.0 40 -> ์ถ”๊ฐ€ ๊ด€์ฐฐ(๊ฒ€์‚ฌ๊ด€ ํ™•์ธ)\n", " U25 95์ผ์งธ 0.62 56.6 40 -> ์ถ”๊ฐ€ ๊ด€์ฐฐ(๊ฒ€์‚ฌ๊ด€ ํ™•์ธ)\n", " U26 148์ผ์งธ 0.92 28.0 40 -> ์ •๋น„ ๊ณ„ํš ์ˆ˜๋ฆฝ ๊ถŒ๊ณ (๊ฒ€์‚ฌ๊ด€ ํ™•์ธ)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 06. ์žฅ๋น„ ์„ผ์„œ ์ƒํƒœ ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๊ฒฐํ•จ ์ง•ํ›„ ๊ฒฝ๋ณด ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์žฅ๋น„ ์„ผ์„œ ์ƒํƒœ ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๊ฒฐํ•จ ๊ฒฝ๋ณด ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์žฅ๋น„ ์„ผ์„œ ์ƒํƒœ๋ฅผ ๊ฐœ์ฒด๋ณ„๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•ด ๊ฒฐํ•จ ๊ฒฝ๋ณด๋ฅผ ๋„์šฐ๋Š” ์ •๋น„๋ณ‘์šฉ ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•˜๊ณ , ๊ฒฝ๋ณด ๊ฑด๋งˆ๋‹ค ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ธ์ž์™€ ์ž์—ฐ์–ด ์š”์•ฝ์„ ์ž๋™ ์‚ฐ์ถœํ•˜๋Š” ์„ค๋ช…๊ฐ€๋Šฅ์„ฑ(XAI) ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์˜คํ† ์ธ์ฝ”๋” ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ๋ฅผ ์ž…๋ ฅ ์ธ์ž๋ณ„๋กœ ๋‚˜๋ˆ„์–ด ๊ฒฝ๋ณด ๊ฑด๋งˆ๋‹ค ์ƒ์œ„ 3๊ฐœ ์ธ์ž๋ฅผ ๋ฝ‘๊ณ , 1์ˆœ์œ„ ์ธ์ž๋ฅผ ์ •์ƒ๊ฐ’์œผ๋กœ ๋˜๋Œ๋ ธ์„ ๋•Œ ์ ์ˆ˜๊ฐ€ ๋‚ด๋ ค๊ฐ€๋Š”์ง€๋กœ ๊ทผ๊ฑฐ ์œ ํšจ์„ฑ์„ ์ ๊ฒ€ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/explainability/mnt04_xai_explainer.py ๋ฐ XAI ๋ฆฌํฌํŠธ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ์žฅ๊ต/๊ตฐ๋ฌด์›์ด ์‹ ๋ขฐํ•˜๊ณ  ์กฐ์น˜ํ•  ์ˆ˜ ์žˆ๋Š” ์„ค๋ช…์˜ ๋ช…ํ™•์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์ฃผ์š” ์˜ˆ์ธก ๊ฑด๋‹น ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ธ์ž ์ž๋™ ์‚ฐ์ถœ๋ฅ  100%\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: SHAP summary plot ๋ฐ waterfall plot ์ƒ์„ฑ ํŒŒ์ดํ”„๋ผ์ธ ์™„์„ฑ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ์‹ค๋ฌด์ž ๋Œ€์ƒ ์„ค๋ช… ๊ทผ๊ฑฐ ์œ ์šฉ์„ฑ ํ‰๊ฐ€ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์žฅ๋น„ ์„ผ์„œ ์ƒํƒœ๋ฅผ ๊ฐœ์ฒด๋ณ„๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•ด ์ด์ƒ ์‹ ํ˜ธ๋ฅผ ๊ฒฝ๋ณด๋กœ ๋„์šฐ๋Š” ์ •๋น„๋ณ‘์šฉ ํ™”๋ฉด(์ƒํƒœํ‘œยท์ ๊ฒ€ ์นด๋“œ)์„ ๊ตฌ์„ฑํ•˜๊ณ , ์˜คํ† ์ธ์ฝ”๋” ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ์—์„œ ๊ฒฝ๋ณด ๊ฑด๋งˆ๋‹ค ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ธ์ž๋ฅผ ์ž๋™ ์‚ฐ์ถœํ•˜๋ฉฐ(๊ทผ๊ฑฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ), ์˜์‹ฌ ๋ถ€ํ’ˆ๊ณผ ์ž์—ฐ์–ด ์š”์•ฝ ๋ฌธ์žฅ์„ ์ƒ์„ฑํ•˜๋Š” ์„ค๋ช…๊ฐ€๋Šฅ์„ฑ(XAI) ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.stats import kurtosis\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "from sklearn.neural_network import MLPRegressor\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๊ธฐ๋™์ฒด 48๋Œ€์˜ ์—”์ง„ยท๊ตฌ๋™๊ณ„ ์ง„๋™ยท์˜จ๋„ยท์••๋ ฅ ์„ผ์„œ ์ž๋ฃŒ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๊ฐœ์ฒด๋งˆ๋‹ค ์ •์ƒ -> ์—ดํ™” -> ๊ณ ์žฅ(์ˆ˜๋ช…์ข…๋ฃŒ)๊นŒ์ง€ ํ•˜๋ฃจ 1ํšŒ ์ธก์ •(์ง„๋™ ํŒŒํ˜• 256ํ‘œ๋ณธ + ์˜จ๋„ + ์••๋ ฅ + ๋ถ€ํ•˜)์„ ๋งŒ๋“ ๋‹ค.\n", "# ์—ดํ™” ์œ ํ˜• 4๊ฐ€์ง€: bearing(๋ฒ ์–ด๋ง), gear(๊ธฐ์–ด), oil_pump(์˜ค์ผ ํŽŒํ”„), cooling(๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ)\n", "# ---------------------------------------------------------------------------\n", "FS, NS = 1024, 256 # ๊ฐ€์ƒ ํ‘œ๋ณธํ™” ์ฃผํŒŒ์ˆ˜(Hz), ํŒŒํ˜• 1๊ฐœ๋‹น ํ‘œ๋ณธ ์ˆ˜\n", "TT = np.arange(NS) / FS\n", "MODES = { # ์—ดํ™” ์œ ํ˜•: (๋ฐœ์ƒ ํ™•๋ฅ , ํ‰๊ท  ์ˆ˜๋ช…(์ผ), ๊ต์ฒด ์ˆ˜๋ฆฌ๋ถ€์†, ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜)\n", " \"bearing\": (0.40, 160, \"์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ\", 6.0), \"gear\": (0.25, 175, \"๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ\", 9.0),\n", " \"oil_pump\": (0.20, 150, \"์˜ค์ผ ํŽŒํ”„\", 4.0), \"cooling\": (0.15, 140, \"๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ\", 3.0)}\n", "KIT_COST = 1.0 # ๋ชจ๋“  ์ •๋น„์— ํ•จ๊ป˜ ์†Œ๋ชจ๋˜๋Š” ์”ฐยทํ•„ํ„ฐ ํ‚คํŠธ ๋‹จ๊ฐ€์ง€์ˆ˜\n", "BANDS = {\"b50\": (40, 60), \"b100\": (90, 110), \"b150\": (140, 156), \"b165\": (156, 176), \"b200\": (190, 215)}\n", "\n", "\n", "def make_runs(n=48, seed=1):\n", " \"\"\"๋ฐ˜ํ™˜: units(๊ฐœ์ฒด ์ •๋ณด), snap(์ผ๋ณ„ ์ธก์ • ์š”์•ฝ), W(์ง„๋™ ํŒŒํ˜• [์ธก์ • ์ˆ˜, 256])\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(MODES)\n", " mode = rng.choice(names, n, p=[MODES[m][0] for m in names])\n", " life = np.clip(np.array([rng.normal(MODES[m][1], 18) for m in mode]), 90, 230).astype(int)\n", " start = np.sort(rng.integers(0, 240, n))\n", " units = pd.DataFrame({\"unit\": [f\"U{i:02d}\" for i in range(n)], \"mode\": mode, \"life\": life, \"start\": start, \"base_load\": rng.uniform(0.4, 0.75, n)})\n", " rows = []\n", " for u in units.itertuples():\n", " d = np.arange(u.life + 1)\n", " onset = 0.45 * u.life\n", " deg = np.clip((d - onset) / (u.life - onset), 0, 1) ** 1.5\n", " load = np.clip(u.base_load + rng.normal(0, 0.06, len(d)), 0.2, 1.0)\n", " hi = rng.random(len(d)) < 0.07 # ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ(์ •์ƒ์ด์ง€๋งŒ ์ง„๋™ยท์˜จ๋„ ์ƒ์Šน)\n", " load = np.where(hi, rng.uniform(0.95, 1.1, len(d)), load)\n", " rows.append(pd.DataFrame({\"unit\": u.unit, \"day\": d, \"cal\": u.start + d, \"rul\": u.life - d, \"deg\": deg, \"load\": load, \"hi_load\": hi, \"mode\": u.mode}))\n", " snap = pd.concat(rows, ignore_index=True)\n", " S = len(snap)\n", " snap[\"ambient\"] = 15 + 12 * np.sin(2 * np.pi * snap[\"cal\"] / 365) + rng.normal(0, 2, S)\n", " dg, ld, md = snap[\"deg\"].values, snap[\"load\"].values, snap[\"mode\"].values\n", " isb, isg, iso, isc = (md == \"bearing\"), (md == \"gear\"), (md == \"oil_pump\"), (md == \"cooling\")\n", " snap[\"temp\"] = 55 + 22 * ld + 0.5 * snap[\"ambient\"] + 9 * dg * iso + 26 * dg * isc + 3 * dg * (isb | isg) + rng.normal(0, 1.5, S)\n", " snap[\"press\"] = 450 - 12 * ld - 85 * dg * iso - 15 * dg * isc + rng.normal(0, 6, S)\n", " ph = rng.uniform(0, 2 * np.pi, (S, 3))\n", " a1, a2 = 1.0 * (0.5 + ld), 0.4 * (0.5 + ld)\n", " W = (a1[:, None] * np.sin(2 * np.pi * 50 * TT + ph[:, :1]) + a2[:, None] * np.sin(2 * np.pi * 100 * TT + ph[:, 1:2])\n", " + rng.normal(0, 0.35, (S, NS)))\n", " W += (0.9 * dg * isb)[:, None] * np.sin(2 * np.pi * 165 * TT + ph[:, 2:3])\n", " W += (0.8 * dg * isg)[:, None] * (np.sin(2 * np.pi * 150 * TT + ph[:, 2:3]) + 0.8 * np.sin(2 * np.pi * 100 * TT))\n", " W += (0.5 * dg * iso)[:, None] * np.sin(2 * np.pi * 200 * TT + ph[:, 2:3])\n", " k = rng.poisson(10 * dg * isb) # ๋ฒ ์–ด๋ง ์—ดํ™”: ์ถฉ๊ฒฉ์„ฑ ํŽ„์Šค(์ฒจ๋„ ์ƒ์Šน)\n", " pos, amp = rng.integers(0, NS, (S, 30)), rng.normal(0, 3.0, (S, 30)) * dg[:, None]\n", " amp = amp * (np.arange(30)[None, :] < k[:, None])\n", " np.add.at(W, (np.repeat(np.arange(S), 30), pos.ravel()), amp.ravel())\n", " return units, snap, W\n", "\n", "\n", "def extract(W):\n", " \"\"\"์ง„๋™ ํŒŒํ˜• -> ํŠน์ง•๋Ÿ‰: RMS, ์ฒจ๋„, ํŒŒ๊ณ ์œจ, ์ตœ๋Œ€-์ตœ์†Œ, FFT ๋Œ€์—ญ ์—๋„ˆ์ง€(50/100/150/165/200Hz ๋ถ€๊ทผ)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ์žฅ๋น„ ์„ผ์„œ ์ƒํƒœ ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๊ฒฐํ•จ ์ง•ํ›„ ๊ฒฝ๋ณด ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "BASE = [\"rms\", \"kurt\", \"crest\", \"p2p\", \"b50\", \"b100\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "ROLL = [\"rms\", \"kurt\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "\n", "\n", "def build_features(snap, W):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ์žฅ๋น„ ์„ผ์„œ ์ƒํƒœ ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๊ฒฐํ•จ ์ง•ํ›„ ๊ฒฝ๋ณด ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "DERIVED = [\"temp_res\", \"press_res\", \"rms_n\", \"hf_ratio\"] + [c + \"_m3\" for c in ROLL] + [\"rms_n_d\", \"temp_res_d\", \"press_res_d\"]\n", "FEATS = BASE + DERIVED\n", "AE_COLS = [\"rms_n\", \"kurt\", \"b150\", \"b165\", \"b200\", \"hf_ratio\", \"temp_res\", \"press_res\"]\n", "\n", "\n", "def split_units(units, n_train=24):\n", " return list(units[\"unit\"][:n_train]), list(units[\"unit\"][n_train:])\n", "\n", "\n", "class AE:\n", " \"\"\"์˜คํ† ์ธ์ฝ”๋”(์ž…๋ ฅ 8 -> 10 -> 4 -> 10 -> 8): ์ •์ƒ ์ž๋ฃŒ๋งŒ ์žฌ๊ตฌ์„ฑํ•˜๋„๋ก ํ•™์Šตํ•˜๊ณ , ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ๋ฅผ ์ด์ƒ ์ ์ˆ˜๋กœ ์“ด๋‹ค.\"\"\"\n", "\n", " def __init__(self, cols=AE_COLS, q=0.99, seed=0):\n", " self.cols, self.q, self.seed = cols, q, seed\n", "\n", " def fit(self, healthy):\n", " self.sc = StandardScaler().fit(healthy[self.cols])\n", " Z = self.sc.transform(healthy[self.cols])\n", " self.net = MLPRegressor(hidden_layer_sizes=(10, 4, 10), max_iter=800, random_state=self.seed).fit(Z, Z)\n", " self.thr = float(np.quantile(self.err(healthy), self.q))\n", " return self\n", "\n", " def contrib(self, d):\n", " Z = self.sc.transform(d[self.cols])\n", " return (Z - self.net.predict(Z)) ** 2 # ์ž…๋ ฅ ํ•ญ๋ชฉ๋ณ„ ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ(์ด์ƒ ๊ธฐ์—ฌ๋„)\n", "\n", " def err(self, d):\n", " return self.contrib(d).mean(axis=1)\n", "\n", "\n", "def latch(d, e, thr, k=2):\n", " \"\"\"๊ฐ™์€ ๊ฐœ์ฒด์—์„œ ์ด์ƒ ์ ์ˆ˜๊ฐ€ k์ผ ์—ฐ์† ๊ธฐ์ค€์„ ๋„˜์œผ๋ฉด ๊ฒฝ๋ณด๋ฅผ ์ผœ๊ณ  ์œ ์ง€(๋ž˜์น˜)ํ•œ๋‹ค.\"\"\"\n", " out, e = np.zeros(len(d), bool), np.asarray(e)\n", " for idx in d.reset_index(drop=True).groupby(\"unit\").indices.values():\n", " run, on = 0, False\n", " for i in idx:\n", " run = run + 1 if e[i] > thr else 0\n", " on = on or run >= k\n", " out[i] = on\n", " return out\n", "\n", "units, snap, W = make_runs()\n", "D = build_features(snap, W)\n", "tr_u, va_u = split_units(units)\n", "tr, va = D[D[\"unit\"].isin(tr_u)].reset_index(drop=True), D[D[\"unit\"].isin(va_u)].reset_index(drop=True)\n", "ae = AE().fit(tr[tr[\"deg\"] == 0])\n", "C = ae.contrib(va) # ๊ฑด x ์ž…๋ ฅํ•ญ๋ชฉ ๊ธฐ์—ฌ๋„\n", "e = C.mean(axis=1)\n", "va[\"alarm\"] = latch(va, e, ae.thr, k=2)\n", "va[\"score\"] = e / ae.thr\n", "\n", "LABEL = {\"rms_n\": \"๋ถ€ํ•˜ ๋ณด์ • ์ง„๋™ ํฌ๊ธฐ(RMS)\", \"kurt\": \"์ง„๋™ ์ถฉ๊ฒฉ์„ฑ(์ฒจ๋„)\", \"b150\": \"150Hz ๋ถ€๊ทผ ์ง„๋™(๊ธฐ์–ด ๋งž๋ฌผ๋ฆผ ๋Œ€์—ญ)\", \"b165\": \"165Hz ๋ถ€๊ทผ ์ง„๋™(๋ฒ ์–ด๋ง ๊ฒฐํ•จ ๋Œ€์—ญ)\",\n", " \"b200\": \"200Hz ๋ถ€๊ทผ ์ง„๋™(ํŽŒํ”„ ๋Œ€์—ญ)\", \"hf_ratio\": \"๊ณ ์ฃผํŒŒ/๊ธฐ๋ณธ์ฃผํŒŒ ์—๋„ˆ์ง€ ๋น„\", \"temp_res\": \"๋ถ€ํ•˜ ๋Œ€๋น„ ์˜จ๋„ ์ƒ์Šน\", \"press_res\": \"๋ถ€ํ•˜ ๋Œ€๋น„ ์••๋ ฅ ์ €ํ•˜\"}\n", "PART = {\"b165\": \"์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ\", \"kurt\": \"์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ\", \"b150\": \"๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ\", \"b200\": \"์˜ค์ผ ํŽŒํ”„\", \"press_res\": \"์˜ค์ผ ํŽŒํ”„\", \"temp_res\": \"๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ\"}\n", "TRUE_PART = {m: MODES[m][2] for m in MODES}\n", "cols = ae.cols\n", "\n", "# 1) ๊ฒฝ๋ณด ๊ฑด๋งˆ๋‹ค ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ธ์ž ์ž๋™ ์‚ฐ์ถœ\n", "idx = np.where(va[\"alarm\"].values)[0]\n", "top3 = np.argsort(-C[idx], axis=1)[:, :3]\n", "ok = sum(len(set(t)) == 3 for t in top3)\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฒ€์ฆ {len(va_u)}๋Œ€ {len(va)}๊ฑด ์ค‘ ๊ฒฝ๋ณด {len(idx)}๊ฑด: ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ธ์ž ์‚ฐ์ถœ {ok}๊ฑด (์‚ฐ์ถœ๋ฅ  {ok / len(idx):.0%}, ๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if ok == len(idx) else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# 2) ๊ทผ๊ฑฐ ์œ ํšจ์„ฑ: 1์ˆœ์œ„ ์ธ์ž๋ฅผ ์ •์ƒ ํ‰๊ท (ํ‘œ์ค€ํ™” 0)์œผ๋กœ ๋˜๋Œ๋ฆฌ๋ฉด ์ด์ƒ ์ ์ˆ˜๊ฐ€ ๋‚ฎ์•„์ง€๋Š”๊ฐ€\n", "Z = ae.sc.transform(va[cols].iloc[idx])\n", "Z2 = Z.copy()\n", "Z2[np.arange(len(idx)), top3[:, 0]] = 0.0\n", "e1, e2 = ((Z - ae.net.predict(Z)) ** 2).mean(axis=1), ((Z2 - ae.net.predict(Z2)) ** 2).mean(axis=1)\n", "print(f\"๊ทผ๊ฑฐ ์œ ํšจ์„ฑ: 1์ˆœ์œ„ ์ธ์ž๋ฅผ ์ •์ƒ๊ฐ’์œผ๋กœ ๋˜๋Œ๋ฆฌ๋ฉด ์ด์ƒ ์ ์ˆ˜๊ฐ€ ๋‚ฎ์•„์ง€๋Š” ๋น„์œจ {(e2 < e1).mean():.1%} (ํ‰๊ท  {e1.mean():.2f} -> {e2.mean():.2f})\")\n", "\n", "# 3) ์˜์‹ฌ ๋ถ€ํ’ˆ: 1์ˆœ์œ„ ์ธ์ž -> ๋ถ€ํ’ˆ ๋งคํ•‘ (์—ดํ™”๊ฐ€ ๋šœ๋ ทํ•œ ๊ฒฝ๋ณด์—์„œ ์ •๋‹ต ๋น„๊ต)\n", "sub = va.iloc[idx].copy()\n", "sub[\"top1\"] = [cols[t[0]] for t in top3]\n", "sub[\"hint\"] = sub[\"top1\"].map(PART).fillna(\"๋ฏธ๋ถ„๋ฅ˜(์ ๊ฒ€ ํ•„์š”)\")\n", "sub[\"true_part\"] = sub[\"mode\"].map(TRUE_PART)\n", "strong = sub[sub[\"deg\"] >= 0.3]\n", "print(f\"์˜์‹ฌ ๋ถ€ํ’ˆ ์ถ”์ • ์ •ํ™•๋„(์—ดํ™” 0.3 ์ด์ƒ ๊ฒฝ๋ณด {len(strong)}๊ฑด): {(strong['hint'] == strong['true_part']).mean():.1%}\")\n", "print(\" 1์ˆœ์œ„ ์ธ์ž ๋ถ„ํฌ: \" + \", \".join(f\"{LABEL[k].split('(')[0]} {v}\" for k, v in sub[\"top1\"].value_counts().head(4).items()))\n", "\n", "\n", "def summarize(r, t):\n", " f = [LABEL[cols[i]] for i in t]\n", " part = PART.get(cols[t[0]], \"์›์ธ ๋ถˆ๋ช…\")\n", " return f\"{part} ์ด์ƒ ๊ฐ€๋Šฅ์„ฑ(์ด์ƒ ์ ์ˆ˜ {r['score']:.1f}๋ฐฐ). ์ฃผ์š” ๊ทผ๊ฑฐ: {f[0]}, {f[1]}, {f[2]}.\"\n", "\n", "\n", "# 4) ์ •๋น„๋ณ‘์šฉ ํ™”๋ฉด: ์ ๊ฒ€ ์นด๋“œ์™€ ์ƒํƒœํ‘œ\n", "print(\"\\n[์ ๊ฒ€ ์นด๋“œ] ๊ฐœ์ฒด | ์ธก์ •์ผ | ์‹ค์ œ ์—ดํ™” ์œ ํ˜•\")\n", "for m in MODES:\n", " k = sub[(sub[\"mode\"] == m) & (sub[\"deg\"] > 0.5)].index[0]\n", " j = list(sub.index).index(k)\n", " r = sub.loc[k]\n", " print(f\" {r['unit']} {int(r['day'])}์ผ์งธ ({m})\")\n", " print(\" \" + summarize(r, top3[j]))\n", " print(\" ๊ทผ๊ฑฐ ๊ธฐ์—ฌ๋„: \" + \" / \".join(f\"{LABEL[cols[i]].split('(')[0]}({C[idx[j], i]:.1f})\" for i in top3[j]))\n", "\n", "CAL = int(va.groupby('cal')['alarm'].sum().idxmax()) # ๊ฒฝ๋ณด๊ฐ€ ๊ฐ€์žฅ ๋งŽ์€ ๋‚ ์„ ๊ฐ€์ƒ ๊ธฐ์ค€์ผ๋กœ ์“ด๋‹ค\n", "act = va[(va[\"cal\"] == CAL)]\n", "reg = {\"์ •์ƒ\": 0, \"์ฃผ์˜\": 0, \"๊ฒฝ๋ณด\": 0}\n", "rows = []\n", "for r in act.itertuples():\n", " st = \"๊ฒฝ๋ณด\" if r.alarm else (\"์ฃผ์˜\" if r.score > 0.7 else \"์ •์ƒ\")\n", " reg[st] += 1\n", "for st in (\"๊ฒฝ๋ณด\", \"์ฃผ์˜\"):\n", " for r in act[(act[\"alarm\"] if st == \"๊ฒฝ๋ณด\" else (~act[\"alarm\"] & (act[\"score\"] > 0.7)))].itertuples():\n", " rows.append((r.unit, st, f\"{r.score:.1f}\", int(r.rul)))\n", "print(f\"\\n[์šด์šฉ ์ƒํƒœํ‘œ] ๊ฐ€์ƒ ๊ธฐ์ค€์ผ {CAL}: ๊ฐ€๋™ ์ค‘ {len(act)}๋Œ€ -> \" + \", \".join(f\"{k} {v}๋Œ€\" for k, v in reg.items()))\n", "for r in rows[:5]:\n", " print(f\" {r[0]} {r[1]} ์ด์ƒ ์ ์ˆ˜ {r[2]}๋ฐฐ (์‹ค์ œ ์ž”์กด {r[3]}์ผ)\")\n", "\n", "plt.figure(figsize=(8, 4))\n", "for u in sub[\"unit\"].unique()[:3]:\n", " g = va[va[\"unit\"] == u]\n", " plt.plot(g[\"day\"], g[\"score\"], label=u)\n", "plt.axhline(1.0, ls=\"--\", color=\"k\"); plt.yscale(\"log\"); plt.xlabel(\"day\"); plt.ylabel(\"anomaly score (x threshold)\"); plt.legend(); plt.title(\"Monitoring view: anomaly score\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 06 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฒ€์ฆ 24๋Œ€ 3886๊ฑด ์ค‘ ๊ฒฝ๋ณด 1327๊ฑด: ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ธ์ž ์‚ฐ์ถœ 1327๊ฑด (์‚ฐ์ถœ๋ฅ  100%, ๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ)\n", "๊ทผ๊ฑฐ ์œ ํšจ์„ฑ: 1์ˆœ์œ„ ์ธ์ž๋ฅผ ์ •์ƒ๊ฐ’์œผ๋กœ ๋˜๋Œ๋ฆฌ๋ฉด ์ด์ƒ ์ ์ˆ˜๊ฐ€ ๋‚ฎ์•„์ง€๋Š” ๋น„์œจ 98.9% (ํ‰๊ท  21.19 -> 6.57)\n", "์˜์‹ฌ ๋ถ€ํ’ˆ ์ถ”์ • ์ •ํ™•๋„(์—ดํ™” 0.3 ์ด์ƒ ๊ฒฝ๋ณด 1167๊ฑด): 99.3%\n", " 1์ˆœ์œ„ ์ธ์ž ๋ถ„ํฌ: 150Hz ๋ถ€๊ทผ ์ง„๋™ 603, 165Hz ๋ถ€๊ทผ ์ง„๋™ 338, ๋ถ€ํ•˜ ๋Œ€๋น„ ์˜จ๋„ ์ƒ์Šน 146, ๋ถ€ํ•˜ ๋Œ€๋น„ ์••๋ ฅ ์ €ํ•˜ 129\n", "\n", "[์ ๊ฒ€ ์นด๋“œ] ๊ฐœ์ฒด | ์ธก์ •์ผ | ์‹ค์ œ ์—ดํ™” ์œ ํ˜•\n", " U27 131์ผ์งธ (bearing)\n", " ์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ ์ด์ƒ ๊ฐ€๋Šฅ์„ฑ(์ด์ƒ ์ ์ˆ˜ 7.1๋ฐฐ). ์ฃผ์š” ๊ทผ๊ฑฐ: 165Hz ๋ถ€๊ทผ ์ง„๋™(๋ฒ ์–ด๋ง ๊ฒฐํ•จ ๋Œ€์—ญ), 150Hz ๋ถ€๊ทผ ์ง„๋™(๊ธฐ์–ด ๋งž๋ฌผ๋ฆผ ๋Œ€์—ญ), ๋ถ€ํ•˜ ๋Œ€๋น„ ์••๋ ฅ ์ €ํ•˜.\n", " ๊ทผ๊ฑฐ ๊ธฐ์—ฌ๋„: 165Hz ๋ถ€๊ทผ ์ง„๋™(36.6) / 150Hz ๋ถ€๊ทผ ์ง„๋™(22.9) / ๋ถ€ํ•˜ ๋Œ€๋น„ ์••๋ ฅ ์ €ํ•˜(5.4)\n", " U26 150์ผ์งธ (gear)\n", " ๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ ์ด์ƒ ๊ฐ€๋Šฅ์„ฑ(์ด์ƒ ์ ์ˆ˜ 10.4๋ฐฐ). ์ฃผ์š” ๊ทผ๊ฑฐ: 150Hz ๋ถ€๊ทผ ์ง„๋™(๊ธฐ์–ด ๋งž๋ฌผ๋ฆผ ๋Œ€์—ญ), 165Hz ๋ถ€๊ทผ ์ง„๋™(๋ฒ ์–ด๋ง ๊ฒฐํ•จ ๋Œ€์—ญ), ์ง„๋™ ์ถฉ๊ฒฉ์„ฑ(์ฒจ๋„).\n", " ๊ทผ๊ฑฐ ๊ธฐ์—ฌ๋„: 150Hz ๋ถ€๊ทผ ์ง„๋™(60.3) / 165Hz ๋ถ€๊ทผ ์ง„๋™(19.9) / ์ง„๋™ ์ถฉ๊ฒฉ์„ฑ(10.9)\n", " U25 108์ผ์งธ (oil_pump)\n", " ์˜ค์ผ ํŽŒํ”„ ์ด์ƒ ๊ฐ€๋Šฅ์„ฑ(์ด์ƒ ์ ์ˆ˜ 6.0๋ฐฐ). ์ฃผ์š” ๊ทผ๊ฑฐ: ๋ถ€ํ•˜ ๋Œ€๋น„ ์••๋ ฅ ์ €ํ•˜, 200Hz ๋ถ€๊ทผ ์ง„๋™(ํŽŒํ”„ ๋Œ€์—ญ), 165Hz ๋ถ€๊ทผ ์ง„๋™(๋ฒ ์–ด๋ง ๊ฒฐํ•จ ๋Œ€์—ญ).\n", " ๊ทผ๊ฑฐ ๊ธฐ์—ฌ๋„: ๋ถ€ํ•˜ ๋Œ€๋น„ ์••๋ ฅ ์ €ํ•˜(29.3) / 200Hz ๋ถ€๊ทผ ์ง„๋™(22.0) / 165Hz ๋ถ€๊ทผ ์ง„๋™(4.7)\n", " U24 107์ผ์งธ (cooling)\n", " ๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ ์ด์ƒ ๊ฐ€๋Šฅ์„ฑ(์ด์ƒ ์ ์ˆ˜ 4.4๋ฐฐ). ์ฃผ์š” ๊ทผ๊ฑฐ: ๋ถ€ํ•˜ ๋Œ€๋น„ ์˜จ๋„ ์ƒ์Šน, 200Hz ๋ถ€๊ทผ ์ง„๋™(ํŽŒํ”„ ๋Œ€์—ญ), ์ง„๋™ ์ถฉ๊ฒฉ์„ฑ(์ฒจ๋„).\n", " ๊ทผ๊ฑฐ ๊ธฐ์—ฌ๋„: ๋ถ€ํ•˜ ๋Œ€๋น„ ์˜จ๋„ ์ƒ์Šน(34.5) / 200Hz ๋ถ€๊ทผ ์ง„๋™(3.9) / ์ง„๋™ ์ถฉ๊ฒฉ์„ฑ(2.4)\n", "\n", "[์šด์šฉ ์ƒํƒœํ‘œ] ๊ฐ€์ƒ ๊ธฐ์ค€์ผ 308: ๊ฐ€๋™ ์ค‘ 18๋Œ€ -> ์ •์ƒ 6๋Œ€, ์ฃผ์˜ 1๋Œ€, ๊ฒฝ๋ณด 11๋Œ€\n", " U26 ๊ฒฝ๋ณด ์ด์ƒ ์ ์ˆ˜ 30.5๋ฐฐ (์‹ค์ œ ์ž”์กด 1์ผ)\n", " U28 ๊ฒฝ๋ณด ์ด์ƒ ์ ์ˆ˜ 55.0๋ฐฐ (์‹ค์ œ ์ž”์กด 5์ผ)\n", " U31 ๊ฒฝ๋ณด ์ด์ƒ ์ ์ˆ˜ 85.1๋ฐฐ (์‹ค์ œ ์ž”์กด 1์ผ)\n", " U32 ๊ฒฝ๋ณด ์ด์ƒ ์ ์ˆ˜ 12.9๋ฐฐ (์‹ค์ œ ์ž”์กด 14์ผ)\n", " U33 ๊ฒฝ๋ณด ์ด์ƒ ์ ์ˆ˜ 27.4๋ฐฐ (์‹ค์ œ ์ž”์กด 14์ผ)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 07. ์˜ˆ๋ฐฉ ์ •๋น„ ์กฐ๊ธฐ ์ž…๊ณ  ๊ฒฐ์ •์„ ์œ„ํ•œ ํ•ญ๊ณต/๊ธฐ๊ฐ‘ ๊ฒ€์‚ฌ๊ด€ HITL ์Šน์ธ ๋ชจ๋“ˆ ์„ค๊ณ„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์˜ˆ๋ฐฉ ์ •๋น„ ์กฐ๊ธฐ ์ž…๊ณ  ๊ฒฐ์ •์„ ์œ„ํ•œ ๊ฒ€์‚ฌ๊ด€ HITL ์Šน์ธ ๋ชจ๋“ˆ์„ ์„ค๊ณ„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์„ผ์„œ ๊ฒฝ๋ณด ์‹œ ์˜ˆ๋ฐฉ ์ •๋น„ ์กฐ๊ธฐ ์ž…๊ณ ๋ฅผ ์ œ์•ˆํ•˜๋˜ ํ•ญ๊ณต/๊ธฐ๊ฐ‘ ๊ฒ€์‚ฌ๊ด€์ด ์Šน์ธํ•ด์•ผ ํ™•์ •๋˜๋Š” HITL ์Šน์ธ ๋ชจ๋“ˆ์„ ์„ค๊ณ„ํ•˜๊ณ  ํšจ๊ณผ๋ฅผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์œผ๋กœ ๊ฒ€์ฆํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ฒฝ๋ณด์™€ RUL ์˜ˆ์ธก์œผ๋กœ ์ž…๊ณ ์ผ์„ ์ œ์•ˆํ•˜๊ณ  ์Šน์ธ ๋Œ€๊ธฐ์—ด์— ์˜ฌ๋ ค ๊ฒ€์‚ฌ๊ด€์˜ ์Šน์ธยท๋ฐ˜๋ คยท๋ฌด์‘๋‹ต(๊ธฐ๋ณธ ๊ณ„ํš์ธ ์ •๊ธฐ ๊ต์ฒด ์œ ์ง€)์„ ์‚ฌ์œ ์™€ ํ•จ๊ป˜ ํ•ด์‹œ ์ฒด์ธ ๊ฐ์‚ฌ ๋กœ๊ทธ๋กœ ๋‚จ๊ธด๋‹ค ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/optimization/mnt04_simulator.py` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ํ˜„์‹ค์  ๊ตฐ์ˆ˜ ์ œ์•ฝ์กฐ๊ฑด ๋ฐ˜์˜๋„ ๋ฐ ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ ๋Œ€์•ˆ ์ œ์‹œ ์ ์ ˆ์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์ตœ์ ํ™” ์ ์šฉ ์‹œ ๋น„์šฉ 15% ์ ˆ๊ฐ ๋˜๋Š” ์žฌ๊ณ  ํšŒ์ „์œจ 20% ํ–ฅ์ƒ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์„ ํ˜•/์ •์ˆ˜ ๊ณ„ํš๋ฒ•(LP/MIP) ์ตœ์ ํ™” ํ•ด ๋„์ถœ ๋ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์™„๋ฃŒ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๊ณผ๊ฑฐ ์‹ค์  ๋ฐ์ดํ„ฐ ๋Œ€๋น„ ์ตœ์ ํ™” ๊ฒฐ๊ณผ ๋น„๊ต ๋ฐฑํ…Œ์ŠคํŒ…\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์„ผ์„œ ์ด์ƒ ๊ฒฝ๋ณด๊ฐ€ ๋‚˜๋ฉด ์˜ˆ๋ฐฉ ์ •๋น„ ์กฐ๊ธฐ ์ž…๊ณ ์ผ์„ ์ œ์•ˆํ•˜๋˜ ์ž๋™ ํ™•์ •ํ•˜์ง€ ์•Š๊ณ  ํ•ญ๊ณต/๊ธฐ๊ฐ‘ ๊ฒ€์‚ฌ๊ด€ ์Šน์ธ ๋Œ€๊ธฐ์—ด์— ์˜ฌ๋ ค ์Šน์ธยท๋ฐ˜๋ คยท๋ฌด์‘๋‹ต(๊ธฐ๋ณธ ๊ณ„ํš ์œ ์ง€)์„ ์‚ฌ์œ ์™€ ํ•จ๊ป˜ ํ•ด์‹œ ์ฒด์ธ ๊ฐ์‚ฌ ๋กœ๊ทธ์— ๋‚จ๊ธฐ๋Š” HITL ์Šน์ธ ๋ชจ๋“ˆ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•˜๊ณ , ์ •๊ธฐ ๊ต์ฒด ๋ฐฉ์‹ ๋Œ€๋น„ ์ •๋น„ยท๊ณ ์žฅ ๋น„์šฉ ์ ˆ๊ฐ๋ฅ ๊ณผ ์Šน์ธ ์ง€์—ฐยท์ „์‹œ ์ „ํ™˜(๊ฐ€๋™ ๊ฐ•๋„ ๊ธ‰์ฆ) ๋ฏผ๊ฐ๋„๋ฅผ ๋ถ„์„ํ•œ๋‹ค.\"\"\"\n", "import hashlib\n", "import json\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.stats import kurtosis\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "from sklearn.neural_network import MLPRegressor\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๊ธฐ๋™์ฒด 48๋Œ€์˜ ์—”์ง„ยท๊ตฌ๋™๊ณ„ ์ง„๋™ยท์˜จ๋„ยท์••๋ ฅ ์„ผ์„œ ์ž๋ฃŒ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๊ฐœ์ฒด๋งˆ๋‹ค ์ •์ƒ -> ์—ดํ™” -> ๊ณ ์žฅ(์ˆ˜๋ช…์ข…๋ฃŒ)๊นŒ์ง€ ํ•˜๋ฃจ 1ํšŒ ์ธก์ •(์ง„๋™ ํŒŒํ˜• 256ํ‘œ๋ณธ + ์˜จ๋„ + ์••๋ ฅ + ๋ถ€ํ•˜)์„ ๋งŒ๋“ ๋‹ค.\n", "# ์—ดํ™” ์œ ํ˜• 4๊ฐ€์ง€: bearing(๋ฒ ์–ด๋ง), gear(๊ธฐ์–ด), oil_pump(์˜ค์ผ ํŽŒํ”„), cooling(๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ)\n", "# ---------------------------------------------------------------------------\n", "FS, NS = 1024, 256 # ๊ฐ€์ƒ ํ‘œ๋ณธํ™” ์ฃผํŒŒ์ˆ˜(Hz), ํŒŒํ˜• 1๊ฐœ๋‹น ํ‘œ๋ณธ ์ˆ˜\n", "TT = np.arange(NS) / FS\n", "MODES = { # ์—ดํ™” ์œ ํ˜•: (๋ฐœ์ƒ ํ™•๋ฅ , ํ‰๊ท  ์ˆ˜๋ช…(์ผ), ๊ต์ฒด ์ˆ˜๋ฆฌ๋ถ€์†, ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜)\n", " \"bearing\": (0.40, 160, \"์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ\", 6.0), \"gear\": (0.25, 175, \"๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ\", 9.0),\n", " \"oil_pump\": (0.20, 150, \"์˜ค์ผ ํŽŒํ”„\", 4.0), \"cooling\": (0.15, 140, \"๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ\", 3.0)}\n", "KIT_COST = 1.0 # ๋ชจ๋“  ์ •๋น„์— ํ•จ๊ป˜ ์†Œ๋ชจ๋˜๋Š” ์”ฐยทํ•„ํ„ฐ ํ‚คํŠธ ๋‹จ๊ฐ€์ง€์ˆ˜\n", "BANDS = {\"b50\": (40, 60), \"b100\": (90, 110), \"b150\": (140, 156), \"b165\": (156, 176), \"b200\": (190, 215)}\n", "\n", "\n", "def make_runs(n=48, seed=1):\n", " \"\"\"๋ฐ˜ํ™˜: units(๊ฐœ์ฒด ์ •๋ณด), snap(์ผ๋ณ„ ์ธก์ • ์š”์•ฝ), W(์ง„๋™ ํŒŒํ˜• [์ธก์ • ์ˆ˜, 256])\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(MODES)\n", " mode = rng.choice(names, n, p=[MODES[m][0] for m in names])\n", " life = np.clip(np.array([rng.normal(MODES[m][1], 18) for m in mode]), 90, 230).astype(int)\n", " start = np.sort(rng.integers(0, 240, n))\n", " units = pd.DataFrame({\"unit\": [f\"U{i:02d}\" for i in range(n)], \"mode\": mode, \"life\": life, \"start\": start, \"base_load\": rng.uniform(0.4, 0.75, n)})\n", " rows = []\n", " for u in units.itertuples():\n", " d = np.arange(u.life + 1)\n", " onset = 0.45 * u.life\n", " deg = np.clip((d - onset) / (u.life - onset), 0, 1) ** 1.5\n", " load = np.clip(u.base_load + rng.normal(0, 0.06, len(d)), 0.2, 1.0)\n", " hi = rng.random(len(d)) < 0.07 # ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ(์ •์ƒ์ด์ง€๋งŒ ์ง„๋™ยท์˜จ๋„ ์ƒ์Šน)\n", " load = np.where(hi, rng.uniform(0.95, 1.1, len(d)), load)\n", " rows.append(pd.DataFrame({\"unit\": u.unit, \"day\": d, \"cal\": u.start + d, \"rul\": u.life - d, \"deg\": deg, \"load\": load, \"hi_load\": hi, \"mode\": u.mode}))\n", " snap = pd.concat(rows, ignore_index=True)\n", " S = len(snap)\n", " snap[\"ambient\"] = 15 + 12 * np.sin(2 * np.pi * snap[\"cal\"] / 365) + rng.normal(0, 2, S)\n", " dg, ld, md = snap[\"deg\"].values, snap[\"load\"].values, snap[\"mode\"].values\n", " isb, isg, iso, isc = (md == \"bearing\"), (md == \"gear\"), (md == \"oil_pump\"), (md == \"cooling\")\n", " snap[\"temp\"] = 55 + 22 * ld + 0.5 * snap[\"ambient\"] + 9 * dg * iso + 26 * dg * isc + 3 * dg * (isb | isg) + rng.normal(0, 1.5, S)\n", " snap[\"press\"] = 450 - 12 * ld - 85 * dg * iso - 15 * dg * isc + rng.normal(0, 6, S)\n", " ph = rng.uniform(0, 2 * np.pi, (S, 3))\n", " a1, a2 = 1.0 * (0.5 + ld), 0.4 * (0.5 + ld)\n", " W = (a1[:, None] * np.sin(2 * np.pi * 50 * TT + ph[:, :1]) + a2[:, None] * np.sin(2 * np.pi * 100 * TT + ph[:, 1:2])\n", " + rng.normal(0, 0.35, (S, NS)))\n", " W += (0.9 * dg * isb)[:, None] * np.sin(2 * np.pi * 165 * TT + ph[:, 2:3])\n", " W += (0.8 * dg * isg)[:, None] * (np.sin(2 * np.pi * 150 * TT + ph[:, 2:3]) + 0.8 * np.sin(2 * np.pi * 100 * TT))\n", " W += (0.5 * dg * iso)[:, None] * np.sin(2 * np.pi * 200 * TT + ph[:, 2:3])\n", " k = rng.poisson(10 * dg * isb) # ๋ฒ ์–ด๋ง ์—ดํ™”: ์ถฉ๊ฒฉ์„ฑ ํŽ„์Šค(์ฒจ๋„ ์ƒ์Šน)\n", " pos, amp = rng.integers(0, NS, (S, 30)), rng.normal(0, 3.0, (S, 30)) * dg[:, None]\n", " amp = amp * (np.arange(30)[None, :] < k[:, None])\n", " np.add.at(W, (np.repeat(np.arange(S), 30), pos.ravel()), amp.ravel())\n", " return units, snap, W\n", "\n", "\n", "def extract(W):\n", " \"\"\"์ง„๋™ ํŒŒํ˜• -> ํŠน์ง•๋Ÿ‰: RMS, ์ฒจ๋„, ํŒŒ๊ณ ์œจ, ์ตœ๋Œ€-์ตœ์†Œ, FFT ๋Œ€์—ญ ์—๋„ˆ์ง€(50/100/150/165/200Hz ๋ถ€๊ทผ)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 7] ์˜ˆ๋ฐฉ ์ •๋น„ ์กฐ๊ธฐ ์ž…๊ณ  ๊ฒฐ์ •์„ ์œ„ํ•œ ํ•ญ๊ณต/๊ธฐ๊ฐ‘ ๊ฒ€์‚ฌ๊ด€ HITL ์Šน์ธ ๋ชจ๋“ˆ ์„ค๊ณ„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "BASE = [\"rms\", \"kurt\", \"crest\", \"p2p\", \"b50\", \"b100\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "ROLL = [\"rms\", \"kurt\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "\n", "\n", "def build_features(snap, W):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 7] ์˜ˆ๋ฐฉ ์ •๋น„ ์กฐ๊ธฐ ์ž…๊ณ  ๊ฒฐ์ •์„ ์œ„ํ•œ ํ•ญ๊ณต/๊ธฐ๊ฐ‘ ๊ฒ€์‚ฌ๊ด€ HITL ์Šน์ธ ๋ชจ๋“ˆ ์„ค๊ณ„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "DERIVED = [\"temp_res\", \"press_res\", \"rms_n\", \"hf_ratio\"] + [c + \"_m3\" for c in ROLL] + [\"rms_n_d\", \"temp_res_d\", \"press_res_d\"]\n", "FEATS = BASE + DERIVED\n", "AE_COLS = [\"rms_n\", \"kurt\", \"b150\", \"b165\", \"b200\", \"hf_ratio\", \"temp_res\", \"press_res\"]\n", "\n", "\n", "def split_units(units, n_train=24):\n", " return list(units[\"unit\"][:n_train]), list(units[\"unit\"][n_train:])\n", "\n", "\n", "class AE:\n", " \"\"\"์˜คํ† ์ธ์ฝ”๋”(์ž…๋ ฅ 8 -> 10 -> 4 -> 10 -> 8): ์ •์ƒ ์ž๋ฃŒ๋งŒ ์žฌ๊ตฌ์„ฑํ•˜๋„๋ก ํ•™์Šตํ•˜๊ณ , ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ๋ฅผ ์ด์ƒ ์ ์ˆ˜๋กœ ์“ด๋‹ค.\"\"\"\n", "\n", " def __init__(self, cols=AE_COLS, q=0.99, seed=0):\n", " self.cols, self.q, self.seed = cols, q, seed\n", "\n", " def fit(self, healthy):\n", " self.sc = StandardScaler().fit(healthy[self.cols])\n", " Z = self.sc.transform(healthy[self.cols])\n", " self.net = MLPRegressor(hidden_layer_sizes=(10, 4, 10), max_iter=800, random_state=self.seed).fit(Z, Z)\n", " self.thr = float(np.quantile(self.err(healthy), self.q))\n", " return self\n", "\n", " def contrib(self, d):\n", " Z = self.sc.transform(d[self.cols])\n", " return (Z - self.net.predict(Z)) ** 2 # ์ž…๋ ฅ ํ•ญ๋ชฉ๋ณ„ ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ(์ด์ƒ ๊ธฐ์—ฌ๋„)\n", "\n", " def err(self, d):\n", " return self.contrib(d).mean(axis=1)\n", "\n", "\n", "def latch(d, e, thr, k=2):\n", " \"\"\"๊ฐ™์€ ๊ฐœ์ฒด์—์„œ ์ด์ƒ ์ ์ˆ˜๊ฐ€ k์ผ ์—ฐ์† ๊ธฐ์ค€์„ ๋„˜์œผ๋ฉด ๊ฒฝ๋ณด๋ฅผ ์ผœ๊ณ  ์œ ์ง€(๋ž˜์น˜)ํ•œ๋‹ค.\"\"\"\n", " out, e = np.zeros(len(d), bool), np.asarray(e)\n", " for idx in d.reset_index(drop=True).groupby(\"unit\").indices.values():\n", " run, on = 0, False\n", " for i in idx:\n", " run = run + 1 if e[i] > thr else 0\n", " on = on or run >= k\n", " out[i] = on\n", " return out\n", "\n", "PERIOD = 90 # ๊ธฐ๋ณธ ๊ณ„ํš: ์ •๊ธฐ ๊ต์ฒด ์ฃผ๊ธฐ(์šด์šฉ์ผ). ๊ฐ€์žฅ ๋น ๋ฅธ ๊ณ ์žฅ ์‚ฌ๋ก€๋ณด๋‹ค ์งง๊ฒŒ ์ •ํ•ด ์‚ฌํ›„ ๊ณ ์žฅ์„ ๋ง‰๋Š” ๋ณด์ˆ˜์  ์ฃผ๊ธฐ\n", "UNPLANNED = (2.0, 12.0) # [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ณ ์žฅ ํ›„ ์ •๋น„ ๋น„์šฉ = ๋ถ€ํ’ˆ ๋‹จ๊ฐ€ x 2.0 + ์—ฐ์‡„ ์†์ƒยท๊ฐ€๋™์ค‘๋‹จ 12.0\n", "PLANNED_FIXED = 1.0 # ์ž…๊ณ  ์ •๋น„ ์ž‘์—… ๊ณ ์ • ๋น„์šฉ(์ง€์ˆ˜)\n", "INSPECT = {\"base\": 0.3, \"pdm\": 0.05} # 30์ผ๋‹น ์ ๊ฒ€ ๋น„์šฉ: ์ •๊ธฐ ๋ฐฉ์‹์€ ๋ถ„ํ•ด ์ ๊ฒ€, ์„ผ์„œ ๊ฐ์‹œ ๋ฐฉ์‹์€ ๊ฒฝ๋ณด ์‹œ ํ™•์ธ ์ ๊ฒ€ ์œ„์ฃผ(๊ฐ€์ •)\n", "\n", "\n", "class Audit:\n", " def __init__(self):\n", " self.log = []\n", "\n", " def add(self, **body):\n", " prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n", " body = {\"seq\": len(self.log), **body}\n", " self.log.append({**body, \"prev\": prev, \"hash\": hashlib.sha256((prev + json.dumps(body, sort_keys=True, ensure_ascii=False)).encode()).hexdigest()})\n", "\n", " def verify(self):\n", " prev = \"GENESIS\"\n", " for e in self.log:\n", " b = {k: v for k, v in e.items() if k not in (\"prev\", \"hash\")}\n", " if e[\"prev\"] != prev or hashlib.sha256((prev + json.dumps(b, sort_keys=True, ensure_ascii=False)).encode()).hexdigest() != e[\"hash\"]:\n", " return e[\"seq\"]\n", " prev = e[\"hash\"]\n", " return None\n", "\n", "\n", "def add_feat(D):\n", " D[\"hf_max\"] = D.groupby(\"unit\")[\"hf_ratio\"].cummax(); D[\"temp_res_max\"] = D.groupby(\"unit\")[\"temp_res\"].cummax()\n", " return D\n", "\n", "\n", "F7 = FEATS + [\"day\", \"hf_max\", \"temp_res_max\"]\n", "u1, s1, w1 = make_runs(seed=1)\n", "D1 = add_feat(build_features(s1, w1))\n", "tr_u, _ = split_units(u1)\n", "tr = D1[D1[\"unit\"].isin(tr_u)]\n", "ae = AE().fit(tr[tr[\"deg\"] == 0])\n", "gbr = GradientBoostingRegressor(n_estimators=100, max_depth=3, learning_rate=0.1, subsample=0.8, random_state=0).fit(tr[F7], np.minimum(tr[\"rul\"], 120))\n", "\n", "# ์‹œํ—˜ ๊ฐœ์ฒด: ๋‹ค๋ฅธ ์‹œ๋“œ๋กœ ๋งŒ๋“  ๊ฐ€์ƒ ๊ฐœ์ฒด 3๋ฌถ์Œ(144๋Œ€)์—์„œ ๊ฐœ์ฒด๋ณ„ ์ฒซ ๊ฒฝ๋ณด์ผ๊ณผ ๊ทธ๋•Œ์˜ RUL ์˜ˆ์ธก์„ ๋ชจ์€๋‹ค\n", "cases = []\n", "for sd in (2, 3, 4):\n", " un, sn, ww = make_runs(seed=sd)\n", " D = add_feat(build_features(sn, ww))\n", " D[\"alarm\"] = latch(D, ae.err(D), ae.thr, k=2)\n", " D[\"rul_hat\"] = gbr.predict(D[F7])\n", " for u in un.itertuples():\n", " g = D[D[\"unit\"] == u.unit]\n", " a = g[g[\"alarm\"]]\n", " cases.append({\"id\": f\"S{sd}-{u.unit}\", \"mode\": u.mode, \"life\": int(u.life), \"a\": int(a[\"day\"].iloc[0]) if len(a) else None,\n", " \"rul_hat\": float(a[\"rul_hat\"].iloc[0]) if len(a) else None, \"load\": float(a[\"load\"].iloc[0]) if len(a) else None})\n", "C = pd.DataFrame(cases)\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์‹œํ—˜ ๊ฐœ์ฒด {len(C)}๋Œ€(์ˆ˜๋ช… {C['life'].min()}~{C['life'].max()}์ผ), ๊ฒฝ๋ณด ๋ฐœ์ƒ {int(C['a'].notna().sum())}๋Œ€\")\n", "\n", "\n", "def cost(mode, life, maint, kind=\"pdm\"):\n", " p = MODES[mode][3] + KIT_COST + INSPECT[kind] * min(maint if maint is not None else life, life) / 30\n", " if maint is None or maint >= life: # ์ •๋น„ ์ „์— ๊ณ ์žฅ\n", " return MODES[mode][3] * UNPLANNED[0] + KIT_COST + UNPLANNED[1] + INSPECT[kind] * life / 30, True\n", " return p + PLANNED_FIXED + MODES[mode][3] * (life - maint) / life, False # ๋‚จ์€ ์ˆ˜๋ช… ๋‚ญ๋น„๋ถ„ ํฌํ•จ\n", "\n", "\n", "def commander(row, rng):\n", " \"\"\"[๋ชจ์˜ ๊ฒ€์‚ฌ๊ด€] ์ž”์กด ์˜ˆ์ธก์ด ๊ธธ๋ฉด ๋ฐ˜๋ ค, 10%๋Š” ๋ฌด์‘๋‹ต.\"\"\"\n", " if rng.random() < 0.10:\n", " return \"๋ฌด์‘๋‹ต\", \"๊ธฐํ•œ ๋‚ด ์‘๋‹ต ์—†์Œ -> ๊ธฐ๋ณธ ๊ณ„ํš(์ •๊ธฐ ๊ต์ฒด) ์œ ์ง€\"\n", " if row[\"rul_hat\"] > 90:\n", " return \"๋ฐ˜๋ ค\", f\"์˜ˆ์ธก ์ž”์กด {row['rul_hat']:.0f}์ผ๋กœ ์ถฉ๋ถ„ -> ํ˜„ ๊ณ„ํš ์œ ์ง€\"\n", " if row[\"load\"] > 1.0:\n", " return \"๋ฐ˜๋ ค\", \"๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ ์‹ ํ˜ธ๋กœ ํŒ๋‹จ -> ์ถ”๊ฐ€ ๊ด€์ฐฐ\"\n", " return \"์Šน์ธ\", f\"์ด์ƒ ์ง€์†, ์˜ˆ์ธก ์ž”์กด {row['rul_hat']:.0f}์ผ -> ์กฐ๊ธฐ ์ž…๊ณ \"\n", "\n", "\n", "def run(mode, delay=2, scale=1.0, seed=0, audit=None):\n", " \"\"\"mode: base(์ •๊ธฐ ๊ต์ฒด) / hitl(์ œ์•ˆ -> ๊ฒ€์‚ฌ๊ด€ ๊ฒฐ์ •) / auto(๋ชจ๋“  ์ œ์•ˆ ์ ์šฉ: ๋น„๊ต์šฉ ์ƒํ•œ, ์‹ค์ œ ์šด์šฉ ๊ธˆ์ง€)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " tot, fails, dec = 0.0, 0, {\"์Šน์ธ\": 0, \"๋ฐ˜๋ ค\": 0, \"๋ฌด์‘๋‹ต\": 0}\n", " for _, r in C.iterrows():\n", " life = r[\"life\"] / scale\n", " maint, kind = PERIOD, \"base\"\n", " if mode != \"base\" and r[\"a\"] is not None:\n", " a, rh = r[\"a\"] / scale, r[\"rul_hat\"] / scale\n", " inbound = a + max(int(0.4 * rh), 3)\n", " row = {\"rul_hat\": rh, \"load\": r[\"load\"]}\n", " if mode == \"auto\":\n", " d, why = \"์Šน์ธ\", \"๋น„๊ต์šฉ ์ „๋ถ€ ์ ์šฉ\"\n", " else:\n", " d, why = commander(row, rng)\n", " audit.add(event=\"PROPOSE\", id=r[\"id\"], day=round(a), inbound=int(inbound), status=\"PENDING\")\n", " audit.add(event=\"DECIDE\", id=r[\"id\"], decision=d, reason=why, by=\"๊ฒ€์‚ฌ๊ด€(๋ชจ์˜)\")\n", " dec[d] += 1\n", " if d == \"์Šน์ธ\":\n", " maint, kind = inbound + (delay if mode == \"hitl\" else 0), \"pdm\"\n", " c, f = cost(r[\"mode\"], life, maint, kind)\n", " tot += c; fails += f\n", " return tot, fails, dec\n", "\n", "\n", "b_cost, b_fail, _ = run(\"base\")\n", "audit = Audit()\n", "h_cost, h_fail, dec = run(\"hitl\", seed=3, audit=audit)\n", "a_cost, a_fail, _ = run(\"auto\")\n", "print(\"\\n[๋น„์šฉ ๋น„๊ต] ๋ฐฉ์‹ | ๋น„์šฉ์ง€์ˆ˜ | ์‚ฌํ›„ ๊ณ ์žฅ ๊ฑด์ˆ˜\")\n", "for nm, c, f in [(\"์ •๊ธฐ ๊ต์ฒด(๊ธฐ๋ณธ ๊ณ„ํš)\", b_cost, b_fail), (\"๊ฒฝ๋ณด ์ œ์•ˆ + ๊ฒ€์‚ฌ๊ด€ ์Šน์ธ\", h_cost, h_fail), (\"์ œ์•ˆ ์ „๋ถ€ ์ ์šฉ(๋น„๊ต์šฉ)\", a_cost, a_fail)]:\n", " print(f\" {nm:22s} {c:8.0f} {f:3d}\")\n", "cut = 1 - h_cost / b_cost\n", "print(f\"HITL ์ ์šฉ ๋น„์šฉ ์ ˆ๊ฐ๋ฅ  {cut:.1%} (๋ชฉํ‘œ 15% ์ด์ƒ: {'๋‹ฌ์„ฑ' if cut >= 0.15 else '๋ฏธ๋‹ฌ'}), ๊ณ ์žฅ {b_fail} -> {h_fail}๊ฑด\")\n", "print(f\"๊ฒฐ์ • {sum(dec.values())}๊ฑด: \" + \", \".join(f\"{k} {v}๊ฑด\" for k, v in dec.items()))\n", "n_prop = sum(e[\"event\"] == \"PROPOSE\" for e in audit.log); n_dec = sum(e[\"event\"] == \"DECIDE\" for e in audit.log)\n", "print(f\"์ œ์•ˆ {n_prop}๊ฑด ์ค‘ ๊ฒฐ์ • ๊ธฐ๋ก {n_dec}๊ฑด(๋กœ๊น…๋ฅ  {n_dec / n_prop:.0%}), ์ž๋™ ์Šน์ธ 0๊ฑด, ๋ฌด์‘๋‹ต ์‹œ ๊ธฐ๋ณธ ๊ณ„ํš ์œ ์ง€\")\n", "rej = [e[\"reason\"] for e in audit.log if e.get(\"decision\") == \"๋ฐ˜๋ ค\"]\n", "print(\"๋ฐ˜๋ ค ์‚ฌ์œ  ์˜ˆ:\", rej[0] if rej else \"-\")\n", "print(f\"๊ฐ์‚ฌ ๋กœ๊ทธ ๋ฌด๊ฒฐ์„ฑ: {'์ •์ƒ' if audit.verify() is None else '์ด์ƒ'}\", end=\", \")\n", "audit.log[5][\"reason\"] = \"์‚ฌํ›„ ์ˆ˜์ •\"\n", "print(f\"์‚ฌํ›„ ๋ณ€์กฐ ์‹œ seq {audit.verify()}์—์„œ ํƒ์ง€\")\n", "\n", "print(\"\\n[๋ฏผ๊ฐ๋„ 1] ์Šน์ธ ํ›„ ์ž…๊ณ ๊นŒ์ง€ ์ง€์—ฐ(์ผ) -> ๋น„์šฉ ์ ˆ๊ฐ๋ฅ \")\n", "for d in (0, 2, 7, 14, 21):\n", " c, f, _ = run(\"hitl\", delay=d, seed=3, audit=Audit())\n", " print(f\" {d:2d}์ผ: {1 - c / b_cost:.1%} (๊ณ ์žฅ {f}๊ฑด)\")\n", "print(\"[๋ฏผ๊ฐ๋„ 2] ๊ฐ€๋™ ๊ฐ•๋„ ๊ธ‰์ฆ(์ „์‹œ ์ „ํ™˜ ๊ฐ€์ •, ์—ดํ™”๊ฐ€ ๋นจ๋ผ์ ธ ์ˆ˜๋ช…์ด 1/๋ฐฐ์ˆ˜๋กœ ๋‹จ์ถ•) -> ์ ˆ๊ฐ๋ฅ \")\n", "for s in (1.0, 1.3, 1.6, 2.0):\n", " bc, bf, _ = run(\"base\", scale=s); hc, hf, _ = run(\"hitl\", scale=s, seed=3, audit=Audit())\n", " print(f\" x{s}: ์ •๊ธฐ ๊ต์ฒด ๋น„์šฉ {bc:.0f}(๊ณ ์žฅ {bf}) -> HITL {hc:.0f}(๊ณ ์žฅ {hf}), ์ ˆ๊ฐ {1 - hc / bc:.1%}\")\n", "\n", "plt.figure(figsize=(6, 4))\n", "plt.bar([\"periodic\", \"HITL\", \"auto(ref)\"], [b_cost, h_cost, a_cost]); plt.ylabel(\"cost index\"); plt.title(\"HITL early-inbound simulator\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 07 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์‹œํ—˜ ๊ฐœ์ฒด 144๋Œ€(์ˆ˜๋ช… 101~212์ผ), ๊ฒฝ๋ณด ๋ฐœ์ƒ 144๋Œ€\n", "\n", "[๋น„์šฉ ๋น„๊ต] ๋ฐฉ์‹ | ๋น„์šฉ์ง€์ˆ˜ | ์‚ฌํ›„ ๊ณ ์žฅ ๊ฑด์ˆ˜\n", " ์ •๊ธฐ ๊ต์ฒด(๊ธฐ๋ณธ ๊ณ„ํš) 1640 0\n", " ๊ฒฝ๋ณด ์ œ์•ˆ + ๊ฒ€์‚ฌ๊ด€ ์Šน์ธ 1389 1\n", " ์ œ์•ˆ ์ „๋ถ€ ์ ์šฉ(๋น„๊ต์šฉ) 1364 1\n", "HITL ์ ์šฉ ๋น„์šฉ ์ ˆ๊ฐ๋ฅ  15.3% (๋ชฉํ‘œ 15% ์ด์ƒ: ๋‹ฌ์„ฑ), ๊ณ ์žฅ 0 -> 1๊ฑด\n", "๊ฒฐ์ • 144๊ฑด: ์Šน์ธ 126๊ฑด, ๋ฐ˜๋ ค 7๊ฑด, ๋ฌด์‘๋‹ต 11๊ฑด\n", "์ œ์•ˆ 144๊ฑด ์ค‘ ๊ฒฐ์ • ๊ธฐ๋ก 144๊ฑด(๋กœ๊น…๋ฅ  100%), ์ž๋™ ์Šน์ธ 0๊ฑด, ๋ฌด์‘๋‹ต ์‹œ ๊ธฐ๋ณธ ๊ณ„ํš ์œ ์ง€\n", "๋ฐ˜๋ ค ์‚ฌ์œ  ์˜ˆ: ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ ์‹ ํ˜ธ๋กœ ํŒ๋‹จ -> ์ถ”๊ฐ€ ๊ด€์ฐฐ\n", "๊ฐ์‚ฌ ๋กœ๊ทธ ๋ฌด๊ฒฐ์„ฑ: ์ •์ƒ, ์‚ฌํ›„ ๋ณ€์กฐ ์‹œ seq 5์—์„œ ํƒ์ง€\n", "\n", "[๋ฏผ๊ฐ๋„ 1] ์Šน์ธ ํ›„ ์ž…๊ณ ๊นŒ์ง€ ์ง€์—ฐ(์ผ) -> ๋น„์šฉ ์ ˆ๊ฐ๋ฅ \n", " 0์ผ: 14.8% (๊ณ ์žฅ 1๊ฑด)\n", " 2์ผ: 15.3% (๊ณ ์žฅ 1๊ฑด)\n", " 7์ผ: 14.1% (๊ณ ์žฅ 4๊ฑด)\n", " 14์ผ: 10.5% (๊ณ ์žฅ 10๊ฑด)\n", " 21์ผ: 1.8% (๊ณ ์žฅ 21๊ฑด)\n", "[๋ฏผ๊ฐ๋„ 2] ๊ฐ€๋™ ๊ฐ•๋„ ๊ธ‰์ฆ(์ „์‹œ ์ „ํ™˜ ๊ฐ€์ •, ์—ดํ™”๊ฐ€ ๋นจ๋ผ์ ธ ์ˆ˜๋ช…์ด 1/๋ฐฐ์ˆ˜๋กœ ๋‹จ์ถ•) -> ์ ˆ๊ฐ๋ฅ \n", " x1.0: ์ •๊ธฐ ๊ต์ฒด ๋น„์šฉ 1640(๊ณ ์žฅ 0) -> HITL 1389(๊ณ ์žฅ 1), ์ ˆ๊ฐ 15.3%\n", " x1.3: ์ •๊ธฐ ๊ต์ฒด ๋น„์šฉ 1571(๊ณ ์žฅ 5) -> HITL 1363(๊ณ ์žฅ 1), ์ ˆ๊ฐ 13.2%\n", " x1.6: ์ •๊ธฐ ๊ต์ฒด ๋น„์šฉ 2005(๊ณ ์žฅ 41) -> HITL 1451(๊ณ ์žฅ 8), ์ ˆ๊ฐ 27.7%\n", " x2.0: ์ •๊ธฐ ๊ต์ฒด ๋น„์šฉ 3394(๊ณ ์žฅ 128) -> HITL 1572(๊ณ ์žฅ 16), ์ ˆ๊ฐ 53.7%\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 08. ์žฅ์‹œ๊ฐ„ ๋น„ํ–‰/๊ธฐ๋™ ์„ผ์„œ ๋ฐ์ดํ„ฐ ๋Œ€์šฉ๋Ÿ‰ ์ŠคํŠธ๋ฆผ ์ฃผ์ž… ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์žฅ์‹œ๊ฐ„ ๊ธฐ๋™ ์„ผ์„œ ๋ฐ์ดํ„ฐ ๋Œ€์šฉ๋Ÿ‰ ์ŠคํŠธ๋ฆผ ์ฃผ์ž… ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์žฅ์‹œ๊ฐ„ ๊ธฐ๋™ ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋Œ€์šฉ๋Ÿ‰ ์ŠคํŠธ๋ฆผ์œผ๋กœ ์ฃผ์ž…ํ•ด ์ฒ˜๋ฆฌ ์‹œ๊ฐ„, ๊ฒฝ๋ณด ์ •ํ™•๋„, ๋ถˆ๋Ÿ‰ ํŒจํ‚ท ๊ฒฉ๋ฆฌ๋ฅผ ํ™•์ธํ•˜๋Š” ์ŠคํŠธ๋ ˆ์Šค ์‹œํ—˜์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ฐ€์ƒ ์„ผ์„œ ์ธก์ • 1,000๊ฑด์— ๊ฒฐ์ธก ํŒจํ‚ท, ์„ผ์„œ ๊ณ ์ •, ํฌํ™”, ๋ฒ”์œ„ ์ดํƒˆ์„ ์„ž์–ด ํ•œ๊บผ๋ฒˆ์— ์ฃผ์ž…ํ•˜๊ณ  ์ •์ƒ ์ฒ˜๋ฆฌยท๊ฒฉ๋ฆฌยท์œ ์‹ค ๊ฑด์ˆ˜์™€ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„์„ ์„ผ๋‹ค ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/dashboard/mnt04_app.py (Streamlit/Dash ๋“ฑ)` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์˜์‚ฌ๊ฒฐ์ • ์ง€์›์— ์ตœ์ ํ™”๋œ ์‹œ๊ฐ์  ์œ„๊ณ„ ๋ฐ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX)\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ ์‹œ๊ฐ„ 2์ดˆ ์ด๋‚ด, ๊ฒฝ๋ณด ๋ฐœ๋ น ์ •ํ™•๋„ 95% ์ด์ƒ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์›น ๊ธฐ๋ฐ˜ ๋ถ„์„ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌ๋™ ๋ฐ ์‹ค์‹œ๊ฐ„ ํ•„ํ„ฐ๋ง ๊ธฐ๋Šฅ ๊ฒ€์ฆ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ์‹ค์ œ ๊ตฐ์ˆ˜ ๋ฐ์ดํ„ฐ ์—ฐ๋™ ํ™”๋ฉด ํ…Œ์ŠคํŠธ ๋ฐ UI ๋™์ž‘ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์žฅ์‹œ๊ฐ„ ๊ธฐ๋™ ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ 1,000~100,000๊ฑด ๊ทœ๋ชจ๋กœ ํ•œ๊บผ๋ฒˆ์— ์ฃผ์ž…ํ•ด ๊ฒ€์ฆยทํŠน์ง• ์ถ”์ถœยท์˜คํ† ์ธ์ฝ”๋” ์ ์ˆ˜ยท๊ฒฝ๋ณด ํŒ์ •์„ ๋ฐฐ์น˜๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ์ŠคํŠธ๋ฆผ ์‹œํ—˜์„ ์ˆ˜ํ–‰ํ•˜๊ณ , ์ฒ˜๋ฆฌ ์‹œ๊ฐ„๊ณผ ์ƒํƒœ ํ™”๋ฉด ๊ตฌ์„ฑ ์‹œ๊ฐ„, ๊ฒฝ๋ณด ์ •ํ™•๋„, ๋ถˆ๋Ÿ‰ ํŒจํ‚ท(๊ฒฐ์ธกยท๊ณ ์ •๊ฐ’ยทํฌํ™”ยท์ค‘๋ณต) ๊ฒฉ๋ฆฌ์™€ ์œ ์‹ค 0๊ฑด ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.stats import kurtosis\n", "from sklearn.neural_network import MLPRegressor\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๊ธฐ๋™์ฒด 48๋Œ€์˜ ์—”์ง„ยท๊ตฌ๋™๊ณ„ ์ง„๋™ยท์˜จ๋„ยท์••๋ ฅ ์„ผ์„œ ์ž๋ฃŒ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๊ฐœ์ฒด๋งˆ๋‹ค ์ •์ƒ -> ์—ดํ™” -> ๊ณ ์žฅ(์ˆ˜๋ช…์ข…๋ฃŒ)๊นŒ์ง€ ํ•˜๋ฃจ 1ํšŒ ์ธก์ •(์ง„๋™ ํŒŒํ˜• 256ํ‘œ๋ณธ + ์˜จ๋„ + ์••๋ ฅ + ๋ถ€ํ•˜)์„ ๋งŒ๋“ ๋‹ค.\n", "# ์—ดํ™” ์œ ํ˜• 4๊ฐ€์ง€: bearing(๋ฒ ์–ด๋ง), gear(๊ธฐ์–ด), oil_pump(์˜ค์ผ ํŽŒํ”„), cooling(๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ)\n", "# ---------------------------------------------------------------------------\n", "FS, NS = 1024, 256 # ๊ฐ€์ƒ ํ‘œ๋ณธํ™” ์ฃผํŒŒ์ˆ˜(Hz), ํŒŒํ˜• 1๊ฐœ๋‹น ํ‘œ๋ณธ ์ˆ˜\n", "TT = np.arange(NS) / FS\n", "MODES = { # ์—ดํ™” ์œ ํ˜•: (๋ฐœ์ƒ ํ™•๋ฅ , ํ‰๊ท  ์ˆ˜๋ช…(์ผ), ๊ต์ฒด ์ˆ˜๋ฆฌ๋ถ€์†, ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜)\n", " \"bearing\": (0.40, 160, \"์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ\", 6.0), \"gear\": (0.25, 175, \"๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ\", 9.0),\n", " \"oil_pump\": (0.20, 150, \"์˜ค์ผ ํŽŒํ”„\", 4.0), \"cooling\": (0.15, 140, \"๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ\", 3.0)}\n", "KIT_COST = 1.0 # ๋ชจ๋“  ์ •๋น„์— ํ•จ๊ป˜ ์†Œ๋ชจ๋˜๋Š” ์”ฐยทํ•„ํ„ฐ ํ‚คํŠธ ๋‹จ๊ฐ€์ง€์ˆ˜\n", "BANDS = {\"b50\": (40, 60), \"b100\": (90, 110), \"b150\": (140, 156), \"b165\": (156, 176), \"b200\": (190, 215)}\n", "\n", "\n", "def make_runs(n=48, seed=1):\n", " \"\"\"๋ฐ˜ํ™˜: units(๊ฐœ์ฒด ์ •๋ณด), snap(์ผ๋ณ„ ์ธก์ • ์š”์•ฝ), W(์ง„๋™ ํŒŒํ˜• [์ธก์ • ์ˆ˜, 256])\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(MODES)\n", " mode = rng.choice(names, n, p=[MODES[m][0] for m in names])\n", " life = np.clip(np.array([rng.normal(MODES[m][1], 18) for m in mode]), 90, 230).astype(int)\n", " start = np.sort(rng.integers(0, 240, n))\n", " units = pd.DataFrame({\"unit\": [f\"U{i:02d}\" for i in range(n)], \"mode\": mode, \"life\": life, \"start\": start, \"base_load\": rng.uniform(0.4, 0.75, n)})\n", " rows = []\n", " for u in units.itertuples():\n", " d = np.arange(u.life + 1)\n", " onset = 0.45 * u.life\n", " deg = np.clip((d - onset) / (u.life - onset), 0, 1) ** 1.5\n", " load = np.clip(u.base_load + rng.normal(0, 0.06, len(d)), 0.2, 1.0)\n", " hi = rng.random(len(d)) < 0.07 # ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ(์ •์ƒ์ด์ง€๋งŒ ์ง„๋™ยท์˜จ๋„ ์ƒ์Šน)\n", " load = np.where(hi, rng.uniform(0.95, 1.1, len(d)), load)\n", " rows.append(pd.DataFrame({\"unit\": u.unit, \"day\": d, \"cal\": u.start + d, \"rul\": u.life - d, \"deg\": deg, \"load\": load, \"hi_load\": hi, \"mode\": u.mode}))\n", " snap = pd.concat(rows, ignore_index=True)\n", " S = len(snap)\n", " snap[\"ambient\"] = 15 + 12 * np.sin(2 * np.pi * snap[\"cal\"] / 365) + rng.normal(0, 2, S)\n", " dg, ld, md = snap[\"deg\"].values, snap[\"load\"].values, snap[\"mode\"].values\n", " isb, isg, iso, isc = (md == \"bearing\"), (md == \"gear\"), (md == \"oil_pump\"), (md == \"cooling\")\n", " snap[\"temp\"] = 55 + 22 * ld + 0.5 * snap[\"ambient\"] + 9 * dg * iso + 26 * dg * isc + 3 * dg * (isb | isg) + rng.normal(0, 1.5, S)\n", " snap[\"press\"] = 450 - 12 * ld - 85 * dg * iso - 15 * dg * isc + rng.normal(0, 6, S)\n", " ph = rng.uniform(0, 2 * np.pi, (S, 3))\n", " a1, a2 = 1.0 * (0.5 + ld), 0.4 * (0.5 + ld)\n", " W = (a1[:, None] * np.sin(2 * np.pi * 50 * TT + ph[:, :1]) + a2[:, None] * np.sin(2 * np.pi * 100 * TT + ph[:, 1:2])\n", " + rng.normal(0, 0.35, (S, NS)))\n", " W += (0.9 * dg * isb)[:, None] * np.sin(2 * np.pi * 165 * TT + ph[:, 2:3])\n", " W += (0.8 * dg * isg)[:, None] * (np.sin(2 * np.pi * 150 * TT + ph[:, 2:3]) + 0.8 * np.sin(2 * np.pi * 100 * TT))\n", " W += (0.5 * dg * iso)[:, None] * np.sin(2 * np.pi * 200 * TT + ph[:, 2:3])\n", " k = rng.poisson(10 * dg * isb) # ๋ฒ ์–ด๋ง ์—ดํ™”: ์ถฉ๊ฒฉ์„ฑ ํŽ„์Šค(์ฒจ๋„ ์ƒ์Šน)\n", " pos, amp = rng.integers(0, NS, (S, 30)), rng.normal(0, 3.0, (S, 30)) * dg[:, None]\n", " amp = amp * (np.arange(30)[None, :] < k[:, None])\n", " np.add.at(W, (np.repeat(np.arange(S), 30), pos.ravel()), amp.ravel())\n", " return units, snap, W\n", "\n", "\n", "def extract(W):\n", " \"\"\"์ง„๋™ ํŒŒํ˜• -> ํŠน์ง•๋Ÿ‰: RMS, ์ฒจ๋„, ํŒŒ๊ณ ์œจ, ์ตœ๋Œ€-์ตœ์†Œ, FFT ๋Œ€์—ญ ์—๋„ˆ์ง€(50/100/150/165/200Hz ๋ถ€๊ทผ)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 8] ์žฅ์‹œ๊ฐ„ ๋น„ํ–‰/๊ธฐ๋™ ์„ผ์„œ ๋ฐ์ดํ„ฐ ๋Œ€์šฉ๋Ÿ‰ ์ŠคํŠธ๋ฆผ ์ฃผ์ž… ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "BASE = [\"rms\", \"kurt\", \"crest\", \"p2p\", \"b50\", \"b100\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "ROLL = [\"rms\", \"kurt\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "\n", "\n", "def build_features(snap, W):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 8] ์žฅ์‹œ๊ฐ„ ๋น„ํ–‰/๊ธฐ๋™ ์„ผ์„œ ๋ฐ์ดํ„ฐ ๋Œ€์šฉ๋Ÿ‰ ์ŠคํŠธ๋ฆผ ์ฃผ์ž… ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "DERIVED = [\"temp_res\", \"press_res\", \"rms_n\", \"hf_ratio\"] + [c + \"_m3\" for c in ROLL] + [\"rms_n_d\", \"temp_res_d\", \"press_res_d\"]\n", "FEATS = BASE + DERIVED\n", "AE_COLS = [\"rms_n\", \"kurt\", \"b150\", \"b165\", \"b200\", \"hf_ratio\", \"temp_res\", \"press_res\"]\n", "\n", "\n", "def split_units(units, n_train=24):\n", " return list(units[\"unit\"][:n_train]), list(units[\"unit\"][n_train:])\n", "\n", "\n", "class AE:\n", " \"\"\"์˜คํ† ์ธ์ฝ”๋”(์ž…๋ ฅ 8 -> 10 -> 4 -> 10 -> 8): ์ •์ƒ ์ž๋ฃŒ๋งŒ ์žฌ๊ตฌ์„ฑํ•˜๋„๋ก ํ•™์Šตํ•˜๊ณ , ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ๋ฅผ ์ด์ƒ ์ ์ˆ˜๋กœ ์“ด๋‹ค.\"\"\"\n", "\n", " def __init__(self, cols=AE_COLS, q=0.99, seed=0):\n", " self.cols, self.q, self.seed = cols, q, seed\n", "\n", " def fit(self, healthy):\n", " self.sc = StandardScaler().fit(healthy[self.cols])\n", " Z = self.sc.transform(healthy[self.cols])\n", " self.net = MLPRegressor(hidden_layer_sizes=(10, 4, 10), max_iter=800, random_state=self.seed).fit(Z, Z)\n", " self.thr = float(np.quantile(self.err(healthy), self.q))\n", " return self\n", "\n", " def contrib(self, d):\n", " Z = self.sc.transform(d[self.cols])\n", " return (Z - self.net.predict(Z)) ** 2 # ์ž…๋ ฅ ํ•ญ๋ชฉ๋ณ„ ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ(์ด์ƒ ๊ธฐ์—ฌ๋„)\n", "\n", " def err(self, d):\n", " return self.contrib(d).mean(axis=1)\n", "\n", "\n", "def latch(d, e, thr, k=2):\n", " \"\"\"๊ฐ™์€ ๊ฐœ์ฒด์—์„œ ์ด์ƒ ์ ์ˆ˜๊ฐ€ k์ผ ์—ฐ์† ๊ธฐ์ค€์„ ๋„˜์œผ๋ฉด ๊ฒฝ๋ณด๋ฅผ ์ผœ๊ณ  ์œ ์ง€(๋ž˜์น˜)ํ•œ๋‹ค.\"\"\"\n", " out, e = np.zeros(len(d), bool), np.asarray(e)\n", " for idx in d.reset_index(drop=True).groupby(\"unit\").indices.values():\n", " run, on = 0, False\n", " for i in idx:\n", " run = run + 1 if e[i] > thr else 0\n", " on = on or run >= k\n", " out[i] = on\n", " return out\n", "\n", "units, snap, W = make_runs()\n", "D = build_features(snap, W)\n", "tr_u, va_u = split_units(units)\n", "tr, va = D[D[\"unit\"].isin(tr_u)].reset_index(drop=True), D[D[\"unit\"].isin(va_u)].reset_index(drop=True)\n", "Wva = W[D[\"unit\"].isin(va_u).values]\n", "ae = AE().fit(tr[tr[\"deg\"] == 0])\n", "KEEP = [\"unit\", \"day\", \"load\", \"ambient\", \"temp\", \"press\"]\n", "\n", "\n", "def score_batch(w, meta):\n", " \"\"\"์ŠคํŠธ๋ฆผ ํ•œ ๋ฌถ์Œ: ํŒŒํ˜• -> ํŠน์ง• -> ์ด์ƒ ์ ์ˆ˜(๋ฐฐ์ˆ˜). ์ ์ˆ˜๋Š” ๊ฐœ์ฒด ์—ฐ์†์„ฑ(๋ž˜์น˜)๊ณผ ๋ฌด๊ด€ํ•œ ๋‹จ๊ฑด ๊ฐ’.\"\"\"\n", " f = extract(w)\n", " d = pd.DataFrame({\"rms_n\": f[\"rms\"] / (0.5 + meta[\"load\"]), \"kurt\": f[\"kurt\"], \"b150\": f[\"b150\"], \"b165\": f[\"b165\"], \"b200\": f[\"b200\"],\n", " \"hf_ratio\": (f[\"b150\"] + f[\"b165\"] + f[\"b200\"]) / (f[\"b50\"] + 1e-6),\n", " \"temp_res\": meta[\"temp\"] - (55 + 22 * meta[\"load\"] + 0.5 * meta[\"ambient\"]), \"press_res\": meta[\"press\"] - (450 - 12 * meta[\"load\"])})\n", " return ae.err(d) / ae.thr\n", "\n", "\n", "def valid(w, meta):\n", " \"\"\"๋ถˆ๋Ÿ‰ ํŒจํ‚ท ํŒ์ •: ๊ฒฐ์ธก(NaN), ์„ผ์„œ ๊ณ ์ •(๋ถ„์‚ฐ 0), ํฌํ™”(|๊ฐ’|>12), ๊ฐ’ ๋ฒ”์œ„ ์ดํƒˆ.\"\"\"\n", " bad = np.isnan(w).any(axis=1) | (np.nanstd(w, axis=1) < 1e-6) | (np.abs(np.nan_to_num(w)).max(axis=1) > 12)\n", " bad |= ~meta[\"temp\"].between(20, 160).values | ~meta[\"press\"].between(100, 600).values\n", " return ~bad\n", "\n", "\n", "def make_stream(n, bad_rate, seed=0):\n", " rng = np.random.default_rng(seed)\n", " pick = rng.integers(0, len(va), n)\n", " w = Wva[pick] + rng.normal(0, 0.02, (n, NS))\n", " meta = va.iloc[pick][KEEP + [\"deg\"]].reset_index(drop=True)\n", " kind = np.array([\"\"] * n, dtype=object)\n", " bad = rng.random(n) < bad_rate\n", " k = rng.integers(0, 4, n)\n", " for t, nm in enumerate([\"๊ฒฐ์ธก ํŒจํ‚ท\", \"์„ผ์„œ ๊ณ ์ •\", \"ํฌํ™”\", \"๋ฒ”์œ„ ์ดํƒˆ\"]):\n", " m = bad & (k == t)\n", " kind[m] = nm\n", " if t == 0: w[m, 10:40] = np.nan\n", " if t == 1: w[m] = 0.5\n", " if t == 2: w[m, ::7] = 50.0\n", " if t == 3: meta.loc[m, \"temp\"] = 400.0\n", " return w, meta, kind\n", "\n", "\n", "# 1) 1,000๊ฑด ๋™์‹œ ์ฃผ์ž…\n", "t0 = time.perf_counter()\n", "w, meta, kind = make_stream(1000, 0.06)\n", "ok = valid(w, meta)\n", "s = np.full(1000, np.nan)\n", "s[ok] = score_batch(w[ok], meta[ok].reset_index(drop=True))\n", "sec = time.perf_counter() - t0\n", "inj = int((kind != \"\").sum())\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] 1,000๊ฑด ๋™์‹œ ์ฃผ์ž…: ์ •์ƒ ์ฒ˜๋ฆฌ {int(ok.sum())}๊ฑด, ๊ฒฉ๋ฆฌ {int((~ok).sum())}๊ฑด, ์œ ์‹ค {1000 - int(ok.sum()) - int((~ok).sum())}๊ฑด, ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ {sec:.2f}์ดˆ (๋ชฉํ‘œ 2์ดˆ ์ด๋‚ด: {'๋‹ฌ์„ฑ' if sec <= 2 else '๋ฏธ๋‹ฌ'})\")\n", "print(f\" ๋ถˆ๋Ÿ‰ ์ฃผ์ž… {inj}๊ฑด ์ค‘ ๊ฒฉ๋ฆฌ {int(((kind != '') & ~ok).sum())}๊ฑด (\" + \", \".join(f\"{k} {int((kind == k).sum())}\" for k in [\"๊ฒฐ์ธก ํŒจํ‚ท\", \"์„ผ์„œ ๊ณ ์ •\", \"ํฌํ™”\", \"๋ฒ”์œ„ ์ดํƒˆ\"]) + \")\")\n", "\n", "# 2) ๊ฒฝ๋ณด ์ •ํ™•๋„: ์ •์ƒ ๊ตฌ๊ฐ„๊ณผ ์—ดํ™”๊ฐ€ ๋šœ๋ ทํ•œ ๊ตฌ๊ฐ„(deg>=0.3)์„ ํŒ์ • ๋Œ€์ƒ์œผ๋กœ ํ•˜๊ณ , ์—ดํ™” ์ดˆ๊ธฐ ํšŒ์ƒ‰ ๊ตฌ๊ฐ„์€ ์ •ํ™•๋„์—์„œ ์ œ์™ธํ•˜๊ณ  ๋”ฐ๋กœ ํ‘œ์‹œ\n", "al = s > 1.0\n", "y = (meta[\"deg\"] >= 0.3).values\n", "eval_m = ok & ((meta[\"deg\"] == 0).values | y)\n", "acc = (al[eval_m] == y[eval_m]).mean()\n", "tp, fp, fn = (al & y & ok).sum(), (al & ~y & eval_m).sum(), (~al & y & ok).sum()\n", "print(f\"๊ฒฝ๋ณด ์ •ํ™•๋„ {acc:.1%} (๋ชฉํ‘œ 95% ์ด์ƒ: {'๋‹ฌ์„ฑ' if acc >= 0.95 else '๋ฏธ๋‹ฌ'}), ์žฌํ˜„์œจ {tp / (tp + fn):.1%}, ์ •๋ฐ€๋„ {tp / (tp + fp):.1%}, ๊ฒฝ๋ณด {int((al & ok).sum())}๊ฑด\")\n", "gray = ok & (meta[\"deg\"] > 0).values & (meta[\"deg\"] < 0.3).values\n", "print(f\" ์ฐธ๊ณ : ์—ดํ™” ์ดˆ๊ธฐ(0~0.3) ๊ตฌ๊ฐ„ {int(gray.sum())}๊ฑด ์ค‘ ๊ฒฝ๋ณด {al[gray].mean():.0%}(ํšŒ์ƒ‰ ๊ตฌ๊ฐ„์€ ์ •ํ™•๋„์—์„œ ์ œ์™ธ)\")\n", "\n", "# 3) ์ƒํƒœ ํ™”๋ฉด ๊ตฌ์„ฑ: ์ตœ๊ทผ 30์ผ x 24๋Œ€ ์š”์•ฝ(๊ฐœ์ฒด๋ณ„ ์ตœ๋Œ“๊ฐ’ยท๊ฒฝ๋ณด ์—ฌ๋ถ€) ์ง‘๊ณ„ ์‹œ๊ฐ„\n", "t0 = time.perf_counter()\n", "last = va[va[\"day\"] >= va.groupby(\"unit\")[\"day\"].transform(\"max\") - 30]\n", "sc = score_batch(Wva[last.index.values], last.reset_index(drop=True))\n", "view = last.assign(score=sc).groupby(\"unit\").agg(max_score=(\"score\", \"max\"), last_score=(\"score\", \"last\"))\n", "view[\"์ƒํƒœ\"] = np.where(view[\"last_score\"] > 1, \"๊ฒฝ๋ณด\", np.where(view[\"max_score\"] > 1, \"์ฃผ์˜\", \"์ •์ƒ\"))\n", "tv = time.perf_counter() - t0\n", "print(f\"์ƒํƒœ ํ™”๋ฉด ๊ตฌ์„ฑ(24๋Œ€ x ์ตœ๊ทผ 30์ผ {len(last)}๊ฑด) {tv:.2f}์ดˆ (๋ชฉํ‘œ 2์ดˆ ์ด๋‚ด: {'๋‹ฌ์„ฑ' if tv <= 2 else '๋ฏธ๋‹ฌ'}), \" + \", \".join(f\"{k} {v}๋Œ€\" for k, v in view[\"์ƒํƒœ\"].value_counts().items()))\n", "\n", "# 4) ๊ทœ๋ชจ ํ™•๋Œ€: ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ๋Ÿ‰๊ณผ ๋ฉ”๋ชจ๋ฆฌ ํ•œ๊ณ„ ๋Œ€์‘(๋ฐฐ์น˜ ํฌ๊ธฐ 5,000)\n", "print(\"\\n[์ฃผ์ž… ๊ทœ๋ชจ๋ณ„ ์ฒ˜๋ฆฌ] ๊ฑด์ˆ˜ | ์‹œ๊ฐ„ | ์ฒ˜๋ฆฌ๋Ÿ‰ | ๊ฒฉ๋ฆฌ | ์œ ์‹ค\")\n", "sizes = (1000, 10000, 50000, 100000)\n", "ts = []\n", "for n in sizes:\n", " w, meta, kind = make_stream(n, 0.06, seed=n)\n", " t0 = time.perf_counter(); done = quarantine = 0\n", " for i in range(0, n, 5000):\n", " wb, mb = w[i:i + 5000], meta.iloc[i:i + 5000].reset_index(drop=True)\n", " okb = valid(wb, mb)\n", " if okb.any():\n", " score_batch(wb[okb], mb[okb].reset_index(drop=True))\n", " done += int(okb.sum()); quarantine += int((~okb).sum())\n", " t = time.perf_counter() - t0; ts.append(t)\n", " print(f\" {n:7d} {t:5.2f}์ดˆ {n / t:8.0f}๊ฑด/์ดˆ {quarantine:5d} {n - done - quarantine}\")\n", "print(\"์šด์šฉ ๊ธฐ์ค€: ์ „์ฒด ๊ฒฉ๋ฆฌ์œจ์ด 20%๋ฅผ ๋„˜์œผ๋ฉด ์„ผ์„œ ๊ณ„ํ†ต ์ ๊ฒ€ ์•Œ๋ฆผ, ์ฒ˜๋ฆฌ ์ง€์—ฐ ์‹œ FFT ๋Œ€์—ญ 5๊ฐœ๋งŒ ๊ณ„์‚ฐํ•˜๋Š” ์ถ•์†Œ ๋ชจ๋“œ๋กœ ์ „ํ™˜\")\n", "\n", "plt.figure(figsize=(6, 4))\n", "plt.plot(sizes, ts, marker=\"o\"); plt.xscale(\"log\"); plt.xlabel(\"stream size\"); plt.ylabel(\"seconds\"); plt.title(\"Stream stress test\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 08 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] 1,000๊ฑด ๋™์‹œ ์ฃผ์ž…: ์ •์ƒ ์ฒ˜๋ฆฌ 945๊ฑด, ๊ฒฉ๋ฆฌ 55๊ฑด, ์œ ์‹ค 0๊ฑด, ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ 0.03์ดˆ (๋ชฉํ‘œ 2์ดˆ ์ด๋‚ด: ๋‹ฌ์„ฑ)\n", " ๋ถˆ๋Ÿ‰ ์ฃผ์ž… 55๊ฑด ์ค‘ ๊ฒฉ๋ฆฌ 55๊ฑด (๊ฒฐ์ธก ํŒจํ‚ท 15, ์„ผ์„œ ๊ณ ์ • 16, ํฌํ™” 15, ๋ฒ”์œ„ ์ดํƒˆ 9)\n", "๊ฒฝ๋ณด ์ •ํ™•๋„ 98.0% (๋ชฉํ‘œ 95% ์ด์ƒ: ๋‹ฌ์„ฑ), ์žฌํ˜„์œจ 98.0%, ์ •๋ฐ€๋„ 97.3%, ๊ฒฝ๋ณด 330๊ฑด\n", " ์ฐธ๊ณ : ์—ดํ™” ์ดˆ๊ธฐ(0~0.3) ๊ตฌ๊ฐ„ 231๊ฑด ์ค‘ ๊ฒฝ๋ณด 15%(ํšŒ์ƒ‰ ๊ตฌ๊ฐ„์€ ์ •ํ™•๋„์—์„œ ์ œ์™ธ)\n", "์ƒํƒœ ํ™”๋ฉด ๊ตฌ์„ฑ(24๋Œ€ x ์ตœ๊ทผ 30์ผ 744๊ฑด) 0.02์ดˆ (๋ชฉํ‘œ 2์ดˆ ์ด๋‚ด: ๋‹ฌ์„ฑ), ๊ฒฝ๋ณด 24๋Œ€\n", "\n", "[์ฃผ์ž… ๊ทœ๋ชจ๋ณ„ ์ฒ˜๋ฆฌ] ๊ฑด์ˆ˜ | ์‹œ๊ฐ„ | ์ฒ˜๋ฆฌ๋Ÿ‰ | ๊ฒฉ๋ฆฌ | ์œ ์‹ค\n", " 1000 0.02์ดˆ 48180๊ฑด/์ดˆ 68 0\n", " 10000 0.21์ดˆ 48054๊ฑด/์ดˆ 618 0\n", " 50000 1.23์ดˆ 40710๊ฑด/์ดˆ 2951 0\n", " 100000 3.51์ดˆ 28472๊ฑด/์ดˆ 5940 0\n", "์šด์šฉ ๊ธฐ์ค€: ์ „์ฒด ๊ฒฉ๋ฆฌ์œจ์ด 20%๋ฅผ ๋„˜์œผ๋ฉด ์„ผ์„œ ๊ณ„ํ†ต ์ ๊ฒ€ ์•Œ๋ฆผ, ์ฒ˜๋ฆฌ ์ง€์—ฐ ์‹œ FFT ๋Œ€์—ญ 5๊ฐœ๋งŒ ๊ณ„์‚ฐํ•˜๋Š” ์ถ•์†Œ ๋ชจ๋“œ๋กœ ์ „ํ™˜\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 09. AAR ๊ธฐ๋ฐ˜ ์˜ˆ์ง€์ •๋น„ ์˜ค๊ฒฝ๋ณด ๋ถ„์„ ๋ฐ ์ด์ƒ ๊ฐ์ง€ ์ž„๊ณ„ ํŒŒ๋ผ๋ฏธํ„ฐ ์žฌ๋ณด์ •\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "AAR ๊ธฐ๋ฐ˜ ์˜ˆ์ง€์ •๋น„ ์˜ค๊ฒฝ๋ณด๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์ž„๊ณ„ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์žฌ๋ณด์ •ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **AAR ๊ธฐ๋ฐ˜์œผ๋กœ ์˜ˆ์ง€์ •๋น„ ์˜ค๊ฒฝ๋ณด๋ฅผ ์›์ธ๋ณ„๋กœ ๋ถ„์„ํ•˜๊ณ  ์ด์ƒ ๊ฐ์ง€ ์ž„๊ณ„ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์žฌ๋ณด์ •ํ•ด ํšจ๊ณผ๋ฅผ ์ง€ํ‘œ๋กœ ์‚ฐ์ถœํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **AAR ๊ธฐ๊ฐ„ ์˜ค๊ฒฝ๋ณด๋ฅผ ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ, ์™ธ๊ธฐ ๊ธ‰๋ณ€, ์„ผ์„œ ์ŠคํŒŒ์ดํฌ, ์—ดํ™” ์ดˆ๊ธฐ ๊ฒฝ๊ณ„ ๋“ฑ์œผ๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ , ์ •์ƒ ๋ถ„์œ„์ˆ˜ยท์—ฐ์† ์ผ์ˆ˜ยท๊ณ ๋ถ€ํ•˜์ผ ์ž„๊ณ„ ์™„ํ™”๋ฅผ AAR ๊ธฐ๊ฐ„ ์ž๋ฃŒ๋งŒ์œผ๋กœ ๊ฒฉ์ž ํƒ์ƒ‰ํ•ด ์žฌ๋ณด์ • ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `reports/mnt04_roi_analysis.md` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์ถ”์ • ์‚ฐ์‹์˜ ๊ฐ๊ด€์„ฑ ๋ฐ ๊ตฐ ์žฌ์ •/๊ตฐ์ˆ˜ ๊ธฐํš ๋ถ€์„œ ์„ค๋“๋ ฅ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๋น„์šฉ ์ ˆ๊ฐ์•ก, ์‹œ๊ฐ„ ๋‹จ์ถ•๋ฅ  ๋“ฑ 3๋Œ€ ์ •๋Ÿ‰ ํšจ๊ณผ ์ง€ํ‘œ ์‚ฐ์ถœ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ROI ๋ถ„์„ ๋ณด๊ณ ์„œ ์™„์„ฑ ๋ฐ ์ฃผ์š” ์ง€ํ‘œ ๋ฏผ๊ฐ๋„ ๋ถ„์„ ์ˆ˜๋ก\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ํšŒ๊ณ„/๊ธฐํš ๊ธฐ์ค€ ๋ชจ๋ธ๊ณผ์˜ ๋น„๊ต ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"AAR ๊ธฐ๊ฐ„์˜ ์˜ˆ์ง€์ •๋น„ ์˜ค๊ฒฝ๋ณด๋ฅผ ์›์ธ(๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผยท์™ธ๊ธฐ ๊ธ‰๋ณ€ยท์„ผ์„œ ์ŠคํŒŒ์ดํฌยท๊ธฐํƒ€)๋ณ„๋กœ ๋ถ„์„ํ•˜๊ณ , ์ •์ƒ ๋ถ„์œ„์ˆ˜ยท์—ฐ์† ์ผ์ˆ˜ยท๊ณ ๋ถ€ํ•˜์ผ ์ž„๊ณ„ ์™„ํ™” ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ AAR ๊ธฐ๊ฐ„ ์ž๋ฃŒ๋กœ๋งŒ ๊ฒฉ์ž ํƒ์ƒ‰ํ•ด ์žฌ๋ณด์ •ํ•œ ๋’ค ๊ฒ€์ฆ ๊ธฐ๊ฐ„์—์„œ ์˜ค๊ฒฝ๋ณดยท๊ณ ์žฅ ์ „ ํ™•๋ณด ์‹œ๊ฐ„ ๋ณ€ํ™”์™€ ๋น„์šฉ ์ ˆ๊ฐยท์ ๊ฒ€ ์‹œ๊ฐ„ ๋‹จ์ถ•ยทํ™•๋ณด ์‹œ๊ฐ„ ์ฆ๊ฐ€์˜ 3๋Œ€ ์ •๋Ÿ‰ ํšจ๊ณผ ์ง€ํ‘œ๋ฅผ ๊ฐ€์ • ๋‹จ๊ฐ€๋กœ ์‚ฐ์ถœํ•œ๋‹ค.\"\"\"\n", "import itertools\n", "import numpy as np\n", "import pandas as pd\n", "from scipy.stats import kurtosis\n", "from sklearn.neural_network import MLPRegressor\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๊ธฐ๋™์ฒด 48๋Œ€์˜ ์—”์ง„ยท๊ตฌ๋™๊ณ„ ์ง„๋™ยท์˜จ๋„ยท์••๋ ฅ ์„ผ์„œ ์ž๋ฃŒ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๊ฐœ์ฒด๋งˆ๋‹ค ์ •์ƒ -> ์—ดํ™” -> ๊ณ ์žฅ(์ˆ˜๋ช…์ข…๋ฃŒ)๊นŒ์ง€ ํ•˜๋ฃจ 1ํšŒ ์ธก์ •(์ง„๋™ ํŒŒํ˜• 256ํ‘œ๋ณธ + ์˜จ๋„ + ์••๋ ฅ + ๋ถ€ํ•˜)์„ ๋งŒ๋“ ๋‹ค.\n", "# ์—ดํ™” ์œ ํ˜• 4๊ฐ€์ง€: bearing(๋ฒ ์–ด๋ง), gear(๊ธฐ์–ด), oil_pump(์˜ค์ผ ํŽŒํ”„), cooling(๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ)\n", "# ---------------------------------------------------------------------------\n", "FS, NS = 1024, 256 # ๊ฐ€์ƒ ํ‘œ๋ณธํ™” ์ฃผํŒŒ์ˆ˜(Hz), ํŒŒํ˜• 1๊ฐœ๋‹น ํ‘œ๋ณธ ์ˆ˜\n", "TT = np.arange(NS) / FS\n", "MODES = { # ์—ดํ™” ์œ ํ˜•: (๋ฐœ์ƒ ํ™•๋ฅ , ํ‰๊ท  ์ˆ˜๋ช…(์ผ), ๊ต์ฒด ์ˆ˜๋ฆฌ๋ถ€์†, ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜)\n", " \"bearing\": (0.40, 160, \"์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ\", 6.0), \"gear\": (0.25, 175, \"๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ\", 9.0),\n", " \"oil_pump\": (0.20, 150, \"์˜ค์ผ ํŽŒํ”„\", 4.0), \"cooling\": (0.15, 140, \"๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ\", 3.0)}\n", "KIT_COST = 1.0 # ๋ชจ๋“  ์ •๋น„์— ํ•จ๊ป˜ ์†Œ๋ชจ๋˜๋Š” ์”ฐยทํ•„ํ„ฐ ํ‚คํŠธ ๋‹จ๊ฐ€์ง€์ˆ˜\n", "BANDS = {\"b50\": (40, 60), \"b100\": (90, 110), \"b150\": (140, 156), \"b165\": (156, 176), \"b200\": (190, 215)}\n", "\n", "\n", "def make_runs(n=48, seed=1):\n", " \"\"\"๋ฐ˜ํ™˜: units(๊ฐœ์ฒด ์ •๋ณด), snap(์ผ๋ณ„ ์ธก์ • ์š”์•ฝ), W(์ง„๋™ ํŒŒํ˜• [์ธก์ • ์ˆ˜, 256])\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(MODES)\n", " mode = rng.choice(names, n, p=[MODES[m][0] for m in names])\n", " life = np.clip(np.array([rng.normal(MODES[m][1], 18) for m in mode]), 90, 230).astype(int)\n", " start = np.sort(rng.integers(0, 240, n))\n", " units = pd.DataFrame({\"unit\": [f\"U{i:02d}\" for i in range(n)], \"mode\": mode, \"life\": life, \"start\": start, \"base_load\": rng.uniform(0.4, 0.75, n)})\n", " rows = []\n", " for u in units.itertuples():\n", " d = np.arange(u.life + 1)\n", " onset = 0.45 * u.life\n", " deg = np.clip((d - onset) / (u.life - onset), 0, 1) ** 1.5\n", " load = np.clip(u.base_load + rng.normal(0, 0.06, len(d)), 0.2, 1.0)\n", " hi = rng.random(len(d)) < 0.07 # ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ(์ •์ƒ์ด์ง€๋งŒ ์ง„๋™ยท์˜จ๋„ ์ƒ์Šน)\n", " load = np.where(hi, rng.uniform(0.95, 1.1, len(d)), load)\n", " rows.append(pd.DataFrame({\"unit\": u.unit, \"day\": d, \"cal\": u.start + d, \"rul\": u.life - d, \"deg\": deg, \"load\": load, \"hi_load\": hi, \"mode\": u.mode}))\n", " snap = pd.concat(rows, ignore_index=True)\n", " S = len(snap)\n", " snap[\"ambient\"] = 15 + 12 * np.sin(2 * np.pi * snap[\"cal\"] / 365) + rng.normal(0, 2, S)\n", " dg, ld, md = snap[\"deg\"].values, snap[\"load\"].values, snap[\"mode\"].values\n", " isb, isg, iso, isc = (md == \"bearing\"), (md == \"gear\"), (md == \"oil_pump\"), (md == \"cooling\")\n", " snap[\"temp\"] = 55 + 22 * ld + 0.5 * snap[\"ambient\"] + 9 * dg * iso + 26 * dg * isc + 3 * dg * (isb | isg) + rng.normal(0, 1.5, S)\n", " snap[\"press\"] = 450 - 12 * ld - 85 * dg * iso - 15 * dg * isc + rng.normal(0, 6, S)\n", " ph = rng.uniform(0, 2 * np.pi, (S, 3))\n", " a1, a2 = 1.0 * (0.5 + ld), 0.4 * (0.5 + ld)\n", " W = (a1[:, None] * np.sin(2 * np.pi * 50 * TT + ph[:, :1]) + a2[:, None] * np.sin(2 * np.pi * 100 * TT + ph[:, 1:2])\n", " + rng.normal(0, 0.35, (S, NS)))\n", " W += (0.9 * dg * isb)[:, None] * np.sin(2 * np.pi * 165 * TT + ph[:, 2:3])\n", " W += (0.8 * dg * isg)[:, None] * (np.sin(2 * np.pi * 150 * TT + ph[:, 2:3]) + 0.8 * np.sin(2 * np.pi * 100 * TT))\n", " W += (0.5 * dg * iso)[:, None] * np.sin(2 * np.pi * 200 * TT + ph[:, 2:3])\n", " k = rng.poisson(10 * dg * isb) # ๋ฒ ์–ด๋ง ์—ดํ™”: ์ถฉ๊ฒฉ์„ฑ ํŽ„์Šค(์ฒจ๋„ ์ƒ์Šน)\n", " pos, amp = rng.integers(0, NS, (S, 30)), rng.normal(0, 3.0, (S, 30)) * dg[:, None]\n", " amp = amp * (np.arange(30)[None, :] < k[:, None])\n", " np.add.at(W, (np.repeat(np.arange(S), 30), pos.ravel()), amp.ravel())\n", " return units, snap, W\n", "\n", "\n", "def extract(W):\n", " \"\"\"์ง„๋™ ํŒŒํ˜• -> ํŠน์ง•๋Ÿ‰: RMS, ์ฒจ๋„, ํŒŒ๊ณ ์œจ, ์ตœ๋Œ€-์ตœ์†Œ, FFT ๋Œ€์—ญ ์—๋„ˆ์ง€(50/100/150/165/200Hz ๋ถ€๊ทผ)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 9] AAR ๊ธฐ๋ฐ˜ ์˜ˆ์ง€์ •๋น„ ์˜ค๊ฒฝ๋ณด ๋ถ„์„ ๋ฐ ์ด์ƒ ๊ฐ์ง€ ์ž„๊ณ„ ํŒŒ๋ผ๋ฏธํ„ฐ ์žฌ๋ณด์ • ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "BASE = [\"rms\", \"kurt\", \"crest\", \"p2p\", \"b50\", \"b100\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "ROLL = [\"rms\", \"kurt\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "\n", "\n", "def build_features(snap, W):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 9] AAR ๊ธฐ๋ฐ˜ ์˜ˆ์ง€์ •๋น„ ์˜ค๊ฒฝ๋ณด ๋ถ„์„ ๋ฐ ์ด์ƒ ๊ฐ์ง€ ์ž„๊ณ„ ํŒŒ๋ผ๋ฏธํ„ฐ ์žฌ๋ณด์ • ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "DERIVED = [\"temp_res\", \"press_res\", \"rms_n\", \"hf_ratio\"] + [c + \"_m3\" for c in ROLL] + [\"rms_n_d\", \"temp_res_d\", \"press_res_d\"]\n", "FEATS = BASE + DERIVED\n", "AE_COLS = [\"rms_n\", \"kurt\", \"b150\", \"b165\", \"b200\", \"hf_ratio\", \"temp_res\", \"press_res\"]\n", "\n", "\n", "def split_units(units, n_train=24):\n", " return list(units[\"unit\"][:n_train]), list(units[\"unit\"][n_train:])\n", "\n", "\n", "class AE:\n", " \"\"\"์˜คํ† ์ธ์ฝ”๋”(์ž…๋ ฅ 8 -> 10 -> 4 -> 10 -> 8): ์ •์ƒ ์ž๋ฃŒ๋งŒ ์žฌ๊ตฌ์„ฑํ•˜๋„๋ก ํ•™์Šตํ•˜๊ณ , ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ๋ฅผ ์ด์ƒ ์ ์ˆ˜๋กœ ์“ด๋‹ค.\"\"\"\n", "\n", " def __init__(self, cols=AE_COLS, q=0.99, seed=0):\n", " self.cols, self.q, self.seed = cols, q, seed\n", "\n", " def fit(self, healthy):\n", " self.sc = StandardScaler().fit(healthy[self.cols])\n", " Z = self.sc.transform(healthy[self.cols])\n", " self.net = MLPRegressor(hidden_layer_sizes=(10, 4, 10), max_iter=800, random_state=self.seed).fit(Z, Z)\n", " self.thr = float(np.quantile(self.err(healthy), self.q))\n", " return self\n", "\n", " def contrib(self, d):\n", " Z = self.sc.transform(d[self.cols])\n", " return (Z - self.net.predict(Z)) ** 2 # ์ž…๋ ฅ ํ•ญ๋ชฉ๋ณ„ ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ(์ด์ƒ ๊ธฐ์—ฌ๋„)\n", "\n", " def err(self, d):\n", " return self.contrib(d).mean(axis=1)\n", "\n", "\n", "def latch(d, e, thr, k=2):\n", " \"\"\"๊ฐ™์€ ๊ฐœ์ฒด์—์„œ ์ด์ƒ ์ ์ˆ˜๊ฐ€ k์ผ ์—ฐ์† ๊ธฐ์ค€์„ ๋„˜์œผ๋ฉด ๊ฒฝ๋ณด๋ฅผ ์ผœ๊ณ  ์œ ์ง€(๋ž˜์น˜)ํ•œ๋‹ค.\"\"\"\n", " out, e = np.zeros(len(d), bool), np.asarray(e)\n", " for idx in d.reset_index(drop=True).groupby(\"unit\").indices.values():\n", " run, on = 0, False\n", " for i in idx:\n", " run = run + 1 if e[i] > thr else 0\n", " on = on or run >= k\n", " out[i] = on\n", " return out\n", "\n", "FA_COST, FA_HOURS, LATE_COST = 1.0, 3.0, 0.15 # [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์˜ค๊ฒฝ๋ณด 1ํšŒ ์ ๊ฒ€ ๋น„์šฉ์ง€์ˆ˜, ์ ๊ฒ€ ์†Œ์š”์‹œ๊ฐ„(h), ํ™•๋ณด ์‹œ๊ฐ„ 1์ผ ์†์‹ค ๋น„์šฉ์ง€์ˆ˜\n", "units, snap, W = make_runs(seed=5)\n", "D = build_features(snap, W)\n", "order = list(units[\"unit\"])\n", "tr_u, aar_u, val_u = order[:24], order[24:36], order[36:]\n", "tr = D[D[\"unit\"].isin(tr_u)]\n", "ae = AE().fit(tr[tr[\"deg\"] == 0])\n", "healthy_err = ae.err(tr[tr[\"deg\"] == 0])\n", "E = {u: (D[D[\"unit\"] == u].reset_index(drop=True), ae.err(D[D[\"unit\"] == u])) for u in order}\n", "\n", "\n", "def evaluate(us, q, k, relax):\n", " \"\"\"๋ฐ˜ํ™˜: ์˜ค๊ฒฝ๋ณด ์—ํ”ผ์†Œ๋“œ ์ˆ˜, ์›์ธ ๋ถ„๋ฅ˜ ๊ฑด์ˆ˜, ๊ณ ์žฅ ์ „ ํ™•๋ณด ์‹œ๊ฐ„(์ผ) ๋ชฉ๋ก, ๋ฏธํƒ ๊ฐœ์ฒด ์ˆ˜, ์†์‹ค ํ™•๋ณด ์ผ์ˆ˜\"\"\"\n", " base_thr = float(np.quantile(healthy_err, q))\n", " fa, causes, leads, miss, lost = 0, {\"๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ\": 0, \"์™ธ๊ธฐ ๊ธ‰๋ณ€ยท๊ณ ์˜จ\": 0, \"์„ผ์„œ ์ŠคํŒŒ์ดํฌ\": 0, \"์—ดํ™” ์ดˆ๊ธฐ ๊ฒฝ๊ณ„\": 0, \"๊ธฐํƒ€\": 0}, [], 0, 0.0\n", " for u in us:\n", " g, e = E[u]\n", " thr = base_thr * np.where(g[\"load\"].values > 0.95, 1 + relax, 1.0)\n", " over = e > thr\n", " run = np.zeros(len(g), int)\n", " for i in range(len(g)):\n", " run[i] = run[i - 1] + 1 if over[i] and i else int(over[i])\n", " al = run >= k\n", " an = (g[\"deg\"] >= 0.1).values\n", " start = al & ~np.r_[False, al[:-1]]\n", " for i in np.where(start & ~an)[0]:\n", " fa += 1\n", " r = g.iloc[i]\n", " c = \"๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ\" if r[\"hi_load\"] else (\"์™ธ๊ธฐ ๊ธ‰๋ณ€ยท๊ณ ์˜จ\" if r[\"ambient\"] > 24 else (\"์„ผ์„œ ์ŠคํŒŒ์ดํฌ\" if r[\"kurt\"] > 3.2 else (\"์—ดํ™” ์ดˆ๊ธฐ ๊ฒฝ๊ณ„\" if r[\"deg\"] > 0 else \"๊ธฐํƒ€\")))\n", " causes[c] += 1\n", " first_an = np.argmax(an)\n", " hit = np.where(al & an)[0]\n", " ideal = g[\"rul\"].iloc[first_an]\n", " if len(hit):\n", " leads.append(int(g[\"rul\"].iloc[hit[0]])); lost += ideal - g[\"rul\"].iloc[hit[0]]\n", " else:\n", " miss += 1; lost += ideal\n", " return fa, causes, leads, miss, lost\n", "\n", "\n", "def J(res):\n", " return FA_COST * res[0] + LATE_COST * res[4]\n", "\n", "\n", "cur = dict(q=0.99, k=1, relax=0.0) # ํ˜„ํ–‰(AAR ์ด์ „) ํŒŒ๋ผ๋ฏธํ„ฐ\n", "r_aar = evaluate(aar_u, **cur)\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] AAR ๊ธฐ๊ฐ„ {len(aar_u)}๋Œ€: ํ˜„ํ–‰ ํŒŒ๋ผ๋ฏธํ„ฐ(์ •์ƒ {cur['q']:.0%} ๋ถ„์œ„, {cur['k']}์ผ, ๊ณ ๋ถ€ํ•˜ ์™„ํ™” {cur['relax']:.0%}) ์˜ค๊ฒฝ๋ณด {r_aar[0]}๊ฑด\")\n", "tot = sum(r_aar[1].values())\n", "for c, v in sorted(r_aar[1].items(), key=lambda x: -x[1]):\n", " print(f\" {c:10s} {v:3d}๊ฑด ({v / max(tot, 1):.0%})\")\n", "\n", "# ์žฌ๋ณด์ •: AAR ๊ธฐ๊ฐ„ ์ž๋ฃŒ๋งŒ์œผ๋กœ ๊ฒฉ์ž ํƒ์ƒ‰ (๋ฏธํƒ ๊ฐœ์ฒด๊ฐ€ ์—†๋Š” ์กฐํ•ฉ ์ค‘ ๋น„์šฉ ์ตœ์†Œ)\n", "grid = []\n", "for q, k, rl in itertools.product((0.98, 0.99, 0.995, 0.999), (1, 2, 3), (0.0, 0.3, 0.6)):\n", " r = evaluate(aar_u, q, k, rl)\n", " if r[3] == 0:\n", " grid.append((J(r), q, k, rl))\n", "grid.sort()\n", "best = dict(q=grid[0][1], k=grid[0][2], relax=grid[0][3])\n", "print(f\"\\n์žฌ๋ณด์ • ๊ฒฐ๊ณผ(๊ฒฉ์ž {4 * 3 * 3}๊ฐœ ์ค‘ ๋ฏธํƒ 0๊ฐœ ์กฐํ•ฉ {len(grid)}๊ฐœ ํƒ์ƒ‰): ์ •์ƒ {best['q']:.1%} ๋ถ„์œ„, ์—ฐ์† {best['k']}์ผ, ๊ณ ๋ถ€ํ•˜์ผ ์ž„๊ณ„ {best['relax']:.0%} ์™„ํ™”\")\n", "\n", "rows = []\n", "for nm, us in ((\"AAR ๊ธฐ๊ฐ„(ํŠœ๋‹)\", aar_u), (\"๊ฒ€์ฆ ๊ธฐ๊ฐ„(๋ฏธ์‚ฌ์šฉ)\", val_u)):\n", " a, b = evaluate(us, **cur), evaluate(us, **best)\n", " rows.append((nm, a, b))\n", " print(f\" {nm:14s} ์˜ค๊ฒฝ๋ณด {a[0]:3d} -> {b[0]:3d}๊ฑด, ๊ณ ์žฅ ์ „ ํ™•๋ณด ์ค‘์•™๊ฐ’ {np.median(a[2]):.0f} -> {np.median(b[2]):.0f}์ผ, ๋ฏธํƒ {a[3]} -> {b[3]}๋Œ€\")\n", "\n", "# 3๋Œ€ ์ •๋Ÿ‰ ํšจ๊ณผ ์ง€ํ‘œ(๊ฒ€์ฆ 3๊ฐœ์›” ๋Œ€์‹  ๊ฒ€์ฆ 12๋Œ€ ๊ธฐ์ค€, ์—ฐํ™˜์‚ฐ์€ ๊ฐœ์ฒด ์ˆ˜ ๋น„๋ก€)\n", "a, b = rows[1][1], rows[1][2]\n", "jc, jn = J(a), J(b)\n", "print(\"\\n[3๋Œ€ ์ •๋Ÿ‰ ํšจ๊ณผ ์ง€ํ‘œ] (๊ฒ€์ฆ 12๋Œ€, ๊ฐ€์ • ๋‹จ๊ฐ€)\")\n", "print(f\" 1) ๋น„์šฉ ์ ˆ๊ฐ: ๋น„์šฉ์ง€์ˆ˜ {jc:.1f} -> {jn:.1f} ({1 - jn / jc:.1%} ์ ˆ๊ฐ), 48๋Œ€ ์—ฐํ™˜์‚ฐ ์•ฝ {(jc - jn) * 4:.0f}\")\n", "print(f\" 2) ์‹œ๊ฐ„ ๋‹จ์ถ•: ๋ถˆํ•„์š” ์ ๊ฒ€ {a[0] * FA_HOURS:.0f}h -> {b[0] * FA_HOURS:.0f}h ({1 - b[0] / max(a[0], 1):.1%} ๋‹จ์ถ•)\")\n", "print(f\" 3) ํ™•๋ณด ์‹œ๊ฐ„: ๊ณ ์žฅ ์ „ ์ฒซ ๊ฒฝ๋ณด ํ™•๋ณด ์ค‘์•™๊ฐ’ {np.median(a[2]):.0f}์ผ -> {np.median(b[2]):.0f}์ผ, ์ •์ƒ ์˜ค๊ฒฝ๋ณด {a[0]}๊ฑด -> {b[0]}๊ฑด\")\n", "print(\"[๋„์ž… ๋กœ๋“œ๋งต] ๋‹จ๊ธฐ(1~3์›”): ์žฌ๋ณด์ • ์ž„๊ณ„ ์ ์šฉ + ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ ๋ณ‘ํ–‰ / ์ค‘๊ธฐ(3~12์›”): ๋ถ„๊ธฐ AAR๋กœ ์ž„๊ณ„ ์žฌ๋ณด์ •, ๋ถ€ํ•˜ ๋ณด์ • ํ•ญ ์ถ”๊ฐ€ / ์žฅ๊ธฐ(1๋…„~): ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„ ์ •๋น„ ์ด๋ ฅ๊ณผ ์—ฐ๋™ํ•ด ์ž„๊ณ„ ์ž๋™ ๊ฐฑ์‹ \")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 09 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] AAR ๊ธฐ๊ฐ„ 12๋Œ€: ํ˜„ํ–‰ ํŒŒ๋ผ๋ฏธํ„ฐ(์ •์ƒ 99% ๋ถ„์œ„, 1์ผ, ๊ณ ๋ถ€ํ•˜ ์™„ํ™” 0%) ์˜ค๊ฒฝ๋ณด 9๊ฑด\n", " ๊ธฐํƒ€ 5๊ฑด (56%)\n", " ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ 2๊ฑด (22%)\n", " ์—ดํ™” ์ดˆ๊ธฐ ๊ฒฝ๊ณ„ 2๊ฑด (22%)\n", " ์™ธ๊ธฐ ๊ธ‰๋ณ€ยท๊ณ ์˜จ 0๊ฑด (0%)\n", " ์„ผ์„œ ์ŠคํŒŒ์ดํฌ 0๊ฑด (0%)\n", "\n", "์žฌ๋ณด์ • ๊ฒฐ๊ณผ(๊ฒฉ์ž 36๊ฐœ ์ค‘ ๋ฏธํƒ 0๊ฐœ ์กฐํ•ฉ 36๊ฐœ ํƒ์ƒ‰): ์ •์ƒ 99.5% ๋ถ„์œ„, ์—ฐ์† 1์ผ, ๊ณ ๋ถ€ํ•˜์ผ ์ž„๊ณ„ 30% ์™„ํ™”\n", " AAR ๊ธฐ๊ฐ„(ํŠœ๋‹) ์˜ค๊ฒฝ๋ณด 9 -> 4๊ฑด, ๊ณ ์žฅ ์ „ ํ™•๋ณด ์ค‘์•™๊ฐ’ 58 -> 56์ผ, ๋ฏธํƒ 0 -> 0๋Œ€\n", " ๊ฒ€์ฆ ๊ธฐ๊ฐ„(๋ฏธ์‚ฌ์šฉ) ์˜ค๊ฒฝ๋ณด 16 -> 10๊ฑด, ๊ณ ์žฅ ์ „ ํ™•๋ณด ์ค‘์•™๊ฐ’ 60 -> 57์ผ, ๋ฏธํƒ 0 -> 0๋Œ€\n", "\n", "[3๋Œ€ ์ •๋Ÿ‰ ํšจ๊ณผ ์ง€ํ‘œ] (๊ฒ€์ฆ 12๋Œ€, ๊ฐ€์ • ๋‹จ๊ฐ€)\n", " 1) ๋น„์šฉ ์ ˆ๊ฐ: ๋น„์šฉ์ง€์ˆ˜ 24.7 -> 22.0 (10.9% ์ ˆ๊ฐ), 48๋Œ€ ์—ฐํ™˜์‚ฐ ์•ฝ 11\n", " 2) ์‹œ๊ฐ„ ๋‹จ์ถ•: ๋ถˆํ•„์š” ์ ๊ฒ€ 48h -> 30h (37.5% ๋‹จ์ถ•)\n", " 3) ํ™•๋ณด ์‹œ๊ฐ„: ๊ณ ์žฅ ์ „ ์ฒซ ๊ฒฝ๋ณด ํ™•๋ณด ์ค‘์•™๊ฐ’ 60์ผ -> 57์ผ, ์ •์ƒ ์˜ค๊ฒฝ๋ณด 16๊ฑด -> 10๊ฑด\n", "[๋„์ž… ๋กœ๋“œ๋งต] ๋‹จ๊ธฐ(1~3์›”): ์žฌ๋ณด์ • ์ž„๊ณ„ ์ ์šฉ + ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ ๋ณ‘ํ–‰ / ์ค‘๊ธฐ(3~12์›”): ๋ถ„๊ธฐ AAR๋กœ ์ž„๊ณ„ ์žฌ๋ณด์ •, ๋ถ€ํ•˜ ๋ณด์ • ํ•ญ ์ถ”๊ฐ€ / ์žฅ๊ธฐ(1๋…„~): ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„ ์ •๋น„ ์ด๋ ฅ๊ณผ ์—ฐ๋™ํ•ด ์ž„๊ณ„ ์ž๋™ ๊ฐฑ์‹ \n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 10. ํ•ญ๊ณต/๊ธฐ๊ฐ‘ ์žฅ๋น„ ์˜ˆ์ง€์ •๋น„(PdM) ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ด์ƒ ์ง•ํ›„ ๊ฐ์ง€ ์‹œ์Šคํ…œ ํ†ตํ•ฉ ์™„์„ฑ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "ํ•ญ๊ณต/๊ธฐ๊ฐ‘ ์žฅ๋น„ ์˜ˆ์ง€์ •๋น„(PdM) ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ด์ƒ ์ง•ํ›„ ๊ฐ์ง€ ์‹œ์Šคํ…œ์„ ์™„์„ฑํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์„ผ์„œ ๊ฒ€์ฆ๋ถ€ํ„ฐ ์ด์ƒ ๊ฐ์ง€, RUL ์˜ˆ์ธก, ์Šน์ธ ๋Œ€๊ธฐ ์ž…๊ณ  ์ œ์•ˆ, ์ˆ˜๋ฆฌ๋ถ€์† ์†Œ์š” ์˜ˆ๊ณ , ์žฌํ•™์Šต๊นŒ์ง€ ํ•œ ํŒŒ์ผ๋กœ ํ†ตํ•ฉํ•˜๊ณ  ์ž์ฒด ์ ๊ฒ€๊ณผ ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ์„ ์ถœ๋ ฅํ•˜๋Š” ๋„๊ตฌ๋ฅผ ์™„์„ฑํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ง€๋‚œ๋‹ฌยท์ด๋ฒˆ ๋‹ฌ ์‹ค์ ์œผ๋กœ ์ฑ”ํ”ผ์–ธ ๋ชจ๋ธ๊ณผ ๋„์ „์ž ๋ชจ๋ธ์„ ํ•™์Šตํ•ด ๊ฒ€์ฆ F1์„ ๋น„๊ตํ•˜๊ณ  ๋น„์Šทํ•˜๊ฑฐ๋‚˜ ๋‚˜์„ ๋•Œ๋งŒ ๊ต์ฒด ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/mnt04_operations_guide.md ๋ฐ pipeline/mnt04_retrain.py` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์ง€์† ๊ฐ€๋Šฅํ•œ ์šด์˜ ๊ฐ€๋Šฅ์„ฑ ๋ฐ ๊ตญ๋ฐฉ ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„์™€์˜ ์—ฐ๋™ ์šฉ์ด์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์ž๋™ ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์ •์ƒ ์‹คํ–‰, ์ตœ์ข… ๋ฌธ์„œ 100% ์™„๋น„\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์ตœ์ข… ์ธ๋„๋ฌผ ๊ฒ€์ˆ˜ ๋ฐ ํ”„๋กœ์ ํŠธ ์™„๋ฃŒ ๋ฐœํ‘œ์ž๋ฃŒ ์™„์„ฑ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: End-to-End ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ ์ž๋™ํ™” ์‹คํ–‰ ๋ฐ ์ตœ์ข… ๊ฒ€์ˆ˜\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์„ผ์„œ ํŒŒํ˜• ๊ฒ€์ฆยท์ •์ œ, ์˜คํ† ์ธ์ฝ”๋” ์ด์ƒ ๊ฐ์ง€ ๋ชจ๋ธ์˜ ์ž๋™ ์žฌํ•™์Šต(์ฑ”ํ”ผ์–ธยท๋„์ „์ž ๋น„๊ต ํ›„ ๊ต์ฒด), ์žฌ๋ณด์ • ์ž„๊ณ„ ์ ์šฉ ๊ฒฝ๋ณด, ์ž”์กด์ˆ˜๋ช…(RUL) ์˜ˆ์ธก, ๊ฒ€์‚ฌ๊ด€ ์Šน์ธ ๋Œ€๊ธฐ ์ž…๊ณ  ์ œ์•ˆ, ์ˆ˜๋ฆฌ๋ถ€์† ์†Œ์š” ์˜ˆ๊ณ ๊นŒ์ง€ ์˜ˆ์ง€์ •๋น„(PdM) ์ด์ƒ ๊ฐ์ง€ ์‹œ์Šคํ…œ์„ ํ•œ ํŒŒ์ผ๋กœ ํ†ตํ•ฉ ์‹คํ–‰ํ•˜๊ณ  ์ž์ฒด ์ ๊ฒ€ยท์šด์˜ ๋ฌธ์„œ ์™„๋น„ ์ ๊ฒ€ยท์ •๋น„ ๋‹ด๋‹น ์žฅ๊ต ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ์„ ์ถœ๋ ฅํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.stats import kurtosis\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "from sklearn.neural_network import MLPRegressor\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๊ธฐ๋™์ฒด 48๋Œ€์˜ ์—”์ง„ยท๊ตฌ๋™๊ณ„ ์ง„๋™ยท์˜จ๋„ยท์••๋ ฅ ์„ผ์„œ ์ž๋ฃŒ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๊ฐœ์ฒด๋งˆ๋‹ค ์ •์ƒ -> ์—ดํ™” -> ๊ณ ์žฅ(์ˆ˜๋ช…์ข…๋ฃŒ)๊นŒ์ง€ ํ•˜๋ฃจ 1ํšŒ ์ธก์ •(์ง„๋™ ํŒŒํ˜• 256ํ‘œ๋ณธ + ์˜จ๋„ + ์••๋ ฅ + ๋ถ€ํ•˜)์„ ๋งŒ๋“ ๋‹ค.\n", "# ์—ดํ™” ์œ ํ˜• 4๊ฐ€์ง€: bearing(๋ฒ ์–ด๋ง), gear(๊ธฐ์–ด), oil_pump(์˜ค์ผ ํŽŒํ”„), cooling(๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ)\n", "# ---------------------------------------------------------------------------\n", "FS, NS = 1024, 256 # ๊ฐ€์ƒ ํ‘œ๋ณธํ™” ์ฃผํŒŒ์ˆ˜(Hz), ํŒŒํ˜• 1๊ฐœ๋‹น ํ‘œ๋ณธ ์ˆ˜\n", "TT = np.arange(NS) / FS\n", "MODES = { # ์—ดํ™” ์œ ํ˜•: (๋ฐœ์ƒ ํ™•๋ฅ , ํ‰๊ท  ์ˆ˜๋ช…(์ผ), ๊ต์ฒด ์ˆ˜๋ฆฌ๋ถ€์†, ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜)\n", " \"bearing\": (0.40, 160, \"์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ\", 6.0), \"gear\": (0.25, 175, \"๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ\", 9.0),\n", " \"oil_pump\": (0.20, 150, \"์˜ค์ผ ํŽŒํ”„\", 4.0), \"cooling\": (0.15, 140, \"๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ\", 3.0)}\n", "KIT_COST = 1.0 # ๋ชจ๋“  ์ •๋น„์— ํ•จ๊ป˜ ์†Œ๋ชจ๋˜๋Š” ์”ฐยทํ•„ํ„ฐ ํ‚คํŠธ ๋‹จ๊ฐ€์ง€์ˆ˜\n", "BANDS = {\"b50\": (40, 60), \"b100\": (90, 110), \"b150\": (140, 156), \"b165\": (156, 176), \"b200\": (190, 215)}\n", "\n", "\n", "def make_runs(n=48, seed=1):\n", " \"\"\"๋ฐ˜ํ™˜: units(๊ฐœ์ฒด ์ •๋ณด), snap(์ผ๋ณ„ ์ธก์ • ์š”์•ฝ), W(์ง„๋™ ํŒŒํ˜• [์ธก์ • ์ˆ˜, 256])\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(MODES)\n", " mode = rng.choice(names, n, p=[MODES[m][0] for m in names])\n", " life = np.clip(np.array([rng.normal(MODES[m][1], 18) for m in mode]), 90, 230).astype(int)\n", " start = np.sort(rng.integers(0, 240, n))\n", " units = pd.DataFrame({\"unit\": [f\"U{i:02d}\" for i in range(n)], \"mode\": mode, \"life\": life, \"start\": start, \"base_load\": rng.uniform(0.4, 0.75, n)})\n", " rows = []\n", " for u in units.itertuples():\n", " d = np.arange(u.life + 1)\n", " onset = 0.45 * u.life\n", " deg = np.clip((d - onset) / (u.life - onset), 0, 1) ** 1.5\n", " load = np.clip(u.base_load + rng.normal(0, 0.06, len(d)), 0.2, 1.0)\n", " hi = rng.random(len(d)) < 0.07 # ๊ณ ๋ถ€ํ•˜ ์šด์šฉ์ผ(์ •์ƒ์ด์ง€๋งŒ ์ง„๋™ยท์˜จ๋„ ์ƒ์Šน)\n", " load = np.where(hi, rng.uniform(0.95, 1.1, len(d)), load)\n", " rows.append(pd.DataFrame({\"unit\": u.unit, \"day\": d, \"cal\": u.start + d, \"rul\": u.life - d, \"deg\": deg, \"load\": load, \"hi_load\": hi, \"mode\": u.mode}))\n", " snap = pd.concat(rows, ignore_index=True)\n", " S = len(snap)\n", " snap[\"ambient\"] = 15 + 12 * np.sin(2 * np.pi * snap[\"cal\"] / 365) + rng.normal(0, 2, S)\n", " dg, ld, md = snap[\"deg\"].values, snap[\"load\"].values, snap[\"mode\"].values\n", " isb, isg, iso, isc = (md == \"bearing\"), (md == \"gear\"), (md == \"oil_pump\"), (md == \"cooling\")\n", " snap[\"temp\"] = 55 + 22 * ld + 0.5 * snap[\"ambient\"] + 9 * dg * iso + 26 * dg * isc + 3 * dg * (isb | isg) + rng.normal(0, 1.5, S)\n", " snap[\"press\"] = 450 - 12 * ld - 85 * dg * iso - 15 * dg * isc + rng.normal(0, 6, S)\n", " ph = rng.uniform(0, 2 * np.pi, (S, 3))\n", " a1, a2 = 1.0 * (0.5 + ld), 0.4 * (0.5 + ld)\n", " W = (a1[:, None] * np.sin(2 * np.pi * 50 * TT + ph[:, :1]) + a2[:, None] * np.sin(2 * np.pi * 100 * TT + ph[:, 1:2])\n", " + rng.normal(0, 0.35, (S, NS)))\n", " W += (0.9 * dg * isb)[:, None] * np.sin(2 * np.pi * 165 * TT + ph[:, 2:3])\n", " W += (0.8 * dg * isg)[:, None] * (np.sin(2 * np.pi * 150 * TT + ph[:, 2:3]) + 0.8 * np.sin(2 * np.pi * 100 * TT))\n", " W += (0.5 * dg * iso)[:, None] * np.sin(2 * np.pi * 200 * TT + ph[:, 2:3])\n", " k = rng.poisson(10 * dg * isb) # ๋ฒ ์–ด๋ง ์—ดํ™”: ์ถฉ๊ฒฉ์„ฑ ํŽ„์Šค(์ฒจ๋„ ์ƒ์Šน)\n", " pos, amp = rng.integers(0, NS, (S, 30)), rng.normal(0, 3.0, (S, 30)) * dg[:, None]\n", " amp = amp * (np.arange(30)[None, :] < k[:, None])\n", " np.add.at(W, (np.repeat(np.arange(S), 30), pos.ravel()), amp.ravel())\n", " return units, snap, W\n", "\n", "\n", "def extract(W):\n", " \"\"\"์ง„๋™ ํŒŒํ˜• -> ํŠน์ง•๋Ÿ‰: RMS, ์ฒจ๋„, ํŒŒ๊ณ ์œจ, ์ตœ๋Œ€-์ตœ์†Œ, FFT ๋Œ€์—ญ ์—๋„ˆ์ง€(50/100/150/165/200Hz ๋ถ€๊ทผ)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 10] ํ•ญ๊ณต/๊ธฐ๊ฐ‘ ์žฅ๋น„ ์˜ˆ์ง€์ •๋น„(PdM) ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ด์ƒ ์ง•ํ›„ ๊ฐ์ง€ ์‹œ์Šคํ…œ ํ†ตํ•ฉ ์™„์„ฑ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "BASE = [\"rms\", \"kurt\", \"crest\", \"p2p\", \"b50\", \"b100\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "ROLL = [\"rms\", \"kurt\", \"b150\", \"b165\", \"b200\", \"temp\", \"press\"]\n", "\n", "\n", "def build_features(snap, W):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 10] ํ•ญ๊ณต/๊ธฐ๊ฐ‘ ์žฅ๋น„ ์˜ˆ์ง€์ •๋น„(PdM) ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ด์ƒ ์ง•ํ›„ ๊ฐ์ง€ ์‹œ์Šคํ…œ ํ†ตํ•ฉ ์™„์„ฑ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "DERIVED = [\"temp_res\", \"press_res\", \"rms_n\", \"hf_ratio\"] + [c + \"_m3\" for c in ROLL] + [\"rms_n_d\", \"temp_res_d\", \"press_res_d\"]\n", "FEATS = BASE + DERIVED\n", "AE_COLS = [\"rms_n\", \"kurt\", \"b150\", \"b165\", \"b200\", \"hf_ratio\", \"temp_res\", \"press_res\"]\n", "\n", "\n", "def split_units(units, n_train=24):\n", " return list(units[\"unit\"][:n_train]), list(units[\"unit\"][n_train:])\n", "\n", "\n", "class AE:\n", " \"\"\"์˜คํ† ์ธ์ฝ”๋”(์ž…๋ ฅ 8 -> 10 -> 4 -> 10 -> 8): ์ •์ƒ ์ž๋ฃŒ๋งŒ ์žฌ๊ตฌ์„ฑํ•˜๋„๋ก ํ•™์Šตํ•˜๊ณ , ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ๋ฅผ ์ด์ƒ ์ ์ˆ˜๋กœ ์“ด๋‹ค.\"\"\"\n", "\n", " def __init__(self, cols=AE_COLS, q=0.99, seed=0):\n", " self.cols, self.q, self.seed = cols, q, seed\n", "\n", " def fit(self, healthy):\n", " self.sc = StandardScaler().fit(healthy[self.cols])\n", " Z = self.sc.transform(healthy[self.cols])\n", " self.net = MLPRegressor(hidden_layer_sizes=(10, 4, 10), max_iter=800, random_state=self.seed).fit(Z, Z)\n", " self.thr = float(np.quantile(self.err(healthy), self.q))\n", " return self\n", "\n", " def contrib(self, d):\n", " Z = self.sc.transform(d[self.cols])\n", " return (Z - self.net.predict(Z)) ** 2 # ์ž…๋ ฅ ํ•ญ๋ชฉ๋ณ„ ์žฌ๊ตฌ์„ฑ ์˜ค์ฐจ(์ด์ƒ ๊ธฐ์—ฌ๋„)\n", "\n", " def err(self, d):\n", " return self.contrib(d).mean(axis=1)\n", "\n", "\n", "def latch(d, e, thr, k=2):\n", " \"\"\"๊ฐ™์€ ๊ฐœ์ฒด์—์„œ ์ด์ƒ ์ ์ˆ˜๊ฐ€ k์ผ ์—ฐ์† ๊ธฐ์ค€์„ ๋„˜์œผ๋ฉด ๊ฒฝ๋ณด๋ฅผ ์ผœ๊ณ  ์œ ์ง€(๋ž˜์น˜)ํ•œ๋‹ค.\"\"\"\n", " out, e = np.zeros(len(d), bool), np.asarray(e)\n", " for idx in d.reset_index(drop=True).groupby(\"unit\").indices.values():\n", " run, on = 0, False\n", " for i in idx:\n", " run = run + 1 if e[i] > thr else 0\n", " on = on or run >= k\n", " out[i] = on\n", " return out\n", "\n", "CONFIG = {\"q\": 0.995, \"k\": 1, \"relax\": 0.3, \"retrain_tolerance\": 0.02, \"approve_rul\": 90, \"spare_horizon\": 60} # ๋ฏธ์…˜ 9 ์žฌ๋ณด์ • ๊ฒฐ๊ณผ\n", "OPS_GUIDE = { # docs/mnt04_operations_guide.md ์š”์•ฝ\n", " \"1. ๋ชฉ์ ๊ณผ ๋ฒ”์œ„\": \"์—”์ง„ยท๊ตฌ๋™๊ณ„ ์„ผ์„œ๋กœ ์ด์ƒ ์ง•ํ›„์™€ ์ž”์กด์ˆ˜๋ช…์„ ๋ณด์กฐ ํŒ๋‹จ. ์ž…๊ณ ยท์ •๋น„ ๊ฒฐ์ •์€ ๊ฒ€์‚ฌ๊ด€.\",\n", " \"2. ์ž…๋ ฅ ๋ฐ์ดํ„ฐ\": \"์ผ 1ํšŒ ์ง„๋™ ํŒŒํ˜•ยท์˜จ๋„ยท์••๋ ฅยท๋ถ€ํ•˜. ์‹ค์ œ ์žฅ๋น„ ์ž๋ฃŒ ๋Œ€์‹  ๊ฐ€์ƒ ์‹๋ณ„์ฝ”๋“œ ์‚ฌ์šฉ.\",\n", " \"3. ์ผ์ผ ์šด์šฉ ์ ˆ์ฐจ\": \"์ˆ˜์ง‘ -> ๊ฒ€์ฆยท๊ฒฉ๋ฆฌ -> ํŠน์ง• ์ถ”์ถœ -> ์ด์ƒ ์ ์ˆ˜ -> ๊ฒฝ๋ณด -> ์ž…๊ณ  ์ œ์•ˆ(๋Œ€๊ธฐ) -> ๊ฒ€์‚ฌ๊ด€ ๊ฒฐ์ • -> ๊ฒฐ๊ณผ ๊ธฐ๋ก.\",\n", " \"4. ์Šน์ธ(HITL) ๊ธฐ์ค€\": \"์กฐ๊ธฐ ์ž…๊ณ  ์ œ์•ˆ์€ ๊ฒ€์‚ฌ๊ด€ ์Šน์ธ ํ›„ ํ™•์ •, ๋ฌด์‘๋‹ต ์‹œ ๊ธฐ๋ณธ ๊ณ„ํš(์ •๊ธฐ ๊ต์ฒด) ์œ ์ง€, ์‚ฌ์œ  ๊ธฐ๋ก.\",\n", " \"5. ์žฌํ•™์Šต ์ ˆ์ฐจ\": \"๋ถ„๊ธฐ 1ํšŒ ์‹ ๊ทœ ์ •์ƒยท๊ณ ์žฅ ์‹ค์ ์œผ๋กœ ๋„์ „์ž ํ•™์Šต, ๊ฒ€์ฆ F1์ด ๋‚˜๋น ์ง€์ง€ ์•Š์„ ๋•Œ๋งŒ ๊ต์ฒด.\",\n", " \"6. ์žฅ์•  ๋Œ€์‘\": \"๋ชจ๋ธ ์˜ค๋ฅ˜ ์‹œ ์ •๊ธฐ ๊ต์ฒด ์ฃผ๊ธฐ ๊ธฐ์ค€์œผ๋กœ ๋Œ€์ฒด, ์†์ƒ ์„ผ์„œ ์ž๋ฃŒ๋Š” ์ˆ˜๋™ ํ™•์ธ ๋ชฉ๋ก์œผ๋กœ.\",\n", " \"7. ์—ฐ๋™ ์ธํ„ฐํŽ˜์ด์Šค\": \"๊ตญ๋ฐฉ ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„์™€ ์ •๋น„ ์ด๋ ฅยท์ˆ˜๋ฆฌ๋ถ€์† ์†Œ๋ชจ ํ‘œ์ค€ ํ•„๋“œ(์žฅ๋น„์ฝ”๋“œยท๋ถ„๋ฅ˜๊ตฐยท์ผ์‹œ)๋ฅผ CSV/API๋กœ ๊ตํ™˜ํ•˜๋Š” ์•ˆ์„ ์ฐจ๊ธฐ ๊ณผ์ œ๋กœ ๋‘”๋‹ค.\",\n", "}\n", "F10 = FEATS + [\"day\", \"hf_max\", \"temp_res_max\"]\n", "\n", "\n", "def prep(units, snap, W):\n", " \"\"\"๊ฒ€์ฆ: ํŒŒํ˜• ๊ฒฐ์ธกยท๊ณ ์ •ยทํฌํ™”, ๊ฐ’ ๋ฒ”์œ„ ์ดํƒˆ ์ธก์ •์€ ๊ฒฉ๋ฆฌํ•˜๊ณ  ๋‚˜๋จธ์ง€๋กœ ํŠน์ง•ํ‘œ๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " bad = np.isnan(W).any(axis=1) | (np.nanstd(W, axis=1) < 1e-6) | (np.abs(np.nan_to_num(W)).max(axis=1) > 12) | ~snap[\"temp\"].between(20, 160).values\n", " ok = ~bad\n", " D = build_features(snap[ok].reset_index(drop=True), W[ok])\n", " D[\"hf_max\"] = D.groupby(\"unit\")[\"hf_ratio\"].cummax(); D[\"temp_res_max\"] = D.groupby(\"unit\")[\"temp_res\"].cummax()\n", " return D, int(bad.sum())\n", "\n", "\n", "def alarms(D, e, thr, cfg=CONFIG):\n", " over = np.asarray(e) > thr * np.where(D[\"load\"].values > 0.95, 1 + cfg[\"relax\"], 1.0)\n", " out = np.zeros(len(D), bool)\n", " for idx in D.reset_index(drop=True).groupby(\"unit\").indices.values():\n", " run = 0\n", " for i in idx:\n", " run = run + 1 if over[i] else 0\n", " out[i] = run >= cfg[\"k\"]\n", " return out\n", "\n", "\n", "def f1(D, al):\n", " y = (D[\"deg\"] >= 0.1).values\n", " tp, fp, fn = (al & y).sum(), (al & ~y).sum(), (~al & y).sum()\n", " return float(2 * tp / (2 * tp + fp + fn))\n", "\n", "\n", "def retrain(champion, D_old, D_new):\n", " \"\"\"์ฑ”ํ”ผ์–ธ AE(์ง€๋‚œ๋‹ฌ ์ž๋ฃŒ) vs ๋„์ „์ž AE(์ง€๋‚œ๋‹ฌ+์ด๋ฒˆ ๋‹ฌ ํ•™์Šต ๊ฐœ์ฒด) -> ์ด๋ฒˆ ๋‹ฌ ๊ฒ€์ฆ ๊ฐœ์ฒด F1 ๋น„๊ต.\"\"\"\n", " new_tr, new_va = D_new[D_new[\"unit\"].isin(list(D_new[\"unit\"].unique())[:24])], D_new[D_new[\"unit\"].isin(list(D_new[\"unit\"].unique())[24:])]\n", " ch = AE(seed=1).fit(pd.concat([D_old[D_old[\"deg\"] == 0], new_tr[new_tr[\"deg\"] == 0]]))\n", " thr_c = float(np.quantile(champion.err(D_old[D_old[\"deg\"] == 0]), CONFIG[\"q\"]))\n", " thr_n = float(np.quantile(ch.err(pd.concat([D_old[D_old[\"deg\"] == 0], new_tr[new_tr[\"deg\"] == 0]])), CONFIG[\"q\"]))\n", " f_old, f_new = f1(new_va, alarms(new_va, champion.err(new_va), thr_c)), f1(new_va, alarms(new_va, ch.err(new_va), thr_n))\n", " swap = f_new >= f_old - CONFIG[\"retrain_tolerance\"]\n", " return (ch if swap else champion), (thr_n if swap else thr_c), {\"์ฑ”ํ”ผ์–ธ F1\": round(f_old, 3), \"๋„์ „์ž F1\": round(f_new, 3), \"๊ต์ฒด\": bool(swap)}\n", "\n", "\n", "def predict_safe(model, D):\n", " try:\n", " return np.maximum(model.predict(D[F10]), 0), \"๋ชจ๋ธ\"\n", " except Exception:\n", " return np.maximum(90 - D[\"day\"].values, 0).astype(float), \"์ •๊ธฐ ์ฃผ๊ธฐ ๊ธฐ์ค€ ๋Œ€์ฒด\"\n", "\n", "\n", "# ---------------- 1. ํ†ตํ•ฉ ์‹คํ–‰ ----------------\n", "t_start = time.perf_counter()\n", "Dold, _ = prep(*make_runs(seed=11)) # ์ง€๋‚œ๋‹ฌ ์‹ค์ \n", "Dnew, _ = prep(*make_runs(seed=1)) # ์ด๋ฒˆ ๋‹ฌ ์‹ค์ \n", "u3, s3, w3 = make_runs(seed=21) # ๋‹ค์Œ ๋‹ฌ ์šด์šฉ(์‹ ๊ทœ ์ธก์ •)\n", "w3[[100, 2000], 20:50] = np.nan # ์†์ƒ ์ธก์ • 2๊ฑด ์ฃผ์ž…(๊ฒฐ์ธก ํŒจํ‚ท)\n", "Dop, n_bad = prep(u3, s3, w3)\n", "champion = AE().fit(Dold[Dold[\"deg\"] == 0])\n", "champ_thr = float(np.quantile(champion.err(Dold[Dold[\"deg\"] == 0]), CONFIG[\"q\"]))\n", "model_ae, thr, rt = retrain(champion, Dold, Dnew)\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฌํ•™์Šต: {rt} -> {'๋„์ „์ž ๊ต์ฒด' if rt['๊ต์ฒด'] else '์ฑ”ํ”ผ์–ธ ์œ ์ง€'}\")\n", "gbr = GradientBoostingRegressor(n_estimators=100, max_depth=3, learning_rate=0.1, subsample=0.8, random_state=0).fit(Dnew[F10], np.minimum(Dnew[\"rul\"], 120))\n", "e = model_ae.err(Dop)\n", "Dop[\"alarm\"] = alarms(Dop, e, thr)\n", "Dop[\"rul_hat\"], src = predict_safe(gbr, Dop)\n", "Dop[\"score\"] = e / thr\n", "sec = time.perf_counter() - t_start\n", "print(f\"์šด์šฉ ์ธก์ • {len(s3)}๊ฑด: ์ฒ˜๋ฆฌ {len(Dop)}๊ฑด, ๊ฒฉ๋ฆฌ {n_bad}๊ฑด, ๊ฒฝ๋ณด ์ธก์ • {int(Dop['alarm'].sum())}๊ฑด, RUL ์ถœ์ฒ˜ '{src}', ์ „์ฒด ์‹คํ–‰ {sec:.1f}์ดˆ\")\n", "\n", "# ์ž…๊ณ  ์ œ์•ˆ(๋Œ€๊ธฐ)๊ณผ ์ˆ˜๋ฆฌ๋ถ€์† ์†Œ์š” ์˜ˆ๊ณ : ๊ฒฝ๋ณด ๊ฐœ์ฒด์˜ ๋งˆ์ง€๋ง‰ ์ธก์ • ๊ธฐ์ค€\n", "PART = {\"b165\": \"์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ\", \"kurt\": \"์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ\", \"b150\": \"๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ\", \"b200\": \"์˜ค์ผ ํŽŒํ”„\", \"press_res\": \"์˜ค์ผ ํŽŒํ”„\", \"temp_res\": \"๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ\"}\n", "first = Dop[Dop[\"alarm\"]].groupby(\"unit\").first()\n", "cols = model_ae.cols\n", "C = model_ae.contrib(Dop)\n", "prop = []\n", "for u, r in first.iterrows():\n", " i = Dop.index[(Dop[\"unit\"] == u) & (Dop[\"day\"] == r[\"day\"])][0]\n", " prop.append({\"unit\": u, \"day\": int(r[\"day\"]), \"rul_hat\": float(r[\"rul_hat\"]), \"rul\": int(r[\"rul\"]), \"part\": PART.get(cols[int(np.argmax(C[i]))], \"๋ฏธ๋ถ„๋ฅ˜\"),\n", " \"mode\": r[\"mode\"], \"status\": \"PENDING\" if r[\"rul_hat\"] <= CONFIG[\"approve_rul\"] else \"๊ด€์ฐฐ\"})\n", "P = pd.DataFrame(prop)\n", "P[\"true_part\"] = P[\"mode\"].map({m: MODES[m][2] for m in MODES})\n", "# ์ˆ˜๋ฆฌ๋ถ€์† ์†Œ์š” ์˜ˆ๊ณ : ์˜ˆ์ธก ์ž”์กด 30์ผ ์ด๋‚ด๋กœ ๋‚ด๋ ค์˜จ ๊ฐœ์ฒด์˜ ์˜์‹ฌ ๋ถ€ํ’ˆ\n", "need = P[P[\"rul_hat\"] <= CONFIG[\"spare_horizon\"]]\n", "\n", "# ---------------- 2. ์ž์ฒด ์ ๊ฒ€ ----------------\n", "_, bad_src = predict_safe(None, Dop.head(3))\n", "y = (Dop[\"deg\"] >= 0.1).values\n", "checks = {\n", " \"์ธก์ • ์œ ์‹ค 0๊ฑด\": len(Dop) + n_bad == len(s3),\n", " \"์†์ƒ ์ธก์ • 2๊ฑด ๊ฒฉ๋ฆฌ\": n_bad == 2,\n", " \"์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰\": \"๊ต์ฒด\" in rt,\n", " \"RUL ์˜ˆ์ธก๊ฐ’ ์Œ์ˆ˜ ์—†์Œ\": bool((Dop[\"rul_hat\"] >= 0).all()),\n", " \"๋ชจ๋ธ ์žฅ์•  ์‹œ ์ •๊ธฐ ์ฃผ๊ธฐ ๋Œ€์ฒด\": bad_src == \"์ •๊ธฐ ์ฃผ๊ธฐ ๊ธฐ์ค€ ๋Œ€์ฒด\",\n", " \"์—ดํ™” ๊ฐœ์ฒด ๋ฏธํƒ 0๋Œ€\": bool(Dop.groupby(\"unit\").apply(lambda g: (g[\"alarm\"] & (g[\"deg\"] >= 0.1)).any() or not (g[\"deg\"] >= 0.1).any()).all()),\n", " \"๊ฒฝ๋ณด ๊ฐœ์ฒด ๋ชจ๋‘ ์ œ์•ˆ ๋˜๋Š” ๊ด€์ฐฐ ๋ถ„๋ฅ˜\": len(P) == int(Dop[Dop[\"alarm\"]][\"unit\"].nunique()),\n", " \"์ž๋™ ์Šน์ธ 0๊ฑด(์Šน์ธ ๋Œ€์ƒ์€ ๋Œ€๊ธฐ)\": bool(P[\"status\"].isin([\"PENDING\", \"๊ด€์ฐฐ\"]).all()),\n", "}\n", "print(f\"\\n์ž์ฒด ์ ๊ฒ€ {sum(checks.values())}/{len(checks)} ํ†ต๊ณผ, ์ž๋™ ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ {'์ •์ƒ ์‹คํ–‰' if checks['์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰'] else '์‹คํŒจ'}\")\n", "for k, v in checks.items():\n", " if not v:\n", " print(\" ์‹คํŒจ:\", k)\n", "doc = sum(bool(v.strip()) for v in OPS_GUIDE.values()) / len(OPS_GUIDE)\n", "print(f\"์šด์˜ ๋ฌธ์„œ {len(OPS_GUIDE)}๊ฐœ ์žฅ ์ž‘์„ฑ๋ฅ  {doc:.0%} (๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if doc == 1 else '๋ฏธ๋‹ฌ'}) - \" + \", \".join(k.split(\". \")[1] for k in OPS_GUIDE))\n", "\n", "# ---------------- 3. ํ™”๋ฉด๊ณผ ๋ธŒ๋ฆฌํ•‘ ----------------\n", "fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n", "for u in list(P[\"unit\"])[:3]:\n", " g = Dop[Dop[\"unit\"] == u]\n", " ax[0].plot(g[\"day\"], g[\"score\"], label=u)\n", "ax[0].axhline(1.0, ls=\"--\", color=\"k\"); ax[0].set_yscale(\"log\"); ax[0].set_title(\"Anomaly score (x threshold)\"); ax[0].set_xlabel(\"day\"); ax[0].legend()\n", "ax[1].scatter(P[\"rul\"], P[\"rul_hat\"]); ax[1].plot([0, 120], [0, 120], \"k--\"); ax[1].set_title(\"RUL at first alarm: actual vs predicted\"); ax[1].set_xlabel(\"actual (days)\")\n", "plt.tight_layout(); plt.show()\n", "\n", "lead = P[\"rul\"]\n", "print(\"\\n[์ •๋น„ ๋‹ด๋‹น ์žฅ๊ต ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ] [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\")\n", "print(f\"1. ์šด์šฉ ๊ฐœ์ฒด {len(u3)}๋Œ€ ์ธก์ • {len(s3)}๊ฑด ์ค‘ ์†์ƒ {n_bad}๊ฑด ๊ฒฉ๋ฆฌ, ์—ดํ™” ๊ฐœ์ฒด {len(P)}๋Œ€์—์„œ ๊ฒฝ๋ณด ๋ฐœ๋ น, ๊ณ ์žฅ ์ „ ํ™•๋ณด ์‹œ๊ฐ„ ์ค‘์•™๊ฐ’ {lead.median():.0f}์ผ(์ตœ์†Œ {lead.min()}์ผ)\")\n", "print(f\"2. ์ด์ƒ ๊ฐ์ง€ ๋ชจ๋ธ: {'์‹ ๊ทœ ์‹ค์ ์œผ๋กœ ๊ต์ฒด' if rt['๊ต์ฒด'] else '๊ธฐ์กด ์œ ์ง€'}(๊ฒ€์ฆ F1 {max(rt['์ฑ”ํ”ผ์–ธ F1'], rt['๋„์ „์ž F1']):.2f}), ์žฌ๋ณด์ • ์ž„๊ณ„ ์ ์šฉ์œผ๋กœ ์ •์ƒ ๊ฐœ์ฒด ์˜ค๊ฒฝ๋ณด {int((Dop['alarm'] & (Dop['deg'] == 0)).sum())}๊ฑด\")\n", "print(f\"3. ์กฐ๊ธฐ ์ž…๊ณ  ์ œ์•ˆ {int((P['status'] == 'PENDING').sum())}๊ฑด์€ ๊ฒ€์‚ฌ๊ด€ ์Šน์ธ ๋Œ€๊ธฐ(์ž๋™ ์Šน์ธ 0๊ฑด), ์˜ˆ์ธก ์ž”์กด {CONFIG['approve_rul']}์ผ ์ดˆ๊ณผ {int((P['status'] == '๊ด€์ฐฐ').sum())}๊ฑด์€ ๊ด€์ฐฐ ์œ ์ง€\")\n", "print(\"4. ์ˆ˜๋ฆฌ๋ถ€์† ์†Œ์š” ์˜ˆ๊ณ (์˜ˆ์ธก ์ž”์กด \" + str(CONFIG[\"spare_horizon\"]) + \"์ผ ์ด๋‚ด ๊ฐœ์ฒด์˜ ์˜์‹ฌ ๋ถ€ํ’ˆ): \" + \", \".join(f\"{k} {v}๊ฑด\" for k, v in need[\"part\"].value_counts().items())\n", " + f\" (์ฒซ ๊ฒฝ๋ณด ์‹œ์  ์˜์‹ฌ ๋ถ€ํ’ˆ ์ •ํ™•๋„ {(P['part'] == P['true_part']).mean():.0%})\")\n", "print(f\"5. RUL ์˜ˆ์ธก: ๊ฒฝ๋ณด ์‹œ์  ํ‰๊ท  ์ ˆ๋Œ€์˜ค์ฐจ {np.mean(np.abs(P['rul_hat'] - np.minimum(P['rul'], 120))):.1f}์ผ\")\n", "print(\"6. ๊ฑด์˜: ๊ตฌ๋™ ๊ธฐ์–ดยท์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ ์šฐ์„  ํ™•๋ณด(์†Œ๋ชจ ๋น„์šฉ ์ƒ์œ„), ๊ตญ๋ฐฉ ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„์™€ ์ •๋น„ ์ด๋ ฅยท์ˆ˜๋ฆฌ๋ถ€์† ์†Œ๋ชจ ํ‘œ์ค€ ํ•„๋“œ ์—ฐ๋™ ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 10 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฌํ•™์Šต: {'์ฑ”ํ”ผ์–ธ F1': 0.868, '๋„์ „์ž F1': 0.837, '๊ต์ฒด': False} -> ์ฑ”ํ”ผ์–ธ ์œ ์ง€\n", "์šด์šฉ ์ธก์ • 7695๊ฑด: ์ฒ˜๋ฆฌ 7693๊ฑด, ๊ฒฉ๋ฆฌ 2๊ฑด, ๊ฒฝ๋ณด ์ธก์ • 2562๊ฑด, RUL ์ถœ์ฒ˜ '๋ชจ๋ธ', ์ „์ฒด ์‹คํ–‰ 20.6์ดˆ\n", "\n", "์ž์ฒด ์ ๊ฒ€ 8/8 ํ†ต๊ณผ, ์ž๋™ ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์ •์ƒ ์‹คํ–‰\n", "์šด์˜ ๋ฌธ์„œ 7๊ฐœ ์žฅ ์ž‘์„ฑ๋ฅ  100% (๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ) - ๋ชฉ์ ๊ณผ ๋ฒ”์œ„, ์ž…๋ ฅ ๋ฐ์ดํ„ฐ, ์ผ์ผ ์šด์šฉ ์ ˆ์ฐจ, ์Šน์ธ(HITL) ๊ธฐ์ค€, ์žฌํ•™์Šต ์ ˆ์ฐจ, ์žฅ์•  ๋Œ€์‘, ์—ฐ๋™ ์ธํ„ฐํŽ˜์ด์Šค\n", "\n", "[์ •๋น„ ๋‹ด๋‹น ์žฅ๊ต ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ] [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\n", "1. ์šด์šฉ ๊ฐœ์ฒด 48๋Œ€ ์ธก์ • 7695๊ฑด ์ค‘ ์†์ƒ 2๊ฑด ๊ฒฉ๋ฆฌ, ์—ดํ™” ๊ฐœ์ฒด 48๋Œ€์—์„œ ๊ฒฝ๋ณด ๋ฐœ๋ น, ๊ณ ์žฅ ์ „ ํ™•๋ณด ์‹œ๊ฐ„ ์ค‘์•™๊ฐ’ 66์ผ(์ตœ์†Œ 37์ผ)\n", "2. ์ด์ƒ ๊ฐ์ง€ ๋ชจ๋ธ: ๊ธฐ์กด ์œ ์ง€(๊ฒ€์ฆ F1 0.87), ์žฌ๋ณด์ • ์ž„๊ณ„ ์ ์šฉ์œผ๋กœ ์ •์ƒ ๊ฐœ์ฒด ์˜ค๊ฒฝ๋ณด 23๊ฑด\n", "3. ์กฐ๊ธฐ ์ž…๊ณ  ์ œ์•ˆ 31๊ฑด์€ ๊ฒ€์‚ฌ๊ด€ ์Šน์ธ ๋Œ€๊ธฐ(์ž๋™ ์Šน์ธ 0๊ฑด), ์˜ˆ์ธก ์ž”์กด 90์ผ ์ดˆ๊ณผ 17๊ฑด์€ ๊ด€์ฐฐ ์œ ์ง€\n", "4. ์ˆ˜๋ฆฌ๋ถ€์† ์†Œ์š” ์˜ˆ๊ณ (์˜ˆ์ธก ์ž”์กด 60์ผ ์ด๋‚ด ๊ฐœ์ฒด์˜ ์˜์‹ฌ ๋ถ€ํ’ˆ): ๊ตฌ๋™ ๊ธฐ์–ด ์„ธํŠธ 6๊ฑด, ์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ 3๊ฑด, ๋ƒ‰๊ฐ ํŒฌ ๋ชจํ„ฐ 3๊ฑด, ์˜ค์ผ ํŽŒํ”„ 2๊ฑด (์ฒซ ๊ฒฝ๋ณด ์‹œ์  ์˜์‹ฌ ๋ถ€ํ’ˆ ์ •ํ™•๋„ 52%)\n", "5. RUL ์˜ˆ์ธก: ๊ฒฝ๋ณด ์‹œ์  ํ‰๊ท  ์ ˆ๋Œ€์˜ค์ฐจ 10.0์ผ\n", "6. ๊ฑด์˜: ๊ตฌ๋™ ๊ธฐ์–ดยท์ฃผ ๋ฒ ์–ด๋ง ์„ธํŠธ ์šฐ์„  ํ™•๋ณด(์†Œ๋ชจ ๋น„์šฉ ์ƒ์œ„), ๊ตญ๋ฐฉ ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„์™€ ์ •๋น„ ์ด๋ ฅยท์ˆ˜๋ฆฌ๋ถ€์† ์†Œ๋ชจ ํ‘œ์ค€ ํ•„๋“œ ์—ฐ๋™ ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜\n", "```\n", "
\n" ] } ], "metadata": { "colab": { "provenance": [] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }