{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ๐ŸŽฏ [QLT03] ๊ตฐ์ˆ˜ํ’ˆ ์ œ์›์ •๋ณด ์ด์ƒ์น˜ ์ž๋™๊ฒ€์ฆ Tool\n", "### Google Colab ์‹ค์Šต ํ™˜๊ฒฝ (ํ’ˆ์งˆยท๊ทœ๊ฒฉยท๊ฒ€์‚ฌ / Level 3)\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/QLT03/QLT03_solution.ipynb)\n", "\n", "---\n", "\n", "## ๐Ÿ“Œ 1. ํ”„๋กœ์ ํŠธ ๊ฐœ์š” ๋ฐ ๋ฐฐ๊ฒฝ\n", "\n", "| ํ•ญ๋ชฉ | ๋‚ด์šฉ |\n", "| :--- | :--- |\n", "| **๊ณผ์ • ๋ถ„๋ฅ˜** | ํ’ˆ์งˆยท๊ทœ๊ฒฉยท๊ฒ€์‚ฌ (Level 3) |\n", "| **ํ•™์Šต ์†Œ์š” ์‹œ๊ฐ„** | 10๊ฐœ ๋ฏธ์…˜ (5์ผ / ์ด 28.5์‹œ๊ฐ„) |\n", "| **ํ™˜๊ฒฝ ์‚ฌ์–‘** | Python 3.10+, numpy, pandas, matplotlib, scipy, scikit-learn |\n", "\n", "### ๐Ÿ“– ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ\n", "๊ตฐ์ˆ˜์žฅ๋น„์˜ ์™ธ๊ด€ ๊ฒ€์‚ฌ๋Š” ๊ฒ€์‚ฌ๊ด€์˜ ๋ˆˆ๊ณผ ๊ฒฝํ—˜์— ํฌ๊ฒŒ ์˜์กดํ•ด ํŒ์ • ํŽธ์ฐจ๊ฐ€ ์ƒ๊ธฐ๊ณ  ๊ฒ€์‚ฌ ๋ณ‘๋ชฉ์ด ๋˜๊ธฐ ์‰ฝ๋‹ค. ๋ณธ ํ”„๋กœ์ ํŠธ๋Š” ๊ทœ๊ฒฉยท์ œ์› ์ •ํ•ฉ์„ฑ๊ณผ ๊ฒ€์‚ฌ ๋ณ‘๋ชฉ, ์ด์ƒ ์‹ ํ˜ธ, ์„ค๋ช… ๊ทผ๊ฑฐ, ํ’ˆ์งˆ ๊ฐœ์„  ํšจ๊ณผ๋ฅผ ํ•จ๊ป˜ ์ตํžˆ๋„๋ก, ๊ต์œก์šฉ ๊ฐ€์ƒ ์™ธ๊ด€ ์‚ฌ์ง„(๊ท ์—ดยท๋ถ€์‹ยทํŒŒ์†ยท๋ˆ„์œ )์„ ์ฝ”๋“œ๋กœ ๋งŒ๋“ค์–ด ๊ฒฐํ•จ ํŒ์ •๊ณผ ์†์ƒ๋„ยท์ •๋น„ ๋“ฑ๊ธ‰ ์‚ฐ์ •์„ ์ž๋™ํ™”ํ•œ๋‹ค. ํ•™์Šต์ž๋Š” ์‚ฌ์ง„ ๋ฐ์ดํ„ฐ์…‹๊ณผ ์ œ์› ๊ธฐ๋ก ์ •์˜, ์กฐ๋ช… ๋ณด์ •ยทํ•ด์ƒ๋„ ํ‘œ์ค€ํ™”ยท๋ฐ•์Šค ์ •์ œ, 1์ฐจ ํŒ๋ณ„ Baseline, ๋ถ„ํ•  ๊ธฐ๋ฐ˜ ๋ฉด์ ยท๊นŠ์ดยท์†์ƒ๋„ ๋ชจ๋ธ, ์ •๋น„ ๋“ฑ๊ธ‰ ๋ถ„๋ฅ˜์™€ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜, ์‚ฌ์ง„ ์—…๋กœ๋“œ ํŒ์ • ํ™”๋ฉด๊ณผ ๊ทผ๊ฑฐ ์„ค๋ช…, ๊ฒ€์‚ฌ๊ด€ ์ˆ˜๋™ ๋ณด์ •๊ณผ ๊ฒฐ์žฌ(HITL), ์ง„ํ™ ํ˜ผ์ž… ์ŠคํŠธ๋ ˆ์Šค ์‹œํ—˜, AAR ์žฌ๋ณด์ •๊ณผ ROI, ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ํ†ตํ•ฉ์„ ์ฐจ๋ก€๋กœ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์ตœ์ข… ๋ชฉํ‘œ๋Š” ์‚ฌ์ง„์œผ๋กœ ๊ฒฐํ•จ๊ณผ ์†์ƒ๋„๋ฅผ ํŒ์ •ํ•˜๊ณ  ์‚ฌ์ง„ ํŒ์ •๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋ก์˜ ๋ถˆ์ผ์น˜๋ฅผ ์ด์ƒ ์‹ ํ˜ธ๋กœ ํ™˜๋ฅ˜ํ•˜๋˜, ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰์€ ๊ฒ€์‚ฌ๊ด€์ด ํ™•์ •ํ•˜๋Š” AI ์™ธ๊ด€ ๊ฒ€์‚ฌ ๋„๊ตฌ๋ฅผ ์™„์„ฑํ•˜๋Š” ๊ฒƒ์ด๋‹ค.\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/qlt03_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๊ฐœ ์ด์ƒ), ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต์„ ์ •์˜ํ•˜๊ณ  ๊ฐ€์ƒ ์ƒ˜ํ”Œ๋กœ ์ œ์› ์ •ํ•ฉ์„ฑ์„ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import json\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "\n", "# 1) ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต: ์ดฌ์˜์—์„œ ์ •๋น„ ๋“ฑ๊ธ‰ ์Šน์ธยท์ œ์› ํ™˜๋ฅ˜๊นŒ์ง€\n", "PROCESS = [(\"์ดฌ์˜\", \"์‚ฌ์ง„ ID, ์ดฌ์˜์ผ, ์ดฌ์˜ ์กฐ๊ฑด(์กฐ๋ช…ยท์˜ค์—ผ)\"), (\"์ œ์› ๋Œ€์กฐ\", \"์žฅ๋น„ ์‹๋ณ„์ฝ”๋“œ, ์ œ์กฐ์—ฐ๋„, ๊ฐ€๋™์‹œ๊ฐ„, ๊ธฐ๋ก์ƒ ์ •๋น„ ๋“ฑ๊ธ‰\"),\n", " (\"์ „์ฒ˜๋ฆฌ\", \"์กฐ๋ช… ๋ณด์ •, ํ•ด์ƒ๋„ ํ‘œ์ค€ํ™”, ๊ฒฐํ•จ ์˜์—ญ(๋ฐ”์šด๋”ฉ ๋ฐ•์Šค)\"), (\"๊ฒฐํ•จ ํŒ๋ณ„\", \"๊ฒฐํ•จ ์œ ํ˜•, ๊ฒฐํ•จ ๋ฉด์ , ์ถ”์ • ๊นŠ์ด\"),\n", " (\"์†์ƒ๋„ ์‚ฐ์ •\", \"์†์ƒ๋„ ์ ์ˆ˜(0~100), ํŒ์ • ์‹ ๋ขฐ๋„\"), (\"๊ฒ€์‚ฌ๊ด€ ๋ณด์ •\", \"์ˆ˜๋™ ๋ณด์ • ์—ฌ๋ถ€, ๋ณด์ • ์‚ฌ์œ \"),\n", " (\"์ •๋น„ ๋“ฑ๊ธ‰ ์Šน์ธ\", \"ํ˜„์žฅยท์•ผ์ „ยท์ฐฝ์ •๋น„ ๋“ฑ๊ธ‰, ์Šน์ธ์ž, ์Šน์ธ ์‹œ๊ฐ\"), (\"์ œ์› ํ™˜๋ฅ˜\", \"ํŒ์ • ๊ฒฐ๊ณผ์™€ ๊ธฐ๋ก์ƒ ๋“ฑ๊ธ‰์˜ ๋ถˆ์ผ์น˜, ์ œ์› ์ˆ˜์ • ์š”์ฒญ\"), (\"์‚ฌํ›„๊ฒ€ํ† (AAR)\", \"์˜คํŒ ์›์ธ, ํŒŒ๋ผ๋ฏธํ„ฐ ์žฌ๋ณด์ • ์ด๋ ฅ\")]\n", "\n", "# 2) ๋ผ๋ฒจ๋ง ๊ธฐ์ค€(๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ์˜ ์ผ๋ถ€)\n", "LABEL_RULES = {\"crack\": \"์„ ํ˜• ์–ด๋‘์šด ๊ท ์—ด, ๊ธธ์ด 10px ์ด์ƒ\", \"corrosion\": \"๋ถ‰์€ ๊ฐˆ์ƒ‰ ๋ฉด, ๋ฉด์  12px ์ด์ƒ\", \"damage\": \"ํ•จ๋ชฐยทํŒŒ๋‹จ ์œค๊ณฝ(๋ฐ์€ ํ…Œ๋‘๋ฆฌ ํฌํ•จ)\",\n", " \"leak\": \"์–ด๋‘ก๊ณ  ๋ฒˆ์ง€๋Š” ์•ก์ฒด ์ž๊ตญ(์•„๋ž˜ ๋ฐฉํ–ฅ ํ๋ฆ„)\", \"none\": \"๊ฒฐํ•จ ์—†์Œ(๋ฌดํ•ดํ•œ ์–ผ๋ฃฉยท๊ธํž˜์€ ๊ฒฐํ•จ์œผ๋กœ ๋ผ๋ฒจํ•˜์ง€ ์•Š์Œ)\"}\n", "\n", "# 3) ์Šคํ‚ค๋งˆ: ๊ทธ๋ฃน / ํ•„๋“œ / ์ž๋ฃŒํ˜• / ๊ทœ์น™ / ์„ค๋ช…\n", "S = {\n", " \"photo\": {\n", " \"img_id\": (str, {\"pattern\": r\"^IMG\\d{6}$\"}, \"์‚ฌ์ง„ ID\"),\n", " \"shot_day\": (int, {\"min\": 0, \"max\": 400}, \"์ดฌ์˜์ผ(์ ๊ฒ€ ์‹œ์ž‘ ํ›„ ๊ฒฝ๊ณผ์ผ)\"),\n", " \"width\": (int, {\"enum\": [64]}, \"ํ‘œ์ค€ ํ•ด์ƒ๋„ ๊ฐ€๋กœ(px)\"),\n", " \"height\": (int, {\"enum\": [64]}, \"ํ‘œ์ค€ ํ•ด์ƒ๋„ ์„ธ๋กœ(px)\"),\n", " \"channels\": (int, {\"enum\": [3]}, \"์ฑ„๋„ ์ˆ˜(RGB)\"),\n", " \"brightness\": (float, {\"min\": 0.05, \"max\": 1.0}, \"ํ‰๊ท  ๋ฐ๊ธฐ(0~1)\"),\n", " \"mud_ratio\": (float, {\"min\": 0, \"max\": 1}, \"์ง„ํ™ยท์˜ค์—ผ ๊ฐ€๋ฆผ ๋น„์œจ(์ถ”์ •)\"),\n", " },\n", " \"spec\": {\n", " \"equip_id\": (str, {\"pattern\": r\"^VX-\\d{4}-\\d{4}$\"}, \"์žฅ๋น„ ์‹๋ณ„์ฝ”๋“œ(๊ตญ๋ฐฉ ํ‘œ์ค€ ์ฝ”๋“œ ํ˜•์‹์„ ๋ชจ์‚ฌํ•œ ๊ฐ€์ƒ ์ฝ”๋“œ)\"),\n", " \"equip_type\": (str, {\"enum\": [\"vehicle\", \"generator\", \"comm\", \"trailer\"]}, \"์žฅ๋น„ ๊ตฌ๋ถ„\"),\n", " \"mfg_year\": (int, {\"min\": 1990, \"max\": 2026}, \"์ œ์กฐ์—ฐ๋„(๊ฐ€์ƒ)\"),\n", " \"op_hours\": (float, {\"min\": 0, \"max\": 30000}, \"๋ˆ„์  ๊ฐ€๋™์‹œ๊ฐ„(h)\"),\n", " \"env\": (str, {\"enum\": [\"coastal\", \"inland\"]}, \"์šด์šฉ ํ™˜๊ฒฝ\"),\n", " \"reg_grade\": (int, {\"enum\": [1, 2, 3]}, \"๊ด€๋ฆฌ ๊ธฐ๋ก์ƒ ์ง์ „ ์ •๋น„ ๋“ฑ๊ธ‰(1 ํ˜„์žฅ, 2 ์•ผ์ „, 3 ์ฐฝ์ •๋น„)\"),\n", " },\n", " \"label\": {\n", " \"defect_class\": (str, {\"enum\": [\"none\", \"crack\", \"corrosion\", \"damage\", \"leak\"]}, \"๊ฒฐํ•จ ์œ ํ˜•\"),\n", " \"bbox_x\": (int, {\"min\": 0, \"max\": 63}, \"๊ฒฐํ•จ ๋ฐ•์Šค ์ขŒ์ƒ๋‹จ x\"),\n", " \"bbox_y\": (int, {\"min\": 0, \"max\": 63}, \"๊ฒฐํ•จ ๋ฐ•์Šค ์ขŒ์ƒ๋‹จ y\"),\n", " \"bbox_w\": (int, {\"min\": 1, \"max\": 64}, \"๊ฒฐํ•จ ๋ฐ•์Šค ํญ\"),\n", " \"bbox_h\": (int, {\"min\": 1, \"max\": 64}, \"๊ฒฐํ•จ ๋ฐ•์Šค ๋†’์ด\"),\n", " \"emergency\": (bool, {}, \"๊ธด๊ธ‰ ์ ๊ฒ€ ์š”์ฒญ ์—ฌ๋ถ€(ํ™”์งˆ ๋ถˆ๋Ÿ‰์ด์–ด๋„ ๋ณด์กด)\"),\n", " },\n", " \"result\": {\n", " \"area_frac\": (float, {\"min\": 0, \"max\": 1}, \"๊ฒฐํ•จ ๋ฉด์  ๋น„์œจ - ์˜ˆ์ธก๋Œ€์ƒ 1\"),\n", " \"depth_mm\": (float, {\"min\": 0, \"max\": 30}, \"์ถ”์ • ๊ฒฐํ•จ ๊นŠ์ด - ์˜ˆ์ธก๋Œ€์ƒ 2\"),\n", " \"damage_idx\": (float, {\"min\": 0, \"max\": 100}, \"์†์ƒ๋„ ์ ์ˆ˜ - ์˜ˆ์ธก๋Œ€์ƒ 3\"),\n", " \"grade\": (int, {\"enum\": [1, 2, 3]}, \"์ •๋น„ ๋“ฑ๊ธ‰(ํ˜„์žฅ/์•ผ์ „/์ฐฝ์ •๋น„) - ์˜ˆ์ธก๋Œ€์ƒ 4\"),\n", " \"confidence\": (float, {\"min\": 0, \"max\": 1}, \"ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜\"),\n", " },\n", "}\n", "\n", "\n", "def check(name, v, typ, rule):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ์žฅ๋น„ ์™ธ๊ด€ ๊ฒฐํ•จ(๊ท ์—ด, ๋ถ€์‹, ํŒŒ์†, ๋ˆ„์œ ) ๊ณ ํ•ด์ƒ๋„ ์‚ฌ์ง„ ๋ฐ์ดํ„ฐ์…‹ ๋ฐ ๋ผ๋ฒจ๋ง ์ •์˜ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def validate(rec):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ์žฅ๋น„ ์™ธ๊ด€ ๊ฒฐํ•จ(๊ท ์—ด, ๋ถ€์‹, ํŒŒ์†, ๋ˆ„์œ ) ๊ณ ํ•ด์ƒ๋„ ์‚ฌ์ง„ ๋ฐ์ดํ„ฐ์…‹ ๋ฐ ๋ผ๋ฒจ๋ง ์ •์˜ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "rng = np.random.default_rng(1)\n", "recs = []\n", "for i in range(20):\n", " g = int(rng.integers(1, 4))\n", " recs.append({\n", " \"photo\": {\"img_id\": f\"IMG{i:06d}\", \"shot_day\": int(rng.integers(0, 90)), \"width\": 64, \"height\": 64, \"channels\": 3,\n", " \"brightness\": float(round(rng.uniform(0.2, 0.9), 2)), \"mud_ratio\": float(round(rng.uniform(0, 0.3), 2))},\n", " \"spec\": {\"equip_id\": f\"VX-{rng.integers(1000, 9999)}-{rng.integers(1000, 9999)}\", \"equip_type\": str(rng.choice([\"vehicle\", \"generator\", \"comm\", \"trailer\"])),\n", " \"mfg_year\": int(rng.integers(2000, 2025)), \"op_hours\": float(round(rng.uniform(100, 9000), 0)), \"env\": str(rng.choice([\"coastal\", \"inland\"])),\n", " \"reg_grade\": int(np.clip(g + rng.integers(-1, 2), 1, 3))},\n", " \"label\": {\"defect_class\": str(rng.choice([\"none\", \"crack\", \"corrosion\", \"damage\", \"leak\"])), \"bbox_x\": int(rng.integers(0, 30)), \"bbox_y\": int(rng.integers(0, 30)),\n", " \"bbox_w\": int(rng.integers(5, 30)), \"bbox_h\": int(rng.integers(5, 30)), \"emergency\": bool(rng.random() < 0.05)},\n", " \"result\": {\"area_frac\": float(round(rng.uniform(0, 0.2), 3)), \"depth_mm\": float(round(rng.uniform(0, 6), 1)), \"damage_idx\": float(round(rng.uniform(0, 100), 1)),\n", " \"grade\": g, \"confidence\": float(round(rng.uniform(0.5, 1), 2))}})\n", "ok = sum(not validate(r) for r in recs)\n", "print(f\"์ •์ƒ ์ƒ˜ํ”Œ {len(recs)}๊ฑด ์ค‘ ๊ฒ€์ฆ ํ†ต๊ณผ {ok}๊ฑด, ์˜ค๋ฅ˜ {len(recs) - ok}๊ฑด\")\n", "\n", "# 5) ์˜ค๋ฅ˜ ์ฃผ์ž… ์‹œํ—˜\n", "bad = json.loads(json.dumps(recs[0]))\n", "bad[\"spec\"][\"mfg_year\"] = 2085 # ์ œ์› ๊ฐ’ ๋ฒ”์œ„ ์ด์ƒ(๋ฏธ๋ž˜ ์ œ์กฐ์—ฐ๋„)\n", "bad[\"label\"][\"bbox_x\"] = 60; bad[\"label\"][\"bbox_w\"] = 20 # ๋ฐ•์Šค๊ฐ€ ์‚ฌ์ง„ ๋ฐ–์œผ๋กœ ๋‚˜๊ฐ\n", "bad[\"result\"][\"grade\"] = 5 # ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๋“ฑ๊ธ‰\n", "del bad[\"spec\"][\"op_hours\"] # ๊ฐ€๋™์‹œ๊ฐ„ ๋ˆ„๋ฝ\n", "bad[\"photo\"][\"brightness\"] = \"์–ด๋‘์›€\" # ์ž๋ฃŒํ˜• ์˜ค๋ฅ˜\n", "print(\"\\n[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ]\")\n", "for e in validate(bad):\n", " print(\" -\", e)\n", "mis = json.loads(json.dumps(recs[1])); mis[\"result\"][\"grade\"] = 3; mis[\"spec\"][\"reg_grade\"] = 1\n", "print(\"์ œ์› ๋ถˆ์ผ์น˜ ์‹œํ—˜:\", validate(mis))\n", "\n", "# 6) ํ”„๋กœ์„ธ์Šค ๋งต๊ณผ ๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ\n", "print(\"\\n[์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต]\")\n", "print(\" -> \".join(f\"{i}.{p}\" for i, (p, _) in enumerate(PROCESS, 1)))\n", "for p, d in PROCESS:\n", " print(f\" {p:10s}: {d}\")\n", "print(\"\\n[๋ผ๋ฒจ๋ง ๊ธฐ์ค€]\")\n", "for k, v in LABEL_RULES.items():\n", " print(f\" {k:10s}: {v}\")\n", "rows = [(g, k, t.__name__, str(r) if r else \"-\", d) for g, f in S.items() for k, (t, r, d) in f.items()]\n", "dd = pd.DataFrame(rows, columns=[\"๊ทธ๋ฃน\", \"ํ•„๋“œ\", \"์ž๋ฃŒํ˜•\", \"๊ทœ์น™\", \"์„ค๋ช…\"])\n", "print(f\"\\n[๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ] ํ”ผ์ฒ˜ {len(dd)}๊ฐœ (๋ชฉํ‘œ 20๊ฐœ ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(dd) >= 20 else '๋ฏธ๋‹ฌ'})\")\n", "print(dd[[\"๊ทธ๋ฃน\", \"ํ•„๋“œ\", \"์ž๋ฃŒํ˜•\", \"์„ค๋ช…\"]].to_string(index=False))\n", "print(\"\\n์˜ˆ์ธก ๋Œ€์ƒ: ๊ฒฐํ•จ ๋ฉด์ , ์ถ”์ • ๊นŠ์ด, ์†์ƒ๋„ ์ ์ˆ˜, ์ •๋น„ ๋“ฑ๊ธ‰. ์ œ์› ํ™œ์šฉ: ์‚ฌ์ง„ ํŒ์ •๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋ก ๋“ฑ๊ธ‰์ด ํฌ๊ฒŒ ๋‹ค๋ฅด๋ฉด ์ œ์› ์ด์ƒ ์‹ ํ˜ธ๋กœ ํ‘œ์‹œ\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 01 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "์ •์ƒ ์ƒ˜ํ”Œ 20๊ฑด ์ค‘ ๊ฒ€์ฆ ํ†ต๊ณผ 20๊ฑด, ์˜ค๋ฅ˜ 0๊ฑด\n", "\n", "[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ]\n", " - photo.brightness: ์ž๋ฃŒํ˜• ์˜ค๋ฅ˜(str)\n", " - spec.mfg_year: ์ตœ๋Œ€๊ฐ’ 2026 ์ดˆ๊ณผ\n", " - spec.op_hours: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\n", " - result.grade: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๊ฐ’ '5'\n", " - label.bbox: ๋ฐ•์Šค๊ฐ€ ์ด๋ฏธ์ง€ ๋ฐ–์œผ๋กœ ๋‚˜๊ฐ\n", "์ œ์› ๋ถˆ์ผ์น˜ ์‹œํ—˜: ['์ œ์› ๋Œ€์กฐ: ํŒ์ • ๋“ฑ๊ธ‰๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋ก ๋“ฑ๊ธ‰์ด 2๋‹จ๊ณ„ ์ด์ƒ ์ฐจ์ด(์ œ์› ์ด์ƒ ์‹ ํ˜ธ)']\n", "\n", "[์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต]\n", "1.์ดฌ์˜ -> 2.์ œ์› ๋Œ€์กฐ -> 3.์ „์ฒ˜๋ฆฌ -> 4.๊ฒฐํ•จ ํŒ๋ณ„ -> 5.์†์ƒ๋„ ์‚ฐ์ • -> 6.๊ฒ€์‚ฌ๊ด€ ๋ณด์ • -> 7.์ •๋น„ ๋“ฑ๊ธ‰ ์Šน์ธ -> 8.์ œ์› ํ™˜๋ฅ˜ -> 9.์‚ฌํ›„๊ฒ€ํ† (AAR)\n", " ์ดฌ์˜ : ์‚ฌ์ง„ ID, ์ดฌ์˜์ผ, ์ดฌ์˜ ์กฐ๊ฑด(์กฐ๋ช…ยท์˜ค์—ผ)\n", " ์ œ์› ๋Œ€์กฐ : ์žฅ๋น„ ์‹๋ณ„์ฝ”๋“œ, ์ œ์กฐ์—ฐ๋„, ๊ฐ€๋™์‹œ๊ฐ„, ๊ธฐ๋ก์ƒ ์ •๋น„ ๋“ฑ๊ธ‰\n", " ์ „์ฒ˜๋ฆฌ : ์กฐ๋ช… ๋ณด์ •, ํ•ด์ƒ๋„ ํ‘œ์ค€ํ™”, ๊ฒฐํ•จ ์˜์—ญ(๋ฐ”์šด๋”ฉ ๋ฐ•์Šค)\n", " ๊ฒฐํ•จ ํŒ๋ณ„ : ๊ฒฐํ•จ ์œ ํ˜•, ๊ฒฐํ•จ ๋ฉด์ , ์ถ”์ • ๊นŠ์ด\n", " ์†์ƒ๋„ ์‚ฐ์ • : ์†์ƒ๋„ ์ ์ˆ˜(0~100), ํŒ์ • ์‹ ๋ขฐ๋„\n", " ๊ฒ€์‚ฌ๊ด€ ๋ณด์ • : ์ˆ˜๋™ ๋ณด์ • ์—ฌ๋ถ€, ๋ณด์ • ์‚ฌ์œ \n", " ์ •๋น„ ๋“ฑ๊ธ‰ ์Šน์ธ : ํ˜„์žฅยท์•ผ์ „ยท์ฐฝ์ •๋น„ ๋“ฑ๊ธ‰, ์Šน์ธ์ž, ์Šน์ธ ์‹œ๊ฐ\n", " ์ œ์› ํ™˜๋ฅ˜ : ํŒ์ • ๊ฒฐ๊ณผ์™€ ๊ธฐ๋ก์ƒ ๋“ฑ๊ธ‰์˜ ๋ถˆ์ผ์น˜, ์ œ์› ์ˆ˜์ • ์š”์ฒญ\n", " ์‚ฌํ›„๊ฒ€ํ† (AAR) : ์˜คํŒ ์›์ธ, ํŒŒ๋ผ๋ฏธํ„ฐ ์žฌ๋ณด์ • ์ด๋ ฅ\n", "\n", "[๋ผ๋ฒจ๋ง ๊ธฐ์ค€]\n", " crack : ์„ ํ˜• ์–ด๋‘์šด ๊ท ์—ด, ๊ธธ์ด 10px ์ด์ƒ\n", " corrosion : ๋ถ‰์€ ๊ฐˆ์ƒ‰ ๋ฉด, ๋ฉด์  12px ์ด์ƒ\n", " damage : ํ•จ๋ชฐยทํŒŒ๋‹จ ์œค๊ณฝ(๋ฐ์€ ํ…Œ๋‘๋ฆฌ ํฌํ•จ)\n", " leak : ์–ด๋‘ก๊ณ  ๋ฒˆ์ง€๋Š” ์•ก์ฒด ์ž๊ตญ(์•„๋ž˜ ๋ฐฉํ–ฅ ํ๋ฆ„)\n", " none : ๊ฒฐํ•จ ์—†์Œ(๋ฌดํ•ดํ•œ ์–ผ๋ฃฉยท๊ธํž˜์€ ๊ฒฐํ•จ์œผ๋กœ ๋ผ๋ฒจํ•˜์ง€ ์•Š์Œ)\n", "\n", "[๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ] ํ”ผ์ฒ˜ 24๊ฐœ (๋ชฉํ‘œ 20๊ฐœ ์ด์ƒ: ๋‹ฌ์„ฑ)\n", " ๊ทธ๋ฃน ํ•„๋“œ ์ž๋ฃŒํ˜• ์„ค๋ช…\n", " photo img_id str ์‚ฌ์ง„ ID\n", " photo shot_day int ์ดฌ์˜์ผ(์ ๊ฒ€ ์‹œ์ž‘ ํ›„ ๊ฒฝ๊ณผ์ผ)\n", " photo width int ํ‘œ์ค€ ํ•ด์ƒ๋„ ๊ฐ€๋กœ(px)\n", " photo height int ํ‘œ์ค€ ํ•ด์ƒ๋„ ์„ธ๋กœ(px)\n", " photo channels int ์ฑ„๋„ ์ˆ˜(RGB)\n", " photo brightness float ํ‰๊ท  ๋ฐ๊ธฐ(0~1)\n", " photo mud_ratio float ์ง„ํ™ยท์˜ค์—ผ ๊ฐ€๋ฆผ ๋น„์œจ(์ถ”์ •)\n", " spec equip_id str ์žฅ๋น„ ์‹๋ณ„์ฝ”๋“œ(๊ตญ๋ฐฉ ํ‘œ์ค€ ์ฝ”๋“œ ํ˜•์‹์„ ๋ชจ์‚ฌํ•œ ๊ฐ€์ƒ ์ฝ”๋“œ)\n", " spec equip_type str ์žฅ๋น„ ๊ตฌ๋ถ„\n", "... (์ดํ•˜ ์ƒ๋žต)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 02. ์ด๋ฏธ์ง€ ์กฐ๋„ ๋ณด์ •, ํ•ด์ƒ๋„ ํ‘œ์ค€ํ™” ๋ฐ ๊ฒฐํ•จ ์˜์—ญ(Bounding Box) ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์กฐ๋„ ๋ณด์ •, ํ•ด์ƒ๋„ ํ‘œ์ค€ํ™” ๋ฐ ๊ฒฐํ•จ ์˜์—ญ ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์กฐ๋ช… ๋ณด์ •, ํ•ด์ƒ๋„ ํ‘œ์ค€ํ™”, ๊ฒฐํ•จ ์˜์—ญ(Bounding Box) ์ •์ œ๋ฅผ ํฌํ•จํ•œ ์‚ฌ์ง„ยท์ œ์› ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **ํ•ด์ƒ๋„, ๋ฐ๊ธฐ, ํ‘œ๊ธฐ๊ฐ€ ์ œ๊ฐ๊ฐ์ธ ๊ฐ€์ƒ ์›์ฒœ ์‚ฌ์ง„๊ณผ ์ œ์›ยท๋ฐ•์Šค ๋ผ๋ฒจ์— ์ •์ œ ๊ทœ์น™ 10์ข… ์ด์ƒ์„ ์ˆœ์„œ๋Œ€๋กœ ์ ์šฉ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/etl/qlt03_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 re\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.impute import KNNImputer\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฅ๋น„ ์™ธ๊ด€ ์‚ฌ์ง„ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์œ„์น˜ ์‚ฌ์ง„์ด ์•„๋‹ˆ๋ผ ๋‚œ์ˆ˜๋กœ ๊ทธ๋ฆฐ 64x64 ํ•ฉ์„ฑ ์ด๋ฏธ์ง€)\n", "# ๋„์žฅ๋œ ๊ธˆ์† ํ‘œ๋ฉด์— ๊ท ์—ด(crack)ยท๋ถ€์‹(corrosion)ยทํŒŒ์†(damage)ยท๋ˆ„์œ (leak)๋ฅผ ๊ทธ๋ฆฌ๊ณ , ์กฐ๋ช… ํŽธ์ฐจ์™€ ์ง„ํ™(mud) ์˜ค์—ผ์„ ์„ž๋Š”๋‹ค.\n", "# ์ด๋ฏธ์ง€์™€ ํ•จ๊ป˜ ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ)๊ณผ ์ •๋‹ต ๊ฒฐํ•จ ๋งˆ์Šคํฌยท์†์ƒ๋„ยท์ •๋น„ ๋“ฑ๊ธ‰์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "from scipy import ndimage as ndi\n", "\n", "SZ = 64\n", "CLASSES = [\"none\", \"crack\", \"corrosion\", \"damage\", \"leak\"]\n", "KO_CLS = {\"none\": \"๊ฒฐํ•จ ์—†์Œ\", \"crack\": \"๊ท ์—ด\", \"corrosion\": \"๋ถ€์‹\", \"damage\": \"ํŒŒ์†\", \"leak\": \"๋ˆ„์œ \"}\n", "CLS_W = {\"none\": 0.0, \"crack\": 1.4, \"corrosion\": 1.0, \"damage\": 1.6, \"leak\": 0.6} # ์†์ƒ๋„ ๊ณ„์‚ฐ์šฉ ๊ฒฐํ•จ ์œ ํ˜• ๊ฐ€์ค‘์น˜\n", "GRADE_NAME = {1: \"ํ˜„์žฅ์ •๋น„\", 2: \"์•ผ์ „์ •๋น„\", 3: \"์ฐฝ์ •๋น„\"}\n", "YY, XX = np.mgrid[0:SZ, 0:SZ]\n", "\n", "\n", "def grade_of(idx):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] ์ด๋ฏธ์ง€ ์กฐ๋„ ๋ณด์ •, ํ•ด์ƒ๋„ ํ‘œ์ค€ํ™” ๋ฐ ๊ฒฐํ•จ ์˜์—ญ(Bounding Box) ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def blob(rng, cy, cx, r, rough=0.35):\n", " \"\"\"๋ถˆ๊ทœ์น™ํ•œ ์›ํ˜• ์˜์—ญ ๋งˆ์Šคํฌ.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] ์ด๋ฏธ์ง€ ์กฐ๋„ ๋ณด์ •, ํ•ด์ƒ๋„ ํ‘œ์ค€ํ™” ๋ฐ ๊ฒฐํ•จ ์˜์—ญ(Bounding Box) ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def draw(rng, cls, sev):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜•ยท์‹ฌ๊ฐ๋„ sev(0~1)์— ๋งž๋Š” ์ •๋‹ต ๋งˆ์Šคํฌ์™€ ์ƒ‰ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m = np.zeros((SZ, SZ), bool)\n", " if cls == \"crack\":\n", " y, x, ang = rng.uniform(12, 52), rng.uniform(12, 52), rng.uniform(0, np.pi)\n", " for _ in range(int(20 + 40 * sev)):\n", " ang += rng.normal(0, 0.35); y += np.sin(ang) * 1.2; x += np.cos(ang) * 1.2\n", " if 0 <= int(y) < SZ and 0 <= int(x) < SZ:\n", " m[int(y), int(x)] = True\n", " m = ndi.binary_dilation(m, iterations=1 + int(sev > 0.6))\n", " elif cls == \"corrosion\":\n", " for _ in range(int(rng.integers(1, 4))):\n", " m |= blob(rng, rng.uniform(14, 50), rng.uniform(14, 50), 3 + 9 * sev * rng.uniform(0.6, 1.1))\n", " elif cls == \"damage\":\n", " m = blob(rng, rng.uniform(20, 44), rng.uniform(20, 44), 6 + 12 * sev, 0.3)\n", " elif cls == \"leak\":\n", " x0, y0 = rng.uniform(16, 48), rng.uniform(8, 24)\n", " w, ln = 2 + 4 * sev, 12 + 26 * sev\n", " m = (np.abs(XX - x0 - 0.15 * (YY - y0)) < w * (1 - 0.4 * (YY - y0) / ln)) & (YY > y0) & (YY < y0 + ln)\n", " return m\n", "\n", "\n", "def make_images(n=600, seed=0, mud=0.0, p_none=0.25, dark=False):\n", " \"\"\"๊ฐ€์ƒ ์™ธ๊ด€ ์‚ฌ์ง„ n์žฅ(+์ง„ํ™ ์˜ค์—ผ mud ๋น„์œจ)๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋กยท์ •๋‹ต. ๋ฐ˜ํ™˜: meta(DataFrame), imgs(n,64,64,3), masks(n,64,64).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " imgs, masks, rows = np.zeros((n, SZ, SZ, 3), np.float32), np.zeros((n, SZ, SZ), bool), []\n", " for i in range(n):\n", " cls = \"none\" if rng.random() < p_none else str(rng.choice(CLASSES[1:], p=[0.25, 0.3, 0.25, 0.2]))\n", " sev = 0.0 if cls == \"none\" else float(rng.beta(1.6, 2.0))\n", " base = np.array([0.42, 0.46, 0.40]) * (1 + 0.5 * ndi.gaussian_filter(rng.normal(size=(SZ, SZ)), 8)[..., None] * 3)\n", " img = base + rng.normal(0, 0.012, (SZ, SZ, 3))\n", " m = draw(rng, cls, sev)\n", " a = ndi.gaussian_filter(m.astype(float), 0.8)[..., None] # ๊ฒฝ๊ณ„๋ฅผ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์„ž๋Š”๋‹ค\n", " if cls == \"crack\":\n", " col = img * rng.uniform(0.2, 0.45)\n", " elif cls == \"corrosion\":\n", " col = np.array([0.55, 0.30, 0.12]) * rng.uniform(0.75, 1.15) * (0.85 + 0.3 * rng.random((SZ, SZ, 1)))\n", " elif cls == \"damage\":\n", " col = np.ones_like(img) * np.array([0.34, 0.34, 0.36]) * (1.0 - 0.45 * sev) * rng.uniform(0.75, 1.0)\n", " rim = m & ~ndi.binary_erosion(m, iterations=2)\n", " a = np.maximum(a, 0.0); img = np.where(rim[..., None], 0.7, img)\n", " elif cls == \"leak\":\n", " col = np.ones_like(img) * np.array([0.10, 0.09, 0.08]) * rng.uniform(0.8, 2.6)\n", " else:\n", " col = img\n", " img = img * (1 - a) + col * a\n", " if rng.random() < 0.35: # ๋ฌดํ•ดํ•œ ์–ผ๋ฃฉ(๊ฒฐํ•จ ์•„๋‹˜): ์˜…์€ ๊ฐˆ์ƒ‰ ๋ฒˆ์ง\n", " st = blob(rng, rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(6, 14), 0.5)\n", " al = ndi.gaussian_filter(st.astype(float), 2.5)[..., None] * rng.uniform(0.15, 0.4)\n", " img = img * (1 - al) + np.array([0.50, 0.40, 0.30]) * al\n", " if rng.random() < 0.3: # ๋ฌดํ•ดํ•œ ๊ธด ์Šคํฌ๋ž˜์น˜(๋ฐ์€ ์„ )\n", " y0, x0, ang = rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(0, np.pi)\n", " for t in np.linspace(0, 25, 40):\n", " yy, xx = int(y0 + t * np.sin(ang)), int(x0 + t * np.cos(ang))\n", " if 0 <= yy < SZ and 0 <= xx < SZ:\n", " img[yy, xx] = np.clip(img[yy, xx] * 1.5, 0, 1)\n", " if mud > 0: # ์ง„ํ™ ์˜ค์—ผ: ๊ฒฐํ•จ ์œ„๋ฅผ ๋ฎ์„ ์ˆ˜ ์žˆ๋‹ค(์ •๋‹ต ๋งˆ์Šคํฌ๋Š” ๊ทธ๋Œ€๋กœ)\n", " mm = np.zeros((SZ, SZ), bool)\n", " while mm.mean() < mud:\n", " mm |= blob(rng, rng.uniform(0, SZ), rng.uniform(0, SZ), rng.uniform(5, 11), 0.5)\n", " img[mm] = np.array([0.38, 0.30, 0.22]) * (0.85 + 0.3 * rng.random((mm.sum(), 1)))\n", " gain = rng.uniform(0.3, 0.55) if dark else rng.uniform(0.5, 1.15)\n", " ang = rng.uniform(0, 2 * np.pi)\n", " light = gain * (1 + 0.3 * (np.cos(ang) * (XX - 32) + np.sin(ang) * (YY - 32)) / 32)\n", " imgs[i] = np.clip(img * light[..., None] + rng.normal(0, 0.025, (SZ, SZ, 3)), 0, 1)\n", " masks[i] = m\n", " area = float(m.mean())\n", " depth = {\"none\": 0.0, \"crack\": 0.3 + 3.0 * sev, \"corrosion\": 0.1 + 1.5 * sev, \"damage\": 1.0 + 8.0 * sev, \"leak\": 0.0}[cls] * rng.lognormal(0, 0.1)\n", " idx = float(np.clip(100 * (1 - np.exp(-(CLS_W[cls] * area * 9 + 0.05 * depth))) + rng.normal(0, 1.5), 0, 100))\n", " rows.append({\"img_id\": f\"IMG{seed:02d}{i:04d}\", \"cls\": cls, \"sev\": sev, \"area_frac\": area, \"depth_mm\": depth, \"damage_idx\": idx,\n", " \"grade\": int(grade_of(idx)), \"gain\": gain, \"mud\": mud})\n", " meta = pd.DataFrame(rows)\n", " # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ): ์‚ฌ์ง„ ํŒ์ •๊ณผ ๋Œ€์กฐํ•  ๊ฐ’\n", " age = rng.integers(1, 25, n)\n", " meta[\"day\"] = np.sort(rng.integers(0, 90, n))\n", " meta[\"equip_type\"] = rng.choice([\"vehicle\", \"generator\", \"comm\", \"trailer\"], n, p=[0.4, 0.2, 0.2, 0.2])\n", " meta[\"mfg_year\"] = 2026 - age\n", " meta[\"op_hours\"] = np.clip(rng.normal(180 * age, 400, n) + 8 * meta[\"damage_idx\"], 10, 20000)\n", " meta[\"env\"] = rng.choice([\"coastal\", \"inland\"], n, p=[0.3, 0.7])\n", " reg = np.clip(meta[\"grade\"] + rng.choice([-1, 0, 0, 0, 0, 0, 1, 2], n), 1, 3) # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก์ƒ ์ง์ „ ์ •๋น„ ๋“ฑ๊ธ‰\n", " meta[\"reg_grade\"] = reg\n", " meta[\"emergency\"] = rng.random(n) < 0.04 # ๊ธด๊ธ‰ ์ ๊ฒ€ ์š”์ฒญ(ํ™”์งˆ ๋ถˆ๋Ÿ‰์ด์–ด๋„ ๋ณด์กด)\n", " return meta, imgs, masks\n", "\n", "# ---- ์ „์ฒ˜๋ฆฌ(์กฐ๋ช… ๋ณด์ •ยทํ‘œ์ค€ํ™”)์™€ ๊ฒฐํ•จ ํ›„๋ณด ๋ถ„ํ• , ํŠน์ง• ์ถ”์ถœ (cv2 ๋Œ€์‹  numpyยทscipy ์‚ฌ์šฉ) ----\n", "def correct(img):\n", " \"\"\"์กฐ๋ช… ๋ณด์ •: ๋ฐ๊ธฐ์— ํ‰๋ฉด(์กฐ๋ช… ๊ธฐ์šธ๊ธฐ)์„ ์ ํ•ฉํ•ด ๋‚˜๋ˆˆ๋‹ค. ์–ด๋‘์šด ๊ฒฐํ•จ ํ™”์†Œ๋Š” ์ œ์™ธํ•˜๊ณ  ๋‘ ๋ฒˆ ๋ฐ˜๋ณต ์ ํ•ฉํ•œ๋‹ค. ๋ฐ˜ํ™˜: ๋ณด์ • ๋ฐ๊ธฐ(์ •์ƒ ๋ฉด ์•ฝ 1), ์ƒ‰ ๋น„์œจ.\"\"\"\n", " g = img.mean(axis=2)\n", " A = np.c_[np.ones(SZ * SZ), XX.ravel() / SZ, YY.ravel() / SZ]\n", " keep = np.ones(SZ * SZ, bool)\n", " for _ in range(3):\n", " coef, *_ = np.linalg.lstsq(A[keep], g.ravel()[keep], rcond=None)\n", " plane = (A @ coef).reshape(SZ, SZ)\n", " keep = (np.abs(g - plane) < 0.08 * np.maximum(plane, 1e-3)).ravel()\n", " if keep.sum() < 200:\n", " keep[:] = True\n", " c = g / np.maximum(plane, 1e-3)\n", " chroma = img[..., 0] / (img.sum(axis=2) + 1e-6)\n", " return c, chroma\n", "\n", "\n", "SEGP = {\"dark\": 0.7, \"rust\": 0.5, \"rim\": 1.45} # ๋ถ„ํ•  ์ž„๊ณ„๊ฐ’(์–ด๋‘์›€ ๋ณด์ • ๋ฐ๊ธฐ, ๋ถ‰์€ ๋น„์œจ, ๋ฐ์€ ํ…Œ๋‘๋ฆฌ): ์žฌ๋ณด์ • ๋Œ€์ƒ\n", "\n", "\n", "def segment(img):\n", " \"\"\"๊ฒฐํ•จ ํ›„๋ณด ๋งˆ์Šคํฌ: ์–ด๋‘์šด ์˜์—ญ(๊ท ์—ดยท๋ˆ„์œ ยทํŒŒ์†) + ๋ถ‰์€ ๊ฐˆ์ƒ‰ ์˜์—ญ(๋ถ€์‹) + ๋ฐ์€ ํ…Œ๋‘๋ฆฌ(ํŒŒ์†).\"\"\"\n", " c, chroma = correct(img)\n", " dark = c < SEGP[\"dark\"]\n", " rust = (chroma > SEGP[\"rust\"]) & (c > 0.45)\n", " rim = c > SEGP[\"rim\"]\n", " m = dark | rust | ndi.binary_dilation(rim, iterations=1) & ndi.binary_dilation(dark, iterations=3)\n", " m = ndi.binary_opening(m, structure=np.ones((2, 2))) if m.sum() > 12 else m\n", " return m, c, chroma, dark, rust, rim\n", "\n", "\n", "def extract(img):\n", " \"\"\"๋ถ„ํ•  ๊ฒฐ๊ณผ์™€ ์ƒ‰ยท๋ชจ์–‘์—์„œ 24๊ฐœ ํŠน์ง•์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m, c, chroma, dark, rust, rim = segment(img)\n", " lab, n = ndi.label(m)\n", " area = float(m.sum())\n", " if n:\n", " sizes = ndi.sum(m, lab, range(1, n + 1))\n", " k = int(np.argmax(sizes)) + 1\n", " sl = ndi.find_objects(lab)[k - 1]\n", " h, w = sl[0].stop - sl[0].start, sl[1].stop - sl[1].start\n", " big = float(sizes.max())\n", " elong, fill = max(h, w) / max(min(h, w), 1), big / (h * w)\n", " else:\n", " big, elong, fill = 0.0, 1.0, 0.0\n", " gy, gx = np.gradient(c)\n", " mag = np.hypot(gx, gy)\n", " inside = m.sum() > 0\n", " return {\"area_px\": area, \"area_frac\": area / SZ ** 2, \"n_comp\": float(n), \"big_px\": big, \"elong\": elong, \"fill\": fill,\n", " \"dark_frac\": float(dark.mean()), \"rust_frac\": float(rust.mean()), \"rim_frac\": float(rim.mean()),\n", " \"c_mean\": float(c.mean()), \"c_std\": float(c.std()), \"c_min\": float(c.min()), \"edge_mean\": float(mag.mean()), \"edge_p95\": float(np.quantile(mag, 0.95)),\n", " \"chroma_mean\": float(chroma.mean()), \"chroma_max\": float(chroma.max()),\n", " \"in_dark\": float(c[m].mean()) if inside else 1.0, \"in_chroma\": float(chroma[m].mean()) if inside else float(chroma.mean()),\n", " \"ring_contrast\": float(c[ndi.binary_dilation(m, iterations=3) & ~m].mean() - c[m].mean()) if inside and (~m).any() else 0.0,\n", " \"thin\": float(1 - fill), \"gain\": float(img.mean()), \"sat\": float((img.max(axis=2) - img.min(axis=2)).mean()),\n", " \"cy\": float(ndi.center_of_mass(m)[0] / SZ) if inside else 0.5, \"cx\": float(ndi.center_of_mass(m)[1] / SZ) if inside else 0.5}\n", "\n", "\n", "def feature_table(imgs):\n", " return pd.DataFrame([extract(im) for im in imgs])\n", "\n", "rng = np.random.default_rng(2)\n", "meta, imgs, masks = make_images(150, seed=2)\n", "n0 = len(meta)\n", "applied = []\n", "\n", "\n", "def rule(name, n):\n", " applied.append((name, int(n)))\n", "\n", "\n", "def true_box(m):\n", " ys, xs = np.where(m)\n", " return (int(xs.min()), int(ys.min()), int(xs.max() - xs.min() + 1), int(ys.max() - ys.min() + 1)) if len(xs) else None\n", "\n", "\n", "# ---------------- 1) ์›์ฒœ ์ž๋ฃŒ ๋งŒ๋“ค๊ธฐ(์ผ๋ถ€๋Ÿฌ ๋„ฃ์€ ์˜ค๋ฅ˜) ----------------\n", "raw_imgs, rows = [], []\n", "sizes = rng.choice([32, 64, 128], n0, p=[0.2, 0.55, 0.25])\n", "dark_set = set(rng.choice(n0, 35, replace=False))\n", "emerg = set(rng.choice(n0, 6, replace=False))\n", "for i in range(n0):\n", " im = imgs[i]\n", " if sizes[i] != 64:\n", " im = np.clip(ndi.zoom(im, (sizes[i] / 64, sizes[i] / 64, 1), order=1), 0, 1)\n", " if i in dark_set or i in emerg:\n", " im = im * 0.45 # ์ €์กฐ๋„ ์ดฌ์˜\n", " raw_imgs.append(im.astype(np.float32))\n", " tb = true_box(masks[i]); k = sizes[i] / 64\n", " box = None if tb is None else [tb[0] * k, tb[1] * k, tb[2] * k, tb[3] * k]\n", " fmt = \"xywh\"\n", " if box is not None and rng.random() < 0.4: # ์ผ๋ถ€๋Š” xyxy ํ˜•์‹์œผ๋กœ ๊ธฐ๋ก\n", " box, fmt = [box[0], box[1], box[0] + box[2], box[1] + box[3]], \"xyxy\"\n", " rows.append({\"img_id\": f\"IMG{i:06d}\", \"shot\": f\"2026-{rng.integers(1, 10):02d}-{rng.integers(1, 28):02d}\", \"equip_id\": f\"VX-{1000 + i:04d}-{2000 + i:04d}\",\n", " \"mfg_year\": int(meta.loc[i, \"mfg_year\"]), \"op_hours\": float(meta.loc[i, \"op_hours\"]), \"hours_unit\": \"h\", \"reg_grade\": float(meta.loc[i, \"reg_grade\"]),\n", " \"w\": int(sizes[i]), \"box\": box, \"box_fmt\": fmt, \"emergency\": i in emerg})\n", "raw = pd.DataFrame(rows)\n", "idx = rng.choice(n0, 60, replace=False)\n", "a, b, c, d, e, f, g, h = idx[:8], idx[8:16], idx[16:24], idx[24:32], idx[32:40], idx[40:46], idx[46:52], idx[52:60]\n", "raw.loc[a, \"img_id\"] = raw.loc[a, \"img_id\"].str.replace(\"IMG\", \"img-\") # ์‚ฌ์ง„ ID ํ‘œ๊ธฐ ๋ถˆ์ผ์น˜\n", "raw.loc[b, \"shot\"] = [x.replace(\"-\", \"\") for x in raw.loc[b, \"shot\"]] # ์ดฌ์˜์ผ ํ˜•์‹ ํ˜ผ์žฌ\n", "raw.loc[c, \"op_hours\"] = raw.loc[c, \"op_hours\"] / 24; raw.loc[c, \"hours_unit\"] = \"day\" # ๊ฐ€๋™์‹œ๊ฐ„ ๋‹จ์œ„ ํ˜ผ์žฌ\n", "raw.loc[d, \"mfg_year\"] = rng.choice([1900, 2085], len(d)) # ๋ถˆ๊ฐ€๋Šฅํ•œ ์ œ์กฐ์—ฐ๋„\n", "raw.loc[e, [\"mfg_year\", \"op_hours\", \"reg_grade\"]] = np.nan # ์ œ์› ๊ฒฐ์ธก\n", "for i in f: # ๋ฐ•์Šค๊ฐ€ ์‚ฌ์ง„ ๋ฐ–์œผ๋กœ ๋‚˜๊ฐ\n", " if raw.at[i, \"box\"] is not None and raw.at[i, \"box_fmt\"] == \"xywh\":\n", " raw.at[i, \"box\"] = [raw.at[i, \"w\"] - 2, raw.at[i, \"box\"][1], raw.at[i, \"box\"][2] + 6, raw.at[i, \"box\"][3]]\n", "for i in g: # ํญ 0 ๋ฐ•์Šค\n", " if raw.at[i, \"box\"] is not None and raw.at[i, \"box_fmt\"] == \"xywh\":\n", " raw.at[i, \"box\"] = [raw.at[i, \"box\"][0], raw.at[i, \"box\"][1], 0, 0]\n", "dup_rows = raw.sample(8, random_state=1)\n", "raw = pd.concat([raw, dup_rows], ignore_index=True)\n", "raw_imgs += [raw_imgs[int(r[\"img_id\"][-6:])] for _, r in dup_rows.iterrows()] # ์ค‘๋ณต ์‚ฌ์ง„\n", "print(f\"์›์ฒœ ์‚ฌ์ง„ {len(raw)}์žฅ (์ค‘๋ณต 8์žฅ, ์˜ค๋ฅ˜ ์ฃผ์ž… {len(idx)}๊ฑด, ์ €์กฐ๋„ {len(dark_set | emerg)}์žฅ, ๊ธด๊ธ‰ ์ ๊ฒ€ {len(emerg)}์žฅ ํฌํ•จ)\")\n", "\n", "# ---------------- 2) ์ •์ œ ๊ทœ์น™ 11์ข… ----------------\n", "df, ims = raw.copy(), list(raw_imgs)\n", "dup = df.duplicated([\"img_id\", \"equip_id\", \"shot\"]); rule(\"R1 ์ค‘๋ณต ์‚ฌ์ง„ ์ œ๊ฑฐ\", dup.sum())\n", "keep = (~dup).values; df = df[keep].reset_index(drop=True); ims = [im for im, k in zip(ims, keep) if k]\n", "bad_id = ~df[\"img_id\"].str.match(r\"^IMG\\d{6}$\"); df[\"img_id\"] = \"IMG\" + df[\"img_id\"].str.extract(r\"(\\d{6})\")[0]; rule(\"R2 ์‚ฌ์ง„ ID ํ‘œ๊ธฐ ํ†ต์ผ\", bad_id.sum())\n", "t1 = pd.to_datetime(df[\"shot\"], format=\"%Y-%m-%d\", errors=\"coerce\"); t2 = pd.to_datetime(df[\"shot\"], format=\"%Y%m%d\", errors=\"coerce\")\n", "rule(\"R3 ์ดฌ์˜์ผ ํ˜•์‹ ํ†ต์ผ\", t1.isna().sum()); df[\"shot\"] = t1.fillna(t2)\n", "day = df[\"hours_unit\"] == \"day\"; df.loc[day, \"op_hours\"] *= 24; df[\"hours_unit\"] = \"h\"; rule(\"R4 ๊ฐ€๋™์‹œ๊ฐ„ ๋‹จ์œ„ ํ†ต์ผ(์ผ -> ์‹œ๊ฐ„)\", day.sum())\n", "bad = ~df[\"mfg_year\"].between(1990, 2026) & df[\"mfg_year\"].notna(); df.loc[bad, \"mfg_year\"] = np.nan; rule(\"R5 ๋ถˆ๊ฐ€๋Šฅ ์ œ์กฐ์—ฐ๋„ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ\", bad.sum())\n", "nres = sum(im.shape[0] != 64 for im in ims)\n", "ims = [np.clip(ndi.zoom(im, (64 / im.shape[0], 64 / im.shape[1], 1), order=1), 0, 1) if im.shape[0] != 64 else im for im in ims]\n", "rule(\"R6 ํ•ด์ƒ๋„ ํ‘œ์ค€ํ™”(32/128 -> 64px)\", nres)\n", "before_b = np.array([im.mean() for im in ims])\n", "fixed = []\n", "for im in ims:\n", " cc, _ = correct(im) # ๋ณด์ • ๋ฐ๊ธฐ = ๋ฐ๊ธฐ / ์กฐ๋ช… ํ‰๋ฉด\n", " plane = im.mean(axis=2) / np.maximum(cc, 1e-6)\n", " fixed.append(np.clip(im * 0.45 / np.maximum(plane, 1e-3)[..., None], 0, 1)) # ์กฐ๋ช… ๊ธฐ์šธ๊ธฐ๋ฅผ ํŽด๊ณ  ์ •์ƒ ๋ฉด ๋ฐ๊ธฐ๋ฅผ 0.45๋กœ ๋งž์ถ˜๋‹ค\n", "ims = fixed; rule(\"R7 ์กฐ๋ช… ๋ณด์ •ยท์ •๊ทœํ™”(ํ‰๊ท  ๋ฐ๊ธฐ 0.3 ๋ฏธ๋งŒ ์‚ฌ์ง„ ์ˆ˜)\", (before_b < 0.3).sum())\n", "bx = []\n", "nx = 0\n", "for (_, r) in df.iterrows():\n", " if r[\"box\"] is None:\n", " bx.append(None); continue\n", " x, y, w_, h_ = r[\"box\"]\n", " if r[\"box_fmt\"] == \"xyxy\":\n", " w_, h_ = w_ - x, h_ - y; nx += 1\n", " k = 64 / r[\"w\"]\n", " bx.append([x * k, y * k, w_ * k, h_ * k])\n", "rule(\"R8 ๋ฐ•์Šค ํ˜•์‹ ํ†ต์ผ(xyxy -> xywh, 64px ์ขŒํ‘œ)\", nx)\n", "clip_n, zero_n, boxes, manual = 0, 0, [], []\n", "for i, b_ in enumerate(bx):\n", " if b_ is None:\n", " boxes.append(None); continue\n", " x, y, w_, h_ = b_\n", " x2, y2 = min(x + w_, 64), min(y + h_, 64)\n", " if x2 > 64 or x + w_ > 64.5 or y + h_ > 64.5:\n", " clip_n += 1\n", " if x2 - x < 1 or y2 - y < 1:\n", " zero_n += 1; manual.append(df.loc[i, \"img_id\"]); boxes.append(None); continue\n", " boxes.append([int(round(x)), int(round(y)), int(round(x2 - x)), int(round(y2 - y))])\n", "rule(\"R9 ๋ฐ•์Šค ์ขŒํ‘œ ํด๋ฆฌํ•‘ยท๋ฌดํšจ ๋ฐ•์Šค ์ˆ˜๋™ ํ™•์ธ ๋ถ„๋ฆฌ\", clip_n + zero_n)\n", "miss = df[[\"mfg_year\", \"op_hours\", \"reg_grade\"]].isna().sum().sum()\n", "df[[\"mfg_year\", \"op_hours\", \"reg_grade\"]] = KNNImputer(n_neighbors=5).fit_transform(df[[\"mfg_year\", \"op_hours\", \"reg_grade\", \"w\"]])[:, :3]\n", "df[\"mfg_year\"] = df[\"mfg_year\"].round().astype(int); df[\"reg_grade\"] = df[\"reg_grade\"].round().astype(int); rule(\"R10 ์ œ์› ๊ฒฐ์ธก KNN ๋ณด์ •\", miss)\n", "df[\"quality_flag\"] = np.array([im.std() < 0.03 for im in ims]) & ~df[\"emergency\"]\n", "rule(\"R11 ๊ธด๊ธ‰ ์ ๊ฒ€ ์‚ฌ์ง„ ๋ณด์กดยท์ผ๋ฐ˜ ์ €ํ™”์งˆ ํ‘œ์‹œ\", int(df[\"emergency\"].sum()))\n", "\n", "# ---------------- 3) ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ ----------------\n", "after_b = np.array([im.mean() for im in ims])\n", "tb = {r[\"img_id\"]: true_box(masks[int(r[\"img_id\"][-6:])]) for _, r in df.iterrows()}\n", "ious = []\n", "for (_, r), bb in zip(df.iterrows(), boxes):\n", " t = tb[r[\"img_id\"]]\n", " if bb is not None and t is not None:\n", " ix = max(0, min(bb[0] + bb[2], t[0] + t[2]) - max(bb[0], t[0])); iy = max(0, min(bb[1] + bb[3], t[1] + t[3]) - max(bb[1], t[1]))\n", " ious.append(ix * iy / (bb[2] * bb[3] + t[2] * t[3] - ix * iy))\n", "chk = {\"์ค‘๋ณต ์‚ฌ์ง„ 0์žฅ\": not df[\"img_id\"].duplicated().any(), \"์‚ฌ์ง„ ID ํ˜•์‹\": df[\"img_id\"].str.match(r\"^IMG\\d{6}$\").all(), \"์ดฌ์˜์ผ ๋ณ€ํ™˜\": df[\"shot\"].notna().all(),\n", " \"๊ฐ€๋™์‹œ๊ฐ„ ๋‹จ์œ„ h\": (df[\"hours_unit\"] == \"h\").all(), \"์ œ์กฐ์—ฐ๋„ 1990~2026\": df[\"mfg_year\"].between(1990, 2026).all(),\n", " \"๋ชจ๋“  ์‚ฌ์ง„ 64x64x3\": all(im.shape == (64, 64, 3) for im in ims), \"๋ฐ๊ธฐ ํŽธ์ฐจ ๊ฐ์†Œ\": after_b.std() < before_b.std() / 2,\n", " \"๋ฐ•์Šค๊ฐ€ ์‚ฌ์ง„ ์•ˆ\": all(b_ is None or (b_[0] + b_[2] <= 64 and b_[1] + b_[3] <= 64) for b_ in boxes), \"๋ฐ•์Šค ๋ฉด์  ์–‘์ˆ˜\": all(b_ is None or (b_[2] > 0 and b_[3] > 0) for b_ in boxes),\n", " \"์ œ์› ๊ฒฐ์ธก 0๊ฑด\": df[[\"mfg_year\", \"op_hours\", \"reg_grade\"]].notna().all().all(), f\"๊ธด๊ธ‰ ์ ๊ฒ€ {int(df['emergency'].sum())}์žฅ ๋ณด์กด\": int(df[\"emergency\"].sum()) == len(emerg),\n", " \"์ •์ œ ๋ฐ•์Šค์™€ ์ •๋‹ต ๋ฐ•์Šค ํ‰๊ท  IoU 0.9 ์ด์ƒ\": float(np.mean(ious)) >= 0.9}\n", "rate = np.mean(list(chk.values()))\n", "print(\"\\n[์ ์šฉ ๊ทœ์น™๊ณผ ์ฒ˜๋ฆฌ ๊ฑด์ˆ˜]\")\n", "for nme, n in applied:\n", " print(f\" {nme:44s} {n:4d}๊ฑด\")\n", "print(f\"\\n์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ {sum(chk.values())}/{len(chk)} ํ†ต๊ณผ, ํ†ต๊ณผ์œจ {rate:.0%} (๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if rate == 1 else '๋ฏธ๋‹ฌ'}), ๊ทœ์น™ {len(applied)}์ข… (๋ชฉํ‘œ 10์ข… ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(applied) >= 10 else '๋ฏธ๋‹ฌ'})\")\n", "for k, v in chk.items():\n", " if not v:\n", " print(\" ์‹คํŒจ:\", k, \"(IoU %.2f)\" % np.mean(ious))\n", "print(f\"์ •์ œ ํ›„ ์‚ฌ์ง„ {len(df)}์žฅ, ๋ฐ•์Šค ์ˆ˜๋™ ํ™•์ธ {len(manual)}์žฅ, ๊ธด๊ธ‰ ์ ๊ฒ€ {int(df['emergency'].sum())}์žฅ ์œ ์ง€, ์ •์ œ ๋ฐ•์Šค ํ‰๊ท  IoU {np.mean(ious):.2f}\")\n", "cmp = pd.DataFrame({\"์ •์ œ ์ „\": [before_b.mean(), before_b.std(), raw[\"op_hours\"].mean(), raw[\"mfg_year\"].min(), raw[\"mfg_year\"].max()],\n", " \"์ •์ œ ํ›„\": [after_b.mean(), after_b.std(), df[\"op_hours\"].mean(), df[\"mfg_year\"].min(), df[\"mfg_year\"].max()]},\n", " index=[\"ํ‰๊ท  ๋ฐ๊ธฐ\", \"๋ฐ๊ธฐ ํ‘œ์ค€ํŽธ์ฐจ\", \"ํ‰๊ท  ๊ฐ€๋™์‹œ๊ฐ„(h)\", \"์ตœ์†Œ ์ œ์กฐ์—ฐ๋„\", \"์ตœ๋Œ€ ์ œ์กฐ์—ฐ๋„\"]).round(3)\n", "print(\"\\n[์ •์ œ ์ „ํ›„ ํ†ต๊ณ„]\"); print(cmp.to_string())\n", "\n", "fig, ax = plt.subplots(1, 3, figsize=(12, 3.5))\n", "ax[0].hist(before_b, bins=25, alpha=0.6, label=\"raw\"); ax[0].hist(after_b, bins=25, alpha=0.6, label=\"normalized\"); ax[0].set_title(\"Mean brightness\"); ax[0].legend()\n", "j = int(np.argmin(before_b))\n", "ax[1].imshow(raw_imgs[j][..., :] / max(raw_imgs[j].max(), 1e-3)); ax[1].set_title(\"dark photo (raw, stretched)\"); ax[1].axis(\"off\")\n", "ax[2].imshow(ims[j]); ax[2].set_title(\"after normalization\"); ax[2].axis(\"off\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 02 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "์›์ฒœ ์‚ฌ์ง„ 158์žฅ (์ค‘๋ณต 8์žฅ, ์˜ค๋ฅ˜ ์ฃผ์ž… 60๊ฑด, ์ €์กฐ๋„ 40์žฅ, ๊ธด๊ธ‰ ์ ๊ฒ€ 6์žฅ ํฌํ•จ)\n", "\n", "[์ ์šฉ ๊ทœ์น™๊ณผ ์ฒ˜๋ฆฌ ๊ฑด์ˆ˜]\n", " R1 ์ค‘๋ณต ์‚ฌ์ง„ ์ œ๊ฑฐ 8๊ฑด\n", " R2 ์‚ฌ์ง„ ID ํ‘œ๊ธฐ ํ†ต์ผ 8๊ฑด\n", " R3 ์ดฌ์˜์ผ ํ˜•์‹ ํ†ต์ผ 8๊ฑด\n", " R4 ๊ฐ€๋™์‹œ๊ฐ„ ๋‹จ์œ„ ํ†ต์ผ(์ผ -> ์‹œ๊ฐ„) 8๊ฑด\n", " R5 ๋ถˆ๊ฐ€๋Šฅ ์ œ์กฐ์—ฐ๋„ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ 8๊ฑด\n", " R6 ํ•ด์ƒ๋„ ํ‘œ์ค€ํ™”(32/128 -> 64px) 71๊ฑด\n", " R7 ์กฐ๋ช… ๋ณด์ •ยท์ •๊ทœํ™”(ํ‰๊ท  ๋ฐ๊ธฐ 0.3 ๋ฏธ๋งŒ ์‚ฌ์ง„ ์ˆ˜) 74๊ฑด\n", " R8 ๋ฐ•์Šค ํ˜•์‹ ํ†ต์ผ(xyxy -> xywh, 64px ์ขŒํ‘œ) 54๊ฑด\n", " R9 ๋ฐ•์Šค ์ขŒํ‘œ ํด๋ฆฌํ•‘ยท๋ฌดํšจ ๋ฐ•์Šค ์ˆ˜๋™ ํ™•์ธ ๋ถ„๋ฆฌ 4๊ฑด\n", " R10 ์ œ์› ๊ฒฐ์ธก KNN ๋ณด์ • 32๊ฑด\n", " R11 ๊ธด๊ธ‰ ์ ๊ฒ€ ์‚ฌ์ง„ ๋ณด์กดยท์ผ๋ฐ˜ ์ €ํ™”์งˆ ํ‘œ์‹œ 6๊ฑด\n", "\n", "์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ 12/12 ํ†ต๊ณผ, ํ†ต๊ณผ์œจ 100% (๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ), ๊ทœ์น™ 11์ข… (๋ชฉํ‘œ 10์ข… ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "์ •์ œ ํ›„ ์‚ฌ์ง„ 150์žฅ, ๋ฐ•์Šค ์ˆ˜๋™ ํ™•์ธ 3์žฅ, ๊ธด๊ธ‰ ์ ๊ฒ€ 6์žฅ ์œ ์ง€, ์ •์ œ ๋ฐ•์Šค ํ‰๊ท  IoU 0.99\n", "\n", "[์ •์ œ ์ „ํ›„ ํ†ต๊ณ„]\n", " ์ •์ œ ์ „ ์ •์ œ ํ›„\n", "ํ‰๊ท  ๋ฐ๊ธฐ 0.293 0.447\n", "๋ฐ๊ธฐ ํ‘œ์ค€ํŽธ์ฐจ 0.117 0.005\n", "ํ‰๊ท  ๊ฐ€๋™์‹œ๊ฐ„(h) 2494.475 2609.453\n", "์ตœ์†Œ ์ œ์กฐ์—ฐ๋„ 1900.000 2002.000\n", "์ตœ๋Œ€ ์ œ์กฐ์—ฐ๋„ 2085.000 2025.000\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 03. ์™ธ๊ด€ ๊ฒฐํ•จ ์œ ๋ฌด 1์ฐจ ํŒ๋ณ„ Baseline ๋น„์ „ ๋ชจ๋ธ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์™ธ๊ด€ ๊ฒฐํ•จ ์œ ๋ฌด 1์ฐจ ํŒ๋ณ„ Baseline ๋น„์ „ ๋ชจ๋ธ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ฐ๊ธฐ ๋ถ„ํฌ์™€ ํ‰๊ท  ์ƒ‰๋งŒ ์“ฐ๋Š” ์™ธ๊ด€ ๊ฒฐํ•จ ์œ ๋ฌด 1์ฐจ ํŒ๋ณ„ Baseline ๋น„์ „ ๋ชจ๋ธ์„ ๊ตฌํ˜„ํ•˜๊ณ  EDA๋กœ ํ•œ๊ณ„์™€ ํ•ต์‹ฌ ๊ด€๋ฆฌ ๋Œ€์ƒ์„ ์ฐพ๋Š”๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋ฐ๊ธฐ ํžˆ์Šคํ† ๊ทธ๋žจ๊ณผ ์ฑ„๋„ ํ‰๊ท ์œผ๋กœ ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€ Baseline์„ ํ•™์Šตํ•ด ์ •ํ™•๋„ยท์žฌํ˜„์œจ์„ ์ธก์ •ํ•˜๊ณ , ์ž‘์€ ๊ฒฐํ•จ๊ณผ ์ €์กฐ๋„ ์‚ฌ์ง„์—์„œ์˜ ํ•œ๊ณ„ ํ™•์ธํ•˜๊ณ  ์œ ํšจ์„ฑ์„ ์ ๊ฒ€ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `notebooks/qlt03_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": [ "\"\"\"๋ฐ๊ธฐ ํžˆ์Šคํ† ๊ทธ๋žจ๊ณผ ํ‰๊ท  ์ƒ‰๋งŒ ์“ฐ๋Š” ์™ธ๊ด€ ๊ฒฐํ•จ ์œ ๋ฌด 1์ฐจ ํŒ๋ณ„ Baseline ๋น„์ „ ๋ชจ๋ธ(๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€)์„ ๊ตฌํ˜„ํ•ด ์„ฑ๋Šฅ๊ณผ ํ•œ๊ณ„(์ž‘์€ ๊ท ์—ดยท์กฐ๋ช… ์˜์กด)๋ฅผ ํ™•์ธํ•˜๊ณ , ํƒ์ƒ‰์  ๋ถ„์„(EDA) ์ฐจํŠธ 8์ข…ยท์ƒ๊ด€๋ถ„์„(p-value)ยท์ •๋น„ ๊ณต์ˆ˜ ํŒŒ๋ ˆํ† (ABC)ยท์ œ์› ๊ธฐ๋ก๊ณผ์˜ ๋ถˆ์ผ์น˜ ๋ถ„์„์œผ๋กœ ํ•ต์‹ฌ ๊ด€๋ฆฌ ๋Œ€์ƒ์„ ์ฐพ๋Š”๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy import stats\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score\n", "from sklearn.preprocessing import StandardScaler\n", "from sklearn.pipeline import make_pipeline\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฅ๋น„ ์™ธ๊ด€ ์‚ฌ์ง„ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์œ„์น˜ ์‚ฌ์ง„์ด ์•„๋‹ˆ๋ผ ๋‚œ์ˆ˜๋กœ ๊ทธ๋ฆฐ 64x64 ํ•ฉ์„ฑ ์ด๋ฏธ์ง€)\n", "# ๋„์žฅ๋œ ๊ธˆ์† ํ‘œ๋ฉด์— ๊ท ์—ด(crack)ยท๋ถ€์‹(corrosion)ยทํŒŒ์†(damage)ยท๋ˆ„์œ (leak)๋ฅผ ๊ทธ๋ฆฌ๊ณ , ์กฐ๋ช… ํŽธ์ฐจ์™€ ์ง„ํ™(mud) ์˜ค์—ผ์„ ์„ž๋Š”๋‹ค.\n", "# ์ด๋ฏธ์ง€์™€ ํ•จ๊ป˜ ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ)๊ณผ ์ •๋‹ต ๊ฒฐํ•จ ๋งˆ์Šคํฌยท์†์ƒ๋„ยท์ •๋น„ ๋“ฑ๊ธ‰์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "from scipy import ndimage as ndi\n", "\n", "SZ = 64\n", "CLASSES = [\"none\", \"crack\", \"corrosion\", \"damage\", \"leak\"]\n", "KO_CLS = {\"none\": \"๊ฒฐํ•จ ์—†์Œ\", \"crack\": \"๊ท ์—ด\", \"corrosion\": \"๋ถ€์‹\", \"damage\": \"ํŒŒ์†\", \"leak\": \"๋ˆ„์œ \"}\n", "CLS_W = {\"none\": 0.0, \"crack\": 1.4, \"corrosion\": 1.0, \"damage\": 1.6, \"leak\": 0.6} # ์†์ƒ๋„ ๊ณ„์‚ฐ์šฉ ๊ฒฐํ•จ ์œ ํ˜• ๊ฐ€์ค‘์น˜\n", "GRADE_NAME = {1: \"ํ˜„์žฅ์ •๋น„\", 2: \"์•ผ์ „์ •๋น„\", 3: \"์ฐฝ์ •๋น„\"}\n", "YY, XX = np.mgrid[0:SZ, 0:SZ]\n", "\n", "\n", "def grade_of(idx):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 3] ์™ธ๊ด€ ๊ฒฐํ•จ ์œ ๋ฌด 1์ฐจ ํŒ๋ณ„ Baseline ๋น„์ „ ๋ชจ๋ธ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def blob(rng, cy, cx, r, rough=0.35):\n", " \"\"\"๋ถˆ๊ทœ์น™ํ•œ ์›ํ˜• ์˜์—ญ ๋งˆ์Šคํฌ.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 3] ์™ธ๊ด€ ๊ฒฐํ•จ ์œ ๋ฌด 1์ฐจ ํŒ๋ณ„ Baseline ๋น„์ „ ๋ชจ๋ธ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def draw(rng, cls, sev):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜•ยท์‹ฌ๊ฐ๋„ sev(0~1)์— ๋งž๋Š” ์ •๋‹ต ๋งˆ์Šคํฌ์™€ ์ƒ‰ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m = np.zeros((SZ, SZ), bool)\n", " if cls == \"crack\":\n", " y, x, ang = rng.uniform(12, 52), rng.uniform(12, 52), rng.uniform(0, np.pi)\n", " for _ in range(int(20 + 40 * sev)):\n", " ang += rng.normal(0, 0.35); y += np.sin(ang) * 1.2; x += np.cos(ang) * 1.2\n", " if 0 <= int(y) < SZ and 0 <= int(x) < SZ:\n", " m[int(y), int(x)] = True\n", " m = ndi.binary_dilation(m, iterations=1 + int(sev > 0.6))\n", " elif cls == \"corrosion\":\n", " for _ in range(int(rng.integers(1, 4))):\n", " m |= blob(rng, rng.uniform(14, 50), rng.uniform(14, 50), 3 + 9 * sev * rng.uniform(0.6, 1.1))\n", " elif cls == \"damage\":\n", " m = blob(rng, rng.uniform(20, 44), rng.uniform(20, 44), 6 + 12 * sev, 0.3)\n", " elif cls == \"leak\":\n", " x0, y0 = rng.uniform(16, 48), rng.uniform(8, 24)\n", " w, ln = 2 + 4 * sev, 12 + 26 * sev\n", " m = (np.abs(XX - x0 - 0.15 * (YY - y0)) < w * (1 - 0.4 * (YY - y0) / ln)) & (YY > y0) & (YY < y0 + ln)\n", " return m\n", "\n", "\n", "def make_images(n=600, seed=0, mud=0.0, p_none=0.25, dark=False):\n", " \"\"\"๊ฐ€์ƒ ์™ธ๊ด€ ์‚ฌ์ง„ n์žฅ(+์ง„ํ™ ์˜ค์—ผ mud ๋น„์œจ)๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋กยท์ •๋‹ต. ๋ฐ˜ํ™˜: meta(DataFrame), imgs(n,64,64,3), masks(n,64,64).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " imgs, masks, rows = np.zeros((n, SZ, SZ, 3), np.float32), np.zeros((n, SZ, SZ), bool), []\n", " for i in range(n):\n", " cls = \"none\" if rng.random() < p_none else str(rng.choice(CLASSES[1:], p=[0.25, 0.3, 0.25, 0.2]))\n", " sev = 0.0 if cls == \"none\" else float(rng.beta(1.6, 2.0))\n", " base = np.array([0.42, 0.46, 0.40]) * (1 + 0.5 * ndi.gaussian_filter(rng.normal(size=(SZ, SZ)), 8)[..., None] * 3)\n", " img = base + rng.normal(0, 0.012, (SZ, SZ, 3))\n", " m = draw(rng, cls, sev)\n", " a = ndi.gaussian_filter(m.astype(float), 0.8)[..., None] # ๊ฒฝ๊ณ„๋ฅผ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์„ž๋Š”๋‹ค\n", " if cls == \"crack\":\n", " col = img * rng.uniform(0.2, 0.45)\n", " elif cls == \"corrosion\":\n", " col = np.array([0.55, 0.30, 0.12]) * rng.uniform(0.75, 1.15) * (0.85 + 0.3 * rng.random((SZ, SZ, 1)))\n", " elif cls == \"damage\":\n", " col = np.ones_like(img) * np.array([0.34, 0.34, 0.36]) * (1.0 - 0.45 * sev) * rng.uniform(0.75, 1.0)\n", " rim = m & ~ndi.binary_erosion(m, iterations=2)\n", " a = np.maximum(a, 0.0); img = np.where(rim[..., None], 0.7, img)\n", " elif cls == \"leak\":\n", " col = np.ones_like(img) * np.array([0.10, 0.09, 0.08]) * rng.uniform(0.8, 2.6)\n", " else:\n", " col = img\n", " img = img * (1 - a) + col * a\n", " if rng.random() < 0.35: # ๋ฌดํ•ดํ•œ ์–ผ๋ฃฉ(๊ฒฐํ•จ ์•„๋‹˜): ์˜…์€ ๊ฐˆ์ƒ‰ ๋ฒˆ์ง\n", " st = blob(rng, rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(6, 14), 0.5)\n", " al = ndi.gaussian_filter(st.astype(float), 2.5)[..., None] * rng.uniform(0.15, 0.4)\n", " img = img * (1 - al) + np.array([0.50, 0.40, 0.30]) * al\n", " if rng.random() < 0.3: # ๋ฌดํ•ดํ•œ ๊ธด ์Šคํฌ๋ž˜์น˜(๋ฐ์€ ์„ )\n", " y0, x0, ang = rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(0, np.pi)\n", " for t in np.linspace(0, 25, 40):\n", " yy, xx = int(y0 + t * np.sin(ang)), int(x0 + t * np.cos(ang))\n", " if 0 <= yy < SZ and 0 <= xx < SZ:\n", " img[yy, xx] = np.clip(img[yy, xx] * 1.5, 0, 1)\n", " if mud > 0: # ์ง„ํ™ ์˜ค์—ผ: ๊ฒฐํ•จ ์œ„๋ฅผ ๋ฎ์„ ์ˆ˜ ์žˆ๋‹ค(์ •๋‹ต ๋งˆ์Šคํฌ๋Š” ๊ทธ๋Œ€๋กœ)\n", " mm = np.zeros((SZ, SZ), bool)\n", " while mm.mean() < mud:\n", " mm |= blob(rng, rng.uniform(0, SZ), rng.uniform(0, SZ), rng.uniform(5, 11), 0.5)\n", " img[mm] = np.array([0.38, 0.30, 0.22]) * (0.85 + 0.3 * rng.random((mm.sum(), 1)))\n", " gain = rng.uniform(0.3, 0.55) if dark else rng.uniform(0.5, 1.15)\n", " ang = rng.uniform(0, 2 * np.pi)\n", " light = gain * (1 + 0.3 * (np.cos(ang) * (XX - 32) + np.sin(ang) * (YY - 32)) / 32)\n", " imgs[i] = np.clip(img * light[..., None] + rng.normal(0, 0.025, (SZ, SZ, 3)), 0, 1)\n", " masks[i] = m\n", " area = float(m.mean())\n", " depth = {\"none\": 0.0, \"crack\": 0.3 + 3.0 * sev, \"corrosion\": 0.1 + 1.5 * sev, \"damage\": 1.0 + 8.0 * sev, \"leak\": 0.0}[cls] * rng.lognormal(0, 0.1)\n", " idx = float(np.clip(100 * (1 - np.exp(-(CLS_W[cls] * area * 9 + 0.05 * depth))) + rng.normal(0, 1.5), 0, 100))\n", " rows.append({\"img_id\": f\"IMG{seed:02d}{i:04d}\", \"cls\": cls, \"sev\": sev, \"area_frac\": area, \"depth_mm\": depth, \"damage_idx\": idx,\n", " \"grade\": int(grade_of(idx)), \"gain\": gain, \"mud\": mud})\n", " meta = pd.DataFrame(rows)\n", " # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ): ์‚ฌ์ง„ ํŒ์ •๊ณผ ๋Œ€์กฐํ•  ๊ฐ’\n", " age = rng.integers(1, 25, n)\n", " meta[\"day\"] = np.sort(rng.integers(0, 90, n))\n", " meta[\"equip_type\"] = rng.choice([\"vehicle\", \"generator\", \"comm\", \"trailer\"], n, p=[0.4, 0.2, 0.2, 0.2])\n", " meta[\"mfg_year\"] = 2026 - age\n", " meta[\"op_hours\"] = np.clip(rng.normal(180 * age, 400, n) + 8 * meta[\"damage_idx\"], 10, 20000)\n", " meta[\"env\"] = rng.choice([\"coastal\", \"inland\"], n, p=[0.3, 0.7])\n", " reg = np.clip(meta[\"grade\"] + rng.choice([-1, 0, 0, 0, 0, 0, 1, 2], n), 1, 3) # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก์ƒ ์ง์ „ ์ •๋น„ ๋“ฑ๊ธ‰\n", " meta[\"reg_grade\"] = reg\n", " meta[\"emergency\"] = rng.random(n) < 0.04 # ๊ธด๊ธ‰ ์ ๊ฒ€ ์š”์ฒญ(ํ™”์งˆ ๋ถˆ๋Ÿ‰์ด์–ด๋„ ๋ณด์กด)\n", " return meta, imgs, masks\n", "\n", "# ---- ์ „์ฒ˜๋ฆฌ(์กฐ๋ช… ๋ณด์ •ยทํ‘œ์ค€ํ™”)์™€ ๊ฒฐํ•จ ํ›„๋ณด ๋ถ„ํ• , ํŠน์ง• ์ถ”์ถœ (cv2 ๋Œ€์‹  numpyยทscipy ์‚ฌ์šฉ) ----\n", "def correct(img):\n", " \"\"\"์กฐ๋ช… ๋ณด์ •: ๋ฐ๊ธฐ์— ํ‰๋ฉด(์กฐ๋ช… ๊ธฐ์šธ๊ธฐ)์„ ์ ํ•ฉํ•ด ๋‚˜๋ˆˆ๋‹ค. ์–ด๋‘์šด ๊ฒฐํ•จ ํ™”์†Œ๋Š” ์ œ์™ธํ•˜๊ณ  ๋‘ ๋ฒˆ ๋ฐ˜๋ณต ์ ํ•ฉํ•œ๋‹ค. ๋ฐ˜ํ™˜: ๋ณด์ • ๋ฐ๊ธฐ(์ •์ƒ ๋ฉด ์•ฝ 1), ์ƒ‰ ๋น„์œจ.\"\"\"\n", " g = img.mean(axis=2)\n", " A = np.c_[np.ones(SZ * SZ), XX.ravel() / SZ, YY.ravel() / SZ]\n", " keep = np.ones(SZ * SZ, bool)\n", " for _ in range(3):\n", " coef, *_ = np.linalg.lstsq(A[keep], g.ravel()[keep], rcond=None)\n", " plane = (A @ coef).reshape(SZ, SZ)\n", " keep = (np.abs(g - plane) < 0.08 * np.maximum(plane, 1e-3)).ravel()\n", " if keep.sum() < 200:\n", " keep[:] = True\n", " c = g / np.maximum(plane, 1e-3)\n", " chroma = img[..., 0] / (img.sum(axis=2) + 1e-6)\n", " return c, chroma\n", "\n", "\n", "SEGP = {\"dark\": 0.7, \"rust\": 0.5, \"rim\": 1.45} # ๋ถ„ํ•  ์ž„๊ณ„๊ฐ’(์–ด๋‘์›€ ๋ณด์ • ๋ฐ๊ธฐ, ๋ถ‰์€ ๋น„์œจ, ๋ฐ์€ ํ…Œ๋‘๋ฆฌ): ์žฌ๋ณด์ • ๋Œ€์ƒ\n", "\n", "\n", "def segment(img):\n", " \"\"\"๊ฒฐํ•จ ํ›„๋ณด ๋งˆ์Šคํฌ: ์–ด๋‘์šด ์˜์—ญ(๊ท ์—ดยท๋ˆ„์œ ยทํŒŒ์†) + ๋ถ‰์€ ๊ฐˆ์ƒ‰ ์˜์—ญ(๋ถ€์‹) + ๋ฐ์€ ํ…Œ๋‘๋ฆฌ(ํŒŒ์†).\"\"\"\n", " c, chroma = correct(img)\n", " dark = c < SEGP[\"dark\"]\n", " rust = (chroma > SEGP[\"rust\"]) & (c > 0.45)\n", " rim = c > SEGP[\"rim\"]\n", " m = dark | rust | ndi.binary_dilation(rim, iterations=1) & ndi.binary_dilation(dark, iterations=3)\n", " m = ndi.binary_opening(m, structure=np.ones((2, 2))) if m.sum() > 12 else m\n", " return m, c, chroma, dark, rust, rim\n", "\n", "\n", "def extract(img):\n", " \"\"\"๋ถ„ํ•  ๊ฒฐ๊ณผ์™€ ์ƒ‰ยท๋ชจ์–‘์—์„œ 24๊ฐœ ํŠน์ง•์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m, c, chroma, dark, rust, rim = segment(img)\n", " lab, n = ndi.label(m)\n", " area = float(m.sum())\n", " if n:\n", " sizes = ndi.sum(m, lab, range(1, n + 1))\n", " k = int(np.argmax(sizes)) + 1\n", " sl = ndi.find_objects(lab)[k - 1]\n", " h, w = sl[0].stop - sl[0].start, sl[1].stop - sl[1].start\n", " big = float(sizes.max())\n", " elong, fill = max(h, w) / max(min(h, w), 1), big / (h * w)\n", " else:\n", " big, elong, fill = 0.0, 1.0, 0.0\n", " gy, gx = np.gradient(c)\n", " mag = np.hypot(gx, gy)\n", " inside = m.sum() > 0\n", " return {\"area_px\": area, \"area_frac\": area / SZ ** 2, \"n_comp\": float(n), \"big_px\": big, \"elong\": elong, \"fill\": fill,\n", " \"dark_frac\": float(dark.mean()), \"rust_frac\": float(rust.mean()), \"rim_frac\": float(rim.mean()),\n", " \"c_mean\": float(c.mean()), \"c_std\": float(c.std()), \"c_min\": float(c.min()), \"edge_mean\": float(mag.mean()), \"edge_p95\": float(np.quantile(mag, 0.95)),\n", " \"chroma_mean\": float(chroma.mean()), \"chroma_max\": float(chroma.max()),\n", " \"in_dark\": float(c[m].mean()) if inside else 1.0, \"in_chroma\": float(chroma[m].mean()) if inside else float(chroma.mean()),\n", " \"ring_contrast\": float(c[ndi.binary_dilation(m, iterations=3) & ~m].mean() - c[m].mean()) if inside and (~m).any() else 0.0,\n", " \"thin\": float(1 - fill), \"gain\": float(img.mean()), \"sat\": float((img.max(axis=2) - img.min(axis=2)).mean()),\n", " \"cy\": float(ndi.center_of_mass(m)[0] / SZ) if inside else 0.5, \"cx\": float(ndi.center_of_mass(m)[1] / SZ) if inside else 0.5}\n", "\n", "\n", "def feature_table(imgs):\n", " return pd.DataFrame([extract(im) for im in imgs])\n", "\n", "mtr, itr, _ = make_images(400, seed=1)\n", "mte, ite, kte = make_images(300, seed=2)\n", "\n", "\n", "def hist_feat(imgs):\n", " \"\"\"Baseline ํŠน์ง•: ๋ฐ๊ธฐ ํžˆ์Šคํ† ๊ทธ๋žจ 16์นธ + ์ฑ„๋„ ํ‰๊ท  3 + ๋ฐ๊ธฐ ํ‘œ์ค€ํŽธ์ฐจ 1.\"\"\"\n", " out = []\n", " for im in imgs:\n", " g = im.mean(axis=2)\n", " h, _ = np.histogram(g / max(g.max(), 1e-3), bins=16, range=(0, 1))\n", " out.append(np.r_[h / g.size, im.mean(axis=(0, 1)) / max(g.mean(), 1e-3), g.std() / max(g.mean(), 1e-3)])\n", " return np.array(out)\n", "\n", "\n", "Htr, Hte = hist_feat(itr), hist_feat(ite)\n", "ytr, yte = (mtr[\"cls\"] != \"none\").values, (mte[\"cls\"] != \"none\").values\n", "base = make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000, C=0.5)).fit(Htr, ytr)\n", "pr = base.predict(Hte)\n", "print(f\"[Baseline ๊ฒฐ๊ณผ] ํ•™์Šต {len(mtr)}์žฅ / ํ‰๊ฐ€ {len(mte)}์žฅ, ๊ฒฐํ•จ ์‚ฌ์ง„ {yte.mean():.1%}\")\n", "print(f\"์ •ํ™•๋„ {accuracy_score(yte, pr):.1%}, ์ •๋ฐ€๋„ {precision_score(yte, pr):.1%}, ์žฌํ˜„์œจ {recall_score(yte, pr):.1%}, F1 {f1_score(yte, pr):.3f}\")\n", "rec = pd.DataFrame({\"cls\": mte[\"cls\"], \"hit\": pr == yte}).query(\"cls != 'none'\").groupby(\"cls\")[\"hit\"].mean().round(2)\n", "print(\"๊ฒฐํ•จ ์œ ํ˜•๋ณ„ ์žฌํ˜„์œจ:\", \", \".join(f\"{KO_CLS[k]} {v:.0%}\" for k, v in rec.items()))\n", "small = (mte[\"area_frac\"] > 0) & (mte[\"area_frac\"] < 0.02)\n", "print(f\"๋ฉด์  2% ๋ฏธ๋งŒ ์ž‘์€ ๊ฒฐํ•จ {int(small.sum())}์žฅ ์žฌํ˜„์œจ {pr[small.values].mean():.0%} (ํฐ ๊ฒฐํ•จ {pr[(mte['area_frac'] >= 0.02).values].mean():.0%})\")\n", "dk = mte[\"gain\"] < 0.6\n", "print(f\"์กฐ๋ช…์ด ์–ด๋‘์šด ์‚ฌ์ง„(gain<0.6) ์ •ํ™•๋„ {(pr == yte)[dk.values].mean():.0%} vs ๋ฐ์€ ์‚ฌ์ง„ {(pr == yte)[~dk.values].mean():.0%}\")\n", "print(\"-> ์ „์ฒด ํ†ต๊ณ„๋งŒ ๋ณด๋Š” Baseline์€ ์ž‘์€ ๊ท ์—ด์„ ๋†“์น˜๊ณ  ์กฐ๋ช…์— ํ”๋“ค๋ฆฐ๋‹ค. ๊ฒฐํ•จ ์œ„์น˜ยท๋ชจ์–‘์„ ๋ณด๋Š” ๋ถ„ํ•  ํŠน์ง•์ด ํ•„์š”ํ•˜๋‹ค(๋ฏธ์…˜ 4~5์˜ ๋™๊ธฐ).\")\n", "\n", "# 1) ์ƒ๊ด€๋ถ„์„: ๊ฒฐํ•จ ์œ ๋ฌด์™€์˜ ๊ด€๊ณ„(์ ์ด์—ฐ ์ƒ๊ด€, ์œ ์˜์ˆ˜์ค€ 0.05) - ์‚ฌ์ง„ ํŠน์ง• + ์žฅ๋น„ ์ œ์› ๊ธฐ๋ก\n", "Fall = pd.concat([feature_table(ite), feature_table(itr[:200])], ignore_index=True)\n", "mall = pd.concat([mte, mtr[:200]], ignore_index=True)\n", "Fall[\"op_hours\"], Fall[\"reg_grade\"], Fall[\"mfg_year\"] = mall[\"op_hours\"], mall[\"reg_grade\"], mall[\"mfg_year\"]\n", "Fall[\"coastal\"] = (mall[\"env\"] == \"coastal\").astype(int)\n", "yall = (mall[\"cls\"] != \"none\").astype(float)\n", "rows = []\n", "for f in [\"c_std\", \"c_min\", \"edge_p95\", \"dark_frac\", \"rust_frac\", \"sat\", \"op_hours\", \"reg_grade\", \"mfg_year\", \"coastal\"]:\n", " r, p = stats.pearsonr(Fall[f].astype(float), yall)\n", " rows.append({\"ํ”ผ์ฒ˜\": f, \"์ƒ๊ด€๊ณ„์ˆ˜\": round(r, 3), \"p-value\": f\"{p:.1e}\", \"์œ ์˜\": p < 0.05})\n", "corr = pd.DataFrame(rows).sort_values(\"์ƒ๊ด€๊ณ„์ˆ˜\", key=abs, ascending=False)\n", "print(\"\\n[๊ฒฐํ•จ ์œ ๋ฌด์™€์˜ ์ƒ๊ด€๋ถ„์„]\")\n", "print(corr.to_string(index=False))\n", "n_sig = int(corr[\"์œ ์˜\"].sum())\n", "print(f\"์œ ์˜๋ฏธํ•œ ํ”ผ์ฒ˜ {n_sig}๊ฐœ (๋ชฉํ‘œ 5๊ฐœ ์ด์ƒ: {'๋‹ฌ์„ฑ' if n_sig >= 5 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# 2) ์ œ์› ๊ธฐ๋ก๊ณผ์˜ ๋ถˆ์ผ์น˜: ์‚ฌ์ง„ ํŒ์ • ๋“ฑ๊ธ‰ vs ๊ด€๋ฆฌ ๊ธฐ๋ก ๋“ฑ๊ธ‰\n", "ct = pd.crosstab(mall[\"grade\"], mall[\"reg_grade\"]).rename(index=GRADE_NAME, columns=GRADE_NAME)\n", "gap = (mall[\"grade\"] - mall[\"reg_grade\"]).abs() >= 2\n", "print(f\"\\n[์ œ์› ๋Œ€์กฐ] ์‚ฌ์ง„ ๋“ฑ๊ธ‰๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋ก ๋“ฑ๊ธ‰์ด 2๋‹จ๊ณ„ ์ด์ƒ ๋‹ค๋ฅธ ์žฅ๋น„ {int(gap.sum())}๋Œ€({gap.mean():.1%}) -> ์ œ์› ์ด์ƒ ์‹ ํ˜ธ ํ›„๋ณด\")\n", "print(ct.to_string())\n", "\n", "# 3) ํŒŒ๋ ˆํ† (ABC): ์ •๋น„ ๊ณต์ˆ˜(๊ฐ€์ƒ)๊ฐ€ ํฐ (์žฅ๋น„ ๊ตฌ๋ถ„, ๊ฒฐํ•จ ์œ ํ˜•) ์กฐํ•ฉ\n", "HOURS = {1: 2, 2: 8, 3: 30}\n", "mall[\"hours\"] = mall[\"grade\"].map(HOURS)\n", "g = mall[mall[\"cls\"] != \"none\"].groupby([\"equip_type\", \"cls\"])[\"hours\"].sum().sort_values(ascending=False)\n", "cum = g.cumsum() / g.sum()\n", "abc = pd.cut(cum, [0, 0.8, 0.95, 1.0], labels=[\"A\", \"B\", \"C\"])\n", "print(\"\\n[ํŒŒ๋ ˆํ†  A๋“ฑ๊ธ‰(๋ˆ„์  ์ •๋น„ ๊ณต์ˆ˜ 80%)]\", \", \".join(f\"{e}/{KO_CLS[c]}\" for (e, c), v in abc.items() if v == \"A\"))\n", "print(f\"A๋“ฑ๊ธ‰์€ ์ „์ฒด ์กฐํ•ฉ {len(g)}๊ฐœ ์ค‘ {int((abc == 'A').sum())}๊ฐœ({(abc == 'A').mean():.0%})\")\n", "\n", "# 4) EDA ์ฐจํŠธ 8์ข…\n", "fig, ax = plt.subplots(2, 4, figsize=(18, 8))\n", "a = ax.ravel()\n", "a[0].imshow(np.concatenate([ite[int(np.where(mte['cls'] == c)[0][0])] for c in CLASSES], axis=1)); a[0].set_title(\"1. Samples: none/crack/corrosion/damage/leak\"); a[0].axis(\"off\")\n", "mall[\"cls\"].value_counts().plot.bar(ax=a[1]); a[1].set_title(\"2. Photos by defect class\")\n", "a[2].hist(mall.loc[yall == 0, \"gain\"], bins=20, alpha=0.6, label=\"none\"); a[2].hist(mall.loc[yall == 1, \"gain\"], bins=20, alpha=0.6, label=\"defect\"); a[2].legend(); a[2].set_title(\"3. Lighting gain\")\n", "mall.boxplot(column=\"damage_idx\", by=\"cls\", ax=a[3], rot=30); a[3].set_title(\"4. Damage index by class\")\n", "a[4].imshow(ct.values, cmap=\"Blues\"); a[4].set_xticks(range(3), [1, 2, 3]); a[4].set_yticks(range(3), [1, 2, 3]); a[4].set_xlabel(\"registered grade\"); a[4].set_ylabel(\"photo grade\"); a[4].set_title(\"5. Photo vs registered grade\")\n", "a[5].scatter(mall[\"op_hours\"], mall[\"damage_idx\"], s=6, c=mall[\"env\"].map({\"coastal\": \"tab:red\", \"inland\": \"tab:blue\"})); a[5].set_title(\"6. Op hours vs damage (red = coastal)\")\n", "a[6].barh(corr[\"ํ”ผ์ฒ˜\"], corr[\"์ƒ๊ด€๊ณ„์ˆ˜\"]); a[6].set_title(\"7. Correlation with defect\")\n", "a[7].plot(range(1, len(cum) + 1), cum.values, \"o-\"); a[7].axhline(0.8, ls=\"--\", c=\"gray\"); a[7].set_title(\"8. Pareto of repair hours\")\n", "plt.suptitle(\"QLT03 EDA - baseline defect screening (synthetic photos)\"); plt.tight_layout(); plt.show()\n", "print(\"\\nEDA ์ฐจํŠธ 8์ข… ์ƒ์„ฑ (๋ชฉํ‘œ 8์ข… ์ด์ƒ: ๋‹ฌ์„ฑ)\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 03 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[Baseline ๊ฒฐ๊ณผ] ํ•™์Šต 400์žฅ / ํ‰๊ฐ€ 300์žฅ, ๊ฒฐํ•จ ์‚ฌ์ง„ 76.7%\n", "์ •ํ™•๋„ 86.0%, ์ •๋ฐ€๋„ 89.8%, ์žฌํ˜„์œจ 92.2%, F1 0.910\n", "๊ฒฐํ•จ ์œ ํ˜•๋ณ„ ์žฌํ˜„์œจ: ๋ถ€์‹ 96%, ๊ท ์—ด 75%, ํŒŒ์† 96%, ๋ˆ„์œ  98%\n", "๋ฉด์  2% ๋ฏธ๋งŒ ์ž‘์€ ๊ฒฐํ•จ 31์žฅ ์žฌํ˜„์œจ 74% (ํฐ ๊ฒฐํ•จ 95%)\n", "์กฐ๋ช…์ด ์–ด๋‘์šด ์‚ฌ์ง„(gain<0.6) ์ •ํ™•๋„ 82% vs ๋ฐ์€ ์‚ฌ์ง„ 87%\n", "-> ์ „์ฒด ํ†ต๊ณ„๋งŒ ๋ณด๋Š” Baseline์€ ์ž‘์€ ๊ท ์—ด์„ ๋†“์น˜๊ณ  ์กฐ๋ช…์— ํ”๋“ค๋ฆฐ๋‹ค. ๊ฒฐํ•จ ์œ„์น˜ยท๋ชจ์–‘์„ ๋ณด๋Š” ๋ถ„ํ•  ํŠน์ง•์ด ํ•„์š”ํ•˜๋‹ค(๋ฏธ์…˜ 4~5์˜ ๋™๊ธฐ).\n", "\n", "[๊ฒฐํ•จ ์œ ๋ฌด์™€์˜ ์ƒ๊ด€๋ถ„์„]\n", " ํ”ผ์ฒ˜ ์ƒ๊ด€๊ณ„์ˆ˜ p-value ์œ ์˜\n", " c_min -0.711 2.7e-78 True\n", " c_std 0.496 1.8e-32 True\n", "dark_frac 0.457 3.5e-27 True\n", " edge_p95 0.373 5.4e-18 True\n", "reg_grade 0.272 6.2e-10 True\n", "rust_frac 0.242 4.5e-08 True\n", " sat 0.186 3.0e-05 True\n", " op_hours 0.066 1.4e-01 False\n", " mfg_year 0.045 3.1e-01 False\n", " coastal 0.004 9.2e-01 False\n", "์œ ์˜๋ฏธํ•œ ํ”ผ์ฒ˜ 7๊ฐœ (๋ชฉํ‘œ 5๊ฐœ ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "\n", "[์ œ์› ๋Œ€์กฐ] ์‚ฌ์ง„ ๋“ฑ๊ธ‰๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋ก ๋“ฑ๊ธ‰์ด 2๋‹จ๊ณ„ ์ด์ƒ ๋‹ค๋ฅธ ์žฅ๋น„ 47๋Œ€(9.4%) -> ์ œ์› ์ด์ƒ ์‹ ํ˜ธ ํ›„๋ณด\n", "reg_grade ํ˜„์žฅ์ •๋น„ ์•ผ์ „์ •๋น„ ์ฐฝ์ •๋น„\n", "grade \n", "ํ˜„์žฅ์ •๋น„ 219 50 47\n", "์•ผ์ „์ •๋น„ 8 58 28\n", "์ฐฝ์ •๋น„ 0 15 75\n", "\n", "[ํŒŒ๋ ˆํ†  A๋“ฑ๊ธ‰(๋ˆ„์  ์ •๋น„ ๊ณต์ˆ˜ 80%)] vehicle/ํŒŒ์†, comm/ํŒŒ์†, vehicle/๋ถ€์‹, trailer/ํŒŒ์†, generator/ํŒŒ์†, generator/๋ถ€์‹\n", "A๋“ฑ๊ธ‰์€ ์ „์ฒด ์กฐํ•ฉ 16๊ฐœ ์ค‘ 6๊ฐœ(38%)\n", "\n", "EDA ์ฐจํŠธ 8์ข… ์ƒ์„ฑ (๋ชฉํ‘œ 8์ข… ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 04. ์„ธ๊ทธ๋ฉ˜ํ…Œ์ด์…˜(Mask R-CNN) ๊ธฐ๋ฐ˜ ๊ฒฐํ•จ ๋ฉด์ /๊นŠ์ด ์ •๋ฐ€ ์ธก์ • ๋ฐ ์†์ƒ๋„ ์ž๋™ ํŒ์ • ๋ชจ๋ธ ๊ณ ๋„ํ™”\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์„ธ๊ทธ๋ฉ˜ํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜ ๊ฒฐํ•จ ๋ฉด์ /๊นŠ์ด ์ •๋ฐ€ ์ธก์ • ๋ฐ ์†์ƒ๋„ ์ž๋™ ํŒ์ • ๋ชจ๋ธ์„ ๊ณ ๋„ํ™”ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ถ„ํ•  ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฒฐํ•จ ๋ฉด์ ๊ณผ ๊นŠ์ด๋ฅผ ์ธก์ •ํ•˜๊ณ  ์†์ƒ๋„๋ฅผ ์ž๋™ ํŒ์ •ํ•˜๋Š” ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด ๊ธฐ์ค€ ์˜ค์ฐจ๋ฅผ ์ธก์ •ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์กฐ๋ช… ๋ณด์ • ํ›„ ์ž„๊ณ„๊ฐ’ ๋ถ„ํ• (Mask R-CNN ๋Œ€์ฒด)๋กœ ๊ฒฐํ•จ ๋งˆ์Šคํฌ๋ฅผ ๋งŒ๋“ค๊ณ  ์ •๋‹ต ๋งˆ์Šคํฌ์™€์˜ IoUยทDice๋กœ ์ •๋ฐ€๋„ ํ‰๊ฐ€ํ•˜๊ณ  ์„ฑ๋Šฅ์„ ๋น„๊ตํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/features/qlt03_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": [ "\"\"\"๋ถ„ํ• (thresholding ๊ธฐ๋ฐ˜ ๊ฒฐํ•จ ํ›„๋ณด ๋ถ„ํ• , Mask R-CNN ๋Œ€์ฒด) ๊ฒฐ๊ณผ์—์„œ ๊ฒฐํ•จ ๋ฉด์ ์„ ์ธก์ •ยท๋ณด์ •ํ•˜๊ณ  ๊นŠ์ด์™€ ์†์ƒ๋„ ์ ์ˆ˜๋ฅผ ํ•™์Šตํ•ด ์†์ƒ๋„๋ฅผ ์ž๋™ ํŒ์ •ํ•˜๋ฉฐ, ๋ถ„ํ•  IoUยท๋ฉด์ /๊นŠ์ด/์†์ƒ๋„ ์˜ค์ฐจ(MAPE)์™€ ๋ณ€์ˆ˜๊ตฐ ์ œ๊ฑฐ ์‹คํ—˜์œผ๋กœ ํŒŒ์ƒ ํ”ผ์ฒ˜ ์„ค๊ณ„์˜ ํƒ€๋‹น์„ฑ์„ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.ensemble import RandomForestClassifier, GradientBoostingRegressor\n", "from sklearn.linear_model import LinearRegression\n", "from sklearn.model_selection import cross_val_predict\n", "from sklearn.metrics import r2_score\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฅ๋น„ ์™ธ๊ด€ ์‚ฌ์ง„ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์œ„์น˜ ์‚ฌ์ง„์ด ์•„๋‹ˆ๋ผ ๋‚œ์ˆ˜๋กœ ๊ทธ๋ฆฐ 64x64 ํ•ฉ์„ฑ ์ด๋ฏธ์ง€)\n", "# ๋„์žฅ๋œ ๊ธˆ์† ํ‘œ๋ฉด์— ๊ท ์—ด(crack)ยท๋ถ€์‹(corrosion)ยทํŒŒ์†(damage)ยท๋ˆ„์œ (leak)๋ฅผ ๊ทธ๋ฆฌ๊ณ , ์กฐ๋ช… ํŽธ์ฐจ์™€ ์ง„ํ™(mud) ์˜ค์—ผ์„ ์„ž๋Š”๋‹ค.\n", "# ์ด๋ฏธ์ง€์™€ ํ•จ๊ป˜ ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ)๊ณผ ์ •๋‹ต ๊ฒฐํ•จ ๋งˆ์Šคํฌยท์†์ƒ๋„ยท์ •๋น„ ๋“ฑ๊ธ‰์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "from scipy import ndimage as ndi\n", "\n", "SZ = 64\n", "CLASSES = [\"none\", \"crack\", \"corrosion\", \"damage\", \"leak\"]\n", "KO_CLS = {\"none\": \"๊ฒฐํ•จ ์—†์Œ\", \"crack\": \"๊ท ์—ด\", \"corrosion\": \"๋ถ€์‹\", \"damage\": \"ํŒŒ์†\", \"leak\": \"๋ˆ„์œ \"}\n", "CLS_W = {\"none\": 0.0, \"crack\": 1.4, \"corrosion\": 1.0, \"damage\": 1.6, \"leak\": 0.6} # ์†์ƒ๋„ ๊ณ„์‚ฐ์šฉ ๊ฒฐํ•จ ์œ ํ˜• ๊ฐ€์ค‘์น˜\n", "GRADE_NAME = {1: \"ํ˜„์žฅ์ •๋น„\", 2: \"์•ผ์ „์ •๋น„\", 3: \"์ฐฝ์ •๋น„\"}\n", "YY, XX = np.mgrid[0:SZ, 0:SZ]\n", "\n", "\n", "def grade_of(idx):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] ์„ธ๊ทธ๋ฉ˜ํ…Œ์ด์…˜(Mask R-CNN) ๊ธฐ๋ฐ˜ ๊ฒฐํ•จ ๋ฉด์ /๊นŠ์ด ์ •๋ฐ€ ์ธก์ • ๋ฐ ์†์ƒ๋„ ์ž๋™ ํŒ์ • ๋ชจ๋ธ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def blob(rng, cy, cx, r, rough=0.35):\n", " \"\"\"๋ถˆ๊ทœ์น™ํ•œ ์›ํ˜• ์˜์—ญ ๋งˆ์Šคํฌ.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] ์„ธ๊ทธ๋ฉ˜ํ…Œ์ด์…˜(Mask R-CNN) ๊ธฐ๋ฐ˜ ๊ฒฐํ•จ ๋ฉด์ /๊นŠ์ด ์ •๋ฐ€ ์ธก์ • ๋ฐ ์†์ƒ๋„ ์ž๋™ ํŒ์ • ๋ชจ๋ธ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def draw(rng, cls, sev):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜•ยท์‹ฌ๊ฐ๋„ sev(0~1)์— ๋งž๋Š” ์ •๋‹ต ๋งˆ์Šคํฌ์™€ ์ƒ‰ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m = np.zeros((SZ, SZ), bool)\n", " if cls == \"crack\":\n", " y, x, ang = rng.uniform(12, 52), rng.uniform(12, 52), rng.uniform(0, np.pi)\n", " for _ in range(int(20 + 40 * sev)):\n", " ang += rng.normal(0, 0.35); y += np.sin(ang) * 1.2; x += np.cos(ang) * 1.2\n", " if 0 <= int(y) < SZ and 0 <= int(x) < SZ:\n", " m[int(y), int(x)] = True\n", " m = ndi.binary_dilation(m, iterations=1 + int(sev > 0.6))\n", " elif cls == \"corrosion\":\n", " for _ in range(int(rng.integers(1, 4))):\n", " m |= blob(rng, rng.uniform(14, 50), rng.uniform(14, 50), 3 + 9 * sev * rng.uniform(0.6, 1.1))\n", " elif cls == \"damage\":\n", " m = blob(rng, rng.uniform(20, 44), rng.uniform(20, 44), 6 + 12 * sev, 0.3)\n", " elif cls == \"leak\":\n", " x0, y0 = rng.uniform(16, 48), rng.uniform(8, 24)\n", " w, ln = 2 + 4 * sev, 12 + 26 * sev\n", " m = (np.abs(XX - x0 - 0.15 * (YY - y0)) < w * (1 - 0.4 * (YY - y0) / ln)) & (YY > y0) & (YY < y0 + ln)\n", " return m\n", "\n", "\n", "def make_images(n=600, seed=0, mud=0.0, p_none=0.25, dark=False):\n", " \"\"\"๊ฐ€์ƒ ์™ธ๊ด€ ์‚ฌ์ง„ n์žฅ(+์ง„ํ™ ์˜ค์—ผ mud ๋น„์œจ)๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋กยท์ •๋‹ต. ๋ฐ˜ํ™˜: meta(DataFrame), imgs(n,64,64,3), masks(n,64,64).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " imgs, masks, rows = np.zeros((n, SZ, SZ, 3), np.float32), np.zeros((n, SZ, SZ), bool), []\n", " for i in range(n):\n", " cls = \"none\" if rng.random() < p_none else str(rng.choice(CLASSES[1:], p=[0.25, 0.3, 0.25, 0.2]))\n", " sev = 0.0 if cls == \"none\" else float(rng.beta(1.6, 2.0))\n", " base = np.array([0.42, 0.46, 0.40]) * (1 + 0.5 * ndi.gaussian_filter(rng.normal(size=(SZ, SZ)), 8)[..., None] * 3)\n", " img = base + rng.normal(0, 0.012, (SZ, SZ, 3))\n", " m = draw(rng, cls, sev)\n", " a = ndi.gaussian_filter(m.astype(float), 0.8)[..., None] # ๊ฒฝ๊ณ„๋ฅผ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์„ž๋Š”๋‹ค\n", " if cls == \"crack\":\n", " col = img * rng.uniform(0.2, 0.45)\n", " elif cls == \"corrosion\":\n", " col = np.array([0.55, 0.30, 0.12]) * rng.uniform(0.75, 1.15) * (0.85 + 0.3 * rng.random((SZ, SZ, 1)))\n", " elif cls == \"damage\":\n", " col = np.ones_like(img) * np.array([0.34, 0.34, 0.36]) * (1.0 - 0.45 * sev) * rng.uniform(0.75, 1.0)\n", " rim = m & ~ndi.binary_erosion(m, iterations=2)\n", " a = np.maximum(a, 0.0); img = np.where(rim[..., None], 0.7, img)\n", " elif cls == \"leak\":\n", " col = np.ones_like(img) * np.array([0.10, 0.09, 0.08]) * rng.uniform(0.8, 2.6)\n", " else:\n", " col = img\n", " img = img * (1 - a) + col * a\n", " if rng.random() < 0.35: # ๋ฌดํ•ดํ•œ ์–ผ๋ฃฉ(๊ฒฐํ•จ ์•„๋‹˜): ์˜…์€ ๊ฐˆ์ƒ‰ ๋ฒˆ์ง\n", " st = blob(rng, rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(6, 14), 0.5)\n", " al = ndi.gaussian_filter(st.astype(float), 2.5)[..., None] * rng.uniform(0.15, 0.4)\n", " img = img * (1 - al) + np.array([0.50, 0.40, 0.30]) * al\n", " if rng.random() < 0.3: # ๋ฌดํ•ดํ•œ ๊ธด ์Šคํฌ๋ž˜์น˜(๋ฐ์€ ์„ )\n", " y0, x0, ang = rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(0, np.pi)\n", " for t in np.linspace(0, 25, 40):\n", " yy, xx = int(y0 + t * np.sin(ang)), int(x0 + t * np.cos(ang))\n", " if 0 <= yy < SZ and 0 <= xx < SZ:\n", " img[yy, xx] = np.clip(img[yy, xx] * 1.5, 0, 1)\n", " if mud > 0: # ์ง„ํ™ ์˜ค์—ผ: ๊ฒฐํ•จ ์œ„๋ฅผ ๋ฎ์„ ์ˆ˜ ์žˆ๋‹ค(์ •๋‹ต ๋งˆ์Šคํฌ๋Š” ๊ทธ๋Œ€๋กœ)\n", " mm = np.zeros((SZ, SZ), bool)\n", " while mm.mean() < mud:\n", " mm |= blob(rng, rng.uniform(0, SZ), rng.uniform(0, SZ), rng.uniform(5, 11), 0.5)\n", " img[mm] = np.array([0.38, 0.30, 0.22]) * (0.85 + 0.3 * rng.random((mm.sum(), 1)))\n", " gain = rng.uniform(0.3, 0.55) if dark else rng.uniform(0.5, 1.15)\n", " ang = rng.uniform(0, 2 * np.pi)\n", " light = gain * (1 + 0.3 * (np.cos(ang) * (XX - 32) + np.sin(ang) * (YY - 32)) / 32)\n", " imgs[i] = np.clip(img * light[..., None] + rng.normal(0, 0.025, (SZ, SZ, 3)), 0, 1)\n", " masks[i] = m\n", " area = float(m.mean())\n", " depth = {\"none\": 0.0, \"crack\": 0.3 + 3.0 * sev, \"corrosion\": 0.1 + 1.5 * sev, \"damage\": 1.0 + 8.0 * sev, \"leak\": 0.0}[cls] * rng.lognormal(0, 0.1)\n", " idx = float(np.clip(100 * (1 - np.exp(-(CLS_W[cls] * area * 9 + 0.05 * depth))) + rng.normal(0, 1.5), 0, 100))\n", " rows.append({\"img_id\": f\"IMG{seed:02d}{i:04d}\", \"cls\": cls, \"sev\": sev, \"area_frac\": area, \"depth_mm\": depth, \"damage_idx\": idx,\n", " \"grade\": int(grade_of(idx)), \"gain\": gain, \"mud\": mud})\n", " meta = pd.DataFrame(rows)\n", " # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ): ์‚ฌ์ง„ ํŒ์ •๊ณผ ๋Œ€์กฐํ•  ๊ฐ’\n", " age = rng.integers(1, 25, n)\n", " meta[\"day\"] = np.sort(rng.integers(0, 90, n))\n", " meta[\"equip_type\"] = rng.choice([\"vehicle\", \"generator\", \"comm\", \"trailer\"], n, p=[0.4, 0.2, 0.2, 0.2])\n", " meta[\"mfg_year\"] = 2026 - age\n", " meta[\"op_hours\"] = np.clip(rng.normal(180 * age, 400, n) + 8 * meta[\"damage_idx\"], 10, 20000)\n", " meta[\"env\"] = rng.choice([\"coastal\", \"inland\"], n, p=[0.3, 0.7])\n", " reg = np.clip(meta[\"grade\"] + rng.choice([-1, 0, 0, 0, 0, 0, 1, 2], n), 1, 3) # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก์ƒ ์ง์ „ ์ •๋น„ ๋“ฑ๊ธ‰\n", " meta[\"reg_grade\"] = reg\n", " meta[\"emergency\"] = rng.random(n) < 0.04 # ๊ธด๊ธ‰ ์ ๊ฒ€ ์š”์ฒญ(ํ™”์งˆ ๋ถˆ๋Ÿ‰์ด์–ด๋„ ๋ณด์กด)\n", " return meta, imgs, masks\n", "# ---- ์ „์ฒ˜๋ฆฌ(์กฐ๋ช… ๋ณด์ •ยทํ‘œ์ค€ํ™”)์™€ ๊ฒฐํ•จ ํ›„๋ณด ๋ถ„ํ• , ํŠน์ง• ์ถ”์ถœ (cv2 ๋Œ€์‹  numpyยทscipy ์‚ฌ์šฉ) ----\n", "def correct(img):\n", " \"\"\"์กฐ๋ช… ๋ณด์ •: ๋ฐ๊ธฐ์— ํ‰๋ฉด(์กฐ๋ช… ๊ธฐ์šธ๊ธฐ)์„ ์ ํ•ฉํ•ด ๋‚˜๋ˆˆ๋‹ค. ์–ด๋‘์šด ๊ฒฐํ•จ ํ™”์†Œ๋Š” ์ œ์™ธํ•˜๊ณ  ๋‘ ๋ฒˆ ๋ฐ˜๋ณต ์ ํ•ฉํ•œ๋‹ค. ๋ฐ˜ํ™˜: ๋ณด์ • ๋ฐ๊ธฐ(์ •์ƒ ๋ฉด ์•ฝ 1), ์ƒ‰ ๋น„์œจ.\"\"\"\n", " g = img.mean(axis=2)\n", " A = np.c_[np.ones(SZ * SZ), XX.ravel() / SZ, YY.ravel() / SZ]\n", " keep = np.ones(SZ * SZ, bool)\n", " for _ in range(3):\n", " coef, *_ = np.linalg.lstsq(A[keep], g.ravel()[keep], rcond=None)\n", " plane = (A @ coef).reshape(SZ, SZ)\n", " keep = (np.abs(g - plane) < 0.08 * np.maximum(plane, 1e-3)).ravel()\n", " if keep.sum() < 200:\n", " keep[:] = True\n", " c = g / np.maximum(plane, 1e-3)\n", " chroma = img[..., 0] / (img.sum(axis=2) + 1e-6)\n", " return c, chroma\n", "\n", "\n", "SEGP = {\"dark\": 0.7, \"rust\": 0.5, \"rim\": 1.45} # ๋ถ„ํ•  ์ž„๊ณ„๊ฐ’(์–ด๋‘์›€ ๋ณด์ • ๋ฐ๊ธฐ, ๋ถ‰์€ ๋น„์œจ, ๋ฐ์€ ํ…Œ๋‘๋ฆฌ): ์žฌ๋ณด์ • ๋Œ€์ƒ\n", "\n", "\n", "def segment(img):\n", " \"\"\"๊ฒฐํ•จ ํ›„๋ณด ๋งˆ์Šคํฌ: ์–ด๋‘์šด ์˜์—ญ(๊ท ์—ดยท๋ˆ„์œ ยทํŒŒ์†) + ๋ถ‰์€ ๊ฐˆ์ƒ‰ ์˜์—ญ(๋ถ€์‹) + ๋ฐ์€ ํ…Œ๋‘๋ฆฌ(ํŒŒ์†).\"\"\"\n", " c, chroma = correct(img)\n", " dark = c < SEGP[\"dark\"]\n", " rust = (chroma > SEGP[\"rust\"]) & (c > 0.45)\n", " rim = c > SEGP[\"rim\"]\n", " m = dark | rust | ndi.binary_dilation(rim, iterations=1) & ndi.binary_dilation(dark, iterations=3)\n", " m = ndi.binary_opening(m, structure=np.ones((2, 2))) if m.sum() > 12 else m\n", " return m, c, chroma, dark, rust, rim\n", "\n", "\n", "def extract(img):\n", " \"\"\"๋ถ„ํ•  ๊ฒฐ๊ณผ์™€ ์ƒ‰ยท๋ชจ์–‘์—์„œ 24๊ฐœ ํŠน์ง•์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m, c, chroma, dark, rust, rim = segment(img)\n", " lab, n = ndi.label(m)\n", " area = float(m.sum())\n", " if n:\n", " sizes = ndi.sum(m, lab, range(1, n + 1))\n", " k = int(np.argmax(sizes)) + 1\n", " sl = ndi.find_objects(lab)[k - 1]\n", " h, w = sl[0].stop - sl[0].start, sl[1].stop - sl[1].start\n", " big = float(sizes.max())\n", " elong, fill = max(h, w) / max(min(h, w), 1), big / (h * w)\n", " else:\n", " big, elong, fill = 0.0, 1.0, 0.0\n", " gy, gx = np.gradient(c)\n", " mag = np.hypot(gx, gy)\n", " inside = m.sum() > 0\n", " return {\"area_px\": area, \"area_frac\": area / SZ ** 2, \"n_comp\": float(n), \"big_px\": big, \"elong\": elong, \"fill\": fill,\n", " \"dark_frac\": float(dark.mean()), \"rust_frac\": float(rust.mean()), \"rim_frac\": float(rim.mean()),\n", " \"c_mean\": float(c.mean()), \"c_std\": float(c.std()), \"c_min\": float(c.min()), \"edge_mean\": float(mag.mean()), \"edge_p95\": float(np.quantile(mag, 0.95)),\n", " \"chroma_mean\": float(chroma.mean()), \"chroma_max\": float(chroma.max()),\n", " \"in_dark\": float(c[m].mean()) if inside else 1.0, \"in_chroma\": float(chroma[m].mean()) if inside else float(chroma.mean()),\n", " \"ring_contrast\": float(c[ndi.binary_dilation(m, iterations=3) & ~m].mean() - c[m].mean()) if inside and (~m).any() else 0.0,\n", " \"thin\": float(1 - fill), \"gain\": float(img.mean()), \"sat\": float((img.max(axis=2) - img.min(axis=2)).mean()),\n", " \"cy\": float(ndi.center_of_mass(m)[0] / SZ) if inside else 0.5, \"cx\": float(ndi.center_of_mass(m)[1] / SZ) if inside else 0.5}\n", "\n", "\n", "def feature_table(imgs):\n", " return pd.DataFrame([extract(im) for im in imgs])\n", "# ---- ํŒ์ • ํŒŒ์ดํ”„๋ผ์ธ: ๊ฒฐํ•จ ์œ ํ˜• ๋ถ„๋ฅ˜ + ์†์ƒ๋„ ํšŒ๊ท€(๋ถ„๋ฅ˜ ํ™•๋ฅ  ํฌํ•จ) -> ์ •๋น„ ๋“ฑ๊ธ‰ ----\n", "def fit_pipeline(meta, F):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜• ๋ถ„๋ฅ˜๊ธฐ์™€ ์†์ƒ๋„ ํšŒ๊ท€๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค. ํšŒ๊ท€๊ธฐ ์ž…๋ ฅ์˜ ๋ถ„๋ฅ˜ ํ™•๋ฅ ์€ ๊ต์ฐจ ์˜ˆ์ธก์œผ๋กœ ๋งŒ๋“ค์–ด ๋ˆ„์„ค์„ ๋ง‰๋Š”๋‹ค.\"\"\"\n", " clf = RandomForestClassifier(n_estimators=150, random_state=0, n_jobs=1)\n", " P = cross_val_predict(clf, F, meta[\"cls\"], cv=4, method=\"predict_proba\")\n", " clf.fit(F, meta[\"cls\"])\n", " reg = GradientBoostingRegressor(n_estimators=200, max_depth=3, learning_rate=0.06, random_state=0).fit(np.c_[F.values, P], meta[\"damage_idx\"])\n", " return {\"clf\": clf, \"reg\": reg, \"cols\": list(F.columns)}\n", "\n", "\n", "def predict_pipeline(pl, F):\n", " X = F[pl[\"cols\"]]\n", " P = pl[\"clf\"].predict_proba(X)\n", " idx = np.clip(pl[\"reg\"].predict(np.c_[X.values, P]), 0, 100)\n", " return pl[\"clf\"].classes_[P.argmax(axis=1)], P, idx, P.max(axis=1)\n", "\n", "mtr, itr, ktr = make_images(600, seed=1)\n", "mte, ite, kte = make_images(300, seed=2)\n", "Ftr, Fte = feature_table(itr), feature_table(ite)\n", "print(f\"[1] ํŒŒ์ƒ ํ”ผ์ฒ˜ {Ftr.shape[1]}๊ฐœ(๋ถ„ํ•  ๋ฉด์ ยท์„ฑ๋ถ„ ์ˆ˜ยท๋ชจ์–‘, ๋ช…์•”, ์ƒ‰, ๊ฐ€์žฅ์ž๋ฆฌ, ์œ„์น˜) / ํ•™์Šต {len(mtr)}์žฅ, ํ‰๊ฐ€ {len(mte)}์žฅ (1ํ™”์†Œ = 1.5mm ๊ฐ€์ •)\")\n", "\n", "# ---------------- 1) ๋ถ„ํ•  ์ •๋ฐ€๋„: ์ •๋‹ต ๋งˆ์Šคํฌ์™€์˜ IoU ----------------\n", "seg = [segment(im)[0] for im in ite]\n", "rows = []\n", "for c in CLASSES[1:]:\n", " ix = np.where(mte[\"cls\"].values == c)[0]\n", " iou = [(seg[i] & kte[i]).sum() / max((seg[i] | kte[i]).sum(), 1) for i in ix]\n", " dice = [2 * (seg[i] & kte[i]).sum() / max(seg[i].sum() + kte[i].sum(), 1) for i in ix]\n", " hit = [seg[i].sum() > 0 and (seg[i] & kte[i]).sum() > 0 for i in ix]\n", " rows.append((KO_CLS[c], len(ix), np.mean(iou), np.mean(dice), np.mean(hit)))\n", "sg = pd.DataFrame(rows, columns=[\"๊ฒฐํ•จ\", \"์žฅ์ˆ˜\", \"ํ‰๊ท  IoU\", \"ํ‰๊ท  Dice\", \"๊ฒ€์ถœ๋ฅ \"])\n", "print(\"[2] ๋ถ„ํ•  ์„ฑ๋Šฅ(ํ‰๊ฐ€ 300์žฅ)\\n\" + sg.round(2).to_string(index=False))\n", "ok0 = np.mean([seg[i].sum() == 0 or (seg[i].sum() < 12) for i in np.where(mte[\"cls\"].values == \"none\")[0]])\n", "print(f\" ๊ฒฐํ•จ ์—†๋Š” ์‚ฌ์ง„ {int((mte['cls'] == 'none').sum())}์žฅ ์ค‘ ๋ถ„ํ•  ๊ฒฐ๊ณผ๊ฐ€ ๊ฑฐ์˜ ๋น„์–ด ์žˆ๋Š”(12ํ™”์†Œ ๋ฏธ๋งŒ) ์‚ฌ์ง„ {ok0:.0%} (๋ฌดํ•ดํ•œ ์–ผ๋ฃฉยท์Šคํฌ๋ž˜์น˜ ์˜คํƒ ์–ต์ œ)\")\n", "\n", "# ---------------- 2) ๋ฉด์  ์ธก์ •๊ณผ ์œ ํ˜•๋ณ„ ๋ณด์ • ----------------\n", "pl = fit_pipeline(mtr, Ftr)\n", "cls_te, P_te, idx_te, conf_te = predict_pipeline(pl, Fte)\n", "a_true, a_raw = mte[\"area_frac\"].values, Fte[\"area_frac\"].values\n", "cal = {c: LinearRegression().fit(Ftr.loc[mtr[\"cls\"].values == c, [\"area_frac\"]], mtr.loc[mtr[\"cls\"].values == c, \"area_frac\"]) for c in CLASSES[1:]}\n", "a_cal = np.array([cal[c].predict(pd.DataFrame({\"area_frac\": [a]}))[0] if c != \"none\" else 0.0 for c, a in zip(cls_te, a_raw)])\n", "m = a_true > 0\n", "mp = lambda y, p: float(np.mean(np.abs(y - p) / y) * 100)\n", "print(f\"\\n[3] ๋ฉด์  ์ธก์ •(๊ฒฐํ•จ {int(m.sum())}์žฅ): ๋ถ„ํ•  ์›์ธก์ • MAPE {mp(a_true[m], a_raw[m]):.1f}% -> ์œ ํ˜•๋ณ„ ์„ ํ˜• ๋ณด์ • ํ›„ {mp(a_true[m], a_cal[m]):.1f}% (๋ถ„๋ฅ˜ ์ •ํ™•๋„ {(cls_te == mte['cls'].values).mean():.1%})\")\n", "print(f\" ๋ฉด์  ํ™˜์‚ฐ ์˜ˆ: ํ‰๊ท  ๊ฒฐํ•จ ๋ฉด์  {a_true[m].mean() * SZ ** 2 * 1.5 ** 2:.0f}mm2(์ •๋‹ต) / {a_cal[m].mean() * SZ ** 2 * 1.5 ** 2:.0f}mm2(๋ณด์ • ์ธก์ •)\")\n", "\n", "# ---------------- 3) ๊นŠ์ดยท์†์ƒ๋„ ๋ชจ๋ธ ----------------\n", "dm_tr = mtr[\"depth_mm\"].values > 0.2\n", "Xtr = np.c_[Ftr[pl[\"cols\"]].values, cross_val_predict(RandomForestClassifier(n_estimators=100, random_state=0, n_jobs=1), Ftr, mtr[\"cls\"], cv=3, method=\"predict_proba\")]\n", "dep = GradientBoostingRegressor(n_estimators=200, max_depth=3, learning_rate=0.06, random_state=0).fit(Xtr[dm_tr], mtr[\"depth_mm\"].values[dm_tr])\n", "d_pred = np.clip(dep.predict(np.c_[Fte[pl[\"cols\"]].values, P_te]), 0, None)\n", "dm = mte[\"depth_mm\"].values > 0.2\n", "print(f\"[4] ๊นŠ์ด ์ถ”์ •(๊นŠ์ด ์žˆ๋Š” ์œ ํ˜• {int(dm.sum())}์žฅ, ๋ˆ„์œ ๋Š” ๊นŠ์ด 0์œผ๋กœ ์ œ์™ธ): MAPE {mp(mte['depth_mm'].values[dm], d_pred[dm]):.1f}%, MAE {np.mean(np.abs(mte['depth_mm'].values[dm] - d_pred[dm])):.2f}mm\")\n", "print(\" ๊นŠ์ด๋Š” ์‚ฌ์ง„ ํ•œ ์žฅ์œผ๋กœ ์ง์ ‘ ๊ด€์ธก๋˜์ง€ ์•Š์•„ ๋ฉด์ ยท๋ช…์•”์—์„œ ๊ฐ„์ ‘ ์ถ”์ •ํ•˜๋ฏ€๋กœ ์†์ƒ๋„๋ณด๋‹ค ์˜ค์ฐจ๊ฐ€ ํฌ๋‹ค(์‹ค์ œ ์—…๋ฌด๋Š” ๊นŠ์ด ๊ฒŒ์ด์ง€ ๋“ฑ ํ˜„์žฅ ์ธก์ •์œผ๋กœ ํ™•์ธ).\")\n", "y = mte[\"damage_idx\"].values\n", "big = y >= 10\n", "mape_d, mae_d = mp(y[big], idx_te[big]), float(np.mean(np.abs(y - idx_te)))\n", "base = LinearRegression().fit(Ftr[[\"area_px\"]], mtr[\"damage_idx\"])\n", "b_pred = np.clip(base.predict(Fte[[\"area_px\"]]), 0, 100)\n", "print(f\"[5] ์†์ƒ๋„ ์ ์ˆ˜(0~100, ์ ์ˆ˜ 10 ์ด์ƒ {int(big.sum())}์žฅ): ๋ฉด์ ๋งŒ ์“ฐ๋Š” ๊ธฐ์ค€ MAPE {mp(y[big], b_pred[big]):.1f}% -> ์ œ์•ˆ ๋ชจ๋ธ MAPE {mape_d:.1f}% (๋ชฉํ‘œ 25% ์ดํ•˜: {'๋‹ฌ์„ฑ' if mape_d <= 25 else '๋ฏธ๋‹ฌ'}), MAE {mae_d:.2f}, R2 {r2_score(y, idx_te):.3f}\")\n", "g_true, g_pred = mte[\"grade\"].values, grade_of(idx_te)\n", "print(f\" ์ •๋น„ ๋“ฑ๊ธ‰ ์ผ์น˜์œจ {(g_true == g_pred).mean():.1%}, ํ•œ ๋‹จ๊ณ„ ์ด๋‚ด {(np.abs(g_true - g_pred) <= 1).mean():.1%}\")\n", "\n", "# ---------------- 4) ๋ณ€์ˆ˜๊ตฐ ์ œ๊ฑฐ ์‹คํ—˜ ----------------\n", "groups = {\"๋ถ„ํ•  ๋ฉด์ ยท๋ชจ์–‘\": [\"area_px\", \"area_frac\", \"n_comp\", \"big_px\", \"elong\", \"fill\", \"thin\", \"cy\", \"cx\"], \"๋ช…์•”(์–ด๋‘์šด ๋น„์œจยท๋Œ€๋น„)\": [\"dark_frac\", \"c_mean\", \"c_std\", \"c_min\", \"in_dark\", \"ring_contrast\"],\n", " \"์ƒ‰(๋ถ‰์€ ๊ฐˆ์ƒ‰)\": [\"rust_frac\", \"chroma_mean\", \"chroma_max\", \"in_chroma\", \"sat\"], \"๊ฐ€์žฅ์ž๋ฆฌยทํ…Œ๋‘๋ฆฌ\": [\"edge_mean\", \"edge_p95\", \"rim_frac\"], \"์กฐ๋ช…\": [\"gain\"]}\n", "full_cols = list(Ftr.columns)\n", "\n", "\n", "def run(cols):\n", " g = GradientBoostingRegressor(n_estimators=200, max_depth=3, learning_rate=0.06, random_state=0).fit(Ftr[cols], mtr[\"damage_idx\"])\n", " p = np.clip(g.predict(Fte[cols]), 0, 100)\n", " return mp(y[big], p[big])\n", "\n", "\n", "full = run(full_cols)\n", "ab = pd.DataFrame([(k, run([c for c in full_cols if c not in v])) for k, v in groups.items()], columns=[\"์ œ๊ฑฐํ•œ ๋ณ€์ˆ˜๊ตฐ\", \"MAPE(%)\"]).assign(์ฆ๊ฐ€=lambda x: x[\"MAPE(%)\"] - full).sort_values(\"์ฆ๊ฐ€\", ascending=False)\n", "print(f\"\\n[6] ๋ณ€์ˆ˜๊ตฐ ์ œ๊ฑฐ(๋ถ„๋ฅ˜ ํ™•๋ฅ  ์—†์ด 24๊ฐœ ํ”ผ์ฒ˜๋งŒ ์‚ฌ์šฉ, ์ „์ฒด MAPE {full:.1f}%)\\n\" + ab.round(2).to_string(index=False))\n", "print(f\" ํ•ด์„: ์ œ๊ฑฐ ์‹œ ์˜ค์ฐจ๊ฐ€ ๊ฐ€์žฅ ํฌ๊ฒŒ ๋Š˜์–ด๋‚œ ๋ณ€์ˆ˜๊ตฐ์€ '{ab.iloc[0]['์ œ๊ฑฐํ•œ ๋ณ€์ˆ˜๊ตฐ']}'(+{ab.iloc[0]['์ฆ๊ฐ€']:.1f}%p)์ด๋‹ค. ๋ณ€์ˆ˜๊ตฐ๋ผ๋ฆฌ ์ •๋ณด๊ฐ€ ๊ฒน์ณ ๋‹จ๋… ์ œ๊ฑฐ ํšจ๊ณผ๋Š” ์ž‘๊ฒŒ ๋‚˜ํƒ€๋‚  ์ˆ˜ ์žˆ๋‹ค.\")\n", "print(\" ํŒ์ •์€ ์ •๋น„ ์˜์‚ฌ๊ฒฐ์ • ๋ณด์กฐ์ด๋ฉฐ ์ตœ์ข… ๋“ฑ๊ธ‰์€ ๊ฒ€์‚ฌ๊ด€์ด ํ™•์ •ํ•œ๋‹ค(M7). ์‚ฌ์ง„ยท์žฅ๋น„๋Š” ๋ชจ๋‘ ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 3, figsize=(15, 4.3))\n", "ax[0].bar(range(len(sg)), sg[\"ํ‰๊ท  IoU\"]); ax[0].set_xticks(range(len(sg))); ax[0].set_xticklabels([\"crack\", \"corrosion\", \"damage\", \"leak\"]); ax[0].set_title(\"Segmentation IoU by defect type\")\n", "ax[1].scatter(a_true[m], a_raw[m], s=8, label=\"raw\"); ax[1].scatter(a_true[m], a_cal[m], s=8, label=\"calibrated\"); ax[1].plot([0, .2], [0, .2], \"k--\"); ax[1].legend(); ax[1].set_title(\"Defect area fraction: true vs measured\")\n", "ax[2].scatter(y, idx_te, s=8); ax[2].plot([0, 100], [0, 100], \"k--\"); ax[2].set_title(\"Damage index: true vs predicted\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 04 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ํŒŒ์ƒ ํ”ผ์ฒ˜ 24๊ฐœ(๋ถ„ํ•  ๋ฉด์ ยท์„ฑ๋ถ„ ์ˆ˜ยท๋ชจ์–‘, ๋ช…์•”, ์ƒ‰, ๊ฐ€์žฅ์ž๋ฆฌ, ์œ„์น˜) / ํ•™์Šต 600์žฅ, ํ‰๊ฐ€ 300์žฅ (1ํ™”์†Œ = 1.5mm ๊ฐ€์ •)\n", "[2] ๋ถ„ํ•  ์„ฑ๋Šฅ(ํ‰๊ฐ€ 300์žฅ)\n", "๊ฒฐํ•จ ์žฅ์ˆ˜ ํ‰๊ท  IoU ํ‰๊ท  Dice ๊ฒ€์ถœ๋ฅ \n", "๊ท ์—ด 48 0.89 0.94 1.0\n", "๋ถ€์‹ 77 0.74 0.84 1.0\n", "ํŒŒ์† 57 0.61 0.71 1.0\n", "๋ˆ„์œ  48 0.94 0.97 1.0\n", " ๊ฒฐํ•จ ์—†๋Š” ์‚ฌ์ง„ 70์žฅ ์ค‘ ๋ถ„ํ•  ๊ฒฐ๊ณผ๊ฐ€ ๊ฑฐ์˜ ๋น„์–ด ์žˆ๋Š”(12ํ™”์†Œ ๋ฏธ๋งŒ) ์‚ฌ์ง„ 100% (๋ฌดํ•ดํ•œ ์–ผ๋ฃฉยท์Šคํฌ๋ž˜์น˜ ์˜คํƒ ์–ต์ œ)\n", "\n", "[3] ๋ฉด์  ์ธก์ •(๊ฒฐํ•จ 230์žฅ): ๋ถ„ํ•  ์›์ธก์ • MAPE 20.8% -> ์œ ํ˜•๋ณ„ ์„ ํ˜• ๋ณด์ • ํ›„ 10.2% (๋ถ„๋ฅ˜ ์ •ํ™•๋„ 98.7%)\n", " ๋ฉด์  ํ™˜์‚ฐ ์˜ˆ: ํ‰๊ท  ๊ฒฐํ•จ ๋ฉด์  565mm2(์ •๋‹ต) / 548mm2(๋ณด์ • ์ธก์ •)\n", "[4] ๊นŠ์ด ์ถ”์ •(๊นŠ์ด ์žˆ๋Š” ์œ ํ˜• 181์žฅ, ๋ˆ„์œ ๋Š” ๊นŠ์ด 0์œผ๋กœ ์ œ์™ธ): MAPE 28.0%, MAE 0.40mm\n", " ๊นŠ์ด๋Š” ์‚ฌ์ง„ ํ•œ ์žฅ์œผ๋กœ ์ง์ ‘ ๊ด€์ธก๋˜์ง€ ์•Š์•„ ๋ฉด์ ยท๋ช…์•”์—์„œ ๊ฐ„์ ‘ ์ถ”์ •ํ•˜๋ฏ€๋กœ ์†์ƒ๋„๋ณด๋‹ค ์˜ค์ฐจ๊ฐ€ ํฌ๋‹ค(์‹ค์ œ ์—…๋ฌด๋Š” ๊นŠ์ด ๊ฒŒ์ด์ง€ ๋“ฑ ํ˜„์žฅ ์ธก์ •์œผ๋กœ ํ™•์ธ).\n", "[5] ์†์ƒ๋„ ์ ์ˆ˜(0~100, ์ ์ˆ˜ 10 ์ด์ƒ 220์žฅ): ๋ฉด์ ๋งŒ ์“ฐ๋Š” ๊ธฐ์ค€ MAPE 30.8% -> ์ œ์•ˆ ๋ชจ๋ธ MAPE 8.8% (๋ชฉํ‘œ 25% ์ดํ•˜: ๋‹ฌ์„ฑ), MAE 3.22, R2 0.946\n", " ์ •๋น„ ๋“ฑ๊ธ‰ ์ผ์น˜์œจ 93.3%, ํ•œ ๋‹จ๊ณ„ ์ด๋‚ด 99.7%\n", "\n", "[6] ๋ณ€์ˆ˜๊ตฐ ์ œ๊ฑฐ(๋ถ„๋ฅ˜ ํ™•๋ฅ  ์—†์ด 24๊ฐœ ํ”ผ์ฒ˜๋งŒ ์‚ฌ์šฉ, ์ „์ฒด MAPE 10.5%)\n", " ์ œ๊ฑฐํ•œ ๋ณ€์ˆ˜๊ตฐ MAPE(%) ์ฆ๊ฐ€\n", " ์ƒ‰(๋ถ‰์€ ๊ฐˆ์ƒ‰) 13.46 2.98\n", " ๋ถ„ํ•  ๋ฉด์ ยท๋ชจ์–‘ 11.45 0.97\n", "๋ช…์•”(์–ด๋‘์šด ๋น„์œจยท๋Œ€๋น„) 11.29 0.81\n", " ์กฐ๋ช… 10.67 0.19\n", " ๊ฐ€์žฅ์ž๋ฆฌยทํ…Œ๋‘๋ฆฌ 10.60 0.12\n", " ํ•ด์„: ์ œ๊ฑฐ ์‹œ ์˜ค์ฐจ๊ฐ€ ๊ฐ€์žฅ ํฌ๊ฒŒ ๋Š˜์–ด๋‚œ ๋ณ€์ˆ˜๊ตฐ์€ '์ƒ‰(๋ถ‰์€ ๊ฐˆ์ƒ‰)'(+3.0%p)์ด๋‹ค. ๋ณ€์ˆ˜๊ตฐ๋ผ๋ฆฌ ์ •๋ณด๊ฐ€ ๊ฒน์ณ ๋‹จ๋… ์ œ๊ฑฐ ํšจ๊ณผ๋Š” ์ž‘๊ฒŒ ๋‚˜ํƒ€๋‚  ์ˆ˜ ์žˆ๋‹ค.\n", " ํŒ์ •์€ ์ •๋น„ ์˜์‚ฌ๊ฒฐ์ • ๋ณด์กฐ์ด๋ฉฐ ์ตœ์ข… ๋“ฑ๊ธ‰์€ ๊ฒ€์‚ฌ๊ด€์ด ํ™•์ •ํ•œ๋‹ค(M7). ์‚ฌ์ง„ยท์žฅ๋น„๋Š” ๋ชจ๋‘ ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 05. ์ •๋น„ ๋“ฑ๊ธ‰(ํ˜„์žฅ์ •๋น„/์•ผ์ „์ •๋น„/์ฐฝ์ •๋น„) ์ž๋™ ๋ถ„๋ฅ˜ ๋ฐ ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ์‚ฐ์ถœ ์—”์ง„ ๊ฐœ๋ฐœ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ •๋น„ ๋“ฑ๊ธ‰(ํ˜„์žฅ/์•ผ์ „/์ฐฝ์ •๋น„) ์ž๋™ ๋ถ„๋ฅ˜ ๋ฐ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์†์ƒ๋„์™€ ๊ฒฐํ•จ ์œ ํ˜• ํ™•๋ฅ ๋กœ ์ •๋น„ ๋“ฑ๊ธ‰(ํ˜„์žฅ์ •๋น„ยท์•ผ์ „์ •๋น„ยท์ฐฝ์ •๋น„)์„ ์ž๋™ ๋ถ„๋ฅ˜ํ•˜๊ณ  ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜๋ฅผ ์‚ฐ์ถœํ•˜๋Š” ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋ถ„ํ•  ํŠน์ง•๊ณผ ์œ ํ˜• ํ™•๋ฅ ๋กœ ์†์ƒ๋„๋ฅผ ํšŒ๊ท€ํ•˜๊ณ  ๊ฒฝ๊ณ„๊ฐ’์œผ๋กœ ์ •๋น„ ๋“ฑ๊ธ‰์„ ์ •ํ•ด ๊ธฐ์ค€ ๋ชจ๋ธ(๋ฐ๊ธฐ ํžˆ์Šคํ† ๊ทธ๋žจ ํšŒ๊ท€)๊ณผ MAEยทRMSEยทR2๋ฅผ ๋น„๊ต ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/qlt03_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": [ "\"\"\"์†์ƒ๋„ ์ ์ˆ˜ ํšŒ๊ท€์™€ ๊ฒฐํ•จ ์œ ํ˜• ํ™•๋ฅ ๋กœ ์ •๋น„ ๋“ฑ๊ธ‰(ํ˜„์žฅ์ •๋น„ยท์•ผ์ „์ •๋น„ยท์ฐฝ์ •๋น„)์„ ์ž๋™ ๋ถ„๋ฅ˜ํ•˜๊ณ , ๋ถ„๋ฅ˜ ํ™•๋ฅ ยท๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌยท์‚ฌ์ง„ ํ’ˆ์งˆ๋กœ ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜(0~100)๋ฅผ ์‚ฐ์ถœํ•˜๋Š” ์—”์ง„์„ ๋งŒ๋“ค์–ด ๊ธฐ์ค€ ๋ชจ๋ธ ๋Œ€๋น„ MAEยทRMSE ๊ฐœ์„ ๊ณผ ์‹ ๋ขฐ๋„ ๋ณด์ •, ์ €์กฐ๋„ยท์ง„ํ™ ์‚ฌ์ง„ ๋“ฑ ๋ถˆ๊ทœ์น™ ์ƒํ™ฉ์—์„œ์˜ ์•ˆ์ •์„ฑ์„ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "from sklearn.ensemble import RandomForestClassifier, GradientBoostingRegressor\n", "from sklearn.linear_model import LinearRegression\n", "from sklearn.model_selection import cross_val_predict\n", "from sklearn.metrics import r2_score, confusion_matrix\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฅ๋น„ ์™ธ๊ด€ ์‚ฌ์ง„ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์œ„์น˜ ์‚ฌ์ง„์ด ์•„๋‹ˆ๋ผ ๋‚œ์ˆ˜๋กœ ๊ทธ๋ฆฐ 64x64 ํ•ฉ์„ฑ ์ด๋ฏธ์ง€)\n", "# ๋„์žฅ๋œ ๊ธˆ์† ํ‘œ๋ฉด์— ๊ท ์—ด(crack)ยท๋ถ€์‹(corrosion)ยทํŒŒ์†(damage)ยท๋ˆ„์œ (leak)๋ฅผ ๊ทธ๋ฆฌ๊ณ , ์กฐ๋ช… ํŽธ์ฐจ์™€ ์ง„ํ™(mud) ์˜ค์—ผ์„ ์„ž๋Š”๋‹ค.\n", "# ์ด๋ฏธ์ง€์™€ ํ•จ๊ป˜ ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ)๊ณผ ์ •๋‹ต ๊ฒฐํ•จ ๋งˆ์Šคํฌยท์†์ƒ๋„ยท์ •๋น„ ๋“ฑ๊ธ‰์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "from scipy import ndimage as ndi\n", "\n", "SZ = 64\n", "CLASSES = [\"none\", \"crack\", \"corrosion\", \"damage\", \"leak\"]\n", "KO_CLS = {\"none\": \"๊ฒฐํ•จ ์—†์Œ\", \"crack\": \"๊ท ์—ด\", \"corrosion\": \"๋ถ€์‹\", \"damage\": \"ํŒŒ์†\", \"leak\": \"๋ˆ„์œ \"}\n", "CLS_W = {\"none\": 0.0, \"crack\": 1.4, \"corrosion\": 1.0, \"damage\": 1.6, \"leak\": 0.6} # ์†์ƒ๋„ ๊ณ„์‚ฐ์šฉ ๊ฒฐํ•จ ์œ ํ˜• ๊ฐ€์ค‘์น˜\n", "GRADE_NAME = {1: \"ํ˜„์žฅ์ •๋น„\", 2: \"์•ผ์ „์ •๋น„\", 3: \"์ฐฝ์ •๋น„\"}\n", "YY, XX = np.mgrid[0:SZ, 0:SZ]\n", "\n", "\n", "def grade_of(idx):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] ์ •๋น„ ๋“ฑ๊ธ‰(ํ˜„์žฅ์ •๋น„/์•ผ์ „์ •๋น„/์ฐฝ์ •๋น„) ์ž๋™ ๋ถ„๋ฅ˜ ๋ฐ ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ์‚ฐ์ถœ ์—”์ง„ ๊ฐœ๋ฐœ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def blob(rng, cy, cx, r, rough=0.35):\n", " \"\"\"๋ถˆ๊ทœ์น™ํ•œ ์›ํ˜• ์˜์—ญ ๋งˆ์Šคํฌ.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] ์ •๋น„ ๋“ฑ๊ธ‰(ํ˜„์žฅ์ •๋น„/์•ผ์ „์ •๋น„/์ฐฝ์ •๋น„) ์ž๋™ ๋ถ„๋ฅ˜ ๋ฐ ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ์‚ฐ์ถœ ์—”์ง„ ๊ฐœ๋ฐœ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def draw(rng, cls, sev):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜•ยท์‹ฌ๊ฐ๋„ sev(0~1)์— ๋งž๋Š” ์ •๋‹ต ๋งˆ์Šคํฌ์™€ ์ƒ‰ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m = np.zeros((SZ, SZ), bool)\n", " if cls == \"crack\":\n", " y, x, ang = rng.uniform(12, 52), rng.uniform(12, 52), rng.uniform(0, np.pi)\n", " for _ in range(int(20 + 40 * sev)):\n", " ang += rng.normal(0, 0.35); y += np.sin(ang) * 1.2; x += np.cos(ang) * 1.2\n", " if 0 <= int(y) < SZ and 0 <= int(x) < SZ:\n", " m[int(y), int(x)] = True\n", " m = ndi.binary_dilation(m, iterations=1 + int(sev > 0.6))\n", " elif cls == \"corrosion\":\n", " for _ in range(int(rng.integers(1, 4))):\n", " m |= blob(rng, rng.uniform(14, 50), rng.uniform(14, 50), 3 + 9 * sev * rng.uniform(0.6, 1.1))\n", " elif cls == \"damage\":\n", " m = blob(rng, rng.uniform(20, 44), rng.uniform(20, 44), 6 + 12 * sev, 0.3)\n", " elif cls == \"leak\":\n", " x0, y0 = rng.uniform(16, 48), rng.uniform(8, 24)\n", " w, ln = 2 + 4 * sev, 12 + 26 * sev\n", " m = (np.abs(XX - x0 - 0.15 * (YY - y0)) < w * (1 - 0.4 * (YY - y0) / ln)) & (YY > y0) & (YY < y0 + ln)\n", " return m\n", "\n", "\n", "def make_images(n=600, seed=0, mud=0.0, p_none=0.25, dark=False):\n", " \"\"\"๊ฐ€์ƒ ์™ธ๊ด€ ์‚ฌ์ง„ n์žฅ(+์ง„ํ™ ์˜ค์—ผ mud ๋น„์œจ)๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋กยท์ •๋‹ต. ๋ฐ˜ํ™˜: meta(DataFrame), imgs(n,64,64,3), masks(n,64,64).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " imgs, masks, rows = np.zeros((n, SZ, SZ, 3), np.float32), np.zeros((n, SZ, SZ), bool), []\n", " for i in range(n):\n", " cls = \"none\" if rng.random() < p_none else str(rng.choice(CLASSES[1:], p=[0.25, 0.3, 0.25, 0.2]))\n", " sev = 0.0 if cls == \"none\" else float(rng.beta(1.6, 2.0))\n", " base = np.array([0.42, 0.46, 0.40]) * (1 + 0.5 * ndi.gaussian_filter(rng.normal(size=(SZ, SZ)), 8)[..., None] * 3)\n", " img = base + rng.normal(0, 0.012, (SZ, SZ, 3))\n", " m = draw(rng, cls, sev)\n", " a = ndi.gaussian_filter(m.astype(float), 0.8)[..., None] # ๊ฒฝ๊ณ„๋ฅผ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์„ž๋Š”๋‹ค\n", " if cls == \"crack\":\n", " col = img * rng.uniform(0.2, 0.45)\n", " elif cls == \"corrosion\":\n", " col = np.array([0.55, 0.30, 0.12]) * rng.uniform(0.75, 1.15) * (0.85 + 0.3 * rng.random((SZ, SZ, 1)))\n", " elif cls == \"damage\":\n", " col = np.ones_like(img) * np.array([0.34, 0.34, 0.36]) * (1.0 - 0.45 * sev) * rng.uniform(0.75, 1.0)\n", " rim = m & ~ndi.binary_erosion(m, iterations=2)\n", " a = np.maximum(a, 0.0); img = np.where(rim[..., None], 0.7, img)\n", " elif cls == \"leak\":\n", " col = np.ones_like(img) * np.array([0.10, 0.09, 0.08]) * rng.uniform(0.8, 2.6)\n", " else:\n", " col = img\n", " img = img * (1 - a) + col * a\n", " if rng.random() < 0.35: # ๋ฌดํ•ดํ•œ ์–ผ๋ฃฉ(๊ฒฐํ•จ ์•„๋‹˜): ์˜…์€ ๊ฐˆ์ƒ‰ ๋ฒˆ์ง\n", " st = blob(rng, rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(6, 14), 0.5)\n", " al = ndi.gaussian_filter(st.astype(float), 2.5)[..., None] * rng.uniform(0.15, 0.4)\n", " img = img * (1 - al) + np.array([0.50, 0.40, 0.30]) * al\n", " if rng.random() < 0.3: # ๋ฌดํ•ดํ•œ ๊ธด ์Šคํฌ๋ž˜์น˜(๋ฐ์€ ์„ )\n", " y0, x0, ang = rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(0, np.pi)\n", " for t in np.linspace(0, 25, 40):\n", " yy, xx = int(y0 + t * np.sin(ang)), int(x0 + t * np.cos(ang))\n", " if 0 <= yy < SZ and 0 <= xx < SZ:\n", " img[yy, xx] = np.clip(img[yy, xx] * 1.5, 0, 1)\n", " if mud > 0: # ์ง„ํ™ ์˜ค์—ผ: ๊ฒฐํ•จ ์œ„๋ฅผ ๋ฎ์„ ์ˆ˜ ์žˆ๋‹ค(์ •๋‹ต ๋งˆ์Šคํฌ๋Š” ๊ทธ๋Œ€๋กœ)\n", " mm = np.zeros((SZ, SZ), bool)\n", " while mm.mean() < mud:\n", " mm |= blob(rng, rng.uniform(0, SZ), rng.uniform(0, SZ), rng.uniform(5, 11), 0.5)\n", " img[mm] = np.array([0.38, 0.30, 0.22]) * (0.85 + 0.3 * rng.random((mm.sum(), 1)))\n", " gain = rng.uniform(0.3, 0.55) if dark else rng.uniform(0.5, 1.15)\n", " ang = rng.uniform(0, 2 * np.pi)\n", " light = gain * (1 + 0.3 * (np.cos(ang) * (XX - 32) + np.sin(ang) * (YY - 32)) / 32)\n", " imgs[i] = np.clip(img * light[..., None] + rng.normal(0, 0.025, (SZ, SZ, 3)), 0, 1)\n", " masks[i] = m\n", " area = float(m.mean())\n", " depth = {\"none\": 0.0, \"crack\": 0.3 + 3.0 * sev, \"corrosion\": 0.1 + 1.5 * sev, \"damage\": 1.0 + 8.0 * sev, \"leak\": 0.0}[cls] * rng.lognormal(0, 0.1)\n", " idx = float(np.clip(100 * (1 - np.exp(-(CLS_W[cls] * area * 9 + 0.05 * depth))) + rng.normal(0, 1.5), 0, 100))\n", " rows.append({\"img_id\": f\"IMG{seed:02d}{i:04d}\", \"cls\": cls, \"sev\": sev, \"area_frac\": area, \"depth_mm\": depth, \"damage_idx\": idx,\n", " \"grade\": int(grade_of(idx)), \"gain\": gain, \"mud\": mud})\n", " meta = pd.DataFrame(rows)\n", " # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ): ์‚ฌ์ง„ ํŒ์ •๊ณผ ๋Œ€์กฐํ•  ๊ฐ’\n", " age = rng.integers(1, 25, n)\n", " meta[\"day\"] = np.sort(rng.integers(0, 90, n))\n", " meta[\"equip_type\"] = rng.choice([\"vehicle\", \"generator\", \"comm\", \"trailer\"], n, p=[0.4, 0.2, 0.2, 0.2])\n", " meta[\"mfg_year\"] = 2026 - age\n", " meta[\"op_hours\"] = np.clip(rng.normal(180 * age, 400, n) + 8 * meta[\"damage_idx\"], 10, 20000)\n", " meta[\"env\"] = rng.choice([\"coastal\", \"inland\"], n, p=[0.3, 0.7])\n", " reg = np.clip(meta[\"grade\"] + rng.choice([-1, 0, 0, 0, 0, 0, 1, 2], n), 1, 3) # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก์ƒ ์ง์ „ ์ •๋น„ ๋“ฑ๊ธ‰\n", " meta[\"reg_grade\"] = reg\n", " meta[\"emergency\"] = rng.random(n) < 0.04 # ๊ธด๊ธ‰ ์ ๊ฒ€ ์š”์ฒญ(ํ™”์งˆ ๋ถˆ๋Ÿ‰์ด์–ด๋„ ๋ณด์กด)\n", " return meta, imgs, masks\n", "# ---- ์ „์ฒ˜๋ฆฌ(์กฐ๋ช… ๋ณด์ •ยทํ‘œ์ค€ํ™”)์™€ ๊ฒฐํ•จ ํ›„๋ณด ๋ถ„ํ• , ํŠน์ง• ์ถ”์ถœ (cv2 ๋Œ€์‹  numpyยทscipy ์‚ฌ์šฉ) ----\n", "def correct(img):\n", " \"\"\"์กฐ๋ช… ๋ณด์ •: ๋ฐ๊ธฐ์— ํ‰๋ฉด(์กฐ๋ช… ๊ธฐ์šธ๊ธฐ)์„ ์ ํ•ฉํ•ด ๋‚˜๋ˆˆ๋‹ค. ์–ด๋‘์šด ๊ฒฐํ•จ ํ™”์†Œ๋Š” ์ œ์™ธํ•˜๊ณ  ๋‘ ๋ฒˆ ๋ฐ˜๋ณต ์ ํ•ฉํ•œ๋‹ค. ๋ฐ˜ํ™˜: ๋ณด์ • ๋ฐ๊ธฐ(์ •์ƒ ๋ฉด ์•ฝ 1), ์ƒ‰ ๋น„์œจ.\"\"\"\n", " g = img.mean(axis=2)\n", " A = np.c_[np.ones(SZ * SZ), XX.ravel() / SZ, YY.ravel() / SZ]\n", " keep = np.ones(SZ * SZ, bool)\n", " for _ in range(3):\n", " coef, *_ = np.linalg.lstsq(A[keep], g.ravel()[keep], rcond=None)\n", " plane = (A @ coef).reshape(SZ, SZ)\n", " keep = (np.abs(g - plane) < 0.08 * np.maximum(plane, 1e-3)).ravel()\n", " if keep.sum() < 200:\n", " keep[:] = True\n", " c = g / np.maximum(plane, 1e-3)\n", " chroma = img[..., 0] / (img.sum(axis=2) + 1e-6)\n", " return c, chroma\n", "\n", "\n", "SEGP = {\"dark\": 0.7, \"rust\": 0.5, \"rim\": 1.45} # ๋ถ„ํ•  ์ž„๊ณ„๊ฐ’(์–ด๋‘์›€ ๋ณด์ • ๋ฐ๊ธฐ, ๋ถ‰์€ ๋น„์œจ, ๋ฐ์€ ํ…Œ๋‘๋ฆฌ): ์žฌ๋ณด์ • ๋Œ€์ƒ\n", "\n", "\n", "def segment(img):\n", " \"\"\"๊ฒฐํ•จ ํ›„๋ณด ๋งˆ์Šคํฌ: ์–ด๋‘์šด ์˜์—ญ(๊ท ์—ดยท๋ˆ„์œ ยทํŒŒ์†) + ๋ถ‰์€ ๊ฐˆ์ƒ‰ ์˜์—ญ(๋ถ€์‹) + ๋ฐ์€ ํ…Œ๋‘๋ฆฌ(ํŒŒ์†).\"\"\"\n", " c, chroma = correct(img)\n", " dark = c < SEGP[\"dark\"]\n", " rust = (chroma > SEGP[\"rust\"]) & (c > 0.45)\n", " rim = c > SEGP[\"rim\"]\n", " m = dark | rust | ndi.binary_dilation(rim, iterations=1) & ndi.binary_dilation(dark, iterations=3)\n", " m = ndi.binary_opening(m, structure=np.ones((2, 2))) if m.sum() > 12 else m\n", " return m, c, chroma, dark, rust, rim\n", "\n", "\n", "def extract(img):\n", " \"\"\"๋ถ„ํ•  ๊ฒฐ๊ณผ์™€ ์ƒ‰ยท๋ชจ์–‘์—์„œ 24๊ฐœ ํŠน์ง•์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m, c, chroma, dark, rust, rim = segment(img)\n", " lab, n = ndi.label(m)\n", " area = float(m.sum())\n", " if n:\n", " sizes = ndi.sum(m, lab, range(1, n + 1))\n", " k = int(np.argmax(sizes)) + 1\n", " sl = ndi.find_objects(lab)[k - 1]\n", " h, w = sl[0].stop - sl[0].start, sl[1].stop - sl[1].start\n", " big = float(sizes.max())\n", " elong, fill = max(h, w) / max(min(h, w), 1), big / (h * w)\n", " else:\n", " big, elong, fill = 0.0, 1.0, 0.0\n", " gy, gx = np.gradient(c)\n", " mag = np.hypot(gx, gy)\n", " inside = m.sum() > 0\n", " return {\"area_px\": area, \"area_frac\": area / SZ ** 2, \"n_comp\": float(n), \"big_px\": big, \"elong\": elong, \"fill\": fill,\n", " \"dark_frac\": float(dark.mean()), \"rust_frac\": float(rust.mean()), \"rim_frac\": float(rim.mean()),\n", " \"c_mean\": float(c.mean()), \"c_std\": float(c.std()), \"c_min\": float(c.min()), \"edge_mean\": float(mag.mean()), \"edge_p95\": float(np.quantile(mag, 0.95)),\n", " \"chroma_mean\": float(chroma.mean()), \"chroma_max\": float(chroma.max()),\n", " \"in_dark\": float(c[m].mean()) if inside else 1.0, \"in_chroma\": float(chroma[m].mean()) if inside else float(chroma.mean()),\n", " \"ring_contrast\": float(c[ndi.binary_dilation(m, iterations=3) & ~m].mean() - c[m].mean()) if inside and (~m).any() else 0.0,\n", " \"thin\": float(1 - fill), \"gain\": float(img.mean()), \"sat\": float((img.max(axis=2) - img.min(axis=2)).mean()),\n", " \"cy\": float(ndi.center_of_mass(m)[0] / SZ) if inside else 0.5, \"cx\": float(ndi.center_of_mass(m)[1] / SZ) if inside else 0.5}\n", "\n", "\n", "def feature_table(imgs):\n", " return pd.DataFrame([extract(im) for im in imgs])\n", "# ---- ํŒ์ • ํŒŒ์ดํ”„๋ผ์ธ: ๊ฒฐํ•จ ์œ ํ˜• ๋ถ„๋ฅ˜ + ์†์ƒ๋„ ํšŒ๊ท€(๋ถ„๋ฅ˜ ํ™•๋ฅ  ํฌํ•จ) -> ์ •๋น„ ๋“ฑ๊ธ‰ ----\n", "def fit_pipeline(meta, F):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜• ๋ถ„๋ฅ˜๊ธฐ์™€ ์†์ƒ๋„ ํšŒ๊ท€๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค. ํšŒ๊ท€๊ธฐ ์ž…๋ ฅ์˜ ๋ถ„๋ฅ˜ ํ™•๋ฅ ์€ ๊ต์ฐจ ์˜ˆ์ธก์œผ๋กœ ๋งŒ๋“ค์–ด ๋ˆ„์„ค์„ ๋ง‰๋Š”๋‹ค.\"\"\"\n", " clf = RandomForestClassifier(n_estimators=150, random_state=0, n_jobs=1)\n", " P = cross_val_predict(clf, F, meta[\"cls\"], cv=4, method=\"predict_proba\")\n", " clf.fit(F, meta[\"cls\"])\n", " reg = GradientBoostingRegressor(n_estimators=200, max_depth=3, learning_rate=0.06, random_state=0).fit(np.c_[F.values, P], meta[\"damage_idx\"])\n", " return {\"clf\": clf, \"reg\": reg, \"cols\": list(F.columns)}\n", "\n", "\n", "def predict_pipeline(pl, F):\n", " X = F[pl[\"cols\"]]\n", " P = pl[\"clf\"].predict_proba(X)\n", " idx = np.clip(pl[\"reg\"].predict(np.c_[X.values, P]), 0, 100)\n", " return pl[\"clf\"].classes_[P.argmax(axis=1)], P, idx, P.max(axis=1)\n", "# ---- ์‚ฌ์ง„ ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ: ์ง„ํ™ยท์˜ค์—ผ ๊ฐ€๋ฆผ ์ถ”์ •๊ณผ ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ----\n", "def mud_est(img):\n", " \"\"\"์ง„ํ™(๊ฐˆ์ƒ‰, ์ฑ„๋„ ๋‚ฎ์€ ๋ถ‰์€ ๊ธฐ) ํ™”์†Œ ๋น„์œจ์„ ์ถ”์ •ํ•œ๋‹ค. 3x3 ํ‰๊ท ์œผ๋กœ ์žก์Œ์„ ์ค„์ธ ๋’ค ์ƒ‰ ๋น„์œจ๋กœ ํŒ๋‹จํ•˜๋ฉฐ, ์‹ค์ œ ๊ฐ€๋ฆผ ๋น„์œจ๋ณด๋‹ค ์ž‘๊ฒŒ ๋‚˜์˜ค์ง€๋งŒ ์ˆœ์„œ๋Š” ์œ ์ง€๋œ๋‹ค.\"\"\"\n", " sm = ndi.uniform_filter(img, size=(3, 3, 1))\n", " s = sm.sum(axis=2) + 1e-6\n", " rr, gg = sm[..., 0] / s, sm[..., 1] / s\n", " return float(((rr > 0.385) & (rr < 0.46) & (gg < 0.325)).mean())\n", "\n", "\n", "def confidence(P, idx, F, mud):\n", " \"\"\"ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ 0~100 = 50 x ๋ถ„๋ฅ˜ ํ™•๋ฅ  ์ตœ๋Œ€๊ฐ’ + 35 x ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„๊นŒ์ง€ ๊ฑฐ๋ฆฌ(8์ ์—์„œ ํฌํ™”) + 15 x ์‚ฌ์ง„ ํ’ˆ์งˆ. ์˜ค์—ผ ์˜์‹ฌ(mud ์ถ”์ • 1% ์ด์ƒ)์ด๋ฉด ์ƒํ•œ 75๋กœ ์ œํ•œํ•œ๋‹ค.\"\"\"\n", " bd = np.minimum(np.abs(idx - 35), np.abs(idx - 65))\n", " qual = np.clip(1.0 - np.clip(0.55 - F[\"gain\"].values, 0, None) * 2 - np.clip(0.9 - F[\"c_mean\"].values, 0, None), 0, 1)\n", " c = 50 * P.max(axis=1) + 35 * np.clip(bd / 8, 0, 1) + 15 * qual\n", " return np.where(np.asarray(mud) >= 0.01, np.minimum(c, 75.0), c)\n", "\n", "mtr, itr, _ = make_images(600, seed=1)\n", "mte, ite, _ = make_images(300, seed=2)\n", "Ftr, Fte = feature_table(itr), feature_table(ite)\n", "pl = fit_pipeline(mtr, Ftr)\n", "\n", "\n", "def hist_feat(imgs):\n", " \"\"\"๊ธฐ์ค€ ๋ชจ๋ธ(M3) ํŠน์ง•: ๋ฐ๊ธฐ ํžˆ์Šคํ† ๊ทธ๋žจ 16์นธ + ์ฑ„๋„ ํ‰๊ท  ๋น„์œจ 3 + ๋ณ€๋™๊ณ„์ˆ˜ 1.\"\"\"\n", " out = []\n", " for im in imgs:\n", " g = im.mean(axis=2)\n", " h, _ = np.histogram(g / max(g.max(), 1e-3), bins=16, range=(0, 1))\n", " out.append(np.r_[h / g.size, im.mean(axis=(0, 1)) / max(g.mean(), 1e-3), g.std() / max(g.mean(), 1e-3)])\n", " return np.array(out)\n", "\n", "\n", "base = LinearRegression().fit(hist_feat(itr), mtr[\"damage_idx\"])\n", "rm = lambda y, p: float(np.sqrt(np.mean((np.asarray(y) - np.asarray(p)) ** 2)))\n", "\n", "\n", "# ---------------- 1) ํ‰๊ฐ€ 300์žฅ: ๊ธฐ์ค€ ๋ชจ๋ธ vs ์ œ์•ˆ ์—”์ง„ ----------------\n", "cls, P, idx, _ = predict_pipeline(pl, Fte)\n", "y = mte[\"damage_idx\"].values\n", "bp = np.clip(base.predict(hist_feat(ite)), 0, 100)\n", "tab = pd.DataFrame({\"๊ธฐ์ค€(๋ฐ๊ธฐ ํžˆ์Šคํ† ๊ทธ๋žจ ํšŒ๊ท€)\": [np.mean(np.abs(y - bp)), rm(y, bp), r2_score(y, bp)], \"์ œ์•ˆ(๋ถ„ํ•  ํŠน์ง•+์œ ํ˜• ํ™•๋ฅ )\": [np.mean(np.abs(y - idx)), rm(y, idx), r2_score(y, idx)]}, index=[\"MAE\", \"RMSE\", \"R2\"])\n", "print(\"[1] ์†์ƒ๋„ ์ ์ˆ˜ ์˜ˆ์ธก ์„ฑ๋Šฅ(ํ‰๊ฐ€ 300์žฅ)\\n\" + tab.round(3).to_string())\n", "b, e = tab.iloc[:, 0], tab.iloc[:, 1]\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", "# ---------------- 2) ์ •๋น„ ๋“ฑ๊ธ‰ ๋ถ„๋ฅ˜ ----------------\n", "g_true, g_pred = mte[\"grade\"].values, grade_of(idx)\n", "cm = pd.DataFrame(confusion_matrix(g_true, g_pred, labels=[1, 2, 3]), index=[f\"์‹ค์ œ {GRADE_NAME[i]}\" for i in (1, 2, 3)], columns=[f\"ํŒ์ • {GRADE_NAME[i]}\" for i in (1, 2, 3)])\n", "under = int(((g_pred < g_true)).sum())\n", "print(f\"\\n[2] ์ •๋น„ ๋“ฑ๊ธ‰ ํŒ์ •: ์ผ์น˜์œจ {(g_true == g_pred).mean():.1%}, ํ•œ ๋‹จ๊ณ„ ์ด๋‚ด {(np.abs(g_true - g_pred) <= 1).mean():.1%}, ๊ณผ์†Œ ํŒ์ •(์‹ค์ œ๋ณด๋‹ค ๋‚ฎ๊ฒŒ) {under}์žฅ, ๊ณผ๋Œ€ ํŒ์ • {int((g_pred > g_true).sum())}์žฅ\\n\" + cm.to_string())\n", "print(\" ์ฐฝ์ •๋น„๋ฅผ ์•ผ์ „ยทํ˜„์žฅ์œผ๋กœ ๋‚ฎ์ถ”๋Š” ๊ณผ์†Œ ํŒ์ •์ด ๊ฐ€์žฅ ์œ„ํ—˜ํ•˜๋ฏ€๋กœ ๊ฒฝ๊ณ„ ๋ถ€๊ทผ์€ ์‹ ๋ขฐ๋„๋กœ ๊ฑธ๋Ÿฌ ๊ฒ€์‚ฌ๊ด€์ด ํ™•์ธํ•œ๋‹ค.\")\n", "\n", "# ---------------- 3) ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ๋ณด์ • ----------------\n", "mud_te = np.array([mud_est(im) for im in ite])\n", "conf = confidence(P, idx, Fte, mud_te)\n", "bins = pd.cut(conf, [0, 70, 80, 90, 101], labels=[\"70 ๋ฏธ๋งŒ\", \"70~80\", \"80~90\", \"90 ์ด์ƒ\"])\n", "cal = pd.DataFrame({\"bin\": bins, \"ok\": g_true == g_pred, \"near\": np.abs(g_true - g_pred) <= 1}).groupby(\"bin\", observed=True).agg(๊ฑด์ˆ˜=(\"ok\", \"size\"), ์ผ์น˜์œจ=(\"ok\", \"mean\"), ํ•œ๋‹จ๊ณ„์ด๋‚ด=(\"near\", \"mean\"))\n", "print(\"\\n[3] ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ๊ตฌ๊ฐ„๋ณ„ ์‹ค์ œ ์ผ์น˜์œจ(๋†’์€ ์‹ ๋ขฐ๋„์ผ์ˆ˜๋ก ๋†’์•„์•ผ ํ•œ๋‹ค)\\n\" + cal.round(3).to_string())\n", "hi = conf >= 80\n", "print(f\" ์‹ ๋ขฐ๋„ 80 ์ด์ƒ ์ž๋™ ํ†ต๊ณผ {hi.sum()}์žฅ({hi.mean():.0%}): ์ผ์น˜์œจ {(g_true == g_pred)[hi].mean():.1%} / 80 ๋ฏธ๋งŒ {(~hi).sum()}์žฅ์€ ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ ๋Œ€์ƒ(์ผ์น˜์œจ {(g_true == g_pred)[~hi].mean():.1%})\")\n", "\n", "# ---------------- 4) ๋ถˆ๊ทœ์น™ ์ƒํ™ฉ: ์ €์กฐ๋„, ์ง„ํ™ ๊ฐ€๋ฆผ, ์ž‘์€ ๊ฒฐํ•จ ----------------\n", "rows = []\n", "for name, kw in [(\"์ •์ƒ ์กฐ๋ช…\", dict(seed=2)), (\"์ €์กฐ๋„\", dict(seed=7, dark=True)), (\"์ง„ํ™ 20% ๊ฐ€๋ฆผ\", dict(seed=8, mud=0.2)), (\"์ง„ํ™ 35% ๊ฐ€๋ฆผ\", dict(seed=9, mud=0.35))]:\n", " mm, ii = (mte, ite) if name == \"์ •์ƒ ์กฐ๋ช…\" else make_images(150, **kw)[:2]\n", " FF = Fte if name == \"์ •์ƒ ์กฐ๋ช…\" else feature_table(ii)\n", " c2, P2, i2, _ = predict_pipeline(pl, FF)\n", " cf = confidence(P2, i2, FF, [mud_est(im) for im in ii])\n", " yy = mm[\"damage_idx\"].values\n", " gg, gp = mm[\"grade\"].values, grade_of(i2)\n", " rows.append((name, len(mm), np.mean(np.abs(yy - np.clip(base.predict(hist_feat(ii)), 0, 100))), np.mean(np.abs(yy - i2)), (gg == gp).mean(), cf.mean(), (gg == gp)[cf >= 80].mean() if (cf >= 80).any() else np.nan, (cf >= 80).mean()))\n", "ir = pd.DataFrame(rows, columns=[\"์กฐ๊ฑด\", \"์žฅ์ˆ˜\", \"๊ธฐ์ค€ MAE\", \"์ œ์•ˆ MAE\", \"๋“ฑ๊ธ‰ ์ผ์น˜์œจ\", \"ํ‰๊ท  ์‹ ๋ขฐ๋„\", \"์‹ ๋ขฐ๋„ 80 ์ด์ƒ ์ผ์น˜์œจ\", \"80 ์ด์ƒ ๋น„์œจ\"])\n", "print(\"\\n[4] ๋ถˆ๊ทœ์น™ ์ƒํ™ฉ๋ณ„ ์„ฑ๋Šฅ(- ๋Š” ํ•ด๋‹น ์‚ฌ์ง„ ์—†์Œ)\\n\" + ir.round(2).to_string(index=False, na_rep=\"-\"))\n", "print(\" ์ง„ํ™ ๊ฐ€๋ฆผ์ด ์ปค์ง€๋ฉด ์˜ค์ฐจ์™€ ๊ณผ์†Œ ํŒ์ •์ด ๋Š˜๊ณ  ํ‰๊ท  ์‹ ๋ขฐ๋„๊ฐ€ ๋–จ์–ด์ง„๋‹ค. ์ด๋Ÿฐ ์‚ฌ์ง„์€ ์žฌ์ดฌ์˜ ์š”๊ตฌ ๋˜๋Š” ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ์œผ๋กœ ๋ณด๋‚ธ๋‹ค.\")\n", "print(\" ๋“ฑ๊ธ‰์€ ์ •๋น„ ์†Œ์š”์˜ 1์ฐจ ํŒ์ •์ด๋ฉฐ, ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰์€ ๊ฒ€์‚ฌ๊ด€์ด ํ™•์ •ํ•œ๋‹ค(M7). ์‚ฌ์ง„์€ ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋‹ค.\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 05 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ์†์ƒ๋„ ์ ์ˆ˜ ์˜ˆ์ธก ์„ฑ๋Šฅ(ํ‰๊ฐ€ 300์žฅ)\n", " ๊ธฐ์ค€(๋ฐ๊ธฐ ํžˆ์Šคํ† ๊ทธ๋žจ ํšŒ๊ท€) ์ œ์•ˆ(๋ถ„ํ•  ํŠน์ง•+์œ ํ˜• ํ™•๋ฅ )\n", "MAE 12.107 3.223\n", "RMSE 15.045 6.759\n", "R2 0.732 0.946\n", " MAE ๊ฐœ์„  73.4%, RMSE ๊ฐœ์„  55.1% (๋ชฉํ‘œ 20% ์ด์ƒ: ๋‹ฌ์„ฑ), R2 0.946 (๋ชฉํ‘œ 0.85 ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "\n", "[2] ์ •๋น„ ๋“ฑ๊ธ‰ ํŒ์ •: ์ผ์น˜์œจ 93.3%, ํ•œ ๋‹จ๊ณ„ ์ด๋‚ด 99.7%, ๊ณผ์†Œ ํŒ์ •(์‹ค์ œ๋ณด๋‹ค ๋‚ฎ๊ฒŒ) 16์žฅ, ๊ณผ๋Œ€ ํŒ์ • 4์žฅ\n", " ํŒ์ • ํ˜„์žฅ์ •๋น„ ํŒ์ • ์•ผ์ „์ •๋น„ ํŒ์ • ์ฐฝ์ •๋น„\n", "์‹ค์ œ ํ˜„์žฅ์ •๋น„ 182 2 0\n", "์‹ค์ œ ์•ผ์ „์ •๋น„ 11 47 2\n", "์‹ค์ œ ์ฐฝ์ •๋น„ 1 4 51\n", " ์ฐฝ์ •๋น„๋ฅผ ์•ผ์ „ยทํ˜„์žฅ์œผ๋กœ ๋‚ฎ์ถ”๋Š” ๊ณผ์†Œ ํŒ์ •์ด ๊ฐ€์žฅ ์œ„ํ—˜ํ•˜๋ฏ€๋กœ ๊ฒฝ๊ณ„ ๋ถ€๊ทผ์€ ์‹ ๋ขฐ๋„๋กœ ๊ฑธ๋Ÿฌ ๊ฒ€์‚ฌ๊ด€์ด ํ™•์ธํ•œ๋‹ค.\n", "\n", "[3] ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ๊ตฌ๊ฐ„๋ณ„ ์‹ค์ œ ์ผ์น˜์œจ(๋†’์€ ์‹ ๋ขฐ๋„์ผ์ˆ˜๋ก ๋†’์•„์•ผ ํ•œ๋‹ค)\n", " ๊ฑด์ˆ˜ ์ผ์น˜์œจ ํ•œ๋‹จ๊ณ„์ด๋‚ด\n", "bin \n", "70 ๋ฏธ๋งŒ 35 0.543 1.000\n", "70~80 31 0.871 0.968\n", "80~90 80 1.000 1.000\n", "90 ์ด์ƒ 154 1.000 1.000\n", " ์‹ ๋ขฐ๋„ 80 ์ด์ƒ ์ž๋™ ํ†ต๊ณผ 234์žฅ(78%): ์ผ์น˜์œจ 100.0% / 80 ๋ฏธ๋งŒ 66์žฅ์€ ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ ๋Œ€์ƒ(์ผ์น˜์œจ 69.7%)\n", "\n", "[4] ๋ถˆ๊ทœ์น™ ์ƒํ™ฉ๋ณ„ ์„ฑ๋Šฅ(- ๋Š” ํ•ด๋‹น ์‚ฌ์ง„ ์—†์Œ)\n", " ์กฐ๊ฑด ์žฅ์ˆ˜ ๊ธฐ์ค€ MAE ์ œ์•ˆ MAE ๋“ฑ๊ธ‰ ์ผ์น˜์œจ ํ‰๊ท  ์‹ ๋ขฐ๋„ ์‹ ๋ขฐ๋„ 80 ์ด์ƒ ์ผ์น˜์œจ 80 ์ด์ƒ ๋น„์œจ\n", " ์ •์ƒ ์กฐ๋ช… 300 12.11 3.22 0.93 86.19 1.0 0.78\n", " ์ €์กฐ๋„ 150 13.17 5.52 0.93 74.60 1.0 0.45\n", "์ง„ํ™ 20% ๊ฐ€๋ฆผ 150 53.72 23.89 0.45 61.91 - 0.00\n", "์ง„ํ™ 35% ๊ฐ€๋ฆผ 150 66.17 19.41 0.57 59.80 - 0.00\n", " ์ง„ํ™ ๊ฐ€๋ฆผ์ด ์ปค์ง€๋ฉด ์˜ค์ฐจ์™€ ๊ณผ์†Œ ํŒ์ •์ด ๋Š˜๊ณ  ํ‰๊ท  ์‹ ๋ขฐ๋„๊ฐ€ ๋–จ์–ด์ง„๋‹ค. ์ด๋Ÿฐ ์‚ฌ์ง„์€ ์žฌ์ดฌ์˜ ์š”๊ตฌ ๋˜๋Š” ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ์œผ๋กœ ๋ณด๋‚ธ๋‹ค.\n", " ๋“ฑ๊ธ‰์€ ์ •๋น„ ์†Œ์š”์˜ 1์ฐจ ํŒ์ •์ด๋ฉฐ, ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰์€ ๊ฒ€์‚ฌ๊ด€์ด ํ™•์ •ํ•œ๋‹ค(M7). ์‚ฌ์ง„์€ ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 06. ์žฅ๋น„ ๊ฒฐํ•จ ์‚ฌ์ง„ ์—…๋กœ๋“œ ๋ฐ ์†์ƒ ๋ถ€์œ„ ์‹œ๊ฐํ™” ํŒ์ • ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์žฅ๋น„ ๊ฒฐํ•จ ์‚ฌ์ง„ ์—…๋กœ๋“œ ๋ฐ ์†์ƒ ๋ถ€์œ„ ์‹œ๊ฐํ™” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์žฅ๋น„ ๊ฒฐํ•จ ์‚ฌ์ง„์„ ์—…๋กœ๋“œํ•˜๋ฉด ์†์ƒ ๋ถ€์œ„๋ฅผ ์‹œ๊ฐํ™”ํ•ด ํŒ์ •๊ณผ ๊ทผ๊ฑฐ๋ฅผ ๋ณด์—ฌ ์ฃผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์—…๋กœ๋“œ ์‚ฌ์ง„ ํ•œ ์žฅ์—์„œ ๊ฒฐํ•จ ์œ ํ˜•, ๋ฉด์ (mm2), ๊นŠ์ด, ์†์ƒ๋„, ์ •๋น„ ๋“ฑ๊ธ‰, ์‹ ๋ขฐ๋„์™€ ์†์ƒ ๋ถ€์œ„ ์œค๊ณฝยท๋ฐ•์Šค๋ฅผ ๊ณ„์‚ฐํ•ด ํ™”๋ฉด์— ๊ฒน์ณ ํ‘œ์‹œ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/explainability/qlt03_xai_explainer.py ๋ฐ XAI ๋ฆฌํฌํŠธ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ์žฅ๊ต/๊ตฐ๋ฌด์›์ด ์‹ ๋ขฐํ•˜๊ณ  ์กฐ์น˜ํ•  ์ˆ˜ ์žˆ๋Š” ์„ค๋ช…์˜ ๋ช…ํ™•์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์ฃผ์š” ์˜ˆ์ธก ๊ฑด๋‹น ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ธ์ž ์ž๋™ ์‚ฐ์ถœ๋ฅ  100%\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: SHAP summary plot ๋ฐ waterfall plot ์ƒ์„ฑ ํŒŒ์ดํ”„๋ผ์ธ ์™„์„ฑ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ์‹ค๋ฌด์ž ๋Œ€์ƒ ์„ค๋ช… ๊ทผ๊ฑฐ ์œ ์šฉ์„ฑ ํ‰๊ฐ€ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์žฅ๋น„ ๊ฒฐํ•จ ์‚ฌ์ง„์„ ์—…๋กœ๋“œํ•˜๋ฉด ๊ฒฐํ•จ ์œ ํ˜•ยท๋ฉด์ ยท๊นŠ์ดยท์†์ƒ๋„ยท์ •๋น„ ๋“ฑ๊ธ‰ยท์‹ ๋ขฐ๋„์™€ ์†์ƒ ๋ถ€์œ„(๋ฐ•์Šคยท์œค๊ณฝ)๋ฅผ ์‹œ๊ฐํ™”ํ•ด ๋ณด์—ฌ ์ฃผ๋Š” ํŒ์ • ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ , ํŒ์ • ๊ฑด๋งˆ๋‹ค ๊ทผ๊ฑฐ ๋ณ€์ˆ˜๊ตฐ ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ๋ฅผ ์ž๋™ ์‚ฐ์ถœํ•ด ์ž์—ฐ์–ด ์„ค๋ช…์œผ๋กœ ์ œ๊ณตํ•œ๋‹ค(์ž๋™ ํ™•์ • ์—†์ด ๊ฒ€์‚ฌ ๋Œ€๊ธฐ๋กœ ๋“ฑ๋ก).\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.ensemble import RandomForestClassifier, GradientBoostingRegressor\n", "from sklearn.model_selection import cross_val_predict\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฅ๋น„ ์™ธ๊ด€ ์‚ฌ์ง„ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์œ„์น˜ ์‚ฌ์ง„์ด ์•„๋‹ˆ๋ผ ๋‚œ์ˆ˜๋กœ ๊ทธ๋ฆฐ 64x64 ํ•ฉ์„ฑ ์ด๋ฏธ์ง€)\n", "# ๋„์žฅ๋œ ๊ธˆ์† ํ‘œ๋ฉด์— ๊ท ์—ด(crack)ยท๋ถ€์‹(corrosion)ยทํŒŒ์†(damage)ยท๋ˆ„์œ (leak)๋ฅผ ๊ทธ๋ฆฌ๊ณ , ์กฐ๋ช… ํŽธ์ฐจ์™€ ์ง„ํ™(mud) ์˜ค์—ผ์„ ์„ž๋Š”๋‹ค.\n", "# ์ด๋ฏธ์ง€์™€ ํ•จ๊ป˜ ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ)๊ณผ ์ •๋‹ต ๊ฒฐํ•จ ๋งˆ์Šคํฌยท์†์ƒ๋„ยท์ •๋น„ ๋“ฑ๊ธ‰์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "from scipy import ndimage as ndi\n", "\n", "SZ = 64\n", "CLASSES = [\"none\", \"crack\", \"corrosion\", \"damage\", \"leak\"]\n", "KO_CLS = {\"none\": \"๊ฒฐํ•จ ์—†์Œ\", \"crack\": \"๊ท ์—ด\", \"corrosion\": \"๋ถ€์‹\", \"damage\": \"ํŒŒ์†\", \"leak\": \"๋ˆ„์œ \"}\n", "CLS_W = {\"none\": 0.0, \"crack\": 1.4, \"corrosion\": 1.0, \"damage\": 1.6, \"leak\": 0.6} # ์†์ƒ๋„ ๊ณ„์‚ฐ์šฉ ๊ฒฐํ•จ ์œ ํ˜• ๊ฐ€์ค‘์น˜\n", "GRADE_NAME = {1: \"ํ˜„์žฅ์ •๋น„\", 2: \"์•ผ์ „์ •๋น„\", 3: \"์ฐฝ์ •๋น„\"}\n", "YY, XX = np.mgrid[0:SZ, 0:SZ]\n", "\n", "\n", "def grade_of(idx):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ์žฅ๋น„ ๊ฒฐํ•จ ์‚ฌ์ง„ ์—…๋กœ๋“œ ๋ฐ ์†์ƒ ๋ถ€์œ„ ์‹œ๊ฐํ™” ํŒ์ • ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def blob(rng, cy, cx, r, rough=0.35):\n", " \"\"\"๋ถˆ๊ทœ์น™ํ•œ ์›ํ˜• ์˜์—ญ ๋งˆ์Šคํฌ.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ์žฅ๋น„ ๊ฒฐํ•จ ์‚ฌ์ง„ ์—…๋กœ๋“œ ๋ฐ ์†์ƒ ๋ถ€์œ„ ์‹œ๊ฐํ™” ํŒ์ • ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def draw(rng, cls, sev):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜•ยท์‹ฌ๊ฐ๋„ sev(0~1)์— ๋งž๋Š” ์ •๋‹ต ๋งˆ์Šคํฌ์™€ ์ƒ‰ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m = np.zeros((SZ, SZ), bool)\n", " if cls == \"crack\":\n", " y, x, ang = rng.uniform(12, 52), rng.uniform(12, 52), rng.uniform(0, np.pi)\n", " for _ in range(int(20 + 40 * sev)):\n", " ang += rng.normal(0, 0.35); y += np.sin(ang) * 1.2; x += np.cos(ang) * 1.2\n", " if 0 <= int(y) < SZ and 0 <= int(x) < SZ:\n", " m[int(y), int(x)] = True\n", " m = ndi.binary_dilation(m, iterations=1 + int(sev > 0.6))\n", " elif cls == \"corrosion\":\n", " for _ in range(int(rng.integers(1, 4))):\n", " m |= blob(rng, rng.uniform(14, 50), rng.uniform(14, 50), 3 + 9 * sev * rng.uniform(0.6, 1.1))\n", " elif cls == \"damage\":\n", " m = blob(rng, rng.uniform(20, 44), rng.uniform(20, 44), 6 + 12 * sev, 0.3)\n", " elif cls == \"leak\":\n", " x0, y0 = rng.uniform(16, 48), rng.uniform(8, 24)\n", " w, ln = 2 + 4 * sev, 12 + 26 * sev\n", " m = (np.abs(XX - x0 - 0.15 * (YY - y0)) < w * (1 - 0.4 * (YY - y0) / ln)) & (YY > y0) & (YY < y0 + ln)\n", " return m\n", "\n", "\n", "def make_images(n=600, seed=0, mud=0.0, p_none=0.25, dark=False):\n", " \"\"\"๊ฐ€์ƒ ์™ธ๊ด€ ์‚ฌ์ง„ n์žฅ(+์ง„ํ™ ์˜ค์—ผ mud ๋น„์œจ)๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋กยท์ •๋‹ต. ๋ฐ˜ํ™˜: meta(DataFrame), imgs(n,64,64,3), masks(n,64,64).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " imgs, masks, rows = np.zeros((n, SZ, SZ, 3), np.float32), np.zeros((n, SZ, SZ), bool), []\n", " for i in range(n):\n", " cls = \"none\" if rng.random() < p_none else str(rng.choice(CLASSES[1:], p=[0.25, 0.3, 0.25, 0.2]))\n", " sev = 0.0 if cls == \"none\" else float(rng.beta(1.6, 2.0))\n", " base = np.array([0.42, 0.46, 0.40]) * (1 + 0.5 * ndi.gaussian_filter(rng.normal(size=(SZ, SZ)), 8)[..., None] * 3)\n", " img = base + rng.normal(0, 0.012, (SZ, SZ, 3))\n", " m = draw(rng, cls, sev)\n", " a = ndi.gaussian_filter(m.astype(float), 0.8)[..., None] # ๊ฒฝ๊ณ„๋ฅผ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์„ž๋Š”๋‹ค\n", " if cls == \"crack\":\n", " col = img * rng.uniform(0.2, 0.45)\n", " elif cls == \"corrosion\":\n", " col = np.array([0.55, 0.30, 0.12]) * rng.uniform(0.75, 1.15) * (0.85 + 0.3 * rng.random((SZ, SZ, 1)))\n", " elif cls == \"damage\":\n", " col = np.ones_like(img) * np.array([0.34, 0.34, 0.36]) * (1.0 - 0.45 * sev) * rng.uniform(0.75, 1.0)\n", " rim = m & ~ndi.binary_erosion(m, iterations=2)\n", " a = np.maximum(a, 0.0); img = np.where(rim[..., None], 0.7, img)\n", " elif cls == \"leak\":\n", " col = np.ones_like(img) * np.array([0.10, 0.09, 0.08]) * rng.uniform(0.8, 2.6)\n", " else:\n", " col = img\n", " img = img * (1 - a) + col * a\n", " if rng.random() < 0.35: # ๋ฌดํ•ดํ•œ ์–ผ๋ฃฉ(๊ฒฐํ•จ ์•„๋‹˜): ์˜…์€ ๊ฐˆ์ƒ‰ ๋ฒˆ์ง\n", " st = blob(rng, rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(6, 14), 0.5)\n", " al = ndi.gaussian_filter(st.astype(float), 2.5)[..., None] * rng.uniform(0.15, 0.4)\n", " img = img * (1 - al) + np.array([0.50, 0.40, 0.30]) * al\n", " if rng.random() < 0.3: # ๋ฌดํ•ดํ•œ ๊ธด ์Šคํฌ๋ž˜์น˜(๋ฐ์€ ์„ )\n", " y0, x0, ang = rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(0, np.pi)\n", " for t in np.linspace(0, 25, 40):\n", " yy, xx = int(y0 + t * np.sin(ang)), int(x0 + t * np.cos(ang))\n", " if 0 <= yy < SZ and 0 <= xx < SZ:\n", " img[yy, xx] = np.clip(img[yy, xx] * 1.5, 0, 1)\n", " if mud > 0: # ์ง„ํ™ ์˜ค์—ผ: ๊ฒฐํ•จ ์œ„๋ฅผ ๋ฎ์„ ์ˆ˜ ์žˆ๋‹ค(์ •๋‹ต ๋งˆ์Šคํฌ๋Š” ๊ทธ๋Œ€๋กœ)\n", " mm = np.zeros((SZ, SZ), bool)\n", " while mm.mean() < mud:\n", " mm |= blob(rng, rng.uniform(0, SZ), rng.uniform(0, SZ), rng.uniform(5, 11), 0.5)\n", " img[mm] = np.array([0.38, 0.30, 0.22]) * (0.85 + 0.3 * rng.random((mm.sum(), 1)))\n", " gain = rng.uniform(0.3, 0.55) if dark else rng.uniform(0.5, 1.15)\n", " ang = rng.uniform(0, 2 * np.pi)\n", " light = gain * (1 + 0.3 * (np.cos(ang) * (XX - 32) + np.sin(ang) * (YY - 32)) / 32)\n", " imgs[i] = np.clip(img * light[..., None] + rng.normal(0, 0.025, (SZ, SZ, 3)), 0, 1)\n", " masks[i] = m\n", " area = float(m.mean())\n", " depth = {\"none\": 0.0, \"crack\": 0.3 + 3.0 * sev, \"corrosion\": 0.1 + 1.5 * sev, \"damage\": 1.0 + 8.0 * sev, \"leak\": 0.0}[cls] * rng.lognormal(0, 0.1)\n", " idx = float(np.clip(100 * (1 - np.exp(-(CLS_W[cls] * area * 9 + 0.05 * depth))) + rng.normal(0, 1.5), 0, 100))\n", " rows.append({\"img_id\": f\"IMG{seed:02d}{i:04d}\", \"cls\": cls, \"sev\": sev, \"area_frac\": area, \"depth_mm\": depth, \"damage_idx\": idx,\n", " \"grade\": int(grade_of(idx)), \"gain\": gain, \"mud\": mud})\n", " meta = pd.DataFrame(rows)\n", " # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ): ์‚ฌ์ง„ ํŒ์ •๊ณผ ๋Œ€์กฐํ•  ๊ฐ’\n", " age = rng.integers(1, 25, n)\n", " meta[\"day\"] = np.sort(rng.integers(0, 90, n))\n", " meta[\"equip_type\"] = rng.choice([\"vehicle\", \"generator\", \"comm\", \"trailer\"], n, p=[0.4, 0.2, 0.2, 0.2])\n", " meta[\"mfg_year\"] = 2026 - age\n", " meta[\"op_hours\"] = np.clip(rng.normal(180 * age, 400, n) + 8 * meta[\"damage_idx\"], 10, 20000)\n", " meta[\"env\"] = rng.choice([\"coastal\", \"inland\"], n, p=[0.3, 0.7])\n", " reg = np.clip(meta[\"grade\"] + rng.choice([-1, 0, 0, 0, 0, 0, 1, 2], n), 1, 3) # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก์ƒ ์ง์ „ ์ •๋น„ ๋“ฑ๊ธ‰\n", " meta[\"reg_grade\"] = reg\n", " meta[\"emergency\"] = rng.random(n) < 0.04 # ๊ธด๊ธ‰ ์ ๊ฒ€ ์š”์ฒญ(ํ™”์งˆ ๋ถˆ๋Ÿ‰์ด์–ด๋„ ๋ณด์กด)\n", " return meta, imgs, masks\n", "# ---- ์ „์ฒ˜๋ฆฌ(์กฐ๋ช… ๋ณด์ •ยทํ‘œ์ค€ํ™”)์™€ ๊ฒฐํ•จ ํ›„๋ณด ๋ถ„ํ• , ํŠน์ง• ์ถ”์ถœ (cv2 ๋Œ€์‹  numpyยทscipy ์‚ฌ์šฉ) ----\n", "def correct(img):\n", " \"\"\"์กฐ๋ช… ๋ณด์ •: ๋ฐ๊ธฐ์— ํ‰๋ฉด(์กฐ๋ช… ๊ธฐ์šธ๊ธฐ)์„ ์ ํ•ฉํ•ด ๋‚˜๋ˆˆ๋‹ค. ์–ด๋‘์šด ๊ฒฐํ•จ ํ™”์†Œ๋Š” ์ œ์™ธํ•˜๊ณ  ๋‘ ๋ฒˆ ๋ฐ˜๋ณต ์ ํ•ฉํ•œ๋‹ค. ๋ฐ˜ํ™˜: ๋ณด์ • ๋ฐ๊ธฐ(์ •์ƒ ๋ฉด ์•ฝ 1), ์ƒ‰ ๋น„์œจ.\"\"\"\n", " g = img.mean(axis=2)\n", " A = np.c_[np.ones(SZ * SZ), XX.ravel() / SZ, YY.ravel() / SZ]\n", " keep = np.ones(SZ * SZ, bool)\n", " for _ in range(3):\n", " coef, *_ = np.linalg.lstsq(A[keep], g.ravel()[keep], rcond=None)\n", " plane = (A @ coef).reshape(SZ, SZ)\n", " keep = (np.abs(g - plane) < 0.08 * np.maximum(plane, 1e-3)).ravel()\n", " if keep.sum() < 200:\n", " keep[:] = True\n", " c = g / np.maximum(plane, 1e-3)\n", " chroma = img[..., 0] / (img.sum(axis=2) + 1e-6)\n", " return c, chroma\n", "\n", "\n", "SEGP = {\"dark\": 0.7, \"rust\": 0.5, \"rim\": 1.45} # ๋ถ„ํ•  ์ž„๊ณ„๊ฐ’(์–ด๋‘์›€ ๋ณด์ • ๋ฐ๊ธฐ, ๋ถ‰์€ ๋น„์œจ, ๋ฐ์€ ํ…Œ๋‘๋ฆฌ): ์žฌ๋ณด์ • ๋Œ€์ƒ\n", "\n", "\n", "def segment(img):\n", " \"\"\"๊ฒฐํ•จ ํ›„๋ณด ๋งˆ์Šคํฌ: ์–ด๋‘์šด ์˜์—ญ(๊ท ์—ดยท๋ˆ„์œ ยทํŒŒ์†) + ๋ถ‰์€ ๊ฐˆ์ƒ‰ ์˜์—ญ(๋ถ€์‹) + ๋ฐ์€ ํ…Œ๋‘๋ฆฌ(ํŒŒ์†).\"\"\"\n", " c, chroma = correct(img)\n", " dark = c < SEGP[\"dark\"]\n", " rust = (chroma > SEGP[\"rust\"]) & (c > 0.45)\n", " rim = c > SEGP[\"rim\"]\n", " m = dark | rust | ndi.binary_dilation(rim, iterations=1) & ndi.binary_dilation(dark, iterations=3)\n", " m = ndi.binary_opening(m, structure=np.ones((2, 2))) if m.sum() > 12 else m\n", " return m, c, chroma, dark, rust, rim\n", "\n", "\n", "def extract(img):\n", " \"\"\"๋ถ„ํ•  ๊ฒฐ๊ณผ์™€ ์ƒ‰ยท๋ชจ์–‘์—์„œ 24๊ฐœ ํŠน์ง•์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m, c, chroma, dark, rust, rim = segment(img)\n", " lab, n = ndi.label(m)\n", " area = float(m.sum())\n", " if n:\n", " sizes = ndi.sum(m, lab, range(1, n + 1))\n", " k = int(np.argmax(sizes)) + 1\n", " sl = ndi.find_objects(lab)[k - 1]\n", " h, w = sl[0].stop - sl[0].start, sl[1].stop - sl[1].start\n", " big = float(sizes.max())\n", " elong, fill = max(h, w) / max(min(h, w), 1), big / (h * w)\n", " else:\n", " big, elong, fill = 0.0, 1.0, 0.0\n", " gy, gx = np.gradient(c)\n", " mag = np.hypot(gx, gy)\n", " inside = m.sum() > 0\n", " return {\"area_px\": area, \"area_frac\": area / SZ ** 2, \"n_comp\": float(n), \"big_px\": big, \"elong\": elong, \"fill\": fill,\n", " \"dark_frac\": float(dark.mean()), \"rust_frac\": float(rust.mean()), \"rim_frac\": float(rim.mean()),\n", " \"c_mean\": float(c.mean()), \"c_std\": float(c.std()), \"c_min\": float(c.min()), \"edge_mean\": float(mag.mean()), \"edge_p95\": float(np.quantile(mag, 0.95)),\n", " \"chroma_mean\": float(chroma.mean()), \"chroma_max\": float(chroma.max()),\n", " \"in_dark\": float(c[m].mean()) if inside else 1.0, \"in_chroma\": float(chroma[m].mean()) if inside else float(chroma.mean()),\n", " \"ring_contrast\": float(c[ndi.binary_dilation(m, iterations=3) & ~m].mean() - c[m].mean()) if inside and (~m).any() else 0.0,\n", " \"thin\": float(1 - fill), \"gain\": float(img.mean()), \"sat\": float((img.max(axis=2) - img.min(axis=2)).mean()),\n", " \"cy\": float(ndi.center_of_mass(m)[0] / SZ) if inside else 0.5, \"cx\": float(ndi.center_of_mass(m)[1] / SZ) if inside else 0.5}\n", "\n", "\n", "def feature_table(imgs):\n", " return pd.DataFrame([extract(im) for im in imgs])\n", "# ---- ํŒ์ • ํŒŒ์ดํ”„๋ผ์ธ: ๊ฒฐํ•จ ์œ ํ˜• ๋ถ„๋ฅ˜ + ์†์ƒ๋„ ํšŒ๊ท€(๋ถ„๋ฅ˜ ํ™•๋ฅ  ํฌํ•จ) -> ์ •๋น„ ๋“ฑ๊ธ‰ ----\n", "def fit_pipeline(meta, F):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜• ๋ถ„๋ฅ˜๊ธฐ์™€ ์†์ƒ๋„ ํšŒ๊ท€๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค. ํšŒ๊ท€๊ธฐ ์ž…๋ ฅ์˜ ๋ถ„๋ฅ˜ ํ™•๋ฅ ์€ ๊ต์ฐจ ์˜ˆ์ธก์œผ๋กœ ๋งŒ๋“ค์–ด ๋ˆ„์„ค์„ ๋ง‰๋Š”๋‹ค.\"\"\"\n", " clf = RandomForestClassifier(n_estimators=150, random_state=0, n_jobs=1)\n", " P = cross_val_predict(clf, F, meta[\"cls\"], cv=4, method=\"predict_proba\")\n", " clf.fit(F, meta[\"cls\"])\n", " reg = GradientBoostingRegressor(n_estimators=200, max_depth=3, learning_rate=0.06, random_state=0).fit(np.c_[F.values, P], meta[\"damage_idx\"])\n", " return {\"clf\": clf, \"reg\": reg, \"cols\": list(F.columns)}\n", "\n", "\n", "def predict_pipeline(pl, F):\n", " X = F[pl[\"cols\"]]\n", " P = pl[\"clf\"].predict_proba(X)\n", " idx = np.clip(pl[\"reg\"].predict(np.c_[X.values, P]), 0, 100)\n", " return pl[\"clf\"].classes_[P.argmax(axis=1)], P, idx, P.max(axis=1)\n", "# ---- ์‚ฌ์ง„ ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ: ์ง„ํ™ยท์˜ค์—ผ ๊ฐ€๋ฆผ ์ถ”์ •๊ณผ ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ----\n", "def mud_est(img):\n", " \"\"\"์ง„ํ™(๊ฐˆ์ƒ‰, ์ฑ„๋„ ๋‚ฎ์€ ๋ถ‰์€ ๊ธฐ) ํ™”์†Œ ๋น„์œจ์„ ์ถ”์ •ํ•œ๋‹ค. 3x3 ํ‰๊ท ์œผ๋กœ ์žก์Œ์„ ์ค„์ธ ๋’ค ์ƒ‰ ๋น„์œจ๋กœ ํŒ๋‹จํ•˜๋ฉฐ, ์‹ค์ œ ๊ฐ€๋ฆผ ๋น„์œจ๋ณด๋‹ค ์ž‘๊ฒŒ ๋‚˜์˜ค์ง€๋งŒ ์ˆœ์„œ๋Š” ์œ ์ง€๋œ๋‹ค.\"\"\"\n", " sm = ndi.uniform_filter(img, size=(3, 3, 1))\n", " s = sm.sum(axis=2) + 1e-6\n", " rr, gg = sm[..., 0] / s, sm[..., 1] / s\n", " return float(((rr > 0.385) & (rr < 0.46) & (gg < 0.325)).mean())\n", "\n", "\n", "def confidence(P, idx, F, mud):\n", " \"\"\"ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ 0~100 = 50 x ๋ถ„๋ฅ˜ ํ™•๋ฅ  ์ตœ๋Œ€๊ฐ’ + 35 x ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„๊นŒ์ง€ ๊ฑฐ๋ฆฌ(8์ ์—์„œ ํฌํ™”) + 15 x ์‚ฌ์ง„ ํ’ˆ์งˆ. ์˜ค์—ผ ์˜์‹ฌ(mud ์ถ”์ • 1% ์ด์ƒ)์ด๋ฉด ์ƒํ•œ 75๋กœ ์ œํ•œํ•œ๋‹ค.\"\"\"\n", " bd = np.minimum(np.abs(idx - 35), np.abs(idx - 65))\n", " qual = np.clip(1.0 - np.clip(0.55 - F[\"gain\"].values, 0, None) * 2 - np.clip(0.9 - F[\"c_mean\"].values, 0, None), 0, 1)\n", " c = 50 * P.max(axis=1) + 35 * np.clip(bd / 8, 0, 1) + 15 * qual\n", " return np.where(np.asarray(mud) >= 0.01, np.minimum(c, 75.0), c)\n", "\n", "mtr, itr, _ = make_images(600, seed=1)\n", "Ftr = feature_table(itr)\n", "pl = fit_pipeline(mtr, Ftr)\n", "reg_p = GradientBoostingRegressor(n_estimators=150, max_depth=3, learning_rate=0.07, random_state=0)\n", "Xd = np.c_[Ftr[pl[\"cols\"]].values, cross_val_predict(RandomForestClassifier(n_estimators=80, random_state=0, n_jobs=1), Ftr, mtr[\"cls\"], cv=3, method=\"predict_proba\")]\n", "dm = mtr[\"depth_mm\"].values > 0.2\n", "depth_model = reg_p.fit(Xd[dm], mtr[\"depth_mm\"].values[dm])\n", "MM2 = 1.5 ** 2 # 1ํ™”์†Œ = 1.5mm ๊ฐ€์ • -> ๋ฉด์  mm2\n", "GROUPS = {\"๊ฒฐํ•จ ๋ฉด์ ยท๋ชจ์–‘\": [\"area_px\", \"area_frac\", \"n_comp\", \"big_px\", \"elong\", \"fill\", \"thin\", \"cy\", \"cx\"], \"๋ช…์•” ๋Œ€๋น„\": [\"dark_frac\", \"c_mean\", \"c_std\", \"c_min\", \"in_dark\", \"ring_contrast\"],\n", " \"๋ถ‰์€ ๊ฐˆ์ƒ‰ ์ƒ‰์ƒ\": [\"rust_frac\", \"chroma_mean\", \"chroma_max\", \"in_chroma\", \"sat\"], \"๊ฐ€์žฅ์ž๋ฆฌยทํ…Œ๋‘๋ฆฌ\": [\"edge_mean\", \"edge_p95\", \"rim_frac\"], \"์กฐ๋ช… ๋ฐ๊ธฐ\": [\"gain\"]}\n", "MEAN = Ftr.mean()\n", "\n", "\n", "def contrib(F):\n", " \"\"\"๋ณ€์ˆ˜๊ตฐ์„ ํ‰๊ท ๊ฐ’์œผ๋กœ ๋Œ€์ฒดํ–ˆ์„ ๋•Œ ์†์ƒ๋„ ์ ์ˆ˜ ๋ณ€ํ™”(์–‘์ˆ˜ = ๊ทธ ๋ณ€์ˆ˜๊ตฐ์ด ์†์ƒ๋„๋ฅผ ๋†’์ธ ์š”์ธ). ์‚ฌ์ง„ ์—ฌ๋Ÿฌ ์žฅ์„ ํ•œ ๋ฒˆ์— ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " full = predict_pipeline(pl, F)[2]\n", " out = {}\n", " for g, cols in GROUPS.items():\n", " x = F.copy(); x[cols] = MEAN[cols].values\n", " out[g] = full - predict_pipeline(pl, x)[2]\n", " return pd.DataFrame(out, index=F.index)\n", "\n", "\n", "def top3(F):\n", " return contrib(F).apply(lambda r: r.reindex(r.abs().sort_values(ascending=False).index)[:3].to_dict(), axis=1)\n", "\n", "\n", "def inspect(img, pid):\n", " \"\"\"์—…๋กœ๋“œ ์‚ฌ์ง„ ํ•œ ์žฅ์„ ํŒ์ •ํ•œ๋‹ค. ๊ฒฐ๊ณผ๋Š” ํ•ญ์ƒ '๊ฒ€์‚ฌ ๋Œ€๊ธฐ'์ด๋ฉฐ ์ž๋™ ํ™•์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.\"\"\"\n", " F = feature_table([img])\n", " cls, P, idx, _ = predict_pipeline(pl, F)\n", " mud = mud_est(img)\n", " conf = float(confidence(P, idx, F, [mud])[0])\n", " m = segment(img)[0]\n", " lab, n = ndi.label(m)\n", " box = None\n", " if n:\n", " k = int(np.argmax(ndi.sum(m, lab, range(1, n + 1)))) + 1\n", " sl = ndi.find_objects(lab)[k - 1]\n", " box = (sl[1].start, sl[0].start, sl[1].stop - sl[1].start, sl[0].stop - sl[0].start)\n", " dep = float(np.clip(depth_model.predict(np.c_[F[pl[\"cols\"]].values, P])[0], 0, None)) if cls[0] in (\"crack\", \"corrosion\", \"damage\") else 0.0\n", " if mud >= 0.03 or F[\"gain\"].iloc[0] < 0.17:\n", " state = \"์žฌ์ดฌ์˜ ์š”๊ตฌ(์˜ค์—ผยท์ €์กฐ๋„)\"\n", " elif conf >= 80:\n", " state = \"๊ฒ€์‚ฌ ๋Œ€๊ธฐ(์‹ ๋ขฐ๋„ ๋†’์Œ, ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ ํ›„ ํ™•์ •)\"\n", " else:\n", " state = \"๊ฒ€์‚ฌ ๋Œ€๊ธฐ(์‹ ๋ขฐ๋„ ๋‚ฎ์Œ, ๊ฒ€์‚ฌ๊ด€ ์ •๋ฐ€ ํ™•์ธ)\"\n", " return {\"id\": pid, \"cls\": cls[0], \"area_mm2\": float(F[\"area_px\"].iloc[0] * MM2), \"depth\": dep, \"idx\": float(idx[0]), \"grade\": int(grade_of(idx[0])), \"conf\": conf, \"mud\": mud, \"box\": box, \"mask\": m, \"state\": state, \"F\": F}\n", "\n", "\n", "# ---------------- 1) ์—…๋กœ๋“œ ์‹œ๋ฎฌ๋ ˆ์ด์…˜: ์กฐ๊ฑด์ด ๋‹ค๋ฅธ ์‚ฌ์ง„ 8์žฅ ----------------\n", "mt, it, kt = make_images(300, seed=2)\n", "mud_m, imud, kmud = make_images(30, seed=11, mud=0.2)\n", "dark_m, idk, kdk = make_images(30, seed=12, dark=True)\n", "picks = [(\"U01\", it[next(i for i in range(300) if mt.cls[i] == \"crack\")], kt, mt, \"crack\"), (\"U02\", it[next(i for i in range(300) if mt.cls[i] == \"corrosion\")], kt, mt, \"corrosion\"),\n", " (\"U03\", it[next(i for i in range(300) if mt.cls[i] == \"damage\")], kt, mt, \"damage\"), (\"U04\", it[next(i for i in range(300) if mt.cls[i] == \"leak\")], kt, mt, \"leak\"),\n", " (\"U05\", it[next(i for i in range(300) if mt.cls[i] == \"none\")], kt, mt, \"none\"), (\"U06\", idk[0], kdk, dark_m, None), (\"U07\", imud[0], kmud, mud_m, None), (\"U08\", imud[1], kmud, mud_m, None)]\n", "res = []\n", "for pid, img, _, _, _ in picks:\n", " res.append(inspect(img, pid))\n", "true_rows = [mt.loc[next(i for i in range(300) if mt.cls[i] == c)] for c in (\"crack\", \"corrosion\", \"damage\", \"leak\", \"none\")] + [dark_m.loc[0], mud_m.loc[0], mud_m.loc[1]]\n", "tb = pd.DataFrame([{\"์‚ฌ์ง„\": r[\"id\"], \"ํŒ์ • ์œ ํ˜•\": KO_CLS[r[\"cls\"]], \"์‹ค์ œ ์œ ํ˜•\": KO_CLS[t[\"cls\"]], \"๋ฉด์ (mm2)\": r[\"area_mm2\"], \"๊นŠ์ด(mm)\": r[\"depth\"], \"์†์ƒ๋„\": r[\"idx\"], \"์‹ค์ œ ์†์ƒ๋„\": t[\"damage_idx\"],\n", " \"๋“ฑ๊ธ‰\": GRADE_NAME[r[\"grade\"]], \"์‹ค์ œ ๋“ฑ๊ธ‰\": GRADE_NAME[int(t[\"grade\"])], \"์‹ ๋ขฐ๋„\": r[\"conf\"], \"์˜ค์—ผ\": r[\"mud\"]} for r, t in zip(res, true_rows)])\n", "print(\"[1] ์—…๋กœ๋“œ ํŒ์ • ๊ฒฐ๊ณผ(8์žฅ, ๋ฉด์ ์€ 1ํ™”์†Œ 1.5mm ๊ฐ€์ •) [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\")\n", "print(tb.round(2).to_string(index=False))\n", "for r in res:\n", " print(f\" {r['id']}: {r['state']}\")\n", "\n", "# ---------------- 2) ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ž๋™ ์‚ฐ์ถœ(ํ‰๊ฐ€ 150์žฅ) ----------------\n", "mte, ite, _ = make_images(300, seed=2)\n", "Fte = feature_table(ite)\n", "T3 = top3(Fte)\n", "ok = int(sum(len(t) == 3 and all(np.isfinite(v) for v in t.values()) for t in T3))\n", "texts = list(T3)\n", "print(f\"\\n[2] ํ‰๊ฐ€ {len(ite)}์žฅ ์ค‘ ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์š”์ธ ์ž๋™ ์‚ฐ์ถœ {ok}๊ฑด ({ok / len(ite):.0%}, ๋ชฉํ‘œ 100%)\")\n", "avg = pd.DataFrame(texts).abs().mean().sort_values(ascending=False)\n", "print(\" ๋ณ€์ˆ˜๊ตฐ๋ณ„ ํ‰๊ท  ๊ธฐ์—ฌ ํฌ๊ธฐ(์†์ƒ๋„ ์ ):\", \", \".join(f\"{k} {v:.1f}\" for k, v in avg.items()))\n", "\n", "\n", "def explain(r):\n", " t3 = top3(r[\"F\"]).iloc[0]\n", " why = {\"๊ฒฐํ•จ ๋ฉด์ ยท๋ชจ์–‘\": (\"๊ฒฐํ•จ ๋ฉด์ ยท๋ชจ์–‘์ด ํ‰๊ท ๋ณด๋‹ค ์ปค์„œ\", \"๊ฒฐํ•จ ๋ฉด์ ยท๋ชจ์–‘์ด ํ‰๊ท ๋ณด๋‹ค ์ž‘์•„\"), \"๋ช…์•” ๋Œ€๋น„\": (\"ํ‘œ๋ฉด๊ณผ ํฌ๊ฒŒ ๋‹ค๋ฅธ ์–ด๋‘์šด ์˜์—ญ์ด ์žˆ์–ด\", \"์–ด๋‘์šด ์˜์—ญ์ด ์ ์–ด\"),\n", " \"๋ถ‰์€ ๊ฐˆ์ƒ‰ ์ƒ‰์ƒ\": (\"๋ถ‰์€ ๊ฐˆ์ƒ‰ ๋ถ€์‹ ์ƒ‰์ƒ์ด ๋„“์–ด\", \"๋ถ‰์€ ๊ฐˆ์ƒ‰ ์ƒ‰์ƒ์ด ์ ์–ด\"), \"๊ฐ€์žฅ์ž๋ฆฌยทํ…Œ๋‘๋ฆฌ\": (\"๋šœ๋ ทํ•œ ํ…Œ๋‘๋ฆฌ๊ฐ€ ์žˆ์–ด\", \"ํ…Œ๋‘๋ฆฌ ๋‹จ์„œ๊ฐ€ ์•ฝํ•ด\"), \"์กฐ๋ช… ๋ฐ๊ธฐ\": (\"์กฐ๋ช… ๋ฐ๊ธฐ ์˜ํ–ฅ์œผ๋กœ\", \"์กฐ๋ช… ๋ฐ๊ธฐ ์˜ํ–ฅ์œผ๋กœ\")}\n", " main = list(t3)[0]\n", " return f\"{r['id']}: {why[main][t3[main] < 0]} ์†์ƒ๋„ {r['idx']:.0f}์ ({GRADE_NAME[r['grade']]}) ํŒ์ •. ์ฃผ์š” ์š”์ธ \" + \", \".join(f\"{g}({v:+.1f}์ )\" for g, v in t3.items())\n", "\n", "\n", "print(\"[3] ํŒ์ • ๊ทผ๊ฑฐ ์ž์—ฐ์–ด ์„ค๋ช…(๊ฒฐํ•จ ์žˆ๋Š” 3์žฅ)\")\n", "for r in (res[0], res[1], res[2]):\n", " print(\" -\", explain(r))\n", "\n", "# ---------------- 3) ์†์ƒ ๋ถ€์œ„ ์‹œ๊ฐํ™” ์ •ํ™•๋„: ๋ฐ•์Šค์™€ ์ •๋‹ต ๋งˆ์Šคํฌ ๊ฒน์นจ ----------------\n", "ious = []\n", "for r, (pid, img, kk, mm, _) in zip(res[:4], picks[:4]):\n", " i = next(j for j in range(300) if mt.cls[j] == {\"U01\": \"crack\", \"U02\": \"corrosion\", \"U03\": \"damage\", \"U04\": \"leak\"}[pid])\n", " gt = kk[i]\n", " ious.append((pid, (r[\"mask\"] & gt).sum() / max((r[\"mask\"] | gt).sum(), 1)))\n", "print(\"[4] ์†์ƒ ๋ถ€์œ„ ํ‘œ์‹œ์™€ ์ •๋‹ต ๋งˆ์Šคํฌ์˜ IoU:\", \", \".join(f\"{p} {v:.2f}\" for p, v in ious))\n", "print(\" ํ™”๋ฉด์€ ์ž๋™ ํ™•์ • ๋ฒ„ํŠผ ์—†์ด '๊ฒ€์‚ฌ ๋Œ€๊ธฐ ๋“ฑ๋ก'๋งŒ ์ œ๊ณตํ•˜๋ฉฐ, ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰์€ ๊ฒ€์‚ฌ๊ด€์ด ํ™•์ •ํ•œ๋‹ค(M7).\")\n", "\n", "fig, ax = plt.subplots(2, 4, figsize=(14, 7))\n", "for a, (pid, img, _, _, _), r in zip(ax.ravel(), picks, res):\n", " a.imshow(img)\n", " a.contour(r[\"mask\"], levels=[0.5], colors=\"yellow\", linewidths=1)\n", " if r[\"box\"]:\n", " x, y, w, h = r[\"box\"]; a.add_patch(plt.Rectangle((x - .5, y - .5), w, h, fill=False, ec=\"red\", lw=1.5))\n", " a.set_title(f\"{pid} {r['cls']} idx {r['idx']:.0f} grade {r['grade']} conf {r['conf']:.0f}\", fontsize=9); a.axis(\"off\")\n", "plt.suptitle(\"Upload inspection view (synthetic photos): yellow = defect region, red = box\"); plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 06 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ์—…๋กœ๋“œ ํŒ์ • ๊ฒฐ๊ณผ(8์žฅ, ๋ฉด์ ์€ 1ํ™”์†Œ 1.5mm ๊ฐ€์ •) [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\n", " ์‚ฌ์ง„ ํŒ์ • ์œ ํ˜• ์‹ค์ œ ์œ ํ˜• ๋ฉด์ (mm2) ๊นŠ์ด(mm) ์†์ƒ๋„ ์‹ค์ œ ์†์ƒ๋„ ๋“ฑ๊ธ‰ ์‹ค์ œ ๋“ฑ๊ธ‰ ์‹ ๋ขฐ๋„ ์˜ค์—ผ\n", "U01 ๊ท ์—ด ๊ท ์—ด 121.50 1.67 19.39 25.53 ํ˜„์žฅ์ •๋น„ ํ˜„์žฅ์ •๋น„ 91.72 0.00\n", "U02 ๋ถ€์‹ ๋ถ€์‹ 171.00 0.44 24.12 24.49 ํ˜„์žฅ์ •๋น„ ํ˜„์žฅ์ •๋น„ 93.58 0.00\n", "U03 ํŒŒ์† ํŒŒ์† 857.25 4.91 82.32 85.72 ์ฐฝ์ •๋น„ ์ฐฝ์ •๋น„ 96.30 0.00\n", "U04 ๋ˆ„์œ  ๋ˆ„์œ  265.50 0.00 14.31 14.60 ํ˜„์žฅ์ •๋น„ ํ˜„์žฅ์ •๋น„ 88.68 0.00\n", "U05 ๊ฒฐํ•จ ์—†์Œ ๊ฒฐํ•จ ์—†์Œ 0.00 0.00 1.17 0.00 ํ˜„์žฅ์ •๋น„ ํ˜„์žฅ์ •๋น„ 91.61 0.00\n", "U06 ๋ˆ„์œ  ๋ˆ„์œ  231.75 0.00 15.84 10.12 ํ˜„์žฅ์ •๋น„ ํ˜„์žฅ์ •๋น„ 86.90 0.00\n", "U07 ํŒŒ์† ๊ฒฐํ•จ ์—†์Œ 103.50 3.21 70.75 1.96 ์ฐฝ์ •๋น„ ํ˜„์žฅ์ •๋น„ 64.69 0.02\n", "U08 ํŒŒ์† ํŒŒ์† 339.75 4.17 57.16 66.53 ์•ผ์ „์ •๋น„ ์ฐฝ์ •๋น„ 72.90 0.02\n", " U01: ๊ฒ€์‚ฌ ๋Œ€๊ธฐ(์‹ ๋ขฐ๋„ ๋†’์Œ, ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ ํ›„ ํ™•์ •)\n", " U02: ๊ฒ€์‚ฌ ๋Œ€๊ธฐ(์‹ ๋ขฐ๋„ ๋†’์Œ, ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ ํ›„ ํ™•์ •)\n", " U03: ๊ฒ€์‚ฌ ๋Œ€๊ธฐ(์‹ ๋ขฐ๋„ ๋†’์Œ, ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ ํ›„ ํ™•์ •)\n", " U04: ๊ฒ€์‚ฌ ๋Œ€๊ธฐ(์‹ ๋ขฐ๋„ ๋†’์Œ, ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ ํ›„ ํ™•์ •)\n", " U05: ๊ฒ€์‚ฌ ๋Œ€๊ธฐ(์‹ ๋ขฐ๋„ ๋†’์Œ, ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ ํ›„ ํ™•์ •)\n", " U06: ์žฌ์ดฌ์˜ ์š”๊ตฌ(์˜ค์—ผยท์ €์กฐ๋„)\n", " U07: ๊ฒ€์‚ฌ ๋Œ€๊ธฐ(์‹ ๋ขฐ๋„ ๋‚ฎ์Œ, ๊ฒ€์‚ฌ๊ด€ ์ •๋ฐ€ ํ™•์ธ)\n", " U08: ๊ฒ€์‚ฌ ๋Œ€๊ธฐ(์‹ ๋ขฐ๋„ ๋‚ฎ์Œ, ๊ฒ€์‚ฌ๊ด€ ์ •๋ฐ€ ํ™•์ธ)\n", "\n", "[2] ํ‰๊ฐ€ 300์žฅ ์ค‘ ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์š”์ธ ์ž๋™ ์‚ฐ์ถœ 300๊ฑด (100%, ๋ชฉํ‘œ 100%)\n", " ๋ณ€์ˆ˜๊ตฐ๋ณ„ ํ‰๊ท  ๊ธฐ์—ฌ ํฌ๊ธฐ(์†์ƒ๋„ ์ ): ๊ฒฐํ•จ ๋ฉด์ ยท๋ชจ์–‘ 25.1, ๋ถ‰์€ ๊ฐˆ์ƒ‰ ์ƒ‰์ƒ 6.3, ๋ช…์•” ๋Œ€๋น„ 5.3, ์กฐ๋ช… ๋ฐ๊ธฐ 2.6, ๊ฐ€์žฅ์ž๋ฆฌยทํ…Œ๋‘๋ฆฌ 1.0\n", "[3] ํŒ์ • ๊ทผ๊ฑฐ ์ž์—ฐ์–ด ์„ค๋ช…(๊ฒฐํ•จ ์žˆ๋Š” 3์žฅ)\n", " - U01: ๊ฒฐํ•จ ๋ฉด์ ยท๋ชจ์–‘์ด ํ‰๊ท ๋ณด๋‹ค ์ž‘์•„ ์†์ƒ๋„ 19์ (ํ˜„์žฅ์ •๋น„) ํŒ์ •. ์ฃผ์š” ์š”์ธ ๊ฒฐํ•จ ๋ฉด์ ยท๋ชจ์–‘(-18.2์ ), ๋ช…์•” ๋Œ€๋น„(-5.0์ ), ๋ถ‰์€ ๊ฐˆ์ƒ‰ ์ƒ‰์ƒ(-1.0์ )\n", " - U02: ๊ฒฐํ•จ ๋ฉด์ ยท๋ชจ์–‘์ด ํ‰๊ท ๋ณด๋‹ค ์ž‘์•„ ์†์ƒ๋„ 24์ (ํ˜„์žฅ์ •๋น„) ํŒ์ •. ์ฃผ์š” ์š”์ธ ๊ฒฐํ•จ ๋ฉด์ ยท๋ชจ์–‘(-11.6์ ), ๋ช…์•” ๋Œ€๋น„(-1.8์ ), ๋ถ‰์€ ๊ฐˆ์ƒ‰ ์ƒ‰์ƒ(+1.7์ )\n", " - U03: ๊ฒฐํ•จ ๋ฉด์ ยท๋ชจ์–‘์ด ํ‰๊ท ๋ณด๋‹ค ์ปค์„œ ์†์ƒ๋„ 82์ (์ฐฝ์ •๋น„) ํŒ์ •. ์ฃผ์š” ์š”์ธ ๊ฒฐํ•จ ๋ฉด์ ยท๋ชจ์–‘(+23.9์ ), ๋ถ‰์€ ๊ฐˆ์ƒ‰ ์ƒ‰์ƒ(+19.6์ ), ๋ช…์•” ๋Œ€๋น„(+4.7์ )\n", "[4] ์†์ƒ ๋ถ€์œ„ ํ‘œ์‹œ์™€ ์ •๋‹ต ๋งˆ์Šคํฌ์˜ IoU: U01 0.95, U02 0.64, U03 0.80, U04 0.98\n", " ํ™”๋ฉด์€ ์ž๋™ ํ™•์ • ๋ฒ„ํŠผ ์—†์ด '๊ฒ€์‚ฌ ๋Œ€๊ธฐ ๋“ฑ๋ก'๋งŒ ์ œ๊ณตํ•˜๋ฉฐ, ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰์€ ๊ฒ€์‚ฌ๊ด€์ด ํ™•์ •ํ•œ๋‹ค(M7).\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 07. ์™ธ๊ด€ ํŒ์ • ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ ๊ฒ€์‚ฌ๊ด€ ์ˆ˜๋™ ๋ณด์ • ๋ฐ ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰ HITL ์Šน์ธ ์ ˆ์ฐจ ์„ค๊ณ„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์™ธ๊ด€ ํŒ์ • ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ ๊ฒ€์‚ฌ๊ด€ ์ˆ˜๋™ ๋ณด์ • ๋ฐ HITL ์Šน์ธ ์ ˆ์ฐจ๋ฅผ ์„ค๊ณ„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **AI ์™ธ๊ด€ ํŒ์ •์— ๋Œ€ํ•œ ๊ฒ€์‚ฌ๊ด€ ์ˆ˜๋™ ๋ณด์ •๊ณผ ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰ ์Šน์ธ HITL ์ ˆ์ฐจ๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  ๊ฒ€์‚ฌ ์šด์˜ ๋น„์šฉ์„ ์ตœ์ ํ™”ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์‹ ๋ขฐ๋„ ๊ธฐ์ค€์œผ๋กœ ๊ฐ„์ด ์Šน์ธ๊ณผ ์ •๋ฐ€ ํŒ๋…์„ ๋‚˜๋ˆ„๋Š” ์šด์˜์•ˆ์„ ์ „์ˆ˜ ์ •๋ฐ€ ํŒ๋… ๊ด€ํ–‰๊ณผ ๋น„๊ตํ•ด ๋น„์šฉ(ํŒ๋…ยท๊ณผ์†Œยท๊ณผ๋Œ€ ํŒ์ • ์†์‹ค)์„ ๊ณ„์‚ฐํ•˜๊ณ , ์‹ ๋ขฐ๋„ ๊ธฐ์ค€๊ณผ ๊ฒ€์‚ฌ๊ด€ ์ผ์ผ ํ•œ๋„์— ๋”ฐ๋ฅธ ๋ฏผ๊ฐ๋„๋ฅผ ๋ณธ๋‹ค ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/optimization/qlt03_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": [ "\"\"\"AI ์ •๋น„ ๋“ฑ๊ธ‰ ํŒ์ •์„ ์‹ ๋ขฐ๋„์— ๋”ฐ๋ผ ๊ฐ„์ด ์Šน์ธยท์ •๋ฐ€ ํŒ๋…์œผ๋กœ ๋‚˜๋ˆ„๋Š” ๊ฒ€์‚ฌ ์šด์˜์•ˆ์„ ๋น„์šฉ์œผ๋กœ ์ตœ์ ํ™”ํ•˜๊ณ (์ „์ˆ˜ ์ •๋ฐ€ ํŒ๋… ๊ด€ํ–‰ ๋Œ€๋น„, ๊ฒ€์‚ฌ๊ด€ ์ผ์ผ ํ•œ๋„ ์ œ์•ฝ ๋ฐ˜์˜), ๋ชจ๋“  ํŒ์ •์„ ๊ฒ€์‚ฌ๊ด€ ์ˆ˜๋™ ๋ณด์ • ํ›„ ์ •๋น„๊ฐ๋…๊ด€(์ฐฝ์ •๋น„ยท๋ณด์ • ๊ฑด์€ ํ’ˆ์งˆ๋ณด์ฆ๊ด€๊นŒ์ง€)์ด ์ง์ ‘ ์Šน์ธํ•˜๋Š” HITL ์ ˆ์ฐจ(๊ถŒํ•œยท์‚ฌ์œ ยทํ•ด์‹œ ์ฒด์ธ ๊ฐ์‚ฌ ๋กœ๊ทธ)๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.\"\"\"\n", "import hashlib\n", "import json\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.ensemble import RandomForestClassifier, GradientBoostingRegressor\n", "from sklearn.model_selection import cross_val_predict\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฅ๋น„ ์™ธ๊ด€ ์‚ฌ์ง„ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์œ„์น˜ ์‚ฌ์ง„์ด ์•„๋‹ˆ๋ผ ๋‚œ์ˆ˜๋กœ ๊ทธ๋ฆฐ 64x64 ํ•ฉ์„ฑ ์ด๋ฏธ์ง€)\n", "# ๋„์žฅ๋œ ๊ธˆ์† ํ‘œ๋ฉด์— ๊ท ์—ด(crack)ยท๋ถ€์‹(corrosion)ยทํŒŒ์†(damage)ยท๋ˆ„์œ (leak)๋ฅผ ๊ทธ๋ฆฌ๊ณ , ์กฐ๋ช… ํŽธ์ฐจ์™€ ์ง„ํ™(mud) ์˜ค์—ผ์„ ์„ž๋Š”๋‹ค.\n", "# ์ด๋ฏธ์ง€์™€ ํ•จ๊ป˜ ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ)๊ณผ ์ •๋‹ต ๊ฒฐํ•จ ๋งˆ์Šคํฌยท์†์ƒ๋„ยท์ •๋น„ ๋“ฑ๊ธ‰์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "from scipy import ndimage as ndi\n", "\n", "SZ = 64\n", "CLASSES = [\"none\", \"crack\", \"corrosion\", \"damage\", \"leak\"]\n", "KO_CLS = {\"none\": \"๊ฒฐํ•จ ์—†์Œ\", \"crack\": \"๊ท ์—ด\", \"corrosion\": \"๋ถ€์‹\", \"damage\": \"ํŒŒ์†\", \"leak\": \"๋ˆ„์œ \"}\n", "CLS_W = {\"none\": 0.0, \"crack\": 1.4, \"corrosion\": 1.0, \"damage\": 1.6, \"leak\": 0.6} # ์†์ƒ๋„ ๊ณ„์‚ฐ์šฉ ๊ฒฐํ•จ ์œ ํ˜• ๊ฐ€์ค‘์น˜\n", "GRADE_NAME = {1: \"ํ˜„์žฅ์ •๋น„\", 2: \"์•ผ์ „์ •๋น„\", 3: \"์ฐฝ์ •๋น„\"}\n", "YY, XX = np.mgrid[0:SZ, 0:SZ]\n", "\n", "\n", "def grade_of(idx):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 7] ์™ธ๊ด€ ํŒ์ • ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ ๊ฒ€์‚ฌ๊ด€ ์ˆ˜๋™ ๋ณด์ • ๋ฐ ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰ HITL ์Šน์ธ ์ ˆ์ฐจ ์„ค๊ณ„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def blob(rng, cy, cx, r, rough=0.35):\n", " \"\"\"๋ถˆ๊ทœ์น™ํ•œ ์›ํ˜• ์˜์—ญ ๋งˆ์Šคํฌ.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 7] ์™ธ๊ด€ ํŒ์ • ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ ๊ฒ€์‚ฌ๊ด€ ์ˆ˜๋™ ๋ณด์ • ๋ฐ ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰ HITL ์Šน์ธ ์ ˆ์ฐจ ์„ค๊ณ„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def draw(rng, cls, sev):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜•ยท์‹ฌ๊ฐ๋„ sev(0~1)์— ๋งž๋Š” ์ •๋‹ต ๋งˆ์Šคํฌ์™€ ์ƒ‰ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m = np.zeros((SZ, SZ), bool)\n", " if cls == \"crack\":\n", " y, x, ang = rng.uniform(12, 52), rng.uniform(12, 52), rng.uniform(0, np.pi)\n", " for _ in range(int(20 + 40 * sev)):\n", " ang += rng.normal(0, 0.35); y += np.sin(ang) * 1.2; x += np.cos(ang) * 1.2\n", " if 0 <= int(y) < SZ and 0 <= int(x) < SZ:\n", " m[int(y), int(x)] = True\n", " m = ndi.binary_dilation(m, iterations=1 + int(sev > 0.6))\n", " elif cls == \"corrosion\":\n", " for _ in range(int(rng.integers(1, 4))):\n", " m |= blob(rng, rng.uniform(14, 50), rng.uniform(14, 50), 3 + 9 * sev * rng.uniform(0.6, 1.1))\n", " elif cls == \"damage\":\n", " m = blob(rng, rng.uniform(20, 44), rng.uniform(20, 44), 6 + 12 * sev, 0.3)\n", " elif cls == \"leak\":\n", " x0, y0 = rng.uniform(16, 48), rng.uniform(8, 24)\n", " w, ln = 2 + 4 * sev, 12 + 26 * sev\n", " m = (np.abs(XX - x0 - 0.15 * (YY - y0)) < w * (1 - 0.4 * (YY - y0) / ln)) & (YY > y0) & (YY < y0 + ln)\n", " return m\n", "\n", "\n", "def make_images(n=600, seed=0, mud=0.0, p_none=0.25, dark=False):\n", " \"\"\"๊ฐ€์ƒ ์™ธ๊ด€ ์‚ฌ์ง„ n์žฅ(+์ง„ํ™ ์˜ค์—ผ mud ๋น„์œจ)๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋กยท์ •๋‹ต. ๋ฐ˜ํ™˜: meta(DataFrame), imgs(n,64,64,3), masks(n,64,64).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " imgs, masks, rows = np.zeros((n, SZ, SZ, 3), np.float32), np.zeros((n, SZ, SZ), bool), []\n", " for i in range(n):\n", " cls = \"none\" if rng.random() < p_none else str(rng.choice(CLASSES[1:], p=[0.25, 0.3, 0.25, 0.2]))\n", " sev = 0.0 if cls == \"none\" else float(rng.beta(1.6, 2.0))\n", " base = np.array([0.42, 0.46, 0.40]) * (1 + 0.5 * ndi.gaussian_filter(rng.normal(size=(SZ, SZ)), 8)[..., None] * 3)\n", " img = base + rng.normal(0, 0.012, (SZ, SZ, 3))\n", " m = draw(rng, cls, sev)\n", " a = ndi.gaussian_filter(m.astype(float), 0.8)[..., None] # ๊ฒฝ๊ณ„๋ฅผ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์„ž๋Š”๋‹ค\n", " if cls == \"crack\":\n", " col = img * rng.uniform(0.2, 0.45)\n", " elif cls == \"corrosion\":\n", " col = np.array([0.55, 0.30, 0.12]) * rng.uniform(0.75, 1.15) * (0.85 + 0.3 * rng.random((SZ, SZ, 1)))\n", " elif cls == \"damage\":\n", " col = np.ones_like(img) * np.array([0.34, 0.34, 0.36]) * (1.0 - 0.45 * sev) * rng.uniform(0.75, 1.0)\n", " rim = m & ~ndi.binary_erosion(m, iterations=2)\n", " a = np.maximum(a, 0.0); img = np.where(rim[..., None], 0.7, img)\n", " elif cls == \"leak\":\n", " col = np.ones_like(img) * np.array([0.10, 0.09, 0.08]) * rng.uniform(0.8, 2.6)\n", " else:\n", " col = img\n", " img = img * (1 - a) + col * a\n", " if rng.random() < 0.35: # ๋ฌดํ•ดํ•œ ์–ผ๋ฃฉ(๊ฒฐํ•จ ์•„๋‹˜): ์˜…์€ ๊ฐˆ์ƒ‰ ๋ฒˆ์ง\n", " st = blob(rng, rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(6, 14), 0.5)\n", " al = ndi.gaussian_filter(st.astype(float), 2.5)[..., None] * rng.uniform(0.15, 0.4)\n", " img = img * (1 - al) + np.array([0.50, 0.40, 0.30]) * al\n", " if rng.random() < 0.3: # ๋ฌดํ•ดํ•œ ๊ธด ์Šคํฌ๋ž˜์น˜(๋ฐ์€ ์„ )\n", " y0, x0, ang = rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(0, np.pi)\n", " for t in np.linspace(0, 25, 40):\n", " yy, xx = int(y0 + t * np.sin(ang)), int(x0 + t * np.cos(ang))\n", " if 0 <= yy < SZ and 0 <= xx < SZ:\n", " img[yy, xx] = np.clip(img[yy, xx] * 1.5, 0, 1)\n", " if mud > 0: # ์ง„ํ™ ์˜ค์—ผ: ๊ฒฐํ•จ ์œ„๋ฅผ ๋ฎ์„ ์ˆ˜ ์žˆ๋‹ค(์ •๋‹ต ๋งˆ์Šคํฌ๋Š” ๊ทธ๋Œ€๋กœ)\n", " mm = np.zeros((SZ, SZ), bool)\n", " while mm.mean() < mud:\n", " mm |= blob(rng, rng.uniform(0, SZ), rng.uniform(0, SZ), rng.uniform(5, 11), 0.5)\n", " img[mm] = np.array([0.38, 0.30, 0.22]) * (0.85 + 0.3 * rng.random((mm.sum(), 1)))\n", " gain = rng.uniform(0.3, 0.55) if dark else rng.uniform(0.5, 1.15)\n", " ang = rng.uniform(0, 2 * np.pi)\n", " light = gain * (1 + 0.3 * (np.cos(ang) * (XX - 32) + np.sin(ang) * (YY - 32)) / 32)\n", " imgs[i] = np.clip(img * light[..., None] + rng.normal(0, 0.025, (SZ, SZ, 3)), 0, 1)\n", " masks[i] = m\n", " area = float(m.mean())\n", " depth = {\"none\": 0.0, \"crack\": 0.3 + 3.0 * sev, \"corrosion\": 0.1 + 1.5 * sev, \"damage\": 1.0 + 8.0 * sev, \"leak\": 0.0}[cls] * rng.lognormal(0, 0.1)\n", " idx = float(np.clip(100 * (1 - np.exp(-(CLS_W[cls] * area * 9 + 0.05 * depth))) + rng.normal(0, 1.5), 0, 100))\n", " rows.append({\"img_id\": f\"IMG{seed:02d}{i:04d}\", \"cls\": cls, \"sev\": sev, \"area_frac\": area, \"depth_mm\": depth, \"damage_idx\": idx,\n", " \"grade\": int(grade_of(idx)), \"gain\": gain, \"mud\": mud})\n", " meta = pd.DataFrame(rows)\n", " # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ): ์‚ฌ์ง„ ํŒ์ •๊ณผ ๋Œ€์กฐํ•  ๊ฐ’\n", " age = rng.integers(1, 25, n)\n", " meta[\"day\"] = np.sort(rng.integers(0, 90, n))\n", " meta[\"equip_type\"] = rng.choice([\"vehicle\", \"generator\", \"comm\", \"trailer\"], n, p=[0.4, 0.2, 0.2, 0.2])\n", " meta[\"mfg_year\"] = 2026 - age\n", " meta[\"op_hours\"] = np.clip(rng.normal(180 * age, 400, n) + 8 * meta[\"damage_idx\"], 10, 20000)\n", " meta[\"env\"] = rng.choice([\"coastal\", \"inland\"], n, p=[0.3, 0.7])\n", " reg = np.clip(meta[\"grade\"] + rng.choice([-1, 0, 0, 0, 0, 0, 1, 2], n), 1, 3) # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก์ƒ ์ง์ „ ์ •๋น„ ๋“ฑ๊ธ‰\n", " meta[\"reg_grade\"] = reg\n", " meta[\"emergency\"] = rng.random(n) < 0.04 # ๊ธด๊ธ‰ ์ ๊ฒ€ ์š”์ฒญ(ํ™”์งˆ ๋ถˆ๋Ÿ‰์ด์–ด๋„ ๋ณด์กด)\n", " return meta, imgs, masks\n", "# ---- ์ „์ฒ˜๋ฆฌ(์กฐ๋ช… ๋ณด์ •ยทํ‘œ์ค€ํ™”)์™€ ๊ฒฐํ•จ ํ›„๋ณด ๋ถ„ํ• , ํŠน์ง• ์ถ”์ถœ (cv2 ๋Œ€์‹  numpyยทscipy ์‚ฌ์šฉ) ----\n", "def correct(img):\n", " \"\"\"์กฐ๋ช… ๋ณด์ •: ๋ฐ๊ธฐ์— ํ‰๋ฉด(์กฐ๋ช… ๊ธฐ์šธ๊ธฐ)์„ ์ ํ•ฉํ•ด ๋‚˜๋ˆˆ๋‹ค. ์–ด๋‘์šด ๊ฒฐํ•จ ํ™”์†Œ๋Š” ์ œ์™ธํ•˜๊ณ  ๋‘ ๋ฒˆ ๋ฐ˜๋ณต ์ ํ•ฉํ•œ๋‹ค. ๋ฐ˜ํ™˜: ๋ณด์ • ๋ฐ๊ธฐ(์ •์ƒ ๋ฉด ์•ฝ 1), ์ƒ‰ ๋น„์œจ.\"\"\"\n", " g = img.mean(axis=2)\n", " A = np.c_[np.ones(SZ * SZ), XX.ravel() / SZ, YY.ravel() / SZ]\n", " keep = np.ones(SZ * SZ, bool)\n", " for _ in range(3):\n", " coef, *_ = np.linalg.lstsq(A[keep], g.ravel()[keep], rcond=None)\n", " plane = (A @ coef).reshape(SZ, SZ)\n", " keep = (np.abs(g - plane) < 0.08 * np.maximum(plane, 1e-3)).ravel()\n", " if keep.sum() < 200:\n", " keep[:] = True\n", " c = g / np.maximum(plane, 1e-3)\n", " chroma = img[..., 0] / (img.sum(axis=2) + 1e-6)\n", " return c, chroma\n", "\n", "\n", "SEGP = {\"dark\": 0.7, \"rust\": 0.5, \"rim\": 1.45} # ๋ถ„ํ•  ์ž„๊ณ„๊ฐ’(์–ด๋‘์›€ ๋ณด์ • ๋ฐ๊ธฐ, ๋ถ‰์€ ๋น„์œจ, ๋ฐ์€ ํ…Œ๋‘๋ฆฌ): ์žฌ๋ณด์ • ๋Œ€์ƒ\n", "\n", "\n", "def segment(img):\n", " \"\"\"๊ฒฐํ•จ ํ›„๋ณด ๋งˆ์Šคํฌ: ์–ด๋‘์šด ์˜์—ญ(๊ท ์—ดยท๋ˆ„์œ ยทํŒŒ์†) + ๋ถ‰์€ ๊ฐˆ์ƒ‰ ์˜์—ญ(๋ถ€์‹) + ๋ฐ์€ ํ…Œ๋‘๋ฆฌ(ํŒŒ์†).\"\"\"\n", " c, chroma = correct(img)\n", " dark = c < SEGP[\"dark\"]\n", " rust = (chroma > SEGP[\"rust\"]) & (c > 0.45)\n", " rim = c > SEGP[\"rim\"]\n", " m = dark | rust | ndi.binary_dilation(rim, iterations=1) & ndi.binary_dilation(dark, iterations=3)\n", " m = ndi.binary_opening(m, structure=np.ones((2, 2))) if m.sum() > 12 else m\n", " return m, c, chroma, dark, rust, rim\n", "\n", "\n", "def extract(img):\n", " \"\"\"๋ถ„ํ•  ๊ฒฐ๊ณผ์™€ ์ƒ‰ยท๋ชจ์–‘์—์„œ 24๊ฐœ ํŠน์ง•์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m, c, chroma, dark, rust, rim = segment(img)\n", " lab, n = ndi.label(m)\n", " area = float(m.sum())\n", " if n:\n", " sizes = ndi.sum(m, lab, range(1, n + 1))\n", " k = int(np.argmax(sizes)) + 1\n", " sl = ndi.find_objects(lab)[k - 1]\n", " h, w = sl[0].stop - sl[0].start, sl[1].stop - sl[1].start\n", " big = float(sizes.max())\n", " elong, fill = max(h, w) / max(min(h, w), 1), big / (h * w)\n", " else:\n", " big, elong, fill = 0.0, 1.0, 0.0\n", " gy, gx = np.gradient(c)\n", " mag = np.hypot(gx, gy)\n", " inside = m.sum() > 0\n", " return {\"area_px\": area, \"area_frac\": area / SZ ** 2, \"n_comp\": float(n), \"big_px\": big, \"elong\": elong, \"fill\": fill,\n", " \"dark_frac\": float(dark.mean()), \"rust_frac\": float(rust.mean()), \"rim_frac\": float(rim.mean()),\n", " \"c_mean\": float(c.mean()), \"c_std\": float(c.std()), \"c_min\": float(c.min()), \"edge_mean\": float(mag.mean()), \"edge_p95\": float(np.quantile(mag, 0.95)),\n", " \"chroma_mean\": float(chroma.mean()), \"chroma_max\": float(chroma.max()),\n", " \"in_dark\": float(c[m].mean()) if inside else 1.0, \"in_chroma\": float(chroma[m].mean()) if inside else float(chroma.mean()),\n", " \"ring_contrast\": float(c[ndi.binary_dilation(m, iterations=3) & ~m].mean() - c[m].mean()) if inside and (~m).any() else 0.0,\n", " \"thin\": float(1 - fill), \"gain\": float(img.mean()), \"sat\": float((img.max(axis=2) - img.min(axis=2)).mean()),\n", " \"cy\": float(ndi.center_of_mass(m)[0] / SZ) if inside else 0.5, \"cx\": float(ndi.center_of_mass(m)[1] / SZ) if inside else 0.5}\n", "\n", "\n", "def feature_table(imgs):\n", " return pd.DataFrame([extract(im) for im in imgs])\n", "# ---- ํŒ์ • ํŒŒ์ดํ”„๋ผ์ธ: ๊ฒฐํ•จ ์œ ํ˜• ๋ถ„๋ฅ˜ + ์†์ƒ๋„ ํšŒ๊ท€(๋ถ„๋ฅ˜ ํ™•๋ฅ  ํฌํ•จ) -> ์ •๋น„ ๋“ฑ๊ธ‰ ----\n", "def fit_pipeline(meta, F):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜• ๋ถ„๋ฅ˜๊ธฐ์™€ ์†์ƒ๋„ ํšŒ๊ท€๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค. ํšŒ๊ท€๊ธฐ ์ž…๋ ฅ์˜ ๋ถ„๋ฅ˜ ํ™•๋ฅ ์€ ๊ต์ฐจ ์˜ˆ์ธก์œผ๋กœ ๋งŒ๋“ค์–ด ๋ˆ„์„ค์„ ๋ง‰๋Š”๋‹ค.\"\"\"\n", " clf = RandomForestClassifier(n_estimators=150, random_state=0, n_jobs=1)\n", " P = cross_val_predict(clf, F, meta[\"cls\"], cv=4, method=\"predict_proba\")\n", " clf.fit(F, meta[\"cls\"])\n", " reg = GradientBoostingRegressor(n_estimators=200, max_depth=3, learning_rate=0.06, random_state=0).fit(np.c_[F.values, P], meta[\"damage_idx\"])\n", " return {\"clf\": clf, \"reg\": reg, \"cols\": list(F.columns)}\n", "\n", "\n", "def predict_pipeline(pl, F):\n", " X = F[pl[\"cols\"]]\n", " P = pl[\"clf\"].predict_proba(X)\n", " idx = np.clip(pl[\"reg\"].predict(np.c_[X.values, P]), 0, 100)\n", " return pl[\"clf\"].classes_[P.argmax(axis=1)], P, idx, P.max(axis=1)\n", "# ---- ์‚ฌ์ง„ ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ: ์ง„ํ™ยท์˜ค์—ผ ๊ฐ€๋ฆผ ์ถ”์ •๊ณผ ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ----\n", "def mud_est(img):\n", " \"\"\"์ง„ํ™(๊ฐˆ์ƒ‰, ์ฑ„๋„ ๋‚ฎ์€ ๋ถ‰์€ ๊ธฐ) ํ™”์†Œ ๋น„์œจ์„ ์ถ”์ •ํ•œ๋‹ค. 3x3 ํ‰๊ท ์œผ๋กœ ์žก์Œ์„ ์ค„์ธ ๋’ค ์ƒ‰ ๋น„์œจ๋กœ ํŒ๋‹จํ•˜๋ฉฐ, ์‹ค์ œ ๊ฐ€๋ฆผ ๋น„์œจ๋ณด๋‹ค ์ž‘๊ฒŒ ๋‚˜์˜ค์ง€๋งŒ ์ˆœ์„œ๋Š” ์œ ์ง€๋œ๋‹ค.\"\"\"\n", " sm = ndi.uniform_filter(img, size=(3, 3, 1))\n", " s = sm.sum(axis=2) + 1e-6\n", " rr, gg = sm[..., 0] / s, sm[..., 1] / s\n", " return float(((rr > 0.385) & (rr < 0.46) & (gg < 0.325)).mean())\n", "\n", "\n", "def confidence(P, idx, F, mud):\n", " \"\"\"ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ 0~100 = 50 x ๋ถ„๋ฅ˜ ํ™•๋ฅ  ์ตœ๋Œ€๊ฐ’ + 35 x ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„๊นŒ์ง€ ๊ฑฐ๋ฆฌ(8์ ์—์„œ ํฌํ™”) + 15 x ์‚ฌ์ง„ ํ’ˆ์งˆ. ์˜ค์—ผ ์˜์‹ฌ(mud ์ถ”์ • 1% ์ด์ƒ)์ด๋ฉด ์ƒํ•œ 75๋กœ ์ œํ•œํ•œ๋‹ค.\"\"\"\n", " bd = np.minimum(np.abs(idx - 35), np.abs(idx - 65))\n", " qual = np.clip(1.0 - np.clip(0.55 - F[\"gain\"].values, 0, None) * 2 - np.clip(0.9 - F[\"c_mean\"].values, 0, None), 0, 1)\n", " c = 50 * P.max(axis=1) + 35 * np.clip(bd / 8, 0, 1) + 15 * qual\n", " return np.where(np.asarray(mud) >= 0.01, np.minimum(c, 75.0), c)\n", "\n", "mtr, itr, _ = make_images(600, seed=1)\n", "Ftr = feature_table(itr)\n", "pl = fit_pipeline(mtr, Ftr)\n", "\n", "\n", "def judge(seed, n, **kw):\n", " \"\"\"์‚ฌ์ง„ n์žฅ์„ ๋งŒ๋“ค์–ด AI ํŒ์ •(๋“ฑ๊ธ‰ยท์‹ ๋ขฐ๋„)๊ณผ ์ •๋‹ต ๋“ฑ๊ธ‰์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " m, im, _ = make_images(n, seed=seed, **kw)\n", " F = feature_table(im)\n", " cls, P, idx, _ = predict_pipeline(pl, F)\n", " conf = confidence(P, idx, F, [mud_est(x) for x in im])\n", " return pd.DataFrame({\"true\": m[\"grade\"].values, \"ai\": grade_of(idx), \"conf\": conf, \"idx\": idx, \"cls\": cls, \"id\": m[\"img_id\"].values})\n", "\n", "\n", "SETS = {\"ํ‰์ƒ(์ •์ƒ ์กฐ๋ช…)\": judge(2, 300), \"์ €์กฐ๋„ ์‚ฌ์ง„ ๋งŽ์Œ\": judge(7, 150, dark=True), \"์ง„ํ™ ๊ฐ€๋ฆผ 20%\": judge(8, 150, mud=0.2)}\n", "\n", "# ---------------- 1) ๋น„์šฉ ๋ชจ๋ธ(๊ต์œก์šฉ ๊ฐ€์ •, ๋งŒ์›/์žฅ) ----------------\n", "C_FULL, C_QUICK, C_AI = 0.9, 0.25, 0.05 # ์ •๋ฐ€ ํŒ๋…, ๊ฐ„์ด ์Šน์ธ(AI ํŒ์ • ์—ด๋žŒ ํ›„ ์Šน์ธ), AI ํŒ์ • ์ „์‚ฐ ๋น„์šฉ\n", "P_HUMAN, P_CATCH = 0.08, 0.5 # ๊ฒ€์‚ฌ๊ด€ ์ •๋ฐ€ ํŒ๋… ์˜ค๋ฅ˜์œจ(+-1 ๋“ฑ๊ธ‰), ๊ฐ„์ด ์Šน์ธ ์‹œ AI ์˜คํŒ์„ ๊ฑธ๋Ÿฌ๋‚ด๋Š” ํ™•๋ฅ \n", "LOSS_UNDER, LOSS_OVER = 20.0, 6.0 # ๊ณผ์†Œ ํŒ์ •(์žฌ์ž…๊ณ ยท๊ณ ์žฅ ์œ„ํ—˜) / ๊ณผ๋Œ€ ํŒ์ •(๊ณผ๋‹ค ์ •๋น„) 1๋“ฑ๊ธ‰๋‹น ์†์‹ค\n", "CAP = 120 # ๊ฒ€์‚ฌ๊ด€ ์ผ์ผ ์ •๋ฐ€ ํŒ๋… ํ•œ๋„(์žฅ)\n", "\n", "\n", "def loss(true, grade):\n", " d = grade - true\n", " return np.where(d < 0, -d * LOSS_UNDER, d * LOSS_OVER)\n", "\n", "\n", "def operate(df, T, cap=CAP, seed=0):\n", " \"\"\"์‹ ๋ขฐ๋„ T ์ด์ƒ์€ ๊ฐ„์ด ์Šน์ธ, ๋ฏธ๋งŒ์€ ์ •๋ฐ€ ํŒ๋…(๊ฒ€์‚ฌ๊ด€ ๋ณด์ •). ์ •๋ฐ€ ํŒ๋… ํ•œ๋„๋ฅผ ๋„˜์œผ๋ฉด ์‹ ๋ขฐ๋„๊ฐ€ ๋‚ฎ์€ ์ˆœ์„œ๋Œ€๋กœ๋งŒ ์ •๋ฐ€ ํŒ๋…ํ•œ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " n = len(df)\n", " full = (df[\"conf\"] < T).values\n", " if full.sum() > cap:\n", " order = np.argsort(df[\"conf\"].values)\n", " full = np.zeros(n, bool); full[order[:cap]] = True\n", " final = df[\"ai\"].values.copy()\n", " err = rng.random(n) < P_HUMAN\n", " human = np.clip(df[\"true\"].values + np.where(err, rng.choice([-1, 1], n), 0), 1, 3)\n", " final[full] = human[full]\n", " wrong_ai = (df[\"ai\"].values != df[\"true\"].values) & ~full\n", " caught = wrong_ai & (rng.random(n) < P_CATCH)\n", " final[caught] = df[\"true\"].values[caught]\n", " cost = full.sum() * C_FULL + (~full).sum() * C_QUICK + n * C_AI + loss(df[\"true\"].values, final).sum()\n", " return cost, full.mean(), (final == df[\"true\"].values).mean(), int(((final < df[\"true\"].values)).sum())\n", "\n", "\n", "def practice(df, seed=0):\n", " \"\"\"๊ด€ํ–‰: ๋ชจ๋“  ์‚ฌ์ง„์„ ๊ฒ€์‚ฌ๊ด€์ด ์ •๋ฐ€ ํŒ๋…(ํ•œ๋„ ์—†์Œ, ๊ฒ€์‚ฌ๊ด€ ์˜ค๋ฅ˜์œจ P_HUMAN).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " err = rng.random(len(df)) < P_HUMAN\n", " final = np.clip(df[\"true\"].values + np.where(err, rng.choice([-1, 1], len(df)), 0), 1, 3)\n", " return len(df) * C_FULL + loss(df[\"true\"].values, final).sum(), (final == df[\"true\"].values).mean()\n", "\n", "\n", "rows = []\n", "for name, df in SETS.items():\n", " cp, ap = practice(df)\n", " co, fr, ao, un = operate(df, 80)\n", " ai_only = len(df) * C_AI + loss(df[\"true\"].values, df[\"ai\"].values).sum()\n", " rows.append((name, len(df), cp, ai_only, co, 1 - co / cp, fr, ap, ao, un))\n", "res = pd.DataFrame(rows, columns=[\"์‹œ๋‚˜๋ฆฌ์˜ค\", \"์žฅ์ˆ˜\", \"๊ด€ํ–‰ ์ „์ˆ˜ ํŒ๋… ๋น„์šฉ\", \"AI ๋‹จ๋… ๋น„์šฉ(๊ธˆ์ง€์•ˆ)\", \"HITL ๋น„์šฉ(T=80)\", \"์ ˆ๊ฐ๋ฅ \", \"์ •๋ฐ€ ํŒ๋… ๋น„์œจ\", \"๊ด€ํ–‰ ์ผ์น˜์œจ\", \"HITL ์ผ์น˜์œจ\", \"HITL ๊ณผ์†Œ ํŒ์ •\"])\n", "print(\"[1] ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ ๊ฒ€์‚ฌ ์šด์˜ ๋น„์šฉ(๊ฐ€์ • ๋‹จ๊ฐ€, ๋งŒ์›)\")\n", "print(res.assign(์ ˆ๊ฐ๋ฅ =res[\"์ ˆ๊ฐ๋ฅ \"].map(\"{:.1%}\".format), **{\"์ •๋ฐ€ ํŒ๋… ๋น„์œจ\": res[\"์ •๋ฐ€ ํŒ๋… ๋น„์œจ\"].map(\"{:.0%}\".format), \"๊ด€ํ–‰ ์ผ์น˜์œจ\": res[\"๊ด€ํ–‰ ์ผ์น˜์œจ\"].map(\"{:.1%}\".format), \"HITL ์ผ์น˜์œจ\": res[\"HITL ์ผ์น˜์œจ\"].map(\"{:.1%}\".format)}).round(1).to_string(index=False))\n", "r0 = res.iloc[0]\n", "passed = [f\"{r['์‹œ๋‚˜๋ฆฌ์˜ค']} {r['์ ˆ๊ฐ๋ฅ ']:.1%}\" for _, r in res.iterrows() if r[\"์ ˆ๊ฐ๋ฅ \"] >= .15]\n", "print(f\" ํ‰์ƒ ์ ˆ๊ฐ {r0['์ ˆ๊ฐ๋ฅ ']:.1%} (๋ชฉํ‘œ 15% ์ด์ƒ: {'๋‹ฌ์„ฑ' if r0['์ ˆ๊ฐ๋ฅ '] >= .15 else '๋ฏธ๋‹ฌ'}), 15% ์ด์ƒ ๋‹ฌ์„ฑ ์‹œ๋‚˜๋ฆฌ์˜ค: {', '.join(passed)}\")\n", "print(f\" AI ๋‹จ๋… ๋น„์šฉ์€ ํ‰์ƒ {r0['AI ๋‹จ๋… ๋น„์šฉ(๊ธˆ์ง€์•ˆ)']:.0f}, ์ง„ํ™ {res.iloc[2]['AI ๋‹จ๋… ๋น„์šฉ(๊ธˆ์ง€์•ˆ)']:.0f}๋กœ ์˜ค์—ผ ์‚ฌ์ง„์—์„œ ๊ณผ์†Œ ํŒ์ • ์†์‹ค์ด ํญ์ฆํ•˜๋ฏ€๋กœ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค(๋ชจ๋“  ํŒ์ •์€ ์‚ฌ๋žŒ์ด ์Šน์ธ). ์ง„ํ™ 20%๋Š” ์ •๋ฐ€ ํŒ๋…์ด ๋Š˜์–ด ์ ˆ๊ฐ์ด ๋‚˜์ง€ ์•Š๋Š”๋‹ค(๋ฏธ๋‹ฌ).\")\n", "\n", "# ---------------- 2) ์‹ ๋ขฐ๋„ ๊ธฐ์ค€ T ํƒ์ƒ‰๊ณผ ๊ฒ€์‚ฌ๊ด€ ํ•œ๋„ ----------------\n", "sw = []\n", "for T in (60, 70, 80, 90, 95):\n", " c, fr, a, un = operate(SETS[\"ํ‰์ƒ(์ •์ƒ ์กฐ๋ช…)\"], T)\n", " sw.append((T, c, fr, a, un))\n", "sw = pd.DataFrame(sw, columns=[\"๊ธฐ์ค€ T\", \"๋น„์šฉ\", \"์ •๋ฐ€ ํŒ๋… ๋น„์œจ\", \"์ผ์น˜์œจ\", \"๊ณผ์†Œ ํŒ์ •\"])\n", "print(\"\\n[2] ์‹ ๋ขฐ๋„ ๊ธฐ์ค€ T ๋ฏผ๊ฐ๋„(ํ‰์ƒ 300์žฅ)\\n\" + sw.round(3).to_string(index=False))\n", "best = sw.loc[sw[\"๋น„์šฉ\"].idxmin()]\n", "print(f\" ๋น„์šฉ ์ตœ์†Œ ๊ธฐ์ค€ T={int(best['๊ธฐ์ค€ T'])}. ๊ธฐ์ค€์„ ๋„ˆ๋ฌด ๋†’์ด๋ฉด ์ •๋ฐ€ ํŒ๋…์ด ๋Š˜์–ด ๋น„์šฉ์ด ์˜ค๋ฅด๊ณ , ๋„ˆ๋ฌด ๋‚ฎ์œผ๋ฉด AI ์˜คํŒ์ด ํ†ต๊ณผํ•œ๋‹ค.\")\n", "cp, _ = practice(SETS[\"์ง„ํ™ ๊ฐ€๋ฆผ 20%\"])\n", "for cap in (150, 60, 20):\n", " c, fr, a, un = operate(SETS[\"์ง„ํ™ ๊ฐ€๋ฆผ 20%\"], 80, cap=cap)\n", " print(f\" ์ง„ํ™ 20% ์‹œ๋‚˜๋ฆฌ์˜ค, ๊ฒ€์‚ฌ๊ด€ ํ•œ๋„ {cap}์žฅ/์ผ: ๋น„์šฉ {c:.0f} (๊ด€ํ–‰ ๋Œ€๋น„ ์ ˆ๊ฐ {1 - c / cp:+.1%}), ์ •๋ฐ€ ํŒ๋… {fr:.0%}, ์ผ์น˜์œจ {a:.1%}, ๊ณผ์†Œ ํŒ์ • {un}์žฅ\")\n", "print(\" ๊ฒ€์‚ฌ๊ด€ ํ•œ๋„๊ฐ€ ์ค„๋ฉด ์‹ ๋ขฐ๋„ ๋‚ฎ์€ ์‚ฌ์ง„๊นŒ์ง€ ๊ฐ„์ด ์Šน์ธ์œผ๋กœ ๋„˜์–ด๊ฐ€ AI ์˜คํŒ์ด ๊ฑธ๋Ÿฌ์ง€์ง€ ์•Š์•„ ์†์‹ค์ด ์ปค์ง„๋‹ค(๋น„์šฉ +). ์˜ค์—ผ ์‚ฌ์ง„์€ ์žฌ์ดฌ์˜ยท์‚ฌ์ „ ์„ธ์ฒ™ ์•ˆ๋‚ด๋กœ ์ค„์—ฌ์•ผ ํ•œ๋‹ค.\")\n", "\n", "# ---------------- 3) HITL ๊ฒฐ์žฌ ์›Œํฌํ”Œ๋กœ์šฐ ----------------\n", "USERS = {\"ai_bot\": \"AI\", \"ins_kim\": \"INS\", \"ins_lee\": \"INS\", \"sup_park\": \"SUP\", \"qa_choi\": \"QA\", \"aud_han\": \"AUD\"}\n", "PERM = {\"AI\": {\"submit\"}, \"INS\": {\"review\", \"correct\", \"reject\"}, \"SUP\": {\"approve\", \"reject\"}, \"QA\": {\"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, pid, grade, conf, parent=None):\n", " if \"submit\" not in PERM.get(USERS.get(user), set()):\n", " return self.deny(user, \"submit\", \"-\", \"AI ํŒ์ • ๋“ฑ๋ก ๊ถŒํ•œ ์—†์Œ\")\n", " did = f\"INS{len(self.docs) + 1:02d}\"\n", " self.docs[did] = {\"photo\": pid, \"ai\": grade, \"grade\": grade, \"conf\": conf, \"status\": \"๊ฒ€์‚ฌ๋Œ€๊ธฐ\", \"line\": [\"INS\", \"SUP\"], \"step\": 0, \"done\": [], \"parent\": parent}\n", " self._audit(user, \"submit\", did, f\"{pid} AI ๋“ฑ๊ธ‰ {GRADE_NAME[grade]} ์‹ ๋ขฐ๋„ {conf:.0f}\")\n", " return did\n", "\n", " def decide(self, user, did, act, reason=\"\", new_grade=None):\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\"] in (\"ํ™•์ •\", \"๋ฐ˜๋ ค\"): 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 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", " if act == \"correct\":\n", " d[\"grade\"] = new_grade\n", " if new_grade != d[\"ai\"] and \"QA\" not in d[\"line\"]:\n", " d[\"line\"] = d[\"line\"] + [\"QA\"] # ๊ฒ€์‚ฌ๊ด€์ด AI ํŒ์ •์„ ๊ณ ์น˜๋ฉด ํ’ˆ์งˆ๋ณด์ฆ๊ด€๊นŒ์ง€ ๊ฒฐ์žฌ\n", " if d[\"grade\"] == 3 and \"QA\" not in d[\"line\"]:\n", " d[\"line\"] = d[\"line\"] + [\"QA\"] # ์ฐฝ์ •๋น„ ํŒ์ •์€ ํ’ˆ์งˆ๋ณด์ฆ๊ด€๊นŒ์ง€ ๊ฒฐ์žฌ\n", " d[\"done\"].append(user); d[\"step\"] += 1\n", " d[\"status\"] = \"ํ™•์ •\" if d[\"step\"] == len(d[\"line\"]) else (\"๊ฐ๋…๋Œ€๊ธฐ\" if d[\"step\"] >= 1 else \"๊ฒ€์‚ฌ๋Œ€๊ธฐ\")\n", " self._audit(user, act, did, reason + (f\" -> {GRADE_NAME[new_grade]}\" if new_grade else \"\")); 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", "base = SETS[\"ํ‰์ƒ(์ •์ƒ ์กฐ๋ช…)\"]\n", "mix = pd.concat([base[base.conf >= 80].head(3), base[base.conf < 80].head(4), base[base.ai == 3].head(2)]).drop_duplicates(\"id\").head(8).reset_index(drop=True)\n", "wf = Flow()\n", "ids = [wf.submit(\"ai_bot\", r.id, int(r.ai), r.conf) for r in mix.itertuples()]\n", "print(f\"\\n[3] AI ํŒ์ • {len(ids)}๊ฑด ์ „๋ถ€ '๊ฒ€์‚ฌ๋Œ€๊ธฐ' ๋“ฑ๋ก, ์ž๋™ ํ™•์ • {sum(wf.docs[i]['status'] == 'ํ™•์ •' for i in ids)}๊ฑด (๋ชฉํ‘œ 0๊ฑด)\")\n", "low = [i for i, r in zip(ids, mix.itertuples()) if r.conf < 80][0]\n", "first = ids[0]\n", "wf.decide(\"ins_kim\", first, \"review\", \"AI ํŒ์ • ์—ด๋žŒ, ์‚ฌ์ง„๊ณผ ์ผ์น˜ ํ™•์ธํ•จ\")\n", "wf.decide(\"sup_park\", first, \"approve\", \"๊ฒ€์‚ฌ๊ด€ ์˜๊ฒฌ ํ™•์ธ, ๋“ฑ๊ธ‰ ํ™•์ •\")\n", "wf.decide(\"ins_lee\", low, \"correct\", \"๊ฒฝ๊ณ„ ๋ถ€๊ทผ ๊ฒฐํ•จ, ๋ฐ•์Šค ์žฌ์ธก์ • ํ›„ ๋“ฑ๊ธ‰ ์กฐ์ •\", new_grade=min(3, wf.docs[low][\"ai\"] + 1))\n", "wf.decide(\"sup_park\", low, \"approve\", \"๋ณด์ • ์‚ฌ์œ  ํƒ€๋‹น, ํ’ˆ์งˆ๋ณด์ฆ ์ƒ์‹ \")\n", "wf.decide(\"qa_choi\", low, \"approve\", \"๋ณด์ • ๊ทผ๊ฑฐ ์‚ฌ์ง„ ๋Œ€์กฐ ์™„๋ฃŒ\")\n", "rj = ids[2]\n", "wf.decide(\"ins_kim\", rj, \"reject\", \"์˜ค์—ผ ์‚ฌ์ง„์œผ๋กœ ํŒ๋… ๋ถˆ๊ฐ€, ์žฌ์ดฌ์˜ ์š”๊ตฌ\")\n", "new = wf.submit(\"ai_bot\", mix.id[2], int(mix.ai[2]), float(mix.conf[2]), parent=rj)\n", "tgt = ids[-1]\n", "attacks = [(\"ai_bot\", tgt, \"review\", \"AI ๊ณ„์ •์ด ์ง์ ‘ ๊ฒ€ํ†  ์ฒ˜๋ฆฌํ•จ\"), (\"sup_park\", tgt, \"approve\", \"๊ฒ€์‚ฌ๊ด€ ๊ฒ€ํ†  ์—†์ด ๊ฐ๋…๊ด€์ด ์Šน์ธํ•จ\"), (\"aud_han\", tgt, \"approve\", \"๊ฐ์‚ฌ ๊ณ„์ •์œผ๋กœ ์Šน์ธ ์‹œ๋„ํ•จ\"),\n", " (\"ghost\", tgt, \"review\", \"์™ธ๋ถ€ ๊ณ„์ •์œผ๋กœ ๊ฒ€ํ†  ์‹œ๋„ํ•จ\"), (\"ins_kim\", tgt, \"review\", \"\"), (\"ins_kim\", tgt, \"correct\", \"์งง์Œ\"), (\"qa_choi\", tgt, \"approve\", \"ํ’ˆ์งˆ๋ณด์ฆ๊ด€์ด ์ˆœ์„œ๋ฅผ ๊ฑด๋„ˆ๋›ฐ์–ด ์Šน์ธ\"),\n", " (\"ins_kim\", first, \"review\", \"์ด๋ฏธ ํ™•์ •๋œ ๊ฑด์„ ๋‹ค์‹œ ๊ฒ€ํ† ํ•จ\"), (\"sup_park\", low, \"reject\", \"ํ™•์ •๋œ ๊ฑด์„ ๋’ค์ง‘์–ด ๋ฐ˜๋ คํ•จ\")]\n", "for u, doc, a, rs in attacks:\n", " wf.decide(u, doc, a, rs, new_grade=2 if a == \"correct\" else None)\n", "wf.edit(\"ins_kim\", first)\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\" {low}: AI {GRADE_NAME[wf.docs[low]['ai']]} -> ๊ฒ€์‚ฌ๊ด€ ๋ณด์ • {GRADE_NAME[wf.docs[low]['grade']]}(๋ณด์ • ์‹œ ํ’ˆ์งˆ๋ณด์ฆ๊ด€๊นŒ์ง€ 3๋‹จ๊ณ„ ๊ฒฐ์žฌ, ์ƒํƒœ {wf.docs[low]['status']}); {rj}(๋ฐ˜๋ ค) -> {new}(์žฌ๋“ฑ๋ก, parent ์—ฐ๊ฒฐ)\")\n", "print(\" ๋ฌธ์„œ ์ƒํƒœ:\", pd.Series({k: v[\"status\"] for k, v in wf.docs.items()}).value_counts().to_dict(), \"| ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰์€ ๊ฒ€์‚ฌ๊ด€ยท์ •๋น„๊ฐ๋…๊ด€์˜ ๊ฒฐ์žฌ๋กœ ํ™•์ •๋˜๋ฉฐ AI๋Š” ํ™•์ • ๊ถŒํ•œ์ด ์—†๋‹ค.\")\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=\"full inspection\"); ax[0].bar(x + .2, res[\"HITL ๋น„์šฉ(T=80)\"], .4, label=\"HITL (T=80)\")\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(\"Total cost by scenario (10k KRW)\")\n", "ax[1].plot(sw[\"๊ธฐ์ค€ T\"], sw[\"๋น„์šฉ\"], \"o-\"); ax[1].set_title(\"Cost vs confidence threshold T\"); ax[1].set_xlabel(\"T\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 07 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ ๊ฒ€์‚ฌ ์šด์˜ ๋น„์šฉ(๊ฐ€์ • ๋‹จ๊ฐ€, ๋งŒ์›)\n", " ์‹œ๋‚˜๋ฆฌ์˜ค ์žฅ์ˆ˜ ๊ด€ํ–‰ ์ „์ˆ˜ ํŒ๋… ๋น„์šฉ AI ๋‹จ๋… ๋น„์šฉ(๊ธˆ์ง€์•ˆ) HITL ๋น„์šฉ(T=80) ์ ˆ๊ฐ๋ฅ  ์ •๋ฐ€ ํŒ๋… ๋น„์œจ ๊ด€ํ–‰ ์ผ์น˜์œจ HITL ์ผ์น˜์œจ HITL ๊ณผ์†Œ ํŒ์ •\n", "ํ‰์ƒ(์ •์ƒ ์กฐ๋ช…) 300 442.0 379.0 178.9 59.5% 22% 94.3% 99.0% 2\n", "์ €์กฐ๋„ ์‚ฌ์ง„ ๋งŽ์Œ 150 263.0 219.5 189.0 28.2% 55% 92.0% 94.7% 3\n", "์ง„ํ™ ๊ฐ€๋ฆผ 20% 150 203.0 771.5 219.0 -7.9% 80% 94.0% 89.3% 0\n", " ํ‰์ƒ ์ ˆ๊ฐ 59.5% (๋ชฉํ‘œ 15% ์ด์ƒ: ๋‹ฌ์„ฑ), 15% ์ด์ƒ ๋‹ฌ์„ฑ ์‹œ๋‚˜๋ฆฌ์˜ค: ํ‰์ƒ(์ •์ƒ ์กฐ๋ช…) 59.5%, ์ €์กฐ๋„ ์‚ฌ์ง„ ๋งŽ์Œ 28.2%\n", " AI ๋‹จ๋… ๋น„์šฉ์€ ํ‰์ƒ 379, ์ง„ํ™ 772๋กœ ์˜ค์—ผ ์‚ฌ์ง„์—์„œ ๊ณผ์†Œ ํŒ์ • ์†์‹ค์ด ํญ์ฆํ•˜๋ฏ€๋กœ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค(๋ชจ๋“  ํŒ์ •์€ ์‚ฌ๋žŒ์ด ์Šน์ธ). ์ง„ํ™ 20%๋Š” ์ •๋ฐ€ ํŒ๋…์ด ๋Š˜์–ด ์ ˆ๊ฐ์ด ๋‚˜์ง€ ์•Š๋Š”๋‹ค(๋ฏธ๋‹ฌ).\n", "\n", "[2] ์‹ ๋ขฐ๋„ ๊ธฐ์ค€ T ๋ฏผ๊ฐ๋„(ํ‰์ƒ 300์žฅ)\n", " ๊ธฐ์ค€ T ๋น„์šฉ ์ •๋ฐ€ ํŒ๋… ๋น„์œจ ์ผ์น˜์œจ ๊ณผ์†Œ ํŒ์ •\n", " 60 267.20 0.027 0.967 8\n", " 70 158.75 0.117 0.990 2\n", " 80 178.90 0.220 0.990 2\n", " 90 238.00 0.400 0.977 2\n", " 95 238.00 0.400 0.977 2\n", " ๋น„์šฉ ์ตœ์†Œ ๊ธฐ์ค€ T=70. ๊ธฐ์ค€์„ ๋„ˆ๋ฌด ๋†’์ด๋ฉด ์ •๋ฐ€ ํŒ๋…์ด ๋Š˜์–ด ๋น„์šฉ์ด ์˜ค๋ฅด๊ณ , ๋„ˆ๋ฌด ๋‚ฎ์œผ๋ฉด AI ์˜คํŒ์ด ํ†ต๊ณผํ•œ๋‹ค.\n", " ์ง„ํ™ 20% ์‹œ๋‚˜๋ฆฌ์˜ค, ๊ฒ€์‚ฌ๊ด€ ํ•œ๋„ 150์žฅ/์ผ: ๋น„์šฉ 210 (๊ด€ํ–‰ ๋Œ€๋น„ ์ ˆ๊ฐ -3.7%), ์ •๋ฐ€ ํŒ๋… 100%, ์ผ์น˜์œจ 94.0%, ๊ณผ์†Œ ํŒ์ • 1์žฅ\n", " ์ง„ํ™ 20% ์‹œ๋‚˜๋ฆฌ์˜ค, ๊ฒ€์‚ฌ๊ด€ ํ•œ๋„ 60์žฅ/์ผ: ๋น„์šฉ 300 (๊ด€ํ–‰ ๋Œ€๋น„ ์ ˆ๊ฐ -47.8%), ์ •๋ฐ€ ํŒ๋… 40%, ์ผ์น˜์œจ 81.3%, ๊ณผ์†Œ ํŒ์ • 3์žฅ\n", " ์ง„ํ™ 20% ์‹œ๋‚˜๋ฆฌ์˜ค, ๊ฒ€์‚ฌ๊ด€ ํ•œ๋„ 20์žฅ/์ผ: ๋น„์šฉ 396 (๊ด€ํ–‰ ๋Œ€๋น„ ์ ˆ๊ฐ -95.1%), ์ •๋ฐ€ ํŒ๋… 13%, ์ผ์น˜์œจ 75.3%, ๊ณผ์†Œ ํŒ์ • 7์žฅ\n", " ๊ฒ€์‚ฌ๊ด€ ํ•œ๋„๊ฐ€ ์ค„๋ฉด ์‹ ๋ขฐ๋„ ๋‚ฎ์€ ์‚ฌ์ง„๊นŒ์ง€ ๊ฐ„์ด ์Šน์ธ์œผ๋กœ ๋„˜์–ด๊ฐ€ AI ์˜คํŒ์ด ๊ฑธ๋Ÿฌ์ง€์ง€ ์•Š์•„ ์†์‹ค์ด ์ปค์ง„๋‹ค(๋น„์šฉ +). ์˜ค์—ผ ์‚ฌ์ง„์€ ์žฌ์ดฌ์˜ยท์‚ฌ์ „ ์„ธ์ฒ™ ์•ˆ๋‚ด๋กœ ์ค„์—ฌ์•ผ ํ•œ๋‹ค.\n", "\n", "[3] AI ํŒ์ • 8๊ฑด ์ „๋ถ€ '๊ฒ€์‚ฌ๋Œ€๊ธฐ' ๋“ฑ๋ก, ์ž๋™ ํ™•์ • 0๊ฑด (๋ชฉํ‘œ 0๊ฑด)\n", " ๊ถŒํ•œ ์šฐํšŒ ์‹œ๋„ 10๊ฑด ์ค‘ ์ฐจ๋‹จ 10๊ฑด (์ฐจ๋‹จ์œจ 100%, ๋ชฉํ‘œ 100%)\n", " ์ฐจ๋‹จ ai_bot review INS08: AI ์—ญํ• ์€ review ๋ถˆ๊ฐ€\n", " ์ฐจ๋‹จ sup_park approve INS08: ๊ฒฐ์žฌ ์ˆœ์„œ ์œ„๋ฐ˜\n", " ์ฐจ๋‹จ aud_han approve INS08: AUD ์—ญํ• ์€ approve ๋ถˆ๊ฐ€\n", " ์ฐจ๋‹จ ghost review INS08: ๋ฏธ๋“ฑ๋ก ์‚ฌ์šฉ์ž\n", " ๊ฐ์‚ฌ ๋กœ๊ทธ 25๊ฑด ํ•ด์‹œ ์ฒด์ธ ๋ฌด๊ฒฐ์„ฑ 100%; 4๋ฒˆ ๊ธฐ๋ก ์‚ฌํ›„ ์ˆ˜์ • ์‹œ ์œ„๋ณ€์กฐ ๊ฒ€์ถœ(4๋ฒˆ)\n", " INS04: AI ์•ผ์ „์ •๋น„ -> ๊ฒ€์‚ฌ๊ด€ ๋ณด์ • ์ฐฝ์ •๋น„(๋ณด์ • ์‹œ ํ’ˆ์งˆ๋ณด์ฆ๊ด€๊นŒ์ง€ 3๋‹จ๊ณ„ ๊ฒฐ์žฌ, ์ƒํƒœ ํ™•์ •); INS03(๋ฐ˜๋ ค) -> INS09(์žฌ๋“ฑ๋ก, parent ์—ฐ๊ฒฐ)\n", " ๋ฌธ์„œ ์ƒํƒœ: {'๊ฒ€์‚ฌ๋Œ€๊ธฐ': 6, 'ํ™•์ •': 2, '๋ฐ˜๋ ค': 1} | ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰์€ ๊ฒ€์‚ฌ๊ด€ยท์ •๋น„๊ฐ๋…๊ด€์˜ ๊ฒฐ์žฌ๋กœ ํ™•์ •๋˜๋ฉฐ AI๋Š” ํ™•์ • ๊ถŒํ•œ์ด ์—†๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 08. ์˜ค์—ผ๋ฌผ/์ง„ํ™ ํ˜ผ์ž… ์‚ฌ์ง„ ๋Œ€๋Ÿ‰ ํŒ์ • ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ๋ฐ ํ•œ๊ณ„์น˜ ๋„์ถœ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์˜ค์—ผ๋ฌผ ํ˜ผ์ž… ์‚ฌ์ง„ ๋Œ€๋Ÿ‰ ํŒ์ • ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์˜ค์—ผ๋ฌผยท์ง„ํ™์ด ์„ž์ธ ์‚ฌ์ง„์„ ๋Œ€๋Ÿ‰ ํŒ์ •ํ•˜๋Š” ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ  ํŒ์ • ํ•œ๊ณ„์น˜๋ฅผ ๋„์ถœํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ง„ํ™ ํ˜ผ์ž… ๋น„์œจ์„ 0~50%๋กœ ์˜ฌ๋ฆฐ ์‚ฌ์ง„ ๋ฌถ์Œ๊ณผ ํ๋ฆผยท์„ผ์„œ ์žก์Œยท์ €์กฐ๋„+์ง„ํ™ ๊ฒฐํ•ฉ ์กฐ๊ฑด์—์„œ ๋“ฑ๊ธ‰ ์ผ์น˜์œจ, ์žฌ์ดฌ์˜ ์š”๊ตฌ์œจ, ์ž๋™ ์ฒ˜๋ฆฌ ๋น„์œจ, ๊ฒฝ๋ณด ์ •ํ™•๋„๋ฅผ ์ธก์ •ํ•˜๊ณ  ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ์˜ ํšจ๊ณผ๋ฅผ ๋น„๊ต ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/dashboard/qlt03_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": [ "\"\"\"์ง„ํ™ยท์˜ค์—ผ๋ฌผ ํ˜ผ์ž… ๋น„์œจ์„ 0~50%๋กœ ์˜ฌ๋ฆฐ ์‚ฌ์ง„ ์ˆ˜๋ฐฑ ์žฅ๊ณผ ํ๋ฆผยท์„ผ์„œ ์žก์Œยท์ €์กฐ๋„ ๊ฒฐํ•ฉ ์‚ฌ์ง„์„ ์ผ๊ด„ ํŒ์ •ํ•˜๋Š” ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ๋กœ ์˜ค์—ผ ์‚ฌ์ง„ ํŒ์ • ํ•œ๊ณ„์น˜(์ž๋™ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ ๊ฐ€๋ฆผ ๋น„์œจ)์™€ ๊ฒฝ๋ณด ์ •ํ™•๋„, ๋Œ€๋Ÿ‰ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ยท๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๊ณ  ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ(์žฌ์ดฌ์˜ยท๊ฒ€์‚ฌ๊ด€ ํ™•์ธ)์˜ ํšจ๊ณผ๋ฅผ ํ™•์ธํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.ensemble import RandomForestClassifier, GradientBoostingRegressor\n", "from sklearn.model_selection import cross_val_predict\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฅ๋น„ ์™ธ๊ด€ ์‚ฌ์ง„ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์œ„์น˜ ์‚ฌ์ง„์ด ์•„๋‹ˆ๋ผ ๋‚œ์ˆ˜๋กœ ๊ทธ๋ฆฐ 64x64 ํ•ฉ์„ฑ ์ด๋ฏธ์ง€)\n", "# ๋„์žฅ๋œ ๊ธˆ์† ํ‘œ๋ฉด์— ๊ท ์—ด(crack)ยท๋ถ€์‹(corrosion)ยทํŒŒ์†(damage)ยท๋ˆ„์œ (leak)๋ฅผ ๊ทธ๋ฆฌ๊ณ , ์กฐ๋ช… ํŽธ์ฐจ์™€ ์ง„ํ™(mud) ์˜ค์—ผ์„ ์„ž๋Š”๋‹ค.\n", "# ์ด๋ฏธ์ง€์™€ ํ•จ๊ป˜ ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ)๊ณผ ์ •๋‹ต ๊ฒฐํ•จ ๋งˆ์Šคํฌยท์†์ƒ๋„ยท์ •๋น„ ๋“ฑ๊ธ‰์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "from scipy import ndimage as ndi\n", "\n", "SZ = 64\n", "CLASSES = [\"none\", \"crack\", \"corrosion\", \"damage\", \"leak\"]\n", "KO_CLS = {\"none\": \"๊ฒฐํ•จ ์—†์Œ\", \"crack\": \"๊ท ์—ด\", \"corrosion\": \"๋ถ€์‹\", \"damage\": \"ํŒŒ์†\", \"leak\": \"๋ˆ„์œ \"}\n", "CLS_W = {\"none\": 0.0, \"crack\": 1.4, \"corrosion\": 1.0, \"damage\": 1.6, \"leak\": 0.6} # ์†์ƒ๋„ ๊ณ„์‚ฐ์šฉ ๊ฒฐํ•จ ์œ ํ˜• ๊ฐ€์ค‘์น˜\n", "GRADE_NAME = {1: \"ํ˜„์žฅ์ •๋น„\", 2: \"์•ผ์ „์ •๋น„\", 3: \"์ฐฝ์ •๋น„\"}\n", "YY, XX = np.mgrid[0:SZ, 0:SZ]\n", "\n", "\n", "def grade_of(idx):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 8] ์˜ค์—ผ๋ฌผ/์ง„ํ™ ํ˜ผ์ž… ์‚ฌ์ง„ ๋Œ€๋Ÿ‰ ํŒ์ • ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ๋ฐ ํ•œ๊ณ„์น˜ ๋„์ถœ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def blob(rng, cy, cx, r, rough=0.35):\n", " \"\"\"๋ถˆ๊ทœ์น™ํ•œ ์›ํ˜• ์˜์—ญ ๋งˆ์Šคํฌ.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 8] ์˜ค์—ผ๋ฌผ/์ง„ํ™ ํ˜ผ์ž… ์‚ฌ์ง„ ๋Œ€๋Ÿ‰ ํŒ์ • ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ๋ฐ ํ•œ๊ณ„์น˜ ๋„์ถœ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def draw(rng, cls, sev):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜•ยท์‹ฌ๊ฐ๋„ sev(0~1)์— ๋งž๋Š” ์ •๋‹ต ๋งˆ์Šคํฌ์™€ ์ƒ‰ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m = np.zeros((SZ, SZ), bool)\n", " if cls == \"crack\":\n", " y, x, ang = rng.uniform(12, 52), rng.uniform(12, 52), rng.uniform(0, np.pi)\n", " for _ in range(int(20 + 40 * sev)):\n", " ang += rng.normal(0, 0.35); y += np.sin(ang) * 1.2; x += np.cos(ang) * 1.2\n", " if 0 <= int(y) < SZ and 0 <= int(x) < SZ:\n", " m[int(y), int(x)] = True\n", " m = ndi.binary_dilation(m, iterations=1 + int(sev > 0.6))\n", " elif cls == \"corrosion\":\n", " for _ in range(int(rng.integers(1, 4))):\n", " m |= blob(rng, rng.uniform(14, 50), rng.uniform(14, 50), 3 + 9 * sev * rng.uniform(0.6, 1.1))\n", " elif cls == \"damage\":\n", " m = blob(rng, rng.uniform(20, 44), rng.uniform(20, 44), 6 + 12 * sev, 0.3)\n", " elif cls == \"leak\":\n", " x0, y0 = rng.uniform(16, 48), rng.uniform(8, 24)\n", " w, ln = 2 + 4 * sev, 12 + 26 * sev\n", " m = (np.abs(XX - x0 - 0.15 * (YY - y0)) < w * (1 - 0.4 * (YY - y0) / ln)) & (YY > y0) & (YY < y0 + ln)\n", " return m\n", "\n", "\n", "def make_images(n=600, seed=0, mud=0.0, p_none=0.25, dark=False):\n", " \"\"\"๊ฐ€์ƒ ์™ธ๊ด€ ์‚ฌ์ง„ n์žฅ(+์ง„ํ™ ์˜ค์—ผ mud ๋น„์œจ)๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋กยท์ •๋‹ต. ๋ฐ˜ํ™˜: meta(DataFrame), imgs(n,64,64,3), masks(n,64,64).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " imgs, masks, rows = np.zeros((n, SZ, SZ, 3), np.float32), np.zeros((n, SZ, SZ), bool), []\n", " for i in range(n):\n", " cls = \"none\" if rng.random() < p_none else str(rng.choice(CLASSES[1:], p=[0.25, 0.3, 0.25, 0.2]))\n", " sev = 0.0 if cls == \"none\" else float(rng.beta(1.6, 2.0))\n", " base = np.array([0.42, 0.46, 0.40]) * (1 + 0.5 * ndi.gaussian_filter(rng.normal(size=(SZ, SZ)), 8)[..., None] * 3)\n", " img = base + rng.normal(0, 0.012, (SZ, SZ, 3))\n", " m = draw(rng, cls, sev)\n", " a = ndi.gaussian_filter(m.astype(float), 0.8)[..., None] # ๊ฒฝ๊ณ„๋ฅผ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์„ž๋Š”๋‹ค\n", " if cls == \"crack\":\n", " col = img * rng.uniform(0.2, 0.45)\n", " elif cls == \"corrosion\":\n", " col = np.array([0.55, 0.30, 0.12]) * rng.uniform(0.75, 1.15) * (0.85 + 0.3 * rng.random((SZ, SZ, 1)))\n", " elif cls == \"damage\":\n", " col = np.ones_like(img) * np.array([0.34, 0.34, 0.36]) * (1.0 - 0.45 * sev) * rng.uniform(0.75, 1.0)\n", " rim = m & ~ndi.binary_erosion(m, iterations=2)\n", " a = np.maximum(a, 0.0); img = np.where(rim[..., None], 0.7, img)\n", " elif cls == \"leak\":\n", " col = np.ones_like(img) * np.array([0.10, 0.09, 0.08]) * rng.uniform(0.8, 2.6)\n", " else:\n", " col = img\n", " img = img * (1 - a) + col * a\n", " if rng.random() < 0.35: # ๋ฌดํ•ดํ•œ ์–ผ๋ฃฉ(๊ฒฐํ•จ ์•„๋‹˜): ์˜…์€ ๊ฐˆ์ƒ‰ ๋ฒˆ์ง\n", " st = blob(rng, rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(6, 14), 0.5)\n", " al = ndi.gaussian_filter(st.astype(float), 2.5)[..., None] * rng.uniform(0.15, 0.4)\n", " img = img * (1 - al) + np.array([0.50, 0.40, 0.30]) * al\n", " if rng.random() < 0.3: # ๋ฌดํ•ดํ•œ ๊ธด ์Šคํฌ๋ž˜์น˜(๋ฐ์€ ์„ )\n", " y0, x0, ang = rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(0, np.pi)\n", " for t in np.linspace(0, 25, 40):\n", " yy, xx = int(y0 + t * np.sin(ang)), int(x0 + t * np.cos(ang))\n", " if 0 <= yy < SZ and 0 <= xx < SZ:\n", " img[yy, xx] = np.clip(img[yy, xx] * 1.5, 0, 1)\n", " if mud > 0: # ์ง„ํ™ ์˜ค์—ผ: ๊ฒฐํ•จ ์œ„๋ฅผ ๋ฎ์„ ์ˆ˜ ์žˆ๋‹ค(์ •๋‹ต ๋งˆ์Šคํฌ๋Š” ๊ทธ๋Œ€๋กœ)\n", " mm = np.zeros((SZ, SZ), bool)\n", " while mm.mean() < mud:\n", " mm |= blob(rng, rng.uniform(0, SZ), rng.uniform(0, SZ), rng.uniform(5, 11), 0.5)\n", " img[mm] = np.array([0.38, 0.30, 0.22]) * (0.85 + 0.3 * rng.random((mm.sum(), 1)))\n", " gain = rng.uniform(0.3, 0.55) if dark else rng.uniform(0.5, 1.15)\n", " ang = rng.uniform(0, 2 * np.pi)\n", " light = gain * (1 + 0.3 * (np.cos(ang) * (XX - 32) + np.sin(ang) * (YY - 32)) / 32)\n", " imgs[i] = np.clip(img * light[..., None] + rng.normal(0, 0.025, (SZ, SZ, 3)), 0, 1)\n", " masks[i] = m\n", " area = float(m.mean())\n", " depth = {\"none\": 0.0, \"crack\": 0.3 + 3.0 * sev, \"corrosion\": 0.1 + 1.5 * sev, \"damage\": 1.0 + 8.0 * sev, \"leak\": 0.0}[cls] * rng.lognormal(0, 0.1)\n", " idx = float(np.clip(100 * (1 - np.exp(-(CLS_W[cls] * area * 9 + 0.05 * depth))) + rng.normal(0, 1.5), 0, 100))\n", " rows.append({\"img_id\": f\"IMG{seed:02d}{i:04d}\", \"cls\": cls, \"sev\": sev, \"area_frac\": area, \"depth_mm\": depth, \"damage_idx\": idx,\n", " \"grade\": int(grade_of(idx)), \"gain\": gain, \"mud\": mud})\n", " meta = pd.DataFrame(rows)\n", " # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ): ์‚ฌ์ง„ ํŒ์ •๊ณผ ๋Œ€์กฐํ•  ๊ฐ’\n", " age = rng.integers(1, 25, n)\n", " meta[\"day\"] = np.sort(rng.integers(0, 90, n))\n", " meta[\"equip_type\"] = rng.choice([\"vehicle\", \"generator\", \"comm\", \"trailer\"], n, p=[0.4, 0.2, 0.2, 0.2])\n", " meta[\"mfg_year\"] = 2026 - age\n", " meta[\"op_hours\"] = np.clip(rng.normal(180 * age, 400, n) + 8 * meta[\"damage_idx\"], 10, 20000)\n", " meta[\"env\"] = rng.choice([\"coastal\", \"inland\"], n, p=[0.3, 0.7])\n", " reg = np.clip(meta[\"grade\"] + rng.choice([-1, 0, 0, 0, 0, 0, 1, 2], n), 1, 3) # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก์ƒ ์ง์ „ ์ •๋น„ ๋“ฑ๊ธ‰\n", " meta[\"reg_grade\"] = reg\n", " meta[\"emergency\"] = rng.random(n) < 0.04 # ๊ธด๊ธ‰ ์ ๊ฒ€ ์š”์ฒญ(ํ™”์งˆ ๋ถˆ๋Ÿ‰์ด์–ด๋„ ๋ณด์กด)\n", " return meta, imgs, masks\n", "# ---- ์ „์ฒ˜๋ฆฌ(์กฐ๋ช… ๋ณด์ •ยทํ‘œ์ค€ํ™”)์™€ ๊ฒฐํ•จ ํ›„๋ณด ๋ถ„ํ• , ํŠน์ง• ์ถ”์ถœ (cv2 ๋Œ€์‹  numpyยทscipy ์‚ฌ์šฉ) ----\n", "def correct(img):\n", " \"\"\"์กฐ๋ช… ๋ณด์ •: ๋ฐ๊ธฐ์— ํ‰๋ฉด(์กฐ๋ช… ๊ธฐ์šธ๊ธฐ)์„ ์ ํ•ฉํ•ด ๋‚˜๋ˆˆ๋‹ค. ์–ด๋‘์šด ๊ฒฐํ•จ ํ™”์†Œ๋Š” ์ œ์™ธํ•˜๊ณ  ๋‘ ๋ฒˆ ๋ฐ˜๋ณต ์ ํ•ฉํ•œ๋‹ค. ๋ฐ˜ํ™˜: ๋ณด์ • ๋ฐ๊ธฐ(์ •์ƒ ๋ฉด ์•ฝ 1), ์ƒ‰ ๋น„์œจ.\"\"\"\n", " g = img.mean(axis=2)\n", " A = np.c_[np.ones(SZ * SZ), XX.ravel() / SZ, YY.ravel() / SZ]\n", " keep = np.ones(SZ * SZ, bool)\n", " for _ in range(3):\n", " coef, *_ = np.linalg.lstsq(A[keep], g.ravel()[keep], rcond=None)\n", " plane = (A @ coef).reshape(SZ, SZ)\n", " keep = (np.abs(g - plane) < 0.08 * np.maximum(plane, 1e-3)).ravel()\n", " if keep.sum() < 200:\n", " keep[:] = True\n", " c = g / np.maximum(plane, 1e-3)\n", " chroma = img[..., 0] / (img.sum(axis=2) + 1e-6)\n", " return c, chroma\n", "\n", "\n", "SEGP = {\"dark\": 0.7, \"rust\": 0.5, \"rim\": 1.45} # ๋ถ„ํ•  ์ž„๊ณ„๊ฐ’(์–ด๋‘์›€ ๋ณด์ • ๋ฐ๊ธฐ, ๋ถ‰์€ ๋น„์œจ, ๋ฐ์€ ํ…Œ๋‘๋ฆฌ): ์žฌ๋ณด์ • ๋Œ€์ƒ\n", "\n", "\n", "def segment(img):\n", " \"\"\"๊ฒฐํ•จ ํ›„๋ณด ๋งˆ์Šคํฌ: ์–ด๋‘์šด ์˜์—ญ(๊ท ์—ดยท๋ˆ„์œ ยทํŒŒ์†) + ๋ถ‰์€ ๊ฐˆ์ƒ‰ ์˜์—ญ(๋ถ€์‹) + ๋ฐ์€ ํ…Œ๋‘๋ฆฌ(ํŒŒ์†).\"\"\"\n", " c, chroma = correct(img)\n", " dark = c < SEGP[\"dark\"]\n", " rust = (chroma > SEGP[\"rust\"]) & (c > 0.45)\n", " rim = c > SEGP[\"rim\"]\n", " m = dark | rust | ndi.binary_dilation(rim, iterations=1) & ndi.binary_dilation(dark, iterations=3)\n", " m = ndi.binary_opening(m, structure=np.ones((2, 2))) if m.sum() > 12 else m\n", " return m, c, chroma, dark, rust, rim\n", "\n", "\n", "def extract(img):\n", " \"\"\"๋ถ„ํ•  ๊ฒฐ๊ณผ์™€ ์ƒ‰ยท๋ชจ์–‘์—์„œ 24๊ฐœ ํŠน์ง•์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m, c, chroma, dark, rust, rim = segment(img)\n", " lab, n = ndi.label(m)\n", " area = float(m.sum())\n", " if n:\n", " sizes = ndi.sum(m, lab, range(1, n + 1))\n", " k = int(np.argmax(sizes)) + 1\n", " sl = ndi.find_objects(lab)[k - 1]\n", " h, w = sl[0].stop - sl[0].start, sl[1].stop - sl[1].start\n", " big = float(sizes.max())\n", " elong, fill = max(h, w) / max(min(h, w), 1), big / (h * w)\n", " else:\n", " big, elong, fill = 0.0, 1.0, 0.0\n", " gy, gx = np.gradient(c)\n", " mag = np.hypot(gx, gy)\n", " inside = m.sum() > 0\n", " return {\"area_px\": area, \"area_frac\": area / SZ ** 2, \"n_comp\": float(n), \"big_px\": big, \"elong\": elong, \"fill\": fill,\n", " \"dark_frac\": float(dark.mean()), \"rust_frac\": float(rust.mean()), \"rim_frac\": float(rim.mean()),\n", " \"c_mean\": float(c.mean()), \"c_std\": float(c.std()), \"c_min\": float(c.min()), \"edge_mean\": float(mag.mean()), \"edge_p95\": float(np.quantile(mag, 0.95)),\n", " \"chroma_mean\": float(chroma.mean()), \"chroma_max\": float(chroma.max()),\n", " \"in_dark\": float(c[m].mean()) if inside else 1.0, \"in_chroma\": float(chroma[m].mean()) if inside else float(chroma.mean()),\n", " \"ring_contrast\": float(c[ndi.binary_dilation(m, iterations=3) & ~m].mean() - c[m].mean()) if inside and (~m).any() else 0.0,\n", " \"thin\": float(1 - fill), \"gain\": float(img.mean()), \"sat\": float((img.max(axis=2) - img.min(axis=2)).mean()),\n", " \"cy\": float(ndi.center_of_mass(m)[0] / SZ) if inside else 0.5, \"cx\": float(ndi.center_of_mass(m)[1] / SZ) if inside else 0.5}\n", "\n", "\n", "def feature_table(imgs):\n", " return pd.DataFrame([extract(im) for im in imgs])\n", "# ---- ํŒ์ • ํŒŒ์ดํ”„๋ผ์ธ: ๊ฒฐํ•จ ์œ ํ˜• ๋ถ„๋ฅ˜ + ์†์ƒ๋„ ํšŒ๊ท€(๋ถ„๋ฅ˜ ํ™•๋ฅ  ํฌํ•จ) -> ์ •๋น„ ๋“ฑ๊ธ‰ ----\n", "def fit_pipeline(meta, F):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜• ๋ถ„๋ฅ˜๊ธฐ์™€ ์†์ƒ๋„ ํšŒ๊ท€๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค. ํšŒ๊ท€๊ธฐ ์ž…๋ ฅ์˜ ๋ถ„๋ฅ˜ ํ™•๋ฅ ์€ ๊ต์ฐจ ์˜ˆ์ธก์œผ๋กœ ๋งŒ๋“ค์–ด ๋ˆ„์„ค์„ ๋ง‰๋Š”๋‹ค.\"\"\"\n", " clf = RandomForestClassifier(n_estimators=150, random_state=0, n_jobs=1)\n", " P = cross_val_predict(clf, F, meta[\"cls\"], cv=4, method=\"predict_proba\")\n", " clf.fit(F, meta[\"cls\"])\n", " reg = GradientBoostingRegressor(n_estimators=200, max_depth=3, learning_rate=0.06, random_state=0).fit(np.c_[F.values, P], meta[\"damage_idx\"])\n", " return {\"clf\": clf, \"reg\": reg, \"cols\": list(F.columns)}\n", "\n", "\n", "def predict_pipeline(pl, F):\n", " X = F[pl[\"cols\"]]\n", " P = pl[\"clf\"].predict_proba(X)\n", " idx = np.clip(pl[\"reg\"].predict(np.c_[X.values, P]), 0, 100)\n", " return pl[\"clf\"].classes_[P.argmax(axis=1)], P, idx, P.max(axis=1)\n", "# ---- ์‚ฌ์ง„ ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ: ์ง„ํ™ยท์˜ค์—ผ ๊ฐ€๋ฆผ ์ถ”์ •๊ณผ ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ----\n", "def mud_est(img):\n", " \"\"\"์ง„ํ™(๊ฐˆ์ƒ‰, ์ฑ„๋„ ๋‚ฎ์€ ๋ถ‰์€ ๊ธฐ) ํ™”์†Œ ๋น„์œจ์„ ์ถ”์ •ํ•œ๋‹ค. 3x3 ํ‰๊ท ์œผ๋กœ ์žก์Œ์„ ์ค„์ธ ๋’ค ์ƒ‰ ๋น„์œจ๋กœ ํŒ๋‹จํ•˜๋ฉฐ, ์‹ค์ œ ๊ฐ€๋ฆผ ๋น„์œจ๋ณด๋‹ค ์ž‘๊ฒŒ ๋‚˜์˜ค์ง€๋งŒ ์ˆœ์„œ๋Š” ์œ ์ง€๋œ๋‹ค.\"\"\"\n", " sm = ndi.uniform_filter(img, size=(3, 3, 1))\n", " s = sm.sum(axis=2) + 1e-6\n", " rr, gg = sm[..., 0] / s, sm[..., 1] / s\n", " return float(((rr > 0.385) & (rr < 0.46) & (gg < 0.325)).mean())\n", "\n", "\n", "def confidence(P, idx, F, mud):\n", " \"\"\"ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ 0~100 = 50 x ๋ถ„๋ฅ˜ ํ™•๋ฅ  ์ตœ๋Œ€๊ฐ’ + 35 x ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„๊นŒ์ง€ ๊ฑฐ๋ฆฌ(8์ ์—์„œ ํฌํ™”) + 15 x ์‚ฌ์ง„ ํ’ˆ์งˆ. ์˜ค์—ผ ์˜์‹ฌ(mud ์ถ”์ • 1% ์ด์ƒ)์ด๋ฉด ์ƒํ•œ 75๋กœ ์ œํ•œํ•œ๋‹ค.\"\"\"\n", " bd = np.minimum(np.abs(idx - 35), np.abs(idx - 65))\n", " qual = np.clip(1.0 - np.clip(0.55 - F[\"gain\"].values, 0, None) * 2 - np.clip(0.9 - F[\"c_mean\"].values, 0, None), 0, 1)\n", " c = 50 * P.max(axis=1) + 35 * np.clip(bd / 8, 0, 1) + 15 * qual\n", " return np.where(np.asarray(mud) >= 0.01, np.minimum(c, 75.0), c)\n", "\n", "mtr, itr, _ = make_images(600, seed=1)\n", "pl = fit_pipeline(mtr, feature_table(itr))\n", "\n", "\n", "def run(imgs, truth):\n", " \"\"\"์‚ฌ์ง„ ๋ฌถ์Œ์„ ํŒ์ •ํ•ด ๊ฒŒ์ดํŠธ ์ ์šฉ ์ „ยทํ›„ ์‹ ๋ขฐ๋„์™€ ๊ฒฝ๋ณด(๋“ฑ๊ธ‰ 2 ์ด์ƒ = ์ •๋น„ ๊ฒฝ๋ณด)๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " t0 = time.perf_counter()\n", " F = feature_table(imgs)\n", " t1 = time.perf_counter()\n", " cls, P, idx, _ = predict_pipeline(pl, F)\n", " t2 = time.perf_counter()\n", " mud = np.array([mud_est(x) for x in imgs])\n", " raw_conf = confidence(P, idx, F, np.zeros(len(imgs)))\n", " conf = confidence(P, idx, F, mud)\n", " t3 = time.perf_counter()\n", " g = grade_of(idx)\n", " return pd.DataFrame({\"true\": truth[\"grade\"].values, \"ai\": g, \"mud\": mud, \"raw_conf\": raw_conf, \"conf\": conf, \"idx\": idx, \"retake\": (mud >= 0.03) | (F[\"gain\"].values < 0.17)}), (t1 - t0, t2 - t1, t3 - t2)\n", "\n", "\n", "# ---------------- 1) ์ง„ํ™ ํ˜ผ์ž… ๋น„์œจ ์Šค์œ• ----------------\n", "rows, tm = [], []\n", "for mudr in (0.0, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50):\n", " m, im, _ = make_images(120, seed=20 + int(mudr * 100), mud=mudr)\n", " r, t = run(im, m)\n", " tm.append(t)\n", " ok = r[\"true\"] == r[\"ai\"]\n", " auto = (r[\"conf\"] >= 80) & ~r[\"retake\"] # ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ๋ฅผ ํ†ต๊ณผํ•ด ์ž๋™ ์ฒ˜๋ฆฌ(๊ฒ€์‚ฌ๊ด€ ๊ฐ„์ด ์Šน์ธ)๋กœ ๊ฐ€๋Š” ์‚ฌ์ง„\n", " alarm_ok = ((r[\"ai\"] >= 2) == (r[\"true\"] >= 2))\n", " ung = r[\"raw_conf\"] >= 80\n", " rows.append((f\"{mudr:.0%}\", len(r), ok.mean(), r[\"conf\"].mean(), r[\"retake\"].mean(), auto.mean(), alarm_ok[auto].mean() if auto.any() else np.nan, alarm_ok[ung].mean() if ung.any() else np.nan, ung.mean()))\n", "sw = pd.DataFrame(rows, columns=[\"์ง„ํ™ ๋น„์œจ\", \"์žฅ์ˆ˜\", \"๋“ฑ๊ธ‰ ์ผ์น˜์œจ(์ „์ฒด)\", \"ํ‰๊ท  ์‹ ๋ขฐ๋„\", \"์žฌ์ดฌ์˜ ์š”๊ตฌ์œจ\", \"์ž๋™ ์ฒ˜๋ฆฌ ๋น„์œจ\", \"๊ฒฝ๋ณด ์ •ํ™•๋„(๊ฒŒ์ดํŠธ ํ›„)\", \"๊ฒฝ๋ณด ์ •ํ™•๋„(๊ฒŒ์ดํŠธ ์—†์ด 80 ์ด์ƒ)\", \"๊ฒŒ์ดํŠธ ์—†์ด 80 ์ด์ƒ ๋น„์œจ\"])\n", "print(\"[1] ์ง„ํ™ ํ˜ผ์ž… ๋น„์œจ๋ณ„ ํŒ์ •(๊ต์œก์šฉ ๊ฐ€์ƒ ์‚ฌ์ง„, ๋น„์œจ๋‹น 120์žฅ)\")\n", "pc = lambda v: \"-\" if pd.isna(v) else f\"{v:.0%}\"\n", "print(sw.assign(**{c: sw[c].map(pc) for c in sw.columns if c not in (\"์ง„ํ™ ๋น„์œจ\", \"์žฅ์ˆ˜\", \"ํ‰๊ท  ์‹ ๋ขฐ๋„\")}).round(1).to_string(index=False))\n", "bad = sw[sw[\"๊ฒฝ๋ณด ์ •ํ™•๋„(๊ฒŒ์ดํŠธ ํ›„)\"] < .95][\"์ง„ํ™ ๋น„์œจ\"].tolist()\n", "print(f\" ๊ฒฝ๋ณด ์ •ํ™•๋„๊ฐ€ 95% ๋ฏธ๋งŒ์œผ๋กœ ์ฒ˜์Œ ๋–จ์–ด์ง€๋Š” ์ง„ํ™ ๋น„์œจ: {bad[0] if bad else '์—†์Œ'} (์ง„ํ™ 0%์—์„œ๋Š” ์ž๋™ ์ฒ˜๋ฆฌ {sw.iloc[0]['์ž๋™ ์ฒ˜๋ฆฌ ๋น„์œจ']:.0%}, ๊ฒฝ๋ณด ์ •ํ™•๋„ {sw.iloc[0]['๊ฒฝ๋ณด ์ •ํ™•๋„(๊ฒŒ์ดํŠธ ํ›„)']:.0%})\")\n", "print(f\" ๊ฒŒ์ดํŠธ๊ฐ€ ์—†์œผ๋ฉด ์ง„ํ™ 20%์—์„œ๋„ ์‹ ๋ขฐ๋„ 80 ์ด์ƒ์ด {sw.iloc[3]['๊ฒŒ์ดํŠธ ์—†์ด 80 ์ด์ƒ ๋น„์œจ']:.0%} ๋‚จ๊ณ  ๊ทธ ๊ฒฝ๋ณด ์ •ํ™•๋„๋Š” {sw.iloc[3]['๊ฒฝ๋ณด ์ •ํ™•๋„(๊ฒŒ์ดํŠธ ์—†์ด 80 ์ด์ƒ)']:.0%}๋กœ ๋‚ฎ๋‹ค. ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ๋Š” ์ด๋Ÿฐ ์‚ฌ์ง„์„ ์ž๋™ ์ฒ˜๋ฆฌ์—์„œ ๋นผ ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ์œผ๋กœ ๋ณด๋‚ธ๋‹ค.\")\n", "\n", "# ---------------- 2) ๋ณตํ•ฉ ์—ดํ™”: ํ๋ฆผ, ์„ผ์„œ ์žก์Œ, ์ €์กฐ๋„+์ง„ํ™ ----------------\n", "rng = np.random.default_rng(3)\n", "cases = {}\n", "m0, i0, _ = make_images(120, seed=40)\n", "cases[\"์ •์ƒ\"] = (m0, i0)\n", "cases[\"ํ๋ฆผ(์ดˆ์  ๋ถˆ๋Ÿ‰)\"] = (m0, np.clip(np.stack([ndi.gaussian_filter(x, sigma=(1.8, 1.8, 0)) for x in i0]), 0, 1))\n", "cases[\"์„ผ์„œ ์žก์Œ +0.08\"] = (m0, np.clip(i0 + rng.normal(0, 0.08, i0.shape), 0, 1).astype(np.float32))\n", "m1, i1, _ = make_images(120, seed=41, mud=0.2, dark=True)\n", "cases[\"์ €์กฐ๋„+์ง„ํ™ 20%\"] = (m1, i1)\n", "rows = []\n", "for name, (m, im) in cases.items():\n", " r, _ = run(im, m)\n", " ok = r[\"true\"] == r[\"ai\"]\n", " auto = (r[\"conf\"] >= 80) & ~r[\"retake\"]\n", " am = ((r[\"ai\"] >= 2) == (r[\"true\"] >= 2))\n", " rows.append((name, ok.mean(), r[\"retake\"].mean(), auto.mean(), am[auto].mean() if auto.any() else np.nan, (r[\"true\"] > r[\"ai\"]).mean()))\n", "cc = pd.DataFrame(rows, columns=[\"์กฐ๊ฑด\", \"๋“ฑ๊ธ‰ ์ผ์น˜์œจ\", \"์žฌ์ดฌ์˜ ์š”๊ตฌ์œจ\", \"์ž๋™ ์ฒ˜๋ฆฌ ๋น„์œจ\", \"์ž๋™ ์ฒ˜๋ฆฌ ๊ฒฝ๋ณด ์ •ํ™•๋„\", \"๊ณผ์†Œ ํŒ์ • ๋น„์œจ\"])\n", "print(\"\\n[2] ๋ณตํ•ฉ ์—ดํ™” ์กฐ๊ฑด(120์žฅ)\\n\" + cc.assign(**{c: cc[c].map(pc) for c in cc.columns[1:]}).to_string(index=False))\n", "print(f\" ํ๋ฆผ ์‚ฌ์ง„์€ ๊ฒŒ์ดํŠธ๊ฐ€ ์ง์ ‘ ์žก์ง€ ๋ชปํ•ด ์ž๋™ ์ฒ˜๋ฆฌ {cc.iloc[1]['์ž๋™ ์ฒ˜๋ฆฌ ๋น„์œจ']:.0%} ์ค‘ ๊ฒฝ๋ณด ์ •ํ™•๋„ {cc.iloc[1]['์ž๋™ ์ฒ˜๋ฆฌ ๊ฒฝ๋ณด ์ •ํ™•๋„']:.0%}๋กœ 95%์— ๋ชป ๋ฏธ์นœ๋‹ค -> ์ดˆ์ ยท์ดฌ์˜ ๊ฑฐ๋ฆฌ ๊ฐ€์ด๋“œ ๋ณด์™„์ด ํ•„์š”ํ•˜๋‹ค.\")\n", "\n", "# ---------------- 3) ๋Œ€๋Ÿ‰ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„๊ณผ ๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ ----------------\n", "tt = np.array(tm).mean(axis=0) / 120\n", "per = tt.sum()\n", "print(f\"\\n[3] ์‚ฌ์ง„ 1์žฅ๋‹น ์ฒ˜๋ฆฌ ์‹œ๊ฐ„(1๊ฐœ ์ฝ”์–ด ๊ฐ€์ •): ๋ถ„ํ• ยทํŠน์ง• {tt[0] * 1000:.1f}ms, ํŒ์ • {tt[1] * 1000:.2f}ms, ์˜ค์—ผ ์ถ”์ •ยท์‹ ๋ขฐ๋„ {tt[2] * 1000:.1f}ms, ํ•ฉ๊ณ„ {per * 1000:.1f}ms ({1 / per:.0f}์žฅ/์ดˆ)\")\n", "for n in (1000, 5000, 20000):\n", " print(f\" {n:>6}์žฅ ์ผ๊ด„ ํŒ์ • ์˜ˆ์ƒ: {n * per:.0f}์ดˆ ({n * per / 60:.1f}๋ถ„) - ์„ ํ˜• ์™ธ์‚ฝ, ์‚ฌ์ง„ ํ•ด์ƒ๋„ยท์žฅ๋น„ ์‚ฌ์–‘์ด ๋‹ค๋ฅด๋ฉด ๋‹ฌ๋ผ์ง„๋‹ค\")\n", "\n", "\n", "def dashboard(df):\n", " t0 = time.perf_counter()\n", " summ = df.groupby(\"ai\").agg(๊ฑด์ˆ˜=(\"idx\", \"size\"), ํ‰๊ท ์‹ ๋ขฐ๋„=(\"conf\", \"mean\"))\n", " low = df.sort_values(\"conf\").head(20)\n", " fg, ax = plt.subplots(1, 2, figsize=(9, 3.5))\n", " ax[0].bar(summ.index.astype(str), summ[\"๊ฑด์ˆ˜\"]); ax[1].hist(df[\"conf\"], bins=20)\n", " fg.canvas.draw(); plt.close(fg)\n", " return time.perf_counter() - t0, len(low)\n", "\n", "\n", "base, _ = run(cases[\"์ •์ƒ\"][1], cases[\"์ •์ƒ\"][0])\n", "loads = []\n", "for n in (300, 3000, 30000, 300000):\n", " big = pd.concat([base] * (n // len(base) + 1), ignore_index=True).head(n)\n", " loads.append((n, dashboard(big)[0]))\n", "print(\" ๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ ์‹œ๊ฐ„(์š”์•ฝ ์ง‘๊ณ„ + ์ƒ์œ„ 20๊ฐœ ๋ชฉ๋ก + ์ฐจํŠธ ๋ Œ๋”๋ง):\", \", \".join(f\"{n}๊ฑด {s:.2f}์ดˆ\" for n, s in loads))\n", "print(f\" 3000๊ฑด ๊ธฐ์ค€ ๋กœ๋”ฉ {loads[1][1]:.2f}์ดˆ(๋ชฉํ‘œ 2์ดˆ ์ด๋‚ด: {'๋‹ฌ์„ฑ' if loads[1][1] <= 2 else '๋ฏธ๋‹ฌ'}), 30๋งŒ ๊ฑด์—์„œ๋„ {loads[3][1]:.2f}์ดˆ. ํ™”๋ฉด์€ ์š”์•ฝ๊ณผ ์‹ ๋ขฐ๋„ ๋‚ฎ์€ ์‚ฌ์ง„ ์šฐ์„  ๋ชฉ๋ก๋งŒ ๋จผ์ € ํ‘œ์‹œํ•œ๋‹ค.\")\n", "\n", "# ---------------- 4) ๊ฒฐ๋ก  ----------------\n", "print(\"\\n[4] ํ•œ๊ณ„์น˜ ์š”์•ฝ\")\n", "print(f\" - ์ž๋™ ์ฒ˜๋ฆฌ(๊ฒ€์‚ฌ๊ด€ ๊ฐ„์ด ์Šน์ธ) ํ›„๋ณด๋Š” ๊ฐ€๋ฆผ์ด ์—†๋Š” ์‚ฌ์ง„ ์ค‘์‹ฌ์ด๋ฉฐ, ์ง„ํ™ {bad[0] if bad else '5%'} ์ด์ƒ๋ถ€ํ„ฐ ๊ฒฝ๋ณด ์ •ํ™•๋„ 95% ๋ชฉํ‘œ๋ฅผ ์ง€ํ‚ค์ง€ ๋ชปํ•ด ์žฌ์ดฌ์˜ ๋˜๋Š” ๊ฒ€์‚ฌ๊ด€ ์ •๋ฐ€ ํŒ๋…์œผ๋กœ ๋ณด๋‚ธ๋‹ค\")\n", "print(f\" - ์ •์ƒ ์‚ฌ์ง„๋„ {1 - sw.iloc[0]['์ž๋™ ์ฒ˜๋ฆฌ ๋น„์œจ']:.0%}๋Š” ์‹ ๋ขฐ๋„ 80 ๋ฏธ๋งŒ์ด๋ผ ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ ๋Œ€์ƒ์ด๋‹ค. ๊ฒฝ๋ณด ์ •ํ™•๋„๋Š” ์ž๋™ ์ฒ˜๋ฆฌ ์‚ฌ์ง„์— ํ•œํ•ด ํŒ๋‹จํ•˜๊ณ  ์ž๋™ ์ฒ˜๋ฆฌ ๋น„์œจ์„ ํ•จ๊ป˜ ๋ณด๊ณ ํ•œ๋‹ค\")\n", "print(\" - ํŒ์ •ยท๊ฒฝ๋ณด๋Š” ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ์˜ ๋ณด์กฐ ์ˆ˜๋‹จ์ด๋ฉฐ ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰์€ ์‚ฌ๋žŒ์ด ํ™•์ •ํ•œ๋‹ค. ๋ชจ๋“  ์‚ฌ์ง„์€ ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 3, figsize=(15, 4.2))\n", "x = np.arange(len(sw))\n", "ax[0].plot(x, sw[\"๋“ฑ๊ธ‰ ์ผ์น˜์œจ(์ „์ฒด)\"] * 100, \"o-\", label=\"grade accuracy (all)\"); ax[0].plot(x, sw[\"์žฌ์ดฌ์˜ ์š”๊ตฌ์œจ\"] * 100, \"s-\", label=\"retake rate\"); ax[0].plot(x, sw[\"์ž๋™ ์ฒ˜๋ฆฌ ๋น„์œจ\"] * 100, \"^-\", label=\"auto-processed\")\n", "ax[0].set_xticks(x); ax[0].set_xticklabels(sw[\"์ง„ํ™ ๋น„์œจ\"]); ax[0].set_xlabel(\"mud ratio\"); ax[0].legend(); ax[0].set_title(\"Mud stress sweep (%)\")\n", "ax[1].bar(range(len(cc)), cc[\"์ž๋™ ์ฒ˜๋ฆฌ ๋น„์œจ\"] * 100); ax[1].set_xticks(range(len(cc))); ax[1].set_xticklabels([\"clean\", \"blur\", \"noise\", \"dark+mud\"]); ax[1].set_title(\"Auto-processed share (%)\")\n", "ax[2].loglog([l[0] for l in loads], [l[1] for l in loads], \"o-\"); ax[2].axhline(2, c=\"r\", ls=\"--\"); ax[2].set_title(\"Dashboard load time (s) vs records\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 08 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ์ง„ํ™ ํ˜ผ์ž… ๋น„์œจ๋ณ„ ํŒ์ •(๊ต์œก์šฉ ๊ฐ€์ƒ ์‚ฌ์ง„, ๋น„์œจ๋‹น 120์žฅ)\n", "์ง„ํ™ ๋น„์œจ ์žฅ์ˆ˜ ๋“ฑ๊ธ‰ ์ผ์น˜์œจ(์ „์ฒด) ํ‰๊ท  ์‹ ๋ขฐ๋„ ์žฌ์ดฌ์˜ ์š”๊ตฌ์œจ ์ž๋™ ์ฒ˜๋ฆฌ ๋น„์œจ ๊ฒฝ๋ณด ์ •ํ™•๋„(๊ฒŒ์ดํŠธ ํ›„) ๊ฒฝ๋ณด ์ •ํ™•๋„(๊ฒŒ์ดํŠธ ์—†์ด 80 ์ด์ƒ) ๊ฒŒ์ดํŠธ ์—†์ด 80 ์ด์ƒ ๋น„์œจ\n", " 0% 120 90% 86.4 0% 73% 100% 100% 73%\n", " 5% 120 65% 67.3 1% 12% 93% 94% 26%\n", " 10% 120 53% 63.3 12% 3% 75% 88% 27%\n", " 20% 120 42% 63.9 66% 0% - 73% 25%\n", " 30% 120 64% 62.1 88% 0% - 77% 11%\n", " 40% 120 59% 59.8 99% 0% - 38% 7%\n", " 50% 120 68% 64.9 100% 0% - 79% 16%\n", " ๊ฒฝ๋ณด ์ •ํ™•๋„๊ฐ€ 95% ๋ฏธ๋งŒ์œผ๋กœ ์ฒ˜์Œ ๋–จ์–ด์ง€๋Š” ์ง„ํ™ ๋น„์œจ: 5% (์ง„ํ™ 0%์—์„œ๋Š” ์ž๋™ ์ฒ˜๋ฆฌ 73%, ๊ฒฝ๋ณด ์ •ํ™•๋„ 100%)\n", " ๊ฒŒ์ดํŠธ๊ฐ€ ์—†์œผ๋ฉด ์ง„ํ™ 20%์—์„œ๋„ ์‹ ๋ขฐ๋„ 80 ์ด์ƒ์ด 25% ๋‚จ๊ณ  ๊ทธ ๊ฒฝ๋ณด ์ •ํ™•๋„๋Š” 73%๋กœ ๋‚ฎ๋‹ค. ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ๋Š” ์ด๋Ÿฐ ์‚ฌ์ง„์„ ์ž๋™ ์ฒ˜๋ฆฌ์—์„œ ๋นผ ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ์œผ๋กœ ๋ณด๋‚ธ๋‹ค.\n", "\n", "[2] ๋ณตํ•ฉ ์—ดํ™” ์กฐ๊ฑด(120์žฅ)\n", " ์กฐ๊ฑด ๋“ฑ๊ธ‰ ์ผ์น˜์œจ ์žฌ์ดฌ์˜ ์š”๊ตฌ์œจ ์ž๋™ ์ฒ˜๋ฆฌ ๋น„์œจ ์ž๋™ ์ฒ˜๋ฆฌ ๊ฒฝ๋ณด ์ •ํ™•๋„ ๊ณผ์†Œ ํŒ์ • ๋น„์œจ\n", " ์ •์ƒ 96% 0% 75% 100% 4%\n", " ํ๋ฆผ(์ดˆ์  ๋ถˆ๋Ÿ‰) 84% 0% 62% 91% 11%\n", "์„ผ์„œ ์žก์Œ +0.08 66% 8% 8% 100% 8%\n", " ์ €์กฐ๋„+์ง„ํ™ 20% 42% 100% 0% - 12%\n", " ํ๋ฆผ ์‚ฌ์ง„์€ ๊ฒŒ์ดํŠธ๊ฐ€ ์ง์ ‘ ์žก์ง€ ๋ชปํ•ด ์ž๋™ ์ฒ˜๋ฆฌ 62% ์ค‘ ๊ฒฝ๋ณด ์ •ํ™•๋„ 91%๋กœ 95%์— ๋ชป ๋ฏธ์นœ๋‹ค -> ์ดˆ์ ยท์ดฌ์˜ ๊ฑฐ๋ฆฌ ๊ฐ€์ด๋“œ ๋ณด์™„์ด ํ•„์š”ํ•˜๋‹ค.\n", "\n", "[3] ์‚ฌ์ง„ 1์žฅ๋‹น ์ฒ˜๋ฆฌ ์‹œ๊ฐ„(1๊ฐœ ์ฝ”์–ด ๊ฐ€์ •): ๋ถ„ํ• ยทํŠน์ง• 4.1ms, ํŒ์ • 0.16ms, ์˜ค์—ผ ์ถ”์ •ยท์‹ ๋ขฐ๋„ 0.3ms, ํ•ฉ๊ณ„ 4.5ms (223์žฅ/์ดˆ)\n", " 1000์žฅ ์ผ๊ด„ ํŒ์ • ์˜ˆ์ƒ: 4์ดˆ (0.1๋ถ„) - ์„ ํ˜• ์™ธ์‚ฝ, ์‚ฌ์ง„ ํ•ด์ƒ๋„ยท์žฅ๋น„ ์‚ฌ์–‘์ด ๋‹ค๋ฅด๋ฉด ๋‹ฌ๋ผ์ง„๋‹ค\n", " 5000์žฅ ์ผ๊ด„ ํŒ์ • ์˜ˆ์ƒ: 22์ดˆ (0.4๋ถ„) - ์„ ํ˜• ์™ธ์‚ฝ, ์‚ฌ์ง„ ํ•ด์ƒ๋„ยท์žฅ๋น„ ์‚ฌ์–‘์ด ๋‹ค๋ฅด๋ฉด ๋‹ฌ๋ผ์ง„๋‹ค\n", " 20000์žฅ ์ผ๊ด„ ํŒ์ • ์˜ˆ์ƒ: 90์ดˆ (1.5๋ถ„) - ์„ ํ˜• ์™ธ์‚ฝ, ์‚ฌ์ง„ ํ•ด์ƒ๋„ยท์žฅ๋น„ ์‚ฌ์–‘์ด ๋‹ค๋ฅด๋ฉด ๋‹ฌ๋ผ์ง„๋‹ค\n", " ๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ ์‹œ๊ฐ„(์š”์•ฝ ์ง‘๊ณ„ + ์ƒ์œ„ 20๊ฐœ ๋ชฉ๋ก + ์ฐจํŠธ ๋ Œ๋”๋ง): 300๊ฑด 0.19์ดˆ, 3000๊ฑด 0.10์ดˆ, 30000๊ฑด 0.14์ดˆ, 300000๊ฑด 0.17์ดˆ\n", " 3000๊ฑด ๊ธฐ์ค€ ๋กœ๋”ฉ 0.10์ดˆ(๋ชฉํ‘œ 2์ดˆ ์ด๋‚ด: ๋‹ฌ์„ฑ), 30๋งŒ ๊ฑด์—์„œ๋„ 0.17์ดˆ. ํ™”๋ฉด์€ ์š”์•ฝ๊ณผ ์‹ ๋ขฐ๋„ ๋‚ฎ์€ ์‚ฌ์ง„ ์šฐ์„  ๋ชฉ๋ก๋งŒ ๋จผ์ € ํ‘œ์‹œํ•œ๋‹ค.\n", "\n", "[4] ํ•œ๊ณ„์น˜ ์š”์•ฝ\n", " - ์ž๋™ ์ฒ˜๋ฆฌ(๊ฒ€์‚ฌ๊ด€ ๊ฐ„์ด ์Šน์ธ) ํ›„๋ณด๋Š” ๊ฐ€๋ฆผ์ด ์—†๋Š” ์‚ฌ์ง„ ์ค‘์‹ฌ์ด๋ฉฐ, ์ง„ํ™ 5% ์ด์ƒ๋ถ€ํ„ฐ ๊ฒฝ๋ณด ์ •ํ™•๋„ 95% ๋ชฉํ‘œ๋ฅผ ์ง€ํ‚ค์ง€ ๋ชปํ•ด ์žฌ์ดฌ์˜ ๋˜๋Š” ๊ฒ€์‚ฌ๊ด€ ์ •๋ฐ€ ํŒ๋…์œผ๋กœ ๋ณด๋‚ธ๋‹ค\n", " - ์ •์ƒ ์‚ฌ์ง„๋„ 27%๋Š” ์‹ ๋ขฐ๋„ 80 ๋ฏธ๋งŒ์ด๋ผ ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ ๋Œ€์ƒ์ด๋‹ค. ๊ฒฝ๋ณด ์ •ํ™•๋„๋Š” ์ž๋™ ์ฒ˜๋ฆฌ ์‚ฌ์ง„์— ํ•œํ•ด ํŒ๋‹จํ•˜๊ณ  ์ž๋™ ์ฒ˜๋ฆฌ ๋น„์œจ์„ ํ•จ๊ป˜ ๋ณด๊ณ ํ•œ๋‹ค\n", " - ํŒ์ •ยท๊ฒฝ๋ณด๋Š” ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ์˜ ๋ณด์กฐ ์ˆ˜๋‹จ์ด๋ฉฐ ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰์€ ์‚ฌ๋žŒ์ด ํ™•์ •ํ•œ๋‹ค. ๋ชจ๋“  ์‚ฌ์ง„์€ ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 09. AAR ๊ธฐ๋ฐ˜ ์™ธ๊ด€ ์˜คํŒ ์‚ฌ๋ก€ ๋ถ„์„ ๋ฐ ์ปดํ“จํ„ฐ ๋น„์ „ ๋ชจ๋ธ ํŒŒ๋ผ๋ฏธํ„ฐ ์žฌ๋ณด์ •\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "AAR ๊ธฐ๋ฐ˜ ์™ธ๊ด€ ์˜คํŒ ์‚ฌ๋ก€๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์ปดํ“จํ„ฐ ๋น„์ „ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์žฌ๋ณด์ •ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **AAR๋กœ ์™ธ๊ด€ ์˜คํŒ ์‚ฌ๋ก€๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์ปดํ“จํ„ฐ ๋น„์ „ ๋ชจ๋ธ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์žฌ๋ณด์ •ํ•˜๋ฉฐ ๋„์ž… ํšจ๊ณผ๋ฅผ ์ •๋Ÿ‰ํ™”ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์šด์˜ ๋ถ„ํฌ(์ €์กฐ๋„ยท์ง„ํ™ยท์ •์ƒ ํ˜ผํ•ฉ)์˜ ํŒ์ • ๋กœ๊ทธ์—์„œ ์˜คํŒ์„ ์˜ค์—ผ ๊ฐ€๋ฆผยท์ €์กฐ๋„ยท์ž‘์€ ๊ฒฐํ•จยท์œ ํ˜• ํ˜ผ๋™ยท๊ฒฝ๊ณ„ ๊ทผ์ฒ˜๋กœ ๋ถ„๋ฅ˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `reports/qlt03_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์„ ์ˆ˜ํ–‰ํ•ด ์˜คํŒ ์‚ฌ๋ก€๋ฅผ ์›์ธ๋ณ„(์˜ค์—ผ ๊ฐ€๋ฆผยท์ €์กฐ๋„ยท์ž‘์€ ๊ฒฐํ•จยท๊ฒฝ๊ณ„ ๊ทผ์ฒ˜ยท์œ ํ˜• ํ˜ผ๋™)๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ , ๋ถ„ํ•  ์ž„๊ณ„๊ฐ’ยทํ•™์Šต ๋ฐ์ดํ„ฐยท๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„(๊ณผ์†Œ ํŒ์ • ์†์‹ค ๋ฐ˜์˜)๋ฅผ ์žฌ๋ณด์ •ํ•ด ๋ณด๋ฅ˜ ๊ตฌ๊ฐ„์—์„œ ๊ฐœ์„ ์„ ๊ฒ€์ฆํ•œ ๋’ค ๋น„์šฉ ์ ˆ๊ฐ์•กยท์‹œ๊ฐ„ ๋‹จ์ถ•๋ฅ ยทํˆฌ์ž ํšŒ์ˆ˜ ๊ธฐ๊ฐ„ 3๋Œ€ ํšจ๊ณผ ์ง€ํ‘œ๋ฅผ ์‚ฐ์‹๊ณผ ๋ฏผ๊ฐ๋„๋กœ ์‚ฐ์ถœํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.ensemble import RandomForestClassifier, GradientBoostingRegressor\n", "from sklearn.model_selection import cross_val_predict\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฅ๋น„ ์™ธ๊ด€ ์‚ฌ์ง„ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์œ„์น˜ ์‚ฌ์ง„์ด ์•„๋‹ˆ๋ผ ๋‚œ์ˆ˜๋กœ ๊ทธ๋ฆฐ 64x64 ํ•ฉ์„ฑ ์ด๋ฏธ์ง€)\n", "# ๋„์žฅ๋œ ๊ธˆ์† ํ‘œ๋ฉด์— ๊ท ์—ด(crack)ยท๋ถ€์‹(corrosion)ยทํŒŒ์†(damage)ยท๋ˆ„์œ (leak)๋ฅผ ๊ทธ๋ฆฌ๊ณ , ์กฐ๋ช… ํŽธ์ฐจ์™€ ์ง„ํ™(mud) ์˜ค์—ผ์„ ์„ž๋Š”๋‹ค.\n", "# ์ด๋ฏธ์ง€์™€ ํ•จ๊ป˜ ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ)๊ณผ ์ •๋‹ต ๊ฒฐํ•จ ๋งˆ์Šคํฌยท์†์ƒ๋„ยท์ •๋น„ ๋“ฑ๊ธ‰์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "from scipy import ndimage as ndi\n", "\n", "SZ = 64\n", "CLASSES = [\"none\", \"crack\", \"corrosion\", \"damage\", \"leak\"]\n", "KO_CLS = {\"none\": \"๊ฒฐํ•จ ์—†์Œ\", \"crack\": \"๊ท ์—ด\", \"corrosion\": \"๋ถ€์‹\", \"damage\": \"ํŒŒ์†\", \"leak\": \"๋ˆ„์œ \"}\n", "CLS_W = {\"none\": 0.0, \"crack\": 1.4, \"corrosion\": 1.0, \"damage\": 1.6, \"leak\": 0.6} # ์†์ƒ๋„ ๊ณ„์‚ฐ์šฉ ๊ฒฐํ•จ ์œ ํ˜• ๊ฐ€์ค‘์น˜\n", "GRADE_NAME = {1: \"ํ˜„์žฅ์ •๋น„\", 2: \"์•ผ์ „์ •๋น„\", 3: \"์ฐฝ์ •๋น„\"}\n", "YY, XX = np.mgrid[0:SZ, 0:SZ]\n", "\n", "\n", "def grade_of(idx):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 9] AAR ๊ธฐ๋ฐ˜ ์™ธ๊ด€ ์˜คํŒ ์‚ฌ๋ก€ ๋ถ„์„ ๋ฐ ์ปดํ“จํ„ฐ ๋น„์ „ ๋ชจ๋ธ ํŒŒ๋ผ๋ฏธํ„ฐ ์žฌ๋ณด์ • ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def blob(rng, cy, cx, r, rough=0.35):\n", " \"\"\"๋ถˆ๊ทœ์น™ํ•œ ์›ํ˜• ์˜์—ญ ๋งˆ์Šคํฌ.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 9] AAR ๊ธฐ๋ฐ˜ ์™ธ๊ด€ ์˜คํŒ ์‚ฌ๋ก€ ๋ถ„์„ ๋ฐ ์ปดํ“จํ„ฐ ๋น„์ „ ๋ชจ๋ธ ํŒŒ๋ผ๋ฏธํ„ฐ ์žฌ๋ณด์ • ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def draw(rng, cls, sev):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜•ยท์‹ฌ๊ฐ๋„ sev(0~1)์— ๋งž๋Š” ์ •๋‹ต ๋งˆ์Šคํฌ์™€ ์ƒ‰ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m = np.zeros((SZ, SZ), bool)\n", " if cls == \"crack\":\n", " y, x, ang = rng.uniform(12, 52), rng.uniform(12, 52), rng.uniform(0, np.pi)\n", " for _ in range(int(20 + 40 * sev)):\n", " ang += rng.normal(0, 0.35); y += np.sin(ang) * 1.2; x += np.cos(ang) * 1.2\n", " if 0 <= int(y) < SZ and 0 <= int(x) < SZ:\n", " m[int(y), int(x)] = True\n", " m = ndi.binary_dilation(m, iterations=1 + int(sev > 0.6))\n", " elif cls == \"corrosion\":\n", " for _ in range(int(rng.integers(1, 4))):\n", " m |= blob(rng, rng.uniform(14, 50), rng.uniform(14, 50), 3 + 9 * sev * rng.uniform(0.6, 1.1))\n", " elif cls == \"damage\":\n", " m = blob(rng, rng.uniform(20, 44), rng.uniform(20, 44), 6 + 12 * sev, 0.3)\n", " elif cls == \"leak\":\n", " x0, y0 = rng.uniform(16, 48), rng.uniform(8, 24)\n", " w, ln = 2 + 4 * sev, 12 + 26 * sev\n", " m = (np.abs(XX - x0 - 0.15 * (YY - y0)) < w * (1 - 0.4 * (YY - y0) / ln)) & (YY > y0) & (YY < y0 + ln)\n", " return m\n", "\n", "\n", "def make_images(n=600, seed=0, mud=0.0, p_none=0.25, dark=False):\n", " \"\"\"๊ฐ€์ƒ ์™ธ๊ด€ ์‚ฌ์ง„ n์žฅ(+์ง„ํ™ ์˜ค์—ผ mud ๋น„์œจ)๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋กยท์ •๋‹ต. ๋ฐ˜ํ™˜: meta(DataFrame), imgs(n,64,64,3), masks(n,64,64).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " imgs, masks, rows = np.zeros((n, SZ, SZ, 3), np.float32), np.zeros((n, SZ, SZ), bool), []\n", " for i in range(n):\n", " cls = \"none\" if rng.random() < p_none else str(rng.choice(CLASSES[1:], p=[0.25, 0.3, 0.25, 0.2]))\n", " sev = 0.0 if cls == \"none\" else float(rng.beta(1.6, 2.0))\n", " base = np.array([0.42, 0.46, 0.40]) * (1 + 0.5 * ndi.gaussian_filter(rng.normal(size=(SZ, SZ)), 8)[..., None] * 3)\n", " img = base + rng.normal(0, 0.012, (SZ, SZ, 3))\n", " m = draw(rng, cls, sev)\n", " a = ndi.gaussian_filter(m.astype(float), 0.8)[..., None] # ๊ฒฝ๊ณ„๋ฅผ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์„ž๋Š”๋‹ค\n", " if cls == \"crack\":\n", " col = img * rng.uniform(0.2, 0.45)\n", " elif cls == \"corrosion\":\n", " col = np.array([0.55, 0.30, 0.12]) * rng.uniform(0.75, 1.15) * (0.85 + 0.3 * rng.random((SZ, SZ, 1)))\n", " elif cls == \"damage\":\n", " col = np.ones_like(img) * np.array([0.34, 0.34, 0.36]) * (1.0 - 0.45 * sev) * rng.uniform(0.75, 1.0)\n", " rim = m & ~ndi.binary_erosion(m, iterations=2)\n", " a = np.maximum(a, 0.0); img = np.where(rim[..., None], 0.7, img)\n", " elif cls == \"leak\":\n", " col = np.ones_like(img) * np.array([0.10, 0.09, 0.08]) * rng.uniform(0.8, 2.6)\n", " else:\n", " col = img\n", " img = img * (1 - a) + col * a\n", " if rng.random() < 0.35: # ๋ฌดํ•ดํ•œ ์–ผ๋ฃฉ(๊ฒฐํ•จ ์•„๋‹˜): ์˜…์€ ๊ฐˆ์ƒ‰ ๋ฒˆ์ง\n", " st = blob(rng, rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(6, 14), 0.5)\n", " al = ndi.gaussian_filter(st.astype(float), 2.5)[..., None] * rng.uniform(0.15, 0.4)\n", " img = img * (1 - al) + np.array([0.50, 0.40, 0.30]) * al\n", " if rng.random() < 0.3: # ๋ฌดํ•ดํ•œ ๊ธด ์Šคํฌ๋ž˜์น˜(๋ฐ์€ ์„ )\n", " y0, x0, ang = rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(0, np.pi)\n", " for t in np.linspace(0, 25, 40):\n", " yy, xx = int(y0 + t * np.sin(ang)), int(x0 + t * np.cos(ang))\n", " if 0 <= yy < SZ and 0 <= xx < SZ:\n", " img[yy, xx] = np.clip(img[yy, xx] * 1.5, 0, 1)\n", " if mud > 0: # ์ง„ํ™ ์˜ค์—ผ: ๊ฒฐํ•จ ์œ„๋ฅผ ๋ฎ์„ ์ˆ˜ ์žˆ๋‹ค(์ •๋‹ต ๋งˆ์Šคํฌ๋Š” ๊ทธ๋Œ€๋กœ)\n", " mm = np.zeros((SZ, SZ), bool)\n", " while mm.mean() < mud:\n", " mm |= blob(rng, rng.uniform(0, SZ), rng.uniform(0, SZ), rng.uniform(5, 11), 0.5)\n", " img[mm] = np.array([0.38, 0.30, 0.22]) * (0.85 + 0.3 * rng.random((mm.sum(), 1)))\n", " gain = rng.uniform(0.3, 0.55) if dark else rng.uniform(0.5, 1.15)\n", " ang = rng.uniform(0, 2 * np.pi)\n", " light = gain * (1 + 0.3 * (np.cos(ang) * (XX - 32) + np.sin(ang) * (YY - 32)) / 32)\n", " imgs[i] = np.clip(img * light[..., None] + rng.normal(0, 0.025, (SZ, SZ, 3)), 0, 1)\n", " masks[i] = m\n", " area = float(m.mean())\n", " depth = {\"none\": 0.0, \"crack\": 0.3 + 3.0 * sev, \"corrosion\": 0.1 + 1.5 * sev, \"damage\": 1.0 + 8.0 * sev, \"leak\": 0.0}[cls] * rng.lognormal(0, 0.1)\n", " idx = float(np.clip(100 * (1 - np.exp(-(CLS_W[cls] * area * 9 + 0.05 * depth))) + rng.normal(0, 1.5), 0, 100))\n", " rows.append({\"img_id\": f\"IMG{seed:02d}{i:04d}\", \"cls\": cls, \"sev\": sev, \"area_frac\": area, \"depth_mm\": depth, \"damage_idx\": idx,\n", " \"grade\": int(grade_of(idx)), \"gain\": gain, \"mud\": mud})\n", " meta = pd.DataFrame(rows)\n", " # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ): ์‚ฌ์ง„ ํŒ์ •๊ณผ ๋Œ€์กฐํ•  ๊ฐ’\n", " age = rng.integers(1, 25, n)\n", " meta[\"day\"] = np.sort(rng.integers(0, 90, n))\n", " meta[\"equip_type\"] = rng.choice([\"vehicle\", \"generator\", \"comm\", \"trailer\"], n, p=[0.4, 0.2, 0.2, 0.2])\n", " meta[\"mfg_year\"] = 2026 - age\n", " meta[\"op_hours\"] = np.clip(rng.normal(180 * age, 400, n) + 8 * meta[\"damage_idx\"], 10, 20000)\n", " meta[\"env\"] = rng.choice([\"coastal\", \"inland\"], n, p=[0.3, 0.7])\n", " reg = np.clip(meta[\"grade\"] + rng.choice([-1, 0, 0, 0, 0, 0, 1, 2], n), 1, 3) # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก์ƒ ์ง์ „ ์ •๋น„ ๋“ฑ๊ธ‰\n", " meta[\"reg_grade\"] = reg\n", " meta[\"emergency\"] = rng.random(n) < 0.04 # ๊ธด๊ธ‰ ์ ๊ฒ€ ์š”์ฒญ(ํ™”์งˆ ๋ถˆ๋Ÿ‰์ด์–ด๋„ ๋ณด์กด)\n", " return meta, imgs, masks\n", "# ---- ์ „์ฒ˜๋ฆฌ(์กฐ๋ช… ๋ณด์ •ยทํ‘œ์ค€ํ™”)์™€ ๊ฒฐํ•จ ํ›„๋ณด ๋ถ„ํ• , ํŠน์ง• ์ถ”์ถœ (cv2 ๋Œ€์‹  numpyยทscipy ์‚ฌ์šฉ) ----\n", "def correct(img):\n", " \"\"\"์กฐ๋ช… ๋ณด์ •: ๋ฐ๊ธฐ์— ํ‰๋ฉด(์กฐ๋ช… ๊ธฐ์šธ๊ธฐ)์„ ์ ํ•ฉํ•ด ๋‚˜๋ˆˆ๋‹ค. ์–ด๋‘์šด ๊ฒฐํ•จ ํ™”์†Œ๋Š” ์ œ์™ธํ•˜๊ณ  ๋‘ ๋ฒˆ ๋ฐ˜๋ณต ์ ํ•ฉํ•œ๋‹ค. ๋ฐ˜ํ™˜: ๋ณด์ • ๋ฐ๊ธฐ(์ •์ƒ ๋ฉด ์•ฝ 1), ์ƒ‰ ๋น„์œจ.\"\"\"\n", " g = img.mean(axis=2)\n", " A = np.c_[np.ones(SZ * SZ), XX.ravel() / SZ, YY.ravel() / SZ]\n", " keep = np.ones(SZ * SZ, bool)\n", " for _ in range(3):\n", " coef, *_ = np.linalg.lstsq(A[keep], g.ravel()[keep], rcond=None)\n", " plane = (A @ coef).reshape(SZ, SZ)\n", " keep = (np.abs(g - plane) < 0.08 * np.maximum(plane, 1e-3)).ravel()\n", " if keep.sum() < 200:\n", " keep[:] = True\n", " c = g / np.maximum(plane, 1e-3)\n", " chroma = img[..., 0] / (img.sum(axis=2) + 1e-6)\n", " return c, chroma\n", "\n", "\n", "SEGP = {\"dark\": 0.7, \"rust\": 0.5, \"rim\": 1.45} # ๋ถ„ํ•  ์ž„๊ณ„๊ฐ’(์–ด๋‘์›€ ๋ณด์ • ๋ฐ๊ธฐ, ๋ถ‰์€ ๋น„์œจ, ๋ฐ์€ ํ…Œ๋‘๋ฆฌ): ์žฌ๋ณด์ • ๋Œ€์ƒ\n", "\n", "\n", "def segment(img):\n", " \"\"\"๊ฒฐํ•จ ํ›„๋ณด ๋งˆ์Šคํฌ: ์–ด๋‘์šด ์˜์—ญ(๊ท ์—ดยท๋ˆ„์œ ยทํŒŒ์†) + ๋ถ‰์€ ๊ฐˆ์ƒ‰ ์˜์—ญ(๋ถ€์‹) + ๋ฐ์€ ํ…Œ๋‘๋ฆฌ(ํŒŒ์†).\"\"\"\n", " c, chroma = correct(img)\n", " dark = c < SEGP[\"dark\"]\n", " rust = (chroma > SEGP[\"rust\"]) & (c > 0.45)\n", " rim = c > SEGP[\"rim\"]\n", " m = dark | rust | ndi.binary_dilation(rim, iterations=1) & ndi.binary_dilation(dark, iterations=3)\n", " m = ndi.binary_opening(m, structure=np.ones((2, 2))) if m.sum() > 12 else m\n", " return m, c, chroma, dark, rust, rim\n", "\n", "\n", "def extract(img):\n", " \"\"\"๋ถ„ํ•  ๊ฒฐ๊ณผ์™€ ์ƒ‰ยท๋ชจ์–‘์—์„œ 24๊ฐœ ํŠน์ง•์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m, c, chroma, dark, rust, rim = segment(img)\n", " lab, n = ndi.label(m)\n", " area = float(m.sum())\n", " if n:\n", " sizes = ndi.sum(m, lab, range(1, n + 1))\n", " k = int(np.argmax(sizes)) + 1\n", " sl = ndi.find_objects(lab)[k - 1]\n", " h, w = sl[0].stop - sl[0].start, sl[1].stop - sl[1].start\n", " big = float(sizes.max())\n", " elong, fill = max(h, w) / max(min(h, w), 1), big / (h * w)\n", " else:\n", " big, elong, fill = 0.0, 1.0, 0.0\n", " gy, gx = np.gradient(c)\n", " mag = np.hypot(gx, gy)\n", " inside = m.sum() > 0\n", " return {\"area_px\": area, \"area_frac\": area / SZ ** 2, \"n_comp\": float(n), \"big_px\": big, \"elong\": elong, \"fill\": fill,\n", " \"dark_frac\": float(dark.mean()), \"rust_frac\": float(rust.mean()), \"rim_frac\": float(rim.mean()),\n", " \"c_mean\": float(c.mean()), \"c_std\": float(c.std()), \"c_min\": float(c.min()), \"edge_mean\": float(mag.mean()), \"edge_p95\": float(np.quantile(mag, 0.95)),\n", " \"chroma_mean\": float(chroma.mean()), \"chroma_max\": float(chroma.max()),\n", " \"in_dark\": float(c[m].mean()) if inside else 1.0, \"in_chroma\": float(chroma[m].mean()) if inside else float(chroma.mean()),\n", " \"ring_contrast\": float(c[ndi.binary_dilation(m, iterations=3) & ~m].mean() - c[m].mean()) if inside and (~m).any() else 0.0,\n", " \"thin\": float(1 - fill), \"gain\": float(img.mean()), \"sat\": float((img.max(axis=2) - img.min(axis=2)).mean()),\n", " \"cy\": float(ndi.center_of_mass(m)[0] / SZ) if inside else 0.5, \"cx\": float(ndi.center_of_mass(m)[1] / SZ) if inside else 0.5}\n", "\n", "\n", "def feature_table(imgs):\n", " return pd.DataFrame([extract(im) for im in imgs])\n", "# ---- ํŒ์ • ํŒŒ์ดํ”„๋ผ์ธ: ๊ฒฐํ•จ ์œ ํ˜• ๋ถ„๋ฅ˜ + ์†์ƒ๋„ ํšŒ๊ท€(๋ถ„๋ฅ˜ ํ™•๋ฅ  ํฌํ•จ) -> ์ •๋น„ ๋“ฑ๊ธ‰ ----\n", "def fit_pipeline(meta, F):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜• ๋ถ„๋ฅ˜๊ธฐ์™€ ์†์ƒ๋„ ํšŒ๊ท€๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค. ํšŒ๊ท€๊ธฐ ์ž…๋ ฅ์˜ ๋ถ„๋ฅ˜ ํ™•๋ฅ ์€ ๊ต์ฐจ ์˜ˆ์ธก์œผ๋กœ ๋งŒ๋“ค์–ด ๋ˆ„์„ค์„ ๋ง‰๋Š”๋‹ค.\"\"\"\n", " clf = RandomForestClassifier(n_estimators=150, random_state=0, n_jobs=1)\n", " P = cross_val_predict(clf, F, meta[\"cls\"], cv=4, method=\"predict_proba\")\n", " clf.fit(F, meta[\"cls\"])\n", " reg = GradientBoostingRegressor(n_estimators=200, max_depth=3, learning_rate=0.06, random_state=0).fit(np.c_[F.values, P], meta[\"damage_idx\"])\n", " return {\"clf\": clf, \"reg\": reg, \"cols\": list(F.columns)}\n", "\n", "\n", "def predict_pipeline(pl, F):\n", " X = F[pl[\"cols\"]]\n", " P = pl[\"clf\"].predict_proba(X)\n", " idx = np.clip(pl[\"reg\"].predict(np.c_[X.values, P]), 0, 100)\n", " return pl[\"clf\"].classes_[P.argmax(axis=1)], P, idx, P.max(axis=1)\n", "# ---- ์‚ฌ์ง„ ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ: ์ง„ํ™ยท์˜ค์—ผ ๊ฐ€๋ฆผ ์ถ”์ •๊ณผ ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ----\n", "def mud_est(img):\n", " \"\"\"์ง„ํ™(๊ฐˆ์ƒ‰, ์ฑ„๋„ ๋‚ฎ์€ ๋ถ‰์€ ๊ธฐ) ํ™”์†Œ ๋น„์œจ์„ ์ถ”์ •ํ•œ๋‹ค. 3x3 ํ‰๊ท ์œผ๋กœ ์žก์Œ์„ ์ค„์ธ ๋’ค ์ƒ‰ ๋น„์œจ๋กœ ํŒ๋‹จํ•˜๋ฉฐ, ์‹ค์ œ ๊ฐ€๋ฆผ ๋น„์œจ๋ณด๋‹ค ์ž‘๊ฒŒ ๋‚˜์˜ค์ง€๋งŒ ์ˆœ์„œ๋Š” ์œ ์ง€๋œ๋‹ค.\"\"\"\n", " sm = ndi.uniform_filter(img, size=(3, 3, 1))\n", " s = sm.sum(axis=2) + 1e-6\n", " rr, gg = sm[..., 0] / s, sm[..., 1] / s\n", " return float(((rr > 0.385) & (rr < 0.46) & (gg < 0.325)).mean())\n", "\n", "\n", "def confidence(P, idx, F, mud):\n", " \"\"\"ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ 0~100 = 50 x ๋ถ„๋ฅ˜ ํ™•๋ฅ  ์ตœ๋Œ€๊ฐ’ + 35 x ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„๊นŒ์ง€ ๊ฑฐ๋ฆฌ(8์ ์—์„œ ํฌํ™”) + 15 x ์‚ฌ์ง„ ํ’ˆ์งˆ. ์˜ค์—ผ ์˜์‹ฌ(mud ์ถ”์ • 1% ์ด์ƒ)์ด๋ฉด ์ƒํ•œ 75๋กœ ์ œํ•œํ•œ๋‹ค.\"\"\"\n", " bd = np.minimum(np.abs(idx - 35), np.abs(idx - 65))\n", " qual = np.clip(1.0 - np.clip(0.55 - F[\"gain\"].values, 0, None) * 2 - np.clip(0.9 - F[\"c_mean\"].values, 0, None), 0, 1)\n", " c = 50 * P.max(axis=1) + 35 * np.clip(bd / 8, 0, 1) + 15 * qual\n", " return np.where(np.asarray(mud) >= 0.01, np.minimum(c, 75.0), c)\n", "\n", "mtr, itr, _ = make_images(600, seed=1)\n", "Ftr = feature_table(itr)\n", "pl0 = fit_pipeline(mtr, Ftr)\n", "LOSS_UNDER, LOSS_OVER = 20.0, 6.0 # ๊ณผ์†Œ/๊ณผ๋Œ€ ํŒ์ • 1๋“ฑ๊ธ‰๋‹น ์†์‹ค(๋งŒ์›, ๊ฐ€์ •)\n", "\n", "\n", "def ops(seed, n):\n", " \"\"\"์šด์˜ ์‚ฌ์ง„ ๋ฌถ์Œ: ์ €์กฐ๋„ 1/3 + ์ง„ํ™ 5% 1/3 + ์ •์ƒ 1/3 (ํ•™์Šต ๋•Œ์™€ ๋ถ„ํฌ๊ฐ€ ๋‹ค๋ฅด๋‹ค).\"\"\"\n", " parts = [make_images(n // 3, seed=seed, dark=True), make_images(n // 3, seed=seed + 1, mud=0.05), make_images(n // 3, seed=seed + 2)]\n", " return pd.concat([p[0] for p in parts], ignore_index=True), np.concatenate([p[1] for p in parts]), np.concatenate([p[2] for p in parts])\n", "\n", "\n", "def loss(true, g):\n", " d = np.asarray(g) - np.asarray(true)\n", " return np.where(d < 0, -d * LOSS_UNDER, d * LOSS_OVER)\n", "\n", "\n", "def grade_shift(idx, delta):\n", " \"\"\"๊ณผ์†Œ ํŒ์ •์„ ์ค„์ด๊ธฐ ์œ„ํ•ด ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„๋ฅผ delta๋งŒํผ ๋‚ฎ์ถ˜๋‹ค(35 -> 35-delta, 65 -> 65-delta).\"\"\"\n", " return np.where(np.asarray(idx) >= 65 - delta, 3, np.where(np.asarray(idx) >= 35 - delta, 2, 1))\n", "\n", "\n", "def evaluate(pl, F, m, ims, delta=0):\n", " cls, P, idx, _ = predict_pipeline(pl, F)\n", " mud = np.array([mud_est(x) for x in ims])\n", " conf = confidence(P, idx, F, mud)\n", " g = grade_shift(idx, delta)\n", " auto = (conf >= 80) & (mud < 0.03) & (F[\"gain\"].values >= 0.17)\n", " ok = g == m[\"grade\"].values\n", " return {\"cls\": cls, \"idx\": idx, \"g\": g, \"mud\": mud, \"conf\": conf, \"ok\": ok, \"mae\": float(np.mean(np.abs(idx - m[\"damage_idx\"].values))), \"under\": int((g < m[\"grade\"].values).sum()),\n", " \"loss\": float(loss(m[\"grade\"].values, g).mean()), \"auto_n\": int(auto.sum()), \"auto_acc\": float(ok[auto].mean()) if auto.any() else np.nan}\n", "\n", "\n", "# ---------------- 1) AAR: ์šด์˜ ๋กœ๊ทธ ๋ถ„์„(์ด๋™ ๊ตฌ๊ฐ„ A, 360์žฅ) ----------------\n", "mA, iA, kA = ops(60, 360)\n", "rng = np.random.default_rng(0)\n", "perm = rng.permutation(len(mA))\n", "FA = feature_table(iA)\n", "ev = evaluate(pl0, FA, mA, iA)\n", "print(f\"[1] AAR ๋Œ€์ƒ ์šด์˜ ์‚ฌ์ง„ {len(mA)}์žฅ(์ €์กฐ๋„ยท์ง„ํ™ 5%ยท์ •์ƒ ํ˜ผํ•ฉ): ๋“ฑ๊ธ‰ ์ผ์น˜์œจ {ev['ok'].mean():.1%}, ์†์ƒ๋„ MAE {ev['mae']:.2f}, ๊ณผ์†Œ ํŒ์ • {ev['under']}์žฅ, ์žฅ๋‹น ์˜คํŒ ์†์‹ค {ev['loss']:.2f}๋งŒ์›\")\n", "wrong = np.where(~ev[\"ok\"])[0]\n", "\n", "\n", "def cause(i):\n", " if ev[\"mud\"][i] >= 0.01: return \"์˜ค์—ผ ๊ฐ€๋ฆผ\"\n", " if FA[\"gain\"].iloc[i] < 0.2: return \"์ €์กฐ๋„\"\n", " if mA[\"cls\"][i] != \"none\" and mA[\"area_frac\"][i] < 0.02: return \"์ž‘์€ ๊ฒฐํ•จ ๋ˆ„๋ฝยท๊ณผ์†Œ\"\n", " if ev[\"cls\"][i] != mA[\"cls\"][i]: return \"๊ฒฐํ•จ ์œ ํ˜• ํ˜ผ๋™\"\n", " if min(abs(ev[\"idx\"][i] - 35), abs(ev[\"idx\"][i] - 65)) < 6: return \"๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„ ๊ทผ์ฒ˜\"\n", " return \"๊ธฐํƒ€\"\n", "\n", "\n", "cs = pd.Series([cause(i) for i in wrong]).value_counts()\n", "print(f\" ์˜คํŒ {len(wrong)}์žฅ์˜ ์›์ธ ๋ถ„๋ฅ˜:\", \", \".join(f\"{k} {v}์žฅ({v / len(wrong):.0%})\" for k, v in cs.items()))\n", "print(f\" ์˜คํŒ ์ค‘ ๊ณผ์†Œ ํŒ์ •(์‹ค์ œ๋ณด๋‹ค ๋‚ฎ์€ ๋“ฑ๊ธ‰) {ev['under']}์žฅ / ๊ณผ๋Œ€ {int((ev['g'] > mA['grade'].values).sum())}์žฅ -> ๊ณผ์†Œ ํŒ์ •์ด ์†์‹ค ๋‹จ๊ฐ€๊ฐ€ {LOSS_UNDER / LOSS_OVER:.1f}๋ฐฐ ๋†’์•„ ์šฐ์„  ๊ต์ • ๋Œ€์ƒ\")\n", "\n", "# ---------------- 2) ์žฌ๋ณด์ •: ๋ถ„ํ•  ์ž„๊ณ„๊ฐ’ -> ์šด์˜ ๋ฐ์ดํ„ฐ ์žฌํ•™์Šต -> ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„ ์ด๋™ ----------------\n", "A1, A2 = perm[:240], perm[240:]\n", "cache = [correct(iA[i]) for i in A1]\n", "\n", "\n", "def mask_from(c, chroma, dt, rt, rim=1.45):\n", " dark, rust, r = c < dt, (chroma > rt) & (c > 0.45), c > rim\n", " m = dark | rust | ndi.binary_dilation(r, iterations=1) & ndi.binary_dilation(dark, iterations=3)\n", " return ndi.binary_opening(m, structure=np.ones((2, 2))) if m.sum() > 12 else m\n", "\n", "\n", "grid = []\n", "for dt in (0.6, 0.7, 0.75, 0.8):\n", " for rt in (0.44, 0.5, 0.54):\n", " iou = np.mean([(mask_from(c, ch, dt, rt) & kA[i]).sum() / max((mask_from(c, ch, dt, rt) | kA[i]).sum(), 1) for (c, ch), i in zip(cache, A1)])\n", " grid.append((dt, rt, iou))\n", "gd = pd.DataFrame(grid, columns=[\"dark ์ž„๊ณ„๊ฐ’\", \"rust ์ž„๊ณ„๊ฐ’\", \"ํ‰๊ท  IoU\"])\n", "bst = gd.loc[gd[\"ํ‰๊ท  IoU\"].idxmax()]\n", "cur = gd[(gd[\"dark ์ž„๊ณ„๊ฐ’\"] == 0.7) & (gd[\"rust ์ž„๊ณ„๊ฐ’\"] == 0.5)][\"ํ‰๊ท  IoU\"].iloc[0]\n", "print(f\"\\n[2] ๋ถ„ํ•  ์ž„๊ณ„๊ฐ’ ์žฌ๋ณด์ •(์šด์˜ ์‚ฌ์ง„ 240์žฅ IoU): ํ˜„ํ–‰(0.70, 0.50) {cur:.3f} -> ์ตœ์ ({bst['dark ์ž„๊ณ„๊ฐ’']:.2f}, {bst['rust ์ž„๊ณ„๊ฐ’']:.2f}) {bst['ํ‰๊ท  IoU']:.3f}\")\n", "SEGP[\"dark\"], SEGP[\"rust\"] = float(bst[\"dark ์ž„๊ณ„๊ฐ’\"]), float(bst[\"rust ์ž„๊ณ„๊ฐ’\"])\n", "FA2 = feature_table(iA)\n", "mm = pd.concat([mtr, mA.iloc[A1]], ignore_index=True)\n", "pl1 = fit_pipeline(mm, pd.concat([Ftr, FA2.iloc[A1]], ignore_index=True)) # ์šด์˜ ๋ถ„ํฌ ์‚ฌ์ง„ 240์žฅ์„ ์ถ”๊ฐ€ ํ•™์Šต\n", "ev2 = evaluate(pl1, FA2.iloc[A2].reset_index(drop=True), mA.iloc[A2].reset_index(drop=True), iA[A2])\n", "costs = []\n", "for dlt in (0, 2, 4, 6, 8):\n", " r = evaluate(pl1, FA2.iloc[A2].reset_index(drop=True), mA.iloc[A2].reset_index(drop=True), iA[A2], delta=dlt)\n", " costs.append((dlt, r[\"loss\"], r[\"under\"], r[\"ok\"].mean()))\n", "cd = pd.DataFrame(costs, columns=[\"๊ฒฝ๊ณ„ ์ด๋™ delta\", \"์žฅ๋‹น ์†์‹ค(๋งŒ์›)\", \"๊ณผ์†Œ ํŒ์ •\", \"์ผ์น˜์œจ\"])\n", "best_d = int(cd.loc[cd[\"์žฅ๋‹น ์†์‹ค(๋งŒ์›)\"].idxmin(), \"๊ฒฝ๊ณ„ ์ด๋™ delta\"])\n", "print(\" ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„ ์ด๋™(๋ณด์ •์šฉ 120์žฅ, ๊ณผ์†Œ ํŒ์ • ์†์‹ค ๋ฐ˜์˜)\\n\" + cd.round(3).to_string(index=False))\n", "if best_d == 0:\n", " print(\" ๊ฒฝ๊ณ„๋ฅผ ๋‚ฎ์ถ”๋ฉด ๊ณผ๋Œ€ ํŒ์ •์ด ๋Š˜์–ด ์†์‹ค์ด ์˜คํžˆ๋ ค ์ปค์ง€๋ฏ€๋กœ ๊ฒฝ๊ณ„ ์ด๋™์€ ์ฑ„ํƒํ•˜์ง€ ์•Š๋Š”๋‹ค(delta=0 ์œ ์ง€). ๊ณผ์†Œ ํŒ์ •์€ ๊ฒฝ๊ณ„๊ฐ€ ์•„๋‹ˆ๋ผ ์˜ค์—ผ ๊ฐ€๋ฆผ์—์„œ ์ฃผ๋กœ ๋ฐœ์ƒํ•œ๋‹ค.\")\n", "else:\n", " print(f\" ์†์‹ค์ด ๊ฐ€์žฅ ์ž‘์€ ๊ฒฝ๊ณ„ ์ด๋™ delta={best_d}์  ์ฑ„ํƒ(๊ณผ์†Œ ํŒ์ •์„ ์ค„์ด๋Š” ๋ฐฉํ–ฅ์ด๋ฉฐ ์‚ฌ๋žŒ ์Šน์ธ ํ›„ ์ ์šฉ)\")\n", "\n", "# ---------------- 3) ๋ณด๋ฅ˜ ๊ตฌ๊ฐ„ B(360์žฅ, ์žฌ๋ณด์ •์— ์“ฐ์ง€ ์•Š์Œ) ๊ฒ€์ฆ ----------------\n", "mB, iB, kB = ops(70, 360)\n", "SEGP[\"dark\"], SEGP[\"rust\"] = 0.7, 0.5\n", "FB0 = feature_table(iB)\n", "e0 = evaluate(pl0, FB0, mB, iB)\n", "SEGP[\"dark\"], SEGP[\"rust\"] = float(bst[\"dark ์ž„๊ณ„๊ฐ’\"]), float(bst[\"rust ์ž„๊ณ„๊ฐ’\"])\n", "FB1 = feature_table(iB)\n", "e1 = evaluate(pl1, FB1, mB, iB, delta=best_d)\n", "tab = pd.DataFrame({\"์žฌ๋ณด์ • ์ „\": [e0[\"ok\"].mean(), e0[\"mae\"], e0[\"under\"], e0[\"loss\"], e0[\"auto_n\"], e0[\"auto_acc\"]], \"์žฌ๋ณด์ • ํ›„\": [e1[\"ok\"].mean(), e1[\"mae\"], e1[\"under\"], e1[\"loss\"], e1[\"auto_n\"], e1[\"auto_acc\"]]},\n", " index=[\"๋“ฑ๊ธ‰ ์ผ์น˜์œจ\", \"์†์ƒ๋„ MAE\", \"๊ณผ์†Œ ํŒ์ •(์žฅ)\", \"์žฅ๋‹น ์˜คํŒ ์†์‹ค(๋งŒ์›)\", \"์ž๋™ ์ฒ˜๋ฆฌ ๊ฑด์ˆ˜\", \"์ž๋™ ์ฒ˜๋ฆฌ ์ผ์น˜์œจ\"])\n", "print(\"\\n[3] ๋ณด๋ฅ˜ ๊ตฌ๊ฐ„ B ๊ฒ€์ฆ\\n\" + tab.round(3).to_string())\n", "print(f\" ์˜คํŒ ์†์‹ค {1 - e1['loss'] / e0['loss']:.1%} ๊ฐ์†Œ, ๊ณผ์†Œ ํŒ์ • {e0['under']} -> {e1['under']}์žฅ. ์˜ค์—ผ ๊ฐ€๋ฆผ ์‚ฌ์ง„์€ ์ •๋ณด ์ž์ฒด๊ฐ€ ๊ฐ€๋ ค์ ธ ์žฌ๋ณด์ •์œผ๋กœ ํ•ด๊ฒฐ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์žฌ์ดฌ์˜์ด ํ•„์š”ํ•˜๋‹ค.\")\n", "\n", "# ---------------- 4) ROI 3๋Œ€ ์ง€ํ‘œ(๊ต์œก์šฉ ๊ฐ€์ •) ----------------\n", "N_YR, MIN_FULL, MIN_QUICK = 20000, 15.0, 4.0 # ์—ฐ ํŒ์ • ์‚ฌ์ง„ ์ˆ˜, ์ •๋ฐ€ ํŒ๋…/๊ฐ„์ด ์Šน์ธ ์†Œ์š”(๋ถ„)\n", "WAGE, INV, OPEX = 1.2, 2500.0, 150.0 # ๊ฒ€์‚ฌ๊ด€ ์ธ๊ฑด๋น„(๋งŒ์›/์‹œ), ๋„์ž… ๋น„์šฉ, ์—ฐ ์šด์˜๋น„(๋งŒ์›)\n", "full_b, full_a = 1 - e0[\"auto_n\"] / len(mB), 1 - e1[\"auto_n\"] / len(mB)\n", "\n", "\n", "def roi(loss_b, loss_a, fb, fa, n=N_YR, inv=INV):\n", " t_b = fb * MIN_FULL + (1 - fb) * MIN_QUICK\n", " t_a = fa * MIN_FULL + (1 - fa) * MIN_QUICK\n", " save_loss = (loss_b - loss_a) * n\n", " save_time = (MIN_FULL - t_a) / 60 * WAGE * n # ๊ด€ํ–‰(์ „์ˆ˜ ์ •๋ฐ€ ํŒ๋…) ๋Œ€๋น„ ํŒ๋… ์ธ๊ฑด๋น„ ์ ˆ๊ฐ\n", " save = save_loss + save_time - OPEX\n", " return save, 1 - t_a / MIN_FULL, inv / max(save / 12, 1e-9), save_loss, save_time\n", "\n", "\n", "sv, tr, pb, sl, st = roi(e0[\"loss\"], e1[\"loss\"], full_b, full_a)\n", "print(f\"\\n[4] ROI 3๋Œ€ ์ง€ํ‘œ [๊ต์œก์šฉ ๊ฐ€์ •: ์—ฐ {N_YR}์žฅ, ์ •๋ฐ€ ํŒ๋… {MIN_FULL:.0f}๋ถ„, ๊ฐ„์ด ์Šน์ธ {MIN_QUICK:.0f}๋ถ„, ์ธ๊ฑด๋น„ {WAGE}๋งŒ์›/์‹œ, ๋„์ž… {INV:.0f}๋งŒ์›, ์šด์˜ {OPEX:.0f}๋งŒ์›/๋…„]\")\n", "print(f\" (1) ์—ฐ๊ฐ„ ๋น„์šฉ ์ ˆ๊ฐ์•ก {sv:,.0f}๋งŒ์›(์˜คํŒ ์†์‹ค ๊ฐ์†Œ {sl:,.0f} + ํŒ๋… ์ธ๊ฑด๋น„ {st:,.0f} - ์šด์˜๋น„) (2) ํŒ๋… ์‹œ๊ฐ„ ๋‹จ์ถ•๋ฅ  {tr:.1%}(๊ด€ํ–‰ ์ „์ˆ˜ ์ •๋ฐ€ ํŒ๋… ๋Œ€๋น„) (3) ํˆฌ์ž ํšŒ์ˆ˜ ๊ธฐ๊ฐ„ {pb:.1f}๊ฐœ์›”\")\n", "print(\" ์‚ฐ์‹: ์ ˆ๊ฐ์•ก = (์žฌ๋ณด์ • ์ „ - ํ›„ ์žฅ๋‹น ์˜คํŒ ์†์‹ค) x ์—ฐ ์žฅ์ˆ˜ + (์ •๋ฐ€ ํŒ๋… - ํ‰๊ท  ํŒ๋… ์‹œ๊ฐ„)/60 x ์ธ๊ฑด๋น„ x ์—ฐ ์žฅ์ˆ˜ - ์šด์˜๋น„\")\n", "print(\" ์žฅ์ˆ˜ ๋ฏผ๊ฐ๋„:\", \"; \".join(f\"{n}์žฅ -> ์ ˆ๊ฐ {roi(e0['loss'], e1['loss'], full_b, full_a, n=n)[0]:,.0f}๋งŒ์›, ํšŒ์ˆ˜ {roi(e0['loss'], e1['loss'], full_b, full_a, n=n)[2]:.0f}๊ฐœ์›”\" for n in (5000, 10000, 20000, 40000)))\n", "print(\" ๋น„๊ด€(์žฌ๋ณด์ • ํšจ๊ณผ ์—†์Œ, ํŒ๋… ์‹œ๊ฐ„ ์ ˆ๊ฐ๋งŒ):\", f\"์ ˆ๊ฐ {roi(e0['loss'], e0['loss'], full_b, full_a)[0]:,.0f}๋งŒ์›, ํšŒ์ˆ˜ {roi(e0['loss'], e0['loss'], full_b, full_a)[2]:.0f}๊ฐœ์›”\")\n", "print(\" ๊ฐ€์ •๊ฐ’์ด๋ฏ€๋กœ ์žฌ์ •ยท๊ธฐํš ๋ถ€์„œ์™€ ์‹ค์ œ ๋‹จ๊ฐ€๋กœ ๋‹ค์‹œ ์‚ฐ์ •ํ•ด์•ผ ํ•œ๋‹ค. ์žฌ๋ณด์ •๋œ ์ž„๊ณ„๊ฐ’ยท๊ฒฝ๊ณ„๋Š” ์ •๋น„๊ฐ๋…๊ด€ ์Šน์ธ ํ›„ ์ ์šฉํ•œ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 3, figsize=(15, 4.2))\n", "ax[0].bar(range(len(cs)), cs.values); ax[0].set_xticks(range(len(cs))); ax[0].set_xticklabels([f\"C{i + 1}\" for i in range(len(cs))]); ax[0].set_title(\"AAR: misjudgment causes (count)\")\n", "ax[1].plot(cd[\"๊ฒฝ๊ณ„ ์ด๋™ delta\"], cd[\"์žฅ๋‹น ์†์‹ค(๋งŒ์›)\"], \"o-\"); ax[1].set_title(\"Loss per photo vs grade-boundary shift\"); ax[1].set_xlabel(\"delta\")\n", "x = np.arange(3); ax[2].bar(x - .2, [e0[\"ok\"].mean() * 100, e0[\"under\"], e0[\"loss\"]], .4, label=\"before\"); ax[2].bar(x + .2, [e1[\"ok\"].mean() * 100, e1[\"under\"], e1[\"loss\"]], .4, label=\"after\")\n", "ax[2].set_xticks(x); ax[2].set_xticklabels([\"accuracy %\", \"under-grade n\", \"loss/photo\"]); ax[2].legend(); ax[2].set_title(\"Hold-out B: before vs after\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 09 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] AAR ๋Œ€์ƒ ์šด์˜ ์‚ฌ์ง„ 360์žฅ(์ €์กฐ๋„ยท์ง„ํ™ 5%ยท์ •์ƒ ํ˜ผํ•ฉ): ๋“ฑ๊ธ‰ ์ผ์น˜์œจ 83.1%, ์†์ƒ๋„ MAE 8.72, ๊ณผ์†Œ ํŒ์ • 22์žฅ, ์žฅ๋‹น ์˜คํŒ ์†์‹ค 1.99๋งŒ์›\n", " ์˜คํŒ 61์žฅ์˜ ์›์ธ ๋ถ„๋ฅ˜: ์˜ค์—ผ ๊ฐ€๋ฆผ 29์žฅ(48%), ๊ฒฐํ•จ ์œ ํ˜• ํ˜ผ๋™ 9์žฅ(15%), ์ €์กฐ๋„ 8์žฅ(13%), ๊ธฐํƒ€ 8์žฅ(13%), ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„ ๊ทผ์ฒ˜ 7์žฅ(11%)\n", " ์˜คํŒ ์ค‘ ๊ณผ์†Œ ํŒ์ •(์‹ค์ œ๋ณด๋‹ค ๋‚ฎ์€ ๋“ฑ๊ธ‰) 22์žฅ / ๊ณผ๋Œ€ 39์žฅ -> ๊ณผ์†Œ ํŒ์ •์ด ์†์‹ค ๋‹จ๊ฐ€๊ฐ€ 3.3๋ฐฐ ๋†’์•„ ์šฐ์„  ๊ต์ • ๋Œ€์ƒ\n", "\n", "[2] ๋ถ„ํ•  ์ž„๊ณ„๊ฐ’ ์žฌ๋ณด์ •(์šด์˜ ์‚ฌ์ง„ 240์žฅ IoU): ํ˜„ํ–‰(0.70, 0.50) 0.495 -> ์ตœ์ (0.75, 0.44) 0.528\n", " ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„ ์ด๋™(๋ณด์ •์šฉ 120์žฅ, ๊ณผ์†Œ ํŒ์ • ์†์‹ค ๋ฐ˜์˜)\n", " ๊ฒฝ๊ณ„ ์ด๋™ delta ์žฅ๋‹น ์†์‹ค(๋งŒ์›) ๊ณผ์†Œ ํŒ์ • ์ผ์น˜์œจ\n", " 0 1.483 5 0.850\n", " 2 1.733 5 0.808\n", " 4 1.933 5 0.775\n", " 6 2.133 5 0.742\n", " 8 1.950 3 0.733\n", " ๊ฒฝ๊ณ„๋ฅผ ๋‚ฎ์ถ”๋ฉด ๊ณผ๋Œ€ ํŒ์ •์ด ๋Š˜์–ด ์†์‹ค์ด ์˜คํžˆ๋ ค ์ปค์ง€๋ฏ€๋กœ ๊ฒฝ๊ณ„ ์ด๋™์€ ์ฑ„ํƒํ•˜์ง€ ์•Š๋Š”๋‹ค(delta=0 ์œ ์ง€). ๊ณผ์†Œ ํŒ์ •์€ ๊ฒฝ๊ณ„๊ฐ€ ์•„๋‹ˆ๋ผ ์˜ค์—ผ ๊ฐ€๋ฆผ์—์„œ ์ฃผ๋กœ ๋ฐœ์ƒํ•œ๋‹ค.\n", "\n", "[3] ๋ณด๋ฅ˜ ๊ตฌ๊ฐ„ B ๊ฒ€์ฆ\n", " ์žฌ๋ณด์ • ์ „ ์žฌ๋ณด์ • ํ›„\n", "๋“ฑ๊ธ‰ ์ผ์น˜์œจ 0.819 0.853\n", "์†์ƒ๋„ MAE 7.579 6.559\n", "๊ณผ์†Œ ํŒ์ •(์žฅ) 26.000 25.000\n", "์žฅ๋‹น ์˜คํŒ ์†์‹ค(๋งŒ์›) 2.194 2.022\n", "์ž๋™ ์ฒ˜๋ฆฌ ๊ฑด์ˆ˜ 150.000 151.000\n", "์ž๋™ ์ฒ˜๋ฆฌ ์ผ์น˜์œจ 0.980 0.980\n", " ์˜คํŒ ์†์‹ค 7.8% ๊ฐ์†Œ, ๊ณผ์†Œ ํŒ์ • 26 -> 25์žฅ. ์˜ค์—ผ ๊ฐ€๋ฆผ ์‚ฌ์ง„์€ ์ •๋ณด ์ž์ฒด๊ฐ€ ๊ฐ€๋ ค์ ธ ์žฌ๋ณด์ •์œผ๋กœ ํ•ด๊ฒฐ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์žฌ์ดฌ์˜์ด ํ•„์š”ํ•˜๋‹ค.\n", "\n", "[4] ROI 3๋Œ€ ์ง€ํ‘œ [๊ต์œก์šฉ ๊ฐ€์ •: ์—ฐ 20000์žฅ, ์ •๋ฐ€ ํŒ๋… 15๋ถ„, ๊ฐ„์ด ์Šน์ธ 4๋ถ„, ์ธ๊ฑด๋น„ 1.2๋งŒ์›/์‹œ, ๋„์ž… 2500๋งŒ์›, ์šด์˜ 150๋งŒ์›/๋…„]\n", " (1) ์—ฐ๊ฐ„ ๋น„์šฉ ์ ˆ๊ฐ์•ก 5,140๋งŒ์›(์˜คํŒ ์†์‹ค ๊ฐ์†Œ 3,444 + ํŒ๋… ์ธ๊ฑด๋น„ 1,846 - ์šด์˜๋น„) (2) ํŒ๋… ์‹œ๊ฐ„ ๋‹จ์ถ•๋ฅ  30.8%(๊ด€ํ–‰ ์ „์ˆ˜ ์ •๋ฐ€ ํŒ๋… ๋Œ€๋น„) (3) ํˆฌ์ž ํšŒ์ˆ˜ ๊ธฐ๊ฐ„ 5.8๊ฐœ์›”\n", " ์‚ฐ์‹: ์ ˆ๊ฐ์•ก = (์žฌ๋ณด์ • ์ „ - ํ›„ ์žฅ๋‹น ์˜คํŒ ์†์‹ค) x ์—ฐ ์žฅ์ˆ˜ + (์ •๋ฐ€ ํŒ๋… - ํ‰๊ท  ํŒ๋… ์‹œ๊ฐ„)/60 x ์ธ๊ฑด๋น„ x ์—ฐ ์žฅ์ˆ˜ - ์šด์˜๋น„\n", " ์žฅ์ˆ˜ ๋ฏผ๊ฐ๋„: 5000์žฅ -> ์ ˆ๊ฐ 1,173๋งŒ์›, ํšŒ์ˆ˜ 26๊ฐœ์›”; 10000์žฅ -> ์ ˆ๊ฐ 2,495๋งŒ์›, ํšŒ์ˆ˜ 12๊ฐœ์›”; 20000์žฅ -> ์ ˆ๊ฐ 5,140๋งŒ์›, ํšŒ์ˆ˜ 6๊ฐœ์›”; 40000์žฅ -> ์ ˆ๊ฐ 10,430๋งŒ์›, ํšŒ์ˆ˜ 3๊ฐœ์›”\n", " ๋น„๊ด€(์žฌ๋ณด์ • ํšจ๊ณผ ์—†์Œ, ํŒ๋… ์‹œ๊ฐ„ ์ ˆ๊ฐ๋งŒ): ์ ˆ๊ฐ 1,696๋งŒ์›, ํšŒ์ˆ˜ 18๊ฐœ์›”\n", " ๊ฐ€์ •๊ฐ’์ด๋ฏ€๋กœ ์žฌ์ •ยท๊ธฐํš ๋ถ€์„œ์™€ ์‹ค์ œ ๋‹จ๊ฐ€๋กœ ๋‹ค์‹œ ์‚ฐ์ •ํ•ด์•ผ ํ•œ๋‹ค. ์žฌ๋ณด์ •๋œ ์ž„๊ณ„๊ฐ’ยท๊ฒฝ๊ณ„๋Š” ์ •๋น„๊ฐ๋…๊ด€ ์Šน์ธ ํ›„ ์ ์šฉํ•œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 10. ์žฅ๋น„ ๊ฒฐํ•จ ์‚ฌ์ง„ ๊ธฐ๋ฐ˜ AI ์™ธ๊ด€ ๊ฒ€์‚ฌ ๋ฐ ์†์ƒ๋„ ์ž๋™ ํŒ์ • ๋„๊ตฌ ํ†ตํ•ฉ ์™„์„ฑ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์žฅ๋น„ ๊ฒฐํ•จ ์‚ฌ์ง„ ๊ธฐ๋ฐ˜ AI ์™ธ๊ด€ ๊ฒ€์‚ฌ ๋ฐ ์†์ƒ๋„ ์ž๋™ ํŒ์ • ๋„๊ตฌ๋ฅผ ์™„์„ฑํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์žฅ๋น„ ๊ฒฐํ•จ ์‚ฌ์ง„ ๊ธฐ๋ฐ˜ AI ์™ธ๊ด€ ๊ฒ€์‚ฌ์™€ ์†์ƒ๋„ ํŒ์ • ๋„๊ตฌ๋ฅผ ํ†ตํ•ฉํ•˜๊ณ  ์ž๋™ ์žฌํ•™์Šตยท์šด์˜ ๋ฌธ์„œยท์ž์ฒด ์ ๊ฒ€์„ ๊ฐ–์ถ˜๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์‹ ๊ทœ ์‚ฌ์ง„ 240์žฅ์„ ์ถ”๊ฐ€ํ•ด ์ •ํ•ฉ์„ฑ์„ ๊ฒ€์ฆํ•˜๊ณ  ๋ถ„ํฌ ๋ณ€ํ™”(PSI)๋ฅผ ์ ๊ฒ€ํ•œ ๋’ค ์ฑŒ๋ฆฐ์ € ๋ชจ๋ธ์„ ํ•™์Šตํ•ด ๊ฒ€์ฆ MAE๊ฐ€ 3% ์ด์ƒ ๊ฐœ์„ ๋  ๋•Œ๋งŒ ์Šน๊ฒฉ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/qlt03_operations_guide.md ๋ฐ pipeline/qlt03_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) ์ ๊ฒ€, ์ž๋™ ์žฌํ•™์Šต(์ฑ”ํ”ผ์–ธ/์ฑŒ๋ฆฐ์ €), ๊ฒฐํ•จ ํŒ์ •ยท์†์ƒ๋„ยท์ •๋น„ ๋“ฑ๊ธ‰ ์‚ฐ์ถœ๊ณผ ๊ฒ€์‚ฌ ๋Œ€๊ธฐยท์žฌ์ดฌ์˜ ๋ถ„๋ฆฌ, ์ œ์› ๋ถˆ์ผ์น˜ ํ™˜๋ฅ˜๊นŒ์ง€ ํ•œ ๋ฒˆ์— ์‹คํ–‰ํ•˜๋Š” ํ†ตํ•ฉ ํŒŒ์ดํ”„๋ผ์ธ์„ ์™„์„ฑํ•˜๊ณ  ์šด์˜ ๋ฌธ์„œ ์™„๋น„์œจยท์ž์ฒด ์ ๊ฒ€ยท์ธํ„ฐํŽ˜์ด์Šค ๋ช…์„ธยท๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ์„ ์ถœ๋ ฅํ•œ๋‹ค.\"\"\"\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.ensemble import RandomForestClassifier, GradientBoostingRegressor\n", "from sklearn.linear_model import LinearRegression\n", "from sklearn.model_selection import cross_val_predict\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฅ๋น„ ์™ธ๊ด€ ์‚ฌ์ง„ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋น„ยท๋ถ€๋Œ€ยท์œ„์น˜ ์‚ฌ์ง„์ด ์•„๋‹ˆ๋ผ ๋‚œ์ˆ˜๋กœ ๊ทธ๋ฆฐ 64x64 ํ•ฉ์„ฑ ์ด๋ฏธ์ง€)\n", "# ๋„์žฅ๋œ ๊ธˆ์† ํ‘œ๋ฉด์— ๊ท ์—ด(crack)ยท๋ถ€์‹(corrosion)ยทํŒŒ์†(damage)ยท๋ˆ„์œ (leak)๋ฅผ ๊ทธ๋ฆฌ๊ณ , ์กฐ๋ช… ํŽธ์ฐจ์™€ ์ง„ํ™(mud) ์˜ค์—ผ์„ ์„ž๋Š”๋‹ค.\n", "# ์ด๋ฏธ์ง€์™€ ํ•จ๊ป˜ ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ)๊ณผ ์ •๋‹ต ๊ฒฐํ•จ ๋งˆ์Šคํฌยท์†์ƒ๋„ยท์ •๋น„ ๋“ฑ๊ธ‰์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "from scipy import ndimage as ndi\n", "\n", "SZ = 64\n", "CLASSES = [\"none\", \"crack\", \"corrosion\", \"damage\", \"leak\"]\n", "KO_CLS = {\"none\": \"๊ฒฐํ•จ ์—†์Œ\", \"crack\": \"๊ท ์—ด\", \"corrosion\": \"๋ถ€์‹\", \"damage\": \"ํŒŒ์†\", \"leak\": \"๋ˆ„์œ \"}\n", "CLS_W = {\"none\": 0.0, \"crack\": 1.4, \"corrosion\": 1.0, \"damage\": 1.6, \"leak\": 0.6} # ์†์ƒ๋„ ๊ณ„์‚ฐ์šฉ ๊ฒฐํ•จ ์œ ํ˜• ๊ฐ€์ค‘์น˜\n", "GRADE_NAME = {1: \"ํ˜„์žฅ์ •๋น„\", 2: \"์•ผ์ „์ •๋น„\", 3: \"์ฐฝ์ •๋น„\"}\n", "YY, XX = np.mgrid[0:SZ, 0:SZ]\n", "\n", "\n", "def grade_of(idx):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 10] ์žฅ๋น„ ๊ฒฐํ•จ ์‚ฌ์ง„ ๊ธฐ๋ฐ˜ AI ์™ธ๊ด€ ๊ฒ€์‚ฌ ๋ฐ ์†์ƒ๋„ ์ž๋™ ํŒ์ • ๋„๊ตฌ ํ†ตํ•ฉ ์™„์„ฑ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def blob(rng, cy, cx, r, rough=0.35):\n", " \"\"\"๋ถˆ๊ทœ์น™ํ•œ ์›ํ˜• ์˜์—ญ ๋งˆ์Šคํฌ.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 10] ์žฅ๋น„ ๊ฒฐํ•จ ์‚ฌ์ง„ ๊ธฐ๋ฐ˜ AI ์™ธ๊ด€ ๊ฒ€์‚ฌ ๋ฐ ์†์ƒ๋„ ์ž๋™ ํŒ์ • ๋„๊ตฌ ํ†ตํ•ฉ ์™„์„ฑ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def draw(rng, cls, sev):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜•ยท์‹ฌ๊ฐ๋„ sev(0~1)์— ๋งž๋Š” ์ •๋‹ต ๋งˆ์Šคํฌ์™€ ์ƒ‰ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m = np.zeros((SZ, SZ), bool)\n", " if cls == \"crack\":\n", " y, x, ang = rng.uniform(12, 52), rng.uniform(12, 52), rng.uniform(0, np.pi)\n", " for _ in range(int(20 + 40 * sev)):\n", " ang += rng.normal(0, 0.35); y += np.sin(ang) * 1.2; x += np.cos(ang) * 1.2\n", " if 0 <= int(y) < SZ and 0 <= int(x) < SZ:\n", " m[int(y), int(x)] = True\n", " m = ndi.binary_dilation(m, iterations=1 + int(sev > 0.6))\n", " elif cls == \"corrosion\":\n", " for _ in range(int(rng.integers(1, 4))):\n", " m |= blob(rng, rng.uniform(14, 50), rng.uniform(14, 50), 3 + 9 * sev * rng.uniform(0.6, 1.1))\n", " elif cls == \"damage\":\n", " m = blob(rng, rng.uniform(20, 44), rng.uniform(20, 44), 6 + 12 * sev, 0.3)\n", " elif cls == \"leak\":\n", " x0, y0 = rng.uniform(16, 48), rng.uniform(8, 24)\n", " w, ln = 2 + 4 * sev, 12 + 26 * sev\n", " m = (np.abs(XX - x0 - 0.15 * (YY - y0)) < w * (1 - 0.4 * (YY - y0) / ln)) & (YY > y0) & (YY < y0 + ln)\n", " return m\n", "\n", "\n", "def make_images(n=600, seed=0, mud=0.0, p_none=0.25, dark=False):\n", " \"\"\"๊ฐ€์ƒ ์™ธ๊ด€ ์‚ฌ์ง„ n์žฅ(+์ง„ํ™ ์˜ค์—ผ mud ๋น„์œจ)๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋กยท์ •๋‹ต. ๋ฐ˜ํ™˜: meta(DataFrame), imgs(n,64,64,3), masks(n,64,64).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " imgs, masks, rows = np.zeros((n, SZ, SZ, 3), np.float32), np.zeros((n, SZ, SZ), bool), []\n", " for i in range(n):\n", " cls = \"none\" if rng.random() < p_none else str(rng.choice(CLASSES[1:], p=[0.25, 0.3, 0.25, 0.2]))\n", " sev = 0.0 if cls == \"none\" else float(rng.beta(1.6, 2.0))\n", " base = np.array([0.42, 0.46, 0.40]) * (1 + 0.5 * ndi.gaussian_filter(rng.normal(size=(SZ, SZ)), 8)[..., None] * 3)\n", " img = base + rng.normal(0, 0.012, (SZ, SZ, 3))\n", " m = draw(rng, cls, sev)\n", " a = ndi.gaussian_filter(m.astype(float), 0.8)[..., None] # ๊ฒฝ๊ณ„๋ฅผ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์„ž๋Š”๋‹ค\n", " if cls == \"crack\":\n", " col = img * rng.uniform(0.2, 0.45)\n", " elif cls == \"corrosion\":\n", " col = np.array([0.55, 0.30, 0.12]) * rng.uniform(0.75, 1.15) * (0.85 + 0.3 * rng.random((SZ, SZ, 1)))\n", " elif cls == \"damage\":\n", " col = np.ones_like(img) * np.array([0.34, 0.34, 0.36]) * (1.0 - 0.45 * sev) * rng.uniform(0.75, 1.0)\n", " rim = m & ~ndi.binary_erosion(m, iterations=2)\n", " a = np.maximum(a, 0.0); img = np.where(rim[..., None], 0.7, img)\n", " elif cls == \"leak\":\n", " col = np.ones_like(img) * np.array([0.10, 0.09, 0.08]) * rng.uniform(0.8, 2.6)\n", " else:\n", " col = img\n", " img = img * (1 - a) + col * a\n", " if rng.random() < 0.35: # ๋ฌดํ•ดํ•œ ์–ผ๋ฃฉ(๊ฒฐํ•จ ์•„๋‹˜): ์˜…์€ ๊ฐˆ์ƒ‰ ๋ฒˆ์ง\n", " st = blob(rng, rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(6, 14), 0.5)\n", " al = ndi.gaussian_filter(st.astype(float), 2.5)[..., None] * rng.uniform(0.15, 0.4)\n", " img = img * (1 - al) + np.array([0.50, 0.40, 0.30]) * al\n", " if rng.random() < 0.3: # ๋ฌดํ•ดํ•œ ๊ธด ์Šคํฌ๋ž˜์น˜(๋ฐ์€ ์„ )\n", " y0, x0, ang = rng.uniform(5, 58), rng.uniform(5, 58), rng.uniform(0, np.pi)\n", " for t in np.linspace(0, 25, 40):\n", " yy, xx = int(y0 + t * np.sin(ang)), int(x0 + t * np.cos(ang))\n", " if 0 <= yy < SZ and 0 <= xx < SZ:\n", " img[yy, xx] = np.clip(img[yy, xx] * 1.5, 0, 1)\n", " if mud > 0: # ์ง„ํ™ ์˜ค์—ผ: ๊ฒฐํ•จ ์œ„๋ฅผ ๋ฎ์„ ์ˆ˜ ์žˆ๋‹ค(์ •๋‹ต ๋งˆ์Šคํฌ๋Š” ๊ทธ๋Œ€๋กœ)\n", " mm = np.zeros((SZ, SZ), bool)\n", " while mm.mean() < mud:\n", " mm |= blob(rng, rng.uniform(0, SZ), rng.uniform(0, SZ), rng.uniform(5, 11), 0.5)\n", " img[mm] = np.array([0.38, 0.30, 0.22]) * (0.85 + 0.3 * rng.random((mm.sum(), 1)))\n", " gain = rng.uniform(0.3, 0.55) if dark else rng.uniform(0.5, 1.15)\n", " ang = rng.uniform(0, 2 * np.pi)\n", " light = gain * (1 + 0.3 * (np.cos(ang) * (XX - 32) + np.sin(ang) * (YY - 32)) / 32)\n", " imgs[i] = np.clip(img * light[..., None] + rng.normal(0, 0.025, (SZ, SZ, 3)), 0, 1)\n", " masks[i] = m\n", " area = float(m.mean())\n", " depth = {\"none\": 0.0, \"crack\": 0.3 + 3.0 * sev, \"corrosion\": 0.1 + 1.5 * sev, \"damage\": 1.0 + 8.0 * sev, \"leak\": 0.0}[cls] * rng.lognormal(0, 0.1)\n", " idx = float(np.clip(100 * (1 - np.exp(-(CLS_W[cls] * area * 9 + 0.05 * depth))) + rng.normal(0, 1.5), 0, 100))\n", " rows.append({\"img_id\": f\"IMG{seed:02d}{i:04d}\", \"cls\": cls, \"sev\": sev, \"area_frac\": area, \"depth_mm\": depth, \"damage_idx\": idx,\n", " \"grade\": int(grade_of(idx)), \"gain\": gain, \"mud\": mud})\n", " meta = pd.DataFrame(rows)\n", " # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก(์ œ์› ์š”์•ฝ): ์‚ฌ์ง„ ํŒ์ •๊ณผ ๋Œ€์กฐํ•  ๊ฐ’\n", " age = rng.integers(1, 25, n)\n", " meta[\"day\"] = np.sort(rng.integers(0, 90, n))\n", " meta[\"equip_type\"] = rng.choice([\"vehicle\", \"generator\", \"comm\", \"trailer\"], n, p=[0.4, 0.2, 0.2, 0.2])\n", " meta[\"mfg_year\"] = 2026 - age\n", " meta[\"op_hours\"] = np.clip(rng.normal(180 * age, 400, n) + 8 * meta[\"damage_idx\"], 10, 20000)\n", " meta[\"env\"] = rng.choice([\"coastal\", \"inland\"], n, p=[0.3, 0.7])\n", " reg = np.clip(meta[\"grade\"] + rng.choice([-1, 0, 0, 0, 0, 0, 1, 2], n), 1, 3) # ์žฅ๋น„ ๊ด€๋ฆฌ ๊ธฐ๋ก์ƒ ์ง์ „ ์ •๋น„ ๋“ฑ๊ธ‰\n", " meta[\"reg_grade\"] = reg\n", " meta[\"emergency\"] = rng.random(n) < 0.04 # ๊ธด๊ธ‰ ์ ๊ฒ€ ์š”์ฒญ(ํ™”์งˆ ๋ถˆ๋Ÿ‰์ด์–ด๋„ ๋ณด์กด)\n", " return meta, imgs, masks\n", "# ---- ์ „์ฒ˜๋ฆฌ(์กฐ๋ช… ๋ณด์ •ยทํ‘œ์ค€ํ™”)์™€ ๊ฒฐํ•จ ํ›„๋ณด ๋ถ„ํ• , ํŠน์ง• ์ถ”์ถœ (cv2 ๋Œ€์‹  numpyยทscipy ์‚ฌ์šฉ) ----\n", "def correct(img):\n", " \"\"\"์กฐ๋ช… ๋ณด์ •: ๋ฐ๊ธฐ์— ํ‰๋ฉด(์กฐ๋ช… ๊ธฐ์šธ๊ธฐ)์„ ์ ํ•ฉํ•ด ๋‚˜๋ˆˆ๋‹ค. ์–ด๋‘์šด ๊ฒฐํ•จ ํ™”์†Œ๋Š” ์ œ์™ธํ•˜๊ณ  ๋‘ ๋ฒˆ ๋ฐ˜๋ณต ์ ํ•ฉํ•œ๋‹ค. ๋ฐ˜ํ™˜: ๋ณด์ • ๋ฐ๊ธฐ(์ •์ƒ ๋ฉด ์•ฝ 1), ์ƒ‰ ๋น„์œจ.\"\"\"\n", " g = img.mean(axis=2)\n", " A = np.c_[np.ones(SZ * SZ), XX.ravel() / SZ, YY.ravel() / SZ]\n", " keep = np.ones(SZ * SZ, bool)\n", " for _ in range(3):\n", " coef, *_ = np.linalg.lstsq(A[keep], g.ravel()[keep], rcond=None)\n", " plane = (A @ coef).reshape(SZ, SZ)\n", " keep = (np.abs(g - plane) < 0.08 * np.maximum(plane, 1e-3)).ravel()\n", " if keep.sum() < 200:\n", " keep[:] = True\n", " c = g / np.maximum(plane, 1e-3)\n", " chroma = img[..., 0] / (img.sum(axis=2) + 1e-6)\n", " return c, chroma\n", "\n", "\n", "SEGP = {\"dark\": 0.7, \"rust\": 0.5, \"rim\": 1.45} # ๋ถ„ํ•  ์ž„๊ณ„๊ฐ’(์–ด๋‘์›€ ๋ณด์ • ๋ฐ๊ธฐ, ๋ถ‰์€ ๋น„์œจ, ๋ฐ์€ ํ…Œ๋‘๋ฆฌ): ์žฌ๋ณด์ • ๋Œ€์ƒ\n", "\n", "\n", "def segment(img):\n", " \"\"\"๊ฒฐํ•จ ํ›„๋ณด ๋งˆ์Šคํฌ: ์–ด๋‘์šด ์˜์—ญ(๊ท ์—ดยท๋ˆ„์œ ยทํŒŒ์†) + ๋ถ‰์€ ๊ฐˆ์ƒ‰ ์˜์—ญ(๋ถ€์‹) + ๋ฐ์€ ํ…Œ๋‘๋ฆฌ(ํŒŒ์†).\"\"\"\n", " c, chroma = correct(img)\n", " dark = c < SEGP[\"dark\"]\n", " rust = (chroma > SEGP[\"rust\"]) & (c > 0.45)\n", " rim = c > SEGP[\"rim\"]\n", " m = dark | rust | ndi.binary_dilation(rim, iterations=1) & ndi.binary_dilation(dark, iterations=3)\n", " m = ndi.binary_opening(m, structure=np.ones((2, 2))) if m.sum() > 12 else m\n", " return m, c, chroma, dark, rust, rim\n", "\n", "\n", "def extract(img):\n", " \"\"\"๋ถ„ํ•  ๊ฒฐ๊ณผ์™€ ์ƒ‰ยท๋ชจ์–‘์—์„œ 24๊ฐœ ํŠน์ง•์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " m, c, chroma, dark, rust, rim = segment(img)\n", " lab, n = ndi.label(m)\n", " area = float(m.sum())\n", " if n:\n", " sizes = ndi.sum(m, lab, range(1, n + 1))\n", " k = int(np.argmax(sizes)) + 1\n", " sl = ndi.find_objects(lab)[k - 1]\n", " h, w = sl[0].stop - sl[0].start, sl[1].stop - sl[1].start\n", " big = float(sizes.max())\n", " elong, fill = max(h, w) / max(min(h, w), 1), big / (h * w)\n", " else:\n", " big, elong, fill = 0.0, 1.0, 0.0\n", " gy, gx = np.gradient(c)\n", " mag = np.hypot(gx, gy)\n", " inside = m.sum() > 0\n", " return {\"area_px\": area, \"area_frac\": area / SZ ** 2, \"n_comp\": float(n), \"big_px\": big, \"elong\": elong, \"fill\": fill,\n", " \"dark_frac\": float(dark.mean()), \"rust_frac\": float(rust.mean()), \"rim_frac\": float(rim.mean()),\n", " \"c_mean\": float(c.mean()), \"c_std\": float(c.std()), \"c_min\": float(c.min()), \"edge_mean\": float(mag.mean()), \"edge_p95\": float(np.quantile(mag, 0.95)),\n", " \"chroma_mean\": float(chroma.mean()), \"chroma_max\": float(chroma.max()),\n", " \"in_dark\": float(c[m].mean()) if inside else 1.0, \"in_chroma\": float(chroma[m].mean()) if inside else float(chroma.mean()),\n", " \"ring_contrast\": float(c[ndi.binary_dilation(m, iterations=3) & ~m].mean() - c[m].mean()) if inside and (~m).any() else 0.0,\n", " \"thin\": float(1 - fill), \"gain\": float(img.mean()), \"sat\": float((img.max(axis=2) - img.min(axis=2)).mean()),\n", " \"cy\": float(ndi.center_of_mass(m)[0] / SZ) if inside else 0.5, \"cx\": float(ndi.center_of_mass(m)[1] / SZ) if inside else 0.5}\n", "\n", "\n", "def feature_table(imgs):\n", " return pd.DataFrame([extract(im) for im in imgs])\n", "# ---- ํŒ์ • ํŒŒ์ดํ”„๋ผ์ธ: ๊ฒฐํ•จ ์œ ํ˜• ๋ถ„๋ฅ˜ + ์†์ƒ๋„ ํšŒ๊ท€(๋ถ„๋ฅ˜ ํ™•๋ฅ  ํฌํ•จ) -> ์ •๋น„ ๋“ฑ๊ธ‰ ----\n", "def fit_pipeline(meta, F):\n", " \"\"\"๊ฒฐํ•จ ์œ ํ˜• ๋ถ„๋ฅ˜๊ธฐ์™€ ์†์ƒ๋„ ํšŒ๊ท€๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค. ํšŒ๊ท€๊ธฐ ์ž…๋ ฅ์˜ ๋ถ„๋ฅ˜ ํ™•๋ฅ ์€ ๊ต์ฐจ ์˜ˆ์ธก์œผ๋กœ ๋งŒ๋“ค์–ด ๋ˆ„์„ค์„ ๋ง‰๋Š”๋‹ค.\"\"\"\n", " clf = RandomForestClassifier(n_estimators=150, random_state=0, n_jobs=1)\n", " P = cross_val_predict(clf, F, meta[\"cls\"], cv=4, method=\"predict_proba\")\n", " clf.fit(F, meta[\"cls\"])\n", " reg = GradientBoostingRegressor(n_estimators=200, max_depth=3, learning_rate=0.06, random_state=0).fit(np.c_[F.values, P], meta[\"damage_idx\"])\n", " return {\"clf\": clf, \"reg\": reg, \"cols\": list(F.columns)}\n", "\n", "\n", "def predict_pipeline(pl, F):\n", " X = F[pl[\"cols\"]]\n", " P = pl[\"clf\"].predict_proba(X)\n", " idx = np.clip(pl[\"reg\"].predict(np.c_[X.values, P]), 0, 100)\n", " return pl[\"clf\"].classes_[P.argmax(axis=1)], P, idx, P.max(axis=1)\n", "# ---- ์‚ฌ์ง„ ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ: ์ง„ํ™ยท์˜ค์—ผ ๊ฐ€๋ฆผ ์ถ”์ •๊ณผ ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ----\n", "def mud_est(img):\n", " \"\"\"์ง„ํ™(๊ฐˆ์ƒ‰, ์ฑ„๋„ ๋‚ฎ์€ ๋ถ‰์€ ๊ธฐ) ํ™”์†Œ ๋น„์œจ์„ ์ถ”์ •ํ•œ๋‹ค. 3x3 ํ‰๊ท ์œผ๋กœ ์žก์Œ์„ ์ค„์ธ ๋’ค ์ƒ‰ ๋น„์œจ๋กœ ํŒ๋‹จํ•˜๋ฉฐ, ์‹ค์ œ ๊ฐ€๋ฆผ ๋น„์œจ๋ณด๋‹ค ์ž‘๊ฒŒ ๋‚˜์˜ค์ง€๋งŒ ์ˆœ์„œ๋Š” ์œ ์ง€๋œ๋‹ค.\"\"\"\n", " sm = ndi.uniform_filter(img, size=(3, 3, 1))\n", " s = sm.sum(axis=2) + 1e-6\n", " rr, gg = sm[..., 0] / s, sm[..., 1] / s\n", " return float(((rr > 0.385) & (rr < 0.46) & (gg < 0.325)).mean())\n", "\n", "\n", "def confidence(P, idx, F, mud):\n", " \"\"\"ํŒ์ • ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ 0~100 = 50 x ๋ถ„๋ฅ˜ ํ™•๋ฅ  ์ตœ๋Œ€๊ฐ’ + 35 x ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„๊นŒ์ง€ ๊ฑฐ๋ฆฌ(8์ ์—์„œ ํฌํ™”) + 15 x ์‚ฌ์ง„ ํ’ˆ์งˆ. ์˜ค์—ผ ์˜์‹ฌ(mud ์ถ”์ • 1% ์ด์ƒ)์ด๋ฉด ์ƒํ•œ 75๋กœ ์ œํ•œํ•œ๋‹ค.\"\"\"\n", " bd = np.minimum(np.abs(idx - 35), np.abs(idx - 65))\n", " qual = np.clip(1.0 - np.clip(0.55 - F[\"gain\"].values, 0, None) * 2 - np.clip(0.9 - F[\"c_mean\"].values, 0, None), 0, 1)\n", " c = 50 * P.max(axis=1) + 35 * np.clip(bd / 8, 0, 1) + 15 * qual\n", " return np.where(np.asarray(mud) >= 0.01, np.minimum(c, 75.0), c)\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", "m_old, i_old, k_old = make_images(600, seed=1) # ๊ธฐ์กด 600์žฅ\n", "parts = [make_images(80, seed=80, dark=True), make_images(80, seed=81, mud=0.05), make_images(80, seed=82)]\n", "m_new = pd.concat([p[0] for p in parts], ignore_index=True); i_new = np.concatenate([p[1] for p in parts]); k_new = np.concatenate([p[2] for p in parts])\n", "m_new[\"img_id\"] = [\"NEW\" + x[3:] for x in m_new[\"img_id\"]] # ์‹ ๊ทœ ์‚ฌ์ง„ ID ๊ตฌ๋ถ„\n", "meta = pd.concat([m_old, m_new], ignore_index=True); imgs = np.concatenate([i_old, i_new]); masks = np.concatenate([k_old, k_new])\n", "chk = {\"์‚ฌ์ง„ ID ์œ ์ผ\": not meta[\"img_id\"].duplicated().any(), \"๊ฒฐ์ธก 0(์ œ์›ยท๋ผ๋ฒจ)\": int(meta[[\"equip_type\", \"mfg_year\", \"op_hours\", \"env\", \"reg_grade\"]].isna().sum().sum()) == 0,\n", " \"ํ™”์†Œ ๋ฒ”์œ„ 0~1\": bool((imgs.min() >= 0) and (imgs.max() <= 1)), \"ํ•ด์ƒ๋„ 64x64x3\": imgs.shape[1:] == (64, 64, 3), \"์ œ์กฐ์—ฐ๋„ ๊ฐ€๋Šฅ ๋ฒ”์œ„\": bool(meta[\"mfg_year\"].between(1990, 2026).all()),\n", " \"๋งˆ์Šคํฌ ๋ฉด์  = ๋ผ๋ฒจ ๋ฉด์ \": bool(np.allclose(masks.mean(axis=(1, 2)), meta[\"area_frac\"], atol=1e-6)), \"์†์ƒ๋„-๋“ฑ๊ธ‰ ์ผ์น˜\": bool((grade_of(meta[\"damage_idx\"]) == meta[\"grade\"]).all())}\n", "print(f\"[1] ์‹ ๊ทœ 240์žฅ ํฌํ•จ {len(meta)}์žฅ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ {sum(chk.values())}/{len(chk)} ํ†ต๊ณผ, ๊ธด๊ธ‰ ์ ๊ฒ€ ์‚ฌ์ง„ {int(meta['emergency'].sum())}์žฅ ๋ณด์กด\")\n", "F = feature_table(imgs)\n", "F[\"mud\"] = [mud_est(x) for x in imgs]\n", "n_old = len(m_old)\n", "\n", "# ---------------- 2) ๋ถ„ํฌ ๋ณ€ํ™” ์ ๊ฒ€(PSI) ๋ฐ ์ž๋™ ์žฌํ•™์Šต ----------------\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", "drift = {c: psi(F[c][:n_old], F[c][n_old:]) for c in [\"gain\", \"c_mean\", \"c_std\", \"rust_frac\", \"area_frac\", \"mud\"]}\n", "worst = max(drift, key=drift.get)\n", "print(\"[2] ๋ถ„ํฌ ๋ณ€ํ™” PSI(๊ธฐ์กด 600 vs ์‹ ๊ทœ 240):\", \", \".join(f\"{k} {v:.2f}\" for k, v in drift.items()), f\"-> ๊ฐ€์žฅ ํฐ ๋ณ€ํ™” {worst} (0.2 ์ด์ƒ์ด๋ฉด ์žฌํ•™์Šต ๊ฒ€ํ† )\")\n", "rng = np.random.default_rng(0)\n", "new_idx = rng.permutation(np.arange(n_old, len(meta)))\n", "tr_new, hold = new_idx[:160], new_idx[160:] # ์‹ ๊ทœ ์ค‘ 160์žฅ์€ ์žฌํ•™์Šต์šฉ, 80์žฅ์€ ๊ฒ€์ฆ์šฉ\n", "cols = [c for c in F.columns if c != \"mud\"]\n", "\n", "\n", "def fit_on(ix):\n", " return fit_pipeline(meta.iloc[ix].reset_index(drop=True), F.iloc[ix][cols].reset_index(drop=True))\n", "\n", "\n", "def score(pl, ix):\n", " cls, P, idx, _ = predict_pipeline(pl, F.iloc[ix][cols])\n", " y = meta[\"damage_idx\"].values[ix]\n", " return float(np.mean(np.abs(y - idx))), float(np.mean(grade_of(idx) == meta[\"grade\"].values[ix]))\n", "\n", "\n", "champ = fit_on(np.arange(n_old))\n", "chall = fit_on(np.r_[np.arange(n_old), tr_new])\n", "a, b = score(champ, hold), score(chall, hold)\n", "promote = (a[0] - b[0]) / a[0] >= 0.03\n", "drifted = max(drift.values()) >= 0.2\n", "print(f\" ๊ฒ€์ฆ 80์žฅ(์‹ ๊ทœ ๋ถ„ํฌ): ์ฑ”ํ”ผ์–ธ MAE {a[0]:.2f}ยท๋“ฑ๊ธ‰ ์ผ์น˜ {a[1]:.1%} vs ์ฑŒ๋ฆฐ์ € MAE {b[0]:.2f}ยท๋“ฑ๊ธ‰ ์ผ์น˜ {b[1]:.1%} -> {'์ฑŒ๋ฆฐ์ € ์Šน๊ฒฉ' if promote else '๊ธฐ์กด ๋ชจ๋ธ ์œ ์ง€'}(MAE 3% ์ด์ƒ ๊ฐœ์„ ์ผ ๋•Œ๋งŒ ์Šน๊ฒฉ)\")\n", "model = chall if promote else champ\n", "\n", "# ---------------- 3) ์‹ ๊ทœ ์‚ฌ์ง„ ํŒ์ •๊ณผ ๊ฒ€์‚ฌ ๋Œ€๊ธฐ ๋ถ„๋ฆฌ ----------------\n", "Xn = F.iloc[n_old:][cols].reset_index(drop=True)\n", "cls, P, idx, _ = predict_pipeline(model, Xn)\n", "mud = F[\"mud\"].values[n_old:]\n", "conf = confidence(P, idx, Xn, mud)\n", "res = pd.DataFrame({\"์‚ฌ์ง„\": meta[\"img_id\"].values[n_old:], \"ํŒ์ • ์œ ํ˜•\": [KO_CLS[c] for c in cls], \"์†์ƒ๋„\": idx, \"AI ๋“ฑ๊ธ‰\": grade_of(idx), \"์‹ ๋ขฐ๋„\": conf, \"์˜ค์—ผ\": mud, \"๊ธฐ๋ก ๋“ฑ๊ธ‰\": meta[\"reg_grade\"].values[n_old:], \"์‹ค์ œ ๋“ฑ๊ธ‰\": meta[\"grade\"].values[n_old:]})\n", "res[\"์ฒ˜๋ฆฌ\"] = np.select([(mud >= 0.03) | (Xn[\"gain\"].values < 0.17), conf >= 80], [\"์žฌ์ดฌ์˜ ์š”๊ตฌ\", \"๊ฒ€์‚ฌ ๋Œ€๊ธฐ(๊ฐ„์ด ์Šน์ธ ํ›„๋ณด)\"], \"๊ฒ€์‚ฌ ๋Œ€๊ธฐ(์ •๋ฐ€ ํŒ๋…)\")\n", "res[\"์ œ์› ์‹ ํ˜ธ\"] = np.abs(res[\"AI ๋“ฑ๊ธ‰\"] - res[\"๊ธฐ๋ก ๋“ฑ๊ธ‰\"]) >= 2\n", "print(f\"\\n[3] ์‹ ๊ทœ {len(res)}์žฅ ํŒ์ • ๊ฒฐ๊ณผ [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] (์ƒ์œ„ 6๊ฑด)\")\n", "print(res.head(6)[[\"์‚ฌ์ง„\", \"ํŒ์ • ์œ ํ˜•\", \"์†์ƒ๋„\", \"AI ๋“ฑ๊ธ‰\", \"์‹ ๋ขฐ๋„\", \"์˜ค์—ผ\", \"์ฒ˜๋ฆฌ\"]].round(2).to_string(index=False))\n", "vc = res[\"์ฒ˜๋ฆฌ\"].value_counts()\n", "print(\" ์ฒ˜๋ฆฌ ๊ตฌ๋ถ„:\", vc.to_dict(), \"/ ์ž๋™ ํ™•์ • 0๊ฑด(๋ชจ๋“  ํŒ์ •์€ ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ๊ณผ ์ •๋น„๊ฐ๋…๊ด€ ๊ฒฐ์žฌ ํ•„์š”)\")\n", "auto = res[\"์ฒ˜๋ฆฌ\"].str.contains(\"๊ฐ„์ด\")\n", "print(f\" ๊ฐ„์ด ์Šน์ธ ํ›„๋ณด {int(auto.sum())}์žฅ์˜ ๋“ฑ๊ธ‰ ์ •๋‹ต ์ผ์น˜์œจ {(res['AI ๋“ฑ๊ธ‰'] == res['์‹ค์ œ ๋“ฑ๊ธ‰'])[auto].mean():.1%}, ์ „์ฒด ์ผ์น˜์œจ {(res['AI ๋“ฑ๊ธ‰'] == res['์‹ค์ œ ๋“ฑ๊ธ‰']).mean():.1%}\")\n", "print(f\" ์ œ์› ํ™˜๋ฅ˜: AI ๋“ฑ๊ธ‰๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋ก ๋“ฑ๊ธ‰์ด 2๋‹จ๊ณ„ ์ด์ƒ ๋‹ค๋ฅธ {int(res['์ œ์› ์‹ ํ˜ธ'].sum())}์žฅ์€ ์ œ์› ์ˆ˜์ • ์š”์ฒญ ํ›„๋ณด(์ž๋™ ์ˆ˜์ • ๊ธˆ์ง€, ๋‹ด๋‹น์ž ํ™•์ธ)\")\n", "\n", "# ---------------- 4) ์šด์˜ ๋ฌธ์„œ ์™„๋น„์œจยท์ธํ„ฐํŽ˜์ด์Šค ๋ช…์„ธ ----------------\n", "docs = {\"๋ฐ์ดํ„ฐ ์‚ฌ์ „ยท์Šคํ‚ค๋งˆ(M1)\": 24, \"์ •์ œ ๊ทœ์น™ 11์ข…(M2)\": 11, \"BaselineยทEDA(M3)\": 8, \"๋ถ„ํ• ยท์†์ƒ๋„ ๋ชจ๋ธ(M4)\": 24, \"๋“ฑ๊ธ‰ยท์‹ ๋ขฐ๋„ ์—”์ง„(M5)\": 3, \"ํŒ์ • ์ธํ„ฐํŽ˜์ด์Šคยท์„ค๋ช…(M6)\": 5,\n", " \"์Šน์ธ ์ ˆ์ฐจยท๊ถŒํ•œ(M7)\": 4, \"์˜ค์—ผ ์ŠคํŠธ๋ ˆ์Šคยทํ•œ๊ณ„์น˜(M8)\": 7, \"AARยทROI(M9)\": 4, \"์šด์˜ยท์žฌํ•™์Šต ์ ˆ์ฐจ(M10)\": 6, \"์žฅ์•  ๋Œ€์‘\": 5, \"์ธํ„ฐํŽ˜์ด์Šค ๊ฐ€์ด๋“œ\": 4}\n", "done = sum(v > 0 for v in docs.values())\n", "iface = {\"input\": {\"photo\": [\"img_id:str\", \"image:64x64x3 float\", \"equip_type:str\", \"reg_grade:int\"], \"format\": \"image+json\"},\n", " \"output\": {\"result\": [\"img_id\", \"defect\", \"damage_idx\", \"grade\", \"confidence\", \"mud\", \"state\"], \"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", "ev_ix = np.arange(n_old, len(meta))\n", "y = meta[\"damage_idx\"].values[ev_ix]\n", "fin_mae = float(np.mean(np.abs(y - idx)))\n", "big = y >= 10\n", "fin_mape = float(np.mean(np.abs(y[big] - idx[big]) / y[big]) * 100)\n", "base = LinearRegression().fit(F.iloc[:n_old][[\"area_px\"]], meta[\"damage_idx\"][:n_old])\n", "base_mae = float(np.mean(np.abs(y - np.clip(base.predict(F.iloc[n_old:][[\"area_px\"]]), 0, 100))))\n", "checks = [(\"ํ™˜๊ฒฝ ์ ๊ฒ€ ํ†ต๊ณผ\", env_ok == len(libs)), (\"๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ์ „ ํ•ญ๋ชฉ ํ†ต๊ณผ\", all(chk.values())), (\"์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์ •์ƒ ์‹คํ–‰(์ฑ”ํ”ผ์–ธ/์ฑŒ๋ฆฐ์ € ํŒ์ • ์ถœ๋ ฅ)\", a[0] > 0 and b[0] > 0),\n", " (\"์†์ƒ๋„ MAPE 25% ์ดํ•˜(์ ์ˆ˜ 10 ์ด์ƒ)\", fin_mape <= 25), (\"๋ฉด์  ๊ธฐ์ค€ ๋ชจ๋ธ ๋Œ€๋น„ MAE 20% ์ด์ƒ ๊ฐœ์„ \", fin_mae <= 0.8 * base_mae),\n", " (\"์ž๋™ ํ™•์ • 0๊ฑด(๊ฒ€์‚ฌ ๋Œ€๊ธฐยท์žฌ์ดฌ์˜ ๋ถ„๋ฆฌ)\", bool(res[\"์ฒ˜๋ฆฌ\"].str.contains(\"๊ฒ€์‚ฌ ๋Œ€๊ธฐ|์žฌ์ดฌ์˜\").all())), (\"์˜ค์—ผ ์˜์‹ฌ ์‚ฌ์ง„ ์ „๋ถ€ ์žฌ์ดฌ์˜ ๋˜๋Š” ์ •๋ฐ€ ํŒ๋…์œผ๋กœ ๋ถ„๋ฆฌ\", bool(res.loc[res[\"์˜ค์—ผ\"] >= 0.01, \"์ฒ˜๋ฆฌ\"].ne(\"๊ฒ€์‚ฌ ๋Œ€๊ธฐ(๊ฐ„์ด ์Šน์ธ ํ›„๋ณด)\").all())),\n", " (\"์šด์˜ ๋ฌธ์„œ 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\" - ์‹ ๊ทœ ์‚ฌ์ง„ 240์žฅ์œผ๋กœ ๋ถ„ํฌ ๋ณ€ํ™”๋ฅผ ์ ๊ฒ€({worst} PSI {drift[worst]:.2f}, {'์žฌํ•™์Šต ๊ฒ€ํ†  ์ˆ˜์ค€' if drifted else '๊ฒฝ๋ฏธ'})ํ•˜๊ณ  ์žฌํ•™์Šต ํŒ๋‹จ์„ ์ž๋™ ์ˆ˜ํ–‰ํ–ˆ๋‹ค({'์ฑŒ๋ฆฐ์ € ์Šน๊ฒฉ' if promote else '๊ธฐ์กด ๋ชจ๋ธ ์œ ์ง€'}).\")\n", "print(f\" - ์‹ ๊ทœ ์‚ฌ์ง„ ์†์ƒ๋„ MAE {base_mae:.1f}(๋ฉด์  ๊ธฐ์ค€) -> {fin_mae:.1f}, MAPE {fin_mape:.1f}%. ์ฒ˜๋ฆฌ ๊ตฌ๋ถ„: \" + \", \".join(f\"{k} {v}์žฅ\" for k, v in vc.items()) + \".\")\n", "print(\" - ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰์€ ๊ฒ€์‚ฌ๊ด€ยท์ •๋น„๊ฐ๋…๊ด€ ๊ฒฐ์žฌ๋กœ ํ™•์ •ํ•˜๋ฉฐ AI ํŒ์ •์€ ์ž๋™ ํ™•์ •๋˜์ง€ ์•Š๋Š”๋‹ค. ์˜ค์—ผ ์‚ฌ์ง„์€ ์žฌ์ดฌ์˜์„ ์š”๊ตฌํ–ˆ๋‹ค.\")\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 photos\"); ax[0].tick_params(axis=\"x\", rotation=30)\n", "ax[1].scatter(y, idx, s=8, c=np.where(res[\"์ฒ˜๋ฆฌ\"] == \"์žฌ์ดฌ์˜ ์š”๊ตฌ\", \"r\", \"g\")); ax[1].plot([0, 100], [0, 100], \"k--\"); ax[1].set_title(\"New photos: damage index true vs predicted (red = retake)\")\n", "plt.suptitle(\"QLT03 integrated pipeline (synthetic photos)\"); 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] ์‹ ๊ทœ 240์žฅ ํฌํ•จ 840์žฅ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ 7/7 ํ†ต๊ณผ, ๊ธด๊ธ‰ ์ ๊ฒ€ ์‚ฌ์ง„ 38์žฅ ๋ณด์กด\n", "[2] ๋ถ„ํฌ ๋ณ€ํ™” PSI(๊ธฐ์กด 600 vs ์‹ ๊ทœ 240): gain 0.56, c_mean 0.10, c_std 0.26, rust_frac 0.39, area_frac 0.07, mud 0.79 -> ๊ฐ€์žฅ ํฐ ๋ณ€ํ™” mud (0.2 ์ด์ƒ์ด๋ฉด ์žฌํ•™์Šต ๊ฒ€ํ† )\n", " ๊ฒ€์ฆ 80์žฅ(์‹ ๊ทœ ๋ถ„ํฌ): ์ฑ”ํ”ผ์–ธ MAE 8.79ยท๋“ฑ๊ธ‰ ์ผ์น˜ 82.5% vs ์ฑŒ๋ฆฐ์ € MAE 6.86ยท๋“ฑ๊ธ‰ ์ผ์น˜ 86.2% -> ์ฑŒ๋ฆฐ์ € ์Šน๊ฒฉ(MAE 3% ์ด์ƒ ๊ฐœ์„ ์ผ ๋•Œ๋งŒ ์Šน๊ฒฉ)\n", "\n", "[3] ์‹ ๊ทœ 240์žฅ ํŒ์ • ๊ฒฐ๊ณผ [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] (์ƒ์œ„ 6๊ฑด)\n", " ์‚ฌ์ง„ ํŒ์ • ์œ ํ˜• ์†์ƒ๋„ AI ๋“ฑ๊ธ‰ ์‹ ๋ขฐ๋„ ์˜ค์—ผ ์ฒ˜๋ฆฌ\n", "NEW800000 ๊ท ์—ด 34.85 1 53.85 0.0 ์žฌ์ดฌ์˜ ์š”๊ตฌ\n", "NEW800001 ๋ถ€์‹ 30.58 1 72.79 0.0 ๊ฒ€์‚ฌ ๋Œ€๊ธฐ(์ •๋ฐ€ ํŒ๋…)\n", "NEW800002 ๊ฒฐํ•จ ์—†์Œ 1.25 1 87.85 0.0 ๊ฒ€์‚ฌ ๋Œ€๊ธฐ(๊ฐ„์ด ์Šน์ธ ํ›„๋ณด)\n", "NEW800003 ๊ฒฐํ•จ ์—†์Œ 9.30 1 86.00 0.0 ๊ฒ€์‚ฌ ๋Œ€๊ธฐ(๊ฐ„์ด ์Šน์ธ ํ›„๋ณด)\n", "NEW800004 ๋ถ€์‹ 41.78 2 81.09 0.0 ์žฌ์ดฌ์˜ ์š”๊ตฌ\n", "NEW800005 ๋ถ€์‹ 36.30 2 58.39 0.0 ์žฌ์ดฌ์˜ ์š”๊ตฌ\n", " ์ฒ˜๋ฆฌ ๊ตฌ๋ถ„: {'๊ฒ€์‚ฌ ๋Œ€๊ธฐ(๊ฐ„์ด ์Šน์ธ ํ›„๋ณด)': 112, '๊ฒ€์‚ฌ ๋Œ€๊ธฐ(์ •๋ฐ€ ํŒ๋…)': 86, '์žฌ์ดฌ์˜ ์š”๊ตฌ': 42} / ์ž๋™ ํ™•์ • 0๊ฑด(๋ชจ๋“  ํŒ์ •์€ ๊ฒ€์‚ฌ๊ด€ ํ™•์ธ๊ณผ ์ •๋น„๊ฐ๋…๊ด€ ๊ฒฐ์žฌ ํ•„์š”)\n", " ๊ฐ„์ด ์Šน์ธ ํ›„๋ณด 112์žฅ์˜ ๋“ฑ๊ธ‰ ์ •๋‹ต ์ผ์น˜์œจ 99.1%, ์ „์ฒด ์ผ์น˜์œจ 90.4%\n", " ์ œ์› ํ™˜๋ฅ˜: AI ๋“ฑ๊ธ‰๊ณผ ๊ด€๋ฆฌ ๊ธฐ๋ก ๋“ฑ๊ธ‰์ด 2๋‹จ๊ณ„ ์ด์ƒ ๋‹ค๋ฅธ 19์žฅ์€ ์ œ์› ์ˆ˜์ • ์š”์ฒญ ํ›„๋ณด(์ž๋™ ์ˆ˜์ • ๊ธˆ์ง€, ๋‹ด๋‹น์ž ํ™•์ธ)\n", "\n", "[4] ์šด์˜ ๋ฌธ์„œ 12๊ฐœ ํ•ญ๋ชฉ ์ค‘ ์ž‘์„ฑ ์™„๋ฃŒ 12๊ฐœ (100%) / ์ธํ„ฐํŽ˜์ด์Šค ๋ช…์„ธ(JSON):\n", " {\"input\": {\"photo\": [\"img_id:str\", \"image:64x64x3 float\", \"equip_type:str\", \"reg_grade:int\"], \"format\": \"image+json\"}, \"output\": {\"result\": [\"img_id\", \"defect\", \"damage_idx\", \"grade\", \"confidence\", \"mud\", \"state\"], \"approval\": \"PENDING_ONLY\"}, \"auth\": \"role-based(๊ฐ€์ƒ)\", \"note\": \"์ƒ์œ„ ๊ตฐ์ˆ˜์ •๋ณด์ฒด๊ณ„๋Š” ๊ฐ€์ƒ ์ธํ„ฐํŽ˜์ด์Šค๋กœ๋งŒ ์ •์˜\"}\n", "\n", "[5] ์ž์ฒด ์ ๊ฒ€\n", " [PASS] ํ™˜๊ฒฝ ์ ๊ฒ€ ํ†ต๊ณผ\n", " [PASS] ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ์ „ ํ•ญ๋ชฉ ํ†ต๊ณผ\n", " [PASS] ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์ •์ƒ ์‹คํ–‰(์ฑ”ํ”ผ์–ธ/์ฑŒ๋ฆฐ์ € ํŒ์ • ์ถœ๋ ฅ)\n", " [PASS] ์†์ƒ๋„ MAPE 25% ์ดํ•˜(์ ์ˆ˜ 10 ์ด์ƒ)\n", " [PASS] ๋ฉด์  ๊ธฐ์ค€ ๋ชจ๋ธ ๋Œ€๋น„ MAE 20% ์ด์ƒ ๊ฐœ์„ \n", " [PASS] ์ž๋™ ํ™•์ • 0๊ฑด(๊ฒ€์‚ฌ ๋Œ€๊ธฐยท์žฌ์ดฌ์˜ ๋ถ„๋ฆฌ)\n", " [PASS] ์˜ค์—ผ ์˜์‹ฌ ์‚ฌ์ง„ ์ „๋ถ€ ์žฌ์ดฌ์˜ ๋˜๋Š” ์ •๋ฐ€ ํŒ๋…์œผ๋กœ ๋ถ„๋ฆฌ\n", " [PASS] ์šด์˜ ๋ฌธ์„œ 100% ์™„๋น„\n", "\n", "[6] ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ\n", " - ์‹ ๊ทœ ์‚ฌ์ง„ 240์žฅ์œผ๋กœ ๋ถ„ํฌ ๋ณ€ํ™”๋ฅผ ์ ๊ฒ€(mud PSI 0.79, ์žฌํ•™์Šต ๊ฒ€ํ†  ์ˆ˜์ค€)ํ•˜๊ณ  ์žฌํ•™์Šต ํŒ๋‹จ์„ ์ž๋™ ์ˆ˜ํ–‰ํ–ˆ๋‹ค(์ฑŒ๋ฆฐ์ € ์Šน๊ฒฉ).\n", " - ์‹ ๊ทœ ์‚ฌ์ง„ ์†์ƒ๋„ MAE 13.0(๋ฉด์  ๊ธฐ์ค€) -> 4.5, MAPE 10.6%. ์ฒ˜๋ฆฌ ๊ตฌ๋ถ„: ๊ฒ€์‚ฌ ๋Œ€๊ธฐ(๊ฐ„์ด ์Šน์ธ ํ›„๋ณด) 112์žฅ, ๊ฒ€์‚ฌ ๋Œ€๊ธฐ(์ •๋ฐ€ ํŒ๋…) 86์žฅ, ์žฌ์ดฌ์˜ ์š”๊ตฌ 42์žฅ.\n", " - ์ตœ์ข… ์ •๋น„ ๋“ฑ๊ธ‰์€ ๊ฒ€์‚ฌ๊ด€ยท์ •๋น„๊ฐ๋…๊ด€ ๊ฒฐ์žฌ๋กœ ํ™•์ •ํ•˜๋ฉฐ AI ํŒ์ •์€ ์ž๋™ ํ™•์ •๋˜์ง€ ์•Š๋Š”๋‹ค. ์˜ค์—ผ ์‚ฌ์ง„์€ ์žฌ์ดฌ์˜์„ ์š”๊ตฌํ–ˆ๋‹ค.\n", "```\n", "
\n" ] } ], "metadata": { "colab": { "provenance": [] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }