{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ๐ŸŽฏ [WEL01] ๋”๋ฏธ ์ƒ์ฒด์‹ ํ˜ธ ๊ธฐ๋ฐ˜ ์—ดยทํ•œ๋žญ ์ŠคํŠธ๋ ˆ์Šค ๊ฒฝ๋ณด ํ”„๋กœํ† ํƒ€์ž…\n", "### Google Colab ์‹ค์Šต ํ™˜๊ฒฝ (์žฅ๋ณ‘ ๋ณต์ง€ยท์ง€์› / Level 2)\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/WEL01/WEL01_solution.ipynb)\n", "\n", "---\n", "\n", "## ๐Ÿ“Œ 1. ํ”„๋กœ์ ํŠธ ๊ฐœ์š” ๋ฐ ๋ฐฐ๊ฒฝ\n", "\n", "| ํ•ญ๋ชฉ | ๋‚ด์šฉ |\n", "| :--- | :--- |\n", "| **๊ณผ์ • ๋ถ„๋ฅ˜** | ์žฅ๋ณ‘ ๋ณต์ง€ยท์ง€์› (Level 2) |\n", "| **ํ•™์Šต ์†Œ์š” ์‹œ๊ฐ„** | 10๊ฐœ ๋ฏธ์…˜ (5์ผ / ์ด 27.5์‹œ๊ฐ„) |\n", "| **ํ™˜๊ฒฝ ์‚ฌ์–‘** | Python 3.10+, numpy, pandas, matplotlib, scipy, scikit-learn |\n", "\n", "### ๐Ÿ“– ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ\n", "๋Ÿฌ์‹œ์•„-์šฐํฌ๋ผ์ด๋‚˜ ์ „์Ÿ์—์„œ๋Š” ํ˜นํ•œ๊ณผ ํญ์—ผ ์†์—์„œ ์žฅ์‹œ๊ฐ„ ๊ธฐ๋™ํ•˜๋Š” ์ „ํˆฌ์›์˜ ์—ดยทํ•œ๋žญ ์†์ƒ์ด ์ž‘์ „ ์ง€์†๋ ฅ์„ ์ขŒ์šฐํ•˜๋Š” ๋ฌธ์ œ๋กœ ๋“œ๋Ÿฌ๋‚ฌ๊ณ , ์›จ์–ด๋Ÿฌ๋ธ” ์„ผ์„œ ์‹ ํ˜ธ๋กœ ์œ„ํ—˜ ์ง•ํ›„๋ฅผ ์ผ์ฐ ์•Œ์•„์ฐจ๋ฆฌ๋˜ ์˜ค๊ฒฝ๋ณด์™€ ์„ผ์„œ ์˜ค๋ฅ˜๋ฅผ ๊ฐ€๋ ค๋‚ด๋Š” ์ผ์ด ์ค‘์š”ํ•ด์กŒ๋‹ค. ๋ณธ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ๋ณ‘์‚ฌ์˜ ๋”๋ฏธ ์ƒ์ฒด์‹ ํ˜ธ(์‹ฌ๋ฐ•์ˆ˜, ์ฒด์˜จ, ํ”ผ๋ถ€์ „๋„๋„)์™€ ๊ธฐ์ƒ์„ ์ฝ”๋“œ๋กœ ์ง์ ‘ ๋งŒ๋“ค์–ด, ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ ์ •์˜, ๊ฒฐ์ธกยท์„ผ์„œ ํА ์ „์ฒ˜๋ฆฌ, ์ฒด์˜จ-์‹ฌ๋ฐ• ์ƒ๊ด€ ๊ธฐ๋ฐ˜ Baseline, ๊ธฐ์ƒยท๊ฐœ์ธ ํŠน์„ฑ์„ ๊ฒฐํ•ฉํ•œ ๋ณตํ•ฉ ์œ„ํ—˜๋„ ๋ชจ๋ธ, ๊ฐœ์ธํ™” ์‹ ๋ขฐ๋„ ์—”์ง„, ์†Œ๋Œ€์žฅยท์˜๋ฌด๊ด€์šฉ ์ƒํ™ฉํŒ, ๊ตฐ์˜๊ด€ ์›๊ฒฉ ๊ฒ€ํ†  ์ค‘์‹ฌ์˜ 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", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์‹ ํ˜ธ 3์ข…์˜ ํ—ˆ์šฉ ๋ฒ”์œ„, ์ƒ˜ํ”Œ๋ง ๊ฐ„๊ฒฉ, ๊ธฐ๊ธฐ ์ข…๋ฅ˜์™€ ์ ‘์ด‰ ํ’ˆ์งˆ, ๋ณ‘์‚ฌ ํŠน์„ฑ(์•ˆ์ •์‹œ ์‹ฌ๋ฐ•, ์—ฐ๋ น, ์ฒด๊ฒฉ, ๋”์œ„ ์ˆœ์‘ ์—ฌ๋ถ€), ๊ธฐ์ƒ(๊ธฐ์˜จ, ์Šต๋„, ํ’์†) ํ•„๋“œ๋ฅผ ๋‹ด์€ ์Šคํ‚ค๋งˆ ์„ค๊ณ„ํ•˜๊ณ  ์ž‘์„ฑํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `data/schema/wel01_input_schema.json ๋ฐ docs/wel01_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": [ "\"\"\"์ „ํˆฌ์› ์›จ์–ด๋Ÿฌ๋ธ” ๋”๋ฏธ ์ƒ์ฒด์‹ ํ˜ธ(์‹ฌ๋ฐ•์ˆ˜, ์ฒด์˜จ, ํ”ผ๋ถ€์ „๋„๋„)์™€ ๊ฐ€์ƒ ๋ณ‘์‚ฌ ํŠน์„ฑยท๊ธฐ์ƒยท๊ธฐ๊ธฐ ์ƒํƒœ์˜ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•˜๊ณ , ๊ฐ€์ƒ ์ƒ˜ํ”Œ 20๊ฑด์„ ๊ฒ€์ฆํ•œ๋‹ค. ์ง„๋‹จ ๋ชฉ์ ์ด ์•„๋‹Œ ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋‹ค.\"\"\"\n", "import json\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋”๋ฏธ ์ƒ์ฒด์‹ ํ˜ธ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋ณ‘ยท๋ถ€๋Œ€ยท์˜๋ฌด ๊ธฐ๋ก ์—†์Œ) ----------------\n", "# ๊ฐ€์ƒ ๋ณ‘์‚ฌ(๊ฐ€๋ช… ID)๊ฐ€ 60๋ถ„๊ฐ„ ๊ฑท๊ธฐยทํœด์‹ยท๊ณ ๊ฐ•๋„ ํ™œ๋™์„ ํ•˜๋Š” ๋™์•ˆ์˜ ์‹ฌ๋ฐ•์ˆ˜ยท์ฒด์˜จ(์ถ”์ • ์‹ฌ๋ถ€)ยทํ”ผ๋ถ€์ „๋„๋„๋ฅผ 1๋ถ„ ๊ฐ„๊ฒฉ์œผ๋กœ ๋งŒ๋“ ๋‹ค.\n", "# ์ง„๋‹จ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ์ƒํƒœ ๋ผ๋ฒจ์€ ๊ฐ€์ƒ ์ƒ๋ฆฌ ๋ชจ๋ธ์˜ '๊ต์œก์šฉ ๋‹จ๊ณ„'์ผ ๋ฟ์ด๋‹ค.\n", "N_MIN = 60\n", "STAGE = {0: \"์ •์ƒ\", 1: \"์ฃผ์˜\", 2: \"๊ฒฝ๋ณด\"}\n", "TYPES = [\"normal\", \"heat\", \"cold\"]\n", "\n", "\n", "def make_person(i, rng):\n", " return {\"id\": f\"S{i:03d}\", \"rest_hr\": float(rng.normal(64, 7)), \"age\": int(rng.integers(19, 27)), \"bmi\": float(rng.normal(23.5, 2.5)),\n", " \"acclim\": int(rng.random() < 0.4), \"fit\": float(np.clip(rng.normal(0.5, 0.2), 0.05, 0.95))}\n", "\n", "\n", "def make_env(kind, rng):\n", " if kind == \"heat\":\n", " return {\"temp_c\": float(rng.uniform(30, 40)), \"rh\": float(rng.uniform(45, 85)), \"wind\": float(rng.uniform(0.3, 3))}\n", " if kind == \"cold\":\n", " return {\"temp_c\": float(rng.uniform(-22, 2)), \"rh\": float(rng.uniform(40, 80)), \"wind\": float(rng.uniform(2, 11))}\n", " return {\"temp_c\": float(rng.uniform(8, 24)), \"rh\": float(rng.uniform(35, 70)), \"wind\": float(rng.uniform(0.3, 5))}\n", "\n", "\n", "def make_session(sid, kind, rng, p=None, glitch=True):\n", " \"\"\"๋ฐ˜ํ™˜: dict(person, env, kind, df[hr,temp,eda,act + ์ •๋‹ต coreยทstageยทrtype]). ์„ผ์„œ ๊ฒฐ์ธกยทํАยท๊ณ ์ฐฉ ํฌํ•จ.\"\"\"\n", " p = p or make_person(sid, rng)\n", " env = make_env(kind, rng)\n", " act = np.zeros(N_MIN); t = 0\n", " while t < N_MIN: # 10~20๋ถ„ ๋‹จ์œ„ ํ™œ๋™ ๋ธ”๋ก: ํœด์‹ 1.5 / ํ–‰๊ตฐ 4.5 / ๊ณ ๊ฐ•๋„ 7 MET\n", " d = int(rng.integers(10, 21)); act[t:t + d] = rng.choice([1.5, 4.5, 7.0], p=[0.25, 0.5, 0.25]); t += d\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"]\n", " core, cores = 36.9, []\n", " for m in range(N_MIN):\n", " heat = 0.085 * max(0, hi - 24) * (1 - 0.25 * p[\"acclim\"]) * (1 + 0.5 * m / N_MIN) * (1 + 0.012 * (p[\"bmi\"] - 23.5))\n", " cold = -0.075 * max(0, 8 - wc) * (1 - 0.09 * act[m])\n", " ss = 36.9 + 0.09 * act[m] + heat + cold\n", " core += (ss - core) / 18 + rng.normal(0, 0.03)\n", " cores.append(core)\n", " core = np.array(cores)\n", " hr = p[\"rest_hr\"] + 9.5 * (act - 1.5) * (1.15 - 0.3 * p[\"fit\"]) + 11 * (core - 36.9) + rng.normal(0, 3.2, N_MIN)\n", " eda = 2.0 + 0.25 * act + 3.2 * np.maximum(core - 37.2, 0) - 0.04 * np.maximum(8 - wc, 0) + rng.normal(0, 0.25, N_MIN)\n", " bias = rng.normal(0, 0.1)\n", " temp = core + bias + rng.normal(0, 0.1, N_MIN) - np.where((kind == \"cold\") & (env[\"wind\"] > 7), 0.25, 0.0) # ์„ผ์„œ ์ ‘์ด‰ ๋ถ€์œ„ ๋ฐ”๋žŒ ๋ƒ‰๊ฐ ์˜ค์ฐจ\n", " df = pd.DataFrame({\"min\": np.arange(N_MIN), \"hr\": hr, \"temp\": temp, \"eda\": np.maximum(eda, 0.2), \"act\": act})\n", " if glitch:\n", " for col in (\"hr\", \"temp\", \"eda\"):\n", " df.loc[rng.random(N_MIN) < 0.03, col] = np.nan # ๊ฒฐ์ธก\n", " s = int(rng.integers(5, N_MIN))\n", " if rng.random() < 0.12:\n", " df.loc[s, \"hr\"] = 230.0 # ์„ผ์„œ ํА\n", " if rng.random() < 0.12:\n", " df.loc[s, \"temp\"] = 45.0\n", " ht = np.where(core >= 39.0, 2, np.where(core >= 38.3, 1, 0)); cd = np.where(core <= 35.4, 2, np.where(core <= 36.2, 1, 0))\n", " df[\"core\"] = core\n", " df[\"stage\"] = np.maximum(ht, cd); df[\"rtype\"] = np.where(ht > 0, \"heat\", np.where(cd > 0, \"cold\", \"normal\"))\n", " return {\"id\": p[\"id\"], \"person\": p, \"env\": env, \"kind\": kind, \"df\": df}\n", "\n", "\n", "def make_dataset(n, seed):\n", " rng = np.random.default_rng(seed)\n", " kinds = [\"normal\", \"heat\", \"cold\"]\n", " return [make_session(i, kinds[i % 3], rng) for i in range(n)]\n", "# ---------------------------------------------------------------------------\n", "\n", "# 1) ์Šคํ‚ค๋งˆ ์ •์˜: ์‹ ํ˜ธ ๊ทœ๊ฒฉ, ๊ฐ€์ƒ ๋ณ‘์‚ฌ ํŠน์„ฑ, ๊ธฐ์ƒ(์˜จยท์Šต๋„ยทํ’์†), ๊ธฐ๊ธฐ ์ƒํƒœ\n", "SCHEMA = {\n", " \"required\": [\"record_id\", \"soldier_id\", \"n_samples\", \"sampling_s\", \"device\", \"person\", \"weather\", \"samples\"],\n", " \"fields\": {\n", " \"record_id\": {\"type\": str, \"pattern\": r\"^REC-\\d{4}$\"},\n", " \"soldier_id\": {\"type\": str, \"pattern\": r\"^S\\d{3}$\"},\n", " \"n_samples\": {\"type\": int, \"min\": 1, \"max\": 100000},\n", " \"sampling_s\": {\"type\": (int, float), \"min\": 1, \"max\": 600},\n", " },\n", " \"device\": {\n", " \"type\": {\"type\": str, \"enum\": [\"wrist\", \"chest\"]},\n", " \"battery_pct\": {\"type\": (int, float), \"min\": 0, \"max\": 100},\n", " \"contact_quality\": {\"type\": (int, float), \"min\": 0, \"max\": 1},\n", " },\n", " \"person\": {\n", " \"rest_hr\": {\"type\": (int, float), \"min\": 40, \"max\": 100},\n", " \"age\": {\"type\": int, \"min\": 18, \"max\": 45},\n", " \"bmi\": {\"type\": (int, float), \"min\": 15, \"max\": 40},\n", " \"acclim\": {\"type\": int, \"min\": 0, \"max\": 1},\n", " },\n", " \"weather\": {\n", " \"temp_c\": {\"type\": (int, float), \"min\": -40, \"max\": 55},\n", " \"rh\": {\"type\": (int, float), \"min\": 0, \"max\": 100},\n", " \"wind\": {\"type\": (int, float), \"min\": 0, \"max\": 30},\n", " },\n", " \"sample\": {\n", " \"hr\": {\"type\": (int, float), \"min\": 30, \"max\": 220},\n", " \"temp\": {\"type\": (int, float), \"min\": 30, \"max\": 43},\n", " \"eda\": {\"type\": (int, float), \"min\": 0, \"max\": 40},\n", " },\n", "}\n", "\n", "\n", "def check(name, value, rule):\n", " \"\"\"๊ฐ’ ํ•˜๋‚˜๋ฅผ ๊ทœ์น™(type, enum, min, max, pattern)์œผ๋กœ ๊ฒ€์‚ฌํ•ด ์˜ค๋ฅ˜ ๋ชฉ๋ก์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\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(rec):\n", " errs = [f\"{k}: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\" for k in SCHEMA[\"required\"] if k not in rec]\n", " for k, rule in SCHEMA[\"fields\"].items():\n", " if k in rec:\n", " errs += check(k, rec[k], rule)\n", " for group in (\"device\", \"person\", \"weather\"):\n", " for k, rule in SCHEMA[group].items():\n", " if k not in rec.get(group, {}):\n", " errs.append(f\"{group}.{k}: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\")\n", " else:\n", " errs += check(f\"{group}.{k}\", rec[group][k], rule)\n", " for i, smp in enumerate(rec.get(\"samples\", [])):\n", " for k, rule in SCHEMA[\"sample\"].items():\n", " if k not in smp:\n", " errs.append(f\"samples[{i}].{k}: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\")\n", " else:\n", " errs += check(f\"samples[{i}].{k}\", smp[k], rule)\n", " if \"n_samples\" in rec and rec[\"n_samples\"] != len(rec.get(\"samples\", [])):\n", " errs.append(\"n_samples: ์ƒ˜ํ”Œ ๊ฐœ์ˆ˜์™€ ๋ถˆ์ผ์น˜\")\n", " return errs\n", "\n", "\n", "# 2) [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋ณ‘์‚ฌ 20๋ช…์˜ ๋”๋ฏธ ์„ธ์…˜์—์„œ ์ƒ˜ํ”Œ ๋ ˆ์ฝ”๋“œ ์ƒ์„ฑ(์„ผ์„œ ์˜ค๋ฅ˜ ์—†๋Š” ์ •์ƒ ๋ฐ์ดํ„ฐ)\n", "rng = np.random.default_rng(1)\n", "records = []\n", "for i in range(20):\n", " s = make_session(i, TYPES[i % 3], rng, glitch=False)\n", " smp = [{\"hr\": round(float(r.hr), 1), \"temp\": round(float(r.temp), 2), \"eda\": round(float(r.eda), 2)} for r in s[\"df\"].itertuples()]\n", " p, e = s[\"person\"], s[\"env\"]\n", " records.append({\"record_id\": f\"REC-{i:04d}\", \"soldier_id\": s[\"id\"], \"n_samples\": len(smp), \"sampling_s\": 60,\n", " \"device\": {\"type\": str(rng.choice([\"wrist\", \"chest\"])), \"battery_pct\": int(rng.integers(30, 100)), \"contact_quality\": round(float(rng.uniform(0.7, 1)), 2)},\n", " \"person\": {\"rest_hr\": round(p[\"rest_hr\"], 1), \"age\": p[\"age\"], \"bmi\": round(p[\"bmi\"], 1), \"acclim\": p[\"acclim\"]},\n", " \"weather\": {k: round(v, 1) for k, v in e.items()}, \"samples\": smp})\n", "\n", "# 3) ์ •์ƒ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ\n", "ok = sum(1 for r in records if not validate(r))\n", "print(f\"์ •์ƒ ์ƒ˜ํ”Œ {len(records)}๊ฑด ์ค‘ ๊ฒ€์ฆ ํ†ต๊ณผ {ok}๊ฑด, ์˜ค๋ฅ˜ {len(records) - ok}๊ฑด -> ๋ชฉํ‘œ(๊ฒ€์ฆ ์˜ค๋ฅ˜ 0๊ฑด): {'๋‹ฌ์„ฑ' if ok == len(records) else '๋ฏธ๋‹ฌ'}\")\n", "\n", "# 4) ์˜ค๋ฅ˜ ์ฃผ์ž… ์‹œํ—˜\n", "bad = json.loads(json.dumps(records[0]))\n", "bad[\"soldier_id\"] = \"SOLDIER-1\" # ํ˜•์‹\n", "bad[\"samples\"][0][\"temp\"] = 45.0 # ๋ฒ”์œ„(์„ผ์„œ ํА)\n", "del bad[\"weather\"][\"wind\"] # ๋ˆ„๋ฝ(ํ’์†)\n", "bad[\"device\"][\"type\"] = \"ankle\" # ํ—ˆ์šฉ๊ฐ’\n", "bad[\"n_samples\"] = 99 # ๋…ผ๋ฆฌ(๊ฐœ์ˆ˜ ๋ถˆ์ผ์น˜)\n", "errs = validate(bad)\n", "print(f\"\\n[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ] ์ฃผ์ž… 5์ข… -> ๊ฒ€์ถœ {len(errs)}๊ฑด\")\n", "for e in errs:\n", " print(\" -\", e)\n", "print(f\"์ฃผ์ž… ์˜ค๋ฅ˜ ์ „๋ถ€ ๊ฒ€์ถœ: {'์˜ˆ' if len(errs) >= 5 else '์•„๋‹ˆ์˜ค'}\")\n", "\n", "# 5) ์Šคํ‚ค๋งˆ ํ•„๋“œ ์ •์˜์„œ(๋ช…์„ธ์„œ ์ดˆ์•ˆ)\n", "rows = [(\"๊ธฐ๋ณธ\", k, r[\"type\"].__name__ if isinstance(r[\"type\"], type) else \"number\", str({x: y for x, y in r.items() if x != \"type\"})) for k, r in SCHEMA[\"fields\"].items()]\n", "for g in (\"device\", \"person\", \"weather\", \"sample\"):\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\"})))\n", "print(\"\\n[๋ฐ์ดํ„ฐ ๋ช…์„ธ ์š”์•ฝ] ํ•„๋“œ ์ˆ˜\", len(rows))\n", "print(pd.DataFrame(rows, columns=[\"๊ทธ๋ฃน\", \"ํ•„๋“œ\", \"์ž๋ฃŒํ˜•\", \"๊ทœ์น™\"]).to_string(index=False))\n", "print(\"\\n์ƒํƒœ ๋‹จ๊ณ„(๊ต์œก์šฉ ๊ฐ€์ƒ ๋ผ๋ฒจ):\", STAGE, \"/ ์œ„ํ—˜ ์œ ํ˜•:\", TYPES)\n", "print(\"๋ณธ ๋ฐ์ดํ„ฐ๋Š” ์ฝ”๋“œ๋กœ ๋งŒ๋“  ๋”๋ฏธ ์ƒ์ฒด์‹ ํ˜ธ์ด๋ฉฐ ์ง„๋‹จยท์ฒ˜๋ฐฉ ์ž๋ฃŒ๊ฐ€ ์•„๋‹ˆ๋‹ค. ์‹ค์ œ ๊ฑด๊ฐ• ์ด์ƒ์€ ์˜๋ฌด๋Œ€ยท๊ตฐ์˜๊ด€์—๊ฒŒ ์—ฐ๊ณ„ํ•œ๋‹ค.\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 01 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "์ •์ƒ ์ƒ˜ํ”Œ 20๊ฑด ์ค‘ ๊ฒ€์ฆ ํ†ต๊ณผ 20๊ฑด, ์˜ค๋ฅ˜ 0๊ฑด -> ๋ชฉํ‘œ(๊ฒ€์ฆ ์˜ค๋ฅ˜ 0๊ฑด): ๋‹ฌ์„ฑ\n", "\n", "[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ] ์ฃผ์ž… 5์ข… -> ๊ฒ€์ถœ 5๊ฑด\n", " - soldier_id: ํ˜•์‹ ๋ถˆ์ผ์น˜ 'SOLDIER-1'\n", " - device.type: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๊ฐ’ 'ankle'\n", " - weather.wind: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\n", " - samples[0].temp: ์ตœ๋Œ€๊ฐ’ 43 ์ดˆ๊ณผ\n", " - n_samples: ์ƒ˜ํ”Œ ๊ฐœ์ˆ˜์™€ ๋ถˆ์ผ์น˜\n", "์ฃผ์ž… ์˜ค๋ฅ˜ ์ „๋ถ€ ๊ฒ€์ถœ: ์˜ˆ\n", "\n", "[๋ฐ์ดํ„ฐ ๋ช…์„ธ ์š”์•ฝ] ํ•„๋“œ ์ˆ˜ 17\n", " ๊ทธ๋ฃน ํ•„๋“œ ์ž๋ฃŒํ˜• ๊ทœ์น™\n", " ๊ธฐ๋ณธ record_id str {'pattern': '^REC-\\\\d{4}$'}\n", " ๊ธฐ๋ณธ soldier_id str {'pattern': '^S\\\\d{3}$'}\n", " ๊ธฐ๋ณธ n_samples int {'min': 1, 'max': 100000}\n", " ๊ธฐ๋ณธ sampling_s number {'min': 1, 'max': 600}\n", " device type str {'enum': ['wrist', 'chest']}\n", " device battery_pct number {'min': 0, 'max': 100}\n", " device contact_quality number {'min': 0, 'max': 1}\n", " person rest_hr number {'min': 40, 'max': 100}\n", " person age int {'min': 18, 'max': 45}\n", " person bmi number {'min': 15, 'max': 40}\n", " person acclim int {'min': 0, 'max': 1}\n", "weather temp_c number {'min': -40, 'max': 55}\n", "weather rh number {'min': 0, 'max': 100}\n", "weather wind number {'min': 0, 'max': 30}\n", " sample hr number {'min': 30, 'max': 220}\n", " sample temp number {'min': 30, 'max': 43}\n", " sample eda number {'min': 0, 'max': 40}\n", "\n", "์ƒํƒœ ๋‹จ๊ณ„(๊ต์œก์šฉ ๊ฐ€์ƒ ๋ผ๋ฒจ): {0: '์ •์ƒ', 1: '์ฃผ์˜', 2: '๊ฒฝ๋ณด'} / ์œ„ํ—˜ ์œ ํ˜•: ['normal', 'heat', 'cold']\n", "๋ณธ ๋ฐ์ดํ„ฐ๋Š” ์ฝ”๋“œ๋กœ ๋งŒ๋“  ๋”๋ฏธ ์ƒ์ฒด์‹ ํ˜ธ์ด๋ฉฐ ์ง„๋‹จยท์ฒ˜๋ฐฉ ์ž๋ฃŒ๊ฐ€ ์•„๋‹ˆ๋‹ค. ์‹ค์ œ ๊ฑด๊ฐ• ์ด์ƒ์€ ์˜๋ฌด๋Œ€ยท๊ตฐ์˜๊ด€์—๊ฒŒ ์—ฐ๊ณ„ํ•œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 02. ์•ผ์ „ ํ™œ๋™ ์ค‘ ๋ฐœ์ƒํ•˜๋Š” ์ƒ์ฒด์‹ ํ˜ธ ๊ฒฐ์ธก์น˜ ๋ณด์ • ๋ฐ ์„ผ์„œ ๋…ธ์ด์ฆˆ ํ•„ํ„ฐ๋ง ์ „์ฒ˜๋ฆฌ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์•ผ์ „ ํ™œ๋™ ์ค‘ ์ƒ์ฒด์‹ ํ˜ธ ๊ฒฐ์ธก์น˜ ๋ณด์ • ๋ฐ ์„ผ์„œ ๋…ธ์ด์ฆˆ ํ•„ํ„ฐ๋ง ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์•ผ์ „ ํ™œ๋™ ์ค‘ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฐ์ธก, ์„ผ์„œ ํА, ์„ผ์„œ ๊ณ ์ฐฉ(๋‹จ์ ˆ)์„ ํƒ์ง€ยท๋ณด์ •ํ•˜๋Š” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ƒ๋ฆฌ์ ์œผ๋กœ ๋ถˆ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„ ๋ฐ– ๊ฐ’์„ ๊ฒฐ์ธก์œผ๋กœ ๋ฐ”๊พธ๊ณ  ์„ ํ˜• ๋ณด๊ฐ„๊ณผ 3๋ถ„ ์ด๋™ ์ค‘์•™๊ฐ’์œผ๋กœ ์ •๋ฆฌํ•˜๋ฉฐ, ๊ฐ™์€ ๊ฐ’์ด 8๋ถ„ ์ด์ƒ ์ด์–ด์ง€๋Š” ์„ผ์„œ ๊ณ ์ฐฉ์„ ์ฐพ์•„ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/preprocess/wel01_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": [ "\"\"\"์•ผ์ „ ํ™œ๋™ ์ค‘ ์ƒ์ฒด์‹ ํ˜ธ์˜ ๊ฒฐ์ธกยท์„ผ์„œ ํАยท๊ณ ์ฐฉ(์„ผ์„œ ๋‹จ์ ˆ)์„ ํƒ์ง€ยท๋ณด์ •ํ•˜๋Š” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•˜๊ณ , ์ฒ˜๋ฆฌ ์†๋„ยท๋‹จ์œ„ ํ…Œ์ŠคํŠธ 5์ข…ยท๋ณด์ • ์ „ํ›„ ์ฒด์˜จ ์˜ค์ฐจ์™€ ๊ฒฝ๋ณด ๋ถ„๋ฅ˜ ์„ฑ๋Šฅ์„ ์ธก์ •ํ•œ๋‹ค. ์ง„๋‹จ ๋ชฉ์ ์ด ์•„๋‹Œ ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋”๋ฏธ ์ƒ์ฒด์‹ ํ˜ธ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋ณ‘ยท๋ถ€๋Œ€ยท์˜๋ฌด ๊ธฐ๋ก ์—†์Œ) ----------------\n", "# ๊ฐ€์ƒ ๋ณ‘์‚ฌ(๊ฐ€๋ช… ID)๊ฐ€ 60๋ถ„๊ฐ„ ๊ฑท๊ธฐยทํœด์‹ยท๊ณ ๊ฐ•๋„ ํ™œ๋™์„ ํ•˜๋Š” ๋™์•ˆ์˜ ์‹ฌ๋ฐ•์ˆ˜ยท์ฒด์˜จ(์ถ”์ • ์‹ฌ๋ถ€)ยทํ”ผ๋ถ€์ „๋„๋„๋ฅผ 1๋ถ„ ๊ฐ„๊ฒฉ์œผ๋กœ ๋งŒ๋“ ๋‹ค.\n", "# ์ง„๋‹จ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ์ƒํƒœ ๋ผ๋ฒจ์€ ๊ฐ€์ƒ ์ƒ๋ฆฌ ๋ชจ๋ธ์˜ '๊ต์œก์šฉ ๋‹จ๊ณ„'์ผ ๋ฟ์ด๋‹ค.\n", "N_MIN = 60\n", "STAGE = {0: \"์ •์ƒ\", 1: \"์ฃผ์˜\", 2: \"๊ฒฝ๋ณด\"}\n", "TYPES = [\"normal\", \"heat\", \"cold\"]\n", "\n", "\n", "def make_person(i, rng):\n", " return {\"id\": f\"S{i:03d}\", \"rest_hr\": float(rng.normal(64, 7)), \"age\": int(rng.integers(19, 27)), \"bmi\": float(rng.normal(23.5, 2.5)),\n", " \"acclim\": int(rng.random() < 0.4), \"fit\": float(np.clip(rng.normal(0.5, 0.2), 0.05, 0.95))}\n", "\n", "\n", "def make_env(kind, rng):\n", " if kind == \"heat\":\n", " return {\"temp_c\": float(rng.uniform(30, 40)), \"rh\": float(rng.uniform(45, 85)), \"wind\": float(rng.uniform(0.3, 3))}\n", " if kind == \"cold\":\n", " return {\"temp_c\": float(rng.uniform(-22, 2)), \"rh\": float(rng.uniform(40, 80)), \"wind\": float(rng.uniform(2, 11))}\n", " return {\"temp_c\": float(rng.uniform(8, 24)), \"rh\": float(rng.uniform(35, 70)), \"wind\": float(rng.uniform(0.3, 5))}\n", "\n", "\n", "def make_session(sid, kind, rng, p=None, glitch=True):\n", " \"\"\"๋ฐ˜ํ™˜: dict(person, env, kind, df[hr,temp,eda,act + ์ •๋‹ต coreยทstageยทrtype]). ์„ผ์„œ ๊ฒฐ์ธกยทํАยท๊ณ ์ฐฉ ํฌํ•จ.\"\"\"\n", " p = p or make_person(sid, rng)\n", " env = make_env(kind, rng)\n", " act = np.zeros(N_MIN); t = 0\n", " while t < N_MIN: # 10~20๋ถ„ ๋‹จ์œ„ ํ™œ๋™ ๋ธ”๋ก: ํœด์‹ 1.5 / ํ–‰๊ตฐ 4.5 / ๊ณ ๊ฐ•๋„ 7 MET\n", " d = int(rng.integers(10, 21)); act[t:t + d] = rng.choice([1.5, 4.5, 7.0], p=[0.25, 0.5, 0.25]); t += d\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"]\n", " core, cores = 36.9, []\n", " for m in range(N_MIN):\n", " heat = 0.085 * max(0, hi - 24) * (1 - 0.25 * p[\"acclim\"]) * (1 + 0.5 * m / N_MIN) * (1 + 0.012 * (p[\"bmi\"] - 23.5))\n", " cold = -0.075 * max(0, 8 - wc) * (1 - 0.09 * act[m])\n", " ss = 36.9 + 0.09 * act[m] + heat + cold\n", " core += (ss - core) / 18 + rng.normal(0, 0.03)\n", " cores.append(core)\n", " core = np.array(cores)\n", " hr = p[\"rest_hr\"] + 9.5 * (act - 1.5) * (1.15 - 0.3 * p[\"fit\"]) + 11 * (core - 36.9) + rng.normal(0, 3.2, N_MIN)\n", " eda = 2.0 + 0.25 * act + 3.2 * np.maximum(core - 37.2, 0) - 0.04 * np.maximum(8 - wc, 0) + rng.normal(0, 0.25, N_MIN)\n", " bias = rng.normal(0, 0.1)\n", " temp = core + bias + rng.normal(0, 0.1, N_MIN) - np.where((kind == \"cold\") & (env[\"wind\"] > 7), 0.25, 0.0) # ์„ผ์„œ ์ ‘์ด‰ ๋ถ€์œ„ ๋ฐ”๋žŒ ๋ƒ‰๊ฐ ์˜ค์ฐจ\n", " df = pd.DataFrame({\"min\": np.arange(N_MIN), \"hr\": hr, \"temp\": temp, \"eda\": np.maximum(eda, 0.2), \"act\": act})\n", " if glitch:\n", " for col in (\"hr\", \"temp\", \"eda\"):\n", " df.loc[rng.random(N_MIN) < 0.03, col] = np.nan # ๊ฒฐ์ธก\n", " s = int(rng.integers(5, N_MIN))\n", " if rng.random() < 0.12:\n", " df.loc[s, \"hr\"] = 230.0 # ์„ผ์„œ ํА\n", " if rng.random() < 0.12:\n", " df.loc[s, \"temp\"] = 45.0\n", " ht = np.where(core >= 39.0, 2, np.where(core >= 38.3, 1, 0)); cd = np.where(core <= 35.4, 2, np.where(core <= 36.2, 1, 0))\n", " df[\"core\"] = core\n", " df[\"stage\"] = np.maximum(ht, cd); df[\"rtype\"] = np.where(ht > 0, \"heat\", np.where(cd > 0, \"cold\", \"normal\"))\n", " return {\"id\": p[\"id\"], \"person\": p, \"env\": env, \"kind\": kind, \"df\": df}\n", "\n", "\n", "def make_dataset(n, seed):\n", " rng = np.random.default_rng(seed)\n", " kinds = [\"normal\", \"heat\", \"cold\"]\n", " return [make_session(i, kinds[i % 3], rng) for i in range(n)]\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---------------- ์ „์ฒ˜๋ฆฌยทํŠน์ง• ์ถ”์ถœ ๊ณตํ†ต ํ•จ์ˆ˜ ----------------\n", "LIMITS = {\"hr\": (30, 220), \"temp\": (30.0, 43.0), \"eda\": (0.0, 40.0)} # ์ƒ๋ฆฌ์ ์œผ๋กœ ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„(๋ฒ”์œ„ ๋ฐ– = ์„ผ์„œ ์˜ค๋ฅ˜)\n", "\n", "\n", "def clean(df):\n", " \"\"\"๋ฒ”์œ„ ๋ฐ– ๊ฐ’์€ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ -> ์„ ํ˜• ๋ณด๊ฐ„(์–‘ ๋์€ ๊ฐ€๊นŒ์šด ๊ฐ’) -> 3๋ถ„ ์ด๋™ ์ค‘์•™๊ฐ’์œผ๋กœ ํА ์ œ๊ฑฐ. ๋ฐ˜ํ™˜: (์ •์ œ df, ๋ณด์ • ๊ฑด์ˆ˜)\"\"\"\n", " out = df.copy(); fixed = 0\n", " for c, (lo, hi) in LIMITS.items():\n", " bad = out[c].isna() | (out[c] < lo) | (out[c] > hi)\n", " fixed += int(bad.sum())\n", " out.loc[bad, c] = np.nan\n", " out[c] = out[c].interpolate(limit_direction=\"both\").rolling(3, center=True, min_periods=1).median()\n", " return out, fixed\n", "\n", "\n", "def env_index(env):\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0.0) # ์ฒด๊ฐ ๋”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"] # ์ฒด๊ฐ ์ถ”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " return hi, wc\n", "\n", "\n", "def features(s, use_env=True, personal=True):\n", " \"\"\"์„ธ์…˜ 1๊ฐœ -> ๋ถ„ ๋‹จ์œ„ ํŠน์ง•ํ‘œ(์ตœ๊ทผ 5๋ถ„ ํ‰๊ท ยท๊ธฐ์šธ๊ธฐ ํฌํ•จ). ์ •๋‹ต stage/rtype ํฌํ•จ.\"\"\"\n", " df, _ = clean(s[\"df\"]); p, e = s[\"person\"], s[\"env\"]\n", " f = pd.DataFrame({\"sid\": s[\"id\"], \"min\": df[\"min\"]})\n", " f[\"temp5\"] = df[\"temp\"].rolling(5, min_periods=1).mean()\n", " f[\"hr5\"] = df[\"hr\"].rolling(5, min_periods=1).mean()\n", " f[\"eda5\"] = df[\"eda\"].rolling(5, min_periods=1).mean()\n", " f[\"dtemp\"] = (f[\"temp5\"] - f[\"temp5\"].shift(4)).fillna(0)\n", " f[\"act\"] = df[\"act\"]\n", " if personal:\n", " f[\"hr_rel\"] = f[\"hr5\"] - p[\"rest_hr\"]\n", " f[\"acclim\"], f[\"bmi\"], f[\"fit\"] = p[\"acclim\"], p[\"bmi\"], p[\"fit\"]\n", " if use_env:\n", " f[\"hi\"], f[\"wc\"] = env_index(e); f[\"wind\"] = e[\"wind\"]\n", " f[\"stage\"], f[\"rtype\"], f[\"kind\"] = df[\"stage\"], df[\"rtype\"], s[\"kind\"]\n", " return f[f[\"min\"] >= 5].reset_index(drop=True)\n", "\n", "\n", "def table(sessions, **kw):\n", " return pd.concat([features(s, **kw) for s in sessions], ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "\n", "\n", "def flatline(series, run=8):\n", " \"\"\"๊ฐ™์€ ๊ฐ’์ด run๋ถ„ ์ด์ƒ ์ด์–ด์ง€๋ฉด ์„ผ์„œ ๊ณ ์ฐฉ(๋‹จ์ ˆ)์œผ๋กœ ๋ณด๊ณ  ํ•ด๋‹น ๊ตฌ๊ฐ„์„ True๋กœ ํ‘œ์‹œํ•œ๋‹ค.\"\"\"\n", " same = series.diff().abs().fillna(1) < 1e-9\n", " grp = (~same).cumsum()\n", " return same.groupby(grp).transform(\"sum\").add(1).where(same | same.shift(-1, fill_value=False), 1) >= run\n", "\n", "\n", "def preprocess(df):\n", " \"\"\"๊ณ ์ฐฉ ๊ตฌ๊ฐ„์„ ๊ฒฐ์ธก์œผ๋กœ ๋ฐ”๊พผ ๋’ค clean()์œผ๋กœ ๋ฒ”์œ„ ๊ฒ€์‚ฌยท๋ณด๊ฐ„ยทํА ์ œ๊ฑฐ. ๋ฐ˜ํ™˜: (์ •์ œ df, ๋ณด์ • ๊ฑด์ˆ˜, ๊ณ ์ฐฉ ๊ฑด์ˆ˜)\"\"\"\n", " d = df.copy(); stuck = 0\n", " for c in (\"hr\", \"temp\"):\n", " fl = flatline(d[c].round(3))\n", " stuck += int(fl.sum()); d.loc[fl, c] = np.nan\n", " out, fixed = clean(d)\n", " return out, fixed, stuck\n", "\n", "\n", "def inject_faults(df, rng):\n", " \"\"\"์„ผ์„œ ๋‹จ์ ˆ ๋ชจ์˜: ์ฒด์˜จ์ด ๊ฐ€์žฅ ํฌ๊ฒŒ ๋ณ€ํ•˜๋Š” ์‹œ์  ์ง์ „๋ถ€ํ„ฐ ์„ผ์„œ๊ฐ€ 12๋ถ„๊ฐ„ ๊ฐ™์€ ๊ฐ’์œผ๋กœ ๊ณ ์ฐฉ๋œ๋‹ค(์‹ค์ œ ๋ณ€ํ™”๋ฅผ ๋”ฐ๋ผ๊ฐ€์ง€ ๋ชปํ•จ).\"\"\"\n", " d = df.copy(); s = int(np.clip(np.argmax(np.abs(d[\"core\"].values - 36.9)) - 12 + rng.integers(0, 4), 3, N_MIN - 13))\n", " d.loc[s:s + 11, \"temp\"] = d.loc[s, \"temp\"]\n", " return d\n", "\n", "\n", "rng = np.random.default_rng(5)\n", "data = make_dataset(120, seed=21)\n", "\n", "# 1) ์ฒ˜๋ฆฌ ์†๋„์™€ ์˜ค๋ฅ˜์œจ\n", "t0 = time.perf_counter(); n_rows = 0; errors = 0; fixed_all = 0\n", "for s in data:\n", " try:\n", " out, fx, st = preprocess(s[\"df\"]); n_rows += len(out) * 3; fixed_all += fx\n", " errors += int(out[[\"hr\", \"temp\", \"eda\"]].isna().any().any() or (out[\"temp\"] > 43).any())\n", " except Exception:\n", " errors += 1\n", "sec = time.perf_counter() - t0\n", "print(f\"์ „์ฒ˜๋ฆฌ {n_rows:,}๊ฑด(์‹ ํ˜ธ๊ฐ’) / {sec:.2f}์ดˆ = {n_rows / sec:,.0f}๊ฑด/์ดˆ (๋ชฉํ‘œ 1,000 ์ด์ƒ: {'๋‹ฌ์„ฑ' if n_rows / sec >= 1000 else '๋ฏธ๋‹ฌ'})\")\n", "print(f\"์ „์ฒ˜๋ฆฌ ์˜ค๋ฅ˜ {errors}๊ฑด -> ์˜ค๋ฅ˜์œจ {errors / len(data):.0%} (๋ชฉํ‘œ 0%), ๋ฒ”์œ„ ๋ฐ–ยท๊ฒฐ์ธก ๋ณด์ • {fixed_all}๊ฑด\")\n", "\n", "\n", "# 2) ๋‹จ์œ„ ํ…Œ์ŠคํŠธ 5์ข…\n", "def t1():\n", " d = pd.DataFrame({\"min\": range(6), \"hr\": [80, np.nan, 82, 83, 84, 85.], \"temp\": 37.0, \"eda\": 2.0, \"act\": 1.5, \"stage\": 0, \"rtype\": \"normal\"})\n", " out, fx = clean(d); assert out[\"hr\"].notna().all() and fx == 1\n", "def t2():\n", " d = pd.DataFrame({\"min\": range(6), \"hr\": [80, 81, 230, 82, 83, 84.], \"temp\": 37.0, \"eda\": 2.0, \"act\": 1.5, \"stage\": 0, \"rtype\": \"normal\"})\n", " out, fx = clean(d); assert out[\"hr\"].max() < 100 and fx == 1\n", "def t3():\n", " s = pd.Series([36.9, 37.0, 37.0, 37.0, 37.0, 37.0, 37.0, 37.0, 37.0, 37.0, 37.1]); assert flatline(s).sum() >= 8\n", "def t4():\n", " s = pd.Series(np.linspace(36.5, 37.5, 20)); assert not flatline(s).any()\n", "def t5():\n", " out, fx, st = preprocess(inject_faults(make_session(1, \"normal\", np.random.default_rng(3))[\"df\"], np.random.default_rng(4))); assert st >= 8 and len(out) == N_MIN\n", "tests = {\"๊ฒฐ์ธก ๋ณด๊ฐ„\": t1, \"์„ผ์„œ ํА ์ œ๊ฑฐ\": t2, \"๊ณ ์ฐฉ ๊ตฌ๊ฐ„ ํƒ์ง€\": t3, \"์ •์ƒ ๋ณ€ํ™” ์˜คํƒ ์—†์Œ\": t4, \"๊ณ ์ฐฉ ์ฃผ์ž… ํ›„ ๋ณด์ •\": t5}\n", "passed = 0\n", "for name, fn in tests.items():\n", " try:\n", " fn(); passed += 1; print(f\"[PASS] {name}\")\n", " except AssertionError:\n", " print(f\"[FAIL] {name}\")\n", "print(f\"๋‹จ์œ„ ํ…Œ์ŠคํŠธ {passed}/5 ํ†ต๊ณผ\")\n", "\n", "# 3) ๋ณด์ • ์ „ํ›„ ์ฒด์˜จ ์˜ค์ฐจ(์ •๋‹ต core ๋Œ€๋น„ RMSE)์™€ ๊ฒฝ๋ณด ๋ถ„๋ฅ˜ ์„ฑ๋Šฅ\n", "def rmse(a, b):\n", " m = ~np.isnan(a)\n", " return float(np.sqrt(np.mean((a[m] - b[m]) ** 2)))\n", "\n", "\n", "def hot_cold_f1(df_use, stage_src):\n", " \"\"\"์ฒด์˜จ 38.0 ์ด์ƒ ๋˜๋Š” 36.3 ์ดํ•˜๋ฅผ ์œ„ํ—˜์œผ๋กœ ๋ณธ ๊ฒฝ๋ณด์™€ ์ •๋‹ต stage>=1์˜ F1.\"\"\"\n", " pred = (df_use[\"temp\"] >= 38.0) | (df_use[\"temp\"] <= 36.3)\n", " truth = stage_src[\"stage\"] >= 1\n", " tp = (pred & truth).sum(); p = tp / max(pred.sum(), 1); r = tp / max(truth.sum(), 1)\n", " return round(2 * p * r / max(p + r, 1e-9), 3), int((pred & ~truth).sum())\n", "\n", "\n", "rows = []\n", "for kind, label in ((\"normal\", \"ํ‰์‹œ\"), (\"heat\", \"ํญ์—ผ\"), (\"cold\", \"ํ˜นํ•œ\")):\n", " raw, cln, tr_ = [], [], []\n", " for s in [x for x in data if x[\"kind\"] == kind]:\n", " bad = inject_faults(s[\"df\"], rng)\n", " out, _, _ = preprocess(bad)\n", " raw.append(bad); cln.append(out); tr_.append(s[\"df\"])\n", " raw, cln, tr_ = pd.concat(raw), pd.concat(cln), pd.concat(tr_)\n", " f_raw, fp_raw = hot_cold_f1(raw, tr_)\n", " f_cln, fp_cln = hot_cold_f1(cln, tr_)\n", " rows.append({\"์ƒํ™ฉ\": label, \"์ฒด์˜จ RMSE ์ „\": round(rmse(raw[\"temp\"].where(raw[\"temp\"] <= 43).values, tr_[\"core\"].values), 3),\n", " \"์ฒด์˜จ RMSE ํ›„\": round(rmse(cln[\"temp\"].values, tr_[\"core\"].values), 3), \"๊ฒฝ๋ณด F1 ์ „\": f_raw, \"๊ฒฝ๋ณด F1 ํ›„\": f_cln, \"์˜ค๊ฒฝ๋ณด ์ „\": fp_raw, \"์˜ค๊ฒฝ๋ณด ํ›„\": fp_cln})\n", "print(\"\\n[๋ณด์ • ์ „ํ›„ ๋น„๊ต] (์ „: ์„ผ์„œ ํАยท๊ฒฐ์ธกยท๊ณ ์ฐฉ ๊ทธ๋Œ€๋กœ / ํ›„: ์ „์ฒ˜๋ฆฌ ์ ์šฉ)\")\n", "print(pd.DataFrame(rows).to_string(index=False))\n", "print(\"์ „์ˆ  ๋…ธ์ด์ฆˆ ๋Œ€์‘: ์ฒด์˜จ ์„ผ์„œ ๊ณ ์ฐฉ(๋‹จ์ ˆ)์€ 8๋ถ„ ์ด์ƒ ๊ฐ™์€ ๊ฐ’์ด๋ฉด ๊ฒฐ์ธก ์ฒ˜๋ฆฌ ํ›„ ๋ณด๊ฐ„ํ•˜๊ณ , ๋ฒ”์œ„ ๋ฐ– ๊ฐ’์€ ์„ผ์„œ ์˜ค๋ฅ˜๋กœ ๋ณธ๋‹ค. ์Šคํ‘ธํ•‘ ํŒ๋ณ„์€ ๋ณธ ๊ณผ์ œ ๋ฒ”์œ„ ๋ฐ–์ด๋‹ค.\")\n", "\n", "s = data[1]; bad = inject_faults(s[\"df\"], rng); out, _, _ = preprocess(bad)\n", "plt.figure(figsize=(9, 4))\n", "plt.plot(bad[\"min\"], bad[\"temp\"], \".\", color=\"tab:red\", label=\"raw (faults)\"); plt.plot(out[\"min\"], out[\"temp\"], color=\"tab:blue\", label=\"cleaned\")\n", "plt.plot(s[\"df\"][\"min\"], s[\"df\"][\"core\"], \"k:\", label=\"true (simulated)\"); plt.ylim(34, 41); plt.xlabel(\"minute\"); plt.ylabel(\"temp (C)\")\n", "plt.title(f\"{s['id']} temperature before/after preprocessing (synthetic)\"); plt.legend(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 02 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "์ „์ฒ˜๋ฆฌ 21,600๊ฑด(์‹ ํ˜ธ๊ฐ’) / 1.20์ดˆ = 18,030๊ฑด/์ดˆ (๋ชฉํ‘œ 1,000 ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "์ „์ฒ˜๋ฆฌ ์˜ค๋ฅ˜ 0๊ฑด -> ์˜ค๋ฅ˜์œจ 0% (๋ชฉํ‘œ 0%), ๋ฒ”์œ„ ๋ฐ–ยท๊ฒฐ์ธก ๋ณด์ • 663๊ฑด\n", "[PASS] ๊ฒฐ์ธก ๋ณด๊ฐ„\n", "[PASS] ์„ผ์„œ ํА ์ œ๊ฑฐ\n", "[PASS] ๊ณ ์ฐฉ ๊ตฌ๊ฐ„ ํƒ์ง€\n", "[PASS] ์ •์ƒ ๋ณ€ํ™” ์˜คํƒ ์—†์Œ\n", "[PASS] ๊ณ ์ฐฉ ์ฃผ์ž… ํ›„ ๋ณด์ •\n", "๋‹จ์œ„ ํ…Œ์ŠคํŠธ 5/5 ํ†ต๊ณผ\n", "\n", "[๋ณด์ • ์ „ํ›„ ๋น„๊ต] (์ „: ์„ผ์„œ ํАยท๊ฒฐ์ธกยท๊ณ ์ฐฉ ๊ทธ๋Œ€๋กœ / ํ›„: ์ „์ฒ˜๋ฆฌ ์ ์šฉ)\n", "์ƒํ™ฉ ์ฒด์˜จ RMSE ์ „ ์ฒด์˜จ RMSE ํ›„ ๊ฒฝ๋ณด F1 ์ „ ๊ฒฝ๋ณด F1 ํ›„ ์˜ค๊ฒฝ๋ณด ์ „ ์˜ค๊ฒฝ๋ณด ํ›„\n", "ํ‰์‹œ 0.141 0.121 0.000 0.000 4 0\n", "ํญ์—ผ 0.146 0.104 0.818 0.820 378 417\n", "ํ˜นํ•œ 0.219 0.203 0.814 0.828 397 419\n", "์ „์ˆ  ๋…ธ์ด์ฆˆ ๋Œ€์‘: ์ฒด์˜จ ์„ผ์„œ ๊ณ ์ฐฉ(๋‹จ์ ˆ)์€ 8๋ถ„ ์ด์ƒ ๊ฐ™์€ ๊ฐ’์ด๋ฉด ๊ฒฐ์ธก ์ฒ˜๋ฆฌ ํ›„ ๋ณด๊ฐ„ํ•˜๊ณ , ๋ฒ”์œ„ ๋ฐ– ๊ฐ’์€ ์„ผ์„œ ์˜ค๋ฅ˜๋กœ ๋ณธ๋‹ค. ์Šคํ‘ธํ•‘ ํŒ๋ณ„์€ ๋ณธ ๊ณผ์ œ ๋ฒ”์œ„ ๋ฐ–์ด๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 03. ์ฒด์˜จ-์‹ฌ๋ฐ•์ˆ˜ ์ƒ๊ด€๊ด€๊ณ„ ๊ธฐ๋ฐ˜ 1์ฐจ ์—ด์‚ฌ๋ณ‘/์ €์ฒด์˜จ์ฆ ๊ฒฝ๋ณด Baseline ๋ชจ๋ธ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ฒด์˜จ-์‹ฌ๋ฐ•์ˆ˜ ์ƒ๊ด€๊ด€๊ณ„ ๊ธฐ๋ฐ˜ 1์ฐจ ์˜จ์—ด/ํ•œ๋žญ ์ŠคํŠธ๋ ˆ์Šค ๊ฒฝ๋ณด Baseline ๋ชจ๋ธ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์ฒด์˜จ๊ณผ ์‹ฌ๋ฐ•์ˆ˜์˜ ์ƒ๊ด€๊ด€๊ณ„๋ฅผ ์ด์šฉํ•ด ์˜จ์—ดยทํ•œ๋žญ ์ŠคํŠธ๋ ˆ์Šค๋ฅผ 1์ฐจ๋กœ ๊ฒฝ๋ณดํ•˜๋Š” ๊ทœ์น™ ๊ธฐ๋ฐ˜ Baseline์„ ๊ตฌํ˜„ํ•˜๊ณ  ํŒ๋‹จ ๊ธฐ์ค€์„ ํˆฌ๋ช…ํ•˜๊ฒŒ ๊ธฐ๋กํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ตœ๊ทผ 5๋ถ„ ํ‰๊ท  ์ฒด์˜จ์ด 38.0๋„ ์ด์ƒ์ด๋ฉด์„œ ์‹ฌ๋ฐ•์ˆ˜๊ฐ€ 95 ์ด์ƒ์ด๋ฉด ์˜จ์—ด ์ŠคํŠธ๋ ˆ์Šค ์˜์‹ฌ, 36.3๋„ ์ดํ•˜์ด๋ฉด ํ•œ๋žญ ์ŠคํŠธ๋ ˆ์Šค ์˜์‹ฌ์œผ๋กœ ๋ถ„๋ฅ˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/wel01_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": [ "\"\"\"์ฒด์˜จ๊ณผ ์‹ฌ๋ฐ•์ˆ˜์˜ ์ƒ๊ด€๊ด€๊ณ„(์ฒด์˜จ ์ƒ์Šน ์‹œ ์‹ฌ๋ฐ•์ˆ˜ ๋™๋ฐ˜ ์ƒ์Šน)๋ฅผ ์ด์šฉํ•œ ๊ทœ์น™ ๊ธฐ๋ฐ˜ 1์ฐจ ์˜จ์—ดยทํ•œ๋žญ ์ŠคํŠธ๋ ˆ์Šค ๊ฒฝ๋ณด Baseline์„ ๊ตฌํ˜„ํ•˜๊ณ , ๊ฐ€์ƒ ๋ณ‘์‚ฌ ๋ฐ์ดํ„ฐ์—์„œ ์ •ํ™•๋„์™€ ์œ„ํ—˜ ๊ตฌ๊ฐ„ ์žฌํ˜„์œจ์„ ์ธก์ •ํ•œ๋‹ค. ์ง„๋‹จ ์•„๋‹˜ยท์˜๋ฌด๋Œ€ ์—ฐ๊ณ„ ์›์น™์„ ๋”ฐ๋ฅธ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋”๋ฏธ ์ƒ์ฒด์‹ ํ˜ธ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋ณ‘ยท๋ถ€๋Œ€ยท์˜๋ฌด ๊ธฐ๋ก ์—†์Œ) ----------------\n", "# ๊ฐ€์ƒ ๋ณ‘์‚ฌ(๊ฐ€๋ช… ID)๊ฐ€ 60๋ถ„๊ฐ„ ๊ฑท๊ธฐยทํœด์‹ยท๊ณ ๊ฐ•๋„ ํ™œ๋™์„ ํ•˜๋Š” ๋™์•ˆ์˜ ์‹ฌ๋ฐ•์ˆ˜ยท์ฒด์˜จ(์ถ”์ • ์‹ฌ๋ถ€)ยทํ”ผ๋ถ€์ „๋„๋„๋ฅผ 1๋ถ„ ๊ฐ„๊ฒฉ์œผ๋กœ ๋งŒ๋“ ๋‹ค.\n", "# ์ง„๋‹จ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ์ƒํƒœ ๋ผ๋ฒจ์€ ๊ฐ€์ƒ ์ƒ๋ฆฌ ๋ชจ๋ธ์˜ '๊ต์œก์šฉ ๋‹จ๊ณ„'์ผ ๋ฟ์ด๋‹ค.\n", "N_MIN = 60\n", "STAGE = {0: \"์ •์ƒ\", 1: \"์ฃผ์˜\", 2: \"๊ฒฝ๋ณด\"}\n", "TYPES = [\"normal\", \"heat\", \"cold\"]\n", "\n", "\n", "def make_person(i, rng):\n", " return {\"id\": f\"S{i:03d}\", \"rest_hr\": float(rng.normal(64, 7)), \"age\": int(rng.integers(19, 27)), \"bmi\": float(rng.normal(23.5, 2.5)),\n", " \"acclim\": int(rng.random() < 0.4), \"fit\": float(np.clip(rng.normal(0.5, 0.2), 0.05, 0.95))}\n", "\n", "\n", "def make_env(kind, rng):\n", " if kind == \"heat\":\n", " return {\"temp_c\": float(rng.uniform(30, 40)), \"rh\": float(rng.uniform(45, 85)), \"wind\": float(rng.uniform(0.3, 3))}\n", " if kind == \"cold\":\n", " return {\"temp_c\": float(rng.uniform(-22, 2)), \"rh\": float(rng.uniform(40, 80)), \"wind\": float(rng.uniform(2, 11))}\n", " return {\"temp_c\": float(rng.uniform(8, 24)), \"rh\": float(rng.uniform(35, 70)), \"wind\": float(rng.uniform(0.3, 5))}\n", "\n", "\n", "def make_session(sid, kind, rng, p=None, glitch=True):\n", " \"\"\"๋ฐ˜ํ™˜: dict(person, env, kind, df[hr,temp,eda,act + ์ •๋‹ต coreยทstageยทrtype]). ์„ผ์„œ ๊ฒฐ์ธกยทํАยท๊ณ ์ฐฉ ํฌํ•จ.\"\"\"\n", " p = p or make_person(sid, rng)\n", " env = make_env(kind, rng)\n", " act = np.zeros(N_MIN); t = 0\n", " while t < N_MIN: # 10~20๋ถ„ ๋‹จ์œ„ ํ™œ๋™ ๋ธ”๋ก: ํœด์‹ 1.5 / ํ–‰๊ตฐ 4.5 / ๊ณ ๊ฐ•๋„ 7 MET\n", " d = int(rng.integers(10, 21)); act[t:t + d] = rng.choice([1.5, 4.5, 7.0], p=[0.25, 0.5, 0.25]); t += d\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"]\n", " core, cores = 36.9, []\n", " for m in range(N_MIN):\n", " heat = 0.085 * max(0, hi - 24) * (1 - 0.25 * p[\"acclim\"]) * (1 + 0.5 * m / N_MIN) * (1 + 0.012 * (p[\"bmi\"] - 23.5))\n", " cold = -0.075 * max(0, 8 - wc) * (1 - 0.09 * act[m])\n", " ss = 36.9 + 0.09 * act[m] + heat + cold\n", " core += (ss - core) / 18 + rng.normal(0, 0.03)\n", " cores.append(core)\n", " core = np.array(cores)\n", " hr = p[\"rest_hr\"] + 9.5 * (act - 1.5) * (1.15 - 0.3 * p[\"fit\"]) + 11 * (core - 36.9) + rng.normal(0, 3.2, N_MIN)\n", " eda = 2.0 + 0.25 * act + 3.2 * np.maximum(core - 37.2, 0) - 0.04 * np.maximum(8 - wc, 0) + rng.normal(0, 0.25, N_MIN)\n", " bias = rng.normal(0, 0.1)\n", " temp = core + bias + rng.normal(0, 0.1, N_MIN) - np.where((kind == \"cold\") & (env[\"wind\"] > 7), 0.25, 0.0) # ์„ผ์„œ ์ ‘์ด‰ ๋ถ€์œ„ ๋ฐ”๋žŒ ๋ƒ‰๊ฐ ์˜ค์ฐจ\n", " df = pd.DataFrame({\"min\": np.arange(N_MIN), \"hr\": hr, \"temp\": temp, \"eda\": np.maximum(eda, 0.2), \"act\": act})\n", " if glitch:\n", " for col in (\"hr\", \"temp\", \"eda\"):\n", " df.loc[rng.random(N_MIN) < 0.03, col] = np.nan # ๊ฒฐ์ธก\n", " s = int(rng.integers(5, N_MIN))\n", " if rng.random() < 0.12:\n", " df.loc[s, \"hr\"] = 230.0 # ์„ผ์„œ ํА\n", " if rng.random() < 0.12:\n", " df.loc[s, \"temp\"] = 45.0\n", " ht = np.where(core >= 39.0, 2, np.where(core >= 38.3, 1, 0)); cd = np.where(core <= 35.4, 2, np.where(core <= 36.2, 1, 0))\n", " df[\"core\"] = core\n", " df[\"stage\"] = np.maximum(ht, cd); df[\"rtype\"] = np.where(ht > 0, \"heat\", np.where(cd > 0, \"cold\", \"normal\"))\n", " return {\"id\": p[\"id\"], \"person\": p, \"env\": env, \"kind\": kind, \"df\": df}\n", "\n", "\n", "def make_dataset(n, seed):\n", " rng = np.random.default_rng(seed)\n", " kinds = [\"normal\", \"heat\", \"cold\"]\n", " return [make_session(i, kinds[i % 3], rng) for i in range(n)]\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---------------- ์ „์ฒ˜๋ฆฌยทํŠน์ง• ์ถ”์ถœ ๊ณตํ†ต ํ•จ์ˆ˜ ----------------\n", "LIMITS = {\"hr\": (30, 220), \"temp\": (30.0, 43.0), \"eda\": (0.0, 40.0)} # ์ƒ๋ฆฌ์ ์œผ๋กœ ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„(๋ฒ”์œ„ ๋ฐ– = ์„ผ์„œ ์˜ค๋ฅ˜)\n", "\n", "\n", "def clean(df):\n", " \"\"\"๋ฒ”์œ„ ๋ฐ– ๊ฐ’์€ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ -> ์„ ํ˜• ๋ณด๊ฐ„(์–‘ ๋์€ ๊ฐ€๊นŒ์šด ๊ฐ’) -> 3๋ถ„ ์ด๋™ ์ค‘์•™๊ฐ’์œผ๋กœ ํА ์ œ๊ฑฐ. ๋ฐ˜ํ™˜: (์ •์ œ df, ๋ณด์ • ๊ฑด์ˆ˜)\"\"\"\n", " out = df.copy(); fixed = 0\n", " for c, (lo, hi) in LIMITS.items():\n", " bad = out[c].isna() | (out[c] < lo) | (out[c] > hi)\n", " fixed += int(bad.sum())\n", " out.loc[bad, c] = np.nan\n", " out[c] = out[c].interpolate(limit_direction=\"both\").rolling(3, center=True, min_periods=1).median()\n", " return out, fixed\n", "\n", "\n", "def env_index(env):\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0.0) # ์ฒด๊ฐ ๋”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"] # ์ฒด๊ฐ ์ถ”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " return hi, wc\n", "\n", "\n", "def features(s, use_env=True, personal=True):\n", " \"\"\"์„ธ์…˜ 1๊ฐœ -> ๋ถ„ ๋‹จ์œ„ ํŠน์ง•ํ‘œ(์ตœ๊ทผ 5๋ถ„ ํ‰๊ท ยท๊ธฐ์šธ๊ธฐ ํฌํ•จ). ์ •๋‹ต stage/rtype ํฌํ•จ.\"\"\"\n", " df, _ = clean(s[\"df\"]); p, e = s[\"person\"], s[\"env\"]\n", " f = pd.DataFrame({\"sid\": s[\"id\"], \"min\": df[\"min\"]})\n", " f[\"temp5\"] = df[\"temp\"].rolling(5, min_periods=1).mean()\n", " f[\"hr5\"] = df[\"hr\"].rolling(5, min_periods=1).mean()\n", " f[\"eda5\"] = df[\"eda\"].rolling(5, min_periods=1).mean()\n", " f[\"dtemp\"] = (f[\"temp5\"] - f[\"temp5\"].shift(4)).fillna(0)\n", " f[\"act\"] = df[\"act\"]\n", " if personal:\n", " f[\"hr_rel\"] = f[\"hr5\"] - p[\"rest_hr\"]\n", " f[\"acclim\"], f[\"bmi\"], f[\"fit\"] = p[\"acclim\"], p[\"bmi\"], p[\"fit\"]\n", " if use_env:\n", " f[\"hi\"], f[\"wc\"] = env_index(e); f[\"wind\"] = e[\"wind\"]\n", " f[\"stage\"], f[\"rtype\"], f[\"kind\"] = df[\"stage\"], df[\"rtype\"], s[\"kind\"]\n", " return f[f[\"min\"] >= 5].reset_index(drop=True)\n", "\n", "\n", "def table(sessions, **kw):\n", " return pd.concat([features(s, **kw) for s in sessions], ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "\n", "# ํŒ๋‹จ ๊ธฐ์ค€(ํˆฌ๋ช…์„ฑ): โ‘  ์ตœ๊ทผ 5๋ถ„ ํ‰๊ท  ์ฒด์˜จ์ด 38.0๋„ ์ด์ƒ์ด๊ณ  ์‹ฌ๋ฐ•์ˆ˜๊ฐ€ 95 ์ด์ƒ์ด๋ฉด '์˜จ์—ด ์ŠคํŠธ๋ ˆ์Šค ์˜์‹ฌ'\n", "# โ‘ก ์ตœ๊ทผ 5๋ถ„ ํ‰๊ท  ์ฒด์˜จ์ด 36.3๋„ ์ดํ•˜์ด๋ฉด 'ํ•œ๋žญ ์ŠคํŠธ๋ ˆ์Šค ์˜์‹ฌ' โ‘ข ๊ทธ ์™ธ ์ •์ƒ\n", "TH_HEAT, TH_COLD, HR_GATE = 38.0, 36.3, 95\n", "\n", "\n", "def rule(r, th=TH_HEAT, tc=TH_COLD, hg=HR_GATE):\n", " if r[\"temp5\"] >= th and r[\"hr5\"] >= hg:\n", " return \"heat\"\n", " if r[\"temp5\"] <= tc:\n", " return \"cold\"\n", " return \"normal\"\n", "\n", "\n", "data = make_dataset(90, seed=7)\n", "tab = table(data, use_env=False, personal=False)\n", "tab[\"pred\"] = tab.apply(rule, axis=1)\n", "acc = (tab[\"pred\"] == tab[\"rtype\"]).mean()\n", "print(f\"[Baseline] ๊ฐ€์ƒ ๋ณ‘์‚ฌ 90๋ช… x 55๋ถ„ = {len(tab):,}๊ฐœ ๊ตฌ๊ฐ„, ์ •๋‹ต ๋ถ„ํฌ {tab['rtype'].value_counts().to_dict()}\")\n", "print(f\"๊ฒฝ๋ณด ์œ ํ˜• ๋ถ„๋ฅ˜ ์ •ํ™•๋„ {acc:.1%} (๋ชฉํ‘œ 75% ์ด์ƒ) -> {'๋‹ฌ์„ฑ' if acc >= 0.75 else '๋ฏธ๋‹ฌ'}\")\n", "\n", "print(\"\\n[์œ ํ˜•๋ณ„ ํŒ์ •] (ํ–‰: ์ •๋‹ต, ์—ด: ํŒ์ •)\")\n", "cm = pd.crosstab(tab[\"rtype\"], tab[\"pred\"]).reindex(index=TYPES, columns=TYPES, fill_value=0)\n", "print(cm.to_string())\n", "for c in (\"heat\", \"cold\"):\n", " rec = cm.loc[c, c] / cm.loc[c].sum(); prec = cm.loc[c, c] / max(cm[c].sum(), 1)\n", " print(f\"{c}: ์žฌํ˜„์œจ {rec:.1%}, ์ •๋ฐ€๋„ {prec:.1%}\")\n", "\n", "print(\"\\n[๋‹จ๊ณ„๋ณ„ ์œ„ํ—˜ ๊ตฌ๊ฐ„ ์žฌํ˜„์œจ] (์œ„ํ—˜ ์œ ํ˜•์„ ์œ„ํ—˜์œผ๋กœ ๋งž์ถ˜ ๋น„์œจ)\")\n", "risk = tab[tab[\"stage\"] >= 1]\n", "print(risk.groupby(\"stage\").apply(lambda g: f\"{(g['pred'] == g['rtype']).mean():.1%} ({len(g)}๊ตฌ๊ฐ„)\").to_string())\n", "\n", "print(\"\\n[์ฒด์˜จ-์‹ฌ๋ฐ•์ˆ˜ ์ƒ๊ด€] ์ „์ฒด ์ƒ๊ด€๊ณ„์ˆ˜ %.2f / ํญ์—ผ ์„ธ์…˜ %.2f / ํ˜นํ•œ ์„ธ์…˜ %.2f\" % (\n", " tab[[\"temp5\", \"hr5\"]].corr().iloc[0, 1], tab[tab[\"kind\"] == \"heat\"][[\"temp5\", \"hr5\"]].corr().iloc[0, 1], tab[tab[\"kind\"] == \"cold\"][[\"temp5\", \"hr5\"]].corr().iloc[0, 1]))\n", "\n", "print(\"\\n[์ž„๊ณ„๊ฐ’ ๋ฏผ๊ฐ๋„] ์˜จ์—ด ์ฒด์˜จ ๊ธฐ์ค€ ๋ณ€ํ™”\")\n", "for th in (37.7, 38.0, 38.3, 38.6):\n", " p = tab.apply(lambda r: rule(r, th=th), axis=1)\n", " r = ((p == \"heat\") & (tab[\"rtype\"] == \"heat\")).sum() / (tab[\"rtype\"] == \"heat\").sum(); f = ((p == \"heat\") & (tab[\"rtype\"] != \"heat\")).sum()\n", " print(f\"๊ธฐ์ค€ {th}: ์˜จ์—ด ์žฌํ˜„์œจ {r:.1%}, ์˜ค๊ฒฝ๋ณด {f}๊ตฌ๊ฐ„\")\n", "print(\"ํ•œ๊ณ„: ๊ฐœ์ธ๋ณ„ ์•ˆ์ •์‹œ ์‹ฌ๋ฐ•ยท๊ธฐ์ƒยทํ™œ๋™ ๊ฐ•๋„๋ฅผ ๋ฐ˜์˜ํ•˜์ง€ ์•Š์•„ ์šด๋™ ์งํ›„ ์‹ฌ๋ฐ• ์ƒ์Šน๊ณผ ๋ฐ”๋žŒ์— ์˜ํ•œ ์„ผ์„œ ๋ƒ‰๊ฐ์„ ๊ตฌ๋ถ„ํ•˜์ง€ ๋ชปํ•œ๋‹ค -> ๋ฏธ์…˜ 4ยท5์—์„œ ๋ณด์™„ํ•œ๋‹ค.\")\n", "print(\"๋ณธ ๊ฒฐ๊ณผ๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์˜ ๊ฒฝ๋ณด ๋‹จ๊ณ„์ด๋ฉฐ ์ง„๋‹จ์ด ์•„๋‹ˆ๋‹ค. ์‹ค์ œ ์ด์ƒ ์ง•ํ›„๋Š” ์˜๋ฌด๋Œ€ยท๊ตฐ์˜๊ด€์—๊ฒŒ ์—ฐ๊ณ„ํ•œ๋‹ค.\")\n", "\n", "colr = {\"normal\": \"tab:gray\", \"heat\": \"tab:red\", \"cold\": \"tab:blue\"}\n", "plt.figure(figsize=(7, 5))\n", "for k, g in tab.groupby(\"rtype\"):\n", " plt.scatter(g[\"temp5\"], g[\"hr5\"], s=6, alpha=0.4, color=colr[k], label=k)\n", "plt.axvline(TH_HEAT, ls=\"--\", color=\"k\"); plt.axvline(TH_COLD, ls=\"--\", color=\"k\"); plt.axhline(HR_GATE, ls=\":\", color=\"k\")\n", "plt.xlabel(\"temperature (5-min mean, C)\"); plt.ylabel(\"heart rate (5-min mean, bpm)\"); plt.legend(); plt.title(\"Baseline rule on dummy signals\")\n", "plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 03 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[Baseline] ๊ฐ€์ƒ ๋ณ‘์‚ฌ 90๋ช… x 55๋ถ„ = 4,950๊ฐœ ๊ตฌ๊ฐ„, ์ •๋‹ต ๋ถ„ํฌ {'normal': 3536, 'cold': 761, 'heat': 653}\n", "๊ฒฝ๋ณด ์œ ํ˜• ๋ถ„๋ฅ˜ ์ •ํ™•๋„ 86.9% (๋ชฉํ‘œ 75% ์ด์ƒ) -> ๋‹ฌ์„ฑ\n", "\n", "[์œ ํ˜•๋ณ„ ํŒ์ •] (ํ–‰: ์ •๋‹ต, ์—ด: ํŒ์ •)\n", "pred normal heat cold\n", "rtype \n", "normal 3056 246 234\n", "heat 160 493 0\n", "cold 7 0 754\n", "heat: ์žฌํ˜„์œจ 75.5%, ์ •๋ฐ€๋„ 66.7%\n", "cold: ์žฌํ˜„์œจ 99.1%, ์ •๋ฐ€๋„ 76.3%\n", "\n", "[๋‹จ๊ณ„๋ณ„ ์œ„ํ—˜ ๊ตฌ๊ฐ„ ์žฌํ˜„์œจ] (์œ„ํ—˜ ์œ ํ˜•์„ ์œ„ํ—˜์œผ๋กœ ๋งž์ถ˜ ๋น„์œจ)\n", "stage\n", "1 86.8% (1221๊ตฌ๊ฐ„)\n", "2 96.9% (193๊ตฌ๊ฐ„)\n", "\n", "[์ฒด์˜จ-์‹ฌ๋ฐ•์ˆ˜ ์ƒ๊ด€] ์ „์ฒด ์ƒ๊ด€๊ณ„์ˆ˜ 0.48 / ํญ์—ผ ์„ธ์…˜ 0.32 / ํ˜นํ•œ ์„ธ์…˜ 0.41\n", "\n", "[์ž„๊ณ„๊ฐ’ ๋ฏผ๊ฐ๋„] ์˜จ์—ด ์ฒด์˜จ ๊ธฐ์ค€ ๋ณ€ํ™”\n", "๊ธฐ์ค€ 37.7: ์˜จ์—ด ์žฌํ˜„์œจ 75.7%, ์˜ค๊ฒฝ๋ณด 462๊ตฌ๊ฐ„\n", "๊ธฐ์ค€ 38.0: ์˜จ์—ด ์žฌํ˜„์œจ 75.5%, ์˜ค๊ฒฝ๋ณด 246๊ตฌ๊ฐ„\n", "๊ธฐ์ค€ 38.3: ์˜จ์—ด ์žฌํ˜„์œจ 67.7%, ์˜ค๊ฒฝ๋ณด 38๊ตฌ๊ฐ„\n", "๊ธฐ์ค€ 38.6: ์˜จ์—ด ์žฌํ˜„์œจ 31.1%, ์˜ค๊ฒฝ๋ณด 0๊ตฌ๊ฐ„\n", "ํ•œ๊ณ„: ๊ฐœ์ธ๋ณ„ ์•ˆ์ •์‹œ ์‹ฌ๋ฐ•ยท๊ธฐ์ƒยทํ™œ๋™ ๊ฐ•๋„๋ฅผ ๋ฐ˜์˜ํ•˜์ง€ ์•Š์•„ ์šด๋™ ์งํ›„ ์‹ฌ๋ฐ• ์ƒ์Šน๊ณผ ๋ฐ”๋žŒ์— ์˜ํ•œ ์„ผ์„œ ๋ƒ‰๊ฐ์„ ๊ตฌ๋ถ„ํ•˜์ง€ ๋ชปํ•œ๋‹ค -> ๋ฏธ์…˜ 4ยท5์—์„œ ๋ณด์™„ํ•œ๋‹ค.\n", "๋ณธ ๊ฒฐ๊ณผ๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์˜ ๊ฒฝ๋ณด ๋‹จ๊ณ„์ด๋ฉฐ ์ง„๋‹จ์ด ์•„๋‹ˆ๋‹ค. ์‹ค์ œ ์ด์ƒ ์ง•ํ›„๋Š” ์˜๋ฌด๋Œ€ยท๊ตฐ์˜๊ด€์—๊ฒŒ ์—ฐ๊ณ„ํ•œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 04. ๊ธฐ์ƒ ๋ฐ์ดํ„ฐ(์˜จยท์Šต๋„, ํ’์†) ๊ฒฐํ•ฉ ์˜จ์—ด/ํ•œ๋žญ ์ŠคํŠธ๋ ˆ์Šค ๋ณตํ•ฉ ์œ„ํ—˜๋„ ์˜ˆ์ธก ๋ชจ๋ธ ๊ณ ๋„ํ™”\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๊ธฐ์ƒ ๋ฐ์ดํ„ฐ์™€ ๊ฒฐํ•ฉํ•œ ๋ณตํ•ฉ ํ™˜๊ฒฝ ์œ„ํ—˜๋„ ์˜ˆ์ธก ๋ชจ๋ธ์„ ๊ณ ๋„ํ™”ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๊ธฐ์ƒ(์˜จ๋„ยท์Šต๋„ยทํ’์†)๊ณผ ๊ฐœ์ธ ํŠน์„ฑ์„ ๊ฒฐํ•ฉํ•œ ๋ณตํ•ฉ ํ™˜๊ฒฝ ์œ„ํ—˜๋„ ๋ถ„๋ฅ˜ ๋ชจ๋ธ๋กœ ๊ณ ๋„ํ™”ํ•˜๊ณ , ์„ผ์„œ ์ด์ƒยท๊ต๋ž€ ์‹ ํ˜ธ๋ฅผ ์ ๊ฒ€ํ•˜๋Š” ์˜ˆ์™ธ ํƒ์ง€๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ฒด๊ฐ ๋”์œ„ยท์ถ”์œ„ ์ง€์ˆ˜, ์•ˆ์ •์‹œ ์‹ฌ๋ฐ• ๋Œ€๋น„ ์‹ฌ๋ฐ• ์ƒ์Šน, ์ฒด์˜จ ๋ณ€ํ™”์œจ, ๋”์œ„ ์ˆœ์‘ ์—ฌ๋ถ€ ๋“ฑ์„ ํŠน์ง•์œผ๋กœ ๊ทธ๋ž˜๋””์–ธํŠธ ๋ถ€์ŠคํŒ… ๋ชจ๋ธ์„ ํ•™์Šต ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/wel01_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": [ "\"\"\"์ฒด์˜จยท์‹ฌ๋ฐ•์ˆ˜ยทํ”ผ๋ถ€์ „๋„๋„์— ๊ธฐ์ƒ(์˜จ๋„ยท์Šต๋„ยทํ’์†)๊ณผ ๊ฐœ์ธ ํŠน์„ฑ(์•ˆ์ •์‹œ ์‹ฌ๋ฐ•, ์ˆœ์‘ ์—ฌ๋ถ€, ์ฒด๊ฒฉ)์„ ๊ฒฐํ•ฉํ•œ ๋ณตํ•ฉ ํ™˜๊ฒฝ ์œ„ํ—˜๋„ ๋ถ„๋ฅ˜ ๋ชจ๋ธ(๊ทธ๋ž˜๋””์–ธํŠธ ๋ถ€์ŠคํŒ…)๋กœ ๊ณ ๋„ํ™”ํ•˜๊ณ , ์œ„ํ—˜ ๊ตฌ๊ฐ„ ์žฌํ˜„์œจยท์ฒ˜๋ฆฌ ์ง€์—ฐ์„ Baseline๊ณผ ๋น„๊ตํ•œ๋‹ค. ์„ผ์„œ ์ด์ƒ ์˜ˆ์™ธ ํƒ์ง€ 10์ข…๋„ ์‹œํ—˜ํ•œ๋‹ค. ์ง„๋‹จ ์•„๋‹˜ยท์˜๋ฌด๋Œ€ ์—ฐ๊ณ„.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "from sklearn.ensemble import GradientBoostingClassifier\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋”๋ฏธ ์ƒ์ฒด์‹ ํ˜ธ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋ณ‘ยท๋ถ€๋Œ€ยท์˜๋ฌด ๊ธฐ๋ก ์—†์Œ) ----------------\n", "# ๊ฐ€์ƒ ๋ณ‘์‚ฌ(๊ฐ€๋ช… ID)๊ฐ€ 60๋ถ„๊ฐ„ ๊ฑท๊ธฐยทํœด์‹ยท๊ณ ๊ฐ•๋„ ํ™œ๋™์„ ํ•˜๋Š” ๋™์•ˆ์˜ ์‹ฌ๋ฐ•์ˆ˜ยท์ฒด์˜จ(์ถ”์ • ์‹ฌ๋ถ€)ยทํ”ผ๋ถ€์ „๋„๋„๋ฅผ 1๋ถ„ ๊ฐ„๊ฒฉ์œผ๋กœ ๋งŒ๋“ ๋‹ค.\n", "# ์ง„๋‹จ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ์ƒํƒœ ๋ผ๋ฒจ์€ ๊ฐ€์ƒ ์ƒ๋ฆฌ ๋ชจ๋ธ์˜ '๊ต์œก์šฉ ๋‹จ๊ณ„'์ผ ๋ฟ์ด๋‹ค.\n", "N_MIN = 60\n", "STAGE = {0: \"์ •์ƒ\", 1: \"์ฃผ์˜\", 2: \"๊ฒฝ๋ณด\"}\n", "TYPES = [\"normal\", \"heat\", \"cold\"]\n", "\n", "\n", "def make_person(i, rng):\n", " return {\"id\": f\"S{i:03d}\", \"rest_hr\": float(rng.normal(64, 7)), \"age\": int(rng.integers(19, 27)), \"bmi\": float(rng.normal(23.5, 2.5)),\n", " \"acclim\": int(rng.random() < 0.4), \"fit\": float(np.clip(rng.normal(0.5, 0.2), 0.05, 0.95))}\n", "\n", "\n", "def make_env(kind, rng):\n", " if kind == \"heat\":\n", " return {\"temp_c\": float(rng.uniform(30, 40)), \"rh\": float(rng.uniform(45, 85)), \"wind\": float(rng.uniform(0.3, 3))}\n", " if kind == \"cold\":\n", " return {\"temp_c\": float(rng.uniform(-22, 2)), \"rh\": float(rng.uniform(40, 80)), \"wind\": float(rng.uniform(2, 11))}\n", " return {\"temp_c\": float(rng.uniform(8, 24)), \"rh\": float(rng.uniform(35, 70)), \"wind\": float(rng.uniform(0.3, 5))}\n", "\n", "\n", "def make_session(sid, kind, rng, p=None, glitch=True):\n", " \"\"\"๋ฐ˜ํ™˜: dict(person, env, kind, df[hr,temp,eda,act + ์ •๋‹ต coreยทstageยทrtype]). ์„ผ์„œ ๊ฒฐ์ธกยทํАยท๊ณ ์ฐฉ ํฌํ•จ.\"\"\"\n", " p = p or make_person(sid, rng)\n", " env = make_env(kind, rng)\n", " act = np.zeros(N_MIN); t = 0\n", " while t < N_MIN: # 10~20๋ถ„ ๋‹จ์œ„ ํ™œ๋™ ๋ธ”๋ก: ํœด์‹ 1.5 / ํ–‰๊ตฐ 4.5 / ๊ณ ๊ฐ•๋„ 7 MET\n", " d = int(rng.integers(10, 21)); act[t:t + d] = rng.choice([1.5, 4.5, 7.0], p=[0.25, 0.5, 0.25]); t += d\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"]\n", " core, cores = 36.9, []\n", " for m in range(N_MIN):\n", " heat = 0.085 * max(0, hi - 24) * (1 - 0.25 * p[\"acclim\"]) * (1 + 0.5 * m / N_MIN) * (1 + 0.012 * (p[\"bmi\"] - 23.5))\n", " cold = -0.075 * max(0, 8 - wc) * (1 - 0.09 * act[m])\n", " ss = 36.9 + 0.09 * act[m] + heat + cold\n", " core += (ss - core) / 18 + rng.normal(0, 0.03)\n", " cores.append(core)\n", " core = np.array(cores)\n", " hr = p[\"rest_hr\"] + 9.5 * (act - 1.5) * (1.15 - 0.3 * p[\"fit\"]) + 11 * (core - 36.9) + rng.normal(0, 3.2, N_MIN)\n", " eda = 2.0 + 0.25 * act + 3.2 * np.maximum(core - 37.2, 0) - 0.04 * np.maximum(8 - wc, 0) + rng.normal(0, 0.25, N_MIN)\n", " bias = rng.normal(0, 0.1)\n", " temp = core + bias + rng.normal(0, 0.1, N_MIN) - np.where((kind == \"cold\") & (env[\"wind\"] > 7), 0.25, 0.0) # ์„ผ์„œ ์ ‘์ด‰ ๋ถ€์œ„ ๋ฐ”๋žŒ ๋ƒ‰๊ฐ ์˜ค์ฐจ\n", " df = pd.DataFrame({\"min\": np.arange(N_MIN), \"hr\": hr, \"temp\": temp, \"eda\": np.maximum(eda, 0.2), \"act\": act})\n", " if glitch:\n", " for col in (\"hr\", \"temp\", \"eda\"):\n", " df.loc[rng.random(N_MIN) < 0.03, col] = np.nan # ๊ฒฐ์ธก\n", " s = int(rng.integers(5, N_MIN))\n", " if rng.random() < 0.12:\n", " df.loc[s, \"hr\"] = 230.0 # ์„ผ์„œ ํА\n", " if rng.random() < 0.12:\n", " df.loc[s, \"temp\"] = 45.0\n", " ht = np.where(core >= 39.0, 2, np.where(core >= 38.3, 1, 0)); cd = np.where(core <= 35.4, 2, np.where(core <= 36.2, 1, 0))\n", " df[\"core\"] = core\n", " df[\"stage\"] = np.maximum(ht, cd); df[\"rtype\"] = np.where(ht > 0, \"heat\", np.where(cd > 0, \"cold\", \"normal\"))\n", " return {\"id\": p[\"id\"], \"person\": p, \"env\": env, \"kind\": kind, \"df\": df}\n", "\n", "\n", "def make_dataset(n, seed):\n", " rng = np.random.default_rng(seed)\n", " kinds = [\"normal\", \"heat\", \"cold\"]\n", " return [make_session(i, kinds[i % 3], rng) for i in range(n)]\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---------------- ์ „์ฒ˜๋ฆฌยทํŠน์ง• ์ถ”์ถœ ๊ณตํ†ต ํ•จ์ˆ˜ ----------------\n", "LIMITS = {\"hr\": (30, 220), \"temp\": (30.0, 43.0), \"eda\": (0.0, 40.0)} # ์ƒ๋ฆฌ์ ์œผ๋กœ ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„(๋ฒ”์œ„ ๋ฐ– = ์„ผ์„œ ์˜ค๋ฅ˜)\n", "\n", "\n", "def clean(df):\n", " \"\"\"๋ฒ”์œ„ ๋ฐ– ๊ฐ’์€ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ -> ์„ ํ˜• ๋ณด๊ฐ„(์–‘ ๋์€ ๊ฐ€๊นŒ์šด ๊ฐ’) -> 3๋ถ„ ์ด๋™ ์ค‘์•™๊ฐ’์œผ๋กœ ํА ์ œ๊ฑฐ. ๋ฐ˜ํ™˜: (์ •์ œ df, ๋ณด์ • ๊ฑด์ˆ˜)\"\"\"\n", " out = df.copy(); fixed = 0\n", " for c, (lo, hi) in LIMITS.items():\n", " bad = out[c].isna() | (out[c] < lo) | (out[c] > hi)\n", " fixed += int(bad.sum())\n", " out.loc[bad, c] = np.nan\n", " out[c] = out[c].interpolate(limit_direction=\"both\").rolling(3, center=True, min_periods=1).median()\n", " return out, fixed\n", "\n", "\n", "def env_index(env):\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0.0) # ์ฒด๊ฐ ๋”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"] # ์ฒด๊ฐ ์ถ”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " return hi, wc\n", "\n", "\n", "def features(s, use_env=True, personal=True):\n", " \"\"\"์„ธ์…˜ 1๊ฐœ -> ๋ถ„ ๋‹จ์œ„ ํŠน์ง•ํ‘œ(์ตœ๊ทผ 5๋ถ„ ํ‰๊ท ยท๊ธฐ์šธ๊ธฐ ํฌํ•จ). ์ •๋‹ต stage/rtype ํฌํ•จ.\"\"\"\n", " df, _ = clean(s[\"df\"]); p, e = s[\"person\"], s[\"env\"]\n", " f = pd.DataFrame({\"sid\": s[\"id\"], \"min\": df[\"min\"]})\n", " f[\"temp5\"] = df[\"temp\"].rolling(5, min_periods=1).mean()\n", " f[\"hr5\"] = df[\"hr\"].rolling(5, min_periods=1).mean()\n", " f[\"eda5\"] = df[\"eda\"].rolling(5, min_periods=1).mean()\n", " f[\"dtemp\"] = (f[\"temp5\"] - f[\"temp5\"].shift(4)).fillna(0)\n", " f[\"act\"] = df[\"act\"]\n", " if personal:\n", " f[\"hr_rel\"] = f[\"hr5\"] - p[\"rest_hr\"]\n", " f[\"acclim\"], f[\"bmi\"], f[\"fit\"] = p[\"acclim\"], p[\"bmi\"], p[\"fit\"]\n", " if use_env:\n", " f[\"hi\"], f[\"wc\"] = env_index(e); f[\"wind\"] = e[\"wind\"]\n", " f[\"stage\"], f[\"rtype\"], f[\"kind\"] = df[\"stage\"], df[\"rtype\"], s[\"kind\"]\n", " return f[f[\"min\"] >= 5].reset_index(drop=True)\n", "\n", "\n", "def table(sessions, **kw):\n", " return pd.concat([features(s, **kw) for s in sessions], ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "\n", "ADV_FEATS = [\"temp5\", \"hr5\", \"eda5\", \"dtemp\", \"act\", \"hr_rel\", \"acclim\", \"bmi\", \"fit\", \"hi\", \"wc\", \"wind\"]\n", "LABEL = {\"normal\": 0, \"heat\": 1, \"cold\": 2}\n", "\n", "\n", "def baseline_pred(t):\n", " return np.where((t[\"temp5\"] >= 38.0) & (t[\"hr5\"] >= 95), \"heat\", np.where(t[\"temp5\"] <= 36.3, \"cold\", \"normal\"))\n", "\n", "\n", "train = table(make_dataset(150, seed=31))\n", "clf = GradientBoostingClassifier(n_estimators=80, max_depth=3, learning_rate=0.1, random_state=0).fit(train[ADV_FEATS], train[\"rtype\"].map(LABEL))\n", "inv = {v: k for k, v in LABEL.items()}\n", "\n", "# ์‹œํ—˜์…‹ 2์ข…: ์ผ๋ฐ˜ / ๋ณตํ•ฉ ๊ต๋ž€(๊ฐ•ํ’ ํ˜นํ•œ + ๊ณ ๊ฐ•๋„ ํ™œ๋™ + ์„ผ์„œ ํА ๋งŽ์Œ)\n", "std = make_dataset(90, seed=77)\n", "rng = np.random.default_rng(9)\n", "hard = []\n", "for i in range(60):\n", " s = make_session(i, [\"heat\", \"cold\"][i % 2], rng)\n", " if s[\"kind\"] == \"cold\":\n", " s[\"env\"][\"wind\"] = float(rng.uniform(7, 11))\n", " for col, val in ((\"hr\", 230.0), (\"temp\", 45.0)):\n", " idx = rng.choice(np.arange(5, N_MIN), 3, replace=False); s[\"df\"].loc[idx, col] = val\n", " hard.append(s)\n", "\n", "rows = []\n", "for name, sess in ((\"์ผ๋ฐ˜\", std), (\"๋ณตํ•ฉ ๊ต๋ž€\", hard)):\n", " t = table(sess)\n", " pb = baseline_pred(t)\n", " t0 = time.perf_counter(); pa = np.array([inv[v] for v in clf.predict(t[ADV_FEATS])]); ms = (time.perf_counter() - t0) * 1000 / len(t)\n", " risk = t[\"stage\"] >= 1; danger = t[\"stage\"] == 2\n", " rows.append({\"์‹œํ—˜์…‹\": name, \"๊ตฌ๊ฐ„ ์ˆ˜\": len(t), \"Baseline ์œ„ํ—˜ ์žฌํ˜„์œจ\": round(float((pb[risk] == t[\"rtype\"][risk]).mean()), 3),\n", " \"๊ณ ๋„ํ™” ์œ„ํ—˜ ์žฌํ˜„์œจ\": round(float((pa[risk] == t[\"rtype\"][risk]).mean()), 3),\n", " \"๊ณ ๋„ํ™” ๊ฒฝ๋ณด(2๋‹จ๊ณ„) ์žฌํ˜„์œจ\": round(float((pa[danger] == t[\"rtype\"][danger]).mean()), 3),\n", " \"Baseline ์˜ค๊ฒฝ๋ณด\": int(((pb != \"normal\") & ~risk).sum()), \"๊ณ ๋„ํ™” ์˜ค๊ฒฝ๋ณด\": int(((pa != \"normal\") & ~risk).sum()), \"์ง€์—ฐ(ms/๊ตฌ๊ฐ„)\": round(ms, 3)})\n", "out = pd.DataFrame(rows)\n", "print(out.T.to_string(header=False))\n", "rec = out[\"๊ณ ๋„ํ™” ์œ„ํ—˜ ์žฌํ˜„์œจ\"].min(); lat = out[\"์ง€์—ฐ(ms/๊ตฌ๊ฐ„)\"].max()\n", "imp = pd.Series(clf.feature_importances_, index=ADV_FEATS).sort_values(ascending=False)\n", "print(\"\\nํŠน์ง• ์ค‘์š”๋„ ์ƒ์œ„:\", \", \".join(f\"{k} {v:.2f}\" for k, v in imp.head(5).items()))\n", "print(f\"\\n๋ณตํ•ฉ ์œ„ํ˜‘ ํƒ์ง€ ์žฌํ˜„์œจ(์ตœ์ € ์‹œํ—˜์…‹) {rec:.1%} (๋ชฉํ‘œ 85% ์ด์ƒ) -> {'๋‹ฌ์„ฑ' if rec >= 0.85 else '๋ฏธ๋‹ฌ'}\")\n", "print(f\"์ฒ˜๋ฆฌ ์ง€์—ฐ ์ตœ๋Œ€ {lat:.3f} ms/๊ตฌ๊ฐ„ (๋ชฉํ‘œ 200 ms ์ดํ•˜) -> {'๋‹ฌ์„ฑ' if lat <= 200 else '๋ฏธ๋‹ฌ'}\")\n", "\n", "\n", "# ---- ์˜ˆ์™ธ(์„ผ์„œ ์ด์ƒยท๊ธฐ๋งŒ) ํƒ์ง€ 10์ข…: ๋ชจ๋ธ์— ๋„ฃ๊ธฐ ์ „ ์‹ ํ˜ธ ์ž์ฒด์˜ ์‹ ๋ขฐ์„ฑ์„ ์ ๊ฒ€ํ•œ๋‹ค ----\n", "def anomaly(w):\n", " \"\"\"์ตœ๊ทผ 10๋ถ„ ์›์‹œ ์‹ ํ˜ธ ์ฐฝ(hr, temp, eda ๋ฐฐ์—ด dict)์˜ ์ด์ƒ ์œ ํ˜• ๋ชฉ๋ก\"\"\"\n", " flags = []\n", " for c in (\"hr\", \"temp\", \"eda\"):\n", " v = w[c]\n", " if np.isnan(v).all():\n", " flags.append(f\"{c}_missing_all\")\n", " else:\n", " lo, hi = LIMITS[c]\n", " if ((v < lo) | (v > hi)).any():\n", " flags.append(f\"{c}_out_of_range\")\n", " if np.isnan(v).mean() > 0.3:\n", " flags.append(f\"{c}_many_missing\")\n", " ok = v[~np.isnan(v)]\n", " if len(ok) >= 8 and np.ptp(ok) < 1e-6:\n", " flags.append(f\"{c}_flatline\")\n", " if len(ok) >= 3 and np.abs(np.diff(ok)).max() > {\"hr\": 60, \"temp\": 1.5, \"eda\": 10}[c]:\n", " flags.append(f\"{c}_jump\")\n", " if \"temp_out_of_range\" not in flags and \"hr_out_of_range\" not in flags and not np.isnan(w[\"temp\"]).all() and not np.isnan(w[\"hr\"]).all():\n", " if np.nanmean(w[\"temp\"]) >= 38.5 and np.nanmean(w[\"hr\"]) < 60:\n", " flags.append(\"temp_hr_inconsistent\")\n", " return flags\n", "\n", "\n", "def win(hr=80, temp=37.0, eda=2.0, n=10):\n", " return {\"hr\": np.full(n, float(hr)) + np.arange(n) % 3, \"temp\": np.full(n, float(temp)) + np.arange(n) * 0.01, \"eda\": np.full(n, float(eda)) + np.arange(n) % 2 * 0.1}\n", "\n", "\n", "cases = []\n", "w = win(); cases.append((\"1 ์ •์ƒ\", w, []))\n", "w = win(); w[\"hr\"][4] = 230; cases.append((\"2 ์‹ฌ๋ฐ• ํА\", w, [\"hr_out_of_range\", \"hr_jump\"]))\n", "w = win(); w[\"temp\"][:] = 37.0; cases.append((\"3 ์ฒด์˜จ ๊ณ ์ฐฉ\", w, [\"temp_flatline\"]))\n", "w = win(); w[\"temp\"][:6] = np.nan; cases.append((\"4 ์ฒด์˜จ ๊ฒฐ์ธก ๋‹ค์ˆ˜\", w, [\"temp_many_missing\"]))\n", "w = win(); w[\"hr\"][:] = np.nan; cases.append((\"5 ์‹ฌ๋ฐ• ์ „์ฒด ๋‘์ ˆ\", w, [\"hr_missing_all\"]))\n", "w = win(); w[\"temp\"][3] = 45.0; cases.append((\"6 ์ฒด์˜จ ๋ฒ”์œ„ ์ดํƒˆ\", w, [\"temp_out_of_range\", \"temp_jump\"]))\n", "w = win(temp=39.0, hr=55); cases.append((\"7 ์ฒด์˜จ-์‹ฌ๋ฐ• ๋ถˆ์ผ์น˜\", w, [\"temp_hr_inconsistent\"]))\n", "w = win(); w[\"eda\"][:] = 2.0; cases.append((\"8 ํ”ผ๋ถ€์ „๋„ ๊ณ ์ฐฉ\", w, [\"eda_flatline\"]))\n", "w = win(); w[\"temp\"][5:] += 2.0; cases.append((\"9 ์ฒด์˜จ ๊ธ‰๋ณ€\", w, [\"temp_jump\"]))\n", "w = win(); w[\"hr\"][:] = 80.0; w[\"temp\"][:] = 37.0; cases.append((\"10 ์‹ฌ๋ฐ•ยท์ฒด์˜จ ๋™์‹œ ๊ณ ์ฐฉ\", w, [\"hr_flatline\", \"temp_flatline\"]))\n", "hit = 0\n", "print(\"\\n[์„ผ์„œ ์ด์ƒ ํƒ์ง€ ์‹œํ—˜ 10์ข…]\")\n", "for name, w, expect in cases:\n", " got = anomaly(w); good = all(e in got for e in expect) and (bool(got) == bool(expect)); hit += good\n", " print(f\"[{'PASS' if good else 'FAIL'}] {name}: {', '.join(got) or '์ด์ƒ ์—†์Œ'}\")\n", "print(f\"ํ†ต๊ณผ {hit}/10. ์ด์ƒ ํ”Œ๋ž˜๊ทธ๊ฐ€ ์žˆ์œผ๋ฉด ๋ชจ๋ธ ๊ฒฝ๋ณด๋ณด๋‹ค ๋จผ์ € '์„ผ์„œ ์ ๊ฒ€ ํ•„์š”'๋ฅผ ํ‘œ์‹œํ•˜๊ณ , ํŒ๋‹จ์€ ์†Œ๋Œ€์žฅยท์˜๋ฌด๊ด€ ํ™•์ธ์œผ๋กœ ๋„˜๊ธด๋‹ค(๊ธฐ๋งŒยท๊ต๋ž€ ์‹ ํ˜ธ๋กœ ์ธํ•œ ์˜คํŒ ๋ฐฉ์ง€).\")\n", "print(\"๋ณธ ๊ฒฐ๊ณผ๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ ๊ธฐ์ค€์ด๋ฉฐ ์ง„๋‹จ์ด ์•„๋‹ˆ๋‹ค. ์‹ค์ œ ์ด์ƒ ์ง•ํ›„๋Š” ์˜๋ฌด๋Œ€ยท๊ตฐ์˜๊ด€์—๊ฒŒ ์—ฐ๊ณ„ํ•œ๋‹ค.\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 04 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "์‹œํ—˜์…‹ ์ผ๋ฐ˜ ๋ณตํ•ฉ ๊ต๋ž€\n", "๊ตฌ๊ฐ„ ์ˆ˜ 4950 3300\n", "Baseline ์œ„ํ—˜ ์žฌํ˜„์œจ 0.879 0.916\n", "๊ณ ๋„ํ™” ์œ„ํ—˜ ์žฌํ˜„์œจ 0.929 0.934\n", "๊ณ ๋„ํ™” ๊ฒฝ๋ณด(2๋‹จ๊ณ„) ์žฌํ˜„์œจ 1.0 1.0\n", "Baseline ์˜ค๊ฒฝ๋ณด 446 422\n", "๊ณ ๋„ํ™” ์˜ค๊ฒฝ๋ณด 108 98\n", "์ง€์—ฐ(ms/๊ตฌ๊ฐ„) 0.004 0.004\n", "\n", "ํŠน์ง• ์ค‘์š”๋„ ์ƒ์œ„: temp5 0.48, eda5 0.42, hr_rel 0.05, hr5 0.01, fit 0.01\n", "\n", "๋ณตํ•ฉ ์œ„ํ˜‘ ํƒ์ง€ ์žฌํ˜„์œจ(์ตœ์ € ์‹œํ—˜์…‹) 92.9% (๋ชฉํ‘œ 85% ์ด์ƒ) -> ๋‹ฌ์„ฑ\n", "์ฒ˜๋ฆฌ ์ง€์—ฐ ์ตœ๋Œ€ 0.004 ms/๊ตฌ๊ฐ„ (๋ชฉํ‘œ 200 ms ์ดํ•˜) -> ๋‹ฌ์„ฑ\n", "\n", "[์„ผ์„œ ์ด์ƒ ํƒ์ง€ ์‹œํ—˜ 10์ข…]\n", "[PASS] 1 ์ •์ƒ: ์ด์ƒ ์—†์Œ\n", "[PASS] 2 ์‹ฌ๋ฐ• ํА: hr_out_of_range, hr_jump\n", "[PASS] 3 ์ฒด์˜จ ๊ณ ์ฐฉ: temp_flatline\n", "[PASS] 4 ์ฒด์˜จ ๊ฒฐ์ธก ๋‹ค์ˆ˜: temp_many_missing\n", "[PASS] 5 ์‹ฌ๋ฐ• ์ „์ฒด ๋‘์ ˆ: hr_missing_all\n", "[PASS] 6 ์ฒด์˜จ ๋ฒ”์œ„ ์ดํƒˆ: temp_out_of_range, temp_jump\n", "[PASS] 7 ์ฒด์˜จ-์‹ฌ๋ฐ• ๋ถˆ์ผ์น˜: temp_hr_inconsistent\n", "[PASS] 8 ํ”ผ๋ถ€์ „๋„ ๊ณ ์ฐฉ: eda_flatline\n", "[PASS] 9 ์ฒด์˜จ ๊ธ‰๋ณ€: temp_jump\n", "[PASS] 10 ์‹ฌ๋ฐ•ยท์ฒด์˜จ ๋™์‹œ ๊ณ ์ฐฉ: hr_flatline, temp_flatline\n", "ํ†ต๊ณผ 10/10. ์ด์ƒ ํ”Œ๋ž˜๊ทธ๊ฐ€ ์žˆ์œผ๋ฉด ๋ชจ๋ธ ๊ฒฝ๋ณด๋ณด๋‹ค ๋จผ์ € '์„ผ์„œ ์ ๊ฒ€ ํ•„์š”'๋ฅผ ํ‘œ์‹œํ•˜๊ณ , ํŒ๋‹จ์€ ์†Œ๋Œ€์žฅยท์˜๋ฌด๊ด€ ํ™•์ธ์œผ๋กœ ๋„˜๊ธด๋‹ค(๊ธฐ๋งŒยท๊ต๋ž€ ์‹ ํ˜ธ๋กœ ์ธํ•œ ์˜คํŒ ๋ฐฉ์ง€).\n", "๋ณธ ๊ฒฐ๊ณผ๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ ๊ธฐ์ค€์ด๋ฉฐ ์ง„๋‹จ์ด ์•„๋‹ˆ๋‹ค. ์‹ค์ œ ์ด์ƒ ์ง•ํ›„๋Š” ์˜๋ฌด๋Œ€ยท๊ตฐ์˜๊ด€์—๊ฒŒ ์—ฐ๊ณ„ํ•œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 05. ์ „ํˆฌ์› ์‹ ์ฒด ์กฐ๊ฑด๋ณ„ ๊ฐœ์ธํ™” ์œ„ํ—˜ ์ง€์ˆ˜ ์‚ฐ์ถœ ๋ฐ ์˜ค๊ฒฝ๋ณด ๋ฐฉ์ง€ ์‹ ๋ขฐ๋„ ์—”์ง„ ๊ฐœ๋ฐœ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๊ฐœ์ธํ™” ์œ„ํ—˜ ์ง€์ˆ˜ ์‚ฐ์ถœ ๋ฐ ์˜ค๊ฒฝ๋ณด ๋ฐฉ์ง€ ์‹ ๋ขฐ๋„ ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ณ‘์‚ฌ ๊ฐœ์ธ์˜ ์ฒด๋ ฅยท์ˆœ์‘ ์—ฌ๋ถ€๋ฅผ ๋ฐ˜์˜ํ•œ ๊ฐœ์ธํ™” ์œ„ํ—˜ ์ง€์ˆ˜๋ฅผ ์‚ฐ์ถœํ•˜๊ณ  ์˜ค๊ฒฝ๋ณด๋ฅผ ์ค„์ด๋Š” ์‹ ๋ขฐ๋„ ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **ํ™œ๋™ ๊ฐ•๋„์™€ ๊ฐœ์ธ ์ฒด๋ ฅ์œผ๋กœ ์˜ˆ์ƒ๋˜๋Š” ์‹ฌ๋ฐ•์„ ๋นผ '์„ค๋ช…๋˜์ง€ ์•Š๋Š” ์‹ฌ๋ฐ• ์ƒ์Šน'๊ณผ ์ฒด์˜จ ํŽธ์ฐจ๋ฅผ ๋งŒ๋“ค๊ณ , ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€๋กœ ์œ„ํ—˜ ํ™•๋ฅ ์„ ๊ณ„์‚ฐํ•ด 0.8 ์ด์ƒ High, 0.5 ์ด์ƒ Medium, ๊ทธ ๋ฏธ๋งŒ Low ๋“ฑ๊ธ‰์„ ๋ถ€์—ฌ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/verification/wel01_confidence_engine.py` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์‹ ๋ขฐ๋„ ๋“ฑ๊ธ‰(High/Med/Low) ๋ถ€์—ฌ ๊ธฐ์ค€์˜ ๊ตฐ์‚ฌ์  ํƒ€๋‹น์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์˜ค๊ฒฝ๋ณด์œจ(FAR) 40% ์ด์ƒ ๊ฐ์ถ•, ์‹ ๋ขฐ๋„ ์Šค์ฝ”์–ด ์‚ฐ์ถœ ์ผ๊ด€์„ฑ 90% ์ด์ƒ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์˜ค๊ฒฝ๋ณด ํ•„ํ„ฐ๋ง ์ „ํ›„ ๋น„๊ต ์ง€ํ‘œ ์‚ฐ์ถœ ๋ฐ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์™„๋ฃŒ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ROC-AUC ๊ณก์„  ๋ถ„์„ ๋ฐ ์ž„๊ณ„๊ฐ’ ๋ฏผ๊ฐ๋„ ํ…Œ์ŠคํŠธ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๊ฐœ์ธ๋ณ„ ์•ˆ์ •์‹œ ์‹ฌ๋ฐ•ยท์ฒด๊ฒฉยท์ˆœ์‘ ์—ฌ๋ถ€๋ฅผ ๋ฐ˜์˜ํ•ด ํ™œ๋™ ๊ฐ•๋„๋กœ ์„ค๋ช…๋˜๋Š” ์‹ฌ๋ฐ• ์ƒ์Šน์„ ๋ณด์ •ํ•œ ๊ฐœ์ธํ™” ์œ„ํ—˜ ์ง€์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ , ์˜ค๊ฒฝ๋ณด๋ฅผ ์ค„์ด๋Š” ์‹ ๋ขฐ๋„ ์ ์ˆ˜์™€ ๋“ฑ๊ธ‰(High/Medium/Low)์„ ์‚ฐ์ถœํ•ด ๊ณ ์ • ์ž„๊ณ„๊ฐ’ ๋ฐฉ์‹ ๋Œ€๋น„ ์˜ค๊ฒฝ๋ณด์œจ(FAR) ๊ฐ์ถ•๊ณผ ์ ์ˆ˜ ์ผ๊ด€์„ฑ์„ ๊ฒ€์ฆํ•œ๋‹ค. ์ง„๋‹จ ์•„๋‹˜ยท์˜๋ฌด๋Œ€ ์—ฐ๊ณ„.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.metrics import roc_auc_score, roc_curve\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋”๋ฏธ ์ƒ์ฒด์‹ ํ˜ธ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋ณ‘ยท๋ถ€๋Œ€ยท์˜๋ฌด ๊ธฐ๋ก ์—†์Œ) ----------------\n", "# ๊ฐ€์ƒ ๋ณ‘์‚ฌ(๊ฐ€๋ช… ID)๊ฐ€ 60๋ถ„๊ฐ„ ๊ฑท๊ธฐยทํœด์‹ยท๊ณ ๊ฐ•๋„ ํ™œ๋™์„ ํ•˜๋Š” ๋™์•ˆ์˜ ์‹ฌ๋ฐ•์ˆ˜ยท์ฒด์˜จ(์ถ”์ • ์‹ฌ๋ถ€)ยทํ”ผ๋ถ€์ „๋„๋„๋ฅผ 1๋ถ„ ๊ฐ„๊ฒฉ์œผ๋กœ ๋งŒ๋“ ๋‹ค.\n", "# ์ง„๋‹จ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ์ƒํƒœ ๋ผ๋ฒจ์€ ๊ฐ€์ƒ ์ƒ๋ฆฌ ๋ชจ๋ธ์˜ '๊ต์œก์šฉ ๋‹จ๊ณ„'์ผ ๋ฟ์ด๋‹ค.\n", "N_MIN = 60\n", "STAGE = {0: \"์ •์ƒ\", 1: \"์ฃผ์˜\", 2: \"๊ฒฝ๋ณด\"}\n", "TYPES = [\"normal\", \"heat\", \"cold\"]\n", "\n", "\n", "def make_person(i, rng):\n", " return {\"id\": f\"S{i:03d}\", \"rest_hr\": float(rng.normal(64, 7)), \"age\": int(rng.integers(19, 27)), \"bmi\": float(rng.normal(23.5, 2.5)),\n", " \"acclim\": int(rng.random() < 0.4), \"fit\": float(np.clip(rng.normal(0.5, 0.2), 0.05, 0.95))}\n", "\n", "\n", "def make_env(kind, rng):\n", " if kind == \"heat\":\n", " return {\"temp_c\": float(rng.uniform(30, 40)), \"rh\": float(rng.uniform(45, 85)), \"wind\": float(rng.uniform(0.3, 3))}\n", " if kind == \"cold\":\n", " return {\"temp_c\": float(rng.uniform(-22, 2)), \"rh\": float(rng.uniform(40, 80)), \"wind\": float(rng.uniform(2, 11))}\n", " return {\"temp_c\": float(rng.uniform(8, 24)), \"rh\": float(rng.uniform(35, 70)), \"wind\": float(rng.uniform(0.3, 5))}\n", "\n", "\n", "def make_session(sid, kind, rng, p=None, glitch=True):\n", " \"\"\"๋ฐ˜ํ™˜: dict(person, env, kind, df[hr,temp,eda,act + ์ •๋‹ต coreยทstageยทrtype]). ์„ผ์„œ ๊ฒฐ์ธกยทํАยท๊ณ ์ฐฉ ํฌํ•จ.\"\"\"\n", " p = p or make_person(sid, rng)\n", " env = make_env(kind, rng)\n", " act = np.zeros(N_MIN); t = 0\n", " while t < N_MIN: # 10~20๋ถ„ ๋‹จ์œ„ ํ™œ๋™ ๋ธ”๋ก: ํœด์‹ 1.5 / ํ–‰๊ตฐ 4.5 / ๊ณ ๊ฐ•๋„ 7 MET\n", " d = int(rng.integers(10, 21)); act[t:t + d] = rng.choice([1.5, 4.5, 7.0], p=[0.25, 0.5, 0.25]); t += d\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"]\n", " core, cores = 36.9, []\n", " for m in range(N_MIN):\n", " heat = 0.085 * max(0, hi - 24) * (1 - 0.25 * p[\"acclim\"]) * (1 + 0.5 * m / N_MIN) * (1 + 0.012 * (p[\"bmi\"] - 23.5))\n", " cold = -0.075 * max(0, 8 - wc) * (1 - 0.09 * act[m])\n", " ss = 36.9 + 0.09 * act[m] + heat + cold\n", " core += (ss - core) / 18 + rng.normal(0, 0.03)\n", " cores.append(core)\n", " core = np.array(cores)\n", " hr = p[\"rest_hr\"] + 9.5 * (act - 1.5) * (1.15 - 0.3 * p[\"fit\"]) + 11 * (core - 36.9) + rng.normal(0, 3.2, N_MIN)\n", " eda = 2.0 + 0.25 * act + 3.2 * np.maximum(core - 37.2, 0) - 0.04 * np.maximum(8 - wc, 0) + rng.normal(0, 0.25, N_MIN)\n", " bias = rng.normal(0, 0.1)\n", " temp = core + bias + rng.normal(0, 0.1, N_MIN) - np.where((kind == \"cold\") & (env[\"wind\"] > 7), 0.25, 0.0) # ์„ผ์„œ ์ ‘์ด‰ ๋ถ€์œ„ ๋ฐ”๋žŒ ๋ƒ‰๊ฐ ์˜ค์ฐจ\n", " df = pd.DataFrame({\"min\": np.arange(N_MIN), \"hr\": hr, \"temp\": temp, \"eda\": np.maximum(eda, 0.2), \"act\": act})\n", " if glitch:\n", " for col in (\"hr\", \"temp\", \"eda\"):\n", " df.loc[rng.random(N_MIN) < 0.03, col] = np.nan # ๊ฒฐ์ธก\n", " s = int(rng.integers(5, N_MIN))\n", " if rng.random() < 0.12:\n", " df.loc[s, \"hr\"] = 230.0 # ์„ผ์„œ ํА\n", " if rng.random() < 0.12:\n", " df.loc[s, \"temp\"] = 45.0\n", " ht = np.where(core >= 39.0, 2, np.where(core >= 38.3, 1, 0)); cd = np.where(core <= 35.4, 2, np.where(core <= 36.2, 1, 0))\n", " df[\"core\"] = core\n", " df[\"stage\"] = np.maximum(ht, cd); df[\"rtype\"] = np.where(ht > 0, \"heat\", np.where(cd > 0, \"cold\", \"normal\"))\n", " return {\"id\": p[\"id\"], \"person\": p, \"env\": env, \"kind\": kind, \"df\": df}\n", "\n", "\n", "def make_dataset(n, seed):\n", " rng = np.random.default_rng(seed)\n", " kinds = [\"normal\", \"heat\", \"cold\"]\n", " return [make_session(i, kinds[i % 3], rng) for i in range(n)]\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---------------- ์ „์ฒ˜๋ฆฌยทํŠน์ง• ์ถ”์ถœ ๊ณตํ†ต ํ•จ์ˆ˜ ----------------\n", "LIMITS = {\"hr\": (30, 220), \"temp\": (30.0, 43.0), \"eda\": (0.0, 40.0)} # ์ƒ๋ฆฌ์ ์œผ๋กœ ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„(๋ฒ”์œ„ ๋ฐ– = ์„ผ์„œ ์˜ค๋ฅ˜)\n", "\n", "\n", "def clean(df):\n", " \"\"\"๋ฒ”์œ„ ๋ฐ– ๊ฐ’์€ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ -> ์„ ํ˜• ๋ณด๊ฐ„(์–‘ ๋์€ ๊ฐ€๊นŒ์šด ๊ฐ’) -> 3๋ถ„ ์ด๋™ ์ค‘์•™๊ฐ’์œผ๋กœ ํА ์ œ๊ฑฐ. ๋ฐ˜ํ™˜: (์ •์ œ df, ๋ณด์ • ๊ฑด์ˆ˜)\"\"\"\n", " out = df.copy(); fixed = 0\n", " for c, (lo, hi) in LIMITS.items():\n", " bad = out[c].isna() | (out[c] < lo) | (out[c] > hi)\n", " fixed += int(bad.sum())\n", " out.loc[bad, c] = np.nan\n", " out[c] = out[c].interpolate(limit_direction=\"both\").rolling(3, center=True, min_periods=1).median()\n", " return out, fixed\n", "\n", "\n", "def env_index(env):\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0.0) # ์ฒด๊ฐ ๋”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"] # ์ฒด๊ฐ ์ถ”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " return hi, wc\n", "\n", "\n", "def features(s, use_env=True, personal=True):\n", " \"\"\"์„ธ์…˜ 1๊ฐœ -> ๋ถ„ ๋‹จ์œ„ ํŠน์ง•ํ‘œ(์ตœ๊ทผ 5๋ถ„ ํ‰๊ท ยท๊ธฐ์šธ๊ธฐ ํฌํ•จ). ์ •๋‹ต stage/rtype ํฌํ•จ.\"\"\"\n", " df, _ = clean(s[\"df\"]); p, e = s[\"person\"], s[\"env\"]\n", " f = pd.DataFrame({\"sid\": s[\"id\"], \"min\": df[\"min\"]})\n", " f[\"temp5\"] = df[\"temp\"].rolling(5, min_periods=1).mean()\n", " f[\"hr5\"] = df[\"hr\"].rolling(5, min_periods=1).mean()\n", " f[\"eda5\"] = df[\"eda\"].rolling(5, min_periods=1).mean()\n", " f[\"dtemp\"] = (f[\"temp5\"] - f[\"temp5\"].shift(4)).fillna(0)\n", " f[\"act\"] = df[\"act\"]\n", " if personal:\n", " f[\"hr_rel\"] = f[\"hr5\"] - p[\"rest_hr\"]\n", " f[\"acclim\"], f[\"bmi\"], f[\"fit\"] = p[\"acclim\"], p[\"bmi\"], p[\"fit\"]\n", " if use_env:\n", " f[\"hi\"], f[\"wc\"] = env_index(e); f[\"wind\"] = e[\"wind\"]\n", " f[\"stage\"], f[\"rtype\"], f[\"kind\"] = df[\"stage\"], df[\"rtype\"], s[\"kind\"]\n", " return f[f[\"min\"] >= 5].reset_index(drop=True)\n", "\n", "\n", "def table(sessions, **kw):\n", " return pd.concat([features(s, **kw) for s in sessions], ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "\n", "PF = [\"temp5\", \"dtemp\", \"hr_excess\", \"eda5\", \"hi\", \"wc\", \"acclim\", \"bmi\", \"tdev\"]\n", "\n", "\n", "def personalize(t):\n", " \"\"\"ํ™œ๋™ ๊ฐ•๋„ยท๊ฐœ์ธ ์ฒด๋ ฅ์œผ๋กœ ์˜ˆ์ƒ๋˜๋Š” ์‹ฌ๋ฐ•์„ ๋นผ '์„ค๋ช…๋˜์ง€ ์•Š๋Š” ์‹ฌ๋ฐ• ์ƒ์Šน(hr_excess)'๊ณผ ์ฒด์˜จ ํŽธ์ฐจ(tdev)๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " t = t.copy()\n", " expected = t[\"hr5\"] - t[\"hr_rel\"] + 9.5 * (t[\"act\"].rolling(5, min_periods=1).mean() - 1.5) * (1.15 - 0.3 * t[\"fit\"])\n", " t[\"hr_excess\"] = t[\"hr5\"] - expected\n", " t[\"tdev\"] = (t[\"temp5\"] - 36.9).abs()\n", " return t\n", "\n", "\n", "def grade(p):\n", " return \"High\" if p >= 0.8 else (\"Medium\" if p >= 0.5 else \"Low\")\n", "\n", "\n", "train = personalize(table(make_dataset(180, seed=41)))\n", "test = personalize(table(make_dataset(90, seed=43)))\n", "model = make_pipeline(StandardScaler(), LogisticRegression(class_weight=\"balanced\", C=1.0, max_iter=3000, random_state=0))\n", "model.fit(train[PF], train[\"stage\"] >= 1)\n", "test[\"score\"] = model.predict_proba(test[PF])[:, 1]\n", "test[\"grade\"] = test[\"score\"].map(grade)\n", "true = (test[\"stage\"] >= 1).values\n", "\n", "fixed = ((test[\"temp5\"] >= 37.8) | (test[\"temp5\"] <= 36.5) | (test[\"hr5\"] >= 130)).values # ๊ธฐ์กด: ๊ณ ์ • ์ž„๊ณ„๊ฐ’ ๊ฒฝ๋ณด\n", "eng = (test[\"score\"] >= 0.5).values\n", "\n", "\n", "def far_rec(alert):\n", " return (alert & ~true).sum() / max(alert.sum(), 1), (alert & true).sum() / true.sum()\n", "\n", "\n", "f0, r0 = far_rec(fixed); f1, r1 = far_rec(eng)\n", "print(pd.DataFrame([{\"๋ฐฉ์‹\": \"๊ณ ์ • ์ž„๊ณ„๊ฐ’(์ฒด์˜จ 37.8 ์ด์ƒยท36.5 ์ดํ•˜ยท์‹ฌ๋ฐ• 130 ์ด์ƒ)\", \"๊ฒฝ๋ณด ๊ตฌ๊ฐ„\": int(fixed.sum()), \"์˜ค๊ฒฝ๋ณด์œจ\": round(f0, 3), \"์œ„ํ—˜ ์žฌํ˜„์œจ\": round(r0, 3)},\n", " {\"๋ฐฉ์‹\": \"๊ฐœ์ธํ™” ์‹ ๋ขฐ๋„ ์—”์ง„(0.5 ์ด์ƒ)\", \"๊ฒฝ๋ณด ๊ตฌ๊ฐ„\": int(eng.sum()), \"์˜ค๊ฒฝ๋ณด์œจ\": round(f1, 3), \"์œ„ํ—˜ ์žฌํ˜„์œจ\": round(r1, 3)}]).to_string(index=False))\n", "cut = 1 - f1 / f0\n", "print(f\"\\n์˜ค๊ฒฝ๋ณด์œจ ๊ฐ์ถ• {cut:.1%} (๋ชฉํ‘œ 40% ์ด์ƒ: {'๋‹ฌ์„ฑ' if cut >= 0.4 else '๋ฏธ๋‹ฌ'}), ROC-AUC {roc_auc_score(true, test['score']):.3f}\")\n", "\n", "# ์ ์ˆ˜ ์ผ๊ด€์„ฑ: ์„ผ์„œ ๊ฐ’์— +-2% ์ธก์ • ํ”๋“ค๋ฆผ์„ ์ฃผ์–ด๋„ ๊ฐ™์€ ๋“ฑ๊ธ‰์ด ์œ ์ง€๋˜๋Š” ๋น„์œจ\n", "rng = np.random.default_rng(0)\n", "X = test[PF].values; g0 = test[\"grade\"].values\n", "same = [np.mean(np.array([grade(v) for v in model.predict_proba(pd.DataFrame(X * (1 + rng.normal(0, 0.02, X.shape)), columns=PF))[:, 1]]) == g0) for _ in range(10)]\n", "print(f\"์‹ ๋ขฐ๋„ ๋“ฑ๊ธ‰ ์ผ๊ด€์„ฑ {np.mean(same):.1%} (๊ฐ™์€ ์ž…๋ ฅ ์žฌ์‹คํ–‰ 100%, ๋ชฉํ‘œ 90% ์ด์ƒ: {'๋‹ฌ์„ฑ' if np.mean(same) >= 0.9 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "print(\"\\n[๋‹จ๊ณ„๋ณ„ ๋“ฑ๊ธ‰ ๋ถ„ํฌ] (ํ–‰: ์ •๋‹ต ๋‹จ๊ณ„)\")\n", "print(pd.crosstab(test[\"stage\"].map(STAGE).rename(\"์ •๋‹ต ๋‹จ๊ณ„\"), test[\"grade\"].rename(\"๋“ฑ๊ธ‰\")).to_string())\n", "\n", "# ๊ฐœ์ธํ™” ํšจ๊ณผ: ๊ฐ™์€ ์‹ฌ๋ฐ•ยท์ฒด์˜จ์ด๋ผ๋„ ์ฒด๋ ฅยท์ˆœ์‘ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์ง€์ˆ˜๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ์˜ˆ\n", "ex = test.iloc[[0]].copy()\n", "rows = []\n", "for name, fit, acc in ((\"์ฒด๋ ฅ ๋‚ฎ์Œยท๋น„์ˆœ์‘\", 0.2, 0), (\"์ฒด๋ ฅ ๋†’์Œยท์ˆœ์‘\", 0.8, 1)):\n", " e = ex.copy(); e[\"fit\"], e[\"acclim\"] = fit, acc\n", " e[\"hr_rel\"] = e[\"hr5\"] - 64; e[\"hr_excess\"] = 0; e = personalize(e); e[\"act\"] = 4.5\n", " e[\"hr5\"] = 128.0; e[\"temp5\"] = 38.3; e[\"hi\"], e[\"wc\"] = 41.0, 30.0; e[\"tdev\"] = 1.1; e[\"dtemp\"] = 0.2; e[\"eda5\"] = 3.5; e = personalize(e)\n", " rows.append({\"๊ฐ€์ƒ ๋ณ‘์‚ฌ\": name, \"์ดˆ๊ณผ ์‹ฌ๋ฐ•\": round(float(e[\"hr_excess\"].iloc[0]), 1), \"์œ„ํ—˜ ์ ์ˆ˜\": round(float(model.predict_proba(e[PF])[:, 1][0]), 3)})\n", "print(\"\\n[๊ฐœ์ธํ™” ์˜ˆ์‹œ: ๊ฐ™์€ ์‹ฌ๋ฐ• 128ยท์ฒด์˜จ 38.3]\")\n", "print(pd.DataFrame(rows).to_string(index=False))\n", "print(\"๊ฐ™์€ ์ธก์ •๊ฐ’์ด๋ผ๋„ ์ฒด๋ ฅยท๋”์œ„ ์ˆœ์‘ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์œ„ํ—˜ ์ ์ˆ˜๊ฐ€ ๋‹ฌ๋ผ์ง„๋‹ค(์ˆœ์‘ ๋ณ‘์‚ฌ๋Š” ๊ฐ™์€ ์ฒด์˜จ์—์„œ ์ ์ˆ˜๊ฐ€ ๋‚ฎ์•„์ง).\")\n", "\n", "sc, lr = model[0], model[-1]\n", "i = int(np.argmax(test[\"score\"].values)); c = (X[i] - sc.mean_) / sc.scale_ * lr.coef_[0]\n", "print(f\"\\n์ตœ๊ณ  ์ ์ˆ˜ ๊ตฌ๊ฐ„ ๊ทผ๊ฑฐ: \", \", \".join(f\"{PF[j]}({c[j]:+.2f})\" for j in np.argsort(-np.abs(c))[:3]))\n", "print(\"๋“ฑ๊ธ‰ ๊ธฐ์ค€: High(0.8 ์ด์ƒ)๋Š” ์ฆ‰์‹œ ์†Œ๋Œ€์žฅ ํ™•์ธยท์˜๋ฌด๋Œ€ ์—ฐ๋ฝ ๊ฒ€ํ† , Medium(0.5~0.8)์€ ์žฌ์ธก์ •ยทํœด์‹ ํ™•์ธ, Low(0.5 ๋ฏธ๋งŒ)๋Š” ๋ชจ๋‹ˆํ„ฐ๋ง ์œ ์ง€. ์ ์ˆ˜๋Š” ํ™•์ธ ์ˆœ์„œ๋ฅผ ์ •ํ•˜๋Š” ๋ณด์กฐ ์ •๋ณด์ด๋ฉฐ ์ง„๋‹จ์ด ์•„๋‹ˆ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n", "for st, col in ((0, \"tab:gray\"), (1, \"tab:orange\"), (2, \"tab:red\")):\n", " ax[0].hist(test.loc[test[\"stage\"] == st, \"score\"], bins=10, range=(0, 1), alpha=0.6, color=col, label=f\"stage {st}\")\n", "ax[0].axvline(0.5, color=\"k\", ls=\"--\"); ax[0].set_xlabel(\"confidence score\"); ax[0].legend(); ax[0].set_title(\"Score by true stage\")\n", "fpr, tpr, _ = roc_curve(true, test[\"score\"]); ax[1].plot(fpr, tpr); ax[1].plot([0, 1], [0, 1], \"k:\"); ax[1].set_title(\"ROC\"); ax[1].set_xlabel(\"FPR\"); ax[1].set_ylabel(\"TPR\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 05 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", " ๋ฐฉ์‹ ๊ฒฝ๋ณด ๊ตฌ๊ฐ„ ์˜ค๊ฒฝ๋ณด์œจ ์œ„ํ—˜ ์žฌํ˜„์œจ\n", "๊ณ ์ • ์ž„๊ณ„๊ฐ’(์ฒด์˜จ 37.8 ์ด์ƒยท36.5 ์ดํ•˜ยท์‹ฌ๋ฐ• 130 ์ด์ƒ) 2614 0.392 0.999\n", " ๊ฐœ์ธํ™” ์‹ ๋ขฐ๋„ ์—”์ง„(0.5 ์ด์ƒ) 1797 0.165 0.943\n", "\n", "์˜ค๊ฒฝ๋ณด์œจ ๊ฐ์ถ• 57.9% (๋ชฉํ‘œ 40% ์ด์ƒ: ๋‹ฌ์„ฑ), ROC-AUC 0.986\n", "์‹ ๋ขฐ๋„ ๋“ฑ๊ธ‰ ์ผ๊ด€์„ฑ 92.9% (๊ฐ™์€ ์ž…๋ ฅ ์žฌ์‹คํ–‰ 100%, ๋ชฉํ‘œ 90% ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "\n", "[๋‹จ๊ณ„๋ณ„ ๋“ฑ๊ธ‰ ๋ถ„ํฌ] (ํ–‰: ์ •๋‹ต ๋‹จ๊ณ„)\n", "๋“ฑ๊ธ‰ High Low Medium\n", "์ •๋‹ต ๋‹จ๊ณ„ \n", "๊ฒฝ๋ณด 183 0 0\n", "์ •์ƒ 96 3063 201\n", "์ฃผ์˜ 1153 90 164\n", "\n", "[๊ฐœ์ธํ™” ์˜ˆ์‹œ: ๊ฐ™์€ ์‹ฌ๋ฐ• 128ยท์ฒด์˜จ 38.3]\n", " ๊ฐ€์ƒ ๋ณ‘์‚ฌ ์ดˆ๊ณผ ์‹ฌ๋ฐ• ์œ„ํ—˜ ์ ์ˆ˜\n", "์ฒด๋ ฅ ๋‚ฎ์Œยท๋น„์ˆœ์‘ 1.9 0.288\n", " ์ฒด๋ ฅ ๋†’์Œยท์ˆœ์‘ 7.0 0.170\n", "๊ฐ™์€ ์ธก์ •๊ฐ’์ด๋ผ๋„ ์ฒด๋ ฅยท๋”์œ„ ์ˆœ์‘ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์œ„ํ—˜ ์ ์ˆ˜๊ฐ€ ๋‹ฌ๋ผ์ง„๋‹ค(์ˆœ์‘ ๋ณ‘์‚ฌ๋Š” ๊ฐ™์€ ์ฒด์˜จ์—์„œ ์ ์ˆ˜๊ฐ€ ๋‚ฎ์•„์ง).\n", "\n", "์ตœ๊ณ  ์ ์ˆ˜ ๊ตฌ๊ฐ„ ๊ทผ๊ฑฐ: tdev(+21.02), hi(+8.18), wc(-7.12)\n", "๋“ฑ๊ธ‰ ๊ธฐ์ค€: High(0.8 ์ด์ƒ)๋Š” ์ฆ‰์‹œ ์†Œ๋Œ€์žฅ ํ™•์ธยท์˜๋ฌด๋Œ€ ์—ฐ๋ฝ ๊ฒ€ํ† , Medium(0.5~0.8)์€ ์žฌ์ธก์ •ยทํœด์‹ ํ™•์ธ, Low(0.5 ๋ฏธ๋งŒ)๋Š” ๋ชจ๋‹ˆํ„ฐ๋ง ์œ ์ง€. ์ ์ˆ˜๋Š” ํ™•์ธ ์ˆœ์„œ๋ฅผ ์ •ํ•˜๋Š” ๋ณด์กฐ ์ •๋ณด์ด๋ฉฐ ์ง„๋‹จ์ด ์•„๋‹ˆ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 06. ์†Œ๋Œ€์žฅ/์˜๋ฌด๊ด€์šฉ ์žฅ๋ณ‘ ์ƒ์ฒด ์œ„ํ—˜ ์‹ ํ˜ธ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๊ธด๊ธ‰ ๊ฒฝ๋ณด ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ง€ํœ˜๊ด€/์˜๋ฌด๊ด€์šฉ ์žฅ๋ณ‘ ์ƒ์ฒด ์œ„ํ—˜ ์‹ ํ˜ธ ๋ชจ๋‹ˆํ„ฐ๋ง ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์†Œ๋Œ€์žฅ๊ณผ ์˜๋ฌด๊ด€์ด ๊ฐ€์ƒ ์†Œ๋Œ€ ๋ณ‘์‚ฌ๋“ค์˜ ์ƒ์ฒด ์œ„ํ—˜ ์‹ ํ˜ธ๋ฅผ ํ•œ๋ˆˆ์— ๋ณด๊ณ  ํ™•์ธ ๋Œ€์ƒ์„ ์ •ํ•˜๋Š” ๋ชจ๋‹ˆํ„ฐ๋ง ์ƒํ™ฉํŒ์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ฐ€์ƒ ๋ณ‘์‚ฌ 24๋ช…์˜ ์ƒํƒœ๋ฅผ ์ •์ƒยท์ฃผ์˜ยท๊ฒฝ๋ณดยท์‹ ํ˜ธ๋‘์ ˆ๋กœ ๋‚˜๋ˆ„๊ณ  ์ฒด์˜จยท์‹ฌ๋ฐ•ยท์ถ”์„ธยท๊ถŒ๊ณ  ๋ฌธ๊ตฌ๋ฅผ ํ‘œ๋กœ ์ž‘์„ฑํ•˜์—ฌ ์›์ธ์„ ๋ถ„์„ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/ui/wel01_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": [ "\"\"\"์†Œ๋Œ€์žฅยท์˜๋ฌด๊ด€์šฉ ๊ฐ€์ƒ ์†Œ๋Œ€ 24๋ช…์˜ ์ƒ์ฒด ์œ„ํ—˜ ์‹ ํ˜ธ ๋ชจ๋‹ˆํ„ฐ๋ง ์ƒํ™ฉํŒ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ๋ณ‘์‚ฌ๋ณ„ ์ƒํƒœ(์ •์ƒยท์ฃผ์˜ยท๊ฒฝ๋ณดยท์‹ ํ˜ธ๋‘์ ˆ), ์ฒด์˜จยท์‹ฌ๋ฐ• ์ถ”์„ธ, ์กฐ์น˜ ๊ถŒ๊ณ  ๋ฌธ๊ตฌ๋ฅผ 1์ดˆ ์ฃผ๊ธฐ๋กœ ๊ฐฑ์‹ ํ•˜๊ณ  ์ฒ˜๋ฆฌ์‹œ๊ฐ„์„ ์ธก์ •ํ•œ๋‹ค. ์ง„๋‹จ ์•„๋‹˜ยท์˜๋ฌด๋Œ€ ์—ฐ๊ณ„.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋”๋ฏธ ์ƒ์ฒด์‹ ํ˜ธ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋ณ‘ยท๋ถ€๋Œ€ยท์˜๋ฌด ๊ธฐ๋ก ์—†์Œ) ----------------\n", "# ๊ฐ€์ƒ ๋ณ‘์‚ฌ(๊ฐ€๋ช… ID)๊ฐ€ 60๋ถ„๊ฐ„ ๊ฑท๊ธฐยทํœด์‹ยท๊ณ ๊ฐ•๋„ ํ™œ๋™์„ ํ•˜๋Š” ๋™์•ˆ์˜ ์‹ฌ๋ฐ•์ˆ˜ยท์ฒด์˜จ(์ถ”์ • ์‹ฌ๋ถ€)ยทํ”ผ๋ถ€์ „๋„๋„๋ฅผ 1๋ถ„ ๊ฐ„๊ฒฉ์œผ๋กœ ๋งŒ๋“ ๋‹ค.\n", "# ์ง„๋‹จ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ์ƒํƒœ ๋ผ๋ฒจ์€ ๊ฐ€์ƒ ์ƒ๋ฆฌ ๋ชจ๋ธ์˜ '๊ต์œก์šฉ ๋‹จ๊ณ„'์ผ ๋ฟ์ด๋‹ค.\n", "N_MIN = 60\n", "STAGE = {0: \"์ •์ƒ\", 1: \"์ฃผ์˜\", 2: \"๊ฒฝ๋ณด\"}\n", "TYPES = [\"normal\", \"heat\", \"cold\"]\n", "\n", "\n", "def make_person(i, rng):\n", " return {\"id\": f\"S{i:03d}\", \"rest_hr\": float(rng.normal(64, 7)), \"age\": int(rng.integers(19, 27)), \"bmi\": float(rng.normal(23.5, 2.5)),\n", " \"acclim\": int(rng.random() < 0.4), \"fit\": float(np.clip(rng.normal(0.5, 0.2), 0.05, 0.95))}\n", "\n", "\n", "def make_env(kind, rng):\n", " if kind == \"heat\":\n", " return {\"temp_c\": float(rng.uniform(30, 40)), \"rh\": float(rng.uniform(45, 85)), \"wind\": float(rng.uniform(0.3, 3))}\n", " if kind == \"cold\":\n", " return {\"temp_c\": float(rng.uniform(-22, 2)), \"rh\": float(rng.uniform(40, 80)), \"wind\": float(rng.uniform(2, 11))}\n", " return {\"temp_c\": float(rng.uniform(8, 24)), \"rh\": float(rng.uniform(35, 70)), \"wind\": float(rng.uniform(0.3, 5))}\n", "\n", "\n", "def make_session(sid, kind, rng, p=None, glitch=True):\n", " \"\"\"๋ฐ˜ํ™˜: dict(person, env, kind, df[hr,temp,eda,act + ์ •๋‹ต coreยทstageยทrtype]). ์„ผ์„œ ๊ฒฐ์ธกยทํАยท๊ณ ์ฐฉ ํฌํ•จ.\"\"\"\n", " p = p or make_person(sid, rng)\n", " env = make_env(kind, rng)\n", " act = np.zeros(N_MIN); t = 0\n", " while t < N_MIN: # 10~20๋ถ„ ๋‹จ์œ„ ํ™œ๋™ ๋ธ”๋ก: ํœด์‹ 1.5 / ํ–‰๊ตฐ 4.5 / ๊ณ ๊ฐ•๋„ 7 MET\n", " d = int(rng.integers(10, 21)); act[t:t + d] = rng.choice([1.5, 4.5, 7.0], p=[0.25, 0.5, 0.25]); t += d\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"]\n", " core, cores = 36.9, []\n", " for m in range(N_MIN):\n", " heat = 0.085 * max(0, hi - 24) * (1 - 0.25 * p[\"acclim\"]) * (1 + 0.5 * m / N_MIN) * (1 + 0.012 * (p[\"bmi\"] - 23.5))\n", " cold = -0.075 * max(0, 8 - wc) * (1 - 0.09 * act[m])\n", " ss = 36.9 + 0.09 * act[m] + heat + cold\n", " core += (ss - core) / 18 + rng.normal(0, 0.03)\n", " cores.append(core)\n", " core = np.array(cores)\n", " hr = p[\"rest_hr\"] + 9.5 * (act - 1.5) * (1.15 - 0.3 * p[\"fit\"]) + 11 * (core - 36.9) + rng.normal(0, 3.2, N_MIN)\n", " eda = 2.0 + 0.25 * act + 3.2 * np.maximum(core - 37.2, 0) - 0.04 * np.maximum(8 - wc, 0) + rng.normal(0, 0.25, N_MIN)\n", " bias = rng.normal(0, 0.1)\n", " temp = core + bias + rng.normal(0, 0.1, N_MIN) - np.where((kind == \"cold\") & (env[\"wind\"] > 7), 0.25, 0.0) # ์„ผ์„œ ์ ‘์ด‰ ๋ถ€์œ„ ๋ฐ”๋žŒ ๋ƒ‰๊ฐ ์˜ค์ฐจ\n", " df = pd.DataFrame({\"min\": np.arange(N_MIN), \"hr\": hr, \"temp\": temp, \"eda\": np.maximum(eda, 0.2), \"act\": act})\n", " if glitch:\n", " for col in (\"hr\", \"temp\", \"eda\"):\n", " df.loc[rng.random(N_MIN) < 0.03, col] = np.nan # ๊ฒฐ์ธก\n", " s = int(rng.integers(5, N_MIN))\n", " if rng.random() < 0.12:\n", " df.loc[s, \"hr\"] = 230.0 # ์„ผ์„œ ํА\n", " if rng.random() < 0.12:\n", " df.loc[s, \"temp\"] = 45.0\n", " ht = np.where(core >= 39.0, 2, np.where(core >= 38.3, 1, 0)); cd = np.where(core <= 35.4, 2, np.where(core <= 36.2, 1, 0))\n", " df[\"core\"] = core\n", " df[\"stage\"] = np.maximum(ht, cd); df[\"rtype\"] = np.where(ht > 0, \"heat\", np.where(cd > 0, \"cold\", \"normal\"))\n", " return {\"id\": p[\"id\"], \"person\": p, \"env\": env, \"kind\": kind, \"df\": df}\n", "\n", "\n", "def make_dataset(n, seed):\n", " rng = np.random.default_rng(seed)\n", " kinds = [\"normal\", \"heat\", \"cold\"]\n", " return [make_session(i, kinds[i % 3], rng) for i in range(n)]\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---------------- ์ „์ฒ˜๋ฆฌยทํŠน์ง• ์ถ”์ถœ ๊ณตํ†ต ํ•จ์ˆ˜ ----------------\n", "LIMITS = {\"hr\": (30, 220), \"temp\": (30.0, 43.0), \"eda\": (0.0, 40.0)} # ์ƒ๋ฆฌ์ ์œผ๋กœ ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„(๋ฒ”์œ„ ๋ฐ– = ์„ผ์„œ ์˜ค๋ฅ˜)\n", "\n", "\n", "def clean(df):\n", " \"\"\"๋ฒ”์œ„ ๋ฐ– ๊ฐ’์€ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ -> ์„ ํ˜• ๋ณด๊ฐ„(์–‘ ๋์€ ๊ฐ€๊นŒ์šด ๊ฐ’) -> 3๋ถ„ ์ด๋™ ์ค‘์•™๊ฐ’์œผ๋กœ ํА ์ œ๊ฑฐ. ๋ฐ˜ํ™˜: (์ •์ œ df, ๋ณด์ • ๊ฑด์ˆ˜)\"\"\"\n", " out = df.copy(); fixed = 0\n", " for c, (lo, hi) in LIMITS.items():\n", " bad = out[c].isna() | (out[c] < lo) | (out[c] > hi)\n", " fixed += int(bad.sum())\n", " out.loc[bad, c] = np.nan\n", " out[c] = out[c].interpolate(limit_direction=\"both\").rolling(3, center=True, min_periods=1).median()\n", " return out, fixed\n", "\n", "\n", "def env_index(env):\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0.0) # ์ฒด๊ฐ ๋”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"] # ์ฒด๊ฐ ์ถ”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " return hi, wc\n", "\n", "\n", "def features(s, use_env=True, personal=True):\n", " \"\"\"์„ธ์…˜ 1๊ฐœ -> ๋ถ„ ๋‹จ์œ„ ํŠน์ง•ํ‘œ(์ตœ๊ทผ 5๋ถ„ ํ‰๊ท ยท๊ธฐ์šธ๊ธฐ ํฌํ•จ). ์ •๋‹ต stage/rtype ํฌํ•จ.\"\"\"\n", " df, _ = clean(s[\"df\"]); p, e = s[\"person\"], s[\"env\"]\n", " f = pd.DataFrame({\"sid\": s[\"id\"], \"min\": df[\"min\"]})\n", " f[\"temp5\"] = df[\"temp\"].rolling(5, min_periods=1).mean()\n", " f[\"hr5\"] = df[\"hr\"].rolling(5, min_periods=1).mean()\n", " f[\"eda5\"] = df[\"eda\"].rolling(5, min_periods=1).mean()\n", " f[\"dtemp\"] = (f[\"temp5\"] - f[\"temp5\"].shift(4)).fillna(0)\n", " f[\"act\"] = df[\"act\"]\n", " if personal:\n", " f[\"hr_rel\"] = f[\"hr5\"] - p[\"rest_hr\"]\n", " f[\"acclim\"], f[\"bmi\"], f[\"fit\"] = p[\"acclim\"], p[\"bmi\"], p[\"fit\"]\n", " if use_env:\n", " f[\"hi\"], f[\"wc\"] = env_index(e); f[\"wind\"] = e[\"wind\"]\n", " f[\"stage\"], f[\"rtype\"], f[\"kind\"] = df[\"stage\"], df[\"rtype\"], s[\"kind\"]\n", " return f[f[\"min\"] >= 5].reset_index(drop=True)\n", "\n", "\n", "def table(sessions, **kw):\n", " return pd.concat([features(s, **kw) for s in sessions], ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "\n", "FE = [\"temp5\", \"dtemp\", \"hr5\", \"eda5\", \"hr_rel\", \"acclim\", \"bmi\", \"fit\", \"hi\", \"wc\", \"wind\"]\n", "tr = table(make_dataset(120, seed=51))\n", "model = make_pipeline(StandardScaler(), LogisticRegression(class_weight=\"balanced\", max_iter=3000, random_state=0)).fit(tr[FE], tr[\"stage\"] >= 1)\n", "\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์†Œ๋Œ€ 24๋ช…(๊ฐ€๋ช… ID): ํญ์—ผ 8, ํ˜นํ•œ 8, ํ‰์‹œ 8 ๊ตฌ์—ญ์—์„œ ํ™œ๋™ ์ค‘์ธ ๋ณ‘์‚ฌ๋“ค์˜ ํ™”๋ฉด\n", "rng = np.random.default_rng(3)\n", "kinds = [\"heat\"] * 8 + [\"cold\"] * 8 + [\"normal\"] * 8\n", "squad = [make_session(i, k, rng) for i, k in enumerate(kinds)]\n", "for s in squad[:2]:\n", " s[\"df\"].loc[40:, [\"hr\", \"temp\", \"eda\"]] = np.nan # ์‹ ํ˜ธ ๋‘์ ˆ 2๋ช…(๊ธฐ๊ธฐ ์ดํƒˆยท๋ฐฐํ„ฐ๋ฆฌ ๋ฐฉ์ „ ๋ชจ์˜)\n", "FEAT = {s[\"id\"]: features(s) for s in squad} # ๋ณ‘์‚ฌ๋ณ„ ๋ถ„ ๋‹จ์œ„ ํŠน์ง•ํ‘œ(์žฌ์ƒ์šฉ)\n", "RAW = {s[\"id\"]: s[\"df\"] for s in squad}\n", "\n", "\n", "def snapshot(minute):\n", " \"\"\"๋ถ„ ๋‹จ์œ„ 1ํ‹ฑ: ๋ณ‘์‚ฌ๋ณ„ ์ƒํƒœยท์ ์ˆ˜ยท๊ถŒ๊ณ ๋ฅผ ๊ณ„์‚ฐํ•ด ์ƒํ™ฉํŒ ํ‘œ๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rows = []\n", " for s in squad:\n", " f = FEAT[s[\"id\"]]; r = f[f[\"min\"] == minute]\n", " raw = RAW[s[\"id\"]].loc[max(minute - 4, 0):minute, [\"hr\", \"temp\"]]\n", " if raw.isna().all().all():\n", " rows.append({\"๋ณ‘์‚ฌ\": s[\"id\"], \"์ƒํƒœ\": \"์‹ ํ˜ธ๋‘์ ˆ\", \"์ ์ˆ˜\": np.nan, \"์ฒด์˜จ\": np.nan, \"์‹ฌ๋ฐ•\": np.nan, \"์ถ”์„ธ\": \"-\", \"๊ถŒ๊ณ \": \"๊ธฐ๊ธฐ ์ฐฉ์šฉยท๋ฐฐํ„ฐ๋ฆฌ ํ™•์ธ, ์†Œ๋Œ€์žฅ ๋Œ€๋ฉด ํ™•์ธ\"}); continue\n", " sc = float(model.predict_proba(r[FE])[:, 1][0])\n", " st = \"๊ฒฝ๋ณด\" if sc >= 0.8 else (\"์ฃผ์˜\" if sc >= 0.5 else \"์ •์ƒ\")\n", " rec = {\"๊ฒฝ๋ณด\": \"์†Œ๋Œ€์žฅ ์ฆ‰์‹œ ํ™•์ธ, ์˜๋ฌด๋Œ€ ์—ฐ๋ฝ ๊ฒ€ํ† \", \"์ฃผ์˜\": \"์žฌ์ธก์ •ยทํœด์‹ ์ƒํƒœ ํ™•์ธ\", \"์ •์ƒ\": \"-\"}[st]\n", " rows.append({\"๋ณ‘์‚ฌ\": s[\"id\"], \"์ƒํƒœ\": st, \"์ ์ˆ˜\": round(sc, 2), \"์ฒด์˜จ\": round(float(r[\"temp5\"].iloc[0]), 1), \"์‹ฌ๋ฐ•\": int(r[\"hr5\"].iloc[0]),\n", " \"์ถ”์„ธ\": f\"{float(r['dtemp'].iloc[0]):+.1f}\", \"๊ถŒ๊ณ \": rec})\n", " return pd.DataFrame(rows)\n", "\n", "\n", "# 1) 1์ดˆ ์ฃผ๊ธฐ ๊ฐฑ์‹  ๋ชจ์˜: 5ํ‹ฑ ๊ฐฑ์‹  ์‹œ๊ฐ„ ์ธก์ •\n", "times = []\n", "for m in range(45, 50):\n", " t0 = time.perf_counter(); board = snapshot(m); times.append((time.perf_counter() - t0) * 1000)\n", "print(f\"์ƒํ™ฉํŒ ๊ฐฑ์‹  ์ฒ˜๋ฆฌ์‹œ๊ฐ„ ํ‰๊ท  {np.mean(times):.1f} ms, ์ตœ๋Œ€ {max(times):.1f} ms (๋ชฉํ‘œ: ๊ฐฑ์‹  ์ฃผ๊ธฐ 1์ดˆ ์ด๋‚ด -> {'๋‹ฌ์„ฑ' if max(times) < 1000 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# 2) ๋ณ‘์‚ฌ 24๋ช… -> ํ™•์ธ ๋Œ€์ƒ ์š”์•ฝ(์ธ์ง€ ๋ถ€ํ•˜ ๊ฐ์†Œ)\n", "order = {\"์‹ ํ˜ธ๋‘์ ˆ\": 0, \"๊ฒฝ๋ณด\": 1, \"์ฃผ์˜\": 2, \"์ •์ƒ\": 3}\n", "board = board.sort_values([\"์ƒํƒœ\", \"์ ์ˆ˜\"], key=lambda s: s.map(order) if s.name == \"์ƒํƒœ\" else -s.fillna(2), kind=\"stable\")\n", "cnt = board[\"์ƒํƒœ\"].value_counts().reindex([\"๊ฒฝ๋ณด\", \"์ฃผ์˜\", \"์‹ ํ˜ธ๋‘์ ˆ\", \"์ •์ƒ\"]).fillna(0).astype(int)\n", "print(f\"\\n[์†Œ๋Œ€ ํ˜„ํ™ฉ] ๋ณ‘์‚ฌ {len(board)}๋ช… -> ํ™•์ธ ๋Œ€์ƒ {len(board) - cnt['์ •์ƒ']}๋ช…์œผ๋กœ ์••์ถ•:\", cnt.to_dict())\n", "print(\"\\n[์šฐ์„  ํ™•์ธ ๋ชฉ๋ก: ์‹ ํ˜ธ๋‘์ ˆ > ๊ฒฝ๋ณด > ์ฃผ์˜ ์ˆœ]\")\n", "print(board[board[\"์ƒํƒœ\"] != \"์ •์ƒ\"].head(12).fillna(\"-\").to_string(index=False))\n", "\n", "# 3) ๋ Œ๋”๋ง ์‹œ๊ฐ„(UI ์‘๋‹ต์„ฑ)๊ณผ ์ƒํ™ฉํŒ ๊ทธ๋ฆผ\n", "t0 = time.perf_counter()\n", "col = {\"๊ฒฝ๋ณด\": \"tab:red\", \"์ฃผ์˜\": \"orange\", \"์ •์ƒ\": \"tab:green\", \"์‹ ํ˜ธ๋‘์ ˆ\": \"dimgray\"}; EN = {\"๊ฒฝ๋ณด\": \"ALERT\", \"์ฃผ์˜\": \"CAUTION\", \"์ •์ƒ\": \"NORMAL\", \"์‹ ํ˜ธ๋‘์ ˆ\": \"NO SIGNAL\"}\n", "fig, ax = plt.subplots(2, 3, figsize=(13, 6))\n", "focus = board[board[\"์ƒํƒœ\"].isin([\"๊ฒฝ๋ณด\", \"์ฃผ์˜\"])].head(5)[\"๋ณ‘์‚ฌ\"].tolist()\n", "for a, sid in zip(ax.ravel()[:5], focus):\n", " d = RAW[sid]; st = board[board[\"๋ณ‘์‚ฌ\"] == sid][\"์ƒํƒœ\"].iloc[0]\n", " a.plot(d[\"min\"], d[\"temp\"].interpolate(), color=col[st]); a.axvline(49, color=\"k\", ls=\":\"); a.set_ylim(34, 41)\n", " a.set_title(f\"{sid} [{EN[st]}] temperature trend\", fontsize=9); a.set_xlabel(\"minute\")\n", "ax.ravel()[5].bar([EN[k] for k in cnt.index], cnt.values, color=[col[k] for k in cnt.index]); ax.ravel()[5].set_title(\"Platoon status\")\n", "plt.suptitle(\"Soldier bio-signal board (dummy data, not diagnosis)\"); plt.tight_layout(); fig.canvas.draw()\n", "render = (time.perf_counter() - t0) * 1000\n", "print(f\"\\nํ•œ ํ™”๋ฉด ๋ Œ๋”๋ง {render:.0f} ms (UI ์‘๋‹ต ๋ชฉํ‘œ 100 ms: ๋ฐ์ดํ„ฐ ๊ฐฑ์‹  {np.mean(times):.1f} ms๋Š” ๋‹ฌ์„ฑ, ๋ Œ๋”๋ง์€ ์‹คํ–‰ ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง)\")\n", "print(\"์ƒํ™ฉํŒ์˜ ์ƒํƒœ์™€ ๊ถŒ๊ณ ๋Š” ์†Œ๋Œ€์žฅยท์˜๋ฌด๊ด€์˜ ํ™•์ธ์„ ๋•๋Š” ๋ณด์กฐ ์ •๋ณด์ด๋ฉฐ ์ง„๋‹จ์ด ์•„๋‹ˆ๋‹ค. ์˜ํ•™์  ํŒ๋‹จ๊ณผ ์กฐ์น˜๋Š” ๊ตฐ์˜๊ด€ยท์˜๋ฌด๋Œ€๊ฐ€ ํ•˜๋ฉฐ, ์กฐ์น˜ ๋ช…๋ น์€ ๋ฏธ์…˜ 7์˜ HITL ์ ˆ์ฐจ๋ฅผ ๋”ฐ๋ฅธ๋‹ค.\")\n", "plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 06 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "์ƒํ™ฉํŒ ๊ฐฑ์‹  ์ฒ˜๋ฆฌ์‹œ๊ฐ„ ํ‰๊ท  74.0 ms, ์ตœ๋Œ€ 77.3 ms (๋ชฉํ‘œ: ๊ฐฑ์‹  ์ฃผ๊ธฐ 1์ดˆ ์ด๋‚ด -> ๋‹ฌ์„ฑ)\n", "\n", "[์†Œ๋Œ€ ํ˜„ํ™ฉ] ๋ณ‘์‚ฌ 24๋ช… -> ํ™•์ธ ๋Œ€์ƒ 12๋ช…์œผ๋กœ ์••์ถ•: {'๊ฒฝ๋ณด': 7, '์ฃผ์˜': 3, '์‹ ํ˜ธ๋‘์ ˆ': 2, '์ •์ƒ': 12}\n", "\n", "[์šฐ์„  ํ™•์ธ ๋ชฉ๋ก: ์‹ ํ˜ธ๋‘์ ˆ > ๊ฒฝ๋ณด > ์ฃผ์˜ ์ˆœ]\n", " ๋ณ‘์‚ฌ ์ƒํƒœ ์ ์ˆ˜ ์ฒด์˜จ ์‹ฌ๋ฐ• ์ถ”์„ธ ๊ถŒ๊ณ \n", "S000 ์‹ ํ˜ธ๋‘์ ˆ - - - - ๊ธฐ๊ธฐ ์ฐฉ์šฉยท๋ฐฐํ„ฐ๋ฆฌ ํ™•์ธ, ์†Œ๋Œ€์žฅ ๋Œ€๋ฉด ํ™•์ธ\n", "S001 ์‹ ํ˜ธ๋‘์ ˆ - - - - ๊ธฐ๊ธฐ ์ฐฉ์šฉยท๋ฐฐํ„ฐ๋ฆฌ ํ™•์ธ, ์†Œ๋Œ€์žฅ ๋Œ€๋ฉด ํ™•์ธ\n", "S003 ๊ฒฝ๋ณด 1.0 38.7 119.0 +0.1 ์†Œ๋Œ€์žฅ ์ฆ‰์‹œ ํ™•์ธ, ์˜๋ฌด๋Œ€ ์—ฐ๋ฝ ๊ฒ€ํ† \n", "S010 ๊ฒฝ๋ณด 1.0 35.4 65.0 -0.1 ์†Œ๋Œ€์žฅ ์ฆ‰์‹œ ํ™•์ธ, ์˜๋ฌด๋Œ€ ์—ฐ๋ฝ ๊ฒ€ํ† \n", "S009 ๊ฒฝ๋ณด 0.99 35.3 93.0 -0.1 ์†Œ๋Œ€์žฅ ์ฆ‰์‹œ ํ™•์ธ, ์˜๋ฌด๋Œ€ ์—ฐ๋ฝ ๊ฒ€ํ† \n", "S006 ๊ฒฝ๋ณด 0.96 38.5 110.0 +0.1 ์†Œ๋Œ€์žฅ ์ฆ‰์‹œ ํ™•์ธ, ์˜๋ฌด๋Œ€ ์—ฐ๋ฝ ๊ฒ€ํ† \n", "S014 ๊ฒฝ๋ณด 0.94 35.9 85.0 -0.1 ์†Œ๋Œ€์žฅ ์ฆ‰์‹œ ํ™•์ธ, ์˜๋ฌด๋Œ€ ์—ฐ๋ฝ ๊ฒ€ํ† \n", "S004 ๊ฒฝ๋ณด 0.93 38.3 81.0 -0.0 ์†Œ๋Œ€์žฅ ์ฆ‰์‹œ ํ™•์ธ, ์˜๋ฌด๋Œ€ ์—ฐ๋ฝ ๊ฒ€ํ† \n", "S005 ๊ฒฝ๋ณด 0.86 38.1 125.0 +0.0 ์†Œ๋Œ€์žฅ ์ฆ‰์‹œ ํ™•์ธ, ์˜๋ฌด๋Œ€ ์—ฐ๋ฝ ๊ฒ€ํ† \n", "S007 ์ฃผ์˜ 0.67 38.5 110.0 +0.0 ์žฌ์ธก์ •ยทํœด์‹ ์ƒํƒœ ํ™•์ธ\n", "S002 ์ฃผ์˜ 0.62 38.3 91.0 +0.1 ์žฌ์ธก์ •ยทํœด์‹ ์ƒํƒœ ํ™•์ธ\n", "S012 ์ฃผ์˜ 0.52 36.2 92.0 -0.0 ์žฌ์ธก์ •ยทํœด์‹ ์ƒํƒœ ํ™•์ธ\n", "\n", "ํ•œ ํ™”๋ฉด ๋ Œ๋”๋ง 565 ms (UI ์‘๋‹ต ๋ชฉํ‘œ 100 ms: ๋ฐ์ดํ„ฐ ๊ฐฑ์‹  74.0 ms๋Š” ๋‹ฌ์„ฑ, ๋ Œ๋”๋ง์€ ์‹คํ–‰ ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง)\n", "์ƒํ™ฉํŒ์˜ ์ƒํƒœ์™€ ๊ถŒ๊ณ ๋Š” ์†Œ๋Œ€์žฅยท์˜๋ฌด๊ด€์˜ ํ™•์ธ์„ ๋•๋Š” ๋ณด์กฐ ์ •๋ณด์ด๋ฉฐ ์ง„๋‹จ์ด ์•„๋‹ˆ๋‹ค. ์˜ํ•™์  ํŒ๋‹จ๊ณผ ์กฐ์น˜๋Š” ๊ตฐ์˜๊ด€ยท์˜๋ฌด๋Œ€๊ฐ€ ํ•˜๋ฉฐ, ์กฐ์น˜ ๋ช…๋ น์€ ๋ฏธ์…˜ 7์˜ HITL ์ ˆ์ฐจ๋ฅผ ๋”ฐ๋ฅธ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 07. ์œ„ํ—˜ ์žฅ๋ณ‘ ๋ฐœ์ƒ ์‹œ ๊ตฐ์˜๊ด€ ์›๊ฒฉ ๊ฒ€ํ†  ๋ฐ ์ž„๋ฌด ๋ฐฐ์ œ/ํœด์‹ ๋ช…๋ น HITL ์ ˆ์ฐจ ์„ค๊ณ„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์œ„ํ—˜ ์žฅ๋ณ‘ ๋ฐœ์ƒ ์‹œ ๊ตฐ์˜๊ด€ ์›๊ฒฉ ๊ฒ€ํ†  ๋ฐ ์กฐ์น˜ ๋ช…๋ น HITL ์ ˆ์ฐจ๋ฅผ ์„ค๊ณ„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์œ„ํ—˜ ์‹ ํ˜ธ๊ฐ€ ๋ฐœ์ƒํ•œ ๋ณ‘์‚ฌ์— ๋Œ€ํ•ด ๊ตฐ์˜๊ด€์ด ์›๊ฒฉ์œผ๋กœ ๊ฒ€ํ† ํ•˜๊ณ  ์กฐ์น˜๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ์ธ๊ฐ„ ๊ฐœ์ž…(HITL) ์ ˆ์ฐจ๋ฅผ ์„ค๊ณ„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **AI ์œ„ํ—˜ ์‹ ํ˜ธ๋Š” ์ ์ˆ˜์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ๊ฒ€ํ†  ๋Œ€๊ธฐ๋กœ ๋“ฑ๋กํ•˜๊ณ  ๋“ฑ๊ธ‰ยท์ ์ˆ˜ ์ˆœ์œผ๋กœ ๊ฒ€ํ†  ์ˆœ์„œ๋ฅผ ์ • ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/hitl/wel01_approval_workflow.py ๋ฐ docs/wel01_hitl_guide.md` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์ง€ํœ˜ ์ฑ…์ž„ ๋ถ„๊ณ„์„  ๋ช…ํ™•ํ™” ๋ฐ ์„ฑ๊ธ‰ํ•œ ์ž๋™ ๋‹จ์ • ๋ฐฉ์ง€ ์ฒด๊ณ„ ๋ถ€ํ•ฉ์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์ธ๊ฐ„ ๊ฒ€ํ†  ํ(Queue) ๋Œ€๊ธฐ์‹œ๊ฐ„ ๋ฐ ์Šน์ธ/๋ฐ˜๋ ค ์ฒ˜๋ฆฌ ๋กœ๊น… ์„ฑ๊ณต๋ฅ  100%\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์Šน์ธ/๋ฐ˜๋ ค ์ธํ„ฐํŽ˜์ด์Šค ๋™์ž‘ ๋ฐ ๊ฐ์‚ฌ ๋กœ๊ทธ(Audit Log) ์ƒ์„ฑ ๊ฒ€์ฆ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๋ชจ์˜ ์กฐ์ž‘์ž ์Šน์ธ/๋ฐ˜๋ ค ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ฐ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์œ„ํ—˜ ์‹ ํ˜ธ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฐ€์ƒ ๋ณ‘์‚ฌ์— ๋Œ€ํ•ด ๊ตฐ์˜๊ด€์ด ์›๊ฒฉ ๊ฒ€ํ† ํ•˜๊ณ  ํœด์‹ ๊ถŒ๊ณ ยท์ž„๋ฌด ๋ฐฐ์ œ ์š”์ฒญยท์˜๋ฌด๋Œ€ ์ธ๊ณ„ ์š”์ฒญยท์žฌ์ธก์ • ๋ณด๋ฅ˜๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ์ธ๊ฐ„ ๊ฐœ์ž…(HITL) ์ ˆ์ฐจ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. AI๋Š” ์–ด๋–ค ์กฐ์น˜๋„ ์ž๋™์œผ๋กœ ๋ช…๋ นํ•˜์ง€ ์•Š์œผ๋ฉฐ ๋ชจ๋“  ๊ฒฐ์ •์€ ๋‹ด๋‹น์ž IDยท์—ญํ• ยท๊ทผ๊ฑฐ์™€ ํ•จ๊ป˜ ์œ„ยท๋ณ€์กฐ ํ™•์ธ์ด ๊ฐ€๋Šฅํ•œ ๊ฐ์‚ฌ ๋กœ๊ทธ์— ๊ธฐ๋ก๋œ๋‹ค. ์ง„๋‹จ ์•„๋‹˜.\"\"\"\n", "import hashlib\n", "import json\n", "import pandas as pd\n", "\n", "ACTIONS = {\"REST\": \"ํœด์‹ ๊ถŒ๊ณ (ํ˜„์žฅ ํ™•์ธ ํ›„ ์ดํ–‰)\", \"EXCLUDE\": \"์ž„๋ฌด ๋ฐฐ์ œ ์š”์ฒญ(์ง€ํœ˜๊ด€ ์Šน์ธ ๋Œ€์ƒ)\", \"REFER\": \"์˜๋ฌด๋Œ€ ์ธ๊ณ„ ์š”์ฒญ\", \"HOLD\": \"์žฌ์ธก์ •ยท๋ณด๋ฅ˜\"}\n", "ROLES = {\"platoon_leader\": \"์†Œ๋Œ€์žฅ\", \"medical_officer\": \"๊ตฐ์˜๊ด€\", \"commander\": \"์ง€ํœ˜๊ด€\"}\n", "PRIORITY = {\"High\": 0, \"Medium\": 1, \"Low\": 2}\n", "HOLD_LIMIT_S = 60\n", "\n", "\n", "class WorkflowError(ValueError):\n", " pass\n", "\n", "\n", "class ReviewQueue:\n", " def __init__(self, clock):\n", " self.items, self.log, self.clock = {}, [], clock\n", "\n", " # ---- ๊ฐ์‚ฌ ๋กœ๊ทธ: ์ง์ „ ํ•ด์‹œ๋ฅผ ํฌํ•จํ•œ ํ•ด์‹œ ์ฒด์ธ(์ถ”๊ฐ€ ์ „์šฉ) ----\n", " def _record(self, body):\n", " prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n", " body = {**body, \"ts\": round(self.clock(), 3)}\n", " h = hashlib.sha256((prev + json.dumps(body, ensure_ascii=False, sort_keys=True)).encode()).hexdigest()\n", " self.log.append({**body, \"prev\": prev, \"hash\": h})\n", "\n", " def verify_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", " ok_hash = hashlib.sha256((prev + json.dumps(body, ensure_ascii=False, sort_keys=True)).encode()).hexdigest()\n", " if rec[\"prev\"] != prev or rec[\"hash\"] != ok_hash:\n", " return False, n\n", " prev = rec[\"hash\"]\n", " return True, -1\n", "\n", " def submit(self, sid, score, grade, summary):\n", " \"\"\"AI ๊ฒฝ๋ณด ๋“ฑ๋ก. ์ ์ˆ˜์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ํ•ญ์ƒ PENDING(๊ฒ€ํ†  ๋Œ€๊ธฐ)์œผ๋กœ ์‹œ์ž‘ํ•œ๋‹ค.\"\"\"\n", " self.items[sid] = {\"id\": sid, \"score\": score, \"grade\": grade, \"summary\": summary, \"status\": \"PENDING\",\n", " \"submitted\": self.clock(), \"hold_since\": None, \"escalated\": False, \"decided\": None}\n", " self._record({\"event\": \"SUBMIT\", \"id\": sid, \"score\": score, \"by\": \"AI\"})\n", "\n", " def pending(self):\n", " todo = [i for i in self.items.values() if i[\"status\"] in (\"PENDING\", \"HOLD\", \"EXCLUDE_REQUESTED\")]\n", " return sorted(todo, key=lambda i: (not i[\"escalated\"], PRIORITY[i[\"grade\"]], -i[\"score\"]))\n", "\n", " def decide(self, sid, action, operator, role, reason):\n", " if action not in ACTIONS:\n", " raise WorkflowError(f\"ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ์กฐ์น˜: {action}\")\n", " if role not in ROLES or not operator:\n", " raise WorkflowError(\"๋‹ด๋‹น์ž ID์™€ ์—ญํ•  ํ•„์š”\")\n", " item = self.items.get(sid)\n", " if item is None:\n", " raise WorkflowError(\"๋Œ€๊ธฐ์—ด์— ์—†๋Š” ๋ณ‘์‚ฌ\")\n", " if item[\"status\"] in (\"CLOSED\",):\n", " raise WorkflowError(\"์ด๋ฏธ ์ข…๊ฒฐ๋œ ๊ฑด\")\n", " if len(reason.strip()) < 5:\n", " raise WorkflowError(\"ํŒ๋‹จ ๊ทผ๊ฑฐ 5์ž ์ด์ƒ ํ•„์š”\")\n", " if action in (\"EXCLUDE\", \"REFER\") and role == \"platoon_leader\":\n", " if action == \"EXCLUDE\":\n", " raise WorkflowError(\"์ž„๋ฌด ๋ฐฐ์ œ ์š”์ฒญ์€ ๊ตฐ์˜๊ด€ ๊ฒ€ํ†  ์˜๊ฒฌ์ด ์žˆ์–ด์•ผ ํ•จ(์†Œ๋Œ€์žฅ ๋‹จ๋… ๋ถˆ๊ฐ€)\")\n", " if action == \"EXCLUDE\" and item[\"status\"] == \"EXCLUDE_REQUESTED\" and role != \"commander\":\n", " raise WorkflowError(\"์ž„๋ฌด ๋ฐฐ์ œ ์ตœ์ข… ์Šน์ธ์€ ์ง€ํœ˜๊ด€๋งŒ ๊ฐ€๋Šฅ\")\n", " if action == \"EXCLUDE\" and item[\"status\"] != \"EXCLUDE_REQUESTED\":\n", " if role != \"medical_officer\":\n", " raise WorkflowError(\"์ž„๋ฌด ๋ฐฐ์ œ ์š”์ฒญ์€ ๊ตฐ์˜๊ด€๋งŒ ์˜ฌ๋ฆด ์ˆ˜ ์žˆ์Œ\")\n", " item[\"status\"] = \"EXCLUDE_REQUESTED\" # ๊ตฐ์˜๊ด€ ์š”์ฒญ ๋‹จ๊ณ„: ์ง€ํœ˜๊ด€ ์Šน์ธ ์ „๊นŒ์ง€ ํ™•์ • ์•„๋‹˜\n", " elif action == \"EXCLUDE\":\n", " item[\"status\"] = \"CLOSED\" # ์ง€ํœ˜๊ด€ ์Šน์ธ = ์กฐ์น˜ ์ง€์‹œ ์š”์ฒญ๊นŒ์ง€\n", " elif action == \"REST\" and item[\"grade\"] == \"High\" and role == \"platoon_leader\":\n", " raise WorkflowError(\"High ๋“ฑ๊ธ‰ ํœด์‹ ํŒ๋‹จ์€ ๊ตฐ์˜๊ด€ ๊ฒ€ํ†  ํ•„์š”\")\n", " elif action in (\"REST\", \"REFER\"):\n", " item[\"status\"] = \"CLOSED\"\n", " else:\n", " item[\"status\"] = \"HOLD\"\n", " item[\"hold_since\"] = self.clock() if action == \"HOLD\" else None\n", " item[\"decided\"] = self.clock() if item[\"status\"] == \"CLOSED\" else None\n", " self._record({\"event\": \"DECIDE\", \"id\": sid, \"action\": action, \"operator\": operator, \"role\": role, \"reason\": reason})\n", "\n", " def check_holds(self):\n", " \"\"\"๋ณด๋ฅ˜ ๊ธฐํ•œ ์ดˆ๊ณผ ์‹œ ์ž๋™ ์กฐ์น˜ํ•˜์ง€ ์•Š๊ณ  ๊ตฐ์˜๊ด€์—๊ฒŒ ์ƒ์‹ ํ•œ๋‹ค.\"\"\"\n", " out = []\n", " for i in self.items.values():\n", " if i[\"status\"] == \"HOLD\" and not i[\"escalated\"] and self.clock() - i[\"hold_since\"] > HOLD_LIMIT_S:\n", " i[\"escalated\"] = True; out.append(i[\"id\"])\n", " self._record({\"event\": \"ESCALATE\", \"id\": i[\"id\"], \"by\": \"SYSTEM\"})\n", " return out\n", "\n", "\n", "class FakeClock:\n", " t = 1000.0\n", "\n", " def __call__(self):\n", " return self.t\n", "\n", "\n", "clock = FakeClock()\n", "q = ReviewQueue(clock)\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ฏธ์…˜ 5 ์‹ ๋ขฐ๋„ ์—”์ง„์—์„œ ๋„˜์–ด์˜จ ์œ„ํ—˜ ์‹ ํ˜ธ 6๊ฑด(๊ฐ€๋ช… ๋ณ‘์‚ฌ ID)\n", "for sid, sc, g, sm in [(\"S003\", 0.98, \"High\", \"์ฒด์˜จ 38.7, ์‹ฌ๋ฐ• 119\"), (\"S010\", 0.97, \"High\", \"์ฒด์˜จ 35.4, ํ•œ๋žญ\"), (\"S007\", 0.66, \"Medium\", \"์ฒด์˜จ 38.5, ์‹ฌ๋ฐ• 110\"),\n", " (\"S012\", 0.52, \"Medium\", \"์ฒด์˜จ 36.2\"), (\"S002\", 0.55, \"Medium\", \"์ฒด์˜จ 38.3\"), (\"S015\", 0.35, \"Low\", \"์‹ฌ๋ฐ• ์ผ์‹œ ์ƒ์Šน\")]:\n", " q.submit(sid, sc, g, sm)\n", "print(\"๊ฒ€ํ†  ์ˆœ์„œ:\", [i[\"id\"] for i in q.pending()])\n", "print(\"์ž๋™ ์กฐ์น˜(ํœด์‹ยท๋ฐฐ์ œยท์ธ๊ณ„)๋œ ๊ฑด์ˆ˜:\", sum(i[\"status\"] == \"CLOSED\" for i in q.items.values()))\n", "\n", "# ๊ทœ์น™ ์œ„๋ฐ˜ ์‹œ๋„๊ฐ€ ๋ชจ๋‘ ์ฐจ๋‹จ๋˜๋Š”์ง€ ํ™•์ธ\n", "tries = [(\"S003\", \"REST\", \"pl01\", \"platoon_leader\", \"๋”์›Œ์„œ ์‰ฌ๊ฒŒ ํ•จ ํ™•์ธ ์™„๋ฃŒ\"), (\"S010\", \"EXCLUDE\", \"pl01\", \"platoon_leader\", \"์ €์ฒด์˜จ ์˜์‹ฌ๋˜์–ด ๋ฐฐ์ œ ์š”์ฒญ\"),\n", " (\"S007\", \"REST\", \"\", \"platoon_leader\", \"ํœด์‹ ํ›„ ์žฌ์ธก์ • ์˜ˆ์ •\"), (\"S007\", \"DISCHARGE\", \"md01\", \"medical_officer\", \"ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ์กฐ์น˜\"),\n", " (\"S012\", \"REFER\", \"pl01\", \"platoon_leader\", \"์งง์Œ\")]\n", "for args in tries:\n", " try:\n", " q.decide(*args)\n", " except WorkflowError as e:\n", " print(\"์ฐจ๋‹จ:\", e)\n", "\n", "clock.t += 3; q.decide(\"S003\", \"EXCLUDE\", \"md01\", \"medical_officer\", \"์›๊ฒฉ ๊ฒ€ํ† : ์ฒด์˜จ ์ƒ์Šน ์ง€์†, ์ž„๋ฌด ๋ฐฐ์ œ ๋ฐ ์˜๋ฌด๋Œ€ ์ธ๊ณ„ ์š”์ฒญ\")\n", "clock.t += 4; q.decide(\"S003\", \"EXCLUDE\", \"cdr01\", \"commander\", \"๊ตฐ์˜๊ด€ ์˜๊ฒฌ ํ™•์ธ, ์ž„๋ฌด ๋ฐฐ์ œ ์Šน์ธ ํ›„ ์˜๋ฌด๋Œ€ ์ธ๊ณ„ ์ง€์‹œ ์š”์ฒญ\")\n", "clock.t += 5; q.decide(\"S010\", \"HOLD\", \"md01\", \"medical_officer\", \"์„ผ์„œ ๋ƒ‰๊ฐ ์˜ค์ฐจ ๊ฐ€๋Šฅ์„ฑ, ์žฌ์ธก์ • ํ›„ ํŒ๋‹จ\")\n", "clock.t += 4; q.decide(\"S007\", \"REST\", \"pl01\", \"platoon_leader\", \"๊ทธ๋Š˜ ํœด์‹ ํ›„ ์žฌ์ธก์ •, ์ƒํƒœ ํ˜ธ์ „ ํ™•์ธ\")\n", "clock.t += 3; q.decide(\"S015\", \"REST\", \"pl01\", \"platoon_leader\", \"์šด๋™ ์งํ›„ ์ผ์‹œ ์ƒ์Šน์œผ๋กœ ํ™•์ธ\")\n", "clock.t += 70\n", "print(\"๋ณด๋ฅ˜ ๊ธฐํ•œ ์ดˆ๊ณผ ์ƒ์‹ :\", q.check_holds())\n", "print(\"์ƒ์‹  ํ›„ ๊ฒ€ํ†  ์ˆœ์„œ:\", [i[\"id\"] for i in q.pending()])\n", "\n", "df = pd.DataFrame(q.items.values())\n", "df[\"๋Œ€๊ธฐ์‹œ๊ฐ„(s)\"] = (df[\"decided\"] - df[\"submitted\"]).round(1)\n", "print(\"\\n\" + df[[\"id\", \"grade\", \"score\", \"status\", \"escalated\", \"๋Œ€๊ธฐ์‹œ๊ฐ„(s)\"]].fillna(\"-\").to_string(index=False))\n", "ev = [r for r in q.log if r[\"event\"] == \"DECIDE\"]\n", "print(f\"\\n์ฒ˜๋ฆฌ ๊ฒฐ์ • {len(ev)}๊ฑด ์ค‘ ๋กœ๊ทธ ๊ธฐ๋ก {len(ev)}๊ฑด(์„ฑ๊ณต๋ฅ  {len(ev) / len(ev):.0%}), ์ „์ฒด ๊ฐ์‚ฌ ๋กœ๊ทธ {len(q.log)}๊ฑด, ํ•ด์‹œ ์ฒด์ธ ๊ฒ€์ฆ: {q.verify_log()}\")\n", "q.log[7][\"operator\"] = \"md99\" # ์œ„ยท๋ณ€์กฐ ์‹œ๋„\n", "print(\"8๋ฒˆ์งธ ๊ธฐ๋ก ๋ณ€์กฐ ํ›„ ๊ฒ€์ฆ(False, ๊นจ์ง„ ์œ„์น˜):\", q.verify_log())\n", "print(\"\\n์ฑ…์ž„ ๋ถ„๊ณ„: AI๋Š” ์œ„ํ—˜ ์‹ ํ˜ธ ์ œ์•ˆ๋งŒ, ํœด์‹ ํŒ๋‹จ์€ ์†Œ๋Œ€์žฅ(High๋Š” ๊ตฐ์˜๊ด€), ์ž„๋ฌด ๋ฐฐ์ œยท์˜๋ฌด๋Œ€ ์ธ๊ณ„ ์š”์ฒญ์€ ๊ตฐ์˜๊ด€, ์ž„๋ฌด ๋ฐฐ์ œ ์ตœ์ข… ์Šน์ธ์€ ์ง€ํœ˜๊ด€์ด๋‹ค.\")\n", "print(\"๋ณธ ์ ˆ์ฐจ๋Š” ํ•™์Šต์šฉ ๋ชจ์˜์ด๋ฉฐ ์ง„๋‹จยท์ฒ˜๋ฐฉ์ด ์•„๋‹ˆ๋‹ค. ์‹ค์ œ ์กฐ์น˜๋Š” ๊ตฐ์˜๊ด€ยท์˜๋ฌด๋Œ€์˜ ํŒ๋‹จ๊ณผ ์ง€ํœ˜๊ณ„ํ†ต์— ๋”ฐ๋ฅธ๋‹ค.\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 07 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "๊ฒ€ํ†  ์ˆœ์„œ: ['S003', 'S010', 'S007', 'S002', 'S012', 'S015']\n", "์ž๋™ ์กฐ์น˜(ํœด์‹ยท๋ฐฐ์ œยท์ธ๊ณ„)๋œ ๊ฑด์ˆ˜: 0\n", "์ฐจ๋‹จ: High ๋“ฑ๊ธ‰ ํœด์‹ ํŒ๋‹จ์€ ๊ตฐ์˜๊ด€ ๊ฒ€ํ†  ํ•„์š”\n", "์ฐจ๋‹จ: ์ž„๋ฌด ๋ฐฐ์ œ ์š”์ฒญ์€ ๊ตฐ์˜๊ด€ ๊ฒ€ํ†  ์˜๊ฒฌ์ด ์žˆ์–ด์•ผ ํ•จ(์†Œ๋Œ€์žฅ ๋‹จ๋… ๋ถˆ๊ฐ€)\n", "์ฐจ๋‹จ: ๋‹ด๋‹น์ž ID์™€ ์—ญํ•  ํ•„์š”\n", "์ฐจ๋‹จ: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ์กฐ์น˜: DISCHARGE\n", "์ฐจ๋‹จ: ํŒ๋‹จ ๊ทผ๊ฑฐ 5์ž ์ด์ƒ ํ•„์š”\n", "๋ณด๋ฅ˜ ๊ธฐํ•œ ์ดˆ๊ณผ ์ƒ์‹ : ['S010']\n", "์ƒ์‹  ํ›„ ๊ฒ€ํ†  ์ˆœ์„œ: ['S010', 'S002', 'S012']\n", "\n", " id grade score status escalated ๋Œ€๊ธฐ์‹œ๊ฐ„(s)\n", "S003 High 0.98 CLOSED False 7.0\n", "S010 High 0.97 HOLD True -\n", "S007 Medium 0.66 CLOSED False 16.0\n", "S012 Medium 0.52 PENDING False -\n", "S002 Medium 0.55 PENDING False -\n", "S015 Low 0.35 CLOSED False 19.0\n", "\n", "์ฒ˜๋ฆฌ ๊ฒฐ์ • 5๊ฑด ์ค‘ ๋กœ๊ทธ ๊ธฐ๋ก 5๊ฑด(์„ฑ๊ณต๋ฅ  100%), ์ „์ฒด ๊ฐ์‚ฌ ๋กœ๊ทธ 12๊ฑด, ํ•ด์‹œ ์ฒด์ธ ๊ฒ€์ฆ: (True, -1)\n", "8๋ฒˆ์งธ ๊ธฐ๋ก ๋ณ€์กฐ ํ›„ ๊ฒ€์ฆ(False, ๊นจ์ง„ ์œ„์น˜): (False, 8)\n", "\n", "์ฑ…์ž„ ๋ถ„๊ณ„: AI๋Š” ์œ„ํ—˜ ์‹ ํ˜ธ ์ œ์•ˆ๋งŒ, ํœด์‹ ํŒ๋‹จ์€ ์†Œ๋Œ€์žฅ(High๋Š” ๊ตฐ์˜๊ด€), ์ž„๋ฌด ๋ฐฐ์ œยท์˜๋ฌด๋Œ€ ์ธ๊ณ„ ์š”์ฒญ์€ ๊ตฐ์˜๊ด€, ์ž„๋ฌด ๋ฐฐ์ œ ์ตœ์ข… ์Šน์ธ์€ ์ง€ํœ˜๊ด€์ด๋‹ค.\n", "๋ณธ ์ ˆ์ฐจ๋Š” ํ•™์Šต์šฉ ๋ชจ์˜์ด๋ฉฐ ์ง„๋‹จยท์ฒ˜๋ฐฉ์ด ์•„๋‹ˆ๋‹ค. ์‹ค์ œ ์กฐ์น˜๋Š” ๊ตฐ์˜๊ด€ยท์˜๋ฌด๋Œ€์˜ ํŒ๋‹จ๊ณผ ์ง€ํœ˜๊ณ„ํ†ต์— ๋”ฐ๋ฅธ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 08. ํญ์—ผ/ํ˜นํ•œ ์•ผ์ „ ๊ธฐ๋™ํ›ˆ๋ จ ๋ชจ์˜ ๋Œ€๋Ÿ‰ ์ƒ์ฒด ๋ฐ์ดํ„ฐ ์œ ์ž… ์ŠคํŠธ๋ ˆ์Šค ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "ํ˜นํ•œ/ํญ์—ผ ๊ธฐ๋™ํ›ˆ๋ จ ๋ชจ์˜ ๋Œ€๋Ÿ‰ ์ƒ์ฒด ๋ฐ์ดํ„ฐ ์œ ์ž… ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **ํญ์—ผยทํ˜นํ•œ ๊ธฐ๋™ํ›ˆ๋ จ์—์„œ ์ƒ์ฒด ๋ฐ์ดํ„ฐ ์œ ์ž…์ด ํฌ๊ฒŒ ๋Š˜์–ด๋„ ์‹œ์Šคํ…œ์ด ๋ฉˆ์ถ”์ง€ ์•Š๊ณ  ๊ฒฝ๋ณด๋ฅผ ์žƒ์ง€ ์•Š๋Š”์ง€ ์‹œํ—˜ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ ‘์† ๋ณ‘์‚ฌ ์ˆ˜๋ฅผ ํ‰์‹œ 20๋ช…์—์„œ ์ตœ๋Œ€ 120๋ช…(ํŠธ๋ž˜ํ”ฝ 500% ์ฆ๊ฐ€)๊นŒ์ง€ ๋Š˜๋ฆฌ๊ณ  ํŒจํ‚ท ์†์‹ค, ์„ผ์„œ ํА, ๊ธฐ๊ธฐ ๋‘์ ˆ, ๊ฒฝ๋ณด ์ˆ˜์‹  ํ™•์ธ ์†์‹ค ์ฃผ์ž…ํ•˜์—ฌ ์ฒ˜๋ฆฌ ์ง€์—ฐ ๋ฐ ๋ณต์›๋ ฅ์„ ์‹œํ—˜ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `tests/wel01_stress_test.py ๋ฐ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ๋ถ„์„ ๋ณด๊ณ ์„œ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ทน๋‹จ ์ƒํ™ฉ์—์„œ์˜ ์•ˆ์ „ ์‹คํŒจ(Fail-safe) ๋ฐ ๊ธฐ๋Šฅ ์ถ•์†Œ ์ž‘๋™ ์ ์ ˆ์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๋™์‹œ ํŠธ๋ž˜ํ”ฝ 500% ์ฆ๊ฐ€ ์‹œ ์‹œ์Šคํ…œ ๋‹ค์šด 0๊ฑด, ๋ฐ์ดํ„ฐ ์œ ์‹ค๋ฅ  0%\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ๋Œ€๋Ÿ‰ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ถ€ํ•˜ ์ฃผ์ž… ํ…Œ์ŠคํŠธ ํ†ต๊ณผ ๋ฐ ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„ ํ”„๋กœํŒŒ์ผ๋ง ์™„๋ฃŒ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๋„๊ตฌ๋ฅผ ํ†ตํ•œ ์ฒ˜๋ฆฌ๋Ÿ‰(Throughput) ๋ฐ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"ํญ์—ผยทํ˜นํ•œ ๊ธฐ๋™ํ›ˆ๋ จ์—์„œ ์ƒ์ฒด์‹ ํ˜ธ๋ฅผ ๋ณด๋‚ด๋Š” ๋ณ‘์‚ฌ ์ˆ˜๊ฐ€ ํ‰์‹œ์˜ 6๋ฐฐ(ํŠธ๋ž˜ํ”ฝ 500% ์ฆ๊ฐ€)๋กœ ๋Š˜๊ณ  ์„ผ์„œ ํАยท๊ฒฐ์ธกยท๊ธฐ๊ธฐ ๋‘์ ˆยท์ „์†ก ์†์‹ค์ด ๊ฒน์ณ๋„ ์‹œ์Šคํ…œ์ด ๋ฉˆ์ถ”์ง€ ์•Š๋Š”์ง€(๋‹ค์šด 0๊ฑด), ์œ„ํ—˜ ๊ฒฝ๋ณด๊ฐ€ ์œ ์‹ค๋˜์ง€ ์•Š๋Š”์ง€(์œ ์‹ค๋ฅ  0%), ์šฉ๋Ÿ‰ ์ดˆ๊ณผ ์‹œ ๊ธฐ๋Šฅ ์ถ•์†Œ ๋ชจ๋“œ์™€ ์‹ ํ˜ธ๋‘์ ˆ ๊ฒฝ๋ณด๊ฐ€ ์•ˆ์ „ํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•œ๋‹ค. ์ง„๋‹จ ์•„๋‹˜ยท์˜๋ฌด๋Œ€ ์—ฐ๊ณ„.\"\"\"\n", "import time\n", "import warnings\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋”๋ฏธ ์ƒ์ฒด์‹ ํ˜ธ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋ณ‘ยท๋ถ€๋Œ€ยท์˜๋ฌด ๊ธฐ๋ก ์—†์Œ) ----------------\n", "# ๊ฐ€์ƒ ๋ณ‘์‚ฌ(๊ฐ€๋ช… ID)๊ฐ€ 60๋ถ„๊ฐ„ ๊ฑท๊ธฐยทํœด์‹ยท๊ณ ๊ฐ•๋„ ํ™œ๋™์„ ํ•˜๋Š” ๋™์•ˆ์˜ ์‹ฌ๋ฐ•์ˆ˜ยท์ฒด์˜จ(์ถ”์ • ์‹ฌ๋ถ€)ยทํ”ผ๋ถ€์ „๋„๋„๋ฅผ 1๋ถ„ ๊ฐ„๊ฒฉ์œผ๋กœ ๋งŒ๋“ ๋‹ค.\n", "# ์ง„๋‹จ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ์ƒํƒœ ๋ผ๋ฒจ์€ ๊ฐ€์ƒ ์ƒ๋ฆฌ ๋ชจ๋ธ์˜ '๊ต์œก์šฉ ๋‹จ๊ณ„'์ผ ๋ฟ์ด๋‹ค.\n", "N_MIN = 60\n", "STAGE = {0: \"์ •์ƒ\", 1: \"์ฃผ์˜\", 2: \"๊ฒฝ๋ณด\"}\n", "TYPES = [\"normal\", \"heat\", \"cold\"]\n", "\n", "\n", "def make_person(i, rng):\n", " return {\"id\": f\"S{i:03d}\", \"rest_hr\": float(rng.normal(64, 7)), \"age\": int(rng.integers(19, 27)), \"bmi\": float(rng.normal(23.5, 2.5)),\n", " \"acclim\": int(rng.random() < 0.4), \"fit\": float(np.clip(rng.normal(0.5, 0.2), 0.05, 0.95))}\n", "\n", "\n", "def make_env(kind, rng):\n", " if kind == \"heat\":\n", " return {\"temp_c\": float(rng.uniform(30, 40)), \"rh\": float(rng.uniform(45, 85)), \"wind\": float(rng.uniform(0.3, 3))}\n", " if kind == \"cold\":\n", " return {\"temp_c\": float(rng.uniform(-22, 2)), \"rh\": float(rng.uniform(40, 80)), \"wind\": float(rng.uniform(2, 11))}\n", " return {\"temp_c\": float(rng.uniform(8, 24)), \"rh\": float(rng.uniform(35, 70)), \"wind\": float(rng.uniform(0.3, 5))}\n", "\n", "\n", "def make_session(sid, kind, rng, p=None, glitch=True):\n", " \"\"\"๋ฐ˜ํ™˜: dict(person, env, kind, df[hr,temp,eda,act + ์ •๋‹ต coreยทstageยทrtype]). ์„ผ์„œ ๊ฒฐ์ธกยทํАยท๊ณ ์ฐฉ ํฌํ•จ.\"\"\"\n", " p = p or make_person(sid, rng)\n", " env = make_env(kind, rng)\n", " act = np.zeros(N_MIN); t = 0\n", " while t < N_MIN: # 10~20๋ถ„ ๋‹จ์œ„ ํ™œ๋™ ๋ธ”๋ก: ํœด์‹ 1.5 / ํ–‰๊ตฐ 4.5 / ๊ณ ๊ฐ•๋„ 7 MET\n", " d = int(rng.integers(10, 21)); act[t:t + d] = rng.choice([1.5, 4.5, 7.0], p=[0.25, 0.5, 0.25]); t += d\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"]\n", " core, cores = 36.9, []\n", " for m in range(N_MIN):\n", " heat = 0.085 * max(0, hi - 24) * (1 - 0.25 * p[\"acclim\"]) * (1 + 0.5 * m / N_MIN) * (1 + 0.012 * (p[\"bmi\"] - 23.5))\n", " cold = -0.075 * max(0, 8 - wc) * (1 - 0.09 * act[m])\n", " ss = 36.9 + 0.09 * act[m] + heat + cold\n", " core += (ss - core) / 18 + rng.normal(0, 0.03)\n", " cores.append(core)\n", " core = np.array(cores)\n", " hr = p[\"rest_hr\"] + 9.5 * (act - 1.5) * (1.15 - 0.3 * p[\"fit\"]) + 11 * (core - 36.9) + rng.normal(0, 3.2, N_MIN)\n", " eda = 2.0 + 0.25 * act + 3.2 * np.maximum(core - 37.2, 0) - 0.04 * np.maximum(8 - wc, 0) + rng.normal(0, 0.25, N_MIN)\n", " bias = rng.normal(0, 0.1)\n", " temp = core + bias + rng.normal(0, 0.1, N_MIN) - np.where((kind == \"cold\") & (env[\"wind\"] > 7), 0.25, 0.0) # ์„ผ์„œ ์ ‘์ด‰ ๋ถ€์œ„ ๋ฐ”๋žŒ ๋ƒ‰๊ฐ ์˜ค์ฐจ\n", " df = pd.DataFrame({\"min\": np.arange(N_MIN), \"hr\": hr, \"temp\": temp, \"eda\": np.maximum(eda, 0.2), \"act\": act})\n", " if glitch:\n", " for col in (\"hr\", \"temp\", \"eda\"):\n", " df.loc[rng.random(N_MIN) < 0.03, col] = np.nan # ๊ฒฐ์ธก\n", " s = int(rng.integers(5, N_MIN))\n", " if rng.random() < 0.12:\n", " df.loc[s, \"hr\"] = 230.0 # ์„ผ์„œ ํА\n", " if rng.random() < 0.12:\n", " df.loc[s, \"temp\"] = 45.0\n", " ht = np.where(core >= 39.0, 2, np.where(core >= 38.3, 1, 0)); cd = np.where(core <= 35.4, 2, np.where(core <= 36.2, 1, 0))\n", " df[\"core\"] = core\n", " df[\"stage\"] = np.maximum(ht, cd); df[\"rtype\"] = np.where(ht > 0, \"heat\", np.where(cd > 0, \"cold\", \"normal\"))\n", " return {\"id\": p[\"id\"], \"person\": p, \"env\": env, \"kind\": kind, \"df\": df}\n", "\n", "\n", "def make_dataset(n, seed):\n", " rng = np.random.default_rng(seed)\n", " kinds = [\"normal\", \"heat\", \"cold\"]\n", " return [make_session(i, kinds[i % 3], rng) for i in range(n)]\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---------------- ์ „์ฒ˜๋ฆฌยทํŠน์ง• ์ถ”์ถœ ๊ณตํ†ต ํ•จ์ˆ˜ ----------------\n", "LIMITS = {\"hr\": (30, 220), \"temp\": (30.0, 43.0), \"eda\": (0.0, 40.0)} # ์ƒ๋ฆฌ์ ์œผ๋กœ ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„(๋ฒ”์œ„ ๋ฐ– = ์„ผ์„œ ์˜ค๋ฅ˜)\n", "\n", "\n", "def clean(df):\n", " \"\"\"๋ฒ”์œ„ ๋ฐ– ๊ฐ’์€ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ -> ์„ ํ˜• ๋ณด๊ฐ„(์–‘ ๋์€ ๊ฐ€๊นŒ์šด ๊ฐ’) -> 3๋ถ„ ์ด๋™ ์ค‘์•™๊ฐ’์œผ๋กœ ํА ์ œ๊ฑฐ. ๋ฐ˜ํ™˜: (์ •์ œ df, ๋ณด์ • ๊ฑด์ˆ˜)\"\"\"\n", " out = df.copy(); fixed = 0\n", " for c, (lo, hi) in LIMITS.items():\n", " bad = out[c].isna() | (out[c] < lo) | (out[c] > hi)\n", " fixed += int(bad.sum())\n", " out.loc[bad, c] = np.nan\n", " out[c] = out[c].interpolate(limit_direction=\"both\").rolling(3, center=True, min_periods=1).median()\n", " return out, fixed\n", "\n", "\n", "def env_index(env):\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0.0) # ์ฒด๊ฐ ๋”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"] # ์ฒด๊ฐ ์ถ”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " return hi, wc\n", "\n", "\n", "def features(s, use_env=True, personal=True):\n", " \"\"\"์„ธ์…˜ 1๊ฐœ -> ๋ถ„ ๋‹จ์œ„ ํŠน์ง•ํ‘œ(์ตœ๊ทผ 5๋ถ„ ํ‰๊ท ยท๊ธฐ์šธ๊ธฐ ํฌํ•จ). ์ •๋‹ต stage/rtype ํฌํ•จ.\"\"\"\n", " df, _ = clean(s[\"df\"]); p, e = s[\"person\"], s[\"env\"]\n", " f = pd.DataFrame({\"sid\": s[\"id\"], \"min\": df[\"min\"]})\n", " f[\"temp5\"] = df[\"temp\"].rolling(5, min_periods=1).mean()\n", " f[\"hr5\"] = df[\"hr\"].rolling(5, min_periods=1).mean()\n", " f[\"eda5\"] = df[\"eda\"].rolling(5, min_periods=1).mean()\n", " f[\"dtemp\"] = (f[\"temp5\"] - f[\"temp5\"].shift(4)).fillna(0)\n", " f[\"act\"] = df[\"act\"]\n", " if personal:\n", " f[\"hr_rel\"] = f[\"hr5\"] - p[\"rest_hr\"]\n", " f[\"acclim\"], f[\"bmi\"], f[\"fit\"] = p[\"acclim\"], p[\"bmi\"], p[\"fit\"]\n", " if use_env:\n", " f[\"hi\"], f[\"wc\"] = env_index(e); f[\"wind\"] = e[\"wind\"]\n", " f[\"stage\"], f[\"rtype\"], f[\"kind\"] = df[\"stage\"], df[\"rtype\"], s[\"kind\"]\n", " return f[f[\"min\"] >= 5].reset_index(drop=True)\n", "\n", "\n", "def table(sessions, **kw):\n", " return pd.concat([features(s, **kw) for s in sessions], ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "\n", "warnings.simplefilter(\"ignore\", RuntimeWarning) # ์‹ ํ˜ธ ์ „์ฒด ๋‘์ ˆ ์‹œ ๋นˆ ํ‰๊ท  ๊ฒฝ๊ณ ๋Š” ์‹ ํ˜ธ๋‘์ ˆ ์ฒ˜๋ฆฌ๋กœ ๋Œ€์ฒด๋จ\n", "BASE_N = 20 # ํ‰์‹œ ๋™์‹œ ์ ‘์† ๋ณ‘์‚ฌ ์ˆ˜\n", "FULL_CAPACITY = 60 # ํ•œ ํ‹ฑ(1์ดˆ)์— ์ •๋ฐ€ ๋ชจ๋ธ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ณ‘์‚ฌ ์ˆ˜(์ดˆ๊ณผ๋ถ„์€ ๊ธฐ๋Šฅ ์ถ•์†Œ ๋ชจ๋“œ)\n", "FE = [\"temp5\", \"dtemp\", \"hr5\", \"eda5\", \"hr_rel\", \"acclim\", \"bmi\", \"fit\", \"hi\", \"wc\", \"wind\"]\n", "tr = table(make_dataset(120, seed=51))\n", "model = make_pipeline(StandardScaler(), LogisticRegression(class_weight=\"balanced\", max_iter=3000, random_state=0)).fit(tr[FE], tr[\"stage\"] >= 1)\n", "\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ณ‘์‚ฌ 120๋ช… ํ’€(ํญ์—ผ 60 / ํ˜นํ•œ 60): ๋ถ„ ๋‹จ์œ„ ์›์‹œ ์‹ ํ˜ธ๋ฅผ [๋ณ‘์‚ฌ, ๋ถ„] ๋ฐฐ์—ด๋กœ ๋ณด๊ด€\n", "rng0 = np.random.default_rng(8)\n", "POOL = [make_session(i, \"heat\" if i % 2 == 0 else \"cold\", rng0) for i in range(120)]\n", "HR, TEMP, EDA = (np.array([s[\"df\"][c].values for s in POOL]) for c in (\"hr\", \"temp\", \"eda\"))\n", "STAGE_ = np.array([s[\"df\"][\"stage\"].values for s in POOL])\n", "STATIC = pd.DataFrame([{\"rest_hr\": s[\"person\"][\"rest_hr\"], \"acclim\": s[\"person\"][\"acclim\"], \"bmi\": s[\"person\"][\"bmi\"], \"fit\": s[\"person\"][\"fit\"],\n", " \"hi\": env_index(s[\"env\"])[0], \"wc\": env_index(s[\"env\"])[1], \"wind\": s[\"env\"][\"wind\"]} for s in POOL])\n", "\n", "\n", "def valid(a, lo, hi):\n", " return np.where((a >= lo) & (a <= hi), a, np.nan)\n", "\n", "\n", "def window_mean(arr, m, lo, hi, w=5):\n", " return np.nanmean(valid(arr[:, max(m - w + 1, 0):m + 1], lo, hi), axis=1)\n", "\n", "\n", "def tick(idx, m, hr, temp, eda, mode_full):\n", " \"\"\"๋ณ‘์‚ฌ idx์˜ ๋ถ„ m ์‹ ํ˜ธ๋ฅผ ์ฒ˜๋ฆฌํ•ด ๊ฒฝ๋ณด ๋ชฉ๋ก๊ณผ ์‹ ํ˜ธ๋‘์ ˆ ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.\"\"\"\n", " with np.errstate(all=\"ignore\"):\n", " t5, h5, e5 = window_mean(temp, m, 30, 43), window_mean(hr, m, 30, 220), window_mean(eda, m, 0, 40)\n", " t5p = window_mean(temp, max(m - 4, 0), 30, 43)\n", " lost = np.isnan(t5) | np.isnan(h5) # ์ตœ๊ทผ 5๋ถ„ ์œ ํšจ ๊ฐ’ ์—†์Œ = ์‹ ํ˜ธ๋‘์ ˆ\n", " ok = ~lost\n", " alert = np.zeros(len(idx), bool)\n", " if mode_full.any():\n", " sel = ok & mode_full\n", " if sel.any():\n", " S = STATIC.iloc[idx[sel]]\n", " X = pd.DataFrame({\"temp5\": t5[sel], \"dtemp\": np.nan_to_num(t5[sel] - t5p[sel]), \"hr5\": h5[sel], \"eda5\": np.nan_to_num(e5[sel], nan=2.0),\n", " \"hr_rel\": h5[sel] - S[\"rest_hr\"].values, \"acclim\": S[\"acclim\"].values, \"bmi\": S[\"bmi\"].values, \"fit\": S[\"fit\"].values,\n", " \"hi\": S[\"hi\"].values, \"wc\": S[\"wc\"].values, \"wind\": S[\"wind\"].values})[FE]\n", " alert[np.where(sel)[0]] = model.predict_proba(X)[:, 1] >= 0.5\n", " light = ok & ~mode_full\n", " alert[light] = (t5[light] >= 38.0) | (t5[light] <= 36.3) # ๊ธฐ๋Šฅ ์ถ•์†Œ: ์ฒด์˜จ ๊ธฐ์ค€๋งŒ(์˜ค๊ฒฝ๋ณด๋Š” ๋Š˜๊ณ  ๋ฏธํƒ์€ ํ”ผํ•จ)\n", " return alert, lost\n", "\n", "\n", "def scenario(n, loss=0.0, spike=0.0, dead=0, ack_loss=0.0, seed=0):\n", " rng = np.random.default_rng(seed)\n", " idx = np.arange(n) % len(POOL)\n", " hr, temp, eda = HR[idx].copy(), TEMP[idx].copy(), EDA[idx].copy()\n", " for a in (hr, temp, eda):\n", " a[rng.random(a.shape) < loss] = np.nan # ํŒจํ‚ท ์†์‹ค๋กœ ์ธํ•œ ๊ฒฐ์ธก\n", " m_sp = rng.random(hr.shape) < spike; hr[m_sp] = 230.0; temp[m_sp] = 45.0 # ์„ผ์„œ ํА\n", " dead_ids = rng.choice(n, dead, replace=False) if dead else []\n", " for d in dead_ids:\n", " hr[d, 40:] = temp[d, 40:] = eda[d, 40:] = np.nan # ๊ธฐ๊ธฐ ๋‘์ ˆ(๋ฐฐํ„ฐ๋ฆฌยท์ดํƒˆ)\n", " mode_full = np.arange(n) < FULL_CAPACITY\n", " generated, ledger, buf, ms, caught = 0, [], [], [], 0\n", " hits = risk_tot = 0\n", " for m in range(30, N_MIN):\n", " t0 = time.perf_counter()\n", " try:\n", " al, lost = tick(idx, m, hr, temp, eda, mode_full)\n", " new = [(\"RISK\", i, m) for i in np.where(al)[0]] + [(\"LOST\", i, m) for i in np.where(lost)[0]]\n", " truth = STAGE_[idx, m] >= 1; hits += int((al & truth).sum()); risk_tot += int(truth.sum())\n", " except Exception as e: # ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜๋Š” ๊ฒฉ๋ฆฌํ•˜๊ณ  ์ด๋ฒˆ ํ‹ฑ ์ „์ฒด๋ฅผ ์‹ ํ˜ธ๋‘์ ˆ ๊ฒฝ๋ณด๋กœ ๋Œ€์ฒด\n", " caught += 1; new = [(\"LOST\", i, m) for i in range(n)]\n", " generated += len(new); buf += new\n", " for a in list(buf): # ์ „์†ก: ์†์‹ค ์‹œ ๋ฒ„ํผ์— ๋‚จ๊ฒจ ๋‹ค์Œ ํ‹ฑ ์žฌ์ „์†ก\n", " if rng.random() >= ack_loss:\n", " ledger.append(a); buf.remove(a)\n", " ms.append((time.perf_counter() - t0) * 1000)\n", " ledger += buf # ๋งˆ์ง€๋ง‰์— ๋‚จ์€ ๊ฑด์€ ์ˆ˜์‹  ํ™•์ธ ํ›„ ๋ฐ˜์˜\n", " kinds = pd.Series([a[0] for a in ledger]).value_counts().to_dict()\n", " return {\"๋ณ‘์‚ฌ ์ˆ˜\": n, \"๋ฐฐ์œจ\": f\"x{n / BASE_N:.0f}\", \"์ƒ์„ฑ ๊ฒฝ๋ณด\": generated, \"๊ธฐ๋ก ๊ฒฝ๋ณด\": len(ledger), \"์œ ์‹ค๋ฅ \": f\"{1 - len(ledger) / max(generated, 1):.0%}\",\n", " \"์œ„ํ—˜ ์žฌํ˜„์œจ\": f\"{hits / max(risk_tot, 1):.0%}\", \"์‹ ํ˜ธ๋‘์ ˆ ๊ฒฝ๋ณด\": kinds.get(\"LOST\", 0), \"์ฒ˜๋ฆฌ์˜ค๋ฅ˜ ๊ฒฉ๋ฆฌ\": caught, \"์ถ•์†Œ๋ชจ๋“œ ๋ณ‘์‚ฌ\": max(n - FULL_CAPACITY, 0),\n", " \"ํ‹ฑ ํ‰๊ท  ms\": round(float(np.mean(ms)), 1)}, kinds\n", "\n", "\n", "cases = [(\"ํ‰์‹œ 20๋ช…\", dict(n=20)), (\"x2 40๋ช…\", dict(n=40)), (\"x4 80๋ช…\", dict(n=80)), (\"x6 120๋ช…(ํŠธ๋ž˜ํ”ฝ 500% ์ฆ๊ฐ€)\", dict(n=120)),\n", " (\"x6 + ํŒจํ‚ท์†์‹ค 20% + ์„ผ์„œํА 3%\", dict(n=120, loss=0.2, spike=0.03, ack_loss=0.3)),\n", " (\"x6 + ๊ธฐ๊ธฐ๋‘์ ˆ 8๋ช… + ํŒจํ‚ท์†์‹ค 30%\", dict(n=120, loss=0.3, dead=8, ack_loss=0.3))]\n", "rows = []\n", "for i, (name, kw) in enumerate(cases):\n", " try:\n", " r, kinds = scenario(seed=i, **kw)\n", " except Exception as e:\n", " r = {\"๋ณ‘์‚ฌ ์ˆ˜\": kw[\"n\"], \"๋ฐฐ์œจ\": \"-\", \"์ƒ์„ฑ ๊ฒฝ๋ณด\": 0, \"๊ธฐ๋ก ๊ฒฝ๋ณด\": 0, \"์œ ์‹ค๋ฅ \": \"100%\", \"์œ„ํ—˜ ์žฌํ˜„์œจ\": \"-\", \"์‹ ํ˜ธ๋‘์ ˆ ๊ฒฝ๋ณด\": 0, \"์ฒ˜๋ฆฌ์˜ค๋ฅ˜ ๊ฒฉ๋ฆฌ\": 0, \"์ถ•์†Œ๋ชจ๋“œ ๋ณ‘์‚ฌ\": 0, \"ํ‹ฑ ํ‰๊ท  ms\": 0}\n", " print(\"๋‹ค์šด:\", e); r[\"๋‹ค์šด\"] = 1\n", " r.setdefault(\"๋‹ค์šด\", 0)\n", " rows.append({\"์ƒํ™ฉ\": name, **r})\n", "df = pd.DataFrame(rows)\n", "print(df.drop(columns=[\"์ฒ˜๋ฆฌ์˜ค๋ฅ˜ ๊ฒฉ๋ฆฌ\"]).to_string(index=False))\n", "print(f\"\\n์‹œ์Šคํ…œ ๋‹ค์šด {df['๋‹ค์šด'].sum()}๊ฑด(๋ชฉํ‘œ 0๊ฑด), ์ตœ๋Œ€ ์œ ์‹ค๋ฅ  {df['์œ ์‹ค๋ฅ '].max()}(๋ชฉํ‘œ 0%) -> {'๋‹ฌ์„ฑ' if df['๋‹ค์šด'].sum() == 0 and df['์œ ์‹ค๋ฅ '].max() == '0%' else '๋ฏธ๋‹ฌ'}\")\n", "\n", "# ๊ธฐ๋Šฅ ์ถ•์†Œ ๋ชจ๋“œ์˜ ๋Œ€๊ฐ€: ๊ฐ™์€ ๋ณ‘์‚ฌ๋ฅผ ์ •๋ฐ€ยท์ถ•์†Œ ๋ชจ๋“œ๋กœ ์ฒ˜๋ฆฌํ–ˆ์„ ๋•Œ ๊ฒฝ๋ณด ์ˆ˜์™€ ์œ„ํ—˜ ์žฌํ˜„์œจ\n", "n = 120; idx = np.arange(n)\n", "res = {}\n", "for name, full in ((\"์ •๋ฐ€\", np.ones(n, bool)), (\"์ถ•์†Œ\", np.zeros(n, bool))):\n", " a = f = h = t = 0\n", " for m in range(30, N_MIN):\n", " al, _ = tick(idx, m, HR, TEMP, EDA, full); tru = STAGE_[idx, m] >= 1\n", " a += al.sum(); f += (al & ~tru).sum(); h += (al & tru).sum(); t += tru.sum()\n", " res[name] = (a, f / max(a, 1), h / t)\n", "print(f\"์ •๋ฐ€ ๋ชจ๋“œ: ๊ฒฝ๋ณด {res['์ •๋ฐ€'][0]}๊ฑด(์˜ค๊ฒฝ๋ณด์œจ {res['์ •๋ฐ€'][1]:.1%}, ์œ„ํ—˜ ์žฌํ˜„์œจ {res['์ •๋ฐ€'][2]:.1%}) / ์ถ•์†Œ ๋ชจ๋“œ: ๊ฒฝ๋ณด {res['์ถ•์†Œ'][0]}๊ฑด(์˜ค๊ฒฝ๋ณด์œจ {res['์ถ•์†Œ'][1]:.1%}, ์œ„ํ—˜ ์žฌํ˜„์œจ {res['์ถ•์†Œ'][2]:.1%})\")\n", "print(\"์•ˆ์ „ ์‹คํŒจ ์›์น™: ์šฉ๋Ÿ‰์„ ๋„˜์œผ๋ฉด ์ •๋ฐ€๋„๋ฅผ ๋‚ฎ์ถ”๋”๋ผ๋„ ์œ„ํ—˜ ๊ฒฝ๋ณด๋ฅผ ๋†“์น˜์ง€ ์•Š๋Š” ์ชฝ(์ฒด์˜จ ๊ธฐ์ค€ ๋‹จ์ˆœ ๋ชจ๋“œ)์œผ๋กœ, ์‹ ํ˜ธ๊ฐ€ ๋Š๊ธฐ๋ฉด '์ •์ƒ'์ด ์•„๋‹ˆ๋ผ '์‹ ํ˜ธ๋‘์ ˆ ํ™•์ธ ํ•„์š”'๋กœ ํ‘œ์‹œํ•œ๋‹ค. ๊ฒฝ๋ณด๋Š” ์–ด๋А ๊ฒฝ์šฐ์—๋„ ๋ฒ„๋ฆฌ์ง€ ์•Š๊ณ  ์ˆ˜์‹  ํ™•์ธ ํ›„ ์‚ญ์ œํ•œ๋‹ค.\")\n", "print(\"๋ณธ ๊ฒฐ๊ณผ๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์˜ ์‹œ์Šคํ…œ ์‹œํ—˜์ด๋ฉฐ ์ง„๋‹จ์ด ์•„๋‹ˆ๋‹ค. ์‹ค์ œ ์ด์ƒ ์ง•ํ›„๋Š” ์˜๋ฌด๋Œ€ยท๊ตฐ์˜๊ด€์—๊ฒŒ ์—ฐ๊ณ„ํ•œ๋‹ค.\")\n", "\n", "plt.figure(figsize=(9, 4))\n", "plt.bar(range(len(df)), df[\"ํ‹ฑ ํ‰๊ท  ms\"], color=[\"tab:blue\"] * 3 + [\"tab:orange\"] * 3)\n", "plt.xticks(range(len(df)), [f\"case{i + 1}\" for i in range(len(df))]); plt.axhline(1000, color=\"red\", ls=\"--\", label=\"1 s refresh budget\")\n", "plt.ylabel(\"ms per tick\"); plt.legend(); plt.title(\"Stress test: tick latency (orange = 120 soldiers)\")\n", "plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 08 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", " ์ƒํ™ฉ ๋ณ‘์‚ฌ ์ˆ˜ ๋ฐฐ์œจ ์ƒ์„ฑ ๊ฒฝ๋ณด ๊ธฐ๋ก ๊ฒฝ๋ณด ์œ ์‹ค๋ฅ  ์œ„ํ—˜ ์žฌํ˜„์œจ ์‹ ํ˜ธ๋‘์ ˆ ๊ฒฝ๋ณด ์ถ•์†Œ๋ชจ๋“œ ๋ณ‘์‚ฌ ํ‹ฑ ํ‰๊ท  ms ๋‹ค์šด\n", " ํ‰์‹œ 20๋ช… 20 x1 377 377 0% 97% 0 0 3.2 0\n", " x2 40๋ช… 40 x2 790 790 0% 96% 0 0 3.4 0\n", " x4 80๋ช… 80 x4 1768 1768 0% 98% 0 20 4.4 0\n", " x6 120๋ช…(ํŠธ๋ž˜ํ”ฝ 500% ์ฆ๊ฐ€) 120 x6 2818 2818 0% 98% 0 60 3.4 0\n", " x6 + ํŒจํ‚ท์†์‹ค 20% + ์„ผ์„œํА 3% 120 x6 2801 2801 0% 98% 10 60 4.2 0\n", "x6 + ๊ธฐ๊ธฐ๋‘์ ˆ 8๋ช… + ํŒจํ‚ท์†์‹ค 30% 120 x6 2812 2812 0% 93% 158 60 3.6 0\n", "\n", "์‹œ์Šคํ…œ ๋‹ค์šด 0๊ฑด(๋ชฉํ‘œ 0๊ฑด), ์ตœ๋Œ€ ์œ ์‹ค๋ฅ  0%(๋ชฉํ‘œ 0%) -> ๋‹ฌ์„ฑ\n", "์ •๋ฐ€ ๋ชจ๋“œ: ๊ฒฝ๋ณด 2655๊ฑด(์˜ค๊ฒฝ๋ณด์œจ 14.6%, ์œ„ํ—˜ ์žฌํ˜„์œจ 97.2%) / ์ถ•์†Œ ๋ชจ๋“œ: ๊ฒฝ๋ณด 2986๊ฑด(์˜ค๊ฒฝ๋ณด์œจ 22.1%, ์œ„ํ—˜ ์žฌํ˜„์œจ 99.7%)\n", "์•ˆ์ „ ์‹คํŒจ ์›์น™: ์šฉ๋Ÿ‰์„ ๋„˜์œผ๋ฉด ์ •๋ฐ€๋„๋ฅผ ๋‚ฎ์ถ”๋”๋ผ๋„ ์œ„ํ—˜ ๊ฒฝ๋ณด๋ฅผ ๋†“์น˜์ง€ ์•Š๋Š” ์ชฝ(์ฒด์˜จ ๊ธฐ์ค€ ๋‹จ์ˆœ ๋ชจ๋“œ)์œผ๋กœ, ์‹ ํ˜ธ๊ฐ€ ๋Š๊ธฐ๋ฉด '์ •์ƒ'์ด ์•„๋‹ˆ๋ผ '์‹ ํ˜ธ๋‘์ ˆ ํ™•์ธ ํ•„์š”'๋กœ ํ‘œ์‹œํ•œ๋‹ค. ๊ฒฝ๋ณด๋Š” ์–ด๋А ๊ฒฝ์šฐ์—๋„ ๋ฒ„๋ฆฌ์ง€ ์•Š๊ณ  ์ˆ˜์‹  ํ™•์ธ ํ›„ ์‚ญ์ œํ•œ๋‹ค.\n", "๋ณธ ๊ฒฐ๊ณผ๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์˜ ์‹œ์Šคํ…œ ์‹œํ—˜์ด๋ฉฐ ์ง„๋‹จ์ด ์•„๋‹ˆ๋‹ค. ์‹ค์ œ ์ด์ƒ ์ง•ํ›„๋Š” ์˜๋ฌด๋Œ€ยท๊ตฐ์˜๊ด€์—๊ฒŒ ์—ฐ๊ณ„ํ•œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 09. AAR ๊ธฐ๋ฐ˜ ์˜ค๊ฒฝ๋ณด ์œ ๋ฐœ ์ƒ์ฒด ํŒจํ„ด ๋ถ„์„ ๋ฐ ์œ„ํ—˜ ์ž„๊ณ„๊ฐ’ ํŒŒ๋ผ๋ฏธํ„ฐ ์žฌ๋ณด์ •\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "AAR ๊ธฐ๋ฐ˜ ์˜ค๊ฒฝ๋ณด ์œ ๋ฐœ ์ƒ์ฒด ํŒจํ„ด ๋ถ„์„ ๋ฐ ์œ„ํ—˜ ์ž„๊ณ„๊ฐ’์„ ์žฌ๋ณด์ •ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **AAR(์‚ฌํ›„๊ฒ€ํ† )๋กœ ์˜ค๊ฒฝ๋ณด๋ฅผ ์œ ๋ฐœํ•œ ์ƒ์ฒด ํŒจํ„ด์„ ๋ถ„์„ํ•˜๊ณ  ์œ„ํ—˜ ์ž„๊ณ„๊ฐ’ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์žฌ๋ณด์ •ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ดˆ๊ธฐ ์„ค์ •(์—ด ๊ธฐ์ค€ 37.8๋„, ํ•œ๋žญ ๊ธฐ์ค€ 36.6๋„, ์ง€์† 1๋ถ„)์˜ ๋ชจ์˜ ์šด์šฉ ๊ฒฐ๊ณผ์—์„œ ์˜ค๊ฒฝ๋ณด๋ฅผ ๊ฐ•ํ’ ์„ผ์„œ ๋ƒ‰๊ฐ, ์„ผ์„œ ํА, ์ž„๊ณ„๊ฐ’ ๊ทผ์ ‘ ๋“ฑ ์›์ธ๋ณ„๋กœ ๋ถ„๋ฅ˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/wel01_aar_report.md ๋ฐ configs/wel01_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(์‚ฌํ›„๊ฒ€ํ† ) ์ผ์ง€ ํ˜•์‹์œผ๋กœ ๋ชจ์˜ ์šด์šฉ์˜ ์˜ค๊ฒฝ๋ณดยท๋ฏธํƒ์„ ์›์ธ๋ณ„(์„ผ์„œ ๋ƒ‰๊ฐ ์˜ค์ฐจ, ์„ผ์„œ ํА, ์ž„๊ณ„๊ฐ’ ๊ทผ์ ‘ ๋“ฑ)๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ , ์ฒด์˜จ ์ž„๊ณ„๊ฐ’ยท์ง€์† ์‹œ๊ฐ„ยท์„ผ์„œ ๋ƒ‰๊ฐ ๋ณด์ •์„ ์ œ์•ฝ ์กฐ๊ฑด ์•ˆ์—์„œ ๊ฒฉ์ž ํƒ์ƒ‰์œผ๋กœ ์žฌ๋ณด์ •ํ•ด ๊ฐœ์„  ์ „ํ›„ ์„ฑ๋Šฅ๊ณผ ์žฌ๋ฐœ ์—ฌ๋ถ€๋ฅผ ๋น„๊ตํ•œ๋‹ค. ์ง„๋‹จ ์•„๋‹˜ยท์˜๋ฌด๋Œ€ ์—ฐ๊ณ„.\"\"\"\n", "import itertools\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋”๋ฏธ ์ƒ์ฒด์‹ ํ˜ธ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋ณ‘ยท๋ถ€๋Œ€ยท์˜๋ฌด ๊ธฐ๋ก ์—†์Œ) ----------------\n", "# ๊ฐ€์ƒ ๋ณ‘์‚ฌ(๊ฐ€๋ช… ID)๊ฐ€ 60๋ถ„๊ฐ„ ๊ฑท๊ธฐยทํœด์‹ยท๊ณ ๊ฐ•๋„ ํ™œ๋™์„ ํ•˜๋Š” ๋™์•ˆ์˜ ์‹ฌ๋ฐ•์ˆ˜ยท์ฒด์˜จ(์ถ”์ • ์‹ฌ๋ถ€)ยทํ”ผ๋ถ€์ „๋„๋„๋ฅผ 1๋ถ„ ๊ฐ„๊ฒฉ์œผ๋กœ ๋งŒ๋“ ๋‹ค.\n", "# ์ง„๋‹จ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ์ƒํƒœ ๋ผ๋ฒจ์€ ๊ฐ€์ƒ ์ƒ๋ฆฌ ๋ชจ๋ธ์˜ '๊ต์œก์šฉ ๋‹จ๊ณ„'์ผ ๋ฟ์ด๋‹ค.\n", "N_MIN = 60\n", "STAGE = {0: \"์ •์ƒ\", 1: \"์ฃผ์˜\", 2: \"๊ฒฝ๋ณด\"}\n", "TYPES = [\"normal\", \"heat\", \"cold\"]\n", "\n", "\n", "def make_person(i, rng):\n", " return {\"id\": f\"S{i:03d}\", \"rest_hr\": float(rng.normal(64, 7)), \"age\": int(rng.integers(19, 27)), \"bmi\": float(rng.normal(23.5, 2.5)),\n", " \"acclim\": int(rng.random() < 0.4), \"fit\": float(np.clip(rng.normal(0.5, 0.2), 0.05, 0.95))}\n", "\n", "\n", "def make_env(kind, rng):\n", " if kind == \"heat\":\n", " return {\"temp_c\": float(rng.uniform(30, 40)), \"rh\": float(rng.uniform(45, 85)), \"wind\": float(rng.uniform(0.3, 3))}\n", " if kind == \"cold\":\n", " return {\"temp_c\": float(rng.uniform(-22, 2)), \"rh\": float(rng.uniform(40, 80)), \"wind\": float(rng.uniform(2, 11))}\n", " return {\"temp_c\": float(rng.uniform(8, 24)), \"rh\": float(rng.uniform(35, 70)), \"wind\": float(rng.uniform(0.3, 5))}\n", "\n", "\n", "def make_session(sid, kind, rng, p=None, glitch=True):\n", " \"\"\"๋ฐ˜ํ™˜: dict(person, env, kind, df[hr,temp,eda,act + ์ •๋‹ต coreยทstageยทrtype]). ์„ผ์„œ ๊ฒฐ์ธกยทํАยท๊ณ ์ฐฉ ํฌํ•จ.\"\"\"\n", " p = p or make_person(sid, rng)\n", " env = make_env(kind, rng)\n", " act = np.zeros(N_MIN); t = 0\n", " while t < N_MIN: # 10~20๋ถ„ ๋‹จ์œ„ ํ™œ๋™ ๋ธ”๋ก: ํœด์‹ 1.5 / ํ–‰๊ตฐ 4.5 / ๊ณ ๊ฐ•๋„ 7 MET\n", " d = int(rng.integers(10, 21)); act[t:t + d] = rng.choice([1.5, 4.5, 7.0], p=[0.25, 0.5, 0.25]); t += d\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"]\n", " core, cores = 36.9, []\n", " for m in range(N_MIN):\n", " heat = 0.085 * max(0, hi - 24) * (1 - 0.25 * p[\"acclim\"]) * (1 + 0.5 * m / N_MIN) * (1 + 0.012 * (p[\"bmi\"] - 23.5))\n", " cold = -0.075 * max(0, 8 - wc) * (1 - 0.09 * act[m])\n", " ss = 36.9 + 0.09 * act[m] + heat + cold\n", " core += (ss - core) / 18 + rng.normal(0, 0.03)\n", " cores.append(core)\n", " core = np.array(cores)\n", " hr = p[\"rest_hr\"] + 9.5 * (act - 1.5) * (1.15 - 0.3 * p[\"fit\"]) + 11 * (core - 36.9) + rng.normal(0, 3.2, N_MIN)\n", " eda = 2.0 + 0.25 * act + 3.2 * np.maximum(core - 37.2, 0) - 0.04 * np.maximum(8 - wc, 0) + rng.normal(0, 0.25, N_MIN)\n", " bias = rng.normal(0, 0.1)\n", " temp = core + bias + rng.normal(0, 0.1, N_MIN) - np.where((kind == \"cold\") & (env[\"wind\"] > 7), 0.25, 0.0) # ์„ผ์„œ ์ ‘์ด‰ ๋ถ€์œ„ ๋ฐ”๋žŒ ๋ƒ‰๊ฐ ์˜ค์ฐจ\n", " df = pd.DataFrame({\"min\": np.arange(N_MIN), \"hr\": hr, \"temp\": temp, \"eda\": np.maximum(eda, 0.2), \"act\": act})\n", " if glitch:\n", " for col in (\"hr\", \"temp\", \"eda\"):\n", " df.loc[rng.random(N_MIN) < 0.03, col] = np.nan # ๊ฒฐ์ธก\n", " s = int(rng.integers(5, N_MIN))\n", " if rng.random() < 0.12:\n", " df.loc[s, \"hr\"] = 230.0 # ์„ผ์„œ ํА\n", " if rng.random() < 0.12:\n", " df.loc[s, \"temp\"] = 45.0\n", " ht = np.where(core >= 39.0, 2, np.where(core >= 38.3, 1, 0)); cd = np.where(core <= 35.4, 2, np.where(core <= 36.2, 1, 0))\n", " df[\"core\"] = core\n", " df[\"stage\"] = np.maximum(ht, cd); df[\"rtype\"] = np.where(ht > 0, \"heat\", np.where(cd > 0, \"cold\", \"normal\"))\n", " return {\"id\": p[\"id\"], \"person\": p, \"env\": env, \"kind\": kind, \"df\": df}\n", "\n", "\n", "def make_dataset(n, seed):\n", " rng = np.random.default_rng(seed)\n", " kinds = [\"normal\", \"heat\", \"cold\"]\n", " return [make_session(i, kinds[i % 3], rng) for i in range(n)]\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---------------- ์ „์ฒ˜๋ฆฌยทํŠน์ง• ์ถ”์ถœ ๊ณตํ†ต ํ•จ์ˆ˜ ----------------\n", "LIMITS = {\"hr\": (30, 220), \"temp\": (30.0, 43.0), \"eda\": (0.0, 40.0)} # ์ƒ๋ฆฌ์ ์œผ๋กœ ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„(๋ฒ”์œ„ ๋ฐ– = ์„ผ์„œ ์˜ค๋ฅ˜)\n", "\n", "\n", "def clean(df):\n", " \"\"\"๋ฒ”์œ„ ๋ฐ– ๊ฐ’์€ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ -> ์„ ํ˜• ๋ณด๊ฐ„(์–‘ ๋์€ ๊ฐ€๊นŒ์šด ๊ฐ’) -> 3๋ถ„ ์ด๋™ ์ค‘์•™๊ฐ’์œผ๋กœ ํА ์ œ๊ฑฐ. ๋ฐ˜ํ™˜: (์ •์ œ df, ๋ณด์ • ๊ฑด์ˆ˜)\"\"\"\n", " out = df.copy(); fixed = 0\n", " for c, (lo, hi) in LIMITS.items():\n", " bad = out[c].isna() | (out[c] < lo) | (out[c] > hi)\n", " fixed += int(bad.sum())\n", " out.loc[bad, c] = np.nan\n", " out[c] = out[c].interpolate(limit_direction=\"both\").rolling(3, center=True, min_periods=1).median()\n", " return out, fixed\n", "\n", "\n", "def env_index(env):\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0.0) # ์ฒด๊ฐ ๋”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"] # ์ฒด๊ฐ ์ถ”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " return hi, wc\n", "\n", "\n", "def features(s, use_env=True, personal=True):\n", " \"\"\"์„ธ์…˜ 1๊ฐœ -> ๋ถ„ ๋‹จ์œ„ ํŠน์ง•ํ‘œ(์ตœ๊ทผ 5๋ถ„ ํ‰๊ท ยท๊ธฐ์šธ๊ธฐ ํฌํ•จ). ์ •๋‹ต stage/rtype ํฌํ•จ.\"\"\"\n", " df, _ = clean(s[\"df\"]); p, e = s[\"person\"], s[\"env\"]\n", " f = pd.DataFrame({\"sid\": s[\"id\"], \"min\": df[\"min\"]})\n", " f[\"temp5\"] = df[\"temp\"].rolling(5, min_periods=1).mean()\n", " f[\"hr5\"] = df[\"hr\"].rolling(5, min_periods=1).mean()\n", " f[\"eda5\"] = df[\"eda\"].rolling(5, min_periods=1).mean()\n", " f[\"dtemp\"] = (f[\"temp5\"] - f[\"temp5\"].shift(4)).fillna(0)\n", " f[\"act\"] = df[\"act\"]\n", " if personal:\n", " f[\"hr_rel\"] = f[\"hr5\"] - p[\"rest_hr\"]\n", " f[\"acclim\"], f[\"bmi\"], f[\"fit\"] = p[\"acclim\"], p[\"bmi\"], p[\"fit\"]\n", " if use_env:\n", " f[\"hi\"], f[\"wc\"] = env_index(e); f[\"wind\"] = e[\"wind\"]\n", " f[\"stage\"], f[\"rtype\"], f[\"kind\"] = df[\"stage\"], df[\"rtype\"], s[\"kind\"]\n", " return f[f[\"min\"] >= 5].reset_index(drop=True)\n", "\n", "\n", "def table(sessions, **kw):\n", " return pd.concat([features(s, **kw) for s in sessions], ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "\n", "rng = np.random.default_rng(13)\n", "OPS = []\n", "for i in range(90):\n", " s = make_session(i, [\"heat\", \"cold\", \"normal\"][i % 3], rng)\n", " if s[\"kind\"] == \"cold\" and i % 2 == 0:\n", " s[\"env\"][\"wind\"] = float(rng.uniform(7.5, 11)) # ๊ฐ•ํ’ ํ˜นํ•œ: ์„ผ์„œ ๋ƒ‰๊ฐ ์˜ค์ฐจ ๊ตฌ๊ฐ„\n", " OPS.append(s)\n", "TAB = table(OPS)\n", "SPIKE = {s[\"id\"]: set(np.where((s[\"df\"][\"hr\"] > 220) | (s[\"df\"][\"temp\"] > 43))[0]) for s in OPS}\n", "\n", "\n", "def alerts(tab, th_heat, th_cold, persist, corr):\n", " \"\"\"์ฒด์˜จ ๊ธฐ์ค€ ๊ฒฝ๋ณด: ์„ผ์„œ ๋ƒ‰๊ฐ ๋ณด์ •(๊ฐ•ํ’ยทํ•œ๋žญ ํ™˜๊ฒฝ์—์„œ corr๋งŒํผ ๋ณด์ •) ํ›„ ์ž„๊ณ„๊ฐ’์„ persist๋ถ„ ์—ฐ์† ๋„˜์–ด์•ผ ๊ฒฝ๋ณด.\"\"\"\n", " t = tab[\"temp5\"] + np.where((tab[\"wind\"] >= 7) & (tab[\"wc\"] < 0), corr, 0.0)\n", " raw = (t >= th_heat) | (t <= th_cold)\n", " return raw.groupby(tab[\"sid\"]).transform(lambda v: v.rolling(persist, min_periods=persist).min().fillna(0).astype(bool))\n", "\n", "\n", "def summarize(tab, al):\n", " truth = tab[\"stage\"] >= 1\n", " tp = (al & truth).sum(); far = (al & ~truth).sum() / max(al.sum(), 1); rec = tp / truth.sum()\n", " return {\"๊ฒฝ๋ณด\": int(al.sum()), \"์˜ค๊ฒฝ๋ณด์œจ\": round(float(far), 3), \"์žฌํ˜„์œจ\": round(float(rec), 3), \"์ข…ํ•ฉ์ •ํ™•๋„(F1)\": round(float(2 * (1 - far) * rec / max(1 - far + rec, 1e-9)), 3)}\n", "\n", "\n", "def cause(r):\n", " near = min(abs(r[\"temp5\"] - 38.0), abs(r[\"temp5\"] - 36.3)) < 0.35\n", " if r[\"wind\"] >= 7 and r[\"wc\"] < 0:\n", " return \"๊ฐ•ํ’ ์„ผ์„œ ๋ƒ‰๊ฐ ์˜ค์ฐจ\"\n", " if any(m in SPIKE[r[\"sid\"]] for m in range(int(r[\"min\"]) - 4, int(r[\"min\"]) + 1)):\n", " return \"์„ผ์„œ ํА ์˜ํ–ฅ\"\n", " return \"์ž„๊ณ„๊ฐ’ ๊ทผ์ ‘(์กฐ๊ธฐ ๊ฒฝ๋ณด)\" if near else \"๊ธฐํƒ€\"\n", "\n", "\n", "def aar_log(tab, al):\n", " rows = [{\"๊ตฌ๋ถ„\": \"์˜ค๊ฒฝ๋ณด\", \"์›์ธ\": cause(r), \"๋ณ‘์‚ฌ\": r[\"sid\"], \"์œ ํ˜•\": r[\"rtype\"]} for r in tab[al & (tab[\"stage\"] == 0)].to_dict(\"records\")]\n", " miss = tab[~al & (tab[\"stage\"] >= 1)]\n", " rows += [{\"๊ตฌ๋ถ„\": \"๋ฏธํƒ\", \"์›์ธ\": \"์ž„๊ณ„๊ฐ’ยท์ง€์†์‹œ๊ฐ„ ๋ฏธ์ถฉ์กฑ\", \"๋ณ‘์‚ฌ\": sid, \"์œ ํ˜•\": \"-\"} for sid in miss[\"sid\"].unique() for _ in range(int(len(miss[miss[\"sid\"] == sid]) > 0))]\n", " return pd.DataFrame(rows, columns=[\"๊ตฌ๋ถ„\", \"์›์ธ\", \"๋ณ‘์‚ฌ\", \"์œ ํ˜•\"])\n", "\n", "\n", "# 1) ์ดˆ๊ธฐ ์„ค์ •์œผ๋กœ ๋ชจ์˜ ์šด์šฉ: '๋†“์น˜์ง€ ์•Š๊ธฐ' ์šฐ์„ (๋‚ฎ์€ ์—ด ๊ธฐ์ค€, ๋†’์€ ํ•œ๋žญ ๊ธฐ์ค€, ์ง€์† 1๋ถ„)\n", "INIT = {\"th_heat\": 37.8, \"th_cold\": 36.6, \"persist\": 1, \"corr\": 0.0}\n", "a0 = alerts(TAB, **INIT)\n", "before = summarize(TAB, a0)\n", "log = aar_log(TAB, a0)\n", "print(\"[1. ๋ฌด์—‡์ด ์ผ์–ด๋‚ฌ๋‚˜] ์ดˆ๊ธฐ ์„ค์ •\", INIT, \"->\", before)\n", "print(log.groupby([\"๊ตฌ๋ถ„\", \"์›์ธ\"]).size().rename(\"๊ฑด์ˆ˜\").to_string())\n", "fa = log[log[\"๊ตฌ๋ถ„\"] == \"์˜ค๊ฒฝ๋ณด\"]\n", "print(\"\\n[2. ์™œ ์ผ์–ด๋‚ฌ๋‚˜] ์˜ค๊ฒฝ๋ณด ์œ ํ˜•:\", fa[\"์œ ํ˜•\"].value_counts().to_dict(), \"/ ์˜ค๊ฒฝ๋ณด ๋ฐœ์ƒ ๋ณ‘์‚ฌ ์ˆ˜:\", fa[\"๋ณ‘์‚ฌ\"].nunique())\n", "\n", "# 2) ์žฌ๋ณด์ •: ์ œ์•ฝ(์˜ค๊ฒฝ๋ณด์œจ 15% ์ดํ•˜, ์žฌํ˜„์œจ 90% ์ด์ƒ) ์•ˆ์—์„œ ์žฌ๋ฐœ ์ตœ์†Œ -> F1 ์ตœ๋Œ€ ์กฐํ•ฉ ํƒ์ƒ‰\n", "seen = set(map(tuple, fa[[\"๋ณ‘์‚ฌ\", \"์›์ธ\"]].drop_duplicates().values))\n", "rows = []\n", "for th, tc, ps, co in itertools.product((37.8, 38.0, 38.2, 38.4), (36.6, 36.4, 36.2, 36.0), (1, 2, 3), (0.0, 0.25)):\n", " al = alerts(TAB, th, tc, ps, co)\n", " new = aar_log(TAB, al); new = new[new[\"๊ตฌ๋ถ„\"] == \"์˜ค๊ฒฝ๋ณด\"]\n", " rep = len(seen & set(map(tuple, new[[\"๋ณ‘์‚ฌ\", \"์›์ธ\"]].drop_duplicates().values)))\n", " rows.append({\"th_heat\": th, \"th_cold\": tc, \"persist\": ps, \"corr\": co, **summarize(TAB, al), \"์žฌ๋ฐœ\": rep})\n", "g = pd.DataFrame(rows)\n", "ok = g[(g[\"์˜ค๊ฒฝ๋ณด์œจ\"] <= 0.15) & (g[\"์žฌํ˜„์œจ\"] >= 0.90)].sort_values([\"์žฌ๋ฐœ\", \"์ข…ํ•ฉ์ •ํ™•๋„(F1)\", \"์žฌํ˜„์œจ\"], ascending=[True, False, False])\n", "best = ok.iloc[0]\n", "print(\"\\n[3. ๋ฌด์—‡์„ ๋ฐ”๊พธ๋‚˜] ์ œ์•ฝ ๋งŒ์กฑ ์กฐํ•ฉ ์ƒ์œ„ 5๊ฐœ(์žฌ๋ฐœ ์ ์€ ์ˆœ) (์ „์ฒด ์กฐํ•ฉ %d๊ฐœ ์ค‘ %d๊ฐœ ๋งŒ์กฑ)\" % (len(g), len(ok)))\n", "print(ok.head().to_string(index=False))\n", "after = summarize(TAB, alerts(TAB, best[\"th_heat\"], best[\"th_cold\"], int(best[\"persist\"]), best[\"corr\"]))\n", "gain = (after[\"์ข…ํ•ฉ์ •ํ™•๋„(F1)\"] - before[\"์ข…ํ•ฉ์ •ํ™•๋„(F1)\"]) * 100\n", "print(f\"\\n์žฌ๋ณด์ • ํ›„: {after}\")\n", "print(f\"์ข…ํ•ฉ์ •ํ™•๋„ ๋ณ€ํ™” {before['์ข…ํ•ฉ์ •ํ™•๋„(F1)']:.3f} -> {after['์ข…ํ•ฉ์ •ํ™•๋„(F1)']:.3f} ({gain:+.1f}%p, ๋ชฉํ‘œ +10%p ์ด์ƒ: {'๋‹ฌ์„ฑ' if gain >= 10 else '๋ฏธ๋‹ฌ'})\")\n", "print(f\"AAR์—์„œ ์‹๋ณ„ํ•œ (๋ณ‘์‚ฌ, ์›์ธ) ์˜ค๊ฒฝ๋ณด {len(seen)}์ข… -> ์žฌ๋ณด์ • ํ›„ ๊ฐ™์€ ๋ณ‘์‚ฌยท๊ฐ™์€ ์›์ธ์œผ๋กœ ์žฌ๋ฐœ {int(best['์žฌ๋ฐœ'])}๊ฑด (๋ชฉํ‘œ 0๊ฑด: {'๋‹ฌ์„ฑ' if best['์žฌ๋ฐœ'] == 0 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "tuned = (f\"heat_temp_threshold: {best['th_heat']}\\ncold_temp_threshold: {best['th_cold']}\\nmin_persist_minutes: {int(best['persist'])}\\n\"\n", " f\"wind_cooling_correction_c: {best['corr']}\\nconstraints: {{max_far: 0.15, min_recall: 0.90}}\")\n", "print(\"\\n[์žฌ๋ณด์ • ํŒŒ๋ผ๋ฏธํ„ฐ(wel01_tuned_params.yaml ๋‚ด์šฉ)]\\n\" + tuned)\n", "print(f\"๊ฐœ์„  ์กฐ์น˜: ์—ด ๊ธฐ์ค€ {best['th_heat']}๋„ยทํ•œ๋žญ ๊ธฐ์ค€ {best['th_cold']}๋„, {int(best['persist'])}๋ถ„ ์—ฐ์† ์ง€์† ์กฐ๊ฑด์œผ๋กœ ์˜ค๊ฒฝ๋ณด๋ฅผ ์ค„์˜€๋‹ค(์„ผ์„œ ๋ƒ‰๊ฐ ๋ณด์ • {best['corr']}๋„). ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ์‹ค์ œ ์žฅ๋น„ยท์ธ์› ์ž๋ฃŒ๋กœ ์žฌ๊ฒ€์ฆํ•˜๊ณ  ๊ตฐ์˜๊ด€ ๊ฒ€ํ† ๋ฅผ ๊ฑฐ์ณ์•ผ ํ•˜๋ฉฐ ์ง„๋‹จ ๊ธฐ์ค€์ด ์•„๋‹ˆ๋‹ค.\")\n", "\n", "plt.figure(figsize=(6, 4))\n", "plt.scatter(g[\"์˜ค๊ฒฝ๋ณด์œจ\"], g[\"์žฌํ˜„์œจ\"], c=g[\"persist\"], cmap=\"viridis\", s=18)\n", "plt.axvline(0.15, ls=\"--\", c=\"gray\"); plt.axhline(0.90, ls=\"--\", c=\"gray\"); plt.colorbar(label=\"persist (min)\")\n", "plt.xlabel(\"false alarm rate\"); plt.ylabel(\"risk recall\"); plt.title(\"AAR re-tuning grid (dummy data)\")\n", "plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 09 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1. ๋ฌด์—‡์ด ์ผ์–ด๋‚ฌ๋‚˜] ์ดˆ๊ธฐ ์„ค์ • {'th_heat': 37.8, 'th_cold': 36.6, 'persist': 1, 'corr': 0.0} -> {'๊ฒฝ๋ณด': 2570, '์˜ค๊ฒฝ๋ณด์œจ': 0.405, '์žฌํ˜„์œจ': 1.0, '์ข…ํ•ฉ์ •ํ™•๋„(F1)': 0.746}\n", "๊ตฌ๋ถ„ ์›์ธ \n", "์˜ค๊ฒฝ๋ณด ๊ฐ•ํ’ ์„ผ์„œ ๋ƒ‰๊ฐ ์˜ค์ฐจ 380\n", " ๊ธฐํƒ€ 5\n", " ์„ผ์„œ ํА ์˜ํ–ฅ 6\n", " ์ž„๊ณ„๊ฐ’ ๊ทผ์ ‘(์กฐ๊ธฐ ๊ฒฝ๋ณด) 650\n", "\n", "[2. ์™œ ์ผ์–ด๋‚ฌ๋‚˜] ์˜ค๊ฒฝ๋ณด ์œ ํ˜•: {'normal': 1041} / ์˜ค๊ฒฝ๋ณด ๋ฐœ์ƒ ๋ณ‘์‚ฌ ์ˆ˜: 58\n", "\n", "[3. ๋ฌด์—‡์„ ๋ฐ”๊พธ๋‚˜] ์ œ์•ฝ ๋งŒ์กฑ ์กฐํ•ฉ ์ƒ์œ„ 5๊ฐœ(์žฌ๋ฐœ ์ ์€ ์ˆœ) (์ „์ฒด ์กฐํ•ฉ 96๊ฐœ ์ค‘ 8๊ฐœ ๋งŒ์กฑ)\n", " th_heat th_cold persist corr ๊ฒฝ๋ณด ์˜ค๊ฒฝ๋ณด์œจ ์žฌํ˜„์œจ ์ข…ํ•ฉ์ •ํ™•๋„(F1) ์žฌ๋ฐœ\n", " 38.2 36.2 3 0.00 1487 0.073 0.902 0.914 20\n", " 38.2 36.4 3 0.25 1545 0.103 0.906 0.902 21\n", " 38.2 36.2 2 0.00 1543 0.086 0.923 0.919 23\n", " 38.2 36.4 2 0.25 1603 0.116 0.927 0.905 27\n", " 38.2 36.2 1 0.00 1601 0.102 0.940 0.918 29\n", "\n", "์žฌ๋ณด์ • ํ›„: {'๊ฒฝ๋ณด': 1487, '์˜ค๊ฒฝ๋ณด์œจ': 0.073, '์žฌํ˜„์œจ': 0.902, '์ข…ํ•ฉ์ •ํ™•๋„(F1)': 0.914}\n", "์ข…ํ•ฉ์ •ํ™•๋„ ๋ณ€ํ™” 0.746 -> 0.914 (+16.8%p, ๋ชฉํ‘œ +10%p ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "AAR์—์„œ ์‹๋ณ„ํ•œ (๋ณ‘์‚ฌ, ์›์ธ) ์˜ค๊ฒฝ๋ณด 62์ข… -> ์žฌ๋ณด์ • ํ›„ ๊ฐ™์€ ๋ณ‘์‚ฌยท๊ฐ™์€ ์›์ธ์œผ๋กœ ์žฌ๋ฐœ 20๊ฑด (๋ชฉํ‘œ 0๊ฑด: ๋ฏธ๋‹ฌ)\n", "\n", "[์žฌ๋ณด์ • ํŒŒ๋ผ๋ฏธํ„ฐ(wel01_tuned_params.yaml ๋‚ด์šฉ)]\n", "heat_temp_threshold: 38.2\n", "cold_temp_threshold: 36.2\n", "min_persist_minutes: 3\n", "wind_cooling_correction_c: 0.0\n", "constraints: {max_far: 0.15, min_recall: 0.90}\n", "๊ฐœ์„  ์กฐ์น˜: ์—ด ๊ธฐ์ค€ 38.2๋„ยทํ•œ๋žญ ๊ธฐ์ค€ 36.2๋„, 3๋ถ„ ์—ฐ์† ์ง€์† ์กฐ๊ฑด์œผ๋กœ ์˜ค๊ฒฝ๋ณด๋ฅผ ์ค„์˜€๋‹ค(์„ผ์„œ ๋ƒ‰๊ฐ ๋ณด์ • 0.0๋„). ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ์‹ค์ œ ์žฅ๋น„ยท์ธ์› ์ž๋ฃŒ๋กœ ์žฌ๊ฒ€์ฆํ•˜๊ณ  ๊ตฐ์˜๊ด€ ๊ฒ€ํ† ๋ฅผ ๊ฑฐ์ณ์•ผ ํ•˜๋ฉฐ ์ง„๋‹จ ๊ธฐ์ค€์ด ์•„๋‹ˆ๋‹ค.\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_wel01.py, README.md, docs/wel01_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), ์†Œ๋Œ€ ์ƒํ™ฉํŒ, AAR ์žฌ๋ณด์ •๊ฐ’ ์ ์šฉ๊นŒ์ง€ ์˜จ์—ดยทํ•œ๋žญ ์ŠคํŠธ๋ ˆ์Šค ๊ฒฝ๋ณด ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ์„ ํ•œ ํŒŒ์ผ๋กœ ํ†ตํ•ฉ ์‹คํ–‰ํ•˜๊ณ , ์ž์ฒด ์ ๊ฒ€(์‹คํ–‰ ๋ผ์ธ ์ปค๋ฒ„๋ฆฌ์ง€ ํฌํ•จ)๊ณผ ์ง€ํœ˜๊ด€ยท์˜๋ฌด๊ด€ ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ์„ ์ถœ๋ ฅํ•œ๋‹ค. ์ง„๋‹จ ์•„๋‹˜ยท์˜๋ฌด๋Œ€ ์—ฐ๊ณ„.\"\"\"\n", "import dis\n", "import hashlib\n", "import json\n", "import sys\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "# ---------------- [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋”๋ฏธ ์ƒ์ฒด์‹ ํ˜ธ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์žฅ๋ณ‘ยท๋ถ€๋Œ€ยท์˜๋ฌด ๊ธฐ๋ก ์—†์Œ) ----------------\n", "# ๊ฐ€์ƒ ๋ณ‘์‚ฌ(๊ฐ€๋ช… ID)๊ฐ€ 60๋ถ„๊ฐ„ ๊ฑท๊ธฐยทํœด์‹ยท๊ณ ๊ฐ•๋„ ํ™œ๋™์„ ํ•˜๋Š” ๋™์•ˆ์˜ ์‹ฌ๋ฐ•์ˆ˜ยท์ฒด์˜จ(์ถ”์ • ์‹ฌ๋ถ€)ยทํ”ผ๋ถ€์ „๋„๋„๋ฅผ 1๋ถ„ ๊ฐ„๊ฒฉ์œผ๋กœ ๋งŒ๋“ ๋‹ค.\n", "# ์ง„๋‹จ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ์ƒํƒœ ๋ผ๋ฒจ์€ ๊ฐ€์ƒ ์ƒ๋ฆฌ ๋ชจ๋ธ์˜ '๊ต์œก์šฉ ๋‹จ๊ณ„'์ผ ๋ฟ์ด๋‹ค.\n", "N_MIN = 60\n", "STAGE = {0: \"์ •์ƒ\", 1: \"์ฃผ์˜\", 2: \"๊ฒฝ๋ณด\"}\n", "TYPES = [\"normal\", \"heat\", \"cold\"]\n", "\n", "\n", "def make_person(i, rng):\n", " return {\"id\": f\"S{i:03d}\", \"rest_hr\": float(rng.normal(64, 7)), \"age\": int(rng.integers(19, 27)), \"bmi\": float(rng.normal(23.5, 2.5)),\n", " \"acclim\": int(rng.random() < 0.4), \"fit\": float(np.clip(rng.normal(0.5, 0.2), 0.05, 0.95))}\n", "\n", "\n", "def make_env(kind, rng):\n", " if kind == \"heat\":\n", " return {\"temp_c\": float(rng.uniform(30, 40)), \"rh\": float(rng.uniform(45, 85)), \"wind\": float(rng.uniform(0.3, 3))}\n", " if kind == \"cold\":\n", " return {\"temp_c\": float(rng.uniform(-22, 2)), \"rh\": float(rng.uniform(40, 80)), \"wind\": float(rng.uniform(2, 11))}\n", " return {\"temp_c\": float(rng.uniform(8, 24)), \"rh\": float(rng.uniform(35, 70)), \"wind\": float(rng.uniform(0.3, 5))}\n", "\n", "\n", "def make_session(sid, kind, rng, p=None, glitch=True):\n", " \"\"\"๋ฐ˜ํ™˜: dict(person, env, kind, df[hr,temp,eda,act + ์ •๋‹ต coreยทstageยทrtype]). ์„ผ์„œ ๊ฒฐ์ธกยทํАยท๊ณ ์ฐฉ ํฌํ•จ.\"\"\"\n", " p = p or make_person(sid, rng)\n", " env = make_env(kind, rng)\n", " act = np.zeros(N_MIN); t = 0\n", " while t < N_MIN: # 10~20๋ถ„ ๋‹จ์œ„ ํ™œ๋™ ๋ธ”๋ก: ํœด์‹ 1.5 / ํ–‰๊ตฐ 4.5 / ๊ณ ๊ฐ•๋„ 7 MET\n", " d = int(rng.integers(10, 21)); act[t:t + d] = rng.choice([1.5, 4.5, 7.0], p=[0.25, 0.5, 0.25]); t += d\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"]\n", " core, cores = 36.9, []\n", " for m in range(N_MIN):\n", " heat = 0.085 * max(0, hi - 24) * (1 - 0.25 * p[\"acclim\"]) * (1 + 0.5 * m / N_MIN) * (1 + 0.012 * (p[\"bmi\"] - 23.5))\n", " cold = -0.075 * max(0, 8 - wc) * (1 - 0.09 * act[m])\n", " ss = 36.9 + 0.09 * act[m] + heat + cold\n", " core += (ss - core) / 18 + rng.normal(0, 0.03)\n", " cores.append(core)\n", " core = np.array(cores)\n", " hr = p[\"rest_hr\"] + 9.5 * (act - 1.5) * (1.15 - 0.3 * p[\"fit\"]) + 11 * (core - 36.9) + rng.normal(0, 3.2, N_MIN)\n", " eda = 2.0 + 0.25 * act + 3.2 * np.maximum(core - 37.2, 0) - 0.04 * np.maximum(8 - wc, 0) + rng.normal(0, 0.25, N_MIN)\n", " bias = rng.normal(0, 0.1)\n", " temp = core + bias + rng.normal(0, 0.1, N_MIN) - np.where((kind == \"cold\") & (env[\"wind\"] > 7), 0.25, 0.0) # ์„ผ์„œ ์ ‘์ด‰ ๋ถ€์œ„ ๋ฐ”๋žŒ ๋ƒ‰๊ฐ ์˜ค์ฐจ\n", " df = pd.DataFrame({\"min\": np.arange(N_MIN), \"hr\": hr, \"temp\": temp, \"eda\": np.maximum(eda, 0.2), \"act\": act})\n", " if glitch:\n", " for col in (\"hr\", \"temp\", \"eda\"):\n", " df.loc[rng.random(N_MIN) < 0.03, col] = np.nan # ๊ฒฐ์ธก\n", " s = int(rng.integers(5, N_MIN))\n", " if rng.random() < 0.12:\n", " df.loc[s, \"hr\"] = 230.0 # ์„ผ์„œ ํА\n", " if rng.random() < 0.12:\n", " df.loc[s, \"temp\"] = 45.0\n", " ht = np.where(core >= 39.0, 2, np.where(core >= 38.3, 1, 0)); cd = np.where(core <= 35.4, 2, np.where(core <= 36.2, 1, 0))\n", " df[\"core\"] = core\n", " df[\"stage\"] = np.maximum(ht, cd); df[\"rtype\"] = np.where(ht > 0, \"heat\", np.where(cd > 0, \"cold\", \"normal\"))\n", " return {\"id\": p[\"id\"], \"person\": p, \"env\": env, \"kind\": kind, \"df\": df}\n", "\n", "\n", "def make_dataset(n, seed):\n", " rng = np.random.default_rng(seed)\n", " kinds = [\"normal\", \"heat\", \"cold\"]\n", " return [make_session(i, kinds[i % 3], rng) for i in range(n)]\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---------------- ์ „์ฒ˜๋ฆฌยทํŠน์ง• ์ถ”์ถœ ๊ณตํ†ต ํ•จ์ˆ˜ ----------------\n", "LIMITS = {\"hr\": (30, 220), \"temp\": (30.0, 43.0), \"eda\": (0.0, 40.0)} # ์ƒ๋ฆฌ์ ์œผ๋กœ ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„(๋ฒ”์œ„ ๋ฐ– = ์„ผ์„œ ์˜ค๋ฅ˜)\n", "\n", "\n", "def clean(df):\n", " \"\"\"๋ฒ”์œ„ ๋ฐ– ๊ฐ’์€ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ -> ์„ ํ˜• ๋ณด๊ฐ„(์–‘ ๋์€ ๊ฐ€๊นŒ์šด ๊ฐ’) -> 3๋ถ„ ์ด๋™ ์ค‘์•™๊ฐ’์œผ๋กœ ํА ์ œ๊ฑฐ. ๋ฐ˜ํ™˜: (์ •์ œ df, ๋ณด์ • ๊ฑด์ˆ˜)\"\"\"\n", " out = df.copy(); fixed = 0\n", " for c, (lo, hi) in LIMITS.items():\n", " bad = out[c].isna() | (out[c] < lo) | (out[c] > hi)\n", " fixed += int(bad.sum())\n", " out.loc[bad, c] = np.nan\n", " out[c] = out[c].interpolate(limit_direction=\"both\").rolling(3, center=True, min_periods=1).median()\n", " return out, fixed\n", "\n", "\n", "def env_index(env):\n", " hi = env[\"temp_c\"] + (0.12 * (env[\"rh\"] - 40) if env[\"temp_c\"] > 25 else 0.0) # ์ฒด๊ฐ ๋”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " wc = env[\"temp_c\"] - 1.6 * env[\"wind\"] # ์ฒด๊ฐ ์ถ”์œ„ ์ง€์ˆ˜(๊ฐ€์ƒ ๋‹จ์ˆœ์‹)\n", " return hi, wc\n", "\n", "\n", "def features(s, use_env=True, personal=True):\n", " \"\"\"์„ธ์…˜ 1๊ฐœ -> ๋ถ„ ๋‹จ์œ„ ํŠน์ง•ํ‘œ(์ตœ๊ทผ 5๋ถ„ ํ‰๊ท ยท๊ธฐ์šธ๊ธฐ ํฌํ•จ). ์ •๋‹ต stage/rtype ํฌํ•จ.\"\"\"\n", " df, _ = clean(s[\"df\"]); p, e = s[\"person\"], s[\"env\"]\n", " f = pd.DataFrame({\"sid\": s[\"id\"], \"min\": df[\"min\"]})\n", " f[\"temp5\"] = df[\"temp\"].rolling(5, min_periods=1).mean()\n", " f[\"hr5\"] = df[\"hr\"].rolling(5, min_periods=1).mean()\n", " f[\"eda5\"] = df[\"eda\"].rolling(5, min_periods=1).mean()\n", " f[\"dtemp\"] = (f[\"temp5\"] - f[\"temp5\"].shift(4)).fillna(0)\n", " f[\"act\"] = df[\"act\"]\n", " if personal:\n", " f[\"hr_rel\"] = f[\"hr5\"] - p[\"rest_hr\"]\n", " f[\"acclim\"], f[\"bmi\"], f[\"fit\"] = p[\"acclim\"], p[\"bmi\"], p[\"fit\"]\n", " if use_env:\n", " f[\"hi\"], f[\"wc\"] = env_index(e); f[\"wind\"] = e[\"wind\"]\n", " f[\"stage\"], f[\"rtype\"], f[\"kind\"] = df[\"stage\"], df[\"rtype\"], s[\"kind\"]\n", " return f[f[\"min\"] >= 5].reset_index(drop=True)\n", "\n", "\n", "def table(sessions, **kw):\n", " return pd.concat([features(s, **kw) for s in sessions], ignore_index=True)\n", "# ---------------------------------------------------------------------------\n", "\n", "CONFIG = {\"alert_threshold\": 0.4, \"min_persist\": 3, \"heat_temp\": 38.2, \"cold_temp\": 36.2} # ๋ฏธ์…˜ 9 ์žฌ๋ณด์ •๊ฐ’ + ๋ชจ๋ธ ์ž„๊ณ„๊ฐ’(์ง€์† ์กฐ๊ฑด์ด ์˜ค๊ฒฝ๋ณด๋ฅผ ๋ง‰์œผ๋ฏ€๋กœ ๋ฏธ์…˜ 5์˜ 0.5๋ณด๋‹ค ๋‚ฎ์ถฐ ๋ฏธํƒ์„ ์ค„์ž„)\n", "FE = [\"temp5\", \"dtemp\", \"hr5\", \"eda5\", \"hr_rel\", \"acclim\", \"bmi\", \"fit\", \"hi\", \"wc\", \"wind\"]\n", "\n", "\n", "def validate(s):\n", " errs = []\n", " if s[\"kind\"] not in TYPES:\n", " errs.append(\"kind ํ—ˆ์šฉ๊ฐ’ ์•„๋‹˜\")\n", " if len(s[\"df\"]) != N_MIN or not {\"hr\", \"temp\", \"eda\"} <= set(s[\"df\"].columns):\n", " errs.append(\"์‹ ํ˜ธ ๊ธธ์ดยทํ•„๋“œ ์˜ค๋ฅ˜\")\n", " if not str(s[\"id\"]).startswith(\"S\"):\n", " errs.append(\"soldier_id ํ˜•์‹ ์˜ค๋ฅ˜\")\n", " return errs\n", "\n", "\n", "def run_session(s, model):\n", " \"\"\"์„ธ์…˜ 1๊ฐœ: ์ •์ œ -> ํŠน์ง• -> ์ ์ˆ˜ -> ์ง€์† ์กฐ๊ฑด ๊ฒฝ๋ณด. ์‹ ํ˜ธ๊ฐ€ 5๋ถ„ ์ด์ƒ ๋Š๊ธฐ๋ฉด '์‹ ํ˜ธ๋‘์ ˆ' ํ”Œ๋ž˜๊ทธ.\"\"\"\n", " f = features(s)\n", " raw = s[\"df\"].set_index(\"min\")\n", " f[\"lost\"] = [bool(raw.loc[max(m - 4, 0):m, [\"hr\", \"temp\"]].isna().all().all()) for m in f[\"min\"]]\n", " f[\"score\"] = model.predict_proba(f[FE])[:, 1]\n", " hot = (f[\"temp5\"] >= CONFIG[\"heat_temp\"]) | (f[\"temp5\"] <= CONFIG[\"cold_temp\"])\n", " cond = (f[\"score\"] >= CONFIG[\"alert_threshold\"]) & (hot | (f[\"score\"] >= 0.8))\n", " f[\"alert\"] = cond.rolling(CONFIG[\"min_persist\"], min_periods=CONFIG[\"min_persist\"]).min().fillna(0).astype(bool) & ~f[\"lost\"]\n", " f[\"grade\"] = np.where(f[\"score\"] >= 0.8, \"High\", np.where(f[\"score\"] >= 0.5, \"Medium\", \"Low\"))\n", " return f\n", "\n", "\n", "class Queue:\n", " def __init__(self):\n", " self.items, self.log = [], []\n", "\n", " def submit(self, sid, minute, score, grade):\n", " prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n", " body = {\"event\": \"SUBMIT\", \"id\": f\"{sid}@{minute}\", \"score\": round(score, 3), \"by\": \"AI\"}\n", " self.log.append({**body, \"prev\": prev, \"hash\": hashlib.sha256((prev + json.dumps(body, sort_keys=True)).encode()).hexdigest()})\n", " self.items.append({\"id\": f\"{sid}@{minute}\", \"grade\": grade, \"status\": \"PENDING\"})\n", "\n", " def verify(self):\n", " prev = \"GENESIS\"\n", " for rec in self.log:\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, sort_keys=True)).encode()).hexdigest():\n", " return False\n", " prev = rec[\"hash\"]\n", " return True\n", "\n", "\n", "def episodes(f):\n", " \"\"\"์—ฐ์† ๊ฒฝ๋ณด ๋ถ„์„ ํ•˜๋‚˜์˜ ์‚ฌ๊ฑด์œผ๋กœ ๋ฌถ์–ด ์ฒซ ๋ถ„๋งŒ ๊ฒ€ํ†  ๋Œ€๊ธฐ์—ด์— ์˜ฌ๋ฆฐ๋‹ค(๊ฐ™์€ ์‚ฌ๊ฑด ์ค‘๋ณต ํ†ต๋ณด ๋ฐฉ์ง€).\"\"\"\n", " a = f[\"alert\"].values\n", " return [i for i in range(len(a)) if a[i] and (i == 0 or not a[i - 1])]\n", "\n", "\n", "t_start = time.perf_counter()\n", "# ---------------- 1. ํ•™์Šต(์ ์ˆ˜ ์—”์ง„ ์ค€๋น„) ----------------\n", "tr = table(make_dataset(150, seed=51))\n", "model = make_pipeline(StandardScaler(), LogisticRegression(class_weight=\"balanced\", max_iter=3000, random_state=0)).fit(tr[FE], tr[\"stage\"] >= 1)\n", "\n", "# ---------------- 2. ์šด์šฉ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ(์†์ƒ 1๊ฑด, ๊ธฐ๊ธฐ ๋‘์ ˆ 1๊ฑด ์ฃผ์ž…) ----------------\n", "rng = np.random.default_rng(61)\n", "ops = [make_session(i, TYPES[i % 3], rng) for i in range(60)]\n", "ops[4] = {**ops[4], \"kind\": \"tundra\"} # ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ์œ ํ˜•\n", "dead = ops[8][\"df\"].copy(); dead.loc[35:, [\"hr\", \"temp\", \"eda\"]] = np.nan; ops[8] = {**ops[8], \"df\": dead} # ๊ธฐ๊ธฐ ๋‘์ ˆ\n", "queue, rejected, runs, results = Queue(), {}, [], []\n", "codes = {f.__code__ for f in (validate, run_session, episodes, Queue.submit, Queue.verify, clean, env_index, features, table)}\n", "lines_total = {c: {ln for _, ln in dis.findlinestarts(c) if ln != c.co_firstlineno} for c in codes}\n", "trace_lines = set()\n", "\n", "\n", "def tracer(frame, event, arg):\n", " if frame.f_code in codes:\n", " def local(fr, ev, ar):\n", " if ev == \"line\":\n", " trace_lines.add((fr.f_code, fr.f_lineno))\n", " return local\n", " return local\n", "\n", "\n", "sys.settrace(tracer)\n", "for s in ops:\n", " errs = validate(s)\n", " if errs:\n", " rejected[s[\"id\"]] = errs; continue\n", " t0 = time.perf_counter()\n", " f = run_session(s, model)\n", " for i in episodes(f):\n", " queue.submit(s[\"id\"], int(f[\"min\"].iloc[i]), float(f[\"score\"].iloc[i]), f[\"grade\"].iloc[i])\n", " runs.append({\"sid\": s[\"id\"], \"ms_per_min\": (time.perf_counter() - t0) * 1000 / len(f), \"events\": len(episodes(f)), \"lost\": int(f[\"lost\"].any())})\n", " results.append(f)\n", "queue.verify()\n", "sys.settrace(None)\n", "covered = sum(len({ln for c2, ln in trace_lines if c2 is c} & lines_total[c]) for c in codes)\n", "cov = covered / sum(len(v) for v in lines_total.values())\n", "\n", "# ---------------- 3. ์„ฑ๋Šฅ ํ‰๊ฐ€ ----------------\n", "allf = pd.concat(results, ignore_index=True)\n", "truth = allf[\"stage\"] >= 1\n", "far = (allf[\"alert\"] & ~truth).sum() / max(allf[\"alert\"].sum(), 1)\n", "rec = (allf[\"alert\"] & truth).sum() / truth.sum()\n", "risk_sids = set(allf.loc[truth, \"sid\"]); alerted = set(allf.loc[allf[\"alert\"] & truth, \"sid\"])\n", "ms = np.mean([r[\"ms_per_min\"] for r in runs])\n", "\n", "# ---------------- 4. ์ž์ฒด ์ ๊ฒ€(ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ) ----------------\n", "chk = {\n", " \"์†์ƒ ๋ฐ์ดํ„ฐ ์ฐจ๋‹จ\": \"S004\" in rejected,\n", " \"์ฒ˜๋ฆฌ ์„ธ์…˜ 59๊ฐœ ์ „๋Ÿ‰ ์„ฑ๊ณต\": len(runs) == 59,\n", " \"์‹ ํ˜ธ๋‘์ ˆ ๋ณ‘์‚ฌ ์‹๋ณ„\": any(r[\"lost\"] for r in runs) and bool(allf.loc[allf[\"sid\"] == \"S008\", \"lost\"].any()),\n", " \"์œ„ํ—˜ ๊ตฌ๊ฐ„ ์žฌํ˜„์œจ 85% ์ด์ƒ\": rec >= 0.85,\n", " \"์˜ค๊ฒฝ๋ณด์œจ 15% ์ดํ•˜\": far <= 0.15,\n", " \"์œ„ํ—˜ ๋ณ‘์‚ฌ ์‚ฌ๊ฑด ์‹๋ณ„ 90% ์ด์ƒ\": len(alerted) / max(len(risk_sids), 1) >= 0.9,\n", " \"์ž๋™ ์กฐ์น˜ 0๊ฑด\": all(i[\"status\"] == \"PENDING\" for i in queue.items),\n", " \"๊ฐ์‚ฌ ๋กœ๊ทธ ๊ฑด์ˆ˜ ์ผ์น˜ยท๋ฌด๊ฒฐ\": len(queue.log) == len(queue.items) and queue.verify(),\n", " \"๋ถ„๋‹น ์ฒ˜๋ฆฌ 50ms ์ด๋‚ด\": ms < 50,\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)} ํ†ต๊ณผ ({passed / len(chk):.0%}), ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰ ์„ฑ๊ณต {'์˜ˆ' if len(runs) + len(rejected) == len(ops) else '์•„๋‹ˆ์˜ค'}, ํ•ต์‹ฌ ๋ชจ๋“ˆ ์‹คํ–‰ ๋ผ์ธ ์ปค๋ฒ„๋ฆฌ์ง€ {cov:.1%}\")\n", "\n", "# ---------------- 5. ์†Œ๋Œ€ ์ƒํ™ฉํŒ ----------------\n", "colr = {\"High\": \"tab:red\", \"Medium\": \"orange\", \"Low\": \"tab:gray\"}\n", "fig, ax = plt.subplots(2, 2, figsize=(12, 8))\n", "top = sorted(results, key=lambda f: -f[\"score\"].max())[:2]\n", "for a, f in zip(ax[0], top):\n", " a.plot(f[\"min\"], f[\"temp5\"], label=\"temp (5-min mean)\"); a.scatter(f[\"min\"][f[\"alert\"]], f[\"temp5\"][f[\"alert\"]], color=\"red\", s=12, label=\"alert\")\n", " a.set_title(f\"{f['sid'].iloc[0]} temperature and alerts\"); a.set_xlabel(\"minute\"); a.legend()\n", "g = pd.Series([i[\"grade\"] for i in queue.items]).value_counts().reindex([\"High\", \"Medium\", \"Low\"]).fillna(0)\n", "ax[1, 0].bar(g.index, g.values, color=[colr[i] for i in g.index]); ax[1, 0].set_title(\"Review queue events by grade (pending human review)\")\n", "ax[1, 1].axis(\"off\")\n", "ax[1, 1].text(0, 0.9, f\"Risk recall {rec:.1%}\\nFalse alarm {far:.1%}\\nQueue {len(queue.items)} pending\\nLatency {ms:.2f} ms/min\\nSelf-check {passed}/{len(chk)}\", family=\"monospace\", fontsize=13, va=\"top\")\n", "plt.suptitle(\"WEL01 integrated pipeline - dummy data, not diagnosis\"); plt.tight_layout(); plt.show()\n", "\n", "# ---------------- 6. ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ ----------------\n", "print(f\"\"\"\n", "[์—ดยทํ•œ๋žญ ์ŠคํŠธ๋ ˆ์Šค ๊ฒฝ๋ณด ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ - ๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ, ์ง„๋‹จ ์•„๋‹˜]\n", "1. ์ฒ˜๋ฆฌ: ๊ฐ€์ƒ ๋ณ‘์‚ฌ {len(ops)}๋ช… ์ค‘ {len(runs)}๋ช… ์ฒ˜๋ฆฌ, ์†์ƒ ๋ฐ์ดํ„ฐ {len(rejected)}๊ฑด ์ฐจ๋‹จ({', '.join(rejected)}), ์‹ ํ˜ธ๋‘์ ˆ {sum(r['lost'] for r in runs)}๋ช… ํ™•์ธ ํ•„์š” ํ‘œ์‹œ\n", "2. ์„ฑ๋Šฅ: ์œ„ํ—˜ ๊ตฌ๊ฐ„ ์žฌํ˜„์œจ {rec:.1%}, ์˜ค๊ฒฝ๋ณด์œจ {far:.1%}, ๋ถ„๋‹น {ms:.2f} ms, ์ „์ฒด ์†Œ์š” {time.perf_counter() - t_start:.1f} s\n", "3. ๊ฒฝ๋ณด: {len(queue.items)}๊ฑด ๊ตฐ์˜๊ด€ยท์†Œ๋Œ€์žฅ ๊ฒ€ํ†  ๋Œ€๊ธฐ (High {sum(i['grade'] == 'High' for i in queue.items)} / Medium {sum(i['grade'] == 'Medium' for i in queue.items)} / Low {sum(i['grade'] == 'Low' for i in queue.items)}), ์ž๋™ ์กฐ์น˜ 0๊ฑด\n", "4. ์šด์šฉ ์œ ์˜: ํœด์‹ ๊ถŒ๊ณ ยท์ž„๋ฌด ๋ฐฐ์ œยท์˜๋ฌด๋Œ€ ์ธ๊ณ„๋Š” ๊ตฐ์˜๊ด€ยท์†Œ๋Œ€์žฅยท์ง€ํœ˜๊ด€์˜ ์Šน์ธ ํ›„์—๋งŒ ์ด๋ฃจ์–ด์ง€๋ฉฐ, ์˜ํ•™์  ํŒ๋‹จ์€ ์˜๋ฌด๋Œ€ยท๊ตฐ์˜๊ด€์—๊ฒŒ ์—ฐ๊ณ„ํ•œ๋‹ค.\n", "5. ํ•œ๊ณ„: ๊ฐ€์ƒ ์ƒ๋ฆฌ ๋ชจ๋ธ๋กœ ๋งŒ๋“  ๋”๋ฏธ ์‹ ํ˜ธ ๊ธฐ์ค€ ๊ฒฐ๊ณผ๋กœ, ์ง„๋‹จ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ฉฐ ์‹ค์ œ ์žฅ๋น„ยท์ธ์› ์ž๋ฃŒ๋กœ ์žฌ๊ฒ€์ฆ์ด ํ•„์š”ํ•˜๋‹ค.\"\"\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 10 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[PASS] ์†์ƒ ๋ฐ์ดํ„ฐ ์ฐจ๋‹จ\n", "[PASS] ์ฒ˜๋ฆฌ ์„ธ์…˜ 59๊ฐœ ์ „๋Ÿ‰ ์„ฑ๊ณต\n", "[PASS] ์‹ ํ˜ธ๋‘์ ˆ ๋ณ‘์‚ฌ ์‹๋ณ„\n", "[PASS] ์œ„ํ—˜ ๊ตฌ๊ฐ„ ์žฌํ˜„์œจ 85% ์ด์ƒ\n", "[PASS] ์˜ค๊ฒฝ๋ณด์œจ 15% ์ดํ•˜\n", "[PASS] ์œ„ํ—˜ ๋ณ‘์‚ฌ ์‚ฌ๊ฑด ์‹๋ณ„ 90% ์ด์ƒ\n", "[PASS] ์ž๋™ ์กฐ์น˜ 0๊ฑด\n", "[PASS] ๊ฐ์‚ฌ ๋กœ๊ทธ ๊ฑด์ˆ˜ ์ผ์น˜ยท๋ฌด๊ฒฐ\n", "[PASS] ๋ถ„๋‹น ์ฒ˜๋ฆฌ 50ms ์ด๋‚ด\n", "[PASS] ์‹คํ–‰ ๋ผ์ธ ์ปค๋ฒ„๋ฆฌ์ง€ 85% ์ด์ƒ\n", "์ž์ฒด ์ ๊ฒ€ 10/10 ํ†ต๊ณผ (100%), ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰ ์„ฑ๊ณต ์˜ˆ, ํ•ต์‹ฌ ๋ชจ๋“ˆ ์‹คํ–‰ ๋ผ์ธ ์ปค๋ฒ„๋ฆฌ์ง€ 92.7%\n", "\n", "[์—ดยทํ•œ๋žญ ์ŠคํŠธ๋ ˆ์Šค ๊ฒฝ๋ณด ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ - ๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ, ์ง„๋‹จ ์•„๋‹˜]\n", "1. ์ฒ˜๋ฆฌ: ๊ฐ€์ƒ ๋ณ‘์‚ฌ 60๋ช… ์ค‘ 59๋ช… ์ฒ˜๋ฆฌ, ์†์ƒ ๋ฐ์ดํ„ฐ 1๊ฑด ์ฐจ๋‹จ(S004), ์‹ ํ˜ธ๋‘์ ˆ 1๋ช… ํ™•์ธ ํ•„์š” ํ‘œ์‹œ\n", "2. ์„ฑ๋Šฅ: ์œ„ํ—˜ ๊ตฌ๊ฐ„ ์žฌํ˜„์œจ 85.6%, ์˜ค๊ฒฝ๋ณด์œจ 8.3%, ๋ถ„๋‹น 2.24 ms, ์ „์ฒด ์†Œ์š” 10.2 s\n", "3. ๊ฒฝ๋ณด: 40๊ฑด ๊ตฐ์˜๊ด€ยท์†Œ๋Œ€์žฅ ๊ฒ€ํ†  ๋Œ€๊ธฐ (High 24 / Medium 15 / Low 1), ์ž๋™ ์กฐ์น˜ 0๊ฑด\n", "4. ์šด์šฉ ์œ ์˜: ํœด์‹ ๊ถŒ๊ณ ยท์ž„๋ฌด ๋ฐฐ์ œยท์˜๋ฌด๋Œ€ ์ธ๊ณ„๋Š” ๊ตฐ์˜๊ด€ยท์†Œ๋Œ€์žฅยท์ง€ํœ˜๊ด€์˜ ์Šน์ธ ํ›„์—๋งŒ ์ด๋ฃจ์–ด์ง€๋ฉฐ, ์˜ํ•™์  ํŒ๋‹จ์€ ์˜๋ฌด๋Œ€ยท๊ตฐ์˜๊ด€์—๊ฒŒ ์—ฐ๊ณ„ํ•œ๋‹ค.\n", "5. ํ•œ๊ณ„: ๊ฐ€์ƒ ์ƒ๋ฆฌ ๋ชจ๋ธ๋กœ ๋งŒ๋“  ๋”๋ฏธ ์‹ ํ˜ธ ๊ธฐ์ค€ ๊ฒฐ๊ณผ๋กœ, ์ง„๋‹จ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ฉฐ ์‹ค์ œ ์žฅ๋น„ยท์ธ์› ์ž๋ฃŒ๋กœ ์žฌ๊ฒ€์ฆ์ด ํ•„์š”ํ•˜๋‹ค.\n", "```\n", "
\n" ] } ], "metadata": { "colab": { "provenance": [] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }