{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# ๐ฏ [TRN01] ๋๋ก ์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ ๊ธฐ๋ฐ ์๋ จ๋ ๋ถ์๊ธฐ\n",
"### Google Colab ์ค์ต ํ๊ฒฝ (๊ต์กยทํ๋ จ / Level 3)\n",
"\n",
"[](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/TRN01/TRN01_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",
"๋ฌ์์-์ฐํฌ๋ผ์ด๋ ์ ์์์๋ ์ํ ๋๋ก ์กฐ์ข
์ธ๋ ฅ์ ์งง์ ๊ธฐ๊ฐ์ ๋๋์ผ๋ก ์์ฑํด์ผ ํ๊ณ , ์๋ฎฌ๋ ์ดํฐ ํ๋ จ์์ ๋๊ฐ ์ด๋ค ์กฐ์ ์ฝ์ ์ ๊ฐ์ง๊ณ ์๋์ง ๋น ๋ฅด๊ฒ ์ฐพ์ ๊ฐ์ธ๋ณ๋ก ๋ณด์ํ๋ ์ผ์ด ์ ํฌ๋ ฅ ์ ์ง์ ๊ด๊ฑด์ด ๋์๋ค. ๋ณธ ํ๋ก์ ํธ์์๋ ๊ต์ก์ฉ ๊ฐ์ ๋๋ก ์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ(๊ฒฝ๋ก ๋นํ, ํธ๋ฒ๋ง, ๋๋ฐ ์ฅ์ ๋ฌผ ํํผ, ๋น์ ์ฐฉ๋ฅ)๋ฅผ ์ฝ๋๋ก ์ง์ ๋ง๋ค์ด, ๋ก๊ทธ ์คํค๋ง ์ ์, ์ก์ยท๊ฒฐ์ธก ์ ์ฒ๋ฆฌ, ๊ฒฝ๋ก ์ดํ๋ยทํธ๋ฒ๋ง ์์ ์ฑ ๊ธฐ๋ฐ Baseline ์ฑ์ , ์ฅ์ ๋ฌผ ๋ฐ์ยท์ฐฉ๋ฅ ์๋๋ฅผ ๋ฐ์ํ ์ ๋ฐ ํ๊ฐ ์์ง, ์กฐ์ ์ต๊ด ๋ถ์๊ณผ ์ ๋ขฐ๋ ๋ฑ๊ธ, 3D ์ฌ์ ๋์๋ณด๋, ๊ต๊ด HITL ํ๊ฐ ์ธํฐํ์ด์ค, ๋๋ ๋ฐฐ์น ์คํธ๋ ์ค ์ํ, AAR ์ฌํ๋, ์ต์ข
ํตํฉ๊น์ง ์๋ จ๋ ๋ถ์๊ธฐ๋ฅผ ๋จ๊ณ๋ณ๋ก ๊ตฌํํ๋ค. ์ต์ข
๋ชฉํ๋ ๊ต๊ด ํ๊ฐ์ ์ผ์นํ๋ ๊ฐ๊ด์ ์๋ จ๋ ์งํ์ ๊ฐ์ธ๋ณ ํผ๋๋ฐฑ์ ์ ๊ณตํ๋, ๋ฑ๊ธ๊ณผ ์ฌ๊ต์ก ๊ฒฐ์ ์ ๊ต๊ด์ด ๊ทผ๊ฑฐ๋ฅผ ๊ธฐ๋กํด ํ์ ํ๋๋ก ์ค๊ณํ๋ ๊ฒ์ด๋ค.\n",
"\n",
"> **โ ๏ธ [๊ต์ก์ฉ ๊ฐ์ ๋ฐ์ดํฐ์
์์น]**\n",
"> - ๋ณธ ๋
ธํธ๋ถ์ ๋ชจ๋ ๋ฐ์ดํฐ(์ ์, ๋ก๊ทธ, ์๋๋ฆฌ์ค ๋ฑ)๋ ๋ณด์ ๊ท์ ์ ์ค์ํ์ฌ **์ฝ๋ ๋ด ๋์ ๊ธฐ๋ฐ ๊ฐ์ ๋ฐ์ดํฐ๋ก ์์ฑ**๋ฉ๋๋ค.\n",
"> - ์ค์ ๋ถ๋ ์์น, ์์ ์ธ๊ฐ, ์ค์ ์ ์ฅ ์ ๋ณด, ๊ตฐ์ฌ ๊ธฐ๋ฐ ๋ฐ์ดํฐ๋ ์ผ์ฒด ํฌํจ๋์ง ์์ต๋๋ค.\n",
"\n",
"---\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ============================================================================\n",
"# [Google Colab ํ๊ฒฝ ์ค์ ] ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น ๋ฐ ์ํฌํธ\n",
"# ============================================================================\n",
"!pip install -q numpy pandas matplotlib scipy scikit-learn\n",
"\n",
"import os\n",
"import sys\n",
"import time\n",
"import math\n",
"import json\n",
"import re\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"import warnings\n",
"warnings.filterwarnings('ignore')\n",
"\n",
"# ๊ทธ๋ํ ์๋ฌธ ํฐํธ ๊ธฐ๋ณธ ์ค์ (ํ๊ฒฝ ๋
๋ฆฝ์ฑ ๋ณด์ฅ)\n",
"plt.rcParams['font.sans-serif'] = 'DejaVu Sans'\n",
"plt.rcParams['axes.unicode_minus'] = False\n",
"\n",
"print(f\"์คํ ํ๊ฒฝ ์ค๋น ์๋ฃ (Python {sys.version.split()[0]})\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
01. ๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ(์คํฑ ์กฐ์, ์์ธ๊ฐ, ๊ณ ๋ ์ ์ง, ๊ฒฝ๋ก ์ค์ฐจ) ๋ฐ์ดํฐ ์คํค๋ง ์ ์\n",
"**์์์๊ฐ**: ์ฝ 1.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ(์คํฑ ์กฐ์, ์์ธ๊ฐ, ๊ณ ๋ ์ ์ง ์ค์ฐจ) ๋ฐ์ดํฐ ์คํค๋ง๋ฅผ ์ ์ํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ(์คํฑ ์กฐ์, ์์ธ๊ฐ, ๊ณ ๋, ์์นยท๋ชฉํ ๊ฒฝ๋ก)์ ๋นํ ์ธ์
๋ฉํ๋ฐ์ดํฐ์ ์คํค๋ง๋ฅผ ์ ์ํ๊ณ ์๋ชป๋ ๋ก๊ทธ๋ฅผ ๊ฑธ๋ฌ๋ด๋ ๊ฒ์ฆ๊ธฐ๋ฅผ ๋ง๋ ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ธ์
(๊ฐ๋ช
์กฐ์ข
์ ID, ์ฝ์ค, ๊ธฐ๋ก ์ฃผ๊ธฐ, ๋นํ ์๊ฐ), ์ ์ ํ๋ผ๋ฏธํฐ(๋นํ ์กฐ๊ฑด, ํ์, GPS ์ค์ฐจ, ์์ ์ง์ฐ), ๋ ์ฝ๋(์คํฑ 3์ถ, ์์ธ๊ฐ, ๊ณ ๋, ์์นยท๋ชฉํ) ํ๋์ ํ์ฉ ๋ฒ์ ์ ์ํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `data/schema/trn01_input_schema.json ๋ฐ docs/trn01_data_spec.md` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ต์กยทํ๋ จ ์ค๋ฌด ๋ฐ ๋ฌ์์-์ฐํฌ๋ผ์ด๋ ์ ์ ์ ์ฅ ํ๊ฒฝ๊ณผ์ ๊ตฐ์ฌ์ ๋ถํฉ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์คํค๋ง ํ๋ ์ ์ ์์ฑ๋ 100%, ์ํ ๋ฐ์ดํฐ ๊ฒ์ฆ ์ค๋ฅ 0๊ฑด\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์
๋ ฅ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฆ ์คํฌ๋ฆฝํธ ํต๊ณผ ๋ฐ ๋ช
์ธ์ ๋ฌธ์ํ ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: JSON Schema ๊ฒ์ฆ ๋๊ตฌ ๋ฐ ์ํ ๋ฐ์ดํฐ ํ์ฑ ๋จ์ ํ
์คํธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ(์คํฑ ์กฐ์, ์์ธ๊ฐ, ๊ณ ๋, ์์นยท๋ชฉํ ๊ฒฝ๋ก)์ ๋นํ ์ธ์
๋ฉํ๋ฐ์ดํฐ(๊ฐ๋ช
์กฐ์ข
์, ๋นํ ์กฐ๊ฑด, ์ ์ ํ๋ผ๋ฏธํฐ)์ ์คํค๋ง๋ฅผ ์ ์ํ๊ณ ๊ฐ์ ์ํ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํ๋ค.\"\"\"\n",
"import json\n",
"import re\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ ์์ฑ๊ธฐ (์ค์ ํ๋ จ์ยท์ฅ๋น ์ ๋ณด ์์, ์กฐ์ข
์๋ ๊ฐ๋ช
P001~)\n",
"# ๋นํ 1ํ = 120์ด(10Hz): ๊ฒฝ๋ก ๋นํ(0~60์ด) -> ํธ๋ฒ๋ง(60~85์ด) -> ๋๋ฐ ์ฅ์ ๋ฌผ ํํผ(85์ด) -> ๋น์ ์ฐฉ๋ฅ(100์ด ์ง์)\n",
"# ์กฐ์ข
์๋ง๋ค ์จ์ ์๋ จ๋(skill)์ ์กฐ์ ์ต๊ด(๊ธ์กฐ์ jerky, ๊ณผ๋ณด์ overcorr)์ด ์๊ณ , ๊ต๊ด ์ ์๋ ์ด๋ฅผ ๋ฐ์ํด ๋ง๋ ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"DT, STEPS = 0.1, 1200\n",
"T = np.arange(STEPS) * DT\n",
"COND = {\"day\": dict(wind=0.4, gust=0.0, gps=0.25, delay=0.0), \"night\": dict(wind=0.4, gust=0.0, gps=0.5, delay=0.15),\n",
" \"gust\": dict(wind=1.2, gust=2.5, gps=0.25, delay=0.0)}\n",
"OBST_T, LAND_T = 85.0, 100.0\n",
"\n",
"\n",
"def make_pilots(n=40, seed=7):\n",
" rng = np.random.default_rng(seed)\n",
" skill = rng.beta(2.2, 2.0, n)\n",
" return pd.DataFrame({\"pilot\": [f\"P{i + 1:03d}\" for i in range(n)], \"skill\": skill,\n",
" \"jerky\": rng.random(n) < 0.15 + 0.35 * (1 - skill), \"overcorr\": rng.random(n) < 0.15 + 0.35 * (1 - skill)})\n",
"\n",
"\n",
"def target_path(t):\n",
" \"\"\"์ฌ๊ฐํ ๊ฒฝ๋ก(40x30m, ๊ณ ๋ 20m)๋ฅผ 3m/s๋ก ๋นํ -> 60์ด ์ง์ ์์ ํธ๋ฒ๋ง -> ์ฐฉ๋ฅ ์ง์ ํ ๊ณ ๋ 0.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 1] ๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ(์คํฑ ์กฐ์, ์์ธ๊ฐ, ๊ณ ๋ ์ ์ง, ๊ฒฝ๋ก ์ค์ฐจ) ๋ฐ์ดํฐ ์คํค๋ง ์ ์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def fly(pilots, cond=\"day\", seed=0, delay_extra=0.0, obstacle_side=None):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์์ ๋นํ 1ํ๋ฅผ ๋์์ ๋ชจ์ํ๋ค(๋ฒกํฐํ). ๋ฐํ: ๋ก๊ทธ ๋ฐฐ์ด dict, ๋นํ๋ณ ์ ๋ต ์ ๋ณด DataFrame.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 1] ๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ(์คํฑ ์กฐ์, ์์ธ๊ฐ, ๊ณ ๋ ์ ์ง, ๊ฒฝ๋ก ์ค์ฐจ) ๋ฐ์ดํฐ ์คํค๋ง ์ ์ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def one(log, j):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ์์ j๋ฒ์งธ ์กฐ์ข
์ 1๋ช
์ ๋ก๊ทธ(DataFrame)๋ฅผ ๊บผ๋ธ๋ค.\"\"\"\n",
" return pd.DataFrame({k: (v if v.ndim == 1 else v[:, j]) for k, v in log.items()})\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"SCHEMA = {\n",
" \"session\": {\n",
" \"session_id\": {\"type\": str, \"pattern\": r\"^S\\d{4}$\"},\n",
" \"pilot\": {\"type\": str, \"pattern\": r\"^P\\d{3}$\"}, # ๊ฐ๋ช
ID๋ง ํ์ฉ(์ค๋ช
ยท๊ตฐ๋ฒ ๊ธ์ง)\n",
" \"sim_course\": {\"type\": str, \"enum\": [\"RECT40x30_HOVER_OBS_LAND\"]},\n",
" \"rate_hz\": {\"type\": int, \"enum\": [10, 20, 50]},\n",
" \"duration_s\": {\"type\": (int, float), \"min\": 10, \"max\": 1800},\n",
" },\n",
" \"tactical\": {\n",
" \"condition\": {\"type\": str, \"enum\": list(COND)},\n",
" \"wind_mps\": {\"type\": (int, float), \"min\": 0, \"max\": 20},\n",
" \"gps_error_m\": {\"type\": (int, float), \"min\": 0, \"max\": 50},\n",
" \"video_delay_ms\": {\"type\": (int, float), \"min\": 0, \"max\": 2000},\n",
" },\n",
" \"record\": {\n",
" \"t\": {\"type\": (int, float), \"min\": 0},\n",
" \"stick_roll\": {\"type\": (int, float), \"min\": -1, \"max\": 1},\n",
" \"stick_pitch\": {\"type\": (int, float), \"min\": -1, \"max\": 1},\n",
" \"throttle\": {\"type\": (int, float), \"min\": -1, \"max\": 1},\n",
" \"roll_deg\": {\"type\": (int, float), \"min\": -90, \"max\": 90},\n",
" \"pitch_deg\": {\"type\": (int, float), \"min\": -90, \"max\": 90},\n",
" \"alt\": {\"type\": (int, float), \"min\": -1, \"max\": 500},\n",
" \"x\": {\"type\": (int, float)}, \"y\": {\"type\": (int, float)},\n",
" \"tx\": {\"type\": (int, float)}, \"ty\": {\"type\": (int, float)}, \"talt\": {\"type\": (int, float), \"min\": 0, \"max\": 500},\n",
" },\n",
"}\n",
"\n",
"\n",
"def check(name, value, rule):\n",
" if not isinstance(value, rule[\"type\"]) or isinstance(value, bool):\n",
" return [f\"{name}: ์๋ฃํ ์ค๋ฅ({type(value).__name__})\"]\n",
" errs = []\n",
" if \"enum\" in rule and value not in rule[\"enum\"]:\n",
" errs.append(f\"{name}: ํ์ฉ๋์ง ์์ ๊ฐ '{value}'\")\n",
" if \"min\" in rule and value < rule[\"min\"]:\n",
" errs.append(f\"{name}: ์ต์๊ฐ {rule['min']} ๋ฏธ๋ง\")\n",
" if \"max\" in rule and value > rule[\"max\"]:\n",
" errs.append(f\"{name}: ์ต๋๊ฐ {rule['max']} ์ด๊ณผ\")\n",
" if \"pattern\" in rule and not re.match(rule[\"pattern\"], value):\n",
" errs.append(f\"{name}: ํ์ ๋ถ์ผ์น '{value}'\")\n",
" return errs\n",
"\n",
"\n",
"def validate(sess):\n",
" errs = []\n",
" for g in (\"session\", \"tactical\"):\n",
" for k, rule in SCHEMA[g].items():\n",
" errs += [f\"{g}.{k}: ํ์ ํ๋ ๋๋ฝ\"] if k not in sess.get(g, {}) else check(f\"{g}.{k}\", sess[g][k], rule)\n",
" prev = -1.0\n",
" for i, rec in enumerate(sess.get(\"records\", [])):\n",
" for k, rule in SCHEMA[\"record\"].items():\n",
" errs += [f\"records[{i}].{k}: ํ์ ํ๋ ๋๋ฝ\"] if k not in rec else check(f\"records[{i}].{k}\", rec[k], rule)\n",
" if isinstance(rec.get(\"t\"), (int, float)) and rec[\"t\"] <= prev:\n",
" errs.append(f\"records[{i}].t: ์๊ฐ ์ญํ/์ค๋ณต\")\n",
" prev = rec.get(\"t\", prev)\n",
" return errs\n",
"\n",
"\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์กฐ์ข
์ 20๋ช
x ๋นํ 1ํ -> ์ธ์
20๊ฑด(๊ฐ ์ธ์
์์ ๋ ์ฝ๋ 30๊ฐ๋ง ํ๋ณธ ๊ฒ์ฆ)\n",
"pilots = make_pilots(20, seed=7)\n",
"log, truth = fly(pilots, \"day\", seed=1)\n",
"rng = np.random.default_rng(2)\n",
"sessions = []\n",
"for j in range(20):\n",
" d = one(log, j).iloc[::40][list(SCHEMA[\"record\"])].round(3)\n",
" sessions.append({\"session\": {\"session_id\": f\"S{j + 1:04d}\", \"pilot\": pilots[\"pilot\"][j], \"sim_course\": \"RECT40x30_HOVER_OBS_LAND\",\n",
" \"rate_hz\": 10, \"duration_s\": 120.0},\n",
" \"tactical\": {\"condition\": \"day\", \"wind_mps\": float(round(rng.uniform(0, 3), 1)), \"gps_error_m\": 0.25,\n",
" \"video_delay_ms\": int(rng.integers(50, 250))},\n",
" \"records\": [{k: float(v) for k, v in r.items()} for r in d.to_dict(\"records\")]})\n",
"ok = sum(1 for s in sessions if not validate(s))\n",
"print(f\"์ ์ ์ธ์
{len(sessions)}๊ฑด(๋ ์ฝ๋ {sum(len(s['records']) for s in sessions)}๊ฐ) ์ค ๊ฒ์ฆ ํต๊ณผ {ok}๊ฑด, ์ค๋ฅ {len(sessions) - ok}๊ฑด\")\n",
"\n",
"bad = json.loads(json.dumps(sessions[0]))\n",
"bad[\"session\"][\"pilot\"] = \"KIM-01\" # ์ค๋ช
ํ ID ์
๋ ฅ\n",
"bad[\"tactical\"][\"condition\"] = \"storm\"\n",
"del bad[\"tactical\"][\"video_delay_ms\"]\n",
"bad[\"records\"][3][\"stick_roll\"] = 1.8\n",
"bad[\"records\"][5][\"t\"] = bad[\"records\"][4][\"t\"]\n",
"print(\"[์ค๋ฅ ์ฃผ์
๊ฒฐ๊ณผ]\")\n",
"for e in validate(bad):\n",
" print(\" -\", e)\n",
"\n",
"rows = []\n",
"for g in SCHEMA:\n",
" for k, r in SCHEMA[g].items():\n",
" t = r[\"type\"].__name__ if isinstance(r[\"type\"], type) else \"number\"\n",
" rows.append((g, k, t, str({x: y for x, y in r.items() if x != \"type\"})[:45]))\n",
"print(\"\\n[๋ฐ์ดํฐ ๋ช
์ธ ์์ฝ]\")\n",
"print(pd.DataFrame(rows, columns=[\"๊ทธ๋ฃน\", \"ํ๋\", \"์๋ฃํ\", \"๊ท์น\"]).to_string(index=False))\n",
"print(\"\\n๋นํ ๋จ๊ณ: ๊ฒฝ๋ก 0~60์ด / ํธ๋ฒ๋ง 60~85์ด / ์ฅ์ ๋ฌผ ํํผ 85์ด / ๋น์ ์ฐฉ๋ฅ ์ง์ 100์ด, 10Hz ๊ธฐ๋ก\")\n",
"print(\"๊ฐ์ธ์ ๋ณด: ์กฐ์ข
์๋ ๊ฐ๋ช
ID(P001~)๋ก๋ง ์ ์ฅํ๊ณ , ์ค๋ช
ยท๊ตฐ๋ฒยท์์์ ๋ณ๋ ๋์ฅ์์ ๊ต๊ด๋ง ๊ด๋ฆฌํ๋ค.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
01 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"์ ์ ์ธ์
20๊ฑด(๋ ์ฝ๋ 600๊ฐ) ์ค ๊ฒ์ฆ ํต๊ณผ 20๊ฑด, ์ค๋ฅ 0๊ฑด\n",
"[์ค๋ฅ ์ฃผ์
๊ฒฐ๊ณผ]\n",
" - session.pilot: ํ์ ๋ถ์ผ์น 'KIM-01'\n",
" - tactical.condition: ํ์ฉ๋์ง ์์ ๊ฐ 'storm'\n",
" - tactical.video_delay_ms: ํ์ ํ๋ ๋๋ฝ\n",
" - records[3].stick_roll: ์ต๋๊ฐ 1 ์ด๊ณผ\n",
" - records[5].t: ์๊ฐ ์ญํ/์ค๋ณต\n",
"\n",
"[๋ฐ์ดํฐ ๋ช
์ธ ์์ฝ]\n",
" ๊ทธ๋ฃน ํ๋ ์๋ฃํ ๊ท์น\n",
" session session_id str {'pattern': '^S\\\\d{4}$'}\n",
" session pilot str {'pattern': '^P\\\\d{3}$'}\n",
" session sim_course str {'enum': ['RECT40x30_HOVER_OBS_LAND']}\n",
" session rate_hz int {'enum': [10, 20, 50]}\n",
" session duration_s number {'min': 10, 'max': 1800}\n",
"tactical condition str {'enum': ['day', 'night', 'gust']}\n",
"tactical wind_mps number {'min': 0, 'max': 20}\n",
"tactical gps_error_m number {'min': 0, 'max': 50}\n",
"tactical video_delay_ms number {'min': 0, 'max': 2000}\n",
" record t number {'min': 0}\n",
" record stick_roll number {'min': -1, 'max': 1}\n",
" record stick_pitch number {'min': -1, 'max': 1}\n",
" record throttle number {'min': -1, 'max': 1}\n",
" record roll_deg number {'min': -90, 'max': 90}\n",
" record pitch_deg number {'min': -90, 'max': 90}\n",
" record alt number {'min': -1, 'max': 500}\n",
" record x number {}\n",
" record y number {}\n",
" record tx number {}\n",
" record ty number {}\n",
" record talt number {'min': 0, 'max': 500}\n",
"\n",
"๋นํ ๋จ๊ณ: ๊ฒฝ๋ก 0~60์ด / ํธ๋ฒ๋ง 60~85์ด / ์ฅ์ ๋ฌผ ํํผ 85์ด / ๋น์ ์ฐฉ๋ฅ ์ง์ 100์ด, 10Hz ๊ธฐ๋ก\n",
"๊ฐ์ธ์ ๋ณด: ์กฐ์ข
์๋ ๊ฐ๋ช
ID(P001~)๋ก๋ง ์ ์ฅํ๊ณ , ์ค๋ช
ยท๊ตฐ๋ฒยท์์์ ๋ณ๋ ๋์ฅ์์ ๊ต๊ด๋ง ๊ด๋ฆฌํ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
02. ์๊ณ์ด ๋นํ ๊ถค์ ๋ฐ์ดํฐ ์ ์ ๋ฐ ์ด์ ์กฐ์ ๋
ธ์ด์ฆ ํํฐ๋ง ์ ์ฒ๋ฆฌ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์๊ณ์ด ๋นํ ๊ถค์ ๋
ธ์ด์ฆ๋ฅผ ํํฐ๋งํ๊ณ ์คํฑ ์กฐ์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ทํํ๋ ์ ์ฒ๋ฆฌ ๋ชจ๋์ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์๊ณ์ด ๋นํ๋ก๊ทธ์ ์๊ฐ ์ค๋ณตยท์ญํ, GPS ํ, ์ผ์ ๋จ์ , ์คํฑ PWM ์์๊ฐ์ ์ ๋ฆฌํ๋ ์ ์ฒ๋ฆฌ ๋ชจ๋์ ๊ตฌํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **10Hz ๊ฒฉ์๋ก ์๊ฐ์ ์ ๋ ฌํ๊ณ ์ค๋ณต์ ์ ๊ฑฐํ ๋ค, ์คํฑ PWM(1000~2000us)์ -1~1๋ก ์ ๊ทํํ๊ณ ์ค๋ฆฝ ๋ฐ๋๋ฐด๋๋ฅผ ์ ์ฉ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/preprocess/trn01_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": [
"\"\"\"์๋ฎฌ๋ ์ดํฐ ์์ ๋นํ๋ก๊ทธ์ ์๊ฐ ์ค๋ณตยท์ญํ ์ ๋ฆฌ, ์คํฑ PWM ๊ฐ(1000~2000us) ์ ๊ทํ, GPS ํ(์ด์์น) ์ ๊ฑฐ(Hampel ํํฐ), ์ผ์ ๋จ์ ๊ตฌ๊ฐ ๋ณด๊ฐ์ ์ํํ๋ ์ ์ฒ๋ฆฌ ๋ชจ๋์ ๊ตฌํํ๊ณ , ๋นํ ์กฐ๊ฑด(์ฃผ๊ฐ/์ผ๊ฐ/๋ํ)๋ณ ์๊ณ๊ฐ์ ๋ฌ๋ฆฌ ์ ์ฉํด ์ฒ๋ฆฌ ์๋์ ์์น ์ค์ฐจ ๊ฐ์ ์ ํ์ธํ๋ค.\"\"\"\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ ์์ฑ๊ธฐ (์ค์ ํ๋ จ์ยท์ฅ๋น ์ ๋ณด ์์, ์กฐ์ข
์๋ ๊ฐ๋ช
P001~)\n",
"# ๋นํ 1ํ = 120์ด(10Hz): ๊ฒฝ๋ก ๋นํ(0~60์ด) -> ํธ๋ฒ๋ง(60~85์ด) -> ๋๋ฐ ์ฅ์ ๋ฌผ ํํผ(85์ด) -> ๋น์ ์ฐฉ๋ฅ(100์ด ์ง์)\n",
"# ์กฐ์ข
์๋ง๋ค ์จ์ ์๋ จ๋(skill)์ ์กฐ์ ์ต๊ด(๊ธ์กฐ์ jerky, ๊ณผ๋ณด์ overcorr)์ด ์๊ณ , ๊ต๊ด ์ ์๋ ์ด๋ฅผ ๋ฐ์ํด ๋ง๋ ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"DT, STEPS = 0.1, 1200\n",
"T = np.arange(STEPS) * DT\n",
"COND = {\"day\": dict(wind=0.4, gust=0.0, gps=0.25, delay=0.0), \"night\": dict(wind=0.4, gust=0.0, gps=0.5, delay=0.15),\n",
" \"gust\": dict(wind=1.2, gust=2.5, gps=0.25, delay=0.0)}\n",
"OBST_T, LAND_T = 85.0, 100.0\n",
"\n",
"\n",
"def make_pilots(n=40, seed=7):\n",
" rng = np.random.default_rng(seed)\n",
" skill = rng.beta(2.2, 2.0, n)\n",
" return pd.DataFrame({\"pilot\": [f\"P{i + 1:03d}\" for i in range(n)], \"skill\": skill,\n",
" \"jerky\": rng.random(n) < 0.15 + 0.35 * (1 - skill), \"overcorr\": rng.random(n) < 0.15 + 0.35 * (1 - skill)})\n",
"\n",
"\n",
"def target_path(t):\n",
" \"\"\"์ฌ๊ฐํ ๊ฒฝ๋ก(40x30m, ๊ณ ๋ 20m)๋ฅผ 3m/s๋ก ๋นํ -> 60์ด ์ง์ ์์ ํธ๋ฒ๋ง -> ์ฐฉ๋ฅ ์ง์ ํ ๊ณ ๋ 0.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 2] ์๊ณ์ด ๋นํ ๊ถค์ ๋ฐ์ดํฐ ์ ์ ๋ฐ ์ด์ ์กฐ์ ๋
ธ์ด์ฆ ํํฐ๋ง ์ ์ฒ๋ฆฌ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def fly(pilots, cond=\"day\", seed=0, delay_extra=0.0, obstacle_side=None):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์์ ๋นํ 1ํ๋ฅผ ๋์์ ๋ชจ์ํ๋ค(๋ฒกํฐํ). ๋ฐํ: ๋ก๊ทธ ๋ฐฐ์ด dict, ๋นํ๋ณ ์ ๋ต ์ ๋ณด DataFrame.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 2] ์๊ณ์ด ๋นํ ๊ถค์ ๋ฐ์ดํฐ ์ ์ ๋ฐ ์ด์ ์กฐ์ ๋
ธ์ด์ฆ ํํฐ๋ง ์ ์ฒ๋ฆฌ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def one(log, j):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ์์ j๋ฒ์งธ ์กฐ์ข
์ 1๋ช
์ ๋ก๊ทธ(DataFrame)๋ฅผ ๊บผ๋ธ๋ค.\"\"\"\n",
" return pd.DataFrame({k: (v if v.ndim == 1 else v[:, j]) for k, v in log.items()})\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"HAMPEL_K = {\"day\": 3.0, \"night\": 3.5, \"gust\": 4.5} # ์กฐ๊ฑด๋ณ ์ด์์น ํ์ ์๊ณ๊ฐ(๋ํ์ ์ค์ ํฐ ์์ง์์ด ๋ง์ ๋์จํ๊ฒ)\n",
"\n",
"\n",
"def make_raw(log, j, cond, rng):\n",
" \"\"\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ์์ ๋ก๊ทธ: PWM ์คํฑ๊ฐ, GPS ํ 1%, ์ผ์ ๋จ์ (0.5~2์ด) 2ํ, ์๊ฐ ์ค๋ณต 2%.\"\"\"\n",
" d = one(log, j)\n",
" raw = d[[\"t\", \"x\", \"y\", \"alt\", \"roll_deg\", \"pitch_deg\", \"true_x\", \"true_y\", \"true_alt\"]].copy()\n",
" for s in (\"stick_roll\", \"stick_pitch\", \"throttle\"):\n",
" raw[s + \"_pwm\"] = (1500 + 500 * d[s] + rng.normal(0, 4, len(d))).round().astype(int)\n",
" k = rng.random(len(raw)) < 0.01\n",
" for c in (\"x\", \"y\"):\n",
" raw.loc[k, c] += rng.choice([-1, 1], k.sum()) * rng.uniform(6, 15, k.sum())\n",
" for _ in range(2):\n",
" a = int(rng.integers(50, len(raw) - 30)); raw.loc[a:a + int(rng.integers(5, 20)), [\"x\", \"y\", \"alt\"]] = np.nan\n",
" dup = raw.sample(frac=0.02, random_state=int(rng.integers(1e6)))\n",
" raw = pd.concat([raw, dup]).sample(frac=1.0, random_state=1).reset_index(drop=True) # ์์ ์์ + ์ค๋ณต\n",
" raw[\"t\"] = raw[\"t\"] + rng.normal(0, 0.003, len(raw)) # ์๊ฐ ํ๋ค๋ฆผ\n",
" return raw\n",
"\n",
"\n",
"def pwm_to_stick(p, dead=8):\n",
" v = (np.asarray(p, float) - 1500) / 500\n",
" v[np.abs(np.asarray(p) - 1500) < dead] = 0.0 # ์ค๋ฆฝ ๋ถ๊ทผ ๋ฐ๋๋ฐด๋\n",
" return np.clip(v, -1, 1)\n",
"\n",
"\n",
"def hampel(x, win=7, k=3.0):\n",
" s = pd.Series(x)\n",
" med = s.rolling(win, center=True, min_periods=1).median()\n",
" mad = (s - med).abs().rolling(win, center=True, min_periods=1).median() * 1.4826\n",
" out = (s - med).abs() > k * (mad + 0.3) # 0.3m: GPS ์ก์ ์์ค ์ดํ ํ๋ค๋ฆผ์ ์ด์์น๋ก ๋ณด์ง ์์\n",
" return np.where(out, np.nan, s), int(out.sum())\n",
"\n",
"\n",
"def preprocess(raw, cond):\n",
" d = raw.copy()\n",
" d[\"k\"] = (d[\"t\"] / DT).round().astype(int) # 10Hz ๊ฒฉ์ ๋ฒํธ๋ก ์๊ฐ ์ ๋ ฌ\n",
" d = d.sort_values(\"k\").drop_duplicates(\"k\").set_index(\"k\")\n",
" d = d.reindex(np.arange(0, d.index.max() + 1))\n",
" d[\"t\"] = np.round(d.index * DT, 1)\n",
" n_missing = int(d[[\"x\", \"y\", \"alt\"]].isna().any(axis=1).sum())\n",
" n_out = 0\n",
" for c in (\"x\", \"y\", \"alt\"):\n",
" d[c], n = hampel(d[c].values, k=HAMPEL_K[cond]); n_out += n\n",
" d[[\"x\", \"y\", \"alt\"]] = d[[\"x\", \"y\", \"alt\"]].interpolate(limit_direction=\"both\")\n",
" for s in (\"stick_roll\", \"stick_pitch\", \"throttle\"):\n",
" d[s] = pwm_to_stick(d[s + \"_pwm\"].interpolate(limit_direction=\"both\").round())\n",
" return d.reset_index(drop=True), n_missing, n_out\n",
"\n",
"\n",
"pilots = make_pilots(30, seed=7)\n",
"rng = np.random.default_rng(3)\n",
"rows, n_rec, t_total = [], 0, 0.0\n",
"for cond in COND:\n",
" log, _ = fly(pilots, cond, seed=1)\n",
" for j in range(len(pilots)):\n",
" raw = make_raw(log, j, cond, rng)\n",
" t0 = time.perf_counter()\n",
" clean, miss, out = preprocess(raw, cond)\n",
" t_total += time.perf_counter() - t0; n_rec += len(raw)\n",
" e0 = np.sqrt((raw[\"x\"] - raw[\"true_x\"]) ** 2 + (raw[\"y\"] - raw[\"true_y\"]) ** 2).mean()\n",
" e1 = np.sqrt((clean[\"x\"] - clean[\"true_x\"]) ** 2 + (clean[\"y\"] - clean[\"true_y\"]) ** 2).mean()\n",
" rows.append({\"์กฐ๊ฑด\": cond, \"๊ฒฐ์ธกํ\": miss, \"์ด์์น\": out, \"๋จ์๊ฒฐ์ธก\": int(clean[[\"x\", \"y\", \"alt\"]].isna().sum().sum()),\n",
" \"์์น์ค์ฐจ ์ (m)\": e0, \"์์น์ค์ฐจ ํ(m)\": e1})\n",
"df = pd.DataFrame(rows)\n",
"print(f\"์์ ๋ ์ฝ๋ {n_rec:,}๊ฐ(๋ก๊ทธ {len(df)}๊ฐ) ์ฒ๋ฆฌ, {n_rec / t_total:,.0f} ๋ ์ฝ๋/์ด (๋ชฉํ 1,000๊ฑด/์ด ์ด์), ์ฒ๋ฆฌ ์ค ์ค๋ฅ 0๊ฑด\")\n",
"print(f\"๊ฒฐ์ธก ๋ณด์ ์จ {1 - df['๋จ์๊ฒฐ์ธก'].sum() / max(df['๊ฒฐ์ธกํ'].sum(), 1):.0%} (๊ฒฐ์ธกํ {df['๊ฒฐ์ธกํ'].sum()}๊ฐ ๋ชจ๋ ๋ณด๊ฐ)\")\n",
"print(\"\\n[์กฐ๊ฑด๋ณ ์ ์ฒ๋ฆฌ ํจ๊ณผ(๋ก๊ทธ ํ๊ท )]\")\n",
"print(df.groupby(\"์กฐ๊ฑด\")[[\"๊ฒฐ์ธกํ\", \"์ด์์น\", \"์์น์ค์ฐจ ์ (m)\", \"์์น์ค์ฐจ ํ(m)\"]].mean().round(2).to_string())\n",
"print(\"์กฐ๊ฑด๋ณ Hampel ์๊ณ๊ฐ:\", HAMPEL_K, \"-> ๋ํ ์กฐ๊ฑด์ ์ค์ ๊ธ๊ธฐ๋์ ์ด์์น๋ก ์ง์ฐ์ง ์๋๋ก ๋์จํ๊ฒ ๋๋ค\")\n",
"\n",
"# ๋จ์ ํ
์คํธ 5์ข
\n",
"raw = make_raw(fly(pilots.iloc[:1], \"day\", seed=9)[0], 0, \"day\", np.random.default_rng(0))\n",
"clean, _, _ = preprocess(raw, \"day\")\n",
"spike = np.r_[np.zeros(10), 50.0, np.zeros(10)]\n",
"tests = {\n",
" \"PWM 1000/1500/2000 -> -1/0/1\": np.allclose(pwm_to_stick([1000, 1500, 2000]), [-1, 0, 1]),\n",
" \"๋ฐ๋๋ฐด๋(1504 -> 0)\": pwm_to_stick([1504])[0] == 0.0,\n",
" \"๋จ์ผ ํ ๊ฐ ์ ๊ฑฐ\": np.isnan(hampel(spike)[0][10]),\n",
" \"์๊ฐ ์ค๋ณต ์ ๊ฑฐยท๋จ์กฐ ์ฆ๊ฐ\": clean[\"t\"].is_monotonic_increasing and clean[\"t\"].duplicated().sum() == 0,\n",
" \"๊ฒฐ์ธก ์์ + 10Hz 1,200ํ\": clean[[\"x\", \"y\", \"alt\"]].isna().sum().sum() == 0 and len(clean) == STEPS,\n",
"}\n",
"for k, v in tests.items():\n",
" print(f\"[{'PASS' if v else 'FAIL'}] {k}\")\n",
"print(f\"๋จ์ ํ
์คํธ {sum(tests.values())}/{len(tests)} ํต๊ณผ\")\n",
"\n",
"seg = slice(400, 700)\n",
"r2 = raw.sort_values(\"t\").iloc[seg]\n",
"plt.figure(figsize=(9, 3.5))\n",
"plt.plot(r2[\"t\"], r2[\"y\"], \".\", ms=3, c=\"gray\", label=\"raw (spikes, gaps)\")\n",
"plt.plot(clean[\"t\"].iloc[seg], clean[\"y\"].iloc[seg], c=\"tab:blue\", label=\"preprocessed\")\n",
"plt.plot(clean[\"t\"].iloc[seg], clean[\"true_y\"].iloc[seg], \"k:\", label=\"true\")\n",
"plt.xlabel(\"time (s)\"); plt.ylabel(\"y (m)\"); plt.legend(); plt.title(\"Flight log preprocessing (synthetic)\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
02 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"์์ ๋ ์ฝ๋ 110,160๊ฐ(๋ก๊ทธ 90๊ฐ) ์ฒ๋ฆฌ, 94,969 ๋ ์ฝ๋/์ด (๋ชฉํ 1,000๊ฑด/์ด ์ด์), ์ฒ๋ฆฌ ์ค ์ค๋ฅ 0๊ฑด\n",
"๊ฒฐ์ธก ๋ณด์ ์จ 100% (๊ฒฐ์ธกํ 2348๊ฐ ๋ชจ๋ ๋ณด๊ฐ)\n",
"\n",
"[์กฐ๊ฑด๋ณ ์ ์ฒ๋ฆฌ ํจ๊ณผ(๋ก๊ทธ ํ๊ท )]\n",
" ๊ฒฐ์ธกํ ์ด์์น ์์น์ค์ฐจ ์ (m) ์์น์ค์ฐจ ํ(m)\n",
"์กฐ๊ฑด \n",
"day 25.37 24.63 0.47 0.31\n",
"gust 26.17 21.87 0.45 0.33\n",
"night 26.73 27.90 0.78 0.62\n",
"์กฐ๊ฑด๋ณ Hampel ์๊ณ๊ฐ: {'day': 3.0, 'night': 3.5, 'gust': 4.5} -> ๋ํ ์กฐ๊ฑด์ ์ค์ ๊ธ๊ธฐ๋์ ์ด์์น๋ก ์ง์ฐ์ง ์๋๋ก ๋์จํ๊ฒ ๋๋ค\n",
"[PASS] PWM 1000/1500/2000 -> -1/0/1\n",
"[PASS] ๋ฐ๋๋ฐด๋(1504 -> 0)\n",
"[PASS] ๋จ์ผ ํ ๊ฐ ์ ๊ฑฐ\n",
"[PASS] ์๊ฐ ์ค๋ณต ์ ๊ฑฐยท๋จ์กฐ ์ฆ๊ฐ\n",
"[PASS] ๊ฒฐ์ธก ์์ + 10Hz 1,200ํ\n",
"๋จ์ ํ
์คํธ 5/5 ํต๊ณผ\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
03. ๋นํ ๊ฒฝ๋ก ์ดํ๋ ๋ฐ ํธ๋ฒ๋ง ์์ ์ฑ ๊ณ์ฐ 1์ฐจ ์๋ จ๋ ์ฑ์ Baseline ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๋นํ ๊ฒฝ๋ก ์ดํ๋ ๋ฐ ํธ๋ฒ๋ง ์์ ์ฑ ๊ธฐ๋ฐ 1์ฐจ ์๋ จ๋ ์ฑ์ Baseline ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๋ฐํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๋นํ ๊ฒฝ๋ก ์ดํ๋์ ํธ๋ฒ๋ง ์์ ์ฑ, ๊ณ ๋ ์ ์ง ์ค์ฐจ๋ก 1์ฐจ ์๋ จ๋ ์ ์์ ๋ฑ๊ธ(A/B/C)์ ๊ณ์ฐํ๋ Baseline ์ฑ์ ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๋ฐํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ฒฝ๋ก RMSE, ํธ๋ฒ๋ง ํ๊ท ์ดํยทํ๋ค๋ฆผ, ๊ณ ๋ ์ค์ฐจ๋ฅผ ์กฐ๊ฑด๋ณ๋ก ํ์คํํ๊ณ ๊ธฐ์ค ์ง๋จ์ผ๋ก ๊ฐ์ค์น๋ฅผ 1์ฐจ ํ๋ํด 0~100์ ์ฑ์ ์ ์์ฑํ์ฌ ์์ธ์ ๋ถ์ํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/trn01_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": [
"\"\"\"๋นํ ๊ฒฝ๋ก ์ดํ๋(๊ฒฝ๋ก RMSE)์ ํธ๋ฒ๋ง ์์ ์ฑ(ํ๊ท ์ดํยทํ๋ค๋ฆผ), ๊ณ ๋ ์ ์ง ์ค์ฐจ๋ก 1์ฐจ ์๋ จ๋ ์ ์(0~100)์ ๋ฑ๊ธ(A/B/C)์ ๊ณ์ฐํ๋ Baseline ์ฑ์ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ๊ณ , ๊ต๊ด ํ๊ฐ ๋ฑ๊ธ๊ณผ์ ์ผ์น์จ(์ฒ๋ฆฌ ์ ํ๋)๊ณผ ํ๋จ ์ง์ฐ์ ์ธก์ ํ๋ค.\"\"\"\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 LinearRegression\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ ์์ฑ๊ธฐ (์ค์ ํ๋ จ์ยท์ฅ๋น ์ ๋ณด ์์, ์กฐ์ข
์๋ ๊ฐ๋ช
P001~)\n",
"# ๋นํ 1ํ = 120์ด(10Hz): ๊ฒฝ๋ก ๋นํ(0~60์ด) -> ํธ๋ฒ๋ง(60~85์ด) -> ๋๋ฐ ์ฅ์ ๋ฌผ ํํผ(85์ด) -> ๋น์ ์ฐฉ๋ฅ(100์ด ์ง์)\n",
"# ์กฐ์ข
์๋ง๋ค ์จ์ ์๋ จ๋(skill)์ ์กฐ์ ์ต๊ด(๊ธ์กฐ์ jerky, ๊ณผ๋ณด์ overcorr)์ด ์๊ณ , ๊ต๊ด ์ ์๋ ์ด๋ฅผ ๋ฐ์ํด ๋ง๋ ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"DT, STEPS = 0.1, 1200\n",
"T = np.arange(STEPS) * DT\n",
"COND = {\"day\": dict(wind=0.4, gust=0.0, gps=0.25, delay=0.0), \"night\": dict(wind=0.4, gust=0.0, gps=0.5, delay=0.15),\n",
" \"gust\": dict(wind=1.2, gust=2.5, gps=0.25, delay=0.0)}\n",
"OBST_T, LAND_T = 85.0, 100.0\n",
"\n",
"\n",
"def make_pilots(n=40, seed=7):\n",
" rng = np.random.default_rng(seed)\n",
" skill = rng.beta(2.2, 2.0, n)\n",
" return pd.DataFrame({\"pilot\": [f\"P{i + 1:03d}\" for i in range(n)], \"skill\": skill,\n",
" \"jerky\": rng.random(n) < 0.15 + 0.35 * (1 - skill), \"overcorr\": rng.random(n) < 0.15 + 0.35 * (1 - skill)})\n",
"\n",
"\n",
"def target_path(t):\n",
" \"\"\"์ฌ๊ฐํ ๊ฒฝ๋ก(40x30m, ๊ณ ๋ 20m)๋ฅผ 3m/s๋ก ๋นํ -> 60์ด ์ง์ ์์ ํธ๋ฒ๋ง -> ์ฐฉ๋ฅ ์ง์ ํ ๊ณ ๋ 0.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 3] ๋นํ ๊ฒฝ๋ก ์ดํ๋ ๋ฐ ํธ๋ฒ๋ง ์์ ์ฑ ๊ณ์ฐ 1์ฐจ ์๋ จ๋ ์ฑ์ Baseline ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def fly(pilots, cond=\"day\", seed=0, delay_extra=0.0, obstacle_side=None):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์์ ๋นํ 1ํ๋ฅผ ๋์์ ๋ชจ์ํ๋ค(๋ฒกํฐํ). ๋ฐํ: ๋ก๊ทธ ๋ฐฐ์ด dict, ๋นํ๋ณ ์ ๋ต ์ ๋ณด DataFrame.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 3] ๋นํ ๊ฒฝ๋ก ์ดํ๋ ๋ฐ ํธ๋ฒ๋ง ์์ ์ฑ ๊ณ์ฐ 1์ฐจ ์๋ จ๋ ์ฑ์ Baseline ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def one(log, j):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ์์ j๋ฒ์งธ ์กฐ์ข
์ 1๋ช
์ ๋ก๊ทธ(DataFrame)๋ฅผ ๊บผ๋ธ๋ค.\"\"\"\n",
" return pd.DataFrame({k: (v if v.ndim == 1 else v[:, j]) for k, v in log.items()})\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ๋นํ๋ก๊ทธ ํน์ง ์ถ์ถ ๊ณตํต ํจ์(์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ๋ฅผ ํ ๋ฒ์ ๊ณ์ฐ) ----\n",
"from scipy.ndimage import uniform_filter1d\n",
"\n",
"BASE_F = [\"path_rmse\", \"hover_drift\", \"hover_std\", \"alt_err\"]\n",
"ADV_F = BASE_F + [\"obs_rt\", \"obs_lateral\", \"touch_vz\", \"att_exceed\", \"jerk\", \"overshoot\", \"n_risk\"]\n",
"RULES = {\"near_collision\": (\"obs_lateral\", \"<\", 2.5), \"hard_landing\": (\"touch_vz\", \">\", 2.0), \"attitude_exceed\": (\"att_exceed\", \">=\", 10)}\n",
"\n",
"\n",
"def detect(df):\n",
" \"\"\"์ํ ์กฐ์ ์ฌ๊ฑด ํ์ง ๊ท์น: ์ฅ์ ๋ฌผ 2.5์ด ํ ํํผ ๊ฑฐ๋ฆฌ 2.5m ๋ฏธ๋ง, ์ ์ง ์๋ 2.0m/s ์ด๊ณผ, ์์ธ 35๋ ์ด๊ณผ 1์ด ์ด์.\"\"\"\n",
" out = {}\n",
" for ev, (f, op, th) in RULES.items():\n",
" out[ev] = df[f] < th if op == \"<\" else (df[f] > th if op == \">\" else df[f] >= th)\n",
" return pd.DataFrame(out)\n",
"\n",
"\n",
"def features(log):\n",
" \"\"\"๊ฒฝ๋ก ์ดํ๋ยทํธ๋ฒ๋ง ์์ ์ฑ(๊ธฐ๋ณธ) + ์ฅ์ ๋ฌผ ๋ฐ์ยท์ฐฉ๋ฅ ์๋ยท์์ธ ํ๊ณยท๊ธ์กฐ์ยท๊ณผ๋ณด์ (๊ณ ๋ํ) ํน์ง.\"\"\"\n",
" X, Y, A = (uniform_filter1d(log[k], 9, axis=0) for k in (\"x\", \"y\", \"alt\")) # GPS ์ก์ ์ํ(์ด๋ํ๊ท 0.9์ด)\n",
" h = np.hypot(X - log[\"tx\"], Y - log[\"ty\"])\n",
" cr, hv = slice(0, 600), slice(620, 850)\n",
" k0, k1 = int(OBST_T / DT), int((OBST_T + 2.5) / DT)\n",
" side = np.sign(log[\"ty\"][k0 + 1] - log[\"ty\"][k0 - 1])\n",
" moved = (log[\"stick_roll\"][k0:k0 + 40] * side) > 0.3\n",
" rt = np.where(moved.any(0), moved.argmax(0) * DT, 4.0)\n",
" vz = -np.gradient(A, DT, axis=0)\n",
" land = np.argmax((A < 0.4) & (T[:, None] >= LAND_T), axis=0)\n",
" touch = np.array([vz[max(i - 6, 0):i - 2, j].mean() if i > 6 else 3.0 for j, i in enumerate(land)])\n",
" ds = np.abs(np.diff(np.stack([log[\"stick_roll\"], log[\"stick_pitch\"]]), axis=1)) / DT\n",
" return pd.DataFrame({\n",
" \"path_rmse\": np.sqrt((h[cr] ** 2).mean(0)), \"hover_drift\": h[hv].mean(0), \"hover_std\": np.hypot(X[hv].std(0), Y[hv].std(0)),\n",
" \"alt_err\": np.abs(A[:850] - log[\"talt\"][:850]).mean(0),\n",
" \"obs_rt\": rt, \"obs_lateral\": np.abs(Y[k1] - Y[k0]), \"touch_vz\": touch,\n",
" \"att_exceed\": (np.maximum(np.abs(log[\"roll_deg\"]), np.abs(log[\"pitch_deg\"])) > 35).sum(0),\n",
" \"jerk\": np.percentile(ds, 95, axis=(0, 1)),\n",
" \"overshoot\": np.clip(np.abs(Y[k0:k0 + 120] - Y[k0]).max(0) - 6.0, 0, None)}).pipe(\n",
" lambda d: d.assign(n_risk=detect(d).sum(axis=1))) # overshoot: ํํผ ํ ๋ชฉํ 6m๋ฅผ ๋์ด์ ๊ฑฐ๋ฆฌ(๊ณผ๋ณด์ )\n",
"\n",
"\n",
"def make_dataset(n_pilots=40, seed=7, conds=(\"day\", \"night\", \"gust\"), flights=1):\n",
" \"\"\"์กฐ์ข
์ n๋ช
x ์กฐ๊ฑด x ๋ฐ๋ณต ๋นํ -> ๋นํ๋ณ ํน์ง + ์ ๋ต(๊ต๊ด ์ ์ยท๋ฑ๊ธยท์ํ ์ฌ๊ฑด) ํ.\"\"\"\n",
" pilots = make_pilots(n_pilots, seed)\n",
" out = []\n",
" for c in conds:\n",
" for f in range(flights):\n",
" log, tr = fly(pilots, c, seed=seed * 100 + list(COND).index(c) * 10 + f)\n",
" out.append(pd.concat([tr.reset_index(drop=True), features(log)], axis=1).assign(flight=f))\n",
" return pd.concat(out, ignore_index=True)\n",
"\n",
"\n",
"def cond_z(df, ref, cols):\n",
" \"\"\"์กฐ๊ฑด๋ณ ๊ธฐ์ค ์ง๋จ(ref)์ ํ๊ท ยทํ์คํธ์ฐจ๋ก ํ์คํ: ๋ํ์ฒ๋ผ ์ด๋ ค์ด ์กฐ๊ฑด์์ ๋ฐ๋ ๋ถ์ด์ต์ ์ค์ธ๋ค.\"\"\"\n",
" z = df.copy()\n",
" z[cols] = z[cols].astype(float)\n",
" for c in df[\"cond\"].unique():\n",
" r = ref[ref[\"cond\"] == c][cols]\n",
" z.loc[df[\"cond\"] == c, cols] = (df.loc[df[\"cond\"] == c, cols] - r.mean()) / (r.std() + 1e-9)\n",
" return z\n",
"\n",
"\n",
"def to_grade(score):\n",
" return np.where(score >= 75, \"A\", np.where(score >= 55, \"B\", \"C\"))\n",
"\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๊ธฐ์ค(ํ๋)์ฉ ์กฐ์ข
์ 40๋ช
, ํ๊ฐ์ฉ ์กฐ์ข
์ 40๋ช
x ์กฐ๊ฑด 3์ข
(์ฃผ๊ฐยท์ผ๊ฐยท๋ํ)\n",
"train = make_dataset(40, seed=7)\n",
"test = make_dataset(40, seed=8)\n",
"\n",
"# 1) ํด๋ฆฌ์คํฑ ๊ฐ์ค์น 1์ฐจ ํ๋: ์กฐ๊ฑด๋ณ ํ์คํ ํน์ง -> ๊ต๊ด ์ ์ ์ ํ ๊ทผ์ฌ\n",
"ztr, zte = cond_z(train, train, BASE_F), cond_z(test, train, BASE_F)\n",
"lr = LinearRegression().fit(ztr[BASE_F], train[\"instructor\"])\n",
"print(\"์ฑ์ ์: ์ ์ = {:.1f}\".format(lr.intercept_) + \"\".join(f\" {w:+.1f}x{f}\" for f, w in zip(BASE_F, lr.coef_)) + \" (ํน์ง์ ์กฐ๊ฑด๋ณ ํ์คํ ๊ฐ)\")\n",
"\n",
"t0 = time.perf_counter()\n",
"test[\"base_score\"] = np.clip(lr.predict(zte[BASE_F]), 0, 100)\n",
"test[\"base_grade\"] = to_grade(test[\"base_score\"])\n",
"ms = (time.perf_counter() - t0) * 1000 / len(test)\n",
"acc = (test[\"base_grade\"] == test[\"grade\"]).mean()\n",
"print(f\"ํ๊ฐ ๋นํ {len(test)}ํ: ๋ฑ๊ธ ์ผ์น์จ(์ฒ๋ฆฌ ์ ํ๋) {acc:.1%} (๋ชฉํ 75% ์ด์: {'๋ฌ์ฑ' if acc >= 0.75 else '๋ฏธ๋ฌ'}), \"\n",
" f\"์ ์ ํ๊ท ์ค์ฐจ {np.abs(test['base_score'] - test['instructor']).mean():.1f}์ , ์ฑ์ ์ง์ฐ {ms:.3f} ms/๋นํ\")\n",
"\n",
"# 2) ์กฐ๊ฑด๋ณ ์ ํ๋ + ์กฐ๊ฑด ํ์คํ๋ฅผ ํ์ง ์์์ ๋ ๋น๊ต\n",
"raw = LinearRegression().fit(train[BASE_F], train[\"instructor\"])\n",
"test[\"raw_grade\"] = to_grade(raw.predict(test[BASE_F]))\n",
"print(\"\\n์กฐ๊ฑด ํ์คํ O ํ์คํ X\")\n",
"for c in COND:\n",
" s = test[test[\"cond\"] == c]\n",
" print(f\"{c:6s} {(s['base_grade'] == s['grade']).mean():8.1%} {(s['raw_grade'] == s['grade']).mean():8.1%}\")\n",
"print(f\"์ ์ฒด {acc:8.1%} {(test['raw_grade'] == test['grade']).mean():8.1%}\")\n",
"gust_raw = raw.predict(test[test[\"cond\"] == \"gust\"][BASE_F]).mean() - raw.predict(test[test[\"cond\"] == \"day\"][BASE_F]).mean()\n",
"print(f\"-> ํ์คํํ์ง ์์ผ๋ฉด ๋ํ ๋นํ ์ ์๊ฐ ์ฃผ๊ฐ๋ณด๋ค ํ๊ท {gust_raw:+.1f}์ ๋ฌ๋ผ์ง๋ค(์กฐ๊ฑด ํจ๊ณผ๊ฐ ์ ์์ ์์). ํ์คํ๋ ๊ฐ์ ์กฐ๊ฑด ๋๋ฃ ๋๋น ๋น๊ต๋ค.\")\n",
"\n",
"# 3) ํผ๋ํ์ ํ๊ณ ๋ถ์\n",
"print(\"\\n[๊ต๊ด ๋ฑ๊ธ x Baseline ๋ฑ๊ธ]\")\n",
"print(pd.crosstab(test[\"grade\"].rename(\"๊ต๊ด\"), test[\"base_grade\"].rename(\"Baseline\")).to_string())\n",
"over = test[(test[\"grade\"] == \"C\") & (test[\"base_grade\"] != \"C\")]\n",
"ev = [\"near_collision\", \"hard_landing\"]\n",
"print(f\"\\n๊ฐ์ฅ ๋ง์ ์ค๋ฅ: ๊ต๊ด C -> Baseline B {len(over)}๊ฑด. ๊ต๊ด C ๋นํ์ {test[test['grade'] == 'C'][ev].any(axis=1).mean():.0%}๊ฐ\"\n",
" f\" ์ฅ์ ๋ฌผ ๊ทผ์ ยท๊ฒฝ์ฐฉ๋ฅ์ ํฌํจํ์ง๋ง Baseline ํน์ง(๊ฒฝ๋กยทํธ๋ฒ๋งยท๊ณ ๋)์๋ ์ด ์ ๋ณด๊ฐ ์๋ค.\")\n",
"print(\"-> ๊ฒฝ๋กยทํธ๋ฒ๋ง๋ง ๋ณด๋ Baseline์ ๋๋ฐ ์ํฉ ๋์(์ฅ์ ๋ฌผ ํํผ, ๋น์ ์ฐฉ๋ฅ)์ ๋ฐ์ํ์ง ๋ชปํ๋ค. ๋ฏธ์
4์์ ๋ฐ์ ํน์ง์ ์ถ๊ฐํ๋ค.\")\n",
"\n",
"plt.figure(figsize=(6, 5))\n",
"for c, col in zip(COND, [\"tab:blue\", \"tab:purple\", \"tab:orange\"]):\n",
" s = test[test[\"cond\"] == c]\n",
" plt.scatter(s[\"instructor\"], s[\"base_score\"], s=15, c=col, label=c)\n",
"plt.plot([20, 100], [20, 100], \"k--\"); plt.axhline(75, c=\"gray\", lw=0.5); plt.axhline(55, c=\"gray\", lw=0.5)\n",
"plt.xlabel(\"instructor score\"); plt.ylabel(\"baseline AI score\"); plt.legend(); plt.title(\"Baseline skill score vs instructor (synthetic)\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
03 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"์ฑ์ ์: ์ ์ = 59.3 -0.4xpath_rmse -0.8xhover_drift +0.2xhover_std -13.0xalt_err (ํน์ง์ ์กฐ๊ฑด๋ณ ํ์คํ ๊ฐ)\n",
"ํ๊ฐ ๋นํ 120ํ: ๋ฑ๊ธ ์ผ์น์จ(์ฒ๋ฆฌ ์ ํ๋) 75.8% (๋ชฉํ 75% ์ด์: ๋ฌ์ฑ), ์ ์ ํ๊ท ์ค์ฐจ 5.3์ , ์ฑ์ ์ง์ฐ 0.025 ms/๋นํ\n",
"\n",
"์กฐ๊ฑด ํ์คํ O ํ์คํ X\n",
"day 70.0% 75.0%\n",
"night 80.0% 70.0%\n",
"gust 77.5% 82.5%\n",
"์ ์ฒด 75.8% 75.8%\n",
"-> ํ์คํํ์ง ์์ผ๋ฉด ๋ํ ๋นํ ์ ์๊ฐ ์ฃผ๊ฐ๋ณด๋ค ํ๊ท -4.0์ ๋ฌ๋ผ์ง๋ค(์กฐ๊ฑด ํจ๊ณผ๊ฐ ์ ์์ ์์). ํ์คํ๋ ๊ฐ์ ์กฐ๊ฑด ๋๋ฃ ๋๋น ๋น๊ต๋ค.\n",
"\n",
"[๊ต๊ด ๋ฑ๊ธ x Baseline ๋ฑ๊ธ]\n",
"Baseline A B C\n",
"๊ต๊ด \n",
"A 15 6 0\n",
"B 3 26 4\n",
"C 0 16 50\n",
"\n",
"๊ฐ์ฅ ๋ง์ ์ค๋ฅ: ๊ต๊ด C -> Baseline B 16๊ฑด. ๊ต๊ด C ๋นํ์ 95%๊ฐ ์ฅ์ ๋ฌผ ๊ทผ์ ยท๊ฒฝ์ฐฉ๋ฅ์ ํฌํจํ์ง๋ง Baseline ํน์ง(๊ฒฝ๋กยทํธ๋ฒ๋งยท๊ณ ๋)์๋ ์ด ์ ๋ณด๊ฐ ์๋ค.\n",
"-> ๊ฒฝ๋กยทํธ๋ฒ๋ง๋ง ๋ณด๋ Baseline์ ๋๋ฐ ์ํฉ ๋์(์ฅ์ ๋ฌผ ํํผ, ๋น์ ์ฐฉ๋ฅ)์ ๋ฐ์ํ์ง ๋ชปํ๋ค. ๋ฏธ์
4์์ ๋ฐ์ ํน์ง์ ์ถ๊ฐํ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
04. ๋๋ฐ ์ฅ์ ๋ฌผ ํํผ ๋ฐ ๋น์ ์ฐฉ๋ฅ ๋ฐ์ ์๋ ๊ธฐ๋ฐ ์ ๋ฐ ์๋ จ๋ ํ๊ฐ ์์ง ๊ณ ๋ํ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๋๋ฐ ์ฅ์ ๋ฌผ ํํผ ๋ฐ ๋น์ ์ฐฉ๋ฅ ๋ฐ์ ์๋๋ฅผ ๋ฐ์ํ ์ ๋ฐ ์๋ จ๋ ํ๊ฐ ์์ง์ ๊ณ ๋ํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๋๋ฐ ์ฅ์ ๋ฌผ ํํผ ๋ฐ์๊ณผ ๋น์ ์ฐฉ๋ฅ ์ ์ง ์๋, ์์ธ ํ๊ณ, ๊ธ์กฐ์ยท๊ณผ๋ณด์ ํน์ง์ ๋ฐ์ํ ์ ๋ฐ ์๋ จ๋ ํ๊ฐ ์์ง์ผ๋ก ๊ณ ๋ํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ฅ์ ๋ฌผ ์ถํ ํ ์คํฑ ๋ฐ์์๊ฐ๊ณผ 2.5์ด ํ ํํผ ๊ฑฐ๋ฆฌ, ์ ์ง ์ง์ ํ๊ฐ ์๋, ์์ธ 35๋ ์ด๊ณผ ์๊ฐ, ์คํฑ ๋ณํ์จ, ํํผ ํ ์ด๊ณผ ์ด๋์ ํน์ง์ผ๋ก ์ถ๊ฐํ๊ณ ์ํ ์กฐ์ ์ฌ๊ฑด(์ฅ์ ๋ฌผ ๊ทผ์ ยท๊ฒฝ์ฐฉ๋ฅยท์์ธ ํ๊ณ) ํ์ง ๊ท์น ์์ฑํ์ฌ ์์ธ์ ๋ถ์ํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/trn01_advanced_detector.py ๋ฐ ์์ธ ํ์ง ํ
์คํธ ๊ฒฐ๊ณผ` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ธฐ๋ง/๊ต๋ ๊ณต๊ฒฉ์ ๋ํ ๋ฐฉ์ด ๋ก์ง์ ํ์ค์ฑ ๋ฐ ์ ๋ขฐ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ๋ณตํฉ ์ํ ํ์ง ์ฌํ์จ(Recall) 85% ์ด์, ์ฒ๋ฆฌ ์ง์ฐ 200ms ์ดํ\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ๋ณตํฉ ์ํ ์๋๋ฆฌ์ค 10์ข
์ ๋ํ ํ์ง ํ
์คํธ ํต๊ณผ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ํฉ์ฑ ์ํ ๋ฐ์ดํฐ์
์ฃผ์
์ ์ด์์น ํ์ง ์ ํ๋ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"๋๋ฐ ์ฅ์ ๋ฌผ ํํผ ๋ฐ์์๊ฐยทํํผ ๊ฑฐ๋ฆฌ, ๋น์ ์ฐฉ๋ฅ ์ ์ง ์๋, ์์ธ ํ๊ณ ์ด๊ณผ, ๊ธ์กฐ์ยท๊ณผ๋ณด์ ํน์ง์ ์ถ๊ฐํ ์ ๋ฐ ์๋ จ๋ ํ๊ฐ ์์ง์ผ๋ก ๊ณ ๋ํํ๊ณ , ๋ณตํฉ ์๋๋ฆฌ์ค 10์ข
์์ ์ํ ์กฐ์ ์ฌ๊ฑด(์ฅ์ ๋ฌผ ๊ทผ์ ยท๊ฒฝ์ฐฉ๋ฅยท์์ธ ํ๊ณ) ํ์ง ์ฌํ์จ, ๋ฑ๊ธ ์ผ์น์จ, ๋ก๊ทธ๋น ์ฒ๋ฆฌ ์ง์ฐ์ ๊ฒ์ฆํ๋ค.\"\"\"\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from sklearn.linear_model import LinearRegression, Ridge\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ ์์ฑ๊ธฐ (์ค์ ํ๋ จ์ยท์ฅ๋น ์ ๋ณด ์์, ์กฐ์ข
์๋ ๊ฐ๋ช
P001~)\n",
"# ๋นํ 1ํ = 120์ด(10Hz): ๊ฒฝ๋ก ๋นํ(0~60์ด) -> ํธ๋ฒ๋ง(60~85์ด) -> ๋๋ฐ ์ฅ์ ๋ฌผ ํํผ(85์ด) -> ๋น์ ์ฐฉ๋ฅ(100์ด ์ง์)\n",
"# ์กฐ์ข
์๋ง๋ค ์จ์ ์๋ จ๋(skill)์ ์กฐ์ ์ต๊ด(๊ธ์กฐ์ jerky, ๊ณผ๋ณด์ overcorr)์ด ์๊ณ , ๊ต๊ด ์ ์๋ ์ด๋ฅผ ๋ฐ์ํด ๋ง๋ ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"DT, STEPS = 0.1, 1200\n",
"T = np.arange(STEPS) * DT\n",
"COND = {\"day\": dict(wind=0.4, gust=0.0, gps=0.25, delay=0.0), \"night\": dict(wind=0.4, gust=0.0, gps=0.5, delay=0.15),\n",
" \"gust\": dict(wind=1.2, gust=2.5, gps=0.25, delay=0.0)}\n",
"OBST_T, LAND_T = 85.0, 100.0\n",
"\n",
"\n",
"def make_pilots(n=40, seed=7):\n",
" rng = np.random.default_rng(seed)\n",
" skill = rng.beta(2.2, 2.0, n)\n",
" return pd.DataFrame({\"pilot\": [f\"P{i + 1:03d}\" for i in range(n)], \"skill\": skill,\n",
" \"jerky\": rng.random(n) < 0.15 + 0.35 * (1 - skill), \"overcorr\": rng.random(n) < 0.15 + 0.35 * (1 - skill)})\n",
"\n",
"\n",
"def target_path(t):\n",
" \"\"\"์ฌ๊ฐํ ๊ฒฝ๋ก(40x30m, ๊ณ ๋ 20m)๋ฅผ 3m/s๋ก ๋นํ -> 60์ด ์ง์ ์์ ํธ๋ฒ๋ง -> ์ฐฉ๋ฅ ์ง์ ํ ๊ณ ๋ 0.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 4] ๋๋ฐ ์ฅ์ ๋ฌผ ํํผ ๋ฐ ๋น์ ์ฐฉ๋ฅ ๋ฐ์ ์๋ ๊ธฐ๋ฐ ์ ๋ฐ ์๋ จ๋ ํ๊ฐ ์์ง ๊ณ ๋ํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def fly(pilots, cond=\"day\", seed=0, delay_extra=0.0, obstacle_side=None):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์์ ๋นํ 1ํ๋ฅผ ๋์์ ๋ชจ์ํ๋ค(๋ฒกํฐํ). ๋ฐํ: ๋ก๊ทธ ๋ฐฐ์ด dict, ๋นํ๋ณ ์ ๋ต ์ ๋ณด DataFrame.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 4] ๋๋ฐ ์ฅ์ ๋ฌผ ํํผ ๋ฐ ๋น์ ์ฐฉ๋ฅ ๋ฐ์ ์๋ ๊ธฐ๋ฐ ์ ๋ฐ ์๋ จ๋ ํ๊ฐ ์์ง ๊ณ ๋ํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def one(log, j):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ์์ j๋ฒ์งธ ์กฐ์ข
์ 1๋ช
์ ๋ก๊ทธ(DataFrame)๋ฅผ ๊บผ๋ธ๋ค.\"\"\"\n",
" return pd.DataFrame({k: (v if v.ndim == 1 else v[:, j]) for k, v in log.items()})\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ๋นํ๋ก๊ทธ ํน์ง ์ถ์ถ ๊ณตํต ํจ์(์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ๋ฅผ ํ ๋ฒ์ ๊ณ์ฐ) ----\n",
"from scipy.ndimage import uniform_filter1d\n",
"\n",
"BASE_F = [\"path_rmse\", \"hover_drift\", \"hover_std\", \"alt_err\"]\n",
"ADV_F = BASE_F + [\"obs_rt\", \"obs_lateral\", \"touch_vz\", \"att_exceed\", \"jerk\", \"overshoot\", \"n_risk\"]\n",
"RULES = {\"near_collision\": (\"obs_lateral\", \"<\", 2.5), \"hard_landing\": (\"touch_vz\", \">\", 2.0), \"attitude_exceed\": (\"att_exceed\", \">=\", 10)}\n",
"\n",
"\n",
"def detect(df):\n",
" \"\"\"์ํ ์กฐ์ ์ฌ๊ฑด ํ์ง ๊ท์น: ์ฅ์ ๋ฌผ 2.5์ด ํ ํํผ ๊ฑฐ๋ฆฌ 2.5m ๋ฏธ๋ง, ์ ์ง ์๋ 2.0m/s ์ด๊ณผ, ์์ธ 35๋ ์ด๊ณผ 1์ด ์ด์.\"\"\"\n",
" out = {}\n",
" for ev, (f, op, th) in RULES.items():\n",
" out[ev] = df[f] < th if op == \"<\" else (df[f] > th if op == \">\" else df[f] >= th)\n",
" return pd.DataFrame(out)\n",
"\n",
"\n",
"def features(log):\n",
" \"\"\"๊ฒฝ๋ก ์ดํ๋ยทํธ๋ฒ๋ง ์์ ์ฑ(๊ธฐ๋ณธ) + ์ฅ์ ๋ฌผ ๋ฐ์ยท์ฐฉ๋ฅ ์๋ยท์์ธ ํ๊ณยท๊ธ์กฐ์ยท๊ณผ๋ณด์ (๊ณ ๋ํ) ํน์ง.\"\"\"\n",
" X, Y, A = (uniform_filter1d(log[k], 9, axis=0) for k in (\"x\", \"y\", \"alt\")) # GPS ์ก์ ์ํ(์ด๋ํ๊ท 0.9์ด)\n",
" h = np.hypot(X - log[\"tx\"], Y - log[\"ty\"])\n",
" cr, hv = slice(0, 600), slice(620, 850)\n",
" k0, k1 = int(OBST_T / DT), int((OBST_T + 2.5) / DT)\n",
" side = np.sign(log[\"ty\"][k0 + 1] - log[\"ty\"][k0 - 1])\n",
" moved = (log[\"stick_roll\"][k0:k0 + 40] * side) > 0.3\n",
" rt = np.where(moved.any(0), moved.argmax(0) * DT, 4.0)\n",
" vz = -np.gradient(A, DT, axis=0)\n",
" land = np.argmax((A < 0.4) & (T[:, None] >= LAND_T), axis=0)\n",
" touch = np.array([vz[max(i - 6, 0):i - 2, j].mean() if i > 6 else 3.0 for j, i in enumerate(land)])\n",
" ds = np.abs(np.diff(np.stack([log[\"stick_roll\"], log[\"stick_pitch\"]]), axis=1)) / DT\n",
" return pd.DataFrame({\n",
" \"path_rmse\": np.sqrt((h[cr] ** 2).mean(0)), \"hover_drift\": h[hv].mean(0), \"hover_std\": np.hypot(X[hv].std(0), Y[hv].std(0)),\n",
" \"alt_err\": np.abs(A[:850] - log[\"talt\"][:850]).mean(0),\n",
" \"obs_rt\": rt, \"obs_lateral\": np.abs(Y[k1] - Y[k0]), \"touch_vz\": touch,\n",
" \"att_exceed\": (np.maximum(np.abs(log[\"roll_deg\"]), np.abs(log[\"pitch_deg\"])) > 35).sum(0),\n",
" \"jerk\": np.percentile(ds, 95, axis=(0, 1)),\n",
" \"overshoot\": np.clip(np.abs(Y[k0:k0 + 120] - Y[k0]).max(0) - 6.0, 0, None)}).pipe(\n",
" lambda d: d.assign(n_risk=detect(d).sum(axis=1))) # overshoot: ํํผ ํ ๋ชฉํ 6m๋ฅผ ๋์ด์ ๊ฑฐ๋ฆฌ(๊ณผ๋ณด์ )\n",
"\n",
"\n",
"def make_dataset(n_pilots=40, seed=7, conds=(\"day\", \"night\", \"gust\"), flights=1):\n",
" \"\"\"์กฐ์ข
์ n๋ช
x ์กฐ๊ฑด x ๋ฐ๋ณต ๋นํ -> ๋นํ๋ณ ํน์ง + ์ ๋ต(๊ต๊ด ์ ์ยท๋ฑ๊ธยท์ํ ์ฌ๊ฑด) ํ.\"\"\"\n",
" pilots = make_pilots(n_pilots, seed)\n",
" out = []\n",
" for c in conds:\n",
" for f in range(flights):\n",
" log, tr = fly(pilots, c, seed=seed * 100 + list(COND).index(c) * 10 + f)\n",
" out.append(pd.concat([tr.reset_index(drop=True), features(log)], axis=1).assign(flight=f))\n",
" return pd.concat(out, ignore_index=True)\n",
"\n",
"\n",
"def cond_z(df, ref, cols):\n",
" \"\"\"์กฐ๊ฑด๋ณ ๊ธฐ์ค ์ง๋จ(ref)์ ํ๊ท ยทํ์คํธ์ฐจ๋ก ํ์คํ: ๋ํ์ฒ๋ผ ์ด๋ ค์ด ์กฐ๊ฑด์์ ๋ฐ๋ ๋ถ์ด์ต์ ์ค์ธ๋ค.\"\"\"\n",
" z = df.copy()\n",
" z[cols] = z[cols].astype(float)\n",
" for c in df[\"cond\"].unique():\n",
" r = ref[ref[\"cond\"] == c][cols]\n",
" z.loc[df[\"cond\"] == c, cols] = (df.loc[df[\"cond\"] == c, cols] - r.mean()) / (r.std() + 1e-9)\n",
" return z\n",
"\n",
"\n",
"def to_grade(score):\n",
" return np.where(score >= 75, \"A\", np.where(score >= 55, \"B\", \"C\"))\n",
"\n",
"train = make_dataset(40, seed=7)\n",
"zt = cond_z(train, train, ADV_F)\n",
"base = LinearRegression().fit(cond_z(train, train, BASE_F)[BASE_F], train[\"instructor\"])\n",
"eng = Ridge(alpha=1.0).fit(zt[ADV_F], train[\"instructor\"])\n",
"\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ณตํฉ ์๋๋ฆฌ์ค 10์ข
: (์กฐ๊ฑด, ์์ ์ง์ฐ ์ถ๊ฐ ์ด, ์ฅ์ ๋ฌผ ๋ฐฉํฅ)\n",
"SCEN = [(\"1 ์ฃผ๊ฐ ๊ธฐ๋ณธ\", \"day\", 0.0, None), (\"2 ์ผ๊ฐ\", \"night\", 0.0, None), (\"3 ๋ํ\", \"gust\", 0.0, None), (\"4 ์ฅ์ ๋ฌผ ์ข์ธก\", \"day\", 0.0, -1),\n",
" (\"5 ์ฅ์ ๋ฌผ ์ฐ์ธก\", \"day\", 0.0, 1), (\"6 ์์ ์ง์ฐ 0.3์ด\", \"day\", 0.3, None), (\"7 ๋ํ+์ฅ์ ๋ฌผ ์ข์ธก\", \"gust\", 0.0, -1),\n",
" (\"8 ์ผ๊ฐ+์์ ์ง์ฐ\", \"night\", 0.3, None), (\"9 ๋ํ+์์ ์ง์ฐ\", \"gust\", 0.3, None), (\"10 ์ผ๊ฐ+์ฅ์ ๋ฌผ ์ฐ์ธก\", \"night\", 0.0, 1)]\n",
"pilots = make_pilots(40, seed=8)\n",
"rows, all_det, ms = [], [], []\n",
"for i, (name, cond, dly, side) in enumerate(SCEN):\n",
" log, tr = fly(pilots, cond, seed=500 + i, delay_extra=dly, obstacle_side=side)\n",
" t0 = time.perf_counter()\n",
" f = features(log)\n",
" df = pd.concat([tr.reset_index(drop=True), f], axis=1)\n",
" df[\"score\"] = np.clip(eng.predict(cond_z(df, train, ADV_F)[ADV_F]), 0, 100)\n",
" det = detect(df)\n",
" ms.append((time.perf_counter() - t0) * 1000 / len(df))\n",
" df[\"base\"] = np.clip(base.predict(cond_z(df, train, BASE_F)[BASE_F]), 0, 100)\n",
" truth = df[list(RULES)].astype(bool)\n",
" tp, pos = int((det & truth).values.sum()), int(truth.values.sum())\n",
" all_det.append((det, truth))\n",
" rows.append({\"์๋๋ฆฌ์ค\": name, \"์ํ์ฌ๊ฑด\": pos, \"ํ์ง\": tp, \"์ฌํ์จ\": round(tp / max(pos, 1), 3), \"์คํ\": int((det & ~truth).values.sum()),\n",
" \"Baseline ์ผ์น\": round((to_grade(df[\"base\"]) == df[\"grade\"]).mean(), 3), \"๊ณ ๋ํ ์ผ์น\": round((to_grade(df[\"score\"]) == df[\"grade\"]).mean(), 3)})\n",
"R = pd.DataFrame(rows)\n",
"print(R.to_string(index=False))\n",
"rec = R[\"ํ์ง\"].sum() / R[\"์ํ์ฌ๊ฑด\"].sum()\n",
"print(f\"\\n์ํ ์กฐ์ ์ฌ๊ฑด ํ์ง ์ฌํ์จ {rec:.1%} (๋ชฉํ 85% ์ด์: {'๋ฌ์ฑ' if rec >= 0.85 else '๋ฏธ๋ฌ'}), ๋ก๊ทธ๋น ์ฒ๋ฆฌ {np.mean(ms):.1f} ms (์ต๋ {np.max(ms):.1f}, ๋ชฉํ 200 ms ์ดํ)\")\n",
"print(f\"๋ฑ๊ธ ์ผ์น์จ Baseline {R['Baseline ์ผ์น'].mean():.1%} -> ๊ณ ๋ํ ์์ง {R['๊ณ ๋ํ ์ผ์น'].mean():.1%}, 10์ข
์ค ์ฌํ์จ 85% ์ด์ {(R['์ฌํ์จ'] >= 0.85).sum()}๊ฐ\")\n",
"\n",
"# ์ฌ๊ฑด๋ณ ์ฌํ์จยท์คํ(์ค๊ฒฝ๋ณด ์ ๋ฐ ํจํด)\n",
"det = pd.concat([d for d, _ in all_det]); tru = pd.concat([t for _, t in all_det])\n",
"print(\"\\n์ฌ๊ฑด ์ค์ ์ฌํ์จ ์คํ\")\n",
"for ev in RULES:\n",
" print(f\"{ev:15s} {tru[ev].sum():4d} {(det[ev] & tru[ev]).sum() / max(tru[ev].sum(), 1):.2f} {(det[ev] & ~tru[ev]).sum():4d}\")\n",
"print(\"์คํ ํจํด: ์ ์ง ์๋๋ ๊ณ ๋ ๋ก๊ทธ๋ฅผ ๋ฏธ๋ถํด ์ถ์ ํ๋ฏ๋ก ๊ณ ๋ ์ก์์ด ํฐ ๋นํ์์ ๊ณผ๋ ์ถ์ ๋๋ค -> ์ ์ง ์ง์ 0.4์ด ํ๊ท ์ฌ์ฉ, ์๊ณ๊ฐ 2.0m/s๋ก ์ฌ์ ๋ฅผ ๋ \")\n",
"\n",
"w = pd.Series(eng.coef_, index=ADV_F).sort_values()\n",
"print(\"\\n์์ง ๊ฐ์ค์น(์กฐ๊ฑด๋ณ ํ์คํ ํน์ง 1๋จ์๋น ์ ์):\", \", \".join(f\"{k} {v:+.1f}\" for k, v in w.items()))\n",
"\n",
"plt.figure(figsize=(8, 4))\n",
"plt.barh(w.index, w.values, color=[\"tab:red\" if v < 0 else \"tab:blue\" for v in w.values])\n",
"plt.xlabel(\"score change per 1 SD\"); plt.title(\"Advanced skill engine weights (synthetic)\"); plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
04 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
" ์๋๋ฆฌ์ค ์ํ์ฌ๊ฑด ํ์ง ์ฌํ์จ ์คํ Baseline ์ผ์น ๊ณ ๋ํ ์ผ์น\n",
" 1 ์ฃผ๊ฐ ๊ธฐ๋ณธ 47 47 1.000 3 0.725 0.800\n",
" 2 ์ผ๊ฐ 46 45 0.978 3 0.775 0.775\n",
" 3 ๋ํ 65 65 1.000 7 0.625 0.825\n",
" 4 ์ฅ์ ๋ฌผ ์ข์ธก 42 41 0.976 4 0.800 0.875\n",
" 5 ์ฅ์ ๋ฌผ ์ฐ์ธก 37 36 0.973 7 0.825 0.850\n",
"6 ์์ ์ง์ฐ 0.3์ด 52 49 0.942 3 0.675 0.625\n",
" 7 ๋ํ+์ฅ์ ๋ฌผ ์ข์ธก 56 56 1.000 6 0.750 0.800\n",
" 8 ์ผ๊ฐ+์์ ์ง์ฐ 54 51 0.944 5 0.650 0.675\n",
" 9 ๋ํ+์์ ์ง์ฐ 63 63 1.000 7 0.750 0.750\n",
"10 ์ผ๊ฐ+์ฅ์ ๋ฌผ ์ฐ์ธก 46 44 0.957 5 0.825 0.800\n",
"\n",
"์ํ ์กฐ์ ์ฌ๊ฑด ํ์ง ์ฌํ์จ 97.8% (๋ชฉํ 85% ์ด์: ๋ฌ์ฑ), ๋ก๊ทธ๋น ์ฒ๋ฆฌ 0.6 ms (์ต๋ 0.9, ๋ชฉํ 200 ms ์ดํ)\n",
"๋ฑ๊ธ ์ผ์น์จ Baseline 74.0% -> ๊ณ ๋ํ ์์ง 77.8%, 10์ข
์ค ์ฌํ์จ 85% ์ด์ 10๊ฐ\n",
"\n",
"์ฌ๊ฑด ์ค์ ์ฌํ์จ ์คํ\n",
"near_collision 187 0.94 6\n",
"hard_landing 256 1.00 44\n",
"attitude_exceed 65 1.00 0\n",
"์คํ ํจํด: ์ ์ง ์๋๋ ๊ณ ๋ ๋ก๊ทธ๋ฅผ ๋ฏธ๋ถํด ์ถ์ ํ๋ฏ๋ก ๊ณ ๋ ์ก์์ด ํฐ ๋นํ์์ ๊ณผ๋ ์ถ์ ๋๋ค -> ์ ์ง ์ง์ 0.4์ด ํ๊ท ์ฌ์ฉ, ์๊ณ๊ฐ 2.0m/s๋ก ์ฌ์ ๋ฅผ ๋ \n",
"\n",
"์์ง ๊ฐ์ค์น(์กฐ๊ฑด๋ณ ํ์คํ ํน์ง 1๋จ์๋น ์ ์): alt_err -6.7, jerk -3.1, touch_vz -2.6, n_risk -2.4, hover_drift -2.2, obs_rt -1.1, overshoot -1.1, att_exceed -0.4, path_rmse +0.2, obs_lateral +2.0, hover_std +2.2\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
05. ์กฐ์ข
์๋ณ ์กฐ์ ์ต๊ด(๊ธ์กฐ์, ๊ณผ๋ณด์ ) ๋ถ์ ๋ฐ ์ ๋ขฐ๋ ๋ฑ๊ธ ์ฐ์ถ ๋ชจ๋ ๊ฐ๋ฐ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์กฐ์ข
์๋ณ ์กฐ์ ์ต๊ด(๊ธ์กฐ์, ๊ณผ๋ณด์ )์ ๋ถ์ํ๊ณ ์๋ จ๋ ๋ฑ๊ธ ๋ฐ ์ ๋ขฐ๋ ์ ์๋ฅผ ์ฐ์ถํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์กฐ์ข
์๋ณ ์กฐ์ ์ต๊ด(๊ธ์กฐ์, ๊ณผ๋ณด์ )์ ๋ถ์ํ๊ณ ์ฌ๋ฌ ๋นํ์ ๋ฌถ์ด ์๋ จ๋ ๋ฑ๊ธ๊ณผ ์ ๋ขฐ๋ ๋ฑ๊ธ(High/Medium/Low)์ ์ฐ์ถํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ฐ์ ์กฐ๊ฑด์ ์ต๊ด ์๋ ์กฐ์ข
์ ๋๋น ์คํฑ ๋ณํ์จ๊ณผ ํํผ ํ ์ด๊ณผ ์ด๋์ผ๋ก ์ต๊ด ํ์ ํ๊ณ ์ ํ๋๋ฅผ ์ธก์ ํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/verification/trn01_confidence_engine.py` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ ๋ขฐ๋ ๋ฑ๊ธ(High/Med/Low) ๋ถ์ฌ ๊ธฐ์ค์ ๊ตฐ์ฌ์ ํ๋น์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์ค๊ฒฝ๋ณด์จ(FAR) 40% ์ด์ ๊ฐ์ถ, ์ ๋ขฐ๋ ์ค์ฝ์ด ์ฐ์ถ ์ผ๊ด์ฑ 90% ์ด์\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์ค๊ฒฝ๋ณด ํํฐ๋ง ์ ํ ๋น๊ต ์งํ ์ฐ์ถ ๋ฐ ํตํฉ ํ
์คํธ ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ROC-AUC ๊ณก์ ๋ถ์ ๋ฐ ์๊ณ๊ฐ ๋ฏผ๊ฐ๋ ํ
์คํธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"์กฐ์ข
์๋ณ ์ฌ๋ฌ ๋นํ์ ์กฐ์ ์ต๊ด(๊ธ์กฐ์=์คํฑ ๋ณํ์จ, ๊ณผ๋ณด์ =ํํผ ํ ์ด๊ณผ ์ด๋)์ ๋ถ์ํ๊ณ , ๋นํ ์ ์๋ฅผ ๋ฌถ์ด ์๋ จ๋ ๋ฑ๊ธ๊ณผ ์ ๋ขฐ๋(๋ถํธ์คํธ๋ฉ ๋ฑ๊ธ ํ๋ฅ )๋ฅผ ์ฐ์ถํ๋ค. ๋นํ 1ํ๋ก '์ฌ๊ต์ก ๋์'์ ์ง์ ํ๋ ๋ฐฉ์ ๋๋น ์ค์ง์ ๋ฅ (FAR) ๊ฐ์ถ, ROC-AUC, ์๊ณ๊ฐ ๋ฏผ๊ฐ๋, ๋ฑ๊ธ ์ผ๊ด์ฑ์ ๊ฒ์ฆํ๋ค.\"\"\"\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from sklearn.linear_model import Ridge\n",
"from sklearn.metrics import roc_auc_score\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ ์์ฑ๊ธฐ (์ค์ ํ๋ จ์ยท์ฅ๋น ์ ๋ณด ์์, ์กฐ์ข
์๋ ๊ฐ๋ช
P001~)\n",
"# ๋นํ 1ํ = 120์ด(10Hz): ๊ฒฝ๋ก ๋นํ(0~60์ด) -> ํธ๋ฒ๋ง(60~85์ด) -> ๋๋ฐ ์ฅ์ ๋ฌผ ํํผ(85์ด) -> ๋น์ ์ฐฉ๋ฅ(100์ด ์ง์)\n",
"# ์กฐ์ข
์๋ง๋ค ์จ์ ์๋ จ๋(skill)์ ์กฐ์ ์ต๊ด(๊ธ์กฐ์ jerky, ๊ณผ๋ณด์ overcorr)์ด ์๊ณ , ๊ต๊ด ์ ์๋ ์ด๋ฅผ ๋ฐ์ํด ๋ง๋ ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"DT, STEPS = 0.1, 1200\n",
"T = np.arange(STEPS) * DT\n",
"COND = {\"day\": dict(wind=0.4, gust=0.0, gps=0.25, delay=0.0), \"night\": dict(wind=0.4, gust=0.0, gps=0.5, delay=0.15),\n",
" \"gust\": dict(wind=1.2, gust=2.5, gps=0.25, delay=0.0)}\n",
"OBST_T, LAND_T = 85.0, 100.0\n",
"\n",
"\n",
"def make_pilots(n=40, seed=7):\n",
" rng = np.random.default_rng(seed)\n",
" skill = rng.beta(2.2, 2.0, n)\n",
" return pd.DataFrame({\"pilot\": [f\"P{i + 1:03d}\" for i in range(n)], \"skill\": skill,\n",
" \"jerky\": rng.random(n) < 0.15 + 0.35 * (1 - skill), \"overcorr\": rng.random(n) < 0.15 + 0.35 * (1 - skill)})\n",
"\n",
"\n",
"def target_path(t):\n",
" \"\"\"์ฌ๊ฐํ ๊ฒฝ๋ก(40x30m, ๊ณ ๋ 20m)๋ฅผ 3m/s๋ก ๋นํ -> 60์ด ์ง์ ์์ ํธ๋ฒ๋ง -> ์ฐฉ๋ฅ ์ง์ ํ ๊ณ ๋ 0.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 5] ์กฐ์ข
์๋ณ ์กฐ์ ์ต๊ด(๊ธ์กฐ์, ๊ณผ๋ณด์ ) ๋ถ์ ๋ฐ ์ ๋ขฐ๋ ๋ฑ๊ธ ์ฐ์ถ ๋ชจ๋ ๊ฐ๋ฐ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def fly(pilots, cond=\"day\", seed=0, delay_extra=0.0, obstacle_side=None):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์์ ๋นํ 1ํ๋ฅผ ๋์์ ๋ชจ์ํ๋ค(๋ฒกํฐํ). ๋ฐํ: ๋ก๊ทธ ๋ฐฐ์ด dict, ๋นํ๋ณ ์ ๋ต ์ ๋ณด DataFrame.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 5] ์กฐ์ข
์๋ณ ์กฐ์ ์ต๊ด(๊ธ์กฐ์, ๊ณผ๋ณด์ ) ๋ถ์ ๋ฐ ์ ๋ขฐ๋ ๋ฑ๊ธ ์ฐ์ถ ๋ชจ๋ ๊ฐ๋ฐ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def one(log, j):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ์์ j๋ฒ์งธ ์กฐ์ข
์ 1๋ช
์ ๋ก๊ทธ(DataFrame)๋ฅผ ๊บผ๋ธ๋ค.\"\"\"\n",
" return pd.DataFrame({k: (v if v.ndim == 1 else v[:, j]) for k, v in log.items()})\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ๋นํ๋ก๊ทธ ํน์ง ์ถ์ถ ๊ณตํต ํจ์(์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ๋ฅผ ํ ๋ฒ์ ๊ณ์ฐ) ----\n",
"from scipy.ndimage import uniform_filter1d\n",
"\n",
"BASE_F = [\"path_rmse\", \"hover_drift\", \"hover_std\", \"alt_err\"]\n",
"ADV_F = BASE_F + [\"obs_rt\", \"obs_lateral\", \"touch_vz\", \"att_exceed\", \"jerk\", \"overshoot\", \"n_risk\"]\n",
"RULES = {\"near_collision\": (\"obs_lateral\", \"<\", 2.5), \"hard_landing\": (\"touch_vz\", \">\", 2.0), \"attitude_exceed\": (\"att_exceed\", \">=\", 10)}\n",
"\n",
"\n",
"def detect(df):\n",
" \"\"\"์ํ ์กฐ์ ์ฌ๊ฑด ํ์ง ๊ท์น: ์ฅ์ ๋ฌผ 2.5์ด ํ ํํผ ๊ฑฐ๋ฆฌ 2.5m ๋ฏธ๋ง, ์ ์ง ์๋ 2.0m/s ์ด๊ณผ, ์์ธ 35๋ ์ด๊ณผ 1์ด ์ด์.\"\"\"\n",
" out = {}\n",
" for ev, (f, op, th) in RULES.items():\n",
" out[ev] = df[f] < th if op == \"<\" else (df[f] > th if op == \">\" else df[f] >= th)\n",
" return pd.DataFrame(out)\n",
"\n",
"\n",
"def features(log):\n",
" \"\"\"๊ฒฝ๋ก ์ดํ๋ยทํธ๋ฒ๋ง ์์ ์ฑ(๊ธฐ๋ณธ) + ์ฅ์ ๋ฌผ ๋ฐ์ยท์ฐฉ๋ฅ ์๋ยท์์ธ ํ๊ณยท๊ธ์กฐ์ยท๊ณผ๋ณด์ (๊ณ ๋ํ) ํน์ง.\"\"\"\n",
" X, Y, A = (uniform_filter1d(log[k], 9, axis=0) for k in (\"x\", \"y\", \"alt\")) # GPS ์ก์ ์ํ(์ด๋ํ๊ท 0.9์ด)\n",
" h = np.hypot(X - log[\"tx\"], Y - log[\"ty\"])\n",
" cr, hv = slice(0, 600), slice(620, 850)\n",
" k0, k1 = int(OBST_T / DT), int((OBST_T + 2.5) / DT)\n",
" side = np.sign(log[\"ty\"][k0 + 1] - log[\"ty\"][k0 - 1])\n",
" moved = (log[\"stick_roll\"][k0:k0 + 40] * side) > 0.3\n",
" rt = np.where(moved.any(0), moved.argmax(0) * DT, 4.0)\n",
" vz = -np.gradient(A, DT, axis=0)\n",
" land = np.argmax((A < 0.4) & (T[:, None] >= LAND_T), axis=0)\n",
" touch = np.array([vz[max(i - 6, 0):i - 2, j].mean() if i > 6 else 3.0 for j, i in enumerate(land)])\n",
" ds = np.abs(np.diff(np.stack([log[\"stick_roll\"], log[\"stick_pitch\"]]), axis=1)) / DT\n",
" return pd.DataFrame({\n",
" \"path_rmse\": np.sqrt((h[cr] ** 2).mean(0)), \"hover_drift\": h[hv].mean(0), \"hover_std\": np.hypot(X[hv].std(0), Y[hv].std(0)),\n",
" \"alt_err\": np.abs(A[:850] - log[\"talt\"][:850]).mean(0),\n",
" \"obs_rt\": rt, \"obs_lateral\": np.abs(Y[k1] - Y[k0]), \"touch_vz\": touch,\n",
" \"att_exceed\": (np.maximum(np.abs(log[\"roll_deg\"]), np.abs(log[\"pitch_deg\"])) > 35).sum(0),\n",
" \"jerk\": np.percentile(ds, 95, axis=(0, 1)),\n",
" \"overshoot\": np.clip(np.abs(Y[k0:k0 + 120] - Y[k0]).max(0) - 6.0, 0, None)}).pipe(\n",
" lambda d: d.assign(n_risk=detect(d).sum(axis=1))) # overshoot: ํํผ ํ ๋ชฉํ 6m๋ฅผ ๋์ด์ ๊ฑฐ๋ฆฌ(๊ณผ๋ณด์ )\n",
"\n",
"\n",
"def make_dataset(n_pilots=40, seed=7, conds=(\"day\", \"night\", \"gust\"), flights=1):\n",
" \"\"\"์กฐ์ข
์ n๋ช
x ์กฐ๊ฑด x ๋ฐ๋ณต ๋นํ -> ๋นํ๋ณ ํน์ง + ์ ๋ต(๊ต๊ด ์ ์ยท๋ฑ๊ธยท์ํ ์ฌ๊ฑด) ํ.\"\"\"\n",
" pilots = make_pilots(n_pilots, seed)\n",
" out = []\n",
" for c in conds:\n",
" for f in range(flights):\n",
" log, tr = fly(pilots, c, seed=seed * 100 + list(COND).index(c) * 10 + f)\n",
" out.append(pd.concat([tr.reset_index(drop=True), features(log)], axis=1).assign(flight=f))\n",
" return pd.concat(out, ignore_index=True)\n",
"\n",
"\n",
"def cond_z(df, ref, cols):\n",
" \"\"\"์กฐ๊ฑด๋ณ ๊ธฐ์ค ์ง๋จ(ref)์ ํ๊ท ยทํ์คํธ์ฐจ๋ก ํ์คํ: ๋ํ์ฒ๋ผ ์ด๋ ค์ด ์กฐ๊ฑด์์ ๋ฐ๋ ๋ถ์ด์ต์ ์ค์ธ๋ค.\"\"\"\n",
" z = df.copy()\n",
" z[cols] = z[cols].astype(float)\n",
" for c in df[\"cond\"].unique():\n",
" r = ref[ref[\"cond\"] == c][cols]\n",
" z.loc[df[\"cond\"] == c, cols] = (df.loc[df[\"cond\"] == c, cols] - r.mean()) / (r.std() + 1e-9)\n",
" return z\n",
"\n",
"\n",
"def to_grade(score):\n",
" return np.where(score >= 75, \"A\", np.where(score >= 55, \"B\", \"C\"))\n",
"\n",
"train = make_dataset(40, seed=7)\n",
"eng = Ridge(alpha=1.0).fit(cond_z(train, train, ADV_F)[ADV_F], train[\"instructor\"])\n",
"base = Ridge(alpha=1.0).fit(cond_z(train, train, BASE_F)[BASE_F], train[\"instructor\"]) # ๋ฏธ์
3 Baseline ์ฑ์ ์\n",
"zt = cond_z(train, train, ADV_F) # ์ต๊ด ํ์ ๊ธฐ์ค: ๊ฐ์ ์กฐ๊ฑด์์ ์ต๊ด ์๋ ์กฐ์ข
์์ ์์ 10%\n",
"jerk_th = zt.loc[~train[\"jerky\"], \"jerk\"].quantile(0.9)\n",
"over_th = zt.loc[~train[\"overcorr\"], \"overshoot\"].quantile(0.9)\n",
"\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ํ๊ฐ ์กฐ์ข
์ 80๋ช
x ๋นํ 6ํ(์ฃผ๊ฐยท์ผ๊ฐยท๋ํ ๊ฐ 2ํ)\n",
"test = make_dataset(80, seed=9, flights=2)\n",
"test[\"score\"] = np.clip(eng.predict(cond_z(test, train, ADV_F)[ADV_F]), 0, 100)\n",
"ze = cond_z(test, train, ADV_F)\n",
"test[\"jerk_z\"], test[\"over_z\"] = ze[\"jerk\"], ze[\"overshoot\"]\n",
"test[\"base\"] = np.clip(base.predict(cond_z(test, train, BASE_F)[BASE_F]), 0, 100)\n",
"rng = np.random.default_rng(0)\n",
"rows = []\n",
"for pid, g in test.groupby(\"pilot\"):\n",
" boot = [to_grade(rng.choice(g[\"score\"].values, len(g)).mean()).item() for _ in range(200)]\n",
" grade = to_grade(g[\"score\"].mean()).item()\n",
" conf = boot.count(grade) / len(boot)\n",
" rows.append({\"pilot\": pid, \"์ ์\": g[\"score\"].mean(), \"์ ์SD\": g[\"score\"].std(), \"๋ฑ๊ธ\": grade, \"์ ๋ขฐ๋\": conf,\n",
" \"์ ๋ขฐ๋ฑ๊ธ\": \"High\" if conf >= 0.9 else (\"Medium\" if conf >= 0.7 else \"Low\"),\n",
" \"๊ธ์กฐ์\": (g[\"jerk_z\"] > jerk_th).mean() >= 0.5, \"๊ณผ๋ณด์ \": (g[\"over_z\"] > over_th).mean() >= 0.5,\n",
" \"์ค์ ๊ธ์กฐ์\": g[\"jerky\"].iloc[0], \"์ค์ ๊ณผ๋ณด์ \": g[\"overcorr\"].iloc[0],\n",
" \"๊ต๊ด๋ฑ๊ธ\": to_grade(g[\"instructor\"].mean()).item(), \"๋จ์ผ๋นํ์ ์\": g[\"base\"].iloc[int(rng.integers(len(g)))]})\n",
"P = pd.DataFrame(rows)\n",
"print(f\"์ต๊ด ํ์ ์ ํ๋: ๊ธ์กฐ์ {(P['๊ธ์กฐ์'] == P['์ค์ ๊ธ์กฐ์']).mean():.0%}, ๊ณผ๋ณด์ {(P['๊ณผ๋ณด์ '] == P['์ค์ ๊ณผ๋ณด์ ']).mean():.0%} (์กฐ์ข
์ {len(P)}๋ช
)\")\n",
"\n",
"# ์ฌ๊ต์ก ๋์(C) ์ง์ : ๊ธฐ์กด(๋ฌด์์ ๋นํ 1ํ์ Baseline ์ ์ 55 ๋ฏธ๋ง) vs ์์ง(6ํ ํ๊ท C + ์ ๋ขฐ๋ Medium ์ด์, Low๋ ๊ต๊ด ํ์ธ์ผ๋ก ๋ณด๋)\n",
"need = P[\"๊ต๊ด๋ฑ๊ธ\"] == \"C\"\n",
"old = P[\"๋จ์ผ๋นํ์ ์\"] < 55\n",
"new = (P[\"๋ฑ๊ธ\"] == \"C\") & (P[\"์ ๋ขฐ๋ฑ๊ธ\"] != \"Low\")\n",
"review = (P[\"๋ฑ๊ธ\"] == \"C\") & (P[\"์ ๋ขฐ๋ฑ๊ธ\"] == \"Low\")\n",
"\n",
"\n",
"def far_rec(flag):\n",
" return (flag & ~need).sum() / max(flag.sum(), 1), (flag & need).sum() / max(need.sum(), 1), int(flag.sum())\n",
"\n",
"\n",
"f0, r0, n0 = far_rec(old)\n",
"f1, r1, n1 = far_rec(new)\n",
"print(pd.DataFrame([{\"๋ฐฉ์\": \"๊ธฐ์กด(๋นํ 1ํ Baseline<55)\", \"์ง์ \": n0, \"์ค์ง์ ๋ฅ \": round(f0, 3), \"์ฌํ์จ\": round(r0, 3)},\n",
" {\"๋ฐฉ์\": \"์์ง(6ํ ํ๊ท C, ์ ๋ขฐ Medium ์ด์)\", \"์ง์ \": n1, \"์ค์ง์ ๋ฅ \": round(f1, 3), \"์ฌํ์จ\": round(r1, 3)}]).to_string(index=False))\n",
"cut = 1 - f1 / max(f0, 1e-9)\n",
"print(f\"์ค์ง์ ๋ฅ (FAR) ๊ฐ์ถ {cut:.1%} (๋ชฉํ 40% ์ด์: {'๋ฌ์ฑ' if cut >= 0.4 else '๋ฏธ๋ฌ'}), ์ ๋ขฐ๋ Low๋ก ๊ต๊ด ํ์ธ์ ๋๊ธด ์กฐ์ข
์ {int(review.sum())}๋ช
\")\n",
"print(f\"ROC-AUC(์ฌ๊ต์ก ํ์ ํ๋ณ, ํ๊ท ์ ์ ๊ธฐ์ค) {roc_auc_score(need, -P['์ ์']):.3f}\")\n",
"\n",
"print(\"\\n๊ธฐ์ค์ ์ ์ง์ ์ค์ง์ ๋ฅ ์ฌํ์จ\")\n",
"for th in [50, 55, 60, 65]:\n",
" f, r, n = far_rec(P[\"์ ์\"] < th)\n",
" print(f\" {th} {n:3d} {f:.3f} {r:.3f}\")\n",
"\n",
"# ๋ฑ๊ธ ์ผ๊ด์ฑ: ๋นํ 6ํ ์ค 5ํ๋ง ๋ฌด์์๋ก ์จ๋ ๊ฐ์ ๋ฑ๊ธ์ด ๋์ค๋ ๋น์จ\n",
"cons = []\n",
"for pid, g in test.groupby(\"pilot\"):\n",
" full = to_grade(g[\"score\"].mean()).item()\n",
" cons += [to_grade(g[\"score\"].sample(5, random_state=k).mean()).item() == full for k in range(20)]\n",
"print(f\"\\n๋ฑ๊ธ ์ผ๊ด์ฑ {np.mean(cons):.1%} (๋ชฉํ 90% ์ด์: {'๋ฌ์ฑ' if np.mean(cons) >= 0.9 else '๋ฏธ๋ฌ'})\")\n",
"print(\"[์ ๋ขฐ๋ฑ๊ธ ๋ถํฌ]\", P[\"์ ๋ขฐ๋ฑ๊ธ\"].value_counts().to_dict(), \"/ ๋ฑ๊ธ ์ผ์น(๊ต๊ด ๋๋น)\", f\"{(P['๋ฑ๊ธ'] == P['๊ต๊ด๋ฑ๊ธ']).mean():.0%}\")\n",
"print(\"์ ๋ขฐ๋ฑ๊ธ ๊ธฐ์ค: High(๋ถํธ์คํธ๋ฉ ๋์ผ ๋ฑ๊ธ 90% ์ด์)=๊ฒฐ๊ณผ ํต๋ณด, Medium(70~90%)=๊ต๊ด ๊ฒํ ํ ํต๋ณด, Low=์ถ๊ฐ ๋นํ ํ ์ฌํ๊ฐ\")\n",
"\n",
"P = P.sort_values(\"์ ์\")\n",
"plt.figure(figsize=(10, 4))\n",
"col = P[\"์ ๋ขฐ๋ฑ๊ธ\"].map({\"High\": \"tab:green\", \"Medium\": \"tab:orange\", \"Low\": \"tab:red\"})\n",
"plt.errorbar(range(len(P)), P[\"์ ์\"], yerr=P[\"์ ์SD\"] / np.sqrt(6) * 2, fmt=\"none\", ecolor=\"gray\")\n",
"plt.scatter(range(len(P)), P[\"์ ์\"], c=col)\n",
"plt.axhline(55, ls=\"--\", c=\"k\"); plt.axhline(75, ls=\"--\", c=\"k\")\n",
"plt.xticks(range(len(P)), P[\"pilot\"], rotation=90, fontsize=6); plt.ylabel(\"mean skill score (+-2 SE)\")\n",
"plt.title(\"Pilot skill score with uncertainty (color = confidence High/Medium/Low)\"); plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
05 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"์ต๊ด ํ์ ์ ํ๋: ๊ธ์กฐ์ 95%, ๊ณผ๋ณด์ 88% (์กฐ์ข
์ 80๋ช
)\n",
" ๋ฐฉ์ ์ง์ ์ค์ง์ ๋ฅ ์ฌํ์จ\n",
" ๊ธฐ์กด(๋นํ 1ํ Baseline<55) 27 0.111 0.750\n",
"์์ง(6ํ ํ๊ท C, ์ ๋ขฐ Medium ์ด์) 31 0.032 0.938\n",
"์ค์ง์ ๋ฅ (FAR) ๊ฐ์ถ 71.0% (๋ชฉํ 40% ์ด์: ๋ฌ์ฑ), ์ ๋ขฐ๋ Low๋ก ๊ต๊ด ํ์ธ์ ๋๊ธด ์กฐ์ข
์ 1๋ช
\n",
"ROC-AUC(์ฌ๊ต์ก ํ์ ํ๋ณ, ํ๊ท ์ ์ ๊ธฐ์ค) 0.997\n",
"\n",
"๊ธฐ์ค์ ์ ์ง์ ์ค์ง์ ๋ฅ ์ฌํ์จ\n",
" 50 22 0.000 0.688\n",
" 55 32 0.031 0.969\n",
" 60 43 0.256 1.000\n",
" 65 53 0.396 1.000\n",
"\n",
"๋ฑ๊ธ ์ผ๊ด์ฑ 97.6% (๋ชฉํ 90% ์ด์: ๋ฌ์ฑ)\n",
"[์ ๋ขฐ๋ฑ๊ธ ๋ถํฌ] {'High': 66, 'Medium': 9, 'Low': 5} / ๋ฑ๊ธ ์ผ์น(๊ต๊ด ๋๋น) 98%\n",
"์ ๋ขฐ๋ฑ๊ธ ๊ธฐ์ค: High(๋ถํธ์คํธ๋ฉ ๋์ผ ๋ฑ๊ธ 90% ์ด์)=๊ฒฐ๊ณผ ํต๋ณด, Medium(70~90%)=๊ต๊ด ๊ฒํ ํ ํต๋ณด, Low=์ถ๊ฐ ๋นํ ํ ์ฌํ๊ฐ\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
06. ์กฐ์ข
์ ๋นํ ๊ถค์ 3D ์ฌ์ ๋ฐ ์ทจ์ฝ ๊ตฌ๊ฐ ํ์ด๋ผ์ดํธ ๋ถ์ ๋์๋ณด๋ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์กฐ์ข
์ ๋นํ ๊ถค์ 3D ์ฌ์ ๋ฐ ์ทจ์ฝ ๊ตฌ๊ฐ ํ์ด๋ผ์ดํธ ๋ถ์ ๋์๋ณด๋๋ฅผ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์กฐ์ข
์ ๋นํ ๊ถค์ ์ 3D๋ก ์ฌ์ํ๊ณ ์ทจ์ฝ ๊ตฌ๊ฐ์ ๊ฐ์กฐํ๋ ๋ถ์ ๋์๋ณด๋๋ฅผ ๊ตฌํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๊ฒฝ๋ก ์ดํ 3m ์ด๊ณผ, ์์ธ 30๋ ์ด๊ณผ, ๊ธ์กฐ์ ๊ตฌ๊ฐ์ ์ทจ์ฝ ๊ตฌ๊ฐ์ผ๋ก ์ฐพ์ 3D ๊ถค์ ์ ๋ถ๊ฒ ํ์ํ๊ณ , ๋นํ ๋จ๊ณ๋ณ ๊ฒฝ๋ก ์ดํ ์ถ์ด, ์คํฑ ์
๋ ฅ, ๋๋ฃ ๋๋น ์ฝ์ ํ๋กํ์ ํจ๊ป ๋ณด์ฌ ์ค๋ค ๊ณผ์ ์ ์งํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/ui/trn01_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๋ช
์ ๋นํ ๊ถค์ ์ 3D๋ก ์ฌ์ํ๊ณ ๊ฒฝ๋ก ์ดํยท์์ธ ํ๊ณยท๊ธ์กฐ์ ๊ตฌ๊ฐ์ ์ทจ์ฝ ๊ตฌ๊ฐ์ผ๋ก ๊ฐ์กฐํ๋ ๋ถ์ ๋์๋ณด๋(์ผ๊ฐ ๋ชจ๋)๋ฅผ ๊ตฌํํ๋ค. ์ฌ์ 1์ด๋ง๋ค ์งํ๋ฅผ ๊ฐฑ์ ํ๋ ์ฒ๋ฆฌ์๊ฐ์ ์ธก์ ํ๊ณ ์ทจ์ฝ ๊ตฌ๊ฐ ๋ชฉ๋ก์ ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import time\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ ์์ฑ๊ธฐ (์ค์ ํ๋ จ์ยท์ฅ๋น ์ ๋ณด ์์, ์กฐ์ข
์๋ ๊ฐ๋ช
P001~)\n",
"# ๋นํ 1ํ = 120์ด(10Hz): ๊ฒฝ๋ก ๋นํ(0~60์ด) -> ํธ๋ฒ๋ง(60~85์ด) -> ๋๋ฐ ์ฅ์ ๋ฌผ ํํผ(85์ด) -> ๋น์ ์ฐฉ๋ฅ(100์ด ์ง์)\n",
"# ์กฐ์ข
์๋ง๋ค ์จ์ ์๋ จ๋(skill)์ ์กฐ์ ์ต๊ด(๊ธ์กฐ์ jerky, ๊ณผ๋ณด์ overcorr)์ด ์๊ณ , ๊ต๊ด ์ ์๋ ์ด๋ฅผ ๋ฐ์ํด ๋ง๋ ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"DT, STEPS = 0.1, 1200\n",
"T = np.arange(STEPS) * DT\n",
"COND = {\"day\": dict(wind=0.4, gust=0.0, gps=0.25, delay=0.0), \"night\": dict(wind=0.4, gust=0.0, gps=0.5, delay=0.15),\n",
" \"gust\": dict(wind=1.2, gust=2.5, gps=0.25, delay=0.0)}\n",
"OBST_T, LAND_T = 85.0, 100.0\n",
"\n",
"\n",
"def make_pilots(n=40, seed=7):\n",
" rng = np.random.default_rng(seed)\n",
" skill = rng.beta(2.2, 2.0, n)\n",
" return pd.DataFrame({\"pilot\": [f\"P{i + 1:03d}\" for i in range(n)], \"skill\": skill,\n",
" \"jerky\": rng.random(n) < 0.15 + 0.35 * (1 - skill), \"overcorr\": rng.random(n) < 0.15 + 0.35 * (1 - skill)})\n",
"\n",
"\n",
"def target_path(t):\n",
" \"\"\"์ฌ๊ฐํ ๊ฒฝ๋ก(40x30m, ๊ณ ๋ 20m)๋ฅผ 3m/s๋ก ๋นํ -> 60์ด ์ง์ ์์ ํธ๋ฒ๋ง -> ์ฐฉ๋ฅ ์ง์ ํ ๊ณ ๋ 0.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 6] ์กฐ์ข
์ ๋นํ ๊ถค์ 3D ์ฌ์ ๋ฐ ์ทจ์ฝ ๊ตฌ๊ฐ ํ์ด๋ผ์ดํธ ๋ถ์ ๋์๋ณด๋ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def fly(pilots, cond=\"day\", seed=0, delay_extra=0.0, obstacle_side=None):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์์ ๋นํ 1ํ๋ฅผ ๋์์ ๋ชจ์ํ๋ค(๋ฒกํฐํ). ๋ฐํ: ๋ก๊ทธ ๋ฐฐ์ด dict, ๋นํ๋ณ ์ ๋ต ์ ๋ณด DataFrame.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 6] ์กฐ์ข
์ ๋นํ ๊ถค์ 3D ์ฌ์ ๋ฐ ์ทจ์ฝ ๊ตฌ๊ฐ ํ์ด๋ผ์ดํธ ๋ถ์ ๋์๋ณด๋ ๊ตฌํ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def one(log, j):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ์์ j๋ฒ์งธ ์กฐ์ข
์ 1๋ช
์ ๋ก๊ทธ(DataFrame)๋ฅผ ๊บผ๋ธ๋ค.\"\"\"\n",
" return pd.DataFrame({k: (v if v.ndim == 1 else v[:, j]) for k, v in log.items()})\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ๋นํ๋ก๊ทธ ํน์ง ์ถ์ถ ๊ณตํต ํจ์(์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ๋ฅผ ํ ๋ฒ์ ๊ณ์ฐ) ----\n",
"from scipy.ndimage import uniform_filter1d\n",
"\n",
"BASE_F = [\"path_rmse\", \"hover_drift\", \"hover_std\", \"alt_err\"]\n",
"ADV_F = BASE_F + [\"obs_rt\", \"obs_lateral\", \"touch_vz\", \"att_exceed\", \"jerk\", \"overshoot\", \"n_risk\"]\n",
"RULES = {\"near_collision\": (\"obs_lateral\", \"<\", 2.5), \"hard_landing\": (\"touch_vz\", \">\", 2.0), \"attitude_exceed\": (\"att_exceed\", \">=\", 10)}\n",
"\n",
"\n",
"def detect(df):\n",
" \"\"\"์ํ ์กฐ์ ์ฌ๊ฑด ํ์ง ๊ท์น: ์ฅ์ ๋ฌผ 2.5์ด ํ ํํผ ๊ฑฐ๋ฆฌ 2.5m ๋ฏธ๋ง, ์ ์ง ์๋ 2.0m/s ์ด๊ณผ, ์์ธ 35๋ ์ด๊ณผ 1์ด ์ด์.\"\"\"\n",
" out = {}\n",
" for ev, (f, op, th) in RULES.items():\n",
" out[ev] = df[f] < th if op == \"<\" else (df[f] > th if op == \">\" else df[f] >= th)\n",
" return pd.DataFrame(out)\n",
"\n",
"\n",
"def features(log):\n",
" \"\"\"๊ฒฝ๋ก ์ดํ๋ยทํธ๋ฒ๋ง ์์ ์ฑ(๊ธฐ๋ณธ) + ์ฅ์ ๋ฌผ ๋ฐ์ยท์ฐฉ๋ฅ ์๋ยท์์ธ ํ๊ณยท๊ธ์กฐ์ยท๊ณผ๋ณด์ (๊ณ ๋ํ) ํน์ง.\"\"\"\n",
" X, Y, A = (uniform_filter1d(log[k], 9, axis=0) for k in (\"x\", \"y\", \"alt\")) # GPS ์ก์ ์ํ(์ด๋ํ๊ท 0.9์ด)\n",
" h = np.hypot(X - log[\"tx\"], Y - log[\"ty\"])\n",
" cr, hv = slice(0, 600), slice(620, 850)\n",
" k0, k1 = int(OBST_T / DT), int((OBST_T + 2.5) / DT)\n",
" side = np.sign(log[\"ty\"][k0 + 1] - log[\"ty\"][k0 - 1])\n",
" moved = (log[\"stick_roll\"][k0:k0 + 40] * side) > 0.3\n",
" rt = np.where(moved.any(0), moved.argmax(0) * DT, 4.0)\n",
" vz = -np.gradient(A, DT, axis=0)\n",
" land = np.argmax((A < 0.4) & (T[:, None] >= LAND_T), axis=0)\n",
" touch = np.array([vz[max(i - 6, 0):i - 2, j].mean() if i > 6 else 3.0 for j, i in enumerate(land)])\n",
" ds = np.abs(np.diff(np.stack([log[\"stick_roll\"], log[\"stick_pitch\"]]), axis=1)) / DT\n",
" return pd.DataFrame({\n",
" \"path_rmse\": np.sqrt((h[cr] ** 2).mean(0)), \"hover_drift\": h[hv].mean(0), \"hover_std\": np.hypot(X[hv].std(0), Y[hv].std(0)),\n",
" \"alt_err\": np.abs(A[:850] - log[\"talt\"][:850]).mean(0),\n",
" \"obs_rt\": rt, \"obs_lateral\": np.abs(Y[k1] - Y[k0]), \"touch_vz\": touch,\n",
" \"att_exceed\": (np.maximum(np.abs(log[\"roll_deg\"]), np.abs(log[\"pitch_deg\"])) > 35).sum(0),\n",
" \"jerk\": np.percentile(ds, 95, axis=(0, 1)),\n",
" \"overshoot\": np.clip(np.abs(Y[k0:k0 + 120] - Y[k0]).max(0) - 6.0, 0, None)}).pipe(\n",
" lambda d: d.assign(n_risk=detect(d).sum(axis=1))) # overshoot: ํํผ ํ ๋ชฉํ 6m๋ฅผ ๋์ด์ ๊ฑฐ๋ฆฌ(๊ณผ๋ณด์ )\n",
"\n",
"\n",
"def make_dataset(n_pilots=40, seed=7, conds=(\"day\", \"night\", \"gust\"), flights=1):\n",
" \"\"\"์กฐ์ข
์ n๋ช
x ์กฐ๊ฑด x ๋ฐ๋ณต ๋นํ -> ๋นํ๋ณ ํน์ง + ์ ๋ต(๊ต๊ด ์ ์ยท๋ฑ๊ธยท์ํ ์ฌ๊ฑด) ํ.\"\"\"\n",
" pilots = make_pilots(n_pilots, seed)\n",
" out = []\n",
" for c in conds:\n",
" for f in range(flights):\n",
" log, tr = fly(pilots, c, seed=seed * 100 + list(COND).index(c) * 10 + f)\n",
" out.append(pd.concat([tr.reset_index(drop=True), features(log)], axis=1).assign(flight=f))\n",
" return pd.concat(out, ignore_index=True)\n",
"\n",
"\n",
"def cond_z(df, ref, cols):\n",
" \"\"\"์กฐ๊ฑด๋ณ ๊ธฐ์ค ์ง๋จ(ref)์ ํ๊ท ยทํ์คํธ์ฐจ๋ก ํ์คํ: ๋ํ์ฒ๋ผ ์ด๋ ค์ด ์กฐ๊ฑด์์ ๋ฐ๋ ๋ถ์ด์ต์ ์ค์ธ๋ค.\"\"\"\n",
" z = df.copy()\n",
" z[cols] = z[cols].astype(float)\n",
" for c in df[\"cond\"].unique():\n",
" r = ref[ref[\"cond\"] == c][cols]\n",
" z.loc[df[\"cond\"] == c, cols] = (df.loc[df[\"cond\"] == c, cols] - r.mean()) / (r.std() + 1e-9)\n",
" return z\n",
"\n",
"\n",
"def to_grade(score):\n",
" return np.where(score >= 75, \"A\", np.where(score >= 55, \"B\", \"C\"))\n",
"\n",
"PHASES = [(0, 60, \"path\"), (60, 85, \"hover\"), (85, 100, \"obstacle\"), (100, 120, \"landing\")]\n",
"\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์กฐ์ข
์ 12๋ช
์ค ๊ฐ์ฅ ์ ์๊ฐ ๋ฎ์ ์กฐ์ข
์์ ๋ํ ๋นํ์ ์ฌ์\n",
"pilots = make_pilots(12, seed=4)\n",
"log, truth = fly(pilots, \"gust\", seed=21)\n",
"j = int(truth[\"instructor\"].idxmin())\n",
"d = one(log, j)\n",
"FE = features(log).assign(cond=\"gust\")\n",
"f = FE.iloc[j]\n",
"d[\"err\"] = np.hypot(d[\"x\"] - d[\"tx\"], d[\"y\"] - d[\"ty\"])\n",
"d[\"att\"] = np.maximum(d[\"roll_deg\"].abs(), d[\"pitch_deg\"].abs())\n",
"d[\"stick_rate\"] = np.r_[0, np.abs(np.diff(d[\"stick_roll\"])) / DT]\n",
"\n",
"\n",
"def weak_flags(df):\n",
" return {\"๊ฒฝ๋ก์ดํ>3m\": df[\"err\"] > 3.0, \"์์ธ>30๋\": df[\"att\"] > 30, \"๊ธ์กฐ์(๋ณํ์จ>6/s)\": df[\"stick_rate\"] > 6}\n",
"\n",
"\n",
"# 1) ์ฌ์ ๋ชจ์: 1์ด(10ํ๋ ์)๋ง๋ค ๋์ ์งํ ๊ฐฑ์ + ์ฒ๋ฆฌ์๊ฐ ์ธก์ \n",
"ms, kpis = [], []\n",
"for k in range(10, len(d) + 1, 10):\n",
" t0 = time.perf_counter()\n",
" cur = d.iloc[:k]\n",
" flags = weak_flags(cur)\n",
" kpis.append({\"t\": cur[\"t\"].iloc[-1], \"err_now\": cur[\"err\"].iloc[-1], \"weak_ratio\": np.mean(np.any(np.c_[tuple(flags.values())], axis=1))})\n",
" ms.append((time.perf_counter() - t0) * 1000)\n",
"print(f\"์กฐ์ข
์ {truth['pilot'][j]}(๊ต๊ด {truth['instructor'][j]}์ , ๋ฑ๊ธ {truth['grade'][j]}) ๋ํ ๋นํ ์ฌ์: ๊ฐฑ์ {len(ms)}ํ, \"\n",
" f\"์ฒ๋ฆฌ ํ๊ท {np.mean(ms):.2f} ms, ์ต๋ {np.max(ms):.2f} ms (๋ชฉํ 1์ด ์ฃผ๊ธฐยท100 ms ์ด๋ด -> {'๋ฌ์ฑ' if np.max(ms) < 100 else '๋ฏธ๋ฌ'})\")\n",
"\n",
"# 2) ์ทจ์ฝ ๊ตฌ๊ฐ ๋ชฉ๋ก(์ฐ์ 0.5์ด ์ด์)\n",
"segs = []\n",
"for name, m in weak_flags(d).items():\n",
" m = m.values.astype(int)\n",
" edges = np.flatnonzero(np.diff(np.r_[0, m, 0]))\n",
" for a, b in zip(edges[::2], edges[1::2]):\n",
" if b - a >= 5:\n",
" ph = next(p for s, e, p in PHASES if s <= d[\"t\"][a] < e)\n",
" segs.append({\"๊ตฌ๋ถ\": name, \"์์(s)\": round(d[\"t\"][a], 1), \"๊ธธ์ด(s)\": round((b - a) * DT, 1), \"๋จ๊ณ\": ph})\n",
"S = pd.DataFrame(segs).sort_values(\"์์(s)\")\n",
"print(f\"\\n[์ทจ์ฝ ๊ตฌ๊ฐ {len(S)}๊ฐ - ์๊ฐ์ ์์ 10๊ฐ]\")\n",
"print(S.head(10).to_string(index=False))\n",
"print(\"\\n๋จ๊ณ๋ณ ์ทจ์ฝ ์๊ฐ(s):\", S.groupby(\"๋จ๊ณ\")[\"๊ธธ์ด(s)\"].sum().round(1).to_dict())\n",
"print(f\"๋๋ฐ ๋์: ์ฅ์ ๋ฌผ ๋ฐ์ {f['obs_rt']:.1f}s, 2.5์ด ํ ํํผ๊ฑฐ๋ฆฌ {f['obs_lateral']:.1f}m, ์ ์ง ์๋ {f['touch_vz']:.1f}m/s -> \"\n",
" f\"์ํ ์ฌ๊ฑด {', '.join(k for k, v in detect(FE.iloc[[j]]).iloc[0].items() if v) or '์์'}\")\n",
"\n",
"# 3) ๋์๋ณด๋(์ผ๊ฐ ๋ชจ๋, ๊ตฌ์ฑ์์ 4์ข
)\n",
"plt.style.use(\"dark_background\")\n",
"fig = plt.figure(figsize=(13, 8))\n",
"ax = fig.add_subplot(2, 2, 1, projection=\"3d\")\n",
"bad = np.any(np.c_[tuple(weak_flags(d).values())], axis=1)\n",
"ax.plot(d[\"tx\"], d[\"ty\"], d[\"talt\"], \"--\", c=\"gray\", lw=1, label=\"course\")\n",
"ax.scatter(d[\"x\"][~bad], d[\"y\"][~bad], d[\"alt\"][~bad], s=2, c=\"deepskyblue\", label=\"normal\")\n",
"ax.scatter(d[\"x\"][bad], d[\"y\"][bad], d[\"alt\"][bad], s=6, c=\"red\", label=\"weak segment\")\n",
"ax.set_xlabel(\"x (m)\"); ax.set_ylabel(\"y (m)\"); ax.set_zlabel(\"alt (m)\"); ax.legend(fontsize=7); ax.set_title(\"3D replay (red = weak)\")\n",
"a2 = fig.add_subplot(2, 2, 2)\n",
"a2.plot(d[\"t\"], d[\"err\"], c=\"lime\", lw=1); a2.axhline(3, c=\"red\", ls=\"--\")\n",
"for s, e, p in PHASES:\n",
" a2.axvspan(s, e, alpha=0.08 if p in (\"path\", \"obstacle\") else 0.16, color=\"white\"); a2.text(s + 1, a2.get_ylim()[1] * 0.9, p, fontsize=8)\n",
"a2.set_title(\"Path deviation (m) by phase\")\n",
"a3 = fig.add_subplot(2, 2, 3)\n",
"a3.plot(d[\"t\"], d[\"stick_roll\"], lw=0.8, label=\"roll\"); a3.plot(d[\"t\"], d[\"stick_pitch\"], lw=0.8, label=\"pitch\"); a3.plot(d[\"t\"], d[\"throttle\"], lw=0.8, label=\"throttle\")\n",
"a3.scatter(d[\"t\"][d[\"stick_rate\"] > 6], d[\"stick_roll\"][d[\"stick_rate\"] > 6], c=\"red\", s=10, label=\"abrupt\")\n",
"a3.legend(fontsize=7, ncol=4); a3.set_title(\"Stick inputs\"); a3.set_xlabel(\"time (s)\")\n",
"a4 = fig.add_subplot(2, 2, 4)\n",
"z = cond_z(FE, FE, ADV_F).iloc[j][[\"path_rmse\", \"hover_std\", \"obs_rt\", \"touch_vz\", \"jerk\", \"overshoot\"]]\n",
"a4.barh(z.index, z.values, color=[\"red\" if v > 1 else \"orange\" if v > 0 else \"deepskyblue\" for v in z.values])\n",
"a4.axvline(0, c=\"white\", lw=0.5); a4.set_title(\"Weakness profile (z-score vs peers, right = worse)\")\n",
"fig.suptitle(f\"Pilot {truth['pilot'][j]} flight replay dashboard (night mode) - training data only\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
06 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"์กฐ์ข
์ P010(๊ต๊ด 33.7์ , ๋ฑ๊ธ C) ๋ํ ๋นํ ์ฌ์: ๊ฐฑ์ 120ํ, ์ฒ๋ฆฌ ํ๊ท 0.50 ms, ์ต๋ 1.30 ms (๋ชฉํ 1์ด ์ฃผ๊ธฐยท100 ms ์ด๋ด -> ๋ฌ์ฑ)\n",
"\n",
"[์ทจ์ฝ ๊ตฌ๊ฐ 34๊ฐ - ์๊ฐ์ ์์ 10๊ฐ]\n",
" ๊ตฌ๋ถ ์์(s) ๊ธธ์ด(s) ๋จ๊ณ\n",
"๊ฒฝ๋ก์ดํ>3m 2.0 2.7 path\n",
"๊ฒฝ๋ก์ดํ>3m 8.6 5.9 path\n",
" ์์ธ>30๋ 11.5 1.8 path\n",
"๊ฒฝ๋ก์ดํ>3m 14.9 3.4 path\n",
" ์์ธ>30๋ 17.3 0.5 path\n",
"๊ฒฝ๋ก์ดํ>3m 19.2 4.5 path\n",
" ์์ธ>30๋ 20.4 1.5 path\n",
"๊ฒฝ๋ก์ดํ>3m 25.6 2.5 path\n",
"๊ฒฝ๋ก์ดํ>3m 29.1 1.2 path\n",
"๊ฒฝ๋ก์ดํ>3m 30.6 4.0 path\n",
"\n",
"๋จ๊ณ๋ณ ์ทจ์ฝ ์๊ฐ(s): {'hover': 27.8, 'landing': 18.5, 'obstacle': 8.0, 'path': 63.7}\n",
"๋๋ฐ ๋์: ์ฅ์ ๋ฌผ ๋ฐ์ 2.1s, 2.5์ด ํ ํํผ๊ฑฐ๋ฆฌ 1.1m, ์ ์ง ์๋ 2.7m/s -> ์ํ ์ฌ๊ฑด near_collision, hard_landing, attitude_exceed\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
07. ๊ต๊ด์ด ์ง์ ์กฐ์ข
์ ์ฑํฅ์ ์ง๋จํ๊ณ ํผ๋๋ฐฑ์ ์
๋ ฅํ๋ HITL ํ๊ฐ ์ธํฐํ์ด์ค ์ค๊ณ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๊ต๊ด์ด ์ง์ ์กฐ์ข
์ ์ฑํฅ์ ์ง๋จํ๊ณ ํผ๋๋ฐฑ์ ์
๋ ฅํ๋ HITL ํ๊ฐ ์ธํฐํ์ด์ค๋ฅผ ์ค๊ณํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๊ต๊ด์ด AI ์ ์ ๋ฑ๊ธ๊ณผ ์กฐ์ ์ฑํฅ ์ง๋จ์ ์ง์ ํ์ธยท์์ ํ๊ณ ํผ๋๋ฐฑ์ ์
๋ ฅํ๋ HITL ํ๊ฐ ์ธํฐํ์ด์ค๋ฅผ ์ค๊ณํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **AI ์ ์์ ํญ์ ๊ฒํ ๋๊ธฐ๋ก ๋ฑ๋กํ๊ณ ์ ๋ขฐ๋๊ฐ ๋ฎ์ ์ ์๊ณผ C ๋ฑ๊ธ์ ๋จผ์ ๋ณด๋๋ก ์์๋ฅผ ์ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/hitl/trn01_approval_workflow.py ๋ฐ docs/trn01_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",
"\"\"\"AI๊ฐ ์ ์ํ ์๋ จ๋ ๋ฑ๊ธยท์กฐ์ ์ฑํฅ์ ๊ต๊ด์ด ์ง์ ํ์ธยท์์ ํ๊ณ ํผ๋๋ฐฑ์ ์
๋ ฅํ๋ HITL ํ๊ฐ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ค. AI๋ ๋ฑ๊ธ์ ํ์ ํ์ง ์์ผ๋ฉฐ, ๋ถ๋ฆฌํ ๊ฒฐ์ (์ฌ๊ต์ก ์ง์ ยท์๊ฒฉ ๋ณด๋ฅ)์ ์์ธ ๊ทผ๊ฑฐ์ ๋ ๋ฒ์งธ ๊ต๊ด ํ์ธ์ ์๊ตฌํ๊ณ , ๋ชจ๋ ๊ฒฐ์ ์ ์ยท๋ณ์กฐ ํ์ธ์ด ๊ฐ๋ฅํ ๊ฐ์ฌ ๋ก๊ทธ์ ๋จ๊ธด๋ค.\"\"\"\n",
"import hashlib\n",
"import json\n",
"import pandas as pd\n",
"\n",
"ACTIONS = {\"CONFIRM\": \"AI ์ ์ ๋ฑ๊ธ ํ์ \", \"MODIFY\": \"๋ฑ๊ธ ์์ \", \"REFLIGHT\": \"์ถ๊ฐ ๋นํ ํ ์ฌํ๊ฐ\", \"RETRAIN\": \"์ฌ๊ต์ก ์ง์ \", \"QUAL_HOLD\": \"์๊ฒฉ ๋ถ์ฌ ๋ณด๋ฅ\"}\n",
"CONF_ORDER = {\"Low\": 0, \"Medium\": 1, \"High\": 2}\n",
"\n",
"\n",
"class ReviewError(ValueError):\n",
" pass\n",
"\n",
"\n",
"class FakeClock:\n",
" t = 0.0\n",
"\n",
" def __call__(self):\n",
" return self.t\n",
"\n",
"\n",
"class ReviewQueue:\n",
" def __init__(self, clock):\n",
" self.items, self.log, self.clock = {}, [], clock\n",
"\n",
" def _record(self, body):\n",
" prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n",
" body = {**body, \"ts\": round(self.clock(), 1)}\n",
" self.log.append({**body, \"prev\": prev, \"hash\": hashlib.sha256((prev + json.dumps(body, ensure_ascii=False, sort_keys=True)).encode()).hexdigest()})\n",
"\n",
" def verify_log(self):\n",
" prev = \"GENESIS\"\n",
" for n, rec in enumerate(self.log, start=1):\n",
" body = {k: v for k, v in rec.items() if k not in (\"prev\", \"hash\")}\n",
" if rec[\"prev\"] != prev or rec[\"hash\"] != hashlib.sha256((prev + json.dumps(body, ensure_ascii=False, sort_keys=True)).encode()).hexdigest():\n",
" return False, n\n",
" prev = rec[\"hash\"]\n",
" return True, -1\n",
"\n",
" def submit(self, pilot, grade, conf, habits, evidence):\n",
" \"\"\"AI ์ ์ ๋ฑ๋ก: ํญ์ PENDING์ผ๋ก ์์ํ๋ฉฐ AI๊ฐ ํ์ ํ์ง ์๋๋ค.\"\"\"\n",
" self.items[pilot] = {\"pilot\": pilot, \"ai_grade\": grade, \"conf\": conf, \"habits\": habits, \"evidence\": evidence,\n",
" \"status\": \"PENDING\", \"final\": None, \"submitted\": self.clock(), \"approvals\": []}\n",
" self._record({\"event\": \"AI_SUGGEST\", \"pilot\": pilot, \"grade\": grade, \"conf\": conf, \"by\": \"AI\"})\n",
"\n",
" def order(self):\n",
" \"\"\"๊ฒํ ์์: ์ ๋ขฐ๋ ๋ฎ์ ์ ์ -> C ๋ฑ๊ธ -> ๋๋จธ์ง(๊ต๊ด์ด ํ๋จ์ด ํ์ํ ๊ฑด๋ถํฐ ๋ณธ๋ค).\"\"\"\n",
" todo = [i for i in self.items.values() if i[\"status\"] in (\"PENDING\", \"AWAIT_2ND\")]\n",
" return sorted(todo, key=lambda i: (CONF_ORDER[i[\"conf\"]], i[\"ai_grade\"] != \"C\", i[\"pilot\"]))\n",
"\n",
" def decide(self, pilot, action, instructor, reason, new_grade=None):\n",
" if action not in ACTIONS:\n",
" raise ReviewError(f\"ํ์ฉ๋์ง ์์ ์กฐ์น: {action}\")\n",
" if not instructor:\n",
" raise ReviewError(\"๊ต๊ด ID ํ์\")\n",
" it = self.items.get(pilot)\n",
" if it is None or it[\"status\"] == \"FINAL\":\n",
" raise ReviewError(\"๊ฒํ ๋๊ธฐ ๊ฑด์ด ์๋\")\n",
" if len(reason.strip()) < 10:\n",
" raise ReviewError(\"์ง๋จยทํผ๋๋ฐฑ 10์ ์ด์ ํ์\")\n",
" if action == \"MODIFY\" and new_grade not in (\"A\", \"B\", \"C\"):\n",
" raise ReviewError(\"์์ ๋ฑ๊ธ(A/B/C) ํ์\")\n",
" if action in (\"RETRAIN\", \"QUAL_HOLD\") and len(reason.strip()) < 20:\n",
" raise ReviewError(\"๋ถ๋ฆฌํ ๊ฒฐ์ ์ ๊ทผ๊ฑฐ 20์ ์ด์ ํ์\")\n",
" if action == \"QUAL_HOLD\":\n",
" if instructor in it[\"approvals\"]:\n",
" raise ReviewError(\"๋ ๋ฒ์งธ ํ์ธ์ ๋ค๋ฅธ ๊ต๊ด์ด ํด์ผ ํจ\")\n",
" it[\"approvals\"].append(instructor)\n",
" self._record({\"event\": \"DECIDE\", \"pilot\": pilot, \"action\": action, \"by\": instructor, \"reason\": reason, \"step\": len(it[\"approvals\"])})\n",
" if len(it[\"approvals\"]) < 2:\n",
" it[\"status\"] = \"AWAIT_2ND\"\n",
" return \"๋ ๋ฒ์งธ ๊ต๊ด ํ์ธ ๋๊ธฐ\"\n",
" else:\n",
" self._record({\"event\": \"DECIDE\", \"pilot\": pilot, \"action\": action, \"by\": instructor, \"reason\": reason, \"new_grade\": new_grade})\n",
" it[\"status\"], it[\"action\"], it[\"decided\"] = \"FINAL\", action, self.clock()\n",
" it[\"final\"] = new_grade if action == \"MODIFY\" else (None if action == \"REFLIGHT\" else it[\"ai_grade\"])\n",
" return \"ํ์ \"\n",
"\n",
"\n",
"clock = FakeClock()\n",
"rq = ReviewQueue(clock)\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฏธ์
5 ๊ฒฐ๊ณผ์์ ๋์ด์จ AI ์ ์ 6๊ฑด(๊ฐ๋ช
)\n",
"for p, g, c, h, e in [(\"P003\", \"A\", \"High\", [], \"๊ฒฝ๋ก RMSE ํ์ 10%, ์ ์ง 0.4m/s\"), (\"P011\", \"C\", \"High\", [\"๊ธ์กฐ์\"], \"๊ฒฝ์ฐฉ๋ฅ 2ํ, ์คํฑ ๋ณํ์จ ์์ 5%\"),\n",
" (\"P017\", \"B\", \"Low\", [\"๊ณผ๋ณด์ \"], \"๋นํ๋ณ ์ ์ ํธ์ฐจ ํผ(48~79)\"), (\"P024\", \"C\", \"Medium\", [\"๊ณผ๋ณด์ \"], \"์ฅ์ ๋ฌผ ๊ทผ์ 3ํ\"),\n",
" (\"P030\", \"B\", \"High\", [], \"ํธ๋ฒ๋ง ์์ , ์ฅ์ ๋ฌผ ๋ฐ์ 1.2s\"), (\"P038\", \"C\", \"Low\", [], \"๋ํ ๋นํ๋ง ๋ฎ์\")]:\n",
" clock.t += 1; rq.submit(p, g, c, h, e)\n",
"print(\"๊ฒํ ์์:\", [f\"{i['pilot']}({i['ai_grade']}/{i['conf']})\" for i in rq.order()])\n",
"print(\"AI๊ฐ ํ์ ํ ๊ฑด์:\", sum(i[\"status\"] == \"FINAL\" for i in rq.items.values()))\n",
"\n",
"for args in [(\"P011\", \"RETRAIN\", \"\", \"๊ฒฝ์ฐฉ๋ฅ ๋ฐ๋ณต, ์ฐฉ๋ฅ ์ ์ฐจ ์ฌ๊ต์ก\"), (\"P003\", \"CONFIRM\", \"inst01\", \"์ข์\"), (\"P017\", \"MODIFY\", \"inst01\", \"ํธ์ฐจ๊ฐ ์ปค์ ๋ฑ๊ธ ์กฐ์ ํจ\"),\n",
" (\"P024\", \"RETRAIN\", \"inst02\", \"๊ทผ์ 3ํ ์ฌ๊ต์ก\"), (\"P003\", \"AUTO\", \"inst01\", \"์๋ ํ์ ์๋ํฉ๋๋ค\")]:\n",
" try:\n",
" rq.decide(*args)\n",
" except ReviewError as e:\n",
" print(f\"์ฐจ๋จ: {args[0]} {args[1]} -> {e}\")\n",
"\n",
"clock.t += 120; print(\"P003:\", rq.decide(\"P003\", \"CONFIRM\", \"inst01\", \"๊ฒฝ๋กยทํธ๋ฒ๋ง ์์ , ๋๋ฐ ๋์ ์ํธ ํ์ธ\"))\n",
"clock.t += 200; print(\"P017:\", rq.decide(\"P017\", \"REFLIGHT\", \"inst01\", \"์ ์ ํธ์ฐจ ํผ, ์ฃผ๊ฐ ๋นํ 2ํ ์ถ๊ฐ ํ ์ฌํ๊ฐ\"))\n",
"clock.t += 150; print(\"P011:\", rq.decide(\"P011\", \"RETRAIN\", \"inst02\", \"๊ฒฝ์ฐฉ๋ฅ 2ํ์ ๊ธ์กฐ์ ํ์ธ, ์ฐฉ๋ฅ ๊ฐ์ ์ ์ฐจ ์ฌ๊ต์ก ์ง์ \"))\n",
"clock.t += 90; print(\"P024 1์ฐจ:\", rq.decide(\"P024\", \"QUAL_HOLD\", \"inst02\", \"์ฅ์ ๋ฌผ ๊ทผ์ 3ํ ๋ฐ๋ณต, ํํผ ๊ธฐ๋ ์๋ฌ ์ ์๊ฒฉ ๋ณด๋ฅ\"))\n",
"clock.t += 10\n",
"try:\n",
" rq.decide(\"P024\", \"QUAL_HOLD\", \"inst02\", \"๊ฐ์ ๊ต๊ด์ด ๋ ๋ฒ์งธ ํ์ธ๊น์ง ์ง์ ํ๋ ค๊ณ ์๋ํจ\")\n",
"except ReviewError as e:\n",
" print(\"์ฐจ๋จ: P024 QUAL_HOLD ->\", e)\n",
"clock.t += 300; print(\"P024 2์ฐจ:\", rq.decide(\"P024\", \"QUAL_HOLD\", \"inst05\", \"์์ ์ฌํ์ธ ๊ฒฐ๊ณผ ํํผ ์ง์ฐ ๋์, ๋ณด๋ฅ ์ ์ง\"))\n",
"clock.t += 60; print(\"P038:\", rq.decide(\"P038\", \"MODIFY\", \"inst03\", \"๋ํ ์ธ ๋นํ์ B ์์ค, ๋ํ ๋์๋ง ๋ณด์\", new_grade=\"B\"))\n",
"\n",
"df = pd.DataFrame(rq.items.values())\n",
"df[\"๋๊ธฐ(s)\"] = (df[\"decided\"] - df[\"submitted\"]).round(0)\n",
"print(\"\\n\" + df[[\"pilot\", \"ai_grade\", \"conf\", \"action\", \"final\", \"๋๊ธฐ(s)\"]].fillna(\"-\").to_string(index=False))\n",
"fin = df[df[\"status\"] == \"FINAL\"]\n",
"print(f\"๊ต๊ด ๊ฒฐ์ {len(fin)}๊ฑด ์ค AI ์ ์ ๋ฑ๊ธ๊ณผ ๋ค๋ฅธ ๊ฒฐ์ {int((fin['final'] != fin['ai_grade']).sum())}๊ฑด(์ถ๊ฐ ๋นํ ํฌํจ), ๋ฏธ๊ฒํ {int((df['status'] != 'FINAL').sum())}๊ฑด์ ๋๊ธฐ ์ ์ง\")\n",
"print(f\"\\n๊ฐ์ฌ ๋ก๊ทธ {len(rq.log)}๊ฑด ๊ธฐ๋ก(์ ์ {len(df)}๊ฑด + ๊ฒฐ์ {len(rq.log) - len(df)}๊ฑด, ๋ก๊น
์ฑ๊ณต๋ฅ 100%), ํด์ ์ฒด์ธ ๊ฒ์ฆ: {rq.verify_log()}\")\n",
"rq.log[7][\"reason\"] = \"๊ทผ๊ฑฐ ์์ \"\n",
"print(\"8๋ฒ์งธ ๊ธฐ๋ก ๋ณ์กฐ ํ ๊ฒ์ฆ(False, ๊นจ์ง ์์น):\", rq.verify_log())\n",
"\n",
"form = {\"pilot\": \"P011\", \"strength\": \"(๊ต๊ด ์
๋ ฅ)\", \"weakness\": \"์ฐฉ๋ฅ ๊ฐ์ ๋ฆ์, ๊ธ์กฐ์\", \"next_training\": \"์ฐฉ๋ฅ ์ ์ฐจ ๋ฐ๋ณต 3ํ\", \"instructor\": \"inst02\", \"pilot_ack\": \"(์๋ช
)\"}\n",
"print(\"\\n[์กฐ์ข
์ ํผ๋๋ฐฑ ์์]\\n\" + json.dumps(form, ensure_ascii=False))\n",
"print(\"AI ์ ์๋ ๊ต๊ด ํ๋จ ๋ณด์กฐ ์๋ฃ์ด๋ฉฐ ์ธ์ฌยท์๊ฒฉ ๊ฒฐ์ ์ ๋จ๋
๊ทผ๊ฑฐ๋ก ์ฐ์ง ์๋๋ค. ์ต์ข
๋ฑ๊ธ์ ์ฑ
์์ ๊ฒฐ์ ํ ๊ต๊ด์๊ฒ ์๋ค.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
07 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"๊ฒํ ์์: ['P038(C/Low)', 'P017(B/Low)', 'P024(C/Medium)', 'P011(C/High)', 'P003(A/High)', 'P030(B/High)']\n",
"AI๊ฐ ํ์ ํ ๊ฑด์: 0\n",
"์ฐจ๋จ: P011 RETRAIN -> ๊ต๊ด ID ํ์\n",
"์ฐจ๋จ: P003 CONFIRM -> ์ง๋จยทํผ๋๋ฐฑ 10์ ์ด์ ํ์\n",
"์ฐจ๋จ: P017 MODIFY -> ์์ ๋ฑ๊ธ(A/B/C) ํ์\n",
"์ฐจ๋จ: P024 RETRAIN -> ์ง๋จยทํผ๋๋ฐฑ 10์ ์ด์ ํ์\n",
"์ฐจ๋จ: P003 AUTO -> ํ์ฉ๋์ง ์์ ์กฐ์น: AUTO\n",
"P003: ํ์ \n",
"P017: ํ์ \n",
"P011: ํ์ \n",
"P024 1์ฐจ: ๋ ๋ฒ์งธ ๊ต๊ด ํ์ธ ๋๊ธฐ\n",
"์ฐจ๋จ: P024 QUAL_HOLD -> ๋ ๋ฒ์งธ ํ์ธ์ ๋ค๋ฅธ ๊ต๊ด์ด ํด์ผ ํจ\n",
"P024 2์ฐจ: ํ์ \n",
"P038: ํ์ \n",
"\n",
"pilot ai_grade conf action final ๋๊ธฐ(s)\n",
" P003 A High CONFIRM A 125.0\n",
" P011 C High RETRAIN C 474.0\n",
" P017 B Low REFLIGHT - 323.0\n",
" P024 C Medium QUAL_HOLD C 872.0\n",
" P030 B High - - -\n",
" P038 C Low MODIFY B 930.0\n",
"๊ต๊ด ๊ฒฐ์ 5๊ฑด ์ค AI ์ ์ ๋ฑ๊ธ๊ณผ ๋ค๋ฅธ ๊ฒฐ์ 2๊ฑด(์ถ๊ฐ ๋นํ ํฌํจ), ๋ฏธ๊ฒํ 1๊ฑด์ ๋๊ธฐ ์ ์ง\n",
"\n",
"๊ฐ์ฌ ๋ก๊ทธ 12๊ฑด ๊ธฐ๋ก(์ ์ 6๊ฑด + ๊ฒฐ์ 6๊ฑด, ๋ก๊น
์ฑ๊ณต๋ฅ 100%), ํด์ ์ฒด์ธ ๊ฒ์ฆ: (True, -1)\n",
"8๋ฒ์งธ ๊ธฐ๋ก ๋ณ์กฐ ํ ๊ฒ์ฆ(False, ๊นจ์ง ์์น): (False, 8)\n",
"\n",
"[์กฐ์ข
์ ํผ๋๋ฐฑ ์์]\n",
"{\"pilot\": \"P011\", \"strength\": \"(๊ต๊ด ์
๋ ฅ)\", \"weakness\": \"์ฐฉ๋ฅ ๊ฐ์ ๋ฆ์, ๊ธ์กฐ์\", \"next_training\": \"์ฐฉ๋ฅ ์ ์ฐจ ๋ฐ๋ณต 3ํ\", \"instructor\": \"inst02\", \"pilot_ack\": \"(์๋ช
)\"}\n",
"AI ์ ์๋ ๊ต๊ด ํ๋จ ๋ณด์กฐ ์๋ฃ์ด๋ฉฐ ์ธ์ฌยท์๊ฒฉ ๊ฒฐ์ ์ ๋จ๋
๊ทผ๊ฑฐ๋ก ์ฐ์ง ์๋๋ค. ์ต์ข
๋ฑ๊ธ์ ์ฑ
์์ ๊ฒฐ์ ํ ๊ต๊ด์๊ฒ ์๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
08. 100๋ช
์กฐ์ข
์ ๋๋ ๋นํ๋ก๊ทธ ๋ฐฐ์น ๋ถ์ ์คํธ๋ ์ค ํ
์คํธ ๋ฐ ์ฑ๋ฅ ํ๋กํ์ผ๋ง\n",
"**์์์๊ฐ**: ์ฝ 3.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"100๋ช
์กฐ์ข
์์ ๋๋ ๋นํ๋ก๊ทธ ๋ฐฐ์น ๋ถ์ ์คํธ๋ ์ค ํ
์คํธ๋ฅผ ์ํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์กฐ์ข
์ 100๋ช
์ ๋๋ ๋นํ๋ก๊ทธ ๋ฐฐ์น ๋ถ์์์ ์ฒ๋ฆฌ๋ ์ฆ๊ฐ์ ์์ ๋ก๊ทธ, ์ฒ๋ฆฌ ์ค ์์ธ ์ํฉ์ ๋ํ ๋ณต์๋ ฅ์ ๊ฒ์ฆํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **ํ์ 100๊ฑด, 100๋ช
x 5ํ 500๊ฑด, 500% ์ฆ๊ฐ 600๊ฑด, ์์ ๋ก๊ทธ 5% ํผ์
, ๋ฌถ์ ์ฒ๋ฆฌ ์์ธ ์ํฉ ์ฃผ์
ํ์ฌ ์ฒ๋ฆฌ ์ง์ฐ ๋ฐ ๋ณต์๋ ฅ์ ์ํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `tests/trn01_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๋ช
์ ๋๋ ๋นํ๋ก๊ทธ๋ฅผ ๋ฐฐ์น ๋ถ์ํ๋ ์คํธ๋ ์ค ํ
์คํธ๋ฅผ ์ํํ๋ค. ํ์ 100๊ฑด์์ 500% ์ฆ๊ฐ(600๊ฑด), ์์ ๋ก๊ทธ ํผ์
, ๋ฌถ์ ์ฒ๋ฆฌ ์ค ์์ธ ์ํฉ์ ์ฃผ์
ํด ์์คํ
๋ค์ด 0๊ฑดยท๋ก๊ทธ ์ ์ค 0๊ฑด(์ฑ์ +๊ฒฉ๋ฆฌ=์ ์ฒด)๊ณผ ์ฒ๋ฆฌ๋ยท๋ฉ๋ชจ๋ฆฌยท๋ณ๋ชฉ ํจ์๋ฅผ ์ธก์ ํ๊ณ , ์์ธ ์ 1๊ฑด์ฉ ์ฒ๋ฆฌ๋ก ๊ธฐ๋ฅ์ ์ถ์ํ๋ ์์ ์คํจ ๋์์ ํ์ธํ๋ค.\"\"\"\n",
"import cProfile\n",
"import io\n",
"import pstats\n",
"import time\n",
"import tracemalloc\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from sklearn.linear_model import Ridge\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ ์์ฑ๊ธฐ (์ค์ ํ๋ จ์ยท์ฅ๋น ์ ๋ณด ์์, ์กฐ์ข
์๋ ๊ฐ๋ช
P001~)\n",
"# ๋นํ 1ํ = 120์ด(10Hz): ๊ฒฝ๋ก ๋นํ(0~60์ด) -> ํธ๋ฒ๋ง(60~85์ด) -> ๋๋ฐ ์ฅ์ ๋ฌผ ํํผ(85์ด) -> ๋น์ ์ฐฉ๋ฅ(100์ด ์ง์)\n",
"# ์กฐ์ข
์๋ง๋ค ์จ์ ์๋ จ๋(skill)์ ์กฐ์ ์ต๊ด(๊ธ์กฐ์ jerky, ๊ณผ๋ณด์ overcorr)์ด ์๊ณ , ๊ต๊ด ์ ์๋ ์ด๋ฅผ ๋ฐ์ํด ๋ง๋ ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"DT, STEPS = 0.1, 1200\n",
"T = np.arange(STEPS) * DT\n",
"COND = {\"day\": dict(wind=0.4, gust=0.0, gps=0.25, delay=0.0), \"night\": dict(wind=0.4, gust=0.0, gps=0.5, delay=0.15),\n",
" \"gust\": dict(wind=1.2, gust=2.5, gps=0.25, delay=0.0)}\n",
"OBST_T, LAND_T = 85.0, 100.0\n",
"\n",
"\n",
"def make_pilots(n=40, seed=7):\n",
" rng = np.random.default_rng(seed)\n",
" skill = rng.beta(2.2, 2.0, n)\n",
" return pd.DataFrame({\"pilot\": [f\"P{i + 1:03d}\" for i in range(n)], \"skill\": skill,\n",
" \"jerky\": rng.random(n) < 0.15 + 0.35 * (1 - skill), \"overcorr\": rng.random(n) < 0.15 + 0.35 * (1 - skill)})\n",
"\n",
"\n",
"def target_path(t):\n",
" \"\"\"์ฌ๊ฐํ ๊ฒฝ๋ก(40x30m, ๊ณ ๋ 20m)๋ฅผ 3m/s๋ก ๋นํ -> 60์ด ์ง์ ์์ ํธ๋ฒ๋ง -> ์ฐฉ๋ฅ ์ง์ ํ ๊ณ ๋ 0.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 8] 100๋ช
์กฐ์ข
์ ๋๋ ๋นํ๋ก๊ทธ ๋ฐฐ์น ๋ถ์ ์คํธ๋ ์ค ํ
์คํธ ๋ฐ ์ฑ๋ฅ ํ๋กํ์ผ๋ง ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def fly(pilots, cond=\"day\", seed=0, delay_extra=0.0, obstacle_side=None):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์์ ๋นํ 1ํ๋ฅผ ๋์์ ๋ชจ์ํ๋ค(๋ฒกํฐํ). ๋ฐํ: ๋ก๊ทธ ๋ฐฐ์ด dict, ๋นํ๋ณ ์ ๋ต ์ ๋ณด DataFrame.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 8] 100๋ช
์กฐ์ข
์ ๋๋ ๋นํ๋ก๊ทธ ๋ฐฐ์น ๋ถ์ ์คํธ๋ ์ค ํ
์คํธ ๋ฐ ์ฑ๋ฅ ํ๋กํ์ผ๋ง ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def one(log, j):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ์์ j๋ฒ์งธ ์กฐ์ข
์ 1๋ช
์ ๋ก๊ทธ(DataFrame)๋ฅผ ๊บผ๋ธ๋ค.\"\"\"\n",
" return pd.DataFrame({k: (v if v.ndim == 1 else v[:, j]) for k, v in log.items()})\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ๋นํ๋ก๊ทธ ํน์ง ์ถ์ถ ๊ณตํต ํจ์(์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ๋ฅผ ํ ๋ฒ์ ๊ณ์ฐ) ----\n",
"from scipy.ndimage import uniform_filter1d\n",
"\n",
"BASE_F = [\"path_rmse\", \"hover_drift\", \"hover_std\", \"alt_err\"]\n",
"ADV_F = BASE_F + [\"obs_rt\", \"obs_lateral\", \"touch_vz\", \"att_exceed\", \"jerk\", \"overshoot\", \"n_risk\"]\n",
"RULES = {\"near_collision\": (\"obs_lateral\", \"<\", 2.5), \"hard_landing\": (\"touch_vz\", \">\", 2.0), \"attitude_exceed\": (\"att_exceed\", \">=\", 10)}\n",
"\n",
"\n",
"def detect(df):\n",
" \"\"\"์ํ ์กฐ์ ์ฌ๊ฑด ํ์ง ๊ท์น: ์ฅ์ ๋ฌผ 2.5์ด ํ ํํผ ๊ฑฐ๋ฆฌ 2.5m ๋ฏธ๋ง, ์ ์ง ์๋ 2.0m/s ์ด๊ณผ, ์์ธ 35๋ ์ด๊ณผ 1์ด ์ด์.\"\"\"\n",
" out = {}\n",
" for ev, (f, op, th) in RULES.items():\n",
" out[ev] = df[f] < th if op == \"<\" else (df[f] > th if op == \">\" else df[f] >= th)\n",
" return pd.DataFrame(out)\n",
"\n",
"\n",
"def features(log):\n",
" \"\"\"๊ฒฝ๋ก ์ดํ๋ยทํธ๋ฒ๋ง ์์ ์ฑ(๊ธฐ๋ณธ) + ์ฅ์ ๋ฌผ ๋ฐ์ยท์ฐฉ๋ฅ ์๋ยท์์ธ ํ๊ณยท๊ธ์กฐ์ยท๊ณผ๋ณด์ (๊ณ ๋ํ) ํน์ง.\"\"\"\n",
" X, Y, A = (uniform_filter1d(log[k], 9, axis=0) for k in (\"x\", \"y\", \"alt\")) # GPS ์ก์ ์ํ(์ด๋ํ๊ท 0.9์ด)\n",
" h = np.hypot(X - log[\"tx\"], Y - log[\"ty\"])\n",
" cr, hv = slice(0, 600), slice(620, 850)\n",
" k0, k1 = int(OBST_T / DT), int((OBST_T + 2.5) / DT)\n",
" side = np.sign(log[\"ty\"][k0 + 1] - log[\"ty\"][k0 - 1])\n",
" moved = (log[\"stick_roll\"][k0:k0 + 40] * side) > 0.3\n",
" rt = np.where(moved.any(0), moved.argmax(0) * DT, 4.0)\n",
" vz = -np.gradient(A, DT, axis=0)\n",
" land = np.argmax((A < 0.4) & (T[:, None] >= LAND_T), axis=0)\n",
" touch = np.array([vz[max(i - 6, 0):i - 2, j].mean() if i > 6 else 3.0 for j, i in enumerate(land)])\n",
" ds = np.abs(np.diff(np.stack([log[\"stick_roll\"], log[\"stick_pitch\"]]), axis=1)) / DT\n",
" return pd.DataFrame({\n",
" \"path_rmse\": np.sqrt((h[cr] ** 2).mean(0)), \"hover_drift\": h[hv].mean(0), \"hover_std\": np.hypot(X[hv].std(0), Y[hv].std(0)),\n",
" \"alt_err\": np.abs(A[:850] - log[\"talt\"][:850]).mean(0),\n",
" \"obs_rt\": rt, \"obs_lateral\": np.abs(Y[k1] - Y[k0]), \"touch_vz\": touch,\n",
" \"att_exceed\": (np.maximum(np.abs(log[\"roll_deg\"]), np.abs(log[\"pitch_deg\"])) > 35).sum(0),\n",
" \"jerk\": np.percentile(ds, 95, axis=(0, 1)),\n",
" \"overshoot\": np.clip(np.abs(Y[k0:k0 + 120] - Y[k0]).max(0) - 6.0, 0, None)}).pipe(\n",
" lambda d: d.assign(n_risk=detect(d).sum(axis=1))) # overshoot: ํํผ ํ ๋ชฉํ 6m๋ฅผ ๋์ด์ ๊ฑฐ๋ฆฌ(๊ณผ๋ณด์ )\n",
"\n",
"\n",
"def make_dataset(n_pilots=40, seed=7, conds=(\"day\", \"night\", \"gust\"), flights=1):\n",
" \"\"\"์กฐ์ข
์ n๋ช
x ์กฐ๊ฑด x ๋ฐ๋ณต ๋นํ -> ๋นํ๋ณ ํน์ง + ์ ๋ต(๊ต๊ด ์ ์ยท๋ฑ๊ธยท์ํ ์ฌ๊ฑด) ํ.\"\"\"\n",
" pilots = make_pilots(n_pilots, seed)\n",
" out = []\n",
" for c in conds:\n",
" for f in range(flights):\n",
" log, tr = fly(pilots, c, seed=seed * 100 + list(COND).index(c) * 10 + f)\n",
" out.append(pd.concat([tr.reset_index(drop=True), features(log)], axis=1).assign(flight=f))\n",
" return pd.concat(out, ignore_index=True)\n",
"\n",
"\n",
"def cond_z(df, ref, cols):\n",
" \"\"\"์กฐ๊ฑด๋ณ ๊ธฐ์ค ์ง๋จ(ref)์ ํ๊ท ยทํ์คํธ์ฐจ๋ก ํ์คํ: ๋ํ์ฒ๋ผ ์ด๋ ค์ด ์กฐ๊ฑด์์ ๋ฐ๋ ๋ถ์ด์ต์ ์ค์ธ๋ค.\"\"\"\n",
" z = df.copy()\n",
" z[cols] = z[cols].astype(float)\n",
" for c in df[\"cond\"].unique():\n",
" r = ref[ref[\"cond\"] == c][cols]\n",
" z.loc[df[\"cond\"] == c, cols] = (df.loc[df[\"cond\"] == c, cols] - r.mean()) / (r.std() + 1e-9)\n",
" return z\n",
"\n",
"\n",
"def to_grade(score):\n",
" return np.where(score >= 75, \"A\", np.where(score >= 55, \"B\", \"C\"))\n",
"\n",
"KEYS = [\"t\", \"x\", \"y\", \"alt\", \"stick_pitch\", \"stick_roll\", \"throttle\", \"roll_deg\", \"pitch_deg\", \"tx\", \"ty\", \"talt\"]\n",
"\n",
"\n",
"def check_log(L):\n",
" \"\"\"๋ก๊ทธ 1๊ฑด ๊ฒ์ฆ. ๋ฌธ์ ๊ฐ ์์ผ๋ฉด ์ฌ์ ๋ฌธ์์ด, ์์ผ๋ฉด None.\"\"\"\n",
" if any(k not in L for k in KEYS):\n",
" return \"ํ๋ ๋๋ฝ\"\n",
" if len(L[\"t\"]) != STEPS:\n",
" return f\"๊ธธ์ด ์ค๋ฅ({len(L['t'])})\"\n",
" if np.isnan(L[\"x\"]).mean() > 0.2:\n",
" return \"๊ฒฐ์ธก 20% ์ด๊ณผ\"\n",
" if np.any(np.diff(L[\"t\"]) <= 0):\n",
" return \"์๊ฐ ์ญํ\"\n",
" if np.abs(L[\"stick_roll\"]).max() > 1.0001:\n",
" return \"์คํฑ ๋ฒ์ ์ด๊ณผ\"\n",
" return None\n",
"\n",
"\n",
"def score_chunk(logs, conds, eng, ref):\n",
" stacked = {k: np.stack([L[k] for L in logs], axis=1) if k != \"t\" else T for k in KEYS}\n",
" stacked = {k: (pd.DataFrame(v).interpolate(limit_direction=\"both\").values if v.ndim == 2 else v) for k, v in stacked.items()}\n",
" f = features(stacked).assign(cond=conds)\n",
" return np.clip(eng.predict(cond_z(f, ref, ADV_F)[ADV_F]), 0, 100)\n",
"\n",
"\n",
"def run_batch(logs, conds, eng, ref, chunk=100, fail_chunk=None):\n",
" scored, quarantine, degraded, down = {}, {}, 0, 0\n",
" try:\n",
" ok = []\n",
" for i, L in enumerate(logs):\n",
" why = check_log(L)\n",
" (quarantine.__setitem__(i, why) if why else ok.append(i))\n",
" for c0 in range(0, len(ok), chunk):\n",
" idx = ok[c0:c0 + chunk]\n",
" try:\n",
" if fail_chunk is not None and c0 // chunk == fail_chunk:\n",
" raise MemoryError(\"๋ชจ์ ์์ ๋ถ์กฑ\")\n",
" for i, s in zip(idx, score_chunk([logs[i] for i in idx], [conds[i] for i in idx], eng, ref)):\n",
" scored[i] = s\n",
" except Exception: # ๊ธฐ๋ฅ ์ถ์: 1๊ฑด์ฉ ์ฒ๋ฆฌ, ๊ทธ๋๋ ์คํจํ๋ฉด ๊ฒฉ๋ฆฌ\n",
" degraded += 1\n",
" for i in idx:\n",
" try:\n",
" scored[i] = score_chunk([logs[i]], [conds[i]], eng, ref)[0]\n",
" except Exception as e:\n",
" quarantine[i] = f\"์ฒ๋ฆฌ ์คํจ({type(e).__name__})\"\n",
" except Exception as e:\n",
" down = 1; print(\"๋ค์ด:\", e)\n",
" return scored, quarantine, degraded, down\n",
"\n",
"\n",
"# ์์ง ์ค๋น(๋ฏธ์
4 ๋ฐฉ์)\n",
"train = make_dataset(40, seed=7)\n",
"eng = Ridge(alpha=1.0).fit(cond_z(train, train, ADV_F)[ADV_F], train[\"instructor\"])\n",
"\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์กฐ์ข
์ 100๋ช
x ๋นํ 6ํ = ๋ก๊ทธ 600๊ฑด ์์ฑ\n",
"pilots = make_pilots(100, seed=12)\n",
"LOGS, CONDS = [], []\n",
"for f in range(6):\n",
" cond = list(COND)[f % 3]\n",
" log, _ = fly(pilots, cond, seed=900 + f)\n",
" for j in range(len(pilots)):\n",
" LOGS.append({k: (v if v.ndim == 1 else v[:, j].copy()) for k, v in log.items() if k in KEYS}); CONDS.append(cond)\n",
"rng = np.random.default_rng(5)\n",
"BAD = []\n",
"for i in rng.choice(len(LOGS), 30, replace=False): # ์์ ๋ก๊ทธ 5% ์ฃผ์
\n",
" L = {k: v.copy() for k, v in LOGS[i].items()}\n",
" kind = i % 4\n",
" if kind == 0:\n",
" del L[\"throttle\"]\n",
" elif kind == 1:\n",
" L[\"x\"][:400] = np.nan\n",
" elif kind == 2:\n",
" L = {k: v[:500] for k, v in L.items()}\n",
" else:\n",
" L[\"stick_roll\"][100] = 7.0\n",
" BAD.append((i, L))\n",
"CORRUPT = list(LOGS)\n",
"for i, L in BAD:\n",
" CORRUPT[i] = L\n",
"\n",
"CASES = [(\"ํ์ 100๊ฑด\", LOGS[:100], CONDS[:100], None), (\"100๋ช
x 5ํ 500๊ฑด\", LOGS[:500], CONDS[:500], None),\n",
" (\"500% ์ฆ๊ฐ 600๊ฑด\", LOGS, CONDS, None), (\"600๊ฑด + ์์ 5%\", CORRUPT, CONDS, None),\n",
" (\"600๊ฑด + ์์ + ๋ฌถ์ ์์ธ\", CORRUPT, CONDS, 2)]\n",
"rows = []\n",
"for name, logs, conds, fail in CASES:\n",
" t0 = time.perf_counter()\n",
" sc, qu, dg, dn = run_batch(logs, conds, eng, train, fail_chunk=fail)\n",
" dt = time.perf_counter() - t0\n",
" rows.append({\"์ํฉ\": name, \"๋ก๊ทธ\": len(logs), \"์ฑ์ \": len(sc), \"๊ฒฉ๋ฆฌ\": len(qu), \"์ ์ค\": len(logs) - len(sc) - len(qu),\n",
" \"๋ค์ด\": dn, \"๊ธฐ๋ฅ์ถ์ ๋ฌถ์\": dg, \"์ฒ๋ฆฌ๋(๊ฑด/s)\": round(len(logs) / dt), \"์์(s)\": round(dt, 2)})\n",
"R = pd.DataFrame(rows)\n",
"print(R.to_string(index=False))\n",
"print(f\"\\n์์คํ
๋ค์ด {R['๋ค์ด'].sum()}๊ฑด, ๋ก๊ทธ ์ ์ค {R['์ ์ค'].sum()}๊ฑด(์ฑ์ +๊ฒฉ๋ฆฌ=์ ์ฒด), ์ต์ ์ฒ๋ฆฌ๋ {R['์ฒ๋ฆฌ๋(๊ฑด/s)'].min()}๊ฑด/s\")\n",
"_, qu, _, _ = run_batch(CORRUPT, CONDS, eng, train)\n",
"print(\"๊ฒฉ๋ฆฌ ์ฌ์ :\", pd.Series(list(qu.values())).value_counts().to_dict(), f\"/ ์ฃผ์
{len(BAD)}๊ฑด\")\n",
"\n",
"tracemalloc.start()\n",
"mem = []\n",
"for k in range(3):\n",
" run_batch(LOGS[:200], CONDS[:200], eng, train)\n",
" mem.append(tracemalloc.get_traced_memory()[0] / 1e6)\n",
"peak = tracemalloc.get_traced_memory()[1] / 1e6\n",
"tracemalloc.stop()\n",
"print(f\"200๊ฑด ์ฒ๋ฆฌ ์ต๋ ๋ฉ๋ชจ๋ฆฌ {peak:.0f} MB, ๋ฐ๋ณต ํ ์๋ฅ {[round(m, 1) for m in mem]} MB -> {'๋์ ์์ฌ' if mem[-1] > mem[0] * 1.5 + 1 else '๋์ ์งํ ์์'}\")\n",
"\n",
"pr = cProfile.Profile(); pr.enable()\n",
"run_batch(LOGS[:300], CONDS[:300], eng, train)\n",
"pr.disable()\n",
"s = io.StringIO(); pstats.Stats(pr, stream=s).sort_stats(\"cumulative\").print_stats(30)\n",
"top = [ln.split() for ln in s.getvalue().splitlines() if ln.strip()[:1].isdigit() and \".py\" in ln and \"m08\" in ln.split()[-1]]\n",
"print(\"[๋์ ์๊ฐ ์์ ํจ์(์ด ํ์ผ)]\")\n",
"for f in top[:4]:\n",
" print(f\" {f[0]:>6}ํ {f[3]}s {f[-1].split(':')[-1]}\")\n",
"print(\"๊ฐ์ ๋ฐฉํฅ: ๋นํ๋ณ ๋ณด๊ฐ(DataFrame.interpolate)์ numpy ์ผ๊ด ๋ณด๊ฐ์ผ๋ก ๋ฐ๊พธ๊ณ , ๋ฌถ์ ํฌ๊ธฐ๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ๋ง์ถฐ ์กฐ์ ํ๋ค.\")\n",
"\n",
"plt.figure(figsize=(8, 4))\n",
"plt.bar([f\"case{i + 1}\" for i in range(len(R))], R[\"์ฒ๋ฆฌ๋(๊ฑด/s)\"], color=[\"tab:blue\"] * 3 + [\"tab:orange\", \"tab:red\"])\n",
"plt.ylabel(\"logs per second\"); plt.title(\"Batch analysis stress test throughput (synthetic)\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
08 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
" ์ํฉ ๋ก๊ทธ ์ฑ์ ๊ฒฉ๋ฆฌ ์ ์ค ๋ค์ด ๊ธฐ๋ฅ์ถ์ ๋ฌถ์ ์ฒ๋ฆฌ๋(๊ฑด/s) ์์(s)\n",
" ํ์ 100๊ฑด 100 100 0 0 0 0 1297 0.08\n",
" 100๋ช
x 5ํ 500๊ฑด 500 500 0 0 0 0 1290 0.39\n",
" 500% ์ฆ๊ฐ 600๊ฑด 600 600 0 0 0 0 1214 0.49\n",
" 600๊ฑด + ์์ 5% 600 570 30 0 0 0 1258 0.48\n",
"600๊ฑด + ์์ + ๋ฌถ์ ์์ธ 600 570 30 0 0 1 264 2.28\n",
"\n",
"์์คํ
๋ค์ด 0๊ฑด, ๋ก๊ทธ ์ ์ค 0๊ฑด(์ฑ์ +๊ฒฉ๋ฆฌ=์ ์ฒด), ์ต์ ์ฒ๋ฆฌ๋ 264๊ฑด/s\n",
"๊ฒฉ๋ฆฌ ์ฌ์ : {'์คํฑ ๋ฒ์ ์ด๊ณผ': 10, 'ํ๋ ๋๋ฝ': 8, '๊ธธ์ด ์ค๋ฅ(500)': 6, '๊ฒฐ์ธก 20% ์ด๊ณผ': 6} / ์ฃผ์
30๊ฑด\n",
"200๊ฑด ์ฒ๋ฆฌ ์ต๋ ๋ฉ๋ชจ๋ฆฌ 22 MB, ๋ฐ๋ณต ํ ์๋ฅ [0.0, 0.0, 0.0] MB -> ๋์ ์งํ ์์\n",
"[๋์ ์๊ฐ ์์ ํจ์(์ด ํ์ผ)]\n",
" 1ํ 0.398s 193(run_batch)\n",
" 3ํ 0.376s 186(score_chunk)\n",
" 3ํ 0.171s 188()\n",
" 3ํ 0.088s 155(cond_z)\n",
"๊ฐ์ ๋ฐฉํฅ: ๋นํ๋ณ ๋ณด๊ฐ(DataFrame.interpolate)์ numpy ์ผ๊ด ๋ณด๊ฐ์ผ๋ก ๋ฐ๊พธ๊ณ , ๋ฌถ์ ํฌ๊ธฐ๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ๋ง์ถฐ ์กฐ์ ํ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
09. AAR ๊ธฐ๋ฐ ๊ต๊ด ํ๊ฐ ์ ์์ AI ์๋ จ๋ ์ ์ ๊ฐ ํธ์ฐจ ๋ถ์ ๋ฐ ๊ฐ์ค์น ์ฌํ๋\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"AAR ๊ธฐ๋ฐ ๊ต๊ด ํ๊ฐ ์ ์์ AI ์ ์ ๊ฐ ํธ์ฐจ๋ฅผ ๋ถ์ํ๊ณ ๊ฐ์ค์น๋ฅผ ์ฌ๋ณด์ ํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **AAR๋ก ๊ต๊ด ํ๊ฐ ์ ์์ AI ์ ์์ ํธ์ฐจ๋ฅผ ๋ถ์ํด ์์ธ์ ๊ท๋ช
ํ๊ณ ์ฑ์ ๊ฐ์ค์น๋ฅผ ์ฌํ๋ํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ฃผ๊ฐ ๋นํ๋ง์ผ๋ก ๋ง์ถ ์ด๊ธฐ AI๋ฅผ ํ ๊ธฐ์ ํ๋ จ์ ์ ์ฉํด ์กฐ๊ฑด๋ณยท์ํ ์ฌ๊ฑด ์๋ณยท์ต๊ด๋ณ ํธ์ฐจ๋ฅผ ๋ถํดํ๊ณ ์์ธ ์ ๋ฆฌํ๊ณ ๊ท๊ฒฉํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `docs/trn01_aar_report.md ๋ฐ configs/trn01_tuned_params.yaml` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ค๋ฅ ์์ธ ๊ท๋ช
์ ๊ฐ๊ด์ฑ ๋ฐ ๊ฐ์ ์กฐ์น์ ๋
ผ๋ฆฌ์ ํ๋น์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: AAR ํผ๋๋ฐฑ ์ ์ฉ ํ ์ข
ํฉ ์ ํ๋ 10%p ์ด์ ํฅ์, ์ฌ๋ฐ ์ค๋ฅ 0๊ฑด\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: AAR ๋ณด๊ณ ์ ์์ฑ ๋ฐ ํ๋ผ๋ฏธํฐ ํ๋ ์ ํ ํ๊ท ํ
์คํธ ํต๊ณผ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ํ๊ท ํ
์คํธ(Regression Test) ์ค์ํธ ์ ์ฒด ์๋ ์คํ ๋ฐ ๋น๊ต\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"AAR(์ฌํ๊ฒํ )๋ก ๊ต๊ด ํ๊ฐ ์ ์์ AI ์๋ จ๋ ์ ์์ ํธ์ฐจ๋ฅผ ๋นํ ์กฐ๊ฑดยท์ํ ์ฌ๊ฑดยท์กฐ์ ์ต๊ด๋ณ๋ก ๋ถ์ํด ์์ธ์ ๊ท๋ช
ํ๊ณ , ํน์ง ๊ตฌ์ฑยท์กฐ๊ฑด ํ์คํยท๊ฐ์ค์น(๊ท์ ๊ฐ๋)๋ฅผ ๊ฒฉ์ ํ์์ผ๋ก ์ฌํ๋ํ ๋ค ๋ค๋ฅธ ์กฐ์ข
์ ์ง๋จ์ผ๋ก ํ๊ท ํ
์คํธํด ๊ฐ์ ์ ํ๋ฅผ ๋น๊ตํ๋ค.\"\"\"\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from sklearn.linear_model import Ridge\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ ์์ฑ๊ธฐ (์ค์ ํ๋ จ์ยท์ฅ๋น ์ ๋ณด ์์, ์กฐ์ข
์๋ ๊ฐ๋ช
P001~)\n",
"# ๋นํ 1ํ = 120์ด(10Hz): ๊ฒฝ๋ก ๋นํ(0~60์ด) -> ํธ๋ฒ๋ง(60~85์ด) -> ๋๋ฐ ์ฅ์ ๋ฌผ ํํผ(85์ด) -> ๋น์ ์ฐฉ๋ฅ(100์ด ์ง์)\n",
"# ์กฐ์ข
์๋ง๋ค ์จ์ ์๋ จ๋(skill)์ ์กฐ์ ์ต๊ด(๊ธ์กฐ์ jerky, ๊ณผ๋ณด์ overcorr)์ด ์๊ณ , ๊ต๊ด ์ ์๋ ์ด๋ฅผ ๋ฐ์ํด ๋ง๋ ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"DT, STEPS = 0.1, 1200\n",
"T = np.arange(STEPS) * DT\n",
"COND = {\"day\": dict(wind=0.4, gust=0.0, gps=0.25, delay=0.0), \"night\": dict(wind=0.4, gust=0.0, gps=0.5, delay=0.15),\n",
" \"gust\": dict(wind=1.2, gust=2.5, gps=0.25, delay=0.0)}\n",
"OBST_T, LAND_T = 85.0, 100.0\n",
"\n",
"\n",
"def make_pilots(n=40, seed=7):\n",
" rng = np.random.default_rng(seed)\n",
" skill = rng.beta(2.2, 2.0, n)\n",
" return pd.DataFrame({\"pilot\": [f\"P{i + 1:03d}\" for i in range(n)], \"skill\": skill,\n",
" \"jerky\": rng.random(n) < 0.15 + 0.35 * (1 - skill), \"overcorr\": rng.random(n) < 0.15 + 0.35 * (1 - skill)})\n",
"\n",
"\n",
"def target_path(t):\n",
" \"\"\"์ฌ๊ฐํ ๊ฒฝ๋ก(40x30m, ๊ณ ๋ 20m)๋ฅผ 3m/s๋ก ๋นํ -> 60์ด ์ง์ ์์ ํธ๋ฒ๋ง -> ์ฐฉ๋ฅ ์ง์ ํ ๊ณ ๋ 0.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 9] AAR ๊ธฐ๋ฐ ๊ต๊ด ํ๊ฐ ์ ์์ AI ์๋ จ๋ ์ ์ ๊ฐ ํธ์ฐจ ๋ถ์ ๋ฐ ๊ฐ์ค์น ์ฌํ๋ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def fly(pilots, cond=\"day\", seed=0, delay_extra=0.0, obstacle_side=None):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์์ ๋นํ 1ํ๋ฅผ ๋์์ ๋ชจ์ํ๋ค(๋ฒกํฐํ). ๋ฐํ: ๋ก๊ทธ ๋ฐฐ์ด dict, ๋นํ๋ณ ์ ๋ต ์ ๋ณด DataFrame.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 9] AAR ๊ธฐ๋ฐ ๊ต๊ด ํ๊ฐ ์ ์์ AI ์๋ จ๋ ์ ์ ๊ฐ ํธ์ฐจ ๋ถ์ ๋ฐ ๊ฐ์ค์น ์ฌํ๋ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def one(log, j):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ์์ j๋ฒ์งธ ์กฐ์ข
์ 1๋ช
์ ๋ก๊ทธ(DataFrame)๋ฅผ ๊บผ๋ธ๋ค.\"\"\"\n",
" return pd.DataFrame({k: (v if v.ndim == 1 else v[:, j]) for k, v in log.items()})\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ๋นํ๋ก๊ทธ ํน์ง ์ถ์ถ ๊ณตํต ํจ์(์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ๋ฅผ ํ ๋ฒ์ ๊ณ์ฐ) ----\n",
"from scipy.ndimage import uniform_filter1d\n",
"\n",
"BASE_F = [\"path_rmse\", \"hover_drift\", \"hover_std\", \"alt_err\"]\n",
"ADV_F = BASE_F + [\"obs_rt\", \"obs_lateral\", \"touch_vz\", \"att_exceed\", \"jerk\", \"overshoot\", \"n_risk\"]\n",
"RULES = {\"near_collision\": (\"obs_lateral\", \"<\", 2.5), \"hard_landing\": (\"touch_vz\", \">\", 2.0), \"attitude_exceed\": (\"att_exceed\", \">=\", 10)}\n",
"\n",
"\n",
"def detect(df):\n",
" \"\"\"์ํ ์กฐ์ ์ฌ๊ฑด ํ์ง ๊ท์น: ์ฅ์ ๋ฌผ 2.5์ด ํ ํํผ ๊ฑฐ๋ฆฌ 2.5m ๋ฏธ๋ง, ์ ์ง ์๋ 2.0m/s ์ด๊ณผ, ์์ธ 35๋ ์ด๊ณผ 1์ด ์ด์.\"\"\"\n",
" out = {}\n",
" for ev, (f, op, th) in RULES.items():\n",
" out[ev] = df[f] < th if op == \"<\" else (df[f] > th if op == \">\" else df[f] >= th)\n",
" return pd.DataFrame(out)\n",
"\n",
"\n",
"def features(log):\n",
" \"\"\"๊ฒฝ๋ก ์ดํ๋ยทํธ๋ฒ๋ง ์์ ์ฑ(๊ธฐ๋ณธ) + ์ฅ์ ๋ฌผ ๋ฐ์ยท์ฐฉ๋ฅ ์๋ยท์์ธ ํ๊ณยท๊ธ์กฐ์ยท๊ณผ๋ณด์ (๊ณ ๋ํ) ํน์ง.\"\"\"\n",
" X, Y, A = (uniform_filter1d(log[k], 9, axis=0) for k in (\"x\", \"y\", \"alt\")) # GPS ์ก์ ์ํ(์ด๋ํ๊ท 0.9์ด)\n",
" h = np.hypot(X - log[\"tx\"], Y - log[\"ty\"])\n",
" cr, hv = slice(0, 600), slice(620, 850)\n",
" k0, k1 = int(OBST_T / DT), int((OBST_T + 2.5) / DT)\n",
" side = np.sign(log[\"ty\"][k0 + 1] - log[\"ty\"][k0 - 1])\n",
" moved = (log[\"stick_roll\"][k0:k0 + 40] * side) > 0.3\n",
" rt = np.where(moved.any(0), moved.argmax(0) * DT, 4.0)\n",
" vz = -np.gradient(A, DT, axis=0)\n",
" land = np.argmax((A < 0.4) & (T[:, None] >= LAND_T), axis=0)\n",
" touch = np.array([vz[max(i - 6, 0):i - 2, j].mean() if i > 6 else 3.0 for j, i in enumerate(land)])\n",
" ds = np.abs(np.diff(np.stack([log[\"stick_roll\"], log[\"stick_pitch\"]]), axis=1)) / DT\n",
" return pd.DataFrame({\n",
" \"path_rmse\": np.sqrt((h[cr] ** 2).mean(0)), \"hover_drift\": h[hv].mean(0), \"hover_std\": np.hypot(X[hv].std(0), Y[hv].std(0)),\n",
" \"alt_err\": np.abs(A[:850] - log[\"talt\"][:850]).mean(0),\n",
" \"obs_rt\": rt, \"obs_lateral\": np.abs(Y[k1] - Y[k0]), \"touch_vz\": touch,\n",
" \"att_exceed\": (np.maximum(np.abs(log[\"roll_deg\"]), np.abs(log[\"pitch_deg\"])) > 35).sum(0),\n",
" \"jerk\": np.percentile(ds, 95, axis=(0, 1)),\n",
" \"overshoot\": np.clip(np.abs(Y[k0:k0 + 120] - Y[k0]).max(0) - 6.0, 0, None)}).pipe(\n",
" lambda d: d.assign(n_risk=detect(d).sum(axis=1))) # overshoot: ํํผ ํ ๋ชฉํ 6m๋ฅผ ๋์ด์ ๊ฑฐ๋ฆฌ(๊ณผ๋ณด์ )\n",
"\n",
"\n",
"def make_dataset(n_pilots=40, seed=7, conds=(\"day\", \"night\", \"gust\"), flights=1):\n",
" \"\"\"์กฐ์ข
์ n๋ช
x ์กฐ๊ฑด x ๋ฐ๋ณต ๋นํ -> ๋นํ๋ณ ํน์ง + ์ ๋ต(๊ต๊ด ์ ์ยท๋ฑ๊ธยท์ํ ์ฌ๊ฑด) ํ.\"\"\"\n",
" pilots = make_pilots(n_pilots, seed)\n",
" out = []\n",
" for c in conds:\n",
" for f in range(flights):\n",
" log, tr = fly(pilots, c, seed=seed * 100 + list(COND).index(c) * 10 + f)\n",
" out.append(pd.concat([tr.reset_index(drop=True), features(log)], axis=1).assign(flight=f))\n",
" return pd.concat(out, ignore_index=True)\n",
"\n",
"\n",
"def cond_z(df, ref, cols):\n",
" \"\"\"์กฐ๊ฑด๋ณ ๊ธฐ์ค ์ง๋จ(ref)์ ํ๊ท ยทํ์คํธ์ฐจ๋ก ํ์คํ: ๋ํ์ฒ๋ผ ์ด๋ ค์ด ์กฐ๊ฑด์์ ๋ฐ๋ ๋ถ์ด์ต์ ์ค์ธ๋ค.\"\"\"\n",
" z = df.copy()\n",
" z[cols] = z[cols].astype(float)\n",
" for c in df[\"cond\"].unique():\n",
" r = ref[ref[\"cond\"] == c][cols]\n",
" z.loc[df[\"cond\"] == c, cols] = (df.loc[df[\"cond\"] == c, cols] - r.mean()) / (r.std() + 1e-9)\n",
" return z\n",
"\n",
"\n",
"def to_grade(score):\n",
" return np.where(score >= 75, \"A\", np.where(score >= 55, \"B\", \"C\"))\n",
"\n",
"\n",
"def fit(df, feats, norm, alpha):\n",
" X = cond_z(df, df, feats)[feats] if norm else df[feats]\n",
" return Ridge(alpha=alpha).fit(X, df[\"instructor\"])\n",
"\n",
"\n",
"def predict(m, df, ref, feats, norm):\n",
" X = cond_z(df, ref, feats)[feats] if norm else df[feats]\n",
" return np.clip(m.predict(X), 0, 100)\n",
"\n",
"\n",
"# ์ด๊ธฐ ์ด์ฉ AI: ์ฃผ๊ฐ ๋นํ๋ง์ผ๋ก ๋ง์ถ Baseline(๊ฒฝ๋กยทํธ๋ฒ๋งยท๊ณ ๋, ์กฐ๊ฑด ํ์คํ ์์)\n",
"day_train = make_dataset(40, seed=7, conds=(\"day\",))\n",
"INIT = dict(feats=BASE_F, norm=False, alpha=1.0)\n",
"init_m = fit(day_train, **INIT)\n",
"\n",
"# 1) ๋ฌด์์ด ์ผ์ด๋ฌ๋: ํ๋ จ 1๊ฐ ๊ธฐ์(์กฐ์ข
์ 40๋ช
x 3์กฐ๊ฑด)์ ๊ต๊ด ์ ์์ AI ์ ์ ๋น๊ต\n",
"aar = make_dataset(40, seed=8)\n",
"aar[\"ai\"] = predict(init_m, aar, day_train, INIT[\"feats\"], INIT[\"norm\"])\n",
"aar[\"dev\"] = aar[\"ai\"] - aar[\"instructor\"]\n",
"acc0 = (to_grade(aar[\"ai\"]) == aar[\"grade\"]).mean()\n",
"print(f\"[1. ๋ฌด์์ด ์ผ์ด๋ฌ๋] ์ด๊ธฐ AI ๋ฑ๊ธ ์ผ์น์จ {acc0:.1%}, ์ ์ ํธ์ฐจ(AI-๊ต๊ด) ํ๊ท {aar['dev'].mean():+.1f}์ , ์ ๋ ํธ์ฐจ {aar['dev'].abs().mean():.1f}์ \")\n",
"\n",
"# 2) ์ ์ผ์ด๋ฌ๋: ํธ์ฐจ ์์ธ ๋ถํด\n",
"aar[\"์ํ์ฌ๊ฑด\"] = aar[[\"near_collision\", \"hard_landing\", \"attitude_exceed\"]].sum(axis=1).clip(0, 2).astype(str).str.replace(\"2\", \"2+\")\n",
"print(\"\\n[2. ์ ์ผ์ด๋ฌ๋] ์กฐ๊ฑด๋ณ ํ๊ท ํธ์ฐจ:\", aar.groupby(\"cond\")[\"dev\"].mean().round(1).to_dict())\n",
"ng = aar[aar[\"cond\"] != \"gust\"] # ์กฐ๊ฑด ํจ๊ณผ๋ฅผ ๋บ ์ฃผ๊ฐยท์ผ๊ฐ ๋นํ์ผ๋ก ๋น๊ต\n",
"ev = ng.groupby(\"์ํ์ฌ๊ฑด\")[\"dev\"].mean().round(1)\n",
"print(\"์ฃผ๊ฐยท์ผ๊ฐ ๋นํ์ ์ํ ์ฌ๊ฑด ์๋ณ ํ๊ท ํธ์ฐจ:\", ev.to_dict())\n",
"print(\"์ฃผ๊ฐยท์ผ๊ฐ ๋นํ์ ์ต๊ด๋ณ ํ๊ท ํธ์ฐจ: ๊ธ์กฐ์\", round(ng[ng[\"jerky\"]][\"dev\"].mean(), 1), \"/ ๊ณผ๋ณด์ \", round(ng[ng[\"overcorr\"]][\"dev\"].mean(), 1),\n",
" \"/ ์ต๊ด ์์\", round(ng[~ng[\"jerky\"] & ~ng[\"overcorr\"]][\"dev\"].mean(), 1))\n",
"print(f\"-> ์์ธ: (๊ฐ) ์ฃผ๊ฐ ์๋ฃ๋ก๋ง ๋ง์ถฐ ๋ํ ๋นํ์ ํ๊ท {aar[aar['cond'] == 'gust']['dev'].mean():.0f}์ ๊ณผ์ํ๊ฐ(๊ฒฝ๋ก ์ดํ์ ์ค๋ ฅ ๋ถ์กฑ์ผ๋ก ์ค์ธ), \"\n",
" f\"(๋) ์ํ ์ฌ๊ฑด 2๊ฑด ์ด์ ๋นํ์ 0๊ฑด ๋๋น {ev.get('2+', np.nan) - ev.get('0', np.nan):+.1f}์ ๊ณผ๋ํ๊ฐ(์ฅ์ ๋ฌผยท์ฐฉ๋ฅ ๋์ ๋ฏธ๋ฐ์), (๋ค) ๊ธ์กฐ์ ์ต๊ด ๋ฏธ๋ฐ์\")\n",
"\n",
"# 3) ๋ฌด์์ ๋ฐ๊พธ๋: AAR ์๋ฃ(3์กฐ๊ฑด)๋ก ๊ฒฉ์ ํ์, ๊ธฐ์ ๋ด 2๊ฒน ๊ต์ฐจ๊ฒ์ฆ์ผ๋ก ์ ํ\n",
"pid = aar[\"pilot\"].str[1:].astype(int) % 2\n",
"grid = []\n",
"for name, feats in [(\"๊ธฐ๋ณธ\", BASE_F), (\"๊ณ ๋ํ\", ADV_F)]:\n",
" for norm in (False, True):\n",
" for alpha in (0.1, 1.0, 10.0):\n",
" accs = []\n",
" for k in (0, 1):\n",
" tr, va = aar[pid != k], aar[pid == k]\n",
" m = fit(tr, feats, norm, alpha)\n",
" accs.append((to_grade(predict(m, va, tr, feats, norm)) == va[\"grade\"]).mean())\n",
" grid.append({\"ํน์ง\": name, \"์กฐ๊ฑดํ์คํ\": norm, \"alpha\": alpha, \"๊ต์ฐจ๊ฒ์ฆ ์ผ์น์จ\": round(np.mean(accs), 3)})\n",
"g = pd.DataFrame(grid).sort_values(\"๊ต์ฐจ๊ฒ์ฆ ์ผ์น์จ\", ascending=False)\n",
"print(\"\\n[3. ๋ฌด์์ ๋ฐ๊พธ๋] ๊ฒฉ์ 12๊ฐ ์ค ์์ 4๊ฐ\")\n",
"print(g.head(4).to_string(index=False))\n",
"best = g.iloc[0]\n",
"TUNED = dict(feats=ADV_F if best[\"ํน์ง\"] == \"๊ณ ๋ํ\" else BASE_F, norm=bool(best[\"์กฐ๊ฑดํ์คํ\"]), alpha=float(best[\"alpha\"]))\n",
"tuned_m = fit(aar, **TUNED)\n",
"\n",
"# 4) ํ๊ท ํ
์คํธ: ์ ๊ธฐ์ 2๊ฐ(ํ์ต์ ์ฐ์ง ์์ ์กฐ์ข
์)\n",
"print(\"\\n[ํ๊ท ํ
์คํธ] ๊ธฐ์ ์ด๊ธฐ AI ์ฌํ๋ ๋ณํ\")\n",
"reg, rep, near = [], [], []\n",
"for sd in (21, 22):\n",
" new = make_dataset(40, seed=sd)\n",
" a0 = to_grade(predict(init_m, new, day_train, INIT[\"feats\"], INIT[\"norm\"]))\n",
" a1 = to_grade(predict(tuned_m, new, aar, TUNED[\"feats\"], TUNED[\"norm\"]))\n",
" r0, r1 = (a0 == new[\"grade\"]).mean(), (a1 == new[\"grade\"]).mean()\n",
" both = (a0 != new[\"grade\"]) & (a1 != new[\"grade\"])\n",
" reg.append((r0, r1)); rep.append(int(both.sum()))\n",
" near += list((np.minimum(np.abs(new[\"instructor\"] - 55), np.abs(new[\"instructor\"] - 75)) <= 5)[both])\n",
" print(f\" #{sd} {r0:6.1%} {r1:6.1%} {(r1 - r0) * 100:+5.1f}%p\")\n",
"gain = np.mean([b - a for a, b in reg])\n",
"print(f\"ํ๊ท ํฅ์ {gain * 100:+.1f}%p (๋ชฉํ +10%p ์ด์: {'๋ฌ์ฑ' if gain >= 0.10 else '๋ฏธ๋ฌ'}), ํ๊ท ํ
์คํธ {sum(b >= a for a, b in reg)}/{len(reg)} ํต๊ณผ\")\n",
"print(f\"์ฌํ๋ ํ์๋ ๋ฐ๋ณต๋ ๋ถ์ผ์น {sum(rep)}๊ฑด(์ ์ฒด {2 * len(new)}ํ ์ค), ๊ทธ์ค ๊ต๊ด ์ ์๊ฐ ๋ฑ๊ธ ๊ฒฝ๊ณ(55ยท75์ ) ยฑ5์ ์ด๋ด์ธ ๋นํ {np.mean(near):.0%}\")\n",
"\n",
"w = pd.Series(tuned_m.coef_, index=TUNED[\"feats\"]).round(2)\n",
"yaml = (f\"features: [{', '.join(TUNED['feats'])}]\\ncondition_normalization: {str(TUNED['norm']).lower()}\\nridge_alpha: {TUNED['alpha']}\\n\"\n",
" f\"grade_cut: {{A: 75, B: 55}}\\nweights: {{{', '.join(f'{k}: {v}' for k, v in w.items())}}}\")\n",
"print(\"\\n[์ฌํ๋ ํ๋ผ๋ฏธํฐ(trn01_tuned_params.yaml ๋ด์ฉ)]\\n\" + yaml)\n",
"print(\"์ง์ ๊ฐ์ : ๊ธฐ์๋ง๋ค ๊ต๊ด ์ ์๋ฅผ AAR ์๋ฃ๋ก ๋์ ํด ๊ฐ์ ์ ์ฐจ๋ก ์ฌํ๋ํ๊ณ , ํ๊ท ํ
์คํธ ํต๊ณผ ์์๋ง ๊ต์ฒดํ๋ค.\")\n",
"\n",
"plt.figure(figsize=(7, 4))\n",
"for c, col in zip(COND, [\"tab:blue\", \"tab:purple\", \"tab:orange\"]):\n",
" s = aar[aar[\"cond\"] == c]\n",
" plt.scatter(s[\"instructor\"], s[\"dev\"], s=12, c=col, label=c)\n",
"plt.axhline(0, c=\"k\"); plt.xlabel(\"instructor score\"); plt.ylabel(\"AI - instructor (initial AI)\")\n",
"plt.legend(); plt.title(\"AAR: deviation of initial AI score (synthetic)\"); plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
09 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[1. ๋ฌด์์ด ์ผ์ด๋ฌ๋] ์ด๊ธฐ AI ๋ฑ๊ธ ์ผ์น์จ 47.5%, ์ ์ ํธ์ฐจ(AI-๊ต๊ด) ํ๊ท -12.2์ , ์ ๋ ํธ์ฐจ 21.1์ \n",
"\n",
"[2. ์ ์ผ์ด๋ฌ๋] ์กฐ๊ฑด๋ณ ํ๊ท ํธ์ฐจ: {'day': 2.7, 'gust': -40.1, 'night': 0.9}\n",
"์ฃผ๊ฐยท์ผ๊ฐ ๋นํ์ ์ํ ์ฌ๊ฑด ์๋ณ ํ๊ท ํธ์ฐจ: {'0': -9.8, '1': 0.6, '2+': 11.7}\n",
"์ฃผ๊ฐยท์ผ๊ฐ ๋นํ์ ์ต๊ด๋ณ ํ๊ท ํธ์ฐจ: ๊ธ์กฐ์ 7.3 / ๊ณผ๋ณด์ 3.6 / ์ต๊ด ์์ 0.3\n",
"-> ์์ธ: (๊ฐ) ์ฃผ๊ฐ ์๋ฃ๋ก๋ง ๋ง์ถฐ ๋ํ ๋นํ์ ํ๊ท -40์ ๊ณผ์ํ๊ฐ(๊ฒฝ๋ก ์ดํ์ ์ค๋ ฅ ๋ถ์กฑ์ผ๋ก ์ค์ธ), (๋) ์ํ ์ฌ๊ฑด 2๊ฑด ์ด์ ๋นํ์ 0๊ฑด ๋๋น +21.5์ ๊ณผ๋ํ๊ฐ(์ฅ์ ๋ฌผยท์ฐฉ๋ฅ ๋์ ๋ฏธ๋ฐ์), (๋ค) ๊ธ์กฐ์ ์ต๊ด ๋ฏธ๋ฐ์\n",
"\n",
"[3. ๋ฌด์์ ๋ฐ๊พธ๋] ๊ฒฉ์ 12๊ฐ ์ค ์์ 4๊ฐ\n",
" ํน์ง ์กฐ๊ฑดํ์คํ alpha ๊ต์ฐจ๊ฒ์ฆ ์ผ์น์จ\n",
"๊ณ ๋ํ True 10.0 0.825\n",
"๊ณ ๋ํ True 0.1 0.817\n",
"๊ณ ๋ํ True 1.0 0.808\n",
"๊ณ ๋ํ False 0.1 0.783\n",
"\n",
"[ํ๊ท ํ
์คํธ] ๊ธฐ์ ์ด๊ธฐ AI ์ฌํ๋ ๋ณํ\n",
" #21 45.0% 89.2% +44.2%p\n",
" #22 40.0% 81.7% +41.7%p\n",
"ํ๊ท ํฅ์ +42.9%p (๋ชฉํ +10%p ์ด์: ๋ฌ์ฑ), ํ๊ท ํ
์คํธ 2/2 ํต๊ณผ\n",
"์ฌํ๋ ํ์๋ ๋ฐ๋ณต๋ ๋ถ์ผ์น 21๊ฑด(์ ์ฒด 240ํ ์ค), ๊ทธ์ค ๊ต๊ด ์ ์๊ฐ ๋ฑ๊ธ ๊ฒฝ๊ณ(55ยท75์ ) ยฑ5์ ์ด๋ด์ธ ๋นํ 86%\n",
"\n",
"[์ฌํ๋ ํ๋ผ๋ฏธํฐ(trn01_tuned_params.yaml ๋ด์ฉ)]\n",
"features: [path_rmse, hover_drift, hover_std, alt_err, obs_rt, obs_lateral, touch_vz, att_exceed, jerk, overshoot, n_risk]\n",
"condition_normalization: true\n",
"ridge_alpha: 10.0\n",
"grade_cut: {A: 75, B: 55}\n",
"weights: {path_rmse: -0.59, hover_drift: 1.22, hover_std: 0.77, alt_err: -7.67, obs_rt: -0.51, obs_lateral: 1.08, touch_vz: -4.19, att_exceed: -0.54, jerk: -1.59, overshoot: -0.66, n_risk: -2.4}\n",
"์ง์ ๊ฐ์ : ๊ธฐ์๋ง๋ค ๊ต๊ด ์ ์๋ฅผ AAR ์๋ฃ๋ก ๋์ ํด ๊ฐ์ ์ ์ฐจ๋ก ์ฌํ๋ํ๊ณ , ํ๊ท ํ
์คํธ ํต๊ณผ ์์๋ง ๊ต์ฒดํ๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
10. ๋๋ก ๋นํ๋ก๊ทธ ์๋ จ๋ ๋ถ์ ์์คํ
ํตํฉ ๋ฐ ๊ต์กํ๋ จ ๋ธ๋ฆฌํ ํจํค์ง ์์ฑ\n",
"**์์์๊ฐ**: ์ฝ 3.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๋๋ก ๋นํ๋ก๊ทธ ์๋ จ๋ ๋ถ์ ์์คํ
ํตํฉ ํจํค์ง ๋ฐ ๊ต์กํ๋ จ ๋ธ๋ฆฌํ ๋ณด๊ณ ์๋ฅผ ์์ฑํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๋ก๊ทธ ๊ฒ์ฆ๋ถํฐ ์ ์ฒ๋ฆฌ, ํน์ง ์ถ์ถ, ์ฌํ๋ ์์ง, ์ํ ์กฐ์ ํ์ง, ์กฐ์ข
์ ๋ฑ๊ธยท์ ๋ขฐ๋, ๊ต๊ด ๊ฒํ ๋๊ธฐ์ด, ๋์๋ณด๋๊น์ง ํตํฉํ๊ณ ๊ต์กํ๋ จ ๋ธ๋ฆฌํ ํจํค์ง๋ฅผ ์์ฑํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ฌํ๋ ํ๋ผ๋ฏธํฐ๋ก ์ ๊ท ๊ธฐ์ ๋ก๊ทธ๋ฅผ ํ ๋ฒ์ ๋ถ์ํ๋ ํตํฉ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์์ ๋ก๊ทธ๊ฐ ๊ฒฉ๋ฆฌ๋๋์ง ํ์ธํ๊ณ ์ ํจ์ฑ์ ์ ๊ฒํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/main_trn01.py, README.md, docs/trn01_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": [
"\"\"\"๋นํ๋ก๊ทธ ๊ฒ์ฆยท์ ์ฒ๋ฆฌ๋ถํฐ ํน์ง ์ถ์ถ, ์ฌํ๋๋ ์๋ จ๋ ์์ง, ์ํ ์กฐ์ ํ์ง, ์กฐ์ข
์๋ณ ๋ฑ๊ธยท์ ๋ขฐ๋, ๊ต๊ด HITL ๊ฒํ ๋๊ธฐ์ด, ๋ถ์ ๋์๋ณด๋๊น์ง ์ ์ฒด ํ์ดํ๋ผ์ธ์ ํ ํ์ผ๋ก ํตํฉ ์คํํ๊ณ , ํจ์ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ํฌํจํ ์์ฒด ์ ๊ฒ ๊ฒฐ๊ณผ์ ๊ต์กํ๋ จ ๋ธ๋ฆฌํ ์์ฝ์ ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import hashlib\n",
"import inspect\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 Ridge\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋๋ก ์กฐ์ข
์๋ฎฌ๋ ์ดํฐ ๋นํ๋ก๊ทธ ์์ฑ๊ธฐ (์ค์ ํ๋ จ์ยท์ฅ๋น ์ ๋ณด ์์, ์กฐ์ข
์๋ ๊ฐ๋ช
P001~)\n",
"# ๋นํ 1ํ = 120์ด(10Hz): ๊ฒฝ๋ก ๋นํ(0~60์ด) -> ํธ๋ฒ๋ง(60~85์ด) -> ๋๋ฐ ์ฅ์ ๋ฌผ ํํผ(85์ด) -> ๋น์ ์ฐฉ๋ฅ(100์ด ์ง์)\n",
"# ์กฐ์ข
์๋ง๋ค ์จ์ ์๋ จ๋(skill)์ ์กฐ์ ์ต๊ด(๊ธ์กฐ์ jerky, ๊ณผ๋ณด์ overcorr)์ด ์๊ณ , ๊ต๊ด ์ ์๋ ์ด๋ฅผ ๋ฐ์ํด ๋ง๋ ๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"DT, STEPS = 0.1, 1200\n",
"T = np.arange(STEPS) * DT\n",
"COND = {\"day\": dict(wind=0.4, gust=0.0, gps=0.25, delay=0.0), \"night\": dict(wind=0.4, gust=0.0, gps=0.5, delay=0.15),\n",
" \"gust\": dict(wind=1.2, gust=2.5, gps=0.25, delay=0.0)}\n",
"OBST_T, LAND_T = 85.0, 100.0\n",
"\n",
"\n",
"def make_pilots(n=40, seed=7):\n",
" rng = np.random.default_rng(seed)\n",
" skill = rng.beta(2.2, 2.0, n)\n",
" return pd.DataFrame({\"pilot\": [f\"P{i + 1:03d}\" for i in range(n)], \"skill\": skill,\n",
" \"jerky\": rng.random(n) < 0.15 + 0.35 * (1 - skill), \"overcorr\": rng.random(n) < 0.15 + 0.35 * (1 - skill)})\n",
"\n",
"\n",
"def target_path(t):\n",
" \"\"\"์ฌ๊ฐํ ๊ฒฝ๋ก(40x30m, ๊ณ ๋ 20m)๋ฅผ 3m/s๋ก ๋นํ -> 60์ด ์ง์ ์์ ํธ๋ฒ๋ง -> ์ฐฉ๋ฅ ์ง์ ํ ๊ณ ๋ 0.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 10] ๋๋ก ๋นํ๋ก๊ทธ ์๋ จ๋ ๋ถ์ ์์คํ
ํตํฉ ๋ฐ ๊ต์กํ๋ จ ๋ธ๋ฆฌํ ํจํค์ง ์์ฑ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def fly(pilots, cond=\"day\", seed=0, delay_extra=0.0, obstacle_side=None):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์์ ๋นํ 1ํ๋ฅผ ๋์์ ๋ชจ์ํ๋ค(๋ฒกํฐํ). ๋ฐํ: ๋ก๊ทธ ๋ฐฐ์ด dict, ๋นํ๋ณ ์ ๋ต ์ ๋ณด DataFrame.\"\"\"\n",
" # [TODO : ํ์ ์ค์ต 10] ๋๋ก ๋นํ๋ก๊ทธ ์๋ จ๋ ๋ถ์ ์์คํ
ํตํฉ ๋ฐ ๊ต์กํ๋ จ ๋ธ๋ฆฌํ ํจํค์ง ์์ฑ ๊ด๋ จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ์ธ์.\n",
" # ํํธ: ์๋จ ์ฃผ์ ๋ฐ ๋ฐ์ดํฐ ์คํค๋ง/์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ์์ฑํฉ๋๋ค.\n",
" # <<< ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์ >>>\n",
" pass # ์์ฑ ํ pass๋ฅผ ์ ๊ฑฐํ์ธ์.\n",
"def one(log, j):\n",
" \"\"\"์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ์์ j๋ฒ์งธ ์กฐ์ข
์ 1๋ช
์ ๋ก๊ทธ(DataFrame)๋ฅผ ๊บผ๋ธ๋ค.\"\"\"\n",
" return pd.DataFrame({k: (v if v.ndim == 1 else v[:, j]) for k, v in log.items()})\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ๋นํ๋ก๊ทธ ํน์ง ์ถ์ถ ๊ณตํต ํจ์(์ฌ๋ฌ ์กฐ์ข
์ ๋ก๊ทธ๋ฅผ ํ ๋ฒ์ ๊ณ์ฐ) ----\n",
"from scipy.ndimage import uniform_filter1d\n",
"\n",
"BASE_F = [\"path_rmse\", \"hover_drift\", \"hover_std\", \"alt_err\"]\n",
"ADV_F = BASE_F + [\"obs_rt\", \"obs_lateral\", \"touch_vz\", \"att_exceed\", \"jerk\", \"overshoot\", \"n_risk\"]\n",
"RULES = {\"near_collision\": (\"obs_lateral\", \"<\", 2.5), \"hard_landing\": (\"touch_vz\", \">\", 2.0), \"attitude_exceed\": (\"att_exceed\", \">=\", 10)}\n",
"\n",
"\n",
"def detect(df):\n",
" \"\"\"์ํ ์กฐ์ ์ฌ๊ฑด ํ์ง ๊ท์น: ์ฅ์ ๋ฌผ 2.5์ด ํ ํํผ ๊ฑฐ๋ฆฌ 2.5m ๋ฏธ๋ง, ์ ์ง ์๋ 2.0m/s ์ด๊ณผ, ์์ธ 35๋ ์ด๊ณผ 1์ด ์ด์.\"\"\"\n",
" out = {}\n",
" for ev, (f, op, th) in RULES.items():\n",
" out[ev] = df[f] < th if op == \"<\" else (df[f] > th if op == \">\" else df[f] >= th)\n",
" return pd.DataFrame(out)\n",
"\n",
"\n",
"def features(log):\n",
" \"\"\"๊ฒฝ๋ก ์ดํ๋ยทํธ๋ฒ๋ง ์์ ์ฑ(๊ธฐ๋ณธ) + ์ฅ์ ๋ฌผ ๋ฐ์ยท์ฐฉ๋ฅ ์๋ยท์์ธ ํ๊ณยท๊ธ์กฐ์ยท๊ณผ๋ณด์ (๊ณ ๋ํ) ํน์ง.\"\"\"\n",
" X, Y, A = (uniform_filter1d(log[k], 9, axis=0) for k in (\"x\", \"y\", \"alt\")) # GPS ์ก์ ์ํ(์ด๋ํ๊ท 0.9์ด)\n",
" h = np.hypot(X - log[\"tx\"], Y - log[\"ty\"])\n",
" cr, hv = slice(0, 600), slice(620, 850)\n",
" k0, k1 = int(OBST_T / DT), int((OBST_T + 2.5) / DT)\n",
" side = np.sign(log[\"ty\"][k0 + 1] - log[\"ty\"][k0 - 1])\n",
" moved = (log[\"stick_roll\"][k0:k0 + 40] * side) > 0.3\n",
" rt = np.where(moved.any(0), moved.argmax(0) * DT, 4.0)\n",
" vz = -np.gradient(A, DT, axis=0)\n",
" land = np.argmax((A < 0.4) & (T[:, None] >= LAND_T), axis=0)\n",
" touch = np.array([vz[max(i - 6, 0):i - 2, j].mean() if i > 6 else 3.0 for j, i in enumerate(land)])\n",
" ds = np.abs(np.diff(np.stack([log[\"stick_roll\"], log[\"stick_pitch\"]]), axis=1)) / DT\n",
" return pd.DataFrame({\n",
" \"path_rmse\": np.sqrt((h[cr] ** 2).mean(0)), \"hover_drift\": h[hv].mean(0), \"hover_std\": np.hypot(X[hv].std(0), Y[hv].std(0)),\n",
" \"alt_err\": np.abs(A[:850] - log[\"talt\"][:850]).mean(0),\n",
" \"obs_rt\": rt, \"obs_lateral\": np.abs(Y[k1] - Y[k0]), \"touch_vz\": touch,\n",
" \"att_exceed\": (np.maximum(np.abs(log[\"roll_deg\"]), np.abs(log[\"pitch_deg\"])) > 35).sum(0),\n",
" \"jerk\": np.percentile(ds, 95, axis=(0, 1)),\n",
" \"overshoot\": np.clip(np.abs(Y[k0:k0 + 120] - Y[k0]).max(0) - 6.0, 0, None)}).pipe(\n",
" lambda d: d.assign(n_risk=detect(d).sum(axis=1))) # overshoot: ํํผ ํ ๋ชฉํ 6m๋ฅผ ๋์ด์ ๊ฑฐ๋ฆฌ(๊ณผ๋ณด์ )\n",
"\n",
"\n",
"def make_dataset(n_pilots=40, seed=7, conds=(\"day\", \"night\", \"gust\"), flights=1):\n",
" \"\"\"์กฐ์ข
์ n๋ช
x ์กฐ๊ฑด x ๋ฐ๋ณต ๋นํ -> ๋นํ๋ณ ํน์ง + ์ ๋ต(๊ต๊ด ์ ์ยท๋ฑ๊ธยท์ํ ์ฌ๊ฑด) ํ.\"\"\"\n",
" pilots = make_pilots(n_pilots, seed)\n",
" out = []\n",
" for c in conds:\n",
" for f in range(flights):\n",
" log, tr = fly(pilots, c, seed=seed * 100 + list(COND).index(c) * 10 + f)\n",
" out.append(pd.concat([tr.reset_index(drop=True), features(log)], axis=1).assign(flight=f))\n",
" return pd.concat(out, ignore_index=True)\n",
"\n",
"\n",
"def cond_z(df, ref, cols):\n",
" \"\"\"์กฐ๊ฑด๋ณ ๊ธฐ์ค ์ง๋จ(ref)์ ํ๊ท ยทํ์คํธ์ฐจ๋ก ํ์คํ: ๋ํ์ฒ๋ผ ์ด๋ ค์ด ์กฐ๊ฑด์์ ๋ฐ๋ ๋ถ์ด์ต์ ์ค์ธ๋ค.\"\"\"\n",
" z = df.copy()\n",
" z[cols] = z[cols].astype(float)\n",
" for c in df[\"cond\"].unique():\n",
" r = ref[ref[\"cond\"] == c][cols]\n",
" z.loc[df[\"cond\"] == c, cols] = (df.loc[df[\"cond\"] == c, cols] - r.mean()) / (r.std() + 1e-9)\n",
" return z\n",
"\n",
"\n",
"def to_grade(score):\n",
" return np.where(score >= 75, \"A\", np.where(score >= 55, \"B\", \"C\"))\n",
"\n",
"CONFIG = {\"features\": ADV_F, \"condition_normalization\": True, \"ridge_alpha\": 10.0} # ๋ฏธ์
9 ์ฌํ๋๊ฐ\n",
"KEYS = [\"x\", \"y\", \"alt\", \"stick_pitch\", \"stick_roll\", \"throttle\", \"roll_deg\", \"pitch_deg\", \"tx\", \"ty\", \"talt\"]\n",
"\n",
"\n",
"def validate(L):\n",
" if any(k not in L for k in KEYS) or len(L[\"x\"]) != STEPS:\n",
" return \"ํ๋ยท๊ธธ์ด ์ค๋ฅ\"\n",
" if np.isnan(L[\"x\"]).mean() > 0.2:\n",
" return \"๊ฒฐ์ธก 20% ์ด๊ณผ\"\n",
" return None\n",
"\n",
"\n",
"def clean(L):\n",
" out = {}\n",
" for k in KEYS:\n",
" s = pd.Series(L[k])\n",
" if k in (\"x\", \"y\", \"alt\"):\n",
" med = s.rolling(7, center=True, min_periods=1).median()\n",
" s = s.mask((s - med).abs() > 3 * ((s - med).abs().rolling(7, center=True, min_periods=1).median() * 1.4826 + 0.3))\n",
" out[k] = s.interpolate(limit_direction=\"both\").values\n",
" return out\n",
"\n",
"\n",
"class Review:\n",
" def __init__(self):\n",
" self.items, self.log = [], []\n",
"\n",
" def submit(self, rec):\n",
" prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n",
" body = {\"event\": \"AI_SUGGEST\", \"pilot\": rec[\"pilot\"], \"grade\": rec[\"grade\"], \"conf\": rec[\"conf\"]}\n",
" self.log.append({**body, \"prev\": prev, \"hash\": hashlib.sha256((prev + json.dumps(body, sort_keys=True)).encode()).hexdigest()})\n",
" self.items.append({**body, \"status\": \"PENDING\"})\n",
"\n",
"\n",
"called = set()\n",
"sys.setprofile(lambda f, e, a: called.add(f.f_code.co_name) if e == \"call\" and f.f_globals is globals() else None)\n",
"t_start = time.perf_counter()\n",
"\n",
"# ---------------- 1. ์์ง ์ค๋น(AAR ์๋ฃ๋ก ์ฌํ๋๋ ์ค์ ) ----------------\n",
"ref = make_dataset(40, seed=8)\n",
"eng = Ridge(alpha=CONFIG[\"ridge_alpha\"]).fit(cond_z(ref, ref, ADV_F)[ADV_F], ref[\"instructor\"])\n",
"\n",
"# ---------------- 2. ์ ๊ท ๊ธฐ์ ๋ก๊ทธ ์์ง(์กฐ์ข
์ 30๋ช
x 3์กฐ๊ฑด) + ์์ยท์ก์ ----------------\n",
"pilots = make_pilots(30, seed=31)\n",
"rng = np.random.default_rng(4)\n",
"logs, conds, truth = [], [], []\n",
"for c in COND:\n",
" log, tr = fly(pilots, c, seed=700 + len(logs))\n",
" for j in range(len(pilots)):\n",
" L = {k: log[k][:, j].copy() for k in KEYS}\n",
" sp = rng.random(STEPS) < 0.01\n",
" L[\"x\"][sp] += 10.0 # GPS ํ\n",
" L[\"y\"][300:310] = np.nan # ์ผ์ ๋จ์ 1์ด\n",
" logs.append(L); conds.append(c)\n",
" truth.append(tr)\n",
"truth = pd.concat(truth, ignore_index=True)\n",
"logs[7] = {k: v for k, v in logs[7].items() if k != \"throttle\"} # ์์ ๋ก๊ทธ 2๊ฑด\n",
"logs[40][\"x\"][:500] = np.nan\n",
"\n",
"# ---------------- 3. ๊ฒ์ฆยท์ ์ฒ๋ฆฌยทํน์งยท์ฑ์ ----------------\n",
"rejected = {i: r for i, L in enumerate(logs) if (r := validate(L))}\n",
"ok = [i for i in range(len(logs)) if i not in rejected]\n",
"t0 = time.perf_counter()\n",
"cleaned = [clean(logs[i]) for i in ok]\n",
"stack = {k: np.stack([c[k] for c in cleaned], axis=1) for k in KEYS}\n",
"stack[\"t\"] = T\n",
"F = features(stack).assign(cond=[conds[i] for i in ok])\n",
"D = pd.concat([truth.iloc[ok].reset_index(drop=True), F.drop(columns=\"cond\")], axis=1)\n",
"D[\"score\"] = np.clip(eng.predict(cond_z(F, ref, ADV_F)[ADV_F]), 0, 100)\n",
"ev = detect(F)\n",
"ms = (time.perf_counter() - t0) * 1000 / len(ok)\n",
"\n",
"# ---------------- 4. ์กฐ์ข
์ ์ง๊ณยท์ ๋ขฐ๋ยท๊ต๊ด ๊ฒํ ๋๊ธฐ์ด ----------------\n",
"rows = []\n",
"for pid, g in D.groupby(\"pilot\"):\n",
" boot = [to_grade(rng.choice(g[\"score\"].values, len(g)).mean()).item() for _ in range(100)]\n",
" gr = to_grade(g[\"score\"].mean()).item(); cf = boot.count(gr) / 100\n",
" rows.append({\"pilot\": pid, \"score\": g[\"score\"].mean(), \"grade\": gr, \"conf\": \"High\" if cf >= 0.9 else (\"Medium\" if cf >= 0.7 else \"Low\"),\n",
" \"inst_grade\": to_grade(g[\"instructor\"].mean()).item(), \"risk\": int(ev.loc[g.index].values.sum())})\n",
"P = pd.DataFrame(rows)\n",
"rv = Review()\n",
"for r in P.to_dict(\"records\"):\n",
" rv.submit(r)\n",
"sys.setprofile(None)\n",
"\n",
"# ---------------- 5. ์์ฒด ์ ๊ฒ ----------------\n",
"mine = [n for n, o in list(globals().items()) if inspect.isfunction(o) and o.__module__ == \"__main__\"] + [\"submit\", \"__init__\"]\n",
"cov = len(set(mine) & called) / len(set(mine))\n",
"fl_acc = (to_grade(D[\"score\"]) == D[\"grade\"]).mean()\n",
"pl_acc = (P[\"grade\"] == P[\"inst_grade\"]).mean()\n",
"truth_ev = D[[\"near_collision\", \"hard_landing\", \"attitude_exceed\"]].astype(bool).values\n",
"rec = (ev.values & truth_ev).sum() / max(truth_ev.sum(), 1)\n",
"chk = {\n",
" \"์์ ๋ก๊ทธ 2๊ฑด ๊ฒฉ๋ฆฌ\": len(rejected) == 2,\n",
" \"๋ก๊ทธ ์ ์ค 0๊ฑด(์ฑ์ +๊ฒฉ๋ฆฌ=์ ์ฒด)\": len(ok) + len(rejected) == len(logs),\n",
" \"์ ์ฒ๋ฆฌ ํ ๊ฒฐ์ธก 0\": not any(np.isnan(v).any() for v in stack.values()),\n",
" \"๋นํ๋ณ ๋ฑ๊ธ ์ผ์น์จ 75% ์ด์\": fl_acc >= 0.75,\n",
" \"์กฐ์ข
์ ๋ฑ๊ธ ์ผ์น์จ 85% ์ด์\": pl_acc >= 0.85,\n",
" \"์ํ ์ฌ๊ฑด ํ์ง ์ฌํ์จ 85% ์ด์\": rec >= 0.85,\n",
" \"๋ก๊ทธ๋น ์ฒ๋ฆฌ 200ms ์ด๋ด\": ms < 200,\n",
" \"AI ํ์ 0๊ฑด(์ ๋ถ ๊ต๊ด ๋๊ธฐ)\": all(i[\"status\"] == \"PENDING\" for i in rv.items),\n",
" \"๊ฐ์ฌ ๋ก๊ทธ ๊ฑด์ ์ผ์น\": len(rv.log) == len(rv.items),\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%}, ํ์ดํ๋ผ์ธ ์คํ ์ฑ๊ณต {'์' if passed == len(chk) else '์๋์ค'}\")\n",
"print(\"์กฐ์ข
์ ๋ฑ๊ธ ๋ถํฌ:\", P[\"grade\"].value_counts().reindex([\"A\", \"B\", \"C\"]).fillna(0).astype(int).to_dict(),\n",
" \"/ ์ ๋ขฐ๋:\", P[\"conf\"].value_counts().to_dict())\n",
"\n",
"# ---------------- 6. ๋์๋ณด๋ ----------------\n",
"fig, ax = plt.subplots(2, 2, figsize=(12, 7))\n",
"ax[0, 0].scatter(D[\"instructor\"], D[\"score\"], s=10, c=D[\"cond\"].map({\"day\": \"tab:blue\", \"night\": \"tab:purple\", \"gust\": \"tab:orange\"}))\n",
"ax[0, 0].plot([20, 100], [20, 100], \"k--\"); ax[0, 0].set_xlabel(\"instructor\"); ax[0, 0].set_ylabel(\"AI\"); ax[0, 0].set_title(\"Flight score: AI vs instructor\")\n",
"ax[0, 1].bar([\"near_collision\", \"hard_landing\", \"attitude\"], ev.sum().values, color=\"tab:red\"); ax[0, 1].set_title(\"Detected risk events\")\n",
"Ps = P.sort_values(\"score\")\n",
"ax[1, 0].bar(Ps[\"pilot\"], Ps[\"score\"], color=Ps[\"conf\"].map({\"High\": \"tab:green\", \"Medium\": \"tab:orange\", \"Low\": \"tab:red\"}))\n",
"ax[1, 0].axhline(55, c=\"k\", ls=\"--\"); ax[1, 0].axhline(75, c=\"k\", ls=\"--\"); ax[1, 0].tick_params(axis=\"x\", rotation=90, labelsize=6)\n",
"ax[1, 0].set_title(\"Pilot mean score (color = confidence)\")\n",
"ax[1, 1].axis(\"off\")\n",
"ax[1, 1].text(0, 0.9, f\"Logs {len(logs)} ({len(rejected)} rejected)\\nFlight acc {fl_acc:.1%}\\nPilot acc {pl_acc:.1%}\\n\"\n",
" f\"Risk recall {rec:.1%}\\nPending {len(rv.items)} reviews\\nSelf-check {passed}/{len(chk)}\", family=\"monospace\", fontsize=13, va=\"top\")\n",
"plt.suptitle(\"TRN01 integrated skill analyzer - training data only\"); plt.tight_layout(); plt.show()\n",
"\n",
"# ---------------- 7. ๊ต์กํ๋ จ ๋ธ๋ฆฌํ ์์ฝ ----------------\n",
"c_list = P[P[\"grade\"] == \"C\"][\"pilot\"].tolist()\n",
"print(f\"\"\"\n",
"[๊ต์กํ๋ จ ๋ธ๋ฆฌํ ์์ฝ - ๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"1. ์ฒ๋ฆฌ: ๋นํ๋ก๊ทธ {len(logs)}๊ฑด ์ค {len(ok)}๊ฑด ๋ถ์, ์์ {len(rejected)}๊ฑด ๊ฒฉ๋ฆฌ, ๋ก๊ทธ๋น {ms:.1f} ms, ์ ์ฒด {time.perf_counter() - t_start:.1f}s\n",
"2. ์ฑ๋ฅ: ๋นํ๋ณ ๋ฑ๊ธ ์ผ์น์จ {fl_acc:.1%}, ์กฐ์ข
์ ๋ฑ๊ธ ์ผ์น์จ {pl_acc:.1%}, ์ํ ์ฌ๊ฑด ํ์ง ์ฌํ์จ {rec:.1%}\n",
"3. ๊ฒฐ๊ณผ: A {int((P['grade'] == 'A').sum())}๋ช
/ B {int((P['grade'] == 'B').sum())}๋ช
/ C {len(c_list)}๋ช
(์ฌ๊ต์ก ๊ฒํ ๋์ {', '.join(c_list[:5])}{' ์ธ' if len(c_list) > 5 else ''}), ์ ์ ๊ต๊ด ๊ฒํ ๋๊ธฐ\n",
"4. ์ด์ฉ ์ ์: AI ๋ฑ๊ธ์ ๊ต๊ด ํ๋จ ๋ณด์กฐ ์๋ฃ์ด๋ฉฐ, ์ฌ๊ต์กยท์๊ฒฉ ๋ณด๋ฅ๋ ๊ต๊ด ๊ทผ๊ฑฐ ๊ธฐ๋ก๊ณผ ๋ ๋ฒ์งธ ๊ต๊ด ํ์ธ ํ ํ์ ํ๋ค.\n",
"5. ํ๊ณยทํ์ฅ: ๊ฐ์ ์๋ฎฌ๋ ์ดํฐ ๋ก๊ทธ ๊ธฐ์ค ๊ฒฐ๊ณผ๋ค. ์ค์ ์๋ฎฌ๋ ์ดํฐ ๋ก๊ทธ ํ์์ ๋ง์ถ ์ฌ๊ฒ์ฆ์ด ํ์ํ๋ฉฐ, ํ๋ จ ๊ด๋ฆฌ ์ฒด๊ณ์๋ ๊ฒฐ๊ณผ ์กฐํ API๋ก ์ฐ๋ํ๋ค.\"\"\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
10 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[PASS] ์์ ๋ก๊ทธ 2๊ฑด ๊ฒฉ๋ฆฌ\n",
"[PASS] ๋ก๊ทธ ์ ์ค 0๊ฑด(์ฑ์ +๊ฒฉ๋ฆฌ=์ ์ฒด)\n",
"[PASS] ์ ์ฒ๋ฆฌ ํ ๊ฒฐ์ธก 0\n",
"[PASS] ๋นํ๋ณ ๋ฑ๊ธ ์ผ์น์จ 75% ์ด์\n",
"[PASS] ์กฐ์ข
์ ๋ฑ๊ธ ์ผ์น์จ 85% ์ด์\n",
"[PASS] ์ํ ์ฌ๊ฑด ํ์ง ์ฌํ์จ 85% ์ด์\n",
"[PASS] ๋ก๊ทธ๋น ์ฒ๋ฆฌ 200ms ์ด๋ด\n",
"[PASS] AI ํ์ 0๊ฑด(์ ๋ถ ๊ต๊ด ๋๊ธฐ)\n",
"[PASS] ๊ฐ์ฌ ๋ก๊ทธ ๊ฑด์ ์ผ์น\n",
"[PASS] ํจ์ ์ปค๋ฒ๋ฆฌ์ง 85% ์ด์\n",
"์์ฒด ์ ๊ฒ 10/10 ํต๊ณผ, ํจ์ ์ปค๋ฒ๋ฆฌ์ง 92%, ํ์ดํ๋ผ์ธ ์คํ ์ฑ๊ณต ์\n",
"์กฐ์ข
์ ๋ฑ๊ธ ๋ถํฌ: {'A': 4, 'B': 15, 'C': 11} / ์ ๋ขฐ๋: {'High': 21, 'Medium': 6, 'Low': 3}\n",
"\n",
"[๊ต์กํ๋ จ ๋ธ๋ฆฌํ ์์ฝ - ๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"1. ์ฒ๋ฆฌ: ๋นํ๋ก๊ทธ 90๊ฑด ์ค 88๊ฑด ๋ถ์, ์์ 2๊ฑด ๊ฒฉ๋ฆฌ, ๋ก๊ทธ๋น 13.6 ms, ์ ์ฒด 5.9s\n",
"2. ์ฑ๋ฅ: ๋นํ๋ณ ๋ฑ๊ธ ์ผ์น์จ 79.5%, ์กฐ์ข
์ ๋ฑ๊ธ ์ผ์น์จ 93.3%, ์ํ ์ฌ๊ฑด ํ์ง ์ฌํ์จ 97.4%\n",
"3. ๊ฒฐ๊ณผ: A 4๋ช
/ B 15๋ช
/ C 11๋ช
(์ฌ๊ต์ก ๊ฒํ ๋์ P001, P007, P012, P015, P016 ์ธ), ์ ์ ๊ต๊ด ๊ฒํ ๋๊ธฐ\n",
"4. ์ด์ฉ ์ ์: AI ๋ฑ๊ธ์ ๊ต๊ด ํ๋จ ๋ณด์กฐ ์๋ฃ์ด๋ฉฐ, ์ฌ๊ต์กยท์๊ฒฉ ๋ณด๋ฅ๋ ๊ต๊ด ๊ทผ๊ฑฐ ๊ธฐ๋ก๊ณผ ๋ ๋ฒ์งธ ๊ต๊ด ํ์ธ ํ ํ์ ํ๋ค.\n",
"5. ํ๊ณยทํ์ฅ: ๊ฐ์ ์๋ฎฌ๋ ์ดํฐ ๋ก๊ทธ ๊ธฐ์ค ๊ฒฐ๊ณผ๋ค. ์ค์ ์๋ฎฌ๋ ์ดํฐ ๋ก๊ทธ ํ์์ ๋ง์ถ ์ฌ๊ฒ์ฆ์ด ํ์ํ๋ฉฐ, ํ๋ จ ๊ด๋ฆฌ ์ฒด๊ณ์๋ ๊ฒฐ๊ณผ ์กฐํ API๋ก ์ฐ๋ํ๋ค.\n",
"```\n",
" \n"
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}