인지신경과학 4주차_인공 뉴런의 구현: 활성화 함수, perceptron 구조, XOR 문제

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

Published

2026년 3월 27일 금요일 4-5교시

1부: 인공 뉴런의 기초와 선형 대수학

1.1. 인지 모델링과 Python 환경

  • 1. 이론적 설명: 이번 주부터는 뇌의 인지과정을 수학적 공장으로 바꾼 ’인공 신경망’을 파이썬 코드로 직접 밑바닥부터 짜보는 실습을 병행함.
  • 2. 심화 해설: 현대의 거대한 인지 모델(수백억 개의 파라미터를 가진 신경망)을 인간의 손으로 일일이 계산하는 것은 불가능함. 파이썬은 내부적으로 C언어 기반의 초고속 행렬 연산 엔진(Numpy 등)을 품고 있어, 수백만 개의 뇌세포 덩어리를 동시에 연산하는 ’병렬 분산 처리(PDP)’를 컴퓨터 상에서 완벽히 구현해 냄.
  • 3. 비유: 인지심리학 이론이 “건물을 이렇게 지어야 한다”는 거대한 ’건축 설계도’라면, 파이썬과 코딩 지식은 흙을 퍼 나르는 ’포크레인’임. 포크레인 조종법을 모르면, 아무리 훌륭한 뇌 설계도를 그려놓아도 영원히 종이 위에만 머물게 됨.

1.2. 생물학적 뉴런과 인공 뉴런의 매핑(Mapping)

  • 1. 이론적 설명(수학적 1:1 대응): 인간의 뇌는 860억 개의 뉴런이 연결된 네트워크임. 컴퓨터 모델로 흉내 내기 위해 뉴런의 4대 요소를 수학 공식으로 변환함.
    • 수상돌기(Dendrite) \(\rightarrow\) 입력값(\(X\)): 주변 뉴런들로부터 신호를 받아들이는 나뭇가지 형태의 안테나. 컴퓨터에서는 외부로부터 들어오는 ’자극의 크기(숫자 데이터)’로 치환함.
    • 시냅스(Synapse) \(\rightarrow\) 가중치(\(W\)): 신호가 지나가는 틈새 통로. 통과할 때의 저항이나 전달 강도를 의미함. 컴퓨터에서는 입력 신호에 곱해지는 중요도 점수(가중치)로 치환함.
    • 세포체(Soma) \(\rightarrow\) 선형 결합(\(\sum\)): 여러 안테나로 들어온 미세한 전기 신호들을 중심부에 모두 모아 합치는 기관. 컴퓨터에서는 모든 입력값과 가중치를 곱하고 몽땅 더하는 ’덧셈 연산(합산)’으로 치환함.
    • 축삭돌기(Axon) \(\rightarrow\) 활성화 함수(\(f\)): 모인 신호 총합이 특정 폭발선(임계치)을 넘으면, 다음 뉴런으로 신호를 거세게 발사(Fire)하는 출력 케이블. 컴퓨터에서는 최종 합산 값이 일정 수치를 넘었을 때만 1을 내보내는 ’결정 필터(함수)’로 치환함.
  • 2. 심화 해설(복잡성의 추상화): 실제 뇌세포 안에서는 나트륨과 칼륨 이온이 이동하는 등 극도로 복잡한 화학 반응이 일어남. 인공 신경망은 이런 화학적 잡음을 모두 쳐내고, 오직 “신호를 증폭/축소해서 더한다”는 수학적 본질만을 남긴 아름다운 ’추상화(abstraction) 모델’임.
  • 3. 비유(댐과 수로 시스템):
    • 비가 내려 산의 여러 갈래 계곡(수상돌기)에서 물이 모여듦. 각 계곡에서 흘러드는 ’물의 양’이 입력값(X)임.
    • 물이 내려오는 중간에 설치된 ’파이프의 굵기(시냅스)’가 가중치(W)임. 굵은 파이프에서는 물이 콸콸 쏟아지고, 좁은 파이프에서는 쫄쫄 흐름.
    • 여러 파이프를 통과한 물들이 하나의 거대한 댐(세포체)에 모여 합쳐지는 과정이 바로 선형 결합(\(\sum\))임.
    • 댐에 모인 물의 수위가 ’안전 한계선(임계치)’을 넘치는 순간, 비상 수문(축삭돌기)이 확 열리면서 다음 하류로 물을 거세게 쏟아냄(fire). 댐의 수문을 열지 말지 결정하는 이 스위치가 바로 활성화 함수(\(f\))임.

[그림 1] 생물학적 뉴런과 인공 뉴런의 구조적 매핑

1.3. 신경망 구현을 위한 Numpy 패키지와 행렬 내적

  • 1. 이론적 설명(Numpy 라이브러리): 100만 개의 뉴런 연산을 파이썬의 기본 반복문(for)으로 하나하나 계산하면, 컴퓨터는 극심한 병목 현상에 빠짐. numpy 라이브러리는 수백만 개의 데이터를 행렬(matrix)이라는 덩어리로 묶어서, 단 한 줄의 코드로 동시에 계산해 치우는 신경망 구현 최적화 패키지임.
  • 2. 심화 해설(행렬 내적, dot product): 수학적 전문 지식이 없어도 두려워할 필요 없음. 행렬 내적 연산이란 복잡한 미적분이 아니라, “두 데이터의 순서에 맞춰 짝꿍끼리 곱한 뒤, 그 결과들을 모두 몽땅 더하는” 단순 산술의 묶음일 뿐임.
  • 3. 비유(마트 영수증 총액 계산):
    • 내 장바구니에 담긴 물건 배열: [사과 2개, 바나나 3개]
    • 마트의 각 물건 가격표 배열: [1000원, 500원]
    • 내적 연산: (2개 * 1000원) +(3개 * 500원) = 2000 + 1500 = 최종 영수증 금액 3500원
    • 이렇게 두 개의 배열을 통째로 집어넣어, 뉴런 세포체에 꽂힌 뇌의 총 자극량(영수증 총액 3500)을 한 방에 뽑아내는 마법의 주문이 바로 Numpy의 np.dot() 함수임.

[실습 1-1] 단일 뉴런의 선형 결합

  • 파이썬의 f-string 용법: 출력문 텍스트 앞에 f를 붙이고 중괄호 {} 안에 변수 이름을 넣으면, 긴 코드를 작성할 필요 없이 문장 중간에 변수의 계산 결과를 직관적으로 쏙 끼워 넣어 출력해주는 매우 편리한 문법임.
Code
import numpy as np # 선형대수 행렬 고속 연산을 위한 numpy 라이브러리를 np라는 짧은 약칭으로 불러옴.

# 1. 입력값(X), 가중치(W), 편향(B) 세팅하기
x = np.array([0.5, 0.8]) # 눈을 통해 들어온 외부 자극(입력값) 2개를 0.5와 0.8의 크기로 담은 numpy 배열을 생성하여 변수 x에 할당함.
w = np.array([0.4, 0.6]) # 각 자극이 통과할 시냅스 파이프의 굵기(가중치)를 0.4와 0.6으로 설정한 배열을 생성하여 변수 w에 할당함.
b = -0.2                 # 세포체의 흥분 난이도를 결정하는 고유의 편향값(임계치의 반대 개념)을 -0.2로 세팅하여 변수 b에 할당함.

# 2. 뉴런 세포체의 총 합산 과정(선형 결합)
# np.dot(x, w) 함수가 바로 영수증 계산기임. 대응하는 원소끼리 곱한 뒤 더함:(0.5 * 0.4) +(0.8 * 0.6) = 0.68
weighted_sum = np.dot(x, w) # 내적 연산 함수 np.dot을 이용해 x와 w를 계산하고 그 결과를 weighted_sum 변수에 저장함.

# 방금 계산된 내적 총합(0.68)에 편향(b: -0.2)을 마저 더해줌.
# 최종적으로 이 뉴런 세포체(댐)에 모인 순수 전기 신호량은 0.48이 됨.
net_input = weighted_sum + b # 내적 결과(weighted_sum)에 편향(b)을 더하여 세포체의 최종 신호 수위(net_input)를 계산함.

# f-string을 활용하여, 최종 신호량 변수인 net_input을 소수점 둘째 자리(.2f)까지만 깔끔하게 자른 뒤 문자열에 끼워 출력함.
print(f"[단일 뉴런] 세포체 댐에 누적된 최종 신호 수위: {net_input:.2f}") # f-string 포맷팅을 통해 계산된 net_input 값을 화면에 출력함.
[단일 뉴런] 세포체 댐에 누적된 최종 신호 수위: 0.48

[실습 1-2] 다수 뉴런의 동시 연산(행렬 곱)

  • “3개의 뉴런으로 구성된 은닉층이 2개의 자극을 동시에 받아들인다”는 것은, 1명의 고객이 사과와 바나나(2개의 자극)를 장바구니에 담아, 마트 3곳(3개의 뉴런)에서 동시에 계산을 진행하여 최종 영수증 3장(3개의 결과값)을 발급받는 상황과 같음.
  • 1개의 행렬 곱셈 수행 코드로 도출된 (1x3) 배열 결과물은, 활성화 함수 스위치를 누르기 직전 각각의 뉴런 세포체에 꽉꽉 차오른 ‘최종 물의 수위(신호량)’ 3개를 의미함. 반복문 하나 없이 단 한 번의 연산으로 3개 뉴런의 상태를 동시 도출해 냄.

[그림 2] 다수 뉴런의 동시 연산(병렬 처리)

Code
# X 행렬: 1명의 사용자(1개의 행)가 2개의 자극 정보(2개의 열)를 네트워크에 던짐. 크기 =(1x2)
X_matrix = np.array([[0.5, 0.8]]) # [[]] 괄호를 두 번 겹쳐 써서 2차원 형태(1x2)의 행렬로 입력값을 생성해 X_matrix에 저장함.

# W 행렬: 2개의 자극(2개의 행)이 각각 3개의 뉴런(3개의 열)으로 흩어질 때 곱해질 가중치 지도임. 크기 =(2x3)
W_matrix = np.array([[0.1, 0.3, 0.5],  # 첫 번째 입력 자극(0.5)이 3개의 뉴런으로 전달될 때 각각 곱해질 가중치들임.
                     [0.2, 0.4, 0.6]]) # 두 번째 입력 자극(0.8)이 3개의 뉴런으로 전달될 때 각각 곱해질 가중치들임.

# B 행렬: 3개의 뉴런 각각이 갖고 있는 깐깐함의 기준(편향) 3개 모음. 크기 =(1x3)
B_matrix = np.array([-0.1, -0.2, -0.3]) # 3개의 뉴런에 각각 더해질 3개의 편향값을 1차원 배열로 생성하여 B_matrix에 할당함.

#(1x2) 크기의 입력 행렬과(2x3) 크기의 가중치 행렬을 내적 연산하면, 
# 수학의 규칙에 따라 결과적으로(1x3) 크기의 출력 행렬이 완성됨. 여기에 편향 3개를 각각 더해줌.
net_input_matrix = np.dot(X_matrix, W_matrix) + B_matrix # 행렬 곱셈 연산 후 편향 행렬을 브로드캐스팅으로 더해 다중 뉴런 상태를 구함.

print(f"[다중 뉴런] 반복문 없이 3개 뉴런의 상태(수위)를 한 번에 계산한 결과:\n{net_input_matrix}") # 최종적으로 계산된 3개 뉴런의 신호량 행렬을 줄바꿈(\n)과 함께 출력함.
[다중 뉴런] 반복문 없이 3개 뉴런의 상태(수위)를 한 번에 계산한 결과:
[[0.11 0.27 0.43]]

1.4. 활성화 함수(activation function)의 이해

  • 1. 이론적 설명(선형 결합된 신호란?): 앞서 구한 세포체의 신호(0.48 등)는 단순히 곱하기와 더하기만으로 만들어진 1차 방정식(\(y = ax + b\)) 형태의 수학적 수치일 뿐임. 이를 ’선형(linear) 결합된 신호’라고 부름.
  • 2. 심화 해설(기울기 소실과 비선형성의 부여):
    • 세상의 인지 현상은 직선 1개로 딱 떨어지지 않음. 아무리 층을 깊게 수백 겹 쌓아도 덧셈과 곱셈(선형)만 반복하면, 수학의 성질상 결국 거대한 1개의 선형 방정식으로 압축되어 버림. 깊은 층이 제 역할을 하려면 중간중간 1차 방정식의 빳빳한 직선을 확 꺾어버리는 ’수학적 필터’를 끼워 넣어야 함. 이것이 비선형성(non-linearity)을 부여하는 과정임.
    • [기울기 소실 현상이란?]: 과거에 쓰이던 시그모이드 함수는 부드러운 S자 곡선을 지니지만, 층이 100개씩 깊어지는 딥러닝에서 역전파(backpropagation) 학습을 수행할 때 치명적인 버그가 발생함. 1보다 한참 작은 0.25 같은 기울기(미분값)를 수백 번 뒤로 곱해나가다 보면, 기울기가 0에 수렴(\(0.25^{100} \approx 0\))하여 앞쪽 뇌세포들이 아예 학습 신호를 받지 못하고 뇌사 상태에 빠지는 현상을 ’기울기 소실(vanishing gradient)’이라고 부름. 이 끔찍한 현상을 해결하기 위해 등장한 것이 바로 렐루(ReLU) 함수임.
  • 3. 비유(3대 활성화 함수의 특징 비교):
    • 계단 함수(Step): 임계치를 넘으면 확 켜지고(1), 못 넘으면 확 꺼지는(0) ’똑딱이 전등 스위치’임. 직선이 도중에 수직으로 완전히 끊어져 있어 학습을 위한 오차 미분 연산이 불가능함.(현재는 버려진 초창기 뉴런).
    • 시그모이드 함수(Sigmoid): 0과 1 사이를 부드럽게 오가도록 선을 구부린 ’다이얼 조광기’임. 켜지고 꺼지는 강도가 부드러워 미분이 가능해져 학습 시대를 열었지만, 기울기 소실 딜레마에 빠트린 주범이기도 함.
    • 렐루 함수(ReLU): 음수(나쁜 신호)일 때는 꽉 잠겨있고, 양수(좋은 신호)일 때는 튼 수압만큼 비례해서 콸콸 나오는 ’수도꼭지 밸브’임. 양수 구간에서는 직선이라 미분 기울기가 정확히 1임. 1은 수백 번을 계속 곱해도(\(1^{100} = 1\)) 소실되지 않으므로, 아무리 깊은 신경망이라도 오차 신호를 앞쪽 뉴런 끝까지 살려내는 딥러닝계의 구원투수임.

[그림 3] 활성화 함수의 비선형성

