데이터에서 학습
학습: 훈련데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것
손실함수: 신경망이 학습할 수 있도록 해주는 지표
- 손실함수의 결괏값을 가급적 작게 만드는 기법 → 함수의 기울기를 활용하는 경사법
신경망의 특징: 데이터를 보고 학습할 수 있다 = 가중치 매개변수의 값을 데이터를 보고 자동으로 결정한다는 뜻
데이터 주도 학습
기계학습의 중심에는 데이터가 존재
사람이 생각한 알고리즘
- 사람의 경험과 직관을 단서로 시행착오를 거듭하며 일을 진행 → 사람중심접근
사람이 생각한 특징 + 기계학습
- 사람의 개입을 최소화하고 수집한 데이터로부터 패턴을 찾음 → 기계학습(데이터가 이끄는 접근)
예를 들어보자, 만약 당신이라면 5를 분류하는 프로그램을 고안할 때 어떤 알고리즘을 사용할 것 같은가?
- 사람이라면, 그 안에 숨겨진 규칙을 찾아 어렵지 않게 인식 가능.
- But, 숨은 규칙성을 명확한 로직으로 풀기가 쉽지 않다.
그럴 때 사용하는게 바로 이미지에서 특징을 추출하고 그 특징의 패턴을 기술로 학습하는 방법
이미지 → 벡터로 변환 → 변환된 벡터를 활용해 SVM, KNN(지도학습)으로 학습 가능
- But, 이미지 → 벡터로 변환할 때 사용하는 특징은 여전히 ‘사람’이 설계
- 문제에 적합한 특징을 사람이 설계하지 않으면 좋은 결과를 얻을 수 없음

신경망
- 이미지를 있는 그대로 학습
- 신경망은 이미지에 포함된 중요한 특징까지도 기계가 스스로 학습
신경망의 장점
- 데이터를 온전히 학습하고 주어진 문제의 패턴을 발견하려 시도
- 모든 문제를 주어진 데이터 그대로를 입력 데이터로 활용해 ‘end-to-end’로 학습 가능
훈련데이터와 시험데이터
훈련데이터만 사용하여 학습하면서 최적의 매개변수 찾음
→ 이후 시험데이터를 사용해 훈련한 모델의 실력 평가
WHY???? 왜 나누는가?????
아직보지 못한 데이터(훈련 데이터에 포함되지 않는 데이터)로도 문제를 올바르게 풀어내는지 확인하기 위해서!
(시험데이터는 학습하는데 사용되지 않았기 때문)
- 오버피팅(과적합): 한 데이터셋에만 지나치게 최적화된 상태

- x축: 모델 복잡도 (Model Complexity)
- y축: 예측 오차 (Prediction Error)
- 파란색: 훈련 데이터 에러 (Training Error)
- 빨간색: 테스트 데이터 에러 (Test Error)
- 왼쪽(High Bias / Low Variance): 모델이 너무 단순해서 패턴을 제대로 학습하지 못함(과소적합)
- 오른쪽(Low Bias/High Variance): 훈련 데이터에 완벽하게 맞춰서 노이즈까지 다 외워버림(과대적합=과적합)
→ 모델 복잡도가 증가할수록 훈련 오차는 계속 감소하지만 일정 지점을 넘어서면 테스트 오차가 증가하는데,
→ 이 구간이 바로 오버피팅(과적합)
오버피팅을 피하는 것이 기계학습의 중요한 과제이기도 함
손실함수
신경망 학습에서 사용하는 지표로 현재의 신경망이 훈련 데이터를 얼마나 잘 처리하지 못하느냐를 나타냄
- 성능의 나쁨과 좋음 중 어느 쪽을 지표로 삼아도 본질적으로 수행하는 일은 다르지 않음
일반적으로는 오차제곱합과 교차 엔트로피 오차를 사용.
오차제곱합
각 원소의 출력(추정 값)과 정답 레이블(참 값)의 차를 제곱한 후, 그 총합을 구함

