계산주의와 인지신경과학 8주차_모델 평가와 파라미터 추정: 그리드 서치와 최적화

고려대학교 일반대학원 심리학과

Published

2026년 4월 24일 금요일 4-5교시

8주차 수업 목표

[그림 1] 데이터를 통한 마음의 역추적(model fitting)

  • 패러다임의 역발상(forward에서 backward로): 파라미터를 조작해 가상의 행동 데이터를 생성했던 정방향 시뮬레이션(forward modeling)을 뒤집어, 실험실의 실제 행동 데이터로부터 뇌 속에 숨겨진 인지 파라미터를 거꾸로 역추적하는 역방향 추론(reverse engineering) 메커니즘을 체화함.
  • 적합도 평가의 수학적 기준(SSE): 연구자가 만든 인지 모델의 예측값이 피험자의 실제 데이터와 얼마나 완벽하게 일치하는지를 정량적으로 평가하는 오차 제곱합(sum of squared errors, SSE)의 수학적 원리와 기하학적 패널티 시스템을 이해함.
  • 과적합(overfitting)의 인지적 통제: 모델의 파라미터가 무한정 늘어날 때 발생하는 과적합의 위험성을 파악하고, 오컴의 면도날(Occam’s razor) 철학을 바탕으로 모델의 설명력과 복잡도 사이의 균형을 통제함.
  • 확률적 최적화(MLE): 단순한 오차 최소화를 넘어, ’현재의 데이터가 관측될 확률을 극대화한다’는 통계학의 근간인 최대우도추정(maximum likelihood estimation, MLE) 기법을 인지 모델링에 도입함.
  • 파라미터 탐색 알고리즘 직접 구현: 무식하지만 확실한 전수조사 기법인 그리드 서치(grid search)와, 알고리즘 기반의 자동 최적화(optimization) 함수를 파이썬으로 직접 코딩하여 피험자의 숨겨진 마음을 해독함.

1부: 모델 피팅의 철학과 적합도 평가(goodness-of-fit)

1.1. 역방향 추론(reverse engineering the mind)

  • 이론적 설명
    • 인지 모델링 연구의 궁극적인 목적: 실제 인간 피험자를 대상으로 실험을 진행한 뒤 기록된 차가운 숫자들(반응시간[RT], 정답률)을 분석하여, 이 피험자의 전두엽 임계값(\(a\))은 몇이었고, 시각 피질의 정보처리 속도(\(v\))는 몇이었는가?를 정밀하게 추정해내는 것.
    • 이처럼 관측된 결과(data)를 바탕으로 원인이 되는 내부 변수(parameters)의 최적값을 찾아 퍼즐을 맞추는 일련의 수학적 과정을 모델 피팅(model fitting) 또는 파라미터 추정(parameter estimation)이라고 부름.
  • 심화 해설(인지과학의 인식론적 도약)
    • 인간의 뇌는 두개골로 덮여 있는 거대한 블랙박스. fMRI나 EEG 같은 뇌영상 기법조차 뇌의 혈류량이나 전위차를 보여줄 뿐, ‘신중함’이나 ’주의력’ 같은 심리학적 변수의 구체적 수치를 직접 뽑아주지는 못함.
    • 계산주의 인지신경과학은 행동 데이터(RT)와 뇌의 내부 기제(parameter) 사이를 연결하는 ’수학적 렌즈(수식)’를 만듦으로써, 두개골을 열지 않고도 인간의 마음을 해독하고 정량화하는 인식론적 도약을 이룩함.

[그림 2] 두개골 속의 블랙박스 해독하기

  • 비유(명탐정의 발자국 추리)
    • 정방향 시뮬레이션(7주차): 키 180cm, 몸무게 80kg인 사람(파라미터)이 진흙탕(모델 공식)을 걸어갈 때, 깊이 3cm의 발자국(데이터)이 남을 것이라고 ’예측’하는 과정.
    • 역방향 모델 피팅(8주차): 범죄현장에 남겨진 깊이 3cm의 발자국(실제 데이터)만을 뚫어져라 쳐다본 뒤, “이 발자국을 남길 수 있는 범인은 키 180cm에 몸무게 80kg일 수밖에 없다!”라고 거꾸로 ’역추적’해내는 명탐정의 과정.

[그림 3] 데이터를 통한 마음의 역추적(model fitting)

[실습 1-1] 타겟이 될 피험자의 가상 행동 데이터 생성

  • 역방향 추론 실습을 진행하기 위해, 먼저 우리의 타겟(범인)이 될 ’피험자의 실제 실험 데이터’를 파이썬을 통해 생성하고 저장함.
Code
import numpy as np  # 수학 연산과 정규분포 난수(노이즈) 생성을 위한 numpy 라이브러리 임포트함.

print("=== [실습 1-1] 피험자의 행동 데이터 확보===")

# ---------------------------------------------------------
# 1. 피험자의 뇌 구조(진짜 파라미터) 세팅 구역
# (주의: 실제 연구에서 분석자는 이 값들을 모른다고 전제함. 가상의 피험자 생성을 위한 설정임.)
# ---------------------------------------------------------
true_a = 2.0        # 임계값(a): 결정을 내리기 위해 모아야 하는 마음속 확신의 양을 2.0으로 설정함
true_v = 0.01       # 표류율(v): 1ms당 들어오는 진짜 정보량. 충분한 고민을 유도하기 위해 0.01로 낮게 설정함
true_noise = 0.1    # 노이즈(sigma): 1ms마다 피험자의 주의력을 흔드는 신경학적 잡음의 크기임
t_er = 250          # 비의사결정 시간(Ter): 지각 및 운동에 소요되는 순수 물리적 딜레이(250ms) 반영함

observed_rt_data = []  # 50번의 시행 결과(반응시간)를 담아둘 빈 리스트를 생성함

np.random.seed(42)     # 동일한 난수 발생을 위해 시드(seed)를 42로 고정함

# ---------------------------------------------------------
# 2. 피험자의 50회 실험(Trial) 시뮬레이션 구역
# ---------------------------------------------------------
for trial in range(50):  # 50번의 반복실험 시작함
    evidence = 0.0       # 새로운 문항마다 증거 저금통을 0으로 초기화함
    decision_time = 0    # 뇌 속 순수 인지 고민 시간을 0ms로 초기화함
    
    while True:          # 결정이 내려질 때까지 무한 반복되는 고민 루프 가동함
        decision_time += 1  # 루프 1회당 물리적 시간 1ms 경과함
        
        # [증거 축적 핵심 로직] 
        # 기존 증거에 (표류율 + 랜덤 노이즈)를 더해 1ms 동안의 증거를 누적함
        evidence += true_v + np.random.normal(0, true_noise)
        
        # [의사결정 방아쇠 격발]
        # 누적 증거가 정답 임계선(+2.0) 또는 오답 임계선(-2.0)을 돌파했는지 검사함
        if evidence >= true_a or evidence <= -true_a:
            
            # 확신 도달 시, 순수 고민 시간(decision_time)에 근육 딜레이(t_er)를 더해 최종 RT 산출함.
            final_rt = decision_time + t_er
            
            # 완성된 최종 반응시간을 리스트에 추가함.
            observed_rt_data.append(final_rt) 
            
            # 결정을 내렸으므로 while 루프를 탈출(break)하고 다음 문항으로 넘어감.
            break

# ---------------------------------------------------------
# 3. 데이터 수집 완료 및 평균 도출
# ---------------------------------------------------------
observed_rt_data = np.array(observed_rt_data) # 리스트를 numpy 배열로 변환함.
actual_mean_rt = np.mean(observed_rt_data)    # 50개 반응시간 데이터의 평균 도출함.

print(f"-> 50회 시행 결과, 피험자의 실제 평균 RT: {actual_mean_rt:.2f} ms")
=== [실습 1-1] 피험자의 행동 데이터 확보===
-> 50회 시행 결과, 피험자의 실제 평균 RT: 443.02 ms

1.2. 오차 제곱합(sum of squared errors, SSE)

  • 이론적 설명
    • 명탐정이 범인의 뇌 속 파라미터를 추리하려면, “내가 임계값(\(a\))을 1.5라고 가정했을 때 모델이 뱉어낼 예측 데이터”와 “실제 현장의 피험자 데이터” 사이의 차이(오차)를 정밀하게 수치화해야 함.
    • 모델의 예측값(\(\hat{y}\))과 실제 데이터(\(y\)) 사이의 적합도(얼마나 잘 들어맞는가)를 평가하는 통계학의 가장 보편적 지표가 바로 오차 제곱합(SSE)임.
    • 수식: \(SSE = \sum_{i=1}^{n}(y_i - \hat{y}_i)^2\)(실제 데이터와 모델 예측값의 차이를 모두 제곱하여 합산함).
  • 심화 해설(왜 ’절댓값’이 아니라 ’제곱’인가?)
    • 단순한 차이(단순 뺄셈)를 합산하면, 양수 오차(+50ms)와 음수 오차(-50ms)가 서로 상쇄되어 0이 되므로, 엉터리 모델을 완벽한 모델로 착각하게 만드는 치명적 오류가 발생함.
    • 절댓값 대신 굳이 제곱을 사용하는 이유는 큰 오차에 기하급수적인 패널티(벌점)를 부여하기 위해서임.
    • 오차가 10ms일 때 벌점은 100이지만, 오차가 100ms로 커지면 벌점은 10,000이 됨. \(\Rightarrow\) 자잘한 오차 여러 개보다 하나의 끔찍하고 거대한 오차(outlier)를 낸 모델을 가차없이 응징하여, 모델이 데이터를 전반적으로 고르게 설명하도록 유도하는 수학적 족쇄임.
  • 비유(양궁 과녁의 가혹한 감점 시스템)
    • 10점 만점 과녁 중심에서 1cm 빗나갔을 때는 1점만 감점하지만, 10cm를 빗나가서 남의 과녁에 화살을 꽂아버리는 엄청난 실수를 저질렀을 때는 10점이 아니라 100점을 감점해 버리는 가혹한 채점 시스템과 같음.
    • SSE 벌점이 \(0\)에 가까울수록 내 모델의 파라미터 조합이 현실 피험자의 뇌를 완벽하게 모사했다는 뜻임.

[그림 4] SSE의 수학적 원리와 패널티 시스템

[실습 1-2] 파이썬으로 구현하는 SSE 적합도 평가기

  • 동일한 피험자 데이터를 두고, 엉터리 가설을 세운 모델 A와 정교한 가설을 세운 모델 B 중 어떤 모델이 더 적합한지 SSE를 계산하여 수학적 심판을 내림.
Code
print("=== [실습 1-2] 오차 제곱합(SSE)을 통한 모델의 적합도(goodness-of-fit) 심판 ===")

# ---------------------------------------------------------
# 1. 두 연구자의 가설(파라미터 추측) 대결 구역
# ---------------------------------------------------------
# [연구자 A의 예측]: 임계값을 지나치게 낮게 추측하여 예측 RT가 250ms로 짧게 산출됨
model_A_predicted_rt = 250.0 

# [연구자 B의 예측]: 파라미터 조합을 정교하게 추측하여 피험자의 실제 RT(약 460ms)와 유사하게 산출됨
model_B_predicted_rt = 460.0 

# ---------------------------------------------------------
# 2. 수학적 심판관: SSE(오차 제곱합) 계산 함수 정의
# ---------------------------------------------------------
def calculate_sse(actual, predicted):  # 실제 데이터(actual)와 예측치(predicted)를 입력받음
    error = actual - predicted         # 실제값에서 예측값을 빼서 순수오차를 계산함
    squared_error = np.square(error)   # 오차를 제곱하여 부호를 양수로 만들고, 큰 오차에 가혹한 패널티 부여함
    return squared_error               # 계산된 벌점(SSE)을 반환함

# ---------------------------------------------------------
# 3. 각 연구자의 벌점 채점 및 최종 결과 발표
# ---------------------------------------------------------
# 연구자 A의 250ms 예측치에 대한 SSE 벌점 계산함(actual_mean_rt는 실습 1-1에서 획득)
sse_A = calculate_sse(actual_mean_rt, model_A_predicted_rt)

# 연구자 B의 460ms 예측치에 대한 SSE 벌점 계산함
sse_B = calculate_sse(actual_mean_rt, model_B_predicted_rt)

# 소수점 둘째 자리까지 포맷팅하여 벌점 출력함
print(f"-> 엉터리 추측 [모델 A]의 SSE 벌점: {sse_A:.2f}")
print(f"-> 정교한 추측 [모델 B]의 SSE 벌점: {sse_B:.2f}")

# SSE 비교를 통해 승리자 선언함
if sse_A > sse_B:
    print("\n 심판 판정: [모델 B]의 SSE 벌점이 훨씬 작으므로, 연구자 B가 피험자의 마음을 완벽히 해독해냄!")
else:
    print("\n 심판 판정: [모델 A]가 승리함.")
=== [실습 1-2] 오차 제곱합(SSE)을 통한 모델의 적합도(goodness-of-fit) 심판 ===
-> 엉터리 추측 [모델 A]의 SSE 벌점: 37256.72
-> 정교한 추측 [모델 B]의 SSE 벌점: 288.32

 심판 판정: [모델 B]의 SSE 벌점이 훨씬 작으므로, 연구자 B가 피험자의 마음을 완벽히 해독해냄!

1.3. 과적합(overfitting)과 오컴의 면도날(Occam’s Razor)

  • 이론적 설명
    • 모델이 현실 데이터를 얼마나 잘 설명하는지(SSE 최소화)에만 집착하면 인지 모델링의 거대한 함정인 과적합에 빠지게 됨.
    • 파라미터의 종류(\(a, v, z, T_{er}\) 등)를 10개, 20개로 무한정 늘리면 수학적 유연성이 극대화되어 현재 실험실에서 얻은 데이터의 SSE를 0으로 완벽하게 맞출 수 있음.
    • 그러나 이는 뇌의 본질적인 인지기제를 파악한 것이 아니라, 그날 피험자가 졸았거나 재채기를 해서 발생한 무의미한 노이즈(우연적 오차)까지 모조리 학습해버린 상태임.
  • 심화 해설(설명력과 복잡도의 상충 관계)
    • 과적합된 모델은 현재 데이터는 완벽히 설명하지만, 내일 똑같은 실험을 다시 했을 때 얻어질 새로운 데이터(test data)에 대해서는 예측력이 붕괴됨.
    • 이를 막기 위해 과학철학의 근간인 오컴의 면도날(Occam’s razor: “현상에 대한 설명은 불필요하게 복잡해져서는 안 된다”) 원칙을 모델 평가에 도입함.
    • 현대 통계학에서는 SSE의 최소화를 추구하면서도 동시에 파라미터 개수가 늘어날 때마다 무거운 벌점을 때리는 AIC(Akaike information criterion)BIC(Bayesian information criterion) 지표를 사용하여, 가장 단순하면서도 설명력이 높은 최적의 모델을 최종 채택함.
  • 비유(기출문제와 수능시험)
    • 정상적 학습: 기출문제를 풀며 수학의 핵심 ’원리’를 깨닫고 수능시험장(새로운 데이터)에 들어가서 고득점을 받음(일반화 성공).
    • 과적합 학습: 수학의 원리는 모른 채 기출문제집의 정답 번호와 오타 위치까지 통째로 외워버림. 기출문제 모의고사(현재 데이터)는 100점(SSE=0)을 맞지만, 문제가 조금만 바뀐 수능시험에서는 0점을 맞게 됨.

[그림 5] 과적합과 오컴의 면도날 곡선

[실습 1-3] 파라미터 개수에 따른 과적합 현상 관찰

  • 단순한 모델(파라미터 1개)과 지나치게 복잡한 다항식 모델(파라미터 15개)을 비교하여, 복잡한 모델이 노이즈까지 추적하느라 궤적이 요동치는 과적합 현상을 시각적으로 코딩함.
Code
import matplotlib.pyplot as plt  # 시각적 그래프를 그리기 위한 matplotlib의 pyplot 모듈을 임포트함

print("=== [실습 1-3] 오컴의 면도날: 모델 복잡도와 과적합의 딜레마 ===")

# ---------------------------------------------------------
# 1. 훈련용 데이터(기출문제) 생성 구역
# ---------------------------------------------------------
np.random.seed(0)  # 코드를 다시 실행해도 똑같은 난수(노이즈)가 발생하도록 시드를 0으로 고정함

# 0부터 10까지의 구간을 15개의 일정한 간격으로 쪼개어 입력 데이터(X축: 자극)를 생성함
x_train = np.linspace(0, 10, 15)  

# 피험자 뇌의 '진짜 원리(정답)'를 부드러운 사인(sin) 곡선이라고 가정하고 계산함
true_trend = np.sin(x_train) 

# 진짜 원리에 무작위 노이즈(평균 0, 표준편차 0.3)를 더해 피험자의 우연한 실수(재채기 등)가 반영된 실제 관측 데이터를 만듦
y_train = true_trend + np.random.normal(0, 0.3, 15) 

# ---------------------------------------------------------
# 2. 두 가지 모델 피팅 시도 구역 (단순 모델 vs 과적합 모델)
# ---------------------------------------------------------
# [단순한 모델]: 3차 방정식(파라미터 4개)으로 데이터의 거시적 흐름만 부드럽게 따라가도록 학습(피팅)함
simple_model_params = np.polyfit(x_train, y_train, 3) 
# 도출된 파라미터를 사용해 X를 넣으면 예측값을 뱉어내는 수학 함수 객체로 변환함
simple_model_func = np.poly1d(simple_model_params)  

# [과적합 모델]: 14차 방정식(파라미터 15개)으로 15개의 데이터를 강제로 모두 지나가도록 학습(피팅)함
overfitted_model_params = np.polyfit(x_train, y_train, 14) 
# 15개의 파라미터를 가져 극도로 구불구불한 궤적을 뱉어내는 괴물 같은 예측 함수 객체를 생성함.
overfitted_model_func = np.poly1d(overfitted_model_params)  

# ---------------------------------------------------------
# 3. 훈련 데이터에 대한 SSE(오차 제곱합) 채점 구역
# ---------------------------------------------------------
# 실제 데이터(y_train)에서 단순 모델의 예측값을 뺀 뒤, 모조리 제곱하여 더함(SSE 벌점 계산)
sse_simple = np.sum((y_train - simple_model_func(x_train))**2)

# 실제 데이터에서 과적합 모델의 예측값을 뺀 뒤, 모조리 제곱하여 더함
sse_overfit = np.sum((y_train - overfitted_model_func(x_train))**2)

# 채점 결과를 소수점 둘째 자리(.2f)까지 깔끔하게 텍스트로 출력함
print(f"-> [단순 모델]의 훈련 SSE: {sse_simple:.2f} (적당한 오차 존재)")
print(f"-> [과적합 모델]의 훈련 SSE: {sse_overfit:.2f} (오차가 0에 수렴! 기출문제 완벽 암기!)")
print("\n-> 통찰: 과적합 모델은 훈련 SSE를 0으로 만들었지만, 15개나 되는 파라미터를 동원해 피험자의 우연한 노이즈까지 수학적 진리로 착각해버린 상태임. 새로운 데이터가 들어오면 예측력이 완전히 박살남.\n")

# ---------------------------------------------------------
# 4. 그래프 시각화 구역(과적합을 눈으로 직접 확인하기)
# ---------------------------------------------------------
# 모델의 예측곡선을 뚝뚝 끊기지 않고 부드럽게 그리기 위해, 0부터 10 구간을 100개로 잘게 쪼갠 X축 렌더링용 데이터를 새로 만듦
x_dense = np.linspace(0, 10, 100)

# 가로 10인치, 세로 6인치 크기의 넉넉한 도화지(그래프 창)를 준비함
plt.figure(figsize=(10, 6))

# [1] 피험자가 남긴 실제 데이터(노이즈 포함)를 검은색 점(scatter)으로 찍음. zorder=5로 설정해 선들보다 맨 위로 튀어나오게 띄움
plt.scatter(x_train, y_train, color='black', s=50, label='Observed Data (with Noise)', zorder=5)

# [2] 자연의 진짜 법칙인 순수 사인(sin) 곡선을 눈에 덜 띄는 회색 점선으로 부드럽게 밑그림으로 깔아줌
plt.plot(x_dense, np.sin(x_dense), color='gray', linestyle='--', label='True Trend (sin)', alpha=0.6)

# [3] 오컴의 면도날 원칙을 지킨 단순한 모델의 예측궤적을 굵은 파란색 선으로 그림(결과적으로 과소적합[underfitting] 모델의 예측궤적을 보여줌)
plt.plot(x_dense, simple_model_func(x_dense), color='blue', linewidth=2.5, label='Simple Model (Degree 3)')

# [4] 오차(SSE)를 0으로 만들려다 무의미한 노이즈까지 모조리 외워버린 과적합 모델의 예측궤적을 굵은 빨간색 지그재그 선으로 그림
plt.plot(x_dense, overfitted_model_func(x_dense), color='red', linewidth=2.5, label='Overfitted Model (Degree 14)')

# 그래프 최상단의 제목을 굵은 폰트(bold)와 15 사이즈로 설정함
plt.title("Occam's Razor: Simple vs Overfitted Model", fontsize=15, fontweight='bold')

# X축과 Y축의 직관적인 이름표를 각각 달아줌
plt.xlabel("X (Stimulus)", fontsize=12)
plt.ylabel("Y (Response)", fontsize=12)

# 빨간 선이 과적합으로 인해 화면 밖으로 뚫고 나가는 것을 막기 위해, Y축의 시각적 출력 범위를 -2부터 2까지로 억제함
plt.ylim(-2, 2)

# 각 선이 무엇을 의미하는지 알려주는 범례(Legend)를 그래프의 오른쪽 위(upper right) 구석에 배치함
plt.legend(loc='upper right', fontsize=10)

# 눈금을 알아보기 쉽게 배경에 점선 형태의 그리드(격자)를 은은하게 깔아줌
plt.grid(True, linestyle=':', alpha=0.6)

# 여러 그래픽 요소나 글자가 서로 겹치지 않게 여백을 깔끔하게 자동 조절함
plt.tight_layout()

# 최종적으로 렌더링이 완성된 그래프 창을 모니터 화면에 띄움
plt.show()
=== [실습 1-3] 오컴의 면도날: 모델 복잡도와 과적합의 딜레마 ===
-> [단순 모델]의 훈련 SSE: 8.28 (적당한 오차 존재)
-> [과적합 모델]의 훈련 SSE: 0.00 (오차가 0에 수렴! 기출문제 완벽 암기!)

-> 통찰: 과적합 모델은 훈련 SSE를 0으로 만들었지만, 15개나 되는 파라미터를 동원해 피험자의 우연한 노이즈까지 수학적 진리로 착각해버린 상태임. 새로운 데이터가 들어오면 예측력이 완전히 박살남.

2부: 최적화 기법과 최대우도추정(optimization & MLE)

2.2. 최대우도추정(maximum likelihood estimation, MLE)

  • 이론적 설명
    • SSE가 모델과 데이터의 기하학적 거리(오차)를 최소화하려는 접근이라면, 현대 통계학과 기계학습의 핵심인 최대우도추정(MLE)은 동일한 목적을 확률적 관점으로 달성하는 수학적 기법임.
    • 우도(likelihood, 가능도): 어떤 특정한 파라미터(\(a=2.0\))를 뇌 속에 세팅해두었을 때, 지금 내 눈앞에 놓인 피험자 데이터가 그대로 나올 그럴듯함(확률밀도)을 뜻함.
    • MLE의 목적: 무수히 많은 파라미터 조합을 대입해보면서, 눈앞의 이 데이터가 튀어나올 확률(우도)을 가장 극대화(maximum)시켜주는 궁극의 원인(파라미터 조합)을 역추적해내는 것임(정규분포 가정 하에서 ‘SSE를 최소화하는 파라미터’ \(=\) ‘우도를 극대화하는 파라미터’).
  • 심화 해설(로그 우도, log-likelihood의 마법)
    • 여러 개의 데이터가 동시에 발생할 전체 확률(교집합)을 구하려면, 각 데이터의 확률(0~1 사이의 소수)을 끝없이 곱해나가야 함(\(0.5 \times 0.2 \times 0.1 \dots\)).
    • 컴퓨터 메모리는 소수점 아래로 너무 길게 내려가는 숫자를 감당하지 못하고 그냥 0으로 날려버리는 치명적인 에러(언더플로우, underflow)를 일으킴.
    • 이를 방지하기 위해 확률의 곱셈식 양변에 로그(\(\log\))를 씌움
        1. 원래의 확률 곱셈식(로그를 씌우기 전)
        • 피험자가 3번의 시행에서 각각 데이터를 하나씩 남겼다고 가정해보자(데이터 A, B, C). 우리가 세운 가설(파라미터)이 맞을 때, 이 세 데이터가 연달아 등장할 전체 확률(우도)을 구하는 공식은 다음과 같음.
          • 좌변(목표): 총 우도(전체 확률, \(L\)).
          • 우변(계산): 개별 데이터가 등장할 확률들의 곱셈.
          • [공식 1] \(L = P(A) \times P(B) \times P(C)\)
        • 각 데이터가 등장할 확률이 모두 10%(0.1)라고 가정해보자. \(\Rightarrow\) \(L = 0.1 \times 0.1 \times 0.1 = 0.001\)
        • 데이터가 3개일 때는 0.001이지만, 데이터가 100개만 되어도 \(0.1\)을 100번 곱해야 하므로 우변의 결과값은 \(0.0000000000000 \dots 1\)이 되어 컴퓨터가 계산을 포기해버림(언더플로우 에러).
        1. 양변에 로그(\(\log\)) 씌우기
        • 이 에러를 막기 위해, 수학자들은 [공식 1]의 좌변(\(L\))과 우변(\(P(A) \times P(B) \dots\)) 양쪽 모두에 똑같이 \(\log\) 기호를 씌워줌.
        • [공식 2] \(\log(L) = \log(P(A) \times P(B) \times P(C))\)
        • 좌변의 변화: 그냥 전체 확률이었던 \(L\)\(\log(L)\)이 됨. 이것이 바로 로그 우도임.
        1. 로그의 마법(곱셈 \(\rightarrow\) 덧셈)
        • 중고등학교 수학 시간에 배운 로그의 가장 특별한 성질 중 하나는 진수의 곱셈은 로그의 덧셈으로 쪼개진다는 것. \(\Rightarrow\) \(\log(X \times Y) = \log(X) + \log(Y)\)
        • 이 성질을 [공식 2]의 우변에 적용: [최종 공식] \(\log(L) = \log(P(A)) + \log(P(B)) + \log(P(C))\)
        • 이제 숫자를 다시 넣어보자(계산의 편의를 위해 밑이 10인 상용로그를 쓴다고 가정. \(\log(0.1) = -1\)임).
          • 변경 전(곱셈): \(0.1 \times 0.1 \times 0.1 = 0.001\) \(\Rightarrow\) 숫자가 0으로 소멸할 위험.
          • 변경 후(덧셈): \((-1) + (-1) + (-1) = -3\) \(\Rightarrow\) 안전하고 깔끔한 덧셈.
      • 결론
        • 수학적으로 어떤 함수의 원래 값(\(L\))을 최대로 만드는 파라미터는 그 함수에 로그를 씌운 값(\(\log L\))을 최대로 만드는 파라미터와 정확히 일치.
        • 따라서 우리는 컴퓨터가 계산하기 편하도록 우변을 덧셈으로 쪼개어 더한 뒤, 이 좌변의 로그 우도가 가장 큰 숫자(가장 0에 가까운 음수)가 되게 하는 파라미터를 찾는 것!
    • 로그의 마법 덕분에 위험한 소수점 곱셈(\(\times\))이, 처리하기 아주 빠르고 안전한 덧셈(\(+\))으로 변환됨. 따라서 모든 인지 모델링 연구는 단순 우도가 아닌 로그 우도를 극대화하는 알고리즘을 사용함.
  • 비유(찌그러진 동전 도박사의 역추적)
    • 눈가리개를 한 상태에서 누군가 동전을 10번 던졌는데 앞면이 9번이나 나왔음(관측 데이터). 정상적인 동전(앞면 확률 0.5)이라면 이런 기괴한 데이터가 나올 확률(우도)은 기적에 가까움.
    • 이때 우리는 거꾸로 추리(MLE)함. 저 동전은 무게중심이 앞면으로 90%쯤 쏠려있는 조작된 동전(파라미터: 0.9)이어야만, 방금 10번 중 9번 앞면이 나온 현실을 가장 높은 확률로 훌륭하게 설명(극대화)할 수 있다! 이것이 최대우도추정의 본질임.

[그림 7] SSE의 최소화와 MLE의 극대화: 동전의 양면

[실습 2-2] 로그 우도(Log-Likelihood)를 이용한 파라미터 역추적

  • 여러 개의 RT 데이터가 주어졌을 때, 언더플로우를 방지하는 로그 덧셈 기법을 활용하여 이 데이터들을 발생시킬 확률이 가장 높은 파라미터(평균 \(\mu\))를 MLE로 역추적함.
Code
import numpy as np # 넘파이 라이브러리 임포트
import scipy.stats as stats # 정규분포 확률 밀도(pdf)를 구하기 위한 사이파이 통계 라이브러리 임포트

print("=== [실습 2-2] 최대우도추정(MLE): 데이터가 관측될 확률(우도) 극대화하기 ===")

# 1. 엑셀에서 가져온 피험자의 미스터리한 RT 데이터 5개(관측된 현실)
data_rt = np.array([390, 410, 400, 420, 380])
print(f"관측된 피험자 데이터: {data_rt}\n")

# 2. 그리드 서치를 위한 평균(mu) 파라미터 공간 설정
# 피험자 RT를 지배하는 뇌 속 평균 파라미터가 350 ~ 450ms 사이일 것으로 보고 10ms 단위로 범위를 쪼갬
mu_candidates = np.arange(350, 460, 10)

# 최적의 챔피언 값을 저장할 변수 초기화(이번엔 최소값이 아니라 '최대' 확률을 찾아야 함)
best_mu = None
# 로그 우도의 최대값을 갱신하기 위해 초기 챔피언 방어선을 마이너스 무한대(-inf)로 뚫어놓음
max_log_likelihood = -float('inf')  

# 3. 최대우도추정(MLE) 루프 가동(전수조사)
for mu_guess in mu_candidates:
    # 연산의 단순화를 위해 뇌의 노이즈(표준편차 sigma)는 20으로 고정되었다고 전제함
    fixed_sigma = 20.0
    
    # [우도 계산 핵심 로직]
    # stats.norm.pdf: 지금 우리가 세운 가설(mu_guess, 가령 평균 RT 350ms)이 피험자의 진짜 뇌 구조(정규분포)라고 가정했을 때, 
    # 눈앞의 5개 실제 데이터(data_rt) 각각이 우연히 튀어나올 확률들을 한 번에 계산하여,
    # '단 1개의 리스트(배열)' 안에 5개의 확률값을 나란히 담아 뱉어냄
    likelihoods = stats.norm.pdf(data_rt, loc=mu_guess, scale=fixed_sigma)
    
    # [로그 변환 및 언더플로우 방지 덧셈]
    # 5개의 소수점을 덧곱(*)하면 언더플로우가 나므로, np.log를 씌워 로그 공간으로 보낸 뒤 np.sum으로 안전하게 덧셈(+) 합산함
    log_likelihood = np.sum(np.log(likelihoods))
    
    print(f"가설: 평균(mu)이 {mu_guess}일 때 -> 이 현실 데이터가 등장할 로그 확률(우도): {log_likelihood:.2f}")
    
    # 4. 최대 우도 갱신(Maximum Likelihood 챔피언전)
    # 방금 계산한 로그 우도 확률이 지금까지의 최대 확률 신기록을 깼다면?
    if log_likelihood > max_log_likelihood:
        max_log_likelihood = log_likelihood # 확률 신기록 덮어쓰기
        best_mu = mu_guess                  # 그때의 위대한 파라미터를 챔피언으로 등극시킴

# 5. 탐색 완료 및 최종 역추적 결과 보고
print("\n [탐색 종료] 최대우도추정(MLE) 결과 보고서:")
print(f"-> 관측된 5개의 데이터를 뱉어낼 확률을 가장 극대화(Maximum)하는 최적의 파라미터(mu)는 [{best_mu}] 로 추정됨!")
print(f"-> 통계적 진실 확인: 실제 5개 데이터의 산술 평균인 {np.mean(data_rt)}과 정확히 일치함. 즉 MLE 확률 모델이 수학적 추론을 통해 블랙박스 속 파라미터를 스스로 완벽히 역추적해낸 것임.")
=== [실습 2-2] 최대우도추정(MLE): 데이터가 관측될 확률(우도) 극대화하기 ===
관측된 피험자 데이터: [390 410 400 420 380]

가설: 평균(mu)이 350일 때 -> 이 현실 데이터가 등장할 로그 확률(우도): -36.45
가설: 평균(mu)이 360일 때 -> 이 현실 데이터가 등장할 로그 확률(우도): -30.82
가설: 평균(mu)이 370일 때 -> 이 현실 데이터가 등장할 로그 확률(우도): -26.45
가설: 평균(mu)이 380일 때 -> 이 현실 데이터가 등장할 로그 확률(우도): -23.32
가설: 평균(mu)이 390일 때 -> 이 현실 데이터가 등장할 로그 확률(우도): -21.45
가설: 평균(mu)이 400일 때 -> 이 현실 데이터가 등장할 로그 확률(우도): -20.82
가설: 평균(mu)이 410일 때 -> 이 현실 데이터가 등장할 로그 확률(우도): -21.45
가설: 평균(mu)이 420일 때 -> 이 현실 데이터가 등장할 로그 확률(우도): -23.32
가설: 평균(mu)이 430일 때 -> 이 현실 데이터가 등장할 로그 확률(우도): -26.45
가설: 평균(mu)이 440일 때 -> 이 현실 데이터가 등장할 로그 확률(우도): -30.82
가설: 평균(mu)이 450일 때 -> 이 현실 데이터가 등장할 로그 확률(우도): -36.45

 [탐색 종료] 최대우도추정(MLE) 결과 보고서:
-> 관측된 5개의 데이터를 뱉어낼 확률을 가장 극대화(Maximum)하는 최적의 파라미터(mu)는 [400] 로 추정됨!
-> 통계적 진실 확인: 실제 5개 데이터의 산술 평균인 400.0과 정확히 일치함. 즉 MLE 확률 모델이 수학적 추론을 통해 블랙박스 속 파라미터를 스스로 완벽히 역추적해낸 것임.