[실습 1-3] 활성화 함수 시각화 코딩

  • 주요 시각화 함수 사전 학습
    • np.linspace(start, stop, count): 시작점부터 끝점까지 동일한 간격으로 도화지에 찍을 점 100개를 균일하게 생성해 내는 X축 도구.
    • np.array(): 파이썬의 일반 숫자를 고속 연산이 가능한 넘파이의 특수 배열 덩어리로 변환시킴.
    • np.exp(-x): 자연의 포화 상태를 나타내는 자연 상수 \(e\)의 지수(\(e^{-x}\))를 계산함.(시그모이드 S곡선 설계도).
    • np.maximum(0, x): 들어온 배열 \(x\)의 원소들을 0과 1:1로 비교해서 더 큰 쪽만 살려냄.(렐루 함수에서 음수를 0으로 쳐내버릴 때 사용함).
    • plt.subplots(1, 3): 1개의 줄에 3칸짜리 방을 나누어, 세 가지 그래프를 한 장의 도화지에 나란히 그리기 위해 틀을 잡는 함수.
    • plot(): X점과 Y점 좌표들을 펜으로 쓱 이어서 꺾은선 그래프 곡선을 생성함.
    • set_title(): 도화지 윗부분에 그래프의 이름을 표기함.
    • grid(True): 허공에 떠 있는 선 뒤로 촘촘한 모눈종이(격자선)를 깔아주어 눈금을 읽기 편하게 함.
Code
import matplotlib.pyplot as plt # 도화지에 그림을 그리는 시각화 라이브러리를 plt라는 약칭으로 불러옴.

# X축에 뿌려줄 데이터 100개 생성( -5부터 5 사이를 균일하게 100조각으로 자름 )
x_val = np.linspace(-5, 5, 100) # -5.0부터 5.0 구간을 100개의 점으로 균일하게 나눈 넘파이 배열을 생성해 x_val 변수에 저장함.

# 1. 똑딱이 스위치(계단 함수)
def step_function(x): # x라는 입력을 받아서 계단 함수 결과를 반환하는 사용자 정의 함수를 선언함.
    # 입력된 x가 0을 넘기면 True(1), 못 넘기면 False(0)가 되는 배열을 만들어 정수형(int)으로 반환함.
    return np.array(x > 0, dtype=int) # 불리언 연산(x > 0)의 결과를 정수형(0 또는 1) 배열로 변환하여 그대로 반환함.

# 2. 다이얼 조광기(시그모이드 함수)
def sigmoid(x): # x라는 입력을 받아서 시그모이드 함수 곡선 결과를 반환하는 사용자 정의 함수를 선언함.
    # 부드러운 S자 곡선을 그리는 시그모이드 공식: 1 /(1 + e^(-x)) 를 파이썬 코드로 구현함.
    return 1 /(1 + np.exp(-x)) # 자연 상수 e의 -x승을 계산하여 분모에 더한 후 1을 나누어 결과값을 반환함.

# 3. 딥러닝 수도꼭지(렐루 함수)
def relu(x): # x라는 입력을 받아서 렐루 함수 결과를 반환하는 사용자 정의 함수를 선언함.
    # 입력된 수많은 x값 중, 0보다 큰 양수들은 자기 자신 그대로 살리고, 음수들은 모조리 0으로 뭉개버림.
    return np.maximum(0, x) # x 원소들과 숫자 0을 비교하여 0보다 작은 음수들을 모두 0으로 덮어씌워 반환함.

# 1줄에 3칸이 나뉘어진 넓은 도화지(가로 15, 세로 4 비율)를 준비함. fig는 캔버스 전체, axes는 3개의 방을 의미함.
fig, axes = plt.subplots(1, 3, figsize=(15, 4)) # 1x3 그리드의 서브플롯 영역을 만들고, 전체 크기를 가로 15인치, 세로 4인치로 지정함.

# 첫 번째 방(axes[0])에 파란색으로 계단 함수 펜선을 긋고(plot), 모눈종이(grid)와 제목을 단다.
axes[0].plot(x_val, step_function(x_val), color='blue', linewidth=2) # x_val을 바탕으로 계단 함수 결과값을 파란색 굵은 선(굵기 2)으로 그림.
axes[0].set_title("1. Step Function") # 첫 번째 그래프 상단에 '1. Step Function'이라는 문자열을 제목으로 달아줌.
axes[0].grid(True) # 첫 번째 그래프의 배경에 눈금을 읽기 쉽게 격자 무늬(모눈종이)를 활성화함.

# 두 번째 방(axes[1])에 빨간색으로 시그모이드 함수 펜선을 긋고 세팅함.
axes[1].plot(x_val, sigmoid(x_val), color='red', linewidth=2) # x_val을 바탕으로 시그모이드 결과를 빨간색 굵은 선(굵기 2)으로 그림.
axes[1].set_title("2. Sigmoid Function") # 두 번째 그래프 상단에 '2. Sigmoid Function'이라는 문자열을 제목으로 달아줌.
axes[1].grid(True) # 두 번째 그래프의 배경에 눈금을 읽기 쉽게 격자 무늬(모눈종이)를 활성화함.

# 세 번째 방(axes[2])에 초록색으로 렐루 함수 펜선을 긋고 세팅함.
axes[2].plot(x_val, relu(x_val), color='green', linewidth=2) # x_val을 바탕으로 렐루 함수 결과를 초록색 굵은 선(굵기 2)으로 그림.
axes[2].set_title("3. ReLU Function") # 세 번째 그래프 상단에 '3. ReLU Function'이라는 문자열을 제목으로 달아줌.
axes[2].grid(True) # 세 번째 그래프의 배경에 눈금을 읽기 쉽게 격자 무늬(모눈종이)를 활성화함.

# 3개의 그래프가 서로 영역을 침범하지 않고 깔끔하게 보이도록 여백을 컴퓨터가 알아서 자동 조정해 줌.
plt.tight_layout() # 서브플롯 간의 간격을 조절하여 글자나 축 번호가 서로 겹치지 않게 여백을 최적화함.
plt.show() # 지금까지 설정한 모든 시각화 결과물들을 렌더링하여 사용자 화면에 실제로 띄워 보여줌.

2부: 논리 게이트 구현과 XOR의 딜레마

2.1. 단층 퍼셉트론(single-layer Perceptron)

  • 1. 이론적 설명: 단층 퍼셉트론은 인간의 뇌신경 구조를 아주 단순화시켜, 은닉층(hidden layer) 없이 맨 앞의 ‘입력층’과 맨 끝의 ’출력층’ 단 두 개의 층으로만 다이렉트로 연결한 최초의 인공지능 모델임.
  • 2. 심화 해설: 비록 중간 가공 단계가 없지만, 기계 장치가 인간의 가장 기본적인 논리적 추론인 AND, OR의 사고방식을 오직 가중치(w)와 편향(b)이라는 두 개의 숫자 곱셈/덧셈만으로 수학적으로 흉내 내고 데이터를 분류할 수 있음을 증명한 위대한 발명임.
  • 3. 비유: 바닥에 모래알(데이터)이 흩뿌려져 있을 때, 가위로 단 한 번 ’일직선’으로 싹둑 잘라서 두 개의 그룹(이것은 참이다 그룹 / 이것은 거짓이다 그룹)으로 완벽히 가르는 재단사와 같음.

[그림 4] 단층 퍼셉트론

2.2. 퍼셉트론을 이용한 기본 논리 연산(AND / OR)

  • 1. 이론적 설명: 파이썬을 이용해 직접 가중치와 편향을 코드로 꽂아 넣으면, 이 인공 세포 1개가 컴퓨터 논리 연산인 AND 게이트와 OR 게이트 역할을 완벽히 수행하게 만듦.
  • 2. 심화 해설(편향 조작의 기하학): 퍼셉트론이 2차원 공간 위에 긋는 직선 칼날의 위치는 ‘편향(Bias)’ 값이 결정함.
    • 편향을 낮게(-0.7) 설정하면: 임계치 허들을 넘기 극도로 어려워지므로 직선 칼날이 우측 상단 끄트머리로 밀려나 깐깐한 AND 영역을 자르게 됨.
    • 편향을 높게(-0.2) 설정하면: 임계치 허들을 손쉽게 훌쩍 넘게 되므로 직선 칼날이 좌측 하단으로 내려와 관대한 OR 영역을 넓게 포용하며 자르게 됨.
  • 3. 비유(깐깐한 경비원 vs 관대한 점원): 컴퓨터 세계에서는 데이터 통로(입력 1, 입력 2)로 들어오는 상태값이 무조건 0(거짓) 아니면 1(참)임.
    • AND 게이트: 클럽 입구에서 “입장 티켓(입력 1)”이 확인되고(값 1), “성인 신분증(입력 2)”도 확인되어야만(값 1) 간신히 최종 통과(출력 1)시켜 주는 깐깐한 덩치 경비원 로직임.
    • OR 게이트: 마트 계산대에서 “현금(입력 1)”을 내거나(값 1), “카드(입력 2)”를 내거나(값 1) 둘 중 하나만 참이어도 결제 승인(출력 1)을 띄워주는 관대한 점원 로직임.

[그림 5] 기본 논리 연산의 기하학적 분리

[실습 2-1, 2-2] AND / OR 게이트 코딩

Code
# AND 게이트(깐깐한 경비원 로직)
def AND_gate(x1, x2): # 입력값 x1과 x2를 파라미터로 받아 AND 연산을 수행하는 함수를 정의함.
    x = np.array([x1, x2]) # 두 입력값을 넘파이 배열 덩어리로 묶어 변수 x에 할당함.
    w = np.array([0.5, 0.5]) # 두 가지 조건을 반반의 중요도(0.5)로 확인하겠다는 세팅 배열을 생성하여 변수 w에 할당함.
    
    # [핵심] 편향을 -0.7로 매우 깊게 파버려서 임계치를 뚫기 극도로 어렵게 만듦.
    # 하나만 1이 들어와선 0.5를 못 뚫고, 무조건 둘 다 1이 들어와 1.0이 되어야 통과함.
    b = -0.7 # 매우 낮은 편향값 -0.7을 변수 b에 할당하여 임계 허들을 깐깐하게 설정함.
    
    tmp = np.dot(x, w) + b # 배열 x와 가중치 w를 내적 연산한 뒤 편향 b를 더해 총 신호량을 tmp 변수에 저장함.
    return 1 if tmp > 0 else 0 # 조건문을 사용하여 계산된 tmp가 0보다 크면 1을 반환하고, 그렇지 않으면 0을 반환함.

# OR 게이트(관대한 점원 로직)
def OR_gate(x1, x2): # 입력값 x1과 x2를 파라미터로 받아 OR 연산을 수행하는 함수를 정의함.
    x = np.array([x1, x2]) # 두 입력값을 넘파이 배열 덩어리로 묶어 변수 x에 할당함.
    w = np.array([0.5, 0.5]) # 중요도는 AND 게이트와 동일하게 각각 0.5씩 설정한 배열을 변수 w에 할당함.
    
    # [핵심] 편향을 -0.2로 얕게 파놓아 허들을 낮춤.
    # 둘 중 하나만 1이 들어와 0.5를 획득해도, -0.2를 쉽게 이기고 통과함.
    b = -0.2 # 비교적 얕은 편향값 -0.2를 변수 b에 할당하여 임계 허들을 관대하게 설정함.
    
    tmp = np.dot(x, w) + b # 배열 x와 가중치 w를 내적 연산한 뒤 편향 b를 더해 총 신호량을 tmp 변수에 저장함.
    return 1 if tmp > 0 else 0 # 조건문을 사용하여 계산된 tmp가 0보다 크면 1을 반환하고, 그렇지 않으면 0을 반환함.

print("--- AND Gate 결과 ---") # AND 게이트 실습 결과의 시작을 알리는 구분 문자열을 출력함.
print(f"입력 [1, 1] -> 출력: {AND_gate(1, 1)}") # 오직 둘 다 1일 때만 1 통과 결과를 f-string으로 출력함.
print(f"입력 [0, 1] -> 출력: {AND_gate(0, 1)}") # 나머지는 가차없이 0 반환 결과를 f-string으로 출력함.

print("\n--- OR Gate 결과 ---") # OR 게이트 실습 결과의 시작을 알리는 구분 문자열과 줄바꿈을 출력함.
print(f"입력 [0, 0] -> 출력: {OR_gate(0, 0)}") # 아무것도 안 가져올 때만 0 반환 결과를 f-string으로 출력함.
print(f"입력 [0, 1] -> 출력: {OR_gate(0, 1)}") # 하나만 내밀어도 1 통과 결과를 f-string으로 출력함.
--- AND Gate 결과 ---
입력 [1, 1] -> 출력: 1
입력 [0, 1] -> 출력: 0

--- OR Gate 결과 ---
입력 [0, 0] -> 출력: 0
입력 [0, 1] -> 출력: 1

2.3. 모든 연산의 기초: NAND 게이트

  • 1. 이론적 설명: NAND는 ’Not AND’의 줄임말로, 앞서 배운 AND 게이트가 내린 결론(0과 1)을 모조리 정반대(1과 0)로 뒤집어버리는 반골 기질의 논리임.
  • 2. 심화 해설: NAND가 중요한 이유는 컴퓨터 공학에서 “유니버설 게이트(universal gate)”로 취급받기 때문임. 수학적으로 이 NAND 스위치 단 하나만 무한히 조립해서 가져다 붙여도 세상의 모든 복잡한 인텔 CPU 프로세서와 메모리 칩을 만들어낼 수 있음.
  • 3. 비유(청개구리 할인 이벤트):
    • “주말(입력 1)인가?”와 “공휴일(입력 2)인가?”라는 두 가지 질문(통로)에 대해, 둘 다 “예(값 1, 1)”인 날만 제외하고 무조건 할인을 해준다(출력 1).
    • 두 악재 조건이 최악으로 겹치는 상황(주말이면서 공휴일, 즉 입력 1의 값이 1이고, 입력 2의 값도 1인 상황)에만 할인 작동을 거부(출력 0)하는 청개구리 논리임.

[그림 6] 모든 컴퓨터 연산의 기초 블록

[실습 2-3] NAND 게이트 코딩

Code
# NAND 게이트(청개구리 로직)
def NAND_gate(x1, x2): # 입력값 x1과 x2를 파라미터로 받아 NAND 연산을 수행하는 함수를 정의함.
    x = np.array([x1, x2]) # 두 입력값을 넘파이 배열 덩어리로 묶어 변수 x에 할당함.
    
    # AND 게이트의 가중치와 편향 부호(+, -)를 거꾸로 싹 뒤집어주면 반골 기질이 완성됨.
    w = np.array([-0.5, -0.5]) # AND 게이트와 정확히 부호가 반대인 음수 가중치 배열을 변수 w에 할당함.
    b = 0.7                    # AND 게이트의 음수 편향(-0.7)을 양수(+0.7)로 완전히 뒤집어 변수 b에 할당함.
    
    tmp = np.dot(x, w) + b # 배열 x와 가중치 w를 내적 연산한 뒤 편향 b를 더해 총 신호량을 tmp 변수에 저장함.
    return 1 if tmp > 0 else 0 # 조건문을 사용하여 계산된 tmp가 0보다 크면 1을 반환하고, 그렇지 않으면 0을 반환함.

print("\n--- NAND Gate 결과 ---") # NAND 게이트 실습 결과의 시작을 알리는 구분 문자열을 출력함.
print(f"입력 [1, 1] -> 출력: {NAND_gate(1, 1)}") # 두 조건이 다 차오르는 얄미운 순간에만 작동을 거부(0)하는 결과를 출력함.
print(f"입력 [1, 0] -> 출력: {NAND_gate(1, 0)}") # 조건이 하나라도 비면 무조건 1을 뱉어내는 결과를 출력함.

--- NAND Gate 결과 ---
입력 [1, 1] -> 출력: 0
입력 [1, 0] -> 출력: 1

