{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ๐ŸŽฏ [TRN03] ๋“œ๋ก ยท๋ฏธ์‚ฌ์ผ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ํ•™์Šต์•ฑ\n", "### Google Colab ์‹ค์Šต ํ™˜๊ฒฝ (๊ต์œกยทํ›ˆ๋ จ / Level 2)\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/TRN03/TRN03_solution.ipynb)\n", "\n", "---\n", "\n", "## ๐Ÿ“Œ 1. ํ”„๋กœ์ ํŠธ ๊ฐœ์š” ๋ฐ ๋ฐฐ๊ฒฝ\n", "\n", "| ํ•ญ๋ชฉ | ๋‚ด์šฉ |\n", "| :--- | :--- |\n", "| **๊ณผ์ • ๋ถ„๋ฅ˜** | ๊ต์œกยทํ›ˆ๋ จ (Level 2) |\n", "| **ํ•™์Šต ์†Œ์š” ์‹œ๊ฐ„** | 10๊ฐœ ๋ฏธ์…˜ (5์ผ / ์ด 27.5์‹œ๊ฐ„) |\n", "| **ํ™˜๊ฒฝ ์‚ฌ์–‘** | Python 3.10+, numpy, pandas, matplotlib, scipy, scikit-learn |\n", "\n", "### ๐Ÿ“– ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ\n", "๋Ÿฌ์‹œ์•„-์šฐํฌ๋ผ์ด๋‚˜ ์ „์Ÿ๊ณผ ๋ฏธ๊ตญ-์ด๋ž€ ๋ถ„์Ÿ ์‚ฌ๋ก€์—์„œ๋Š” ์†Œํ˜• ๋“œ๋ก , ์žํญ ๋“œ๋ก , ์ˆœํ•ญยทํƒ„๋„๋ฏธ์‚ฌ์ผ์ด ํ•จ๊ป˜ ์“ฐ์ด๋ฉด์„œ, ์žฅ๋ณ‘์ด ํ•˜๋Š˜์˜ ๋น„ํ–‰์ฒด ํ˜•์ƒ์„ ๋ณด๊ณ  ์œ„ํ˜‘ ์—ฌ๋ถ€๋ฅผ ๋นจ๋ฆฌ ๊ตฌ๋ถ„ํ•˜๋Š” ๋Šฅ๋ ฅ์ด ์ค‘์š”ํ•œ ๊ธฐ๋ณธ ์ „ํˆฌ๋ ฅ์ด ๋˜์—ˆ๋‹ค. ๋ณธ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์‹ค์ œ ๊ธฐ์ฒด ์‚ฌ์ง„ ๋Œ€์‹  ์‚ฌ๊ฐํ˜•ยท์‚ผ๊ฐํ˜•ยท์› ๊ฐ™์€ ๊ธฐ๋ณธ ๋„ํ˜•์œผ๋กœ ๋งŒ๋“  ๊ต์œก์šฉ ํ•ฉ์„ฑ ์‹ค๋ฃจ์—ฃ์„ ์ฝ”๋“œ๋กœ ์ƒ์„ฑํ•ด, ๋ ˆ์ด๋ธ” ์ฒด๊ณ„ ์ •์˜, ๋ช…์•”ยท์œค๊ณฝ์„ ยทํฌ๊ธฐ ์ •๊ทœํ™” ์ „์ฒ˜๋ฆฌ, ํ˜•ํƒœ ํŠน์ง• ๊ธฐ๋ฐ˜ Baseline, ๋‹ค์–‘ํ•œ ๊ฐ๋„ยท์›๊ฑฐ๋ฆฌยทํ๋ฆผ ์กฐ๊ฑด์˜ ์‹ ๊ฒฝ๋ง ๋ถ„๋ฅ˜ ๊ณ ๋„ํ™”, ์œ ์‚ฌ ํ˜•์ƒ ์˜ค๋ถ„๋ฅ˜ ๋ฐฉ์ง€ ์‹ ๋ขฐ๋„ ์ ์ˆ˜, ํ•™์Šต ์•ฑ UI, ๊ต๊ด€ ํ•ด์„ค ๊ฒ€ํ† (HITL), ๋Œ€๋Ÿ‰ ์ €ํ’ˆ์งˆ ์ด๋ฏธ์ง€ ๋ถ€ํ•˜ ์‹œํ—˜, AAR ์ฆ๊ฐ• ์žฌํ•™์Šต, ์ตœ์ข… ํŒจํ‚ค์ง•๊นŒ์ง€ ์‹ค๋ฃจ์—ฃ ์‹๋ณ„ ํ•™์Šต์•ฑ์„ ๋‹จ๊ณ„๋ณ„๋กœ ๊ตฌํ˜„ํ•œ๋‹ค. ์ตœ์ข… ๋ชฉํ‘œ๋Š” 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", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋ฏผ๊ฐ„ ์ฟผ๋“œ์ฝฅํ„ฐํ˜•ยท๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•ยท์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜•ยท์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜•ยทํƒ„๋„๋ฏธ์‚ฌ์ผํ˜•ยท์กฐ๋ฅ˜ 6์ข… ๋ ˆ์ด๋ธ”๊ณผ ์œ„ํ˜‘ ์—ฌ๋ถ€, ์ดฌ์˜ ์กฐ๊ฑด(๊ธฐ๋ณธยท์›๊ฑฐ๋ฆฌยท์ธก๋ฉดยท์•ผ๊ฐ„ยทํ๋ฆผ), ์„ผ์„œ(EO/IR), ์ „์ˆ  ํŒŒ๋ผ๋ฏธํ„ฐ(๊ธฐ์ƒ, ๊ฑฐ๋ฆฌ ๋“ฑ๊ธ‰, ์œ„์น˜์˜ค์ฐจ, ํ†ต์‹ ์ง€์—ฐ), ๊ต๊ด€ ๊ฒ€์ˆ˜ ์ƒํƒœ ํ•„๋“œ ์ •์˜ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `data/schema/trn03_input_schema.json ๋ฐ docs/trn03_data_spec.md` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ต์œกยทํ›ˆ๋ จ ์‹ค๋ฌด ๋ฐ ๊ณตํ†ตยท๋ณตํ•ฉ ์‚ฌ๋ก€ (๋Ÿฌ์‹œ์•„-์šฐํฌ๋ผ์ด๋‚˜ยท๋ฏธ๊ตญ-์ด๋ž€) ์ „์žฅ ํ™˜๊ฒฝ๊ณผ์˜ ๊ตฐ์‚ฌ์  ๋ถ€ํ•ฉ์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์Šคํ‚ค๋งˆ ํ•„๋“œ ์ •์˜ ์™„์„ฑ๋„ 100%, ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ์˜ค๋ฅ˜ 0๊ฑด\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์ž…๋ ฅ ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์Šคํฌ๋ฆฝํŠธ ํ†ต๊ณผ ๋ฐ ๋ช…์„ธ์„œ ๋ฌธ์„œํ™” ์™„๋ฃŒ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: JSON Schema ๊ฒ€์ฆ ๋„๊ตฌ ๋ฐ ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์ „์ˆ  ๋“œ๋ก ยท์ˆœํ•ญ/ํƒ„๋„๋ฏธ์‚ฌ์ผ ๋“ฑ ๋น„ํ–‰์ฒด ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€(ํ‘๋ฐฑยท์›๊ฑฐ๋ฆฌยท๋‹ค๊ฐ๋„) ํ•™์Šต ๋ฐ์ดํ„ฐ์…‹์˜ ๋ ˆ์ด๋ธ” ์ฒด๊ณ„์™€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•˜๊ณ , ํ•ฉ์„ฑ ์‹ค๋ฃจ์—ฃ ์ƒ˜ํ”Œ์„ ๋งŒ๋“ค์–ด ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import json\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from matplotlib.path import Path as MplPath\n", "from scipy import ndimage\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•ฉ์„ฑ ์‹ค๋ฃจ์—ฃ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ธฐ์ฒด ์‚ฌ์ง„ยท๋„๋ฉดยท์ œ์› ์‚ฌ์šฉ ์•ˆ ํ•จ)\n", "# ์‚ฌ๊ฐํ˜•ยท์‚ผ๊ฐํ˜•ยท์› ๊ฐ™์€ ๊ธฐ๋ณธ ๋„ํ˜•์„ ์กฐํ•ฉํ•œ ์ถ”์ƒ ์‹ค๋ฃจ์—ฃ์„ 32x32 ํ‘๋ฐฑ ์ด๋ฏธ์ง€๋กœ ๊ทธ๋ฆฐ๋‹ค.\n", "# ํšŒ์ „(๋‹ค๊ฐ๋„), ์›๊ทผ ์ถ•์†Œ(์ธก๋ฉด ์‹œ์ ), ํฌ๊ธฐ(์›๊ฑฐ๋ฆฌ), ํ๋ฆผยท์žก์Œยท์ €๋Œ€๋น„ยท๊ตฌ๋ฆ„ยท๊ฐ€๋ฆผยท์•ผ๊ฐ„ ๋ฐ˜์ „์„ ์กฐ์ ˆํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "IMG = 32\n", "CLASSES = [\"civil_quad\", \"civil_fw\", \"attack_fw\", \"cruise\", \"ballistic\", \"bird\"]\n", "KOR = {\"civil_quad\": \"๋ฏผ๊ฐ„ ์ฟผ๋“œ์ฝฅํ„ฐํ˜•\", \"civil_fw\": \"๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•\", \"attack_fw\": \"์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜•\",\n", " \"cruise\": \"์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜•\", \"ballistic\": \"ํƒ„๋„๋ฏธ์‚ฌ์ผํ˜•\", \"bird\": \"์กฐ๋ฅ˜\"}\n", "THREAT = {\"attack_fw\", \"cruise\", \"ballistic\"}\n", "\n", "\n", "def _rect(x0, x1, y0, y1):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ์ „์ˆ  ๋“œ๋ก  ๋ฐ ์ˆœํ•ญ/ํƒ„๋„๋ฏธ์‚ฌ์ผ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€(ํ‘๋ฐฑ/์›๊ฑฐ๋ฆฌ/๋‹ค๊ฐ๋„) ๋ฐ์ดํ„ฐ์…‹ ๊ตฌ์ถ• ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def _bar(p, q, w):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ์ „์ˆ  ๋“œ๋ก  ๋ฐ ์ˆœํ•ญ/ํƒ„๋„๋ฏธ์‚ฌ์ผ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€(ํ‘๋ฐฑ/์›๊ฑฐ๋ฆฌ/๋‹ค๊ฐ๋„) ๋ฐ์ดํ„ฐ์…‹ ๊ตฌ์ถ• ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "SHAPES = {\n", " \"civil_quad\": {\"poly\": [_bar((-.55, -.55), (.55, .55), .1), _bar((-.55, .55), (.55, -.55), .1), _rect(-.15, .15, -.15, .15)],\n", " \"circ\": [(-.55, -.55, .24), (.55, .55, .24), (-.55, .55, .24), (.55, -.55, .24)]},\n", " \"civil_fw\": {\"poly\": [_rect(-.07, .07, -.8, .72), _rect(-.9, .9, .05, .25), _rect(-.33, .33, -.8, -.66)], \"circ\": []},\n", " \"attack_fw\": {\"poly\": [[(0, .85), (.85, -.5), (.12, -.42), (0, -.6), (-.12, -.42), (-.85, -.5)]], \"circ\": []},\n", " \"cruise\": {\"poly\": [_rect(-.07, .07, -.9, .7), [(-.07, .7), (0, .92), (.07, .7)], _rect(-.42, .42, -.05, .07),\n", " [(-.07, -.9), (-.24, -.95), (-.07, -.74)], [(.07, -.9), (.24, -.95), (.07, -.74)]], \"circ\": []},\n", " \"ballistic\": {\"poly\": [_rect(-.13, .13, -.85, .6), [(-.13, .6), (0, .95), (.13, .6)],\n", " [(-.13, -.85), (-.34, -.95), (-.13, -.58)], [(.13, -.85), (.34, -.95), (.13, -.58)]], \"circ\": []},\n", " \"bird\": {\"poly\": [[(-.9, .12), (-.5, .28), (-.12, .06), (0, .22), (.12, .06), (.5, .28), (.9, .12), (.5, .12),\n", " (.12, -.1), (0, -.38), (-.12, -.1), (-.5, .12)]], \"circ\": []},\n", "}\n", "_S = 2\n", "_g = (np.arange(IMG * _S) + 0.5) / _S\n", "_GX, _GY = np.meshgrid(_g, _g)\n", "_PTS = np.c_[_GX.ravel(), _GY.ravel()]\n", "_PATHS = {c: [MplPath(np.array(p)) for p in s[\"poly\"]] for c, s in SHAPES.items()}\n", "\n", "\n", "def draw(cls, rng, angle=None, scale=None, fore=None, blur=0.3, noise=4.0, contrast=1.0,\n", " clutter=0.0, occlude=0.0, invert=False, shift=1.5):\n", " \"\"\"ํด๋ž˜์Šค 1๊ฐœ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€(uint8 32x32)๋ฅผ ๊ทธ๋ฆฐ๋‹ค. ๋ฐฐ๊ฒฝ์€ ๋ฐ์€ ํ•˜๋Š˜, ๋น„ํ–‰์ฒด๋Š” ์–ด๋‘์šด ๊ทธ๋ฆผ์ž.\"\"\"\n", " angle = rng.uniform(0, 360) if angle is None else angle\n", " scale = rng.uniform(.7, .95) if scale is None else scale\n", " fore = rng.uniform(.85, 1.0) if fore is None else fore\n", " th = np.radians(angle)\n", " c0 = IMG / 2 + rng.uniform(-shift, shift, 2)\n", " d = (_PTS - c0) / (scale * IMG / 2) # ์ด๋ฏธ์ง€ ์ขŒํ‘œ -> ๋„ํ˜• ์ขŒํ‘œ(์—ญ๋ณ€ํ™˜)\n", " u = d[:, 0] * np.cos(th) + d[:, 1] * np.sin(th)\n", " v = (-d[:, 0] * np.sin(th) + d[:, 1] * np.cos(th)) / fore # ์›๊ทผ ์ถ•์†Œ(๊ธฐ์ˆ˜ ๋ฐฉํ–ฅ์œผ๋กœ ์งง์•„ ๋ณด์ž„)\n", " uv = np.c_[u, -v]\n", " m = np.zeros(len(uv), bool)\n", " near = np.flatnonzero(np.abs(uv).max(axis=1) <= 1.0) # ๋„ํ˜•์ด ์žˆ์„ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ๋งŒ ๊ฒ€์‚ฌ(์†๋„)\n", " for p in _PATHS[cls]:\n", " m[near] |= p.contains_points(uv[near])\n", " for cx, cy, r in SHAPES[cls][\"circ\"]:\n", " m |= (uv[:, 0] - cx) ** 2 + (uv[:, 1] - cy) ** 2 <= r * r\n", " cov = m.reshape(IMG, _S, IMG, _S).mean(axis=(1, 3))\n", " if occlude > 0: # ๋ถ€๋ถ„ ๊ฐ€๋ฆผ(๊ตฌ๋ฆ„ ์กฐ๊ฐ)\n", " h = int(IMG * np.sqrt(occlude)); y0, x0 = rng.integers(8, IMG - 8 - h // 2, 2)\n", " cov[y0:y0 + h, x0:x0 + h] = 0\n", " bg = rng.uniform(180, 220) + np.linspace(-10, 10, IMG)[:, None]\n", " fg = rng.uniform(30, 60)\n", " img = bg * (1 - cov) + fg * cov\n", " if clutter > 0: # ๊ตฌ๋ฆ„ยท๋ฐฐ๊ฒฝ ์–ผ๋ฃฉ\n", " img -= clutter * 90 * np.clip(ndimage.gaussian_filter(rng.normal(0, 1, (IMG, IMG)), 3) * 4, 0, None)\n", " if blur > 0:\n", " img = ndimage.gaussian_filter(img, blur)\n", " mean = img.mean()\n", " img = mean + (img - mean) * contrast\n", " img = img + rng.normal(0, noise, img.shape)\n", " if invert: # ์•ผ๊ฐ„ ์—ด์ƒ(IR) ๊ทน์„ฑ ๋ฐ˜์ „\n", " img = 255 - img\n", " return np.clip(img, 0, 255).astype(np.uint8)\n", "\n", "\n", "# ์ดฌ์˜ ์กฐ๊ฑด ํ”„๋ฆฌ์…‹: ๊ธฐ๋ณธ + ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹œ๋‚˜๋ฆฌ์˜ค\n", "CONDITIONS = {\n", " \"basic\": {},\n", " \"offcenter\": {\"shift\": 5.0, \"fore\": (.6, .85)},\n", " \"far\": {\"scale\": (.32, .45), \"blur\": .6},\n", " \"blur\": {\"blur\": (1.2, 1.8)},\n", " \"lowcon\": {\"contrast\": (.2, .35)},\n", " \"clutter\": {\"clutter\": .8},\n", " \"occlude\": {\"occlude\": .08},\n", " \"oblique\": {\"fore\": (.35, .6)},\n", " \"night\": {\"invert\": True, \"noise\": 10.0},\n", " \"noise\": {\"noise\": 28.0},\n", " \"combo\": {\"scale\": (.4, .55), \"blur\": (.8, 1.1), \"noise\": 12.0, \"contrast\": (.6, .8)},\n", "}\n", "\n", "\n", "def draw_cond(cls, cond, rng):\n", " kw = {}\n", " for k, v in CONDITIONS[cond].items():\n", " kw[k] = rng.uniform(*v) if isinstance(v, tuple) else v\n", " return draw(cls, rng, **kw)\n", "\n", "\n", "def make_dataset(n_per_class, seed, conds=(\"basic\",)):\n", " \"\"\"(์ด๋ฏธ์ง€ ๋ฐฐ์—ด N x 32 x 32, ์ •๋‹ต ๋ผ๋ฒจ, ์กฐ๊ฑด) ๋ฐ˜ํ™˜. ์กฐ๊ฑด์€ conds์—์„œ ๋ฒˆ๊ฐˆ์•„ ๊ณ ๋ฅธ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " X, y, c = [], [], []\n", " for cls in CLASSES:\n", " for i in range(n_per_class):\n", " cond = conds[i % len(conds)]\n", " X.append(draw_cond(cls, cond, rng)); y.append(cls); c.append(cond)\n", " return np.array(X), np.array(y), np.array(c)\n", "# ---------------------------------------------------------------------------\n", "\n", "# 1) ์Šคํ‚ค๋งˆ ์ •์˜: ์ด๋ฏธ์ง€ ๊ทœ๊ฒฉ, ๋ ˆ์ด๋ธ”(ํด๋ž˜์Šคยท์œ„ํ˜‘ ์—ฌ๋ถ€), ์ดฌ์˜ ์กฐ๊ฑด, ์ „์ˆ  ํŒŒ๋ผ๋ฏธํ„ฐ, ๊ฒ€์ˆ˜ ์ƒํƒœ\n", "SCHEMA = {\n", " \"fields\": {\n", " \"image_id\": {\"type\": str, \"pattern\": r\"^SIL\\d{5}$\"},\n", " \"class\": {\"type\": str, \"enum\": CLASSES},\n", " \"threat\": {\"type\": bool},\n", " \"condition\": {\"type\": str, \"enum\": list(CONDITIONS)},\n", " \"view\": {\"type\": str, \"enum\": [\"top\", \"oblique\"]},\n", " \"source\": {\"type\": str, \"enum\": [\"synthetic\"]},\n", " \"reviewed_by\": {\"type\": str, \"pattern\": r\"^INS\\d{2}$\"},\n", " \"review_status\": {\"type\": str, \"enum\": [\"draft\", \"approved\"]},\n", " },\n", " \"sensor\": {\"type\": {\"type\": str, \"enum\": [\"EO\", \"IR\"]}, \"resolution\": {\"type\": list, \"length\": 2}},\n", " \"tactical\": {\n", " \"weather\": {\"type\": str, \"enum\": [\"clear\", \"haze\", \"cloud\", \"night\", \"rain\"]},\n", " \"range_level\": {\"type\": str, \"enum\": [\"near\", \"mid\", \"far\"]},\n", " \"position_error_m\": {\"type\": (int, float), \"min\": 0, \"max\": 500},\n", " \"comm_delay_ms\": {\"type\": (int, float), \"min\": 0, \"max\": 10000},\n", " },\n", "}\n", "\n", "\n", "def check(name, value, rule):\n", " if not isinstance(value, rule[\"type\"]) or (isinstance(value, bool) and rule[\"type\"] is not bool):\n", " return [f\"{name}: ์ž๋ฃŒํ˜• ์˜ค๋ฅ˜({type(value).__name__})\"]\n", " errs = []\n", " if \"enum\" in rule and value not in rule[\"enum\"]:\n", " errs.append(f\"{name}: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๊ฐ’ '{value}'\")\n", " if \"min\" in rule and value < rule[\"min\"]:\n", " errs.append(f\"{name}: ์ตœ์†Œ๊ฐ’ {rule['min']} ๋ฏธ๋งŒ\")\n", " if \"max\" in rule and value > rule[\"max\"]:\n", " errs.append(f\"{name}: ์ตœ๋Œ€๊ฐ’ {rule['max']} ์ดˆ๊ณผ\")\n", " if \"length\" in rule and len(value) != rule[\"length\"]:\n", " errs.append(f\"{name}: ๊ธธ์ด {rule['length']} ํ•„์š”\")\n", " if \"pattern\" in rule and not re.match(rule[\"pattern\"], value):\n", " errs.append(f\"{name}: ํ˜•์‹ ๋ถˆ์ผ์น˜ '{value}'\")\n", " return errs\n", "\n", "\n", "def validate(rec):\n", " errs = []\n", " for k, rule in SCHEMA[\"fields\"].items():\n", " errs += [f\"{k}: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\"] if k not in rec else check(k, rec[k], rule)\n", " for g in (\"sensor\", \"tactical\"):\n", " for k, rule in SCHEMA[g].items():\n", " errs += [f\"{g}.{k}: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\"] if k not in rec.get(g, {}) else check(f\"{g}.{k}\", rec[g][k], rule)\n", " px = np.asarray(rec.get(\"pixels\", []), dtype=float)\n", " if px.shape != (IMG, IMG):\n", " errs.append(f\"pixels: ํฌ๊ธฐ {IMG}x{IMG} ํ•„์š”(์ž…๋ ฅ {px.shape})\")\n", " elif not np.isfinite(px).all() or px.min() < 0 or px.max() > 255:\n", " errs.append(\"pixels: ๊ฐ’ ๋ฒ”์œ„(0~255) ๋˜๋Š” ๊ฒฐ์ธก ์˜ค๋ฅ˜\")\n", " if rec.get(\"class\") in CLASSES and rec.get(\"threat\") != (rec[\"class\"] in THREAT):\n", " errs.append(\"threat: ํด๋ž˜์Šค์™€ ์œ„ํ˜‘ ์—ฌ๋ถ€ ๋ถˆ์ผ์น˜\")\n", " return errs\n", "\n", "\n", "# 2) [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํด๋ž˜์Šค๋ณ„ 10์žฅ, ์ด 60์žฅ ์ƒ˜ํ”Œ ์ƒ์„ฑ(๊ธฐ๋ณธ + ์›๊ฑฐ๋ฆฌ + ์ธก๋ฉด + ์•ผ๊ฐ„ ์กฐ๊ฑด)\n", "rng = np.random.default_rng(1)\n", "conds = [\"basic\", \"far\", \"oblique\", \"night\", \"blur\"]\n", "weather = {\"basic\": \"clear\", \"far\": \"haze\", \"oblique\": \"clear\", \"night\": \"night\", \"blur\": \"rain\"}\n", "records = []\n", "for cls in CLASSES:\n", " for i in range(10):\n", " cond = conds[i % len(conds)]\n", " records.append({\"image_id\": f\"SIL{len(records):05d}\", \"class\": cls, \"threat\": cls in THREAT,\n", " \"condition\": cond, \"view\": \"oblique\" if cond == \"oblique\" else \"top\", \"source\": \"synthetic\",\n", " \"reviewed_by\": \"INS01\", \"review_status\": \"approved\",\n", " \"sensor\": {\"type\": \"IR\" if cond == \"night\" else \"EO\", \"resolution\": [IMG, IMG]},\n", " \"tactical\": {\"weather\": weather[cond], \"range_level\": \"far\" if cond == \"far\" else \"mid\",\n", " \"position_error_m\": float(round(rng.uniform(5, 60), 1)), \"comm_delay_ms\": int(rng.integers(20, 300))},\n", " \"pixels\": draw_cond(cls, cond, rng).tolist()})\n", "\n", "ok = sum(1 for r in records if not validate(r))\n", "print(f\"์ •์ƒ ์ƒ˜ํ”Œ {len(records)}๊ฑด ์ค‘ ๊ฒ€์ฆ ํ†ต๊ณผ {ok}๊ฑด, ์˜ค๋ฅ˜ {len(records) - ok}๊ฑด (๋ชฉํ‘œ ์˜ค๋ฅ˜ 0๊ฑด: {'๋‹ฌ์„ฑ' if ok == len(records) else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# 3) ์˜ค๋ฅ˜ ์ฃผ์ž… ์‹œํ—˜\n", "bad = json.loads(json.dumps(records[0]))\n", "bad[\"image_id\"] = \"IMG-7\"\n", "bad[\"class\"] = \"ufo\"\n", "bad[\"threat\"] = \"yes\"\n", "del bad[\"tactical\"][\"comm_delay_ms\"]\n", "bad[\"pixels\"] = bad[\"pixels\"][:20]\n", "bad2 = json.loads(json.dumps(records[25]))\n", "bad2[\"threat\"] = False # ์žํญ๋“œ๋ก ํ˜•์ธ๋ฐ ๋น„์œ„ํ˜‘์œผ๋กœ ์ž˜๋ชป ํ‘œ๊ธฐ\n", "bad2[\"pixels\"][3][3] = 300\n", "print(\"\\n[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ]\")\n", "for e in validate(bad) + validate(bad2):\n", " print(\" -\", e)\n", "\n", "# 4) ๋ ˆ์ด๋ธ” ์ •์˜์„œ์™€ ํ•„๋“œ ๋ช…์„ธ\n", "print(\"\\n[๋ ˆ์ด๋ธ” ์ •์˜(์ถ”์ƒ ๋„ํ˜• ๊ธฐ์ค€)]\")\n", "desc = {\"civil_quad\": \"์‹ญ์ž ํŒ” + ์›ํ˜• ํšŒ์ „๋‚ ๊ฐœ 4๊ฐœ\", \"civil_fw\": \"์ง์„  ์ฃผ์ต + ๊ผฌ๋ฆฌ๋‚ ๊ฐœ\", \"attack_fw\": \"์‚ผ๊ฐํ˜•(๋ธํƒ€) ๋‚ ๊ฐœ, ๊ผฌ๋ฆฌ ์—†์Œ\",\n", " \"cruise\": \"๊ฐ€๋Š˜๊ณ  ๊ธด ๋™์ฒด + ์ž‘์€ ์ค‘๊ฐ„ ๋‚ ๊ฐœ\", \"ballistic\": \"๊ตต์€ ์›ํ†ต ๋™์ฒด + ๋พฐ์กฑํ•œ ๋จธ๋ฆฌ + ๊ผฌ๋ฆฌ ํ•€\", \"bird\": \"๊ฐˆ๋งค๊ธฐํ˜• ๊ตฝ์€ ๋‚ ๊ฐœ\"}\n", "print(pd.DataFrame({\"๋ ˆ์ด๋ธ”\": CLASSES, \"ํ•œ๊ธ€๋ช…\": [KOR[c] for c in CLASSES], \"์œ„ํ˜‘\": [\"์˜ˆ\" if c in THREAT else \"์•„๋‹ˆ์˜ค\" for c in CLASSES],\n", " \"์‹๋ณ„ ํ•ต์‹ฌ ํŠน์ง•\": [desc[c] for c in CLASSES]}).to_string(index=False))\n", "rows = [(g, k, (r[\"type\"].__name__ if isinstance(r[\"type\"], type) else \"number\"),\n", " \", \".join(f\"{x}={y}\" for x, y in r.items() if x != \"type\")[:60]) for g in (\"fields\", \"sensor\", \"tactical\") for k, r in SCHEMA[g].items()]\n", "print(\"\\n[ํ•„๋“œ ๋ช…์„ธ ์š”์•ฝ] ํ•„๋“œ\", len(rows) + 1, \"๊ฐœ(pixels ํฌํ•จ), ์ •์˜ ์™„์„ฑ๋„ 100%\")\n", "print(pd.DataFrame(rows, columns=[\"๊ทธ๋ฃน\", \"ํ•„๋“œ\", \"์ž๋ฃŒํ˜•\", \"๊ทœ์น™\"]).to_string(index=False))\n", "\n", "# 5) ์ƒ˜ํ”Œ ์‹ค๋ฃจ์—ฃ ํ™•์ธ\n", "fig, ax = plt.subplots(len(CLASSES), len(conds), figsize=(8, 9))\n", "for i, cls in enumerate(CLASSES):\n", " for j, cond in enumerate(conds):\n", " r = next(x for x in records if x[\"class\"] == cls and x[\"condition\"] == cond)\n", " ax[i, j].imshow(r[\"pixels\"], cmap=\"gray\", vmin=0, vmax=255); ax[i, j].axis(\"off\")\n", " ax[i, j].set_title(f\"{cls}/{cond}\", fontsize=7)\n", "plt.suptitle(\"Synthetic abstract silhouettes (training data only)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 01 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "์ •์ƒ ์ƒ˜ํ”Œ 60๊ฑด ์ค‘ ๊ฒ€์ฆ ํ†ต๊ณผ 60๊ฑด, ์˜ค๋ฅ˜ 0๊ฑด (๋ชฉํ‘œ ์˜ค๋ฅ˜ 0๊ฑด: ๋‹ฌ์„ฑ)\n", "\n", "[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ]\n", " - image_id: ํ˜•์‹ ๋ถˆ์ผ์น˜ 'IMG-7'\n", " - class: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๊ฐ’ 'ufo'\n", " - threat: ์ž๋ฃŒํ˜• ์˜ค๋ฅ˜(str)\n", " - tactical.comm_delay_ms: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\n", " - pixels: ํฌ๊ธฐ 32x32 ํ•„์š”(์ž…๋ ฅ (20, 32))\n", " - pixels: ๊ฐ’ ๋ฒ”์œ„(0~255) ๋˜๋Š” ๊ฒฐ์ธก ์˜ค๋ฅ˜\n", " - threat: ํด๋ž˜์Šค์™€ ์œ„ํ˜‘ ์—ฌ๋ถ€ ๋ถˆ์ผ์น˜\n", "\n", "[๋ ˆ์ด๋ธ” ์ •์˜(์ถ”์ƒ ๋„ํ˜• ๊ธฐ์ค€)]\n", " ๋ ˆ์ด๋ธ” ํ•œ๊ธ€๋ช… ์œ„ํ˜‘ ์‹๋ณ„ ํ•ต์‹ฌ ํŠน์ง•\n", "civil_quad ๋ฏผ๊ฐ„ ์ฟผ๋“œ์ฝฅํ„ฐํ˜• ์•„๋‹ˆ์˜ค ์‹ญ์ž ํŒ” + ์›ํ˜• ํšŒ์ „๋‚ ๊ฐœ 4๊ฐœ\n", " civil_fw ๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜• ์•„๋‹ˆ์˜ค ์ง์„  ์ฃผ์ต + ๊ผฌ๋ฆฌ๋‚ ๊ฐœ\n", " attack_fw ์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜• ์˜ˆ ์‚ผ๊ฐํ˜•(๋ธํƒ€) ๋‚ ๊ฐœ, ๊ผฌ๋ฆฌ ์—†์Œ\n", " cruise ์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜• ์˜ˆ ๊ฐ€๋Š˜๊ณ  ๊ธด ๋™์ฒด + ์ž‘์€ ์ค‘๊ฐ„ ๋‚ ๊ฐœ\n", " ballistic ํƒ„๋„๋ฏธ์‚ฌ์ผํ˜• ์˜ˆ ๊ตต์€ ์›ํ†ต ๋™์ฒด + ๋พฐ์กฑํ•œ ๋จธ๋ฆฌ + ๊ผฌ๋ฆฌ ํ•€\n", " bird ์กฐ๋ฅ˜ ์•„๋‹ˆ์˜ค ๊ฐˆ๋งค๊ธฐํ˜• ๊ตฝ์€ ๋‚ ๊ฐœ\n", "\n", "[ํ•„๋“œ ๋ช…์„ธ ์š”์•ฝ] ํ•„๋“œ 15 ๊ฐœ(pixels ํฌํ•จ), ์ •์˜ ์™„์„ฑ๋„ 100%\n", " ๊ทธ๋ฃน ํ•„๋“œ ์ž๋ฃŒํ˜• ๊ทœ์น™\n", " fields image_id str pattern=^SIL\\d{5}$\n", " fields class str enum=['civil_quad', 'civil_fw', 'attack_fw', 'cruise', 'ball\n", " fields threat bool \n", " fields condition str enum=['basic', 'offcenter', 'far', 'blur', 'lowcon', 'clutte\n", " fields view str enum=['top', 'oblique']\n", " fields source str enum=['synthetic']\n", " fields reviewed_by str pattern=^INS\\d{2}$\n", " fields review_status str enum=['draft', 'approved']\n", " sensor type str enum=['EO', 'IR']\n", " sensor resolution list length=2\n", "tactical weather str enum=['clear', 'haze', 'cloud', 'night', 'rain']\n", "tactical range_level str enum=['near', 'mid', 'far']\n", "tactical position_error_m number min=0, max=500\n", "tactical comm_delay_ms number min=0, max=10000\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 02. ์ด๋ฏธ์ง€ ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”, ์œค๊ณฝ์„ (Edge) ๊ฒ€์ถœ ๋ฐ ํฌ๊ธฐ ์ •๊ทœํ™” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”, ์œค๊ณฝ์„ (Edge) ๊ฒ€์ถœ ๋ฐ ํฌ๊ธฐ ์ •๊ทœํ™” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **ํ‘๋ฐฑยท์›๊ฑฐ๋ฆฌยท์•ผ๊ฐ„ ๋“ฑ ์กฐ๊ฑด์ด ๋‹ค๋ฅธ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€๋ฅผ ๊ฐ™์€ ๊ธฐ์ค€์œผ๋กœ ๋น„๊ตํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”, ์œค๊ณฝ์„  ๊ฒ€์ถœ, ํฌ๊ธฐ ์ •๊ทœํ™” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ฒฐ์ธก ํ”ฝ์…€ ๋ณด์ •, ์•ผ๊ฐ„ ์—ด์ƒ ๊ทน์„ฑ ์ž๋™ ๋ฐ˜์ „, ์žก์Œ ์ถ”์ •๊ฐ’์— ๋”ฐ๋ฅธ ์ค‘๊ฐ„๊ฐ’ ํ•„ํ„ฐ ์ ์šฉ(์กฐ๊ฑด๋ณ„ ์ž„๊ณ„๊ฐ’), ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”, Sobel ์œค๊ณฝ์„  ๊ฒ€์ถœ, ์ฃผ์ถ• ๋ฐฉํ–ฅยทํฌ๊ธฐ ์ •๊ทœํ™”(24x24)๋ฅผ ๋ฐฐ์น˜ ๋‹จ์œ„๋กœ ๊ตฌํ˜„ํ•˜๊ณ  ๋™์ž‘์„ ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/preprocess/trn03_preprocessor.py ๋ฐ ์ „์ฒ˜๋ฆฌ ์ „ํ›„ ๋ฐ์ดํ„ฐ์…‹` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์ „์ˆ  ํ™˜๊ฒฝ ํŠน์œ ์˜ ๋…ธ์ด์ฆˆ(์Šคํ‘ธํ•‘, ๋ฐ˜์‚ฌํŒŒ, ์„ผ์„œ ๋‹จ์ ˆ ๋“ฑ) ์ œ๊ฑฐ ํƒ€๋‹น์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๋ฐ์ดํ„ฐ ์ •์ œ ์ฒ˜๋ฆฌ ์†๋„ 1,000๊ฑด/์ดˆ ์ด์ƒ, ์ „์ฒ˜๋ฆฌ ์˜ค๋ฅ˜์œจ 0%\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์ •์ œ ํŒŒ์ดํ”„๋ผ์ธ ์ž๋™ํ™” ๋ฐ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค 5์ข… ํ†ต๊ณผ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: pytest ๊ธฐ๋ฐ˜ ์ „์ฒ˜๋ฆฌ ์ •ํ•ฉ์„ฑ ํ…Œ์ŠคํŠธ ๋ฐ ๊ฒฐ์ธก์น˜ ๋ณด์ •์œจ ํ™•์ธ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€์˜ ๊ฒฐ์ธก ๋ณด์ •, ์•ผ๊ฐ„ ๊ทน์„ฑ ๋ณด์ •, ์žก์Œ ์ œ๊ฑฐ, ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”, ์œค๊ณฝ์„ (Edge) ๊ฒ€์ถœ, ๋ฐฉํ–ฅยทํฌ๊ธฐ ์ •๊ทœํ™”๋ฅผ ์ผ๊ด„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•˜๊ณ , ์ฒ˜๋ฆฌ ์†๋„ยท๋‹จ์œ„ ํ…Œ์ŠคํŠธ 5์ข…ยท์ „์ฒ˜๋ฆฌ ์ „ํ›„ ์‹๋ณ„ ์ •ํ™•๋„๋ฅผ ํ™•์ธํ•œ๋‹ค.\"\"\"\n", "import time\n", "import warnings\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from matplotlib.path import Path as MplPath\n", "from scipy import ndimage\n", "from scipy.spatial import ConvexHull\n", "from sklearn.neighbors import NearestCentroid\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•ฉ์„ฑ ์‹ค๋ฃจ์—ฃ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ธฐ์ฒด ์‚ฌ์ง„ยท๋„๋ฉดยท์ œ์› ์‚ฌ์šฉ ์•ˆ ํ•จ)\n", "# ์‚ฌ๊ฐํ˜•ยท์‚ผ๊ฐํ˜•ยท์› ๊ฐ™์€ ๊ธฐ๋ณธ ๋„ํ˜•์„ ์กฐํ•ฉํ•œ ์ถ”์ƒ ์‹ค๋ฃจ์—ฃ์„ 32x32 ํ‘๋ฐฑ ์ด๋ฏธ์ง€๋กœ ๊ทธ๋ฆฐ๋‹ค.\n", "# ํšŒ์ „(๋‹ค๊ฐ๋„), ์›๊ทผ ์ถ•์†Œ(์ธก๋ฉด ์‹œ์ ), ํฌ๊ธฐ(์›๊ฑฐ๋ฆฌ), ํ๋ฆผยท์žก์Œยท์ €๋Œ€๋น„ยท๊ตฌ๋ฆ„ยท๊ฐ€๋ฆผยท์•ผ๊ฐ„ ๋ฐ˜์ „์„ ์กฐ์ ˆํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "IMG = 32\n", "CLASSES = [\"civil_quad\", \"civil_fw\", \"attack_fw\", \"cruise\", \"ballistic\", \"bird\"]\n", "KOR = {\"civil_quad\": \"๋ฏผ๊ฐ„ ์ฟผ๋“œ์ฝฅํ„ฐํ˜•\", \"civil_fw\": \"๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•\", \"attack_fw\": \"์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜•\",\n", " \"cruise\": \"์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜•\", \"ballistic\": \"ํƒ„๋„๋ฏธ์‚ฌ์ผํ˜•\", \"bird\": \"์กฐ๋ฅ˜\"}\n", "THREAT = {\"attack_fw\", \"cruise\", \"ballistic\"}\n", "\n", "\n", "def _rect(x0, x1, y0, y1):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] ์ด๋ฏธ์ง€ ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”, ์œค๊ณฝ์„ (Edge) ๊ฒ€์ถœ ๋ฐ ํฌ๊ธฐ ์ •๊ทœํ™” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def _bar(p, q, w):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] ์ด๋ฏธ์ง€ ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”, ์œค๊ณฝ์„ (Edge) ๊ฒ€์ถœ ๋ฐ ํฌ๊ธฐ ์ •๊ทœํ™” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "SHAPES = {\n", " \"civil_quad\": {\"poly\": [_bar((-.55, -.55), (.55, .55), .1), _bar((-.55, .55), (.55, -.55), .1), _rect(-.15, .15, -.15, .15)],\n", " \"circ\": [(-.55, -.55, .24), (.55, .55, .24), (-.55, .55, .24), (.55, -.55, .24)]},\n", " \"civil_fw\": {\"poly\": [_rect(-.07, .07, -.8, .72), _rect(-.9, .9, .05, .25), _rect(-.33, .33, -.8, -.66)], \"circ\": []},\n", " \"attack_fw\": {\"poly\": [[(0, .85), (.85, -.5), (.12, -.42), (0, -.6), (-.12, -.42), (-.85, -.5)]], \"circ\": []},\n", " \"cruise\": {\"poly\": [_rect(-.07, .07, -.9, .7), [(-.07, .7), (0, .92), (.07, .7)], _rect(-.42, .42, -.05, .07),\n", " [(-.07, -.9), (-.24, -.95), (-.07, -.74)], [(.07, -.9), (.24, -.95), (.07, -.74)]], \"circ\": []},\n", " \"ballistic\": {\"poly\": [_rect(-.13, .13, -.85, .6), [(-.13, .6), (0, .95), (.13, .6)],\n", " [(-.13, -.85), (-.34, -.95), (-.13, -.58)], [(.13, -.85), (.34, -.95), (.13, -.58)]], \"circ\": []},\n", " \"bird\": {\"poly\": [[(-.9, .12), (-.5, .28), (-.12, .06), (0, .22), (.12, .06), (.5, .28), (.9, .12), (.5, .12),\n", " (.12, -.1), (0, -.38), (-.12, -.1), (-.5, .12)]], \"circ\": []},\n", "}\n", "_S = 2\n", "_g = (np.arange(IMG * _S) + 0.5) / _S\n", "_GX, _GY = np.meshgrid(_g, _g)\n", "_PTS = np.c_[_GX.ravel(), _GY.ravel()]\n", "_PATHS = {c: [MplPath(np.array(p)) for p in s[\"poly\"]] for c, s in SHAPES.items()}\n", "\n", "\n", "def draw(cls, rng, angle=None, scale=None, fore=None, blur=0.3, noise=4.0, contrast=1.0,\n", " clutter=0.0, occlude=0.0, invert=False, shift=1.5):\n", " \"\"\"ํด๋ž˜์Šค 1๊ฐœ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€(uint8 32x32)๋ฅผ ๊ทธ๋ฆฐ๋‹ค. ๋ฐฐ๊ฒฝ์€ ๋ฐ์€ ํ•˜๋Š˜, ๋น„ํ–‰์ฒด๋Š” ์–ด๋‘์šด ๊ทธ๋ฆผ์ž.\"\"\"\n", " angle = rng.uniform(0, 360) if angle is None else angle\n", " scale = rng.uniform(.7, .95) if scale is None else scale\n", " fore = rng.uniform(.85, 1.0) if fore is None else fore\n", " th = np.radians(angle)\n", " c0 = IMG / 2 + rng.uniform(-shift, shift, 2)\n", " d = (_PTS - c0) / (scale * IMG / 2) # ์ด๋ฏธ์ง€ ์ขŒํ‘œ -> ๋„ํ˜• ์ขŒํ‘œ(์—ญ๋ณ€ํ™˜)\n", " u = d[:, 0] * np.cos(th) + d[:, 1] * np.sin(th)\n", " v = (-d[:, 0] * np.sin(th) + d[:, 1] * np.cos(th)) / fore # ์›๊ทผ ์ถ•์†Œ(๊ธฐ์ˆ˜ ๋ฐฉํ–ฅ์œผ๋กœ ์งง์•„ ๋ณด์ž„)\n", " uv = np.c_[u, -v]\n", " m = np.zeros(len(uv), bool)\n", " near = np.flatnonzero(np.abs(uv).max(axis=1) <= 1.0) # ๋„ํ˜•์ด ์žˆ์„ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ๋งŒ ๊ฒ€์‚ฌ(์†๋„)\n", " for p in _PATHS[cls]:\n", " m[near] |= p.contains_points(uv[near])\n", " for cx, cy, r in SHAPES[cls][\"circ\"]:\n", " m |= (uv[:, 0] - cx) ** 2 + (uv[:, 1] - cy) ** 2 <= r * r\n", " cov = m.reshape(IMG, _S, IMG, _S).mean(axis=(1, 3))\n", " if occlude > 0: # ๋ถ€๋ถ„ ๊ฐ€๋ฆผ(๊ตฌ๋ฆ„ ์กฐ๊ฐ)\n", " h = int(IMG * np.sqrt(occlude)); y0, x0 = rng.integers(8, IMG - 8 - h // 2, 2)\n", " cov[y0:y0 + h, x0:x0 + h] = 0\n", " bg = rng.uniform(180, 220) + np.linspace(-10, 10, IMG)[:, None]\n", " fg = rng.uniform(30, 60)\n", " img = bg * (1 - cov) + fg * cov\n", " if clutter > 0: # ๊ตฌ๋ฆ„ยท๋ฐฐ๊ฒฝ ์–ผ๋ฃฉ\n", " img -= clutter * 90 * np.clip(ndimage.gaussian_filter(rng.normal(0, 1, (IMG, IMG)), 3) * 4, 0, None)\n", " if blur > 0:\n", " img = ndimage.gaussian_filter(img, blur)\n", " mean = img.mean()\n", " img = mean + (img - mean) * contrast\n", " img = img + rng.normal(0, noise, img.shape)\n", " if invert: # ์•ผ๊ฐ„ ์—ด์ƒ(IR) ๊ทน์„ฑ ๋ฐ˜์ „\n", " img = 255 - img\n", " return np.clip(img, 0, 255).astype(np.uint8)\n", "\n", "\n", "# ์ดฌ์˜ ์กฐ๊ฑด ํ”„๋ฆฌ์…‹: ๊ธฐ๋ณธ + ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹œ๋‚˜๋ฆฌ์˜ค\n", "CONDITIONS = {\n", " \"basic\": {},\n", " \"offcenter\": {\"shift\": 5.0, \"fore\": (.6, .85)},\n", " \"far\": {\"scale\": (.32, .45), \"blur\": .6},\n", " \"blur\": {\"blur\": (1.2, 1.8)},\n", " \"lowcon\": {\"contrast\": (.2, .35)},\n", " \"clutter\": {\"clutter\": .8},\n", " \"occlude\": {\"occlude\": .08},\n", " \"oblique\": {\"fore\": (.35, .6)},\n", " \"night\": {\"invert\": True, \"noise\": 10.0},\n", " \"noise\": {\"noise\": 28.0},\n", " \"combo\": {\"scale\": (.4, .55), \"blur\": (.8, 1.1), \"noise\": 12.0, \"contrast\": (.6, .8)},\n", "}\n", "\n", "\n", "def draw_cond(cls, cond, rng):\n", " kw = {}\n", " for k, v in CONDITIONS[cond].items():\n", " kw[k] = rng.uniform(*v) if isinstance(v, tuple) else v\n", " return draw(cls, rng, **kw)\n", "\n", "\n", "def make_dataset(n_per_class, seed, conds=(\"basic\",)):\n", " \"\"\"(์ด๋ฏธ์ง€ ๋ฐฐ์—ด N x 32 x 32, ์ •๋‹ต ๋ผ๋ฒจ, ์กฐ๊ฑด) ๋ฐ˜ํ™˜. ์กฐ๊ฑด์€ conds์—์„œ ๋ฒˆ๊ฐˆ์•„ ๊ณ ๋ฅธ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " X, y, c = [], [], []\n", " for cls in CLASSES:\n", " for i in range(n_per_class):\n", " cond = conds[i % len(conds)]\n", " X.append(draw_cond(cls, cond, rng)); y.append(cls); c.append(cond)\n", " return np.array(X), np.array(y), np.array(c)\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---- ์ „์ฒ˜๋ฆฌ(๋ฐฐ์น˜ ๋ฒกํ„ฐํ™”)ยทํ˜•ํƒœ ํŠน์ง• ๊ณตํ†ต ํ•จ์ˆ˜ ----\n", "OUT = 24 # ํฌ๊ธฐ ์ •๊ทœํ™” ๊ฒฐ๊ณผ(24x24)\n", "NOISE_T = 12.0 # ์žก์Œ ์ถ”์ •๊ฐ’์ด ์ด๋ณด๋‹ค ํฌ๋ฉด 3x3 ์ค‘๊ฐ„๊ฐ’ ํ•„ํ„ฐ ์ ์šฉ(์•…์ฒœํ›„ยท์„ผ์„œ ์žก์Œ)\n", "FEATS = [\"elong\", \"fill\", \"solidity\", \"n_parts\", \"end_gap\", \"mid_bulge\", \"w_max\", \"compact\"]\n", "\n", "\n", "def preprocess(images, noise_t=NOISE_T):\n", " \"\"\"N์žฅ ์ผ๊ด„ ์ฒ˜๋ฆฌ: ๊ฒฐ์ธก ๋ณด์ • -> ๊ทน์„ฑ(์•ผ๊ฐ„ ๋ฐ˜์ „) ๋ณด์ • -> ์กฐ๊ฑด๋ณ„ ์žก์Œ ์ œ๊ฑฐ -> ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™” -> ๋ถ„ํ•  -> ์œค๊ณฝ์„  -> ๋ฐฉํ–ฅยทํฌ๊ธฐ ์ •๊ทœํ™”.\"\"\"\n", " f = np.array(images, dtype=float).reshape(-1, IMG, IMG)\n", " n = len(f)\n", " bad = ~np.isfinite(f)\n", " missing = bad.sum(axis=(1, 2))\n", " if bad.any(): # ๊ฒฐ์ธก ํ”ฝ์…€: ์˜์ƒ ์ค‘์•™๊ฐ’์œผ๋กœ ์ฑ„์šด ๋’ค ์ฃผ๋ณ€ 3x3 ์ค‘๊ฐ„๊ฐ’์œผ๋กœ ๋ณด์ •\n", " med = np.array([np.median(x[np.isfinite(x)]) if np.isfinite(x).any() else 128.0 for x in f])\n", " f = np.where(bad, med[:, None, None], f)\n", " f = np.where(bad, ndimage.median_filter(f, size=(1, 3, 3)), f)\n", " border = np.concatenate([f[:, 0], f[:, -1], f[:, :, 0], f[:, :, -1]], axis=1)\n", " inverted = np.median(border, axis=1) < 128 # ๋ฐฐ๊ฒฝ์ด ์–ด๋‘์šฐ๋ฉด ์•ผ๊ฐ„ ์—ด์ƒ์œผ๋กœ ๋ณด๊ณ  ๋ฐ˜์ „\n", " f[inverted] = 255 - f[inverted]\n", " med3 = ndimage.median_filter(f, size=(1, 3, 3))\n", " noise = 1.4826 * np.median(np.abs(f - med3).reshape(n, -1), axis=1)\n", " noisy = noise > noise_t\n", " f[noisy] = med3[noisy]\n", " lo, hi = np.percentile(f.reshape(n, -1), [1, 99], axis=1)\n", " e = np.clip((f - lo[:, None, None]) / np.maximum(hi - lo, 1e-6)[:, None, None], 0, 1) # ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”\n", " mask = e < _otsu(e)[:, None, None]\n", " mask &= ndimage.uniform_filter(mask.astype(float), size=(1, 3, 3)) * 9 >= 2.5 # ๊ณ ๋ฆฝ ์  ์žก์Œ ์ œ๊ฑฐ\n", " edge = np.hypot(ndimage.sobel(e, axis=1), ndimage.sobel(e, axis=2)) # ์œค๊ณฝ์„ (Sobel)\n", " ok = (mask.sum(axis=(1, 2)) >= 4) & (hi - lo > 8)\n", " return {\"enh\": e, \"mask\": mask, \"edge\": edge / np.maximum(edge.max(axis=(1, 2)), 1e-6)[:, None, None],\n", " \"norm\": _normalize(1 - e, mask, ok), \"inverted\": inverted, \"noise\": noise, \"denoised\": noisy,\n", " \"missing\": missing, \"contrast\": hi - lo, \"ok\": ok}\n", "\n", "\n", "def _otsu(e, bins=64):\n", " n = len(e)\n", " idx = np.minimum((e * bins).astype(int), bins - 1) + bins * np.arange(n)[:, None, None]\n", " hist = np.bincount(idx.ravel(), minlength=bins * n).reshape(n, bins).astype(float)\n", " centers = (np.arange(bins) + 0.5) / bins\n", " w = np.cumsum(hist, axis=1); m = np.cumsum(hist * centers, axis=1)\n", " tot, mt = w[:, -1:], m[:, -1:]\n", " between = (mt * w / tot - m) ** 2 / np.maximum(w * (tot - w), 1)\n", " return (np.argmax(between, axis=1) + 1) / bins\n", "\n", "\n", "def _normalize(obj, mask, ok):\n", " \"\"\"์ฃผ์ถ• ๋ฐฉํ–ฅ์„ ์„ธ๋กœ๋กœ ๋Œ๋ฆฌ๊ณ  ๋น„ํ–‰์ฒด ์˜์—ญ๋งŒ 24x24๋กœ ๋‹ค์‹œ ์ƒ˜ํ”Œ๋งํ•œ๋‹ค(ํšŒ์ „ยทํฌ๊ธฐยท์œ„์น˜ ๋ถˆ๋ณ€). ํ•œ ๋ฒˆ์˜ ๋ณด๊ฐ„์œผ๋กœ ์ผ๊ด„ ์ฒ˜๋ฆฌ.\"\"\"\n", " n = len(obj)\n", " w = mask.astype(float)\n", " m00 = np.maximum(w.sum(axis=(1, 2)), 1)\n", " yy, xx = np.mgrid[0:IMG, 0:IMG].astype(float)\n", " cy, cx = (w * yy).sum(axis=(1, 2)) / m00, (w * xx).sum(axis=(1, 2)) / m00\n", " dy, dx = yy - cy[:, None, None], xx - cx[:, None, None]\n", " mu20, mu02, mu11 = (w * dy * dy).sum(axis=(1, 2)), (w * dx * dx).sum(axis=(1, 2)), (w * dy * dx).sum(axis=(1, 2))\n", " th = 0.5 * np.arctan2(2 * mu11, mu20 - mu02)\n", " a = np.stack([np.cos(th), np.sin(th)], 1) # ์ฃผ์ถ•(y, x)\n", " b = np.stack([-np.sin(th), np.cos(th)], 1) # ๋ถ€์ถ•\n", " pa = np.where(mask, dy * a[:, 0, None, None] + dx * a[:, 1, None, None], 0)\n", " pb = np.where(mask, dy * b[:, 0, None, None] + dx * b[:, 1, None, None], 0)\n", " half = np.maximum(np.abs(pa).max(axis=(1, 2)), np.abs(pb).max(axis=(1, 2))) + 1.0\n", " g = np.linspace(-1, 1, OUT)\n", " U, V = np.meshgrid(g, g, indexing=\"ij\")\n", " Y = cy[:, None, None] + half[:, None, None] * (U * a[:, 0, None, None] + V * b[:, 0, None, None])\n", " X = cx[:, None, None] + half[:, None, None] * (U * a[:, 1, None, None] + V * b[:, 1, None, None])\n", " N = np.broadcast_to(np.arange(n)[:, None, None], Y.shape)\n", " out = ndimage.map_coordinates(obj * mask, [N, Y, X], order=1, cval=0.0)\n", " skew = (out * (U ** 3)).sum(axis=(1, 2))\n", " out[skew < 0] = out[skew < 0, ::-1] # ๋ฌด๊ฒŒ๊ฐ€ ์ ๋ฆฐ ์ชฝ์„ ์œ„๋กœ(180๋„ ๋ชจํ˜ธ์„ฑ ์ œ๊ฑฐ)\n", " out[~ok] = 0\n", " return np.clip(out, 0, 1)\n", "\n", "\n", "def shape_features(mask):\n", " \"\"\"๋ถ„ํ•  ๋งˆ์Šคํฌ 1์žฅ์—์„œ ํšŒ์ „์— ๋ฌด๊ด€ํ•œ ํ˜•ํƒœ ํŠน์ง• 8์ข…์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " if mask.sum() < 4:\n", " return dict.fromkeys(FEATS, 0.0)\n", " yx = np.argwhere(mask).astype(float)\n", " c = yx - yx.mean(axis=0)\n", " val, vec = np.linalg.eigh(np.cov(c.T) + 1e-6 * np.eye(2))\n", " t, s = c @ vec[:, 1], c @ vec[:, 0]\n", " length, width = np.ptp(t) + 1, np.ptp(s) + 1\n", " corners = np.vstack([yx + d for d in ([.5, .5], [.5, -.5], [-.5, .5], [-.5, -.5])])\n", " try:\n", " hull = ConvexHull(corners).volume\n", " except Exception:\n", " hull = float(mask.sum())\n", " bins = np.digitize(t, np.linspace(t.min(), t.max() + 1e-6, 4)[1:-1])\n", " w = np.array([np.ptp(s[bins == k]) + 1 if (bins == k).any() else 0 for k in range(3)]) / length\n", " boundary = mask & ~ndimage.binary_erosion(mask)\n", " return {\"elong\": float(np.sqrt(val[1] / max(val[0], 1e-6))), \"fill\": float(mask.sum() / (length * width)),\n", " \"solidity\": float(mask.sum() / max(hull, 1)), \"n_parts\": float(ndimage.label(mask)[1]),\n", " \"end_gap\": float(abs(w[0] - w[2])), \"mid_bulge\": float(w[1] - max(w[0], w[2])),\n", " \"w_max\": float(w.max()), \"compact\": float(boundary.sum() / np.sqrt(mask.sum()))}\n", "\n", "\n", "def feature_matrix(images):\n", " \"\"\"์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ, ํ˜•ํƒœ ํŠน์ง• ํ–‰๋ ฌ(N x 8), ์ •๊ทœํ™” ์ด๋ฏธ์ง€ ํ–‰๋ ฌ(N x 576)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " P = preprocess(images)\n", " F = np.array([[d[k] for k in FEATS] for d in map(shape_features, P[\"mask\"])])\n", " return P, F, P[\"norm\"].reshape(len(F), -1)\n", "\n", "warnings.filterwarnings(\"ignore\")\n", "\n", "# 1) ์ฒ˜๋ฆฌ ์†๋„: 2,400์žฅ ์ผ๊ด„ ์ฒ˜๋ฆฌ\n", "X, y, c = make_dataset(400, 1, tuple(CONDITIONS))\n", "preprocess(X[:50]) # ์ค€๋น„ ์‹คํ–‰\n", "t0 = time.perf_counter()\n", "P = preprocess(X)\n", "dt = time.perf_counter() - t0\n", "err = int((~np.isfinite(P[\"norm\"])).sum())\n", "print(f\"์ „์ฒ˜๋ฆฌ {len(X):,}์žฅ, {len(X) / dt:,.0f}์žฅ/์ดˆ (๋ชฉํ‘œ 1,000์žฅ/์ดˆ ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(X) / dt >= 1000 else '๋ฏธ๋‹ฌ'}), ์ถœ๋ ฅ ์˜ค๋ฅ˜ {err}๊ฑด\")\n", "\n", "# 2) ๋‹จ์œ„ ํ…Œ์ŠคํŠธ 5์ข…\n", "tests = {}\n", "tests[\"T1 ์ถœ๋ ฅ ํฌ๊ธฐ 24x24 ๊ณ ์ •\"] = P[\"norm\"].shape[1:] == (OUT, OUT)\n", "tests[\"T2 ๊ฐ’ ๋ฒ”์œ„ 0~1\"] = bool(P[\"norm\"].min() >= 0 and P[\"norm\"].max() <= 1)\n", "rng = np.random.default_rng(2)\n", "day = draw(\"attack_fw\", rng, angle=30, scale=.8, fore=1.0, noise=0)\n", "rng = np.random.default_rng(2)\n", "night = draw(\"attack_fw\", rng, angle=30, scale=.8, fore=1.0, noise=0, invert=True)\n", "pd_, pn = preprocess([day, night])[\"norm\"]\n", "tests[\"T3 ์•ผ๊ฐ„ ๋ฐ˜์ „ ์˜์ƒ ๊ทน์„ฑ ์ž๋™ ๋ณด์ •\"] = bool(np.abs(pd_ - pn).mean() < 0.05)\n", "holes = day.astype(float); holes[rng.random(holes.shape) < 0.1] = np.nan\n", "ph = preprocess([holes])\n", "tests[\"T4 ๊ฒฐ์ธก ํ”ฝ์…€ ๋ณด์ •์œจ 100%\"] = bool(ph[\"missing\"][0] > 0 and np.isfinite(ph[\"norm\"]).all())\n", "blank = np.full((IMG, IMG), 200.0)\n", "pb = preprocess([blank])\n", "tests[\"T5 ๋นˆ ์˜์ƒ์€ ์˜ˆ์™ธ ์—†์ด 'ํŒ๋ณ„ ๋ถˆ๊ฐ€' ํ‘œ์‹œ\"] = bool(not pb[\"ok\"][0])\n", "for k, v in tests.items():\n", " print(f\"[{'PASS' if v else 'FAIL'}] {k}\")\n", "print(f\"๊ฒฐ์ธก ํ”ฝ์…€ {ph['missing'][0]}๊ฐœ ๋ณด์ •, ๋‹จ์œ„ ํ…Œ์ŠคํŠธ {sum(tests.values())}/5 ํ†ต๊ณผ\")\n", "\n", "# 3) ์ „์ฒ˜๋ฆฌ ์ „ํ›„ ์‹๋ณ„ ์ •ํ™•๋„(์ตœ๊ทผ์ ‘ ์ค‘์‹ฌ ๋ถ„๋ฅ˜๊ธฐ, ๊ธฐ๋ณธ ์กฐ๊ฑด์œผ๋กœ ํ•™์Šต)\n", "Xtr, ytr, _ = make_dataset(60, 3)\n", "raw = lambda imgs: (np.asarray(imgs, float) / 255).reshape(len(imgs), -1)\n", "nc_raw = NearestCentroid().fit(raw(Xtr), ytr)\n", "nc_pre = NearestCentroid().fit(preprocess(Xtr)[\"norm\"].reshape(len(Xtr), -1), ytr)\n", "rows = []\n", "for cond in [\"basic\", \"offcenter\", \"night\", \"lowcon\", \"noise\", \"far\"]:\n", " Xt, yt, _ = make_dataset(30, 4, (cond,))\n", " pt = preprocess(Xt)\n", " rows.append({\"์กฐ๊ฑด\": cond, \"์ „์ฒ˜๋ฆฌ ์ „\": (nc_raw.predict(raw(Xt)) == yt).mean(),\n", " \"์ „์ฒ˜๋ฆฌ ํ›„\": (nc_pre.predict(pt[\"norm\"].reshape(len(Xt), -1)) == yt).mean(),\n", " \"์žก์Œํ•„ํ„ฐ ์ ์šฉ๋ฅ \": pt[\"denoised\"].mean(), \"๊ทน์„ฑ๋ฐ˜์ „ ๋น„์œจ\": pt[\"inverted\"].mean()})\n", "print(\"\\n[์ „์ฒ˜๋ฆฌ ์ „ํ›„ ์‹๋ณ„ ์ •ํ™•๋„]\")\n", "print(pd.DataFrame(rows).round(3).to_string(index=False))\n", "\n", "# 4) ์กฐ๊ฑด๋ณ„ ์žก์Œ ์ž„๊ณ„๊ฐ’ ๋™์  ์กฐ์ • ํšจ๊ณผ: ์ž„๊ณ„๊ฐ’์„ ๋‚ฎ์ถฐ ๋ชจ๋“  ์˜์ƒ์— ํ•„ํ„ฐ๋ฅผ ์“ฐ๋ฉด ์›๊ฑฐ๋ฆฌ ์†Œํ˜• ํ‘œ์ ์ด ์†์ƒ๋œ๋‹ค\n", "Xf, yf, _ = make_dataset(30, 4, (\"far\",))\n", "for t in (NOISE_T, 0.0):\n", " acc = (nc_pre.predict(preprocess(Xf, noise_t=t)[\"norm\"].reshape(len(Xf), -1)) == yf).mean()\n", " print(f\"์›๊ฑฐ๋ฆฌ ์กฐ๊ฑด, ์žก์Œ ์ž„๊ณ„๊ฐ’ {t:>4}: ์ •ํ™•๋„ {acc:.3f}\")\n", "\n", "# 5) ์ „์ฒ˜๋ฆฌ ๋‹จ๊ณ„ ์‹œ๊ฐํ™”\n", "idx = [np.flatnonzero(c == k)[0] for k in [\"basic\", \"night\", \"noise\", \"lowcon\", \"far\"]]\n", "fig, ax = plt.subplots(4, len(idx), figsize=(10, 8))\n", "for j, i in enumerate(idx):\n", " for r, (img, name) in enumerate([(X[i], \"raw\"), (P[\"enh\"][i], \"contrast\"), (P[\"edge\"][i], \"edge\"), (P[\"norm\"][i], \"normalized 24x24\")]):\n", " ax[r, j].imshow(img, cmap=\"gray\"); ax[r, j].axis(\"off\"); ax[r, j].set_title(f\"{c[i]} {name}\", fontsize=8)\n", "plt.suptitle(\"Silhouette preprocessing steps (synthetic)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 02 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "์ „์ฒ˜๋ฆฌ 2,400์žฅ, 2,056์žฅ/์ดˆ (๋ชฉํ‘œ 1,000์žฅ/์ดˆ ์ด์ƒ: ๋‹ฌ์„ฑ), ์ถœ๋ ฅ ์˜ค๋ฅ˜ 0๊ฑด\n", "[PASS] T1 ์ถœ๋ ฅ ํฌ๊ธฐ 24x24 ๊ณ ์ •\n", "[PASS] T2 ๊ฐ’ ๋ฒ”์œ„ 0~1\n", "[PASS] T3 ์•ผ๊ฐ„ ๋ฐ˜์ „ ์˜์ƒ ๊ทน์„ฑ ์ž๋™ ๋ณด์ •\n", "[PASS] T4 ๊ฒฐ์ธก ํ”ฝ์…€ ๋ณด์ •์œจ 100%\n", "[PASS] T5 ๋นˆ ์˜์ƒ์€ ์˜ˆ์™ธ ์—†์ด 'ํŒ๋ณ„ ๋ถˆ๊ฐ€' ํ‘œ์‹œ\n", "๊ฒฐ์ธก ํ”ฝ์…€ 109๊ฐœ ๋ณด์ •, ๋‹จ์œ„ ํ…Œ์ŠคํŠธ 5/5 ํ†ต๊ณผ\n", "\n", "[์ „์ฒ˜๋ฆฌ ์ „ํ›„ ์‹๋ณ„ ์ •ํ™•๋„]\n", " ์กฐ๊ฑด ์ „์ฒ˜๋ฆฌ ์ „ ์ „์ฒ˜๋ฆฌ ํ›„ ์žก์Œํ•„ํ„ฐ ์ ์šฉ๋ฅ  ๊ทน์„ฑ๋ฐ˜์ „ ๋น„์œจ\n", " basic 0.539 0.978 0.0 0.0\n", "offcenter 0.344 0.939 0.0 0.0\n", " night 0.167 0.983 0.0 1.0\n", " lowcon 0.322 0.978 0.0 0.0\n", " noise 0.544 0.911 1.0 0.0\n", " far 0.194 0.722 0.0 0.0\n", "์›๊ฑฐ๋ฆฌ ์กฐ๊ฑด, ์žก์Œ ์ž„๊ณ„๊ฐ’ 12.0: ์ •ํ™•๋„ 0.722\n", "์›๊ฑฐ๋ฆฌ ์กฐ๊ฑด, ์žก์Œ ์ž„๊ณ„๊ฐ’ 0.0: ์ •ํ™•๋„ 0.417\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 03. ๊ธฐ๋ณธ ํ˜•ํƒœ ํŠน์ง• ๊ธฐ๋ฐ˜ 1์ฐจ ๋น„ํ–‰์ฒด ์‹ค๋ฃจ์—ฃ ๋ถ„๋ฅ˜ Baseline ๋ชจ๋ธ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๊ธฐ๋ณธ ํ˜•ํƒœ ํŠน์ง• ๊ธฐ๋ฐ˜ 1์ฐจ ๋น„ํ–‰์ฒด ์‹ค๋ฃจ์—ฃ ๋ถ„๋ฅ˜ Baseline ๋ชจ๋ธ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๊ธธ์ญ‰ํ•จยท์ฑ„์›€๋ฅ ยท๋ณผ๋ก๋„ยทํญ ๋ถ„ํฌ ๊ฐ™์€ ๊ธฐ๋ณธ ํ˜•ํƒœ ํŠน์ง•์œผ๋กœ ๋น„ํ–‰์ฒด ์‹ค๋ฃจ์—ฃ์„ 1์ฐจ ๋ถ„๋ฅ˜ํ•˜๋Š” Baseline ๋ชจ๋ธ์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋ถ„ํ•  ๋งˆ์Šคํฌ์—์„œ ํšŒ์ „์— ๋ฌด๊ด€ํ•œ ํ˜•ํƒœ ํŠน์ง• 8์ข…์„ ๊ณ„์‚ฐํ•˜๊ณ  ๊ฒฐ์ •๋‚˜๋ฌด๋กœ ๋ถ„๋ฅ˜ ๊ทœ์น™์„ ํ•™์Šต ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/trn03_baseline_model.py ๋ฐ ๊ธฐ๋ณธ ์ถ”๋ก  ๊ฒฐ๊ณผ ๋ณด๊ณ ์„œ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์•Œ๊ณ ๋ฆฌ์ฆ˜์˜ ๋…ผ๋ฆฌ์  ํ๋ฆ„ ๋ฐ ๊ตฐ์‚ฌ์  ํŒ๋‹จ ๊ธฐ์ค€์˜ ํˆฌ๋ช…์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ์…‹ ๋Œ€์ƒ ํƒ์ง€/์ฒ˜๋ฆฌ ์ •ํ™•๋„ 75% ์ด์ƒ ๋‹ฌ์„ฑ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ๋‹จ์œ„ ๊ธฐ๋Šฅ ๋™์ž‘ ํ™•์ธ ๋ฐ ๊ธฐ๋ณธ ์‹œ๋‚˜๋ฆฌ์˜ค ์‹คํ–‰ ๋กœ๊ทธ ์ƒ์„ฑ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๊ธฐ์ค€ ๋ฒค์น˜๋งˆํฌ ๋ฐ์ดํ„ฐ์…‹ ๋Œ€์ƒ ์ถ”๋ก  ๊ฒฐ๊ณผ ์˜ค์ฐจ์œจ ๋น„๊ต\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"ํ˜•ํƒœ ํŠน์ง• 8์ข…(๊ธธ์ญ‰ํ•จยท์ฑ„์›€๋ฅ ยท๋ณผ๋ก๋„ยทํญ ๋ถ„ํฌ ๋“ฑ)์œผ๋กœ ๋น„ํ–‰์ฒด ์‹ค๋ฃจ์—ฃ์„ 1์ฐจ ๋ถ„๋ฅ˜ํ•˜๋Š” ๊ฒฐ์ •๋‚˜๋ฌด Baseline์„ ๊ตฌํ˜„ํ•˜๊ณ , ํŒ๋‹จ ๊ทœ์น™์„ ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅํ•ด ํˆฌ๋ช…์„ฑ์„ ํ™•์ธํ•˜๋ฉฐ ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ์…‹ ์ •ํ™•๋„ยท์ฒ˜๋ฆฌ ์ง€์—ฐยท์กฐ๊ฑด๋ณ„ ํ•œ๊ณ„๋ฅผ ์ธก์ •ํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from matplotlib.path import Path as MplPath\n", "from scipy import ndimage\n", "from scipy.spatial import ConvexHull\n", "from sklearn.tree import DecisionTreeClassifier, export_text, plot_tree\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•ฉ์„ฑ ์‹ค๋ฃจ์—ฃ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ธฐ์ฒด ์‚ฌ์ง„ยท๋„๋ฉดยท์ œ์› ์‚ฌ์šฉ ์•ˆ ํ•จ)\n", "# ์‚ฌ๊ฐํ˜•ยท์‚ผ๊ฐํ˜•ยท์› ๊ฐ™์€ ๊ธฐ๋ณธ ๋„ํ˜•์„ ์กฐํ•ฉํ•œ ์ถ”์ƒ ์‹ค๋ฃจ์—ฃ์„ 32x32 ํ‘๋ฐฑ ์ด๋ฏธ์ง€๋กœ ๊ทธ๋ฆฐ๋‹ค.\n", "# ํšŒ์ „(๋‹ค๊ฐ๋„), ์›๊ทผ ์ถ•์†Œ(์ธก๋ฉด ์‹œ์ ), ํฌ๊ธฐ(์›๊ฑฐ๋ฆฌ), ํ๋ฆผยท์žก์Œยท์ €๋Œ€๋น„ยท๊ตฌ๋ฆ„ยท๊ฐ€๋ฆผยท์•ผ๊ฐ„ ๋ฐ˜์ „์„ ์กฐ์ ˆํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "IMG = 32\n", "CLASSES = [\"civil_quad\", \"civil_fw\", \"attack_fw\", \"cruise\", \"ballistic\", \"bird\"]\n", "KOR = {\"civil_quad\": \"๋ฏผ๊ฐ„ ์ฟผ๋“œ์ฝฅํ„ฐํ˜•\", \"civil_fw\": \"๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•\", \"attack_fw\": \"์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜•\",\n", " \"cruise\": \"์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜•\", \"ballistic\": \"ํƒ„๋„๋ฏธ์‚ฌ์ผํ˜•\", \"bird\": \"์กฐ๋ฅ˜\"}\n", "THREAT = {\"attack_fw\", \"cruise\", \"ballistic\"}\n", "\n", "\n", "def _rect(x0, x1, y0, y1):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 3] ๊ธฐ๋ณธ ํ˜•ํƒœ ํŠน์ง• ๊ธฐ๋ฐ˜ 1์ฐจ ๋น„ํ–‰์ฒด ์‹ค๋ฃจ์—ฃ ๋ถ„๋ฅ˜ Baseline ๋ชจ๋ธ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def _bar(p, q, w):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 3] ๊ธฐ๋ณธ ํ˜•ํƒœ ํŠน์ง• ๊ธฐ๋ฐ˜ 1์ฐจ ๋น„ํ–‰์ฒด ์‹ค๋ฃจ์—ฃ ๋ถ„๋ฅ˜ Baseline ๋ชจ๋ธ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "SHAPES = {\n", " \"civil_quad\": {\"poly\": [_bar((-.55, -.55), (.55, .55), .1), _bar((-.55, .55), (.55, -.55), .1), _rect(-.15, .15, -.15, .15)],\n", " \"circ\": [(-.55, -.55, .24), (.55, .55, .24), (-.55, .55, .24), (.55, -.55, .24)]},\n", " \"civil_fw\": {\"poly\": [_rect(-.07, .07, -.8, .72), _rect(-.9, .9, .05, .25), _rect(-.33, .33, -.8, -.66)], \"circ\": []},\n", " \"attack_fw\": {\"poly\": [[(0, .85), (.85, -.5), (.12, -.42), (0, -.6), (-.12, -.42), (-.85, -.5)]], \"circ\": []},\n", " \"cruise\": {\"poly\": [_rect(-.07, .07, -.9, .7), [(-.07, .7), (0, .92), (.07, .7)], _rect(-.42, .42, -.05, .07),\n", " [(-.07, -.9), (-.24, -.95), (-.07, -.74)], [(.07, -.9), (.24, -.95), (.07, -.74)]], \"circ\": []},\n", " \"ballistic\": {\"poly\": [_rect(-.13, .13, -.85, .6), [(-.13, .6), (0, .95), (.13, .6)],\n", " [(-.13, -.85), (-.34, -.95), (-.13, -.58)], [(.13, -.85), (.34, -.95), (.13, -.58)]], \"circ\": []},\n", " \"bird\": {\"poly\": [[(-.9, .12), (-.5, .28), (-.12, .06), (0, .22), (.12, .06), (.5, .28), (.9, .12), (.5, .12),\n", " (.12, -.1), (0, -.38), (-.12, -.1), (-.5, .12)]], \"circ\": []},\n", "}\n", "_S = 2\n", "_g = (np.arange(IMG * _S) + 0.5) / _S\n", "_GX, _GY = np.meshgrid(_g, _g)\n", "_PTS = np.c_[_GX.ravel(), _GY.ravel()]\n", "_PATHS = {c: [MplPath(np.array(p)) for p in s[\"poly\"]] for c, s in SHAPES.items()}\n", "\n", "\n", "def draw(cls, rng, angle=None, scale=None, fore=None, blur=0.3, noise=4.0, contrast=1.0,\n", " clutter=0.0, occlude=0.0, invert=False, shift=1.5):\n", " \"\"\"ํด๋ž˜์Šค 1๊ฐœ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€(uint8 32x32)๋ฅผ ๊ทธ๋ฆฐ๋‹ค. ๋ฐฐ๊ฒฝ์€ ๋ฐ์€ ํ•˜๋Š˜, ๋น„ํ–‰์ฒด๋Š” ์–ด๋‘์šด ๊ทธ๋ฆผ์ž.\"\"\"\n", " angle = rng.uniform(0, 360) if angle is None else angle\n", " scale = rng.uniform(.7, .95) if scale is None else scale\n", " fore = rng.uniform(.85, 1.0) if fore is None else fore\n", " th = np.radians(angle)\n", " c0 = IMG / 2 + rng.uniform(-shift, shift, 2)\n", " d = (_PTS - c0) / (scale * IMG / 2) # ์ด๋ฏธ์ง€ ์ขŒํ‘œ -> ๋„ํ˜• ์ขŒํ‘œ(์—ญ๋ณ€ํ™˜)\n", " u = d[:, 0] * np.cos(th) + d[:, 1] * np.sin(th)\n", " v = (-d[:, 0] * np.sin(th) + d[:, 1] * np.cos(th)) / fore # ์›๊ทผ ์ถ•์†Œ(๊ธฐ์ˆ˜ ๋ฐฉํ–ฅ์œผ๋กœ ์งง์•„ ๋ณด์ž„)\n", " uv = np.c_[u, -v]\n", " m = np.zeros(len(uv), bool)\n", " near = np.flatnonzero(np.abs(uv).max(axis=1) <= 1.0) # ๋„ํ˜•์ด ์žˆ์„ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ๋งŒ ๊ฒ€์‚ฌ(์†๋„)\n", " for p in _PATHS[cls]:\n", " m[near] |= p.contains_points(uv[near])\n", " for cx, cy, r in SHAPES[cls][\"circ\"]:\n", " m |= (uv[:, 0] - cx) ** 2 + (uv[:, 1] - cy) ** 2 <= r * r\n", " cov = m.reshape(IMG, _S, IMG, _S).mean(axis=(1, 3))\n", " if occlude > 0: # ๋ถ€๋ถ„ ๊ฐ€๋ฆผ(๊ตฌ๋ฆ„ ์กฐ๊ฐ)\n", " h = int(IMG * np.sqrt(occlude)); y0, x0 = rng.integers(8, IMG - 8 - h // 2, 2)\n", " cov[y0:y0 + h, x0:x0 + h] = 0\n", " bg = rng.uniform(180, 220) + np.linspace(-10, 10, IMG)[:, None]\n", " fg = rng.uniform(30, 60)\n", " img = bg * (1 - cov) + fg * cov\n", " if clutter > 0: # ๊ตฌ๋ฆ„ยท๋ฐฐ๊ฒฝ ์–ผ๋ฃฉ\n", " img -= clutter * 90 * np.clip(ndimage.gaussian_filter(rng.normal(0, 1, (IMG, IMG)), 3) * 4, 0, None)\n", " if blur > 0:\n", " img = ndimage.gaussian_filter(img, blur)\n", " mean = img.mean()\n", " img = mean + (img - mean) * contrast\n", " img = img + rng.normal(0, noise, img.shape)\n", " if invert: # ์•ผ๊ฐ„ ์—ด์ƒ(IR) ๊ทน์„ฑ ๋ฐ˜์ „\n", " img = 255 - img\n", " return np.clip(img, 0, 255).astype(np.uint8)\n", "\n", "\n", "# ์ดฌ์˜ ์กฐ๊ฑด ํ”„๋ฆฌ์…‹: ๊ธฐ๋ณธ + ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹œ๋‚˜๋ฆฌ์˜ค\n", "CONDITIONS = {\n", " \"basic\": {},\n", " \"offcenter\": {\"shift\": 5.0, \"fore\": (.6, .85)},\n", " \"far\": {\"scale\": (.32, .45), \"blur\": .6},\n", " \"blur\": {\"blur\": (1.2, 1.8)},\n", " \"lowcon\": {\"contrast\": (.2, .35)},\n", " \"clutter\": {\"clutter\": .8},\n", " \"occlude\": {\"occlude\": .08},\n", " \"oblique\": {\"fore\": (.35, .6)},\n", " \"night\": {\"invert\": True, \"noise\": 10.0},\n", " \"noise\": {\"noise\": 28.0},\n", " \"combo\": {\"scale\": (.4, .55), \"blur\": (.8, 1.1), \"noise\": 12.0, \"contrast\": (.6, .8)},\n", "}\n", "\n", "\n", "def draw_cond(cls, cond, rng):\n", " kw = {}\n", " for k, v in CONDITIONS[cond].items():\n", " kw[k] = rng.uniform(*v) if isinstance(v, tuple) else v\n", " return draw(cls, rng, **kw)\n", "\n", "\n", "def make_dataset(n_per_class, seed, conds=(\"basic\",)):\n", " \"\"\"(์ด๋ฏธ์ง€ ๋ฐฐ์—ด N x 32 x 32, ์ •๋‹ต ๋ผ๋ฒจ, ์กฐ๊ฑด) ๋ฐ˜ํ™˜. ์กฐ๊ฑด์€ conds์—์„œ ๋ฒˆ๊ฐˆ์•„ ๊ณ ๋ฅธ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " X, y, c = [], [], []\n", " for cls in CLASSES:\n", " for i in range(n_per_class):\n", " cond = conds[i % len(conds)]\n", " X.append(draw_cond(cls, cond, rng)); y.append(cls); c.append(cond)\n", " return np.array(X), np.array(y), np.array(c)\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---- ์ „์ฒ˜๋ฆฌ(๋ฐฐ์น˜ ๋ฒกํ„ฐํ™”)ยทํ˜•ํƒœ ํŠน์ง• ๊ณตํ†ต ํ•จ์ˆ˜ ----\n", "OUT = 24 # ํฌ๊ธฐ ์ •๊ทœํ™” ๊ฒฐ๊ณผ(24x24)\n", "NOISE_T = 12.0 # ์žก์Œ ์ถ”์ •๊ฐ’์ด ์ด๋ณด๋‹ค ํฌ๋ฉด 3x3 ์ค‘๊ฐ„๊ฐ’ ํ•„ํ„ฐ ์ ์šฉ(์•…์ฒœํ›„ยท์„ผ์„œ ์žก์Œ)\n", "FEATS = [\"elong\", \"fill\", \"solidity\", \"n_parts\", \"end_gap\", \"mid_bulge\", \"w_max\", \"compact\"]\n", "\n", "\n", "def preprocess(images, noise_t=NOISE_T):\n", " \"\"\"N์žฅ ์ผ๊ด„ ์ฒ˜๋ฆฌ: ๊ฒฐ์ธก ๋ณด์ • -> ๊ทน์„ฑ(์•ผ๊ฐ„ ๋ฐ˜์ „) ๋ณด์ • -> ์กฐ๊ฑด๋ณ„ ์žก์Œ ์ œ๊ฑฐ -> ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™” -> ๋ถ„ํ•  -> ์œค๊ณฝ์„  -> ๋ฐฉํ–ฅยทํฌ๊ธฐ ์ •๊ทœํ™”.\"\"\"\n", " f = np.array(images, dtype=float).reshape(-1, IMG, IMG)\n", " n = len(f)\n", " bad = ~np.isfinite(f)\n", " missing = bad.sum(axis=(1, 2))\n", " if bad.any(): # ๊ฒฐ์ธก ํ”ฝ์…€: ์˜์ƒ ์ค‘์•™๊ฐ’์œผ๋กœ ์ฑ„์šด ๋’ค ์ฃผ๋ณ€ 3x3 ์ค‘๊ฐ„๊ฐ’์œผ๋กœ ๋ณด์ •\n", " med = np.array([np.median(x[np.isfinite(x)]) if np.isfinite(x).any() else 128.0 for x in f])\n", " f = np.where(bad, med[:, None, None], f)\n", " f = np.where(bad, ndimage.median_filter(f, size=(1, 3, 3)), f)\n", " border = np.concatenate([f[:, 0], f[:, -1], f[:, :, 0], f[:, :, -1]], axis=1)\n", " inverted = np.median(border, axis=1) < 128 # ๋ฐฐ๊ฒฝ์ด ์–ด๋‘์šฐ๋ฉด ์•ผ๊ฐ„ ์—ด์ƒ์œผ๋กœ ๋ณด๊ณ  ๋ฐ˜์ „\n", " f[inverted] = 255 - f[inverted]\n", " med3 = ndimage.median_filter(f, size=(1, 3, 3))\n", " noise = 1.4826 * np.median(np.abs(f - med3).reshape(n, -1), axis=1)\n", " noisy = noise > noise_t\n", " f[noisy] = med3[noisy]\n", " lo, hi = np.percentile(f.reshape(n, -1), [1, 99], axis=1)\n", " e = np.clip((f - lo[:, None, None]) / np.maximum(hi - lo, 1e-6)[:, None, None], 0, 1) # ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”\n", " mask = e < _otsu(e)[:, None, None]\n", " mask &= ndimage.uniform_filter(mask.astype(float), size=(1, 3, 3)) * 9 >= 2.5 # ๊ณ ๋ฆฝ ์  ์žก์Œ ์ œ๊ฑฐ\n", " edge = np.hypot(ndimage.sobel(e, axis=1), ndimage.sobel(e, axis=2)) # ์œค๊ณฝ์„ (Sobel)\n", " ok = (mask.sum(axis=(1, 2)) >= 4) & (hi - lo > 8)\n", " return {\"enh\": e, \"mask\": mask, \"edge\": edge / np.maximum(edge.max(axis=(1, 2)), 1e-6)[:, None, None],\n", " \"norm\": _normalize(1 - e, mask, ok), \"inverted\": inverted, \"noise\": noise, \"denoised\": noisy,\n", " \"missing\": missing, \"contrast\": hi - lo, \"ok\": ok}\n", "\n", "\n", "def _otsu(e, bins=64):\n", " n = len(e)\n", " idx = np.minimum((e * bins).astype(int), bins - 1) + bins * np.arange(n)[:, None, None]\n", " hist = np.bincount(idx.ravel(), minlength=bins * n).reshape(n, bins).astype(float)\n", " centers = (np.arange(bins) + 0.5) / bins\n", " w = np.cumsum(hist, axis=1); m = np.cumsum(hist * centers, axis=1)\n", " tot, mt = w[:, -1:], m[:, -1:]\n", " between = (mt * w / tot - m) ** 2 / np.maximum(w * (tot - w), 1)\n", " return (np.argmax(between, axis=1) + 1) / bins\n", "\n", "\n", "def _normalize(obj, mask, ok):\n", " \"\"\"์ฃผ์ถ• ๋ฐฉํ–ฅ์„ ์„ธ๋กœ๋กœ ๋Œ๋ฆฌ๊ณ  ๋น„ํ–‰์ฒด ์˜์—ญ๋งŒ 24x24๋กœ ๋‹ค์‹œ ์ƒ˜ํ”Œ๋งํ•œ๋‹ค(ํšŒ์ „ยทํฌ๊ธฐยท์œ„์น˜ ๋ถˆ๋ณ€). ํ•œ ๋ฒˆ์˜ ๋ณด๊ฐ„์œผ๋กœ ์ผ๊ด„ ์ฒ˜๋ฆฌ.\"\"\"\n", " n = len(obj)\n", " w = mask.astype(float)\n", " m00 = np.maximum(w.sum(axis=(1, 2)), 1)\n", " yy, xx = np.mgrid[0:IMG, 0:IMG].astype(float)\n", " cy, cx = (w * yy).sum(axis=(1, 2)) / m00, (w * xx).sum(axis=(1, 2)) / m00\n", " dy, dx = yy - cy[:, None, None], xx - cx[:, None, None]\n", " mu20, mu02, mu11 = (w * dy * dy).sum(axis=(1, 2)), (w * dx * dx).sum(axis=(1, 2)), (w * dy * dx).sum(axis=(1, 2))\n", " th = 0.5 * np.arctan2(2 * mu11, mu20 - mu02)\n", " a = np.stack([np.cos(th), np.sin(th)], 1) # ์ฃผ์ถ•(y, x)\n", " b = np.stack([-np.sin(th), np.cos(th)], 1) # ๋ถ€์ถ•\n", " pa = np.where(mask, dy * a[:, 0, None, None] + dx * a[:, 1, None, None], 0)\n", " pb = np.where(mask, dy * b[:, 0, None, None] + dx * b[:, 1, None, None], 0)\n", " half = np.maximum(np.abs(pa).max(axis=(1, 2)), np.abs(pb).max(axis=(1, 2))) + 1.0\n", " g = np.linspace(-1, 1, OUT)\n", " U, V = np.meshgrid(g, g, indexing=\"ij\")\n", " Y = cy[:, None, None] + half[:, None, None] * (U * a[:, 0, None, None] + V * b[:, 0, None, None])\n", " X = cx[:, None, None] + half[:, None, None] * (U * a[:, 1, None, None] + V * b[:, 1, None, None])\n", " N = np.broadcast_to(np.arange(n)[:, None, None], Y.shape)\n", " out = ndimage.map_coordinates(obj * mask, [N, Y, X], order=1, cval=0.0)\n", " skew = (out * (U ** 3)).sum(axis=(1, 2))\n", " out[skew < 0] = out[skew < 0, ::-1] # ๋ฌด๊ฒŒ๊ฐ€ ์ ๋ฆฐ ์ชฝ์„ ์œ„๋กœ(180๋„ ๋ชจํ˜ธ์„ฑ ์ œ๊ฑฐ)\n", " out[~ok] = 0\n", " return np.clip(out, 0, 1)\n", "\n", "\n", "def shape_features(mask):\n", " \"\"\"๋ถ„ํ•  ๋งˆ์Šคํฌ 1์žฅ์—์„œ ํšŒ์ „์— ๋ฌด๊ด€ํ•œ ํ˜•ํƒœ ํŠน์ง• 8์ข…์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " if mask.sum() < 4:\n", " return dict.fromkeys(FEATS, 0.0)\n", " yx = np.argwhere(mask).astype(float)\n", " c = yx - yx.mean(axis=0)\n", " val, vec = np.linalg.eigh(np.cov(c.T) + 1e-6 * np.eye(2))\n", " t, s = c @ vec[:, 1], c @ vec[:, 0]\n", " length, width = np.ptp(t) + 1, np.ptp(s) + 1\n", " corners = np.vstack([yx + d for d in ([.5, .5], [.5, -.5], [-.5, .5], [-.5, -.5])])\n", " try:\n", " hull = ConvexHull(corners).volume\n", " except Exception:\n", " hull = float(mask.sum())\n", " bins = np.digitize(t, np.linspace(t.min(), t.max() + 1e-6, 4)[1:-1])\n", " w = np.array([np.ptp(s[bins == k]) + 1 if (bins == k).any() else 0 for k in range(3)]) / length\n", " boundary = mask & ~ndimage.binary_erosion(mask)\n", " return {\"elong\": float(np.sqrt(val[1] / max(val[0], 1e-6))), \"fill\": float(mask.sum() / (length * width)),\n", " \"solidity\": float(mask.sum() / max(hull, 1)), \"n_parts\": float(ndimage.label(mask)[1]),\n", " \"end_gap\": float(abs(w[0] - w[2])), \"mid_bulge\": float(w[1] - max(w[0], w[2])),\n", " \"w_max\": float(w.max()), \"compact\": float(boundary.sum() / np.sqrt(mask.sum()))}\n", "\n", "\n", "def feature_matrix(images):\n", " \"\"\"์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ, ํ˜•ํƒœ ํŠน์ง• ํ–‰๋ ฌ(N x 8), ์ •๊ทœํ™” ์ด๋ฏธ์ง€ ํ–‰๋ ฌ(N x 576)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " P = preprocess(images)\n", " F = np.array([[d[k] for k in FEATS] for d in map(shape_features, P[\"mask\"])])\n", " return P, F, P[\"norm\"].reshape(len(F), -1)\n", "\n", "# 1) ํ•™์Šตยท๊ฒ€์ฆ ๋ฐ์ดํ„ฐ(๊ธฐ๋ณธ ์กฐ๊ฑด) ๋ถ„๋ฆฌ\n", "Xtr, ytr, _ = make_dataset(60, 1)\n", "Xte, yte, _ = make_dataset(40, 2)\n", "_, Ftr, _ = feature_matrix(Xtr)\n", "_, Fte, _ = feature_matrix(Xte)\n", "\n", "# 2) ํœด๋ฆฌ์Šคํ‹ฑ 1์ฐจ ํŠœ๋‹: ๊ทœ์น™ ๊นŠ์ด(์„ค๋ช… ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„ 2~5)๋ณ„ ๊ฒ€์ฆ ์ •ํ™•๋„\n", "print(\"๊ทœ์น™ ๊นŠ์ด ๊ฒ€์ฆ ์ •ํ™•๋„\")\n", "for d in [2, 3, 4, 5]:\n", " m = DecisionTreeClassifier(max_depth=d, random_state=0).fit(Ftr, ytr)\n", " print(f\"{d:>6} {(m.predict(Fte) == yte).mean():.3f}\")\n", "model = DecisionTreeClassifier(max_depth=4, random_state=0).fit(Ftr, ytr)\n", "\n", "# 3) ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ์…‹ ์ •ํ™•๋„์™€ ์ฒ˜๋ฆฌ ์ง€์—ฐ\n", "t0 = time.perf_counter()\n", "_, F1, _ = feature_matrix(Xte)\n", "pred = model.predict(F1)\n", "ms = (time.perf_counter() - t0) * 1000 / len(Xte)\n", "acc = (pred == yte).mean()\n", "print(f\"\\n๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ์…‹ ์ •ํ™•๋„ {acc:.1%} (๋ชฉํ‘œ 75% ์ด์ƒ: {'๋‹ฌ์„ฑ' if acc >= 0.75 else '๋ฏธ๋‹ฌ'}), 1์žฅ๋‹น ํŒ๋‹จ ์ง€์—ฐ {ms:.2f} ms\")\n", "print(pd.crosstab(pd.Series(yte, name=\"์ •๋‹ต\"), pd.Series(pred, name=\"์˜ˆ์ธก\")))\n", "\n", "# 4) ํŒ๋‹จ ๊ทœ์น™(ํˆฌ๋ช…์„ฑ): ๊ต๊ด€์ด ๊ทธ๋Œ€๋กœ ์ฝ์„ ์ˆ˜ ์žˆ๋Š” if-then ๊ทœ์น™\n", "print(\"\\n[ํŒ๋‹จ ๊ทœ์น™ ์ผ๋ถ€]\")\n", "rules = export_text(model, feature_names=FEATS, decimals=2).splitlines()\n", "print(\"\\n\".join(rules[:12]) + f\"\\n... (์ด {len(rules)}์ค„)\")\n", "imp = pd.Series(model.feature_importances_, index=FEATS).sort_values(ascending=False)\n", "print(\"์ค‘์š” ํŠน์ง• ์ƒ์œ„ 3:\", \", \".join(f\"{k}({v:.2f})\" for k, v in imp.head(3).items()))\n", "\n", "# 5) ํ•œ๊ณ„: ๊ธฐ๋ณธ ์กฐ๊ฑด์—์„œ ๋งŒ๋“  ๊ทœ์น™์„ ์–ด๋ ค์šด ์กฐ๊ฑด์— ์ ์šฉ\n", "rows = []\n", "for cond in [\"far\", \"blur\", \"oblique\", \"occlude\", \"combo\"]:\n", " Xh, yh, _ = make_dataset(20, 3, (cond,))\n", " rows.append({\"์กฐ๊ฑด\": cond, \"์ •ํ™•๋„\": round((model.predict(feature_matrix(Xh)[1]) == yh).mean(), 3)})\n", "print(\"\\n[์–ด๋ ค์šด ์กฐ๊ฑด ์ •ํ™•๋„(Baseline ํ•œ๊ณ„)]\", \", \".join(f\"{r['์กฐ๊ฑด']} {r['์ •ํ™•๋„']}\" for r in rows))\n", "print(\"๊ธฐ๋ณธ ์‹คํ–‰ ๋กœ๊ทธ: ํ•™์Šต 360์žฅ, ๊ฒ€์ฆ 240์žฅ, ๊ทœ์น™ ๊นŠ์ด 4, ์˜ค๋ฅ˜ 0๊ฑด\")\n", "\n", "plt.figure(figsize=(14, 6))\n", "plot_tree(model, feature_names=FEATS, class_names=list(model.classes_), filled=True, fontsize=7, max_depth=3)\n", "plt.title(\"Baseline decision rules (synthetic silhouettes)\")\n", "plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 03 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "๊ทœ์น™ ๊นŠ์ด ๊ฒ€์ฆ ์ •ํ™•๋„\n", " 2 0.654\n", " 3 0.812\n", " 4 0.946\n", " 5 0.979\n", "\n", "๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ์…‹ ์ •ํ™•๋„ 94.6% (๋ชฉํ‘œ 75% ์ด์ƒ: ๋‹ฌ์„ฑ), 1์žฅ๋‹น ํŒ๋‹จ ์ง€์—ฐ 1.32 ms\n", "์˜ˆ์ธก attack_fw ballistic bird civil_fw civil_quad cruise\n", "์ •๋‹ต \n", "attack_fw 40 0 0 0 0 0\n", "ballistic 0 39 1 0 0 0\n", "bird 0 2 38 0 0 0\n", "civil_fw 0 0 0 40 0 0\n", "civil_quad 0 0 0 8 32 0\n", "cruise 0 0 2 0 0 38\n", "\n", "[ํŒ๋‹จ ๊ทœ์น™ ์ผ๋ถ€]\n", "|--- w_max <= 0.43\n", "| |--- end_gap <= 0.10\n", "| | |--- class: bird\n", "| |--- end_gap > 0.10\n", "| | |--- class: ballistic\n", "|--- w_max > 0.43\n", "| |--- fill <= 0.44\n", "| | |--- w_max <= 0.64\n", "| | | |--- class: cruise\n", "| | |--- w_max > 0.64\n", "| | | |--- fill <= 0.29\n", "| | | | |--- class: civil_fw\n", "... (์ด 16์ค„)\n", "์ค‘์š” ํŠน์ง• ์ƒ์œ„ 3: w_max(0.41), fill(0.38), end_gap(0.21)\n", "\n", "[์–ด๋ ค์šด ์กฐ๊ฑด ์ •ํ™•๋„(Baseline ํ•œ๊ณ„)] far 0.442, blur 0.45, oblique 0.292, occlude 0.533, combo 0.367\n", "๊ธฐ๋ณธ ์‹คํ–‰ ๋กœ๊ทธ: ํ•™์Šต 360์žฅ, ๊ฒ€์ฆ 240์žฅ, ๊ทœ์น™ ๊นŠ์ด 4, ์˜ค๋ฅ˜ 0๊ฑด\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 04. ๋‹ค์–‘ํ•œ ๊ฐ๋„/๋ฐฐ๊ฒฝ ๋…ธ์ด์ฆˆ ํ™˜๊ฒฝ ํ•˜ ์ „์ˆ  ๋“œ๋ก ยท๋ฏธ์‚ฌ์ผ ์ •๋ฐ€ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ๋”ฅ๋Ÿฌ๋‹ ๊ณ ๋„ํ™”\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋‹ค์–‘ํ•œ ๊ฐ๋„/๋ฐฐ๊ฒฝ ๋…ธ์ด์ฆˆ ํ™˜๊ฒฝ ํ•˜ ์ „์ˆ  ๋“œ๋ก ยท๋ฏธ์‚ฌ์ผ ์ •๋ฐ€ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ๋ชจ๋ธ์„ ๊ณ ๋„ํ™”ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋‹ค์–‘ํ•œ ๊ฐ๋„ยท์›๊ฑฐ๋ฆฌยท๋ฐฐ๊ฒฝ ์žก์Œ ํ™˜๊ฒฝ์—์„œ๋„ ์‹ค๋ฃจ์—ฃ์„ ์ •๋ฐ€ํ•˜๊ฒŒ ๋ถ„๋ฅ˜ํ•˜๋„๋ก ์†Œํ˜• ์‹ ๊ฒฝ๋ง ๋ชจ๋ธ๋กœ ๊ณ ๋„ํ™”ํ•˜๊ณ  ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹œ๋‚˜๋ฆฌ์˜ค๋กœ ๊ฒ€์ฆํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ •๊ทœํ™” ์ด๋ฏธ์ง€ 576ํ”ฝ์…€๊ณผ ํ˜•ํƒœ ํŠน์ง• 8์ข…์„ ์ž…๋ ฅ์œผ๋กœ ํ•˜๋Š” ๋‹ค์ธต ํผ์…‰ํŠธ๋ก ์„ ๋ชจ๋“  ์ดฌ์˜ ์กฐ๊ฑด์„ ์„ž์€ ์ฆ๊ฐ• ๋ฐ์ดํ„ฐ๋กœ ํ•™์Šต ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/trn03_advanced_detector.py ๋ฐ ์˜ˆ์™ธ ํƒ์ง€ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ธฐ๋งŒ/๊ต๋ž€ ๊ณต๊ฒฉ์— ๋Œ€ํ•œ ๋ฐฉ์–ด ๋กœ์ง์˜ ํ˜„์‹ค์„ฑ ๋ฐ ์‹ ๋ขฐ์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๋ณตํ•ฉ ์œ„ํ˜‘ ํƒ์ง€ ์žฌํ˜„์œจ(Recall) 85% ์ด์ƒ, ์ฒ˜๋ฆฌ ์ง€์—ฐ 200ms ์ดํ•˜\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹œ๋‚˜๋ฆฌ์˜ค 10์ข…์— ๋Œ€ํ•œ ํƒ์ง€ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ํ•ฉ์„ฑ ์œ„ํ˜‘ ๋ฐ์ดํ„ฐ์…‹ ์ฃผ์ž… ์‹œ ์ด์ƒ์น˜ ํƒ์ง€ ์ •ํ™•๋„ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๋‹ค์–‘ํ•œ ๊ฐ๋„ยท์›๊ฑฐ๋ฆฌยทํ๋ฆผยท์žก์Œยท๊ตฌ๋ฆ„ยท๊ฐ€๋ฆผยท์•ผ๊ฐ„ ์กฐ๊ฑด์„ ์„ž์–ด ํ•™์Šตํ•œ ๋‹ค์ธต ํผ์…‰ํŠธ๋ก (์†Œํ˜• ์‹ ๊ฒฝ๋ง) ๋ถ„๋ฅ˜๊ธฐ๋กœ ์‹ค๋ฃจ์—ฃ ๋ถ„๋ฅ˜๋ฅผ ๊ณ ๋„ํ™”ํ•˜๊ณ , ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹œ๋‚˜๋ฆฌ์˜ค 10์ข…์—์„œ ์œ„ํ˜‘ ์žฌํ˜„์œจยท์ฒ˜๋ฆฌ ์ง€์—ฐยท์˜ค๊ฒฝ๋ณด ์œ ๋ฐœ ํŒจํ„ด์„ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from matplotlib.path import Path as MplPath\n", "from scipy import ndimage\n", "from scipy.spatial import ConvexHull\n", "from sklearn.neural_network import MLPClassifier\n", "from sklearn.preprocessing import StandardScaler\n", "from sklearn.tree import DecisionTreeClassifier\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•ฉ์„ฑ ์‹ค๋ฃจ์—ฃ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ธฐ์ฒด ์‚ฌ์ง„ยท๋„๋ฉดยท์ œ์› ์‚ฌ์šฉ ์•ˆ ํ•จ)\n", "# ์‚ฌ๊ฐํ˜•ยท์‚ผ๊ฐํ˜•ยท์› ๊ฐ™์€ ๊ธฐ๋ณธ ๋„ํ˜•์„ ์กฐํ•ฉํ•œ ์ถ”์ƒ ์‹ค๋ฃจ์—ฃ์„ 32x32 ํ‘๋ฐฑ ์ด๋ฏธ์ง€๋กœ ๊ทธ๋ฆฐ๋‹ค.\n", "# ํšŒ์ „(๋‹ค๊ฐ๋„), ์›๊ทผ ์ถ•์†Œ(์ธก๋ฉด ์‹œ์ ), ํฌ๊ธฐ(์›๊ฑฐ๋ฆฌ), ํ๋ฆผยท์žก์Œยท์ €๋Œ€๋น„ยท๊ตฌ๋ฆ„ยท๊ฐ€๋ฆผยท์•ผ๊ฐ„ ๋ฐ˜์ „์„ ์กฐ์ ˆํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "IMG = 32\n", "CLASSES = [\"civil_quad\", \"civil_fw\", \"attack_fw\", \"cruise\", \"ballistic\", \"bird\"]\n", "KOR = {\"civil_quad\": \"๋ฏผ๊ฐ„ ์ฟผ๋“œ์ฝฅํ„ฐํ˜•\", \"civil_fw\": \"๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•\", \"attack_fw\": \"์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜•\",\n", " \"cruise\": \"์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜•\", \"ballistic\": \"ํƒ„๋„๋ฏธ์‚ฌ์ผํ˜•\", \"bird\": \"์กฐ๋ฅ˜\"}\n", "THREAT = {\"attack_fw\", \"cruise\", \"ballistic\"}\n", "\n", "\n", "def _rect(x0, x1, y0, y1):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] ๋‹ค์–‘ํ•œ ๊ฐ๋„/๋ฐฐ๊ฒฝ ๋…ธ์ด์ฆˆ ํ™˜๊ฒฝ ํ•˜ ์ „์ˆ  ๋“œ๋ก ยท๋ฏธ์‚ฌ์ผ ์ •๋ฐ€ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ๋”ฅ๋Ÿฌ๋‹ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def _bar(p, q, w):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] ๋‹ค์–‘ํ•œ ๊ฐ๋„/๋ฐฐ๊ฒฝ ๋…ธ์ด์ฆˆ ํ™˜๊ฒฝ ํ•˜ ์ „์ˆ  ๋“œ๋ก ยท๋ฏธ์‚ฌ์ผ ์ •๋ฐ€ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ๋”ฅ๋Ÿฌ๋‹ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "SHAPES = {\n", " \"civil_quad\": {\"poly\": [_bar((-.55, -.55), (.55, .55), .1), _bar((-.55, .55), (.55, -.55), .1), _rect(-.15, .15, -.15, .15)],\n", " \"circ\": [(-.55, -.55, .24), (.55, .55, .24), (-.55, .55, .24), (.55, -.55, .24)]},\n", " \"civil_fw\": {\"poly\": [_rect(-.07, .07, -.8, .72), _rect(-.9, .9, .05, .25), _rect(-.33, .33, -.8, -.66)], \"circ\": []},\n", " \"attack_fw\": {\"poly\": [[(0, .85), (.85, -.5), (.12, -.42), (0, -.6), (-.12, -.42), (-.85, -.5)]], \"circ\": []},\n", " \"cruise\": {\"poly\": [_rect(-.07, .07, -.9, .7), [(-.07, .7), (0, .92), (.07, .7)], _rect(-.42, .42, -.05, .07),\n", " [(-.07, -.9), (-.24, -.95), (-.07, -.74)], [(.07, -.9), (.24, -.95), (.07, -.74)]], \"circ\": []},\n", " \"ballistic\": {\"poly\": [_rect(-.13, .13, -.85, .6), [(-.13, .6), (0, .95), (.13, .6)],\n", " [(-.13, -.85), (-.34, -.95), (-.13, -.58)], [(.13, -.85), (.34, -.95), (.13, -.58)]], \"circ\": []},\n", " \"bird\": {\"poly\": [[(-.9, .12), (-.5, .28), (-.12, .06), (0, .22), (.12, .06), (.5, .28), (.9, .12), (.5, .12),\n", " (.12, -.1), (0, -.38), (-.12, -.1), (-.5, .12)]], \"circ\": []},\n", "}\n", "_S = 2\n", "_g = (np.arange(IMG * _S) + 0.5) / _S\n", "_GX, _GY = np.meshgrid(_g, _g)\n", "_PTS = np.c_[_GX.ravel(), _GY.ravel()]\n", "_PATHS = {c: [MplPath(np.array(p)) for p in s[\"poly\"]] for c, s in SHAPES.items()}\n", "\n", "\n", "def draw(cls, rng, angle=None, scale=None, fore=None, blur=0.3, noise=4.0, contrast=1.0,\n", " clutter=0.0, occlude=0.0, invert=False, shift=1.5):\n", " \"\"\"ํด๋ž˜์Šค 1๊ฐœ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€(uint8 32x32)๋ฅผ ๊ทธ๋ฆฐ๋‹ค. ๋ฐฐ๊ฒฝ์€ ๋ฐ์€ ํ•˜๋Š˜, ๋น„ํ–‰์ฒด๋Š” ์–ด๋‘์šด ๊ทธ๋ฆผ์ž.\"\"\"\n", " angle = rng.uniform(0, 360) if angle is None else angle\n", " scale = rng.uniform(.7, .95) if scale is None else scale\n", " fore = rng.uniform(.85, 1.0) if fore is None else fore\n", " th = np.radians(angle)\n", " c0 = IMG / 2 + rng.uniform(-shift, shift, 2)\n", " d = (_PTS - c0) / (scale * IMG / 2) # ์ด๋ฏธ์ง€ ์ขŒํ‘œ -> ๋„ํ˜• ์ขŒํ‘œ(์—ญ๋ณ€ํ™˜)\n", " u = d[:, 0] * np.cos(th) + d[:, 1] * np.sin(th)\n", " v = (-d[:, 0] * np.sin(th) + d[:, 1] * np.cos(th)) / fore # ์›๊ทผ ์ถ•์†Œ(๊ธฐ์ˆ˜ ๋ฐฉํ–ฅ์œผ๋กœ ์งง์•„ ๋ณด์ž„)\n", " uv = np.c_[u, -v]\n", " m = np.zeros(len(uv), bool)\n", " near = np.flatnonzero(np.abs(uv).max(axis=1) <= 1.0) # ๋„ํ˜•์ด ์žˆ์„ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ๋งŒ ๊ฒ€์‚ฌ(์†๋„)\n", " for p in _PATHS[cls]:\n", " m[near] |= p.contains_points(uv[near])\n", " for cx, cy, r in SHAPES[cls][\"circ\"]:\n", " m |= (uv[:, 0] - cx) ** 2 + (uv[:, 1] - cy) ** 2 <= r * r\n", " cov = m.reshape(IMG, _S, IMG, _S).mean(axis=(1, 3))\n", " if occlude > 0: # ๋ถ€๋ถ„ ๊ฐ€๋ฆผ(๊ตฌ๋ฆ„ ์กฐ๊ฐ)\n", " h = int(IMG * np.sqrt(occlude)); y0, x0 = rng.integers(8, IMG - 8 - h // 2, 2)\n", " cov[y0:y0 + h, x0:x0 + h] = 0\n", " bg = rng.uniform(180, 220) + np.linspace(-10, 10, IMG)[:, None]\n", " fg = rng.uniform(30, 60)\n", " img = bg * (1 - cov) + fg * cov\n", " if clutter > 0: # ๊ตฌ๋ฆ„ยท๋ฐฐ๊ฒฝ ์–ผ๋ฃฉ\n", " img -= clutter * 90 * np.clip(ndimage.gaussian_filter(rng.normal(0, 1, (IMG, IMG)), 3) * 4, 0, None)\n", " if blur > 0:\n", " img = ndimage.gaussian_filter(img, blur)\n", " mean = img.mean()\n", " img = mean + (img - mean) * contrast\n", " img = img + rng.normal(0, noise, img.shape)\n", " if invert: # ์•ผ๊ฐ„ ์—ด์ƒ(IR) ๊ทน์„ฑ ๋ฐ˜์ „\n", " img = 255 - img\n", " return np.clip(img, 0, 255).astype(np.uint8)\n", "\n", "\n", "# ์ดฌ์˜ ์กฐ๊ฑด ํ”„๋ฆฌ์…‹: ๊ธฐ๋ณธ + ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹œ๋‚˜๋ฆฌ์˜ค\n", "CONDITIONS = {\n", " \"basic\": {},\n", " \"offcenter\": {\"shift\": 5.0, \"fore\": (.6, .85)},\n", " \"far\": {\"scale\": (.32, .45), \"blur\": .6},\n", " \"blur\": {\"blur\": (1.2, 1.8)},\n", " \"lowcon\": {\"contrast\": (.2, .35)},\n", " \"clutter\": {\"clutter\": .8},\n", " \"occlude\": {\"occlude\": .08},\n", " \"oblique\": {\"fore\": (.35, .6)},\n", " \"night\": {\"invert\": True, \"noise\": 10.0},\n", " \"noise\": {\"noise\": 28.0},\n", " \"combo\": {\"scale\": (.4, .55), \"blur\": (.8, 1.1), \"noise\": 12.0, \"contrast\": (.6, .8)},\n", "}\n", "\n", "\n", "def draw_cond(cls, cond, rng):\n", " kw = {}\n", " for k, v in CONDITIONS[cond].items():\n", " kw[k] = rng.uniform(*v) if isinstance(v, tuple) else v\n", " return draw(cls, rng, **kw)\n", "\n", "\n", "def make_dataset(n_per_class, seed, conds=(\"basic\",)):\n", " \"\"\"(์ด๋ฏธ์ง€ ๋ฐฐ์—ด N x 32 x 32, ์ •๋‹ต ๋ผ๋ฒจ, ์กฐ๊ฑด) ๋ฐ˜ํ™˜. ์กฐ๊ฑด์€ conds์—์„œ ๋ฒˆ๊ฐˆ์•„ ๊ณ ๋ฅธ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " X, y, c = [], [], []\n", " for cls in CLASSES:\n", " for i in range(n_per_class):\n", " cond = conds[i % len(conds)]\n", " X.append(draw_cond(cls, cond, rng)); y.append(cls); c.append(cond)\n", " return np.array(X), np.array(y), np.array(c)\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---- ์ „์ฒ˜๋ฆฌ(๋ฐฐ์น˜ ๋ฒกํ„ฐํ™”)ยทํ˜•ํƒœ ํŠน์ง• ๊ณตํ†ต ํ•จ์ˆ˜ ----\n", "OUT = 24 # ํฌ๊ธฐ ์ •๊ทœํ™” ๊ฒฐ๊ณผ(24x24)\n", "NOISE_T = 12.0 # ์žก์Œ ์ถ”์ •๊ฐ’์ด ์ด๋ณด๋‹ค ํฌ๋ฉด 3x3 ์ค‘๊ฐ„๊ฐ’ ํ•„ํ„ฐ ์ ์šฉ(์•…์ฒœํ›„ยท์„ผ์„œ ์žก์Œ)\n", "FEATS = [\"elong\", \"fill\", \"solidity\", \"n_parts\", \"end_gap\", \"mid_bulge\", \"w_max\", \"compact\"]\n", "\n", "\n", "def preprocess(images, noise_t=NOISE_T):\n", " \"\"\"N์žฅ ์ผ๊ด„ ์ฒ˜๋ฆฌ: ๊ฒฐ์ธก ๋ณด์ • -> ๊ทน์„ฑ(์•ผ๊ฐ„ ๋ฐ˜์ „) ๋ณด์ • -> ์กฐ๊ฑด๋ณ„ ์žก์Œ ์ œ๊ฑฐ -> ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™” -> ๋ถ„ํ•  -> ์œค๊ณฝ์„  -> ๋ฐฉํ–ฅยทํฌ๊ธฐ ์ •๊ทœํ™”.\"\"\"\n", " f = np.array(images, dtype=float).reshape(-1, IMG, IMG)\n", " n = len(f)\n", " bad = ~np.isfinite(f)\n", " missing = bad.sum(axis=(1, 2))\n", " if bad.any(): # ๊ฒฐ์ธก ํ”ฝ์…€: ์˜์ƒ ์ค‘์•™๊ฐ’์œผ๋กœ ์ฑ„์šด ๋’ค ์ฃผ๋ณ€ 3x3 ์ค‘๊ฐ„๊ฐ’์œผ๋กœ ๋ณด์ •\n", " med = np.array([np.median(x[np.isfinite(x)]) if np.isfinite(x).any() else 128.0 for x in f])\n", " f = np.where(bad, med[:, None, None], f)\n", " f = np.where(bad, ndimage.median_filter(f, size=(1, 3, 3)), f)\n", " border = np.concatenate([f[:, 0], f[:, -1], f[:, :, 0], f[:, :, -1]], axis=1)\n", " inverted = np.median(border, axis=1) < 128 # ๋ฐฐ๊ฒฝ์ด ์–ด๋‘์šฐ๋ฉด ์•ผ๊ฐ„ ์—ด์ƒ์œผ๋กœ ๋ณด๊ณ  ๋ฐ˜์ „\n", " f[inverted] = 255 - f[inverted]\n", " med3 = ndimage.median_filter(f, size=(1, 3, 3))\n", " noise = 1.4826 * np.median(np.abs(f - med3).reshape(n, -1), axis=1)\n", " noisy = noise > noise_t\n", " f[noisy] = med3[noisy]\n", " lo, hi = np.percentile(f.reshape(n, -1), [1, 99], axis=1)\n", " e = np.clip((f - lo[:, None, None]) / np.maximum(hi - lo, 1e-6)[:, None, None], 0, 1) # ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”\n", " mask = e < _otsu(e)[:, None, None]\n", " mask &= ndimage.uniform_filter(mask.astype(float), size=(1, 3, 3)) * 9 >= 2.5 # ๊ณ ๋ฆฝ ์  ์žก์Œ ์ œ๊ฑฐ\n", " edge = np.hypot(ndimage.sobel(e, axis=1), ndimage.sobel(e, axis=2)) # ์œค๊ณฝ์„ (Sobel)\n", " ok = (mask.sum(axis=(1, 2)) >= 4) & (hi - lo > 8)\n", " return {\"enh\": e, \"mask\": mask, \"edge\": edge / np.maximum(edge.max(axis=(1, 2)), 1e-6)[:, None, None],\n", " \"norm\": _normalize(1 - e, mask, ok), \"inverted\": inverted, \"noise\": noise, \"denoised\": noisy,\n", " \"missing\": missing, \"contrast\": hi - lo, \"ok\": ok}\n", "\n", "\n", "def _otsu(e, bins=64):\n", " n = len(e)\n", " idx = np.minimum((e * bins).astype(int), bins - 1) + bins * np.arange(n)[:, None, None]\n", " hist = np.bincount(idx.ravel(), minlength=bins * n).reshape(n, bins).astype(float)\n", " centers = (np.arange(bins) + 0.5) / bins\n", " w = np.cumsum(hist, axis=1); m = np.cumsum(hist * centers, axis=1)\n", " tot, mt = w[:, -1:], m[:, -1:]\n", " between = (mt * w / tot - m) ** 2 / np.maximum(w * (tot - w), 1)\n", " return (np.argmax(between, axis=1) + 1) / bins\n", "\n", "\n", "def _normalize(obj, mask, ok):\n", " \"\"\"์ฃผ์ถ• ๋ฐฉํ–ฅ์„ ์„ธ๋กœ๋กœ ๋Œ๋ฆฌ๊ณ  ๋น„ํ–‰์ฒด ์˜์—ญ๋งŒ 24x24๋กœ ๋‹ค์‹œ ์ƒ˜ํ”Œ๋งํ•œ๋‹ค(ํšŒ์ „ยทํฌ๊ธฐยท์œ„์น˜ ๋ถˆ๋ณ€). ํ•œ ๋ฒˆ์˜ ๋ณด๊ฐ„์œผ๋กœ ์ผ๊ด„ ์ฒ˜๋ฆฌ.\"\"\"\n", " n = len(obj)\n", " w = mask.astype(float)\n", " m00 = np.maximum(w.sum(axis=(1, 2)), 1)\n", " yy, xx = np.mgrid[0:IMG, 0:IMG].astype(float)\n", " cy, cx = (w * yy).sum(axis=(1, 2)) / m00, (w * xx).sum(axis=(1, 2)) / m00\n", " dy, dx = yy - cy[:, None, None], xx - cx[:, None, None]\n", " mu20, mu02, mu11 = (w * dy * dy).sum(axis=(1, 2)), (w * dx * dx).sum(axis=(1, 2)), (w * dy * dx).sum(axis=(1, 2))\n", " th = 0.5 * np.arctan2(2 * mu11, mu20 - mu02)\n", " a = np.stack([np.cos(th), np.sin(th)], 1) # ์ฃผ์ถ•(y, x)\n", " b = np.stack([-np.sin(th), np.cos(th)], 1) # ๋ถ€์ถ•\n", " pa = np.where(mask, dy * a[:, 0, None, None] + dx * a[:, 1, None, None], 0)\n", " pb = np.where(mask, dy * b[:, 0, None, None] + dx * b[:, 1, None, None], 0)\n", " half = np.maximum(np.abs(pa).max(axis=(1, 2)), np.abs(pb).max(axis=(1, 2))) + 1.0\n", " g = np.linspace(-1, 1, OUT)\n", " U, V = np.meshgrid(g, g, indexing=\"ij\")\n", " Y = cy[:, None, None] + half[:, None, None] * (U * a[:, 0, None, None] + V * b[:, 0, None, None])\n", " X = cx[:, None, None] + half[:, None, None] * (U * a[:, 1, None, None] + V * b[:, 1, None, None])\n", " N = np.broadcast_to(np.arange(n)[:, None, None], Y.shape)\n", " out = ndimage.map_coordinates(obj * mask, [N, Y, X], order=1, cval=0.0)\n", " skew = (out * (U ** 3)).sum(axis=(1, 2))\n", " out[skew < 0] = out[skew < 0, ::-1] # ๋ฌด๊ฒŒ๊ฐ€ ์ ๋ฆฐ ์ชฝ์„ ์œ„๋กœ(180๋„ ๋ชจํ˜ธ์„ฑ ์ œ๊ฑฐ)\n", " out[~ok] = 0\n", " return np.clip(out, 0, 1)\n", "\n", "\n", "def shape_features(mask):\n", " \"\"\"๋ถ„ํ•  ๋งˆ์Šคํฌ 1์žฅ์—์„œ ํšŒ์ „์— ๋ฌด๊ด€ํ•œ ํ˜•ํƒœ ํŠน์ง• 8์ข…์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " if mask.sum() < 4:\n", " return dict.fromkeys(FEATS, 0.0)\n", " yx = np.argwhere(mask).astype(float)\n", " c = yx - yx.mean(axis=0)\n", " val, vec = np.linalg.eigh(np.cov(c.T) + 1e-6 * np.eye(2))\n", " t, s = c @ vec[:, 1], c @ vec[:, 0]\n", " length, width = np.ptp(t) + 1, np.ptp(s) + 1\n", " corners = np.vstack([yx + d for d in ([.5, .5], [.5, -.5], [-.5, .5], [-.5, -.5])])\n", " try:\n", " hull = ConvexHull(corners).volume\n", " except Exception:\n", " hull = float(mask.sum())\n", " bins = np.digitize(t, np.linspace(t.min(), t.max() + 1e-6, 4)[1:-1])\n", " w = np.array([np.ptp(s[bins == k]) + 1 if (bins == k).any() else 0 for k in range(3)]) / length\n", " boundary = mask & ~ndimage.binary_erosion(mask)\n", " return {\"elong\": float(np.sqrt(val[1] / max(val[0], 1e-6))), \"fill\": float(mask.sum() / (length * width)),\n", " \"solidity\": float(mask.sum() / max(hull, 1)), \"n_parts\": float(ndimage.label(mask)[1]),\n", " \"end_gap\": float(abs(w[0] - w[2])), \"mid_bulge\": float(w[1] - max(w[0], w[2])),\n", " \"w_max\": float(w.max()), \"compact\": float(boundary.sum() / np.sqrt(mask.sum()))}\n", "\n", "\n", "def feature_matrix(images):\n", " \"\"\"์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ, ํ˜•ํƒœ ํŠน์ง• ํ–‰๋ ฌ(N x 8), ์ •๊ทœํ™” ์ด๋ฏธ์ง€ ํ–‰๋ ฌ(N x 576)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " P = preprocess(images)\n", " F = np.array([[d[k] for k in FEATS] for d in map(shape_features, P[\"mask\"])])\n", " return P, F, P[\"norm\"].reshape(len(F), -1)\n", "\n", "# ---- ๋ถ„๋ฅ˜ ๋ชจ๋ธ ๊ณตํ†ต ํ•จ์ˆ˜(๋‹ค์ธต ํผ์…‰ํŠธ๋ก : ์ •๊ทœํ™” ์ด๋ฏธ์ง€ 576ํ”ฝ์…€ + ํ˜•ํƒœ ํŠน์ง• 8์ข…) ----\n", "def to_input(F, N, scaler):\n", " return np.c_[N, scaler.transform(F) * 0.3]\n", "\n", "\n", "def train_model(n_per_class=150, seed=5, conds=tuple(CONDITIONS), hidden=(64,)):\n", " X, y, _ = make_dataset(n_per_class, seed, conds)\n", " P, F, N = feature_matrix(X)\n", " scaler = StandardScaler().fit(F)\n", " model = MLPClassifier(hidden, max_iter=200, early_stopping=True, random_state=0)\n", " model.fit(to_input(F, N, scaler), [CLASSES.index(v) for v in y])\n", " return model, scaler\n", "\n", "\n", "def predict(model, scaler, images):\n", " \"\"\"(์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ, ํด๋ž˜์Šค๋ณ„ ํ™•๋ฅ  N x 6)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " P, F, N = feature_matrix(images)\n", " return P, model.predict_proba(to_input(F, N, scaler))\n", "\n", "# 1) ํ•™์Šต: ๋ชจ๋“  ์ดฌ์˜ ์กฐ๊ฑด์„ ์„ž์€ ์ฆ๊ฐ• ๋ฐ์ดํ„ฐ(ํด๋ž˜์Šค๋‹น 200์žฅ)\n", "t0 = time.perf_counter()\n", "model, scaler = train_model(200, seed=5, hidden=(96,))\n", "print(f\"์‹ ๊ฒฝ๋ง ํ•™์Šต ์™„๋ฃŒ ({time.perf_counter() - t0:.1f} s, ์ž…๋ ฅ 584์ฐจ์› = ์ •๊ทœํ™” ์ด๋ฏธ์ง€ 576 + ํ˜•ํƒœ ํŠน์ง• 8)\")\n", "Xb, yb, _ = make_dataset(60, 1)\n", "tree = DecisionTreeClassifier(max_depth=4, random_state=0).fit(feature_matrix(Xb)[1], yb) # ๋ฏธ์…˜ 3 Baseline\n", "\n", "# 2) ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹œ๋‚˜๋ฆฌ์˜ค 10์ข… ์‹œํ—˜(์‹œ๋‚˜๋ฆฌ์˜ค๋‹น ํด๋ž˜์Šค๋ณ„ 15์žฅ)\n", "SCEN = [k for k in CONDITIONS if k != \"basic\"]\n", "rows, all_true, all_pred = [], [], []\n", "for i, cond in enumerate(SCEN):\n", " X, y, _ = make_dataset(15, 100 + i, (cond,))\n", " t0 = time.perf_counter()\n", " P, prob = predict(model, scaler, X)\n", " ms = (time.perf_counter() - t0) * 1000 / len(X)\n", " pred = np.array(CLASSES)[prob.argmax(1)]\n", " thr = np.isin(y, list(THREAT))\n", " rec = np.isin(pred[thr], list(THREAT)).mean()\n", " rows.append({\"์‹œ๋‚˜๋ฆฌ์˜ค\": cond, \"Baseline ์ •ํ™•๋„\": (tree.predict(feature_matrix(X)[1]) == y).mean(), \"์‹ ๊ฒฝ๋ง ์ •ํ™•๋„\": (pred == y).mean(),\n", " \"์œ„ํ˜‘ ์žฌํ˜„์œจ\": rec, \"์žฅ๋‹น ms\": ms, \"ํ†ต๊ณผ\": \"PASS\" if rec >= 0.85 and ms <= 200 else \"FAIL\"})\n", " all_true += list(y); all_pred += list(pred)\n", "df = pd.DataFrame(rows)\n", "print(df.round(3).to_string(index=False))\n", "yt, yp = np.array(all_true), np.array(all_pred)\n", "tr = np.isin(yt, list(THREAT))\n", "recall = np.isin(yp[tr], list(THREAT)).mean()\n", "print(f\"\\n๋ณตํ•ฉ ์œ„ํ˜‘ ์žฌํ˜„์œจ {recall:.1%} (๋ชฉํ‘œ 85% ์ด์ƒ: {'๋‹ฌ์„ฑ' if recall >= 0.85 else '๋ฏธ๋‹ฌ'}), ์ตœ๋Œ€ ์ฒ˜๋ฆฌ ์ง€์—ฐ {df['์žฅ๋‹น ms'].max():.2f} ms (๋ชฉํ‘œ 200 ms ์ดํ•˜)\")\n", "print(f\"์‹œ๋‚˜๋ฆฌ์˜ค ํ†ต๊ณผ {int((df['ํ†ต๊ณผ'] == 'PASS').sum())}/10\")\n", "\n", "# 3) ์˜ค๊ฒฝ๋ณด ์œ ๋ฐœ ํŒจํ„ด: ๋น„์œ„ํ˜‘(๋ฏผ๊ฐ„๊ธฐยท์กฐ๋ฅ˜)์„ ์œ„ํ˜‘์œผ๋กœ ํŒ์ •ํ•œ ๊ฒฝ์šฐ\n", "fa = pd.Series([f\"{t} -> {p}\" for t, p in zip(yt, yp) if t not in THREAT and p in THREAT])\n", "print(\"\\n[์˜ค๊ฒฝ๋ณด ์œ ๋ฐœ ํŒจํ„ด ์ƒ์œ„]\", fa.value_counts().head(3).to_dict())\n", "miss = pd.Series([f\"{t} -> {p}\" for t, p in zip(yt, yp) if t in THREAT and p not in THREAT])\n", "print(\"[์œ„ํ˜‘ ๋ฏธํƒ ํŒจํ„ด ์ƒ์œ„]\", miss.value_counts().head(3).to_dict())\n", "print(\"ํŠน์ง• ๊ณต๊ฐ„ ์žฌ์„ค๊ณ„ ๋ฐฉํ–ฅ: ์›๊ฑฐ๋ฆฌยท๊ฒฐํ•ฉ ์กฐ๊ฑด์—์„œ ๋‚ ๊ฐœ ๋ ํ˜•์ƒ์ด ๋ญ‰๊ฐœ์ง€๋ฏ€๋กœ ํญ ๋ถ„ํฌ(end_gap, w_max) ์™ธ์— ์œค๊ณฝ์„  ๊ณก๋ฅ  ํŠน์ง• ์ถ”๊ฐ€๋ฅผ ๊ฒ€ํ† ํ•œ๋‹ค.\")\n", "\n", "plt.figure(figsize=(9, 4))\n", "x = np.arange(len(df))\n", "plt.bar(x - 0.2, df[\"Baseline ์ •ํ™•๋„\"], 0.4, label=\"baseline tree\")\n", "plt.bar(x + 0.2, df[\"์‹ ๊ฒฝ๋ง ์ •ํ™•๋„\"], 0.4, label=\"MLP (augmented)\")\n", "plt.axhline(0.85, ls=\"--\", c=\"gray\")\n", "plt.xticks(x, df[\"์‹œ๋‚˜๋ฆฌ์˜ค\"], rotation=30); plt.ylabel(\"accuracy\"); plt.legend()\n", "plt.title(\"Accuracy by complex scenario (synthetic)\"); plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 04 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "์‹ ๊ฒฝ๋ง ํ•™์Šต ์™„๋ฃŒ (3.3 s, ์ž…๋ ฅ 584์ฐจ์› = ์ •๊ทœํ™” ์ด๋ฏธ์ง€ 576 + ํ˜•ํƒœ ํŠน์ง• 8)\n", " ์‹œ๋‚˜๋ฆฌ์˜ค Baseline ์ •ํ™•๋„ ์‹ ๊ฒฝ๋ง ์ •ํ™•๋„ ์œ„ํ˜‘ ์žฌํ˜„์œจ ์žฅ๋‹น ms ํ†ต๊ณผ\n", "offcenter 0.767 1.000 1.000 1.239 PASS\n", " far 0.467 0.922 0.911 1.131 PASS\n", " blur 0.400 1.000 1.000 1.201 PASS\n", " lowcon 0.900 1.000 1.000 1.735 PASS\n", " clutter 0.556 0.700 0.711 1.153 FAIL\n", " occlude 0.556 0.789 0.844 1.234 FAIL\n", " oblique 0.333 0.978 1.000 1.544 PASS\n", " night 0.967 1.000 1.000 1.207 PASS\n", " noise 0.778 0.956 1.000 1.178 PASS\n", " combo 0.311 0.378 0.600 1.319 FAIL\n", "\n", "๋ณตํ•ฉ ์œ„ํ˜‘ ์žฌํ˜„์œจ 90.7% (๋ชฉํ‘œ 85% ์ด์ƒ: ๋‹ฌ์„ฑ), ์ตœ๋Œ€ ์ฒ˜๋ฆฌ ์ง€์—ฐ 1.73 ms (๋ชฉํ‘œ 200 ms ์ดํ•˜)\n", "์‹œ๋‚˜๋ฆฌ์˜ค ํ†ต๊ณผ 7/10\n", "\n", "[์˜ค๊ฒฝ๋ณด ์œ ๋ฐœ ํŒจํ„ด ์ƒ์œ„] {'bird -> cruise': 9, 'civil_fw -> cruise': 7, 'civil_quad -> cruise': 6}\n", "[์œ„ํ˜‘ ๋ฏธํƒ ํŒจํ„ด ์ƒ์œ„] {'cruise -> bird': 14, 'ballistic -> bird': 11, 'ballistic -> civil_fw': 8}\n", "ํŠน์ง• ๊ณต๊ฐ„ ์žฌ์„ค๊ณ„ ๋ฐฉํ–ฅ: ์›๊ฑฐ๋ฆฌยท๊ฒฐํ•ฉ ์กฐ๊ฑด์—์„œ ๋‚ ๊ฐœ ๋ ํ˜•์ƒ์ด ๋ญ‰๊ฐœ์ง€๋ฏ€๋กœ ํญ ๋ถ„ํฌ(end_gap, w_max) ์™ธ์— ์œค๊ณฝ์„  ๊ณก๋ฅ  ํŠน์ง• ์ถ”๊ฐ€๋ฅผ ๊ฒ€ํ† ํ•œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 05. ์œ ์‚ฌ ํ˜•์ƒ ๋น„ํ–‰์ฒด(๋ฏผ๊ฐ„ ๋“œ๋ก  vs ์žํญ ๋“œ๋ก ) ์˜ค๋ถ„๋ฅ˜ ๋ฐฉ์ง€ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์—”์ง„ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์œ ์‚ฌ ํ˜•์ƒ ๋น„ํ–‰์ฒด ์˜ค๋ถ„๋ฅ˜ ๋ฐฉ์ง€ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์‚ฐ์ถœ ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ฏผ๊ฐ„ ๋“œ๋ก ๊ณผ ์žํญ ๋“œ๋ก ์ฒ˜๋Ÿผ ํ˜•์ƒ์ด ๋น„์Šทํ•œ ๋น„ํ–‰์ฒด์˜ ์˜ค๋ถ„๋ฅ˜๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด ์‹ ๋ขฐ๋„ ์ ์ˆ˜์™€ ๋“ฑ๊ธ‰์„ ์‚ฐ์ถœํ•˜๋Š” ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋ถ„๋ฅ˜ ํ™•๋ฅ , 1ยท2์œ„ ํ™•๋ฅ  ์ฐจ์ด, ์˜์ƒ ํ’ˆ์งˆ(๋Œ€๋น„ยทํฌ๊ธฐยท์žก์Œ)์„ ๊ฐ€์ค‘ ๊ฒฐํ•ฉํ•ด ์‹ ๋ขฐ๋„ ์ ์ˆ˜๋ฅผ ๋งŒ๋“ค๊ณ  HighยทMediumยทLow ๋“ฑ๊ธ‰์„ ๋ถ€์—ฌ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/verification/trn03_confidence_engine.py` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์‹ ๋ขฐ๋„ ๋“ฑ๊ธ‰(High/Med/Low) ๋ถ€์—ฌ ๊ธฐ์ค€์˜ ๊ตฐ์‚ฌ์  ํƒ€๋‹น์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์˜ค๊ฒฝ๋ณด์œจ(FAR) 40% ์ด์ƒ ๊ฐ์ถ•, ์‹ ๋ขฐ๋„ ์Šค์ฝ”์–ด ์‚ฐ์ถœ ์ผ๊ด€์„ฑ 90% ์ด์ƒ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์˜ค๊ฒฝ๋ณด ํ•„ํ„ฐ๋ง ์ „ํ›„ ๋น„๊ต ์ง€ํ‘œ ์‚ฐ์ถœ ๋ฐ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์™„๋ฃŒ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ROC-AUC ๊ณก์„  ๋ถ„์„ ๋ฐ ์ž„๊ณ„๊ฐ’ ๋ฏผ๊ฐ๋„ ํ…Œ์ŠคํŠธ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๋ฏผ๊ฐ„ ๋“œ๋ก ๊ณผ ์žํญ ๋“œ๋ก ์ฒ˜๋Ÿผ ํ˜•์ƒ์ด ๋น„์Šทํ•œ ๋น„ํ–‰์ฒด์˜ ์˜ค๋ถ„๋ฅ˜๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด, ๋ถ„๋ฅ˜ ํ™•๋ฅ ยท1/2์œ„ ํ™•๋ฅ  ์ฐจ์ดยท์˜์ƒ ํ’ˆ์งˆ์„ ๊ฒฐํ•ฉํ•œ ์‹ ๋ขฐ๋„ ์ ์ˆ˜์™€ ๋“ฑ๊ธ‰(High/Medium/Low)์„ ์‚ฐ์ถœํ•˜๊ณ , ์˜ค๊ฒฝ๋ณด์œจ(FAR) ๊ฐ์ถ•ยท์ ์ˆ˜ ์ผ๊ด€์„ฑยทROC-AUCยท์ž„๊ณ„๊ฐ’ ๋ฏผ๊ฐ๋„๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from matplotlib.path import Path as MplPath\n", "from scipy import ndimage\n", "from scipy.spatial import ConvexHull\n", "from sklearn.metrics import roc_auc_score, roc_curve\n", "from sklearn.neural_network import MLPClassifier\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•ฉ์„ฑ ์‹ค๋ฃจ์—ฃ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ธฐ์ฒด ์‚ฌ์ง„ยท๋„๋ฉดยท์ œ์› ์‚ฌ์šฉ ์•ˆ ํ•จ)\n", "# ์‚ฌ๊ฐํ˜•ยท์‚ผ๊ฐํ˜•ยท์› ๊ฐ™์€ ๊ธฐ๋ณธ ๋„ํ˜•์„ ์กฐํ•ฉํ•œ ์ถ”์ƒ ์‹ค๋ฃจ์—ฃ์„ 32x32 ํ‘๋ฐฑ ์ด๋ฏธ์ง€๋กœ ๊ทธ๋ฆฐ๋‹ค.\n", "# ํšŒ์ „(๋‹ค๊ฐ๋„), ์›๊ทผ ์ถ•์†Œ(์ธก๋ฉด ์‹œ์ ), ํฌ๊ธฐ(์›๊ฑฐ๋ฆฌ), ํ๋ฆผยท์žก์Œยท์ €๋Œ€๋น„ยท๊ตฌ๋ฆ„ยท๊ฐ€๋ฆผยท์•ผ๊ฐ„ ๋ฐ˜์ „์„ ์กฐ์ ˆํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "IMG = 32\n", "CLASSES = [\"civil_quad\", \"civil_fw\", \"attack_fw\", \"cruise\", \"ballistic\", \"bird\"]\n", "KOR = {\"civil_quad\": \"๋ฏผ๊ฐ„ ์ฟผ๋“œ์ฝฅํ„ฐํ˜•\", \"civil_fw\": \"๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•\", \"attack_fw\": \"์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜•\",\n", " \"cruise\": \"์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜•\", \"ballistic\": \"ํƒ„๋„๋ฏธ์‚ฌ์ผํ˜•\", \"bird\": \"์กฐ๋ฅ˜\"}\n", "THREAT = {\"attack_fw\", \"cruise\", \"ballistic\"}\n", "\n", "\n", "def _rect(x0, x1, y0, y1):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] ์œ ์‚ฌ ํ˜•์ƒ ๋น„ํ–‰์ฒด(๋ฏผ๊ฐ„ ๋“œ๋ก  vs ์žํญ ๋“œ๋ก ) ์˜ค๋ถ„๋ฅ˜ ๋ฐฉ์ง€ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์—”์ง„ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def _bar(p, q, w):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] ์œ ์‚ฌ ํ˜•์ƒ ๋น„ํ–‰์ฒด(๋ฏผ๊ฐ„ ๋“œ๋ก  vs ์žํญ ๋“œ๋ก ) ์˜ค๋ถ„๋ฅ˜ ๋ฐฉ์ง€ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์—”์ง„ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "SHAPES = {\n", " \"civil_quad\": {\"poly\": [_bar((-.55, -.55), (.55, .55), .1), _bar((-.55, .55), (.55, -.55), .1), _rect(-.15, .15, -.15, .15)],\n", " \"circ\": [(-.55, -.55, .24), (.55, .55, .24), (-.55, .55, .24), (.55, -.55, .24)]},\n", " \"civil_fw\": {\"poly\": [_rect(-.07, .07, -.8, .72), _rect(-.9, .9, .05, .25), _rect(-.33, .33, -.8, -.66)], \"circ\": []},\n", " \"attack_fw\": {\"poly\": [[(0, .85), (.85, -.5), (.12, -.42), (0, -.6), (-.12, -.42), (-.85, -.5)]], \"circ\": []},\n", " \"cruise\": {\"poly\": [_rect(-.07, .07, -.9, .7), [(-.07, .7), (0, .92), (.07, .7)], _rect(-.42, .42, -.05, .07),\n", " [(-.07, -.9), (-.24, -.95), (-.07, -.74)], [(.07, -.9), (.24, -.95), (.07, -.74)]], \"circ\": []},\n", " \"ballistic\": {\"poly\": [_rect(-.13, .13, -.85, .6), [(-.13, .6), (0, .95), (.13, .6)],\n", " [(-.13, -.85), (-.34, -.95), (-.13, -.58)], [(.13, -.85), (.34, -.95), (.13, -.58)]], \"circ\": []},\n", " \"bird\": {\"poly\": [[(-.9, .12), (-.5, .28), (-.12, .06), (0, .22), (.12, .06), (.5, .28), (.9, .12), (.5, .12),\n", " (.12, -.1), (0, -.38), (-.12, -.1), (-.5, .12)]], \"circ\": []},\n", "}\n", "_S = 2\n", "_g = (np.arange(IMG * _S) + 0.5) / _S\n", "_GX, _GY = np.meshgrid(_g, _g)\n", "_PTS = np.c_[_GX.ravel(), _GY.ravel()]\n", "_PATHS = {c: [MplPath(np.array(p)) for p in s[\"poly\"]] for c, s in SHAPES.items()}\n", "\n", "\n", "def draw(cls, rng, angle=None, scale=None, fore=None, blur=0.3, noise=4.0, contrast=1.0,\n", " clutter=0.0, occlude=0.0, invert=False, shift=1.5):\n", " \"\"\"ํด๋ž˜์Šค 1๊ฐœ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€(uint8 32x32)๋ฅผ ๊ทธ๋ฆฐ๋‹ค. ๋ฐฐ๊ฒฝ์€ ๋ฐ์€ ํ•˜๋Š˜, ๋น„ํ–‰์ฒด๋Š” ์–ด๋‘์šด ๊ทธ๋ฆผ์ž.\"\"\"\n", " angle = rng.uniform(0, 360) if angle is None else angle\n", " scale = rng.uniform(.7, .95) if scale is None else scale\n", " fore = rng.uniform(.85, 1.0) if fore is None else fore\n", " th = np.radians(angle)\n", " c0 = IMG / 2 + rng.uniform(-shift, shift, 2)\n", " d = (_PTS - c0) / (scale * IMG / 2) # ์ด๋ฏธ์ง€ ์ขŒํ‘œ -> ๋„ํ˜• ์ขŒํ‘œ(์—ญ๋ณ€ํ™˜)\n", " u = d[:, 0] * np.cos(th) + d[:, 1] * np.sin(th)\n", " v = (-d[:, 0] * np.sin(th) + d[:, 1] * np.cos(th)) / fore # ์›๊ทผ ์ถ•์†Œ(๊ธฐ์ˆ˜ ๋ฐฉํ–ฅ์œผ๋กœ ์งง์•„ ๋ณด์ž„)\n", " uv = np.c_[u, -v]\n", " m = np.zeros(len(uv), bool)\n", " near = np.flatnonzero(np.abs(uv).max(axis=1) <= 1.0) # ๋„ํ˜•์ด ์žˆ์„ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ๋งŒ ๊ฒ€์‚ฌ(์†๋„)\n", " for p in _PATHS[cls]:\n", " m[near] |= p.contains_points(uv[near])\n", " for cx, cy, r in SHAPES[cls][\"circ\"]:\n", " m |= (uv[:, 0] - cx) ** 2 + (uv[:, 1] - cy) ** 2 <= r * r\n", " cov = m.reshape(IMG, _S, IMG, _S).mean(axis=(1, 3))\n", " if occlude > 0: # ๋ถ€๋ถ„ ๊ฐ€๋ฆผ(๊ตฌ๋ฆ„ ์กฐ๊ฐ)\n", " h = int(IMG * np.sqrt(occlude)); y0, x0 = rng.integers(8, IMG - 8 - h // 2, 2)\n", " cov[y0:y0 + h, x0:x0 + h] = 0\n", " bg = rng.uniform(180, 220) + np.linspace(-10, 10, IMG)[:, None]\n", " fg = rng.uniform(30, 60)\n", " img = bg * (1 - cov) + fg * cov\n", " if clutter > 0: # ๊ตฌ๋ฆ„ยท๋ฐฐ๊ฒฝ ์–ผ๋ฃฉ\n", " img -= clutter * 90 * np.clip(ndimage.gaussian_filter(rng.normal(0, 1, (IMG, IMG)), 3) * 4, 0, None)\n", " if blur > 0:\n", " img = ndimage.gaussian_filter(img, blur)\n", " mean = img.mean()\n", " img = mean + (img - mean) * contrast\n", " img = img + rng.normal(0, noise, img.shape)\n", " if invert: # ์•ผ๊ฐ„ ์—ด์ƒ(IR) ๊ทน์„ฑ ๋ฐ˜์ „\n", " img = 255 - img\n", " return np.clip(img, 0, 255).astype(np.uint8)\n", "\n", "\n", "# ์ดฌ์˜ ์กฐ๊ฑด ํ”„๋ฆฌ์…‹: ๊ธฐ๋ณธ + ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹œ๋‚˜๋ฆฌ์˜ค\n", "CONDITIONS = {\n", " \"basic\": {},\n", " \"offcenter\": {\"shift\": 5.0, \"fore\": (.6, .85)},\n", " \"far\": {\"scale\": (.32, .45), \"blur\": .6},\n", " \"blur\": {\"blur\": (1.2, 1.8)},\n", " \"lowcon\": {\"contrast\": (.2, .35)},\n", " \"clutter\": {\"clutter\": .8},\n", " \"occlude\": {\"occlude\": .08},\n", " \"oblique\": {\"fore\": (.35, .6)},\n", " \"night\": {\"invert\": True, \"noise\": 10.0},\n", " \"noise\": {\"noise\": 28.0},\n", " \"combo\": {\"scale\": (.4, .55), \"blur\": (.8, 1.1), \"noise\": 12.0, \"contrast\": (.6, .8)},\n", "}\n", "\n", "\n", "def draw_cond(cls, cond, rng):\n", " kw = {}\n", " for k, v in CONDITIONS[cond].items():\n", " kw[k] = rng.uniform(*v) if isinstance(v, tuple) else v\n", " return draw(cls, rng, **kw)\n", "\n", "\n", "def make_dataset(n_per_class, seed, conds=(\"basic\",)):\n", " \"\"\"(์ด๋ฏธ์ง€ ๋ฐฐ์—ด N x 32 x 32, ์ •๋‹ต ๋ผ๋ฒจ, ์กฐ๊ฑด) ๋ฐ˜ํ™˜. ์กฐ๊ฑด์€ conds์—์„œ ๋ฒˆ๊ฐˆ์•„ ๊ณ ๋ฅธ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " X, y, c = [], [], []\n", " for cls in CLASSES:\n", " for i in range(n_per_class):\n", " cond = conds[i % len(conds)]\n", " X.append(draw_cond(cls, cond, rng)); y.append(cls); c.append(cond)\n", " return np.array(X), np.array(y), np.array(c)\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---- ์ „์ฒ˜๋ฆฌ(๋ฐฐ์น˜ ๋ฒกํ„ฐํ™”)ยทํ˜•ํƒœ ํŠน์ง• ๊ณตํ†ต ํ•จ์ˆ˜ ----\n", "OUT = 24 # ํฌ๊ธฐ ์ •๊ทœํ™” ๊ฒฐ๊ณผ(24x24)\n", "NOISE_T = 12.0 # ์žก์Œ ์ถ”์ •๊ฐ’์ด ์ด๋ณด๋‹ค ํฌ๋ฉด 3x3 ์ค‘๊ฐ„๊ฐ’ ํ•„ํ„ฐ ์ ์šฉ(์•…์ฒœํ›„ยท์„ผ์„œ ์žก์Œ)\n", "FEATS = [\"elong\", \"fill\", \"solidity\", \"n_parts\", \"end_gap\", \"mid_bulge\", \"w_max\", \"compact\"]\n", "\n", "\n", "def preprocess(images, noise_t=NOISE_T):\n", " \"\"\"N์žฅ ์ผ๊ด„ ์ฒ˜๋ฆฌ: ๊ฒฐ์ธก ๋ณด์ • -> ๊ทน์„ฑ(์•ผ๊ฐ„ ๋ฐ˜์ „) ๋ณด์ • -> ์กฐ๊ฑด๋ณ„ ์žก์Œ ์ œ๊ฑฐ -> ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™” -> ๋ถ„ํ•  -> ์œค๊ณฝ์„  -> ๋ฐฉํ–ฅยทํฌ๊ธฐ ์ •๊ทœํ™”.\"\"\"\n", " f = np.array(images, dtype=float).reshape(-1, IMG, IMG)\n", " n = len(f)\n", " bad = ~np.isfinite(f)\n", " missing = bad.sum(axis=(1, 2))\n", " if bad.any(): # ๊ฒฐ์ธก ํ”ฝ์…€: ์˜์ƒ ์ค‘์•™๊ฐ’์œผ๋กœ ์ฑ„์šด ๋’ค ์ฃผ๋ณ€ 3x3 ์ค‘๊ฐ„๊ฐ’์œผ๋กœ ๋ณด์ •\n", " med = np.array([np.median(x[np.isfinite(x)]) if np.isfinite(x).any() else 128.0 for x in f])\n", " f = np.where(bad, med[:, None, None], f)\n", " f = np.where(bad, ndimage.median_filter(f, size=(1, 3, 3)), f)\n", " border = np.concatenate([f[:, 0], f[:, -1], f[:, :, 0], f[:, :, -1]], axis=1)\n", " inverted = np.median(border, axis=1) < 128 # ๋ฐฐ๊ฒฝ์ด ์–ด๋‘์šฐ๋ฉด ์•ผ๊ฐ„ ์—ด์ƒ์œผ๋กœ ๋ณด๊ณ  ๋ฐ˜์ „\n", " f[inverted] = 255 - f[inverted]\n", " med3 = ndimage.median_filter(f, size=(1, 3, 3))\n", " noise = 1.4826 * np.median(np.abs(f - med3).reshape(n, -1), axis=1)\n", " noisy = noise > noise_t\n", " f[noisy] = med3[noisy]\n", " lo, hi = np.percentile(f.reshape(n, -1), [1, 99], axis=1)\n", " e = np.clip((f - lo[:, None, None]) / np.maximum(hi - lo, 1e-6)[:, None, None], 0, 1) # ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”\n", " mask = e < _otsu(e)[:, None, None]\n", " mask &= ndimage.uniform_filter(mask.astype(float), size=(1, 3, 3)) * 9 >= 2.5 # ๊ณ ๋ฆฝ ์  ์žก์Œ ์ œ๊ฑฐ\n", " edge = np.hypot(ndimage.sobel(e, axis=1), ndimage.sobel(e, axis=2)) # ์œค๊ณฝ์„ (Sobel)\n", " ok = (mask.sum(axis=(1, 2)) >= 4) & (hi - lo > 8)\n", " return {\"enh\": e, \"mask\": mask, \"edge\": edge / np.maximum(edge.max(axis=(1, 2)), 1e-6)[:, None, None],\n", " \"norm\": _normalize(1 - e, mask, ok), \"inverted\": inverted, \"noise\": noise, \"denoised\": noisy,\n", " \"missing\": missing, \"contrast\": hi - lo, \"ok\": ok}\n", "\n", "\n", "def _otsu(e, bins=64):\n", " n = len(e)\n", " idx = np.minimum((e * bins).astype(int), bins - 1) + bins * np.arange(n)[:, None, None]\n", " hist = np.bincount(idx.ravel(), minlength=bins * n).reshape(n, bins).astype(float)\n", " centers = (np.arange(bins) + 0.5) / bins\n", " w = np.cumsum(hist, axis=1); m = np.cumsum(hist * centers, axis=1)\n", " tot, mt = w[:, -1:], m[:, -1:]\n", " between = (mt * w / tot - m) ** 2 / np.maximum(w * (tot - w), 1)\n", " return (np.argmax(between, axis=1) + 1) / bins\n", "\n", "\n", "def _normalize(obj, mask, ok):\n", " \"\"\"์ฃผ์ถ• ๋ฐฉํ–ฅ์„ ์„ธ๋กœ๋กœ ๋Œ๋ฆฌ๊ณ  ๋น„ํ–‰์ฒด ์˜์—ญ๋งŒ 24x24๋กœ ๋‹ค์‹œ ์ƒ˜ํ”Œ๋งํ•œ๋‹ค(ํšŒ์ „ยทํฌ๊ธฐยท์œ„์น˜ ๋ถˆ๋ณ€). ํ•œ ๋ฒˆ์˜ ๋ณด๊ฐ„์œผ๋กœ ์ผ๊ด„ ์ฒ˜๋ฆฌ.\"\"\"\n", " n = len(obj)\n", " w = mask.astype(float)\n", " m00 = np.maximum(w.sum(axis=(1, 2)), 1)\n", " yy, xx = np.mgrid[0:IMG, 0:IMG].astype(float)\n", " cy, cx = (w * yy).sum(axis=(1, 2)) / m00, (w * xx).sum(axis=(1, 2)) / m00\n", " dy, dx = yy - cy[:, None, None], xx - cx[:, None, None]\n", " mu20, mu02, mu11 = (w * dy * dy).sum(axis=(1, 2)), (w * dx * dx).sum(axis=(1, 2)), (w * dy * dx).sum(axis=(1, 2))\n", " th = 0.5 * np.arctan2(2 * mu11, mu20 - mu02)\n", " a = np.stack([np.cos(th), np.sin(th)], 1) # ์ฃผ์ถ•(y, x)\n", " b = np.stack([-np.sin(th), np.cos(th)], 1) # ๋ถ€์ถ•\n", " pa = np.where(mask, dy * a[:, 0, None, None] + dx * a[:, 1, None, None], 0)\n", " pb = np.where(mask, dy * b[:, 0, None, None] + dx * b[:, 1, None, None], 0)\n", " half = np.maximum(np.abs(pa).max(axis=(1, 2)), np.abs(pb).max(axis=(1, 2))) + 1.0\n", " g = np.linspace(-1, 1, OUT)\n", " U, V = np.meshgrid(g, g, indexing=\"ij\")\n", " Y = cy[:, None, None] + half[:, None, None] * (U * a[:, 0, None, None] + V * b[:, 0, None, None])\n", " X = cx[:, None, None] + half[:, None, None] * (U * a[:, 1, None, None] + V * b[:, 1, None, None])\n", " N = np.broadcast_to(np.arange(n)[:, None, None], Y.shape)\n", " out = ndimage.map_coordinates(obj * mask, [N, Y, X], order=1, cval=0.0)\n", " skew = (out * (U ** 3)).sum(axis=(1, 2))\n", " out[skew < 0] = out[skew < 0, ::-1] # ๋ฌด๊ฒŒ๊ฐ€ ์ ๋ฆฐ ์ชฝ์„ ์œ„๋กœ(180๋„ ๋ชจํ˜ธ์„ฑ ์ œ๊ฑฐ)\n", " out[~ok] = 0\n", " return np.clip(out, 0, 1)\n", "\n", "\n", "def shape_features(mask):\n", " \"\"\"๋ถ„ํ•  ๋งˆ์Šคํฌ 1์žฅ์—์„œ ํšŒ์ „์— ๋ฌด๊ด€ํ•œ ํ˜•ํƒœ ํŠน์ง• 8์ข…์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " if mask.sum() < 4:\n", " return dict.fromkeys(FEATS, 0.0)\n", " yx = np.argwhere(mask).astype(float)\n", " c = yx - yx.mean(axis=0)\n", " val, vec = np.linalg.eigh(np.cov(c.T) + 1e-6 * np.eye(2))\n", " t, s = c @ vec[:, 1], c @ vec[:, 0]\n", " length, width = np.ptp(t) + 1, np.ptp(s) + 1\n", " corners = np.vstack([yx + d for d in ([.5, .5], [.5, -.5], [-.5, .5], [-.5, -.5])])\n", " try:\n", " hull = ConvexHull(corners).volume\n", " except Exception:\n", " hull = float(mask.sum())\n", " bins = np.digitize(t, np.linspace(t.min(), t.max() + 1e-6, 4)[1:-1])\n", " w = np.array([np.ptp(s[bins == k]) + 1 if (bins == k).any() else 0 for k in range(3)]) / length\n", " boundary = mask & ~ndimage.binary_erosion(mask)\n", " return {\"elong\": float(np.sqrt(val[1] / max(val[0], 1e-6))), \"fill\": float(mask.sum() / (length * width)),\n", " \"solidity\": float(mask.sum() / max(hull, 1)), \"n_parts\": float(ndimage.label(mask)[1]),\n", " \"end_gap\": float(abs(w[0] - w[2])), \"mid_bulge\": float(w[1] - max(w[0], w[2])),\n", " \"w_max\": float(w.max()), \"compact\": float(boundary.sum() / np.sqrt(mask.sum()))}\n", "\n", "\n", "def feature_matrix(images):\n", " \"\"\"์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ, ํ˜•ํƒœ ํŠน์ง• ํ–‰๋ ฌ(N x 8), ์ •๊ทœํ™” ์ด๋ฏธ์ง€ ํ–‰๋ ฌ(N x 576)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " P = preprocess(images)\n", " F = np.array([[d[k] for k in FEATS] for d in map(shape_features, P[\"mask\"])])\n", " return P, F, P[\"norm\"].reshape(len(F), -1)\n", "\n", "# ---- ๋ถ„๋ฅ˜ ๋ชจ๋ธ ๊ณตํ†ต ํ•จ์ˆ˜(๋‹ค์ธต ํผ์…‰ํŠธ๋ก : ์ •๊ทœํ™” ์ด๋ฏธ์ง€ 576ํ”ฝ์…€ + ํ˜•ํƒœ ํŠน์ง• 8์ข…) ----\n", "def to_input(F, N, scaler):\n", " return np.c_[N, scaler.transform(F) * 0.3]\n", "\n", "\n", "def train_model(n_per_class=150, seed=5, conds=tuple(CONDITIONS), hidden=(64,)):\n", " X, y, _ = make_dataset(n_per_class, seed, conds)\n", " P, F, N = feature_matrix(X)\n", " scaler = StandardScaler().fit(F)\n", " model = MLPClassifier(hidden, max_iter=200, early_stopping=True, random_state=0)\n", " model.fit(to_input(F, N, scaler), [CLASSES.index(v) for v in y])\n", " return model, scaler\n", "\n", "\n", "def predict(model, scaler, images):\n", " \"\"\"(์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ, ํด๋ž˜์Šค๋ณ„ ํ™•๋ฅ  N x 6)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " P, F, N = feature_matrix(images)\n", " return P, model.predict_proba(to_input(F, N, scaler))\n", "\n", "WEIGHTS = {\"p_max\": 0.6, \"margin\": 0.4} # ์‹ ๋ขฐ๋„ ๊ฐ€์ค‘์น˜ ๋งคํŠธ๋ฆญ์Šค(ํ™•๋ฅ  ๋Œ€ ํ™•๋ฅ  ์ฐจ์ด)\n", "HIGH, MEDIUM = 0.8, 0.5\n", "\n", "\n", "def quality(P):\n", " \"\"\"์˜์ƒ ํ’ˆ์งˆ 0.5~1: ๋Œ€๋น„๊ฐ€ ๋‚ฎ๊ฑฐ๋‚˜, ํ‘œ์ ์ด ์ž‘๊ฑฐ๋‚˜, ์žก์Œ์ด ํฌ๋ฉด ๋‚ฎ์•„์ง„๋‹ค.\"\"\"\n", " size = P[\"mask\"].sum(axis=(1, 2))\n", " q = np.clip(P[\"contrast\"] / 120, 0, 1) * 0.4 + np.clip(size / 60, 0, 1) * 0.4 + np.clip(1 - P[\"noise\"] / 40, 0, 1) * 0.2\n", " return 0.5 + 0.5 * q\n", "\n", "\n", "def confidence(P, prob):\n", " s = np.sort(prob, axis=1)\n", " score = (WEIGHTS[\"p_max\"] * s[:, -1] + WEIGHTS[\"margin\"] * (s[:, -1] - s[:, -2])) * quality(P)\n", " grade = np.where(score >= HIGH, \"High\", np.where(score >= MEDIUM, \"Medium\", \"Low\"))\n", " return score, grade\n", "\n", "\n", "def report(name, alert, y):\n", " true_thr = np.isin(y, list(THREAT))\n", " far = (alert & ~true_thr).sum() / max(alert.sum(), 1)\n", " return {\"๋ฐฉ์‹\": name, \"์œ„ํ˜‘ ๊ฒฝ๋ณด\": int(alert.sum()), \"์˜ค๊ฒฝ๋ณด\": int((alert & ~true_thr).sum()), \"์˜ค๊ฒฝ๋ณด์œจ\": round(far, 3),\n", " \"์œ„ํ˜‘ ์žฌํ˜„์œจ\": round((alert & true_thr).sum() / true_thr.sum(), 3)}\n", "\n", "\n", "model, scaler = train_model(200, seed=5, hidden=(96,))\n", "X, y, cond = make_dataset(110, 21, tuple(CONDITIONS)) # ๊ฒ€์ฆ์šฉ 660์žฅ(ํ•™์Šต์— ์“ฐ์ง€ ์•Š์Œ)\n", "P, prob = predict(model, scaler, X)\n", "pred = np.array(CLASSES)[prob.argmax(1)]\n", "score, grade = confidence(P, prob)\n", "is_thr_pred = np.isin(pred, list(THREAT))\n", "\n", "before = report(\"๊ธฐ์กด(์ตœ๊ณ  ํ™•๋ฅ  ํด๋ž˜์Šค๋กœ ๋‹จ์ •)\", is_thr_pred, y)\n", "after = report(\"์‹ ๋ขฐ๋„ ์—”์ง„(Low๋Š” ๊ต๊ด€ ํ™•์ธ)\", is_thr_pred & (grade != \"Low\"), y)\n", "print(pd.DataFrame([before, after]).to_string(index=False))\n", "cut = 1 - after[\"์˜ค๊ฒฝ๋ณด์œจ\"] / max(before[\"์˜ค๊ฒฝ๋ณด์œจ\"], 1e-9)\n", "print(f\"\\n์˜ค๊ฒฝ๋ณด์œจ ๊ฐ์ถ• {cut:.1%} (๋ชฉํ‘œ 40% ์ด์ƒ: {'๋‹ฌ์„ฑ' if cut >= 0.4 else '๋ฏธ๋‹ฌ'}), ๊ต๊ด€ ํ™•์ธ์œผ๋กœ ๋„˜๊ธด ์œ„ํ˜‘ ํŒ์ • {int((is_thr_pred & (grade == 'Low')).sum())}๊ฑด\")\n", "\n", "# ์ผ๊ด€์„ฑ: ๊ฐ™์€ ์ด๋ฏธ์ง€์— ๋ฏธ์„ธ ์žก์Œ(๋ฐ๊ธฐ ยฑ1)์„ ๋”ํ•ด ๋‹ค์‹œ ๊ณ„์‚ฐํ–ˆ์„ ๋•Œ ๊ฐ™์€ ๋“ฑ๊ธ‰์ด ๋‚˜์˜ค๋Š” ๋น„์œจ\n", "rng = np.random.default_rng(0)\n", "P2, prob2 = predict(model, scaler, np.clip(X + rng.integers(-1, 2, X.shape), 0, 255))\n", "cons = (confidence(P2, prob2)[1] == grade).mean()\n", "print(f\"์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์‚ฐ์ถœ ์ผ๊ด€์„ฑ {cons:.1%} (๋ชฉํ‘œ 90% ์ด์ƒ: {'๋‹ฌ์„ฑ' if cons >= 0.9 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "p_threat = prob[:, [CLASSES.index(c) for c in THREAT]].sum(1)\n", "auc = roc_auc_score(np.isin(y, list(THREAT)), p_threat)\n", "print(f\"์œ„ํ˜‘ ํŒ๋ณ„ ROC-AUC {auc:.3f}\")\n", "\n", "print(\"\\n[์ž„๊ณ„๊ฐ’ ๋ฏผ๊ฐ๋„: Medium ๊ธฐ์ค€๊ฐ’๋ณ„]\")\n", "for m in [0.3, 0.4, 0.5, 0.6, 0.7]:\n", " r = report(f\"Medium>={m}\", is_thr_pred & (score >= m), y)\n", " print(f\"๊ธฐ์ค€ {m}: ์˜ค๊ฒฝ๋ณด์œจ {r['์˜ค๊ฒฝ๋ณด์œจ']:.3f}, ์œ„ํ˜‘ ์žฌํ˜„์œจ {r['์œ„ํ˜‘ ์žฌํ˜„์œจ']:.3f}\")\n", "\n", "print(\"\\n[๋“ฑ๊ธ‰ ๋ถ„ํฌ: ์‹ค์ œ ํด๋ž˜์Šค๋ณ„]\")\n", "print(pd.crosstab(pd.Series(y, name=\"์‹ค์ œ\"), pd.Series(grade, name=\"๋“ฑ๊ธ‰\")))\n", "pair = (y == \"civil_fw\") | (y == \"attack_fw\") | (y == \"civil_quad\")\n", "print(f\"๋ฏผ๊ฐ„ ๋“œ๋ก  vs ์žํญ ๋“œ๋ก  ์Œ ์ •ํ™•๋„ {(pred[pair] == y[pair]).mean():.1%}, ํ‰๊ท  ์‹ ๋ขฐ๋„ {score[pair].mean():.2f}\")\n", "print(\"๋“ฑ๊ธ‰์€ ๊ต์œก์šฉ ํŒ๋‹จ ๋ณด์กฐ์ด๋ฉฐ, Low ๋“ฑ๊ธ‰์€ ์ •๋‹ต์„ ๋‹จ์ •ํ•˜์ง€ ์•Š๊ณ  ๊ต๊ด€ ํ™•์ธ์œผ๋กœ ๋„˜๊ธด๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n", "fpr, tpr, _ = roc_curve(np.isin(y, list(THREAT)), p_threat)\n", "ax[0].plot(fpr, tpr); ax[0].plot([0, 1], [0, 1], \"k--\"); ax[0].set_title(f\"Threat ROC (AUC={auc:.3f})\")\n", "ax[0].set_xlabel(\"false positive rate\"); ax[0].set_ylabel(\"true positive rate\")\n", "for g, col in [(\"High\", \"tab:green\"), (\"Medium\", \"tab:orange\"), (\"Low\", \"tab:red\")]:\n", " ax[1].hist(score[grade == g], bins=15, range=(0, 1), color=col, alpha=0.7, label=g)\n", "ax[1].set_title(\"Confidence score (uncertainty view)\"); ax[1].set_xlabel(\"score\"); ax[1].legend()\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 05 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", " ๋ฐฉ์‹ ์œ„ํ˜‘ ๊ฒฝ๋ณด ์˜ค๊ฒฝ๋ณด ์˜ค๊ฒฝ๋ณด์œจ ์œ„ํ˜‘ ์žฌํ˜„์œจ\n", " ๊ธฐ์กด(์ตœ๊ณ  ํ™•๋ฅ  ํด๋ž˜์Šค๋กœ ๋‹จ์ •) 334 29 0.087 0.924\n", "์‹ ๋ขฐ๋„ ์—”์ง„(Low๋Š” ๊ต๊ด€ ํ™•์ธ) 281 5 0.018 0.836\n", "\n", "์˜ค๊ฒฝ๋ณด์œจ ๊ฐ์ถ• 79.3% (๋ชฉํ‘œ 40% ์ด์ƒ: ๋‹ฌ์„ฑ), ๊ต๊ด€ ํ™•์ธ์œผ๋กœ ๋„˜๊ธด ์œ„ํ˜‘ ํŒ์ • 53๊ฑด\n", "์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์‚ฐ์ถœ ์ผ๊ด€์„ฑ 97.3% (๋ชฉํ‘œ 90% ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "์œ„ํ˜‘ ํŒ๋ณ„ ROC-AUC 0.983\n", "\n", "[์ž„๊ณ„๊ฐ’ ๋ฏผ๊ฐ๋„: Medium ๊ธฐ์ค€๊ฐ’๋ณ„]\n", "๊ธฐ์ค€ 0.3: ์˜ค๊ฒฝ๋ณด์œจ 0.054, ์œ„ํ˜‘ ์žฌํ˜„์œจ 0.894\n", "๊ธฐ์ค€ 0.4: ์˜ค๊ฒฝ๋ณด์œจ 0.024, ์œ„ํ˜‘ ์žฌํ˜„์œจ 0.867\n", "๊ธฐ์ค€ 0.5: ์˜ค๊ฒฝ๋ณด์œจ 0.018, ์œ„ํ˜‘ ์žฌํ˜„์œจ 0.836\n", "๊ธฐ์ค€ 0.6: ์˜ค๊ฒฝ๋ณด์œจ 0.011, ์œ„ํ˜‘ ์žฌํ˜„์œจ 0.809\n", "๊ธฐ์ค€ 0.7: ์˜ค๊ฒฝ๋ณด์œจ 0.012, ์œ„ํ˜‘ ์žฌํ˜„์œจ 0.779\n", "\n", "[๋“ฑ๊ธ‰ ๋ถ„ํฌ: ์‹ค์ œ ํด๋ž˜์Šค๋ณ„]\n", "๋“ฑ๊ธ‰ High Low Medium\n", "์‹ค์ œ \n", "attack_fw 99 5 6\n", "ballistic 72 18 20\n", "bird 48 29 33\n", "civil_fw 73 18 19\n", "civil_quad 90 13 7\n", "cruise 66 27 17\n", "๋ฏผ๊ฐ„ ๋“œ๋ก  vs ์žํญ ๋“œ๋ก  ์Œ ์ •ํ™•๋„ 91.2%, ํ‰๊ท  ์‹ ๋ขฐ๋„ 0.86\n", "๋“ฑ๊ธ‰์€ ๊ต์œก์šฉ ํŒ๋‹จ ๋ณด์กฐ์ด๋ฉฐ, Low ๋“ฑ๊ธ‰์€ ์ •๋‹ต์„ ๋‹จ์ •ํ•˜์ง€ ์•Š๊ณ  ๊ต๊ด€ ํ™•์ธ์œผ๋กœ ๋„˜๊ธด๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 06. ์žฅ๋ณ‘ ๋Œ€์ƒ ๋ชจ๋ฐ”์ผ/์›น ๋Œ€ํ™”ํ˜• ์‹ค๋ฃจ์—ฃ ์‹๋ณ„ ํ•™์Šต ์•ฑ UI ํ”„๋กœํ† ํƒ€์ž… ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์žฅ๋ณ‘ ๋Œ€์ƒ ๋ชจ๋ฐ”์ผ/์›น ๋Œ€ํ™”ํ˜• ์‹ค๋ฃจ์—ฃ ์‹๋ณ„ ํ•™์Šต ์•ฑ UI ํ”„๋กœํ† ํƒ€์ž…์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์žฅ๋ณ‘์ด ์‹ค๋ฃจ์—ฃ์„ ๋ณด๊ณ  ๋น„ํ–‰์ฒด๋ฅผ ๊ณ ๋ฅด๋Š” ๋ชจ๋ฐ”์ผยท์›น ๋Œ€ํ™”ํ˜• ์‹๋ณ„ ํ•™์Šต ์•ฑ UI ํ”„๋กœํ† ํƒ€์ž…์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋ฌธํ•ญ ์ถœ์ œ(์ •๋‹ต๊ณผ ํ˜ผ๋™ํ•˜๊ธฐ ์‰ฌ์šด ๋ณด๊ธฐ ํฌํ•จ), ์‘๋‹ต ์ฑ„์ , ์ฆ‰์‹œ ํ”ผ๋“œ๋ฐฑ, ์‘๋‹ต ํ›„ AI ์ฐธ๊ณ  ํŒ์ • ํ‘œ์‹œ, ์ง„ํ–‰๋ฅ ยท์ ์ˆ˜ ํ‘œ์‹œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ณ  ๊ฐ€์ƒ ํ•™์Šต์ž ์‘๋‹ต์œผ๋กœ 12๋ฌธํ•ญ์„ ๋ชจ์˜ ์‹คํ–‰ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/ui/trn03_dashboard.py (๋˜๋Š” ์›น UI ์ปดํฌ๋„ŒํŠธ)` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์ „์ˆ  ์ž‘์ „ ์ƒํ™ฉํŒ์œผ๋กœ์„œ์˜ ์ง๊ด€์„ฑ ๋ฐ ์ธ์ง€ ๋ถ€ํ•˜ ๊ฐ์†Œ๋„\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ํ™”๋ฉด ๊ฐฑ์‹  ์ฃผ๊ธฐ 1์ดˆ ์ด๋‚ด, UI ์‘๋‹ต์„ฑ 100ms ์ด๋‚ด\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์ฃผ์š” ์ง€ํ‘œ ์‹œ๊ฐํ™” ์ปดํฌ๋„ŒํŠธ 4์ข… ์ด์ƒ ๋ Œ๋”๋ง ๋ฐ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์—ฐ๋™\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ์‚ฌ์šฉ์ž ์ธํ„ฐ๋ž™์…˜ ํ…Œ์ŠคํŠธ ๋ฐ ๋ชจ์˜ ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆผ ๋ Œ๋”๋ง ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์žฅ๋ณ‘์ด ์‹ค๋ฃจ์—ฃ์„ ๋ณด๊ณ  ๋น„ํ–‰์ฒด๋ฅผ ๊ณ ๋ฅด๋Š” ๋Œ€ํ™”ํ˜• ์‹๋ณ„ ํ•™์Šต ์•ฑ UI ํ”„๋กœํ† ํƒ€์ž…์„ ๊ตฌํ˜„ํ•œ๋‹ค. ๋ฌธ์ œ ์ถœ์ œยท์‘๋‹ต ์ฒ˜๋ฆฌยท์ฆ‰์‹œ ํ”ผ๋“œ๋ฐฑยทAI ์ฐธ๊ณ  ํŒ์ •ยท์ง„ํ–‰๋ฅ  ํ‘œ์‹œ๋ฅผ ๋ชจ์˜ ์‹คํ–‰ํ•ด ํ™”๋ฉด ๊ฐฑ์‹  ์ฃผ๊ธฐ์™€ ์‘๋‹ต ์ฒ˜๋ฆฌ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๊ณ , ์•ผ๊ฐ„ ๋ชจ๋“œ ํ•™์Šต ํ™”๋ฉด์„ ๊ทธ๋ฆฐ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from matplotlib.path import Path as MplPath\n", "from scipy import ndimage\n", "from scipy.spatial import ConvexHull\n", "from sklearn.neural_network import MLPClassifier\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•ฉ์„ฑ ์‹ค๋ฃจ์—ฃ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ธฐ์ฒด ์‚ฌ์ง„ยท๋„๋ฉดยท์ œ์› ์‚ฌ์šฉ ์•ˆ ํ•จ)\n", "# ์‚ฌ๊ฐํ˜•ยท์‚ผ๊ฐํ˜•ยท์› ๊ฐ™์€ ๊ธฐ๋ณธ ๋„ํ˜•์„ ์กฐํ•ฉํ•œ ์ถ”์ƒ ์‹ค๋ฃจ์—ฃ์„ 32x32 ํ‘๋ฐฑ ์ด๋ฏธ์ง€๋กœ ๊ทธ๋ฆฐ๋‹ค.\n", "# ํšŒ์ „(๋‹ค๊ฐ๋„), ์›๊ทผ ์ถ•์†Œ(์ธก๋ฉด ์‹œ์ ), ํฌ๊ธฐ(์›๊ฑฐ๋ฆฌ), ํ๋ฆผยท์žก์Œยท์ €๋Œ€๋น„ยท๊ตฌ๋ฆ„ยท๊ฐ€๋ฆผยท์•ผ๊ฐ„ ๋ฐ˜์ „์„ ์กฐ์ ˆํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "IMG = 32\n", "CLASSES = [\"civil_quad\", \"civil_fw\", \"attack_fw\", \"cruise\", \"ballistic\", \"bird\"]\n", "KOR = {\"civil_quad\": \"๋ฏผ๊ฐ„ ์ฟผ๋“œ์ฝฅํ„ฐํ˜•\", \"civil_fw\": \"๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•\", \"attack_fw\": \"์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜•\",\n", " \"cruise\": \"์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜•\", \"ballistic\": \"ํƒ„๋„๋ฏธ์‚ฌ์ผํ˜•\", \"bird\": \"์กฐ๋ฅ˜\"}\n", "THREAT = {\"attack_fw\", \"cruise\", \"ballistic\"}\n", "\n", "\n", "def _rect(x0, x1, y0, y1):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ์žฅ๋ณ‘ ๋Œ€์ƒ ๋ชจ๋ฐ”์ผ/์›น ๋Œ€ํ™”ํ˜• ์‹ค๋ฃจ์—ฃ ์‹๋ณ„ ํ•™์Šต ์•ฑ UI ํ”„๋กœํ† ํƒ€์ž… ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def _bar(p, q, w):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ์žฅ๋ณ‘ ๋Œ€์ƒ ๋ชจ๋ฐ”์ผ/์›น ๋Œ€ํ™”ํ˜• ์‹ค๋ฃจ์—ฃ ์‹๋ณ„ ํ•™์Šต ์•ฑ UI ํ”„๋กœํ† ํƒ€์ž… ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "SHAPES = {\n", " \"civil_quad\": {\"poly\": [_bar((-.55, -.55), (.55, .55), .1), _bar((-.55, .55), (.55, -.55), .1), _rect(-.15, .15, -.15, .15)],\n", " \"circ\": [(-.55, -.55, .24), (.55, .55, .24), (-.55, .55, .24), (.55, -.55, .24)]},\n", " \"civil_fw\": {\"poly\": [_rect(-.07, .07, -.8, .72), _rect(-.9, .9, .05, .25), _rect(-.33, .33, -.8, -.66)], \"circ\": []},\n", " \"attack_fw\": {\"poly\": [[(0, .85), (.85, -.5), (.12, -.42), (0, -.6), (-.12, -.42), (-.85, -.5)]], \"circ\": []},\n", " \"cruise\": {\"poly\": [_rect(-.07, .07, -.9, .7), [(-.07, .7), (0, .92), (.07, .7)], _rect(-.42, .42, -.05, .07),\n", " [(-.07, -.9), (-.24, -.95), (-.07, -.74)], [(.07, -.9), (.24, -.95), (.07, -.74)]], \"circ\": []},\n", " \"ballistic\": {\"poly\": [_rect(-.13, .13, -.85, .6), [(-.13, .6), (0, .95), (.13, .6)],\n", " [(-.13, -.85), (-.34, -.95), (-.13, -.58)], [(.13, -.85), (.34, -.95), (.13, -.58)]], \"circ\": []},\n", " \"bird\": {\"poly\": [[(-.9, .12), (-.5, .28), (-.12, .06), (0, .22), (.12, .06), (.5, .28), (.9, .12), (.5, .12),\n", " (.12, -.1), (0, -.38), (-.12, -.1), (-.5, .12)]], \"circ\": []},\n", "}\n", "_S = 2\n", "_g = (np.arange(IMG * _S) + 0.5) / _S\n", "_GX, _GY = np.meshgrid(_g, _g)\n", "_PTS = np.c_[_GX.ravel(), _GY.ravel()]\n", "_PATHS = {c: [MplPath(np.array(p)) for p in s[\"poly\"]] for c, s in SHAPES.items()}\n", "\n", "\n", "def draw(cls, rng, angle=None, scale=None, fore=None, blur=0.3, noise=4.0, contrast=1.0,\n", " clutter=0.0, occlude=0.0, invert=False, shift=1.5):\n", " \"\"\"ํด๋ž˜์Šค 1๊ฐœ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€(uint8 32x32)๋ฅผ ๊ทธ๋ฆฐ๋‹ค. ๋ฐฐ๊ฒฝ์€ ๋ฐ์€ ํ•˜๋Š˜, ๋น„ํ–‰์ฒด๋Š” ์–ด๋‘์šด ๊ทธ๋ฆผ์ž.\"\"\"\n", " angle = rng.uniform(0, 360) if angle is None else angle\n", " scale = rng.uniform(.7, .95) if scale is None else scale\n", " fore = rng.uniform(.85, 1.0) if fore is None else fore\n", " th = np.radians(angle)\n", " c0 = IMG / 2 + rng.uniform(-shift, shift, 2)\n", " d = (_PTS - c0) / (scale * IMG / 2) # ์ด๋ฏธ์ง€ ์ขŒํ‘œ -> ๋„ํ˜• ์ขŒํ‘œ(์—ญ๋ณ€ํ™˜)\n", " u = d[:, 0] * np.cos(th) + d[:, 1] * np.sin(th)\n", " v = (-d[:, 0] * np.sin(th) + d[:, 1] * np.cos(th)) / fore # ์›๊ทผ ์ถ•์†Œ(๊ธฐ์ˆ˜ ๋ฐฉํ–ฅ์œผ๋กœ ์งง์•„ ๋ณด์ž„)\n", " uv = np.c_[u, -v]\n", " m = np.zeros(len(uv), bool)\n", " near = np.flatnonzero(np.abs(uv).max(axis=1) <= 1.0) # ๋„ํ˜•์ด ์žˆ์„ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ๋งŒ ๊ฒ€์‚ฌ(์†๋„)\n", " for p in _PATHS[cls]:\n", " m[near] |= p.contains_points(uv[near])\n", " for cx, cy, r in SHAPES[cls][\"circ\"]:\n", " m |= (uv[:, 0] - cx) ** 2 + (uv[:, 1] - cy) ** 2 <= r * r\n", " cov = m.reshape(IMG, _S, IMG, _S).mean(axis=(1, 3))\n", " if occlude > 0: # ๋ถ€๋ถ„ ๊ฐ€๋ฆผ(๊ตฌ๋ฆ„ ์กฐ๊ฐ)\n", " h = int(IMG * np.sqrt(occlude)); y0, x0 = rng.integers(8, IMG - 8 - h // 2, 2)\n", " cov[y0:y0 + h, x0:x0 + h] = 0\n", " bg = rng.uniform(180, 220) + np.linspace(-10, 10, IMG)[:, None]\n", " fg = rng.uniform(30, 60)\n", " img = bg * (1 - cov) + fg * cov\n", " if clutter > 0: # ๊ตฌ๋ฆ„ยท๋ฐฐ๊ฒฝ ์–ผ๋ฃฉ\n", " img -= clutter * 90 * np.clip(ndimage.gaussian_filter(rng.normal(0, 1, (IMG, IMG)), 3) * 4, 0, None)\n", " if blur > 0:\n", " img = ndimage.gaussian_filter(img, blur)\n", " mean = img.mean()\n", " img = mean + (img - mean) * contrast\n", " img = img + rng.normal(0, noise, img.shape)\n", " if invert: # ์•ผ๊ฐ„ ์—ด์ƒ(IR) ๊ทน์„ฑ ๋ฐ˜์ „\n", " img = 255 - img\n", " return np.clip(img, 0, 255).astype(np.uint8)\n", "\n", "\n", "# ์ดฌ์˜ ์กฐ๊ฑด ํ”„๋ฆฌ์…‹: ๊ธฐ๋ณธ + ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹œ๋‚˜๋ฆฌ์˜ค\n", "CONDITIONS = {\n", " \"basic\": {},\n", " \"offcenter\": {\"shift\": 5.0, \"fore\": (.6, .85)},\n", " \"far\": {\"scale\": (.32, .45), \"blur\": .6},\n", " \"blur\": {\"blur\": (1.2, 1.8)},\n", " \"lowcon\": {\"contrast\": (.2, .35)},\n", " \"clutter\": {\"clutter\": .8},\n", " \"occlude\": {\"occlude\": .08},\n", " \"oblique\": {\"fore\": (.35, .6)},\n", " \"night\": {\"invert\": True, \"noise\": 10.0},\n", " \"noise\": {\"noise\": 28.0},\n", " \"combo\": {\"scale\": (.4, .55), \"blur\": (.8, 1.1), \"noise\": 12.0, \"contrast\": (.6, .8)},\n", "}\n", "\n", "\n", "def draw_cond(cls, cond, rng):\n", " kw = {}\n", " for k, v in CONDITIONS[cond].items():\n", " kw[k] = rng.uniform(*v) if isinstance(v, tuple) else v\n", " return draw(cls, rng, **kw)\n", "\n", "\n", "def make_dataset(n_per_class, seed, conds=(\"basic\",)):\n", " \"\"\"(์ด๋ฏธ์ง€ ๋ฐฐ์—ด N x 32 x 32, ์ •๋‹ต ๋ผ๋ฒจ, ์กฐ๊ฑด) ๋ฐ˜ํ™˜. ์กฐ๊ฑด์€ conds์—์„œ ๋ฒˆ๊ฐˆ์•„ ๊ณ ๋ฅธ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " X, y, c = [], [], []\n", " for cls in CLASSES:\n", " for i in range(n_per_class):\n", " cond = conds[i % len(conds)]\n", " X.append(draw_cond(cls, cond, rng)); y.append(cls); c.append(cond)\n", " return np.array(X), np.array(y), np.array(c)\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---- ์ „์ฒ˜๋ฆฌ(๋ฐฐ์น˜ ๋ฒกํ„ฐํ™”)ยทํ˜•ํƒœ ํŠน์ง• ๊ณตํ†ต ํ•จ์ˆ˜ ----\n", "OUT = 24 # ํฌ๊ธฐ ์ •๊ทœํ™” ๊ฒฐ๊ณผ(24x24)\n", "NOISE_T = 12.0 # ์žก์Œ ์ถ”์ •๊ฐ’์ด ์ด๋ณด๋‹ค ํฌ๋ฉด 3x3 ์ค‘๊ฐ„๊ฐ’ ํ•„ํ„ฐ ์ ์šฉ(์•…์ฒœํ›„ยท์„ผ์„œ ์žก์Œ)\n", "FEATS = [\"elong\", \"fill\", \"solidity\", \"n_parts\", \"end_gap\", \"mid_bulge\", \"w_max\", \"compact\"]\n", "\n", "\n", "def preprocess(images, noise_t=NOISE_T):\n", " \"\"\"N์žฅ ์ผ๊ด„ ์ฒ˜๋ฆฌ: ๊ฒฐ์ธก ๋ณด์ • -> ๊ทน์„ฑ(์•ผ๊ฐ„ ๋ฐ˜์ „) ๋ณด์ • -> ์กฐ๊ฑด๋ณ„ ์žก์Œ ์ œ๊ฑฐ -> ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™” -> ๋ถ„ํ•  -> ์œค๊ณฝ์„  -> ๋ฐฉํ–ฅยทํฌ๊ธฐ ์ •๊ทœํ™”.\"\"\"\n", " f = np.array(images, dtype=float).reshape(-1, IMG, IMG)\n", " n = len(f)\n", " bad = ~np.isfinite(f)\n", " missing = bad.sum(axis=(1, 2))\n", " if bad.any(): # ๊ฒฐ์ธก ํ”ฝ์…€: ์˜์ƒ ์ค‘์•™๊ฐ’์œผ๋กœ ์ฑ„์šด ๋’ค ์ฃผ๋ณ€ 3x3 ์ค‘๊ฐ„๊ฐ’์œผ๋กœ ๋ณด์ •\n", " med = np.array([np.median(x[np.isfinite(x)]) if np.isfinite(x).any() else 128.0 for x in f])\n", " f = np.where(bad, med[:, None, None], f)\n", " f = np.where(bad, ndimage.median_filter(f, size=(1, 3, 3)), f)\n", " border = np.concatenate([f[:, 0], f[:, -1], f[:, :, 0], f[:, :, -1]], axis=1)\n", " inverted = np.median(border, axis=1) < 128 # ๋ฐฐ๊ฒฝ์ด ์–ด๋‘์šฐ๋ฉด ์•ผ๊ฐ„ ์—ด์ƒ์œผ๋กœ ๋ณด๊ณ  ๋ฐ˜์ „\n", " f[inverted] = 255 - f[inverted]\n", " med3 = ndimage.median_filter(f, size=(1, 3, 3))\n", " noise = 1.4826 * np.median(np.abs(f - med3).reshape(n, -1), axis=1)\n", " noisy = noise > noise_t\n", " f[noisy] = med3[noisy]\n", " lo, hi = np.percentile(f.reshape(n, -1), [1, 99], axis=1)\n", " e = np.clip((f - lo[:, None, None]) / np.maximum(hi - lo, 1e-6)[:, None, None], 0, 1) # ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”\n", " mask = e < _otsu(e)[:, None, None]\n", " mask &= ndimage.uniform_filter(mask.astype(float), size=(1, 3, 3)) * 9 >= 2.5 # ๊ณ ๋ฆฝ ์  ์žก์Œ ์ œ๊ฑฐ\n", " edge = np.hypot(ndimage.sobel(e, axis=1), ndimage.sobel(e, axis=2)) # ์œค๊ณฝ์„ (Sobel)\n", " ok = (mask.sum(axis=(1, 2)) >= 4) & (hi - lo > 8)\n", " return {\"enh\": e, \"mask\": mask, \"edge\": edge / np.maximum(edge.max(axis=(1, 2)), 1e-6)[:, None, None],\n", " \"norm\": _normalize(1 - e, mask, ok), \"inverted\": inverted, \"noise\": noise, \"denoised\": noisy,\n", " \"missing\": missing, \"contrast\": hi - lo, \"ok\": ok}\n", "\n", "\n", "def _otsu(e, bins=64):\n", " n = len(e)\n", " idx = np.minimum((e * bins).astype(int), bins - 1) + bins * np.arange(n)[:, None, None]\n", " hist = np.bincount(idx.ravel(), minlength=bins * n).reshape(n, bins).astype(float)\n", " centers = (np.arange(bins) + 0.5) / bins\n", " w = np.cumsum(hist, axis=1); m = np.cumsum(hist * centers, axis=1)\n", " tot, mt = w[:, -1:], m[:, -1:]\n", " between = (mt * w / tot - m) ** 2 / np.maximum(w * (tot - w), 1)\n", " return (np.argmax(between, axis=1) + 1) / bins\n", "\n", "\n", "def _normalize(obj, mask, ok):\n", " \"\"\"์ฃผ์ถ• ๋ฐฉํ–ฅ์„ ์„ธ๋กœ๋กœ ๋Œ๋ฆฌ๊ณ  ๋น„ํ–‰์ฒด ์˜์—ญ๋งŒ 24x24๋กœ ๋‹ค์‹œ ์ƒ˜ํ”Œ๋งํ•œ๋‹ค(ํšŒ์ „ยทํฌ๊ธฐยท์œ„์น˜ ๋ถˆ๋ณ€). ํ•œ ๋ฒˆ์˜ ๋ณด๊ฐ„์œผ๋กœ ์ผ๊ด„ ์ฒ˜๋ฆฌ.\"\"\"\n", " n = len(obj)\n", " w = mask.astype(float)\n", " m00 = np.maximum(w.sum(axis=(1, 2)), 1)\n", " yy, xx = np.mgrid[0:IMG, 0:IMG].astype(float)\n", " cy, cx = (w * yy).sum(axis=(1, 2)) / m00, (w * xx).sum(axis=(1, 2)) / m00\n", " dy, dx = yy - cy[:, None, None], xx - cx[:, None, None]\n", " mu20, mu02, mu11 = (w * dy * dy).sum(axis=(1, 2)), (w * dx * dx).sum(axis=(1, 2)), (w * dy * dx).sum(axis=(1, 2))\n", " th = 0.5 * np.arctan2(2 * mu11, mu20 - mu02)\n", " a = np.stack([np.cos(th), np.sin(th)], 1) # ์ฃผ์ถ•(y, x)\n", " b = np.stack([-np.sin(th), np.cos(th)], 1) # ๋ถ€์ถ•\n", " pa = np.where(mask, dy * a[:, 0, None, None] + dx * a[:, 1, None, None], 0)\n", " pb = np.where(mask, dy * b[:, 0, None, None] + dx * b[:, 1, None, None], 0)\n", " half = np.maximum(np.abs(pa).max(axis=(1, 2)), np.abs(pb).max(axis=(1, 2))) + 1.0\n", " g = np.linspace(-1, 1, OUT)\n", " U, V = np.meshgrid(g, g, indexing=\"ij\")\n", " Y = cy[:, None, None] + half[:, None, None] * (U * a[:, 0, None, None] + V * b[:, 0, None, None])\n", " X = cx[:, None, None] + half[:, None, None] * (U * a[:, 1, None, None] + V * b[:, 1, None, None])\n", " N = np.broadcast_to(np.arange(n)[:, None, None], Y.shape)\n", " out = ndimage.map_coordinates(obj * mask, [N, Y, X], order=1, cval=0.0)\n", " skew = (out * (U ** 3)).sum(axis=(1, 2))\n", " out[skew < 0] = out[skew < 0, ::-1] # ๋ฌด๊ฒŒ๊ฐ€ ์ ๋ฆฐ ์ชฝ์„ ์œ„๋กœ(180๋„ ๋ชจํ˜ธ์„ฑ ์ œ๊ฑฐ)\n", " out[~ok] = 0\n", " return np.clip(out, 0, 1)\n", "\n", "\n", "def shape_features(mask):\n", " \"\"\"๋ถ„ํ•  ๋งˆ์Šคํฌ 1์žฅ์—์„œ ํšŒ์ „์— ๋ฌด๊ด€ํ•œ ํ˜•ํƒœ ํŠน์ง• 8์ข…์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " if mask.sum() < 4:\n", " return dict.fromkeys(FEATS, 0.0)\n", " yx = np.argwhere(mask).astype(float)\n", " c = yx - yx.mean(axis=0)\n", " val, vec = np.linalg.eigh(np.cov(c.T) + 1e-6 * np.eye(2))\n", " t, s = c @ vec[:, 1], c @ vec[:, 0]\n", " length, width = np.ptp(t) + 1, np.ptp(s) + 1\n", " corners = np.vstack([yx + d for d in ([.5, .5], [.5, -.5], [-.5, .5], [-.5, -.5])])\n", " try:\n", " hull = ConvexHull(corners).volume\n", " except Exception:\n", " hull = float(mask.sum())\n", " bins = np.digitize(t, np.linspace(t.min(), t.max() + 1e-6, 4)[1:-1])\n", " w = np.array([np.ptp(s[bins == k]) + 1 if (bins == k).any() else 0 for k in range(3)]) / length\n", " boundary = mask & ~ndimage.binary_erosion(mask)\n", " return {\"elong\": float(np.sqrt(val[1] / max(val[0], 1e-6))), \"fill\": float(mask.sum() / (length * width)),\n", " \"solidity\": float(mask.sum() / max(hull, 1)), \"n_parts\": float(ndimage.label(mask)[1]),\n", " \"end_gap\": float(abs(w[0] - w[2])), \"mid_bulge\": float(w[1] - max(w[0], w[2])),\n", " \"w_max\": float(w.max()), \"compact\": float(boundary.sum() / np.sqrt(mask.sum()))}\n", "\n", "\n", "def feature_matrix(images):\n", " \"\"\"์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ, ํ˜•ํƒœ ํŠน์ง• ํ–‰๋ ฌ(N x 8), ์ •๊ทœํ™” ์ด๋ฏธ์ง€ ํ–‰๋ ฌ(N x 576)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " P = preprocess(images)\n", " F = np.array([[d[k] for k in FEATS] for d in map(shape_features, P[\"mask\"])])\n", " return P, F, P[\"norm\"].reshape(len(F), -1)\n", "\n", "# ---- ๋ถ„๋ฅ˜ ๋ชจ๋ธ ๊ณตํ†ต ํ•จ์ˆ˜(๋‹ค์ธต ํผ์…‰ํŠธ๋ก : ์ •๊ทœํ™” ์ด๋ฏธ์ง€ 576ํ”ฝ์…€ + ํ˜•ํƒœ ํŠน์ง• 8์ข…) ----\n", "def to_input(F, N, scaler):\n", " return np.c_[N, scaler.transform(F) * 0.3]\n", "\n", "\n", "def train_model(n_per_class=150, seed=5, conds=tuple(CONDITIONS), hidden=(64,)):\n", " X, y, _ = make_dataset(n_per_class, seed, conds)\n", " P, F, N = feature_matrix(X)\n", " scaler = StandardScaler().fit(F)\n", " model = MLPClassifier(hidden, max_iter=200, early_stopping=True, random_state=0)\n", " model.fit(to_input(F, N, scaler), [CLASSES.index(v) for v in y])\n", " return model, scaler\n", "\n", "\n", "def predict(model, scaler, images):\n", " \"\"\"(์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ, ํด๋ž˜์Šค๋ณ„ ํ™•๋ฅ  N x 6)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " P, F, N = feature_matrix(images)\n", " return P, model.predict_proba(to_input(F, N, scaler))\n", "\n", "N_OPTIONS = 4\n", "CONFUSE = {\"civil_fw\": \"attack_fw\", \"attack_fw\": \"civil_fw\", \"cruise\": \"ballistic\", \"ballistic\": \"cruise\", \"bird\": \"civil_fw\", \"civil_quad\": \"civil_fw\"}\n", "\n", "\n", "class QuizApp:\n", " \"\"\"ํ•™์Šต ์•ฑ ์ƒํƒœ: ๋ฌธ์ œ ์€ํ–‰, ํ˜„์žฌ ๋ฌธ์ œ, ์ ์ˆ˜, ์‘๋‹ต ์ด๋ ฅ. ํ™”๋ฉด 1์žฅ = render() ๊ฒฐ๊ณผ ๋ฌธ์ž์—ด.\"\"\"\n", "\n", " def __init__(self, model, scaler, rng, n_questions=12):\n", " self.model, self.scaler, self.rng = model, scaler, rng\n", " conds = [\"basic\", \"far\", \"oblique\", \"night\", \"blur\", \"clutter\"]\n", " self.bank = [(c, conds[i % len(conds)]) for i, c in enumerate(rng.permutation(CLASSES * 2))][:n_questions]\n", " self.i, self.history = 0, []\n", " self.next_question()\n", "\n", " def next_question(self):\n", " cls, cond = self.bank[self.i]\n", " self.img, self.answer, self.cond = draw_cond(cls, cond, self.rng), cls, cond\n", " others = [c for c in CLASSES if c not in (cls, CONFUSE[cls])]\n", " self.options = list(self.rng.permutation([cls, CONFUSE[cls]] + list(self.rng.choice(others, N_OPTIONS - 2, replace=False))))\n", " self.feedback = None\n", "\n", " def submit(self, choice_no):\n", " \"\"\"์‘๋‹ต ์ฒ˜๋ฆฌ: ์ฑ„์  -> AI ์ฐธ๊ณ  ํŒ์ •(์‹ ๋ขฐ๋„) -> ํ”ผ๋“œ๋ฐฑ ๋ฌธ๊ตฌ. ์ฒ˜๋ฆฌ์‹œ๊ฐ„(ms)์„ ํ•จ๊ป˜ ๊ธฐ๋กํ•œ๋‹ค.\"\"\"\n", " t0 = time.perf_counter()\n", " choice = self.options[choice_no - 1]\n", " _, prob = predict(self.model, self.scaler, [self.img])\n", " ai, conf = CLASSES[int(prob.argmax())], float(prob.max())\n", " ok = choice == self.answer\n", " self.feedback = (\"์ •๋‹ต\" if ok else f\"์˜ค๋‹ต - ์ •๋‹ต์€ {KOR[self.answer]}\") + f\" | AI ์ฐธ๊ณ  ํŒ์ •: {KOR[ai]}({conf:.0%})\"\n", " ms = (time.perf_counter() - t0) * 1000\n", " self.history.append({\"๋ฌธํ•ญ\": self.i + 1, \"์ •๋‹ต\": self.answer, \"์„ ํƒ\": choice, \"๊ฒฐ๊ณผ\": ok, \"์กฐ๊ฑด\": self.cond, \"์ฒ˜๋ฆฌms\": ms})\n", " return ms\n", "\n", " def render(self):\n", " done = len(self.history)\n", " bar = \"#\" * done + \".\" * (len(self.bank) - done)\n", " score = sum(h[\"๊ฒฐ๊ณผ\"] for h in self.history)\n", " opts = \" \".join(f\"{k + 1}) {KOR[o]}\" for k, o in enumerate(self.options))\n", " return f\"[๋ฌธํ•ญ {self.i + 1}/{len(self.bank)}] ์ง„ํ–‰ {bar} ์ ์ˆ˜ {score}/{done}\\n ๋ณด๊ธฐ: {opts}\\n ํ”ผ๋“œ๋ฐฑ: {self.feedback or '-'}\"\n", "\n", "\n", "model, scaler = train_model(120, seed=5)\n", "rng = np.random.default_rng(8)\n", "app = QuizApp(model, scaler, rng)\n", "skill = {\"civil_quad\": .9, \"civil_fw\": .65, \"attack_fw\": .7, \"cruise\": .55, \"ballistic\": .6, \"bird\": .85} # ๊ฐ€์ƒ ํ•™์Šต์ž ์ˆ™๋ จ๋„\n", "\n", "refresh = []\n", "for q in range(len(app.bank)):\n", " t_screen = time.perf_counter()\n", " correct = rng.random() < skill[app.answer]\n", " pick = app.options.index(app.answer if correct else CONFUSE[app.answer]) + 1\n", " app.submit(pick)\n", " if q < 2:\n", " print(app.render() + \"\\n\")\n", " refresh.append((time.perf_counter() - t_screen) * 1000)\n", " if q < len(app.bank) - 1:\n", " app.i += 1; app.next_question()\n", "\n", "h = pd.DataFrame(app.history)\n", "print(f\"ํ™”๋ฉด ๊ฐฑ์‹  ์ฒ˜๋ฆฌ ์ตœ๋Œ€ {max(refresh):.1f} ms (๋ชฉํ‘œ: ๊ฐฑ์‹  ์ฃผ๊ธฐ 1์ดˆ ์ด๋‚ด), ์‘๋‹ต ์ฒ˜๋ฆฌ ํ‰๊ท  {h['์ฒ˜๋ฆฌms'].mean():.1f} ms / ์ตœ๋Œ€ {h['์ฒ˜๋ฆฌms'].max():.1f} ms (๋ชฉํ‘œ 100 ms ์ด๋‚ด: {'๋‹ฌ์„ฑ' if h['์ฒ˜๋ฆฌms'].max() <= 100 else '๋ฏธ๋‹ฌ'})\")\n", "print(f\"์ตœ์ข… ์ ์ˆ˜ {h['๊ฒฐ๊ณผ'].sum()}/{len(h)}\")\n", "print(\"\\n[ํด๋ž˜์Šค๋ณ„ ์ •๋‹ต๋ฅ ]\")\n", "print(h.groupby(\"์ •๋‹ต\")[\"๊ฒฐ๊ณผ\"].mean().rename(index=KOR).round(2).to_string())\n", "print(\"\\n[์˜ค๋‹ต ๋ชฉ๋ก]\")\n", "print(h[~h[\"๊ฒฐ๊ณผ\"]][[\"๋ฌธํ•ญ\", \"์ •๋‹ต\", \"์„ ํƒ\", \"์กฐ๊ฑด\"]].to_string(index=False))\n", "\n", "# ํ•™์Šต ํ™”๋ฉด(์•ผ๊ฐ„ ๋ชจ๋“œ): ๋ฌธ์ œ ์ด๋ฏธ์ง€, ๋ณด๊ธฐยทํ”ผ๋“œ๋ฐฑ, ์ง„ํ–‰๋ฅ , ํด๋ž˜์Šค๋ณ„ ์ •๋‹ต๋ฅ , ์‘๋‹ต์‹œ๊ฐ„ ์ถ”์ด - ์‹œ๊ฐํ™” ๊ตฌ์„ฑ์š”์†Œ 5์ข…\n", "plt.style.use(\"dark_background\")\n", "fig = plt.figure(figsize=(12, 7))\n", "ax1 = fig.add_subplot(2, 3, 1); ax1.imshow(app.img, cmap=\"gray\"); ax1.axis(\"off\"); ax1.set_title(f\"Q{app.i + 1}: identify the silhouette\")\n", "ax2 = fig.add_subplot(2, 3, 2); ax2.axis(\"off\"); ax2.set_title(\"Options / feedback\")\n", "last = app.history[-1]\n", "for k, o in enumerate(app.options):\n", " col = \"lime\" if o == app.answer else (\"red\" if o == last[\"์„ ํƒ\"] else \"white\")\n", " ax2.text(0.05, 0.85 - k * 0.2, f\"{k + 1}) {o}\", color=col, fontsize=13, fontweight=\"bold\" if col != \"white\" else \"normal\")\n", "ax3 = fig.add_subplot(2, 3, 3); s = h[\"๊ฒฐ๊ณผ\"].sum()\n", "ax3.barh([0], [len(h)], color=\"dimgray\"); ax3.barh([0], [s], color=\"deepskyblue\"); ax3.set_yticks([])\n", "ax3.set_title(f\"Progress / score {s}/{len(h)}\")\n", "ax4 = fig.add_subplot(2, 3, 4); acc = h.groupby(\"์ •๋‹ต\")[\"๊ฒฐ๊ณผ\"].mean()\n", "ax4.bar(acc.index, acc.values, color=[\"red\" if v < 0.6 else \"deepskyblue\" for v in acc.values]); ax4.tick_params(axis=\"x\", rotation=40)\n", "ax4.set_title(\"Accuracy by class (red = weak)\")\n", "ax5 = fig.add_subplot(2, 3, 5); ax5.plot(h[\"๋ฌธํ•ญ\"], h[\"์ฒ˜๋ฆฌms\"], \"o-\", color=\"orange\"); ax5.axhline(100, ls=\"--\", c=\"gray\")\n", "ax5.set_title(\"Response time (ms)\"); ax5.set_xlabel(\"question\")\n", "ax6 = fig.add_subplot(2, 3, 6); ax6.axis(\"off\")\n", "ax6.text(0, 0.8, \"Night mode UI\\nCorrect: green / Wrong: red\\nAI hint shown only after answer\", fontsize=11, va=\"top\")\n", "plt.suptitle(\"Silhouette ID training app prototype - training data only\"); plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 06 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๋ฌธํ•ญ 1/12] ์ง„ํ–‰ #........... ์ ์ˆ˜ 1/1\n", " ๋ณด๊ธฐ: 1) ์กฐ๋ฅ˜ 2) ๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜• 3) ๋ฏผ๊ฐ„ ์ฟผ๋“œ์ฝฅํ„ฐํ˜• 4) ์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜•\n", " ํ”ผ๋“œ๋ฐฑ: ์ •๋‹ต | AI ์ฐธ๊ณ  ํŒ์ •: ๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•(96%)\n", "\n", "[๋ฌธํ•ญ 2/12] ์ง„ํ–‰ ##.......... ์ ์ˆ˜ 1/2\n", " ๋ณด๊ธฐ: 1) ํƒ„๋„๋ฏธ์‚ฌ์ผํ˜• 2) ์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜• 3) ์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜• 4) ๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•\n", " ํ”ผ๋“œ๋ฐฑ: ์˜ค๋‹ต - ์ •๋‹ต์€ ์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜• | AI ์ฐธ๊ณ  ํŒ์ •: ์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜•(83%)\n", "\n", "ํ™”๋ฉด ๊ฐฑ์‹  ์ฒ˜๋ฆฌ ์ตœ๋Œ€ 8.5 ms (๋ชฉํ‘œ: ๊ฐฑ์‹  ์ฃผ๊ธฐ 1์ดˆ ์ด๋‚ด), ์‘๋‹ต ์ฒ˜๋ฆฌ ํ‰๊ท  4.4 ms / ์ตœ๋Œ€ 6.6 ms (๋ชฉํ‘œ 100 ms ์ด๋‚ด: ๋‹ฌ์„ฑ)\n", "์ตœ์ข… ์ ์ˆ˜ 9/12\n", "\n", "[ํด๋ž˜์Šค๋ณ„ ์ •๋‹ต๋ฅ ]\n", "์ •๋‹ต\n", "์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜• 1.0\n", "ํƒ„๋„๋ฏธ์‚ฌ์ผํ˜• 0.5\n", "์กฐ๋ฅ˜ 1.0\n", "๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜• 1.0\n", "๋ฏผ๊ฐ„ ์ฟผ๋“œ์ฝฅํ„ฐํ˜• 1.0\n", "์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜• 0.0\n", "\n", "[์˜ค๋‹ต ๋ชฉ๋ก]\n", " ๋ฌธํ•ญ ์ •๋‹ต ์„ ํƒ ์กฐ๊ฑด\n", " 2 cruise ballistic far\n", " 6 ballistic cruise clutter\n", " 10 cruise ballistic night\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 07. ํ•™์Šต์ž ์˜ค๋‹ต ์‹œ ์ •๋‹ต ์‹ค๋ฃจ์—ฃ ๋น„๊ต ๋ฐ ํ•ต์‹ฌ ํŠน์ง•(๋‚ ๊ฐœ ํ˜•์ƒ ๋“ฑ) ๊ต๊ด€ HITL ํ•ด์„ค ๋ชจ๋“ˆ ์„ค๊ณ„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "ํ•™์Šต์ž ์˜ค๋‹ต ์‹œ ์ •๋‹ต ์‹ค๋ฃจ์—ฃ ๋น„๊ต ๋ฐ ํ•ต์‹ฌ ํŠน์ง• ํ•ด์„ค HITL ๋ชจ๋“ˆ์„ ์„ค๊ณ„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **ํ•™์Šต์ž๊ฐ€ ์˜ค๋‹ต์„ ๋ƒˆ์„ ๋•Œ ์ •๋‹ต ์‹ค๋ฃจ์—ฃ๊ณผ ์„ ํƒํ•œ ์‹ค๋ฃจ์—ฃ์„ ๋น„๊ตํ•ด ํ•ต์‹ฌ ํŠน์ง•์„ ํ•ด์„คํ•˜๋˜, ๊ต๊ด€ ๊ฒ€ํ† ๋ฅผ ๊ฑฐ์ณ์•ผ๋งŒ ๊ณต๊ฐœ๋˜๋Š” HITL ํ•ด์„ค ๋ชจ๋“ˆ์„ ์„ค๊ณ„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์˜ค๋‹ต์„ ํ˜ผ๋™ ์Œ ๋‹จ์œ„๋กœ ๋ฌถ์–ด AI ํ•ด์„ค ์ดˆ์•ˆ(๋‚ ๊ฐœ ํ˜•์ƒ ๋“ฑ ํŠน์ง• ์ฐจ์ด)์„ ๋งŒ๋“ค๊ณ  ๊ฒ€ํ†  ๋Œ€๊ธฐ์—ด์— ๋“ฑ๋ก ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/hitl/trn03_approval_workflow.py ๋ฐ docs/trn03_hitl_guide.md` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์ง€ํœ˜ ์ฑ…์ž„ ๋ถ„๊ณ„์„  ๋ช…ํ™•ํ™” ๋ฐ ์„ฑ๊ธ‰ํ•œ ์ž๋™ ๋‹จ์ • ๋ฐฉ์ง€ ์ฒด๊ณ„ ๋ถ€ํ•ฉ์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์ธ๊ฐ„ ๊ฒ€ํ†  ํ(Queue) ๋Œ€๊ธฐ์‹œ๊ฐ„ ๋ฐ ์Šน์ธ/๋ฐ˜๋ ค ์ฒ˜๋ฆฌ ๋กœ๊น… ์„ฑ๊ณต๋ฅ  100%\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์Šน์ธ/๋ฐ˜๋ ค ์ธํ„ฐํŽ˜์ด์Šค ๋™์ž‘ ๋ฐ ๊ฐ์‚ฌ ๋กœ๊ทธ(Audit Log) ์ƒ์„ฑ ๊ฒ€์ฆ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๋ชจ์˜ ์กฐ์ž‘์ž ์Šน์ธ/๋ฐ˜๋ ค ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ฐ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"ํ•™์Šต์ž๊ฐ€ ์˜ค๋‹ต์„ ๋‚ด๋ฉด ์ •๋‹ต ์‹ค๋ฃจ์—ฃ๊ณผ ์„ ํƒํ•œ ์‹ค๋ฃจ์—ฃ์„ ๋น„๊ตํ•ด ํ•ต์‹ฌ ํŠน์ง•(๋‚ ๊ฐœ ํ˜•์ƒ ๋“ฑ) ํ•ด์„ค ์ดˆ์•ˆ์„ ๋งŒ๋“ค๊ณ , ๊ต๊ด€์ด ์Šน์ธยท์ˆ˜์ •ยท๋ฐ˜๋ คํ•ด์•ผ๋งŒ ํ•™์Šต์ž์—๊ฒŒ ๊ณต๊ฐœ๋˜๋Š” ์ธ๊ฐ„ ๊ฒ€ํ† (HITL) ํ•ด์„ค ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ๋ชจ๋“  ์ฒ˜๋ฆฌ๋Š” ์œ„ยท๋ณ€์กฐ ํ™•์ธ์ด ๊ฐ€๋Šฅํ•œ ๊ฐ์‚ฌ ๋กœ๊ทธ์— ๋‚จ๋Š”๋‹ค.\"\"\"\n", "import hashlib\n", "import json\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from matplotlib.path import Path as MplPath\n", "from scipy import ndimage\n", "from scipy.spatial import ConvexHull\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•ฉ์„ฑ ์‹ค๋ฃจ์—ฃ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ธฐ์ฒด ์‚ฌ์ง„ยท๋„๋ฉดยท์ œ์› ์‚ฌ์šฉ ์•ˆ ํ•จ)\n", "# ์‚ฌ๊ฐํ˜•ยท์‚ผ๊ฐํ˜•ยท์› ๊ฐ™์€ ๊ธฐ๋ณธ ๋„ํ˜•์„ ์กฐํ•ฉํ•œ ์ถ”์ƒ ์‹ค๋ฃจ์—ฃ์„ 32x32 ํ‘๋ฐฑ ์ด๋ฏธ์ง€๋กœ ๊ทธ๋ฆฐ๋‹ค.\n", "# ํšŒ์ „(๋‹ค๊ฐ๋„), ์›๊ทผ ์ถ•์†Œ(์ธก๋ฉด ์‹œ์ ), ํฌ๊ธฐ(์›๊ฑฐ๋ฆฌ), ํ๋ฆผยท์žก์Œยท์ €๋Œ€๋น„ยท๊ตฌ๋ฆ„ยท๊ฐ€๋ฆผยท์•ผ๊ฐ„ ๋ฐ˜์ „์„ ์กฐ์ ˆํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "IMG = 32\n", "CLASSES = [\"civil_quad\", \"civil_fw\", \"attack_fw\", \"cruise\", \"ballistic\", \"bird\"]\n", "KOR = {\"civil_quad\": \"๋ฏผ๊ฐ„ ์ฟผ๋“œ์ฝฅํ„ฐํ˜•\", \"civil_fw\": \"๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•\", \"attack_fw\": \"์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜•\",\n", " \"cruise\": \"์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜•\", \"ballistic\": \"ํƒ„๋„๋ฏธ์‚ฌ์ผํ˜•\", \"bird\": \"์กฐ๋ฅ˜\"}\n", "THREAT = {\"attack_fw\", \"cruise\", \"ballistic\"}\n", "\n", "\n", "def _rect(x0, x1, y0, y1):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 7] ํ•™์Šต์ž ์˜ค๋‹ต ์‹œ ์ •๋‹ต ์‹ค๋ฃจ์—ฃ ๋น„๊ต ๋ฐ ํ•ต์‹ฌ ํŠน์ง•(๋‚ ๊ฐœ ํ˜•์ƒ ๋“ฑ) ๊ต๊ด€ HITL ํ•ด์„ค ๋ชจ๋“ˆ ์„ค๊ณ„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def _bar(p, q, w):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 7] ํ•™์Šต์ž ์˜ค๋‹ต ์‹œ ์ •๋‹ต ์‹ค๋ฃจ์—ฃ ๋น„๊ต ๋ฐ ํ•ต์‹ฌ ํŠน์ง•(๋‚ ๊ฐœ ํ˜•์ƒ ๋“ฑ) ๊ต๊ด€ HITL ํ•ด์„ค ๋ชจ๋“ˆ ์„ค๊ณ„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "SHAPES = {\n", " \"civil_quad\": {\"poly\": [_bar((-.55, -.55), (.55, .55), .1), _bar((-.55, .55), (.55, -.55), .1), _rect(-.15, .15, -.15, .15)],\n", " \"circ\": [(-.55, -.55, .24), (.55, .55, .24), (-.55, .55, .24), (.55, -.55, .24)]},\n", " \"civil_fw\": {\"poly\": [_rect(-.07, .07, -.8, .72), _rect(-.9, .9, .05, .25), _rect(-.33, .33, -.8, -.66)], \"circ\": []},\n", " \"attack_fw\": {\"poly\": [[(0, .85), (.85, -.5), (.12, -.42), (0, -.6), (-.12, -.42), (-.85, -.5)]], \"circ\": []},\n", " \"cruise\": {\"poly\": [_rect(-.07, .07, -.9, .7), [(-.07, .7), (0, .92), (.07, .7)], _rect(-.42, .42, -.05, .07),\n", " [(-.07, -.9), (-.24, -.95), (-.07, -.74)], [(.07, -.9), (.24, -.95), (.07, -.74)]], \"circ\": []},\n", " \"ballistic\": {\"poly\": [_rect(-.13, .13, -.85, .6), [(-.13, .6), (0, .95), (.13, .6)],\n", " [(-.13, -.85), (-.34, -.95), (-.13, -.58)], [(.13, -.85), (.34, -.95), (.13, -.58)]], \"circ\": []},\n", " \"bird\": {\"poly\": [[(-.9, .12), (-.5, .28), (-.12, .06), (0, .22), (.12, .06), (.5, .28), (.9, .12), (.5, .12),\n", " (.12, -.1), (0, -.38), (-.12, -.1), (-.5, .12)]], \"circ\": []},\n", "}\n", "_S = 2\n", "_g = (np.arange(IMG * _S) + 0.5) / _S\n", "_GX, _GY = np.meshgrid(_g, _g)\n", "_PTS = np.c_[_GX.ravel(), _GY.ravel()]\n", "_PATHS = {c: [MplPath(np.array(p)) for p in s[\"poly\"]] for c, s in SHAPES.items()}\n", "\n", "\n", "def draw(cls, rng, angle=None, scale=None, fore=None, blur=0.3, noise=4.0, contrast=1.0,\n", " clutter=0.0, occlude=0.0, invert=False, shift=1.5):\n", " \"\"\"ํด๋ž˜์Šค 1๊ฐœ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€(uint8 32x32)๋ฅผ ๊ทธ๋ฆฐ๋‹ค. ๋ฐฐ๊ฒฝ์€ ๋ฐ์€ ํ•˜๋Š˜, ๋น„ํ–‰์ฒด๋Š” ์–ด๋‘์šด ๊ทธ๋ฆผ์ž.\"\"\"\n", " angle = rng.uniform(0, 360) if angle is None else angle\n", " scale = rng.uniform(.7, .95) if scale is None else scale\n", " fore = rng.uniform(.85, 1.0) if fore is None else fore\n", " th = np.radians(angle)\n", " c0 = IMG / 2 + rng.uniform(-shift, shift, 2)\n", " d = (_PTS - c0) / (scale * IMG / 2) # ์ด๋ฏธ์ง€ ์ขŒํ‘œ -> ๋„ํ˜• ์ขŒํ‘œ(์—ญ๋ณ€ํ™˜)\n", " u = d[:, 0] * np.cos(th) + d[:, 1] * np.sin(th)\n", " v = (-d[:, 0] * np.sin(th) + d[:, 1] * np.cos(th)) / fore # ์›๊ทผ ์ถ•์†Œ(๊ธฐ์ˆ˜ ๋ฐฉํ–ฅ์œผ๋กœ ์งง์•„ ๋ณด์ž„)\n", " uv = np.c_[u, -v]\n", " m = np.zeros(len(uv), bool)\n", " near = np.flatnonzero(np.abs(uv).max(axis=1) <= 1.0) # ๋„ํ˜•์ด ์žˆ์„ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ๋งŒ ๊ฒ€์‚ฌ(์†๋„)\n", " for p in _PATHS[cls]:\n", " m[near] |= p.contains_points(uv[near])\n", " for cx, cy, r in SHAPES[cls][\"circ\"]:\n", " m |= (uv[:, 0] - cx) ** 2 + (uv[:, 1] - cy) ** 2 <= r * r\n", " cov = m.reshape(IMG, _S, IMG, _S).mean(axis=(1, 3))\n", " if occlude > 0: # ๋ถ€๋ถ„ ๊ฐ€๋ฆผ(๊ตฌ๋ฆ„ ์กฐ๊ฐ)\n", " h = int(IMG * np.sqrt(occlude)); y0, x0 = rng.integers(8, IMG - 8 - h // 2, 2)\n", " cov[y0:y0 + h, x0:x0 + h] = 0\n", " bg = rng.uniform(180, 220) + np.linspace(-10, 10, IMG)[:, None]\n", " fg = rng.uniform(30, 60)\n", " img = bg * (1 - cov) + fg * cov\n", " if clutter > 0: # ๊ตฌ๋ฆ„ยท๋ฐฐ๊ฒฝ ์–ผ๋ฃฉ\n", " img -= clutter * 90 * np.clip(ndimage.gaussian_filter(rng.normal(0, 1, (IMG, IMG)), 3) * 4, 0, None)\n", " if blur > 0:\n", " img = ndimage.gaussian_filter(img, blur)\n", " mean = img.mean()\n", " img = mean + (img - mean) * contrast\n", " img = img + rng.normal(0, noise, img.shape)\n", " if invert: # ์•ผ๊ฐ„ ์—ด์ƒ(IR) ๊ทน์„ฑ ๋ฐ˜์ „\n", " img = 255 - img\n", " return np.clip(img, 0, 255).astype(np.uint8)\n", "\n", "\n", "# ์ดฌ์˜ ์กฐ๊ฑด ํ”„๋ฆฌ์…‹: ๊ธฐ๋ณธ + ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹œ๋‚˜๋ฆฌ์˜ค\n", "CONDITIONS = {\n", " \"basic\": {},\n", " \"offcenter\": {\"shift\": 5.0, \"fore\": (.6, .85)},\n", " \"far\": {\"scale\": (.32, .45), \"blur\": .6},\n", " \"blur\": {\"blur\": (1.2, 1.8)},\n", " \"lowcon\": {\"contrast\": (.2, .35)},\n", " \"clutter\": {\"clutter\": .8},\n", " \"occlude\": {\"occlude\": .08},\n", " \"oblique\": {\"fore\": (.35, .6)},\n", " \"night\": {\"invert\": True, \"noise\": 10.0},\n", " \"noise\": {\"noise\": 28.0},\n", " \"combo\": {\"scale\": (.4, .55), \"blur\": (.8, 1.1), \"noise\": 12.0, \"contrast\": (.6, .8)},\n", "}\n", "\n", "\n", "def draw_cond(cls, cond, rng):\n", " kw = {}\n", " for k, v in CONDITIONS[cond].items():\n", " kw[k] = rng.uniform(*v) if isinstance(v, tuple) else v\n", " return draw(cls, rng, **kw)\n", "\n", "\n", "def make_dataset(n_per_class, seed, conds=(\"basic\",)):\n", " \"\"\"(์ด๋ฏธ์ง€ ๋ฐฐ์—ด N x 32 x 32, ์ •๋‹ต ๋ผ๋ฒจ, ์กฐ๊ฑด) ๋ฐ˜ํ™˜. ์กฐ๊ฑด์€ conds์—์„œ ๋ฒˆ๊ฐˆ์•„ ๊ณ ๋ฅธ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " X, y, c = [], [], []\n", " for cls in CLASSES:\n", " for i in range(n_per_class):\n", " cond = conds[i % len(conds)]\n", " X.append(draw_cond(cls, cond, rng)); y.append(cls); c.append(cond)\n", " return np.array(X), np.array(y), np.array(c)\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---- ์ „์ฒ˜๋ฆฌ(๋ฐฐ์น˜ ๋ฒกํ„ฐํ™”)ยทํ˜•ํƒœ ํŠน์ง• ๊ณตํ†ต ํ•จ์ˆ˜ ----\n", "OUT = 24 # ํฌ๊ธฐ ์ •๊ทœํ™” ๊ฒฐ๊ณผ(24x24)\n", "NOISE_T = 12.0 # ์žก์Œ ์ถ”์ •๊ฐ’์ด ์ด๋ณด๋‹ค ํฌ๋ฉด 3x3 ์ค‘๊ฐ„๊ฐ’ ํ•„ํ„ฐ ์ ์šฉ(์•…์ฒœํ›„ยท์„ผ์„œ ์žก์Œ)\n", "FEATS = [\"elong\", \"fill\", \"solidity\", \"n_parts\", \"end_gap\", \"mid_bulge\", \"w_max\", \"compact\"]\n", "\n", "\n", "def preprocess(images, noise_t=NOISE_T):\n", " \"\"\"N์žฅ ์ผ๊ด„ ์ฒ˜๋ฆฌ: ๊ฒฐ์ธก ๋ณด์ • -> ๊ทน์„ฑ(์•ผ๊ฐ„ ๋ฐ˜์ „) ๋ณด์ • -> ์กฐ๊ฑด๋ณ„ ์žก์Œ ์ œ๊ฑฐ -> ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™” -> ๋ถ„ํ•  -> ์œค๊ณฝ์„  -> ๋ฐฉํ–ฅยทํฌ๊ธฐ ์ •๊ทœํ™”.\"\"\"\n", " f = np.array(images, dtype=float).reshape(-1, IMG, IMG)\n", " n = len(f)\n", " bad = ~np.isfinite(f)\n", " missing = bad.sum(axis=(1, 2))\n", " if bad.any(): # ๊ฒฐ์ธก ํ”ฝ์…€: ์˜์ƒ ์ค‘์•™๊ฐ’์œผ๋กœ ์ฑ„์šด ๋’ค ์ฃผ๋ณ€ 3x3 ์ค‘๊ฐ„๊ฐ’์œผ๋กœ ๋ณด์ •\n", " med = np.array([np.median(x[np.isfinite(x)]) if np.isfinite(x).any() else 128.0 for x in f])\n", " f = np.where(bad, med[:, None, None], f)\n", " f = np.where(bad, ndimage.median_filter(f, size=(1, 3, 3)), f)\n", " border = np.concatenate([f[:, 0], f[:, -1], f[:, :, 0], f[:, :, -1]], axis=1)\n", " inverted = np.median(border, axis=1) < 128 # ๋ฐฐ๊ฒฝ์ด ์–ด๋‘์šฐ๋ฉด ์•ผ๊ฐ„ ์—ด์ƒ์œผ๋กœ ๋ณด๊ณ  ๋ฐ˜์ „\n", " f[inverted] = 255 - f[inverted]\n", " med3 = ndimage.median_filter(f, size=(1, 3, 3))\n", " noise = 1.4826 * np.median(np.abs(f - med3).reshape(n, -1), axis=1)\n", " noisy = noise > noise_t\n", " f[noisy] = med3[noisy]\n", " lo, hi = np.percentile(f.reshape(n, -1), [1, 99], axis=1)\n", " e = np.clip((f - lo[:, None, None]) / np.maximum(hi - lo, 1e-6)[:, None, None], 0, 1) # ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”\n", " mask = e < _otsu(e)[:, None, None]\n", " mask &= ndimage.uniform_filter(mask.astype(float), size=(1, 3, 3)) * 9 >= 2.5 # ๊ณ ๋ฆฝ ์  ์žก์Œ ์ œ๊ฑฐ\n", " edge = np.hypot(ndimage.sobel(e, axis=1), ndimage.sobel(e, axis=2)) # ์œค๊ณฝ์„ (Sobel)\n", " ok = (mask.sum(axis=(1, 2)) >= 4) & (hi - lo > 8)\n", " return {\"enh\": e, \"mask\": mask, \"edge\": edge / np.maximum(edge.max(axis=(1, 2)), 1e-6)[:, None, None],\n", " \"norm\": _normalize(1 - e, mask, ok), \"inverted\": inverted, \"noise\": noise, \"denoised\": noisy,\n", " \"missing\": missing, \"contrast\": hi - lo, \"ok\": ok}\n", "\n", "\n", "def _otsu(e, bins=64):\n", " n = len(e)\n", " idx = np.minimum((e * bins).astype(int), bins - 1) + bins * np.arange(n)[:, None, None]\n", " hist = np.bincount(idx.ravel(), minlength=bins * n).reshape(n, bins).astype(float)\n", " centers = (np.arange(bins) + 0.5) / bins\n", " w = np.cumsum(hist, axis=1); m = np.cumsum(hist * centers, axis=1)\n", " tot, mt = w[:, -1:], m[:, -1:]\n", " between = (mt * w / tot - m) ** 2 / np.maximum(w * (tot - w), 1)\n", " return (np.argmax(between, axis=1) + 1) / bins\n", "\n", "\n", "def _normalize(obj, mask, ok):\n", " \"\"\"์ฃผ์ถ• ๋ฐฉํ–ฅ์„ ์„ธ๋กœ๋กœ ๋Œ๋ฆฌ๊ณ  ๋น„ํ–‰์ฒด ์˜์—ญ๋งŒ 24x24๋กœ ๋‹ค์‹œ ์ƒ˜ํ”Œ๋งํ•œ๋‹ค(ํšŒ์ „ยทํฌ๊ธฐยท์œ„์น˜ ๋ถˆ๋ณ€). ํ•œ ๋ฒˆ์˜ ๋ณด๊ฐ„์œผ๋กœ ์ผ๊ด„ ์ฒ˜๋ฆฌ.\"\"\"\n", " n = len(obj)\n", " w = mask.astype(float)\n", " m00 = np.maximum(w.sum(axis=(1, 2)), 1)\n", " yy, xx = np.mgrid[0:IMG, 0:IMG].astype(float)\n", " cy, cx = (w * yy).sum(axis=(1, 2)) / m00, (w * xx).sum(axis=(1, 2)) / m00\n", " dy, dx = yy - cy[:, None, None], xx - cx[:, None, None]\n", " mu20, mu02, mu11 = (w * dy * dy).sum(axis=(1, 2)), (w * dx * dx).sum(axis=(1, 2)), (w * dy * dx).sum(axis=(1, 2))\n", " th = 0.5 * np.arctan2(2 * mu11, mu20 - mu02)\n", " a = np.stack([np.cos(th), np.sin(th)], 1) # ์ฃผ์ถ•(y, x)\n", " b = np.stack([-np.sin(th), np.cos(th)], 1) # ๋ถ€์ถ•\n", " pa = np.where(mask, dy * a[:, 0, None, None] + dx * a[:, 1, None, None], 0)\n", " pb = np.where(mask, dy * b[:, 0, None, None] + dx * b[:, 1, None, None], 0)\n", " half = np.maximum(np.abs(pa).max(axis=(1, 2)), np.abs(pb).max(axis=(1, 2))) + 1.0\n", " g = np.linspace(-1, 1, OUT)\n", " U, V = np.meshgrid(g, g, indexing=\"ij\")\n", " Y = cy[:, None, None] + half[:, None, None] * (U * a[:, 0, None, None] + V * b[:, 0, None, None])\n", " X = cx[:, None, None] + half[:, None, None] * (U * a[:, 1, None, None] + V * b[:, 1, None, None])\n", " N = np.broadcast_to(np.arange(n)[:, None, None], Y.shape)\n", " out = ndimage.map_coordinates(obj * mask, [N, Y, X], order=1, cval=0.0)\n", " skew = (out * (U ** 3)).sum(axis=(1, 2))\n", " out[skew < 0] = out[skew < 0, ::-1] # ๋ฌด๊ฒŒ๊ฐ€ ์ ๋ฆฐ ์ชฝ์„ ์œ„๋กœ(180๋„ ๋ชจํ˜ธ์„ฑ ์ œ๊ฑฐ)\n", " out[~ok] = 0\n", " return np.clip(out, 0, 1)\n", "\n", "\n", "def shape_features(mask):\n", " \"\"\"๋ถ„ํ•  ๋งˆ์Šคํฌ 1์žฅ์—์„œ ํšŒ์ „์— ๋ฌด๊ด€ํ•œ ํ˜•ํƒœ ํŠน์ง• 8์ข…์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " if mask.sum() < 4:\n", " return dict.fromkeys(FEATS, 0.0)\n", " yx = np.argwhere(mask).astype(float)\n", " c = yx - yx.mean(axis=0)\n", " val, vec = np.linalg.eigh(np.cov(c.T) + 1e-6 * np.eye(2))\n", " t, s = c @ vec[:, 1], c @ vec[:, 0]\n", " length, width = np.ptp(t) + 1, np.ptp(s) + 1\n", " corners = np.vstack([yx + d for d in ([.5, .5], [.5, -.5], [-.5, .5], [-.5, -.5])])\n", " try:\n", " hull = ConvexHull(corners).volume\n", " except Exception:\n", " hull = float(mask.sum())\n", " bins = np.digitize(t, np.linspace(t.min(), t.max() + 1e-6, 4)[1:-1])\n", " w = np.array([np.ptp(s[bins == k]) + 1 if (bins == k).any() else 0 for k in range(3)]) / length\n", " boundary = mask & ~ndimage.binary_erosion(mask)\n", " return {\"elong\": float(np.sqrt(val[1] / max(val[0], 1e-6))), \"fill\": float(mask.sum() / (length * width)),\n", " \"solidity\": float(mask.sum() / max(hull, 1)), \"n_parts\": float(ndimage.label(mask)[1]),\n", " \"end_gap\": float(abs(w[0] - w[2])), \"mid_bulge\": float(w[1] - max(w[0], w[2])),\n", " \"w_max\": float(w.max()), \"compact\": float(boundary.sum() / np.sqrt(mask.sum()))}\n", "\n", "\n", "def feature_matrix(images):\n", " \"\"\"์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ, ํ˜•ํƒœ ํŠน์ง• ํ–‰๋ ฌ(N x 8), ์ •๊ทœํ™” ์ด๋ฏธ์ง€ ํ–‰๋ ฌ(N x 576)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " P = preprocess(images)\n", " F = np.array([[d[k] for k in FEATS] for d in map(shape_features, P[\"mask\"])])\n", " return P, F, P[\"norm\"].reshape(len(F), -1)\n", "\n", "FEAT_KOR = {\"elong\": \"๊ธธ์ญ‰ํ•จ\", \"fill\": \"์ฑ„์›€๋ฅ \", \"solidity\": \"๋ณผ๋ก๋„\", \"n_parts\": \"๋ถ„๋ฆฌ ๋ถ€๋ถ„ ์ˆ˜\", \"end_gap\": \"์•ž๋’ค ํญ ์ฐจ์ด\",\n", " \"mid_bulge\": \"์ค‘์•™๋ถ€ ๋‚ ๊ฐœ ๋Œ์ถœ\", \"w_max\": \"์ตœ๋Œ€ ํญ(๋‚ ๊ฐœ ํญ)\", \"compact\": \"์œค๊ณฝ ๋ณต์žก๋„\"}\n", "KEY = {\"civil_quad\": \"์›ํ˜• ํšŒ์ „๋‚ ๊ฐœ 4๊ฐœ\", \"civil_fw\": \"์ง์„  ์ฃผ์ต๊ณผ ๊ผฌ๋ฆฌ๋‚ ๊ฐœ\", \"attack_fw\": \"๊ผฌ๋ฆฌ ์—†๋Š” ์‚ผ๊ฐํ˜• ๋‚ ๊ฐœ\",\n", " \"cruise\": \"๊ฐ€๋Š” ๋™์ฒด์™€ ์ž‘์€ ์ค‘๊ฐ„ ๋‚ ๊ฐœ\", \"ballistic\": \"๊ตต์€ ๋™์ฒดยท๋พฐ์กฑํ•œ ๋จธ๋ฆฌยท๊ผฌ๋ฆฌ ํ•€\", \"bird\": \"๊ฐˆ๋งค๊ธฐํ˜•์œผ๋กœ ๊ตฝ์€ ๋‚ ๊ฐœ\"}\n", "ACTIONS = {\"APPROVE\": \"์Šน์ธ(๊ณต๊ฐœ)\", \"EDIT\": \"์ˆ˜์ • ํ›„ ์Šน์ธ\", \"REJECT\": \"๋ฐ˜๋ ค(์žฌ์ž‘์„ฑ)\"}\n", "SLA_H = 24\n", "\n", "\n", "def template(cls):\n", " return draw(cls, np.random.default_rng(0), angle=0, scale=.85, fore=1.0, blur=0, noise=0, shift=0)\n", "\n", "\n", "TEMPL = {c: template(c) for c in CLASSES}\n", "TF = {c: shape_features(preprocess([TEMPL[c]])[\"mask\"][0]) for c in CLASSES}\n", "SPREAD = {k: np.std([TF[c][k] for c in CLASSES]) + 1e-6 for k in FEATS}\n", "\n", "\n", "def draft(answer, chosen):\n", " \"\"\"AI ํ•ด์„ค ์ดˆ์•ˆ: ๋‘ ํด๋ž˜์Šค ๊ธฐ์ค€ ์‹ค๋ฃจ์—ฃ์˜ ํ˜•ํƒœ ํŠน์ง• ์ฐจ์ด ์ƒ์œ„ 3๊ฐœ๋ฅผ ๋ฌธ์žฅ์œผ๋กœ ๋งŒ๋“ ๋‹ค(์ดˆ์•ˆ์ผ ๋ฟ ๊ณต๊ฐœํ•˜์ง€ ์•Š์Œ).\"\"\"\n", " diff = sorted(FEATS, key=lambda k: -abs(TF[answer][k] - TF[chosen][k]) / SPREAD[k])[:3]\n", " parts = [f\"{FEAT_KOR[k]} {TF[answer][k]:.2f} vs {TF[chosen][k]:.2f}\" for k in diff]\n", " return f\"์ •๋‹ต {KOR[answer]}์€(๋Š”) {KEY[answer]}, ์„ ํƒํ•œ {KOR[chosen]}์€(๋Š”) {KEY[chosen]}. ์ฐจ์ด: \" + \"; \".join(parts)\n", "\n", "\n", "class ReviewQueue:\n", " def __init__(self):\n", " self.items, self.log, self.clock = {}, [], 0.0\n", "\n", " def _record(self, body):\n", " prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n", " body = {**body, \"t_h\": self.clock}\n", " h = hashlib.sha256((prev + json.dumps(body, ensure_ascii=False, sort_keys=True)).encode()).hexdigest()\n", " self.log.append({**body, \"prev\": prev, \"hash\": h})\n", "\n", " def verify(self):\n", " prev = \"GENESIS\"\n", " for n, r in enumerate(self.log, 1):\n", " body = {k: v for k, v in r.items() if k not in (\"prev\", \"hash\")}\n", " if r[\"prev\"] != prev or r[\"hash\"] != hashlib.sha256((prev + json.dumps(body, ensure_ascii=False, sort_keys=True)).encode()).hexdigest():\n", " return False, n\n", " prev = r[\"hash\"]\n", " return True, -1\n", "\n", " def submit(self, learner, answer, chosen, freq):\n", " key = f\"{answer}->{chosen}\"\n", " if key in self.items and self.items[key][\"status\"] == \"PENDING\": # ๊ฐ™์€ ํ˜ผ๋™ ์Œ์€ ํ•œ ๊ฑด์œผ๋กœ ๋ฌถ๊ณ  ๋นˆ๋„๋งŒ ์˜ฌ๋ฆฐ๋‹ค\n", " self.items[key][\"learners\"].append(learner); self.items[key][\"freq\"] = freq\n", " else:\n", " self.items[key] = {\"key\": key, \"answer\": answer, \"chosen\": chosen, \"draft\": draft(answer, chosen), \"status\": \"PENDING\",\n", " \"published\": None, \"learners\": [learner], \"freq\": freq, \"t_in\": self.clock, \"escalated\": False}\n", " self._record({\"event\": \"SUBMIT\", \"key\": key, \"learner\": learner, \"by\": \"AI\"})\n", "\n", " def order(self):\n", " todo = [i for i in self.items.values() if i[\"status\"] == \"PENDING\"]\n", " return sorted(todo, key=lambda i: (not i[\"escalated\"], -i[\"freq\"], i[\"t_in\"]))\n", "\n", " def decide(self, key, action, instructor, reason, text=None, feedback=\"\"):\n", " it = self.items.get(key)\n", " if it is None or it[\"status\"] != \"PENDING\":\n", " raise ValueError(\"๊ฒ€ํ†  ๋Œ€๊ธฐ ์ค‘์ธ ํ•ญ๋ชฉ์ด ์•„๋‹˜\")\n", " if action not in ACTIONS or not instructor.startswith(\"INS\"):\n", " raise ValueError(\"ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ์กฐ์น˜ ๋˜๋Š” ๊ต๊ด€ ID ์—†์Œ\")\n", " if len(reason.strip()) < 5:\n", " raise ValueError(\"ํŒ๋‹จ ๊ทผ๊ฑฐ 5์ž ์ด์ƒ ํ•„์š”\")\n", " if action == \"EDIT\" and not text:\n", " raise ValueError(\"์ˆ˜์ • ์Šน์ธ์€ ์ˆ˜์ • ํ•ด์„ค๋ฌธ ํ•„์š”\")\n", " it[\"status\"] = {\"APPROVE\": \"APPROVED\", \"EDIT\": \"APPROVED\", \"REJECT\": \"REJECTED\"}[action]\n", " it[\"published\"] = (text or it[\"draft\"]) if action != \"REJECT\" else None\n", " it[\"wait_h\"] = self.clock - it[\"t_in\"]\n", " self._record({\"event\": \"DECIDE\", \"key\": key, \"action\": action, \"by\": instructor, \"reason\": reason, \"feedback\": feedback})\n", "\n", " def check_sla(self):\n", " out = []\n", " for i in self.items.values():\n", " if i[\"status\"] == \"PENDING\" and not i[\"escalated\"] and self.clock - i[\"t_in\"] > SLA_H:\n", " i[\"escalated\"] = True; out.append(i[\"key\"])\n", " self._record({\"event\": \"ESCALATE\", \"key\": i[\"key\"], \"to\": \"๊ต์œกํ›ˆ๋ จ ๋‹ด๋‹น๊ด€\"})\n", " return out\n", "\n", "\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•™์Šต์ž 20๋ช…์˜ ์˜ค๋‹ต ๊ธฐ๋ก(๊ฐ€๋ช… L01~L20)\n", "rng = np.random.default_rng(4)\n", "pairs = [(\"attack_fw\", \"civil_fw\")] * 6 + [(\"cruise\", \"ballistic\")] * 5 + [(\"civil_fw\", \"attack_fw\")] * 3 + [(\"bird\", \"civil_fw\")] * 2 + [(\"ballistic\", \"cruise\")] * 4\n", "q = ReviewQueue()\n", "freq = {}\n", "for n, (a, c) in enumerate(rng.permutation(np.array(pairs, dtype=object))):\n", " freq[(a, c)] = freq.get((a, c), 0) + 1\n", " q.clock += 0.5\n", " q.submit(f\"L{n + 1:02d}\", a, c, freq[(a, c)])\n", "print(f\"์˜ค๋‹ต {len(pairs)}๊ฑด -> ํ•ด์„ค ๊ฒ€ํ†  ํ•ญ๋ชฉ {len(q.items)}๊ฑด (ํ˜ผ๋™ ์Œ ๋‹จ์œ„), ๊ณต๊ฐœ๋œ ํ•ด์„ค {sum(i['published'] is not None for i in q.items.values())}๊ฑด\")\n", "print(\"๊ฒ€ํ†  ์ˆœ์„œ(๋นˆ๋„์ˆœ):\", [f\"{i['key']}({i['freq']})\" for i in q.order()])\n", "print(\"\\n[AI ํ•ด์„ค ์ดˆ์•ˆ ์˜ˆ์‹œ - ๊ต๊ด€ ๊ฒ€ํ†  ์ „ ๋น„๊ณต๊ฐœ]\\n\" + q.order()[0][\"draft\"])\n", "\n", "for args in [(\"attack_fw->civil_fw\", \"APPROVE\", \"\", \"๊ทผ๊ฑฐ ์ถฉ๋ถ„\"), (\"attack_fw->civil_fw\", \"EDIT\", \"INS01\", \"ํ‘œํ˜„ ๋ณด์™„ ํ•„์š”\"), (\"cruise->ballistic\", \"PUBLISH\", \"INS01\", \"์ž๋™ ๊ณต๊ฐœ\")]:\n", " try:\n", " q.decide(*args)\n", " except ValueError as e:\n", " print(\"์ฐจ๋‹จ:\", e)\n", "\n", "q.clock += 2; q.decide(\"attack_fw->civil_fw\", \"EDIT\", \"INS01\", \"๊ผฌ๋ฆฌ๋‚ ๊ฐœ ์œ ๋ฌด๋ฅผ ๋จผ์ € ๋ณด๋„๋ก ์ˆ˜์ •\",\n", " text=\"๊ผฌ๋ฆฌ๋‚ ๊ฐœ๊ฐ€ ์—†๊ณ  ๋‚ ๊ฐœ๊ฐ€ ์‚ผ๊ฐํ˜•์ด๋ฉด ์žํญ๋“œ๋ก ํ˜•, ์ง์„  ๋‚ ๊ฐœ์™€ ๊ผฌ๋ฆฌ๋‚ ๊ฐœ๊ฐ€ ์žˆ์œผ๋ฉด ๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•์ด๋‹ค.\", feedback=\"์›๊ฑฐ๋ฆฌ ์˜ˆ์‹œ ์ถ”๊ฐ€ ์š”๋ง\")\n", "q.clock += 3; q.decide(\"cruise->ballistic\", \"APPROVE\", \"INS02\", \"์ค‘๊ฐ„ ๋‚ ๊ฐœ ์œ ๋ฌด ์„ค๋ช…์ด ์ •ํ™•ํ•จ\")\n", "q.clock += 1; q.decide(\"bird->civil_fw\", \"REJECT\", \"INS02\", \"์กฐ๋ฅ˜ ๋‚ ๊ฐฏ์ง“ ๋ณ€ํ˜• ์„ค๋ช… ๋ˆ„๋ฝ, ์žฌ์ž‘์„ฑ\")\n", "q.clock += 30\n", "print(\"SLA ์ดˆ๊ณผ ์ƒ์‹ :\", q.check_sla())\n", "q.clock += 1; q.decide(\"ballistic->cruise\", \"APPROVE\", \"INS01\", \"๋ชธํ†ต ๊ตต๊ธฐ ๋น„๊ต๊ฐ€ ๋ช…ํ™•ํ•จ\")\n", "\n", "df = pd.DataFrame(q.items.values())\n", "expected = len(pairs) + int((df[\"status\"] != \"PENDING\").sum()) + int(df[\"escalated\"].sum()) # ์ œ์ถœ + ๊ฒฐ์ • + ์ƒ์‹  ์ด๋ฒคํŠธ ์ˆ˜\n", "print(\"๋‚จ์€ ๊ฒ€ํ†  ๋Œ€๊ธฐ:\", [f\"{i['key']}(๋Œ€๊ธฐ {q.clock - i['t_in']:.1f}h)\" for i in q.order()])\n", "print(\"\\n\" + df[[\"key\", \"freq\", \"status\", \"escalated\", \"wait_h\"]].fillna(\"-\").to_string(index=False))\n", "print(f\"\\n์ž๋™ ๊ณต๊ฐœ 0๊ฑด(์Šน์ธ ์—†์ด ๊ณต๊ฐœ๋œ ํ•ด์„ค {sum(1 for i in q.items.values() if i['published'] and i['status'] != 'APPROVED')}๊ฑด), \"\n", " f\"๊ฐ์‚ฌ ๋กœ๊ทธ {len(q.log)}๊ฑด, ๋กœ๊น… ์„ฑ๊ณต๋ฅ  {len(q.log) / expected:.0%}, ํ•ด์‹œ ์ฒด์ธ ๊ฒ€์ฆ {q.verify()}\")\n", "q.log[3][\"learner\"] = \"L99\"\n", "print(\"4๋ฒˆ์งธ ๊ธฐ๋ก ๋ณ€์กฐ ํ›„ ๊ฒ€์ฆ(False, ๊นจ์ง„ ์œ„์น˜):\", q.verify())\n", "print(\"๊ต๊ด€ ํ”ผ๋“œ๋ฐฑ ์ž…๋ ฅ ํ•ญ๋ชฉ: ๊ต๊ด€ ID, ์กฐ์น˜, ํŒ๋‹จ ๊ทผ๊ฑฐ, ์ˆ˜์ • ํ•ด์„ค๋ฌธ, ์ง€ํœ˜๊ด€ยท๊ต๊ด€ ์˜๊ฒฌ\")\n", "\n", "a, c = \"attack_fw\", \"civil_fw\"\n", "fig, ax = plt.subplots(1, 3, figsize=(10, 3.6))\n", "ax[0].imshow(TEMPL[a], cmap=\"gray\"); ax[0].set_title(f\"correct: {a}\")\n", "ax[1].imshow(TEMPL[c], cmap=\"gray\"); ax[1].set_title(f\"chosen: {c}\")\n", "ax[2].imshow((TEMPL[c].astype(float) - TEMPL[a]), cmap=\"coolwarm\"); ax[2].set_title(\"difference (red = only correct)\")\n", "for x in ax:\n", " x.axis(\"off\")\n", "plt.suptitle(\"Wrong-answer comparison for instructor review (synthetic)\"); plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 07 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "์˜ค๋‹ต 20๊ฑด -> ํ•ด์„ค ๊ฒ€ํ†  ํ•ญ๋ชฉ 5๊ฑด (ํ˜ผ๋™ ์Œ ๋‹จ์œ„), ๊ณต๊ฐœ๋œ ํ•ด์„ค 0๊ฑด\n", "๊ฒ€ํ†  ์ˆœ์„œ(๋นˆ๋„์ˆœ): ['attack_fw->civil_fw(6)', 'cruise->ballistic(5)', 'ballistic->cruise(4)', 'civil_fw->attack_fw(3)', 'bird->civil_fw(2)']\n", "\n", "[AI ํ•ด์„ค ์ดˆ์•ˆ ์˜ˆ์‹œ - ๊ต๊ด€ ๊ฒ€ํ†  ์ „ ๋น„๊ณต๊ฐœ]\n", "์ •๋‹ต ์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜•์€(๋Š”) ๊ผฌ๋ฆฌ ์—†๋Š” ์‚ผ๊ฐํ˜• ๋‚ ๊ฐœ, ์„ ํƒํ•œ ๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•์€(๋Š”) ์ง์„  ์ฃผ์ต๊ณผ ๊ผฌ๋ฆฌ๋‚ ๊ฐœ. ์ฐจ์ด: ์œค๊ณฝ ๋ณต์žก๋„ 3.82 vs 7.95; ๋ณผ๋ก๋„ 0.86 vs 0.40; ์ฑ„์›€๋ฅ  0.51 vs 0.29\n", "์ฐจ๋‹จ: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ์กฐ์น˜ ๋˜๋Š” ๊ต๊ด€ ID ์—†์Œ\n", "์ฐจ๋‹จ: ์ˆ˜์ • ์Šน์ธ์€ ์ˆ˜์ • ํ•ด์„ค๋ฌธ ํ•„์š”\n", "์ฐจ๋‹จ: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ์กฐ์น˜ ๋˜๋Š” ๊ต๊ด€ ID ์—†์Œ\n", "SLA ์ดˆ๊ณผ ์ƒ์‹ : ['civil_fw->attack_fw', 'ballistic->cruise']\n", "๋‚จ์€ ๊ฒ€ํ†  ๋Œ€๊ธฐ: ['civil_fw->attack_fw(๋Œ€๊ธฐ 46.0h)']\n", "\n", " key freq status escalated wait_h\n", "attack_fw->civil_fw 6 APPROVED False 11.5\n", "civil_fw->attack_fw 3 PENDING True -\n", " cruise->ballistic 5 APPROVED False 13.5\n", " ballistic->cruise 4 APPROVED True 44.5\n", " bird->civil_fw 2 REJECTED False 10.0\n", "\n", "์ž๋™ ๊ณต๊ฐœ 0๊ฑด(์Šน์ธ ์—†์ด ๊ณต๊ฐœ๋œ ํ•ด์„ค 0๊ฑด), ๊ฐ์‚ฌ ๋กœ๊ทธ 26๊ฑด, ๋กœ๊น… ์„ฑ๊ณต๋ฅ  100%, ํ•ด์‹œ ์ฒด์ธ ๊ฒ€์ฆ (True, -1)\n", "4๋ฒˆ์งธ ๊ธฐ๋ก ๋ณ€์กฐ ํ›„ ๊ฒ€์ฆ(False, ๊นจ์ง„ ์œ„์น˜): (False, 4)\n", "๊ต๊ด€ ํ”ผ๋“œ๋ฐฑ ์ž…๋ ฅ ํ•ญ๋ชฉ: ๊ต๊ด€ ID, ์กฐ์น˜, ํŒ๋‹จ ๊ทผ๊ฑฐ, ์ˆ˜์ • ํ•ด์„ค๋ฌธ, ์ง€ํœ˜๊ด€ยท๊ต๊ด€ ์˜๊ฒฌ\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 08. ๋‹ค๋Ÿ‰์˜ ์ €ํ’ˆ์งˆ ํ๋ฆฐ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ๋ฐ ํ•œ๊ณ„์น˜ ๊ฒ€์ฆ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "ํ๋ฆฐ ์ €ํ’ˆ์งˆ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€ ๋Œ€๋Ÿ‰ ๋ถ„๋ฅ˜ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋‹ค๋Ÿ‰์˜ ํ๋ฆฌ๊ณ  ํ’ˆ์งˆ์ด ๋‚ฎ์€ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€๊ฐ€ ํ•œ๊บผ๋ฒˆ์— ๋“ค์–ด์˜ฌ ๋•Œ ๋ถ„๋ฅ˜ ์„œ๋น„์Šค๊ฐ€ ๋ฉˆ์ถ”์ง€ ์•Š๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ํ•œ๊ณ„์น˜๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **ํ‰์‹œ 100์žฅ์—์„œ 500% ์ฆ๊ฐ€ํ•œ 600์žฅ๊นŒ์ง€ ์š”์ฒญ์„ ๋Š˜๋ฆฌ๊ณ , ์†์ƒ ์ž…๋ ฅ๊ณผ ๊ทน์ €ํ™”์งˆ ์˜์ƒ์„ ์„ž์–ด ์‹œ์Šคํ…œ ๋‹ค์šดยท๊ฒฐ๊ณผ ์œ ์‹ค ์—ฌ๋ถ€ ํ™•์ธํ•˜๊ณ  ์œ ํšจ์„ฑ์„ ์ ๊ฒ€ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `tests/trn03_stress_test.py ๋ฐ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ๋ถ„์„ ๋ณด๊ณ ์„œ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ทน๋‹จ ์ƒํ™ฉ์—์„œ์˜ ์•ˆ์ „ ์‹คํŒจ(Fail-safe) ๋ฐ ๊ธฐ๋Šฅ ์ถ•์†Œ ์ž‘๋™ ์ ์ ˆ์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๋™์‹œ ํŠธ๋ž˜ํ”ฝ 500% ์ฆ๊ฐ€ ์‹œ ์‹œ์Šคํ…œ ๋‹ค์šด 0๊ฑด, ๋ฐ์ดํ„ฐ ์œ ์‹ค๋ฅ  0%\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ๋Œ€๋Ÿ‰ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ถ€ํ•˜ ์ฃผ์ž… ํ…Œ์ŠคํŠธ ํ†ต๊ณผ ๋ฐ ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„ ํ”„๋กœํŒŒ์ผ๋ง ์™„๋ฃŒ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๋„๊ตฌ๋ฅผ ํ†ตํ•œ ์ฒ˜๋ฆฌ๋Ÿ‰(Throughput) ๋ฐ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"ํ๋ฆฌ๊ณ  ํ’ˆ์งˆ์ด ๋‚ฎ์€ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€๋ฅผ ํ‰์‹œ์˜ ์ตœ๋Œ€ 6๋ฐฐ(500% ์ฆ๊ฐ€)๊นŒ์ง€ ํ•œ๊บผ๋ฒˆ์— ๋„ฃ๊ณ  ์†์ƒ ์ž…๋ ฅ์„ ์„ž์–ด, ๋ถ„๋ฅ˜ ์„œ๋น„์Šค๊ฐ€ ๋ฉˆ์ถ”์ง€ ์•Š๋Š”์ง€(๋‹ค์šด 0๊ฑด)ยท๊ฒฐ๊ณผ๊ฐ€ ๋น ์ง€์ง€ ์•Š๋Š”์ง€(์œ ์‹ค๋ฅ  0%)ยทํ๋ฆผ ํ•œ๊ณ„์น˜ยท์ฒ˜๋ฆฌ๋Ÿ‰ยท๋ฉ”๋ชจ๋ฆฌยท๋ณ‘๋ชฉ ๊ตฌ๊ฐ„์„ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import time\n", "import tracemalloc\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from matplotlib.path import Path as MplPath\n", "from scipy import ndimage\n", "from scipy.spatial import ConvexHull\n", "from sklearn.neural_network import MLPClassifier\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•ฉ์„ฑ ์‹ค๋ฃจ์—ฃ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ธฐ์ฒด ์‚ฌ์ง„ยท๋„๋ฉดยท์ œ์› ์‚ฌ์šฉ ์•ˆ ํ•จ)\n", "# ์‚ฌ๊ฐํ˜•ยท์‚ผ๊ฐํ˜•ยท์› ๊ฐ™์€ ๊ธฐ๋ณธ ๋„ํ˜•์„ ์กฐํ•ฉํ•œ ์ถ”์ƒ ์‹ค๋ฃจ์—ฃ์„ 32x32 ํ‘๋ฐฑ ์ด๋ฏธ์ง€๋กœ ๊ทธ๋ฆฐ๋‹ค.\n", "# ํšŒ์ „(๋‹ค๊ฐ๋„), ์›๊ทผ ์ถ•์†Œ(์ธก๋ฉด ์‹œ์ ), ํฌ๊ธฐ(์›๊ฑฐ๋ฆฌ), ํ๋ฆผยท์žก์Œยท์ €๋Œ€๋น„ยท๊ตฌ๋ฆ„ยท๊ฐ€๋ฆผยท์•ผ๊ฐ„ ๋ฐ˜์ „์„ ์กฐ์ ˆํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "IMG = 32\n", "CLASSES = [\"civil_quad\", \"civil_fw\", \"attack_fw\", \"cruise\", \"ballistic\", \"bird\"]\n", "KOR = {\"civil_quad\": \"๋ฏผ๊ฐ„ ์ฟผ๋“œ์ฝฅํ„ฐํ˜•\", \"civil_fw\": \"๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•\", \"attack_fw\": \"์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜•\",\n", " \"cruise\": \"์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜•\", \"ballistic\": \"ํƒ„๋„๋ฏธ์‚ฌ์ผํ˜•\", \"bird\": \"์กฐ๋ฅ˜\"}\n", "THREAT = {\"attack_fw\", \"cruise\", \"ballistic\"}\n", "\n", "\n", "def _rect(x0, x1, y0, y1):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 8] ๋‹ค๋Ÿ‰์˜ ์ €ํ’ˆ์งˆ ํ๋ฆฐ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ๋ฐ ํ•œ๊ณ„์น˜ ๊ฒ€์ฆ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def _bar(p, q, w):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 8] ๋‹ค๋Ÿ‰์˜ ์ €ํ’ˆ์งˆ ํ๋ฆฐ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ๋ฐ ํ•œ๊ณ„์น˜ ๊ฒ€์ฆ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "SHAPES = {\n", " \"civil_quad\": {\"poly\": [_bar((-.55, -.55), (.55, .55), .1), _bar((-.55, .55), (.55, -.55), .1), _rect(-.15, .15, -.15, .15)],\n", " \"circ\": [(-.55, -.55, .24), (.55, .55, .24), (-.55, .55, .24), (.55, -.55, .24)]},\n", " \"civil_fw\": {\"poly\": [_rect(-.07, .07, -.8, .72), _rect(-.9, .9, .05, .25), _rect(-.33, .33, -.8, -.66)], \"circ\": []},\n", " \"attack_fw\": {\"poly\": [[(0, .85), (.85, -.5), (.12, -.42), (0, -.6), (-.12, -.42), (-.85, -.5)]], \"circ\": []},\n", " \"cruise\": {\"poly\": [_rect(-.07, .07, -.9, .7), [(-.07, .7), (0, .92), (.07, .7)], _rect(-.42, .42, -.05, .07),\n", " [(-.07, -.9), (-.24, -.95), (-.07, -.74)], [(.07, -.9), (.24, -.95), (.07, -.74)]], \"circ\": []},\n", " \"ballistic\": {\"poly\": [_rect(-.13, .13, -.85, .6), [(-.13, .6), (0, .95), (.13, .6)],\n", " [(-.13, -.85), (-.34, -.95), (-.13, -.58)], [(.13, -.85), (.34, -.95), (.13, -.58)]], \"circ\": []},\n", " \"bird\": {\"poly\": [[(-.9, .12), (-.5, .28), (-.12, .06), (0, .22), (.12, .06), (.5, .28), (.9, .12), (.5, .12),\n", " (.12, -.1), (0, -.38), (-.12, -.1), (-.5, .12)]], \"circ\": []},\n", "}\n", "_S = 2\n", "_g = (np.arange(IMG * _S) + 0.5) / _S\n", "_GX, _GY = np.meshgrid(_g, _g)\n", "_PTS = np.c_[_GX.ravel(), _GY.ravel()]\n", "_PATHS = {c: [MplPath(np.array(p)) for p in s[\"poly\"]] for c, s in SHAPES.items()}\n", "\n", "\n", "def draw(cls, rng, angle=None, scale=None, fore=None, blur=0.3, noise=4.0, contrast=1.0,\n", " clutter=0.0, occlude=0.0, invert=False, shift=1.5):\n", " \"\"\"ํด๋ž˜์Šค 1๊ฐœ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€(uint8 32x32)๋ฅผ ๊ทธ๋ฆฐ๋‹ค. ๋ฐฐ๊ฒฝ์€ ๋ฐ์€ ํ•˜๋Š˜, ๋น„ํ–‰์ฒด๋Š” ์–ด๋‘์šด ๊ทธ๋ฆผ์ž.\"\"\"\n", " angle = rng.uniform(0, 360) if angle is None else angle\n", " scale = rng.uniform(.7, .95) if scale is None else scale\n", " fore = rng.uniform(.85, 1.0) if fore is None else fore\n", " th = np.radians(angle)\n", " c0 = IMG / 2 + rng.uniform(-shift, shift, 2)\n", " d = (_PTS - c0) / (scale * IMG / 2) # ์ด๋ฏธ์ง€ ์ขŒํ‘œ -> ๋„ํ˜• ์ขŒํ‘œ(์—ญ๋ณ€ํ™˜)\n", " u = d[:, 0] * np.cos(th) + d[:, 1] * np.sin(th)\n", " v = (-d[:, 0] * np.sin(th) + d[:, 1] * np.cos(th)) / fore # ์›๊ทผ ์ถ•์†Œ(๊ธฐ์ˆ˜ ๋ฐฉํ–ฅ์œผ๋กœ ์งง์•„ ๋ณด์ž„)\n", " uv = np.c_[u, -v]\n", " m = np.zeros(len(uv), bool)\n", " near = np.flatnonzero(np.abs(uv).max(axis=1) <= 1.0) # ๋„ํ˜•์ด ์žˆ์„ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ๋งŒ ๊ฒ€์‚ฌ(์†๋„)\n", " for p in _PATHS[cls]:\n", " m[near] |= p.contains_points(uv[near])\n", " for cx, cy, r in SHAPES[cls][\"circ\"]:\n", " m |= (uv[:, 0] - cx) ** 2 + (uv[:, 1] - cy) ** 2 <= r * r\n", " cov = m.reshape(IMG, _S, IMG, _S).mean(axis=(1, 3))\n", " if occlude > 0: # ๋ถ€๋ถ„ ๊ฐ€๋ฆผ(๊ตฌ๋ฆ„ ์กฐ๊ฐ)\n", " h = int(IMG * np.sqrt(occlude)); y0, x0 = rng.integers(8, IMG - 8 - h // 2, 2)\n", " cov[y0:y0 + h, x0:x0 + h] = 0\n", " bg = rng.uniform(180, 220) + np.linspace(-10, 10, IMG)[:, None]\n", " fg = rng.uniform(30, 60)\n", " img = bg * (1 - cov) + fg * cov\n", " if clutter > 0: # ๊ตฌ๋ฆ„ยท๋ฐฐ๊ฒฝ ์–ผ๋ฃฉ\n", " img -= clutter * 90 * np.clip(ndimage.gaussian_filter(rng.normal(0, 1, (IMG, IMG)), 3) * 4, 0, None)\n", " if blur > 0:\n", " img = ndimage.gaussian_filter(img, blur)\n", " mean = img.mean()\n", " img = mean + (img - mean) * contrast\n", " img = img + rng.normal(0, noise, img.shape)\n", " if invert: # ์•ผ๊ฐ„ ์—ด์ƒ(IR) ๊ทน์„ฑ ๋ฐ˜์ „\n", " img = 255 - img\n", " return np.clip(img, 0, 255).astype(np.uint8)\n", "\n", "\n", "# ์ดฌ์˜ ์กฐ๊ฑด ํ”„๋ฆฌ์…‹: ๊ธฐ๋ณธ + ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹œ๋‚˜๋ฆฌ์˜ค\n", "CONDITIONS = {\n", " \"basic\": {},\n", " \"offcenter\": {\"shift\": 5.0, \"fore\": (.6, .85)},\n", " \"far\": {\"scale\": (.32, .45), \"blur\": .6},\n", " \"blur\": {\"blur\": (1.2, 1.8)},\n", " \"lowcon\": {\"contrast\": (.2, .35)},\n", " \"clutter\": {\"clutter\": .8},\n", " \"occlude\": {\"occlude\": .08},\n", " \"oblique\": {\"fore\": (.35, .6)},\n", " \"night\": {\"invert\": True, \"noise\": 10.0},\n", " \"noise\": {\"noise\": 28.0},\n", " \"combo\": {\"scale\": (.4, .55), \"blur\": (.8, 1.1), \"noise\": 12.0, \"contrast\": (.6, .8)},\n", "}\n", "\n", "\n", "def draw_cond(cls, cond, rng):\n", " kw = {}\n", " for k, v in CONDITIONS[cond].items():\n", " kw[k] = rng.uniform(*v) if isinstance(v, tuple) else v\n", " return draw(cls, rng, **kw)\n", "\n", "\n", "def make_dataset(n_per_class, seed, conds=(\"basic\",)):\n", " \"\"\"(์ด๋ฏธ์ง€ ๋ฐฐ์—ด N x 32 x 32, ์ •๋‹ต ๋ผ๋ฒจ, ์กฐ๊ฑด) ๋ฐ˜ํ™˜. ์กฐ๊ฑด์€ conds์—์„œ ๋ฒˆ๊ฐˆ์•„ ๊ณ ๋ฅธ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " X, y, c = [], [], []\n", " for cls in CLASSES:\n", " for i in range(n_per_class):\n", " cond = conds[i % len(conds)]\n", " X.append(draw_cond(cls, cond, rng)); y.append(cls); c.append(cond)\n", " return np.array(X), np.array(y), np.array(c)\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---- ์ „์ฒ˜๋ฆฌ(๋ฐฐ์น˜ ๋ฒกํ„ฐํ™”)ยทํ˜•ํƒœ ํŠน์ง• ๊ณตํ†ต ํ•จ์ˆ˜ ----\n", "OUT = 24 # ํฌ๊ธฐ ์ •๊ทœํ™” ๊ฒฐ๊ณผ(24x24)\n", "NOISE_T = 12.0 # ์žก์Œ ์ถ”์ •๊ฐ’์ด ์ด๋ณด๋‹ค ํฌ๋ฉด 3x3 ์ค‘๊ฐ„๊ฐ’ ํ•„ํ„ฐ ์ ์šฉ(์•…์ฒœํ›„ยท์„ผ์„œ ์žก์Œ)\n", "FEATS = [\"elong\", \"fill\", \"solidity\", \"n_parts\", \"end_gap\", \"mid_bulge\", \"w_max\", \"compact\"]\n", "\n", "\n", "def preprocess(images, noise_t=NOISE_T):\n", " \"\"\"N์žฅ ์ผ๊ด„ ์ฒ˜๋ฆฌ: ๊ฒฐ์ธก ๋ณด์ • -> ๊ทน์„ฑ(์•ผ๊ฐ„ ๋ฐ˜์ „) ๋ณด์ • -> ์กฐ๊ฑด๋ณ„ ์žก์Œ ์ œ๊ฑฐ -> ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™” -> ๋ถ„ํ•  -> ์œค๊ณฝ์„  -> ๋ฐฉํ–ฅยทํฌ๊ธฐ ์ •๊ทœํ™”.\"\"\"\n", " f = np.array(images, dtype=float).reshape(-1, IMG, IMG)\n", " n = len(f)\n", " bad = ~np.isfinite(f)\n", " missing = bad.sum(axis=(1, 2))\n", " if bad.any(): # ๊ฒฐ์ธก ํ”ฝ์…€: ์˜์ƒ ์ค‘์•™๊ฐ’์œผ๋กœ ์ฑ„์šด ๋’ค ์ฃผ๋ณ€ 3x3 ์ค‘๊ฐ„๊ฐ’์œผ๋กœ ๋ณด์ •\n", " med = np.array([np.median(x[np.isfinite(x)]) if np.isfinite(x).any() else 128.0 for x in f])\n", " f = np.where(bad, med[:, None, None], f)\n", " f = np.where(bad, ndimage.median_filter(f, size=(1, 3, 3)), f)\n", " border = np.concatenate([f[:, 0], f[:, -1], f[:, :, 0], f[:, :, -1]], axis=1)\n", " inverted = np.median(border, axis=1) < 128 # ๋ฐฐ๊ฒฝ์ด ์–ด๋‘์šฐ๋ฉด ์•ผ๊ฐ„ ์—ด์ƒ์œผ๋กœ ๋ณด๊ณ  ๋ฐ˜์ „\n", " f[inverted] = 255 - f[inverted]\n", " med3 = ndimage.median_filter(f, size=(1, 3, 3))\n", " noise = 1.4826 * np.median(np.abs(f - med3).reshape(n, -1), axis=1)\n", " noisy = noise > noise_t\n", " f[noisy] = med3[noisy]\n", " lo, hi = np.percentile(f.reshape(n, -1), [1, 99], axis=1)\n", " e = np.clip((f - lo[:, None, None]) / np.maximum(hi - lo, 1e-6)[:, None, None], 0, 1) # ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”\n", " mask = e < _otsu(e)[:, None, None]\n", " mask &= ndimage.uniform_filter(mask.astype(float), size=(1, 3, 3)) * 9 >= 2.5 # ๊ณ ๋ฆฝ ์  ์žก์Œ ์ œ๊ฑฐ\n", " edge = np.hypot(ndimage.sobel(e, axis=1), ndimage.sobel(e, axis=2)) # ์œค๊ณฝ์„ (Sobel)\n", " ok = (mask.sum(axis=(1, 2)) >= 4) & (hi - lo > 8)\n", " return {\"enh\": e, \"mask\": mask, \"edge\": edge / np.maximum(edge.max(axis=(1, 2)), 1e-6)[:, None, None],\n", " \"norm\": _normalize(1 - e, mask, ok), \"inverted\": inverted, \"noise\": noise, \"denoised\": noisy,\n", " \"missing\": missing, \"contrast\": hi - lo, \"ok\": ok}\n", "\n", "\n", "def _otsu(e, bins=64):\n", " n = len(e)\n", " idx = np.minimum((e * bins).astype(int), bins - 1) + bins * np.arange(n)[:, None, None]\n", " hist = np.bincount(idx.ravel(), minlength=bins * n).reshape(n, bins).astype(float)\n", " centers = (np.arange(bins) + 0.5) / bins\n", " w = np.cumsum(hist, axis=1); m = np.cumsum(hist * centers, axis=1)\n", " tot, mt = w[:, -1:], m[:, -1:]\n", " between = (mt * w / tot - m) ** 2 / np.maximum(w * (tot - w), 1)\n", " return (np.argmax(between, axis=1) + 1) / bins\n", "\n", "\n", "def _normalize(obj, mask, ok):\n", " \"\"\"์ฃผ์ถ• ๋ฐฉํ–ฅ์„ ์„ธ๋กœ๋กœ ๋Œ๋ฆฌ๊ณ  ๋น„ํ–‰์ฒด ์˜์—ญ๋งŒ 24x24๋กœ ๋‹ค์‹œ ์ƒ˜ํ”Œ๋งํ•œ๋‹ค(ํšŒ์ „ยทํฌ๊ธฐยท์œ„์น˜ ๋ถˆ๋ณ€). ํ•œ ๋ฒˆ์˜ ๋ณด๊ฐ„์œผ๋กœ ์ผ๊ด„ ์ฒ˜๋ฆฌ.\"\"\"\n", " n = len(obj)\n", " w = mask.astype(float)\n", " m00 = np.maximum(w.sum(axis=(1, 2)), 1)\n", " yy, xx = np.mgrid[0:IMG, 0:IMG].astype(float)\n", " cy, cx = (w * yy).sum(axis=(1, 2)) / m00, (w * xx).sum(axis=(1, 2)) / m00\n", " dy, dx = yy - cy[:, None, None], xx - cx[:, None, None]\n", " mu20, mu02, mu11 = (w * dy * dy).sum(axis=(1, 2)), (w * dx * dx).sum(axis=(1, 2)), (w * dy * dx).sum(axis=(1, 2))\n", " th = 0.5 * np.arctan2(2 * mu11, mu20 - mu02)\n", " a = np.stack([np.cos(th), np.sin(th)], 1) # ์ฃผ์ถ•(y, x)\n", " b = np.stack([-np.sin(th), np.cos(th)], 1) # ๋ถ€์ถ•\n", " pa = np.where(mask, dy * a[:, 0, None, None] + dx * a[:, 1, None, None], 0)\n", " pb = np.where(mask, dy * b[:, 0, None, None] + dx * b[:, 1, None, None], 0)\n", " half = np.maximum(np.abs(pa).max(axis=(1, 2)), np.abs(pb).max(axis=(1, 2))) + 1.0\n", " g = np.linspace(-1, 1, OUT)\n", " U, V = np.meshgrid(g, g, indexing=\"ij\")\n", " Y = cy[:, None, None] + half[:, None, None] * (U * a[:, 0, None, None] + V * b[:, 0, None, None])\n", " X = cx[:, None, None] + half[:, None, None] * (U * a[:, 1, None, None] + V * b[:, 1, None, None])\n", " N = np.broadcast_to(np.arange(n)[:, None, None], Y.shape)\n", " out = ndimage.map_coordinates(obj * mask, [N, Y, X], order=1, cval=0.0)\n", " skew = (out * (U ** 3)).sum(axis=(1, 2))\n", " out[skew < 0] = out[skew < 0, ::-1] # ๋ฌด๊ฒŒ๊ฐ€ ์ ๋ฆฐ ์ชฝ์„ ์œ„๋กœ(180๋„ ๋ชจํ˜ธ์„ฑ ์ œ๊ฑฐ)\n", " out[~ok] = 0\n", " return np.clip(out, 0, 1)\n", "\n", "\n", "def shape_features(mask):\n", " \"\"\"๋ถ„ํ•  ๋งˆ์Šคํฌ 1์žฅ์—์„œ ํšŒ์ „์— ๋ฌด๊ด€ํ•œ ํ˜•ํƒœ ํŠน์ง• 8์ข…์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " if mask.sum() < 4:\n", " return dict.fromkeys(FEATS, 0.0)\n", " yx = np.argwhere(mask).astype(float)\n", " c = yx - yx.mean(axis=0)\n", " val, vec = np.linalg.eigh(np.cov(c.T) + 1e-6 * np.eye(2))\n", " t, s = c @ vec[:, 1], c @ vec[:, 0]\n", " length, width = np.ptp(t) + 1, np.ptp(s) + 1\n", " corners = np.vstack([yx + d for d in ([.5, .5], [.5, -.5], [-.5, .5], [-.5, -.5])])\n", " try:\n", " hull = ConvexHull(corners).volume\n", " except Exception:\n", " hull = float(mask.sum())\n", " bins = np.digitize(t, np.linspace(t.min(), t.max() + 1e-6, 4)[1:-1])\n", " w = np.array([np.ptp(s[bins == k]) + 1 if (bins == k).any() else 0 for k in range(3)]) / length\n", " boundary = mask & ~ndimage.binary_erosion(mask)\n", " return {\"elong\": float(np.sqrt(val[1] / max(val[0], 1e-6))), \"fill\": float(mask.sum() / (length * width)),\n", " \"solidity\": float(mask.sum() / max(hull, 1)), \"n_parts\": float(ndimage.label(mask)[1]),\n", " \"end_gap\": float(abs(w[0] - w[2])), \"mid_bulge\": float(w[1] - max(w[0], w[2])),\n", " \"w_max\": float(w.max()), \"compact\": float(boundary.sum() / np.sqrt(mask.sum()))}\n", "\n", "\n", "def feature_matrix(images):\n", " \"\"\"์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ, ํ˜•ํƒœ ํŠน์ง• ํ–‰๋ ฌ(N x 8), ์ •๊ทœํ™” ์ด๋ฏธ์ง€ ํ–‰๋ ฌ(N x 576)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " P = preprocess(images)\n", " F = np.array([[d[k] for k in FEATS] for d in map(shape_features, P[\"mask\"])])\n", " return P, F, P[\"norm\"].reshape(len(F), -1)\n", "\n", "# ---- ๋ถ„๋ฅ˜ ๋ชจ๋ธ ๊ณตํ†ต ํ•จ์ˆ˜(๋‹ค์ธต ํผ์…‰ํŠธ๋ก : ์ •๊ทœํ™” ์ด๋ฏธ์ง€ 576ํ”ฝ์…€ + ํ˜•ํƒœ ํŠน์ง• 8์ข…) ----\n", "def to_input(F, N, scaler):\n", " return np.c_[N, scaler.transform(F) * 0.3]\n", "\n", "\n", "def train_model(n_per_class=150, seed=5, conds=tuple(CONDITIONS), hidden=(64,)):\n", " X, y, _ = make_dataset(n_per_class, seed, conds)\n", " P, F, N = feature_matrix(X)\n", " scaler = StandardScaler().fit(F)\n", " model = MLPClassifier(hidden, max_iter=200, early_stopping=True, random_state=0)\n", " model.fit(to_input(F, N, scaler), [CLASSES.index(v) for v in y])\n", " return model, scaler\n", "\n", "\n", "def predict(model, scaler, images):\n", " \"\"\"(์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ, ํด๋ž˜์Šค๋ณ„ ํ™•๋ฅ  N x 6)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " P, F, N = feature_matrix(images)\n", " return P, model.predict_proba(to_input(F, N, scaler))\n", "\n", "CAPACITY = 200 # ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๋Š” ์ตœ๋Œ€ ์žฅ์ˆ˜(์ดˆ๊ณผ ์‹œ ๋Œ€๊ธฐ์—ด๋กœ ๋‚˜๋ˆ  ์ฒ˜๋ฆฌ = ์„ฑ๋Šฅ์ €ํ•˜ ๋ชจ๋“œ)\n", "MIN_CONF = 0.5 # ์ด๋ณด๋‹ค ๋‚ฎ์œผ๋ฉด ์ •๋‹ต์„ ๋‹จ์ •ํ•˜์ง€ ์•Š๊ณ  'ํŒ๋ณ„ ๋ถˆ๊ฐ€(๊ต๊ด€ ํ™•์ธ)'\n", "\n", "\n", "def serve(requests, stage):\n", " \"\"\"์š”์ฒญ ์ „๋ถ€์— ๊ฒฐ๊ณผ๋ฅผ ํ•˜๋‚˜์”ฉ ๋Œ๋ ค์ค€๋‹ค. ์†์ƒ ์ž…๋ ฅ์€ '์ž…๋ ฅ ์˜ค๋ฅ˜', ์ €์‹ ๋ขฐ๋Š” 'ํŒ๋ณ„ ๋ถˆ๊ฐ€'๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•œ๋‹ค.\"\"\"\n", " results, valid = {}, []\n", " for rid, img in requests:\n", " a = np.asarray(img, dtype=float) if img is not None else None\n", " if a is None or a.shape != (IMG, IMG) or np.isnan(a).mean() > 0.5:\n", " results[rid] = \"์ž…๋ ฅ ์˜ค๋ฅ˜(์žฌ์ „์†ก ์š”์ฒญ)\"\n", " else:\n", " valid.append((rid, a))\n", " for k in range(0, len(valid), CAPACITY): # ๋Œ€๊ธฐ์—ด ์ฒญํฌ ์ฒ˜๋ฆฌ\n", " ids, imgs = zip(*valid[k:k + CAPACITY])\n", " t0 = time.perf_counter(); P = preprocess(np.array(imgs)); t1 = time.perf_counter()\n", " F = np.array([[d[f] for f in FEATS] for d in map(shape_features, P[\"mask\"])]); t2 = time.perf_counter()\n", " prob = model.predict_proba(to_input(F, P[\"norm\"].reshape(len(F), -1), scaler)); t3 = time.perf_counter()\n", " stage[\"์ „์ฒ˜๋ฆฌ\"] += t1 - t0; stage[\"ํ˜•ํƒœํŠน์ง•\"] += t2 - t1; stage[\"์ถ”๋ก \"] += t3 - t2\n", " for rid, ok, p in zip(ids, P[\"ok\"], prob):\n", " results[rid] = CLASSES[int(p.argmax())] if ok and p.max() >= MIN_CONF else \"ํŒ๋ณ„ ๋ถˆ๊ฐ€(๊ต๊ด€ ํ™•์ธ)\"\n", " return results\n", "\n", "\n", "def load_case(n, seed, blur=(1.2, 2.2), corrupt=0.0):\n", " rng = np.random.default_rng(seed)\n", " y = rng.choice(CLASSES, n)\n", " reqs = [(f\"R{i:05d}\", draw(c, rng, blur=rng.uniform(*blur), contrast=rng.uniform(.5, 1), noise=10)) for i, c in enumerate(y)]\n", " for i in rng.choice(n, int(n * corrupt), replace=False):\n", " reqs[i] = (reqs[i][0], [None, np.full((IMG, IMG), np.nan), np.zeros((20, 20))][i % 3])\n", " return reqs, dict(zip([r[0] for r in reqs], y))\n", "\n", "\n", "model, scaler = train_model(120, seed=5)\n", "rows, stage = [], {\"์ „์ฒ˜๋ฆฌ\": 0.0, \"ํ˜•ํƒœํŠน์ง•\": 0.0, \"์ถ”๋ก \": 0.0}\n", "for name, n, kw in [(\"ํ‰์‹œ 100์žฅ\", 100, {}), (\"300% ์ฆ๊ฐ€ 400์žฅ\", 400, {}), (\"500% ์ฆ๊ฐ€ 600์žฅ\", 600, {}),\n", " (\"500% + ์†์ƒ ์ž…๋ ฅ 3%\", 600, {\"corrupt\": .03}), (\"500% + ๊ทน์ €ํ™”์งˆ(ํ๋ฆผ 3.0)\", 600, {\"blur\": (2.8, 3.2)})]:\n", " reqs, truth = load_case(n, len(rows) + 1, **kw)\n", " t0 = time.perf_counter()\n", " try:\n", " res, down = serve(reqs, stage), 0\n", " except Exception as e:\n", " res, down = {}, 1; print(\"๋‹ค์šด:\", e)\n", " dt = time.perf_counter() - t0\n", " judged = [r for r, v in res.items() if v in CLASSES]\n", " rows.append({\"์ƒํ™ฉ\": name, \"์š”์ฒญ\": n, \"์‘๋‹ต\": len(res), \"์œ ์‹ค๋ฅ \": 1 - len(res) / n, \"๋‹ค์šด\": down,\n", " \"ํŒ๋ณ„๋ถˆ๊ฐ€\": sum(v.startswith(\"ํŒ๋ณ„\") for v in res.values()), \"์ž…๋ ฅ์˜ค๋ฅ˜\": sum(v.startswith(\"์ž…๋ ฅ\") for v in res.values()),\n", " \"ํŒ๋ณ„ ์ •ํ™•๋„\": np.mean([res[r] == truth[r] for r in judged]) if judged else 0, \"์ฒ˜๋ฆฌ๋Ÿ‰(์žฅ/s)\": int(n / dt),\n", " \"์„ฑ๋Šฅ์ €ํ•˜ ๋ชจ๋“œ\": f\"์ฒญํฌ {int(np.ceil(n / CAPACITY))}ํšŒ\" if n > CAPACITY else \"-\"})\n", "df = pd.DataFrame(rows)\n", "print(df.round(3).to_string(index=False))\n", "print(f\"\\n์‹œ์Šคํ…œ ๋‹ค์šด {df['๋‹ค์šด'].sum()}๊ฑด, ์ตœ๋Œ€ ์œ ์‹ค๋ฅ  {df['์œ ์‹ค๋ฅ '].max():.0%} (๋ชฉํ‘œ ๋‹ค์šด 0๊ฑดยท์œ ์‹ค 0%: {'๋‹ฌ์„ฑ' if df['๋‹ค์šด'].sum() == 0 and df['์œ ์‹ค๋ฅ '].max() == 0 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# ํ๋ฆผ ํ•œ๊ณ„์น˜: ํ๋ฆผ ์ •๋„๋ฅผ ์˜ฌ๋ฆฌ๋ฉฐ ์ •ํ™•๋„๊ฐ€ 75% ์•„๋ž˜๋กœ ๋–จ์–ด์ง€๋Š” ์ง€์ \n", "print(\"\\nํ๋ฆผ(sigma) ํŒ๋ณ„ ์ •ํ™•๋„ ํŒ๋ณ„๋ถˆ๊ฐ€ ๋น„์œจ\")\n", "limit = None\n", "for b in [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5]:\n", " reqs, truth = load_case(150, 50, blur=(b, b))\n", " res = serve(reqs, {\"์ „์ฒ˜๋ฆฌ\": 0, \"ํ˜•ํƒœํŠน์ง•\": 0, \"์ถ”๋ก \": 0})\n", " judged = [r for r, v in res.items() if v in CLASSES]\n", " acc = np.mean([res[r] == truth[r] for r in judged])\n", " if limit is None and acc < 0.75:\n", " limit = b\n", " print(f\"{b:>8} {acc:.3f} {1 - len(judged) / len(res):.3f}\")\n", "print(f\"ํ•œ๊ณ„์น˜: ํ๋ฆผ {limit} ์ด์ƒ์—์„œ ์ •ํ™•๋„ 75% ๋ฏธ๋งŒ -> ํ•ด๋‹น ์˜์ƒ์€ ํ•™์Šต ๋ฌธํ•ญ์—์„œ ์ œ์™ธํ•˜๊ฑฐ๋‚˜ '๊ณ ๋‚œ๋„'๋กœ ๋ถ„๋ฅ˜\")\n", "\n", "# ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ํ™•์ธ: ๊ฐ™์€ ๋ถ€ํ•˜๋ฅผ 3ํšŒ ๋ฐ˜๋ณตํ•ด ๋ฉ”๋ชจ๋ฆฌ ์ฆ๊ฐ€๋Ÿ‰ ์ธก์ •\n", "tracemalloc.start()\n", "reqs, _ = load_case(300, 99)\n", "mem = []\n", "for _ in range(3):\n", " serve(reqs, {\"์ „์ฒ˜๋ฆฌ\": 0, \"ํ˜•ํƒœํŠน์ง•\": 0, \"์ถ”๋ก \": 0}); mem.append(tracemalloc.get_traced_memory()[0] / 1e6)\n", "peak = tracemalloc.get_traced_memory()[1] / 1e6\n", "tracemalloc.stop()\n", "print(f\"๋ฐ˜๋ณต ํ›„ ๋ฉ”๋ชจ๋ฆฌ {', '.join(f'{m:.1f}' for m in mem)} MB (์ตœ๋Œ€ {peak:.1f} MB), ์ฆ๊ฐ€ ์—†์Œ: {mem[-1] - mem[0] < 1}\")\n", "tot = sum(stage.values())\n", "print(\"๋ณ‘๋ชฉ ๊ตฌ๊ฐ„:\", \", \".join(f\"{k} {v / tot:.0%}\" for k, v in sorted(stage.items(), key=lambda x: -x[1])), \"-> ํ˜•ํƒœํŠน์ง• ๊ณ„์‚ฐ์„ ๋ณ‘๋ ฌํ™”ํ•˜๋ฉด ์ฒ˜๋ฆฌ๋Ÿ‰์ด ๊ฐ€์žฅ ํฌ๊ฒŒ ์˜ค๋ฅธ๋‹ค\")\n", "\n", "plt.figure(figsize=(8, 4))\n", "plt.bar(range(len(df)), df[\"์ฒ˜๋ฆฌ๋Ÿ‰(์žฅ/s)\"], color=[\"tab:red\" if m != \"-\" else \"tab:blue\" for m in df[\"์„ฑ๋Šฅ์ €ํ•˜ ๋ชจ๋“œ\"]])\n", "plt.xticks(range(len(df)), [f\"case{i + 1}\" for i in range(len(df))]); plt.ylabel(\"images / s\")\n", "plt.title(\"Stress test throughput (red = queued/degraded mode)\"); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 08 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", " ์ƒํ™ฉ ์š”์ฒญ ์‘๋‹ต ์œ ์‹ค๋ฅ  ๋‹ค์šด ํŒ๋ณ„๋ถˆ๊ฐ€ ์ž…๋ ฅ์˜ค๋ฅ˜ ํŒ๋ณ„ ์ •ํ™•๋„ ์ฒ˜๋ฆฌ๋Ÿ‰(์žฅ/s) ์„ฑ๋Šฅ์ €ํ•˜ ๋ชจ๋“œ\n", " ํ‰์‹œ 100์žฅ 100 100 0.0 0 37 0 0.889 731 -\n", " 300% ์ฆ๊ฐ€ 400์žฅ 400 400 0.0 0 155 0 0.918 706 ์ฒญํฌ 2ํšŒ\n", " 500% ์ฆ๊ฐ€ 600์žฅ 600 600 0.0 0 221 0 0.918 707 ์ฒญํฌ 3ํšŒ\n", " 500% + ์†์ƒ ์ž…๋ ฅ 3% 600 600 0.0 0 216 18 0.915 802 ์ฒญํฌ 3ํšŒ\n", "500% + ๊ทน์ €ํ™”์งˆ(ํ๋ฆผ 3.0) 600 600 0.0 0 366 0 0.654 660 ์ฒญํฌ 3ํšŒ\n", "\n", "์‹œ์Šคํ…œ ๋‹ค์šด 0๊ฑด, ์ตœ๋Œ€ ์œ ์‹ค๋ฅ  0% (๋ชฉํ‘œ ๋‹ค์šด 0๊ฑดยท์œ ์‹ค 0%: ๋‹ฌ์„ฑ)\n", "\n", "ํ๋ฆผ(sigma) ํŒ๋ณ„ ์ •ํ™•๋„ ํŒ๋ณ„๋ถˆ๊ฐ€ ๋น„์œจ\n", " 0.5 1.000 0.020\n", " 1.0 0.992 0.167\n", " 1.5 0.937 0.367\n", " 2.0 0.825 0.467\n", " 2.5 0.710 0.587\n", " 3.0 0.566 0.647\n", " 3.5 0.462 0.653\n", "ํ•œ๊ณ„์น˜: ํ๋ฆผ 2.5 ์ด์ƒ์—์„œ ์ •ํ™•๋„ 75% ๋ฏธ๋งŒ -> ํ•ด๋‹น ์˜์ƒ์€ ํ•™์Šต ๋ฌธํ•ญ์—์„œ ์ œ์™ธํ•˜๊ฑฐ๋‚˜ '๊ณ ๋‚œ๋„'๋กœ ๋ถ„๋ฅ˜\n", "๋ฐ˜๋ณต ํ›„ ๋ฉ”๋ชจ๋ฆฌ 0.4, 0.4, 0.4 MB (์ตœ๋Œ€ 30.4 MB), ์ฆ๊ฐ€ ์—†์Œ: True\n", "๋ณ‘๋ชฉ ๊ตฌ๊ฐ„: ํ˜•ํƒœํŠน์ง• 68%, ์ „์ฒ˜๋ฆฌ 31%, ์ถ”๋ก  1% -> ํ˜•ํƒœํŠน์ง• ๊ณ„์‚ฐ์„ ๋ณ‘๋ ฌํ™”ํ•˜๋ฉด ์ฒ˜๋ฆฌ๋Ÿ‰์ด ๊ฐ€์žฅ ํฌ๊ฒŒ ์˜ค๋ฅธ๋‹ค\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 09. AAR ๊ธฐ๋ฐ˜ ์žฅ๋ณ‘ ๋‹ค๋นˆ๋„ ์˜ค๋‹ต ๋น„ํ–‰์ฒด ๋ถ„์„ ๋ฐ ํ•™์Šต ๋ฐ์ดํ„ฐ์…‹ ์ฆ๊ฐ• ์žฌํ•™์Šต\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "AAR ๊ธฐ๋ฐ˜ ์žฅ๋ณ‘ ๋‹ค๋นˆ๋„ ์˜ค๋‹ต ๋น„ํ–‰์ฒด ๋ถ„์„ ๋ฐ ํ•™์Šต ๋ฐ์ดํ„ฐ๋ฅผ ์ฆ๊ฐ• ์žฌํ•™์Šตํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **AAR ์‚ฌํ›„๊ฒ€ํ† ๋กœ ์žฅ๋ณ‘์˜ ๋‹ค๋นˆ๋„ ์˜ค๋‹ต ๋น„ํ–‰์ฒด์™€ AI ๋ถ„๋ฅ˜๊ธฐ์˜ ์˜ค๋ฅ˜ ์กฐ๊ฑด์„ ๋ถ„์„ํ•˜๊ณ , ์ทจ์•ฝ ๋ฐ์ดํ„ฐ๋ฅผ ์ฆ๊ฐ•ํ•ด ์žฌํ•™์Šตํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ฐ€์ƒ ์žฅ๋ณ‘ 40๋ช…์˜ ํ•™์Šต ๊ธฐ๋ก์—์„œ ๋‹ค๋นˆ๋„ ์˜ค๋‹ต ์Œ์„ ์ฐพ๊ณ , ๊ธฐ๋ณธ ์กฐ๊ฑด ์ค‘์‹ฌ์œผ๋กœ ํ•™์Šตํ•œ ํ˜„ํ–‰ ๋ชจ๋ธ์˜ ์˜ค๋ฅ˜๋ฅผ ์กฐ๊ฑดยทํด๋ž˜์Šค๋ณ„๋กœ ๋ถ„๋ฅ˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/trn03_aar_report.md ๋ฐ configs/trn03_tuned_params.yaml` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์˜ค๋ฅ˜ ์›์ธ ๊ทœ๋ช…์˜ ๊ฐ๊ด€์„ฑ ๋ฐ ๊ฐœ์„  ์กฐ์น˜์˜ ๋…ผ๋ฆฌ์  ํƒ€๋‹น์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: AAR ํ”ผ๋“œ๋ฐฑ ์ ์šฉ ํ›„ ์ข…ํ•ฉ ์ •ํ™•๋„ 10%p ์ด์ƒ ํ–ฅ์ƒ, ์žฌ๋ฐœ ์˜ค๋ฅ˜ 0๊ฑด\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: AAR ๋ณด๊ณ ์„œ ์ž‘์„ฑ ๋ฐ ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ์ „ํ›„ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ํšŒ๊ท€ ํ…Œ์ŠคํŠธ(Regression Test) ์Šค์œ„ํŠธ ์ „์ฒด ์ž๋™ ์‹คํ–‰ ๋ฐ ๋น„๊ต\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"AAR(์‚ฌํ›„๊ฒ€ํ† ) ํ˜•์‹์œผ๋กœ ์žฅ๋ณ‘ ํ•™์Šต ๊ธฐ๋ก์˜ ๋‹ค๋นˆ๋„ ์˜ค๋‹ต ๋น„ํ–‰์ฒด์™€ AI ๋ถ„๋ฅ˜๊ธฐ์˜ ์˜ค๋ฅ˜ ์กฐ๊ฑด์„ ์›์ธ๋ณ„๋กœ ๋ถ„์„ํ•˜๊ณ , ์ทจ์•ฝ ์กฐ๊ฑดยทํด๋ž˜์Šค๋ฅผ ์ฆ๊ฐ•ํ•œ ๋ฐ์ดํ„ฐ๋กœ ์žฌํ•™์Šตํ•ด ์ข…ํ•ฉ ์ •ํ™•๋„ ํ–ฅ์ƒยท์žฌ๋ฐœ ์˜ค๋ฅ˜ยทํšŒ๊ท€ ์—ฌ๋ถ€์™€ ์žฌ๋ณด์ • ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฐ์ถœํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from matplotlib.path import Path as MplPath\n", "from scipy import ndimage\n", "from scipy.spatial import ConvexHull\n", "from sklearn.neural_network import MLPClassifier\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•ฉ์„ฑ ์‹ค๋ฃจ์—ฃ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ธฐ์ฒด ์‚ฌ์ง„ยท๋„๋ฉดยท์ œ์› ์‚ฌ์šฉ ์•ˆ ํ•จ)\n", "# ์‚ฌ๊ฐํ˜•ยท์‚ผ๊ฐํ˜•ยท์› ๊ฐ™์€ ๊ธฐ๋ณธ ๋„ํ˜•์„ ์กฐํ•ฉํ•œ ์ถ”์ƒ ์‹ค๋ฃจ์—ฃ์„ 32x32 ํ‘๋ฐฑ ์ด๋ฏธ์ง€๋กœ ๊ทธ๋ฆฐ๋‹ค.\n", "# ํšŒ์ „(๋‹ค๊ฐ๋„), ์›๊ทผ ์ถ•์†Œ(์ธก๋ฉด ์‹œ์ ), ํฌ๊ธฐ(์›๊ฑฐ๋ฆฌ), ํ๋ฆผยท์žก์Œยท์ €๋Œ€๋น„ยท๊ตฌ๋ฆ„ยท๊ฐ€๋ฆผยท์•ผ๊ฐ„ ๋ฐ˜์ „์„ ์กฐ์ ˆํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "IMG = 32\n", "CLASSES = [\"civil_quad\", \"civil_fw\", \"attack_fw\", \"cruise\", \"ballistic\", \"bird\"]\n", "KOR = {\"civil_quad\": \"๋ฏผ๊ฐ„ ์ฟผ๋“œ์ฝฅํ„ฐํ˜•\", \"civil_fw\": \"๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•\", \"attack_fw\": \"์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜•\",\n", " \"cruise\": \"์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜•\", \"ballistic\": \"ํƒ„๋„๋ฏธ์‚ฌ์ผํ˜•\", \"bird\": \"์กฐ๋ฅ˜\"}\n", "THREAT = {\"attack_fw\", \"cruise\", \"ballistic\"}\n", "\n", "\n", "def _rect(x0, x1, y0, y1):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 9] AAR ๊ธฐ๋ฐ˜ ์žฅ๋ณ‘ ๋‹ค๋นˆ๋„ ์˜ค๋‹ต ๋น„ํ–‰์ฒด ๋ถ„์„ ๋ฐ ํ•™์Šต ๋ฐ์ดํ„ฐ์…‹ ์ฆ๊ฐ• ์žฌํ•™์Šต ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def _bar(p, q, w):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 9] AAR ๊ธฐ๋ฐ˜ ์žฅ๋ณ‘ ๋‹ค๋นˆ๋„ ์˜ค๋‹ต ๋น„ํ–‰์ฒด ๋ถ„์„ ๋ฐ ํ•™์Šต ๋ฐ์ดํ„ฐ์…‹ ์ฆ๊ฐ• ์žฌํ•™์Šต ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "SHAPES = {\n", " \"civil_quad\": {\"poly\": [_bar((-.55, -.55), (.55, .55), .1), _bar((-.55, .55), (.55, -.55), .1), _rect(-.15, .15, -.15, .15)],\n", " \"circ\": [(-.55, -.55, .24), (.55, .55, .24), (-.55, .55, .24), (.55, -.55, .24)]},\n", " \"civil_fw\": {\"poly\": [_rect(-.07, .07, -.8, .72), _rect(-.9, .9, .05, .25), _rect(-.33, .33, -.8, -.66)], \"circ\": []},\n", " \"attack_fw\": {\"poly\": [[(0, .85), (.85, -.5), (.12, -.42), (0, -.6), (-.12, -.42), (-.85, -.5)]], \"circ\": []},\n", " \"cruise\": {\"poly\": [_rect(-.07, .07, -.9, .7), [(-.07, .7), (0, .92), (.07, .7)], _rect(-.42, .42, -.05, .07),\n", " [(-.07, -.9), (-.24, -.95), (-.07, -.74)], [(.07, -.9), (.24, -.95), (.07, -.74)]], \"circ\": []},\n", " \"ballistic\": {\"poly\": [_rect(-.13, .13, -.85, .6), [(-.13, .6), (0, .95), (.13, .6)],\n", " [(-.13, -.85), (-.34, -.95), (-.13, -.58)], [(.13, -.85), (.34, -.95), (.13, -.58)]], \"circ\": []},\n", " \"bird\": {\"poly\": [[(-.9, .12), (-.5, .28), (-.12, .06), (0, .22), (.12, .06), (.5, .28), (.9, .12), (.5, .12),\n", " (.12, -.1), (0, -.38), (-.12, -.1), (-.5, .12)]], \"circ\": []},\n", "}\n", "_S = 2\n", "_g = (np.arange(IMG * _S) + 0.5) / _S\n", "_GX, _GY = np.meshgrid(_g, _g)\n", "_PTS = np.c_[_GX.ravel(), _GY.ravel()]\n", "_PATHS = {c: [MplPath(np.array(p)) for p in s[\"poly\"]] for c, s in SHAPES.items()}\n", "\n", "\n", "def draw(cls, rng, angle=None, scale=None, fore=None, blur=0.3, noise=4.0, contrast=1.0,\n", " clutter=0.0, occlude=0.0, invert=False, shift=1.5):\n", " \"\"\"ํด๋ž˜์Šค 1๊ฐœ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€(uint8 32x32)๋ฅผ ๊ทธ๋ฆฐ๋‹ค. ๋ฐฐ๊ฒฝ์€ ๋ฐ์€ ํ•˜๋Š˜, ๋น„ํ–‰์ฒด๋Š” ์–ด๋‘์šด ๊ทธ๋ฆผ์ž.\"\"\"\n", " angle = rng.uniform(0, 360) if angle is None else angle\n", " scale = rng.uniform(.7, .95) if scale is None else scale\n", " fore = rng.uniform(.85, 1.0) if fore is None else fore\n", " th = np.radians(angle)\n", " c0 = IMG / 2 + rng.uniform(-shift, shift, 2)\n", " d = (_PTS - c0) / (scale * IMG / 2) # ์ด๋ฏธ์ง€ ์ขŒํ‘œ -> ๋„ํ˜• ์ขŒํ‘œ(์—ญ๋ณ€ํ™˜)\n", " u = d[:, 0] * np.cos(th) + d[:, 1] * np.sin(th)\n", " v = (-d[:, 0] * np.sin(th) + d[:, 1] * np.cos(th)) / fore # ์›๊ทผ ์ถ•์†Œ(๊ธฐ์ˆ˜ ๋ฐฉํ–ฅ์œผ๋กœ ์งง์•„ ๋ณด์ž„)\n", " uv = np.c_[u, -v]\n", " m = np.zeros(len(uv), bool)\n", " near = np.flatnonzero(np.abs(uv).max(axis=1) <= 1.0) # ๋„ํ˜•์ด ์žˆ์„ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ๋งŒ ๊ฒ€์‚ฌ(์†๋„)\n", " for p in _PATHS[cls]:\n", " m[near] |= p.contains_points(uv[near])\n", " for cx, cy, r in SHAPES[cls][\"circ\"]:\n", " m |= (uv[:, 0] - cx) ** 2 + (uv[:, 1] - cy) ** 2 <= r * r\n", " cov = m.reshape(IMG, _S, IMG, _S).mean(axis=(1, 3))\n", " if occlude > 0: # ๋ถ€๋ถ„ ๊ฐ€๋ฆผ(๊ตฌ๋ฆ„ ์กฐ๊ฐ)\n", " h = int(IMG * np.sqrt(occlude)); y0, x0 = rng.integers(8, IMG - 8 - h // 2, 2)\n", " cov[y0:y0 + h, x0:x0 + h] = 0\n", " bg = rng.uniform(180, 220) + np.linspace(-10, 10, IMG)[:, None]\n", " fg = rng.uniform(30, 60)\n", " img = bg * (1 - cov) + fg * cov\n", " if clutter > 0: # ๊ตฌ๋ฆ„ยท๋ฐฐ๊ฒฝ ์–ผ๋ฃฉ\n", " img -= clutter * 90 * np.clip(ndimage.gaussian_filter(rng.normal(0, 1, (IMG, IMG)), 3) * 4, 0, None)\n", " if blur > 0:\n", " img = ndimage.gaussian_filter(img, blur)\n", " mean = img.mean()\n", " img = mean + (img - mean) * contrast\n", " img = img + rng.normal(0, noise, img.shape)\n", " if invert: # ์•ผ๊ฐ„ ์—ด์ƒ(IR) ๊ทน์„ฑ ๋ฐ˜์ „\n", " img = 255 - img\n", " return np.clip(img, 0, 255).astype(np.uint8)\n", "\n", "\n", "# ์ดฌ์˜ ์กฐ๊ฑด ํ”„๋ฆฌ์…‹: ๊ธฐ๋ณธ + ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹œ๋‚˜๋ฆฌ์˜ค\n", "CONDITIONS = {\n", " \"basic\": {},\n", " \"offcenter\": {\"shift\": 5.0, \"fore\": (.6, .85)},\n", " \"far\": {\"scale\": (.32, .45), \"blur\": .6},\n", " \"blur\": {\"blur\": (1.2, 1.8)},\n", " \"lowcon\": {\"contrast\": (.2, .35)},\n", " \"clutter\": {\"clutter\": .8},\n", " \"occlude\": {\"occlude\": .08},\n", " \"oblique\": {\"fore\": (.35, .6)},\n", " \"night\": {\"invert\": True, \"noise\": 10.0},\n", " \"noise\": {\"noise\": 28.0},\n", " \"combo\": {\"scale\": (.4, .55), \"blur\": (.8, 1.1), \"noise\": 12.0, \"contrast\": (.6, .8)},\n", "}\n", "\n", "\n", "def draw_cond(cls, cond, rng):\n", " kw = {}\n", " for k, v in CONDITIONS[cond].items():\n", " kw[k] = rng.uniform(*v) if isinstance(v, tuple) else v\n", " return draw(cls, rng, **kw)\n", "\n", "\n", "def make_dataset(n_per_class, seed, conds=(\"basic\",)):\n", " \"\"\"(์ด๋ฏธ์ง€ ๋ฐฐ์—ด N x 32 x 32, ์ •๋‹ต ๋ผ๋ฒจ, ์กฐ๊ฑด) ๋ฐ˜ํ™˜. ์กฐ๊ฑด์€ conds์—์„œ ๋ฒˆ๊ฐˆ์•„ ๊ณ ๋ฅธ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " X, y, c = [], [], []\n", " for cls in CLASSES:\n", " for i in range(n_per_class):\n", " cond = conds[i % len(conds)]\n", " X.append(draw_cond(cls, cond, rng)); y.append(cls); c.append(cond)\n", " return np.array(X), np.array(y), np.array(c)\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---- ์ „์ฒ˜๋ฆฌ(๋ฐฐ์น˜ ๋ฒกํ„ฐํ™”)ยทํ˜•ํƒœ ํŠน์ง• ๊ณตํ†ต ํ•จ์ˆ˜ ----\n", "OUT = 24 # ํฌ๊ธฐ ์ •๊ทœํ™” ๊ฒฐ๊ณผ(24x24)\n", "NOISE_T = 12.0 # ์žก์Œ ์ถ”์ •๊ฐ’์ด ์ด๋ณด๋‹ค ํฌ๋ฉด 3x3 ์ค‘๊ฐ„๊ฐ’ ํ•„ํ„ฐ ์ ์šฉ(์•…์ฒœํ›„ยท์„ผ์„œ ์žก์Œ)\n", "FEATS = [\"elong\", \"fill\", \"solidity\", \"n_parts\", \"end_gap\", \"mid_bulge\", \"w_max\", \"compact\"]\n", "\n", "\n", "def preprocess(images, noise_t=NOISE_T):\n", " \"\"\"N์žฅ ์ผ๊ด„ ์ฒ˜๋ฆฌ: ๊ฒฐ์ธก ๋ณด์ • -> ๊ทน์„ฑ(์•ผ๊ฐ„ ๋ฐ˜์ „) ๋ณด์ • -> ์กฐ๊ฑด๋ณ„ ์žก์Œ ์ œ๊ฑฐ -> ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™” -> ๋ถ„ํ•  -> ์œค๊ณฝ์„  -> ๋ฐฉํ–ฅยทํฌ๊ธฐ ์ •๊ทœํ™”.\"\"\"\n", " f = np.array(images, dtype=float).reshape(-1, IMG, IMG)\n", " n = len(f)\n", " bad = ~np.isfinite(f)\n", " missing = bad.sum(axis=(1, 2))\n", " if bad.any(): # ๊ฒฐ์ธก ํ”ฝ์…€: ์˜์ƒ ์ค‘์•™๊ฐ’์œผ๋กœ ์ฑ„์šด ๋’ค ์ฃผ๋ณ€ 3x3 ์ค‘๊ฐ„๊ฐ’์œผ๋กœ ๋ณด์ •\n", " med = np.array([np.median(x[np.isfinite(x)]) if np.isfinite(x).any() else 128.0 for x in f])\n", " f = np.where(bad, med[:, None, None], f)\n", " f = np.where(bad, ndimage.median_filter(f, size=(1, 3, 3)), f)\n", " border = np.concatenate([f[:, 0], f[:, -1], f[:, :, 0], f[:, :, -1]], axis=1)\n", " inverted = np.median(border, axis=1) < 128 # ๋ฐฐ๊ฒฝ์ด ์–ด๋‘์šฐ๋ฉด ์•ผ๊ฐ„ ์—ด์ƒ์œผ๋กœ ๋ณด๊ณ  ๋ฐ˜์ „\n", " f[inverted] = 255 - f[inverted]\n", " med3 = ndimage.median_filter(f, size=(1, 3, 3))\n", " noise = 1.4826 * np.median(np.abs(f - med3).reshape(n, -1), axis=1)\n", " noisy = noise > noise_t\n", " f[noisy] = med3[noisy]\n", " lo, hi = np.percentile(f.reshape(n, -1), [1, 99], axis=1)\n", " e = np.clip((f - lo[:, None, None]) / np.maximum(hi - lo, 1e-6)[:, None, None], 0, 1) # ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”\n", " mask = e < _otsu(e)[:, None, None]\n", " mask &= ndimage.uniform_filter(mask.astype(float), size=(1, 3, 3)) * 9 >= 2.5 # ๊ณ ๋ฆฝ ์  ์žก์Œ ์ œ๊ฑฐ\n", " edge = np.hypot(ndimage.sobel(e, axis=1), ndimage.sobel(e, axis=2)) # ์œค๊ณฝ์„ (Sobel)\n", " ok = (mask.sum(axis=(1, 2)) >= 4) & (hi - lo > 8)\n", " return {\"enh\": e, \"mask\": mask, \"edge\": edge / np.maximum(edge.max(axis=(1, 2)), 1e-6)[:, None, None],\n", " \"norm\": _normalize(1 - e, mask, ok), \"inverted\": inverted, \"noise\": noise, \"denoised\": noisy,\n", " \"missing\": missing, \"contrast\": hi - lo, \"ok\": ok}\n", "\n", "\n", "def _otsu(e, bins=64):\n", " n = len(e)\n", " idx = np.minimum((e * bins).astype(int), bins - 1) + bins * np.arange(n)[:, None, None]\n", " hist = np.bincount(idx.ravel(), minlength=bins * n).reshape(n, bins).astype(float)\n", " centers = (np.arange(bins) + 0.5) / bins\n", " w = np.cumsum(hist, axis=1); m = np.cumsum(hist * centers, axis=1)\n", " tot, mt = w[:, -1:], m[:, -1:]\n", " between = (mt * w / tot - m) ** 2 / np.maximum(w * (tot - w), 1)\n", " return (np.argmax(between, axis=1) + 1) / bins\n", "\n", "\n", "def _normalize(obj, mask, ok):\n", " \"\"\"์ฃผ์ถ• ๋ฐฉํ–ฅ์„ ์„ธ๋กœ๋กœ ๋Œ๋ฆฌ๊ณ  ๋น„ํ–‰์ฒด ์˜์—ญ๋งŒ 24x24๋กœ ๋‹ค์‹œ ์ƒ˜ํ”Œ๋งํ•œ๋‹ค(ํšŒ์ „ยทํฌ๊ธฐยท์œ„์น˜ ๋ถˆ๋ณ€). ํ•œ ๋ฒˆ์˜ ๋ณด๊ฐ„์œผ๋กœ ์ผ๊ด„ ์ฒ˜๋ฆฌ.\"\"\"\n", " n = len(obj)\n", " w = mask.astype(float)\n", " m00 = np.maximum(w.sum(axis=(1, 2)), 1)\n", " yy, xx = np.mgrid[0:IMG, 0:IMG].astype(float)\n", " cy, cx = (w * yy).sum(axis=(1, 2)) / m00, (w * xx).sum(axis=(1, 2)) / m00\n", " dy, dx = yy - cy[:, None, None], xx - cx[:, None, None]\n", " mu20, mu02, mu11 = (w * dy * dy).sum(axis=(1, 2)), (w * dx * dx).sum(axis=(1, 2)), (w * dy * dx).sum(axis=(1, 2))\n", " th = 0.5 * np.arctan2(2 * mu11, mu20 - mu02)\n", " a = np.stack([np.cos(th), np.sin(th)], 1) # ์ฃผ์ถ•(y, x)\n", " b = np.stack([-np.sin(th), np.cos(th)], 1) # ๋ถ€์ถ•\n", " pa = np.where(mask, dy * a[:, 0, None, None] + dx * a[:, 1, None, None], 0)\n", " pb = np.where(mask, dy * b[:, 0, None, None] + dx * b[:, 1, None, None], 0)\n", " half = np.maximum(np.abs(pa).max(axis=(1, 2)), np.abs(pb).max(axis=(1, 2))) + 1.0\n", " g = np.linspace(-1, 1, OUT)\n", " U, V = np.meshgrid(g, g, indexing=\"ij\")\n", " Y = cy[:, None, None] + half[:, None, None] * (U * a[:, 0, None, None] + V * b[:, 0, None, None])\n", " X = cx[:, None, None] + half[:, None, None] * (U * a[:, 1, None, None] + V * b[:, 1, None, None])\n", " N = np.broadcast_to(np.arange(n)[:, None, None], Y.shape)\n", " out = ndimage.map_coordinates(obj * mask, [N, Y, X], order=1, cval=0.0)\n", " skew = (out * (U ** 3)).sum(axis=(1, 2))\n", " out[skew < 0] = out[skew < 0, ::-1] # ๋ฌด๊ฒŒ๊ฐ€ ์ ๋ฆฐ ์ชฝ์„ ์œ„๋กœ(180๋„ ๋ชจํ˜ธ์„ฑ ์ œ๊ฑฐ)\n", " out[~ok] = 0\n", " return np.clip(out, 0, 1)\n", "\n", "\n", "def shape_features(mask):\n", " \"\"\"๋ถ„ํ•  ๋งˆ์Šคํฌ 1์žฅ์—์„œ ํšŒ์ „์— ๋ฌด๊ด€ํ•œ ํ˜•ํƒœ ํŠน์ง• 8์ข…์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " if mask.sum() < 4:\n", " return dict.fromkeys(FEATS, 0.0)\n", " yx = np.argwhere(mask).astype(float)\n", " c = yx - yx.mean(axis=0)\n", " val, vec = np.linalg.eigh(np.cov(c.T) + 1e-6 * np.eye(2))\n", " t, s = c @ vec[:, 1], c @ vec[:, 0]\n", " length, width = np.ptp(t) + 1, np.ptp(s) + 1\n", " corners = np.vstack([yx + d for d in ([.5, .5], [.5, -.5], [-.5, .5], [-.5, -.5])])\n", " try:\n", " hull = ConvexHull(corners).volume\n", " except Exception:\n", " hull = float(mask.sum())\n", " bins = np.digitize(t, np.linspace(t.min(), t.max() + 1e-6, 4)[1:-1])\n", " w = np.array([np.ptp(s[bins == k]) + 1 if (bins == k).any() else 0 for k in range(3)]) / length\n", " boundary = mask & ~ndimage.binary_erosion(mask)\n", " return {\"elong\": float(np.sqrt(val[1] / max(val[0], 1e-6))), \"fill\": float(mask.sum() / (length * width)),\n", " \"solidity\": float(mask.sum() / max(hull, 1)), \"n_parts\": float(ndimage.label(mask)[1]),\n", " \"end_gap\": float(abs(w[0] - w[2])), \"mid_bulge\": float(w[1] - max(w[0], w[2])),\n", " \"w_max\": float(w.max()), \"compact\": float(boundary.sum() / np.sqrt(mask.sum()))}\n", "\n", "\n", "def feature_matrix(images):\n", " \"\"\"์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ, ํ˜•ํƒœ ํŠน์ง• ํ–‰๋ ฌ(N x 8), ์ •๊ทœํ™” ์ด๋ฏธ์ง€ ํ–‰๋ ฌ(N x 576)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " P = preprocess(images)\n", " F = np.array([[d[k] for k in FEATS] for d in map(shape_features, P[\"mask\"])])\n", " return P, F, P[\"norm\"].reshape(len(F), -1)\n", "\n", "# ---- ๋ถ„๋ฅ˜ ๋ชจ๋ธ ๊ณตํ†ต ํ•จ์ˆ˜(๋‹ค์ธต ํผ์…‰ํŠธ๋ก : ์ •๊ทœํ™” ์ด๋ฏธ์ง€ 576ํ”ฝ์…€ + ํ˜•ํƒœ ํŠน์ง• 8์ข…) ----\n", "def to_input(F, N, scaler):\n", " return np.c_[N, scaler.transform(F) * 0.3]\n", "\n", "\n", "def train_model(n_per_class=150, seed=5, conds=tuple(CONDITIONS), hidden=(64,)):\n", " X, y, _ = make_dataset(n_per_class, seed, conds)\n", " P, F, N = feature_matrix(X)\n", " scaler = StandardScaler().fit(F)\n", " model = MLPClassifier(hidden, max_iter=200, early_stopping=True, random_state=0)\n", " model.fit(to_input(F, N, scaler), [CLASSES.index(v) for v in y])\n", " return model, scaler\n", "\n", "\n", "def predict(model, scaler, images):\n", " \"\"\"(์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ, ํด๋ž˜์Šค๋ณ„ ํ™•๋ฅ  N x 6)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " P, F, N = feature_matrix(images)\n", " return P, model.predict_proba(to_input(F, N, scaler))\n", "\n", "\n", "def fit(X, y):\n", " P, F, N = feature_matrix(X)\n", " sc = StandardScaler().fit(F)\n", " m = MLPClassifier((96,), max_iter=200, early_stopping=True, random_state=0).fit(to_input(F, N, sc), [CLASSES.index(v) for v in y])\n", " return m, sc\n", "\n", "\n", "def evaluate(m, sc, X):\n", " return np.array(CLASSES)[predict(m, sc, X)[1].argmax(1)]\n", "\n", "\n", "# [1. ๋ฌด์—‡์ด ์ผ์–ด๋‚ฌ๋‚˜] ์žฅ๋ณ‘ ํ•™์Šต ๊ธฐ๋ก(๊ฐ€์ƒ 40๋ช… x 30๋ฌธํ•ญ) ๋‹ค๋นˆ๋„ ์˜ค๋‹ต\n", "rng = np.random.default_rng(3)\n", "CONF = {\"civil_fw\": (\"attack_fw\", .30), \"attack_fw\": (\"civil_fw\", .25), \"cruise\": (\"ballistic\", .40), \"ballistic\": (\"cruise\", .35),\n", " \"bird\": (\"civil_fw\", .15), \"civil_quad\": (\"civil_fw\", .05)}\n", "log = []\n", "for s in range(40):\n", " for q in range(30):\n", " c = CLASSES[rng.integers(6)]\n", " wrong, p = CONF[c]\n", " log.append({\"์žฅ๋ณ‘\": f\"L{s + 1:02d}\", \"์ •๋‹ต\": c, \"์‘๋‹ต\": wrong if rng.random() < p else c})\n", "lg = pd.DataFrame(log)\n", "err = lg[lg[\"์ •๋‹ต\"] != lg[\"์‘๋‹ต\"]]\n", "print(f\"[1. ๋ฌด์—‡์ด ์ผ์–ด๋‚ฌ๋‚˜] ์‘๋‹ต {len(lg)}๊ฑด ์ค‘ ์˜ค๋‹ต {len(err)}๊ฑด({len(err) / len(lg):.1%})\")\n", "print(\"๋‹ค๋นˆ๋„ ์˜ค๋‹ต ์Œ:\", (err[\"์ •๋‹ต\"] + \"->\" + err[\"์‘๋‹ต\"]).value_counts().head(3).to_dict())\n", "\n", "# [2. ์™œ ์ผ์–ด๋‚ฌ๋‚˜] AI ๋ถ„๋ฅ˜๊ธฐ(ํ˜„ํ–‰: ๊ธฐ๋ณธยทํšŒ์ „ยท์ €๋Œ€๋น„ยท์•ผ๊ฐ„ ์กฐ๊ฑด ์ค‘์‹ฌ ํ•™์Šต) ์˜ค๋ฅ˜๋ฅผ ์กฐ๊ฑด๋ณ„๋กœ ๋ถ„๋ฅ˜\n", "base_conds = (\"basic\", \"offcenter\", \"lowcon\", \"night\")\n", "Xtr, ytr, _ = make_dataset(150, 5, base_conds)\n", "m0, s0 = fit(Xtr, ytr)\n", "Xev, yev, cev = make_dataset(110, 31, tuple(CONDITIONS)) # AAR ํ‰๊ฐ€ ์„ธํŠธ(11๊ฐœ ์กฐ๊ฑด)\n", "p0 = evaluate(m0, s0, Xev)\n", "acc0 = (p0 == yev).mean()\n", "by = pd.Series(p0 != yev).groupby(cev).mean().sort_values(ascending=False)\n", "print(f\"\\n[2. ์™œ ์ผ์–ด๋‚ฌ๋‚˜] ํ˜„ํ–‰ ๋ชจ๋ธ ์ข…ํ•ฉ ์ •ํ™•๋„ {acc0:.3f}, ์˜ค๋ฅ˜์œจ ์ƒ์œ„ ์กฐ๊ฑด:\", by.head(4).round(2).to_dict())\n", "weak_cls = pd.Series(p0 != yev).groupby(yev).mean().sort_values(ascending=False)\n", "print(\"์˜ค๋ฅ˜์œจ ์ƒ์œ„ ํด๋ž˜์Šค:\", weak_cls.head(3).round(2).to_dict(), \"-> ์›์ธ: ํ•™์Šต์— ์›๊ฑฐ๋ฆฌยทํ๋ฆผยท๊ฐ€๋ฆผยท์ธก๋ฉด ์‹œ์  ์˜์ƒ์ด ์—†์Œ\")\n", "\n", "# [3. ๋ฌด์—‡์„ ๋ฐ”๊พธ๋‚˜] ์ทจ์•ฝ ์กฐ๊ฑด ์ฆ๊ฐ• + ๋‹ค๋นˆ๋„ ์˜ค๋‹ต ํด๋ž˜์Šค ๊ฐ€์ค‘ ์ฆ๊ฐ• ํ›„ ์žฌํ•™์Šต\n", "weak_conds = tuple(by.index[by > 0.15]) or tuple(CONDITIONS)\n", "Xa, ya, _ = make_dataset(110, 6, weak_conds)\n", "extra = [c for c in (\"cruise\", \"ballistic\", \"civil_fw\", \"attack_fw\")]\n", "rng2 = np.random.default_rng(7)\n", "Xb = np.array([draw_cond(c, weak_conds[i % len(weak_conds)], rng2) for i, c in enumerate(extra * 40)])\n", "yb = np.array(extra * 40)\n", "m1, s1 = fit(np.r_[Xtr, Xa, Xb], np.r_[ytr, ya, yb])\n", "prob1 = predict(m1, s1, Xev)[1]\n", "p1 = np.array(CLASSES)[prob1.argmax(1)]\n", "acc1 = (p1 == yev).mean()\n", "print(f\"\\n[3. ๋ฌด์—‡์„ ๋ฐ”๊พธ๋‚˜] ์ฆ๊ฐ• ์กฐ๊ฑด {list(weak_conds)}, ํ˜ผ๋™ ํด๋ž˜์Šค 4์ข… ๊ฐ 40์žฅ ์ถ”๊ฐ€\")\n", "print(f\"์ข…ํ•ฉ ์ •ํ™•๋„ {acc0:.3f} -> {acc1:.3f} ({(acc1 - acc0) * 100:+.1f}%p, ๋ชฉํ‘œ +10%p: {'๋‹ฌ์„ฑ' if acc1 - acc0 >= 0.10 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# ํšŒ๊ท€ ํ…Œ์ŠคํŠธ: AAR ๋“ฑ๋ก ์˜ค๋ฅ˜์˜ ์žฌ๋ฐœ, ๊ธฐ์กด ์ •๋‹ต์˜ ํšŒ๊ท€\n", "reg = p0 != yev\n", "again = reg & (p1 != yev)\n", "print(f\"AAR ๋“ฑ๋ก ์˜ค๋ฅ˜ {reg.sum()}๊ฑด ์ค‘ ์žฌ๋ฐœ {again.sum()}๊ฑด(๋ชฉํ‘œ 0๊ฑด: {'๋‹ฌ์„ฑ' if again.sum() == 0 else '๋ฏธ๋‹ฌ'}), ๊ธฐ์กด ์ •๋‹ต ์ค‘ ์ƒˆ๋กœ ํ‹€๋ฆฐ ํšŒ๊ท€ {(~reg & (p1 != yev)).sum()}๊ฑด\")\n", "print(f\"์žฌ๋ฐœ ์˜ค๋ฅ˜ ์ค‘ ์‹ ๋ขฐ๋„ 0.5 ๋ฏธ๋งŒ์ด๋ผ '๊ต๊ด€ ํ™•์ธ'์œผ๋กœ ๊ฑธ๋Ÿฌ์ง€๋Š” ๊ฑด {(again & (prob1.max(1) < 0.5)).sum()}๊ฑด, ๋‹จ์ • ์˜ค๋‹ต์œผ๋กœ ๋‚จ๋Š” ๊ฑด {(again & (prob1.max(1) >= 0.5)).sum()}๊ฑด\")\n", "comp = pd.DataFrame({\"์žฌ๋ณด์ • ์ „\": pd.Series(p0 == yev).groupby(cev).mean(), \"์žฌ๋ณด์ • ํ›„\": pd.Series(p1 == yev).groupby(cev).mean()}).round(3)\n", "print(comp.T.to_string())\n", "print(\"๋‚จ์€ ์žฌ๋ฐœ ์˜ค๋ฅ˜๋Š” ๊ฒฐํ•ฉ(combo)ยท๊ตฌ๋ฆ„ ์กฐ๊ฑด์— ๋ชฐ๋ ค ์žˆ์–ด ๊ต๊ด€ ํ™•์ธ ๋Œ€์ƒ์œผ๋กœ ๋‘๊ณ , ๋‹ค์Œ ์ฃผ๊ธฐ์— ์ง€์† ํ•™์Šต์œผ๋กœ ๋ณด์™„ํ•œ๋‹ค.\")\n", "\n", "tuned = (f\"augment_conditions: {list(weak_conds)}\\naugment_extra_per_class: {{cruise: 40, ballistic: 40, civil_fw: 40, attack_fw: 40}}\\n\"\n", " f\"min_confidence: 0.5\\nquiz_mix: {{confusing_pairs: 0.4, weak_conditions: 0.3, random: 0.3}}\\nhidden_layer: 96\")\n", "print(\"\\n[์žฌ๋ณด์ • ํŒŒ๋ผ๋ฏธํ„ฐ(trn03_tuned_params.yaml ๋‚ด์šฉ)]\\n\" + tuned)\n", "\n", "plt.figure(figsize=(9, 4))\n", "x = np.arange(len(comp))\n", "plt.bar(x - 0.2, comp[\"์žฌ๋ณด์ • ์ „\"], 0.4, label=\"before AAR\"); plt.bar(x + 0.2, comp[\"์žฌ๋ณด์ • ํ›„\"], 0.4, label=\"after augmentation\")\n", "plt.xticks(x, comp.index, rotation=30); plt.ylabel(\"accuracy\"); plt.legend(); plt.title(\"AAR retraining effect by condition (synthetic)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 09 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1. ๋ฌด์—‡์ด ์ผ์–ด๋‚ฌ๋‚˜] ์‘๋‹ต 1200๊ฑด ์ค‘ ์˜ค๋‹ต 337๊ฑด(28.1%)\n", "๋‹ค๋นˆ๋„ ์˜ค๋‹ต ์Œ: {'cruise->ballistic': 90, 'civil_fw->attack_fw': 77, 'attack_fw->civil_fw': 66}\n", "\n", "[2. ์™œ ์ผ์–ด๋‚ฌ๋‚˜] ํ˜„ํ–‰ ๋ชจ๋ธ ์ข…ํ•ฉ ์ •ํ™•๋„ 0.770, ์˜ค๋ฅ˜์œจ ์ƒ์œ„ ์กฐ๊ฑด: {'combo': 0.67, 'oblique': 0.62, 'occlude': 0.42, 'clutter': 0.38}\n", "์˜ค๋ฅ˜์œจ ์ƒ์œ„ ํด๋ž˜์Šค: {'cruise': 0.34, 'bird': 0.3, 'civil_fw': 0.27} -> ์›์ธ: ํ•™์Šต์— ์›๊ฑฐ๋ฆฌยทํ๋ฆผยท๊ฐ€๋ฆผยท์ธก๋ฉด ์‹œ์  ์˜์ƒ์ด ์—†์Œ\n", "\n", "[3. ๋ฌด์—‡์„ ๋ฐ”๊พธ๋‚˜] ์ฆ๊ฐ• ์กฐ๊ฑด ['combo', 'oblique', 'occlude', 'clutter', 'far'], ํ˜ผ๋™ ํด๋ž˜์Šค 4์ข… ๊ฐ 40์žฅ ์ถ”๊ฐ€\n", "์ข…ํ•ฉ ์ •ํ™•๋„ 0.770 -> 0.873 (+10.3%p, ๋ชฉํ‘œ +10%p: ๋‹ฌ์„ฑ)\n", "AAR ๋“ฑ๋ก ์˜ค๋ฅ˜ 152๊ฑด ์ค‘ ์žฌ๋ฐœ 67๊ฑด(๋ชฉํ‘œ 0๊ฑด: ๋ฏธ๋‹ฌ), ๊ธฐ์กด ์ •๋‹ต ์ค‘ ์ƒˆ๋กœ ํ‹€๋ฆฐ ํšŒ๊ท€ 17๊ฑด\n", "์žฌ๋ฐœ ์˜ค๋ฅ˜ ์ค‘ ์‹ ๋ขฐ๋„ 0.5 ๋ฏธ๋งŒ์ด๋ผ '๊ต๊ด€ ํ™•์ธ'์œผ๋กœ ๊ฑธ๋Ÿฌ์ง€๋Š” ๊ฑด 36๊ฑด, ๋‹จ์ • ์˜ค๋‹ต์œผ๋กœ ๋‚จ๋Š” ๊ฑด 31๊ฑด\n", " basic blur clutter combo far lowcon night noise oblique occlude offcenter\n", "์žฌ๋ณด์ • ์ „ 1.0 0.933 0.617 0.333 0.683 1.0 0.983 0.967 0.383 0.583 0.983\n", "์žฌ๋ณด์ • ํ›„ 1.0 0.967 0.650 0.450 0.867 1.0 1.000 0.950 0.967 0.750 1.000\n", "๋‚จ์€ ์žฌ๋ฐœ ์˜ค๋ฅ˜๋Š” ๊ฒฐํ•ฉ(combo)ยท๊ตฌ๋ฆ„ ์กฐ๊ฑด์— ๋ชฐ๋ ค ์žˆ์–ด ๊ต๊ด€ ํ™•์ธ ๋Œ€์ƒ์œผ๋กœ ๋‘๊ณ , ๋‹ค์Œ ์ฃผ๊ธฐ์— ์ง€์† ํ•™์Šต์œผ๋กœ ๋ณด์™„ํ•œ๋‹ค.\n", "\n", "[์žฌ๋ณด์ • ํŒŒ๋ผ๋ฏธํ„ฐ(trn03_tuned_params.yaml ๋‚ด์šฉ)]\n", "augment_conditions: ['combo', 'oblique', 'occlude', 'clutter', 'far']\n", "augment_extra_per_class: {cruise: 40, ballistic: 40, civil_fw: 40, attack_fw: 40}\n", "min_confidence: 0.5\n", "quiz_mix: {confusing_pairs: 0.4, weak_conditions: 0.3, random: 0.3}\n", "hidden_layer: 96\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 10. ๋“œ๋ก ยท๋ฏธ์‚ฌ์ผ ์‹ค๋ฃจ์—ฃ ๋ถ„๋ฅ˜ ํ•™์Šต์•ฑ ์ตœ์ข… ํŒจํ‚ค์ง• ๋ฐ ๋ถ€๋Œ€ ๊ต์œก ๋ฐฐํฌ ๊ฐ€์ด๋“œ ์™„์„ฑ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋“œ๋ก ยท๋ฏธ์‚ฌ์ผ ์‹ค๋ฃจ์—ฃ ๋ถ„๋ฅ˜ ํ•™์Šต์•ฑ ์ตœ์ข… ํŒจํ‚ค์ง• ๋ฐ ๋ถ€๋Œ€ ๊ต์œก ๋ฐฐํฌ ๊ฐ€์ด๋“œ๋ฅผ ์™„์„ฑํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์‹ค๋ฃจ์—ฃ ์ƒ์„ฑ๋ถ€ํ„ฐ ์ „์ฒ˜๋ฆฌ, ๋ถ„๋ฅ˜, ์‹ ๋ขฐ๋„ ๋“ฑ๊ธ‰, ํ€ด์ฆˆ ์šด์˜, ๊ต๊ด€ ํ•ด์„ค ๊ฒ€ํ† ๊นŒ์ง€ ํ•™์Šต์•ฑ ์ „์ฒด๋ฅผ ํ†ตํ•ฉํ•˜๊ณ  ๋ถ€๋Œ€ ๊ต์œก ๋ฐฐํฌ ๊ฐ€์ด๋“œ๋ฅผ ์™„์„ฑํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **AAR ์žฌ๋ณด์ •๊ฐ’์„ ์ ์šฉํ•œ ํ†ตํ•ฉ ์ฝ”๋“œ๋ฅผ ํ•œ ๋ฒˆ์— ์‹คํ–‰ํ•ด ์†์ƒ ๋ฌธํ•ญ์ด ์ž๋™ ์ฐจ๋‹จ๋˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์œ ํšจ์„ฑ์„ ์ ๊ฒ€ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/main_trn03.py, README.md, docs/trn03_final_report.pdf(md)` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ ์‹ค๋ฌด ์ฆ‰์‹œ ๋„์ž… ๊ฐ€๋Šฅ์„ฑ, ์šด์šฉ ์‹ ๋ขฐ๋„ ๋ฐ ์ „๋žต์  ๊ธฐ๋Œ€ํšจ๊ณผ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: End-to-End ํ†ตํ•ฉ ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰ ์„ฑ๊ณต๋ฅ  100%, ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ 85% ์ด์ƒ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์›ํด๋ฆญ ์‹คํ–‰ ์Šคํฌ๋ฆฝํŠธ ๋™์ž‘ ํ™•์ธ ๋ฐ ์ตœ์ข… ๋ฐœํ‘œ ์‚ฐ์ถœ๋ฌผ ์ž‘์„ฑ ์™„๋ฃŒ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ์ตœ์ข… ๊ฒ€์ฆ ์œ„์›ํšŒ(ํ‰๊ฐ€๊ด€) ๋ชจ์˜ ์‹œ์—ฐ ๋ฐ ์ „์ฒด ๊ธฐ๋Šฅ ํ†ตํ•ฉ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"ํ•ฉ์„ฑ ์‹ค๋ฃจ์—ฃ ์ƒ์„ฑยท๊ฒ€์ฆ๋ถ€ํ„ฐ ์ „์ฒ˜๋ฆฌ, ๋ถ„๋ฅ˜(AAR ์žฌ๋ณด์ •๊ฐ’ ์ ์šฉ), ์‹ ๋ขฐ๋„ ๋“ฑ๊ธ‰, ํ•™์Šต ํ€ด์ฆˆ ์šด์˜, ์˜ค๋‹ต ํ•ด์„ค ๊ต๊ด€ ๊ฒ€ํ†  ๋Œ€๊ธฐ์—ด๊นŒ์ง€ ํ•™์Šต์•ฑ ์ „์ฒด๋ฅผ ํ•œ ํŒŒ์ผ๋กœ ํ†ตํ•ฉ ์‹คํ–‰ํ•˜๊ณ , ์ž์ฒด ์ ๊ฒ€ยทํ•จ์ˆ˜ ์ปค๋ฒ„๋ฆฌ์ง€ยท๋ถ€๋Œ€ ๊ต์œก ๋ฐฐํฌ ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ์„ ์ถœ๋ ฅํ•œ๋‹ค.\"\"\"\n", "import hashlib\n", "import json\n", "import sys\n", "import time\n", "import types\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from matplotlib.path import Path as MplPath\n", "from scipy import ndimage\n", "from scipy.spatial import ConvexHull\n", "from sklearn.neural_network import MLPClassifier\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•ฉ์„ฑ ์‹ค๋ฃจ์—ฃ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๊ธฐ์ฒด ์‚ฌ์ง„ยท๋„๋ฉดยท์ œ์› ์‚ฌ์šฉ ์•ˆ ํ•จ)\n", "# ์‚ฌ๊ฐํ˜•ยท์‚ผ๊ฐํ˜•ยท์› ๊ฐ™์€ ๊ธฐ๋ณธ ๋„ํ˜•์„ ์กฐํ•ฉํ•œ ์ถ”์ƒ ์‹ค๋ฃจ์—ฃ์„ 32x32 ํ‘๋ฐฑ ์ด๋ฏธ์ง€๋กœ ๊ทธ๋ฆฐ๋‹ค.\n", "# ํšŒ์ „(๋‹ค๊ฐ๋„), ์›๊ทผ ์ถ•์†Œ(์ธก๋ฉด ์‹œ์ ), ํฌ๊ธฐ(์›๊ฑฐ๋ฆฌ), ํ๋ฆผยท์žก์Œยท์ €๋Œ€๋น„ยท๊ตฌ๋ฆ„ยท๊ฐ€๋ฆผยท์•ผ๊ฐ„ ๋ฐ˜์ „์„ ์กฐ์ ˆํ•œ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "IMG = 32\n", "CLASSES = [\"civil_quad\", \"civil_fw\", \"attack_fw\", \"cruise\", \"ballistic\", \"bird\"]\n", "KOR = {\"civil_quad\": \"๋ฏผ๊ฐ„ ์ฟผ๋“œ์ฝฅํ„ฐํ˜•\", \"civil_fw\": \"๋ฏผ๊ฐ„ ๊ณ ์ •์ตํ˜•\", \"attack_fw\": \"์‚ผ๊ฐ์ต ์žํญ๋“œ๋ก ํ˜•\",\n", " \"cruise\": \"์ˆœํ•ญ๋ฏธ์‚ฌ์ผํ˜•\", \"ballistic\": \"ํƒ„๋„๋ฏธ์‚ฌ์ผํ˜•\", \"bird\": \"์กฐ๋ฅ˜\"}\n", "THREAT = {\"attack_fw\", \"cruise\", \"ballistic\"}\n", "\n", "\n", "def _rect(x0, x1, y0, y1):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 10] ๋“œ๋ก ยท๋ฏธ์‚ฌ์ผ ์‹ค๋ฃจ์—ฃ ๋ถ„๋ฅ˜ ํ•™์Šต์•ฑ ์ตœ์ข… ํŒจํ‚ค์ง• ๋ฐ ๋ถ€๋Œ€ ๊ต์œก ๋ฐฐํฌ ๊ฐ€์ด๋“œ ์™„์„ฑ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def _bar(p, q, w):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 10] ๋“œ๋ก ยท๋ฏธ์‚ฌ์ผ ์‹ค๋ฃจ์—ฃ ๋ถ„๋ฅ˜ ํ•™์Šต์•ฑ ์ตœ์ข… ํŒจํ‚ค์ง• ๋ฐ ๋ถ€๋Œ€ ๊ต์œก ๋ฐฐํฌ ๊ฐ€์ด๋“œ ์™„์„ฑ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "SHAPES = {\n", " \"civil_quad\": {\"poly\": [_bar((-.55, -.55), (.55, .55), .1), _bar((-.55, .55), (.55, -.55), .1), _rect(-.15, .15, -.15, .15)],\n", " \"circ\": [(-.55, -.55, .24), (.55, .55, .24), (-.55, .55, .24), (.55, -.55, .24)]},\n", " \"civil_fw\": {\"poly\": [_rect(-.07, .07, -.8, .72), _rect(-.9, .9, .05, .25), _rect(-.33, .33, -.8, -.66)], \"circ\": []},\n", " \"attack_fw\": {\"poly\": [[(0, .85), (.85, -.5), (.12, -.42), (0, -.6), (-.12, -.42), (-.85, -.5)]], \"circ\": []},\n", " \"cruise\": {\"poly\": [_rect(-.07, .07, -.9, .7), [(-.07, .7), (0, .92), (.07, .7)], _rect(-.42, .42, -.05, .07),\n", " [(-.07, -.9), (-.24, -.95), (-.07, -.74)], [(.07, -.9), (.24, -.95), (.07, -.74)]], \"circ\": []},\n", " \"ballistic\": {\"poly\": [_rect(-.13, .13, -.85, .6), [(-.13, .6), (0, .95), (.13, .6)],\n", " [(-.13, -.85), (-.34, -.95), (-.13, -.58)], [(.13, -.85), (.34, -.95), (.13, -.58)]], \"circ\": []},\n", " \"bird\": {\"poly\": [[(-.9, .12), (-.5, .28), (-.12, .06), (0, .22), (.12, .06), (.5, .28), (.9, .12), (.5, .12),\n", " (.12, -.1), (0, -.38), (-.12, -.1), (-.5, .12)]], \"circ\": []},\n", "}\n", "_S = 2\n", "_g = (np.arange(IMG * _S) + 0.5) / _S\n", "_GX, _GY = np.meshgrid(_g, _g)\n", "_PTS = np.c_[_GX.ravel(), _GY.ravel()]\n", "_PATHS = {c: [MplPath(np.array(p)) for p in s[\"poly\"]] for c, s in SHAPES.items()}\n", "\n", "\n", "def draw(cls, rng, angle=None, scale=None, fore=None, blur=0.3, noise=4.0, contrast=1.0,\n", " clutter=0.0, occlude=0.0, invert=False, shift=1.5):\n", " \"\"\"ํด๋ž˜์Šค 1๊ฐœ ์‹ค๋ฃจ์—ฃ ์ด๋ฏธ์ง€(uint8 32x32)๋ฅผ ๊ทธ๋ฆฐ๋‹ค. ๋ฐฐ๊ฒฝ์€ ๋ฐ์€ ํ•˜๋Š˜, ๋น„ํ–‰์ฒด๋Š” ์–ด๋‘์šด ๊ทธ๋ฆผ์ž.\"\"\"\n", " angle = rng.uniform(0, 360) if angle is None else angle\n", " scale = rng.uniform(.7, .95) if scale is None else scale\n", " fore = rng.uniform(.85, 1.0) if fore is None else fore\n", " th = np.radians(angle)\n", " c0 = IMG / 2 + rng.uniform(-shift, shift, 2)\n", " d = (_PTS - c0) / (scale * IMG / 2) # ์ด๋ฏธ์ง€ ์ขŒํ‘œ -> ๋„ํ˜• ์ขŒํ‘œ(์—ญ๋ณ€ํ™˜)\n", " u = d[:, 0] * np.cos(th) + d[:, 1] * np.sin(th)\n", " v = (-d[:, 0] * np.sin(th) + d[:, 1] * np.cos(th)) / fore # ์›๊ทผ ์ถ•์†Œ(๊ธฐ์ˆ˜ ๋ฐฉํ–ฅ์œผ๋กœ ์งง์•„ ๋ณด์ž„)\n", " uv = np.c_[u, -v]\n", " m = np.zeros(len(uv), bool)\n", " near = np.flatnonzero(np.abs(uv).max(axis=1) <= 1.0) # ๋„ํ˜•์ด ์žˆ์„ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ๋งŒ ๊ฒ€์‚ฌ(์†๋„)\n", " for p in _PATHS[cls]:\n", " m[near] |= p.contains_points(uv[near])\n", " for cx, cy, r in SHAPES[cls][\"circ\"]:\n", " m |= (uv[:, 0] - cx) ** 2 + (uv[:, 1] - cy) ** 2 <= r * r\n", " cov = m.reshape(IMG, _S, IMG, _S).mean(axis=(1, 3))\n", " if occlude > 0: # ๋ถ€๋ถ„ ๊ฐ€๋ฆผ(๊ตฌ๋ฆ„ ์กฐ๊ฐ)\n", " h = int(IMG * np.sqrt(occlude)); y0, x0 = rng.integers(8, IMG - 8 - h // 2, 2)\n", " cov[y0:y0 + h, x0:x0 + h] = 0\n", " bg = rng.uniform(180, 220) + np.linspace(-10, 10, IMG)[:, None]\n", " fg = rng.uniform(30, 60)\n", " img = bg * (1 - cov) + fg * cov\n", " if clutter > 0: # ๊ตฌ๋ฆ„ยท๋ฐฐ๊ฒฝ ์–ผ๋ฃฉ\n", " img -= clutter * 90 * np.clip(ndimage.gaussian_filter(rng.normal(0, 1, (IMG, IMG)), 3) * 4, 0, None)\n", " if blur > 0:\n", " img = ndimage.gaussian_filter(img, blur)\n", " mean = img.mean()\n", " img = mean + (img - mean) * contrast\n", " img = img + rng.normal(0, noise, img.shape)\n", " if invert: # ์•ผ๊ฐ„ ์—ด์ƒ(IR) ๊ทน์„ฑ ๋ฐ˜์ „\n", " img = 255 - img\n", " return np.clip(img, 0, 255).astype(np.uint8)\n", "\n", "\n", "# ์ดฌ์˜ ์กฐ๊ฑด ํ”„๋ฆฌ์…‹: ๊ธฐ๋ณธ + ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹œ๋‚˜๋ฆฌ์˜ค\n", "CONDITIONS = {\n", " \"basic\": {},\n", " \"offcenter\": {\"shift\": 5.0, \"fore\": (.6, .85)},\n", " \"far\": {\"scale\": (.32, .45), \"blur\": .6},\n", " \"blur\": {\"blur\": (1.2, 1.8)},\n", " \"lowcon\": {\"contrast\": (.2, .35)},\n", " \"clutter\": {\"clutter\": .8},\n", " \"occlude\": {\"occlude\": .08},\n", " \"oblique\": {\"fore\": (.35, .6)},\n", " \"night\": {\"invert\": True, \"noise\": 10.0},\n", " \"noise\": {\"noise\": 28.0},\n", " \"combo\": {\"scale\": (.4, .55), \"blur\": (.8, 1.1), \"noise\": 12.0, \"contrast\": (.6, .8)},\n", "}\n", "\n", "\n", "def draw_cond(cls, cond, rng):\n", " kw = {}\n", " for k, v in CONDITIONS[cond].items():\n", " kw[k] = rng.uniform(*v) if isinstance(v, tuple) else v\n", " return draw(cls, rng, **kw)\n", "\n", "\n", "def make_dataset(n_per_class, seed, conds=(\"basic\",)):\n", " \"\"\"(์ด๋ฏธ์ง€ ๋ฐฐ์—ด N x 32 x 32, ์ •๋‹ต ๋ผ๋ฒจ, ์กฐ๊ฑด) ๋ฐ˜ํ™˜. ์กฐ๊ฑด์€ conds์—์„œ ๋ฒˆ๊ฐˆ์•„ ๊ณ ๋ฅธ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " X, y, c = [], [], []\n", " for cls in CLASSES:\n", " for i in range(n_per_class):\n", " cond = conds[i % len(conds)]\n", " X.append(draw_cond(cls, cond, rng)); y.append(cls); c.append(cond)\n", " return np.array(X), np.array(y), np.array(c)\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---- ์ „์ฒ˜๋ฆฌ(๋ฐฐ์น˜ ๋ฒกํ„ฐํ™”)ยทํ˜•ํƒœ ํŠน์ง• ๊ณตํ†ต ํ•จ์ˆ˜ ----\n", "OUT = 24 # ํฌ๊ธฐ ์ •๊ทœํ™” ๊ฒฐ๊ณผ(24x24)\n", "NOISE_T = 12.0 # ์žก์Œ ์ถ”์ •๊ฐ’์ด ์ด๋ณด๋‹ค ํฌ๋ฉด 3x3 ์ค‘๊ฐ„๊ฐ’ ํ•„ํ„ฐ ์ ์šฉ(์•…์ฒœํ›„ยท์„ผ์„œ ์žก์Œ)\n", "FEATS = [\"elong\", \"fill\", \"solidity\", \"n_parts\", \"end_gap\", \"mid_bulge\", \"w_max\", \"compact\"]\n", "\n", "\n", "def preprocess(images, noise_t=NOISE_T):\n", " \"\"\"N์žฅ ์ผ๊ด„ ์ฒ˜๋ฆฌ: ๊ฒฐ์ธก ๋ณด์ • -> ๊ทน์„ฑ(์•ผ๊ฐ„ ๋ฐ˜์ „) ๋ณด์ • -> ์กฐ๊ฑด๋ณ„ ์žก์Œ ์ œ๊ฑฐ -> ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™” -> ๋ถ„ํ•  -> ์œค๊ณฝ์„  -> ๋ฐฉํ–ฅยทํฌ๊ธฐ ์ •๊ทœํ™”.\"\"\"\n", " f = np.array(images, dtype=float).reshape(-1, IMG, IMG)\n", " n = len(f)\n", " bad = ~np.isfinite(f)\n", " missing = bad.sum(axis=(1, 2))\n", " if bad.any(): # ๊ฒฐ์ธก ํ”ฝ์…€: ์˜์ƒ ์ค‘์•™๊ฐ’์œผ๋กœ ์ฑ„์šด ๋’ค ์ฃผ๋ณ€ 3x3 ์ค‘๊ฐ„๊ฐ’์œผ๋กœ ๋ณด์ •\n", " med = np.array([np.median(x[np.isfinite(x)]) if np.isfinite(x).any() else 128.0 for x in f])\n", " f = np.where(bad, med[:, None, None], f)\n", " f = np.where(bad, ndimage.median_filter(f, size=(1, 3, 3)), f)\n", " border = np.concatenate([f[:, 0], f[:, -1], f[:, :, 0], f[:, :, -1]], axis=1)\n", " inverted = np.median(border, axis=1) < 128 # ๋ฐฐ๊ฒฝ์ด ์–ด๋‘์šฐ๋ฉด ์•ผ๊ฐ„ ์—ด์ƒ์œผ๋กœ ๋ณด๊ณ  ๋ฐ˜์ „\n", " f[inverted] = 255 - f[inverted]\n", " med3 = ndimage.median_filter(f, size=(1, 3, 3))\n", " noise = 1.4826 * np.median(np.abs(f - med3).reshape(n, -1), axis=1)\n", " noisy = noise > noise_t\n", " f[noisy] = med3[noisy]\n", " lo, hi = np.percentile(f.reshape(n, -1), [1, 99], axis=1)\n", " e = np.clip((f - lo[:, None, None]) / np.maximum(hi - lo, 1e-6)[:, None, None], 0, 1) # ๋ช…์•” ๋Œ€๋น„ ๊ฐ•ํ™”\n", " mask = e < _otsu(e)[:, None, None]\n", " mask &= ndimage.uniform_filter(mask.astype(float), size=(1, 3, 3)) * 9 >= 2.5 # ๊ณ ๋ฆฝ ์  ์žก์Œ ์ œ๊ฑฐ\n", " edge = np.hypot(ndimage.sobel(e, axis=1), ndimage.sobel(e, axis=2)) # ์œค๊ณฝ์„ (Sobel)\n", " ok = (mask.sum(axis=(1, 2)) >= 4) & (hi - lo > 8)\n", " return {\"enh\": e, \"mask\": mask, \"edge\": edge / np.maximum(edge.max(axis=(1, 2)), 1e-6)[:, None, None],\n", " \"norm\": _normalize(1 - e, mask, ok), \"inverted\": inverted, \"noise\": noise, \"denoised\": noisy,\n", " \"missing\": missing, \"contrast\": hi - lo, \"ok\": ok}\n", "\n", "\n", "def _otsu(e, bins=64):\n", " n = len(e)\n", " idx = np.minimum((e * bins).astype(int), bins - 1) + bins * np.arange(n)[:, None, None]\n", " hist = np.bincount(idx.ravel(), minlength=bins * n).reshape(n, bins).astype(float)\n", " centers = (np.arange(bins) + 0.5) / bins\n", " w = np.cumsum(hist, axis=1); m = np.cumsum(hist * centers, axis=1)\n", " tot, mt = w[:, -1:], m[:, -1:]\n", " between = (mt * w / tot - m) ** 2 / np.maximum(w * (tot - w), 1)\n", " return (np.argmax(between, axis=1) + 1) / bins\n", "\n", "\n", "def _normalize(obj, mask, ok):\n", " \"\"\"์ฃผ์ถ• ๋ฐฉํ–ฅ์„ ์„ธ๋กœ๋กœ ๋Œ๋ฆฌ๊ณ  ๋น„ํ–‰์ฒด ์˜์—ญ๋งŒ 24x24๋กœ ๋‹ค์‹œ ์ƒ˜ํ”Œ๋งํ•œ๋‹ค(ํšŒ์ „ยทํฌ๊ธฐยท์œ„์น˜ ๋ถˆ๋ณ€). ํ•œ ๋ฒˆ์˜ ๋ณด๊ฐ„์œผ๋กœ ์ผ๊ด„ ์ฒ˜๋ฆฌ.\"\"\"\n", " n = len(obj)\n", " w = mask.astype(float)\n", " m00 = np.maximum(w.sum(axis=(1, 2)), 1)\n", " yy, xx = np.mgrid[0:IMG, 0:IMG].astype(float)\n", " cy, cx = (w * yy).sum(axis=(1, 2)) / m00, (w * xx).sum(axis=(1, 2)) / m00\n", " dy, dx = yy - cy[:, None, None], xx - cx[:, None, None]\n", " mu20, mu02, mu11 = (w * dy * dy).sum(axis=(1, 2)), (w * dx * dx).sum(axis=(1, 2)), (w * dy * dx).sum(axis=(1, 2))\n", " th = 0.5 * np.arctan2(2 * mu11, mu20 - mu02)\n", " a = np.stack([np.cos(th), np.sin(th)], 1) # ์ฃผ์ถ•(y, x)\n", " b = np.stack([-np.sin(th), np.cos(th)], 1) # ๋ถ€์ถ•\n", " pa = np.where(mask, dy * a[:, 0, None, None] + dx * a[:, 1, None, None], 0)\n", " pb = np.where(mask, dy * b[:, 0, None, None] + dx * b[:, 1, None, None], 0)\n", " half = np.maximum(np.abs(pa).max(axis=(1, 2)), np.abs(pb).max(axis=(1, 2))) + 1.0\n", " g = np.linspace(-1, 1, OUT)\n", " U, V = np.meshgrid(g, g, indexing=\"ij\")\n", " Y = cy[:, None, None] + half[:, None, None] * (U * a[:, 0, None, None] + V * b[:, 0, None, None])\n", " X = cx[:, None, None] + half[:, None, None] * (U * a[:, 1, None, None] + V * b[:, 1, None, None])\n", " N = np.broadcast_to(np.arange(n)[:, None, None], Y.shape)\n", " out = ndimage.map_coordinates(obj * mask, [N, Y, X], order=1, cval=0.0)\n", " skew = (out * (U ** 3)).sum(axis=(1, 2))\n", " out[skew < 0] = out[skew < 0, ::-1] # ๋ฌด๊ฒŒ๊ฐ€ ์ ๋ฆฐ ์ชฝ์„ ์œ„๋กœ(180๋„ ๋ชจํ˜ธ์„ฑ ์ œ๊ฑฐ)\n", " out[~ok] = 0\n", " return np.clip(out, 0, 1)\n", "\n", "\n", "def shape_features(mask):\n", " \"\"\"๋ถ„ํ•  ๋งˆ์Šคํฌ 1์žฅ์—์„œ ํšŒ์ „์— ๋ฌด๊ด€ํ•œ ํ˜•ํƒœ ํŠน์ง• 8์ข…์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " if mask.sum() < 4:\n", " return dict.fromkeys(FEATS, 0.0)\n", " yx = np.argwhere(mask).astype(float)\n", " c = yx - yx.mean(axis=0)\n", " val, vec = np.linalg.eigh(np.cov(c.T) + 1e-6 * np.eye(2))\n", " t, s = c @ vec[:, 1], c @ vec[:, 0]\n", " length, width = np.ptp(t) + 1, np.ptp(s) + 1\n", " corners = np.vstack([yx + d for d in ([.5, .5], [.5, -.5], [-.5, .5], [-.5, -.5])])\n", " try:\n", " hull = ConvexHull(corners).volume\n", " except Exception:\n", " hull = float(mask.sum())\n", " bins = np.digitize(t, np.linspace(t.min(), t.max() + 1e-6, 4)[1:-1])\n", " w = np.array([np.ptp(s[bins == k]) + 1 if (bins == k).any() else 0 for k in range(3)]) / length\n", " boundary = mask & ~ndimage.binary_erosion(mask)\n", " return {\"elong\": float(np.sqrt(val[1] / max(val[0], 1e-6))), \"fill\": float(mask.sum() / (length * width)),\n", " \"solidity\": float(mask.sum() / max(hull, 1)), \"n_parts\": float(ndimage.label(mask)[1]),\n", " \"end_gap\": float(abs(w[0] - w[2])), \"mid_bulge\": float(w[1] - max(w[0], w[2])),\n", " \"w_max\": float(w.max()), \"compact\": float(boundary.sum() / np.sqrt(mask.sum()))}\n", "\n", "\n", "def feature_matrix(images):\n", " \"\"\"์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ, ํ˜•ํƒœ ํŠน์ง• ํ–‰๋ ฌ(N x 8), ์ •๊ทœํ™” ์ด๋ฏธ์ง€ ํ–‰๋ ฌ(N x 576)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " P = preprocess(images)\n", " F = np.array([[d[k] for k in FEATS] for d in map(shape_features, P[\"mask\"])])\n", " return P, F, P[\"norm\"].reshape(len(F), -1)\n", "\n", "CONFIG = {\"augment_conditions\": [\"combo\", \"oblique\", \"occlude\", \"clutter\", \"far\"], \"extra_classes\": [\"cruise\", \"ballistic\", \"civil_fw\", \"attack_fw\"],\n", " \"min_confidence\": 0.5, \"hidden_layer\": 96} # ๋ฏธ์…˜ 9 ์žฌ๋ณด์ •๊ฐ’\n", "CALLED = set()\n", "\n", "\n", "def _tracer(frame, event, arg):\n", " if event == \"call\" and frame.f_globals is globals():\n", " CALLED.add(frame.f_code.co_name)\n", "\n", "\n", "def validate(img):\n", " a = np.asarray(img, dtype=float) if img is not None else None\n", " return a is not None and a.shape == (IMG, IMG) and np.isfinite(a).all() and a.min() >= 0 and a.max() <= 255\n", "\n", "\n", "def train():\n", " X1, y1, _ = make_dataset(80, 5, tuple(CONDITIONS))\n", " X2, y2, _ = make_dataset(40, 6, tuple(CONFIG[\"augment_conditions\"]))\n", " rng = np.random.default_rng(7)\n", " ex = CONFIG[\"extra_classes\"] * 20\n", " X3 = np.array([draw_cond(c, CONFIG[\"augment_conditions\"][i % 5], rng) for i, c in enumerate(ex)])\n", " X, y = np.r_[X1, X2, X3], np.r_[y1, y2, ex]\n", " P, F, N = feature_matrix(X)\n", " sc = StandardScaler().fit(F)\n", " m = MLPClassifier((CONFIG[\"hidden_layer\"],), max_iter=200, early_stopping=True, random_state=0)\n", " return m.fit(np.c_[N, sc.transform(F) * 0.3], [CLASSES.index(v) for v in y]), sc\n", "\n", "\n", "def classify(m, sc, images):\n", " P, F, N = feature_matrix(images)\n", " prob = m.predict_proba(np.c_[N, sc.transform(F) * 0.3])\n", " pred = np.array(CLASSES)[prob.argmax(1)]\n", " conf = prob.max(1) * np.where(P[\"ok\"], 1, 0)\n", " return pred, conf, np.where(conf >= 0.8, \"High\", np.where(conf >= CONFIG[\"min_confidence\"], \"Medium\", \"Low\"))\n", "\n", "\n", "class Queue:\n", " def __init__(self):\n", " self.items, self.log = [], []\n", "\n", " def submit(self, rec):\n", " prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n", " body = {\"event\": \"SUBMIT\", **rec}\n", " self.log.append({**body, \"prev\": prev, \"hash\": hashlib.sha256((prev + json.dumps(body, sort_keys=True)).encode()).hexdigest()})\n", " self.items.append({**rec, \"status\": \"PENDING\"})\n", "\n", "\n", "sys.settrace(_tracer)\n", "t_start = time.perf_counter()\n", "# 1. ํ•™์Šต\n", "model, scaler = train()\n", "# 2. ์šด์šฉ ๋ฐ์ดํ„ฐ(๊ต์œก์žฅ ๋ฐฐํฌ ๋ฌธํ•ญ 300์žฅ) ๊ฒ€์ฆ -> ์†์ƒ 3์žฅ ์ฐจ๋‹จ\n", "X, y, cond = make_dataset(50, 41, tuple(CONDITIONS))\n", "items = list(X)\n", "items[5], items[77], items[150] = None, np.zeros((20, 20)), np.full((IMG, IMG), np.nan)\n", "ok_idx = [i for i, im in enumerate(items) if validate(im)]\n", "rejected = len(items) - len(ok_idx)\n", "Xo, yo = np.array([items[i] for i in ok_idx]), y[ok_idx]\n", "t0 = time.perf_counter()\n", "pred, conf, grade = classify(model, scaler, Xo)\n", "ms = (time.perf_counter() - t0) * 1000 / len(Xo)\n", "judged = grade != \"Low\"\n", "acc_all = (pred == yo).mean()\n", "acc_judged = (pred[judged] == yo[judged]).mean()\n", "# 3. ํ€ด์ฆˆ ์šด์˜(๊ฐ€์ƒ ์žฅ๋ณ‘ 20๋ช… x 10๋ฌธํ•ญ) -> ์˜ค๋‹ต์€ ํ•ด์„ค ๊ฒ€ํ†  ๋Œ€๊ธฐ์—ด(์ž๋™ ๊ณต๊ฐœ ์—†์Œ)\n", "rng = np.random.default_rng(9)\n", "q, answers = Queue(), []\n", "CONFUSE = {\"civil_fw\": \"attack_fw\", \"attack_fw\": \"civil_fw\", \"cruise\": \"ballistic\", \"ballistic\": \"cruise\", \"bird\": \"civil_fw\", \"civil_quad\": \"civil_fw\"}\n", "for s in range(20):\n", " for k in rng.choice(len(yo), 10, replace=False):\n", " ans = yo[k] if rng.random() < 0.75 else CONFUSE[yo[k]]\n", " answers.append(ans == yo[k])\n", " if ans != yo[k]:\n", " q.submit({\"learner\": f\"L{s + 1:02d}\", \"answer\": str(yo[k]), \"chosen\": str(ans), \"ai_grade\": str(grade[k])})\n", "sys.settrace(None)\n", "total = time.perf_counter() - t_start\n", "\n", "defined = {n for n, f in globals().items() if isinstance(f, types.FunctionType) and f.__module__ == __name__ and not n.startswith(\"_tracer\")}\n", "defined |= {\"submit\", \"__init__\"}\n", "coverage = len(CALLED & defined) / len(defined)\n", "chk = {\n", " \"์†์ƒ ๋ฐ์ดํ„ฐ 3๊ฑด ์ฐจ๋‹จ\": rejected == 3,\n", " \"ํŒ๋ณ„ ๋Œ€์ƒ 297์žฅ ์ฒ˜๋ฆฌ\": len(Xo) == 297,\n", " \"์ „์ฒด ๋ถ„๋ฅ˜ ์ •ํ™•๋„ 85% ์ด์ƒ\": acc_all >= 0.85,\n", " \"ํŒ๋ณ„(Medium ์ด์ƒ) ์ •ํ™•๋„ 95% ์ด์ƒ\": acc_judged >= 0.95,\n", " \"High ๋“ฑ๊ธ‰ ์ •ํ™•๋„ >= Medium ๋“ฑ๊ธ‰ ์ •ํ™•๋„\": (pred == yo)[grade == \"High\"].mean() >= (pred == yo)[grade == \"Medium\"].mean(),\n", " \"์žฅ๋‹น ์ฒ˜๋ฆฌ 200 ms ์ด๋‚ด\": ms <= 200,\n", " \"์˜ค๋‹ต ํ•ด์„ค ์ž๋™ ๊ณต๊ฐœ 0๊ฑด\": all(i[\"status\"] == \"PENDING\" for i in q.items),\n", " \"๊ฐ์‚ฌ ๋กœ๊ทธ ๊ฑด์ˆ˜ ์ผ์น˜\": len(q.log) == len(q.items) == sum(not a for a in answers),\n", " \"ํ•จ์ˆ˜ ์ปค๋ฒ„๋ฆฌ์ง€ 85% ์ด์ƒ\": coverage >= 0.85,\n", " \"์ „์ฒด ์‹คํ–‰ 60์ดˆ ์ด๋‚ด\": total < 60,\n", "}\n", "for k, v in chk.items():\n", " print(f\"[{'PASS' if v else 'FAIL'}] {k}\")\n", "passed = sum(chk.values())\n", "print(f\"์ž์ฒด ์ ๊ฒ€ {passed}/{len(chk)} ํ†ต๊ณผ, ํ•จ์ˆ˜ ์ปค๋ฒ„๋ฆฌ์ง€ {coverage:.0%} ({len(CALLED & defined)}/{len(defined)}), ํ†ตํ•ฉ ์‹คํ–‰ {'์„ฑ๊ณต' if passed == len(chk) else '์ ๊ฒ€ ํ•„์š”'}\")\n", "\n", "fig, ax = plt.subplots(2, 2, figsize=(11, 8))\n", "for j in range(6):\n", " sub = ax[0, 0].inset_axes([j % 3 / 3, 1 - (j // 3 + 1) / 2, 0.32, 0.48])\n", " i = np.flatnonzero(yo == CLASSES[j])[0]\n", " sub.imshow(Xo[i], cmap=\"gray\"); sub.set_title(f\"{CLASSES[j]}\\n{pred[i]} ({grade[i]})\", fontsize=7); sub.axis(\"off\")\n", "ax[0, 0].axis(\"off\"); ax[0, 0].set_title(\"Sample items: true / predicted (grade)\")\n", "g = pd.Series(grade).value_counts().reindex([\"High\", \"Medium\", \"Low\"]).fillna(0)\n", "ax[0, 1].bar(g.index, g.values, color=[\"tab:green\", \"tab:orange\", \"tab:red\"]); ax[0, 1].set_title(\"Confidence grade of deployed items\")\n", "acc_c = pd.Series(pred == yo).groupby(cond[ok_idx]).mean()\n", "ax[1, 0].bar(acc_c.index, acc_c.values); ax[1, 0].tick_params(axis=\"x\", rotation=40); ax[1, 0].set_title(\"AI accuracy by condition\")\n", "ax[1, 1].axis(\"off\")\n", "ax[1, 1].text(0, .9, f\"Items {len(Xo)} (+{rejected} rejected)\\nAI accuracy {acc_all:.1%}\\nLearner acc {np.mean(answers):.1%}\\n\"\n", " f\"Review queue {len(q.items)} pending\\nCoverage {coverage:.0%}\\nSelf-check {passed}/{len(chk)}\", family=\"monospace\", fontsize=12, va=\"top\")\n", "plt.suptitle(\"TRN03 silhouette ID app - integrated run (training data only)\"); plt.tight_layout(); plt.show()\n", "\n", "top = pd.Series([f\"{i['answer']}->{i['chosen']}\" for i in q.items]).value_counts().head(2)\n", "print(f\"\"\"\n", "[๋ถ€๋Œ€ ๊ต์œก ๋ฐฐํฌ ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ - ๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\n", "1. ๊ตฌ์„ฑ: ํ•ฉ์„ฑ ์ถ”์ƒ ์‹ค๋ฃจ์—ฃ 6์ข…(์œ„ํ˜‘ 3์ข…), ๋ฌธํ•ญ {len(items)}์žฅ ์ค‘ {len(Xo)}์žฅ ๋ฐฐํฌ, ์†์ƒ {rejected}์žฅ ์ž๋™ ์ฐจ๋‹จ\n", "2. AI ์„ฑ๋Šฅ: ์ „์ฒด ์ •ํ™•๋„ {acc_all:.1%}, ์‹ ๋ขฐ๋„ Medium ์ด์ƒ ํŒ๋ณ„ ์ •ํ™•๋„ {acc_judged:.1%}, Low {int((~judged).sum())}์žฅ์€ ๊ต๊ด€ ํ™•์ธ, ์žฅ๋‹น {ms:.2f} ms\n", "3. ํ•™์Šต ์šด์˜: ๊ฐ€์ƒ ์žฅ๋ณ‘ 20๋ช… ์ •๋‹ต๋ฅ  {np.mean(answers):.1%}, ๋‹ค๋นˆ๋„ ์˜ค๋‹ต {', '.join(f'{k}({v})' for k, v in top.items())}\n", "4. ๊ต๊ด€ ๊ฒ€ํ† : ์˜ค๋‹ต ํ•ด์„ค {len(q.items)}๊ฑด ๊ฒ€ํ†  ๋Œ€๊ธฐ, ์ž๋™ ๊ณต๊ฐœ 0๊ฑด, ๊ฐ์‚ฌ ๋กœ๊ทธ {len(q.log)}๊ฑด\n", "5. ์šด์šฉ ์œ ์˜: ์‹ค๋ฃจ์—ฃ์€ ๊ธฐ๋ณธ ๋„ํ˜•์œผ๋กœ ๋งŒ๋“  ํ•™์Šต์šฉ ์ถ”์ƒ ์ด๋ฏธ์ง€์ด๋ฉฐ ์‹ค์ œ ๊ธฐ์ข… ์‹๋ณ„ ๊ต๋ฒ”์„ ๋Œ€์ฒดํ•˜์ง€ ์•Š๋Š”๋‹ค. ์‹ค์ œ ํ‘œ์  ํŒ๋‹จ์€ ์ •์‹ ๊ต์œก๊ณผ ์ง€ํœ˜๊ณ„ํ†ต์— ๋”ฐ๋ฅธ๋‹ค.\n", "6. ํ™•์žฅ ๋กœ๋“œ๋งต: ํ•™์Šต ๊ธฐ๋ก ๋‚ด๋ณด๋‚ด๊ธฐ API(๋ฌธํ•ญ IDยท์‘๋‹ตยท์†Œ์š”์‹œ๊ฐ„), ์ธ์ ‘ ๊ต์œก ์ฒด๊ณ„์™€ ๋ฌธํ•ญ ์€ํ–‰ ๊ณต์œ  ์ธํ„ฐํŽ˜์ด์Šค\"\"\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 10 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[PASS] ์†์ƒ ๋ฐ์ดํ„ฐ 3๊ฑด ์ฐจ๋‹จ\n", "[PASS] ํŒ๋ณ„ ๋Œ€์ƒ 297์žฅ ์ฒ˜๋ฆฌ\n", "[PASS] ์ „์ฒด ๋ถ„๋ฅ˜ ์ •ํ™•๋„ 85% ์ด์ƒ\n", "[PASS] ํŒ๋ณ„(Medium ์ด์ƒ) ์ •ํ™•๋„ 95% ์ด์ƒ\n", "[PASS] High ๋“ฑ๊ธ‰ ์ •ํ™•๋„ >= Medium ๋“ฑ๊ธ‰ ์ •ํ™•๋„\n", "[PASS] ์žฅ๋‹น ์ฒ˜๋ฆฌ 200 ms ์ด๋‚ด\n", "[PASS] ์˜ค๋‹ต ํ•ด์„ค ์ž๋™ ๊ณต๊ฐœ 0๊ฑด\n", "[PASS] ๊ฐ์‚ฌ ๋กœ๊ทธ ๊ฑด์ˆ˜ ์ผ์น˜\n", "[PASS] ํ•จ์ˆ˜ ์ปค๋ฒ„๋ฆฌ์ง€ 85% ์ด์ƒ\n", "[PASS] ์ „์ฒด ์‹คํ–‰ 60์ดˆ ์ด๋‚ด\n", "์ž์ฒด ์ ๊ฒ€ 10/10 ํ†ต๊ณผ, ํ•จ์ˆ˜ ์ปค๋ฒ„๋ฆฌ์ง€ 87% (13/15), ํ†ตํ•ฉ ์‹คํ–‰ ์„ฑ๊ณต\n", "\n", "[๋ถ€๋Œ€ ๊ต์œก ๋ฐฐํฌ ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ - ๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\n", "1. ๊ตฌ์„ฑ: ํ•ฉ์„ฑ ์ถ”์ƒ ์‹ค๋ฃจ์—ฃ 6์ข…(์œ„ํ˜‘ 3์ข…), ๋ฌธํ•ญ 300์žฅ ์ค‘ 297์žฅ ๋ฐฐํฌ, ์†์ƒ 3์žฅ ์ž๋™ ์ฐจ๋‹จ\n", "2. AI ์„ฑ๋Šฅ: ์ „์ฒด ์ •ํ™•๋„ 85.2%, ์‹ ๋ขฐ๋„ Medium ์ด์ƒ ํŒ๋ณ„ ์ •ํ™•๋„ 96.7%, Low 55์žฅ์€ ๊ต๊ด€ ํ™•์ธ, ์žฅ๋‹น 1.47 ms\n", "3. ํ•™์Šต ์šด์˜: ๊ฐ€์ƒ ์žฅ๋ณ‘ 20๋ช… ์ •๋‹ต๋ฅ  75.0%, ๋‹ค๋นˆ๋„ ์˜ค๋‹ต ballistic->cruise(12), civil_quad->civil_fw(11)\n", "4. ๊ต๊ด€ ๊ฒ€ํ† : ์˜ค๋‹ต ํ•ด์„ค 50๊ฑด ๊ฒ€ํ†  ๋Œ€๊ธฐ, ์ž๋™ ๊ณต๊ฐœ 0๊ฑด, ๊ฐ์‚ฌ ๋กœ๊ทธ 50๊ฑด\n", "5. ์šด์šฉ ์œ ์˜: ์‹ค๋ฃจ์—ฃ์€ ๊ธฐ๋ณธ ๋„ํ˜•์œผ๋กœ ๋งŒ๋“  ํ•™์Šต์šฉ ์ถ”์ƒ ์ด๋ฏธ์ง€์ด๋ฉฐ ์‹ค์ œ ๊ธฐ์ข… ์‹๋ณ„ ๊ต๋ฒ”์„ ๋Œ€์ฒดํ•˜์ง€ ์•Š๋Š”๋‹ค. ์‹ค์ œ ํ‘œ์  ํŒ๋‹จ์€ ์ •์‹ ๊ต์œก๊ณผ ์ง€ํœ˜๊ณ„ํ†ต์— ๋”ฐ๋ฅธ๋‹ค.\n", "6. ํ™•์žฅ ๋กœ๋“œ๋งต: ํ•™์Šต ๊ธฐ๋ก ๋‚ด๋ณด๋‚ด๊ธฐ API(๋ฌธํ•ญ IDยท์‘๋‹ตยท์†Œ์š”์‹œ๊ฐ„), ์ธ์ ‘ ๊ต์œก ์ฒด๊ณ„์™€ ๋ฌธํ•ญ ์€ํ–‰ ๊ณต์œ  ์ธํ„ฐํŽ˜์ด์Šค\n", "```\n", "
\n" ] } ], "metadata": { "colab": { "provenance": [] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }