컴퓨터공학 300 주제 시리즈의 013번째 글이다. 전체 지도는 여기.

한 줄 요약

조건부 확률은 “B 가 일어났다는 정보를 알 때 A 의 확률” 이고, 베이즈 정리는 그 방향을 뒤집어 “증거를 봤을 때 원인의 확률” 을 계산하게 해 준다. 기저율을 무시하면 정확도 99% 검사의 양성 결과도 대부분 오경보일 수 있다.

왜 필요한가

알림 시스템이 “이상 징후” 를 보냈을 때 실제 장애일 확률은 얼마인가? 스팸 필터가 어떤 단어를 봤을 때 그 메일이 스팸일 확률은? 테스트가 실패했을 때 진짜 버그일 확률은? 모두 조건부 확률 문제이고, 답은 직관과 크게 다를 때가 많다.

확률은 무작위 알고리즘(퀵정렬의 피벗, 해시 함수, 블룸 필터), 분산 시스템의 장애 모델, 머신러닝 전반의 언어이기도 하다. 이 글은 그 출발점인 확률 공간, 조건부 확률, 독립, 베이즈 정리를 다룬다.

(이 글에서 조건부 확률은 P(A ∣ B) 로 쓴다. 가운데 기호는 “주어졌을 때” 로 읽는다.)

핵심 개념

확률 공간

  • 표본 공간 S: 일어날 수 있는 모든 결과의 집합. 주사위 하나면 {1, 2, 3, 4, 5, 6}.
  • 사건: 표본 공간의 부분집합. “짝수가 나온다” = {2, 4, 6}.
  • 확률 P: 사건에 0 이상 1 이하의 수를 주는 함수로, 다음 공리를 만족한다.
    1. 모든 사건 A 에 대해 P(A) ≥ 0
    2. P(S) = 1
    3. 서로 겹치지 않는 사건들에 대해 P(A ∪ B ∪ …) = P(A) + P(B) + …

공리에서 바로 나오는 성질은 다음과 같다.

  • P(Aᶜ) = 1 − P(A) (여사건)
  • P(A ∪ B) = P(A) + P(B) − P(A ∩ B) (007번 글의 포함-배제와 같은 모양)
  • 결과가 모두 같은 확률이면 P(A) = n(A) / n(S). 이때 확률 계산은 경우의 수 세기다.

조건부 확률

B 가 일어났다는 것을 알면 표본 공간이 B 로 줄어든다. 그 안에서 A 가 차지하는 비율이 조건부 확률이다.

P(A ∣ B) = P(A ∩ B) / P(B)     (P(B) > 0)

주사위에서 “짝수가 나왔다” 는 것을 알 때 “4 이상” 일 확률은 P({4, 6}) / P({2, 4, 6}) = (2/6) / (3/6) = 2/3 이다. 정보가 없을 때의 1/2 보다 크다.

식을 바꾸면 곱셈 법칙 P(A ∩ B) = P(B)·P(A ∣ B) 가 된다. 여러 단계로 일어나는 사건의 확률을 단계별 조건부 확률의 곱으로 계산할 때 쓴다.

주의: P(A ∣ B) 와 P(B ∣ A) 는 다르다. “장애가 나면 알림이 울린다” 의 확률이 99% 라도, “알림이 울리면 장애다” 의 확률은 훨씬 낮을 수 있다. 이 혼동을 “조건 뒤집기 오류” 라 부르며, 아래 베이즈 정리가 정확한 관계를 준다.

전체 확률의 법칙

B₁, …, Bₖ 가 표본 공간을 겹치지 않게 나누면(분할이면)

P(A) = P(A ∣ B₁)P(B₁) + P(A ∣ B₂)P(B₂) + ... + P(A ∣ Bₖ)P(Bₖ)

“경우를 나눠 계산하고 가중 평균한다” 는 뜻이다.

독립

P(A ∩ B) = P(A)·P(B) 이면 A 와 B 는 독립이다. P(B) > 0 이면 이는 P(A ∣ B) = P(A), 즉 B 를 알아도 A 에 대한 정보가 없다는 뜻이다.

독립은 서로소(겹치지 않음)와 다르다. 확률이 양수인 두 사건이 서로소면 하나가 일어나면 다른 하나는 반드시 안 일어나므로 강하게 종속이다.

독립을 함부로 가정하면 위험하다. “디스크 하나가 1 년 안에 고장 날 확률이 p 면, 두 디스크가 모두 고장 날 확률은 p²” 이라는 계산은 두 고장이 독립일 때만 맞다. 같은 배치에서 생산되어 같은 랙에서 같은 열과 진동을 받는 디스크는 함께 고장 나는 경향이 있다.

베이즈 정리

곱셈 법칙을 양쪽으로 쓰면 P(A ∣ B)P(B) = P(B ∣ A)P(A) 이므로

             P(B ∣ A) · P(A)
P(A ∣ B) = ------------------
                  P(B)

분모 P(B) 는 전체 확률의 법칙으로 P(B ∣ A)P(A) + P(B ∣ Aᶜ)P(Aᶜ) 다.

항 이름 뜻
P(A) 사전 확률(prior) 증거를 보기 전의 믿음, 기저율
P(B ∣ A) 가능도(likelihood) 가설이 참일 때 이 증거가 나올 확률
P(A ∣ B) 사후 확률(posterior) 증거를 본 뒤의 믿음

기저율 오류: 정확도 99% 의 함정

어떤 장애 탐지기가 실제 장애의 99% 를 잡고(민감도), 정상 상황의 1% 에서 잘못 울린다(위양성률). 장애는 전체 시간 구간의 0.1% 에서만 일어난다. 경보가 울렸을 때 실제 장애일 확률은?

P(장애) = 0.001,  P(경보 ∣ 장애) = 0.99,  P(경보 ∣ 정상) = 0.01

P(경보) = 0.99 × 0.001 + 0.01 × 0.999 = 0.01098
P(장애 ∣ 경보) = 0.99 × 0.001 / 0.01098 ≈ 0.090

약 9% 다. 경보 열한 번 중 열 번은 오경보다. 탐지기가 나빠서가 아니라 장애가 드물어서다. 10 만 구간으로 바꿔 세어 보면 직관적이다. 장애 100 구간 중 99 개에서 울리고, 정상 99,900 구간 중 999 개에서 잘못 울린다. 울린 1,098 번 중 진짜는 99 번이다.

직접 해 보기

위 계산을 정확한 분수로 하고, 무작위 시뮬레이션으로 같은 값이 나오는지 확인한다. fractions.Fraction 을 쓰면 부동소수점 오차 없이 계산할 수 있다.

from fractions import Fraction as F
import random

def posterior(prior, sens, fpr):
    evidence = sens * prior + fpr * (1 - prior)
    return sens * prior / evidence

p = posterior(F(1, 1000), F(99, 100), F(1, 100))
print(p, float(p))

# 기저율에 따른 사후 확률 변화
for base in [F(1, 10000), F(1, 1000), F(1, 100), F(1, 10)]:
    print(f"기저율 {float(base):.4f} -> 사후 확률 {float(posterior(base, F(99,100), F(1,100))):.3f}")

# 시뮬레이션
random.seed(42)
N = 1_000_000
alarms = true_alarms = 0
for _ in range(N):
    incident = random.random() < 0.001
    alarm = random.random() < (0.99 if incident else 0.01)
    if alarm:
        alarms += 1
        true_alarms += incident
print(alarms, true_alarms, round(true_alarms / alarms, 3))

# 독립 vs 서로소: 주사위에서 A={짝수}, B={1,2}
S = range(1, 7)
P = lambda E: F(sum(1 for s in S if E(s)), 6)
A = lambda s: s % 2 == 0
B = lambda s: s in (1, 2)
print(P(lambda s: A(s) and B(s)) == P(A) * P(B))

실행 결과다(시뮬레이션 줄은 시드에 따라 조금씩 다르다).

11/122 0.09016393442622951
기저율 0.0001 -> 사후 확률 0.010
기저율 0.0010 -> 사후 확률 0.090
기저율 0.0100 -> 사후 확률 0.500
기저율 0.1000 -> 사후 확률 0.917
10969 935 0.085
True

정확한 답은 11/122 ≈ 0.090 이다. 시뮬레이션의 0.085 는 진짜 장애가 천 번 안팎뿐이라 생기는 표본 오차다. 같은 탐지기라도 기저율이 1% 면 사후 확률이 0.5, 10% 면 0.917 로 올라간다. 마지막 줄은 “짝수” 와 “1 또는 2” 가 독립이라는 뜻이다. P(짝수 ∩ {1,2}) = 1/6 = (1/2)(1/3).

현업에서는

  • 알림 설계. 드물게 일어나는 사건을 탐지하는 알림은 위양성률이 조금만 높아도 오경보가 대부분이 된다. 알림 피로를 줄이려면 탐지 정확도를 올리는 것만큼, 알림 조건을 여러 신호의 AND 로 묶어 위양성률을 곱으로 낮추는 것이 효과적이다. 단, 신호들이 서로 독립일 때 얘기다.
  • 나이브 베이즈 분류기. 스팸 필터의 고전적 방법은 단어들이 (클래스가 주어졌을 때) 서로 독립이라고 가정하고 베이즈 정리를 적용한다. 가정은 틀렸지만 실무에서 꽤 잘 동작한다. scikit-learn 같은 라이브러리에 기본 모델로 들어 있다.
  • 상관된 장애. 쿠버네티스에서 레플리카를 여러 개 띄워도 모두 같은 노드에 있으면 노드 하나의 장애가 전부를 죽인다. 파드 안티 어피니티나 토폴로지 분산 제약은 장애 사건들을 독립에 가깝게 만들려는 장치다(Kubernetes, Assigning Pods to Nodes).
  • 플래키 테스트 판단. 어떤 테스트가 평소 2% 확률로 무작위 실패한다면, 이번 실패가 진짜 회귀인지는 “최근 변경이 버그를 넣었을 사전 확률” 에 크게 좌우된다. 재실행해서 또 실패하면 가능도가 곱해지므로 사후 확률이 빠르게 오른다(재실행 결과가 독립이라는 가정 아래).

확인 문제

  1. 동전 두 개를 던져 적어도 하나가 앞면일 때, 둘 다 앞면일 확률은?
  2. 확률이 양수인 두 사건 A, B 가 서로소면 독립일 수 있는가?
  3. 질병 유병률 2%, 민감도 90%, 위양성률 5% 인 검사에서 양성일 때 실제 환자일 확률은?
  4. 서버 3 대가 각각 독립적으로 하루에 1% 확률로 다운된다. 하루에 적어도 한 대가 다운될 확률은?

풀이

  1. 표본 공간 {HH, HT, TH, TT} 에서 조건 사건은 {HH, HT, TH}. 답은 1/3.
  2. 없다. P(A ∩ B) = 0 이지만 P(A)P(B) > 0 이다.
  3. 0.9 × 0.02 / (0.9 × 0.02 + 0.05 × 0.98) = 0.018 / 0.067 ≈ 0.269.
  4. 1 − 0.99³ ≈ 0.0297.

더 읽을거리 (References)