2.4. 선형 분리 불가능성: XOR 문제의 비극

  • 1. 이론적 설명: 배타적 논리합(XOR)은 “두 입력값이 서로 다를 때만 1을 반환하고, 두 입력이 같을 때는 0을 뱉어내는” 모순적인 편가르기 논리 구조임. 인간의 뇌로는 처리하기 아주 쉬운 일상적인 결정 논리지만, 초기 인공지능에게는 이 간단한 논리가 치명적인 ’비극’이자 ’딜레마’로 작용함.
  • 2. 심화 해설(수학적 도구의 한계):
    • 기호주의 인공지능의 거두 마빈 민스키(Marvin Minsky)는, 퍼셉트론 세포 1개의 내부 수학 공식인 \(w_1x_1 + w_2x_2 + b = 0\) 이, 중학교 때 배우는 2차원 평면의 직선 방정식 \(y = ax + b\) 와 완전히 똑같은 껍데기임을 수학적으로 증명함.
    • 즉, 이 모델은 태생적으로 도화지 위에 ‘단 1개의 빳빳한 직선’만 그을 수 있게 속박되어 있다는 뜻임. 그런데 XOR 데이터를 좌표계에 그려보면 참(1)과 거짓(0)을 의미하는 점들이 ’X자 대각선’으로 엇갈려 찍히게 됨. 아무리 용을 써도 1개의 직선으로는 대각선으로 엇갈린 두 그룹을 완벽히 양분할 수 없었으며, 기계가 인간의 가장 기초적인 일상 논리조차 수학적으로 분리해내지 못한다는 이 치명적 조롱 하나로 인해 ’1차 AI 겨울’이 도래함.
  • 3. 비유(수프와 샐러드의 딜레마와 한 번의 가위질): * 인간은 “수프와 샐러드 중 하나만 고르세요”라는 배타적(XOR) 조건을 머릿속에서 유연하게 처리하지만, 이를 식당 테이블(2차원 도화지) 위에 점으로 찍어 기계의 상황으로 바꿔보면 왜 딜레마인지 알 수 있음.
    • 수프(0), 샐러드(0): 아무것도 안 시킴 \(\rightarrow\) 거부(빨간 점).
    • 수프(1), 샐러드(0): 수프만 시킴 \(\rightarrow\) 허용(파란 점).
    • 수프(0), 샐러드(1): 샐러드만 시킴 \(\rightarrow\) 허용(파란 점).
    • 수프(1), 샐러드(1): 둘 다 시킴 \(\rightarrow\) 거부(빨간 점).
    • 종이 위에 대각선으로 엇갈리게 찍힌 이 4개의 점을 향해, ’가위질 단 한 번(직선 1개)’만 해서 파란 점 그룹과 빨간 점 그룹을 완벽하게 나누어보라는 불가능한 미션을 기계에게 요구했던 것.

[그림 7] XOR(배타적 논리합) 문제의 비극

[실습 2-4] 단층 퍼셉트론의 XOR 실패 증명

Code
# 억지로 가중치를 요리조리 바꿔가며 XOR을 풀어보려는 단층 모델의 비참한 시도
def XOR_attempt(x1, x2): # 입력값 x1과 x2를 받아 단층 모델로 XOR 연산을 억지로 시도하는 함수를 정의함.
    x = np.array([x1, x2]) # 두 입력값을 넘파이 배열 덩어리로 묶어 변수 x에 할당함.
    
    # 컴퓨터가 아무리 가중치와 편향을 임의로 바꿔 도화지 위에서 직선을 옮겨보아도...
    w = np.array([0.5, 0.5]) # 임의로 가중치 0.5를 두 개 가진 배열을 생성하여 변수 w에 할당함.
    b = -0.5                 # 직선을 이동시키기 위해 임의의 편향값 -0.5를 지정하여 변수 b에 할당함.
    
    tmp = np.dot(x, w) + b # 배열 x와 가중치 w를 내적 연산한 뒤 편향 b를 더해 총 신호량을 tmp 변수에 저장함.
    return 1 if tmp > 0 else 0 # 계산된 tmp 결과에 따라 1 또는 0을 반환하는 계단 함수 역할을 수행함.

print("\n--- 단층 퍼셉트론의 XOR 시도(필연적 실패) ---") # XOR 시도 결과의 시작을 알리는 구분 문자열을 출력함.
print(f"입력 [0, 1] -> 기댓값: 1 | 출력: {XOR_attempt(0, 1)}") # 0,1 입력 시 1이 나와야 하므로 이는 맞게 동작하는 척 함.

# 아래 결과는 기댓값(0)과 달리 오답(1)이 무조건 튀어나옴. 1개 칼질(직선)의 수학적 한계임.
print(f"입력 [1, 1] -> 기댓값: 0 | 출력: {XOR_attempt(1, 1)}") # 하지만 1,1 입력 시 0이 나와야 하는데 1이 나와버려 결국 실패함을 증명함.

--- 단층 퍼셉트론의 XOR 시도(필연적 실패) ---
입력 [0, 1] -> 기댓값: 1 | 출력: 0
입력 [1, 1] -> 기댓값: 0 | 출력: 1

2.5. 다층 퍼셉트론(MLP)을 통한 XOR 딜레마 돌파

  • 1. 이론적 설명: 들어온 입력 데이터를 섣불리 곧바로 결론(출력층) 내리지 않고, 중간에서 정보를 한 번 새롭게 가공하는 ’은닉층(hidden Layer)’이라는 룸을 하나 더 끼워 넣는 발상의 전환이 AI 겨울을 끝냈음. 앞서 만든 단일 게이트(NAND, OR, AND)들을 레고 블록처럼 2층으로 조립하면 XOR 난제를 완벽히 해결함.
  • 2. 심화 해설(어떻게 단일 게이트 조립이 공간을 고차원으로 구부리는가?):
    • 가위질 한 번(직선 1개)으로는 불가능하지만, 1층(은닉층)에서 NAND 칼질과 OR 칼질을 각각 한 번씩 수행하여 특징이 걸러진 새로운 데이터 힌트 2개를 획득함.
    • 이를 2층(출력층)의 마지막 AND 게이트가 종합하여 판단하면, 마치 도화지에 두 번 가위질을 해서 십자 모양으로 도형을 완벽히 오려내는 것과 같은 엄청난 기하학적 돌파 효과를 거둠.
  • 3. 비유(1차원 공간을 허공으로 붕 띄워 올리기):
    • 1차원의 빳빳한 줄(선) 위에 파랑-빨강-파랑 점이 교대로 줄 서 있다고 상상함. 이 1차원 공간에서는 점 한 번 찍어(분할점) 파란색과 빨간색을 분리할 길이 없음.
    • 그러나 은닉층이 이 점들의 위치(데이터 특징)를 억지로 ‘제곱’ 연산시켜 버리면? 1차원 바닥에 있던 점들이 2차원 허공으로 붕 솟아올라 ’U자형 포물선’이 됨!
    • 이제 바닥 쪽에 남은 빨간 점과, 허공으로 솟아오른 파란 점들 사이에 가로로 ’평평한 판자(직선)’를 쓱 끼워 넣으면 두 그룹이 완벽히 잘라져 분리됨! 이것이 은닉층이 수행하는 딥러닝 100만 차원 공간 변환의 본질임.

[그림 8] 은닉층과 다층 퍼셉트론

[실습 2-5] 은닉층을 활용한 XOR 해결 코딩

Code
# 앞서 우리가 정성스레 만든 3개의 1층짜리 게이트(NAND, OR, AND)를 2층 구조로 조립함.
def XOR_gate_with_MLP(x1, x2): # 입력값 x1과 x2를 받아 은닉층이 포함된 다층 구조로 XOR 연산을 수행하는 함수를 정의함.
    # 1. 은닉층 연산 단계: 원본 데이터가 NAND 방과 OR 방 두 갈래로 들어가 새로운 특징(힌트)을 추출함.
    hidden_node_1 = NAND_gate(x1, x2) # x1과 x2를 NAND 게이트에 통과시켜 도출된 결과를 첫 번째 은닉 노드의 힌트(hidden_node_1)로 저장함.
    hidden_node_2 = OR_gate(x1, x2)   # x1과 x2를 동시에 OR 게이트에도 통과시켜 두 번째 은닉 노드의 힌트(hidden_node_2)로 저장함.
    
    # 2. 출력층 연산 단계: 은닉층이 가공해 던져준 힌트 2개를 AND 방이 최종적으로 종합함.
    final_output = AND_gate(hidden_node_1, hidden_node_2) # 앞서 가공된 두 힌트 변수를 AND 게이트의 입력으로 넣어 최종 분리 결과를 도출함.
    
    return final_output # AND 게이트를 통과하여 도출된 최종 XOR 연산 결과(0 또는 1)를 반환함.

print("\n--- 다층 퍼셉트론을 통한 XOR 완벽 해결 ---") # MLP를 이용한 XOR 해결 결과의 시작을 알리는 문자열을 출력함.
print(f"입력 [0, 0] -> 출력: {XOR_gate_with_MLP(0, 0)}") # 입력이 0,0일 때 기댓값인 0이 정상적으로 출력됨을 확인하여 화면에 뿌려줌.
print(f"입력 [1, 0] -> 출력: {XOR_gate_with_MLP(1, 0)}") # 입력이 1,0일 때 기댓값인 1이 정상적으로 출력됨을 확인하여 화면에 뿌려줌.
print(f"입력 [0, 1] -> 출력: {XOR_gate_with_MLP(0, 1)}") # 입력이 0,1일 때 기댓값인 1이 정상적으로 출력됨을 확인하여 화면에 뿌려줌.

# 드디어 모순점이었던 둘 다 참일 때 0을 정확하게 걸러냄! 기하학적 한계를 정복함.
print(f"입력 [1, 1] -> 출력: {XOR_gate_with_MLP(1, 1)}") # 직선 1개로는 불가능했던 1,1 입력 시의 결과가 정확하게 0으로 도출됨을 증명해 출력함.

--- 다층 퍼셉트론을 통한 XOR 완벽 해결 ---
입력 [0, 0] -> 출력: 0
입력 [1, 0] -> 출력: 1
입력 [0, 1] -> 출력: 1
입력 [1, 1] -> 출력: 0