Developer's Development
3.2.16 [딥러닝] 인공신경망 본문
퍼셉트론
생물학적 뉴런을 모방한 간단한 인공지능 모델이다.
퍼셉트론은 논리 게이트를 모방할 수 있는 인공 신경망의 한 종류이다.
가중치와 편향을 통해 논리 게이트와 같은 동작을 한다.
- AND/NAND/OR 게이트
AND, NAND, OR 게이트는 퍼셉트론의 기본적인 논리 게이트를 구현한다. 각각의 게이트는 입력값에 대해 일정한 규칙을 적용하여 특정 조건을 만족할 때만 출력을 반환한다.
- AND 게이트 : 두 입력이 모두 1일 때만 출력이 1이 된다. 퍼셉트론 모델에서는 가중치와 편향 값을 설정하여 이를 구현할 수 있다.
- NAND 게이트 : AND 게이트의 출력을 뒤집은 형태로, 두 입력이 모두 1일 때만 출력이 0이 된다.
- OR 게이트 : 두 입력 중 하나라도 1이면 출력이 1이 된다.
import numpy as np
import matplotlib.pyplot as plt
class Perceptron:
def __init__(self, weights, bias):
self.weights = weights
self.bias = bias
# perceptron 연산
def activate(self, x):
return 1 if np.dot(self.weights, x) + self.bias > 0 else 0
test_case = [(0, 0), (0, 1), (1, 0), (1, 1)]
AND_gate = Perceptron(weights=[0.5, 0.5], bias=-0.7)
for test in test_case:
print(f"input: {test} | output: {AND_gate.activate(test)}")
OR_gate = Perceptron(weights=[0.5, 0.5], bias=-0.2)
for test in test_case:
print(f"input: {test} | output: {OR_gate.activate(test)}")
NAND_gate = Perceptron(weights=[-0.5, -0.5], bias=0.7)
for test in test_case:
print(f"input: {test} | output: {NAND_gate.activate(test)}")
# 시각화
inputs = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
gates = {
"AND": AND_gate,
"OR": OR_gate,
"NAND": NAND_gate
}
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
for idx, (gate_name, perceptron) in enumerate(gates.items()):
ax = axes[idx]
outputs = np.array([perceptron.activate(x) for x in inputs])
# 각 데이터포인트 시각화
for (x1, x2), y in zip(inputs, outputs):
ax.scatter(x1, x2, c='red' if y==1 else 'blue', s=100, edgecolor='black')
# 결정경계 시각화
x_vals = np.linspace(-0.1, 1.1, 100)
y_vals = (-perceptron.weights[0] * x_vals - perceptron.bias) / perceptron.weights[1]
ax.plot(x_vals, y_vals, 'k--')
ax.set_xlim(-0.1, 1.1)
ax.set_ylim(-0.1, 1.1)
ax.set_xticks([0, 1])
ax.set_yticks([0, 1])
ax.set_title(f'{gate_name} Gate')
ax.set_xlabel('x1')
ax.set_ylabel('x2')
plt.show()

- XOR Gate
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 0])
plt.figure(figsize=(4, 4))
plt.scatter(X[:, 0], X[:, 1], c=y, s=200, edgecolors='black', cmap='coolwarm')
plt.show()

