{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# ๐ฏ [TRN02] ๋ฐฉ๊ณตยท๋๋ก ์๋ณ ํด์ฆ ์๋์ฑ์ ยท์ค๋ต์ ํ ๋ถ์๊ธฐ\n",
"### Google Colab ์ค์ต ํ๊ฒฝ (๊ต์กยทํ๋ จ / Level 2)\n",
"\n",
"[](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/TRN02/TRN02_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",
"๋ฏธ๊ตญ-์ด๋ ์ ์ ์ฌ๋ก์์ ๋ณด๋ฏ ๋๋ก ๊ณผ ๋ฏธ์ฌ์ผ์ด ์์ฌ ๋ ์์ค๋ ์ ์ฅ์์๋ ์ฅ๋ณ ํ ์ฌ๋ ํ ์ฌ๋์ด ๋นํ์ฒด ํ์์ ์๋ณํ๊ณ , ์๊ตฐยท๋ฏผ๊ฐ๊ธฐ์ ๊ตฌ๋ถํ๋ฉฐ, ์ ํด์ง ๋ณด๊ณ ยทํต์ ์ ์ฐจ๋ฅผ ์งํค๋ ๋ฅ๋ ฅ์ด ๋ฐฉ๊ณต์ ๊ธฐ์ด๊ฐ ๋๋ค. ๊ทธ๋ฌ๋ ๋ถ๋ ๋จ์ ๋ฐฉ๊ณต ์๋ณ ๊ต์ก์ ์ฑ์ ๊ณผ ์ค๋ต ๋ถ์์ ๊ต๊ด์ ์์ด ๋ง์ด ๋ค์ด ๊ฐ์ธ๋ณ ์ฝ์ ์ ์ ๋ ์ง์ด ์ฃผ๊ธฐ ์ด๋ ต๋ค. ๋ณธ ํ๋ก์ ํธ์์๋ ๊ต์ก์ฉ ๊ฐ์ ๋ฌธํญ ์ํ๊ณผ ๊ฐ์ ์์์ ์๋ต์ ์ฝ๋๋ก ๋ง๋ค์ด, ๋ฌธํญ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ์, ์๋ต ์ ์ ยทํ์ฑ, ์๋ ์ฑ์ , ์ค๋ต ์ ํ(๊ฐ๋
ํผ๋ยทํผ์ ์ค์ธยท์ ์ฐจ ๋๋ฝ) ๋ถ๋ฅ, ์ทจ์ฝ ์์ญ ์ถ์ฒ, ๊ต์์ ๋์๋ณด๋, ๊ต๊ด ๊ฒํ (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",
"๋ฐฉ๊ณต ์๋ณ ๋ฐ ๋๋๋ก ๊ต์ ์์น ํด์ฆ ๋ฌธํญ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฐ ํ๊ฐ ๊ธฐ์ค์ ์๋ฆฝํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **ํ์์๋ณยทํผ์์๋ณยท๊ฒฝ๋ณด๋ณด๊ณ ยท๊ต์ ํต์ ยท๋๋๋ก ์ผ๋ฐ 5๊ฐ ์์ญ์ ํด์ฆ ๋ฌธํญ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ตฌ์กฐ์ ๋์ด๋๋ณ ๋ฐฐ์ ยทํฉ๊ฒฉ ๊ธฐ์ค์ ์ ํ๊ณ , ์๋ชป๋ ๋ฌธํญยท์๋ต ๊ธฐ๋ก์ ๊ฑธ๋ฌ๋ด๋ ๊ฒ์ฆ๊ธฐ๋ฅผ ๋ง๋ ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ ๋คํยท๋จ๋ตํยท์์ํ ๋ฌธํญ์ ํ๋(๋ฌธํญID, ์์ญ, ๋์ด๋, ์ ๋ต, ์ค๋ต ๋ณด๊ธฐ๋ณ ์ค๋ต ์ ํ)์ ์๋ต ๊ธฐ๋ก ํ๋(์์์, ๋ฌธํญ, ์๋ณธ ์๋ต, ์ ์ถ ์๊ฐ), ์์ ์ํฉ ํ๋(์ฅ์ยทํต์ ์ํ ๋ฑ) ์ ์ํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `data/schema/trn02_input_schema.json ๋ฐ docs/trn02_data_spec.md` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ๊ต์กยทํ๋ จ ์ค๋ฌด ๋ฐ ๋ฏธ๊ตญ-์ด๋ ์ ์ ์ ์ฅ ํ๊ฒฝ๊ณผ์ ๊ตฐ์ฌ์ ๋ถํฉ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์คํค๋ง ํ๋ ์ ์ ์์ฑ๋ 100%, ์ํ ๋ฐ์ดํฐ ๊ฒ์ฆ ์ค๋ฅ 0๊ฑด\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์
๋ ฅ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฆ ์คํฌ๋ฆฝํธ ํต๊ณผ ๋ฐ ๋ช
์ธ์ ๋ฌธ์ํ ์๋ฃ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: JSON Schema ๊ฒ์ฆ ๋๊ตฌ ๋ฐ ์ํ ๋ฐ์ดํฐ ํ์ฑ ๋จ์ ํ
์คํธ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"๋ฐฉ๊ณต ์๋ณยท๋๋๋ก ๊ต์ ํต์ ์์น ํด์ฆ์ ๋ฌธํญ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์๋ต ๊ธฐ๋ก ์คํค๋ง, ๋์ด๋๋ณ ๋ฐฐ์ ยทํฉ๊ฒฉ ๊ธฐ์ค์ ์ ์ํ๊ณ , ๊ฐ์ ๋ฌธํญยท์๋ต ์ํ์ ๊ฒ์ฆํ๋ค.\"\"\"\n",
"import copy\n",
"import re\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฐฉ๊ณตยท๋๋ก ์๋ณ ํด์ฆ ๋ฌธํญ ์ํ๊ณผ ๊ฐ์ ์์์ ์๋ต ์์ฑ๊ธฐ\n",
"# ๋ฌธํญ์ ์ผ๋ฐ ์์น ์์ค์ ๊ฐ์ ๋ฌธํญ์ด๋ฉฐ ์ค์ ๊ต๋ฒยท๊ต์ ๊ท์น ์๋ฌธ์ด ์๋๋ค. ์์์๋ ๊ฐ๋ช
(S001~)์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"AREAS = [\"ํ์์๋ณ\", \"ํผ์์๋ณ\", \"๊ฒฝ๋ณด๋ณด๊ณ \", \"๊ต์ ํต์ \", \"๋๋๋ก ์ผ๋ฐ\"]\n",
"ETYPES = [\"๊ฐ๋
ํผ๋\", \"ํผ์ ์ค์ธ\", \"์ ์ฐจ ๋๋ฝ\"]\n",
"POINTS = {1: 2, 2: 3, 3: 5}\n",
"C, F, P = ETYPES\n",
"\n",
"\n",
"def _mc(qid, area, diff, stem, opts, ans, dist):\n",
" return {\"qid\": qid, \"type\": \"MC\", \"area\": area, \"diff\": diff, \"stem\": stem, \"options\": opts, \"answer\": ans,\n",
" \"dist\": dict(zip([i for i in range(1, 5) if i != ans], dist))}\n",
"\n",
"\n",
"def _sa(qid, area, diff, stem, accept, wrong):\n",
" return {\"qid\": qid, \"type\": \"SA\", \"area\": area, \"diff\": diff, \"stem\": stem, \"accept\": accept, \"wrong\": wrong}\n",
"\n",
"\n",
"def _order(qid, area, diff, stem):\n",
" return {\"qid\": qid, \"type\": \"ORDER\", \"area\": area, \"diff\": diff, \"stem\": stem, \"answer\": \"ABCD\"}\n",
"\n",
"\n",
"BANK = [\n",
" _mc(\"Q01\", \"ํ์์๋ณ\", 1, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์์ด ์ผ๊ฐํ ๋ ๊ฐ๋ฅผ ๊ฐ์ง ์ํ ๊ณ ์ ์ต ์ค๋ฃจ์ฃ์?\", [\"์ฟผ๋์ฝฅํฐํ\", \"์ผ๊ฐ์ต ๊ณ ์ ์ตํ\", \"ํ์ ์ต ํฌ๊ธฐํ\", \"์กฐ๋ฅ\"], 2, [C, C, F]),\n",
" _mc(\"Q02\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๊ฐ ์ญ์๋ก ๋ฐฐ์น๋ ์ค๋ฃจ์ฃ์?\", [\"๊ณ ์ ์ต ๋๋ก ํ\", \"์ฟผ๋์ฝฅํฐํ\", \"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\"], 2, [C, C, C]),\n",
" _mc(\"Q03\", \"ํ์์๋ณ\", 2, \"๊ฐ๋๊ณ ๊ธด ๋์ฒด ์ค๊ฐ์ ์์ ๋ ๊ฐ๊ฐ ๋ถ์ ์ค๋ฃจ์ฃ์?\", [\"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\", \"๋ฏผ๊ฐ ๊ฒฝ๋นํ๊ธฐํ\", \"์กฐ๋ฅ\"], 1, [C, F, F]),\n",
" _mc(\"Q04\", \"ํ์์๋ณ\", 3, \"์๊ฑฐ๋ฆฌ๋ผ ์ค๋ฃจ์ฃ ์๋ณ์ด ์ด๋ ค์ธ ๋ ๋จผ์ ํ ํ๋์?\", [\"์ถ์ ์ ํ์ผ๋ก ํ์ ๋ณด๊ณ \", \"๊ด์ธก ์ ๋ณด๋ฅผ ๊ทธ๋๋ก ๋ณด๊ณ ํ๊ณ ์ถ๊ฐ ๊ด์ธก\", \"๊ด์ธก ์ค๋จ\", \"๋ฏผ๊ฐ๊ธฐ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _mc(\"Q05\", \"ํผ์์๋ณ\", 2, \"์๋ณ ์ ํธ ์๋ต์ด ์๋ ํญ๊ณต๊ธฐ์ ๋ํ ์ฌ๋ฐ๋ฅธ ํ๋จ์?\", [\"์ฆ์ ์ ๊ธฐ๋ก ํ์ \", \"๋ฏธํ์ธ ํญ๊ณต๊ธฐ๋ก ๋ถ๋ฅํ๊ณ ์ถ๊ฐ ํ์ธ\", \"์๊ตฐ๊ธฐ๋ก ๊ฐ์ฃผ\", \"๋ฌด์\"], 2, [F, F, P]),\n",
" _mc(\"Q06\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด์ ์ผ์นํ๋ ์ ์ ๋นํ์ฒด์ ๋ถ๋ฅ๋?\", [\"์ ๋๋ก \", \"๋ฏผ๊ฐ๊ธฐ ๊ฐ๋ฅ์ฑ ๋์, ์ถ๊ฐ ํ์ธ\", \"์ํญ๋ฏธ์ฌ์ผ\", \"์๊ตฐ ์ ์ฐฐ๊ธฐ๋ก ํ์ \"], 2, [F, C, F]),\n",
" _mc(\"Q07\", \"ํผ์์๋ณ\", 3, \"์๊ตฐ ๋๋ก ์ด์ฉ ๊ณต์ง๊ฐ ์๋ ๊ณต์ญ์์ ๋ฏธํ์ธ ๋๋ก ์ ๋ณด๋ฉด ์ฐ์ ํ์ธํ ๊ฒ์?\", [\"๊ธฐ์ ์ ๋ณด\", \"์๊ตฐ ์ด์ฉ ๊ณํ๊ณผ ์๋ณ ์ ํธ\", \"๋์ ์ค๋น ์ํ\", \"์ฃผ๋ณ ์งํ\"], 2, [C, P, C]),\n",
" _mc(\"Q08\", \"ํผ์์๋ณ\", 3, \"์กฐ๋ฅ ๋ฌด๋ฆฌ์ ์ํ ๋๋ก ๊ตฐ์ง ๊ตฌ๋ถ์ ๊ฐ์ฅ ๋์์ด ๋๋ ์ ๋ณด๋?\", [\"ํฌ๊ธฐ๋ง ๋น๊ต\", \"๊ถค์ ์ ๊ท์น์ฑ๊ณผ ์ด ์ ํธ\", \"์์\", \"๊ด์ธก ์๊ฐ\"], 2, [C, C, C]),\n",
" _mc(\"Q09\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๊ณต์ค ์ํ ์ต์ด ๋ณด๊ณ ์ ํฌํจํ์ง ์์๋ ๋๋ ๊ฒ์?\", [\"๊ด์ธก ์๊ฐ\", \"๋ฐฉํฅ๊ณผ ๊ฑฐ๋ฆฌ\", \"์ถ์ ์ ํ\", \"๊ด์ธก์ ๊ฐ์ธ ์ฐ๋ฝ์ฒ\"], 4, [P, P, P]),\n",
" _mc(\"Q10\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๊ฒฝ๋ณด ์ ํ ํ ์ํฉ์ด ๋ฐ๋๋ฉด?\", [\"์ต์ด ๋ณด๊ณ ์ ์ง\", \"๋ณ๊ฒฝ ์ฌํญ์ ์ฆ์ ์ถ๊ฐ ๋ณด๊ณ \", \"์ํฉ ์ข
๋ฃ ํ ์ผ๊ด ๋ณด๊ณ \", \"๊ฐ์ธ ํ๋จ์ผ๋ก ๊ฒฝ๋ณด ํด์ \"], 2, [P, P, P]),\n",
" _mc(\"Q11\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ณด๊ณ ๋ด์ฉ์ด ๋ถํ์คํ ๋ ์ฌ๋ฐ๋ฅธ ํํ์?\", [\"ํ์ ํํ์ผ๋ก ๋ณด๊ณ \", \"'์ถ์ '์์ ๋ฐํ๊ณ ๊ทผ๊ฑฐ์ ํจ๊ป ๋ณด๊ณ \", \"๋ณด๊ณ ๋ณด๋ฅ\", \"๋ค๋ฅธ ๊ด์ธก์ ๋ณด๊ณ ๋ฅผ ๋ณต์ฌ\"], 2, [C, P, P]),\n",
" _mc(\"Q12\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋๋ก ํ์ง ํ ๋ณด๊ณ ๊ฒฝ๋ก๋ก ์๋ง์ ๊ฒ์?\", [\"์ธ์ ๋ถ๋์๋ง ์ ํ\", \"์งํ๊ณํต์ ๋ฐ๋ผ ๋ณด๊ณ \", \"๊ฐ์ธ ๋ฉ์ ์ ๋ก ๊ณต์ \", \"๋ณด๊ณ ์์ด ๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q13\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ๋ง์ผ๋ก ๋์์ ๊ฒฐ์ ํ ์ ์๋๊ฐ?\", [\"๊ฐ๋ฅํ๋ค\", \"์ธ๊ฐ ์งํ๊ด์ ์น์ธ์ด ํ์ํ๋ค\", \"์ ๋ขฐ๋ 90% ์ด์์ด๋ฉด ๊ฐ๋ฅ\", \"์ผ๊ฐ์๋ง ๊ฐ๋ฅ\"], 2, [C, C, C]),\n",
" _mc(\"Q14\", \"๊ต์ ํต์ \", 2, \"๋ฏผ๊ฐ ํผํด ๊ฐ๋ฅ์ฑ์ด ํ์ธ๋๋ฉด?\", [\"๊ทธ๋๋ก ์งํ\", \"์ฆ์ ๋ณด๊ณ ํ๊ณ ์งํ๊ด ํ๋จ์ ๋ฐ๋๋ค\", \"์กฐ์น ํ ๋ณด๊ณ \", \"๋ฌด์\"], 2, [P, P, F]),\n",
" _mc(\"Q15\", \"๊ต์ ํต์ \", 3, \"์๋ณ์ด ๋ถํ์คํ ํ์ ์ ๋ํ ์์น์?\", [\"์ํ์ผ๋ก ๊ฐ์ฃผํด ์ฆ์ ๋์\", \"์๋ณ ํ์ธ ์ ๊น์ง ์น์ธ ์ ์ฐจ๋ฅผ ๋ฐ๋ฅธ๋ค\", \"๋ฌด์กฐ๊ฑด ๋ฌด์\", \"๋ค์๊ฒฐ๋ก ๊ฒฐ์ \"], 2, [F, F, C]),\n",
" _mc(\"Q16\", \"๊ต์ ํต์ \", 2, \"ํ๋จ ๊ทผ๊ฑฐ๋ฅผ ๊ธฐ๋กํด์ผ ํ๋ ์ด์ ๋?\", [\"ํ์์ ํ์\", \"์ฌํ ๊ฒํ ์ ์ฑ
์ ์ถ์ \", \"๋ณด๊ณ ์๋ต\", \"๊ธฐ๋ก ๋ถํ์\"], 2, [C, P, P]),\n",
" _mc(\"Q17\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"๋๋๋ก ์ฒด๊ณ์ ์ผ๋ฐ์ ์ฒ๋ฆฌ ์์๋?\", [\"๋์-ํ์ง-์๋ณ\", \"ํ์ง-์๋ณ-๋ณด๊ณ ยทํ๋จ-๋์\", \"์๋ณ-๋์-ํ์ง\", \"๋ณด๊ณ -ํ์ง-๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q18\", \"๋๋๋ก ์ผ๋ฐ\", 3, \"๋ ์ด๋๋ง์ผ๋ก ์กฐ๋ฅ์ ๋๋ก ๊ตฌ๋ถ์ด ์ด๋ ค์ด ์ด์ ๋?\", [\"๋ฐ์ฌ ์ ํธ ํฌ๊ธฐ๊ฐ ๋น์ทํ ์ ์์ด์\", \"๋ ์ด๋๋ ์ผ๊ฐ์ ๋ชป ์จ์\", \"๋๋ก ์๋ ๊ธ์์ด ์์ด์\", \"์กฐ๋ฅ๋ ํ์ง๋์ง ์์์\"], 1, [C, C, C]),\n",
" _mc(\"Q19\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ ์๊ดํ(EO) ์ผ์์ ์ฝ์ ์ด ์ปค์ง๋ ์กฐ๊ฑด์?\", [\"๋ง์ ์ฃผ๊ฐ\", \"์๊ฐยท์ผ๊ฐ\", \"์ ์ ํ์ \", \"๊ทผ๊ฑฐ๋ฆฌ\"], 2, [C, C, C]),\n",
" _mc(\"Q20\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"๊ฒฝ๋ณด๊ฐ ๋ฐ๋ณตํด์ ์ค๊ฒฝ๋ณด๋ก ํ๋ช
๋๋ฉด?\", [\"๊ฒฝ๋ณด ์ฅ์น๋ฅผ ๋๋ค\", \"์ฌ๋ก๋ฅผ ๊ธฐ๋กยท๋ณด๊ณ ํ๊ณ ๊ธฐ์ค ์ฌ๊ฒํ ์์ฒญ\", \"๋ฌด์ํ๋ค\", \"๋ชจ๋ ์ค์ ์ํ์ผ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _sa(\"Q21\", \"ํผ์์๋ณ\", 1, \"์๊ตฐยท์ ยท๋ฏผ๊ฐ์ ๊ตฌ๋ถํ๋ ์ ์ฐจ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"ํผ์์๋ณ\", \"IFF\"], {\"ํ์ ํ๋\": C, \"์ ๊ธฐํ์ \": F, \"๊ฒฝ๊ณ\": C}),\n",
" _sa(\"Q22\", \"ํ์์๋ณ\", 2, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์๋ ์ผ๊ฐํ ๋ ๊ฐ ํํ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ผ๊ฐ์ต\", \"๋ธํ์ต\", \"delta\"], {\"์ง์ ์ต\": C, \"ํํด์ต\": C, \"์ฟผ๋์ฝฅํฐ\": C}),\n",
" _sa(\"Q23\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ถํ์คํ ๊ด์ธก์ ๋ณด๊ณ ํ ๋ ๋ถ์ด๋ ํํ์?\", [\"์ถ์ \"], {\"ํ์ \": C, \"ํ์ธ\": C, \"์๋ต\": P}),\n",
" _sa(\"Q24\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ์ ๋ํด ์ต์ข
๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ์ฃผ์ฒด๋?\", [\"์งํ๊ด\"], {\"AI\": C, \"์ด์ฉ๋ณ\": P, \"๋ ์ด๋\": C}),\n",
" _sa(\"Q25\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"์กฐ๋ฅ๋ฅผ ๋๋ก ์ผ๋ก ์๋ชป ๊ฒฝ๋ณดํ๋ ๊ฒ์ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ค๊ฒฝ๋ณด\", \"์คํ\", \"false alarm\"], {\"๋ฏธํ\": C, \"์ฌ๊ฒฝ๋ณด\": C, \"์ ๊ธฐ\": F}),\n",
" _sa(\"Q26\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด๊ฐ ํ์ธ๋ ์ฌ๊ฐ๊ธฐ๋ ์ด๋ป๊ฒ ๋ถ๋ฅํ๋๊ฐ?\", [\"๋ฏผ๊ฐ๊ธฐ\", \"๋ฏผํญ๊ธฐ\"], {\"์ ๊ธฐ\": F, \"๋ฏธํ์ธ๊ธฐ\": P, \"์๊ตฐ๊ธฐ\": F}),\n",
" _sa(\"Q27\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๋ฅผ ๊ฐ์ง ๋๋ก ํํ๋?\", [\"์ฟผ๋์ฝฅํฐ\", \"quadcopter\"], {\"ํฅ์ฌ์ฝฅํฐ\": C, \"๊ณ ์ ์ต\": C, \"ํฌ๊ธฐ\": F}),\n",
" _sa(\"Q28\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋ณด๊ณ ๋ ๋ฌด์์ ๋ฐ๋ผ ์ฌ๋ผ๊ฐ๋๊ฐ?\", [\"์งํ๊ณํต\"], {\"์ธ์ ๋ถ๋\": P, \"๊ฐ์ธ๋ฉ์ ์ \": P, \"ํต์ ๋ง\": C}),\n",
" _order(\"Q29\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"์ต์ด ๋ณด๊ณ ์์: A ๊ด์ธก ์๊ฐ, B ๋ฐฉํฅยท๊ฑฐ๋ฆฌ, C ์ถ์ ์ ํ, D ๊ด์ธก์ ์กฐ์น\"),\n",
" _order(\"Q30\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ฒ๋ฆฌ ์์: A ํ์ง, B ์๋ณ, C ๋ณด๊ณ ยทํ๋จ, D ๋์\"),\n",
" _order(\"Q31\", \"๊ต์ ํต์ \", 3, \"ํต์ ์์: A ์๋ณ ํ์ธ, B ์๊ธ ์น์ธ ์์ฒญ, C ์น์ธ ์๋ น, D ์กฐ์น ๊ธฐ๋ก\"),\n",
" _order(\"Q32\", \"ํผ์์๋ณ\", 3, \"ํ์ธ ์์: A ์ดํญ์ ๋ณด ๋์กฐ, B ์๋ณ ์ ํธ ํ์ธ, C ๋ฏธํ์ธ ๋ถ๋ฅ, D ๋ณด๊ณ \"),\n",
"]\n",
"QIDX = {q[\"qid\"]: q for q in BANK}\n",
"UNITS = [\"๊ฐ์1์ค๋\", \"๊ฐ์2์ค๋\", \"๊ฐ์3์ค๋\"]\n",
"_CIRC = \"โ โกโขโฃ\"\n",
"PARA = {\"Q21\": \"์๊ตฐ์ ๊ตฐ ๊ตฌ๋ถ\", \"Q22\": \"์ธ๋ชจ ๋ ๊ฐ\", \"Q23\": \"์ถ์ ๋จ์ผ๋ก ํ๊ธฐ\", \"Q24\": \"์ค๋์ฅ(์งํ๊ด)\", \"Q25\": \"์๋ชป๋ ๊ฒฝ๋ณด\",\n",
" \"Q26\": \"๋ฏผ๊ฐ ์ฌ๊ฐ๊ธฐ\", \"Q27\": \"4๋ฐ ๋๋ก \", \"Q28\": \"์งํ ๋ผ์ธ\"} # ๋ป์ ๋ง์ง๋ง ์ธ์ ๋ต์ ๋ชฉ๋ก์ ์๋ ํํ\n",
"\n",
"\n",
"def make_soldiers(n_per_unit, seed):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for u, unit in enumerate(UNITS):\n",
" base = [0.3, -0.1, 0.0][u]\n",
" for i in range(n_per_unit):\n",
" off = rng.normal(0, 0.5, len(AREAS)) + (np.array([0, 0, 0, -0.6, 0]) if u == 1 else 0)\n",
" out.append({\"sid\": f\"S{len(out) + 1:03d}\", \"unit\": unit, \"theta\": dict(zip(AREAS, base + rng.normal(0, 0.6) + off)),\n",
" \"tend\": dict(zip(ETYPES, rng.dirichlet([1.2, 1.2, 1.2])))})\n",
" return out\n",
"\n",
"\n",
"def p_correct(theta, diff):\n",
" return 1 / (1 + np.exp(-1.7 * (theta - (diff - 2) * 0.9 + 0.9)))\n",
"\n",
"\n",
"def _typo(s, rng):\n",
" if len(s) < 2:\n",
" return s\n",
" i = int(rng.integers(len(s) - 1))\n",
" return s[:i] + s[i + 1] + s[i] + s[i + 2:] if len(s) <= 3 or rng.random() < 0.5 else s[:i] + s[i + 1:]\n",
"\n",
"\n",
"def _fmt_mc(k, rng):\n",
" r = rng.random()\n",
" if r < 0.55:\n",
" return str(k)\n",
" return rng.choice([_CIRC[k - 1], f\"{k}๋ฒ\", \"ABCD\"[k - 1], \"abcd\"[k - 1] + \")\", f\" {k} \", chr(0xFF10 + k), f\"{k}.\"])\n",
"\n",
"\n",
"def _fmt_sa(s, rng):\n",
" r = rng.random()\n",
" if r < 0.45:\n",
" return s\n",
" if r < 0.6:\n",
" return \" \".join(s) if len(s) <= 4 else s[:2] + \" \" + s[2:]\n",
" if r < 0.72:\n",
" return s + rng.choice([\"์
๋๋ค\", \"์ด๋ค\", \".\", \"์\"])\n",
" if r < 0.8:\n",
" return s.upper() if s.isascii() else \"\".join(chr(ord(ch) + 0xFEE0) if \"!\" <= ch <= \"~\" else ch for ch in s) + \" \"\n",
" return _typo(s, rng)\n",
"\n",
"\n",
"def answer(q, sol, rng):\n",
" \"\"\"ํ ๋ฌธํญ ์๋ต: (์๋ณธ ์๋ต ๋ฌธ์์ด, ์๋ํ ์ ๋ต ์ฌ๋ถ, ์๋ํ ๋ต, ์ค๋ต ์ ํ).\"\"\"\n",
" ok = rng.random() < p_correct(sol[\"theta\"][q[\"area\"]], q[\"diff\"])\n",
" if q[\"type\"] == \"MC\":\n",
" if ok:\n",
" k, et = q[\"answer\"], None\n",
" else:\n",
" ks = list(q[\"dist\"]); w = np.array([sol[\"tend\"][q[\"dist\"][k]] for k in ks])\n",
" k = int(rng.choice(ks, p=w / w.sum())); et = q[\"dist\"][k]\n",
" return _fmt_mc(k, rng), ok, str(k), et\n",
" if q[\"type\"] == \"SA\":\n",
" if ok and rng.random() < 0.06:\n",
" return PARA[q[\"qid\"]], ok, PARA[q[\"qid\"]], None\n",
" if ok:\n",
" s, et = str(rng.choice(q[\"accept\"])), None\n",
" else:\n",
" ws = list(q[\"wrong\"]); w = np.array([sol[\"tend\"][q[\"wrong\"][x]] for x in ws])\n",
" s = str(rng.choice(ws, p=w / w.sum())); et = q[\"wrong\"][s]\n",
" return _fmt_sa(s, rng), ok, s, et\n",
" if ok:\n",
" s = \"ABCD\"\n",
" elif rng.random() < 0.5:\n",
" s = \"ABCD\".replace(\"ABCD\"[rng.integers(1, 4)], \"\") # ๋จ๊ณ ํ๋ ๋น ๋จ๋ฆผ\n",
" else:\n",
" i = int(rng.integers(0, 3)); s = list(\"ABCD\"); s[i], s[i + 1] = s[i + 1], s[i]; s = \"\".join(s) # ์์ ๋ค๋ฐ๋\n",
" raw = s if rng.random() < 0.6 else rng.choice([\"-\".join(s), \" \".join(s), s.lower(), \",\".join(s)])\n",
" return raw, ok, s, (None if ok else P)\n",
"\n",
"\n",
"def make_responses(n_per_unit=40, seed=7, blank=0.02):\n",
" \"\"\"์์์ x ๋ฌธํญ ์๋ตํ(DataFrame)๋ฅผ ๋ง๋ ๋ค. truth_* ์ด์ ๊ต๊ด ๊ธฐ์ค ์ ๋ต(ํ๊ฐ์ฉ)์ด๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows = []\n",
" for sol in make_soldiers(n_per_unit, seed):\n",
" for q in BANK:\n",
" raw, ok, intended, et = answer(q, sol, rng)\n",
" if rng.random() < blank:\n",
" raw, ok, intended, et = \"\", False, \"\", None\n",
" rows.append({\"rid\": f\"R{len(rows):05d}\", \"sid\": sol[\"sid\"], \"unit\": sol[\"unit\"], \"qid\": q[\"qid\"], \"raw\": raw,\n",
" \"truth_correct\": ok, \"truth_answer\": intended, \"truth_etype\": et})\n",
" return pd.DataFrame(rows)\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# 1) ์คํค๋ง: ๋ฌธํญ(์ ํยท์์ญยท๋์ด๋ยท์ ๋ตยท์ค๋ต ์ ํ ๋งคํยท์ํฉ ์กฐ๊ฑด) / ์๋ต(์์์ ๊ฐ๋ช
ยท๋ฌธํญยท์๋ณธ ์๋ตยท์ ์ถ ์๊ฐ)\n",
"SCHEMA = {\n",
" \"item\": {\n",
" \"qid\": {\"type\": str, \"pattern\": r\"^Q\\d{2}$\"},\n",
" \"type\": {\"type\": str, \"enum\": [\"MC\", \"SA\", \"ORDER\", \"ESSAY\"]},\n",
" \"area\": {\"type\": str, \"enum\": AREAS},\n",
" \"diff\": {\"type\": int, \"enum\": [1, 2, 3]},\n",
" \"stem\": {\"type\": str, \"minlen\": 5},\n",
" },\n",
" \"context\": { # ๋ณด์ํ ์ ์ ์ํฉ ํ๋ผ๋ฏธํฐ\n",
" \"time\": {\"type\": str, \"enum\": [\"์ฃผ๊ฐ\", \"์ผ๊ฐ\"]},\n",
" \"weather\": {\"type\": str, \"enum\": [\"๋ง์\", \"์๊ฐ\", \"๋น\", \"๋\"]},\n",
" \"comm\": {\"type\": str, \"enum\": [\"์ ์\", \"์ง์ฐ\", \"๋์ \"]},\n",
" \"position_error_m\": {\"type\": (int, float), \"min\": 0, \"max\": 500},\n",
" },\n",
" \"response\": {\n",
" \"rid\": {\"type\": str, \"pattern\": r\"^R\\d{5}$\"},\n",
" \"sid\": {\"type\": str, \"pattern\": r\"^S\\d{3}$\"},\n",
" \"qid\": {\"type\": str, \"pattern\": r\"^Q\\d{2}$\"},\n",
" \"raw\": {\"type\": str},\n",
" \"submitted_s\": {\"type\": (int, float), \"min\": 0, \"max\": 7200},\n",
" },\n",
"}\n",
"RUBRIC = {\"๋ฐฐ์ (๋์ด๋ 1/2/3)\": \"2 / 3 / 5์ \", \"์์ ํ\": \"10์ (๊ต๊ด ์ฑ์ )\", \"ํฉ๊ฒฉ ๊ธฐ์ค\": \"์ด์ ์ 70% ์ด์\", \"๋ฏธ์๋ต\": \"0์ , ๋ฏธ์๋ต์ผ๋ก ๊ธฐ๋ก(์ญ์ ์ ํจ)\"}\n",
"\n",
"\n",
"def check(name, v, r):\n",
" if not isinstance(v, r[\"type\"]) or isinstance(v, bool):\n",
" return [f\"{name}: ์๋ฃํ ์ค๋ฅ({type(v).__name__})\"]\n",
" e = []\n",
" if \"enum\" in r and v not in r[\"enum\"]:\n",
" e.append(f\"{name}: ํ์ฉ๋์ง ์์ ๊ฐ '{v}'\")\n",
" if \"pattern\" in r and not re.match(r[\"pattern\"], v):\n",
" e.append(f\"{name}: ํ์ ๋ถ์ผ์น '{v}'\")\n",
" if \"minlen\" in r and len(v) < r[\"minlen\"]:\n",
" e.append(f\"{name}: ๊ธธ์ด ๋ถ์กฑ\")\n",
" if \"min\" in r and v < r[\"min\"]:\n",
" e.append(f\"{name}: ์ต์๊ฐ ๋ฏธ๋ง\")\n",
" if \"max\" in r and v > r[\"max\"]:\n",
" e.append(f\"{name}: ์ต๋๊ฐ ์ด๊ณผ\")\n",
" return e\n",
"\n",
"\n",
"def validate_item(it):\n",
" e = []\n",
" for g in (\"item\", \"context\"):\n",
" src = it if g == \"item\" else it.get(\"context\", {})\n",
" for k, r in SCHEMA[g].items():\n",
" e += [f\"{g}.{k}: ํ์ ํ๋ ๋๋ฝ\"] if k not in src else check(f\"{g}.{k}\", src[k], r)\n",
" if it.get(\"type\") == \"MC\":\n",
" if len(it.get(\"options\", [])) != 4 or it.get(\"answer\") not in (1, 2, 3, 4):\n",
" e.append(\"MC: ๋ณด๊ธฐ 4๊ฐยท์ ๋ต ๋ฒํธ(1~4) ํ์\")\n",
" elif set(it.get(\"dist\", {})) != {1, 2, 3, 4} - {it[\"answer\"]} or not set(it[\"dist\"].values()) <= set(ETYPES):\n",
" e.append(\"MC: ์ค๋ต ๋ณด๊ธฐ๋ณ ์ค๋ต ์ ํ ๋งคํ ๋ถ์์ \")\n",
" if it.get(\"type\") == \"SA\" and (not it.get(\"accept\") or not set(it.get(\"wrong\", {}).values()) <= set(ETYPES)):\n",
" e.append(\"SA: ์ธ์ ๋ต์ยท์ค๋ต ์ ํ ๋ชฉ๋ก ํ์\")\n",
" return e\n",
"\n",
"\n",
"def validate_resp(r):\n",
" e = []\n",
" for k, rule in SCHEMA[\"response\"].items():\n",
" e += [f\"response.{k}: ํ์ ํ๋ ๋๋ฝ\"] if k not in r else check(f\"response.{k}\", r[k], rule)\n",
" if r.get(\"qid\") not in QIDX:\n",
" e.append(\"response.qid: ๋ฌธํญ DB์ ์๋ ๋ฌธํญ\")\n",
" return e\n",
"\n",
"\n",
"rng = np.random.default_rng(1)\n",
"items = []\n",
"for q in BANK:\n",
" it = copy.deepcopy(q)\n",
" it[\"context\"] = {\"time\": str(rng.choice([\"์ฃผ๊ฐ\", \"์ผ๊ฐ\"])), \"weather\": str(rng.choice([\"๋ง์\", \"์๊ฐ\", \"๋น\", \"๋\"])),\n",
" \"comm\": str(rng.choice([\"์ ์\", \"์ง์ฐ\"])), \"position_error_m\": float(round(rng.uniform(5, 80), 1))}\n",
" items.append(it)\n",
"resp = make_responses(5, seed=3).head(50)\n",
"recs = [{\"rid\": r.rid, \"sid\": r.sid, \"qid\": r.qid, \"raw\": r.raw, \"submitted_s\": float(i * 12)} for i, r in enumerate(resp.itertuples())]\n",
"ni, nr = sum(not validate_item(i) for i in items), sum(not validate_resp(r) for r in recs)\n",
"print(f\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฌธํญ {len(items)}๊ฐ ์ค ๊ฒ์ฆ ํต๊ณผ {ni}๊ฐ, ์๋ต {len(recs)}๊ฑด ์ค ํต๊ณผ {nr}๊ฑด (๋ชฉํ ์ค๋ฅ 0๊ฑด: {'๋ฌ์ฑ' if ni == len(items) and nr == len(recs) else '๋ฏธ๋ฌ'})\")\n",
"\n",
"bad = copy.deepcopy(items[0]); bad[\"qid\"] = \"Q1\"; bad[\"diff\"] = 4; bad[\"dist\"].pop(4); del bad[\"context\"][\"comm\"]\n",
"bad_r = dict(recs[0], sid=\"๋ณ์ฌ1\", qid=\"Q99\", submitted_s=9999)\n",
"print(\"\\n[์ค๋ฅ ์ฃผ์
๊ฒฐ๊ณผ]\")\n",
"for e in validate_item(bad) + validate_resp(bad_r):\n",
" print(\" -\", e)\n",
"\n",
"print(\"\\n[๋ฌธํญ ๊ตฌ์ฑ]\")\n",
"print(pd.crosstab(pd.Series([i[\"area\"] for i in items], name=\"์์ญ\"), pd.Series([i[\"type\"] for i in items], name=\"์ ํ\"), margins=True, margins_name=\"ํฉ๊ณ\"))\n",
"print(\"\\n[ํ๊ฐ ๊ธฐ์ค]\")\n",
"for k, v in RUBRIC.items():\n",
" print(f\" - {k}: {v}\")\n",
"print(f\" - ๋ง์ : {sum(POINTS[i['diff']] for i in items)}์ (์์ ํ ์ ์ธ), ์ค๋ต ์ ํ: {', '.join(ETYPES)}\")\n",
"n_fields = sum(len(v) for v in SCHEMA.values())\n",
"print(f\"\\n์คํค๋ง ํ๋ {n_fields}๊ฐ ์ ์(๋ฌธํญ {len(SCHEMA['item'])}, ์ํฉ {len(SCHEMA['context'])}, ์๋ต {len(SCHEMA['response'])}), ์ ์ ์์ฑ๋ 100%\")\n",
"print(\"๋ฌธํญ ์์:\", items[3][\"qid\"], items[3][\"stem\"], \"| ์ ๋ต\", items[3][\"answer\"], \"| ์ค๋ต ์ ํ\", items[3][\"dist\"])\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
01 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฌธํญ 32๊ฐ ์ค ๊ฒ์ฆ ํต๊ณผ 32๊ฐ, ์๋ต 50๊ฑด ์ค ํต๊ณผ 50๊ฑด (๋ชฉํ ์ค๋ฅ 0๊ฑด: ๋ฌ์ฑ)\n",
"\n",
"[์ค๋ฅ ์ฃผ์
๊ฒฐ๊ณผ]\n",
" - item.qid: ํ์ ๋ถ์ผ์น 'Q1'\n",
" - item.diff: ํ์ฉ๋์ง ์์ ๊ฐ '4'\n",
" - context.comm: ํ์ ํ๋ ๋๋ฝ\n",
" - MC: ์ค๋ต ๋ณด๊ธฐ๋ณ ์ค๋ต ์ ํ ๋งคํ ๋ถ์์ \n",
" - response.sid: ํ์ ๋ถ์ผ์น '๋ณ์ฌ1'\n",
" - response.submitted_s: ์ต๋๊ฐ ์ด๊ณผ\n",
" - response.qid: ๋ฌธํญ DB์ ์๋ ๋ฌธํญ\n",
"\n",
"[๋ฌธํญ ๊ตฌ์ฑ]\n",
"์ ํ MC ORDER SA ํฉ๊ณ\n",
"์์ญ \n",
"๊ฒฝ๋ณด๋ณด๊ณ 4 1 2 7\n",
"๊ต์ ํต์ 4 1 1 6\n",
"๋๋๋ก ์ผ๋ฐ 4 1 1 6\n",
"ํผ์์๋ณ 4 1 2 7\n",
"ํ์์๋ณ 4 0 2 6\n",
"ํฉ๊ณ 20 4 8 32\n",
"\n",
"[ํ๊ฐ ๊ธฐ์ค]\n",
" - ๋ฐฐ์ (๋์ด๋ 1/2/3): 2 / 3 / 5์ \n",
" - ์์ ํ: 10์ (๊ต๊ด ์ฑ์ )\n",
" - ํฉ๊ฒฉ ๊ธฐ์ค: ์ด์ ์ 70% ์ด์\n",
" - ๋ฏธ์๋ต: 0์ , ๋ฏธ์๋ต์ผ๋ก ๊ธฐ๋ก(์ญ์ ์ ํจ)\n",
" - ๋ง์ : 99์ (์์ ํ ์ ์ธ), ์ค๋ต ์ ํ: ๊ฐ๋
ํผ๋, ํผ์ ์ค์ธ, ์ ์ฐจ ๋๋ฝ\n",
"\n",
"์คํค๋ง ํ๋ 14๊ฐ ์ ์(๋ฌธํญ 5, ์ํฉ 4, ์๋ต 5), ์ ์ ์์ฑ๋ 100%\n",
"๋ฌธํญ ์์: Q04 ์๊ฑฐ๋ฆฌ๋ผ ์ค๋ฃจ์ฃ ์๋ณ์ด ์ด๋ ค์ธ ๋ ๋จผ์ ํ ํ๋์? | ์ ๋ต 2 | ์ค๋ต ์ ํ {1: '์ ์ฐจ ๋๋ฝ', 3: '์ ์ฐจ ๋๋ฝ', 4: 'ํผ์ ์ค์ธ'}\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
02. ์ฅ๋ณ ์๋ต ํ
์คํธ/์ ํ์ง ์ ์ ๋ฐ ์ํ ๋ต์ ํ์ฑ ์ ์ฒ๋ฆฌ ๋ชจ๋ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์ฅ๋ณ ์๋ต ํ
์คํธ/์ ํ์ง ์ ์ ๋ฐ ์ํ ๋ต์ ํ์ฑ ์ ์ฒ๋ฆฌ ๋ชจ๋์ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ฅ๋ณ ์๋ต์ ์ ๊ฐ ์ซ์ยท์๋ฌธ์ยท๊ณต๋ฐฑยท์ด๋ฏธ ๊ฐ์ ํ๊ธฐ ์ฐจ์ด๋ฅผ ์ ์ ํ๊ณ ๋ฌธํญ ์ ํ๋ณ๋ก ๋ต์์ ํ์ฑํ๋ ์ ์ฒ๋ฆฌ ๋ชจ๋์ ๊ตฌํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ ๋์ฝ๋ ์ ๊ทํ(NFKC), ์๋ฌธ์ยท์๋ฌธ ๋ณด๊ธฐยท'๋ฒ' ํ๊ธฐ ํต์ผ, ๋จ๋ตํ ๊ณต๋ฐฑยท๋๋งบ์ ์ด๋ฏธ ์ ๊ฑฐ, ์์ํ ๊ตฌ๋ถ์ ์ ๊ฑฐ ๊ตฌํํ๊ณ ๋์์ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/preprocess/trn02_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 difflib\n",
"import re\n",
"import time\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฐฉ๊ณตยท๋๋ก ์๋ณ ํด์ฆ ๋ฌธํญ ์ํ๊ณผ ๊ฐ์ ์์์ ์๋ต ์์ฑ๊ธฐ\n",
"# ๋ฌธํญ์ ์ผ๋ฐ ์์น ์์ค์ ๊ฐ์ ๋ฌธํญ์ด๋ฉฐ ์ค์ ๊ต๋ฒยท๊ต์ ๊ท์น ์๋ฌธ์ด ์๋๋ค. ์์์๋ ๊ฐ๋ช
(S001~)์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"AREAS = [\"ํ์์๋ณ\", \"ํผ์์๋ณ\", \"๊ฒฝ๋ณด๋ณด๊ณ \", \"๊ต์ ํต์ \", \"๋๋๋ก ์ผ๋ฐ\"]\n",
"ETYPES = [\"๊ฐ๋
ํผ๋\", \"ํผ์ ์ค์ธ\", \"์ ์ฐจ ๋๋ฝ\"]\n",
"POINTS = {1: 2, 2: 3, 3: 5}\n",
"C, F, P = ETYPES\n",
"\n",
"\n",
"def _mc(qid, area, diff, stem, opts, ans, dist):\n",
" return {\"qid\": qid, \"type\": \"MC\", \"area\": area, \"diff\": diff, \"stem\": stem, \"options\": opts, \"answer\": ans,\n",
" \"dist\": dict(zip([i for i in range(1, 5) if i != ans], dist))}\n",
"\n",
"\n",
"def _sa(qid, area, diff, stem, accept, wrong):\n",
" return {\"qid\": qid, \"type\": \"SA\", \"area\": area, \"diff\": diff, \"stem\": stem, \"accept\": accept, \"wrong\": wrong}\n",
"\n",
"\n",
"def _order(qid, area, diff, stem):\n",
" return {\"qid\": qid, \"type\": \"ORDER\", \"area\": area, \"diff\": diff, \"stem\": stem, \"answer\": \"ABCD\"}\n",
"\n",
"\n",
"BANK = [\n",
" _mc(\"Q01\", \"ํ์์๋ณ\", 1, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์์ด ์ผ๊ฐํ ๋ ๊ฐ๋ฅผ ๊ฐ์ง ์ํ ๊ณ ์ ์ต ์ค๋ฃจ์ฃ์?\", [\"์ฟผ๋์ฝฅํฐํ\", \"์ผ๊ฐ์ต ๊ณ ์ ์ตํ\", \"ํ์ ์ต ํฌ๊ธฐํ\", \"์กฐ๋ฅ\"], 2, [C, C, F]),\n",
" _mc(\"Q02\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๊ฐ ์ญ์๋ก ๋ฐฐ์น๋ ์ค๋ฃจ์ฃ์?\", [\"๊ณ ์ ์ต ๋๋ก ํ\", \"์ฟผ๋์ฝฅํฐํ\", \"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\"], 2, [C, C, C]),\n",
" _mc(\"Q03\", \"ํ์์๋ณ\", 2, \"๊ฐ๋๊ณ ๊ธด ๋์ฒด ์ค๊ฐ์ ์์ ๋ ๊ฐ๊ฐ ๋ถ์ ์ค๋ฃจ์ฃ์?\", [\"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\", \"๋ฏผ๊ฐ ๊ฒฝ๋นํ๊ธฐํ\", \"์กฐ๋ฅ\"], 1, [C, F, F]),\n",
" _mc(\"Q04\", \"ํ์์๋ณ\", 3, \"์๊ฑฐ๋ฆฌ๋ผ ์ค๋ฃจ์ฃ ์๋ณ์ด ์ด๋ ค์ธ ๋ ๋จผ์ ํ ํ๋์?\", [\"์ถ์ ์ ํ์ผ๋ก ํ์ ๋ณด๊ณ \", \"๊ด์ธก ์ ๋ณด๋ฅผ ๊ทธ๋๋ก ๋ณด๊ณ ํ๊ณ ์ถ๊ฐ ๊ด์ธก\", \"๊ด์ธก ์ค๋จ\", \"๋ฏผ๊ฐ๊ธฐ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _mc(\"Q05\", \"ํผ์์๋ณ\", 2, \"์๋ณ ์ ํธ ์๋ต์ด ์๋ ํญ๊ณต๊ธฐ์ ๋ํ ์ฌ๋ฐ๋ฅธ ํ๋จ์?\", [\"์ฆ์ ์ ๊ธฐ๋ก ํ์ \", \"๋ฏธํ์ธ ํญ๊ณต๊ธฐ๋ก ๋ถ๋ฅํ๊ณ ์ถ๊ฐ ํ์ธ\", \"์๊ตฐ๊ธฐ๋ก ๊ฐ์ฃผ\", \"๋ฌด์\"], 2, [F, F, P]),\n",
" _mc(\"Q06\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด์ ์ผ์นํ๋ ์ ์ ๋นํ์ฒด์ ๋ถ๋ฅ๋?\", [\"์ ๋๋ก \", \"๋ฏผ๊ฐ๊ธฐ ๊ฐ๋ฅ์ฑ ๋์, ์ถ๊ฐ ํ์ธ\", \"์ํญ๋ฏธ์ฌ์ผ\", \"์๊ตฐ ์ ์ฐฐ๊ธฐ๋ก ํ์ \"], 2, [F, C, F]),\n",
" _mc(\"Q07\", \"ํผ์์๋ณ\", 3, \"์๊ตฐ ๋๋ก ์ด์ฉ ๊ณต์ง๊ฐ ์๋ ๊ณต์ญ์์ ๋ฏธํ์ธ ๋๋ก ์ ๋ณด๋ฉด ์ฐ์ ํ์ธํ ๊ฒ์?\", [\"๊ธฐ์ ์ ๋ณด\", \"์๊ตฐ ์ด์ฉ ๊ณํ๊ณผ ์๋ณ ์ ํธ\", \"๋์ ์ค๋น ์ํ\", \"์ฃผ๋ณ ์งํ\"], 2, [C, P, C]),\n",
" _mc(\"Q08\", \"ํผ์์๋ณ\", 3, \"์กฐ๋ฅ ๋ฌด๋ฆฌ์ ์ํ ๋๋ก ๊ตฐ์ง ๊ตฌ๋ถ์ ๊ฐ์ฅ ๋์์ด ๋๋ ์ ๋ณด๋?\", [\"ํฌ๊ธฐ๋ง ๋น๊ต\", \"๊ถค์ ์ ๊ท์น์ฑ๊ณผ ์ด ์ ํธ\", \"์์\", \"๊ด์ธก ์๊ฐ\"], 2, [C, C, C]),\n",
" _mc(\"Q09\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๊ณต์ค ์ํ ์ต์ด ๋ณด๊ณ ์ ํฌํจํ์ง ์์๋ ๋๋ ๊ฒ์?\", [\"๊ด์ธก ์๊ฐ\", \"๋ฐฉํฅ๊ณผ ๊ฑฐ๋ฆฌ\", \"์ถ์ ์ ํ\", \"๊ด์ธก์ ๊ฐ์ธ ์ฐ๋ฝ์ฒ\"], 4, [P, P, P]),\n",
" _mc(\"Q10\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๊ฒฝ๋ณด ์ ํ ํ ์ํฉ์ด ๋ฐ๋๋ฉด?\", [\"์ต์ด ๋ณด๊ณ ์ ์ง\", \"๋ณ๊ฒฝ ์ฌํญ์ ์ฆ์ ์ถ๊ฐ ๋ณด๊ณ \", \"์ํฉ ์ข
๋ฃ ํ ์ผ๊ด ๋ณด๊ณ \", \"๊ฐ์ธ ํ๋จ์ผ๋ก ๊ฒฝ๋ณด ํด์ \"], 2, [P, P, P]),\n",
" _mc(\"Q11\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ณด๊ณ ๋ด์ฉ์ด ๋ถํ์คํ ๋ ์ฌ๋ฐ๋ฅธ ํํ์?\", [\"ํ์ ํํ์ผ๋ก ๋ณด๊ณ \", \"'์ถ์ '์์ ๋ฐํ๊ณ ๊ทผ๊ฑฐ์ ํจ๊ป ๋ณด๊ณ \", \"๋ณด๊ณ ๋ณด๋ฅ\", \"๋ค๋ฅธ ๊ด์ธก์ ๋ณด๊ณ ๋ฅผ ๋ณต์ฌ\"], 2, [C, P, P]),\n",
" _mc(\"Q12\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋๋ก ํ์ง ํ ๋ณด๊ณ ๊ฒฝ๋ก๋ก ์๋ง์ ๊ฒ์?\", [\"์ธ์ ๋ถ๋์๋ง ์ ํ\", \"์งํ๊ณํต์ ๋ฐ๋ผ ๋ณด๊ณ \", \"๊ฐ์ธ ๋ฉ์ ์ ๋ก ๊ณต์ \", \"๋ณด๊ณ ์์ด ๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q13\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ๋ง์ผ๋ก ๋์์ ๊ฒฐ์ ํ ์ ์๋๊ฐ?\", [\"๊ฐ๋ฅํ๋ค\", \"์ธ๊ฐ ์งํ๊ด์ ์น์ธ์ด ํ์ํ๋ค\", \"์ ๋ขฐ๋ 90% ์ด์์ด๋ฉด ๊ฐ๋ฅ\", \"์ผ๊ฐ์๋ง ๊ฐ๋ฅ\"], 2, [C, C, C]),\n",
" _mc(\"Q14\", \"๊ต์ ํต์ \", 2, \"๋ฏผ๊ฐ ํผํด ๊ฐ๋ฅ์ฑ์ด ํ์ธ๋๋ฉด?\", [\"๊ทธ๋๋ก ์งํ\", \"์ฆ์ ๋ณด๊ณ ํ๊ณ ์งํ๊ด ํ๋จ์ ๋ฐ๋๋ค\", \"์กฐ์น ํ ๋ณด๊ณ \", \"๋ฌด์\"], 2, [P, P, F]),\n",
" _mc(\"Q15\", \"๊ต์ ํต์ \", 3, \"์๋ณ์ด ๋ถํ์คํ ํ์ ์ ๋ํ ์์น์?\", [\"์ํ์ผ๋ก ๊ฐ์ฃผํด ์ฆ์ ๋์\", \"์๋ณ ํ์ธ ์ ๊น์ง ์น์ธ ์ ์ฐจ๋ฅผ ๋ฐ๋ฅธ๋ค\", \"๋ฌด์กฐ๊ฑด ๋ฌด์\", \"๋ค์๊ฒฐ๋ก ๊ฒฐ์ \"], 2, [F, F, C]),\n",
" _mc(\"Q16\", \"๊ต์ ํต์ \", 2, \"ํ๋จ ๊ทผ๊ฑฐ๋ฅผ ๊ธฐ๋กํด์ผ ํ๋ ์ด์ ๋?\", [\"ํ์์ ํ์\", \"์ฌํ ๊ฒํ ์ ์ฑ
์ ์ถ์ \", \"๋ณด๊ณ ์๋ต\", \"๊ธฐ๋ก ๋ถํ์\"], 2, [C, P, P]),\n",
" _mc(\"Q17\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"๋๋๋ก ์ฒด๊ณ์ ์ผ๋ฐ์ ์ฒ๋ฆฌ ์์๋?\", [\"๋์-ํ์ง-์๋ณ\", \"ํ์ง-์๋ณ-๋ณด๊ณ ยทํ๋จ-๋์\", \"์๋ณ-๋์-ํ์ง\", \"๋ณด๊ณ -ํ์ง-๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q18\", \"๋๋๋ก ์ผ๋ฐ\", 3, \"๋ ์ด๋๋ง์ผ๋ก ์กฐ๋ฅ์ ๋๋ก ๊ตฌ๋ถ์ด ์ด๋ ค์ด ์ด์ ๋?\", [\"๋ฐ์ฌ ์ ํธ ํฌ๊ธฐ๊ฐ ๋น์ทํ ์ ์์ด์\", \"๋ ์ด๋๋ ์ผ๊ฐ์ ๋ชป ์จ์\", \"๋๋ก ์๋ ๊ธ์์ด ์์ด์\", \"์กฐ๋ฅ๋ ํ์ง๋์ง ์์์\"], 1, [C, C, C]),\n",
" _mc(\"Q19\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ ์๊ดํ(EO) ์ผ์์ ์ฝ์ ์ด ์ปค์ง๋ ์กฐ๊ฑด์?\", [\"๋ง์ ์ฃผ๊ฐ\", \"์๊ฐยท์ผ๊ฐ\", \"์ ์ ํ์ \", \"๊ทผ๊ฑฐ๋ฆฌ\"], 2, [C, C, C]),\n",
" _mc(\"Q20\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"๊ฒฝ๋ณด๊ฐ ๋ฐ๋ณตํด์ ์ค๊ฒฝ๋ณด๋ก ํ๋ช
๋๋ฉด?\", [\"๊ฒฝ๋ณด ์ฅ์น๋ฅผ ๋๋ค\", \"์ฌ๋ก๋ฅผ ๊ธฐ๋กยท๋ณด๊ณ ํ๊ณ ๊ธฐ์ค ์ฌ๊ฒํ ์์ฒญ\", \"๋ฌด์ํ๋ค\", \"๋ชจ๋ ์ค์ ์ํ์ผ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _sa(\"Q21\", \"ํผ์์๋ณ\", 1, \"์๊ตฐยท์ ยท๋ฏผ๊ฐ์ ๊ตฌ๋ถํ๋ ์ ์ฐจ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"ํผ์์๋ณ\", \"IFF\"], {\"ํ์ ํ๋\": C, \"์ ๊ธฐํ์ \": F, \"๊ฒฝ๊ณ\": C}),\n",
" _sa(\"Q22\", \"ํ์์๋ณ\", 2, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์๋ ์ผ๊ฐํ ๋ ๊ฐ ํํ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ผ๊ฐ์ต\", \"๋ธํ์ต\", \"delta\"], {\"์ง์ ์ต\": C, \"ํํด์ต\": C, \"์ฟผ๋์ฝฅํฐ\": C}),\n",
" _sa(\"Q23\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ถํ์คํ ๊ด์ธก์ ๋ณด๊ณ ํ ๋ ๋ถ์ด๋ ํํ์?\", [\"์ถ์ \"], {\"ํ์ \": C, \"ํ์ธ\": C, \"์๋ต\": P}),\n",
" _sa(\"Q24\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ์ ๋ํด ์ต์ข
๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ์ฃผ์ฒด๋?\", [\"์งํ๊ด\"], {\"AI\": C, \"์ด์ฉ๋ณ\": P, \"๋ ์ด๋\": C}),\n",
" _sa(\"Q25\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"์กฐ๋ฅ๋ฅผ ๋๋ก ์ผ๋ก ์๋ชป ๊ฒฝ๋ณดํ๋ ๊ฒ์ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ค๊ฒฝ๋ณด\", \"์คํ\", \"false alarm\"], {\"๋ฏธํ\": C, \"์ฌ๊ฒฝ๋ณด\": C, \"์ ๊ธฐ\": F}),\n",
" _sa(\"Q26\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด๊ฐ ํ์ธ๋ ์ฌ๊ฐ๊ธฐ๋ ์ด๋ป๊ฒ ๋ถ๋ฅํ๋๊ฐ?\", [\"๋ฏผ๊ฐ๊ธฐ\", \"๋ฏผํญ๊ธฐ\"], {\"์ ๊ธฐ\": F, \"๋ฏธํ์ธ๊ธฐ\": P, \"์๊ตฐ๊ธฐ\": F}),\n",
" _sa(\"Q27\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๋ฅผ ๊ฐ์ง ๋๋ก ํํ๋?\", [\"์ฟผ๋์ฝฅํฐ\", \"quadcopter\"], {\"ํฅ์ฌ์ฝฅํฐ\": C, \"๊ณ ์ ์ต\": C, \"ํฌ๊ธฐ\": F}),\n",
" _sa(\"Q28\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋ณด๊ณ ๋ ๋ฌด์์ ๋ฐ๋ผ ์ฌ๋ผ๊ฐ๋๊ฐ?\", [\"์งํ๊ณํต\"], {\"์ธ์ ๋ถ๋\": P, \"๊ฐ์ธ๋ฉ์ ์ \": P, \"ํต์ ๋ง\": C}),\n",
" _order(\"Q29\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"์ต์ด ๋ณด๊ณ ์์: A ๊ด์ธก ์๊ฐ, B ๋ฐฉํฅยท๊ฑฐ๋ฆฌ, C ์ถ์ ์ ํ, D ๊ด์ธก์ ์กฐ์น\"),\n",
" _order(\"Q30\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ฒ๋ฆฌ ์์: A ํ์ง, B ์๋ณ, C ๋ณด๊ณ ยทํ๋จ, D ๋์\"),\n",
" _order(\"Q31\", \"๊ต์ ํต์ \", 3, \"ํต์ ์์: A ์๋ณ ํ์ธ, B ์๊ธ ์น์ธ ์์ฒญ, C ์น์ธ ์๋ น, D ์กฐ์น ๊ธฐ๋ก\"),\n",
" _order(\"Q32\", \"ํผ์์๋ณ\", 3, \"ํ์ธ ์์: A ์ดํญ์ ๋ณด ๋์กฐ, B ์๋ณ ์ ํธ ํ์ธ, C ๋ฏธํ์ธ ๋ถ๋ฅ, D ๋ณด๊ณ \"),\n",
"]\n",
"QIDX = {q[\"qid\"]: q for q in BANK}\n",
"UNITS = [\"๊ฐ์1์ค๋\", \"๊ฐ์2์ค๋\", \"๊ฐ์3์ค๋\"]\n",
"_CIRC = \"โ โกโขโฃ\"\n",
"PARA = {\"Q21\": \"์๊ตฐ์ ๊ตฐ ๊ตฌ๋ถ\", \"Q22\": \"์ธ๋ชจ ๋ ๊ฐ\", \"Q23\": \"์ถ์ ๋จ์ผ๋ก ํ๊ธฐ\", \"Q24\": \"์ค๋์ฅ(์งํ๊ด)\", \"Q25\": \"์๋ชป๋ ๊ฒฝ๋ณด\",\n",
" \"Q26\": \"๋ฏผ๊ฐ ์ฌ๊ฐ๊ธฐ\", \"Q27\": \"4๋ฐ ๋๋ก \", \"Q28\": \"์งํ ๋ผ์ธ\"} # ๋ป์ ๋ง์ง๋ง ์ธ์ ๋ต์ ๋ชฉ๋ก์ ์๋ ํํ\n",
"\n",
"\n",
"def make_soldiers(n_per_unit, seed):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for u, unit in enumerate(UNITS):\n",
" base = [0.3, -0.1, 0.0][u]\n",
" for i in range(n_per_unit):\n",
" off = rng.normal(0, 0.5, len(AREAS)) + (np.array([0, 0, 0, -0.6, 0]) if u == 1 else 0)\n",
" out.append({\"sid\": f\"S{len(out) + 1:03d}\", \"unit\": unit, \"theta\": dict(zip(AREAS, base + rng.normal(0, 0.6) + off)),\n",
" \"tend\": dict(zip(ETYPES, rng.dirichlet([1.2, 1.2, 1.2])))})\n",
" return out\n",
"\n",
"\n",
"def p_correct(theta, diff):\n",
" return 1 / (1 + np.exp(-1.7 * (theta - (diff - 2) * 0.9 + 0.9)))\n",
"\n",
"\n",
"def _typo(s, rng):\n",
" if len(s) < 2:\n",
" return s\n",
" i = int(rng.integers(len(s) - 1))\n",
" return s[:i] + s[i + 1] + s[i] + s[i + 2:] if len(s) <= 3 or rng.random() < 0.5 else s[:i] + s[i + 1:]\n",
"\n",
"\n",
"def _fmt_mc(k, rng):\n",
" r = rng.random()\n",
" if r < 0.55:\n",
" return str(k)\n",
" return rng.choice([_CIRC[k - 1], f\"{k}๋ฒ\", \"ABCD\"[k - 1], \"abcd\"[k - 1] + \")\", f\" {k} \", chr(0xFF10 + k), f\"{k}.\"])\n",
"\n",
"\n",
"def _fmt_sa(s, rng):\n",
" r = rng.random()\n",
" if r < 0.45:\n",
" return s\n",
" if r < 0.6:\n",
" return \" \".join(s) if len(s) <= 4 else s[:2] + \" \" + s[2:]\n",
" if r < 0.72:\n",
" return s + rng.choice([\"์
๋๋ค\", \"์ด๋ค\", \".\", \"์\"])\n",
" if r < 0.8:\n",
" return s.upper() if s.isascii() else \"\".join(chr(ord(ch) + 0xFEE0) if \"!\" <= ch <= \"~\" else ch for ch in s) + \" \"\n",
" return _typo(s, rng)\n",
"\n",
"\n",
"def answer(q, sol, rng):\n",
" \"\"\"ํ ๋ฌธํญ ์๋ต: (์๋ณธ ์๋ต ๋ฌธ์์ด, ์๋ํ ์ ๋ต ์ฌ๋ถ, ์๋ํ ๋ต, ์ค๋ต ์ ํ).\"\"\"\n",
" ok = rng.random() < p_correct(sol[\"theta\"][q[\"area\"]], q[\"diff\"])\n",
" if q[\"type\"] == \"MC\":\n",
" if ok:\n",
" k, et = q[\"answer\"], None\n",
" else:\n",
" ks = list(q[\"dist\"]); w = np.array([sol[\"tend\"][q[\"dist\"][k]] for k in ks])\n",
" k = int(rng.choice(ks, p=w / w.sum())); et = q[\"dist\"][k]\n",
" return _fmt_mc(k, rng), ok, str(k), et\n",
" if q[\"type\"] == \"SA\":\n",
" if ok and rng.random() < 0.06:\n",
" return PARA[q[\"qid\"]], ok, PARA[q[\"qid\"]], None\n",
" if ok:\n",
" s, et = str(rng.choice(q[\"accept\"])), None\n",
" else:\n",
" ws = list(q[\"wrong\"]); w = np.array([sol[\"tend\"][q[\"wrong\"][x]] for x in ws])\n",
" s = str(rng.choice(ws, p=w / w.sum())); et = q[\"wrong\"][s]\n",
" return _fmt_sa(s, rng), ok, s, et\n",
" if ok:\n",
" s = \"ABCD\"\n",
" elif rng.random() < 0.5:\n",
" s = \"ABCD\".replace(\"ABCD\"[rng.integers(1, 4)], \"\") # ๋จ๊ณ ํ๋ ๋น ๋จ๋ฆผ\n",
" else:\n",
" i = int(rng.integers(0, 3)); s = list(\"ABCD\"); s[i], s[i + 1] = s[i + 1], s[i]; s = \"\".join(s) # ์์ ๋ค๋ฐ๋\n",
" raw = s if rng.random() < 0.6 else rng.choice([\"-\".join(s), \" \".join(s), s.lower(), \",\".join(s)])\n",
" return raw, ok, s, (None if ok else P)\n",
"\n",
"\n",
"def make_responses(n_per_unit=40, seed=7, blank=0.02):\n",
" \"\"\"์์์ x ๋ฌธํญ ์๋ตํ(DataFrame)๋ฅผ ๋ง๋ ๋ค. truth_* ์ด์ ๊ต๊ด ๊ธฐ์ค ์ ๋ต(ํ๊ฐ์ฉ)์ด๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows = []\n",
" for sol in make_soldiers(n_per_unit, seed):\n",
" for q in BANK:\n",
" raw, ok, intended, et = answer(q, sol, rng)\n",
" if rng.random() < blank:\n",
" raw, ok, intended, et = \"\", False, \"\", None\n",
" rows.append({\"rid\": f\"R{len(rows):05d}\", \"sid\": sol[\"sid\"], \"unit\": sol[\"unit\"], \"qid\": q[\"qid\"], \"raw\": raw,\n",
" \"truth_correct\": ok, \"truth_answer\": intended, \"truth_etype\": et})\n",
" return pd.DataFrame(rows)\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์๋ต ์ ์ ยทํ์ฑยท์ฑ์ ๊ณตํต ํจ์ ----\n",
"TAILS = (\"์
๋๋ค\", \"์ด๋ค\", \"์\")\n",
"\n",
"\n",
"def norm_text(s):\n",
" \"\"\"์ ๊ฐ->๋ฐ๊ฐ(NFKC), ์๋ฌธ์, ๊ณต๋ฐฑยท๋ฌธ์ฅ๋ถํธ ์ ๊ฑฐ, ๋๋งบ์ ์ด๋ฏธ ์ ๊ฑฐ.\"\"\"\n",
" s = unicodedata.normalize(\"NFKC\", str(s)).strip().lower()\n",
" s = re.sub(r\"[\\s\\.\\,\\-\\(\\)ยท]\", \"\", s)\n",
" for t in TAILS:\n",
" if s.endswith(t) and len(s) > len(t) + 1:\n",
" s = s[:-len(t)]\n",
" return s\n",
"\n",
"\n",
"def parse(q, raw):\n",
" \"\"\"์๋ณธ ์๋ต -> (์ ์ ๊ฐ, ์ํ). ์ํ: ์ ์ / ๋ฏธ์๋ต / ํ์์ค๋ฅ\"\"\"\n",
" if raw is None or str(raw).strip() == \"\":\n",
" return \"\", \"๋ฏธ์๋ต\"\n",
" s = unicodedata.normalize(\"NFKC\", str(raw)).strip()\n",
" if q[\"type\"] == \"MC\":\n",
" t = re.sub(r\"[\\s๋ฒ\\.\\)]\", \"\", s).lower()\n",
" t = {\"a\": \"1\", \"b\": \"2\", \"c\": \"3\", \"d\": \"4\"}.get(t, t)\n",
" return (t, \"์ ์\") if t in (\"1\", \"2\", \"3\", \"4\") else (t, \"ํ์์ค๋ฅ\")\n",
" if q[\"type\"] == \"ORDER\":\n",
" t = re.sub(r\"[^A-Za-z]\", \"\", s).upper()\n",
" return (t, \"์ ์\") if t and set(t) <= set(\"ABCD\") and len(set(t)) == len(t) else (t, \"ํ์์ค๋ฅ\")\n",
" return norm_text(s), \"์ ์\"\n",
"\n",
"\n",
"def grade_exact(q, val):\n",
" if q[\"type\"] == \"MC\":\n",
" return val == str(q[\"answer\"])\n",
" if q[\"type\"] == \"ORDER\":\n",
" return val == q[\"answer\"]\n",
" return val in {norm_text(a) for a in q[\"accept\"]}\n",
"\n",
"\n",
"def grade_fuzzy(q, val, cut=0.75):\n",
" \"\"\"๋จ๋ตํ์ ์คํ์(๊ธ์ ์์ ๋ฐ๋ยทํ ๊ธ์ ๋๋ฝ)๋ฅผ ํ์ฉ(์ ์ฌ๋ cut ์ด์)ํ๋, ์๋ ค์ง ์ค๋ต๊ณผ ๋ ๊ฐ๊น์ฐ๋ฉด ์ค๋ต์ผ๋ก ๋ณธ๋ค.\"\"\"\n",
" if grade_exact(q, val) or q[\"type\"] != \"SA\" or len(val) < 2:\n",
" return grade_exact(q, val)\n",
" best = max(1.0 if sorted(val) == sorted(norm_text(a)) else difflib.SequenceMatcher(None, val, norm_text(a)).ratio() for a in q[\"accept\"])\n",
" worst = max(difflib.SequenceMatcher(None, val, norm_text(w)).ratio() for w in q[\"wrong\"])\n",
" return best >= cut and best > worst\n",
"\n",
"\n",
"def score_of(q, correct):\n",
" return POINTS[q[\"diff\"]] if correct else 0\n",
"\n",
"DUP_WINDOW_S = {\"์ ์\": 5, \"์ง์ฐ\": 30} # ํต์ ์ํ๋ณ ์ฌ์ ์ก ํ๋จ ์๊ฐ(๋์ ์๊ณ๊ฐ)\n",
"\n",
"\n",
"def inject_noise(df, seed=1):\n",
" \"\"\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ์ฌ์ ์ก ์ค๋ณตยทํ์ ์ค๋ฅยท์ ์ถ ์๊ฐยทํต์ ์ํ๋ฅผ ๋ง๋ถ์ธ๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" df = df.copy()\n",
" df[\"comm\"] = np.where(df[\"unit\"] == \"๊ฐ์2์ค๋\", \"์ง์ฐ\", \"์ ์\")\n",
" df[\"ts\"] = np.arange(len(df)) * 3.0\n",
" bad = rng.choice(len(df), 20, replace=False)\n",
" df.loc[bad, \"raw\"] = rng.choice([\"7\", \"๋ชจ๋ฆ\", \"??\"], 20)\n",
" df.loc[bad, \"truth_correct\"] = False\n",
" dup = df.sample(frac=0.03, random_state=seed).copy()\n",
" dup[\"ts\"] = dup[\"ts\"] + np.where(dup[\"comm\"] == \"์ง์ฐ\", 20.0, 2.0)\n",
" dup[\"rid\"] = dup[\"rid\"] + \"D\"\n",
" return pd.concat([df, dup]).sort_values(\"ts\").reset_index(drop=True)\n",
"\n",
"\n",
"def preprocess(df):\n",
" \"\"\"์ค๋ณต ์ ๊ฑฐ -> ํ์ฑ -> ์ํ ๋ถ๋ฅ. ๋ฏธ์๋ต์ ์ญ์ ํ์ง ์๊ณ ์ํ๋ก ๋จ๊ธด๋ค(๊ฒฐ์ธก ๋ณด์ ).\"\"\"\n",
" df = df.sort_values(\"ts\")\n",
" win = df[\"comm\"].map(DUP_WINDOW_S)\n",
" prev_ts = df.groupby([\"sid\", \"qid\"])[\"ts\"].shift(1)\n",
" df = df.assign(dup=(df[\"ts\"] - prev_ts) <= win)\n",
" keep = df[~df.groupby([\"sid\", \"qid\"])[\"dup\"].shift(-1, fill_value=False)] # ์ฌ์ ์ก์ด๋ฉด ๋ง์ง๋ง ์ ์ถ๋ง ์ ์ง\n",
" pv = [parse(QIDX[q], r) for q, r in zip(keep[\"qid\"], keep[\"raw\"])]\n",
" return keep.assign(val=[p[0] for p in pv], status=[p[1] for p in pv]).drop(columns=\"dup\"), len(df) - len(keep)\n",
"\n",
"\n",
"data = inject_noise(make_responses(40, seed=7))\n",
"preprocess(data.head(100))\n",
"t0 = time.perf_counter()\n",
"clean, n_dup = preprocess(data)\n",
"dt = time.perf_counter() - t0\n",
"print(f\"์๋ต {len(data):,}๊ฑด ์ ์ฒ๋ฆฌ {dt * 1000:.0f} ms, {len(data) / dt:,.0f}๊ฑด/์ด (๋ชฉํ 1,000๊ฑด/์ด ์ด์: {'๋ฌ์ฑ' if len(data) / dt >= 1000 else '๋ฏธ๋ฌ'})\")\n",
"print(f\"์ฌ์ ์ก ์ค๋ณต ์ ๊ฑฐ {n_dup}๊ฑด, ๋จ์ ์๋ต {len(clean):,}๊ฑด(์์์ 120๋ช
x 32๋ฌธํญ = 3,840๊ฑด๊ณผ ์ผ์น: {len(clean) == 3840})\")\n",
"print(\"์ํ ๋ถํฌ:\", clean[\"status\"].value_counts().to_dict())\n",
"err = clean[(clean[\"status\"] == \"์ ์\") & clean.apply(lambda r: QIDX[r.qid][\"type\"] == \"MC\" and r.val not in \"1234\", axis=1)]\n",
"print(f\"์ ์ฒ๋ฆฌ ์ค๋ฅ(์ ์์ธ๋ฐ ํ์ฑ ์คํจ) {len(err)}๊ฑด, ์ค๋ฅ์จ {len(err) / len(clean):.1%}\")\n",
"\n",
"# ๋จ์ ํ
์คํธ 5์ข
\n",
"Q = QIDX\n",
"tests = {\n",
" \"T1 ์ ๊ฐ ์ซ์ '๏ผ' -> 3\": parse(Q[\"Q01\"], \"๏ผ\")[0] == \"3\",\n",
" \"T2 ์๋ฌธ์ยท์๋ฌธยท'๋ฒ' ํ๊ธฐ ํต์ผ\": [parse(Q[\"Q01\"], s)[0] for s in [\"โข\", \"c)\", \"3๋ฒ\", \" 3 \"]] == [\"3\"] * 4,\n",
" \"T3 ๋จ๋ต ๊ณต๋ฐฑยท์ด๋ฏธ ์ ๊ฑฐ\": parse(Q[\"Q24\"], \"์ง ํ ๊ด ์
๋๋ค\")[0] == \"์งํ๊ด\" and parse(Q[\"Q21\"], \"๏ผฉ๏ผฆ๏ผฆ\")[0] == \"iff\",\n",
" \"T4 ์์ํ ๊ตฌ๋ถ์ ์ ๊ฑฐ\": parse(Q[\"Q30\"], \"a-b-c-d\") == (\"ABCD\", \"์ ์\") and parse(Q[\"Q30\"], \"A,B,B,D\")[1] == \"ํ์์ค๋ฅ\",\n",
" \"T5 ๋ฏธ์๋ต ๋ณด์กด(๊ฒฐ์ธก ๋ณด์ ์จ 100%)\": (clean[\"status\"] == \"๋ฏธ์๋ต\").sum() == (data.drop_duplicates([\"sid\", \"qid\"])[\"raw\"] == \"\").sum(),\n",
"}\n",
"for k, v in tests.items():\n",
" print(f\"[{'PASS' if v else 'FAIL'}] {k}\")\n",
"print(f\"๋จ์ ํ
์คํธ {sum(tests.values())}/5 ํต๊ณผ\")\n",
"\n",
"print(\"\\n[์ ์ฒ๋ฆฌ ์ ํ ์์]\")\n",
"ex = clean[(clean[\"raw\"] != clean[\"val\"]) & clean[\"qid\"].isin([\"Q05\", \"Q13\", \"Q21\", \"Q22\", \"Q24\", \"Q25\", \"Q29\", \"Q31\"])].groupby(\"qid\").head(1)\n",
"print(ex[[\"qid\", \"raw\", \"val\", \"status\"]].rename(columns={\"raw\": \"์๋ณธ\", \"val\": \"์ ์ ๊ฐ\", \"status\": \"์ํ\"}).to_string(index=False))\n",
"print(\"ํต์ ์ง์ฐ ์ค๋๋ ์ฌ์ ์ก ํ๋จ ์๊ฐ์ 30์ด๋ก ๋ํ ์ค๋ณต ์ ์ถ์ ๋์น์ง ์๋๋ค.\")\n",
"\n",
"st = clean.groupby([\"unit\", \"status\"]).size().unstack(fill_value=0)\n",
"st.index = [\"Co.1\", \"Co.2 (delayed comm)\", \"Co.3\"]\n",
"st.columns = st.columns.map({\"์ ์\": \"valid\", \"๋ฏธ์๋ต\": \"blank\", \"ํ์์ค๋ฅ\": \"format error\"})\n",
"st.plot(kind=\"bar\", stacked=True, figsize=(7, 4))\n",
"plt.title(\"Response status after preprocessing (synthetic)\"); plt.ylabel(\"responses\"); plt.xticks(rotation=0)\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
02 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"์๋ต 3,955๊ฑด ์ ์ฒ๋ฆฌ 18 ms, 220,239๊ฑด/์ด (๋ชฉํ 1,000๊ฑด/์ด ์ด์: ๋ฌ์ฑ)\n",
"์ฌ์ ์ก ์ค๋ณต ์ ๊ฑฐ 115๊ฑด, ๋จ์ ์๋ต 3,840๊ฑด(์์์ 120๋ช
x 32๋ฌธํญ = 3,840๊ฑด๊ณผ ์ผ์น: True)\n",
"์ํ ๋ถํฌ: {'์ ์': 3753, '๋ฏธ์๋ต': 71, 'ํ์์ค๋ฅ': 16}\n",
"์ ์ฒ๋ฆฌ ์ค๋ฅ(์ ์์ธ๋ฐ ํ์ฑ ์คํจ) 0๊ฑด, ์ค๋ฅ์จ 0.0%\n",
"[PASS] T1 ์ ๊ฐ ์ซ์ '๏ผ' -> 3\n",
"[PASS] T2 ์๋ฌธ์ยท์๋ฌธยท'๋ฒ' ํ๊ธฐ ํต์ผ\n",
"[PASS] T3 ๋จ๋ต ๊ณต๋ฐฑยท์ด๋ฏธ ์ ๊ฑฐ\n",
"[PASS] T4 ์์ํ ๊ตฌ๋ถ์ ์ ๊ฑฐ\n",
"[PASS] T5 ๋ฏธ์๋ต ๋ณด์กด(๊ฒฐ์ธก ๋ณด์ ์จ 100%)\n",
"๋จ์ ํ
์คํธ 5/5 ํต๊ณผ\n",
"\n",
"[์ ์ฒ๋ฆฌ ์ ํ ์์]\n",
"qid ์๋ณธ ์ ์ ๊ฐ ์ํ\n",
"Q05 b) 2 ์ ์\n",
"Q13 3 3 ์ ์\n",
"Q21 ํผ์์๋ณ์ ํผ์์๋ณ ์ ์\n",
"Q25 ์๋ชป๋ ๊ฒฝ๋ณด ์๋ชป๋๊ฒฝ๋ณด ์ ์\n",
"Q31 A C D ACD ์ ์\n",
"Q22 de lta delta ์ ์\n",
"Q24 ์ง ํ ๊ด ์งํ๊ด ์ ์\n",
"Q29 A,B,C,D ABCD ์ ์\n",
"ํต์ ์ง์ฐ ์ค๋๋ ์ฌ์ ์ก ํ๋จ ์๊ฐ์ 30์ด๋ก ๋ํ ์ค๋ณต ์ ์ถ์ ๋์น์ง ์๋๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
03. ์ ๋คํ ๋ฐ ๋จ๋ตํ ๋ฐฉ๊ณต ํด์ฆ ์๋ ์ฑ์ Baseline ๋ก์ง ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์ ๋คํ ๋ฐ ๋จ๋ตํ ๋ฐฉ๊ณต ํด์ฆ ์๋ ์ฑ์ Baseline ๋ก์ง์ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ ๋คํยท๋จ๋ตํยท์์ํ ํด์ฆ๋ฅผ ์๋ ์ฑ์ ํ๋ Baseline์ ๊ตฌํํ๊ณ ๊ต๊ด ๊ธฐ์ค ํ์ ๊ณผ ๋น๊ตํด ์ ํ๋๋ฅผ ํ์ธํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ ํ ์ผ์น ์ฑ์ ๊ณผ ์คํ์(๊ธ์ ์์ ๋ฐ๋ยทํ ๊ธ์ ๋๋ฝ)๋ฅผ ํ์ฉํ๋ ์ ์ฌ๋ ์ฑ์ ์ ๊ตฌํํ๊ณ , ์ ์ฌ๋ ๊ธฐ์ค๊ฐ์ ๋ฐ๊ฟ ๊ฐ๋ฉฐ ๋จ๋ตํ ์ฑ์ ์ ํ๋๋ฅผ ๋น๊ต ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 5๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/trn02_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": [
"\"\"\"์ ๋คํยท๋จ๋ตํยท์์ํ ๋ฐฉ๊ณต ํด์ฆ๋ฅผ ์๋ ์ฑ์ ํ๋ Baseline์ ๊ตฌํํ๋ค. ์ ํ ์ผ์น ์ฑ์ ๊ณผ ์คํ์ ํ์ฉ ์ฑ์ ์ ์ ํ๋(๊ต๊ด ๊ธฐ์ค ๋๋น)ยท์ฒ๋ฆฌ ์ง์ฐ์ ๋น๊ตํ๊ณ , ์์์๋ณ ์ ์์ ํฉ๊ฒฉ ์ฌ๋ถ, ์คํ ๋ก๊ทธ๋ฅผ ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import difflib\n",
"import re\n",
"import time\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฐฉ๊ณตยท๋๋ก ์๋ณ ํด์ฆ ๋ฌธํญ ์ํ๊ณผ ๊ฐ์ ์์์ ์๋ต ์์ฑ๊ธฐ\n",
"# ๋ฌธํญ์ ์ผ๋ฐ ์์น ์์ค์ ๊ฐ์ ๋ฌธํญ์ด๋ฉฐ ์ค์ ๊ต๋ฒยท๊ต์ ๊ท์น ์๋ฌธ์ด ์๋๋ค. ์์์๋ ๊ฐ๋ช
(S001~)์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"AREAS = [\"ํ์์๋ณ\", \"ํผ์์๋ณ\", \"๊ฒฝ๋ณด๋ณด๊ณ \", \"๊ต์ ํต์ \", \"๋๋๋ก ์ผ๋ฐ\"]\n",
"ETYPES = [\"๊ฐ๋
ํผ๋\", \"ํผ์ ์ค์ธ\", \"์ ์ฐจ ๋๋ฝ\"]\n",
"POINTS = {1: 2, 2: 3, 3: 5}\n",
"C, F, P = ETYPES\n",
"\n",
"\n",
"def _mc(qid, area, diff, stem, opts, ans, dist):\n",
" return {\"qid\": qid, \"type\": \"MC\", \"area\": area, \"diff\": diff, \"stem\": stem, \"options\": opts, \"answer\": ans,\n",
" \"dist\": dict(zip([i for i in range(1, 5) if i != ans], dist))}\n",
"\n",
"\n",
"def _sa(qid, area, diff, stem, accept, wrong):\n",
" return {\"qid\": qid, \"type\": \"SA\", \"area\": area, \"diff\": diff, \"stem\": stem, \"accept\": accept, \"wrong\": wrong}\n",
"\n",
"\n",
"def _order(qid, area, diff, stem):\n",
" return {\"qid\": qid, \"type\": \"ORDER\", \"area\": area, \"diff\": diff, \"stem\": stem, \"answer\": \"ABCD\"}\n",
"\n",
"\n",
"BANK = [\n",
" _mc(\"Q01\", \"ํ์์๋ณ\", 1, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์์ด ์ผ๊ฐํ ๋ ๊ฐ๋ฅผ ๊ฐ์ง ์ํ ๊ณ ์ ์ต ์ค๋ฃจ์ฃ์?\", [\"์ฟผ๋์ฝฅํฐํ\", \"์ผ๊ฐ์ต ๊ณ ์ ์ตํ\", \"ํ์ ์ต ํฌ๊ธฐํ\", \"์กฐ๋ฅ\"], 2, [C, C, F]),\n",
" _mc(\"Q02\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๊ฐ ์ญ์๋ก ๋ฐฐ์น๋ ์ค๋ฃจ์ฃ์?\", [\"๊ณ ์ ์ต ๋๋ก ํ\", \"์ฟผ๋์ฝฅํฐํ\", \"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\"], 2, [C, C, C]),\n",
" _mc(\"Q03\", \"ํ์์๋ณ\", 2, \"๊ฐ๋๊ณ ๊ธด ๋์ฒด ์ค๊ฐ์ ์์ ๋ ๊ฐ๊ฐ ๋ถ์ ์ค๋ฃจ์ฃ์?\", [\"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\", \"๋ฏผ๊ฐ ๊ฒฝ๋นํ๊ธฐํ\", \"์กฐ๋ฅ\"], 1, [C, F, F]),\n",
" _mc(\"Q04\", \"ํ์์๋ณ\", 3, \"์๊ฑฐ๋ฆฌ๋ผ ์ค๋ฃจ์ฃ ์๋ณ์ด ์ด๋ ค์ธ ๋ ๋จผ์ ํ ํ๋์?\", [\"์ถ์ ์ ํ์ผ๋ก ํ์ ๋ณด๊ณ \", \"๊ด์ธก ์ ๋ณด๋ฅผ ๊ทธ๋๋ก ๋ณด๊ณ ํ๊ณ ์ถ๊ฐ ๊ด์ธก\", \"๊ด์ธก ์ค๋จ\", \"๋ฏผ๊ฐ๊ธฐ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _mc(\"Q05\", \"ํผ์์๋ณ\", 2, \"์๋ณ ์ ํธ ์๋ต์ด ์๋ ํญ๊ณต๊ธฐ์ ๋ํ ์ฌ๋ฐ๋ฅธ ํ๋จ์?\", [\"์ฆ์ ์ ๊ธฐ๋ก ํ์ \", \"๋ฏธํ์ธ ํญ๊ณต๊ธฐ๋ก ๋ถ๋ฅํ๊ณ ์ถ๊ฐ ํ์ธ\", \"์๊ตฐ๊ธฐ๋ก ๊ฐ์ฃผ\", \"๋ฌด์\"], 2, [F, F, P]),\n",
" _mc(\"Q06\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด์ ์ผ์นํ๋ ์ ์ ๋นํ์ฒด์ ๋ถ๋ฅ๋?\", [\"์ ๋๋ก \", \"๋ฏผ๊ฐ๊ธฐ ๊ฐ๋ฅ์ฑ ๋์, ์ถ๊ฐ ํ์ธ\", \"์ํญ๋ฏธ์ฌ์ผ\", \"์๊ตฐ ์ ์ฐฐ๊ธฐ๋ก ํ์ \"], 2, [F, C, F]),\n",
" _mc(\"Q07\", \"ํผ์์๋ณ\", 3, \"์๊ตฐ ๋๋ก ์ด์ฉ ๊ณต์ง๊ฐ ์๋ ๊ณต์ญ์์ ๋ฏธํ์ธ ๋๋ก ์ ๋ณด๋ฉด ์ฐ์ ํ์ธํ ๊ฒ์?\", [\"๊ธฐ์ ์ ๋ณด\", \"์๊ตฐ ์ด์ฉ ๊ณํ๊ณผ ์๋ณ ์ ํธ\", \"๋์ ์ค๋น ์ํ\", \"์ฃผ๋ณ ์งํ\"], 2, [C, P, C]),\n",
" _mc(\"Q08\", \"ํผ์์๋ณ\", 3, \"์กฐ๋ฅ ๋ฌด๋ฆฌ์ ์ํ ๋๋ก ๊ตฐ์ง ๊ตฌ๋ถ์ ๊ฐ์ฅ ๋์์ด ๋๋ ์ ๋ณด๋?\", [\"ํฌ๊ธฐ๋ง ๋น๊ต\", \"๊ถค์ ์ ๊ท์น์ฑ๊ณผ ์ด ์ ํธ\", \"์์\", \"๊ด์ธก ์๊ฐ\"], 2, [C, C, C]),\n",
" _mc(\"Q09\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๊ณต์ค ์ํ ์ต์ด ๋ณด๊ณ ์ ํฌํจํ์ง ์์๋ ๋๋ ๊ฒ์?\", [\"๊ด์ธก ์๊ฐ\", \"๋ฐฉํฅ๊ณผ ๊ฑฐ๋ฆฌ\", \"์ถ์ ์ ํ\", \"๊ด์ธก์ ๊ฐ์ธ ์ฐ๋ฝ์ฒ\"], 4, [P, P, P]),\n",
" _mc(\"Q10\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๊ฒฝ๋ณด ์ ํ ํ ์ํฉ์ด ๋ฐ๋๋ฉด?\", [\"์ต์ด ๋ณด๊ณ ์ ์ง\", \"๋ณ๊ฒฝ ์ฌํญ์ ์ฆ์ ์ถ๊ฐ ๋ณด๊ณ \", \"์ํฉ ์ข
๋ฃ ํ ์ผ๊ด ๋ณด๊ณ \", \"๊ฐ์ธ ํ๋จ์ผ๋ก ๊ฒฝ๋ณด ํด์ \"], 2, [P, P, P]),\n",
" _mc(\"Q11\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ณด๊ณ ๋ด์ฉ์ด ๋ถํ์คํ ๋ ์ฌ๋ฐ๋ฅธ ํํ์?\", [\"ํ์ ํํ์ผ๋ก ๋ณด๊ณ \", \"'์ถ์ '์์ ๋ฐํ๊ณ ๊ทผ๊ฑฐ์ ํจ๊ป ๋ณด๊ณ \", \"๋ณด๊ณ ๋ณด๋ฅ\", \"๋ค๋ฅธ ๊ด์ธก์ ๋ณด๊ณ ๋ฅผ ๋ณต์ฌ\"], 2, [C, P, P]),\n",
" _mc(\"Q12\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋๋ก ํ์ง ํ ๋ณด๊ณ ๊ฒฝ๋ก๋ก ์๋ง์ ๊ฒ์?\", [\"์ธ์ ๋ถ๋์๋ง ์ ํ\", \"์งํ๊ณํต์ ๋ฐ๋ผ ๋ณด๊ณ \", \"๊ฐ์ธ ๋ฉ์ ์ ๋ก ๊ณต์ \", \"๋ณด๊ณ ์์ด ๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q13\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ๋ง์ผ๋ก ๋์์ ๊ฒฐ์ ํ ์ ์๋๊ฐ?\", [\"๊ฐ๋ฅํ๋ค\", \"์ธ๊ฐ ์งํ๊ด์ ์น์ธ์ด ํ์ํ๋ค\", \"์ ๋ขฐ๋ 90% ์ด์์ด๋ฉด ๊ฐ๋ฅ\", \"์ผ๊ฐ์๋ง ๊ฐ๋ฅ\"], 2, [C, C, C]),\n",
" _mc(\"Q14\", \"๊ต์ ํต์ \", 2, \"๋ฏผ๊ฐ ํผํด ๊ฐ๋ฅ์ฑ์ด ํ์ธ๋๋ฉด?\", [\"๊ทธ๋๋ก ์งํ\", \"์ฆ์ ๋ณด๊ณ ํ๊ณ ์งํ๊ด ํ๋จ์ ๋ฐ๋๋ค\", \"์กฐ์น ํ ๋ณด๊ณ \", \"๋ฌด์\"], 2, [P, P, F]),\n",
" _mc(\"Q15\", \"๊ต์ ํต์ \", 3, \"์๋ณ์ด ๋ถํ์คํ ํ์ ์ ๋ํ ์์น์?\", [\"์ํ์ผ๋ก ๊ฐ์ฃผํด ์ฆ์ ๋์\", \"์๋ณ ํ์ธ ์ ๊น์ง ์น์ธ ์ ์ฐจ๋ฅผ ๋ฐ๋ฅธ๋ค\", \"๋ฌด์กฐ๊ฑด ๋ฌด์\", \"๋ค์๊ฒฐ๋ก ๊ฒฐ์ \"], 2, [F, F, C]),\n",
" _mc(\"Q16\", \"๊ต์ ํต์ \", 2, \"ํ๋จ ๊ทผ๊ฑฐ๋ฅผ ๊ธฐ๋กํด์ผ ํ๋ ์ด์ ๋?\", [\"ํ์์ ํ์\", \"์ฌํ ๊ฒํ ์ ์ฑ
์ ์ถ์ \", \"๋ณด๊ณ ์๋ต\", \"๊ธฐ๋ก ๋ถํ์\"], 2, [C, P, P]),\n",
" _mc(\"Q17\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"๋๋๋ก ์ฒด๊ณ์ ์ผ๋ฐ์ ์ฒ๋ฆฌ ์์๋?\", [\"๋์-ํ์ง-์๋ณ\", \"ํ์ง-์๋ณ-๋ณด๊ณ ยทํ๋จ-๋์\", \"์๋ณ-๋์-ํ์ง\", \"๋ณด๊ณ -ํ์ง-๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q18\", \"๋๋๋ก ์ผ๋ฐ\", 3, \"๋ ์ด๋๋ง์ผ๋ก ์กฐ๋ฅ์ ๋๋ก ๊ตฌ๋ถ์ด ์ด๋ ค์ด ์ด์ ๋?\", [\"๋ฐ์ฌ ์ ํธ ํฌ๊ธฐ๊ฐ ๋น์ทํ ์ ์์ด์\", \"๋ ์ด๋๋ ์ผ๊ฐ์ ๋ชป ์จ์\", \"๋๋ก ์๋ ๊ธ์์ด ์์ด์\", \"์กฐ๋ฅ๋ ํ์ง๋์ง ์์์\"], 1, [C, C, C]),\n",
" _mc(\"Q19\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ ์๊ดํ(EO) ์ผ์์ ์ฝ์ ์ด ์ปค์ง๋ ์กฐ๊ฑด์?\", [\"๋ง์ ์ฃผ๊ฐ\", \"์๊ฐยท์ผ๊ฐ\", \"์ ์ ํ์ \", \"๊ทผ๊ฑฐ๋ฆฌ\"], 2, [C, C, C]),\n",
" _mc(\"Q20\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"๊ฒฝ๋ณด๊ฐ ๋ฐ๋ณตํด์ ์ค๊ฒฝ๋ณด๋ก ํ๋ช
๋๋ฉด?\", [\"๊ฒฝ๋ณด ์ฅ์น๋ฅผ ๋๋ค\", \"์ฌ๋ก๋ฅผ ๊ธฐ๋กยท๋ณด๊ณ ํ๊ณ ๊ธฐ์ค ์ฌ๊ฒํ ์์ฒญ\", \"๋ฌด์ํ๋ค\", \"๋ชจ๋ ์ค์ ์ํ์ผ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _sa(\"Q21\", \"ํผ์์๋ณ\", 1, \"์๊ตฐยท์ ยท๋ฏผ๊ฐ์ ๊ตฌ๋ถํ๋ ์ ์ฐจ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"ํผ์์๋ณ\", \"IFF\"], {\"ํ์ ํ๋\": C, \"์ ๊ธฐํ์ \": F, \"๊ฒฝ๊ณ\": C}),\n",
" _sa(\"Q22\", \"ํ์์๋ณ\", 2, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์๋ ์ผ๊ฐํ ๋ ๊ฐ ํํ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ผ๊ฐ์ต\", \"๋ธํ์ต\", \"delta\"], {\"์ง์ ์ต\": C, \"ํํด์ต\": C, \"์ฟผ๋์ฝฅํฐ\": C}),\n",
" _sa(\"Q23\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ถํ์คํ ๊ด์ธก์ ๋ณด๊ณ ํ ๋ ๋ถ์ด๋ ํํ์?\", [\"์ถ์ \"], {\"ํ์ \": C, \"ํ์ธ\": C, \"์๋ต\": P}),\n",
" _sa(\"Q24\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ์ ๋ํด ์ต์ข
๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ์ฃผ์ฒด๋?\", [\"์งํ๊ด\"], {\"AI\": C, \"์ด์ฉ๋ณ\": P, \"๋ ์ด๋\": C}),\n",
" _sa(\"Q25\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"์กฐ๋ฅ๋ฅผ ๋๋ก ์ผ๋ก ์๋ชป ๊ฒฝ๋ณดํ๋ ๊ฒ์ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ค๊ฒฝ๋ณด\", \"์คํ\", \"false alarm\"], {\"๋ฏธํ\": C, \"์ฌ๊ฒฝ๋ณด\": C, \"์ ๊ธฐ\": F}),\n",
" _sa(\"Q26\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด๊ฐ ํ์ธ๋ ์ฌ๊ฐ๊ธฐ๋ ์ด๋ป๊ฒ ๋ถ๋ฅํ๋๊ฐ?\", [\"๋ฏผ๊ฐ๊ธฐ\", \"๋ฏผํญ๊ธฐ\"], {\"์ ๊ธฐ\": F, \"๋ฏธํ์ธ๊ธฐ\": P, \"์๊ตฐ๊ธฐ\": F}),\n",
" _sa(\"Q27\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๋ฅผ ๊ฐ์ง ๋๋ก ํํ๋?\", [\"์ฟผ๋์ฝฅํฐ\", \"quadcopter\"], {\"ํฅ์ฌ์ฝฅํฐ\": C, \"๊ณ ์ ์ต\": C, \"ํฌ๊ธฐ\": F}),\n",
" _sa(\"Q28\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋ณด๊ณ ๋ ๋ฌด์์ ๋ฐ๋ผ ์ฌ๋ผ๊ฐ๋๊ฐ?\", [\"์งํ๊ณํต\"], {\"์ธ์ ๋ถ๋\": P, \"๊ฐ์ธ๋ฉ์ ์ \": P, \"ํต์ ๋ง\": C}),\n",
" _order(\"Q29\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"์ต์ด ๋ณด๊ณ ์์: A ๊ด์ธก ์๊ฐ, B ๋ฐฉํฅยท๊ฑฐ๋ฆฌ, C ์ถ์ ์ ํ, D ๊ด์ธก์ ์กฐ์น\"),\n",
" _order(\"Q30\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ฒ๋ฆฌ ์์: A ํ์ง, B ์๋ณ, C ๋ณด๊ณ ยทํ๋จ, D ๋์\"),\n",
" _order(\"Q31\", \"๊ต์ ํต์ \", 3, \"ํต์ ์์: A ์๋ณ ํ์ธ, B ์๊ธ ์น์ธ ์์ฒญ, C ์น์ธ ์๋ น, D ์กฐ์น ๊ธฐ๋ก\"),\n",
" _order(\"Q32\", \"ํผ์์๋ณ\", 3, \"ํ์ธ ์์: A ์ดํญ์ ๋ณด ๋์กฐ, B ์๋ณ ์ ํธ ํ์ธ, C ๋ฏธํ์ธ ๋ถ๋ฅ, D ๋ณด๊ณ \"),\n",
"]\n",
"QIDX = {q[\"qid\"]: q for q in BANK}\n",
"UNITS = [\"๊ฐ์1์ค๋\", \"๊ฐ์2์ค๋\", \"๊ฐ์3์ค๋\"]\n",
"_CIRC = \"โ โกโขโฃ\"\n",
"PARA = {\"Q21\": \"์๊ตฐ์ ๊ตฐ ๊ตฌ๋ถ\", \"Q22\": \"์ธ๋ชจ ๋ ๊ฐ\", \"Q23\": \"์ถ์ ๋จ์ผ๋ก ํ๊ธฐ\", \"Q24\": \"์ค๋์ฅ(์งํ๊ด)\", \"Q25\": \"์๋ชป๋ ๊ฒฝ๋ณด\",\n",
" \"Q26\": \"๋ฏผ๊ฐ ์ฌ๊ฐ๊ธฐ\", \"Q27\": \"4๋ฐ ๋๋ก \", \"Q28\": \"์งํ ๋ผ์ธ\"} # ๋ป์ ๋ง์ง๋ง ์ธ์ ๋ต์ ๋ชฉ๋ก์ ์๋ ํํ\n",
"\n",
"\n",
"def make_soldiers(n_per_unit, seed):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for u, unit in enumerate(UNITS):\n",
" base = [0.3, -0.1, 0.0][u]\n",
" for i in range(n_per_unit):\n",
" off = rng.normal(0, 0.5, len(AREAS)) + (np.array([0, 0, 0, -0.6, 0]) if u == 1 else 0)\n",
" out.append({\"sid\": f\"S{len(out) + 1:03d}\", \"unit\": unit, \"theta\": dict(zip(AREAS, base + rng.normal(0, 0.6) + off)),\n",
" \"tend\": dict(zip(ETYPES, rng.dirichlet([1.2, 1.2, 1.2])))})\n",
" return out\n",
"\n",
"\n",
"def p_correct(theta, diff):\n",
" return 1 / (1 + np.exp(-1.7 * (theta - (diff - 2) * 0.9 + 0.9)))\n",
"\n",
"\n",
"def _typo(s, rng):\n",
" if len(s) < 2:\n",
" return s\n",
" i = int(rng.integers(len(s) - 1))\n",
" return s[:i] + s[i + 1] + s[i] + s[i + 2:] if len(s) <= 3 or rng.random() < 0.5 else s[:i] + s[i + 1:]\n",
"\n",
"\n",
"def _fmt_mc(k, rng):\n",
" r = rng.random()\n",
" if r < 0.55:\n",
" return str(k)\n",
" return rng.choice([_CIRC[k - 1], f\"{k}๋ฒ\", \"ABCD\"[k - 1], \"abcd\"[k - 1] + \")\", f\" {k} \", chr(0xFF10 + k), f\"{k}.\"])\n",
"\n",
"\n",
"def _fmt_sa(s, rng):\n",
" r = rng.random()\n",
" if r < 0.45:\n",
" return s\n",
" if r < 0.6:\n",
" return \" \".join(s) if len(s) <= 4 else s[:2] + \" \" + s[2:]\n",
" if r < 0.72:\n",
" return s + rng.choice([\"์
๋๋ค\", \"์ด๋ค\", \".\", \"์\"])\n",
" if r < 0.8:\n",
" return s.upper() if s.isascii() else \"\".join(chr(ord(ch) + 0xFEE0) if \"!\" <= ch <= \"~\" else ch for ch in s) + \" \"\n",
" return _typo(s, rng)\n",
"\n",
"\n",
"def answer(q, sol, rng):\n",
" \"\"\"ํ ๋ฌธํญ ์๋ต: (์๋ณธ ์๋ต ๋ฌธ์์ด, ์๋ํ ์ ๋ต ์ฌ๋ถ, ์๋ํ ๋ต, ์ค๋ต ์ ํ).\"\"\"\n",
" ok = rng.random() < p_correct(sol[\"theta\"][q[\"area\"]], q[\"diff\"])\n",
" if q[\"type\"] == \"MC\":\n",
" if ok:\n",
" k, et = q[\"answer\"], None\n",
" else:\n",
" ks = list(q[\"dist\"]); w = np.array([sol[\"tend\"][q[\"dist\"][k]] for k in ks])\n",
" k = int(rng.choice(ks, p=w / w.sum())); et = q[\"dist\"][k]\n",
" return _fmt_mc(k, rng), ok, str(k), et\n",
" if q[\"type\"] == \"SA\":\n",
" if ok and rng.random() < 0.06:\n",
" return PARA[q[\"qid\"]], ok, PARA[q[\"qid\"]], None\n",
" if ok:\n",
" s, et = str(rng.choice(q[\"accept\"])), None\n",
" else:\n",
" ws = list(q[\"wrong\"]); w = np.array([sol[\"tend\"][q[\"wrong\"][x]] for x in ws])\n",
" s = str(rng.choice(ws, p=w / w.sum())); et = q[\"wrong\"][s]\n",
" return _fmt_sa(s, rng), ok, s, et\n",
" if ok:\n",
" s = \"ABCD\"\n",
" elif rng.random() < 0.5:\n",
" s = \"ABCD\".replace(\"ABCD\"[rng.integers(1, 4)], \"\") # ๋จ๊ณ ํ๋ ๋น ๋จ๋ฆผ\n",
" else:\n",
" i = int(rng.integers(0, 3)); s = list(\"ABCD\"); s[i], s[i + 1] = s[i + 1], s[i]; s = \"\".join(s) # ์์ ๋ค๋ฐ๋\n",
" raw = s if rng.random() < 0.6 else rng.choice([\"-\".join(s), \" \".join(s), s.lower(), \",\".join(s)])\n",
" return raw, ok, s, (None if ok else P)\n",
"\n",
"\n",
"def make_responses(n_per_unit=40, seed=7, blank=0.02):\n",
" \"\"\"์์์ x ๋ฌธํญ ์๋ตํ(DataFrame)๋ฅผ ๋ง๋ ๋ค. truth_* ์ด์ ๊ต๊ด ๊ธฐ์ค ์ ๋ต(ํ๊ฐ์ฉ)์ด๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows = []\n",
" for sol in make_soldiers(n_per_unit, seed):\n",
" for q in BANK:\n",
" raw, ok, intended, et = answer(q, sol, rng)\n",
" if rng.random() < blank:\n",
" raw, ok, intended, et = \"\", False, \"\", None\n",
" rows.append({\"rid\": f\"R{len(rows):05d}\", \"sid\": sol[\"sid\"], \"unit\": sol[\"unit\"], \"qid\": q[\"qid\"], \"raw\": raw,\n",
" \"truth_correct\": ok, \"truth_answer\": intended, \"truth_etype\": et})\n",
" return pd.DataFrame(rows)\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์๋ต ์ ์ ยทํ์ฑยท์ฑ์ ๊ณตํต ํจ์ ----\n",
"TAILS = (\"์
๋๋ค\", \"์ด๋ค\", \"์\")\n",
"\n",
"\n",
"def norm_text(s):\n",
" \"\"\"์ ๊ฐ->๋ฐ๊ฐ(NFKC), ์๋ฌธ์, ๊ณต๋ฐฑยท๋ฌธ์ฅ๋ถํธ ์ ๊ฑฐ, ๋๋งบ์ ์ด๋ฏธ ์ ๊ฑฐ.\"\"\"\n",
" s = unicodedata.normalize(\"NFKC\", str(s)).strip().lower()\n",
" s = re.sub(r\"[\\s\\.\\,\\-\\(\\)ยท]\", \"\", s)\n",
" for t in TAILS:\n",
" if s.endswith(t) and len(s) > len(t) + 1:\n",
" s = s[:-len(t)]\n",
" return s\n",
"\n",
"\n",
"def parse(q, raw):\n",
" \"\"\"์๋ณธ ์๋ต -> (์ ์ ๊ฐ, ์ํ). ์ํ: ์ ์ / ๋ฏธ์๋ต / ํ์์ค๋ฅ\"\"\"\n",
" if raw is None or str(raw).strip() == \"\":\n",
" return \"\", \"๋ฏธ์๋ต\"\n",
" s = unicodedata.normalize(\"NFKC\", str(raw)).strip()\n",
" if q[\"type\"] == \"MC\":\n",
" t = re.sub(r\"[\\s๋ฒ\\.\\)]\", \"\", s).lower()\n",
" t = {\"a\": \"1\", \"b\": \"2\", \"c\": \"3\", \"d\": \"4\"}.get(t, t)\n",
" return (t, \"์ ์\") if t in (\"1\", \"2\", \"3\", \"4\") else (t, \"ํ์์ค๋ฅ\")\n",
" if q[\"type\"] == \"ORDER\":\n",
" t = re.sub(r\"[^A-Za-z]\", \"\", s).upper()\n",
" return (t, \"์ ์\") if t and set(t) <= set(\"ABCD\") and len(set(t)) == len(t) else (t, \"ํ์์ค๋ฅ\")\n",
" return norm_text(s), \"์ ์\"\n",
"\n",
"\n",
"def grade_exact(q, val):\n",
" if q[\"type\"] == \"MC\":\n",
" return val == str(q[\"answer\"])\n",
" if q[\"type\"] == \"ORDER\":\n",
" return val == q[\"answer\"]\n",
" return val in {norm_text(a) for a in q[\"accept\"]}\n",
"\n",
"\n",
"def grade_fuzzy(q, val, cut=0.75):\n",
" \"\"\"๋จ๋ตํ์ ์คํ์(๊ธ์ ์์ ๋ฐ๋ยทํ ๊ธ์ ๋๋ฝ)๋ฅผ ํ์ฉ(์ ์ฌ๋ cut ์ด์)ํ๋, ์๋ ค์ง ์ค๋ต๊ณผ ๋ ๊ฐ๊น์ฐ๋ฉด ์ค๋ต์ผ๋ก ๋ณธ๋ค.\"\"\"\n",
" if grade_exact(q, val) or q[\"type\"] != \"SA\" or len(val) < 2:\n",
" return grade_exact(q, val)\n",
" best = max(1.0 if sorted(val) == sorted(norm_text(a)) else difflib.SequenceMatcher(None, val, norm_text(a)).ratio() for a in q[\"accept\"])\n",
" worst = max(difflib.SequenceMatcher(None, val, norm_text(w)).ratio() for w in q[\"wrong\"])\n",
" return best >= cut and best > worst\n",
"\n",
"\n",
"def score_of(q, correct):\n",
" return POINTS[q[\"diff\"]] if correct else 0\n",
"\n",
"PASS_RATE = 0.7\n",
"df = make_responses(40, seed=7)\n",
"df[\"type\"] = df[\"qid\"].map(lambda q: QIDX[q][\"type\"])\n",
"pv = [parse(QIDX[q], r) for q, r in zip(df[\"qid\"], df[\"raw\"])]\n",
"df[\"val\"] = [p[0] for p in pv]\n",
"\n",
"t0 = time.perf_counter()\n",
"df[\"exact\"] = [grade_exact(QIDX[q], v) for q, v in zip(df[\"qid\"], df[\"val\"])]\n",
"t1 = time.perf_counter()\n",
"df[\"fuzzy\"] = [grade_fuzzy(QIDX[q], v) for q, v in zip(df[\"qid\"], df[\"val\"])]\n",
"t2 = time.perf_counter()\n",
"\n",
"rows = []\n",
"for name, col, t in [(\"์ ํ ์ผ์น\", \"exact\", t1 - t0), (\"์คํ์ ํ์ฉ\", \"fuzzy\", t2 - t1)]:\n",
" rows.append({\"์ฑ์ ๋ฐฉ์\": name, \"์ฑ์ ์ ํ๋\": (df[col] == df[\"truth_correct\"]).mean(),\n",
" **{f\"{k} ์ ํ๋\": (g[col] == g[\"truth_correct\"]).mean() for k, g in df.groupby(\"type\")},\n",
" \"๊ฑด๋น ms\": t / len(df) * 1000})\n",
"res = pd.DataFrame(rows)\n",
"print(f\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ์์์ 120๋ช
x 32๋ฌธํญ = {len(df):,}๊ฑด ์ฑ์ (๊ต๊ด ๊ธฐ์ค ์ ๋ต๊ณผ ๋น๊ต)\")\n",
"print(res.round(4).to_string(index=False))\n",
"acc = res.loc[1, \"์ฑ์ ์ ํ๋\"]\n",
"print(f\"\\n๋ชฉํ(์ฑ์ ์ ํ๋ 75% ์ด์): {'๋ฌ์ฑ' if acc >= 0.75 else '๋ฏธ๋ฌ'} ({acc:.1%}), ๊ฑด๋น ์ฒ๋ฆฌ {res.loc[1, '๊ฑด๋น ms']:.3f} ms\")\n",
"\n",
"print(\"\\n[์ ํ ์ผ์น ์ฑ์ ์ด ํ๋ฆฐ ์ฌ๋ก(์คํ์ยท๋์ด์ฐ๊ธฐ)]\")\n",
"miss = df[(df[\"exact\"] != df[\"truth_correct\"])].groupby(\"qid\").head(1).head(5)\n",
"print(miss[[\"qid\", \"raw\", \"val\", \"truth_correct\", \"exact\", \"fuzzy\"]].rename(columns={\"truth_correct\": \"๊ต๊ด ํ์ \", \"exact\": \"์ ํ์ผ์น\", \"fuzzy\": \"์คํ์ํ์ฉ\"}).to_string(index=False))\n",
"\n",
"print(\"์คํ์ ํ์ฉ ์ฑ์ ๋ ๋์น๋ '๋ป์ ๋ง์ง๋ง ์ธ์ ๋ต์ ๋ชฉ๋ก์ ์๋ ํํ'์ ์ด์ ์ ๊ธฐยท๊ต๊ด ๊ฒํ (๋ฏธ์
7) ๋์์ผ๋ก ๋๊ธด๋ค.\")\n",
"print(\"\\n[ํด๋ฆฌ์คํฑ 1์ฐจ ํ๋: ์ ์ฌ๋ ๊ธฐ์ค๊ฐ]\")\n",
"sa = df[df[\"type\"] == \"SA\"]\n",
"for cut in [0.6, 0.7, 0.75, 0.8, 0.9]:\n",
" a = np.mean([grade_fuzzy(QIDX[q], v, cut) == t for q, v, t in zip(sa[\"qid\"], sa[\"val\"], sa[\"truth_correct\"])])\n",
" print(f\"๊ธฐ์ค {cut}: ๋จ๋ตํ ์ฑ์ ์ ํ๋ {a:.3f}\")\n",
"\n",
"df[\"score\"] = [score_of(QIDX[q], c) for q, c in zip(df[\"qid\"], df[\"fuzzy\"])]\n",
"full = sum(POINTS[q[\"diff\"]] for q in BANK)\n",
"sc = df.groupby([\"sid\", \"unit\"])[\"score\"].sum().reset_index()\n",
"sc[\"ํฉ๊ฒฉ\"] = sc[\"score\"] >= full * PASS_RATE\n",
"print(f\"\\n๋ง์ {full}์ , ํ๊ท {sc['score'].mean():.1f}์ , ํฉ๊ฒฉ๋ฅ {sc['ํฉ๊ฒฉ'].mean():.1%}\")\n",
"print(sc.groupby(\"unit\").agg(ํ๊ท ์ ์=(\"score\", \"mean\"), ํฉ๊ฒฉ๋ฅ =(\"ํฉ๊ฒฉ\", \"mean\")).round(2).to_string())\n",
"print(f\"์คํ ๋ก๊ทธ: ๋ฌธํญ {len(BANK)}๊ฐ, ์๋ต {len(df):,}๊ฑด, ๋ฏธ์๋ต {(df['raw'] == '').sum()}๊ฑด, ์ฑ์ ์์ธ 0๊ฑด\")\n",
"\n",
"plt.figure(figsize=(7, 4))\n",
"plt.hist(sc[\"score\"], bins=15, color=\"tab:blue\"); plt.axvline(full * PASS_RATE, color=\"red\", ls=\"--\", label=\"pass line (70%)\")\n",
"plt.xlabel(\"total score\"); plt.ylabel(\"soldiers\"); plt.legend(); plt.title(\"Auto-graded score distribution (synthetic)\"); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
03 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ์์์ 120๋ช
x 32๋ฌธํญ = 3,840๊ฑด ์ฑ์ (๊ต๊ด ๊ธฐ์ค ์ ๋ต๊ณผ ๋น๊ต)\n",
" ์ฑ์ ๋ฐฉ์ ์ฑ์ ์ ํ๋ MC ์ ํ๋ ORDER ์ ํ๋ SA ์ ํ๋ ๊ฑด๋น ms\n",
" ์ ํ ์ผ์น 0.9516 1.0 1.0 0.8062 0.0017\n",
"์คํ์ ํ์ฉ 0.9901 1.0 1.0 0.9604 0.0070\n",
"\n",
"๋ชฉํ(์ฑ์ ์ ํ๋ 75% ์ด์): ๋ฌ์ฑ (99.0%), ๊ฑด๋น ์ฒ๋ฆฌ 0.007 ms\n",
"\n",
"[์ ํ ์ผ์น ์ฑ์ ์ด ํ๋ฆฐ ์ฌ๋ก(์คํ์ยท๋์ด์ฐ๊ธฐ)]\n",
"qid raw val ๊ต๊ด ํ์ ์ ํ์ผ์น ์คํ์ํ์ฉ\n",
"Q25 ์๋ชป๋ ๊ฒฝ๋ณด ์๋ชป๋๊ฒฝ๋ณด True False False\n",
"Q21 ์๊ตฐ์ ๊ตฐ ๊ตฌ๋ถ ์๊ตฐ์ ๊ตฐ๊ตฌ๋ถ True False False\n",
"Q28 ์งํํต ์งํํต True False True\n",
"Q22 ํ๋ธ์ต ํ๋ธ์ต True False True\n",
"Q26 ๋ฏผ๊ธฐ๊ฐ ๋ฏผ๊ธฐ๊ฐ True False True\n",
"์คํ์ ํ์ฉ ์ฑ์ ๋ ๋์น๋ '๋ป์ ๋ง์ง๋ง ์ธ์ ๋ต์ ๋ชฉ๋ก์ ์๋ ํํ'์ ์ด์ ์ ๊ธฐยท๊ต๊ด ๊ฒํ (๋ฏธ์
7) ๋์์ผ๋ก ๋๊ธด๋ค.\n",
"\n",
"[ํด๋ฆฌ์คํฑ 1์ฐจ ํ๋: ์ ์ฌ๋ ๊ธฐ์ค๊ฐ]\n",
"๊ธฐ์ค 0.6: ๋จ๋ตํ ์ฑ์ ์ ํ๋ 0.965\n",
"๊ธฐ์ค 0.7: ๋จ๋ตํ ์ฑ์ ์ ํ๋ 0.960\n",
"๊ธฐ์ค 0.75: ๋จ๋ตํ ์ฑ์ ์ ํ๋ 0.960\n",
"๊ธฐ์ค 0.8: ๋จ๋ตํ ์ฑ์ ์ ํ๋ 0.956\n",
"๊ธฐ์ค 0.9: ๋จ๋ตํ ์ฑ์ ์ ํ๋ 0.935\n",
"\n",
"๋ง์ 99์ , ํ๊ท 67.4์ , ํฉ๊ฒฉ๋ฅ 50.8%\n",
" ํ๊ท ์ ์ ํฉ๊ฒฉ๋ฅ \n",
"unit \n",
"๊ฐ์1์ค๋ 75.40 0.68\n",
"๊ฐ์2์ค๋ 58.55 0.28\n",
"๊ฐ์3์ค๋ 68.18 0.57\n",
"์คํ ๋ก๊ทธ: ๋ฌธํญ 32๊ฐ, ์๋ต 3,840๊ฑด, ๋ฏธ์๋ต 72๊ฑด, ์ฑ์ ์์ธ 0๊ฑด\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
04. ์ค๋ต ์ ํ(๊ฐ๋
ํผ๋, ํผ์ ์ค์ธ, ์ ์ฐจ ๋๋ฝ) ์๋ ๋ถ๋ฅ ๋ฐ ํจํด ๋ถ์ ์์ง ๊ณ ๋ํ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์ค๋ต ์ ํ(๊ฐ๋
ํผ๋, ํผ์ ์ค์ธ, ์ ์ฐจ ๋๋ฝ) ์๋ ๋ถ๋ฅ ์์ง์ ๊ณ ๋ํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ค๋ต์ ๊ฐ๋
ํผ๋ยทํผ์ ์ค์ธยท์ ์ฐจ ๋๋ฝ ์ ํ์ผ๋ก ์๋ ๋ถ๋ฅํ๋ ์์ง์ ๊ณ ๋ํํ๊ณ ๊ฐ์ธยท์ค๋๋ณ ์ค๋ต ํจํด์ ๋ถ์ํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ ๋คํ์ ์ค๋ต ๋ณด๊ธฐ๋ณ ์ ํ ๋งคํ, ์์ํ์ ๋จ๊ณ ๋๋ฝยท์์ ๋ค๋ฐ๋ ๊ท์น, ๋จ๋ตํ์ ์ค๋ต ๋ชฉ๋ก ๋์กฐ์ ์คํ์ ๊ทผ์ฌ ์ผ์น์ ๋ฌธ์ n-gram ๋ถ๋ฅ ๋ชจ๋ธ์ ๋ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/trn02_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": [
"\"\"\"์ค๋ต์ ๊ฐ๋
ํผ๋ยทํผ์ ์ค์ธยท์ ์ฐจ ๋๋ฝ ์ ํ์ผ๋ก ์๋ ๋ถ๋ฅํ๋ ์์ง์ ๊ณ ๋ํํ๋ค. ๋ณด๊ธฐ ๋งคํยท์์ ๊ท์นยท์ค๋ต ๋ชฉ๋ก ๋์กฐ์ ๋ฌธ์ n-gram ๋ถ๋ฅ ๋ชจ๋ธ์ ๋ํด, ๋ณตํฉ ์ค๋ต ์๋๋ฆฌ์ค 10์ข
์์ ์ ํ๋ณ ์ฌํ์จ๊ณผ ์ฒ๋ฆฌ ์ง์ฐ์ ๊ฒ์ฆํ๊ณ ๊ฐ์ธยท์ค๋๋ณ ์ค๋ต ํจํด์ ๋ถ์ํ๋ค.\"\"\"\n",
"import difflib\n",
"import re\n",
"import time\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.pipeline import make_pipeline\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฐฉ๊ณตยท๋๋ก ์๋ณ ํด์ฆ ๋ฌธํญ ์ํ๊ณผ ๊ฐ์ ์์์ ์๋ต ์์ฑ๊ธฐ\n",
"# ๋ฌธํญ์ ์ผ๋ฐ ์์น ์์ค์ ๊ฐ์ ๋ฌธํญ์ด๋ฉฐ ์ค์ ๊ต๋ฒยท๊ต์ ๊ท์น ์๋ฌธ์ด ์๋๋ค. ์์์๋ ๊ฐ๋ช
(S001~)์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"AREAS = [\"ํ์์๋ณ\", \"ํผ์์๋ณ\", \"๊ฒฝ๋ณด๋ณด๊ณ \", \"๊ต์ ํต์ \", \"๋๋๋ก ์ผ๋ฐ\"]\n",
"ETYPES = [\"๊ฐ๋
ํผ๋\", \"ํผ์ ์ค์ธ\", \"์ ์ฐจ ๋๋ฝ\"]\n",
"POINTS = {1: 2, 2: 3, 3: 5}\n",
"C, F, P = ETYPES\n",
"\n",
"\n",
"def _mc(qid, area, diff, stem, opts, ans, dist):\n",
" return {\"qid\": qid, \"type\": \"MC\", \"area\": area, \"diff\": diff, \"stem\": stem, \"options\": opts, \"answer\": ans,\n",
" \"dist\": dict(zip([i for i in range(1, 5) if i != ans], dist))}\n",
"\n",
"\n",
"def _sa(qid, area, diff, stem, accept, wrong):\n",
" return {\"qid\": qid, \"type\": \"SA\", \"area\": area, \"diff\": diff, \"stem\": stem, \"accept\": accept, \"wrong\": wrong}\n",
"\n",
"\n",
"def _order(qid, area, diff, stem):\n",
" return {\"qid\": qid, \"type\": \"ORDER\", \"area\": area, \"diff\": diff, \"stem\": stem, \"answer\": \"ABCD\"}\n",
"\n",
"\n",
"BANK = [\n",
" _mc(\"Q01\", \"ํ์์๋ณ\", 1, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์์ด ์ผ๊ฐํ ๋ ๊ฐ๋ฅผ ๊ฐ์ง ์ํ ๊ณ ์ ์ต ์ค๋ฃจ์ฃ์?\", [\"์ฟผ๋์ฝฅํฐํ\", \"์ผ๊ฐ์ต ๊ณ ์ ์ตํ\", \"ํ์ ์ต ํฌ๊ธฐํ\", \"์กฐ๋ฅ\"], 2, [C, C, F]),\n",
" _mc(\"Q02\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๊ฐ ์ญ์๋ก ๋ฐฐ์น๋ ์ค๋ฃจ์ฃ์?\", [\"๊ณ ์ ์ต ๋๋ก ํ\", \"์ฟผ๋์ฝฅํฐํ\", \"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\"], 2, [C, C, C]),\n",
" _mc(\"Q03\", \"ํ์์๋ณ\", 2, \"๊ฐ๋๊ณ ๊ธด ๋์ฒด ์ค๊ฐ์ ์์ ๋ ๊ฐ๊ฐ ๋ถ์ ์ค๋ฃจ์ฃ์?\", [\"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\", \"๋ฏผ๊ฐ ๊ฒฝ๋นํ๊ธฐํ\", \"์กฐ๋ฅ\"], 1, [C, F, F]),\n",
" _mc(\"Q04\", \"ํ์์๋ณ\", 3, \"์๊ฑฐ๋ฆฌ๋ผ ์ค๋ฃจ์ฃ ์๋ณ์ด ์ด๋ ค์ธ ๋ ๋จผ์ ํ ํ๋์?\", [\"์ถ์ ์ ํ์ผ๋ก ํ์ ๋ณด๊ณ \", \"๊ด์ธก ์ ๋ณด๋ฅผ ๊ทธ๋๋ก ๋ณด๊ณ ํ๊ณ ์ถ๊ฐ ๊ด์ธก\", \"๊ด์ธก ์ค๋จ\", \"๋ฏผ๊ฐ๊ธฐ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _mc(\"Q05\", \"ํผ์์๋ณ\", 2, \"์๋ณ ์ ํธ ์๋ต์ด ์๋ ํญ๊ณต๊ธฐ์ ๋ํ ์ฌ๋ฐ๋ฅธ ํ๋จ์?\", [\"์ฆ์ ์ ๊ธฐ๋ก ํ์ \", \"๋ฏธํ์ธ ํญ๊ณต๊ธฐ๋ก ๋ถ๋ฅํ๊ณ ์ถ๊ฐ ํ์ธ\", \"์๊ตฐ๊ธฐ๋ก ๊ฐ์ฃผ\", \"๋ฌด์\"], 2, [F, F, P]),\n",
" _mc(\"Q06\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด์ ์ผ์นํ๋ ์ ์ ๋นํ์ฒด์ ๋ถ๋ฅ๋?\", [\"์ ๋๋ก \", \"๋ฏผ๊ฐ๊ธฐ ๊ฐ๋ฅ์ฑ ๋์, ์ถ๊ฐ ํ์ธ\", \"์ํญ๋ฏธ์ฌ์ผ\", \"์๊ตฐ ์ ์ฐฐ๊ธฐ๋ก ํ์ \"], 2, [F, C, F]),\n",
" _mc(\"Q07\", \"ํผ์์๋ณ\", 3, \"์๊ตฐ ๋๋ก ์ด์ฉ ๊ณต์ง๊ฐ ์๋ ๊ณต์ญ์์ ๋ฏธํ์ธ ๋๋ก ์ ๋ณด๋ฉด ์ฐ์ ํ์ธํ ๊ฒ์?\", [\"๊ธฐ์ ์ ๋ณด\", \"์๊ตฐ ์ด์ฉ ๊ณํ๊ณผ ์๋ณ ์ ํธ\", \"๋์ ์ค๋น ์ํ\", \"์ฃผ๋ณ ์งํ\"], 2, [C, P, C]),\n",
" _mc(\"Q08\", \"ํผ์์๋ณ\", 3, \"์กฐ๋ฅ ๋ฌด๋ฆฌ์ ์ํ ๋๋ก ๊ตฐ์ง ๊ตฌ๋ถ์ ๊ฐ์ฅ ๋์์ด ๋๋ ์ ๋ณด๋?\", [\"ํฌ๊ธฐ๋ง ๋น๊ต\", \"๊ถค์ ์ ๊ท์น์ฑ๊ณผ ์ด ์ ํธ\", \"์์\", \"๊ด์ธก ์๊ฐ\"], 2, [C, C, C]),\n",
" _mc(\"Q09\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๊ณต์ค ์ํ ์ต์ด ๋ณด๊ณ ์ ํฌํจํ์ง ์์๋ ๋๋ ๊ฒ์?\", [\"๊ด์ธก ์๊ฐ\", \"๋ฐฉํฅ๊ณผ ๊ฑฐ๋ฆฌ\", \"์ถ์ ์ ํ\", \"๊ด์ธก์ ๊ฐ์ธ ์ฐ๋ฝ์ฒ\"], 4, [P, P, P]),\n",
" _mc(\"Q10\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๊ฒฝ๋ณด ์ ํ ํ ์ํฉ์ด ๋ฐ๋๋ฉด?\", [\"์ต์ด ๋ณด๊ณ ์ ์ง\", \"๋ณ๊ฒฝ ์ฌํญ์ ์ฆ์ ์ถ๊ฐ ๋ณด๊ณ \", \"์ํฉ ์ข
๋ฃ ํ ์ผ๊ด ๋ณด๊ณ \", \"๊ฐ์ธ ํ๋จ์ผ๋ก ๊ฒฝ๋ณด ํด์ \"], 2, [P, P, P]),\n",
" _mc(\"Q11\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ณด๊ณ ๋ด์ฉ์ด ๋ถํ์คํ ๋ ์ฌ๋ฐ๋ฅธ ํํ์?\", [\"ํ์ ํํ์ผ๋ก ๋ณด๊ณ \", \"'์ถ์ '์์ ๋ฐํ๊ณ ๊ทผ๊ฑฐ์ ํจ๊ป ๋ณด๊ณ \", \"๋ณด๊ณ ๋ณด๋ฅ\", \"๋ค๋ฅธ ๊ด์ธก์ ๋ณด๊ณ ๋ฅผ ๋ณต์ฌ\"], 2, [C, P, P]),\n",
" _mc(\"Q12\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋๋ก ํ์ง ํ ๋ณด๊ณ ๊ฒฝ๋ก๋ก ์๋ง์ ๊ฒ์?\", [\"์ธ์ ๋ถ๋์๋ง ์ ํ\", \"์งํ๊ณํต์ ๋ฐ๋ผ ๋ณด๊ณ \", \"๊ฐ์ธ ๋ฉ์ ์ ๋ก ๊ณต์ \", \"๋ณด๊ณ ์์ด ๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q13\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ๋ง์ผ๋ก ๋์์ ๊ฒฐ์ ํ ์ ์๋๊ฐ?\", [\"๊ฐ๋ฅํ๋ค\", \"์ธ๊ฐ ์งํ๊ด์ ์น์ธ์ด ํ์ํ๋ค\", \"์ ๋ขฐ๋ 90% ์ด์์ด๋ฉด ๊ฐ๋ฅ\", \"์ผ๊ฐ์๋ง ๊ฐ๋ฅ\"], 2, [C, C, C]),\n",
" _mc(\"Q14\", \"๊ต์ ํต์ \", 2, \"๋ฏผ๊ฐ ํผํด ๊ฐ๋ฅ์ฑ์ด ํ์ธ๋๋ฉด?\", [\"๊ทธ๋๋ก ์งํ\", \"์ฆ์ ๋ณด๊ณ ํ๊ณ ์งํ๊ด ํ๋จ์ ๋ฐ๋๋ค\", \"์กฐ์น ํ ๋ณด๊ณ \", \"๋ฌด์\"], 2, [P, P, F]),\n",
" _mc(\"Q15\", \"๊ต์ ํต์ \", 3, \"์๋ณ์ด ๋ถํ์คํ ํ์ ์ ๋ํ ์์น์?\", [\"์ํ์ผ๋ก ๊ฐ์ฃผํด ์ฆ์ ๋์\", \"์๋ณ ํ์ธ ์ ๊น์ง ์น์ธ ์ ์ฐจ๋ฅผ ๋ฐ๋ฅธ๋ค\", \"๋ฌด์กฐ๊ฑด ๋ฌด์\", \"๋ค์๊ฒฐ๋ก ๊ฒฐ์ \"], 2, [F, F, C]),\n",
" _mc(\"Q16\", \"๊ต์ ํต์ \", 2, \"ํ๋จ ๊ทผ๊ฑฐ๋ฅผ ๊ธฐ๋กํด์ผ ํ๋ ์ด์ ๋?\", [\"ํ์์ ํ์\", \"์ฌํ ๊ฒํ ์ ์ฑ
์ ์ถ์ \", \"๋ณด๊ณ ์๋ต\", \"๊ธฐ๋ก ๋ถํ์\"], 2, [C, P, P]),\n",
" _mc(\"Q17\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"๋๋๋ก ์ฒด๊ณ์ ์ผ๋ฐ์ ์ฒ๋ฆฌ ์์๋?\", [\"๋์-ํ์ง-์๋ณ\", \"ํ์ง-์๋ณ-๋ณด๊ณ ยทํ๋จ-๋์\", \"์๋ณ-๋์-ํ์ง\", \"๋ณด๊ณ -ํ์ง-๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q18\", \"๋๋๋ก ์ผ๋ฐ\", 3, \"๋ ์ด๋๋ง์ผ๋ก ์กฐ๋ฅ์ ๋๋ก ๊ตฌ๋ถ์ด ์ด๋ ค์ด ์ด์ ๋?\", [\"๋ฐ์ฌ ์ ํธ ํฌ๊ธฐ๊ฐ ๋น์ทํ ์ ์์ด์\", \"๋ ์ด๋๋ ์ผ๊ฐ์ ๋ชป ์จ์\", \"๋๋ก ์๋ ๊ธ์์ด ์์ด์\", \"์กฐ๋ฅ๋ ํ์ง๋์ง ์์์\"], 1, [C, C, C]),\n",
" _mc(\"Q19\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ ์๊ดํ(EO) ์ผ์์ ์ฝ์ ์ด ์ปค์ง๋ ์กฐ๊ฑด์?\", [\"๋ง์ ์ฃผ๊ฐ\", \"์๊ฐยท์ผ๊ฐ\", \"์ ์ ํ์ \", \"๊ทผ๊ฑฐ๋ฆฌ\"], 2, [C, C, C]),\n",
" _mc(\"Q20\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"๊ฒฝ๋ณด๊ฐ ๋ฐ๋ณตํด์ ์ค๊ฒฝ๋ณด๋ก ํ๋ช
๋๋ฉด?\", [\"๊ฒฝ๋ณด ์ฅ์น๋ฅผ ๋๋ค\", \"์ฌ๋ก๋ฅผ ๊ธฐ๋กยท๋ณด๊ณ ํ๊ณ ๊ธฐ์ค ์ฌ๊ฒํ ์์ฒญ\", \"๋ฌด์ํ๋ค\", \"๋ชจ๋ ์ค์ ์ํ์ผ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _sa(\"Q21\", \"ํผ์์๋ณ\", 1, \"์๊ตฐยท์ ยท๋ฏผ๊ฐ์ ๊ตฌ๋ถํ๋ ์ ์ฐจ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"ํผ์์๋ณ\", \"IFF\"], {\"ํ์ ํ๋\": C, \"์ ๊ธฐํ์ \": F, \"๊ฒฝ๊ณ\": C}),\n",
" _sa(\"Q22\", \"ํ์์๋ณ\", 2, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์๋ ์ผ๊ฐํ ๋ ๊ฐ ํํ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ผ๊ฐ์ต\", \"๋ธํ์ต\", \"delta\"], {\"์ง์ ์ต\": C, \"ํํด์ต\": C, \"์ฟผ๋์ฝฅํฐ\": C}),\n",
" _sa(\"Q23\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ถํ์คํ ๊ด์ธก์ ๋ณด๊ณ ํ ๋ ๋ถ์ด๋ ํํ์?\", [\"์ถ์ \"], {\"ํ์ \": C, \"ํ์ธ\": C, \"์๋ต\": P}),\n",
" _sa(\"Q24\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ์ ๋ํด ์ต์ข
๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ์ฃผ์ฒด๋?\", [\"์งํ๊ด\"], {\"AI\": C, \"์ด์ฉ๋ณ\": P, \"๋ ์ด๋\": C}),\n",
" _sa(\"Q25\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"์กฐ๋ฅ๋ฅผ ๋๋ก ์ผ๋ก ์๋ชป ๊ฒฝ๋ณดํ๋ ๊ฒ์ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ค๊ฒฝ๋ณด\", \"์คํ\", \"false alarm\"], {\"๋ฏธํ\": C, \"์ฌ๊ฒฝ๋ณด\": C, \"์ ๊ธฐ\": F}),\n",
" _sa(\"Q26\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด๊ฐ ํ์ธ๋ ์ฌ๊ฐ๊ธฐ๋ ์ด๋ป๊ฒ ๋ถ๋ฅํ๋๊ฐ?\", [\"๋ฏผ๊ฐ๊ธฐ\", \"๋ฏผํญ๊ธฐ\"], {\"์ ๊ธฐ\": F, \"๋ฏธํ์ธ๊ธฐ\": P, \"์๊ตฐ๊ธฐ\": F}),\n",
" _sa(\"Q27\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๋ฅผ ๊ฐ์ง ๋๋ก ํํ๋?\", [\"์ฟผ๋์ฝฅํฐ\", \"quadcopter\"], {\"ํฅ์ฌ์ฝฅํฐ\": C, \"๊ณ ์ ์ต\": C, \"ํฌ๊ธฐ\": F}),\n",
" _sa(\"Q28\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋ณด๊ณ ๋ ๋ฌด์์ ๋ฐ๋ผ ์ฌ๋ผ๊ฐ๋๊ฐ?\", [\"์งํ๊ณํต\"], {\"์ธ์ ๋ถ๋\": P, \"๊ฐ์ธ๋ฉ์ ์ \": P, \"ํต์ ๋ง\": C}),\n",
" _order(\"Q29\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"์ต์ด ๋ณด๊ณ ์์: A ๊ด์ธก ์๊ฐ, B ๋ฐฉํฅยท๊ฑฐ๋ฆฌ, C ์ถ์ ์ ํ, D ๊ด์ธก์ ์กฐ์น\"),\n",
" _order(\"Q30\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ฒ๋ฆฌ ์์: A ํ์ง, B ์๋ณ, C ๋ณด๊ณ ยทํ๋จ, D ๋์\"),\n",
" _order(\"Q31\", \"๊ต์ ํต์ \", 3, \"ํต์ ์์: A ์๋ณ ํ์ธ, B ์๊ธ ์น์ธ ์์ฒญ, C ์น์ธ ์๋ น, D ์กฐ์น ๊ธฐ๋ก\"),\n",
" _order(\"Q32\", \"ํผ์์๋ณ\", 3, \"ํ์ธ ์์: A ์ดํญ์ ๋ณด ๋์กฐ, B ์๋ณ ์ ํธ ํ์ธ, C ๋ฏธํ์ธ ๋ถ๋ฅ, D ๋ณด๊ณ \"),\n",
"]\n",
"QIDX = {q[\"qid\"]: q for q in BANK}\n",
"UNITS = [\"๊ฐ์1์ค๋\", \"๊ฐ์2์ค๋\", \"๊ฐ์3์ค๋\"]\n",
"_CIRC = \"โ โกโขโฃ\"\n",
"PARA = {\"Q21\": \"์๊ตฐ์ ๊ตฐ ๊ตฌ๋ถ\", \"Q22\": \"์ธ๋ชจ ๋ ๊ฐ\", \"Q23\": \"์ถ์ ๋จ์ผ๋ก ํ๊ธฐ\", \"Q24\": \"์ค๋์ฅ(์งํ๊ด)\", \"Q25\": \"์๋ชป๋ ๊ฒฝ๋ณด\",\n",
" \"Q26\": \"๋ฏผ๊ฐ ์ฌ๊ฐ๊ธฐ\", \"Q27\": \"4๋ฐ ๋๋ก \", \"Q28\": \"์งํ ๋ผ์ธ\"} # ๋ป์ ๋ง์ง๋ง ์ธ์ ๋ต์ ๋ชฉ๋ก์ ์๋ ํํ\n",
"\n",
"\n",
"def make_soldiers(n_per_unit, seed):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for u, unit in enumerate(UNITS):\n",
" base = [0.3, -0.1, 0.0][u]\n",
" for i in range(n_per_unit):\n",
" off = rng.normal(0, 0.5, len(AREAS)) + (np.array([0, 0, 0, -0.6, 0]) if u == 1 else 0)\n",
" out.append({\"sid\": f\"S{len(out) + 1:03d}\", \"unit\": unit, \"theta\": dict(zip(AREAS, base + rng.normal(0, 0.6) + off)),\n",
" \"tend\": dict(zip(ETYPES, rng.dirichlet([1.2, 1.2, 1.2])))})\n",
" return out\n",
"\n",
"\n",
"def p_correct(theta, diff):\n",
" return 1 / (1 + np.exp(-1.7 * (theta - (diff - 2) * 0.9 + 0.9)))\n",
"\n",
"\n",
"def _typo(s, rng):\n",
" if len(s) < 2:\n",
" return s\n",
" i = int(rng.integers(len(s) - 1))\n",
" return s[:i] + s[i + 1] + s[i] + s[i + 2:] if len(s) <= 3 or rng.random() < 0.5 else s[:i] + s[i + 1:]\n",
"\n",
"\n",
"def _fmt_mc(k, rng):\n",
" r = rng.random()\n",
" if r < 0.55:\n",
" return str(k)\n",
" return rng.choice([_CIRC[k - 1], f\"{k}๋ฒ\", \"ABCD\"[k - 1], \"abcd\"[k - 1] + \")\", f\" {k} \", chr(0xFF10 + k), f\"{k}.\"])\n",
"\n",
"\n",
"def _fmt_sa(s, rng):\n",
" r = rng.random()\n",
" if r < 0.45:\n",
" return s\n",
" if r < 0.6:\n",
" return \" \".join(s) if len(s) <= 4 else s[:2] + \" \" + s[2:]\n",
" if r < 0.72:\n",
" return s + rng.choice([\"์
๋๋ค\", \"์ด๋ค\", \".\", \"์\"])\n",
" if r < 0.8:\n",
" return s.upper() if s.isascii() else \"\".join(chr(ord(ch) + 0xFEE0) if \"!\" <= ch <= \"~\" else ch for ch in s) + \" \"\n",
" return _typo(s, rng)\n",
"\n",
"\n",
"def answer(q, sol, rng):\n",
" \"\"\"ํ ๋ฌธํญ ์๋ต: (์๋ณธ ์๋ต ๋ฌธ์์ด, ์๋ํ ์ ๋ต ์ฌ๋ถ, ์๋ํ ๋ต, ์ค๋ต ์ ํ).\"\"\"\n",
" ok = rng.random() < p_correct(sol[\"theta\"][q[\"area\"]], q[\"diff\"])\n",
" if q[\"type\"] == \"MC\":\n",
" if ok:\n",
" k, et = q[\"answer\"], None\n",
" else:\n",
" ks = list(q[\"dist\"]); w = np.array([sol[\"tend\"][q[\"dist\"][k]] for k in ks])\n",
" k = int(rng.choice(ks, p=w / w.sum())); et = q[\"dist\"][k]\n",
" return _fmt_mc(k, rng), ok, str(k), et\n",
" if q[\"type\"] == \"SA\":\n",
" if ok and rng.random() < 0.06:\n",
" return PARA[q[\"qid\"]], ok, PARA[q[\"qid\"]], None\n",
" if ok:\n",
" s, et = str(rng.choice(q[\"accept\"])), None\n",
" else:\n",
" ws = list(q[\"wrong\"]); w = np.array([sol[\"tend\"][q[\"wrong\"][x]] for x in ws])\n",
" s = str(rng.choice(ws, p=w / w.sum())); et = q[\"wrong\"][s]\n",
" return _fmt_sa(s, rng), ok, s, et\n",
" if ok:\n",
" s = \"ABCD\"\n",
" elif rng.random() < 0.5:\n",
" s = \"ABCD\".replace(\"ABCD\"[rng.integers(1, 4)], \"\") # ๋จ๊ณ ํ๋ ๋น ๋จ๋ฆผ\n",
" else:\n",
" i = int(rng.integers(0, 3)); s = list(\"ABCD\"); s[i], s[i + 1] = s[i + 1], s[i]; s = \"\".join(s) # ์์ ๋ค๋ฐ๋\n",
" raw = s if rng.random() < 0.6 else rng.choice([\"-\".join(s), \" \".join(s), s.lower(), \",\".join(s)])\n",
" return raw, ok, s, (None if ok else P)\n",
"\n",
"\n",
"def make_responses(n_per_unit=40, seed=7, blank=0.02):\n",
" \"\"\"์์์ x ๋ฌธํญ ์๋ตํ(DataFrame)๋ฅผ ๋ง๋ ๋ค. truth_* ์ด์ ๊ต๊ด ๊ธฐ์ค ์ ๋ต(ํ๊ฐ์ฉ)์ด๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows = []\n",
" for sol in make_soldiers(n_per_unit, seed):\n",
" for q in BANK:\n",
" raw, ok, intended, et = answer(q, sol, rng)\n",
" if rng.random() < blank:\n",
" raw, ok, intended, et = \"\", False, \"\", None\n",
" rows.append({\"rid\": f\"R{len(rows):05d}\", \"sid\": sol[\"sid\"], \"unit\": sol[\"unit\"], \"qid\": q[\"qid\"], \"raw\": raw,\n",
" \"truth_correct\": ok, \"truth_answer\": intended, \"truth_etype\": et})\n",
" return pd.DataFrame(rows)\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์๋ต ์ ์ ยทํ์ฑยท์ฑ์ ๊ณตํต ํจ์ ----\n",
"TAILS = (\"์
๋๋ค\", \"์ด๋ค\", \"์\")\n",
"\n",
"\n",
"def norm_text(s):\n",
" \"\"\"์ ๊ฐ->๋ฐ๊ฐ(NFKC), ์๋ฌธ์, ๊ณต๋ฐฑยท๋ฌธ์ฅ๋ถํธ ์ ๊ฑฐ, ๋๋งบ์ ์ด๋ฏธ ์ ๊ฑฐ.\"\"\"\n",
" s = unicodedata.normalize(\"NFKC\", str(s)).strip().lower()\n",
" s = re.sub(r\"[\\s\\.\\,\\-\\(\\)ยท]\", \"\", s)\n",
" for t in TAILS:\n",
" if s.endswith(t) and len(s) > len(t) + 1:\n",
" s = s[:-len(t)]\n",
" return s\n",
"\n",
"\n",
"def parse(q, raw):\n",
" \"\"\"์๋ณธ ์๋ต -> (์ ์ ๊ฐ, ์ํ). ์ํ: ์ ์ / ๋ฏธ์๋ต / ํ์์ค๋ฅ\"\"\"\n",
" if raw is None or str(raw).strip() == \"\":\n",
" return \"\", \"๋ฏธ์๋ต\"\n",
" s = unicodedata.normalize(\"NFKC\", str(raw)).strip()\n",
" if q[\"type\"] == \"MC\":\n",
" t = re.sub(r\"[\\s๋ฒ\\.\\)]\", \"\", s).lower()\n",
" t = {\"a\": \"1\", \"b\": \"2\", \"c\": \"3\", \"d\": \"4\"}.get(t, t)\n",
" return (t, \"์ ์\") if t in (\"1\", \"2\", \"3\", \"4\") else (t, \"ํ์์ค๋ฅ\")\n",
" if q[\"type\"] == \"ORDER\":\n",
" t = re.sub(r\"[^A-Za-z]\", \"\", s).upper()\n",
" return (t, \"์ ์\") if t and set(t) <= set(\"ABCD\") and len(set(t)) == len(t) else (t, \"ํ์์ค๋ฅ\")\n",
" return norm_text(s), \"์ ์\"\n",
"\n",
"\n",
"def grade_exact(q, val):\n",
" if q[\"type\"] == \"MC\":\n",
" return val == str(q[\"answer\"])\n",
" if q[\"type\"] == \"ORDER\":\n",
" return val == q[\"answer\"]\n",
" return val in {norm_text(a) for a in q[\"accept\"]}\n",
"\n",
"\n",
"def grade_fuzzy(q, val, cut=0.75):\n",
" \"\"\"๋จ๋ตํ์ ์คํ์(๊ธ์ ์์ ๋ฐ๋ยทํ ๊ธ์ ๋๋ฝ)๋ฅผ ํ์ฉ(์ ์ฌ๋ cut ์ด์)ํ๋, ์๋ ค์ง ์ค๋ต๊ณผ ๋ ๊ฐ๊น์ฐ๋ฉด ์ค๋ต์ผ๋ก ๋ณธ๋ค.\"\"\"\n",
" if grade_exact(q, val) or q[\"type\"] != \"SA\" or len(val) < 2:\n",
" return grade_exact(q, val)\n",
" best = max(1.0 if sorted(val) == sorted(norm_text(a)) else difflib.SequenceMatcher(None, val, norm_text(a)).ratio() for a in q[\"accept\"])\n",
" worst = max(difflib.SequenceMatcher(None, val, norm_text(w)).ratio() for w in q[\"wrong\"])\n",
" return best >= cut and best > worst\n",
"\n",
"\n",
"def score_of(q, correct):\n",
" return POINTS[q[\"diff\"]] if correct else 0\n",
"\n",
"# ---- ์ค๋ต ์ ํ ๋ถ๋ฅ๊ธฐ(๊ท์น + ๋ฌธ์ n-gram ๋ก์ง์คํฑ ํ๊ท) ----\n",
"SEED = {\"ํผ์ ์ค์ธ\": [\"์ ๊ธฐ\", \"์ ๋๋ก \", \"์๊ตฐ๊ธฐ\", \"์ ํฌ๊ธฐ\", \"์ ๊ธฐํ์ \", \"ํฌ๊ธฐ\", \"์ ํญ๊ณต๊ธฐ ํ์ \", \"์๊ตฐ ๋๋ก \"],\n",
" \"์ ์ฐจ ๋๋ฝ\": [\"์๋ต\", \"์ธ์ ๋ถ๋\", \"๊ฐ์ธ๋ฉ์ ์ \", \"๋ณด๊ณ ์ ํจ\", \"์ด์ฉ๋ณ\", \"๋ฏธํ์ธ๊ธฐ\", \"๋จ๊ณ ๊ฑด๋๋\", \"์ง์ ์ ํ\"],\n",
" \"๊ฐ๋
ํผ๋\": [\"์ง์ ์ต\", \"ํํด์ต\", \"ํฅ์ฌ์ฝฅํฐ\", \"๋ฏธํ\", \"์ฌ๊ฒฝ๋ณด\", \"ํ์ \", \"ํ์ธ\", \"๋ ์ด๋\", \"AI\", \"ํ์ ํ๋\", \"๊ฒฝ๊ณ\", \"ํต์ ๋ง\", \"๊ณ ์ ์ต\"]}\n",
"\n",
"\n",
"def build_text_model():\n",
" texts, labels = [], []\n",
" for q in BANK:\n",
" if q[\"type\"] == \"SA\":\n",
" for w, et in q[\"wrong\"].items():\n",
" texts.append(w); labels.append(et)\n",
" for et, ws in SEED.items():\n",
" texts += ws; labels += [et] * len(ws)\n",
" m = make_pipeline(TfidfVectorizer(analyzer=\"char_wb\", ngram_range=(1, 3)), LogisticRegression(max_iter=2000, C=5))\n",
" return m.fit([norm_text(t) for t in texts], labels)\n",
"\n",
"\n",
"def classify_error(q, val, text_model):\n",
" \"\"\"์ค๋ต 1๊ฑด์ ์ ํ๊ณผ ํ๋จ ๊ทผ๊ฑฐ(rule/text)๋ฅผ ๋๋ ค์ค๋ค.\"\"\"\n",
" if q[\"type\"] == \"MC\":\n",
" return q[\"dist\"].get(int(val), \"๊ฐ๋
ํผ๋\") if val in \"1234\" and val else \"๊ฐ๋
ํผ๋\", \"๋ณด๊ธฐ ๋งคํ\"\n",
" if q[\"type\"] == \"ORDER\":\n",
" return \"์ ์ฐจ ๋๋ฝ\", (\"๋จ๊ณ ๋๋ฝ\" if len(val) < 4 else \"์์ ๋ค๋ฐ๋\")\n",
" known = {norm_text(w): et for w, et in q[\"wrong\"].items()}\n",
" if val in known:\n",
" return known[val], \"์ค๋ต ๋ชฉ๋ก\"\n",
" near = max(known, key=lambda w: difflib.SequenceMatcher(None, val, w).ratio())\n",
" if difflib.SequenceMatcher(None, val, near).ratio() >= 0.75 or sorted(val) == sorted(near):\n",
" return known[near], \"์ค๋ต ๋ชฉ๋ก(์คํ์)\"\n",
" return str(text_model.predict([val])[0]), \"๋ฌธ์ ๋ชจ๋ธ\"\n",
"\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ์ธ์ ๋ต์ยท์ค๋ต ๋ชฉ๋ก์ ์๋ ์๋ก์ด ์ค๋ต ํํ(๋ฌธ์ ๋ชจ๋ธ ์ํ์ฉ)\n",
"NOVEL = [(\"Q21\", \"์ ๊ธฐ ํ์ \", \"ํผ์ ์ค์ธ\"), (\"Q21\", \"ํ์ ์ถ์ \", \"๊ฐ๋
ํผ๋\"), (\"Q22\", \"์ง์ ๋ ๊ฐ\", \"๊ฐ๋
ํผ๋\"), (\"Q23\", \"ํ์คํจ\", \"๊ฐ๋
ํผ๋\"),\n",
" (\"Q23\", \"๋ณด๊ณ ์ํจ\", \"์ ์ฐจ ๋๋ฝ\"), (\"Q24\", \"์๋ ํ์ \", \"๊ฐ๋
ํผ๋\"), (\"Q24\", \"๊ด์ธก ์ด์ฉ๋ณ\", \"์ ์ฐจ ๋๋ฝ\"), (\"Q25\", \"๋ฏธํ์ง\", \"๊ฐ๋
ํผ๋\"),\n",
" (\"Q25\", \"์ ๋๋ก \", \"ํผ์ ์ค์ธ\"), (\"Q26\", \"์ ์ ์ฐฐ๊ธฐ\", \"ํผ์ ์ค์ธ\"), (\"Q26\", \"๋ณด๊ณ ์๋ต\", \"์ ์ฐจ ๋๋ฝ\"), (\"Q27\", \"๊ณ ์ ์ต ๋๋ก \", \"๊ฐ๋
ํผ๋\"),\n",
" (\"Q27\", \"์ ํฌ๊ธฐ\", \"ํผ์ ์ค์ธ\"), (\"Q28\", \"์ธ์ ๋ถ๋ ์ง๋ณด\", \"์ ์ฐจ ๋๋ฝ\"), (\"Q28\", \"๋จ์ฒด ๋ฉ์ ์ \", \"์ ์ฐจ ๋๋ฝ\"), (\"Q21\", \"์๊ตฐ๊ธฐ\", \"ํผ์ ์ค์ธ\"),\n",
" (\"Q23\", \"๋์ถฉ ๋ณด๊ณ \", \"์ ์ฐจ ๋๋ฝ\"), (\"Q22\", \"๋ ๊ฐ ์์\", \"๊ฐ๋
ํผ๋\"), (\"Q26\", \"๋ฏธํ์ธ ํ์ ์ผ๋ก ๋์\", \"ํผ์ ์ค์ธ\"),\n",
" (\"Q24\", \"๋ถ๋์ ๋๊ตฌ๋\", \"์ ์ฐจ ๋๋ฝ\"), (\"Q25\", \"์ ์ ๊ฒฝ๋ณด\", \"๊ฐ๋
ํผ๋\"), (\"Q27\", \"์ ์ฐฐ ํฌ๊ธฐ\", \"ํผ์ ์ค์ธ\")]\n",
"\n",
"model = build_text_model()\n",
"df = make_responses(40, seed=7)\n",
"df[\"type\"] = df[\"qid\"].map(lambda q: QIDX[q][\"type\"])\n",
"df[\"val\"] = [parse(QIDX[q], r)[0] for q, r in zip(df[\"qid\"], df[\"raw\"])]\n",
"wrong = df[df[\"truth_etype\"].notna()].copy()\n",
"\n",
"t0 = time.perf_counter()\n",
"out = [classify_error(QIDX[q], v, model) for q, v in zip(wrong[\"qid\"], wrong[\"val\"])]\n",
"ms = (time.perf_counter() - t0) * 1000 / len(wrong)\n",
"wrong[\"pred\"], wrong[\"basis\"] = [o[0] for o in out], [o[1] for o in out]\n",
"\n",
"# ๋ณตํฉ ์ค๋ต ์๋๋ฆฌ์ค 10์ข
\n",
"rng = np.random.default_rng(5)\n",
"sa_typo = wrong[(wrong[\"type\"] == \"SA\")].copy()\n",
"sa_typo[\"val\"] = [norm_text(_typo(v, rng)) if len(v) >= 3 else v for v in sa_typo[\"truth_answer\"]]\n",
"sa_typo[\"pred\"] = [classify_error(QIDX[q], v, model)[0] for q, v in zip(sa_typo[\"qid\"], sa_typo[\"val\"])]\n",
"nov = pd.DataFrame(NOVEL, columns=[\"qid\", \"val\", \"truth_etype\"])\n",
"nov[\"val\"] = nov[\"val\"].map(norm_text)\n",
"nov[\"pred\"] = [classify_error(QIDX[q], v, model)[0] for q, v in zip(nov[\"qid\"], nov[\"val\"])]\n",
"raw_fmt = wrong[\"raw\"].str.strip() != wrong[\"val\"]\n",
"SCEN = {\n",
" \"S1 ์ ๋คํ ํ์ค ํ๊ธฐ\": wrong[(wrong[\"type\"] == \"MC\") & ~raw_fmt], \"S2 ์ ๋คํ ์๋ฌธ์ยท์ ๊ฐ ํ๊ธฐ\": wrong[(wrong[\"type\"] == \"MC\") & raw_fmt],\n",
" \"S3 ๋จ๋ต ์ค๋ต ๋ชฉ๋ก ์ผ์น\": wrong[(wrong[\"type\"] == \"SA\") & (wrong[\"basis\"] == \"์ค๋ต ๋ชฉ๋ก\")], \"S4 ๋จ๋ต ์ค๋ต + ์คํ์\": sa_typo,\n",
" \"S5 ๋จ๋ต ์ค๋ต + ์ด๋ฏธยท๋์ด์ฐ๊ธฐ\": wrong[(wrong[\"type\"] == \"SA\") & raw_fmt], \"S6 ์์ํ ๋จ๊ณ ๋๋ฝ\": wrong[(wrong[\"type\"] == \"ORDER\") & (wrong[\"val\"].str.len() < 4)],\n",
" \"S7 ์์ํ ์์ ๋ค๋ฐ๋\": wrong[(wrong[\"type\"] == \"ORDER\") & (wrong[\"val\"].str.len() == 4)], \"S8 ์์ํ ๊ตฌ๋ถ์ ํผ์
\": wrong[(wrong[\"type\"] == \"ORDER\") & raw_fmt],\n",
" \"S9 ๋ชฉ๋ก์ ์๋ ์ ์ค๋ต\": nov, \"S10 ์ ์ฒด ํผํฉ\": pd.concat([wrong, sa_typo, nov]),\n",
"}\n",
"rows = []\n",
"for name, d in SCEN.items():\n",
" rec = {et: (d[d[\"truth_etype\"] == et][\"pred\"] == et).mean() if (d[\"truth_etype\"] == et).any() else np.nan for et in ETYPES}\n",
" rows.append({\"์๋๋ฆฌ์ค\": name, \"๊ฑด์\": len(d), \"์ ํ๋\": (d[\"pred\"] == d[\"truth_etype\"]).mean(), **{f\"{k} ์ฌํ์จ\": v for k, v in rec.items()}})\n",
"res = pd.DataFrame(rows)\n",
"res[\"ํต๊ณผ\"] = np.where(res[\"์ ํ๋\"] >= 0.85, \"PASS\", \"FAIL\")\n",
"print(res.round(3).fillna(\"-\").to_string(index=False))\n",
"allr = SCEN[\"S10 ์ ์ฒด ํผํฉ\"]\n",
"recs = {et: (allr[allr[\"truth_etype\"] == et][\"pred\"] == et).mean() for et in ETYPES}\n",
"print(\"\\n์ค๋ต ์ ํ๋ณ ์ฌํ์จ:\", {k: round(float(v), 3) for k, v in recs.items()},\n",
" f\"(๋ชฉํ 85% ์ด์: {'๋ฌ์ฑ' if min(recs.values()) >= 0.85 else '๋ฏธ๋ฌ'}), ๊ฑด๋น ์ฒ๋ฆฌ {ms:.3f} ms (๋ชฉํ 200 ms ์ดํ)\")\n",
"print(f\"์๋๋ฆฌ์ค ํต๊ณผ {(res['ํต๊ณผ'] == 'PASS').sum()}/10\")\n",
"fa = nov[nov[\"pred\"] != nov[\"truth_etype\"]]\n",
"print(\"์ ์ค๋ต ์ค๋ถ๋ฅ(ํน์ง ์ฌ์ค๊ณ ๋์):\", [f\"{v}->{p}\" for v, p in zip(fa[\"val\"], fa[\"pred\"])])\n",
"\n",
"# ์ค๋ต ํจํด ๋ถ์: ๊ฐ์ธ๋ณ ์ฃผ๋ ์ค๋ต ์ ํ, ์ค๋๋ณ ๋ถํฌ\n",
"dom = wrong.groupby(\"sid\")[\"pred\"].agg(lambda s: s.value_counts().idxmax())\n",
"print(\"\\n๊ฐ์ธ๋ณ ์ฃผ๋ ์ค๋ต ์ ํ ๋ถํฌ:\", dom.value_counts().to_dict())\n",
"print(pd.crosstab(wrong[\"unit\"], wrong[\"pred\"], normalize=\"index\").round(2).to_string())\n",
"\n",
"ct = pd.crosstab(wrong[\"qid\"].map(lambda q: QIDX[q][\"area\"]), wrong[\"pred\"])\n",
"ct.index = ct.index.map({\"ํ์์๋ณ\": \"Shape ID\", \"ํผ์์๋ณ\": \"Friend/Foe\", \"๊ฒฝ๋ณด๋ณด๊ณ \": \"Alert/Report\", \"๊ต์ ํต์ \": \"Control\", \"๋๋๋ก ์ผ๋ฐ\": \"C-UAS basics\"})\n",
"ct.columns = ct.columns.map({\"๊ฐ๋
ํผ๋\": \"concept\", \"ํผ์ ์ค์ธ\": \"friend/foe\", \"์ ์ฐจ ๋๋ฝ\": \"procedure\"})\n",
"ct.plot(kind=\"barh\", stacked=True, figsize=(8, 4)); plt.xlabel(\"wrong answers\"); plt.title(\"Error types by area (synthetic)\"); plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
04 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
" ์๋๋ฆฌ์ค ๊ฑด์ ์ ํ๋ ๊ฐ๋
ํผ๋ ์ฌํ์จ ํผ์ ์ค์ธ ์ฌํ์จ ์ ์ฐจ ๋๋ฝ ์ฌํ์จ ํต๊ณผ\n",
" S1 ์ ๋คํ ํ์ค ํ๊ธฐ 367 1.000 1.0 1.0 1.000 PASS\n",
" S2 ์ ๋คํ ์๋ฌธ์ยท์ ๊ฐ ํ๊ธฐ 235 1.000 1.0 1.0 1.000 PASS\n",
" S3 ๋จ๋ต ์ค๋ต ๋ชฉ๋ก ์ผ์น 120 1.000 1.0 1.0 1.000 PASS\n",
" S4 ๋จ๋ต ์ค๋ต + ์คํ์ 148 1.000 1.0 1.0 1.000 PASS\n",
"S5 ๋จ๋ต ์ค๋ต + ์ด๋ฏธยท๋์ด์ฐ๊ธฐ 55 1.000 1.0 1.0 1.000 PASS\n",
" S6 ์์ํ ๋จ๊ณ ๋๋ฝ 90 1.000 - - 1.000 PASS\n",
" S7 ์์ํ ์์ ๋ค๋ฐ๋ 94 1.000 - - 1.000 PASS\n",
" S8 ์์ํ ๊ตฌ๋ถ์ ํผ์
81 1.000 - - 1.000 PASS\n",
" S9 ๋ชฉ๋ก์ ์๋ ์ ์ค๋ต 22 0.864 1.0 0.857 0.714 PASS\n",
" S10 ์ ์ฒด ํผํฉ 1104 0.997 1.0 0.995 0.996 PASS\n",
"\n",
"์ค๋ต ์ ํ๋ณ ์ฌํ์จ: {'๊ฐ๋
ํผ๋': 1.0, 'ํผ์ ์ค์ธ': 0.995, '์ ์ฐจ ๋๋ฝ': 0.996} (๋ชฉํ 85% ์ด์: ๋ฌ์ฑ), ๊ฑด๋น ์ฒ๋ฆฌ 0.005 ms (๋ชฉํ 200 ms ์ดํ)\n",
"์๋๋ฆฌ์ค ํต๊ณผ 10/10\n",
"์ ์ค๋ต ์ค๋ถ๋ฅ(ํน์ง ์ฌ์ค๊ณ ๋์): ['๋์ถฉ๋ณด๊ณ ->๊ฐ๋
ํผ๋', '๋ฏธํ์ธํ์ ์ผ๋ก๋์->์ ์ฐจ ๋๋ฝ', '๋ถ๋์๋๊ตฌ๋->๊ฐ๋
ํผ๋']\n",
"\n",
"๊ฐ์ธ๋ณ ์ฃผ๋ ์ค๋ต ์ ํ ๋ถํฌ: {'์ ์ฐจ ๋๋ฝ': 60, '๊ฐ๋
ํผ๋': 42, 'ํผ์ ์ค์ธ': 12}\n",
"pred ๊ฐ๋
ํผ๋ ์ ์ฐจ ๋๋ฝ ํผ์ ์ค์ธ\n",
"unit \n",
"๊ฐ์1์ค๋ 0.36 0.46 0.18\n",
"๊ฐ์2์ค๋ 0.40 0.41 0.19\n",
"๊ฐ์3์ค๋ 0.34 0.49 0.17\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
05. ๊ฐ์ธ๋ณ/๋ถ๋๋ณ ๋ฐฉ๊ณต ์ทจ์ฝ ์์ญ ์๋ณ ๋ฐ ๋ง์ถคํ ๋ณด์ถฉ ํ์ต ์ถ์ฒ ์๊ณ ๋ฆฌ์ฆ ๊ฐ๋ฐ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๊ฐ์ธ๋ณ/๋ถ๋๋ณ ๋ฐฉ๊ณต ์ทจ์ฝ ์์ญ ์๋ณ ๋ฐ ๋ง์ถคํ ๋ณด์ถฉ ํ์ต ์ถ์ฒ ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๋ฐํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ฑ์ ๊ฒฐ๊ณผ๋ก ๊ฐ์ธ๋ณยท์ค๋๋ณ ๋ฐฉ๊ณต ์ทจ์ฝ ์์ญ์ ์ฐพ๊ณ , ๋ถํ์ํ ์ถ์ฒ์ ์ค์ธ ๋ง์ถคํ ๋ณด์ถฉ ํ์ต ์ถ์ฒ ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๋ฐํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๋ฌธํญ ๋์ด๋๋ฅผ ๋ฐ์ํ ๋ฒ ์ด์ฆ ์ถ์ ์ผ๋ก ์์ญ๋ณ '์ทจ์ฝํ ํ๋ฅ '์ ๊ณ์ฐํ๊ณ High/Medium/Low ์ ๋ขฐ๋ ๋ฑ๊ธ์ ๋ถ์ฌ ์ํํ๊ณ ๊ฒ์ฆํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/verification/trn02_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": [
"\"\"\"์ฑ์ ๊ฒฐ๊ณผ๋ก ๊ฐ์ธ๋ณยท์ค๋๋ณ ๋ฐฉ๊ณต ์ทจ์ฝ ์์ญ์ ์ฐพ์ ๋ณด์ถฉ ํ์ต์ ์ถ์ฒํ๋ ์๊ณ ๋ฆฌ์ฆ์ ๊ฐ๋ฐํ๋ค. ๋ฌธํญ ๋์ด๋๋ฅผ ๋ฐ์ํ ๋ฒ ์ด์ฆ ์ถ์ ์ผ๋ก ์์ญ๋ณ '์ทจ์ฝํ ํ๋ฅ '๊ณผ ์ ๋ขฐ๋ ๋ฑ๊ธ์ ์ฐ์ถํ๊ณ , ํ๋ฆฐ ๋ฌธํญ์ด ์์ผ๋ฉด ๋ชจ๋ ์ถ์ฒํ๋ ๋ฐฉ์ ๋๋น ๋ถํ์ํ ์ถ์ฒ(์ค๊ฒฝ๋ณด) ๊ฐ์ถยท๊ฒฐ์ ์ผ๊ด์ฑยทROC-AUC๋ฅผ ๊ฒ์ฆํ๋ค.\"\"\"\n",
"import difflib\n",
"import re\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from scipy import stats\n",
"from sklearn.metrics import roc_auc_score\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฐฉ๊ณตยท๋๋ก ์๋ณ ํด์ฆ ๋ฌธํญ ์ํ๊ณผ ๊ฐ์ ์์์ ์๋ต ์์ฑ๊ธฐ\n",
"# ๋ฌธํญ์ ์ผ๋ฐ ์์น ์์ค์ ๊ฐ์ ๋ฌธํญ์ด๋ฉฐ ์ค์ ๊ต๋ฒยท๊ต์ ๊ท์น ์๋ฌธ์ด ์๋๋ค. ์์์๋ ๊ฐ๋ช
(S001~)์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"AREAS = [\"ํ์์๋ณ\", \"ํผ์์๋ณ\", \"๊ฒฝ๋ณด๋ณด๊ณ \", \"๊ต์ ํต์ \", \"๋๋๋ก ์ผ๋ฐ\"]\n",
"ETYPES = [\"๊ฐ๋
ํผ๋\", \"ํผ์ ์ค์ธ\", \"์ ์ฐจ ๋๋ฝ\"]\n",
"POINTS = {1: 2, 2: 3, 3: 5}\n",
"C, F, P = ETYPES\n",
"\n",
"\n",
"def _mc(qid, area, diff, stem, opts, ans, dist):\n",
" return {\"qid\": qid, \"type\": \"MC\", \"area\": area, \"diff\": diff, \"stem\": stem, \"options\": opts, \"answer\": ans,\n",
" \"dist\": dict(zip([i for i in range(1, 5) if i != ans], dist))}\n",
"\n",
"\n",
"def _sa(qid, area, diff, stem, accept, wrong):\n",
" return {\"qid\": qid, \"type\": \"SA\", \"area\": area, \"diff\": diff, \"stem\": stem, \"accept\": accept, \"wrong\": wrong}\n",
"\n",
"\n",
"def _order(qid, area, diff, stem):\n",
" return {\"qid\": qid, \"type\": \"ORDER\", \"area\": area, \"diff\": diff, \"stem\": stem, \"answer\": \"ABCD\"}\n",
"\n",
"\n",
"BANK = [\n",
" _mc(\"Q01\", \"ํ์์๋ณ\", 1, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์์ด ์ผ๊ฐํ ๋ ๊ฐ๋ฅผ ๊ฐ์ง ์ํ ๊ณ ์ ์ต ์ค๋ฃจ์ฃ์?\", [\"์ฟผ๋์ฝฅํฐํ\", \"์ผ๊ฐ์ต ๊ณ ์ ์ตํ\", \"ํ์ ์ต ํฌ๊ธฐํ\", \"์กฐ๋ฅ\"], 2, [C, C, F]),\n",
" _mc(\"Q02\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๊ฐ ์ญ์๋ก ๋ฐฐ์น๋ ์ค๋ฃจ์ฃ์?\", [\"๊ณ ์ ์ต ๋๋ก ํ\", \"์ฟผ๋์ฝฅํฐํ\", \"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\"], 2, [C, C, C]),\n",
" _mc(\"Q03\", \"ํ์์๋ณ\", 2, \"๊ฐ๋๊ณ ๊ธด ๋์ฒด ์ค๊ฐ์ ์์ ๋ ๊ฐ๊ฐ ๋ถ์ ์ค๋ฃจ์ฃ์?\", [\"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\", \"๋ฏผ๊ฐ ๊ฒฝ๋นํ๊ธฐํ\", \"์กฐ๋ฅ\"], 1, [C, F, F]),\n",
" _mc(\"Q04\", \"ํ์์๋ณ\", 3, \"์๊ฑฐ๋ฆฌ๋ผ ์ค๋ฃจ์ฃ ์๋ณ์ด ์ด๋ ค์ธ ๋ ๋จผ์ ํ ํ๋์?\", [\"์ถ์ ์ ํ์ผ๋ก ํ์ ๋ณด๊ณ \", \"๊ด์ธก ์ ๋ณด๋ฅผ ๊ทธ๋๋ก ๋ณด๊ณ ํ๊ณ ์ถ๊ฐ ๊ด์ธก\", \"๊ด์ธก ์ค๋จ\", \"๋ฏผ๊ฐ๊ธฐ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _mc(\"Q05\", \"ํผ์์๋ณ\", 2, \"์๋ณ ์ ํธ ์๋ต์ด ์๋ ํญ๊ณต๊ธฐ์ ๋ํ ์ฌ๋ฐ๋ฅธ ํ๋จ์?\", [\"์ฆ์ ์ ๊ธฐ๋ก ํ์ \", \"๋ฏธํ์ธ ํญ๊ณต๊ธฐ๋ก ๋ถ๋ฅํ๊ณ ์ถ๊ฐ ํ์ธ\", \"์๊ตฐ๊ธฐ๋ก ๊ฐ์ฃผ\", \"๋ฌด์\"], 2, [F, F, P]),\n",
" _mc(\"Q06\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด์ ์ผ์นํ๋ ์ ์ ๋นํ์ฒด์ ๋ถ๋ฅ๋?\", [\"์ ๋๋ก \", \"๋ฏผ๊ฐ๊ธฐ ๊ฐ๋ฅ์ฑ ๋์, ์ถ๊ฐ ํ์ธ\", \"์ํญ๋ฏธ์ฌ์ผ\", \"์๊ตฐ ์ ์ฐฐ๊ธฐ๋ก ํ์ \"], 2, [F, C, F]),\n",
" _mc(\"Q07\", \"ํผ์์๋ณ\", 3, \"์๊ตฐ ๋๋ก ์ด์ฉ ๊ณต์ง๊ฐ ์๋ ๊ณต์ญ์์ ๋ฏธํ์ธ ๋๋ก ์ ๋ณด๋ฉด ์ฐ์ ํ์ธํ ๊ฒ์?\", [\"๊ธฐ์ ์ ๋ณด\", \"์๊ตฐ ์ด์ฉ ๊ณํ๊ณผ ์๋ณ ์ ํธ\", \"๋์ ์ค๋น ์ํ\", \"์ฃผ๋ณ ์งํ\"], 2, [C, P, C]),\n",
" _mc(\"Q08\", \"ํผ์์๋ณ\", 3, \"์กฐ๋ฅ ๋ฌด๋ฆฌ์ ์ํ ๋๋ก ๊ตฐ์ง ๊ตฌ๋ถ์ ๊ฐ์ฅ ๋์์ด ๋๋ ์ ๋ณด๋?\", [\"ํฌ๊ธฐ๋ง ๋น๊ต\", \"๊ถค์ ์ ๊ท์น์ฑ๊ณผ ์ด ์ ํธ\", \"์์\", \"๊ด์ธก ์๊ฐ\"], 2, [C, C, C]),\n",
" _mc(\"Q09\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๊ณต์ค ์ํ ์ต์ด ๋ณด๊ณ ์ ํฌํจํ์ง ์์๋ ๋๋ ๊ฒ์?\", [\"๊ด์ธก ์๊ฐ\", \"๋ฐฉํฅ๊ณผ ๊ฑฐ๋ฆฌ\", \"์ถ์ ์ ํ\", \"๊ด์ธก์ ๊ฐ์ธ ์ฐ๋ฝ์ฒ\"], 4, [P, P, P]),\n",
" _mc(\"Q10\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๊ฒฝ๋ณด ์ ํ ํ ์ํฉ์ด ๋ฐ๋๋ฉด?\", [\"์ต์ด ๋ณด๊ณ ์ ์ง\", \"๋ณ๊ฒฝ ์ฌํญ์ ์ฆ์ ์ถ๊ฐ ๋ณด๊ณ \", \"์ํฉ ์ข
๋ฃ ํ ์ผ๊ด ๋ณด๊ณ \", \"๊ฐ์ธ ํ๋จ์ผ๋ก ๊ฒฝ๋ณด ํด์ \"], 2, [P, P, P]),\n",
" _mc(\"Q11\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ณด๊ณ ๋ด์ฉ์ด ๋ถํ์คํ ๋ ์ฌ๋ฐ๋ฅธ ํํ์?\", [\"ํ์ ํํ์ผ๋ก ๋ณด๊ณ \", \"'์ถ์ '์์ ๋ฐํ๊ณ ๊ทผ๊ฑฐ์ ํจ๊ป ๋ณด๊ณ \", \"๋ณด๊ณ ๋ณด๋ฅ\", \"๋ค๋ฅธ ๊ด์ธก์ ๋ณด๊ณ ๋ฅผ ๋ณต์ฌ\"], 2, [C, P, P]),\n",
" _mc(\"Q12\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋๋ก ํ์ง ํ ๋ณด๊ณ ๊ฒฝ๋ก๋ก ์๋ง์ ๊ฒ์?\", [\"์ธ์ ๋ถ๋์๋ง ์ ํ\", \"์งํ๊ณํต์ ๋ฐ๋ผ ๋ณด๊ณ \", \"๊ฐ์ธ ๋ฉ์ ์ ๋ก ๊ณต์ \", \"๋ณด๊ณ ์์ด ๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q13\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ๋ง์ผ๋ก ๋์์ ๊ฒฐ์ ํ ์ ์๋๊ฐ?\", [\"๊ฐ๋ฅํ๋ค\", \"์ธ๊ฐ ์งํ๊ด์ ์น์ธ์ด ํ์ํ๋ค\", \"์ ๋ขฐ๋ 90% ์ด์์ด๋ฉด ๊ฐ๋ฅ\", \"์ผ๊ฐ์๋ง ๊ฐ๋ฅ\"], 2, [C, C, C]),\n",
" _mc(\"Q14\", \"๊ต์ ํต์ \", 2, \"๋ฏผ๊ฐ ํผํด ๊ฐ๋ฅ์ฑ์ด ํ์ธ๋๋ฉด?\", [\"๊ทธ๋๋ก ์งํ\", \"์ฆ์ ๋ณด๊ณ ํ๊ณ ์งํ๊ด ํ๋จ์ ๋ฐ๋๋ค\", \"์กฐ์น ํ ๋ณด๊ณ \", \"๋ฌด์\"], 2, [P, P, F]),\n",
" _mc(\"Q15\", \"๊ต์ ํต์ \", 3, \"์๋ณ์ด ๋ถํ์คํ ํ์ ์ ๋ํ ์์น์?\", [\"์ํ์ผ๋ก ๊ฐ์ฃผํด ์ฆ์ ๋์\", \"์๋ณ ํ์ธ ์ ๊น์ง ์น์ธ ์ ์ฐจ๋ฅผ ๋ฐ๋ฅธ๋ค\", \"๋ฌด์กฐ๊ฑด ๋ฌด์\", \"๋ค์๊ฒฐ๋ก ๊ฒฐ์ \"], 2, [F, F, C]),\n",
" _mc(\"Q16\", \"๊ต์ ํต์ \", 2, \"ํ๋จ ๊ทผ๊ฑฐ๋ฅผ ๊ธฐ๋กํด์ผ ํ๋ ์ด์ ๋?\", [\"ํ์์ ํ์\", \"์ฌํ ๊ฒํ ์ ์ฑ
์ ์ถ์ \", \"๋ณด๊ณ ์๋ต\", \"๊ธฐ๋ก ๋ถํ์\"], 2, [C, P, P]),\n",
" _mc(\"Q17\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"๋๋๋ก ์ฒด๊ณ์ ์ผ๋ฐ์ ์ฒ๋ฆฌ ์์๋?\", [\"๋์-ํ์ง-์๋ณ\", \"ํ์ง-์๋ณ-๋ณด๊ณ ยทํ๋จ-๋์\", \"์๋ณ-๋์-ํ์ง\", \"๋ณด๊ณ -ํ์ง-๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q18\", \"๋๋๋ก ์ผ๋ฐ\", 3, \"๋ ์ด๋๋ง์ผ๋ก ์กฐ๋ฅ์ ๋๋ก ๊ตฌ๋ถ์ด ์ด๋ ค์ด ์ด์ ๋?\", [\"๋ฐ์ฌ ์ ํธ ํฌ๊ธฐ๊ฐ ๋น์ทํ ์ ์์ด์\", \"๋ ์ด๋๋ ์ผ๊ฐ์ ๋ชป ์จ์\", \"๋๋ก ์๋ ๊ธ์์ด ์์ด์\", \"์กฐ๋ฅ๋ ํ์ง๋์ง ์์์\"], 1, [C, C, C]),\n",
" _mc(\"Q19\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ ์๊ดํ(EO) ์ผ์์ ์ฝ์ ์ด ์ปค์ง๋ ์กฐ๊ฑด์?\", [\"๋ง์ ์ฃผ๊ฐ\", \"์๊ฐยท์ผ๊ฐ\", \"์ ์ ํ์ \", \"๊ทผ๊ฑฐ๋ฆฌ\"], 2, [C, C, C]),\n",
" _mc(\"Q20\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"๊ฒฝ๋ณด๊ฐ ๋ฐ๋ณตํด์ ์ค๊ฒฝ๋ณด๋ก ํ๋ช
๋๋ฉด?\", [\"๊ฒฝ๋ณด ์ฅ์น๋ฅผ ๋๋ค\", \"์ฌ๋ก๋ฅผ ๊ธฐ๋กยท๋ณด๊ณ ํ๊ณ ๊ธฐ์ค ์ฌ๊ฒํ ์์ฒญ\", \"๋ฌด์ํ๋ค\", \"๋ชจ๋ ์ค์ ์ํ์ผ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _sa(\"Q21\", \"ํผ์์๋ณ\", 1, \"์๊ตฐยท์ ยท๋ฏผ๊ฐ์ ๊ตฌ๋ถํ๋ ์ ์ฐจ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"ํผ์์๋ณ\", \"IFF\"], {\"ํ์ ํ๋\": C, \"์ ๊ธฐํ์ \": F, \"๊ฒฝ๊ณ\": C}),\n",
" _sa(\"Q22\", \"ํ์์๋ณ\", 2, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์๋ ์ผ๊ฐํ ๋ ๊ฐ ํํ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ผ๊ฐ์ต\", \"๋ธํ์ต\", \"delta\"], {\"์ง์ ์ต\": C, \"ํํด์ต\": C, \"์ฟผ๋์ฝฅํฐ\": C}),\n",
" _sa(\"Q23\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ถํ์คํ ๊ด์ธก์ ๋ณด๊ณ ํ ๋ ๋ถ์ด๋ ํํ์?\", [\"์ถ์ \"], {\"ํ์ \": C, \"ํ์ธ\": C, \"์๋ต\": P}),\n",
" _sa(\"Q24\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ์ ๋ํด ์ต์ข
๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ์ฃผ์ฒด๋?\", [\"์งํ๊ด\"], {\"AI\": C, \"์ด์ฉ๋ณ\": P, \"๋ ์ด๋\": C}),\n",
" _sa(\"Q25\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"์กฐ๋ฅ๋ฅผ ๋๋ก ์ผ๋ก ์๋ชป ๊ฒฝ๋ณดํ๋ ๊ฒ์ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ค๊ฒฝ๋ณด\", \"์คํ\", \"false alarm\"], {\"๋ฏธํ\": C, \"์ฌ๊ฒฝ๋ณด\": C, \"์ ๊ธฐ\": F}),\n",
" _sa(\"Q26\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด๊ฐ ํ์ธ๋ ์ฌ๊ฐ๊ธฐ๋ ์ด๋ป๊ฒ ๋ถ๋ฅํ๋๊ฐ?\", [\"๋ฏผ๊ฐ๊ธฐ\", \"๋ฏผํญ๊ธฐ\"], {\"์ ๊ธฐ\": F, \"๋ฏธํ์ธ๊ธฐ\": P, \"์๊ตฐ๊ธฐ\": F}),\n",
" _sa(\"Q27\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๋ฅผ ๊ฐ์ง ๋๋ก ํํ๋?\", [\"์ฟผ๋์ฝฅํฐ\", \"quadcopter\"], {\"ํฅ์ฌ์ฝฅํฐ\": C, \"๊ณ ์ ์ต\": C, \"ํฌ๊ธฐ\": F}),\n",
" _sa(\"Q28\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋ณด๊ณ ๋ ๋ฌด์์ ๋ฐ๋ผ ์ฌ๋ผ๊ฐ๋๊ฐ?\", [\"์งํ๊ณํต\"], {\"์ธ์ ๋ถ๋\": P, \"๊ฐ์ธ๋ฉ์ ์ \": P, \"ํต์ ๋ง\": C}),\n",
" _order(\"Q29\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"์ต์ด ๋ณด๊ณ ์์: A ๊ด์ธก ์๊ฐ, B ๋ฐฉํฅยท๊ฑฐ๋ฆฌ, C ์ถ์ ์ ํ, D ๊ด์ธก์ ์กฐ์น\"),\n",
" _order(\"Q30\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ฒ๋ฆฌ ์์: A ํ์ง, B ์๋ณ, C ๋ณด๊ณ ยทํ๋จ, D ๋์\"),\n",
" _order(\"Q31\", \"๊ต์ ํต์ \", 3, \"ํต์ ์์: A ์๋ณ ํ์ธ, B ์๊ธ ์น์ธ ์์ฒญ, C ์น์ธ ์๋ น, D ์กฐ์น ๊ธฐ๋ก\"),\n",
" _order(\"Q32\", \"ํผ์์๋ณ\", 3, \"ํ์ธ ์์: A ์ดํญ์ ๋ณด ๋์กฐ, B ์๋ณ ์ ํธ ํ์ธ, C ๋ฏธํ์ธ ๋ถ๋ฅ, D ๋ณด๊ณ \"),\n",
"]\n",
"QIDX = {q[\"qid\"]: q for q in BANK}\n",
"UNITS = [\"๊ฐ์1์ค๋\", \"๊ฐ์2์ค๋\", \"๊ฐ์3์ค๋\"]\n",
"_CIRC = \"โ โกโขโฃ\"\n",
"PARA = {\"Q21\": \"์๊ตฐ์ ๊ตฐ ๊ตฌ๋ถ\", \"Q22\": \"์ธ๋ชจ ๋ ๊ฐ\", \"Q23\": \"์ถ์ ๋จ์ผ๋ก ํ๊ธฐ\", \"Q24\": \"์ค๋์ฅ(์งํ๊ด)\", \"Q25\": \"์๋ชป๋ ๊ฒฝ๋ณด\",\n",
" \"Q26\": \"๋ฏผ๊ฐ ์ฌ๊ฐ๊ธฐ\", \"Q27\": \"4๋ฐ ๋๋ก \", \"Q28\": \"์งํ ๋ผ์ธ\"} # ๋ป์ ๋ง์ง๋ง ์ธ์ ๋ต์ ๋ชฉ๋ก์ ์๋ ํํ\n",
"\n",
"\n",
"def make_soldiers(n_per_unit, seed):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for u, unit in enumerate(UNITS):\n",
" base = [0.3, -0.1, 0.0][u]\n",
" for i in range(n_per_unit):\n",
" off = rng.normal(0, 0.5, len(AREAS)) + (np.array([0, 0, 0, -0.6, 0]) if u == 1 else 0)\n",
" out.append({\"sid\": f\"S{len(out) + 1:03d}\", \"unit\": unit, \"theta\": dict(zip(AREAS, base + rng.normal(0, 0.6) + off)),\n",
" \"tend\": dict(zip(ETYPES, rng.dirichlet([1.2, 1.2, 1.2])))})\n",
" return out\n",
"\n",
"\n",
"def p_correct(theta, diff):\n",
" return 1 / (1 + np.exp(-1.7 * (theta - (diff - 2) * 0.9 + 0.9)))\n",
"\n",
"\n",
"def _typo(s, rng):\n",
" if len(s) < 2:\n",
" return s\n",
" i = int(rng.integers(len(s) - 1))\n",
" return s[:i] + s[i + 1] + s[i] + s[i + 2:] if len(s) <= 3 or rng.random() < 0.5 else s[:i] + s[i + 1:]\n",
"\n",
"\n",
"def _fmt_mc(k, rng):\n",
" r = rng.random()\n",
" if r < 0.55:\n",
" return str(k)\n",
" return rng.choice([_CIRC[k - 1], f\"{k}๋ฒ\", \"ABCD\"[k - 1], \"abcd\"[k - 1] + \")\", f\" {k} \", chr(0xFF10 + k), f\"{k}.\"])\n",
"\n",
"\n",
"def _fmt_sa(s, rng):\n",
" r = rng.random()\n",
" if r < 0.45:\n",
" return s\n",
" if r < 0.6:\n",
" return \" \".join(s) if len(s) <= 4 else s[:2] + \" \" + s[2:]\n",
" if r < 0.72:\n",
" return s + rng.choice([\"์
๋๋ค\", \"์ด๋ค\", \".\", \"์\"])\n",
" if r < 0.8:\n",
" return s.upper() if s.isascii() else \"\".join(chr(ord(ch) + 0xFEE0) if \"!\" <= ch <= \"~\" else ch for ch in s) + \" \"\n",
" return _typo(s, rng)\n",
"\n",
"\n",
"def answer(q, sol, rng):\n",
" \"\"\"ํ ๋ฌธํญ ์๋ต: (์๋ณธ ์๋ต ๋ฌธ์์ด, ์๋ํ ์ ๋ต ์ฌ๋ถ, ์๋ํ ๋ต, ์ค๋ต ์ ํ).\"\"\"\n",
" ok = rng.random() < p_correct(sol[\"theta\"][q[\"area\"]], q[\"diff\"])\n",
" if q[\"type\"] == \"MC\":\n",
" if ok:\n",
" k, et = q[\"answer\"], None\n",
" else:\n",
" ks = list(q[\"dist\"]); w = np.array([sol[\"tend\"][q[\"dist\"][k]] for k in ks])\n",
" k = int(rng.choice(ks, p=w / w.sum())); et = q[\"dist\"][k]\n",
" return _fmt_mc(k, rng), ok, str(k), et\n",
" if q[\"type\"] == \"SA\":\n",
" if ok and rng.random() < 0.06:\n",
" return PARA[q[\"qid\"]], ok, PARA[q[\"qid\"]], None\n",
" if ok:\n",
" s, et = str(rng.choice(q[\"accept\"])), None\n",
" else:\n",
" ws = list(q[\"wrong\"]); w = np.array([sol[\"tend\"][q[\"wrong\"][x]] for x in ws])\n",
" s = str(rng.choice(ws, p=w / w.sum())); et = q[\"wrong\"][s]\n",
" return _fmt_sa(s, rng), ok, s, et\n",
" if ok:\n",
" s = \"ABCD\"\n",
" elif rng.random() < 0.5:\n",
" s = \"ABCD\".replace(\"ABCD\"[rng.integers(1, 4)], \"\") # ๋จ๊ณ ํ๋ ๋น ๋จ๋ฆผ\n",
" else:\n",
" i = int(rng.integers(0, 3)); s = list(\"ABCD\"); s[i], s[i + 1] = s[i + 1], s[i]; s = \"\".join(s) # ์์ ๋ค๋ฐ๋\n",
" raw = s if rng.random() < 0.6 else rng.choice([\"-\".join(s), \" \".join(s), s.lower(), \",\".join(s)])\n",
" return raw, ok, s, (None if ok else P)\n",
"\n",
"\n",
"def make_responses(n_per_unit=40, seed=7, blank=0.02):\n",
" \"\"\"์์์ x ๋ฌธํญ ์๋ตํ(DataFrame)๋ฅผ ๋ง๋ ๋ค. truth_* ์ด์ ๊ต๊ด ๊ธฐ์ค ์ ๋ต(ํ๊ฐ์ฉ)์ด๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows = []\n",
" for sol in make_soldiers(n_per_unit, seed):\n",
" for q in BANK:\n",
" raw, ok, intended, et = answer(q, sol, rng)\n",
" if rng.random() < blank:\n",
" raw, ok, intended, et = \"\", False, \"\", None\n",
" rows.append({\"rid\": f\"R{len(rows):05d}\", \"sid\": sol[\"sid\"], \"unit\": sol[\"unit\"], \"qid\": q[\"qid\"], \"raw\": raw,\n",
" \"truth_correct\": ok, \"truth_answer\": intended, \"truth_etype\": et})\n",
" return pd.DataFrame(rows)\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์๋ต ์ ์ ยทํ์ฑยท์ฑ์ ๊ณตํต ํจ์ ----\n",
"TAILS = (\"์
๋๋ค\", \"์ด๋ค\", \"์\")\n",
"\n",
"\n",
"def norm_text(s):\n",
" \"\"\"์ ๊ฐ->๋ฐ๊ฐ(NFKC), ์๋ฌธ์, ๊ณต๋ฐฑยท๋ฌธ์ฅ๋ถํธ ์ ๊ฑฐ, ๋๋งบ์ ์ด๋ฏธ ์ ๊ฑฐ.\"\"\"\n",
" s = unicodedata.normalize(\"NFKC\", str(s)).strip().lower()\n",
" s = re.sub(r\"[\\s\\.\\,\\-\\(\\)ยท]\", \"\", s)\n",
" for t in TAILS:\n",
" if s.endswith(t) and len(s) > len(t) + 1:\n",
" s = s[:-len(t)]\n",
" return s\n",
"\n",
"\n",
"def parse(q, raw):\n",
" \"\"\"์๋ณธ ์๋ต -> (์ ์ ๊ฐ, ์ํ). ์ํ: ์ ์ / ๋ฏธ์๋ต / ํ์์ค๋ฅ\"\"\"\n",
" if raw is None or str(raw).strip() == \"\":\n",
" return \"\", \"๋ฏธ์๋ต\"\n",
" s = unicodedata.normalize(\"NFKC\", str(raw)).strip()\n",
" if q[\"type\"] == \"MC\":\n",
" t = re.sub(r\"[\\s๋ฒ\\.\\)]\", \"\", s).lower()\n",
" t = {\"a\": \"1\", \"b\": \"2\", \"c\": \"3\", \"d\": \"4\"}.get(t, t)\n",
" return (t, \"์ ์\") if t in (\"1\", \"2\", \"3\", \"4\") else (t, \"ํ์์ค๋ฅ\")\n",
" if q[\"type\"] == \"ORDER\":\n",
" t = re.sub(r\"[^A-Za-z]\", \"\", s).upper()\n",
" return (t, \"์ ์\") if t and set(t) <= set(\"ABCD\") and len(set(t)) == len(t) else (t, \"ํ์์ค๋ฅ\")\n",
" return norm_text(s), \"์ ์\"\n",
"\n",
"\n",
"def grade_exact(q, val):\n",
" if q[\"type\"] == \"MC\":\n",
" return val == str(q[\"answer\"])\n",
" if q[\"type\"] == \"ORDER\":\n",
" return val == q[\"answer\"]\n",
" return val in {norm_text(a) for a in q[\"accept\"]}\n",
"\n",
"\n",
"def grade_fuzzy(q, val, cut=0.75):\n",
" \"\"\"๋จ๋ตํ์ ์คํ์(๊ธ์ ์์ ๋ฐ๋ยทํ ๊ธ์ ๋๋ฝ)๋ฅผ ํ์ฉ(์ ์ฌ๋ cut ์ด์)ํ๋, ์๋ ค์ง ์ค๋ต๊ณผ ๋ ๊ฐ๊น์ฐ๋ฉด ์ค๋ต์ผ๋ก ๋ณธ๋ค.\"\"\"\n",
" if grade_exact(q, val) or q[\"type\"] != \"SA\" or len(val) < 2:\n",
" return grade_exact(q, val)\n",
" best = max(1.0 if sorted(val) == sorted(norm_text(a)) else difflib.SequenceMatcher(None, val, norm_text(a)).ratio() for a in q[\"accept\"])\n",
" worst = max(difflib.SequenceMatcher(None, val, norm_text(w)).ratio() for w in q[\"wrong\"])\n",
" return best >= cut and best > worst\n",
"\n",
"\n",
"def score_of(q, correct):\n",
" return POINTS[q[\"diff\"]] if correct else 0\n",
"\n",
"MASTERY = 0.6 # ์์ญ ์ ๋ต๋ฅ ์ด ์ด๋ณด๋ค ๋ฎ์ผ๋ฉด '์ทจ์ฝ'\n",
"HIGH, MEDIUM = 0.8, 0.4\n",
"GRID = np.linspace(-3, 3, 121)\n",
"PRIOR = stats.norm.pdf(GRID, 0, 1)\n",
"CUT = GRID[np.argmin(np.abs(p_correct(GRID, 2) - MASTERY))] # ์ค๊ฐ ๋์ด๋ ์ ๋ต๋ฅ 0.6์ ํด๋นํ๋ ์๋ฌ๋\n",
"\n",
"\n",
"def weak_prob(resp):\n",
" \"\"\"(๋์ด๋, ์ ๋ต ์ฌ๋ถ) ๋ชฉ๋ก -> ์๋ฌ๋ ์ฌํ๋ถํฌ์์ CUT ๋ฏธ๋ง์ผ ํ๋ฅ . ์ด๋ ค์ด ๋ฌธํญ ์ค๋ต์ ๋, ์ฌ์ด ๋ฌธํญ ์ค๋ต์ ๋ ํฌ๊ฒ ๋ฐ์๋๋ค.\"\"\"\n",
" post = PRIOR.copy()\n",
" for d, c in resp:\n",
" p = p_correct(GRID, d)\n",
" post *= p if c else 1 - p\n",
" return float(post[GRID < CUT].sum() / post.sum())\n",
"\n",
"\n",
"def area_table(df):\n",
" g = df.assign(d=df[\"qid\"].map(lambda q: QIDX[q][\"diff\"]))\n",
" t = g.groupby([\"sid\", \"unit\", \"area\"]).agg(wrong=(\"correct\", lambda s: int((~s).sum())),\n",
" resp=(\"d\", lambda s: list(zip(s, g.loc[s.index, \"correct\"])))).reset_index()\n",
" t[\"score\"] = [weak_prob(r) for r in t[\"resp\"]]\n",
" t[\"grade\"] = np.where(t[\"score\"] >= HIGH, \"High\", np.where(t[\"score\"] >= MEDIUM, \"Medium\", \"Low\"))\n",
" return t\n",
"\n",
"\n",
"df = make_responses(40, seed=7)\n",
"df[\"area\"] = df[\"qid\"].map(lambda q: QIDX[q][\"area\"])\n",
"df[\"correct\"] = [grade_fuzzy(QIDX[q], parse(QIDX[q], r)[0]) for q, r in zip(df[\"qid\"], df[\"raw\"])]\n",
"sol = {s[\"sid\"]: s for s in make_soldiers(40, 7)}\n",
"t = area_table(df)\n",
"t[\"truth_weak\"] = [p_correct(sol[s][\"theta\"][a], 2) < MASTERY for s, a in zip(t[\"sid\"], t[\"area\"])] # ๊ฐ์ ์ค์ ์๋ฌ๋(ํ๊ฐ์ฉ)\n",
"\n",
"\n",
"def report(name, rec):\n",
" fa = (rec & ~t[\"truth_weak\"]).sum()\n",
" return {\"๋ฐฉ์\": name, \"์ถ์ฒ ์\": int(rec.sum()), \"๋ถํ์ ์ถ์ฒ\": int(fa), \"์ค๊ฒฝ๋ณด์จ\": round(fa / max(rec.sum(), 1), 3),\n",
" \"์ทจ์ฝ ์์ญ ์ฌํ์จ\": round((rec & t[\"truth_weak\"]).sum() / t[\"truth_weak\"].sum(), 3)}\n",
"\n",
"\n",
"before = report(\"๊ธฐ์กด(์ค๋ต 1๊ฐ๋ผ๋ ์์ผ๋ฉด ์ถ์ฒ)\", t[\"wrong\"] >= 1)\n",
"after = report(\"๋์ด๋ ๋ฐ์ ์ถ์ (Medium ์ด์)\", t[\"grade\"] != \"Low\")\n",
"print(f\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ์์์ 120๋ช
x ์์ญ 5๊ฐ = {len(t)}๊ฑด, ์ค์ ์ทจ์ฝ {int(t['truth_weak'].sum())}๊ฑด\")\n",
"print(pd.DataFrame([before, after]).to_string(index=False))\n",
"cut = 1 - after[\"์ค๊ฒฝ๋ณด์จ\"] / before[\"์ค๊ฒฝ๋ณด์จ\"]\n",
"retest = ((t[\"grade\"] == \"Low\") & (t[\"score\"] >= 0.2)).sum()\n",
"print(f\"Low ๋ฑ๊ธ ์ค ์ทจ์ฝ ํ๋ฅ 0.2 ์ด์ {retest}๊ฑด์ ์ถ์ฒ ๋์ '์ถ๊ฐ ๋ฌธํญ ์ถ์ ํ ์ฌํ๋จ' ๋์์ผ๋ก ๋๋ค.\")\n",
"print(f\"\\n์ค๊ฒฝ๋ณด์จ ๊ฐ์ถ {cut:.1%} (๋ชฉํ 40% ์ด์: {'๋ฌ์ฑ' if cut >= 0.4 else '๋ฏธ๋ฌ'})\")\n",
"\n",
"# ์ผ๊ด์ฑ: ์์ญ๋ง๋ค ์๋ต 1๊ฑด์ฉ ๋นผ๊ณ ๋ค์ ๊ณ์ฐํด๋ ์ถ์ฒ ๊ฒฐ์ ์ด ์ ์ง๋๋ ๋น์จ\n",
"rng = np.random.default_rng(0)\n",
"drop = df.groupby([\"sid\", \"area\"]).sample(1, random_state=1).index\n",
"t2 = area_table(df.drop(drop))\n",
"cons = ((t2[\"grade\"] != \"Low\").values == (t[\"grade\"] != \"Low\").values).mean()\n",
"print(f\"์ถ์ฒ ๊ฒฐ์ ์ผ๊ด์ฑ(์๋ต 1๊ฑด ์ ์ธ ์ฌ๊ณ์ฐ) {cons:.1%} (๋ชฉํ 90% ์ด์: {'๋ฌ์ฑ' if cons >= 0.9 else '๋ฏธ๋ฌ'})\")\n",
"auc = roc_auc_score(t[\"truth_weak\"], t[\"score\"])\n",
"print(f\"์ทจ์ฝ ํ๋ฅ ์ ์ ROC-AUC {auc:.3f}\")\n",
"print(\"[์๊ณ๊ฐ ๋ฏผ๊ฐ๋]\", \", \".join(f\"{m}: ์ค๊ฒฝ๋ณด์จ {report('', t['score'] >= m)['์ค๊ฒฝ๋ณด์จ']:.2f}/์ฌํ์จ {report('', t['score'] >= m)['์ทจ์ฝ ์์ญ ์ฌํ์จ']:.2f}\" for m in [0.3, 0.4, 0.5, 0.7]))\n",
"\n",
"# ๊ฐ์ธ ๋ณด์ถฉ ํ์ต ์ถ์ฒ ์์\n",
"s0 = t[t[\"grade\"] != \"Low\"].sort_values(\"score\", ascending=False)[\"sid\"].iloc[0]\n",
"print(f\"\\n[๊ฐ์ธ ์ถ์ฒ ์์: {s0}]\")\n",
"for r in t[(t[\"sid\"] == s0) & (t[\"grade\"] != \"Low\")].itertuples():\n",
" wrong_q = df[(df[\"sid\"] == s0) & (df[\"area\"] == r.area) & ~df[\"correct\"]][\"qid\"].tolist()\n",
" easy = [q[\"qid\"] for q in BANK if q[\"area\"] == r.area and q[\"diff\"] == 1]\n",
" print(f\" - {r.area}: ์ทจ์ฝ ํ๋ฅ {r.score:.2f}({r.grade}), ์ค๋ต {wrong_q} -> ๊ธฐ์ด ๋ฌธํญ {easy} ์ฌํ์ต ํ ์ฌํ๊ฐ\")\n",
"\n",
"# ์ค๋๋ณ ์ทจ์ฝ ์์ญ: ์์ ์ธ์ 30% ์ด์์ด ์ทจ์ฝ(Medium ์ด์)์ด๋ฉด ์ค๋ ๋จ์ ๋ณด์ถฉ ๊ต์ก ๊ถ๊ณ \n",
"u = t.assign(rec=t[\"grade\"] != \"Low\").pivot_table(index=\"unit\", columns=\"area\", values=\"rec\", aggfunc=\"mean\").round(2)\n",
"print(\"\\n[์ค๋๋ณ ์ทจ์ฝ ์ธ์ ๋น์จ]\")\n",
"print(u.to_string())\n",
"print(\"์ค๋ ๋จ์ ๋ณด์ถฉ ๊ต์ก ๊ถ๊ณ :\", [f\"{i}-{c}\" for i in u.index for c in u.columns if u.loc[i, c] >= 0.3])\n",
"print(\"์ถ์ฒ์ ํ์ต ๋ณด์กฐ ์ ๋ณด์ด๋ฉฐ ๊ฐ์ธ ํ๊ฐยท์ธ์ฌ ์๋ฃ๋ก ์ฐ์ง ์๋๋ค.\")\n",
"\n",
"fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n",
"for g, col in [(\"High\", \"tab:red\"), (\"Medium\", \"tab:orange\"), (\"Low\", \"tab:green\")]:\n",
" ax[0].hist(t[t[\"grade\"] == g][\"score\"], bins=20, range=(0, 1), color=col, alpha=.7, label=g)\n",
"ax[0].set_title(\"Weakness probability by grade\"); ax[0].set_xlabel(\"P(accuracy < 0.6)\"); ax[0].legend()\n",
"im = ax[1].imshow(u.values, cmap=\"Reds\", vmin=0, vmax=1)\n",
"ax[1].set_xticks(range(5)); ax[1].set_xticklabels([\"Shape\", \"IFF\", \"Report\", \"Control\", \"C-UAS\"][:0] or [{\"ํ์์๋ณ\": \"Shape\", \"ํผ์์๋ณ\": \"IFF\", \"๊ฒฝ๋ณด๋ณด๊ณ \": \"Report\", \"๊ต์ ํต์ \": \"Control\", \"๋๋๋ก ์ผ๋ฐ\": \"C-UAS\"}[c] for c in u.columns])\n",
"ax[1].set_yticks(range(3)); ax[1].set_yticklabels([\"Co.1\", \"Co.2\", \"Co.3\"]); plt.colorbar(im, ax=ax[1]); ax[1].set_title(\"Share of weak soldiers by unit/area\")\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
05 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ์์์ 120๋ช
x ์์ญ 5๊ฐ = 600๊ฑด, ์ค์ ์ทจ์ฝ 120๊ฑด\n",
" ๋ฐฉ์ ์ถ์ฒ ์ ๋ถํ์ ์ถ์ฒ ์ค๊ฒฝ๋ณด์จ ์ทจ์ฝ ์์ญ ์ฌํ์จ\n",
" ๊ธฐ์กด(์ค๋ต 1๊ฐ๋ผ๋ ์์ผ๋ฉด ์ถ์ฒ) 444 324 0.730 1.000\n",
"๋์ด๋ ๋ฐ์ ์ถ์ (Medium ์ด์) 148 62 0.419 0.717\n",
"Low ๋ฑ๊ธ ์ค ์ทจ์ฝ ํ๋ฅ 0.2 ์ด์ 96๊ฑด์ ์ถ์ฒ ๋์ '์ถ๊ฐ ๋ฌธํญ ์ถ์ ํ ์ฌํ๋จ' ๋์์ผ๋ก ๋๋ค.\n",
"\n",
"์ค๊ฒฝ๋ณด์จ ๊ฐ์ถ 42.6% (๋ชฉํ 40% ์ด์: ๋ฌ์ฑ)\n",
"์ถ์ฒ ๊ฒฐ์ ์ผ๊ด์ฑ(์๋ต 1๊ฑด ์ ์ธ ์ฌ๊ณ์ฐ) 91.2% (๋ชฉํ 90% ์ด์: ๋ฌ์ฑ)\n",
"์ทจ์ฝ ํ๋ฅ ์ ์ ROC-AUC 0.898\n",
"[์๊ณ๊ฐ ๋ฏผ๊ฐ๋] 0.3: ์ค๊ฒฝ๋ณด์จ 0.50/์ฌํ์จ 0.82, 0.4: ์ค๊ฒฝ๋ณด์จ 0.42/์ฌํ์จ 0.72, 0.5: ์ค๊ฒฝ๋ณด์จ 0.35/์ฌํ์จ 0.57, 0.7: ์ค๊ฒฝ๋ณด์จ 0.15/์ฌํ์จ 0.47\n",
"\n",
"[๊ฐ์ธ ์ถ์ฒ ์์: S098]\n",
" - ๊ฒฝ๋ณด๋ณด๊ณ : ์ทจ์ฝ ํ๋ฅ 1.00(High), ์ค๋ต ['Q09', 'Q10', 'Q11', 'Q12', 'Q23', 'Q28', 'Q29'] -> ๊ธฐ์ด ๋ฌธํญ ['Q09', 'Q12', 'Q28'] ์ฌํ์ต ํ ์ฌํ๊ฐ\n",
" - ๋๋๋ก ์ผ๋ฐ: ์ทจ์ฝ ํ๋ฅ 0.87(High), ์ค๋ต ['Q18', 'Q19', 'Q25', 'Q30'] -> ๊ธฐ์ด ๋ฌธํญ ['Q17', 'Q25'] ์ฌํ์ต ํ ์ฌํ๊ฐ\n",
"\n",
"[์ค๋๋ณ ์ทจ์ฝ ์ธ์ ๋น์จ]\n",
"area ๊ฒฝ๋ณด๋ณด๊ณ ๊ต์ ํต์ ๋๋๋ก ์ผ๋ฐ ํผ์์๋ณ ํ์์๋ณ\n",
"unit \n",
"๊ฐ์1์ค๋ 0.10 0.20 0.12 0.15 0.10\n",
"๊ฐ์2์ค๋ 0.25 0.57 0.25 0.38 0.25\n",
"๊ฐ์3์ค๋ 0.30 0.35 0.18 0.32 0.18\n",
"์ค๋ ๋จ์ ๋ณด์ถฉ ๊ต์ก ๊ถ๊ณ : ['๊ฐ์2์ค๋-๊ต์ ํต์ ', '๊ฐ์2์ค๋-ํผ์์๋ณ', '๊ฐ์3์ค๋-๊ฒฝ๋ณด๋ณด๊ณ ', '๊ฐ์3์ค๋-๊ต์ ํต์ ', '๊ฐ์3์ค๋-ํผ์์๋ณ']\n",
"์ถ์ฒ์ ํ์ต ๋ณด์กฐ ์ ๋ณด์ด๋ฉฐ ๊ฐ์ธ ํ๊ฐยท์ธ์ฌ ์๋ฃ๋ก ์ฐ์ง ์๋๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
06. ๋ถ๋ ๋ฐฉ๊ณต ์ง์ ์์ค ํต๊ณ ๋ฐ ์ค๋ต ๋ถํฌ ๊ฐ์ํ ๊ต์์ ๋์๋ณด๋ ๊ตฌํ\n",
"**์์์๊ฐ**: ์ฝ 2.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๋ถ๋ ๋ฐฉ๊ณต ์ง์ ํต๊ณ ๋ฐ ์ค๋ต ๋ถํฌ ๊ฐ์ํ ๊ต์์ ๋์๋ณด๋๋ฅผ ๊ตฌํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๋ถ๋ ๋ฐฉ๊ณต ์ง์ ์์ค๊ณผ ์ค๋ต ๋ถํฌ๋ฅผ ํ๋์ ๋ณด์ฌ ์ฃผ๋ ๊ต์์ ๋์๋ณด๋๋ฅผ ๊ตฌํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ค๋x์์ญ ์ ๋ต๋ฅ ํํธ๋งต, ์ค๋๋ณ ์ค๋ต ์ ํ ๋น์จ, ์ ์ ๋ถํฌ์ ํฉ๊ฒฉ์ , ๊ณ ๋๋ ๋ฌธํญ ๋ชฉ๋ก ๋ฑ ๊ตฌ์ฑ์์ 4์ข
์ด์์ ๊ทธ๋ฆฐ๋ค ๊ณผ์ ์ ์งํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/ui/trn02_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": [
"\"\"\"์ฑ์ ยท์ค๋ต ๋ถ๋ฅ ๊ฒฐ๊ณผ๋ก ์ค๋๋ณ ์์ญ ์ ๋ต๋ฅ , ์ค๋ต ์ ํ ๋ถํฌ, ์ ์ ๋ถํฌ, ๋ฌธํญ ๋์ด๋ ๋ฑ ๊ต์์ ๋์๋ณด๋ ๊ตฌ์ฑ์์ 4์ข
์ด์์ ๊ทธ๋ฆฌ๊ณ , ์ ์๋ต ๋ฌถ์์ด ๋ค์ด์ฌ ๋ ์ง๊ณ ๊ฐฑ์ ์๊ฐ๊ณผ ํํฐ ์กฐํ ์๋ต ์๊ฐ์ ์ธก์ ํ๋ฉฐ ๊ฒฝ๋ณด ํ์ด๋ผ์ดํธ ๋์์ ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import difflib\n",
"import re\n",
"import time\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.pipeline import make_pipeline\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฐฉ๊ณตยท๋๋ก ์๋ณ ํด์ฆ ๋ฌธํญ ์ํ๊ณผ ๊ฐ์ ์์์ ์๋ต ์์ฑ๊ธฐ\n",
"# ๋ฌธํญ์ ์ผ๋ฐ ์์น ์์ค์ ๊ฐ์ ๋ฌธํญ์ด๋ฉฐ ์ค์ ๊ต๋ฒยท๊ต์ ๊ท์น ์๋ฌธ์ด ์๋๋ค. ์์์๋ ๊ฐ๋ช
(S001~)์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"AREAS = [\"ํ์์๋ณ\", \"ํผ์์๋ณ\", \"๊ฒฝ๋ณด๋ณด๊ณ \", \"๊ต์ ํต์ \", \"๋๋๋ก ์ผ๋ฐ\"]\n",
"ETYPES = [\"๊ฐ๋
ํผ๋\", \"ํผ์ ์ค์ธ\", \"์ ์ฐจ ๋๋ฝ\"]\n",
"POINTS = {1: 2, 2: 3, 3: 5}\n",
"C, F, P = ETYPES\n",
"\n",
"\n",
"def _mc(qid, area, diff, stem, opts, ans, dist):\n",
" return {\"qid\": qid, \"type\": \"MC\", \"area\": area, \"diff\": diff, \"stem\": stem, \"options\": opts, \"answer\": ans,\n",
" \"dist\": dict(zip([i for i in range(1, 5) if i != ans], dist))}\n",
"\n",
"\n",
"def _sa(qid, area, diff, stem, accept, wrong):\n",
" return {\"qid\": qid, \"type\": \"SA\", \"area\": area, \"diff\": diff, \"stem\": stem, \"accept\": accept, \"wrong\": wrong}\n",
"\n",
"\n",
"def _order(qid, area, diff, stem):\n",
" return {\"qid\": qid, \"type\": \"ORDER\", \"area\": area, \"diff\": diff, \"stem\": stem, \"answer\": \"ABCD\"}\n",
"\n",
"\n",
"BANK = [\n",
" _mc(\"Q01\", \"ํ์์๋ณ\", 1, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์์ด ์ผ๊ฐํ ๋ ๊ฐ๋ฅผ ๊ฐ์ง ์ํ ๊ณ ์ ์ต ์ค๋ฃจ์ฃ์?\", [\"์ฟผ๋์ฝฅํฐํ\", \"์ผ๊ฐ์ต ๊ณ ์ ์ตํ\", \"ํ์ ์ต ํฌ๊ธฐํ\", \"์กฐ๋ฅ\"], 2, [C, C, F]),\n",
" _mc(\"Q02\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๊ฐ ์ญ์๋ก ๋ฐฐ์น๋ ์ค๋ฃจ์ฃ์?\", [\"๊ณ ์ ์ต ๋๋ก ํ\", \"์ฟผ๋์ฝฅํฐํ\", \"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\"], 2, [C, C, C]),\n",
" _mc(\"Q03\", \"ํ์์๋ณ\", 2, \"๊ฐ๋๊ณ ๊ธด ๋์ฒด ์ค๊ฐ์ ์์ ๋ ๊ฐ๊ฐ ๋ถ์ ์ค๋ฃจ์ฃ์?\", [\"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\", \"๋ฏผ๊ฐ ๊ฒฝ๋นํ๊ธฐํ\", \"์กฐ๋ฅ\"], 1, [C, F, F]),\n",
" _mc(\"Q04\", \"ํ์์๋ณ\", 3, \"์๊ฑฐ๋ฆฌ๋ผ ์ค๋ฃจ์ฃ ์๋ณ์ด ์ด๋ ค์ธ ๋ ๋จผ์ ํ ํ๋์?\", [\"์ถ์ ์ ํ์ผ๋ก ํ์ ๋ณด๊ณ \", \"๊ด์ธก ์ ๋ณด๋ฅผ ๊ทธ๋๋ก ๋ณด๊ณ ํ๊ณ ์ถ๊ฐ ๊ด์ธก\", \"๊ด์ธก ์ค๋จ\", \"๋ฏผ๊ฐ๊ธฐ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _mc(\"Q05\", \"ํผ์์๋ณ\", 2, \"์๋ณ ์ ํธ ์๋ต์ด ์๋ ํญ๊ณต๊ธฐ์ ๋ํ ์ฌ๋ฐ๋ฅธ ํ๋จ์?\", [\"์ฆ์ ์ ๊ธฐ๋ก ํ์ \", \"๋ฏธํ์ธ ํญ๊ณต๊ธฐ๋ก ๋ถ๋ฅํ๊ณ ์ถ๊ฐ ํ์ธ\", \"์๊ตฐ๊ธฐ๋ก ๊ฐ์ฃผ\", \"๋ฌด์\"], 2, [F, F, P]),\n",
" _mc(\"Q06\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด์ ์ผ์นํ๋ ์ ์ ๋นํ์ฒด์ ๋ถ๋ฅ๋?\", [\"์ ๋๋ก \", \"๋ฏผ๊ฐ๊ธฐ ๊ฐ๋ฅ์ฑ ๋์, ์ถ๊ฐ ํ์ธ\", \"์ํญ๋ฏธ์ฌ์ผ\", \"์๊ตฐ ์ ์ฐฐ๊ธฐ๋ก ํ์ \"], 2, [F, C, F]),\n",
" _mc(\"Q07\", \"ํผ์์๋ณ\", 3, \"์๊ตฐ ๋๋ก ์ด์ฉ ๊ณต์ง๊ฐ ์๋ ๊ณต์ญ์์ ๋ฏธํ์ธ ๋๋ก ์ ๋ณด๋ฉด ์ฐ์ ํ์ธํ ๊ฒ์?\", [\"๊ธฐ์ ์ ๋ณด\", \"์๊ตฐ ์ด์ฉ ๊ณํ๊ณผ ์๋ณ ์ ํธ\", \"๋์ ์ค๋น ์ํ\", \"์ฃผ๋ณ ์งํ\"], 2, [C, P, C]),\n",
" _mc(\"Q08\", \"ํผ์์๋ณ\", 3, \"์กฐ๋ฅ ๋ฌด๋ฆฌ์ ์ํ ๋๋ก ๊ตฐ์ง ๊ตฌ๋ถ์ ๊ฐ์ฅ ๋์์ด ๋๋ ์ ๋ณด๋?\", [\"ํฌ๊ธฐ๋ง ๋น๊ต\", \"๊ถค์ ์ ๊ท์น์ฑ๊ณผ ์ด ์ ํธ\", \"์์\", \"๊ด์ธก ์๊ฐ\"], 2, [C, C, C]),\n",
" _mc(\"Q09\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๊ณต์ค ์ํ ์ต์ด ๋ณด๊ณ ์ ํฌํจํ์ง ์์๋ ๋๋ ๊ฒ์?\", [\"๊ด์ธก ์๊ฐ\", \"๋ฐฉํฅ๊ณผ ๊ฑฐ๋ฆฌ\", \"์ถ์ ์ ํ\", \"๊ด์ธก์ ๊ฐ์ธ ์ฐ๋ฝ์ฒ\"], 4, [P, P, P]),\n",
" _mc(\"Q10\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๊ฒฝ๋ณด ์ ํ ํ ์ํฉ์ด ๋ฐ๋๋ฉด?\", [\"์ต์ด ๋ณด๊ณ ์ ์ง\", \"๋ณ๊ฒฝ ์ฌํญ์ ์ฆ์ ์ถ๊ฐ ๋ณด๊ณ \", \"์ํฉ ์ข
๋ฃ ํ ์ผ๊ด ๋ณด๊ณ \", \"๊ฐ์ธ ํ๋จ์ผ๋ก ๊ฒฝ๋ณด ํด์ \"], 2, [P, P, P]),\n",
" _mc(\"Q11\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ณด๊ณ ๋ด์ฉ์ด ๋ถํ์คํ ๋ ์ฌ๋ฐ๋ฅธ ํํ์?\", [\"ํ์ ํํ์ผ๋ก ๋ณด๊ณ \", \"'์ถ์ '์์ ๋ฐํ๊ณ ๊ทผ๊ฑฐ์ ํจ๊ป ๋ณด๊ณ \", \"๋ณด๊ณ ๋ณด๋ฅ\", \"๋ค๋ฅธ ๊ด์ธก์ ๋ณด๊ณ ๋ฅผ ๋ณต์ฌ\"], 2, [C, P, P]),\n",
" _mc(\"Q12\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋๋ก ํ์ง ํ ๋ณด๊ณ ๊ฒฝ๋ก๋ก ์๋ง์ ๊ฒ์?\", [\"์ธ์ ๋ถ๋์๋ง ์ ํ\", \"์งํ๊ณํต์ ๋ฐ๋ผ ๋ณด๊ณ \", \"๊ฐ์ธ ๋ฉ์ ์ ๋ก ๊ณต์ \", \"๋ณด๊ณ ์์ด ๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q13\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ๋ง์ผ๋ก ๋์์ ๊ฒฐ์ ํ ์ ์๋๊ฐ?\", [\"๊ฐ๋ฅํ๋ค\", \"์ธ๊ฐ ์งํ๊ด์ ์น์ธ์ด ํ์ํ๋ค\", \"์ ๋ขฐ๋ 90% ์ด์์ด๋ฉด ๊ฐ๋ฅ\", \"์ผ๊ฐ์๋ง ๊ฐ๋ฅ\"], 2, [C, C, C]),\n",
" _mc(\"Q14\", \"๊ต์ ํต์ \", 2, \"๋ฏผ๊ฐ ํผํด ๊ฐ๋ฅ์ฑ์ด ํ์ธ๋๋ฉด?\", [\"๊ทธ๋๋ก ์งํ\", \"์ฆ์ ๋ณด๊ณ ํ๊ณ ์งํ๊ด ํ๋จ์ ๋ฐ๋๋ค\", \"์กฐ์น ํ ๋ณด๊ณ \", \"๋ฌด์\"], 2, [P, P, F]),\n",
" _mc(\"Q15\", \"๊ต์ ํต์ \", 3, \"์๋ณ์ด ๋ถํ์คํ ํ์ ์ ๋ํ ์์น์?\", [\"์ํ์ผ๋ก ๊ฐ์ฃผํด ์ฆ์ ๋์\", \"์๋ณ ํ์ธ ์ ๊น์ง ์น์ธ ์ ์ฐจ๋ฅผ ๋ฐ๋ฅธ๋ค\", \"๋ฌด์กฐ๊ฑด ๋ฌด์\", \"๋ค์๊ฒฐ๋ก ๊ฒฐ์ \"], 2, [F, F, C]),\n",
" _mc(\"Q16\", \"๊ต์ ํต์ \", 2, \"ํ๋จ ๊ทผ๊ฑฐ๋ฅผ ๊ธฐ๋กํด์ผ ํ๋ ์ด์ ๋?\", [\"ํ์์ ํ์\", \"์ฌํ ๊ฒํ ์ ์ฑ
์ ์ถ์ \", \"๋ณด๊ณ ์๋ต\", \"๊ธฐ๋ก ๋ถํ์\"], 2, [C, P, P]),\n",
" _mc(\"Q17\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"๋๋๋ก ์ฒด๊ณ์ ์ผ๋ฐ์ ์ฒ๋ฆฌ ์์๋?\", [\"๋์-ํ์ง-์๋ณ\", \"ํ์ง-์๋ณ-๋ณด๊ณ ยทํ๋จ-๋์\", \"์๋ณ-๋์-ํ์ง\", \"๋ณด๊ณ -ํ์ง-๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q18\", \"๋๋๋ก ์ผ๋ฐ\", 3, \"๋ ์ด๋๋ง์ผ๋ก ์กฐ๋ฅ์ ๋๋ก ๊ตฌ๋ถ์ด ์ด๋ ค์ด ์ด์ ๋?\", [\"๋ฐ์ฌ ์ ํธ ํฌ๊ธฐ๊ฐ ๋น์ทํ ์ ์์ด์\", \"๋ ์ด๋๋ ์ผ๊ฐ์ ๋ชป ์จ์\", \"๋๋ก ์๋ ๊ธ์์ด ์์ด์\", \"์กฐ๋ฅ๋ ํ์ง๋์ง ์์์\"], 1, [C, C, C]),\n",
" _mc(\"Q19\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ ์๊ดํ(EO) ์ผ์์ ์ฝ์ ์ด ์ปค์ง๋ ์กฐ๊ฑด์?\", [\"๋ง์ ์ฃผ๊ฐ\", \"์๊ฐยท์ผ๊ฐ\", \"์ ์ ํ์ \", \"๊ทผ๊ฑฐ๋ฆฌ\"], 2, [C, C, C]),\n",
" _mc(\"Q20\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"๊ฒฝ๋ณด๊ฐ ๋ฐ๋ณตํด์ ์ค๊ฒฝ๋ณด๋ก ํ๋ช
๋๋ฉด?\", [\"๊ฒฝ๋ณด ์ฅ์น๋ฅผ ๋๋ค\", \"์ฌ๋ก๋ฅผ ๊ธฐ๋กยท๋ณด๊ณ ํ๊ณ ๊ธฐ์ค ์ฌ๊ฒํ ์์ฒญ\", \"๋ฌด์ํ๋ค\", \"๋ชจ๋ ์ค์ ์ํ์ผ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _sa(\"Q21\", \"ํผ์์๋ณ\", 1, \"์๊ตฐยท์ ยท๋ฏผ๊ฐ์ ๊ตฌ๋ถํ๋ ์ ์ฐจ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"ํผ์์๋ณ\", \"IFF\"], {\"ํ์ ํ๋\": C, \"์ ๊ธฐํ์ \": F, \"๊ฒฝ๊ณ\": C}),\n",
" _sa(\"Q22\", \"ํ์์๋ณ\", 2, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์๋ ์ผ๊ฐํ ๋ ๊ฐ ํํ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ผ๊ฐ์ต\", \"๋ธํ์ต\", \"delta\"], {\"์ง์ ์ต\": C, \"ํํด์ต\": C, \"์ฟผ๋์ฝฅํฐ\": C}),\n",
" _sa(\"Q23\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ถํ์คํ ๊ด์ธก์ ๋ณด๊ณ ํ ๋ ๋ถ์ด๋ ํํ์?\", [\"์ถ์ \"], {\"ํ์ \": C, \"ํ์ธ\": C, \"์๋ต\": P}),\n",
" _sa(\"Q24\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ์ ๋ํด ์ต์ข
๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ์ฃผ์ฒด๋?\", [\"์งํ๊ด\"], {\"AI\": C, \"์ด์ฉ๋ณ\": P, \"๋ ์ด๋\": C}),\n",
" _sa(\"Q25\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"์กฐ๋ฅ๋ฅผ ๋๋ก ์ผ๋ก ์๋ชป ๊ฒฝ๋ณดํ๋ ๊ฒ์ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ค๊ฒฝ๋ณด\", \"์คํ\", \"false alarm\"], {\"๋ฏธํ\": C, \"์ฌ๊ฒฝ๋ณด\": C, \"์ ๊ธฐ\": F}),\n",
" _sa(\"Q26\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด๊ฐ ํ์ธ๋ ์ฌ๊ฐ๊ธฐ๋ ์ด๋ป๊ฒ ๋ถ๋ฅํ๋๊ฐ?\", [\"๋ฏผ๊ฐ๊ธฐ\", \"๋ฏผํญ๊ธฐ\"], {\"์ ๊ธฐ\": F, \"๋ฏธํ์ธ๊ธฐ\": P, \"์๊ตฐ๊ธฐ\": F}),\n",
" _sa(\"Q27\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๋ฅผ ๊ฐ์ง ๋๋ก ํํ๋?\", [\"์ฟผ๋์ฝฅํฐ\", \"quadcopter\"], {\"ํฅ์ฌ์ฝฅํฐ\": C, \"๊ณ ์ ์ต\": C, \"ํฌ๊ธฐ\": F}),\n",
" _sa(\"Q28\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋ณด๊ณ ๋ ๋ฌด์์ ๋ฐ๋ผ ์ฌ๋ผ๊ฐ๋๊ฐ?\", [\"์งํ๊ณํต\"], {\"์ธ์ ๋ถ๋\": P, \"๊ฐ์ธ๋ฉ์ ์ \": P, \"ํต์ ๋ง\": C}),\n",
" _order(\"Q29\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"์ต์ด ๋ณด๊ณ ์์: A ๊ด์ธก ์๊ฐ, B ๋ฐฉํฅยท๊ฑฐ๋ฆฌ, C ์ถ์ ์ ํ, D ๊ด์ธก์ ์กฐ์น\"),\n",
" _order(\"Q30\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ฒ๋ฆฌ ์์: A ํ์ง, B ์๋ณ, C ๋ณด๊ณ ยทํ๋จ, D ๋์\"),\n",
" _order(\"Q31\", \"๊ต์ ํต์ \", 3, \"ํต์ ์์: A ์๋ณ ํ์ธ, B ์๊ธ ์น์ธ ์์ฒญ, C ์น์ธ ์๋ น, D ์กฐ์น ๊ธฐ๋ก\"),\n",
" _order(\"Q32\", \"ํผ์์๋ณ\", 3, \"ํ์ธ ์์: A ์ดํญ์ ๋ณด ๋์กฐ, B ์๋ณ ์ ํธ ํ์ธ, C ๋ฏธํ์ธ ๋ถ๋ฅ, D ๋ณด๊ณ \"),\n",
"]\n",
"QIDX = {q[\"qid\"]: q for q in BANK}\n",
"UNITS = [\"๊ฐ์1์ค๋\", \"๊ฐ์2์ค๋\", \"๊ฐ์3์ค๋\"]\n",
"_CIRC = \"โ โกโขโฃ\"\n",
"PARA = {\"Q21\": \"์๊ตฐ์ ๊ตฐ ๊ตฌ๋ถ\", \"Q22\": \"์ธ๋ชจ ๋ ๊ฐ\", \"Q23\": \"์ถ์ ๋จ์ผ๋ก ํ๊ธฐ\", \"Q24\": \"์ค๋์ฅ(์งํ๊ด)\", \"Q25\": \"์๋ชป๋ ๊ฒฝ๋ณด\",\n",
" \"Q26\": \"๋ฏผ๊ฐ ์ฌ๊ฐ๊ธฐ\", \"Q27\": \"4๋ฐ ๋๋ก \", \"Q28\": \"์งํ ๋ผ์ธ\"} # ๋ป์ ๋ง์ง๋ง ์ธ์ ๋ต์ ๋ชฉ๋ก์ ์๋ ํํ\n",
"\n",
"\n",
"def make_soldiers(n_per_unit, seed):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for u, unit in enumerate(UNITS):\n",
" base = [0.3, -0.1, 0.0][u]\n",
" for i in range(n_per_unit):\n",
" off = rng.normal(0, 0.5, len(AREAS)) + (np.array([0, 0, 0, -0.6, 0]) if u == 1 else 0)\n",
" out.append({\"sid\": f\"S{len(out) + 1:03d}\", \"unit\": unit, \"theta\": dict(zip(AREAS, base + rng.normal(0, 0.6) + off)),\n",
" \"tend\": dict(zip(ETYPES, rng.dirichlet([1.2, 1.2, 1.2])))})\n",
" return out\n",
"\n",
"\n",
"def p_correct(theta, diff):\n",
" return 1 / (1 + np.exp(-1.7 * (theta - (diff - 2) * 0.9 + 0.9)))\n",
"\n",
"\n",
"def _typo(s, rng):\n",
" if len(s) < 2:\n",
" return s\n",
" i = int(rng.integers(len(s) - 1))\n",
" return s[:i] + s[i + 1] + s[i] + s[i + 2:] if len(s) <= 3 or rng.random() < 0.5 else s[:i] + s[i + 1:]\n",
"\n",
"\n",
"def _fmt_mc(k, rng):\n",
" r = rng.random()\n",
" if r < 0.55:\n",
" return str(k)\n",
" return rng.choice([_CIRC[k - 1], f\"{k}๋ฒ\", \"ABCD\"[k - 1], \"abcd\"[k - 1] + \")\", f\" {k} \", chr(0xFF10 + k), f\"{k}.\"])\n",
"\n",
"\n",
"def _fmt_sa(s, rng):\n",
" r = rng.random()\n",
" if r < 0.45:\n",
" return s\n",
" if r < 0.6:\n",
" return \" \".join(s) if len(s) <= 4 else s[:2] + \" \" + s[2:]\n",
" if r < 0.72:\n",
" return s + rng.choice([\"์
๋๋ค\", \"์ด๋ค\", \".\", \"์\"])\n",
" if r < 0.8:\n",
" return s.upper() if s.isascii() else \"\".join(chr(ord(ch) + 0xFEE0) if \"!\" <= ch <= \"~\" else ch for ch in s) + \" \"\n",
" return _typo(s, rng)\n",
"\n",
"\n",
"def answer(q, sol, rng):\n",
" \"\"\"ํ ๋ฌธํญ ์๋ต: (์๋ณธ ์๋ต ๋ฌธ์์ด, ์๋ํ ์ ๋ต ์ฌ๋ถ, ์๋ํ ๋ต, ์ค๋ต ์ ํ).\"\"\"\n",
" ok = rng.random() < p_correct(sol[\"theta\"][q[\"area\"]], q[\"diff\"])\n",
" if q[\"type\"] == \"MC\":\n",
" if ok:\n",
" k, et = q[\"answer\"], None\n",
" else:\n",
" ks = list(q[\"dist\"]); w = np.array([sol[\"tend\"][q[\"dist\"][k]] for k in ks])\n",
" k = int(rng.choice(ks, p=w / w.sum())); et = q[\"dist\"][k]\n",
" return _fmt_mc(k, rng), ok, str(k), et\n",
" if q[\"type\"] == \"SA\":\n",
" if ok and rng.random() < 0.06:\n",
" return PARA[q[\"qid\"]], ok, PARA[q[\"qid\"]], None\n",
" if ok:\n",
" s, et = str(rng.choice(q[\"accept\"])), None\n",
" else:\n",
" ws = list(q[\"wrong\"]); w = np.array([sol[\"tend\"][q[\"wrong\"][x]] for x in ws])\n",
" s = str(rng.choice(ws, p=w / w.sum())); et = q[\"wrong\"][s]\n",
" return _fmt_sa(s, rng), ok, s, et\n",
" if ok:\n",
" s = \"ABCD\"\n",
" elif rng.random() < 0.5:\n",
" s = \"ABCD\".replace(\"ABCD\"[rng.integers(1, 4)], \"\") # ๋จ๊ณ ํ๋ ๋น ๋จ๋ฆผ\n",
" else:\n",
" i = int(rng.integers(0, 3)); s = list(\"ABCD\"); s[i], s[i + 1] = s[i + 1], s[i]; s = \"\".join(s) # ์์ ๋ค๋ฐ๋\n",
" raw = s if rng.random() < 0.6 else rng.choice([\"-\".join(s), \" \".join(s), s.lower(), \",\".join(s)])\n",
" return raw, ok, s, (None if ok else P)\n",
"\n",
"\n",
"def make_responses(n_per_unit=40, seed=7, blank=0.02):\n",
" \"\"\"์์์ x ๋ฌธํญ ์๋ตํ(DataFrame)๋ฅผ ๋ง๋ ๋ค. truth_* ์ด์ ๊ต๊ด ๊ธฐ์ค ์ ๋ต(ํ๊ฐ์ฉ)์ด๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows = []\n",
" for sol in make_soldiers(n_per_unit, seed):\n",
" for q in BANK:\n",
" raw, ok, intended, et = answer(q, sol, rng)\n",
" if rng.random() < blank:\n",
" raw, ok, intended, et = \"\", False, \"\", None\n",
" rows.append({\"rid\": f\"R{len(rows):05d}\", \"sid\": sol[\"sid\"], \"unit\": sol[\"unit\"], \"qid\": q[\"qid\"], \"raw\": raw,\n",
" \"truth_correct\": ok, \"truth_answer\": intended, \"truth_etype\": et})\n",
" return pd.DataFrame(rows)\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์๋ต ์ ์ ยทํ์ฑยท์ฑ์ ๊ณตํต ํจ์ ----\n",
"TAILS = (\"์
๋๋ค\", \"์ด๋ค\", \"์\")\n",
"\n",
"\n",
"def norm_text(s):\n",
" \"\"\"์ ๊ฐ->๋ฐ๊ฐ(NFKC), ์๋ฌธ์, ๊ณต๋ฐฑยท๋ฌธ์ฅ๋ถํธ ์ ๊ฑฐ, ๋๋งบ์ ์ด๋ฏธ ์ ๊ฑฐ.\"\"\"\n",
" s = unicodedata.normalize(\"NFKC\", str(s)).strip().lower()\n",
" s = re.sub(r\"[\\s\\.\\,\\-\\(\\)ยท]\", \"\", s)\n",
" for t in TAILS:\n",
" if s.endswith(t) and len(s) > len(t) + 1:\n",
" s = s[:-len(t)]\n",
" return s\n",
"\n",
"\n",
"def parse(q, raw):\n",
" \"\"\"์๋ณธ ์๋ต -> (์ ์ ๊ฐ, ์ํ). ์ํ: ์ ์ / ๋ฏธ์๋ต / ํ์์ค๋ฅ\"\"\"\n",
" if raw is None or str(raw).strip() == \"\":\n",
" return \"\", \"๋ฏธ์๋ต\"\n",
" s = unicodedata.normalize(\"NFKC\", str(raw)).strip()\n",
" if q[\"type\"] == \"MC\":\n",
" t = re.sub(r\"[\\s๋ฒ\\.\\)]\", \"\", s).lower()\n",
" t = {\"a\": \"1\", \"b\": \"2\", \"c\": \"3\", \"d\": \"4\"}.get(t, t)\n",
" return (t, \"์ ์\") if t in (\"1\", \"2\", \"3\", \"4\") else (t, \"ํ์์ค๋ฅ\")\n",
" if q[\"type\"] == \"ORDER\":\n",
" t = re.sub(r\"[^A-Za-z]\", \"\", s).upper()\n",
" return (t, \"์ ์\") if t and set(t) <= set(\"ABCD\") and len(set(t)) == len(t) else (t, \"ํ์์ค๋ฅ\")\n",
" return norm_text(s), \"์ ์\"\n",
"\n",
"\n",
"def grade_exact(q, val):\n",
" if q[\"type\"] == \"MC\":\n",
" return val == str(q[\"answer\"])\n",
" if q[\"type\"] == \"ORDER\":\n",
" return val == q[\"answer\"]\n",
" return val in {norm_text(a) for a in q[\"accept\"]}\n",
"\n",
"\n",
"def grade_fuzzy(q, val, cut=0.75):\n",
" \"\"\"๋จ๋ตํ์ ์คํ์(๊ธ์ ์์ ๋ฐ๋ยทํ ๊ธ์ ๋๋ฝ)๋ฅผ ํ์ฉ(์ ์ฌ๋ cut ์ด์)ํ๋, ์๋ ค์ง ์ค๋ต๊ณผ ๋ ๊ฐ๊น์ฐ๋ฉด ์ค๋ต์ผ๋ก ๋ณธ๋ค.\"\"\"\n",
" if grade_exact(q, val) or q[\"type\"] != \"SA\" or len(val) < 2:\n",
" return grade_exact(q, val)\n",
" best = max(1.0 if sorted(val) == sorted(norm_text(a)) else difflib.SequenceMatcher(None, val, norm_text(a)).ratio() for a in q[\"accept\"])\n",
" worst = max(difflib.SequenceMatcher(None, val, norm_text(w)).ratio() for w in q[\"wrong\"])\n",
" return best >= cut and best > worst\n",
"\n",
"\n",
"def score_of(q, correct):\n",
" return POINTS[q[\"diff\"]] if correct else 0\n",
"\n",
"# ---- ์ค๋ต ์ ํ ๋ถ๋ฅ๊ธฐ(๊ท์น + ๋ฌธ์ n-gram ๋ก์ง์คํฑ ํ๊ท) ----\n",
"SEED = {\"ํผ์ ์ค์ธ\": [\"์ ๊ธฐ\", \"์ ๋๋ก \", \"์๊ตฐ๊ธฐ\", \"์ ํฌ๊ธฐ\", \"์ ๊ธฐํ์ \", \"ํฌ๊ธฐ\", \"์ ํญ๊ณต๊ธฐ ํ์ \", \"์๊ตฐ ๋๋ก \"],\n",
" \"์ ์ฐจ ๋๋ฝ\": [\"์๋ต\", \"์ธ์ ๋ถ๋\", \"๊ฐ์ธ๋ฉ์ ์ \", \"๋ณด๊ณ ์ ํจ\", \"์ด์ฉ๋ณ\", \"๋ฏธํ์ธ๊ธฐ\", \"๋จ๊ณ ๊ฑด๋๋\", \"์ง์ ์ ํ\"],\n",
" \"๊ฐ๋
ํผ๋\": [\"์ง์ ์ต\", \"ํํด์ต\", \"ํฅ์ฌ์ฝฅํฐ\", \"๋ฏธํ\", \"์ฌ๊ฒฝ๋ณด\", \"ํ์ \", \"ํ์ธ\", \"๋ ์ด๋\", \"AI\", \"ํ์ ํ๋\", \"๊ฒฝ๊ณ\", \"ํต์ ๋ง\", \"๊ณ ์ ์ต\"]}\n",
"\n",
"\n",
"def build_text_model():\n",
" texts, labels = [], []\n",
" for q in BANK:\n",
" if q[\"type\"] == \"SA\":\n",
" for w, et in q[\"wrong\"].items():\n",
" texts.append(w); labels.append(et)\n",
" for et, ws in SEED.items():\n",
" texts += ws; labels += [et] * len(ws)\n",
" m = make_pipeline(TfidfVectorizer(analyzer=\"char_wb\", ngram_range=(1, 3)), LogisticRegression(max_iter=2000, C=5))\n",
" return m.fit([norm_text(t) for t in texts], labels)\n",
"\n",
"\n",
"def classify_error(q, val, text_model):\n",
" \"\"\"์ค๋ต 1๊ฑด์ ์ ํ๊ณผ ํ๋จ ๊ทผ๊ฑฐ(rule/text)๋ฅผ ๋๋ ค์ค๋ค.\"\"\"\n",
" if q[\"type\"] == \"MC\":\n",
" return q[\"dist\"].get(int(val), \"๊ฐ๋
ํผ๋\") if val in \"1234\" and val else \"๊ฐ๋
ํผ๋\", \"๋ณด๊ธฐ ๋งคํ\"\n",
" if q[\"type\"] == \"ORDER\":\n",
" return \"์ ์ฐจ ๋๋ฝ\", (\"๋จ๊ณ ๋๋ฝ\" if len(val) < 4 else \"์์ ๋ค๋ฐ๋\")\n",
" known = {norm_text(w): et for w, et in q[\"wrong\"].items()}\n",
" if val in known:\n",
" return known[val], \"์ค๋ต ๋ชฉ๋ก\"\n",
" near = max(known, key=lambda w: difflib.SequenceMatcher(None, val, w).ratio())\n",
" if difflib.SequenceMatcher(None, val, near).ratio() >= 0.75 or sorted(val) == sorted(near):\n",
" return known[near], \"์ค๋ต ๋ชฉ๋ก(์คํ์)\"\n",
" return str(text_model.predict([val])[0]), \"๋ฌธ์ ๋ชจ๋ธ\"\n",
"\n",
"PASS_RATE = 0.7\n",
"ALERT_AREA = 0.55 # ์ค๋ ์์ญ ์ ๋ต๋ฅ ์ด ์ด๋ณด๋ค ๋ฎ์ผ๋ฉด ๊ฒฝ๋ณด ํ์ด๋ผ์ดํธ\n",
"TM = build_text_model()\n",
"\n",
"\n",
"def grade_batch(df):\n",
" \"\"\"์๋ณธ ์๋ต ๋ฌถ์ -> ์ ์ ๊ฐยท์ ๋ต ์ฌ๋ถยท์ ์ยท์ค๋ต ์ ํ ์ด ์ถ๊ฐ.\"\"\"\n",
" out = df.copy()\n",
" pv = [parse(QIDX[q], r) for q, r in zip(out[\"qid\"], out[\"raw\"])]\n",
" out[\"val\"], out[\"status\"] = [p[0] for p in pv], [p[1] for p in pv]\n",
" out[\"area\"] = out[\"qid\"].map(lambda q: QIDX[q][\"area\"])\n",
" out[\"correct\"] = [s == \"์ ์\" and grade_fuzzy(QIDX[q], v) for q, v, s in zip(out[\"qid\"], out[\"val\"], out[\"status\"])]\n",
" out[\"score\"] = [score_of(QIDX[q], c) for q, c in zip(out[\"qid\"], out[\"correct\"])]\n",
" out[\"etype\"] = [None if c or s != \"์ ์\" else classify_error(QIDX[q], v, TM)[0]\n",
" for q, v, c, s in zip(out[\"qid\"], out[\"val\"], out[\"correct\"], out[\"status\"])]\n",
" return out\n",
"\n",
"\n",
"class Board:\n",
" \"\"\"๋์๋ณด๋ ์ํ: ๋์ ์ง๊ณ๋ง ๊ฐฑ์ ํด ๋งค๋ฒ ์ ์ฒด๋ฅผ ๋ค์ ๊ณ์ฐํ์ง ์๋๋ค.\"\"\"\n",
" def __init__(self):\n",
" self.data = pd.DataFrame()\n",
"\n",
" def update(self, batch):\n",
" self.data = pd.concat([self.data, grade_batch(batch)], ignore_index=True)\n",
" d = self.data\n",
" self.area = d.pivot_table(index=\"unit\", columns=\"area\", values=\"correct\", aggfunc=\"mean\")\n",
" self.etype = d.dropna(subset=[\"etype\"]).groupby([\"unit\", \"etype\"]).size().unstack(fill_value=0)\n",
" self.person = d.groupby([\"sid\", \"unit\"])[\"score\"].sum().reset_index()\n",
" self.item = d.groupby(\"qid\")[\"correct\"].mean()\n",
"\n",
" def query(self, unit=None, area=None):\n",
" d = self.data\n",
" if unit:\n",
" d = d[d[\"unit\"] == unit]\n",
" if area:\n",
" d = d[d[\"area\"] == area]\n",
" return d.groupby(\"qid\")[\"correct\"].mean().sort_values().head(3)\n",
"\n",
"\n",
"full = make_responses(40, seed=7)\n",
"FULL_SCORE = sum(POINTS[q[\"diff\"]] for q in BANK)\n",
"board = Board()\n",
"# 1) ์ค์๊ฐ ๊ฐฑ์ ๋ชจ์: ์์์ ์๋ต์ด 30๋ช
๋จ์๋ก 4๋ฒ ๋ค์ด์จ๋ค\n",
"times = []\n",
"for k, sids in enumerate(np.array_split(full[\"sid\"].unique(), 4), 1):\n",
" t0 = time.perf_counter()\n",
" board.update(full[full[\"sid\"].isin(sids)])\n",
" times.append((time.perf_counter() - t0) * 1000)\n",
" print(f\"๊ฐฑ์ {k}: ๋์ ์๋ต {len(board.data):,}๊ฑด, ์ง๊ณ ๊ฐฑ์ {times[-1]:.0f} ms\")\n",
"print(f\"์ต๋ ๊ฐฑ์ ์๊ฐ {max(times):.0f} ms (๋ชฉํ 1์ด ์ด๋ด: {'๋ฌ์ฑ' if max(times) < 1000 else '๋ฏธ๋ฌ'})\")\n",
"\n",
"# 2) ํํฐ ์กฐํ ์๋ต์ฑ(๊ต์์๊ฐ ์ค๋ยท์์ญ์ ๋ฐ๊ฟ ๊ฐ๋ฉฐ ์กฐํ)\n",
"q_times = []\n",
"for u in UNITS + [None]:\n",
" for a in AREAS + [None]:\n",
" t0 = time.perf_counter(); board.query(u, a); q_times.append((time.perf_counter() - t0) * 1000)\n",
"print(f\"ํํฐ ์กฐํ {len(q_times)}ํ, ํ๊ท {np.mean(q_times):.1f} ms, ์ต๋ {max(q_times):.1f} ms (๋ชฉํ 100 ms ์ด๋ด: {'๋ฌ์ฑ' if max(q_times) < 100 else '๋ฏธ๋ฌ'})\")\n",
"\n",
"# 3) ๋์๋ณด๋ ๊ตฌ์ฑ์์ 4์ข
\n",
"fig, ax = plt.subplots(2, 2, figsize=(12, 8))\n",
"AREA_EN = {\"ํ์์๋ณ\": \"Shape\", \"ํผ์์๋ณ\": \"IFF\", \"๊ฒฝ๋ณด๋ณด๊ณ \": \"Report\", \"๊ต์ ํต์ \": \"Control\", \"๋๋๋ก ์ผ๋ฐ\": \"C-UAS\"}\n",
"UNIT_EN = {\"๊ฐ์1์ค๋\": \"Co.1\", \"๊ฐ์2์ค๋\": \"Co.2\", \"๊ฐ์3์ค๋\": \"Co.3\"}\n",
"ET_EN = {\"๊ฐ๋
ํผ๋\": \"Concept\", \"ํผ์ ์ค์ธ\": \"IFF error\", \"์ ์ฐจ ๋๋ฝ\": \"Procedure\"}\n",
"A = board.area[AREAS]\n",
"im = ax[0, 0].imshow(A.values, cmap=\"RdYlGn\", vmin=0.3, vmax=1.0)\n",
"ax[0, 0].set_xticks(range(len(AREAS)), [AREA_EN[a] for a in AREAS]); ax[0, 0].set_yticks(range(3), [UNIT_EN[u] for u in A.index])\n",
"for i in range(A.shape[0]):\n",
" for j in range(A.shape[1]):\n",
" v = A.values[i, j]\n",
" ax[0, 0].text(j, i, f\"{v:.2f}\", ha=\"center\", va=\"center\", fontweight=\"bold\" if v < ALERT_AREA else \"normal\",\n",
" color=\"white\" if v < ALERT_AREA else \"black\")\n",
"ax[0, 0].set_title(\"Accuracy by company x area (bold = alert)\"); fig.colorbar(im, ax=ax[0, 0])\n",
"E = board.etype.rename(index=UNIT_EN, columns=ET_EN)\n",
"E.div(E.sum(axis=1), axis=0).plot.bar(stacked=True, ax=ax[0, 1], rot=0)\n",
"ax[0, 1].set_title(\"Error type share by company\"); ax[0, 1].set_ylabel(\"share\")\n",
"for u in UNITS:\n",
" ax[1, 0].hist(board.person.loc[board.person[\"unit\"] == u, \"score\"], bins=12, alpha=0.5, label=UNIT_EN[u])\n",
"ax[1, 0].axvline(FULL_SCORE * PASS_RATE, color=\"k\", ls=\"--\", label=\"pass line\"); ax[1, 0].legend()\n",
"ax[1, 0].set_title(\"Score distribution\"); ax[1, 0].set_xlabel(\"score\")\n",
"it = board.item.sort_values()\n",
"ax[1, 1].barh(it.index[:10], it.values[:10], color=\"tab:red\"); ax[1, 1].set_xlim(0, 1)\n",
"ax[1, 1].set_title(\"10 hardest items (accuracy)\")\n",
"plt.tight_layout(); plt.show()\n",
"print(\"๋์๋ณด๋ ๊ตฌ์ฑ์์ 4์ข
๋ ๋๋ง: ์ค๋x์์ญ ์ ๋ต๋ฅ , ์ค๋ต ์ ํ ๋น์จ, ์ ์ ๋ถํฌ, ๊ณ ๋๋ ๋ฌธํญ\")\n",
"\n",
"# 4) ์์ฝ ์งํ์ ๊ฒฝ๋ณด ํ์ด๋ผ์ดํธ\n",
"p = board.person.assign(passed=board.person[\"score\"] >= FULL_SCORE * PASS_RATE)\n",
"print(\"\\n[์ค๋๋ณ ์์ฝ]\")\n",
"print(p.groupby(\"unit\").agg(์์=(\"sid\", \"size\"), ํ๊ท ์ ์=(\"score\", \"mean\"), ํฉ๊ฒฉ๋ฅ =(\"passed\", \"mean\")).round(2).to_string())\n",
"alerts = [(u, a, A.loc[u, a]) for u in A.index for a in AREAS if A.loc[u, a] < ALERT_AREA]\n",
"print(f\"\\n[๊ฒฝ๋ณด ํ์ด๋ผ์ดํธ] ์์ญ ์ ๋ต๋ฅ {ALERT_AREA} ๋ฏธ๋ง {len(alerts)}๊ฑด\")\n",
"for u, a, v in sorted(alerts, key=lambda x: x[2]):\n",
" print(f\" - {u} {a}: ์ ๋ต๋ฅ {v:.2f}, ์ต์ ๋ฌธํญ {list(board.query(u, a).index)}\")\n",
"top = board.etype.sum().sort_values(ascending=False)\n",
"print(\"๋ถ๋ ์ ์ฒด ์ค๋ต ์ ํ:\", {k: int(v) for k, v in top.items()})\n",
"print(\"์ผ๊ฐ ๊ต์ก์ฅ ์ฌ์ฉ ์ plt.style.use('dark_background')๋ก ์ด๋์ด ํ๋ฉด ๋ชจ๋๋ฅผ ์ ์ฉํ ์ ์๋ค.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
06 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"๊ฐฑ์ 1: ๋์ ์๋ต 960๊ฑด, ์ง๊ณ ๊ฐฑ์ 39 ms\n",
"๊ฐฑ์ 2: ๋์ ์๋ต 1,920๊ฑด, ์ง๊ณ ๊ฐฑ์ 35 ms\n",
"๊ฐฑ์ 3: ๋์ ์๋ต 2,880๊ฑด, ์ง๊ณ ๊ฐฑ์ 39 ms\n",
"๊ฐฑ์ 4: ๋์ ์๋ต 3,840๊ฑด, ์ง๊ณ ๊ฐฑ์ 38 ms\n",
"์ต๋ ๊ฐฑ์ ์๊ฐ 39 ms (๋ชฉํ 1์ด ์ด๋ด: ๋ฌ์ฑ)\n",
"ํํฐ ์กฐํ 24ํ, ํ๊ท 2.7 ms, ์ต๋ 3.4 ms (๋ชฉํ 100 ms ์ด๋ด: ๋ฌ์ฑ)\n",
"๋์๋ณด๋ ๊ตฌ์ฑ์์ 4์ข
๋ ๋๋ง: ์ค๋x์์ญ ์ ๋ต๋ฅ , ์ค๋ต ์ ํ ๋น์จ, ์ ์ ๋ถํฌ, ๊ณ ๋๋ ๋ฌธํญ\n",
"\n",
"[์ค๋๋ณ ์์ฝ]\n",
" ์์ ํ๊ท ์ ์ ํฉ๊ฒฉ๋ฅ \n",
"unit \n",
"๊ฐ์1์ค๋ 40 75.40 0.68\n",
"๊ฐ์2์ค๋ 40 58.55 0.28\n",
"๊ฐ์3์ค๋ 40 68.18 0.57\n",
"\n",
"[๊ฒฝ๋ณด ํ์ด๋ผ์ดํธ] ์์ญ ์ ๋ต๋ฅ 0.55 ๋ฏธ๋ง 1๊ฑด\n",
" - ๊ฐ์2์ค๋ ๊ต์ ํต์ : ์ ๋ต๋ฅ 0.50, ์ต์ ๋ฌธํญ ['Q31', 'Q15', 'Q14']\n",
"๋ถ๋ ์ ์ฒด ์ค๋ต ์ ํ: {'์ ์ฐจ ๋๋ฝ': 419, '๊ฐ๋
ํผ๋': 367, 'ํผ์ ์ค์ธ': 186}\n",
"์ผ๊ฐ ๊ต์ก์ฅ ์ฌ์ฉ ์ plt.style.use('dark_background')๋ก ์ด๋์ด ํ๋ฉด ๋ชจ๋๋ฅผ ์ ์ฉํ ์ ์๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
07. ์ด์ ์ ๊ธฐ ๋ฌธํญ ๋ฐ ์์ ํ ๋ต๋ณ์ ๋ํ ๊ต๊ด ์๋ ์ฑ์ (HITL) ๊ฒํ ์ธํฐํ์ด์ค ์ค๊ณ\n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์ด์ ์ ๊ธฐ ๋ฌธํญ ๋ฐ ์์ ํ ๋ต๋ณ์ ๋ํ ๊ต๊ด ์๋ ์ฑ์ (HITL) ์ธํฐํ์ด์ค๋ฅผ ์ค๊ณํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ด์ ์ ๊ธฐ ๋ฌธํญ๊ณผ ์์ ํ ๋ต๋ณ์ ๊ต๊ด์ด ๊ฒํ ยทํ์ ํ๋ HITL ํ๋ฆ์ ์ค๊ณํ๊ณ , ์๋ ์ฑ์ ์ด ์ ์๋ฅผ ๋จ์ ํ์ง ๋ชปํ๊ฒ ํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์์ ํ์ ํต์ฌ ์์ ํค์๋๋ก ์ ์๋ฅผ '์ ์'๋ง ํ๊ณ , ์ด์ ์ ๊ธฐ์ ํจ๊ป ํฉ๊ฒฉ์ ๊ทผ์ฒยท์ ์ ๋ขฐ ๋ต์๋ถํฐ ๊ฒํ ํ๋ ์ฐ์ ์์ ๋๊ธฐ์ด์ ์ฌ๋ฆฐ๋ค ๊ณผ์ ์ ์งํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/hitl/trn02_approval_workflow.py ๋ฐ docs/trn02_hitl_guide.md` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์งํ ์ฑ
์ ๋ถ๊ณ์ ๋ช
ํํ ๋ฐ ์ฑ๊ธํ ์๋ ๋จ์ ๋ฐฉ์ง ์ฒด๊ณ ๋ถํฉ์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: ์ธ๊ฐ ๊ฒํ ํ(Queue) ๋๊ธฐ์๊ฐ ๋ฐ ์น์ธ/๋ฐ๋ ค ์ฒ๋ฆฌ ๋ก๊น
์ฑ๊ณต๋ฅ 100%\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: ์น์ธ/๋ฐ๋ ค ์ธํฐํ์ด์ค ๋์ ๋ฐ ๊ฐ์ฌ ๋ก๊ทธ(Audit Log) ์์ฑ ๊ฒ์ฆ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ๋ชจ์ ์กฐ์์ ์น์ธ/๋ฐ๋ ค ์๋ฎฌ๋ ์ด์
๋ฐ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"์ด์ ์ ๊ธฐ๋ ๋จ๋ตํ ๋ฌธํญ๊ณผ ์์ ํ ๋ต๋ณ์ ๊ต๊ด ๊ฒํ ๋๊ธฐ์ด(HITL)์ ์ฌ๋ ค, ์๋ ์ฑ์ ์ '์ ์'์ผ๋ก๋ง ๋๊ณ ๊ต๊ด์ ์ธ์ ยท๊ธฐ๊ฐยท์์ ๊ฒฐ์ ๊ณผ ์ฌ์ ๋ฅผ ํด์ ์ฒด์ธ ๊ฐ์ฌ ๋ก๊ทธ์ ๋จ๊ธฐ๋ ๊ฒํ ํ๋ฆ์ ์ค๊ณํ๋ค. ํฉ๊ฒฉ์ ๊ทผ์ฒ ์์์๋ฅผ ๋จผ์ ๊ฒํ ํ๋ ์ฐ์ ์์ ํ์ ๋๊ธฐ์๊ฐ, ๋ก๊น
์ฑ๊ณต๋ฅ , ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ์ ํ์ธํ๋ค.\"\"\"\n",
"import difflib\n",
"import hashlib\n",
"import heapq\n",
"import json\n",
"import re\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฐฉ๊ณตยท๋๋ก ์๋ณ ํด์ฆ ๋ฌธํญ ์ํ๊ณผ ๊ฐ์ ์์์ ์๋ต ์์ฑ๊ธฐ\n",
"# ๋ฌธํญ์ ์ผ๋ฐ ์์น ์์ค์ ๊ฐ์ ๋ฌธํญ์ด๋ฉฐ ์ค์ ๊ต๋ฒยท๊ต์ ๊ท์น ์๋ฌธ์ด ์๋๋ค. ์์์๋ ๊ฐ๋ช
(S001~)์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"AREAS = [\"ํ์์๋ณ\", \"ํผ์์๋ณ\", \"๊ฒฝ๋ณด๋ณด๊ณ \", \"๊ต์ ํต์ \", \"๋๋๋ก ์ผ๋ฐ\"]\n",
"ETYPES = [\"๊ฐ๋
ํผ๋\", \"ํผ์ ์ค์ธ\", \"์ ์ฐจ ๋๋ฝ\"]\n",
"POINTS = {1: 2, 2: 3, 3: 5}\n",
"C, F, P = ETYPES\n",
"\n",
"\n",
"def _mc(qid, area, diff, stem, opts, ans, dist):\n",
" return {\"qid\": qid, \"type\": \"MC\", \"area\": area, \"diff\": diff, \"stem\": stem, \"options\": opts, \"answer\": ans,\n",
" \"dist\": dict(zip([i for i in range(1, 5) if i != ans], dist))}\n",
"\n",
"\n",
"def _sa(qid, area, diff, stem, accept, wrong):\n",
" return {\"qid\": qid, \"type\": \"SA\", \"area\": area, \"diff\": diff, \"stem\": stem, \"accept\": accept, \"wrong\": wrong}\n",
"\n",
"\n",
"def _order(qid, area, diff, stem):\n",
" return {\"qid\": qid, \"type\": \"ORDER\", \"area\": area, \"diff\": diff, \"stem\": stem, \"answer\": \"ABCD\"}\n",
"\n",
"\n",
"BANK = [\n",
" _mc(\"Q01\", \"ํ์์๋ณ\", 1, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์์ด ์ผ๊ฐํ ๋ ๊ฐ๋ฅผ ๊ฐ์ง ์ํ ๊ณ ์ ์ต ์ค๋ฃจ์ฃ์?\", [\"์ฟผ๋์ฝฅํฐํ\", \"์ผ๊ฐ์ต ๊ณ ์ ์ตํ\", \"ํ์ ์ต ํฌ๊ธฐํ\", \"์กฐ๋ฅ\"], 2, [C, C, F]),\n",
" _mc(\"Q02\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๊ฐ ์ญ์๋ก ๋ฐฐ์น๋ ์ค๋ฃจ์ฃ์?\", [\"๊ณ ์ ์ต ๋๋ก ํ\", \"์ฟผ๋์ฝฅํฐํ\", \"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\"], 2, [C, C, C]),\n",
" _mc(\"Q03\", \"ํ์์๋ณ\", 2, \"๊ฐ๋๊ณ ๊ธด ๋์ฒด ์ค๊ฐ์ ์์ ๋ ๊ฐ๊ฐ ๋ถ์ ์ค๋ฃจ์ฃ์?\", [\"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\", \"๋ฏผ๊ฐ ๊ฒฝ๋นํ๊ธฐํ\", \"์กฐ๋ฅ\"], 1, [C, F, F]),\n",
" _mc(\"Q04\", \"ํ์์๋ณ\", 3, \"์๊ฑฐ๋ฆฌ๋ผ ์ค๋ฃจ์ฃ ์๋ณ์ด ์ด๋ ค์ธ ๋ ๋จผ์ ํ ํ๋์?\", [\"์ถ์ ์ ํ์ผ๋ก ํ์ ๋ณด๊ณ \", \"๊ด์ธก ์ ๋ณด๋ฅผ ๊ทธ๋๋ก ๋ณด๊ณ ํ๊ณ ์ถ๊ฐ ๊ด์ธก\", \"๊ด์ธก ์ค๋จ\", \"๋ฏผ๊ฐ๊ธฐ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _mc(\"Q05\", \"ํผ์์๋ณ\", 2, \"์๋ณ ์ ํธ ์๋ต์ด ์๋ ํญ๊ณต๊ธฐ์ ๋ํ ์ฌ๋ฐ๋ฅธ ํ๋จ์?\", [\"์ฆ์ ์ ๊ธฐ๋ก ํ์ \", \"๋ฏธํ์ธ ํญ๊ณต๊ธฐ๋ก ๋ถ๋ฅํ๊ณ ์ถ๊ฐ ํ์ธ\", \"์๊ตฐ๊ธฐ๋ก ๊ฐ์ฃผ\", \"๋ฌด์\"], 2, [F, F, P]),\n",
" _mc(\"Q06\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด์ ์ผ์นํ๋ ์ ์ ๋นํ์ฒด์ ๋ถ๋ฅ๋?\", [\"์ ๋๋ก \", \"๋ฏผ๊ฐ๊ธฐ ๊ฐ๋ฅ์ฑ ๋์, ์ถ๊ฐ ํ์ธ\", \"์ํญ๋ฏธ์ฌ์ผ\", \"์๊ตฐ ์ ์ฐฐ๊ธฐ๋ก ํ์ \"], 2, [F, C, F]),\n",
" _mc(\"Q07\", \"ํผ์์๋ณ\", 3, \"์๊ตฐ ๋๋ก ์ด์ฉ ๊ณต์ง๊ฐ ์๋ ๊ณต์ญ์์ ๋ฏธํ์ธ ๋๋ก ์ ๋ณด๋ฉด ์ฐ์ ํ์ธํ ๊ฒ์?\", [\"๊ธฐ์ ์ ๋ณด\", \"์๊ตฐ ์ด์ฉ ๊ณํ๊ณผ ์๋ณ ์ ํธ\", \"๋์ ์ค๋น ์ํ\", \"์ฃผ๋ณ ์งํ\"], 2, [C, P, C]),\n",
" _mc(\"Q08\", \"ํผ์์๋ณ\", 3, \"์กฐ๋ฅ ๋ฌด๋ฆฌ์ ์ํ ๋๋ก ๊ตฐ์ง ๊ตฌ๋ถ์ ๊ฐ์ฅ ๋์์ด ๋๋ ์ ๋ณด๋?\", [\"ํฌ๊ธฐ๋ง ๋น๊ต\", \"๊ถค์ ์ ๊ท์น์ฑ๊ณผ ์ด ์ ํธ\", \"์์\", \"๊ด์ธก ์๊ฐ\"], 2, [C, C, C]),\n",
" _mc(\"Q09\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๊ณต์ค ์ํ ์ต์ด ๋ณด๊ณ ์ ํฌํจํ์ง ์์๋ ๋๋ ๊ฒ์?\", [\"๊ด์ธก ์๊ฐ\", \"๋ฐฉํฅ๊ณผ ๊ฑฐ๋ฆฌ\", \"์ถ์ ์ ํ\", \"๊ด์ธก์ ๊ฐ์ธ ์ฐ๋ฝ์ฒ\"], 4, [P, P, P]),\n",
" _mc(\"Q10\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๊ฒฝ๋ณด ์ ํ ํ ์ํฉ์ด ๋ฐ๋๋ฉด?\", [\"์ต์ด ๋ณด๊ณ ์ ์ง\", \"๋ณ๊ฒฝ ์ฌํญ์ ์ฆ์ ์ถ๊ฐ ๋ณด๊ณ \", \"์ํฉ ์ข
๋ฃ ํ ์ผ๊ด ๋ณด๊ณ \", \"๊ฐ์ธ ํ๋จ์ผ๋ก ๊ฒฝ๋ณด ํด์ \"], 2, [P, P, P]),\n",
" _mc(\"Q11\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ณด๊ณ ๋ด์ฉ์ด ๋ถํ์คํ ๋ ์ฌ๋ฐ๋ฅธ ํํ์?\", [\"ํ์ ํํ์ผ๋ก ๋ณด๊ณ \", \"'์ถ์ '์์ ๋ฐํ๊ณ ๊ทผ๊ฑฐ์ ํจ๊ป ๋ณด๊ณ \", \"๋ณด๊ณ ๋ณด๋ฅ\", \"๋ค๋ฅธ ๊ด์ธก์ ๋ณด๊ณ ๋ฅผ ๋ณต์ฌ\"], 2, [C, P, P]),\n",
" _mc(\"Q12\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋๋ก ํ์ง ํ ๋ณด๊ณ ๊ฒฝ๋ก๋ก ์๋ง์ ๊ฒ์?\", [\"์ธ์ ๋ถ๋์๋ง ์ ํ\", \"์งํ๊ณํต์ ๋ฐ๋ผ ๋ณด๊ณ \", \"๊ฐ์ธ ๋ฉ์ ์ ๋ก ๊ณต์ \", \"๋ณด๊ณ ์์ด ๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q13\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ๋ง์ผ๋ก ๋์์ ๊ฒฐ์ ํ ์ ์๋๊ฐ?\", [\"๊ฐ๋ฅํ๋ค\", \"์ธ๊ฐ ์งํ๊ด์ ์น์ธ์ด ํ์ํ๋ค\", \"์ ๋ขฐ๋ 90% ์ด์์ด๋ฉด ๊ฐ๋ฅ\", \"์ผ๊ฐ์๋ง ๊ฐ๋ฅ\"], 2, [C, C, C]),\n",
" _mc(\"Q14\", \"๊ต์ ํต์ \", 2, \"๋ฏผ๊ฐ ํผํด ๊ฐ๋ฅ์ฑ์ด ํ์ธ๋๋ฉด?\", [\"๊ทธ๋๋ก ์งํ\", \"์ฆ์ ๋ณด๊ณ ํ๊ณ ์งํ๊ด ํ๋จ์ ๋ฐ๋๋ค\", \"์กฐ์น ํ ๋ณด๊ณ \", \"๋ฌด์\"], 2, [P, P, F]),\n",
" _mc(\"Q15\", \"๊ต์ ํต์ \", 3, \"์๋ณ์ด ๋ถํ์คํ ํ์ ์ ๋ํ ์์น์?\", [\"์ํ์ผ๋ก ๊ฐ์ฃผํด ์ฆ์ ๋์\", \"์๋ณ ํ์ธ ์ ๊น์ง ์น์ธ ์ ์ฐจ๋ฅผ ๋ฐ๋ฅธ๋ค\", \"๋ฌด์กฐ๊ฑด ๋ฌด์\", \"๋ค์๊ฒฐ๋ก ๊ฒฐ์ \"], 2, [F, F, C]),\n",
" _mc(\"Q16\", \"๊ต์ ํต์ \", 2, \"ํ๋จ ๊ทผ๊ฑฐ๋ฅผ ๊ธฐ๋กํด์ผ ํ๋ ์ด์ ๋?\", [\"ํ์์ ํ์\", \"์ฌํ ๊ฒํ ์ ์ฑ
์ ์ถ์ \", \"๋ณด๊ณ ์๋ต\", \"๊ธฐ๋ก ๋ถํ์\"], 2, [C, P, P]),\n",
" _mc(\"Q17\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"๋๋๋ก ์ฒด๊ณ์ ์ผ๋ฐ์ ์ฒ๋ฆฌ ์์๋?\", [\"๋์-ํ์ง-์๋ณ\", \"ํ์ง-์๋ณ-๋ณด๊ณ ยทํ๋จ-๋์\", \"์๋ณ-๋์-ํ์ง\", \"๋ณด๊ณ -ํ์ง-๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q18\", \"๋๋๋ก ์ผ๋ฐ\", 3, \"๋ ์ด๋๋ง์ผ๋ก ์กฐ๋ฅ์ ๋๋ก ๊ตฌ๋ถ์ด ์ด๋ ค์ด ์ด์ ๋?\", [\"๋ฐ์ฌ ์ ํธ ํฌ๊ธฐ๊ฐ ๋น์ทํ ์ ์์ด์\", \"๋ ์ด๋๋ ์ผ๊ฐ์ ๋ชป ์จ์\", \"๋๋ก ์๋ ๊ธ์์ด ์์ด์\", \"์กฐ๋ฅ๋ ํ์ง๋์ง ์์์\"], 1, [C, C, C]),\n",
" _mc(\"Q19\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ ์๊ดํ(EO) ์ผ์์ ์ฝ์ ์ด ์ปค์ง๋ ์กฐ๊ฑด์?\", [\"๋ง์ ์ฃผ๊ฐ\", \"์๊ฐยท์ผ๊ฐ\", \"์ ์ ํ์ \", \"๊ทผ๊ฑฐ๋ฆฌ\"], 2, [C, C, C]),\n",
" _mc(\"Q20\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"๊ฒฝ๋ณด๊ฐ ๋ฐ๋ณตํด์ ์ค๊ฒฝ๋ณด๋ก ํ๋ช
๋๋ฉด?\", [\"๊ฒฝ๋ณด ์ฅ์น๋ฅผ ๋๋ค\", \"์ฌ๋ก๋ฅผ ๊ธฐ๋กยท๋ณด๊ณ ํ๊ณ ๊ธฐ์ค ์ฌ๊ฒํ ์์ฒญ\", \"๋ฌด์ํ๋ค\", \"๋ชจ๋ ์ค์ ์ํ์ผ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _sa(\"Q21\", \"ํผ์์๋ณ\", 1, \"์๊ตฐยท์ ยท๋ฏผ๊ฐ์ ๊ตฌ๋ถํ๋ ์ ์ฐจ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"ํผ์์๋ณ\", \"IFF\"], {\"ํ์ ํ๋\": C, \"์ ๊ธฐํ์ \": F, \"๊ฒฝ๊ณ\": C}),\n",
" _sa(\"Q22\", \"ํ์์๋ณ\", 2, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์๋ ์ผ๊ฐํ ๋ ๊ฐ ํํ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ผ๊ฐ์ต\", \"๋ธํ์ต\", \"delta\"], {\"์ง์ ์ต\": C, \"ํํด์ต\": C, \"์ฟผ๋์ฝฅํฐ\": C}),\n",
" _sa(\"Q23\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ถํ์คํ ๊ด์ธก์ ๋ณด๊ณ ํ ๋ ๋ถ์ด๋ ํํ์?\", [\"์ถ์ \"], {\"ํ์ \": C, \"ํ์ธ\": C, \"์๋ต\": P}),\n",
" _sa(\"Q24\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ์ ๋ํด ์ต์ข
๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ์ฃผ์ฒด๋?\", [\"์งํ๊ด\"], {\"AI\": C, \"์ด์ฉ๋ณ\": P, \"๋ ์ด๋\": C}),\n",
" _sa(\"Q25\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"์กฐ๋ฅ๋ฅผ ๋๋ก ์ผ๋ก ์๋ชป ๊ฒฝ๋ณดํ๋ ๊ฒ์ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ค๊ฒฝ๋ณด\", \"์คํ\", \"false alarm\"], {\"๋ฏธํ\": C, \"์ฌ๊ฒฝ๋ณด\": C, \"์ ๊ธฐ\": F}),\n",
" _sa(\"Q26\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด๊ฐ ํ์ธ๋ ์ฌ๊ฐ๊ธฐ๋ ์ด๋ป๊ฒ ๋ถ๋ฅํ๋๊ฐ?\", [\"๋ฏผ๊ฐ๊ธฐ\", \"๋ฏผํญ๊ธฐ\"], {\"์ ๊ธฐ\": F, \"๋ฏธํ์ธ๊ธฐ\": P, \"์๊ตฐ๊ธฐ\": F}),\n",
" _sa(\"Q27\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๋ฅผ ๊ฐ์ง ๋๋ก ํํ๋?\", [\"์ฟผ๋์ฝฅํฐ\", \"quadcopter\"], {\"ํฅ์ฌ์ฝฅํฐ\": C, \"๊ณ ์ ์ต\": C, \"ํฌ๊ธฐ\": F}),\n",
" _sa(\"Q28\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋ณด๊ณ ๋ ๋ฌด์์ ๋ฐ๋ผ ์ฌ๋ผ๊ฐ๋๊ฐ?\", [\"์งํ๊ณํต\"], {\"์ธ์ ๋ถ๋\": P, \"๊ฐ์ธ๋ฉ์ ์ \": P, \"ํต์ ๋ง\": C}),\n",
" _order(\"Q29\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"์ต์ด ๋ณด๊ณ ์์: A ๊ด์ธก ์๊ฐ, B ๋ฐฉํฅยท๊ฑฐ๋ฆฌ, C ์ถ์ ์ ํ, D ๊ด์ธก์ ์กฐ์น\"),\n",
" _order(\"Q30\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ฒ๋ฆฌ ์์: A ํ์ง, B ์๋ณ, C ๋ณด๊ณ ยทํ๋จ, D ๋์\"),\n",
" _order(\"Q31\", \"๊ต์ ํต์ \", 3, \"ํต์ ์์: A ์๋ณ ํ์ธ, B ์๊ธ ์น์ธ ์์ฒญ, C ์น์ธ ์๋ น, D ์กฐ์น ๊ธฐ๋ก\"),\n",
" _order(\"Q32\", \"ํผ์์๋ณ\", 3, \"ํ์ธ ์์: A ์ดํญ์ ๋ณด ๋์กฐ, B ์๋ณ ์ ํธ ํ์ธ, C ๋ฏธํ์ธ ๋ถ๋ฅ, D ๋ณด๊ณ \"),\n",
"]\n",
"QIDX = {q[\"qid\"]: q for q in BANK}\n",
"UNITS = [\"๊ฐ์1์ค๋\", \"๊ฐ์2์ค๋\", \"๊ฐ์3์ค๋\"]\n",
"_CIRC = \"โ โกโขโฃ\"\n",
"PARA = {\"Q21\": \"์๊ตฐ์ ๊ตฐ ๊ตฌ๋ถ\", \"Q22\": \"์ธ๋ชจ ๋ ๊ฐ\", \"Q23\": \"์ถ์ ๋จ์ผ๋ก ํ๊ธฐ\", \"Q24\": \"์ค๋์ฅ(์งํ๊ด)\", \"Q25\": \"์๋ชป๋ ๊ฒฝ๋ณด\",\n",
" \"Q26\": \"๋ฏผ๊ฐ ์ฌ๊ฐ๊ธฐ\", \"Q27\": \"4๋ฐ ๋๋ก \", \"Q28\": \"์งํ ๋ผ์ธ\"} # ๋ป์ ๋ง์ง๋ง ์ธ์ ๋ต์ ๋ชฉ๋ก์ ์๋ ํํ\n",
"\n",
"\n",
"def make_soldiers(n_per_unit, seed):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for u, unit in enumerate(UNITS):\n",
" base = [0.3, -0.1, 0.0][u]\n",
" for i in range(n_per_unit):\n",
" off = rng.normal(0, 0.5, len(AREAS)) + (np.array([0, 0, 0, -0.6, 0]) if u == 1 else 0)\n",
" out.append({\"sid\": f\"S{len(out) + 1:03d}\", \"unit\": unit, \"theta\": dict(zip(AREAS, base + rng.normal(0, 0.6) + off)),\n",
" \"tend\": dict(zip(ETYPES, rng.dirichlet([1.2, 1.2, 1.2])))})\n",
" return out\n",
"\n",
"\n",
"def p_correct(theta, diff):\n",
" return 1 / (1 + np.exp(-1.7 * (theta - (diff - 2) * 0.9 + 0.9)))\n",
"\n",
"\n",
"def _typo(s, rng):\n",
" if len(s) < 2:\n",
" return s\n",
" i = int(rng.integers(len(s) - 1))\n",
" return s[:i] + s[i + 1] + s[i] + s[i + 2:] if len(s) <= 3 or rng.random() < 0.5 else s[:i] + s[i + 1:]\n",
"\n",
"\n",
"def _fmt_mc(k, rng):\n",
" r = rng.random()\n",
" if r < 0.55:\n",
" return str(k)\n",
" return rng.choice([_CIRC[k - 1], f\"{k}๋ฒ\", \"ABCD\"[k - 1], \"abcd\"[k - 1] + \")\", f\" {k} \", chr(0xFF10 + k), f\"{k}.\"])\n",
"\n",
"\n",
"def _fmt_sa(s, rng):\n",
" r = rng.random()\n",
" if r < 0.45:\n",
" return s\n",
" if r < 0.6:\n",
" return \" \".join(s) if len(s) <= 4 else s[:2] + \" \" + s[2:]\n",
" if r < 0.72:\n",
" return s + rng.choice([\"์
๋๋ค\", \"์ด๋ค\", \".\", \"์\"])\n",
" if r < 0.8:\n",
" return s.upper() if s.isascii() else \"\".join(chr(ord(ch) + 0xFEE0) if \"!\" <= ch <= \"~\" else ch for ch in s) + \" \"\n",
" return _typo(s, rng)\n",
"\n",
"\n",
"def answer(q, sol, rng):\n",
" \"\"\"ํ ๋ฌธํญ ์๋ต: (์๋ณธ ์๋ต ๋ฌธ์์ด, ์๋ํ ์ ๋ต ์ฌ๋ถ, ์๋ํ ๋ต, ์ค๋ต ์ ํ).\"\"\"\n",
" ok = rng.random() < p_correct(sol[\"theta\"][q[\"area\"]], q[\"diff\"])\n",
" if q[\"type\"] == \"MC\":\n",
" if ok:\n",
" k, et = q[\"answer\"], None\n",
" else:\n",
" ks = list(q[\"dist\"]); w = np.array([sol[\"tend\"][q[\"dist\"][k]] for k in ks])\n",
" k = int(rng.choice(ks, p=w / w.sum())); et = q[\"dist\"][k]\n",
" return _fmt_mc(k, rng), ok, str(k), et\n",
" if q[\"type\"] == \"SA\":\n",
" if ok and rng.random() < 0.06:\n",
" return PARA[q[\"qid\"]], ok, PARA[q[\"qid\"]], None\n",
" if ok:\n",
" s, et = str(rng.choice(q[\"accept\"])), None\n",
" else:\n",
" ws = list(q[\"wrong\"]); w = np.array([sol[\"tend\"][q[\"wrong\"][x]] for x in ws])\n",
" s = str(rng.choice(ws, p=w / w.sum())); et = q[\"wrong\"][s]\n",
" return _fmt_sa(s, rng), ok, s, et\n",
" if ok:\n",
" s = \"ABCD\"\n",
" elif rng.random() < 0.5:\n",
" s = \"ABCD\".replace(\"ABCD\"[rng.integers(1, 4)], \"\") # ๋จ๊ณ ํ๋ ๋น ๋จ๋ฆผ\n",
" else:\n",
" i = int(rng.integers(0, 3)); s = list(\"ABCD\"); s[i], s[i + 1] = s[i + 1], s[i]; s = \"\".join(s) # ์์ ๋ค๋ฐ๋\n",
" raw = s if rng.random() < 0.6 else rng.choice([\"-\".join(s), \" \".join(s), s.lower(), \",\".join(s)])\n",
" return raw, ok, s, (None if ok else P)\n",
"\n",
"\n",
"def make_responses(n_per_unit=40, seed=7, blank=0.02):\n",
" \"\"\"์์์ x ๋ฌธํญ ์๋ตํ(DataFrame)๋ฅผ ๋ง๋ ๋ค. truth_* ์ด์ ๊ต๊ด ๊ธฐ์ค ์ ๋ต(ํ๊ฐ์ฉ)์ด๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows = []\n",
" for sol in make_soldiers(n_per_unit, seed):\n",
" for q in BANK:\n",
" raw, ok, intended, et = answer(q, sol, rng)\n",
" if rng.random() < blank:\n",
" raw, ok, intended, et = \"\", False, \"\", None\n",
" rows.append({\"rid\": f\"R{len(rows):05d}\", \"sid\": sol[\"sid\"], \"unit\": sol[\"unit\"], \"qid\": q[\"qid\"], \"raw\": raw,\n",
" \"truth_correct\": ok, \"truth_answer\": intended, \"truth_etype\": et})\n",
" return pd.DataFrame(rows)\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์๋ต ์ ์ ยทํ์ฑยท์ฑ์ ๊ณตํต ํจ์ ----\n",
"TAILS = (\"์
๋๋ค\", \"์ด๋ค\", \"์\")\n",
"\n",
"\n",
"def norm_text(s):\n",
" \"\"\"์ ๊ฐ->๋ฐ๊ฐ(NFKC), ์๋ฌธ์, ๊ณต๋ฐฑยท๋ฌธ์ฅ๋ถํธ ์ ๊ฑฐ, ๋๋งบ์ ์ด๋ฏธ ์ ๊ฑฐ.\"\"\"\n",
" s = unicodedata.normalize(\"NFKC\", str(s)).strip().lower()\n",
" s = re.sub(r\"[\\s\\.\\,\\-\\(\\)ยท]\", \"\", s)\n",
" for t in TAILS:\n",
" if s.endswith(t) and len(s) > len(t) + 1:\n",
" s = s[:-len(t)]\n",
" return s\n",
"\n",
"\n",
"def parse(q, raw):\n",
" \"\"\"์๋ณธ ์๋ต -> (์ ์ ๊ฐ, ์ํ). ์ํ: ์ ์ / ๋ฏธ์๋ต / ํ์์ค๋ฅ\"\"\"\n",
" if raw is None or str(raw).strip() == \"\":\n",
" return \"\", \"๋ฏธ์๋ต\"\n",
" s = unicodedata.normalize(\"NFKC\", str(raw)).strip()\n",
" if q[\"type\"] == \"MC\":\n",
" t = re.sub(r\"[\\s๋ฒ\\.\\)]\", \"\", s).lower()\n",
" t = {\"a\": \"1\", \"b\": \"2\", \"c\": \"3\", \"d\": \"4\"}.get(t, t)\n",
" return (t, \"์ ์\") if t in (\"1\", \"2\", \"3\", \"4\") else (t, \"ํ์์ค๋ฅ\")\n",
" if q[\"type\"] == \"ORDER\":\n",
" t = re.sub(r\"[^A-Za-z]\", \"\", s).upper()\n",
" return (t, \"์ ์\") if t and set(t) <= set(\"ABCD\") and len(set(t)) == len(t) else (t, \"ํ์์ค๋ฅ\")\n",
" return norm_text(s), \"์ ์\"\n",
"\n",
"\n",
"def grade_exact(q, val):\n",
" if q[\"type\"] == \"MC\":\n",
" return val == str(q[\"answer\"])\n",
" if q[\"type\"] == \"ORDER\":\n",
" return val == q[\"answer\"]\n",
" return val in {norm_text(a) for a in q[\"accept\"]}\n",
"\n",
"\n",
"def grade_fuzzy(q, val, cut=0.75):\n",
" \"\"\"๋จ๋ตํ์ ์คํ์(๊ธ์ ์์ ๋ฐ๋ยทํ ๊ธ์ ๋๋ฝ)๋ฅผ ํ์ฉ(์ ์ฌ๋ cut ์ด์)ํ๋, ์๋ ค์ง ์ค๋ต๊ณผ ๋ ๊ฐ๊น์ฐ๋ฉด ์ค๋ต์ผ๋ก ๋ณธ๋ค.\"\"\"\n",
" if grade_exact(q, val) or q[\"type\"] != \"SA\" or len(val) < 2:\n",
" return grade_exact(q, val)\n",
" best = max(1.0 if sorted(val) == sorted(norm_text(a)) else difflib.SequenceMatcher(None, val, norm_text(a)).ratio() for a in q[\"accept\"])\n",
" worst = max(difflib.SequenceMatcher(None, val, norm_text(w)).ratio() for w in q[\"wrong\"])\n",
" return best >= cut and best > worst\n",
"\n",
"\n",
"def score_of(q, correct):\n",
" return POINTS[q[\"diff\"]] if correct else 0\n",
"\n",
"# ---- ์์ ํ ๋ฌธํญ(10์ ): ํต์ฌ ์์ 5๊ฐ x 2์ ----\n",
"ESSAY = \"๋ฏธํ์ธ ๋๋ก ์ ๊ด์ธกํ์ ๋ ๋ณด๊ณ ์ ๋์ ์ ์ฐจ๋ฅผ ์์ ํ์์ค.\"\n",
"ELEM = {\"๊ด์ธก์ ๋ณด\": ([\"๊ด์ธก ์๊ฐ๊ณผ ๋ฐฉํฅ, ๊ฑฐ๋ฆฌ๋ฅผ ์ ๋๋ค\", \"์ธ์ ์ด๋ ๋ฐฉํฅ ๋ช km์ธ์ง ๊ธฐ๋กํ๋ค\"], [\"์๊ฐ\", \"๋ฐฉํฅ\", \"๊ฑฐ๋ฆฌ\"]),\n",
" \"์ถ์ ํํ\": ([\"ํ์คํ์ง ์์ผ๋ฉด ์ถ์ ์ด๋ผ๊ณ ๋ฐํ๋ค\", \"๋ชจ๋ฅด๋ ๋ถ๋ถ์ ์ถ์ ์์ ๋ช
์ํ๋ค\"], [\"์ถ์ \"]),\n",
" \"์งํ๊ณํต\": ([\"์งํ๊ณํต์ ๋ฐ๋ผ ์ฆ์ ๋ณด๊ณ ํ๋ค\", \"์๋์ฅ์๊ฒ ๋ณด๊ณ ํ๊ณ ์๊ธ์ผ๋ก ์ฌ๋ฆฐ๋ค\"], [\"์งํ๊ณํต\"]),\n",
" \"์น์ธ๋๊ธฐ\": ([\"์งํ๊ด ์น์ธ ์ ์๋ ๋์ํ์ง ์๋๋ค\", \"์น์ธ์ด ๋๊ธฐ ์ ๊น์ง ์กฐ์นํ์ง ์๊ณ ๋๊ธฐํ๋ค\"], [\"์น์ธ\"]),\n",
" \"๊ทผ๊ฑฐ๊ธฐ๋ก\": ([\"ํ๋จ ๊ทผ๊ฑฐ๋ฅผ ๊ธฐ๋กํ๋ค\", \"์กฐ์น ๋ด์ฉ์ ์ผ์ง์ ๋จ๊ธด๋ค\"], [\"๊ธฐ๋ก\", \"๊ทผ๊ฑฐ\"])}\n",
"WRONG_SENT = [\"์น์ธ ์์ด ๋ฐ๋ก ๋์ํ๋ค\", \"๊ธฐ๋ก์ ์ํฉ ์ข
๋ฃ ํ ์๋ตํ๋ค\", \"ํ์ ๋ ์ ๋๋ก ์ผ๋ก ๋ณด๊ณ ํ๋ค\"] # ํค์๋๋ ์์ง๋ง ํ๋ฆฐ ์ง์ \n",
"\n",
"\n",
"def make_essays(sids, seed):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for s in sids:\n",
" have = [e for e in ELEM if rng.random() < 0.65]\n",
" sents = [str(rng.choice(ELEM[e][0])) for e in have]\n",
" if rng.random() < 0.2:\n",
" sents.append(str(rng.choice(WRONG_SENT)))\n",
" rng.shuffle(sents)\n",
" out.append({\"sid\": s, \"text\": \" \".join(sents), \"truth\": 2 * len(have)})\n",
" return out\n",
"\n",
"\n",
"def auto_suggest(text):\n",
" \"\"\"ํค์๋ ํฌํจ ์ฌ๋ถ๋ก ์ ์๋ฅผ '์ ์'ํ๋ค(ํ์ ์๋). ๋ถ์ ๋ฌธ๋งฅ์ ๊ตฌ๋ถํ์ง ๋ชปํ๋ ํ๊ณ๊ฐ ์๋ค.\"\"\"\n",
" hit = [e for e, (_, kws) in ELEM.items() if any(k in text for k in kws)]\n",
" conf = \"๋ฎ์\" if any(w in text for w in (\"์์ด\", \"์๋ต\", \"ํ์ \")) or len(text) < 25 else \"๋ณดํต\"\n",
" return 2 * len(hit), hit, conf\n",
"\n",
"\n",
"class ReviewQueue:\n",
" \"\"\"์ฐ์ ์์ ํ + ํด์ ์ฒด์ธ ๊ฐ์ฌ ๋ก๊ทธ. ๊ต๊ด ๊ฒฐ์ ์์ด๋ ์ ์๋ฅผ ํ์ ํ ์ ์๋ค.\"\"\"\n",
" def __init__(self):\n",
" self.heap, self.items, self.log, self.final = [], {}, [], {}\n",
"\n",
" def _audit(self, event, **kw):\n",
" prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n",
" body = {\"seq\": len(self.log), \"event\": event, **kw}\n",
" self.log.append({**body, \"prev\": prev, \"hash\": hashlib.sha256((prev + json.dumps(body, sort_keys=True, ensure_ascii=False)).encode()).hexdigest()})\n",
"\n",
" def submit(self, iid, prio, t, payload):\n",
" self.items[iid] = {**payload, \"t_in\": t, \"status\": \"PENDING\"}\n",
" heapq.heappush(self.heap, (prio, t, iid))\n",
" self._audit(\"SUBMIT\", iid=iid, prio=prio, suggest=payload.get(\"suggest\"))\n",
"\n",
" def pop(self):\n",
" return heapq.heappop(self.heap)[2] if self.heap else None\n",
"\n",
" def decide(self, iid, reviewer, decision, score, reason, t):\n",
" if not reason:\n",
" raise ValueError(\"์ฌ์ ์๋ ๊ฒฐ์ ์ ๊ธฐ๋กํ ์ ์๋ค\")\n",
" it = self.items[iid]\n",
" it.update(status=decision, score=score, reviewer=reviewer, t_out=t)\n",
" self.final[iid] = score\n",
" self._audit(\"DECIDE\", iid=iid, reviewer=reviewer, decision=decision, score=score, reason=reason)\n",
"\n",
" def finalize_without_review(self, iid):\n",
" if self.items[iid][\"status\"] == \"PENDING\":\n",
" raise PermissionError(\"๊ต๊ด ๊ฒฐ์ ์ ์๋ ํ์ ๊ธ์ง\")\n",
"\n",
" def verify(self):\n",
" prev = \"GENESIS\"\n",
" for e in self.log:\n",
" body = {k: v for k, v in e.items() if k not in (\"prev\", \"hash\")}\n",
" if e[\"prev\"] != prev or hashlib.sha256((prev + json.dumps(body, sort_keys=True, ensure_ascii=False)).encode()).hexdigest() != e[\"hash\"]:\n",
" return False, e[\"seq\"]\n",
" prev = e[\"hash\"]\n",
" return True, None\n",
"\n",
"\n",
"rng = np.random.default_rng(3)\n",
"df = make_responses(40, seed=7)\n",
"df[\"val\"] = [parse(QIDX[q], r)[0] for q, r in zip(df[\"qid\"], df[\"raw\"])]\n",
"df[\"correct\"] = [grade_fuzzy(QIDX[q], v) for q, v in zip(df[\"qid\"], df[\"val\"])]\n",
"df[\"score\"] = [score_of(QIDX[q], c) for q, c in zip(df[\"qid\"], df[\"correct\"])]\n",
"total = df.groupby(\"sid\")[\"score\"].sum()\n",
"PASS = 0.7 * (sum(POINTS[q[\"diff\"]] for q in BANK) + 10)\n",
"\n",
"# 1) ์ด์ ์ ๊ธฐ: ์๋ ์ฑ์ ์ค๋ต ์ฒ๋ฆฌ๋ ๋จ๋ตํ ์ค ์์์๊ฐ ์ด์๋ฅผ ๋ธ ๊ฑด(์ค์ ๋ง์ ๋ต์ ๋๋ถ๋ถ ์ด์ ์ ๊ธฐ, ํ๋ฆฐ ๋ต๋ ์ผ๋ถ ์ ๊ธฐ)\n",
"sa_wrong = df[(df[\"qid\"].map(lambda q: QIDX[q][\"type\"]) == \"SA\") & ~df[\"correct\"] & (df[\"raw\"] != \"\")]\n",
"appeal = sa_wrong[sa_wrong[\"truth_correct\"] | (rng.random(len(sa_wrong)) < 0.08)]\n",
"essays = make_essays(sorted(df[\"sid\"].unique()), seed=5)\n",
"print(f\"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ์ด์ ์ ๊ธฐ {len(appeal)}๊ฑด, ์์ ํ ๋ต์ {len(essays)}๊ฑด์ด ๊ต๊ด ๊ฒํ ๋๊ธฐ์ด์ ๋ค์ด๊ฐ๋ค.\")\n",
"\n",
"q = ReviewQueue()\n",
"t = 0.0\n",
"for e in essays:\n",
" sug, hit, conf = auto_suggest(e[\"text\"])\n",
" est = total[e[\"sid\"]] + sug\n",
" prio = (0 if abs(est - PASS) <= 6 else 1) * 10 + (0 if conf == \"๋ฎ์\" else 1) # ํฉ๊ฒฉ์ ๊ทผ์ฒยท์ ์ ๋ขฐ ๋จผ์ \n",
" q.submit(f\"E-{e['sid']}\", prio, t, {\"kind\": \"์์ ํ\", \"sid\": e[\"sid\"], \"suggest\": sug, \"conf\": conf, \"truth\": e[\"truth\"], \"text\": e[\"text\"]})\n",
" t += 0.05\n",
"for _, r in appeal.iterrows():\n",
" est = total[r[\"sid\"]]\n",
" prio = (0 if abs(est - PASS) <= 6 else 1) * 10 + 2\n",
" q.submit(f\"A-{r['rid']}\", prio, t, {\"kind\": \"์ด์ ์ ๊ธฐ\", \"sid\": r[\"sid\"], \"qid\": r[\"qid\"], \"raw\": r[\"raw\"], \"suggest\": 0,\n",
" \"truth\": score_of(QIDX[r[\"qid\"]], r[\"truth_correct\"])})\n",
" t += 0.05\n",
"\n",
"# 2) ์๋ ํ์ ์๋๋ ์ฐจ๋จ๋์ด์ผ ํ๋ค\n",
"blocked = 0\n",
"for iid in list(q.items)[:5]:\n",
" try:\n",
" q.finalize_without_review(iid)\n",
" except PermissionError:\n",
" blocked += 1\n",
"try:\n",
" q.decide(list(q.items)[0], \"๊ต๊ดA\", \"์ธ์ \", 2, \"\", t)\n",
" no_reason = \"ํต๊ณผ(์ค๋ฅ)\"\n",
"except ValueError:\n",
" no_reason = \"์ฐจ๋จ\"\n",
"print(f\"๊ฒฐ์ ์ ์๋ ํ์ ์๋ 5๊ฑด ์ค ์ฐจ๋จ {blocked}๊ฑด, ์ฌ์ ์๋ ๊ฒฐ์ {no_reason}\")\n",
"\n",
"# 3) ๊ต๊ด 2๋ช
์ด ์ฐ์ ์์๋๋ก ๊ฒํ (๋ชจ์ ๊ต๊ด์ ๊ธฐ์ค ๋ต์์ผ๋ก ํ๋จ: ๊ฑด๋น ์์ ํ 2๋ถ, ์ด์ ์ ๊ธฐ 1๋ถ)\n",
"clock = {\"๊ต๊ดA\": 0.0, \"๊ต๊ดB\": 0.0}\n",
"while True:\n",
" iid = q.pop()\n",
" if iid is None:\n",
" break\n",
" it = q.items[iid]\n",
" who = min(clock, key=clock.get)\n",
" clock[who] = max(clock[who], it[\"t_in\"]) + (2.0 if it[\"kind\"] == \"์์ ํ\" else 1.0)\n",
" if it[\"kind\"] == \"์์ ํ\":\n",
" dec = \"์น์ธ\" if it[\"truth\"] == it[\"suggest\"] else \"์์ \"\n",
" reason = \"์ ์ ์ ์์ ์์ ํ๋จ ์ผ์น\" if dec == \"์น์ธ\" else f\"์์ ์ฌํ๋จ(์ ์ {it['suggest']} -> {it['truth']})\"\n",
" else:\n",
" dec = \"์ธ์ \" if it[\"truth\"] > 0 else \"๊ธฐ๊ฐ\"\n",
" reason = f\"'{it['raw']}' ์๋ฏธ์ ์ ๋ต ์ธ์ \" if dec == \"์ธ์ \" else f\"'{it['raw']}'๋ ์ค๋ต ์ ์ง\"\n",
" q.decide(iid, who, dec, it[\"truth\"], reason, clock[who])\n",
"\n",
"R = pd.DataFrame(q.items.values())\n",
"R[\"wait\"] = R[\"t_out\"] - R[\"t_in\"]\n",
"R[\"border\"] = [abs(total[s] + (sg if k == \"์์ ํ\" else 0) - PASS) <= 6 for s, sg, k in zip(R[\"sid\"], R[\"suggest\"], R[\"kind\"])]\n",
"print(\"\\n[๊ฒํ ๊ฒฐ๊ณผ]\")\n",
"print(R.groupby([\"kind\", \"status\"]).size().to_string())\n",
"ess = R[R[\"kind\"] == \"์์ ํ\"]\n",
"print(f\"์์ ํ ์๋ ์ ์ ์ ์์ ๊ต๊ด ํ๋จ ์ผ์น {(ess['status'] == '์น์ธ').mean():.1%}, ์์ {(ess['status'] == '์์ ').sum()}๊ฑด\"\n",
" f\"(ํ๋ฆฐ ์ง์ ์ ํค์๋์ ์์ ์ ์ ํฌํจ)\")\n",
"print(f\"๋๊ธฐ์๊ฐ(๋ถ): ํฉ๊ฒฉ์ ๊ทผ์ฒ ํ๊ท {R.loc[R['border'], 'wait'].mean():.1f}, ๊ทธ ์ธ ํ๊ท {R.loc[~R['border'], 'wait'].mean():.1f}, ์ต๋ {R['wait'].max():.1f}\")\n",
"\n",
"n_dec = sum(e[\"event\"] == \"DECIDE\" for e in q.log)\n",
"print(f\"์น์ธ/๋ฐ๋ ค ์ฒ๋ฆฌ {len(R)}๊ฑด, ๊ฐ์ฌ ๋ก๊ทธ ๊ธฐ๋ก {n_dec}๊ฑด, ๋ก๊น
์ฑ๊ณต๋ฅ {n_dec / len(R):.0%} (๋ชฉํ 100%: {'๋ฌ์ฑ' if n_dec == len(R) else '๋ฏธ๋ฌ'})\")\n",
"ok, _ = q.verify()\n",
"q.log[7][\"score\"] = 10 # ๊ฐ์ฌ ๋ก๊ทธ ์ฌํ ๋ณ์กฐ ์๋\n",
"ok2, where = q.verify()\n",
"print(f\"๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ: ์๋ณธ {'์ ์' if ok else '์ด์'}, ๋ณ์กฐ ํ {'์ ์' if ok2 else f'์ด์ ํ์ง(seq {where})'}\")\n",
"\n",
"changed = R[(R[\"kind\"] == \"์ด์ ์ ๊ธฐ\") & (R[\"status\"] == \"์ธ์ \")].groupby(\"sid\")[\"score\"].sum()\n",
"newtot = total.add(changed, fill_value=0) + ess.set_index(\"sid\")[\"score\"]\n",
"flip = int(((total + ess.set_index(\"sid\")[\"suggest\"] >= PASS) != (newtot >= PASS)).sum())\n",
"print(f\"๊ต๊ด ๊ฒํ ๋ก ํฉ๊ฒฉ/๋ถํฉ๊ฒฉ์ด ๋ฐ๋ ์์์ {flip}๋ช
-> ์๋ ์ฑ์ ๋ง์ผ๋ก ํ์ ํ๋ฉด ์ ๋๋ ์ด์ \")\n",
"print(\"\\n[HITL ๊ฒํ ์ง์นจ ์์ฝ] 1) ์๋ ์ ์๋ ์ ์์ผ ๋ฟ ๊ต๊ด์ด ํ์ 2) ์ธ์ ยท๊ธฐ๊ฐยท์์ ๋ชจ๋ ์ฌ์ ํ์\"\n",
" \" 3) ํฉ๊ฒฉ์ ๊ทผ์ฒยท์ ์ ๋ขฐ ๋ต์ ์ฐ์ 4) ๊ฐ์ฌ ๋ก๊ทธ๋ ์์ ๋ถ๊ฐ, ์ด์ ์ฌ์ฌ์ ์๊ธ ๊ต๊ด(์งํ ์ฑ
์์ )์ด ํ๋จ\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
07 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[๊ต์ก์ฉ๊ฐ์์๋ฃ] ์ด์ ์ ๊ธฐ 45๊ฑด, ์์ ํ ๋ต์ 120๊ฑด์ด ๊ต๊ด ๊ฒํ ๋๊ธฐ์ด์ ๋ค์ด๊ฐ๋ค.\n",
"๊ฒฐ์ ์ ์๋ ํ์ ์๋ 5๊ฑด ์ค ์ฐจ๋จ 5๊ฑด, ์ฌ์ ์๋ ๊ฒฐ์ ์ฐจ๋จ\n",
"\n",
"[๊ฒํ ๊ฒฐ๊ณผ]\n",
"kind status\n",
"์์ ํ ์์ 54\n",
" ์น์ธ 66\n",
"์ด์ ์ ๊ธฐ ๊ธฐ๊ฐ 7\n",
" ์ธ์ 38\n",
"์์ ํ ์๋ ์ ์ ์ ์์ ๊ต๊ด ํ๋จ ์ผ์น 55.0%, ์์ 54๊ฑด(ํ๋ฆฐ ์ง์ ์ ํค์๋์ ์์ ์ ์ ํฌํจ)\n",
"๋๊ธฐ์๊ฐ(๋ถ): ํฉ๊ฒฉ์ ๊ทผ์ฒ ํ๊ท 20.0, ๊ทธ ์ธ ํ๊ท 94.7, ์ต๋ 137.4\n",
"์น์ธ/๋ฐ๋ ค ์ฒ๋ฆฌ 165๊ฑด, ๊ฐ์ฌ ๋ก๊ทธ ๊ธฐ๋ก 165๊ฑด, ๋ก๊น
์ฑ๊ณต๋ฅ 100% (๋ชฉํ 100%: ๋ฌ์ฑ)\n",
"๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ: ์๋ณธ ์ ์, ๋ณ์กฐ ํ ์ด์ ํ์ง(seq 7)\n",
"๊ต๊ด ๊ฒํ ๋ก ํฉ๊ฒฉ/๋ถํฉ๊ฒฉ์ด ๋ฐ๋ ์์์ 3๋ช
-> ์๋ ์ฑ์ ๋ง์ผ๋ก ํ์ ํ๋ฉด ์ ๋๋ ์ด์ \n",
"\n",
"[HITL ๊ฒํ ์ง์นจ ์์ฝ] 1) ์๋ ์ ์๋ ์ ์์ผ ๋ฟ ๊ต๊ด์ด ํ์ 2) ์ธ์ ยท๊ธฐ๊ฐยท์์ ๋ชจ๋ ์ฌ์ ํ์ 3) ํฉ๊ฒฉ์ ๊ทผ์ฒยท์ ์ ๋ขฐ ๋ต์ ์ฐ์ 4) ๊ฐ์ฌ ๋ก๊ทธ๋ ์์ ๋ถ๊ฐ, ์ด์ ์ฌ์ฌ์ ์๊ธ ๊ต๊ด(์งํ ์ฑ
์์ )์ด ํ๋จ\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
08. ์ ๋ถ๋์ ๋์ ์์ ๋ชจ์ ๋๊ท๋ชจ ํธ๋ํฝ ์ฑ์ ๋ถํ ํ
์คํธ ๋ฐ ์ต์ ํ\n",
"**์์์๊ฐ**: ์ฝ 3.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"์ ๋ถ๋์ ๋์ ์์ ๋ชจ์ ๋๊ท๋ชจ ํธ๋ํฝ ์ฑ์ ๋ถํ ํ
์คํธ๋ฅผ ์ํํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ ๋ถ๋์์ด ๋์์ ์์ํ๋ ์ํฉ์ ๋ชจ์ํด ์ฑ์ ์์คํ
์ด ๋๊ท๋ชจ ์ ์ถ๊ณผ ์ฅ์ ์์์๋ ๋ฉ์ถ๊ฑฐ๋ ๋ต์์ ์์ง ์๋์ง ์ํํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **ํ์ 120๋ช
์ ์ถ๋์ 3๋ฐฐยท6๋ฐฐยท11๋ฐฐ๋ก ๋๋ฆฌ๊ณ ์ด์ฅ๋ฌธยท์ ์ด๋ฌธ์ยท์๋ ๋ฌธํญ ๋ฑ ์์ ๋ต์๊ณผ ์ฑ์ ๊ธฐ ์๋ต ์ง์ฐ(์ฅ์ 2%) ์ฃผ์
ํ์ฌ ์ฒ๋ฆฌ ์ง์ฐ ๋ฐ ๋ณต์๋ ฅ์ ์ํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `tests/trn02_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": [
"\"\"\"์ ๋ถ๋์ ๋์ ์์๋ฅผ ๋ชจ์ํด ํ์(120๋ช
) ๋๋น ์ต๋ 1,000% ๋์ด๋ ์ ์ถ๋๊ณผ ์์ ๋ต์ยท์ฑ์ ๊ธฐ ์ฅ์ ๋ฅผ ์ฃผ์
ํ๋ ์ฑ์ ๋ถํ ํ
์คํธ๋ฅผ ์ํํ๋ค. ์ฒ๋ฆฌ๋, ์์คํ
๋ค์ด, ๋ฐ์ดํฐ ์ ์ค์ ์ธก์ ํ๊ณ , ์ฅ์ ์ '์ฑ์ ๋ณด๋ฅ'๋ก ๋ณด์กดํ๋ ์์ ์คํจ ๋์๊ณผ ์บ์ ์ต์ ํ ์ ํ ์ฒ๋ฆฌ๋์ ๋น๊ตํ๋ค.\"\"\"\n",
"import difflib\n",
"import functools\n",
"import re\n",
"import time\n",
"import tracemalloc\n",
"import unicodedata\n",
"from collections import deque\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฐฉ๊ณตยท๋๋ก ์๋ณ ํด์ฆ ๋ฌธํญ ์ํ๊ณผ ๊ฐ์ ์์์ ์๋ต ์์ฑ๊ธฐ\n",
"# ๋ฌธํญ์ ์ผ๋ฐ ์์น ์์ค์ ๊ฐ์ ๋ฌธํญ์ด๋ฉฐ ์ค์ ๊ต๋ฒยท๊ต์ ๊ท์น ์๋ฌธ์ด ์๋๋ค. ์์์๋ ๊ฐ๋ช
(S001~)์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"AREAS = [\"ํ์์๋ณ\", \"ํผ์์๋ณ\", \"๊ฒฝ๋ณด๋ณด๊ณ \", \"๊ต์ ํต์ \", \"๋๋๋ก ์ผ๋ฐ\"]\n",
"ETYPES = [\"๊ฐ๋
ํผ๋\", \"ํผ์ ์ค์ธ\", \"์ ์ฐจ ๋๋ฝ\"]\n",
"POINTS = {1: 2, 2: 3, 3: 5}\n",
"C, F, P = ETYPES\n",
"\n",
"\n",
"def _mc(qid, area, diff, stem, opts, ans, dist):\n",
" return {\"qid\": qid, \"type\": \"MC\", \"area\": area, \"diff\": diff, \"stem\": stem, \"options\": opts, \"answer\": ans,\n",
" \"dist\": dict(zip([i for i in range(1, 5) if i != ans], dist))}\n",
"\n",
"\n",
"def _sa(qid, area, diff, stem, accept, wrong):\n",
" return {\"qid\": qid, \"type\": \"SA\", \"area\": area, \"diff\": diff, \"stem\": stem, \"accept\": accept, \"wrong\": wrong}\n",
"\n",
"\n",
"def _order(qid, area, diff, stem):\n",
" return {\"qid\": qid, \"type\": \"ORDER\", \"area\": area, \"diff\": diff, \"stem\": stem, \"answer\": \"ABCD\"}\n",
"\n",
"\n",
"BANK = [\n",
" _mc(\"Q01\", \"ํ์์๋ณ\", 1, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์์ด ์ผ๊ฐํ ๋ ๊ฐ๋ฅผ ๊ฐ์ง ์ํ ๊ณ ์ ์ต ์ค๋ฃจ์ฃ์?\", [\"์ฟผ๋์ฝฅํฐํ\", \"์ผ๊ฐ์ต ๊ณ ์ ์ตํ\", \"ํ์ ์ต ํฌ๊ธฐํ\", \"์กฐ๋ฅ\"], 2, [C, C, F]),\n",
" _mc(\"Q02\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๊ฐ ์ญ์๋ก ๋ฐฐ์น๋ ์ค๋ฃจ์ฃ์?\", [\"๊ณ ์ ์ต ๋๋ก ํ\", \"์ฟผ๋์ฝฅํฐํ\", \"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\"], 2, [C, C, C]),\n",
" _mc(\"Q03\", \"ํ์์๋ณ\", 2, \"๊ฐ๋๊ณ ๊ธด ๋์ฒด ์ค๊ฐ์ ์์ ๋ ๊ฐ๊ฐ ๋ถ์ ์ค๋ฃจ์ฃ์?\", [\"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\", \"๋ฏผ๊ฐ ๊ฒฝ๋นํ๊ธฐํ\", \"์กฐ๋ฅ\"], 1, [C, F, F]),\n",
" _mc(\"Q04\", \"ํ์์๋ณ\", 3, \"์๊ฑฐ๋ฆฌ๋ผ ์ค๋ฃจ์ฃ ์๋ณ์ด ์ด๋ ค์ธ ๋ ๋จผ์ ํ ํ๋์?\", [\"์ถ์ ์ ํ์ผ๋ก ํ์ ๋ณด๊ณ \", \"๊ด์ธก ์ ๋ณด๋ฅผ ๊ทธ๋๋ก ๋ณด๊ณ ํ๊ณ ์ถ๊ฐ ๊ด์ธก\", \"๊ด์ธก ์ค๋จ\", \"๋ฏผ๊ฐ๊ธฐ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _mc(\"Q05\", \"ํผ์์๋ณ\", 2, \"์๋ณ ์ ํธ ์๋ต์ด ์๋ ํญ๊ณต๊ธฐ์ ๋ํ ์ฌ๋ฐ๋ฅธ ํ๋จ์?\", [\"์ฆ์ ์ ๊ธฐ๋ก ํ์ \", \"๋ฏธํ์ธ ํญ๊ณต๊ธฐ๋ก ๋ถ๋ฅํ๊ณ ์ถ๊ฐ ํ์ธ\", \"์๊ตฐ๊ธฐ๋ก ๊ฐ์ฃผ\", \"๋ฌด์\"], 2, [F, F, P]),\n",
" _mc(\"Q06\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด์ ์ผ์นํ๋ ์ ์ ๋นํ์ฒด์ ๋ถ๋ฅ๋?\", [\"์ ๋๋ก \", \"๋ฏผ๊ฐ๊ธฐ ๊ฐ๋ฅ์ฑ ๋์, ์ถ๊ฐ ํ์ธ\", \"์ํญ๋ฏธ์ฌ์ผ\", \"์๊ตฐ ์ ์ฐฐ๊ธฐ๋ก ํ์ \"], 2, [F, C, F]),\n",
" _mc(\"Q07\", \"ํผ์์๋ณ\", 3, \"์๊ตฐ ๋๋ก ์ด์ฉ ๊ณต์ง๊ฐ ์๋ ๊ณต์ญ์์ ๋ฏธํ์ธ ๋๋ก ์ ๋ณด๋ฉด ์ฐ์ ํ์ธํ ๊ฒ์?\", [\"๊ธฐ์ ์ ๋ณด\", \"์๊ตฐ ์ด์ฉ ๊ณํ๊ณผ ์๋ณ ์ ํธ\", \"๋์ ์ค๋น ์ํ\", \"์ฃผ๋ณ ์งํ\"], 2, [C, P, C]),\n",
" _mc(\"Q08\", \"ํผ์์๋ณ\", 3, \"์กฐ๋ฅ ๋ฌด๋ฆฌ์ ์ํ ๋๋ก ๊ตฐ์ง ๊ตฌ๋ถ์ ๊ฐ์ฅ ๋์์ด ๋๋ ์ ๋ณด๋?\", [\"ํฌ๊ธฐ๋ง ๋น๊ต\", \"๊ถค์ ์ ๊ท์น์ฑ๊ณผ ์ด ์ ํธ\", \"์์\", \"๊ด์ธก ์๊ฐ\"], 2, [C, C, C]),\n",
" _mc(\"Q09\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๊ณต์ค ์ํ ์ต์ด ๋ณด๊ณ ์ ํฌํจํ์ง ์์๋ ๋๋ ๊ฒ์?\", [\"๊ด์ธก ์๊ฐ\", \"๋ฐฉํฅ๊ณผ ๊ฑฐ๋ฆฌ\", \"์ถ์ ์ ํ\", \"๊ด์ธก์ ๊ฐ์ธ ์ฐ๋ฝ์ฒ\"], 4, [P, P, P]),\n",
" _mc(\"Q10\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๊ฒฝ๋ณด ์ ํ ํ ์ํฉ์ด ๋ฐ๋๋ฉด?\", [\"์ต์ด ๋ณด๊ณ ์ ์ง\", \"๋ณ๊ฒฝ ์ฌํญ์ ์ฆ์ ์ถ๊ฐ ๋ณด๊ณ \", \"์ํฉ ์ข
๋ฃ ํ ์ผ๊ด ๋ณด๊ณ \", \"๊ฐ์ธ ํ๋จ์ผ๋ก ๊ฒฝ๋ณด ํด์ \"], 2, [P, P, P]),\n",
" _mc(\"Q11\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ณด๊ณ ๋ด์ฉ์ด ๋ถํ์คํ ๋ ์ฌ๋ฐ๋ฅธ ํํ์?\", [\"ํ์ ํํ์ผ๋ก ๋ณด๊ณ \", \"'์ถ์ '์์ ๋ฐํ๊ณ ๊ทผ๊ฑฐ์ ํจ๊ป ๋ณด๊ณ \", \"๋ณด๊ณ ๋ณด๋ฅ\", \"๋ค๋ฅธ ๊ด์ธก์ ๋ณด๊ณ ๋ฅผ ๋ณต์ฌ\"], 2, [C, P, P]),\n",
" _mc(\"Q12\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋๋ก ํ์ง ํ ๋ณด๊ณ ๊ฒฝ๋ก๋ก ์๋ง์ ๊ฒ์?\", [\"์ธ์ ๋ถ๋์๋ง ์ ํ\", \"์งํ๊ณํต์ ๋ฐ๋ผ ๋ณด๊ณ \", \"๊ฐ์ธ ๋ฉ์ ์ ๋ก ๊ณต์ \", \"๋ณด๊ณ ์์ด ๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q13\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ๋ง์ผ๋ก ๋์์ ๊ฒฐ์ ํ ์ ์๋๊ฐ?\", [\"๊ฐ๋ฅํ๋ค\", \"์ธ๊ฐ ์งํ๊ด์ ์น์ธ์ด ํ์ํ๋ค\", \"์ ๋ขฐ๋ 90% ์ด์์ด๋ฉด ๊ฐ๋ฅ\", \"์ผ๊ฐ์๋ง ๊ฐ๋ฅ\"], 2, [C, C, C]),\n",
" _mc(\"Q14\", \"๊ต์ ํต์ \", 2, \"๋ฏผ๊ฐ ํผํด ๊ฐ๋ฅ์ฑ์ด ํ์ธ๋๋ฉด?\", [\"๊ทธ๋๋ก ์งํ\", \"์ฆ์ ๋ณด๊ณ ํ๊ณ ์งํ๊ด ํ๋จ์ ๋ฐ๋๋ค\", \"์กฐ์น ํ ๋ณด๊ณ \", \"๋ฌด์\"], 2, [P, P, F]),\n",
" _mc(\"Q15\", \"๊ต์ ํต์ \", 3, \"์๋ณ์ด ๋ถํ์คํ ํ์ ์ ๋ํ ์์น์?\", [\"์ํ์ผ๋ก ๊ฐ์ฃผํด ์ฆ์ ๋์\", \"์๋ณ ํ์ธ ์ ๊น์ง ์น์ธ ์ ์ฐจ๋ฅผ ๋ฐ๋ฅธ๋ค\", \"๋ฌด์กฐ๊ฑด ๋ฌด์\", \"๋ค์๊ฒฐ๋ก ๊ฒฐ์ \"], 2, [F, F, C]),\n",
" _mc(\"Q16\", \"๊ต์ ํต์ \", 2, \"ํ๋จ ๊ทผ๊ฑฐ๋ฅผ ๊ธฐ๋กํด์ผ ํ๋ ์ด์ ๋?\", [\"ํ์์ ํ์\", \"์ฌํ ๊ฒํ ์ ์ฑ
์ ์ถ์ \", \"๋ณด๊ณ ์๋ต\", \"๊ธฐ๋ก ๋ถํ์\"], 2, [C, P, P]),\n",
" _mc(\"Q17\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"๋๋๋ก ์ฒด๊ณ์ ์ผ๋ฐ์ ์ฒ๋ฆฌ ์์๋?\", [\"๋์-ํ์ง-์๋ณ\", \"ํ์ง-์๋ณ-๋ณด๊ณ ยทํ๋จ-๋์\", \"์๋ณ-๋์-ํ์ง\", \"๋ณด๊ณ -ํ์ง-๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q18\", \"๋๋๋ก ์ผ๋ฐ\", 3, \"๋ ์ด๋๋ง์ผ๋ก ์กฐ๋ฅ์ ๋๋ก ๊ตฌ๋ถ์ด ์ด๋ ค์ด ์ด์ ๋?\", [\"๋ฐ์ฌ ์ ํธ ํฌ๊ธฐ๊ฐ ๋น์ทํ ์ ์์ด์\", \"๋ ์ด๋๋ ์ผ๊ฐ์ ๋ชป ์จ์\", \"๋๋ก ์๋ ๊ธ์์ด ์์ด์\", \"์กฐ๋ฅ๋ ํ์ง๋์ง ์์์\"], 1, [C, C, C]),\n",
" _mc(\"Q19\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ ์๊ดํ(EO) ์ผ์์ ์ฝ์ ์ด ์ปค์ง๋ ์กฐ๊ฑด์?\", [\"๋ง์ ์ฃผ๊ฐ\", \"์๊ฐยท์ผ๊ฐ\", \"์ ์ ํ์ \", \"๊ทผ๊ฑฐ๋ฆฌ\"], 2, [C, C, C]),\n",
" _mc(\"Q20\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"๊ฒฝ๋ณด๊ฐ ๋ฐ๋ณตํด์ ์ค๊ฒฝ๋ณด๋ก ํ๋ช
๋๋ฉด?\", [\"๊ฒฝ๋ณด ์ฅ์น๋ฅผ ๋๋ค\", \"์ฌ๋ก๋ฅผ ๊ธฐ๋กยท๋ณด๊ณ ํ๊ณ ๊ธฐ์ค ์ฌ๊ฒํ ์์ฒญ\", \"๋ฌด์ํ๋ค\", \"๋ชจ๋ ์ค์ ์ํ์ผ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _sa(\"Q21\", \"ํผ์์๋ณ\", 1, \"์๊ตฐยท์ ยท๋ฏผ๊ฐ์ ๊ตฌ๋ถํ๋ ์ ์ฐจ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"ํผ์์๋ณ\", \"IFF\"], {\"ํ์ ํ๋\": C, \"์ ๊ธฐํ์ \": F, \"๊ฒฝ๊ณ\": C}),\n",
" _sa(\"Q22\", \"ํ์์๋ณ\", 2, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์๋ ์ผ๊ฐํ ๋ ๊ฐ ํํ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ผ๊ฐ์ต\", \"๋ธํ์ต\", \"delta\"], {\"์ง์ ์ต\": C, \"ํํด์ต\": C, \"์ฟผ๋์ฝฅํฐ\": C}),\n",
" _sa(\"Q23\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ถํ์คํ ๊ด์ธก์ ๋ณด๊ณ ํ ๋ ๋ถ์ด๋ ํํ์?\", [\"์ถ์ \"], {\"ํ์ \": C, \"ํ์ธ\": C, \"์๋ต\": P}),\n",
" _sa(\"Q24\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ์ ๋ํด ์ต์ข
๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ์ฃผ์ฒด๋?\", [\"์งํ๊ด\"], {\"AI\": C, \"์ด์ฉ๋ณ\": P, \"๋ ์ด๋\": C}),\n",
" _sa(\"Q25\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"์กฐ๋ฅ๋ฅผ ๋๋ก ์ผ๋ก ์๋ชป ๊ฒฝ๋ณดํ๋ ๊ฒ์ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ค๊ฒฝ๋ณด\", \"์คํ\", \"false alarm\"], {\"๋ฏธํ\": C, \"์ฌ๊ฒฝ๋ณด\": C, \"์ ๊ธฐ\": F}),\n",
" _sa(\"Q26\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด๊ฐ ํ์ธ๋ ์ฌ๊ฐ๊ธฐ๋ ์ด๋ป๊ฒ ๋ถ๋ฅํ๋๊ฐ?\", [\"๋ฏผ๊ฐ๊ธฐ\", \"๋ฏผํญ๊ธฐ\"], {\"์ ๊ธฐ\": F, \"๋ฏธํ์ธ๊ธฐ\": P, \"์๊ตฐ๊ธฐ\": F}),\n",
" _sa(\"Q27\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๋ฅผ ๊ฐ์ง ๋๋ก ํํ๋?\", [\"์ฟผ๋์ฝฅํฐ\", \"quadcopter\"], {\"ํฅ์ฌ์ฝฅํฐ\": C, \"๊ณ ์ ์ต\": C, \"ํฌ๊ธฐ\": F}),\n",
" _sa(\"Q28\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋ณด๊ณ ๋ ๋ฌด์์ ๋ฐ๋ผ ์ฌ๋ผ๊ฐ๋๊ฐ?\", [\"์งํ๊ณํต\"], {\"์ธ์ ๋ถ๋\": P, \"๊ฐ์ธ๋ฉ์ ์ \": P, \"ํต์ ๋ง\": C}),\n",
" _order(\"Q29\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"์ต์ด ๋ณด๊ณ ์์: A ๊ด์ธก ์๊ฐ, B ๋ฐฉํฅยท๊ฑฐ๋ฆฌ, C ์ถ์ ์ ํ, D ๊ด์ธก์ ์กฐ์น\"),\n",
" _order(\"Q30\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ฒ๋ฆฌ ์์: A ํ์ง, B ์๋ณ, C ๋ณด๊ณ ยทํ๋จ, D ๋์\"),\n",
" _order(\"Q31\", \"๊ต์ ํต์ \", 3, \"ํต์ ์์: A ์๋ณ ํ์ธ, B ์๊ธ ์น์ธ ์์ฒญ, C ์น์ธ ์๋ น, D ์กฐ์น ๊ธฐ๋ก\"),\n",
" _order(\"Q32\", \"ํผ์์๋ณ\", 3, \"ํ์ธ ์์: A ์ดํญ์ ๋ณด ๋์กฐ, B ์๋ณ ์ ํธ ํ์ธ, C ๋ฏธํ์ธ ๋ถ๋ฅ, D ๋ณด๊ณ \"),\n",
"]\n",
"QIDX = {q[\"qid\"]: q for q in BANK}\n",
"UNITS = [\"๊ฐ์1์ค๋\", \"๊ฐ์2์ค๋\", \"๊ฐ์3์ค๋\"]\n",
"_CIRC = \"โ โกโขโฃ\"\n",
"PARA = {\"Q21\": \"์๊ตฐ์ ๊ตฐ ๊ตฌ๋ถ\", \"Q22\": \"์ธ๋ชจ ๋ ๊ฐ\", \"Q23\": \"์ถ์ ๋จ์ผ๋ก ํ๊ธฐ\", \"Q24\": \"์ค๋์ฅ(์งํ๊ด)\", \"Q25\": \"์๋ชป๋ ๊ฒฝ๋ณด\",\n",
" \"Q26\": \"๋ฏผ๊ฐ ์ฌ๊ฐ๊ธฐ\", \"Q27\": \"4๋ฐ ๋๋ก \", \"Q28\": \"์งํ ๋ผ์ธ\"} # ๋ป์ ๋ง์ง๋ง ์ธ์ ๋ต์ ๋ชฉ๋ก์ ์๋ ํํ\n",
"\n",
"\n",
"def make_soldiers(n_per_unit, seed):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for u, unit in enumerate(UNITS):\n",
" base = [0.3, -0.1, 0.0][u]\n",
" for i in range(n_per_unit):\n",
" off = rng.normal(0, 0.5, len(AREAS)) + (np.array([0, 0, 0, -0.6, 0]) if u == 1 else 0)\n",
" out.append({\"sid\": f\"S{len(out) + 1:03d}\", \"unit\": unit, \"theta\": dict(zip(AREAS, base + rng.normal(0, 0.6) + off)),\n",
" \"tend\": dict(zip(ETYPES, rng.dirichlet([1.2, 1.2, 1.2])))})\n",
" return out\n",
"\n",
"\n",
"def p_correct(theta, diff):\n",
" return 1 / (1 + np.exp(-1.7 * (theta - (diff - 2) * 0.9 + 0.9)))\n",
"\n",
"\n",
"def _typo(s, rng):\n",
" if len(s) < 2:\n",
" return s\n",
" i = int(rng.integers(len(s) - 1))\n",
" return s[:i] + s[i + 1] + s[i] + s[i + 2:] if len(s) <= 3 or rng.random() < 0.5 else s[:i] + s[i + 1:]\n",
"\n",
"\n",
"def _fmt_mc(k, rng):\n",
" r = rng.random()\n",
" if r < 0.55:\n",
" return str(k)\n",
" return rng.choice([_CIRC[k - 1], f\"{k}๋ฒ\", \"ABCD\"[k - 1], \"abcd\"[k - 1] + \")\", f\" {k} \", chr(0xFF10 + k), f\"{k}.\"])\n",
"\n",
"\n",
"def _fmt_sa(s, rng):\n",
" r = rng.random()\n",
" if r < 0.45:\n",
" return s\n",
" if r < 0.6:\n",
" return \" \".join(s) if len(s) <= 4 else s[:2] + \" \" + s[2:]\n",
" if r < 0.72:\n",
" return s + rng.choice([\"์
๋๋ค\", \"์ด๋ค\", \".\", \"์\"])\n",
" if r < 0.8:\n",
" return s.upper() if s.isascii() else \"\".join(chr(ord(ch) + 0xFEE0) if \"!\" <= ch <= \"~\" else ch for ch in s) + \" \"\n",
" return _typo(s, rng)\n",
"\n",
"\n",
"def answer(q, sol, rng):\n",
" \"\"\"ํ ๋ฌธํญ ์๋ต: (์๋ณธ ์๋ต ๋ฌธ์์ด, ์๋ํ ์ ๋ต ์ฌ๋ถ, ์๋ํ ๋ต, ์ค๋ต ์ ํ).\"\"\"\n",
" ok = rng.random() < p_correct(sol[\"theta\"][q[\"area\"]], q[\"diff\"])\n",
" if q[\"type\"] == \"MC\":\n",
" if ok:\n",
" k, et = q[\"answer\"], None\n",
" else:\n",
" ks = list(q[\"dist\"]); w = np.array([sol[\"tend\"][q[\"dist\"][k]] for k in ks])\n",
" k = int(rng.choice(ks, p=w / w.sum())); et = q[\"dist\"][k]\n",
" return _fmt_mc(k, rng), ok, str(k), et\n",
" if q[\"type\"] == \"SA\":\n",
" if ok and rng.random() < 0.06:\n",
" return PARA[q[\"qid\"]], ok, PARA[q[\"qid\"]], None\n",
" if ok:\n",
" s, et = str(rng.choice(q[\"accept\"])), None\n",
" else:\n",
" ws = list(q[\"wrong\"]); w = np.array([sol[\"tend\"][q[\"wrong\"][x]] for x in ws])\n",
" s = str(rng.choice(ws, p=w / w.sum())); et = q[\"wrong\"][s]\n",
" return _fmt_sa(s, rng), ok, s, et\n",
" if ok:\n",
" s = \"ABCD\"\n",
" elif rng.random() < 0.5:\n",
" s = \"ABCD\".replace(\"ABCD\"[rng.integers(1, 4)], \"\") # ๋จ๊ณ ํ๋ ๋น ๋จ๋ฆผ\n",
" else:\n",
" i = int(rng.integers(0, 3)); s = list(\"ABCD\"); s[i], s[i + 1] = s[i + 1], s[i]; s = \"\".join(s) # ์์ ๋ค๋ฐ๋\n",
" raw = s if rng.random() < 0.6 else rng.choice([\"-\".join(s), \" \".join(s), s.lower(), \",\".join(s)])\n",
" return raw, ok, s, (None if ok else P)\n",
"\n",
"\n",
"def make_responses(n_per_unit=40, seed=7, blank=0.02):\n",
" \"\"\"์์์ x ๋ฌธํญ ์๋ตํ(DataFrame)๋ฅผ ๋ง๋ ๋ค. truth_* ์ด์ ๊ต๊ด ๊ธฐ์ค ์ ๋ต(ํ๊ฐ์ฉ)์ด๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows = []\n",
" for sol in make_soldiers(n_per_unit, seed):\n",
" for q in BANK:\n",
" raw, ok, intended, et = answer(q, sol, rng)\n",
" if rng.random() < blank:\n",
" raw, ok, intended, et = \"\", False, \"\", None\n",
" rows.append({\"rid\": f\"R{len(rows):05d}\", \"sid\": sol[\"sid\"], \"unit\": sol[\"unit\"], \"qid\": q[\"qid\"], \"raw\": raw,\n",
" \"truth_correct\": ok, \"truth_answer\": intended, \"truth_etype\": et})\n",
" return pd.DataFrame(rows)\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์๋ต ์ ์ ยทํ์ฑยท์ฑ์ ๊ณตํต ํจ์ ----\n",
"TAILS = (\"์
๋๋ค\", \"์ด๋ค\", \"์\")\n",
"\n",
"\n",
"def norm_text(s):\n",
" \"\"\"์ ๊ฐ->๋ฐ๊ฐ(NFKC), ์๋ฌธ์, ๊ณต๋ฐฑยท๋ฌธ์ฅ๋ถํธ ์ ๊ฑฐ, ๋๋งบ์ ์ด๋ฏธ ์ ๊ฑฐ.\"\"\"\n",
" s = unicodedata.normalize(\"NFKC\", str(s)).strip().lower()\n",
" s = re.sub(r\"[\\s\\.\\,\\-\\(\\)ยท]\", \"\", s)\n",
" for t in TAILS:\n",
" if s.endswith(t) and len(s) > len(t) + 1:\n",
" s = s[:-len(t)]\n",
" return s\n",
"\n",
"\n",
"def parse(q, raw):\n",
" \"\"\"์๋ณธ ์๋ต -> (์ ์ ๊ฐ, ์ํ). ์ํ: ์ ์ / ๋ฏธ์๋ต / ํ์์ค๋ฅ\"\"\"\n",
" if raw is None or str(raw).strip() == \"\":\n",
" return \"\", \"๋ฏธ์๋ต\"\n",
" s = unicodedata.normalize(\"NFKC\", str(raw)).strip()\n",
" if q[\"type\"] == \"MC\":\n",
" t = re.sub(r\"[\\s๋ฒ\\.\\)]\", \"\", s).lower()\n",
" t = {\"a\": \"1\", \"b\": \"2\", \"c\": \"3\", \"d\": \"4\"}.get(t, t)\n",
" return (t, \"์ ์\") if t in (\"1\", \"2\", \"3\", \"4\") else (t, \"ํ์์ค๋ฅ\")\n",
" if q[\"type\"] == \"ORDER\":\n",
" t = re.sub(r\"[^A-Za-z]\", \"\", s).upper()\n",
" return (t, \"์ ์\") if t and set(t) <= set(\"ABCD\") and len(set(t)) == len(t) else (t, \"ํ์์ค๋ฅ\")\n",
" return norm_text(s), \"์ ์\"\n",
"\n",
"\n",
"def grade_exact(q, val):\n",
" if q[\"type\"] == \"MC\":\n",
" return val == str(q[\"answer\"])\n",
" if q[\"type\"] == \"ORDER\":\n",
" return val == q[\"answer\"]\n",
" return val in {norm_text(a) for a in q[\"accept\"]}\n",
"\n",
"\n",
"def grade_fuzzy(q, val, cut=0.75):\n",
" \"\"\"๋จ๋ตํ์ ์คํ์(๊ธ์ ์์ ๋ฐ๋ยทํ ๊ธ์ ๋๋ฝ)๋ฅผ ํ์ฉ(์ ์ฌ๋ cut ์ด์)ํ๋, ์๋ ค์ง ์ค๋ต๊ณผ ๋ ๊ฐ๊น์ฐ๋ฉด ์ค๋ต์ผ๋ก ๋ณธ๋ค.\"\"\"\n",
" if grade_exact(q, val) or q[\"type\"] != \"SA\" or len(val) < 2:\n",
" return grade_exact(q, val)\n",
" best = max(1.0 if sorted(val) == sorted(norm_text(a)) else difflib.SequenceMatcher(None, val, norm_text(a)).ratio() for a in q[\"accept\"])\n",
" worst = max(difflib.SequenceMatcher(None, val, norm_text(w)).ratio() for w in q[\"wrong\"])\n",
" return best >= cut and best > worst\n",
"\n",
"\n",
"def score_of(q, correct):\n",
" return POINTS[q[\"diff\"]] if correct else 0\n",
"\n",
"\n",
"def grade_one(qid, raw):\n",
" q = QIDX[qid]\n",
" val, st = parse(q, raw)\n",
" return st, (st == \"์ ์\" and grade_fuzzy(q, val))\n",
"\n",
"\n",
"@functools.lru_cache(maxsize=50000)\n",
"def grade_cached(qid, raw):\n",
" \"\"\"๊ฐ์ (๋ฌธํญ, ์๋ณธ ์๋ต)์ ๋ค์ ๊ณ์ฐํ์ง ์๋๋ค(๋๊ท๋ชจ ์์์์ ๊ฐ์ ๋ต์ด ๋ฐ๋ณต๋จ).\"\"\"\n",
" return grade_one(qid, raw)\n",
"\n",
"\n",
"def corrupt(df, rng, rate):\n",
" \"\"\"์์ ๋ต์ ์ฃผ์
: ์ด์ฅ๋ฌธ, ์ ์ด๋ฌธ์, ์กด์ฌํ์ง ์๋ ๋ฌธํญ, None.\"\"\"\n",
" df = df.copy()\n",
" idx = rng.choice(len(df), int(len(df) * rate), replace=False)\n",
" kinds = rng.integers(0, 4, len(idx))\n",
" for i, k in zip(idx, kinds):\n",
" df.at[df.index[i], \"raw\"] = [\"๊ฐ\" * 20000, \"\\x00\\x07์ผ๊ฐ์ต\", None, \"1\"][k]\n",
" if k == 3:\n",
" df.at[df.index[i], \"qid\"] = \"Q99\"\n",
" return df\n",
"\n",
"\n",
"def run(df, fn, fail_rate=0.0, seed=0, cap=2000):\n",
" \"\"\"์ ์ถ ํ์์ cap๊ฑด์ฉ ๊บผ๋ด ์ฑ์ . ์ฑ์ ์ค ์์ธยท์ฅ์ ๊ฐ ๋๋ฉด '์ฑ์ ๋ณด๋ฅ' ํ๋ก ์ฎ๊ธฐ๊ณ ๊ณ์ ์ฒ๋ฆฌํ๋ค(์์ ์คํจ).\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" inbox, result, hold = deque(df[[\"rid\", \"qid\", \"raw\"]].itertuples(index=False)), {}, {}\n",
" t0, crashes, max_q = time.perf_counter(), 0, len(inbox)\n",
" while inbox:\n",
" batch = [inbox.popleft() for _ in range(min(cap, len(inbox)))]\n",
" for rid, qid, raw in batch:\n",
" try:\n",
" if qid not in QIDX:\n",
" raise KeyError(\"๋ฌธํญ ์์\")\n",
" if raw is not None and len(str(raw)) > 200:\n",
" raise ValueError(\"์๋ต ๊ธธ์ด ์ด๊ณผ\")\n",
" if rng.random() < fail_rate:\n",
" raise TimeoutError(\"์ฑ์ ๊ธฐ ์๋ต ์์\")\n",
" result[rid] = fn(qid, \"\" if raw is None else str(raw).replace(\"\\x00\", \"\").replace(\"\\x07\", \"\"))\n",
" except Exception as e: # ์์คํ
์ ๋ฉ์ถ์ง ์๊ณ ๋ณด๋ฅ ํ์ ์๋ณธ์ ๋ณด์กด\n",
" hold[rid] = type(e).__name__\n",
" sec = time.perf_counter() - t0\n",
" lost = len(df) - len(result) - len(hold)\n",
" return {\"๊ฑด์\": len(df), \"์ฒ๋ฆฌ(์ด)\": round(sec, 2), \"์ฒ๋ฆฌ๋(๊ฑด/์ด)\": int(len(df) / sec), \"์ฑ์ ์๋ฃ\": len(result),\n",
" \"๋ณด๋ฅ\": len(hold), \"์ ์ค\": lost, \"๋ค์ด\": crashes, \"์ต๋ ๋๊ธฐ์ด\": max_q}, hold\n",
"\n",
"\n",
"base = make_responses(40, seed=7) # ํ์: 120๋ช
x 32๋ฌธํญ\n",
"rng = np.random.default_rng(1)\n",
"rows, holds = [], {}\n",
"for mult, fail in [(1, 0.0), (3, 0.0), (6, 0.0), (6, 0.02), (11, 0.02)]:\n",
" df = pd.concat([base.assign(rid=base[\"rid\"] + f\"-{k}\") for k in range(mult)], ignore_index=True)\n",
" df = corrupt(df, rng, 0.003)\n",
" grade_cached.cache_clear()\n",
" tracemalloc.start()\n",
" r, hold = run(df, grade_cached, fail_rate=fail, seed=mult)\n",
" peak = tracemalloc.get_traced_memory()[1] / 1e6; tracemalloc.stop()\n",
" rows.append({\"๋ถํ\": f\"ํ์ x{mult}\" + (\" +์ฅ์ 2%\" if fail else \"\"), **r, \"๋ฉ๋ชจ๋ฆฌ ์ต๋(MB)\": round(peak, 1)})\n",
" holds[rows[-1][\"๋ถํ\"]] = hold\n",
"T = pd.DataFrame(rows)\n",
"print(T.drop(columns=[\"๋ค์ด\", \"์ต๋ ๋๊ธฐ์ด\"]).to_string(index=False))\n",
"print(\"(์ ํ์ ์ฒ๋ฆฌ๋์ ๋ฉ๋ชจ๋ฆฌ ์ถ์ ์ ์ผ ์ํ๋ผ ์ค์ ๋ณด๋ค ๋ฎ๊ฒ ๋์จ๋ค)\")\n",
"w = T[T[\"๋ถํ\"] == \"ํ์ x6\"].iloc[0]\n",
"print(f\"\\n๋์ ์์ 500% ์ฆ๊ฐ(ํ์ x6): ์์คํ
๋ค์ด {int(T['๋ค์ด'].sum())}๊ฑด, ๋ฐ์ดํฐ ์ ์ค {int(T['์ ์ค'].sum())}๊ฑด \"\n",
" f\"(๋ชฉํ ๋ค์ด 0๊ฑดยท์ ์ค๋ฅ 0%: {'๋ฌ์ฑ' if T['๋ค์ด'].sum() == 0 and T['์ ์ค'].sum() == 0 else '๋ฏธ๋ฌ'})\")\n",
"h = pd.Series(holds[\"ํ์ x11 +์ฅ์ 2%\"]).value_counts()\n",
"print(\"๋ณด๋ฅ ์ฌ์ (ํ์ x11 +์ฅ์ 2%):\", {k: int(v) for k, v in h.items()}, \"-> ์๋ณธ ๋ณด์กด, ์ฅ์ ๋ณต๊ตฌ ํ ์ฌ์ฑ์ ๋๋ ๊ต๊ด ํ์ธ\")\n",
"\n",
"# ๋ณ๋ชฉ ํ๋กํ์ผ๋ง๊ณผ ์ต์ ํ: ์บ์ ์์ vs ์บ์\n",
"df6 = pd.concat([base.assign(rid=base[\"rid\"] + f\"-{k}\") for k in range(6)], ignore_index=True)\n",
"prof = {}\n",
"t0 = time.perf_counter(); [parse(QIDX[q], r) for q, r in zip(df6[\"qid\"], df6[\"raw\"])]; prof[\"ํ์ฑ\"] = time.perf_counter() - t0\n",
"sa = df6[df6[\"qid\"].map(lambda q: QIDX[q][\"type\"]) == \"SA\"]\n",
"t0 = time.perf_counter(); [grade_fuzzy(QIDX[q], parse(QIDX[q], r)[0]) for q, r in zip(sa[\"qid\"], sa[\"raw\"])]; prof[\"๋จ๋ต ์ ์ฌ๋ ์ฑ์ \"] = time.perf_counter() - t0\n",
"print(\"\\n[๋ณ๋ชฉ ํ๋กํ์ผ] \" + \", \".join(f\"{k} {v * 1000:.0f} ms\" for k, v in prof.items()) + f\" (๋จ๋ตํ์ ์ ์ฒด์ {len(sa) / len(df6):.0%})\")\n",
"r0, _ = run(df6, grade_one)\n",
"grade_cached.cache_clear(); r1, _ = run(df6, grade_cached)\n",
"print(f\"์ต์ ํ ์ {r0['์ฒ๋ฆฌ๋(๊ฑด/์ด)']:,}๊ฑด/์ด -> ์บ์ ์ ์ฉ ํ {r1['์ฒ๋ฆฌ๋(๊ฑด/์ด)']:,}๊ฑด/์ด ({r1['์ฒ๋ฆฌ๋(๊ฑด/์ด)'] / r0['์ฒ๋ฆฌ๋(๊ฑด/์ด)']:.1f}๋ฐฐ)\")\n",
"print(f\"๊ธฐ๋ฅ ์ถ์ ์ด์ฉ: ์ฅ์ ์จ์ด 5%๋ฅผ ๋์ผ๋ฉด ๋จ๋ตํ ์ ์ฌ๋ ์ฑ์ ์ ๋๊ณ ์ ํ ์ผ์น๋ง ์ฑ์ , ๋๋จธ์ง๋ ๊ต๊ด ๊ฒํ ๋ก ๋๊ธด๋ค.\")\n",
"\n",
"plt.figure(figsize=(8, 4))\n",
"plt.bar(T[\"๋ถํ\"].str.replace(\"ํ์\", \"base\").str.replace(\"+์ฅ์ 2%\", \"+fail2%\"), T[\"์ฒ๋ฆฌ๋(๊ฑด/์ด)\"])\n",
"plt.ylabel(\"responses / sec\"); plt.title(\"Grading throughput under load (cached)\"); plt.xticks(rotation=15)\n",
"plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
08 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
" ๋ถํ ๊ฑด์ ์ฒ๋ฆฌ(์ด) ์ฒ๋ฆฌ๋(๊ฑด/์ด) ์ฑ์ ์๋ฃ ๋ณด๋ฅ ์ ์ค ๋ฉ๋ชจ๋ฆฌ ์ต๋(MB)\n",
" ํ์ x1 3840 0.11 33625 3834 6 0 0.5\n",
" ํ์ x3 11520 0.19 59703 11501 19 0 1.1\n",
" ํ์ x6 23040 0.27 86855 23004 36 0 2.0\n",
" ํ์ x6 +์ฅ์ 2% 23040 0.24 96047 22558 482 0 2.0\n",
"ํ์ x11 +์ฅ์ 2% 42240 0.46 91783 41326 914 0 3.6\n",
"(์ ํ์ ์ฒ๋ฆฌ๋์ ๋ฉ๋ชจ๋ฆฌ ์ถ์ ์ ์ผ ์ํ๋ผ ์ค์ ๋ณด๋ค ๋ฎ๊ฒ ๋์จ๋ค)\n",
"\n",
"๋์ ์์ 500% ์ฆ๊ฐ(ํ์ x6): ์์คํ
๋ค์ด 0๊ฑด, ๋ฐ์ดํฐ ์ ์ค 0๊ฑด (๋ชฉํ ๋ค์ด 0๊ฑดยท์ ์ค๋ฅ 0%: ๋ฌ์ฑ)\n",
"๋ณด๋ฅ ์ฌ์ (ํ์ x11 +์ฅ์ 2%): {'TimeoutError': 857, 'ValueError': 30, 'KeyError': 27} -> ์๋ณธ ๋ณด์กด, ์ฅ์ ๋ณต๊ตฌ ํ ์ฌ์ฑ์ ๋๋ ๊ต๊ด ํ์ธ\n",
"\n",
"[๋ณ๋ชฉ ํ๋กํ์ผ] ํ์ฑ 52 ms, ๋จ๋ต ์ ์ฌ๋ ์ฑ์ 150 ms (๋จ๋ตํ์ ์ ์ฒด์ 25%)\n",
"์ต์ ํ ์ 89,083๊ฑด/์ด -> ์บ์ ์ ์ฉ ํ 498,682๊ฑด/์ด (5.6๋ฐฐ)\n",
"๊ธฐ๋ฅ ์ถ์ ์ด์ฉ: ์ฅ์ ์จ์ด 5%๋ฅผ ๋์ผ๋ฉด ๋จ๋ตํ ์ ์ฌ๋ ์ฑ์ ์ ๋๊ณ ์ ํ ์ผ์น๋ง ์ฑ์ , ๋๋จธ์ง๋ ๊ต๊ด ๊ฒํ ๋ก ๋๊ธด๋ค.\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
09. AAR ๊ธฐ๋ฐ ๋ฌธํญ ๋ณ๋ณ๋ ฅ ๋ถ์ ๋ฐ ๋์ด๋๋ณ ๊ฐ์ค์น ํ๋ผ๋ฏธํฐ ์ฌ๋ณด์ \n",
"**์์์๊ฐ**: ์ฝ 3.0์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"AAR ๊ธฐ๋ฐ ๋ฌธํญ ๋ณ๋ณ๋ ฅ ๋ถ์ ๋ฐ ๋์ด๋ ๊ฐ์ค์น ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ๋ณด์ ํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **์ํ ํ ๊ฒํ (AAR)๋ก ๋ฌธํญ์ ๋ณ๋ณ๋ ฅ๊ณผ ์ ๋ต ํค ์ค๋ฅ๋ฅผ ์ฐพ์๋ด๊ณ , ๊ฒฐํจ ๋ฌธํญ ์กฐ์น์ ๋ฐฐ์ ๊ฐ์ค์น ์ฌ๋ณด์ ์ผ๋ก ํฉ๊ฒฉ ํ์ ์ ์ ํ๋๋ฅผ ๋์ธ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **๋ฌธํญ๋ณ ์ ๋ต๋ฅ , ์ ์ด์ฐ ์๊ด(๋ณ๋ณ๋), ์ค๋ต ๋ณด๊ธฐ ์ ํ์์ ํ๊ท ์ ์๋ฅผ ๋ถ์ํด ์ ๋ต ํค ์
๋ ฅ ์ค๋ฅ์ ๋ณ๋ณ๋ ฅ ๋ถ์กฑ ๋ฌธํญ์ ์ฐพ๋๋ค ๊ณผ์ ์ ์งํํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `docs/trn02_aar_report.md ๋ฐ configs/trn02_tuned_params.yaml` ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํ๋ค.\n",
"\n",
"### ๐ ํ๊ฐ ๋ฐ ๊ฒ์ฆ ๊ธฐ์ค (PASS ์กฐ๊ฑด)\n",
"#### ๋ฏธ์
ํ์ \n",
"\n",
"**ํ๊ฐ ๊ตฌ๋ถ:** ํ์\n",
"\n",
"**ํต์ฌ ํ๊ฐ์์ญ:** ์ค๋ฅ ์์ธ ๊ท๋ช
์ ๊ฐ๊ด์ฑ ๋ฐ ๊ฐ์ ์กฐ์น์ ๋
ผ๋ฆฌ์ ํ๋น์ฑ\n",
"\n",
"#### ๋ฏธ์
PASS ์กฐ๊ฑด\n",
"\n",
"- ์ ๋ ๊ธฐ์ค: AAR ํผ๋๋ฐฑ ์ ์ฉ ํ ์ข
ํฉ ์ ํ๋ 10%p ์ด์ ํฅ์, ์ฌ๋ฐ ์ค๋ฅ 0๊ฑด\n",
"\n",
"\n",
"- ์งํ ๊ธฐ์ค: AAR ๋ณด๊ณ ์ ์์ฑ ๋ฐ ํ๋ผ๋ฏธํฐ ํ๋ ์ ํ ํ๊ท ํ
์คํธ ํต๊ณผ\n",
"\n",
"\n",
"- ๊ฒ์ฆ ๋ฐฉ๋ฒ: ํ๊ท ํ
์คํธ(Regression Test) ์ค์ํธ ์ ์ฒด ์๋ ์คํ ๋ฐ ๋น๊ต\n",
"\n",
"\n",
"#### ์ฑ์ ์ ํ์ ๋ฉ๋ชจ\n",
"\n",
"> ๋จ์ ์ฝ๋ ์คํ ์ฌ๋ถ๋ฟ๋ง ์๋๋ผ ์ถ๋ ฅ๋ ์์น์ ๊ทผ๊ฑฐ ์๋ฃ๊ฐ ๊ตฐ ์ค๋ฌด ์๊ตฌ์ฌํญ์ ๋ถํฉํ๋์ง ๋ฉด๋ฐํ ํ์ธํ๋ค.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"์ํ ํ ๊ฒํ (AAR)๋ก ๋ฌธํญ๋ณ ์ ๋ต๋ฅ ยท๋ณ๋ณ๋(์ ์ด์ฐ ์๊ด)ยท์ค๋ต ๋ณด๊ธฐ ์ ํ ๋ถํฌ๋ฅผ ๋ถ์ํด ์ ๋ต ํค ์ค๋ฅ์ ๋ณ๋ณ๋ ฅ ์๋ ๋ฌธํญ์ ์ฐพ์๋ด๊ณ , ๋์ด๋ยท๋ณ๋ณ๋ ๊ฐ์ค์น๋ฅผ ์ฌ๋ณด์ ํ๋ค. ์ค์ ์๋ฌ๋ ๋๋น ํฉ๊ฒฉ ํ์ ์ ํ๋์ ๊ฐ์ ๊ณผ ์ ์์ ์ง๋จ์์์ ์ฌ๋ฐ ์ค๋ฅ๋ฅผ ํ์ธํ๊ณ ์ฌ๋ณด์ ํ๋ผ๋ฏธํฐ๋ฅผ YAML๋ก ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import difflib\n",
"import re\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฐฉ๊ณตยท๋๋ก ์๋ณ ํด์ฆ ๋ฌธํญ ์ํ๊ณผ ๊ฐ์ ์์์ ์๋ต ์์ฑ๊ธฐ\n",
"# ๋ฌธํญ์ ์ผ๋ฐ ์์น ์์ค์ ๊ฐ์ ๋ฌธํญ์ด๋ฉฐ ์ค์ ๊ต๋ฒยท๊ต์ ๊ท์น ์๋ฌธ์ด ์๋๋ค. ์์์๋ ๊ฐ๋ช
(S001~)์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"AREAS = [\"ํ์์๋ณ\", \"ํผ์์๋ณ\", \"๊ฒฝ๋ณด๋ณด๊ณ \", \"๊ต์ ํต์ \", \"๋๋๋ก ์ผ๋ฐ\"]\n",
"ETYPES = [\"๊ฐ๋
ํผ๋\", \"ํผ์ ์ค์ธ\", \"์ ์ฐจ ๋๋ฝ\"]\n",
"POINTS = {1: 2, 2: 3, 3: 5}\n",
"C, F, P = ETYPES\n",
"\n",
"\n",
"def _mc(qid, area, diff, stem, opts, ans, dist):\n",
" return {\"qid\": qid, \"type\": \"MC\", \"area\": area, \"diff\": diff, \"stem\": stem, \"options\": opts, \"answer\": ans,\n",
" \"dist\": dict(zip([i for i in range(1, 5) if i != ans], dist))}\n",
"\n",
"\n",
"def _sa(qid, area, diff, stem, accept, wrong):\n",
" return {\"qid\": qid, \"type\": \"SA\", \"area\": area, \"diff\": diff, \"stem\": stem, \"accept\": accept, \"wrong\": wrong}\n",
"\n",
"\n",
"def _order(qid, area, diff, stem):\n",
" return {\"qid\": qid, \"type\": \"ORDER\", \"area\": area, \"diff\": diff, \"stem\": stem, \"answer\": \"ABCD\"}\n",
"\n",
"\n",
"BANK = [\n",
" _mc(\"Q01\", \"ํ์์๋ณ\", 1, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์์ด ์ผ๊ฐํ ๋ ๊ฐ๋ฅผ ๊ฐ์ง ์ํ ๊ณ ์ ์ต ์ค๋ฃจ์ฃ์?\", [\"์ฟผ๋์ฝฅํฐํ\", \"์ผ๊ฐ์ต ๊ณ ์ ์ตํ\", \"ํ์ ์ต ํฌ๊ธฐํ\", \"์กฐ๋ฅ\"], 2, [C, C, F]),\n",
" _mc(\"Q02\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๊ฐ ์ญ์๋ก ๋ฐฐ์น๋ ์ค๋ฃจ์ฃ์?\", [\"๊ณ ์ ์ต ๋๋ก ํ\", \"์ฟผ๋์ฝฅํฐํ\", \"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\"], 2, [C, C, C]),\n",
" _mc(\"Q03\", \"ํ์์๋ณ\", 2, \"๊ฐ๋๊ณ ๊ธด ๋์ฒด ์ค๊ฐ์ ์์ ๋ ๊ฐ๊ฐ ๋ถ์ ์ค๋ฃจ์ฃ์?\", [\"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\", \"๋ฏผ๊ฐ ๊ฒฝ๋นํ๊ธฐํ\", \"์กฐ๋ฅ\"], 1, [C, F, F]),\n",
" _mc(\"Q04\", \"ํ์์๋ณ\", 3, \"์๊ฑฐ๋ฆฌ๋ผ ์ค๋ฃจ์ฃ ์๋ณ์ด ์ด๋ ค์ธ ๋ ๋จผ์ ํ ํ๋์?\", [\"์ถ์ ์ ํ์ผ๋ก ํ์ ๋ณด๊ณ \", \"๊ด์ธก ์ ๋ณด๋ฅผ ๊ทธ๋๋ก ๋ณด๊ณ ํ๊ณ ์ถ๊ฐ ๊ด์ธก\", \"๊ด์ธก ์ค๋จ\", \"๋ฏผ๊ฐ๊ธฐ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _mc(\"Q05\", \"ํผ์์๋ณ\", 2, \"์๋ณ ์ ํธ ์๋ต์ด ์๋ ํญ๊ณต๊ธฐ์ ๋ํ ์ฌ๋ฐ๋ฅธ ํ๋จ์?\", [\"์ฆ์ ์ ๊ธฐ๋ก ํ์ \", \"๋ฏธํ์ธ ํญ๊ณต๊ธฐ๋ก ๋ถ๋ฅํ๊ณ ์ถ๊ฐ ํ์ธ\", \"์๊ตฐ๊ธฐ๋ก ๊ฐ์ฃผ\", \"๋ฌด์\"], 2, [F, F, P]),\n",
" _mc(\"Q06\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด์ ์ผ์นํ๋ ์ ์ ๋นํ์ฒด์ ๋ถ๋ฅ๋?\", [\"์ ๋๋ก \", \"๋ฏผ๊ฐ๊ธฐ ๊ฐ๋ฅ์ฑ ๋์, ์ถ๊ฐ ํ์ธ\", \"์ํญ๋ฏธ์ฌ์ผ\", \"์๊ตฐ ์ ์ฐฐ๊ธฐ๋ก ํ์ \"], 2, [F, C, F]),\n",
" _mc(\"Q07\", \"ํผ์์๋ณ\", 3, \"์๊ตฐ ๋๋ก ์ด์ฉ ๊ณต์ง๊ฐ ์๋ ๊ณต์ญ์์ ๋ฏธํ์ธ ๋๋ก ์ ๋ณด๋ฉด ์ฐ์ ํ์ธํ ๊ฒ์?\", [\"๊ธฐ์ ์ ๋ณด\", \"์๊ตฐ ์ด์ฉ ๊ณํ๊ณผ ์๋ณ ์ ํธ\", \"๋์ ์ค๋น ์ํ\", \"์ฃผ๋ณ ์งํ\"], 2, [C, P, C]),\n",
" _mc(\"Q08\", \"ํผ์์๋ณ\", 3, \"์กฐ๋ฅ ๋ฌด๋ฆฌ์ ์ํ ๋๋ก ๊ตฐ์ง ๊ตฌ๋ถ์ ๊ฐ์ฅ ๋์์ด ๋๋ ์ ๋ณด๋?\", [\"ํฌ๊ธฐ๋ง ๋น๊ต\", \"๊ถค์ ์ ๊ท์น์ฑ๊ณผ ์ด ์ ํธ\", \"์์\", \"๊ด์ธก ์๊ฐ\"], 2, [C, C, C]),\n",
" _mc(\"Q09\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๊ณต์ค ์ํ ์ต์ด ๋ณด๊ณ ์ ํฌํจํ์ง ์์๋ ๋๋ ๊ฒ์?\", [\"๊ด์ธก ์๊ฐ\", \"๋ฐฉํฅ๊ณผ ๊ฑฐ๋ฆฌ\", \"์ถ์ ์ ํ\", \"๊ด์ธก์ ๊ฐ์ธ ์ฐ๋ฝ์ฒ\"], 4, [P, P, P]),\n",
" _mc(\"Q10\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๊ฒฝ๋ณด ์ ํ ํ ์ํฉ์ด ๋ฐ๋๋ฉด?\", [\"์ต์ด ๋ณด๊ณ ์ ์ง\", \"๋ณ๊ฒฝ ์ฌํญ์ ์ฆ์ ์ถ๊ฐ ๋ณด๊ณ \", \"์ํฉ ์ข
๋ฃ ํ ์ผ๊ด ๋ณด๊ณ \", \"๊ฐ์ธ ํ๋จ์ผ๋ก ๊ฒฝ๋ณด ํด์ \"], 2, [P, P, P]),\n",
" _mc(\"Q11\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ณด๊ณ ๋ด์ฉ์ด ๋ถํ์คํ ๋ ์ฌ๋ฐ๋ฅธ ํํ์?\", [\"ํ์ ํํ์ผ๋ก ๋ณด๊ณ \", \"'์ถ์ '์์ ๋ฐํ๊ณ ๊ทผ๊ฑฐ์ ํจ๊ป ๋ณด๊ณ \", \"๋ณด๊ณ ๋ณด๋ฅ\", \"๋ค๋ฅธ ๊ด์ธก์ ๋ณด๊ณ ๋ฅผ ๋ณต์ฌ\"], 2, [C, P, P]),\n",
" _mc(\"Q12\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋๋ก ํ์ง ํ ๋ณด๊ณ ๊ฒฝ๋ก๋ก ์๋ง์ ๊ฒ์?\", [\"์ธ์ ๋ถ๋์๋ง ์ ํ\", \"์งํ๊ณํต์ ๋ฐ๋ผ ๋ณด๊ณ \", \"๊ฐ์ธ ๋ฉ์ ์ ๋ก ๊ณต์ \", \"๋ณด๊ณ ์์ด ๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q13\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ๋ง์ผ๋ก ๋์์ ๊ฒฐ์ ํ ์ ์๋๊ฐ?\", [\"๊ฐ๋ฅํ๋ค\", \"์ธ๊ฐ ์งํ๊ด์ ์น์ธ์ด ํ์ํ๋ค\", \"์ ๋ขฐ๋ 90% ์ด์์ด๋ฉด ๊ฐ๋ฅ\", \"์ผ๊ฐ์๋ง ๊ฐ๋ฅ\"], 2, [C, C, C]),\n",
" _mc(\"Q14\", \"๊ต์ ํต์ \", 2, \"๋ฏผ๊ฐ ํผํด ๊ฐ๋ฅ์ฑ์ด ํ์ธ๋๋ฉด?\", [\"๊ทธ๋๋ก ์งํ\", \"์ฆ์ ๋ณด๊ณ ํ๊ณ ์งํ๊ด ํ๋จ์ ๋ฐ๋๋ค\", \"์กฐ์น ํ ๋ณด๊ณ \", \"๋ฌด์\"], 2, [P, P, F]),\n",
" _mc(\"Q15\", \"๊ต์ ํต์ \", 3, \"์๋ณ์ด ๋ถํ์คํ ํ์ ์ ๋ํ ์์น์?\", [\"์ํ์ผ๋ก ๊ฐ์ฃผํด ์ฆ์ ๋์\", \"์๋ณ ํ์ธ ์ ๊น์ง ์น์ธ ์ ์ฐจ๋ฅผ ๋ฐ๋ฅธ๋ค\", \"๋ฌด์กฐ๊ฑด ๋ฌด์\", \"๋ค์๊ฒฐ๋ก ๊ฒฐ์ \"], 2, [F, F, C]),\n",
" _mc(\"Q16\", \"๊ต์ ํต์ \", 2, \"ํ๋จ ๊ทผ๊ฑฐ๋ฅผ ๊ธฐ๋กํด์ผ ํ๋ ์ด์ ๋?\", [\"ํ์์ ํ์\", \"์ฌํ ๊ฒํ ์ ์ฑ
์ ์ถ์ \", \"๋ณด๊ณ ์๋ต\", \"๊ธฐ๋ก ๋ถํ์\"], 2, [C, P, P]),\n",
" _mc(\"Q17\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"๋๋๋ก ์ฒด๊ณ์ ์ผ๋ฐ์ ์ฒ๋ฆฌ ์์๋?\", [\"๋์-ํ์ง-์๋ณ\", \"ํ์ง-์๋ณ-๋ณด๊ณ ยทํ๋จ-๋์\", \"์๋ณ-๋์-ํ์ง\", \"๋ณด๊ณ -ํ์ง-๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q18\", \"๋๋๋ก ์ผ๋ฐ\", 3, \"๋ ์ด๋๋ง์ผ๋ก ์กฐ๋ฅ์ ๋๋ก ๊ตฌ๋ถ์ด ์ด๋ ค์ด ์ด์ ๋?\", [\"๋ฐ์ฌ ์ ํธ ํฌ๊ธฐ๊ฐ ๋น์ทํ ์ ์์ด์\", \"๋ ์ด๋๋ ์ผ๊ฐ์ ๋ชป ์จ์\", \"๋๋ก ์๋ ๊ธ์์ด ์์ด์\", \"์กฐ๋ฅ๋ ํ์ง๋์ง ์์์\"], 1, [C, C, C]),\n",
" _mc(\"Q19\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ ์๊ดํ(EO) ์ผ์์ ์ฝ์ ์ด ์ปค์ง๋ ์กฐ๊ฑด์?\", [\"๋ง์ ์ฃผ๊ฐ\", \"์๊ฐยท์ผ๊ฐ\", \"์ ์ ํ์ \", \"๊ทผ๊ฑฐ๋ฆฌ\"], 2, [C, C, C]),\n",
" _mc(\"Q20\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"๊ฒฝ๋ณด๊ฐ ๋ฐ๋ณตํด์ ์ค๊ฒฝ๋ณด๋ก ํ๋ช
๋๋ฉด?\", [\"๊ฒฝ๋ณด ์ฅ์น๋ฅผ ๋๋ค\", \"์ฌ๋ก๋ฅผ ๊ธฐ๋กยท๋ณด๊ณ ํ๊ณ ๊ธฐ์ค ์ฌ๊ฒํ ์์ฒญ\", \"๋ฌด์ํ๋ค\", \"๋ชจ๋ ์ค์ ์ํ์ผ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _sa(\"Q21\", \"ํผ์์๋ณ\", 1, \"์๊ตฐยท์ ยท๋ฏผ๊ฐ์ ๊ตฌ๋ถํ๋ ์ ์ฐจ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"ํผ์์๋ณ\", \"IFF\"], {\"ํ์ ํ๋\": C, \"์ ๊ธฐํ์ \": F, \"๊ฒฝ๊ณ\": C}),\n",
" _sa(\"Q22\", \"ํ์์๋ณ\", 2, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์๋ ์ผ๊ฐํ ๋ ๊ฐ ํํ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ผ๊ฐ์ต\", \"๋ธํ์ต\", \"delta\"], {\"์ง์ ์ต\": C, \"ํํด์ต\": C, \"์ฟผ๋์ฝฅํฐ\": C}),\n",
" _sa(\"Q23\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ถํ์คํ ๊ด์ธก์ ๋ณด๊ณ ํ ๋ ๋ถ์ด๋ ํํ์?\", [\"์ถ์ \"], {\"ํ์ \": C, \"ํ์ธ\": C, \"์๋ต\": P}),\n",
" _sa(\"Q24\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ์ ๋ํด ์ต์ข
๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ์ฃผ์ฒด๋?\", [\"์งํ๊ด\"], {\"AI\": C, \"์ด์ฉ๋ณ\": P, \"๋ ์ด๋\": C}),\n",
" _sa(\"Q25\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"์กฐ๋ฅ๋ฅผ ๋๋ก ์ผ๋ก ์๋ชป ๊ฒฝ๋ณดํ๋ ๊ฒ์ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ค๊ฒฝ๋ณด\", \"์คํ\", \"false alarm\"], {\"๋ฏธํ\": C, \"์ฌ๊ฒฝ๋ณด\": C, \"์ ๊ธฐ\": F}),\n",
" _sa(\"Q26\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด๊ฐ ํ์ธ๋ ์ฌ๊ฐ๊ธฐ๋ ์ด๋ป๊ฒ ๋ถ๋ฅํ๋๊ฐ?\", [\"๋ฏผ๊ฐ๊ธฐ\", \"๋ฏผํญ๊ธฐ\"], {\"์ ๊ธฐ\": F, \"๋ฏธํ์ธ๊ธฐ\": P, \"์๊ตฐ๊ธฐ\": F}),\n",
" _sa(\"Q27\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๋ฅผ ๊ฐ์ง ๋๋ก ํํ๋?\", [\"์ฟผ๋์ฝฅํฐ\", \"quadcopter\"], {\"ํฅ์ฌ์ฝฅํฐ\": C, \"๊ณ ์ ์ต\": C, \"ํฌ๊ธฐ\": F}),\n",
" _sa(\"Q28\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋ณด๊ณ ๋ ๋ฌด์์ ๋ฐ๋ผ ์ฌ๋ผ๊ฐ๋๊ฐ?\", [\"์งํ๊ณํต\"], {\"์ธ์ ๋ถ๋\": P, \"๊ฐ์ธ๋ฉ์ ์ \": P, \"ํต์ ๋ง\": C}),\n",
" _order(\"Q29\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"์ต์ด ๋ณด๊ณ ์์: A ๊ด์ธก ์๊ฐ, B ๋ฐฉํฅยท๊ฑฐ๋ฆฌ, C ์ถ์ ์ ํ, D ๊ด์ธก์ ์กฐ์น\"),\n",
" _order(\"Q30\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ฒ๋ฆฌ ์์: A ํ์ง, B ์๋ณ, C ๋ณด๊ณ ยทํ๋จ, D ๋์\"),\n",
" _order(\"Q31\", \"๊ต์ ํต์ \", 3, \"ํต์ ์์: A ์๋ณ ํ์ธ, B ์๊ธ ์น์ธ ์์ฒญ, C ์น์ธ ์๋ น, D ์กฐ์น ๊ธฐ๋ก\"),\n",
" _order(\"Q32\", \"ํผ์์๋ณ\", 3, \"ํ์ธ ์์: A ์ดํญ์ ๋ณด ๋์กฐ, B ์๋ณ ์ ํธ ํ์ธ, C ๋ฏธํ์ธ ๋ถ๋ฅ, D ๋ณด๊ณ \"),\n",
"]\n",
"QIDX = {q[\"qid\"]: q for q in BANK}\n",
"UNITS = [\"๊ฐ์1์ค๋\", \"๊ฐ์2์ค๋\", \"๊ฐ์3์ค๋\"]\n",
"_CIRC = \"โ โกโขโฃ\"\n",
"PARA = {\"Q21\": \"์๊ตฐ์ ๊ตฐ ๊ตฌ๋ถ\", \"Q22\": \"์ธ๋ชจ ๋ ๊ฐ\", \"Q23\": \"์ถ์ ๋จ์ผ๋ก ํ๊ธฐ\", \"Q24\": \"์ค๋์ฅ(์งํ๊ด)\", \"Q25\": \"์๋ชป๋ ๊ฒฝ๋ณด\",\n",
" \"Q26\": \"๋ฏผ๊ฐ ์ฌ๊ฐ๊ธฐ\", \"Q27\": \"4๋ฐ ๋๋ก \", \"Q28\": \"์งํ ๋ผ์ธ\"} # ๋ป์ ๋ง์ง๋ง ์ธ์ ๋ต์ ๋ชฉ๋ก์ ์๋ ํํ\n",
"\n",
"\n",
"def make_soldiers(n_per_unit, seed):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for u, unit in enumerate(UNITS):\n",
" base = [0.3, -0.1, 0.0][u]\n",
" for i in range(n_per_unit):\n",
" off = rng.normal(0, 0.5, len(AREAS)) + (np.array([0, 0, 0, -0.6, 0]) if u == 1 else 0)\n",
" out.append({\"sid\": f\"S{len(out) + 1:03d}\", \"unit\": unit, \"theta\": dict(zip(AREAS, base + rng.normal(0, 0.6) + off)),\n",
" \"tend\": dict(zip(ETYPES, rng.dirichlet([1.2, 1.2, 1.2])))})\n",
" return out\n",
"\n",
"\n",
"def p_correct(theta, diff):\n",
" return 1 / (1 + np.exp(-1.7 * (theta - (diff - 2) * 0.9 + 0.9)))\n",
"\n",
"\n",
"def _typo(s, rng):\n",
" if len(s) < 2:\n",
" return s\n",
" i = int(rng.integers(len(s) - 1))\n",
" return s[:i] + s[i + 1] + s[i] + s[i + 2:] if len(s) <= 3 or rng.random() < 0.5 else s[:i] + s[i + 1:]\n",
"\n",
"\n",
"def _fmt_mc(k, rng):\n",
" r = rng.random()\n",
" if r < 0.55:\n",
" return str(k)\n",
" return rng.choice([_CIRC[k - 1], f\"{k}๋ฒ\", \"ABCD\"[k - 1], \"abcd\"[k - 1] + \")\", f\" {k} \", chr(0xFF10 + k), f\"{k}.\"])\n",
"\n",
"\n",
"def _fmt_sa(s, rng):\n",
" r = rng.random()\n",
" if r < 0.45:\n",
" return s\n",
" if r < 0.6:\n",
" return \" \".join(s) if len(s) <= 4 else s[:2] + \" \" + s[2:]\n",
" if r < 0.72:\n",
" return s + rng.choice([\"์
๋๋ค\", \"์ด๋ค\", \".\", \"์\"])\n",
" if r < 0.8:\n",
" return s.upper() if s.isascii() else \"\".join(chr(ord(ch) + 0xFEE0) if \"!\" <= ch <= \"~\" else ch for ch in s) + \" \"\n",
" return _typo(s, rng)\n",
"\n",
"\n",
"def answer(q, sol, rng):\n",
" \"\"\"ํ ๋ฌธํญ ์๋ต: (์๋ณธ ์๋ต ๋ฌธ์์ด, ์๋ํ ์ ๋ต ์ฌ๋ถ, ์๋ํ ๋ต, ์ค๋ต ์ ํ).\"\"\"\n",
" ok = rng.random() < p_correct(sol[\"theta\"][q[\"area\"]], q[\"diff\"])\n",
" if q[\"type\"] == \"MC\":\n",
" if ok:\n",
" k, et = q[\"answer\"], None\n",
" else:\n",
" ks = list(q[\"dist\"]); w = np.array([sol[\"tend\"][q[\"dist\"][k]] for k in ks])\n",
" k = int(rng.choice(ks, p=w / w.sum())); et = q[\"dist\"][k]\n",
" return _fmt_mc(k, rng), ok, str(k), et\n",
" if q[\"type\"] == \"SA\":\n",
" if ok and rng.random() < 0.06:\n",
" return PARA[q[\"qid\"]], ok, PARA[q[\"qid\"]], None\n",
" if ok:\n",
" s, et = str(rng.choice(q[\"accept\"])), None\n",
" else:\n",
" ws = list(q[\"wrong\"]); w = np.array([sol[\"tend\"][q[\"wrong\"][x]] for x in ws])\n",
" s = str(rng.choice(ws, p=w / w.sum())); et = q[\"wrong\"][s]\n",
" return _fmt_sa(s, rng), ok, s, et\n",
" if ok:\n",
" s = \"ABCD\"\n",
" elif rng.random() < 0.5:\n",
" s = \"ABCD\".replace(\"ABCD\"[rng.integers(1, 4)], \"\") # ๋จ๊ณ ํ๋ ๋น ๋จ๋ฆผ\n",
" else:\n",
" i = int(rng.integers(0, 3)); s = list(\"ABCD\"); s[i], s[i + 1] = s[i + 1], s[i]; s = \"\".join(s) # ์์ ๋ค๋ฐ๋\n",
" raw = s if rng.random() < 0.6 else rng.choice([\"-\".join(s), \" \".join(s), s.lower(), \",\".join(s)])\n",
" return raw, ok, s, (None if ok else P)\n",
"\n",
"\n",
"def make_responses(n_per_unit=40, seed=7, blank=0.02):\n",
" \"\"\"์์์ x ๋ฌธํญ ์๋ตํ(DataFrame)๋ฅผ ๋ง๋ ๋ค. truth_* ์ด์ ๊ต๊ด ๊ธฐ์ค ์ ๋ต(ํ๊ฐ์ฉ)์ด๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows = []\n",
" for sol in make_soldiers(n_per_unit, seed):\n",
" for q in BANK:\n",
" raw, ok, intended, et = answer(q, sol, rng)\n",
" if rng.random() < blank:\n",
" raw, ok, intended, et = \"\", False, \"\", None\n",
" rows.append({\"rid\": f\"R{len(rows):05d}\", \"sid\": sol[\"sid\"], \"unit\": sol[\"unit\"], \"qid\": q[\"qid\"], \"raw\": raw,\n",
" \"truth_correct\": ok, \"truth_answer\": intended, \"truth_etype\": et})\n",
" return pd.DataFrame(rows)\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์๋ต ์ ์ ยทํ์ฑยท์ฑ์ ๊ณตํต ํจ์ ----\n",
"TAILS = (\"์
๋๋ค\", \"์ด๋ค\", \"์\")\n",
"\n",
"\n",
"def norm_text(s):\n",
" \"\"\"์ ๊ฐ->๋ฐ๊ฐ(NFKC), ์๋ฌธ์, ๊ณต๋ฐฑยท๋ฌธ์ฅ๋ถํธ ์ ๊ฑฐ, ๋๋งบ์ ์ด๋ฏธ ์ ๊ฑฐ.\"\"\"\n",
" s = unicodedata.normalize(\"NFKC\", str(s)).strip().lower()\n",
" s = re.sub(r\"[\\s\\.\\,\\-\\(\\)ยท]\", \"\", s)\n",
" for t in TAILS:\n",
" if s.endswith(t) and len(s) > len(t) + 1:\n",
" s = s[:-len(t)]\n",
" return s\n",
"\n",
"\n",
"def parse(q, raw):\n",
" \"\"\"์๋ณธ ์๋ต -> (์ ์ ๊ฐ, ์ํ). ์ํ: ์ ์ / ๋ฏธ์๋ต / ํ์์ค๋ฅ\"\"\"\n",
" if raw is None or str(raw).strip() == \"\":\n",
" return \"\", \"๋ฏธ์๋ต\"\n",
" s = unicodedata.normalize(\"NFKC\", str(raw)).strip()\n",
" if q[\"type\"] == \"MC\":\n",
" t = re.sub(r\"[\\s๋ฒ\\.\\)]\", \"\", s).lower()\n",
" t = {\"a\": \"1\", \"b\": \"2\", \"c\": \"3\", \"d\": \"4\"}.get(t, t)\n",
" return (t, \"์ ์\") if t in (\"1\", \"2\", \"3\", \"4\") else (t, \"ํ์์ค๋ฅ\")\n",
" if q[\"type\"] == \"ORDER\":\n",
" t = re.sub(r\"[^A-Za-z]\", \"\", s).upper()\n",
" return (t, \"์ ์\") if t and set(t) <= set(\"ABCD\") and len(set(t)) == len(t) else (t, \"ํ์์ค๋ฅ\")\n",
" return norm_text(s), \"์ ์\"\n",
"\n",
"\n",
"def grade_exact(q, val):\n",
" if q[\"type\"] == \"MC\":\n",
" return val == str(q[\"answer\"])\n",
" if q[\"type\"] == \"ORDER\":\n",
" return val == q[\"answer\"]\n",
" return val in {norm_text(a) for a in q[\"accept\"]}\n",
"\n",
"\n",
"def grade_fuzzy(q, val, cut=0.75):\n",
" \"\"\"๋จ๋ตํ์ ์คํ์(๊ธ์ ์์ ๋ฐ๋ยทํ ๊ธ์ ๋๋ฝ)๋ฅผ ํ์ฉ(์ ์ฌ๋ cut ์ด์)ํ๋, ์๋ ค์ง ์ค๋ต๊ณผ ๋ ๊ฐ๊น์ฐ๋ฉด ์ค๋ต์ผ๋ก ๋ณธ๋ค.\"\"\"\n",
" if grade_exact(q, val) or q[\"type\"] != \"SA\" or len(val) < 2:\n",
" return grade_exact(q, val)\n",
" best = max(1.0 if sorted(val) == sorted(norm_text(a)) else difflib.SequenceMatcher(None, val, norm_text(a)).ratio() for a in q[\"accept\"])\n",
" worst = max(difflib.SequenceMatcher(None, val, norm_text(w)).ratio() for w in q[\"wrong\"])\n",
" return best >= cut and best > worst\n",
"\n",
"\n",
"def score_of(q, correct):\n",
" return POINTS[q[\"diff\"]] if correct else 0\n",
"\n",
"KEY_REG = {\"Q11\": 3} # [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฌธํญ ๋ฑ๋ก ์ ์ ๋ต ํค๋ฅผ ์๋ชป ์
๋ ฅํ ๋ฌธํญ(์ ๋ต์ 2)\n",
"AMBIG = \"Q08\" # ๋ณด๊ธฐ 2ยท3์ด ๋ชจ๋ ๊ทธ๋ด๋ฏํด ์ค๋ ฅ๊ณผ ๋ฌด๊ดํ๊ฒ ๊ฐ๋ฆฌ๋ ๋ฌธํญ\n",
"\n",
"\n",
"def field_data(seed):\n",
" \"\"\"ํ์ฅ ์ํ ๋ฐ์ดํฐ: Q08์ ์ค๋ ฅ๊ณผ ๋ฌด๊ดํ๊ฒ 2ยท3์ด ๋ฐ๋ฐ, ๋๋จธ์ง๋ ์์ฑ๊ธฐ ๊ทธ๋๋ก.\"\"\"\n",
" df = make_responses(40, seed=seed)\n",
" rng = np.random.default_rng(seed + 100)\n",
" m = df[\"qid\"] == AMBIG\n",
" df.loc[m, \"raw\"] = rng.choice([\"2\", \"3\"], m.sum())\n",
" df[\"val\"] = [parse(QIDX[q], r)[0] for q, r in zip(df[\"qid\"], df[\"raw\"])]\n",
" return df\n",
"\n",
"\n",
"def grade(df, key_fix=False, drop=(), weights=None):\n",
" def ok(q, v):\n",
" if q[\"qid\"] in KEY_REG and not key_fix:\n",
" return v == str(KEY_REG[q[\"qid\"]])\n",
" return grade_fuzzy(q, v)\n",
" d = df[~df[\"qid\"].isin(drop)].copy()\n",
" d[\"correct\"] = [ok(QIDX[q], v) for q, v in zip(d[\"qid\"], d[\"val\"])]\n",
" w = weights or {}\n",
" d[\"pts\"] = [w.get(q, POINTS[QIDX[q][\"diff\"]]) for q in d[\"qid\"]]\n",
" d[\"score\"] = d[\"pts\"] * d[\"correct\"]\n",
" tot = d.groupby(\"sid\")[[\"score\", \"pts\"]].sum()\n",
" return d, tot[\"score\"] / tot[\"pts\"]\n",
"\n",
"\n",
"def item_analysis(d, key_fix=False):\n",
" \"\"\"์ ๋ต๋ฅ (p), ์ ์ด์ฐ ์๊ด(ํด๋น ๋ฌธํญ ์ ์ธ ์ด์ ๊ณผ์ ์๊ด), ์ค๋ต ๋ณด๊ธฐ ์ ํ์์ ํ๊ท ์ด์ ์ด ์ ๋ต ์ ํ์๋ณด๋ค ๋์์ง(ํค ์ค๋ฅ ์์ฌ).\"\"\"\n",
" tot = d.groupby(\"sid\")[\"correct\"].mean()\n",
" rows = []\n",
" for qid, g in d.groupby(\"qid\"):\n",
" rest = tot[g[\"sid\"]].values - g[\"correct\"].values / len(BANK)\n",
" r = np.corrcoef(g[\"correct\"].astype(float), rest)[0, 1] if g[\"correct\"].std() > 0 else 0.0\n",
" key_sus = False\n",
" if QIDX[qid][\"type\"] == \"MC\":\n",
" by = pd.Series(rest, index=g[\"val\"].values).groupby(level=0).mean()\n",
" key = KEY_REG[qid] if qid in KEY_REG and not key_fix else QIDX[qid][\"answer\"]\n",
" right = by.get(str(key), np.nan)\n",
" key_sus = bool(by.drop(index=[k for k in by.index if k not in \"1234\" or k == \"\"], errors=\"ignore\").max() > right + 0.08)\n",
" rows.append({\"qid\": qid, \"p\": round(g[\"correct\"].mean(), 2), \"r_pb\": round(r, 2), \"key_suspect\": key_sus})\n",
" t = pd.DataFrame(rows).set_index(\"qid\")\n",
" t[\"flag\"] = np.where(t[\"key_suspect\"], \"์ ๋ต ํค ์ค๋ฅ ์์ฌ\", np.where(t[\"r_pb\"] < 0.1, \"๋ณ๋ณ๋ ฅ ๋ถ์กฑ\", \"\"))\n",
" return t\n",
"\n",
"\n",
"def pass_accuracy(df, ratio, cut=0.7):\n",
" \"\"\"ํฉ๊ฒฉ ํ์ (์ ์ ๋น์จ cut ์ด์)๊ณผ ๊ฐ์ ์ค์ ์๋ฌ๋(๊ฒฐํจ ์๋ ์ํ์์์ ๊ธฐ๋ ๋์ ๋ฅ 0.7 ์ด์)์ ์ผ์น์จ.\"\"\"\n",
" sol = {s[\"sid\"]: s for s in make_soldiers(40, SEED_OF[id(df)])}\n",
" pts = np.array([POINTS[q[\"diff\"]] for q in BANK])\n",
" exp = {s: float(np.dot(pts, [p_correct(sol[s][\"theta\"][q[\"area\"]], q[\"diff\"]) for q in BANK]) / pts.sum()) for s in ratio.index}\n",
" truth = pd.Series(exp) >= 0.7\n",
" return float(((ratio >= cut) == truth[ratio.index]).mean())\n",
"\n",
"\n",
"SEED_OF = {}\n",
"A = field_data(7); SEED_OF[id(A)] = 7\n",
"d0, r0 = grade(A)\n",
"acc0 = pass_accuracy(A, r0)\n",
"ia = item_analysis(d0)\n",
"print(\"[AAR 1. ๋ฌธํญ ๋ถ์ - ๊ฒฝ๊ณ ๋ฌธํญ]\")\n",
"print(ia[ia[\"flag\"] != \"\"].to_string())\n",
"print(f\"์ ์ฒด ๋ฌธํญ ๋ณ๋ณ๋ ์ค์๊ฐ {ia['r_pb'].median():.2f}, ์ ๋ต๋ฅ 0.9 ์ด๊ณผ(๋๋ฌด ์ฌ์) {int((ia['p'] > 0.9).sum())}๊ฐ, 0.3 ๋ฏธ๋ง(๋๋ฌด ์ด๋ ค์) {int((ia['p'] < 0.3).sum())}๊ฐ\")\n",
"print(\"์์ธ ํ์ : Q11์ ์์๊ถ์ด ๊ณ ๋ฅธ ๋ณด๊ธฐ(2)๊ฐ ๋ฑ๋ก ํค(3)์ ๋ฌ๋ผ ํค ์
๋ ฅ ์ค๋ฅ, Q08์ ๋ณด๊ธฐ 2ยท3์ด ์ค๋ ฅ๊ณผ ๋ฌด๊ดํ๊ฒ ๊ฐ๋ ค ๋ฌธํญ ์์ฒด ๊ฒฐํจ.\")\n",
"\n",
"# AAR 2. ์ฌ๋ณด์ : ํค ์์ -> ๊ฒฐํจ ๋ฌธํญ ์ ์ธ -> ๋ฐฐ์ ๊ฐ์ค์น ํ์\n",
"flagged = list(ia[ia[\"flag\"] == \"๋ณ๋ณ๋ ฅ ๋ถ์กฑ\"].index)\n",
"rows = [{\"๋จ๊ณ\": \"์ฌ๋ณด์ ์ \", \"ํฉ๊ฒฉ ํ์ ์ ํ๋\": acc0}]\n",
"d1, r1 = grade(A, key_fix=True)\n",
"rows.append({\"๋จ๊ณ\": \"1) ์ ๋ต ํค ์์ \", \"ํฉ๊ฒฉ ํ์ ์ ํ๋\": pass_accuracy(A, r1)})\n",
"d2, r2 = grade(A, key_fix=True, drop=flagged)\n",
"rows.append({\"๋จ๊ณ\": \"2) ๋ณ๋ณ๋ ฅ ๋ถ์กฑ ๋ฌธํญ ์ ์ธ\", \"ํฉ๊ฒฉ ํ์ ์ ํ๋\": pass_accuracy(A, r2)})\n",
"ia2 = item_analysis(d2, key_fix=True)\n",
"best = None\n",
"for base in [(2, 3, 5), (1, 2, 3), (3, 3, 3)]:\n",
" for alpha in [0.0, 0.5, 1.0, 2.0]:\n",
" w = {q: base[QIDX[q][\"diff\"] - 1] * (1 + alpha * max(ia2.loc[q, \"r_pb\"], 0)) for q in ia2.index}\n",
" _, r = grade(A, key_fix=True, drop=flagged, weights=w)\n",
" a = pass_accuracy(A, r) # ํฉ๊ฒฉ์ 70%๋ ์ ์ฑ
๊ฐ์ด๋ผ ํ๋ํ์ง ์๋๋ค\n",
" if best is None or a > best[0] + 1e-9:\n",
" best = (a, base, alpha, 0.7, w)\n",
"acc3, base, alpha, cut, W = best\n",
"rows.append({\"๋จ๊ณ\": f\"3) ๋ฐฐ์ {base}ยท๋ณ๋ณ๋ ๊ฐ์ค {alpha}\", \"ํฉ๊ฒฉ ํ์ ์ ํ๋\": acc3})\n",
"S = pd.DataFrame(rows); S[\"ํฉ๊ฒฉ ํ์ ์ ํ๋\"] = S[\"ํฉ๊ฒฉ ํ์ ์ ํ๋\"].round(3)\n",
"print(\"\\n[AAR 2. ์ฌ๋ณด์ ๋จ๊ณ๋ณ ํจ๊ณผ(์์ ์ง๋จ A)]\")\n",
"print(S.to_string(index=False))\n",
"gain = (acc3 - acc0) * 100\n",
"print(f\"์ข
ํฉ ์ ํ๋ {acc0:.1%} -> {acc3:.1%} (+{gain:.1f}%p, ๋ชฉํ 10%p ์ด์: {'๋ฌ์ฑ' if gain >= 10 else '๋ฏธ๋ฌ'})\")\n",
"print(\"-> ๊ฒฐํจ ๋ฌธํญ์ด 32๊ฐ ์ค 2๊ฐ๋ผ ๊ฐ์ ํญ์ด ์ ํ๋๋ค. ๋๋จธ์ง ๋ถ์ผ์น๋ ํฉ๊ฒฉ์ ๊ทผ์ฒ ์์์์ ์ธก์ ์ค์ฐจ๋ก, ๋ฌธํญ ์๋ฅผ ๋๋ ค์ผ ์ค์ด๋ ๋ค.\")\n",
"\n",
"# AAR 3. ์ ์์ ์ง๋จ B(๋ค๋ฅธ ์ธ์)์์ ๊ฒ์ฆยท์ฌ๋ฐ ํ์ธ\n",
"B = field_data(21); SEED_OF[id(B)] = 21\n",
"_, rb0 = grade(B)\n",
"dB, rb = grade(B, key_fix=True, drop=flagged, weights=W)\n",
"accB0, accB = pass_accuracy(B, rb0), pass_accuracy(B, rb, cut)\n",
"iaB = item_analysis(dB, key_fix=True)\n",
"rec = iaB[iaB[\"key_suspect\"] | iaB.index.isin(flagged + list(KEY_REG))]\n",
"rec = rec[rec[\"flag\"] != \"\"]\n",
"new = iaB[(iaB[\"flag\"] != \"\") & ~iaB.index.isin(rec.index)]\n",
"print(f\"\\n[AAR 3. ์์ ์ง๋จ B ๊ฒ์ฆ] ํฉ๊ฒฉ ํ์ ์ ํ๋ {accB0:.1%} -> {accB:.1%} (+{(accB - accB0) * 100:.1f}%p)\")\n",
"print(f\"์ฌ๋ฐ ์ค๋ฅ(์กฐ์นํ ํค ์ค๋ฅยท๊ฒฐํจ ๋ฌธํญ์ ์ฌ๊ฒ์ถ) {len(rec)}๊ฑด (๋ชฉํ 0๊ฑด: {'๋ฌ์ฑ' if len(rec) == 0 else '๋ฏธ๋ฌ'})\")\n",
"print(f\"์ ๊ท ์ ๊ฒ ๋์ {len(new)}๊ฑด: {list(new.index)} -> ๋ค์ AAR์์ ๋ฌธํญ ๊ฐ์ ์ฌ๋ถ ํ๋จ\")\n",
"\n",
"yaml = \"\\n\".join([\"# configs/trn02_tuned_params.yaml [๊ต์ก์ฉ๊ฐ์์๋ฃ]\", \"answer_key_fix:\", \" Q11: 2\", \"excluded_items:\"]\n",
" + [f\" - {q} # ๋ณ๋ณ๋ ฅ ๋ถ์กฑ, ๋ฌธํญ ๊ฐ์ ํ ์ฌํฌ์
\" for q in flagged]\n",
" + [f\"points_by_difficulty: {{1: {base[0]}, 2: {base[1]}, 3: {base[2]}}}\", f\"discrimination_alpha: {alpha}\",\n",
" f\"pass_ratio: {cut}\", \"review: ๋ค์ ์ํ ํ AAR์์ ๋ณ๋ณ๋ 0.1 ๋ฏธ๋ง ๋ฌธํญ ์ฌ์ ๊ฒ\"])\n",
"print(\"\\n\" + yaml)\n",
"\n",
"plt.figure(figsize=(7, 4))\n",
"c = [\"tab:red\" if f else \"tab:blue\" for f in ia[\"flag\"]]\n",
"plt.scatter(ia[\"p\"], ia[\"r_pb\"], c=c)\n",
"for q in ia[ia[\"flag\"] != \"\"].index:\n",
" plt.annotate(q, (ia.loc[q, \"p\"], ia.loc[q, \"r_pb\"]))\n",
"plt.axhline(0.1, ls=\"--\", color=\"gray\"); plt.xlabel(\"item difficulty p (accuracy)\"); plt.ylabel(\"discrimination r_pb\")\n",
"plt.title(\"AAR item analysis (red = flagged)\"); plt.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
09 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"[AAR 1. ๋ฌธํญ ๋ถ์ - ๊ฒฝ๊ณ ๋ฌธํญ]\n",
" p r_pb key_suspect flag\n",
"qid \n",
"Q08 0.56 -0.10 False ๋ณ๋ณ๋ ฅ ๋ถ์กฑ\n",
"Q11 0.08 -0.26 True ์ ๋ต ํค ์ค๋ฅ ์์ฌ\n",
"Q27 0.89 0.08 False ๋ณ๋ณ๋ ฅ ๋ถ์กฑ\n",
"Q28 0.87 0.07 False ๋ณ๋ณ๋ ฅ ๋ถ์กฑ\n",
"์ ์ฒด ๋ฌธํญ ๋ณ๋ณ๋ ์ค์๊ฐ 0.30, ์ ๋ต๋ฅ 0.9 ์ด๊ณผ(๋๋ฌด ์ฌ์) 2๊ฐ, 0.3 ๋ฏธ๋ง(๋๋ฌด ์ด๋ ค์) 1๊ฐ\n",
"์์ธ ํ์ : Q11์ ์์๊ถ์ด ๊ณ ๋ฅธ ๋ณด๊ธฐ(2)๊ฐ ๋ฑ๋ก ํค(3)์ ๋ฌ๋ผ ํค ์
๋ ฅ ์ค๋ฅ, Q08์ ๋ณด๊ธฐ 2ยท3์ด ์ค๋ ฅ๊ณผ ๋ฌด๊ดํ๊ฒ ๊ฐ๋ ค ๋ฌธํญ ์์ฒด ๊ฒฐํจ.\n",
"\n",
"[AAR 2. ์ฌ๋ณด์ ๋จ๊ณ๋ณ ํจ๊ณผ(์์ ์ง๋จ A)]\n",
" ๋จ๊ณ ํฉ๊ฒฉ ํ์ ์ ํ๋\n",
" ์ฌ๋ณด์ ์ 0.833\n",
" 1) ์ ๋ต ํค ์์ 0.858\n",
" 2) ๋ณ๋ณ๋ ฅ ๋ถ์กฑ ๋ฌธํญ ์ ์ธ 0.875\n",
"3) ๋ฐฐ์ (2, 3, 5)ยท๋ณ๋ณ๋ ๊ฐ์ค 0.0 0.875\n",
"์ข
ํฉ ์ ํ๋ 83.3% -> 87.5% (+4.2%p, ๋ชฉํ 10%p ์ด์: ๋ฏธ๋ฌ)\n",
"-> ๊ฒฐํจ ๋ฌธํญ์ด 32๊ฐ ์ค 2๊ฐ๋ผ ๊ฐ์ ํญ์ด ์ ํ๋๋ค. ๋๋จธ์ง ๋ถ์ผ์น๋ ํฉ๊ฒฉ์ ๊ทผ์ฒ ์์์์ ์ธก์ ์ค์ฐจ๋ก, ๋ฌธํญ ์๋ฅผ ๋๋ ค์ผ ์ค์ด๋ ๋ค.\n",
"\n",
"[AAR 3. ์์ ์ง๋จ B ๊ฒ์ฆ] ํฉ๊ฒฉ ํ์ ์ ํ๋ 75.8% -> 77.5% (+1.7%p)\n",
"์ฌ๋ฐ ์ค๋ฅ(์กฐ์นํ ํค ์ค๋ฅยท๊ฒฐํจ ๋ฌธํญ์ ์ฌ๊ฒ์ถ) 0๊ฑด (๋ชฉํ 0๊ฑด: ๋ฌ์ฑ)\n",
"์ ๊ท ์ ๊ฒ ๋์ 3๊ฑด: ['Q12', 'Q13', 'Q21'] -> ๋ค์ AAR์์ ๋ฌธํญ ๊ฐ์ ์ฌ๋ถ ํ๋จ\n",
"\n",
"# configs/trn02_tuned_params.yaml [๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"answer_key_fix:\n",
" Q11: 2\n",
"excluded_items:\n",
" - Q08 # ๋ณ๋ณ๋ ฅ ๋ถ์กฑ, ๋ฌธํญ ๊ฐ์ ํ ์ฌํฌ์
\n",
" - Q27 # ๋ณ๋ณ๋ ฅ ๋ถ์กฑ, ๋ฌธํญ ๊ฐ์ ํ ์ฌํฌ์
\n",
" - Q28 # ๋ณ๋ณ๋ ฅ ๋ถ์กฑ, ๋ฌธํญ ๊ฐ์ ํ ์ฌํฌ์
\n",
"points_by_difficulty: {1: 2, 2: 3, 3: 5}\n",
"discrimination_alpha: 0.0\n",
"pass_ratio: 0.7\n",
"review: ๋ค์ ์ํ ํ AAR์์ ๋ณ๋ณ๋ 0.1 ๋ฏธ๋ง ๋ฌธํญ ์ฌ์ ๊ฒ\n",
"```\n",
" \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## ๐ฏ ๋ฏธ์
10. ๋ฐฉ๊ณต ํด์ฆ ์๋์ฑ์ ๋ฐ ์ค๋ต๋ถ์ ์์คํ
ํตํฉ ์ต์ข
์ด์ ํจํค์ง ์์ฑ\n",
"**์์์๊ฐ**: ์ฝ 3.5์๊ฐ\n",
"\n",
"### ๐ ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ์ํ ์ง์นจ\n",
"#### ๋ฏธ์
๋ฐฐ๊ฒฝ ๋ฐ ๋น์์ฑ\n",
"\n",
"๋ฐฉ๊ณต ํด์ฆ ์๋์ฑ์ ๋ฐ ์ค๋ต๋ถ์ ์์คํ
ํตํฉ ์ต์ข
์ด์ ํจํค์ง๋ฅผ ์์ฑํ๋ค. ์ค์ ์ ์ ํ์ฅ์์๋ ๋จ์ ํ์ง์ ๊ทธ์น์ง ์๊ณ ์ ์ํ ์ํ ํ๋จ๊ณผ ์ ๋ขฐ๋ ๋์ ์์ฌ๊ฒฐ์ ์ด ํ์์ ์ด๋ฏ๋ก, **๋ฌธํญ ๊ฒ์ฆ๋ถํฐ ์ ์ , ์ฑ์ , ์ค๋ต ๋ถ๋ฅ, ์ทจ์ฝ ์์ญ ์ถ์ฒ, ๊ต๊ด ๊ฒํ ๋๊ธฐ์ด๊น์ง ์ ์ฒด ์์คํ
์ ํ๋๋ก ํตํฉํ๊ณ ์ด์ ํจํค์ง๋ก ์์ฑํ๋ค.** ๊ณผ์
์ ์์ํ์ฌ ์คํ์ ๋ฐฉ์งํ๊ณ ์์ ๋์๋ ฅ์ ๊ทน๋ํํด์ผ ํฉ๋๋ค.\n",
"\n",
"์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ณธ ๋ฏธ์
์์๋ **์ฌ๋ณด์ ์ค์ (CONFIG)์ ์ ์ฉํ ํตํฉ ํ์ดํ๋ผ์ธ์ ์ฌ๋ฌ ์์ ํ์ฐจ์ ์คํํด End-to-End ์ฑ๊ณต๋ฅ ํ์ธํ๊ณ ์ ํจ์ฑ์ ์ ๊ฒํ๋** ์ค์ต์ ์ฒด๊ณ์ ์ผ๋ก ์ํํฉ๋๋ค.\n",
"\n",
"ํ์ต์๋ ์ค์ต๊ฐ์ด๋์ ์ ์๋ ์ด 4๊ฐ ์ธ๋ถ ํ๋(๊ธฐ๋ณธ ๊ธฐ๋ฅ ๊ตฌํ โ ๊ฒฐ๊ณผ ์งํ ๊ฒ์ฆ โ ํ๋ผ๋ฏธํฐ ํ๋ ๋ฐ ๋์ ๊ณผ์ )์ ์์ฐจ์ ์ผ๋ก ์งํํ๋ฉฐ ํต์ฌ ํ์ ๊ธฐ์ค์ ๋ฌ์ฑํฉ๋๋ค.\n",
"\n",
"**์ด๋ฒ ๊ฒฐ๊ณผ๋ฌผ:** `src/main_trn02.py, README.md, docs/trn02_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": [
"\"\"\"๋ฌธํญ DB ๊ฒ์ฆ, ์๋ต ์ ์ , ์๋ ์ฑ์ , ์ค๋ต ์ ํ ๋ถ๋ฅ, ์ทจ์ฝ ์์ญ ์ถ์ฒ, ๊ต๊ด ๊ฒํ (HITL) ๋๊ธฐ์ด, AAR ์ฌ๋ณด์ ํ๋ผ๋ฏธํฐ ์ ์ฉ๊น์ง ๋ฐฉ๊ณต ํด์ฆ ์ฑ์ ยท์ค๋ต๋ถ์ ์์คํ
์ ์ฒด๋ฅผ ํ ํ์ผ๋ก ํตํฉ ์คํํ๊ณ , ์์ฒด ์ ๊ฒ(์คํ ์ฑ๊ณต๋ฅ ยทํจ์ ์ปค๋ฒ๋ฆฌ์ง)๊ณผ ๊ต์กํ๋ จ ๋ด๋น๊ด ๋ธ๋ฆฌํ ์์ฝ์ ์ถ๋ ฅํ๋ค.\"\"\"\n",
"import difflib\n",
"import hashlib\n",
"import inspect\n",
"import json\n",
"import re\n",
"import sys\n",
"import time\n",
"import unicodedata\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.pipeline import make_pipeline\n",
"\n",
"# ---------------------------------------------------------------------------\n",
"# [๊ต์ก์ฉ๊ฐ์์๋ฃ] ๋ฐฉ๊ณตยท๋๋ก ์๋ณ ํด์ฆ ๋ฌธํญ ์ํ๊ณผ ๊ฐ์ ์์์ ์๋ต ์์ฑ๊ธฐ\n",
"# ๋ฌธํญ์ ์ผ๋ฐ ์์น ์์ค์ ๊ฐ์ ๋ฌธํญ์ด๋ฉฐ ์ค์ ๊ต๋ฒยท๊ต์ ๊ท์น ์๋ฌธ์ด ์๋๋ค. ์์์๋ ๊ฐ๋ช
(S001~)์ด๋ค.\n",
"# ---------------------------------------------------------------------------\n",
"AREAS = [\"ํ์์๋ณ\", \"ํผ์์๋ณ\", \"๊ฒฝ๋ณด๋ณด๊ณ \", \"๊ต์ ํต์ \", \"๋๋๋ก ์ผ๋ฐ\"]\n",
"ETYPES = [\"๊ฐ๋
ํผ๋\", \"ํผ์ ์ค์ธ\", \"์ ์ฐจ ๋๋ฝ\"]\n",
"POINTS = {1: 2, 2: 3, 3: 5}\n",
"C, F, P = ETYPES\n",
"\n",
"\n",
"def _mc(qid, area, diff, stem, opts, ans, dist):\n",
" return {\"qid\": qid, \"type\": \"MC\", \"area\": area, \"diff\": diff, \"stem\": stem, \"options\": opts, \"answer\": ans,\n",
" \"dist\": dict(zip([i for i in range(1, 5) if i != ans], dist))}\n",
"\n",
"\n",
"def _sa(qid, area, diff, stem, accept, wrong):\n",
" return {\"qid\": qid, \"type\": \"SA\", \"area\": area, \"diff\": diff, \"stem\": stem, \"accept\": accept, \"wrong\": wrong}\n",
"\n",
"\n",
"def _order(qid, area, diff, stem):\n",
" return {\"qid\": qid, \"type\": \"ORDER\", \"area\": area, \"diff\": diff, \"stem\": stem, \"answer\": \"ABCD\"}\n",
"\n",
"\n",
"BANK = [\n",
" _mc(\"Q01\", \"ํ์์๋ณ\", 1, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์์ด ์ผ๊ฐํ ๋ ๊ฐ๋ฅผ ๊ฐ์ง ์ํ ๊ณ ์ ์ต ์ค๋ฃจ์ฃ์?\", [\"์ฟผ๋์ฝฅํฐํ\", \"์ผ๊ฐ์ต ๊ณ ์ ์ตํ\", \"ํ์ ์ต ํฌ๊ธฐํ\", \"์กฐ๋ฅ\"], 2, [C, C, F]),\n",
" _mc(\"Q02\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๊ฐ ์ญ์๋ก ๋ฐฐ์น๋ ์ค๋ฃจ์ฃ์?\", [\"๊ณ ์ ์ต ๋๋ก ํ\", \"์ฟผ๋์ฝฅํฐํ\", \"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\"], 2, [C, C, C]),\n",
" _mc(\"Q03\", \"ํ์์๋ณ\", 2, \"๊ฐ๋๊ณ ๊ธด ๋์ฒด ์ค๊ฐ์ ์์ ๋ ๊ฐ๊ฐ ๋ถ์ ์ค๋ฃจ์ฃ์?\", [\"์ํญ๋ฏธ์ฌ์ผํ\", \"ํ๋๋ฏธ์ฌ์ผํ\", \"๋ฏผ๊ฐ ๊ฒฝ๋นํ๊ธฐํ\", \"์กฐ๋ฅ\"], 1, [C, F, F]),\n",
" _mc(\"Q04\", \"ํ์์๋ณ\", 3, \"์๊ฑฐ๋ฆฌ๋ผ ์ค๋ฃจ์ฃ ์๋ณ์ด ์ด๋ ค์ธ ๋ ๋จผ์ ํ ํ๋์?\", [\"์ถ์ ์ ํ์ผ๋ก ํ์ ๋ณด๊ณ \", \"๊ด์ธก ์ ๋ณด๋ฅผ ๊ทธ๋๋ก ๋ณด๊ณ ํ๊ณ ์ถ๊ฐ ๊ด์ธก\", \"๊ด์ธก ์ค๋จ\", \"๋ฏผ๊ฐ๊ธฐ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _mc(\"Q05\", \"ํผ์์๋ณ\", 2, \"์๋ณ ์ ํธ ์๋ต์ด ์๋ ํญ๊ณต๊ธฐ์ ๋ํ ์ฌ๋ฐ๋ฅธ ํ๋จ์?\", [\"์ฆ์ ์ ๊ธฐ๋ก ํ์ \", \"๋ฏธํ์ธ ํญ๊ณต๊ธฐ๋ก ๋ถ๋ฅํ๊ณ ์ถ๊ฐ ํ์ธ\", \"์๊ตฐ๊ธฐ๋ก ๊ฐ์ฃผ\", \"๋ฌด์\"], 2, [F, F, P]),\n",
" _mc(\"Q06\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด์ ์ผ์นํ๋ ์ ์ ๋นํ์ฒด์ ๋ถ๋ฅ๋?\", [\"์ ๋๋ก \", \"๋ฏผ๊ฐ๊ธฐ ๊ฐ๋ฅ์ฑ ๋์, ์ถ๊ฐ ํ์ธ\", \"์ํญ๋ฏธ์ฌ์ผ\", \"์๊ตฐ ์ ์ฐฐ๊ธฐ๋ก ํ์ \"], 2, [F, C, F]),\n",
" _mc(\"Q07\", \"ํผ์์๋ณ\", 3, \"์๊ตฐ ๋๋ก ์ด์ฉ ๊ณต์ง๊ฐ ์๋ ๊ณต์ญ์์ ๋ฏธํ์ธ ๋๋ก ์ ๋ณด๋ฉด ์ฐ์ ํ์ธํ ๊ฒ์?\", [\"๊ธฐ์ ์ ๋ณด\", \"์๊ตฐ ์ด์ฉ ๊ณํ๊ณผ ์๋ณ ์ ํธ\", \"๋์ ์ค๋น ์ํ\", \"์ฃผ๋ณ ์งํ\"], 2, [C, P, C]),\n",
" _mc(\"Q08\", \"ํผ์์๋ณ\", 3, \"์กฐ๋ฅ ๋ฌด๋ฆฌ์ ์ํ ๋๋ก ๊ตฐ์ง ๊ตฌ๋ถ์ ๊ฐ์ฅ ๋์์ด ๋๋ ์ ๋ณด๋?\", [\"ํฌ๊ธฐ๋ง ๋น๊ต\", \"๊ถค์ ์ ๊ท์น์ฑ๊ณผ ์ด ์ ํธ\", \"์์\", \"๊ด์ธก ์๊ฐ\"], 2, [C, C, C]),\n",
" _mc(\"Q09\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๊ณต์ค ์ํ ์ต์ด ๋ณด๊ณ ์ ํฌํจํ์ง ์์๋ ๋๋ ๊ฒ์?\", [\"๊ด์ธก ์๊ฐ\", \"๋ฐฉํฅ๊ณผ ๊ฑฐ๋ฆฌ\", \"์ถ์ ์ ํ\", \"๊ด์ธก์ ๊ฐ์ธ ์ฐ๋ฝ์ฒ\"], 4, [P, P, P]),\n",
" _mc(\"Q10\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๊ฒฝ๋ณด ์ ํ ํ ์ํฉ์ด ๋ฐ๋๋ฉด?\", [\"์ต์ด ๋ณด๊ณ ์ ์ง\", \"๋ณ๊ฒฝ ์ฌํญ์ ์ฆ์ ์ถ๊ฐ ๋ณด๊ณ \", \"์ํฉ ์ข
๋ฃ ํ ์ผ๊ด ๋ณด๊ณ \", \"๊ฐ์ธ ํ๋จ์ผ๋ก ๊ฒฝ๋ณด ํด์ \"], 2, [P, P, P]),\n",
" _mc(\"Q11\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ณด๊ณ ๋ด์ฉ์ด ๋ถํ์คํ ๋ ์ฌ๋ฐ๋ฅธ ํํ์?\", [\"ํ์ ํํ์ผ๋ก ๋ณด๊ณ \", \"'์ถ์ '์์ ๋ฐํ๊ณ ๊ทผ๊ฑฐ์ ํจ๊ป ๋ณด๊ณ \", \"๋ณด๊ณ ๋ณด๋ฅ\", \"๋ค๋ฅธ ๊ด์ธก์ ๋ณด๊ณ ๋ฅผ ๋ณต์ฌ\"], 2, [C, P, P]),\n",
" _mc(\"Q12\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋๋ก ํ์ง ํ ๋ณด๊ณ ๊ฒฝ๋ก๋ก ์๋ง์ ๊ฒ์?\", [\"์ธ์ ๋ถ๋์๋ง ์ ํ\", \"์งํ๊ณํต์ ๋ฐ๋ผ ๋ณด๊ณ \", \"๊ฐ์ธ ๋ฉ์ ์ ๋ก ๊ณต์ \", \"๋ณด๊ณ ์์ด ๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q13\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ๋ง์ผ๋ก ๋์์ ๊ฒฐ์ ํ ์ ์๋๊ฐ?\", [\"๊ฐ๋ฅํ๋ค\", \"์ธ๊ฐ ์งํ๊ด์ ์น์ธ์ด ํ์ํ๋ค\", \"์ ๋ขฐ๋ 90% ์ด์์ด๋ฉด ๊ฐ๋ฅ\", \"์ผ๊ฐ์๋ง ๊ฐ๋ฅ\"], 2, [C, C, C]),\n",
" _mc(\"Q14\", \"๊ต์ ํต์ \", 2, \"๋ฏผ๊ฐ ํผํด ๊ฐ๋ฅ์ฑ์ด ํ์ธ๋๋ฉด?\", [\"๊ทธ๋๋ก ์งํ\", \"์ฆ์ ๋ณด๊ณ ํ๊ณ ์งํ๊ด ํ๋จ์ ๋ฐ๋๋ค\", \"์กฐ์น ํ ๋ณด๊ณ \", \"๋ฌด์\"], 2, [P, P, F]),\n",
" _mc(\"Q15\", \"๊ต์ ํต์ \", 3, \"์๋ณ์ด ๋ถํ์คํ ํ์ ์ ๋ํ ์์น์?\", [\"์ํ์ผ๋ก ๊ฐ์ฃผํด ์ฆ์ ๋์\", \"์๋ณ ํ์ธ ์ ๊น์ง ์น์ธ ์ ์ฐจ๋ฅผ ๋ฐ๋ฅธ๋ค\", \"๋ฌด์กฐ๊ฑด ๋ฌด์\", \"๋ค์๊ฒฐ๋ก ๊ฒฐ์ \"], 2, [F, F, C]),\n",
" _mc(\"Q16\", \"๊ต์ ํต์ \", 2, \"ํ๋จ ๊ทผ๊ฑฐ๋ฅผ ๊ธฐ๋กํด์ผ ํ๋ ์ด์ ๋?\", [\"ํ์์ ํ์\", \"์ฌํ ๊ฒํ ์ ์ฑ
์ ์ถ์ \", \"๋ณด๊ณ ์๋ต\", \"๊ธฐ๋ก ๋ถํ์\"], 2, [C, P, P]),\n",
" _mc(\"Q17\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"๋๋๋ก ์ฒด๊ณ์ ์ผ๋ฐ์ ์ฒ๋ฆฌ ์์๋?\", [\"๋์-ํ์ง-์๋ณ\", \"ํ์ง-์๋ณ-๋ณด๊ณ ยทํ๋จ-๋์\", \"์๋ณ-๋์-ํ์ง\", \"๋ณด๊ณ -ํ์ง-๋์\"], 2, [P, P, P]),\n",
" _mc(\"Q18\", \"๋๋๋ก ์ผ๋ฐ\", 3, \"๋ ์ด๋๋ง์ผ๋ก ์กฐ๋ฅ์ ๋๋ก ๊ตฌ๋ถ์ด ์ด๋ ค์ด ์ด์ ๋?\", [\"๋ฐ์ฌ ์ ํธ ํฌ๊ธฐ๊ฐ ๋น์ทํ ์ ์์ด์\", \"๋ ์ด๋๋ ์ผ๊ฐ์ ๋ชป ์จ์\", \"๋๋ก ์๋ ๊ธ์์ด ์์ด์\", \"์กฐ๋ฅ๋ ํ์ง๋์ง ์์์\"], 1, [C, C, C]),\n",
" _mc(\"Q19\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ ์๊ดํ(EO) ์ผ์์ ์ฝ์ ์ด ์ปค์ง๋ ์กฐ๊ฑด์?\", [\"๋ง์ ์ฃผ๊ฐ\", \"์๊ฐยท์ผ๊ฐ\", \"์ ์ ํ์ \", \"๊ทผ๊ฑฐ๋ฆฌ\"], 2, [C, C, C]),\n",
" _mc(\"Q20\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"๊ฒฝ๋ณด๊ฐ ๋ฐ๋ณตํด์ ์ค๊ฒฝ๋ณด๋ก ํ๋ช
๋๋ฉด?\", [\"๊ฒฝ๋ณด ์ฅ์น๋ฅผ ๋๋ค\", \"์ฌ๋ก๋ฅผ ๊ธฐ๋กยท๋ณด๊ณ ํ๊ณ ๊ธฐ์ค ์ฌ๊ฒํ ์์ฒญ\", \"๋ฌด์ํ๋ค\", \"๋ชจ๋ ์ค์ ์ํ์ผ๋ก ๊ฐ์ฃผ\"], 2, [P, P, F]),\n",
" _sa(\"Q21\", \"ํผ์์๋ณ\", 1, \"์๊ตฐยท์ ยท๋ฏผ๊ฐ์ ๊ตฌ๋ถํ๋ ์ ์ฐจ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"ํผ์์๋ณ\", \"IFF\"], {\"ํ์ ํ๋\": C, \"์ ๊ธฐํ์ \": F, \"๊ฒฝ๊ณ\": C}),\n",
" _sa(\"Q22\", \"ํ์์๋ณ\", 2, \"๊ผฌ๋ฆฌ๋ ๊ฐ ์๋ ์ผ๊ฐํ ๋ ๊ฐ ํํ๋ฅผ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ผ๊ฐ์ต\", \"๋ธํ์ต\", \"delta\"], {\"์ง์ ์ต\": C, \"ํํด์ต\": C, \"์ฟผ๋์ฝฅํฐ\": C}),\n",
" _sa(\"Q23\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"๋ถํ์คํ ๊ด์ธก์ ๋ณด๊ณ ํ ๋ ๋ถ์ด๋ ํํ์?\", [\"์ถ์ \"], {\"ํ์ \": C, \"ํ์ธ\": C, \"์๋ต\": P}),\n",
" _sa(\"Q24\", \"๊ต์ ํต์ \", 1, \"AI ํ์ง ๊ฒฐ๊ณผ์ ๋ํด ์ต์ข
๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ์ฃผ์ฒด๋?\", [\"์งํ๊ด\"], {\"AI\": C, \"์ด์ฉ๋ณ\": P, \"๋ ์ด๋\": C}),\n",
" _sa(\"Q25\", \"๋๋๋ก ์ผ๋ฐ\", 1, \"์กฐ๋ฅ๋ฅผ ๋๋ก ์ผ๋ก ์๋ชป ๊ฒฝ๋ณดํ๋ ๊ฒ์ ๋ฌด์์ด๋ผ ํ๋๊ฐ?\", [\"์ค๊ฒฝ๋ณด\", \"์คํ\", \"false alarm\"], {\"๋ฏธํ\": C, \"์ฌ๊ฒฝ๋ณด\": C, \"์ ๊ธฐ\": F}),\n",
" _sa(\"Q26\", \"ํผ์์๋ณ\", 2, \"์ดํญ ์ ๋ณด๊ฐ ํ์ธ๋ ์ฌ๊ฐ๊ธฐ๋ ์ด๋ป๊ฒ ๋ถ๋ฅํ๋๊ฐ?\", [\"๋ฏผ๊ฐ๊ธฐ\", \"๋ฏผํญ๊ธฐ\"], {\"์ ๊ธฐ\": F, \"๋ฏธํ์ธ๊ธฐ\": P, \"์๊ตฐ๊ธฐ\": F}),\n",
" _sa(\"Q27\", \"ํ์์๋ณ\", 1, \"ํ์ ๋ ๊ฐ 4๊ฐ๋ฅผ ๊ฐ์ง ๋๋ก ํํ๋?\", [\"์ฟผ๋์ฝฅํฐ\", \"quadcopter\"], {\"ํฅ์ฌ์ฝฅํฐ\": C, \"๊ณ ์ ์ต\": C, \"ํฌ๊ธฐ\": F}),\n",
" _sa(\"Q28\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 1, \"๋ณด๊ณ ๋ ๋ฌด์์ ๋ฐ๋ผ ์ฌ๋ผ๊ฐ๋๊ฐ?\", [\"์งํ๊ณํต\"], {\"์ธ์ ๋ถ๋\": P, \"๊ฐ์ธ๋ฉ์ ์ \": P, \"ํต์ ๋ง\": C}),\n",
" _order(\"Q29\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"์ต์ด ๋ณด๊ณ ์์: A ๊ด์ธก ์๊ฐ, B ๋ฐฉํฅยท๊ฑฐ๋ฆฌ, C ์ถ์ ์ ํ, D ๊ด์ธก์ ์กฐ์น\"),\n",
" _order(\"Q30\", \"๋๋๋ก ์ผ๋ฐ\", 2, \"์ฒ๋ฆฌ ์์: A ํ์ง, B ์๋ณ, C ๋ณด๊ณ ยทํ๋จ, D ๋์\"),\n",
" _order(\"Q31\", \"๊ต์ ํต์ \", 3, \"ํต์ ์์: A ์๋ณ ํ์ธ, B ์๊ธ ์น์ธ ์์ฒญ, C ์น์ธ ์๋ น, D ์กฐ์น ๊ธฐ๋ก\"),\n",
" _order(\"Q32\", \"ํผ์์๋ณ\", 3, \"ํ์ธ ์์: A ์ดํญ์ ๋ณด ๋์กฐ, B ์๋ณ ์ ํธ ํ์ธ, C ๋ฏธํ์ธ ๋ถ๋ฅ, D ๋ณด๊ณ \"),\n",
"]\n",
"QIDX = {q[\"qid\"]: q for q in BANK}\n",
"UNITS = [\"๊ฐ์1์ค๋\", \"๊ฐ์2์ค๋\", \"๊ฐ์3์ค๋\"]\n",
"_CIRC = \"โ โกโขโฃ\"\n",
"PARA = {\"Q21\": \"์๊ตฐ์ ๊ตฐ ๊ตฌ๋ถ\", \"Q22\": \"์ธ๋ชจ ๋ ๊ฐ\", \"Q23\": \"์ถ์ ๋จ์ผ๋ก ํ๊ธฐ\", \"Q24\": \"์ค๋์ฅ(์งํ๊ด)\", \"Q25\": \"์๋ชป๋ ๊ฒฝ๋ณด\",\n",
" \"Q26\": \"๋ฏผ๊ฐ ์ฌ๊ฐ๊ธฐ\", \"Q27\": \"4๋ฐ ๋๋ก \", \"Q28\": \"์งํ ๋ผ์ธ\"} # ๋ป์ ๋ง์ง๋ง ์ธ์ ๋ต์ ๋ชฉ๋ก์ ์๋ ํํ\n",
"\n",
"\n",
"def make_soldiers(n_per_unit, seed):\n",
" rng = np.random.default_rng(seed)\n",
" out = []\n",
" for u, unit in enumerate(UNITS):\n",
" base = [0.3, -0.1, 0.0][u]\n",
" for i in range(n_per_unit):\n",
" off = rng.normal(0, 0.5, len(AREAS)) + (np.array([0, 0, 0, -0.6, 0]) if u == 1 else 0)\n",
" out.append({\"sid\": f\"S{len(out) + 1:03d}\", \"unit\": unit, \"theta\": dict(zip(AREAS, base + rng.normal(0, 0.6) + off)),\n",
" \"tend\": dict(zip(ETYPES, rng.dirichlet([1.2, 1.2, 1.2])))})\n",
" return out\n",
"\n",
"\n",
"def p_correct(theta, diff):\n",
" return 1 / (1 + np.exp(-1.7 * (theta - (diff - 2) * 0.9 + 0.9)))\n",
"\n",
"\n",
"def _typo(s, rng):\n",
" if len(s) < 2:\n",
" return s\n",
" i = int(rng.integers(len(s) - 1))\n",
" return s[:i] + s[i + 1] + s[i] + s[i + 2:] if len(s) <= 3 or rng.random() < 0.5 else s[:i] + s[i + 1:]\n",
"\n",
"\n",
"def _fmt_mc(k, rng):\n",
" r = rng.random()\n",
" if r < 0.55:\n",
" return str(k)\n",
" return rng.choice([_CIRC[k - 1], f\"{k}๋ฒ\", \"ABCD\"[k - 1], \"abcd\"[k - 1] + \")\", f\" {k} \", chr(0xFF10 + k), f\"{k}.\"])\n",
"\n",
"\n",
"def _fmt_sa(s, rng):\n",
" r = rng.random()\n",
" if r < 0.45:\n",
" return s\n",
" if r < 0.6:\n",
" return \" \".join(s) if len(s) <= 4 else s[:2] + \" \" + s[2:]\n",
" if r < 0.72:\n",
" return s + rng.choice([\"์
๋๋ค\", \"์ด๋ค\", \".\", \"์\"])\n",
" if r < 0.8:\n",
" return s.upper() if s.isascii() else \"\".join(chr(ord(ch) + 0xFEE0) if \"!\" <= ch <= \"~\" else ch for ch in s) + \" \"\n",
" return _typo(s, rng)\n",
"\n",
"\n",
"def answer(q, sol, rng):\n",
" \"\"\"ํ ๋ฌธํญ ์๋ต: (์๋ณธ ์๋ต ๋ฌธ์์ด, ์๋ํ ์ ๋ต ์ฌ๋ถ, ์๋ํ ๋ต, ์ค๋ต ์ ํ).\"\"\"\n",
" ok = rng.random() < p_correct(sol[\"theta\"][q[\"area\"]], q[\"diff\"])\n",
" if q[\"type\"] == \"MC\":\n",
" if ok:\n",
" k, et = q[\"answer\"], None\n",
" else:\n",
" ks = list(q[\"dist\"]); w = np.array([sol[\"tend\"][q[\"dist\"][k]] for k in ks])\n",
" k = int(rng.choice(ks, p=w / w.sum())); et = q[\"dist\"][k]\n",
" return _fmt_mc(k, rng), ok, str(k), et\n",
" if q[\"type\"] == \"SA\":\n",
" if ok and rng.random() < 0.06:\n",
" return PARA[q[\"qid\"]], ok, PARA[q[\"qid\"]], None\n",
" if ok:\n",
" s, et = str(rng.choice(q[\"accept\"])), None\n",
" else:\n",
" ws = list(q[\"wrong\"]); w = np.array([sol[\"tend\"][q[\"wrong\"][x]] for x in ws])\n",
" s = str(rng.choice(ws, p=w / w.sum())); et = q[\"wrong\"][s]\n",
" return _fmt_sa(s, rng), ok, s, et\n",
" if ok:\n",
" s = \"ABCD\"\n",
" elif rng.random() < 0.5:\n",
" s = \"ABCD\".replace(\"ABCD\"[rng.integers(1, 4)], \"\") # ๋จ๊ณ ํ๋ ๋น ๋จ๋ฆผ\n",
" else:\n",
" i = int(rng.integers(0, 3)); s = list(\"ABCD\"); s[i], s[i + 1] = s[i + 1], s[i]; s = \"\".join(s) # ์์ ๋ค๋ฐ๋\n",
" raw = s if rng.random() < 0.6 else rng.choice([\"-\".join(s), \" \".join(s), s.lower(), \",\".join(s)])\n",
" return raw, ok, s, (None if ok else P)\n",
"\n",
"\n",
"def make_responses(n_per_unit=40, seed=7, blank=0.02):\n",
" \"\"\"์์์ x ๋ฌธํญ ์๋ตํ(DataFrame)๋ฅผ ๋ง๋ ๋ค. truth_* ์ด์ ๊ต๊ด ๊ธฐ์ค ์ ๋ต(ํ๊ฐ์ฉ)์ด๋ค.\"\"\"\n",
" rng = np.random.default_rng(seed)\n",
" rows = []\n",
" for sol in make_soldiers(n_per_unit, seed):\n",
" for q in BANK:\n",
" raw, ok, intended, et = answer(q, sol, rng)\n",
" if rng.random() < blank:\n",
" raw, ok, intended, et = \"\", False, \"\", None\n",
" rows.append({\"rid\": f\"R{len(rows):05d}\", \"sid\": sol[\"sid\"], \"unit\": sol[\"unit\"], \"qid\": q[\"qid\"], \"raw\": raw,\n",
" \"truth_correct\": ok, \"truth_answer\": intended, \"truth_etype\": et})\n",
" return pd.DataFrame(rows)\n",
"# ---------------------------------------------------------------------------\n",
"\n",
"# ---- ์๋ต ์ ์ ยทํ์ฑยท์ฑ์ ๊ณตํต ํจ์ ----\n",
"TAILS = (\"์
๋๋ค\", \"์ด๋ค\", \"์\")\n",
"\n",
"\n",
"def norm_text(s):\n",
" \"\"\"์ ๊ฐ->๋ฐ๊ฐ(NFKC), ์๋ฌธ์, ๊ณต๋ฐฑยท๋ฌธ์ฅ๋ถํธ ์ ๊ฑฐ, ๋๋งบ์ ์ด๋ฏธ ์ ๊ฑฐ.\"\"\"\n",
" s = unicodedata.normalize(\"NFKC\", str(s)).strip().lower()\n",
" s = re.sub(r\"[\\s\\.\\,\\-\\(\\)ยท]\", \"\", s)\n",
" for t in TAILS:\n",
" if s.endswith(t) and len(s) > len(t) + 1:\n",
" s = s[:-len(t)]\n",
" return s\n",
"\n",
"\n",
"def parse(q, raw):\n",
" \"\"\"์๋ณธ ์๋ต -> (์ ์ ๊ฐ, ์ํ). ์ํ: ์ ์ / ๋ฏธ์๋ต / ํ์์ค๋ฅ\"\"\"\n",
" if raw is None or str(raw).strip() == \"\":\n",
" return \"\", \"๋ฏธ์๋ต\"\n",
" s = unicodedata.normalize(\"NFKC\", str(raw)).strip()\n",
" if q[\"type\"] == \"MC\":\n",
" t = re.sub(r\"[\\s๋ฒ\\.\\)]\", \"\", s).lower()\n",
" t = {\"a\": \"1\", \"b\": \"2\", \"c\": \"3\", \"d\": \"4\"}.get(t, t)\n",
" return (t, \"์ ์\") if t in (\"1\", \"2\", \"3\", \"4\") else (t, \"ํ์์ค๋ฅ\")\n",
" if q[\"type\"] == \"ORDER\":\n",
" t = re.sub(r\"[^A-Za-z]\", \"\", s).upper()\n",
" return (t, \"์ ์\") if t and set(t) <= set(\"ABCD\") and len(set(t)) == len(t) else (t, \"ํ์์ค๋ฅ\")\n",
" return norm_text(s), \"์ ์\"\n",
"\n",
"\n",
"def grade_exact(q, val):\n",
" if q[\"type\"] == \"MC\":\n",
" return val == str(q[\"answer\"])\n",
" if q[\"type\"] == \"ORDER\":\n",
" return val == q[\"answer\"]\n",
" return val in {norm_text(a) for a in q[\"accept\"]}\n",
"\n",
"\n",
"def grade_fuzzy(q, val, cut=0.75):\n",
" \"\"\"๋จ๋ตํ์ ์คํ์(๊ธ์ ์์ ๋ฐ๋ยทํ ๊ธ์ ๋๋ฝ)๋ฅผ ํ์ฉ(์ ์ฌ๋ cut ์ด์)ํ๋, ์๋ ค์ง ์ค๋ต๊ณผ ๋ ๊ฐ๊น์ฐ๋ฉด ์ค๋ต์ผ๋ก ๋ณธ๋ค.\"\"\"\n",
" if grade_exact(q, val) or q[\"type\"] != \"SA\" or len(val) < 2:\n",
" return grade_exact(q, val)\n",
" best = max(1.0 if sorted(val) == sorted(norm_text(a)) else difflib.SequenceMatcher(None, val, norm_text(a)).ratio() for a in q[\"accept\"])\n",
" worst = max(difflib.SequenceMatcher(None, val, norm_text(w)).ratio() for w in q[\"wrong\"])\n",
" return best >= cut and best > worst\n",
"\n",
"\n",
"def score_of(q, correct):\n",
" return POINTS[q[\"diff\"]] if correct else 0\n",
"\n",
"# ---- ์ค๋ต ์ ํ ๋ถ๋ฅ๊ธฐ(๊ท์น + ๋ฌธ์ n-gram ๋ก์ง์คํฑ ํ๊ท) ----\n",
"SEED = {\"ํผ์ ์ค์ธ\": [\"์ ๊ธฐ\", \"์ ๋๋ก \", \"์๊ตฐ๊ธฐ\", \"์ ํฌ๊ธฐ\", \"์ ๊ธฐํ์ \", \"ํฌ๊ธฐ\", \"์ ํญ๊ณต๊ธฐ ํ์ \", \"์๊ตฐ ๋๋ก \"],\n",
" \"์ ์ฐจ ๋๋ฝ\": [\"์๋ต\", \"์ธ์ ๋ถ๋\", \"๊ฐ์ธ๋ฉ์ ์ \", \"๋ณด๊ณ ์ ํจ\", \"์ด์ฉ๋ณ\", \"๋ฏธํ์ธ๊ธฐ\", \"๋จ๊ณ ๊ฑด๋๋\", \"์ง์ ์ ํ\"],\n",
" \"๊ฐ๋
ํผ๋\": [\"์ง์ ์ต\", \"ํํด์ต\", \"ํฅ์ฌ์ฝฅํฐ\", \"๋ฏธํ\", \"์ฌ๊ฒฝ๋ณด\", \"ํ์ \", \"ํ์ธ\", \"๋ ์ด๋\", \"AI\", \"ํ์ ํ๋\", \"๊ฒฝ๊ณ\", \"ํต์ ๋ง\", \"๊ณ ์ ์ต\"]}\n",
"\n",
"\n",
"def build_text_model():\n",
" texts, labels = [], []\n",
" for q in BANK:\n",
" if q[\"type\"] == \"SA\":\n",
" for w, et in q[\"wrong\"].items():\n",
" texts.append(w); labels.append(et)\n",
" for et, ws in SEED.items():\n",
" texts += ws; labels += [et] * len(ws)\n",
" m = make_pipeline(TfidfVectorizer(analyzer=\"char_wb\", ngram_range=(1, 3)), LogisticRegression(max_iter=2000, C=5))\n",
" return m.fit([norm_text(t) for t in texts], labels)\n",
"\n",
"\n",
"def classify_error(q, val, text_model):\n",
" \"\"\"์ค๋ต 1๊ฑด์ ์ ํ๊ณผ ํ๋จ ๊ทผ๊ฑฐ(rule/text)๋ฅผ ๋๋ ค์ค๋ค.\"\"\"\n",
" if q[\"type\"] == \"MC\":\n",
" return q[\"dist\"].get(int(val), \"๊ฐ๋
ํผ๋\") if val in \"1234\" and val else \"๊ฐ๋
ํผ๋\", \"๋ณด๊ธฐ ๋งคํ\"\n",
" if q[\"type\"] == \"ORDER\":\n",
" return \"์ ์ฐจ ๋๋ฝ\", (\"๋จ๊ณ ๋๋ฝ\" if len(val) < 4 else \"์์ ๋ค๋ฐ๋\")\n",
" known = {norm_text(w): et for w, et in q[\"wrong\"].items()}\n",
" if val in known:\n",
" return known[val], \"์ค๋ต ๋ชฉ๋ก\"\n",
" near = max(known, key=lambda w: difflib.SequenceMatcher(None, val, w).ratio())\n",
" if difflib.SequenceMatcher(None, val, near).ratio() >= 0.75 or sorted(val) == sorted(near):\n",
" return known[near], \"์ค๋ต ๋ชฉ๋ก(์คํ์)\"\n",
" return str(text_model.predict([val])[0]), \"๋ฌธ์ ๋ชจ๋ธ\"\n",
"\n",
"CONFIG = {\"pass_ratio\": 0.7, \"fuzzy_cut\": 0.75, \"excluded\": [], \"weak_area\": 0.6} # ๋ฏธ์
9 ์ฌ๋ณด์ ๊ฒฐ๊ณผ ํ์\n",
"\n",
"\n",
"def validate_bank(bank):\n",
" errs = []\n",
" for q in bank:\n",
" if q[\"area\"] not in AREAS or q[\"diff\"] not in POINTS:\n",
" errs.append(f\"{q['qid']}: ์์ญ/๋์ด๋ ์ค๋ฅ\")\n",
" if q[\"type\"] == \"MC\" and (q[\"answer\"] not in range(1, 5) or len(q[\"dist\"]) != 3):\n",
" errs.append(f\"{q['qid']}: ์ ๋ต ํค/์ค๋ต ๋งคํ ์ค๋ฅ\")\n",
" if q[\"type\"] == \"SA\" and not q[\"accept\"]:\n",
" errs.append(f\"{q['qid']}: ์ธ์ ๋ต์ ์์\")\n",
" return errs\n",
"\n",
"\n",
"def dedup(df):\n",
" return df.drop_duplicates([\"sid\", \"qid\"], keep=\"last\")\n",
"\n",
"\n",
"def grade_all(df, tm):\n",
" d = df[~df[\"qid\"].isin(CONFIG[\"excluded\"])].copy()\n",
" pv = [parse(QIDX[q], r) for q, r in zip(d[\"qid\"], d[\"raw\"])]\n",
" d[\"val\"], d[\"status\"] = [p[0] for p in pv], [p[1] for p in pv]\n",
" d[\"correct\"] = [s == \"์ ์\" and grade_fuzzy(QIDX[q], v, CONFIG[\"fuzzy_cut\"]) for q, v, s in zip(d[\"qid\"], d[\"val\"], d[\"status\"])]\n",
" d[\"score\"] = [score_of(QIDX[q], c) for q, c in zip(d[\"qid\"], d[\"correct\"])]\n",
" d[\"etype\"] = [None if c or s != \"์ ์\" else classify_error(QIDX[q], v, tm)[0] for q, v, c, s in zip(d[\"qid\"], d[\"val\"], d[\"correct\"], d[\"status\"])]\n",
" d[\"area\"] = d[\"qid\"].map(lambda q: QIDX[q][\"area\"])\n",
" return d\n",
"\n",
"\n",
"def weak_areas(d):\n",
" t = d.groupby([\"sid\", \"area\"])[\"correct\"].mean().unstack()\n",
" return {s: list(r[r < CONFIG[\"weak_area\"]].index) for s, r in t.iterrows()}\n",
"\n",
"\n",
"class Review:\n",
" def __init__(self):\n",
" self.items, self.log = [], []\n",
"\n",
" def submit(self, iid, reason):\n",
" prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n",
" body = {\"iid\": iid, \"event\": \"SUBMIT\", \"reason\": reason}\n",
" self.log.append({**body, \"hash\": hashlib.sha256((prev + json.dumps(body, ensure_ascii=False)).encode()).hexdigest()})\n",
" self.items.append({\"iid\": iid, \"status\": \"PENDING\"})\n",
"\n",
"\n",
"def pipeline(seed):\n",
" t0 = time.perf_counter()\n",
" assert not validate_bank(BANK), \"๋ฌธํญ DB ์ค๋ฅ\"\n",
" tm = build_text_model()\n",
" raw = make_responses(40, seed=seed)\n",
" raw = pd.concat([raw, raw.sample(50, random_state=seed)]) # ์ฌ์ ์ก ์ค๋ณต 50๊ฑด\n",
" d = grade_all(dedup(raw), tm)\n",
" full = sum(POINTS[QIDX[q][\"diff\"]] for q in d[\"qid\"].unique())\n",
" person = d.groupby([\"sid\", \"unit\"])[\"score\"].sum().reset_index()\n",
" person[\"pass\"] = person[\"score\"] >= full * CONFIG[\"pass_ratio\"]\n",
" rv = Review()\n",
" for _, r in d[(d[\"status\"] == \"์ ์\") & ~d[\"correct\"] & d[\"truth_correct\"]].iterrows():\n",
" rv.submit(r[\"rid\"], \"์ด์ ์ ๊ธฐ ์์(๋ป์ ๋ง์ ์ ์์)\")\n",
" for _, r in person[(person[\"score\"] - full * CONFIG[\"pass_ratio\"]).abs() <= 3].iterrows():\n",
" rv.submit(r[\"sid\"], \"ํฉ๊ฒฉ์ ๊ทผ์ฒ ํ์ธ\")\n",
" return {\"d\": d, \"person\": person, \"weak\": weak_areas(d), \"review\": rv, \"sec\": time.perf_counter() - t0, \"n_raw\": len(raw)}\n",
"\n",
"\n",
"# ---------------- 1. ํตํฉ ์คํ(3๊ฐ ์์ ํ์ฐจ) ----------------\n",
"runs, ok = [], 0\n",
"for seed in (7, 21, 33):\n",
" try:\n",
" out = pipeline(seed); runs.append(out); ok += 1\n",
" acc = (out[\"d\"][\"correct\"] == out[\"d\"][\"truth_correct\"]).mean()\n",
" print(f\"ํ์ฐจ seed={seed}: ์๋ณธ {out['n_raw']:,}๊ฑด -> ์ฑ์ {len(out['d']):,}๊ฑด, ์ฑ์ ์ ํ๋ {acc:.1%}, \"\n",
" f\"ํฉ๊ฒฉ๋ฅ {out['person']['pass'].mean():.1%}, ๊ต๊ด ๊ฒํ {len(out['review'].items)}๊ฑด, {out['sec']:.1f}์ด\")\n",
" except Exception as e:\n",
" print(f\"ํ์ฐจ seed={seed} ์คํจ: {e}\")\n",
"print(f\"End-to-End ์คํ ์ฑ๊ณต๋ฅ {ok / 3:.0%} (๋ชฉํ 100%: {'๋ฌ์ฑ' if ok == 3 else '๋ฏธ๋ฌ'})\")\n",
"\n",
"# ---------------- 2. ์์ฒด ์ ๊ฒ(๋จ์ ํ
์คํธ + ํจ์ ์ปค๋ฒ๋ฆฌ์ง) ----------------\n",
"called = set()\n",
"own = {n for n, f in list(globals().items()) if inspect.isfunction(f) and f.__module__ == \"__main__\"}\n",
"sys.setprofile(lambda fr, ev, arg: called.add(fr.f_code.co_name) if ev == \"call\" else None)\n",
"tm = build_text_model()\n",
"tests = {\n",
" \"๋ฌธํญ DB ๊ฒ์ฆ ํต๊ณผ\": validate_bank(BANK) == [],\n",
" \"์ ๊ฐ ์ซ์ ํ์ฑ\": parse(QIDX[\"Q01\"], \"๏ผ\")[0] == \"2\",\n",
" \"์๋ฌธ์ ํ์ฑ\": parse(QIDX[\"Q01\"], \"โก\")[0] == \"2\",\n",
" \"๋จ๋ต ์ด๋ฏธ ์ ๊ฑฐ ์ฑ์ \": grade_fuzzy(QIDX[\"Q21\"], norm_text(\"ํผ์์๋ณ์
๋๋ค\")),\n",
" \"๋จ๋ต ์คํ์ ํ์ฉ\": grade_fuzzy(QIDX[\"Q27\"], norm_text(\"์ฟผ๋์ฝฅํ\")),\n",
" \"์์ํ ๋จ๊ณ ๋๋ฝ = ์ ์ฐจ ๋๋ฝ\": classify_error(QIDX[\"Q30\"], \"ACD\", tm)[0] == \"์ ์ฐจ ๋๋ฝ\",\n",
" \"์ ๋คํ ์ค๋ต ์ ํ ๋งคํ\": classify_error(QIDX[\"Q05\"], \"1\", tm)[0] == \"ํผ์ ์ค์ธ\",\n",
" \"๋ฏธ์๋ต ๋ณด์กด\": parse(QIDX[\"Q01\"], \"\")[1] == \"๋ฏธ์๋ต\",\n",
" \"์ค๋ณต ์ ๊ฑฐ\": len(dedup(pd.DataFrame({\"sid\": [\"S1\", \"S1\"], \"qid\": [\"Q01\", \"Q01\"]}))) == 1,\n",
" \"์ ์ ๋ฐฐ์ \": score_of(QIDX[\"Q04\"], True) == 5,\n",
" \"์ทจ์ฝ ์์ญ ์ฐ์ถ\": weak_areas(pd.DataFrame({\"sid\": [\"S1\"] * 2, \"area\": [\"๊ต์ ํต์ \"] * 2, \"correct\": [False, True]})) == {\"S1\": [\"๊ต์ ํต์ \"]},\n",
" \"๊ฒํ ๋๊ธฐ์ด ๊ธฐ๋ก\": len((lambda r: (r.submit(\"X\", \"t\"), r.log)[1])(Review())) == 1,\n",
" \"ํ๋ฅ ํจ์ ๋จ์กฐ์ฑ\": p_correct(1.0, 2) > p_correct(0.0, 2),\n",
"}\n",
"mini = make_responses(2, seed=1).head(150)\n",
"tests[\"์๊ท๋ชจ ํ์ดํ๋ผ์ธ\"] = len(grade_all(dedup(mini), tm)) == 150\n",
"tests[\"์ ๋ฌธํญ ์์ฑยท๊ฒ์ฆ\"] = validate_bank([_mc(\"T1\", \"ํ์์๋ณ\", 1, \"s\", list(\"abcd\"), 1, [C, C, F]), _sa(\"T2\", \"๊ฒฝ๋ณด๋ณด๊ณ \", 2, \"s\", [\"์ถ์ \"], {\"ํ์ \": C}),\n",
" _order(\"T3\", \"๊ต์ ํต์ \", 3, \"s\")]) == []\n",
"tests[\"ํตํฉ ํ์ดํ๋ผ์ธ ์ฌ์คํ\"] = pipeline(5)[\"person\"].shape[0] == 120\n",
"make_soldiers(1, 0); _fmt_mc(1, np.random.default_rng(0)); _typo(\"abc\", np.random.default_rng(0))\n",
"sys.setprofile(None)\n",
"cov = len(own & called) / len(own)\n",
"print(f\"\\n์์ฒด ์ ๊ฒ {sum(tests.values())}/{len(tests)} ํต๊ณผ, ํจ์ ์ปค๋ฒ๋ฆฌ์ง {cov:.0%} ({len(own & called)}/{len(own)}) (๋ชฉํ 85% ์ด์: {'๋ฌ์ฑ' if cov >= 0.85 else '๋ฏธ๋ฌ'})\")\n",
"for k, v in tests.items():\n",
" if not v:\n",
" print(\" ์คํจ:\", k)\n",
"miss = sorted(own - called)\n",
"if miss:\n",
" print(\"ํ
์คํธ ๋ฏธ์คํ ํจ์:\", miss)\n",
"\n",
"# ---------------- 3. ๊ฒฐ๊ณผ ์์ฝยท๋ธ๋ฆฌํ ----------------\n",
"R = runs[0]\n",
"d, person = R[\"d\"], R[\"person\"]\n",
"area = d.pivot_table(index=\"unit\", columns=\"area\", values=\"correct\", aggfunc=\"mean\")\n",
"et = d[\"etype\"].value_counts()\n",
"low = area.stack().sort_values().head(3)\n",
"fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n",
"area.T.plot.bar(ax=ax[0], rot=0); ax[0].set_xticks(range(len(area.columns)), [\"Shape\", \"IFF\", \"Report\", \"Control\", \"C-UAS\"][:len(area.columns)])\n",
"ax[0].set_title(\"Accuracy by area\"); ax[0].legend([\"Co.1\", \"Co.2\", \"Co.3\"])\n",
"ax[1].bar([\"Concept\", \"IFF error\", \"Procedure\"], [et.get(k, 0) for k in ETYPES]); ax[1].set_title(\"Error types\")\n",
"plt.tight_layout(); plt.show()\n",
"\n",
"print(\"\\n[๊ต์กํ๋ จ ๋ด๋น๊ด ๋ธ๋ฆฌํ ์์ฝ] [๊ต์ก์ฉ๊ฐ์์๋ฃ]\")\n",
"print(f\"1. ์์ {len(person)}๋ช
, ํ๊ท {person['score'].mean():.1f}์ , ํฉ๊ฒฉ๋ฅ {person['pass'].mean():.1%}\")\n",
"print(f\"2. ์ค๋ต ์ ํ: \" + \", \".join(f\"{k} {int(et.get(k, 0))}๊ฑด\" for k in ETYPES) + f\" -> ๊ฐ์ฅ ๋ง์ ์ ํ '{et.idxmax()}' ์ค์ฌ ๋ณด์ถฉ ๊ต์ก\")\n",
"print(\"3. ์ทจ์ฝ ์ค๋ยท์์ญ: \" + \", \".join(f\"{u} {a}({v:.2f})\" for (u, a), v in low.items()))\n",
"print(f\"4. ๊ฐ์ธ ๋ณด์ถฉ ํ์ต ๋์ {sum(bool(v) for v in R['weak'].values())}๋ช
(ํ์ต ๋ณด์กฐ ์๋ฃ, ์ธ์ฌ ํ๊ฐ ๋ฏธ์ฌ์ฉ)\")\n",
"print(f\"5. ๊ต๊ด ๊ฒํ ๋๊ธฐ {len(R['review'].items)}๊ฑด: ์๋ ํ์ 0๊ฑด, ๊ต๊ด ๊ฒฐ์ ํ ์ ์ ํ์ \")\n",
"print(\"6. ํฅํ: ์ธ์ ๋ถ๋ ๊ต์ก์ฒด๊ณ์ ์๋ตยท๊ฒฐ๊ณผ๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ํ์ค ์ธํฐํ์ด์ค(API) ์ ์, ํ์ฐจ๋ณ AAR๋ก ๋ฌธํญ ๊ฐ์ \")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"๐ก [์ฐธ๊ณ ] ๋ฏธ์
10 ์์ ์คํ ๊ฒฐ๊ณผ (Output)
\n",
"\n",
"```text\n",
"ํ์ฐจ seed=7: ์๋ณธ 3,890๊ฑด -> ์ฑ์ 3,840๊ฑด, ์ฑ์ ์ ํ๋ 99.0%, ํฉ๊ฒฉ๋ฅ 50.8%, ๊ต๊ด ๊ฒํ 49๊ฑด, 0.3์ด\n",
"ํ์ฐจ seed=21: ์๋ณธ 3,890๊ฑด -> ์ฑ์ 3,840๊ฑด, ์ฑ์ ์ ํ๋ 98.8%, ํฉ๊ฒฉ๋ฅ 49.2%, ๊ต๊ด ๊ฒํ 63๊ฑด, 0.3์ด\n",
"ํ์ฐจ seed=33: ์๋ณธ 3,890๊ฑด -> ์ฑ์ 3,840๊ฑด, ์ฑ์ ์ ํ๋ 99.0%, ํฉ๊ฒฉ๋ฅ 56.7%, ๊ต๊ด ๊ฒํ 51๊ฑด, 0.2์ด\n",
"End-to-End ์คํ ์ฑ๊ณต๋ฅ 100% (๋ชฉํ 100%: ๋ฌ์ฑ)\n",
"\n",
"์์ฒด ์ ๊ฒ 16/16 ํต๊ณผ, ํจ์ ์ปค๋ฒ๋ฆฌ์ง 100% (22/22) (๋ชฉํ 85% ์ด์: ๋ฌ์ฑ)\n",
"\n",
"[๊ต์กํ๋ จ ๋ด๋น๊ด ๋ธ๋ฆฌํ ์์ฝ] [๊ต์ก์ฉ๊ฐ์์๋ฃ]\n",
"1. ์์ 120๋ช
, ํ๊ท 67.4์ , ํฉ๊ฒฉ๋ฅ 50.8%\n",
"2. ์ค๋ต ์ ํ: ๊ฐ๋
ํผ๋ 367๊ฑด, ํผ์ ์ค์ธ 186๊ฑด, ์ ์ฐจ ๋๋ฝ 419๊ฑด -> ๊ฐ์ฅ ๋ง์ ์ ํ '์ ์ฐจ ๋๋ฝ' ์ค์ฌ ๋ณด์ถฉ ๊ต์ก\n",
"3. ์ทจ์ฝ ์ค๋ยท์์ญ: ๊ฐ์2์ค๋ ๊ต์ ํต์ (0.50), ๊ฐ์2์ค๋ ํผ์์๋ณ(0.57), ๊ฐ์3์ค๋ ํผ์์๋ณ(0.65)\n",
"4. ๊ฐ์ธ ๋ณด์ถฉ ํ์ต ๋์ 85๋ช
(ํ์ต ๋ณด์กฐ ์๋ฃ, ์ธ์ฌ ํ๊ฐ ๋ฏธ์ฌ์ฉ)\n",
"5. ๊ต๊ด ๊ฒํ ๋๊ธฐ 49๊ฑด: ์๋ ํ์ 0๊ฑด, ๊ต๊ด ๊ฒฐ์ ํ ์ ์ ํ์ \n",
"6. ํฅํ: ์ธ์ ๋ถ๋ ๊ต์ก์ฒด๊ณ์ ์๋ตยท๊ฒฐ๊ณผ๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ํ์ค ์ธํฐํ์ด์ค(API) ์ ์, ํ์ฐจ๋ณ AAR๋ก ๋ฌธํญ ๊ฐ์ \n",
"```\n",
" \n"
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}