이진·다중 분류, 배치 학습, 퍼셉트론 한 번에
논리 회귀(Logistic Regression)로 이진·다중 분류를 구현하고, 손글씨 숫자 데이터셋으로 배치 단위 학습(DataLoader)과 데이터 증강을 적용해 본다. 이어서 퍼셉트론과 다층 퍼셉트론(MLP) 으로 AND·OR·XOR 문제를 풀고, 비선형 활성화 함수(Sigmoid, Tanh, ReLU, Softmax)까지 정리한다.
(파이토치로 구현한 논리 회귀, 손 글씨 숫자 데이터셋, 딥러닝: 퍼셉트론과 다층 퍼셉트론을 기반으로 재구성)
1. 논리 회귀 (Logistic Regression)
논리 회귀는 입력 데이터를 두 개 이상의 범주로 분류하는 지도 학습 알고리즘이다. 주로 이진 분류에 쓰이며, 입력의 선형 결합을 시그모이드로 0~1 확률로 바꾼 뒤, 임계값(보통 0.5)으로 클래스를 정한다. 스팸 여부, 병 진단 등에 활용되며, 계산이 단순하고 해석이 쉽다.
※ 시그모이드 함수
시그모이드(Sigmoid)는 입력을 0과 1 사이로 압축하는 함수다. 확률로 해석할 수 있어 이진 분류의 출력에 많이 쓴다. (e: 자연상수 ≈ 2.718)
1-1. 단항 논리 회귀 (이진 분류)
입력 변수 하나로 합격/불합격 같은 이진 분류를 하는 경우다.
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
# 선형 결합 + 시그모이드 예시
x = torch.tensor([1.0, 2.0, 3.0])
w = torch.tensor([0.1, 0.2, 0.3])
b = torch.tensor(0.5)
z = torch.dot(w, x) + b
sigmoid = nn.Sigmoid()
output = sigmoid(z)
print(z, output)
torch.manual_seed(2026)
x_train = torch.FloatTensor([[0], [1], [3], [5], [8], [11], [15], [20]])
y_train = torch.FloatTensor([[0], [0], [0], [0], [1], [1], [1], [1]])
print(x_train.shape, y_train.shape)
plt.figure(figsize=(8, 5))
plt.scatter(x_train, y_train)
모델은 nn.Linear(1, 1) + nn.Sigmoid()로 구성한다.
model = nn.Sequential(
nn.Linear(1, 1),
nn.Sigmoid()
)
print(model)
print(list(model.parameters()))
y_pred = model(x_train)
print(y_pred)
※ BCE 손실 함수
Binary Cross Entropy(BCE) 는 이진 분류에서 예측 확률과 정답(0 또는 1)의 차이를 측정한다. 예측이 정답에 가까울수록 손실이 작아진다.
loss = nn.BCELoss()(y_pred, y_train)
loss
optimizer = optim.SGD(model.parameters(), lr=0.01)
epochs = 1000
for epoch in range(epochs + 1):
y_pred = model(x_train)
loss = nn.BCELoss()(y_pred, y_train)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoch % 100 == 0:
print(f'Epoch: {epoch}/{epochs} Loss: {loss:.6f}')
print(list(model.parameters()))
x_test = torch.FloatTensor([[10]])
y_pred = model(x_test)
print(y_pred)
y_bool = (y_pred >= 0.5).float() # 임계값 0.5로 이진 레이블
print(y_bool)
1-2. 다항 논리 회귀 (다중 분류)
종속 변수가 세 개 이상 범주일 때는 다항 논리 회귀를 쓴다. Softmax로 각 클래스 확률을 구하고, CrossEntropyLoss로 학습한다.
x_train = [[1, 2, 1, 1], [2, 1, 3, 2], [3, 1, 3, 4], [4, 2, 5, 5],
[1, 6, 5, 5], [1, 4, 5, 8], [1, 7, 7, 7], [2, 8, 7, 8],
[2, 7, 6, 7], [2, 6, 6, 6]]
y_train = [0, 0, 0, 1, 1, 1, 2, 2, 2, 2]
x_train = torch.FloatTensor(x_train)
y_train = torch.LongTensor(y_train)
print(x_train.shape, y_train.shape)
model = nn.Sequential(
nn.Linear(4, 3) # 입력 4, 클래스 3
)
print(model)
print(list(model.parameters()))
y_pred = model(x_train)
print(y_pred)
※ CrossEntropyLoss와 Softmax
CrossEntropyLoss는 다중 클래스 분류에서 예측 분포와 정답 레이블의 차이를 잰다. Softmax는 각 클래스 점수를 확률로 바꾸며, 모든 클래스 확률의 합이 1이 된다.
loss = nn.CrossEntropyLoss()(y_pred, y_train)
loss
optimizer = optim.SGD(model.parameters(), lr=0.01)
epochs = 10000
for epoch in range(epochs + 1):
y_pred = model(x_train)
loss = nn.CrossEntropyLoss()(y_pred, y_train)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoch % 1000 == 0:
print(f'Epoch: {epoch}/{epochs} Loss: {loss:.6f}')
예측 시에는 로짓에 Softmax를 적용해 확률을 보고, argmax로 클래스를 고른다.
x_test = torch.FloatTensor([[2, 8, 8, 8]])
y_pred = model(x_test)
print(y_pred)
# dim=1: 클래스 축 방향, dim=0: 배치 축 방향
y_prob = nn.Softmax(dim=1)(y_pred)
print(y_prob)
print(f'클래스 0 확률: {y_prob[0][0]:.2f}')
print(f'클래스 1 확률: {y_prob[0][1]:.2f}')
print(f'클래스 2 확률: {y_prob[0][2]:.2f}')
print(torch.argmax(y_prob, dim=1)) # 예측 클래스
2. 손글씨 숫자 데이터셋 (load_digits)
손글씨 숫자 데이터셋은 0
9 숫자를 손으로 쓴 8×8 픽셀 흑백 이미지로, 각 픽셀은 0(흰색)
16(검은색) 명암값을 가진다. 샘플은 64차원 벡터로 표현되며, 총 1797개이고 클래스 레이블(0~9)이 붙어 있다. 분류 학습, 시각화, 차원 축소 실험에 자주 쓴다.
2-1. 데이터 로드 및 시각화
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from torch.utils.data import DataLoader
digits = load_digits()
X_data = digits['data']
y_data = digits['target']
print(X_data.shape) # (1797, 64)
print(y_data.shape) # (1797,)
fig, axes = plt.subplots(nrows=2, ncols=5, figsize=(14, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(X_data[i].reshape((8, 8)), cmap='gray')
ax.set_title(y_data[i])
ax.axis('off')
2-2. 텐서 변환 및 train/test 분할
X_data = torch.FloatTensor(X_data)
y_data = torch.LongTensor(y_data)
X_train, X_test, y_train, y_test = train_test_split(
X_data, y_data, test_size=0.2, random_state=2026
)
print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)
2-3. 데이터 로더 (DataLoader)
데이터 로더는 데이터셋을 배치 단위로 나눠 모델에 넘겨 주는 도구다. 대용량 데이터도 메모리를 아끼며 처리할 수 있고, 셔플·병렬 로드·반복 제공을 지원한다.
※ 데이터 로더의 주요 역할
- 배치 처리: 지정한 크기(batch_size)로 나눠 제공
- 셔플링: 순서를 무작위로 섞어 과적합 완화
- 병렬 처리:
num_workers로 로딩 속도 향상 - 반복 처리: epoch 동안 자동으로 배치 반복
loader = DataLoader(
dataset=list(zip(X_train, y_train)),
batch_size=64,
shuffle=True,
drop_last=False
)
imgs, labels = next(iter(loader))
print(imgs.shape, labels.shape)
※ axes.flatten()
plt.subplots()가 반환하는 서브플롯 배열은 2차원이다. axes.flatten()으로 1차원으로 펼치면 인덱스 하나로 순회하기 편하다.
fig, axes = plt.subplots(nrows=8, ncols=8, figsize=(14, 14))
for ax, img, label in zip(axes.flatten(), imgs, labels):
ax.imshow(img.reshape((8, 8)), cmap='gray')
ax.set_title(str(label.item()))
ax.axis('off')
2-4. 모델 학습 (배치 단위)
입력 64차원, 출력 10클래스이므로 nn.Linear(64, 10) + CrossEntropyLoss로 학습한다.
model = nn.Sequential(
nn.Linear(64, 10)
)
optimizer = optim.Adam(model.parameters(), lr=0.01)
epochs = 50
for epoch in range(epochs + 1):
sum_losses = 0
sum_accs = 0
for x_batch, y_batch in loader:
y_pred = model(x_batch)
loss = nn.CrossEntropyLoss()(y_pred, y_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
sum_losses = sum_losses + loss
y_prob = nn.Softmax(dim=1)(y_pred)
y_pred_index = torch.argmax(y_prob, dim=1)
acc = (y_batch == y_pred_index).float().sum() / len(y_batch) * 100
sum_accs = sum_accs + acc
avg_loss = sum_losses / len(loader)
avg_acc = sum_accs / len(loader)
if epoch % 10 == 0:
print(f'Epoch {epoch:4d}/{epochs} Loss: {avg_loss:.6f} Accuracy: {avg_acc:.2f}%')
2-5. 예측 및 정확도
모델 출력은 로짓(logit) 값이고, Softmax를 거치면 확률이 된다.
idx = 129
plt.imshow(X_test[idx].reshape((8, 8)), cmap='gray')
print('정답:', y_test[idx].item())
y_pred = model(X_test)
print('로짓(일부):', y_pred[idx])
y_prob = nn.Softmax(dim=1)(y_pred)
print('확률:', y_prob[idx])
for i in range(10):
print(f'숫자 {i}일 확률: {y_prob[idx][i]:.2f}')
y_pred_index = torch.argmax(y_prob, dim=1)
accuracy = (y_test == y_pred_index).float().sum() / len(y_test) * 100
print(f'테스트 정확도: {accuracy:.2f}%')
3. 데이터 증강 (Data Augmentation)
데이터 증강은 학습 데이터에 회전·확대·비틀기 등을 적용해 데이터 다양성을 키우는 기법이다. 데이터 부족을 완화하고, 특정 패턴에의 과적합을 줄여 일반화를 돕는다. 이미지·음성에서 많이 쓴다.
3-1. TensorDataset과 transforms
from torchvision import transforms
from torch.utils.data import TensorDataset, Dataset
X_train, X_test, y_train, y_test = train_test_split(
X_data, y_data, test_size=0.2, random_state=2026
)
train_dataset = TensorDataset(X_train, y_train)
test_dataset = TensorDataset(X_test, y_test)
transform = transforms.Compose([
transforms.RandomRotation(10), # -10° ~ +10° 무작위 회전
transforms.RandomAffine(0, shear=5, scale=(0.9, 1.1)) # shear·scale 변환
])
※ transforms.Compose
여러 변환(transform) 을 순서대로 적용하게 해 준다. 이미지 전처리·증강에서 자주 쓴다.
- RandomRotation(10): 이미지를 -10° ~ +10° 범위에서 무작위 회전
- RandomAffine(0, shear=5, scale=(0.9, 1.1))
0: 회전 없음shear=5: 최대 5° 비틀기 (평행사변형처럼)scale=(0.9, 1.1): 0.9배~1.1배 무작위 크기 조정
3-2. 커스텀 AugmentedDataset
torchvision 변환은 보통 이미지가 (C, H, W) 형태일 때 쓰므로, 64차원 벡터를 8×8로 바꾼 뒤 증강하고 다시 flatten 한다.
class AugmentedDataset(Dataset):
def __init__(self, dataset, transform):
self.dataset = dataset
self.transform = transform
def __len__(self):
return len(self.dataset)
def __getitem__(self, idx):
x, y = self.dataset[idx]
x = x.view(8, 8).unsqueeze(0) # (1, 8, 8) 형태로 변환
x = self.transform(x) # 증강 적용
return x.flatten(), y # 다시 (64,)로
augmented_train_dataset = AugmentedDataset(train_dataset, transform)
3-3. 증강 데이터로 학습
train_loader = DataLoader(augmented_train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
imgs, labels = next(iter(train_loader))
fig, axes = plt.subplots(nrows=8, ncols=8, figsize=(14, 14))
for ax, img, label in zip(axes.flatten(), imgs, labels):
ax.imshow(img.reshape((8, 8)), cmap='gray')
ax.set_title(str(label.item()))
ax.axis('off')
model = nn.Sequential(nn.Linear(64, 10))
optimizer = optim.Adam(model.parameters(), lr=0.01)
epochs = 50
for epoch in range(epochs + 1):
sum_losses = 0
sum_accs = 0
for x_batch, y_batch in train_loader:
y_pred = model(x_batch)
loss = nn.CrossEntropyLoss()(y_pred, y_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
sum_losses = sum_losses + loss
y_prob = nn.Softmax(dim=1)(y_pred)
y_pred_index = torch.argmax(y_prob, dim=1)
acc = (y_batch == y_pred_index).float().sum() / len(y_batch) * 100
sum_accs = sum_accs + acc
if epoch % 10 == 0:
avg_loss = sum_losses / len(train_loader)
avg_acc = sum_accs / len(train_loader)
print(f'Epoch {epoch:4d}/{epochs} Loss: {avg_loss:.6f} Accuracy: {avg_acc:.2f}%')
4. 퍼셉트론과 다층 퍼셉트론
4-1. 생물학적 뉴런
뉴런은 수상돌기(dendrite) 로 신호를 받고, 세포체(cell body) 에서 처리한 뒤 축삭(axon) 과 시냅스를 통해 다음 뉴런이나 조직으로 전달한다. 이 구조를 모방한 것이 인공 신경망이다.
4-2. 인공 신경망 (ANN)
인공 신경망(ANN) 은 입력층·은닉층·출력층으로 이루어지며, 가중치와 활성화 함수로 복잡한 패턴을 학습한다. 이미지 분류, 음성 인식, 자연어 처리 등에 쓰인다.
4-3. 퍼셉트론
퍼셉트론은 1958년 로젠블렛이 제안한 단순한 인공 뉴런이다. 입력과 가중치의 곱의 합에 편향을 더하고, 활성화 함수(예: 계단 함수·시그모이드)를 적용해 이진 출력(0 또는 1)을 낸다. 선형 분리 가능한 문제(AND, OR)는 풀 수 있지만, XOR 같은 비선형 문제는 단층으로는 풀 수 없다.
4-4. AND, OR, XOR (단층 퍼셉트론)
논리 회귀(단층 퍼셉트론)로 AND·OR은 잘 학습되지만, XOR은 단층으로는 정확도가 올라가지 않는다.
import torch
import torch.nn as nn
import torch.optim as optim
# AND
X = torch.FloatTensor([[0, 0], [0, 1], [1, 0], [1, 1]])
y = torch.FloatTensor([[0], [0], [0], [1]])
model = nn.Sequential(
nn.Linear(2, 1),
nn.Sigmoid()
)
optimizer = optim.SGD(model.parameters(), lr=1)
epochs = 1000
for epoch in range(epochs + 1):
y_pred = model(X)
loss = nn.BCELoss()(y_pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoch % 100 == 0:
y_bool = (y_pred >= 0.5).float()
acc = (y == y_bool).float().sum() / len(y) * 100
print(f'Epoch {epoch:4d}/{epochs} Loss: {loss:.6f} Accuracy: {acc:.2f}%')
OR은 y = [[0], [1], [1], [1]] 로만 바꾸면 된다. XOR은 같은 단층 구조로는 Loss가 잘 줄지 않고 Accuracy도 50% 근처에 머문다.
4-5. 다층 퍼셉트론 (MLP)과 XOR
다층 퍼셉트론(MLP) 은 입력층·은닉층(1개 이상)·출력층으로 이루어진다. 비선형 활성화 함수(Sigmoid, ReLU 등)를 쓰면 비선형 경계를 학습할 수 있어 XOR을 풀 수 있다. 역전파(Backpropagation) 로 출력층에서 입력층 방향으로 오차를 전파하며 가중치를 갱신한다.
model = nn.Sequential(
nn.Linear(2, 2),
nn.Sigmoid(), # 은닉층 비선형
nn.Linear(2, 1),
nn.Sigmoid() # 출력 확률
)
print(model)
# XOR
X = torch.FloatTensor([[0, 0], [0, 1], [1, 0], [1, 1]])
y = torch.FloatTensor([[0], [1], [1], [0]])
optimizer = optim.SGD(model.parameters(), lr=1)
epochs = 1000
for epoch in range(epochs + 1):
y_pred = model(X)
loss = nn.BCELoss()(y_pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoch % 100 == 0:
y_bool = (y_pred >= 0.5).float()
acc = (y == y_bool).float().sum() / len(y) * 100
print(f'Epoch {epoch:4d}/{epochs} Loss: {loss:.6f} Accuracy: {acc:.2f}%')
※ 오차 역전파 (Backpropagation)
순전파로 예측을 구하고, 손실 함수로 오차를 계산한 뒤, 체인 룰으로 각 층의 가중치가 오차에 기여한 정도(그래디언트)를 구한다. 이 그래디언트로 경사 하강법을 적용해 가중치를 업데이트한다.
5. 비선형 활성화 함수
선형 변환만으로는 복잡한 패턴을 표현하기 어렵다. 비선형 활성화 함수를 층 사이에 넣어야 비선형 경계와 복잡한 함수를 학습할 수 있다.
5-1. 시그모이드 (Sigmoid)
입력을 0~1로 압축한다. 이진 분류 출력·확률 해석에 쓰인다. 입력이 극단적이면 기울기가 0에 가까워져 그래디언트 소실이 생길 수 있다.
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.axvline(x=0, color='gray', linestyle=':')
plt.title('Sigmoid Function')
plt.show()
5-2. 하이퍼볼릭 탄젠트 (Tanh)
입력을 -1~1로 압축한다. 0 중심이라 Sigmoid보다 출력 분포가 대칭에 가깝다. 역시 극단 구간에서 기울기 소실이 있을 수 있다.
x = np.arange(-5.0, 5.0, 0.1)
y = np.tanh(x)
plt.plot(x, y)
plt.axvline(x=0, color='gray', linestyle=':')
plt.axhline(y=0, color='orange', linestyle='--')
plt.title('Tanh Function')
plt.show()
5-3. ReLU (Rectified Linear Unit)
입력이 0보다 크면 그대로, 0 이하면 0을 출력한다. 양수 구간에서 기울기가 1로 유지되어 기울기 소실을 완화하고, 깊은 네트워크에서 많이 쓴다. 0 이하에서는 기울기가 0이 되는 죽은 ReLU 문제가 있을 수 있어, Leaky ReLU 등 변형도 쓴다.
def relu(x):
return np.maximum(0, x)
x = np.arange(-5.0, 5.0, 0.1)
y = relu(x)
plt.plot(x, y)
plt.axvline(x=0, color='gray', linestyle=':')
plt.title('ReLU Function')
plt.show()
5-4. 소프트맥스 (Softmax)
다중 클래스 분류의 출력층에서 쓴다. 각 클래스 로짓을 지수 취한 뒤 합으로 나누어 확률 분포로 만든다. 모든 클래스 확률의 합은 1이다.
마치며
- 논리 회귀: 이진(시그모이드 + BCE), 다중(로짓 + CrossEntropyLoss, 예측 시 Softmax + argmax)으로 분류를 구현할 수 있다.
- 손글씨 숫자:
load_digits+ DataLoader로 배치 학습을 하고, 데이터 증강(RandomRotation, RandomAffine)으로 일반화를 도울 수 있다. - 퍼셉트론: 단층은 선형 분리 가능(AND, OR)만 가능하고, 다층 퍼셉트론(MLP) 과 비선형 활성화 함수로 XOR 같은 비선형 문제를 풀 수 있다.
- 활성화 함수: Sigmoid, Tanh, ReLU, Softmax의 역할과 특성을 알아 두면 모델 설계에 도움이 된다.
다음 단계로는 CNN, RNN, Transformer 등 구조를 확장해 보면 좋다.
(논리 회귀 · 손 글씨 숫자 데이터셋 · 퍼셉트론과 다층 퍼셉트론 참고)
'AI·머신러닝 > 딥러닝·비전' 카테고리의 다른 글
| CNN과 손글씨 도형 분류 - 합성곱 신경망, 학습, FastAPI·Gradio 서빙 (0) | 2025.12.23 |
|---|---|
| Multi-class Weather 이미지 분류 실습 - ImageFolder, DataLoader, 완전연결 신경망 (0) | 2025.12.22 |
| 파이토치로 시작하는 딥러닝 - 텐서, 선형 회귀, 논리 회귀 실습 (0) | 2025.12.20 |
| 슈퍼스토어 마케팅 캠페인과 K-Means 클러스터링 - 고객 세그먼트 실습 (0) | 2025.12.17 |
| 서울 자전거 · 호텔 예약 수요 예측 실습 - 결정 트리, 랜덤 포레스트, 로지스틱 회귀, 교차 검증 (0) | 2025.12.16 |