{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ๐ŸŽฏ [MNT06] AI ๊ธฐ๋ฐ˜ ๋ถ€ํ’ˆ ํ๊ธฐ์œจ ์‚ฐ์ •ยท์˜ˆ์ธก ๋ชจ๋ธ\n", "### Google Colab ์‹ค์Šต ํ™˜๊ฒฝ (์žฅ๋น„์ •๋น„ยท์ˆ˜๋ฆฌ๋ถ€์† / Level 3)\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/MNT06/MNT06_solution.ipynb)\n", "\n", "---\n", "\n", "## ๐Ÿ“Œ 1. ํ”„๋กœ์ ํŠธ ๊ฐœ์š” ๋ฐ ๋ฐฐ๊ฒฝ\n", "\n", "| ํ•ญ๋ชฉ | ๋‚ด์šฉ |\n", "| :--- | :--- |\n", "| **๊ณผ์ • ๋ถ„๋ฅ˜** | ์žฅ๋น„์ •๋น„ยท์ˆ˜๋ฆฌ๋ถ€์† (Level 3) |\n", "| **ํ•™์Šต ์†Œ์š” ์‹œ๊ฐ„** | 10๊ฐœ ๋ฏธ์…˜ (5์ผ / ์ด 28.5์‹œ๊ฐ„) |\n", "| **ํ™˜๊ฒฝ ์‚ฌ์–‘** | Python 3.10+, numpy, pandas, matplotlib, scipy, scikit-learn |\n", "\n", "### ๐Ÿ“– ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ\n", "์ˆ˜๋ฆฌ๋ถ€์†์˜ ํ๊ธฐ์œจ์ด ๋†’์œผ๋ฉด ์ •๋น„๋น„์šฉ๊ณผ ์žฌ๊ณ  ๋ถ€๋‹ด์ด ์ปค์ง€๊ณ , ์กฐ๋‹ฌ์ด ๋Šฆ์–ด์ง€๋Š” ๋™์•ˆ ์žฅ๋น„๋Š” ๊ฐ€๋™ํ•˜์ง€ ๋ชปํ•œ๋‹ค. ํ๊ธฐ๊ฐ€ ์žฆ์€ ๋ถ€ํ’ˆ ๊ฐ€์šด๋ฐ ์•ผ์ „์—์„œ 3D ํ”„๋ฆฐํŒ…์œผ๋กœ ๋Œ€์ฒด ์ œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€ํ’ˆ์„ ๊ฐ€๋ ค๋‚ด๋ฉด ํ๊ธฐ ์†์‹ค๊ณผ ์กฐ๋‹ฌ ๋Œ€๊ธฐ๋ฅผ ํ•จ๊ป˜ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค. ๋ณธ ํ”„๋กœ์ ํŠธ๋Š” ๋ถ€ํ’ˆ ํ๊ธฐ์œจ์˜ ์‚ฐ์ •ยท์˜ˆ์ธก์„ ๋Œ€์ฒด ์ œ์ž‘ ํƒ€๋‹น์„ฑ ํŒ๋‹จ๊ณผ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ, ๊ต์œก์šฉ ๊ฐ€์ƒ ๋ถ€ํ’ˆ ์ž๋ฃŒ(CAD ์š”์•ฝ ์น˜์ˆ˜, ๊ธˆ์†ยทํด๋ฆฌ๋จธ ์†Œ์žฌ ๋ฌผ์„ฑ, ์‚ฌ์šฉ ํ•˜์ค‘, ํ๊ธฐ์œจ, ์กฐ๋‹ฌ ์†Œ์š”์ผ)๋ฅผ ์ฝ”๋“œ๋กœ ๋งŒ๋“ค์–ด ๋ถ„์„ํ•œ๋‹ค. ๋ฐ์ดํ„ฐ ์ •์˜์™€ ๋ฉ”์‰ฌยท์Šฌ๋ผ์ด์‹ฑ ์ „์ฒ˜๋ฆฌ, ํ˜•์ƒ ๊ธฐ๋ฐ˜ Baseline๊ณผ ํƒ์ƒ‰์  ๋ถ„์„, ์ธ์žฅ๊ฐ•๋„ยทํ”ผ๋กœ ์ˆ˜๋ช… ์˜ˆ์ธก๊ณผ ๊ตฐ ๊ทœ๊ฒฉ ๋ถ€ํ•ฉ ์‹ ๋ขฐ๋„ ์ ์ˆ˜, ๋Œ€์‹œ๋ณด๋“œ์™€ ์„ค๋ช…๊ฐ€๋Šฅ์„ฑ, ์ •๋น„๊ธฐ์ˆ ๊ด€ ์Šน์ธ(HITL) ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, 50์ข… ์ผ๊ด„ ํ‰๊ฐ€ ์ŠคํŠธ๋ ˆ์Šค ์‹œํ—˜, ํŒŒ๊ดด ์‹œํ—˜ ๋Œ€๋น„ 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. 3D ํ”„๋ฆฐํŒ… ์ œ์ž‘ ๋Œ€์ƒ ๊ตฐ์ˆ˜ ๋ถ€ํ’ˆ 3D CAD ๋ชจ๋ธ ๋ฐ ๊ธˆ์†/ํด๋ฆฌ๋จธ ์†Œ์žฌ ๋ฌผ์„ฑ ๋ฐ์ดํ„ฐ ์ •์˜\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 1.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "3D ํ”„๋ฆฐํŒ… ๋Œ€์ƒ ๊ตฐ์ˆ˜ ๋ถ€ํ’ˆ 3D CAD ๋ชจ๋ธ ๋ฐ ์†Œ์žฌ ๋ฌผ์„ฑ ๋ฐ์ดํ„ฐ๋ฅผ ์ •์˜ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **ํ๊ธฐ์œจ์ด ๋†’์€ ์ˆ˜๋ฆฌ๋ถ€์† ๊ฐ€์šด๋ฐ 3D ํ”„๋ฆฐํŒ… ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด์˜ 3D CAD ๋ชจ๋ธ ์š”์•ฝ ์ •๋ณด์™€ ๊ธˆ์†ยทํด๋ฆฌ๋จธ ์†Œ์žฌ ๋ฌผ์„ฑ ๋ฐ์ดํ„ฐ๋ฅผ ์ •์˜ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **ํ๊ธฐ ๋ถ€ํ’ˆ ์‹๋ณ„๋ถ€ํ„ฐ CAD ํ™•๋ณด, ์†Œ์žฌ ์„ ์ •, ์ œ์ž‘ ๊ฐ€๋Šฅ์„ฑ ํŒ๋ณ„, ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€, ์ •๋น„๊ธฐ์ˆ ๊ด€ ์Šน์ธ, ์žฅ์ฐฉ ์‹œํ—˜, ์‚ฌํ›„๊ฒ€ํ† ๊นŒ์ง€ ์—…๋ฌด ํ๋ฆ„์„ ๋‹จ๊ณ„๋ณ„๋กœ ์ •๋ฆฌํ•˜๊ณ  ๋ถ€ํ’ˆยทCADยท์†Œ์žฌยท์‚ฌ์šฉ ์กฐ๊ฑดยท๊ฒฐ๊ณผ ๋‹ค์„ฏ ๋ฌถ์Œ์œผ๋กœ ํ”ผ์ฒ˜ 20๊ฐœ ์ด์ƒ ์ •์˜ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/mnt06_logistics_domain_spec.md ๋ฐ ๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ์‹ค๋ฌด ์—…๋ฌด ํ”„๋กœ์„ธ์Šค์™€์˜ ์ผ์น˜์„ฑ ๋ฐ ๋ถ„์„ ๋ชฉํ‘œ์˜ ๋ช…ํ™•์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต ์ž‘์„ฑ ์™„๋ฃŒ, ํ•„์š” ํ”ผ์ฒ˜(Feature) ๋ชฉ๋ก 20๊ฐœ ์ด์ƒ ์ •์˜\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ๋„๋ฉ”์ธ ๋ถ„์„์„œ ๋ฐ ๋ฐ์ดํ„ฐ ์š”๊ตฌ๋ช…์„ธ์„œ ์Šน์ธ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๊ตฐ์ˆ˜ ๋‹ด๋‹น ์‹ค๋ฌด์ž ์ธํ„ฐ๋ทฐ ๋ฐ ๊ธฐ์กด ์ „์‚ฐ์ฒด๊ณ„ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ๋Œ€์กฐ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"ํ๊ธฐ์œจ์ด ๋†’์€ ์ˆ˜๋ฆฌ๋ถ€์†์˜ ์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ๋Œ€์ฒด ์ œ์ž‘ ํŒ๋‹จ์— ํ•„์š”ํ•œ 3D CAD ๋ชจ๋ธยท๊ธˆ์†/ํด๋ฆฌ๋จธ ์†Œ์žฌ ๋ฌผ์„ฑยท์‚ฌ์šฉ ์กฐ๊ฑด ๋ฐ์ดํ„ฐ์˜ ์Šคํ‚ค๋งˆ์™€ ๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ(ํ”ผ์ฒ˜ 20๊ฐœ ์ด์ƒ), ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต์„ ์ •์˜ํ•˜๊ณ  ๊ฐ€์ƒ ์ƒ˜ํ”Œ์„ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import json\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "\n", "# 1) ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต: ํ๊ธฐ ๋ถ€ํ’ˆ ์‹๋ณ„์—์„œ ๋Œ€์ฒด ์ œ์ž‘ ์Šน์ธยทํšจ๊ณผ ํ™•์ธ๊นŒ์ง€\n", "PROCESS = [(\"ํ๊ธฐ ๋ถ€ํ’ˆ ์‹๋ณ„\", \"๋ถ€ํ’ˆ ์ฝ”๋“œ, ์›” ํ๊ธฐ์ˆ˜๋Ÿ‰, ํ๊ธฐ์œจ, ์กฐ๋‹ฌ ์†Œ์š”์ผ\"), (\"CAD ํ™•๋ณด\", \"CAD ํŒŒ์ผ ํ˜•์‹, ์‚ผ๊ฐํ˜• ์ˆ˜, ๋‹จ์œ„, ์น˜์ˆ˜\"),\n", " (\"์†Œ์žฌ ์„ ์ •\", \"์†Œ์žฌ ์ฝ”๋“œ, ๊ณต์ • ๊ตฌ๋ถ„, ์ธ์žฅ๊ฐ•๋„ ๊ธฐ์ค€๊ฐ’, ์ธ์ฆ์„œ ์—ฌ๋ถ€\"), (\"์ œ์ž‘ ๊ฐ€๋Šฅ์„ฑ ํŒ๋ณ„\", \"๋ฒฝ๋‘๊ป˜, ๋Œ์ถœ ๋น„์œจ, ์ตœ๋Œ€ ์น˜์ˆ˜, ๋‚˜์‚ฌ๋ถ€\"),\n", " (\"์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€\", \"์‚ฌ์šฉ ํ•˜์ค‘, ์•ˆ์ „์œจ, ํ”ผ๋กœ ์ˆ˜๋ช…, ๊ตฐ ๊ทœ๊ฒฉ ๋ถ€ํ•ฉ ์ ์ˆ˜\"), (\"์ •๋น„๊ธฐ์ˆ ๊ด€ ์Šน์ธ\", \"์Šน์ธยท๋ฐ˜๋ คยท๋ณด๋ฅ˜, ์‚ฌ์œ \"),\n", " (\"์ถœ๋ ฅยทํ›„์ฒ˜๋ฆฌ\", \"์ถœ๋ ฅ ์‹œ๊ฐ„, ์†Œ์žฌ๋Ÿ‰, ํ›„์ฒ˜๋ฆฌ ํ•ญ๋ชฉ\"), (\"์‹ค ์žฅ์ฐฉยท์‹œํ—˜\", \"์žฅ์ฐฉ ๋ถ€๋Œ€, ์‹œํ—˜ ๊ฒฐ๊ณผ, ํŒŒ๊ดด์‹œํ—˜ ๊ฐ•๋„\"), (\"์‚ฌํ›„๊ฒ€ํ† (AAR)\", \"์˜ˆ์ธก ์˜ค์ฐจ, ํ๊ธฐ์œจ ๋ณ€ํ™”, ๋ณด์ • ์ด๋ ฅ\")]\n", "\n", "# 2) ์Šคํ‚ค๋งˆ(๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ): ๊ทธ๋ฃน / ํ•„๋“œ / ์ž๋ฃŒํ˜• / ๊ทœ์น™ / ์„ค๋ช…\n", "S = {\n", " \"part\": {\n", " \"part_id\": (str, {\"pattern\": r\"^P\\d{3}$\"}, \"๋ถ€ํ’ˆ ID(๊ฐ€์ƒ)\"),\n", " \"item_code\": (str, {\"pattern\": r\"^VX-\\d{4}-\\d{4}$\"}, \"๋ถ€ํ’ˆ ์‹๋ณ„์ฝ”๋“œ(๊ตญ๋ฐฉ ํ‘œ์ค€ ๊ตฐ์ˆ˜ ์ฝ”๋“œ ํ˜•์‹์„ ๋ชจ์‚ฌํ•œ ๊ฐ€์ƒ ์ฝ”๋“œ)\"),\n", " \"fsc_group\": (str, {\"pattern\": r\"^\\d{4}$\"}, \"๋ณด๊ธ‰ ๋ถ„๋ฅ˜๊ตฐ(๊ฐ€์ƒ 4์ž๋ฆฌ)\"),\n", " \"category\": (str, {\"enum\": [\"bracket\", \"housing\", \"clip\", \"gear\", \"duct\", \"handle\"]}, \"๋ถ€ํ’ˆ๊ตฐ\"),\n", " \"crit\": (int, {\"enum\": [1, 2, 3]}, \"์ž‘์ „ ์ค‘์š”๋„(1 ์ผ๋ฐ˜, 2 ์ค‘์š”, 3 ํ•ต์‹ฌ)\"),\n", " \"scrap_rate\": (float, {\"min\": 0, \"max\": 1}, \"์›” ํ๊ธฐ์œจ(ํ๊ธฐ์ˆ˜๋Ÿ‰/์†Œ์š”์ˆ˜๋Ÿ‰) - ์˜ˆ์ธก๋Œ€์ƒ 1\"),\n", " \"lead_days\": (float, {\"min\": 0, \"max\": 400}, \"๊ธฐ์กด ์กฐ๋‹ฌ ์†Œ์š”์ผ\"),\n", " },\n", " \"cad\": {\n", " \"cad_format\": (str, {\"enum\": [\"stl\", \"step\", \"obj\"]}, \"CAD ํŒŒ์ผ ํ˜•์‹\"),\n", " \"unit\": (str, {\"enum\": [\"mm\"]}, \"์น˜์ˆ˜ ๋‹จ์œ„(mm๋กœ ํ†ต์ผ)\"),\n", " \"volume_cm3\": (float, {\"min\": 0.5, \"max\": 1000}, \"๋ถ€ํ”ผ\"),\n", " \"max_dim_mm\": (float, {\"min\": 5, \"max\": 400}, \"์ตœ๋Œ€ ์น˜์ˆ˜\"),\n", " \"min_wall_mm\": (float, {\"min\": 0.2, \"max\": 20}, \"์ตœ์†Œ ๋ฒฝ๋‘๊ป˜\"),\n", " \"overhang\": (float, {\"min\": 0, \"max\": 1}, \"45๋„ ์ดˆ๊ณผ ๋Œ์ถœ๋ฉด ๋น„์œจ(์ง€์ง€๋Œ€ ํ•„์š” ์ •๋„)\"),\n", " \"n_triangles\": (int, {\"min\": 100, \"max\": 5000000}, \"๋ฉ”์‰ฌ ์‚ผ๊ฐํ˜• ์ˆ˜\"),\n", " \"thread\": (bool, {}, \"๋‚˜์‚ฌ๋ถ€ ํฌํ•จ ์—ฌ๋ถ€\"),\n", " },\n", " \"material\": {\n", " \"material\": (str, {\"enum\": [\"PA12\", \"PETG\", \"CF-PA\", \"AlSi10Mg\", \"SS316L\", \"Ti64\"]}, \"์†Œ์žฌ ์ฝ”๋“œ\"),\n", " \"process\": (str, {\"enum\": [\"polymer\", \"metal\"]}, \"๊ณต์ • ๊ตฌ๋ถ„(ํด๋ฆฌ๋จธ ์••์ถœยท๋ถ„๋ง / ๊ธˆ์† ๋ถ„๋ง ์šฉ์œต)\"),\n", " \"base_strength\": (float, {\"min\": 10, \"max\": 1500}, \"์†Œ์žฌ ๊ธฐ์ค€ ์ธ์žฅ๊ฐ•๋„(MPa, ๊ฐ€์ƒ)\"),\n", " \"cert\": (bool, {}, \"์†Œ์žฌ ์ธ์ฆ์„œ ๋ณด์œ  ์—ฌ๋ถ€\"),\n", " },\n", " \"use\": {\n", " \"load_N\": (float, {\"min\": 1, \"max\": 30000}, \"์‚ฌ์šฉ ํ•˜์ค‘(N)\"),\n", " \"temp_C\": (float, {\"min\": -40, \"max\": 150}, \"์‚ฌ์šฉ ์˜จ๋„\"),\n", " \"orient_deg\": (float, {\"min\": 0, \"max\": 90}, \"์ถœ๋ ฅ ๋ฐฉํ–ฅ ๊ฐ๋„(์ ์ธต ๋ฐฉํ–ฅ ๋Œ€๋น„)\"),\n", " },\n", " \"result\": {\n", " \"printable\": (bool, {}, \"์‹œํ—˜ ์ถœ๋ ฅ ์„ฑ๊ณต ์—ฌ๋ถ€ - ์˜ˆ์ธก๋Œ€์ƒ 2\"),\n", " \"strength_mpa\": (float, {\"min\": 0, \"max\": 1500}, \"์‹ค์ธก ์ธ์žฅ๊ฐ•๋„ - ์˜ˆ์ธก๋Œ€์ƒ 3\"),\n", " \"log_cycles\": (float, {\"min\": 0, \"max\": 9}, \"ํ”ผ๋กœ ์ˆ˜๋ช… log10(ํšŒ)\"),\n", " \"conform\": (float, {\"min\": 0, \"max\": 100}, \"๊ตฐ ๊ทœ๊ฒฉ ๋ถ€ํ•ฉ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ - ์˜ˆ์ธก๋Œ€์ƒ 4\"),\n", " },\n", "}\n", "\n", "\n", "def check(name, v, typ, rule):\n", " if not isinstance(v, typ) or (typ in (int, float) and isinstance(v, bool)):\n", " return [f\"{name}: ์ž๋ฃŒํ˜• ์˜ค๋ฅ˜({type(v).__name__})\"]\n", " e = []\n", " if \"enum\" in rule and v not in rule[\"enum\"]:\n", " e.append(f\"{name}: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๊ฐ’ '{v}'\")\n", " if \"min\" in rule and v < rule[\"min\"]:\n", " e.append(f\"{name}: ์ตœ์†Œ๊ฐ’ {rule['min']} ๋ฏธ๋งŒ\")\n", " if \"max\" in rule and v > rule[\"max\"]:\n", " e.append(f\"{name}: ์ตœ๋Œ€๊ฐ’ {rule['max']} ์ดˆ๊ณผ\")\n", " if \"pattern\" in rule and not re.match(rule[\"pattern\"], v):\n", " e.append(f\"{name}: ํ˜•์‹ ๋ถˆ์ผ์น˜ '{v}'\")\n", " return e\n", "\n", "\n", "def validate(rec):\n", " errs = []\n", " for g, fields in S.items():\n", " for k, (typ, rule, _) in fields.items():\n", " if k not in rec.get(g, {}):\n", " errs.append(f\"{g}.{k}: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\")\n", " else:\n", " errs += check(f\"{g}.{k}\", rec[g][k], typ, rule)\n", " return errs\n", "\n", "\n", "# 3) [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์ƒ˜ํ”Œ 20๊ฑด\n", "rng = np.random.default_rng(1)\n", "MATS = {\"PA12\": (\"polymer\", 45), \"PETG\": (\"polymer\", 50), \"CF-PA\": (\"polymer\", 70), \"AlSi10Mg\": (\"metal\", 350), \"SS316L\": (\"metal\", 520), \"Ti64\": (\"metal\", 900)}\n", "recs = []\n", "for i in range(20):\n", " m = str(rng.choice(list(MATS)))\n", " recs.append({\n", " \"part\": {\"part_id\": f\"P{i:03d}\", \"item_code\": f\"VX-{rng.integers(1000, 9999)}-{rng.integers(1000, 9999)}\", \"fsc_group\": f\"{rng.integers(1000, 9999)}\",\n", " \"category\": str(rng.choice([\"bracket\", \"housing\", \"clip\", \"gear\", \"duct\", \"handle\"])), \"crit\": int(rng.integers(1, 4)),\n", " \"scrap_rate\": float(round(rng.uniform(0.02, 0.3), 3)), \"lead_days\": float(round(rng.uniform(10, 120), 0))},\n", " \"cad\": {\"cad_format\": str(rng.choice([\"stl\", \"step\", \"obj\"])), \"unit\": \"mm\", \"volume_cm3\": float(round(rng.uniform(5, 200), 1)),\n", " \"max_dim_mm\": float(round(rng.uniform(20, 180), 1)), \"min_wall_mm\": float(round(rng.uniform(0.8, 6), 1)),\n", " \"overhang\": float(round(rng.uniform(0, 0.5), 2)), \"n_triangles\": int(rng.integers(2000, 400000)), \"thread\": bool(rng.random() < 0.25)},\n", " \"material\": {\"material\": m, \"process\": MATS[m][0], \"base_strength\": float(MATS[m][1]), \"cert\": bool(rng.random() < 0.5)},\n", " \"use\": {\"load_N\": float(round(rng.uniform(50, 3000), 0)), \"temp_C\": float(round(rng.uniform(-10, 80), 0)), \"orient_deg\": float(round(rng.uniform(0, 90), 0))},\n", " \"result\": {\"printable\": bool(rng.random() < 0.8), \"strength_mpa\": float(round(MATS[m][1] * rng.uniform(0.6, 1.0), 1)),\n", " \"log_cycles\": float(round(rng.uniform(3, 7.5), 2)), \"conform\": float(round(rng.uniform(30, 98), 1))}})\n", "\n", "ok = sum(not validate(r) for r in recs)\n", "print(f\"์ •์ƒ ์ƒ˜ํ”Œ {len(recs)}๊ฑด ์ค‘ ๊ฒ€์ฆ ํ†ต๊ณผ {ok}๊ฑด, ์˜ค๋ฅ˜ {len(recs) - ok}๊ฑด\")\n", "\n", "# 4) ์˜ค๋ฅ˜ ์ฃผ์ž… ์‹œํ—˜\n", "bad = json.loads(json.dumps(recs[0]))\n", "bad[\"cad\"][\"min_wall_mm\"] = 0.0 # ํ—ˆ์šฉ ๋ฒ”์œ„ ๋ฏธ๋งŒ(0 ์ดํ•˜ ๋ฒฝ๋‘๊ป˜)\n", "bad[\"cad\"][\"unit\"] = \"inch\" # ๋‹จ์œ„ ํ˜ผ์žฌ\n", "bad[\"part\"][\"item_code\"] = \"1234567890123\" # ์ฝ”๋“œ ํ˜•์‹ ์˜ค๋ฅ˜\n", "del bad[\"use\"][\"load_N\"] # ์‚ฌ์šฉ ํ•˜์ค‘ ๋ˆ„๋ฝ\n", "bad[\"result\"][\"conform\"] = \"๋†’์Œ\" # ์ž๋ฃŒํ˜• ์˜ค๋ฅ˜(๋ฌธ์ž์—ด)\n", "print(\"\\n[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ]\")\n", "for e in validate(bad):\n", " print(\" -\", e)\n", "\n", "# 5) ํ”„๋กœ์„ธ์Šค ๋งต๊ณผ ๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ\n", "print(\"\\n[์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต]\")\n", "print(\" -> \".join(f\"{i}.{p}\" for i, (p, _) in enumerate(PROCESS, 1)))\n", "for p, d in PROCESS:\n", " print(f\" {p:10s}: {d}\")\n", "rows = [(g, k, t.__name__, str(r) if r else \"-\", d) for g, f in S.items() for k, (t, r, d) in f.items()]\n", "dd = pd.DataFrame(rows, columns=[\"๊ทธ๋ฃน\", \"ํ•„๋“œ\", \"์ž๋ฃŒํ˜•\", \"๊ทœ์น™\", \"์„ค๋ช…\"])\n", "print(f\"\\n[๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ] ํ”ผ์ฒ˜ {len(dd)}๊ฐœ (๋ชฉํ‘œ 20๊ฐœ ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(dd) >= 20 else '๋ฏธ๋‹ฌ'})\")\n", "print(dd[[\"๊ทธ๋ฃน\", \"ํ•„๋“œ\", \"์ž๋ฃŒํ˜•\", \"์„ค๋ช…\"]].to_string(index=False))\n", "print(\"\\n์˜ˆ์ธก ๋Œ€์ƒ: ์›” ํ๊ธฐ์œจ, ์‹œํ—˜ ์ถœ๋ ฅ ์„ฑ๊ณต ์—ฌ๋ถ€, ์ธ์žฅ๊ฐ•๋„, ๊ตฐ ๊ทœ๊ฒฉ ๋ถ€ํ•ฉ ์ ์ˆ˜. ์ตœ์ข… ํ™œ์šฉ: ํ๊ธฐ์œจ์ด ๋†’๊ณ  ๋Œ€์ฒด ์ œ์ž‘์ด ๊ฐ€๋Šฅํ•œ ๋ถ€ํ’ˆ์˜ ์šฐ์„  ์„ ์ •\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 01 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "์ •์ƒ ์ƒ˜ํ”Œ 20๊ฑด ์ค‘ ๊ฒ€์ฆ ํ†ต๊ณผ 20๊ฑด, ์˜ค๋ฅ˜ 0๊ฑด\n", "\n", "[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ]\n", " - part.item_code: ํ˜•์‹ ๋ถˆ์ผ์น˜ '1234567890123'\n", " - cad.unit: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๊ฐ’ 'inch'\n", " - cad.min_wall_mm: ์ตœ์†Œ๊ฐ’ 0.2 ๋ฏธ๋งŒ\n", " - use.load_N: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\n", " - result.conform: ์ž๋ฃŒํ˜• ์˜ค๋ฅ˜(str)\n", "\n", "[์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต]\n", "1.ํ๊ธฐ ๋ถ€ํ’ˆ ์‹๋ณ„ -> 2.CAD ํ™•๋ณด -> 3.์†Œ์žฌ ์„ ์ • -> 4.์ œ์ž‘ ๊ฐ€๋Šฅ์„ฑ ํŒ๋ณ„ -> 5.์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ -> 6.์ •๋น„๊ธฐ์ˆ ๊ด€ ์Šน์ธ -> 7.์ถœ๋ ฅยทํ›„์ฒ˜๋ฆฌ -> 8.์‹ค ์žฅ์ฐฉยท์‹œํ—˜ -> 9.์‚ฌํ›„๊ฒ€ํ† (AAR)\n", " ํ๊ธฐ ๋ถ€ํ’ˆ ์‹๋ณ„ : ๋ถ€ํ’ˆ ์ฝ”๋“œ, ์›” ํ๊ธฐ์ˆ˜๋Ÿ‰, ํ๊ธฐ์œจ, ์กฐ๋‹ฌ ์†Œ์š”์ผ\n", " CAD ํ™•๋ณด : CAD ํŒŒ์ผ ํ˜•์‹, ์‚ผ๊ฐํ˜• ์ˆ˜, ๋‹จ์œ„, ์น˜์ˆ˜\n", " ์†Œ์žฌ ์„ ์ • : ์†Œ์žฌ ์ฝ”๋“œ, ๊ณต์ • ๊ตฌ๋ถ„, ์ธ์žฅ๊ฐ•๋„ ๊ธฐ์ค€๊ฐ’, ์ธ์ฆ์„œ ์—ฌ๋ถ€\n", " ์ œ์ž‘ ๊ฐ€๋Šฅ์„ฑ ํŒ๋ณ„ : ๋ฒฝ๋‘๊ป˜, ๋Œ์ถœ ๋น„์œจ, ์ตœ๋Œ€ ์น˜์ˆ˜, ๋‚˜์‚ฌ๋ถ€\n", " ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ : ์‚ฌ์šฉ ํ•˜์ค‘, ์•ˆ์ „์œจ, ํ”ผ๋กœ ์ˆ˜๋ช…, ๊ตฐ ๊ทœ๊ฒฉ ๋ถ€ํ•ฉ ์ ์ˆ˜\n", " ์ •๋น„๊ธฐ์ˆ ๊ด€ ์Šน์ธ : ์Šน์ธยท๋ฐ˜๋ คยท๋ณด๋ฅ˜, ์‚ฌ์œ \n", " ์ถœ๋ ฅยทํ›„์ฒ˜๋ฆฌ : ์ถœ๋ ฅ ์‹œ๊ฐ„, ์†Œ์žฌ๋Ÿ‰, ํ›„์ฒ˜๋ฆฌ ํ•ญ๋ชฉ\n", " ์‹ค ์žฅ์ฐฉยท์‹œํ—˜ : ์žฅ์ฐฉ ๋ถ€๋Œ€, ์‹œํ—˜ ๊ฒฐ๊ณผ, ํŒŒ๊ดด์‹œํ—˜ ๊ฐ•๋„\n", " ์‚ฌํ›„๊ฒ€ํ† (AAR) : ์˜ˆ์ธก ์˜ค์ฐจ, ํ๊ธฐ์œจ ๋ณ€ํ™”, ๋ณด์ • ์ด๋ ฅ\n", "\n", "[๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ] ํ”ผ์ฒ˜ 26๊ฐœ (๋ชฉํ‘œ 20๊ฐœ ์ด์ƒ: ๋‹ฌ์„ฑ)\n", " ๊ทธ๋ฃน ํ•„๋“œ ์ž๋ฃŒํ˜• ์„ค๋ช…\n", " part part_id str ๋ถ€ํ’ˆ ID(๊ฐ€์ƒ)\n", " part item_code str ๋ถ€ํ’ˆ ์‹๋ณ„์ฝ”๋“œ(๊ตญ๋ฐฉ ํ‘œ์ค€ ๊ตฐ์ˆ˜ ์ฝ”๋“œ ํ˜•์‹์„ ๋ชจ์‚ฌํ•œ ๊ฐ€์ƒ ์ฝ”๋“œ)\n", " part fsc_group str ๋ณด๊ธ‰ ๋ถ„๋ฅ˜๊ตฐ(๊ฐ€์ƒ 4์ž๋ฆฌ)\n", " part category str ๋ถ€ํ’ˆ๊ตฐ\n", " part crit int ์ž‘์ „ ์ค‘์š”๋„(1 ์ผ๋ฐ˜, 2 ์ค‘์š”, 3 ํ•ต์‹ฌ)\n", " part scrap_rate float ์›” ํ๊ธฐ์œจ(ํ๊ธฐ์ˆ˜๋Ÿ‰/์†Œ์š”์ˆ˜๋Ÿ‰) - ์˜ˆ์ธก๋Œ€์ƒ 1\n", " part lead_days float ๊ธฐ์กด ์กฐ๋‹ฌ ์†Œ์š”์ผ\n", " cad cad_format str CAD ํŒŒ์ผ ํ˜•์‹\n", " cad unit str ์น˜์ˆ˜ ๋‹จ์œ„(mm๋กœ ํ†ต์ผ)\n", " cad volume_cm3 float ๋ถ€ํ”ผ\n", " cad max_dim_mm float ์ตœ๋Œ€ ์น˜์ˆ˜\n", " cad min_wall_mm float ์ตœ์†Œ ๋ฒฝ๋‘๊ป˜\n", " cad overhang float 45๋„ ์ดˆ๊ณผ ๋Œ์ถœ๋ฉด ๋น„์œจ(์ง€์ง€๋Œ€ ํ•„์š” ์ •๋„)\n", " cad n_triangles int ๋ฉ”์‰ฌ ์‚ผ๊ฐํ˜• ์ˆ˜\n", " cad thread bool ๋‚˜์‚ฌ๋ถ€ ํฌํ•จ ์—ฌ๋ถ€\n", "material material str ์†Œ์žฌ ์ฝ”๋“œ\n", "material process str ๊ณต์ • ๊ตฌ๋ถ„(ํด๋ฆฌ๋จธ ์••์ถœยท๋ถ„๋ง / ๊ธˆ์† ๋ถ„๋ง ์šฉ์œต)\n", "... (์ดํ•˜ ์ƒ๋žต)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 02. CAD ํŒŒ์ผ ๋ฉ”์‰ฌ ์ •์ œ, ์Šฌ๋ผ์ด์‹ฑ ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ ๋ฐ ์ œ์ž‘ ๊ทœ๊ฒฉ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "CAD ๋ฉ”์‰ฌ ์ •์ œ ๋ฐ ์Šฌ๋ผ์ด์‹ฑ ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **CAD ๋ฉ”์‰ฌ ์ •์ œ์™€ ์Šฌ๋ผ์ด์‹ฑ ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ, ๋ถ€ํ’ˆ ์ด๋ ฅ ๋ ˆ์ฝ”๋“œ ์ •์ œ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ์ œ์ž‘ ๊ทœ๊ฒฉ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์‚ผ๊ฐํ˜• ๋ฉ”์‰ฌ์˜ NaN ์ •์ ยทํ‡ดํ™” ์‚ผ๊ฐํ˜•ยท์ค‘๋ณต ์‚ผ๊ฐํ˜•์„ ์ œ๊ฑฐํ•˜๊ณ  ๋’ค์ง‘ํžŒ ๋ฒ•์„ ์„ ๋ฐ”๋กœ์žก์•„ ๋ถ€ํ”ผ๊ฐ€ ๊ธฐ์ค€๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์œ ํšจ์„ฑ์„ ์ ๊ฒ€ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/etl/mnt06_data_cleansing.py ๋ฐ ์ •์ œ ํ’ˆ์งˆ ๋ณด๊ณ ์„œ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ํŠน์ˆ˜ ์˜ˆ์™ธ(๋น„๊ณ„ํš ์ •๋น„, ๊ธด๊ธ‰ ๋ถˆ์ถœ ๋“ฑ) ๋ฐ์ดํ„ฐ ๋ณด์กด์˜ ํƒ€๋‹น์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๋ฐ์ดํ„ฐ ์ •์ œ ๊ทœ์น™ 10์ข… ์ ์šฉ, ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ ํ†ต๊ณผ์œจ 100%\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ETL ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌํ˜„ ๋ฐ ์ •์ œ๋œ ๋งˆ์Šคํ„ฐ ๋ฐ์ดํ„ฐ์…‹ ๊ตฌ์ถ•\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ์ „ํ›„ ๋ฐ์ดํ„ฐ ํ†ต๊ณ„๋Ÿ‰(๊ธฐ์ˆ ํ†ต๊ณ„) ๋น„๊ต ๋ฐ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๋ถ€ํ’ˆ ์ด๋ ฅ ๋ ˆ์ฝ”๋“œ(ํ‘œ๊ธฐยท๋‹จ์œ„ยท์ค‘๋ณตยท๊ฒฐ์ธก ์˜ค๋ฅ˜), CAD ์‚ผ๊ฐํ˜• ๋ฉ”์‰ฌ(NaN ์ •์ ยทํ‡ดํ™”ยท์ค‘๋ณตยท๋’ค์ง‘ํžŒ ๋ฒ•์„ ), ์Šฌ๋ผ์ด์‹ฑ(G-code๋ฅ˜) ์ถœ๋ ฅ ํŒŒ์ผ์˜ ๋ถˆ๋Ÿ‰ ๋ผ์ธ์ด ์„ž์ธ ๊ฐ€์ƒ ์›์ฒœ ์ž๋ฃŒ์— ์ •์ œ ๊ทœ์น™ 12์ข…์„ ์ ์šฉํ•˜๊ณ , ๊ธด๊ธ‰ ์ œ์ž‘ ์˜ˆ์™ธ ๋ ˆ์ฝ”๋“œ๋Š” ๋ณด์กดํ•˜๋ฉด์„œ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ ํ†ต๊ณผ์œจ๊ณผ ์ •์ œ ์ „ํ›„ ํ†ต๊ณ„๋ฅผ ํ™•์ธํ•œ๋‹ค.\"\"\"\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.impute import KNNImputer\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด ๋ถ€ํ’ˆ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€ํ’ˆยท์žฌ๊ณ ยท๊ทœ๊ฒฉ ๊ฐ’ ์•„๋‹˜)\n", "# ํ๊ธฐ์œจ์ด ๋†’์€ ์ˆ˜๋ฆฌ๋ถ€์† ์ค‘ 3D ํ”„๋ฆฐํŒ…์œผ๋กœ ๋Œ€์ฒด ์ œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์ƒ ๋ถ€ํ’ˆ ์ž๋ฃŒ๋ฅผ ๋งŒ๋“ ๋‹ค.\n", "# ์†Œ์žฌ ๋ฌผ์„ฑยท์น˜์ˆ˜ยทํ•˜์ค‘์€ ๊ต์œก์šฉ์œผ๋กœ ๋‹จ์ˆœํ™”ํ•œ ๊ฐ’์ด๋ฉฐ ์‹ค์ œ ์‹œํ—˜ ์„ฑ์ ์„œ๋‚˜ ๊ตฐ ๊ทœ๊ฒฉ ๊ฐ’๊ณผ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "MAT = { # ์†Œ์žฌ: (๊ณต์ •, ๊ธฐ์ค€ ์ธ์žฅ๊ฐ•๋„ MPa, ๋ฐ€๋„ g/cm3, ์†Œ์žฌ ๋‹จ๊ฐ€ ๋งŒ์›/kg, ์ถœ๋ ฅ๋น„ ๋งŒ์›/h, ์ตœ์†Œ ๋ฒฝ๋‘๊ป˜ mm, ์ถœ๋ ฅ ์†๋„ cm3/h)\n", " \"PA12\": (\"polymer\", 45, 1.0, 8, 3, 0.8, 18), \"PETG\": (\"polymer\", 50, 1.27, 5, 2, 0.8, 15),\n", " \"CF-PA\": (\"polymer\", 70, 1.2, 14, 3, 1.0, 12), \"AlSi10Mg\": (\"metal\", 350, 2.67, 30, 25, 0.5, 4),\n", " \"SS316L\": (\"metal\", 520, 7.9, 28, 25, 0.6, 3.5), \"Ti64\": (\"metal\", 900, 4.4, 90, 30, 0.6, 3)}\n", "CATS = [\"bracket\", \"housing\", \"clip\", \"gear\", \"duct\", \"handle\"]\n", "CAT_SCRAP = {\"bracket\": 0.04, \"housing\": 0.05, \"clip\": 0.12, \"gear\": 0.09, \"duct\": 0.08, \"handle\": 0.10} # ๋ถ€ํ’ˆ๊ตฐ๋ณ„ ๊ธฐ๋ณธ ์›” ํ๊ธฐ์œจ\n", "BUILD_MM = {\"polymer\": 300, \"metal\": 200} # ๊ฐ€์ƒ ์ถœ๋ ฅ ๊ฐ€๋Šฅ ์ตœ๋Œ€ ์น˜์ˆ˜\n", "\n", "\n", "def make_parts(n=300, seed=0):\n", " \"\"\"๊ฐ€์ƒ ๋ถ€ํ’ˆ n์ข…: ํ˜•์ƒ(๋ถ€ํ”ผยท๋ฒฝ๋‘๊ป˜ยท๋Œ์ถœ ๋น„์œจ), ์†Œ์žฌ, ์‚ฌ์šฉ ํ•˜์ค‘, ํ๊ธฐ์œจ๊ณผ ์กฐ๋‹ฌ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " mat = rng.choice(list(MAT), n, p=[0.2, 0.15, 0.1, 0.2, 0.2, 0.15])\n", " proc = np.array([MAT[m][0] for m in mat])\n", " vol = np.clip(rng.lognormal(np.log(30), 0.9, n), 2, 800)\n", " aspect = rng.lognormal(0.3, 0.4, n)\n", " max_dim = 10 * vol ** (1 / 3) * aspect ** (1 / 3) * rng.uniform(1.0, 1.6, n)\n", " wall = np.clip(rng.lognormal(np.log(2.0), 0.6, n), 0.3, 8)\n", " ovh = rng.beta(2, 5, n)\n", " is_poly = proc == \"polymer\"\n", " df = pd.DataFrame({\n", " \"part_id\": [f\"P{i:03d}\" for i in range(n)], \"cat\": rng.choice(CATS, n, p=[0.2, 0.15, 0.2, 0.1, 0.15, 0.2]), \"material\": mat, \"process\": proc,\n", " \"volume_cm3\": vol, \"max_dim_mm\": max_dim, \"area_cm2\": 6 * vol ** (2 / 3) * rng.uniform(1.0, 2.2, n), \"min_wall_mm\": wall, \"overhang\": ovh,\n", " \"n_holes\": rng.poisson(2.0, n), \"thread\": rng.random(n) < 0.25, \"orient_deg\": rng.uniform(0, 90, n),\n", " \"layer_mm\": np.where(is_poly, rng.choice([0.1, 0.2, 0.3], n), rng.choice([0.03, 0.05], n)),\n", " \"infill\": np.where(is_poly, rng.choice([40, 60, 80, 100], n, p=[0.15, 0.3, 0.3, 0.25]), 100),\n", " \"load_N\": np.clip(rng.lognormal(np.where(is_poly, np.log(230), np.log(2300)), 0.7, n), 10, 20000), \"temp_C\": rng.uniform(-20, 90, n),\n", " \"crit\": rng.choice([1, 2, 3], n, p=[0.35, 0.4, 0.25]), \"cert\": rng.choice([0, 1], n, p=[0.45, 0.55]), # ์†Œ์žฌ ์ธ์ฆ์„œ ๋ณด์œ \n", " \"import_part\": rng.random(n) < 0.4})\n", " df[\"scrap_rate\"] = np.clip(df[\"cat\"].map(CAT_SCRAP) * rng.lognormal(0, 0.35, n) * (1 + 0.5 * (df[\"load_N\"] > 800)) * (1 + 0.3 * (df[\"temp_C\"] > 60)), 0.005, 0.5)\n", " df[\"lead_days\"] = np.clip(rng.lognormal(np.log(25), 0.5, n) * np.where(df[\"import_part\"], 2.2, 1.0), 5, 300)\n", " # ---- ์ˆจ์€ ์ง„์‹ค๊ฐ’(์‹ค์ œ ์‹œํ—˜ ๊ฒฐ๊ณผ์— ํ•ด๋‹น): ๋ชจ๋ธ์€ ์œ„ ์ž…๋ ฅ์œผ๋กœ ์ด๊ฒƒ์„ ์˜ˆ์ธกํ•œ๋‹ค ----\n", " eff = np.where(is_poly, 0.55 + 0.45 * df[\"infill\"] / 100, 1 - 0.3 * ovh * 0.2)\n", " aniso = np.where(is_poly, 1 - 0.3 * np.sin(np.radians(df[\"orient_deg\"])) ** 2, 1 - 0.08 * np.sin(np.radians(df[\"orient_deg\"])))\n", " heat = np.where(is_poly, 1 - 0.35 * np.clip((df[\"temp_C\"] - 40) / 80, 0, 1), 0.98)\n", " base = np.array([MAT[m][1] for m in mat])\n", " df[\"strength_mpa\"] = base * eff * aniso * heat * rng.lognormal(0, 0.06, n)\n", " cross = df[\"min_wall_mm\"] * np.sqrt(df[\"max_dim_mm\"]) * 1.0 # ํ•˜์ค‘ ์ง€์ง€ ๋‹จ๋ฉด์  mm2(๋‹จ์ˆœํ™”)\n", " df[\"stress_mpa\"] = df[\"load_N\"] / cross\n", " df[\"safety\"] = df[\"strength_mpa\"] / df[\"stress_mpa\"]\n", " ratio = np.clip(df[\"stress_mpa\"] / df[\"strength_mpa\"], 0, 1.5)\n", " df[\"log_cycles\"] = np.clip(7.5 - 6.0 * ratio + rng.normal(0, 0.25, n), 2, 7.5) # ํ”ผ๋กœ ์ˆ˜๋ช… log10(ํšŒ)\n", " support = 1 + 0.5 * ovh\n", " df[\"build_h\"] = vol * support / np.array([MAT[m][6] for m in mat]) + 0.5\n", " mass_kg = vol * np.array([MAT[m][2] for m in mat]) * support / 1000\n", " df[\"print_cost\"] = mass_kg * np.array([MAT[m][3] for m in mat]) + df[\"build_h\"] * np.array([MAT[m][4] for m in mat]) + 3\n", " df[\"buy_cost\"] = df[\"print_cost\"] * rng.lognormal(0.25, 0.45, n) + 5 # ๊ตฌ๋งค ๋‹จ๊ฐ€(๋งŒ์›, ๊ฐ€์ƒ)\n", " wall_ok = df[\"min_wall_mm\"] >= np.array([MAT[m][5] for m in mat]) * rng.uniform(0.95, 1.25, n)\n", " dim_ok = df[\"max_dim_mm\"] <= np.where(is_poly, BUILD_MM[\"polymer\"], BUILD_MM[\"metal\"])\n", " warp = (~is_poly) & (df[\"max_dim_mm\"] > 140) & (df[\"overhang\"] > 0.3) # ๊ธˆ์† ๋Œ€ํ˜• ์˜ค๋ฒ„ํ–‰ ๋ณ€ํ˜•\n", " thr_bad = df[\"thread\"] & (df[\"min_wall_mm\"] < 1.5) & is_poly # ์–‡์€ ๋‚˜์‚ฌ๋ถ€ ํŒŒ์†\n", " df[\"printable\"] = wall_ok & dim_ok & (ovh < 0.5) & ~warp & ~thr_bad\n", " risk = np.clip(1 - df[\"safety\"] / 3, 0, 1)\n", " df[\"conform\"] = np.clip(100 * (0.45 * np.minimum(df[\"safety\"] / 3, 1) + 0.25 * np.clip((df[\"log_cycles\"] - 3) / 4.5, 0, 1)\n", " + 0.15 * (1 - np.clip(ovh * 1.2, 0, 1)) + 0.15 * df[\"cert\"]) - 8 * risk * df[\"crit\"] / 3 + rng.normal(0, 2, n), 0, 100)\n", " df[\"demand_month\"] = rng.poisson(np.where(df[\"cat\"].isin([\"clip\", \"handle\"]), 40, 15)) + 1 # ์›” ์†Œ์š”์ˆ˜๋Ÿ‰(๊ฐ€์ƒ)\n", " df[\"scrap_cost\"] = df[\"scrap_rate\"] * df[\"demand_month\"] * df[\"buy_cost\"] # ์›” ํ๊ธฐ ์†์‹ค์•ก(๋งŒ์›, ๊ฐ€์ƒ)\n", " return df\n", "\n", "rng = np.random.default_rng(2)\n", "applied = []\n", "\n", "\n", "def rule(name, n):\n", " applied.append((name, int(n)))\n", "\n", "\n", "# ---------------- 1) ๋ถ€ํ’ˆ ์ด๋ ฅ ๋ ˆ์ฝ”๋“œ ----------------\n", "P = make_parts(200, seed=5)\n", "raw = P[[\"part_id\", \"material\", \"max_dim_mm\", \"min_wall_mm\", \"volume_cm3\", \"scrap_rate\", \"load_N\"]].copy()\n", "raw[\"unit\"] = \"mm\"\n", "raw[\"test_date\"] = [f\"2026-{rng.integers(1, 10):02d}-{rng.integers(1, 28):02d}\" for _ in range(len(raw))]\n", "raw[\"emergency\"] = False\n", "idx = rng.choice(len(raw), 80, replace=False)\n", "a, b, c, d, e, f, g = np.split(idx[:77], 7)\n", "raw.loc[a, \"material\"] = rng.choice([\"pa12\", \"PA-12\", \"ss316l\", \"SS 316L\", \"ti64\"], len(a)) # ์†Œ์žฌ ํ‘œ๊ธฐ ๋ถˆ์ผ์น˜\n", "raw.loc[b, [\"max_dim_mm\", \"min_wall_mm\"]] = raw.loc[b, [\"max_dim_mm\", \"min_wall_mm\"]].values / 25.4 # inch ๊ฐ’\n", "raw.loc[b, \"unit\"] = \"inch\"\n", "raw.loc[c, \"scrap_rate\"] = (raw.loc[c, \"scrap_rate\"] * 100).round(1) # % ๋‹จ์œ„ ํ˜ผ์žฌ\n", "raw.loc[d, \"test_date\"] = [x.replace(\"-\", \"/\") for x in raw.loc[d, \"test_date\"]] # ๋‚ ์งœ ํ˜•์‹ ํ˜ผ์žฌ\n", "raw.loc[e, \"min_wall_mm\"] = rng.choice([0.0, -1.2], len(e)) # ๋ถˆ๊ฐ€๋Šฅ ๊ฐ’\n", "raw.loc[f, [\"load_N\", \"volume_cm3\"]] = np.nan # ๊ฒฐ์ธก\n", "raw.loc[g, \"emergency\"] = True; raw.loc[g, \"load_N\"] = raw.loc[g, \"load_N\"] * 12 # ๊ธด๊ธ‰ ์ œ์ž‘ ์š”์ฒญ(์˜ˆ์™ธ: ๊ณผ๋Œ€ ํ•˜์ค‘ ๊ธฐ๋ก, ๋ณด์กด)\n", "raw = pd.concat([raw, raw.sample(10, random_state=1)], ignore_index=True) # ์ค‘๋ณต ๋ ˆ์ฝ”๋“œ\n", "print(f\"[๋ถ€ํ’ˆ ์ด๋ ฅ] ์›์ฒœ {len(raw)}๊ฑด (์ค‘๋ณต 10๊ฑด, ์˜ค๋ฅ˜ ์ฃผ์ž… {len(idx[:77])}๊ฑด ํฌํ•จ)\")\n", "df = raw.copy()\n", "rule(\"R1 ์ค‘๋ณต ๋ ˆ์ฝ”๋“œ ์ œ๊ฑฐ\", df.duplicated().sum()); df = df.drop_duplicates().reset_index(drop=True)\n", "mat = df[\"material\"].str.upper().str.replace(r\"[\\s-]\", \"\", regex=True).map({\"PA12\": \"PA12\", \"PETG\": \"PETG\", \"CFPA\": \"CF-PA\", \"ALSI10MG\": \"AlSi10Mg\", \"SS316L\": \"SS316L\", \"TI64\": \"Ti64\"})\n", "rule(\"R2 ์†Œ์žฌ ์ฝ”๋“œ ํ‘œ๊ธฐ ํ†ต์ผ\", (mat != df[\"material\"]).sum()); df[\"material\"] = mat\n", "inch = df[\"unit\"] == \"inch\"\n", "df.loc[inch, [\"max_dim_mm\", \"min_wall_mm\"]] *= 25.4; df[\"unit\"] = \"mm\"; rule(\"R3 CAD ์น˜์ˆ˜ ๋‹จ์œ„ ํ†ต์ผ(inch -> mm)\", inch.sum())\n", "pct = df[\"scrap_rate\"] > 1; df.loc[pct, \"scrap_rate\"] /= 100; rule(\"R4 ํ๊ธฐ์œจ ๋‹จ์œ„ ํ†ต์ผ(% -> ๋น„์œจ)\", pct.sum())\n", "t1 = pd.to_datetime(df[\"test_date\"], format=\"%Y-%m-%d\", errors=\"coerce\")\n", "t2 = pd.to_datetime(df[\"test_date\"], format=\"%Y/%m/%d\", errors=\"coerce\"); rule(\"R5 ์‹œํ—˜์ผ ํ˜•์‹ ํ†ต์ผ\", t1.isna().sum()); df[\"test_date\"] = t1.fillna(t2)\n", "bad = df[\"min_wall_mm\"] <= 0; df.loc[bad, \"min_wall_mm\"] = np.nan; rule(\"R6 ๋ถˆ๊ฐ€๋Šฅ ๋ฒฝ๋‘๊ป˜ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ\", bad.sum())\n", "miss = df[[\"load_N\", \"volume_cm3\", \"min_wall_mm\"]].isna().sum().sum()\n", "df[[\"load_N\", \"volume_cm3\", \"min_wall_mm\"]] = KNNImputer(n_neighbors=5).fit_transform(df[[\"load_N\", \"volume_cm3\", \"min_wall_mm\", \"max_dim_mm\"]])[:, :3]\n", "rule(\"R7 ๊ฒฐ์ธก KNN ๋ณด์ •\", miss)\n", "q1, q3 = df[\"load_N\"].quantile([0.25, 0.75]); out = df[\"load_N\"] > q3 + 3 * (q3 - q1)\n", "df[\"outlier_flag\"] = out & ~df[\"emergency\"]\n", "rule(\"R12 ๊ธด๊ธ‰ ์ œ์ž‘ ์˜ˆ์™ธ ๋ณด์กดยท์ผ๋ฐ˜ ์ด์ƒ๊ฐ’ ํ‘œ์‹œ\", (out & df[\"emergency\"]).sum())\n", "\n", "# ---------------- 2) CAD ๋ฉ”์‰ฌ ----------------\n", "def box_tris(a_, b_, c_, k=4):\n", " dims, tris = (a_, b_, c_), []\n", " for ax in range(3):\n", " u, v = [i for i in range(3) if i != ax]\n", " for sgn in (0, 1):\n", " for i in range(k):\n", " for j in range(k):\n", " def Pt(ii, jj):\n", " p = np.zeros(3); p[ax] = dims[ax] * sgn; p[u] = dims[u] * ii / k; p[v] = dims[v] * jj / k; return p\n", " p00, p10, p11, p01 = Pt(i, j), Pt(i + 1, j), Pt(i + 1, j + 1), Pt(i, j + 1)\n", " flip = (-1 if ax == 1 else 1) != (1 if sgn else -1)\n", " for t in ([p00, p10, p11], [p00, p11, p01]):\n", " tris.append(t[::-1] if flip else t)\n", " return np.array(tris)\n", "\n", "\n", "def volume(T):\n", " return float(np.einsum(\"ij,ij->i\", T[:, 0], np.cross(T[:, 1], T[:, 2])).sum() / 6)\n", "\n", "\n", "T0 = box_tris(40.0, 30.0, 20.0) # ์ •์ƒ ๋ฉ”์‰ฌ(๊ฐ€์ƒ ๋ธŒ๋ž˜ํ‚ท ๋ธ”๋žญํฌ, ๋ถ€ํ”ผ 24 cm3)\n", "nan_extra = T0[rng.choice(len(T0), 3, replace=False)].copy(); nan_extra[:, 0] = np.nan # NaN ์ •์ ์„ ๊ฐ€์ง„ ๋ถˆ๋Ÿ‰ ์‚ผ๊ฐํ˜•\n", "deg_extra = T0[rng.choice(len(T0), 8, replace=False)].copy(); deg_extra[:, 1] = deg_extra[:, 0] # ํ‡ดํ™”(๋ฉด์  0) ์‚ผ๊ฐํ˜•\n", "dup_extra = T0[rng.choice(len(T0), 6, replace=False)].copy() # ์ค‘๋ณต ์‚ผ๊ฐํ˜•\n", "m = np.concatenate([T0, nan_extra, deg_extra, dup_extra])\n", "fl = rng.choice(len(T0), 10, replace=False); m[fl] = m[fl][:, ::-1] # ๋ฒ•์„  ๋’ค์ง‘ํž˜(์›๋ณธ ์ค‘ 10๊ฐœ)\n", "vol_raw = volume(np.nan_to_num(m))\n", "n_raw = len(m)\n", "nan_t = np.isnan(m).any(axis=(1, 2)); rule(\"R8 NaN ์ •์  ์‚ผ๊ฐํ˜• ์ œ๊ฑฐ\", nan_t.sum()); m = m[~nan_t]\n", "area = 0.5 * np.linalg.norm(np.cross(m[:, 1] - m[:, 0], m[:, 2] - m[:, 0]), axis=1)\n", "degen = area < 1e-9\n", "keys = [tuple(sorted(map(tuple, np.round(t, 4)))) for t in m]\n", "dup = np.array([k in set(keys[:i]) for i, k in enumerate(keys)])\n", "rule(\"R9 ํ‡ดํ™”ยท์ค‘๋ณต ์‚ผ๊ฐํ˜• ์ œ๊ฑฐ\", (degen | dup).sum()); m = m[~(degen | dup)]\n", "cen = m.mean(axis=(0, 1)); nrm = np.cross(m[:, 1] - m[:, 0], m[:, 2] - m[:, 0]); inward = np.einsum(\"ij,ij->i\", nrm, m.mean(axis=1) - cen) < 0\n", "m[inward] = m[inward][:, ::-1]; rule(\"R10 ๋ฒ•์„  ๋ฐฉํ–ฅ ํ†ต์ผ(์•ˆ์ชฝ ํ–ฅํ•จ ๋’ค์ง‘๊ธฐ)\", inward.sum())\n", "vol_clean = volume(m)\n", "\n", "# ---------------- 3) ์Šฌ๋ผ์ด์‹ฑ ์ถœ๋ ฅ ํŒŒ์‹ฑ ----------------\n", "lines = [\"; layer_height=0.2\"]\n", "for L in range(30):\n", " lines.append(f\";LAYER:{L}\")\n", " for s in range(12):\n", " lines.append(f\"G1 X{rng.uniform(0, 40):.2f} Y{rng.uniform(0, 30):.2f} E{rng.uniform(0.01, 0.2):.3f} F{rng.choice([1200, 1800, 2400])}\")\n", "for i in rng.choice(len(lines), 8, replace=False):\n", " if lines[i].startswith(\"G1\"):\n", " lines[i] = lines[i].replace(\" Y\", \" Yx\") if i % 2 else lines[i].replace(\"F\", \"F-\") # ์ขŒํ‘œ ๊นจ์งยท์Œ์ˆ˜ ์ด์†ก์†๋„\n", "pat = re.compile(r\"^G1 X(-?\\d+\\.\\d+) Y(-?\\d+\\.\\d+) E(\\d+\\.\\d+) F(\\d+)$\")\n", "good, rej, layer = [], [], -1\n", "for ln in lines:\n", " if ln.startswith(\";LAYER:\"):\n", " layer = int(ln.split(\":\")[1])\n", " elif ln.startswith(\"G1\"):\n", " mt = pat.match(ln)\n", " (good if mt else rej).append((layer, *map(float, mt.groups())) if mt else ln)\n", "G = pd.DataFrame(good, columns=[\"layer\", \"x\", \"y\", \"e\", \"f\"])\n", "rule(\"R11 ์Šฌ๋ผ์ด์‹ฑ ๋ถˆ๋Ÿ‰ ๋ผ์ธ ๊ฒฉ๋ฆฌ(์ˆ˜๋™ ํ™•์ธ ๋ชฉ๋ก)\", len(rej))\n", "\n", "# ---------------- 4) ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ ----------------\n", "chk = {\"์ค‘๋ณต ๋ ˆ์ฝ”๋“œ 0๊ฑด\": not df.duplicated().any(), \"์†Œ์žฌ ์ฝ”๋“œ ํ—ˆ์šฉ๊ฐ’\": df[\"material\"].isin(list(MAT)).all(),\n", " \"์น˜์ˆ˜ ๋‹จ์œ„ mm\": (df[\"unit\"] == \"mm\").all(), \"ํ๊ธฐ์œจ 0~1\": df[\"scrap_rate\"].between(0, 1).all(),\n", " \"์‹œํ—˜์ผ ๋ณ€ํ™˜\": df[\"test_date\"].notna().all(), \"๋ฒฝ๋‘๊ป˜ 0 ์ดˆ๊ณผ\": (df[\"min_wall_mm\"] > 0).all(),\n", " \"๊ฒฐ์ธก 0๊ฑด\": df[[\"load_N\", \"volume_cm3\", \"min_wall_mm\"]].notna().all().all(),\n", " f\"๊ธด๊ธ‰ ์ œ์ž‘ {int(df['emergency'].sum())}๊ฑด ๋ณด์กด\": int(df[\"emergency\"].sum()) == len(g),\n", " \"๋ฉ”์‰ฌ NaN 0๊ฑด\": not np.isnan(m).any(), \"๋ฉ”์‰ฌ ๋ถ€ํ”ผ ์˜ค์ฐจ 1% ์ด๋‚ด\": abs(vol_clean - 24000) / 24000 < 0.01,\n", " \"์Šฌ๋ผ์ด์‹ฑ ์ด์†ก์†๋„ ์–‘์ˆ˜\": bool((G[\"f\"] > 0).all())}\n", "rate = np.mean(list(chk.values()))\n", "print(\"\\n[์ ์šฉ ๊ทœ์น™๊ณผ ์ฒ˜๋ฆฌ ๊ฑด์ˆ˜]\")\n", "applied.sort(key=lambda r_: int(re.match(r\"R(\\d+)\", r_[0]).group(1)))\n", "for nme, n in applied:\n", " print(f\" {nme:42s} {n:4d}๊ฑด\")\n", "print(f\"\\n์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ {sum(chk.values())}/{len(chk)} ํ†ต๊ณผ, ํ†ต๊ณผ์œจ {rate:.0%} (๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if rate == 1 else '๋ฏธ๋‹ฌ'}), ๊ทœ์น™ {len(applied)}์ข… (๋ชฉํ‘œ 10์ข… ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(applied) >= 10 else '๋ฏธ๋‹ฌ'})\")\n", "for k, v in chk.items():\n", " if not v:\n", " print(\" ์‹คํŒจ:\", k)\n", "print(f\"์ •์ œ ํ›„ ๋ถ€ํ’ˆ ๋ ˆ์ฝ”๋“œ {len(df)}๊ฑด, ์ผ๋ฐ˜ ์ด์ƒ๊ฐ’ ํ‘œ์‹œ {int(df['outlier_flag'].sum())}๊ฑด, ๊ธด๊ธ‰ ์ œ์ž‘ ์˜ˆ์™ธ {int(df['emergency'].sum())}๊ฑด ์œ ์ง€\")\n", "print(f\"๋ฉ”์‰ฌ ์‚ผ๊ฐํ˜• {n_raw} -> {len(m)}๊ฐœ, ๋ถ€ํ”ผ {vol_raw / 1000:.2f} -> {vol_clean / 1000:.2f} cm3(๊ธฐ์ค€ 24.00)\")\n", "print(f\"์Šฌ๋ผ์ด์‹ฑ: {int(G['layer'].nunique())}์ธต, ์œ ํšจ {len(G)}์ค„, ๊ฒฉ๋ฆฌ {len(rej)}์ค„, ์ด ์••์ถœ๋Ÿ‰ {G['e'].sum():.1f}, ์˜ˆ์ƒ ์ด์†ก ์‹œ๊ฐ„ {(np.hypot(G['x'].diff(), G['y'].diff()).fillna(0) / G['f'] * 60).sum():.2f}๋ถ„\")\n", "\n", "num = lambda x: pd.to_numeric(x, errors=\"coerce\")\n", "cmp = pd.DataFrame({\"์ •์ œ ์ „ ํ‰๊ท \": [num(raw[c_]).mean() for c_ in [\"max_dim_mm\", \"min_wall_mm\", \"scrap_rate\", \"load_N\"]],\n", " \"์ •์ œ ํ›„ ํ‰๊ท \": [df[c_].mean() for c_ in [\"max_dim_mm\", \"min_wall_mm\", \"scrap_rate\", \"load_N\"]],\n", " \"์ •์ œ ์ „ ์ตœ์†Œ\": [num(raw[c_]).min() for c_ in [\"max_dim_mm\", \"min_wall_mm\", \"scrap_rate\", \"load_N\"]],\n", " \"์ •์ œ ํ›„ ์ตœ์†Œ\": [df[c_].min() for c_ in [\"max_dim_mm\", \"min_wall_mm\", \"scrap_rate\", \"load_N\"]]},\n", " index=[\"์ตœ๋Œ€์น˜์ˆ˜mm\", \"๋ฒฝ๋‘๊ป˜mm\", \"ํ๊ธฐ์œจ\", \"ํ•˜์ค‘N\"]).round(3)\n", "print(\"\\n[์ •์ œ ์ „ํ›„ ๊ธฐ์ˆ ํ†ต๊ณ„]\"); print(cmp.to_string())\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(10, 3.5))\n", "ax[0].hist(num(raw[\"scrap_rate\"]).dropna(), bins=30, alpha=0.6, label=\"raw\"); ax[0].hist(df[\"scrap_rate\"], bins=30, alpha=0.6, label=\"cleaned\")\n", "ax[0].set_title(\"Scrap rate before/after unit fix\"); ax[0].legend()\n", "ax[1].bar([n_.split()[0] for n_, _ in applied], [n_ for _, n_ in applied]); ax[1].set_title(\"Records changed by rule\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 02 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๋ถ€ํ’ˆ ์ด๋ ฅ] ์›์ฒœ 210๊ฑด (์ค‘๋ณต 10๊ฑด, ์˜ค๋ฅ˜ ์ฃผ์ž… 77๊ฑด ํฌํ•จ)\n", "\n", "[์ ์šฉ ๊ทœ์น™๊ณผ ์ฒ˜๋ฆฌ ๊ฑด์ˆ˜]\n", " R1 ์ค‘๋ณต ๋ ˆ์ฝ”๋“œ ์ œ๊ฑฐ 10๊ฑด\n", " R2 ์†Œ์žฌ ์ฝ”๋“œ ํ‘œ๊ธฐ ํ†ต์ผ 11๊ฑด\n", " R3 CAD ์น˜์ˆ˜ ๋‹จ์œ„ ํ†ต์ผ(inch -> mm) 11๊ฑด\n", " R4 ํ๊ธฐ์œจ ๋‹จ์œ„ ํ†ต์ผ(% -> ๋น„์œจ) 11๊ฑด\n", " R5 ์‹œํ—˜์ผ ํ˜•์‹ ํ†ต์ผ 11๊ฑด\n", " R6 ๋ถˆ๊ฐ€๋Šฅ ๋ฒฝ๋‘๊ป˜ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ 11๊ฑด\n", " R7 ๊ฒฐ์ธก KNN ๋ณด์ • 33๊ฑด\n", " R8 NaN ์ •์  ์‚ผ๊ฐํ˜• ์ œ๊ฑฐ 3๊ฑด\n", " R9 ํ‡ดํ™”ยท์ค‘๋ณต ์‚ผ๊ฐํ˜• ์ œ๊ฑฐ 14๊ฑด\n", " R10 ๋ฒ•์„  ๋ฐฉํ–ฅ ํ†ต์ผ(์•ˆ์ชฝ ํ–ฅํ•จ ๋’ค์ง‘๊ธฐ) 10๊ฑด\n", " R11 ์Šฌ๋ผ์ด์‹ฑ ๋ถˆ๋Ÿ‰ ๋ผ์ธ ๊ฒฉ๋ฆฌ(์ˆ˜๋™ ํ™•์ธ ๋ชฉ๋ก) 7๊ฑด\n", " R12 ๊ธด๊ธ‰ ์ œ์ž‘ ์˜ˆ์™ธ ๋ณด์กดยท์ผ๋ฐ˜ ์ด์ƒ๊ฐ’ ํ‘œ์‹œ 5๊ฑด\n", "\n", "์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ 11/11 ํ†ต๊ณผ, ํ†ต๊ณผ์œจ 100% (๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ), ๊ทœ์น™ 12์ข… (๋ชฉํ‘œ 10์ข… ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "์ •์ œ ํ›„ ๋ถ€ํ’ˆ ๋ ˆ์ฝ”๋“œ 200๊ฑด, ์ผ๋ฐ˜ ์ด์ƒ๊ฐ’ ํ‘œ์‹œ 3๊ฑด, ๊ธด๊ธ‰ ์ œ์ž‘ ์˜ˆ์™ธ 11๊ฑด ์œ ์ง€\n", "๋ฉ”์‰ฌ ์‚ผ๊ฐํ˜• 209 -> 192๊ฐœ, ๋ถ€ํ”ผ 21.00 -> 24.00 cm3(๊ธฐ์ค€ 24.00)\n", "์Šฌ๋ผ์ด์‹ฑ: 30์ธต, ์œ ํšจ 353์ค„, ๊ฒฉ๋ฆฌ 7์ค„, ์ด ์••์ถœ๋Ÿ‰ 39.0, ์˜ˆ์ƒ ์ด์†ก ์‹œ๊ฐ„ 238.36๋ถ„\n", "\n", "[์ •์ œ ์ „ํ›„ ๊ธฐ์ˆ ํ†ต๊ณ„]\n", " ์ •์ œ ์ „ ํ‰๊ท  ์ •์ œ ํ›„ ํ‰๊ท  ์ •์ œ ์ „ ์ตœ์†Œ ์ •์ œ ํ›„ ์ตœ์†Œ\n", "์ตœ๋Œ€์น˜์ˆ˜mm 43.270 46.097 0.817 18.448\n", "๋ฒฝ๋‘๊ป˜mm 2.114 2.407 -1.200 0.405\n", "ํ๊ธฐ์œจ 0.663 0.117 0.029 0.029\n", "ํ•˜์ค‘N 2785.396 2864.990 50.301 50.301\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 03. ๊ธฐํ•˜ํ•™์  ํ˜•์ƒ ๊ธฐ๋ฐ˜ 1์ฐจ 3D ํ”„๋ฆฐํŒ… ์ œ์ž‘ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํŒ๋ณ„ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "ํ˜•์ƒ ๊ธฐ๋ฐ˜ 1์ฐจ 3D ํ”„๋ฆฐํŒ… ์ œ์ž‘ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํŒ๋ณ„ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ฒฝ๋‘๊ป˜ยท๋Œ์ถœ ๋น„์œจยท์ตœ๋Œ€ ์น˜์ˆ˜ ๊ฐ™์€ ๊ธฐํ•˜ํ•™์  ํ˜•์ƒ๋งŒ์œผ๋กœ 3D ํ”„๋ฆฐํŒ… ์ œ์ž‘ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ 1์ฐจ ํŒ๋ณ„ํ•˜๋Š” Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•˜๊ณ  ํƒ์ƒ‰์  ๋ถ„์„์œผ๋กœ ํ•œ๊ณ„์™€ ํ•ต์‹ฌ ๊ด€๋ฆฌ ๋Œ€์ƒ์„ ์ฐพ๋Š”๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ฐ€์ƒ ๋ถ€ํ’ˆ 600์ข…์— ๊ทœ์น™ ๊ธฐ๋ฐ˜ ํŒ๋ณ„์„ ์ ์šฉํ•ด ์ •ํ™•๋„ยท์ •๋ฐ€๋„ยท์žฌํ˜„์œจ๊ณผ ํ˜ผ๋™ํ–‰๋ ฌ์„ ๊ตฌํ•˜๊ณ , ๊ฐ€๋Šฅ์ด๋ผ ํŒ์ •ํ–ˆ์œผ๋‚˜ ์‹คํŒจํ•œ ์›์ธ(์–‡์€ ๋‚˜์‚ฌ๋ถ€, ์†Œ์žฌ๋ณ„ ์ตœ์†Œ ๋ฒฝ๋‘๊ป˜)์„ ๋ถ„๋ฅ˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `notebooks/mnt06_eda_analysis.ipynb ๋ฐ EDA ์š”์•ฝ ๋ฆฌํฌํŠธ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ์˜์‚ฌ๊ฒฐ์ •์— ์ง๊ด€์ ์ธ ์ธ์‚ฌ์ดํŠธ ์ œ๊ณต ์ˆ˜์ค€\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: EDA ์‹œ๊ฐํ™” ์ฐจํŠธ 8์ข… ์ด์ƒ ์ƒ์„ฑ, ์œ ์˜๋ฏธํ•œ ์ƒ๊ด€๊ด€๊ณ„ ํ”ผ์ฒ˜ 5๊ฐœ ์ด์ƒ ๋„์ถœ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: EDA ๋…ธํŠธ๋ถ ์‹คํ–‰ ์™„๋ฃŒ ๋ฐ ํ•ต์‹ฌ ๋ฐœ๊ฒฌ์ (Key Findings) ๋ฌธ์„œํ™”\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ํ†ต๊ณ„์  ๊ฐ€์„ค ๊ฒ€์ •(p-value, ์ƒ๊ด€๊ณ„์ˆ˜)์„ ํ†ตํ•œ ์œ ์˜์„ฑ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๋ถ€ํ’ˆ์˜ ํ˜•์ƒ(์ตœ์†Œ ๋ฒฝ๋‘๊ป˜ยท๋Œ์ถœ ๋น„์œจยท์ตœ๋Œ€ ์น˜์ˆ˜)๋งŒ์œผ๋กœ 3D ํ”„๋ฆฐํŒ… ์ œ์ž‘ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ 1์ฐจ ํŒ๋ณ„ํ•˜๋Š” ๊ทœ์น™ ๊ธฐ๋ฐ˜ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•˜๊ณ , ํƒ์ƒ‰์  ๋ถ„์„(EDA) ์ฐจํŠธ 8์ข…ยท์ƒ๊ด€๋ถ„์„(p-value)ยทํŒŒ๋ ˆํ† (ABC) ๋ถ„์„์œผ๋กœ Baseline์˜ ํ•œ๊ณ„์™€ ํ๊ธฐ ์†์‹ค์ด ํฐ ํ•ต์‹ฌ ๊ด€๋ฆฌ ๋Œ€์ƒ์„ ์ฐพ๋Š”๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy import stats\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด ๋ถ€ํ’ˆ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€ํ’ˆยท์žฌ๊ณ ยท๊ทœ๊ฒฉ ๊ฐ’ ์•„๋‹˜)\n", "# ํ๊ธฐ์œจ์ด ๋†’์€ ์ˆ˜๋ฆฌ๋ถ€์† ์ค‘ 3D ํ”„๋ฆฐํŒ…์œผ๋กœ ๋Œ€์ฒด ์ œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์ƒ ๋ถ€ํ’ˆ ์ž๋ฃŒ๋ฅผ ๋งŒ๋“ ๋‹ค.\n", "# ์†Œ์žฌ ๋ฌผ์„ฑยท์น˜์ˆ˜ยทํ•˜์ค‘์€ ๊ต์œก์šฉ์œผ๋กœ ๋‹จ์ˆœํ™”ํ•œ ๊ฐ’์ด๋ฉฐ ์‹ค์ œ ์‹œํ—˜ ์„ฑ์ ์„œ๋‚˜ ๊ตฐ ๊ทœ๊ฒฉ ๊ฐ’๊ณผ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "MAT = { # ์†Œ์žฌ: (๊ณต์ •, ๊ธฐ์ค€ ์ธ์žฅ๊ฐ•๋„ MPa, ๋ฐ€๋„ g/cm3, ์†Œ์žฌ ๋‹จ๊ฐ€ ๋งŒ์›/kg, ์ถœ๋ ฅ๋น„ ๋งŒ์›/h, ์ตœ์†Œ ๋ฒฝ๋‘๊ป˜ mm, ์ถœ๋ ฅ ์†๋„ cm3/h)\n", " \"PA12\": (\"polymer\", 45, 1.0, 8, 3, 0.8, 18), \"PETG\": (\"polymer\", 50, 1.27, 5, 2, 0.8, 15),\n", " \"CF-PA\": (\"polymer\", 70, 1.2, 14, 3, 1.0, 12), \"AlSi10Mg\": (\"metal\", 350, 2.67, 30, 25, 0.5, 4),\n", " \"SS316L\": (\"metal\", 520, 7.9, 28, 25, 0.6, 3.5), \"Ti64\": (\"metal\", 900, 4.4, 90, 30, 0.6, 3)}\n", "CATS = [\"bracket\", \"housing\", \"clip\", \"gear\", \"duct\", \"handle\"]\n", "CAT_SCRAP = {\"bracket\": 0.04, \"housing\": 0.05, \"clip\": 0.12, \"gear\": 0.09, \"duct\": 0.08, \"handle\": 0.10} # ๋ถ€ํ’ˆ๊ตฐ๋ณ„ ๊ธฐ๋ณธ ์›” ํ๊ธฐ์œจ\n", "BUILD_MM = {\"polymer\": 300, \"metal\": 200} # ๊ฐ€์ƒ ์ถœ๋ ฅ ๊ฐ€๋Šฅ ์ตœ๋Œ€ ์น˜์ˆ˜\n", "\n", "\n", "def make_parts(n=300, seed=0):\n", " \"\"\"๊ฐ€์ƒ ๋ถ€ํ’ˆ n์ข…: ํ˜•์ƒ(๋ถ€ํ”ผยท๋ฒฝ๋‘๊ป˜ยท๋Œ์ถœ ๋น„์œจ), ์†Œ์žฌ, ์‚ฌ์šฉ ํ•˜์ค‘, ํ๊ธฐ์œจ๊ณผ ์กฐ๋‹ฌ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " mat = rng.choice(list(MAT), n, p=[0.2, 0.15, 0.1, 0.2, 0.2, 0.15])\n", " proc = np.array([MAT[m][0] for m in mat])\n", " vol = np.clip(rng.lognormal(np.log(30), 0.9, n), 2, 800)\n", " aspect = rng.lognormal(0.3, 0.4, n)\n", " max_dim = 10 * vol ** (1 / 3) * aspect ** (1 / 3) * rng.uniform(1.0, 1.6, n)\n", " wall = np.clip(rng.lognormal(np.log(2.0), 0.6, n), 0.3, 8)\n", " ovh = rng.beta(2, 5, n)\n", " is_poly = proc == \"polymer\"\n", " df = pd.DataFrame({\n", " \"part_id\": [f\"P{i:03d}\" for i in range(n)], \"cat\": rng.choice(CATS, n, p=[0.2, 0.15, 0.2, 0.1, 0.15, 0.2]), \"material\": mat, \"process\": proc,\n", " \"volume_cm3\": vol, \"max_dim_mm\": max_dim, \"area_cm2\": 6 * vol ** (2 / 3) * rng.uniform(1.0, 2.2, n), \"min_wall_mm\": wall, \"overhang\": ovh,\n", " \"n_holes\": rng.poisson(2.0, n), \"thread\": rng.random(n) < 0.25, \"orient_deg\": rng.uniform(0, 90, n),\n", " \"layer_mm\": np.where(is_poly, rng.choice([0.1, 0.2, 0.3], n), rng.choice([0.03, 0.05], n)),\n", " \"infill\": np.where(is_poly, rng.choice([40, 60, 80, 100], n, p=[0.15, 0.3, 0.3, 0.25]), 100),\n", " \"load_N\": np.clip(rng.lognormal(np.where(is_poly, np.log(230), np.log(2300)), 0.7, n), 10, 20000), \"temp_C\": rng.uniform(-20, 90, n),\n", " \"crit\": rng.choice([1, 2, 3], n, p=[0.35, 0.4, 0.25]), \"cert\": rng.choice([0, 1], n, p=[0.45, 0.55]), # ์†Œ์žฌ ์ธ์ฆ์„œ ๋ณด์œ \n", " \"import_part\": rng.random(n) < 0.4})\n", " df[\"scrap_rate\"] = np.clip(df[\"cat\"].map(CAT_SCRAP) * rng.lognormal(0, 0.35, n) * (1 + 0.5 * (df[\"load_N\"] > 800)) * (1 + 0.3 * (df[\"temp_C\"] > 60)), 0.005, 0.5)\n", " df[\"lead_days\"] = np.clip(rng.lognormal(np.log(25), 0.5, n) * np.where(df[\"import_part\"], 2.2, 1.0), 5, 300)\n", " # ---- ์ˆจ์€ ์ง„์‹ค๊ฐ’(์‹ค์ œ ์‹œํ—˜ ๊ฒฐ๊ณผ์— ํ•ด๋‹น): ๋ชจ๋ธ์€ ์œ„ ์ž…๋ ฅ์œผ๋กœ ์ด๊ฒƒ์„ ์˜ˆ์ธกํ•œ๋‹ค ----\n", " eff = np.where(is_poly, 0.55 + 0.45 * df[\"infill\"] / 100, 1 - 0.3 * ovh * 0.2)\n", " aniso = np.where(is_poly, 1 - 0.3 * np.sin(np.radians(df[\"orient_deg\"])) ** 2, 1 - 0.08 * np.sin(np.radians(df[\"orient_deg\"])))\n", " heat = np.where(is_poly, 1 - 0.35 * np.clip((df[\"temp_C\"] - 40) / 80, 0, 1), 0.98)\n", " base = np.array([MAT[m][1] for m in mat])\n", " df[\"strength_mpa\"] = base * eff * aniso * heat * rng.lognormal(0, 0.06, n)\n", " cross = df[\"min_wall_mm\"] * np.sqrt(df[\"max_dim_mm\"]) * 1.0 # ํ•˜์ค‘ ์ง€์ง€ ๋‹จ๋ฉด์  mm2(๋‹จ์ˆœํ™”)\n", " df[\"stress_mpa\"] = df[\"load_N\"] / cross\n", " df[\"safety\"] = df[\"strength_mpa\"] / df[\"stress_mpa\"]\n", " ratio = np.clip(df[\"stress_mpa\"] / df[\"strength_mpa\"], 0, 1.5)\n", " df[\"log_cycles\"] = np.clip(7.5 - 6.0 * ratio + rng.normal(0, 0.25, n), 2, 7.5) # ํ”ผ๋กœ ์ˆ˜๋ช… log10(ํšŒ)\n", " support = 1 + 0.5 * ovh\n", " df[\"build_h\"] = vol * support / np.array([MAT[m][6] for m in mat]) + 0.5\n", " mass_kg = vol * np.array([MAT[m][2] for m in mat]) * support / 1000\n", " df[\"print_cost\"] = mass_kg * np.array([MAT[m][3] for m in mat]) + df[\"build_h\"] * np.array([MAT[m][4] for m in mat]) + 3\n", " df[\"buy_cost\"] = df[\"print_cost\"] * rng.lognormal(0.25, 0.45, n) + 5 # ๊ตฌ๋งค ๋‹จ๊ฐ€(๋งŒ์›, ๊ฐ€์ƒ)\n", " wall_ok = df[\"min_wall_mm\"] >= np.array([MAT[m][5] for m in mat]) * rng.uniform(0.95, 1.25, n)\n", " dim_ok = df[\"max_dim_mm\"] <= np.where(is_poly, BUILD_MM[\"polymer\"], BUILD_MM[\"metal\"])\n", " warp = (~is_poly) & (df[\"max_dim_mm\"] > 140) & (df[\"overhang\"] > 0.3) # ๊ธˆ์† ๋Œ€ํ˜• ์˜ค๋ฒ„ํ–‰ ๋ณ€ํ˜•\n", " thr_bad = df[\"thread\"] & (df[\"min_wall_mm\"] < 1.5) & is_poly # ์–‡์€ ๋‚˜์‚ฌ๋ถ€ ํŒŒ์†\n", " df[\"printable\"] = wall_ok & dim_ok & (ovh < 0.5) & ~warp & ~thr_bad\n", " risk = np.clip(1 - df[\"safety\"] / 3, 0, 1)\n", " df[\"conform\"] = np.clip(100 * (0.45 * np.minimum(df[\"safety\"] / 3, 1) + 0.25 * np.clip((df[\"log_cycles\"] - 3) / 4.5, 0, 1)\n", " + 0.15 * (1 - np.clip(ovh * 1.2, 0, 1)) + 0.15 * df[\"cert\"]) - 8 * risk * df[\"crit\"] / 3 + rng.normal(0, 2, n), 0, 100)\n", " df[\"demand_month\"] = rng.poisson(np.where(df[\"cat\"].isin([\"clip\", \"handle\"]), 40, 15)) + 1 # ์›” ์†Œ์š”์ˆ˜๋Ÿ‰(๊ฐ€์ƒ)\n", " df[\"scrap_cost\"] = df[\"scrap_rate\"] * df[\"demand_month\"] * df[\"buy_cost\"] # ์›” ํ๊ธฐ ์†์‹ค์•ก(๋งŒ์›, ๊ฐ€์ƒ)\n", " return df\n", "\n", "df = make_parts(600, seed=3)\n", "\n", "\n", "def baseline(d):\n", " \"\"\"ํ˜•์ƒ ๊ธฐ๋ฐ˜ 1์ฐจ ํŒ๋ณ„: ๋ฒฝ๋‘๊ป˜ >= ๊ณตํ†ต ๊ธฐ์ค€ 1.0mm, ์ตœ๋Œ€ ์น˜์ˆ˜ <= ์ถœ๋ ฅ ๊ฐ€๋Šฅ ์น˜์ˆ˜, ๋Œ์ถœ ๋น„์œจ < 0.5 (์†Œ์žฌ๋ณ„ ์ฐจ์ด๋Š” ๋ณด์ง€ ์•Š๋Š”๋‹ค).\"\"\"\n", " return (d[\"min_wall_mm\"] >= 1.0) & (d[\"max_dim_mm\"] <= d[\"process\"].map(BUILD_MM)) & (d[\"overhang\"] < 0.5)\n", "\n", "\n", "df[\"pred\"] = baseline(df)\n", "tp, fp = int((df[\"pred\"] & df[\"printable\"]).sum()), int((df[\"pred\"] & ~df[\"printable\"]).sum())\n", "fn, tn = int((~df[\"pred\"] & df[\"printable\"]).sum()), int((~df[\"pred\"] & ~df[\"printable\"]).sum())\n", "acc, prec, rec = (tp + tn) / len(df), tp / (tp + fp), tp / (tp + fn)\n", "print(f\"[Baseline ๊ฒฐ๊ณผ] ๋ถ€ํ’ˆ {len(df)}์ข…, ์‹œํ—˜ ์ถœ๋ ฅ ์„ฑ๊ณต {df['printable'].mean():.1%}\")\n", "print(f\"์ •ํ™•๋„ {acc:.1%}, ์ •๋ฐ€๋„ {prec:.1%}, ์žฌํ˜„์œจ {rec:.1%} (ํ˜ผ๋™ํ–‰๋ ฌ TP {tp} FP {fp} FN {fn} TN {tn})\")\n", "miss = df[df[\"pred\"] & ~df[\"printable\"]]\n", "why = pd.Series(np.select([miss[\"thread\"] & (miss[\"process\"] == \"polymer\") & (miss[\"min_wall_mm\"] < 1.5), (miss[\"process\"] == \"metal\") & (miss[\"max_dim_mm\"] > 140) & (miss[\"overhang\"] > 0.3)],\n", " [\"์–‡์€ ๋‚˜์‚ฌ๋ถ€ ํŒŒ์†\", \"๊ธˆ์† ๋Œ€ํ˜• ์˜ค๋ฒ„ํ–‰ ๋ณ€ํ˜•\"], \"์†Œ์žฌ๋ณ„ ์ตœ์†Œ ๋ฒฝ๋‘๊ป˜ ๋ฏธ๋‹ฌ\"))\n", "print(\"Baseline์ด '๊ฐ€๋Šฅ'์ด๋ผ ํ–ˆ์œผ๋‚˜ ์‹คํŒจํ•œ ์›์ธ:\", \", \".join(f\"{k} {v}๊ฑด\" for k, v in why.value_counts().items()))\n", "print(f\"Baseline์ด '๋ถˆ๊ฐ€'๋ผ ํ–ˆ์œผ๋‚˜ ์‹ค์ œ ์„ฑ๊ณตํ•œ ๋ถ€ํ’ˆ {fn}๊ฑด(๊ธˆ์† ๋“ฑ ์–‡์€ ๋ฒฝ ์ถœ๋ ฅ ๊ฐ€๋Šฅ ์†Œ์žฌ๋ฅผ ๋†“์นจ)\")\n", "print(\"-> ํ˜•์ƒ ๊ทœ์น™๋งŒ์œผ๋กœ๋Š” ๋‚˜์‚ฌ๋ถ€ยท์—ด๋ณ€ํ˜•ยท์†Œ์žฌ ํŽธ์ฐจ๋ฅผ ๋ชป ๋ณธ๋‹ค. ์œ„ํ—˜ํ•œ ์˜คํŒ(FP)์€ ์ •๋น„๊ธฐ์ˆ ๊ด€ ๊ฒ€ํ†  ์ „ ๊ฑธ๋Ÿฌ์•ผ ํ•œ๋‹ค(๋ฏธ์…˜ 4~5์˜ ๋™๊ธฐ).\")\n", "\n", "# 1) ์ƒ๊ด€๋ถ„์„: ์‹œํ—˜ ์ถœ๋ ฅ ์„ฑ๊ณต ์—ฌ๋ถ€์™€์˜ ๊ด€๊ณ„(์ ์ด์—ฐ ์ƒ๊ด€, ์œ ์˜์ˆ˜์ค€ 0.05)\n", "df[\"wall_ratio\"] = df[\"min_wall_mm\"] / df[\"material\"].map(lambda m: MAT[m][5])\n", "df[\"is_metal\"] = (df[\"process\"] == \"metal\").astype(int)\n", "feats = [\"wall_ratio\", \"min_wall_mm\", \"overhang\", \"max_dim_mm\", \"volume_cm3\", \"is_metal\", \"n_holes\", \"thread\", \"layer_mm\", \"load_N\", \"crit\"]\n", "rows = []\n", "for f in feats:\n", " r, p = stats.pearsonr(df[f].astype(float), df[\"printable\"].astype(float))\n", " rows.append({\"ํ”ผ์ฒ˜\": f, \"์ƒ๊ด€๊ณ„์ˆ˜\": round(r, 3), \"p-value\": f\"{p:.1e}\", \"์œ ์˜\": p < 0.05})\n", "corr = pd.DataFrame(rows).sort_values(\"์ƒ๊ด€๊ณ„์ˆ˜\", key=abs, ascending=False)\n", "print(\"\\n[์‹œํ—˜ ์ถœ๋ ฅ ์„ฑ๊ณต ์—ฌ๋ถ€์™€์˜ ์ƒ๊ด€๋ถ„์„]\")\n", "print(corr.to_string(index=False))\n", "n_sig = int(corr[\"์œ ์˜\"].sum())\n", "print(f\"์œ ์˜๋ฏธํ•œ ํ”ผ์ฒ˜ {n_sig}๊ฐœ (๋ชฉํ‘œ 5๊ฐœ ์ด์ƒ: {'๋‹ฌ์„ฑ' if n_sig >= 5 else '๋ฏธ๋‹ฌ'})\")\n", "t, p = stats.ttest_ind(df.loc[df[\"printable\"], \"scrap_rate\"], df.loc[~df[\"printable\"], \"scrap_rate\"])\n", "print(f\"ํ๊ธฐ์œจ ํ‰๊ท : ์ œ์ž‘ ๊ฐ€๋Šฅ {df.loc[df['printable'], 'scrap_rate'].mean():.3f} vs ๋ถˆ๊ฐ€ {df.loc[~df['printable'], 'scrap_rate'].mean():.3f} (t๊ฒ€์ • p={p:.2f}) -> ํ๊ธฐ์œจ๊ณผ ์ œ์ž‘ ๊ฐ€๋Šฅ์„ฑ์€ ๋ณ„๊ฐœ ์ถ•์ด๋ฏ€๋กœ ๋‘˜์„ ํ•จ๊ป˜ ๋ด์•ผ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ์ •ํ•ด์ง„๋‹ค\")\n", "\n", "# 2) ํŒŒ๋ ˆํ† (ABC): ์›” ํ๊ธฐ ์†์‹ค์•ก์ด ํฐ (๋ถ€ํ’ˆ๊ตฐ, ์†Œ์žฌ) ์กฐํ•ฉ ์ค‘ ์ œ์ž‘ ๊ฐ€๋Šฅํ•œ ๊ฒƒ\n", "g = df.groupby([\"cat\", \"material\"])[\"scrap_cost\"].sum().sort_values(ascending=False)\n", "cum = g.cumsum() / g.sum()\n", "abc = pd.cut(cum, [0, 0.8, 0.95, 1.0], labels=[\"A\", \"B\", \"C\"])\n", "top = [f\"{c}/{m}\" for (c, m), v in abc.items() if v == \"A\"]\n", "print(f\"\\n[ํŒŒ๋ ˆํ†  A๋“ฑ๊ธ‰(๋ˆ„์  ํ๊ธฐ ์†์‹ค 80%)] {len(top)}๊ฐœ ์กฐํ•ฉ: \" + \", \".join(top[:6]) + \" ...\")\n", "print(f\"A๋“ฑ๊ธ‰์€ ์ „์ฒด ์กฐํ•ฉ {len(g)}๊ฐœ ์ค‘ {(abc == 'A').mean():.0%}, ์ด ์ค‘ Baseline ์ œ์ž‘ ๊ฐ€๋Šฅ ๋ถ€ํ’ˆ ๋น„์œจ {df[df.set_index(['cat', 'material']).index.isin(list(abc[abc == 'A'].index))]['pred'].mean():.0%}\")\n", "\n", "# 3) EDA ์ฐจํŠธ 8์ข…\n", "fig, ax = plt.subplots(2, 4, figsize=(18, 8))\n", "a = ax.ravel()\n", "a[0].hist(df[\"scrap_rate\"], bins=30); a[0].set_title(\"1. Scrap rate distribution\")\n", "df.groupby(\"cat\")[\"scrap_rate\"].mean().plot.bar(ax=a[1]); a[1].set_title(\"2. Mean scrap rate by category\")\n", "a[2].scatter(df[\"min_wall_mm\"], df[\"max_dim_mm\"], c=df[\"printable\"], cmap=\"coolwarm_r\", s=8); a[2].set_title(\"3. Wall vs size (blue=printable)\")\n", "df.boxplot(column=\"overhang\", by=\"printable\", ax=a[3]); a[3].set_title(\"4. Overhang by print result\")\n", "df[\"material\"].value_counts().plot.bar(ax=a[4]); a[4].set_title(\"5. Parts by material\")\n", "a[5].scatter(df[\"load_N\"], df[\"safety\"], s=6); a[5].set_xscale(\"log\"); a[5].set_yscale(\"log\"); a[5].set_title(\"6. Load vs safety factor\")\n", "a[6].barh(corr[\"ํ”ผ์ฒ˜\"], corr[\"์ƒ๊ด€๊ณ„์ˆ˜\"]); a[6].set_title(\"7. Correlation with print success\")\n", "a[7].plot(range(1, len(cum) + 1), cum.values, \"o-\"); a[7].axhline(0.8, ls=\"--\", c=\"gray\"); a[7].set_title(\"8. Pareto of scrap loss\")\n", "plt.suptitle(\"MNT06 EDA - baseline printability rule (synthetic data)\"); plt.tight_layout(); plt.show()\n", "print(\"\\nEDA ์ฐจํŠธ 8์ข… ์ƒ์„ฑ (๋ชฉํ‘œ 8์ข… ์ด์ƒ: ๋‹ฌ์„ฑ)\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 03 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[Baseline ๊ฒฐ๊ณผ] ๋ถ€ํ’ˆ 600์ข…, ์‹œํ—˜ ์ถœ๋ ฅ ์„ฑ๊ณต 80.8%\n", "์ •ํ™•๋„ 91.0%, ์ •๋ฐ€๋„ 96.5%, ์žฌํ˜„์œจ 92.2% (ํ˜ผ๋™ํ–‰๋ ฌ TP 447 FP 16 FN 38 TN 99)\n", "Baseline์ด '๊ฐ€๋Šฅ'์ด๋ผ ํ–ˆ์œผ๋‚˜ ์‹คํŒจํ•œ ์›์ธ: ์–‡์€ ๋‚˜์‚ฌ๋ถ€ ํŒŒ์† 14๊ฑด, ์†Œ์žฌ๋ณ„ ์ตœ์†Œ ๋ฒฝ๋‘๊ป˜ ๋ฏธ๋‹ฌ 2๊ฑด\n", "Baseline์ด '๋ถˆ๊ฐ€'๋ผ ํ–ˆ์œผ๋‚˜ ์‹ค์ œ ์„ฑ๊ณตํ•œ ๋ถ€ํ’ˆ 38๊ฑด(๊ธˆ์† ๋“ฑ ์–‡์€ ๋ฒฝ ์ถœ๋ ฅ ๊ฐ€๋Šฅ ์†Œ์žฌ๋ฅผ ๋†“์นจ)\n", "-> ํ˜•์ƒ ๊ทœ์น™๋งŒ์œผ๋กœ๋Š” ๋‚˜์‚ฌ๋ถ€ยท์—ด๋ณ€ํ˜•ยท์†Œ์žฌ ํŽธ์ฐจ๋ฅผ ๋ชป ๋ณธ๋‹ค. ์œ„ํ—˜ํ•œ ์˜คํŒ(FP)์€ ์ •๋น„๊ธฐ์ˆ ๊ด€ ๊ฒ€ํ†  ์ „ ๊ฑธ๋Ÿฌ์•ผ ํ•œ๋‹ค(๋ฏธ์…˜ 4~5์˜ ๋™๊ธฐ).\n", "\n", "[์‹œํ—˜ ์ถœ๋ ฅ ์„ฑ๊ณต ์—ฌ๋ถ€์™€์˜ ์ƒ๊ด€๋ถ„์„]\n", " ํ”ผ์ฒ˜ ์ƒ๊ด€๊ณ„์ˆ˜ p-value ์œ ์˜\n", " overhang -0.385 1.3e-22 True\n", " wall_ratio 0.294 2.1e-13 True\n", "min_wall_mm 0.279 3.5e-12 True\n", " is_metal 0.188 3.7e-06 True\n", " layer_mm -0.159 8.9e-05 True\n", " load_N 0.103 1.1e-02 True\n", " crit -0.078 5.5e-02 False\n", " thread -0.069 9.2e-02 False\n", " max_dim_mm 0.026 5.3e-01 False\n", " n_holes 0.018 6.6e-01 False\n", " volume_cm3 -0.005 9.1e-01 False\n", "์œ ์˜๋ฏธํ•œ ํ”ผ์ฒ˜ 6๊ฐœ (๋ชฉํ‘œ 5๊ฐœ ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "ํ๊ธฐ์œจ ํ‰๊ท : ์ œ์ž‘ ๊ฐ€๋Šฅ 0.124 vs ๋ถˆ๊ฐ€ 0.105 (t๊ฒ€์ • p=0.01) -> ํ๊ธฐ์œจ๊ณผ ์ œ์ž‘ ๊ฐ€๋Šฅ์„ฑ์€ ๋ณ„๊ฐœ ์ถ•์ด๋ฏ€๋กœ ๋‘˜์„ ํ•จ๊ป˜ ๋ด์•ผ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ์ •ํ•ด์ง„๋‹ค\n", "\n", "[ํŒŒ๋ ˆํ†  A๋“ฑ๊ธ‰(๋ˆ„์  ํ๊ธฐ ์†์‹ค 80%)] 8๊ฐœ ์กฐํ•ฉ: handle/SS316L, handle/AlSi10Mg, clip/Ti64, clip/AlSi10Mg, handle/Ti64, clip/SS316L ...\n", "A๋“ฑ๊ธ‰์€ ์ „์ฒด ์กฐํ•ฉ 36๊ฐœ ์ค‘ 22%, ์ด ์ค‘ Baseline ์ œ์ž‘ ๊ฐ€๋Šฅ ๋ถ€ํ’ˆ ๋น„์œจ 82%\n", "\n", "EDA ์ฐจํŠธ 8์ข… ์ƒ์„ฑ (๋ชฉํ‘œ 8์ข… ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 04. ์œ ํ•œ์š”์†Œํ•ด์„(FEA) ์—ฐ๊ณ„ ์•ผ์ „ ๋ถ€ํ’ˆ ์ธ์žฅ๊ฐ•๋„ยทํ”ผ๋กœ๋‚ด๊ตฌ๋„ ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ ๋จธ์‹ ๋Ÿฌ๋‹ ๊ณ ๋„ํ™”\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์œ ํ•œ์š”์†Œํ•ด์„(FEA) ์—ฐ๊ณ„ ๋ถ€ํ’ˆ ์ธ์žฅ๊ฐ•๋„ยท๋‚ด๊ตฌ๋„ ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ ๋ชจ๋ธ์„ ๊ณ ๋„ํ™”ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์œ ํ•œ์š”์†Œํ•ด์„(FEA)์„ ๊ฐ„์ดํ™”ํ•œ ์‘๋ ฅ ๊ณ„์‚ฐ๊ณผ ์†Œ์žฌยท๊ณต์ • ์ง€์‹์„ ์—ฐ๊ณ„ํ•ด ์•ผ์ „ ๋ถ€ํ’ˆ์˜ ์ธ์žฅ๊ฐ•๋„์™€ ํ”ผ๋กœ ์ˆ˜๋ช…์„ ์˜ˆ์ธกํ•˜๋Š” ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ์„ ๊ณ ๋„ํ™”ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋‹จ๋ฉด์ , ๊ตฌ๋ฉยท๋‚˜์‚ฌ๋ถ€ ์‘๋ ฅ์ง‘์ค‘, ์ ์ธต ๋ฐฉํ–ฅ, ์ถฉ์ „์œจ, ์˜จ๋„ ์ €ํ•˜ ๋“ฑ์œผ๋กœ ํŒŒ์ƒ ํ”ผ์ฒ˜ 10๊ฐœ ์ด์ƒ์„ ๋งŒ๋“ค๊ณ  ์†Œ์žฌ ๊ธฐ์ค€๊ฐ•๋„ ๋Œ€๋น„ ์ €๊ฐ ๊ณ„์ˆ˜๋ฅผ ํ•™์Šต ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/features/mnt06_feature_engineering.py ๋ฐ ๊ธฐ์ค€ ๋ชจ๋ธ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ์‹ค๋ฌด์—์„œ ๋‚ฉ๋“ ๊ฐ€๋Šฅํ•œ ํ”ผ์ฒ˜ ์„ค๊ณ„์˜ ๋…ผ๋ฆฌ์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ํŒŒ์ƒ ํ”ผ์ฒ˜ 10๊ฐœ ์ด์ƒ ์ƒ์„ฑ, ๊ธฐ์ค€ ๋ชจ๋ธ MAPE 25% ์ดํ•˜ ๋‹ฌ์„ฑ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ํ”ผ์ฒ˜ ํŒŒ์ดํ”„๋ผ์ธ ๋ฐ ๋ฒ ์ด์Šค๋ผ์ธ ๋ชจ๋ธ ์„ฑ๋Šฅ ์ง€ํ‘œ ๋„์ถœ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๊ต์ฐจ ๊ฒ€์ฆ(Time-series Cross Validation)์„ ํ†ตํ•œ ๊ณผ์ ํ•ฉ ๋ฐฉ์ง€\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์œ ํ•œ์š”์†Œํ•ด์„(FEA)์„ ๊ฐ„์ดํ™”ํ•œ ์‘๋ ฅ ๊ณ„์‚ฐ๊ฐ’๊ณผ ์†Œ์žฌยท๊ณต์ • ์ง€์‹์œผ๋กœ ํŒŒ์ƒ ํ”ผ์ฒ˜ 16๊ฐœ๋ฅผ ๋งŒ๋“ค์–ด ์•ผ์ „ ๋ถ€ํ’ˆ์˜ ์ธ์žฅ๊ฐ•๋„์™€ ํ”ผ๋กœ ์ˆ˜๋ช…(์‹ ๋ขฐ์„ฑ)์„ ์˜ˆ์ธกํ•˜๋Š” ๋จธ์‹ ๋Ÿฌ๋‹ ๊ธฐ์ค€ ๋ชจ๋ธ์„ ์‹œ๊ณ„์—ด ๊ต์ฐจ๊ฒ€์ฆ(MAPE)์œผ๋กœ ํ‰๊ฐ€ํ•˜๊ณ , ํ”ผ์ฒ˜ ์ค‘์š”๋„๋กœ ์ฐจ์› ์ถ•์†Œ ํ›„๋ณด๋ฅผ ๊ณ ๋ฅธ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "from sklearn.model_selection import TimeSeriesSplit\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด ๋ถ€ํ’ˆ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€ํ’ˆยท์žฌ๊ณ ยท๊ทœ๊ฒฉ ๊ฐ’ ์•„๋‹˜)\n", "# ํ๊ธฐ์œจ์ด ๋†’์€ ์ˆ˜๋ฆฌ๋ถ€์† ์ค‘ 3D ํ”„๋ฆฐํŒ…์œผ๋กœ ๋Œ€์ฒด ์ œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์ƒ ๋ถ€ํ’ˆ ์ž๋ฃŒ๋ฅผ ๋งŒ๋“ ๋‹ค.\n", "# ์†Œ์žฌ ๋ฌผ์„ฑยท์น˜์ˆ˜ยทํ•˜์ค‘์€ ๊ต์œก์šฉ์œผ๋กœ ๋‹จ์ˆœํ™”ํ•œ ๊ฐ’์ด๋ฉฐ ์‹ค์ œ ์‹œํ—˜ ์„ฑ์ ์„œ๋‚˜ ๊ตฐ ๊ทœ๊ฒฉ ๊ฐ’๊ณผ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "MAT = { # ์†Œ์žฌ: (๊ณต์ •, ๊ธฐ์ค€ ์ธ์žฅ๊ฐ•๋„ MPa, ๋ฐ€๋„ g/cm3, ์†Œ์žฌ ๋‹จ๊ฐ€ ๋งŒ์›/kg, ์ถœ๋ ฅ๋น„ ๋งŒ์›/h, ์ตœ์†Œ ๋ฒฝ๋‘๊ป˜ mm, ์ถœ๋ ฅ ์†๋„ cm3/h)\n", " \"PA12\": (\"polymer\", 45, 1.0, 8, 3, 0.8, 18), \"PETG\": (\"polymer\", 50, 1.27, 5, 2, 0.8, 15),\n", " \"CF-PA\": (\"polymer\", 70, 1.2, 14, 3, 1.0, 12), \"AlSi10Mg\": (\"metal\", 350, 2.67, 30, 25, 0.5, 4),\n", " \"SS316L\": (\"metal\", 520, 7.9, 28, 25, 0.6, 3.5), \"Ti64\": (\"metal\", 900, 4.4, 90, 30, 0.6, 3)}\n", "CATS = [\"bracket\", \"housing\", \"clip\", \"gear\", \"duct\", \"handle\"]\n", "CAT_SCRAP = {\"bracket\": 0.04, \"housing\": 0.05, \"clip\": 0.12, \"gear\": 0.09, \"duct\": 0.08, \"handle\": 0.10} # ๋ถ€ํ’ˆ๊ตฐ๋ณ„ ๊ธฐ๋ณธ ์›” ํ๊ธฐ์œจ\n", "BUILD_MM = {\"polymer\": 300, \"metal\": 200} # ๊ฐ€์ƒ ์ถœ๋ ฅ ๊ฐ€๋Šฅ ์ตœ๋Œ€ ์น˜์ˆ˜\n", "\n", "\n", "def make_parts(n=300, seed=0):\n", " \"\"\"๊ฐ€์ƒ ๋ถ€ํ’ˆ n์ข…: ํ˜•์ƒ(๋ถ€ํ”ผยท๋ฒฝ๋‘๊ป˜ยท๋Œ์ถœ ๋น„์œจ), ์†Œ์žฌ, ์‚ฌ์šฉ ํ•˜์ค‘, ํ๊ธฐ์œจ๊ณผ ์กฐ๋‹ฌ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " mat = rng.choice(list(MAT), n, p=[0.2, 0.15, 0.1, 0.2, 0.2, 0.15])\n", " proc = np.array([MAT[m][0] for m in mat])\n", " vol = np.clip(rng.lognormal(np.log(30), 0.9, n), 2, 800)\n", " aspect = rng.lognormal(0.3, 0.4, n)\n", " max_dim = 10 * vol ** (1 / 3) * aspect ** (1 / 3) * rng.uniform(1.0, 1.6, n)\n", " wall = np.clip(rng.lognormal(np.log(2.0), 0.6, n), 0.3, 8)\n", " ovh = rng.beta(2, 5, n)\n", " is_poly = proc == \"polymer\"\n", " df = pd.DataFrame({\n", " \"part_id\": [f\"P{i:03d}\" for i in range(n)], \"cat\": rng.choice(CATS, n, p=[0.2, 0.15, 0.2, 0.1, 0.15, 0.2]), \"material\": mat, \"process\": proc,\n", " \"volume_cm3\": vol, \"max_dim_mm\": max_dim, \"area_cm2\": 6 * vol ** (2 / 3) * rng.uniform(1.0, 2.2, n), \"min_wall_mm\": wall, \"overhang\": ovh,\n", " \"n_holes\": rng.poisson(2.0, n), \"thread\": rng.random(n) < 0.25, \"orient_deg\": rng.uniform(0, 90, n),\n", " \"layer_mm\": np.where(is_poly, rng.choice([0.1, 0.2, 0.3], n), rng.choice([0.03, 0.05], n)),\n", " \"infill\": np.where(is_poly, rng.choice([40, 60, 80, 100], n, p=[0.15, 0.3, 0.3, 0.25]), 100),\n", " \"load_N\": np.clip(rng.lognormal(np.where(is_poly, np.log(230), np.log(2300)), 0.7, n), 10, 20000), \"temp_C\": rng.uniform(-20, 90, n),\n", " \"crit\": rng.choice([1, 2, 3], n, p=[0.35, 0.4, 0.25]), \"cert\": rng.choice([0, 1], n, p=[0.45, 0.55]), # ์†Œ์žฌ ์ธ์ฆ์„œ ๋ณด์œ \n", " \"import_part\": rng.random(n) < 0.4})\n", " df[\"scrap_rate\"] = np.clip(df[\"cat\"].map(CAT_SCRAP) * rng.lognormal(0, 0.35, n) * (1 + 0.5 * (df[\"load_N\"] > 800)) * (1 + 0.3 * (df[\"temp_C\"] > 60)), 0.005, 0.5)\n", " df[\"lead_days\"] = np.clip(rng.lognormal(np.log(25), 0.5, n) * np.where(df[\"import_part\"], 2.2, 1.0), 5, 300)\n", " # ---- ์ˆจ์€ ์ง„์‹ค๊ฐ’(์‹ค์ œ ์‹œํ—˜ ๊ฒฐ๊ณผ์— ํ•ด๋‹น): ๋ชจ๋ธ์€ ์œ„ ์ž…๋ ฅ์œผ๋กœ ์ด๊ฒƒ์„ ์˜ˆ์ธกํ•œ๋‹ค ----\n", " eff = np.where(is_poly, 0.55 + 0.45 * df[\"infill\"] / 100, 1 - 0.3 * ovh * 0.2)\n", " aniso = np.where(is_poly, 1 - 0.3 * np.sin(np.radians(df[\"orient_deg\"])) ** 2, 1 - 0.08 * np.sin(np.radians(df[\"orient_deg\"])))\n", " heat = np.where(is_poly, 1 - 0.35 * np.clip((df[\"temp_C\"] - 40) / 80, 0, 1), 0.98)\n", " base = np.array([MAT[m][1] for m in mat])\n", " df[\"strength_mpa\"] = base * eff * aniso * heat * rng.lognormal(0, 0.06, n)\n", " cross = df[\"min_wall_mm\"] * np.sqrt(df[\"max_dim_mm\"]) * 1.0 # ํ•˜์ค‘ ์ง€์ง€ ๋‹จ๋ฉด์  mm2(๋‹จ์ˆœํ™”)\n", " df[\"stress_mpa\"] = df[\"load_N\"] / cross\n", " df[\"safety\"] = df[\"strength_mpa\"] / df[\"stress_mpa\"]\n", " ratio = np.clip(df[\"stress_mpa\"] / df[\"strength_mpa\"], 0, 1.5)\n", " df[\"log_cycles\"] = np.clip(7.5 - 6.0 * ratio + rng.normal(0, 0.25, n), 2, 7.5) # ํ”ผ๋กœ ์ˆ˜๋ช… log10(ํšŒ)\n", " support = 1 + 0.5 * ovh\n", " df[\"build_h\"] = vol * support / np.array([MAT[m][6] for m in mat]) + 0.5\n", " mass_kg = vol * np.array([MAT[m][2] for m in mat]) * support / 1000\n", " df[\"print_cost\"] = mass_kg * np.array([MAT[m][3] for m in mat]) + df[\"build_h\"] * np.array([MAT[m][4] for m in mat]) + 3\n", " df[\"buy_cost\"] = df[\"print_cost\"] * rng.lognormal(0.25, 0.45, n) + 5 # ๊ตฌ๋งค ๋‹จ๊ฐ€(๋งŒ์›, ๊ฐ€์ƒ)\n", " wall_ok = df[\"min_wall_mm\"] >= np.array([MAT[m][5] for m in mat]) * rng.uniform(0.95, 1.25, n)\n", " dim_ok = df[\"max_dim_mm\"] <= np.where(is_poly, BUILD_MM[\"polymer\"], BUILD_MM[\"metal\"])\n", " warp = (~is_poly) & (df[\"max_dim_mm\"] > 140) & (df[\"overhang\"] > 0.3) # ๊ธˆ์† ๋Œ€ํ˜• ์˜ค๋ฒ„ํ–‰ ๋ณ€ํ˜•\n", " thr_bad = df[\"thread\"] & (df[\"min_wall_mm\"] < 1.5) & is_poly # ์–‡์€ ๋‚˜์‚ฌ๋ถ€ ํŒŒ์†\n", " df[\"printable\"] = wall_ok & dim_ok & (ovh < 0.5) & ~warp & ~thr_bad\n", " risk = np.clip(1 - df[\"safety\"] / 3, 0, 1)\n", " df[\"conform\"] = np.clip(100 * (0.45 * np.minimum(df[\"safety\"] / 3, 1) + 0.25 * np.clip((df[\"log_cycles\"] - 3) / 4.5, 0, 1)\n", " + 0.15 * (1 - np.clip(ovh * 1.2, 0, 1)) + 0.15 * df[\"cert\"]) - 8 * risk * df[\"crit\"] / 3 + rng.normal(0, 2, n), 0, 100)\n", " df[\"demand_month\"] = rng.poisson(np.where(df[\"cat\"].isin([\"clip\", \"handle\"]), 40, 15)) + 1 # ์›” ์†Œ์š”์ˆ˜๋Ÿ‰(๊ฐ€์ƒ)\n", " df[\"scrap_cost\"] = df[\"scrap_rate\"] * df[\"demand_month\"] * df[\"buy_cost\"] # ์›” ํ๊ธฐ ์†์‹ค์•ก(๋งŒ์›, ๊ฐ€์ƒ)\n", " return df\n", "\n", "# ---- ๋‹จ์ˆœํ™”ํ•œ ํ•ด์„(FEA ๋Œ€์šฉ) ๊ฐ’๊ณผ ํŒŒ์ƒ ํ”ผ์ฒ˜: ์ž…๋ ฅ ์ •๋ณด๋งŒ์œผ๋กœ ๊ณ„์‚ฐํ•œ๋‹ค(์‹œํ—˜ ๊ฒฐ๊ณผ ๋ˆ„์„ค ์—†์Œ) ----\n", "def make_features(d):\n", " \"\"\"๊ฐ„์ด ์‘๋ ฅ ํ•ด์„(์‘๋ ฅ์ง‘์ค‘๊ณ„์ˆ˜ ํฌํ•จ)๊ณผ ์†Œ์žฌยท๊ณต์ • ์ง€์‹์—์„œ ๋งŒ๋“  ํŒŒ์ƒ ํ”ผ์ฒ˜ 16๊ฐœ.\"\"\"\n", " X = pd.DataFrame(index=d.index)\n", " base = d[\"material\"].map(lambda m: MAT[m][1]).astype(float)\n", " X[\"base_strength\"] = base\n", " X[\"is_metal\"] = (d[\"process\"] == \"metal\").astype(int)\n", " X[\"cross_mm2\"] = d[\"min_wall_mm\"] * np.sqrt(d[\"max_dim_mm\"])\n", " X[\"kt\"] = 1 + 0.15 * d[\"n_holes\"] + 0.5 * d[\"thread\"].astype(int) # ๊ตฌ๋ฉยท๋‚˜์‚ฌ๋ถ€ ์‘๋ ฅ์ง‘์ค‘๊ณ„์ˆ˜(๊ฐ„์ด)\n", " X[\"fea_stress\"] = d[\"load_N\"] / X[\"cross_mm2\"] * X[\"kt\"] # ๊ฐ„์ด ํ•ด์„ ์‘๋ ฅ(MPa)\n", " X[\"stress_ratio\"] = X[\"fea_stress\"] / base # ๊ธฐ์ค€๊ฐ•๋„ ๋Œ€๋น„ ์‘๋ ฅ๋น„\n", " X[\"infill_eff\"] = np.where(X[\"is_metal\"] == 1, 1.0, 0.55 + 0.45 * d[\"infill\"] / 100)\n", " X[\"orient_sin2\"] = np.sin(np.radians(d[\"orient_deg\"])) ** 2\n", " X[\"temp_derate\"] = np.where(X[\"is_metal\"] == 1, 0.0, np.clip((d[\"temp_C\"] - 40) / 80, 0, 1))\n", " X[\"overhang_layer\"] = d[\"overhang\"] * d[\"layer_mm\"]\n", " X[\"slender\"] = d[\"max_dim_mm\"] / d[\"min_wall_mm\"]\n", " X[\"surf_vol\"] = d[\"area_cm2\"] / d[\"volume_cm3\"]\n", " X[\"wall_ratio\"] = d[\"min_wall_mm\"] / d[\"material\"].map(lambda m: MAT[m][5])\n", " X[\"load_per_vol\"] = d[\"load_N\"] / d[\"volume_cm3\"]\n", " X[\"cert\"] = d[\"cert\"].astype(int)\n", " X[\"crit\"] = d[\"crit\"]\n", " return X\n", "\n", "parts = make_parts(900, seed=7) # ์ธ๋ฑ์Šค ์ˆœ์„œ = ์‹œํ—˜ ์ˆ˜ํ–‰ ์ˆœ์„œ(์•ž์ด ๊ณผ๊ฑฐ)\n", "X = make_features(parts)\n", "print(f\"ํŒŒ์ƒ ํ”ผ์ฒ˜ {X.shape[1]}๊ฐœ ์ƒ์„ฑ (๋ชฉํ‘œ 10๊ฐœ ์ด์ƒ: {'๋‹ฌ์„ฑ' if X.shape[1] >= 10 else '๋ฏธ๋‹ฌ'})\")\n", "mape = lambda a, p: float(np.mean(np.abs(a - p) / np.abs(a)))\n", "gb = lambda: GradientBoostingRegressor(n_estimators=150, max_depth=3, learning_rate=0.05, random_state=0)\n", "\n", "# 1) ์‹œ๊ณ„์—ด ๊ต์ฐจ๊ฒ€์ฆ: ๊ณผ๊ฑฐ ์‹œํ—˜์œผ๋กœ ํ•™์Šต -> ์ดํ›„ ์‹œํ—˜ ์˜ˆ์ธก (5๋ถ„ํ• )\n", "rows = []\n", "for i, (tr, te) in enumerate(TimeSeriesSplit(5).split(X), 1):\n", " ys, yf, bs = parts[\"strength_mpa\"], parts[\"log_cycles\"], X[\"base_strength\"]\n", " base = parts.iloc[tr].groupby(\"material\")[\"strength_mpa\"].mean() # ๊ธฐ์ค€๋ชจ๋ธ: ์†Œ์žฌ๋ณ„ ํ‰๊ท  ๊ฐ•๋„\n", " p0 = parts.iloc[te][\"material\"].map(base)\n", " p1 = gb().fit(X.iloc[tr], (ys / bs).iloc[tr]).predict(X.iloc[te]) * bs.iloc[te] # ์†Œ์žฌ ๊ธฐ์ค€๊ฐ•๋„ ๋Œ€๋น„ ์ €๊ฐ ๊ณ„์ˆ˜๋ฅผ ํ•™์Šต\n", " q1 = gb().fit(X.iloc[tr], yf.iloc[tr]).predict(X.iloc[te])\n", " rows.append({\"๋ถ„ํ• \": i, \"ํ•™์Šต\": len(tr), \"๊ฒ€์ฆ\": len(te), \"๊ฐ•๋„ ๊ธฐ์ค€ MAPE\": round(mape(ys.iloc[te], p0), 3), \"๊ฐ•๋„ GBM MAPE\": round(mape(ys.iloc[te], p1), 3),\n", " \"ํ”ผ๋กœ์ˆ˜๋ช… MAE(log10)\": round(float(np.mean(np.abs(yf.iloc[te] - q1))), 2)})\n", "cv = pd.DataFrame(rows)\n", "print(cv.to_string(index=False))\n", "m_b, m_g = cv[\"๊ฐ•๋„ ๊ธฐ์ค€ MAPE\"].mean(), cv[\"๊ฐ•๋„ GBM MAPE\"].mean()\n", "print(f\"ํ‰๊ท  ์ธ์žฅ๊ฐ•๋„ MAPE: ๊ธฐ์ค€๋ชจ๋ธ(์†Œ์žฌ๋ณ„ ํ‰๊ท ) {m_b:.1%}, GBM {m_g:.1%} (๋ชฉํ‘œ 25% ์ดํ•˜: {'๋‹ฌ์„ฑ' if m_g <= 0.25 else '๋ฏธ๋‹ฌ'})\")\n", "print(f\"ํ”ผ๋กœ ์ˆ˜๋ช… ์˜ˆ์ธก ํ‰๊ท  ์˜ค์ฐจ {cv['ํ”ผ๋กœ์ˆ˜๋ช… MAE(log10)'].mean():.2f} ์ž๋ฆฟ์ˆ˜(log10 ํšŒ)\")\n", "\n", "# 2) ๋งˆ์ง€๋ง‰ 20%๋ฅผ ์‹ ๋ขฐ์„ฑ Hold-out์œผ๋กœ: ์˜ˆ์ธก ์•ˆ์ „์œจ๋กœ '๋Œ€์ฒด ์ œ์ž‘ ์‹ ๋ขฐ์„ฑ ์ถฉ์กฑ(์•ˆ์ „์œจ 1.5 ์ด์ƒ)' ํŒ๋ณ„\n", "cut = int(len(parts) * 0.8)\n", "ratio = parts[\"strength_mpa\"] / X[\"base_strength\"]\n", "ms = gb().fit(X.iloc[:cut], ratio.iloc[:cut])\n", "te = parts.iloc[cut:]\n", "p_all = ms.predict(X.iloc[cut:]) * X.iloc[cut:][\"base_strength\"]\n", "pred_sf = p_all / (te[\"load_N\"] / X.iloc[cut:][\"cross_mm2\"])\n", "ok_true, ok_pred = te[\"safety\"] >= 1.5, pred_sf >= 1.5\n", "print(f\"\\n[์•ˆ์ „์œจ 1.5 ์ด์ƒ ํŒ๋ณ„] ์ •ํ™•๋„ {(ok_true == ok_pred).mean():.1%}, ์œ„ํ—˜ ์˜คํŒ(๋ถˆ์ถฉ๋ถ„ํ•œ๋ฐ ์ถฉ์กฑ์ด๋ผ ํŒ์ •) {int((~ok_true & ok_pred).sum())}๊ฑด / {len(te)}๊ฑด\")\n", "print(\"๊ฐ„์ด ํ•ด์„ ์‘๋ ฅ๋งŒ์œผ๋กœ ๋งŒ๋“  ํŒ๋ณ„์˜ ์ •ํ™•๋„:\", f\"{((X.iloc[cut:]['stress_ratio'] < 1 / 1.5) == ok_true).mean():.1%}\")\n", "\n", "# 3) ํ”ผ์ฒ˜ ์ค‘์š”๋„์™€ ์ฐจ์› ์ถ•์†Œ ํ›„๋ณด\n", "model = gb().fit(X, ratio)\n", "imp = pd.Series(model.feature_importances_, X.columns).sort_values(ascending=False)\n", "print(\"\\nํ”ผ์ฒ˜ ์ค‘์š”๋„ ์ƒ์œ„ 5(๊ฐ•๋„ ์ €๊ฐ ๊ณ„์ˆ˜ ๋ชจ๋ธ):\", \", \".join(f\"{k}({v:.2f})\" for k, v in imp.head(5).items()))\n", "print(\"์ค‘์š”๋„ 0.01 ๋ฏธ๋งŒ ํ”ผ์ฒ˜(์ฐจ์› ์ถ•์†Œ ํ›„๋ณด):\", list(imp[imp < 0.01].index))\n", "print(\"๋‹จ, ๋‹จ๋ฉด์ ยท์‘๋ ฅ์ง‘์ค‘ยท์‘๋ ฅ๋น„๋Š” ๊ฐ•๋„ ๋ชจ๋ธ์—์„œ๋Š” ์•ฝํ•˜์ง€๋งŒ ์•ˆ์ „์œจ(ํ•˜์ค‘ ์ชฝ) ๊ณ„์‚ฐ์— ํ•„์š”ํ•˜๋ฏ€๋กœ ์‚ญ์ œํ•˜์ง€ ์•Š๊ณ  ๋ชจ๋ธ๋ณ„๋กœ ๋‚˜๋ˆ  ์“ด๋‹ค.\")\n", "print(\"ํ”ผ์ฒ˜ ์„ค๊ณ„ ๊ทผ๊ฑฐ: ์ถฉ์ „์œจยท์ ์ธต ๋ฐฉํ–ฅยท์†Œ์žฌยท์˜จ๋„ ์ €ํ•˜๋Š” ๊ฐ•๋„๋ฅผ ๊นŽ๋Š” ์š”์ธ์ด๊ณ  ๋‹จ๋ฉด์ ยท๊ตฌ๋ฉยท๋‚˜์‚ฌ๋ถ€๋Š” ์‘๋ ฅ์„ ํ‚ค์šฐ๋Š” ์š”์ธ์ด๋ผ ์ •๋น„ ํ˜„์žฅ์—์„œ๋„ ์„ค๋ช…์ด ๋œ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n", "imp.head(10)[::-1].plot.barh(ax=ax[0]); ax[0].set_title(\"Feature importance (strength knock-down)\")\n", "ax[1].scatter(te[\"strength_mpa\"], p_all, s=10); ax[1].plot([0, 1000], [0, 1000], \"r--\")\n", "ax[1].set_xlabel(\"measured strength (MPa)\"); ax[1].set_ylabel(\"predicted\"); ax[1].set_title(\"Hold-out: strength prediction\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 04 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "ํŒŒ์ƒ ํ”ผ์ฒ˜ 16๊ฐœ ์ƒ์„ฑ (๋ชฉํ‘œ 10๊ฐœ ์ด์ƒ: ๋‹ฌ์„ฑ)\n", " ๋ถ„ํ•  ํ•™์Šต ๊ฒ€์ฆ ๊ฐ•๋„ ๊ธฐ์ค€ MAPE ๊ฐ•๋„ GBM MAPE ํ”ผ๋กœ์ˆ˜๋ช… MAE(log10)\n", " 1 150 150 0.100 0.066 0.39\n", " 2 300 150 0.102 0.057 0.27\n", " 3 450 150 0.121 0.056 0.28\n", " 4 600 150 0.095 0.052 0.32\n", " 5 750 150 0.118 0.062 0.26\n", "ํ‰๊ท  ์ธ์žฅ๊ฐ•๋„ MAPE: ๊ธฐ์ค€๋ชจ๋ธ(์†Œ์žฌ๋ณ„ ํ‰๊ท ) 10.7%, GBM 5.9% (๋ชฉํ‘œ 25% ์ดํ•˜: ๋‹ฌ์„ฑ)\n", "ํ”ผ๋กœ ์ˆ˜๋ช… ์˜ˆ์ธก ํ‰๊ท  ์˜ค์ฐจ 0.30 ์ž๋ฆฟ์ˆ˜(log10 ํšŒ)\n", "\n", "[์•ˆ์ „์œจ 1.5 ์ด์ƒ ํŒ๋ณ„] ์ •ํ™•๋„ 97.8%, ์œ„ํ—˜ ์˜คํŒ(๋ถˆ์ถฉ๋ถ„ํ•œ๋ฐ ์ถฉ์กฑ์ด๋ผ ํŒ์ •) 2๊ฑด / 180๊ฑด\n", "๊ฐ„์ด ํ•ด์„ ์‘๋ ฅ๋งŒ์œผ๋กœ ๋งŒ๋“  ํŒ๋ณ„์˜ ์ •ํ™•๋„: 93.9%\n", "\n", "ํ”ผ์ฒ˜ ์ค‘์š”๋„ ์ƒ์œ„ 5(๊ฐ•๋„ ์ €๊ฐ ๊ณ„์ˆ˜ ๋ชจ๋ธ): infill_eff(0.33), orient_sin2(0.22), is_metal(0.19), base_strength(0.13), temp_derate(0.11)\n", "์ค‘์š”๋„ 0.01 ๋ฏธ๋งŒ ํ”ผ์ฒ˜(์ฐจ์› ์ถ•์†Œ ํ›„๋ณด): ['overhang_layer', 'fea_stress', 'load_per_vol', 'surf_vol', 'stress_ratio', 'slender', 'kt', 'wall_ratio', 'crit', 'cross_mm2', 'cert']\n", "๋‹จ, ๋‹จ๋ฉด์ ยท์‘๋ ฅ์ง‘์ค‘ยท์‘๋ ฅ๋น„๋Š” ๊ฐ•๋„ ๋ชจ๋ธ์—์„œ๋Š” ์•ฝํ•˜์ง€๋งŒ ์•ˆ์ „์œจ(ํ•˜์ค‘ ์ชฝ) ๊ณ„์‚ฐ์— ํ•„์š”ํ•˜๋ฏ€๋กœ ์‚ญ์ œํ•˜์ง€ ์•Š๊ณ  ๋ชจ๋ธ๋ณ„๋กœ ๋‚˜๋ˆ  ์“ด๋‹ค.\n", "ํ”ผ์ฒ˜ ์„ค๊ณ„ ๊ทผ๊ฑฐ: ์ถฉ์ „์œจยท์ ์ธต ๋ฐฉํ–ฅยท์†Œ์žฌยท์˜จ๋„ ์ €ํ•˜๋Š” ๊ฐ•๋„๋ฅผ ๊นŽ๋Š” ์š”์ธ์ด๊ณ  ๋‹จ๋ฉด์ ยท๊ตฌ๋ฉยท๋‚˜์‚ฌ๋ถ€๋Š” ์‘๋ ฅ์„ ํ‚ค์šฐ๋Š” ์š”์ธ์ด๋ผ ์ •๋น„ ํ˜„์žฅ์—์„œ๋„ ์„ค๋ช…์ด ๋œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 05. ๊ธด๊ธ‰ ์•ผ์ „ ์ œ์ž‘ ๋ถ€ํ’ˆ์˜ ๊ตฐ์‚ฌ ๊ทœ๊ฒฉ(MIL-STD) ๋ถ€ํ•ฉ์„ฑ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์‚ฐ์ถœ ์—”์ง„ ๊ฐœ๋ฐœ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์•ผ์ „ ์ œ์ž‘ ๋ถ€ํ’ˆ์˜ ๊ตฐ์‚ฌ ๊ทœ๊ฒฉ(MIL-STD) ๋ถ€ํ•ฉ์„ฑ ์‹ ๋ขฐ๋„ ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๊ธด๊ธ‰ ์•ผ์ „ ์ œ์ž‘ ๋ถ€ํ’ˆ์˜ ๊ตฐ ๊ทœ๊ฒฉ ๋ถ€ํ•ฉ์„ฑ์„ 0~100์ ์œผ๋กœ ํ™˜์‚ฐํ•˜๋Š” ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์‚ฐ์ถœ ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค(์‹ค์ œ ๊ทœ๊ฒฉ ์›๋ฌธ ๋Œ€์‹  ๊ฐ€์ƒ ๊ธฐ์ค€ ์ ์ˆ˜ ์‚ฌ์šฉ).** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์•ˆ์ „์œจยทํ”ผ๋กœ ์ˆ˜๋ช…ยท๋Œ์ถœ ์œ„ํ—˜ยท์†Œ์žฌ ์ธ์ฆ์„œ๋ฅผ ๋ฐ˜์˜ํ•œ ๊ฐ€์ƒ ๋ถ€ํ•ฉ ์ ์ˆ˜๋ฅผ ์•™์ƒ๋ธ” ํšŒ๊ท€(GBM+RandomForest)๋กœ ์˜ˆ์ธกํ•ด ์› ์ž…๋ ฅ 5๊ฐœ ์„ ํ˜• ๊ธฐ์ค€๋ชจ๋ธ๊ณผ Hold-out์—์„œ MAEยทRMSEยทR2๋ฅผ ๋น„๊ต ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/mnt06_advanced_model.py` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๋น„์ •ํ˜•/๋ถˆ๊ทœ์น™ ์ˆ˜์š”์— ๋Œ€ํ•œ ๋ชจ๋ธ์˜ ์ ์‘๋ ฅ ๋ฐ ์˜ˆ์ธก ์•ˆ์ •์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๊ธฐ์ค€ ๋ชจ๋ธ ๋Œ€๋น„ ์˜ค์ฐจ์œจ(MAE/RMSE) 20% ์ด์ƒ ๊ฐœ์„ , R2 Score 0.85 ์ด์ƒ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ๋ชจ๋ธ ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ์™„๋ฃŒ ๋ฐ ์ตœ์  ๋ชจ๋ธ ๊ฐ€์ค‘์น˜ ์ €์žฅ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: Hold-out ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ์…‹ ๋Œ€์ƒ ์ตœ์ข… ์„ฑ๋Šฅ ํ‰๊ฐ€\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๊ธด๊ธ‰ ์•ผ์ „ ์ œ์ž‘ ๋ถ€ํ’ˆ์˜ ๊ตฐ ๊ทœ๊ฒฉ ๋ถ€ํ•ฉ ์‹ ๋ขฐ๋„ ์ ์ˆ˜(0~100, ๊ฐ€์ƒ ๊ธฐ์ค€)๋ฅผ ์•™์ƒ๋ธ” ํšŒ๊ท€(GBM+RandomForest)๋กœ ์‚ฐ์ถœํ•˜๋Š” ์—”์ง„์„ ๋งŒ๋“ค๊ณ , Hold-out ๋ถ€ํ’ˆ์—์„œ ์› ์ž…๋ ฅ 5๊ฐœ ์„ ํ˜• ๊ธฐ์ค€๋ชจ๋ธ ๋Œ€๋น„ MAE/RMSE ๊ฐœ์„ ์œจ๊ณผ R2, ์ ์ˆ˜ ๊ตฌ๊ฐ„๋ณ„ ์‹ค์ œ ๋ถ€ํ•ฉ๋ฅ (๋ณด์ • ์ƒํƒœ)๊ณผ ์˜ˆ์ธก ๋ถˆํ™•์‹ค์„ฑ์„ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.ensemble import GradientBoostingRegressor, RandomForestRegressor\n", "from sklearn.linear_model import LinearRegression\n", "from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score, roc_auc_score\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด ๋ถ€ํ’ˆ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€ํ’ˆยท์žฌ๊ณ ยท๊ทœ๊ฒฉ ๊ฐ’ ์•„๋‹˜)\n", "# ํ๊ธฐ์œจ์ด ๋†’์€ ์ˆ˜๋ฆฌ๋ถ€์† ์ค‘ 3D ํ”„๋ฆฐํŒ…์œผ๋กœ ๋Œ€์ฒด ์ œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์ƒ ๋ถ€ํ’ˆ ์ž๋ฃŒ๋ฅผ ๋งŒ๋“ ๋‹ค.\n", "# ์†Œ์žฌ ๋ฌผ์„ฑยท์น˜์ˆ˜ยทํ•˜์ค‘์€ ๊ต์œก์šฉ์œผ๋กœ ๋‹จ์ˆœํ™”ํ•œ ๊ฐ’์ด๋ฉฐ ์‹ค์ œ ์‹œํ—˜ ์„ฑ์ ์„œ๋‚˜ ๊ตฐ ๊ทœ๊ฒฉ ๊ฐ’๊ณผ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "MAT = { # ์†Œ์žฌ: (๊ณต์ •, ๊ธฐ์ค€ ์ธ์žฅ๊ฐ•๋„ MPa, ๋ฐ€๋„ g/cm3, ์†Œ์žฌ ๋‹จ๊ฐ€ ๋งŒ์›/kg, ์ถœ๋ ฅ๋น„ ๋งŒ์›/h, ์ตœ์†Œ ๋ฒฝ๋‘๊ป˜ mm, ์ถœ๋ ฅ ์†๋„ cm3/h)\n", " \"PA12\": (\"polymer\", 45, 1.0, 8, 3, 0.8, 18), \"PETG\": (\"polymer\", 50, 1.27, 5, 2, 0.8, 15),\n", " \"CF-PA\": (\"polymer\", 70, 1.2, 14, 3, 1.0, 12), \"AlSi10Mg\": (\"metal\", 350, 2.67, 30, 25, 0.5, 4),\n", " \"SS316L\": (\"metal\", 520, 7.9, 28, 25, 0.6, 3.5), \"Ti64\": (\"metal\", 900, 4.4, 90, 30, 0.6, 3)}\n", "CATS = [\"bracket\", \"housing\", \"clip\", \"gear\", \"duct\", \"handle\"]\n", "CAT_SCRAP = {\"bracket\": 0.04, \"housing\": 0.05, \"clip\": 0.12, \"gear\": 0.09, \"duct\": 0.08, \"handle\": 0.10} # ๋ถ€ํ’ˆ๊ตฐ๋ณ„ ๊ธฐ๋ณธ ์›” ํ๊ธฐ์œจ\n", "BUILD_MM = {\"polymer\": 300, \"metal\": 200} # ๊ฐ€์ƒ ์ถœ๋ ฅ ๊ฐ€๋Šฅ ์ตœ๋Œ€ ์น˜์ˆ˜\n", "\n", "\n", "def make_parts(n=300, seed=0):\n", " \"\"\"๊ฐ€์ƒ ๋ถ€ํ’ˆ n์ข…: ํ˜•์ƒ(๋ถ€ํ”ผยท๋ฒฝ๋‘๊ป˜ยท๋Œ์ถœ ๋น„์œจ), ์†Œ์žฌ, ์‚ฌ์šฉ ํ•˜์ค‘, ํ๊ธฐ์œจ๊ณผ ์กฐ๋‹ฌ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " mat = rng.choice(list(MAT), n, p=[0.2, 0.15, 0.1, 0.2, 0.2, 0.15])\n", " proc = np.array([MAT[m][0] for m in mat])\n", " vol = np.clip(rng.lognormal(np.log(30), 0.9, n), 2, 800)\n", " aspect = rng.lognormal(0.3, 0.4, n)\n", " max_dim = 10 * vol ** (1 / 3) * aspect ** (1 / 3) * rng.uniform(1.0, 1.6, n)\n", " wall = np.clip(rng.lognormal(np.log(2.0), 0.6, n), 0.3, 8)\n", " ovh = rng.beta(2, 5, n)\n", " is_poly = proc == \"polymer\"\n", " df = pd.DataFrame({\n", " \"part_id\": [f\"P{i:03d}\" for i in range(n)], \"cat\": rng.choice(CATS, n, p=[0.2, 0.15, 0.2, 0.1, 0.15, 0.2]), \"material\": mat, \"process\": proc,\n", " \"volume_cm3\": vol, \"max_dim_mm\": max_dim, \"area_cm2\": 6 * vol ** (2 / 3) * rng.uniform(1.0, 2.2, n), \"min_wall_mm\": wall, \"overhang\": ovh,\n", " \"n_holes\": rng.poisson(2.0, n), \"thread\": rng.random(n) < 0.25, \"orient_deg\": rng.uniform(0, 90, n),\n", " \"layer_mm\": np.where(is_poly, rng.choice([0.1, 0.2, 0.3], n), rng.choice([0.03, 0.05], n)),\n", " \"infill\": np.where(is_poly, rng.choice([40, 60, 80, 100], n, p=[0.15, 0.3, 0.3, 0.25]), 100),\n", " \"load_N\": np.clip(rng.lognormal(np.where(is_poly, np.log(230), np.log(2300)), 0.7, n), 10, 20000), \"temp_C\": rng.uniform(-20, 90, n),\n", " \"crit\": rng.choice([1, 2, 3], n, p=[0.35, 0.4, 0.25]), \"cert\": rng.choice([0, 1], n, p=[0.45, 0.55]), # ์†Œ์žฌ ์ธ์ฆ์„œ ๋ณด์œ \n", " \"import_part\": rng.random(n) < 0.4})\n", " df[\"scrap_rate\"] = np.clip(df[\"cat\"].map(CAT_SCRAP) * rng.lognormal(0, 0.35, n) * (1 + 0.5 * (df[\"load_N\"] > 800)) * (1 + 0.3 * (df[\"temp_C\"] > 60)), 0.005, 0.5)\n", " df[\"lead_days\"] = np.clip(rng.lognormal(np.log(25), 0.5, n) * np.where(df[\"import_part\"], 2.2, 1.0), 5, 300)\n", " # ---- ์ˆจ์€ ์ง„์‹ค๊ฐ’(์‹ค์ œ ์‹œํ—˜ ๊ฒฐ๊ณผ์— ํ•ด๋‹น): ๋ชจ๋ธ์€ ์œ„ ์ž…๋ ฅ์œผ๋กœ ์ด๊ฒƒ์„ ์˜ˆ์ธกํ•œ๋‹ค ----\n", " eff = np.where(is_poly, 0.55 + 0.45 * df[\"infill\"] / 100, 1 - 0.3 * ovh * 0.2)\n", " aniso = np.where(is_poly, 1 - 0.3 * np.sin(np.radians(df[\"orient_deg\"])) ** 2, 1 - 0.08 * np.sin(np.radians(df[\"orient_deg\"])))\n", " heat = np.where(is_poly, 1 - 0.35 * np.clip((df[\"temp_C\"] - 40) / 80, 0, 1), 0.98)\n", " base = np.array([MAT[m][1] for m in mat])\n", " df[\"strength_mpa\"] = base * eff * aniso * heat * rng.lognormal(0, 0.06, n)\n", " cross = df[\"min_wall_mm\"] * np.sqrt(df[\"max_dim_mm\"]) * 1.0 # ํ•˜์ค‘ ์ง€์ง€ ๋‹จ๋ฉด์  mm2(๋‹จ์ˆœํ™”)\n", " df[\"stress_mpa\"] = df[\"load_N\"] / cross\n", " df[\"safety\"] = df[\"strength_mpa\"] / df[\"stress_mpa\"]\n", " ratio = np.clip(df[\"stress_mpa\"] / df[\"strength_mpa\"], 0, 1.5)\n", " df[\"log_cycles\"] = np.clip(7.5 - 6.0 * ratio + rng.normal(0, 0.25, n), 2, 7.5) # ํ”ผ๋กœ ์ˆ˜๋ช… log10(ํšŒ)\n", " support = 1 + 0.5 * ovh\n", " df[\"build_h\"] = vol * support / np.array([MAT[m][6] for m in mat]) + 0.5\n", " mass_kg = vol * np.array([MAT[m][2] for m in mat]) * support / 1000\n", " df[\"print_cost\"] = mass_kg * np.array([MAT[m][3] for m in mat]) + df[\"build_h\"] * np.array([MAT[m][4] for m in mat]) + 3\n", " df[\"buy_cost\"] = df[\"print_cost\"] * rng.lognormal(0.25, 0.45, n) + 5 # ๊ตฌ๋งค ๋‹จ๊ฐ€(๋งŒ์›, ๊ฐ€์ƒ)\n", " wall_ok = df[\"min_wall_mm\"] >= np.array([MAT[m][5] for m in mat]) * rng.uniform(0.95, 1.25, n)\n", " dim_ok = df[\"max_dim_mm\"] <= np.where(is_poly, BUILD_MM[\"polymer\"], BUILD_MM[\"metal\"])\n", " warp = (~is_poly) & (df[\"max_dim_mm\"] > 140) & (df[\"overhang\"] > 0.3) # ๊ธˆ์† ๋Œ€ํ˜• ์˜ค๋ฒ„ํ–‰ ๋ณ€ํ˜•\n", " thr_bad = df[\"thread\"] & (df[\"min_wall_mm\"] < 1.5) & is_poly # ์–‡์€ ๋‚˜์‚ฌ๋ถ€ ํŒŒ์†\n", " df[\"printable\"] = wall_ok & dim_ok & (ovh < 0.5) & ~warp & ~thr_bad\n", " risk = np.clip(1 - df[\"safety\"] / 3, 0, 1)\n", " df[\"conform\"] = np.clip(100 * (0.45 * np.minimum(df[\"safety\"] / 3, 1) + 0.25 * np.clip((df[\"log_cycles\"] - 3) / 4.5, 0, 1)\n", " + 0.15 * (1 - np.clip(ovh * 1.2, 0, 1)) + 0.15 * df[\"cert\"]) - 8 * risk * df[\"crit\"] / 3 + rng.normal(0, 2, n), 0, 100)\n", " df[\"demand_month\"] = rng.poisson(np.where(df[\"cat\"].isin([\"clip\", \"handle\"]), 40, 15)) + 1 # ์›” ์†Œ์š”์ˆ˜๋Ÿ‰(๊ฐ€์ƒ)\n", " df[\"scrap_cost\"] = df[\"scrap_rate\"] * df[\"demand_month\"] * df[\"buy_cost\"] # ์›” ํ๊ธฐ ์†์‹ค์•ก(๋งŒ์›, ๊ฐ€์ƒ)\n", " return df\n", "\n", "# ---- ๋‹จ์ˆœํ™”ํ•œ ํ•ด์„(FEA ๋Œ€์šฉ) ๊ฐ’๊ณผ ํŒŒ์ƒ ํ”ผ์ฒ˜: ์ž…๋ ฅ ์ •๋ณด๋งŒ์œผ๋กœ ๊ณ„์‚ฐํ•œ๋‹ค(์‹œํ—˜ ๊ฒฐ๊ณผ ๋ˆ„์„ค ์—†์Œ) ----\n", "def make_features(d):\n", " \"\"\"๊ฐ„์ด ์‘๋ ฅ ํ•ด์„(์‘๋ ฅ์ง‘์ค‘๊ณ„์ˆ˜ ํฌํ•จ)๊ณผ ์†Œ์žฌยท๊ณต์ • ์ง€์‹์—์„œ ๋งŒ๋“  ํŒŒ์ƒ ํ”ผ์ฒ˜ 16๊ฐœ.\"\"\"\n", " X = pd.DataFrame(index=d.index)\n", " base = d[\"material\"].map(lambda m: MAT[m][1]).astype(float)\n", " X[\"base_strength\"] = base\n", " X[\"is_metal\"] = (d[\"process\"] == \"metal\").astype(int)\n", " X[\"cross_mm2\"] = d[\"min_wall_mm\"] * np.sqrt(d[\"max_dim_mm\"])\n", " X[\"kt\"] = 1 + 0.15 * d[\"n_holes\"] + 0.5 * d[\"thread\"].astype(int) # ๊ตฌ๋ฉยท๋‚˜์‚ฌ๋ถ€ ์‘๋ ฅ์ง‘์ค‘๊ณ„์ˆ˜(๊ฐ„์ด)\n", " X[\"fea_stress\"] = d[\"load_N\"] / X[\"cross_mm2\"] * X[\"kt\"] # ๊ฐ„์ด ํ•ด์„ ์‘๋ ฅ(MPa)\n", " X[\"stress_ratio\"] = X[\"fea_stress\"] / base # ๊ธฐ์ค€๊ฐ•๋„ ๋Œ€๋น„ ์‘๋ ฅ๋น„\n", " X[\"infill_eff\"] = np.where(X[\"is_metal\"] == 1, 1.0, 0.55 + 0.45 * d[\"infill\"] / 100)\n", " X[\"orient_sin2\"] = np.sin(np.radians(d[\"orient_deg\"])) ** 2\n", " X[\"temp_derate\"] = np.where(X[\"is_metal\"] == 1, 0.0, np.clip((d[\"temp_C\"] - 40) / 80, 0, 1))\n", " X[\"overhang_layer\"] = d[\"overhang\"] * d[\"layer_mm\"]\n", " X[\"slender\"] = d[\"max_dim_mm\"] / d[\"min_wall_mm\"]\n", " X[\"surf_vol\"] = d[\"area_cm2\"] / d[\"volume_cm3\"]\n", " X[\"wall_ratio\"] = d[\"min_wall_mm\"] / d[\"material\"].map(lambda m: MAT[m][5])\n", " X[\"load_per_vol\"] = d[\"load_N\"] / d[\"volume_cm3\"]\n", " X[\"cert\"] = d[\"cert\"].astype(int)\n", " X[\"crit\"] = d[\"crit\"]\n", " return X\n", "\n", "train = pd.concat([make_parts(1000, seed=s) for s in (11, 12)], ignore_index=True)\n", "test = make_parts(600, seed=21) # Hold-out: ํ•™์Šต์— ์“ฐ์ง€ ์•Š์€ ๋ถ€ํ’ˆ\n", "Xtr, Xte, ytr, yte = make_features(train), make_features(test), train[\"conform\"], test[\"conform\"]\n", "\n", "# 1) ๊ธฐ์ค€๋ชจ๋ธ(์› ์ž…๋ ฅ 5๊ฐœ ์„ ํ˜•) vs ์•™์ƒ๋ธ”\n", "raw_cols = [\"load_N\", \"min_wall_mm\", \"infill\", \"temp_C\", \"cert\"]\n", "base = LinearRegression().fit(train[raw_cols], ytr)\n", "gbm = GradientBoostingRegressor(n_estimators=300, max_depth=3, learning_rate=0.05, random_state=0).fit(Xtr, ytr)\n", "rf = RandomForestRegressor(n_estimators=150, min_samples_leaf=3, random_state=0, n_jobs=1).fit(Xtr, ytr)\n", "preds = {\"๊ธฐ์ค€๋ชจ๋ธ(์„ ํ˜• 5์ž…๋ ฅ)\": base.predict(test[raw_cols]), \"GBM\": gbm.predict(Xte), \"RandomForest\": rf.predict(Xte)}\n", "preds[\"์•™์ƒ๋ธ”(GBM+RF)\"] = (preds[\"GBM\"] + preds[\"RandomForest\"]) / 2\n", "tab = pd.DataFrame([{\"๋ชจ๋ธ\": k, \"MAE\": mean_absolute_error(yte, p), \"RMSE\": mean_squared_error(yte, p) ** 0.5, \"R2\": r2_score(yte, p)} for k, p in preds.items()]).round(3)\n", "print(f\"ํ•™์Šต {len(train)}์ข… / Hold-out {len(test)}์ข…\")\n", "print(tab.to_string(index=False))\n", "b, e = tab.iloc[0], tab.iloc[-1]\n", "imp_mae, imp_rmse = 1 - e[\"MAE\"] / b[\"MAE\"], 1 - e[\"RMSE\"] / b[\"RMSE\"]\n", "print(f\"๊ธฐ์ค€๋ชจ๋ธ ๋Œ€๋น„ ๊ฐœ์„ : MAE {imp_mae:.1%}, RMSE {imp_rmse:.1%} (๋ชฉํ‘œ 20% ์ด์ƒ: {'๋‹ฌ์„ฑ' if min(imp_mae, imp_rmse) >= 0.2 else '๋ฏธ๋‹ฌ'}), \"\n", " f\"R2 {e['R2']:.3f} (๋ชฉํ‘œ 0.85 ์ด์ƒ: {'๋‹ฌ์„ฑ' if e['R2'] >= 0.85 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# 2) ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ๊ตฌ๊ฐ„๋ณ„ ์‹ค์ œ ๋ถ€ํ•ฉ๋ฅ : ์‹ค์ œ ์ ์ˆ˜ 70 ์ด์ƒ์„ '๊ทœ๊ฒฉ ๋ถ€ํ•ฉ'์œผ๋กœ ๋ณธ๋‹ค(๊ฐ€์ƒ ๊ธฐ์ค€)\n", "score = np.clip(preds[\"์•™์ƒ๋ธ”(GBM+RF)\"], 0, 100)\n", "actual = (yte >= 70).values\n", "bins = pd.cut(score, [-1, 50, 70, 85, 101], labels=[\"<50 ๋ถ€์ ํ•ฉ ์˜์‹ฌ\", \"50~70 ๋ณด์™„ ํ•„์š”\", \"70~85 ์กฐ๊ฑด๋ถ€\", \">=85 ๋ถ€ํ•ฉ ์œ ๋ ฅ\"])\n", "cal = pd.DataFrame({\"๊ตฌ๊ฐ„\": bins, \"์ ์ˆ˜\": score, \"์‹ค์ œ๋ถ€ํ•ฉ\": actual}).groupby(\"๊ตฌ๊ฐ„\", observed=True).agg(๊ฑด์ˆ˜=(\"์ ์ˆ˜\", \"size\"), ํ‰๊ท ์ ์ˆ˜=(\"์ ์ˆ˜\", \"mean\"), ์‹ค์ œ๋ถ€ํ•ฉ๋ฅ =(\"์‹ค์ œ๋ถ€ํ•ฉ\", \"mean\")).round(2)\n", "print(\"\\n[์ ์ˆ˜ ๊ตฌ๊ฐ„๋ณ„ ์‹ค์ œ ๋ถ€ํ•ฉ๋ฅ ]\")\n", "print(cal.to_string())\n", "mono = bool((cal[\"์‹ค์ œ๋ถ€ํ•ฉ๋ฅ \"].diff().dropna() >= 0).all())\n", "print(f\"์ ์ˆ˜ ๊ตฌ๊ฐ„์ด ๋†’์„์ˆ˜๋ก ์‹ค์ œ ๋ถ€ํ•ฉ๋ฅ ์ด {'๋‹จ์กฐ ์ฆ๊ฐ€' if mono else '๋‹จ์กฐ ์ฆ๊ฐ€ํ•˜์ง€ ์•Š์Œ'}, AUC {roc_auc_score(actual, score):.3f}\")\n", "\n", "# 3) ์˜ˆ์ธก ์•ˆ์ •์„ฑ: ๋‘ ๋ชจ๋ธ์˜ ์˜๊ฒฌ ์ฐจ์ด๊ฐ€ ํฐ ๋ถ€ํ’ˆ์€ 'ํŒ์ • ๋ณด๋ฅ˜' ํ›„๋ณด\n", "gap = np.abs(preds[\"GBM\"] - preds[\"RandomForest\"])\n", "hold = gap > np.quantile(gap, 0.9)\n", "print(f\"\\n[์˜ˆ์ธก ์•ˆ์ •์„ฑ] ๋ชจ๋ธ ๊ฐ„ ์ฐจ์ด ์ƒ์œ„ 10% {int(hold.sum())}์ข…: ํ‰๊ท  ์˜ค์ฐจ {np.abs(yte[hold] - score[hold]).mean():.1f}์  vs ๋‚˜๋จธ์ง€ {np.abs(yte[~hold] - score[~hold]).mean():.1f}์ \")\n", "irr = test[\"cert\"].eq(0) & test[\"crit\"].eq(3)\n", "print(f\"์ธ์ฆ์„œ ์—†๋Š” ํ•ต์‹ฌ ๋ถ€ํ’ˆ {int(irr.sum())}์ข…: ํ‰๊ท  ์ ์ˆ˜ {score[irr.values].mean():.1f}์  -> ์ ์ˆ˜์™€ ๋ณ„๊ฐœ๋กœ ์†Œ์žฌ ์ธ์ฆ ํ™•๋ณด๊ฐ€ ์„ ํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n", "ax[0].scatter(yte, score, s=8); ax[0].plot([0, 100], [0, 100], \"r--\"); ax[0].set_xlabel(\"actual score\"); ax[0].set_ylabel(\"predicted\")\n", "ax[0].set_title(f\"Conformity score hold-out (R2={e['R2']:.2f})\")\n", "ax[1].plot(cal[\"ํ‰๊ท ์ ์ˆ˜\"], cal[\"์‹ค์ œ๋ถ€ํ•ฉ๋ฅ \"], \"o-\"); ax[1].set_xlabel(\"mean confidence score\"); ax[1].set_ylabel(\"actual conform rate\"); ax[1].set_title(\"Score band calibration\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 05 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "ํ•™์Šต 2000์ข… / Hold-out 600์ข…\n", " ๋ชจ๋ธ MAE RMSE R2\n", "๊ธฐ์ค€๋ชจ๋ธ(์„ ํ˜• 5์ž…๋ ฅ) 15.800 18.627 0.438\n", " GBM 3.893 5.120 0.958\n", "RandomForest 4.246 5.740 0.947\n", " ์•™์ƒ๋ธ”(GBM+RF) 3.916 5.208 0.956\n", "๊ธฐ์ค€๋ชจ๋ธ ๋Œ€๋น„ ๊ฐœ์„ : MAE 75.2%, RMSE 72.0% (๋ชฉํ‘œ 20% ์ด์ƒ: ๋‹ฌ์„ฑ), R2 0.956 (๋ชฉํ‘œ 0.85 ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "\n", "[์ ์ˆ˜ ๊ตฌ๊ฐ„๋ณ„ ์‹ค์ œ ๋ถ€ํ•ฉ๋ฅ ]\n", " ๊ฑด์ˆ˜ ํ‰๊ท ์ ์ˆ˜ ์‹ค์ œ๋ถ€ํ•ฉ๋ฅ \n", "๊ตฌ๊ฐ„ \n", "<50 ๋ถ€์ ํ•ฉ ์˜์‹ฌ 217 32.12 0.00\n", "50~70 ๋ณด์™„ ํ•„์š” 131 60.96 0.16\n", "70~85 ์กฐ๊ฑด๋ถ€ 139 77.20 0.91\n", ">=85 ๋ถ€ํ•ฉ ์œ ๋ ฅ 113 90.07 1.00\n", "์ ์ˆ˜ ๊ตฌ๊ฐ„์ด ๋†’์„์ˆ˜๋ก ์‹ค์ œ ๋ถ€ํ•ฉ๋ฅ ์ด ๋‹จ์กฐ ์ฆ๊ฐ€, AUC 0.987\n", "\n", "[์˜ˆ์ธก ์•ˆ์ •์„ฑ] ๋ชจ๋ธ ๊ฐ„ ์ฐจ์ด ์ƒ์œ„ 10% 60์ข…: ํ‰๊ท  ์˜ค์ฐจ 6.6์  vs ๋‚˜๋จธ์ง€ 3.6์ \n", "์ธ์ฆ์„œ ์—†๋Š” ํ•ต์‹ฌ ๋ถ€ํ’ˆ 57์ข…: ํ‰๊ท  ์ ์ˆ˜ 48.5์  -> ์ ์ˆ˜์™€ ๋ณ„๊ฐœ๋กœ ์†Œ์žฌ ์ธ์ฆ ํ™•๋ณด๊ฐ€ ์„ ํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 06. ๋ถ€ํ’ˆ๋ณ„ ์ œ์ž‘ ์†Œ์š” ์‹œ๊ฐ„, ๋น„์šฉ ๋ฐ ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ ๊ฒฐ๊ณผ ๊ฐ€์‹œํ™” ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋ถ€ํ’ˆ๋ณ„ ์ œ์ž‘ ์†Œ์š” ์‹œ๊ฐ„, ๋น„์šฉ ๋ฐ ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ ๊ฐ€์‹œํ™” ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ถ€ํ’ˆ๋ณ„ ์ œ์ž‘ ์†Œ์š” ์‹œ๊ฐ„, ๋น„์šฉ, ์‹ ๋ขฐ์„ฑ ์ ์ˆ˜์™€ ํ๊ธฐ ์†์‹ค์„ ๋ณด์—ฌ ์ฃผ๋Š” ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๋งŒ๋“ค๊ณ  ์˜ˆ์ธก๋งˆ๋‹ค ๊ทผ๊ฑฐ๋ฅผ ์„ค๋ช…ํ•˜๋Š” ์„ค๋ช…๊ฐ€๋Šฅ์„ฑ(XAI) ๊ธฐ๋Šฅ์„ ๋ถ™์ธ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ถœ๋ ฅ ์‹œ๊ฐ„-๋น„์šฉ, ์ถœ๋ ฅ ๋Œ€ ๊ตฌ๋งค ๋น„์šฉ, ์ ์ˆ˜ ๋ถ„ํฌ, ์ ์ˆ˜-ํ๊ธฐ ์†์‹ค, ์šฐ์„  ํ›„๋ณด 5์ข…, ์ ์ˆ˜ ์›Œํ„ฐํด๋กœ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌ์„ฑ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/explainability/mnt06_xai_explainer.py ๋ฐ XAI ๋ฆฌํฌํŠธ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ์žฅ๊ต/๊ตฐ๋ฌด์›์ด ์‹ ๋ขฐํ•˜๊ณ  ์กฐ์น˜ํ•  ์ˆ˜ ์žˆ๋Š” ์„ค๋ช…์˜ ๋ช…ํ™•์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์ฃผ์š” ์˜ˆ์ธก ๊ฑด๋‹น ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ธ์ž ์ž๋™ ์‚ฐ์ถœ๋ฅ  100%\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: SHAP summary plot ๋ฐ waterfall plot ์ƒ์„ฑ ํŒŒ์ดํ”„๋ผ์ธ ์™„์„ฑ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ์‹ค๋ฌด์ž ๋Œ€์ƒ ์„ค๋ช… ๊ทผ๊ฑฐ ์œ ์šฉ์„ฑ ํ‰๊ฐ€ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๋ถ€ํ’ˆ๋ณ„ ์ถœ๋ ฅ ์†Œ์š” ์‹œ๊ฐ„ยท์ œ์ž‘ ๋น„์šฉยท์‹ ๋ขฐ์„ฑ ์ ์ˆ˜์™€ ํ๊ธฐ ์†์‹ค์„ ํ•œ ํ™”๋ฉด์— ๋ณด์—ฌ ์ฃผ๋Š” ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ทธ๋ฆฌ๊ณ , ๊ตฐ ๊ทœ๊ฒฉ ๋ถ€ํ•ฉ ์ ์ˆ˜ ์˜ˆ์ธก๋งˆ๋‹ค ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ธ์ž๋ฅผ ๊ฐ€๋ฆผ(occlusion) ๋ฐฉ์‹(SHAP ๊ทผ์‚ฌ)์œผ๋กœ ์ž๋™ ์‚ฐ์ถœํ•ด ์›Œํ„ฐํด ๊ทธ๋ฆผ๊ณผ ์ž์—ฐ์–ด ์„ค๋ช…์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ์„ค๋ช…๊ฐ€๋Šฅ์„ฑ(XAI) ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด ๋ถ€ํ’ˆ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€ํ’ˆยท์žฌ๊ณ ยท๊ทœ๊ฒฉ ๊ฐ’ ์•„๋‹˜)\n", "# ํ๊ธฐ์œจ์ด ๋†’์€ ์ˆ˜๋ฆฌ๋ถ€์† ์ค‘ 3D ํ”„๋ฆฐํŒ…์œผ๋กœ ๋Œ€์ฒด ์ œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์ƒ ๋ถ€ํ’ˆ ์ž๋ฃŒ๋ฅผ ๋งŒ๋“ ๋‹ค.\n", "# ์†Œ์žฌ ๋ฌผ์„ฑยท์น˜์ˆ˜ยทํ•˜์ค‘์€ ๊ต์œก์šฉ์œผ๋กœ ๋‹จ์ˆœํ™”ํ•œ ๊ฐ’์ด๋ฉฐ ์‹ค์ œ ์‹œํ—˜ ์„ฑ์ ์„œ๋‚˜ ๊ตฐ ๊ทœ๊ฒฉ ๊ฐ’๊ณผ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "MAT = { # ์†Œ์žฌ: (๊ณต์ •, ๊ธฐ์ค€ ์ธ์žฅ๊ฐ•๋„ MPa, ๋ฐ€๋„ g/cm3, ์†Œ์žฌ ๋‹จ๊ฐ€ ๋งŒ์›/kg, ์ถœ๋ ฅ๋น„ ๋งŒ์›/h, ์ตœ์†Œ ๋ฒฝ๋‘๊ป˜ mm, ์ถœ๋ ฅ ์†๋„ cm3/h)\n", " \"PA12\": (\"polymer\", 45, 1.0, 8, 3, 0.8, 18), \"PETG\": (\"polymer\", 50, 1.27, 5, 2, 0.8, 15),\n", " \"CF-PA\": (\"polymer\", 70, 1.2, 14, 3, 1.0, 12), \"AlSi10Mg\": (\"metal\", 350, 2.67, 30, 25, 0.5, 4),\n", " \"SS316L\": (\"metal\", 520, 7.9, 28, 25, 0.6, 3.5), \"Ti64\": (\"metal\", 900, 4.4, 90, 30, 0.6, 3)}\n", "CATS = [\"bracket\", \"housing\", \"clip\", \"gear\", \"duct\", \"handle\"]\n", "CAT_SCRAP = {\"bracket\": 0.04, \"housing\": 0.05, \"clip\": 0.12, \"gear\": 0.09, \"duct\": 0.08, \"handle\": 0.10} # ๋ถ€ํ’ˆ๊ตฐ๋ณ„ ๊ธฐ๋ณธ ์›” ํ๊ธฐ์œจ\n", "BUILD_MM = {\"polymer\": 300, \"metal\": 200} # ๊ฐ€์ƒ ์ถœ๋ ฅ ๊ฐ€๋Šฅ ์ตœ๋Œ€ ์น˜์ˆ˜\n", "\n", "\n", "def make_parts(n=300, seed=0):\n", " \"\"\"๊ฐ€์ƒ ๋ถ€ํ’ˆ n์ข…: ํ˜•์ƒ(๋ถ€ํ”ผยท๋ฒฝ๋‘๊ป˜ยท๋Œ์ถœ ๋น„์œจ), ์†Œ์žฌ, ์‚ฌ์šฉ ํ•˜์ค‘, ํ๊ธฐ์œจ๊ณผ ์กฐ๋‹ฌ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " mat = rng.choice(list(MAT), n, p=[0.2, 0.15, 0.1, 0.2, 0.2, 0.15])\n", " proc = np.array([MAT[m][0] for m in mat])\n", " vol = np.clip(rng.lognormal(np.log(30), 0.9, n), 2, 800)\n", " aspect = rng.lognormal(0.3, 0.4, n)\n", " max_dim = 10 * vol ** (1 / 3) * aspect ** (1 / 3) * rng.uniform(1.0, 1.6, n)\n", " wall = np.clip(rng.lognormal(np.log(2.0), 0.6, n), 0.3, 8)\n", " ovh = rng.beta(2, 5, n)\n", " is_poly = proc == \"polymer\"\n", " df = pd.DataFrame({\n", " \"part_id\": [f\"P{i:03d}\" for i in range(n)], \"cat\": rng.choice(CATS, n, p=[0.2, 0.15, 0.2, 0.1, 0.15, 0.2]), \"material\": mat, \"process\": proc,\n", " \"volume_cm3\": vol, \"max_dim_mm\": max_dim, \"area_cm2\": 6 * vol ** (2 / 3) * rng.uniform(1.0, 2.2, n), \"min_wall_mm\": wall, \"overhang\": ovh,\n", " \"n_holes\": rng.poisson(2.0, n), \"thread\": rng.random(n) < 0.25, \"orient_deg\": rng.uniform(0, 90, n),\n", " \"layer_mm\": np.where(is_poly, rng.choice([0.1, 0.2, 0.3], n), rng.choice([0.03, 0.05], n)),\n", " \"infill\": np.where(is_poly, rng.choice([40, 60, 80, 100], n, p=[0.15, 0.3, 0.3, 0.25]), 100),\n", " \"load_N\": np.clip(rng.lognormal(np.where(is_poly, np.log(230), np.log(2300)), 0.7, n), 10, 20000), \"temp_C\": rng.uniform(-20, 90, n),\n", " \"crit\": rng.choice([1, 2, 3], n, p=[0.35, 0.4, 0.25]), \"cert\": rng.choice([0, 1], n, p=[0.45, 0.55]), # ์†Œ์žฌ ์ธ์ฆ์„œ ๋ณด์œ \n", " \"import_part\": rng.random(n) < 0.4})\n", " df[\"scrap_rate\"] = np.clip(df[\"cat\"].map(CAT_SCRAP) * rng.lognormal(0, 0.35, n) * (1 + 0.5 * (df[\"load_N\"] > 800)) * (1 + 0.3 * (df[\"temp_C\"] > 60)), 0.005, 0.5)\n", " df[\"lead_days\"] = np.clip(rng.lognormal(np.log(25), 0.5, n) * np.where(df[\"import_part\"], 2.2, 1.0), 5, 300)\n", " # ---- ์ˆจ์€ ์ง„์‹ค๊ฐ’(์‹ค์ œ ์‹œํ—˜ ๊ฒฐ๊ณผ์— ํ•ด๋‹น): ๋ชจ๋ธ์€ ์œ„ ์ž…๋ ฅ์œผ๋กœ ์ด๊ฒƒ์„ ์˜ˆ์ธกํ•œ๋‹ค ----\n", " eff = np.where(is_poly, 0.55 + 0.45 * df[\"infill\"] / 100, 1 - 0.3 * ovh * 0.2)\n", " aniso = np.where(is_poly, 1 - 0.3 * np.sin(np.radians(df[\"orient_deg\"])) ** 2, 1 - 0.08 * np.sin(np.radians(df[\"orient_deg\"])))\n", " heat = np.where(is_poly, 1 - 0.35 * np.clip((df[\"temp_C\"] - 40) / 80, 0, 1), 0.98)\n", " base = np.array([MAT[m][1] for m in mat])\n", " df[\"strength_mpa\"] = base * eff * aniso * heat * rng.lognormal(0, 0.06, n)\n", " cross = df[\"min_wall_mm\"] * np.sqrt(df[\"max_dim_mm\"]) * 1.0 # ํ•˜์ค‘ ์ง€์ง€ ๋‹จ๋ฉด์  mm2(๋‹จ์ˆœํ™”)\n", " df[\"stress_mpa\"] = df[\"load_N\"] / cross\n", " df[\"safety\"] = df[\"strength_mpa\"] / df[\"stress_mpa\"]\n", " ratio = np.clip(df[\"stress_mpa\"] / df[\"strength_mpa\"], 0, 1.5)\n", " df[\"log_cycles\"] = np.clip(7.5 - 6.0 * ratio + rng.normal(0, 0.25, n), 2, 7.5) # ํ”ผ๋กœ ์ˆ˜๋ช… log10(ํšŒ)\n", " support = 1 + 0.5 * ovh\n", " df[\"build_h\"] = vol * support / np.array([MAT[m][6] for m in mat]) + 0.5\n", " mass_kg = vol * np.array([MAT[m][2] for m in mat]) * support / 1000\n", " df[\"print_cost\"] = mass_kg * np.array([MAT[m][3] for m in mat]) + df[\"build_h\"] * np.array([MAT[m][4] for m in mat]) + 3\n", " df[\"buy_cost\"] = df[\"print_cost\"] * rng.lognormal(0.25, 0.45, n) + 5 # ๊ตฌ๋งค ๋‹จ๊ฐ€(๋งŒ์›, ๊ฐ€์ƒ)\n", " wall_ok = df[\"min_wall_mm\"] >= np.array([MAT[m][5] for m in mat]) * rng.uniform(0.95, 1.25, n)\n", " dim_ok = df[\"max_dim_mm\"] <= np.where(is_poly, BUILD_MM[\"polymer\"], BUILD_MM[\"metal\"])\n", " warp = (~is_poly) & (df[\"max_dim_mm\"] > 140) & (df[\"overhang\"] > 0.3) # ๊ธˆ์† ๋Œ€ํ˜• ์˜ค๋ฒ„ํ–‰ ๋ณ€ํ˜•\n", " thr_bad = df[\"thread\"] & (df[\"min_wall_mm\"] < 1.5) & is_poly # ์–‡์€ ๋‚˜์‚ฌ๋ถ€ ํŒŒ์†\n", " df[\"printable\"] = wall_ok & dim_ok & (ovh < 0.5) & ~warp & ~thr_bad\n", " risk = np.clip(1 - df[\"safety\"] / 3, 0, 1)\n", " df[\"conform\"] = np.clip(100 * (0.45 * np.minimum(df[\"safety\"] / 3, 1) + 0.25 * np.clip((df[\"log_cycles\"] - 3) / 4.5, 0, 1)\n", " + 0.15 * (1 - np.clip(ovh * 1.2, 0, 1)) + 0.15 * df[\"cert\"]) - 8 * risk * df[\"crit\"] / 3 + rng.normal(0, 2, n), 0, 100)\n", " df[\"demand_month\"] = rng.poisson(np.where(df[\"cat\"].isin([\"clip\", \"handle\"]), 40, 15)) + 1 # ์›” ์†Œ์š”์ˆ˜๋Ÿ‰(๊ฐ€์ƒ)\n", " df[\"scrap_cost\"] = df[\"scrap_rate\"] * df[\"demand_month\"] * df[\"buy_cost\"] # ์›” ํ๊ธฐ ์†์‹ค์•ก(๋งŒ์›, ๊ฐ€์ƒ)\n", " return df\n", "\n", "# ---- ๋‹จ์ˆœํ™”ํ•œ ํ•ด์„(FEA ๋Œ€์šฉ) ๊ฐ’๊ณผ ํŒŒ์ƒ ํ”ผ์ฒ˜: ์ž…๋ ฅ ์ •๋ณด๋งŒ์œผ๋กœ ๊ณ„์‚ฐํ•œ๋‹ค(์‹œํ—˜ ๊ฒฐ๊ณผ ๋ˆ„์„ค ์—†์Œ) ----\n", "def make_features(d):\n", " \"\"\"๊ฐ„์ด ์‘๋ ฅ ํ•ด์„(์‘๋ ฅ์ง‘์ค‘๊ณ„์ˆ˜ ํฌํ•จ)๊ณผ ์†Œ์žฌยท๊ณต์ • ์ง€์‹์—์„œ ๋งŒ๋“  ํŒŒ์ƒ ํ”ผ์ฒ˜ 16๊ฐœ.\"\"\"\n", " X = pd.DataFrame(index=d.index)\n", " base = d[\"material\"].map(lambda m: MAT[m][1]).astype(float)\n", " X[\"base_strength\"] = base\n", " X[\"is_metal\"] = (d[\"process\"] == \"metal\").astype(int)\n", " X[\"cross_mm2\"] = d[\"min_wall_mm\"] * np.sqrt(d[\"max_dim_mm\"])\n", " X[\"kt\"] = 1 + 0.15 * d[\"n_holes\"] + 0.5 * d[\"thread\"].astype(int) # ๊ตฌ๋ฉยท๋‚˜์‚ฌ๋ถ€ ์‘๋ ฅ์ง‘์ค‘๊ณ„์ˆ˜(๊ฐ„์ด)\n", " X[\"fea_stress\"] = d[\"load_N\"] / X[\"cross_mm2\"] * X[\"kt\"] # ๊ฐ„์ด ํ•ด์„ ์‘๋ ฅ(MPa)\n", " X[\"stress_ratio\"] = X[\"fea_stress\"] / base # ๊ธฐ์ค€๊ฐ•๋„ ๋Œ€๋น„ ์‘๋ ฅ๋น„\n", " X[\"infill_eff\"] = np.where(X[\"is_metal\"] == 1, 1.0, 0.55 + 0.45 * d[\"infill\"] / 100)\n", " X[\"orient_sin2\"] = np.sin(np.radians(d[\"orient_deg\"])) ** 2\n", " X[\"temp_derate\"] = np.where(X[\"is_metal\"] == 1, 0.0, np.clip((d[\"temp_C\"] - 40) / 80, 0, 1))\n", " X[\"overhang_layer\"] = d[\"overhang\"] * d[\"layer_mm\"]\n", " X[\"slender\"] = d[\"max_dim_mm\"] / d[\"min_wall_mm\"]\n", " X[\"surf_vol\"] = d[\"area_cm2\"] / d[\"volume_cm3\"]\n", " X[\"wall_ratio\"] = d[\"min_wall_mm\"] / d[\"material\"].map(lambda m: MAT[m][5])\n", " X[\"load_per_vol\"] = d[\"load_N\"] / d[\"volume_cm3\"]\n", " X[\"cert\"] = d[\"cert\"].astype(int)\n", " X[\"crit\"] = d[\"crit\"]\n", " return X\n", "\n", "KO = {\"base_strength\": \"์†Œ์žฌ ๊ธฐ์ค€๊ฐ•๋„\", \"is_metal\": \"๊ธˆ์† ๊ณต์ •\", \"cross_mm2\": \"ํ•˜์ค‘ ์ง€์ง€ ๋‹จ๋ฉด์ \", \"kt\": \"๊ตฌ๋ฉยท๋‚˜์‚ฌ๋ถ€ ์‘๋ ฅ์ง‘์ค‘\", \"fea_stress\": \"๊ฐ„์ด ํ•ด์„ ์‘๋ ฅ\",\n", " \"stress_ratio\": \"์‘๋ ฅ/๊ธฐ์ค€๊ฐ•๋„ ๋น„\", \"infill_eff\": \"์ถฉ์ „์œจ\", \"orient_sin2\": \"์ ์ธต ๋ฐฉํ–ฅ ์˜ํ–ฅ\", \"temp_derate\": \"๊ณ ์˜จ ๊ฐ•๋„ ์ €ํ•˜\", \"overhang_layer\": \"๋Œ์ถœx์ธต๋‘๊ป˜\",\n", " \"slender\": \"์„ธ์žฅ๋น„\", \"surf_vol\": \"ํ‘œ๋ฉด์ /๋ถ€ํ”ผ\", \"wall_ratio\": \"๋ฒฝ๋‘๊ป˜ ์—ฌ์œ \", \"load_per_vol\": \"๋ถ€ํ”ผ๋‹น ํ•˜์ค‘\", \"cert\": \"์†Œ์žฌ ์ธ์ฆ์„œ\", \"crit\": \"์ž‘์ „ ์ค‘์š”๋„\"}\n", "\n", "\n", "def explain(model, X, bg, top=3):\n", " \"\"\"๊ฐ€๋ฆผ(occlusion) ๋ฐฉ์‹ ๊ตญ์†Œ ๊ธฐ์—ฌ๋„: ํ”ผ์ฒ˜ j๋ฅผ ๋ฐฐ๊ฒฝ ํ‘œ๋ณธ ๊ฐ’์œผ๋กœ ๋ฐ”๊ฟจ์„ ๋•Œ ์˜ˆ์ธก์ด ํ‰๊ท ์ ์œผ๋กœ ์–ผ๋งˆ๋‚˜ ๋‹ฌ๋ผ์ง€๋Š”์ง€(SHAP ๊ทผ์‚ฌ).\"\"\"\n", " base = model.predict(X)\n", " contrib = np.zeros(X.shape)\n", " for j, c in enumerate(X.columns):\n", " for b in bg:\n", " Xb = X.copy(); Xb[c] = b[j]\n", " contrib[:, j] += base - model.predict(Xb)\n", " contrib /= len(bg)\n", " C = pd.DataFrame(contrib, columns=X.columns, index=X.index)\n", " return base, C, [list(C.loc[i].abs().sort_values(ascending=False).index[:top]) for i in C.index]\n", "\n", "\n", "hist, parts = make_parts(1000, seed=11), make_parts(300, seed=21)\n", "Xh, X = make_features(hist), make_features(parts)\n", "model = GradientBoostingRegressor(n_estimators=150, max_depth=3, learning_rate=0.06, random_state=0).fit(Xh, hist[\"conform\"])\n", "AVG = float(hist[\"conform\"].mean())\n", "\n", "# 1) ์„ค๋ช…๊ฐ€๋Šฅ์„ฑ: ์ „์ฒด ๋ถ€ํ’ˆ์— ๋Œ€ํ•ด ์ƒ์œ„ 3๊ฐœ ์ธ์ž ์ž๋™ ์‚ฐ์ถœ\n", "t0 = time.perf_counter()\n", "pred, C, tops = explain(model, X, Xh.sample(25, random_state=0).values)\n", "sec = time.perf_counter() - t0\n", "rate = np.mean([len(t) == 3 for t in tops])\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถ€ํ’ˆ {len(X)}์ข… ์„ค๋ช… ์‚ฐ์ถœ {sec:.1f}์ดˆ, ์ƒ์œ„ 3๊ฐœ ์ธ์ž ์ž๋™ ์‚ฐ์ถœ๋ฅ  {rate:.0%} (๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if rate == 1 else '๋ฏธ๋‹ฌ'})\")\n", "gap = np.abs(C.sum(axis=1) - (pred - model.predict(Xh).mean())).mean()\n", "print(f\"๊ธฐ์—ฌ๋„ ํ•ฉ๊ณผ (์˜ˆ์ธก - ํ‰๊ท  ์˜ˆ์ธก)์˜ ํ‰๊ท  ์ฐจ์ด {gap:.1f}์  -> ๊ทผ์‚ฌ ์„ค๋ช…์ด๋ฏ€๋กœ ์ฐธ๊ณ  ์ง€ํ‘œ๋กœ ์“ด๋‹ค\")\n", "freq = pd.Series([f for t in tops for f in t]).map(lambda f: KO.get(f, f)).value_counts().head(5)\n", "print(\"์ƒ์œ„ 3๊ฐœ ์ธ์ž์— ์ž์ฃผ ๋“ฑ์žฅํ•œ ์š”์ธ:\", \", \".join(f\"{k} {v}๊ฑด\" for k, v in freq.items()))\n", "\n", "# 2) ๋Œ€์‹œ๋ณด๋“œ์šฉ ํ‘œ: ์ถœ๋ ฅ ์‹œ๊ฐ„ยท๋น„์šฉยท์‹ ๋ขฐ์„ฑยทํ๊ธฐ ์†์‹ค\n", "parts[\"score\"] = np.clip(pred, 0, 100)\n", "parts[\"save\"] = parts[\"buy_cost\"] - parts[\"print_cost\"] # ๋ถ€ํ’ˆ 1๊ฐœ๋‹น ๊ตฌ๋งค ๋Œ€๋น„ ์ ˆ๊ฐ(๋งŒ์›)\n", "cand = parts[(parts[\"score\"] >= 70) & parts[\"printable\"]].copy()\n", "cand[\"priority\"] = cand[\"scrap_cost\"] * (cand[\"score\"] / 100)\n", "print(f\"\\n[๋Œ€์‹œ๋ณด๋“œ ์š”์•ฝ] ๋ถ€ํ’ˆ {len(parts)}์ข… ์ค‘ ์ ์ˆ˜ 70 ์ด์ƒ์ด๋ฉฐ ์ถœ๋ ฅ ๊ฐ€๋Šฅ {len(cand)}์ข…, ์›” ํ๊ธฐ ์†์‹ค ํ•ฉ๊ณ„ {cand['scrap_cost'].sum():,.0f}๋งŒ์›(๊ฐ€์ƒ)\")\n", "top = cand.sort_values(\"priority\", ascending=False).head(5)\n", "print(top[[\"part_id\", \"cat\", \"material\", \"build_h\", \"print_cost\", \"buy_cost\", \"score\", \"scrap_cost\"]].round(1).to_string(index=False))\n", "\n", "fig, ax = plt.subplots(2, 3, figsize=(15, 8))\n", "col = {1: \"tab:gray\", 2: \"tab:orange\", 3: \"tab:red\"}\n", "ax[0, 0].scatter(parts[\"build_h\"], parts[\"print_cost\"], c=parts[\"crit\"].map(col), s=10); ax[0, 0].set_xscale(\"log\"); ax[0, 0].set_yscale(\"log\")\n", "ax[0, 0].set_title(\"Build time vs cost (red = critical)\"); ax[0, 0].set_xlabel(\"build hours\")\n", "ax[0, 1].scatter(parts[\"print_cost\"], parts[\"buy_cost\"], s=8); ax[0, 1].plot([1, 5000], [1, 5000], \"r--\"); ax[0, 1].set_xscale(\"log\"); ax[0, 1].set_yscale(\"log\")\n", "ax[0, 1].set_title(\"Print cost vs purchase cost\"); ax[0, 1].set_xlabel(\"print cost\")\n", "ax[0, 2].hist(parts[\"score\"], bins=25); ax[0, 2].axvline(70, ls=\"--\", c=\"k\"); ax[0, 2].set_title(\"Conformity score distribution\")\n", "ax[1, 0].scatter(parts[\"score\"], parts[\"scrap_cost\"], c=parts[\"printable\"].map({True: \"tab:blue\", False: \"tab:red\"}), s=8); ax[1, 0].set_yscale(\"log\")\n", "ax[1, 0].set_title(\"Score vs scrap loss (blue = printable)\"); ax[1, 0].set_xlabel(\"score\")\n", "ax[1, 1].barh(top[\"part_id\"], top[\"scrap_cost\"]); ax[1, 1].set_title(\"Top 5 candidates by scrap loss\")\n", "i = int(parts[\"score\"].idxmin())\n", "ci = C.loc[i].reindex(C.loc[i].abs().sort_values(ascending=False).index)[:6][::-1]\n", "start = model.predict(Xh).mean()\n", "ax[1, 2].barh(range(len(ci)), ci.values, left=start + np.r_[0, np.cumsum(ci.values)[:-1]], color=[\"tab:green\" if v > 0 else \"tab:red\" for v in ci.values])\n", "ax[1, 2].set_yticks(range(len(ci)), ci.index); ax[1, 2].set_title(f\"Waterfall: {parts.loc[i, 'part_id']} (lowest score)\"); ax[1, 2].set_xlabel(\"score\")\n", "plt.tight_layout(); plt.show()\n", "print(\"๋Œ€์‹œ๋ณด๋“œ ๊ตฌ์„ฑ์š”์†Œ 6์ข…: ์‹œ๊ฐ„-๋น„์šฉ, ์ถœ๋ ฅ-๊ตฌ๋งค ๋น„์šฉ, ์ ์ˆ˜ ๋ถ„ํฌ, ์ ์ˆ˜-ํ๊ธฐ์†์‹ค, ์šฐ์„  ํ›„๋ณด 5์ข…, ์ ์ˆ˜ ์›Œํ„ฐํด\")\n", "\n", "# 3) ์ž์—ฐ์–ด ์„ค๋ช… ์˜ˆ์‹œ(์ •๋น„๊ธฐ์ˆ ๊ด€ ํ™”๋ฉด ํ•˜๋‹จ)\n", "def sentence(k):\n", " parts_ = [f\"{KO.get(f, f)}({'+' if C.loc[k, f] > 0 else ''}{C.loc[k, f]:.1f}์ )\" for f in tops[k]]\n", " return f\"{parts.loc[k, 'part_id']} {parts.loc[k, 'cat']}/{parts.loc[k, 'material']} ๋ถ€ํ•ฉ ์ ์ˆ˜ {pred[k]:.1f}์ (ํ‰๊ท  ๋Œ€๋น„ {pred[k] - AVG:+.1f}์ ), ์˜ํ–ฅ ์š”์ธ: \" + \", \".join(parts_)\n", "\n", "\n", "print(\"\\n[์˜ˆ์ธก ์„ค๋ช… ์˜ˆ์‹œ]\")\n", "for k in list(top.index[:3]) + [i]:\n", " print(\" - \" + sentence(k))\n", "print(\"\\n์ ์ˆ˜๊ฐ€ ๋‚ฎ์€ ๋ถ€ํ’ˆ์€ ์ธ์ž๋ณ„ ์กฐ์น˜(์ธ์ฆ์„œ ํ™•๋ณด, ์ถฉ์ „์œจ ์ƒํ–ฅ, ๋ฒฝ๋‘๊ป˜ ๋ณด๊ฐ• ๋“ฑ)๋ฅผ ์ •๋น„๊ธฐ์ˆ ๊ด€์ด ํŒ๋‹จํ•œ๋‹ค. ์„ค๋ช…์€ ๋ชจ๋ธ ๊ทผ์‚ฌ์ด๋ฉฐ ์Šน์ธ ๊ฒฐ์ •์„ ๋Œ€์‹ ํ•˜์ง€ ์•Š๋Š”๋‹ค.\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 06 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถ€ํ’ˆ 300์ข… ์„ค๋ช… ์‚ฐ์ถœ 2.4์ดˆ, ์ƒ์œ„ 3๊ฐœ ์ธ์ž ์ž๋™ ์‚ฐ์ถœ๋ฅ  100% (๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ)\n", "๊ธฐ์—ฌ๋„ ํ•ฉ๊ณผ (์˜ˆ์ธก - ํ‰๊ท  ์˜ˆ์ธก)์˜ ํ‰๊ท  ์ฐจ์ด 6.7์  -> ๊ทผ์‚ฌ ์„ค๋ช…์ด๋ฏ€๋กœ ์ฐธ๊ณ  ์ง€ํ‘œ๋กœ ์“ด๋‹ค\n", "์ƒ์œ„ 3๊ฐœ ์ธ์ž์— ์ž์ฃผ ๋“ฑ์žฅํ•œ ์š”์ธ: ์†Œ์žฌ ์ธ์ฆ์„œ 286๊ฑด, ์‘๋ ฅ/๊ธฐ์ค€๊ฐ•๋„ ๋น„ 282๊ฑด, ๊ตฌ๋ฉยท๋‚˜์‚ฌ๋ถ€ ์‘๋ ฅ์ง‘์ค‘ 137๊ฑด, ๋Œ์ถœx์ธต๋‘๊ป˜ 116๊ฑด, ์ ์ธต ๋ฐฉํ–ฅ ์˜ํ–ฅ 31๊ฑด\n", "\n", "[๋Œ€์‹œ๋ณด๋“œ ์š”์•ฝ] ๋ถ€ํ’ˆ 300์ข… ์ค‘ ์ ์ˆ˜ 70 ์ด์ƒ์ด๋ฉฐ ์ถœ๋ ฅ ๊ฐ€๋Šฅ 106์ข…, ์›” ํ๊ธฐ ์†์‹ค ํ•ฉ๊ณ„ 222,932๋งŒ์›(๊ฐ€์ƒ)\n", "part_id cat material build_h print_cost buy_cost score scrap_cost\n", " P121 handle Ti64 93.7 2925.4 7443.9 71.2 45271.3\n", " P117 handle AlSi10Mg 23.6 600.5 1218.9 83.5 17632.8\n", " P159 handle Ti64 15.7 491.7 878.2 81.6 17124.9\n", " P293 handle SS316L 57.4 1483.2 1960.5 92.0 15131.3\n", " P090 handle Ti64 40.6 1268.2 3157.5 90.5 12245.6\n", "๋Œ€์‹œ๋ณด๋“œ ๊ตฌ์„ฑ์š”์†Œ 6์ข…: ์‹œ๊ฐ„-๋น„์šฉ, ์ถœ๋ ฅ-๊ตฌ๋งค ๋น„์šฉ, ์ ์ˆ˜ ๋ถ„ํฌ, ์ ์ˆ˜-ํ๊ธฐ์†์‹ค, ์šฐ์„  ํ›„๋ณด 5์ข…, ์ ์ˆ˜ ์›Œํ„ฐํด\n", "\n", "[์˜ˆ์ธก ์„ค๋ช… ์˜ˆ์‹œ]\n", " - P121 handle/Ti64 ๋ถ€ํ•ฉ ์ ์ˆ˜ 71.2์ (ํ‰๊ท  ๋Œ€๋น„ +13.2์ ), ์˜ํ–ฅ ์š”์ธ: ์‘๋ ฅ/๊ธฐ์ค€๊ฐ•๋„ ๋น„(+10.0์ ), ์†Œ์žฌ ์ธ์ฆ์„œ(-9.3์ ), ๊ตฌ๋ฉยท๋‚˜์‚ฌ๋ถ€ ์‘๋ ฅ์ง‘์ค‘(+4.2์ )\n", " - P117 handle/AlSi10Mg ๋ถ€ํ•ฉ ์ ์ˆ˜ 83.5์ (ํ‰๊ท  ๋Œ€๋น„ +25.5์ ), ์˜ํ–ฅ ์š”์ธ: ์‘๋ ฅ/๊ธฐ์ค€๊ฐ•๋„ ๋น„(+10.1์ ), ์†Œ์žฌ ์ธ์ฆ์„œ(+3.8์ ), ๊ตฌ๋ฉยท๋‚˜์‚ฌ๋ถ€ ์‘๋ ฅ์ง‘์ค‘(+3.1์ )\n", " - P159 handle/Ti64 ๋ถ€ํ•ฉ ์ ์ˆ˜ 81.6์ (ํ‰๊ท  ๋Œ€๋น„ +23.6์ ), ์˜ํ–ฅ ์š”์ธ: ์‘๋ ฅ/๊ธฐ์ค€๊ฐ•๋„ ๋น„(+10.0์ ), ๋Œ์ถœx์ธต๋‘๊ป˜(+5.0์ ), ์†Œ์žฌ ์ธ์ฆ์„œ(+4.2์ )\n", " - P282 bracket/PA12 ๋ถ€ํ•ฉ ์ ์ˆ˜ 7.2์ (ํ‰๊ท  ๋Œ€๋น„ -50.7์ ), ์˜ํ–ฅ ์š”์ธ: ์‘๋ ฅ/๊ธฐ์ค€๊ฐ•๋„ ๋น„(-39.5์ ), ์†Œ์žฌ ์ธ์ฆ์„œ(-12.9์ ), ์ ์ธต ๋ฐฉํ–ฅ ์˜ํ–ฅ(-1.9์ )\n", "\n", "์ ์ˆ˜๊ฐ€ ๋‚ฎ์€ ๋ถ€ํ’ˆ์€ ์ธ์ž๋ณ„ ์กฐ์น˜(์ธ์ฆ์„œ ํ™•๋ณด, ์ถฉ์ „์œจ ์ƒํ–ฅ, ๋ฒฝ๋‘๊ป˜ ๋ณด๊ฐ• ๋“ฑ)๋ฅผ ์ •๋น„๊ธฐ์ˆ ๊ด€์ด ํŒ๋‹จํ•œ๋‹ค. ์„ค๋ช…์€ ๋ชจ๋ธ ๊ทผ์‚ฌ์ด๋ฉฐ ์Šน์ธ ๊ฒฐ์ •์„ ๋Œ€์‹ ํ•˜์ง€ ์•Š๋Š”๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 07. ์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ๋ถ€ํ’ˆ ์‹ค ์žฅ์ฐฉ ์Šน์ธ์„ ์œ„ํ•œ ์ •๋น„๊ธฐ์ˆ ๊ด€ HITL ๊ฒ€ํ†  ์ ˆ์ฐจ ์„ค๊ณ„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์•ผ์ „ ๋ถ€ํ’ˆ ์‹ค ์žฅ์ฐฉ ์Šน์ธ์„ ์œ„ํ•œ ์ •๋น„๊ธฐ์ˆ ๊ด€ HITL ๊ฒ€ํ†  ์ ˆ์ฐจ๋ฅผ ์„ค๊ณ„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ๋ถ€ํ’ˆ์˜ ์‹ค ์žฅ์ฐฉ ์Šน์ธ์„ ์œ„ํ•ด ๋ชจ๋ธ ์ œ์•ˆ์ด ์ •๋น„๊ธฐ์ˆ ๊ด€ ๊ฒ€ํ† ๋ฅผ ๊ฑฐ์ณ์•ผ๋งŒ ์ ์šฉ๋˜๋Š” HITL ์Šน์ธ ์ ˆ์ฐจ๋ฅผ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ๋กœ ์„ค๊ณ„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์›”๊ฐ„ ์š”์ฒญ๋งˆ๋‹ค ์ ์ˆ˜์™€ ํ˜•์ƒ ๊ทœ์น™์œผ๋กœ ๋Œ€์ฒด ์ œ์ž‘์„ ์ œ์•ˆํ•ด ์Šน์ธ ๋Œ€๊ธฐ์—ด์— ์˜ฌ๋ฆฌ๊ณ , ๋ชจ์˜ ์ •๋น„๊ธฐ์ˆ ๊ด€์€ ์ธ์ฆ์„œยท์ ์ˆ˜ยท์žฅ๋น„ ์ ์œ ๋ฅผ ๊ทผ๊ฑฐ๋กœ ์Šน์ธยท๋ฐ˜๋ คํ•˜๋ฉฐ ๋ฌด์‘๋‹ต์ด๋ฉด ๊ธฐ์กด ์กฐ๋‹ฌ์„ ์œ ์ง€ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/optimization/mnt06_simulator.py` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ํ˜„์‹ค์  ๊ตฐ์ˆ˜ ์ œ์•ฝ์กฐ๊ฑด ๋ฐ˜์˜๋„ ๋ฐ ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ ๋Œ€์•ˆ ์ œ์‹œ ์ ์ ˆ์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์ตœ์ ํ™” ์ ์šฉ ์‹œ ๋น„์šฉ 15% ์ ˆ๊ฐ ๋˜๋Š” ์žฌ๊ณ  ํšŒ์ „์œจ 20% ํ–ฅ์ƒ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์„ ํ˜•/์ •์ˆ˜ ๊ณ„ํš๋ฒ•(LP/MIP) ์ตœ์ ํ™” ํ•ด ๋„์ถœ ๋ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์™„๋ฃŒ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๊ณผ๊ฑฐ ์‹ค์  ๋ฐ์ดํ„ฐ ๋Œ€๋น„ ์ตœ์ ํ™” ๊ฒฐ๊ณผ ๋น„๊ต ๋ฐฑํ…Œ์ŠคํŒ…\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ๋ถ€ํ’ˆ์˜ ์‹ค ์žฅ์ฐฉ ์Šน์ธ์„ ์œ„ํ•ด, ๋ชจ๋ธ์ด ๋Œ€์ฒด ์ œ์ž‘์„ ์ œ์•ˆํ•˜๋ฉด ์ •๋น„๊ธฐ์ˆ ๊ด€ ์Šน์ธ ๋Œ€๊ธฐ์—ด์— ์˜ฌ๋ฆฌ๊ณ  ์Šน์ธยท๋ฐ˜๋ คยท๋ฌด์‘๋‹ต(๊ตฌ๋งค ์œ ์ง€)์„ ์‚ฌ์œ ์™€ ํ•จ๊ป˜ ํ•ด์‹œ ์ฒด์ธ ๊ฐ์‚ฌ ๋กœ๊ทธ์— ๋‚จ๊ธฐ๋Š” HITL ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ๊ตฌ๋งค ์œ ์ง€ ๋Œ€๋น„ ๋น„์šฉ ์ ˆ๊ฐ๋ฅ ๊ณผ ์Šน์ธ ์ง€์—ฐยท์š”์ฒญ ๊ธ‰์ฆ์— ๋Œ€ํ•œ ๋ฏผ๊ฐ๋„, ์ž๋™ ์Šน์ธ ์‹œ์˜ ์œ„ํ—˜์„ ๋น„๊ตํ•œ๋‹ค.\"\"\"\n", "import hashlib\n", "import json\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด ๋ถ€ํ’ˆ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€ํ’ˆยท์žฌ๊ณ ยท๊ทœ๊ฒฉ ๊ฐ’ ์•„๋‹˜)\n", "# ํ๊ธฐ์œจ์ด ๋†’์€ ์ˆ˜๋ฆฌ๋ถ€์† ์ค‘ 3D ํ”„๋ฆฐํŒ…์œผ๋กœ ๋Œ€์ฒด ์ œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์ƒ ๋ถ€ํ’ˆ ์ž๋ฃŒ๋ฅผ ๋งŒ๋“ ๋‹ค.\n", "# ์†Œ์žฌ ๋ฌผ์„ฑยท์น˜์ˆ˜ยทํ•˜์ค‘์€ ๊ต์œก์šฉ์œผ๋กœ ๋‹จ์ˆœํ™”ํ•œ ๊ฐ’์ด๋ฉฐ ์‹ค์ œ ์‹œํ—˜ ์„ฑ์ ์„œ๋‚˜ ๊ตฐ ๊ทœ๊ฒฉ ๊ฐ’๊ณผ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "MAT = { # ์†Œ์žฌ: (๊ณต์ •, ๊ธฐ์ค€ ์ธ์žฅ๊ฐ•๋„ MPa, ๋ฐ€๋„ g/cm3, ์†Œ์žฌ ๋‹จ๊ฐ€ ๋งŒ์›/kg, ์ถœ๋ ฅ๋น„ ๋งŒ์›/h, ์ตœ์†Œ ๋ฒฝ๋‘๊ป˜ mm, ์ถœ๋ ฅ ์†๋„ cm3/h)\n", " \"PA12\": (\"polymer\", 45, 1.0, 8, 3, 0.8, 18), \"PETG\": (\"polymer\", 50, 1.27, 5, 2, 0.8, 15),\n", " \"CF-PA\": (\"polymer\", 70, 1.2, 14, 3, 1.0, 12), \"AlSi10Mg\": (\"metal\", 350, 2.67, 30, 25, 0.5, 4),\n", " \"SS316L\": (\"metal\", 520, 7.9, 28, 25, 0.6, 3.5), \"Ti64\": (\"metal\", 900, 4.4, 90, 30, 0.6, 3)}\n", "CATS = [\"bracket\", \"housing\", \"clip\", \"gear\", \"duct\", \"handle\"]\n", "CAT_SCRAP = {\"bracket\": 0.04, \"housing\": 0.05, \"clip\": 0.12, \"gear\": 0.09, \"duct\": 0.08, \"handle\": 0.10} # ๋ถ€ํ’ˆ๊ตฐ๋ณ„ ๊ธฐ๋ณธ ์›” ํ๊ธฐ์œจ\n", "BUILD_MM = {\"polymer\": 300, \"metal\": 200} # ๊ฐ€์ƒ ์ถœ๋ ฅ ๊ฐ€๋Šฅ ์ตœ๋Œ€ ์น˜์ˆ˜\n", "\n", "\n", "def make_parts(n=300, seed=0):\n", " \"\"\"๊ฐ€์ƒ ๋ถ€ํ’ˆ n์ข…: ํ˜•์ƒ(๋ถ€ํ”ผยท๋ฒฝ๋‘๊ป˜ยท๋Œ์ถœ ๋น„์œจ), ์†Œ์žฌ, ์‚ฌ์šฉ ํ•˜์ค‘, ํ๊ธฐ์œจ๊ณผ ์กฐ๋‹ฌ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " mat = rng.choice(list(MAT), n, p=[0.2, 0.15, 0.1, 0.2, 0.2, 0.15])\n", " proc = np.array([MAT[m][0] for m in mat])\n", " vol = np.clip(rng.lognormal(np.log(30), 0.9, n), 2, 800)\n", " aspect = rng.lognormal(0.3, 0.4, n)\n", " max_dim = 10 * vol ** (1 / 3) * aspect ** (1 / 3) * rng.uniform(1.0, 1.6, n)\n", " wall = np.clip(rng.lognormal(np.log(2.0), 0.6, n), 0.3, 8)\n", " ovh = rng.beta(2, 5, n)\n", " is_poly = proc == \"polymer\"\n", " df = pd.DataFrame({\n", " \"part_id\": [f\"P{i:03d}\" for i in range(n)], \"cat\": rng.choice(CATS, n, p=[0.2, 0.15, 0.2, 0.1, 0.15, 0.2]), \"material\": mat, \"process\": proc,\n", " \"volume_cm3\": vol, \"max_dim_mm\": max_dim, \"area_cm2\": 6 * vol ** (2 / 3) * rng.uniform(1.0, 2.2, n), \"min_wall_mm\": wall, \"overhang\": ovh,\n", " \"n_holes\": rng.poisson(2.0, n), \"thread\": rng.random(n) < 0.25, \"orient_deg\": rng.uniform(0, 90, n),\n", " \"layer_mm\": np.where(is_poly, rng.choice([0.1, 0.2, 0.3], n), rng.choice([0.03, 0.05], n)),\n", " \"infill\": np.where(is_poly, rng.choice([40, 60, 80, 100], n, p=[0.15, 0.3, 0.3, 0.25]), 100),\n", " \"load_N\": np.clip(rng.lognormal(np.where(is_poly, np.log(230), np.log(2300)), 0.7, n), 10, 20000), \"temp_C\": rng.uniform(-20, 90, n),\n", " \"crit\": rng.choice([1, 2, 3], n, p=[0.35, 0.4, 0.25]), \"cert\": rng.choice([0, 1], n, p=[0.45, 0.55]), # ์†Œ์žฌ ์ธ์ฆ์„œ ๋ณด์œ \n", " \"import_part\": rng.random(n) < 0.4})\n", " df[\"scrap_rate\"] = np.clip(df[\"cat\"].map(CAT_SCRAP) * rng.lognormal(0, 0.35, n) * (1 + 0.5 * (df[\"load_N\"] > 800)) * (1 + 0.3 * (df[\"temp_C\"] > 60)), 0.005, 0.5)\n", " df[\"lead_days\"] = np.clip(rng.lognormal(np.log(25), 0.5, n) * np.where(df[\"import_part\"], 2.2, 1.0), 5, 300)\n", " # ---- ์ˆจ์€ ์ง„์‹ค๊ฐ’(์‹ค์ œ ์‹œํ—˜ ๊ฒฐ๊ณผ์— ํ•ด๋‹น): ๋ชจ๋ธ์€ ์œ„ ์ž…๋ ฅ์œผ๋กœ ์ด๊ฒƒ์„ ์˜ˆ์ธกํ•œ๋‹ค ----\n", " eff = np.where(is_poly, 0.55 + 0.45 * df[\"infill\"] / 100, 1 - 0.3 * ovh * 0.2)\n", " aniso = np.where(is_poly, 1 - 0.3 * np.sin(np.radians(df[\"orient_deg\"])) ** 2, 1 - 0.08 * np.sin(np.radians(df[\"orient_deg\"])))\n", " heat = np.where(is_poly, 1 - 0.35 * np.clip((df[\"temp_C\"] - 40) / 80, 0, 1), 0.98)\n", " base = np.array([MAT[m][1] for m in mat])\n", " df[\"strength_mpa\"] = base * eff * aniso * heat * rng.lognormal(0, 0.06, n)\n", " cross = df[\"min_wall_mm\"] * np.sqrt(df[\"max_dim_mm\"]) * 1.0 # ํ•˜์ค‘ ์ง€์ง€ ๋‹จ๋ฉด์  mm2(๋‹จ์ˆœํ™”)\n", " df[\"stress_mpa\"] = df[\"load_N\"] / cross\n", " df[\"safety\"] = df[\"strength_mpa\"] / df[\"stress_mpa\"]\n", " ratio = np.clip(df[\"stress_mpa\"] / df[\"strength_mpa\"], 0, 1.5)\n", " df[\"log_cycles\"] = np.clip(7.5 - 6.0 * ratio + rng.normal(0, 0.25, n), 2, 7.5) # ํ”ผ๋กœ ์ˆ˜๋ช… log10(ํšŒ)\n", " support = 1 + 0.5 * ovh\n", " df[\"build_h\"] = vol * support / np.array([MAT[m][6] for m in mat]) + 0.5\n", " mass_kg = vol * np.array([MAT[m][2] for m in mat]) * support / 1000\n", " df[\"print_cost\"] = mass_kg * np.array([MAT[m][3] for m in mat]) + df[\"build_h\"] * np.array([MAT[m][4] for m in mat]) + 3\n", " df[\"buy_cost\"] = df[\"print_cost\"] * rng.lognormal(0.25, 0.45, n) + 5 # ๊ตฌ๋งค ๋‹จ๊ฐ€(๋งŒ์›, ๊ฐ€์ƒ)\n", " wall_ok = df[\"min_wall_mm\"] >= np.array([MAT[m][5] for m in mat]) * rng.uniform(0.95, 1.25, n)\n", " dim_ok = df[\"max_dim_mm\"] <= np.where(is_poly, BUILD_MM[\"polymer\"], BUILD_MM[\"metal\"])\n", " warp = (~is_poly) & (df[\"max_dim_mm\"] > 140) & (df[\"overhang\"] > 0.3) # ๊ธˆ์† ๋Œ€ํ˜• ์˜ค๋ฒ„ํ–‰ ๋ณ€ํ˜•\n", " thr_bad = df[\"thread\"] & (df[\"min_wall_mm\"] < 1.5) & is_poly # ์–‡์€ ๋‚˜์‚ฌ๋ถ€ ํŒŒ์†\n", " df[\"printable\"] = wall_ok & dim_ok & (ovh < 0.5) & ~warp & ~thr_bad\n", " risk = np.clip(1 - df[\"safety\"] / 3, 0, 1)\n", " df[\"conform\"] = np.clip(100 * (0.45 * np.minimum(df[\"safety\"] / 3, 1) + 0.25 * np.clip((df[\"log_cycles\"] - 3) / 4.5, 0, 1)\n", " + 0.15 * (1 - np.clip(ovh * 1.2, 0, 1)) + 0.15 * df[\"cert\"]) - 8 * risk * df[\"crit\"] / 3 + rng.normal(0, 2, n), 0, 100)\n", " df[\"demand_month\"] = rng.poisson(np.where(df[\"cat\"].isin([\"clip\", \"handle\"]), 40, 15)) + 1 # ์›” ์†Œ์š”์ˆ˜๋Ÿ‰(๊ฐ€์ƒ)\n", " df[\"scrap_cost\"] = df[\"scrap_rate\"] * df[\"demand_month\"] * df[\"buy_cost\"] # ์›” ํ๊ธฐ ์†์‹ค์•ก(๋งŒ์›, ๊ฐ€์ƒ)\n", " return df\n", "\n", "# ---- ๋‹จ์ˆœํ™”ํ•œ ํ•ด์„(FEA ๋Œ€์šฉ) ๊ฐ’๊ณผ ํŒŒ์ƒ ํ”ผ์ฒ˜: ์ž…๋ ฅ ์ •๋ณด๋งŒ์œผ๋กœ ๊ณ„์‚ฐํ•œ๋‹ค(์‹œํ—˜ ๊ฒฐ๊ณผ ๋ˆ„์„ค ์—†์Œ) ----\n", "def make_features(d):\n", " \"\"\"๊ฐ„์ด ์‘๋ ฅ ํ•ด์„(์‘๋ ฅ์ง‘์ค‘๊ณ„์ˆ˜ ํฌํ•จ)๊ณผ ์†Œ์žฌยท๊ณต์ • ์ง€์‹์—์„œ ๋งŒ๋“  ํŒŒ์ƒ ํ”ผ์ฒ˜ 16๊ฐœ.\"\"\"\n", " X = pd.DataFrame(index=d.index)\n", " base = d[\"material\"].map(lambda m: MAT[m][1]).astype(float)\n", " X[\"base_strength\"] = base\n", " X[\"is_metal\"] = (d[\"process\"] == \"metal\").astype(int)\n", " X[\"cross_mm2\"] = d[\"min_wall_mm\"] * np.sqrt(d[\"max_dim_mm\"])\n", " X[\"kt\"] = 1 + 0.15 * d[\"n_holes\"] + 0.5 * d[\"thread\"].astype(int) # ๊ตฌ๋ฉยท๋‚˜์‚ฌ๋ถ€ ์‘๋ ฅ์ง‘์ค‘๊ณ„์ˆ˜(๊ฐ„์ด)\n", " X[\"fea_stress\"] = d[\"load_N\"] / X[\"cross_mm2\"] * X[\"kt\"] # ๊ฐ„์ด ํ•ด์„ ์‘๋ ฅ(MPa)\n", " X[\"stress_ratio\"] = X[\"fea_stress\"] / base # ๊ธฐ์ค€๊ฐ•๋„ ๋Œ€๋น„ ์‘๋ ฅ๋น„\n", " X[\"infill_eff\"] = np.where(X[\"is_metal\"] == 1, 1.0, 0.55 + 0.45 * d[\"infill\"] / 100)\n", " X[\"orient_sin2\"] = np.sin(np.radians(d[\"orient_deg\"])) ** 2\n", " X[\"temp_derate\"] = np.where(X[\"is_metal\"] == 1, 0.0, np.clip((d[\"temp_C\"] - 40) / 80, 0, 1))\n", " X[\"overhang_layer\"] = d[\"overhang\"] * d[\"layer_mm\"]\n", " X[\"slender\"] = d[\"max_dim_mm\"] / d[\"min_wall_mm\"]\n", " X[\"surf_vol\"] = d[\"area_cm2\"] / d[\"volume_cm3\"]\n", " X[\"wall_ratio\"] = d[\"min_wall_mm\"] / d[\"material\"].map(lambda m: MAT[m][5])\n", " X[\"load_per_vol\"] = d[\"load_N\"] / d[\"volume_cm3\"]\n", " X[\"cert\"] = d[\"cert\"].astype(int)\n", " X[\"crit\"] = d[\"crit\"]\n", " return X\n", "\n", "# ---- ๋Œ€์ฒด ์ œ์ž‘ ์š”์ฒญ ๋น„์šฉ ๋ชจ์˜(๊ฐ€์ƒ ๋‹จ๊ฐ€): ๊ตฌ๋งค ๋Œ€๊ธฐ vs 3D ํ”„๋ฆฐํŒ… ----\n", "DOWN = {1: 0.5, 2: 2.0, 3: 5.0} # [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถ€ํ’ˆ ๋ถ€์žฌ๋กœ ์ธํ•œ ์žฅ๋น„ ๊ฐ€๋™์ค‘๋‹จ ๋น„์šฉ(๋งŒ์›/์ผ), ์ค‘์š”๋„๋ณ„\n", "FAIL = {1: 10.0, 2: 40.0, 3: 150.0} # [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋Œ€์ฒดํ’ˆ ์žฅ์ฐฉ ํ›„ ๋ถˆํ•ฉ๊ฒฉ ์‹œ ์žฌ์ž‘์—…ยท์ž„๋ฌด ์˜ํ–ฅ ๋น„์šฉ(๋งŒ์›)\n", "POST = 3.0 # ํ›„์ฒ˜๋ฆฌยท๊ฒ€์‚ฌ ๋น„์šฉ(๋งŒ์›)\n", "\n", "\n", "def make_requests_month(catalog, n=250, seed=0):\n", " \"\"\"์›”๊ฐ„ ๋ถ€ํ’ˆ ์š”์ฒญ: ์›” ์†Œ์š”์ˆ˜๋Ÿ‰์ด ํฐ ๋ถ€ํ’ˆ์ด ๋” ์ž์ฃผ ์š”์ฒญ๋œ๋‹ค.\"\"\"\n", " return catalog.sample(n, replace=True, weights=catalog[\"demand_month\"], random_state=seed).reset_index(drop=True)\n", "\n", "\n", "def buy_cost(r):\n", " return r[\"buy_cost\"] + r[\"lead_days\"] * DOWN[r[\"crit\"]]\n", "\n", "\n", "def print_outcome(r, delay_d=1.0):\n", " \"\"\"์‹ค์ œ ์‹œํ—˜ ๊ฒฐ๊ณผ(์ˆจ์€ ์ง„์‹ค)์— ๋”ฐ๋ฅธ ๋Œ€์ฒด ์ œ์ž‘ ๋น„์šฉ. ๋ถˆํ•ฉ๊ฒฉ์ด๋ฉด ๊ตฌ๋งค๋กœ ๋˜๋Œ์•„๊ฐ€๊ณ  ๋ถˆํ•ฉ๊ฒฉ ๋น„์šฉ์ด ๋“ ๋‹ค.\"\"\"\n", " wait = (r[\"build_h\"] / 24 + delay_d + 2.0) * DOWN[r[\"crit\"]]\n", " ok = bool(r[\"printable\"] and r[\"conform\"] >= 70)\n", " c = r[\"print_cost\"] + POST + wait\n", " return (c, True) if ok else (c + buy_cost(r) + FAIL[r[\"crit\"]], False)\n", "\n", "\n", "class Audit:\n", " def __init__(self):\n", " self.log = []\n", "\n", " def add(self, **body):\n", " prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n", " body = {\"seq\": len(self.log), **body}\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 verify(self):\n", " prev = \"GENESIS\"\n", " for e in self.log:\n", " b = {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(b, sort_keys=True, ensure_ascii=False)).encode()).hexdigest() != e[\"hash\"]:\n", " return e[\"seq\"]\n", " prev = e[\"hash\"]\n", " return None\n", "\n", "\n", "def officer(r, rng):\n", " \"\"\"[๋ชจ์˜ ์ •๋น„๊ธฐ์ˆ ๊ด€] ํ•ต์‹ฌ ๋ถ€ํ’ˆ์˜ ์ธ์ฆ์„œ ๋ฏธ๋ณด์œ ยท๋‚ฎ์€ ์ ์ˆ˜ยท์žฅ์‹œ๊ฐ„ ์ ์œ ๋Š” ๋ฐ˜๋ ค, 8%๋Š” ๊ธฐํ•œ ๋‚ด ๋ฌด์‘๋‹ต(๊ตฌ๋งค ์œ ์ง€).\"\"\"\n", " if rng.random() < 0.08:\n", " return \"๋ฌด์‘๋‹ต\", \"๊ธฐํ•œ ๋‚ด ์‘๋‹ต ์—†์Œ -> ๊ธฐ์กด ์กฐ๋‹ฌ ์œ ์ง€\"\n", " if r[\"crit\"] == 3 and r[\"cert\"] == 0:\n", " return \"๋ฐ˜๋ ค\", \"ํ•ต์‹ฌ ๋ถ€ํ’ˆ ์†Œ์žฌ ์ธ์ฆ์„œ ์—†์Œ\"\n", " if r[\"crit\"] == 3 and r[\"score\"] < 80:\n", " return \"๋ฐ˜๋ ค\", f\"ํ•ต์‹ฌ ๋ถ€ํ’ˆ ์ ์ˆ˜ {r['score']:.0f} < 80\"\n", " if r[\"build_h\"] > 80:\n", " return \"๋ฐ˜๋ ค\", f\"์ถœ๋ ฅ {r['build_h']:.0f}์‹œ๊ฐ„์œผ๋กœ ์žฅ๋น„ ์ ์œ  ๊ณผ๋‹ค\"\n", " return \"์Šน์ธ\", f\"์ ์ˆ˜ {r['score']:.0f}, ๊ตฌ๋งค ๋Œ€๋น„ ์ด๋“ ํ™•์ธ\"\n", "\n", "\n", "def run(req, mode, delay=1.0, seed=0, audit=None):\n", " \"\"\"mode: buy(๊ธฐ์กด ์กฐ๋‹ฌ) / hitl(์ œ์•ˆ -> ์Šน์ธ ์‹œ ์žฅ์ฐฉ) / auto(์ œ์•ˆ ์ „๋ถ€ ์ ์šฉ: ๋น„๊ต์šฉ ์ƒํ•œ, ์‹ค์ œ ์šด์šฉ ๊ธˆ์ง€)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " rows = []\n", " for k, r in req.iterrows():\n", " c_buy, how, fail = buy_cost(r), \"๊ตฌ๋งค\", False\n", " proposed = mode != \"buy\" and r[\"score\"] >= 70 and r[\"rule_ok\"]\n", " if proposed:\n", " if mode == \"auto\":\n", " dec = \"์Šน์ธ\"\n", " else:\n", " dec, why = officer(r, rng)\n", " audit.add(event=\"PROPOSE\", part=r[\"part_id\"], score=round(float(r[\"score\"]), 1), status=\"PENDING\")\n", " audit.add(event=\"DECIDE\", part=r[\"part_id\"], decision=dec, reason=why, by=\"์ •๋น„๊ธฐ์ˆ ๊ด€(๋ชจ์˜)\")\n", " if dec == \"์Šน์ธ\":\n", " c, ok = print_outcome(r, delay)\n", " how, fail = \"3D ์ œ์ž‘\", not ok\n", " c_buy = c\n", " rows.append({\"part_id\": r[\"part_id\"], \"crit\": r[\"crit\"], \"proposed\": proposed, \"how\": how, \"fail\": fail, \"cost\": c_buy,\n", " \"dec\": dec if proposed else \"-\", \"scrap_cost\": r[\"scrap_cost\"]})\n", " return pd.DataFrame(rows)\n", "\n", "\n", "hist = make_parts(1000, seed=11)\n", "model = GradientBoostingRegressor(n_estimators=150, max_depth=3, learning_rate=0.06, random_state=0).fit(make_features(hist), hist[\"conform\"])\n", "cat = make_parts(300, seed=21)\n", "cat[\"score\"] = np.clip(model.predict(make_features(cat)), 0, 100)\n", "cat[\"rule_ok\"] = (cat[\"min_wall_mm\"] >= 1.0) & (cat[\"max_dim_mm\"] <= cat[\"process\"].map(BUILD_MM)) & (cat[\"overhang\"] < 0.5) # ํ˜•์ƒ Baseline(๋ฏธ์…˜ 3)\n", "req = make_requests_month(cat, 250, seed=3)\n", "\n", "audit = Audit()\n", "R = {m: run(req, m, seed=5, audit=audit) for m in (\"buy\", \"hitl\", \"auto\")}\n", "T = pd.DataFrame([{\"๋ฐฉ์‹\": n, \"์ด๋น„์šฉ(๋งŒ์›)\": round(R[m][\"cost\"].sum()), \"3D ์ œ์ž‘ ๊ฑด์ˆ˜\": int((R[m][\"how\"] == \"3D ์ œ์ž‘\").sum()), \"์žฅ์ฐฉ ํ›„ ๋ถˆํ•ฉ๊ฒฉ\": int(R[m][\"fail\"].sum()),\n", " \"๋ถˆํ•ฉ๊ฒฉ ๋น„์šฉ(๋งŒ์›)\": int(sum(FAIL[c] for c in R[m].loc[R[m][\"fail\"], \"crit\"]))}\n", " for m, n in [(\"buy\", \"๊ธฐ์กด ์กฐ๋‹ฌ(๊ตฌ๋งค)\"), (\"hitl\", \"์ œ์•ˆ + ์ •๋น„๊ธฐ์ˆ ๊ด€ ์Šน์ธ\"), (\"auto\", \"์ œ์•ˆ ์ „๋ถ€ ์ ์šฉ(๋น„๊ต์šฉ)\")] for n in [n]])\n", "T.insert(0, \"๋ฐฉ์‹\", T.pop(\"๋ฐฉ์‹\"))\n", "print(\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์›”๊ฐ„ ๋Œ€์ฒด ์ œ์ž‘ ์š”์ฒญ 250๊ฑด ๋ชจ์˜\")\n", "print(T.to_string(index=False))\n", "cut = 1 - T.loc[1, \"์ด๋น„์šฉ(๋งŒ์›)\"] / T.loc[0, \"์ด๋น„์šฉ(๋งŒ์›)\"]\n", "print(f\"HITL ์ ์šฉ ์ด๋น„์šฉ ์ ˆ๊ฐ๋ฅ  {cut:.1%} (๋ชฉํ‘œ 15% ์ด์ƒ: {'๋‹ฌ์„ฑ' if cut >= 0.15 else '๋ฏธ๋‹ฌ'})\")\n", "print(f\"์ œ์•ˆ ์ „๋ถ€ ์ ์šฉ์€ ์ ˆ๊ฐ๋ฅ  {1 - T.loc[2, '์ด๋น„์šฉ(๋งŒ์›)'] / T.loc[0, '์ด๋น„์šฉ(๋งŒ์›)']:.1%}๋กœ ๋” ๋†’์ง€๋งŒ ์ •๋น„๊ธฐ์ˆ ๊ด€ ํŒ๋‹จ ์—†์ด ์žฅ์ฐฉํ•˜๋Š” ๋ฐฉ์‹์ด๋ผ ์‹ค์ œ ์šด์šฉ์—์„œ๋Š” ๊ธˆ์ง€ํ•œ๋‹ค(๋น„๊ต์šฉ ์ƒํ•œ)\")\n", "rj = R[\"hitl\"][R[\"hitl\"][\"dec\"] == \"๋ฐ˜๋ ค\"]\n", "would_fail = sum(not print_outcome(req.loc[i])[1] for i in rj.index)\n", "print(f\"๋ฐ˜๋ ค {len(rj)}๊ฑด ์ค‘ ์‹ค์ œ๋กœ ์žฅ์ฐฉํ–ˆ๋‹ค๋ฉด ๋ถˆํ•ฉ๊ฒฉ์ด์—ˆ์„ ๋ถ€ํ’ˆ {would_fail}๊ฑด -> \"\n", " + (\"์Šน์ธ ๋‹จ๊ณ„๊ฐ€ ๋ถˆํ•ฉ๊ฒฉ ์œ„ํ—˜์„ ๋ฏธ๋ฆฌ ๊ฑธ๋Ÿฌ ๋ƒˆ๋‹ค\" if would_fail else \"์ด๋ฒˆ ๋ชจ์˜์—์„œ๋Š” ๋ฐ˜๋ ค๊ฐ€ ๋ชจ๋‘ ๋ณด์ˆ˜์  ํŒ๋‹จ์ด์—ˆ๋‹ค(๊ธฐํšŒ์†์‹ค). ๋ฐ˜๋ ค ๊ธฐ์ค€์€ ๋ฏธ์…˜ 9 AAR์—์„œ ์žฌ๊ฒ€ํ† ํ•œ๋‹ค\"))\n", "\n", "dec = R[\"hitl\"][R[\"hitl\"][\"proposed\"]][\"dec\"].value_counts()\n", "print(f\"\\n๋ณ€๊ฒฝ ์ œ์•ˆ {int(dec.sum())}๊ฑด: \" + \", \".join(f\"{k} {v}๊ฑด\" for k, v in dec.items()))\n", "rej = [e[\"reason\"] for e in audit.log if e.get(\"decision\") == \"๋ฐ˜๋ ค\"]\n", "print(\"๋ฐ˜๋ ค ์‚ฌ์œ  ์˜ˆ:\", \"; \".join([x for x in (next((x for x in rej if k in x), None) for k in (\"์ธ์ฆ์„œ\", \"์ ์ˆ˜\", \"์ ์œ \")) if x]))\n", "n_dec = sum(e[\"event\"] == \"DECIDE\" for e in audit.log)\n", "print(f\"์ œ์•ˆ {int(dec.sum())}๊ฑด ์ค‘ ๊ฒฐ์ • ๊ธฐ๋ก {n_dec}๊ฑด(๋กœ๊น…๋ฅ  {n_dec / max(int(dec.sum()), 1):.0%}), ์ž๋™ ์Šน์ธ 0๊ฑด, ๋ฌด์‘๋‹ต ์‹œ ๊ธฐ์กด ์กฐ๋‹ฌ ์œ ์ง€\")\n", "print(f\"๊ฐ์‚ฌ ๋กœ๊ทธ ๋ฌด๊ฒฐ์„ฑ: {'์ •์ƒ' if audit.verify() is None else '์ด์ƒ'}\", end=\", \")\n", "audit.log[5][\"reason\"] = \"์‚ฌํ›„ ์ˆ˜์ •\"\n", "print(f\"์‚ฌํ›„ ๋ณ€์กฐ ์‹œ seq {audit.verify()}์—์„œ ํƒ์ง€\")\n", "\n", "print(\"\\n[๋ฏผ๊ฐ๋„] ์Šน์ธ ์ง€์—ฐ(์ผ) -> ๋น„์šฉ ์ ˆ๊ฐ๋ฅ \")\n", "for d in (0.0, 1.0, 3.0, 7.0, 14.0):\n", " r_ = run(req, \"hitl\", delay=d, seed=5, audit=Audit())\n", " print(f\" {d:>4}: {1 - r_['cost'].sum() / T.loc[0, '์ด๋น„์šฉ(๋งŒ์›)']:.1%}\")\n", "surge = make_requests_month(cat, 400, seed=3)\n", "b, h = run(surge, \"buy\"), run(surge, \"hitl\", seed=5, audit=Audit())\n", "print(f\"์š”์ฒญ 1.6๋ฐฐ ๊ธ‰์ฆ(400๊ฑด) ์‹œ๋‚˜๋ฆฌ์˜ค: ๋น„์šฉ ์ ˆ๊ฐ๋ฅ  {1 - h['cost'].sum() / b['cost'].sum():.1%}, ์Šน์ธ ๋Œ€๊ธฐ ๊ฑด์ˆ˜ {int(h['proposed'].sum())}๊ฑด -> ์ •๋น„๊ธฐ์ˆ ๊ด€ ๊ฒ€ํ†  ์ธ๋ ฅ์ด ๋ณ‘๋ชฉ\")\n", "\n", "plt.figure(figsize=(7, 4))\n", "plt.bar([\"buy\", \"HITL\", \"auto(compare)\"], T[\"์ด๋น„์šฉ(๋งŒ์›)\"], color=[\"tab:gray\", \"tab:blue\", \"tab:orange\"]); plt.ylabel(\"total cost (virtual)\")\n", "plt.title(\"HITL simulator: monthly cost\"); plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 07 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์›”๊ฐ„ ๋Œ€์ฒด ์ œ์ž‘ ์š”์ฒญ 250๊ฑด ๋ชจ์˜\n", " ๋ฐฉ์‹ ์ด๋น„์šฉ(๋งŒ์›) 3D ์ œ์ž‘ ๊ฑด์ˆ˜ ์žฅ์ฐฉ ํ›„ ๋ถˆํ•ฉ๊ฒฉ ๋ถˆํ•ฉ๊ฒฉ ๋น„์šฉ(๋งŒ์›)\n", " ๊ธฐ์กด ์กฐ๋‹ฌ(๊ตฌ๋งค) 102267 0 0 0\n", "์ œ์•ˆ + ์ •๋น„๊ธฐ์ˆ ๊ด€ ์Šน์ธ 86487 72 1 10\n", "์ œ์•ˆ ์ „๋ถ€ ์ ์šฉ(๋น„๊ต์šฉ) 80225 96 1 10\n", "HITL ์ ์šฉ ์ด๋น„์šฉ ์ ˆ๊ฐ๋ฅ  15.4% (๋ชฉํ‘œ 15% ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "์ œ์•ˆ ์ „๋ถ€ ์ ์šฉ์€ ์ ˆ๊ฐ๋ฅ  21.6%๋กœ ๋” ๋†’์ง€๋งŒ ์ •๋น„๊ธฐ์ˆ ๊ด€ ํŒ๋‹จ ์—†์ด ์žฅ์ฐฉํ•˜๋Š” ๋ฐฉ์‹์ด๋ผ ์‹ค์ œ ์šด์šฉ์—์„œ๋Š” ๊ธˆ์ง€ํ•œ๋‹ค(๋น„๊ต์šฉ ์ƒํ•œ)\n", "๋ฐ˜๋ ค 15๊ฑด ์ค‘ ์‹ค์ œ๋กœ ์žฅ์ฐฉํ–ˆ๋‹ค๋ฉด ๋ถˆํ•ฉ๊ฒฉ์ด์—ˆ์„ ๋ถ€ํ’ˆ 0๊ฑด -> ์ด๋ฒˆ ๋ชจ์˜์—์„œ๋Š” ๋ฐ˜๋ ค๊ฐ€ ๋ชจ๋‘ ๋ณด์ˆ˜์  ํŒ๋‹จ์ด์—ˆ๋‹ค(๊ธฐํšŒ์†์‹ค). ๋ฐ˜๋ ค ๊ธฐ์ค€์€ ๋ฏธ์…˜ 9 AAR์—์„œ ์žฌ๊ฒ€ํ† ํ•œ๋‹ค\n", "\n", "๋ณ€๊ฒฝ ์ œ์•ˆ 96๊ฑด: ์Šน์ธ 72๊ฑด, ๋ฐ˜๋ ค 15๊ฑด, ๋ฌด์‘๋‹ต 9๊ฑด\n", "๋ฐ˜๋ ค ์‚ฌ์œ  ์˜ˆ: ํ•ต์‹ฌ ๋ถ€ํ’ˆ ์†Œ์žฌ ์ธ์ฆ์„œ ์—†์Œ; ํ•ต์‹ฌ ๋ถ€ํ’ˆ ์ ์ˆ˜ 79 < 80\n", "์ œ์•ˆ 96๊ฑด ์ค‘ ๊ฒฐ์ • ๊ธฐ๋ก 96๊ฑด(๋กœ๊น…๋ฅ  100%), ์ž๋™ ์Šน์ธ 0๊ฑด, ๋ฌด์‘๋‹ต ์‹œ ๊ธฐ์กด ์กฐ๋‹ฌ ์œ ์ง€\n", "๊ฐ์‚ฌ ๋กœ๊ทธ ๋ฌด๊ฒฐ์„ฑ: ์ •์ƒ, ์‚ฌํ›„ ๋ณ€์กฐ ์‹œ seq 5์—์„œ ํƒ์ง€\n", "\n", "[๋ฏผ๊ฐ๋„] ์Šน์ธ ์ง€์—ฐ(์ผ) -> ๋น„์šฉ ์ ˆ๊ฐ๋ฅ \n", " 0.0: 15.6%\n", " 1.0: 15.4%\n", " 3.0: 15.1%\n", " 7.0: 14.5%\n", " 14.0: 13.5%\n", "์š”์ฒญ 1.6๋ฐฐ ๊ธ‰์ฆ(400๊ฑด) ์‹œ๋‚˜๋ฆฌ์˜ค: ๋น„์šฉ ์ ˆ๊ฐ๋ฅ  13.1%, ์Šน์ธ ๋Œ€๊ธฐ ๊ฑด์ˆ˜ 141๊ฑด -> ์ •๋น„๊ธฐ์ˆ ๊ด€ ๊ฒ€ํ†  ์ธ๋ ฅ์ด ๋ณ‘๋ชฉ\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 08. ๋ณต์žก ํ˜•์ƒ ๋ถ€ํ’ˆ 50์ข… ์ผ๊ด„ ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋ณต์žก ํ˜•์ƒ ๋ถ€ํ’ˆ 50์ข… ์ผ๊ด„ ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ณต์žก ํ˜•์ƒ ๋ถ€ํ’ˆ 50์ข…(์ตœ๋Œ€ 500์ข…)์„ ์ผ๊ด„ ํ‰๊ฐ€ํ•˜๋Š” ์ƒํ™ฉ์—์„œ ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€์™€ ๋Œ€์‹œ๋ณด๋“œ๊ฐ€ ์ œ๋•Œ ๋™์ž‘ํ•˜๋Š”์ง€ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋ณต์žก ํ˜•์ƒ ๋ถ€ํ’ˆ 50ยท100ยท200ยท500์ข… ์ผ๊ด„ ์š”์ฒญ์— ์†์ƒ ๋ ˆ์ฝ”๋“œ์™€ ๋ชจ๋ธ ์žฅ์• ๋ฅผ ์ฃผ์ž…ํ•˜๊ณ  ์ฒ˜๋ฆฌ์™€ ํ™”๋ฉด ์ƒ์„ฑ ์‹œ๊ฐ„, ์œ ์‹ค ์—ฌ๋ถ€, ํ•„ํ„ฐ ์กฐํšŒ ์‘๋‹ต์„ ์ธก์ • ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/dashboard/mnt06_app.py (Streamlit/Dash ๋“ฑ)` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์˜์‚ฌ๊ฒฐ์ • ์ง€์›์— ์ตœ์ ํ™”๋œ ์‹œ๊ฐ์  ์œ„๊ณ„ ๋ฐ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX)\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ ์‹œ๊ฐ„ 2์ดˆ ์ด๋‚ด, ๊ฒฝ๋ณด ๋ฐœ๋ น ์ •ํ™•๋„ 95% ์ด์ƒ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์›น ๊ธฐ๋ฐ˜ ๋ถ„์„ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌ๋™ ๋ฐ ์‹ค์‹œ๊ฐ„ ํ•„ํ„ฐ๋ง ๊ธฐ๋Šฅ ๊ฒ€์ฆ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ์‹ค์ œ ๊ตฐ์ˆ˜ ๋ฐ์ดํ„ฐ ์—ฐ๋™ ํ™”๋ฉด ํ…Œ์ŠคํŠธ ๋ฐ UI ๋™์ž‘ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๋ณต์žก ํ˜•์ƒ ๋ถ€ํ’ˆ 50์ข…(์ตœ๋Œ€ 500์ข…) ์ผ๊ด„ ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€๋ฅผ ์š”์ฒญํ•˜๋ฉด์„œ ์†์ƒ ๋ ˆ์ฝ”๋“œ ์ฃผ์ž…๊ณผ ํ‰๊ฐ€ ๋ชจ๋ธ ์žฅ์• ๋ฅผ ์ผ์œผ์ผœ, ์ผ๊ด„ ์ฒ˜๋ฆฌ+ํ™”๋ฉด ์ƒ์„ฑ ์‹œ๊ฐ„, '์‹ ๋ขฐ์„ฑ ๋ฏธ๋‹ฌ ๊ฒฝ๋ณด' ์ •ํ™•๋„, ์š”์ฒญ ์œ ์‹ค, ์†์ƒ ๋ฐ์ดํ„ฐ์˜ ์ˆ˜๋™ ํ™•์ธ ๋ถ„๋ฆฌ, ํ•„ํ„ฐ ์กฐํšŒ ์‘๋‹ต, ๋ชจ๋ธ ์žฅ์•  ์‹œ ๊ฐ„์ด ๊ทœ์น™์œผ๋กœ์˜ ๊ธฐ๋Šฅ ์ถ•์†Œ ์šด์šฉ์„ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด ๋ถ€ํ’ˆ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€ํ’ˆยท์žฌ๊ณ ยท๊ทœ๊ฒฉ ๊ฐ’ ์•„๋‹˜)\n", "# ํ๊ธฐ์œจ์ด ๋†’์€ ์ˆ˜๋ฆฌ๋ถ€์† ์ค‘ 3D ํ”„๋ฆฐํŒ…์œผ๋กœ ๋Œ€์ฒด ์ œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์ƒ ๋ถ€ํ’ˆ ์ž๋ฃŒ๋ฅผ ๋งŒ๋“ ๋‹ค.\n", "# ์†Œ์žฌ ๋ฌผ์„ฑยท์น˜์ˆ˜ยทํ•˜์ค‘์€ ๊ต์œก์šฉ์œผ๋กœ ๋‹จ์ˆœํ™”ํ•œ ๊ฐ’์ด๋ฉฐ ์‹ค์ œ ์‹œํ—˜ ์„ฑ์ ์„œ๋‚˜ ๊ตฐ ๊ทœ๊ฒฉ ๊ฐ’๊ณผ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "MAT = { # ์†Œ์žฌ: (๊ณต์ •, ๊ธฐ์ค€ ์ธ์žฅ๊ฐ•๋„ MPa, ๋ฐ€๋„ g/cm3, ์†Œ์žฌ ๋‹จ๊ฐ€ ๋งŒ์›/kg, ์ถœ๋ ฅ๋น„ ๋งŒ์›/h, ์ตœ์†Œ ๋ฒฝ๋‘๊ป˜ mm, ์ถœ๋ ฅ ์†๋„ cm3/h)\n", " \"PA12\": (\"polymer\", 45, 1.0, 8, 3, 0.8, 18), \"PETG\": (\"polymer\", 50, 1.27, 5, 2, 0.8, 15),\n", " \"CF-PA\": (\"polymer\", 70, 1.2, 14, 3, 1.0, 12), \"AlSi10Mg\": (\"metal\", 350, 2.67, 30, 25, 0.5, 4),\n", " \"SS316L\": (\"metal\", 520, 7.9, 28, 25, 0.6, 3.5), \"Ti64\": (\"metal\", 900, 4.4, 90, 30, 0.6, 3)}\n", "CATS = [\"bracket\", \"housing\", \"clip\", \"gear\", \"duct\", \"handle\"]\n", "CAT_SCRAP = {\"bracket\": 0.04, \"housing\": 0.05, \"clip\": 0.12, \"gear\": 0.09, \"duct\": 0.08, \"handle\": 0.10} # ๋ถ€ํ’ˆ๊ตฐ๋ณ„ ๊ธฐ๋ณธ ์›” ํ๊ธฐ์œจ\n", "BUILD_MM = {\"polymer\": 300, \"metal\": 200} # ๊ฐ€์ƒ ์ถœ๋ ฅ ๊ฐ€๋Šฅ ์ตœ๋Œ€ ์น˜์ˆ˜\n", "\n", "\n", "def make_parts(n=300, seed=0):\n", " \"\"\"๊ฐ€์ƒ ๋ถ€ํ’ˆ n์ข…: ํ˜•์ƒ(๋ถ€ํ”ผยท๋ฒฝ๋‘๊ป˜ยท๋Œ์ถœ ๋น„์œจ), ์†Œ์žฌ, ์‚ฌ์šฉ ํ•˜์ค‘, ํ๊ธฐ์œจ๊ณผ ์กฐ๋‹ฌ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " mat = rng.choice(list(MAT), n, p=[0.2, 0.15, 0.1, 0.2, 0.2, 0.15])\n", " proc = np.array([MAT[m][0] for m in mat])\n", " vol = np.clip(rng.lognormal(np.log(30), 0.9, n), 2, 800)\n", " aspect = rng.lognormal(0.3, 0.4, n)\n", " max_dim = 10 * vol ** (1 / 3) * aspect ** (1 / 3) * rng.uniform(1.0, 1.6, n)\n", " wall = np.clip(rng.lognormal(np.log(2.0), 0.6, n), 0.3, 8)\n", " ovh = rng.beta(2, 5, n)\n", " is_poly = proc == \"polymer\"\n", " df = pd.DataFrame({\n", " \"part_id\": [f\"P{i:03d}\" for i in range(n)], \"cat\": rng.choice(CATS, n, p=[0.2, 0.15, 0.2, 0.1, 0.15, 0.2]), \"material\": mat, \"process\": proc,\n", " \"volume_cm3\": vol, \"max_dim_mm\": max_dim, \"area_cm2\": 6 * vol ** (2 / 3) * rng.uniform(1.0, 2.2, n), \"min_wall_mm\": wall, \"overhang\": ovh,\n", " \"n_holes\": rng.poisson(2.0, n), \"thread\": rng.random(n) < 0.25, \"orient_deg\": rng.uniform(0, 90, n),\n", " \"layer_mm\": np.where(is_poly, rng.choice([0.1, 0.2, 0.3], n), rng.choice([0.03, 0.05], n)),\n", " \"infill\": np.where(is_poly, rng.choice([40, 60, 80, 100], n, p=[0.15, 0.3, 0.3, 0.25]), 100),\n", " \"load_N\": np.clip(rng.lognormal(np.where(is_poly, np.log(230), np.log(2300)), 0.7, n), 10, 20000), \"temp_C\": rng.uniform(-20, 90, n),\n", " \"crit\": rng.choice([1, 2, 3], n, p=[0.35, 0.4, 0.25]), \"cert\": rng.choice([0, 1], n, p=[0.45, 0.55]), # ์†Œ์žฌ ์ธ์ฆ์„œ ๋ณด์œ \n", " \"import_part\": rng.random(n) < 0.4})\n", " df[\"scrap_rate\"] = np.clip(df[\"cat\"].map(CAT_SCRAP) * rng.lognormal(0, 0.35, n) * (1 + 0.5 * (df[\"load_N\"] > 800)) * (1 + 0.3 * (df[\"temp_C\"] > 60)), 0.005, 0.5)\n", " df[\"lead_days\"] = np.clip(rng.lognormal(np.log(25), 0.5, n) * np.where(df[\"import_part\"], 2.2, 1.0), 5, 300)\n", " # ---- ์ˆจ์€ ์ง„์‹ค๊ฐ’(์‹ค์ œ ์‹œํ—˜ ๊ฒฐ๊ณผ์— ํ•ด๋‹น): ๋ชจ๋ธ์€ ์œ„ ์ž…๋ ฅ์œผ๋กœ ์ด๊ฒƒ์„ ์˜ˆ์ธกํ•œ๋‹ค ----\n", " eff = np.where(is_poly, 0.55 + 0.45 * df[\"infill\"] / 100, 1 - 0.3 * ovh * 0.2)\n", " aniso = np.where(is_poly, 1 - 0.3 * np.sin(np.radians(df[\"orient_deg\"])) ** 2, 1 - 0.08 * np.sin(np.radians(df[\"orient_deg\"])))\n", " heat = np.where(is_poly, 1 - 0.35 * np.clip((df[\"temp_C\"] - 40) / 80, 0, 1), 0.98)\n", " base = np.array([MAT[m][1] for m in mat])\n", " df[\"strength_mpa\"] = base * eff * aniso * heat * rng.lognormal(0, 0.06, n)\n", " cross = df[\"min_wall_mm\"] * np.sqrt(df[\"max_dim_mm\"]) * 1.0 # ํ•˜์ค‘ ์ง€์ง€ ๋‹จ๋ฉด์  mm2(๋‹จ์ˆœํ™”)\n", " df[\"stress_mpa\"] = df[\"load_N\"] / cross\n", " df[\"safety\"] = df[\"strength_mpa\"] / df[\"stress_mpa\"]\n", " ratio = np.clip(df[\"stress_mpa\"] / df[\"strength_mpa\"], 0, 1.5)\n", " df[\"log_cycles\"] = np.clip(7.5 - 6.0 * ratio + rng.normal(0, 0.25, n), 2, 7.5) # ํ”ผ๋กœ ์ˆ˜๋ช… log10(ํšŒ)\n", " support = 1 + 0.5 * ovh\n", " df[\"build_h\"] = vol * support / np.array([MAT[m][6] for m in mat]) + 0.5\n", " mass_kg = vol * np.array([MAT[m][2] for m in mat]) * support / 1000\n", " df[\"print_cost\"] = mass_kg * np.array([MAT[m][3] for m in mat]) + df[\"build_h\"] * np.array([MAT[m][4] for m in mat]) + 3\n", " df[\"buy_cost\"] = df[\"print_cost\"] * rng.lognormal(0.25, 0.45, n) + 5 # ๊ตฌ๋งค ๋‹จ๊ฐ€(๋งŒ์›, ๊ฐ€์ƒ)\n", " wall_ok = df[\"min_wall_mm\"] >= np.array([MAT[m][5] for m in mat]) * rng.uniform(0.95, 1.25, n)\n", " dim_ok = df[\"max_dim_mm\"] <= np.where(is_poly, BUILD_MM[\"polymer\"], BUILD_MM[\"metal\"])\n", " warp = (~is_poly) & (df[\"max_dim_mm\"] > 140) & (df[\"overhang\"] > 0.3) # ๊ธˆ์† ๋Œ€ํ˜• ์˜ค๋ฒ„ํ–‰ ๋ณ€ํ˜•\n", " thr_bad = df[\"thread\"] & (df[\"min_wall_mm\"] < 1.5) & is_poly # ์–‡์€ ๋‚˜์‚ฌ๋ถ€ ํŒŒ์†\n", " df[\"printable\"] = wall_ok & dim_ok & (ovh < 0.5) & ~warp & ~thr_bad\n", " risk = np.clip(1 - df[\"safety\"] / 3, 0, 1)\n", " df[\"conform\"] = np.clip(100 * (0.45 * np.minimum(df[\"safety\"] / 3, 1) + 0.25 * np.clip((df[\"log_cycles\"] - 3) / 4.5, 0, 1)\n", " + 0.15 * (1 - np.clip(ovh * 1.2, 0, 1)) + 0.15 * df[\"cert\"]) - 8 * risk * df[\"crit\"] / 3 + rng.normal(0, 2, n), 0, 100)\n", " df[\"demand_month\"] = rng.poisson(np.where(df[\"cat\"].isin([\"clip\", \"handle\"]), 40, 15)) + 1 # ์›” ์†Œ์š”์ˆ˜๋Ÿ‰(๊ฐ€์ƒ)\n", " df[\"scrap_cost\"] = df[\"scrap_rate\"] * df[\"demand_month\"] * df[\"buy_cost\"] # ์›” ํ๊ธฐ ์†์‹ค์•ก(๋งŒ์›, ๊ฐ€์ƒ)\n", " return df\n", "\n", "# ---- ๋‹จ์ˆœํ™”ํ•œ ํ•ด์„(FEA ๋Œ€์šฉ) ๊ฐ’๊ณผ ํŒŒ์ƒ ํ”ผ์ฒ˜: ์ž…๋ ฅ ์ •๋ณด๋งŒ์œผ๋กœ ๊ณ„์‚ฐํ•œ๋‹ค(์‹œํ—˜ ๊ฒฐ๊ณผ ๋ˆ„์„ค ์—†์Œ) ----\n", "def make_features(d):\n", " \"\"\"๊ฐ„์ด ์‘๋ ฅ ํ•ด์„(์‘๋ ฅ์ง‘์ค‘๊ณ„์ˆ˜ ํฌํ•จ)๊ณผ ์†Œ์žฌยท๊ณต์ • ์ง€์‹์—์„œ ๋งŒ๋“  ํŒŒ์ƒ ํ”ผ์ฒ˜ 16๊ฐœ.\"\"\"\n", " X = pd.DataFrame(index=d.index)\n", " base = d[\"material\"].map(lambda m: MAT[m][1]).astype(float)\n", " X[\"base_strength\"] = base\n", " X[\"is_metal\"] = (d[\"process\"] == \"metal\").astype(int)\n", " X[\"cross_mm2\"] = d[\"min_wall_mm\"] * np.sqrt(d[\"max_dim_mm\"])\n", " X[\"kt\"] = 1 + 0.15 * d[\"n_holes\"] + 0.5 * d[\"thread\"].astype(int) # ๊ตฌ๋ฉยท๋‚˜์‚ฌ๋ถ€ ์‘๋ ฅ์ง‘์ค‘๊ณ„์ˆ˜(๊ฐ„์ด)\n", " X[\"fea_stress\"] = d[\"load_N\"] / X[\"cross_mm2\"] * X[\"kt\"] # ๊ฐ„์ด ํ•ด์„ ์‘๋ ฅ(MPa)\n", " X[\"stress_ratio\"] = X[\"fea_stress\"] / base # ๊ธฐ์ค€๊ฐ•๋„ ๋Œ€๋น„ ์‘๋ ฅ๋น„\n", " X[\"infill_eff\"] = np.where(X[\"is_metal\"] == 1, 1.0, 0.55 + 0.45 * d[\"infill\"] / 100)\n", " X[\"orient_sin2\"] = np.sin(np.radians(d[\"orient_deg\"])) ** 2\n", " X[\"temp_derate\"] = np.where(X[\"is_metal\"] == 1, 0.0, np.clip((d[\"temp_C\"] - 40) / 80, 0, 1))\n", " X[\"overhang_layer\"] = d[\"overhang\"] * d[\"layer_mm\"]\n", " X[\"slender\"] = d[\"max_dim_mm\"] / d[\"min_wall_mm\"]\n", " X[\"surf_vol\"] = d[\"area_cm2\"] / d[\"volume_cm3\"]\n", " X[\"wall_ratio\"] = d[\"min_wall_mm\"] / d[\"material\"].map(lambda m: MAT[m][5])\n", " X[\"load_per_vol\"] = d[\"load_N\"] / d[\"volume_cm3\"]\n", " X[\"cert\"] = d[\"cert\"].astype(int)\n", " X[\"crit\"] = d[\"crit\"]\n", " return X\n", "\n", "ALERT, BAND = 70.0, 5.0 # ์ ์ˆ˜ 65 ๋ฏธ๋งŒ '๊ฒฝ๋ณด', 75 ์ด์ƒ '์ •์ƒ', ๊ทธ ์‚ฌ์ด๋Š” ๋ชจ๋ธ ๋ถˆํ™•์‹ค ๊ตฌ๊ฐ„์ด๋ผ '๋ณด๋ฅ˜(์ˆ˜๋™ ํ™•์ธ)'\n", "\n", "\n", "def make_batch(n, seed):\n", " \"\"\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ณต์žก ํ˜•์ƒ(๋Œ์ถœ ๋งŽ๊ณ  ๊ตฌ๋ฉ 3๊ฐœ ์ด์ƒ) ๋ถ€ํ’ˆ์„ n์ข… ๋ฝ‘์•„ ํ•œ๊บผ๋ฒˆ์— ํ‰๊ฐ€๋ฅผ ์š”์ฒญํ•œ๋‹ค.\"\"\"\n", " pool = make_parts(max(4 * n, 600), seed=seed)\n", " cx = pool[(pool[\"overhang\"] > 0.3) & (pool[\"n_holes\"] >= 3)]\n", " return cx.sample(n, replace=len(cx) < n, random_state=seed).reset_index(drop=True)\n", "\n", "\n", "def validate(r):\n", " errs = []\n", " if not r[\"min_wall_mm\"] > 0 or not r[\"max_dim_mm\"] > 0:\n", " errs.append(\"์น˜์ˆ˜ ์˜ค๋ฅ˜\")\n", " if r[\"material\"] not in MAT:\n", " errs.append(\"์†Œ์žฌ ์ฝ”๋“œ ์˜ค๋ฅ˜\")\n", " if not np.isfinite(r[\"load_N\"]):\n", " errs.append(\"ํ•˜์ค‘ ๋ˆ„๋ฝ\")\n", " return errs\n", "\n", "\n", "def fallback_score(X):\n", " \"\"\"๋ชจ๋ธ ์žฅ์•  ์‹œ ๊ฐ„์ด ๊ทœ์น™: ์‘๋ ฅ/๊ธฐ์ค€๊ฐ•๋„ ๋น„๋กœ๋งŒ ์ ์ˆ˜ํ™”(๋ณด์ˆ˜์ ).\"\"\"\n", " return np.clip(100 * (1 - 1.8 * X[\"stress_ratio\"]), 0, 100).values\n", "\n", "\n", "def evaluate(batch, model, fail_model=False):\n", " ok_mask = np.array([not validate(r) for r in batch.to_dict(\"records\")])\n", " good, manual = batch[ok_mask].copy(), batch[~ok_mask]\n", " X = make_features(good)\n", " try:\n", " if fail_model:\n", " raise RuntimeError(\"model down\")\n", " good[\"score\"], good[\"src\"] = np.clip(model.predict(X), 0, 100), \"๋ชจ๋ธ\"\n", " except Exception:\n", " good[\"score\"], good[\"src\"] = fallback_score(X), \"๊ฐ„์ด ๊ทœ์น™\"\n", " good[\"state\"] = np.where(good[\"score\"] < ALERT - BAND, \"๊ฒฝ๋ณด\", np.where(good[\"score\"] >= ALERT + BAND, \"์ •์ƒ\", \"๋ณด๋ฅ˜\"))\n", " good[\"alert\"] = good[\"state\"] == \"๊ฒฝ๋ณด\"\n", " return good, manual\n", "\n", "\n", "def dashboard(good):\n", " fig, ax = plt.subplots(1, 3, figsize=(14, 4))\n", " ax[0].hist(good[\"score\"], bins=20); ax[0].axvline(ALERT, ls=\"--\", c=\"r\"); ax[0].set_title(\"Score distribution\")\n", " ax[1].scatter(good[\"score\"], good[\"build_h\"], c=good[\"state\"].map({\"๊ฒฝ๋ณด\": \"red\", \"๋ณด๋ฅ˜\": \"orange\", \"์ •์ƒ\": \"gray\"}), s=12); ax[1].set_yscale(\"log\"); ax[1].set_title(\"Score vs build hours (red = alert, orange = hold)\")\n", " good.groupby(\"cat\")[\"alert\"].sum().plot.bar(ax=ax[2]); ax[2].set_title(\"Alerts by category\")\n", " fig.tight_layout(); fig.canvas.draw()\n", " return fig\n", "\n", "\n", "hist = make_parts(1000, seed=11)\n", "model = GradientBoostingRegressor(n_estimators=150, max_depth=3, learning_rate=0.06, random_state=0).fit(make_features(hist), hist[\"conform\"])\n", "rows, keep = [], {}\n", "for n, bad, down in [(50, 0, False), (50, 4, False), (100, 6, False), (200, 10, False), (500, 20, False), (50, 4, True)]:\n", " batch = make_batch(n, seed=n + bad)\n", " for k in range(bad): # ์†์ƒ ๋ ˆ์ฝ”๋“œ ์ฃผ์ž…\n", " i = k * 3\n", " if k % 3 == 0:\n", " batch.loc[i, \"min_wall_mm\"] = -1.0\n", " elif k % 3 == 1:\n", " batch.loc[i, \"material\"] = \"unknown\"\n", " else:\n", " batch.loc[i, \"load_N\"] = np.nan\n", " t0 = time.perf_counter()\n", " good, manual = evaluate(batch, model, fail_model=down)\n", " fig = dashboard(good)\n", " sec = time.perf_counter() - t0\n", " truth = good[\"conform\"] < ALERT\n", " decided = good[\"state\"] != \"๋ณด๋ฅ˜\"\n", " acc = float((good[\"alert\"] == truth)[decided].mean())\n", " rows.append({\"์ƒํ™ฉ\": f\"{n}์ข…\" + (f\", ์†์ƒ {bad}๊ฑด\" if bad else \"\") + (\", ๋ชจ๋ธ ์žฅ์• \" if down else \"\"), \"ํ‰๊ฐ€\": len(good), \"์ˆ˜๋™ ํ™•์ธ\": len(manual),\n", " \"์œ ์‹ค\": n - len(good) - len(manual), \"์ฒ˜๋ฆฌ+ํ™”๋ฉด(์ดˆ)\": round(sec, 2), \"๊ฒฝ๋ณด\": int(good[\"alert\"].sum()), \"๋ณด๋ฅ˜\": int((~decided).sum()), \"๊ฒฝ๋ณด ์ •ํ™•๋„\": round(acc, 3),\n", " \"๋ฏธ๋‹ฌ ๋†“์นจ\": int((truth & (good[\"state\"] == \"์ •์ƒ\")).sum())})\n", " keep[n, bad, down] = (fig, good)\n", " if (n, bad, down) != (50, 4, False):\n", " plt.close(fig)\n", "T = pd.DataFrame(rows)\n", "print(\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ณต์žก ํ˜•์ƒ ๋ถ€ํ’ˆ ์ผ๊ด„ ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ\")\n", "print(T.to_string(index=False))\n", "m = T.iloc[1]\n", "normal = T[~T[\"์ƒํ™ฉ\"].str.contains(\"์žฅ์• \")]\n", "print(f\"\\n50์ข…(์†์ƒ 4๊ฑด) ๊ธฐ์ค€: ์ฒ˜๋ฆฌ+ํ™”๋ฉด {m['์ฒ˜๋ฆฌ+ํ™”๋ฉด(์ดˆ)']}์ดˆ (๋ชฉํ‘œ 2์ดˆ ์ด๋‚ด: {'๋‹ฌ์„ฑ' if normal['์ฒ˜๋ฆฌ+ํ™”๋ฉด(์ดˆ)'].max() <= 2 else '๋ฏธ๋‹ฌ'}), \"\n", " f\"ํ™•์ • ํŒ์ • ๊ฒฝ๋ณด ์ •ํ™•๋„ {normal['๊ฒฝ๋ณด ์ •ํ™•๋„'].min():.1%}~{normal['๊ฒฝ๋ณด ์ •ํ™•๋„'].max():.1%} (๋ชฉํ‘œ 95% ์ด์ƒ: {'๋‹ฌ์„ฑ' if normal['๊ฒฝ๋ณด ์ •ํ™•๋„'].min() >= 0.95 else '๋ฏธ๋‹ฌ'}), ์œ ์‹ค {int(T['์œ ์‹ค'].sum())}๊ฑด\")\n", "print(f\"๋ณด๋ฅ˜(65~75์ ) ๋น„์œจ {(T['๋ณด๋ฅ˜'] / T['ํ‰๊ฐ€']).min():.0%}~{(T['๋ณด๋ฅ˜'] / T['ํ‰๊ฐ€']).max():.0%}: ๋ชจ๋ธ์ด ํŒ๋‹จํ•˜๊ธฐ ์–ด๋ ค์šด ๊ตฌ๊ฐ„์€ ์ž๋™ ํŒ์ •ํ•˜์ง€ ์•Š๊ณ  ์ •๋น„๊ธฐ์ˆ ๊ด€ ์ˆ˜๋™ ํ™•์ธ์œผ๋กœ ๋„˜๊ธด๋‹ค\")\n", "print(f\"ํ•œ๊ณ„์น˜: 500์ข…์—์„œ๋„ {T.iloc[4]['์ฒ˜๋ฆฌ+ํ™”๋ฉด(์ดˆ)']}์ดˆ -> ์ด ๊ทœ๋ชจ์—์„œ๋Š” ์ฒ˜๋ฆฌ ์‹œ๊ฐ„์ด ๋ณ‘๋ชฉ์ด ์•„๋‹ˆ๋‹ค. ๋ณ‘๋ชฉ์€ ์†์ƒ ๋ ˆ์ฝ”๋“œ ์ˆ˜๋™ ํ™•์ธ๊ณผ ์ •๋น„๊ธฐ์ˆ ๊ด€ ๊ฒ€ํ†  ์ธ๋ ฅ์ด๋‹ค.\")\n", "print(\"์†์ƒ ๋ ˆ์ฝ”๋“œ๋Š” ์‚ญ์ œํ•˜์ง€ ์•Š๊ณ  '์ˆ˜๋™ ํ™•์ธ' ๋ชฉ๋ก์œผ๋กœ ๋„˜๊ฒจ ์„ค๊ณ„์ž๊ฐ€ CADยท์‹œํ—˜ ๊ธฐ๋ก์„ ์žฌํ™•์ธํ•œ๋‹ค.\")\n", "\n", "good = keep[50, 4, False][1]\n", "qt = []\n", "for c in good[\"cat\"].unique():\n", " for mt in MAT:\n", " t0 = time.perf_counter(); good[(good[\"cat\"] == c) & (good[\"material\"] == mt)]; qt.append((time.perf_counter() - t0) * 1000)\n", "print(f\"์‹ค์‹œ๊ฐ„ ํ•„ํ„ฐ(๋ถ€ํ’ˆ๊ตฐ x ์†Œ์žฌ) ํ‰๊ท  {np.mean(qt):.2f} ms\")\n", "fb = T.iloc[5]\n", "print(f\"\\n๋ชจ๋ธ ์žฅ์•  ์‹œ ๊ฐ„์ด ๊ทœ์น™์œผ๋กœ ์ „ํ™˜: ํ™•์ • ํŒ์ • ์ •ํ™•๋„ {fb['๊ฒฝ๋ณด ์ •ํ™•๋„']:.1%}, ๊ฒฝ๋ณด {fb['๊ฒฝ๋ณด']}๊ฑด(์ •์ƒ ๋ชจ๋ธ {T.iloc[1]['๊ฒฝ๋ณด']}๊ฑด), ๋ฏธ๋‹ฌ ๋†“์นจ {fb['๋ฏธ๋‹ฌ ๋†“์นจ']}๊ฑด(์ •์ƒ ๋ชจ๋ธ {T.iloc[1]['๋ฏธ๋‹ฌ ๋†“์นจ']}๊ฑด)\")\n", "print(\"๊ธฐ๋Šฅ ์ถ•์†Œ ์šด์šฉ: ๋ชจ๋ธ ์žฅ์•  ๋˜๋Š” ์ฒ˜๋ฆฌ 2์ดˆ ์ดˆ๊ณผ ์‹œ ์‘๋ ฅ/๊ธฐ์ค€๊ฐ•๋„ ๋น„ ๊ทœ์น™ ์ ์ˆ˜๋กœ ์ฆ‰์‹œ ํ‰๊ฐ€ํ•˜๊ณ , ์ •๋ฐ€ ์ ์ˆ˜๋Š” ๋ณต๊ตฌ ํ›„ ์žฌ๊ณ„์‚ฐํ•œ๋‹ค. ๊ทœ์น™ ์ ์ˆ˜๋Š” ๋ณด์ˆ˜์ ์ด๋ผ ๊ฒฝ๋ณด๊ฐ€ ๋Š˜์–ด๋‚œ๋‹ค.\")\n", "plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 08 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ณต์žก ํ˜•์ƒ ๋ถ€ํ’ˆ ์ผ๊ด„ ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ\n", " ์ƒํ™ฉ ํ‰๊ฐ€ ์ˆ˜๋™ ํ™•์ธ ์œ ์‹ค ์ฒ˜๋ฆฌ+ํ™”๋ฉด(์ดˆ) ๊ฒฝ๋ณด ๋ณด๋ฅ˜ ๊ฒฝ๋ณด ์ •ํ™•๋„ ๋ฏธ๋‹ฌ ๋†“์นจ\n", " 50์ข… 50 0 0 0.88 29 9 0.976 1\n", " 50์ข…, ์†์ƒ 4๊ฑด 46 4 0 0.67 26 11 0.971 1\n", " 100์ข…, ์†์ƒ 6๊ฑด 94 6 0 0.78 67 10 1.000 0\n", " 200์ข…, ์†์ƒ 10๊ฑด 190 10 0 0.57 104 35 0.987 0\n", " 500์ข…, ์†์ƒ 20๊ฑด 480 20 0 0.50 262 110 0.978 0\n", "50์ข…, ์†์ƒ 4๊ฑด, ๋ชจ๋ธ ์žฅ์•  46 4 0 0.61 40 4 0.810 0\n", "\n", "50์ข…(์†์ƒ 4๊ฑด) ๊ธฐ์ค€: ์ฒ˜๋ฆฌ+ํ™”๋ฉด 0.67์ดˆ (๋ชฉํ‘œ 2์ดˆ ์ด๋‚ด: ๋‹ฌ์„ฑ), ํ™•์ • ํŒ์ • ๊ฒฝ๋ณด ์ •ํ™•๋„ 97.1%~100.0% (๋ชฉํ‘œ 95% ์ด์ƒ: ๋‹ฌ์„ฑ), ์œ ์‹ค 0๊ฑด\n", "๋ณด๋ฅ˜(65~75์ ) ๋น„์œจ 9%~24%: ๋ชจ๋ธ์ด ํŒ๋‹จํ•˜๊ธฐ ์–ด๋ ค์šด ๊ตฌ๊ฐ„์€ ์ž๋™ ํŒ์ •ํ•˜์ง€ ์•Š๊ณ  ์ •๋น„๊ธฐ์ˆ ๊ด€ ์ˆ˜๋™ ํ™•์ธ์œผ๋กœ ๋„˜๊ธด๋‹ค\n", "ํ•œ๊ณ„์น˜: 500์ข…์—์„œ๋„ 0.5์ดˆ -> ์ด ๊ทœ๋ชจ์—์„œ๋Š” ์ฒ˜๋ฆฌ ์‹œ๊ฐ„์ด ๋ณ‘๋ชฉ์ด ์•„๋‹ˆ๋‹ค. ๋ณ‘๋ชฉ์€ ์†์ƒ ๋ ˆ์ฝ”๋“œ ์ˆ˜๋™ ํ™•์ธ๊ณผ ์ •๋น„๊ธฐ์ˆ ๊ด€ ๊ฒ€ํ†  ์ธ๋ ฅ์ด๋‹ค.\n", "์†์ƒ ๋ ˆ์ฝ”๋“œ๋Š” ์‚ญ์ œํ•˜์ง€ ์•Š๊ณ  '์ˆ˜๋™ ํ™•์ธ' ๋ชฉ๋ก์œผ๋กœ ๋„˜๊ฒจ ์„ค๊ณ„์ž๊ฐ€ CADยท์‹œํ—˜ ๊ธฐ๋ก์„ ์žฌํ™•์ธํ•œ๋‹ค.\n", "์‹ค์‹œ๊ฐ„ ํ•„ํ„ฐ(๋ถ€ํ’ˆ๊ตฐ x ์†Œ์žฌ) ํ‰๊ท  0.72 ms\n", "\n", "๋ชจ๋ธ ์žฅ์•  ์‹œ ๊ฐ„์ด ๊ทœ์น™์œผ๋กœ ์ „ํ™˜: ํ™•์ • ํŒ์ • ์ •ํ™•๋„ 81.0%, ๊ฒฝ๋ณด 40๊ฑด(์ •์ƒ ๋ชจ๋ธ 26๊ฑด), ๋ฏธ๋‹ฌ ๋†“์นจ 0๊ฑด(์ •์ƒ ๋ชจ๋ธ 1๊ฑด)\n", "๊ธฐ๋Šฅ ์ถ•์†Œ ์šด์šฉ: ๋ชจ๋ธ ์žฅ์•  ๋˜๋Š” ์ฒ˜๋ฆฌ 2์ดˆ ์ดˆ๊ณผ ์‹œ ์‘๋ ฅ/๊ธฐ์ค€๊ฐ•๋„ ๋น„ ๊ทœ์น™ ์ ์ˆ˜๋กœ ์ฆ‰์‹œ ํ‰๊ฐ€ํ•˜๊ณ , ์ •๋ฐ€ ์ ์ˆ˜๋Š” ๋ณต๊ตฌ ํ›„ ์žฌ๊ณ„์‚ฐํ•œ๋‹ค. ๊ทœ์น™ ์ ์ˆ˜๋Š” ๋ณด์ˆ˜์ ์ด๋ผ ๊ฒฝ๋ณด๊ฐ€ ๋Š˜์–ด๋‚œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 09. AAR ๊ธฐ๋ฐ˜ ์‹ค๋ฌผ ํŒŒ๊ดด ์‹œํ—˜ ๊ฒฐ๊ณผ์™€ 3D ๋ชจ๋ธ ์˜ˆ์ธก์น˜ ๊ฐ„ ์˜ค์ฐจ ๋ถ„์„ ๋ฐ ๋ณด์ •\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "AAR ๊ธฐ๋ฐ˜ ์‹ค๋ฌผ ํŒŒ๊ดด ์‹œํ—˜ ๊ฒฐ๊ณผ์™€ ์˜ˆ์ธก์น˜ ๊ฐ„ ์˜ค์ฐจ๋ฅผ ๋ถ„์„ ๋ณด์ •ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์‹ค๋ฌผ ํŒŒ๊ดด ์‹œํ—˜ ๊ฒฐ๊ณผ์™€ 3D ๋ชจ๋ธ ์˜ˆ์ธก ๊ฐ•๋„์˜ ์˜ค์ฐจ๋ฅผ AAR๋กœ ๋ถ„์„ํ•ด ์›์ธ์„ ๋ถ„๋ฅ˜ํ•˜๊ณ  ๋ณด์ •ํ•ด ๋„์ž… ํšจ๊ณผ๋ฅผ ์ •๋Ÿ‰ ์ง€ํ‘œ๋กœ ์‚ฐ์ถœํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์˜ˆ์ธก์ด ์‹ค์ธก๋ณด๋‹ค 10% ์ด์ƒ ๋†’์•˜๋˜ ์œ„ํ—˜ ์‚ฌ๋ก€๋ฅผ ์–‡์€ ๋ฒฝ ๋ฏธ์ถฉ์ „, ๊ธˆ์† ์ž”๋ฅ˜์‘๋ ฅ, ์ธ์ฆ์„œ ์—†๋Š” ์†Œ์žฌ ํŽธ์ฐจ, ์‹œํ—˜ ์‚ฐํฌ๋กœ ๋ถ„๋ฅ˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `reports/mnt06_roi_analysis.md` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์ถ”์ • ์‚ฐ์‹์˜ ๊ฐ๊ด€์„ฑ ๋ฐ ๊ตฐ ์žฌ์ •/๊ตฐ์ˆ˜ ๊ธฐํš ๋ถ€์„œ ์„ค๋“๋ ฅ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๋น„์šฉ ์ ˆ๊ฐ์•ก, ์‹œ๊ฐ„ ๋‹จ์ถ•๋ฅ  ๋“ฑ 3๋Œ€ ์ •๋Ÿ‰ ํšจ๊ณผ ์ง€ํ‘œ ์‚ฐ์ถœ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ROI ๋ถ„์„ ๋ณด๊ณ ์„œ ์™„์„ฑ ๋ฐ ์ฃผ์š” ์ง€ํ‘œ ๋ฏผ๊ฐ๋„ ๋ถ„์„ ์ˆ˜๋ก\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ํšŒ๊ณ„/๊ธฐํš ๊ธฐ์ค€ ๋ชจ๋ธ๊ณผ์˜ ๋น„๊ต ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"AAR๋กœ ์‹ค๋ฌผ ํŒŒ๊ดด ์‹œํ—˜ ๊ฒฐ๊ณผ์™€ 3D ๋ชจ๋ธ(๊ฐ•๋„ ์˜ˆ์ธก)์˜ ์˜ค์ฐจ๋ฅผ ๋ถ„์„ํ•ด ๊ณผ๋Œ€ ์˜ˆ์ธก ์›์ธ์„ ๋ถ„๋ฅ˜ํ•˜๊ณ , ๊ณผ๊ฑฐ ์‹œํ—˜์œผ๋กœ ๋ณด์ • ๊ณ„์ˆ˜์™€ ์Šน์ธ ๊ธฐ์ค€์„ ์žฌ๋ณด์ •ํ•ด ์ดํ›„ ์‹œํ—˜์—์„œ ๊ฒ€์ฆํ•œ๋‹ค. ๋น„์šฉ ์ ˆ๊ฐ์•กยท๊ณต๊ธ‰ ์‹œ๊ฐ„ ๋‹จ์ถ•๋ฅ ยทํ๊ธฐ ๊ฐ์†Œ๋Ÿ‰์˜ 3๋Œ€ ์ •๋Ÿ‰ ํšจ๊ณผ ์ง€ํ‘œ์™€ ๋‹จ๊ณ„๋ณ„ ๋„์ž… ๋กœ๋“œ๋งต์„ ์ถœ๋ ฅํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด ๋ถ€ํ’ˆ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€ํ’ˆยท์žฌ๊ณ ยท๊ทœ๊ฒฉ ๊ฐ’ ์•„๋‹˜)\n", "# ํ๊ธฐ์œจ์ด ๋†’์€ ์ˆ˜๋ฆฌ๋ถ€์† ์ค‘ 3D ํ”„๋ฆฐํŒ…์œผ๋กœ ๋Œ€์ฒด ์ œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์ƒ ๋ถ€ํ’ˆ ์ž๋ฃŒ๋ฅผ ๋งŒ๋“ ๋‹ค.\n", "# ์†Œ์žฌ ๋ฌผ์„ฑยท์น˜์ˆ˜ยทํ•˜์ค‘์€ ๊ต์œก์šฉ์œผ๋กœ ๋‹จ์ˆœํ™”ํ•œ ๊ฐ’์ด๋ฉฐ ์‹ค์ œ ์‹œํ—˜ ์„ฑ์ ์„œ๋‚˜ ๊ตฐ ๊ทœ๊ฒฉ ๊ฐ’๊ณผ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "MAT = { # ์†Œ์žฌ: (๊ณต์ •, ๊ธฐ์ค€ ์ธ์žฅ๊ฐ•๋„ MPa, ๋ฐ€๋„ g/cm3, ์†Œ์žฌ ๋‹จ๊ฐ€ ๋งŒ์›/kg, ์ถœ๋ ฅ๋น„ ๋งŒ์›/h, ์ตœ์†Œ ๋ฒฝ๋‘๊ป˜ mm, ์ถœ๋ ฅ ์†๋„ cm3/h)\n", " \"PA12\": (\"polymer\", 45, 1.0, 8, 3, 0.8, 18), \"PETG\": (\"polymer\", 50, 1.27, 5, 2, 0.8, 15),\n", " \"CF-PA\": (\"polymer\", 70, 1.2, 14, 3, 1.0, 12), \"AlSi10Mg\": (\"metal\", 350, 2.67, 30, 25, 0.5, 4),\n", " \"SS316L\": (\"metal\", 520, 7.9, 28, 25, 0.6, 3.5), \"Ti64\": (\"metal\", 900, 4.4, 90, 30, 0.6, 3)}\n", "CATS = [\"bracket\", \"housing\", \"clip\", \"gear\", \"duct\", \"handle\"]\n", "CAT_SCRAP = {\"bracket\": 0.04, \"housing\": 0.05, \"clip\": 0.12, \"gear\": 0.09, \"duct\": 0.08, \"handle\": 0.10} # ๋ถ€ํ’ˆ๊ตฐ๋ณ„ ๊ธฐ๋ณธ ์›” ํ๊ธฐ์œจ\n", "BUILD_MM = {\"polymer\": 300, \"metal\": 200} # ๊ฐ€์ƒ ์ถœ๋ ฅ ๊ฐ€๋Šฅ ์ตœ๋Œ€ ์น˜์ˆ˜\n", "\n", "\n", "def make_parts(n=300, seed=0):\n", " \"\"\"๊ฐ€์ƒ ๋ถ€ํ’ˆ n์ข…: ํ˜•์ƒ(๋ถ€ํ”ผยท๋ฒฝ๋‘๊ป˜ยท๋Œ์ถœ ๋น„์œจ), ์†Œ์žฌ, ์‚ฌ์šฉ ํ•˜์ค‘, ํ๊ธฐ์œจ๊ณผ ์กฐ๋‹ฌ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " mat = rng.choice(list(MAT), n, p=[0.2, 0.15, 0.1, 0.2, 0.2, 0.15])\n", " proc = np.array([MAT[m][0] for m in mat])\n", " vol = np.clip(rng.lognormal(np.log(30), 0.9, n), 2, 800)\n", " aspect = rng.lognormal(0.3, 0.4, n)\n", " max_dim = 10 * vol ** (1 / 3) * aspect ** (1 / 3) * rng.uniform(1.0, 1.6, n)\n", " wall = np.clip(rng.lognormal(np.log(2.0), 0.6, n), 0.3, 8)\n", " ovh = rng.beta(2, 5, n)\n", " is_poly = proc == \"polymer\"\n", " df = pd.DataFrame({\n", " \"part_id\": [f\"P{i:03d}\" for i in range(n)], \"cat\": rng.choice(CATS, n, p=[0.2, 0.15, 0.2, 0.1, 0.15, 0.2]), \"material\": mat, \"process\": proc,\n", " \"volume_cm3\": vol, \"max_dim_mm\": max_dim, \"area_cm2\": 6 * vol ** (2 / 3) * rng.uniform(1.0, 2.2, n), \"min_wall_mm\": wall, \"overhang\": ovh,\n", " \"n_holes\": rng.poisson(2.0, n), \"thread\": rng.random(n) < 0.25, \"orient_deg\": rng.uniform(0, 90, n),\n", " \"layer_mm\": np.where(is_poly, rng.choice([0.1, 0.2, 0.3], n), rng.choice([0.03, 0.05], n)),\n", " \"infill\": np.where(is_poly, rng.choice([40, 60, 80, 100], n, p=[0.15, 0.3, 0.3, 0.25]), 100),\n", " \"load_N\": np.clip(rng.lognormal(np.where(is_poly, np.log(230), np.log(2300)), 0.7, n), 10, 20000), \"temp_C\": rng.uniform(-20, 90, n),\n", " \"crit\": rng.choice([1, 2, 3], n, p=[0.35, 0.4, 0.25]), \"cert\": rng.choice([0, 1], n, p=[0.45, 0.55]), # ์†Œ์žฌ ์ธ์ฆ์„œ ๋ณด์œ \n", " \"import_part\": rng.random(n) < 0.4})\n", " df[\"scrap_rate\"] = np.clip(df[\"cat\"].map(CAT_SCRAP) * rng.lognormal(0, 0.35, n) * (1 + 0.5 * (df[\"load_N\"] > 800)) * (1 + 0.3 * (df[\"temp_C\"] > 60)), 0.005, 0.5)\n", " df[\"lead_days\"] = np.clip(rng.lognormal(np.log(25), 0.5, n) * np.where(df[\"import_part\"], 2.2, 1.0), 5, 300)\n", " # ---- ์ˆจ์€ ์ง„์‹ค๊ฐ’(์‹ค์ œ ์‹œํ—˜ ๊ฒฐ๊ณผ์— ํ•ด๋‹น): ๋ชจ๋ธ์€ ์œ„ ์ž…๋ ฅ์œผ๋กœ ์ด๊ฒƒ์„ ์˜ˆ์ธกํ•œ๋‹ค ----\n", " eff = np.where(is_poly, 0.55 + 0.45 * df[\"infill\"] / 100, 1 - 0.3 * ovh * 0.2)\n", " aniso = np.where(is_poly, 1 - 0.3 * np.sin(np.radians(df[\"orient_deg\"])) ** 2, 1 - 0.08 * np.sin(np.radians(df[\"orient_deg\"])))\n", " heat = np.where(is_poly, 1 - 0.35 * np.clip((df[\"temp_C\"] - 40) / 80, 0, 1), 0.98)\n", " base = np.array([MAT[m][1] for m in mat])\n", " df[\"strength_mpa\"] = base * eff * aniso * heat * rng.lognormal(0, 0.06, n)\n", " cross = df[\"min_wall_mm\"] * np.sqrt(df[\"max_dim_mm\"]) * 1.0 # ํ•˜์ค‘ ์ง€์ง€ ๋‹จ๋ฉด์  mm2(๋‹จ์ˆœํ™”)\n", " df[\"stress_mpa\"] = df[\"load_N\"] / cross\n", " df[\"safety\"] = df[\"strength_mpa\"] / df[\"stress_mpa\"]\n", " ratio = np.clip(df[\"stress_mpa\"] / df[\"strength_mpa\"], 0, 1.5)\n", " df[\"log_cycles\"] = np.clip(7.5 - 6.0 * ratio + rng.normal(0, 0.25, n), 2, 7.5) # ํ”ผ๋กœ ์ˆ˜๋ช… log10(ํšŒ)\n", " support = 1 + 0.5 * ovh\n", " df[\"build_h\"] = vol * support / np.array([MAT[m][6] for m in mat]) + 0.5\n", " mass_kg = vol * np.array([MAT[m][2] for m in mat]) * support / 1000\n", " df[\"print_cost\"] = mass_kg * np.array([MAT[m][3] for m in mat]) + df[\"build_h\"] * np.array([MAT[m][4] for m in mat]) + 3\n", " df[\"buy_cost\"] = df[\"print_cost\"] * rng.lognormal(0.25, 0.45, n) + 5 # ๊ตฌ๋งค ๋‹จ๊ฐ€(๋งŒ์›, ๊ฐ€์ƒ)\n", " wall_ok = df[\"min_wall_mm\"] >= np.array([MAT[m][5] for m in mat]) * rng.uniform(0.95, 1.25, n)\n", " dim_ok = df[\"max_dim_mm\"] <= np.where(is_poly, BUILD_MM[\"polymer\"], BUILD_MM[\"metal\"])\n", " warp = (~is_poly) & (df[\"max_dim_mm\"] > 140) & (df[\"overhang\"] > 0.3) # ๊ธˆ์† ๋Œ€ํ˜• ์˜ค๋ฒ„ํ–‰ ๋ณ€ํ˜•\n", " thr_bad = df[\"thread\"] & (df[\"min_wall_mm\"] < 1.5) & is_poly # ์–‡์€ ๋‚˜์‚ฌ๋ถ€ ํŒŒ์†\n", " df[\"printable\"] = wall_ok & dim_ok & (ovh < 0.5) & ~warp & ~thr_bad\n", " risk = np.clip(1 - df[\"safety\"] / 3, 0, 1)\n", " df[\"conform\"] = np.clip(100 * (0.45 * np.minimum(df[\"safety\"] / 3, 1) + 0.25 * np.clip((df[\"log_cycles\"] - 3) / 4.5, 0, 1)\n", " + 0.15 * (1 - np.clip(ovh * 1.2, 0, 1)) + 0.15 * df[\"cert\"]) - 8 * risk * df[\"crit\"] / 3 + rng.normal(0, 2, n), 0, 100)\n", " df[\"demand_month\"] = rng.poisson(np.where(df[\"cat\"].isin([\"clip\", \"handle\"]), 40, 15)) + 1 # ์›” ์†Œ์š”์ˆ˜๋Ÿ‰(๊ฐ€์ƒ)\n", " df[\"scrap_cost\"] = df[\"scrap_rate\"] * df[\"demand_month\"] * df[\"buy_cost\"] # ์›” ํ๊ธฐ ์†์‹ค์•ก(๋งŒ์›, ๊ฐ€์ƒ)\n", " return df\n", "\n", "# ---- ๋‹จ์ˆœํ™”ํ•œ ํ•ด์„(FEA ๋Œ€์šฉ) ๊ฐ’๊ณผ ํŒŒ์ƒ ํ”ผ์ฒ˜: ์ž…๋ ฅ ์ •๋ณด๋งŒ์œผ๋กœ ๊ณ„์‚ฐํ•œ๋‹ค(์‹œํ—˜ ๊ฒฐ๊ณผ ๋ˆ„์„ค ์—†์Œ) ----\n", "def make_features(d):\n", " \"\"\"๊ฐ„์ด ์‘๋ ฅ ํ•ด์„(์‘๋ ฅ์ง‘์ค‘๊ณ„์ˆ˜ ํฌํ•จ)๊ณผ ์†Œ์žฌยท๊ณต์ • ์ง€์‹์—์„œ ๋งŒ๋“  ํŒŒ์ƒ ํ”ผ์ฒ˜ 16๊ฐœ.\"\"\"\n", " X = pd.DataFrame(index=d.index)\n", " base = d[\"material\"].map(lambda m: MAT[m][1]).astype(float)\n", " X[\"base_strength\"] = base\n", " X[\"is_metal\"] = (d[\"process\"] == \"metal\").astype(int)\n", " X[\"cross_mm2\"] = d[\"min_wall_mm\"] * np.sqrt(d[\"max_dim_mm\"])\n", " X[\"kt\"] = 1 + 0.15 * d[\"n_holes\"] + 0.5 * d[\"thread\"].astype(int) # ๊ตฌ๋ฉยท๋‚˜์‚ฌ๋ถ€ ์‘๋ ฅ์ง‘์ค‘๊ณ„์ˆ˜(๊ฐ„์ด)\n", " X[\"fea_stress\"] = d[\"load_N\"] / X[\"cross_mm2\"] * X[\"kt\"] # ๊ฐ„์ด ํ•ด์„ ์‘๋ ฅ(MPa)\n", " X[\"stress_ratio\"] = X[\"fea_stress\"] / base # ๊ธฐ์ค€๊ฐ•๋„ ๋Œ€๋น„ ์‘๋ ฅ๋น„\n", " X[\"infill_eff\"] = np.where(X[\"is_metal\"] == 1, 1.0, 0.55 + 0.45 * d[\"infill\"] / 100)\n", " X[\"orient_sin2\"] = np.sin(np.radians(d[\"orient_deg\"])) ** 2\n", " X[\"temp_derate\"] = np.where(X[\"is_metal\"] == 1, 0.0, np.clip((d[\"temp_C\"] - 40) / 80, 0, 1))\n", " X[\"overhang_layer\"] = d[\"overhang\"] * d[\"layer_mm\"]\n", " X[\"slender\"] = d[\"max_dim_mm\"] / d[\"min_wall_mm\"]\n", " X[\"surf_vol\"] = d[\"area_cm2\"] / d[\"volume_cm3\"]\n", " X[\"wall_ratio\"] = d[\"min_wall_mm\"] / d[\"material\"].map(lambda m: MAT[m][5])\n", " X[\"load_per_vol\"] = d[\"load_N\"] / d[\"volume_cm3\"]\n", " X[\"cert\"] = d[\"cert\"].astype(int)\n", " X[\"crit\"] = d[\"crit\"]\n", " return X\n", "\n", "# ---- ๋Œ€์ฒด ์ œ์ž‘ ์š”์ฒญ ๋น„์šฉ ๋ชจ์˜(๊ฐ€์ƒ ๋‹จ๊ฐ€): ๊ตฌ๋งค ๋Œ€๊ธฐ vs 3D ํ”„๋ฆฐํŒ… ----\n", "DOWN = {1: 0.5, 2: 2.0, 3: 5.0} # [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถ€ํ’ˆ ๋ถ€์žฌ๋กœ ์ธํ•œ ์žฅ๋น„ ๊ฐ€๋™์ค‘๋‹จ ๋น„์šฉ(๋งŒ์›/์ผ), ์ค‘์š”๋„๋ณ„\n", "FAIL = {1: 10.0, 2: 40.0, 3: 150.0} # [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋Œ€์ฒดํ’ˆ ์žฅ์ฐฉ ํ›„ ๋ถˆํ•ฉ๊ฒฉ ์‹œ ์žฌ์ž‘์—…ยท์ž„๋ฌด ์˜ํ–ฅ ๋น„์šฉ(๋งŒ์›)\n", "POST = 3.0 # ํ›„์ฒ˜๋ฆฌยท๊ฒ€์‚ฌ ๋น„์šฉ(๋งŒ์›)\n", "\n", "\n", "def make_requests_month(catalog, n=250, seed=0):\n", " \"\"\"์›”๊ฐ„ ๋ถ€ํ’ˆ ์š”์ฒญ: ์›” ์†Œ์š”์ˆ˜๋Ÿ‰์ด ํฐ ๋ถ€ํ’ˆ์ด ๋” ์ž์ฃผ ์š”์ฒญ๋œ๋‹ค.\"\"\"\n", " return catalog.sample(n, replace=True, weights=catalog[\"demand_month\"], random_state=seed).reset_index(drop=True)\n", "\n", "\n", "def buy_cost(r):\n", " return r[\"buy_cost\"] + r[\"lead_days\"] * DOWN[r[\"crit\"]]\n", "\n", "\n", "def print_outcome(r, delay_d=1.0):\n", " \"\"\"์‹ค์ œ ์‹œํ—˜ ๊ฒฐ๊ณผ(์ˆจ์€ ์ง„์‹ค)์— ๋”ฐ๋ฅธ ๋Œ€์ฒด ์ œ์ž‘ ๋น„์šฉ. ๋ถˆํ•ฉ๊ฒฉ์ด๋ฉด ๊ตฌ๋งค๋กœ ๋˜๋Œ์•„๊ฐ€๊ณ  ๋ถˆํ•ฉ๊ฒฉ ๋น„์šฉ์ด ๋“ ๋‹ค.\"\"\"\n", " wait = (r[\"build_h\"] / 24 + delay_d + 2.0) * DOWN[r[\"crit\"]]\n", " ok = bool(r[\"printable\"] and r[\"conform\"] >= 70)\n", " c = r[\"print_cost\"] + POST + wait\n", " return (c, True) if ok else (c + buy_cost(r) + FAIL[r[\"crit\"]], False)\n", "\n", "SF_MIN = 1.5 # ์ตœ์†Œ ์•ˆ์ „์œจ\n", "SCRAP_CUT = 0.30 # [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋Œ€์ฒด ๊ณต๊ธ‰ ์„ฑ๊ณต ๋ถ€ํ’ˆ์˜ ํ๊ธฐ ์ˆ˜๋Ÿ‰์ด ์ค„์–ด๋“ ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋Š” ๋น„์œจ\n", "\n", "\n", "def destructive_test(d, seed):\n", " \"\"\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์‹ค๋ฌผ ํŒŒ๊ดด ์‹œํ—˜ ๊ฐ•๋„: ๋ชจ๋ธ์ด ๋ชจ๋ฅด๋Š” ์˜ํ–ฅ(์–‡์€ ๋ฒฝ ๋ฏธ์ถฉ์ „ยท๊ธˆ์† ์ž”๋ฅ˜์‘๋ ฅยท์ธ์ฆ์„œ ์—†๋Š” ์†Œ์žฌ ํŽธ์ฐจ)์ด ์„ž์ธ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " f = np.ones(len(d))\n", " f *= np.where((d[\"process\"] == \"polymer\") & (d[\"min_wall_mm\"] < 1.5), 0.86, 1.0)\n", " f *= np.where((d[\"process\"] == \"metal\") & (d[\"overhang\"] > 0.3), 0.88, 1.0)\n", " f *= np.where(d[\"cert\"] == 0, 0.94, 1.0)\n", " return d[\"strength_mpa\"] * f * rng.lognormal(0, 0.03, len(d))\n", "\n", "\n", "hist = make_parts(1000, seed=11)\n", "Xh = make_features(hist)\n", "model = GradientBoostingRegressor(n_estimators=150, max_depth=3, learning_rate=0.06, random_state=0).fit(Xh, hist[\"strength_mpa\"] / Xh[\"base_strength\"])\n", "\n", "\n", "def predict_strength(d):\n", " X = make_features(d)\n", " return model.predict(X) * X[\"base_strength\"], X\n", "\n", "\n", "def group(d):\n", " return np.select([(d[\"process\"] == \"polymer\") & (d[\"min_wall_mm\"] < 1.5), (d[\"process\"] == \"metal\") & (d[\"overhang\"] > 0.3)], [\"ํด๋ฆฌ๋จธ ์–‡์€ ๋ฒฝ\", \"๊ธˆ์† ๋Œ์ถœ ๋งŽ์Œ\"], \"์ผ๋ฐ˜\")\n", "\n", "\n", "cal, test = make_parts(250, seed=31), make_parts(250, seed=32) # ๊ณผ๊ฑฐ ์‹œํ—˜(๋ณด์ •์šฉ) / ์ดํ›„ ์‹œํ—˜(๊ฒ€์ฆ์šฉ)\n", "for d, s in ((cal, 41), (test, 42)):\n", " d[\"meas\"] = destructive_test(d, s); d[\"pred\"], _ = predict_strength(d); d[\"grp\"] = group(d)\n", " d[\"err\"] = d[\"pred\"] / d[\"meas\"] - 1\n", "\n", "# 1) AAR: ๊ณผ๋Œ€ ์˜ˆ์ธก(+10% ์ดˆ๊ณผ, ์œ„ํ—˜ํ•œ ๋ฐฉํ–ฅ) ์‚ฌ๋ก€์˜ ์›์ธ ๋ถ„๋ฅ˜\n", "over = cal[cal[\"err\"] > 0.10].copy()\n", "over[\"์›์ธ\"] = np.select([over[\"grp\"] == \"ํด๋ฆฌ๋จธ ์–‡์€ ๋ฒฝ\", over[\"grp\"] == \"๊ธˆ์† ๋Œ์ถœ ๋งŽ์Œ\", over[\"cert\"] == 0], [\"์–‡์€ ๋ฒฝ ๋ฏธ์ถฉ์ „(๋ชจ๋ธ ๋ฏธ๋ฐ˜์˜)\", \"๊ธˆ์† ์ž”๋ฅ˜์‘๋ ฅ(๋ชจ๋ธ ๋ฏธ๋ฐ˜์˜)\", \"์ธ์ฆ์„œ ์—†๋Š” ์†Œ์žฌ ํŽธ์ฐจ\"], \"๊ธฐํƒ€(์‹œํ—˜ ์‚ฐํฌ)\")\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] AAR: ๊ณผ๊ฑฐ ์‹œํ—˜ {len(cal)}์ข… ์ค‘ ์˜ˆ์ธก์ด ์‹ค์ธก๋ณด๋‹ค 10% ์ด์ƒ ๋†’์•˜๋˜ ์œ„ํ—˜ ์‚ฌ๋ก€ {len(over)}๊ฑด (ํ‰๊ท  ์˜ค์ฐจ {cal['err'].mean():+.1%}, MAPE {cal['err'].abs().mean():.1%})\")\n", "print(over[\"์›์ธ\"].value_counts().to_string())\n", "print(\"-> ์›์ธ ๋Œ€๋ถ€๋ถ„์ด ๋ชจ๋ธ์— ์—†๋Š” ํ˜•์ƒยท๊ณต์ • ํšจ๊ณผ๋ผ ์Šน์ธ ๊ธฐ์ค€ ๋ณด์ •์œผ๋กœ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค. '๊ธฐํƒ€'๋Š” ์‹œํ—˜ ์‚ฐํฌ๋ผ ๋ณด์ •์ด ์•„๋‹Œ ์•ˆ์ „์œจ ์—ฌ์œ ๋กœ ๋Œ€์‘ํ•œ๋‹ค.\")\n", "\n", "# 2) ์žฌ๋ณด์ •: ๊ทธ๋ฃน๋ณ„ ๋ณด์ • ๊ณ„์ˆ˜(์ค‘์•™๊ฐ’ ์‹ค์ธก/์˜ˆ์ธก) + ์Šน์ธ ์•ˆ์ „์œจ ์ž„๊ณ„๊ฐ’\n", "corr = pd.Series({g_: float(np.median((cal[\"meas\"] / cal[\"pred\"])[cal[\"grp\"] == g_])) for g_ in np.unique(cal[\"grp\"])})\n", "print(\"\\n๋ณด์ • ๊ณ„์ˆ˜(์‹ค์ธก/์˜ˆ์ธก ์ค‘์•™๊ฐ’):\", {k: round(v, 3) for k, v in corr.items()})\n", "stress = lambda d: d[\"load_N\"] / (d[\"min_wall_mm\"] * np.sqrt(d[\"max_dim_mm\"]))\n", "\n", "\n", "def sf_pred(d, use_corr):\n", " p = d[\"pred\"] * (d[\"grp\"].map(corr) if use_corr else 1.0)\n", " return p / stress(d)\n", "\n", "\n", "def unsafe_rate(d, use_corr, thr):\n", " ok = sf_pred(d, use_corr) >= thr\n", " real_bad = (d[\"meas\"] / stress(d)) < SF_MIN\n", " return float((ok & real_bad).sum() / max(ok.sum(), 1)), int(ok.sum())\n", "\n", "\n", "thr_new = next((t for t in np.arange(1.5, 4.01, 0.05) if unsafe_rate(cal, True, t)[0] <= 0.02), 4.0)\n", "rows = [{\"๋ฐฉ์‹\": \"ํ˜„ํ–‰(์˜ˆ์ธก ์•ˆ์ „์œจ 1.5 ์ด์ƒ)\", **dict(zip([\"์œ„ํ—˜ ์Šน์ธ ๋น„์œจ\", \"์Šน์ธ ๊ฐ€๋Šฅ ๋ถ€ํ’ˆ\"], unsafe_rate(test, False, SF_MIN)))},\n", " {\"๋ฐฉ์‹\": \"๋ณด์ •๋งŒ(๊ณ„์ˆ˜ ์ ์šฉ)\", **dict(zip([\"์œ„ํ—˜ ์Šน์ธ ๋น„์œจ\", \"์Šน์ธ ๊ฐ€๋Šฅ ๋ถ€ํ’ˆ\"], unsafe_rate(test, True, SF_MIN)))},\n", " {\"๋ฐฉ์‹\": f\"์žฌ๋ณด์ •(๊ณ„์ˆ˜ + ์ž„๊ณ„ {thr_new:.2f})\", **dict(zip([\"์œ„ํ—˜ ์Šน์ธ ๋น„์œจ\", \"์Šน์ธ ๊ฐ€๋Šฅ ๋ถ€ํ’ˆ\"], unsafe_rate(test, True, thr_new)))}]\n", "Tq = pd.DataFrame(rows)\n", "Tq[\"์œ„ํ—˜ ์Šน์ธ ๋น„์œจ\"] = (Tq[\"์œ„ํ—˜ ์Šน์ธ ๋น„์œจ\"] * 100).round(1).astype(str) + \"%\"\n", "print(f\"๊ณผ๊ฑฐ ์‹œํ—˜์—์„œ ์œ„ํ—˜ ์Šน์ธ ๋น„์œจ 2% ์ดํ•˜๋ฅผ ๋งŒ์กฑํ•˜๋Š” ์ตœ์†Œ ์ž„๊ณ„ ์•ˆ์ „์œจ: {thr_new:.2f} (๊ณ„์ˆ˜ ๋ณด์ • ํ›„์—๋Š” ๊ธฐ๋ณธ 1.5๋กœ ์ถฉ๋ถ„)\")\n", "print(\"\\n[์ดํ›„ ์‹œํ—˜ 250์ข… ๊ฒ€์ฆ]\")\n", "print(Tq.to_string(index=False))\n", "test[\"pred2\"] = test[\"pred\"] * test[\"grp\"].map(corr)\n", "print(f\"๊ฐ•๋„ ์˜ˆ์ธก MAPE: ๋ณด์ • ์ „ {(test['pred'] / test['meas'] - 1).abs().mean():.1%} -> ๋ณด์ • ํ›„ {(test['pred2'] / test['meas'] - 1).abs().mean():.1%}\")\n", "\n", "# 3) 3๋Œ€ ์ •๋Ÿ‰ ํšจ๊ณผ ์ง€ํ‘œ: ์›”๊ฐ„ ์š”์ฒญ 250๊ฑด, ์Šน์ธ ๊ธฐ์ค€ ํ˜„ํ–‰ vs ์žฌ๋ณด์ •(๊ตฌ๋งค ๊ธฐ์ค€ ๋Œ€๋น„)\n", "cat = make_parts(300, seed=51)\n", "cat[\"meas\"] = destructive_test(cat, 61); cat[\"pred\"], _ = predict_strength(cat); cat[\"grp\"] = group(cat)\n", "cat[\"rule_ok\"] = (cat[\"min_wall_mm\"] >= 1.0) & (cat[\"max_dim_mm\"] <= cat[\"process\"].map(BUILD_MM)) & (cat[\"overhang\"] < 0.5)\n", "req = make_requests_month(cat, 250, seed=3)\n", "\n", "\n", "def outcome(r, approve):\n", " c, days = buy_cost(r), r[\"lead_days\"]\n", " if approve:\n", " real_ok = bool(r[\"printable\"] and r[\"meas\"] / (r[\"load_N\"] / (r[\"min_wall_mm\"] * np.sqrt(r[\"max_dim_mm\"]))) >= SF_MIN)\n", " pdays = r[\"build_h\"] / 24 + 3.0\n", " cp = r[\"print_cost\"] + POST + pdays * DOWN[r[\"crit\"]]\n", " return (cp, pdays, True, real_ok) if real_ok else (cp + c + FAIL[r[\"crit\"]], pdays + r[\"lead_days\"], True, False)\n", " return c, days, False, True\n", "\n", "\n", "def policy(use_corr, thr):\n", " out = []\n", " for _, r in req.iterrows():\n", " sf = r[\"pred\"] * (corr[r[\"grp\"]] if use_corr else 1.0) / (r[\"load_N\"] / (r[\"min_wall_mm\"] * np.sqrt(r[\"max_dim_mm\"])))\n", " out.append(outcome(r, bool(r[\"rule_ok\"] and sf >= thr)))\n", " return pd.DataFrame(out, columns=[\"cost\", \"days\", \"printed\", \"real_ok\"]).join(req[[\"scrap_rate\", \"demand_month\", \"crit\"]])\n", "\n", "\n", "base = pd.DataFrame([outcome(r, False) for _, r in req.iterrows()], columns=[\"cost\", \"days\", \"printed\", \"real_ok\"])\n", "res = {\"ํ˜„ํ–‰ ๊ธฐ์ค€\": policy(False, SF_MIN), \"์žฌ๋ณด์ • ๊ธฐ์ค€\": policy(True, thr_new)}\n", "print(\"\\n[์›”๊ฐ„ 250๊ฑด ๋ชจ์˜: ๊ตฌ๋งค ๊ธฐ์ค€ ๋Œ€๋น„]\")\n", "for k, d in res.items():\n", " fail = int((d[\"printed\"] & ~d[\"real_ok\"]).sum())\n", " save, cut_days = base[\"cost\"].sum() - d[\"cost\"].sum(), 1 - d[\"days\"].mean() / base[\"days\"].mean()\n", " scr = (d.loc[d[\"printed\"] & d[\"real_ok\"], \"scrap_rate\"] * d.loc[d[\"printed\"] & d[\"real_ok\"], \"demand_month\"]).sum() * SCRAP_CUT\n", " print(f\" {k}: 3D ์ œ์ž‘ {int(d['printed'].sum())}๊ฑด(๋ถˆํ•ฉ๊ฒฉ {fail}๊ฑด) | 1) ๋น„์šฉ ์ ˆ๊ฐ {save:,.0f}๋งŒ์›({save / base['cost'].sum():.1%}) | 2) ๊ณต๊ธ‰ ์‹œ๊ฐ„ ๋‹จ์ถ• {cut_days:.1%}\"\n", " f\" ({base['days'].mean():.1f}์ผ -> {d['days'].mean():.1f}์ผ) | 3) ํ๊ธฐ ๊ฐ์†Œ {scr:.1f}๊ฐœ/์›”(๊ฐ€์ • {SCRAP_CUT:.0%})\")\n", "print(\"ROI ๋กœ๋“œ๋งต: ๋‹จ๊ธฐ(1~3๊ฐœ์›”) ํด๋ฆฌ๋จธ ๋น„ํ•ต์‹ฌ ๋ถ€ํ’ˆ ์‹œ๋ฒ” -> ์ค‘๊ธฐ(6๊ฐœ์›”) ํŒŒ๊ดด ์‹œํ—˜ ๊ฒฐ๊ณผ๋กœ ๋ถ„๊ธฐ๋ณ„ ๋ณด์ • -> ์žฅ๊ธฐ(1๋…„) ๊ธˆ์† ํ•ต์‹ฌ ๋ถ€ํ’ˆ ํ™•๋Œ€\")\n", "print(\"ํ๊ธฐ ๊ฐ์†Œ ์ˆ˜๋Ÿ‰์€ ๋Œ€์ฒด ๊ณต๊ธ‰์ด ํ๊ธฐ๋ฅผ ์ค„์ธ๋‹ค๋Š” ๊ฐ€์ • ๋น„์œจ์— ๋”ฐ๋ฅธ ์ถ”์ •์ด๋ฉฐ, ๋ชจ๋“  ๊ธˆ์•ก์€ ๊ต์œก์šฉ ๊ฐ€์ • ๋‹จ๊ฐ€์˜ ์ƒ๋Œ€ ๋น„๊ต๊ฐ’์ด๋‹ค. ์‹ค์ œ ์žฅ์ฐฉ ์Šน์ธ์€ ์ •๋น„๊ธฐ์ˆ ๊ด€์ด ํ•œ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n", "for g_, c in zip([\"ํด๋ฆฌ๋จธ ์–‡์€ ๋ฒฝ\", \"๊ธˆ์† ๋Œ์ถœ ๋งŽ์Œ\", \"์ผ๋ฐ˜\"], [\"tab:red\", \"tab:orange\", \"tab:blue\"]):\n", " s = test[test[\"grp\"] == g_]\n", " ax[0].scatter(s[\"meas\"], s[\"pred\"], s=8, c=c, label={\"ํด๋ฆฌ๋จธ ์–‡์€ ๋ฒฝ\": \"thin polymer\", \"๊ธˆ์† ๋Œ์ถœ ๋งŽ์Œ\": \"metal overhang\", \"์ผ๋ฐ˜\": \"other\"}[g_])\n", "ax[0].plot([0, 1000], [0, 1000], \"k--\"); ax[0].set_xlabel(\"measured (MPa)\"); ax[0].set_ylabel(\"predicted\"); ax[0].legend(); ax[0].set_title(\"Prediction vs destructive test\")\n", "ax[1].bar([\"current\", \"recalibrated\"], [unsafe_rate(test, False, SF_MIN)[0] * 100, unsafe_rate(test, True, thr_new)[0] * 100]); ax[1].set_ylabel(\"unsafe approval (%)\"); ax[1].set_title(\"Unsafe approval rate (hold-out)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 09 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] AAR: ๊ณผ๊ฑฐ ์‹œํ—˜ 250์ข… ์ค‘ ์˜ˆ์ธก์ด ์‹ค์ธก๋ณด๋‹ค 10% ์ด์ƒ ๋†’์•˜๋˜ ์œ„ํ—˜ ์‚ฌ๋ก€ 110๊ฑด (ํ‰๊ท  ์˜ค์ฐจ +9.8%, MAPE 12.0%)\n", "์›์ธ\n", "๊ธˆ์† ์ž”๋ฅ˜์‘๋ ฅ(๋ชจ๋ธ ๋ฏธ๋ฐ˜์˜) 54\n", "์–‡์€ ๋ฒฝ ๋ฏธ์ถฉ์ „(๋ชจ๋ธ ๋ฏธ๋ฐ˜์˜) 34\n", "์ธ์ฆ์„œ ์—†๋Š” ์†Œ์žฌ ํŽธ์ฐจ 15\n", "๊ธฐํƒ€(์‹œํ—˜ ์‚ฐํฌ) 7\n", "-> ์›์ธ ๋Œ€๋ถ€๋ถ„์ด ๋ชจ๋ธ์— ์—†๋Š” ํ˜•์ƒยท๊ณต์ • ํšจ๊ณผ๋ผ ์Šน์ธ ๊ธฐ์ค€ ๋ณด์ •์œผ๋กœ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค. '๊ธฐํƒ€'๋Š” ์‹œํ—˜ ์‚ฐํฌ๋ผ ๋ณด์ •์ด ์•„๋‹Œ ์•ˆ์ „์œจ ์—ฌ์œ ๋กœ ๋Œ€์‘ํ•œ๋‹ค.\n", "\n", "๋ณด์ • ๊ณ„์ˆ˜(์‹ค์ธก/์˜ˆ์ธก ์ค‘์•™๊ฐ’): {'๊ธˆ์† ๋Œ์ถœ ๋งŽ์Œ': 0.831, '์ผ๋ฐ˜': 0.98, 'ํด๋ฆฌ๋จธ ์–‡์€ ๋ฒฝ': 0.826}\n", "๊ณผ๊ฑฐ ์‹œํ—˜์—์„œ ์œ„ํ—˜ ์Šน์ธ ๋น„์œจ 2% ์ดํ•˜๋ฅผ ๋งŒ์กฑํ•˜๋Š” ์ตœ์†Œ ์ž„๊ณ„ ์•ˆ์ „์œจ: 1.50 (๊ณ„์ˆ˜ ๋ณด์ • ํ›„์—๋Š” ๊ธฐ๋ณธ 1.5๋กœ ์ถฉ๋ถ„)\n", "\n", "[์ดํ›„ ์‹œํ—˜ 250์ข… ๊ฒ€์ฆ]\n", " ๋ฐฉ์‹ ์œ„ํ—˜ ์Šน์ธ ๋น„์œจ ์Šน์ธ ๊ฐ€๋Šฅ ๋ถ€ํ’ˆ\n", "ํ˜„ํ–‰(์˜ˆ์ธก ์•ˆ์ „์œจ 1.5 ์ด์ƒ) 7.5% 159\n", " ๋ณด์ •๋งŒ(๊ณ„์ˆ˜ ์ ์šฉ) 2.6% 151\n", "์žฌ๋ณด์ •(๊ณ„์ˆ˜ + ์ž„๊ณ„ 1.50) 2.6% 151\n", "๊ฐ•๋„ ์˜ˆ์ธก MAPE: ๋ณด์ • ์ „ 11.2% -> ๋ณด์ • ํ›„ 6.5%\n", "\n", "[์›”๊ฐ„ 250๊ฑด ๋ชจ์˜: ๊ตฌ๋งค ๊ธฐ์ค€ ๋Œ€๋น„]\n", " ํ˜„ํ–‰ ๊ธฐ์ค€: 3D ์ œ์ž‘ 130๊ฑด(๋ถˆํ•ฉ๊ฒฉ 2๊ฑด) | 1) ๋น„์šฉ ์ ˆ๊ฐ 37,491๋งŒ์›(29.0%) | 2) ๊ณต๊ธ‰ ์‹œ๊ฐ„ ๋‹จ์ถ• 52.5% (40.9์ผ -> 19.5์ผ) | 3) ํ๊ธฐ ๊ฐ์†Œ 201.5๊ฐœ/์›”(๊ฐ€์ • 30%)\n", " ์žฌ๋ณด์ • ๊ธฐ์ค€: 3D ์ œ์ž‘ 129๊ฑด(๋ถˆํ•ฉ๊ฒฉ 1๊ฑด) | 1) ๋น„์šฉ ์ ˆ๊ฐ 37,822๋งŒ์›(29.3%) | 2) ๊ณต๊ธ‰ ์‹œ๊ฐ„ ๋‹จ์ถ• 52.5% (40.9์ผ -> 19.4์ผ) | 3) ํ๊ธฐ ๊ฐ์†Œ 201.5๊ฐœ/์›”(๊ฐ€์ • 30%)\n", "ROI ๋กœ๋“œ๋งต: ๋‹จ๊ธฐ(1~3๊ฐœ์›”) ํด๋ฆฌ๋จธ ๋น„ํ•ต์‹ฌ ๋ถ€ํ’ˆ ์‹œ๋ฒ” -> ์ค‘๊ธฐ(6๊ฐœ์›”) ํŒŒ๊ดด ์‹œํ—˜ ๊ฒฐ๊ณผ๋กœ ๋ถ„๊ธฐ๋ณ„ ๋ณด์ • -> ์žฅ๊ธฐ(1๋…„) ๊ธˆ์† ํ•ต์‹ฌ ๋ถ€ํ’ˆ ํ™•๋Œ€\n", "ํ๊ธฐ ๊ฐ์†Œ ์ˆ˜๋Ÿ‰์€ ๋Œ€์ฒด ๊ณต๊ธ‰์ด ํ๊ธฐ๋ฅผ ์ค„์ธ๋‹ค๋Š” ๊ฐ€์ • ๋น„์œจ์— ๋”ฐ๋ฅธ ์ถ”์ •์ด๋ฉฐ, ๋ชจ๋“  ๊ธˆ์•ก์€ ๊ต์œก์šฉ ๊ฐ€์ • ๋‹จ๊ฐ€์˜ ์ƒ๋Œ€ ๋น„๊ต๊ฐ’์ด๋‹ค. ์‹ค์ œ ์žฅ์ฐฉ ์Šน์ธ์€ ์ •๋น„๊ธฐ์ˆ ๊ด€์ด ํ•œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 10. 3D ํ”„๋ฆฐํŒ… ์•ผ์ „ ๊ธด๊ธ‰ ๋ถ€ํ’ˆ ์ œ์ž‘ ํƒ€๋‹น์„ฑ ๋ฐ ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ ๋„๊ตฌ ์ตœ์ข… ์™„์„ฑ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "3D ํ”„๋ฆฐํŒ… ์•ผ์ „ ๊ธด๊ธ‰ ๋ถ€ํ’ˆ ์ œ์ž‘ ํƒ€๋‹น์„ฑ ๋ฐ ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ ๋„๊ตฌ๋ฅผ ์™„์„ฑํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์š”์ฒญ ๊ฒ€์ฆ๋ถ€ํ„ฐ ์˜ˆ์ธก ๋ชจ๋ธ ์žฌํ•™์Šต, ์•ˆ์ „์œจยท์ ์ˆ˜ ๊ธฐ๋ฐ˜ ํ›„๋ณด ์„ ๋ณ„, ์Šน์ธ ๋Œ€๊ธฐ์—ด ์ƒ์„ฑ๊นŒ์ง€ 3D ํ”„๋ฆฐํŒ… ์•ผ์ „ ๊ธด๊ธ‰ ๋ถ€ํ’ˆ ์ œ์ž‘ ํƒ€๋‹น์„ฑยท์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ ๋„๊ตฌ๋ฅผ ํ†ตํ•ฉํ•˜๊ณ  ์šด์˜ ๋ฌธ์„œ๋ฅผ ์™„๋น„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์‹ ๊ทœ ํŒŒ๊ดด ์‹œํ—˜์œผ๋กœ ๋„์ „์ž ๋ชจ๋ธ์„ ํ•™์Šตํ•ด ์ฑ”ํ”ผ์–ธ ๋ชจ๋ธ๊ณผ ๊ฒ€์ฆ MAPE๋ฅผ ๋น„๊ตํ•œ ๋’ค ๊ต์ฒด ์—ฌ๋ถ€๋ฅผ ์ •ํ•˜๋Š” ์ž๋™ ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌํ˜„ํ•˜๊ณ  ๋™์ž‘์„ ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/mnt06_operations_guide.md ๋ฐ pipeline/mnt06_retrain.py` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์ง€์† ๊ฐ€๋Šฅํ•œ ์šด์˜ ๊ฐ€๋Šฅ์„ฑ ๋ฐ ๊ตญ๋ฐฉ ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„์™€์˜ ์—ฐ๋™ ์šฉ์ด์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์ž๋™ ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์ •์ƒ ์‹คํ–‰, ์ตœ์ข… ๋ฌธ์„œ 100% ์™„๋น„\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์ตœ์ข… ์ธ๋„๋ฌผ ๊ฒ€์ˆ˜ ๋ฐ ํ”„๋กœ์ ํŠธ ์™„๋ฃŒ ๋ฐœํ‘œ์ž๋ฃŒ ์™„์„ฑ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: End-to-End ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ ์ž๋™ํ™” ์‹คํ–‰ ๋ฐ ์ตœ์ข… ๊ฒ€์ˆ˜\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๋ถ€ํ’ˆ ์š”์ฒญ ๊ฒ€์ฆยท์†์ƒ ๋ ˆ์ฝ”๋“œ ๋ถ„๋ฆฌ, ๊ฐ•๋„ ์˜ˆ์ธก ๋ชจ๋ธ์˜ ์ž๋™ ์žฌํ•™์Šต(์ฑ”ํ”ผ์–ธยท๋„์ „์ž ๋น„๊ต ํ›„ ๊ต์ฒด), ๊ตฐ ๊ทœ๊ฒฉ ๋ถ€ํ•ฉ ์ ์ˆ˜์™€ ์•ˆ์ „์œจ ๊ธฐ๋ฐ˜ ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด ์„ ๋ณ„, ์ •๋น„๊ธฐ์ˆ ๊ด€ ์Šน์ธ ๋Œ€๊ธฐ์—ด ์ƒ์„ฑ๊นŒ์ง€ ์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ์ œ์ž‘ ํƒ€๋‹น์„ฑยท์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ ๋„๊ตฌ๋ฅผ ํ•œ ํŒŒ์ผ๋กœ ํ†ตํ•ฉ ์‹คํ–‰ํ•˜๊ณ , ์ž์ฒด ์ ๊ฒ€ยท์šด์˜ ๋ฌธ์„œ ์™„๋น„ ์ ๊ฒ€ยท์ •๋น„๊ธฐ์ˆ ๊ด€ ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ์„ ์ถœ๋ ฅํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด ๋ถ€ํ’ˆ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€ํ’ˆยท์žฌ๊ณ ยท๊ทœ๊ฒฉ ๊ฐ’ ์•„๋‹˜)\n", "# ํ๊ธฐ์œจ์ด ๋†’์€ ์ˆ˜๋ฆฌ๋ถ€์† ์ค‘ 3D ํ”„๋ฆฐํŒ…์œผ๋กœ ๋Œ€์ฒด ์ œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์ƒ ๋ถ€ํ’ˆ ์ž๋ฃŒ๋ฅผ ๋งŒ๋“ ๋‹ค.\n", "# ์†Œ์žฌ ๋ฌผ์„ฑยท์น˜์ˆ˜ยทํ•˜์ค‘์€ ๊ต์œก์šฉ์œผ๋กœ ๋‹จ์ˆœํ™”ํ•œ ๊ฐ’์ด๋ฉฐ ์‹ค์ œ ์‹œํ—˜ ์„ฑ์ ์„œ๋‚˜ ๊ตฐ ๊ทœ๊ฒฉ ๊ฐ’๊ณผ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "MAT = { # ์†Œ์žฌ: (๊ณต์ •, ๊ธฐ์ค€ ์ธ์žฅ๊ฐ•๋„ MPa, ๋ฐ€๋„ g/cm3, ์†Œ์žฌ ๋‹จ๊ฐ€ ๋งŒ์›/kg, ์ถœ๋ ฅ๋น„ ๋งŒ์›/h, ์ตœ์†Œ ๋ฒฝ๋‘๊ป˜ mm, ์ถœ๋ ฅ ์†๋„ cm3/h)\n", " \"PA12\": (\"polymer\", 45, 1.0, 8, 3, 0.8, 18), \"PETG\": (\"polymer\", 50, 1.27, 5, 2, 0.8, 15),\n", " \"CF-PA\": (\"polymer\", 70, 1.2, 14, 3, 1.0, 12), \"AlSi10Mg\": (\"metal\", 350, 2.67, 30, 25, 0.5, 4),\n", " \"SS316L\": (\"metal\", 520, 7.9, 28, 25, 0.6, 3.5), \"Ti64\": (\"metal\", 900, 4.4, 90, 30, 0.6, 3)}\n", "CATS = [\"bracket\", \"housing\", \"clip\", \"gear\", \"duct\", \"handle\"]\n", "CAT_SCRAP = {\"bracket\": 0.04, \"housing\": 0.05, \"clip\": 0.12, \"gear\": 0.09, \"duct\": 0.08, \"handle\": 0.10} # ๋ถ€ํ’ˆ๊ตฐ๋ณ„ ๊ธฐ๋ณธ ์›” ํ๊ธฐ์œจ\n", "BUILD_MM = {\"polymer\": 300, \"metal\": 200} # ๊ฐ€์ƒ ์ถœ๋ ฅ ๊ฐ€๋Šฅ ์ตœ๋Œ€ ์น˜์ˆ˜\n", "\n", "\n", "def make_parts(n=300, seed=0):\n", " \"\"\"๊ฐ€์ƒ ๋ถ€ํ’ˆ n์ข…: ํ˜•์ƒ(๋ถ€ํ”ผยท๋ฒฝ๋‘๊ป˜ยท๋Œ์ถœ ๋น„์œจ), ์†Œ์žฌ, ์‚ฌ์šฉ ํ•˜์ค‘, ํ๊ธฐ์œจ๊ณผ ์กฐ๋‹ฌ ์ •๋ณด๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " mat = rng.choice(list(MAT), n, p=[0.2, 0.15, 0.1, 0.2, 0.2, 0.15])\n", " proc = np.array([MAT[m][0] for m in mat])\n", " vol = np.clip(rng.lognormal(np.log(30), 0.9, n), 2, 800)\n", " aspect = rng.lognormal(0.3, 0.4, n)\n", " max_dim = 10 * vol ** (1 / 3) * aspect ** (1 / 3) * rng.uniform(1.0, 1.6, n)\n", " wall = np.clip(rng.lognormal(np.log(2.0), 0.6, n), 0.3, 8)\n", " ovh = rng.beta(2, 5, n)\n", " is_poly = proc == \"polymer\"\n", " df = pd.DataFrame({\n", " \"part_id\": [f\"P{i:03d}\" for i in range(n)], \"cat\": rng.choice(CATS, n, p=[0.2, 0.15, 0.2, 0.1, 0.15, 0.2]), \"material\": mat, \"process\": proc,\n", " \"volume_cm3\": vol, \"max_dim_mm\": max_dim, \"area_cm2\": 6 * vol ** (2 / 3) * rng.uniform(1.0, 2.2, n), \"min_wall_mm\": wall, \"overhang\": ovh,\n", " \"n_holes\": rng.poisson(2.0, n), \"thread\": rng.random(n) < 0.25, \"orient_deg\": rng.uniform(0, 90, n),\n", " \"layer_mm\": np.where(is_poly, rng.choice([0.1, 0.2, 0.3], n), rng.choice([0.03, 0.05], n)),\n", " \"infill\": np.where(is_poly, rng.choice([40, 60, 80, 100], n, p=[0.15, 0.3, 0.3, 0.25]), 100),\n", " \"load_N\": np.clip(rng.lognormal(np.where(is_poly, np.log(230), np.log(2300)), 0.7, n), 10, 20000), \"temp_C\": rng.uniform(-20, 90, n),\n", " \"crit\": rng.choice([1, 2, 3], n, p=[0.35, 0.4, 0.25]), \"cert\": rng.choice([0, 1], n, p=[0.45, 0.55]), # ์†Œ์žฌ ์ธ์ฆ์„œ ๋ณด์œ \n", " \"import_part\": rng.random(n) < 0.4})\n", " df[\"scrap_rate\"] = np.clip(df[\"cat\"].map(CAT_SCRAP) * rng.lognormal(0, 0.35, n) * (1 + 0.5 * (df[\"load_N\"] > 800)) * (1 + 0.3 * (df[\"temp_C\"] > 60)), 0.005, 0.5)\n", " df[\"lead_days\"] = np.clip(rng.lognormal(np.log(25), 0.5, n) * np.where(df[\"import_part\"], 2.2, 1.0), 5, 300)\n", " # ---- ์ˆจ์€ ์ง„์‹ค๊ฐ’(์‹ค์ œ ์‹œํ—˜ ๊ฒฐ๊ณผ์— ํ•ด๋‹น): ๋ชจ๋ธ์€ ์œ„ ์ž…๋ ฅ์œผ๋กœ ์ด๊ฒƒ์„ ์˜ˆ์ธกํ•œ๋‹ค ----\n", " eff = np.where(is_poly, 0.55 + 0.45 * df[\"infill\"] / 100, 1 - 0.3 * ovh * 0.2)\n", " aniso = np.where(is_poly, 1 - 0.3 * np.sin(np.radians(df[\"orient_deg\"])) ** 2, 1 - 0.08 * np.sin(np.radians(df[\"orient_deg\"])))\n", " heat = np.where(is_poly, 1 - 0.35 * np.clip((df[\"temp_C\"] - 40) / 80, 0, 1), 0.98)\n", " base = np.array([MAT[m][1] for m in mat])\n", " df[\"strength_mpa\"] = base * eff * aniso * heat * rng.lognormal(0, 0.06, n)\n", " cross = df[\"min_wall_mm\"] * np.sqrt(df[\"max_dim_mm\"]) * 1.0 # ํ•˜์ค‘ ์ง€์ง€ ๋‹จ๋ฉด์  mm2(๋‹จ์ˆœํ™”)\n", " df[\"stress_mpa\"] = df[\"load_N\"] / cross\n", " df[\"safety\"] = df[\"strength_mpa\"] / df[\"stress_mpa\"]\n", " ratio = np.clip(df[\"stress_mpa\"] / df[\"strength_mpa\"], 0, 1.5)\n", " df[\"log_cycles\"] = np.clip(7.5 - 6.0 * ratio + rng.normal(0, 0.25, n), 2, 7.5) # ํ”ผ๋กœ ์ˆ˜๋ช… log10(ํšŒ)\n", " support = 1 + 0.5 * ovh\n", " df[\"build_h\"] = vol * support / np.array([MAT[m][6] for m in mat]) + 0.5\n", " mass_kg = vol * np.array([MAT[m][2] for m in mat]) * support / 1000\n", " df[\"print_cost\"] = mass_kg * np.array([MAT[m][3] for m in mat]) + df[\"build_h\"] * np.array([MAT[m][4] for m in mat]) + 3\n", " df[\"buy_cost\"] = df[\"print_cost\"] * rng.lognormal(0.25, 0.45, n) + 5 # ๊ตฌ๋งค ๋‹จ๊ฐ€(๋งŒ์›, ๊ฐ€์ƒ)\n", " wall_ok = df[\"min_wall_mm\"] >= np.array([MAT[m][5] for m in mat]) * rng.uniform(0.95, 1.25, n)\n", " dim_ok = df[\"max_dim_mm\"] <= np.where(is_poly, BUILD_MM[\"polymer\"], BUILD_MM[\"metal\"])\n", " warp = (~is_poly) & (df[\"max_dim_mm\"] > 140) & (df[\"overhang\"] > 0.3) # ๊ธˆ์† ๋Œ€ํ˜• ์˜ค๋ฒ„ํ–‰ ๋ณ€ํ˜•\n", " thr_bad = df[\"thread\"] & (df[\"min_wall_mm\"] < 1.5) & is_poly # ์–‡์€ ๋‚˜์‚ฌ๋ถ€ ํŒŒ์†\n", " df[\"printable\"] = wall_ok & dim_ok & (ovh < 0.5) & ~warp & ~thr_bad\n", " risk = np.clip(1 - df[\"safety\"] / 3, 0, 1)\n", " df[\"conform\"] = np.clip(100 * (0.45 * np.minimum(df[\"safety\"] / 3, 1) + 0.25 * np.clip((df[\"log_cycles\"] - 3) / 4.5, 0, 1)\n", " + 0.15 * (1 - np.clip(ovh * 1.2, 0, 1)) + 0.15 * df[\"cert\"]) - 8 * risk * df[\"crit\"] / 3 + rng.normal(0, 2, n), 0, 100)\n", " df[\"demand_month\"] = rng.poisson(np.where(df[\"cat\"].isin([\"clip\", \"handle\"]), 40, 15)) + 1 # ์›” ์†Œ์š”์ˆ˜๋Ÿ‰(๊ฐ€์ƒ)\n", " df[\"scrap_cost\"] = df[\"scrap_rate\"] * df[\"demand_month\"] * df[\"buy_cost\"] # ์›” ํ๊ธฐ ์†์‹ค์•ก(๋งŒ์›, ๊ฐ€์ƒ)\n", " return df\n", "\n", "# ---- ๋‹จ์ˆœํ™”ํ•œ ํ•ด์„(FEA ๋Œ€์šฉ) ๊ฐ’๊ณผ ํŒŒ์ƒ ํ”ผ์ฒ˜: ์ž…๋ ฅ ์ •๋ณด๋งŒ์œผ๋กœ ๊ณ„์‚ฐํ•œ๋‹ค(์‹œํ—˜ ๊ฒฐ๊ณผ ๋ˆ„์„ค ์—†์Œ) ----\n", "def make_features(d):\n", " \"\"\"๊ฐ„์ด ์‘๋ ฅ ํ•ด์„(์‘๋ ฅ์ง‘์ค‘๊ณ„์ˆ˜ ํฌํ•จ)๊ณผ ์†Œ์žฌยท๊ณต์ • ์ง€์‹์—์„œ ๋งŒ๋“  ํŒŒ์ƒ ํ”ผ์ฒ˜ 16๊ฐœ.\"\"\"\n", " X = pd.DataFrame(index=d.index)\n", " base = d[\"material\"].map(lambda m: MAT[m][1]).astype(float)\n", " X[\"base_strength\"] = base\n", " X[\"is_metal\"] = (d[\"process\"] == \"metal\").astype(int)\n", " X[\"cross_mm2\"] = d[\"min_wall_mm\"] * np.sqrt(d[\"max_dim_mm\"])\n", " X[\"kt\"] = 1 + 0.15 * d[\"n_holes\"] + 0.5 * d[\"thread\"].astype(int) # ๊ตฌ๋ฉยท๋‚˜์‚ฌ๋ถ€ ์‘๋ ฅ์ง‘์ค‘๊ณ„์ˆ˜(๊ฐ„์ด)\n", " X[\"fea_stress\"] = d[\"load_N\"] / X[\"cross_mm2\"] * X[\"kt\"] # ๊ฐ„์ด ํ•ด์„ ์‘๋ ฅ(MPa)\n", " X[\"stress_ratio\"] = X[\"fea_stress\"] / base # ๊ธฐ์ค€๊ฐ•๋„ ๋Œ€๋น„ ์‘๋ ฅ๋น„\n", " X[\"infill_eff\"] = np.where(X[\"is_metal\"] == 1, 1.0, 0.55 + 0.45 * d[\"infill\"] / 100)\n", " X[\"orient_sin2\"] = np.sin(np.radians(d[\"orient_deg\"])) ** 2\n", " X[\"temp_derate\"] = np.where(X[\"is_metal\"] == 1, 0.0, np.clip((d[\"temp_C\"] - 40) / 80, 0, 1))\n", " X[\"overhang_layer\"] = d[\"overhang\"] * d[\"layer_mm\"]\n", " X[\"slender\"] = d[\"max_dim_mm\"] / d[\"min_wall_mm\"]\n", " X[\"surf_vol\"] = d[\"area_cm2\"] / d[\"volume_cm3\"]\n", " X[\"wall_ratio\"] = d[\"min_wall_mm\"] / d[\"material\"].map(lambda m: MAT[m][5])\n", " X[\"load_per_vol\"] = d[\"load_N\"] / d[\"volume_cm3\"]\n", " X[\"cert\"] = d[\"cert\"].astype(int)\n", " X[\"crit\"] = d[\"crit\"]\n", " return X\n", "\n", "# ---- ๋Œ€์ฒด ์ œ์ž‘ ์š”์ฒญ ๋น„์šฉ ๋ชจ์˜(๊ฐ€์ƒ ๋‹จ๊ฐ€): ๊ตฌ๋งค ๋Œ€๊ธฐ vs 3D ํ”„๋ฆฐํŒ… ----\n", "DOWN = {1: 0.5, 2: 2.0, 3: 5.0} # [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถ€ํ’ˆ ๋ถ€์žฌ๋กœ ์ธํ•œ ์žฅ๋น„ ๊ฐ€๋™์ค‘๋‹จ ๋น„์šฉ(๋งŒ์›/์ผ), ์ค‘์š”๋„๋ณ„\n", "FAIL = {1: 10.0, 2: 40.0, 3: 150.0} # [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋Œ€์ฒดํ’ˆ ์žฅ์ฐฉ ํ›„ ๋ถˆํ•ฉ๊ฒฉ ์‹œ ์žฌ์ž‘์—…ยท์ž„๋ฌด ์˜ํ–ฅ ๋น„์šฉ(๋งŒ์›)\n", "POST = 3.0 # ํ›„์ฒ˜๋ฆฌยท๊ฒ€์‚ฌ ๋น„์šฉ(๋งŒ์›)\n", "\n", "\n", "def make_requests_month(catalog, n=250, seed=0):\n", " \"\"\"์›”๊ฐ„ ๋ถ€ํ’ˆ ์š”์ฒญ: ์›” ์†Œ์š”์ˆ˜๋Ÿ‰์ด ํฐ ๋ถ€ํ’ˆ์ด ๋” ์ž์ฃผ ์š”์ฒญ๋œ๋‹ค.\"\"\"\n", " return catalog.sample(n, replace=True, weights=catalog[\"demand_month\"], random_state=seed).reset_index(drop=True)\n", "\n", "\n", "def buy_cost(r):\n", " return r[\"buy_cost\"] + r[\"lead_days\"] * DOWN[r[\"crit\"]]\n", "\n", "\n", "def print_outcome(r, delay_d=1.0):\n", " \"\"\"์‹ค์ œ ์‹œํ—˜ ๊ฒฐ๊ณผ(์ˆจ์€ ์ง„์‹ค)์— ๋”ฐ๋ฅธ ๋Œ€์ฒด ์ œ์ž‘ ๋น„์šฉ. ๋ถˆํ•ฉ๊ฒฉ์ด๋ฉด ๊ตฌ๋งค๋กœ ๋˜๋Œ์•„๊ฐ€๊ณ  ๋ถˆํ•ฉ๊ฒฉ ๋น„์šฉ์ด ๋“ ๋‹ค.\"\"\"\n", " wait = (r[\"build_h\"] / 24 + delay_d + 2.0) * DOWN[r[\"crit\"]]\n", " ok = bool(r[\"printable\"] and r[\"conform\"] >= 70)\n", " c = r[\"print_cost\"] + POST + wait\n", " return (c, True) if ok else (c + buy_cost(r) + FAIL[r[\"crit\"]], False)\n", "\n", "CONFIG = {\"sf_min\": 1.5, \"score_min\": 70.0, \"retrain_tolerance\": 0.02, \"scrap_cut\": 0.30,\n", " \"corr\": {\"ํด๋ฆฌ๋จธ ์–‡์€ ๋ฒฝ\": 0.83, \"๊ธˆ์† ๋Œ์ถœ ๋งŽ์Œ\": 0.83, \"์ผ๋ฐ˜\": 0.98}} # ๋ฏธ์…˜ 9 ์žฌ๋ณด์ • ๊ฒฐ๊ณผ(๋ชจ๋ธ์„ ๊ต์ฒดํ•˜์ง€ ์•Š์„ ๋•Œ๋งŒ ์ ์šฉ)\n", "OPS_GUIDE = { # docs/mnt06_operations_guide.md ์š”์•ฝ\n", " \"1. ๋ชฉ์ ๊ณผ ๋ฒ”์œ„\": \"ํ๊ธฐ์œจ์ด ๋†’์€ ์ˆ˜๋ฆฌ๋ถ€์† ์ค‘ ์•ผ์ „ 3D ํ”„๋ฆฐํŒ… ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด ์„ ๋ณ„. ์žฅ์ฐฉ ์Šน์ธ์€ ์ •๋น„๊ธฐ์ˆ ๊ด€.\",\n", " \"2. ์ž…๋ ฅ ๋ฐ์ดํ„ฐ\": \"๋ถ€ํ’ˆ(CAD ์š”์•ฝ ์น˜์ˆ˜ยท์†Œ์žฌยทํ•˜์ค‘ยทํ๊ธฐ์œจยท์กฐ๋‹ฌ์ผ), ํŒŒ๊ดด ์‹œํ—˜ ๊ฒฐ๊ณผ. ์‹ค์ œ ๋ถ€ํ’ˆยท์žฌ๊ณ  ์ •๋ณด๋Š” ์ž…๋ ฅํ•˜์ง€ ์•Š๋Š”๋‹ค.\",\n", " \"3. ์ผ์ผ ์šด์šฉ ์ ˆ์ฐจ\": \"์š”์ฒญ ๊ฒ€์ฆ -> ํ›„๋ณด ์„ ๋ณ„ -> ์Šน์ธ ๋Œ€๊ธฐ์—ด -> ์ •๋น„๊ธฐ์ˆ ๊ด€ ๊ฒฐ์ • -> ์ถœ๋ ฅยทํ›„์ฒ˜๋ฆฌ -> ์‹œํ—˜ ๊ฒฐ๊ณผ ์ž…๋ ฅ.\",\n", " \"4. ์Šน์ธ(HITL) ๊ธฐ์ค€\": \"๋ชจ๋“  ๋Œ€์ฒด ์ œ์ž‘์€ ์ •๋น„๊ธฐ์ˆ ๊ด€ ์Šน์ธ, ํ•ต์‹ฌ ๋ถ€ํ’ˆ์€ ์†Œ์žฌ ์ธ์ฆ์„œ ํ™•๋ณด, ๋ฌด์‘๋‹ต ์‹œ ๊ธฐ์กด ์กฐ๋‹ฌ ์œ ์ง€, ์‚ฌ์œ  ๊ธฐ๋ก.\",\n", " \"5. ์žฌํ•™์Šต ์ ˆ์ฐจ\": \"๋ถ„๊ธฐ 1ํšŒ ์‹ ๊ทœ ํŒŒ๊ดด ์‹œํ—˜์œผ๋กœ ๋„์ „์ž ๋ชจ๋ธ ํ•™์Šต, ๊ฒ€์ฆ MAPE๊ฐ€ ๋‚˜๋น ์ง€์ง€ ์•Š์„ ๋•Œ๋งŒ ๊ต์ฒด.\",\n", " \"6. ์žฅ์•  ๋Œ€์‘\": \"๋ชจ๋ธ ์˜ค๋ฅ˜ ์‹œ ์‘๋ ฅ/๊ธฐ์ค€๊ฐ•๋„ ๋น„ ๊ฐ„์ด ๊ทœ์น™์œผ๋กœ ๋Œ€์ฒด, ์†์ƒ ๋ ˆ์ฝ”๋“œ๋Š” ์ˆ˜๋™ ํ™•์ธ ๋ชฉ๋ก์œผ๋กœ.\",\n", " \"7. ์—ฐ๋™ ์ธํ„ฐํŽ˜์ด์Šค\": \"๊ตญ๋ฐฉ ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„์™€ ๋ถ€ํ’ˆ ์‹๋ณ„์ฝ”๋“œยท๋ถ„๋ฅ˜๊ตฐยทํ๊ธฐ์ˆ˜๋Ÿ‰ ํ‘œ์ค€ ํ•„๋“œ๋ฅผ CSV/API๋กœ ๊ตํ™˜ํ•˜๋Š” ์•ˆ์„ ์ฐจ๊ธฐ ๊ณผ์ œ๋กœ ๋‘”๋‹ค.\",\n", "}\n", "\n", "\n", "def destructive_test(d, seed):\n", " \"\"\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์‹ค๋ฌผ ํŒŒ๊ดด ์‹œํ—˜ ๊ฐ•๋„(๋ชจ๋ธ์ด ๋ชจ๋ฅด๋Š” ํ˜•์ƒยท๊ณต์ • ํšจ๊ณผ ํฌํ•จ).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " f = np.where((d[\"process\"] == \"polymer\") & (d[\"min_wall_mm\"] < 1.5), 0.86, 1.0) * np.where((d[\"process\"] == \"metal\") & (d[\"overhang\"] > 0.3), 0.88, 1.0) \\\n", " * np.where(d[\"cert\"] == 0, 0.94, 1.0)\n", " return d[\"strength_mpa\"] * f * rng.lognormal(0, 0.03, len(d))\n", "\n", "\n", "def group(d):\n", " return np.select([(d[\"process\"] == \"polymer\") & (d[\"min_wall_mm\"] < 1.5), (d[\"process\"] == \"metal\") & (d[\"overhang\"] > 0.3)], [\"ํด๋ฆฌ๋จธ ์–‡์€ ๋ฒฝ\", \"๊ธˆ์† ๋Œ์ถœ ๋งŽ์Œ\"], \"์ผ๋ฐ˜\")\n", "\n", "\n", "def validate(df):\n", " ok = (df[\"min_wall_mm\"] > 0) & (df[\"max_dim_mm\"] > 0) & df[\"material\"].isin(list(MAT)) & df[\"load_N\"].notna()\n", " return df[ok].copy(), df[~ok].copy()\n", "\n", "\n", "def gb():\n", " return GradientBoostingRegressor(n_estimators=150, max_depth=3, learning_rate=0.06, random_state=0)\n", "\n", "\n", "def fit_strength(d):\n", " X = make_features(d)\n", " return gb().fit(X, d[\"meas\"] / X[\"base_strength\"])\n", "\n", "\n", "def pred_strength(model, d):\n", " X = make_features(d)\n", " return model.predict(X) * X[\"base_strength\"]\n", "\n", "\n", "def mape(a, p):\n", " return float(np.mean(np.abs(np.asarray(a) - p) / np.asarray(a)))\n", "\n", "\n", "def retrain(champion, train_df, new_df):\n", " \"\"\"๋„์ „์ž ๋ชจ๋ธ์„ (๊ธฐ์กด+์‹ ๊ทœ ์•ž 2/3)๋กœ ํ•™์Šต, ์‹ ๊ทœ ๋’ค 1/3์—์„œ ์ฑ”ํ”ผ์–ธ๊ณผ ๋น„๊ตํ•ด ๋” ๋‚ซ๊ฑฐ๋‚˜ ๋น„์Šทํ•˜๋ฉด ๊ต์ฒด.\"\"\"\n", " cut = int(len(new_df) * 2 / 3)\n", " hold = new_df.iloc[cut:]\n", " challenger = fit_strength(pd.concat([train_df, new_df.iloc[:cut]], ignore_index=True))\n", " m_old, m_new = mape(hold[\"meas\"], pred_strength(champion, hold)), mape(hold[\"meas\"], pred_strength(challenger, hold))\n", " swap = m_new <= m_old + CONFIG[\"retrain_tolerance\"]\n", " return (challenger if swap else champion), {\"์ฑ”ํ”ผ์–ธ MAPE\": round(m_old, 3), \"๋„์ „์ž MAPE\": round(m_new, 3), \"๊ต์ฒด\": swap}\n", "\n", "\n", "def stress(d):\n", " return d[\"load_N\"] / (d[\"min_wall_mm\"] * np.sqrt(d[\"max_dim_mm\"]))\n", "\n", "\n", "# ---------------- 1. ํ†ตํ•ฉ ์‹คํ–‰ ----------------\n", "t_start = time.perf_counter()\n", "past = make_parts(800, seed=11); past[\"meas\"] = past[\"strength_mpa\"] # ๊ณผ๊ฑฐ ์‹œํ—˜(๋ชจ๋ธ์ด ๋ชจ๋ฅด๋Š” ํšจ๊ณผ ์—†์Œ)\n", "new = make_parts(300, seed=12); new[\"meas\"] = destructive_test(new, 14) # ์ด๋ฒˆ ๋ถ„๊ธฐ ์‹ ๊ทœ ํŒŒ๊ดด ์‹œํ—˜\n", "champion = fit_strength(past)\n", "model, rt = retrain(champion, past, new)\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฌํ•™์Šต: {rt} -> {'๋„์ „์ž ๊ต์ฒด' if rt['๊ต์ฒด'] else '์ฑ”ํ”ผ์–ธ ์œ ์ง€'}\")\n", "Xp = make_features(past)\n", "score_model = gb().fit(Xp, past[\"conform\"])\n", "nxt = make_parts(250, seed=22) # ๋‹ค์Œ ๋ถ„๊ธฐ ๋Œ€์ฒด ์ œ์ž‘ ์š”์ฒญ ํ›„๋ณด\n", "nxt.loc[[5, 40], \"min_wall_mm\"] = [-1.0, 0.0] # ์†์ƒ ๋ ˆ์ฝ”๋“œ 2๊ฑด ์ฃผ์ž…\n", "ops, manual = validate(nxt)\n", "ops = ops.reset_index(drop=True)\n", "ops[\"pred\"] = pred_strength(model, ops)\n", "ops[\"grp\"] = group(ops)\n", "if not rt[\"๊ต์ฒด\"]:\n", " ops[\"pred\"] *= ops[\"grp\"].map(CONFIG[\"corr\"])\n", "ops[\"sf\"] = ops[\"pred\"] / stress(ops)\n", "ops[\"score\"] = np.clip(score_model.predict(make_features(ops)), 0, 100)\n", "ops[\"rule_ok\"] = (ops[\"min_wall_mm\"] >= 1.0) & (ops[\"max_dim_mm\"] <= ops[\"process\"].map(BUILD_MM)) & (ops[\"overhang\"] < 0.5)\n", "cand = ops[ops[\"rule_ok\"] & (ops[\"sf\"] >= CONFIG[\"sf_min\"]) & (ops[\"score\"] >= CONFIG[\"score_min\"])].copy()\n", "cand[\"status\"] = \"PENDING\" # ์ •๋น„๊ธฐ์ˆ ๊ด€ ๊ฒฐ์ • ์ „๊นŒ์ง€ ํ™•์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค\n", "cand[\"need_cert\"] = (cand[\"crit\"] == 3) & (cand[\"cert\"] == 0)\n", "sec = time.perf_counter() - t_start\n", "print(f\"ํ›„๋ณด ์š”์ฒญ {len(nxt)}๊ฑด: ํ‰๊ฐ€ {len(ops)}๊ฑด, ์ˆ˜๋™ ํ™•์ธ {len(manual)}๊ฑด, ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด {len(cand)}๊ฑด, ์ „์ฒด ์‹คํ–‰ {sec:.1f}์ดˆ\")\n", "\n", "# ---------------- 2. ์ž์ฒด ์ ๊ฒ€ ----------------\n", "try:\n", " bad_pred = pred_strength(None, ops.head(3))\n", " fallback = \"๊ฐ„์ด ๊ทœ์น™ ์ „ํ™˜ ๋ถˆํ•„์š”\"\n", "except Exception:\n", " fallback = \"๊ฐ„์ด ๊ทœ์น™ ๋Œ€์ฒด\"\n", "simple = np.clip(100 * (1 - 1.8 * make_features(ops.head(3))[\"stress_ratio\"]), 0, 100)\n", "checks = {\n", " \"์š”์ฒญ ์œ ์‹ค 0๊ฑด\": len(ops) + len(manual) == len(nxt),\n", " \"์†์ƒ ๋ ˆ์ฝ”๋“œ ์ˆ˜๋™ ํ™•์ธ ๋ถ„๋ฆฌ\": len(manual) == 2,\n", " \"์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰\": \"๊ต์ฒด\" in rt,\n", " \"์˜ˆ์ธก ๊ฐ•๋„ ์–‘์ˆ˜\": bool((ops[\"pred\"] > 0).all()),\n", " \"๋ชจ๋ธ ์žฅ์•  ์‹œ ๊ฐ„์ด ๊ทœ์น™ ๋Œ€์ฒด\": fallback == \"๊ฐ„์ด ๊ทœ์น™ ๋Œ€์ฒด\" and bool(np.isfinite(simple).all()),\n", " \"๋ชจ๋“  ํ›„๋ณด ์ ์ˆ˜ยท์•ˆ์ „์œจ ์‚ฐ์ถœ\": bool(ops[[\"sf\", \"score\"]].notna().all().all()),\n", " \"ํ›„๋ณด๋Š” ์•ˆ์ „์œจ ๊ธฐ์ค€ ์ด์ƒ\": bool((cand[\"sf\"] >= CONFIG[\"sf_min\"]).all()),\n", " \"์ž๋™ ์Šน์ธ 0๊ฑด(์Šน์ธ ๋Œ€์ƒ์€ ๋Œ€๊ธฐ)\": bool((cand[\"status\"] == \"PENDING\").all()),\n", "}\n", "print(f\"\\n์ž์ฒด ์ ๊ฒ€ {sum(checks.values())}/{len(checks)} ํ†ต๊ณผ, ์ž๋™ ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ {'์ •์ƒ ์‹คํ–‰' if checks['์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰'] else '์‹คํŒจ'}\")\n", "for k, v in checks.items():\n", " if not v:\n", " print(\" ์‹คํŒจ:\", k)\n", "doc = sum(bool(v.strip()) for v in OPS_GUIDE.values()) / len(OPS_GUIDE)\n", "print(f\"์šด์˜ ๋ฌธ์„œ {len(OPS_GUIDE)}๊ฐœ ์žฅ ์ž‘์„ฑ๋ฅ  {doc:.0%} (๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if doc == 1 else '๋ฏธ๋‹ฌ'}) - \" + \", \".join(k.split(\". \")[1] for k in OPS_GUIDE))\n", "\n", "# ---------------- 3. ํ™”๋ฉด๊ณผ ๋ธŒ๋ฆฌํ•‘ ----------------\n", "real_ok = (destructive_test(cand, 24) / stress(cand)) >= CONFIG[\"sf_min\"] # ๋ชจ์˜ ์ •๋‹ต: ์Šน์ธ ํ›„ ์‹ค์ œ ํŒŒ๊ดด ์‹œํ—˜์„ ํ–ˆ๋‹ค๋ฉด\n", "cost_buy = cand.apply(buy_cost, axis=1).sum()\n", "cost_print = sum(r[\"print_cost\"] + POST + (r[\"build_h\"] / 24 + 3.0) * DOWN[r[\"crit\"]] for _, r in cand.iterrows())\n", "days_buy, days_print = cand[\"lead_days\"].mean(), (cand[\"build_h\"] / 24 + 3.0).mean()\n", "scrap = (cand[\"scrap_rate\"] * cand[\"demand_month\"]).sum() * CONFIG[\"scrap_cut\"]\n", "fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n", "ax[0].scatter(ops[\"score\"], ops[\"sf\"].clip(upper=10), s=8, c=np.where(ops.index.isin(cand.index), \"tab:blue\", \"lightgray\"))\n", "ax[0].axvline(CONFIG[\"score_min\"], ls=\"--\", c=\"k\"); ax[0].axhline(CONFIG[\"sf_min\"], ls=\"--\", c=\"k\"); ax[0].set_xlabel(\"conformity score\"); ax[0].set_ylabel(\"safety factor (clipped)\")\n", "ax[0].set_title(\"Candidates (blue) pending officer approval\")\n", "ax[1].bar([\"purchase\", \"3D print\"], [days_buy, days_print]); ax[1].set_title(\"Mean supply days (candidates)\")\n", "plt.tight_layout(); plt.show()\n", "\n", "print(\"\\n[์ •๋น„๊ธฐ์ˆ ๊ด€ ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ] [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\")\n", "print(f\"1. ๋‹ค์Œ ๋ถ„๊ธฐ ์š”์ฒญ {len(nxt)}๊ฑด ์ค‘ ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด {len(cand)}๊ฑด(์Šน์ธ ๋Œ€๊ธฐ), ํ›„๋ณด ๋ถ€ํ’ˆ์˜ ์›” ํ๊ธฐ ์†์‹ค ํ•ฉ๊ณ„ {cand['scrap_cost'].sum():,.0f}๋งŒ์›(๊ฐ€์ƒ)\")\n", "print(f\"2. ๊ณต๊ธ‰ ์‹œ๊ฐ„ ํ‰๊ท  {days_buy:.1f}์ผ -> {days_print:.1f}์ผ({1 - days_print / days_buy:.0%} ๋‹จ์ถ•), ์˜ˆ์ƒ ๋น„์šฉ {cost_buy:,.0f} -> {cost_print:,.0f}๋งŒ์›(๊ฐ€์ • ๋‹จ๊ฐ€)\")\n", "print(f\"3. ํ•ต์‹ฌ ๋ถ€ํ’ˆ ํ›„๋ณด {int((cand['crit'] == 3).sum())}๊ฑด ์ค‘ ์†Œ์žฌ ์ธ์ฆ์„œ ํ™•๋ณด ํ•„์š” {int(cand['need_cert'].sum())}๊ฑด, ํ๊ธฐ ๊ฐ์†Œ ์ถ”์ • {scrap:.0f}๊ฐœ/์›”(๊ฐ€์ • {CONFIG['scrap_cut']:.0%})\")\n", "print(f\"4. ํ›„๋ณด ์ค‘ ์‹ค๋ฌผ ์‹œํ—˜์„ ํ•˜๋ฉด ์•ˆ์ „์œจ ๋ฏธ๋‹ฌ์ผ ๋ถ€ํ’ˆ(๋ชจ์˜ ์ •๋‹ต) {int((~real_ok).sum())}๊ฑด, ์†์ƒ ๋ ˆ์ฝ”๋“œ {len(manual)}๊ฑด ์ˆ˜๋™ ํ™•์ธ -> ์Šน์ธ ์ „ ๋Œ€ํ‘œ ๋ถ€ํ’ˆ ํŒŒ๊ดด ์‹œํ—˜์„ ํŽธ์„ฑํ•œ๋‹ค\")\n", "print(f\"5. ๊ฐ•๋„ ์˜ˆ์ธก ๋ชจ๋ธ: {'์‹ ๊ทœ ์‹œํ—˜์œผ๋กœ ๊ต์ฒด' if rt['๊ต์ฒด'] else '๊ธฐ์กด ์œ ์ง€'}(๊ฒ€์ฆ MAPE {min(rt['์ฑ”ํ”ผ์–ธ MAPE'], rt['๋„์ „์ž MAPE']):.1%})\")\n", "print(\"6. ๊ฑด์˜: ๊ธˆ์† ํ•ต์‹ฌ ๋ถ€ํ’ˆ์€ ์‹œํ—˜ ํ›„ ํ™•๋Œ€, ๊ตญ๋ฐฉ ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„์™€ ํ‘œ์ค€ ํ•„๋“œ ์—ฐ๋™ ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜. ์žฅ์ฐฉ ์Šน์ธ์€ ์ •๋น„๊ธฐ์ˆ ๊ด€ ๊ฒฐ์ •\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 10 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฌํ•™์Šต: {'์ฑ”ํ”ผ์–ธ MAPE': 0.12, '๋„์ „์ž MAPE': 0.105, '๊ต์ฒด': True} -> ๋„์ „์ž ๊ต์ฒด\n", "ํ›„๋ณด ์š”์ฒญ 250๊ฑด: ํ‰๊ฐ€ 248๊ฑด, ์ˆ˜๋™ ํ™•์ธ 2๊ฑด, ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด 86๊ฑด, ์ „์ฒด ์‹คํ–‰ 2.6์ดˆ\n", "\n", "์ž์ฒด ์ ๊ฒ€ 8/8 ํ†ต๊ณผ, ์ž๋™ ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์ •์ƒ ์‹คํ–‰\n", "์šด์˜ ๋ฌธ์„œ 7๊ฐœ ์žฅ ์ž‘์„ฑ๋ฅ  100% (๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ) - ๋ชฉ์ ๊ณผ ๋ฒ”์œ„, ์ž…๋ ฅ ๋ฐ์ดํ„ฐ, ์ผ์ผ ์šด์šฉ ์ ˆ์ฐจ, ์Šน์ธ(HITL) ๊ธฐ์ค€, ์žฌํ•™์Šต ์ ˆ์ฐจ, ์žฅ์•  ๋Œ€์‘, ์—ฐ๋™ ์ธํ„ฐํŽ˜์ด์Šค\n", "\n", "[์ •๋น„๊ธฐ์ˆ ๊ด€ ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ] [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\n", "1. ๋‹ค์Œ ๋ถ„๊ธฐ ์š”์ฒญ 250๊ฑด ์ค‘ ๋Œ€์ฒด ์ œ์ž‘ ํ›„๋ณด 86๊ฑด(์Šน์ธ ๋Œ€๊ธฐ), ํ›„๋ณด ๋ถ€ํ’ˆ์˜ ์›” ํ๊ธฐ ์†์‹ค ํ•ฉ๊ณ„ 104,000๋งŒ์›(๊ฐ€์ƒ)\n", "2. ๊ณต๊ธ‰ ์‹œ๊ฐ„ ํ‰๊ท  40.1์ผ -> 3.4์ผ(92% ๋‹จ์ถ•), ์˜ˆ์ƒ ๋น„์šฉ 35,338 -> 20,276๋งŒ์›(๊ฐ€์ • ๋‹จ๊ฐ€)\n", "3. ํ•ต์‹ฌ ๋ถ€ํ’ˆ ํ›„๋ณด 24๊ฑด ์ค‘ ์†Œ์žฌ ์ธ์ฆ์„œ ํ™•๋ณด ํ•„์š” 9๊ฑด, ํ๊ธฐ ๊ฐ์†Œ ์ถ”์ • 102๊ฐœ/์›”(๊ฐ€์ • 30%)\n", "4. ํ›„๋ณด ์ค‘ ์‹ค๋ฌผ ์‹œํ—˜์„ ํ•˜๋ฉด ์•ˆ์ „์œจ ๋ฏธ๋‹ฌ์ผ ๋ถ€ํ’ˆ(๋ชจ์˜ ์ •๋‹ต) 0๊ฑด, ์†์ƒ ๋ ˆ์ฝ”๋“œ 2๊ฑด ์ˆ˜๋™ ํ™•์ธ -> ์Šน์ธ ์ „ ๋Œ€ํ‘œ ๋ถ€ํ’ˆ ํŒŒ๊ดด ์‹œํ—˜์„ ํŽธ์„ฑํ•œ๋‹ค\n", "5. ๊ฐ•๋„ ์˜ˆ์ธก ๋ชจ๋ธ: ์‹ ๊ทœ ์‹œํ—˜์œผ๋กœ ๊ต์ฒด(๊ฒ€์ฆ MAPE 10.5%)\n", "6. ๊ฑด์˜: ๊ธˆ์† ํ•ต์‹ฌ ๋ถ€ํ’ˆ์€ ์‹œํ—˜ ํ›„ ํ™•๋Œ€, ๊ตญ๋ฐฉ ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„์™€ ํ‘œ์ค€ ํ•„๋“œ ์—ฐ๋™ ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜. ์žฅ์ฐฉ ์Šน์ธ์€ ์ •๋น„๊ธฐ์ˆ ๊ด€ ๊ฒฐ์ •\n", "```\n", "
\n" ] } ], "metadata": { "colab": { "provenance": [] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }