{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ๐ŸŽฏ [SUP02] ๊ตฐ์ˆ˜ํ’ˆ ์‹œํšจ์„ฑ ํ’ˆ๋ชฉ ์žฌ๊ณ ํ†ต์ œยท์ž”์—ฌ์ผ ๊ด€๋ฆฌ\n", "### Google Colab ์‹ค์Šต ํ™˜๊ฒฝ (๋ณด๊ธ‰ยท์žฌ์‚ฐยท์‹œํšจ๊ด€๋ฆฌ / Level 2)\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/SUP02/SUP02_solution.ipynb)\n", "\n", "---\n", "\n", "## ๐Ÿ“Œ 1. ํ”„๋กœ์ ํŠธ ๊ฐœ์š” ๋ฐ ๋ฐฐ๊ฒฝ\n", "\n", "| ํ•ญ๋ชฉ | ๋‚ด์šฉ |\n", "| :--- | :--- |\n", "| **๊ณผ์ • ๋ถ„๋ฅ˜** | ๋ณด๊ธ‰ยท์žฌ์‚ฐยท์‹œํšจ๊ด€๋ฆฌ (Level 2) |\n", "| **ํ•™์Šต ์†Œ์š” ์‹œ๊ฐ„** | 10๊ฐœ ๋ฏธ์…˜ (5์ผ / ์ด 28.5์‹œ๊ฐ„) |\n", "| **ํ™˜๊ฒฝ ์‚ฌ์–‘** | Python 3.10+, numpy, pandas, matplotlib, scipy, scikit-learn |\n", "\n", "### ๐Ÿ“– ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ\n", "๊ตฐ์ˆ˜ํ’ˆ ์ค‘์—๋Š” ์ •๋น„ ์ฃผ๊ธฐ์™€ ์‚ฌ์šฉ ๊ฐ€๋Šฅ ๊ธฐํ•œ์ด ์ •ํ•ด์ ธ ์žˆ์–ด ์‹œํšจ๋ฅผ ๋„˜๊ธฐ๊ฑฐ๋‚˜ ์ •๋น„ ์‹œ์ ์„ ๋†“์น˜๋ฉด ์†์‹ค์ด ์ƒ๊ธฐ๋Š” ํ’ˆ๋ชฉ์ด ๋งŽ๋‹ค. ์ด ๊ณผ์ œ๋Š” ์‹œํšจ์„ฑ ํ’ˆ๋ชฉ์˜ ์ž”์—ฌ์ผ ๊ด€๋ฆฌ ๋ฌธ์ œ๋ฅผ ์‹œ๊ฐ„ ํ•œ๋„๊ฐ€ ์žˆ๋Š” ์žฅ๋น„ยท๋ถ€ํ’ˆ์˜ ์ž”์—ฌ ์ˆ˜๋ช…(RUL) ๊ด€๋ฆฌ์™€ ์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ์˜ ์„ ์ž…์„ ์ถœ(FEFO) ์žฌ๊ณ  ํ†ต์ œ๋กœ ์—ฐ๊ฒฐํ•ด ๋‹ค๋ฃฌ๋‹ค. ๊ฐ€์ƒ ์žฅ๋น„ ๋ณด์œ  ํ˜„ํ™ฉ์˜ ์šด์šฉ ์‹œ๊ฐ„, ํ™˜๊ฒฝ, ์ •๋น„ ์ด๋ ฅ์œผ๋กœ ์ƒ์กด ๋ถ„์„ ๊ธฐ๋ฐ˜ ์ˆ˜๋ช… ๋ชจ๋ธ์„ ๋งŒ๋“ค๊ณ , ์ •๋น„์ฐฝ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐยทํ‚คํŠธ ์œ ํšจ๊ธฐ๊ฐ„ ์ œ์•ฝ ์•„๋ž˜ 12๊ฐœ์›” ๊ณ„ํš์ •๋น„ ์ผ์ •์„ ์ถ”์ฒœํ•œ๋‹ค. ์ผ์ • ํ™•์ •๊ณผ ๋„ํƒœ ๊ฑด์˜๋Š” ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ ์ ˆ์ฐจ๋กœ ๋‘๊ณ , ๋Œ€๊ทœ๋ชจ ๋ถ€ํ•˜ ์‹œํ—˜, 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", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์žฅ๋น„ ์‹๋ณ„, ๋ถ€๋Œ€, ์šด์šฉ ํ™˜๊ฒฝ, ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„, ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„, ๊ธฐ๋™๊ฑฐ๋ฆฌ, ์ผ ๊ฐ€๋™๋ฅ , ๊ณ„ํšยท๋น„๊ณ„ํš ์ •๋น„ ํšŸ์ˆ˜, ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ๊ณผ ๊ณ ์žฅ ๋ผ๋ฒจ์˜ ํ•„๋“œยทํ—ˆ์šฉ๊ฐ’์„ ์ • ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/sup02_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๊ฐœ ์ด์ƒ)์„ ์‹ค์ œ ์ƒ์„ฑ ๋ฐ์ดํ„ฐ์™€ ๋Œ€์กฐํ•˜๋ฉฐ, ํ—ˆ์šฉ๊ฐ’ ๊ฒ€์ฆ๊ธฐ์— ์˜ค๋ฅ˜ 6์ข…์„ ์ฃผ์ž…ํ•ด ๊ฒ€์ถœํ•˜๊ณ  ์žฅ๋น„ ์ˆ˜๋ช…๊ด€๋ฆฌ ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", "import copy\n", "import numpy as np\n", "import pandas as pd\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์žฅ๋น„ ๋ณด์œ  ํ˜„ํ™ฉยท๊ณ ์žฅ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ˆ˜์น˜ ์—†์Œ)\n", "# ๊ฐ€์ƒ ๋ถ€๋Œ€ 6๊ฐœ(U1~U6)์˜ ์žฅ๋น„๊ฐ€ ์šด์šฉ ํ™˜๊ฒฝ(normal/rough/salt/cold)์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์†๋„๋กœ ๋…ธํ›„ํ™”ํ•œ๋‹ค. ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„(h_since)์ด\n", "# ์žฅ๋น„๋งˆ๋‹ค ์ˆจ์€ ์ˆ˜๋ช…(t_fail)์„ ๋„˜์œผ๋ฉด ๊ณ ์žฅ(event=1) ์ด๋ ฅ, ๋„˜์ง€ ์•Š์•˜์œผ๋ฉด ํ˜„์žฌ๊นŒ์ง€ ์ƒ์กด(event=0, ๊ด€์ธก ์ค‘๋„์ ˆ๋‹จ)์ด๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ENVS = [\"normal\", \"rough\", \"salt\", \"cold\"]\n", "ENV_W = {\"normal\": 1.00, \"rough\": 1.35, \"salt\": 1.25, \"cold\": 1.12} # ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜(์ˆจ์€ ์ •๋‹ต, ๊ฐ€์ •๊ฐ’): ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ์งง๋‹ค\n", "ENV_KO = {\"normal\": \"์ผ๋ฐ˜\", \"rough\": \"ํ—˜์ง€\", \"salt\": \"์—ผ๋ถ„\", \"cold\": \"ํ˜นํ•œ\"}\n", "UNITS = [\"U1\", \"U2\", \"U3\", \"U4\", \"U5\", \"U6\"]\n", "UNIT_ENV = {\"U1\": [.7, .1, .1, .1], \"U2\": [.2, .6, .1, .1], \"U3\": [.2, .1, .6, .1], \"U4\": [.3, .1, .1, .5], \"U5\": [.5, .3, .1, .1], \"U6\": [.4, .1, .4, .1]}\n", "ETYPES = [\"truck\", \"generator\", \"trailer\", \"radio\"]\n", "CYCLE = 8000.0 # ๊ทœ์ • ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ(์šด์šฉ์‹œ๊ฐ„ h, ๊ฐ€์ •๊ฐ’)\n", "KSHAPE = 12.0 # ์ˆ˜๋ช… ๋ถ„ํฌ ์™€์ด๋ธ” ํ˜•์ƒ(๋งˆ๋ชจํ˜• ๊ณ ์žฅ, ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ๋ชจ์—ฌ ์žˆ๋‹ค)\n", "INSP_NOISE = 0.025 # ์ •๋น„ ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ ์ธก์ • ์˜ค์ฐจ(๊ฐ€์ •๊ฐ’)\n", "\n", "\n", "def make_fleet(n=900, seed=0, env_w=None, shift=1.0):\n", " \"\"\"์žฅ๋น„ n๋Œ€. env_w๋กœ ์ˆจ์€ ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜๋ฅผ ๋ฐ”๊ฟ” ์šด์˜ ๋ถ„ํฌ ๋ณ€ํ™”๋ฅผ ๋ชจ์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. shift>1์ด๋ฉด ๊ฐ€๋™๋ฅ ์ด ๋†’์•„์ง„๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " w = env_w or ENV_W\n", " unit = rng.choice(UNITS, n)\n", " env = np.array([rng.choice(ENVS, p=UNIT_ENV[u]) for u in unit])\n", " et = rng.choice(ETYPES, n, p=[.4, .2, .2, .2])\n", " rate = rng.lognormal(np.log(3.0 * shift), 0.25, n) # ์ผ ํ‰๊ท  ์šด์šฉ์‹œ๊ฐ„(h/์ผ)\n", " h_since = rng.uniform(300, 11500, n) # ๋งˆ์ง€๋ง‰ ์ฐฝ์ •๋น„ ์ดํ›„ ์šด์šฉ์‹œ๊ฐ„\n", " n_ovh = rng.integers(0, 3, n)\n", " hours = h_since + n_ovh * rng.uniform(5000, 8000, n) # ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„\n", " km = hours * rng.normal(17, 3, n) * np.where(env == \"rough\", 0.8, 1.0) # ๋ˆ„์  ๊ธฐ๋™๊ฑฐ๋ฆฌ(km)\n", " age_y = np.clip(hours / (rate * 365) * rng.uniform(0.9, 1.15, n), 0.5, 25)\n", " unpl = rng.poisson(0.35 * h_since / 1000 * np.array([w[e] for e in env]) / 1.3) # ๋น„๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜(ํ™˜๊ฒฝ์ด ๊ฐ€ํ˜นํ• ์ˆ˜๋ก ๋งŽ๋‹ค)\n", " n_maint = rng.poisson(0.6 * h_since / 1000 + 1) # ๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜\n", " scale = 9600 / np.array([w[e] for e in env]) * 0.985 ** unpl * np.exp(rng.normal(0, 0.04, n))\n", " t_fail = scale * rng.weibull(KSHAPE, n) # ์ฐฝ์ •๋น„ ํ›„ ๊ณ ์žฅ๊นŒ์ง€ ์šด์šฉ์‹œ๊ฐ„(์ˆจ์€ ์ •๋‹ต)\n", " event = (t_fail <= h_since).astype(int)\n", " dur = np.where(event == 1, t_fail, h_since)\n", " df = pd.DataFrame({\"eq_id\": [f\"EQ{seed:02d}{i:04d}\" for i in range(n)], \"unit\": unit, \"env\": env, \"etype\": et, \"rate\": rate, \"hours\": hours, \"km\": km, \"age_y\": age_y, \"n_ovh\": n_ovh,\n", " \"h_since\": h_since, \"unpl\": unpl, \"n_maint\": n_maint, \"t_fail\": t_fail, \"event\": event, \"dur\": dur})\n", " df[\"insp\"] = np.clip(dur / t_fail + rng.normal(0, INSP_NOISE, n), 0, 1.3) # ๋งˆ์ง€๋ง‰ ์ •๋น„ ์ ๊ฒ€์—์„œ ์ธก์ •ํ•œ ๋งˆ๋ชจ ๋น„์œจ(์†Œ๋ชจ ์ˆ˜๋ช…/์ด ์ˆ˜๋ช…, ์˜ค์ฐจ ํฌํ•จ)\n", " df[\"rul_h\"] = np.where(event == 0, t_fail - h_since, np.nan) # ์ƒ์กด ์žฅ๋น„์˜ ๋‚จ์€ ์šด์šฉ์‹œ๊ฐ„(์ •๋‹ต)\n", " df[\"rul_d\"] = df[\"rul_h\"] / df[\"rate\"] # ๋‚จ์€ ์ผ์ˆ˜(์ •๋‹ต)\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " y, p = np.asarray(y, float), np.asarray(p, float)\n", " return float(np.mean(np.abs(y - p) / np.abs(y)) * 100)\n", "\n", "\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "\n", "\n", "def rmse(y, p):\n", " return float(np.sqrt(np.mean((np.asarray(y, float) - np.asarray(p, float)) ** 2)))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธํ›„ํ™” ํŒŒ์ƒ ํ”ผ์ฒ˜์™€ ํ•™์Šต ํ‘œ๋ณธ ๋งŒ๋“ค๊ธฐ. ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ W0๋Š” ๋„์ž… ์‹œ์ ์˜ ์ „๋ฌธ๊ฐ€ ์ถ”์ •๊ฐ’์ด๋ฉฐ(์‹ค์ œ ๊ฐ€์ค‘์น˜์™€ ๋‹ค๋ฆ„) M9์—์„œ ์žฌ๋ณด์ •ํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "W0 = {\"normal\": 1.00, \"rough\": 1.15, \"salt\": 1.10, \"cold\": 1.05}\n", "FEATS = [\"h_since\", \"hours\", \"km\", \"age_y\", \"rate\", \"n_maint\", \"unpl\", \"unpl_per_kh\", \"km_per_h\", \"n_ovh\", \"k_rough\", \"k_salt\", \"k_cold\", \"k_truck\", \"k_gen\", \"k_trailer\",\n", " \"aging_idx\", \"lin_rul_d\", \"hs_frac\", \"insp\", \"insp_eta_h\"]\n", "\n", "\n", "def add_features(df, w=None):\n", " d = df.copy()\n", " w = w or W0\n", " d[\"unpl_per_kh\"] = d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)\n", " d[\"km_per_h\"] = d[\"km\"] / d[\"hours\"]\n", " for e in (\"rough\", \"salt\", \"cold\"):\n", " d[\"k_\" + e] = (d[\"env\"] == e).astype(int)\n", " d[\"k_truck\"], d[\"k_gen\"], d[\"k_trailer\"] = (d[\"etype\"] == \"truck\").astype(int), (d[\"etype\"] == \"generator\").astype(int), (d[\"etype\"] == \"trailer\").astype(int)\n", " d[\"aging_idx\"] = d[\"h_since\"] * d[\"env\"].map(w) / CYCLE # ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜(1์ด๋ฉด ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ์— ๋„๋‹ฌ)\n", " d[\"lin_rul_d\"] = (CYCLE - d[\"h_since\"]) / d[\"rate\"] # ์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ์ž”์—ฌ ์ผ์ˆ˜(Baseline)\n", " d[\"hs_frac\"] = d[\"h_since\"] / CYCLE\n", " d[\"insp_eta_h\"] = d[\"h_since\"] / np.maximum(d[\"insp\"], 0.05) # ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ๋กœ ํ™˜์‚ฐํ•œ ์ด ์ˆ˜๋ช… ์ถ”์ •(h)\n", " return d\n", "\n", "\n", "def snapshots(df, k=4, seed=0):\n", " \"\"\"๊ณ ์žฅ ์ด๋ ฅ ์žฅ๋น„์˜ ๊ณผ๊ฑฐ ์‹œ์  ํ‘œ๋ณธ: ๊ณ ์žฅ ์ „ ์ž„์˜ ์‹œ์ ์˜ ์ ๊ฒ€๊ฐ’ยท์šด์šฉ์‹œ๊ฐ„๊ณผ ๊ทธ๋•Œ์˜ ๋‚จ์€ ์ผ์ˆ˜. (์ƒ์กด ์žฅ๋น„์˜ ๋ฏธ๋ž˜๋Š” ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๊ณ ์žฅ ์ด๋ ฅ๋งŒ ์“ด๋‹ค)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " f = df[df[\"event\"] == 1]\n", " out = []\n", " for _ in range(k):\n", " s = f.copy()\n", " h = s[\"t_fail\"] * rng.uniform(0.2, 0.98, len(s))\n", " ratio = h / s[\"h_since\"]\n", " s[\"hours\"] = s[\"hours\"] - (s[\"h_since\"] - h)\n", " s[\"km\"] = s[\"km\"] * ratio.clip(upper=1)\n", " s[\"unpl\"] = np.floor(s[\"unpl\"] * ratio)\n", " s[\"n_maint\"] = np.floor(s[\"n_maint\"] * ratio)\n", " s[\"insp\"] = np.clip(h / s[\"t_fail\"] + rng.normal(0, INSP_NOISE, len(s)), 0, 1.3)\n", " s[\"h_since\"] = h\n", " s[\"rul_d\"] = (s[\"t_fail\"] - h) / s[\"rate\"]\n", " s[\"event\"] = 0\n", " out.append(s)\n", " return pd.concat(out, ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "\n", "# 1) ๋ฐ์ดํ„ฐ ์‚ฌ์ „ (ํ•„๋“œ, ๊ตฌ๋ถ„, ์ž๋ฃŒํ˜•, ํ—ˆ์šฉ ๋ฒ”์œ„/๊ฐ’, ์„ค๋ช…)\n", "DICT = [\n", " (\"eq_id\", \"์‹๋ณ„\", \"str\", r\"^EQ\\d{6}$\", \"์žฅ๋น„ ์‹๋ณ„์ฝ”๋“œ(๊ฐ€์ƒ)\"), (\"unit\", \"์‹๋ณ„\", \"str\", \"U1~U6\", \"๊ฐ€์ƒ ๋ถ€๋Œ€ ์ฝ”๋“œ\"), (\"etype\", \"์‹๋ณ„\", \"str\", \"truck|generator|trailer|radio\", \"์žฅ๋น„ ๊ตฌ๋ถ„(๊ฐ€์ƒ)\"),\n", " (\"env\", \"ํ™˜๊ฒฝ\", \"str\", \"normal|rough|salt|cold\", \"์ฃผ ์šด์šฉ ํ™˜๊ฒฝ(์ผ๋ฐ˜/ํ—˜์ง€/์—ผ๋ถ„/ํ˜นํ•œ)\"), (\"rate\", \"์šด์šฉ\", \"float\", \"0.5~20\", \"์ผ ํ‰๊ท  ์šด์šฉ์‹œ๊ฐ„(h/์ผ)\"),\n", " (\"hours\", \"์šด์šฉ\", \"float\", \"0~60000\", \"๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„(h)\"), (\"km\", \"์šด์šฉ\", \"float\", \"0~\", \"๋ˆ„์  ๊ธฐ๋™๊ฑฐ๋ฆฌ(km)\"), (\"age_y\", \"์šด์šฉ\", \"float\", \"0.5~25\", \"์‚ฌ์šฉ ์—ฐ์ˆ˜(๋…„)\"),\n", " (\"n_ovh\", \"์ •๋น„\", \"int\", \"0~5\", \"์ฐฝ์ •๋น„ ์ด๋ ฅ ํšŸ์ˆ˜\"), (\"h_since\", \"์ •๋น„\", \"float\", \"0~hours\", \"๋งˆ์ง€๋ง‰ ์ฐฝ์ •๋น„ ์ดํ›„ ์šด์šฉ์‹œ๊ฐ„(h)\"), (\"unpl\", \"์ •๋น„\", \"int\", \"0~\", \"๋น„๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜(๊ธด๊ธ‰ ํฌํ•จ)\"),\n", " (\"n_maint\", \"์ •๋น„\", \"int\", \"0~\", \"๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜\"), (\"insp\", \"์ •๋น„\", \"float\", \"0~1.3\", \"์ •๋น„ ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ(์†Œ๋ชจ ์ˆ˜๋ช…/์ด ์ˆ˜๋ช…)\"),\n", " (\"t_fail\", \"๋ผ๋ฒจ\", \"float\", \"0~\", \"๊ณ ์žฅ๊นŒ์ง€ ์šด์šฉ์‹œ๊ฐ„ - ๋ชจ์˜ ์ •๋‹ต(์‹ค์ œ ์šด์šฉ์—๋Š” ์—†์Œ)\"), (\"event\", \"๋ผ๋ฒจ\", \"int\", \"0|1\", \"๊ณ ์žฅ ์—ฌ๋ถ€(1=๊ณ ์žฅ ์ด๋ ฅ, 0=์ƒ์กด)\"),\n", " (\"dur\", \"๋ผ๋ฒจ\", \"float\", \"0~\", \"๊ด€์ธก ์šด์šฉ์‹œ๊ฐ„(๊ณ ์žฅ ์‹œ์  ๋˜๋Š” ์ค‘๋„์ ˆ๋‹จ)\"), (\"rul_h\", \"๋ผ๋ฒจ\", \"float\", \"0~\", \"๋‚จ์€ ์šด์šฉ์‹œ๊ฐ„ - ๋ชจ์˜ ์ •๋‹ต\"), (\"rul_d\", \"๋ผ๋ฒจ\", \"float\", \"0~\", \"๋‚จ์€ ์ผ์ˆ˜ - ๋ชจ์˜ ์ •๋‹ต\"),\n", " (\"unpl_per_kh\", \"ํŒŒ์ƒ\", \"float\", \"0~\", \"1000h๋‹น ๋น„๊ณ„ํš ์ •๋น„์œจ\"), (\"km_per_h\", \"ํŒŒ์ƒ\", \"float\", \"5~40\", \"์šด์šฉ์‹œ๊ฐ„๋‹น ๊ธฐ๋™๊ฑฐ๋ฆฌ\"), (\"k_rough\", \"ํŒŒ์ƒ\", \"int\", \"0|1\", \"ํ—˜์ง€ ๋”๋ฏธ\"),\n", " (\"k_salt\", \"ํŒŒ์ƒ\", \"int\", \"0|1\", \"์—ผ๋ถ„ ๋”๋ฏธ\"), (\"k_cold\", \"ํŒŒ์ƒ\", \"int\", \"0|1\", \"ํ˜นํ•œ ๋”๋ฏธ\"), (\"k_truck\", \"ํŒŒ์ƒ\", \"int\", \"0|1\", \"์ฐจ๋Ÿ‰ ๋”๋ฏธ\"), (\"k_gen\", \"ํŒŒ์ƒ\", \"int\", \"0|1\", \"๋ฐœ์ „๊ธฐ ๋”๋ฏธ\"),\n", " (\"k_trailer\", \"ํŒŒ์ƒ\", \"int\", \"0|1\", \"ํŠธ๋ ˆ์ผ๋Ÿฌ ๋”๋ฏธ\"), (\"aging_idx\", \"ํŒŒ์ƒ\", \"float\", \"0~2\", \"ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜(1์ด๋ฉด ์ฃผ๊ธฐ ๋„๋‹ฌ)\"), (\"lin_rul_d\", \"ํŒŒ์ƒ\", \"float\", \"-\", \"์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ์ž”์—ฌ ์ผ์ˆ˜\"),\n", " (\"hs_frac\", \"ํŒŒ์ƒ\", \"float\", \"0~2\", \"์ฐฝ์ •๋น„ ์ฃผ๊ธฐ ๋Œ€๋น„ ๊ฒฝ๊ณผ ๋น„์œจ\"), (\"insp_eta_h\", \"ํŒŒ์ƒ\", \"float\", \"0~\", \"์ ๊ฒ€ ๋งˆ๋ชจ๋กœ ํ™˜์‚ฐํ•œ ์ด ์ˆ˜๋ช… ์ถ”์ •(h)\")]\n", "dd = pd.DataFrame(DICT, columns=[\"ํ•„๋“œ\", \"๊ตฌ๋ถ„\", \"ํ˜•\", \"ํ—ˆ์šฉ๊ฐ’\", \"์„ค๋ช…\"])\n", "print(f\"[1] ๋ฐ์ดํ„ฐ ์‚ฌ์ „ {len(dd)}๊ฐœ ํ•„๋“œ \" + str(dd[\"๊ตฌ๋ถ„\"].value_counts().to_dict()))\n", "LOT_SCHEMA = [(\"lot_id\", \"str\", \"์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ ๋กœํŠธ ๋ฒˆํ˜ธ\"), (\"qty\", \"int\", \"์žฌ๊ณ  ์ˆ˜๋Ÿ‰(๊ฐœ)\"), (\"expiry_d\", \"int\", \"์œ ํšจ๊ธฐํ•œ๊นŒ์ง€ ๋‚จ์€ ์ผ์ˆ˜\"), (\"margin\", \"int\", \"์ •๋น„์ผ ๊ธฐ์ค€ ์ตœ์†Œ ์œ ํšจ๊ธฐ๊ฐ„ ์—ฌ์œ  14์ผ(๊ทœ์น™)\")]\n", "print(\" ์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ ๋กœํŠธ ์Šคํ‚ค๋งˆ:\", \", \".join(f\"{a}({b})\" for a, b, _ in LOT_SCHEMA), \"- ์ •๋น„ ์˜ˆ์ •์ผ์— ์œ ํšจ๊ธฐ๊ฐ„์ด ๋ถ€์กฑํ•œ ํ‚คํŠธ๋Š” ์“ธ ์ˆ˜ ์—†๋‹ค\")\n", "\n", "# 2) ์‹ค์ œ ์ƒ์„ฑ ๋ฐ์ดํ„ฐ์™€ ๋Œ€์กฐ\n", "df = make_fleet(300, seed=1)\n", "feat = add_features(df)\n", "miss = sorted(set(feat.columns) - set(dd[\"ํ•„๋“œ\"]))\n", "extra = sorted(set(dd[\"ํ•„๋“œ\"]) - set(feat.columns))\n", "fe = dd[~dd[\"๊ตฌ๋ถ„\"].isin([\"๋ผ๋ฒจ\", \"์‹๋ณ„\"])]\n", "print(f\"[2] ๋ฐ์ดํ„ฐ ์‚ฌ์ „ ๋Œ€์กฐ({len(feat)}ํ–‰): ์‚ฌ์ „ ๋ˆ„๋ฝ ์ปฌ๋Ÿผ {miss or '์—†์Œ'}, ๋ฐ์ดํ„ฐ์— ์—†๋Š” ํ•„๋“œ {extra or '์—†์Œ'}\")\n", "print(f\" ์˜ˆ์ธก ํ”ผ์ฒ˜ ํ›„๋ณด(์‹๋ณ„ยท๋ผ๋ฒจ ์ œ์™ธ) {len(fe)}๊ฐœ -> ๋ชฉํ‘œ(20๊ฐœ ์ด์ƒ) {'๋‹ฌ์„ฑ' if len(fe) >= 20 else '๋ฏธ๋‹ฌ'} / ์ฐฝ์ •๋น„ ๊ทœ์ • ์ฃผ๊ธฐ {CYCLE:.0f}h(๊ฐ€์ •)\")\n", "\n", "# 3) ํ—ˆ์šฉ๊ฐ’ ๊ฒ€์ฆ๊ธฐ์™€ ์˜ค๋ฅ˜ ์ฃผ์ž…\n", "RULES = {\"eq_id\": lambda s: s.astype(str).str.match(r\"^EQ\\d{6}$\"), \"unit\": lambda s: s.isin(UNITS), \"env\": lambda s: s.isin(ENVS), \"rate\": lambda s: s.between(0.5, 20),\n", " \"hours\": lambda s: s.between(0, 60000), \"h_since\": lambda s: s >= 0, \"insp\": lambda s: s.between(0, 1.3), \"age_y\": lambda s: s.between(0.5, 25)}\n", "\n", "\n", "def validate(d):\n", " bad = {}\n", " for c, f in RULES.items():\n", " n = int((~f(d[c])).sum() + d[c].isna().sum())\n", " if n: bad[c] = n\n", " n = int((d[\"h_since\"] > d[\"hours\"] + 1e-6).sum())\n", " if n: bad[\"h_since>hours(๋ชจ์ˆœ)\"] = n\n", " n = int(d[\"eq_id\"].duplicated().sum())\n", " if n: bad[\"eq_id ์ค‘๋ณต\"] = n\n", " n = int((d[\"km\"] / d[\"hours\"].clip(lower=1) > 60).sum())\n", " if n: bad[\"km ๋‹จ์œ„ ์˜์‹ฌ\"] = n\n", " return bad\n", "\n", "\n", "print(f\"\\n[3] ์ •์ƒ ์ƒ˜ํ”Œ {len(df)}๊ฑด ๊ฒ€์ฆ ์˜ค๋ฅ˜ {sum(validate(df).values())}๊ฑด\")\n", "inj = {\"ํ™˜๊ฒฝ ํ‘œ๊ธฐ ์˜ค๋ฅ˜\": (\"env\", 0, \"ํ—˜์ง€\"), \"์šด์šฉ์‹œ๊ฐ„ ์Œ์ˆ˜\": (\"hours\", 1, -120.0), \"๋ฏธ๋“ฑ๋ก ๋ถ€๋Œ€\": (\"unit\", 2, \"U9\"), \"์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ ๋ฒ”์œ„ ์ดˆ๊ณผ\": (\"insp\", 3, 2.5),\n", " \"์ฐฝ์ •๋น„ ํ›„ ์‹œ๊ฐ„์ด ๋ˆ„์ ๋ณด๋‹ค ํผ\": (\"h_since\", 4, 99999.0), \"km ๋‹จ์œ„ ์˜ค๋ฅ˜(m)\": (\"km\", 5, 1.2e7)}\n", "det = 0\n", "for name, (col, i, val) in inj.items():\n", " d2 = copy.deepcopy(df)\n", " d2.loc[i, col] = val\n", " bad = validate(d2)\n", " ok = bool(bad)\n", " det += ok\n", " print(f\" - {name}: {'๊ฒ€์ถœ' if ok else '๋ฏธ๊ฒ€์ถœ'} {bad}\")\n", "print(f\" ์ฃผ์ž… ์˜ค๋ฅ˜ {len(inj)}์ข… ์ค‘ {det}์ข… ๊ฒ€์ถœ\")\n", "\n", "# 4) ์žฅ๋น„ ์ˆ˜๋ช…๊ด€๋ฆฌ ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต\n", "proc = [(\"์ •๋น„ ์†Œ์š”ยท๊ณ ์žฅ ์ด๋ ฅ ์ ‘์ˆ˜\", \"์ •๋น„๊ณ„ํš๊ด€\", \"์ •๋น„ ์š”์ฒญ, ๊ณ ์žฅ ๋ณด๊ณ \", \"๊ณ ์žฅ ์ด๋ ฅ ํ‘œ\", 30), (\"์šด์šฉ์‹œ๊ฐ„ยท๊ธฐ๋™๊ฑฐ๋ฆฌ ์ง‘๊ณ„\", \"์šด์šฉ ๋‹ด๋‹น\", \"์ผ์ผ ์šด์šฉ ๋ณด๊ณ \", \"hours, km, rate\", 25),\n", " (\"์ •๋น„ ์ด๋ ฅยท์ ๊ฒ€ ์ž…๋ ฅ\", \"์ •๋น„ ๋‹ด๋‹น\", \"์ •๋น„ยท์ ๊ฒ€ ๊ธฐ๋ก\", \"n_maint, unpl, insp\", 25), (\"ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ ์ ์šฉ\", \"๋ถ„์„ ๋‹ด๋‹น\", \"์šด์šฉ ํ™˜๊ฒฝ ๊ตฌ๋ถ„\", \"aging_idx\", 15), (\"์ž”์—ฌ ์ˆ˜๋ช… ์˜ˆ์ธก\", \"๋ถ„์„ ๋‹ด๋‹น\", \"ํ”ผ์ฒ˜ ํ…Œ์ด๋ธ”\", \"RUL(์ผ), ๊ณ ์žฅ ํ™•๋ฅ \", 40),\n", " (\"์ •๋น„์ฐฝ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐ ๋Œ€์กฐ\", \"์ •๋น„๊ณ„ํš๊ด€\", \"์›”๋ณ„ ์Šฌ๋กฏ, ์˜ˆ์‚ฐ, ํ‚คํŠธ ์žฌ๊ณ \", \"์ •๋น„ ์ผ์ • ํ›„๋ณด\", 35), (\"์ •๋น„ ์ผ์ • ํ™•์ •ยท๋„ํƒœ ๊ฑด์˜\", \"๊ตฐ์ˆ˜์ง€ํœ˜๊ด€\", \"์ถ”์ฒœ์•ˆ, ์‚ฌ์œ \", \"์Šน์ธ ์ผ์ •\", 30), (\"์ •๋น„ ์‹คํ–‰ยท๊ฒฐ๊ณผ ์ž…๋ ฅ\", \"์ •๋น„์ฐฝ\", \"์Šน์ธ ์ผ์ •\", \"์ •๋น„ ์™„๋ฃŒ ๊ธฐ๋ก\", 20),\n", " (\"์˜ˆ์ธก ์˜ค์ฐจ ํ™˜๋ฅ˜(AAR)\", \"๋ถ„์„ ๋‹ด๋‹น\", \"์˜ˆ์ธก ๋Œ€ ์‹ค์ \", \"์žฌ๋ณด์ • ๋ณด๊ณ \", 30)]\n", "pm = pd.DataFrame(proc, columns=[\"๋‹จ๊ณ„\", \"๋‹ด๋‹น\", \"์ž…๋ ฅ\", \"์‚ฐ์ถœ\", \"ํ˜„ํ–‰(๋ถ„)\"])\n", "pm.index = range(1, len(pm) + 1)\n", "print(f\"\\n[4] ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต {len(pm)}๋‹จ๊ณ„, ํ˜„ํ–‰ ํ•ฉ๊ณ„ {pm['ํ˜„ํ–‰(๋ถ„)'].sum()}๋ถ„ (๊ฐ€์žฅ ๊ธด ๋‹จ๊ณ„: {pm.loc[pm['ํ˜„ํ–‰(๋ถ„)'].idxmax(), '๋‹จ๊ณ„']})\")\n", "print(pm.to_string())\n", "print(\"\\nํ”„๋กœ์„ธ์Šค ๋งต ์ž‘์„ฑ ์™„๋ฃŒ(9๋‹จ๊ณ„), ํ”ผ์ฒ˜ ๋ชฉ๋ก 20๊ฐœ ์ด์ƒ -> ๋ชฉํ‘œ\", \"๋‹ฌ์„ฑ\" if len(pm) >= 5 and len(fe) >= 20 else \"๋ฏธ๋‹ฌ\")\n", "print(\"๋ชจ๋“  ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ˆ˜์น˜๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋ฉฐ ์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„์™€ ๋ฌด๊ด€ํ•˜๋‹ค.\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 01 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ๋ฐ์ดํ„ฐ ์‚ฌ์ „ 30๊ฐœ ํ•„๋“œ {'ํŒŒ์ƒ': 12, '์ •๋น„': 5, '๋ผ๋ฒจ': 5, '์šด์šฉ': 4, '์‹๋ณ„': 3, 'ํ™˜๊ฒฝ': 1}\n", " ์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ ๋กœํŠธ ์Šคํ‚ค๋งˆ: lot_id(str), qty(int), expiry_d(int), margin(int) - ์ •๋น„ ์˜ˆ์ •์ผ์— ์œ ํšจ๊ธฐ๊ฐ„์ด ๋ถ€์กฑํ•œ ํ‚คํŠธ๋Š” ์“ธ ์ˆ˜ ์—†๋‹ค\n", "[2] ๋ฐ์ดํ„ฐ ์‚ฌ์ „ ๋Œ€์กฐ(300ํ–‰): ์‚ฌ์ „ ๋ˆ„๋ฝ ์ปฌ๋Ÿผ ์—†์Œ, ๋ฐ์ดํ„ฐ์— ์—†๋Š” ํ•„๋“œ ์—†์Œ\n", " ์˜ˆ์ธก ํ”ผ์ฒ˜ ํ›„๋ณด(์‹๋ณ„ยท๋ผ๋ฒจ ์ œ์™ธ) 22๊ฐœ -> ๋ชฉํ‘œ(20๊ฐœ ์ด์ƒ) ๋‹ฌ์„ฑ / ์ฐฝ์ •๋น„ ๊ทœ์ • ์ฃผ๊ธฐ 8000h(๊ฐ€์ •)\n", "\n", "[3] ์ •์ƒ ์ƒ˜ํ”Œ 300๊ฑด ๊ฒ€์ฆ ์˜ค๋ฅ˜ 0๊ฑด\n", " - ํ™˜๊ฒฝ ํ‘œ๊ธฐ ์˜ค๋ฅ˜: ๊ฒ€์ถœ {'env': 1}\n", " - ์šด์šฉ์‹œ๊ฐ„ ์Œ์ˆ˜: ๊ฒ€์ถœ {'hours': 1, 'h_since>hours(๋ชจ์ˆœ)': 1, 'km ๋‹จ์œ„ ์˜์‹ฌ': 1}\n", " - ๋ฏธ๋“ฑ๋ก ๋ถ€๋Œ€: ๊ฒ€์ถœ {'unit': 1}\n", " - ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ ๋ฒ”์œ„ ์ดˆ๊ณผ: ๊ฒ€์ถœ {'insp': 1}\n", " - ์ฐฝ์ •๋น„ ํ›„ ์‹œ๊ฐ„์ด ๋ˆ„์ ๋ณด๋‹ค ํผ: ๊ฒ€์ถœ {'h_since>hours(๋ชจ์ˆœ)': 1}\n", " - km ๋‹จ์œ„ ์˜ค๋ฅ˜(m): ๊ฒ€์ถœ {'km ๋‹จ์œ„ ์˜์‹ฌ': 1}\n", " ์ฃผ์ž… ์˜ค๋ฅ˜ 6์ข… ์ค‘ 6์ข… ๊ฒ€์ถœ\n", "\n", "[4] ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต 9๋‹จ๊ณ„, ํ˜„ํ–‰ ํ•ฉ๊ณ„ 250๋ถ„ (๊ฐ€์žฅ ๊ธด ๋‹จ๊ณ„: ์ž”์—ฌ ์ˆ˜๋ช… ์˜ˆ์ธก)\n", " ๋‹จ๊ณ„ ๋‹ด๋‹น ์ž…๋ ฅ ์‚ฐ์ถœ ํ˜„ํ–‰(๋ถ„)\n", "1 ์ •๋น„ ์†Œ์š”ยท๊ณ ์žฅ ์ด๋ ฅ ์ ‘์ˆ˜ ์ •๋น„๊ณ„ํš๊ด€ ์ •๋น„ ์š”์ฒญ, ๊ณ ์žฅ ๋ณด๊ณ  ๊ณ ์žฅ ์ด๋ ฅ ํ‘œ 30\n", "2 ์šด์šฉ์‹œ๊ฐ„ยท๊ธฐ๋™๊ฑฐ๋ฆฌ ์ง‘๊ณ„ ์šด์šฉ ๋‹ด๋‹น ์ผ์ผ ์šด์šฉ ๋ณด๊ณ  hours, km, rate 25\n", "3 ์ •๋น„ ์ด๋ ฅยท์ ๊ฒ€ ์ž…๋ ฅ ์ •๋น„ ๋‹ด๋‹น ์ •๋น„ยท์ ๊ฒ€ ๊ธฐ๋ก n_maint, unpl, insp 25\n", "4 ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ ์ ์šฉ ๋ถ„์„ ๋‹ด๋‹น ์šด์šฉ ํ™˜๊ฒฝ ๊ตฌ๋ถ„ aging_idx 15\n", "5 ์ž”์—ฌ ์ˆ˜๋ช… ์˜ˆ์ธก ๋ถ„์„ ๋‹ด๋‹น ํ”ผ์ฒ˜ ํ…Œ์ด๋ธ” RUL(์ผ), ๊ณ ์žฅ ํ™•๋ฅ  40\n", "6 ์ •๋น„์ฐฝ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐ ๋Œ€์กฐ ์ •๋น„๊ณ„ํš๊ด€ ์›”๋ณ„ ์Šฌ๋กฏ, ์˜ˆ์‚ฐ, ํ‚คํŠธ ์žฌ๊ณ  ์ •๋น„ ์ผ์ • ํ›„๋ณด 35\n", "7 ์ •๋น„ ์ผ์ • ํ™•์ •ยท๋„ํƒœ ๊ฑด์˜ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์ถ”์ฒœ์•ˆ, ์‚ฌ์œ  ์Šน์ธ ์ผ์ • 30\n", "8 ์ •๋น„ ์‹คํ–‰ยท๊ฒฐ๊ณผ ์ž…๋ ฅ ์ •๋น„์ฐฝ ์Šน์ธ ์ผ์ • ์ •๋น„ ์™„๋ฃŒ ๊ธฐ๋ก 20\n", "9 ์˜ˆ์ธก ์˜ค์ฐจ ํ™˜๋ฅ˜(AAR) ๋ถ„์„ ๋‹ด๋‹น ์˜ˆ์ธก ๋Œ€ ์‹ค์  ์žฌ๋ณด์ • ๋ณด๊ณ  30\n", "\n", "ํ”„๋กœ์„ธ์Šค ๋งต ์ž‘์„ฑ ์™„๋ฃŒ(9๋‹จ๊ณ„), ํ”ผ์ฒ˜ ๋ชฉ๋ก 20๊ฐœ ์ด์ƒ -> ๋ชฉํ‘œ ๋‹ฌ์„ฑ\n", "๋ชจ๋“  ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ˆ˜์น˜๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋ฉฐ ์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„์™€ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 02. ์šด์šฉ ํ™˜๊ฒฝ(ํ—˜์ง€/์—ผ๋ถ„/ํ˜นํ•œ) ๊ฐ€์ค‘์น˜ ๋ฐ˜์˜ ๋ฐ ์žฅ๋น„ ๋…ธํ›„ํ™” ์ง€ํ‘œ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์šด์šฉ ํ™˜๊ฒฝ(ํ—˜์ง€/์—ผ๋ถ„) ๊ฐ€์ค‘์น˜๋ฅผ ๋ฐ˜์˜ํ•œ ๋…ธํ›„ํ™” ์ง€ํ‘œ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์šด์šฉ ํ™˜๊ฒฝ(ํ—˜์ง€ยท์—ผ๋ถ„ยทํ˜นํ•œ) ๊ฐ€์ค‘์น˜๋ฅผ ๋ฐ˜์˜ํ•œ ๋…ธํ›„ํ™” ์ง€ํ‘œ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์˜ค๋ฅ˜๋ฅผ ์ฃผ์ž…ํ•œ ์˜ค์—ผ ๋ฐ์ดํ„ฐ์— ํ™˜๊ฒฝ ํ‘œ๊ธฐ ํ†ต์ผ, ์ค‘๋ณต ์ œ๊ฑฐ, ๋ถ€ํ˜ธยท๋‹จ์œ„ ์˜ค๋ฅ˜ ๋ณด์ •, ๊ฒฐ์ธก ๋ณด์ • ๋“ฑ ์ •์ œ ๊ทœ์น™์„ ์ˆœ์„œ๋Œ€๋กœ ์ ์šฉํ•˜๊ณ  ์ฒ˜๋ฆฌ ๊ฑด์ˆ˜๋ฅผ ๋กœ๊ทธ๋กœ ๋‚จ๊ธด๋‹ค ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/etl/sup02_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": [ "\"\"\"ํ‘œ๊ธฐยท๋‹จ์œ„ยท๋ชจ์ˆœยท๊ฒฐ์ธกยท์ค‘๋ณต ์˜ค๋ฅ˜๋ฅผ ์ฃผ์ž…ํ•œ ๊ฐ€์ƒ ์žฅ๋น„ ์šด์šฉ ๊ธฐ๋ก์— ์šด์šฉ ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜๋ฅผ ๋ฐ˜์˜ํ•œ ๋…ธํ›„ํ™” ์ง€ํ‘œ ์‚ฐ์ถœ์„ ํฌํ•จํ•œ ์ •์ œ ๊ทœ์น™ 11์ข…์„ ์ ์šฉํ•˜๊ณ , ๊ธด๊ธ‰ ์šด์šฉยท๋‹ค์ˆ˜ ๋น„๊ณ„ํš ์ •๋น„ ๊ฐ™์€ ๊ตฐ์ˆ˜ ํŠน์ˆ˜ ์˜ˆ์™ธ๋Š” ์‚ญ์ œํ•˜์ง€ ์•Š๊ณ  ๋ณด์กดํ•˜๋ฉฐ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ ํ†ต๊ณผ์œจ๊ณผ ์ •์ œ ์ „ํ›„ ํ†ต๊ณ„๋ฅผ ํ™•์ธํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์žฅ๋น„ ๋ณด์œ  ํ˜„ํ™ฉยท๊ณ ์žฅ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ˆ˜์น˜ ์—†์Œ)\n", "# ๊ฐ€์ƒ ๋ถ€๋Œ€ 6๊ฐœ(U1~U6)์˜ ์žฅ๋น„๊ฐ€ ์šด์šฉ ํ™˜๊ฒฝ(normal/rough/salt/cold)์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์†๋„๋กœ ๋…ธํ›„ํ™”ํ•œ๋‹ค. ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„(h_since)์ด\n", "# ์žฅ๋น„๋งˆ๋‹ค ์ˆจ์€ ์ˆ˜๋ช…(t_fail)์„ ๋„˜์œผ๋ฉด ๊ณ ์žฅ(event=1) ์ด๋ ฅ, ๋„˜์ง€ ์•Š์•˜์œผ๋ฉด ํ˜„์žฌ๊นŒ์ง€ ์ƒ์กด(event=0, ๊ด€์ธก ์ค‘๋„์ ˆ๋‹จ)์ด๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ENVS = [\"normal\", \"rough\", \"salt\", \"cold\"]\n", "ENV_W = {\"normal\": 1.00, \"rough\": 1.35, \"salt\": 1.25, \"cold\": 1.12} # ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜(์ˆจ์€ ์ •๋‹ต, ๊ฐ€์ •๊ฐ’): ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ์งง๋‹ค\n", "ENV_KO = {\"normal\": \"์ผ๋ฐ˜\", \"rough\": \"ํ—˜์ง€\", \"salt\": \"์—ผ๋ถ„\", \"cold\": \"ํ˜นํ•œ\"}\n", "UNITS = [\"U1\", \"U2\", \"U3\", \"U4\", \"U5\", \"U6\"]\n", "UNIT_ENV = {\"U1\": [.7, .1, .1, .1], \"U2\": [.2, .6, .1, .1], \"U3\": [.2, .1, .6, .1], \"U4\": [.3, .1, .1, .5], \"U5\": [.5, .3, .1, .1], \"U6\": [.4, .1, .4, .1]}\n", "ETYPES = [\"truck\", \"generator\", \"trailer\", \"radio\"]\n", "CYCLE = 8000.0 # ๊ทœ์ • ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ(์šด์šฉ์‹œ๊ฐ„ h, ๊ฐ€์ •๊ฐ’)\n", "KSHAPE = 12.0 # ์ˆ˜๋ช… ๋ถ„ํฌ ์™€์ด๋ธ” ํ˜•์ƒ(๋งˆ๋ชจํ˜• ๊ณ ์žฅ, ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ๋ชจ์—ฌ ์žˆ๋‹ค)\n", "INSP_NOISE = 0.025 # ์ •๋น„ ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ ์ธก์ • ์˜ค์ฐจ(๊ฐ€์ •๊ฐ’)\n", "\n", "\n", "def make_fleet(n=900, seed=0, env_w=None, shift=1.0):\n", " \"\"\"์žฅ๋น„ n๋Œ€. env_w๋กœ ์ˆจ์€ ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜๋ฅผ ๋ฐ”๊ฟ” ์šด์˜ ๋ถ„ํฌ ๋ณ€ํ™”๋ฅผ ๋ชจ์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. shift>1์ด๋ฉด ๊ฐ€๋™๋ฅ ์ด ๋†’์•„์ง„๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " w = env_w or ENV_W\n", " unit = rng.choice(UNITS, n)\n", " env = np.array([rng.choice(ENVS, p=UNIT_ENV[u]) for u in unit])\n", " et = rng.choice(ETYPES, n, p=[.4, .2, .2, .2])\n", " rate = rng.lognormal(np.log(3.0 * shift), 0.25, n) # ์ผ ํ‰๊ท  ์šด์šฉ์‹œ๊ฐ„(h/์ผ)\n", " h_since = rng.uniform(300, 11500, n) # ๋งˆ์ง€๋ง‰ ์ฐฝ์ •๋น„ ์ดํ›„ ์šด์šฉ์‹œ๊ฐ„\n", " n_ovh = rng.integers(0, 3, n)\n", " hours = h_since + n_ovh * rng.uniform(5000, 8000, n) # ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„\n", " km = hours * rng.normal(17, 3, n) * np.where(env == \"rough\", 0.8, 1.0) # ๋ˆ„์  ๊ธฐ๋™๊ฑฐ๋ฆฌ(km)\n", " age_y = np.clip(hours / (rate * 365) * rng.uniform(0.9, 1.15, n), 0.5, 25)\n", " unpl = rng.poisson(0.35 * h_since / 1000 * np.array([w[e] for e in env]) / 1.3) # ๋น„๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜(ํ™˜๊ฒฝ์ด ๊ฐ€ํ˜นํ• ์ˆ˜๋ก ๋งŽ๋‹ค)\n", " n_maint = rng.poisson(0.6 * h_since / 1000 + 1) # ๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜\n", " scale = 9600 / np.array([w[e] for e in env]) * 0.985 ** unpl * np.exp(rng.normal(0, 0.04, n))\n", " t_fail = scale * rng.weibull(KSHAPE, n) # ์ฐฝ์ •๋น„ ํ›„ ๊ณ ์žฅ๊นŒ์ง€ ์šด์šฉ์‹œ๊ฐ„(์ˆจ์€ ์ •๋‹ต)\n", " event = (t_fail <= h_since).astype(int)\n", " dur = np.where(event == 1, t_fail, h_since)\n", " df = pd.DataFrame({\"eq_id\": [f\"EQ{seed:02d}{i:04d}\" for i in range(n)], \"unit\": unit, \"env\": env, \"etype\": et, \"rate\": rate, \"hours\": hours, \"km\": km, \"age_y\": age_y, \"n_ovh\": n_ovh,\n", " \"h_since\": h_since, \"unpl\": unpl, \"n_maint\": n_maint, \"t_fail\": t_fail, \"event\": event, \"dur\": dur})\n", " df[\"insp\"] = np.clip(dur / t_fail + rng.normal(0, INSP_NOISE, n), 0, 1.3) # ๋งˆ์ง€๋ง‰ ์ •๋น„ ์ ๊ฒ€์—์„œ ์ธก์ •ํ•œ ๋งˆ๋ชจ ๋น„์œจ(์†Œ๋ชจ ์ˆ˜๋ช…/์ด ์ˆ˜๋ช…, ์˜ค์ฐจ ํฌํ•จ)\n", " df[\"rul_h\"] = np.where(event == 0, t_fail - h_since, np.nan) # ์ƒ์กด ์žฅ๋น„์˜ ๋‚จ์€ ์šด์šฉ์‹œ๊ฐ„(์ •๋‹ต)\n", " df[\"rul_d\"] = df[\"rul_h\"] / df[\"rate\"] # ๋‚จ์€ ์ผ์ˆ˜(์ •๋‹ต)\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " y, p = np.asarray(y, float), np.asarray(p, float)\n", " return float(np.mean(np.abs(y - p) / np.abs(y)) * 100)\n", "\n", "\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "\n", "\n", "def rmse(y, p):\n", " return float(np.sqrt(np.mean((np.asarray(y, float) - np.asarray(p, float)) ** 2)))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธํ›„ํ™” ํŒŒ์ƒ ํ”ผ์ฒ˜์™€ ํ•™์Šต ํ‘œ๋ณธ ๋งŒ๋“ค๊ธฐ. ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ W0๋Š” ๋„์ž… ์‹œ์ ์˜ ์ „๋ฌธ๊ฐ€ ์ถ”์ •๊ฐ’์ด๋ฉฐ(์‹ค์ œ ๊ฐ€์ค‘์น˜์™€ ๋‹ค๋ฆ„) M9์—์„œ ์žฌ๋ณด์ •ํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "W0 = {\"normal\": 1.00, \"rough\": 1.15, \"salt\": 1.10, \"cold\": 1.05}\n", "FEATS = [\"h_since\", \"hours\", \"km\", \"age_y\", \"rate\", \"n_maint\", \"unpl\", \"unpl_per_kh\", \"km_per_h\", \"n_ovh\", \"k_rough\", \"k_salt\", \"k_cold\", \"k_truck\", \"k_gen\", \"k_trailer\",\n", " \"aging_idx\", \"lin_rul_d\", \"hs_frac\", \"insp\", \"insp_eta_h\"]\n", "\n", "\n", "def add_features(df, w=None):\n", " d = df.copy()\n", " w = w or W0\n", " d[\"unpl_per_kh\"] = d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)\n", " d[\"km_per_h\"] = d[\"km\"] / d[\"hours\"]\n", " for e in (\"rough\", \"salt\", \"cold\"):\n", " d[\"k_\" + e] = (d[\"env\"] == e).astype(int)\n", " d[\"k_truck\"], d[\"k_gen\"], d[\"k_trailer\"] = (d[\"etype\"] == \"truck\").astype(int), (d[\"etype\"] == \"generator\").astype(int), (d[\"etype\"] == \"trailer\").astype(int)\n", " d[\"aging_idx\"] = d[\"h_since\"] * d[\"env\"].map(w) / CYCLE # ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜(1์ด๋ฉด ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ์— ๋„๋‹ฌ)\n", " d[\"lin_rul_d\"] = (CYCLE - d[\"h_since\"]) / d[\"rate\"] # ์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ์ž”์—ฌ ์ผ์ˆ˜(Baseline)\n", " d[\"hs_frac\"] = d[\"h_since\"] / CYCLE\n", " d[\"insp_eta_h\"] = d[\"h_since\"] / np.maximum(d[\"insp\"], 0.05) # ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ๋กœ ํ™˜์‚ฐํ•œ ์ด ์ˆ˜๋ช… ์ถ”์ •(h)\n", " return d\n", "\n", "\n", "def snapshots(df, k=4, seed=0):\n", " \"\"\"๊ณ ์žฅ ์ด๋ ฅ ์žฅ๋น„์˜ ๊ณผ๊ฑฐ ์‹œ์  ํ‘œ๋ณธ: ๊ณ ์žฅ ์ „ ์ž„์˜ ์‹œ์ ์˜ ์ ๊ฒ€๊ฐ’ยท์šด์šฉ์‹œ๊ฐ„๊ณผ ๊ทธ๋•Œ์˜ ๋‚จ์€ ์ผ์ˆ˜. (์ƒ์กด ์žฅ๋น„์˜ ๋ฏธ๋ž˜๋Š” ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๊ณ ์žฅ ์ด๋ ฅ๋งŒ ์“ด๋‹ค)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " f = df[df[\"event\"] == 1]\n", " out = []\n", " for _ in range(k):\n", " s = f.copy()\n", " h = s[\"t_fail\"] * rng.uniform(0.2, 0.98, len(s))\n", " ratio = h / s[\"h_since\"]\n", " s[\"hours\"] = s[\"hours\"] - (s[\"h_since\"] - h)\n", " s[\"km\"] = s[\"km\"] * ratio.clip(upper=1)\n", " s[\"unpl\"] = np.floor(s[\"unpl\"] * ratio)\n", " s[\"n_maint\"] = np.floor(s[\"n_maint\"] * ratio)\n", " s[\"insp\"] = np.clip(h / s[\"t_fail\"] + rng.normal(0, INSP_NOISE, len(s)), 0, 1.3)\n", " s[\"h_since\"] = h\n", " s[\"rul_d\"] = (s[\"t_fail\"] - h) / s[\"rate\"]\n", " s[\"event\"] = 0\n", " out.append(s)\n", " return pd.concat(out, ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "\n", "truth = make_fleet(220, seed=5)\n", "rng = np.random.default_rng(2)\n", "raw = truth.drop(columns=[\"t_fail\", \"event\", \"dur\", \"rul_h\", \"rul_d\"]).copy()\n", "raw[\"urgent_ops\"] = False\n", "# ๊ธด๊ธ‰ ์šด์šฉ(๊ฐ€๋™๋ฅ ์ด ๋น„์ •์ƒ์ ์œผ๋กœ ๋†’์ง€๋งŒ ์‹ค์ œ ์‚ฌ์‹ค์ธ ๊ธฐ๋ก): ์‚ญ์ œํ•˜๋ฉด ์•ˆ ๋œ๋‹ค\n", "urg = rng.choice(len(raw), 8, replace=False)\n", "raw.loc[urg, \"rate\"] = rng.uniform(13, 17, 8)\n", "raw.loc[urg, \"urgent_ops\"] = True\n", "# ์˜ค๋ฅ˜ ์ฃผ์ž…\n", "errs = {}\n", "i = rng.choice(len(raw), 12, replace=False); raw.loc[i, \"env\"] = rng.choice([\"ํ—˜์ง€\", \"ROUGH\", \"Salt \", \"ํ˜นํ•œ\", \"์ผ๋ฐ˜\", \"normal \"], 12); errs[\"ํ™˜๊ฒฝ ํ‘œ๊ธฐ\"] = 12\n", "i = rng.choice(len(raw), 8, replace=False); raw.loc[i, \"unit\"] = rng.choice([\"u3\", \"U 2\", \"U9\", \"u5\"], 8); errs[\"๋ถ€๋Œ€ ์ฝ”๋“œ\"] = 8\n", "i = rng.choice(len(raw), 6, replace=False); raw.loc[i, \"hours\"] = -raw.loc[i, \"hours\"]; errs[\"์šด์šฉ์‹œ๊ฐ„ ์Œ์ˆ˜\"] = 6\n", "i = rng.choice(len(raw), 7, replace=False); raw.loc[i, \"h_since\"] = raw.loc[i, \"hours\"] * rng.uniform(1.3, 2.0, 7); errs[\"์ฐฝ์ •๋น„ ํ›„ ์‹œ๊ฐ„ > ๋ˆ„์ \"] = 7\n", "i = rng.choice(len(raw), 6, replace=False); raw.loc[i, \"km\"] = raw.loc[i, \"km\"] * 1000; errs[\"km ๋‹จ์œ„(m)\"] = 6\n", "i = rng.choice(len(raw), 9, replace=False); raw.loc[i, \"rate\"] = np.nan; errs[\"๊ฐ€๋™๋ฅ  ๊ฒฐ์ธก\"] = 9\n", "i = rng.choice(len(raw), 9, replace=False); raw.loc[i, \"insp\"] = np.nan; errs[\"์ ๊ฒ€ ๋งˆ๋ชจ ๊ฒฐ์ธก\"] = 9\n", "i = rng.choice(len(raw), 5, replace=False); raw.loc[i, \"insp\"] = rng.uniform(1.8, 3.0, 5); errs[\"์ ๊ฒ€ ๋งˆ๋ชจ ๋ฒ”์œ„ ์ดˆ๊ณผ\"] = 5\n", "raw = pd.concat([raw, raw.iloc[rng.choice(len(raw), 7, replace=False)]], ignore_index=True); errs[\"์ค‘๋ณต ํ–‰\"] = 7\n", "raw[\"eq_id\"] = raw[\"eq_id\"].where(rng.random(len(raw)) > 0.05, raw[\"eq_id\"].str.lower()); errs[\"ID ํ‘œ๊ธฐ\"] = int((raw[\"eq_id\"].str.islower()).sum())\n", "print(f\"[0] ์˜ค์—ผ ๋ฐ์ดํ„ฐ {len(raw)}ํ–‰(์›๋ณธ {len(truth)}ํ–‰), ์ฃผ์ž… ์˜ค๋ฅ˜: \" + \", \".join(f\"{k} {v}\" for k, v in errs.items()) + f\", ๊ธด๊ธ‰ ์šด์šฉ ์ •์ƒ ๊ธฐ๋ก {len(urg)}ํ–‰\")\n", "\n", "# ---------------- ์ •์ œ ๊ทœ์น™ ----------------\n", "log = []\n", "d = raw.copy()\n", "\n", "\n", "def rule(no, desc, n):\n", " log.append((no, desc, int(n)))\n", "\n", "\n", "n0 = len(d)\n", "d[\"eq_id\"] = d[\"eq_id\"].str.upper().str.strip()\n", "rule(1, \"์žฅ๋น„ ID ๋Œ€๋ฌธ์žยท๊ณต๋ฐฑ ํ†ต์ผ\", (raw[\"eq_id\"] != d[\"eq_id\"]).sum())\n", "d = d.drop_duplicates(\"eq_id\").reset_index(drop=True)\n", "rule(2, \"์ค‘๋ณต ์žฅ๋น„ ํ–‰ ์ œ๊ฑฐ\", n0 - len(d))\n", "d[\"unit\"] = d[\"unit\"].astype(str).str.upper().str.replace(\" \", \"\")\n", "d[\"env\"] = d[\"env\"].astype(str).str.strip()\n", "EMAP = {\"ํ—˜์ง€\": \"rough\", \"rough\": \"rough\", \"salt\": \"salt\", \"ํ˜นํ•œ\": \"cold\", \"cold\": \"cold\", \"์ผ๋ฐ˜\": \"normal\", \"normal\": \"normal\", \"์—ผ๋ถ„\": \"salt\"}\n", "env_fix = d[\"env\"].str.lower().map(EMAP)\n", "rule(3, \"์šด์šฉ ํ™˜๊ฒฝ ํ‘œ๊ธฐ ํ‘œ์ค€ํ™”(ํ•œ๊ธ€ยท๋Œ€์†Œ๋ฌธ์žยท๊ณต๋ฐฑ)\", (env_fix != d[\"env\"]).sum())\n", "d[\"env\"] = env_fix.fillna(\"normal\")\n", "iso = ~d[\"unit\"].isin(UNITS)\n", "quarantine = d[iso].copy()\n", "d = d[~iso].reset_index(drop=True)\n", "rule(4, \"๋ฏธ๋“ฑ๋ก ๋ถ€๋Œ€ ์ฝ”๋“œ ํ–‰ ๊ฒฉ๋ฆฌ(์‚ญ์ œํ•˜์ง€ ์•Š๊ณ  ๋ณ„๋„ ๋ณด๊ด€)\", iso.sum())\n", "neg = d[\"hours\"] < 0\n", "d.loc[neg, \"hours\"] = -d.loc[neg, \"hours\"]\n", "rule(5, \"๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„ ๋ถ€ํ˜ธ ์˜ค๋ฅ˜ ๋ณด์ •\", neg.sum())\n", "bad_hs = d[\"h_since\"] > d[\"hours\"]\n", "d.loc[bad_hs, \"h_since\"] = d.loc[bad_hs, \"hours\"]\n", "rule(6, \"์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„ > ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„ ๋ชจ์ˆœ ๋ณด์ •\", bad_hs.sum())\n", "unit_km = d[\"km\"] / d[\"hours\"].clip(lower=1) > 100\n", "d.loc[unit_km, \"km\"] = d.loc[unit_km, \"km\"] / 1000\n", "rule(7, \"๊ธฐ๋™๊ฑฐ๋ฆฌ ๋‹จ์œ„ ์˜ค๋ฅ˜(m -> km) ๋ณด์ •\", unit_km.sum())\n", "nan_rate = d[\"rate\"].isna()\n", "d[\"rate\"] = d[\"rate\"].fillna(d.groupby([\"etype\"])[\"rate\"].transform(\"median\"))\n", "rule(8, \"์ผ ํ‰๊ท  ์šด์šฉ์‹œ๊ฐ„ ๊ฒฐ์ธก์„ ์žฅ๋น„ ๊ตฌ๋ถ„ ์ค‘์•™๊ฐ’์œผ๋กœ ๋ณด์ •\", nan_rate.sum())\n", "out_i = d[\"insp\"] > 1.3\n", "d.loc[out_i, \"insp\"] = np.nan\n", "miss_i = d[\"insp\"].isna()\n", "d[\"insp_flag\"] = np.where(miss_i, \"์ถ”์ •\", \"์ธก์ •\")\n", "d[\"insp\"] = d[\"insp\"].fillna((d[\"h_since\"] / CYCLE * d[\"env\"].map(W0)).clip(0, 1.3))\n", "rule(9, \"์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ ๋ฒ”์œ„ ์ดˆ๊ณผ ์ œ๊ฑฐ ํ›„ ๊ฒฐ์ธก์„ ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜๋กœ ๋Œ€์ฒด(์ถ”์ • ํ‘œ์‹œ)\", miss_i.sum())\n", "exc = (d[\"rate\"] > 12) | d[\"urgent_ops\"]\n", "d[\"exception\"] = np.where(exc, \"๊ธด๊ธ‰ ์šด์šฉ ๋ณด์กด\", \"\")\n", "rule(10, \"๊ธด๊ธ‰ ์šด์šฉ(๊ฐ€๋™๋ฅ  12h/์ผ ์ดˆ๊ณผ) ์˜ˆ์™ธ ํ‘œ์‹œ ํ›„ ์ด์ƒ์น˜ ์‚ญ์ œ ์—†์ด ๋ณด์กด\", exc.sum())\n", "d[\"aging_idx\"] = d[\"h_since\"] * d[\"env\"].map(W0) / CYCLE\n", "d[\"eff_hours\"] = d[\"h_since\"] * d[\"env\"].map(W0)\n", "rule(11, \"ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ ๋ฐ˜์˜ ๋…ธํ›„ํ™” ์ง€ํ‘œ(aging_idx, eff_hours) ์‚ฐ์ถœ\", len(d))\n", "print(\"\\n[1] ์ •์ œ ๊ทœ์น™ ์ ์šฉ ๋กœ๊ทธ\")\n", "print(pd.DataFrame(log, columns=[\"๊ทœ์น™\", \"๋‚ด์šฉ\", \"์ฒ˜๋ฆฌ ๊ฑด์ˆ˜\"]).to_string(index=False))\n", "print(f\" ์ •์ œ ๊ทœ์น™ {len(log)}์ข… ์ ์šฉ(๋ชฉํ‘œ 10์ข… ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(log) >= 10 else '๋ฏธ๋‹ฌ'}), ๊ฒฉ๋ฆฌ {len(quarantine)}ํ–‰์€ ๋‹ด๋‹น์ž ํ™•์ธ ๋Œ€๊ธฐ\")\n", "\n", "# ---------------- ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ ----------------\n", "checks = {\"์žฅ๋น„ ID ์œ ์ผ\": not d[\"eq_id\"].duplicated().any(), \"๋ถ€๋Œ€ ์ฝ”๋“œ ๋“ฑ๋ก\": bool(d[\"unit\"].isin(UNITS).all()), \"ํ™˜๊ฒฝ ๊ฐ’ 4์ข…\": bool(d[\"env\"].isin(ENVS).all()),\n", " \"์šด์šฉ์‹œ๊ฐ„ 0 ์ด์ƒ\": bool((d[\"hours\"] >= 0).all()), \"์ฐฝ์ •๋น„ ํ›„ <= ๋ˆ„์ \": bool((d[\"h_since\"] <= d[\"hours\"] + 1e-6).all()), \"๊ธฐ๋™ ์†๋„ 5~40km/h\": bool((d[\"km\"] / d[\"hours\"].clip(lower=1)).between(5, 40).all()),\n", " \"์ ๊ฒ€ ๋งˆ๋ชจ 0~1.3\": bool(d[\"insp\"].between(0, 1.3).all()), \"ํ•„์ˆ˜ ์ปฌ๋Ÿผ ๊ฒฐ์ธก 0\": int(d[[\"rate\", \"insp\", \"aging_idx\"]].isna().sum().sum()) == 0}\n", "print(\"\\n[2] ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ\")\n", "for k, v in checks.items():\n", " print(f\" [{'PASS' if v else 'FAIL'}] {k}\")\n", "print(f\" ํ†ต๊ณผ์œจ {sum(checks.values()) / len(checks):.0%} (๋ชฉํ‘œ 100%)\")\n", "\n", "# ---------------- ๋ณต์› ์ •ํ™•๋„ยท์˜ˆ์™ธ ๋ณด์กดยท์ „ํ›„ ํ†ต๊ณ„ ----------------\n", "t = truth.set_index(\"eq_id\").loc[d[\"eq_id\"].isin(truth[\"eq_id\"]).pipe(lambda m: d.loc[m, \"eq_id\"])]\n", "dd = d.set_index(\"eq_id\").loc[t.index]\n", "print(f\"\\n[3] ๋ณต์› ์ •ํ™•๋„(์›๋ณธ๊ณผ ๋น„๊ต): ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„ ์˜ค์ฐจ {np.abs(dd['hours'] - t['hours']).mean():.1f}h, ๊ธฐ๋™๊ฑฐ๋ฆฌ ์˜ค์ฐจ {np.abs(dd['km'] - t['km']).mean():.1f}km, ๊ฐ€๋™๋ฅ  ์˜ค์ฐจ {np.abs(dd['rate'] - t['rate'])[dd['exception'] == ''].mean():.3f}h/์ผ(๊ธด๊ธ‰ ์šด์šฉ ์ œ์™ธ)\")\n", "print(f\" ๊ธด๊ธ‰ ์šด์šฉ ๊ธฐ๋ก {int((d['exception'] != '').sum())}ํ–‰ ๋ณด์กด(์‚ญ์ œ 0ํ–‰), ์ ๊ฒ€ ๋งˆ๋ชจ ์ถ”์ • ๋Œ€์ฒด {int((d['insp_flag'] == '์ถ”์ •').sum())}ํ–‰์€ ์ถ”์ • ํ‘œ์‹œ\")\n", "rows = [(\"์šด์šฉ์‹œ๊ฐ„ ์ตœ์†Ÿ๊ฐ’(h)\", raw[\"hours\"].min(), d[\"hours\"].min(), truth[\"hours\"].min()), (\"์šด์šฉ์‹œ๊ฐ„ ํ‰๊ท (h)\", raw[\"hours\"].mean(), d[\"hours\"].mean(), truth[\"hours\"].mean()),\n", " (\"๊ธฐ๋™๊ฑฐ๋ฆฌ ์ตœ๋Œ“๊ฐ’(km)\", raw[\"km\"].max(), d[\"km\"].max(), truth[\"km\"].max()), (\"๊ฐ€๋™๋ฅ  ์œ ํšจ ๊ฑด์ˆ˜\", raw[\"rate\"].notna().sum(), d[\"rate\"].notna().sum(), truth[\"rate\"].notna().sum()),\n", " (\"๊ฐ€๋™๋ฅ  ์ตœ๋Œ“๊ฐ’(h/์ผ)\", raw[\"rate\"].max(), d[\"rate\"].max(), truth[\"rate\"].max()), (\"์ ๊ฒ€ ๋งˆ๋ชจ ์ตœ๋Œ“๊ฐ’\", raw[\"insp\"].max(), d[\"insp\"].max(), truth[\"insp\"].max())]\n", "print(\"[4] ์ „ํ›„ ๊ธฐ์ˆ ํ†ต๊ณ„(์˜ค์—ผ / ์ •์ œ ํ›„ / ์›๋ณธ)\")\n", "print(pd.DataFrame(rows, columns=[\"ํ•ญ๋ชฉ\", \"์˜ค์—ผ\", \"์ •์ œ ํ›„\", \"์›๋ณธ\"]).round(2).to_string(index=False))\n", "w = d.groupby(\"env\")[\"aging_idx\"].mean().round(3)\n", "print(\" ํ™˜๊ฒฝ๋ณ„ ํ‰๊ท  ๋…ธํ›„ ์ง€์ˆ˜(๊ฐ€์ค‘์น˜ W0 ์ ์šฉ):\", w.to_dict())\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 02 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[0] ์˜ค์—ผ ๋ฐ์ดํ„ฐ 227ํ–‰(์›๋ณธ 220ํ–‰), ์ฃผ์ž… ์˜ค๋ฅ˜: ํ™˜๊ฒฝ ํ‘œ๊ธฐ 12, ๋ถ€๋Œ€ ์ฝ”๋“œ 8, ์šด์šฉ์‹œ๊ฐ„ ์Œ์ˆ˜ 6, ์ฐฝ์ •๋น„ ํ›„ ์‹œ๊ฐ„ > ๋ˆ„์  7, km ๋‹จ์œ„(m) 6, ๊ฐ€๋™๋ฅ  ๊ฒฐ์ธก 9, ์ ๊ฒ€ ๋งˆ๋ชจ ๊ฒฐ์ธก 9, ์ ๊ฒ€ ๋งˆ๋ชจ ๋ฒ”์œ„ ์ดˆ๊ณผ 5, ์ค‘๋ณต ํ–‰ 7, ID ํ‘œ๊ธฐ 7, ๊ธด๊ธ‰ ์šด์šฉ ์ •์ƒ ๊ธฐ๋ก 8ํ–‰\n", "\n", "[1] ์ •์ œ ๊ทœ์น™ ์ ์šฉ ๋กœ๊ทธ\n", " ๊ทœ์น™ ๋‚ด์šฉ ์ฒ˜๋ฆฌ ๊ฑด์ˆ˜\n", " 1 ์žฅ๋น„ ID ๋Œ€๋ฌธ์žยท๊ณต๋ฐฑ ํ†ต์ผ 7\n", " 2 ์ค‘๋ณต ์žฅ๋น„ ํ–‰ ์ œ๊ฑฐ 7\n", " 3 ์šด์šฉ ํ™˜๊ฒฝ ํ‘œ๊ธฐ ํ‘œ์ค€ํ™”(ํ•œ๊ธ€ยท๋Œ€์†Œ๋ฌธ์žยท๊ณต๋ฐฑ) 10\n", " 4 ๋ฏธ๋“ฑ๋ก ๋ถ€๋Œ€ ์ฝ”๋“œ ํ–‰ ๊ฒฉ๋ฆฌ(์‚ญ์ œํ•˜์ง€ ์•Š๊ณ  ๋ณ„๋„ ๋ณด๊ด€) 1\n", " 5 ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„ ๋ถ€ํ˜ธ ์˜ค๋ฅ˜ ๋ณด์ • 6\n", " 6 ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„ > ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„ ๋ชจ์ˆœ ๋ณด์ • 7\n", " 7 ๊ธฐ๋™๊ฑฐ๋ฆฌ ๋‹จ์œ„ ์˜ค๋ฅ˜(m -> km) ๋ณด์ • 6\n", " 8 ์ผ ํ‰๊ท  ์šด์šฉ์‹œ๊ฐ„ ๊ฒฐ์ธก์„ ์žฅ๋น„ ๊ตฌ๋ถ„ ์ค‘์•™๊ฐ’์œผ๋กœ ๋ณด์ • 9\n", " 9 ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ ๋ฒ”์œ„ ์ดˆ๊ณผ ์ œ๊ฑฐ ํ›„ ๊ฒฐ์ธก์„ ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜๋กœ ๋Œ€์ฒด(์ถ”์ • ํ‘œ์‹œ) 13\n", " 10 ๊ธด๊ธ‰ ์šด์šฉ(๊ฐ€๋™๋ฅ  12h/์ผ ์ดˆ๊ณผ) ์˜ˆ์™ธ ํ‘œ์‹œ ํ›„ ์ด์ƒ์น˜ ์‚ญ์ œ ์—†์ด ๋ณด์กด 8\n", " 11 ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ ๋ฐ˜์˜ ๋…ธํ›„ํ™” ์ง€ํ‘œ(aging_idx, eff_hours) ์‚ฐ์ถœ 219\n", " ์ •์ œ ๊ทœ์น™ 11์ข… ์ ์šฉ(๋ชฉํ‘œ 10์ข… ์ด์ƒ: ๋‹ฌ์„ฑ), ๊ฒฉ๋ฆฌ 1ํ–‰์€ ๋‹ด๋‹น์ž ํ™•์ธ ๋Œ€๊ธฐ\n", "\n", "[2] ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ\n", " [PASS] ์žฅ๋น„ ID ์œ ์ผ\n", " [PASS] ๋ถ€๋Œ€ ์ฝ”๋“œ ๋“ฑ๋ก\n", " [PASS] ํ™˜๊ฒฝ ๊ฐ’ 4์ข…\n", " [PASS] ์šด์šฉ์‹œ๊ฐ„ 0 ์ด์ƒ\n", " [PASS] ์ฐฝ์ •๋น„ ํ›„ <= ๋ˆ„์ \n", " [PASS] ๊ธฐ๋™ ์†๋„ 5~40km/h\n", " [PASS] ์ ๊ฒ€ ๋งˆ๋ชจ 0~1.3\n", " [PASS] ํ•„์ˆ˜ ์ปฌ๋Ÿผ ๊ฒฐ์ธก 0\n", " ํ†ต๊ณผ์œจ 100% (๋ชฉํ‘œ 100%)\n", "\n", "[3] ๋ณต์› ์ •ํ™•๋„(์›๋ณธ๊ณผ ๋น„๊ต): ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„ ์˜ค์ฐจ 0.0h, ๊ธฐ๋™๊ฑฐ๋ฆฌ ์˜ค์ฐจ 0.0km, ๊ฐ€๋™๋ฅ  ์˜ค์ฐจ 0.016h/์ผ(๊ธด๊ธ‰ ์šด์šฉ ์ œ์™ธ)\n", " ๊ธด๊ธ‰ ์šด์šฉ ๊ธฐ๋ก 8ํ–‰ ๋ณด์กด(์‚ญ์ œ 0ํ–‰), ์ ๊ฒ€ ๋งˆ๋ชจ ์ถ”์ • ๋Œ€์ฒด 13ํ–‰์€ ์ถ”์ • ํ‘œ์‹œ\n", "[4] ์ „ํ›„ ๊ธฐ์ˆ ํ†ต๊ณ„(์˜ค์—ผ / ์ •์ œ ํ›„ / ์›๋ณธ)\n", " ํ•ญ๋ชฉ ์˜ค์—ผ ์ •์ œ ํ›„ ์›๋ณธ\n", " ์šด์šฉ์‹œ๊ฐ„ ์ตœ์†Ÿ๊ฐ’(h) -25733.90 485.39 485.39\n", " ์šด์šฉ์‹œ๊ฐ„ ํ‰๊ท (h) 11623.87 12523.40 12475.96\n", "๊ธฐ๋™๊ฑฐ๋ฆฌ ์ตœ๋Œ“๊ฐ’(km) 476232660.71 490734.91 490734.91\n", " ๊ฐ€๋™๋ฅ  ์œ ํšจ ๊ฑด์ˆ˜ 218.00 219.00 220.00\n", "๊ฐ€๋™๋ฅ  ์ตœ๋Œ“๊ฐ’(h/์ผ) 15.68 15.68 6.53\n", " ์ ๊ฒ€ ๋งˆ๋ชจ ์ตœ๋Œ“๊ฐ’ 2.34 1.30 1.08\n", " ํ™˜๊ฒฝ๋ณ„ ํ‰๊ท  ๋…ธํ›„ ์ง€์ˆ˜(๊ฐ€์ค‘์น˜ W0 ์ ์šฉ): {'cold': 0.8, 'normal': 0.848, 'rough': 0.846, 'salt': 0.83}\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 03. ์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ๊ธฐ๋ฐ˜ 1์ฐจ ์ž”์—ฌ ์ˆ˜๋ช…(RUL) ๊ณ„์‚ฐ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ๊ธฐ๋ฐ˜ 1์ฐจ ์ž”์—ฌ ์ˆ˜๋ช…(RUL) ๊ณ„์‚ฐ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ๊ธฐ๋ฐ˜ 1์ฐจ ์ž”์—ฌ ์ˆ˜๋ช…(RUL) Baseline์„ ๊ตฌํ˜„ํ•˜๊ณ  EDA๋กœ ์˜ค์ฐจ ์š”์ธ์„ ์ฐพ๋Š”๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ทœ์ • ์ฃผ๊ธฐ์—์„œ ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„์„ ๋นผ๊ณ  ์ผ ๊ฐ€๋™๋ฅ ๋กœ ๋‚˜๋ˆ  ์ž”์—ฌ ์ˆ˜๋ช… ์‚ฐ์ถœํ•˜๊ณ  ๋ชจ๋ธ์— ์ ์šฉํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `notebooks/sup02_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": [ "\"\"\"์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ(๊ทœ์ • ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ - ๊ฒฝ๊ณผ ์šด์šฉ์‹œ๊ฐ„) ๊ธฐ๋ฐ˜ ์ž”์—ฌ ์ˆ˜๋ช…(RUL) Baseline์„ ๊ตฌํ˜„ํ•ด ์˜ค์ฐจ์™€ ํ™˜๊ฒฝ๋ณ„ ํŽธํ–ฅยทํ•œ๊ณ„๋ฅผ ํ™•์ธํ•˜๊ณ , EDA ์ฐจํŠธ 8์ข…ยท์ƒ๊ด€๋ถ„์„(p-value)ยทํ™˜๊ฒฝ๋ณ„ ์ฐจ์ด ๊ฒ€์ •ยท๋ถ€๋Œ€๋ณ„ ์œ„ํ—˜ ํŒŒ๋ ˆํ† ๋กœ ํ•ต์‹ฌ ๋…ธํ›„ํ™” ์š”์ธ์„ ์ฐพ๋Š”๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy import stats\n", "from sklearn.metrics import r2_score\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์žฅ๋น„ ๋ณด์œ  ํ˜„ํ™ฉยท๊ณ ์žฅ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ˆ˜์น˜ ์—†์Œ)\n", "# ๊ฐ€์ƒ ๋ถ€๋Œ€ 6๊ฐœ(U1~U6)์˜ ์žฅ๋น„๊ฐ€ ์šด์šฉ ํ™˜๊ฒฝ(normal/rough/salt/cold)์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์†๋„๋กœ ๋…ธํ›„ํ™”ํ•œ๋‹ค. ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„(h_since)์ด\n", "# ์žฅ๋น„๋งˆ๋‹ค ์ˆจ์€ ์ˆ˜๋ช…(t_fail)์„ ๋„˜์œผ๋ฉด ๊ณ ์žฅ(event=1) ์ด๋ ฅ, ๋„˜์ง€ ์•Š์•˜์œผ๋ฉด ํ˜„์žฌ๊นŒ์ง€ ์ƒ์กด(event=0, ๊ด€์ธก ์ค‘๋„์ ˆ๋‹จ)์ด๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ENVS = [\"normal\", \"rough\", \"salt\", \"cold\"]\n", "ENV_W = {\"normal\": 1.00, \"rough\": 1.35, \"salt\": 1.25, \"cold\": 1.12} # ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜(์ˆจ์€ ์ •๋‹ต, ๊ฐ€์ •๊ฐ’): ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ์งง๋‹ค\n", "ENV_KO = {\"normal\": \"์ผ๋ฐ˜\", \"rough\": \"ํ—˜์ง€\", \"salt\": \"์—ผ๋ถ„\", \"cold\": \"ํ˜นํ•œ\"}\n", "UNITS = [\"U1\", \"U2\", \"U3\", \"U4\", \"U5\", \"U6\"]\n", "UNIT_ENV = {\"U1\": [.7, .1, .1, .1], \"U2\": [.2, .6, .1, .1], \"U3\": [.2, .1, .6, .1], \"U4\": [.3, .1, .1, .5], \"U5\": [.5, .3, .1, .1], \"U6\": [.4, .1, .4, .1]}\n", "ETYPES = [\"truck\", \"generator\", \"trailer\", \"radio\"]\n", "CYCLE = 8000.0 # ๊ทœ์ • ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ(์šด์šฉ์‹œ๊ฐ„ h, ๊ฐ€์ •๊ฐ’)\n", "KSHAPE = 12.0 # ์ˆ˜๋ช… ๋ถ„ํฌ ์™€์ด๋ธ” ํ˜•์ƒ(๋งˆ๋ชจํ˜• ๊ณ ์žฅ, ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ๋ชจ์—ฌ ์žˆ๋‹ค)\n", "INSP_NOISE = 0.025 # ์ •๋น„ ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ ์ธก์ • ์˜ค์ฐจ(๊ฐ€์ •๊ฐ’)\n", "\n", "\n", "def make_fleet(n=900, seed=0, env_w=None, shift=1.0):\n", " \"\"\"์žฅ๋น„ n๋Œ€. env_w๋กœ ์ˆจ์€ ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜๋ฅผ ๋ฐ”๊ฟ” ์šด์˜ ๋ถ„ํฌ ๋ณ€ํ™”๋ฅผ ๋ชจ์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. shift>1์ด๋ฉด ๊ฐ€๋™๋ฅ ์ด ๋†’์•„์ง„๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " w = env_w or ENV_W\n", " unit = rng.choice(UNITS, n)\n", " env = np.array([rng.choice(ENVS, p=UNIT_ENV[u]) for u in unit])\n", " et = rng.choice(ETYPES, n, p=[.4, .2, .2, .2])\n", " rate = rng.lognormal(np.log(3.0 * shift), 0.25, n) # ์ผ ํ‰๊ท  ์šด์šฉ์‹œ๊ฐ„(h/์ผ)\n", " h_since = rng.uniform(300, 11500, n) # ๋งˆ์ง€๋ง‰ ์ฐฝ์ •๋น„ ์ดํ›„ ์šด์šฉ์‹œ๊ฐ„\n", " n_ovh = rng.integers(0, 3, n)\n", " hours = h_since + n_ovh * rng.uniform(5000, 8000, n) # ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„\n", " km = hours * rng.normal(17, 3, n) * np.where(env == \"rough\", 0.8, 1.0) # ๋ˆ„์  ๊ธฐ๋™๊ฑฐ๋ฆฌ(km)\n", " age_y = np.clip(hours / (rate * 365) * rng.uniform(0.9, 1.15, n), 0.5, 25)\n", " unpl = rng.poisson(0.35 * h_since / 1000 * np.array([w[e] for e in env]) / 1.3) # ๋น„๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜(ํ™˜๊ฒฝ์ด ๊ฐ€ํ˜นํ• ์ˆ˜๋ก ๋งŽ๋‹ค)\n", " n_maint = rng.poisson(0.6 * h_since / 1000 + 1) # ๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜\n", " scale = 9600 / np.array([w[e] for e in env]) * 0.985 ** unpl * np.exp(rng.normal(0, 0.04, n))\n", " t_fail = scale * rng.weibull(KSHAPE, n) # ์ฐฝ์ •๋น„ ํ›„ ๊ณ ์žฅ๊นŒ์ง€ ์šด์šฉ์‹œ๊ฐ„(์ˆจ์€ ์ •๋‹ต)\n", " event = (t_fail <= h_since).astype(int)\n", " dur = np.where(event == 1, t_fail, h_since)\n", " df = pd.DataFrame({\"eq_id\": [f\"EQ{seed:02d}{i:04d}\" for i in range(n)], \"unit\": unit, \"env\": env, \"etype\": et, \"rate\": rate, \"hours\": hours, \"km\": km, \"age_y\": age_y, \"n_ovh\": n_ovh,\n", " \"h_since\": h_since, \"unpl\": unpl, \"n_maint\": n_maint, \"t_fail\": t_fail, \"event\": event, \"dur\": dur})\n", " df[\"insp\"] = np.clip(dur / t_fail + rng.normal(0, INSP_NOISE, n), 0, 1.3) # ๋งˆ์ง€๋ง‰ ์ •๋น„ ์ ๊ฒ€์—์„œ ์ธก์ •ํ•œ ๋งˆ๋ชจ ๋น„์œจ(์†Œ๋ชจ ์ˆ˜๋ช…/์ด ์ˆ˜๋ช…, ์˜ค์ฐจ ํฌํ•จ)\n", " df[\"rul_h\"] = np.where(event == 0, t_fail - h_since, np.nan) # ์ƒ์กด ์žฅ๋น„์˜ ๋‚จ์€ ์šด์šฉ์‹œ๊ฐ„(์ •๋‹ต)\n", " df[\"rul_d\"] = df[\"rul_h\"] / df[\"rate\"] # ๋‚จ์€ ์ผ์ˆ˜(์ •๋‹ต)\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " y, p = np.asarray(y, float), np.asarray(p, float)\n", " return float(np.mean(np.abs(y - p) / np.abs(y)) * 100)\n", "\n", "\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "\n", "\n", "def rmse(y, p):\n", " return float(np.sqrt(np.mean((np.asarray(y, float) - np.asarray(p, float)) ** 2)))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธํ›„ํ™” ํŒŒ์ƒ ํ”ผ์ฒ˜์™€ ํ•™์Šต ํ‘œ๋ณธ ๋งŒ๋“ค๊ธฐ. ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ W0๋Š” ๋„์ž… ์‹œ์ ์˜ ์ „๋ฌธ๊ฐ€ ์ถ”์ •๊ฐ’์ด๋ฉฐ(์‹ค์ œ ๊ฐ€์ค‘์น˜์™€ ๋‹ค๋ฆ„) M9์—์„œ ์žฌ๋ณด์ •ํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "W0 = {\"normal\": 1.00, \"rough\": 1.15, \"salt\": 1.10, \"cold\": 1.05}\n", "FEATS = [\"h_since\", \"hours\", \"km\", \"age_y\", \"rate\", \"n_maint\", \"unpl\", \"unpl_per_kh\", \"km_per_h\", \"n_ovh\", \"k_rough\", \"k_salt\", \"k_cold\", \"k_truck\", \"k_gen\", \"k_trailer\",\n", " \"aging_idx\", \"lin_rul_d\", \"hs_frac\", \"insp\", \"insp_eta_h\"]\n", "\n", "\n", "def add_features(df, w=None):\n", " d = df.copy()\n", " w = w or W0\n", " d[\"unpl_per_kh\"] = d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)\n", " d[\"km_per_h\"] = d[\"km\"] / d[\"hours\"]\n", " for e in (\"rough\", \"salt\", \"cold\"):\n", " d[\"k_\" + e] = (d[\"env\"] == e).astype(int)\n", " d[\"k_truck\"], d[\"k_gen\"], d[\"k_trailer\"] = (d[\"etype\"] == \"truck\").astype(int), (d[\"etype\"] == \"generator\").astype(int), (d[\"etype\"] == \"trailer\").astype(int)\n", " d[\"aging_idx\"] = d[\"h_since\"] * d[\"env\"].map(w) / CYCLE # ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜(1์ด๋ฉด ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ์— ๋„๋‹ฌ)\n", " d[\"lin_rul_d\"] = (CYCLE - d[\"h_since\"]) / d[\"rate\"] # ์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ์ž”์—ฌ ์ผ์ˆ˜(Baseline)\n", " d[\"hs_frac\"] = d[\"h_since\"] / CYCLE\n", " d[\"insp_eta_h\"] = d[\"h_since\"] / np.maximum(d[\"insp\"], 0.05) # ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ๋กœ ํ™˜์‚ฐํ•œ ์ด ์ˆ˜๋ช… ์ถ”์ •(h)\n", " return d\n", "\n", "\n", "def snapshots(df, k=4, seed=0):\n", " \"\"\"๊ณ ์žฅ ์ด๋ ฅ ์žฅ๋น„์˜ ๊ณผ๊ฑฐ ์‹œ์  ํ‘œ๋ณธ: ๊ณ ์žฅ ์ „ ์ž„์˜ ์‹œ์ ์˜ ์ ๊ฒ€๊ฐ’ยท์šด์šฉ์‹œ๊ฐ„๊ณผ ๊ทธ๋•Œ์˜ ๋‚จ์€ ์ผ์ˆ˜. (์ƒ์กด ์žฅ๋น„์˜ ๋ฏธ๋ž˜๋Š” ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๊ณ ์žฅ ์ด๋ ฅ๋งŒ ์“ด๋‹ค)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " f = df[df[\"event\"] == 1]\n", " out = []\n", " for _ in range(k):\n", " s = f.copy()\n", " h = s[\"t_fail\"] * rng.uniform(0.2, 0.98, len(s))\n", " ratio = h / s[\"h_since\"]\n", " s[\"hours\"] = s[\"hours\"] - (s[\"h_since\"] - h)\n", " s[\"km\"] = s[\"km\"] * ratio.clip(upper=1)\n", " s[\"unpl\"] = np.floor(s[\"unpl\"] * ratio)\n", " s[\"n_maint\"] = np.floor(s[\"n_maint\"] * ratio)\n", " s[\"insp\"] = np.clip(h / s[\"t_fail\"] + rng.normal(0, INSP_NOISE, len(s)), 0, 1.3)\n", " s[\"h_since\"] = h\n", " s[\"rul_d\"] = (s[\"t_fail\"] - h) / s[\"rate\"]\n", " s[\"event\"] = 0\n", " out.append(s)\n", " return pd.concat(out, ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "\n", "df = make_fleet(900, seed=1)\n", "d = add_features(df)\n", "al = d[d.event == 0].copy()\n", "al[\"err\"] = al[\"lin_rul_d\"] - al[\"rul_d\"] # ์–‘์ˆ˜ = Baseline์ด ์ˆ˜๋ช…์„ ๊ธธ๊ฒŒ ๋ณธ๋‹ค(์œ„ํ—˜ํ•œ ๊ณผ๋Œ€ ์˜ˆ์ธก)\n", "big = al[\"rul_d\"] >= 90\n", "print(f\"[1] ๊ฐ€์ƒ ์žฅ๋น„ {len(d)}๋Œ€(๊ณ ์žฅ ์ด๋ ฅ {int(d.event.sum())}๋Œ€, ์ƒ์กด {len(al)}๋Œ€). Baseline RUL(์ผ) = (๊ทœ์ • ์ฃผ๊ธฐ {CYCLE:.0f}h - ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„) / ์ผ ๊ฐ€๋™๋ฅ \")\n", "print(f\" ์ƒ์กด ์žฅ๋น„ ํ‰๊ฐ€: MAE {mae(al.rul_d, al.lin_rul_d):.0f}์ผ, RMSE {rmse(al.rul_d, al.lin_rul_d):.0f}์ผ, MAPE(์ฐธ RUL 90์ผ ์ด์ƒ) {mape(al.rul_d[big], al.lin_rul_d[big]):.1f}%, R2 {r2_score(al.rul_d, al.lin_rul_d):.3f}, ํ‰๊ท  ํŽธํ–ฅ {al.err.mean():+.0f}์ผ\")\n", "over = (al[\"lin_rul_d\"] > al[\"rul_d\"] + 30)\n", "print(f\" Baseline์ด ์‹ค์ œ๋ณด๋‹ค 30์ผ ๋„˜๊ฒŒ ๊ธธ๊ฒŒ ๋ณธ ์žฅ๋น„ {int(over.sum())}๋Œ€({over.mean():.0%}) - ๊ณ ์žฅ ์ „์— ์ •๋น„ ์‹œ์ ์„ ๋†“์น  ์œ„ํ—˜\")\n", "fail_before = d[(d.event == 1) & (d.t_fail < CYCLE)]\n", "print(f\" ๊ณ ์žฅ ์ด๋ ฅ {int(d.event.sum())}๋Œ€ ์ค‘ ๊ทœ์ • ์ฃผ๊ธฐ({CYCLE:.0f}h) ์ด์ „์— ๊ณ ์žฅ ๋‚œ ์žฅ๋น„ {len(fail_before)}๋Œ€({len(fail_before) / d.event.sum():.0%}) -> ๊ณ ์ • ์ฃผ๊ธฐ๋Š” ๊ฐ€ํ˜น ํ™˜๊ฒฝ ์žฅ๋น„๋ฅผ ์ง€ํ‚ค์ง€ ๋ชปํ•œ๋‹ค\")\n", "\n", "# ์กฐ๊ฑด๋ณ„ ์˜ค์ฐจ\n", "conds = {\"์ผ๋ฐ˜ ํ™˜๊ฒฝ\": al.env == \"normal\", \"ํ—˜์ง€\": al.env == \"rough\", \"์—ผ๋ถ„\": al.env == \"salt\", \"ํ˜นํ•œ\": al.env == \"cold\", \"๋น„๊ณ„ํš ์ •๋น„์œจ ์ƒ์œ„ 25%\": al.unpl_per_kh >= al.unpl_per_kh.quantile(.75),\n", " \"์ผ ๊ฐ€๋™๋ฅ  4h ์ด์ƒ\": al.rate >= 4, \"์ ๊ฒ€ ๋งˆ๋ชจ 0.8 ์ด์ƒ(๋…ธํ›„)\": al.insp >= 0.8}\n", "ct = pd.DataFrame([(k, int(m.sum()), mae(al.rul_d[m], al.lin_rul_d[m]), al.err[m].mean()) for k, m in conds.items()], columns=[\"์กฐ๊ฑด\", \"๋Œ€์ˆ˜\", \"MAE(์ผ)\", \"ํ‰๊ท  ํŽธํ–ฅ(์ผ)\"])\n", "print(\"\\n[2] ์กฐ๊ฑด๋ณ„ Baseline ์˜ค์ฐจ(์–‘์ˆ˜ = ๊ณผ๋Œ€ ์˜ˆ์ธก)\\n\" + ct.round(0).to_string(index=False))\n", "print(\" -> ํ—˜์ง€ยท์—ผ๋ถ„ ์žฅ๋น„๋Š” ์ˆ˜๋ช…์„ ๊ธธ๊ฒŒ(์œ„ํ—˜), ์ผ๋ฐ˜ ํ™˜๊ฒฝ ์žฅ๋น„๋Š” ์งง๊ฒŒ(๊ณผ์ž‰ ์ •๋น„) ๋ณด์•„ ํ™˜๊ฒฝ๋ณ„ ํŽธํ–ฅ ๋ฐฉํ–ฅ์ด ๋ฐ˜๋Œ€๋‹ค. ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ ๋ฏธ๋ฐ˜์˜์ด ํ•œ๊ณ„๋‹ค.\")\n", "\n", "# ์ƒ๊ด€๋ถ„์„\n", "rows = []\n", "for c in FEATS:\n", " if al[c].nunique() < 2: continue\n", " r, p = stats.spearmanr(al[c], al[\"err\"])\n", " rows.append((c, r, p))\n", "cr = pd.DataFrame(rows, columns=[\"ํ”ผ์ฒ˜\", \"์Šคํ”ผ์–ด๋งŒ ์ƒ๊ด€\", \"p-value\"]).assign(abs_r=lambda x: x[\"์Šคํ”ผ์–ด๋งŒ ์ƒ๊ด€\"].abs()).sort_values(\"abs_r\", ascending=False)\n", "sig = cr[(cr[\"abs_r\"] >= 0.2) & (cr[\"p-value\"] < 0.05)]\n", "print(f\"\\n[3] Baseline ์˜ค์ฐจ(๋ถ€ํ˜ธ ์žˆ์Œ: ์–‘์ˆ˜ = ๊ณผ๋Œ€ ์˜ˆ์ธก)์™€ ์œ ์˜ํ•œ ์ƒ๊ด€(|r|>=0.2, p<0.05) ํ”ผ์ฒ˜ {len(sig)}๊ฐœ (๋ชฉํ‘œ 5๊ฐœ ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(sig) >= 5 else '๋ฏธ๋‹ฌ'})\")\n", "print(sig.head(8)[[\"ํ”ผ์ฒ˜\", \"์Šคํ”ผ์–ด๋งŒ ์ƒ๊ด€\", \"p-value\"]].round(4).to_string(index=False))\n", "print(\" (lin_rul_dยทh_sinceยทhs_frac๋Š” Baseline ์‚ฐ์‹๊ณผ ์ง์ ‘ ๊ฒน์น˜๋ฏ€๋กœ ํ•ด์„์—์„œ๋Š” ํ™˜๊ฒฝ ๋”๋ฏธยท์ ๊ฒ€ ๋งˆ๋ชจยท๊ธฐ๋™ ๊ฐ•๋„ ๊ฐ™์€ ๋…๋ฆฝ ์š”์ธ์„ ๋ณธ๋‹ค)\")\n", "hi, lo = al[al.env.isin([\"rough\", \"salt\"])][\"err\"], al[al.env.isin([\"normal\"])][\"err\"]\n", "u = stats.mannwhitneyu(hi, lo)\n", "kw = stats.kruskal(*[g[\"insp_eta_h\"].values for _, g in al.groupby(\"env\")])\n", "print(f\" ๊ฐ€์„ค๊ฒ€์ •: ํ—˜์ง€ยท์—ผ๋ถ„ vs ์ผ๋ฐ˜ ํ™˜๊ฒฝ Baseline ํŽธํ–ฅ ํ‰๊ท  {hi.mean():+.0f}์ผ vs {lo.mean():+.0f}์ผ(Mann-Whitney p={u.pvalue:.1e}), ํ™˜๊ฒฝ๋ณ„ ์ ๊ฒ€ ํ™˜์‚ฐ ์ด ์ˆ˜๋ช… ์ฐจ์ด Kruskal p={kw.pvalue:.1e}\")\n", "\n", "# ํŒŒ๋ ˆํ† : ๋ถ€๋Œ€๋ณ„ ๊ณ ์œ„ํ—˜(์ฐธ RUL 180์ผ ๋ฏธ๋งŒ) ์žฅ๋น„\n", "risk = al[al.rul_d < 180].groupby(\"unit\").size().sort_values(ascending=False)\n", "cum = risk.cumsum() / risk.sum()\n", "print(f\"\\n[4] ํŒŒ๋ ˆํ† : ์ฐธ RUL 180์ผ ๋ฏธ๋งŒ ๊ณ ์œ„ํ—˜ ์žฅ๋น„ {int(risk.sum())}๋Œ€, ์ƒ์œ„ {int((cum <= 0.8).sum() + 1)}๊ฐœ ๋ถ€๋Œ€๊ฐ€ 80% ์ฐจ์ง€ -> \" + \", \".join(f\"{u} {n}๋Œ€\" for u, n in risk.items()))\n", "print(\" ํ•ต์‹ฌ ๋ฐœ๊ฒฌ: ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜์™€ ์ •๋น„ ์ด๋ ฅยท์ ๊ฒ€ ๋งˆ๋ชจ๊ฐ€ Baseline์ด ๋†“์น˜๋Š” ์š”์ธ์ด๋ฉฐ, ์ด๋Š” M4 ํ”ผ์ฒ˜ ์„ค๊ณ„(ํ™˜๊ฒฝ ๋”๋ฏธ, ๋น„๊ณ„ํš ์ •๋น„์œจ, ์ ๊ฒ€ ๋งˆ๋ชจ)์˜ ๊ทผ๊ฑฐ๊ฐ€ ๋œ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(2, 4, figsize=(18, 8))\n", "ax[0, 0].boxplot([d.loc[d.env == e, \"insp_eta_h\"] for e in ENVS]); ax[0, 0].set_xticklabels([\"normal\", \"rough\", \"salt\", \"cold\"]); ax[0, 0].set_title(\"1 Estimated total life by environment (h)\")\n", "ax[0, 1].scatter(d.h_since, d.dur, c=d.event, s=6, cmap=\"coolwarm\"); ax[0, 1].set_title(\"2 Hours since overhaul vs observed (red = failed)\")\n", "ax[0, 2].hist(d.rate, 30); ax[0, 2].set_title(\"3 Daily usage rate (h/day)\")\n", "ax[0, 3].scatter(d.h_since, d.unpl, s=6, c=d.env.map({e: i for i, e in enumerate(ENVS)})); ax[0, 3].set_title(\"4 Unplanned maintenance vs hours\")\n", "ax[1, 0].bar(range(len(ct)), ct[\"ํ‰๊ท  ํŽธํ–ฅ(์ผ)\"]); ax[1, 0].set_xticks(range(len(ct))); ax[1, 0].set_xticklabels([f\"C{i + 1}\" for i in range(len(ct))]); ax[1, 0].set_title(\"5 Baseline bias by condition (days)\")\n", "ax[1, 1].scatter(al.insp, al.hs_frac, s=6, c=al.env.map({e: i for i, e in enumerate(ENVS)})); ax[1, 1].set_title(\"6 Inspection wear vs hours fraction\")\n", "ax[1, 2].scatter(al.rul_d, al.lin_rul_d, s=6); ax[1, 2].plot([0, 4000], [0, 4000], \"k--\"); ax[1, 2].set_title(\"7 True RUL vs Baseline RUL (days)\")\n", "ax[1, 3].bar(risk.index, risk.values); ax[1, 3].set_title(\"8 High-risk equipment by unit (Pareto)\")\n", "plt.tight_layout(); plt.show()\n", "print(\"\\nEDA ์ฐจํŠธ 8์ข… ์ƒ์„ฑ ์™„๋ฃŒ(๋ชฉํ‘œ 8์ข… ์ด์ƒ: ๋‹ฌ์„ฑ)\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 03 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ๊ฐ€์ƒ ์žฅ๋น„ 900๋Œ€(๊ณ ์žฅ ์ด๋ ฅ 288๋Œ€, ์ƒ์กด 612๋Œ€). Baseline RUL(์ผ) = (๊ทœ์ • ์ฃผ๊ธฐ 8000h - ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„) / ์ผ ๊ฐ€๋™๋ฅ \n", " ์ƒ์กด ์žฅ๋น„ ํ‰๊ฐ€: MAE 366์ผ, RMSE 455์ผ, MAPE(์ฐธ RUL 90์ผ ์ด์ƒ) 51.8%, R2 0.740, ํ‰๊ท  ํŽธํ–ฅ -48์ผ\n", " Baseline์ด ์‹ค์ œ๋ณด๋‹ค 30์ผ ๋„˜๊ฒŒ ๊ธธ๊ฒŒ ๋ณธ ์žฅ๋น„ 270๋Œ€(44%) - ๊ณ ์žฅ ์ „์— ์ •๋น„ ์‹œ์ ์„ ๋†“์น  ์œ„ํ—˜\n", " ๊ณ ์žฅ ์ด๋ ฅ 288๋Œ€ ์ค‘ ๊ทœ์ • ์ฃผ๊ธฐ(8000h) ์ด์ „์— ๊ณ ์žฅ ๋‚œ ์žฅ๋น„ 225๋Œ€(78%) -> ๊ณ ์ • ์ฃผ๊ธฐ๋Š” ๊ฐ€ํ˜น ํ™˜๊ฒฝ ์žฅ๋น„๋ฅผ ์ง€ํ‚ค์ง€ ๋ชปํ•œ๋‹ค\n", "\n", "[2] ์กฐ๊ฑด๋ณ„ Baseline ์˜ค์ฐจ(์–‘์ˆ˜ = ๊ณผ๋Œ€ ์˜ˆ์ธก)\n", " ์กฐ๊ฑด ๋Œ€์ˆ˜ MAE(์ผ) ํ‰๊ท  ํŽธํ–ฅ(์ผ)\n", " ์ผ๋ฐ˜ ํ™˜๊ฒฝ 261 431.0 -385.0\n", " ํ—˜์ง€ 102 434.0 426.0\n", " ์—ผ๋ถ„ 146 286.0 221.0\n", " ํ˜นํ•œ 103 246.0 -45.0\n", " ๋น„๊ณ„ํš ์ •๋น„์œจ ์ƒ์œ„ 25% 153 380.0 51.0\n", " ์ผ ๊ฐ€๋™๋ฅ  4h ์ด์ƒ 71 236.0 -88.0\n", "์ ๊ฒ€ ๋งˆ๋ชจ 0.8 ์ด์ƒ(๋…ธํ›„) 140 352.0 -19.0\n", " -> ํ—˜์ง€ยท์—ผ๋ถ„ ์žฅ๋น„๋Š” ์ˆ˜๋ช…์„ ๊ธธ๊ฒŒ(์œ„ํ—˜), ์ผ๋ฐ˜ ํ™˜๊ฒฝ ์žฅ๋น„๋Š” ์งง๊ฒŒ(๊ณผ์ž‰ ์ •๋น„) ๋ณด์•„ ํ™˜๊ฒฝ๋ณ„ ํŽธํ–ฅ ๋ฐฉํ–ฅ์ด ๋ฐ˜๋Œ€๋‹ค. ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ ๋ฏธ๋ฐ˜์˜์ด ํ•œ๊ณ„๋‹ค.\n", "\n", "[3] Baseline ์˜ค์ฐจ(๋ถ€ํ˜ธ ์žˆ์Œ: ์–‘์ˆ˜ = ๊ณผ๋Œ€ ์˜ˆ์ธก)์™€ ์œ ์˜ํ•œ ์ƒ๊ด€(|r|>=0.2, p<0.05) ํ”ผ์ฒ˜ 7๊ฐœ (๋ชฉํ‘œ 5๊ฐœ ์ด์ƒ: ๋‹ฌ์„ฑ)\n", " ํ”ผ์ฒ˜ ์Šคํ”ผ์–ด๋งŒ ์ƒ๊ด€ p-value\n", "insp_eta_h -0.8807 0.0\n", " k_rough 0.4811 0.0\n", " k_salt 0.3550 0.0\n", " lin_rul_d 0.2540 0.0\n", " h_since -0.2498 0.0\n", " hs_frac -0.2498 0.0\n", " km_per_h -0.2257 0.0\n", " (lin_rul_dยทh_sinceยทhs_frac๋Š” Baseline ์‚ฐ์‹๊ณผ ์ง์ ‘ ๊ฒน์น˜๋ฏ€๋กœ ํ•ด์„์—์„œ๋Š” ํ™˜๊ฒฝ ๋”๋ฏธยท์ ๊ฒ€ ๋งˆ๋ชจยท๊ธฐ๋™ ๊ฐ•๋„ ๊ฐ™์€ ๋…๋ฆฝ ์š”์ธ์„ ๋ณธ๋‹ค)\n", " ๊ฐ€์„ค๊ฒ€์ •: ํ—˜์ง€ยท์—ผ๋ถ„ vs ์ผ๋ฐ˜ ํ™˜๊ฒฝ Baseline ํŽธํ–ฅ ํ‰๊ท  +305์ผ vs -385์ผ(Mann-Whitney p=1.9e-65), ํ™˜๊ฒฝ๋ณ„ ์ ๊ฒ€ ํ™˜์‚ฐ ์ด ์ˆ˜๋ช… ์ฐจ์ด Kruskal p=1.1e-57\n", "\n", "[4] ํŒŒ๋ ˆํ† : ์ฐธ RUL 180์ผ ๋ฏธ๋งŒ ๊ณ ์œ„ํ—˜ ์žฅ๋น„ 46๋Œ€, ์ƒ์œ„ 5๊ฐœ ๋ถ€๋Œ€๊ฐ€ 80% ์ฐจ์ง€ -> U5 12๋Œ€, U3 9๋Œ€, U4 8๋Œ€, U6 7๋Œ€, U2 5๋Œ€, U1 5๋Œ€\n", " ํ•ต์‹ฌ ๋ฐœ๊ฒฌ: ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜์™€ ์ •๋น„ ์ด๋ ฅยท์ ๊ฒ€ ๋งˆ๋ชจ๊ฐ€ Baseline์ด ๋†“์น˜๋Š” ์š”์ธ์ด๋ฉฐ, ์ด๋Š” M4 ํ”ผ์ฒ˜ ์„ค๊ณ„(ํ™˜๊ฒฝ ๋”๋ฏธ, ๋น„๊ณ„ํš ์ •๋น„์œจ, ์ ๊ฒ€ ๋งˆ๋ชจ)์˜ ๊ทผ๊ฑฐ๊ฐ€ ๋œ๋‹ค.\n", "\n", "EDA ์ฐจํŠธ 8์ข… ์ƒ์„ฑ ์™„๋ฃŒ(๋ชฉํ‘œ 8์ข… ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 04. ์ƒ์กด ๋ถ„์„(Weibull/Cox) ๊ธฐ๋ฐ˜ ์žฅ๋น„/๋ถ€ํ’ˆ ๊ณ ์žฅ ํ™•๋ฅ  ๊ณก์„  ๋ฐ ๊ธฐ๋Œ€์ˆ˜๋ช… ์˜ˆ์ธก ๋ชจ๋ธ ๊ณ ๋„ํ™”\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ƒ์กด ๋ถ„์„ ๊ธฐ๋ฐ˜ ์žฅ๋น„/๋ถ€ํ’ˆ ๊ณ ์žฅ ํ™•๋ฅ  ๋ฐ ๊ธฐ๋Œ€์ˆ˜๋ช… ์˜ˆ์ธก ๋ชจ๋ธ์„ ๊ณ ๋„ํ™”ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์™€์ด๋ธ” AFT์™€ Cox ๋น„๋ก€์œ„ํ—˜ ๋ชจํ˜•์œผ๋กœ ๊ณ ์žฅ ํ™•๋ฅ  ๊ณก์„ ๊ณผ ๊ธฐ๋Œ€์ˆ˜๋ช…์„ ์˜ˆ์ธกํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ƒ์กด ๋ถ„์„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์“ฐ์ง€ ์•Š๊ณ  scipy๋กœ ์™€์ด๋ธ” AFT์™€ Cox ๋ชจํ˜•์„ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๊ณ  ๋™์ž‘์„ ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/features/sup02_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": [ "\"\"\"์ƒ์กด ๋ถ„์„(์™€์ด๋ธ” AFT ์ตœ๋Œ€์šฐ๋„ยทCox ๋น„๋ก€์œ„ํ—˜์„ scipy๋กœ ์ง์ ‘ ๊ตฌํ˜„)์œผ๋กœ ํ™˜๊ฒฝ๋ณ„ ๊ณ ์žฅ ํ™•๋ฅ  ๊ณก์„ ๊ณผ ๊ธฐ๋Œ€์ˆ˜๋ช…์„ ์ถ”์ •ํ•˜๊ณ , ํ™˜๊ฒฝ ๊ฐ€์† ๊ณ„์ˆ˜๋ฅผ ์ˆจ์€ ์ •๋‹ต๊ณผ ๋น„๊ตํ•˜๋ฉฐ C-indexยท๊ธฐ๋Œ€์ˆ˜๋ช… MAPEยท1๋…„ ๋‚ด ๊ณ ์žฅ ํ™•๋ฅ  ๋ณด์ •์œผ๋กœ ๋ชจ๋ธ์„ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์žฅ๋น„ ๋ณด์œ  ํ˜„ํ™ฉยท๊ณ ์žฅ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ˆ˜์น˜ ์—†์Œ)\n", "# ๊ฐ€์ƒ ๋ถ€๋Œ€ 6๊ฐœ(U1~U6)์˜ ์žฅ๋น„๊ฐ€ ์šด์šฉ ํ™˜๊ฒฝ(normal/rough/salt/cold)์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์†๋„๋กœ ๋…ธํ›„ํ™”ํ•œ๋‹ค. ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„(h_since)์ด\n", "# ์žฅ๋น„๋งˆ๋‹ค ์ˆจ์€ ์ˆ˜๋ช…(t_fail)์„ ๋„˜์œผ๋ฉด ๊ณ ์žฅ(event=1) ์ด๋ ฅ, ๋„˜์ง€ ์•Š์•˜์œผ๋ฉด ํ˜„์žฌ๊นŒ์ง€ ์ƒ์กด(event=0, ๊ด€์ธก ์ค‘๋„์ ˆ๋‹จ)์ด๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ENVS = [\"normal\", \"rough\", \"salt\", \"cold\"]\n", "ENV_W = {\"normal\": 1.00, \"rough\": 1.35, \"salt\": 1.25, \"cold\": 1.12} # ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜(์ˆจ์€ ์ •๋‹ต, ๊ฐ€์ •๊ฐ’): ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ์งง๋‹ค\n", "ENV_KO = {\"normal\": \"์ผ๋ฐ˜\", \"rough\": \"ํ—˜์ง€\", \"salt\": \"์—ผ๋ถ„\", \"cold\": \"ํ˜นํ•œ\"}\n", "UNITS = [\"U1\", \"U2\", \"U3\", \"U4\", \"U5\", \"U6\"]\n", "UNIT_ENV = {\"U1\": [.7, .1, .1, .1], \"U2\": [.2, .6, .1, .1], \"U3\": [.2, .1, .6, .1], \"U4\": [.3, .1, .1, .5], \"U5\": [.5, .3, .1, .1], \"U6\": [.4, .1, .4, .1]}\n", "ETYPES = [\"truck\", \"generator\", \"trailer\", \"radio\"]\n", "CYCLE = 8000.0 # ๊ทœ์ • ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ(์šด์šฉ์‹œ๊ฐ„ h, ๊ฐ€์ •๊ฐ’)\n", "KSHAPE = 12.0 # ์ˆ˜๋ช… ๋ถ„ํฌ ์™€์ด๋ธ” ํ˜•์ƒ(๋งˆ๋ชจํ˜• ๊ณ ์žฅ, ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ๋ชจ์—ฌ ์žˆ๋‹ค)\n", "INSP_NOISE = 0.025 # ์ •๋น„ ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ ์ธก์ • ์˜ค์ฐจ(๊ฐ€์ •๊ฐ’)\n", "\n", "\n", "def make_fleet(n=900, seed=0, env_w=None, shift=1.0):\n", " \"\"\"์žฅ๋น„ n๋Œ€. env_w๋กœ ์ˆจ์€ ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜๋ฅผ ๋ฐ”๊ฟ” ์šด์˜ ๋ถ„ํฌ ๋ณ€ํ™”๋ฅผ ๋ชจ์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. shift>1์ด๋ฉด ๊ฐ€๋™๋ฅ ์ด ๋†’์•„์ง„๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " w = env_w or ENV_W\n", " unit = rng.choice(UNITS, n)\n", " env = np.array([rng.choice(ENVS, p=UNIT_ENV[u]) for u in unit])\n", " et = rng.choice(ETYPES, n, p=[.4, .2, .2, .2])\n", " rate = rng.lognormal(np.log(3.0 * shift), 0.25, n) # ์ผ ํ‰๊ท  ์šด์šฉ์‹œ๊ฐ„(h/์ผ)\n", " h_since = rng.uniform(300, 11500, n) # ๋งˆ์ง€๋ง‰ ์ฐฝ์ •๋น„ ์ดํ›„ ์šด์šฉ์‹œ๊ฐ„\n", " n_ovh = rng.integers(0, 3, n)\n", " hours = h_since + n_ovh * rng.uniform(5000, 8000, n) # ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„\n", " km = hours * rng.normal(17, 3, n) * np.where(env == \"rough\", 0.8, 1.0) # ๋ˆ„์  ๊ธฐ๋™๊ฑฐ๋ฆฌ(km)\n", " age_y = np.clip(hours / (rate * 365) * rng.uniform(0.9, 1.15, n), 0.5, 25)\n", " unpl = rng.poisson(0.35 * h_since / 1000 * np.array([w[e] for e in env]) / 1.3) # ๋น„๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜(ํ™˜๊ฒฝ์ด ๊ฐ€ํ˜นํ• ์ˆ˜๋ก ๋งŽ๋‹ค)\n", " n_maint = rng.poisson(0.6 * h_since / 1000 + 1) # ๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜\n", " scale = 9600 / np.array([w[e] for e in env]) * 0.985 ** unpl * np.exp(rng.normal(0, 0.04, n))\n", " t_fail = scale * rng.weibull(KSHAPE, n) # ์ฐฝ์ •๋น„ ํ›„ ๊ณ ์žฅ๊นŒ์ง€ ์šด์šฉ์‹œ๊ฐ„(์ˆจ์€ ์ •๋‹ต)\n", " event = (t_fail <= h_since).astype(int)\n", " dur = np.where(event == 1, t_fail, h_since)\n", " df = pd.DataFrame({\"eq_id\": [f\"EQ{seed:02d}{i:04d}\" for i in range(n)], \"unit\": unit, \"env\": env, \"etype\": et, \"rate\": rate, \"hours\": hours, \"km\": km, \"age_y\": age_y, \"n_ovh\": n_ovh,\n", " \"h_since\": h_since, \"unpl\": unpl, \"n_maint\": n_maint, \"t_fail\": t_fail, \"event\": event, \"dur\": dur})\n", " df[\"insp\"] = np.clip(dur / t_fail + rng.normal(0, INSP_NOISE, n), 0, 1.3) # ๋งˆ์ง€๋ง‰ ์ •๋น„ ์ ๊ฒ€์—์„œ ์ธก์ •ํ•œ ๋งˆ๋ชจ ๋น„์œจ(์†Œ๋ชจ ์ˆ˜๋ช…/์ด ์ˆ˜๋ช…, ์˜ค์ฐจ ํฌํ•จ)\n", " df[\"rul_h\"] = np.where(event == 0, t_fail - h_since, np.nan) # ์ƒ์กด ์žฅ๋น„์˜ ๋‚จ์€ ์šด์šฉ์‹œ๊ฐ„(์ •๋‹ต)\n", " df[\"rul_d\"] = df[\"rul_h\"] / df[\"rate\"] # ๋‚จ์€ ์ผ์ˆ˜(์ •๋‹ต)\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " y, p = np.asarray(y, float), np.asarray(p, float)\n", " return float(np.mean(np.abs(y - p) / np.abs(y)) * 100)\n", "\n", "\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "\n", "\n", "def rmse(y, p):\n", " return float(np.sqrt(np.mean((np.asarray(y, float) - np.asarray(p, float)) ** 2)))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธํ›„ํ™” ํŒŒ์ƒ ํ”ผ์ฒ˜์™€ ํ•™์Šต ํ‘œ๋ณธ ๋งŒ๋“ค๊ธฐ. ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ W0๋Š” ๋„์ž… ์‹œ์ ์˜ ์ „๋ฌธ๊ฐ€ ์ถ”์ •๊ฐ’์ด๋ฉฐ(์‹ค์ œ ๊ฐ€์ค‘์น˜์™€ ๋‹ค๋ฆ„) M9์—์„œ ์žฌ๋ณด์ •ํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "W0 = {\"normal\": 1.00, \"rough\": 1.15, \"salt\": 1.10, \"cold\": 1.05}\n", "FEATS = [\"h_since\", \"hours\", \"km\", \"age_y\", \"rate\", \"n_maint\", \"unpl\", \"unpl_per_kh\", \"km_per_h\", \"n_ovh\", \"k_rough\", \"k_salt\", \"k_cold\", \"k_truck\", \"k_gen\", \"k_trailer\",\n", " \"aging_idx\", \"lin_rul_d\", \"hs_frac\", \"insp\", \"insp_eta_h\"]\n", "\n", "\n", "def add_features(df, w=None):\n", " d = df.copy()\n", " w = w or W0\n", " d[\"unpl_per_kh\"] = d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)\n", " d[\"km_per_h\"] = d[\"km\"] / d[\"hours\"]\n", " for e in (\"rough\", \"salt\", \"cold\"):\n", " d[\"k_\" + e] = (d[\"env\"] == e).astype(int)\n", " d[\"k_truck\"], d[\"k_gen\"], d[\"k_trailer\"] = (d[\"etype\"] == \"truck\").astype(int), (d[\"etype\"] == \"generator\").astype(int), (d[\"etype\"] == \"trailer\").astype(int)\n", " d[\"aging_idx\"] = d[\"h_since\"] * d[\"env\"].map(w) / CYCLE # ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜(1์ด๋ฉด ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ์— ๋„๋‹ฌ)\n", " d[\"lin_rul_d\"] = (CYCLE - d[\"h_since\"]) / d[\"rate\"] # ์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ์ž”์—ฌ ์ผ์ˆ˜(Baseline)\n", " d[\"hs_frac\"] = d[\"h_since\"] / CYCLE\n", " d[\"insp_eta_h\"] = d[\"h_since\"] / np.maximum(d[\"insp\"], 0.05) # ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ๋กœ ํ™˜์‚ฐํ•œ ์ด ์ˆ˜๋ช… ์ถ”์ •(h)\n", " return d\n", "\n", "\n", "def snapshots(df, k=4, seed=0):\n", " \"\"\"๊ณ ์žฅ ์ด๋ ฅ ์žฅ๋น„์˜ ๊ณผ๊ฑฐ ์‹œ์  ํ‘œ๋ณธ: ๊ณ ์žฅ ์ „ ์ž„์˜ ์‹œ์ ์˜ ์ ๊ฒ€๊ฐ’ยท์šด์šฉ์‹œ๊ฐ„๊ณผ ๊ทธ๋•Œ์˜ ๋‚จ์€ ์ผ์ˆ˜. (์ƒ์กด ์žฅ๋น„์˜ ๋ฏธ๋ž˜๋Š” ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๊ณ ์žฅ ์ด๋ ฅ๋งŒ ์“ด๋‹ค)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " f = df[df[\"event\"] == 1]\n", " out = []\n", " for _ in range(k):\n", " s = f.copy()\n", " h = s[\"t_fail\"] * rng.uniform(0.2, 0.98, len(s))\n", " ratio = h / s[\"h_since\"]\n", " s[\"hours\"] = s[\"hours\"] - (s[\"h_since\"] - h)\n", " s[\"km\"] = s[\"km\"] * ratio.clip(upper=1)\n", " s[\"unpl\"] = np.floor(s[\"unpl\"] * ratio)\n", " s[\"n_maint\"] = np.floor(s[\"n_maint\"] * ratio)\n", " s[\"insp\"] = np.clip(h / s[\"t_fail\"] + rng.normal(0, INSP_NOISE, len(s)), 0, 1.3)\n", " s[\"h_since\"] = h\n", " s[\"rul_d\"] = (s[\"t_fail\"] - h) / s[\"rate\"]\n", " s[\"event\"] = 0\n", " out.append(s)\n", " return pd.concat(out, ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ƒ์กด ๋ถ„์„ ๋„๊ตฌ(scipy๋งŒ ์‚ฌ์šฉ): ์™€์ด๋ธ” AFT(๊ฐ€์† ์ˆ˜๋ช…) ๋ชจํ˜•๊ณผ Cox ๋น„๋ก€์œ„ํ—˜ ๋ชจํ˜•\n", "# ---------------------------------------------------------------------------\n", "from scipy.optimize import minimize\n", "\n", "\n", "def aft_x(d):\n", " return np.c_[np.ones(len(d)), d[\"env\"].eq(\"rough\"), d[\"env\"].eq(\"salt\"), d[\"env\"].eq(\"cold\"), d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)].astype(float)\n", "\n", "\n", "AFT_COLS = [\"์ ˆํŽธ\", \"ํ—˜์ง€\", \"์—ผ๋ถ„\", \"ํ˜นํ•œ\", \"๋น„๊ณ„ํš ์ •๋น„์œจ\"]\n", "\n", "\n", "def fit_aft(d):\n", " \"\"\"์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„ dur(๊ณ ์žฅ์ด๋ฉด event=1, ์ƒ์กด์ด๋ฉด ์ค‘๋„์ ˆ๋‹จ)์— ๋Œ€ํ•œ ์™€์ด๋ธ” AFT ์ตœ๋Œ€์šฐ๋„ ์ถ”์ •. ๋ฐ˜ํ™˜: ๊ณ„์ˆ˜(๋งˆ์ง€๋ง‰์€ log ํ˜•์ƒ).\"\"\"\n", " x, t, e = aft_x(d), d[\"dur\"].values, d[\"event\"].values\n", "\n", " def nll(p):\n", " lam, k = np.exp(x @ p[:-1]), np.exp(p[-1])\n", " z = (t / lam) ** k\n", " return -(e * (np.log(k) - np.log(lam) + (k - 1) * (np.log(t) - np.log(lam))) - z).sum()\n", "\n", " r = minimize(nll, np.r_[np.log(8000), np.zeros(x.shape[1] - 1), np.log(5)], method=\"BFGS\")\n", " return r.x\n", "\n", "\n", "def aft_pred(p, d, q=0.5, h=None):\n", " \"\"\"์ด๋ฏธ h(์šด์šฉ์‹œ๊ฐ„)๊นŒ์ง€ ์ƒ์กดํ–ˆ๋‹ค๋Š” ์กฐ๊ฑด์—์„œ ๋ถ„์œ„์ˆ˜ q ์ƒ์กด ์‹œ์ (์šด์šฉ์‹œ๊ฐ„ h ๊ธฐ์ค€ ์ด ์ˆ˜๋ช…)์„ ๋Œ๋ ค์ค€๋‹ค. q=0.5๊ฐ€ ์กฐ๊ฑด๋ถ€ ์ค‘์•™๊ฐ’.\"\"\"\n", " lam, k = np.exp(aft_x(d) @ p[:-1]), np.exp(p[-1])\n", " h = d[\"h_since\"].values if h is None else h\n", " return lam * ((h / lam) ** k - np.log(q)) ** (1 / k)\n", "\n", "\n", "def aft_fail_prob(p, d, h0, h1):\n", " \"\"\"h0๊นŒ์ง€ ์ƒ์กดํ•œ ์žฅ๋น„๊ฐ€ h0~h1 ์‚ฌ์ด์— ๊ณ ์žฅ ๋‚  ์กฐ๊ฑด๋ถ€ ํ™•๋ฅ .\"\"\"\n", " lam, k = np.exp(aft_x(d) @ p[:-1]), np.exp(p[-1])\n", " s0, s1 = np.exp(-(h0 / lam) ** k), np.exp(-(h1 / lam) ** k)\n", " return 1 - s1 / s0\n", "\n", "\n", "def fit_cox(d, iters=25):\n", " \"\"\"Cox ๋น„๋ก€์œ„ํ—˜(Breslow ๊ทผ์‚ฌ) ๋‰ดํ„ด-๋žฉ์Šจ. ๊ณต๋ณ€๋Ÿ‰: ํ—˜์ง€ยท์—ผ๋ถ„ยทํ˜นํ•œยท๋น„๊ณ„ํš ์ •๋น„์œจ. ๋ฐ˜ํ™˜: ๊ณ„์ˆ˜.\"\"\"\n", " x = aft_x(d)[:, 1:]\n", " o = np.argsort(-d[\"dur\"].values)\n", " x, e = x[o], d[\"event\"].values[o]\n", " b = np.zeros(x.shape[1])\n", " for _ in range(iters):\n", " r = np.exp(x @ b)\n", " S0 = np.cumsum(r)\n", " S1 = np.cumsum(r[:, None] * x, axis=0)\n", " S2 = np.cumsum(r[:, None, None] * x[:, :, None] * x[:, None, :], axis=0)\n", " m1 = S1 / S0[:, None]\n", " g = ((x - m1) * e[:, None]).sum(axis=0)\n", " H = -sum(e[i] * (S2[i] / S0[i] - np.outer(m1[i], m1[i])) for i in np.where(e == 1)[0])\n", " step = np.linalg.solve(H - 1e-6 * np.eye(len(b)), g)\n", " b = b - step\n", " if np.abs(step).max() < 1e-7:\n", " break\n", " return b\n", "\n", "\n", "def cindex(time, event, risk):\n", " \"\"\"์ผ์น˜๋„ C-index: ์œ„ํ—˜ ์ ์ˆ˜๊ฐ€ ๋†’์€ ์ชฝ์ด ๋จผ์ € ๊ณ ์žฅ ๋‚ฌ๋Š”๊ฐ€(๊ด€์ธก ๊ฐ€๋Šฅํ•œ ์Œ๋งŒ).\"\"\"\n", " t, e, r = np.asarray(time), np.asarray(event), np.asarray(risk)\n", " num = den = 0.0\n", " for i in np.where(e == 1)[0]:\n", " m = t > t[i]\n", " den += m.sum()\n", " num += (r[m] < r[i]).sum() + 0.5 * (r[m] == r[i]).sum()\n", " return num / max(den, 1)\n", "# ---------------------------------------------------------------------------\n", "\n", "tr = make_fleet(900, seed=1)\n", "te = make_fleet(600, seed=2)\n", "derived = [c for c in FEATS if c in (\"unpl_per_kh\", \"km_per_h\", \"k_rough\", \"k_salt\", \"k_cold\", \"k_truck\", \"k_gen\", \"k_trailer\", \"aging_idx\", \"lin_rul_d\", \"hs_frac\", \"insp_eta_h\")]\n", "print(f\"[1] ํŒŒ์ƒ ํ”ผ์ฒ˜ {len(derived)}๊ฐœ(ํ™˜๊ฒฝยท์žฅ๋น„ ๋”๋ฏธ, ๋น„๊ณ„ํš ์ •๋น„์œจ, ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜, ์ ๊ฒ€ ํ™˜์‚ฐ ์ˆ˜๋ช… ๋“ฑ) / ํ•™์Šต {len(tr)}๋Œ€(๊ณ ์žฅ {int(tr.event.sum())}๋Œ€, ์ค‘๋„์ ˆ๋‹จ {int((1 - tr.event).sum())}๋Œ€), ํ‰๊ฐ€ {len(te)}๋Œ€\")\n", "\n", "# ---------------- 1) ์™€์ด๋ธ” AFTยทCox ์ถ”์ • ----------------\n", "p = fit_aft(tr)\n", "b = fit_cox(tr)\n", "k = float(np.exp(p[-1]))\n", "acc = pd.DataFrame({\"AFT ์ˆ˜๋ช…๋น„ exp(b)\": np.exp(p[1:5]), \"์ˆจ์€ ์ •๋‹ต ์ˆ˜๋ช…๋น„\": [1 / ENV_W[\"rough\"], 1 / ENV_W[\"salt\"], 1 / ENV_W[\"cold\"], np.nan], \"Cox ์œ„ํ—˜๋น„\": np.exp(b)}, index=AFT_COLS[1:])\n", "print(f\"[2] ์™€์ด๋ธ” AFT ํ˜•์ƒ k={k:.2f}(1๋ณด๋‹ค ํฌ๋ฉด ๋งˆ๋ชจํ˜• ๊ณ ์žฅ), ๊ธฐ์ค€ ์ˆ˜๋ช…(์ผ๋ฐ˜ ํ™˜๊ฒฝ) {np.exp(p[0]):.0f}h\")\n", "print(acc.round(3).to_string())\n", "print(\" ํ™˜๊ฒฝ ๊ฐ€์†(์ˆ˜๋ช…๋น„ < 1)์ด ๊ฐ€ํ˜น ํ™˜๊ฒฝ์ผ์ˆ˜๋ก ํฌ๋ฉฐ, ์ˆจ์€ ์ •๋‹ต(1/๊ฐ€์ค‘์น˜)๊ณผ ๋น„์Šทํ•˜๊ฒŒ ๋ณต์›๋œ๋‹ค. ๋น„๊ณ„ํš ์ •๋น„์œจ์€ ์ˆ˜๋ช…์„ ์ค„์ด๋Š” ๋ฐฉํ–ฅ์ด๋‹ค.\")\n", "\n", "# ---------------- 2) ๊ณ ์žฅ ํ™•๋ฅ  ๊ณก์„  ----------------\n", "grid = np.array([5000, 6000, 7000, 8000, 9000, 10000])\n", "typ = pd.DataFrame({\"env\": ENVS, \"unpl\": 1.0, \"h_since\": 1000.0})\n", "typ[\"unpl\"] = 1.0 * (typ[\"h_since\"] / 1000 + 0.5)\n", "lam = np.exp(aft_x(typ) @ p[:-1])\n", "curves = pd.DataFrame({f\"{h}h\": 1 - np.exp(-(h / lam) ** k) for h in grid}, index=ENVS)\n", "b10 = lam * (-np.log(0.9)) ** (1 / k)\n", "print(\"\\n[3] ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„๋ณ„ ๋ˆ„์  ๊ณ ์žฅ ํ™•๋ฅ (๋น„๊ณ„ํš ์ •๋น„์œจ 1.0/1000h ์žฅ๋น„)\")\n", "print(curves.apply(lambda c: c.map(\"{:.0%}\".format)).to_string())\n", "print(\" B10 ์ˆ˜๋ช…(๊ณ ์žฅ 10% ์‹œ์ ): \" + \", \".join(f\"{e} {v:.0f}h\" for e, v in zip(ENVS, b10)) + f\" / ๊ทœ์ • ์ฃผ๊ธฐ {CYCLE:.0f}h ์‹œ์  ๊ณ ์žฅ ํ™•๋ฅ  ์ผ๋ฐ˜ {curves.loc['normal', '8000h']:.0%}, ํ—˜์ง€ {curves.loc['rough', '8000h']:.0%}\")\n", "\n", "# ---------------- 3) ํ‰๊ฐ€: ๊ธฐ๋Œ€์ˆ˜๋ช… MAPE, C-index ----------------\n", "zero = np.full(len(te), 1e-6)\n", "life = aft_pred(p, te, 0.5, h=zero)\n", "m1, m0 = mape(te.t_fail, life), mape(te.t_fail, np.full(len(te), CYCLE))\n", "x = aft_x(te)[:, 1:]\n", "ci_cox, ci_aft = cindex(te.dur, te.event, x @ b), cindex(te.dur, te.event, -life)\n", "ci_base = cindex(te.dur, te.event, np.zeros(len(te)))\n", "print(f\"\\n[4] ํ‰๊ฐ€(์ˆจ์€ ์ •๋‹ต t_fail์€ ๋ชจ์˜ ํ‰๊ฐ€์—๋งŒ ์‚ฌ์šฉ): ๊ธฐ๋Œ€์ˆ˜๋ช…(์ค‘์•™๊ฐ’) MAPE {m1:.1f}% (๋ชฉํ‘œ 25% ์ดํ•˜: {'๋‹ฌ์„ฑ' if m1 <= 25 else '๋ฏธ๋‹ฌ'}), ๊ทœ์ • ์ฃผ๊ธฐ {CYCLE:.0f}h๋ฅผ ๋ชจ๋‘์— ์ ์šฉํ•˜๋ฉด {m0:.1f}%\")\n", "print(f\" C-index: AFT {ci_aft:.3f}, Cox {ci_cox:.3f}, ํ™˜๊ฒฝ ๋ฌด์‹œ(๋ฌด์ฐจ๋ณ„) {ci_base:.3f} (0.5 = ๋ฌด์ž‘์œ„)\")\n", "by = pd.DataFrame({\"ํ™˜๊ฒฝ\": te.env, \"์ •๋‹ต ํ‰๊ท \": te.t_fail, \"AFT\": life, \"๊ทœ์ • ์ฃผ๊ธฐ\": CYCLE}).groupby(\"ํ™˜๊ฒฝ\").mean().round(0)\n", "print(\" ํ™˜๊ฒฝ๋ณ„ ํ‰๊ท  ์ˆ˜๋ช…(h):\\n\" + by.to_string())\n", "\n", "# ---------------- 4) 1๋…„ ๋‚ด ๊ณ ์žฅ ํ™•๋ฅ  ๋ณด์ •(์ƒ์กด ์žฅ๋น„) ----------------\n", "a = te[te.event == 0].copy()\n", "a[\"p1y\"] = aft_fail_prob(p, a, a[\"h_since\"].values, a[\"h_since\"].values + 365 * a[\"rate\"].values)\n", "a[\"real\"] = (a[\"rul_d\"] < 365).astype(int)\n", "a[\"bin\"] = pd.qcut(a[\"p1y\"], 5, labels=False, duplicates=\"drop\")\n", "cal = a.groupby(\"bin\").agg(์˜ˆ์ธกํ™•๋ฅ =(\"p1y\", \"mean\"), ์‹ค์ œ๋น„์œจ=(\"real\", \"mean\"), ๋Œ€์ˆ˜=(\"real\", \"size\"))\n", "print(\"\\n[5] 1๋…„ ๋‚ด ๊ณ ์žฅ ํ™•๋ฅ  ๋ณด์ •(์ƒ์กด ์žฅ๋น„, ํ™•๋ฅ  5๋ถ„์œ„)\\n\" + cal.round(2).to_string())\n", "brier = float(((a[\"p1y\"] - a[\"real\"]) ** 2).mean())\n", "print(f\" Brier ์ ์ˆ˜ {brier:.3f}(๋ฌด์ •๋ณด ๊ธฐ์ค€ {float(a['real'].mean() * (1 - a['real'].mean())):.3f}). ์ ๊ฒ€ ๋งˆ๋ชจ๋ฅผ ์“ฐ์ง€ ์•Š๋Š” ์ƒ์กด ๋ชจํ˜•์€ ํ™•๋ฅ ์˜ ์ˆœ์œ„๋Š” ๋งž์ง€๋งŒ ๊ฐœ๋ณ„ ์žฅ๋น„์˜ ์‹œ์ ์€ ๊ฑฐ์น ๋‹ค -> M5์—์„œ ์ ๊ฒ€ ๋งˆ๋ชจ ํ”ผ์ฒ˜๋กœ ๋ณด๊ฐ•ํ•œ๋‹ค.\")\n", "\n", "# ---------------- 5) ๋ณ€์ˆ˜ ์ œ๊ฑฐ ----------------\n", "def ablate(cols):\n", " keep = [0] + cols\n", " sub_x = aft_x(tr)[:, keep]; t, e = tr[\"dur\"].values, tr[\"event\"].values\n", " from scipy.optimize import minimize\n", "\n", " def nll(q):\n", " lm, kk = np.exp(sub_x @ q[:-1]), np.exp(q[-1]); z = (t / lm) ** kk\n", " return -(e * (np.log(kk) - np.log(lm) + (kk - 1) * (np.log(t) - np.log(lm))) - z).sum()\n", "\n", " q = minimize(nll, np.r_[np.log(8000), np.zeros(len(keep) - 1), np.log(5)], method=\"BFGS\").x\n", " lm, kk = np.exp(aft_x(te)[:, keep] @ q[:-1]), np.exp(q[-1])\n", " med = lm * np.log(2) ** (1 / kk)\n", " return mape(te.t_fail, med), cindex(te.dur, te.event, -med)\n", "\n", "\n", "rows = [(\"์ „์ฒด(ํ™˜๊ฒฝ+๋น„๊ณ„ํš ์ •๋น„์œจ)\", *ablate([1, 2, 3, 4])), (\"ํ™˜๊ฒฝ ์ œ๊ฑฐ\", *ablate([4])), (\"๋น„๊ณ„ํš ์ •๋น„์œจ ์ œ๊ฑฐ\", *ablate([1, 2, 3])), (\"๋ชจ๋‘ ์ œ๊ฑฐ(์ ˆํŽธ๋งŒ)\", *ablate([]))]\n", "print(\"\\n[6] ๋ณ€์ˆ˜ ์ œ๊ฑฐ ์‹คํ—˜\\n\" + pd.DataFrame(rows, columns=[\"๋ชจํ˜•\", \"๊ธฐ๋Œ€์ˆ˜๋ช… MAPE(%)\", \"C-index\"]).round(3).to_string(index=False))\n", "\n", "fig, ax = plt.subplots(1, 3, figsize=(15, 4.3))\n", "hh = np.linspace(2000, 11000, 100)\n", "for e, l in zip(ENVS, lam):\n", " ax[0].plot(hh, 1 - np.exp(-(hh / l) ** k), label=e)\n", "ax[0].axvline(CYCLE, c=\"k\", ls=\"--\"); ax[0].legend(); ax[0].set_title(\"Failure probability by environment (h since overhaul)\")\n", "ax[1].plot(cal[\"์˜ˆ์ธกํ™•๋ฅ \"], cal[\"์‹ค์ œ๋น„์œจ\"], \"o-\"); ax[1].plot([0, 1], [0, 1], \"k--\"); ax[1].set_title(\"1-year failure probability calibration\")\n", "ax[2].bar([\"rough\", \"salt\", \"cold\", \"unplanned rate\"], np.exp(p[1:5])); ax[2].axhline(1, c=\"k\", ls=\"--\"); ax[2].set_title(\"AFT life ratio vs normal (exp(b))\"); ax[2].tick_params(axis=\"x\", rotation=20)\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 04 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ํŒŒ์ƒ ํ”ผ์ฒ˜ 12๊ฐœ(ํ™˜๊ฒฝยท์žฅ๋น„ ๋”๋ฏธ, ๋น„๊ณ„ํš ์ •๋น„์œจ, ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜, ์ ๊ฒ€ ํ™˜์‚ฐ ์ˆ˜๋ช… ๋“ฑ) / ํ•™์Šต 900๋Œ€(๊ณ ์žฅ 288๋Œ€, ์ค‘๋„์ ˆ๋‹จ 612๋Œ€), ํ‰๊ฐ€ 600๋Œ€\n", "[2] ์™€์ด๋ธ” AFT ํ˜•์ƒ k=10.83(1๋ณด๋‹ค ํฌ๋ฉด ๋งˆ๋ชจํ˜• ๊ณ ์žฅ), ๊ธฐ์ค€ ์ˆ˜๋ช…(์ผ๋ฐ˜ ํ™˜๊ฒฝ) 9602h\n", " AFT ์ˆ˜๋ช…๋น„ exp(b) ์ˆจ์€ ์ •๋‹ต ์ˆ˜๋ช…๋น„ Cox ์œ„ํ—˜๋น„\n", "ํ—˜์ง€ 0.752 0.741 20.984\n", "์—ผ๋ถ„ 0.802 0.800 10.983\n", "ํ˜นํ•œ 0.878 0.893 4.253\n", "๋น„๊ณ„ํš ์ •๋น„์œจ 0.889 NaN 3.418\n", " ํ™˜๊ฒฝ ๊ฐ€์†(์ˆ˜๋ช…๋น„ < 1)์ด ๊ฐ€ํ˜น ํ™˜๊ฒฝ์ผ์ˆ˜๋ก ํฌ๋ฉฐ, ์ˆจ์€ ์ •๋‹ต(1/๊ฐ€์ค‘์น˜)๊ณผ ๋น„์Šทํ•˜๊ฒŒ ๋ณต์›๋œ๋‹ค. ๋น„๊ณ„ํš ์ •๋น„์œจ์€ ์ˆ˜๋ช…์„ ์ค„์ด๋Š” ๋ฐฉํ–ฅ์ด๋‹ค.\n", "\n", "[3] ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„๋ณ„ ๋ˆ„์  ๊ณ ์žฅ ํ™•๋ฅ (๋น„๊ณ„ํš ์ •๋น„์œจ 1.0/1000h ์žฅ๋น„)\n", " 5000h 6000h 7000h 8000h 9000h 10000h\n", "normal 0% 2% 11% 39% 83% 100%\n", "rough 6% 38% 92% 100% 100% 100%\n", "salt 3% 21% 72% 100% 100% 100%\n", "cold 1% 9% 38% 87% 100% 100%\n", " B10 ์ˆ˜๋ช…(๊ณ ์žฅ 10% ์‹œ์ ): normal 6936h, rough 5218h, salt 5560h, cold 6089h / ๊ทœ์ • ์ฃผ๊ธฐ 8000h ์‹œ์  ๊ณ ์žฅ ํ™•๋ฅ  ์ผ๋ฐ˜ 39%, ํ—˜์ง€ 100%\n", "\n", "[4] ํ‰๊ฐ€(์ˆจ์€ ์ •๋‹ต t_fail์€ ๋ชจ์˜ ํ‰๊ฐ€์—๋งŒ ์‚ฌ์šฉ): ๊ธฐ๋Œ€์ˆ˜๋ช…(์ค‘์•™๊ฐ’) MAPE 10.2% (๋ชฉํ‘œ 25% ์ดํ•˜: ๋‹ฌ์„ฑ), ๊ทœ์ • ์ฃผ๊ธฐ 8000h๋ฅผ ๋ชจ๋‘์— ์ ์šฉํ•˜๋ฉด 15.2%\n", " C-index: AFT 0.767, Cox 0.767, ํ™˜๊ฒฝ ๋ฌด์‹œ(๋ฌด์ฐจ๋ณ„) 0.500 (0.5 = ๋ฌด์ž‘์œ„)\n", " ํ™˜๊ฒฝ๋ณ„ ํ‰๊ท  ์ˆ˜๋ช…(h):\n", " ์ •๋‹ต ํ‰๊ท  AFT ๊ทœ์ • ์ฃผ๊ธฐ\n", "ํ™˜๊ฒฝ \n", "cold 7969.0 7890.0 8000.0\n", "normal 8992.0 9046.0 8000.0\n", "rough 6563.0 6700.0 8000.0\n", "salt 7231.0 7167.0 8000.0\n", "\n", "[5] 1๋…„ ๋‚ด ๊ณ ์žฅ ํ™•๋ฅ  ๋ณด์ •(์ƒ์กด ์žฅ๋น„, ํ™•๋ฅ  5๋ถ„์œ„)\n", " ์˜ˆ์ธกํ™•๋ฅ  ์‹ค์ œ๋น„์œจ ๋Œ€์ˆ˜\n", "bin \n", "0 0.00 0.00 78\n", "1 0.00 0.00 78\n", "2 0.01 0.00 78\n", "3 0.09 0.05 78\n", "4 0.64 0.59 78\n", " Brier ์ ์ˆ˜ 0.045(๋ฌด์ •๋ณด ๊ธฐ์ค€ 0.112). ์ ๊ฒ€ ๋งˆ๋ชจ๋ฅผ ์“ฐ์ง€ ์•Š๋Š” ์ƒ์กด ๋ชจํ˜•์€ ํ™•๋ฅ ์˜ ์ˆœ์œ„๋Š” ๋งž์ง€๋งŒ ๊ฐœ๋ณ„ ์žฅ๋น„์˜ ์‹œ์ ์€ ๊ฑฐ์น ๋‹ค -> M5์—์„œ ์ ๊ฒ€ ๋งˆ๋ชจ ํ”ผ์ฒ˜๋กœ ๋ณด๊ฐ•ํ•œ๋‹ค.\n", "\n", "[6] ๋ณ€์ˆ˜ ์ œ๊ฑฐ ์‹คํ—˜\n", " ๋ชจํ˜• ๊ธฐ๋Œ€์ˆ˜๋ช… MAPE(%) C-index\n", "์ „์ฒด(ํ™˜๊ฒฝ+๋น„๊ณ„ํš ์ •๋น„์œจ) 10.152 0.767\n", "... (์ดํ•˜ ์ƒ๋žต)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 05. ์˜ˆ์‚ฐ ๋ฐ ์ •๋น„์ฐฝ ๋Šฅ๋ ฅ ์—ฐ๊ณ„ ์ตœ์  ๊ณ„ํš์ •๋น„(Overhaul) ์ผ์ • ์ถ”์ฒœ ์—”์ง„ ๊ฐœ๋ฐœ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ •๋น„์ฐฝ ๋Šฅ๋ ฅ ์—ฐ๊ณ„ ์ตœ์  ๊ณ„ํš์ •๋น„(Overhaul) ์ผ์ • ์ถ”์ฒœ ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์˜ˆ์‚ฐ๊ณผ ์ •๋น„์ฐฝ ๋Šฅ๋ ฅ์„ ์—ฐ๊ณ„ํ•œ 12๊ฐœ์›” ๊ณ„ํš์ •๋น„ ์ผ์ • ์ถ”์ฒœ ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์™€์ด๋ธ” AFT์™€ ์ ๊ฒ€ ๋งˆ๋ชจ ํ”ผ์ฒ˜๋ฅผ ์“ฐ๋Š” GBR์„ ์•™์ƒ๋ธ”ํ•ด ์ž”์—ฌ ์ˆ˜๋ช…์„ ์˜ˆ์ธกํ•˜๊ณ  ์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ๊ธฐ์ค€๊ณผ ๋น„๊ต ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/sup02_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": [ "\"\"\"์™€์ด๋ธ” AFT์™€ ์ ๊ฒ€ ๋งˆ๋ชจ ํ”ผ์ฒ˜๋ฅผ ์“ฐ๋Š” ๊ทธ๋ž˜๋””์–ธํŠธ ๋ถ€์ŠคํŒ…์„ ๊ฒฐํ•ฉํ•œ ์ž”์—ฌ ์ˆ˜๋ช…(RUL) ์˜ˆ์ธก ์—”์ง„์„ ๊ธฐ์ค€ ๋ชจ๋ธ ๋Œ€๋น„ MAEยทRMSEยทR2๋กœ ๊ฒ€์ฆํ•˜๊ณ , ์ •๋น„์ฐฝ ์›” ๋Šฅ๋ ฅยท์˜ˆ์‚ฐยท์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ ์œ ํšจ๊ธฐ๊ฐ„ ์ œ์•ฝ ์•„๋ž˜ ์ •์ˆ˜๊ณ„ํš์œผ๋กœ ์ตœ์  ๊ณ„ํš์ •๋น„(์ฐฝ์ •๋น„) ์ผ์ •์„ ์ถ”์ฒœํ•ด ๊ทœ์ • ์ฃผ๊ธฐ ๊ณ ์ • ์ผ์ •๊ณผ ๋น„์šฉ์„ ๋น„๊ตํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "from sklearn.metrics import r2_score\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์žฅ๋น„ ๋ณด์œ  ํ˜„ํ™ฉยท๊ณ ์žฅ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ˆ˜์น˜ ์—†์Œ)\n", "# ๊ฐ€์ƒ ๋ถ€๋Œ€ 6๊ฐœ(U1~U6)์˜ ์žฅ๋น„๊ฐ€ ์šด์šฉ ํ™˜๊ฒฝ(normal/rough/salt/cold)์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์†๋„๋กœ ๋…ธํ›„ํ™”ํ•œ๋‹ค. ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„(h_since)์ด\n", "# ์žฅ๋น„๋งˆ๋‹ค ์ˆจ์€ ์ˆ˜๋ช…(t_fail)์„ ๋„˜์œผ๋ฉด ๊ณ ์žฅ(event=1) ์ด๋ ฅ, ๋„˜์ง€ ์•Š์•˜์œผ๋ฉด ํ˜„์žฌ๊นŒ์ง€ ์ƒ์กด(event=0, ๊ด€์ธก ์ค‘๋„์ ˆ๋‹จ)์ด๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ENVS = [\"normal\", \"rough\", \"salt\", \"cold\"]\n", "ENV_W = {\"normal\": 1.00, \"rough\": 1.35, \"salt\": 1.25, \"cold\": 1.12} # ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜(์ˆจ์€ ์ •๋‹ต, ๊ฐ€์ •๊ฐ’): ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ์งง๋‹ค\n", "ENV_KO = {\"normal\": \"์ผ๋ฐ˜\", \"rough\": \"ํ—˜์ง€\", \"salt\": \"์—ผ๋ถ„\", \"cold\": \"ํ˜นํ•œ\"}\n", "UNITS = [\"U1\", \"U2\", \"U3\", \"U4\", \"U5\", \"U6\"]\n", "UNIT_ENV = {\"U1\": [.7, .1, .1, .1], \"U2\": [.2, .6, .1, .1], \"U3\": [.2, .1, .6, .1], \"U4\": [.3, .1, .1, .5], \"U5\": [.5, .3, .1, .1], \"U6\": [.4, .1, .4, .1]}\n", "ETYPES = [\"truck\", \"generator\", \"trailer\", \"radio\"]\n", "CYCLE = 8000.0 # ๊ทœ์ • ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ(์šด์šฉ์‹œ๊ฐ„ h, ๊ฐ€์ •๊ฐ’)\n", "KSHAPE = 12.0 # ์ˆ˜๋ช… ๋ถ„ํฌ ์™€์ด๋ธ” ํ˜•์ƒ(๋งˆ๋ชจํ˜• ๊ณ ์žฅ, ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ๋ชจ์—ฌ ์žˆ๋‹ค)\n", "INSP_NOISE = 0.025 # ์ •๋น„ ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ ์ธก์ • ์˜ค์ฐจ(๊ฐ€์ •๊ฐ’)\n", "\n", "\n", "def make_fleet(n=900, seed=0, env_w=None, shift=1.0):\n", " \"\"\"์žฅ๋น„ n๋Œ€. env_w๋กœ ์ˆจ์€ ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜๋ฅผ ๋ฐ”๊ฟ” ์šด์˜ ๋ถ„ํฌ ๋ณ€ํ™”๋ฅผ ๋ชจ์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. shift>1์ด๋ฉด ๊ฐ€๋™๋ฅ ์ด ๋†’์•„์ง„๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " w = env_w or ENV_W\n", " unit = rng.choice(UNITS, n)\n", " env = np.array([rng.choice(ENVS, p=UNIT_ENV[u]) for u in unit])\n", " et = rng.choice(ETYPES, n, p=[.4, .2, .2, .2])\n", " rate = rng.lognormal(np.log(3.0 * shift), 0.25, n) # ์ผ ํ‰๊ท  ์šด์šฉ์‹œ๊ฐ„(h/์ผ)\n", " h_since = rng.uniform(300, 11500, n) # ๋งˆ์ง€๋ง‰ ์ฐฝ์ •๋น„ ์ดํ›„ ์šด์šฉ์‹œ๊ฐ„\n", " n_ovh = rng.integers(0, 3, n)\n", " hours = h_since + n_ovh * rng.uniform(5000, 8000, n) # ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„\n", " km = hours * rng.normal(17, 3, n) * np.where(env == \"rough\", 0.8, 1.0) # ๋ˆ„์  ๊ธฐ๋™๊ฑฐ๋ฆฌ(km)\n", " age_y = np.clip(hours / (rate * 365) * rng.uniform(0.9, 1.15, n), 0.5, 25)\n", " unpl = rng.poisson(0.35 * h_since / 1000 * np.array([w[e] for e in env]) / 1.3) # ๋น„๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜(ํ™˜๊ฒฝ์ด ๊ฐ€ํ˜นํ• ์ˆ˜๋ก ๋งŽ๋‹ค)\n", " n_maint = rng.poisson(0.6 * h_since / 1000 + 1) # ๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜\n", " scale = 9600 / np.array([w[e] for e in env]) * 0.985 ** unpl * np.exp(rng.normal(0, 0.04, n))\n", " t_fail = scale * rng.weibull(KSHAPE, n) # ์ฐฝ์ •๋น„ ํ›„ ๊ณ ์žฅ๊นŒ์ง€ ์šด์šฉ์‹œ๊ฐ„(์ˆจ์€ ์ •๋‹ต)\n", " event = (t_fail <= h_since).astype(int)\n", " dur = np.where(event == 1, t_fail, h_since)\n", " df = pd.DataFrame({\"eq_id\": [f\"EQ{seed:02d}{i:04d}\" for i in range(n)], \"unit\": unit, \"env\": env, \"etype\": et, \"rate\": rate, \"hours\": hours, \"km\": km, \"age_y\": age_y, \"n_ovh\": n_ovh,\n", " \"h_since\": h_since, \"unpl\": unpl, \"n_maint\": n_maint, \"t_fail\": t_fail, \"event\": event, \"dur\": dur})\n", " df[\"insp\"] = np.clip(dur / t_fail + rng.normal(0, INSP_NOISE, n), 0, 1.3) # ๋งˆ์ง€๋ง‰ ์ •๋น„ ์ ๊ฒ€์—์„œ ์ธก์ •ํ•œ ๋งˆ๋ชจ ๋น„์œจ(์†Œ๋ชจ ์ˆ˜๋ช…/์ด ์ˆ˜๋ช…, ์˜ค์ฐจ ํฌํ•จ)\n", " df[\"rul_h\"] = np.where(event == 0, t_fail - h_since, np.nan) # ์ƒ์กด ์žฅ๋น„์˜ ๋‚จ์€ ์šด์šฉ์‹œ๊ฐ„(์ •๋‹ต)\n", " df[\"rul_d\"] = df[\"rul_h\"] / df[\"rate\"] # ๋‚จ์€ ์ผ์ˆ˜(์ •๋‹ต)\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " y, p = np.asarray(y, float), np.asarray(p, float)\n", " return float(np.mean(np.abs(y - p) / np.abs(y)) * 100)\n", "\n", "\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "\n", "\n", "def rmse(y, p):\n", " return float(np.sqrt(np.mean((np.asarray(y, float) - np.asarray(p, float)) ** 2)))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธํ›„ํ™” ํŒŒ์ƒ ํ”ผ์ฒ˜์™€ ํ•™์Šต ํ‘œ๋ณธ ๋งŒ๋“ค๊ธฐ. ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ W0๋Š” ๋„์ž… ์‹œ์ ์˜ ์ „๋ฌธ๊ฐ€ ์ถ”์ •๊ฐ’์ด๋ฉฐ(์‹ค์ œ ๊ฐ€์ค‘์น˜์™€ ๋‹ค๋ฆ„) M9์—์„œ ์žฌ๋ณด์ •ํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "W0 = {\"normal\": 1.00, \"rough\": 1.15, \"salt\": 1.10, \"cold\": 1.05}\n", "FEATS = [\"h_since\", \"hours\", \"km\", \"age_y\", \"rate\", \"n_maint\", \"unpl\", \"unpl_per_kh\", \"km_per_h\", \"n_ovh\", \"k_rough\", \"k_salt\", \"k_cold\", \"k_truck\", \"k_gen\", \"k_trailer\",\n", " \"aging_idx\", \"lin_rul_d\", \"hs_frac\", \"insp\", \"insp_eta_h\"]\n", "\n", "\n", "def add_features(df, w=None):\n", " d = df.copy()\n", " w = w or W0\n", " d[\"unpl_per_kh\"] = d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)\n", " d[\"km_per_h\"] = d[\"km\"] / d[\"hours\"]\n", " for e in (\"rough\", \"salt\", \"cold\"):\n", " d[\"k_\" + e] = (d[\"env\"] == e).astype(int)\n", " d[\"k_truck\"], d[\"k_gen\"], d[\"k_trailer\"] = (d[\"etype\"] == \"truck\").astype(int), (d[\"etype\"] == \"generator\").astype(int), (d[\"etype\"] == \"trailer\").astype(int)\n", " d[\"aging_idx\"] = d[\"h_since\"] * d[\"env\"].map(w) / CYCLE # ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜(1์ด๋ฉด ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ์— ๋„๋‹ฌ)\n", " d[\"lin_rul_d\"] = (CYCLE - d[\"h_since\"]) / d[\"rate\"] # ์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ์ž”์—ฌ ์ผ์ˆ˜(Baseline)\n", " d[\"hs_frac\"] = d[\"h_since\"] / CYCLE\n", " d[\"insp_eta_h\"] = d[\"h_since\"] / np.maximum(d[\"insp\"], 0.05) # ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ๋กœ ํ™˜์‚ฐํ•œ ์ด ์ˆ˜๋ช… ์ถ”์ •(h)\n", " return d\n", "\n", "\n", "def snapshots(df, k=4, seed=0):\n", " \"\"\"๊ณ ์žฅ ์ด๋ ฅ ์žฅ๋น„์˜ ๊ณผ๊ฑฐ ์‹œ์  ํ‘œ๋ณธ: ๊ณ ์žฅ ์ „ ์ž„์˜ ์‹œ์ ์˜ ์ ๊ฒ€๊ฐ’ยท์šด์šฉ์‹œ๊ฐ„๊ณผ ๊ทธ๋•Œ์˜ ๋‚จ์€ ์ผ์ˆ˜. (์ƒ์กด ์žฅ๋น„์˜ ๋ฏธ๋ž˜๋Š” ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๊ณ ์žฅ ์ด๋ ฅ๋งŒ ์“ด๋‹ค)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " f = df[df[\"event\"] == 1]\n", " out = []\n", " for _ in range(k):\n", " s = f.copy()\n", " h = s[\"t_fail\"] * rng.uniform(0.2, 0.98, len(s))\n", " ratio = h / s[\"h_since\"]\n", " s[\"hours\"] = s[\"hours\"] - (s[\"h_since\"] - h)\n", " s[\"km\"] = s[\"km\"] * ratio.clip(upper=1)\n", " s[\"unpl\"] = np.floor(s[\"unpl\"] * ratio)\n", " s[\"n_maint\"] = np.floor(s[\"n_maint\"] * ratio)\n", " s[\"insp\"] = np.clip(h / s[\"t_fail\"] + rng.normal(0, INSP_NOISE, len(s)), 0, 1.3)\n", " s[\"h_since\"] = h\n", " s[\"rul_d\"] = (s[\"t_fail\"] - h) / s[\"rate\"]\n", " s[\"event\"] = 0\n", " out.append(s)\n", " return pd.concat(out, ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ƒ์กด ๋ถ„์„ ๋„๊ตฌ(scipy๋งŒ ์‚ฌ์šฉ): ์™€์ด๋ธ” AFT(๊ฐ€์† ์ˆ˜๋ช…) ๋ชจํ˜•๊ณผ Cox ๋น„๋ก€์œ„ํ—˜ ๋ชจํ˜•\n", "# ---------------------------------------------------------------------------\n", "from scipy.optimize import minimize\n", "\n", "\n", "def aft_x(d):\n", " return np.c_[np.ones(len(d)), d[\"env\"].eq(\"rough\"), d[\"env\"].eq(\"salt\"), d[\"env\"].eq(\"cold\"), d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)].astype(float)\n", "\n", "\n", "AFT_COLS = [\"์ ˆํŽธ\", \"ํ—˜์ง€\", \"์—ผ๋ถ„\", \"ํ˜นํ•œ\", \"๋น„๊ณ„ํš ์ •๋น„์œจ\"]\n", "\n", "\n", "def fit_aft(d):\n", " \"\"\"์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„ dur(๊ณ ์žฅ์ด๋ฉด event=1, ์ƒ์กด์ด๋ฉด ์ค‘๋„์ ˆ๋‹จ)์— ๋Œ€ํ•œ ์™€์ด๋ธ” AFT ์ตœ๋Œ€์šฐ๋„ ์ถ”์ •. ๋ฐ˜ํ™˜: ๊ณ„์ˆ˜(๋งˆ์ง€๋ง‰์€ log ํ˜•์ƒ).\"\"\"\n", " x, t, e = aft_x(d), d[\"dur\"].values, d[\"event\"].values\n", "\n", " def nll(p):\n", " lam, k = np.exp(x @ p[:-1]), np.exp(p[-1])\n", " z = (t / lam) ** k\n", " return -(e * (np.log(k) - np.log(lam) + (k - 1) * (np.log(t) - np.log(lam))) - z).sum()\n", "\n", " r = minimize(nll, np.r_[np.log(8000), np.zeros(x.shape[1] - 1), np.log(5)], method=\"BFGS\")\n", " return r.x\n", "\n", "\n", "def aft_pred(p, d, q=0.5, h=None):\n", " \"\"\"์ด๋ฏธ h(์šด์šฉ์‹œ๊ฐ„)๊นŒ์ง€ ์ƒ์กดํ–ˆ๋‹ค๋Š” ์กฐ๊ฑด์—์„œ ๋ถ„์œ„์ˆ˜ q ์ƒ์กด ์‹œ์ (์šด์šฉ์‹œ๊ฐ„ h ๊ธฐ์ค€ ์ด ์ˆ˜๋ช…)์„ ๋Œ๋ ค์ค€๋‹ค. q=0.5๊ฐ€ ์กฐ๊ฑด๋ถ€ ์ค‘์•™๊ฐ’.\"\"\"\n", " lam, k = np.exp(aft_x(d) @ p[:-1]), np.exp(p[-1])\n", " h = d[\"h_since\"].values if h is None else h\n", " return lam * ((h / lam) ** k - np.log(q)) ** (1 / k)\n", "\n", "\n", "def aft_fail_prob(p, d, h0, h1):\n", " \"\"\"h0๊นŒ์ง€ ์ƒ์กดํ•œ ์žฅ๋น„๊ฐ€ h0~h1 ์‚ฌ์ด์— ๊ณ ์žฅ ๋‚  ์กฐ๊ฑด๋ถ€ ํ™•๋ฅ .\"\"\"\n", " lam, k = np.exp(aft_x(d) @ p[:-1]), np.exp(p[-1])\n", " s0, s1 = np.exp(-(h0 / lam) ** k), np.exp(-(h1 / lam) ** k)\n", " return 1 - s1 / s0\n", "\n", "\n", "def fit_cox(d, iters=25):\n", " \"\"\"Cox ๋น„๋ก€์œ„ํ—˜(Breslow ๊ทผ์‚ฌ) ๋‰ดํ„ด-๋žฉ์Šจ. ๊ณต๋ณ€๋Ÿ‰: ํ—˜์ง€ยท์—ผ๋ถ„ยทํ˜นํ•œยท๋น„๊ณ„ํš ์ •๋น„์œจ. ๋ฐ˜ํ™˜: ๊ณ„์ˆ˜.\"\"\"\n", " x = aft_x(d)[:, 1:]\n", " o = np.argsort(-d[\"dur\"].values)\n", " x, e = x[o], d[\"event\"].values[o]\n", " b = np.zeros(x.shape[1])\n", " for _ in range(iters):\n", " r = np.exp(x @ b)\n", " S0 = np.cumsum(r)\n", " S1 = np.cumsum(r[:, None] * x, axis=0)\n", " S2 = np.cumsum(r[:, None, None] * x[:, :, None] * x[:, None, :], axis=0)\n", " m1 = S1 / S0[:, None]\n", " g = ((x - m1) * e[:, None]).sum(axis=0)\n", " H = -sum(e[i] * (S2[i] / S0[i] - np.outer(m1[i], m1[i])) for i in np.where(e == 1)[0])\n", " step = np.linalg.solve(H - 1e-6 * np.eye(len(b)), g)\n", " b = b - step\n", " if np.abs(step).max() < 1e-7:\n", " break\n", " return b\n", "\n", "\n", "def cindex(time, event, risk):\n", " \"\"\"์ผ์น˜๋„ C-index: ์œ„ํ—˜ ์ ์ˆ˜๊ฐ€ ๋†’์€ ์ชฝ์ด ๋จผ์ € ๊ณ ์žฅ ๋‚ฌ๋Š”๊ฐ€(๊ด€์ธก ๊ฐ€๋Šฅํ•œ ์Œ๋งŒ).\"\"\"\n", " t, e, r = np.asarray(time), np.asarray(event), np.asarray(risk)\n", " num = den = 0.0\n", " for i in np.where(e == 1)[0]:\n", " m = t > t[i]\n", " den += m.sum()\n", " num += (r[m] < r[i]).sum() + 0.5 * (r[m] == r[i]).sum()\n", " return num / max(den, 1)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ž”์—ฌ ์ˆ˜๋ช…(RUL, ์ผ) ์˜ˆ์ธก ์—”์ง„: ์™€์ด๋ธ” AFT ์กฐ๊ฑด๋ถ€ ์ค‘์•™๊ฐ’ + ๊ทธ๋ž˜๋””์–ธํŠธ ๋ถ€์ŠคํŒ…(๊ณ ์žฅ ์ด๋ ฅ ์‹œ์  ํ‘œ๋ณธ ํ•™์Šต)์˜ ํ‰๊ท \n", "# ---------------------------------------------------------------------------\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "\n", "\n", "def fit_engine(train, seed=0):\n", " p = fit_aft(train)\n", " sn = add_features(snapshots(train, 5, seed))\n", " sn[\"aft\"] = (aft_pred(p, sn) - sn[\"h_since\"]) / sn[\"rate\"]\n", " g = GradientBoostingRegressor(n_estimators=250, max_depth=3, learning_rate=0.06, random_state=0).fit(sn[FEATS + [\"aft\"]], sn[\"rul_d\"])\n", " return {\"p\": p, \"g\": g}\n", "\n", "\n", "def predict_rul(eng, d):\n", " \"\"\"d๋Š” add_features๋ฅผ ๊ฑฐ์นœ ํ‘œ. ๋ฐ˜ํ™˜: (์•™์ƒ๋ธ” RUL, AFT RUL, GBR RUL) - ์ผ ๋‹จ์œ„.\"\"\"\n", " aft = ((aft_pred(eng[\"p\"], d) - d[\"h_since\"]) / d[\"rate\"]).values\n", " x = d[FEATS].copy()\n", " x[\"aft\"] = aft\n", " gb = eng[\"g\"].predict(x)\n", " return np.clip(0.5 * aft + 0.5 * gb, 1, None), aft, gb\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๊ณ„ํš์ •๋น„(์ฐฝ์ •๋น„) ์ผ์ • ์ถ”์ฒœ: ์›”๋ณ„ ์ •๋น„์ฐฝ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐยท์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ(์œ ํšจ๊ธฐํ•œ) ์ œ์•ฝ ์•„๋ž˜ ๊ธฐ๋Œ€ ๋น„์šฉ ์ตœ์†Œํ™” (๋น„์šฉ ๋‹จ๊ฐ€๋Š” ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’, ๋งŒ์›)\n", "# ---------------------------------------------------------------------------\n", "from scipy.optimize import milp, LinearConstraint, Bounds\n", "from scipy.stats import norm\n", "\n", "C_PLAN, C_FAIL, C_WASTE_KH, C_KIT = 600.0, 2400.0, 40.0, 120.0 # ๊ณ„ํš ์ฐฝ์ •๋น„, ๋น„๊ณ„ํš ๊ณ ์žฅ(๊ธด๊ธ‰ ์ •๋น„ ํฌํ•จ), ๋ฏธ์‚ฌ์šฉ ์ˆ˜๋ช… 1000h๋‹น ์†์‹ค, ์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ\n", "HORIZON, MDAYS = 12, 30 # ๊ณ„ํš 12๊ฐœ์›”, ์›” 30์ผ\n", "CAP_M, BUDGET_M = 22, 15000.0 # ์›”๋ณ„ ์ •๋น„์ฐฝ ๋Šฅ๋ ฅ(๋Œ€), ์›”๋ณ„ ์˜ˆ์‚ฐ\n", "LOTS = [(30, 70), (45, 170), (45, 390), (70, 800)] # ์‹œํšจ์„ฑ ํ‚คํŠธ ๋กœํŠธ(์ˆ˜๋Ÿ‰, ์œ ํšจ๊ธฐํ•œ๊นŒ์ง€ ๋‚จ์€ ์ผ์ˆ˜)\n", "KIT_MARGIN = 14 # ์ •๋น„์ผ ๊ธฐ์ค€ ์ตœ์†Œ ์œ ํšจ๊ธฐ๊ฐ„ ์—ฌ์œ (์ผ)\n", "\n", "\n", "def exp_cost_matrix(pred, rate, month_days):\n", " \"\"\"์žฅ๋น„ i๋ฅผ ์›” m์— ์ •๋น„ํ•  ๋•Œ์˜ ๊ธฐ๋Œ€ ๋น„์šฉ. ์ฐธ RUL ~ N(pred, sigma), sigma = 35์ผ + 0.12 x pred (๊ฐ€์ •). ๋งˆ์ง€๋ง‰ ์—ด์€ ๊ณ„ํš ๊ธฐ๊ฐ„ ๋ฐ–(๋ฏธ์ •๋น„).\"\"\"\n", " sig = 35 + 0.12 * pred\n", " C = np.zeros((len(pred), len(month_days) + 1))\n", " for j, day in enumerate(month_days):\n", " z = (day - pred) / sig\n", " pf = norm.cdf(z)\n", " unused = np.maximum(pred - day, 0) * rate * 0.6 # ์ •๋น„๋ฅผ ์•ž๋‹น๊ธด ๋งŒํผ ๋ฒ„๋ฆฌ๋Š” ์šด์šฉ์‹œ๊ฐ„ h(๊ทผ์‚ฌ)\n", " C[:, j] = C_PLAN + C_KIT + pf * (C_FAIL - C_PLAN) + C_WASTE_KH * unused / 1000 * (1 - pf)\n", " C[:, -1] = norm.cdf((HORIZON * MDAYS - pred) / sig) * C_FAIL\n", " return C\n", "\n", "\n", "def plan_milp(pred, rate, cap=CAP_M, budget=BUDGET_M, lots=LOTS, use_kit=True):\n", " \"\"\"์ •์ˆ˜๊ณ„ํš์œผ๋กœ ์žฅ๋น„๋ณ„ ์ •๋น„ ์›”(0~11) ๋˜๋Š” ๋ฏธ์ •๋น„(-1)๋ฅผ ๋ฐฐ์ •ํ•œ๋‹ค. ํ›„๋ณด๋Š” ์˜ˆ์ธก RUL์ด ๊ณ„ํš ๊ธฐ๊ฐ„ +90์ผ ์ด๋‚ด์ธ ์žฅ๋น„.\"\"\"\n", " md = np.arange(1, HORIZON + 1) * MDAYS\n", " cand = np.where(pred < HORIZON * MDAYS + 90)[0]\n", " n, M = len(cand), HORIZON\n", " C = exp_cost_matrix(pred[cand], rate[cand], md)\n", " c = C.ravel()\n", " A, lo, hi = [], [], []\n", " for i in range(n): # ์žฅ๋น„๋‹น ํ•œ ๋ฒˆ๋งŒ ๋ฐฐ์ •(์›” 12์นธ + ๋ฏธ์ •๋น„)\n", " row = np.zeros(n * (M + 1)); row[i * (M + 1):(i + 1) * (M + 1)] = 1; A.append(row); lo.append(1); hi.append(1)\n", " for m in range(M): # ์›”๋ณ„ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐ\n", " row = np.zeros(n * (M + 1)); row[m::(M + 1)] = 1\n", " A.append(row); lo.append(0); hi.append(cap)\n", " A.append(row * (C_PLAN + C_KIT)); lo.append(0); hi.append(budget)\n", " if use_kit: # ์‹œํšจ์„ฑ ํ‚คํŠธ: m์›” ์ดํ›„ ์ •๋น„ ์ˆ˜์š”๋Š” ๊ทธ ์‹œ์ ์— ์œ ํšจ๊ธฐ๊ฐ„์ด ๋‚จ์€ ๋กœํŠธ ์ˆ˜๋Ÿ‰์„ ๋„˜์„ ์ˆ˜ ์—†๋‹ค\n", " for m in range(M):\n", " sup = sum(q for q, e in lots if e >= md[m] + KIT_MARGIN)\n", " row = np.zeros(n * (M + 1))\n", " for mm in range(m, M):\n", " row[mm::(M + 1)] = 1\n", " A.append(row); lo.append(0); hi.append(sup)\n", " r = milp(c, constraints=LinearConstraint(np.array(A), lo, hi), integrality=np.ones(len(c)), bounds=Bounds(0, 1), options={\"time_limit\": 20})\n", " month = np.full(len(pred), -1)\n", " if r.x is not None:\n", " x = np.round(r.x).reshape(n, M + 1)\n", " assign = np.where(x[:, :M].sum(axis=1) > 0.5, x[:, :M].argmax(axis=1), -1)\n", " month[cand] = assign\n", " return month\n", "\n", "\n", "def plan_greedy(pred, margin=45, cap=CAP_M, budget=BUDGET_M):\n", " \"\"\"๊ฐ„๋‹จํ•œ ํƒ์š• ๋ฐฐ์ •: ์ •๋น„ ๋ชฉํ‘œ์ผ(์˜ˆ์ธก RUL - margin)์ด ๋น ๋ฅธ ์ˆœ์œผ๋กœ ๋ชฉํ‘œ์ผ ์ด์ „ ๊ฐ€์žฅ ๋Šฆ์€ ๋นˆ ์›”, ์—†์œผ๋ฉด ๊ฐ€์žฅ ์ด๋ฅธ ๋นˆ ์›”์— ๋ฐฐ์ •.\"\"\"\n", " cap = min(cap, int(budget // (C_PLAN + C_KIT))) # ์›” ์˜ˆ์‚ฐ์ด ํ—ˆ์šฉํ•˜๋Š” ๋Œ€์ˆ˜๋กœ ๋Šฅ๋ ฅ์„ ์ œํ•œ\n", " month = np.full(len(pred), -1)\n", " left = np.full(HORIZON, cap)\n", " for i in np.argsort(pred):\n", " if pred[i] > HORIZON * MDAYS + 60: break\n", " tgt = int(np.clip((pred[i] - margin) // MDAYS - 1, 0, HORIZON - 1))\n", " for m in list(range(tgt, -1, -1)) + list(range(tgt + 1, HORIZON)):\n", " if left[m] > 0:\n", " month[i] = m; left[m] -= 1; break\n", " return month\n", "\n", "\n", "def plan_fixed(lin_rul, cap=CAP_M, budget=BUDGET_M):\n", " \"\"\"๊ด€ํ–‰: ๊ทœ์ • ์ฃผ๊ธฐ ๋„๋‹ฌ ์ˆœ์„œ๋Œ€๋กœ(์„ ํ˜• ์ž”์—ฌ ์ผ์ˆ˜) ์›”๋ณ„ ๋Šฅ๋ ฅ ์•ˆ์—์„œ ๋ฐฐ์ •.\"\"\"\n", " return plan_greedy(np.asarray(lin_rul, float), margin=0, cap=cap, budget=budget)\n", "\n", "\n", "def evaluate_plan(month, true_rul, rate, cost_kit=True):\n", " \"\"\"์ฐธ RUL๋กœ ๊ณ„ํš์˜ ์‹ค์ œ ๋น„์šฉ์„ ๊ณ„์‚ฐํ•œ๋‹ค: ์ •๋น„์ผ ์ด์ „์— ๊ณ ์žฅ -> ๋น„๊ณ„ํš ๊ณ ์žฅ ๋น„์šฉ, ์•„๋‹ˆ๋ฉด ๊ณ„ํš ๋น„์šฉ + ๋ฏธ์‚ฌ์šฉ ์ˆ˜๋ช… ์†์‹ค.\"\"\"\n", " day = np.where(month >= 0, (month + 1) * MDAYS, 10 ** 9)\n", " fail = (true_rul < day) & (true_rul < HORIZON * MDAYS)\n", " planned = (month >= 0) & ~fail\n", " waste = np.where(planned, (true_rul - day) * rate * 0.6, 0) * C_WASTE_KH / 1000\n", " cost = fail * C_FAIL + planned * (C_PLAN + (C_KIT if cost_kit else 0)) + waste\n", " return {\"total\": float(cost.sum()), \"fail\": int(fail.sum()), \"planned\": int(planned.sum()), \"waste\": float(waste.sum()), \"unsched_fail\": int(((month < 0) & fail).sum())}\n", "\n", "\n", "def kit_fefo(month, lots=LOTS):\n", " \"\"\"์œ ํšจ๊ธฐํ•œ์ด ๋น ๋ฅธ ๋กœํŠธ๋ถ€ํ„ฐ ์“ฐ๋Š” FEFO๋กœ ํ‚คํŠธ๋ฅผ ๋ฐฐ์ •ํ•˜๊ณ  ์ •๋น„์ผ์— ์œ ํšจ๊ธฐ๊ฐ„์ด ๋ถ€์กฑํ•œ ๋ฐฐ์ • ์ˆ˜์™€ ๋งŒ๋ฃŒ ํ๊ธฐ ์ˆ˜๋Ÿ‰์„ ์„ผ๋‹ค.\"\"\"\n", " q = [[qty, e] for qty, e in sorted(lots, key=lambda t: t[1])]\n", " short = 0\n", " for m in sorted(month[month >= 0]):\n", " day = (m + 1) * MDAYS\n", " for lot in q:\n", " if lot[1] >= day + KIT_MARGIN and lot[0] > 0:\n", " lot[0] -= 1; break\n", " else:\n", " short += 1\n", " expired = sum(l[0] for l in q if l[1] < HORIZON * MDAYS)\n", " return {\"short\": short, \"expired_unused\": expired, \"left\": sum(l[0] for l in q)}\n", "# ---------------------------------------------------------------------------\n", "\n", "tr = make_fleet(900, seed=1)\n", "te = add_features(make_fleet(900, seed=3))\n", "eng = fit_engine(tr)\n", "al = te[te.event == 0].reset_index(drop=True)\n", "pred, aft, gb = predict_rul(eng, al)\n", "y = al[\"rul_d\"].values\n", "big = y >= 90\n", "P = {\"๊ธฐ์ค€(์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ)\": al[\"lin_rul_d\"].values, \"์™€์ด๋ธ” AFT\": aft, \"GBR(์ ๊ฒ€ ๋งˆ๋ชจ ํฌํ•จ)\": gb, \"์•™์ƒ๋ธ”(์ œ์•ˆ)\": pred}\n", "tab = pd.DataFrame({k: {\"MAE(์ผ)\": mae(y, v), \"RMSE(์ผ)\": rmse(y, v), \"MAPE(%)\": mape(y[big], v[big]), \"R2\": r2_score(y, v)} for k, v in P.items()}).T\n", "print(f\"[1] ์ƒ์กด ์žฅ๋น„ {len(al)}๋Œ€ RUL ์˜ˆ์ธก ์„ฑ๋Šฅ(ํ•™์Šต {len(tr)}๋Œ€์™€ ๋‹ค๋ฅธ ์žฅ๋น„, MAPE๋Š” ์ฐธ RUL 90์ผ ์ด์ƒ)\")\n", "print(tab.round(3).to_string())\n", "b, e = tab.loc[\"๊ธฐ์ค€(์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ)\"], tab.loc[\"์•™์ƒ๋ธ”(์ œ์•ˆ)\"]\n", "ok = 1 - e[\"MAE(์ผ)\"] / b[\"MAE(์ผ)\"] >= .2 and 1 - e[\"RMSE(์ผ)\"] / b[\"RMSE(์ผ)\"] >= .2\n", "print(f\" ๊ธฐ์ค€ ๋Œ€๋น„ MAE ๊ฐœ์„  {1 - e['MAE(์ผ)'] / b['MAE(์ผ)']:.1%}, RMSE ๊ฐœ์„  {1 - e['RMSE(์ผ)'] / b['RMSE(์ผ)']:.1%} (๋ชฉํ‘œ 20% ์ด์ƒ: {'๋‹ฌ์„ฑ' if ok else '๋ฏธ๋‹ฌ'}), R2 {e['R2']:.3f} (๋ชฉํ‘œ 0.85 ์ด์ƒ: {'๋‹ฌ์„ฑ' if e['R2'] >= .85 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# ์ ๊ฒ€ ๋งˆ๋ชจ ํ”ผ์ฒ˜์˜ ํšจ๊ณผ: ์ œ๊ฑฐ ์‹คํ—˜\n", "tr_f = add_features(snapshots(tr, 5, 0))\n", "cols_no = [c for c in FEATS if c not in (\"insp\", \"insp_eta_h\")]\n", "g_no = GradientBoostingRegressor(n_estimators=250, max_depth=3, learning_rate=0.06, random_state=0).fit(tr_f[cols_no], tr_f[\"rul_d\"])\n", "p_no = g_no.predict(al[cols_no])\n", "print(f\" ์ ๊ฒ€ ๋งˆ๋ชจ ํ”ผ์ฒ˜๋ฅผ ๋นผ๋ฉด GBR MAE {mae(y, gb):.0f}์ผ -> {mae(y, p_no):.0f}์ผ, R2 {r2_score(y, gb):.3f} -> {r2_score(y, p_no):.3f} (์ •๋น„ ์ ๊ฒ€ ๊ธฐ๋ก์˜ ๊ฐ€์น˜)\")\n", "\n", "# ๋ถˆ๊ทœ์น™ ์ƒํ™ฉ๋ณ„ ์˜ค์ฐจ\n", "conds = {\"์ผ๋ฐ˜\": al.env == \"normal\", \"ํ—˜์ง€\": al.env == \"rough\", \"์—ผ๋ถ„\": al.env == \"salt\", \"ํ˜นํ•œ\": al.env == \"cold\", \"๊ณ ๊ฐ€๋™(์ผ 4h ์ด์ƒ)\": al.rate >= 4, \"๋น„๊ณ„ํš ์ •๋น„์œจ ์ƒ์œ„ 25%\": al.unpl_per_kh >= al.unpl_per_kh.quantile(.75)}\n", "rr = pd.DataFrame([(k, int(m.sum()), mae(y[m.values], P[\"๊ธฐ์ค€(์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ)\"][m.values]), mae(y[m.values], pred[m.values])) for k, m in conds.items()], columns=[\"์กฐ๊ฑด\", \"๋Œ€์ˆ˜\", \"๊ธฐ์ค€ MAE\", \"์•™์ƒ๋ธ” MAE\"])\n", "print(\"\\n[2] ์กฐ๊ฑด๋ณ„ MAE(์ผ)\\n\" + rr.round(0).to_string(index=False))\n", "\n", "# ---------------- ๊ณ„ํš์ •๋น„ ์ผ์ • ์ถ”์ฒœ ----------------\n", "rate, true_rul = al[\"rate\"].values, al[\"rul_d\"].values\n", "mf, mg = plan_fixed(al[\"lin_rul_d\"].values), plan_greedy(pred)\n", "mm = plan_milp(pred, rate)\n", "pl = {\"๊ทœ์ • ์ฃผ๊ธฐ ๊ณ ์ • ์ผ์ •(๊ด€ํ–‰)\": mf, \"์˜ˆ์ธก ๊ธฐ๋ฐ˜ ํƒ์š• ๋ฐฐ์ •\": mg, \"์˜ˆ์ธก ๊ธฐ๋ฐ˜ ์ •์ˆ˜๊ณ„ํš(์ œ์•ˆ)\": mm}\n", "rows = []\n", "for k, v in pl.items():\n", " r, kf = evaluate_plan(v, true_rul, rate), kit_fefo(v)\n", " rows.append((k, int((v >= 0).sum()), r[\"total\"], r[\"fail\"], r[\"planned\"], kf[\"short\"]))\n", "pt = pd.DataFrame(rows, columns=[\"์ผ์ •์•ˆ\", \"๋ฐฐ์ • ๋Œ€์ˆ˜\", \"์‹ค์ œ ์ด๋น„์šฉ(๋งŒ์›)\", \"๋น„๊ณ„ํš ๊ณ ์žฅ\", \"๊ณ„ํš ์ •๋น„\", \"ํ‚คํŠธ ์œ ํšจ๊ธฐ๊ฐ„ ๋ถ€์กฑ\"])\n", "print(f\"\\n[3] 12๊ฐœ์›” ๊ณ„ํš์ •๋น„ ์ผ์ • ๋น„๊ต(์›” ๋Šฅ๋ ฅ {CAP_M}๋Œ€, ์›” ์˜ˆ์‚ฐ {BUDGET_M:,.0f}๋งŒ์›, ํ‚คํŠธ ์žฌ๊ณ  {sum(q for q, _ in LOTS)}๊ฐœ ๋กœํŠธ {len(LOTS)}์ข…, ๋น„์šฉ ๋‹จ๊ฐ€๋Š” ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\")\n", "print(pt.round(0).to_string(index=False))\n", "sv = 1 - pt.iloc[2][\"์‹ค์ œ ์ด๋น„์šฉ(๋งŒ์›)\"] / pt.iloc[0][\"์‹ค์ œ ์ด๋น„์šฉ(๋งŒ์›)\"]\n", "print(f\" ์ •์ˆ˜๊ณ„ํš ์•ˆ์€ ๊ด€ํ–‰ ๋Œ€๋น„ ์ด๋น„์šฉ {sv:.1%} ์ ˆ๊ฐ, ๋น„๊ณ„ํš ๊ณ ์žฅ {int(pt.iloc[0]['๋น„๊ณ„ํš ๊ณ ์žฅ'])} -> {int(pt.iloc[2]['๋น„๊ณ„ํš ๊ณ ์žฅ'])}๋Œ€\")\n", "cnt = np.bincount(mm[mm >= 0], minlength=HORIZON)\n", "print(\" ์›”๋ณ„ ์ •๋น„ ๋Œ€์ˆ˜(์ œ์•ˆ):\", \" \".join(f\"{i + 1}์›” {c}\" for i, c in enumerate(cnt)), f\"(๋Šฅ๋ ฅ {CAP_M}๋Œ€ ์ดํ•˜ ํ™•์ธ: {'OK' if cnt.max() <= CAP_M else '์ดˆ๊ณผ'}, ์›” ์˜ˆ์‚ฐ ํ™•์ธ: {'OK' if (cnt * (C_PLAN + C_KIT)).max() <= BUDGET_M else '์ดˆ๊ณผ'})\")\n", "kf = kit_fefo(mm)\n", "print(f\" ์‹œํšจ์„ฑ ํ‚คํŠธ FEFO ๋ฐฐ์ •: ์œ ํšจ๊ธฐ๊ฐ„ ๋ถ€์กฑ {kf['short']}๊ฑด, ๋งŒ๋ฃŒ ์ž„๋ฐ• ํ๊ธฐ ์˜ˆ์ƒ {kf['expired_unused']}๊ฐœ -> ์œ ํšจ๊ธฐํ•œ์ด ๋น ๋ฅธ ๋กœํŠธ๋ถ€ํ„ฐ ์“ฐ๋„๋ก ์ •๋น„ ์›”์„ ๋งž์ถค\")\n", "print(\" ์ถ”์ฒœ ์ผ์ •์€ ์ •๋น„๊ณ„ํš๊ด€ ๊ฒ€ํ† ์™€ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ(M7) ์ „๊นŒ์ง€ ํ™•์ •๋˜์ง€ ์•Š๋Š”๋‹ค. ์žฅ๋น„ยท๋‹จ๊ฐ€๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 3, figsize=(15, 4.3))\n", "ax[0].scatter(y, P[\"๊ธฐ์ค€(์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ)\"], s=6, label=\"baseline\"); ax[0].scatter(y, pred, s=6, label=\"ensemble\"); ax[0].plot([0, 4000], [0, 4000], \"k--\"); ax[0].legend(); ax[0].set_title(\"RUL: true vs predicted (days)\")\n", "ax[1].bar(range(len(pt)), pt[\"์‹ค์ œ ์ด๋น„์šฉ(๋งŒ์›)\"]); ax[1].set_xticks(range(len(pt))); ax[1].set_xticklabels([\"fixed\", \"greedy\", \"MILP\"]); ax[1].set_title(\"Total cost of 12-month plan (10k KRW)\")\n", "ax[2].bar(np.arange(1, HORIZON + 1), cnt); ax[2].axhline(CAP_M, c=\"r\", ls=\"--\"); ax[2].set_title(\"Overhauls per month (capacity line)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 05 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ์ƒ์กด ์žฅ๋น„ 601๋Œ€ RUL ์˜ˆ์ธก ์„ฑ๋Šฅ(ํ•™์Šต 900๋Œ€์™€ ๋‹ค๋ฅธ ์žฅ๋น„, MAPE๋Š” ์ฐธ RUL 90์ผ ์ด์ƒ)\n", " MAE(์ผ) RMSE(์ผ) MAPE(%) R2\n", "๊ธฐ์ค€(์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ) 354.501 435.605 46.016 0.808\n", "์™€์ด๋ธ” AFT 212.324 277.206 24.014 0.922\n", "GBR(์ ๊ฒ€ ๋งˆ๋ชจ ํฌํ•จ) 253.028 396.280 16.536 0.842\n", "์•™์ƒ๋ธ”(์ œ์•ˆ) 210.946 289.055 17.907 0.916\n", " ๊ธฐ์ค€ ๋Œ€๋น„ MAE ๊ฐœ์„  40.5%, RMSE ๊ฐœ์„  33.6% (๋ชฉํ‘œ 20% ์ด์ƒ: ๋‹ฌ์„ฑ), R2 0.916 (๋ชฉํ‘œ 0.85 ์ด์ƒ: ๋‹ฌ์„ฑ)\n", " ์ ๊ฒ€ ๋งˆ๋ชจ ํ”ผ์ฒ˜๋ฅผ ๋นผ๋ฉด GBR MAE 253์ผ -> 491์ผ, R2 0.842 -> 0.578 (์ •๋น„ ์ ๊ฒ€ ๊ธฐ๋ก์˜ ๊ฐ€์น˜)\n", "\n", "[2] ์กฐ๊ฑด๋ณ„ MAE(์ผ)\n", " ์กฐ๊ฑด ๋Œ€์ˆ˜ ๊ธฐ์ค€ MAE ์•™์ƒ๋ธ” MAE\n", " ์ผ๋ฐ˜ 265 432.0 238.0\n", " ํ—˜์ง€ 99 379.0 163.0\n", " ์—ผ๋ถ„ 124 265.0 192.0\n", " ํ˜นํ•œ 113 249.0 212.0\n", " ๊ณ ๊ฐ€๋™(์ผ 4h ์ด์ƒ) 89 240.0 145.0\n", "๋น„๊ณ„ํš ์ •๋น„์œจ ์ƒ์œ„ 25% 151 320.0 198.0\n", "\n", "[3] 12๊ฐœ์›” ๊ณ„ํš์ •๋น„ ์ผ์ • ๋น„๊ต(์›” ๋Šฅ๋ ฅ 22๋Œ€, ์›” ์˜ˆ์‚ฐ 15,000๋งŒ์›, ํ‚คํŠธ ์žฌ๊ณ  190๊ฐœ ๋กœํŠธ 4์ข…, ๋น„์šฉ ๋‹จ๊ฐ€๋Š” ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\n", " ์ผ์ •์•ˆ ๋ฐฐ์ • ๋Œ€์ˆ˜ ์‹ค์ œ ์ด๋น„์šฉ(๋งŒ์›) ๋น„๊ณ„ํš ๊ณ ์žฅ ๊ณ„ํš ์ •๋น„ ํ‚คํŠธ ์œ ํšจ๊ธฐ๊ฐ„ ๋ถ€์กฑ\n", "๊ทœ์ • ์ฃผ๊ธฐ ๊ณ ์ • ์ผ์ •(๊ด€ํ–‰) 123 184752.0 47 97 0\n", " ์˜ˆ์ธก ๊ธฐ๋ฐ˜ ํƒ์š• ๋ฐฐ์ • 105 141558.0 35 79 0\n", " ์˜ˆ์ธก ๊ธฐ๋ฐ˜ ์ •์ˆ˜๊ณ„ํš(์ œ์•ˆ) 101 116447.0 21 90 0\n", " ์ •์ˆ˜๊ณ„ํš ์•ˆ์€ ๊ด€ํ–‰ ๋Œ€๋น„ ์ด๋น„์šฉ 37.0% ์ ˆ๊ฐ, ๋น„๊ณ„ํš ๊ณ ์žฅ 47 -> 21๋Œ€\n", " ์›”๋ณ„ ์ •๋น„ ๋Œ€์ˆ˜(์ œ์•ˆ): 1์›” 20 2์›” 20 3์›” 20 4์›” 20 5์›” 20 6์›” 1 7์›” 0 8์›” 0 9์›” 0 10์›” 0 11์›” 0 12์›” 0 (๋Šฅ๋ ฅ 22๋Œ€ ์ดํ•˜ ํ™•์ธ: OK, ์›” ์˜ˆ์‚ฐ ํ™•์ธ: OK)\n", " ์‹œํšจ์„ฑ ํ‚คํŠธ FEFO ๋ฐฐ์ •: ์œ ํšจ๊ธฐ๊ฐ„ ๋ถ€์กฑ 0๊ฑด, ๋งŒ๋ฃŒ ์ž„๋ฐ• ํ๊ธฐ ์˜ˆ์ƒ 10๊ฐœ -> ์œ ํšจ๊ธฐํ•œ์ด ๋น ๋ฅธ ๋กœํŠธ๋ถ€ํ„ฐ ์“ฐ๋„๋ก ์ •๋น„ ์›”์„ ๋งž์ถค\n", " ์ถ”์ฒœ ์ผ์ •์€ ์ •๋น„๊ณ„ํš๊ด€ ๊ฒ€ํ† ์™€ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ(M7) ์ „๊นŒ์ง€ ํ™•์ •๋˜์ง€ ์•Š๋Š”๋‹ค. ์žฅ๋น„ยท๋‹จ๊ฐ€๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 06. ๋ถ€๋Œ€๋ณ„ ์žฅ๋น„ ๋…ธํ›„๋„ ํ˜„ํ™ฉ ๋ฐ ๊ถŒ์žฅ ์ •๋น„ ์บ˜๋ฆฐ๋” ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋ถ€๋Œ€๋ณ„ ์žฅ๋น„ ๋…ธํ›„๋„ ํ˜„ํ™ฉ ๋ฐ ๊ถŒ์žฅ ์ •๋น„ ์บ˜๋ฆฐ๋” ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ถ€๋Œ€๋ณ„ ์žฅ๋น„ ๋…ธํ›„๋„ ํ˜„ํ™ฉ๊ณผ ๊ถŒ์žฅ ์ •๋น„ ์บ˜๋ฆฐ๋” ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์˜ˆ์ธก ๊ทผ๊ฑฐ๋ฅผ ์„ค๋ช…ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋ถ€๋Œ€๋ณ„ ์žฅ๋น„ ์ˆ˜, ํ‰๊ท  ๋…ธํ›„ ์ง€์ˆ˜, ํ‰๊ท  ์˜ˆ์ธก ์ž”์—ฌ ์ˆ˜๋ช…, ๊ณ ์œ„ํ—˜ ๋น„์œจ์„ ์ง‘๊ณ„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/explainability/sup02_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": [ "\"\"\"๋ถ€๋Œ€๋ณ„ ์žฅ๋น„ ๋…ธํ›„๋„ ํ˜„ํ™ฉ๊ณผ ๊ถŒ์žฅ ์ •๋น„ ์บ˜๋ฆฐ๋” ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ , ์žฅ๋น„๋งˆ๋‹ค ์ž”์—ฌ ์ˆ˜๋ช…(RUL) ์˜ˆ์ธก์˜ ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์š”์ธ์„ ๋ณ€์ˆ˜๊ตฐ ํ‰๊ท  ๋Œ€์ฒด ๋ฐฉ์‹์œผ๋กœ ์ž๋™ ์‚ฐ์ถœํ•ด ์ž์—ฐ์–ด ์„ค๋ช…์œผ๋กœ ๋ณด์—ฌ ์ค€๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์žฅ๋น„ ๋ณด์œ  ํ˜„ํ™ฉยท๊ณ ์žฅ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ˆ˜์น˜ ์—†์Œ)\n", "# ๊ฐ€์ƒ ๋ถ€๋Œ€ 6๊ฐœ(U1~U6)์˜ ์žฅ๋น„๊ฐ€ ์šด์šฉ ํ™˜๊ฒฝ(normal/rough/salt/cold)์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์†๋„๋กœ ๋…ธํ›„ํ™”ํ•œ๋‹ค. ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„(h_since)์ด\n", "# ์žฅ๋น„๋งˆ๋‹ค ์ˆจ์€ ์ˆ˜๋ช…(t_fail)์„ ๋„˜์œผ๋ฉด ๊ณ ์žฅ(event=1) ์ด๋ ฅ, ๋„˜์ง€ ์•Š์•˜์œผ๋ฉด ํ˜„์žฌ๊นŒ์ง€ ์ƒ์กด(event=0, ๊ด€์ธก ์ค‘๋„์ ˆ๋‹จ)์ด๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ENVS = [\"normal\", \"rough\", \"salt\", \"cold\"]\n", "ENV_W = {\"normal\": 1.00, \"rough\": 1.35, \"salt\": 1.25, \"cold\": 1.12} # ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜(์ˆจ์€ ์ •๋‹ต, ๊ฐ€์ •๊ฐ’): ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ์งง๋‹ค\n", "ENV_KO = {\"normal\": \"์ผ๋ฐ˜\", \"rough\": \"ํ—˜์ง€\", \"salt\": \"์—ผ๋ถ„\", \"cold\": \"ํ˜นํ•œ\"}\n", "UNITS = [\"U1\", \"U2\", \"U3\", \"U4\", \"U5\", \"U6\"]\n", "UNIT_ENV = {\"U1\": [.7, .1, .1, .1], \"U2\": [.2, .6, .1, .1], \"U3\": [.2, .1, .6, .1], \"U4\": [.3, .1, .1, .5], \"U5\": [.5, .3, .1, .1], \"U6\": [.4, .1, .4, .1]}\n", "ETYPES = [\"truck\", \"generator\", \"trailer\", \"radio\"]\n", "CYCLE = 8000.0 # ๊ทœ์ • ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ(์šด์šฉ์‹œ๊ฐ„ h, ๊ฐ€์ •๊ฐ’)\n", "KSHAPE = 12.0 # ์ˆ˜๋ช… ๋ถ„ํฌ ์™€์ด๋ธ” ํ˜•์ƒ(๋งˆ๋ชจํ˜• ๊ณ ์žฅ, ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ๋ชจ์—ฌ ์žˆ๋‹ค)\n", "INSP_NOISE = 0.025 # ์ •๋น„ ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ ์ธก์ • ์˜ค์ฐจ(๊ฐ€์ •๊ฐ’)\n", "\n", "\n", "def make_fleet(n=900, seed=0, env_w=None, shift=1.0):\n", " \"\"\"์žฅ๋น„ n๋Œ€. env_w๋กœ ์ˆจ์€ ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜๋ฅผ ๋ฐ”๊ฟ” ์šด์˜ ๋ถ„ํฌ ๋ณ€ํ™”๋ฅผ ๋ชจ์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. shift>1์ด๋ฉด ๊ฐ€๋™๋ฅ ์ด ๋†’์•„์ง„๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " w = env_w or ENV_W\n", " unit = rng.choice(UNITS, n)\n", " env = np.array([rng.choice(ENVS, p=UNIT_ENV[u]) for u in unit])\n", " et = rng.choice(ETYPES, n, p=[.4, .2, .2, .2])\n", " rate = rng.lognormal(np.log(3.0 * shift), 0.25, n) # ์ผ ํ‰๊ท  ์šด์šฉ์‹œ๊ฐ„(h/์ผ)\n", " h_since = rng.uniform(300, 11500, n) # ๋งˆ์ง€๋ง‰ ์ฐฝ์ •๋น„ ์ดํ›„ ์šด์šฉ์‹œ๊ฐ„\n", " n_ovh = rng.integers(0, 3, n)\n", " hours = h_since + n_ovh * rng.uniform(5000, 8000, n) # ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„\n", " km = hours * rng.normal(17, 3, n) * np.where(env == \"rough\", 0.8, 1.0) # ๋ˆ„์  ๊ธฐ๋™๊ฑฐ๋ฆฌ(km)\n", " age_y = np.clip(hours / (rate * 365) * rng.uniform(0.9, 1.15, n), 0.5, 25)\n", " unpl = rng.poisson(0.35 * h_since / 1000 * np.array([w[e] for e in env]) / 1.3) # ๋น„๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜(ํ™˜๊ฒฝ์ด ๊ฐ€ํ˜นํ• ์ˆ˜๋ก ๋งŽ๋‹ค)\n", " n_maint = rng.poisson(0.6 * h_since / 1000 + 1) # ๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜\n", " scale = 9600 / np.array([w[e] for e in env]) * 0.985 ** unpl * np.exp(rng.normal(0, 0.04, n))\n", " t_fail = scale * rng.weibull(KSHAPE, n) # ์ฐฝ์ •๋น„ ํ›„ ๊ณ ์žฅ๊นŒ์ง€ ์šด์šฉ์‹œ๊ฐ„(์ˆจ์€ ์ •๋‹ต)\n", " event = (t_fail <= h_since).astype(int)\n", " dur = np.where(event == 1, t_fail, h_since)\n", " df = pd.DataFrame({\"eq_id\": [f\"EQ{seed:02d}{i:04d}\" for i in range(n)], \"unit\": unit, \"env\": env, \"etype\": et, \"rate\": rate, \"hours\": hours, \"km\": km, \"age_y\": age_y, \"n_ovh\": n_ovh,\n", " \"h_since\": h_since, \"unpl\": unpl, \"n_maint\": n_maint, \"t_fail\": t_fail, \"event\": event, \"dur\": dur})\n", " df[\"insp\"] = np.clip(dur / t_fail + rng.normal(0, INSP_NOISE, n), 0, 1.3) # ๋งˆ์ง€๋ง‰ ์ •๋น„ ์ ๊ฒ€์—์„œ ์ธก์ •ํ•œ ๋งˆ๋ชจ ๋น„์œจ(์†Œ๋ชจ ์ˆ˜๋ช…/์ด ์ˆ˜๋ช…, ์˜ค์ฐจ ํฌํ•จ)\n", " df[\"rul_h\"] = np.where(event == 0, t_fail - h_since, np.nan) # ์ƒ์กด ์žฅ๋น„์˜ ๋‚จ์€ ์šด์šฉ์‹œ๊ฐ„(์ •๋‹ต)\n", " df[\"rul_d\"] = df[\"rul_h\"] / df[\"rate\"] # ๋‚จ์€ ์ผ์ˆ˜(์ •๋‹ต)\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " y, p = np.asarray(y, float), np.asarray(p, float)\n", " return float(np.mean(np.abs(y - p) / np.abs(y)) * 100)\n", "\n", "\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "\n", "\n", "def rmse(y, p):\n", " return float(np.sqrt(np.mean((np.asarray(y, float) - np.asarray(p, float)) ** 2)))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธํ›„ํ™” ํŒŒ์ƒ ํ”ผ์ฒ˜์™€ ํ•™์Šต ํ‘œ๋ณธ ๋งŒ๋“ค๊ธฐ. ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ W0๋Š” ๋„์ž… ์‹œ์ ์˜ ์ „๋ฌธ๊ฐ€ ์ถ”์ •๊ฐ’์ด๋ฉฐ(์‹ค์ œ ๊ฐ€์ค‘์น˜์™€ ๋‹ค๋ฆ„) M9์—์„œ ์žฌ๋ณด์ •ํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "W0 = {\"normal\": 1.00, \"rough\": 1.15, \"salt\": 1.10, \"cold\": 1.05}\n", "FEATS = [\"h_since\", \"hours\", \"km\", \"age_y\", \"rate\", \"n_maint\", \"unpl\", \"unpl_per_kh\", \"km_per_h\", \"n_ovh\", \"k_rough\", \"k_salt\", \"k_cold\", \"k_truck\", \"k_gen\", \"k_trailer\",\n", " \"aging_idx\", \"lin_rul_d\", \"hs_frac\", \"insp\", \"insp_eta_h\"]\n", "\n", "\n", "def add_features(df, w=None):\n", " d = df.copy()\n", " w = w or W0\n", " d[\"unpl_per_kh\"] = d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)\n", " d[\"km_per_h\"] = d[\"km\"] / d[\"hours\"]\n", " for e in (\"rough\", \"salt\", \"cold\"):\n", " d[\"k_\" + e] = (d[\"env\"] == e).astype(int)\n", " d[\"k_truck\"], d[\"k_gen\"], d[\"k_trailer\"] = (d[\"etype\"] == \"truck\").astype(int), (d[\"etype\"] == \"generator\").astype(int), (d[\"etype\"] == \"trailer\").astype(int)\n", " d[\"aging_idx\"] = d[\"h_since\"] * d[\"env\"].map(w) / CYCLE # ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜(1์ด๋ฉด ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ์— ๋„๋‹ฌ)\n", " d[\"lin_rul_d\"] = (CYCLE - d[\"h_since\"]) / d[\"rate\"] # ์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ์ž”์—ฌ ์ผ์ˆ˜(Baseline)\n", " d[\"hs_frac\"] = d[\"h_since\"] / CYCLE\n", " d[\"insp_eta_h\"] = d[\"h_since\"] / np.maximum(d[\"insp\"], 0.05) # ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ๋กœ ํ™˜์‚ฐํ•œ ์ด ์ˆ˜๋ช… ์ถ”์ •(h)\n", " return d\n", "\n", "\n", "def snapshots(df, k=4, seed=0):\n", " \"\"\"๊ณ ์žฅ ์ด๋ ฅ ์žฅ๋น„์˜ ๊ณผ๊ฑฐ ์‹œ์  ํ‘œ๋ณธ: ๊ณ ์žฅ ์ „ ์ž„์˜ ์‹œ์ ์˜ ์ ๊ฒ€๊ฐ’ยท์šด์šฉ์‹œ๊ฐ„๊ณผ ๊ทธ๋•Œ์˜ ๋‚จ์€ ์ผ์ˆ˜. (์ƒ์กด ์žฅ๋น„์˜ ๋ฏธ๋ž˜๋Š” ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๊ณ ์žฅ ์ด๋ ฅ๋งŒ ์“ด๋‹ค)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " f = df[df[\"event\"] == 1]\n", " out = []\n", " for _ in range(k):\n", " s = f.copy()\n", " h = s[\"t_fail\"] * rng.uniform(0.2, 0.98, len(s))\n", " ratio = h / s[\"h_since\"]\n", " s[\"hours\"] = s[\"hours\"] - (s[\"h_since\"] - h)\n", " s[\"km\"] = s[\"km\"] * ratio.clip(upper=1)\n", " s[\"unpl\"] = np.floor(s[\"unpl\"] * ratio)\n", " s[\"n_maint\"] = np.floor(s[\"n_maint\"] * ratio)\n", " s[\"insp\"] = np.clip(h / s[\"t_fail\"] + rng.normal(0, INSP_NOISE, len(s)), 0, 1.3)\n", " s[\"h_since\"] = h\n", " s[\"rul_d\"] = (s[\"t_fail\"] - h) / s[\"rate\"]\n", " s[\"event\"] = 0\n", " out.append(s)\n", " return pd.concat(out, ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ƒ์กด ๋ถ„์„ ๋„๊ตฌ(scipy๋งŒ ์‚ฌ์šฉ): ์™€์ด๋ธ” AFT(๊ฐ€์† ์ˆ˜๋ช…) ๋ชจํ˜•๊ณผ Cox ๋น„๋ก€์œ„ํ—˜ ๋ชจํ˜•\n", "# ---------------------------------------------------------------------------\n", "from scipy.optimize import minimize\n", "\n", "\n", "def aft_x(d):\n", " return np.c_[np.ones(len(d)), d[\"env\"].eq(\"rough\"), d[\"env\"].eq(\"salt\"), d[\"env\"].eq(\"cold\"), d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)].astype(float)\n", "\n", "\n", "AFT_COLS = [\"์ ˆํŽธ\", \"ํ—˜์ง€\", \"์—ผ๋ถ„\", \"ํ˜นํ•œ\", \"๋น„๊ณ„ํš ์ •๋น„์œจ\"]\n", "\n", "\n", "def fit_aft(d):\n", " \"\"\"์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„ dur(๊ณ ์žฅ์ด๋ฉด event=1, ์ƒ์กด์ด๋ฉด ์ค‘๋„์ ˆ๋‹จ)์— ๋Œ€ํ•œ ์™€์ด๋ธ” AFT ์ตœ๋Œ€์šฐ๋„ ์ถ”์ •. ๋ฐ˜ํ™˜: ๊ณ„์ˆ˜(๋งˆ์ง€๋ง‰์€ log ํ˜•์ƒ).\"\"\"\n", " x, t, e = aft_x(d), d[\"dur\"].values, d[\"event\"].values\n", "\n", " def nll(p):\n", " lam, k = np.exp(x @ p[:-1]), np.exp(p[-1])\n", " z = (t / lam) ** k\n", " return -(e * (np.log(k) - np.log(lam) + (k - 1) * (np.log(t) - np.log(lam))) - z).sum()\n", "\n", " r = minimize(nll, np.r_[np.log(8000), np.zeros(x.shape[1] - 1), np.log(5)], method=\"BFGS\")\n", " return r.x\n", "\n", "\n", "def aft_pred(p, d, q=0.5, h=None):\n", " \"\"\"์ด๋ฏธ h(์šด์šฉ์‹œ๊ฐ„)๊นŒ์ง€ ์ƒ์กดํ–ˆ๋‹ค๋Š” ์กฐ๊ฑด์—์„œ ๋ถ„์œ„์ˆ˜ q ์ƒ์กด ์‹œ์ (์šด์šฉ์‹œ๊ฐ„ h ๊ธฐ์ค€ ์ด ์ˆ˜๋ช…)์„ ๋Œ๋ ค์ค€๋‹ค. q=0.5๊ฐ€ ์กฐ๊ฑด๋ถ€ ์ค‘์•™๊ฐ’.\"\"\"\n", " lam, k = np.exp(aft_x(d) @ p[:-1]), np.exp(p[-1])\n", " h = d[\"h_since\"].values if h is None else h\n", " return lam * ((h / lam) ** k - np.log(q)) ** (1 / k)\n", "\n", "\n", "def aft_fail_prob(p, d, h0, h1):\n", " \"\"\"h0๊นŒ์ง€ ์ƒ์กดํ•œ ์žฅ๋น„๊ฐ€ h0~h1 ์‚ฌ์ด์— ๊ณ ์žฅ ๋‚  ์กฐ๊ฑด๋ถ€ ํ™•๋ฅ .\"\"\"\n", " lam, k = np.exp(aft_x(d) @ p[:-1]), np.exp(p[-1])\n", " s0, s1 = np.exp(-(h0 / lam) ** k), np.exp(-(h1 / lam) ** k)\n", " return 1 - s1 / s0\n", "\n", "\n", "def fit_cox(d, iters=25):\n", " \"\"\"Cox ๋น„๋ก€์œ„ํ—˜(Breslow ๊ทผ์‚ฌ) ๋‰ดํ„ด-๋žฉ์Šจ. ๊ณต๋ณ€๋Ÿ‰: ํ—˜์ง€ยท์—ผ๋ถ„ยทํ˜นํ•œยท๋น„๊ณ„ํš ์ •๋น„์œจ. ๋ฐ˜ํ™˜: ๊ณ„์ˆ˜.\"\"\"\n", " x = aft_x(d)[:, 1:]\n", " o = np.argsort(-d[\"dur\"].values)\n", " x, e = x[o], d[\"event\"].values[o]\n", " b = np.zeros(x.shape[1])\n", " for _ in range(iters):\n", " r = np.exp(x @ b)\n", " S0 = np.cumsum(r)\n", " S1 = np.cumsum(r[:, None] * x, axis=0)\n", " S2 = np.cumsum(r[:, None, None] * x[:, :, None] * x[:, None, :], axis=0)\n", " m1 = S1 / S0[:, None]\n", " g = ((x - m1) * e[:, None]).sum(axis=0)\n", " H = -sum(e[i] * (S2[i] / S0[i] - np.outer(m1[i], m1[i])) for i in np.where(e == 1)[0])\n", " step = np.linalg.solve(H - 1e-6 * np.eye(len(b)), g)\n", " b = b - step\n", " if np.abs(step).max() < 1e-7:\n", " break\n", " return b\n", "\n", "\n", "def cindex(time, event, risk):\n", " \"\"\"์ผ์น˜๋„ C-index: ์œ„ํ—˜ ์ ์ˆ˜๊ฐ€ ๋†’์€ ์ชฝ์ด ๋จผ์ € ๊ณ ์žฅ ๋‚ฌ๋Š”๊ฐ€(๊ด€์ธก ๊ฐ€๋Šฅํ•œ ์Œ๋งŒ).\"\"\"\n", " t, e, r = np.asarray(time), np.asarray(event), np.asarray(risk)\n", " num = den = 0.0\n", " for i in np.where(e == 1)[0]:\n", " m = t > t[i]\n", " den += m.sum()\n", " num += (r[m] < r[i]).sum() + 0.5 * (r[m] == r[i]).sum()\n", " return num / max(den, 1)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ž”์—ฌ ์ˆ˜๋ช…(RUL, ์ผ) ์˜ˆ์ธก ์—”์ง„: ์™€์ด๋ธ” AFT ์กฐ๊ฑด๋ถ€ ์ค‘์•™๊ฐ’ + ๊ทธ๋ž˜๋””์–ธํŠธ ๋ถ€์ŠคํŒ…(๊ณ ์žฅ ์ด๋ ฅ ์‹œ์  ํ‘œ๋ณธ ํ•™์Šต)์˜ ํ‰๊ท \n", "# ---------------------------------------------------------------------------\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "\n", "\n", "def fit_engine(train, seed=0):\n", " p = fit_aft(train)\n", " sn = add_features(snapshots(train, 5, seed))\n", " sn[\"aft\"] = (aft_pred(p, sn) - sn[\"h_since\"]) / sn[\"rate\"]\n", " g = GradientBoostingRegressor(n_estimators=250, max_depth=3, learning_rate=0.06, random_state=0).fit(sn[FEATS + [\"aft\"]], sn[\"rul_d\"])\n", " return {\"p\": p, \"g\": g}\n", "\n", "\n", "def predict_rul(eng, d):\n", " \"\"\"d๋Š” add_features๋ฅผ ๊ฑฐ์นœ ํ‘œ. ๋ฐ˜ํ™˜: (์•™์ƒ๋ธ” RUL, AFT RUL, GBR RUL) - ์ผ ๋‹จ์œ„.\"\"\"\n", " aft = ((aft_pred(eng[\"p\"], d) - d[\"h_since\"]) / d[\"rate\"]).values\n", " x = d[FEATS].copy()\n", " x[\"aft\"] = aft\n", " gb = eng[\"g\"].predict(x)\n", " return np.clip(0.5 * aft + 0.5 * gb, 1, None), aft, gb\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๊ณ„ํš์ •๋น„(์ฐฝ์ •๋น„) ์ผ์ • ์ถ”์ฒœ: ์›”๋ณ„ ์ •๋น„์ฐฝ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐยท์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ(์œ ํšจ๊ธฐํ•œ) ์ œ์•ฝ ์•„๋ž˜ ๊ธฐ๋Œ€ ๋น„์šฉ ์ตœ์†Œํ™” (๋น„์šฉ ๋‹จ๊ฐ€๋Š” ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’, ๋งŒ์›)\n", "# ---------------------------------------------------------------------------\n", "from scipy.optimize import milp, LinearConstraint, Bounds\n", "from scipy.stats import norm\n", "\n", "C_PLAN, C_FAIL, C_WASTE_KH, C_KIT = 600.0, 2400.0, 40.0, 120.0 # ๊ณ„ํš ์ฐฝ์ •๋น„, ๋น„๊ณ„ํš ๊ณ ์žฅ(๊ธด๊ธ‰ ์ •๋น„ ํฌํ•จ), ๋ฏธ์‚ฌ์šฉ ์ˆ˜๋ช… 1000h๋‹น ์†์‹ค, ์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ\n", "HORIZON, MDAYS = 12, 30 # ๊ณ„ํš 12๊ฐœ์›”, ์›” 30์ผ\n", "CAP_M, BUDGET_M = 22, 15000.0 # ์›”๋ณ„ ์ •๋น„์ฐฝ ๋Šฅ๋ ฅ(๋Œ€), ์›”๋ณ„ ์˜ˆ์‚ฐ\n", "LOTS = [(30, 70), (45, 170), (45, 390), (70, 800)] # ์‹œํšจ์„ฑ ํ‚คํŠธ ๋กœํŠธ(์ˆ˜๋Ÿ‰, ์œ ํšจ๊ธฐํ•œ๊นŒ์ง€ ๋‚จ์€ ์ผ์ˆ˜)\n", "KIT_MARGIN = 14 # ์ •๋น„์ผ ๊ธฐ์ค€ ์ตœ์†Œ ์œ ํšจ๊ธฐ๊ฐ„ ์—ฌ์œ (์ผ)\n", "\n", "\n", "def exp_cost_matrix(pred, rate, month_days):\n", " \"\"\"์žฅ๋น„ i๋ฅผ ์›” m์— ์ •๋น„ํ•  ๋•Œ์˜ ๊ธฐ๋Œ€ ๋น„์šฉ. ์ฐธ RUL ~ N(pred, sigma), sigma = 35์ผ + 0.12 x pred (๊ฐ€์ •). ๋งˆ์ง€๋ง‰ ์—ด์€ ๊ณ„ํš ๊ธฐ๊ฐ„ ๋ฐ–(๋ฏธ์ •๋น„).\"\"\"\n", " sig = 35 + 0.12 * pred\n", " C = np.zeros((len(pred), len(month_days) + 1))\n", " for j, day in enumerate(month_days):\n", " z = (day - pred) / sig\n", " pf = norm.cdf(z)\n", " unused = np.maximum(pred - day, 0) * rate * 0.6 # ์ •๋น„๋ฅผ ์•ž๋‹น๊ธด ๋งŒํผ ๋ฒ„๋ฆฌ๋Š” ์šด์šฉ์‹œ๊ฐ„ h(๊ทผ์‚ฌ)\n", " C[:, j] = C_PLAN + C_KIT + pf * (C_FAIL - C_PLAN) + C_WASTE_KH * unused / 1000 * (1 - pf)\n", " C[:, -1] = norm.cdf((HORIZON * MDAYS - pred) / sig) * C_FAIL\n", " return C\n", "\n", "\n", "def plan_milp(pred, rate, cap=CAP_M, budget=BUDGET_M, lots=LOTS, use_kit=True):\n", " \"\"\"์ •์ˆ˜๊ณ„ํš์œผ๋กœ ์žฅ๋น„๋ณ„ ์ •๋น„ ์›”(0~11) ๋˜๋Š” ๋ฏธ์ •๋น„(-1)๋ฅผ ๋ฐฐ์ •ํ•œ๋‹ค. ํ›„๋ณด๋Š” ์˜ˆ์ธก RUL์ด ๊ณ„ํš ๊ธฐ๊ฐ„ +90์ผ ์ด๋‚ด์ธ ์žฅ๋น„.\"\"\"\n", " md = np.arange(1, HORIZON + 1) * MDAYS\n", " cand = np.where(pred < HORIZON * MDAYS + 90)[0]\n", " n, M = len(cand), HORIZON\n", " C = exp_cost_matrix(pred[cand], rate[cand], md)\n", " c = C.ravel()\n", " A, lo, hi = [], [], []\n", " for i in range(n): # ์žฅ๋น„๋‹น ํ•œ ๋ฒˆ๋งŒ ๋ฐฐ์ •(์›” 12์นธ + ๋ฏธ์ •๋น„)\n", " row = np.zeros(n * (M + 1)); row[i * (M + 1):(i + 1) * (M + 1)] = 1; A.append(row); lo.append(1); hi.append(1)\n", " for m in range(M): # ์›”๋ณ„ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐ\n", " row = np.zeros(n * (M + 1)); row[m::(M + 1)] = 1\n", " A.append(row); lo.append(0); hi.append(cap)\n", " A.append(row * (C_PLAN + C_KIT)); lo.append(0); hi.append(budget)\n", " if use_kit: # ์‹œํšจ์„ฑ ํ‚คํŠธ: m์›” ์ดํ›„ ์ •๋น„ ์ˆ˜์š”๋Š” ๊ทธ ์‹œ์ ์— ์œ ํšจ๊ธฐ๊ฐ„์ด ๋‚จ์€ ๋กœํŠธ ์ˆ˜๋Ÿ‰์„ ๋„˜์„ ์ˆ˜ ์—†๋‹ค\n", " for m in range(M):\n", " sup = sum(q for q, e in lots if e >= md[m] + KIT_MARGIN)\n", " row = np.zeros(n * (M + 1))\n", " for mm in range(m, M):\n", " row[mm::(M + 1)] = 1\n", " A.append(row); lo.append(0); hi.append(sup)\n", " r = milp(c, constraints=LinearConstraint(np.array(A), lo, hi), integrality=np.ones(len(c)), bounds=Bounds(0, 1), options={\"time_limit\": 20})\n", " month = np.full(len(pred), -1)\n", " if r.x is not None:\n", " x = np.round(r.x).reshape(n, M + 1)\n", " assign = np.where(x[:, :M].sum(axis=1) > 0.5, x[:, :M].argmax(axis=1), -1)\n", " month[cand] = assign\n", " return month\n", "\n", "\n", "def plan_greedy(pred, margin=45, cap=CAP_M, budget=BUDGET_M):\n", " \"\"\"๊ฐ„๋‹จํ•œ ํƒ์š• ๋ฐฐ์ •: ์ •๋น„ ๋ชฉํ‘œ์ผ(์˜ˆ์ธก RUL - margin)์ด ๋น ๋ฅธ ์ˆœ์œผ๋กœ ๋ชฉํ‘œ์ผ ์ด์ „ ๊ฐ€์žฅ ๋Šฆ์€ ๋นˆ ์›”, ์—†์œผ๋ฉด ๊ฐ€์žฅ ์ด๋ฅธ ๋นˆ ์›”์— ๋ฐฐ์ •.\"\"\"\n", " cap = min(cap, int(budget // (C_PLAN + C_KIT))) # ์›” ์˜ˆ์‚ฐ์ด ํ—ˆ์šฉํ•˜๋Š” ๋Œ€์ˆ˜๋กœ ๋Šฅ๋ ฅ์„ ์ œํ•œ\n", " month = np.full(len(pred), -1)\n", " left = np.full(HORIZON, cap)\n", " for i in np.argsort(pred):\n", " if pred[i] > HORIZON * MDAYS + 60: break\n", " tgt = int(np.clip((pred[i] - margin) // MDAYS - 1, 0, HORIZON - 1))\n", " for m in list(range(tgt, -1, -1)) + list(range(tgt + 1, HORIZON)):\n", " if left[m] > 0:\n", " month[i] = m; left[m] -= 1; break\n", " return month\n", "\n", "\n", "def plan_fixed(lin_rul, cap=CAP_M, budget=BUDGET_M):\n", " \"\"\"๊ด€ํ–‰: ๊ทœ์ • ์ฃผ๊ธฐ ๋„๋‹ฌ ์ˆœ์„œ๋Œ€๋กœ(์„ ํ˜• ์ž”์—ฌ ์ผ์ˆ˜) ์›”๋ณ„ ๋Šฅ๋ ฅ ์•ˆ์—์„œ ๋ฐฐ์ •.\"\"\"\n", " return plan_greedy(np.asarray(lin_rul, float), margin=0, cap=cap, budget=budget)\n", "\n", "\n", "def evaluate_plan(month, true_rul, rate, cost_kit=True):\n", " \"\"\"์ฐธ RUL๋กœ ๊ณ„ํš์˜ ์‹ค์ œ ๋น„์šฉ์„ ๊ณ„์‚ฐํ•œ๋‹ค: ์ •๋น„์ผ ์ด์ „์— ๊ณ ์žฅ -> ๋น„๊ณ„ํš ๊ณ ์žฅ ๋น„์šฉ, ์•„๋‹ˆ๋ฉด ๊ณ„ํš ๋น„์šฉ + ๋ฏธ์‚ฌ์šฉ ์ˆ˜๋ช… ์†์‹ค.\"\"\"\n", " day = np.where(month >= 0, (month + 1) * MDAYS, 10 ** 9)\n", " fail = (true_rul < day) & (true_rul < HORIZON * MDAYS)\n", " planned = (month >= 0) & ~fail\n", " waste = np.where(planned, (true_rul - day) * rate * 0.6, 0) * C_WASTE_KH / 1000\n", " cost = fail * C_FAIL + planned * (C_PLAN + (C_KIT if cost_kit else 0)) + waste\n", " return {\"total\": float(cost.sum()), \"fail\": int(fail.sum()), \"planned\": int(planned.sum()), \"waste\": float(waste.sum()), \"unsched_fail\": int(((month < 0) & fail).sum())}\n", "\n", "\n", "def kit_fefo(month, lots=LOTS):\n", " \"\"\"์œ ํšจ๊ธฐํ•œ์ด ๋น ๋ฅธ ๋กœํŠธ๋ถ€ํ„ฐ ์“ฐ๋Š” FEFO๋กœ ํ‚คํŠธ๋ฅผ ๋ฐฐ์ •ํ•˜๊ณ  ์ •๋น„์ผ์— ์œ ํšจ๊ธฐ๊ฐ„์ด ๋ถ€์กฑํ•œ ๋ฐฐ์ • ์ˆ˜์™€ ๋งŒ๋ฃŒ ํ๊ธฐ ์ˆ˜๋Ÿ‰์„ ์„ผ๋‹ค.\"\"\"\n", " q = [[qty, e] for qty, e in sorted(lots, key=lambda t: t[1])]\n", " short = 0\n", " for m in sorted(month[month >= 0]):\n", " day = (m + 1) * MDAYS\n", " for lot in q:\n", " if lot[1] >= day + KIT_MARGIN and lot[0] > 0:\n", " lot[0] -= 1; break\n", " else:\n", " short += 1\n", " expired = sum(l[0] for l in q if l[1] < HORIZON * MDAYS)\n", " return {\"short\": short, \"expired_unused\": expired, \"left\": sum(l[0] for l in q)}\n", "# ---------------------------------------------------------------------------\n", "\n", "tr = make_fleet(900, seed=1)\n", "eng = fit_engine(tr)\n", "fl = add_features(make_fleet(900, seed=3))\n", "al = fl[fl.event == 0].reset_index(drop=True)\n", "al[\"rul_pred\"], _, _ = predict_rul(eng, al)\n", "al[\"month\"] = plan_milp(al[\"rul_pred\"].values, al[\"rate\"].values)\n", "al[\"p90\"] = aft_fail_prob(eng[\"p\"], al, al[\"h_since\"].values, al[\"h_since\"].values + 90 * al[\"rate\"].values)\n", "\n", "# ---------------- 1) ๋ถ€๋Œ€๋ณ„ ๋…ธํ›„๋„ ํ˜„ํ™ฉ ----------------\n", "g = al.groupby(\"unit\")\n", "board = pd.DataFrame({\"์žฅ๋น„ ์ˆ˜\": g.size(), \"ํ‰๊ท  ๋…ธํ›„ ์ง€์ˆ˜\": g[\"aging_idx\"].mean(), \"ํ‰๊ท  ์˜ˆ์ธก RUL(์ผ)\": g[\"rul_pred\"].mean(), \"180์ผ ๋‚ด ๊ณ ์œ„ํ—˜\": g.apply(lambda x: int((x.rul_pred < 180).sum())),\n", " \"ํ—˜์ง€ยท์—ผ๋ถ„ ๋น„์œจ\": g.apply(lambda x: x.env.isin([\"rough\", \"salt\"]).mean()), \"12๊ฐœ์›” ๋‚ด ๊ถŒ์žฅ ์ •๋น„\": g.apply(lambda x: int((x.month >= 0).sum()))})\n", "board[\"๊ณ ์œ„ํ—˜ ๋น„์œจ\"] = board[\"180์ผ ๋‚ด ๊ณ ์œ„ํ—˜\"] / board[\"์žฅ๋น„ ์ˆ˜\"]\n", "print(f\"[1] ๋ถ€๋Œ€๋ณ„ ๋…ธํ›„๋„ ํ˜„ํ™ฉ(์ƒ์กด ์žฅ๋น„ {len(al)}๋Œ€, ๊ฐ€์ƒ ๋ถ€๋Œ€ 6๊ฐœ) [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\")\n", "print(board.round(2).to_string())\n", "worst = board[\"๊ณ ์œ„ํ—˜ ๋น„์œจ\"].idxmax()\n", "print(f\" ๊ณ ์œ„ํ—˜ ๋น„์œจ์ด ๊ฐ€์žฅ ๋†’์€ ๋ถ€๋Œ€ {worst}({board.loc[worst, '๊ณ ์œ„ํ—˜ ๋น„์œจ']:.0%}), ํ™˜๊ฒฝ ๊ตฌ์„ฑ(ํ—˜์ง€ยท์—ผ๋ถ„ {board.loc[worst, 'ํ—˜์ง€ยท์—ผ๋ถ„ ๋น„์œจ']:.0%})๊ณผ ํ•จ๊ป˜ ๋ณธ๋‹ค.\")\n", "\n", "# ---------------- 2) ๊ถŒ์žฅ ์ •๋น„ ์บ˜๋ฆฐ๋” ----------------\n", "cal = pd.crosstab(al.loc[al.month >= 0, \"unit\"], al.loc[al.month >= 0, \"month\"] + 1).reindex(columns=range(1, HORIZON + 1), fill_value=0)\n", "print(\"\\n[2] ๊ถŒ์žฅ ์ •๋น„ ์บ˜๋ฆฐ๋”(๋ถ€๋Œ€ x ์›”, ๋Œ€์ˆ˜; ์›” ์˜ˆ์‚ฐยท๋Šฅ๋ ฅยทํ‚คํŠธ ์œ ํšจ๊ธฐ๊ฐ„ ๋ฐ˜์˜)\")\n", "print(cal.to_string())\n", "print(f\" ์›” ํ•ฉ๊ณ„: \" + \" \".join(f\"{m}์›” {int(c)}\" for m, c in cal.sum().items()) + f\" / ์ •๋น„ ์ผ์ •์ด ์žกํžˆ์ง€ ์•Š์€ ๊ณ ์œ„ํ—˜(RUL 180์ผ ๋ฏธ๋งŒ) ์žฅ๋น„ {int(((al.month < 0) & (al.rul_pred < 180)).sum())}๋Œ€\")\n", "top = al.sort_values(\"rul_pred\").head(8)\n", "print(\"\\n ์œ„ํ—˜ ์ƒ์œ„ 8๋Œ€ [๊ถŒ์žฅ ์›”์€ ์Šน์ธ ์ „ ์ถ”์ฒœ์•ˆ]\")\n", "print(pd.DataFrame({\"์žฅ๋น„\": top.eq_id, \"๋ถ€๋Œ€\": top.unit, \"ํ™˜๊ฒฝ\": top.env.map(ENV_KO), \"์˜ˆ์ธก RUL(์ผ)\": top.rul_pred, \"90์ผ ๋‚ด ๊ณ ์žฅ ํ™•๋ฅ \": top.p90, \"๊ถŒ์žฅ ์›”\": top.month.map(lambda m: f\"{m + 1}์›”\" if m >= 0 else \"-\")}).round(2).to_string(index=False))\n", "\n", "# ---------------- 3) ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ(๋ณ€์ˆ˜๊ตฐ ํ‰๊ท  ๋Œ€์ฒด) ----------------\n", "GROUPS = {\"์šด์šฉ ์ด๋ ฅ(๊ฒฝ๊ณผ ์‹œ๊ฐ„ยท๊ฑฐ๋ฆฌยท์—ฐ์ˆ˜)\": [\"h_since\", \"hours\", \"km\", \"age_y\"], \"์šด์šฉ ํ™˜๊ฒฝ\": [\"env\"], \"์ •๋น„ ์ด๋ ฅ(๋น„๊ณ„ํšยท๊ณ„ํš ์ •๋น„)\": [\"unpl\", \"n_maint\"], \"์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ\": [\"insp\"], \"์ผ ๊ฐ€๋™๋ฅ \": [\"rate\"]}\n", "MEAN = tr[[\"h_since\", \"hours\", \"km\", \"age_y\", \"unpl\", \"n_maint\", \"insp\", \"rate\"]].mean()\n", "raw_cols = [\"eq_id\", \"unit\", \"env\", \"etype\", \"rate\", \"hours\", \"km\", \"age_y\", \"n_ovh\", \"h_since\", \"unpl\", \"n_maint\", \"insp\"]\n", "base_raw = al[raw_cols].copy()\n", "full = al[\"rul_pred\"].values\n", "contrib = {}\n", "for gname, cols in GROUPS.items():\n", " x = base_raw.copy()\n", " for c in cols:\n", " x[c] = \"normal\" if c == \"env\" else MEAN[c]\n", " xf = add_features(x.assign(event=0, t_fail=0.0, dur=x[\"h_since\"]))\n", " contrib[gname] = full - predict_rul(eng, xf)[0] # ์Œ์ˆ˜ = ์ด ์š”์ธ ๋•Œ๋ฌธ์— ํ‰๊ท  ์žฅ๋น„๋ณด๋‹ค ์ž”์—ฌ ์ˆ˜๋ช…์ด ์งง๋‹ค\n", "C = pd.DataFrame(contrib)\n", "t3 = C.apply(lambda r: r.reindex(r.abs().sort_values(ascending=False).index)[:3], axis=1)\n", "okn = int(sum(t3.iloc[i].notna().sum() == 3 and np.isfinite(t3.iloc[i].dropna().values).all() for i in range(len(t3))))\n", "print(f\"\\n[3] ์žฅ๋น„ {len(al)}๋Œ€ ์ค‘ ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์š”์ธ ์ž๋™ ์‚ฐ์ถœ {okn}๋Œ€ ({okn / len(al):.0%}, ๋ชฉํ‘œ 100%)\")\n", "print(\" ์š”์ธ๊ตฐ๋ณ„ ํ‰๊ท  ๊ธฐ์—ฌ ํฌ๊ธฐ(์ผ):\", \", \".join(f\"{k} {v:.0f}\" for k, v in C.abs().mean().sort_values(ascending=False).items()))\n", "\n", "\n", "def explain(i):\n", " r, row = al.loc[i], C.loc[i]\n", " ordered = row.reindex(row.abs().sort_values(ascending=False).index)[:3]\n", " main = ordered.index[0]\n", " why = {\"์šด์šฉ ์ด๋ ฅ(๊ฒฝ๊ณผ ์‹œ๊ฐ„ยท๊ฑฐ๋ฆฌยท์—ฐ์ˆ˜)\": \"์ฐฝ์ •๋น„ ์ดํ›„ ์šด์šฉ ์‹œ๊ฐ„์ด ๊ธธ์–ด\", \"์šด์šฉ ํ™˜๊ฒฝ\": f\"{ENV_KO[r.env]} ํ™˜๊ฒฝ ๊ฐ€ํ˜น๋„ ๋•Œ๋ฌธ์—\", \"์ •๋น„ ์ด๋ ฅ(๋น„๊ณ„ํšยท๊ณ„ํš ์ •๋น„)\": \"๋น„๊ณ„ํš ์ •๋น„๊ฐ€ ์žฆ์•„\", \"์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ\": \"์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ์ด ๋†’์•„\", \"์ผ ๊ฐ€๋™๋ฅ \": \"์ผ ๊ฐ€๋™๋ฅ ์ด ๋†’์•„\"}[main]\n", " return f\"{r.eq_id}({r.unit}, {ENV_KO[r.env]}): {why} ์ž”์—ฌ ์ˆ˜๋ช… {r.rul_pred:.0f}์ผ ์˜ˆ์ธก. ์ฃผ์š” ์š”์ธ \" + \", \".join(f\"{k.split('(')[0]}({v:+.0f}์ผ)\" for k, v in ordered.items())\n", "\n", "\n", "print(\"[4] ์œ„ํ—˜ ์ƒ์œ„ 3๋Œ€ ์ž์—ฐ์–ด ์„ค๋ช…(์Œ์ˆ˜ = ํ‰๊ท  ์žฅ๋น„๋ณด๋‹ค ์ž”์—ฌ ์ˆ˜๋ช…์ด ์งง์•„์ง€๋Š” ์š”์ธ)\")\n", "for i in top.index[:3]:\n", " print(\" -\", explain(i))\n", "print(\" ๋Œ€์‹œ๋ณด๋“œ์™€ ์„ค๋ช…์€ ์ •๋น„๊ณ„ํš๊ด€์˜ ํŒ๋‹จ ๋ณด์กฐ์ด๋ฉฐ ์ผ์ • ํ™•์ •๊ณผ ์žฅ๋น„ ๋„ํƒœ ๊ฑด์˜๋Š” ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ(M7)์— ๋”ฐ๋ฅธ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(2, 2, figsize=(13, 8))\n", "ax[0, 0].bar(board.index, board[\"๊ณ ์œ„ํ—˜ ๋น„์œจ\"] * 100); ax[0, 0].set_title(\"1 High-risk share by unit (%, RUL < 180 days)\")\n", "im = ax[0, 1].imshow(cal.values, aspect=\"auto\", cmap=\"YlOrRd\"); ax[0, 1].set_xticks(range(HORIZON)); ax[0, 1].set_xticklabels(range(1, HORIZON + 1)); ax[0, 1].set_yticks(range(len(cal))); ax[0, 1].set_yticklabels(cal.index)\n", "ax[0, 1].set_title(\"2 Recommended overhaul calendar (unit x month)\"); plt.colorbar(im, ax=ax[0, 1])\n", "ax[1, 0].bar(board.index, board[\"ํ‰๊ท  ๋…ธํ›„ ์ง€์ˆ˜\"]); ax[1, 0].axhline(1, c=\"r\", ls=\"--\"); ax[1, 0].set_title(\"3 Mean aging index by unit\")\n", "r0 = C.loc[top.index[0]]\n", "ax[1, 1].barh([\"usage history\", \"environment\", \"maintenance history\", \"inspection wear\", \"daily usage\"][::-1], r0.values[::-1]); ax[1, 1].set_title(\"4 Contribution to RUL, riskiest unit (days)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 06 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ๋ถ€๋Œ€๋ณ„ ๋…ธํ›„๋„ ํ˜„ํ™ฉ(์ƒ์กด ์žฅ๋น„ 601๋Œ€, ๊ฐ€์ƒ ๋ถ€๋Œ€ 6๊ฐœ) [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\n", " ์žฅ๋น„ ์ˆ˜ ํ‰๊ท  ๋…ธํ›„ ์ง€์ˆ˜ ํ‰๊ท  ์˜ˆ์ธก RUL(์ผ) 180์ผ ๋‚ด ๊ณ ์œ„ํ—˜ ํ—˜์ง€ยท์—ผ๋ถ„ ๋น„์œจ 12๊ฐœ์›” ๋‚ด ๊ถŒ์žฅ ์ •๋น„ ๊ณ ์œ„ํ—˜ ๋น„์œจ\n", "unit \n", "U1 110 0.58 1267.69 5 0.13 21 0.05\n", "U2 87 0.50 1340.48 5 0.64 11 0.06\n", "U3 101 0.53 1200.60 4 0.63 15 0.04\n", "U4 110 0.57 1259.99 7 0.13 24 0.06\n", "U5 106 0.58 1287.43 6 0.31 18 0.06\n", "U6 87 0.51 1370.82 5 0.48 12 0.06\n", " ๊ณ ์œ„ํ—˜ ๋น„์œจ์ด ๊ฐ€์žฅ ๋†’์€ ๋ถ€๋Œ€ U4(6%), ํ™˜๊ฒฝ ๊ตฌ์„ฑ(ํ—˜์ง€ยท์—ผ๋ถ„ 13%)๊ณผ ํ•จ๊ป˜ ๋ณธ๋‹ค.\n", "\n", "[2] ๊ถŒ์žฅ ์ •๋น„ ์บ˜๋ฆฐ๋”(๋ถ€๋Œ€ x ์›”, ๋Œ€์ˆ˜; ์›” ์˜ˆ์‚ฐยท๋Šฅ๋ ฅยทํ‚คํŠธ ์œ ํšจ๊ธฐ๊ฐ„ ๋ฐ˜์˜)\n", "month 1 2 3 4 5 6 7 8 9 10 11 12\n", "unit \n", "U1 1 8 2 6 4 0 0 0 0 0 0 0\n", "U2 3 2 3 1 2 0 0 0 0 0 0 0\n", "U3 4 1 3 4 3 0 0 0 0 0 0 0\n", "U4 4 4 7 3 5 1 0 0 0 0 0 0\n", "U5 6 1 3 4 4 0 0 0 0 0 0 0\n", "U6 2 4 2 2 2 0 0 0 0 0 0 0\n", " ์›” ํ•ฉ๊ณ„: 1์›” 20 2์›” 20 3์›” 20 4์›” 20 5์›” 20 6์›” 1 7์›” 0 8์›” 0 9์›” 0 10์›” 0 11์›” 0 12์›” 0 / ์ •๋น„ ์ผ์ •์ด ์žกํžˆ์ง€ ์•Š์€ ๊ณ ์œ„ํ—˜(RUL 180์ผ ๋ฏธ๋งŒ) ์žฅ๋น„ 0๋Œ€\n", "\n", " ์œ„ํ—˜ ์ƒ์œ„ 8๋Œ€ [๊ถŒ์žฅ ์›”์€ ์Šน์ธ ์ „ ์ถ”์ฒœ์•ˆ]\n", " ์žฅ๋น„ ๋ถ€๋Œ€ ํ™˜๊ฒฝ ์˜ˆ์ธก RUL(์ผ) 90์ผ ๋‚ด ๊ณ ์žฅ ํ™•๋ฅ  ๊ถŒ์žฅ ์›”\n", "EQ030723 U6 ์—ผ๋ถ„ 78.41 0.49 1์›”\n", "EQ030395 U2 ํ—˜์ง€ 83.07 0.72 1์›”\n", "EQ030208 U5 ํ˜นํ•œ 88.97 0.73 1์›”\n", "EQ030501 U4 ์ผ๋ฐ˜ 90.25 0.43 1์›”\n", "EQ030658 U3 ํ˜นํ•œ 98.04 0.49 1์›”\n", "EQ030648 U2 ํ—˜์ง€ 98.17 0.41 1์›”\n", "EQ030142 U4 ์ผ๋ฐ˜ 107.32 0.66 1์›”\n", "EQ030652 U3 ํ˜นํ•œ 107.97 0.34 1์›”\n", "\n", "[3] ์žฅ๋น„ 601๋Œ€ ์ค‘ ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์š”์ธ ์ž๋™ ์‚ฐ์ถœ 601๋Œ€ (100%, ๋ชฉํ‘œ 100%)\n", " ์š”์ธ๊ตฐ๋ณ„ ํ‰๊ท  ๊ธฐ์—ฌ ํฌ๊ธฐ(์ผ): ์šด์šฉ ์ด๋ ฅ(๊ฒฝ๊ณผ ์‹œ๊ฐ„ยท๊ฑฐ๋ฆฌยท์—ฐ์ˆ˜) 487, ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ 344, ์ผ ๊ฐ€๋™๋ฅ  260, ์šด์šฉ ํ™˜๊ฒฝ 207, ์ •๋น„ ์ด๋ ฅ(๋น„๊ณ„ํšยท๊ณ„ํš ์ •๋น„) 94\n", "[4] ์œ„ํ—˜ ์ƒ์œ„ 3๋Œ€ ์ž์—ฐ์–ด ์„ค๋ช…(์Œ์ˆ˜ = ํ‰๊ท  ์žฅ๋น„๋ณด๋‹ค ์ž”์—ฌ ์ˆ˜๋ช…์ด ์งง์•„์ง€๋Š” ์š”์ธ)\n", " - EQ030723(U6, ์—ผ๋ถ„): ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ์ด ๋†’์•„ ์ž”์—ฌ ์ˆ˜๋ช… 78์ผ ์˜ˆ์ธก. ์ฃผ์š” ์š”์ธ ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ(-286์ผ), ์šด์šฉ ํ™˜๊ฒฝ(-172์ผ), ์šด์šฉ ์ด๋ ฅ(-128์ผ)\n", " - EQ030395(U2, ํ—˜์ง€): ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ์ด ๋†’์•„ ์ž”์—ฌ ์ˆ˜๋ช… 83์ผ ์˜ˆ์ธก. ์ฃผ์š” ์š”์ธ ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ(-279์ผ), ์šด์šฉ ํ™˜๊ฒฝ(-218์ผ), ์šด์šฉ ์ด๋ ฅ(-110์ผ)\n", " - EQ030208(U5, ํ˜นํ•œ): ์ฐฝ์ •๋น„ ์ดํ›„ ์šด์šฉ ์‹œ๊ฐ„์ด ๊ธธ์–ด ์ž”์—ฌ ์ˆ˜๋ช… 89์ผ ์˜ˆ์ธก. ์ฃผ์š” ์š”์ธ ์šด์šฉ ์ด๋ ฅ(-382์ผ), ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ(-351์ผ), ์šด์šฉ ํ™˜๊ฒฝ(-64์ผ)\n", " ๋Œ€์‹œ๋ณด๋“œ์™€ ์„ค๋ช…์€ ์ •๋น„๊ณ„ํš๊ด€์˜ ํŒ๋‹จ ๋ณด์กฐ์ด๋ฉฐ ์ผ์ • ํ™•์ •๊ณผ ์žฅ๋น„ ๋„ํƒœ ๊ฑด์˜๋Š” ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ(M7)์— ๋”ฐ๋ฅธ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 07. ์ •๋น„ ์ผ์ • ํ™•์ • ๋ฐ ์žฅ๋น„ ๋„ํƒœ ๊ฑด์˜๋ฅผ ์œ„ํ•œ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ HITL ์Šน์ธ ์ ˆ์ฐจ ์„ค๊ณ„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ •๋น„ ์ผ์ • ํ™•์ •์„ ์œ„ํ•œ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ HITL ์Šน์ธ ์ ˆ์ฐจ๋ฅผ ์„ค๊ณ„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์ •๋น„ ์ผ์ • ํ™•์ •๊ณผ ์žฅ๋น„ ๋„ํƒœ ๊ฑด์˜๋ฅผ ์œ„ํ•œ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ HITL ์Šน์ธ ์ ˆ์ฐจ๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  ๋น„์šฉ ์ ˆ๊ฐ ํšจ๊ณผ๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ด€ํ–‰ ์ผ์ • ๋Œ€๋น„ ์ œ์•ˆ ์ผ์ •์˜ 12๊ฐœ์›” ๋น„์šฉ์„ ํ‰์ƒ, ์ •๋น„์ฐฝ ๋Šฅ๋ ฅ ๊ฐ์†Œ, ์˜ˆ์‚ฐ ์‚ญ๊ฐ, ํ‚คํŠธ ์žฌ๊ณ  ๋ถ€์กฑ, ๊ฐ€๋™๋ฅ  ์ƒ์Šน ๋“ฑ ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„๋กœ ๋น„๊ตํ•˜๊ณ  ๋ฐฑํ…Œ์ŠคํŠธ์™€ ๋ฏผ๊ฐ๋„ ํ™•์ธํ•˜๊ณ  ์œ ํšจ์„ฑ์„ ์ ๊ฒ€ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/optimization/sup02_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": [ "\"\"\"์ •๋น„์ฐฝ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐยท์‹œํšจ์„ฑ ํ‚คํŠธ ์ œ์•ฝ ์•„๋ž˜ ์˜ˆ์ธก ๊ธฐ๋ฐ˜ ๊ณ„ํš์ •๋น„ ์ผ์ •์„ ๊ทœ์ • ์ฃผ๊ธฐ ๊ณ ์ • ์ผ์ •๊ณผ ๋น„๊ตํ•ด ๋น„์šฉยทํ‚คํŠธ ์†Œ์ง„์œจ์„ ์‹œ๋‚˜๋ฆฌ์˜คยท๋ฐฑํ…Œ์ŠคํŠธ๋กœ ๊ฒ€์ฆํ•˜๊ณ (๋น„์šฉ 15% ์ด์ƒ ์ ˆ๊ฐ), ์ •๋น„ ์ผ์ • ํ™•์ •๊ณผ ์žฅ๋น„ ๋„ํƒœ ๊ฑด์˜๋ฅผ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€(๋„ํƒœ๋Š” ์žฌ์ • ๋‹ด๋‹น 2๋‹จ๊ณ„)์ด ์ง์ ‘ ์Šน์ธยท๋ฐ˜๋ คํ•˜๋Š” HITL ๊ฒฐ์žฌ ์›Œํฌํ”Œ๋กœ์šฐ(๊ถŒํ•œยท์‚ฌ์œ ยทํ•ด์‹œ ์ฒด์ธ ๊ฐ์‚ฌ ๋กœ๊ทธ)๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.\"\"\"\n", "import hashlib\n", "import json\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์žฅ๋น„ ๋ณด์œ  ํ˜„ํ™ฉยท๊ณ ์žฅ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ˆ˜์น˜ ์—†์Œ)\n", "# ๊ฐ€์ƒ ๋ถ€๋Œ€ 6๊ฐœ(U1~U6)์˜ ์žฅ๋น„๊ฐ€ ์šด์šฉ ํ™˜๊ฒฝ(normal/rough/salt/cold)์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์†๋„๋กœ ๋…ธํ›„ํ™”ํ•œ๋‹ค. ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„(h_since)์ด\n", "# ์žฅ๋น„๋งˆ๋‹ค ์ˆจ์€ ์ˆ˜๋ช…(t_fail)์„ ๋„˜์œผ๋ฉด ๊ณ ์žฅ(event=1) ์ด๋ ฅ, ๋„˜์ง€ ์•Š์•˜์œผ๋ฉด ํ˜„์žฌ๊นŒ์ง€ ์ƒ์กด(event=0, ๊ด€์ธก ์ค‘๋„์ ˆ๋‹จ)์ด๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ENVS = [\"normal\", \"rough\", \"salt\", \"cold\"]\n", "ENV_W = {\"normal\": 1.00, \"rough\": 1.35, \"salt\": 1.25, \"cold\": 1.12} # ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜(์ˆจ์€ ์ •๋‹ต, ๊ฐ€์ •๊ฐ’): ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ์งง๋‹ค\n", "ENV_KO = {\"normal\": \"์ผ๋ฐ˜\", \"rough\": \"ํ—˜์ง€\", \"salt\": \"์—ผ๋ถ„\", \"cold\": \"ํ˜นํ•œ\"}\n", "UNITS = [\"U1\", \"U2\", \"U3\", \"U4\", \"U5\", \"U6\"]\n", "UNIT_ENV = {\"U1\": [.7, .1, .1, .1], \"U2\": [.2, .6, .1, .1], \"U3\": [.2, .1, .6, .1], \"U4\": [.3, .1, .1, .5], \"U5\": [.5, .3, .1, .1], \"U6\": [.4, .1, .4, .1]}\n", "ETYPES = [\"truck\", \"generator\", \"trailer\", \"radio\"]\n", "CYCLE = 8000.0 # ๊ทœ์ • ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ(์šด์šฉ์‹œ๊ฐ„ h, ๊ฐ€์ •๊ฐ’)\n", "KSHAPE = 12.0 # ์ˆ˜๋ช… ๋ถ„ํฌ ์™€์ด๋ธ” ํ˜•์ƒ(๋งˆ๋ชจํ˜• ๊ณ ์žฅ, ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ๋ชจ์—ฌ ์žˆ๋‹ค)\n", "INSP_NOISE = 0.025 # ์ •๋น„ ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ ์ธก์ • ์˜ค์ฐจ(๊ฐ€์ •๊ฐ’)\n", "\n", "\n", "def make_fleet(n=900, seed=0, env_w=None, shift=1.0):\n", " \"\"\"์žฅ๋น„ n๋Œ€. env_w๋กœ ์ˆจ์€ ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜๋ฅผ ๋ฐ”๊ฟ” ์šด์˜ ๋ถ„ํฌ ๋ณ€ํ™”๋ฅผ ๋ชจ์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. shift>1์ด๋ฉด ๊ฐ€๋™๋ฅ ์ด ๋†’์•„์ง„๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " w = env_w or ENV_W\n", " unit = rng.choice(UNITS, n)\n", " env = np.array([rng.choice(ENVS, p=UNIT_ENV[u]) for u in unit])\n", " et = rng.choice(ETYPES, n, p=[.4, .2, .2, .2])\n", " rate = rng.lognormal(np.log(3.0 * shift), 0.25, n) # ์ผ ํ‰๊ท  ์šด์šฉ์‹œ๊ฐ„(h/์ผ)\n", " h_since = rng.uniform(300, 11500, n) # ๋งˆ์ง€๋ง‰ ์ฐฝ์ •๋น„ ์ดํ›„ ์šด์šฉ์‹œ๊ฐ„\n", " n_ovh = rng.integers(0, 3, n)\n", " hours = h_since + n_ovh * rng.uniform(5000, 8000, n) # ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„\n", " km = hours * rng.normal(17, 3, n) * np.where(env == \"rough\", 0.8, 1.0) # ๋ˆ„์  ๊ธฐ๋™๊ฑฐ๋ฆฌ(km)\n", " age_y = np.clip(hours / (rate * 365) * rng.uniform(0.9, 1.15, n), 0.5, 25)\n", " unpl = rng.poisson(0.35 * h_since / 1000 * np.array([w[e] for e in env]) / 1.3) # ๋น„๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜(ํ™˜๊ฒฝ์ด ๊ฐ€ํ˜นํ• ์ˆ˜๋ก ๋งŽ๋‹ค)\n", " n_maint = rng.poisson(0.6 * h_since / 1000 + 1) # ๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜\n", " scale = 9600 / np.array([w[e] for e in env]) * 0.985 ** unpl * np.exp(rng.normal(0, 0.04, n))\n", " t_fail = scale * rng.weibull(KSHAPE, n) # ์ฐฝ์ •๋น„ ํ›„ ๊ณ ์žฅ๊นŒ์ง€ ์šด์šฉ์‹œ๊ฐ„(์ˆจ์€ ์ •๋‹ต)\n", " event = (t_fail <= h_since).astype(int)\n", " dur = np.where(event == 1, t_fail, h_since)\n", " df = pd.DataFrame({\"eq_id\": [f\"EQ{seed:02d}{i:04d}\" for i in range(n)], \"unit\": unit, \"env\": env, \"etype\": et, \"rate\": rate, \"hours\": hours, \"km\": km, \"age_y\": age_y, \"n_ovh\": n_ovh,\n", " \"h_since\": h_since, \"unpl\": unpl, \"n_maint\": n_maint, \"t_fail\": t_fail, \"event\": event, \"dur\": dur})\n", " df[\"insp\"] = np.clip(dur / t_fail + rng.normal(0, INSP_NOISE, n), 0, 1.3) # ๋งˆ์ง€๋ง‰ ์ •๋น„ ์ ๊ฒ€์—์„œ ์ธก์ •ํ•œ ๋งˆ๋ชจ ๋น„์œจ(์†Œ๋ชจ ์ˆ˜๋ช…/์ด ์ˆ˜๋ช…, ์˜ค์ฐจ ํฌํ•จ)\n", " df[\"rul_h\"] = np.where(event == 0, t_fail - h_since, np.nan) # ์ƒ์กด ์žฅ๋น„์˜ ๋‚จ์€ ์šด์šฉ์‹œ๊ฐ„(์ •๋‹ต)\n", " df[\"rul_d\"] = df[\"rul_h\"] / df[\"rate\"] # ๋‚จ์€ ์ผ์ˆ˜(์ •๋‹ต)\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " y, p = np.asarray(y, float), np.asarray(p, float)\n", " return float(np.mean(np.abs(y - p) / np.abs(y)) * 100)\n", "\n", "\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "\n", "\n", "def rmse(y, p):\n", " return float(np.sqrt(np.mean((np.asarray(y, float) - np.asarray(p, float)) ** 2)))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธํ›„ํ™” ํŒŒ์ƒ ํ”ผ์ฒ˜์™€ ํ•™์Šต ํ‘œ๋ณธ ๋งŒ๋“ค๊ธฐ. ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ W0๋Š” ๋„์ž… ์‹œ์ ์˜ ์ „๋ฌธ๊ฐ€ ์ถ”์ •๊ฐ’์ด๋ฉฐ(์‹ค์ œ ๊ฐ€์ค‘์น˜์™€ ๋‹ค๋ฆ„) M9์—์„œ ์žฌ๋ณด์ •ํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "W0 = {\"normal\": 1.00, \"rough\": 1.15, \"salt\": 1.10, \"cold\": 1.05}\n", "FEATS = [\"h_since\", \"hours\", \"km\", \"age_y\", \"rate\", \"n_maint\", \"unpl\", \"unpl_per_kh\", \"km_per_h\", \"n_ovh\", \"k_rough\", \"k_salt\", \"k_cold\", \"k_truck\", \"k_gen\", \"k_trailer\",\n", " \"aging_idx\", \"lin_rul_d\", \"hs_frac\", \"insp\", \"insp_eta_h\"]\n", "\n", "\n", "def add_features(df, w=None):\n", " d = df.copy()\n", " w = w or W0\n", " d[\"unpl_per_kh\"] = d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)\n", " d[\"km_per_h\"] = d[\"km\"] / d[\"hours\"]\n", " for e in (\"rough\", \"salt\", \"cold\"):\n", " d[\"k_\" + e] = (d[\"env\"] == e).astype(int)\n", " d[\"k_truck\"], d[\"k_gen\"], d[\"k_trailer\"] = (d[\"etype\"] == \"truck\").astype(int), (d[\"etype\"] == \"generator\").astype(int), (d[\"etype\"] == \"trailer\").astype(int)\n", " d[\"aging_idx\"] = d[\"h_since\"] * d[\"env\"].map(w) / CYCLE # ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜(1์ด๋ฉด ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ์— ๋„๋‹ฌ)\n", " d[\"lin_rul_d\"] = (CYCLE - d[\"h_since\"]) / d[\"rate\"] # ์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ์ž”์—ฌ ์ผ์ˆ˜(Baseline)\n", " d[\"hs_frac\"] = d[\"h_since\"] / CYCLE\n", " d[\"insp_eta_h\"] = d[\"h_since\"] / np.maximum(d[\"insp\"], 0.05) # ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ๋กœ ํ™˜์‚ฐํ•œ ์ด ์ˆ˜๋ช… ์ถ”์ •(h)\n", " return d\n", "\n", "\n", "def snapshots(df, k=4, seed=0):\n", " \"\"\"๊ณ ์žฅ ์ด๋ ฅ ์žฅ๋น„์˜ ๊ณผ๊ฑฐ ์‹œ์  ํ‘œ๋ณธ: ๊ณ ์žฅ ์ „ ์ž„์˜ ์‹œ์ ์˜ ์ ๊ฒ€๊ฐ’ยท์šด์šฉ์‹œ๊ฐ„๊ณผ ๊ทธ๋•Œ์˜ ๋‚จ์€ ์ผ์ˆ˜. (์ƒ์กด ์žฅ๋น„์˜ ๋ฏธ๋ž˜๋Š” ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๊ณ ์žฅ ์ด๋ ฅ๋งŒ ์“ด๋‹ค)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " f = df[df[\"event\"] == 1]\n", " out = []\n", " for _ in range(k):\n", " s = f.copy()\n", " h = s[\"t_fail\"] * rng.uniform(0.2, 0.98, len(s))\n", " ratio = h / s[\"h_since\"]\n", " s[\"hours\"] = s[\"hours\"] - (s[\"h_since\"] - h)\n", " s[\"km\"] = s[\"km\"] * ratio.clip(upper=1)\n", " s[\"unpl\"] = np.floor(s[\"unpl\"] * ratio)\n", " s[\"n_maint\"] = np.floor(s[\"n_maint\"] * ratio)\n", " s[\"insp\"] = np.clip(h / s[\"t_fail\"] + rng.normal(0, INSP_NOISE, len(s)), 0, 1.3)\n", " s[\"h_since\"] = h\n", " s[\"rul_d\"] = (s[\"t_fail\"] - h) / s[\"rate\"]\n", " s[\"event\"] = 0\n", " out.append(s)\n", " return pd.concat(out, ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ƒ์กด ๋ถ„์„ ๋„๊ตฌ(scipy๋งŒ ์‚ฌ์šฉ): ์™€์ด๋ธ” AFT(๊ฐ€์† ์ˆ˜๋ช…) ๋ชจํ˜•๊ณผ Cox ๋น„๋ก€์œ„ํ—˜ ๋ชจํ˜•\n", "# ---------------------------------------------------------------------------\n", "from scipy.optimize import minimize\n", "\n", "\n", "def aft_x(d):\n", " return np.c_[np.ones(len(d)), d[\"env\"].eq(\"rough\"), d[\"env\"].eq(\"salt\"), d[\"env\"].eq(\"cold\"), d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)].astype(float)\n", "\n", "\n", "AFT_COLS = [\"์ ˆํŽธ\", \"ํ—˜์ง€\", \"์—ผ๋ถ„\", \"ํ˜นํ•œ\", \"๋น„๊ณ„ํš ์ •๋น„์œจ\"]\n", "\n", "\n", "def fit_aft(d):\n", " \"\"\"์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„ dur(๊ณ ์žฅ์ด๋ฉด event=1, ์ƒ์กด์ด๋ฉด ์ค‘๋„์ ˆ๋‹จ)์— ๋Œ€ํ•œ ์™€์ด๋ธ” AFT ์ตœ๋Œ€์šฐ๋„ ์ถ”์ •. ๋ฐ˜ํ™˜: ๊ณ„์ˆ˜(๋งˆ์ง€๋ง‰์€ log ํ˜•์ƒ).\"\"\"\n", " x, t, e = aft_x(d), d[\"dur\"].values, d[\"event\"].values\n", "\n", " def nll(p):\n", " lam, k = np.exp(x @ p[:-1]), np.exp(p[-1])\n", " z = (t / lam) ** k\n", " return -(e * (np.log(k) - np.log(lam) + (k - 1) * (np.log(t) - np.log(lam))) - z).sum()\n", "\n", " r = minimize(nll, np.r_[np.log(8000), np.zeros(x.shape[1] - 1), np.log(5)], method=\"BFGS\")\n", " return r.x\n", "\n", "\n", "def aft_pred(p, d, q=0.5, h=None):\n", " \"\"\"์ด๋ฏธ h(์šด์šฉ์‹œ๊ฐ„)๊นŒ์ง€ ์ƒ์กดํ–ˆ๋‹ค๋Š” ์กฐ๊ฑด์—์„œ ๋ถ„์œ„์ˆ˜ q ์ƒ์กด ์‹œ์ (์šด์šฉ์‹œ๊ฐ„ h ๊ธฐ์ค€ ์ด ์ˆ˜๋ช…)์„ ๋Œ๋ ค์ค€๋‹ค. q=0.5๊ฐ€ ์กฐ๊ฑด๋ถ€ ์ค‘์•™๊ฐ’.\"\"\"\n", " lam, k = np.exp(aft_x(d) @ p[:-1]), np.exp(p[-1])\n", " h = d[\"h_since\"].values if h is None else h\n", " return lam * ((h / lam) ** k - np.log(q)) ** (1 / k)\n", "\n", "\n", "def aft_fail_prob(p, d, h0, h1):\n", " \"\"\"h0๊นŒ์ง€ ์ƒ์กดํ•œ ์žฅ๋น„๊ฐ€ h0~h1 ์‚ฌ์ด์— ๊ณ ์žฅ ๋‚  ์กฐ๊ฑด๋ถ€ ํ™•๋ฅ .\"\"\"\n", " lam, k = np.exp(aft_x(d) @ p[:-1]), np.exp(p[-1])\n", " s0, s1 = np.exp(-(h0 / lam) ** k), np.exp(-(h1 / lam) ** k)\n", " return 1 - s1 / s0\n", "\n", "\n", "def fit_cox(d, iters=25):\n", " \"\"\"Cox ๋น„๋ก€์œ„ํ—˜(Breslow ๊ทผ์‚ฌ) ๋‰ดํ„ด-๋žฉ์Šจ. ๊ณต๋ณ€๋Ÿ‰: ํ—˜์ง€ยท์—ผ๋ถ„ยทํ˜นํ•œยท๋น„๊ณ„ํš ์ •๋น„์œจ. ๋ฐ˜ํ™˜: ๊ณ„์ˆ˜.\"\"\"\n", " x = aft_x(d)[:, 1:]\n", " o = np.argsort(-d[\"dur\"].values)\n", " x, e = x[o], d[\"event\"].values[o]\n", " b = np.zeros(x.shape[1])\n", " for _ in range(iters):\n", " r = np.exp(x @ b)\n", " S0 = np.cumsum(r)\n", " S1 = np.cumsum(r[:, None] * x, axis=0)\n", " S2 = np.cumsum(r[:, None, None] * x[:, :, None] * x[:, None, :], axis=0)\n", " m1 = S1 / S0[:, None]\n", " g = ((x - m1) * e[:, None]).sum(axis=0)\n", " H = -sum(e[i] * (S2[i] / S0[i] - np.outer(m1[i], m1[i])) for i in np.where(e == 1)[0])\n", " step = np.linalg.solve(H - 1e-6 * np.eye(len(b)), g)\n", " b = b - step\n", " if np.abs(step).max() < 1e-7:\n", " break\n", " return b\n", "\n", "\n", "def cindex(time, event, risk):\n", " \"\"\"์ผ์น˜๋„ C-index: ์œ„ํ—˜ ์ ์ˆ˜๊ฐ€ ๋†’์€ ์ชฝ์ด ๋จผ์ € ๊ณ ์žฅ ๋‚ฌ๋Š”๊ฐ€(๊ด€์ธก ๊ฐ€๋Šฅํ•œ ์Œ๋งŒ).\"\"\"\n", " t, e, r = np.asarray(time), np.asarray(event), np.asarray(risk)\n", " num = den = 0.0\n", " for i in np.where(e == 1)[0]:\n", " m = t > t[i]\n", " den += m.sum()\n", " num += (r[m] < r[i]).sum() + 0.5 * (r[m] == r[i]).sum()\n", " return num / max(den, 1)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ž”์—ฌ ์ˆ˜๋ช…(RUL, ์ผ) ์˜ˆ์ธก ์—”์ง„: ์™€์ด๋ธ” AFT ์กฐ๊ฑด๋ถ€ ์ค‘์•™๊ฐ’ + ๊ทธ๋ž˜๋””์–ธํŠธ ๋ถ€์ŠคํŒ…(๊ณ ์žฅ ์ด๋ ฅ ์‹œ์  ํ‘œ๋ณธ ํ•™์Šต)์˜ ํ‰๊ท \n", "# ---------------------------------------------------------------------------\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "\n", "\n", "def fit_engine(train, seed=0):\n", " p = fit_aft(train)\n", " sn = add_features(snapshots(train, 5, seed))\n", " sn[\"aft\"] = (aft_pred(p, sn) - sn[\"h_since\"]) / sn[\"rate\"]\n", " g = GradientBoostingRegressor(n_estimators=250, max_depth=3, learning_rate=0.06, random_state=0).fit(sn[FEATS + [\"aft\"]], sn[\"rul_d\"])\n", " return {\"p\": p, \"g\": g}\n", "\n", "\n", "def predict_rul(eng, d):\n", " \"\"\"d๋Š” add_features๋ฅผ ๊ฑฐ์นœ ํ‘œ. ๋ฐ˜ํ™˜: (์•™์ƒ๋ธ” RUL, AFT RUL, GBR RUL) - ์ผ ๋‹จ์œ„.\"\"\"\n", " aft = ((aft_pred(eng[\"p\"], d) - d[\"h_since\"]) / d[\"rate\"]).values\n", " x = d[FEATS].copy()\n", " x[\"aft\"] = aft\n", " gb = eng[\"g\"].predict(x)\n", " return np.clip(0.5 * aft + 0.5 * gb, 1, None), aft, gb\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๊ณ„ํš์ •๋น„(์ฐฝ์ •๋น„) ์ผ์ • ์ถ”์ฒœ: ์›”๋ณ„ ์ •๋น„์ฐฝ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐยท์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ(์œ ํšจ๊ธฐํ•œ) ์ œ์•ฝ ์•„๋ž˜ ๊ธฐ๋Œ€ ๋น„์šฉ ์ตœ์†Œํ™” (๋น„์šฉ ๋‹จ๊ฐ€๋Š” ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’, ๋งŒ์›)\n", "# ---------------------------------------------------------------------------\n", "from scipy.optimize import milp, LinearConstraint, Bounds\n", "from scipy.stats import norm\n", "\n", "C_PLAN, C_FAIL, C_WASTE_KH, C_KIT = 600.0, 2400.0, 40.0, 120.0 # ๊ณ„ํš ์ฐฝ์ •๋น„, ๋น„๊ณ„ํš ๊ณ ์žฅ(๊ธด๊ธ‰ ์ •๋น„ ํฌํ•จ), ๋ฏธ์‚ฌ์šฉ ์ˆ˜๋ช… 1000h๋‹น ์†์‹ค, ์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ\n", "HORIZON, MDAYS = 12, 30 # ๊ณ„ํš 12๊ฐœ์›”, ์›” 30์ผ\n", "CAP_M, BUDGET_M = 22, 15000.0 # ์›”๋ณ„ ์ •๋น„์ฐฝ ๋Šฅ๋ ฅ(๋Œ€), ์›”๋ณ„ ์˜ˆ์‚ฐ\n", "LOTS = [(30, 70), (45, 170), (45, 390), (70, 800)] # ์‹œํšจ์„ฑ ํ‚คํŠธ ๋กœํŠธ(์ˆ˜๋Ÿ‰, ์œ ํšจ๊ธฐํ•œ๊นŒ์ง€ ๋‚จ์€ ์ผ์ˆ˜)\n", "KIT_MARGIN = 14 # ์ •๋น„์ผ ๊ธฐ์ค€ ์ตœ์†Œ ์œ ํšจ๊ธฐ๊ฐ„ ์—ฌ์œ (์ผ)\n", "\n", "\n", "def exp_cost_matrix(pred, rate, month_days):\n", " \"\"\"์žฅ๋น„ i๋ฅผ ์›” m์— ์ •๋น„ํ•  ๋•Œ์˜ ๊ธฐ๋Œ€ ๋น„์šฉ. ์ฐธ RUL ~ N(pred, sigma), sigma = 35์ผ + 0.12 x pred (๊ฐ€์ •). ๋งˆ์ง€๋ง‰ ์—ด์€ ๊ณ„ํš ๊ธฐ๊ฐ„ ๋ฐ–(๋ฏธ์ •๋น„).\"\"\"\n", " sig = 35 + 0.12 * pred\n", " C = np.zeros((len(pred), len(month_days) + 1))\n", " for j, day in enumerate(month_days):\n", " z = (day - pred) / sig\n", " pf = norm.cdf(z)\n", " unused = np.maximum(pred - day, 0) * rate * 0.6 # ์ •๋น„๋ฅผ ์•ž๋‹น๊ธด ๋งŒํผ ๋ฒ„๋ฆฌ๋Š” ์šด์šฉ์‹œ๊ฐ„ h(๊ทผ์‚ฌ)\n", " C[:, j] = C_PLAN + C_KIT + pf * (C_FAIL - C_PLAN) + C_WASTE_KH * unused / 1000 * (1 - pf)\n", " C[:, -1] = norm.cdf((HORIZON * MDAYS - pred) / sig) * C_FAIL\n", " return C\n", "\n", "\n", "def plan_milp(pred, rate, cap=CAP_M, budget=BUDGET_M, lots=LOTS, use_kit=True):\n", " \"\"\"์ •์ˆ˜๊ณ„ํš์œผ๋กœ ์žฅ๋น„๋ณ„ ์ •๋น„ ์›”(0~11) ๋˜๋Š” ๋ฏธ์ •๋น„(-1)๋ฅผ ๋ฐฐ์ •ํ•œ๋‹ค. ํ›„๋ณด๋Š” ์˜ˆ์ธก RUL์ด ๊ณ„ํš ๊ธฐ๊ฐ„ +90์ผ ์ด๋‚ด์ธ ์žฅ๋น„.\"\"\"\n", " md = np.arange(1, HORIZON + 1) * MDAYS\n", " cand = np.where(pred < HORIZON * MDAYS + 90)[0]\n", " n, M = len(cand), HORIZON\n", " C = exp_cost_matrix(pred[cand], rate[cand], md)\n", " c = C.ravel()\n", " A, lo, hi = [], [], []\n", " for i in range(n): # ์žฅ๋น„๋‹น ํ•œ ๋ฒˆ๋งŒ ๋ฐฐ์ •(์›” 12์นธ + ๋ฏธ์ •๋น„)\n", " row = np.zeros(n * (M + 1)); row[i * (M + 1):(i + 1) * (M + 1)] = 1; A.append(row); lo.append(1); hi.append(1)\n", " for m in range(M): # ์›”๋ณ„ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐ\n", " row = np.zeros(n * (M + 1)); row[m::(M + 1)] = 1\n", " A.append(row); lo.append(0); hi.append(cap)\n", " A.append(row * (C_PLAN + C_KIT)); lo.append(0); hi.append(budget)\n", " if use_kit: # ์‹œํšจ์„ฑ ํ‚คํŠธ: m์›” ์ดํ›„ ์ •๋น„ ์ˆ˜์š”๋Š” ๊ทธ ์‹œ์ ์— ์œ ํšจ๊ธฐ๊ฐ„์ด ๋‚จ์€ ๋กœํŠธ ์ˆ˜๋Ÿ‰์„ ๋„˜์„ ์ˆ˜ ์—†๋‹ค\n", " for m in range(M):\n", " sup = sum(q for q, e in lots if e >= md[m] + KIT_MARGIN)\n", " row = np.zeros(n * (M + 1))\n", " for mm in range(m, M):\n", " row[mm::(M + 1)] = 1\n", " A.append(row); lo.append(0); hi.append(sup)\n", " r = milp(c, constraints=LinearConstraint(np.array(A), lo, hi), integrality=np.ones(len(c)), bounds=Bounds(0, 1), options={\"time_limit\": 20})\n", " month = np.full(len(pred), -1)\n", " if r.x is not None:\n", " x = np.round(r.x).reshape(n, M + 1)\n", " assign = np.where(x[:, :M].sum(axis=1) > 0.5, x[:, :M].argmax(axis=1), -1)\n", " month[cand] = assign\n", " return month\n", "\n", "\n", "def plan_greedy(pred, margin=45, cap=CAP_M, budget=BUDGET_M):\n", " \"\"\"๊ฐ„๋‹จํ•œ ํƒ์š• ๋ฐฐ์ •: ์ •๋น„ ๋ชฉํ‘œ์ผ(์˜ˆ์ธก RUL - margin)์ด ๋น ๋ฅธ ์ˆœ์œผ๋กœ ๋ชฉํ‘œ์ผ ์ด์ „ ๊ฐ€์žฅ ๋Šฆ์€ ๋นˆ ์›”, ์—†์œผ๋ฉด ๊ฐ€์žฅ ์ด๋ฅธ ๋นˆ ์›”์— ๋ฐฐ์ •.\"\"\"\n", " cap = min(cap, int(budget // (C_PLAN + C_KIT))) # ์›” ์˜ˆ์‚ฐ์ด ํ—ˆ์šฉํ•˜๋Š” ๋Œ€์ˆ˜๋กœ ๋Šฅ๋ ฅ์„ ์ œํ•œ\n", " month = np.full(len(pred), -1)\n", " left = np.full(HORIZON, cap)\n", " for i in np.argsort(pred):\n", " if pred[i] > HORIZON * MDAYS + 60: break\n", " tgt = int(np.clip((pred[i] - margin) // MDAYS - 1, 0, HORIZON - 1))\n", " for m in list(range(tgt, -1, -1)) + list(range(tgt + 1, HORIZON)):\n", " if left[m] > 0:\n", " month[i] = m; left[m] -= 1; break\n", " return month\n", "\n", "\n", "def plan_fixed(lin_rul, cap=CAP_M, budget=BUDGET_M):\n", " \"\"\"๊ด€ํ–‰: ๊ทœ์ • ์ฃผ๊ธฐ ๋„๋‹ฌ ์ˆœ์„œ๋Œ€๋กœ(์„ ํ˜• ์ž”์—ฌ ์ผ์ˆ˜) ์›”๋ณ„ ๋Šฅ๋ ฅ ์•ˆ์—์„œ ๋ฐฐ์ •.\"\"\"\n", " return plan_greedy(np.asarray(lin_rul, float), margin=0, cap=cap, budget=budget)\n", "\n", "\n", "def evaluate_plan(month, true_rul, rate, cost_kit=True):\n", " \"\"\"์ฐธ RUL๋กœ ๊ณ„ํš์˜ ์‹ค์ œ ๋น„์šฉ์„ ๊ณ„์‚ฐํ•œ๋‹ค: ์ •๋น„์ผ ์ด์ „์— ๊ณ ์žฅ -> ๋น„๊ณ„ํš ๊ณ ์žฅ ๋น„์šฉ, ์•„๋‹ˆ๋ฉด ๊ณ„ํš ๋น„์šฉ + ๋ฏธ์‚ฌ์šฉ ์ˆ˜๋ช… ์†์‹ค.\"\"\"\n", " day = np.where(month >= 0, (month + 1) * MDAYS, 10 ** 9)\n", " fail = (true_rul < day) & (true_rul < HORIZON * MDAYS)\n", " planned = (month >= 0) & ~fail\n", " waste = np.where(planned, (true_rul - day) * rate * 0.6, 0) * C_WASTE_KH / 1000\n", " cost = fail * C_FAIL + planned * (C_PLAN + (C_KIT if cost_kit else 0)) + waste\n", " return {\"total\": float(cost.sum()), \"fail\": int(fail.sum()), \"planned\": int(planned.sum()), \"waste\": float(waste.sum()), \"unsched_fail\": int(((month < 0) & fail).sum())}\n", "\n", "\n", "def kit_fefo(month, lots=LOTS):\n", " \"\"\"์œ ํšจ๊ธฐํ•œ์ด ๋น ๋ฅธ ๋กœํŠธ๋ถ€ํ„ฐ ์“ฐ๋Š” FEFO๋กœ ํ‚คํŠธ๋ฅผ ๋ฐฐ์ •ํ•˜๊ณ  ์ •๋น„์ผ์— ์œ ํšจ๊ธฐ๊ฐ„์ด ๋ถ€์กฑํ•œ ๋ฐฐ์ • ์ˆ˜์™€ ๋งŒ๋ฃŒ ํ๊ธฐ ์ˆ˜๋Ÿ‰์„ ์„ผ๋‹ค.\"\"\"\n", " q = [[qty, e] for qty, e in sorted(lots, key=lambda t: t[1])]\n", " short = 0\n", " for m in sorted(month[month >= 0]):\n", " day = (m + 1) * MDAYS\n", " for lot in q:\n", " if lot[1] >= day + KIT_MARGIN and lot[0] > 0:\n", " lot[0] -= 1; break\n", " else:\n", " short += 1\n", " expired = sum(l[0] for l in q if l[1] < HORIZON * MDAYS)\n", " return {\"short\": short, \"expired_unused\": expired, \"left\": sum(l[0] for l in q)}\n", "# ---------------------------------------------------------------------------\n", "\n", "tr = make_fleet(900, seed=1)\n", "eng = fit_engine(tr)\n", "\n", "\n", "def scenario(seed, shift=1.0, cap=CAP_M, budget=BUDGET_M, lots=LOTS, use_kit=True):\n", " fl = add_features(make_fleet(900, seed=seed, shift=shift))\n", " al = fl[fl.event == 0].reset_index(drop=True)\n", " pred, _, _ = predict_rul(eng, al)\n", " mf = plan_fixed(al[\"lin_rul_d\"].values, cap=cap, budget=budget)\n", " mm = plan_milp(pred, al[\"rate\"].values, cap=cap, budget=budget, lots=lots, use_kit=use_kit)\n", " rf, rm = evaluate_plan(mf, al[\"rul_d\"].values, al[\"rate\"].values), evaluate_plan(mm, al[\"rul_d\"].values, al[\"rate\"].values)\n", " kf, km = kit_fefo(mf, lots), kit_fefo(mm, lots)\n", " stock = sum(q for q, _ in lots)\n", " return al, pred, mf, mm, rf, rm, kf, km, stock\n", "\n", "\n", "# ---------------- 1) ์‹œ๋‚˜๋ฆฌ์˜ค 6์ข… ----------------\n", "SC = [(\"ํ‰์ƒ\", dict(seed=3)), (\"์ •๋น„์ฐฝ ๋Šฅ๋ ฅ 70%(15๋Œ€/์›”)\", dict(seed=3, cap=15)), (\"์˜ˆ์‚ฐ ์‚ญ๊ฐ(์›” 10,000๋งŒ์›)\", dict(seed=3, budget=10000.0)), (\"์‹œํšจ์„ฑ ํ‚คํŠธ ์žฌ๊ณ  ์ ˆ๋ฐ˜ยท๋‹จ๊ธฐ ์œ ํšจ\", dict(seed=3, lots=[(15, 70), (20, 170), (20, 240), (30, 330)])),\n", " (\"๊ฐ€๋™๋ฅ  +30%\", dict(seed=3, shift=1.3)), (\"๋‹ค๋ฅธ ๋ณด์œ  ์žฅ๋น„(์‹œ๋“œ 4)\", dict(seed=4))]\n", "rows, cache = [], {}\n", "for name, kw in SC:\n", " al, pred, mf, mm, rf, rm, kf, km, stock = scenario(**kw)\n", " cache[name] = (al, pred, mm)\n", " rows.append((name, len(al), rf[\"total\"], rm[\"total\"], 1 - rm[\"total\"] / rf[\"total\"], rf[\"fail\"], rm[\"fail\"], kf[\"expired_unused\"], km[\"expired_unused\"], km[\"short\"]))\n", "res = pd.DataFrame(rows, columns=[\"์‹œ๋‚˜๋ฆฌ์˜ค\", \"์ƒ์กด ์žฅ๋น„\", \"๊ด€ํ–‰ ๋น„์šฉ(๋งŒ์›)\", \"์ œ์•ˆ ๋น„์šฉ(๋งŒ์›)\", \"์ ˆ๊ฐ๋ฅ \", \"๊ด€ํ–‰ ๋น„๊ณ„ํš ๊ณ ์žฅ\", \"์ œ์•ˆ ๋น„๊ณ„ํš ๊ณ ์žฅ\", \"๊ด€ํ–‰ ํ‚คํŠธ ๋งŒ๋ฃŒ ํ๊ธฐ\", \"์ œ์•ˆ ํ‚คํŠธ ๋งŒ๋ฃŒ ํ๊ธฐ\", \"์ œ์•ˆ ํ‚คํŠธ ์œ ํšจ๊ธฐ๊ฐ„ ๋ถ€์กฑ\"])\n", "print(\"[1] ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ 12๊ฐœ์›” ๋น„์šฉ ๋น„๊ต(๊ด€ํ–‰ = ๊ทœ์ • ์ฃผ๊ธฐ ๋„๋‹ฌ ์ˆœ ๊ณ ์ • ์ผ์ •, ๋‹จ๊ฐ€๋Š” ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\")\n", "print(res.assign(์ ˆ๊ฐ๋ฅ =res[\"์ ˆ๊ฐ๋ฅ \"].map(\"{:.1%}\".format)).round(0).to_string(index=False))\n", "avg = res[\"์ ˆ๊ฐ๋ฅ \"].mean()\n", "print(f\" ์‹œ๋‚˜๋ฆฌ์˜ค 6์ข… ํ‰๊ท  ์ ˆ๊ฐ {avg:.1%} (๋ชฉํ‘œ 15% ์ด์ƒ: {'๋‹ฌ์„ฑ' if avg >= .15 else '๋ฏธ๋‹ฌ'}), ์ตœ์†Œ {res['์ ˆ๊ฐ๋ฅ '].min():.1%}({res.loc[res['์ ˆ๊ฐ๋ฅ '].idxmin(), '์‹œ๋‚˜๋ฆฌ์˜ค']})\")\n", "\n", "# ---------------- 2) ๋ฐฑํ…Œ์ŠคํŠธ: ์„œ๋กœ ๋‹ค๋ฅธ ๋ณด์œ  ์žฅ๋น„ 5์„ธํŠธ ----------------\n", "bt = []\n", "for sd in (10, 11, 12, 13, 14):\n", " _, _, _, _, rf, rm, _, _, _ = scenario(seed=sd)\n", " bt.append((sd, 1 - rm[\"total\"] / rf[\"total\"], rf[\"fail\"], rm[\"fail\"]))\n", "bt = pd.DataFrame(bt, columns=[\"์žฅ๋น„ ์„ธํŠธ\", \"์ ˆ๊ฐ๋ฅ \", \"๊ด€ํ–‰ ๊ณ ์žฅ\", \"์ œ์•ˆ ๊ณ ์žฅ\"])\n", "print(f\"\\n[2] ๋ฐฑํ…Œ์ŠคํŠธ(์„œ๋กœ ๋‹ค๋ฅธ ๋ณด์œ  ์žฅ๋น„ 5์„ธํŠธ): ํ‰๊ท  ์ ˆ๊ฐ {bt['์ ˆ๊ฐ๋ฅ '].mean():.1%}, ์ตœ์†Œ {bt['์ ˆ๊ฐ๋ฅ '].min():.1%}, ์ตœ๋Œ€ {bt['์ ˆ๊ฐ๋ฅ '].max():.1%}, ๋น„๊ณ„ํš ๊ณ ์žฅ ํ‰๊ท  {bt['๊ด€ํ–‰ ๊ณ ์žฅ'].mean():.0f} -> {bt['์ œ์•ˆ ๊ณ ์žฅ'].mean():.0f}๋Œ€\")\n", "\n", "# ---------------- 3) ๋‹จ๊ฐ€ ๋ฏผ๊ฐ๋„: ๋น„๊ณ„ํš ๊ณ ์žฅ ๋น„์šฉ ----------------\n", "sens = []\n", "base_cfail = C_FAIL\n", "for mult in (0.5, 1.0, 1.5, 2.0):\n", " C_FAIL = base_cfail * mult\n", " _, _, _, _, rf, rm, _, _, _ = scenario(seed=3)\n", " sens.append((mult, 1 - rm[\"total\"] / rf[\"total\"]))\n", "C_FAIL = base_cfail\n", "print(\"[3] ๋น„๊ณ„ํš ๊ณ ์žฅ ๋น„์šฉ ๋ฏผ๊ฐ๋„:\", \"; \".join(f\"x{m} -> ์ ˆ๊ฐ {s:.0%}\" for m, s in sens))\n", "print(\" ๊ณ ์žฅ ๋น„์šฉ์ด ๋‚ฎ์•„์ง€๋ฉด ์•ž๋‹น๊ธด ์ •๋น„์˜ ๋‚ญ๋น„๊ฐ€ ์ƒ๋Œ€์ ์œผ๋กœ ์ปค์ ธ ์ ˆ๊ฐ ํญ์ด ์ค„์–ด๋“ ๋‹ค. ๋‹จ๊ฐ€๋Š” ์žฌ์ • ๋ถ€์„œ์™€ ์‹ค์ œ ๊ฐ’์œผ๋กœ ๋‹ค์‹œ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.\")\n", "\n", "# ---------------- 4) HITL ๊ฒฐ์žฌ ์›Œํฌํ”Œ๋กœ์šฐ ----------------\n", "USERS = {\"ai_bot\": \"AI\", \"pln_kim\": \"PLN\", \"pln_lee\": \"PLN\", \"log_park\": \"LOG\", \"fin_choi\": \"FIN\", \"aud_han\": \"AUD\"}\n", "PERM = {\"AI\": {\"submit\"}, \"PLN\": {\"submit\", \"redraft\"}, \"LOG\": {\"approve\", \"reject\"}, \"FIN\": {\"approve\", \"reject\"}, \"AUD\": {\"read\"}}\n", "\n", "\n", "class Flow:\n", " def __init__(self):\n", " self.docs, self.log, self.blocked = {}, [], []\n", "\n", " def _audit(self, user, action, doc, detail):\n", " prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n", " rec = {\"seq\": len(self.log) + 1, \"user\": user, \"action\": action, \"doc\": doc, \"detail\": detail, \"prev\": prev}\n", " rec[\"hash\"] = hashlib.sha256(json.dumps(rec, ensure_ascii=False, sort_keys=True).encode()).hexdigest()[:12]\n", " self.log.append(rec)\n", "\n", " def verify(self):\n", " prev = \"GENESIS\"\n", " for r in self.log:\n", " body = {k: v for k, v in r.items() if k != \"hash\"}\n", " if r[\"prev\"] != prev or hashlib.sha256(json.dumps(body, ensure_ascii=False, sort_keys=True).encode()).hexdigest()[:12] != r[\"hash\"]:\n", " return False, r[\"seq\"]\n", " prev = r[\"hash\"]\n", " return True, None\n", "\n", " def deny(self, user, act, doc, why):\n", " self.blocked.append((user, act, doc, why)); self._audit(user, \"DENIED:\" + act, doc, why); return False\n", "\n", " def submit(self, user, kind, title, n, parent=None):\n", " need = \"redraft\" if parent else \"submit\"\n", " if need not in PERM.get(USERS.get(user), set()):\n", " return self.deny(user, need, \"-\", \"์ƒ์‹  ๊ถŒํ•œ ์—†์Œ\")\n", " did = f\"MNT{len(self.docs) + 1:02d}\"\n", " self.docs[did] = {\"kind\": kind, \"title\": title, \"line\": [\"LOG\", \"FIN\"] if kind == \"๋„ํƒœ ๊ฑด์˜\" else [\"LOG\"], \"step\": 0, \"status\": \"๊ฒ€ํ† ๋Œ€๊ธฐ\", \"drafter\": user, \"done\": [], \"parent\": parent}\n", " self._audit(user, \"submit\", did, f\"{kind} {title} {n}๋Œ€\")\n", " return did\n", "\n", " def decide(self, user, did, act, reason=\"\"):\n", " role, d = USERS.get(user), self.docs.get(did)\n", " if role is None: return self.deny(user, act, did, \"๋ฏธ๋“ฑ๋ก ์‚ฌ์šฉ์ž\")\n", " if d is None or d[\"status\"] != \"๊ฒ€ํ† ๋Œ€๊ธฐ\": return self.deny(user, act, did, \"๊ฒฐ์žฌ ๋Œ€์ƒ ์•„๋‹˜(์ฒ˜๋ฆฌ ์™„๋ฃŒ)\")\n", " if act not in PERM[role]: return self.deny(user, act, did, f\"{role} ์—ญํ• ์€ {act} ๋ถˆ๊ฐ€\")\n", " if d[\"line\"][d[\"step\"]] != role: return self.deny(user, act, did, \"๊ฒฐ์žฌ ์ˆœ์„œ ์œ„๋ฐ˜\")\n", " if user == d[\"drafter\"] or user in d[\"done\"]: return self.deny(user, act, did, \"๊ธฐ์•ˆ์žยท๊ธฐ๊ฒฐ์žฌ์ž ์žฌ๊ฒฐ์žฌ ๊ธˆ์ง€\")\n", " if len(reason.strip()) < 10: return self.deny(user, act, did, \"์‚ฌ์œ  10์ž ์ด์ƒ ํ•„์ˆ˜\")\n", " if act == \"reject\":\n", " d[\"status\"] = \"๋ฐ˜๋ ค\"\n", " else:\n", " d[\"done\"].append(user); d[\"step\"] += 1\n", " d[\"status\"] = \"์Šน์ธ\" if d[\"step\"] == len(d[\"line\"]) else \"๊ฒ€ํ† ๋Œ€๊ธฐ\"\n", " self._audit(user, act, did, reason); return True\n", "\n", " def edit(self, user, did):\n", " if self.docs[did][\"status\"] == \"์Šน์ธ\": return self.deny(user, \"edit\", did, \"์Šน์ธ ํ›„ ์ผ์ • ๋ณ€๊ฒฝ ๊ธˆ์ง€(์žฌ๊ธฐ์•ˆ)\")\n", " return True\n", "\n", "\n", "al, pred, mm = cache[\"ํ‰์ƒ\"]\n", "al = al.assign(rul_pred=pred, month=mm)\n", "scrap = al[(al[\"age_y\"] >= 14) & (al[\"rul_pred\"] < 365)] # ๋„ํƒœ ๊ฑด์˜ ํ›„๋ณด: ์‚ฌ์šฉ 14๋…„ ์ด์ƒ์ด๊ณ  1๋…„ ๋‚ด ์ˆ˜๋ช… ์ข…๋ฃŒ ์˜ˆ์ธก\n", "wf = Flow()\n", "docs = [wf.submit(\"ai_bot\" if q % 2 == 0 else \"pln_kim\", \"์ •๋น„ ์ผ์ •\", f\"{3 * q + 1}~{3 * q + 3}์›” ์ผ์ •์•ˆ\", int(((al.month >= 3 * q) & (al.month < 3 * q + 3)).sum())) for q in range(2)]\n", "docs += [wf.submit(\"pln_lee\", \"๋„ํƒœ ๊ฑด์˜\", f\"{u} ๋ถ€๋Œ€ ๋…ธํ›„ ์žฅ๋น„\", int((scrap.unit == u).sum())) for u in scrap[\"unit\"].value_counts().index[:2]]\n", "two = [i for i in docs if len(wf.docs[i][\"line\"]) == 2]\n", "print(f\"\\n[4] AI ์ถ”์ฒœ์•ˆ {len(docs)}๊ฑด(์ •๋น„ ์ผ์ • 2๊ฑด, ๋„ํƒœ ๊ฑด์˜ {len(two)}๊ฑด; ๋„ํƒœ ํ›„๋ณด {len(scrap)}๋Œ€) ์ „๋ถ€ '๊ฒ€ํ† ๋Œ€๊ธฐ' ๋“ฑ๋ก, ์ž๋™ ์Šน์ธ {sum(wf.docs[i]['status'] == '์Šน์ธ' for i in docs)}๊ฑด (๋ชฉํ‘œ 0๊ฑด). 2๋‹จ๊ณ„ ๊ฒฐ์žฌ(๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ + ์žฌ์ • ๋‹ด๋‹น) ๋Œ€์ƒ {two}\")\n", "wf.decide(\"log_park\", docs[0], \"reject\", \"1~3์›” ์ •๋น„ ์ˆœ์„œ์— ํ‚คํŠธ ์œ ํšจ๊ธฐ๊ฐ„ ์žฌํ™•์ธ ํ•„์š”, ์žฌ๊ธฐ์•ˆ ์š”์ฒญ\")\n", "new = wf.submit(\"pln_kim\", \"์ •๋น„ ์ผ์ •\", \"1~3์›” ์ผ์ •์•ˆ(์žฌ๊ธฐ์•ˆ)\", int(((al.month >= 0) & (al.month < 3)).sum()), parent=docs[0])\n", "wf.decide(\"log_park\", new, \"approve\", \"ํ‚คํŠธ ์œ ํšจ๊ธฐ๊ฐ„ ํ™•์ธ ์™„๋ฃŒ, ์ผ์ • ํ™•์ •\")\n", "wf.decide(\"log_park\", docs[1], \"approve\", \"4~6์›” ์ผ์ •, ์ •๋น„์ฐฝ ๋Šฅ๋ ฅ ๋ฒ”์œ„ ๋‚ด ํ™•์ธํ•จ\")\n", "if two:\n", " wf.decide(\"log_park\", two[0], \"approve\", \"๋„ํƒœ ๊ฑด์˜ ์‚ฌ์œ  ํƒ€๋‹น, ์žฌ์ • ๋‹ด๋‹น ๊ฒ€ํ†  ์ƒ์‹ \")\n", " wf.decide(\"fin_choi\", two[0], \"approve\", \"์ž”์กด ๊ฐ€์น˜ ๋Œ€๋น„ ์ˆ˜๋ฆฌ๋น„ ์ดˆ๊ณผ ํ™•์ธ, ๋„ํƒœ ๊ฑด์˜ ๋™์˜\")\n", "tgt = two[-1] if two else docs[-1]\n", "attacks = [(\"pln_kim\", tgt, \"approve\", \"์ƒ์‹ ์ž๊ฐ€ ์ง์ ‘ ์Šน์ธํ•œ๋‹ค๊ณ  ์ฒ˜๋ฆฌํ•จ\"), (\"ai_bot\", tgt, \"approve\", \"AI ๊ณ„์ •์ด ์ง์ ‘ ์Šน์ธํ•˜๋ ค๊ณ  ์‹œ๋„ํ•จ\"), (\"aud_han\", tgt, \"approve\", \"๊ฐ์‚ฌ ๊ณ„์ •์œผ๋กœ ์Šน์ธ ์‹œ๋„ํ•จ\"), (\"ghost\", tgt, \"approve\", \"์™ธ๋ถ€ ๊ณ„์ •์œผ๋กœ ์Šน์ธ ์‹œ๋„ํ•จ\"),\n", " (\"log_park\", tgt, \"approve\", \"\"), (\"log_park\", tgt, \"reject\", \"์งง์Œ\"), (\"fin_choi\", tgt, \"approve\", \"์žฌ์ • ๋‹ด๋‹น์ด ์ˆœ์„œ๋ฅผ ๊ฑด๋„ˆ๋›ฐ์–ด ์Šน์ธ\"), (\"log_park\", new, \"reject\", \"์Šน์ธ๋œ ์ผ์ •์„ ๋’ค์ง‘์–ด ๋ฐ˜๋ คํ•จ\"),\n", " (\"pln_lee\", tgt, \"approve\", \"์ƒ์‹ ์ž ๊ณ„์ •์œผ๋กœ ์Šน์ธ ์‹œ๋„ํ•จ\")]\n", "for u, doc, a, rs in attacks:\n", " wf.decide(u, doc, a, rs)\n", "wf.edit(\"pln_kim\", new)\n", "print(f\" ๊ถŒํ•œ ์šฐํšŒ ์‹œ๋„ 10๊ฑด ์ค‘ ์ฐจ๋‹จ {len(wf.blocked)}๊ฑด (์ฐจ๋‹จ์œจ {len(wf.blocked) / 10:.0%}, ๋ชฉํ‘œ 100%)\")\n", "for u, a, doc, why in wf.blocked[:4]:\n", " print(f\" ์ฐจ๋‹จ {u:<9} {a:<8} {doc}: {why}\")\n", "ok, _ = wf.verify()\n", "snap = json.loads(json.dumps(wf.log))\n", "wf.log[3][\"detail\"] = \"์‚ฌํ›„์— ๊ณ ์ณ ์“ด ์‚ฌ์œ \"\n", "ok2, at = wf.verify()\n", "wf.log = snap\n", "print(f\" ๊ฐ์‚ฌ ๋กœ๊ทธ {len(wf.log)}๊ฑด ํ•ด์‹œ ์ฒด์ธ ๋ฌด๊ฒฐ์„ฑ {'100%' if ok else 'ํ›ผ์†'}; 4๋ฒˆ ๊ธฐ๋ก ์‚ฌํ›„ ์ˆ˜์ • ์‹œ {'ํ†ต๊ณผ' if ok2 else f'์œ„๋ณ€์กฐ ๊ฒ€์ถœ({at}๋ฒˆ)'}\")\n", "print(f\" {docs[0]}(๋ฐ˜๋ ค) -> {new}(์žฌ๊ธฐ์•ˆ, parent ์—ฐ๊ฒฐ) ์ด๋ ฅ:\", [(r['action'], r['user']) for r in wf.log if r['doc'] in (docs[0], new)])\n", "print(\" ๋ฌธ์„œ ์ƒํƒœ:\", pd.Series({k: v[\"status\"] for k, v in wf.docs.items()}).value_counts().to_dict(), \"| ์ •๋น„ ์ผ์ • ํ™•์ •๊ณผ ์žฅ๋น„ ๋„ํƒœ๋Š” ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ๊ฒฐ์žฌ(๋„ํƒœ๋Š” ์žฌ์ • ๋‹ด๋‹น ํฌํ•จ)๋กœ๋งŒ ํ™•์ •๋œ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(12, 4))\n", "x = np.arange(len(res)); ax[0].bar(x - .2, res[\"๊ด€ํ–‰ ๋น„์šฉ(๋งŒ์›)\"], .4, label=\"fixed-cycle\"); ax[0].bar(x + .2, res[\"์ œ์•ˆ ๋น„์šฉ(๋งŒ์›)\"], .4, label=\"risk-based plan\")\n", "ax[0].set_xticks(x); ax[0].set_xticklabels([f\"S{i + 1}\" for i in x]); ax[0].legend(); ax[0].set_title(\"12-month cost by scenario (10k KRW)\")\n", "ax[1].plot([s[0] for s in sens], [s[1] * 100 for s in sens], \"o-\"); ax[1].set_title(\"Saving (%) vs unplanned-failure cost multiplier\"); ax[1].set_xlabel(\"multiplier\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 07 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ 12๊ฐœ์›” ๋น„์šฉ ๋น„๊ต(๊ด€ํ–‰ = ๊ทœ์ • ์ฃผ๊ธฐ ๋„๋‹ฌ ์ˆœ ๊ณ ์ • ์ผ์ •, ๋‹จ๊ฐ€๋Š” ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\n", " ์‹œ๋‚˜๋ฆฌ์˜ค ์ƒ์กด ์žฅ๋น„ ๊ด€ํ–‰ ๋น„์šฉ(๋งŒ์›) ์ œ์•ˆ ๋น„์šฉ(๋งŒ์›) ์ ˆ๊ฐ๋ฅ  ๊ด€ํ–‰ ๋น„๊ณ„ํš ๊ณ ์žฅ ์ œ์•ˆ ๋น„๊ณ„ํš ๊ณ ์žฅ ๊ด€ํ–‰ ํ‚คํŠธ ๋งŒ๋ฃŒ ํ๊ธฐ ์ œ์•ˆ ํ‚คํŠธ ๋งŒ๋ฃŒ ํ๊ธฐ ์ œ์•ˆ ํ‚คํŠธ ์œ ํšจ๊ธฐ๊ฐ„ ๋ถ€์กฑ\n", " ํ‰์ƒ 601 184752.0 116447.0 37.0% 47 21 10 10 0\n", " ์ •๋น„์ฐฝ ๋Šฅ๋ ฅ 70%(15๋Œ€/์›”) 601 184691.0 122971.0 33.4% 47 25 15 15 0\n", " ์˜ˆ์‚ฐ ์‚ญ๊ฐ(์›” 10,000๋งŒ์›) 601 184655.0 124543.0 32.6% 47 26 17 17 0\n", "์‹œํšจ์„ฑ ํ‚คํŠธ ์žฌ๊ณ  ์ ˆ๋ฐ˜ยท๋‹จ๊ธฐ ์œ ํšจ 601 184752.0 114897.0 37.8% 47 25 0 0 0\n", " ๊ฐ€๋™๋ฅ  +30% 601 225885.0 149598.0 33.8% 61 29 10 10 0\n", " ๋‹ค๋ฅธ ๋ณด์œ  ์žฅ๋น„(์‹œ๋“œ 4) 590 164663.0 102951.0 37.5% 46 19 17 10 0\n", " ์‹œ๋‚˜๋ฆฌ์˜ค 6์ข… ํ‰๊ท  ์ ˆ๊ฐ 35.3% (๋ชฉํ‘œ 15% ์ด์ƒ: ๋‹ฌ์„ฑ), ์ตœ์†Œ 32.6%(์˜ˆ์‚ฐ ์‚ญ๊ฐ(์›” 10,000๋งŒ์›))\n", "\n", "[2] ๋ฐฑํ…Œ์ŠคํŠธ(์„œ๋กœ ๋‹ค๋ฅธ ๋ณด์œ  ์žฅ๋น„ 5์„ธํŠธ): ํ‰๊ท  ์ ˆ๊ฐ 35.4%, ์ตœ์†Œ 32.4%, ์ตœ๋Œ€ 41.6%, ๋น„๊ณ„ํš ๊ณ ์žฅ ํ‰๊ท  54 -> 27๋Œ€\n", "[3] ๋น„๊ณ„ํš ๊ณ ์žฅ ๋น„์šฉ ๋ฏผ๊ฐ๋„: x0.5 -> ์ ˆ๊ฐ 35%; x1.0 -> ์ ˆ๊ฐ 37%; x1.5 -> ์ ˆ๊ฐ 41%; x2.0 -> ์ ˆ๊ฐ 45%\n", " ๊ณ ์žฅ ๋น„์šฉ์ด ๋‚ฎ์•„์ง€๋ฉด ์•ž๋‹น๊ธด ์ •๋น„์˜ ๋‚ญ๋น„๊ฐ€ ์ƒ๋Œ€์ ์œผ๋กœ ์ปค์ ธ ์ ˆ๊ฐ ํญ์ด ์ค„์–ด๋“ ๋‹ค. ๋‹จ๊ฐ€๋Š” ์žฌ์ • ๋ถ€์„œ์™€ ์‹ค์ œ ๊ฐ’์œผ๋กœ ๋‹ค์‹œ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.\n", "\n", "[4] AI ์ถ”์ฒœ์•ˆ 4๊ฑด(์ •๋น„ ์ผ์ • 2๊ฑด, ๋„ํƒœ ๊ฑด์˜ 2๊ฑด; ๋„ํƒœ ํ›„๋ณด 22๋Œ€) ์ „๋ถ€ '๊ฒ€ํ† ๋Œ€๊ธฐ' ๋“ฑ๋ก, ์ž๋™ ์Šน์ธ 0๊ฑด (๋ชฉํ‘œ 0๊ฑด). 2๋‹จ๊ณ„ ๊ฒฐ์žฌ(๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ + ์žฌ์ • ๋‹ด๋‹น) ๋Œ€์ƒ ['MNT03', 'MNT04']\n", " ๊ถŒํ•œ ์šฐํšŒ ์‹œ๋„ 10๊ฑด ์ค‘ ์ฐจ๋‹จ 10๊ฑด (์ฐจ๋‹จ์œจ 100%, ๋ชฉํ‘œ 100%)\n", " ์ฐจ๋‹จ pln_kim approve MNT04: PLN ์—ญํ• ์€ approve ๋ถˆ๊ฐ€\n", " ์ฐจ๋‹จ ai_bot approve MNT04: AI ์—ญํ• ์€ approve ๋ถˆ๊ฐ€\n", " ์ฐจ๋‹จ aud_han approve MNT04: AUD ์—ญํ• ์€ approve ๋ถˆ๊ฐ€\n", " ์ฐจ๋‹จ ghost approve MNT04: ๋ฏธ๋“ฑ๋ก ์‚ฌ์šฉ์ž\n", " ๊ฐ์‚ฌ ๋กœ๊ทธ 20๊ฑด ํ•ด์‹œ ์ฒด์ธ ๋ฌด๊ฒฐ์„ฑ 100%; 4๋ฒˆ ๊ธฐ๋ก ์‚ฌํ›„ ์ˆ˜์ • ์‹œ ์œ„๋ณ€์กฐ ๊ฒ€์ถœ(4๋ฒˆ)\n", " MNT01(๋ฐ˜๋ ค) -> MNT05(์žฌ๊ธฐ์•ˆ, parent ์—ฐ๊ฒฐ) ์ด๋ ฅ: [('submit', 'ai_bot'), ('reject', 'log_park'), ('submit', 'pln_kim'), ('approve', 'log_park'), ('DENIED:reject', 'log_park'), ('DENIED:edit', 'pln_kim')]\n", " ๋ฌธ์„œ ์ƒํƒœ: {'์Šน์ธ': 3, '๋ฐ˜๋ ค': 1, '๊ฒ€ํ† ๋Œ€๊ธฐ': 1} | ์ •๋น„ ์ผ์ • ํ™•์ •๊ณผ ์žฅ๋น„ ๋„ํƒœ๋Š” ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ๊ฒฐ์žฌ(๋„ํƒœ๋Š” ์žฌ์ • ๋‹ด๋‹น ํฌํ•จ)๋กœ๋งŒ ํ™•์ •๋œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 08. ์ „์ฒด ๊ตฐ๋‹จ ๋ณด์œ  ์žฅ๋น„ 1๋งŒ๋Œ€ ์ˆ˜๋ช… ์ผ๊ด„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋ณด์œ  ์žฅ๋น„ 1๋งŒ๋Œ€ ์ˆ˜๋ช… ์ผ๊ด„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ณด์œ  ์žฅ๋น„ 1๋งŒ ๋Œ€ ์ˆ˜๋ช… ์ผ๊ด„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ถ€ํ•˜ ์‹œํ—˜๊ณผ ๊ฒฝ๋ณด ์ •ํ™•๋„ ์‹œํ—˜์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋ณด์œ  ์žฅ๋น„ 1์ฒœ~10๋งŒ ๋Œ€ ๊ทœ๋ชจ์—์„œ ํ”ผ์ฒ˜ ์ƒ์„ฑ, ์ˆ˜๋ช… ์˜ˆ์ธก, ์ผ์ • ๋ฐฐ์ •, ๋Œ€์‹œ๋ณด๋“œ ์ง‘๊ณ„ ์‹œ๊ฐ„์„ ์žฐ๋‹ค ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/dashboard/sup02_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์ฒœ~10๋งŒ ๋Œ€์˜ ์ˆ˜๋ช…(RUL) ์ผ๊ด„ ์˜ˆ์ธกยท์œ„ํ—˜ ๊ฒฝ๋ณดยท๊ณ„ํš์ •๋น„ ์ผ์ • ๋ฐฐ์ •ยท๋Œ€์‹œ๋ณด๋“œ ์ง‘๊ณ„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์œผ๋กœ ๋‹จ๊ณ„๋ณ„ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„๊ณผ ๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ ์‹œ๊ฐ„์„ ์žฌ๊ณ , ๊ฒฐ์ธกยทํ™˜๊ฒฝ ์˜ค๊ธฐ์žฌยท๋‹จ์œ„ ์˜ค๋ฅ˜ ์ฃผ์ž… ์กฐ๊ฑด์—์„œ ์œ„ํ—˜ ๊ฒฝ๋ณด ์ •ํ™•๋„๋ฅผ ์ธก์ •ํ•˜๋ฉฐ ์ •์ˆ˜๊ณ„ํš์˜ ๊ทœ๋ชจ ํ•œ๊ณ„์™€ ๋Œ€์‘(ํƒ์š• ๋ฐฐ์ •)์„ ์ •๋ฆฌํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์žฅ๋น„ ๋ณด์œ  ํ˜„ํ™ฉยท๊ณ ์žฅ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ˆ˜์น˜ ์—†์Œ)\n", "# ๊ฐ€์ƒ ๋ถ€๋Œ€ 6๊ฐœ(U1~U6)์˜ ์žฅ๋น„๊ฐ€ ์šด์šฉ ํ™˜๊ฒฝ(normal/rough/salt/cold)์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์†๋„๋กœ ๋…ธํ›„ํ™”ํ•œ๋‹ค. ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„(h_since)์ด\n", "# ์žฅ๋น„๋งˆ๋‹ค ์ˆจ์€ ์ˆ˜๋ช…(t_fail)์„ ๋„˜์œผ๋ฉด ๊ณ ์žฅ(event=1) ์ด๋ ฅ, ๋„˜์ง€ ์•Š์•˜์œผ๋ฉด ํ˜„์žฌ๊นŒ์ง€ ์ƒ์กด(event=0, ๊ด€์ธก ์ค‘๋„์ ˆ๋‹จ)์ด๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ENVS = [\"normal\", \"rough\", \"salt\", \"cold\"]\n", "ENV_W = {\"normal\": 1.00, \"rough\": 1.35, \"salt\": 1.25, \"cold\": 1.12} # ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜(์ˆจ์€ ์ •๋‹ต, ๊ฐ€์ •๊ฐ’): ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ์งง๋‹ค\n", "ENV_KO = {\"normal\": \"์ผ๋ฐ˜\", \"rough\": \"ํ—˜์ง€\", \"salt\": \"์—ผ๋ถ„\", \"cold\": \"ํ˜นํ•œ\"}\n", "UNITS = [\"U1\", \"U2\", \"U3\", \"U4\", \"U5\", \"U6\"]\n", "UNIT_ENV = {\"U1\": [.7, .1, .1, .1], \"U2\": [.2, .6, .1, .1], \"U3\": [.2, .1, .6, .1], \"U4\": [.3, .1, .1, .5], \"U5\": [.5, .3, .1, .1], \"U6\": [.4, .1, .4, .1]}\n", "ETYPES = [\"truck\", \"generator\", \"trailer\", \"radio\"]\n", "CYCLE = 8000.0 # ๊ทœ์ • ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ(์šด์šฉ์‹œ๊ฐ„ h, ๊ฐ€์ •๊ฐ’)\n", "KSHAPE = 12.0 # ์ˆ˜๋ช… ๋ถ„ํฌ ์™€์ด๋ธ” ํ˜•์ƒ(๋งˆ๋ชจํ˜• ๊ณ ์žฅ, ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ๋ชจ์—ฌ ์žˆ๋‹ค)\n", "INSP_NOISE = 0.025 # ์ •๋น„ ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ ์ธก์ • ์˜ค์ฐจ(๊ฐ€์ •๊ฐ’)\n", "\n", "\n", "def make_fleet(n=900, seed=0, env_w=None, shift=1.0):\n", " \"\"\"์žฅ๋น„ n๋Œ€. env_w๋กœ ์ˆจ์€ ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜๋ฅผ ๋ฐ”๊ฟ” ์šด์˜ ๋ถ„ํฌ ๋ณ€ํ™”๋ฅผ ๋ชจ์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. shift>1์ด๋ฉด ๊ฐ€๋™๋ฅ ์ด ๋†’์•„์ง„๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " w = env_w or ENV_W\n", " unit = rng.choice(UNITS, n)\n", " env = np.array([rng.choice(ENVS, p=UNIT_ENV[u]) for u in unit])\n", " et = rng.choice(ETYPES, n, p=[.4, .2, .2, .2])\n", " rate = rng.lognormal(np.log(3.0 * shift), 0.25, n) # ์ผ ํ‰๊ท  ์šด์šฉ์‹œ๊ฐ„(h/์ผ)\n", " h_since = rng.uniform(300, 11500, n) # ๋งˆ์ง€๋ง‰ ์ฐฝ์ •๋น„ ์ดํ›„ ์šด์šฉ์‹œ๊ฐ„\n", " n_ovh = rng.integers(0, 3, n)\n", " hours = h_since + n_ovh * rng.uniform(5000, 8000, n) # ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„\n", " km = hours * rng.normal(17, 3, n) * np.where(env == \"rough\", 0.8, 1.0) # ๋ˆ„์  ๊ธฐ๋™๊ฑฐ๋ฆฌ(km)\n", " age_y = np.clip(hours / (rate * 365) * rng.uniform(0.9, 1.15, n), 0.5, 25)\n", " unpl = rng.poisson(0.35 * h_since / 1000 * np.array([w[e] for e in env]) / 1.3) # ๋น„๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜(ํ™˜๊ฒฝ์ด ๊ฐ€ํ˜นํ• ์ˆ˜๋ก ๋งŽ๋‹ค)\n", " n_maint = rng.poisson(0.6 * h_since / 1000 + 1) # ๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜\n", " scale = 9600 / np.array([w[e] for e in env]) * 0.985 ** unpl * np.exp(rng.normal(0, 0.04, n))\n", " t_fail = scale * rng.weibull(KSHAPE, n) # ์ฐฝ์ •๋น„ ํ›„ ๊ณ ์žฅ๊นŒ์ง€ ์šด์šฉ์‹œ๊ฐ„(์ˆจ์€ ์ •๋‹ต)\n", " event = (t_fail <= h_since).astype(int)\n", " dur = np.where(event == 1, t_fail, h_since)\n", " df = pd.DataFrame({\"eq_id\": [f\"EQ{seed:02d}{i:04d}\" for i in range(n)], \"unit\": unit, \"env\": env, \"etype\": et, \"rate\": rate, \"hours\": hours, \"km\": km, \"age_y\": age_y, \"n_ovh\": n_ovh,\n", " \"h_since\": h_since, \"unpl\": unpl, \"n_maint\": n_maint, \"t_fail\": t_fail, \"event\": event, \"dur\": dur})\n", " df[\"insp\"] = np.clip(dur / t_fail + rng.normal(0, INSP_NOISE, n), 0, 1.3) # ๋งˆ์ง€๋ง‰ ์ •๋น„ ์ ๊ฒ€์—์„œ ์ธก์ •ํ•œ ๋งˆ๋ชจ ๋น„์œจ(์†Œ๋ชจ ์ˆ˜๋ช…/์ด ์ˆ˜๋ช…, ์˜ค์ฐจ ํฌํ•จ)\n", " df[\"rul_h\"] = np.where(event == 0, t_fail - h_since, np.nan) # ์ƒ์กด ์žฅ๋น„์˜ ๋‚จ์€ ์šด์šฉ์‹œ๊ฐ„(์ •๋‹ต)\n", " df[\"rul_d\"] = df[\"rul_h\"] / df[\"rate\"] # ๋‚จ์€ ์ผ์ˆ˜(์ •๋‹ต)\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " y, p = np.asarray(y, float), np.asarray(p, float)\n", " return float(np.mean(np.abs(y - p) / np.abs(y)) * 100)\n", "\n", "\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "\n", "\n", "def rmse(y, p):\n", " return float(np.sqrt(np.mean((np.asarray(y, float) - np.asarray(p, float)) ** 2)))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธํ›„ํ™” ํŒŒ์ƒ ํ”ผ์ฒ˜์™€ ํ•™์Šต ํ‘œ๋ณธ ๋งŒ๋“ค๊ธฐ. ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ W0๋Š” ๋„์ž… ์‹œ์ ์˜ ์ „๋ฌธ๊ฐ€ ์ถ”์ •๊ฐ’์ด๋ฉฐ(์‹ค์ œ ๊ฐ€์ค‘์น˜์™€ ๋‹ค๋ฆ„) M9์—์„œ ์žฌ๋ณด์ •ํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "W0 = {\"normal\": 1.00, \"rough\": 1.15, \"salt\": 1.10, \"cold\": 1.05}\n", "FEATS = [\"h_since\", \"hours\", \"km\", \"age_y\", \"rate\", \"n_maint\", \"unpl\", \"unpl_per_kh\", \"km_per_h\", \"n_ovh\", \"k_rough\", \"k_salt\", \"k_cold\", \"k_truck\", \"k_gen\", \"k_trailer\",\n", " \"aging_idx\", \"lin_rul_d\", \"hs_frac\", \"insp\", \"insp_eta_h\"]\n", "\n", "\n", "def add_features(df, w=None):\n", " d = df.copy()\n", " w = w or W0\n", " d[\"unpl_per_kh\"] = d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)\n", " d[\"km_per_h\"] = d[\"km\"] / d[\"hours\"]\n", " for e in (\"rough\", \"salt\", \"cold\"):\n", " d[\"k_\" + e] = (d[\"env\"] == e).astype(int)\n", " d[\"k_truck\"], d[\"k_gen\"], d[\"k_trailer\"] = (d[\"etype\"] == \"truck\").astype(int), (d[\"etype\"] == \"generator\").astype(int), (d[\"etype\"] == \"trailer\").astype(int)\n", " d[\"aging_idx\"] = d[\"h_since\"] * d[\"env\"].map(w) / CYCLE # ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜(1์ด๋ฉด ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ์— ๋„๋‹ฌ)\n", " d[\"lin_rul_d\"] = (CYCLE - d[\"h_since\"]) / d[\"rate\"] # ์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ์ž”์—ฌ ์ผ์ˆ˜(Baseline)\n", " d[\"hs_frac\"] = d[\"h_since\"] / CYCLE\n", " d[\"insp_eta_h\"] = d[\"h_since\"] / np.maximum(d[\"insp\"], 0.05) # ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ๋กœ ํ™˜์‚ฐํ•œ ์ด ์ˆ˜๋ช… ์ถ”์ •(h)\n", " return d\n", "\n", "\n", "def snapshots(df, k=4, seed=0):\n", " \"\"\"๊ณ ์žฅ ์ด๋ ฅ ์žฅ๋น„์˜ ๊ณผ๊ฑฐ ์‹œ์  ํ‘œ๋ณธ: ๊ณ ์žฅ ์ „ ์ž„์˜ ์‹œ์ ์˜ ์ ๊ฒ€๊ฐ’ยท์šด์šฉ์‹œ๊ฐ„๊ณผ ๊ทธ๋•Œ์˜ ๋‚จ์€ ์ผ์ˆ˜. (์ƒ์กด ์žฅ๋น„์˜ ๋ฏธ๋ž˜๋Š” ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๊ณ ์žฅ ์ด๋ ฅ๋งŒ ์“ด๋‹ค)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " f = df[df[\"event\"] == 1]\n", " out = []\n", " for _ in range(k):\n", " s = f.copy()\n", " h = s[\"t_fail\"] * rng.uniform(0.2, 0.98, len(s))\n", " ratio = h / s[\"h_since\"]\n", " s[\"hours\"] = s[\"hours\"] - (s[\"h_since\"] - h)\n", " s[\"km\"] = s[\"km\"] * ratio.clip(upper=1)\n", " s[\"unpl\"] = np.floor(s[\"unpl\"] * ratio)\n", " s[\"n_maint\"] = np.floor(s[\"n_maint\"] * ratio)\n", " s[\"insp\"] = np.clip(h / s[\"t_fail\"] + rng.normal(0, INSP_NOISE, len(s)), 0, 1.3)\n", " s[\"h_since\"] = h\n", " s[\"rul_d\"] = (s[\"t_fail\"] - h) / s[\"rate\"]\n", " s[\"event\"] = 0\n", " out.append(s)\n", " return pd.concat(out, ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ƒ์กด ๋ถ„์„ ๋„๊ตฌ(scipy๋งŒ ์‚ฌ์šฉ): ์™€์ด๋ธ” AFT(๊ฐ€์† ์ˆ˜๋ช…) ๋ชจํ˜•๊ณผ Cox ๋น„๋ก€์œ„ํ—˜ ๋ชจํ˜•\n", "# ---------------------------------------------------------------------------\n", "from scipy.optimize import minimize\n", "\n", "\n", "def aft_x(d):\n", " return np.c_[np.ones(len(d)), d[\"env\"].eq(\"rough\"), d[\"env\"].eq(\"salt\"), d[\"env\"].eq(\"cold\"), d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)].astype(float)\n", "\n", "\n", "AFT_COLS = [\"์ ˆํŽธ\", \"ํ—˜์ง€\", \"์—ผ๋ถ„\", \"ํ˜นํ•œ\", \"๋น„๊ณ„ํš ์ •๋น„์œจ\"]\n", "\n", "\n", "def fit_aft(d):\n", " \"\"\"์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„ dur(๊ณ ์žฅ์ด๋ฉด event=1, ์ƒ์กด์ด๋ฉด ์ค‘๋„์ ˆ๋‹จ)์— ๋Œ€ํ•œ ์™€์ด๋ธ” AFT ์ตœ๋Œ€์šฐ๋„ ์ถ”์ •. ๋ฐ˜ํ™˜: ๊ณ„์ˆ˜(๋งˆ์ง€๋ง‰์€ log ํ˜•์ƒ).\"\"\"\n", " x, t, e = aft_x(d), d[\"dur\"].values, d[\"event\"].values\n", "\n", " def nll(p):\n", " lam, k = np.exp(x @ p[:-1]), np.exp(p[-1])\n", " z = (t / lam) ** k\n", " return -(e * (np.log(k) - np.log(lam) + (k - 1) * (np.log(t) - np.log(lam))) - z).sum()\n", "\n", " r = minimize(nll, np.r_[np.log(8000), np.zeros(x.shape[1] - 1), np.log(5)], method=\"BFGS\")\n", " return r.x\n", "\n", "\n", "def aft_pred(p, d, q=0.5, h=None):\n", " \"\"\"์ด๋ฏธ h(์šด์šฉ์‹œ๊ฐ„)๊นŒ์ง€ ์ƒ์กดํ–ˆ๋‹ค๋Š” ์กฐ๊ฑด์—์„œ ๋ถ„์œ„์ˆ˜ q ์ƒ์กด ์‹œ์ (์šด์šฉ์‹œ๊ฐ„ h ๊ธฐ์ค€ ์ด ์ˆ˜๋ช…)์„ ๋Œ๋ ค์ค€๋‹ค. q=0.5๊ฐ€ ์กฐ๊ฑด๋ถ€ ์ค‘์•™๊ฐ’.\"\"\"\n", " lam, k = np.exp(aft_x(d) @ p[:-1]), np.exp(p[-1])\n", " h = d[\"h_since\"].values if h is None else h\n", " return lam * ((h / lam) ** k - np.log(q)) ** (1 / k)\n", "\n", "\n", "def aft_fail_prob(p, d, h0, h1):\n", " \"\"\"h0๊นŒ์ง€ ์ƒ์กดํ•œ ์žฅ๋น„๊ฐ€ h0~h1 ์‚ฌ์ด์— ๊ณ ์žฅ ๋‚  ์กฐ๊ฑด๋ถ€ ํ™•๋ฅ .\"\"\"\n", " lam, k = np.exp(aft_x(d) @ p[:-1]), np.exp(p[-1])\n", " s0, s1 = np.exp(-(h0 / lam) ** k), np.exp(-(h1 / lam) ** k)\n", " return 1 - s1 / s0\n", "\n", "\n", "def fit_cox(d, iters=25):\n", " \"\"\"Cox ๋น„๋ก€์œ„ํ—˜(Breslow ๊ทผ์‚ฌ) ๋‰ดํ„ด-๋žฉ์Šจ. ๊ณต๋ณ€๋Ÿ‰: ํ—˜์ง€ยท์—ผ๋ถ„ยทํ˜นํ•œยท๋น„๊ณ„ํš ์ •๋น„์œจ. ๋ฐ˜ํ™˜: ๊ณ„์ˆ˜.\"\"\"\n", " x = aft_x(d)[:, 1:]\n", " o = np.argsort(-d[\"dur\"].values)\n", " x, e = x[o], d[\"event\"].values[o]\n", " b = np.zeros(x.shape[1])\n", " for _ in range(iters):\n", " r = np.exp(x @ b)\n", " S0 = np.cumsum(r)\n", " S1 = np.cumsum(r[:, None] * x, axis=0)\n", " S2 = np.cumsum(r[:, None, None] * x[:, :, None] * x[:, None, :], axis=0)\n", " m1 = S1 / S0[:, None]\n", " g = ((x - m1) * e[:, None]).sum(axis=0)\n", " H = -sum(e[i] * (S2[i] / S0[i] - np.outer(m1[i], m1[i])) for i in np.where(e == 1)[0])\n", " step = np.linalg.solve(H - 1e-6 * np.eye(len(b)), g)\n", " b = b - step\n", " if np.abs(step).max() < 1e-7:\n", " break\n", " return b\n", "\n", "\n", "def cindex(time, event, risk):\n", " \"\"\"์ผ์น˜๋„ C-index: ์œ„ํ—˜ ์ ์ˆ˜๊ฐ€ ๋†’์€ ์ชฝ์ด ๋จผ์ € ๊ณ ์žฅ ๋‚ฌ๋Š”๊ฐ€(๊ด€์ธก ๊ฐ€๋Šฅํ•œ ์Œ๋งŒ).\"\"\"\n", " t, e, r = np.asarray(time), np.asarray(event), np.asarray(risk)\n", " num = den = 0.0\n", " for i in np.where(e == 1)[0]:\n", " m = t > t[i]\n", " den += m.sum()\n", " num += (r[m] < r[i]).sum() + 0.5 * (r[m] == r[i]).sum()\n", " return num / max(den, 1)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ž”์—ฌ ์ˆ˜๋ช…(RUL, ์ผ) ์˜ˆ์ธก ์—”์ง„: ์™€์ด๋ธ” AFT ์กฐ๊ฑด๋ถ€ ์ค‘์•™๊ฐ’ + ๊ทธ๋ž˜๋””์–ธํŠธ ๋ถ€์ŠคํŒ…(๊ณ ์žฅ ์ด๋ ฅ ์‹œ์  ํ‘œ๋ณธ ํ•™์Šต)์˜ ํ‰๊ท \n", "# ---------------------------------------------------------------------------\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "\n", "\n", "def fit_engine(train, seed=0):\n", " p = fit_aft(train)\n", " sn = add_features(snapshots(train, 5, seed))\n", " sn[\"aft\"] = (aft_pred(p, sn) - sn[\"h_since\"]) / sn[\"rate\"]\n", " g = GradientBoostingRegressor(n_estimators=250, max_depth=3, learning_rate=0.06, random_state=0).fit(sn[FEATS + [\"aft\"]], sn[\"rul_d\"])\n", " return {\"p\": p, \"g\": g}\n", "\n", "\n", "def predict_rul(eng, d):\n", " \"\"\"d๋Š” add_features๋ฅผ ๊ฑฐ์นœ ํ‘œ. ๋ฐ˜ํ™˜: (์•™์ƒ๋ธ” RUL, AFT RUL, GBR RUL) - ์ผ ๋‹จ์œ„.\"\"\"\n", " aft = ((aft_pred(eng[\"p\"], d) - d[\"h_since\"]) / d[\"rate\"]).values\n", " x = d[FEATS].copy()\n", " x[\"aft\"] = aft\n", " gb = eng[\"g\"].predict(x)\n", " return np.clip(0.5 * aft + 0.5 * gb, 1, None), aft, gb\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๊ณ„ํš์ •๋น„(์ฐฝ์ •๋น„) ์ผ์ • ์ถ”์ฒœ: ์›”๋ณ„ ์ •๋น„์ฐฝ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐยท์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ(์œ ํšจ๊ธฐํ•œ) ์ œ์•ฝ ์•„๋ž˜ ๊ธฐ๋Œ€ ๋น„์šฉ ์ตœ์†Œํ™” (๋น„์šฉ ๋‹จ๊ฐ€๋Š” ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’, ๋งŒ์›)\n", "# ---------------------------------------------------------------------------\n", "from scipy.optimize import milp, LinearConstraint, Bounds\n", "from scipy.stats import norm\n", "\n", "C_PLAN, C_FAIL, C_WASTE_KH, C_KIT = 600.0, 2400.0, 40.0, 120.0 # ๊ณ„ํš ์ฐฝ์ •๋น„, ๋น„๊ณ„ํš ๊ณ ์žฅ(๊ธด๊ธ‰ ์ •๋น„ ํฌํ•จ), ๋ฏธ์‚ฌ์šฉ ์ˆ˜๋ช… 1000h๋‹น ์†์‹ค, ์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ\n", "HORIZON, MDAYS = 12, 30 # ๊ณ„ํš 12๊ฐœ์›”, ์›” 30์ผ\n", "CAP_M, BUDGET_M = 22, 15000.0 # ์›”๋ณ„ ์ •๋น„์ฐฝ ๋Šฅ๋ ฅ(๋Œ€), ์›”๋ณ„ ์˜ˆ์‚ฐ\n", "LOTS = [(30, 70), (45, 170), (45, 390), (70, 800)] # ์‹œํšจ์„ฑ ํ‚คํŠธ ๋กœํŠธ(์ˆ˜๋Ÿ‰, ์œ ํšจ๊ธฐํ•œ๊นŒ์ง€ ๋‚จ์€ ์ผ์ˆ˜)\n", "KIT_MARGIN = 14 # ์ •๋น„์ผ ๊ธฐ์ค€ ์ตœ์†Œ ์œ ํšจ๊ธฐ๊ฐ„ ์—ฌ์œ (์ผ)\n", "\n", "\n", "def exp_cost_matrix(pred, rate, month_days):\n", " \"\"\"์žฅ๋น„ i๋ฅผ ์›” m์— ์ •๋น„ํ•  ๋•Œ์˜ ๊ธฐ๋Œ€ ๋น„์šฉ. ์ฐธ RUL ~ N(pred, sigma), sigma = 35์ผ + 0.12 x pred (๊ฐ€์ •). ๋งˆ์ง€๋ง‰ ์—ด์€ ๊ณ„ํš ๊ธฐ๊ฐ„ ๋ฐ–(๋ฏธ์ •๋น„).\"\"\"\n", " sig = 35 + 0.12 * pred\n", " C = np.zeros((len(pred), len(month_days) + 1))\n", " for j, day in enumerate(month_days):\n", " z = (day - pred) / sig\n", " pf = norm.cdf(z)\n", " unused = np.maximum(pred - day, 0) * rate * 0.6 # ์ •๋น„๋ฅผ ์•ž๋‹น๊ธด ๋งŒํผ ๋ฒ„๋ฆฌ๋Š” ์šด์šฉ์‹œ๊ฐ„ h(๊ทผ์‚ฌ)\n", " C[:, j] = C_PLAN + C_KIT + pf * (C_FAIL - C_PLAN) + C_WASTE_KH * unused / 1000 * (1 - pf)\n", " C[:, -1] = norm.cdf((HORIZON * MDAYS - pred) / sig) * C_FAIL\n", " return C\n", "\n", "\n", "def plan_milp(pred, rate, cap=CAP_M, budget=BUDGET_M, lots=LOTS, use_kit=True):\n", " \"\"\"์ •์ˆ˜๊ณ„ํš์œผ๋กœ ์žฅ๋น„๋ณ„ ์ •๋น„ ์›”(0~11) ๋˜๋Š” ๋ฏธ์ •๋น„(-1)๋ฅผ ๋ฐฐ์ •ํ•œ๋‹ค. ํ›„๋ณด๋Š” ์˜ˆ์ธก RUL์ด ๊ณ„ํš ๊ธฐ๊ฐ„ +90์ผ ์ด๋‚ด์ธ ์žฅ๋น„.\"\"\"\n", " md = np.arange(1, HORIZON + 1) * MDAYS\n", " cand = np.where(pred < HORIZON * MDAYS + 90)[0]\n", " n, M = len(cand), HORIZON\n", " C = exp_cost_matrix(pred[cand], rate[cand], md)\n", " c = C.ravel()\n", " A, lo, hi = [], [], []\n", " for i in range(n): # ์žฅ๋น„๋‹น ํ•œ ๋ฒˆ๋งŒ ๋ฐฐ์ •(์›” 12์นธ + ๋ฏธ์ •๋น„)\n", " row = np.zeros(n * (M + 1)); row[i * (M + 1):(i + 1) * (M + 1)] = 1; A.append(row); lo.append(1); hi.append(1)\n", " for m in range(M): # ์›”๋ณ„ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐ\n", " row = np.zeros(n * (M + 1)); row[m::(M + 1)] = 1\n", " A.append(row); lo.append(0); hi.append(cap)\n", " A.append(row * (C_PLAN + C_KIT)); lo.append(0); hi.append(budget)\n", " if use_kit: # ์‹œํšจ์„ฑ ํ‚คํŠธ: m์›” ์ดํ›„ ์ •๋น„ ์ˆ˜์š”๋Š” ๊ทธ ์‹œ์ ์— ์œ ํšจ๊ธฐ๊ฐ„์ด ๋‚จ์€ ๋กœํŠธ ์ˆ˜๋Ÿ‰์„ ๋„˜์„ ์ˆ˜ ์—†๋‹ค\n", " for m in range(M):\n", " sup = sum(q for q, e in lots if e >= md[m] + KIT_MARGIN)\n", " row = np.zeros(n * (M + 1))\n", " for mm in range(m, M):\n", " row[mm::(M + 1)] = 1\n", " A.append(row); lo.append(0); hi.append(sup)\n", " r = milp(c, constraints=LinearConstraint(np.array(A), lo, hi), integrality=np.ones(len(c)), bounds=Bounds(0, 1), options={\"time_limit\": 20})\n", " month = np.full(len(pred), -1)\n", " if r.x is not None:\n", " x = np.round(r.x).reshape(n, M + 1)\n", " assign = np.where(x[:, :M].sum(axis=1) > 0.5, x[:, :M].argmax(axis=1), -1)\n", " month[cand] = assign\n", " return month\n", "\n", "\n", "def plan_greedy(pred, margin=45, cap=CAP_M, budget=BUDGET_M):\n", " \"\"\"๊ฐ„๋‹จํ•œ ํƒ์š• ๋ฐฐ์ •: ์ •๋น„ ๋ชฉํ‘œ์ผ(์˜ˆ์ธก RUL - margin)์ด ๋น ๋ฅธ ์ˆœ์œผ๋กœ ๋ชฉํ‘œ์ผ ์ด์ „ ๊ฐ€์žฅ ๋Šฆ์€ ๋นˆ ์›”, ์—†์œผ๋ฉด ๊ฐ€์žฅ ์ด๋ฅธ ๋นˆ ์›”์— ๋ฐฐ์ •.\"\"\"\n", " cap = min(cap, int(budget // (C_PLAN + C_KIT))) # ์›” ์˜ˆ์‚ฐ์ด ํ—ˆ์šฉํ•˜๋Š” ๋Œ€์ˆ˜๋กœ ๋Šฅ๋ ฅ์„ ์ œํ•œ\n", " month = np.full(len(pred), -1)\n", " left = np.full(HORIZON, cap)\n", " for i in np.argsort(pred):\n", " if pred[i] > HORIZON * MDAYS + 60: break\n", " tgt = int(np.clip((pred[i] - margin) // MDAYS - 1, 0, HORIZON - 1))\n", " for m in list(range(tgt, -1, -1)) + list(range(tgt + 1, HORIZON)):\n", " if left[m] > 0:\n", " month[i] = m; left[m] -= 1; break\n", " return month\n", "\n", "\n", "def plan_fixed(lin_rul, cap=CAP_M, budget=BUDGET_M):\n", " \"\"\"๊ด€ํ–‰: ๊ทœ์ • ์ฃผ๊ธฐ ๋„๋‹ฌ ์ˆœ์„œ๋Œ€๋กœ(์„ ํ˜• ์ž”์—ฌ ์ผ์ˆ˜) ์›”๋ณ„ ๋Šฅ๋ ฅ ์•ˆ์—์„œ ๋ฐฐ์ •.\"\"\"\n", " return plan_greedy(np.asarray(lin_rul, float), margin=0, cap=cap, budget=budget)\n", "\n", "\n", "def evaluate_plan(month, true_rul, rate, cost_kit=True):\n", " \"\"\"์ฐธ RUL๋กœ ๊ณ„ํš์˜ ์‹ค์ œ ๋น„์šฉ์„ ๊ณ„์‚ฐํ•œ๋‹ค: ์ •๋น„์ผ ์ด์ „์— ๊ณ ์žฅ -> ๋น„๊ณ„ํš ๊ณ ์žฅ ๋น„์šฉ, ์•„๋‹ˆ๋ฉด ๊ณ„ํš ๋น„์šฉ + ๋ฏธ์‚ฌ์šฉ ์ˆ˜๋ช… ์†์‹ค.\"\"\"\n", " day = np.where(month >= 0, (month + 1) * MDAYS, 10 ** 9)\n", " fail = (true_rul < day) & (true_rul < HORIZON * MDAYS)\n", " planned = (month >= 0) & ~fail\n", " waste = np.where(planned, (true_rul - day) * rate * 0.6, 0) * C_WASTE_KH / 1000\n", " cost = fail * C_FAIL + planned * (C_PLAN + (C_KIT if cost_kit else 0)) + waste\n", " return {\"total\": float(cost.sum()), \"fail\": int(fail.sum()), \"planned\": int(planned.sum()), \"waste\": float(waste.sum()), \"unsched_fail\": int(((month < 0) & fail).sum())}\n", "\n", "\n", "def kit_fefo(month, lots=LOTS):\n", " \"\"\"์œ ํšจ๊ธฐํ•œ์ด ๋น ๋ฅธ ๋กœํŠธ๋ถ€ํ„ฐ ์“ฐ๋Š” FEFO๋กœ ํ‚คํŠธ๋ฅผ ๋ฐฐ์ •ํ•˜๊ณ  ์ •๋น„์ผ์— ์œ ํšจ๊ธฐ๊ฐ„์ด ๋ถ€์กฑํ•œ ๋ฐฐ์ • ์ˆ˜์™€ ๋งŒ๋ฃŒ ํ๊ธฐ ์ˆ˜๋Ÿ‰์„ ์„ผ๋‹ค.\"\"\"\n", " q = [[qty, e] for qty, e in sorted(lots, key=lambda t: t[1])]\n", " short = 0\n", " for m in sorted(month[month >= 0]):\n", " day = (m + 1) * MDAYS\n", " for lot in q:\n", " if lot[1] >= day + KIT_MARGIN and lot[0] > 0:\n", " lot[0] -= 1; break\n", " else:\n", " short += 1\n", " expired = sum(l[0] for l in q if l[1] < HORIZON * MDAYS)\n", " return {\"short\": short, \"expired_unused\": expired, \"left\": sum(l[0] for l in q)}\n", "# ---------------------------------------------------------------------------\n", "\n", "tr = make_fleet(900, seed=1)\n", "t0 = time.perf_counter()\n", "eng = fit_engine(tr)\n", "print(f\"[0] ์˜ˆ์ธก ์—”์ง„ ํ•™์Šต {time.perf_counter() - t0:.2f}์ดˆ(ํ•™์Šต {len(tr)}๋Œ€, ์‹œ์  ํ‘œ๋ณธ {int(tr.event.sum()) * 5}๊ฑด)\")\n", "ALARM_D, MARGIN = 180, 60 # ์œ„ํ—˜ ์ •์˜: ์ฐธ RUL 180์ผ ๋ฏธ๋งŒ / ๊ฒฝ๋ณด ๊ธฐ์ค€: ์˜ˆ์ธก RUL์ด 180+60์ผ ๋ฏธ๋งŒ(์˜ˆ์ธก ์˜ค์ฐจ๋ฅผ ๊ณ ๋ คํ•œ ์—ฌ์œ )\n", "\n", "\n", "def pipeline(fl):\n", " \"\"\"์ผ๊ด„ ์ฒ˜๋ฆฌ: ํ”ผ์ฒ˜ -> RUL ์˜ˆ์ธก -> ๊ฒฝ๋ณด -> ์ผ์ • ๋ฐฐ์ •(ํƒ์š•) -> ๋Œ€์‹œ๋ณด๋“œ ์ง‘๊ณ„. ๋‹จ๊ณ„๋ณ„ ์‹œ๊ฐ„์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.\"\"\"\n", " t = {}\n", " s = time.perf_counter(); d = add_features(fl); t[\"ํ”ผ์ฒ˜\"] = time.perf_counter() - s\n", " al = d[d.event == 0].reset_index(drop=True)\n", " s = time.perf_counter(); pred, _, _ = predict_rul(eng, al); t[\"์˜ˆ์ธก\"] = time.perf_counter() - s\n", " s = time.perf_counter(); month = plan_greedy(pred, cap=max(CAP_M, int(len(al) * 0.03)), budget=1e9); t[\"์ผ์ •\"] = time.perf_counter() - s\n", " s = time.perf_counter()\n", " al[\"pred\"], al[\"month\"] = pred, month\n", " summ = al.groupby(\"unit\").agg(์žฅ๋น„=(\"pred\", \"size\"), ํ‰๊ท RUL=(\"pred\", \"mean\"), ์œ„ํ—˜=(\"pred\", lambda x: int((x < ALARM_D + MARGIN).sum())))\n", " cal = pd.crosstab(al.loc[al.month >= 0, \"unit\"], al.loc[al.month >= 0, \"month\"])\n", " low = al.nsmallest(20, \"pred\")\n", " fg, ax = plt.subplots(1, 2, figsize=(9, 3.5)); ax[0].bar(summ.index, summ[\"์œ„ํ—˜\"]); ax[1].imshow(cal.values, aspect=\"auto\"); fg.canvas.draw(); plt.close(fg)\n", " t[\"๋Œ€์‹œ๋ณด๋“œ\"] = time.perf_counter() - s\n", " return al, t\n", "\n", "\n", "# ---------------- 1) ๊ทœ๋ชจ๋ณ„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ----------------\n", "rows = []\n", "for N in (1000, 5000, 10000, 50000, 100000):\n", " s = time.perf_counter(); fl = make_fleet(N, seed=100 + N % 7); tg = time.perf_counter() - s\n", " al, t = pipeline(fl)\n", " tot = t[\"ํ”ผ์ฒ˜\"] + t[\"์˜ˆ์ธก\"] + t[\"์ผ์ •\"]\n", " rows.append((N, len(al), tg, t[\"ํ”ผ์ฒ˜\"], t[\"์˜ˆ์ธก\"], t[\"์ผ์ •\"], t[\"๋Œ€์‹œ๋ณด๋“œ\"], len(al) / tot))\n", "ld = pd.DataFrame(rows, columns=[\"๋ณด์œ  ์žฅ๋น„\", \"์ƒ์กด ์žฅ๋น„\", \"์ƒ์„ฑ(์ดˆ)\", \"ํ”ผ์ฒ˜(์ดˆ)\", \"์˜ˆ์ธก(์ดˆ)\", \"์ผ์ •(์ดˆ)\", \"๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ(์ดˆ)\", \"์ฒ˜๋ฆฌ๋Ÿ‰(๋Œ€/์ดˆ)\"])\n", "print(\"\\n[1] ๊ทœ๋ชจ๋ณ„ ์ผ๊ด„ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„(์ƒ์„ฑ์€ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์šฉ์ด๋ฉฐ ์‹ค์ œ ์šด์˜์—๋Š” ์—†์Œ)\")\n", "print(ld.round(3).to_string(index=False))\n", "r10 = ld[ld[\"๋ณด์œ  ์žฅ๋น„\"] == 10000].iloc[0]\n", "print(f\" 1๋งŒ ๋Œ€ ์ผ๊ด„ ์ˆ˜๋ช… ์‹œ๋ฎฌ๋ ˆ์ด์…˜: ํ”ผ์ฒ˜+์˜ˆ์ธก+์ผ์ • {r10['ํ”ผ์ฒ˜(์ดˆ)'] + r10['์˜ˆ์ธก(์ดˆ)'] + r10['์ผ์ •(์ดˆ)']:.2f}์ดˆ, ๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ {r10['๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ(์ดˆ)']:.3f}์ดˆ(๋ชฉํ‘œ 2์ดˆ ์ด๋‚ด: {'๋‹ฌ์„ฑ' if r10['๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ(์ดˆ)'] <= 2 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# ---------------- 2) ์œ„ํ—˜ ๊ฒฝ๋ณด ์ •ํ™•๋„(1๋งŒ ๋Œ€) ----------------\n", "fl10 = make_fleet(10000, seed=5)\n", "d10 = add_features(fl10)\n", "a10 = d10[d10.event == 0].reset_index(drop=True)\n", "pred10, _, _ = predict_rul(eng, a10)\n", "\n", "\n", "def alarm_metrics(pred, truth_d, margin=MARGIN):\n", " al_, tr_ = pred < ALARM_D + margin, truth_d < ALARM_D\n", " tp, fp, fn, tn = int((al_ & tr_).sum()), int((al_ & ~tr_).sum()), int((~al_ & tr_).sum()), int((~al_ & ~tr_).sum())\n", " return (tp + tn) / len(pred), tp / max(tp + fn, 1), tp / max(tp + fp, 1), fp, fn\n", "\n", "\n", "acc, rec, prec, fp, fn = alarm_metrics(pred10, a10[\"rul_d\"].values)\n", "sw = pd.DataFrame([(m, *alarm_metrics(pred10, a10[\"rul_d\"].values, m)) for m in (0, 60, 120, 180)], columns=[\"์—ฌ์œ (์ผ)\", \"์ •ํ™•๋„\", \"์žฌํ˜„์œจ\", \"์ •๋ฐ€๋„\", \"์˜ค๊ฒฝ๋ณด\", \"๋ฏธํƒ\"])\n", "print(f\"\\n[2] ์œ„ํ—˜ ๊ฒฝ๋ณด ์ •ํ™•๋„: ์ƒ์กด {len(a10)}๋Œ€, ์‹ค์ œ ์œ„ํ—˜(์ฐธ RUL {ALARM_D}์ผ ๋ฏธ๋งŒ) {int((a10.rul_d < ALARM_D).sum())}๋Œ€({(a10.rul_d < ALARM_D).mean():.1%})\")\n", "print(\" ๊ฒฝ๋ณด ๊ธฐ์ค€(์˜ˆ์ธก RUL < 180 + ์—ฌ์œ ) ๋น„๊ต\\n\" + sw.assign(์ •ํ™•๋„=sw[\"์ •ํ™•๋„\"].map(\"{:.1%}\".format), ์žฌํ˜„์œจ=sw[\"์žฌํ˜„์œจ\"].map(\"{:.1%}\".format), ์ •๋ฐ€๋„=sw[\"์ •๋ฐ€๋„\"].map(\"{:.1%}\".format)).to_string(index=False))\n", "print(f\" ์ฑ„ํƒ ์—ฌ์œ  {MARGIN}์ผ: ์ •ํ™•๋„ {acc:.1%} (๋ชฉํ‘œ 95% ์ด์ƒ: {'๋‹ฌ์„ฑ' if acc >= .95 else '๋ฏธ๋‹ฌ'}), ์žฌํ˜„์œจ {rec:.1%}, ์ •๋ฐ€๋„ {prec:.1%}, ์˜ค๊ฒฝ๋ณด {fp}, ๋ฏธํƒ {fn}\")\n", "print(\" ์œ„ํ—˜ ์žฅ๋น„๊ฐ€ ๋“œ๋ฌผ์–ด ์ •ํ™•๋„๋งŒ์œผ๋กœ๋Š” ๋ถ€ํ’€๋ ค์ง€๋ฏ€๋กœ ์žฌํ˜„์œจยท์ •๋ฐ€๋„๋ฅผ ํ•จ๊ป˜ ๋ณธ๋‹ค. ์—ฌ์œ ๋ฅผ ๋Š˜๋ฆฌ๋ฉด ์žฌํ˜„์œจ์€ ์˜ค๋ฅด์ง€๋งŒ ์˜ค๊ฒฝ๋ณด๊ฐ€ ๋Š˜์–ด ์ •ํ™•๋„๊ฐ€ 95% ์•„๋ž˜๋กœ ๋‚ด๋ ค๊ฐ„๋‹ค. ๋†“์นœ ์žฅ๋น„๋Š” ์ •๊ธฐ ์ ๊ฒ€(์ ๊ฒ€ ๋งˆ๋ชจ)์œผ๋กœ ๋ณด์™„ํ•œ๋‹ค.\")\n", "\n", "# ---------------- 3) ์˜ค๋ฅ˜ ์ฃผ์ž… ----------------\n", "rng = np.random.default_rng(7)\n", "W_FIX = {\"normal\": 1.0, \"rough\": 1.15, \"salt\": 1.1, \"cold\": 1.05}\n", "\n", "\n", "def corrupt(kind):\n", " x = a10.copy()\n", " if kind == \"์ ๊ฒ€ ๋งˆ๋ชจ 20% ๊ฒฐ์ธก(๋Œ€์ฒด)\":\n", " m = rng.random(len(x)) < 0.2\n", " x.loc[m, \"insp\"] = (x.loc[m, \"h_since\"] / CYCLE * x.loc[m, \"env\"].map(W_FIX)).clip(0, 1.3) # ์ •์ œ ๊ทœ์น™: ๋…ธํ›„ ์ง€์ˆ˜๋กœ ๋Œ€์ฒด\n", " elif kind == \"์ ๊ฒ€ ๋งˆ๋ชจ 20% ๊ฒฐ์ธก(0์œผ๋กœ ๋ฐฉ์น˜)\":\n", " m = rng.random(len(x)) < 0.2\n", " x.loc[m, \"insp\"] = 0.0\n", " elif kind == \"ํ™˜๊ฒฝ 10% ์˜ค๊ธฐ์žฌ\":\n", " m = rng.random(len(x)) < 0.1\n", " x.loc[m, \"env\"] = rng.choice(ENVS, int(m.sum()))\n", " elif kind == \"๊ฐ€๋™๋ฅ  5% ๋‹จ์œ„ ์˜ค๋ฅ˜(x30)\":\n", " m = rng.random(len(x)) < 0.05\n", " x.loc[m, \"rate\"] = x.loc[m, \"rate\"] * 30\n", " elif kind == \"๋ณตํ•ฉ(๊ฒฐ์ธก ๋Œ€์ฒด+ํ™˜๊ฒฝ ์˜ค๊ธฐ์žฌ+๋‹จ์œ„ ์˜ค๋ฅ˜)\":\n", " m = rng.random(len(x)) < 0.2; x.loc[m, \"insp\"] = (x.loc[m, \"h_since\"] / CYCLE * x.loc[m, \"env\"].map(W_FIX)).clip(0, 1.3)\n", " m = rng.random(len(x)) < 0.1; x.loc[m, \"env\"] = rng.choice(ENVS, int(m.sum()))\n", " m = rng.random(len(x)) < 0.05; x.loc[m, \"rate\"] = x.loc[m, \"rate\"] * 30\n", " return add_features(x[[\"eq_id\", \"unit\", \"env\", \"etype\", \"rate\", \"hours\", \"km\", \"age_y\", \"n_ovh\", \"h_since\", \"unpl\", \"n_maint\", \"insp\", \"t_fail\", \"event\", \"dur\", \"rul_h\", \"rul_d\"]])\n", "\n", "\n", "rows = [(\"์ •์ƒ\", acc, rec, prec, fp, fn)]\n", "for kind in (\"์ ๊ฒ€ ๋งˆ๋ชจ 20% ๊ฒฐ์ธก(๋Œ€์ฒด)\", \"์ ๊ฒ€ ๋งˆ๋ชจ 20% ๊ฒฐ์ธก(0์œผ๋กœ ๋ฐฉ์น˜)\", \"ํ™˜๊ฒฝ 10% ์˜ค๊ธฐ์žฌ\", \"๊ฐ€๋™๋ฅ  5% ๋‹จ์œ„ ์˜ค๋ฅ˜(x30)\", \"๋ณตํ•ฉ(๊ฒฐ์ธก ๋Œ€์ฒด+ํ™˜๊ฒฝ ์˜ค๊ธฐ์žฌ+๋‹จ์œ„ ์˜ค๋ฅ˜)\"):\n", " x = corrupt(kind)\n", " p, _, _ = predict_rul(eng, x)\n", " rows.append((kind, *alarm_metrics(p, a10[\"rul_d\"].values)))\n", "er = pd.DataFrame(rows, columns=[\"์กฐ๊ฑด\", \"์ •ํ™•๋„\", \"์žฌํ˜„์œจ\", \"์ •๋ฐ€๋„\", \"์˜ค๊ฒฝ๋ณด\", \"๋ฏธํƒ\"])\n", "print(\"\\n[3] ์˜ค๋ฅ˜ ์ฃผ์ž… ์‹œ ๊ฒฝ๋ณด ์„ฑ๋Šฅ(1๋งŒ ๋Œ€)\\n\" + er.assign(์ •ํ™•๋„=er[\"์ •ํ™•๋„\"].map(\"{:.1%}\".format), ์žฌํ˜„์œจ=er[\"์žฌํ˜„์œจ\"].map(\"{:.1%}\".format), ์ •๋ฐ€๋„=er[\"์ •๋ฐ€๋„\"].map(\"{:.1%}\".format)).to_string(index=False))\n", "worst = er.iloc[1:].sort_values(\"์ •ํ™•๋„\").iloc[0]\n", "z = er[er[\"์กฐ๊ฑด\"].str.contains(\"0์œผ๋กœ\")].iloc[0]\n", "print(f\" ์ •ํ™•๋„๊ฐ€ ๊ฐ€์žฅ ๋‚ฎ์€ ์กฐ๊ฑด์€ '{worst['์กฐ๊ฑด']}'({worst['์ •ํ™•๋„']:.1%}, ์˜ค๊ฒฝ๋ณด {int(worst['์˜ค๊ฒฝ๋ณด'])}๊ฑด). ๊ฒฐ์ธก์„ 0์œผ๋กœ ๋ฐฉ์น˜ํ•˜๋ฉด ์žฌํ˜„์œจ์ด {z['์žฌํ˜„์œจ']:.1%}๋กœ ์ •์ƒ({er.iloc[0]['์žฌํ˜„์œจ']:.1%})๋ณด๋‹ค ๋–จ์–ด์ง€๋ฏ€๋กœ ์ •์ œ ๊ทœ์น™(M2)์˜ ๋Œ€์ฒด๊ฐ’ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.\")\n", "print(f\" 95% ๋ชฉํ‘œ๋Š” ์ •์ƒ ์กฐ๊ฑด์—์„œ๋งŒ ๋‹ฌ์„ฑํ•˜๋ฉฐ ์˜ค๋ฅ˜๊ฐ€ ๊ฒน์น˜๋ฉด {er.iloc[1:]['์ •ํ™•๋„'].min():.1%}๊นŒ์ง€ ๋‚ฎ์•„์ง„๋‹ค -> ์ž…๋ ฅ ๊ฒ€์ฆ(M1ยทM2)์ด ๊ฒฝ๋ณด ํ’ˆ์งˆ์˜ ์ „์ œ์ด๋‹ค.\")\n", "\n", "# ---------------- 4) ์ •์ˆ˜๊ณ„ํš ๊ทœ๋ชจ ํ•œ๊ณ„ ----------------\n", "mt = []\n", "for n in (50, 150, 300, 600):\n", " sub = a10.head(20000).copy()\n", " pr_ = np.sort(pred10)[:n] * 0 + np.linspace(30, 440, n) # ์˜ˆ์ธก RUL์ด ๊ณ„ํš ๊ธฐ๊ฐ„ ์•ˆ์— ๋ถ„ํฌํ•˜๋Š” ํ›„๋ณด n๋Œ€\n", " s = time.perf_counter(); m = plan_milp(pr_, np.full(n, 3.0), cap=max(CAP_M, n // 8), budget=1e9); dt = time.perf_counter() - s\n", " mt.append((n, dt, int((m >= 0).sum())))\n", "print(\"\\n[4] ์ •์ˆ˜๊ณ„ํš ํ›„๋ณด ์žฅ๋น„ ์ˆ˜๋ณ„ ํ’€์ด ์‹œ๊ฐ„:\", \", \".join(f\"{n}๋Œ€ {s:.2f}์ดˆ\" for n, s, _ in mt))\n", "print(\" ํ›„๋ณด๊ฐ€ ์ˆ˜์ฒœ ๋Œ€๋ฅผ ๋„˜์œผ๋ฉด ์ •์ˆ˜๊ณ„ํš์ด ๋А๋ ค์ง€๋ฏ€๋กœ ์œ„ํ—˜ ์ƒ์œ„ ํ›„๋ณด๋งŒ ์ •์ˆ˜๊ณ„ํš, ๋‚˜๋จธ์ง€๋Š” ํƒ์š• ๋ฐฐ์ •์œผ๋กœ ๋‚˜๋ˆ„๋Š” ๊ตฌ์กฐ๋ฅผ ์“ด๋‹ค. ๊ฒฝ๋ณดยท์ผ์ •์€ ํŒ๋‹จ ๋ณด์กฐ์ด๋ฉฐ ํ™•์ •์€ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ(M7)์— ๋”ฐ๋ฅธ๋‹ค.\")\n", "print(\" ๋ชจ๋“  ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ˆ˜์น˜๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 3, figsize=(15, 4.2))\n", "ax[0].loglog(ld[\"๋ณด์œ  ์žฅ๋น„\"], ld[\"ํ”ผ์ฒ˜(์ดˆ)\"] + ld[\"์˜ˆ์ธก(์ดˆ)\"] + ld[\"์ผ์ •(์ดˆ)\"], \"o-\", label=\"batch total\"); ax[0].loglog(ld[\"๋ณด์œ  ์žฅ๋น„\"], ld[\"๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ(์ดˆ)\"], \"s-\", label=\"dashboard\"); ax[0].axhline(2, c=\"r\", ls=\"--\"); ax[0].legend(); ax[0].set_title(\"Processing time vs fleet size (s)\")\n", "ax[1].barh(range(len(er)), er[\"์žฌํ˜„์œจ\"] * 100); ax[1].set_yticks(range(len(er))); ax[1].set_yticklabels([f\"C{i + 1}\" for i in range(len(er))]); ax[1].set_title(\"Alarm recall by fault case (%)\")\n", "ax[2].plot([m[0] for m in mt], [m[1] for m in mt], \"o-\"); ax[2].set_title(\"MILP solve time vs candidate units (s)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 08 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[0] ์˜ˆ์ธก ์—”์ง„ ํ•™์Šต 1.44์ดˆ(ํ•™์Šต 900๋Œ€, ์‹œ์  ํ‘œ๋ณธ 1440๊ฑด)\n", "\n", "[1] ๊ทœ๋ชจ๋ณ„ ์ผ๊ด„ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„(์ƒ์„ฑ์€ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์šฉ์ด๋ฉฐ ์‹ค์ œ ์šด์˜์—๋Š” ์—†์Œ)\n", " ๋ณด์œ  ์žฅ๋น„ ์ƒ์กด ์žฅ๋น„ ์ƒ์„ฑ(์ดˆ) ํ”ผ์ฒ˜(์ดˆ) ์˜ˆ์ธก(์ดˆ) ์ผ์ •(์ดˆ) ๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ(์ดˆ) ์ฒ˜๋ฆฌ๋Ÿ‰(๋Œ€/์ดˆ)\n", " 1000 676 0.021 0.007 0.008 0.001 0.125 41807.644\n", " 5000 3346 0.132 0.010 0.020 0.005 0.111 95891.024\n", " 10000 6643 0.282 0.013 0.036 0.010 0.107 113284.295\n", " 50000 33436 0.989 0.051 0.177 0.038 0.127 125508.675\n", "100000 66873 2.168 0.083 0.388 0.079 0.132 121715.318\n", " 1๋งŒ ๋Œ€ ์ผ๊ด„ ์ˆ˜๋ช… ์‹œ๋ฎฌ๋ ˆ์ด์…˜: ํ”ผ์ฒ˜+์˜ˆ์ธก+์ผ์ • 0.06์ดˆ, ๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ 0.107์ดˆ(๋ชฉํ‘œ 2์ดˆ ์ด๋‚ด: ๋‹ฌ์„ฑ)\n", "\n", "[2] ์œ„ํ—˜ ๊ฒฝ๋ณด ์ •ํ™•๋„: ์ƒ์กด 6741๋Œ€, ์‹ค์ œ ์œ„ํ—˜(์ฐธ RUL 180์ผ ๋ฏธ๋งŒ) 474๋Œ€(7.0%)\n", " ๊ฒฝ๋ณด ๊ธฐ์ค€(์˜ˆ์ธก RUL < 180 + ์—ฌ์œ ) ๋น„๊ต\n", " ์—ฌ์œ (์ผ) ์ •ํ™•๋„ ์žฌํ˜„์œจ ์ •๋ฐ€๋„ ์˜ค๊ฒฝ๋ณด ๋ฏธํƒ\n", " 0 95.7% 51.1% 80.7% 58 232\n", " 60 95.5% 70.9% 67.5% 162 138\n", " 120 94.7% 84.6% 58.3% 287 73\n", " 180 92.5% 92.0% 48.1% 470 38\n", " ์ฑ„ํƒ ์—ฌ์œ  60์ผ: ์ •ํ™•๋„ 95.5% (๋ชฉํ‘œ 95% ์ด์ƒ: ๋‹ฌ์„ฑ), ์žฌํ˜„์œจ 70.9%, ์ •๋ฐ€๋„ 67.5%, ์˜ค๊ฒฝ๋ณด 162, ๋ฏธํƒ 138\n", " ์œ„ํ—˜ ์žฅ๋น„๊ฐ€ ๋“œ๋ฌผ์–ด ์ •ํ™•๋„๋งŒ์œผ๋กœ๋Š” ๋ถ€ํ’€๋ ค์ง€๋ฏ€๋กœ ์žฌํ˜„์œจยท์ •๋ฐ€๋„๋ฅผ ํ•จ๊ป˜ ๋ณธ๋‹ค. ์—ฌ์œ ๋ฅผ ๋Š˜๋ฆฌ๋ฉด ์žฌํ˜„์œจ์€ ์˜ค๋ฅด์ง€๋งŒ ์˜ค๊ฒฝ๋ณด๊ฐ€ ๋Š˜์–ด ์ •ํ™•๋„๊ฐ€ 95% ์•„๋ž˜๋กœ ๋‚ด๋ ค๊ฐ„๋‹ค. ๋†“์นœ ์žฅ๋น„๋Š” ์ •๊ธฐ ์ ๊ฒ€(์ ๊ฒ€ ๋งˆ๋ชจ)์œผ๋กœ ๋ณด์™„ํ•œ๋‹ค.\n", "\n", "[3] ์˜ค๋ฅ˜ ์ฃผ์ž… ์‹œ ๊ฒฝ๋ณด ์„ฑ๋Šฅ(1๋งŒ ๋Œ€)\n", " ์กฐ๊ฑด ์ •ํ™•๋„ ์žฌํ˜„์œจ ์ •๋ฐ€๋„ ์˜ค๊ฒฝ๋ณด ๋ฏธํƒ\n", " ์ •์ƒ 95.5% 70.9% 67.5% 162 138\n", " ์ ๊ฒ€ ๋งˆ๋ชจ 20% ๊ฒฐ์ธก(๋Œ€์ฒด) 94.9% 69.2% 62.1% 200 146\n", " ์ ๊ฒ€ ๋งˆ๋ชจ 20% ๊ฒฐ์ธก(0์œผ๋กœ ๋ฐฉ์น˜) 94.9% 54.6% 67.1% 127 215\n", " ํ™˜๊ฒฝ 10% ์˜ค๊ธฐ์žฌ 95.4% 70.5% 66.0% 172 140\n", " ๊ฐ€๋™๋ฅ  5% ๋‹จ์œ„ ์˜ค๋ฅ˜(x30) 94.7% 72.6% 60.4% 226 130\n", "๋ณตํ•ฉ(๊ฒฐ์ธก ๋Œ€์ฒด+ํ™˜๊ฒฝ ์˜ค๊ธฐ์žฌ+๋‹จ์œ„ ์˜ค๋ฅ˜) 93.5% 69.2% 52.9% 292 146\n", " ์ •ํ™•๋„๊ฐ€ ๊ฐ€์žฅ ๋‚ฎ์€ ์กฐ๊ฑด์€ '๋ณตํ•ฉ(๊ฒฐ์ธก ๋Œ€์ฒด+ํ™˜๊ฒฝ ์˜ค๊ธฐ์žฌ+๋‹จ์œ„ ์˜ค๋ฅ˜)'(93.5%, ์˜ค๊ฒฝ๋ณด 292๊ฑด). ๊ฒฐ์ธก์„ 0์œผ๋กœ ๋ฐฉ์น˜ํ•˜๋ฉด ์žฌํ˜„์œจ์ด 54.6%๋กœ ์ •์ƒ(70.9%)๋ณด๋‹ค ๋–จ์–ด์ง€๋ฏ€๋กœ ์ •์ œ ๊ทœ์น™(M2)์˜ ๋Œ€์ฒด๊ฐ’ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.\n", " 95% ๋ชฉํ‘œ๋Š” ์ •์ƒ ์กฐ๊ฑด์—์„œ๋งŒ ๋‹ฌ์„ฑํ•˜๋ฉฐ ์˜ค๋ฅ˜๊ฐ€ ๊ฒน์น˜๋ฉด 93.5%๊นŒ์ง€ ๋‚ฎ์•„์ง„๋‹ค -> ์ž…๋ ฅ ๊ฒ€์ฆ(M1ยทM2)์ด ๊ฒฝ๋ณด ํ’ˆ์งˆ์˜ ์ „์ œ์ด๋‹ค.\n", "\n", "[4] ์ •์ˆ˜๊ณ„ํš ํ›„๋ณด ์žฅ๋น„ ์ˆ˜๋ณ„ ํ’€์ด ์‹œ๊ฐ„: 50๋Œ€ 0.04์ดˆ, 150๋Œ€ 0.11์ดˆ, 300๋Œ€ 0.22์ดˆ, 600๋Œ€ 0.50์ดˆ\n", " ํ›„๋ณด๊ฐ€ ์ˆ˜์ฒœ ๋Œ€๋ฅผ ๋„˜์œผ๋ฉด ์ •์ˆ˜๊ณ„ํš์ด ๋А๋ ค์ง€๋ฏ€๋กœ ์œ„ํ—˜ ์ƒ์œ„ ํ›„๋ณด๋งŒ ์ •์ˆ˜๊ณ„ํš, ๋‚˜๋จธ์ง€๋Š” ํƒ์š• ๋ฐฐ์ •์œผ๋กœ ๋‚˜๋ˆ„๋Š” ๊ตฌ์กฐ๋ฅผ ์“ด๋‹ค. ๊ฒฝ๋ณดยท์ผ์ •์€ ํŒ๋‹จ ๋ณด์กฐ์ด๋ฉฐ ํ™•์ •์€ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ(M7)์— ๋”ฐ๋ฅธ๋‹ค.\n", " ๋ชจ๋“  ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ˆ˜์น˜๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 09. AAR ๊ธฐ๋ฐ˜ ์ˆ˜๋ช… ์˜ˆ์ธก ์˜ค์ฐจ ๋ถ„์„ ๋ฐ ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ ํŒŒ๋ผ๋ฏธํ„ฐ ์žฌ๋ณด์ •\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "AAR ๊ธฐ๋ฐ˜ ์ˆ˜๋ช… ์˜ˆ์ธก ์˜ค์ฐจ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜๋ฅผ ์žฌ๋ณด์ •ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **AAR๋กœ ์ˆ˜๋ช… ์˜ˆ์ธก ์˜ค์ฐจ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜๋ฅผ ์žฌ๋ณด์ •ํ•ด ROI๋ฅผ ์‚ฐ์ถœํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์šด์˜ ์žฅ๋น„์˜ ์˜ˆ์ธก ์˜ค์ฐจ๋ฅผ ํ™˜๊ฒฝ, ๊ฐ€๋™๋ฅ , ๋น„๊ณ„ํš ์ •๋น„์œจ๋ณ„๋กœ ๋‚˜๋ˆ„๊ณ  ํฐ ์˜ค์ฐจ์˜ ์›์ธ์„ ๋ถ„๋ฅ˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `reports/sup02_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๋กœ ํ™˜๊ฒฝ๋ณ„ยท์›์ธ๋ณ„ ๋ถ„์„ํ•˜๊ณ , ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜๋ฅผ ์šด์˜ ๊ณ ์žฅ ์ด๋ ฅ์˜ AFT ๊ณ„์ˆ˜๋กœ ์žฌ๋ณด์ •ํ•˜๊ณ  ์šด์˜ ๋ฐ์ดํ„ฐ๋กœ ์˜ˆ์ธก ์—”์ง„์„ ์žฌํ•™์Šตํ•ด ๋ณด๋ฅ˜ ์žฅ๋น„ ์ง‘๋‹จ์—์„œ ๊ฐœ์„ ์„ ๊ฒ€์ฆํ•œ ๋’ค ๋น„์šฉ ์ ˆ๊ฐ์•กยท๊ฐ€๋™ ์ค‘๋‹จ์ผ์ˆ˜ ๋‹จ์ถ•๋ฅ ยทํˆฌ์ž ํšŒ์ˆ˜ ๊ธฐ๊ฐ„ 3๋Œ€ ํšจ๊ณผ ์ง€ํ‘œ๋ฅผ ์‚ฐ์‹๊ณผ ๋ฏผ๊ฐ๋„๋กœ ์‚ฐ์ถœํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.metrics import r2_score\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์žฅ๋น„ ๋ณด์œ  ํ˜„ํ™ฉยท๊ณ ์žฅ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ˆ˜์น˜ ์—†์Œ)\n", "# ๊ฐ€์ƒ ๋ถ€๋Œ€ 6๊ฐœ(U1~U6)์˜ ์žฅ๋น„๊ฐ€ ์šด์šฉ ํ™˜๊ฒฝ(normal/rough/salt/cold)์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์†๋„๋กœ ๋…ธํ›„ํ™”ํ•œ๋‹ค. ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„(h_since)์ด\n", "# ์žฅ๋น„๋งˆ๋‹ค ์ˆจ์€ ์ˆ˜๋ช…(t_fail)์„ ๋„˜์œผ๋ฉด ๊ณ ์žฅ(event=1) ์ด๋ ฅ, ๋„˜์ง€ ์•Š์•˜์œผ๋ฉด ํ˜„์žฌ๊นŒ์ง€ ์ƒ์กด(event=0, ๊ด€์ธก ์ค‘๋„์ ˆ๋‹จ)์ด๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ENVS = [\"normal\", \"rough\", \"salt\", \"cold\"]\n", "ENV_W = {\"normal\": 1.00, \"rough\": 1.35, \"salt\": 1.25, \"cold\": 1.12} # ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜(์ˆจ์€ ์ •๋‹ต, ๊ฐ€์ •๊ฐ’): ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ์งง๋‹ค\n", "ENV_KO = {\"normal\": \"์ผ๋ฐ˜\", \"rough\": \"ํ—˜์ง€\", \"salt\": \"์—ผ๋ถ„\", \"cold\": \"ํ˜นํ•œ\"}\n", "UNITS = [\"U1\", \"U2\", \"U3\", \"U4\", \"U5\", \"U6\"]\n", "UNIT_ENV = {\"U1\": [.7, .1, .1, .1], \"U2\": [.2, .6, .1, .1], \"U3\": [.2, .1, .6, .1], \"U4\": [.3, .1, .1, .5], \"U5\": [.5, .3, .1, .1], \"U6\": [.4, .1, .4, .1]}\n", "ETYPES = [\"truck\", \"generator\", \"trailer\", \"radio\"]\n", "CYCLE = 8000.0 # ๊ทœ์ • ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ(์šด์šฉ์‹œ๊ฐ„ h, ๊ฐ€์ •๊ฐ’)\n", "KSHAPE = 12.0 # ์ˆ˜๋ช… ๋ถ„ํฌ ์™€์ด๋ธ” ํ˜•์ƒ(๋งˆ๋ชจํ˜• ๊ณ ์žฅ, ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ๋ชจ์—ฌ ์žˆ๋‹ค)\n", "INSP_NOISE = 0.025 # ์ •๋น„ ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ ์ธก์ • ์˜ค์ฐจ(๊ฐ€์ •๊ฐ’)\n", "\n", "\n", "def make_fleet(n=900, seed=0, env_w=None, shift=1.0):\n", " \"\"\"์žฅ๋น„ n๋Œ€. env_w๋กœ ์ˆจ์€ ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜๋ฅผ ๋ฐ”๊ฟ” ์šด์˜ ๋ถ„ํฌ ๋ณ€ํ™”๋ฅผ ๋ชจ์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. shift>1์ด๋ฉด ๊ฐ€๋™๋ฅ ์ด ๋†’์•„์ง„๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " w = env_w or ENV_W\n", " unit = rng.choice(UNITS, n)\n", " env = np.array([rng.choice(ENVS, p=UNIT_ENV[u]) for u in unit])\n", " et = rng.choice(ETYPES, n, p=[.4, .2, .2, .2])\n", " rate = rng.lognormal(np.log(3.0 * shift), 0.25, n) # ์ผ ํ‰๊ท  ์šด์šฉ์‹œ๊ฐ„(h/์ผ)\n", " h_since = rng.uniform(300, 11500, n) # ๋งˆ์ง€๋ง‰ ์ฐฝ์ •๋น„ ์ดํ›„ ์šด์šฉ์‹œ๊ฐ„\n", " n_ovh = rng.integers(0, 3, n)\n", " hours = h_since + n_ovh * rng.uniform(5000, 8000, n) # ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„\n", " km = hours * rng.normal(17, 3, n) * np.where(env == \"rough\", 0.8, 1.0) # ๋ˆ„์  ๊ธฐ๋™๊ฑฐ๋ฆฌ(km)\n", " age_y = np.clip(hours / (rate * 365) * rng.uniform(0.9, 1.15, n), 0.5, 25)\n", " unpl = rng.poisson(0.35 * h_since / 1000 * np.array([w[e] for e in env]) / 1.3) # ๋น„๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜(ํ™˜๊ฒฝ์ด ๊ฐ€ํ˜นํ• ์ˆ˜๋ก ๋งŽ๋‹ค)\n", " n_maint = rng.poisson(0.6 * h_since / 1000 + 1) # ๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜\n", " scale = 9600 / np.array([w[e] for e in env]) * 0.985 ** unpl * np.exp(rng.normal(0, 0.04, n))\n", " t_fail = scale * rng.weibull(KSHAPE, n) # ์ฐฝ์ •๋น„ ํ›„ ๊ณ ์žฅ๊นŒ์ง€ ์šด์šฉ์‹œ๊ฐ„(์ˆจ์€ ์ •๋‹ต)\n", " event = (t_fail <= h_since).astype(int)\n", " dur = np.where(event == 1, t_fail, h_since)\n", " df = pd.DataFrame({\"eq_id\": [f\"EQ{seed:02d}{i:04d}\" for i in range(n)], \"unit\": unit, \"env\": env, \"etype\": et, \"rate\": rate, \"hours\": hours, \"km\": km, \"age_y\": age_y, \"n_ovh\": n_ovh,\n", " \"h_since\": h_since, \"unpl\": unpl, \"n_maint\": n_maint, \"t_fail\": t_fail, \"event\": event, \"dur\": dur})\n", " df[\"insp\"] = np.clip(dur / t_fail + rng.normal(0, INSP_NOISE, n), 0, 1.3) # ๋งˆ์ง€๋ง‰ ์ •๋น„ ์ ๊ฒ€์—์„œ ์ธก์ •ํ•œ ๋งˆ๋ชจ ๋น„์œจ(์†Œ๋ชจ ์ˆ˜๋ช…/์ด ์ˆ˜๋ช…, ์˜ค์ฐจ ํฌํ•จ)\n", " df[\"rul_h\"] = np.where(event == 0, t_fail - h_since, np.nan) # ์ƒ์กด ์žฅ๋น„์˜ ๋‚จ์€ ์šด์šฉ์‹œ๊ฐ„(์ •๋‹ต)\n", " df[\"rul_d\"] = df[\"rul_h\"] / df[\"rate\"] # ๋‚จ์€ ์ผ์ˆ˜(์ •๋‹ต)\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " y, p = np.asarray(y, float), np.asarray(p, float)\n", " return float(np.mean(np.abs(y - p) / np.abs(y)) * 100)\n", "\n", "\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "\n", "\n", "def rmse(y, p):\n", " return float(np.sqrt(np.mean((np.asarray(y, float) - np.asarray(p, float)) ** 2)))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธํ›„ํ™” ํŒŒ์ƒ ํ”ผ์ฒ˜์™€ ํ•™์Šต ํ‘œ๋ณธ ๋งŒ๋“ค๊ธฐ. ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ W0๋Š” ๋„์ž… ์‹œ์ ์˜ ์ „๋ฌธ๊ฐ€ ์ถ”์ •๊ฐ’์ด๋ฉฐ(์‹ค์ œ ๊ฐ€์ค‘์น˜์™€ ๋‹ค๋ฆ„) M9์—์„œ ์žฌ๋ณด์ •ํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "W0 = {\"normal\": 1.00, \"rough\": 1.15, \"salt\": 1.10, \"cold\": 1.05}\n", "FEATS = [\"h_since\", \"hours\", \"km\", \"age_y\", \"rate\", \"n_maint\", \"unpl\", \"unpl_per_kh\", \"km_per_h\", \"n_ovh\", \"k_rough\", \"k_salt\", \"k_cold\", \"k_truck\", \"k_gen\", \"k_trailer\",\n", " \"aging_idx\", \"lin_rul_d\", \"hs_frac\", \"insp\", \"insp_eta_h\"]\n", "\n", "\n", "def add_features(df, w=None):\n", " d = df.copy()\n", " w = w or W0\n", " d[\"unpl_per_kh\"] = d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)\n", " d[\"km_per_h\"] = d[\"km\"] / d[\"hours\"]\n", " for e in (\"rough\", \"salt\", \"cold\"):\n", " d[\"k_\" + e] = (d[\"env\"] == e).astype(int)\n", " d[\"k_truck\"], d[\"k_gen\"], d[\"k_trailer\"] = (d[\"etype\"] == \"truck\").astype(int), (d[\"etype\"] == \"generator\").astype(int), (d[\"etype\"] == \"trailer\").astype(int)\n", " d[\"aging_idx\"] = d[\"h_since\"] * d[\"env\"].map(w) / CYCLE # ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜(1์ด๋ฉด ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ์— ๋„๋‹ฌ)\n", " d[\"lin_rul_d\"] = (CYCLE - d[\"h_since\"]) / d[\"rate\"] # ์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ์ž”์—ฌ ์ผ์ˆ˜(Baseline)\n", " d[\"hs_frac\"] = d[\"h_since\"] / CYCLE\n", " d[\"insp_eta_h\"] = d[\"h_since\"] / np.maximum(d[\"insp\"], 0.05) # ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ๋กœ ํ™˜์‚ฐํ•œ ์ด ์ˆ˜๋ช… ์ถ”์ •(h)\n", " return d\n", "\n", "\n", "def snapshots(df, k=4, seed=0):\n", " \"\"\"๊ณ ์žฅ ์ด๋ ฅ ์žฅ๋น„์˜ ๊ณผ๊ฑฐ ์‹œ์  ํ‘œ๋ณธ: ๊ณ ์žฅ ์ „ ์ž„์˜ ์‹œ์ ์˜ ์ ๊ฒ€๊ฐ’ยท์šด์šฉ์‹œ๊ฐ„๊ณผ ๊ทธ๋•Œ์˜ ๋‚จ์€ ์ผ์ˆ˜. (์ƒ์กด ์žฅ๋น„์˜ ๋ฏธ๋ž˜๋Š” ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๊ณ ์žฅ ์ด๋ ฅ๋งŒ ์“ด๋‹ค)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " f = df[df[\"event\"] == 1]\n", " out = []\n", " for _ in range(k):\n", " s = f.copy()\n", " h = s[\"t_fail\"] * rng.uniform(0.2, 0.98, len(s))\n", " ratio = h / s[\"h_since\"]\n", " s[\"hours\"] = s[\"hours\"] - (s[\"h_since\"] - h)\n", " s[\"km\"] = s[\"km\"] * ratio.clip(upper=1)\n", " s[\"unpl\"] = np.floor(s[\"unpl\"] * ratio)\n", " s[\"n_maint\"] = np.floor(s[\"n_maint\"] * ratio)\n", " s[\"insp\"] = np.clip(h / s[\"t_fail\"] + rng.normal(0, INSP_NOISE, len(s)), 0, 1.3)\n", " s[\"h_since\"] = h\n", " s[\"rul_d\"] = (s[\"t_fail\"] - h) / s[\"rate\"]\n", " s[\"event\"] = 0\n", " out.append(s)\n", " return pd.concat(out, ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ƒ์กด ๋ถ„์„ ๋„๊ตฌ(scipy๋งŒ ์‚ฌ์šฉ): ์™€์ด๋ธ” AFT(๊ฐ€์† ์ˆ˜๋ช…) ๋ชจํ˜•๊ณผ Cox ๋น„๋ก€์œ„ํ—˜ ๋ชจํ˜•\n", "# ---------------------------------------------------------------------------\n", "from scipy.optimize import minimize\n", "\n", "\n", "def aft_x(d):\n", " return np.c_[np.ones(len(d)), d[\"env\"].eq(\"rough\"), d[\"env\"].eq(\"salt\"), d[\"env\"].eq(\"cold\"), d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)].astype(float)\n", "\n", "\n", "AFT_COLS = [\"์ ˆํŽธ\", \"ํ—˜์ง€\", \"์—ผ๋ถ„\", \"ํ˜นํ•œ\", \"๋น„๊ณ„ํš ์ •๋น„์œจ\"]\n", "\n", "\n", "def fit_aft(d):\n", " \"\"\"์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„ dur(๊ณ ์žฅ์ด๋ฉด event=1, ์ƒ์กด์ด๋ฉด ์ค‘๋„์ ˆ๋‹จ)์— ๋Œ€ํ•œ ์™€์ด๋ธ” AFT ์ตœ๋Œ€์šฐ๋„ ์ถ”์ •. ๋ฐ˜ํ™˜: ๊ณ„์ˆ˜(๋งˆ์ง€๋ง‰์€ log ํ˜•์ƒ).\"\"\"\n", " x, t, e = aft_x(d), d[\"dur\"].values, d[\"event\"].values\n", "\n", " def nll(p):\n", " lam, k = np.exp(x @ p[:-1]), np.exp(p[-1])\n", " z = (t / lam) ** k\n", " return -(e * (np.log(k) - np.log(lam) + (k - 1) * (np.log(t) - np.log(lam))) - z).sum()\n", "\n", " r = minimize(nll, np.r_[np.log(8000), np.zeros(x.shape[1] - 1), np.log(5)], method=\"BFGS\")\n", " return r.x\n", "\n", "\n", "def aft_pred(p, d, q=0.5, h=None):\n", " \"\"\"์ด๋ฏธ h(์šด์šฉ์‹œ๊ฐ„)๊นŒ์ง€ ์ƒ์กดํ–ˆ๋‹ค๋Š” ์กฐ๊ฑด์—์„œ ๋ถ„์œ„์ˆ˜ q ์ƒ์กด ์‹œ์ (์šด์šฉ์‹œ๊ฐ„ h ๊ธฐ์ค€ ์ด ์ˆ˜๋ช…)์„ ๋Œ๋ ค์ค€๋‹ค. q=0.5๊ฐ€ ์กฐ๊ฑด๋ถ€ ์ค‘์•™๊ฐ’.\"\"\"\n", " lam, k = np.exp(aft_x(d) @ p[:-1]), np.exp(p[-1])\n", " h = d[\"h_since\"].values if h is None else h\n", " return lam * ((h / lam) ** k - np.log(q)) ** (1 / k)\n", "\n", "\n", "def aft_fail_prob(p, d, h0, h1):\n", " \"\"\"h0๊นŒ์ง€ ์ƒ์กดํ•œ ์žฅ๋น„๊ฐ€ h0~h1 ์‚ฌ์ด์— ๊ณ ์žฅ ๋‚  ์กฐ๊ฑด๋ถ€ ํ™•๋ฅ .\"\"\"\n", " lam, k = np.exp(aft_x(d) @ p[:-1]), np.exp(p[-1])\n", " s0, s1 = np.exp(-(h0 / lam) ** k), np.exp(-(h1 / lam) ** k)\n", " return 1 - s1 / s0\n", "\n", "\n", "def fit_cox(d, iters=25):\n", " \"\"\"Cox ๋น„๋ก€์œ„ํ—˜(Breslow ๊ทผ์‚ฌ) ๋‰ดํ„ด-๋žฉ์Šจ. ๊ณต๋ณ€๋Ÿ‰: ํ—˜์ง€ยท์—ผ๋ถ„ยทํ˜นํ•œยท๋น„๊ณ„ํš ์ •๋น„์œจ. ๋ฐ˜ํ™˜: ๊ณ„์ˆ˜.\"\"\"\n", " x = aft_x(d)[:, 1:]\n", " o = np.argsort(-d[\"dur\"].values)\n", " x, e = x[o], d[\"event\"].values[o]\n", " b = np.zeros(x.shape[1])\n", " for _ in range(iters):\n", " r = np.exp(x @ b)\n", " S0 = np.cumsum(r)\n", " S1 = np.cumsum(r[:, None] * x, axis=0)\n", " S2 = np.cumsum(r[:, None, None] * x[:, :, None] * x[:, None, :], axis=0)\n", " m1 = S1 / S0[:, None]\n", " g = ((x - m1) * e[:, None]).sum(axis=0)\n", " H = -sum(e[i] * (S2[i] / S0[i] - np.outer(m1[i], m1[i])) for i in np.where(e == 1)[0])\n", " step = np.linalg.solve(H - 1e-6 * np.eye(len(b)), g)\n", " b = b - step\n", " if np.abs(step).max() < 1e-7:\n", " break\n", " return b\n", "\n", "\n", "def cindex(time, event, risk):\n", " \"\"\"์ผ์น˜๋„ C-index: ์œ„ํ—˜ ์ ์ˆ˜๊ฐ€ ๋†’์€ ์ชฝ์ด ๋จผ์ € ๊ณ ์žฅ ๋‚ฌ๋Š”๊ฐ€(๊ด€์ธก ๊ฐ€๋Šฅํ•œ ์Œ๋งŒ).\"\"\"\n", " t, e, r = np.asarray(time), np.asarray(event), np.asarray(risk)\n", " num = den = 0.0\n", " for i in np.where(e == 1)[0]:\n", " m = t > t[i]\n", " den += m.sum()\n", " num += (r[m] < r[i]).sum() + 0.5 * (r[m] == r[i]).sum()\n", " return num / max(den, 1)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ž”์—ฌ ์ˆ˜๋ช…(RUL, ์ผ) ์˜ˆ์ธก ์—”์ง„: ์™€์ด๋ธ” AFT ์กฐ๊ฑด๋ถ€ ์ค‘์•™๊ฐ’ + ๊ทธ๋ž˜๋””์–ธํŠธ ๋ถ€์ŠคํŒ…(๊ณ ์žฅ ์ด๋ ฅ ์‹œ์  ํ‘œ๋ณธ ํ•™์Šต)์˜ ํ‰๊ท \n", "# ---------------------------------------------------------------------------\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "\n", "\n", "def fit_engine(train, seed=0):\n", " p = fit_aft(train)\n", " sn = add_features(snapshots(train, 5, seed))\n", " sn[\"aft\"] = (aft_pred(p, sn) - sn[\"h_since\"]) / sn[\"rate\"]\n", " g = GradientBoostingRegressor(n_estimators=250, max_depth=3, learning_rate=0.06, random_state=0).fit(sn[FEATS + [\"aft\"]], sn[\"rul_d\"])\n", " return {\"p\": p, \"g\": g}\n", "\n", "\n", "def predict_rul(eng, d):\n", " \"\"\"d๋Š” add_features๋ฅผ ๊ฑฐ์นœ ํ‘œ. ๋ฐ˜ํ™˜: (์•™์ƒ๋ธ” RUL, AFT RUL, GBR RUL) - ์ผ ๋‹จ์œ„.\"\"\"\n", " aft = ((aft_pred(eng[\"p\"], d) - d[\"h_since\"]) / d[\"rate\"]).values\n", " x = d[FEATS].copy()\n", " x[\"aft\"] = aft\n", " gb = eng[\"g\"].predict(x)\n", " return np.clip(0.5 * aft + 0.5 * gb, 1, None), aft, gb\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๊ณ„ํš์ •๋น„(์ฐฝ์ •๋น„) ์ผ์ • ์ถ”์ฒœ: ์›”๋ณ„ ์ •๋น„์ฐฝ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐยท์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ(์œ ํšจ๊ธฐํ•œ) ์ œ์•ฝ ์•„๋ž˜ ๊ธฐ๋Œ€ ๋น„์šฉ ์ตœ์†Œํ™” (๋น„์šฉ ๋‹จ๊ฐ€๋Š” ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’, ๋งŒ์›)\n", "# ---------------------------------------------------------------------------\n", "from scipy.optimize import milp, LinearConstraint, Bounds\n", "from scipy.stats import norm\n", "\n", "C_PLAN, C_FAIL, C_WASTE_KH, C_KIT = 600.0, 2400.0, 40.0, 120.0 # ๊ณ„ํš ์ฐฝ์ •๋น„, ๋น„๊ณ„ํš ๊ณ ์žฅ(๊ธด๊ธ‰ ์ •๋น„ ํฌํ•จ), ๋ฏธ์‚ฌ์šฉ ์ˆ˜๋ช… 1000h๋‹น ์†์‹ค, ์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ\n", "HORIZON, MDAYS = 12, 30 # ๊ณ„ํš 12๊ฐœ์›”, ์›” 30์ผ\n", "CAP_M, BUDGET_M = 22, 15000.0 # ์›”๋ณ„ ์ •๋น„์ฐฝ ๋Šฅ๋ ฅ(๋Œ€), ์›”๋ณ„ ์˜ˆ์‚ฐ\n", "LOTS = [(30, 70), (45, 170), (45, 390), (70, 800)] # ์‹œํšจ์„ฑ ํ‚คํŠธ ๋กœํŠธ(์ˆ˜๋Ÿ‰, ์œ ํšจ๊ธฐํ•œ๊นŒ์ง€ ๋‚จ์€ ์ผ์ˆ˜)\n", "KIT_MARGIN = 14 # ์ •๋น„์ผ ๊ธฐ์ค€ ์ตœ์†Œ ์œ ํšจ๊ธฐ๊ฐ„ ์—ฌ์œ (์ผ)\n", "\n", "\n", "def exp_cost_matrix(pred, rate, month_days):\n", " \"\"\"์žฅ๋น„ i๋ฅผ ์›” m์— ์ •๋น„ํ•  ๋•Œ์˜ ๊ธฐ๋Œ€ ๋น„์šฉ. ์ฐธ RUL ~ N(pred, sigma), sigma = 35์ผ + 0.12 x pred (๊ฐ€์ •). ๋งˆ์ง€๋ง‰ ์—ด์€ ๊ณ„ํš ๊ธฐ๊ฐ„ ๋ฐ–(๋ฏธ์ •๋น„).\"\"\"\n", " sig = 35 + 0.12 * pred\n", " C = np.zeros((len(pred), len(month_days) + 1))\n", " for j, day in enumerate(month_days):\n", " z = (day - pred) / sig\n", " pf = norm.cdf(z)\n", " unused = np.maximum(pred - day, 0) * rate * 0.6 # ์ •๋น„๋ฅผ ์•ž๋‹น๊ธด ๋งŒํผ ๋ฒ„๋ฆฌ๋Š” ์šด์šฉ์‹œ๊ฐ„ h(๊ทผ์‚ฌ)\n", " C[:, j] = C_PLAN + C_KIT + pf * (C_FAIL - C_PLAN) + C_WASTE_KH * unused / 1000 * (1 - pf)\n", " C[:, -1] = norm.cdf((HORIZON * MDAYS - pred) / sig) * C_FAIL\n", " return C\n", "\n", "\n", "def plan_milp(pred, rate, cap=CAP_M, budget=BUDGET_M, lots=LOTS, use_kit=True):\n", " \"\"\"์ •์ˆ˜๊ณ„ํš์œผ๋กœ ์žฅ๋น„๋ณ„ ์ •๋น„ ์›”(0~11) ๋˜๋Š” ๋ฏธ์ •๋น„(-1)๋ฅผ ๋ฐฐ์ •ํ•œ๋‹ค. ํ›„๋ณด๋Š” ์˜ˆ์ธก RUL์ด ๊ณ„ํš ๊ธฐ๊ฐ„ +90์ผ ์ด๋‚ด์ธ ์žฅ๋น„.\"\"\"\n", " md = np.arange(1, HORIZON + 1) * MDAYS\n", " cand = np.where(pred < HORIZON * MDAYS + 90)[0]\n", " n, M = len(cand), HORIZON\n", " C = exp_cost_matrix(pred[cand], rate[cand], md)\n", " c = C.ravel()\n", " A, lo, hi = [], [], []\n", " for i in range(n): # ์žฅ๋น„๋‹น ํ•œ ๋ฒˆ๋งŒ ๋ฐฐ์ •(์›” 12์นธ + ๋ฏธ์ •๋น„)\n", " row = np.zeros(n * (M + 1)); row[i * (M + 1):(i + 1) * (M + 1)] = 1; A.append(row); lo.append(1); hi.append(1)\n", " for m in range(M): # ์›”๋ณ„ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐ\n", " row = np.zeros(n * (M + 1)); row[m::(M + 1)] = 1\n", " A.append(row); lo.append(0); hi.append(cap)\n", " A.append(row * (C_PLAN + C_KIT)); lo.append(0); hi.append(budget)\n", " if use_kit: # ์‹œํšจ์„ฑ ํ‚คํŠธ: m์›” ์ดํ›„ ์ •๋น„ ์ˆ˜์š”๋Š” ๊ทธ ์‹œ์ ์— ์œ ํšจ๊ธฐ๊ฐ„์ด ๋‚จ์€ ๋กœํŠธ ์ˆ˜๋Ÿ‰์„ ๋„˜์„ ์ˆ˜ ์—†๋‹ค\n", " for m in range(M):\n", " sup = sum(q for q, e in lots if e >= md[m] + KIT_MARGIN)\n", " row = np.zeros(n * (M + 1))\n", " for mm in range(m, M):\n", " row[mm::(M + 1)] = 1\n", " A.append(row); lo.append(0); hi.append(sup)\n", " r = milp(c, constraints=LinearConstraint(np.array(A), lo, hi), integrality=np.ones(len(c)), bounds=Bounds(0, 1), options={\"time_limit\": 20})\n", " month = np.full(len(pred), -1)\n", " if r.x is not None:\n", " x = np.round(r.x).reshape(n, M + 1)\n", " assign = np.where(x[:, :M].sum(axis=1) > 0.5, x[:, :M].argmax(axis=1), -1)\n", " month[cand] = assign\n", " return month\n", "\n", "\n", "def plan_greedy(pred, margin=45, cap=CAP_M, budget=BUDGET_M):\n", " \"\"\"๊ฐ„๋‹จํ•œ ํƒ์š• ๋ฐฐ์ •: ์ •๋น„ ๋ชฉํ‘œ์ผ(์˜ˆ์ธก RUL - margin)์ด ๋น ๋ฅธ ์ˆœ์œผ๋กœ ๋ชฉํ‘œ์ผ ์ด์ „ ๊ฐ€์žฅ ๋Šฆ์€ ๋นˆ ์›”, ์—†์œผ๋ฉด ๊ฐ€์žฅ ์ด๋ฅธ ๋นˆ ์›”์— ๋ฐฐ์ •.\"\"\"\n", " cap = min(cap, int(budget // (C_PLAN + C_KIT))) # ์›” ์˜ˆ์‚ฐ์ด ํ—ˆ์šฉํ•˜๋Š” ๋Œ€์ˆ˜๋กœ ๋Šฅ๋ ฅ์„ ์ œํ•œ\n", " month = np.full(len(pred), -1)\n", " left = np.full(HORIZON, cap)\n", " for i in np.argsort(pred):\n", " if pred[i] > HORIZON * MDAYS + 60: break\n", " tgt = int(np.clip((pred[i] - margin) // MDAYS - 1, 0, HORIZON - 1))\n", " for m in list(range(tgt, -1, -1)) + list(range(tgt + 1, HORIZON)):\n", " if left[m] > 0:\n", " month[i] = m; left[m] -= 1; break\n", " return month\n", "\n", "\n", "def plan_fixed(lin_rul, cap=CAP_M, budget=BUDGET_M):\n", " \"\"\"๊ด€ํ–‰: ๊ทœ์ • ์ฃผ๊ธฐ ๋„๋‹ฌ ์ˆœ์„œ๋Œ€๋กœ(์„ ํ˜• ์ž”์—ฌ ์ผ์ˆ˜) ์›”๋ณ„ ๋Šฅ๋ ฅ ์•ˆ์—์„œ ๋ฐฐ์ •.\"\"\"\n", " return plan_greedy(np.asarray(lin_rul, float), margin=0, cap=cap, budget=budget)\n", "\n", "\n", "def evaluate_plan(month, true_rul, rate, cost_kit=True):\n", " \"\"\"์ฐธ RUL๋กœ ๊ณ„ํš์˜ ์‹ค์ œ ๋น„์šฉ์„ ๊ณ„์‚ฐํ•œ๋‹ค: ์ •๋น„์ผ ์ด์ „์— ๊ณ ์žฅ -> ๋น„๊ณ„ํš ๊ณ ์žฅ ๋น„์šฉ, ์•„๋‹ˆ๋ฉด ๊ณ„ํš ๋น„์šฉ + ๋ฏธ์‚ฌ์šฉ ์ˆ˜๋ช… ์†์‹ค.\"\"\"\n", " day = np.where(month >= 0, (month + 1) * MDAYS, 10 ** 9)\n", " fail = (true_rul < day) & (true_rul < HORIZON * MDAYS)\n", " planned = (month >= 0) & ~fail\n", " waste = np.where(planned, (true_rul - day) * rate * 0.6, 0) * C_WASTE_KH / 1000\n", " cost = fail * C_FAIL + planned * (C_PLAN + (C_KIT if cost_kit else 0)) + waste\n", " return {\"total\": float(cost.sum()), \"fail\": int(fail.sum()), \"planned\": int(planned.sum()), \"waste\": float(waste.sum()), \"unsched_fail\": int(((month < 0) & fail).sum())}\n", "\n", "\n", "def kit_fefo(month, lots=LOTS):\n", " \"\"\"์œ ํšจ๊ธฐํ•œ์ด ๋น ๋ฅธ ๋กœํŠธ๋ถ€ํ„ฐ ์“ฐ๋Š” FEFO๋กœ ํ‚คํŠธ๋ฅผ ๋ฐฐ์ •ํ•˜๊ณ  ์ •๋น„์ผ์— ์œ ํšจ๊ธฐ๊ฐ„์ด ๋ถ€์กฑํ•œ ๋ฐฐ์ • ์ˆ˜์™€ ๋งŒ๋ฃŒ ํ๊ธฐ ์ˆ˜๋Ÿ‰์„ ์„ผ๋‹ค.\"\"\"\n", " q = [[qty, e] for qty, e in sorted(lots, key=lambda t: t[1])]\n", " short = 0\n", " for m in sorted(month[month >= 0]):\n", " day = (m + 1) * MDAYS\n", " for lot in q:\n", " if lot[1] >= day + KIT_MARGIN and lot[0] > 0:\n", " lot[0] -= 1; break\n", " else:\n", " short += 1\n", " expired = sum(l[0] for l in q if l[1] < HORIZON * MDAYS)\n", " return {\"short\": short, \"expired_unused\": expired, \"left\": sum(l[0] for l in q)}\n", "# ---------------------------------------------------------------------------\n", "\n", "OPS_W = {\"normal\": 1.00, \"rough\": 1.55, \"salt\": 1.15, \"cold\": 1.30} # ์šด์˜ ๊ธฐ๊ฐ„์˜ ์ˆจ์€ ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜(๋„์ž… ๋‹น์‹œ์™€ ๋‹ฌ๋ผ์ง„ ๊ฐ€์ •)\n", "tr = make_fleet(900, seed=1)\n", "eng0 = fit_engine(tr)\n", "A = make_fleet(700, seed=60, env_w=OPS_W, shift=1.2) # AAR ๋Œ€์ƒ ์šด์˜ ์žฅ๋น„\n", "B = make_fleet(700, seed=70, env_w=OPS_W, shift=1.2) # ์žฌ๋ณด์ •์— ์“ฐ์ง€ ์•Š๋Š” ๋ณด๋ฅ˜ ์žฅ๋น„\n", "\n", "# ---------------- 1) AAR: ์šด์˜ ์˜ˆ์ธก ์˜ค์ฐจ ๋ถ„์„ ----------------\n", "Af = add_features(A)\n", "al = Af[Af.event == 0].reset_index(drop=True)\n", "pred, _, _ = predict_rul(eng0, al)\n", "y = al[\"rul_d\"].values\n", "al[\"err\"] = pred - y # ์–‘์ˆ˜ = ์ˆ˜๋ช…์„ ๊ธธ๊ฒŒ ์˜ˆ์ธก(์œ„ํ—˜)\n", "big = y >= 90\n", "print(f\"[1] AAR ๋Œ€์ƒ ์šด์˜ ์žฅ๋น„ {len(A)}๋Œ€(์ƒ์กด {len(al)}๋Œ€, ๊ณ ์žฅ {int(A.event.sum())}๋Œ€): ํ˜„ํ–‰ ์—”์ง„ MAE {mae(y, pred):.0f}์ผ, RMSE {rmse(y, pred):.0f}์ผ, MAPE {mape(y[big], pred[big]):.1f}%, R2 {r2_score(y, pred):.3f}, ํ‰๊ท  ํŽธํ–ฅ {al.err.mean():+.0f}์ผ\")\n", "ct = pd.DataFrame([(k, int(m.sum()), mae(y[m.values], pred[m.values]), al.err[m].mean()) for k, m in {\"์ผ๋ฐ˜\": al.env == \"normal\", \"ํ—˜์ง€\": al.env == \"rough\", \"์—ผ๋ถ„\": al.env == \"salt\", \"ํ˜นํ•œ\": al.env == \"cold\",\n", " \"์ผ ๊ฐ€๋™๋ฅ  4h ์ด์ƒ\": al.rate >= 4, \"๋น„๊ณ„ํš ์ •๋น„์œจ ์ƒ์œ„ 25%\": al.unpl_per_kh >= al.unpl_per_kh.quantile(.75)}.items()], columns=[\"์กฐ๊ฑด\", \"๋Œ€์ˆ˜\", \"MAE(์ผ)\", \"ํ‰๊ท  ํŽธํ–ฅ(์ผ)\"])\n", "print(ct.round(0).to_string(index=False))\n", "worst = al.reindex(al.err.abs().sort_values(ascending=False).index).head(40)\n", "\n", "\n", "def cause(r):\n", " if r.env == \"rough\" and r.err > 0: return \"ํ—˜์ง€ ๊ฐ€ํ˜น๋„ ๊ณผ์†Œ ๋ฐ˜์˜\"\n", " if r.env == \"cold\" and r.err > 0: return \"ํ˜นํ•œ ๊ฐ€ํ˜น๋„ ๊ณผ์†Œ ๋ฐ˜์˜\"\n", " if r.env == \"salt\" and r.err < 0: return \"์—ผ๋ถ„ ๊ฐ€ํ˜น๋„ ๊ณผ๋Œ€ ๋ฐ˜์˜\"\n", " if r.rate >= 4: return \"๊ฐ€๋™๋ฅ  ์ƒ์Šน(์šด์šฉ ๊ฐ•๋„ ๋ณ€ํ™”)\"\n", " if r.unpl_per_kh >= 1.2: return \"๋น„๊ณ„ํš ์ •๋น„ ๋‹ค์ˆ˜\"\n", " return \"๊ธฐํƒ€(๊ฐœ์ฒด ํŽธ์ฐจ)\"\n", "\n", "\n", "cs = worst.apply(cause, axis=1).value_counts()\n", "print(\" ์˜ค์ฐจ ํฐ ์ƒ์œ„ 40๋Œ€์˜ ์›์ธ ๋ถ„๋ฅ˜:\", \", \".join(f\"{k} {v}๋Œ€({v / 40:.0%})\" for k, v in cs.items()))\n", "\n", "# ---------------- 2) ์žฌ๋ณด์ •: ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ -> ์šด์˜ ๋ฐ์ดํ„ฐ ์žฌํ•™์Šต ----------------\n", "pa = fit_aft(A)\n", "life_ratio = np.exp(pa[1:4])\n", "W1 = {\"normal\": 1.0, \"rough\": 1 / life_ratio[0], \"salt\": 1 / life_ratio[1], \"cold\": 1 / life_ratio[2]}\n", "wt = pd.DataFrame({\"๋„์ž… ์‹œ ์ถ”์ •(W0)\": W0, \"์žฌ๋ณด์ •(์šด์˜ AFT)\": W1, \"์ˆจ์€ ์ •๋‹ต(์šด์˜)\": OPS_W}).loc[ENVS]\n", "wt[\"๋ณ€ํ™”์œจ\"] = wt[\"์žฌ๋ณด์ •(์šด์˜ AFT)\"] / wt[\"๋„์ž… ์‹œ ์ถ”์ •(W0)\"] - 1\n", "print(\"\\n[2] ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ ๋งคํŠธ๋ฆญ์Šค(ํด์ˆ˜๋ก ๋นจ๋ฆฌ ๋…ธํ›„)\")\n", "print(wt.round(3).to_string())\n", "flag = wt[wt[\"๋ณ€ํ™”์œจ\"].abs() > 0.2].index.tolist()\n", "print(f\" ๋ณ€ํ™”์œจ 20% ์ดˆ๊ณผ ํ™˜๊ฒฝ {flag or '์—†์Œ'}๋Š” ์ •๋น„๊ณ„ํš๊ด€ ๊ฒ€ํ† ยท๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ ๋’ค ์ ์šฉํ•œ๋‹ค(์ž๋™ ๋ฐ˜์˜ ๊ธˆ์ง€).\")\n", "eng1 = fit_engine(A, seed=1)\n", "Bf0, Bf1 = add_features(B, W0), add_features(B, W1)\n", "bl0, bl1 = Bf0[Bf0.event == 0].reset_index(drop=True), Bf1[Bf1.event == 0].reset_index(drop=True)\n", "p0, _, _ = predict_rul(eng0, bl0)\n", "p1, _, _ = predict_rul(eng1, bl1)\n", "yb = bl0[\"rul_d\"].values\n", "bb = yb >= 90\n", "tab = pd.DataFrame({\"์žฌ๋ณด์ • ์ „\": [mae(yb, p0), rmse(yb, p0), mape(yb[bb], p0[bb]), r2_score(yb, p0), (p0 - yb).mean()], \"์žฌ๋ณด์ • ํ›„\": [mae(yb, p1), rmse(yb, p1), mape(yb[bb], p1[bb]), r2_score(yb, p1), (p1 - yb).mean()]},\n", " index=[\"MAE(์ผ)\", \"RMSE(์ผ)\", \"MAPE(%)\", \"R2\", \"ํ‰๊ท  ํŽธํ–ฅ(์ผ)\"])\n", "print(f\"\\n[3] ๋ณด๋ฅ˜ ์žฅ๋น„ {len(bl0)}๋Œ€(์žฌ๋ณด์ •์— ์“ฐ์ง€ ์•Š์Œ) ๊ฒ€์ฆ\\n\" + tab.round(3).to_string())\n", "print(f\" MAE {1 - tab.loc['MAE(์ผ)', '์žฌ๋ณด์ • ํ›„'] / tab.loc['MAE(์ผ)', '์žฌ๋ณด์ • ์ „']:.1%} ๊ฐ์†Œ, ํ™˜๊ฒฝ๋ณ„ ํŽธํ–ฅ(์ „ -> ํ›„):\", \", \".join(f\"{e} {(p0 - yb)[(bl0.env == e).values].mean():+.0f} -> {(p1 - yb)[(bl0.env == e).values].mean():+.0f}\" for e in ENVS))\n", "\n", "# ---------------- 3) ROI 3๋Œ€ ์ง€ํ‘œ(๊ต์œก์šฉ ๊ฐ€์ •) ----------------\n", "rate_b, true_b = bl0[\"rate\"].values, yb\n", "m0, m1 = plan_milp(p0, rate_b), plan_milp(p1, rate_b)\n", "r0, r1 = evaluate_plan(m0, true_b, rate_b), evaluate_plan(m1, true_b, rate_b)\n", "D_FAIL, D_PLAN = 45, 12 # ๋น„๊ณ„ํš ๊ณ ์žฅ/๊ณ„ํš ์ฐฝ์ •๋น„ ๊ฐ€๋™ ์ค‘๋‹จ ์ผ์ˆ˜(๊ฐ€์ •)\n", "down0, down1 = r0[\"fail\"] * D_FAIL + r0[\"planned\"] * D_PLAN, r1[\"fail\"] * D_FAIL + r1[\"planned\"] * D_PLAN\n", "SCALE, INV, OPEX = 1.0, 12000.0, 1200.0 # ๊ด€๋ฆฌ ์žฅ๋น„ ๊ทœ๋ชจ ๋ฐฐ์ˆ˜(์—ฐ), ๋„์ž… ๋น„์šฉ, ์—ฐ ์šด์˜๋น„(๋งŒ์›)\n", "\n", "\n", "def roi(c0, c1, d0, d1, scale=SCALE, inv=INV):\n", " save = (c0 - c1) * scale - OPEX\n", " return save, 1 - d1 / d0, inv / max(save / 12, 1e-9)\n", "\n", "\n", "sv, tr_, pb = roi(r0[\"total\"], r1[\"total\"], down0, down1)\n", "print(f\"\\n[4] 12๊ฐœ์›” ๊ณ„ํš ๋น„๊ต(๋ณด๋ฅ˜ ์žฅ๋น„): ๋น„์šฉ {r0['total']:,.0f} -> {r1['total']:,.0f}๋งŒ์›, ๋น„๊ณ„ํš ๊ณ ์žฅ {r0['fail']} -> {r1['fail']}๋Œ€, ๊ณ„ํš ์ •๋น„ {r0['planned']} -> {r1['planned']}๋Œ€\")\n", "print(f\" ROI 3๋Œ€ ์ง€ํ‘œ [๊ต์œก์šฉ ๊ฐ€์ •: ๊ด€๋ฆฌ ๊ทœ๋ชจ x{SCALE:.0f}, ๋„์ž… {INV:.0f}๋งŒ์›, ์šด์˜ {OPEX:.0f}๋งŒ์›/๋…„, ๊ณ ์žฅ ์ค‘๋‹จ {D_FAIL}์ผยท์ฐฝ์ •๋น„ {D_PLAN}์ผ]\")\n", "print(f\" (1) ์—ฐ๊ฐ„ ๋น„์šฉ ์ ˆ๊ฐ์•ก {sv:,.0f}๋งŒ์› (2) ๊ฐ€๋™ ์ค‘๋‹จ์ผ์ˆ˜ ๋‹จ์ถ•๋ฅ  {tr_:.1%}({down0:,}์ผ -> {down1:,}์ผ) (3) ํˆฌ์ž ํšŒ์ˆ˜ ๊ธฐ๊ฐ„ {pb:.1f}๊ฐœ์›”\")\n", "print(\" ์‚ฐ์‹: ์ ˆ๊ฐ์•ก = (์žฌ๋ณด์ • ์ „ - ํ›„ 12๊ฐœ์›” ๋น„์šฉ) x ๊ทœ๋ชจ - ์šด์˜๋น„, ๋‹จ์ถ•๋ฅ  = 1 - ํ›„ ์ค‘๋‹จ์ผ์ˆ˜/์ „ ์ค‘๋‹จ์ผ์ˆ˜, ํšŒ์ˆ˜ ๊ธฐ๊ฐ„ = ๋„์ž…๋น„ / (์›” ์ ˆ๊ฐ์•ก)\")\n", "print(\" ๊ด€๋ฆฌ ๊ทœ๋ชจ ๋ฏผ๊ฐ๋„:\", \"; \".join(f\"x{s} -> ์ ˆ๊ฐ {roi(r0['total'], r1['total'], down0, down1, scale=s)[0]:,.0f}๋งŒ์›, ํšŒ์ˆ˜ {roi(r0['total'], r1['total'], down0, down1, scale=s)[2]:.0f}๊ฐœ์›”\" for s in (0.5, 1, 2, 3)))\n", "half = r0[\"total\"] - (r0[\"total\"] - r1[\"total\"]) / 2\n", "print(f\" ๋น„๊ด€(์žฌ๋ณด์ • ํšจ๊ณผ ์ ˆ๋ฐ˜๋งŒ ์‹คํ˜„, x1): ์ ˆ๊ฐ {roi(r0['total'], half, down0, down1, scale=1)[0]:,.0f}๋งŒ์›, ํšŒ์ˆ˜ {roi(r0['total'], half, down0, down1, scale=1)[2]:.0f}๊ฐœ์›”\")\n", "print(\" ๊ฐ€์ •๊ฐ’์ด๋ฏ€๋กœ ์žฌ์ •ยท๊ธฐํš ๋ถ€์„œ์™€ ์‹ค์ œ ๋‹จ๊ฐ€๋กœ ๋‹ค์‹œ ์‚ฐ์ •ํ•ด์•ผ ํ•œ๋‹ค. ์žฌ๋ณด์ • ๊ฐ€์ค‘์น˜๋Š” ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ ํ›„ ์šด์˜์— ๋ฐ˜์˜ํ•œ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 3, figsize=(15, 4.3))\n", "ax[0].bar(range(len(ct)), ct[\"ํ‰๊ท  ํŽธํ–ฅ(์ผ)\"]); ax[0].set_xticks(range(len(ct))); ax[0].set_xticklabels([f\"C{i + 1}\" for i in range(len(ct))]); ax[0].set_title(\"AAR: mean bias by condition (days)\")\n", "x = np.arange(4); ax[1].bar(x - .27, wt[\"๋„์ž… ์‹œ ์ถ”์ •(W0)\"], .27, label=\"initial W0\"); ax[1].bar(x, wt[\"์žฌ๋ณด์ •(์šด์˜ AFT)\"], .27, label=\"recalibrated\"); ax[1].bar(x + .27, wt[\"์ˆจ์€ ์ •๋‹ต(์šด์˜)\"], .27, label=\"hidden truth\")\n", "ax[1].set_xticks(x); ax[1].set_xticklabels(ENVS); ax[1].legend(); ax[1].set_title(\"Environment weight matrix\")\n", "ax[2].scatter(yb, p0, s=6, alpha=.5, label=\"before\"); ax[2].scatter(yb, p1, s=6, alpha=.5, label=\"after\"); ax[2].plot([0, 4000], [0, 4000], \"k--\"); ax[2].legend(); ax[2].set_title(\"Hold-out RUL: true vs predicted (days)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 09 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] AAR ๋Œ€์ƒ ์šด์˜ ์žฅ๋น„ 700๋Œ€(์ƒ์กด 448๋Œ€, ๊ณ ์žฅ 252๋Œ€): ํ˜„ํ–‰ ์—”์ง„ MAE 180์ผ, RMSE 241์ผ, MAPE 21.1%, R2 0.909, ํ‰๊ท  ํŽธํ–ฅ -55์ผ\n", " ์กฐ๊ฑด ๋Œ€์ˆ˜ MAE(์ผ) ํ‰๊ท  ํŽธํ–ฅ(์ผ)\n", " ์ผ๋ฐ˜ 202 196.0 -106.0\n", " ํ—˜์ง€ 75 132.0 100.0\n", " ์—ผ๋ถ„ 106 205.0 -169.0\n", " ํ˜นํ•œ 65 144.0 111.0\n", " ์ผ ๊ฐ€๋™๋ฅ  4h ์ด์ƒ 157 127.0 -9.0\n", "๋น„๊ณ„ํš ์ •๋น„์œจ ์ƒ์œ„ 25% 112 195.0 -63.0\n", " ์˜ค์ฐจ ํฐ ์ƒ์œ„ 40๋Œ€์˜ ์›์ธ ๋ถ„๋ฅ˜: ๊ธฐํƒ€(๊ฐœ์ฒด ํŽธ์ฐจ) 22๋Œ€(55%), ์—ผ๋ถ„ ๊ฐ€ํ˜น๋„ ๊ณผ๋Œ€ ๋ฐ˜์˜ 14๋Œ€(35%), ํ˜นํ•œ ๊ฐ€ํ˜น๋„ ๊ณผ์†Œ ๋ฐ˜์˜ 2๋Œ€(5%), ๊ฐ€๋™๋ฅ  ์ƒ์Šน(์šด์šฉ ๊ฐ•๋„ ๋ณ€ํ™”) 1๋Œ€(2%), ๋น„๊ณ„ํš ์ •๋น„ ๋‹ค์ˆ˜ 1๋Œ€(2%)\n", "\n", "[2] ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ ๋งคํŠธ๋ฆญ์Šค(ํด์ˆ˜๋ก ๋นจ๋ฆฌ ๋…ธํ›„)\n", " ๋„์ž… ์‹œ ์ถ”์ •(W0) ์žฌ๋ณด์ •(์šด์˜ AFT) ์ˆจ์€ ์ •๋‹ต(์šด์˜) ๋ณ€ํ™”์œจ\n", "normal 1.00 1.000 1.00 0.000\n", "rough 1.15 1.580 1.55 0.374\n", "salt 1.10 1.164 1.15 0.058\n", "cold 1.05 1.283 1.30 0.222\n", " ๋ณ€ํ™”์œจ 20% ์ดˆ๊ณผ ํ™˜๊ฒฝ ['rough', 'cold']๋Š” ์ •๋น„๊ณ„ํš๊ด€ ๊ฒ€ํ† ยท๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ ๋’ค ์ ์šฉํ•œ๋‹ค(์ž๋™ ๋ฐ˜์˜ ๊ธˆ์ง€).\n", "\n", "[3] ๋ณด๋ฅ˜ ์žฅ๋น„ 461๋Œ€(์žฌ๋ณด์ •์— ์“ฐ์ง€ ์•Š์Œ) ๊ฒ€์ฆ\n", " ์žฌ๋ณด์ • ์ „ ์žฌ๋ณด์ • ํ›„\n", "MAE(์ผ) 184.536 163.118\n", "RMSE(์ผ) 258.982 234.928\n", "MAPE(%) 24.450 19.425\n", "R2 0.887 0.907\n", "ํ‰๊ท  ํŽธํ–ฅ(์ผ) -34.864 -64.634\n", " MAE 11.6% ๊ฐ์†Œ, ํ™˜๊ฒฝ๋ณ„ ํŽธํ–ฅ(์ „ -> ํ›„): normal -98 -> -83, rough +101 -> -73, salt -135 -> -55, cold +141 -> -18\n", "\n", "[4] 12๊ฐœ์›” ๊ณ„ํš ๋น„๊ต(๋ณด๋ฅ˜ ์žฅ๋น„): ๋น„์šฉ 118,444 -> 101,921๋งŒ์›, ๋น„๊ณ„ํš ๊ณ ์žฅ 27 -> 18๋Œ€, ๊ณ„ํš ์ •๋น„ 73 -> 80๋Œ€\n", " ROI 3๋Œ€ ์ง€ํ‘œ [๊ต์œก์šฉ ๊ฐ€์ •: ๊ด€๋ฆฌ ๊ทœ๋ชจ x1, ๋„์ž… 12000๋งŒ์›, ์šด์˜ 1200๋งŒ์›/๋…„, ๊ณ ์žฅ ์ค‘๋‹จ 45์ผยท์ฐฝ์ •๋น„ 12์ผ]\n", " (1) ์—ฐ๊ฐ„ ๋น„์šฉ ์ ˆ๊ฐ์•ก 15,323๋งŒ์› (2) ๊ฐ€๋™ ์ค‘๋‹จ์ผ์ˆ˜ ๋‹จ์ถ•๋ฅ  15.4%(2,091์ผ -> 1,770์ผ) (3) ํˆฌ์ž ํšŒ์ˆ˜ ๊ธฐ๊ฐ„ 9.4๊ฐœ์›”\n", " ์‚ฐ์‹: ์ ˆ๊ฐ์•ก = (์žฌ๋ณด์ • ์ „ - ํ›„ 12๊ฐœ์›” ๋น„์šฉ) x ๊ทœ๋ชจ - ์šด์˜๋น„, ๋‹จ์ถ•๋ฅ  = 1 - ํ›„ ์ค‘๋‹จ์ผ์ˆ˜/์ „ ์ค‘๋‹จ์ผ์ˆ˜, ํšŒ์ˆ˜ ๊ธฐ๊ฐ„ = ๋„์ž…๋น„ / (์›” ์ ˆ๊ฐ์•ก)\n", " ๊ด€๋ฆฌ ๊ทœ๋ชจ ๋ฏผ๊ฐ๋„: x0.5 -> ์ ˆ๊ฐ 7,061๋งŒ์›, ํšŒ์ˆ˜ 20๊ฐœ์›”; x1 -> ์ ˆ๊ฐ 15,323๋งŒ์›, ํšŒ์ˆ˜ 9๊ฐœ์›”; x2 -> ์ ˆ๊ฐ 31,846๋งŒ์›, ํšŒ์ˆ˜ 5๊ฐœ์›”; x3 -> ์ ˆ๊ฐ 48,368๋งŒ์›, ํšŒ์ˆ˜ 3๊ฐœ์›”\n", " ๋น„๊ด€(์žฌ๋ณด์ • ํšจ๊ณผ ์ ˆ๋ฐ˜๋งŒ ์‹คํ˜„, x1): ์ ˆ๊ฐ 7,061๋งŒ์›, ํšŒ์ˆ˜ 20๊ฐœ์›”\n", " ๊ฐ€์ •๊ฐ’์ด๋ฏ€๋กœ ์žฌ์ •ยท๊ธฐํš ๋ถ€์„œ์™€ ์‹ค์ œ ๋‹จ๊ฐ€๋กœ ๋‹ค์‹œ ์‚ฐ์ •ํ•ด์•ผ ํ•œ๋‹ค. ์žฌ๋ณด์ • ๊ฐ€์ค‘์น˜๋Š” ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ ํ›„ ์šด์˜์— ๋ฐ˜์˜ํ•œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 10. ์žฅ๋น„ ๋…ธํ›„๋„ ๊ธฐ๋ฐ˜ ๊ธฐ๋Œ€์ˆ˜๋ช… ์˜ˆ์ธก ๋ฐ ๊ณ„ํš์ •๋น„ ์ถ”์ฒœ ์‹œ์Šคํ…œ ํ†ตํ•ฉ ์™„์„ฑ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์žฅ๋น„ ๋…ธํ›„๋„ ๊ธฐ๋ฐ˜ ๊ธฐ๋Œ€์ˆ˜๋ช… ์˜ˆ์ธก ๋ฐ ๊ณ„ํš์ •๋น„ ์ผ์ • ์ถ”์ฒœ ๋„๊ตฌ๋ฅผ ์™„์„ฑํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๊ธฐ๋Œ€์ˆ˜๋ช… ์˜ˆ์ธก๊ณผ ๊ณ„ํš์ •๋น„ ์ถ”์ฒœ ์‹œ์Šคํ…œ์„ ํ†ตํ•ฉ ์‹คํ–‰ํ•˜๊ณ  ์šด์˜ ๋ฌธ์„œ์™€ ์ž๋™ ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ์„ ์™„์„ฑํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋ณด์œ  ํ˜„ํ™ฉ ๊ฐฑ์‹ , ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ, ๋ถ„ํฌ ๋ณ€ํ™”(PSI) ์ ๊ฒ€, ์ฑ”ํ”ผ์–ธ/์ฑŒ๋ฆฐ์ € ์žฌํ•™์Šต, ์ˆ˜๋ช… ์˜ˆ์ธก, 12๊ฐœ์›” ์ •๋น„ ์ผ์ • ์ถ”์ฒœ์„ ํ•œ ๋ฒˆ์— ์‹คํ–‰ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/sup02_operations_guide.md ๋ฐ pipeline/sup02_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": [ "\"\"\"์žฅ๋น„ ๋ณด์œ  ํ˜„ํ™ฉ ๊ฐฑ์‹ , ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ, ๋ถ„ํฌ ๋ณ€ํ™”(PSI) ์ ๊ฒ€, ์ž๋™ ์žฌํ•™์Šต(์ฑ”ํ”ผ์–ธ/์ฑŒ๋ฆฐ์ €), ์ž”์—ฌ ์ˆ˜๋ช…(RUL) ์˜ˆ์ธก, ์ •๋น„์ฐฝ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐยท์‹œํšจ์„ฑ ํ‚คํŠธ ์ œ์•ฝ์˜ 12๊ฐœ์›” ๊ณ„ํš์ •๋น„ ์ผ์ • ์ถ”์ฒœ๊ณผ ์Šน์ธ๋Œ€๊ธฐยท๋„ํƒœ ํ›„๋ณด ๋ถ„๋ฆฌ๊นŒ์ง€ ํ•œ ๋ฒˆ์— ์‹คํ–‰ํ•˜๋Š” ํ†ตํ•ฉ ํŒŒ์ดํ”„๋ผ์ธ์„ ์™„์„ฑํ•˜๊ณ  ์šด์˜ ๋ฌธ์„œ ์™„๋น„์œจยท์ž์ฒด ์ ๊ฒ€ยท์ธํ„ฐํŽ˜์ด์Šค ๋ช…์„ธยท๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ์„ ์ถœ๋ ฅํ•œ๋‹ค.\"\"\"\n", "import importlib\n", "import json\n", "import sys\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.metrics import r2_score\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์žฅ๋น„ ๋ณด์œ  ํ˜„ํ™ฉยท๊ณ ์žฅ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ˆ˜์น˜ ์—†์Œ)\n", "# ๊ฐ€์ƒ ๋ถ€๋Œ€ 6๊ฐœ(U1~U6)์˜ ์žฅ๋น„๊ฐ€ ์šด์šฉ ํ™˜๊ฒฝ(normal/rough/salt/cold)์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์†๋„๋กœ ๋…ธํ›„ํ™”ํ•œ๋‹ค. ์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„(h_since)์ด\n", "# ์žฅ๋น„๋งˆ๋‹ค ์ˆจ์€ ์ˆ˜๋ช…(t_fail)์„ ๋„˜์œผ๋ฉด ๊ณ ์žฅ(event=1) ์ด๋ ฅ, ๋„˜์ง€ ์•Š์•˜์œผ๋ฉด ํ˜„์žฌ๊นŒ์ง€ ์ƒ์กด(event=0, ๊ด€์ธก ์ค‘๋„์ ˆ๋‹จ)์ด๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ENVS = [\"normal\", \"rough\", \"salt\", \"cold\"]\n", "ENV_W = {\"normal\": 1.00, \"rough\": 1.35, \"salt\": 1.25, \"cold\": 1.12} # ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜(์ˆจ์€ ์ •๋‹ต, ๊ฐ€์ •๊ฐ’): ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ์งง๋‹ค\n", "ENV_KO = {\"normal\": \"์ผ๋ฐ˜\", \"rough\": \"ํ—˜์ง€\", \"salt\": \"์—ผ๋ถ„\", \"cold\": \"ํ˜นํ•œ\"}\n", "UNITS = [\"U1\", \"U2\", \"U3\", \"U4\", \"U5\", \"U6\"]\n", "UNIT_ENV = {\"U1\": [.7, .1, .1, .1], \"U2\": [.2, .6, .1, .1], \"U3\": [.2, .1, .6, .1], \"U4\": [.3, .1, .1, .5], \"U5\": [.5, .3, .1, .1], \"U6\": [.4, .1, .4, .1]}\n", "ETYPES = [\"truck\", \"generator\", \"trailer\", \"radio\"]\n", "CYCLE = 8000.0 # ๊ทœ์ • ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ(์šด์šฉ์‹œ๊ฐ„ h, ๊ฐ€์ •๊ฐ’)\n", "KSHAPE = 12.0 # ์ˆ˜๋ช… ๋ถ„ํฌ ์™€์ด๋ธ” ํ˜•์ƒ(๋งˆ๋ชจํ˜• ๊ณ ์žฅ, ํด์ˆ˜๋ก ์ˆ˜๋ช…์ด ๋ชจ์—ฌ ์žˆ๋‹ค)\n", "INSP_NOISE = 0.025 # ์ •๋น„ ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ ์ธก์ • ์˜ค์ฐจ(๊ฐ€์ •๊ฐ’)\n", "\n", "\n", "def make_fleet(n=900, seed=0, env_w=None, shift=1.0):\n", " \"\"\"์žฅ๋น„ n๋Œ€. env_w๋กœ ์ˆจ์€ ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜๋ฅผ ๋ฐ”๊ฟ” ์šด์˜ ๋ถ„ํฌ ๋ณ€ํ™”๋ฅผ ๋ชจ์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. shift>1์ด๋ฉด ๊ฐ€๋™๋ฅ ์ด ๋†’์•„์ง„๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " w = env_w or ENV_W\n", " unit = rng.choice(UNITS, n)\n", " env = np.array([rng.choice(ENVS, p=UNIT_ENV[u]) for u in unit])\n", " et = rng.choice(ETYPES, n, p=[.4, .2, .2, .2])\n", " rate = rng.lognormal(np.log(3.0 * shift), 0.25, n) # ์ผ ํ‰๊ท  ์šด์šฉ์‹œ๊ฐ„(h/์ผ)\n", " h_since = rng.uniform(300, 11500, n) # ๋งˆ์ง€๋ง‰ ์ฐฝ์ •๋น„ ์ดํ›„ ์šด์šฉ์‹œ๊ฐ„\n", " n_ovh = rng.integers(0, 3, n)\n", " hours = h_since + n_ovh * rng.uniform(5000, 8000, n) # ๋ˆ„์  ์šด์šฉ์‹œ๊ฐ„\n", " km = hours * rng.normal(17, 3, n) * np.where(env == \"rough\", 0.8, 1.0) # ๋ˆ„์  ๊ธฐ๋™๊ฑฐ๋ฆฌ(km)\n", " age_y = np.clip(hours / (rate * 365) * rng.uniform(0.9, 1.15, n), 0.5, 25)\n", " unpl = rng.poisson(0.35 * h_since / 1000 * np.array([w[e] for e in env]) / 1.3) # ๋น„๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜(ํ™˜๊ฒฝ์ด ๊ฐ€ํ˜นํ• ์ˆ˜๋ก ๋งŽ๋‹ค)\n", " n_maint = rng.poisson(0.6 * h_since / 1000 + 1) # ๊ณ„ํš ์ •๋น„ ๊ฑด์ˆ˜\n", " scale = 9600 / np.array([w[e] for e in env]) * 0.985 ** unpl * np.exp(rng.normal(0, 0.04, n))\n", " t_fail = scale * rng.weibull(KSHAPE, n) # ์ฐฝ์ •๋น„ ํ›„ ๊ณ ์žฅ๊นŒ์ง€ ์šด์šฉ์‹œ๊ฐ„(์ˆจ์€ ์ •๋‹ต)\n", " event = (t_fail <= h_since).astype(int)\n", " dur = np.where(event == 1, t_fail, h_since)\n", " df = pd.DataFrame({\"eq_id\": [f\"EQ{seed:02d}{i:04d}\" for i in range(n)], \"unit\": unit, \"env\": env, \"etype\": et, \"rate\": rate, \"hours\": hours, \"km\": km, \"age_y\": age_y, \"n_ovh\": n_ovh,\n", " \"h_since\": h_since, \"unpl\": unpl, \"n_maint\": n_maint, \"t_fail\": t_fail, \"event\": event, \"dur\": dur})\n", " df[\"insp\"] = np.clip(dur / t_fail + rng.normal(0, INSP_NOISE, n), 0, 1.3) # ๋งˆ์ง€๋ง‰ ์ •๋น„ ์ ๊ฒ€์—์„œ ์ธก์ •ํ•œ ๋งˆ๋ชจ ๋น„์œจ(์†Œ๋ชจ ์ˆ˜๋ช…/์ด ์ˆ˜๋ช…, ์˜ค์ฐจ ํฌํ•จ)\n", " df[\"rul_h\"] = np.where(event == 0, t_fail - h_since, np.nan) # ์ƒ์กด ์žฅ๋น„์˜ ๋‚จ์€ ์šด์šฉ์‹œ๊ฐ„(์ •๋‹ต)\n", " df[\"rul_d\"] = df[\"rul_h\"] / df[\"rate\"] # ๋‚จ์€ ์ผ์ˆ˜(์ •๋‹ต)\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " y, p = np.asarray(y, float), np.asarray(p, float)\n", " return float(np.mean(np.abs(y - p) / np.abs(y)) * 100)\n", "\n", "\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "\n", "\n", "def rmse(y, p):\n", " return float(np.sqrt(np.mean((np.asarray(y, float) - np.asarray(p, float)) ** 2)))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธํ›„ํ™” ํŒŒ์ƒ ํ”ผ์ฒ˜์™€ ํ•™์Šต ํ‘œ๋ณธ ๋งŒ๋“ค๊ธฐ. ํ™˜๊ฒฝ ๊ฐ€์ค‘์น˜ W0๋Š” ๋„์ž… ์‹œ์ ์˜ ์ „๋ฌธ๊ฐ€ ์ถ”์ •๊ฐ’์ด๋ฉฐ(์‹ค์ œ ๊ฐ€์ค‘์น˜์™€ ๋‹ค๋ฆ„) M9์—์„œ ์žฌ๋ณด์ •ํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "W0 = {\"normal\": 1.00, \"rough\": 1.15, \"salt\": 1.10, \"cold\": 1.05}\n", "FEATS = [\"h_since\", \"hours\", \"km\", \"age_y\", \"rate\", \"n_maint\", \"unpl\", \"unpl_per_kh\", \"km_per_h\", \"n_ovh\", \"k_rough\", \"k_salt\", \"k_cold\", \"k_truck\", \"k_gen\", \"k_trailer\",\n", " \"aging_idx\", \"lin_rul_d\", \"hs_frac\", \"insp\", \"insp_eta_h\"]\n", "\n", "\n", "def add_features(df, w=None):\n", " d = df.copy()\n", " w = w or W0\n", " d[\"unpl_per_kh\"] = d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)\n", " d[\"km_per_h\"] = d[\"km\"] / d[\"hours\"]\n", " for e in (\"rough\", \"salt\", \"cold\"):\n", " d[\"k_\" + e] = (d[\"env\"] == e).astype(int)\n", " d[\"k_truck\"], d[\"k_gen\"], d[\"k_trailer\"] = (d[\"etype\"] == \"truck\").astype(int), (d[\"etype\"] == \"generator\").astype(int), (d[\"etype\"] == \"trailer\").astype(int)\n", " d[\"aging_idx\"] = d[\"h_since\"] * d[\"env\"].map(w) / CYCLE # ํ™˜๊ฒฝ ๊ฐ€์ค‘ ๋…ธํ›„ ์ง€์ˆ˜(1์ด๋ฉด ์ฐฝ์ •๋น„ ์ฃผ๊ธฐ์— ๋„๋‹ฌ)\n", " d[\"lin_rul_d\"] = (CYCLE - d[\"h_since\"]) / d[\"rate\"] # ์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ์ž”์—ฌ ์ผ์ˆ˜(Baseline)\n", " d[\"hs_frac\"] = d[\"h_since\"] / CYCLE\n", " d[\"insp_eta_h\"] = d[\"h_since\"] / np.maximum(d[\"insp\"], 0.05) # ์ ๊ฒ€ ๋งˆ๋ชจ ๋น„์œจ๋กœ ํ™˜์‚ฐํ•œ ์ด ์ˆ˜๋ช… ์ถ”์ •(h)\n", " return d\n", "\n", "\n", "def snapshots(df, k=4, seed=0):\n", " \"\"\"๊ณ ์žฅ ์ด๋ ฅ ์žฅ๋น„์˜ ๊ณผ๊ฑฐ ์‹œ์  ํ‘œ๋ณธ: ๊ณ ์žฅ ์ „ ์ž„์˜ ์‹œ์ ์˜ ์ ๊ฒ€๊ฐ’ยท์šด์šฉ์‹œ๊ฐ„๊ณผ ๊ทธ๋•Œ์˜ ๋‚จ์€ ์ผ์ˆ˜. (์ƒ์กด ์žฅ๋น„์˜ ๋ฏธ๋ž˜๋Š” ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๊ณ ์žฅ ์ด๋ ฅ๋งŒ ์“ด๋‹ค)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " f = df[df[\"event\"] == 1]\n", " out = []\n", " for _ in range(k):\n", " s = f.copy()\n", " h = s[\"t_fail\"] * rng.uniform(0.2, 0.98, len(s))\n", " ratio = h / s[\"h_since\"]\n", " s[\"hours\"] = s[\"hours\"] - (s[\"h_since\"] - h)\n", " s[\"km\"] = s[\"km\"] * ratio.clip(upper=1)\n", " s[\"unpl\"] = np.floor(s[\"unpl\"] * ratio)\n", " s[\"n_maint\"] = np.floor(s[\"n_maint\"] * ratio)\n", " s[\"insp\"] = np.clip(h / s[\"t_fail\"] + rng.normal(0, INSP_NOISE, len(s)), 0, 1.3)\n", " s[\"h_since\"] = h\n", " s[\"rul_d\"] = (s[\"t_fail\"] - h) / s[\"rate\"]\n", " s[\"event\"] = 0\n", " out.append(s)\n", " return pd.concat(out, ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ƒ์กด ๋ถ„์„ ๋„๊ตฌ(scipy๋งŒ ์‚ฌ์šฉ): ์™€์ด๋ธ” AFT(๊ฐ€์† ์ˆ˜๋ช…) ๋ชจํ˜•๊ณผ Cox ๋น„๋ก€์œ„ํ—˜ ๋ชจํ˜•\n", "# ---------------------------------------------------------------------------\n", "from scipy.optimize import minimize\n", "\n", "\n", "def aft_x(d):\n", " return np.c_[np.ones(len(d)), d[\"env\"].eq(\"rough\"), d[\"env\"].eq(\"salt\"), d[\"env\"].eq(\"cold\"), d[\"unpl\"] / (d[\"h_since\"] / 1000 + 0.5)].astype(float)\n", "\n", "\n", "AFT_COLS = [\"์ ˆํŽธ\", \"ํ—˜์ง€\", \"์—ผ๋ถ„\", \"ํ˜นํ•œ\", \"๋น„๊ณ„ํš ์ •๋น„์œจ\"]\n", "\n", "\n", "def fit_aft(d):\n", " \"\"\"์ฐฝ์ •๋น„ ํ›„ ์šด์šฉ์‹œ๊ฐ„ dur(๊ณ ์žฅ์ด๋ฉด event=1, ์ƒ์กด์ด๋ฉด ์ค‘๋„์ ˆ๋‹จ)์— ๋Œ€ํ•œ ์™€์ด๋ธ” AFT ์ตœ๋Œ€์šฐ๋„ ์ถ”์ •. ๋ฐ˜ํ™˜: ๊ณ„์ˆ˜(๋งˆ์ง€๋ง‰์€ log ํ˜•์ƒ).\"\"\"\n", " x, t, e = aft_x(d), d[\"dur\"].values, d[\"event\"].values\n", "\n", " def nll(p):\n", " lam, k = np.exp(x @ p[:-1]), np.exp(p[-1])\n", " z = (t / lam) ** k\n", " return -(e * (np.log(k) - np.log(lam) + (k - 1) * (np.log(t) - np.log(lam))) - z).sum()\n", "\n", " r = minimize(nll, np.r_[np.log(8000), np.zeros(x.shape[1] - 1), np.log(5)], method=\"BFGS\")\n", " return r.x\n", "\n", "\n", "def aft_pred(p, d, q=0.5, h=None):\n", " \"\"\"์ด๋ฏธ h(์šด์šฉ์‹œ๊ฐ„)๊นŒ์ง€ ์ƒ์กดํ–ˆ๋‹ค๋Š” ์กฐ๊ฑด์—์„œ ๋ถ„์œ„์ˆ˜ q ์ƒ์กด ์‹œ์ (์šด์šฉ์‹œ๊ฐ„ h ๊ธฐ์ค€ ์ด ์ˆ˜๋ช…)์„ ๋Œ๋ ค์ค€๋‹ค. q=0.5๊ฐ€ ์กฐ๊ฑด๋ถ€ ์ค‘์•™๊ฐ’.\"\"\"\n", " lam, k = np.exp(aft_x(d) @ p[:-1]), np.exp(p[-1])\n", " h = d[\"h_since\"].values if h is None else h\n", " return lam * ((h / lam) ** k - np.log(q)) ** (1 / k)\n", "\n", "\n", "def aft_fail_prob(p, d, h0, h1):\n", " \"\"\"h0๊นŒ์ง€ ์ƒ์กดํ•œ ์žฅ๋น„๊ฐ€ h0~h1 ์‚ฌ์ด์— ๊ณ ์žฅ ๋‚  ์กฐ๊ฑด๋ถ€ ํ™•๋ฅ .\"\"\"\n", " lam, k = np.exp(aft_x(d) @ p[:-1]), np.exp(p[-1])\n", " s0, s1 = np.exp(-(h0 / lam) ** k), np.exp(-(h1 / lam) ** k)\n", " return 1 - s1 / s0\n", "\n", "\n", "def fit_cox(d, iters=25):\n", " \"\"\"Cox ๋น„๋ก€์œ„ํ—˜(Breslow ๊ทผ์‚ฌ) ๋‰ดํ„ด-๋žฉ์Šจ. ๊ณต๋ณ€๋Ÿ‰: ํ—˜์ง€ยท์—ผ๋ถ„ยทํ˜นํ•œยท๋น„๊ณ„ํš ์ •๋น„์œจ. ๋ฐ˜ํ™˜: ๊ณ„์ˆ˜.\"\"\"\n", " x = aft_x(d)[:, 1:]\n", " o = np.argsort(-d[\"dur\"].values)\n", " x, e = x[o], d[\"event\"].values[o]\n", " b = np.zeros(x.shape[1])\n", " for _ in range(iters):\n", " r = np.exp(x @ b)\n", " S0 = np.cumsum(r)\n", " S1 = np.cumsum(r[:, None] * x, axis=0)\n", " S2 = np.cumsum(r[:, None, None] * x[:, :, None] * x[:, None, :], axis=0)\n", " m1 = S1 / S0[:, None]\n", " g = ((x - m1) * e[:, None]).sum(axis=0)\n", " H = -sum(e[i] * (S2[i] / S0[i] - np.outer(m1[i], m1[i])) for i in np.where(e == 1)[0])\n", " step = np.linalg.solve(H - 1e-6 * np.eye(len(b)), g)\n", " b = b - step\n", " if np.abs(step).max() < 1e-7:\n", " break\n", " return b\n", "\n", "\n", "def cindex(time, event, risk):\n", " \"\"\"์ผ์น˜๋„ C-index: ์œ„ํ—˜ ์ ์ˆ˜๊ฐ€ ๋†’์€ ์ชฝ์ด ๋จผ์ € ๊ณ ์žฅ ๋‚ฌ๋Š”๊ฐ€(๊ด€์ธก ๊ฐ€๋Šฅํ•œ ์Œ๋งŒ).\"\"\"\n", " t, e, r = np.asarray(time), np.asarray(event), np.asarray(risk)\n", " num = den = 0.0\n", " for i in np.where(e == 1)[0]:\n", " m = t > t[i]\n", " den += m.sum()\n", " num += (r[m] < r[i]).sum() + 0.5 * (r[m] == r[i]).sum()\n", " return num / max(den, 1)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ž”์—ฌ ์ˆ˜๋ช…(RUL, ์ผ) ์˜ˆ์ธก ์—”์ง„: ์™€์ด๋ธ” AFT ์กฐ๊ฑด๋ถ€ ์ค‘์•™๊ฐ’ + ๊ทธ๋ž˜๋””์–ธํŠธ ๋ถ€์ŠคํŒ…(๊ณ ์žฅ ์ด๋ ฅ ์‹œ์  ํ‘œ๋ณธ ํ•™์Šต)์˜ ํ‰๊ท \n", "# ---------------------------------------------------------------------------\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "\n", "\n", "def fit_engine(train, seed=0):\n", " p = fit_aft(train)\n", " sn = add_features(snapshots(train, 5, seed))\n", " sn[\"aft\"] = (aft_pred(p, sn) - sn[\"h_since\"]) / sn[\"rate\"]\n", " g = GradientBoostingRegressor(n_estimators=250, max_depth=3, learning_rate=0.06, random_state=0).fit(sn[FEATS + [\"aft\"]], sn[\"rul_d\"])\n", " return {\"p\": p, \"g\": g}\n", "\n", "\n", "def predict_rul(eng, d):\n", " \"\"\"d๋Š” add_features๋ฅผ ๊ฑฐ์นœ ํ‘œ. ๋ฐ˜ํ™˜: (์•™์ƒ๋ธ” RUL, AFT RUL, GBR RUL) - ์ผ ๋‹จ์œ„.\"\"\"\n", " aft = ((aft_pred(eng[\"p\"], d) - d[\"h_since\"]) / d[\"rate\"]).values\n", " x = d[FEATS].copy()\n", " x[\"aft\"] = aft\n", " gb = eng[\"g\"].predict(x)\n", " return np.clip(0.5 * aft + 0.5 * gb, 1, None), aft, gb\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๊ณ„ํš์ •๋น„(์ฐฝ์ •๋น„) ์ผ์ • ์ถ”์ฒœ: ์›”๋ณ„ ์ •๋น„์ฐฝ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐยท์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ(์œ ํšจ๊ธฐํ•œ) ์ œ์•ฝ ์•„๋ž˜ ๊ธฐ๋Œ€ ๋น„์šฉ ์ตœ์†Œํ™” (๋น„์šฉ ๋‹จ๊ฐ€๋Š” ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’, ๋งŒ์›)\n", "# ---------------------------------------------------------------------------\n", "from scipy.optimize import milp, LinearConstraint, Bounds\n", "from scipy.stats import norm\n", "\n", "C_PLAN, C_FAIL, C_WASTE_KH, C_KIT = 600.0, 2400.0, 40.0, 120.0 # ๊ณ„ํš ์ฐฝ์ •๋น„, ๋น„๊ณ„ํš ๊ณ ์žฅ(๊ธด๊ธ‰ ์ •๋น„ ํฌํ•จ), ๋ฏธ์‚ฌ์šฉ ์ˆ˜๋ช… 1000h๋‹น ์†์‹ค, ์‹œํšจ์„ฑ ๋ถ€ํ’ˆ ํ‚คํŠธ\n", "HORIZON, MDAYS = 12, 30 # ๊ณ„ํš 12๊ฐœ์›”, ์›” 30์ผ\n", "CAP_M, BUDGET_M = 22, 15000.0 # ์›”๋ณ„ ์ •๋น„์ฐฝ ๋Šฅ๋ ฅ(๋Œ€), ์›”๋ณ„ ์˜ˆ์‚ฐ\n", "LOTS = [(30, 70), (45, 170), (45, 390), (70, 800)] # ์‹œํšจ์„ฑ ํ‚คํŠธ ๋กœํŠธ(์ˆ˜๋Ÿ‰, ์œ ํšจ๊ธฐํ•œ๊นŒ์ง€ ๋‚จ์€ ์ผ์ˆ˜)\n", "KIT_MARGIN = 14 # ์ •๋น„์ผ ๊ธฐ์ค€ ์ตœ์†Œ ์œ ํšจ๊ธฐ๊ฐ„ ์—ฌ์œ (์ผ)\n", "\n", "\n", "def exp_cost_matrix(pred, rate, month_days):\n", " \"\"\"์žฅ๋น„ i๋ฅผ ์›” m์— ์ •๋น„ํ•  ๋•Œ์˜ ๊ธฐ๋Œ€ ๋น„์šฉ. ์ฐธ RUL ~ N(pred, sigma), sigma = 35์ผ + 0.12 x pred (๊ฐ€์ •). ๋งˆ์ง€๋ง‰ ์—ด์€ ๊ณ„ํš ๊ธฐ๊ฐ„ ๋ฐ–(๋ฏธ์ •๋น„).\"\"\"\n", " sig = 35 + 0.12 * pred\n", " C = np.zeros((len(pred), len(month_days) + 1))\n", " for j, day in enumerate(month_days):\n", " z = (day - pred) / sig\n", " pf = norm.cdf(z)\n", " unused = np.maximum(pred - day, 0) * rate * 0.6 # ์ •๋น„๋ฅผ ์•ž๋‹น๊ธด ๋งŒํผ ๋ฒ„๋ฆฌ๋Š” ์šด์šฉ์‹œ๊ฐ„ h(๊ทผ์‚ฌ)\n", " C[:, j] = C_PLAN + C_KIT + pf * (C_FAIL - C_PLAN) + C_WASTE_KH * unused / 1000 * (1 - pf)\n", " C[:, -1] = norm.cdf((HORIZON * MDAYS - pred) / sig) * C_FAIL\n", " return C\n", "\n", "\n", "def plan_milp(pred, rate, cap=CAP_M, budget=BUDGET_M, lots=LOTS, use_kit=True):\n", " \"\"\"์ •์ˆ˜๊ณ„ํš์œผ๋กœ ์žฅ๋น„๋ณ„ ์ •๋น„ ์›”(0~11) ๋˜๋Š” ๋ฏธ์ •๋น„(-1)๋ฅผ ๋ฐฐ์ •ํ•œ๋‹ค. ํ›„๋ณด๋Š” ์˜ˆ์ธก RUL์ด ๊ณ„ํš ๊ธฐ๊ฐ„ +90์ผ ์ด๋‚ด์ธ ์žฅ๋น„.\"\"\"\n", " md = np.arange(1, HORIZON + 1) * MDAYS\n", " cand = np.where(pred < HORIZON * MDAYS + 90)[0]\n", " n, M = len(cand), HORIZON\n", " C = exp_cost_matrix(pred[cand], rate[cand], md)\n", " c = C.ravel()\n", " A, lo, hi = [], [], []\n", " for i in range(n): # ์žฅ๋น„๋‹น ํ•œ ๋ฒˆ๋งŒ ๋ฐฐ์ •(์›” 12์นธ + ๋ฏธ์ •๋น„)\n", " row = np.zeros(n * (M + 1)); row[i * (M + 1):(i + 1) * (M + 1)] = 1; A.append(row); lo.append(1); hi.append(1)\n", " for m in range(M): # ์›”๋ณ„ ๋Šฅ๋ ฅยท์˜ˆ์‚ฐ\n", " row = np.zeros(n * (M + 1)); row[m::(M + 1)] = 1\n", " A.append(row); lo.append(0); hi.append(cap)\n", " A.append(row * (C_PLAN + C_KIT)); lo.append(0); hi.append(budget)\n", " if use_kit: # ์‹œํšจ์„ฑ ํ‚คํŠธ: m์›” ์ดํ›„ ์ •๋น„ ์ˆ˜์š”๋Š” ๊ทธ ์‹œ์ ์— ์œ ํšจ๊ธฐ๊ฐ„์ด ๋‚จ์€ ๋กœํŠธ ์ˆ˜๋Ÿ‰์„ ๋„˜์„ ์ˆ˜ ์—†๋‹ค\n", " for m in range(M):\n", " sup = sum(q for q, e in lots if e >= md[m] + KIT_MARGIN)\n", " row = np.zeros(n * (M + 1))\n", " for mm in range(m, M):\n", " row[mm::(M + 1)] = 1\n", " A.append(row); lo.append(0); hi.append(sup)\n", " r = milp(c, constraints=LinearConstraint(np.array(A), lo, hi), integrality=np.ones(len(c)), bounds=Bounds(0, 1), options={\"time_limit\": 20})\n", " month = np.full(len(pred), -1)\n", " if r.x is not None:\n", " x = np.round(r.x).reshape(n, M + 1)\n", " assign = np.where(x[:, :M].sum(axis=1) > 0.5, x[:, :M].argmax(axis=1), -1)\n", " month[cand] = assign\n", " return month\n", "\n", "\n", "def plan_greedy(pred, margin=45, cap=CAP_M, budget=BUDGET_M):\n", " \"\"\"๊ฐ„๋‹จํ•œ ํƒ์š• ๋ฐฐ์ •: ์ •๋น„ ๋ชฉํ‘œ์ผ(์˜ˆ์ธก RUL - margin)์ด ๋น ๋ฅธ ์ˆœ์œผ๋กœ ๋ชฉํ‘œ์ผ ์ด์ „ ๊ฐ€์žฅ ๋Šฆ์€ ๋นˆ ์›”, ์—†์œผ๋ฉด ๊ฐ€์žฅ ์ด๋ฅธ ๋นˆ ์›”์— ๋ฐฐ์ •.\"\"\"\n", " cap = min(cap, int(budget // (C_PLAN + C_KIT))) # ์›” ์˜ˆ์‚ฐ์ด ํ—ˆ์šฉํ•˜๋Š” ๋Œ€์ˆ˜๋กœ ๋Šฅ๋ ฅ์„ ์ œํ•œ\n", " month = np.full(len(pred), -1)\n", " left = np.full(HORIZON, cap)\n", " for i in np.argsort(pred):\n", " if pred[i] > HORIZON * MDAYS + 60: break\n", " tgt = int(np.clip((pred[i] - margin) // MDAYS - 1, 0, HORIZON - 1))\n", " for m in list(range(tgt, -1, -1)) + list(range(tgt + 1, HORIZON)):\n", " if left[m] > 0:\n", " month[i] = m; left[m] -= 1; break\n", " return month\n", "\n", "\n", "def plan_fixed(lin_rul, cap=CAP_M, budget=BUDGET_M):\n", " \"\"\"๊ด€ํ–‰: ๊ทœ์ • ์ฃผ๊ธฐ ๋„๋‹ฌ ์ˆœ์„œ๋Œ€๋กœ(์„ ํ˜• ์ž”์—ฌ ์ผ์ˆ˜) ์›”๋ณ„ ๋Šฅ๋ ฅ ์•ˆ์—์„œ ๋ฐฐ์ •.\"\"\"\n", " return plan_greedy(np.asarray(lin_rul, float), margin=0, cap=cap, budget=budget)\n", "\n", "\n", "def evaluate_plan(month, true_rul, rate, cost_kit=True):\n", " \"\"\"์ฐธ RUL๋กœ ๊ณ„ํš์˜ ์‹ค์ œ ๋น„์šฉ์„ ๊ณ„์‚ฐํ•œ๋‹ค: ์ •๋น„์ผ ์ด์ „์— ๊ณ ์žฅ -> ๋น„๊ณ„ํš ๊ณ ์žฅ ๋น„์šฉ, ์•„๋‹ˆ๋ฉด ๊ณ„ํš ๋น„์šฉ + ๋ฏธ์‚ฌ์šฉ ์ˆ˜๋ช… ์†์‹ค.\"\"\"\n", " day = np.where(month >= 0, (month + 1) * MDAYS, 10 ** 9)\n", " fail = (true_rul < day) & (true_rul < HORIZON * MDAYS)\n", " planned = (month >= 0) & ~fail\n", " waste = np.where(planned, (true_rul - day) * rate * 0.6, 0) * C_WASTE_KH / 1000\n", " cost = fail * C_FAIL + planned * (C_PLAN + (C_KIT if cost_kit else 0)) + waste\n", " return {\"total\": float(cost.sum()), \"fail\": int(fail.sum()), \"planned\": int(planned.sum()), \"waste\": float(waste.sum()), \"unsched_fail\": int(((month < 0) & fail).sum())}\n", "\n", "\n", "def kit_fefo(month, lots=LOTS):\n", " \"\"\"์œ ํšจ๊ธฐํ•œ์ด ๋น ๋ฅธ ๋กœํŠธ๋ถ€ํ„ฐ ์“ฐ๋Š” FEFO๋กœ ํ‚คํŠธ๋ฅผ ๋ฐฐ์ •ํ•˜๊ณ  ์ •๋น„์ผ์— ์œ ํšจ๊ธฐ๊ฐ„์ด ๋ถ€์กฑํ•œ ๋ฐฐ์ • ์ˆ˜์™€ ๋งŒ๋ฃŒ ํ๊ธฐ ์ˆ˜๋Ÿ‰์„ ์„ผ๋‹ค.\"\"\"\n", " q = [[qty, e] for qty, e in sorted(lots, key=lambda t: t[1])]\n", " short = 0\n", " for m in sorted(month[month >= 0]):\n", " day = (m + 1) * MDAYS\n", " for lot in q:\n", " if lot[1] >= day + KIT_MARGIN and lot[0] > 0:\n", " lot[0] -= 1; break\n", " else:\n", " short += 1\n", " expired = sum(l[0] for l in q if l[1] < HORIZON * MDAYS)\n", " return {\"short\": short, \"expired_unused\": expired, \"left\": sum(l[0] for l in q)}\n", "# ---------------------------------------------------------------------------\n", "\n", "libs = [\"numpy\", \"pandas\", \"scipy\", \"sklearn\", \"matplotlib\"]\n", "env_ok = sum(importlib.util.find_spec(m) is not None for m in libs)\n", "print(f\"[0] ํ™˜๊ฒฝ ์ ๊ฒ€: Python {sys.version_info.major}.{sys.version_info.minor}, ํ•„์ˆ˜ ๋ชจ๋“ˆ {env_ok}/{len(libs)}\")\n", "\n", "# ---------------- 1) ๋ณด์œ  ํ˜„ํ™ฉ ๊ฐฑ์‹ ยท์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ(๋ฏธ์…˜ 1~2 ์š”์•ฝ) ----------------\n", "OPS_W = {\"normal\": 1.00, \"rough\": 1.55, \"salt\": 1.15, \"cold\": 1.30}\n", "base = make_fleet(900, seed=1) # ๊ธฐ์กด ๋ณด์œ  ํ˜„ํ™ฉ\n", "new = make_fleet(500, seed=90, env_w=OPS_W, shift=1.1) # ์‹ ๊ทœ ์ž…๋ ฅ๋ถ„(์šด์˜ ํ™˜๊ฒฝ์ด ๋‹ฌ๋ผ์ง)\n", "raw = pd.concat([base, new], ignore_index=True)\n", "chk = {\"์žฅ๋น„ ID ์œ ์ผ\": not raw[\"eq_id\"].duplicated().any(), \"๊ฒฐ์ธก 0\": int(raw[[\"rate\", \"hours\", \"h_since\", \"insp\", \"unpl\"]].isna().sum().sum()) == 0, \"ํ™˜๊ฒฝ ๊ฐ’ 4์ข…\": bool(raw[\"env\"].isin(ENVS).all()),\n", " \"๋ถ€๋Œ€ ์ฝ”๋“œ ๋“ฑ๋ก\": bool(raw[\"unit\"].isin(UNITS).all()), \"์šด์šฉ์‹œ๊ฐ„ 0 ์ด์ƒ\": bool((raw[\"hours\"] >= 0).all()), \"์ฐฝ์ •๋น„ ํ›„ <= ๋ˆ„์ \": bool((raw[\"h_since\"] <= raw[\"hours\"] + 1e-6).all()),\n", " \"๊ธฐ๋™ ์†๋„ 5~40km/h\": bool((raw[\"km\"] / raw[\"hours\"]).between(5, 40).all()), \"์ ๊ฒ€ ๋งˆ๋ชจ 0~1.3\": bool(raw[\"insp\"].between(0, 1.3).all())}\n", "print(f\"[1] ์‹ ๊ทœ {len(new)}๋Œ€ ํฌํ•จ {len(raw)}๋Œ€ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ {sum(chk.values())}/{len(chk)} ํ†ต๊ณผ\")\n", "\n", "# ---------------- 2) ๋ถ„ํฌ ๋ณ€ํ™” ์ ๊ฒ€๊ณผ ์ž๋™ ์žฌํ•™์Šต ----------------\n", "def psi(a, b, bins=8):\n", " q = np.unique(np.quantile(a, np.linspace(0, 1, bins + 1)))\n", " q[0], q[-1] = -np.inf, np.inf\n", " pa, pb = np.histogram(a, q)[0] / len(a) + 1e-4, np.histogram(b, q)[0] / len(b) + 1e-4\n", " return float(np.sum((pa - pb) * np.log(pa / pb)))\n", "\n", "\n", "fb, fn = add_features(base), add_features(new)\n", "drift = {c: psi(fb[c], fn[c]) for c in [\"rate\", \"unpl_per_kh\", \"insp_eta_h\", \"aging_idx\", \"km_per_h\"]}\n", "worst = max(drift, key=drift.get)\n", "print(\"[2] ๋ถ„ํฌ ๋ณ€ํ™” PSI(๊ธฐ์กด vs ์‹ ๊ทœ):\", \", \".join(f\"{k} {v:.2f}\" for k, v in drift.items()), f\"-> ๊ฐ€์žฅ ํฐ ๋ณ€ํ™” {worst}(0.2 ์ด์ƒ์ด๋ฉด ์žฌํ•™์Šต ๊ฒ€ํ† )\")\n", "new_tr, new_ho = new.iloc[:300].reset_index(drop=True), new.iloc[300:].reset_index(drop=True)\n", "champ = fit_engine(base)\n", "chall = fit_engine(pd.concat([base.sample(300, random_state=0), new_tr], ignore_index=True), seed=1) # ์ตœ๊ทผ ๋ถ„ํฌ๋ฅผ ๋ฐ˜์˜ํ•ด ๊ธฐ์กด ์ผ๋ถ€ + ์‹ ๊ทœ ํ•™์Šต๋ถ„์œผ๋กœ ์žฌํ•™์Šต\n", "ho = add_features(new_ho)\n", "ho = ho[ho.event == 0].reset_index(drop=True)\n", "pc, _, _ = predict_rul(champ, ho)\n", "ph, _, _ = predict_rul(chall, ho)\n", "y = ho[\"rul_d\"].values\n", "big = y >= 90\n", "ac, bc = mae(y, pc), mae(y, ph)\n", "promote = (ac - bc) / ac >= 0.03\n", "print(f\" ๊ฒ€์ฆ {len(ho)}๋Œ€(์‹ ๊ทœ ๋ถ„ํฌ) MAE ์ฑ”ํ”ผ์–ธ {ac:.0f}์ผ vs ์ฑŒ๋ฆฐ์ € {bc:.0f}์ผ, MAPE {mape(y[big], pc[big]):.1f}% vs {mape(y[big], ph[big]):.1f}% -> {'์ฑŒ๋ฆฐ์ € ์Šน๊ฒฉ' if promote else '๊ธฐ์กด ๋ชจ๋ธ ์œ ์ง€'}(MAE 3% ์ด์ƒ ๊ฐœ์„ ์ผ ๋•Œ๋งŒ ์Šน๊ฒฉ)\")\n", "model = chall if promote else champ\n", "\n", "# ---------------- 3) RUL ์˜ˆ์ธก๊ณผ 12๊ฐœ์›” ์ •๋น„ ์ผ์ • ----------------\n", "fl = add_features(new)\n", "al = fl[fl.event == 0].reset_index(drop=True)\n", "al[\"rul_pred\"], _, _ = predict_rul(model, al)\n", "al[\"month\"] = plan_milp(al[\"rul_pred\"].values, al[\"rate\"].values)\n", "mf = plan_fixed(al[\"lin_rul_d\"].values)\n", "r_fix, r_new = evaluate_plan(mf, al[\"rul_d\"].values, al[\"rate\"].values), evaluate_plan(al[\"month\"].values, al[\"rul_d\"].values, al[\"rate\"].values)\n", "kf = kit_fefo(al[\"month\"].values)\n", "print(f\"\\n[3] ์‹ ๊ทœ ๋ณด์œ  {len(al)}๋Œ€ 12๊ฐœ์›” ์ •๋น„ ์ผ์ • [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]: ์ •๋น„ ๋ฐฐ์ • {int((al.month >= 0).sum())}๋Œ€, ์›”๋ณ„ \" + \" \".join(f\"{m + 1}์›” {c}\" for m, c in enumerate(np.bincount(al.loc[al.month >= 0, 'month'], minlength=HORIZON))))\n", "print(f\" ๊ทœ์ • ์ฃผ๊ธฐ ๊ณ ์ • ์ผ์ • ๋Œ€๋น„ ์‹ค์ œ ๋น„์šฉ {r_fix['total']:,.0f} -> {r_new['total']:,.0f}๋งŒ์›({1 - r_new['total'] / r_fix['total']:.1%} ์ ˆ๊ฐ), ๋น„๊ณ„ํš ๊ณ ์žฅ {r_fix['fail']} -> {r_new['fail']}๋Œ€, ํ‚คํŠธ ์œ ํšจ๊ธฐ๊ฐ„ ๋ถ€์กฑ {kf['short']}๊ฑด\")\n", "al[\"์ฒ˜๋ฆฌ\"] = np.select([al.rul_pred < 90, (al.age_y >= 14) & (al.rul_pred < 365)], [\"๊ธด๊ธ‰ ์ ๊ฒ€(ํ˜„์žฅ ํ™•์ธ ํ›„ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ)\", \"๋„ํƒœ ๊ฑด์˜ ํ›„๋ณด(2๋‹จ๊ณ„ ๊ฒฐ์žฌ)\"], np.where(al.month >= 0, \"์ •๋น„ ์ผ์ • ์Šน์ธ๋Œ€๊ธฐ\", \"์ •๊ธฐ ์ ๊ฒ€ ์œ ์ง€\"))\n", "top = al.sort_values(\"rul_pred\").head(6)\n", "print(pd.DataFrame({\"์žฅ๋น„\": top.eq_id, \"๋ถ€๋Œ€\": top.unit, \"ํ™˜๊ฒฝ\": top.env.map(ENV_KO), \"์˜ˆ์ธก RUL(์ผ)\": top.rul_pred, \"๊ถŒ์žฅ ์›”\": top.month.map(lambda m: f\"{m + 1}์›”\" if m >= 0 else \"-\"), \"์ฒ˜๋ฆฌ\": top[\"์ฒ˜๋ฆฌ\"]}).round(0).to_string(index=False))\n", "vc = al[\"์ฒ˜๋ฆฌ\"].value_counts()\n", "print(\" ์ฒ˜๋ฆฌ ๊ตฌ๋ถ„:\", vc.to_dict(), \"/ ์ž๋™ ํ™•์ • 0๊ฑด(๋ชจ๋“  ์ผ์ •ยท๋„ํƒœ๋Š” ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ ํ•„์š”)\")\n", "\n", "# ---------------- 4) ์šด์˜ ๋ฌธ์„œ ์™„๋น„์œจยท์ธํ„ฐํŽ˜์ด์Šค ๋ช…์„ธ ----------------\n", "docs = {\"๋ฐ์ดํ„ฐ ์‚ฌ์ „ยท์Šคํ‚ค๋งˆ(M1)\": 30, \"์ •์ œ ๊ทœ์น™ 11์ข…(M2)\": 11, \"BaselineยทEDA(M3)\": 8, \"์ƒ์กด ๋ถ„์„ ๋ชจ๋ธ(M4)\": 12, \"RUL ์—”์ง„ยท์ •๋น„ ์ผ์ •(M5)\": 4, \"๋Œ€์‹œ๋ณด๋“œยท์„ค๋ช…(M6)\": 5, \"์Šน์ธ ์ ˆ์ฐจยท๊ถŒํ•œ(M7)\": 4,\n", " \"์ผ๊ด„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ยท๋ถ€ํ•˜(M8)\": 5, \"AARยทROI(M9)\": 4, \"์šด์˜ยท์žฌํ•™์Šต ์ ˆ์ฐจ(M10)\": 6, \"์žฅ์•  ๋Œ€์‘\": 5, \"์ธํ„ฐํŽ˜์ด์Šค ๊ฐ€์ด๋“œ\": 4}\n", "done = sum(v > 0 for v in docs.values())\n", "iface = {\"input\": {\"fleet\": [\"eq_id:str\", \"unit:str\", \"env:str\", \"rate:float\", \"hours:float\", \"h_since:float\", \"unpl:int\", \"insp:float\"], \"lots\": [\"lot_id\", \"qty\", \"expiry_d\"], \"format\": \"json-lines\"},\n", " \"output\": {\"plan\": [\"eq_id\", \"rul_days\", \"month\", \"status\"], \"approval\": \"PENDING_ONLY\"}, \"auth\": \"role-based(๊ฐ€์ƒ)\", \"note\": \"์ƒ์œ„ ๊ตฐ์ˆ˜์ •๋ณด์ฒด๊ณ„๋Š” ๊ฐ€์ƒ ์ธํ„ฐํŽ˜์ด์Šค๋กœ๋งŒ ์ •์˜\"}\n", "print(f\"\\n[4] ์šด์˜ ๋ฌธ์„œ {len(docs)}๊ฐœ ํ•ญ๋ชฉ ์ค‘ ์ž‘์„ฑ ์™„๋ฃŒ {done}๊ฐœ ({done / len(docs):.0%}) / ์ธํ„ฐํŽ˜์ด์Šค ๋ช…์„ธ(JSON):\")\n", "print(\" \", json.dumps(iface, ensure_ascii=False))\n", "\n", "# ---------------- 5) ์ž์ฒด ์ ๊ฒ€ยท๋ธŒ๋ฆฌํ•‘ ----------------\n", "pm, _, _ = predict_rul(model, ho)\n", "fin_mae, fin_mape, base_mae = mae(y, pm), mape(y[big], pm[big]), mae(y, ho[\"lin_rul_d\"].values)\n", "checks = [(\"ํ™˜๊ฒฝ ์ ๊ฒ€ ํ†ต๊ณผ\", env_ok == len(libs)), (\"๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ์ „ ํ•ญ๋ชฉ ํ†ต๊ณผ\", all(chk.values())), (\"์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์ •์ƒ ์‹คํ–‰(์ฑ”ํ”ผ์–ธ/์ฑŒ๋ฆฐ์ € ํŒ์ • ์ถœ๋ ฅ)\", ac > 0 and bc > 0),\n", " (\"RUL MAPE 25% ์ดํ•˜(์ฐธ RUL 90์ผ ์ด์ƒ)\", fin_mape <= 25), (\"์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ๋Œ€๋น„ MAE 20% ์ด์ƒ ๊ฐœ์„ \", fin_mae <= 0.8 * base_mae), (\"๊ณ ์ • ์ฃผ๊ธฐ ๋Œ€๋น„ ๊ณ„ํš ๋น„์šฉ 15% ์ด์ƒ ์ ˆ๊ฐ\", r_new[\"total\"] <= 0.85 * r_fix[\"total\"]),\n", " (\"์ž๋™ ํ™•์ • 0๊ฑดยทํ‚คํŠธ ์œ ํšจ๊ธฐ๊ฐ„ ๋ถ€์กฑ 0๊ฑด\", bool(al[\"์ฒ˜๋ฆฌ\"].str.contains(\"์Šน์ธ|์ ๊ฒ€|๊ฑด์˜\").all()) and kf[\"short\"] == 0), (\"์šด์˜ ๋ฌธ์„œ 100% ์™„๋น„\", done == len(docs))]\n", "print(\"\\n[5] ์ž์ฒด ์ ๊ฒ€\")\n", "for n, ok in checks:\n", " print(f\" [{'PASS' if ok else 'FAIL'}] {n}\")\n", "print(\"\\n[6] ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ\")\n", "print(f\" - ์‹ ๊ทœ {len(new)}๋Œ€๋กœ ๋ถ„ํฌ ๋ณ€ํ™”๋ฅผ ์ ๊ฒ€({worst} PSI {drift[worst]:.2f})ํ•˜๊ณ  ์žฌํ•™์Šต ํŒ๋‹จ์„ ์ž๋™ ์ˆ˜ํ–‰ํ–ˆ๋‹ค({'์ฑŒ๋ฆฐ์ € ์Šน๊ฒฉ' if promote else '๊ธฐ์กด ๋ชจ๋ธ ์œ ์ง€'}).\")\n", "print(f\" - ์‹ ๊ทœ ๋ถ„ํฌ RUL MAE {base_mae:.0f}์ผ(์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ) -> {fin_mae:.0f}์ผ, MAPE {fin_mape:.1f}%. 12๊ฐœ์›” ๊ณ„ํš ๋น„์šฉ {1 - r_new['total'] / r_fix['total']:.1%} ์ ˆ๊ฐ, ๋น„๊ณ„ํš ๊ณ ์žฅ {r_fix['fail']} -> {r_new['fail']}๋Œ€(๊ฐ€์ • ๋‹จ๊ฐ€ ๊ธฐ์ค€).\")\n", "print(\" - ์ •๋น„ ์ผ์ •ยท๊ธด๊ธ‰ ์ ๊ฒ€ยท๋„ํƒœ ํ›„๋ณด๋Š” ๋ชจ๋‘ ์Šน์ธ๋Œ€๊ธฐ๋กœ ๋ถ„๋ฆฌํ–ˆ๋‹ค. ํ™•์ •์€ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€(๋„ํƒœ๋Š” ์žฌ์ • ๋‹ด๋‹น ํฌํ•จ) ๊ฒฐ์žฌ์— ๋”ฐ๋ฅธ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(12, 4))\n", "ax[0].bar(list(drift), list(drift.values())); ax[0].axhline(0.2, c=\"r\", ls=\"--\"); ax[0].set_title(\"Feature drift (PSI), old vs new fleet\"); ax[0].tick_params(axis=\"x\", rotation=20)\n", "ax[1].scatter(y, pc, s=6, alpha=.5, label=\"champion\"); ax[1].scatter(y, ph, s=6, alpha=.5, label=\"challenger\"); ax[1].plot([0, 4000], [0, 4000], \"k--\"); ax[1].legend(); ax[1].set_title(\"Hold-out RUL: true vs predicted (days)\")\n", "plt.suptitle(\"SUP02 integrated pipeline (synthetic)\"); plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 10 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[0] ํ™˜๊ฒฝ ์ ๊ฒ€: Python 3.11, ํ•„์ˆ˜ ๋ชจ๋“ˆ 5/5\n", "[1] ์‹ ๊ทœ 500๋Œ€ ํฌํ•จ 1400๋Œ€ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ 8/8 ํ†ต๊ณผ\n", "[2] ๋ถ„ํฌ ๋ณ€ํ™” PSI(๊ธฐ์กด vs ์‹ ๊ทœ): rate 0.09, unpl_per_kh 0.03, insp_eta_h 0.03, aging_idx 0.02, km_per_h 0.03 -> ๊ฐ€์žฅ ํฐ ๋ณ€ํ™” rate(0.2 ์ด์ƒ์ด๋ฉด ์žฌํ•™์Šต ๊ฒ€ํ† )\n", " ๊ฒ€์ฆ 132๋Œ€(์‹ ๊ทœ ๋ถ„ํฌ) MAE ์ฑ”ํ”ผ์–ธ 216์ผ vs ์ฑŒ๋ฆฐ์ € 209์ผ, MAPE 24.9% vs 22.8% -> ์ฑŒ๋ฆฐ์ € ์Šน๊ฒฉ(MAE 3% ์ด์ƒ ๊ฐœ์„ ์ผ ๋•Œ๋งŒ ์Šน๊ฒฉ)\n", "\n", "[3] ์‹ ๊ทœ ๋ณด์œ  322๋Œ€ 12๊ฐœ์›” ์ •๋น„ ์ผ์ • [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]: ์ •๋น„ ๋ฐฐ์ • 56๋Œ€, ์›”๋ณ„ 1์›” 20 2์›” 20 3์›” 8 4์›” 7 5์›” 1 6์›” 0 7์›” 0 8์›” 0 9์›” 0 10์›” 0 11์›” 0 12์›” 0\n", " ๊ทœ์ • ์ฃผ๊ธฐ ๊ณ ์ • ์ผ์ • ๋Œ€๋น„ ์‹ค์ œ ๋น„์šฉ 117,515 -> 76,804๋งŒ์›(34.6% ์ ˆ๊ฐ), ๋น„๊ณ„ํš ๊ณ ์žฅ 36 -> 17๋Œ€, ํ‚คํŠธ ์œ ํšจ๊ธฐ๊ฐ„ ๋ถ€์กฑ 0๊ฑด\n", " ์žฅ๋น„ ๋ถ€๋Œ€ ํ™˜๊ฒฝ ์˜ˆ์ธก RUL(์ผ) ๊ถŒ์žฅ ์›” ์ฒ˜๋ฆฌ\n", "EQ900423 U6 ์ผ๋ฐ˜ 74.0 1์›” ๊ธด๊ธ‰ ์ ๊ฒ€(ํ˜„์žฅ ํ™•์ธ ํ›„ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ)\n", "EQ900136 U5 ํ—˜์ง€ 75.0 1์›” ๊ธด๊ธ‰ ์ ๊ฒ€(ํ˜„์žฅ ํ™•์ธ ํ›„ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ)\n", "EQ900455 U6 ์—ผ๋ถ„ 90.0 1์›” ์ •๋น„ ์ผ์ • ์Šน์ธ๋Œ€๊ธฐ\n", "EQ900280 U3 ์—ผ๋ถ„ 111.0 1์›” ์ •๋น„ ์ผ์ • ์Šน์ธ๋Œ€๊ธฐ\n", "EQ900040 U1 ์—ผ๋ถ„ 124.0 1์›” ์ •๋น„ ์ผ์ • ์Šน์ธ๋Œ€๊ธฐ\n", "EQ900035 U1 ์ผ๋ฐ˜ 134.0 1์›” ์ •๋น„ ์ผ์ • ์Šน์ธ๋Œ€๊ธฐ\n", " ์ฒ˜๋ฆฌ ๊ตฌ๋ถ„: {'์ •๊ธฐ ์ ๊ฒ€ ์œ ์ง€': 266, '์ •๋น„ ์ผ์ • ์Šน์ธ๋Œ€๊ธฐ': 37, '๋„ํƒœ ๊ฑด์˜ ํ›„๋ณด(2๋‹จ๊ณ„ ๊ฒฐ์žฌ)': 17, '๊ธด๊ธ‰ ์ ๊ฒ€(ํ˜„์žฅ ํ™•์ธ ํ›„ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ)': 2} / ์ž๋™ ํ™•์ • 0๊ฑด(๋ชจ๋“  ์ผ์ •ยท๋„ํƒœ๋Š” ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€ ์Šน์ธ ํ•„์š”)\n", "\n", "[4] ์šด์˜ ๋ฌธ์„œ 12๊ฐœ ํ•ญ๋ชฉ ์ค‘ ์ž‘์„ฑ ์™„๋ฃŒ 12๊ฐœ (100%) / ์ธํ„ฐํŽ˜์ด์Šค ๋ช…์„ธ(JSON):\n", " {\"input\": {\"fleet\": [\"eq_id:str\", \"unit:str\", \"env:str\", \"rate:float\", \"hours:float\", \"h_since:float\", \"unpl:int\", \"insp:float\"], \"lots\": [\"lot_id\", \"qty\", \"expiry_d\"], \"format\": \"json-lines\"}, \"output\": {\"plan\": [\"eq_id\", \"rul_days\", \"month\", \"status\"], \"approval\": \"PENDING_ONLY\"}, \"auth\": \"role-based(๊ฐ€์ƒ)\", \"note\": \"์ƒ์œ„ ๊ตฐ์ˆ˜์ •๋ณด์ฒด๊ณ„๋Š” ๊ฐ€์ƒ ์ธํ„ฐํŽ˜์ด์Šค๋กœ๋งŒ ์ •์˜\"}\n", "\n", "[5] ์ž์ฒด ์ ๊ฒ€\n", " [PASS] ํ™˜๊ฒฝ ์ ๊ฒ€ ํ†ต๊ณผ\n", " [PASS] ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ์ „ ํ•ญ๋ชฉ ํ†ต๊ณผ\n", " [PASS] ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์ •์ƒ ์‹คํ–‰(์ฑ”ํ”ผ์–ธ/์ฑŒ๋ฆฐ์ € ํŒ์ • ์ถœ๋ ฅ)\n", " [PASS] RUL MAPE 25% ์ดํ•˜(์ฐธ RUL 90์ผ ์ด์ƒ)\n", " [PASS] ์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ ๋Œ€๋น„ MAE 20% ์ด์ƒ ๊ฐœ์„ \n", " [PASS] ๊ณ ์ • ์ฃผ๊ธฐ ๋Œ€๋น„ ๊ณ„ํš ๋น„์šฉ 15% ์ด์ƒ ์ ˆ๊ฐ\n", " [PASS] ์ž๋™ ํ™•์ • 0๊ฑดยทํ‚คํŠธ ์œ ํšจ๊ธฐ๊ฐ„ ๋ถ€์กฑ 0๊ฑด\n", " [PASS] ์šด์˜ ๋ฌธ์„œ 100% ์™„๋น„\n", "\n", "[6] ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ\n", " - ์‹ ๊ทœ 500๋Œ€๋กœ ๋ถ„ํฌ ๋ณ€ํ™”๋ฅผ ์ ๊ฒ€(rate PSI 0.09)ํ•˜๊ณ  ์žฌํ•™์Šต ํŒ๋‹จ์„ ์ž๋™ ์ˆ˜ํ–‰ํ–ˆ๋‹ค(์ฑŒ๋ฆฐ์ € ์Šน๊ฒฉ).\n", " - ์‹ ๊ทœ ๋ถ„ํฌ RUL MAE 377์ผ(์„ ํ˜• ๊ฐ๊ฐ€์ƒ๊ฐ) -> 209์ผ, MAPE 22.8%. 12๊ฐœ์›” ๊ณ„ํš ๋น„์šฉ 34.6% ์ ˆ๊ฐ, ๋น„๊ณ„ํš ๊ณ ์žฅ 36 -> 17๋Œ€(๊ฐ€์ • ๋‹จ๊ฐ€ ๊ธฐ์ค€).\n", " - ์ •๋น„ ์ผ์ •ยท๊ธด๊ธ‰ ์ ๊ฒ€ยท๋„ํƒœ ํ›„๋ณด๋Š” ๋ชจ๋‘ ์Šน์ธ๋Œ€๊ธฐ๋กœ ๋ถ„๋ฆฌํ–ˆ๋‹ค. ํ™•์ •์€ ๊ตฐ์ˆ˜์ง€ํœ˜๊ด€(๋„ํƒœ๋Š” ์žฌ์ • ๋‹ด๋‹น ํฌํ•จ) ๊ฒฐ์žฌ์— ๋”ฐ๋ฅธ๋‹ค.\n", "```\n", "
\n" ] } ], "metadata": { "colab": { "provenance": [] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }