{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "# 실습 04 · 퍼셉트론과 머신러닝 기초\n\n> **연계 강의자료:** M4 「머신러닝 기초」 · 5~7주차\n> **목표:** 퍼셉트론 학습 규칙을 numpy로 직접 구현해 AND는 학습되지만 XOR은 학습되지 않는다는 것을 확인하고, 혼동행렬 4대 지표와 베이즈 정리를 코드로 계산한다.\n>\n> 중간고사 계산 문항(퍼셉트론 1 epoch, 혼동행렬, 베이즈)과 직결된다. 강의자료 M4의 실습 위젯과 같은 수치를 재현한다.\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. 퍼셉트론을 numpy로 (교재 코드)\n",
    "\n",
    "학습 규칙은 단 한 줄이다: **W ← W + η·(정답−예측)·x** — 틀린 만큼, 틀린 방향으로.\n",
    "입력 벡터 끝의 1은 바이어스용 가상 입력이다(강의자료 M4 그림 5)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "X = np.array([[0,0,1],[0,1,1],[1,0,1],[1,1,1]])  # 끝의 1 = 바이어스 입력\n",
    "y = np.array([0, 0, 0, 1])                        # AND 정답\n",
    "W = np.zeros(3); eta = 0.2\n",
    "\n",
    "step = lambda t: 1 if t > 1e-7 else 0             # 계단 함수\n",
    "\n",
    "for epoch in range(6):\n",
    "    for i in range(len(X)):\n",
    "        e = y[i] - step(X[i] @ W)   # 오차 = 정답 - 예측\n",
    "        W += eta * e * X[i]         # 틀린 만큼, 틀린 방향으로\n",
    "    print(f\"epoch {epoch}: W = {W}\")\n",
    "\n",
    "print(\"\\n예측:\", [step(x @ W) for x in X])\n",
    "# 기대: 마지막에 W = [0.4  0.2 -0.4] 로 수렴, 예측 [0, 0, 0, 1]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### 과제 1-1\n위 코드에서 `y`를 **XOR**(`[0,1,1,0]`)로 바꾸고 20 에폭을 돌려 보라.\n- W가 수렴하는가? 몇 개까지 맞히는가?\n- 이것이 \"조정의 문제가 아니라 해가 없는 문제\"인 이유를 강의자료 M4 그림 6으로 설명해 보라."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. 같은 것을 sklearn 한 줄로"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "from sklearn.linear_model import Perceptron\n\nX2 = [[0,0],[0,1],[1,0],[1,1]]\nfor name, y2 in [(\"AND\", [0,0,0,1]), (\"OR\", [0,1,1,1]), (\"XOR\", [0,1,1,0])]:\n    clf = Perceptron(tol=1e-3, random_state=0).fit(X2, y2)\n    print(f\"{name}: 예측 {clf.predict(X2)}  정답 {y2}  정확도 {clf.score(X2, y2):.2f}\")\n# 관찰: AND/OR은 1.0, XOR은 아무리 돌려도 1.0이 되지 않는다"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. 혼동행렬과 4대 지표\n",
    "\n",
    "시나리오: 스팸 필터가 300건을 판정했다 — 스팸 100건 중 60건 적중(TP), 40건 놓침(FN),\n",
    "정상 200건 중 15건 오판(FP), 185건 정상 처리(TN). *(중간고사 유형과 동일한 수치)*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from sklearn.metrics import confusion_matrix, classification_report\n",
    "\n",
    "# 판정 결과를 배열로 재구성 (1=스팸, 0=정상)\n",
    "y_true = np.array([1]*100 + [0]*200)\n",
    "y_pred = np.array([1]*60 + [0]*40 + [1]*15 + [0]*185)\n",
    "\n",
    "cm = confusion_matrix(y_true, y_pred, labels=[1, 0])\n",
    "print(\"혼동행렬 [[TP FN],[FP TN]]:\\n\", cm)\n",
    "print(classification_report(y_true, y_pred, target_names=[\"정상\", \"스팸\"], digits=2))\n",
    "# 손계산 검산: accuracy 0.82, precision 0.80, recall 0.60, F1 0.69 (스팸 기준)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### 과제 3-1\nFN을 0으로 만든 경우(스팸을 전부 잡되 FP가 늘어남)와 반대로 FP를 0으로 만든 경우의 가상 판정 배열을 만들어\nprecision과 recall이 시소처럼 움직이는 것을 확인하라. 스팸 필터에서는 어느 쪽 오류가 더 비싼가?"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. 베이즈 정리 — 양성이면 병일까?\n",
    "\n",
    "유병률 1%, 민감도 90%, 특이도 91%인 검사에서 양성이 나왔을 때 실제 병일 확률을 계산한다.\n",
    "*(강의자료 M4 실습 2와 같은 수치 — 답은 약 9.2%)*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def p_disease_given_positive(prevalence, sensitivity, specificity, N=10_000):\n",
    "    sick = N * prevalence\n",
    "    tp = sick * sensitivity                      # 진양성\n",
    "    fp = (N - sick) * (1 - specificity)          # 위양성\n",
    "    post = tp / (tp + fp)\n",
    "    print(f\"1만 명 중 환자 {sick:.0f}명 · 양성 {tp+fp:.0f}명(진 {tp:.0f} / 위 {fp:.0f})\"\n",
    "          f\" → P(병|양성) = {post:.1%}\")\n",
    "    return post\n",
    "\n",
    "p_disease_given_positive(0.01, 0.90, 0.91)   # 기대: 9.2%\n",
    "p_disease_given_positive(0.20, 0.90, 0.91)   # 유행기: 같은 검사인데 확률이 급등한다"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### 과제 4-1\n양성 판정을 받은 사람이 **독립적으로 한 번 더** 검사를 받아 또 양성이 나오면 어떻게 될까? 1차 사후 확률을\n2차 검사의 사전 확률로 넣어 계산하고, 의사가 재검사를 지시하는 이유를 설명하라."
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}