2.3. 자동화된 최적화 알고리즘(algorithmic optimization)

  • 이론적 설명
    • 그리드 서치는 직관적이지만 파라미터가 5~6개만 되어도 우주가 멸망할 때까지 계산이 끝나지 않는 차원의 저주에 걸림.
    • 이를 극복하기 위해 현대 컴퓨팅은 일일이 바둑판을 뒤지는 대신, 수학적 미분이나 기하학적 경험칙 등을 활용해 컴퓨터가 스스로 오차가 줄어드는 방향을 찾아가는 자동 최적화 알고리즘(optimization algorithm)을 사용함.
    • 대표적 사례: 경사하강법(gradient descent), 넬더-미드 심플렉스(Nelder-Mead simplex), BFGS(Broyden–Fletcher–Goldfarb–Shanno) 등.
    • 파이썬 함수: 파이썬에서는 scipy.optimize.minimize 함수 단 한 줄로 최적화 알고리즘이라는 거대한 톱니바퀴를 돌릴 수 있음.
  • 심화 해설(어떻게 스스로 길을 찾는가?)
    • 넬더-미드 알고리즘: 미분이 불가능한 복잡한 모델(DDM 등)에서 가장 널리 쓰이는 아메바 탐색법임. 파라미터 공간에 아메바처럼 생긴 다각형(simplex)을 던져놓고, 가장 오차가 큰(나쁜) 모서리를 오차가 작은(좋은) 방향으로 뒤집어엎으며 꿈틀꿈틀 최적점을 향해 스스로 기어내려감.
    • 빠른 연산속도: 그리드 서치처럼 모든 좌표를 계산할 필요 없이, 가장 가파른 내리막만 골라서 순식간에 바닥에 도달하므로 연산속도가 수백 배 빠름.
  • 비유(안대 낀 등산객의 지팡이 더듬기)
    • 그리드 서치: 헬기에서 떨어져 한라산 전체의 모든 좌표를 1미터 간격으로 직접 밟아보며 어디가 제일 고도가 낮은지 전부 수첩에 적는 무식한 방법.
    • 자동 최적화 알고리즘: 안대를 낀 등산객이 헬기에서 떨어진 직후, 발바닥과 지팡이로 내 주변 땅의 기울기(오차의 변화량)만 더듬은 뒤 가장 가파른 내리막 방향으로만 한 걸음씩 잰걸음으로 미끄러져 내려가는 똑똑하고 효율적인 방법.

[그림 8] 그리드 서치 vs 넬더-미드 알고리즘의 탐색 궤적 비교

[실습 2-3] Scipy 라이브러리를 활용한 자동 파라미터 최적화(model fitting) 구현

  • 무식한 for 루프(그리드 서치)를 버리고, 파이썬의 강력한 scipy.optimize 알고리즘을 호출하여 컴퓨터가 알아서 오차의 밑바닥(최적 파라미터)으로 굴러 내려가도록 구현함.
Code
import numpy as np # numpy 임포트
from scipy.optimize import minimize # 자동 최적화 함수 minimize 임포트

print("=== [실습 2-3] 스마트한 지팡이: scipy를 이용한 자동 파라미터 최적화 ===")

# 1. 엑셀에서 가져온 피험자의 관측 데이터 배열(맞춰야 할 타겟)
actual_data = np.array([450, 480, 510, 460, 490])

# 2. 알고리즘에게 던져줄 목적함수(objective function: 알고리즘이 특정 파라미터(정답 후보)를 입력했을 때, 그것이 "얼마나 좋은/나쁜 상태인지"를 하나의 숫자로 평가해주는 수학공식) 정의
# 이 함수는 파라미터 추측값(params)을 입력받아, 그에 따른 SSE 벌점을 뱉어내는 샌드백 역할을 함.
# 최적화 알고리즘의 유일한 목적은 이 함수가 뱉어내는 벌점을 최소(minimize)로 만드는 params를 찾는 것임.
def objective_function(params):
    a_guess = params[0] # 알고리즘이 찔러보는 첫 번째 파라미터(임계값 a)
    
    # 추측한 a 값으로 예측 데이터 배열을 찍어냄(단순화를 위해 100 곱하고 250 더하는 선형식으로 모사)
    simulated_data = a_guess * 100 + 250 
    
    # 예측 데이터와 실제 데이터 간의 오차 제곱합(SSE) 벌점을 무자비하게 계산함
    sse = np.sum(np.square(actual_data - simulated_data))
    return sse # 계산된 벌점을 알고리즘에게 반환함

# 3. 탐색 시작점(초기 추측치) 설정
# 안대 낀 등산객을 산의 어느 좌표에 떨어뜨릴 것인가?(대충 a=1.0 쯤일 것이라고 던져줌)
initial_guess = [1.0]

# 4. 최적화 알고리즘(minimize) 가동!
# 목적함수와 시작점(initial_guess)을 던져주고, 
# Nelder-Mead 방식으로 알아서 내리막길을 찾아가라고 명령함
print("-> 알고리즘이 산등성이를 더듬으며 스스로 최소 오차점을 찾아 굴러 내려감...\n")
result = minimize(objective_function, initial_guess, method='Nelder-Mead')

# 5. 알고리즘 탐색 완료 및 결과 브리핑
# result.x 안에는 알고리즘이 최종적으로 도달한 가장 깊은 골짜기의 파라미터 좌표가 들어 있음
optimal_a = result.x[0]

print("[자동 탐색 종료] 알고리즘 보고서:")
print(f"-> 며칠이 걸릴 그리드 서치 대신, 알고리즘이 단 몇 밀리초 만에 찾아낸 최적의 파라미터 a = [{optimal_a:.2f}] 임!")
print(f"-> 이때 알고리즘이 깎아낸 최소 오차(SSE) 극소값(오차가 도달할 수 있는 가장 밑바닥)은 {result.fun:.2f} 임!")
print("-> 모델 피팅(model fitting)의 최종 진화 형태를 완벽히 코드로 구현함.")
=== [실습 2-3] 스마트한 지팡이: scipy를 이용한 자동 파라미터 최적화 ===
-> 알고리즘이 산등성이를 더듬으며 스스로 최소 오차점을 찾아 굴러 내려감...