- 가중치와 편향 구현
가중치 : 입력에 대해 얼마나 중요한지 결정하는 값
편향 : 출력 값을 조정하는 역할 (이를 통해 퍼셉트론의 결정을 유연하게 만듦)
- 선형과 비선형 모델
퍼셉트론은 선형 모델이다. (입력값과 가중치의 선형 결합에 의해 출력 결정)
→ 단층 퍼셉트론만으로는 XOR과 같은 비선형 문제를 해결할 수 없다.
▶ 이를 극복하기 위해 비선형 활성화 함수와 다층 구조가 필요하다.
- 다층 퍼셉트론 (Multi-Layer Preceptron, MLP)
여러 개의 퍼셉트론을 연결하여 비선형 문제를 해결한다. 입력층, 은닉층, 출력층을 가지고 있으며 은닉층에서 비선형 활성화 함수를 사용하여 복잡한 패턴을 학습할 수 있다.
각 층은 활성화 함수를 사용하여 출력을 계산한다.
# 여러 개 퍼셉트론 조합으로 XOR 문제 해결해보기
def XOR_gate(x1, x2):
or_out = OR_gate.activate((x1, x2))
nand_out = NAND_gate.activate((x1, x2))
return AND_gate.activate((nand_out, or_out))
for test in test_case:
print(f"input: {test} | output: {XOR_gate(test[0], test[1])}")
from sklearn.neural_network import MLPClassifier
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 0])
mlp = MLPClassifier(
hidden_layer_sizes=(4, 4), # 은닉층 크기
activation='relu', # 활성화 함수
solver='adam', # 가중치 업데이트 방식(최적화 알고리즘)
max_iter=1000, # 모델의 학습 반복 횟수(epoch)
random_state=42 # 가중치 초기화 값 고정
)
mlp.fit(X, y)
pred = mlp.predict(X)
for i in range(4):
print(f"XOR({X[i][0]}, {X[i][1]}) = {pred[i]}")
# 은닉층 크기에 따른 결과 확인
hidden_layers = [(1,), (2,), (4,), (8,)]
for config in hidden_layers:
mlp = MLPClassifier(
hidden_layer_sizes=config, # 은닉층 크기
activation='relu', # 활성화 함수
solver='adam', # 가중치 업데이트 방식(최적화 알고리즘)
max_iter=1000, # 모델의 학습 반복 횟수(epoch)
random_state=42 # 가중치 초기화 값 고정
)
mlp.fit(X, y)
pred = mlp.predict(X)
print(f"은닉층 구조 {config} -> XOR 예측 결과: {pred}")
"""
은닉층 구조 (1,) -> XOR 예측 결과: [0 1 0 1]
은닉층 구조 (2,) -> XOR 예측 결과: [1 1 1 1]
은닉층 구조 (4,) -> XOR 예측 결과: [0 0 0 0]
은닉층 구조 (8,) -> XOR 예측 결과: [0 1 1 0]
"""
활성화 함수
import numpy as np
import matplotlib.pyplot as plt
# 시각화를 위한 X 값
X = np.linspace(-5, 5, 100)
- 시그모이드 함수
출력값을 0과 1 사이로 압축하는 비선형 함수이다. 주로 이진 분류에서 사용된다.
단, 시그모이드 함수는 경사하강법을 사용하여 학습할 때 기울기가 매우 작은 값으로 변할 수 있어, 학습이 느려지는 기울기 소실 (Vanishing Gradient) 문제가 발생할 수 있다.
매끄러운 S자 곡선 형태로 확률 해석이 가능하다.
def sigmoid(x):
return 1 / (1 + np.exp(-x))
plt.figure(figsize=(5, 3))
plt.plot(X, sigmoid(X))
plt.title('Sigmoid Function')
plt.grid()
plt.ylim(-0.1, 1.1)
plt.show()

- 계단 함수
입력값이 0(일정 임계값)보다 크면 1, 작거나 같으면 0을 반환하는 함수이다. 퍼셉트론에서 출력층으로 사용된다.
비선형 문제를 해결할 수 있지만, 미분이 불가능하여 신경망 학습에 사용하기 어렵다.
작은 변화에도 값이 급격히 변하여 역전파 학습에 부적합하다.
def step_function(x):
return np.where(x >= 0, 1, 0)
plt.figure(figsize=(5, 3))
plt.plot(X, step_function(X))
plt.title('Step Function')
plt.grid()
plt.ylim(-0.1, 1.1)
plt.show()

- ReLU 함수
ReLU(Rectified Linear Unit)는 입력값이 음수나 0이면 0으로 변환하고, 양수는 그대로 출력한다. 비선형성을 제공하면서도 계산이 간단하여 딥러닝에서 자주 사용된다.
시그모이드에서 발생할 수 있는 기울기 소실 문제를 해결하고 학습 속도를 빠르게 만든다.
음수 입력에 대해 0을 출력하여 계산이 간단하고 학습이 빠르지만, 죽은 뉴런(Dying ReLU) 문제가 발생할 수 있다.
def relu(x):
return np.maximum(0, x)
plt.figure(figsize=(5, 3))
plt.plot(X, relu(X))
plt.title('ReLU Function')
plt.grid()
plt.show()

- Leaky ReLU (Leaky Rectified Linear Unit)
ReLU의 변형된 형태로, ReLU의 단점 죽은 뉴런 문제를 해결하기 위해 고안되었다.
음수 입력에 작은 기울기 alpha를 적용한다. (일반적으로 0.01을 사용)
def leaky_relu(x, alpha=0.01):
return np.where(x >= 0, x, alpha * x)
plt.figure(figsize=(5, 3))
plt.plot(X, leaky_relu(X, alpha=0.1))
plt.title('Leaky ReLU Function')
plt.grid()
plt.show()

- Tanh (Hyperbolic Tangent)
시그모이드(Sigmoid) 함수의 변형된 형태로, 출력 범위가 -1에서 1까지 확장된 함수이다.
시그모이드보다 중심이 0에 가까워 더 빠른 학습 진행이 가능하고, 기울기 소실 문제가 발생할 수 있다.
def tanh(x):
# return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))
return np.tanh(x)
plt.figure(figsize=(5, 3))
plt.plot(X, tanh(X))
plt.title('Tanh Function')
plt.grid()
plt.show()

import pandas as pd
np.random.seed(42)
X = np.random.uniform(-5, 5, size=10)
df = pd.DataFrame({
"input (X)": X,
"Step Function": step_function(X),
"Sigmoid": sigmoid(X),
"Tanh": tanh(X),
"ReLU": relu(X),
"Leaky ReLU": leaky_relu(X)
})
plt.figure(figsize=(8, 6))
plt.plot(df["input (X)"], df["Step Function"], marker="o", label="step function")
plt.plot(df["input (X)"], df["Sigmoid"], marker="*", label="Sigmoid")
plt.plot(df["input (X)"], df["Tanh"], marker="^", label="Tanh")
plt.plot(df["input (X)"], df["ReLU"], marker="d", label="ReLU")
plt.plot(df["input (X)"], df["Leaky ReLU"], marker="p", label="Leaky ReLU")
plt.xlabel('input (X)')
plt.ylabel('Activation output')
plt.legend()
plt.grid()
plt.show()

| 함수 | 출력 범위 | 비선형성 | 미분 기능 | 장점 | 단점 | 주요 사용처 |
| Sigmoid | (0, 1) | O | O | 이진 분류 출력층 | 기울기 소실 | 출력층 (Binary) |
| Tanh | (-1, 1) | O | O | 중심화, 학습 빠름 | 기울기 소실 | RNN 등 시계열 |
| ReLU | (0, ∞) | O | O | 계산 단순, 학습 빠름 | Dying ReLU | CNN, DNN 중간층 |
| Leaky ReLU | (- ∞, ∞) | O | O | Dying ReLU 보완 | α 설정 필요 | ReLU 대체용 |
| Step | (0, 1) | O | X | 개념적 단순성 | 학습 불가 | 이론적 개념 (X) |
다차원 배열의 계산
- 다차원 배열
여러 개의 차원을 가지는 배열로, 2차원 이상의 배열을 의미한다. 인공지능에서는 주로 행렬(Matrix) 또는 텐서(Tensor)라는 용어를 사용한다. 파이썬에서는 numpy 라이브러리를 활용하여 다차원 배열을 쉽게 다룰 수 있다.
딥러닝에서 자주 다루는 이미지 처리 문제와 관련할 때도 보통 4차원 텐서로 이미지 데이터를 표현한다.
- 다차원 배열의 계산 복습
import numpy as np
A = np.array([[1, 2, 3],
[4, 5, 6]])
B = np.array([[7, 8, 9],
[10, 11, 12]])
# 단순 합
print(A + B) # (2, 3)
# 브로드캐스팅 연산
vec = np.array([1, 2, 3])
print(A + vec) # (2, 3)
# 점곱(내적) with 전치
print(A.dot(B.T)) # (2, 2)
- 다층 퍼셉트론
X = np.random.randn(6, 10)
# 은닉층: 10개의 입력 -> 8개의 뉴런
W1 = np.random.randn(10, 8) # 가중치
b1 = np.random.randn(1, 8) # 편향
# 출력층: 8개의 입력 -> 4개의 클래스
W2 = np.random.randn(8, 4)
b2 = np.random.randn(1, 4)
# 은닉층 계산 (선형 계산 + tanh 활성화 함수)
z1 = np.dot(X, W1) + b1
result1 = np.tanh(z1)
# 출력층 계산 (선형 계산 + softmax 활성화 함수)
z2 = np.dot(result1, W2) + b2
exp_z = np.exp(z2 - np.max(z2, axis=1, keepdims=True))
result2 = exp_z / np.sum(exp_z, axis=1, keepdims=True)
print(result2)
- 이미지 데이터 예시
import matplotlib.pyplot as plt
# 이미지 생성
batch_size = 5
channels = 3 # 3=RGB, 4=PNG(투명도까지)
height = 32
width = 32
images = np.random.rand(batch_size, channels, height, width)
bright_images = np.clip(images + 0.2, 0, 1)
gray_images = np.mean(images, axis=1)
images_for_display = images[0].transpose(1, 2, 0)
bright_images_for_display = bright_images[0].transpose(1, 2, 0)
plt.subplot(1, 3, 1)
plt.imshow(images_for_display)
plt.axis('off')
plt.subplot(1, 3, 2)
plt.imshow(bright_images_for_display)
plt.axis('off')
plt.subplot(1, 3, 3)
plt.imshow(gray_images[0], cmap='gray')
plt.axis('off')
plt.show()