- yk: 신경망의 출력(신경망이 추정한 값)
- tk: 정답 레이블
- k: 데이터의 차원 수
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
위 배열들의 원소는 첫 번째 인덱스부터 순서대로 숫자 ‘0’, ‘1’, ‘2’, … 일 때의 값
- y(신경망의 출력) = 소프트맥스 함수의 출력(각 이미지가 인덱스(숫자)일 확률)
- t(정답 레이블) = 정답 인덱스 원소는 1, 그 외는 0(원-핫인코딩 되어있다고 생각하면 됨)
💡 근데, 오차제곱합에 왜 1/2을 붙일까? WHY?
먼저, 손실함수는 단순히 값을 계산하려고 사용하는 것이 아니라 E 값을 줄이기 위해 사용된다.
즉, E의 값이 작아질수록 예측이 정답에 가까워진다는 의미.
그럼 어떻게 줄여야할까?
- 모델은 가중치 w를 바꾸면서 이 손실값 E를 계속 줄여나간다. 그런데 문제는 “어느 방향으로 바꿔야 줄어드는지” 모른다는 것.
- 그래서 여기서 나오는게 바로 바로 ~ “미분(기울기)!” ∂w/∂E 이 값은 손실이 얼마나, 어떤 방향으로 변하는지 알려주는 값이다.
즉, 손실함수는 단순히 오차를 측정하는 것이 아니라, 미분을 통해 오차를 줄이는 방향을 찾기 위해 사용된다.
이때 계산을 더 간단하게 하기 위해 손실함수에 1/2를 붙인다.
오차제곱합은 [각 원소의 출력(추정 값)과 정답 레이블(참 값)의 차를 제곱한 후, 그 총합을 구함] 이라고 했는데,
해당 말을 그대로 수식으로 풀어보면 E=(y−t)^2 값이 된다.
이 식을 미분하면 dy/dE=2(y−t) 앞에 2가 붙는다.
그런데 이 앞의 2는 계산을 복잡하게 만들 뿐, 의미는 전혀 없다.
아 그래서 왜 붙였는데요??????? → 어차피 미분할 거라서, 계산 편하게 하려고 미리 2를 없애기 위함!
def sum_squares_error(y, t):
return 0.5 * np.sum((y-t)**2)
t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0] # 정답은 '2'
# ex1) '2'일 확률이 가장 높다고 추정했을 때(0.65)
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
sum_squares_error(np.array(y), np.array(t))
# 0.0975000000000031
# ex2) '7'일 확률이 가장 높다고 추정했을 때
y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
sum_squares_error(np.array(y), np.array(t))
# 0.5975000000000003
- ex1: 정답레이블 = 2 & 신경망의 출력도 2에서 가장 높은 경우
- ex2: 정답레이블 = 2 & 신경망의 출력은 7에서 가장 높은 경우
ex1 < ex2 (오차제곱합 기준으로 ex1 추정결과가 (오차가 더 작으니) 정답에 가까울 것으로 판단)
- 값이 작다 → 덜 틀림 (잘 맞춤)
- 값이 크다 → 많이 틀림
교차 엔트로피 오차

- log는 밑이 e인 자연로그(loge)
- yk: 신경망의 출력
- tk: 정답 레이블(원-핫인코딩)
정답 레이블이 원-핫인코딩 되어있기 때문에 정답에 해당하는 인덱스 원소만 1, 나머지는 0

