케라스 창시자에게 배우는 딥러닝 개정 3판 - 2

책의 3장과 4장에 대한 학습 정리입니다.
3장. 텐서플로, 파이토치, JAX 그리고 케라스에 대한 소개
4장. 분류와 회귀
앞선 2장에서는 텐서(Tensor), 텐서 연산, 그레이디언트(Gradient), 역전파(Backpropagation) 등을 학습했습니다.
그동안 배운 내용은 딥러닝 모델이 어떤 원리로 데이터를 계산하고, 예측하고, 학습하는지에 대한 이야기였습니다.
그렇다면 실제로 이러한 딥러닝 모델을 개발하려면 어떻게 해야 할까요?
모든 행렬 연산을 직접 구현하고, 도함수를 계산하고, 역전파 알고리즘까지 처음부터 개발해야 할까요?
다행히 그렇지는 않습니다.
이미 이러한 복잡한 계산을 편리하게 수행할 수 있도록 도와주는 딥러닝 프레임워크(Deep Learning Framework) 들이 존재하기 때문입니다.
3장에서는 대표적인 딥러닝 프레임워크인 텐서플로(TensorFlow), 파이토치(PyTorch), JAX, 케라스(Keras)를 알아보고, 4장에서는 이 프레임워크를 활용하여 실제 분류와 회귀 문제를 해결하는 과정을 알아보겠습니다.
3장. 텐서플로, 파이토치, JAX 그리고 케라스에 대한 소개
3.1 딥러닝 프레임워크의 간략한 역사
딥러닝 프레임워크는 신경망을 개발하고 학습시키기 위해 필요한 여러 기능을 제공하는 소프트웨어 도구입니다.
과거에는 연구자가 신경망을 만들기 위해 수많은 계산 과정을 직접 구현해야 했습니다.
예를 들어, 어떤 인공지능 모델이 시험 점수를 예측한다고 가정하겠습니다.
모델을 학습시키려면 다음과 같은 작업들이 필요합니다.
- 데이터를 숫자로 변환하기
- 가중치와 입력값 계산하기
- 예측값과 실제 정답의 차이 구하기
- 미분을 이용하여 가중치의 기울기 계산하기
- 기울기를 이용해 가중치 수정하기
- 위 과정을 반복하기
이러한 작업은 모델이 복잡해질수록 구현하기 어려워집니다.
그래서 연구자들은 공통으로 사용하는 계산 기능을 라이브러리와 프레임워크로 만들기 시작했습니다.
대표적인 발전 흐름은 다음과 같습니다.
📚 시기프레임워크특징
| 2007년경 | Theano | 수학 연산의 최적화와 자동 미분 |
| 2015년 | TensorFlow | 대규모 딥러닝 계산과 모델 개발 |
| 2015년 | Keras | 신경망을 쉽고 간결하게 작성 |
| 2016년 | PyTorch | 직관적인 파이썬 코드와 동적 계산 그래프 |
| 2018년 | JAX | 자동 미분과 컴파일 기반 수치 계산 |
| 2023년 | Keras 3 | 여러 딥러닝 백엔드에서 동작하는 통합 API |
이러한 프레임워크의 공통적인 목표는 개발자가 복잡한 계산을 직접 구현하는 대신 모델 구조와 학습 방법에 집중하도록 돕는 것입니다.
한 줄 요약: 딥러닝 프레임워크는 텐서 연산, 자동 미분, GPU 연산 등을 제공하여 신경망을 더 쉽고 효율적으로 개발할 수 있도록 도와주는 도구입니다.
3.2 프레임워크 사이의 관계
텐서플로, 파이토치, JAX, 케라스는 모두 딥러닝에 사용되지만 역할과 추구하는 방식에는 차이가 있습니다.
이를 요리 과정에 비유해 보겠습니다.
- TensorFlow: 재료를 손질하고 요리하는 데 필요한 다양한 기능을 제공하는 주방
- PyTorch: 요리 과정을 직접 조정하기 편리한 주방
- JAX: 복잡한 요리 과정을 빠르게 계산하고 최적화하는 주방
- Keras: 여러 주방에서 공통으로 사용할 수 있는 요리법과 조리 인터페이스
특히 케라스 3의 중요한 특징은 여러 백엔드를 지원한다는 것입니다.
Keras 3
|
---------------------
| | |
TensorFlow PyTorch JAX
| | |
---------------------
|
CPU / GPU / TPU
(지원 환경에 따라)
여기서 백엔드(Backend)는 실제 텐서 연산과 미분 등의 계산을 수행하는 실행 엔진을 의미합니다.
케라스는 신경망을 만드는 상위 수준의 API를 제공하고, 백엔드는 계산을 처리합니다.
다만 케라스의 모든 기능과 사용자 정의 코드가 백엔드와 무관하게 자동으로 호환되는 것은 아닙니다. 여러 백엔드에서 동작하도록 작성하려면 케라스가 제공하는 공통 연산 API를 사용하는 것이 중요합니다.
한 줄 요약: 텐서플로, 파이토치, JAX는 딥러닝 계산을 수행하는 프레임워크이며, 케라스는 이들을 활용하여 모델을 편리하게 개발할 수 있도록 돕는 고수준 API입니다.
3.3 텐서플로 소개
텐서플로(TensorFlow)는 구글에서 개발한 오픈소스 머신러닝 프레임워크입니다.
텐서(Tensor)와 플로(Flow)라는 이름에서 알 수 있듯이, 데이터를 텐서 형태로 표현하고 다양한 연산을 수행합니다.
3.3.1 텐서플로 첫걸음
앞서 배운 텐서는 숫자를 담는 다차원 배열이라고 설명했습니다.
텐서플로에서도 텐서를 생성하고 계산할 수 있습니다.
import tensorflow as tf
a = tf.constant([1, 2, 3])
b = tf.constant([4, 5, 6])
print(a + b)
# [5 7 9]
print(a * b)
# [4 10 18]
tf.constant()는 값이 고정된 텐서를 생성합니다.
그렇다면 학습 중 값이 계속 변하는 가중치는 어떻게 표현할까요?
이때 사용하는 것이 tf.Variable입니다.
weight = tf.Variable(2.0)
weight.assign(3.0)
print(weight.numpy())
# 3.0
가중치는 학습 과정에서 계속 수정되므로 변경할 수 있는 변수로 관리하는 것이 적합합니다.
자동 미분(Automatic Differentiation)
딥러닝 모델이 학습하려면 손실을 줄이는 방향을 알아야 합니다.
텐서플로에서는 tf.GradientTape()를 이용하여 기울기를 계산할 수 있습니다.
x = tf.Variable(3.0)
with tf.GradientTape() as tape:
y = x ** 2
gradient = tape.gradient(y, x)
print(gradient.numpy())
# 6.0
여기서 함수는 다음과 같습니다.
y = x²
이 함수를 미분하면 dy/dx = 2x이므로, x가 3일 때 기울기는 6입니다.
GradientTape는 계산 과정을 추적하여 어떤 변수에 대해 미분해야 하는지 계산해 줍니다.
한 줄 요약: 텐서플로는 텐서 연산을 수행하고, tf.Variable로 학습할 가중치를 관리하며, GradientTape로 자동 미분을 지원합니다.
3.3.2 엔드-투-엔드 예제: 순수 텐서플로로 구현하는 선형 분류기
선형 분류기(Linear Classifier)는 데이터를 직선 또는 초평면을 기준으로 구분하는 모델입니다.
예를 들어 공부 시간과 문제 풀이 개수를 기준으로 시험 합격 여부를 예측한다고 가정하겠습니다.
입력값은 두 개입니다.
- 공부 시간
- 문제 풀이 개수
출력값은 합격 또는 불합격입니다.
이를 수식으로 나타내면 다음과 같습니다.
예측값 = 입력값 × 가중치 + 편향
선형 분류기의 학습 과정을 간단하게 구현해 보겠습니다.
import tensorflow as tf
# 입력 데이터
x = tf.constant([
[1.0, 2.0],
[2.0, 1.0],
[3.0, 4.0],
[4.0, 3.0]
])
# 정답 레이블
y = tf.constant([
[0.0],
[0.0],
[1.0],
[1.0]
])
# 학습할 가중치와 편향
w = tf.Variable(tf.random.normal((2, 1)))
b = tf.Variable(tf.zeros((1,)))
optimizer = tf.keras.optimizers.SGD(
learning_rate=0.1
)
for epoch in range(100):
with tf.GradientTape() as tape:
logits = tf.matmul(x, w) + b
loss = tf.reduce_mean(
tf.nn.sigmoid_cross_entropy_with_logits(
labels=y,
logits=logits
)
)
gradients = tape.gradient(loss, [w, b])
optimizer.apply_gradients(
zip(gradients, [w, b])
)
if epoch % 20 == 0:
print(epoch, float(loss))
코드의 흐름을 이해해 보겠습니다.
- 입력 데이터와 정답을 준비합니다.
- 가중치와 편향을 생성합니다.
- 현재 가중치로 예측값을 계산합니다.
- 손실 함수로 예측이 얼마나 틀렸는지 계산합니다.
- 자동 미분으로 기울기를 구합니다.
- SGD를 이용하여 가중치를 수정합니다.
이 과정을 100번 반복하면서 모델이 데이터를 구분하는 기준을 학습합니다.
여기서는 케라스의 고수준 모델 API 없이 직접 학습 루프를 작성했습니다. 최적화 도구에는 텐서플로의 Keras API를 사용했습니다.
한 줄 요약: 선형 분류기는 가중치와 편향으로 데이터를 구분하며, 손실 계산과 자동 미분, 가중치 수정 과정을 반복하면서 학습합니다.
3.3.3 텐서플로 방식의 차별점
텐서플로의 주요 특징 중 하나는 계산을 그래프 형태로 최적화하여 실행할 수 있다는 것입니다.
일반적인 파이썬 함수를 tf.function으로 변환할 수 있습니다.
@tf.function
def calculate(x):
return x * x + 2
print(calculate(tf.constant(3)))
# tf.Tensor(11, ...)
tf.function은 함수를 텐서플로 그래프로 추적하고 실행하도록 도와줍니다.
이를 통해 실행 최적화나 모델 배포에 필요한 기능을 활용할 수 있습니다.
다만 모든 코드가 자동으로 빨라지는 것은 아니며, 함수의 구성과 연산 방식에 따라 효과가 달라집니다.
3.4 파이토치 소개
파이토치(PyTorch)는 파이썬 개발자가 직관적으로 딥러닝 모델을 작성할 수 있도록 설계된 프레임워크입니다.
특히 일반적인 파이썬 코드처럼 실행하면서 중간 결과를 확인하기 편리하다는 특징이 있습니다.
3.4.1 파이토치 첫걸음
파이토치에서도 텐서를 생성하고 연산할 수 있습니다.
import torch
a = torch.tensor([1, 2, 3])
b = torch.tensor([4, 5, 6])
print(a + b)
# tensor([5, 7, 9])
print(a * b)
# tensor([4, 10, 18])
텐서플로와 비슷하게 작성할 수 있습니다.
그렇다면 자동 미분은 어떻게 할까요?
파이토치에서는 autograd라는 자동 미분 시스템을 사용합니다.
x = torch.tensor(
3.0,
requires_grad=True
)
y = x ** 2
y.backward()
print(x.grad)
# tensor(6.)
여기서 중요한 것은 두 가지입니다.
- requires_grad=True: 이 텐서에 대한 기울기를 추적하도록 설정
- backward(): 역전파를 수행하여 기울기 계산
텐서플로의 GradientTape()와 목적이 비슷하지만 사용하는 방식은 다릅니다.
한 줄 요약: 파이토치는 requires_grad와 backward()를 이용하여 자동 미분을 수행하며, 파이썬 코드처럼 직관적으로 신경망을 개발할 수 있습니다.
3.4.2 엔드-투-엔드 예제: 순수 파이토치로 구현하는 선형 분류기
앞서 텐서플로에서 구현했던 선형 분류기를 파이토치로 작성하겠습니다.
import torch
x = torch.tensor([
[1.0, 2.0],
[2.0, 1.0],
[3.0, 4.0],
[4.0, 3.0]
])
y = torch.tensor([
[0.0],
[0.0],
[1.0],
[1.0]
])
w = torch.nn.Parameter(torch.randn(2, 1))
b = torch.nn.Parameter(torch.zeros(1))
optimizer = torch.optim.SGD(
[w, b],
lr=0.1
)
loss_fn = torch.nn.BCEWithLogitsLoss()
for epoch in range(100):
optimizer.zero_grad()
logits = x @ w + b
loss = loss_fn(logits, y)
loss.backward()
optimizer.step()
if epoch % 20 == 0:
print(epoch, loss.item())
텐서플로와 다른 부분을 살펴보겠습니다.
optimizer.zero_grad()는 기존 기울기를 초기화합니다.
파이토치는 기본적으로 기울기를 누적하므로 새로운 학습 단계에 들어가기 전에 이를 초기화해야 합니다.
loss.backward()는 역전파를 수행합니다.
optimizer.step()은 계산된 기울기를 이용하여 가중치를 수정합니다.
결국 텐서플로와 파이토치는 표현 방식이 다를 뿐 기본적인 학습 원리는 같습니다.
3.4.3 파이토치 방식의 차별점
파이토치의 특징은 일반적인 파이썬 제어 흐름과 자연스럽게 결합된다는 것입니다.
예를 들어 반복문이나 조건문을 사용하면서 계산할 수 있습니다.
이러한 특징은 연구자가 새로운 신경망 구조를 실험하거나 중간 계산 과정을 디버깅할 때 유용합니다.
또한 torch.compile()을 통해 지원되는 모델과 함수의 실행을 최적화할 수도 있습니다.
한 줄 요약: 파이토치는 코드의 실행 흐름을 이해하고 디버깅하기 편리하며, 유연하게 신경망을 구현할 수 있다는 장점이 있습니다.
3.5 JAX 소개
JAX는 수치 계산, 자동 미분, 컴파일 최적화 등에 초점을 맞춘 라이브러리입니다.
NumPy와 유사한 연산 인터페이스를 제공하지만 자동 미분과 JIT 컴파일 같은 기능을 지원합니다.
3.5.1 JAX 첫걸음
import jax.numpy as jnp
a = jnp.array([1, 2, 3])
b = jnp.array([4, 5, 6])
print(a + b)
# [5 7 9]
지금까지 살펴본 프레임워크와 사용 방법이 비슷합니다.
JAX의 대표적인 기능은 다음과 같습니다.
- jax.grad(): 자동 미분
- jax.jit(): 컴파일을 통한 실행 최적화
- jax.vmap(): 연산의 벡터화
- jax.random: 난수 생성
3.5.2 JAX의 텐서
JAX의 배열은 기본적으로 불변(Immutable)입니다.
쉽게 말하면 생성된 배열의 특정 위치에 직접 값을 대입하여 수정할 수 없다는 뜻입니다.
import jax.numpy as jnp
x = jnp.array([1, 2, 3])
# x[0] = 10 # 오류 발생
new_x = x.at[0].set(10)
print(new_x)
# [10 2 3]
print(x)
# [1 2 3]
기존 배열을 직접 변경하는 대신 수정된 새로운 배열을 생성합니다.
이러한 방식은 함수의 입력과 출력 관계를 예측하기 쉽게 만들어 줍니다.
3.5.3 JAX의 난수 생성
일반적인 파이썬에서는 난수 함수를 호출할 때 내부 상태를 사용합니다.
반면 JAX에서는 명시적인 난수 키(PRNG Key)를 사용합니다.
import jax
key = jax.random.key(42)
key, subkey = jax.random.split(key)
random_values = jax.random.normal(
subkey,
shape=(3,)
)
print(random_values)
같은 키와 같은 연산을 사용하면 재현 가능한 난수 결과를 얻을 수 있습니다.
새로운 난수를 생성하려면 기존 키에서 새로운 키를 분리하여 사용하는 방식이 권장됩니다.
한 줄 요약: JAX는 불변 배열과 명시적인 난수 키를 사용하여 계산 과정을 예측하고 재현하기 쉽게 만듭니다.
3.5.4 엔드-투-엔드 예제: 순수 JAX로 구현하는 선형 분류기
이번에는 JAX로 선형 분류기를 구현하겠습니다.
JAX에서는 모델의 파라미터를 함수에 전달하고 새로운 파라미터를 반환하는 방식을 사용할 수 있습니다.
import jax
import jax.numpy as jnp
import jax.nn as jnn
x = jnp.array([
[1.0, 2.0],
[2.0, 1.0],
[3.0, 4.0],
[4.0, 3.0]
])
y = jnp.array([[0.0], [0.0], [1.0], [1.0]])
key = jax.random.key(42)
params = {
"w": jax.random.normal(key, (2, 1)) * 0.1,
"b": jnp.zeros((1,))
}
def loss_fn(params, x, y):
logits = x @ params["w"] + params["b"]
loss = jnp.maximum(logits, 0) \
- logits * y \
+ jnp.log1p(jnp.exp(-jnp.abs(logits)))
return jnp.mean(loss)
@jax.jit
def train_step(params, x, y):
gradients = jax.grad(loss_fn)(params, x, y)
return jax.tree.map(
lambda p, g: p - 0.1 * g,
params,
gradients
)
for epoch in range(100):
params = train_step(params, x, y)
print(loss_fn(params, x, y))
여기서 핵심은 jax.grad()입니다.
손실 함수를 전달하면 파라미터에 대한 기울기를 계산하는 함수를 만들어 줍니다.
jax.jit()은 학습 단계의 계산을 컴파일할 수 있도록 합니다.
이 예제는 학습 과정에서 기존 파라미터를 직접 수정하는 대신 새롭게 계산된 파라미터를 반환합니다.
3.5.5 JAX 방식의 차별점
JAX는 함수형 프로그래밍의 특징을 적극적으로 활용합니다.
일반적으로 함수형 프로그래밍에서는 입력값이 동일하면 출력값도 동일하고, 함수가 외부 상태를 변경하지 않는 방식을 중요하게 생각합니다.
JAX가 제공하는 주요 변환 기능은 이러한 특징과 잘 어울립니다.
한 줄 요약: JAX는 자동 미분과 함수 변환, 컴파일 최적화를 조합하여 고성능 수치 계산과 머신러닝 연구에 활용할 수 있는 도구입니다.
3.6 케라스 소개
앞서 소개한 세 가지 프레임워크에서는 가중치, 손실, 기울기, 파라미터 업데이트 등을 직접 관리했습니다.
하지만 일반적인 딥러닝 개발에서는 이러한 과정을 반복해서 작성하는 일이 많습니다.
케라스(Keras)는 이러한 반복 작업을 줄이고, 신경망의 구조와 학습 과정을 간단하게 작성하도록 도와줍니다.
3.6.1 케라스 첫걸음
케라스 3는 백엔드를 선택하여 사용할 수 있습니다.
import os
os.environ["KERAS_BACKEND"] = "tensorflow"
import keras
print(keras.backend.backend())
# tensorflow
백엔드는 케라스를 불러오기 전에 설정해야 합니다.
케라스로 간단한 신경망을 생성해 보겠습니다.
model = keras.Sequential([
keras.layers.Input(shape=(2,)),
keras.layers.Dense(16, activation="relu"),
keras.layers.Dense(1, activation="sigmoid")
])
이 코드만으로 입력층, 은닉층, 출력층을 가진 간단한 모델을 구성할 수 있습니다.
3.6.2 층: 딥러닝의 구성 요소
신경망의 핵심 구성 요소는 층(Layer)입니다.
층은 입력 데이터를 받아 계산한 뒤 새로운 출력 데이터를 만들어 냅니다.
예를 들어 공장에서 제품을 생산한다고 가정하겠습니다.
- 첫 번째 공정: 재료의 기본 형태를 가공
- 두 번째 공정: 가공한 부품을 조립
- 세 번째 공정: 최종 제품을 생산
딥러닝의 층도 이와 비슷하게 이전 단계에서 전달받은 데이터를 변환합니다.
layer = keras.layers.Dense(
units=16,
activation="relu"
)
여기서 Dense는 완전 연결층(Fully Connected Layer)입니다.
units=16은 출력 특징의 크기가 16이라는 뜻입니다.
activation="relu"는 활성화 함수를 지정합니다.
ReLU 함수는 음수를 0으로 만들고 양수는 그대로 유지합니다.
ReLU(x) = max(0, x)
활성화 함수가 없다면 여러 선형층을 쌓더라도 전체 계산은 하나의 선형 변환과 같습니다.
비선형 활성화 함수를 사용해야 더욱 복잡한 데이터 관계를 학습할 수 있습니다.
한 줄 요약: 층은 입력 데이터를 새로운 표현으로 변환하는 신경망의 기본 구성 요소이며, 활성화 함수를 이용하여 복잡한 패턴을 학습할 수 있습니다.
3.6.3 층에서 모델로
층 하나만으로는 복잡한 문제를 해결하기 어렵습니다.
따라서 여러 층을 연결하여 하나의 모델(Model)을 만듭니다.
model = keras.Sequential([
keras.layers.Input(shape=(10,)),
keras.layers.Dense(32, activation="relu"),
keras.layers.Dense(16, activation="relu"),
keras.layers.Dense(1, activation="sigmoid")
])
model.summary()
이 모델은 10개의 입력 특징을 받아 두 개의 은닉층을 통과한 뒤 하나의 예측 확률을 출력합니다.
Sequential은 층을 순서대로 연결할 때 사용하는 가장 간단한 모델 구성 방식입니다.
3.6.4 '컴파일' 단계: 학습 과정 설정
모델 구조를 만들었다면 어떻게 학습할 것인지 설정해야 합니다.
model.compile(
optimizer="adam",
loss="binary_crossentropy",
metrics=["accuracy"]
)
여기서 중요한 세 가지를 살펴보겠습니다.
항목역할쉽게 이해하기
| Optimizer | 가중치 수정 방법 | 어떻게 공부할 것인가? |
| Loss | 예측 오차 계산 | 얼마나 틀렸는가? |
| Metrics | 모델 성능 평가 | 얼마나 잘 맞혔는가? |
compile()은 모델의 학습 방식을 설정합니다.
일반적인 프로그램 언어의 컴파일 과정과 완전히 같은 의미는 아닙니다.
3.6.5 손실 함수 선택하기
손실 함수(Loss Function)는 모델이 얼마나 틀렸는지 계산합니다.
문제의 유형에 따라 사용하는 손실 함수가 달라집니다.
문제 유형대표적인 손실 함수
| 이진 분류 | Binary Crossentropy |
| 다중 분류(원-핫 레이블) | Categorical Crossentropy |
| 다중 분류(정수 레이블) | Sparse Categorical Crossentropy |
| 회귀 | Mean Squared Error(MSE) |
| 회귀 | Mean Absolute Error(MAE) |
예를 들어 실제 주택 가격이 5억 원인데 모델이 4억 원이라고 예측했다면 오차는 1억 원입니다.
회귀에서는 이러한 숫자의 차이를 측정합니다.
반면 분류에서는 정답 클래스에 대해 모델이 얼마나 적절한 확률을 부여했는지가 중요합니다.
한 줄 요약: 손실 함수는 예측의 오류를 수치화하며, 분류인지 회귀인지와 레이블 표현 방식에 따라 적합한 함수를 선택해야 합니다.
3.6.6 fit() 메서드 이해하기
fit()은 모델의 실제 학습을 수행하는 메서드입니다.
history = model.fit(
x_train,
y_train,
epochs=10,
batch_size=32
)
두 가지 중요한 개념을 알아보겠습니다.
Epoch(에포크)
전체 훈련 데이터를 한 번 학습하는 것을 의미합니다.
에포크가 10이라면 전체 훈련 데이터를 10번 반복하여 학습합니다.
Batch Size(배치 크기)
한 번의 가중치 업데이트에 사용할 데이터 개수입니다.
예를 들어 훈련 데이터가 1,000개이고 배치 크기가 100이라면 한 에포크에 보통 10번의 업데이트가 이루어집니다.
3.6.7 검증 데이터에서 손실과 평가 지표 모니터링하기
모델의 학습 성능이 좋다고 해서 새로운 데이터에서도 잘 작동하는 것은 아닙니다.
학습 데이터만 지나치게 기억하는 과대적합(Overfitting)이 발생할 수 있기 때문입니다.
history = model.fit(
x_train,
y_train,
epochs=20,
validation_split=0.2
)
validation_split=0.2는 전달한 훈련 데이터의 20%를 검증 데이터로 사용한다는 의미입니다.
훈련 손실과 검증 손실을 함께 확인하면 모델이 지나치게 학습 데이터에만 맞춰지고 있는지 확인하는 데 도움이 됩니다.
3.6.8 추론: 훈련한 모델 사용하기
학습이 완료된 모델을 실제 데이터에 적용하는 과정을 추론(Inference)이라고 합니다.
predictions = model.predict(x_test)
print(predictions[:5])
이진 분류에서 출력층에 시그모이드 함수를 사용했다면 일반적으로 0부터 1 사이의 확률값이 출력됩니다.
predicted_classes = (predictions >= 0.5).astype("int32")
0.5 이상의 확률을 1로, 그보다 낮은 확률을 0으로 분류하는 예제입니다.
실제 분류 임계값은 문제의 목적에 따라 조정할 수 있습니다.
3.7 요약
3장에서는 딥러닝 모델을 개발할 때 사용하는 대표적인 프레임워크를 알아보았습니다.
구분TensorFlowPyTorchJAXKeras
| 주요 목적 | 딥러닝 개발 및 실행 | 유연한 신경망 개발 | 수치 계산과 함수 최적화 | 간결한 모델 개발 |
| 자동 미분 | GradientTape | autograd | grad | 백엔드 활용 |
| 대표 특징 | 계산 그래프, 배포 생태계 | 직관적인 실행과 디버깅 | 함수 변환, JIT | 고수준 모델 API |
| 학습 방법 | 직접 또는 고수준 API | 직접 또는 고수준 API | 함수형 학습 루프 | fit() |
| 활용 | 학습과 배포 | 연구 및 서비스 | 연구 및 고성능 계산 | 빠른 모델 개발 |
결국 딥러닝 학습의 본질은 동일합니다.
데이터 입력 → 예측 → 손실 계산 → 기울기 계산 → 가중치 수정
프레임워크는 이 과정의 구현 방식과 추상화 수준을 다르게 제공할 뿐입니다.
4장. 분류와 회귀
3장에서는 딥러닝 프레임워크를 이용하여 신경망을 만드는 방법을 배웠습니다.
그렇다면 이렇게 만든 모델은 어떤 문제를 해결할 수 있을까요?
대표적인 머신러닝 문제는 분류(Classification)와 회귀(Regression)로 나뉩니다.
- 분류: 입력 데이터가 어떤 종류에 속하는지 예측하는 문제
- 회귀: 입력 데이터로부터 연속적인 숫자를 예측하는 문제
예를 들어 이메일이 스팸인지 판별하는 것은 분류 문제입니다.
반면 아파트의 면적과 위치를 바탕으로 가격을 예측하는 것은 회귀 문제입니다.
이번 장에서는 이진 분류, 다중 분류, 회귀라는 세 가지 대표적인 문제를 학습하겠습니다.
4.1 영화 리뷰 분류: 이진 분류 문제
이진 분류(Binary Classification)는 데이터를 두 개의 범주 중 하나로 구분하는 문제입니다.
예를 들어 영화 리뷰가 긍정적인지 부정적인지 판단하는 경우입니다.
4.1.1 IMDB 데이터셋
IMDB 데이터셋은 영화 리뷰와 해당 리뷰의 감정 레이블을 포함한 데이터셋입니다.
대표적인 케라스 IMDB 데이터셋은 50,000개의 리뷰로 구성되며, 훈련 데이터와 테스트 데이터가 각각 25,000개입니다.
레이블은 다음과 같습니다.
- 0: 부정적인 리뷰
- 1: 긍정적인 리뷰
from keras.datasets import imdb
(train_data, train_labels), (test_data, test_labels) = (
imdb.load_data(num_words=10000)
)
여기서 num_words=10000은 데이터셋에서 가장 자주 등장하는 단어 10,000개만 사용하는 설정입니다.
리뷰는 문자열이 아니라 단어 인덱스의 나열로 제공됩니다.
print(train_data[0][:10])
각 숫자는 특정 단어에 대응합니다.
4.1.2 데이터 준비
신경망에 입력하려면 길이가 서로 다른 리뷰들을 일정한 형태의 텐서로 표현해야 합니다.
여기에서는 원-핫에 가까운 멀티-핫(Multi-hot) 표현 방식을 사용하겠습니다.
리뷰에 특정 단어가 등장하면 1, 등장하지 않으면 0으로 표현합니다.
import numpy as np
def vectorize_sequences(sequences, dimension=10000):
results = np.zeros(
(len(sequences), dimension),
dtype="float32"
)
for i, sequence in enumerate(sequences):
results[i, sequence] = 1.0
return results
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
y_train = np.asarray(
train_labels,
dtype="float32"
)
y_test = np.asarray(
test_labels,
dtype="float32"
)
예를 들어 사용할 수 있는 단어가 다섯 개라고 가정하겠습니다.
리뷰에 1번, 3번 단어가 등장했다면 다음과 같이 표현할 수 있습니다.
[1, 0, 1, 0, 0]
이러한 방식은 단어의 등장 여부를 표현하지만 단어의 순서와 등장 횟수는 보존하지 않습니다.
4.1.3 모델 만들기
이진 분류에서는 최종적으로 하나의 확률값을 출력하도록 모델을 구성할 수 있습니다.
import keras
from keras import layers
model = keras.Sequential([
layers.Input(shape=(10000,)),
layers.Dense(16, activation="relu"),
layers.Dense(16, activation="relu"),
layers.Dense(1, activation="sigmoid")
])
model.compile(
optimizer="rmsprop",
loss="binary_crossentropy",
metrics=["accuracy"]
)
여기서 마지막 층이 중요한 역할을 합니다.
Dense(1, activation="sigmoid")
출력 뉴런이 하나이므로 하나의 확률을 예측합니다.
시그모이드 함수는 출력값을 0과 1 사이로 변환합니다.
한 줄 요약: 이진 분류에서는 일반적으로 하나의 출력 뉴런과 시그모이드 활성화 함수를 사용해 특정 클래스에 속할 확률을 예측합니다.
4.1.4 훈련 및 검증
모델이 학습 데이터를 잘 분류하는지 확인하면서 검증 데이터에 대한 성능도 측정하겠습니다.
x_val = x_train[:10000]
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train = y_train[10000:]
history = model.fit(
partial_x_train,
partial_y_train,
epochs=20,
batch_size=512,
validation_data=(x_val, y_val)
)
학습 결과에는 훈련 손실과 검증 손실이 기록됩니다.
import matplotlib.pyplot as plt
history_dict = history.history
plt.plot(
history_dict["loss"],
label="Training Loss"
)
plt.plot(
history_dict["val_loss"],
label="Validation Loss"
)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.show()
일반적으로 학습이 진행될수록 훈련 손실은 감소합니다.
하지만 검증 손실이 어느 순간부터 증가한다면 과대적합이 발생하고 있을 가능성이 있습니다.
이는 모델이 학습 데이터에 지나치게 맞춰지면서 새로운 데이터에 대한 일반화 성능이 떨어지고 있다는 의미입니다.
4.1.5 훈련된 모델로 새로운 데이터에 대해 예측하기
predictions = model.predict(x_test)
print(predictions[:5])
예를 들어 다음과 같은 확률이 출력되었다고 가정하겠습니다.
[0.91, 0.12, 0.87, 0.34, 0.76]
이는 각각의 리뷰가 긍정적일 것으로 예측한 확률입니다.
0.5를 기준으로 분류한다면 첫 번째 리뷰는 긍정적, 두 번째 리뷰는 부정적이라고 판단합니다.
4.1.6 추가 실험
이번에는 모델 구조와 학습 설정을 변경해 보겠습니다.
- 은닉층을 하나만 사용하면 어떻게 될까요?
- 은닉층의 뉴런을 16개에서 32개로 늘리면 어떻게 될까요?
- 에포크를 20에서 5로 줄이면 어떻게 될까요?
- ReLU 대신 tanh 활성화 함수를 사용하면 어떻게 될까요?
이러한 실험을 통해 모델의 복잡도와 학습 횟수가 성능에 어떤 영향을 미치는지 확인할 수 있습니다.
단순히 층을 늘리거나 학습을 오래 한다고 해서 항상 더 좋은 모델이 만들어지는 것은 아닙니다.
4.1.7 정리
이진 분류에서는 두 가지 범주 중 하나를 예측합니다.
핵심적으로 기억할 사항은 다음과 같습니다.
- 출력층: 일반적으로 뉴런 1개
- 활성화 함수: Sigmoid
- 손실 함수: Binary Crossentropy
- 평가 지표: Accuracy 등
4.2 뉴스 기사 분류: 다중 분류 문제
앞서 영화 리뷰를 긍정과 부정이라는 두 범주로 분류했습니다.
하지만 현실에서는 두 개보다 많은 범주로 데이터를 나누어야 하는 경우가 많습니다.
예를 들어 뉴스 기사를 경제, 정치, 스포츠, 기술 등 여러 주제로 분류하는 경우입니다.
이러한 문제를 다중 분류(Multiclass Classification)라고 합니다.
4.2.1 로이터 데이터셋
로이터(Reuters) 데이터셋은 뉴스 기사들을 46개의 주제로 분류하는 데이터셋입니다.
from keras.datasets import reuters
(train_data, train_labels), (test_data, test_labels) = (
reuters.load_data(num_words=10000)
)
IMDB와 마찬가지로 각 뉴스 기사는 단어 인덱스의 나열로 표현됩니다.
하지만 레이블은 0과 1이 아니라 0부터 45까지의 46개 범주를 나타냅니다.
4.2.2 데이터 준비
입력 데이터는 앞서 사용한 벡터화 함수를 그대로 사용할 수 있습니다.
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
정답 레이블은 원-핫 인코딩(One-hot Encoding)으로 변환할 수 있습니다.
원-핫 인코딩은 정답 클래스의 위치만 1이고 나머지는 모두 0인 벡터로 표현하는 방식입니다.
from keras.utils import to_categorical
y_train = to_categorical(train_labels)
y_test = to_categorical(test_labels)
예를 들어 4개 클래스 중 세 번째가 정답이라면 다음과 같습니다.
[0, 0, 1, 0]
46개 클래스라면 길이가 46인 벡터가 만들어집니다.
한 줄 요약: 원-핫 인코딩은 여러 클래스 중 정답에 해당하는 위치만 1로 표시하는 데이터 표현 방식입니다.
4.2.3 모델 만들기
model = keras.Sequential([
layers.Input(shape=(10000,)),
layers.Dense(64, activation="relu"),
layers.Dense(64, activation="relu"),
layers.Dense(46, activation="softmax")
])
model.compile(
optimizer="rmsprop",
loss="categorical_crossentropy",
metrics=["accuracy"]
)
영화 리뷰 분류와 비교하면 두 가지 중요한 차이가 있습니다.
첫 번째는 출력 뉴런의 개수입니다.
46개의 클래스로 분류해야 하므로 출력층의 뉴런도 46개입니다.
두 번째는 활성화 함수입니다, 여기에서는 Softmax 함수를 사용합니다.
Softmax는 여러 출력값을 각 클래스에 대한 확률 분포로 변환하며, 확률의 합이 1이 되도록 합니다.
예를 들어 세 가지 클래스의 확률이 다음과 같다고 가정하겠습니다.
[0.15, 0.75, 0.10]
모델은 두 번째 클래스에 속할 확률이 가장 높다고 판단합니다.
4.2.4 훈련 및 검증
x_val = x_train[:1000]
partial_x_train = x_train[1000:]
y_val = y_train[:1000]
partial_y_train = y_train[1000:]
history = model.fit(
partial_x_train,
partial_y_train,
epochs=20,
batch_size=512,
validation_data=(x_val, y_val)
)
IMDB 예제와 마찬가지로 검증 손실과 정확도를 관찰합니다.
특히 다중 분류에서는 클래스별 데이터 개수가 서로 다를 수 있으므로 전체 정확도뿐 아니라 클래스별 성능도 확인하는 것이 좋습니다.
4.2.5 새로운 데이터에 대해 예측하기
predictions = model.predict(x_test)
print(predictions[0].shape)
# (46,)
한 뉴스 기사에 대해 46개의 확률이 출력됩니다.
가장 높은 확률을 가진 클래스는 argmax()로 확인할 수 있습니다.
predicted_class = np.argmax(predictions[0])
print(predicted_class)
이 값은 모델이 가장 가능성이 높다고 판단한 뉴스 주제의 인덱스입니다.
4.2.6 레이블과 손실을 다루는 다른 방법
앞에서는 레이블을 원-핫 인코딩으로 변환했습니다.
하지만 반드시 원-핫 인코딩을 사용해야 하는 것은 아닙니다.
정수 레이블을 그대로 사용할 수도 있습니다.
model.compile(
optimizer="rmsprop",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"]
)
이 경우 정수 레이블인 train_labels를 그대로 사용합니다.
두 손실 함수의 차이를 정리하면 다음과 같습니다.
손실 함수레이블 형태
| Categorical Crossentropy | [0, 0, 1, 0] |
| Sparse Categorical Crossentropy | 2 |
두 방식 모두 다중 분류 문제에 사용하지만 레이블을 표현하는 방법이 다릅니다.
4.2.7 중간층을 충분히 크게 만들어야 하는 이유
신경망의 중간층은 데이터를 새로운 특징 표현으로 변환합니다.
만약 중간층의 뉴런이 지나치게 적으면 어떻게 될까요?
46개의 뉴스를 분류해야 하는데 중간층이 단 4개의 값만 출력하도록 구성한다고 가정하겠습니다.
model = keras.Sequential([
layers.Input(shape=(10000,)),
layers.Dense(64, activation="relu"),
layers.Dense(4, activation="relu"),
layers.Dense(46, activation="softmax")
])
이 경우 중간층을 통과하면서 정보를 표현하는 공간이 매우 작아집니다.
필요한 정보를 충분히 표현하기 어려워져 성능이 떨어질 수 있습니다.
이를 정보 병목(Information Bottleneck)의 관점에서 이해할 수 있습니다.
다만 중간층의 뉴런 수가 반드시 클래스 개수보다 커야 하는 것은 아닙니다. 문제의 복잡도와 필요한 특징 표현에 따라 적절한 크기가 달라집니다.
한 줄 요약: 중간층의 크기가 지나치게 작으면 분류에 필요한 정보를 충분히 표현하지 못할 수 있으므로 모델의 표현 능력을 고려해야 합니다.
4.2.8 추가 실험
다음과 같은 실험을 진행할 수 있습니다.
- 은닉층의 크기를 64개에서 32개로 변경하기
- 중간층을 4개 뉴런으로 줄이기
- 은닉층을 하나 또는 세 개로 변경하기
- 학습 에포크와 배치 크기 변경하기
각 실험에서 훈련 손실, 검증 손실, 정확도를 비교하면 모델 용량과 일반화 성능의 관계를 이해하는 데 도움이 됩니다.
4.2.9 정리
다중 분류 문제에서는 여러 개의 클래스 중 하나를 예측합니다.
핵심 특징은 다음과 같습니다.
- 출력 뉴런: 클래스 개수만큼 설정
- 활성화 함수: Softmax
- 원-핫 레이블: Categorical Crossentropy
- 정수 레이블: Sparse Categorical Crossentropy
4.3 주택 가격 예측: 회귀 문제
지금까지는 어떤 범주에 속하는지 예측하는 분류 문제를 살펴보았습니다.
이번에는 숫자 자체를 예측하는 회귀(Regression) 문제를 알아보겠습니다.
예를 들어 주택의 위치, 면적, 주변 환경 등을 바탕으로 집값을 예측하는 경우입니다.
4.3.1 캘리포니아 주택 가격 데이터셋
캘리포니아 주택 가격 데이터셋은 지역별 주택 관련 특성과 주택 가격 정보를 포함한 데이터셋입니다.
대표적인 특성은 다음과 같습니다.
- 지역의 중위 소득
- 주택의 중위 연식
- 평균 방 개수
- 평균 침실 개수
- 인구 관련 정보
- 위도
- 경도
이러한 특징들을 입력받아 주택 가격을 예측합니다.
from sklearn.datasets import fetch_california_housing
housing = fetch_california_housing()
x = housing.data
y = housing.target
print(x.shape)
# (20640, 8)
이 데이터셋은 20,640개의 샘플과 8개의 입력 특징으로 구성됩니다.
목표값은 주택 가격의 중윗값을 10만 달러 단위로 나타냅니다.
4.3.2 데이터 준비
회귀에서는 입력 특징마다 값의 범위가 서로 다를 수 있습니다.
예를 들어 중위 소득과 지역 인구수는 숫자의 크기부터 크게 다릅니다.
이런 데이터를 그대로 사용하면 학습이 어려워질 수 있습니다.
그래서 특성 표준화(Standardization)를 수행합니다.
표준화는 각 특징의 평균을 0, 표준편차를 1에 가깝게 만드는 방법입니다.
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
x_train, x_test, y_train, y_test = train_test_split(
x,
y,
test_size=0.2,
random_state=42
)
scaler = StandardScaler()
x_train = scaler.fit_transform(x_train)
x_test = scaler.transform(x_test)
여기서 중요한 점은 훈련 데이터로만 표준화 기준을 계산해야 한다는 것입니다.
테스트 데이터의 평균과 표준편차까지 학습에 활용하면 데이터 누수(Data Leakage)가 발생할 수 있기 때문입니다.
한 줄 요약: 회귀 모델에서는 입력 특징의 범위를 맞추기 위해 표준화를 사용할 수 있으며, 표준화 기준은 훈련 데이터에서만 학습해야 합니다.
4.3.3 모델 만들기
회귀 모델은 분류 모델과 달리 일반적으로 연속적인 숫자를 출력합니다.
model = keras.Sequential([
layers.Input(shape=(8,)),
layers.Dense(64, activation="relu"),
layers.Dense(64, activation="relu"),
layers.Dense(1)
])
model.compile(
optimizer="rmsprop",
loss="mse",
metrics=["mae"]
)
출력층에는 활성화 함수를 지정하지 않았습니다.
주택 가격은 0과 1 사이의 확률이 아니라 연속적인 숫자이기 때문입니다.
이번에는 손실 함수로 MSE, 평가 지표로 MAE를 사용했습니다.
MSE(Mean Squared Error)
실제값과 예측값의 차이를 제곱한 다음 평균을 계산합니다.
오차가 큰 데이터에 더 큰 벌점을 부여하는 특징이 있습니다.
MAE(Mean Absolute Error)
실제값과 예측값 차이의 절댓값을 평균으로 계산합니다.
예를 들어 실제 가격이 5억 원인데 4억 원으로 예측했다면 절대 오차는 1억 원입니다.
MAE는 이렇게 발생한 오차의 평균적인 크기를 이해하는 데 유용합니다.
4.3.4 훈련 및 K-겹 교차 검증
모델을 평가할 때는 훈련 데이터와 검증 데이터를 나누어 사용합니다.
하지만 데이터가 많지 않다면 한 번의 분할 결과만으로 모델을 평가하기 어려울 수 있습니다.
이때 사용하는 방법 중 하나가 K-겹 교차 검증(K-Fold Cross Validation)입니다.
예를 들어 데이터를 4개 그룹으로 나눈다고 가정하겠습니다.
- 1회차: 1번 그룹 검증, 나머지 그룹 훈련
- 2회차: 2번 그룹 검증, 나머지 그룹 훈련
- 3회차: 3번 그룹 검증, 나머지 그룹 훈련
- 4회차: 4번 그룹 검증, 나머지 그룹 훈련
총 4번 모델을 새롭게 학습시켜 각각의 검증 성능을 계산합니다.
from sklearn.model_selection import KFold
kf = KFold(
n_splits=4,
shuffle=True,
random_state=42
)
scores = []
for train_idx, val_idx in kf.split(x_train):
fold_x_train = x_train[train_idx]
fold_x_val = x_train[val_idx]
fold_y_train = y_train[train_idx]
fold_y_val = y_train[val_idx]
model = keras.Sequential([
layers.Input(shape=(8,)),
layers.Dense(64, activation="relu"),
layers.Dense(64, activation="relu"),
layers.Dense(1)
])
model.compile(
optimizer="rmsprop",
loss="mse",
metrics=["mae"]
)
model.fit(
fold_x_train,
fold_y_train,
epochs=50,
batch_size=32,
verbose=0
)
loss, mae = model.evaluate(
fold_x_val,
fold_y_val,
verbose=0
)
scores.append(mae)
print("평균 MAE:", np.mean(scores))
위 예제는 K-겹 교차 검증의 흐름을 간단하게 보여줍니다.
실제 실험에서는 각 폴드의 훈련 부분만 사용하여 표준화 기준을 새로 계산하고, 검증 부분에 적용해야 데이터 누수를 방지할 수 있습니다.
K-겹 교차 검증은 모델 성능을 여러 데이터 분할에서 확인하므로 단일 검증 결과에 대한 의존도를 줄여줍니다.
4.3.5 새로운 데이터에 대해 예측하기
학습한 모델을 새로운 주택 데이터에 적용할 수 있습니다.
다만 교차 검증에서 사용한 모델 하나를 그대로 최종 모델로 사용하기보다는, 교차 검증을 통해 학습 설정을 결정한 뒤 전체 훈련 데이터로 새로운 모델을 학습하는 것이 일반적입니다.
최종 모델을 학습했다고 가정하면 예측은 다음과 같습니다.
predictions = model.predict(x_test)
print(predictions[:5])
예측값은 주택 가격을 나타내는 연속적인 숫자입니다.
예를 들어 결과가 3.5라면, 해당 데이터셋의 단위상 주택 가격의 중윗값을 약 35만 달러로 예측한 것입니다.
4.3.6 정리
회귀 문제에서는 클래스가 아니라 연속적인 숫자를 예측합니다.
주요 특징은 다음과 같습니다.
- 출력층: 일반적으로 예측 대상당 뉴런 1개
- 활성화 함수: 제한 없는 회귀에서는 보통 지정하지 않음
- 손실 함수: MSE 등
- 평가 지표: MAE 등
- 데이터 전처리: 표준화
- 모델 평가: 검증 데이터 또는 K-겹 교차 검증
4.4 요약
4장에서는 가장 대표적인 세 가지 지도학습 문제를 살펴보았습니다.
구분이진 분류다중 분류회귀
| 예제 | 영화 리뷰 | 뉴스 기사 | 주택 가격 |
| 목적 | 두 범주 중 하나 예측 | 여러 범주 중 하나 예측 | 연속적인 수치 예측 |
| 출력층 | Dense(1) | Dense(클래스 수) | Dense(1) |
| 활성화 함수 | Sigmoid | Softmax | 보통 없음 |
| 대표 손실 함수 | Binary Crossentropy | Categorical Crossentropy | MSE |
| 대표 평가 지표 | Accuracy | Accuracy | MAE |
| 출력 결과 | 긍정일 확률 | 클래스별 확률 | 가격 등의 숫자 |
전체 학습 과정 정리
지금까지 배운 딥러닝 학습 과정은 다음과 같습니다.
1. 데이터 준비
문제에 적합한 데이터셋을 준비하고, 신경망이 처리할 수 있는 숫자 형태로 변환합니다.
2. 모델 생성
문제 유형에 맞는 입력층, 은닉층, 출력층을 구성합니다.
3. 학습 설정
손실 함수, 옵티마이저, 평가 지표를 선택합니다.
4. 모델 훈련
훈련 데이터를 입력하고 예측, 손실 계산, 역전파, 가중치 업데이트를 반복합니다.
5. 검증 및 평가
모델이 새로운 데이터에서도 잘 작동하는지 확인합니다.
6. 추론
학습된 모델을 실제 입력 데이터에 적용하여 예측 결과를 얻습니다.
마지막으로
3장과 4장을 학습하면서 중요한 것은 프레임워크의 문법을 모두 외우는 것이 아니라, 딥러닝 모델이 어떤 과정을 거쳐 학습하고 예측하는지 이해하는 것이라고 생각합니다.
2장에서 살펴본 텐서 연산과 그레이디언트 기반 최적화는 3장에서 프레임워크의 코드로 연결되고, 다시 4장에서는 영화 리뷰, 뉴스 기사, 주택 가격과 같은 실제 문제를 해결하는 과정으로 이어집니다.
결국 우리가 어떤 프레임워크를 사용하더라도 기본적인 과정은 동일합니다.
데이터를 준비하고, 모델을 만들고, 예측한 결과의 오차를 계산하고, 가중치를 수정하면서 점점 더 나은 예측을 수행하도록 학습하는 것입니다.
그리고 여기에서 한 가지 더 기억해야 할 점은, 학습 데이터에서 좋은 성능을 기록하는 것과 새로운 데이터에서 좋은 성능을 발휘하는 것은 서로 다르다는 사실입니다.
이 차이를 이해하는 것이 앞으로 과대적합, 일반화, 모델 규제 등의 개념을 학습하는 데 중요한 기초가 될 것입니다.
최종 한 줄 요약: 딥러닝 프레임워크는 신경망의 계산과 학습을 편리하게 구현하도록 도와주며, 이진 분류·다중 분류·회귀는 해결하려는 문제에 따라 모델의 출력층과 손실 함수를 다르게 설계하는 대표적인 머신러닝 문제입니다.
참고 자료
- 프랑소와 숄레, 매튜 왓슨, 『케라스 창시자에게 배우는 딥러닝 개정 3판』, 길벗, 2026.
- Keras 공식 문서: https://keras.io
- TensorFlow 공식 문서: https://www.tensorflow.org
- PyTorch 공식 문서: https://pytorch.org
- JAX 공식 문서: https://docs.jax.dev