[Zero to ML Master #07] 베이즈 정리 쉽게 이해하기 — 확률·조건부 확률과 병원 검사의 함정 (1부)

오톨로리즘·2일 전
post-thumbnail

정확도 99%짜리 검사에서 양성이 나왔습니다. 실제로 병에 걸렸을 확률은 얼마일까요? 대부분 "99%"라고 답하지만, 이 글의 계산으로는 겨우 1.94% 입니다. 😱

이 함정을 풀어 주는 도구가 베이즈 정리예요. 새로운 증거가 들어올 때마다 믿음을 조금씩 조정하는 저울이죠. 1부에서는 확률 → 조건부 확률 → 베이즈 정리를 차근차근 쌓아 올립니다.

📘 이 글은 위키독스 도서 Zero to 머신러닝 딥러닝 Master 의 00-05 확률·베이즈 정리 — 병원 검사의 함정 (1부) 을 블로그용으로 다듬은 글입니다.
💻 실습 코드: GitHub · 🏠 mldict.net · ✍️ 기술 블로그

💻 이 글의 코드: 00-05-probability-bayes · Open in Colab — 핵심 코드와 실제 실행 결과는 글 아래 💻 GitHub 실습 섹션에 있습니다.

⬅️ 이전 글: #06 경사하강법 완전 정복 — 학습률·발산 조건·선형회귀·자동미분 (딥러닝 수학, 2부) \
➡️ 다음 글: #08 정규분포·소프트맥스·교차 엔트로피 — AI는 확률로 대답한다 (2부)

TL;DR — 3줄 요약

  • 확률은 원하는 경우의 수 ÷ 전체 경우의 수이고, 많이 반복할수록 이론값에 가까워집니다(대수의 법칙).
  • 조건부 확률은 분모를 갈아끼우는 연산이라 P(A∣B)P(A|B)와 P(B∣A)P(B|A)는 완전히 다른 값입니다.
  • 베이즈 정리는 사전 확률 × 우도로 믿음을 갱신합니다. 사건이 희귀하면 정확한 검사도 신뢰도가 낮아져요.

✏️ 꼭 알아야 할 TIP

🎯 핵심 개념: 확률은 "원하는 경우의 수 ÷ 전체 경우의 수"이고, 조건부 확률은 "새 정보를 알고 나서 전체(분모)를 다시 좁혀 계산한 확률"입니다.

⚖️ 한 줄 비유: 베이즈 정리는 "새로운 증거가 들어올 때마다 내 믿음을 조금씩 조정하는 저울"입니다.

🔑 이 장을 관통하는 한 줄 공식:

P(원인∣증거)=P(증거∣원인) P(원인)P(증거)P(\text{원인}\mid\text{증거}) = \frac{P(\text{증거}\mid\text{원인})\,P(\text{원인})}{P(\text{증거})}

코드에서는 posterior = like*prior / (like*prior + fpr*(1-prior)) 딱 한 줄이 됩니다. 수식 한 줄 = 코드 한 줄의 대응을 계속 확인하며 읽어보세요.

😱 가장 중요한 포인트: 검사 정확도가 99%여도, 병이 희귀하면(발병률 0.1%) 양성 판정을 받은 사람이 실제 환자일 확률은 겨우 1.9%입니다. 이 장에서 그 이유를 인원수·공식·코드 세 가지 방법으로 확인합니다.

🏗️ 실무에서 쓰는 이유: 스팸 필터, 추천 시스템, 의료 진단 보조, 자율주행 센서 융합 — "불확실한 증거로 확률을 업데이트하는" 모든 AI의 근간이 베이즈 정리입니다. 그리고 5단계의 소프트맥스·교차 엔트로피는 여러분이 쓰게 될 모든 분류 모델의 출력과 손실입니다.

📋 선행지식 (Prerequisites)

  • ➗ 초등 수준의 분수와 나눗셈, 백분율(%) 개념 — 이것만 있으면 3단계까지 갑니다
  • 🔢 지수 표기 exe^x와 로그 log⁡x\log x의 대략적인 느낌 (5단계에서 다시 설명합니다)
  • 🧮 00-01 벡터의 성분 개념 — 소프트맥스 출력이 벡터라서 필요
  • 📈 00-04 미분·경사하강법 — 5단계에서 교차 엔트로피의 기울기를 유도할 때 연결됩니다
  • 🐍 파이썬 함수 정의(def)와 리스트 — 코드마다 한 줄씩 해설하니 몰라도 괜찮습니다

🎯 학습 목표

☐ 🎲 확률을 P(A)=n(A)/n(Ω)P(A)=n(A)/n(\Omega)로 계산하고, 시뮬레이션으로 검증할 수 있다 🟢 초급

☐ 📊 대수의 법칙을 실험으로 확인하고, 시행 횟수와 오차의 관계를 설명할 수 있다 🟢 초급

☐ 🔀 P(A∣B)P(A|B)와 P(B∣A)P(B|A)가 왜 다른 값인지 분할표로 설명할 수 있다 🟢 초급 ⭐

☐ 🧮 곱셈정리 P(A∩B)=P(A∣B)P(B)P(A\cap B)=P(A|B)P(B)로부터 베이즈 정리를 직접 유도할 수 있다 🔵 중급 ⭐

☐ 🏥 발병률·정확도·오탐률이 주어졌을 때 사후 확률을 손과 코드로 계산할 수 있다 🔵 중급 ⭐

☐ ⚖️ 오즈(odds) 형태 "사후 오즈 = 사전 오즈 × 우도비"를 유도하고 암산에 활용할 수 있다 🔴 심화

☐ 🔁 검사를 2번, 3번 반복했을 때 확률이 어떻게 갱신되는지 코드로 시뮬레이션할 수 있다 🔴 심화 🚀

☐ 🔔 정규분포의 확률밀도함수를 직접 구현하고 68-95-99.7 규칙을 검증할 수 있다 🔵 중급

☐ 🗳️ 소프트맥스를 구현하고, 최댓값을 빼도 결과가 같은 이유를 증명할 수 있다 🔵 중급

☐ 🎁 소프트맥스+교차엔트로피의 기울기가 y^−y\hat{y}-y임을 유도하고 수치미분으로 검증할 수 있다 🔴 심화 ⭐🚀

📊 핵심 학습 내용 — 수식과 코드의 대응

단계학습 내용핵심 수학공식대응하는 코드
1️⃣확률의 정의·대수의 법칙P(A)=n(A)n(Ω)P(A)=\dfrac{n(A)}{n(\Omega)}len(favorable)/len(space)
2️⃣조건부 확률·곱셈정리P(A∣B)=P(A∩B)P(B)P(A\vert B)=\dfrac{P(A\cap B)}{P(B)}data[mask].mean()
3️⃣베이즈 정리·오즈 형태P(H∣E)=P(E∣H)P(H)P(E)P(H\vert E)=\dfrac{P(E\vert H)P(H)}{P(E)}like*prior/evidence
4️⃣정규분포·표준화f(x)=1σ2πe−(x−μ)22σ2f(x)=\dfrac{1}{\sigma\sqrt{2\pi}}e^{-\frac{(x-\mu)^2}{2\sigma^2}}np.exp(-(x-mu)**2/(2*s**2))
5️⃣소프트맥스·교차 엔트로피y^i=ezi∑jezj\hat{y}_i=\dfrac{e^{z_i}}{\sum_j e^{z_j}}, L=−∑yilog⁡y^iL=-\sum y_i\log\hat{y}_inp.exp(z)/np.exp(z).sum()
🎁00-04와 연결: 손실의 기울기∂L∂z=y^−y\dfrac{\partial L}{\partial z}=\hat{y}-ygz = y_hat - y

🗺️ 시각화 ① — 이번 장의 전체 흐름

🗺️ 시각화 ① — 이번 장의 전체 흐름


📍 00-05-A · 1단계 — 확률이란 무엇인가

🔎 ① 핵심 정의 — 무엇인가?\
확률(Probability)은 "어떤 일이 일어날 가능성"을 0과 1 사이의 숫자로 표현한 것입니다. 0은 "절대 일어나지 않는다", 1은 "반드시 일어난다", 0.5는 "반반"입니다.

❓ 왜 필요한가?\
세상의 거의 모든 일은 100% 확실하지 않기 때문입니다. 내일 비가 올지, 이 메일이 스팸인지, 이 사진이 강아지인지는 미리 확정할 수 없습니다. 대신 "가능성이 얼마인지"를 숫자로 다뤄야 합리적 판단이 가능해집니다. AI가 "강아지 86%"라고 말하는 것도 이 때문입니다.

⏰ 언제 사용하는가?\
분류 모델의 출력, 생성 모델의 샘플링, 강화학습의 행동 선택 — AI가 "확신의 정도"를 표현해야 하는 모든 순간입니다.

💜 더 쉽게 이해하기 — 🎱 복권 추첨함

추첨함에 공이 100개 들어 있고 그중 30개가 빨간 공입니다. 눈을 감고 하나를 뽑을 때 빨간 공이 나올 가능성은? "전체 100개 중 빨강 30개"이므로 30%입니다. 🎱

확률 계산은 언제나 이 추첨함을 상상하는 것에서 시작합니다.\
① "전체 공은 몇 개인가?" → 분모\
② "내가 원하는 공은 몇 개인가?" → 분자\
이 두 질문만 정확히 답하면 확률 문제의 90%는 풀립니다. 🔑

🔢 진행 과정 — 확률 계산 4단계

  1. 일어날 수 있는 모든 경우를 나열한다 → 표본공간 Ω\Omega
  2. 내가 관심 있는 결과들을 골라낸다 → 사건 AA
  3. AA의 개수를 Ω\Omega의 개수로 나눈다
  4. 결과를 0~1 사이 숫자 또는 %로 표현한다

🔬 수학공식 ① — 확률의 정의와 세 가지 성질

P(A)=n(A)n(Ω)P(A) = \frac{n(A)}{n(\Omega)}

변수 정의\
• Ω\Omega (오메가) : 표본공간 — 일어날 수 있는 모든 경우의 집합\
• AA : 관심 있는 사건 (표본공간의 부분집합)\
• n(⋅)n(\cdot) : 원소의 개수\
• P(A)P(A) : 사건 AA가 일어날 확률\
\
반드시 성립하는 세 가지 성질(확률의 공리)\
① 범위: 0≤P(A)≤10 \le P(A) \le 1 — 확률은 절대 음수나 1 초과가 될 수 없습니다\
② 전체: P(Ω)=1P(\Omega)=1 — "무언가는 반드시 일어난다"\
③ 여사건: P(Ac)=1−P(A)P(A^c) = 1 - P(A) — "일어나지 않을 확률 = 1 − 일어날 확률"\
\
📌 ③번은 실전에서 매우 유용합니다. "적어도 하나는 나올 확률"처럼 계산이 복잡한 문제는, 반대(하나도 안 나올 확률)를 구해서 1에서 빼는 것이 훨씬 쉽습니다. 🔄

📖 수학적 의미 — 예제로 직접 확인

예제 ① 🟢 주사위 짝수\
• Ω={1,2,3,4,5,6}\Omega=\{1,2,3,4,5,6\} → n(Ω)=6n(\Omega)=6\
• A={2,4,6}A=\{2,4,6\} → n(A)=3n(A)=3\
• P(A)=36=0.5P(A)=\frac{3}{6}=\mathbf{0.5}

예제 ② 🔵 주사위 두 개의 합이 7\
• 표본공간은 (1,1)(1,1)부터 (6,6)(6,6)까지 → n(Ω)=6×6=36n(\Omega)=6\times6=\mathbf{36}\
• 합이 7인 경우: (1,6),(2,5),(3,4),(4,3),(5,2),(6,1)(1,6),(2,5),(3,4),(4,3),(5,2),(6,1) → n(A)=6n(A)=\mathbf{6}\
• P(A)=636=16≈0.167P(A)=\frac{6}{36}=\frac{1}{6}\approx\mathbf{0.167}\
• 📌 합이 7이 가장 흔합니다. 합 2나 12는 방법이 1가지뿐이라 136\frac{1}{36}입니다.

예제 ③ 🔵 여사건 활용 — "주사위 3번 던져 6이 적어도 한 번"\
• 직접 세면 복잡합니다 (1번만, 2번만, 3번 모두… 경우가 많음) 😵\
• 반대를 생각합니다: "6이 한 번도 안 나올 확률" =(56)3=125216=\left(\frac{5}{6}\right)^3 = \frac{125}{216}\
• 따라서 P=1−125216=91216≈0.421P = 1 - \frac{125}{216} = \frac{91}{216} \approx \mathbf{0.421}\
• ⚠️ 흔한 오답: 16×3=0.5\frac{1}{6}\times3=0.5 — 중복을 세어버려 틀립니다!

💡 직관적 의미\
확률 공식은 결국 "전체를 똑같은 크기의 조각으로 나눴을 때, 내가 원하는 조각이 몇 개인가"를 재는 것입니다. 🍕 여기서 "똑같은 크기"가 중요합니다 — 주사위 두 개의 합에서 "합이 7"이 "합이 2"보다 6배 흔한 이유는, 7을 만드는 조각(경우)이 6개나 되기 때문입니다.

📐 시각화 ② — 표본공간과 사건

📐 시각화 ② — 표본공간과 사건

⚙️ 구현 원리 — 이론값과 실험값 두 갈래로 확인하기\
확률은 두 가지 방식으로 구할 수 있고, 코드에서 둘 다 해보는 것이 최고의 학습법입니다.\
① 이론값(해석적) — 공식 n(A)/n(Ω)n(A)/n(\Omega)를 그대로 계산\
② 실험값(시뮬레이션) — 주사위를 실제로 수만 번 던져 비율을 셈\
두 값이 가까워지면 이론이 옳다는 것을 실험으로 검증한 셈입니다. 이는 00-04에서 "공식 미분 vs 수치 미분"을 비교했던 것과 완전히 같은 습관입니다. 🔗

🔗 프레임워크 관점

개념파이썬 표준NumPySciPyPyTorch
무작위 시행random.randint(1,6)rng.integers(1,7,n)—torch.randint
확률분포 객체—직접 계산stats.randint(1,7)torch.distributions
재현성(시드)random.seed(0)default_rng(0)random_state=0torch.manual_seed

💻 코드 구현 — 공식을 그대로 파이썬으로

🐍 Python — 이론값 vs 시뮬레이션, 그리고 대수의 법칙

import numpy as np

def theoretical_prob(sample_space, condition):
    """
    공식 P(A) = n(A) / n(Ω) 를 그대로 계산합니다.

    비유:
    추첨함 안의 공 전체(n(Ω)) 중에서
    원하는 색 공(n(A))이 몇 개인지 세는 것과 같습니다.

    매개변수:
        sample_space: 표본공간 (모든 가능한 결과의 리스트)
        condition   : 원하는 결과인지 판정하는 함수
    """
    favorable = [x for x in sample_space if condition(x)]   # n(A)
    return len(favorable) / len(sample_space)                # n(A)/n(Ω)

def simulate_prob(n_trials, seed=0):
    """주사위를 n_trials 번 던져 '짝수가 나온 비율'을 셉니다."""
    rng = np.random.default_rng(seed)          # 재현 가능한 난수 생성기
    rolls = rng.integers(1, 7, n_trials)      # 1~6 (7은 미포함!)
    return np.mean(rolls % 2 == 0)                # True 비율 = 확률 추정

dice = [1, 2, 3, 4, 5, 6]

# ── 예제 ①: 짝수 ──────────────────────────────────────
print("[짝수]   이론값:", theoretical_prob(dice, lambda x: x % 2 == 0))

# ── 예제 ②: 두 주사위 합이 7 (표본공간 36개) ───────────
two_dice = [(a, b) for a in dice for b in dice]   # 6×6 = 36가지
print("[합=7]   이론값:", round(theoretical_prob(two_dice, lambda p: sum(p) == 7), 4))

# ── 예제 ③: 여사건 — 3번 던져 6이 적어도 한 번 ─────────
p_no_six = (5 / 6) ** 3          # 6이 한 번도 안 나올 확률
print("[6 등장] 이론값:", round(1 - p_no_six, 4), "← 1 - P(여사건)")

# ── 대수의 법칙: 시행 횟수를 늘리면 이론값에 수렴 ───────
print("\n--- 대수의 법칙 (짝수 확률, 이론값 0.5) ---")
for n in [10, 100, 1000, 10000, 100000]:
    est = simulate_prob(n)
    print(f"{n:>7}회 → 추정 {est:.4f}  (오차 {abs(est-0.5):.4f})")

🔗 GitHub에서 코드 보기 — 00_05_probability_bayes.py L12–L52

📘 코드 이해 — 수식과 코드 한 줄씩 대응

수학공식파이썬 코드설명
Ω\Omegasample_space표본공간을 리스트로 표현
n(A)n(A)len([x for x in ... if cond(x)])조건을 만족하는 원소만 세기
n(A)n(Ω)\frac{n(A)}{n(\Omega)}len(favorable)/len(sample_space)공식을 1:1로 옮김
6×6=366\times6=36[(a,b) for a in dice for b in dice]이중 컴프리헨션 = 곱의 법칙
P(Ac)=1−P(A)P(A^c)=1-P(A)1 - (5/6)**3여사건 공식 — 계산이 훨씬 간단
실험 비율np.mean(rolls % 2 == 0)True/False 배열의 평균 = 비율 ⭐

🧩 코드 설명 — Python/NumPy 기초

• 리스트 컴프리헨션 [x for x in A if cond(x)] — "A의 각 원소 중 조건을 만족하는 것만 모아 새 리스트를 만들라"는 뜻입니다. for문 3줄을 1줄로 줄여줍니다.\
\
• 함수를 인자로 넘기기 lambda x: x % 2 == 0 — 이름 없는 한 줄 함수입니다. 이렇게 하면 theoretical_prob 하나로 어떤 조건이든 계산할 수 있어 재사용성이 높아집니다.\
\
• np.mean(불리언 배열) ⭐ — 파이썬은 True를 1, False를 0으로 취급합니다. 그래서 불리언 배열의 평균 = True의 비율 = 확률 추정치가 됩니다. 확률 코딩에서 가장 자주 쓰는 관용구이니 꼭 기억하세요!\
\
• rng.integers(1, 7, n) ⚠️ — 끝값 7은 포함되지 않습니다. 주사위(1~6)를 만들려면 반드시 7을 써야 합니다. 6을 쓰면 1~5만 나오는 흔한 버그입니다.\
\
• np.random.default_rng(seed) — 요즘 NumPy 권장 방식입니다. seed를 고정하면 매번 같은 난수가 나와 결과를 재현할 수 있습니다.

✅🟩 실행 결과

[짝수]   이론값: 0.5
[합=7]   이론값: 0.1667
[6 등장] 이론값: 0.4213 ← 1 - P(여사건)

--- 대수의 법칙 (짝수 확률, 이론값 0.5) ---
     10회 → 추정 0.6000  (오차 0.1000)
    100회 → 추정 0.5000  (오차 0.0000)
   1000회 → 추정 0.5200  (오차 0.0200)
  10000회 → 추정 0.5019  (오차 0.0019)
 100000회 → 추정 0.4976  (오차 0.0024)

🔍 결과 해석: 10회에서는 오차가 0.1이나 되지만, 10,000회에서는 0.0019까지 줄어듭니다. 이것이 대수의 법칙(Law of Large Numbers)입니다. 📌 흥미롭게도 100회에서 오차가 0.0000이 나왔는데, 이는 실력이 아니라 우연입니다 — 적은 횟수에서는 운 좋게 정확할 수도, 크게 빗나갈 수도 있습니다. 오차는 대략 1/n1/\sqrt{n}에 비례해 줄어들어, 정확도를 10배 높이려면 시행을 100배 늘려야 합니다. 🐢

🛠️ 실습 1 🟢

① theoretical_prob로 "3 이상이 나올 확률"을 구하세요. (정답: 4/6 ≈ 0.667)\
② 두 주사위의 합이 2부터 12까지 각각 나올 확률을 모두 구해 표로 출력하세요. (힌트: for s in range(2,13)) 어떤 합이 가장 흔한가요?\
③ "주사위 5번 던져 6이 한 번도 안 나올 확률"을 여사건으로 구하세요. (정답: (5/6)5≈0.402(5/6)^5\approx0.402)

🐞 실수하기 쉬운 부분 & 디버깅 포인트

❌ 실수 ① 시뮬레이션이 이론값과 다르다고 당황\
왜 틀리나? 시행이 10회뿐이면 우연히 0.6, 0.3도 나옵니다. 원인: 버그가 아니라 통계적으로 자연스러운 흔들림입니다. 수정: 시행을 10,000회 이상으로 늘려보세요. 오차가 1/n1/\sqrt{n}로 줄어듭니다.

❌ 실수 ② randint의 끝값\
NumPy rng.integers(1, 6)는 1~5만 만듭니다. 주사위는 (1, 7)이 맞습니다. 반면 파이썬 표준 random.randint(1, 6)은 6을 포함합니다 — 두 라이브러리의 규칙이 달라 가장 헷갈리는 부분입니다! ⚠️ 확인법: print(rolls.min(), rolls.max())

❌ 실수 ③ "적어도 한 번"을 곱셈으로\
16×3=0.5\frac{1}{6}\times3=0.5로 계산하면 틀립니다(정답 0.4213). 원인: 6이 두 번 이상 나오는 경우를 중복해서 세기 때문입니다. 수정: 반드시 여사건 1−(5/6)31-(5/6)^3을 쓰세요.

✅ 검산 습관: 확률을 구했으면 assert 0 <= p <= 1을 넣으세요. 1을 넘으면 분모를 잘못 잡은 것입니다.

📍 00-05-B · 2단계 — 조건부 확률: 정보가 확률을 바꾼다

🔎 ① 핵심 정의 — 무엇인가?\
조건부 확률(Conditional Probability)은 "사건 BB가 이미 일어났다는 사실을 알고 있을 때, 사건 AA가 일어날 확률"입니다. P(A∣B)P(A|B)로 쓰고 "B라는 조건 하에서 A"라고 읽습니다.

❓ 왜 필요한가?\
아무 정보가 없을 때의 확률과 어떤 정보를 알고 난 뒤의 확률이 서로 다르기 때문입니다. 새 정보를 얻으면 우리는 확률을 "다시 계산"해야 합니다. 이 아이디어가 다음 단계 베이즈 정리의 뼈대가 됩니다.

⏰ 언제 사용하는가?\
"이 단어가 들어 있을 때 스팸일 확률", "이 픽셀 패턴일 때 고양이일 확률" — AI의 거의 모든 예측이 사실은 조건부 확률입니다.

💜 더 쉽게 이해하기 — 🧭 내비게이션의 경로 재탐색

출발 전 내비게이션은 "이 길이면 30분"이라 예상합니다. 그런데 달리던 중 "앞에 사고 발생"이라는 새 정보(조건)가 들어오면, 내비게이션은 즉시 소요 시간을 다시 계산합니다. 🚗

핵심은 "고려 대상이 좁아진다"는 것입니다. 사고가 났다는 정보를 알기 전에는 모든 교통 상황이 후보였지만, 알고 난 뒤에는 "사고가 난 상황들"만이 새로운 전체(분모)가 됩니다. 조건부 확률은 이렇게 분모를 갈아끼우는 연산입니다. 🔄

🔢 진행 과정 — 조건부 확률 3단계

  1. 조건이 되는 사건 BB의 경우의 수를 구한다 → 새로운 "전체"(분모)가 된다
  2. 그 안에서 AA도 동시에 만족하는 경우의 수를 구한다 → 새로운 분자
  3. 2번을 1번으로 나눈다 → P(A∣B)P(A|B)

📌 기억법: "세로줄(|) 뒤에 있는 것이 분모"입니다. P(A∣B)P(A|B)면 분모는 반드시 P(B)P(B)입니다. 🔑

🔬 수학공식 ② — 조건부 확률과 곱셈정리

조건부 확률의 정의

P(A∣B)=P(A∩B)P(B)(P(B)>0)P(A|B) = \frac{P(A \cap B)}{P(B)} \qquad (P(B) > 0)

변수 정의\
• P(A∣B)P(A|B) : BB가 일어난 조건에서 AA가 일어날 확률\
• P(A∩B)P(A \cap B) : AA와 BB가 동시에 일어날 확률 (교집합, "그리고")\
• P(B)P(B) : 조건이 되는 사건의 확률 — 새로운 분모\
• 조건: P(B)>0P(B)>0 — 일어나지 않는 일을 조건으로 삼을 수는 없습니다\
\
양변에 P(B)P(B)를 곱하면 → 곱셈정리(chain rule) ⭐

  P(A∩B)=P(A∣B) P(B)=P(B∣A) P(A)  \boxed{\;P(A \cap B) = P(A|B)\,P(B) = P(B|A)\,P(A)\;}

📌 이 식이 3단계 베이즈 정리의 씨앗입니다. 같은 교집합을 두 가지 방향으로 쪼갤 수 있다는 사실 하나에서 베이즈 정리가 곧바로 유도됩니다! 🌱\
\
독립(independence) — 정보가 확률을 바꾸지 않는 특수한 경우

P(A∣B)=P(A)  ⟺  P(A∩B)=P(A)P(B)P(A|B) = P(A) \iff P(A \cap B) = P(A)P(B)

예: 주사위를 두 번 던질 때, 첫 번째 결과는 두 번째에 아무 영향이 없습니다. 🎲🎲

📖 수학적 의미 — 의대 지원자 1000명 예제\
지원자 1000명 중 수능 고득점자 400명, 합격자 200명, 그중 고득점이면서 합격 160명입니다. 이 숫자들을 표(분할표)로 정리하면 관계가 한눈에 보입니다.

합격 ✅불합격 ❌행 합계
고득점 📈160240400
저득점 📉40560600
열 합계2008001000

모든 수를 1000으로 나누면 확률이 됩니다: P(고득점)=0.4P(\text{고득점})=0.4, P(합격)=0.2P(\text{합격})=0.2, P(고득점∩합격)=0.16P(\text{고득점}\cap\text{합격})=0.16.

질문 A — "고득점자 중 합격 비율은?" (분모 = 고득점 400명, 표의 가로줄)

P(합격∣고득점)=P(합격∩고득점)P(고득점)=0.160.4=0.40P(\text{합격}|\text{고득점}) = \frac{P(\text{합격}\cap\text{고득점})}{P(\text{고득점})} = \frac{0.16}{0.4} = \mathbf{0.40}

질문 B — "합격자 중 고득점 비율은?" (분모 = 합격 200명, 표의 세로줄)

P(고득점∣합격)=P(고득점∩합격)P(합격)=0.160.2=0.80P(\text{고득점}|\text{합격}) = \frac{P(\text{고득점}\cap\text{합격})}{P(\text{합격})} = \frac{0.16}{0.2} = \mathbf{0.80}

⚠️ 40%와 80%. 분자는 똑같이 160명인데 답이 두 배 차이납니다.\
분모가 400 vs 200으로 다르기 때문입니다. "고득점자 중 합격 비율"과 "합격자 중 고득점 비율"은 완전히 다른 질문입니다. 이것을 혼동하는 것이 3단계에서 다룰 "병원 검사의 함정"의 정체입니다! 🏥

💡 직관적 의미 — 표에서 방향 읽기\
분할표에서 가로로 나누면 "행 조건", 세로로 나누면 "열 조건"입니다. 조건부 확률을 계산할 때는 언제나 "지금 나는 표의 어느 방향으로 나누고 있는가?"를 스스로에게 물어보세요. 🧭

📌 독립인지 확인해보기: 만약 두 사건이 독립이라면 P(합격∣고득점)=P(합격)=0.2P(\text{합격}|\text{고득점})=P(\text{합격})=0.2여야 합니다. 그런데 실제로는 0.4이므로, 고득점은 합격 확률을 2배로 높이는 유의미한 정보입니다. ✅

📐 시각화 ③ — 벤 다이어그램: 같은 교집합, 다른 분모

📐 시각화 ③ — 벤 다이어그램: 같은 교집합, 다른 분모

⚙️ 구현 원리 — 마스크로 "분모 갈아끼우기"\
코드에서 조건부 확률은 두 가지로 구현할 수 있습니다.\
① 공식 그대로 — 확률값을 넣어 나누기\
② 데이터에서 직접 — 불리언 마스크로 조건을 만족하는 부분집합만 걸러낸 뒤, 그 안에서 다시 비율을 셈\
②번이 훨씬 중요합니다. 실무에서는 확률이 아니라 원본 데이터가 주어지기 때문입니다. 그리고 이 방식은 "분모를 갈아끼운다"는 개념을 코드로 그대로 보여줍니다. 🔍

🔗 프레임워크 관점

개념NumPyPandasSciPy
조건부 필터링arr[mask]df[df["고득점"]]—
분할표직접 계산pd.crosstab(a, b)stats.contingency
독립성 검정——stats.chi2_contingency

🐍 Python — 공식 계산 + 실제 1000명 데이터로 검증

import numpy as np

def conditional_prob(p_joint, p_condition):
    """
    P(A|B) = P(A∩B) / P(B) 를 계산합니다.

    비유:
    조건(B)에 해당하는 사람들만 방에 남기고,
    그 방 안에서만 다시 비율을 재는 것과 같습니다.

    매개변수:
        p_joint    : P(A∩B) — 둘 다 만족할 확률 (분자)
        p_condition: P(B)   — 조건 사건의 확률 (분모)
    """
    if p_condition <= 0:
        raise ValueError("조건 사건의 확률이 0이면 정의되지 않습니다")
    return p_joint / p_condition

# ══════ 방법 1) 공식에 확률을 직접 대입 ══════
p_high  = 400 / 1000          # P(고득점) = 0.4
p_pass  = 200 / 1000          # P(합격)   = 0.2
p_both  = 160 / 1000          # P(고득점 ∩ 합격) = 0.16 (분자는 공통!)

print(f"P(합격|고득점) = {conditional_prob(p_both, p_high):.2f}")   # 분모=0.4
print(f"P(고득점|합격) = {conditional_prob(p_both, p_pass):.2f}")   # 분모=0.2

# ══════ 방법 2) 실제 1000명 데이터에서 마스크로 계산 ══════
# 분할표대로 1000명을 만듭니다: [고득점여부, 합격여부]
high  = np.array([True]*400 + [False]*600)   # 고득점 400명
# 고득점 400명 중 160명 합격 / 저득점 600명 중 40명 합격
passed = np.array([True]*160 + [False]*240
                  + [True]*40  + [False]*560)

# ★ 핵심: high 마스크로 '고득점자만' 골라낸 뒤 그 안에서 평균 = 조건부 확률
print(f"\n[데이터] P(합격|고득점) = {passed[high].mean():.2f}",
      f"(분모 {high.sum()}명)")
print(f"[데이터] P(고득점|합격) = {high[passed].mean():.2f}",
      f"(분모 {passed.sum()}명)")

# ══════ 독립인지 확인 ══════
# 독립이라면 P(A∩B) == P(A)×P(B) 여야 합니다
print(f"\n실제 P(A∩B)   = {p_both:.3f}")
print(f"독립이라면     = {p_high * p_pass:.3f}")
print("→ 다르므로 두 사건은 독립이 아니다 (고득점은 유의미한 정보!)")

🔗 GitHub에서 코드 보기 — 00_05_probability_bayes.py L56–L99

📘 코드 이해 — 수식과 코드 대응

수학공식코드설명
P(A∩B)P(A\cap B)p_both분자 — 두 계산에서 공통
P(B)P(B)p_high 또는 p_pass분모 — 여기만 바뀐다 ⭐
"조건으로 좁히기"passed[high]고득점자 400명만 남긴 배열
P(A∣B)P(A\vert B)passed[high].mean()좁힌 집단 안에서의 비율
P(A)P(B)P(A)P(B)p_high * p_pass독립 가정 시의 교집합 (비교용)

🧩 코드 설명 — NumPy 불리언 인덱싱

• passed[high] ⭐ — high가 True인 위치의 원소만 뽑아 새 배열을 만듭니다. 이것이 바로 "분모를 갈아끼우는" 연산입니다. 400명짜리 배열이 되고, 그 평균이 곧 P(합격∣고득점)P(\text{합격}|\text{고득점})입니다.\
\
• high[passed] — 순서만 바꿨을 뿐인데 완전히 다른 질문이 됩니다. 합격자 200명만 남기고 그중 고득점 비율을 재는 것이죠. 인덱싱의 방향이 조건의 방향입니다. 🔄\
\
• [True]*400 + [False]*600 — 리스트에 *는 반복, +는 이어붙이기입니다. 분할표를 그대로 데이터로 만드는 간편한 방법입니다.\
\
• .sum() on 불리언 — True가 1이므로 합계가 곧 개수입니다. high.sum()은 400을 돌려줍니다.\
\
• raise ValueError(...) — 분모가 0이면 명확한 메시지와 함께 프로그램을 멈춥니다. 조용히 inf가 나오는 것보다 훨씬 안전한 습관입니다. 🛡️

✅🟩 실행 결과

P(합격|고득점) = 0.40
P(고득점|합격) = 0.80

[데이터] P(합격|고득점) = 0.40 (분모 400명)
[데이터] P(고득점|합격) = 0.80 (분모 200명)

실제 P(A∩B)   = 0.160
독립이라면     = 0.080
→ 다르므로 두 사건은 독립이 아니다 (고득점은 유의미한 정보!)

✅ 공식 계산과 데이터 계산이 정확히 일치합니다. 그리고 실제 교집합(0.160)이 독립 가정값(0.080)의 정확히 2배라는 사실은, "고득점이라는 정보가 합격 확률을 2배로 끌어올린다"는 뜻입니다. 이 "2배"라는 비율이 3단계에서 우도비(likelihood ratio)라는 이름으로 다시 등장합니다! 🔗

🛠️ 실습 2 🟢🔵

① 어느 반 40명 중 숙제를 한 학생 30명, 그중 시험 합격 24명일 때 P(합격∣숙제함)P(\text{합격}|\text{숙제함})을 구하세요. (정답: 24/30 = 0.8)\
② 위 코드에서 passed[high]를 passed[~high]로 바꿔 저득점자의 합격률을 구하세요. (~는 부정 연산자, 정답: 40/600 ≈ 0.067)\
③ 주사위 두 개를 던져 "합이 7일 때 첫 주사위가 1일 확률"을 코드로 구하세요. (정답: 1/6)

🐞 실수하기 쉬운 부분 & 디버깅 포인트

❌ 실수 ① P(A∣B)P(A|B)와 P(B∣A)P(B|A) 혼동 (가장 흔함!)\
왜 틀리나? 두 표현이 같은 글자 A, B를 쓰기 때문에 분모가 바뀐다는 사실을 놓칩니다. 수정: "세로줄 뒤가 분모"를 주문처럼 외우세요. 확신이 안 서면 분할표를 그려서 가로로 나눌지 세로로 나눌지 눈으로 확인하세요.

❌ 실수 ② 교집합과 조건부를 혼동\
P(A∩B)=0.16P(A\cap B)=0.16과 P(A∣B)=0.8P(A|B)=0.8은 전혀 다른 값입니다. "둘 다 일어날 확률"과 "B가 일어난 상황에서 A일 확률"을 구분하세요. 확인법: 조건부 확률은 항상 교집합보다 크거나 같습니다(분모가 1 이하이므로).

❌ 실수 ③ 분모가 0\
ZeroDivisionError 또는 nan이 나옵니다. 수정: 위 코드처럼 if p_condition <= 0: raise ...로 미리 막으세요.

❌ 실수 ④ 마스크 길이 불일치\
passed[high]에서 두 배열의 길이가 다르면 IndexError가 납니다. 확인법: print(len(high), len(passed))로 둘 다 1000인지 확인하세요.

📍 00-05-C · 3단계 — 베이즈 정리: 병원 검사의 함정

🔎 ① 핵심 정의 — 무엇인가?\
베이즈 정리(Bayes' Theorem)는 "결과(증거)를 관찰한 뒤, 원인이 무엇이었을지를 역으로 계산하는 공식"입니다. 즉 P(원인∣결과)P(\text{원인}|\text{결과})를 알고 싶은데 우리가 아는 것은 P(결과∣원인)P(\text{결과}|\text{원인})뿐일 때 씁니다.

❓ 왜 필요한가?\
현실에서 측정 가능한 것과 알고 싶은 것의 방향이 정반대이기 때문입니다.\
• 우리가 아는 것: "병에 걸린 사람이 양성이 나올 확률 99%" ← 검사 스펙, 실험으로 측정 가능\
• 우리가 알고 싶은 것: "양성이 나온 내가 실제로 병에 걸렸을 확률" ← 진짜 중요한 것\
2단계에서 배웠듯 이 둘은 분모가 다른 완전히 다른 값입니다.

⏰ 언제 사용하는가?\
스팸 필터(단어를 보고 스팸 판정), 의료 진단, 스팸/사기 탐지, 로봇의 위치 추정, A/B 테스트 — 증거를 보고 원인을 추정하는 모든 곳입니다.

💜 더 쉽게 이해하기 — 🕵️ 탐정의 추리

명탐정은 두 가지를 함께 고려합니다.

① 증거의 결정력(우도) — "범인이라면 이런 흔적을 남길 확률이 높다"\
② 배경 지식(사전 확률) — "애초에 이 사람이 범인일 가능성이 얼마나 되나?"

🔍 결정적 증거가 나와도, 애초에 용의자가 10만 명이라면 결론은 신중해야 합니다. 반대로 용의자가 2명뿐이었다면 같은 증거가 훨씬 강력합니다.

이것이 베이즈 정리의 전부입니다: "원래 믿음(사전) × 새 증거의 힘(우도) = 갱신된 믿음(사후)" ⚖️

🔢 진행 과정 — 인원수로 먼저 이해하기 (공식 없이!)\
공식을 보기 전에 10만 명을 실제로 검사한다고 상상해봅시다. 조건은 발병률 0.1%, 검사 정확도(민감도) 99%, 오탐률 5%입니다.

  1. 인원을 나눈다 — 실제 환자: 100,000×0.001=100100{,}000\times0.001=\mathbf{100}명 / 비환자: 99,900\mathbf{99{,}900}명
  2. 환자 중 양성 — 100×0.99=99100\times0.99=\mathbf{99}명 (1명은 놓침 😢)
  3. 비환자 중 양성(오탐) — 99,900×0.05=4,99599{,}900\times0.05=\mathbf{4{,}995}명 😱
  4. 양성 판정 총원 — 99+4,995=5,09499+4{,}995=\mathbf{5{,}094}명
  5. 그중 진짜 환자 비율 — 995,094≈0.0194=1.94%\dfrac{99}{5{,}094}\approx\mathbf{0.0194}=\mathbf{1.94\%}

😱 충격적인 결론: 정확도 99%짜리 검사에서 양성이 나와도, 실제 환자일 확률은 겨우 1.94%입니다.\
\
왜? 진짜 환자는 99명인데 가짜 양성이 4,995명입니다. 진짜가 가짜에 50배 넘게 파묻혀 버린 것이죠. 병이 희귀할수록 비환자 집단이 압도적으로 크기 때문에, 5%라는 "작아 보이는" 오탐률도 절대 인원수로는 거대해집니다. 🌊

🗺️ 시각화 ④ — 10만 명이 갈라지는 과정

🗺️ 시각화 ④ — 10만 명이 갈라지는 과정

📐 시각화 ⑤ — 양성 판정자 5,094명의 구성 (면적 비교)

📐 시각화 ⑤ — 양성 판정자 5,094명의 구성 (면적 비교)

🔬 수학공식 ③ — 베이즈 정리 직접 유도하기 ⭐ (이 장의 하이라이트)

놀랍게도 베이즈 정리는 2단계의 곱셈정리 한 줄에서 곧바로 나옵니다.\
\
① 같은 교집합을 두 방향으로 쪼갠다 (2단계 곱셈정리)

P(H∩E)=P(E∣H) P(H)그리고P(H∩E)=P(H∣E) P(E)P(H \cap E) = P(E|H)\,P(H) \qquad\text{그리고}\qquad P(H \cap E) = P(H|E)\,P(E)

② 왼쪽이 같으므로 오른쪽끼리 같다

P(H∣E) P(E)=P(E∣H) P(H)P(H|E)\,P(E) = P(E|H)\,P(H)

③ 양변을 P(E)P(E)로 나눈다 → 베이즈 정리 완성!

  P(H∣E)=P(E∣H) P(H)P(E)  \boxed{\;P(H|E) = \frac{P(E|H)\,P(H)}{P(E)}\;}

변수 정의\
• HH (Hypothesis) : 가설 = 원인 (예: "병에 걸렸다")\
• EE (Evidence) : 증거 = 관찰된 결과 (예: "검사 양성")\
• P(H)P(H) : 사전 확률(prior) — 증거를 보기 전의 믿음 = 발병률 0.001\
• P(E∣H)P(E|H) : 우도(likelihood) — 원인이 참일 때 증거가 나올 확률 = 민감도 0.99\
• P(E)P(E) : 증거 확률(evidence) — 증거가 나올 전체 확률 (정규화 상수)\
• P(H∣E)P(H|E) : 사후 확률(posterior) — 증거를 본 후 갱신된 믿음 ← 우리가 원하는 것\
\
④ 분모 P(E)P(E)는 어떻게 구하나? — 전확률 정리\
증거(양성)가 나오는 모든 경로를 더합니다. 경로는 두 개뿐입니다.

P(E)=P(E∣H)P(H)⏟환자이면서 양성+P(E∣Hc)P(Hc)⏟비환자이면서 양성(오탐)P(E) = \underbrace{P(E|H)P(H)}_{\text{환자이면서 양성}} + \underbrace{P(E|H^c)P(H^c)}_{\text{비환자이면서 양성(오탐)}}

📖 수학적 의미 — 공식에 숫자를 넣어보기

P(E)=0.99×0.001+0.05×0.999=0.00099+0.04995=0.05094P(E) = 0.99 \times 0.001 + 0.05 \times 0.999 = 0.00099 + 0.04995 = 0.05094
P(H∣E)=0.99×0.0010.05094=0.000990.05094≈0.01943=1.94%P(H|E) = \frac{0.99 \times 0.001}{0.05094} = \frac{0.00099}{0.05094} \approx \mathbf{0.01943} = \mathbf{1.94\%}

✅ 앞서 인원수로 센 1.94%와 완벽히 일치합니다! 사실 분자 0.000990.00099는 10만 명 중 99명, 분모 0.050940.05094는 5,094명을 각각 10만으로 나눈 값입니다. 공식은 인원수 계산을 확률로 표현한 것일 뿐입니다. 🔗

💡 직관적 의미 — 분자와 분모가 말하는 것\
• 분자는 "내가 원하는 경로 하나"(환자→양성)입니다.\
• 분모는 "증거가 나올 수 있는 모든 경로의 합"입니다.\
즉 베이즈 정리는 결국 "전체 양성 중 내가 원하는 양성의 비율"이라는, 1단계의 n(A)/n(Ω)n(A)/n(\Omega) 그 자체입니다. 표본공간이 "전체 인구"에서 "양성 판정자"로 좁혀졌을 뿐입니다. 🎯

🔬 수학공식 ④ — 오즈(odds) 형태: 암산으로 베이즈 하기 🔴 심화

베이즈 정리에는 훨씬 외우기 쉬운 형태가 있습니다. 분모를 없애는 방법입니다.\
\
① 가설이 참일 때와 거짓일 때를 각각 쓴다

P(H∣E)=P(E∣H)P(H)P(E),P(Hc∣E)=P(E∣Hc)P(Hc)P(E)P(H|E) = \frac{P(E|H)P(H)}{P(E)}, \qquad P(H^c|E) = \frac{P(E|H^c)P(H^c)}{P(E)}

② 두 식을 나눈다 — 공통 분모 P(E)P(E)가 깔끔하게 약분됩니다! ✨

P(H∣E)P(Hc∣E)=P(E∣H)P(E∣Hc)×P(H)P(Hc)\frac{P(H|E)}{P(H^c|E)} = \frac{P(E|H)}{P(E|H^c)} \times \frac{P(H)}{P(H^c)}

③ 말로 옮기면

  사후 오즈=우도비×사전 오즈  \boxed{\;\text{사후 오즈} = \text{우도비} \times \text{사전 오즈}\;}

변수 정의\
• 오즈(odds) : "일어날 확률 ÷ 안 일어날 확률". 확률 0.5 → 오즈 1(1:1), 확률 0.9 → 오즈 9(9:1)\
• 우도비(LR) =P(E∣H)P(E∣Hc)=\dfrac{P(E|H)}{P(E|H^c)} : 증거의 힘 — 이 증거가 가설을 몇 배 그럴듯하게 만드는가\
\
④ 우리 예제로 암산해보기\
• 사전 오즈 =0.0010.999≈1999=\dfrac{0.001}{0.999}\approx\dfrac{1}{999} (환자 1명당 비환자 999명)\
• 우도비 =0.990.05=19.8=\dfrac{0.99}{0.05}=\mathbf{19.8} (검사는 믿음을 약 20배 강화한다)\
• 사후 오즈 =1999×19.8≈19.8999≈0.0198=\dfrac{1}{999}\times19.8\approx\dfrac{19.8}{999}\approx0.0198\
• 확률로 환산 =0.01981+0.0198≈0.0194=\dfrac{0.0198}{1+0.0198}\approx\mathbf{0.0194} ✅\
\
📌 이 형태의 위력: "1:999에서 출발해 증거가 20배 밀어줬으니 약 20:999 ≈ 2%" — 계산기 없이 암산이 됩니다! 🧠 또한 증거가 여러 개면 우도비를 계속 곱하기만 하면 됩니다. 이것이 스팸 필터가 단어를 하나씩 보며 점수를 누적하는 원리입니다. 📧

⚙️ 구현 원리 — 함수 하나로 시나리오 전체 탐색하기\
베이즈 정리를 사전확률·우도·오탐률 세 인자를 받는 함수로 만들면, 값을 바꿔가며 "무엇이 결과를 좌우하는가"를 실험할 수 있습니다. 이것이 공식을 그냥 계산하는 것과 코드로 만드는 것의 결정적 차이입니다. 🔬

🔗 프레임워크 관점

개념NumPyscikit-learn확률 프로그래밍
베이즈 정리 계산수식 직접 구현——
나이브 베이즈 분류직접 구현MultinomialNB()—
베이즈 추론(분포 갱신)직접 구현—PyMC, Stan, NumPyro
평가 지표직접 구현precision_score ⭐—

📌 중요: 우리가 구한 사후 확률 1.94%는 머신러닝에서 정밀도(precision)와 정확히 같은 개념입니다. sklearn.metrics.precision_score가 재는 것이 바로 이것입니다! 🔗

🐍 Python — 베이즈 정리 + 인원수 검증 + 시나리오 탐색

def bayes_posterior(prior, likelihood, false_positive_rate):
    """
    P(H|E) = P(E|H)·P(H) / P(E) 를 계산합니다.

    비유:
    탐정이 '원래 의심 정도(prior)'에 '증거의 힘(likelihood)'을 곱한 뒤,
    '증거가 나올 모든 경우(evidence)'로 나눠 갱신된 의심 정도를 구합니다.

    매개변수:
        prior              : P(H)     — 사전 확률(발병률)
        likelihood         : P(E|H)   — 우도(민감도, 검사 정확도)
        false_positive_rate: P(E|H^c) — 오탐률(위양성률)
    반환:
        P(H|E) — 사후 확률
    """
    # 분자: '원인이 참이면서 증거가 나온' 경로 하나
    numerator = likelihood * prior

    # 분모: 전확률 정리 — 증거가 나올 모든 경로의 합
    evidence = numerator + false_positive_rate * (1 - prior)

    return numerator / evidence

def bayes_by_odds(prior, likelihood, false_positive_rate):
    """오즈 형태로 같은 값을 계산합니다: 사후오즈 = 사전오즈 × 우도비"""
    prior_odds = prior / (1 - prior)                    # P(H)/P(H^c)
    likelihood_ratio = likelihood / false_positive_rate    # LR
    posterior_odds = prior_odds * likelihood_ratio      # 곱하기만!
    return posterior_odds / (1 + posterior_odds)       # 오즈 → 확률

# ══════ ① 기본 시나리오 ══════
p = bayes_posterior(prior=0.001, likelihood=0.99, false_positive_rate=0.05)
print(f"양성 판정 후 실제 발병 확률: {p:.4f} ({p:.2%})")
print(f"오즈 형태로 계산해도 동일 : {bayes_by_odds(0.001, 0.99, 0.05):.4f}")

# ══════ ② 인원수로 검증 (공식이 맞는지 확인) ══════
total = 100_000
sick = total * 0.001                    # 환자 100명
healthy = total - sick                    # 비환자 99,900명
true_pos = sick * 0.99                 # 진짜 양성 99명
false_pos = healthy * 0.05            # 가짜 양성 4,995명
print(f"\n[인원수] 진짜양성 {true_pos:.0f}명 / 전체양성 {true_pos+false_pos:.0f}명"
      f" = {true_pos/(true_pos+false_pos):.4f}")

# ══════ ③ 발병률을 바꿔가며 탐색 ══════
print("\n--- 발병률의 영향 (정확도 99%, 오탐 5% 고정) ---")
for rate in [0.001, 0.01, 0.1, 0.5]:
    print(f"  발병률 {rate:>6.1%} → 사후 확률 {bayes_posterior(rate,0.99,0.05):>6.1%}")

# ══════ ④ 오탐률을 바꿔가며 탐색 (더 중요!) ══════
print("\n--- 오탐률의 영향 (발병률 0.1%, 정확도 99% 고정) ---")
for fpr in [0.05, 0.01, 0.001, 0.0001]:
    print(f"  오탐률 {fpr:>7.2%} → 사후 확률 {bayes_posterior(0.001,0.99,fpr):>6.2%}")

# ══════ ⑤ 재검사 — 사후 확률을 다음 사전 확률로! ══════
print("\n--- 같은 검사를 반복하면? (사후 → 다음 사전) ---")
current = 0.001
for n in range(1, 4):
    current = bayes_posterior(current, 0.99, 0.05)   # ★ 갱신된 값을 다시 입력
    print(f"  {n}차 양성 후 → {current:.4f} ({current:.2%})")

🔗 GitHub에서 코드 보기 — 00_05_probability_bayes.py L103–L162

📘 코드 이해 — 수식이 코드의 어느 줄인가

수학공식코드설명
P(E∣H)P(H)P(E\vert H)P(H)numerator = likelihood * prior분자 — 원하는 경로 하나
P(E∣Hc)P(Hc)P(E\vert H^c)P(H^c)fpr * (1 - prior)오탐 경로 — 1-prior가 P(Hc)P(H^c)
P(E)P(E) (전확률)evidence = numerator + ...분모 — 모든 경로의 합
P(H)P(Hc)\frac{P(H)}{P(H^c)}prior/(1-prior)사전 오즈
P(E∣H)P(E∣Hc)\frac{P(E\vert H)}{P(E\vert H^c)}likelihood/fpr우도비 — 증거의 힘
오즈 → 확률o/(1+o)p=odds1+oddsp=\frac{\text{odds}}{1+\text{odds}}
사후 → 새 사전current = bayes(current, ...)⭐ 반복 갱신의 핵심

🧩 코드 설명 — Python 기초

• 키워드 인자 bayes_posterior(prior=0.001, likelihood=0.99, ...) — 값 세 개가 모두 0~1 사이 소수라서 순서를 헷갈리기 매우 쉽습니다. 이름을 붙여 넘기면 실수를 원천 차단할 수 있습니다. 🛡️\
\
• 서식 {p:.2%} — 소수를 백분율로 자동 변환해 출력합니다. 0.0194 → 1.94%. *100을 직접 곱할 필요가 없습니다.\
\
• 정렬 {rate:>6.1%} — >는 오른쪽 정렬, 6은 너비입니다. 반복문 출력이 표처럼 줄 맞춰져 읽기 좋아집니다.\
\
• 숫자 구분자 100_000 — 파이썬은 큰 숫자에 밑줄을 넣어 읽기 쉽게 쓸 수 있습니다. 100000과 완전히 같은 값입니다.\
\
• current = bayes_posterior(current, ...) ⭐ — 이 장에서 가장 중요한 한 줄입니다. 이전 결과를 다시 입력으로 넣는 반복 갱신이며, 로봇의 위치 추정이나 온라인 학습이 이 구조로 동작합니다.

✅🟩 실행 결과

양성 판정 후 실제 발병 확률: 0.0194 (1.94%)
오즈 형태로 계산해도 동일 : 0.0194

[인원수] 진짜양성 99명 / 전체양성 5094명 = 0.0194

--- 발병률의 영향 (정확도 99%, 오탐 5% 고정) ---
  발병률   0.1% → 사후 확률   1.9%
  발병률   1.0% → 사후 확률  16.7%
  발병률  10.0% → 사후 확률  68.8%
  발병률  50.0% → 사후 확률  95.2%

--- 오탐률의 영향 (발병률 0.1%, 정확도 99% 고정) ---
  오탐률   5.00% → 사후 확률  1.94%
  오탐률   1.00% → 사후 확률  9.02%
  오탐률   0.10% → 사후 확률 49.77%
  오탐률   0.01% → 사후 확률 90.83%

--- 같은 검사를 반복하면? (사후 → 다음 사전) ---
  1차 양성 후 → 0.0194 (1.94%)
  2차 양성 후 → 0.2818 (28.18%)
  3차 양성 후 → 0.8860 (88.60%)

🔍 결과 해석 — 세 가지 큰 교훈

① 세 가지 계산법이 모두 일치 — 공식(0.0194), 오즈 형태(0.0194), 인원수(99/5094=0.0194). 수학이 맞다는 삼중 검증입니다. ✅

② 사전 확률(발병률)이 결과를 지배한다 — 검사 성능은 전혀 바꾸지 않았는데 발병률만 0.1%→50%로 올리자 사후 확률이 1.9%→95.2%로 50배 뛰었습니다. 그래서 의사는 증상이 있는 사람에게만 검사를 권합니다 — 증상 자체가 사전 확률을 끌어올리기 때문입니다! 🏥

③ 희귀병에서는 "정확도"보다 "오탐률"이 훨씬 중요하다 ⭐ — 오탐률만 5%→0.01%로 낮추자 사후 확률이 1.94%→90.83%로 폭등했습니다. 민감도 99%는 그대로인데도 말이죠. 희귀한 것을 찾을 때는 "놓치지 않는 능력"보다 "헛짚지 않는 능력"이 결정적입니다. 스팸 필터가 "정상 메일을 스팸으로 분류하지 않는 것"에 목숨 거는 이유입니다. 📧

④ 재검사의 위력 🔁 — 1.94% → 28.18% → 88.60%. 1차 양성이 사전 확률을 0.1%에서 1.94%로 약 20배 끌어올렸고(우도비가 19.8이므로!), 그 상태에서 2차 양성이 또 20배를 밀어줍니다. 이것이 실제 의료 현장에서 정밀 재검사를 하는 수학적 근거입니다. 🏥

🛠️ 실습 3 🔵🔴

① 오탐률을 5%→1%로 낮췄을 때 사후 확률이 몇 배 올라가는지 계산하세요. (정답: 1.94%→9.02%, 약 4.6배)\
② 음성이 나왔을 때 실제로 건강할 확률(음성 예측도)을 구하는 함수를 작성하세요. (힌트: 우도가 P(음성∣건강)=1−fprP(\text{음성}|\text{건강})=1-\text{fpr})\
③ 재검사 코드를 5차까지 늘려보고, 확률이 1에 수렴하는 모습을 관찰하세요.\
④ 🚀 스팸 필터 만들기: 단어 3개의 우도비가 각각 10, 5, 0.2일 때, 사전 오즈 1:9에서 시작해 사후 확률을 구하세요. (힌트: 오즈에 우도비를 모두 곱하기)

🐞 실수하기 쉬운 부분 & 디버깅 포인트

❌ 실수 ① "정확도 99%니까 양성이면 99% 확률로 환자" ⭐ 가장 위험한 오해\
왜 틀리나? P(양성∣병)P(\text{양성}|\text{병})(우도)과 P(병∣양성)P(\text{병}|\text{양성})(사후)을 혼동한 것입니다. 2단계에서 배운 "방향이 다르면 값도 다르다"를 놓친 경우죠. 수정: 반드시 사전 확률을 곱하세요. 우도만 보고 판단하면 안 됩니다.

❌ 실수 ② 분모에 오탐 경로를 빠뜨림\
evidence = likelihood * prior만 쓰면 분자와 분모가 같아져 사후 확률이 항상 1이 나옵니다. 확인법: 결과가 1.0이면 100% 이 버그입니다. print(evidence)로 분모를 찍어보세요.

❌ 실수 ③ P(Hc)P(H^c)를 1로 착각\
비환자 비율은 1 - prior(0.999)이지 1이 아닙니다. 발병률이 낮으면 차이가 작아 버그를 발견하기 어렵습니다. 발병률 50%로 테스트해보면 즉시 드러납니다.

❌ 실수 ④ 인자 순서 뒤바꿈\
bayes_posterior(0.99, 0.001, 0.05)처럼 사전확률과 우도를 바꿔 넣어도 에러 없이 그럴듯한 숫자가 나옵니다. 수정: 항상 키워드 인자를 쓰세요.

✅ 검산 습관: ① assert 0 <= p <= 1 ② 인원수로 다시 세보기 ③ 오즈 형태로 교차검증 — 세 방법이 일치하면 안심해도 됩니다. 🛡️


💻 GitHub 실습 — 코드로 직접 확인하기

📂 이 장의 코드 폴더: book/00-math-overview-for-beginners/00-05-probability-bayes

책에 실린 코드를 장(절)별로 모아 둔 폴더입니다. 아래 파일을 열어 보거나, Colab에서 바로 실행해 보세요.

파일용도링크
00_05_probability_bayes.py책의 코드 블록 6개를 순서대로 모은 실행 스크립트 (# %% 셀 구분)코드 보기
00_05_probability_bayes.ipynb같은 코드를 블록당 셀 1개로 담은 주피터 노트북보기 · Colab
README.md코드 블록 목록 · 실행 방법 · 검증 결과보기
🎬 medium-series/07-1-probability-bayes/Medium 시리즈 예제 — 같은 주제를 블로그용으로 다듬은 실습폴더 · Colab

🧪 저장소 검증 상태: ✅ 실행 OK

🔑 핵심 코드 1 — 베이즈 정리 bayes_posterior와 오즈 형태

📄 00_05_probability_bayes.py L103–L136 · Block 3 (📍 00-05-C · 3단계 — 베이즈 정리: 병원 검사의 함정)

def bayes_posterior(prior, likelihood, false_positive_rate):
    """
    P(H|E) = P(E|H)·P(H) / P(E) 를 계산합니다.

    비유:
    탐정이 '원래 의심 정도(prior)'에 '증거의 힘(likelihood)'을 곱한 뒤,
    '증거가 나올 모든 경우(evidence)'로 나눠 갱신된 의심 정도를 구합니다.

    매개변수:
        prior              : P(H)     — 사전 확률(발병률)
        likelihood         : P(E|H)   — 우도(민감도, 검사 정확도)
        false_positive_rate: P(E|H^c) — 오탐률(위양성률)
    반환:
        P(H|E) — 사후 확률
    """
    # 분자: '원인이 참이면서 증거가 나온' 경로 하나
    numerator = likelihood * prior

    # 분모: 전확률 정리 — 증거가 나올 모든 경로의 합
    evidence = numerator + false_positive_rate * (1 - prior)

    return numerator / evidence

def bayes_by_odds(prior, likelihood, false_positive_rate):
    """오즈 형태로 같은 값을 계산합니다: 사후오즈 = 사전오즈 × 우도비"""
    prior_odds = prior / (1 - prior)                    # P(H)/P(H^c)
    likelihood_ratio = likelihood / false_positive_rate    # LR
    posterior_odds = prior_odds * likelihood_ratio      # 곱하기만!
    return posterior_odds / (1 + posterior_odds)       # 오즈 → 확률

# ══════ ① 기본 시나리오 ══════
p = bayes_posterior(prior=0.001, likelihood=0.99, false_positive_rate=0.05)
print(f"양성 판정 후 실제 발병 확률: {p:.4f} ({p:.2%})")
print(f"오즈 형태로 계산해도 동일 : {bayes_by_odds(0.001, 0.99, 0.05):.4f}")

▶ 실행 결과 — 위 코드를 실행한 출력 (직접 실행: Windows · Python 3.11.2 · numpy 2.4.6 · scipy 1.17.1, 앞 블록부터 순서대로 실행)

양성 판정 후 실제 발병 확률: 0.0194 (1.94%)
오즈 형태로 계산해도 동일 : 0.0194

🔑 핵심 코드 2 — 재검사 — 사후 확률을 다음 사전 확률로

📄 00_05_probability_bayes.py L157–L162 · Block 3 (📍 00-05-C · 3단계 — 베이즈 정리: 병원 검사의 함정)

# ══════ ⑤ 재검사 — 사후 확률을 다음 사전 확률로! ══════
print("\n--- 같은 검사를 반복하면? (사후 → 다음 사전) ---")
current = 0.001
for n in range(1, 4):
    current = bayes_posterior(current, 0.99, 0.05)   # ★ 갱신된 값을 다시 입력
    print(f"  {n}차 양성 후 → {current:.4f} ({current:.2%})")

▶ 실행 결과 — 위 코드를 실행한 출력 (직접 실행: Windows · Python 3.11.2 · numpy 2.4.6 · scipy 1.17.1, 앞 블록부터 순서대로 실행)


--- 같은 검사를 반복하면? (사후 → 다음 사전) ---
  1차 양성 후 → 0.0194 (1.94%)
  2차 양성 후 → 0.2818 (28.18%)
  3차 양성 후 → 0.8860 (88.60%)

☁️ Colab에서 바로 실행

👉 Open in Colab — 00_05_probability_bayes.ipynb · 설치 없이 브라우저에서 셀을 위에서부터 순서대로 실행하세요.

🖥️ 내 컴퓨터에서 실행하기

git clone https://github.com/otoloism/Zero_to_ML_Master.git
cd Zero_to_ML_Master
python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install -r book/requirements.txt   # 책 전체용 (PyTorch 등 포함, 용량 큼)
# 이 장만 실행한다면 이것만으로 충분합니다: pip install numpy scipy
cd book/00-math-overview-for-beginners/00-05-probability-bayes
python 00_05_probability_bayes.py

⭐ GitHub Star / Fork

⭐ 코드가 도움이 되셨다면 GitHub 저장소에 Star를 눌러 주세요. 새 장의 코드가 올라올 때 가장 먼저 알 수 있습니다.
🍴 직접 고쳐 보며 실험하고 싶다면 Fork 해서 내 저장소에서 마음껏 바꿔 보세요. 오류 제보·개선 제안은 Issues로 환영합니다!


🔮 다음 글 예고

다음 글은 00-05 확률·베이즈 정리 (2부) — 정규분포·소프트맥스·교차 엔트로피 🔔 입니다. 자연계의 종 모양 곡선인 정규분포와 68-95-99.7 규칙, 점수를 확률로 바꾸는 소프트맥스, 그리고 분류기를 채점하는 교차 엔트로피와 그 놀랍도록 단순한 기울기 y^−y\hat{y}-y 까지, "AI는 확률로 대답한다"는 말의 의미를 확인합니다.

⬅️ 이전 글: #06 경사하강법 완전 정복 — 학습률·발산 조건·선형회귀·자동미분 (딥러닝 수학, 2부) \
➡️ 다음 글: #08 정규분포·소프트맥스·교차 엔트로피 — AI는 확률로 대답한다 (2부)


📚 더 깊이 공부하고 싶다면

이 시리즈는 수학을 놓았던 비전공자가 AI를 직접 구현하기까지의 공부 기록을 그대로 옮긴 연재입니다. 원문 책, 코드, 개념 사전을 함께 보면 훨씬 빨리 늡니다. 🚀

바로가기이런 분께
📘 책 (위키독스, 무료)Zero to 머신러닝 딥러닝 Master5권 · 20개 장 · 163개 절 전체를 순서대로 읽고 싶은 분
📄 이 글의 원문00-05 확률·베이즈 정리 — 병원 검사의 함정 (1부)연습문제·해설까지 전부 보고 싶은 분
💻 실습 코드 (GitHub)otoloism/Zero_to_ML_Master.py·.ipynb를 Colab에서 바로 돌려보고 싶은 분
🏠 머신러닝 백과사전mldict.net본문 속 핵심 개념을 카드로 다시 확인하고 싶은 분
✍️ 기술 블로그wikidocs.net/blog/@mldict새 글 알림을 가장 먼저 받고 싶은 분

⭐ 도움이 되셨다면 velog 팔로우와 GitHub Star를 눌러 주세요. 다음 글을 쓰는 가장 큰 힘이 됩니다. 궁금한 점은 댓글로 편하게 남겨 주세요! 🙌

profile
자유주의자

0개의 댓글