그래서 실제 계산은 이렇게 됨! 정답 위치의 예측값만 본다는 뜻.
즉, 교차 엔트로피 오차는 정답일 때의 출력이 전체 값을 정하게 됨
💡 근데 그러면 왜? 자연로그를 쓰는거죠?? WHY??
log는 확률이 작아질수록 손실을 급격히 키워서, 틀린 예측을 더 강하게 학습시키기 위한 장치!
- y=0.6 → 꽤 맞춘 경우
- y=0.1 → 거의 틀린 경우
근데 이걸 그냥 비교하게 되면 차이가 0.5로 크지 않다.
log를 사용하면?
- −log(0.6)≈0.51
- −log(0.1)≈2.30
둘 사이의 차이가 커진다.
엥 근데 별로 안큰데요?
But, 우리가 봐야 하는건 단순한 숫자의 절대값이 아니라! 변화율(기울기)!
기울기는 0에 가까워질수록 기울기가 굉장히 커짐 → 빠르고, 강하게 학습
교차 엔트로피는 정답 클래스의 확률이 낮을수록 큰 기울기를 만들어, 틀린 예측을 빠르게 보정하도록 유도
def cross_entropy_error(y, t):
delta = 1e-7
return -np.sum(t * np.log(y + delta))
위의 코드를 보면 np.log를 계산할 때 아주 작은 값인 delta를 더해줌. WHY?
교차 엔트로피는 E=−log(정답 클래스의 y) 정답 클래스의 확률만 사용한다.
근데 만약 모델이 정답 클래스에 대해 0이라고 예측한다면? log(0)=−∞ 계산이 불가능하고 손실값이 무한대가 되어버린다.
log에 아주 작은 값을 넣으면 매우 큰 손실값으로 처리되기 때문에 delta는 log(0)으로 인한 무한대 값을 방지하기 위해 추가됐다. 또, 모델이 완전히 틀린 경우에도 안정적으로 큰 손실값을 계산하도록 한다.
* 교차 엔트로피 함수를 사용한 결과는 오차제곱합의 판단과 일치했음
미니배치 학습
훈련데이터 N개 모두에 대한 평균손실함수를 구하는 방법

→ 기존의 교차 엔트로피 오차를 각 데이터에 대해 계산한 뒤, 모두 합한 값을 데이터 개수 N으로 나누어 평균을 구한 것
훈련 데이터 개수와 관계없이 통일된 지표 계산 가능
🤔 엥 근데 만약 데이터 개수가 한 9만개 정도 되면요?….
→ 이럴 경우 데이터 일부를 추려 전체의 ‘근사치’로 이용.
미니배치: 신경망 학습에서 훈련 데이터로부터 일부만 골라 학습을 수행하는 것 ex) 9만장의 훈련 데이터 중에서 100장을 무작위로 뽑아 100장만을 사용해 학습하는 것
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
(x_train, t_train), (x_test, t_test) = \\
load_mnist(normalize=True, one_hot_label=True)
print(x_train.shape) # (60000, 784)
print(t_train.shape) # (60000, 10)
- 훈련 데이터 60,000개, 입력 데이터는 784열(MNIST 이미지 = 28 × 28)
- 정답 레이블은 10줄짜리 데이터(10개의 값(벡터))
- [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
batch_mask = np.random.choice(x_train.shape[0], 10) # 10은 배치사이즈
- np.random.choice(60000,10)은 무작위로 10개를 골라냄
- 무작위로 선택한 이 인덱스 사용해서 미니배치 뽑아내기만 하면 됨
💡 근데 그러면 라벨별로 비율이 안맞을 것 같은데요?
→ 맞다. 미니배치는 무작위로 선택되기 때문에 라벨 분포가 깨질 수 있다.
왜냐하면 무작위 랜덤으로 뽑기 때문에, 어떤 배치는특정 숫자(예: 2)가 많을 수도 있고어떤 숫자(예: 7)는 하나도 없을 수도 있다.
하지만 문제되지 않는다. 그 이유는 미니배치는 한 번이 아니라 여러 번 반복해서 사용되기 때문이다.
각 배치는 일시적으로 불균형할 수 있지만, 랜덤하게 반복 샘플링을 수행하면 전체적으로는 원래 데이터의 분포를 평균적으로 잘 반영하게 된다.
(배치용) 교차 엔트로피 오차 구현하기
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0]
return -np.sum(t * np.log(y + 1e-7)) / batch_size
- y: 미니배치로 들어온 입력 데이터를 모델에 넣었을 때 나온 예측값
- t: 미니배치 데이터에 대응하는 정답 레이블
np.log(np.arange(batch_size), t)
두 값을 이용해 교차 엔트로피 손실을 계산한 뒤, 배치 크기만큼 나누어 평균 손실을 구한다.
즉, 모델이 낸 답(y) 중에서 정답 위치(t)의 확률만 뽑아서 틀린 정도를 계산하고 평균 계산
왜 손실함수를 설정하는가?
우리의 궁극적인 목적은 ‘높은 정확도’를 끌어내는 매개변수 값을 찾는건데, ‘정확도’라는 지표를 놔두고 ‘손실함수의 값’이라는 우회적인 방법을 택하는 이유가 뭘까?
정확도를 지표로 삼아서는 안되는 이유는 미분 값이 대부분의 장소에서 0이 되어 매개변수를 갱신할 수 없기 때문.
한 신경망이 100개의 훈련 데이터 중 32장을 올바로 인식할 경우 → 정확도 32%
만약 정확도가 지표라면 가중치 매개변수의 값을 약간 바꾸더라도 → 정확도 32%
-> 정확도가 개선된다 하더라도 연속적인 변화(32.0123%)가 아니라 33%나 34%처럼 불연속적인 띄엄띄엄한 값으로 바뀌게 된다.

수치 미분
미분이란?
함수의 변화율(기울기)을 구하는 것

- 어떤 점에서 함수가 얼마나 빠르게 변하는지를 의미
- 즉, 기울기 = 변화량 / 변화한 정도
- 평균 변화율 → 두 점 사이 기울기
- 미분 → 한 점에서의 기울기
h를 점점 0에 가깝게 만들면 → 한 점에서의 순간 변화율(= 미분)
수치 미분 (Numerical Differentiation)
컴퓨터는 극한 계산이 어려움 → 근사값으로 계산
def numerical_diff(f, x):
h = 1e-50
return (f(x+h) - f(x)) / h
❗ h를 너무 작게 하면?????
np.float32(1e-50) → 0.0
컴퓨터에서는 너무 작은 값은 0으로 처리됨 → 계산이 깨짐 (반올림 오차)
해결 방법
- 적절한 h값 사용(h = 1e-4 이 값을 사용하면 잘 나온다는 이야기가..)
- 중심 차분 사용 (더 정확)
def numerical_diff(f, x):
h = 1e-4
return (f(x+h) - f(x-h)) / (2*h)
- 앞쪽만 보면 → 한쪽 기준 (오차 큼)
- 양쪽 평균 → 더 정확
수치 미분은 함수의 기울기를 극한 대신 작은 값으로 근사해서 계산하는 방법
편미분 (Partial Derivative)
여러 변수 중에서 하나만 바꾸고, 나머지는 고정한 상태에서 미분하는 것

기울기 (Gradient)

각 방향으로 얼마나 증가/감소하는지 알려주는 벡터
→ 기울기는 함수 값이 가장 빠르게 증가하는 방향
그래서 우리는 반대로 감
→ -gradient 방향 = 최소값 방향
경사하강법 (Gradient Descent)
기울기를 이용해서 손실을 줄인다
- 기울기 방향 → 증가
- 반대 방향 → 감소
→ 그래서 기울기의 반대 방향으로 이동
def gradient_descent(f, init_x, lr=0.01, step_num=100):
x = init_x
for i in range(step_num):
grad = numerical_gradient(f, x)
x -= lr * grad
return x
학습률 (learning rate)
너무 크면?
- 발산 (튀어버림)
너무 작으면?
- 학습 안됨 (느림)
“경사하강법은 기울기를 이용해 함수 값을 점점 줄여가는 방법이다.”
신경망에서의 기울기
우리는 가중치 W에 대한 손실 함수의 기울기를 구함
“가중치를 조금 바꿨을 때, 손실이 얼마나 변하는지”
기울기의 shape = W와 동일
- 미니배치 뽑기
- 예측 (forward)
- 손실 계산
- 기울기 계산
- 가중치 업데이트
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
grad = network.numerical_gradient(x_batch, t_batch)
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
예측 → 틀린 정도 계산 → 기울기 계산 → 조금 수정
'AI' 카테고리의 다른 글
| [밑바닥부터 시작하는 딥러닝] ch.3 신경망 (0) | 2026.05.24 |
|---|---|
| [밑바닥부터 시작하는 딥러닝] ch2. 퍼셉트론 (0) | 2026.05.24 |
| 완벽한 스미싱 분류 모델을 향해서 (2) (1) | 2025.05.28 |
| 완벽한 스미싱 분류 모델을 향해서 (1) (4) | 2025.05.27 |
| [LLM] RAG와 Vector Database (0) | 2025.05.26 |