[자동 탐색 종료] 알고리즘 보고서:
-> 며칠이 걸릴 그리드 서치 대신, 알고리즘이 단 몇 밀리초 만에 찾아낸 최적의 파라미터 a = [2.28] 임!
-> 이때 알고리즘이 깎아낸 최소 오차(SSE) 극소값(오차가 도달할 수 있는 가장 밑바닥)은 2280.00 임!
-> 모델 피팅(model fitting)의 최종 진화 형태를 완벽히 코드로 구현함.

8주차 요약 및 다음 주 예고

[그림 9] 파라미터 탐색 공간과 최적화의 궤적

  • 요약
    • 모델 피팅: 데이터를 생성하는 장난감 시뮬레이션을 넘어, 실제 피험자의 관측 데이터로부터 두개골 속의 인지 파라미터(\(a, v, T_{er}\))를 정량적으로 끄집어내는 역방향 추론.
    • 이를 달성하기 위해 우리는 모델 예측과 현실의 차이를 가혹하게 평가하는 오차 제곱합(SSE) 최소화 기법 그리고 현재의 데이터가 등장할 확률을 극대화하는 최대우도추정(MLE) 기법의 철학을 체화함.
    • 컴퓨터의 연산력을 빌려 파라미터 공간의 격자를 무식하게 훑는 그리드 서치와, 미분과 기울기를 통해 스스로 똑똑하게 골짜기를 내려가는 자동 최적화 알고리즘을 파이썬으로 구현하여 블랙박스를 해독하는 데 성공함.
  • 다음 주 예고
    • 다음 주 9주차(5/1)는 노동자의 날로 휴강임.
    • 그 다음 주 10주차(5/8)에는 인지심리학의 베스트셀러 주의와 간섭: Stroop 과제 모델링을 다룸.
    • 우리가 배운 DDM 모델의 표류율(\(v\)) 파라미터가 빨간색 잉크로 쓰인 ’초록’이라는 글자 앞에서 어떻게 신경학적 충돌을 일으키고 힘을 깎아 먹는지, 전두엽의 강력한 인지 통제 매커니즘을 컴퓨터로 직접 시뮬레이션해볼 예정임(학기 중반 프로그래밍 과제 대비 필수!)

[그림 10] 넥스트 스텝: Stroop 충돌과 전두엽의 인지 통제

Footnotes

    • 해석적 해: 수학적인 방정식이나 공식을 풀어서 연필과 종이만으로 한 번에 딱 떨어지게 구하는 완벽한 정답.
      • 비유: [근의 공식] vs [숫자 때려 맞히기] \(\Rightarrow\) 방정식: \(x^2 - 4 = 0\)
      • 해석적 해: 우리는 중학교 때 배운 수학 지식(이항 및 루트)을 이용해 \(x^2 = 4\), 따라서 \(x = 2\) 또는 \(x = -2\)라는 것을 1초 만에 완벽한 수식으로 찾아냄. 이것이 해석적 해를 구한 것.
      • 수치적 해(numerical solution, 해석적 해의 반대말): 만약 공식을 모른다면? \(x\)에 1을 넣어보고(결과 -3), 3을 넣어보고(결과 5), “아, 1과 3 사이인가 보다” 하고 2를 넣어봐서(결과 0) 정답을 찾음. 컴퓨터가 수만 번의 시도를 통해 정답에 가까워지는 이 방식이 수치적 해임.
      • 인지 모델링(AI)에서의 의미: 딥러닝이나 표류확산 모델 같은 복잡한 시스템은 파라미터가 너무 많고 비선형적으로 얽혀 있어서, 근의 공식처럼 한 번에 정답을 뱉어내는 마법의 수식(해석적 해)이 우주상에 존재하지 않음. 따라서 컴퓨터의 무식한 연산력을 빌려 일일이 대입해보는 ‘그리드 서치’나, 지팡이로 더듬으며 내려가는 ’경사하강법’ 같은 수치적 방법을 어쩔 수 없이 사용하는 것.
    ↩︎
    • 전역 최적해: 탐색할 수 있는 전체 공간(우주 전체)을 통틀어서 절대적으로 가장 완벽한 단 하나의 최고(또는 최저)의 정답.
      • 비유: [지구상 가장 깊은 바다] vs [우리 동네 웅덩이] \(\Rightarrow\) 모델의 오차(SSE 벌점)를 최소화하기 위해 안대를 낀 등산객이 산을 내려간다고 상상해보자. 발바닥의 감각만 믿고 무조건 ’내리막길’로만 걸어감.
      • 지역 최적해(local minimum): 한참을 내려가다 보니 사방이 오르막인 평평한 곳에 도달함. 안대 낀 등산객은 “아, 오차를 0으로 만드는 바닥을 찾았다!”라고 기뻐함. 하지만 안대를 벗어보니, 그곳은 진짜 산 밑바닥이 아니라 산 중턱에 파인 작은 웅덩이(지역 최적해)였음.
      • 전역 최적해(global minimum): 산 중턱의 웅덩이들을 모두 무시하고, 한라산 전체 지형을 샅샅이 뒤져서 찾아낸 진짜 가장 고도가 낮은 절대적인 밑바닥임. 전체 세계(global)에서 단 하나뿐인 완벽한 정답.
      • 인지 모델링(AI)에서의 의미: 경사하강법 같은 똑똑한 자동탐색 알고리즘은 아주 빠르지만, 가끔 운이 나쁘면 시작위치에 따라 ’동네 웅덩이(지역 최적해)’에 빠져서 거기가 정답인 줄 착각하고 탐색을 멈춰버리는 치명적인 단점이 있음. 반면에 무식하게 바둑판을 전부 뒤지는 그리드 서치는 시간이 매우 오래 걸리지만, 모든 땅을 다 밟아보기 때문에 무조건 ’전역 최적해’를 찾아낸다는 강력한 보장이 있음.
    ↩︎