출력층 설계
- 출력층 활성화 함수
주로 항등 함수, 시그모이드 함수, 소프트맥스 함수의 세 가지 활성화 함수가 사용된다.
👉🏻 세 가지 함수를 주로 사용하는 이유
1. 문제 유형에 최적화
- 항등 함수 (Identify Function) : 회귀 문제에서, 연속적인 값을 그대로 출력할 때 적합하다.
- 시그모이드 함수 (Sigmoid Function) : 이진 분류나 다중 레이블 분류에서, 각 클래스의 존재 확률을 0과 1 사이로 표현할 때 유용하다.
- 소프트맥스 함수 (Softmax Function) : 다중 클래스 분류에서, 각 클래스에 대한 예측 확률 분포를 생성하기 위해 사용된다.
2. 미분 가능성 및 학습의 안정성
- 세 함수 모두 미분 가능하여 역전파(backpropagation) 알고리즘에 적합하다.
- 문제 유형에 따라 적절한 함수 선택이 학습의 수렴과 안정성을 보장한다.
3. 수학적, 실무적 검증
- 각 함수는 해당 문제에 대해 오랜 연구와 실제 적용에서 최적의 성능을 보임으로써 표준으로 자리 잡았다.
- 항등 함수
입력 값을 그대로 출력하는 함수이다.
신경망의 회귀 문제에서는 출력층에서 항등 함수를 사용하여 연속적인 값을 그대로 출력한다.
- 시그모이드 함수
이진 분류나 다중 레이블 분류 문제에서 주로 샤용되는 활성화 함수이다.
이진 분류 문제에서 출력층에 시그모이드 함수를 적용하면, 각 뉴런의 출력이 특정 클래스에 속할 확률로 해석되며, 다중 레이블 분류에서도 각 레이블에 대한 존재 여부를 예측할 수 있다.
- 소프트맥스 함수
분류 문제에서 출력증에서 사용되는 활성화 함수이다.
입력 값을 정규화하여 각 클래스에 대한 확률 분포를 생성한다.
- 출력층의 뉴런 수
출력층의 뉴런 수는 해결하려는 문제에 따라 결정된다.
- 회귀 문제 : 1개 (연속된 값 출력)
- 이진 분류 : 1개 또는 2개 (시그모이드 사용 시 1개, 소프트맥스 사용 시 2개)
- 다중 클래스 분류 : 클래스 개수와 동일한 뉴런 개수 (소프트맥스 사용)
- torch 사용을 위한 가상환경 생성
👉🏻 아래 사이트에서 PC 환경에 맞게 Compute Platform 선택(CPU) 후 Run this Command 부분 복사
PyTorch
PyTorch Foundation is the deep learning community home for the open source PyTorch framework and ecosystem.
pytorch.org
conda create -n torch_env python=3.12
conda activate torch_env
pip install jupyter notebook ipykernel numpy pandas matplotlib seaborn
python -m ipykernel install --user --name torch_env --display-name torch_env
pip3 install torch torchvision torchaudio
- 소프트맥스 오버플로우 방지
import numpy as np
def softmax(z):
exp_z = np.exp(z)
return exp_z / np.sum(exp_z)
def stable_softmax(z):
exp_z = np.exp(z - np.max(z))
return exp_z / np.sum(exp_z)
x = np.array([1000, 1001, 1002])
print(softmax(x)) # [nan nan nan]
print(stable_softmax(x)) # [0.09003057 0.24472847 0.66524096]
👉🏻 pytorch 라이브러리 사용
import torch
import torch.nn.functional as F
x = torch.tensor([1000, 1001, 1002], dtype=torch.float32)
softmax_output = F.softmax(x)
print(softmax_output) # tensor([0.0900, 0.2447, 0.6652])
sigmoid_output = torch.sigmoid(x)
print(sigmoid_output) # tensor([1., 1., 1.])
- 손실 함수와 연계
import torch
import torch.nn as nn
import torch.optim as optim
# 간단한 다중 클래스 분류 모델 정의
class SimpleMultiClassModel(nn.Module):
def __init__(self):
super(SimpleMultiClassModel, self).__init__()
self.fc = nn.Linear(5, 3)
def forward(self, x):
return self.fc(x)
# 모델, 손실함수, 최적화함수 설정
model = SimpleMultiClassModel()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)
# 데이터 생성
inputs = torch.randn(4, 5)
labels = torch.tensor([0, 2, 1, 0])
# 학습
for _ in range(10):
preds = model(inputs) # 순전파
loss = criterion(preds, labels) # 손실 계산
print(loss.item())
optimizer.zero_grad() # 기울기 초기화 (이전 단계에서 계산된 기울기를 0으로 초기화)
loss.backward() # 역전파 (손실에 대한 역전파 수행 - 파라미터에 대한 기울기 계산)
optimizer.step() # 가중치 업데이트 (계산된 기울기를 사용하여 옵티마이저가 모델 파라미터 갱신)
'데이터 분석과 머신러닝, 딥러닝 > 딥러닝' 카테고리의 다른 글
| 3.2.21 [딥러닝] 최적 모델 학습 (과적합 해결) (5) | 2025.08.07 |
|---|---|
| 3.2.20 [딥러닝] 최적 모델 학습 (최적화 함수) (2) | 2025.08.06 |
| 3.2.19 [딥러닝] 최적 모델 학습 (오차역전파법) (1) | 2025.08.05 |
| 3.2.18 [딥러닝] 인공신경망(2) (4) | 2025.08.04 |
| 3.2.15 [딥러닝] 개요 (2) | 2025.08.03 |