{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ๐ŸŽฏ [OPS04] ์ƒคํ—ค๋“œยท๋ฏธ์‚ฌ์ผ ํฌํ™”๊ฒฝ๋ณด ์šฐ์„ ์ˆœ์œ„ ๋Œ€์‹œ๋ณด๋“œ ๋ชจ์˜ ๊ตฌํ˜„\n", "### Google Colab ์‹ค์Šต ํ™˜๊ฒฝ (์ž‘์ „ยท์ „ํˆฌ ์ˆ˜ํ–‰ / Level 3)\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/OPS04/OPS04_solution.ipynb)\n", "\n", "---\n", "\n", "## ๐Ÿ“Œ 1. ํ”„๋กœ์ ํŠธ ๊ฐœ์š” ๋ฐ ๋ฐฐ๊ฒฝ\n", "\n", "| ํ•ญ๋ชฉ | ๋‚ด์šฉ |\n", "| :--- | :--- |\n", "| **๊ณผ์ • ๋ถ„๋ฅ˜** | ์ž‘์ „ยท์ „ํˆฌ ์ˆ˜ํ–‰ (Level 3) |\n", "| **ํ•™์Šต ์†Œ์š” ์‹œ๊ฐ„** | 10๊ฐœ ๋ฏธ์…˜ (5์ผ / ์ด 27.5์‹œ๊ฐ„) |\n", "| **ํ™˜๊ฒฝ ์‚ฌ์–‘** | Python 3.10+, numpy, pandas, matplotlib, scipy, scikit-learn |\n", "\n", "### ๐Ÿ“– ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ\n", "๋Ÿฌ์‹œ์•„-์šฐํฌ๋ผ์ด๋‚˜ ์ „์Ÿ์—์„œ๋Š” ์ €์† ์žํญ๋ฌด์ธ๊ธฐ์™€ ๊ณ ์† ์ˆœํ•ญํ˜• ๋น„ํ–‰์ฒด๊ฐ€ ์—ฌ๋Ÿฌ ๋ฐฉํ–ฅ์—์„œ ๋™์‹œ์— ๋“ค์–ด์˜ค๋Š” ํฌํ™” ๊ณต๊ฒฉ์ด ๋ฐ˜๋ณต๋˜๋ฉด์„œ, ์งง์€ ์‹œ๊ฐ„ ์•ˆ์— ์–ด๋–ค ํ•ญ์ ์„ ๋จผ์ € ๋ณผ์ง€ ์ •ํ•˜๋Š” ๋ฐฉ๊ณต ์ƒํ™ฉํŒ๋‹จ์˜ ๋ถ€๋‹ด์ด ์ปค์กŒ๋‹ค. ๋ณธ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๊ฐ€์ƒ ๋ฐฉ์–ด์ง€์—ญ๊ณผ ๊ฐ€์ƒ ์ž์‚ฐ, ์ถ”์ƒ ๋ฒ”์ฃผ์˜ ๊ฐ€์ƒ ๋น„ํ–‰์ฒด๋ฅผ ์ฝ”๋“œ๋กœ ๋งŒ๋“ค์–ด ๊ฒฝ๋ณด ๋ฐ์ดํ„ฐ ๊ทœ๊ฒฉ ์ •์˜, ๋‹ค์ค‘ ์„ผ์„œ ๋ฉ”์‹œ์ง€ ์ „์ฒ˜๋ฆฌ, ๊ฑฐ๋ฆฌยท๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„(TTI) ๊ธฐ๋ฐ˜ Baseline ์ˆœ์œ„, ์ž์‚ฐ ๊ฐ€์น˜ยท์œ„ํ˜‘ ์œ ์‚ฌ๋„๋ฅผ ๊ฒฐํ•ฉํ•œ ๋‹ค์ฐจ์› ํ‰๊ฐ€, ์‹ ๋ขฐ๋„ ๋“ฑ๊ธ‰๊ณผ ๊ถŒ๊ณ  ๋Œ€์‘ ์ˆœ์œ„, ์ง€ํœ˜๊ด€์šฉ ๋Œ€์‹œ๋ณด๋“œ, ์ธ๊ฐ„ ๊ฐœ์ž…(HITL) ์Šน์ธ ์ ˆ์ฐจ, ํฌํ™” ๋ถ€ํ•˜ ์‹œํ—˜, AAR ์žฌ๋ณด์ •, ์ตœ์ข… ํ†ตํ•ฉ๊นŒ์ง€ ๋‹จ๊ณ„๋ณ„๋กœ ๊ตฌํ˜„ํ•œ๋‹ค. ์œ„ํ˜‘ ์ ์ˆ˜๋Š” ์‹ค์ œ ๋ฌด๊ธฐ ์ œ์›์ด ์•„๋‹Œ ๊ฑฐ๋ฆฌยท๋„๋‹ฌ์‹œ๊ฐ„ยท์ž์‚ฐ ๊ฐ€์น˜ ๊ธฐ๋ฐ˜์˜ ์ถ”์ƒ ๋ชจ๋ธ๋กœ๋งŒ ๊ณ„์‚ฐํ•œ๋‹ค. ์ตœ์ข… ๋ชฉํ‘œ๋Š” ๊ธด๊ธ‰ ํ‘œ์ ์„ ๋†“์น˜์ง€ ์•Š์œผ๋ฉด์„œ ๊ธฐ๋งŒ์ฒดยท์Šคํ‘ธํ•‘์— ์˜ํ•œ ์˜ค๊ฒฝ๋ณด๋ฅผ ์ค„์ด๊ณ , ๋ชจ๋“  ๋Œ€์‘ ์ˆœ์œ„๊ฐ€ '๊ถŒ๊ณ '๋กœ์„œ ์ง€ํœ˜๊ด€ ์Šน์ธ์„ ๊ฑฐ์น˜๋„๋ก ์„ค๊ณ„ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.\n", "\n", "> **โš ๏ธ [๊ต์œก์šฉ ๊ฐ€์ƒ ๋ฐ์ดํ„ฐ์…‹ ์›์น™]**\n", "> - ๋ณธ ๋…ธํŠธ๋ถ์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ(์ œ์›, ๋กœ๊ทธ, ์‹œ๋‚˜๋ฆฌ์˜ค ๋“ฑ)๋Š” ๋ณด์•ˆ ๊ทœ์ •์„ ์ค€์ˆ˜ํ•˜์—ฌ **์ฝ”๋“œ ๋‚ด ๋‚œ์ˆ˜ ๊ธฐ๋ฐ˜ ๊ฐ€์ƒ ๋ฐ์ดํ„ฐ๋กœ ์ƒ์„ฑ**๋ฉ๋‹ˆ๋‹ค.\n", "> - ์‹ค์ œ ๋ถ€๋Œ€ ์œ„์น˜, ์ž‘์ „ ์ธ๊ฐ€, ์‹ค์ œ ์ „์žฅ ์ •๋ณด, ๊ตฐ์‚ฌ ๊ธฐ๋ฐ€ ๋ฐ์ดํ„ฐ๋Š” ์ผ์ฒด ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.\n", "\n", "---\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ============================================================================\n", "# [Google Colab ํ™˜๊ฒฝ ์„ค์ •] ํ•„์ˆ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ ๋ฐ ์ž„ํฌํŠธ\n", "# ============================================================================\n", "!pip install -q numpy pandas matplotlib scipy scikit-learn\n", "\n", "import os\n", "import sys\n", "import time\n", "import math\n", "import json\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "import warnings\n", "warnings.filterwarnings('ignore')\n", "\n", "# ๊ทธ๋ž˜ํ”„ ์˜๋ฌธ ํฐํŠธ ๊ธฐ๋ณธ ์„ค์ • (ํ™˜๊ฒฝ ๋…๋ฆฝ์„ฑ ๋ณด์žฅ)\n", "plt.rcParams['font.sans-serif'] = 'DejaVu Sans'\n", "plt.rcParams['axes.unicode_minus'] = False\n", "\n", "print(f\"์‹คํ–‰ ํ™˜๊ฒฝ ์ค€๋น„ ์™„๋ฃŒ (Python {sys.version.split()[0]})\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 01. ์ƒคํ—ค๋“œ ์žํญ๋“œ๋ก  ๋ฐ ์ˆœํ•ญ๋ฏธ์‚ฌ์ผ ๋ณตํ•ฉ ๋น„ํ–‰ ํŠน์„ฑ ๋ถ„์„ ๋ฐ ํฌํ™”๊ฒฝ๋ณด ๋ฐ์ดํ„ฐ ๊ทœ๊ฒฉ ์ •์˜\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 1.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ƒคํ—ค๋“œ-136 ์žํญ๋“œ๋ก  ๋ฐ ์ˆœํ•ญ๋ฏธ์‚ฌ์ผ ๋™์‹œ ์นจํˆฌ ์ „์ˆ ์„ ๋ถ„์„ํ•˜์—ฌ ๋น„ํ–‰ ์†๋„, ๊ณ ๋„, ํƒ„๋‘ ์œ„๋ ฅ, ํ‘œ์  ์ ‘๊ทผ ๋ฐฉํ–ฅ ๋“ฑ ํฌํ™”๊ฒฝ๋ณด ํ•ต์‹ฌ ์†์„ฑ ๋ฐ์ดํ„ฐ ๊ทœ๊ฒฉ์„ ์ •์˜ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **ํฌํ™”๊ฒฝ๋ณด ์ƒํ™ฉ์—์„œ ์„ผ์„œ๊ฐ€ ๋ณด๋‚ด๋Š” ํ•ญ์  ๋ฉ”์‹œ์ง€์˜ ํ•ต์‹ฌ ์†์„ฑ์„ ์ •์˜ํ•˜๊ณ , ์ž˜๋ชป๋œ ๋ฉ”์‹œ์ง€๋ฅผ ๊ฑธ๋Ÿฌ๋‚ด๋Š” ๊ฒ€์ฆ๊ธฐ๋ฅผ ๋งŒ๋“ ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **ํ•ญ์  ID, ์„ผ์„œ, ์‹œ๊ฐ, ์œ„์น˜, ๊ณ ๋„, ์†๋„, ๋ฐฉ์œ„, ์ ‘๊ทผ ๋ฐฉํ–ฅ, ์œ ํ˜• ์ถ”์ •, ์œ„ํ˜‘ ๊ฐ€์ค‘ ๋“ฑ๊ธ‰ ํ•„๋“œ๋ฅผ ์ •ํ•˜๊ณ  ๊ธฐ์ƒยท์œ„์น˜์˜ค์ฐจยทํ†ต์‹ ์ง€์—ฐ์„ ์ „์ˆ  ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ณด์™„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `data/schema/ops04_input_schema.json ๋ฐ docs/ops04_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": [ "\"\"\"ํฌํ™”๊ฒฝ๋ณด ์ž…๋ ฅ ๋ฉ”์‹œ์ง€์˜ ๋ฐ์ดํ„ฐ ๊ทœ๊ฒฉ(JSON ์Šคํ‚ค๋งˆ)์„ ์ •์˜ํ•˜๊ณ , ๊ฐ€์ƒ ์ƒ˜ํ”Œ 20๊ฑด ๊ฒ€์ฆ๊ณผ ์˜ค๋ฅ˜ 5์ข… ์ฃผ์ž… ๊ฒ€์ถœ๋กœ ๊ฒ€์ฆ๊ธฐ๋ฅผ ํ™•์ธํ•œ ๋’ค ๋ฐ์ดํ„ฐ ๋ช…์„ธ์„œ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค.\"\"\"\n", "import json\n", "import numpy as np\n", "import pandas as pd\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋ฐฉ์–ด์ง€์—ญยท๊ฐ€์ƒ ๋น„ํ–‰์ฒด ์ƒ์„ฑ๊ธฐ ----------------\n", "# ์ขŒํ‘œ๋Š” ๊ฐ€์ƒ ์ง€ํœ˜์†Œ๋ฅผ ์›์ ์œผ๋กœ ํ•œ km ๋‹จ์œ„ ํ‰๋ฉด์ด๋‹ค. ๋น„ํ–‰์ฒด ์œ ํ˜•์€ ์ถ”์ƒ ๋ฒ”์ฃผ์ด๋ฉฐ\n", "# ์†๋„ยท๊ณ ๋„ยท๋ฐ˜์‚ฌ๊ฐ•๋„๋Š” ํ•™์Šต์šฉ ์ž„์˜ ๋ฒ”์œ„๋กœ, ์‹ค์ œ ๋ฌด๊ธฐ ์ œ์›ยท์‚ฌ๊ฑฐ๋ฆฌยทํƒ„๋„์™€ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n", " [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n", " [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n", " [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n", "TYPES = { # ์œ ํ˜•: (์œ„ํ˜‘ ์—ฌ๋ถ€, ์†๋„ m/s ๋ฒ”์œ„, ๊ณ ๋„ m ๋ฒ”์œ„, ์ถœํ˜„ ๋น„์œจ)\n", " \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ €์† ๋ฌด์ธ๊ธฐํ˜•(๊ฐ€์ƒ)\n", " \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์† ์ˆœํ•ญํ˜•(๊ฐ€์ƒ)\n", " \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋งŒ์ฒด(๊ฐ€์ƒ)\n", " \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํ•ญ๊ธฐ(๊ฐ€์ƒ)\n", " \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ˜ ๊ตฐ์ง‘\n", "}\n", "TTI_URGENT = 600 # ๊ธด๊ธ‰ ํŒ์ • ๊ธฐ์ค€: ๊ฐ€์ƒ ์ž์‚ฐ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„ 600์ดˆ ์ด๋‚ด\n", "CPA_KM = 1.5 # ์ž์‚ฐ ์ƒ๊ณต ํ†ต๊ณผ ํŒ์ • ๋ฐ˜๊ฒฝ(๊ฐ€์ƒ)\n", "\n", "\n", "def make_raid(seed, n=80, spoof=0):\n", " \"\"\"๊ฐ€์ƒ ํฌํ™” ์ƒํ™ฉ 1ํšŒ๋ถ„(๋น„ํ–‰์ฒด n๊ฐœ + ์Šคํ‘ธํ•‘ ๊ฐ€์งœ ํ•ญ์  spoof๊ฐœ)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(TYPES)\n", " rows = []\n", " for i in range(n + spoof):\n", " typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n", " threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n", " brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n", " x, y = dist * np.sin(brg), dist * np.cos(brg)\n", " spd = rng.uniform(*sp); alt = rng.uniform(*al)\n", " aim = int(rng.integers(0, len(ASSETS)))\n", " if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ€์ƒ ์ž์‚ฐ ๋ฐฉํ–ฅ(์ž‘์€ ๋ฐฉ์œ„ ์˜ค์ฐจ)\n", " ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n", " hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n", " else:\n", " hd = rng.uniform(0, 2 * np.pi); aim = -1\n", " vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n", " sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n", " \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n", " \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n", " jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n", " true_tti = np.inf\n", " if threat:\n", " d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n", " true_tti = d_aim * 1000 / spd\n", " rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n", " \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n", " \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n", " \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n", " \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n", " df = pd.DataFrame(rows)\n", " df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n", " for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n", " return df\n", "\n", "\n", "def geometry(df):\n", " \"\"\"๊ฐ ํ•ญ์ ๊ณผ ๊ฐ€์ƒ ์ž์‚ฐ ์‚ฌ์ด ์ตœ๊ทผ์ ‘์ (CPA) ๊ฑฐ๋ฆฌ์™€ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„(TTI, s)์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ์ƒคํ—ค๋“œ ์žํญ๋“œ๋ก  ๋ฐ ์ˆœํ•ญ๋ฏธ์‚ฌ์ผ ๋ณตํ•ฉ ๋น„ํ–‰ ํŠน์„ฑ ๋ถ„์„ ๋ฐ ํฌํ™”๊ฒฝ๋ณด ๋ฐ์ดํ„ฐ ๊ทœ๊ฒฉ ์ •์˜ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "SCHEMA = {\n", " \"track_id\": {\"type\": str, \"rule\": \"T+์ˆซ์ž 5์ž๋ฆฌ\", \"check\": lambda v: v[0] == \"T\" and v[1:].isdigit() and len(v) == 6},\n", " \"sensor_id\": {\"type\": str, \"allowed\": [\"RDR-1\", \"RDR-2\", \"EO-1\", \"ACU-1\"]},\n", " \"timestamp_ms\": {\"type\": int, \"range\": (0, 10 ** 13)},\n", " \"x_km\": {\"type\": float, \"range\": (-100, 100)},\n", " \"y_km\": {\"type\": float, \"range\": (-100, 100)},\n", " \"alt_m\": {\"type\": float, \"range\": (0, 15000)},\n", " \"speed_mps\": {\"type\": float, \"range\": (0, 400)},\n", " \"heading_deg\": {\"type\": float, \"range\": (0, 360)},\n", " \"approach_dir\": {\"type\": str, \"allowed\": [\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\"]},\n", " \"class_hint\": {\"type\": str, \"allowed\": [\"uav_type\", \"fast_type\", \"unknown\", \"civil\", \"bird\"]},\n", " \"threat_grade\": {\"type\": int, \"range\": (1, 3)}, # ์œ„ํ˜‘ ๊ฐ€์ค‘ ๋“ฑ๊ธ‰(๊ฐ€์ƒ, 1 ๋‚ฎ์Œ ~ 3 ๋†’์Œ)\n", " \"rcs\": {\"type\": float, \"range\": (0, 1)}, # ์ •๊ทœํ™” ๋ฐ˜์‚ฌ๊ฐ•๋„(๊ฐ€์ƒ)\n", " \"weather\": {\"type\": str, \"allowed\": [\"clear\", \"rain\", \"fog\", \"snow\"]},\n", " \"pos_error_m\": {\"type\": float, \"range\": (0, 2000)}, # ๋ณด์™„ ์ „์ˆ  ํŒŒ๋ผ๋ฏธํ„ฐ: ์œ„์น˜ ์˜ค์ฐจ\n", " \"comm_delay_ms\": {\"type\": int, \"range\": (0, 5000)}, # ๋ณด์™„ ์ „์ˆ  ํŒŒ๋ผ๋ฏธํ„ฐ: ํ†ต์‹  ์ง€์—ฐ\n", "}\n", "REQUIRED = list(SCHEMA)\n", "\n", "\n", "def validate(msg):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ์ƒคํ—ค๋“œ ์žํญ๋“œ๋ก  ๋ฐ ์ˆœํ•ญ๋ฏธ์‚ฌ์ผ ๋ณตํ•ฉ ๋น„ํ–‰ ํŠน์„ฑ ๋ถ„์„ ๋ฐ ํฌํ™”๊ฒฝ๋ณด ๋ฐ์ดํ„ฐ ๊ทœ๊ฒฉ ์ •์˜ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def to_message(r, rng):\n", " hd = (np.degrees(np.arctan2(r.vx, r.vy)) + 360) % 360\n", " frm = (hd + 180) % 360\n", " hint = {\"uav_type\": \"uav_type\", \"fast_type\": \"fast_type\", \"civil\": \"civil\", \"bird\": \"bird\"}.get(r.type, \"unknown\")\n", " return {\"track_id\": r.id, \"sensor_id\": str(rng.choice(SCHEMA[\"sensor_id\"][\"allowed\"])), \"timestamp_ms\": int(1.7e12 + rng.integers(0, 60000)),\n", " \"x_km\": round(float(r.x), 2), \"y_km\": round(float(r.y), 2), \"alt_m\": round(float(r.alt), 0), \"speed_mps\": round(float(r.spd), 1),\n", " \"heading_deg\": round(float(hd), 1), \"approach_dir\": [\"N\", \"NE\", \"E\", \"SE\", \"S\", \"SW\", \"W\", \"NW\"][int(((frm + 22.5) % 360) // 45)],\n", " \"class_hint\": hint, \"threat_grade\": 3 if r.type == \"fast_type\" else (2 if r.threat else 1), \"rcs\": round(float(r.rcs), 2),\n", " \"weather\": str(rng.choice([\"clear\", \"rain\", \"fog\", \"snow\"])), \"pos_error_m\": round(float(rng.uniform(20, 300)), 0),\n", " \"comm_delay_ms\": int(rng.integers(50, 800))}\n", "\n", "\n", "rng = np.random.default_rng(1)\n", "raid = make_raid(11, 20)\n", "samples = [to_message(r, rng) for r in raid.itertuples()]\n", "normal_err = sum(len(validate(m)) for m in samples)\n", "print(f\"[์ •์ƒ ์ƒ˜ํ”Œ ๊ฒ€์ฆ] {len(samples)}๊ฑด, ์˜ค๋ฅ˜ {normal_err}๊ฑด\")\n", "print(\"์ƒ˜ํ”Œ 1๊ฑด:\", json.dumps(samples[0], ensure_ascii=False)[:150], \"...\")\n", "\n", "bad = [dict(samples[0]) for _ in range(5)]\n", "bad[0][\"speed_mps\"] = \"fast\" # ํ˜•์‹ ์˜ค๋ฅ˜\n", "bad[1][\"sensor_id\"] = \"RDR-9\" # ํ—ˆ์šฉ๊ฐ’ ์˜ค๋ฅ˜\n", "bad[2][\"alt_m\"] = -50.0 # ๋ฒ”์œ„ ์˜ค๋ฅ˜\n", "del bad[3][\"comm_delay_ms\"] # ๋ˆ„๋ฝ(ํ†ต์‹  ์ง€์—ฐ)\n", "bad[4][\"warhead_kg\"] = 50 # ์ •์˜๋˜์ง€ ์•Š์€ ํ•„๋“œ(์‹ค์ œ ์ œ์› ์ž…๋ ฅ ์ฐจ๋‹จ)\n", "kinds = [\"ํ˜•์‹\", \"ํ—ˆ์šฉ๊ฐ’\", \"๋ฒ”์œ„\", \"๋ˆ„๋ฝ\", \"๋ฏธ์ •์˜ ํ•„๋“œ\"]\n", "print(\"\\n[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒ€์ถœ]\")\n", "detected = 0\n", "for k, m in zip(kinds, bad):\n", " e = validate(m); detected += bool(e)\n", " print(f\" {k:7s} -> {'๊ฒ€์ถœ' if e else '๋ฏธ๊ฒ€์ถœ'}: {e[0] if e else ''}\")\n", "\n", "print(\"\\n[๋ฐ์ดํ„ฐ ๋ช…์„ธ ์š”์•ฝ]\")\n", "spec = pd.DataFrame([{\"ํ•„๋“œ\": f, \"ํ˜•์‹\": s[\"type\"].__name__, \"๊ทœ์น™\": s.get(\"allowed\", s.get(\"range\", s.get(\"rule\")))} for f, s in SCHEMA.items()])\n", "print(spec.to_string(index=False))\n", "complete = sum(all(k in s for k in (\"type\",)) and any(k in s for k in (\"allowed\", \"range\", \"check\")) for s in SCHEMA.values()) / len(SCHEMA)\n", "print(f\"\\n์Šคํ‚ค๋งˆ ํ•„๋“œ ์ •์˜ ์™„์„ฑ๋„ {complete:.0%} (๋ชฉํ‘œ 100%) -> {'๋‹ฌ์„ฑ' if complete == 1 else '๋ฏธ๋‹ฌ'}\")\n", "print(f\"์ƒ˜ํ”Œ ๊ฒ€์ฆ ์˜ค๋ฅ˜ {normal_err}๊ฑด (๋ชฉํ‘œ 0๊ฑด) -> {'๋‹ฌ์„ฑ' if normal_err == 0 else '๋ฏธ๋‹ฌ'}, ์ฃผ์ž… ์˜ค๋ฅ˜ {detected}/5 ๊ฒ€์ถœ\")\n", "print(\"์ฐธ๊ณ : ์‹ค์ œ ๋ฌด๊ธฐ ์ œ์›ยท์‚ฌ๊ฑฐ๋ฆฌ ํ•„๋“œ๋Š” ๋‘์ง€ ์•Š์œผ๋ฉฐ, ๋ฏธ์ •์˜ ํ•„๋“œ๋Š” ๊ฒ€์ฆ ๋‹จ๊ณ„์—์„œ ์ฐจ๋‹จํ•œ๋‹ค.\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 01 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[์ •์ƒ ์ƒ˜ํ”Œ ๊ฒ€์ฆ] 20๊ฑด, ์˜ค๋ฅ˜ 0๊ฑด\n", "์ƒ˜ํ”Œ 1๊ฑด: {\"track_id\": \"T11000\", \"sensor_id\": \"RDR-2\", \"timestamp_ms\": 1700000030709, \"x_km\": 0.17, \"y_km\": -38.48, \"alt_m\": 204.0, \"speed_mps\": 30.9, \"heading_ ...\n", "\n", "[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒ€์ถœ]\n", " ํ˜•์‹ -> ๊ฒ€์ถœ: speed_mps: ํ˜•์‹ ์˜ค๋ฅ˜(str)\n", " ํ—ˆ์šฉ๊ฐ’ -> ๊ฒ€์ถœ: sensor_id: ํ—ˆ์šฉ๊ฐ’ ์•„๋‹˜(RDR-9)\n", " ๋ฒ”์œ„ -> ๊ฒ€์ถœ: alt_m: ๋ฒ”์œ„ ์ดˆ๊ณผ(-50.0)\n", " ๋ˆ„๋ฝ -> ๊ฒ€์ถœ: comm_delay_ms: ๋ˆ„๋ฝ\n", " ๋ฏธ์ •์˜ ํ•„๋“œ -> ๊ฒ€์ถœ: ์ •์˜๋˜์ง€ ์•Š์€ ํ•„๋“œ: ['warhead_kg']\n", "\n", "[๋ฐ์ดํ„ฐ ๋ช…์„ธ ์š”์•ฝ]\n", " ํ•„๋“œ ํ˜•์‹ ๊ทœ์น™\n", " track_id str T+์ˆซ์ž 5์ž๋ฆฌ\n", " sensor_id str [RDR-1, RDR-2, EO-1, ACU-1]\n", " timestamp_ms int (0, 10000000000000)\n", " x_km float (-100, 100)\n", " y_km float (-100, 100)\n", " alt_m float (0, 15000)\n", " speed_mps float (0, 400)\n", " heading_deg float (0, 360)\n", " approach_dir str [N, NE, E, SE, S, SW, W, NW]\n", " class_hint str [uav_type, fast_type, unknown, civil, bird]\n", " threat_grade int (1, 3)\n", " rcs float (0, 1)\n", " weather str [clear, rain, fog, snow]\n", " pos_error_m float (0, 2000)\n", "comm_delay_ms int (0, 5000)\n", "\n", "์Šคํ‚ค๋งˆ ํ•„๋“œ ์ •์˜ ์™„์„ฑ๋„ 100% (๋ชฉํ‘œ 100%) -> ๋‹ฌ์„ฑ\n", "์ƒ˜ํ”Œ ๊ฒ€์ฆ ์˜ค๋ฅ˜ 0๊ฑด (๋ชฉํ‘œ 0๊ฑด) -> ๋‹ฌ์„ฑ, ์ฃผ์ž… ์˜ค๋ฅ˜ 5/5 ๊ฒ€์ถœ\n", "์ฐธ๊ณ : ์‹ค์ œ ๋ฌด๊ธฐ ์ œ์›ยท์‚ฌ๊ฑฐ๋ฆฌ ํ•„๋“œ๋Š” ๋‘์ง€ ์•Š์œผ๋ฉฐ, ๋ฏธ์ •์˜ ํ•„๋“œ๋Š” ๊ฒ€์ฆ ๋‹จ๊ณ„์—์„œ ์ฐจ๋‹จํ•œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 02. ๋‹ค๋ฐฉํ–ฅ ๋™์‹œ ์นจํˆฌ ํ•ญ์  ๋ฐ์ดํ„ฐ ์ •์ œ ๋ฐ ์„ผ์„œ๋ณ„ ๊ฒฝ๋ณด ๋ฉ”์‹œ์ง€ ํ์ž‰ ์ „์ฒ˜๋ฆฌ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋‹ค๋ฐฉํ–ฅ์—์„œ ๋™์‹œ ์œ ์ž…๋˜๋Š” ๋Œ€๋Ÿ‰์˜ ๊ฒฝ๋ณด ๋ฉ”์‹œ์ง€๋ฅผ ํƒ€์ž„์Šคํƒฌํ”„ ์ˆœ์œผ๋กœ ์ •์ œํ•˜๊ณ  ์„ผ์„œ๋ณ„ ์ค‘๋ณต ๋ฉ”์‹œ์ง€๋ฅผ ํ์ž‰ํ•˜๋Š” ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ „์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์ถ•ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์—ฌ๋Ÿฌ ๋ฐฉํ–ฅยท์—ฌ๋Ÿฌ ์„ผ์„œ์—์„œ ๋™์‹œ์— ๋“ค์–ด์˜ค๋Š” ๊ฒฝ๋ณด ๋ฉ”์‹œ์ง€๋ฅผ ์‹œ๊ฐ ์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜๊ณ  ์ค‘๋ณตยท์žก์Œ์„ ๊ฑธ๋Ÿฌ๋‚ด๋Š” ์ „์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ์„ ๋งŒ๋“ ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์„ผ์„œ๋ณ„ ํ์— ์Œ“์ธ ๋ฉ”์‹œ์ง€๋ฅผ ๊ฐ๊ฐ ์ •๋ ฌํ•œ ๋’ค k-way ๋ณ‘ํ•ฉ์œผ๋กœ ํƒ€์ž„์Šคํƒฌํ”„ ์ˆœ์„œ๋ฅผ ๋งž์ถ˜๋‹ค ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/preprocess/ops04_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": [ "\"\"\"๋‹ค๋ฐฉํ–ฅ์—์„œ ๋™์‹œ์— ๋“ค์–ด์˜ค๋Š” ๊ฐ€์ƒ ์„ผ์„œ ๊ฒฝ๋ณด ๋ฉ”์‹œ์ง€๋ฅผ ์„ผ์„œ๋ณ„ ํ์— ๋ฐ›์•„ ํƒ€์ž„์Šคํƒฌํ”„ ์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜๊ณ , ์ค‘๋ณตยท๋ฐ˜์‚ฌํŒŒ(์œ ๋ น ํ•ญ์ )ยท์Šคํ‘ธํ•‘ยท์„ผ์„œ ๋‹จ์ ˆ์„ ์ •์ œํ•˜๋Š” ์ „์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌํ˜„ํ•ด ์ฒ˜๋ฆฌ ์†๋„์™€ ์˜ค๋ฅ˜์œจ์„ ์ธก์ •ํ•œ๋‹ค.\"\"\"\n", "import heapq\n", "import time\n", "from collections import defaultdict, deque\n", "import numpy as np\n", "import pandas as pd\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋ฐฉ์–ด์ง€์—ญยท๊ฐ€์ƒ ๋น„ํ–‰์ฒด ์ƒ์„ฑ๊ธฐ ----------------\n", "# ์ขŒํ‘œ๋Š” ๊ฐ€์ƒ ์ง€ํœ˜์†Œ๋ฅผ ์›์ ์œผ๋กœ ํ•œ km ๋‹จ์œ„ ํ‰๋ฉด์ด๋‹ค. ๋น„ํ–‰์ฒด ์œ ํ˜•์€ ์ถ”์ƒ ๋ฒ”์ฃผ์ด๋ฉฐ\n", "# ์†๋„ยท๊ณ ๋„ยท๋ฐ˜์‚ฌ๊ฐ•๋„๋Š” ํ•™์Šต์šฉ ์ž„์˜ ๋ฒ”์œ„๋กœ, ์‹ค์ œ ๋ฌด๊ธฐ ์ œ์›ยท์‚ฌ๊ฑฐ๋ฆฌยทํƒ„๋„์™€ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n", " [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n", " [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n", " [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n", "TYPES = { # ์œ ํ˜•: (์œ„ํ˜‘ ์—ฌ๋ถ€, ์†๋„ m/s ๋ฒ”์œ„, ๊ณ ๋„ m ๋ฒ”์œ„, ์ถœํ˜„ ๋น„์œจ)\n", " \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ €์† ๋ฌด์ธ๊ธฐํ˜•(๊ฐ€์ƒ)\n", " \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์† ์ˆœํ•ญํ˜•(๊ฐ€์ƒ)\n", " \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋งŒ์ฒด(๊ฐ€์ƒ)\n", " \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํ•ญ๊ธฐ(๊ฐ€์ƒ)\n", " \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ˜ ๊ตฐ์ง‘\n", "}\n", "TTI_URGENT = 600 # ๊ธด๊ธ‰ ํŒ์ • ๊ธฐ์ค€: ๊ฐ€์ƒ ์ž์‚ฐ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„ 600์ดˆ ์ด๋‚ด\n", "CPA_KM = 1.5 # ์ž์‚ฐ ์ƒ๊ณต ํ†ต๊ณผ ํŒ์ • ๋ฐ˜๊ฒฝ(๊ฐ€์ƒ)\n", "\n", "\n", "def make_raid(seed, n=80, spoof=0):\n", " \"\"\"๊ฐ€์ƒ ํฌํ™” ์ƒํ™ฉ 1ํšŒ๋ถ„(๋น„ํ–‰์ฒด n๊ฐœ + ์Šคํ‘ธํ•‘ ๊ฐ€์งœ ํ•ญ์  spoof๊ฐœ)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(TYPES)\n", " rows = []\n", " for i in range(n + spoof):\n", " typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n", " threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n", " brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n", " x, y = dist * np.sin(brg), dist * np.cos(brg)\n", " spd = rng.uniform(*sp); alt = rng.uniform(*al)\n", " aim = int(rng.integers(0, len(ASSETS)))\n", " if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ€์ƒ ์ž์‚ฐ ๋ฐฉํ–ฅ(์ž‘์€ ๋ฐฉ์œ„ ์˜ค์ฐจ)\n", " ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n", " hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n", " else:\n", " hd = rng.uniform(0, 2 * np.pi); aim = -1\n", " vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n", " sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n", " \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n", " \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n", " jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n", " true_tti = np.inf\n", " if threat:\n", " d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n", " true_tti = d_aim * 1000 / spd\n", " rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n", " \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n", " \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n", " \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n", " \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n", " df = pd.DataFrame(rows)\n", " df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n", " for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n", " return df\n", "\n", "\n", "def geometry(df):\n", " \"\"\"๊ฐ ํ•ญ์ ๊ณผ ๊ฐ€์ƒ ์ž์‚ฐ ์‚ฌ์ด ์ตœ๊ทผ์ ‘์ (CPA) ๊ฑฐ๋ฆฌ์™€ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„(TTI, s)์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] ๋‹ค๋ฐฉํ–ฅ ๋™์‹œ ์นจํˆฌ ํ•ญ์  ๋ฐ์ดํ„ฐ ์ •์ œ ๋ฐ ์„ผ์„œ๋ณ„ ๊ฒฝ๋ณด ๋ฉ”์‹œ์ง€ ํ์ž‰ ์ „์ฒ˜๋ฆฌ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "SENSORS = [\"RDR-1\", \"RDR-2\", \"EO-1\", \"ACU-1\"]\n", "\n", "\n", "def make_stream(seed=21, n=120, steps=30):\n", " \"\"\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์„ผ์„œ๋ณ„ ๋ฉ”์‹œ์ง€ ์ŠคํŠธ๋ฆผ. label: valid/dup/ghost/spoof (์ฑ„์ ์šฉ ์ •๋‹ต)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " raid = make_raid(seed, n, spoof=6)\n", " queues = {s: [] for s in SENSORS}\n", " mid = 0\n", " for r in raid.itertuples():\n", " sens = list(rng.choice(SENSORS, size=max(1, r.n_sensors), replace=False))\n", " for k in range(steps):\n", " if r.Index % 9 == 0 and 10 <= k < 15: # ์ผ๋ถ€ ํ•ญ์ ์€ ์„ผ์„œ ๋‹จ์ ˆ ๊ตฌ๊ฐ„\n", " continue\n", " t = k * 1000 + int(rng.integers(0, 300))\n", " x, y = r.x + r.vx * k, r.y + r.vy * k\n", " if r.type == \"spoof\": # ์Šคํ‘ธํ•‘: ์œ„์น˜๊ฐ€ ๋น„์ •์ƒ์ ์œผ๋กœ ๋„์•ฝ\n", " x, y = x + rng.normal(0, 3), y + rng.normal(0, 3)\n", " for s in sens:\n", " m = {\"mid\": mid, \"sensor\": s, \"track\": r.id, \"ts\": t + int(rng.integers(0, 80)), \"x\": x, \"y\": y,\n", " \"spd\": r.spd, \"snr\": rng.uniform(0.4, 1.0), \"label\": \"spoof\" if r.type == \"spoof\" else \"valid\"}\n", " queues[s].append(m); mid += 1\n", " if rng.random() < 0.08: # ์ค‘๋ณต ์žฌ์ „์†ก\n", " queues[s].append({**m, \"mid\": mid, \"label\": \"dup\"}); mid += 1\n", " if s.startswith(\"RDR\") and rng.random() < 0.03: # ๋ฐ˜์‚ฌํŒŒ ์œ ๋ น ํ•ญ์ (์•ฝํ•œ ์‹ ํ˜ธ, ๋Œ€์นญ ์œ„์น˜)\n", " queues[s].append({**m, \"mid\": mid, \"track\": \"G\" + r.id[1:], \"x\": -x, \"snr\": rng.uniform(0.05, 0.2),\n", " \"label\": \"ghost\"}); mid += 1\n", " for s in SENSORS: # ์ „์†ก ์ง€์—ฐ์œผ๋กœ ์ˆœ์„œ๊ฐ€ ์„ž์—ฌ ๋„์ฐฉ\n", " q = queues[s]\n", " idx = rng.permutation(len(q))[: len(q) // 10]\n", " for a, b in zip(idx[::2], idx[1::2]):\n", " q[a], q[b] = q[b], q[a]\n", " return queues\n", "\n", "\n", "def preprocess(queues, snr_min=0.25, jump_ratio=3.0, gap_ms=5000):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] ๋‹ค๋ฐฉํ–ฅ ๋™์‹œ ์นจํˆฌ ํ•ญ์  ๋ฐ์ดํ„ฐ ์ •์ œ ๋ฐ ์„ผ์„œ๋ณ„ ๊ฒฝ๋ณด ๋ฉ”์‹œ์ง€ ํ์ž‰ ์ „์ฒ˜๋ฆฌ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "queues = make_stream()\n", "n_in = sum(len(q) for q in queues.values())\n", "t0 = time.perf_counter()\n", "out, stats, merged = preprocess(queues)\n", "dt = time.perf_counter() - t0\n", "rate = n_in / dt\n", "\n", "kept = {mid for f in out for mid in f[\"src\"]}\n", "labels = {m[\"mid\"]: m[\"label\"] for m in merged}\n", "wrong_drop = sum(1 for k, l in labels.items() if l == \"valid\" and k not in kept)\n", "wrong_keep = sum(1 for k, l in labels.items() if l != \"valid\" and k in kept)\n", "err = (wrong_drop + wrong_keep) / n_in\n", "\n", "print(f\"[์ „์ฒ˜๋ฆฌ ์ „] ๋ฉ”์‹œ์ง€ {n_in}๊ฑด (์„ผ์„œ {len(queues)}๊ฐœ), ์œ ํ˜•๋ณ„:\", pd.Series(labels).value_counts().to_dict())\n", "print(f\"[์ „์ฒ˜๋ฆฌ ํ›„] ์œตํ•ฉ ๋ณด๊ณ  {len(out)}๊ฑด, ํ•ญ์  {len({f['track'] for f in out})}๊ฐœ\")\n", "for k, v in stats.items():\n", " print(f\" - {k}: {v}\")\n", "ts = [m[\"ts\"] for m in merged]\n", "print(f\" - ํƒ€์ž„์Šคํƒฌํ”„ ์ •๋ ฌ ์œ„๋ฐ˜: {sum(b < a for a, b in zip(ts, ts[1:]))}๊ฑด\")\n", "multi = sum(len(f[\"sensors\"]) > 1 for f in out) / len(out)\n", "print(f\" - ๋‹ค์ค‘ ์„ผ์„œ ์œตํ•ฉ ๋น„์œจ: {multi:.1%}\")\n", "print(f\"\\n์ฒ˜๋ฆฌ ์‹œ๊ฐ„ {dt * 1000:.1f} ms, ์ฒ˜๋ฆฌ ์†๋„ {rate:,.0f}๊ฑด/์ดˆ (๋ชฉํ‘œ 1,000๊ฑด/์ดˆ ์ด์ƒ) -> {'๋‹ฌ์„ฑ' if rate >= 1000 else '๋ฏธ๋‹ฌ'}\")\n", "print(f\"์ •์ƒ ๋ฉ”์‹œ์ง€ ์˜ค์‚ญ์ œ {wrong_drop}๊ฑด, ์žก์Œ ๋ฏธ์ œ๊ฑฐ {wrong_keep}๊ฑด, ์ „์ฒ˜๋ฆฌ ์˜ค๋ฅ˜์œจ {err:.2%} (๋ชฉํ‘œ 0%) -> {'๋‹ฌ์„ฑ' if err == 0 else '๋ฏธ๋‹ฌ'}\")\n", "print(\"\\n[์ „์ฒ˜๋ฆฌ ํ›„ ๋ฐ์ดํ„ฐ ์•ž๋ถ€๋ถ„]\")\n", "print(pd.DataFrame([{\"track\": f[\"track\"], \"sec\": f[\"sec\"], \"x_km\": round(f[\"x\"], 2), \"y_km\": round(f[\"y\"], 2), \"์„ผ์„œ์ˆ˜\": len(f[\"sensors\"])}\n", " for f in sorted(out, key=lambda f: (f[\"sec\"], f[\"track\"]))[:6]]).to_string(index=False))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 02 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[์ „์ฒ˜๋ฆฌ ์ „] ๋ฉ”์‹œ์ง€ 8100๊ฑด (์„ผ์„œ 4๊ฐœ), ์œ ํ˜•๋ณ„: {'valid': 7145, 'dup': 612, 'spoof': 240, 'ghost': 103}\n", "[์ „์ฒ˜๋ฆฌ ํ›„] ์œตํ•ฉ ๋ณด๊ณ  3530๊ฑด, ํ•ญ์  120๊ฐœ\n", " - ๋ฐ˜์‚ฌํŒŒ ์ œ๊ฑฐ: 103\n", " - ์ค‘๋ณต ์ œ๊ฑฐ: 612\n", " - ์Šคํ‘ธํ•‘ ํ•ญ์  ๊ฒฉ๋ฆฌ: 6\n", " - ์„ผ์„œ ๋‹จ์ ˆ ๊ตฌ๊ฐ„ ํ‘œ์‹œ: 14\n", " - ํƒ€์ž„์Šคํƒฌํ”„ ์ •๋ ฌ ์œ„๋ฐ˜: 0๊ฑด\n", " - ๋‹ค์ค‘ ์„ผ์„œ ์œตํ•ฉ ๋น„์œจ: 71.8%\n", "\n", "์ฒ˜๋ฆฌ ์‹œ๊ฐ„ 49.3 ms, ์ฒ˜๋ฆฌ ์†๋„ 164,466๊ฑด/์ดˆ (๋ชฉํ‘œ 1,000๊ฑด/์ดˆ ์ด์ƒ) -> ๋‹ฌ์„ฑ\n", "์ •์ƒ ๋ฉ”์‹œ์ง€ ์˜ค์‚ญ์ œ 0๊ฑด, ์žก์Œ ๋ฏธ์ œ๊ฑฐ 0๊ฑด, ์ „์ฒ˜๋ฆฌ ์˜ค๋ฅ˜์œจ 0.00% (๋ชฉํ‘œ 0%) -> ๋‹ฌ์„ฑ\n", "\n", "[์ „์ฒ˜๋ฆฌ ํ›„ ๋ฐ์ดํ„ฐ ์•ž๋ถ€๋ถ„]\n", " track sec x_km y_km ์„ผ์„œ์ˆ˜\n", "T21000 0 -27.36 -34.88 3\n", "T21001 0 50.43 13.12 2\n", "T21002 0 0.03 20.05 3\n", "T21003 0 -8.61 -25.31 3\n", "T21004 0 -13.38 -24.24 1\n", "T21005 0 50.83 -22.68 2\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 03. ์†๋„ยท๊ณ ๋„ยท์ ‘๊ทผ ๋ฐฉ์œ„๊ฐ ๊ธฐ๋ฐ˜ 1์ฐจ ๊ณต์ค‘์œ„ํ˜‘ ์šฐ์„ ์ˆœ์œ„ ์‚ฐ์ • ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋ฐฉ์–ด ๊ฑฐ์ ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ, ์ž”์—ฌ ๋„๋‹ฌ ์‹œ๊ฐ„, ์ ‘๊ทผ ์†๋„๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ 1์ฐจ ์œ„ํ˜‘ ์ˆœ์œ„๋ฅผ ์ •๋ ฌํ•˜๋Š” Baseline ๋žญํ‚น ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ฐฉ์–ด ์ž์‚ฐ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ์™€ ์ž”์—ฌ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„(TTI)๋งŒ์œผ๋กœ 1์ฐจ ์œ„ํ˜‘ ์ˆœ์œ„๋ฅผ ์ •๋ ฌํ•˜๋Š” ํˆฌ๋ช…ํ•œ Baseline์„ ๋งŒ๋“ ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ฐ ํ•ญ์ ์˜ ์ตœ๊ทผ์ ‘์ (CPA)์ด ๊ฐ€์ƒ ์ž์‚ฐ ๋ฐ˜๊ฒฝ 1.5 km ์•ˆ์„ ์ง€๋‚˜๋Š”์ง€ ๊ณ„์‚ฐํ•˜๊ณ , ํ•ด๋‹น ์ž์‚ฐ๊นŒ์ง€์˜ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„์„ ๊ตฌํ•ด ์งง์€ ์ˆœ์œผ๋กœ ์ •๋ ฌ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/ops04_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": [ "\"\"\"๊ฐ€์ƒ ๋ฐฉ์–ด์ž์‚ฐ๊นŒ์ง€์˜ ์ตœ๊ทผ์ ‘ ๊ฑฐ๋ฆฌ์™€ ์ž”์—ฌ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„(TTI), ์ ‘๊ทผ ์†๋„๋งŒ์œผ๋กœ 1์ฐจ ์œ„ํ˜‘ ์ˆœ์œ„๋ฅผ ์ •๋ ฌํ•˜๋Š” Baseline ๋žญํ‚น ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•˜๊ณ  ๊ธด๊ธ‰ ํŒ์ • ์ •ํ™•๋„๋ฅผ ์ธก์ •ํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋ฐฉ์–ด์ง€์—ญยท๊ฐ€์ƒ ๋น„ํ–‰์ฒด ์ƒ์„ฑ๊ธฐ ----------------\n", "# ์ขŒํ‘œ๋Š” ๊ฐ€์ƒ ์ง€ํœ˜์†Œ๋ฅผ ์›์ ์œผ๋กœ ํ•œ km ๋‹จ์œ„ ํ‰๋ฉด์ด๋‹ค. ๋น„ํ–‰์ฒด ์œ ํ˜•์€ ์ถ”์ƒ ๋ฒ”์ฃผ์ด๋ฉฐ\n", "# ์†๋„ยท๊ณ ๋„ยท๋ฐ˜์‚ฌ๊ฐ•๋„๋Š” ํ•™์Šต์šฉ ์ž„์˜ ๋ฒ”์œ„๋กœ, ์‹ค์ œ ๋ฌด๊ธฐ ์ œ์›ยท์‚ฌ๊ฑฐ๋ฆฌยทํƒ„๋„์™€ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n", " [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n", " [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n", " [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n", "TYPES = { # ์œ ํ˜•: (์œ„ํ˜‘ ์—ฌ๋ถ€, ์†๋„ m/s ๋ฒ”์œ„, ๊ณ ๋„ m ๋ฒ”์œ„, ์ถœํ˜„ ๋น„์œจ)\n", " \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ €์† ๋ฌด์ธ๊ธฐํ˜•(๊ฐ€์ƒ)\n", " \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์† ์ˆœํ•ญํ˜•(๊ฐ€์ƒ)\n", " \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋งŒ์ฒด(๊ฐ€์ƒ)\n", " \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํ•ญ๊ธฐ(๊ฐ€์ƒ)\n", " \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ˜ ๊ตฐ์ง‘\n", "}\n", "TTI_URGENT = 600 # ๊ธด๊ธ‰ ํŒ์ • ๊ธฐ์ค€: ๊ฐ€์ƒ ์ž์‚ฐ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„ 600์ดˆ ์ด๋‚ด\n", "CPA_KM = 1.5 # ์ž์‚ฐ ์ƒ๊ณต ํ†ต๊ณผ ํŒ์ • ๋ฐ˜๊ฒฝ(๊ฐ€์ƒ)\n", "\n", "\n", "def make_raid(seed, n=80, spoof=0):\n", " \"\"\"๊ฐ€์ƒ ํฌํ™” ์ƒํ™ฉ 1ํšŒ๋ถ„(๋น„ํ–‰์ฒด n๊ฐœ + ์Šคํ‘ธํ•‘ ๊ฐ€์งœ ํ•ญ์  spoof๊ฐœ)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(TYPES)\n", " rows = []\n", " for i in range(n + spoof):\n", " typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n", " threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n", " brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n", " x, y = dist * np.sin(brg), dist * np.cos(brg)\n", " spd = rng.uniform(*sp); alt = rng.uniform(*al)\n", " aim = int(rng.integers(0, len(ASSETS)))\n", " if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ€์ƒ ์ž์‚ฐ ๋ฐฉํ–ฅ(์ž‘์€ ๋ฐฉ์œ„ ์˜ค์ฐจ)\n", " ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n", " hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n", " else:\n", " hd = rng.uniform(0, 2 * np.pi); aim = -1\n", " vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n", " sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n", " \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n", " \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n", " jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n", " true_tti = np.inf\n", " if threat:\n", " d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n", " true_tti = d_aim * 1000 / spd\n", " rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n", " \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n", " \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n", " \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n", " \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n", " df = pd.DataFrame(rows)\n", " df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n", " for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n", " return df\n", "\n", "\n", "def geometry(df):\n", " \"\"\"๊ฐ ํ•ญ์ ๊ณผ ๊ฐ€์ƒ ์ž์‚ฐ ์‚ฌ์ด ์ตœ๊ทผ์ ‘์ (CPA) ๊ฑฐ๋ฆฌ์™€ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„(TTI, s)์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 3] ์†๋„ยท๊ณ ๋„ยท์ ‘๊ทผ ๋ฐฉ์œ„๊ฐ ๊ธฐ๋ฐ˜ 1์ฐจ ๊ณต์ค‘์œ„ํ˜‘ ์šฐ์„ ์ˆœ์œ„ ์‚ฐ์ • ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "FEATS = [\"log_spd\", \"alt_km\", \"rcs\", \"rcs_var\", \"n_sensors\", \"civ_iff\", \"jump_ratio\"]\n", "\n", "\n", "def features(df):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 3] ์†๋„ยท๊ณ ๋„ยท์ ‘๊ทผ ๋ฐฉ์œ„๊ฐ ๊ธฐ๋ฐ˜ 1์ฐจ ๊ณต์ค‘์œ„ํ˜‘ ์šฐ์„ ์ˆœ์œ„ ์‚ฐ์ • ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def train_classifier(seeds=(101, 102, 103, 104)):\n", " \"\"\"ํ•™์Šต์šฉ ๊ฐ€์ƒ ์ƒํ™ฉ(์‹œํ—˜ ์ƒํ™ฉ๊ณผ ๋‹ค๋ฅธ ์‹œ๋“œ)์œผ๋กœ '์œ„ํ˜‘ ์œ ์‚ฌ๋„' ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค.\"\"\"\n", " tr = pd.concat([make_raid(s, 120, spoof=10) for s in seeds], ignore_index=True)\n", " clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500, C=1.0))\n", " clf.fit(features(tr)[FEATS], tr[\"threat\"].astype(int))\n", " return clf\n", "\n", "\n", "def baseline_rank(df):\n", " \"\"\"Baseline: ๊ฑฐ๋ฆฌยท๋„๋‹ฌ์‹œ๊ฐ„๋งŒ์œผ๋กœ 1์ฐจ ์œ„ํ˜‘ ์ ์ˆ˜๋ฅผ ๋งค๊ธด๋‹ค(์ž์‚ฐ ๊ฐ€์น˜ยท์œ ํ˜• ํŒ๋ณ„ ์—†์Œ).\"\"\"\n", " _, tti = geometry(df)\n", " t_min = tti.min(axis=1)\n", " out = df[[\"id\"]].copy()\n", " out[\"tti_s\"] = t_min\n", " out[\"score\"] = np.exp(-t_min / 300)\n", " out[\"urgent_pred\"] = t_min <= TTI_URGENT\n", " return out.sort_values(\"score\", ascending=False)\n", "\n", "\n", "def advanced_rank(df, clf, w=None):\n", " \"\"\"๊ณ ๋„ํ™”: ์ž์‚ฐ ๊ฐ€์น˜ x ๋„๋‹ฌ์‹œ๊ฐ„ x ์œ„ํ˜‘ ์œ ์‚ฌ๋„, ์šด๋™ํ•™ ๋ถˆ์ผ์น˜(์Šคํ‘ธํ•‘ ์˜์‹ฌ) ํ•ญ์ ์€ ๋ณ„๋„ ํ‘œ์‹œํ•œ๋‹ค.\"\"\"\n", " w = w or {\"tau\": 300, \"p_min\": 0.5, \"value_pow\": 1.0, \"jump_max\": 2.0}\n", " _, tti = geometry(df)\n", " val = ASSETS[\"value\"].to_numpy()[None] ** w[\"value_pow\"]\n", " asset_score = val * np.exp(-tti / w[\"tau\"])\n", " best = asset_score.argmax(axis=1)\n", " p = clf.predict_proba(features(df)[FEATS])[:, 1]\n", " spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > w[\"jump_max\"]\n", " out = df[[\"id\"]].copy()\n", " out[\"asset\"] = ASSETS[\"asset\"].to_numpy()[best]\n", " out[\"tti_s\"] = tti[np.arange(len(df)), best]\n", " out[\"p_threat\"] = p\n", " out[\"spoof_flag\"] = spoof\n", " out[\"score\"] = np.where(spoof, 0, p * asset_score.max(axis=1))\n", " out[\"urgent_pred\"] = (p >= w[\"p_min\"]) & (out[\"tti_s\"] <= TTI_URGENT) & ~spoof\n", " return out.sort_values(\"score\", ascending=False)\n", "\n", "# ํŒ๋‹จ ๊ธฐ์ค€(ํˆฌ๋ช…์„ฑ): โ‘  ๋น„ํ–‰ ๊ฒฝ๋กœ๊ฐ€ ๊ฐ€์ƒ ์ž์‚ฐ ๋ฐ˜๊ฒฝ 1.5 km ์•ˆ์„ ์ง€๋‚˜๋Š”๊ฐ€(CPA)\n", "# โ‘ก ๊ทธ ์ž์‚ฐ๊นŒ์ง€ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„(TTI)์ด ์งง์€๊ฐ€ โ‘ข TTI 600์ดˆ ์ด๋‚ด๋ฉด '๊ธด๊ธ‰'\n", "raid = make_raid(7, 80)\n", "rank = baseline_rank(raid).merge(raid[[\"id\", \"type\", \"spd\", \"urgent\"]], on=\"id\")\n", "_, tti = geometry(raid)\n", "dist = np.hypot(raid[\"x\"], raid[\"y\"])\n", "y, p = rank[\"urgent\"].to_numpy(), rank[\"urgent_pred\"].to_numpy()\n", "acc = (y == p).mean()\n", "print(f\"[Baseline 1์ฐจ ์œ„ํ˜‘ ์ˆœ์œ„] ๊ฐ€์ƒ ๋น„ํ–‰์ฒด {len(raid)}๊ฐœ, ์‹ค์ œ ๊ธด๊ธ‰ {y.sum()}๊ฐœ, ๊ธด๊ธ‰ ํŒ์ • {p.sum()}๊ฐœ\")\n", "top = rank.head(10).copy()\n", "top[\"์ˆœ์œ„\"] = range(1, 11)\n", "top[\"tti_s\"] = top[\"tti_s\"].round(0); top[\"spd\"] = top[\"spd\"].round(0)\n", "print(top[[\"์ˆœ์œ„\", \"id\", \"tti_s\", \"spd\", \"type\", \"urgent\"]].rename(columns={\"tti_s\": \"๋„๋‹ฌ์˜ˆ์ƒ(s)\", \"spd\": \"์†๋„(m/s)\", \"type\": \"์‹ค์ œ์œ ํ˜•\", \"urgent\": \"์‹ค์ œ๊ธด๊ธ‰\"}).to_string(index=False))\n", "\n", "print(\"\\n[์œ ํ˜•๋ณ„ ํŒ์ • ๊ฒฐ๊ณผ] (๊ธด๊ธ‰ ํŒ์ • ์ˆ˜ / ์ „์ฒด)\")\n", "for t, g in rank.groupby(\"type\"):\n", " print(f\" {t:10s}: {int(g['urgent_pred'].sum())}/{len(g)} (์‹ค์ œ ๊ธด๊ธ‰ {int(g['urgent'].sum())})\")\n", "top10_true = set(raid.sort_values(\"true_prio\", ascending=False).head(10)[\"id\"])\n", "overlap = len(top10_true & set(rank.head(10)[\"id\"])) / 10\n", "print(f\"\\n๊ธด๊ธ‰ ํŒ์ • ์ •ํ™•๋„ {acc:.1%} (๋ชฉํ‘œ 75% ์ด์ƒ) -> {'๋‹ฌ์„ฑ' if acc >= 0.75 else '๋ฏธ๋‹ฌ'}\")\n", "print(f\"์ƒ์œ„ 10๊ฐœ ์ˆœ์œ„ ์ผ์น˜์œจ {overlap:.0%}, ์˜ค๊ฒฝ๋ณด {int((p & ~y).sum())}๊ฑด(๊ธฐ๋งŒ์ฒดยท์กฐ๋ฅ˜ ๋“ฑ ์œ ํ˜• ํŒ๋ณ„ ์—†์Œ์ด ์›์ธ)\")\n", "print(\"ํ•œ๊ณ„: ์ž์‚ฐ ๊ฐ€์น˜์™€ ๋น„ํ–‰์ฒด ์œ ํ˜•์„ ๊ณ ๋ คํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ฏธ์…˜ 4์—์„œ ๊ณ ๋„ํ™”ํ•œ๋‹ค. ์ˆœ์œ„๋Š” '๊ถŒ๊ณ '์ด๋ฉฐ ํŒ๋‹จ์€ ์ง€ํœ˜๊ด€์ด ํ•œ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(12, 5))\n", "ax[0].scatter(raid[\"x\"], raid[\"y\"], c=np.where(rank.set_index(\"id\").loc[raid[\"id\"], \"urgent_pred\"], \"red\", \"gray\"), s=18)\n", "ax[0].scatter(ASSETS[\"x\"], ASSETS[\"y\"], marker=\"^\", s=120, c=\"blue\", label=\"virtual assets\")\n", "ax[0].set_title(\"Baseline: red = urgent (TTI <= 600 s)\"); ax[0].set_xlabel(\"East (km)\"); ax[0].set_ylabel(\"North (km)\"); ax[0].legend()\n", "ax[0].set_aspect(\"equal\")\n", "t15 = rank.head(15)\n", "ax[1].barh(t15[\"id\"][::-1], t15[\"tti_s\"][::-1], color=[\"red\" if u else \"gray\" for u in t15[\"urgent\"][::-1]])\n", "ax[1].axvline(TTI_URGENT, ls=\"--\", c=\"k\"); ax[1].set_title(\"Top-15 by TTI (red = truly urgent)\"); ax[1].set_xlabel(\"TTI (s)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 03 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[Baseline 1์ฐจ ์œ„ํ˜‘ ์ˆœ์œ„] ๊ฐ€์ƒ ๋น„ํ–‰์ฒด 80๊ฐœ, ์‹ค์ œ ๊ธด๊ธ‰ 31๊ฐœ, ๊ธด๊ธ‰ ํŒ์ • 39๊ฐœ\n", " ์ˆœ์œ„ id ๋„๋‹ฌ์˜ˆ์ƒ(s) ์†๋„(m/s) ์‹ค์ œ์œ ํ˜• ์‹ค์ œ๊ธด๊ธ‰\n", " 1 T07061 42.0 59.0 decoy False\n", " 2 T07055 53.0 55.0 uav_type True\n", " 3 T07048 68.0 221.0 fast_type True\n", " 4 T07014 103.0 231.0 fast_type True\n", " 5 T07004 152.0 224.0 fast_type True\n", " 6 T07038 158.0 59.0 uav_type True\n", " 7 T07019 162.0 54.0 uav_type True\n", " 8 T07033 173.0 45.0 uav_type True\n", " 9 T07065 176.0 154.0 fast_type True\n", " 10 T07062 180.0 158.0 fast_type True\n", "\n", "[์œ ํ˜•๋ณ„ ํŒ์ • ๊ฒฐ๊ณผ] (๊ธด๊ธ‰ ํŒ์ • ์ˆ˜ / ์ „์ฒด)\n", " bird : 0/6 (์‹ค์ œ ๊ธด๊ธ‰ 0)\n", " civil : 1/4 (์‹ค์ œ ๊ธด๊ธ‰ 0)\n", " decoy : 7/16 (์‹ค์ œ ๊ธด๊ธ‰ 0)\n", " fast_type : 13/13 (์‹ค์ œ ๊ธด๊ธ‰ 13)\n", " uav_type : 18/41 (์‹ค์ œ ๊ธด๊ธ‰ 18)\n", "\n", "๊ธด๊ธ‰ ํŒ์ • ์ •ํ™•๋„ 90.0% (๋ชฉํ‘œ 75% ์ด์ƒ) -> ๋‹ฌ์„ฑ\n", "์ƒ์œ„ 10๊ฐœ ์ˆœ์œ„ ์ผ์น˜์œจ 80%, ์˜ค๊ฒฝ๋ณด 8๊ฑด(๊ธฐ๋งŒ์ฒดยท์กฐ๋ฅ˜ ๋“ฑ ์œ ํ˜• ํŒ๋ณ„ ์—†์Œ์ด ์›์ธ)\n", "ํ•œ๊ณ„: ์ž์‚ฐ ๊ฐ€์น˜์™€ ๋น„ํ–‰์ฒด ์œ ํ˜•์„ ๊ณ ๋ คํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ฏธ์…˜ 4์—์„œ ๊ณ ๋„ํ™”ํ•œ๋‹ค. ์ˆœ์œ„๋Š” '๊ถŒ๊ณ '์ด๋ฉฐ ํŒ๋‹จ์€ ์ง€ํœ˜๊ด€์ด ํ•œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 04. ํ•ต์‹ฌ ๋ฐฉ์–ด์ž์‚ฐ(์ง€ํœ˜์†Œยท์ธํ”„๋ผ) ๊ฑฐ๋ฆฌ ๋ฐ ๋„๋‹ฌ ์˜ˆ์ƒ ์‹œ๊ฐ„(TTI) ์—ฐ๊ณ„ ๋‹ค์ฐจ์› ์œ„ํ˜‘ ํ‰๊ฐ€ ๊ณ ๋„ํ™”\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "ํ•ต์‹ฌ ๋ฐฉ์–ด์ž์‚ฐ(์ง€ํœ˜์†Œ, ๋ ˆ์ด๋”, ํƒ„์•ฝ์ฐฝ)์˜ ๋ฐฉํ˜ธ ๊ฐ€์น˜์™€ ํ‘œ์  ๋„๋‹ฌ ์˜ˆ์ƒ ์‹œ๊ฐ„(TTI)์„ ๊ฒฐํ•ฉํ•˜์—ฌ ๋ณตํ•ฉ ์œ„ํ˜‘ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋™์ ์œผ๋กœ ์žฌ๊ณ„์‚ฐํ•˜๋Š” ๋ชจ๋ธ์„ ๊ณ ๋„ํ™”ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์ž์‚ฐ ๋ฐฉํ˜ธ ๊ฐ€์น˜, ์ž์‚ฐ๋ณ„ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„, ์œ„ํ˜‘ ์œ ์‚ฌ๋„๋ฅผ ๊ฒฐํ•ฉํ•ด ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋™์ ์œผ๋กœ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜๋Š” ๋‹ค์ฐจ์› ํ‰๊ฐ€ ๋ชจ๋ธ๋กœ ๊ณ ๋„ํ™”ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ž์‚ฐ ๊ฐ€์น˜ x exp(-TTI/300)๋กœ ์ž์‚ฐ๋ณ„ ์œ„ํ˜‘์„ ๊ตฌํ•˜๊ณ , ๋ฐ˜์‚ฌ๊ฐ•๋„ยท์‹ ํ˜ธ ๋ณ€๋™ยท์„ผ์„œ ํ•ฉ์˜ยท๋ฏผ๊ฐ„ ์‘๋‹ต ์‹ ํ˜ธ๋กœ ํ•™์Šตํ•œ ์œ„ํ˜‘ ์œ ์‚ฌ๋„ ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ๊ณฑ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/ops04_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": [ "\"\"\"๊ฐ€์ƒ ๋ฐฉ์–ด์ž์‚ฐ์˜ ๋ฐฉํ˜ธ ๊ฐ€์น˜์™€ ์ž์‚ฐ๋ณ„ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„(TTI), ์œ„ํ˜‘ ์œ ์‚ฌ๋„ ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ๊ฒฐํ•ฉํ•œ ๋‹ค์ฐจ์› ์œ„ํ˜‘ ํ‰๊ฐ€ ๋ชจ๋ธ๋กœ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋™์ ์œผ๋กœ ์žฌ๊ณ„์‚ฐํ•˜๊ณ , ๊ธฐ๋งŒ์ฒดยท์Šคํ‘ธํ•‘ ํ•ญ์ ์— ๋Œ€ํ•œ ๋ฐฉ์–ด ๋กœ์ง๊ณผ ์žฌํ˜„์œจยท์ฒ˜๋ฆฌ ์ง€์—ฐ์„ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋ฐฉ์–ด์ง€์—ญยท๊ฐ€์ƒ ๋น„ํ–‰์ฒด ์ƒ์„ฑ๊ธฐ ----------------\n", "# ์ขŒํ‘œ๋Š” ๊ฐ€์ƒ ์ง€ํœ˜์†Œ๋ฅผ ์›์ ์œผ๋กœ ํ•œ km ๋‹จ์œ„ ํ‰๋ฉด์ด๋‹ค. ๋น„ํ–‰์ฒด ์œ ํ˜•์€ ์ถ”์ƒ ๋ฒ”์ฃผ์ด๋ฉฐ\n", "# ์†๋„ยท๊ณ ๋„ยท๋ฐ˜์‚ฌ๊ฐ•๋„๋Š” ํ•™์Šต์šฉ ์ž„์˜ ๋ฒ”์œ„๋กœ, ์‹ค์ œ ๋ฌด๊ธฐ ์ œ์›ยท์‚ฌ๊ฑฐ๋ฆฌยทํƒ„๋„์™€ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n", " [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n", " [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n", " [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n", "TYPES = { # ์œ ํ˜•: (์œ„ํ˜‘ ์—ฌ๋ถ€, ์†๋„ m/s ๋ฒ”์œ„, ๊ณ ๋„ m ๋ฒ”์œ„, ์ถœํ˜„ ๋น„์œจ)\n", " \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ €์† ๋ฌด์ธ๊ธฐํ˜•(๊ฐ€์ƒ)\n", " \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์† ์ˆœํ•ญํ˜•(๊ฐ€์ƒ)\n", " \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋งŒ์ฒด(๊ฐ€์ƒ)\n", " \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํ•ญ๊ธฐ(๊ฐ€์ƒ)\n", " \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ˜ ๊ตฐ์ง‘\n", "}\n", "TTI_URGENT = 600 # ๊ธด๊ธ‰ ํŒ์ • ๊ธฐ์ค€: ๊ฐ€์ƒ ์ž์‚ฐ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„ 600์ดˆ ์ด๋‚ด\n", "CPA_KM = 1.5 # ์ž์‚ฐ ์ƒ๊ณต ํ†ต๊ณผ ํŒ์ • ๋ฐ˜๊ฒฝ(๊ฐ€์ƒ)\n", "\n", "\n", "def make_raid(seed, n=80, spoof=0):\n", " \"\"\"๊ฐ€์ƒ ํฌํ™” ์ƒํ™ฉ 1ํšŒ๋ถ„(๋น„ํ–‰์ฒด n๊ฐœ + ์Šคํ‘ธํ•‘ ๊ฐ€์งœ ํ•ญ์  spoof๊ฐœ)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(TYPES)\n", " rows = []\n", " for i in range(n + spoof):\n", " typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n", " threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n", " brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n", " x, y = dist * np.sin(brg), dist * np.cos(brg)\n", " spd = rng.uniform(*sp); alt = rng.uniform(*al)\n", " aim = int(rng.integers(0, len(ASSETS)))\n", " if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ€์ƒ ์ž์‚ฐ ๋ฐฉํ–ฅ(์ž‘์€ ๋ฐฉ์œ„ ์˜ค์ฐจ)\n", " ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n", " hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n", " else:\n", " hd = rng.uniform(0, 2 * np.pi); aim = -1\n", " vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n", " sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n", " \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n", " \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n", " jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n", " true_tti = np.inf\n", " if threat:\n", " d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n", " true_tti = d_aim * 1000 / spd\n", " rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n", " \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n", " \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n", " \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n", " \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n", " df = pd.DataFrame(rows)\n", " df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n", " for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n", " return df\n", "\n", "\n", "def geometry(df):\n", " \"\"\"๊ฐ ํ•ญ์ ๊ณผ ๊ฐ€์ƒ ์ž์‚ฐ ์‚ฌ์ด ์ตœ๊ทผ์ ‘์ (CPA) ๊ฑฐ๋ฆฌ์™€ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„(TTI, s)์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] ํ•ต์‹ฌ ๋ฐฉ์–ด์ž์‚ฐ(์ง€ํœ˜์†Œยท์ธํ”„๋ผ) ๊ฑฐ๋ฆฌ ๋ฐ ๋„๋‹ฌ ์˜ˆ์ƒ ์‹œ๊ฐ„(TTI) ์—ฐ๊ณ„ ๋‹ค์ฐจ์› ์œ„ํ˜‘ ํ‰๊ฐ€ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "FEATS = [\"log_spd\", \"alt_km\", \"rcs\", \"rcs_var\", \"n_sensors\", \"civ_iff\", \"jump_ratio\"]\n", "\n", "\n", "def features(df):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] ํ•ต์‹ฌ ๋ฐฉ์–ด์ž์‚ฐ(์ง€ํœ˜์†Œยท์ธํ”„๋ผ) ๊ฑฐ๋ฆฌ ๋ฐ ๋„๋‹ฌ ์˜ˆ์ƒ ์‹œ๊ฐ„(TTI) ์—ฐ๊ณ„ ๋‹ค์ฐจ์› ์œ„ํ˜‘ ํ‰๊ฐ€ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def train_classifier(seeds=(101, 102, 103, 104)):\n", " \"\"\"ํ•™์Šต์šฉ ๊ฐ€์ƒ ์ƒํ™ฉ(์‹œํ—˜ ์ƒํ™ฉ๊ณผ ๋‹ค๋ฅธ ์‹œ๋“œ)์œผ๋กœ '์œ„ํ˜‘ ์œ ์‚ฌ๋„' ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค.\"\"\"\n", " tr = pd.concat([make_raid(s, 120, spoof=10) for s in seeds], ignore_index=True)\n", " clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500, C=1.0))\n", " clf.fit(features(tr)[FEATS], tr[\"threat\"].astype(int))\n", " return clf\n", "\n", "\n", "def baseline_rank(df):\n", " \"\"\"Baseline: ๊ฑฐ๋ฆฌยท๋„๋‹ฌ์‹œ๊ฐ„๋งŒ์œผ๋กœ 1์ฐจ ์œ„ํ˜‘ ์ ์ˆ˜๋ฅผ ๋งค๊ธด๋‹ค(์ž์‚ฐ ๊ฐ€์น˜ยท์œ ํ˜• ํŒ๋ณ„ ์—†์Œ).\"\"\"\n", " _, tti = geometry(df)\n", " t_min = tti.min(axis=1)\n", " out = df[[\"id\"]].copy()\n", " out[\"tti_s\"] = t_min\n", " out[\"score\"] = np.exp(-t_min / 300)\n", " out[\"urgent_pred\"] = t_min <= TTI_URGENT\n", " return out.sort_values(\"score\", ascending=False)\n", "\n", "\n", "def advanced_rank(df, clf, w=None):\n", " \"\"\"๊ณ ๋„ํ™”: ์ž์‚ฐ ๊ฐ€์น˜ x ๋„๋‹ฌ์‹œ๊ฐ„ x ์œ„ํ˜‘ ์œ ์‚ฌ๋„, ์šด๋™ํ•™ ๋ถˆ์ผ์น˜(์Šคํ‘ธํ•‘ ์˜์‹ฌ) ํ•ญ์ ์€ ๋ณ„๋„ ํ‘œ์‹œํ•œ๋‹ค.\"\"\"\n", " w = w or {\"tau\": 300, \"p_min\": 0.5, \"value_pow\": 1.0, \"jump_max\": 2.0}\n", " _, tti = geometry(df)\n", " val = ASSETS[\"value\"].to_numpy()[None] ** w[\"value_pow\"]\n", " asset_score = val * np.exp(-tti / w[\"tau\"])\n", " best = asset_score.argmax(axis=1)\n", " p = clf.predict_proba(features(df)[FEATS])[:, 1]\n", " spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > w[\"jump_max\"]\n", " out = df[[\"id\"]].copy()\n", " out[\"asset\"] = ASSETS[\"asset\"].to_numpy()[best]\n", " out[\"tti_s\"] = tti[np.arange(len(df)), best]\n", " out[\"p_threat\"] = p\n", " out[\"spoof_flag\"] = spoof\n", " out[\"score\"] = np.where(spoof, 0, p * asset_score.max(axis=1))\n", " out[\"urgent_pred\"] = (p >= w[\"p_min\"]) & (out[\"tti_s\"] <= TTI_URGENT) & ~spoof\n", " return out.sort_values(\"score\", ascending=False)\n", "\n", "clf = train_classifier()\n", "rows = []\n", "for seed in (7, 8, 9): # ์‹œํ—˜์šฉ ๊ฐ€์ƒ ์ƒํ™ฉ 3๊ฐœ(์Šคํ‘ธํ•‘ ํ•ญ์  ํฌํ•จ)\n", " raid = make_raid(seed, 80, spoof=6)\n", " t0 = time.perf_counter()\n", " adv = advanced_rank(raid, clf)\n", " ms = (time.perf_counter() - t0) * 1000\n", " base = baseline_rank(raid)\n", " for name, r in ((\"Baseline\", base), (\"๊ณ ๋„ํ™”\", adv)):\n", " m = raid.merge(r[[\"id\", \"urgent_pred\"]], on=\"id\")\n", " y, p = m[\"urgent\"].to_numpy(), m[\"urgent_pred\"].to_numpy()\n", " rows.append({\"์ƒํ™ฉ\": seed, \"๋ชจ๋ธ\": name, \"์žฌํ˜„์œจ\": (p & y).sum() / y.sum(), \"์ •๋ฐ€๋„\": (p & y).sum() / max(p.sum(), 1),\n", " \"๊ธฐ๋งŒยท์Šคํ‘ธํ•‘ ์˜คํŒ\": int((p & m[\"type\"].isin([\"decoy\", \"spoof\"]).to_numpy()).sum()),\n", " \"์ง€์—ฐ(ms)\": ms if name == \"๊ณ ๋„ํ™”\" else np.nan})\n", "res = pd.DataFrame(rows)\n", "print(\"[๋ชจ๋ธ ๋น„๊ต] ์‹ค์ œ ๊ธด๊ธ‰ ํ‘œ์  ๊ธฐ์ค€\")\n", "print(res.round(3).fillna(\"-\").to_string(index=False))\n", "\n", "# ๋™์  ์žฌ๊ณ„์‚ฐ: 10์ดˆ ๊ฒฝ๊ณผ ํ›„ ์œ„์น˜๋ฅผ ๊ฐฑ์‹ ํ•ด ์ˆœ์œ„ ๋ณ€ํ™”๋ฅผ ํ™•์ธ\n", "raid = make_raid(7, 80, spoof=6)\n", "r0 = advanced_rank(raid, clf).head(5)\n", "moved = raid.copy(); moved[\"x\"] += moved[\"vx\"] * 10; moved[\"y\"] += moved[\"vy\"] * 10\n", "r1 = advanced_rank(moved, clf).head(5)\n", "print(\"\\n[๋™์  ์žฌ๊ณ„์‚ฐ] ์ƒ์œ„ 5๊ฐœ (ํ˜„์žฌ -> 10์ดˆ ํ›„)\")\n", "for a, b in zip(r0.itertuples(), r1.itertuples()):\n", " print(f\" {a.id} {a.asset} TTI {a.tti_s:5.0f}s ์ ์ˆ˜ {a.score:.2f} -> {b.id} {b.asset} TTI {b.tti_s:5.0f}s ์ ์ˆ˜ {b.score:.2f}\")\n", "\n", "adv_res = res[res[\"๋ชจ๋ธ\"] == \"๊ณ ๋„ํ™”\"]\n", "rec, lat = adv_res[\"์žฌํ˜„์œจ\"].mean(), adv_res[\"์ง€์—ฐ(ms)\"].max()\n", "print(f\"\\n๋ณตํ•ฉ ์œ„ํ˜‘ ์žฌํ˜„์œจ ํ‰๊ท  {rec:.1%} (๋ชฉํ‘œ 85% ์ด์ƒ) -> {'๋‹ฌ์„ฑ' if rec >= 0.85 else '๋ฏธ๋‹ฌ'}\")\n", "print(f\"์ฒ˜๋ฆฌ ์ง€์—ฐ ์ตœ๋Œ€ {lat:.1f} ms (๋ชฉํ‘œ 200 ms ์ดํ•˜) -> {'๋‹ฌ์„ฑ' if lat <= 200 else '๋ฏธ๋‹ฌ'}\")\n", "print(f\"์Šคํ‘ธํ•‘ ์˜์‹ฌ ํ•ญ์  ๊ฒฉ๋ฆฌ {int(advanced_rank(raid, clf)['spoof_flag'].sum())}/6๊ฑด (์šด๋™ํ•™ ๋ถˆ์ผ์น˜ = ๋ณด๊ณ  ์†๋„ ๋Œ€๋น„ ์œ„์น˜ ๋„์•ฝ 2๋ฐฐ ์ดˆ๊ณผ)\")\n", "print(\"์œ ์˜: ์œ„ํ˜‘ ์œ ์‚ฌ๋„๋Š” ๊ฐ€์ƒ ๋ฐ˜์‚ฌ๊ฐ•๋„ยท์„ผ์„œ ํ•ฉ์˜ยท๋ฏผ๊ฐ„ ์‘๋‹ต ์‹ ํ˜ธ ๋“ฑ ์ถ”์ƒ ํŠน์ง• ๊ธฐ๋ฐ˜์ด๋ฉฐ, ์ตœ์ข… ์‹๋ณ„์€ ์šด์šฉ์ž ํ™•์ธ์ด ํ•„์š”ํ•˜๋‹ค.\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 04 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๋ชจ๋ธ ๋น„๊ต] ์‹ค์ œ ๊ธด๊ธ‰ ํ‘œ์  ๊ธฐ์ค€\n", " ์ƒํ™ฉ ๋ชจ๋ธ ์žฌํ˜„์œจ ์ •๋ฐ€๋„ ๊ธฐ๋งŒยท์Šคํ‘ธํ•‘ ์˜คํŒ ์ง€์—ฐ(ms)\n", " 7 Baseline 1.000 0.721 11 -\n", " 7 ๊ณ ๋„ํ™” 0.968 1.000 0 7.677\n", " 8 Baseline 1.000 0.861 3 -\n", " 8 ๊ณ ๋„ํ™” 1.000 1.000 0 6.501\n", " 9 Baseline 1.000 0.703 9 -\n", " 9 ๊ณ ๋„ํ™” 1.000 0.963 0 6.603\n", "\n", "[๋™์  ์žฌ๊ณ„์‚ฐ] ์ƒ์œ„ 5๊ฐœ (ํ˜„์žฌ -> 10์ดˆ ํ›„)\n", " T07055 A2 TTI 53s ์ ์ˆ˜ 0.60 -> T07055 A2 TTI 43s ์ ์ˆ˜ 0.62\n", " T07014 A2 TTI 103s ์ ์ˆ˜ 0.57 -> T07014 A2 TTI 93s ์ ์ˆ˜ 0.59\n", " T07033 A1 TTI 173s ์ ์ˆ˜ 0.56 -> T07033 A1 TTI 163s ์ ์ˆ˜ 0.58\n", " T07004 A1 TTI 181s ์ ์ˆ˜ 0.55 -> T07004 A1 TTI 171s ์ ์ˆ˜ 0.57\n", " T07053 A1 TTI 185s ์ ์ˆ˜ 0.54 -> T07053 A1 TTI 175s ์ ์ˆ˜ 0.56\n", "\n", "๋ณตํ•ฉ ์œ„ํ˜‘ ์žฌํ˜„์œจ ํ‰๊ท  98.9% (๋ชฉํ‘œ 85% ์ด์ƒ) -> ๋‹ฌ์„ฑ\n", "์ฒ˜๋ฆฌ ์ง€์—ฐ ์ตœ๋Œ€ 7.7 ms (๋ชฉํ‘œ 200 ms ์ดํ•˜) -> ๋‹ฌ์„ฑ\n", "์Šคํ‘ธํ•‘ ์˜์‹ฌ ํ•ญ์  ๊ฒฉ๋ฆฌ 6/6๊ฑด (์šด๋™ํ•™ ๋ถˆ์ผ์น˜ = ๋ณด๊ณ  ์†๋„ ๋Œ€๋น„ ์œ„์น˜ ๋„์•ฝ 2๋ฐฐ ์ดˆ๊ณผ)\n", "์œ ์˜: ์œ„ํ˜‘ ์œ ์‚ฌ๋„๋Š” ๊ฐ€์ƒ ๋ฐ˜์‚ฌ๊ฐ•๋„ยท์„ผ์„œ ํ•ฉ์˜ยท๋ฏผ๊ฐ„ ์‘๋‹ต ์‹ ํ˜ธ ๋“ฑ ์ถ”์ƒ ํŠน์ง• ๊ธฐ๋ฐ˜์ด๋ฉฐ, ์ตœ์ข… ์‹๋ณ„์€ ์šด์šฉ์ž ํ™•์ธ์ด ํ•„์š”ํ•˜๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 05. ๋ฐฉ๊ณต ์œ ๋„๋ฌด๊ธฐ ๊ต์ „ ์šฐ์„ ์ˆœ์œ„ ๋žญํ‚น ๋ฐ ๋ณตํ•ฉ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ์‚ฐ์ถœ ๋ชจ๋“ˆ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋ฐฉ๊ณต ํฌ๋Œ€๋ณ„ ์ž”์—ฌ ์š”๊ฒฉ ๋ฏธ์‚ฌ์ผ ์ˆ˜๋Ÿ‰ ๋ฐ ๊ต์ „ ๊ฐ€๋Šฅ ํ™•๋ฅ ์„ ๋ฐ˜์˜ํ•˜์—ฌ ์ตœ์  ๋Œ€์‘ ์ˆœ์œ„ ๋ฐ ๋ณตํ•ฉ ์œ„ํ˜‘ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜๋ฅผ ์‚ฐ์ถœํ•˜๋Š” ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ณตํ•ฉ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜์™€ High/Med/Low ๋“ฑ๊ธ‰์œผ๋กœ ์˜ค๊ฒฝ๋ณด๋ฅผ ์ค„์ด๊ณ , ๊ฐ€์ƒ ๋Œ€์‘ ์ž์› ์ƒํ™ฉ์„ ๋ฐ˜์˜ํ•œ '๊ถŒ๊ณ ' ๋Œ€์‘ ์ˆœ์œ„๋ฅผ ๋งŒ๋“ ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์œ„ํ˜‘ ์œ ์‚ฌ๋„, ์„ผ์„œ ํ•ฉ์˜, ์‹ ํ˜ธ ์•ˆ์ •์„ฑ, ์šด๋™ํ•™ ์ผ๊ด€์„ฑ์„ ๊ฐ€์ค‘ ํ•ฉ์‚ฐํ•ด ์‹ ๋ขฐ๋„ ์ง€์ˆ˜๋ฅผ ๋งŒ๋“ค๊ณ  ๋“ฑ๊ธ‰ ๊ธฐ์ค€์„ ์ • ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/verification/ops04_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": [ "\"\"\"์œ„ํ˜‘ ์œ ์‚ฌ๋„ยท์„ผ์„œ ํ•ฉ์˜ยท์‹ ํ˜ธ ์•ˆ์ •์„ฑยท์šด๋™ํ•™ ์ผ๊ด€์„ฑ์„ ๊ฒฐํ•ฉํ•œ ๋ณตํ•ฉ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜(High/Med/Low)๋ฅผ ์‚ฐ์ถœํ•ด ์˜ค๊ฒฝ๋ณด๋ฅผ ์ค„์ด๊ณ , ๊ฐ€์ƒ ๋Œ€์‘ ์ž์›์˜ ์ž”์—ฌ ์ˆ˜๋Ÿ‰๊ณผ ๊ฐ€์ • ๋Œ€์‘ ์„ฑ๊ณต ์ ์ˆ˜๋ฅผ ๋ฐ˜์˜ํ•œ '๊ถŒ๊ณ ' ๋Œ€์‘ ์ˆœ์œ„๋ฅผ ๋งŒ๋“ ๋‹ค(๋ชจ๋“  ๊ถŒ๊ณ ๋Š” ์ง€ํœ˜๊ด€ ์Šน์ธ ๋Œ€๊ธฐ).\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋ฐฉ์–ด์ง€์—ญยท๊ฐ€์ƒ ๋น„ํ–‰์ฒด ์ƒ์„ฑ๊ธฐ ----------------\n", "# ์ขŒํ‘œ๋Š” ๊ฐ€์ƒ ์ง€ํœ˜์†Œ๋ฅผ ์›์ ์œผ๋กœ ํ•œ km ๋‹จ์œ„ ํ‰๋ฉด์ด๋‹ค. ๋น„ํ–‰์ฒด ์œ ํ˜•์€ ์ถ”์ƒ ๋ฒ”์ฃผ์ด๋ฉฐ\n", "# ์†๋„ยท๊ณ ๋„ยท๋ฐ˜์‚ฌ๊ฐ•๋„๋Š” ํ•™์Šต์šฉ ์ž„์˜ ๋ฒ”์œ„๋กœ, ์‹ค์ œ ๋ฌด๊ธฐ ์ œ์›ยท์‚ฌ๊ฑฐ๋ฆฌยทํƒ„๋„์™€ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n", " [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n", " [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n", " [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n", "TYPES = { # ์œ ํ˜•: (์œ„ํ˜‘ ์—ฌ๋ถ€, ์†๋„ m/s ๋ฒ”์œ„, ๊ณ ๋„ m ๋ฒ”์œ„, ์ถœํ˜„ ๋น„์œจ)\n", " \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ €์† ๋ฌด์ธ๊ธฐํ˜•(๊ฐ€์ƒ)\n", " \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์† ์ˆœํ•ญํ˜•(๊ฐ€์ƒ)\n", " \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋งŒ์ฒด(๊ฐ€์ƒ)\n", " \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํ•ญ๊ธฐ(๊ฐ€์ƒ)\n", " \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ˜ ๊ตฐ์ง‘\n", "}\n", "TTI_URGENT = 600 # ๊ธด๊ธ‰ ํŒ์ • ๊ธฐ์ค€: ๊ฐ€์ƒ ์ž์‚ฐ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„ 600์ดˆ ์ด๋‚ด\n", "CPA_KM = 1.5 # ์ž์‚ฐ ์ƒ๊ณต ํ†ต๊ณผ ํŒ์ • ๋ฐ˜๊ฒฝ(๊ฐ€์ƒ)\n", "\n", "\n", "def make_raid(seed, n=80, spoof=0):\n", " \"\"\"๊ฐ€์ƒ ํฌํ™” ์ƒํ™ฉ 1ํšŒ๋ถ„(๋น„ํ–‰์ฒด n๊ฐœ + ์Šคํ‘ธํ•‘ ๊ฐ€์งœ ํ•ญ์  spoof๊ฐœ)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(TYPES)\n", " rows = []\n", " for i in range(n + spoof):\n", " typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n", " threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n", " brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n", " x, y = dist * np.sin(brg), dist * np.cos(brg)\n", " spd = rng.uniform(*sp); alt = rng.uniform(*al)\n", " aim = int(rng.integers(0, len(ASSETS)))\n", " if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ€์ƒ ์ž์‚ฐ ๋ฐฉํ–ฅ(์ž‘์€ ๋ฐฉ์œ„ ์˜ค์ฐจ)\n", " ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n", " hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n", " else:\n", " hd = rng.uniform(0, 2 * np.pi); aim = -1\n", " vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n", " sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n", " \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n", " \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n", " jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n", " true_tti = np.inf\n", " if threat:\n", " d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n", " true_tti = d_aim * 1000 / spd\n", " rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n", " \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n", " \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n", " \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n", " \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n", " df = pd.DataFrame(rows)\n", " df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n", " for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n", " return df\n", "\n", "\n", "def geometry(df):\n", " \"\"\"๊ฐ ํ•ญ์ ๊ณผ ๊ฐ€์ƒ ์ž์‚ฐ ์‚ฌ์ด ์ตœ๊ทผ์ ‘์ (CPA) ๊ฑฐ๋ฆฌ์™€ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„(TTI, s)์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] ๋ฐฉ๊ณต ์œ ๋„๋ฌด๊ธฐ ๊ต์ „ ์šฐ์„ ์ˆœ์œ„ ๋žญํ‚น ๋ฐ ๋ณตํ•ฉ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ์‚ฐ์ถœ ๋ชจ๋“ˆ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "FEATS = [\"log_spd\", \"alt_km\", \"rcs\", \"rcs_var\", \"n_sensors\", \"civ_iff\", \"jump_ratio\"]\n", "\n", "\n", "def features(df):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] ๋ฐฉ๊ณต ์œ ๋„๋ฌด๊ธฐ ๊ต์ „ ์šฐ์„ ์ˆœ์œ„ ๋žญํ‚น ๋ฐ ๋ณตํ•ฉ ์‹ ๋ขฐ๋„ ์ง€์ˆ˜ ์‚ฐ์ถœ ๋ชจ๋“ˆ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def train_classifier(seeds=(101, 102, 103, 104)):\n", " \"\"\"ํ•™์Šต์šฉ ๊ฐ€์ƒ ์ƒํ™ฉ(์‹œํ—˜ ์ƒํ™ฉ๊ณผ ๋‹ค๋ฅธ ์‹œ๋“œ)์œผ๋กœ '์œ„ํ˜‘ ์œ ์‚ฌ๋„' ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค.\"\"\"\n", " tr = pd.concat([make_raid(s, 120, spoof=10) for s in seeds], ignore_index=True)\n", " clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500, C=1.0))\n", " clf.fit(features(tr)[FEATS], tr[\"threat\"].astype(int))\n", " return clf\n", "\n", "\n", "def baseline_rank(df):\n", " \"\"\"Baseline: ๊ฑฐ๋ฆฌยท๋„๋‹ฌ์‹œ๊ฐ„๋งŒ์œผ๋กœ 1์ฐจ ์œ„ํ˜‘ ์ ์ˆ˜๋ฅผ ๋งค๊ธด๋‹ค(์ž์‚ฐ ๊ฐ€์น˜ยท์œ ํ˜• ํŒ๋ณ„ ์—†์Œ).\"\"\"\n", " _, tti = geometry(df)\n", " t_min = tti.min(axis=1)\n", " out = df[[\"id\"]].copy()\n", " out[\"tti_s\"] = t_min\n", " out[\"score\"] = np.exp(-t_min / 300)\n", " out[\"urgent_pred\"] = t_min <= TTI_URGENT\n", " return out.sort_values(\"score\", ascending=False)\n", "\n", "\n", "def advanced_rank(df, clf, w=None):\n", " \"\"\"๊ณ ๋„ํ™”: ์ž์‚ฐ ๊ฐ€์น˜ x ๋„๋‹ฌ์‹œ๊ฐ„ x ์œ„ํ˜‘ ์œ ์‚ฌ๋„, ์šด๋™ํ•™ ๋ถˆ์ผ์น˜(์Šคํ‘ธํ•‘ ์˜์‹ฌ) ํ•ญ์ ์€ ๋ณ„๋„ ํ‘œ์‹œํ•œ๋‹ค.\"\"\"\n", " w = w or {\"tau\": 300, \"p_min\": 0.5, \"value_pow\": 1.0, \"jump_max\": 2.0}\n", " _, tti = geometry(df)\n", " val = ASSETS[\"value\"].to_numpy()[None] ** w[\"value_pow\"]\n", " asset_score = val * np.exp(-tti / w[\"tau\"])\n", " best = asset_score.argmax(axis=1)\n", " p = clf.predict_proba(features(df)[FEATS])[:, 1]\n", " spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > w[\"jump_max\"]\n", " out = df[[\"id\"]].copy()\n", " out[\"asset\"] = ASSETS[\"asset\"].to_numpy()[best]\n", " out[\"tti_s\"] = tti[np.arange(len(df)), best]\n", " out[\"p_threat\"] = p\n", " out[\"spoof_flag\"] = spoof\n", " out[\"score\"] = np.where(spoof, 0, p * asset_score.max(axis=1))\n", " out[\"urgent_pred\"] = (p >= w[\"p_min\"]) & (out[\"tti_s\"] <= TTI_URGENT) & ~spoof\n", " return out.sort_values(\"score\", ascending=False)\n", "\n", "GRADE = [(0.70, \"High\"), (0.45, \"Med\"), (0.0, \"Low\")]\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋Œ€์‘ ์ž์›: ์ž”์—ฌ ์ˆ˜๋Ÿ‰, ๋‹ด๋‹น ์ž์‚ฐ, ์œ ํ˜•๋ณ„ '๊ฐ€์ • ๋Œ€์‘ ์„ฑ๊ณต ์ ์ˆ˜'(์ถ”์ƒ๊ฐ’, ์‹ค์ œ ๋ฌด๊ธฐ ์„ฑ๋Šฅ ์•„๋‹˜)\n", "RESOURCES = pd.DataFrame([[\"R1\", 6, [\"A1\", \"A2\"], {\"uav_type\": 0.8, \"fast_type\": 0.5}],\n", " [\"R2\", 4, [\"A1\", \"A3\"], {\"uav_type\": 0.7, \"fast_type\": 0.7}],\n", " [\"R3\", 8, [\"A2\", \"A3\", \"A4\"], {\"uav_type\": 0.6, \"fast_type\": 0.3}]],\n", " columns=[\"res\", \"remain\", \"covers\", \"score_by_type\"])\n", "\n", "\n", "def confidence(df, clf):\n", " p = clf.predict_proba(features(df)[FEATS])[:, 1]\n", " agree = df[\"n_sensors\"].to_numpy() / 3\n", " stable = 1 - np.clip(df[\"rcs_var\"].to_numpy() / 0.4, 0, 1)\n", " kin = np.clip(2 - df[\"max_jump\"].to_numpy() / df[\"spd\"].to_numpy(), 0, 1)\n", " conf = 0.5 * p + 0.2 * agree + 0.15 * stable + 0.15 * kin\n", " grade = np.select([conf >= GRADE[0][0], conf >= GRADE[1][0]], [\"High\", \"Med\"], \"Low\")\n", " return conf, grade\n", "\n", "\n", "clf = train_classifier()\n", "raid = make_raid(8, 80, spoof=6)\n", "base = raid.merge(baseline_rank(raid)[[\"id\", \"urgent_pred\"]], on=\"id\")\n", "adv = raid.merge(advanced_rank(raid, clf), on=\"id\")\n", "conf, grade = confidence(raid, clf)\n", "adv[\"conf\"] = conf[raid.set_index(\"id\").index.get_indexer(adv[\"id\"])]\n", "adv[\"grade\"] = grade[raid.set_index(\"id\").index.get_indexer(adv[\"id\"])]\n", "alert = adv[\"urgent_pred\"] & (adv[\"grade\"] != \"Low\")\n", "\n", "far_b = (base[\"urgent_pred\"] & ~base[\"urgent\"]).sum() / base[\"urgent_pred\"].sum()\n", "far_n = (alert & ~adv[\"urgent\"]).sum() / max(alert.sum(), 1)\n", "cut = (far_b - far_n) / far_b if far_b > 0 else 1.0\n", "print(f\"[์˜ค๊ฒฝ๋ณด์œจ] Baseline {far_b:.1%} -> ์‹ ๋ขฐ๋„ ์—”์ง„ {far_n:.1%}, ๊ฐ์ถ•๋ฅ  {cut:.0%} (๋ชฉํ‘œ 40% ์ด์ƒ) -> {'๋‹ฌ์„ฑ' if cut >= 0.4 else '๋ฏธ๋‹ฌ'}\")\n", "print(\"[๋“ฑ๊ธ‰ ๊ธฐ์ค€] High: ์ง€์ˆ˜ 0.70 ์ด์ƒ(๋ณต์ˆ˜ ์„ผ์„œยท์•ˆ์ • ์‹ ํ˜ธ), Med: 0.45~0.70(์ถ”๊ฐ€ ํ™•์ธ ํ•„์š”), Low: 0.45 ๋ฏธ๋งŒ(๊ฒฝ๋ณด ๋ณด๋ฅ˜)\")\n", "print(pd.crosstab(adv[\"type\"], adv[\"grade\"]).to_string())\n", "\n", "# ์ผ๊ด€์„ฑ: ์ธก์ • ์žก์Œ(๋ฐ˜์‚ฌ๊ฐ•๋„ยท์†๋„ ยฑ5%)์„ ๋„ฃ์–ด 20ํšŒ ๋ฐ˜๋ณตํ•  ๋•Œ ๋“ฑ๊ธ‰์ด ์œ ์ง€๋˜๋Š” ๋น„์œจ\n", "rng = np.random.default_rng(0)\n", "same = []\n", "for _ in range(20):\n", " noisy = raid.copy()\n", " noisy[\"rcs\"] *= rng.normal(1, 0.05, len(raid)); noisy[\"spd\"] *= rng.normal(1, 0.05, len(raid))\n", " same.append((confidence(noisy, clf)[1] == grade).mean())\n", "cons = float(np.mean(same))\n", "print(f\"\\n์‹ ๋ขฐ๋„ ๋“ฑ๊ธ‰ ์ผ๊ด€์„ฑ {cons:.1%} (๋ชฉํ‘œ 90% ์ด์ƒ) -> {'๋‹ฌ์„ฑ' if cons >= 0.9 else '๋ฏธ๋‹ฌ'}\")\n", "\n", "# ๊ถŒ๊ณ  ๋Œ€์‘ ์ˆœ์œ„: ์ ์ˆ˜ ์ˆœ์œผ๋กœ ๋‹ด๋‹น ์ž์› ์ค‘ (๊ฐ€์ • ๋Œ€์‘ ์ ์ˆ˜ x ์ž”์—ฌ ๋น„์œจ)์ด ๊ฐ€์žฅ ํฐ ์ž์›์„ '๊ถŒ๊ณ '\n", "remain = dict(zip(RESOURCES[\"res\"], RESOURCES[\"remain\"]))\n", "recs = []\n", "for r in adv[alert].sort_values(\"score\", ascending=False).itertuples():\n", " cand = [(row.score_by_type.get(r.type, 0.4) * remain[row.res] / row.remain, row.res) for row in RESOURCES.itertuples()\n", " if r.asset in row.covers and remain[row.res] > 0]\n", " res = max(cand)[1] if cand else \"์ž์›๋ถ€์กฑ-์ƒ๊ธ‰๋ณด๊ณ \"\n", " if cand:\n", " remain[res] -= 1\n", " recs.append({\"๊ถŒ๊ณ ์ˆœ์œ„\": len(recs) + 1, \"ํ‘œ์ \": r.id, \"์ง€ํ–ฅ์ž์‚ฐ\": r.asset, \"๋„๋‹ฌ์˜ˆ์ƒ(s)\": int(r.tti_s), \"์‹ ๋ขฐ๋„\": round(r.conf, 2),\n", " \"๋“ฑ๊ธ‰\": r.grade, \"๊ถŒ๊ณ ์ž์›\": res, \"์ƒํƒœ\": \"์Šน์ธ ๋Œ€๊ธฐ\"})\n", "rec = pd.DataFrame(recs)\n", "print(f\"\\n[๊ถŒ๊ณ  ๋Œ€์‘ ์ˆœ์œ„] ๊ฒฝ๋ณด {len(rec)}๊ฑด ์ค‘ ์ƒ์œ„ 10๊ฑด (์ž๋™ ์‹คํ–‰ ์—†์Œ, ์ง€ํœ˜๊ด€ ์Šน์ธ ํ•„์š”)\")\n", "print(rec.head(10).to_string(index=False))\n", "print(\"์ž”์—ฌ ์ˆ˜๋Ÿ‰(๊ฐ€์ƒ):\", remain, f\"| ์ž์› ๋ถ€์กฑ์œผ๋กœ ์ƒ๊ธ‰ ๋ณด๊ณ  ๊ถŒ๊ณ  {int((rec['๊ถŒ๊ณ ์ž์›'] == '์ž์›๋ถ€์กฑ-์ƒ๊ธ‰๋ณด๊ณ ').sum())}๊ฑด\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 05 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[์˜ค๊ฒฝ๋ณด์œจ] Baseline 13.9% -> ์‹ ๋ขฐ๋„ ์—”์ง„ 0.0%, ๊ฐ์ถ•๋ฅ  100% (๋ชฉํ‘œ 40% ์ด์ƒ) -> ๋‹ฌ์„ฑ\n", "[๋“ฑ๊ธ‰ ๊ธฐ์ค€] High: ์ง€์ˆ˜ 0.70 ์ด์ƒ(๋ณต์ˆ˜ ์„ผ์„œยท์•ˆ์ • ์‹ ํ˜ธ), Med: 0.45~0.70(์ถ”๊ฐ€ ํ™•์ธ ํ•„์š”), Low: 0.45 ๋ฏธ๋งŒ(๊ฒฝ๋ณด ๋ณด๋ฅ˜)\n", "grade High Low Med\n", "type \n", "bird 0 8 1\n", "civil 0 0 10\n", "decoy 1 7 2\n", "fast_type 15 0 0\n", "spoof 0 6 0\n", "uav_type 35 0 1\n", "\n", "์‹ ๋ขฐ๋„ ๋“ฑ๊ธ‰ ์ผ๊ด€์„ฑ 99.4% (๋ชฉํ‘œ 90% ์ด์ƒ) -> ๋‹ฌ์„ฑ\n", "\n", "[๊ถŒ๊ณ  ๋Œ€์‘ ์ˆœ์œ„] ๊ฒฝ๋ณด 31๊ฑด ์ค‘ ์ƒ์œ„ 10๊ฑด (์ž๋™ ์‹คํ–‰ ์—†์Œ, ์ง€ํœ˜๊ด€ ์Šน์ธ ํ•„์š”)\n", " ๊ถŒ๊ณ ์ˆœ์œ„ ํ‘œ์  ์ง€ํ–ฅ์ž์‚ฐ ๋„๋‹ฌ์˜ˆ์ƒ(s) ์‹ ๋ขฐ๋„ ๋“ฑ๊ธ‰ ๊ถŒ๊ณ ์ž์› ์ƒํƒœ\n", " 1 T08023 A1 163 0.96 High R2 ์Šน์ธ ๋Œ€๊ธฐ\n", " 2 T08044 A2 99 0.90 High R1 ์Šน์ธ ๋Œ€๊ธฐ\n", " 3 T08034 A1 170 0.94 High R2 ์Šน์ธ ๋Œ€๊ธฐ\n", " 4 T08015 A3 69 0.94 High R2 ์Šน์ธ ๋Œ€๊ธฐ\n", " 5 T08036 A1 192 0.96 High R1 ์Šน์ธ ๋Œ€๊ธฐ\n", " 6 T08027 A2 107 0.89 High R3 ์Šน์ธ ๋Œ€๊ธฐ\n", " 7 T08058 A2 143 0.97 High R1 ์Šน์ธ ๋Œ€๊ธฐ\n", " 8 T08006 A2 145 0.87 High R3 ์Šน์ธ ๋Œ€๊ธฐ\n", " 9 T08068 A3 118 0.98 High R3 ์Šน์ธ ๋Œ€๊ธฐ\n", " 10 T08003 A2 153 0.90 High R1 ์Šน์ธ ๋Œ€๊ธฐ\n", "์ž”์—ฌ ์ˆ˜๋Ÿ‰(๊ฐ€์ƒ): {'R1': 0, 'R2': 0, 'R3': 0} | ์ž์› ๋ถ€์กฑ์œผ๋กœ ์ƒ๊ธ‰ ๋ณด๊ณ  ๊ถŒ๊ณ  13๊ฑด\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 06. ๋ฐฉ๊ณต ์ง€ํœ˜๊ด€ ๊ฒฐ์‹ฌ ์ง€์›์„ ์œ„ํ•œ ์‹ค์‹œ๊ฐ„ ํฌํ™”๊ฒฝ๋ณด ์šฐ์„ ์ˆœ์œ„ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "ํฌํ™” ๊ณต๊ฒฉ ์ƒํ™ฉ์—์„œ ๊ฐ€์žฅ ๋จผ์ € ์š”๊ฒฉํ•ด์•ผ ํ•  ํ‘œ์  ๋ชฉ๋ก๊ณผ ๋ฐฉ๊ณต๋ง ๋Œ€์‘ ํ˜„ํ™ฉ์„ ์ง๊ด€์ ์œผ๋กœ ํ‘œ์ถœํ•˜๋Š” ํฌํ™”๊ฒฝ๋ณด ์šฐ์„ ์ˆœ์œ„ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **ํฌํ™” ์ƒํ™ฉ์—์„œ ๋จผ์ € ํ™•์ธํ•  ํ‘œ์ ๊ณผ ๋Œ€์‘ ์ž์› ํ˜„ํ™ฉ์„ ํ•œ ํ™”๋ฉด์— ๋ณด์—ฌ ์ฃผ๋Š” ์ง€ํœ˜๊ด€์šฉ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๋งŒ๋“ ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ฐ€์ƒ ๋ฐฉ์–ด์ง€์—ญ ์ง€๋„์— ํ•ญ์ ์„ ์šฐ์„ ์ˆœ์œ„ ์ƒ‰(P1 180์ดˆ ์ด๋‚ด, P2 600์ดˆ ์ด๋‚ด, ์Šคํ‘ธํ•‘ ์˜์‹ฌ)์œผ๋กœ ํ‘œ์‹œํ•˜๊ณ , ๋„๋‹ฌ์‹œ๊ฐ„ ๋ถ„ํฌ์™€ ๊ฐ€์ƒ ๋Œ€์‘ ์ž์› ์ž”์—ฌ ํ˜„ํ™ฉ์„ ๊ณ ์ • ๋ฐฐ์น˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/ui/ops04_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": [ "\"\"\"๋ฐฉ๊ณต ์ง€ํœ˜๊ด€ ๊ฒฐ์‹ฌ ์ง€์›์šฉ ํฌํ™”๊ฒฝ๋ณด ์šฐ์„ ์ˆœ์œ„ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ๊ฐ€์ƒ ๋ฐฉ์–ด์ง€์—ญ ์ง€๋„์— ํ‘œ์  ํ•ญ์ ๊ณผ ์šฐ์„ ์ˆœ์œ„๋ฅผ, ์˜†์— ๊ถŒ๊ณ  ์šฐ์„ ์ˆœ์œ„ ๋ชฉ๋กยท๋„๋‹ฌ์‹œ๊ฐ„ ๋ถ„ํฌยท๊ฐ€์ƒ ๋Œ€์‘ ์ž์› ํ˜„ํ™ฉ์„ ํ‘œ์‹œํ•˜๊ณ  1์ดˆ ์ฃผ๊ธฐ ๊ฐฑ์‹  ์ฒ˜๋ฆฌ์‹œ๊ฐ„๊ณผ ์กฐํšŒ ์‘๋‹ต์‹œ๊ฐ„์„ ์ธก์ •ํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋ฐฉ์–ด์ง€์—ญยท๊ฐ€์ƒ ๋น„ํ–‰์ฒด ์ƒ์„ฑ๊ธฐ ----------------\n", "# ์ขŒํ‘œ๋Š” ๊ฐ€์ƒ ์ง€ํœ˜์†Œ๋ฅผ ์›์ ์œผ๋กœ ํ•œ km ๋‹จ์œ„ ํ‰๋ฉด์ด๋‹ค. ๋น„ํ–‰์ฒด ์œ ํ˜•์€ ์ถ”์ƒ ๋ฒ”์ฃผ์ด๋ฉฐ\n", "# ์†๋„ยท๊ณ ๋„ยท๋ฐ˜์‚ฌ๊ฐ•๋„๋Š” ํ•™์Šต์šฉ ์ž„์˜ ๋ฒ”์œ„๋กœ, ์‹ค์ œ ๋ฌด๊ธฐ ์ œ์›ยท์‚ฌ๊ฑฐ๋ฆฌยทํƒ„๋„์™€ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n", " [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n", " [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n", " [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n", "TYPES = { # ์œ ํ˜•: (์œ„ํ˜‘ ์—ฌ๋ถ€, ์†๋„ m/s ๋ฒ”์œ„, ๊ณ ๋„ m ๋ฒ”์œ„, ์ถœํ˜„ ๋น„์œจ)\n", " \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ €์† ๋ฌด์ธ๊ธฐํ˜•(๊ฐ€์ƒ)\n", " \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์† ์ˆœํ•ญํ˜•(๊ฐ€์ƒ)\n", " \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋งŒ์ฒด(๊ฐ€์ƒ)\n", " \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํ•ญ๊ธฐ(๊ฐ€์ƒ)\n", " \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ˜ ๊ตฐ์ง‘\n", "}\n", "TTI_URGENT = 600 # ๊ธด๊ธ‰ ํŒ์ • ๊ธฐ์ค€: ๊ฐ€์ƒ ์ž์‚ฐ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„ 600์ดˆ ์ด๋‚ด\n", "CPA_KM = 1.5 # ์ž์‚ฐ ์ƒ๊ณต ํ†ต๊ณผ ํŒ์ • ๋ฐ˜๊ฒฝ(๊ฐ€์ƒ)\n", "\n", "\n", "def make_raid(seed, n=80, spoof=0):\n", " \"\"\"๊ฐ€์ƒ ํฌํ™” ์ƒํ™ฉ 1ํšŒ๋ถ„(๋น„ํ–‰์ฒด n๊ฐœ + ์Šคํ‘ธํ•‘ ๊ฐ€์งœ ํ•ญ์  spoof๊ฐœ)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(TYPES)\n", " rows = []\n", " for i in range(n + spoof):\n", " typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n", " threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n", " brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n", " x, y = dist * np.sin(brg), dist * np.cos(brg)\n", " spd = rng.uniform(*sp); alt = rng.uniform(*al)\n", " aim = int(rng.integers(0, len(ASSETS)))\n", " if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ€์ƒ ์ž์‚ฐ ๋ฐฉํ–ฅ(์ž‘์€ ๋ฐฉ์œ„ ์˜ค์ฐจ)\n", " ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n", " hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n", " else:\n", " hd = rng.uniform(0, 2 * np.pi); aim = -1\n", " vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n", " sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n", " \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n", " \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n", " jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n", " true_tti = np.inf\n", " if threat:\n", " d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n", " true_tti = d_aim * 1000 / spd\n", " rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n", " \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n", " \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n", " \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n", " \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n", " df = pd.DataFrame(rows)\n", " df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n", " for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n", " return df\n", "\n", "\n", "def geometry(df):\n", " \"\"\"๊ฐ ํ•ญ์ ๊ณผ ๊ฐ€์ƒ ์ž์‚ฐ ์‚ฌ์ด ์ตœ๊ทผ์ ‘์ (CPA) ๊ฑฐ๋ฆฌ์™€ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„(TTI, s)์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ๋ฐฉ๊ณต ์ง€ํœ˜๊ด€ ๊ฒฐ์‹ฌ ์ง€์›์„ ์œ„ํ•œ ์‹ค์‹œ๊ฐ„ ํฌํ™”๊ฒฝ๋ณด ์šฐ์„ ์ˆœ์œ„ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "FEATS = [\"log_spd\", \"alt_km\", \"rcs\", \"rcs_var\", \"n_sensors\", \"civ_iff\", \"jump_ratio\"]\n", "\n", "\n", "def features(df):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ๋ฐฉ๊ณต ์ง€ํœ˜๊ด€ ๊ฒฐ์‹ฌ ์ง€์›์„ ์œ„ํ•œ ์‹ค์‹œ๊ฐ„ ํฌํ™”๊ฒฝ๋ณด ์šฐ์„ ์ˆœ์œ„ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def train_classifier(seeds=(101, 102, 103, 104)):\n", " \"\"\"ํ•™์Šต์šฉ ๊ฐ€์ƒ ์ƒํ™ฉ(์‹œํ—˜ ์ƒํ™ฉ๊ณผ ๋‹ค๋ฅธ ์‹œ๋“œ)์œผ๋กœ '์œ„ํ˜‘ ์œ ์‚ฌ๋„' ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค.\"\"\"\n", " tr = pd.concat([make_raid(s, 120, spoof=10) for s in seeds], ignore_index=True)\n", " clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500, C=1.0))\n", " clf.fit(features(tr)[FEATS], tr[\"threat\"].astype(int))\n", " return clf\n", "\n", "\n", "def baseline_rank(df):\n", " \"\"\"Baseline: ๊ฑฐ๋ฆฌยท๋„๋‹ฌ์‹œ๊ฐ„๋งŒ์œผ๋กœ 1์ฐจ ์œ„ํ˜‘ ์ ์ˆ˜๋ฅผ ๋งค๊ธด๋‹ค(์ž์‚ฐ ๊ฐ€์น˜ยท์œ ํ˜• ํŒ๋ณ„ ์—†์Œ).\"\"\"\n", " _, tti = geometry(df)\n", " t_min = tti.min(axis=1)\n", " out = df[[\"id\"]].copy()\n", " out[\"tti_s\"] = t_min\n", " out[\"score\"] = np.exp(-t_min / 300)\n", " out[\"urgent_pred\"] = t_min <= TTI_URGENT\n", " return out.sort_values(\"score\", ascending=False)\n", "\n", "\n", "def advanced_rank(df, clf, w=None):\n", " \"\"\"๊ณ ๋„ํ™”: ์ž์‚ฐ ๊ฐ€์น˜ x ๋„๋‹ฌ์‹œ๊ฐ„ x ์œ„ํ˜‘ ์œ ์‚ฌ๋„, ์šด๋™ํ•™ ๋ถˆ์ผ์น˜(์Šคํ‘ธํ•‘ ์˜์‹ฌ) ํ•ญ์ ์€ ๋ณ„๋„ ํ‘œ์‹œํ•œ๋‹ค.\"\"\"\n", " w = w or {\"tau\": 300, \"p_min\": 0.5, \"value_pow\": 1.0, \"jump_max\": 2.0}\n", " _, tti = geometry(df)\n", " val = ASSETS[\"value\"].to_numpy()[None] ** w[\"value_pow\"]\n", " asset_score = val * np.exp(-tti / w[\"tau\"])\n", " best = asset_score.argmax(axis=1)\n", " p = clf.predict_proba(features(df)[FEATS])[:, 1]\n", " spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > w[\"jump_max\"]\n", " out = df[[\"id\"]].copy()\n", " out[\"asset\"] = ASSETS[\"asset\"].to_numpy()[best]\n", " out[\"tti_s\"] = tti[np.arange(len(df)), best]\n", " out[\"p_threat\"] = p\n", " out[\"spoof_flag\"] = spoof\n", " out[\"score\"] = np.where(spoof, 0, p * asset_score.max(axis=1))\n", " out[\"urgent_pred\"] = (p >= w[\"p_min\"]) & (out[\"tti_s\"] <= TTI_URGENT) & ~spoof\n", " return out.sort_values(\"score\", ascending=False)\n", "\n", "clf = train_classifier()\n", "raid = make_raid(9, 100, spoof=6)\n", "RES = {\"R1\": [6, 6], \"R2\": [4, 4], \"R3\": [8, 8]} # [์ž”์—ฌ, ์ตœ์ดˆ] ๊ฐ€์ƒ ๋Œ€์‘ ์ž์›\n", "\n", "\n", "def refresh(df):\n", " r = advanced_rank(df, clf).merge(df[[\"id\", \"x\", \"y\", \"vx\", \"vy\"]], on=\"id\")\n", " r[\"prio\"] = np.where(r[\"urgent_pred\"] & (r[\"tti_s\"] <= 180), \"P1\", np.where(r[\"urgent_pred\"], \"P2\", np.where(r[\"spoof_flag\"], \"SPOOF\", \"P3\")))\n", " return r\n", "\n", "\n", "# 1) 1์ดˆ ์ฃผ๊ธฐ ๊ฐฑ์‹  10ํšŒ ๋ชจ์˜: ๊ฐฑ์‹  ์ฒ˜๋ฆฌ์‹œ๊ฐ„๊ณผ ์กฐํšŒ(ํ•„ํ„ฐยท์ •๋ ฌ) ์‘๋‹ต์‹œ๊ฐ„ ์ธก์ •\n", "upd, qry = [], []\n", "for k in range(10):\n", " t0 = time.perf_counter(); board = refresh(raid); upd.append((time.perf_counter() - t0) * 1000)\n", " t0 = time.perf_counter(); view = board[board[\"prio\"].isin([\"P1\", \"P2\"])].sort_values(\"tti_s\").head(12); qry.append((time.perf_counter() - t0) * 1000)\n", " raid = raid.assign(x=raid[\"x\"] + raid[\"vx\"], y=raid[\"y\"] + raid[\"vy\"])\n", " raid = raid[np.hypot(raid[\"x\"], raid[\"y\"]) > 0.5].reset_index(drop=True) # ๋„๋‹ฌ ์ฒ˜๋ฆฌ๋œ ํ•ญ์  ์ œ๊ฑฐ(๊ฐ€์ƒ)\n", "print(f\"๊ฐฑ์‹  ์ฒ˜๋ฆฌ์‹œ๊ฐ„ ํ‰๊ท  {np.mean(upd):.1f} ms / ์ตœ๋Œ€ {np.max(upd):.1f} ms (๋ชฉํ‘œ: ๊ฐฑ์‹  ์ฃผ๊ธฐ 1์ดˆ ์ด๋‚ด) -> {'๋‹ฌ์„ฑ' if max(upd) < 1000 else '๋ฏธ๋‹ฌ'}\")\n", "print(f\"์กฐํšŒ ์‘๋‹ต์‹œ๊ฐ„ ์ตœ๋Œ€ {np.max(qry):.2f} ms (๋ชฉํ‘œ 100 ms ์ด๋‚ด) -> {'๋‹ฌ์„ฑ' if max(qry) < 100 else '๋ฏธ๋‹ฌ'}\")\n", "cnt = board[\"prio\"].value_counts()\n", "print(f\"ํ˜„์žฌ ํ‘œ์‹œ: P1(180์ดˆ ์ด๋‚ด) {cnt.get('P1', 0)}๊ฑด, P2(600์ดˆ ์ด๋‚ด) {cnt.get('P2', 0)}๊ฑด, ์Šคํ‘ธํ•‘ ์˜์‹ฌ {cnt.get('SPOOF', 0)}๊ฑด, ๊ธฐํƒ€ {cnt.get('P3', 0)}๊ฑด\")\n", "print(\"\\n[๊ถŒ๊ณ  ์šฐ์„ ์ˆœ์œ„ ๋ชฉ๋ก] (P1ยทP2, ๋„๋‹ฌ ์˜ˆ์ƒ ์ˆœ)\")\n", "print(view[[\"id\", \"prio\", \"asset\", \"tti_s\", \"p_threat\"]].round({\"tti_s\": 0, \"p_threat\": 2}).rename(\n", " columns={\"id\": \"ํ‘œ์ \", \"prio\": \"๋“ฑ๊ธ‰\", \"asset\": \"์ง€ํ–ฅ์ž์‚ฐ\", \"tti_s\": \"๋„๋‹ฌ์˜ˆ์ƒ(s)\", \"p_threat\": \"์œ„ํ˜‘์œ ์‚ฌ๋„\"}).to_string(index=False))\n", "\n", "# 2) ๋Œ€์‹œ๋ณด๋“œ(์–ด๋‘์šด ๋ฐฐ๊ฒฝ, P1์€ ๋นจ๊ฐ• ๊ตต๊ฒŒ, ์ •๋ณด๋Š” 4๊ฐœ ์˜์—ญ์œผ๋กœ ๊ณ ์ • ๋ฐฐ์น˜ํ•ด ์ธ์ง€ ๋ถ€ํ•˜๋ฅผ ์ค„์ธ๋‹ค)\n", "plt.style.use(\"dark_background\")\n", "fig = plt.figure(figsize=(14, 7))\n", "gs = fig.add_gridspec(2, 3)\n", "a = fig.add_subplot(gs[:, :2])\n", "col = {\"P1\": \"red\", \"P2\": \"orange\", \"P3\": \"gray\", \"SPOOF\": \"magenta\"}\n", "for p, g in board.groupby(\"prio\"):\n", " a.quiver(g[\"x\"], g[\"y\"], g[\"vx\"], g[\"vy\"], color=col[p], scale=3, width=0.003, label=f\"{p} ({len(g)})\")\n", "a.scatter(ASSETS[\"x\"], ASSETS[\"y\"], marker=\"^\", s=150, c=\"cyan\")\n", "for r in ASSETS.itertuples():\n", " a.text(r.x + 0.8, r.y + 0.8, f\"{r.asset} v={r.value}\", color=\"cyan\", fontsize=9)\n", "for r in board[board[\"prio\"] == \"P1\"].itertuples():\n", " a.text(r.x, r.y, r.id[-3:], color=\"red\", fontsize=8, fontweight=\"bold\")\n", "a.set_xlim(-60, 60); a.set_ylim(-60, 60); a.set_aspect(\"equal\"); a.grid(alpha=0.2); a.legend(loc=\"lower left\")\n", "a.set_title(\"Saturation alert picture (virtual area)\"); a.set_xlabel(\"East (km)\"); a.set_ylabel(\"North (km)\")\n", "b = fig.add_subplot(gs[0, 2])\n", "urg = board[board[\"urgent_pred\"]]\n", "b.hist(urg[\"tti_s\"], bins=np.arange(0, 660, 60), color=\"orange\"); b.axvline(180, c=\"red\", ls=\"--\")\n", "b.set_title(\"Urgent tracks by TTI (s)\")\n", "c = fig.add_subplot(gs[1, 2])\n", "c.barh(list(RES), [v[1] for v in RES.values()], color=\"dimgray\", label=\"initial\")\n", "c.barh(list(RES), [v[0] for v in RES.values()], color=\"lime\", label=\"remaining\")\n", "c.set_title(\"Virtual response resources\"); c.legend(fontsize=8)\n", "plt.suptitle(\"OPS04 dashboard - recommendations only, commander approval required (training data)\")\n", "plt.tight_layout(); plt.show()\n", "print(\"\\n๋Œ€์‹œ๋ณด๋“œ์˜ ์ˆœ์œ„๋Š” ๊ถŒ๊ณ  ์ •๋ณด์ด๋ฉฐ, ๋Œ€์‘ ์—ฌ๋ถ€๋Š” ๋ฏธ์…˜ 7์˜ ์ง€ํœ˜๊ด€ ์Šน์ธ ์ ˆ์ฐจ์™€ ๊ต์ „์ˆ˜์น™ยท์ง€ํœ˜๊ณ„ํ†ต์— ๋”ฐ๋ฅธ๋‹ค.\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 06 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "๊ฐฑ์‹  ์ฒ˜๋ฆฌ์‹œ๊ฐ„ ํ‰๊ท  9.4 ms / ์ตœ๋Œ€ 10.9 ms (๋ชฉํ‘œ: ๊ฐฑ์‹  ์ฃผ๊ธฐ 1์ดˆ ์ด๋‚ด) -> ๋‹ฌ์„ฑ\n", "์กฐํšŒ ์‘๋‹ต์‹œ๊ฐ„ ์ตœ๋Œ€ 1.64 ms (๋ชฉํ‘œ 100 ms ์ด๋‚ด) -> ๋‹ฌ์„ฑ\n", "ํ˜„์žฌ ํ‘œ์‹œ: P1(180์ดˆ ์ด๋‚ด) 13๊ฑด, P2(600์ดˆ ์ด๋‚ด) 20๊ฑด, ์Šคํ‘ธํ•‘ ์˜์‹ฌ 6๊ฑด, ๊ธฐํƒ€ 67๊ฑด\n", "\n", "[๊ถŒ๊ณ  ์šฐ์„ ์ˆœ์œ„ ๋ชฉ๋ก] (P1ยทP2, ๋„๋‹ฌ ์˜ˆ์ƒ ์ˆœ)\n", " ํ‘œ์  ๋“ฑ๊ธ‰ ์ง€ํ–ฅ์ž์‚ฐ ๋„๋‹ฌ์˜ˆ์ƒ(s) ์œ„ํ˜‘์œ ์‚ฌ๋„\n", "T09027 P1 A3 0.0 1.00\n", "T09017 P1 A4 70.0 1.00\n", "T09072 P1 A1 76.0 1.00\n", "T09047 P1 A2 78.0 0.99\n", "T09038 P1 A3 85.0 0.96\n", "T09069 P1 A4 97.0 1.00\n", "T09049 P1 A3 136.0 0.99\n", "T09023 P1 A4 147.0 1.00\n", "T09092 P1 A3 149.0 0.97\n", "T09006 P1 A1 155.0 0.93\n", "T09089 P1 A2 160.0 1.00\n", "T09026 P1 A2 167.0 1.00\n", "\n", "๋Œ€์‹œ๋ณด๋“œ์˜ ์ˆœ์œ„๋Š” ๊ถŒ๊ณ  ์ •๋ณด์ด๋ฉฐ, ๋Œ€์‘ ์—ฌ๋ถ€๋Š” ๋ฏธ์…˜ 7์˜ ์ง€ํœ˜๊ด€ ์Šน์ธ ์ ˆ์ฐจ์™€ ๊ต์ „์ˆ˜์น™ยท์ง€ํœ˜๊ณ„ํ†ต์— ๋”ฐ๋ฅธ๋‹ค.\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/ops04_approval_workflow.py ๋ฐ docs/ops04_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": [ "# [TODO : ํ•™์ƒ ์‹ค์Šต 7] ์ž๋™ ํ• ๋‹น๋œ ๊ต์ „ ์ˆœ์œ„ ์žฌ์กฐ์ • ๋ฐ ๊ธด๊ธ‰ ์ˆ˜๋™ ์ „ํ™˜์„ ์œ„ํ•œ HITL ์Šน์ธ ์ ˆ์ฐจ ์„ค๊ณ„\n", "# ๋ฏธ์…˜ ์ง€์นจ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€์„ ํ™•์ธํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ ๋ฐ ํŠœ๋‹ํ•˜์„ธ์š”.\n", "\"\"\"์ž๋™ ๊ณ„์‚ฐ๋œ ๋Œ€์‘ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋ฐฉ๊ณต ์ง€ํœ˜๊ด€์ด ์Šน์ธยท๋ณด๋ฅ˜ยท๋ฐ˜๋ คํ•˜๊ฑฐ๋‚˜ ์ˆ˜๋™์œผ๋กœ ์ˆœ์œ„๋ฅผ ๋ฐ”๊พธ๊ณ , ๊ธด๊ธ‰ ์‹œ ์ˆ˜๋™ ํ†ต์ œ ๋ชจ๋“œ๋กœ ์ „ํ™˜ํ•˜๋Š” ์ธ๊ฐ„ ๊ฐœ์ž…(HITL) ์Šน์ธ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์ž๋™ ์Šน์ธ์€ ์—†์œผ๋ฉฐ, ๋ชจ๋“  ์‹œ๋„(๊ฑฐ๋ถ€๋œ ์‹œ๋„ ํฌํ•จ)๋Š” ์—ญํ• ยท๊ทผ๊ฑฐ์™€ ํ•จ๊ป˜ ํ•ด์‹œ ์ฒด์ธ ๊ฐ์‚ฌ ๋กœ๊ทธ์— ๊ธฐ๋ก๋œ๋‹ค.\"\"\"\n", "import hashlib\n", "import json\n", "import numpy as np\n", "import pandas as pd\n", "\n", "ROLES = {\"CDR-01\": \"commander\", \"OPR-07\": \"operator\", \"OPR-09\": \"operator\"} # ๊ฐ€์ƒ ์šด์šฉ์ž\n", "PERM = {\"APPROVE\": {\"commander\"}, \"REORDER\": {\"commander\"}, \"MODE\": {\"commander\"},\n", " \"HOLD\": {\"commander\", \"operator\"}, \"REJECT\": {\"commander\", \"operator\"}}\n", "HOLD_LIMIT_S = 30\n", "\n", "\n", "class FakeClock:\n", " t = 0.0\n", "\n", " def __call__(self):\n", " return self.t\n", "\n", "\n", "class HitlBoard:\n", " def __init__(self, clock):\n", " self.clock, self.items, self.log, self.mode = clock, {}, [], \"RECOMMEND\"\n", " self.attempts = 0\n", "\n", " def _record(self, body):\n", " prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n", " body = {**body, \"ts\": self.clock(), \"mode\": self.mode}\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, tid, ai_rank, tti, grade):\n", " self.items[tid] = {\"id\": tid, \"ai_rank\": ai_rank, \"rank\": ai_rank, \"tti\": tti, \"grade\": grade, \"status\": \"PENDING\",\n", " \"t_in\": self.clock(), \"t_out\": None, \"hold_at\": None, \"escalated\": False}\n", " self._record({\"event\": \"SUBMIT\", \"id\": tid, \"ai_rank\": ai_rank, \"by\": \"AI\"})\n", "\n", " def act(self, op, action, tid=None, reason=\"\", new_rank=None):\n", " \"\"\"๋ชจ๋“  ์‹œ๋„๋Š” ์„ฑ๊ณตยท๊ฑฐ๋ถ€์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ๊ฐ์‚ฌ ๋กœ๊ทธ์— ๋‚จ๊ธด๋‹ค.\"\"\"\n", " self.attempts += 1\n", " err = None\n", " role = ROLES.get(op)\n", " it = self.items.get(tid)\n", " if role is None or role not in PERM.get(action, set()):\n", " err = f\"๊ถŒํ•œ ์—†์Œ({op}:{action})\"\n", " elif len(reason.strip()) < 6:\n", " err = \"ํŒ๋‹จ ๊ทผ๊ฑฐ 6์ž ์ด์ƒ ํ•„์š”\"\n", " elif action != \"MODE\" and (it is None or it[\"status\"] in (\"APPROVED\", \"REJECTED\")):\n", " err = \"๋Œ€์ƒ ์—†์Œ ๋˜๋Š” ์ด๋ฏธ ์ข…๊ฒฐ\"\n", " elif action == \"APPROVE\" and it[\"grade\"] == \"Low\":\n", " err = \"Low ๋“ฑ๊ธ‰์€ ์Šน์ธ ์ „ ์ถ”๊ฐ€ ํ™•์ธ(HOLD) ํ•„์š”\"\n", " if err:\n", " self._record({\"event\": \"REFUSED\", \"action\": action, \"id\": tid, \"operator\": op, \"reason\": reason, \"error\": err})\n", " return err\n", " if action == \"MODE\":\n", " self.mode = \"MANUAL\" if self.mode == \"RECOMMEND\" else \"RECOMMEND\"\n", " elif action == \"REORDER\":\n", " it[\"rank\"] = new_rank\n", " else:\n", " it[\"status\"] = {\"APPROVE\": \"APPROVED\", \"HOLD\": \"HOLD\", \"REJECT\": \"REJECTED\"}[action]\n", " it[\"hold_at\"] = self.clock() if action == \"HOLD\" else None\n", " if action != \"HOLD\":\n", " it[\"t_out\"] = self.clock()\n", " self._record({\"event\": action, \"id\": tid, \"operator\": op, \"role\": role, \"reason\": reason, \"new_rank\": new_rank})\n", " return \"OK\"\n", "\n", " def tick(self):\n", " \"\"\"๋ณด๋ฅ˜ ๊ธฐํ•œ ์ดˆ๊ณผ ์‹œ ์ž๋™ ์Šน์ธํ•˜์ง€ ์•Š๊ณ  ์ง€ํœ˜๊ด€์—๊ฒŒ ์ƒ์‹ (ESCALATE)๋งŒ ํ•œ๋‹ค.\"\"\"\n", " for it in self.items.values():\n", " if it[\"status\"] == \"HOLD\" and not it[\"escalated\"] and self.clock() - it[\"hold_at\"] > HOLD_LIMIT_S:\n", " it[\"escalated\"] = True\n", " self._record({\"event\": \"ESCALATE\", \"id\": it[\"id\"], \"by\": \"SYSTEM\"})\n", "\n", " def queue(self):\n", " todo = [i for i in self.items.values() if i[\"status\"] in (\"PENDING\", \"HOLD\")]\n", " return sorted(todo, key=lambda i: (not i[\"escalated\"], i[\"rank\"]))\n", "\n", "\n", "clk = FakeClock()\n", "hb = HitlBoard(clk)\n", "rng = np.random.default_rng(5)\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ฏธ์…˜ 5 ๊ถŒ๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ •ํ•œ 12๊ฑด\n", "for k in range(12):\n", " hb.submit(f\"T09{k:03d}\", k + 1, int(rng.integers(60, 600)), [\"High\", \"High\", \"Med\", \"Low\"][k % 4])\n", "scenario = [ # (๊ฒฝ๊ณผ s, ์šด์šฉ์ž, ์กฐ์น˜, ํ‘œ์ , ๊ทผ๊ฑฐ, ์ƒˆ ์ˆœ์œ„)\n", " (2, \"OPR-07\", \"APPROVE\", \"T09000\", \"๋„๋‹ฌ ์ž„๋ฐ• ํ‘œ์ \", None), # ๊ถŒํ•œ ์—†์Œ -> ๊ฑฐ๋ถ€\n", " (3, \"CDR-01\", \"APPROVE\", \"T09000\", \"๋ณต์ˆ˜ ์„ผ์„œ ํ™•์ธ, ์ง€ํœ˜์†Œ ์ง€ํ–ฅ\", None),\n", " (5, \"CDR-01\", \"REORDER\", \"T09005\", \"ํƒ„์•ฝ๊ณ  ์ง€ํ–ฅ ํ™•์ธ, ์šฐ์„  ๋Œ€์‘ ํ•„์š”\", 2),\n", " (6, \"OPR-09\", \"HOLD\", \"T09003\", \"๋‹จ์ผ ์„ผ์„œ, ์œก์•ˆ ํ™•์ธ ์š”์ฒญ\", None),\n", " (7, \"CDR-01\", \"APPROVE\", \"T09003\", \"๊ธ‰ํ•จ\", None), # ๊ทผ๊ฑฐ ๋ถ€์กฑ -> ๊ฑฐ๋ถ€\n", " (8, \"OPR-07\", \"REJECT\", \"T09007\", \"๋ฏผ๊ฐ„ ์‘๋‹ต ์‹ ํ˜ธ ํ™•์ธ, ์˜ค๊ฒฝ๋ณด\", None),\n", " (10, \"CDR-01\", \"MODE\", None, \"ํ†ต์‹  ์ง€์—ฐ์œผ๋กœ ๊ธด๊ธ‰ ์ˆ˜๋™ ํ†ต์ œ ์ „ํ™˜\", None),\n", " (12, \"CDR-01\", \"APPROVE\", \"T09001\", \"์ˆ˜๋™ ํ†ต์ œ ์ค‘ ์ง€ํœ˜๊ด€ ์ง์ ‘ ํŒ๋‹จ\", None),\n", " (15, \"CDR-01\", \"APPROVE\", \"T09011\", \"Low ๋“ฑ๊ธ‰์ด๋‚˜ ๋„๋‹ฌ ์ž„๋ฐ• ํŒ๋‹จ\", None), # Low ์Šน์ธ -> ๊ฑฐ๋ถ€\n", " (20, \"CDR-01\", \"APPROVE\", \"T09001\", \"์ค‘๋ณต ์Šน์ธ ์‹œ๋„ ํ™•์ธ์šฉ\", None), # ์ด๋ฏธ ์ข…๊ฒฐ -> ๊ฑฐ๋ถ€\n", " (25, \"CDR-01\", \"MODE\", None, \"ํ†ต์‹  ๋ณต๊ตฌ, ๊ถŒ๊ณ  ๋ชจ๋“œ ๋ณต๊ท€\", None),\n", "]\n", "for t, op, a, tid, rsn, nr in scenario:\n", " clk.t = t; hb.tick()\n", " res = hb.act(op, a, tid, rsn, nr)\n", " print(f\"[t={t:>3}s] {op} {a:8s} {tid or '-':7s} -> {res}\")\n", "clk.t = 45; hb.tick()\n", "\n", "print(\"\\n[๊ฒ€ํ†  ๋Œ€๊ธฐ์—ด] (์ƒ์‹  ์šฐ์„ , ๊ทธ๋‹ค์Œ ์ˆœ์œ„)\")\n", "print(pd.DataFrame(hb.queue())[[\"id\", \"ai_rank\", \"rank\", \"grade\", \"status\", \"escalated\"]].head(6).to_string(index=False))\n", "auto = sum(1 for r in hb.log if r[\"event\"] == \"APPROVE\" and r.get(\"operator\") in (None, \"AI\", \"SYSTEM\"))\n", "logged = sum(1 for r in hb.log if r[\"event\"] not in (\"SUBMIT\", \"ESCALATE\"))\n", "ok, _ = hb.verify()\n", "done = [i for i in hb.items.values() if i[\"t_out\"] is not None]\n", "waits = [i[\"t_out\"] - i[\"t_in\"] for i in done]\n", "print(f\"\\n์กฐ์น˜ ์‹œ๋„ {hb.attempts}๊ฑด ์ค‘ ๋กœ๊ทธ ๊ธฐ๋ก {logged}๊ฑด -> ๋กœ๊น… ์„ฑ๊ณต๋ฅ  {logged / hb.attempts:.0%} (๋ชฉํ‘œ 100%) -> {'๋‹ฌ์„ฑ' if logged == hb.attempts else '๋ฏธ๋‹ฌ'}\")\n", "print(f\"์ข…๊ฒฐ {len(done)}๊ฑด ๋Œ€๊ธฐ์‹œ๊ฐ„ ํ‰๊ท  {np.mean(waits):.1f}s / ์ตœ๋Œ€ {max(waits):.0f}s, ๋ฏธ๊ฒฐ {len(hb.queue())}๊ฑด, ์ž๋™ ์Šน์ธ {auto}๊ฑด\")\n", "print(f\"๊ฐ์‚ฌ ๋กœ๊ทธ ๋ฌด๊ฒฐ์„ฑ: {'์ •์ƒ' if ok else 'ํ›ผ์†'} ({len(hb.log)}๊ฑด)\")\n", "hb.log[3][\"reason\"] = \"์‚ฌํ›„ ์ˆ˜์ •\"\n", "ok2, n2 = hb.verify()\n", "print(f\"์œ„ยท๋ณ€์กฐ ์‹œํ—˜(4๋ฒˆ์งธ ๊ธฐ๋ก ๊ทผ๊ฑฐ ์ˆ˜์ •) -> {'๊ฒ€์ถœ(' + str(n2) + '๋ฒˆ ๊ธฐ๋ก)' if not ok2 else '๋ฏธ๊ฒ€์ถœ'}\")\n", "print(\"\\n[์ง€ํœ˜ ์ฑ…์ž„ ๋ถ„๊ณ„์„ ] AI: ์ˆœ์œ„ '๊ถŒ๊ณ '๋งŒ / ์šด์šฉ์ž: ๋ณด๋ฅ˜ยท๋ฐ˜๋ ค / ์ง€ํœ˜๊ด€: ์Šน์ธยท์ˆœ์œ„ ๋ณ€๊ฒฝยท์ˆ˜๋™ ์ „ํ™˜\")\n", "print(\"์Šน์ธ์€ ์ง€ํœ˜๊ณ„ํ†ต์— ๋Œ€์‘ ์ธ๊ฐ€๋ฅผ ์š”์ฒญํ•˜๋Š” ๋‹จ๊ณ„์ด๋ฉฐ, ์‹ค์ œ ๊ต์ „์€ ๊ต์ „์ˆ˜์น™๊ณผ ์ง€ํœ˜๊ณ„ํ†ต์— ๋”ฐ๋ฅธ๋‹ค.\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 07 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[t= 2s] OPR-07 APPROVE T09000 -> ๊ถŒํ•œ ์—†์Œ(OPR-07:APPROVE)\n", "[t= 3s] CDR-01 APPROVE T09000 -> OK\n", "[t= 5s] CDR-01 REORDER T09005 -> OK\n", "[t= 6s] OPR-09 HOLD T09003 -> OK\n", "[t= 7s] CDR-01 APPROVE T09003 -> ํŒ๋‹จ ๊ทผ๊ฑฐ 6์ž ์ด์ƒ ํ•„์š”\n", "[t= 8s] OPR-07 REJECT T09007 -> OK\n", "[t= 10s] CDR-01 MODE - -> OK\n", "[t= 12s] CDR-01 APPROVE T09001 -> OK\n", "[t= 15s] CDR-01 APPROVE T09011 -> Low ๋“ฑ๊ธ‰์€ ์Šน์ธ ์ „ ์ถ”๊ฐ€ ํ™•์ธ(HOLD) ํ•„์š”\n", "[t= 20s] CDR-01 APPROVE T09001 -> ๋Œ€์ƒ ์—†์Œ ๋˜๋Š” ์ด๋ฏธ ์ข…๊ฒฐ\n", "[t= 25s] CDR-01 MODE - -> OK\n", "\n", "[๊ฒ€ํ†  ๋Œ€๊ธฐ์—ด] (์ƒ์‹  ์šฐ์„ , ๊ทธ๋‹ค์Œ ์ˆœ์œ„)\n", " id ai_rank rank grade status escalated\n", "T09003 4 4 Low HOLD True\n", "T09005 6 2 High PENDING False\n", "T09002 3 3 Med PENDING False\n", "T09004 5 5 High PENDING False\n", "T09006 7 7 Med PENDING False\n", "T09008 9 9 High PENDING False\n", "\n", "์กฐ์น˜ ์‹œ๋„ 11๊ฑด ์ค‘ ๋กœ๊ทธ ๊ธฐ๋ก 11๊ฑด -> ๋กœ๊น… ์„ฑ๊ณต๋ฅ  100% (๋ชฉํ‘œ 100%) -> ๋‹ฌ์„ฑ\n", "์ข…๊ฒฐ 3๊ฑด ๋Œ€๊ธฐ์‹œ๊ฐ„ ํ‰๊ท  7.7s / ์ตœ๋Œ€ 12s, ๋ฏธ๊ฒฐ 9๊ฑด, ์ž๋™ ์Šน์ธ 0๊ฑด\n", "๊ฐ์‚ฌ ๋กœ๊ทธ ๋ฌด๊ฒฐ์„ฑ: ์ •์ƒ (24๊ฑด)\n", "์œ„ยท๋ณ€์กฐ ์‹œํ—˜(4๋ฒˆ์งธ ๊ธฐ๋ก ๊ทผ๊ฑฐ ์ˆ˜์ •) -> ๊ฒ€์ถœ(4๋ฒˆ ๊ธฐ๋ก)\n", "\n", "[์ง€ํœ˜ ์ฑ…์ž„ ๋ถ„๊ณ„์„ ] AI: ์ˆœ์œ„ '๊ถŒ๊ณ '๋งŒ / ์šด์šฉ์ž: ๋ณด๋ฅ˜ยท๋ฐ˜๋ ค / ์ง€ํœ˜๊ด€: ์Šน์ธยท์ˆœ์œ„ ๋ณ€๊ฒฝยท์ˆ˜๋™ ์ „ํ™˜\n", "์Šน์ธ์€ ์ง€ํœ˜๊ณ„ํ†ต์— ๋Œ€์‘ ์ธ๊ฐ€๋ฅผ ์š”์ฒญํ•˜๋Š” ๋‹จ๊ณ„์ด๋ฉฐ, ์‹ค์ œ ๊ต์ „์€ ๊ต์ „์ˆ˜์น™๊ณผ ์ง€ํœ˜๊ณ„ํ†ต์— ๋”ฐ๋ฅธ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 08. 100๊ธฐ ์ด์ƒ ๋™์‹œ ํฌํ™” ๊ณต๊ฒฉ ๊ฐ€์ƒ ์ „์žฅ ๋ชจ์˜ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๋ฐ ์ฒ˜๋ฆฌ ์ง€์—ฐ ๊ฒ€์ฆ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ˆ˜๋ฐฑ ๋ฐœ์˜ ๋“œ๋ก /๋ฏธ์‚ฌ์ผ ๋™์‹œ ์œ ์ž… ๋ชจ์˜ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜์—ฌ ์‹œ์Šคํ…œ ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„์„ ํ”„๋กœํŒŒ์ผ๋งํ•˜๊ณ  ์‹ค์‹œ๊ฐ„ ์ฒ˜๋ฆฌ ์„ฑ๋Šฅ์„ ๊ฒ€์ฆํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **100๊ธฐ ์ด์ƒ์ด ๋™์‹œ์— ๋“ค์–ด์˜ค๋Š” ํฌํ™” ์ƒํ™ฉ์—์„œ ์‹œ์Šคํ…œ์ด ๋ฉˆ์ถ”๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ๋ฅผ ์žƒ์ง€ ์•Š๋Š”์ง€ ๊ฒ€์ฆํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **ํ‰์‹œ 100๊ธฐ์—์„œ 600๊ธฐ(500% ์ฆ๊ฐ€)๊นŒ์ง€ ๋‹จ๊ณ„์ ์œผ๋กœ ๋Š˜๋ฆฌ๋ฉฐ ์†์ƒ ๋ฉ”์‹œ์ง€ยท์ค‘๋ณตยท์ˆœ์„œ ๋’ค์„ž์ž„ ์ฃผ์ž…ํ•˜์—ฌ ์ฒ˜๋ฆฌ ์ง€์—ฐ ๋ฐ ๋ณต์›๋ ฅ์„ ์‹œํ—˜ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `tests/ops04_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": [ "\"\"\"ํ‰์‹œ 100๊ธฐ ๊ธฐ์ค€์—์„œ ์ตœ๋Œ€ 600๊ธฐ(500% ์ฆ๊ฐ€)๊นŒ์ง€ ๊ฐ€์ƒ ๋น„ํ–‰์ฒด๊ฐ€ ๋™์‹œ์— ์œ ์ž…๋˜๋Š” ํฌํ™” ๊ณต๊ฒฉ ์ƒํ™ฉ์— ์†์ƒ ๋ฉ”์‹œ์ง€ยท์ค‘๋ณตยท์ˆœ์„œ ๋’ค์„ž์ž„์„ ์ฃผ์ž…ํ•ด ๋ถ€ํ•˜ ์‹œํ—˜์„ ์ˆ˜ํ–‰ํ•˜๊ณ , ๋‹จ๊ณ„๋ณ„ ์ฒ˜๋ฆฌ ์ง€์—ฐยท์‹œ์Šคํ…œ ๋‹ค์šดยท๋ฐ์ดํ„ฐ ์œ ์‹ค๊ณผ ์‹œ๊ฐ„ ์˜ˆ์‚ฐ ์ดˆ๊ณผ ์‹œ ๊ธฐ๋Šฅ ์ถ•์†Œ(Fail-safe) ๋™์ž‘์„ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋ฐฉ์–ด์ง€์—ญยท๊ฐ€์ƒ ๋น„ํ–‰์ฒด ์ƒ์„ฑ๊ธฐ ----------------\n", "# ์ขŒํ‘œ๋Š” ๊ฐ€์ƒ ์ง€ํœ˜์†Œ๋ฅผ ์›์ ์œผ๋กœ ํ•œ km ๋‹จ์œ„ ํ‰๋ฉด์ด๋‹ค. ๋น„ํ–‰์ฒด ์œ ํ˜•์€ ์ถ”์ƒ ๋ฒ”์ฃผ์ด๋ฉฐ\n", "# ์†๋„ยท๊ณ ๋„ยท๋ฐ˜์‚ฌ๊ฐ•๋„๋Š” ํ•™์Šต์šฉ ์ž„์˜ ๋ฒ”์œ„๋กœ, ์‹ค์ œ ๋ฌด๊ธฐ ์ œ์›ยท์‚ฌ๊ฑฐ๋ฆฌยทํƒ„๋„์™€ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n", " [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n", " [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n", " [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n", "TYPES = { # ์œ ํ˜•: (์œ„ํ˜‘ ์—ฌ๋ถ€, ์†๋„ m/s ๋ฒ”์œ„, ๊ณ ๋„ m ๋ฒ”์œ„, ์ถœํ˜„ ๋น„์œจ)\n", " \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ €์† ๋ฌด์ธ๊ธฐํ˜•(๊ฐ€์ƒ)\n", " \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์† ์ˆœํ•ญํ˜•(๊ฐ€์ƒ)\n", " \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋งŒ์ฒด(๊ฐ€์ƒ)\n", " \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํ•ญ๊ธฐ(๊ฐ€์ƒ)\n", " \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ˜ ๊ตฐ์ง‘\n", "}\n", "TTI_URGENT = 600 # ๊ธด๊ธ‰ ํŒ์ • ๊ธฐ์ค€: ๊ฐ€์ƒ ์ž์‚ฐ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„ 600์ดˆ ์ด๋‚ด\n", "CPA_KM = 1.5 # ์ž์‚ฐ ์ƒ๊ณต ํ†ต๊ณผ ํŒ์ • ๋ฐ˜๊ฒฝ(๊ฐ€์ƒ)\n", "\n", "\n", "def make_raid(seed, n=80, spoof=0):\n", " \"\"\"๊ฐ€์ƒ ํฌํ™” ์ƒํ™ฉ 1ํšŒ๋ถ„(๋น„ํ–‰์ฒด n๊ฐœ + ์Šคํ‘ธํ•‘ ๊ฐ€์งœ ํ•ญ์  spoof๊ฐœ)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(TYPES)\n", " rows = []\n", " for i in range(n + spoof):\n", " typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n", " threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n", " brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n", " x, y = dist * np.sin(brg), dist * np.cos(brg)\n", " spd = rng.uniform(*sp); alt = rng.uniform(*al)\n", " aim = int(rng.integers(0, len(ASSETS)))\n", " if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ€์ƒ ์ž์‚ฐ ๋ฐฉํ–ฅ(์ž‘์€ ๋ฐฉ์œ„ ์˜ค์ฐจ)\n", " ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n", " hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n", " else:\n", " hd = rng.uniform(0, 2 * np.pi); aim = -1\n", " vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n", " sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n", " \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n", " \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n", " jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n", " true_tti = np.inf\n", " if threat:\n", " d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n", " true_tti = d_aim * 1000 / spd\n", " rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n", " \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n", " \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n", " \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n", " \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n", " df = pd.DataFrame(rows)\n", " df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n", " for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n", " return df\n", "\n", "\n", "def geometry(df):\n", " \"\"\"๊ฐ ํ•ญ์ ๊ณผ ๊ฐ€์ƒ ์ž์‚ฐ ์‚ฌ์ด ์ตœ๊ทผ์ ‘์ (CPA) ๊ฑฐ๋ฆฌ์™€ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„(TTI, s)์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 8] 100๊ธฐ ์ด์ƒ ๋™์‹œ ํฌํ™” ๊ณต๊ฒฉ ๊ฐ€์ƒ ์ „์žฅ ๋ชจ์˜ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๋ฐ ์ฒ˜๋ฆฌ ์ง€์—ฐ ๊ฒ€์ฆ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "FEATS = [\"log_spd\", \"alt_km\", \"rcs\", \"rcs_var\", \"n_sensors\", \"civ_iff\", \"jump_ratio\"]\n", "\n", "\n", "def features(df):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 8] 100๊ธฐ ์ด์ƒ ๋™์‹œ ํฌํ™” ๊ณต๊ฒฉ ๊ฐ€์ƒ ์ „์žฅ ๋ชจ์˜ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๋ฐ ์ฒ˜๋ฆฌ ์ง€์—ฐ ๊ฒ€์ฆ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def train_classifier(seeds=(101, 102, 103, 104)):\n", " \"\"\"ํ•™์Šต์šฉ ๊ฐ€์ƒ ์ƒํ™ฉ(์‹œํ—˜ ์ƒํ™ฉ๊ณผ ๋‹ค๋ฅธ ์‹œ๋“œ)์œผ๋กœ '์œ„ํ˜‘ ์œ ์‚ฌ๋„' ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค.\"\"\"\n", " tr = pd.concat([make_raid(s, 120, spoof=10) for s in seeds], ignore_index=True)\n", " clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500, C=1.0))\n", " clf.fit(features(tr)[FEATS], tr[\"threat\"].astype(int))\n", " return clf\n", "\n", "\n", "def baseline_rank(df):\n", " \"\"\"Baseline: ๊ฑฐ๋ฆฌยท๋„๋‹ฌ์‹œ๊ฐ„๋งŒ์œผ๋กœ 1์ฐจ ์œ„ํ˜‘ ์ ์ˆ˜๋ฅผ ๋งค๊ธด๋‹ค(์ž์‚ฐ ๊ฐ€์น˜ยท์œ ํ˜• ํŒ๋ณ„ ์—†์Œ).\"\"\"\n", " _, tti = geometry(df)\n", " t_min = tti.min(axis=1)\n", " out = df[[\"id\"]].copy()\n", " out[\"tti_s\"] = t_min\n", " out[\"score\"] = np.exp(-t_min / 300)\n", " out[\"urgent_pred\"] = t_min <= TTI_URGENT\n", " return out.sort_values(\"score\", ascending=False)\n", "\n", "\n", "def advanced_rank(df, clf, w=None):\n", " \"\"\"๊ณ ๋„ํ™”: ์ž์‚ฐ ๊ฐ€์น˜ x ๋„๋‹ฌ์‹œ๊ฐ„ x ์œ„ํ˜‘ ์œ ์‚ฌ๋„, ์šด๋™ํ•™ ๋ถˆ์ผ์น˜(์Šคํ‘ธํ•‘ ์˜์‹ฌ) ํ•ญ์ ์€ ๋ณ„๋„ ํ‘œ์‹œํ•œ๋‹ค.\"\"\"\n", " w = w or {\"tau\": 300, \"p_min\": 0.5, \"value_pow\": 1.0, \"jump_max\": 2.0}\n", " _, tti = geometry(df)\n", " val = ASSETS[\"value\"].to_numpy()[None] ** w[\"value_pow\"]\n", " asset_score = val * np.exp(-tti / w[\"tau\"])\n", " best = asset_score.argmax(axis=1)\n", " p = clf.predict_proba(features(df)[FEATS])[:, 1]\n", " spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > w[\"jump_max\"]\n", " out = df[[\"id\"]].copy()\n", " out[\"asset\"] = ASSETS[\"asset\"].to_numpy()[best]\n", " out[\"tti_s\"] = tti[np.arange(len(df)), best]\n", " out[\"p_threat\"] = p\n", " out[\"spoof_flag\"] = spoof\n", " out[\"score\"] = np.where(spoof, 0, p * asset_score.max(axis=1))\n", " out[\"urgent_pred\"] = (p >= w[\"p_min\"]) & (out[\"tti_s\"] <= TTI_URGENT) & ~spoof\n", " return out.sort_values(\"score\", ascending=False)\n", "\n", "BUDGET_MS = 200 # 1ํšŒ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ์˜ˆ์‚ฐ\n", "clf = train_classifier()\n", "\n", "\n", "def inject(df, rng):\n", " \"\"\"์†์ƒ(NaNยท์Œ์ˆ˜ ์†๋„) 5%, ์ค‘๋ณต 10%, ์ˆœ์„œ ๋’ค์„ž๊ธฐ\"\"\"\n", " d = df.copy()\n", " bad = rng.random(len(d)) < 0.05\n", " d.loc[bad, \"spd\"] = rng.choice([np.nan, -1.0], bad.sum())\n", " dup = d.sample(frac=0.10, random_state=int(rng.integers(1e6)))\n", " return pd.concat([d, dup]).sample(frac=1, random_state=1).reset_index(drop=True)\n", "\n", "\n", "def process(msgs, degrade=False):\n", " \"\"\"๊ฒ€์ฆ -> ๊ฒฉ๋ฆฌ -> ์ค‘๋ณต ์ œ๊ฑฐ -> ์ˆœ์œ„ ์‚ฐ์ •. ์–ด๋–ค ๋ฉ”์‹œ์ง€๋„ ์กฐ์šฉํžˆ ๋ฒ„๋ฆฌ์ง€ ์•Š๊ณ  ๊ฒฐ๊ณผ/๊ฒฉ๋ฆฌ/์ค‘๋ณต ์ค‘ ํ•˜๋‚˜๋กœ ๊ณ„์ˆ˜ํ•œ๋‹ค.\"\"\"\n", " ok = msgs[\"spd\"].notna() & (msgs[\"spd\"] > 0) & msgs[[\"x\", \"y\"]].notna().all(axis=1)\n", " quarantine = msgs[~ok]\n", " good = msgs[ok]\n", " uniq = good.drop_duplicates(\"id\")\n", " if degrade: # ๊ธฐ๋Šฅ ์ถ•์†Œ: ๋ถ„๋ฅ˜๊ธฐ ์ƒ๋žต, ๋„๋‹ฌ์‹œ๊ฐ„ ์ˆœ ์ •๋ ฌ๋งŒ ์ˆ˜ํ–‰(๊ฒฝ๋ณด ๋ˆ„๋ฝ ๋ฐฉ์ง€ ์šฐ์„ )\n", " ranked = baseline_rank(uniq)\n", " else:\n", " ranked = advanced_rank(uniq, clf)\n", " return ranked, len(quarantine), len(good) - len(uniq)\n", "\n", "\n", "def run_stage(msgs, budget_ms):\n", " \"\"\"์‹œ๊ฐ„ ์˜ˆ์‚ฐ์„ ๋„˜์œผ๋ฉด ๊ธฐ๋Šฅ ์ถ•์†Œ ๋ชจ๋“œ๋กœ ๋‹ค์‹œ ์ฒ˜๋ฆฌํ•œ๋‹ค(Fail-safe).\"\"\"\n", " t0 = time.perf_counter()\n", " out = process(msgs)\n", " ms = (time.perf_counter() - t0) * 1000\n", " if ms <= budget_ms:\n", " return out, ms, False\n", " t0 = time.perf_counter()\n", " out = process(msgs, degrade=True)\n", " return out, (time.perf_counter() - t0) * 1000, True\n", "\n", "\n", "rows = []\n", "rng = np.random.default_rng(8)\n", "for n in (100, 200, 300, 450, 600):\n", " raid = make_raid(30 + n, n, spoof=n // 20)\n", " msgs = inject(raid, rng)\n", " crash = 0\n", " try:\n", " (ranked, nq, ndup), ms, degraded = run_stage(msgs, BUDGET_MS)\n", " except Exception: # ๋‹ค์šด ๊ณ„์ˆ˜(๋ฐœ์ƒ ์‹œ์—๋„ ๋‹ค์Œ ๋‹จ๊ณ„ ๊ณ„์†)\n", " crash, ranked, nq, ndup, ms, degraded = 1, pd.DataFrame(columns=[\"id\"]), 0, 0, np.nan, False\n", " accounted = len(ranked) + nq + ndup\n", " expect_ids = set(msgs.loc[msgs[\"spd\"].notna() & (msgs[\"spd\"] > 0), \"id\"])\n", " lost = len(expect_ids - set(ranked[\"id\"]))\n", " rows.append({\"๋น„ํ–‰์ฒด\": n, \"์ฆ๊ฐ€์œจ\": f\"{(n / 100 - 1):.0%}\", \"๋ฉ”์‹œ์ง€\": len(msgs), \"์ฒ˜๋ฆฌ(ms)\": round(ms, 1), \"๊ฒฉ๋ฆฌ\": nq, \"์ค‘๋ณต\": ndup,\n", " \"๊ณ„์ˆ˜๋ˆ„๋ฝ\": len(msgs) - accounted, \"์œ ์‹ค\": lost, \"๋‹ค์šด\": crash, \"๊ธฐ๋Šฅ์ถ•์†Œ\": degraded})\n", "res = pd.DataFrame(rows)\n", "print(\"[๋ถ€ํ•˜ ๋‹จ๊ณ„๋ณ„ ๊ฒฐ๊ณผ]\")\n", "print(res.to_string(index=False))\n", "\n", "# Fail-safe ํ™•์ธ: ์˜ˆ์‚ฐ์„ 0.1 ms๋กœ ๊ฐ•์ œํ•ด ๊ธฐ๋Šฅ ์ถ•์†Œ ๊ฒฝ๋กœ๊ฐ€ ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ์‹œํ—˜\n", "raid = make_raid(99, 600, spoof=30); msgs = inject(raid, rng)\n", "(r_deg, nq, _), ms, degraded = run_stage(msgs, 0.1)\n", "valid = set(msgs.loc[msgs[\"spd\"].notna() & (msgs[\"spd\"] > 0), \"id\"])\n", "urgent_ids = set(raid.loc[raid[\"urgent\"], \"id\"]) & valid\n", "miss = len(urgent_ids - set(r_deg.loc[r_deg[\"urgent_pred\"], \"id\"]))\n", "print(f\"\\n[Fail-safe ์‹œํ—˜] ์˜ˆ์‚ฐ 0.1 ms ๊ฐ•์ œ -> ๊ธฐ๋Šฅ ์ถ•์†Œ {'์ „ํ™˜' if degraded else '๋ฏธ์ „ํ™˜'}, ๊ธด๊ธ‰ ํ‘œ์  {len(urgent_ids)}๊ฑด ์ค‘ ๋ˆ„๋ฝ {miss}๊ฑด\")\n", "print(f\" ๊ธฐ๋Šฅ ์ถ•์†Œ ๋ชจ๋“œ๋Š” ๋„๋‹ฌ์‹œ๊ฐ„ ๊ธฐ์ค€ ๊ฒฝ๋ณด๋ฅผ ์œ ์ง€ํ•˜๊ณ (๋ˆ„๋ฝ ๋ฐฉ์ง€ ์šฐ์„ ), ๋Š˜์–ด๋‚œ ์˜ค๊ฒฝ๋ณด์™€ ๊ฒฉ๋ฆฌ ๋ฉ”์‹œ์ง€ {nq}๊ฑด์€ ์šด์šฉ์ž ํ™•์ธ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.\")\n", "print(\"๋ณ‘๋ชฉ ๊ตฌ๊ฐ„: ๋ถ„๋ฅ˜๊ธฐ ์ถ”๋ก ยท์ž์‚ฐ๋ณ„ CPA ํ–‰๋ ฌ ๊ณ„์‚ฐ(ํ•ญ์  ์ˆ˜ x ์ž์‚ฐ ์ˆ˜)์— ๋น„๋ก€, 600๊ธฐ์—์„œ๋„ ์˜ˆ์‚ฐ ๋‚ด\")\n", "\n", "down, loss = int(res[\"๋‹ค์šด\"].sum()), int(res[\"์œ ์‹ค\"].sum() + res[\"๊ณ„์ˆ˜๋ˆ„๋ฝ\"].sum())\n", "print(f\"\\nํŠธ๋ž˜ํ”ฝ 500% ์ฆ๊ฐ€ ์‹œ ์‹œ์Šคํ…œ ๋‹ค์šด {down}๊ฑด (๋ชฉํ‘œ 0๊ฑด) -> {'๋‹ฌ์„ฑ' if down == 0 else '๋ฏธ๋‹ฌ'}\")\n", "print(f\"๋ฐ์ดํ„ฐ ์œ ์‹ค {loss}๊ฑด, ์œ ์‹ค๋ฅ  {loss / res['๋ฉ”์‹œ์ง€'].sum():.2%} (๋ชฉํ‘œ 0%) -> {'๋‹ฌ์„ฑ' if loss == 0 else '๋ฏธ๋‹ฌ'}\")\n", "\n", "plt.figure(figsize=(7, 4))\n", "plt.plot(res[\"๋น„ํ–‰์ฒด\"], res[\"์ฒ˜๋ฆฌ(ms)\"], \"o-\", label=\"processing time\")\n", "plt.axhline(200, ls=\"--\", c=\"r\", label=\"budget 200 ms\")\n", "plt.xlabel(\"Simultaneous tracks\"); plt.ylabel(\"ms\"); plt.title(\"Stress test: latency vs load (virtual)\"); plt.legend(); plt.grid(alpha=.3)\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 08 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๋ถ€ํ•˜ ๋‹จ๊ณ„๋ณ„ ๊ฒฐ๊ณผ]\n", " ๋น„ํ–‰์ฒด ์ฆ๊ฐ€์œจ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ(ms) ๊ฒฉ๋ฆฌ ์ค‘๋ณต ๊ณ„์ˆ˜๋ˆ„๋ฝ ์œ ์‹ค ๋‹ค์šด ๊ธฐ๋Šฅ์ถ•์†Œ\n", " 100 0% 115 9.8 2 10 0 0 0 False\n", " 200 100% 231 9.8 9 20 0 0 0 False\n", " 300 200% 347 11.8 9 31 0 0 0 False\n", " 450 350% 519 14.8 27 46 0 0 0 False\n", " 600 500% 693 10.9 28 60 0 0 0 False\n", "\n", "[Fail-safe ์‹œํ—˜] ์˜ˆ์‚ฐ 0.1 ms ๊ฐ•์ œ -> ๊ธฐ๋Šฅ ์ถ•์†Œ ์ „ํ™˜, ๊ธด๊ธ‰ ํ‘œ์  197๊ฑด ์ค‘ ๋ˆ„๋ฝ 0๊ฑด\n", " ๊ธฐ๋Šฅ ์ถ•์†Œ ๋ชจ๋“œ๋Š” ๋„๋‹ฌ์‹œ๊ฐ„ ๊ธฐ์ค€ ๊ฒฝ๋ณด๋ฅผ ์œ ์ง€ํ•˜๊ณ (๋ˆ„๋ฝ ๋ฐฉ์ง€ ์šฐ์„ ), ๋Š˜์–ด๋‚œ ์˜ค๊ฒฝ๋ณด์™€ ๊ฒฉ๋ฆฌ ๋ฉ”์‹œ์ง€ 23๊ฑด์€ ์šด์šฉ์ž ํ™•์ธ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.\n", "๋ณ‘๋ชฉ ๊ตฌ๊ฐ„: ๋ถ„๋ฅ˜๊ธฐ ์ถ”๋ก ยท์ž์‚ฐ๋ณ„ CPA ํ–‰๋ ฌ ๊ณ„์‚ฐ(ํ•ญ์  ์ˆ˜ x ์ž์‚ฐ ์ˆ˜)์— ๋น„๋ก€, 600๊ธฐ์—์„œ๋„ ์˜ˆ์‚ฐ ๋‚ด\n", "\n", "ํŠธ๋ž˜ํ”ฝ 500% ์ฆ๊ฐ€ ์‹œ ์‹œ์Šคํ…œ ๋‹ค์šด 0๊ฑด (๋ชฉํ‘œ 0๊ฑด) -> ๋‹ฌ์„ฑ\n", "๋ฐ์ดํ„ฐ ์œ ์‹ค 0๊ฑด, ์œ ์‹ค๋ฅ  0.00% (๋ชฉํ‘œ 0%) -> ๋‹ฌ์„ฑ\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 09. AAR ๊ธฐ๋ฐ˜ ๋ฐฉ๊ณต ์š”๊ฒฉ ์ˆœ์œ„ ํŒ์ • ์˜ค๋ฅ˜ ๋ถ„์„ ๋ฐ ์œ„ํ˜‘ ๊ฐ€์ค‘์น˜ ๋งคํŠธ๋ฆญ์Šค ์žฌ๋ณด์ •\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "AAR ์‚ฌํ›„๊ฒ€ํ† ๋ฅผ ํ†ตํ•ด ์š”๊ฒฉ ์ˆœ์œ„ ์‚ฐ์ • ์ง€์—ฐ ๋ฐ ๊ฐ€์น˜ ํ‰๊ฐ€ ์˜ค๋ฅ˜ ์‚ฌ๋ก€๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์œ„ํ˜‘ ๊ฐ€์ค‘์น˜ ๋งคํŠธ๋ฆญ์Šค๋ฅผ ์—ญํ™˜๋ฅ˜ ์ˆ˜์ •ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **AAR๋กœ ์ˆœ์œ„ ํŒ์ • ์˜ค๋ฅ˜์˜ ์›์ธ์„ ๋ถ„๋ฅ˜ํ•˜๊ณ  ์œ„ํ˜‘ ๊ฐ€์ค‘์น˜ ๋งคํŠธ๋ฆญ์Šค๋ฅผ ์žฌ๋ณด์ •ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋„๋‹ฌ์‹œ๊ฐ„๋งŒ ๋ฐ˜์˜ํ•œ ์ดˆ๊ธฐ ๊ฐ€์ค‘์น˜๋กœ ๊ฐ€์ƒ ์ƒํ™ฉ์„ ํŒ์ •ํ•ด ์˜ค๋ฅ˜๋ฅผ '๋น„์œ„ํ˜‘ ๊ณผ๋Œ€ ํŒ์ •, ์Šคํ‘ธํ•‘ ๋ฏธ๊ฒฉ๋ฆฌ, ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„ ์˜ค์ฐจ'๋กœ ๋ถ„๋ฅ˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/ops04_aar_report.md ๋ฐ configs/ops04_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(์‚ฌํ›„๊ฒ€ํ† )๋กœ ์ดˆ๊ธฐ ์œ„ํ˜‘ ๊ฐ€์ค‘์น˜ ๋งคํŠธ๋ฆญ์Šค์˜ ์šฐ์„ ์ˆœ์œ„ ํŒ์ • ์˜ค๋ฅ˜๋ฅผ ์œ ํ˜•๋ณ„๋กœ ๋ถ„์„ํ•˜๊ณ , ํ•™์Šต์šฉ ๊ฐ€์ƒ ์ƒํ™ฉ์—์„œ ๊ฐ€์ค‘์น˜๋ฅผ ์žฌ๋ณด์ •ํ•œ ๋’ค ๋ณ„๋„ ์‹œํ—˜ ์ƒํ™ฉ์—์„œ ์ •ํ™•๋„ ํ–ฅ์ƒ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜์˜ ์žฌ๋ฐœ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ด ์กฐ์ • ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ YAML ๋ฌธ์ž์—ด๋กœ ์ถœ๋ ฅํ•œ๋‹ค.\"\"\"\n", "import itertools\n", "import numpy as np\n", "import pandas as pd\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋ฐฉ์–ด์ง€์—ญยท๊ฐ€์ƒ ๋น„ํ–‰์ฒด ์ƒ์„ฑ๊ธฐ ----------------\n", "# ์ขŒํ‘œ๋Š” ๊ฐ€์ƒ ์ง€ํœ˜์†Œ๋ฅผ ์›์ ์œผ๋กœ ํ•œ km ๋‹จ์œ„ ํ‰๋ฉด์ด๋‹ค. ๋น„ํ–‰์ฒด ์œ ํ˜•์€ ์ถ”์ƒ ๋ฒ”์ฃผ์ด๋ฉฐ\n", "# ์†๋„ยท๊ณ ๋„ยท๋ฐ˜์‚ฌ๊ฐ•๋„๋Š” ํ•™์Šต์šฉ ์ž„์˜ ๋ฒ”์œ„๋กœ, ์‹ค์ œ ๋ฌด๊ธฐ ์ œ์›ยท์‚ฌ๊ฑฐ๋ฆฌยทํƒ„๋„์™€ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n", " [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n", " [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n", " [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n", "TYPES = { # ์œ ํ˜•: (์œ„ํ˜‘ ์—ฌ๋ถ€, ์†๋„ m/s ๋ฒ”์œ„, ๊ณ ๋„ m ๋ฒ”์œ„, ์ถœํ˜„ ๋น„์œจ)\n", " \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ €์† ๋ฌด์ธ๊ธฐํ˜•(๊ฐ€์ƒ)\n", " \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์† ์ˆœํ•ญํ˜•(๊ฐ€์ƒ)\n", " \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋งŒ์ฒด(๊ฐ€์ƒ)\n", " \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํ•ญ๊ธฐ(๊ฐ€์ƒ)\n", " \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ˜ ๊ตฐ์ง‘\n", "}\n", "TTI_URGENT = 600 # ๊ธด๊ธ‰ ํŒ์ • ๊ธฐ์ค€: ๊ฐ€์ƒ ์ž์‚ฐ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„ 600์ดˆ ์ด๋‚ด\n", "CPA_KM = 1.5 # ์ž์‚ฐ ์ƒ๊ณต ํ†ต๊ณผ ํŒ์ • ๋ฐ˜๊ฒฝ(๊ฐ€์ƒ)\n", "\n", "\n", "def make_raid(seed, n=80, spoof=0):\n", " \"\"\"๊ฐ€์ƒ ํฌํ™” ์ƒํ™ฉ 1ํšŒ๋ถ„(๋น„ํ–‰์ฒด n๊ฐœ + ์Šคํ‘ธํ•‘ ๊ฐ€์งœ ํ•ญ์  spoof๊ฐœ)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(TYPES)\n", " rows = []\n", " for i in range(n + spoof):\n", " typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n", " threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n", " brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n", " x, y = dist * np.sin(brg), dist * np.cos(brg)\n", " spd = rng.uniform(*sp); alt = rng.uniform(*al)\n", " aim = int(rng.integers(0, len(ASSETS)))\n", " if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ€์ƒ ์ž์‚ฐ ๋ฐฉํ–ฅ(์ž‘์€ ๋ฐฉ์œ„ ์˜ค์ฐจ)\n", " ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n", " hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n", " else:\n", " hd = rng.uniform(0, 2 * np.pi); aim = -1\n", " vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n", " sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n", " \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n", " \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n", " jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n", " true_tti = np.inf\n", " if threat:\n", " d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n", " true_tti = d_aim * 1000 / spd\n", " rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n", " \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n", " \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n", " \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n", " \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n", " df = pd.DataFrame(rows)\n", " df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n", " for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n", " return df\n", "\n", "\n", "def geometry(df):\n", " \"\"\"๊ฐ ํ•ญ์ ๊ณผ ๊ฐ€์ƒ ์ž์‚ฐ ์‚ฌ์ด ์ตœ๊ทผ์ ‘์ (CPA) ๊ฑฐ๋ฆฌ์™€ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„(TTI, s)์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 9] AAR ๊ธฐ๋ฐ˜ ๋ฐฉ๊ณต ์š”๊ฒฉ ์ˆœ์œ„ ํŒ์ • ์˜ค๋ฅ˜ ๋ถ„์„ ๋ฐ ์œ„ํ˜‘ ๊ฐ€์ค‘์น˜ ๋งคํŠธ๋ฆญ์Šค ์žฌ๋ณด์ • ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "FEATS = [\"log_spd\", \"alt_km\", \"rcs\", \"rcs_var\", \"n_sensors\", \"civ_iff\", \"jump_ratio\"]\n", "\n", "\n", "def features(df):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 9] AAR ๊ธฐ๋ฐ˜ ๋ฐฉ๊ณต ์š”๊ฒฉ ์ˆœ์œ„ ํŒ์ • ์˜ค๋ฅ˜ ๋ถ„์„ ๋ฐ ์œ„ํ˜‘ ๊ฐ€์ค‘์น˜ ๋งคํŠธ๋ฆญ์Šค ์žฌ๋ณด์ • ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def train_classifier(seeds=(101, 102, 103, 104)):\n", " \"\"\"ํ•™์Šต์šฉ ๊ฐ€์ƒ ์ƒํ™ฉ(์‹œํ—˜ ์ƒํ™ฉ๊ณผ ๋‹ค๋ฅธ ์‹œ๋“œ)์œผ๋กœ '์œ„ํ˜‘ ์œ ์‚ฌ๋„' ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค.\"\"\"\n", " tr = pd.concat([make_raid(s, 120, spoof=10) for s in seeds], ignore_index=True)\n", " clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500, C=1.0))\n", " clf.fit(features(tr)[FEATS], tr[\"threat\"].astype(int))\n", " return clf\n", "\n", "\n", "def baseline_rank(df):\n", " \"\"\"Baseline: ๊ฑฐ๋ฆฌยท๋„๋‹ฌ์‹œ๊ฐ„๋งŒ์œผ๋กœ 1์ฐจ ์œ„ํ˜‘ ์ ์ˆ˜๋ฅผ ๋งค๊ธด๋‹ค(์ž์‚ฐ ๊ฐ€์น˜ยท์œ ํ˜• ํŒ๋ณ„ ์—†์Œ).\"\"\"\n", " _, tti = geometry(df)\n", " t_min = tti.min(axis=1)\n", " out = df[[\"id\"]].copy()\n", " out[\"tti_s\"] = t_min\n", " out[\"score\"] = np.exp(-t_min / 300)\n", " out[\"urgent_pred\"] = t_min <= TTI_URGENT\n", " return out.sort_values(\"score\", ascending=False)\n", "\n", "\n", "def advanced_rank(df, clf, w=None):\n", " \"\"\"๊ณ ๋„ํ™”: ์ž์‚ฐ ๊ฐ€์น˜ x ๋„๋‹ฌ์‹œ๊ฐ„ x ์œ„ํ˜‘ ์œ ์‚ฌ๋„, ์šด๋™ํ•™ ๋ถˆ์ผ์น˜(์Šคํ‘ธํ•‘ ์˜์‹ฌ) ํ•ญ์ ์€ ๋ณ„๋„ ํ‘œ์‹œํ•œ๋‹ค.\"\"\"\n", " w = w or {\"tau\": 300, \"p_min\": 0.5, \"value_pow\": 1.0, \"jump_max\": 2.0}\n", " _, tti = geometry(df)\n", " val = ASSETS[\"value\"].to_numpy()[None] ** w[\"value_pow\"]\n", " asset_score = val * np.exp(-tti / w[\"tau\"])\n", " best = asset_score.argmax(axis=1)\n", " p = clf.predict_proba(features(df)[FEATS])[:, 1]\n", " spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > w[\"jump_max\"]\n", " out = df[[\"id\"]].copy()\n", " out[\"asset\"] = ASSETS[\"asset\"].to_numpy()[best]\n", " out[\"tti_s\"] = tti[np.arange(len(df)), best]\n", " out[\"p_threat\"] = p\n", " out[\"spoof_flag\"] = spoof\n", " out[\"score\"] = np.where(spoof, 0, p * asset_score.max(axis=1))\n", " out[\"urgent_pred\"] = (p >= w[\"p_min\"]) & (out[\"tti_s\"] <= TTI_URGENT) & ~spoof\n", " return out.sort_values(\"score\", ascending=False)\n", "\n", "LEVELS = [\"๊ธด๊ธ‰\", \"์ฃผ์˜\", \"์ผ๋ฐ˜\"]\n", "\n", "\n", "def truth(df):\n", " return np.select([df[\"threat\"] & (df[\"true_tti\"] <= 300), df[\"threat\"] & (df[\"true_tti\"] <= 900)], [\"๊ธด๊ธ‰\", \"์ฃผ์˜\"], \"์ผ๋ฐ˜\")\n", "\n", "\n", "def judge(df, clf, W):\n", " \"\"\"์œ„ํ˜‘ ์ง€์ˆ˜ = exp(-TTI/300) x ์œ„ํ˜‘์œ ์‚ฌ๋„^w_cls x (1 - w_spoof x ์Šคํ‘ธํ•‘์˜์‹ฌ) -> ์ž„๊ณ„๊ฐ’์œผ๋กœ 3๋‹จ๊ณ„ ํŒ์ •\"\"\"\n", " _, tti = geometry(df)\n", " t = tti.min(axis=1)\n", " p = clf.predict_proba(features(df)[FEATS])[:, 1]\n", " spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > 2.0\n", " idx = np.exp(-t / 300) * p ** W[\"w_cls\"] * (1 - W[\"w_spoof\"] * spoof)\n", " return np.select([idx >= W[\"th_urgent\"], idx >= W[\"th_watch\"]], [\"๊ธด๊ธ‰\", \"์ฃผ์˜\"], \"์ผ๋ฐ˜\")\n", "\n", "\n", "def acc(df, clf, W):\n", " return (judge(df, clf, W) == truth(df)).mean()\n", "\n", "\n", "clf = train_classifier()\n", "W0 = {\"w_cls\": 0.0, \"w_spoof\": 0.0, \"th_urgent\": 0.37, \"th_watch\": 0.05} # ์ดˆ๊ธฐ: ๋„๋‹ฌ์‹œ๊ฐ„๋งŒ ๋ฐ˜์˜\n", "aar = make_raid(7, 80, spoof=6) # AAR ๋Œ€์ƒ ์ƒํ™ฉ\n", "pred0, y = judge(aar, clf, W0), truth(aar)\n", "err = aar[pred0 != y].assign(pred=pred0[pred0 != y], true=y[pred0 != y])\n", "\n", "\n", "def cause(r):\n", " if r.type == \"spoof\":\n", " return \"์Šคํ‘ธํ•‘ ํ•ญ์  ๋ฏธ๊ฒฉ๋ฆฌ\"\n", " if r.type in (\"decoy\", \"bird\", \"civil\"):\n", " return \"๋น„์œ„ํ˜‘(๊ธฐ๋งŒ์ฒดยท์กฐ๋ฅ˜ยท๋ฏผํ•ญ๊ธฐ) ๊ณผ๋Œ€ ํŒ์ •\"\n", " return \"๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„ ์˜ค์ฐจ(๋„๋‹ฌ์‹œ๊ฐ„ ์ถ”์ •)\"\n", "\n", "\n", "err[\"์›์ธ\"] = [cause(r) for r in err.itertuples()]\n", "print(f\"[AAR] ์ดˆ๊ธฐ ๊ฐ€์ค‘์น˜ ์ •ํ™•๋„ {acc(aar, clf, W0):.1%}, ์˜ค๋ฅ˜ {len(err)}๊ฑด\")\n", "print(err[\"์›์ธ\"].value_counts().to_string())\n", "print(err[[\"id\", \"type\", \"true\", \"pred\", \"์›์ธ\"]].head(5).to_string(index=False))\n", "\n", "# ์žฌ๋ณด์ •: ํ•™์Šต์šฉ ๊ฐ€์ƒ ์ƒํ™ฉ 3๊ฐœ(์‹œํ—˜๊ณผ ๋‹ค๋ฅธ ์‹œ๋“œ)์—์„œ ๊ฒฉ์ž ํƒ์ƒ‰\n", "train = [make_raid(s, 80, spoof=6) for s in (201, 202, 203)]\n", "grid = itertools.product([0.0, 0.5, 1.0, 2.0, 4.0], [1.0, 0.0], [0.30, 0.37, 0.45], [0.03, 0.05, 0.08])\n", "cands = [{\"w_cls\": a, \"w_spoof\": b, \"th_urgent\": c, \"th_watch\": d} for a, b, c, d in grid]\n", "aar_idx = [i for i in np.where(pred0 != y)[0] if cause(aar.iloc[i]) != \"๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„ ์˜ค์ฐจ(๋„๋‹ฌ์‹œ๊ฐ„ ์ถ”์ •)\"]\n", "\n", "\n", "def recurrence(W):\n", " p = judge(aar, clf, W)\n", " return int(sum(p[i] != y[i] for i in aar_idx))\n", "\n", "\n", "score = {i: np.mean([acc(r, clf, W) for r in train]) for i, W in enumerate(cands)}\n", "top = max(score.values())\n", "# ์—ญํ™˜๋ฅ˜: ํ•™์Šต ์ •ํ™•๋„ ์ตœ๊ณ ์น˜์™€ 0.5%p ์ด๋‚ด์ธ ํ›„๋ณด ์ค‘ AAR ์ง€์  ์˜ค๋ฅ˜ ์žฌ๋ฐœ์ด ๊ฐ€์žฅ ์ ์€ ๊ฐ€์ค‘์น˜๋ฅผ ์ฑ„ํƒ\n", "best = min((W for i, W in enumerate(cands) if score[i] >= top - 0.005), key=lambda W: (recurrence(W), -np.mean([acc(r, clf, W) for r in train])))\n", "tests = [make_raid(s, 80, spoof=6) for s in (8, 9, 10)]\n", "a0 = np.mean([acc(r, clf, W0) for r in tests]); a1 = np.mean([acc(r, clf, best) for r in tests])\n", "pred1 = judge(aar, clf, best)\n", "recur = recurrence(best)\n", "boundary_left = int(sum((pred1[i] != y[i]) for i in np.where(pred0 != y)[0]))\n", "print(\"\\n[์žฌ๋ณด์ • ๊ฒฐ๊ณผ] (์‹œํ—˜ ์ƒํ™ฉ 3๊ฐœ ํ‰๊ท , ํ•™์Šต์— ์“ฐ์ง€ ์•Š์€ ์ƒํ™ฉ)\")\n", "print(f\" ์ข…ํ•ฉ ์ •ํ™•๋„ {a0:.1%} -> {a1:.1%} ({(a1 - a0) * 100:+.1f}%p, ๋ชฉํ‘œ +10%p ์ด์ƒ) -> {'๋‹ฌ์„ฑ' if a1 - a0 >= 0.10 else '๋ฏธ๋‹ฌ'}\")\n", "print(f\" AAR ๋Œ€์ƒ ์ƒํ™ฉ ์ •ํ™•๋„ {acc(aar, clf, W0):.1%} -> {acc(aar, clf, best):.1%}\")\n", "print(f\" AAR ์ง€์  ์˜ค๋ฅ˜(๊ธฐ๋งŒยท์Šคํ‘ธํ•‘ยท๋น„์œ„ํ˜‘) ์žฌ๋ฐœ {recur}๊ฑด (๋ชฉํ‘œ 0๊ฑด) -> {'๋‹ฌ์„ฑ' if recur == 0 else '๋ฏธ๋‹ฌ'}, ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„ ์˜ค๋ฅ˜ ํฌํ•จ ์ž”์—ฌ {boundary_left}๊ฑด\")\n", "for i in aar_idx:\n", " if pred1[i] != y[i]:\n", " r = aar.iloc[i]\n", " print(f\" ์žฌ๋ฐœ ์‚ฌ๋ก€ {r.id}({r.type}): ์ž์‚ฐ ๊ทผ์ ‘(๋„๋‹ฌ {geometry(aar.iloc[[i]])[1].min():.0f}s)์œผ๋กœ ์ง€์ˆ˜๊ฐ€ '์ฃผ์˜' ์ž„๊ณ„๋ฅผ ๋„˜์Œ -> ๊ทผ๊ฑฐ๋ฆฌ ๋ฏธํ™•์ธ ํ•ญ์ ์€ ์šด์šฉ์ž ์œก์•ˆยท์ถ”๊ฐ€ ์„ผ์„œ ํ™•์ธ์œผ๋กœ ์ฒ˜๋ฆฌ\")\n", "print(pd.crosstab(pd.Series(truth(tests[0]), name=\"true\"), pd.Series(judge(tests[0], clf, best), name=\"pred\")).to_string())\n", "print(\"\\n# configs/ops04_tuned_params.yaml\")\n", "print(f\"\"\"threat_matrix:\n", " w_cls: {best['w_cls']} # ์œ„ํ˜‘ ์œ ์‚ฌ๋„ ๋ฐ˜์˜ ์ง€์ˆ˜ (์ดˆ๊ธฐ 0.0)\n", " w_spoof: {best['w_spoof']} # ์Šคํ‘ธํ•‘ ์˜์‹ฌ ๊ฐ์  (์ดˆ๊ธฐ 0.0)\n", " th_urgent: {best['th_urgent']} # ๊ธด๊ธ‰ ์ž„๊ณ„ (exp(-TTI/300) ๊ธฐ์ค€)\n", " th_watch: {best['th_watch']} # ์ฃผ์˜ ์ž„๊ณ„\n", "evaluation:\n", " baseline_acc: {a0:.3f}\n", " tuned_acc: {a1:.3f}\n", "note: \"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์ถ”์ƒ ์œ„ํ˜‘ ์ง€์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜์ด๋ฉฐ ์‹ค์ œ ๋ฌด๊ธฐ ์ œ์›๊ณผ ๋ฌด๊ด€\"\n", "\"\"\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 09 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[AAR] ์ดˆ๊ธฐ ๊ฐ€์ค‘์น˜ ์ •ํ™•๋„ 75.6%, ์˜ค๋ฅ˜ 21๊ฑด\n", "์›์ธ\n", "๋น„์œ„ํ˜‘(๊ธฐ๋งŒ์ฒดยท์กฐ๋ฅ˜ยท๋ฏผํ•ญ๊ธฐ) ๊ณผ๋Œ€ ํŒ์ • 13\n", "์Šคํ‘ธํ•‘ ํ•ญ์  ๋ฏธ๊ฒฉ๋ฆฌ 5\n", "๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„ ์˜ค์ฐจ(๋„๋‹ฌ์‹œ๊ฐ„ ์ถ”์ •) 3\n", " id type true pred ์›์ธ\n", "T07002 uav_type ์ฃผ์˜ ๊ธด๊ธ‰ ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„ ์˜ค์ฐจ(๋„๋‹ฌ์‹œ๊ฐ„ ์ถ”์ •)\n", "T07005 uav_type ์ฃผ์˜ ๊ธด๊ธ‰ ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„ ์˜ค์ฐจ(๋„๋‹ฌ์‹œ๊ฐ„ ์ถ”์ •)\n", "T07010 decoy ์ผ๋ฐ˜ ์ฃผ์˜ ๋น„์œ„ํ˜‘(๊ธฐ๋งŒ์ฒดยท์กฐ๋ฅ˜ยท๋ฏผํ•ญ๊ธฐ) ๊ณผ๋Œ€ ํŒ์ •\n", "T07020 decoy ์ผ๋ฐ˜ ์ฃผ์˜ ๋น„์œ„ํ˜‘(๊ธฐ๋งŒ์ฒดยท์กฐ๋ฅ˜ยท๋ฏผํ•ญ๊ธฐ) ๊ณผ๋Œ€ ํŒ์ •\n", "T07021 decoy ์ผ๋ฐ˜ ์ฃผ์˜ ๋น„์œ„ํ˜‘(๊ธฐ๋งŒ์ฒดยท์กฐ๋ฅ˜ยท๋ฏผํ•ญ๊ธฐ) ๊ณผ๋Œ€ ํŒ์ •\n", "\n", "[์žฌ๋ณด์ • ๊ฒฐ๊ณผ] (์‹œํ—˜ ์ƒํ™ฉ 3๊ฐœ ํ‰๊ท , ํ•™์Šต์— ์“ฐ์ง€ ์•Š์€ ์ƒํ™ฉ)\n", " ์ข…ํ•ฉ ์ •ํ™•๋„ 84.9% -> 95.3% (+10.5%p, ๋ชฉํ‘œ +10%p ์ด์ƒ) -> ๋‹ฌ์„ฑ\n", " AAR ๋Œ€์ƒ ์ƒํ™ฉ ์ •ํ™•๋„ 75.6% -> 95.3%\n", " AAR ์ง€์  ์˜ค๋ฅ˜(๊ธฐ๋งŒยท์Šคํ‘ธํ•‘ยท๋น„์œ„ํ˜‘) ์žฌ๋ฐœ 1๊ฑด (๋ชฉํ‘œ 0๊ฑด) -> ๋ฏธ๋‹ฌ, ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„ ์˜ค๋ฅ˜ ํฌํ•จ ์ž”์—ฌ 3๊ฑด\n", " ์žฌ๋ฐœ ์‚ฌ๋ก€ T07061(decoy): ์ž์‚ฐ ๊ทผ์ ‘(๋„๋‹ฌ 42s)์œผ๋กœ ์ง€์ˆ˜๊ฐ€ '์ฃผ์˜' ์ž„๊ณ„๋ฅผ ๋„˜์Œ -> ๊ทผ๊ฑฐ๋ฆฌ ๋ฏธํ™•์ธ ํ•ญ์ ์€ ์šด์šฉ์ž ์œก์•ˆยท์ถ”๊ฐ€ ์„ผ์„œ ํ™•์ธ์œผ๋กœ ์ฒ˜๋ฆฌ\n", "pred ๊ธด๊ธ‰ ์ผ๋ฐ˜ ์ฃผ์˜\n", "true \n", "๊ธด๊ธ‰ 18 0 2\n", "์ผ๋ฐ˜ 0 46 1\n", "์ฃผ์˜ 2 0 17\n", "\n", "# configs/ops04_tuned_params.yaml\n", "threat_matrix:\n", " w_cls: 1.0 # ์œ„ํ˜‘ ์œ ์‚ฌ๋„ ๋ฐ˜์˜ ์ง€์ˆ˜ (์ดˆ๊ธฐ 0.0)\n", " w_spoof: 1.0 # ์Šคํ‘ธํ•‘ ์˜์‹ฌ ๊ฐ์  (์ดˆ๊ธฐ 0.0)\n", " th_urgent: 0.37 # ๊ธด๊ธ‰ ์ž„๊ณ„ (exp(-TTI/300) ๊ธฐ์ค€)\n", " th_watch: 0.05 # ์ฃผ์˜ ์ž„๊ณ„\n", "evaluation:\n", " baseline_acc: 0.849\n", " tuned_acc: 0.953\n", "note: \"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์ถ”์ƒ ์œ„ํ˜‘ ์ง€์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜์ด๋ฉฐ ์‹ค์ œ ๋ฌด๊ธฐ ์ œ์›๊ณผ ๋ฌด๊ด€\"\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 10. ์ƒคํ—ค๋“œยท๋ฏธ์‚ฌ์ผ ํฌํ™”๊ฒฝ๋ณด ์šฐ์„ ์ˆœ์œ„ ์‹œ์Šคํ…œ ์ตœ์ข… ํ†ตํ•ฉ ๋ฐ ๋ฐฉ๊ณต์ž‘์ „ ๋ธŒ๋ฆฌํ•‘ ํŒจํ‚ค์ง€ ์™„์„ฑ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ƒคํ—ค๋“œยท๋ฏธ์‚ฌ์ผ ํฌํ™”๊ฒฝ๋ณด ์šฐ์„ ์ˆœ์œ„ ์‹œ์Šคํ…œ์„ ์ตœ์ข… ํ†ตํ•ฉํ•˜๊ณ  ์ง€ํœ˜๊ด€ ๊ฒฐ์‹ฌ ์ง€์›์šฉ ์‹ค์ „ ๋ธŒ๋ฆฌํ•‘ ํŒจํ‚ค์ง€์™€ ์šด์˜ ๋งค๋‰ด์–ผ์„ ์™„์„ฑํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ์„ ํ†ตํ•ฉํ•ด ์‹คํ–‰ยท์ž์ฒด ์ ๊ฒ€ํ•˜๊ณ  ์ง€ํœ˜๊ด€ ๊ฒฐ์‹ฌ ์ง€์›์šฉ ๋ธŒ๋ฆฌํ•‘ ํŒจํ‚ค์ง€๋ฅผ ์™„์„ฑํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ฒ€์ฆยท์ „์ฒ˜๋ฆฌ, ๋‹ค์ฐจ์› ํ‰๊ฐ€(์žฌ๋ณด์ •๊ฐ’), ์‹ ๋ขฐ๋„ ๋“ฑ๊ธ‰, ๊ถŒ๊ณ  ์ˆœ์œ„, ์Šน์ธ ๋Œ€๊ธฐ์—ด์„ ํ•œ ํŒŒ์ผ๋กœ ๋ฌถ์–ด ๊ฐ€์ƒ ์ƒํ™ฉ 5๊ฐœ๋ฅผ ์ฒ˜๋ฆฌ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/main_ops04.py, README.md, docs/ops04_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": [ "\"\"\"๋ฉ”์‹œ์ง€ ๊ฒ€์ฆยท์ „์ฒ˜๋ฆฌ, ๋‹ค์ฐจ์› ์œ„ํ˜‘ ํ‰๊ฐ€(์žฌ๋ณด์ • ๊ฐ€์ค‘์น˜), ์‹ ๋ขฐ๋„ ๋“ฑ๊ธ‰, ๊ถŒ๊ณ  ๋Œ€์‘ ์ˆœ์œ„, ์ง€ํœ˜๊ด€ ์Šน์ธ ๋Œ€๊ธฐ์—ด, ๋Œ€์‹œ๋ณด๋“œ๊นŒ์ง€ ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ์„ ํ•œ ํŒŒ์ผ๋กœ ํ†ตํ•ฉ ์‹คํ–‰ํ•˜๊ณ , ์ž์ฒด ์ ๊ฒ€(ํ•จ์ˆ˜ ์ปค๋ฒ„๋ฆฌ์ง€ ํฌํ•จ) ๊ฒฐ๊ณผ์™€ ๋ฐฉ๊ณต์ž‘์ „ ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ์„ ์ถœ๋ ฅํ•œ๋‹ค.\"\"\"\n", "import hashlib\n", "import json\n", "import sys\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋ฐฉ์–ด์ง€์—ญยท๊ฐ€์ƒ ๋น„ํ–‰์ฒด ์ƒ์„ฑ๊ธฐ ----------------\n", "# ์ขŒํ‘œ๋Š” ๊ฐ€์ƒ ์ง€ํœ˜์†Œ๋ฅผ ์›์ ์œผ๋กœ ํ•œ km ๋‹จ์œ„ ํ‰๋ฉด์ด๋‹ค. ๋น„ํ–‰์ฒด ์œ ํ˜•์€ ์ถ”์ƒ ๋ฒ”์ฃผ์ด๋ฉฐ\n", "# ์†๋„ยท๊ณ ๋„ยท๋ฐ˜์‚ฌ๊ฐ•๋„๋Š” ํ•™์Šต์šฉ ์ž„์˜ ๋ฒ”์œ„๋กœ, ์‹ค์ œ ๋ฌด๊ธฐ ์ œ์›ยท์‚ฌ๊ฑฐ๋ฆฌยทํƒ„๋„์™€ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "ASSETS = pd.DataFrame([[\"A1\", \"CP(virtual)\", 0.0, 0.0, 1.0],\n", " [\"A2\", \"Radar(virtual)\", 4.0, -3.0, 0.8],\n", " [\"A3\", \"Depot(virtual)\", -5.0, 4.0, 0.7],\n", " [\"A4\", \"Power(virtual)\", 7.0, 6.0, 0.5]], columns=[\"asset\", \"name\", \"x\", \"y\", \"value\"])\n", "TYPES = { # ์œ ํ˜•: (์œ„ํ˜‘ ์—ฌ๋ถ€, ์†๋„ m/s ๋ฒ”์œ„, ๊ณ ๋„ m ๋ฒ”์œ„, ์ถœํ˜„ ๋น„์œจ)\n", " \"uav_type\": (True, (30, 60), (100, 800), 0.42), # ์ €์† ๋ฌด์ธ๊ธฐํ˜•(๊ฐ€์ƒ)\n", " \"fast_type\": (True, (150, 250), (50, 200), 0.16), # ๊ณ ์† ์ˆœํ•ญํ˜•(๊ฐ€์ƒ)\n", " \"decoy\": (False, (30, 60), (100, 800), 0.16), # ๊ธฐ๋งŒ์ฒด(๊ฐ€์ƒ)\n", " \"civil\": (False, (120, 220), (3000, 9000), 0.10), # ๋ฏผํ•ญ๊ธฐ(๊ฐ€์ƒ)\n", " \"bird\": (False, (8, 18), (50, 400), 0.16), # ์กฐ๋ฅ˜ ๊ตฐ์ง‘\n", "}\n", "TTI_URGENT = 600 # ๊ธด๊ธ‰ ํŒ์ • ๊ธฐ์ค€: ๊ฐ€์ƒ ์ž์‚ฐ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„ 600์ดˆ ์ด๋‚ด\n", "CPA_KM = 1.5 # ์ž์‚ฐ ์ƒ๊ณต ํ†ต๊ณผ ํŒ์ • ๋ฐ˜๊ฒฝ(๊ฐ€์ƒ)\n", "\n", "\n", "def make_raid(seed, n=80, spoof=0):\n", " \"\"\"๊ฐ€์ƒ ํฌํ™” ์ƒํ™ฉ 1ํšŒ๋ถ„(๋น„ํ–‰์ฒด n๊ฐœ + ์Šคํ‘ธํ•‘ ๊ฐ€์งœ ํ•ญ์  spoof๊ฐœ)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " names = list(TYPES)\n", " rows = []\n", " for i in range(n + spoof):\n", " typ = \"spoof\" if i >= n else str(rng.choice(names, p=[TYPES[k][3] for k in names]))\n", " threat, sp, al, _ = TYPES[\"decoy\" if typ == \"spoof\" else typ]\n", " brg = np.radians(rng.uniform(0, 360)); dist = rng.uniform(6, 60)\n", " x, y = dist * np.sin(brg), dist * np.cos(brg)\n", " spd = rng.uniform(*sp); alt = rng.uniform(*al)\n", " aim = int(rng.integers(0, len(ASSETS)))\n", " if typ in (\"uav_type\", \"fast_type\", \"decoy\", \"spoof\"): # ๊ฐ€์ƒ ์ž์‚ฐ ๋ฐฉํ–ฅ(์ž‘์€ ๋ฐฉ์œ„ ์˜ค์ฐจ)\n", " ax_, ay_ = ASSETS.loc[aim, [\"x\", \"y\"]]\n", " hd = np.arctan2(ax_ - x, ay_ - y) + np.radians(rng.normal(0, 0.6))\n", " else:\n", " hd = rng.uniform(0, 2 * np.pi); aim = -1\n", " vx, vy = spd * np.sin(hd) / 1000, spd * np.cos(hd) / 1000 # km/s\n", " sig = {\"uav_type\": ((0.2, 0.5), (0.02, 0.12), [0, .1, .5, .4]), \"fast_type\": ((0.1, 0.4), (0.02, 0.12), [0, .2, .4, .4]),\n", " \"decoy\": ((0.3, 0.8), (0.08, 0.35), [0, .55, .4, .05]), \"spoof\": ((0.3, 0.8), (0.05, 0.3), [0, .7, .3, 0]),\n", " \"civil\": ((0.7, 1.0), (0.01, 0.05), [0, 0, .1, .9]), \"bird\": ((0.01, 0.12), (0.1, 0.4), [0, .6, .4, 0])}[typ]\n", " jump = spd * rng.uniform(0.95, 1.2) if typ != \"spoof\" else spd * rng.uniform(4, 15)\n", " true_tti = np.inf\n", " if threat:\n", " d_aim = np.hypot(ASSETS.loc[aim, \"x\"] - x, ASSETS.loc[aim, \"y\"] - y)\n", " true_tti = d_aim * 1000 / spd\n", " rows.append({\"id\": f\"T{seed % 100:02d}{i:03d}\", \"type\": typ, \"x\": x, \"y\": y, \"vx\": vx, \"vy\": vy, \"alt\": alt, \"spd\": spd,\n", " \"rcs\": rng.uniform(*sig[0]), \"rcs_var\": rng.uniform(*sig[1]), \"n_sensors\": int(rng.choice(4, p=sig[2])),\n", " \"max_jump\": jump, \"civ_iff\": int(typ == \"civil\" and rng.random() < 0.95),\n", " \"aim\": aim, \"threat\": threat, \"true_tti\": true_tti,\n", " \"urgent\": bool(threat and true_tti <= TTI_URGENT)})\n", " df = pd.DataFrame(rows)\n", " df[\"true_prio\"] = [ASSETS.loc[a, \"value\"] * np.exp(-t / 300) if th else 0.0\n", " for a, t, th in zip(df[\"aim\"], df[\"true_tti\"], df[\"threat\"])]\n", " return df\n", "\n", "\n", "def geometry(df):\n", " \"\"\"๊ฐ ํ•ญ์ ๊ณผ ๊ฐ€์ƒ ์ž์‚ฐ ์‚ฌ์ด ์ตœ๊ทผ์ ‘์ (CPA) ๊ฑฐ๋ฆฌ์™€ ๋„๋‹ฌ ์˜ˆ์ƒ์‹œ๊ฐ„(TTI, s)์„ ๊ณ„์‚ฐํ•œ๋‹ค.\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 10] ์ƒคํ—ค๋“œยท๋ฏธ์‚ฌ์ผ ํฌํ™”๊ฒฝ๋ณด ์šฐ์„ ์ˆœ์œ„ ์‹œ์Šคํ…œ ์ตœ์ข… ํ†ตํ•ฉ ๋ฐ ๋ฐฉ๊ณต์ž‘์ „ ๋ธŒ๋ฆฌํ•‘ ํŒจํ‚ค์ง€ ์™„์„ฑ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "FEATS = [\"log_spd\", \"alt_km\", \"rcs\", \"rcs_var\", \"n_sensors\", \"civ_iff\", \"jump_ratio\"]\n", "\n", "\n", "def features(df):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 10] ์ƒคํ—ค๋“œยท๋ฏธ์‚ฌ์ผ ํฌํ™”๊ฒฝ๋ณด ์šฐ์„ ์ˆœ์œ„ ์‹œ์Šคํ…œ ์ตœ์ข… ํ†ตํ•ฉ ๋ฐ ๋ฐฉ๊ณต์ž‘์ „ ๋ธŒ๋ฆฌํ•‘ ํŒจํ‚ค์ง€ ์™„์„ฑ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def train_classifier(seeds=(101, 102, 103, 104)):\n", " \"\"\"ํ•™์Šต์šฉ ๊ฐ€์ƒ ์ƒํ™ฉ(์‹œํ—˜ ์ƒํ™ฉ๊ณผ ๋‹ค๋ฅธ ์‹œ๋“œ)์œผ๋กœ '์œ„ํ˜‘ ์œ ์‚ฌ๋„' ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ํ•™์Šตํ•œ๋‹ค.\"\"\"\n", " tr = pd.concat([make_raid(s, 120, spoof=10) for s in seeds], ignore_index=True)\n", " clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500, C=1.0))\n", " clf.fit(features(tr)[FEATS], tr[\"threat\"].astype(int))\n", " return clf\n", "\n", "\n", "def baseline_rank(df):\n", " \"\"\"Baseline: ๊ฑฐ๋ฆฌยท๋„๋‹ฌ์‹œ๊ฐ„๋งŒ์œผ๋กœ 1์ฐจ ์œ„ํ˜‘ ์ ์ˆ˜๋ฅผ ๋งค๊ธด๋‹ค(์ž์‚ฐ ๊ฐ€์น˜ยท์œ ํ˜• ํŒ๋ณ„ ์—†์Œ).\"\"\"\n", " _, tti = geometry(df)\n", " t_min = tti.min(axis=1)\n", " out = df[[\"id\"]].copy()\n", " out[\"tti_s\"] = t_min\n", " out[\"score\"] = np.exp(-t_min / 300)\n", " out[\"urgent_pred\"] = t_min <= TTI_URGENT\n", " return out.sort_values(\"score\", ascending=False)\n", "\n", "\n", "def advanced_rank(df, clf, w=None):\n", " \"\"\"๊ณ ๋„ํ™”: ์ž์‚ฐ ๊ฐ€์น˜ x ๋„๋‹ฌ์‹œ๊ฐ„ x ์œ„ํ˜‘ ์œ ์‚ฌ๋„, ์šด๋™ํ•™ ๋ถˆ์ผ์น˜(์Šคํ‘ธํ•‘ ์˜์‹ฌ) ํ•ญ์ ์€ ๋ณ„๋„ ํ‘œ์‹œํ•œ๋‹ค.\"\"\"\n", " w = w or {\"tau\": 300, \"p_min\": 0.5, \"value_pow\": 1.0, \"jump_max\": 2.0}\n", " _, tti = geometry(df)\n", " val = ASSETS[\"value\"].to_numpy()[None] ** w[\"value_pow\"]\n", " asset_score = val * np.exp(-tti / w[\"tau\"])\n", " best = asset_score.argmax(axis=1)\n", " p = clf.predict_proba(features(df)[FEATS])[:, 1]\n", " spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > w[\"jump_max\"]\n", " out = df[[\"id\"]].copy()\n", " out[\"asset\"] = ASSETS[\"asset\"].to_numpy()[best]\n", " out[\"tti_s\"] = tti[np.arange(len(df)), best]\n", " out[\"p_threat\"] = p\n", " out[\"spoof_flag\"] = spoof\n", " out[\"score\"] = np.where(spoof, 0, p * asset_score.max(axis=1))\n", " out[\"urgent_pred\"] = (p >= w[\"p_min\"]) & (out[\"tti_s\"] <= TTI_URGENT) & ~spoof\n", " return out.sort_values(\"score\", ascending=False)\n", "\n", "CONFIG = {\"w_cls\": 1.0, \"w_spoof\": 1.0, \"th_urgent\": 0.37, \"th_watch\": 0.05, \"budget_ms\": 200} # ๋ฏธ์…˜ 9 ์žฌ๋ณด์ •๊ฐ’\n", "\n", "\n", "def validate(df):\n", " ok = df[\"spd\"].between(0.1, 400) & df[\"alt\"].between(0, 15000) & df[[\"x\", \"y\"]].abs().le(100).all(axis=1)\n", " return df[ok], df[~ok]\n", "\n", "\n", "def preprocess(df):\n", " good, bad = validate(df)\n", " return good.drop_duplicates(\"id\").reset_index(drop=True), bad\n", "\n", "\n", "def assess(df, clf):\n", " _, tti = geometry(df)\n", " best = (ASSETS[\"value\"].to_numpy()[None] * np.exp(-tti / 300)).argmax(axis=1)\n", " t = tti[np.arange(len(df)), best]\n", " p = clf.predict_proba(features(df)[FEATS])[:, 1]\n", " spoof = (df[\"max_jump\"] / df[\"spd\"]).to_numpy() > 2.0\n", " idx = np.exp(-t / 300) * p ** CONFIG[\"w_cls\"] * (1 - CONFIG[\"w_spoof\"] * spoof)\n", " lvl = np.select([idx >= CONFIG[\"th_urgent\"], idx >= CONFIG[\"th_watch\"]], [\"๊ธด๊ธ‰\", \"์ฃผ์˜\"], \"์ผ๋ฐ˜\")\n", " conf = 0.5 * p + 0.2 * df[\"n_sensors\"].to_numpy() / 3 + 0.15 * (1 - np.clip(df[\"rcs_var\"].to_numpy() / 0.4, 0, 1)) + 0.15 * (~spoof)\n", " grade = np.select([conf >= 0.7, conf >= 0.45], [\"High\", \"Med\"], \"Low\")\n", " return df.assign(asset=ASSETS[\"asset\"].to_numpy()[best], tti_s=t, p=p, spoof_flag=spoof, index_=idx, level=lvl, conf=conf, grade=grade)\n", "\n", "\n", "def recommend(a):\n", " rec = a[a[\"level\"] != \"์ผ๋ฐ˜\"].sort_values(\"index_\", ascending=False).reset_index(drop=True)\n", " rec[\"๊ถŒ๊ณ ์ˆœ์œ„\"] = np.arange(1, len(rec) + 1)\n", " rec[\"์ƒํƒœ\"] = \"์Šน์ธ ๋Œ€๊ธฐ\" # ์ž๋™ ์‹คํ–‰ยท์ž๋™ ์Šน์ธ ์—†์Œ\n", " return rec\n", "\n", "\n", "class Queue:\n", " def __init__(self):\n", " self.items, self.log = {}, []\n", "\n", " def submit(self, r):\n", " self.items[r.id] = \"PENDING\"\n", " prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n", " body = json.dumps({\"id\": r.id, \"rank\": int(r.๊ถŒ๊ณ ์ˆœ์œ„), \"by\": \"AI\"}, sort_keys=True)\n", " self.log.append({\"body\": body, \"hash\": hashlib.sha256((prev + body).encode()).hexdigest()})\n", "\n", "\n", "def run(seed, clf):\n", " raw = make_raid(seed, 120, spoof=8)\n", " rng = np.random.default_rng(seed)\n", " raw.loc[rng.choice(len(raw), 4, replace=False), \"spd\"] = np.nan # ์†์ƒ ๋ฉ”์‹œ์ง€ ์ฃผ์ž…\n", " raw = pd.concat([raw, raw.sample(10, random_state=seed)]) # ์ค‘๋ณต ์ฃผ์ž…\n", " t0 = time.perf_counter()\n", " df, bad = preprocess(raw)\n", " a = assess(df, clf)\n", " rec = recommend(a)\n", " q = Queue()\n", " for r in rec.itertuples():\n", " q.submit(r)\n", " return {\"raw\": raw, \"df\": df, \"bad\": bad, \"a\": a, \"rec\": rec, \"q\": q, \"ms\": (time.perf_counter() - t0) * 1000}\n", "\n", "\n", "# ---------------- 1. ํ†ตํ•ฉ ์‹คํ–‰ + ํ•จ์ˆ˜ ์ปค๋ฒ„๋ฆฌ์ง€ ์ธก์ • ----------------\n", "called = set()\n", "mine = {\"make_raid\", \"geometry\", \"features\", \"train_classifier\", \"baseline_rank\", \"advanced_rank\", \"validate\", \"preprocess\",\n", " \"assess\", \"recommend\", \"submit\", \"run\"}\n", "sys.setprofile(lambda f, e, a: called.add(f.f_code.co_name) if e == \"call\" and f.f_code.co_name in mine else None)\n", "t_start = time.perf_counter()\n", "clf = train_classifier()\n", "results, fails = [], 0\n", "for seed in (8, 9, 10, 11, 12):\n", " try:\n", " results.append(run(seed, clf))\n", " except Exception as e:\n", " fails += 1; print(\"์‹คํ–‰ ์‹คํŒจ:\", seed, e)\n", "base_chk = baseline_rank(results[0][\"df\"]) # ๋น„๊ต ๊ธฐ์ค€(Baseline) ๊ฒฝ๋กœ๋„ ์ ๊ฒ€์— ํฌํ•จ\n", "adv_chk = advanced_rank(results[0][\"df\"], clf)\n", "sys.setprofile(None)\n", "cov = len(called & mine) / len(mine)\n", "\n", "# ---------------- 2. ์„ฑ๋Šฅ ์ง‘๊ณ„ ----------------\n", "acc, rec_u, far = [], [], []\n", "for R in results:\n", " a = R[\"a\"]\n", " y = np.select([a[\"threat\"] & (a[\"true_tti\"] <= 300), a[\"threat\"] & (a[\"true_tti\"] <= 900)], [\"๊ธด๊ธ‰\", \"์ฃผ์˜\"], \"์ผ๋ฐ˜\")\n", " acc.append((a[\"level\"] == y).mean())\n", " rec_u.append(((a[\"level\"] != \"์ผ๋ฐ˜\") & (y == \"๊ธด๊ธ‰\")).sum() / max((y == \"๊ธด๊ธ‰\").sum(), 1))\n", " far.append((~a[\"threat\"] & (a[\"level\"] != \"์ผ๋ฐ˜\")).sum() / max((a[\"level\"] != \"์ผ๋ฐ˜\").sum(), 1))\n", "R = results[0]\n", "print(f\"[ํ†ตํ•ฉ ์‹คํ–‰] ์ƒํ™ฉ {len(results) + fails}๊ฐœ ์ค‘ ์„ฑ๊ณต {len(results)}๊ฐœ, ํ‰๊ท  ์ฒ˜๋ฆฌ {np.mean([r['ms'] for r in results]):.1f} ms\")\n", "print(f\" ์ƒํ™ฉ 1: ์ž…๋ ฅ {len(R['raw'])}๊ฑด -> ์†์ƒ ๊ฒฉ๋ฆฌ {len(R['bad'])}๊ฑด, ์ค‘๋ณต ์ œ๊ฑฐ {len(R['raw']) - len(R['bad']) - len(R['df'])}๊ฑด, ํ‰๊ฐ€ {len(R['df'])}๊ฑด\")\n", "print(f\" ์šฐ์„ ์ˆœ์œ„ ํŒ์ • ์ •ํ™•๋„ {np.mean(acc):.1%}, ๊ธด๊ธ‰ ํ‘œ์  ๊ฒฝ๋ณด ์žฌํ˜„์œจ {np.mean(rec_u):.1%}, ๋น„์œ„ํ˜‘ ๊ฒฝ๋ณด ๋น„์œจ {np.mean(far):.1%}\")\n", "print(\"\\n[์ƒํ™ฉ 1 ๊ถŒ๊ณ  ๋Œ€์‘ ์ˆœ์œ„ ์ƒ์œ„ 5] (๋ชจ๋‘ ์Šน์ธ ๋Œ€๊ธฐ)\")\n", "print(R[\"rec\"].head(5)[[\"๊ถŒ๊ณ ์ˆœ์œ„\", \"id\", \"asset\", \"tti_s\", \"level\", \"grade\", \"์ƒํƒœ\"]].round({\"tti_s\": 0}).to_string(index=False))\n", "\n", "# ---------------- 3. ์ž์ฒด ์ ๊ฒ€ ----------------\n", "chk = {\n", " \"E2E ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰ ์„ฑ๊ณต๋ฅ  100%\": fails == 0,\n", " \"์†์ƒ ๋ฉ”์‹œ์ง€ ์ „๋Ÿ‰ ๊ฒฉ๋ฆฌ\": all(r[\"bad\"][\"spd\"].isna().all() and r[\"bad\"][\"id\"].nunique() == 4 for r in results),\n", " \"์Šคํ‘ธํ•‘ ์˜์‹ฌ ํ•ญ์  ๊ฒฝ๋ณด 0๊ฑด\": all(((r[\"a\"][\"spoof_flag\"]) & (r[\"a\"][\"level\"] != \"์ผ๋ฐ˜\")).sum() == 0 for r in results),\n", " \"์ž๋™ ์Šน์ธ 0๊ฑด(์ „๋ถ€ ์Šน์ธ ๋Œ€๊ธฐ)\": all(set(r[\"q\"].items.values()) <= {\"PENDING\"} for r in results),\n", " \"๊ธด๊ธ‰ ํ‘œ์  ๊ฒฝ๋ณด ์žฌํ˜„์œจ 90% ์ด์ƒ\": np.mean(rec_u) >= 0.9,\n", " \"์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ์˜ˆ์‚ฐ ์ด๋‚ด\": max(r[\"ms\"] for r in results) <= CONFIG[\"budget_ms\"],\n", " \"ํ•จ์ˆ˜ ์ปค๋ฒ„๋ฆฌ์ง€ 85% ์ด์ƒ\": cov >= 0.85,\n", "}\n", "passed = sum(chk.values())\n", "for k, v in chk.items():\n", " print(f\"[{'PASS' if v else 'FAIL'}] {k}\")\n", "print(f\"์ž์ฒด ์ ๊ฒ€ {passed}/{len(chk)} ํ†ต๊ณผ, ํ•จ์ˆ˜ ์ปค๋ฒ„๋ฆฌ์ง€ {cov:.0%} (๋ชฉํ‘œ 85% ์ด์ƒ) -> {'๋‹ฌ์„ฑ' if cov >= 0.85 else '๋ฏธ๋‹ฌ'}\")\n", "\n", "# ---------------- 4. ๋ธŒ๋ฆฌํ•‘ ์ƒํ™ฉํŒ ----------------\n", "a = R[\"a\"]\n", "fig, ax = plt.subplots(1, 2, figsize=(12, 5))\n", "col = {\"๊ธด๊ธ‰\": \"red\", \"์ฃผ์˜\": \"orange\", \"์ผ๋ฐ˜\": \"lightgray\"}\n", "for lv, g in a.groupby(\"level\"):\n", " ax[0].scatter(g[\"x\"], g[\"y\"], s=14, c=col[lv], label={\"๊ธด๊ธ‰\": \"urgent\", \"์ฃผ์˜\": \"watch\", \"์ผ๋ฐ˜\": \"normal\"}[lv])\n", "ax[0].scatter(ASSETS[\"x\"], ASSETS[\"y\"], marker=\"^\", s=120, c=\"blue\", label=\"virtual assets\")\n", "ax[0].set_aspect(\"equal\"); ax[0].legend(fontsize=8); ax[0].set_title(\"Integrated picture (scenario 1)\")\n", "ax[1].axis(\"off\")\n", "ax[1].text(0, 0.9, f\"Accuracy {np.mean(acc):.1%}\\nUrgent recall {np.mean(rec_u):.1%}\\nPending recs {len(R['rec'])}\\n\"\n", " f\"Auto-approved 0\\nSelf-check {passed}/{len(chk)}\\nCoverage {cov:.0%}\", family=\"monospace\", fontsize=13, va=\"top\")\n", "plt.suptitle(\"OPS04 saturation alert system - training data only\"); plt.tight_layout(); plt.show()\n", "\n", "# ---------------- 5. ๋ฐฉ๊ณต์ž‘์ „ ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ ----------------\n", "lv = R[\"a\"][\"level\"].value_counts()\n", "print(f\"\"\"\n", "[ํฌํ™”๊ฒฝ๋ณด ์šฐ์„ ์ˆœ์œ„ ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ - ๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\n", "1. ์ƒํ™ฉ: ๊ฐ€์ƒ ๋น„ํ–‰์ฒด {len(R['df'])}๊ฑด ํ‰๊ฐ€, ๊ธด๊ธ‰ {lv.get('๊ธด๊ธ‰', 0)} / ์ฃผ์˜ {lv.get('์ฃผ์˜', 0)} / ์ผ๋ฐ˜ {lv.get('์ผ๋ฐ˜', 0)}\n", "2. ์„ฑ๋Šฅ: ํŒ์ • ์ •ํ™•๋„ {np.mean(acc):.1%}, ๊ธด๊ธ‰ ์žฌํ˜„์œจ {np.mean(rec_u):.1%}, ์ฒ˜๋ฆฌ {R['ms']:.1f} ms, ์ „์ฒด ์†Œ์š” {time.perf_counter() - t_start:.1f} s\n", "3. ๊ถŒ๊ณ : ๋Œ€์‘ ๊ถŒ๊ณ  {len(R['rec'])}๊ฑด ๋ชจ๋‘ ์ง€ํœ˜๊ด€ ์Šน์ธ ๋Œ€๊ธฐ, ์ž๋™ ์Šน์ธ 0๊ฑด\n", "4. ์šด์šฉ ์œ ์˜: ์ˆœ์œ„๋Š” ๊ถŒ๊ณ ์ด๋ฉฐ ๋Œ€์‘ ์—ฌ๋ถ€๋Š” ์ง€ํœ˜๊ด€ ์Šน์ธ๊ณผ ๊ต์ „์ˆ˜์น™ยท์ง€ํœ˜๊ณ„ํ†ต์— ๋”ฐ๋ฅธ๋‹ค.\n", "5. ํ•œ๊ณ„: ์ถ”์ƒ ์œ„ํ˜‘ ์ง€์ˆ˜์™€ ๊ฐ€์ƒ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ ๊ฒฐ๊ณผ๋กœ, ์‹ค์ œ ์šด์šฉ ์ „์—๋Š” ์‹ค์ œ ์„ผ์„œ ์ž๋ฃŒ๋กœ ์žฌ๊ฒ€์ฆํ•ด์•ผ ํ•œ๋‹ค.\"\"\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 10 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[ํ†ตํ•ฉ ์‹คํ–‰] ์ƒํ™ฉ 5๊ฐœ ์ค‘ ์„ฑ๊ณต 5๊ฐœ, ํ‰๊ท  ์ฒ˜๋ฆฌ 31.0 ms\n", " ์ƒํ™ฉ 1: ์ž…๋ ฅ 138๊ฑด -> ์†์ƒ ๊ฒฉ๋ฆฌ 4๊ฑด, ์ค‘๋ณต ์ œ๊ฑฐ 10๊ฑด, ํ‰๊ฐ€ 124๊ฑด\n", " ์šฐ์„ ์ˆœ์œ„ ํŒ์ • ์ •ํ™•๋„ 94.5%, ๊ธด๊ธ‰ ํ‘œ์  ๊ฒฝ๋ณด ์žฌํ˜„์œจ 100.0%, ๋น„์œ„ํ˜‘ ๊ฒฝ๋ณด ๋น„์œจ 3.3%\n", "\n", "[์ƒํ™ฉ 1 ๊ถŒ๊ณ  ๋Œ€์‘ ์ˆœ์œ„ ์ƒ์œ„ 5] (๋ชจ๋‘ ์Šน์ธ ๋Œ€๊ธฐ)\n", " ๊ถŒ๊ณ ์ˆœ์œ„ id asset tti_s level grade ์ƒํƒœ\n", " 1 T08115 A3 11.0 ๊ธด๊ธ‰ High ์Šน์ธ ๋Œ€๊ธฐ\n", " 2 T08017 A4 54.0 ๊ธด๊ธ‰ High ์Šน์ธ ๋Œ€๊ธฐ\n", " 3 T08094 A3 60.0 ๊ธด๊ธ‰ High ์Šน์ธ ๋Œ€๊ธฐ\n", " 4 T08119 A4 64.0 ๊ธด๊ธ‰ High ์Šน์ธ ๋Œ€๊ธฐ\n", " 5 T08015 A3 69.0 ๊ธด๊ธ‰ High ์Šน์ธ ๋Œ€๊ธฐ\n", "[PASS] E2E ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰ ์„ฑ๊ณต๋ฅ  100%\n", "[PASS] ์†์ƒ ๋ฉ”์‹œ์ง€ ์ „๋Ÿ‰ ๊ฒฉ๋ฆฌ\n", "[PASS] ์Šคํ‘ธํ•‘ ์˜์‹ฌ ํ•ญ์  ๊ฒฝ๋ณด 0๊ฑด\n", "[PASS] ์ž๋™ ์Šน์ธ 0๊ฑด(์ „๋ถ€ ์Šน์ธ ๋Œ€๊ธฐ)\n", "[PASS] ๊ธด๊ธ‰ ํ‘œ์  ๊ฒฝ๋ณด ์žฌํ˜„์œจ 90% ์ด์ƒ\n", "[PASS] ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ์˜ˆ์‚ฐ ์ด๋‚ด\n", "[PASS] ํ•จ์ˆ˜ ์ปค๋ฒ„๋ฆฌ์ง€ 85% ์ด์ƒ\n", "์ž์ฒด ์ ๊ฒ€ 7/7 ํ†ต๊ณผ, ํ•จ์ˆ˜ ์ปค๋ฒ„๋ฆฌ์ง€ 100% (๋ชฉํ‘œ 85% ์ด์ƒ) -> ๋‹ฌ์„ฑ\n", "\n", "[ํฌํ™”๊ฒฝ๋ณด ์šฐ์„ ์ˆœ์œ„ ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ - ๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\n", "1. ์ƒํ™ฉ: ๊ฐ€์ƒ ๋น„ํ–‰์ฒด 124๊ฑด ํ‰๊ฐ€, ๊ธด๊ธ‰ 30 / ์ฃผ์˜ 30 / ์ผ๋ฐ˜ 64\n", "2. ์„ฑ๋Šฅ: ํŒ์ • ์ •ํ™•๋„ 94.5%, ๊ธด๊ธ‰ ์žฌํ˜„์œจ 100.0%, ์ฒ˜๋ฆฌ 29.1 ms, ์ „์ฒด ์†Œ์š” 1.7 s\n", "3. ๊ถŒ๊ณ : ๋Œ€์‘ ๊ถŒ๊ณ  60๊ฑด ๋ชจ๋‘ ์ง€ํœ˜๊ด€ ์Šน์ธ ๋Œ€๊ธฐ, ์ž๋™ ์Šน์ธ 0๊ฑด\n", "4. ์šด์šฉ ์œ ์˜: ์ˆœ์œ„๋Š” ๊ถŒ๊ณ ์ด๋ฉฐ ๋Œ€์‘ ์—ฌ๋ถ€๋Š” ์ง€ํœ˜๊ด€ ์Šน์ธ๊ณผ ๊ต์ „์ˆ˜์น™ยท์ง€ํœ˜๊ณ„ํ†ต์— ๋”ฐ๋ฅธ๋‹ค.\n", "5. ํ•œ๊ณ„: ์ถ”์ƒ ์œ„ํ˜‘ ์ง€์ˆ˜์™€ ๊ฐ€์ƒ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ ๊ฒฐ๊ณผ๋กœ, ์‹ค์ œ ์šด์šฉ ์ „์—๋Š” ์‹ค์ œ ์„ผ์„œ ์ž๋ฃŒ๋กœ ์žฌ๊ฒ€์ฆํ•ด์•ผ ํ•œ๋‹ค.\n", "```\n", "
\n" ] } ], "metadata": { "colab": { "provenance": [] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }