딥러닝의 핵심인 신경망은 복잡한 함수를 단순한 함수들의 합성으로 표현할 수있는 수학적엔티티다. 신경망이라는 용어는 인간의 뇌를 연상시키고 신경과학분야의 영향을 초기에받긴 했지만 오늘날 인공신경마은 실제 뇌에있는 뉴런의 동작과는 조금만 비슷하다.
import torch
import torch.nn as nn
# -----------------------------
# 단순 신경망 (함수의 합성)
model = nn.Sequential(
nn.Linear(3, 8),
nn.ReLU(),
nn.Linear(8, 4),
nn.ReLU(),
nn.Linear(4, 1)
)
# -----------------------------
# 입력 (batch = 2)
x = torch.tensor([
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]
])
# -----------------------------
# forward (여러 함수의 합성)
y = model(x)
print("output:")
print(y)
print("shape:", y.shape)
뉴런. 본질적으로 뉴런은 단순히 입력에 대한 선형변환. 가중치를 곱하고 편향값을 더하는 활성함수라 부르는 고정된 비선형 함수를 적용하는 역할을 한다 .이러한 표현식은 뉴런 계층이라고 한다 .
import torch
import torch.nn as nn
# -----------------------------
# 뉴런(Neuron)의 핵심: 선형변환 + 비선형 활성함수
class SimpleNeuron(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(3, 1) # w * x + b
self.activation = nn.Sigmoid() # 비선형 함수
def forward(self, x):
z = self.linear(x) # 선형 변환 (wx + b)
a = self.activation(z) # 활성화 함수 적용
return a
# -----------------------------
# 입력
x = torch.tensor([
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]
])
model = SimpleNeuron()
out = model(x)
print(out)
높은 점수가 어느정도인지정의해야한다는 것이다. 10점만점으로 정의해도모델은 11점을 만들 수도 있다. 행렬곱과 행렬합이 존해하고 연산의 출력값이 알아서 특정 범위로 제한되지않는다. 이 경우 출력값을 제한할수있다. 0이하는 무조건 0으로 하고 10이상은 무조건 10으로 하면된다. 이가 torch.nn.Hardtanh 라는 단순한 활성함수다.
이와 유사하며 잘 동작하는 함수중 하나로 torch.nn.Sigmoid 가 있다. x가 음의 무한대로 가면 0이나 -1에 점차가까워지고 양의 무한대로 갈수록 1에가까워지는 곡선을 가지며0일떄는 상수기울기를 가진다. 개념적으로 함수는 매우 잘 동작한다.
import torch
import torch.nn as nn
# -----------------------------
# 입력값 (모델 출력 가정)
x = torch.linspace(-10, 10, 200)
# -----------------------------
# 1) Hardtanh: 값 범위 강제 제한 (클리핑)
hardtanh = nn.Hardtanh(min_val=0.0, max_val=10.0)
y_hardtanh = hardtanh(x)
# -----------------------------
# 2) Sigmoid: 0~1로 부드럽게 압축
sigmoid = nn.Sigmoid()
y_sigmoid = sigmoid(x)
print("Hardtanh output sample:", y_hardtanh[:10])
print("Sigmoid output sample:", y_sigmoid[:10])
# -----------------------------
# 시각화를 원하면 (옵션)
# import matplotlib.pyplot as plt
# plt.plot(x.numpy(), y_hardtanh.numpy(), label="Hardtanh")
# plt.plot(x.numpy(), y_sigmoid.numpy(), label="Sigmoid")
# plt.legend()
# plt.show()
지금까지 다룬 활성함수 외에도 상담수가있다. 그래프로도 확인해볼 수 있다.
Tanh 매끄러운 함수
haedtanh 각진함수 일부영역에 대해 여러 가중치와 편향값을 조합해 구간적인 선형근사를 만드는 데 사용할 수있다.
sigmoid 매끄러운 함수 로지스틱 함수라고도 하는데 초기 딥러닝에서 많이 사용했으니 이제는 출력값이 확률적일때 값을 0에서 1 사이로 옮기려는 경우 외에는 사용하지않는다.
softplus
relu 각진함수 통상활용함수들 중 가장성능이 좋다. 최근 연구결과에 많이 사용된다.
leakyrelu 표준 relu를 변형하여 입력이 음수일때 출력을 0 대신 약간의 양의기울기를 가지게 만든것.
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
# 입력 범위
x = torch.linspace(-10, 10, 500)
# -----------------------------
# 활성함수들
acts = {
"tanh": nn.Tanh(),
"hardtanh": nn.Hardtanh(),
"sigmoid": nn.Sigmoid(),
"softplus": nn.Softplus(),
"relu": nn.ReLU(),
"leakyrelu": nn.LeakyReLU(0.1),
}
# -----------------------------
# forward
ys = {name: f(x) for name, f in acts.items()}
# -----------------------------
# plot
plt.figure(figsize=(10, 6))
for name, y in ys.items():
plt.plot(x.numpy(), y.detach().numpy(), label=name)
plt.axhline(0, color="black", linewidth=0.5)
plt.axvline(0, color="black", linewidth=0.5)
plt.legend()
plt.title("Activation Functions")
plt.show()
실제에서 효과가 증명된 함수는더 많다. 활성함수는 흥미로운 영역이고 제한도 별로 없다. 어떤 경우에는 명백히 틀렸다고 입증 될 수있다. 활성함수는 비선형이고 미분이 가능하다. 기울기 계산이가능하다. 최소한 하나의 민감구간을 가지고 입력에서 중요범위가 변경되면 일치하는 출력영역에서 주요한 변화가생긴다. 훈련에서 필요한 구간이다. 대부분이 둔감한 구간을 가지고 이 구간에서는 입력의 변화가 출력에 거의 영향을 주지않거나 아예 변화가 없다. 역전파가 일어나는 것을 생각해봤을때 입력이 응답범위에 있을때 오차가활성단계를 통해 더 효과적으로 역방향으로 전파되어야하고 반대로 입력이 포화된 경우(출력값의 편평한 부분으로 인해기울기가 0 에 가까워지는 상황)에는 오차가 뉴런에 큰 영향을 미치지 않아야한다. 항상은 아니지만 입력이 무한대로 가거나 만나거나 가까워지는 지점이있고 양의 무한대로 갈때도 고점이 있다 .이러한 특성들을 모두 합치면 강력한 매커니즘이 만들어진다.신경망에 다른 입력이 나타나면 같은 입력이라도 반응하는 유닛이 다르고 각 유닛 별로다른 범위로 반응하며 학습 과정에서도 다른 유닛은 여향을 받지않으면서 입력에 대해 민감한 범위를 가지고 있는관련된 유닛만 오차값에 의해 영향받는다 또 입력에 대해 활성화된 곳에서 1에 가까운 값을 가지는 경우가 빈번한데 선형식에서 맞춰간다.
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
# 입력
x = torch.linspace(-10, 10, 1000)
# -----------------------------
# 활성함수들
relu = nn.ReLU()
leaky_relu = nn.LeakyReLU(0.1)
sigmoid = nn.Sigmoid()
tanh = nn.Tanh()
softplus = nn.Softplus()
# -----------------------------
# forward
y_relu = relu(x)
y_leaky = leaky_relu(x)
y_sigmoid = sigmoid(x)
y_tanh = tanh(x)
y_softplus = softplus(x)
# -----------------------------
# plot
plt.figure(figsize=(10, 6))
plt.plot(x.numpy(), y_relu.numpy(), label="ReLU")
plt.plot(x.numpy(), y_leaky.numpy(), label="LeakyReLU")
plt.plot(x.numpy(), y_sigmoid.numpy(), label="Sigmoid")
plt.plot(x.numpy(), y_tanh.numpy(), label="Tanh")
plt.plot(x.numpy(), y_softplus.numpy(), label="Softplus")
plt.axhline(0, color="black", linewidth=0.5)
plt.axvline(0, color="black", linewidth=0.5)
plt.legend()
plt.title("Activation Functions: saturation vs gradient flow")
plt.show()
# -----------------------------
# gradient 예시 (포화 vs 비포화)
x2 = torch.tensor([-10.0, -1.0, 0.0, 1.0, 10.0], requires_grad=True)
y = torch.sigmoid(x2).sum()
y.backward()
print("input:", x2)
print("sigmoid grad:", x2.grad)
파이토치에는 torch.nn 이라는 신경망 전용 서브모듈이 있다 이모듈에는 신경망 아키텍처를 만 들 수 있는 빌딩 블럭이 들어있고 파이토치에서는 이런 빌딩 블럭들을 모듈이라 부른다. 다른 프레임워크에서는 계층이라고 부른다. 파이토치 모듈은 nn.Module 베이스 클래스에서 파생된 파이썬 클래스로 하나이상의 파라미터 객체를 인자로 받는데 텐서 타입으로 훈련과정을 통해 값이 최적화된다. 서브 모듈은 list나 dict가 아닌 최상위 레벨 속성이어야 옵티마이저가 서브모듈을 찾는다. 모델이 리스트이거나 딕셔너리 형태의 서브 모듈을 요구하는 경우에 대응할 목적이로 파이토치는 nn.ModuleList와 nn.ModuleDict 를 제공한다.
import torch
import torch.nn as nn
import torch.optim as optim
# -----------------------------
# nn.Module 기본 구조
class MyModel(nn.Module):
def __init__(self):
super().__init__()
# nn.Module (파라미터 포함)
self.fc1 = nn.Linear(3, 8)
self.fc2 = nn.Linear(8, 1)
# 리스트/딕셔너리 형태의 서브모듈 대응
self.layers = nn.ModuleList([
nn.Linear(3, 3),
nn.Linear(3, 3)
])
self.named_layers = nn.ModuleDict({
"a": nn.Linear(3, 3),
"b": nn.Linear(3, 3)
})
self.relu = nn.ReLU()
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.fc2(x)
return x
# -----------------------------
# 모델 생성
model = MyModel()
# -----------------------------
# optimizer는 Module의 parameters를 자동 추적
optimizer = optim.Adam(model.parameters(), lr=0.01)
# -----------------------------
# 더미 데이터
x = torch.randn(5, 3)
y = torch.randn(5, 1)
# -----------------------------
# forward / backward
pred = model(x)
loss = nn.MSELoss()(pred, y)
loss.backward()
optimizer.step()
optimizer.zero_grad()
# -----------------------------
# 파라미터 확인
for name, p in model.named_parameters():
print(name, p.shape)
파이토치가 제공하는 nn.Module 모든 서브클래스에는 __call__메소드가 정의되어있다. 마치 nn.linear 를 인스턴스화해 함수인것처럼 실행할 수있다. forward는 순방향연산을 수행하는 반면 __call__은 forward를 호출하기 전후에 작업을 수행하며 forward를 직접 호출할 수 있으나 유저코드에서는 사용하지 않는 편이 좋다.
import torch
import torch.nn as nn
# -----------------------------
# nn.Module subclass
class MyLayer(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(3, 1)
def forward(self, x):
print(">>> forward called")
return self.linear(x)
# -----------------------------
layer = MyLayer()
x = torch.randn(2, 3)
# -----------------------------
# __call__ 사용 (권장)
print("=== using __call__ ===")
y1 = layer(x)
# -----------------------------
# forward 직접 호출 (비권장)
print("\n=== calling forward directly ===")
y2 = layer.forward(x)
print("\noutput shape:", y1.shape, y2.shape)
nn.linear는 세개의 인자를 받는다. 입력피처의 수와 출력피처의 수 그리고 편향값의 여부. 기본값은 T이다.
import torch
import torch.nn as nn
# Linear layer: in_features, out_features, bias
linear = nn.Linear(in_features=3, out_features=1, bias=True)
# 입력 (batch_size=2, feature=3)
x = torch.tensor([
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]
])
# forward
y = linear(x)
print("input:")
print(x)
print("\nweight:")
print(linear.weight)
print("\nbias:")
print(linear.bias)
print("\noutput:")
print(y)
nn에 있는 모든 모듈은 한번에 여러 입력을 가진 batch에 대한 출력을 만들도록 작성되어있다. batch를 수행하는 주요이유는 연산량을 충분히 크게 만들어 준비한 자원을 최대한 활용하기 위함으로 병렬 연산에 최적화된 gpu 모델에 작은 모델을 넣고 입력하나를 수행하면 대부분의 자원이 놀게 됨으로 입력을 묶어하나의 배치로 한번에 실행해 여러개의 입력을 처리하는데 걸리는 시간이 하나의 입력을 처리하는데 걸리는 시간과동일함으로 이득이다. 어떤 고급모델은 전체 배치에 대해 통계정보를 사용할때 배치 사이즈 가 더 클수록 더 나은 통계값을 얻기도 한다.
import torch
import torch.nn as nn
# -----------------------------
# nn.Module은 기본적으로 batch 입력을 가정
linear = nn.Linear(3, 1)
# -----------------------------
# batch 입력 (batch_size = 4, feature = 3)
x = torch.tensor([
[1.0, 2.0, 3.0],
[2.0, 3.0, 4.0],
[3.0, 4.0, 5.0],
[4.0, 5.0, 6.0]
])
# -----------------------------
# forward (한 번에 batch 처리)
y = linear(x)
print("input shape:", x.shape)
print("output shape:", y.shape)
# -----------------------------
# 배치 vs 단일 입력 시간 비교
import time
single = torch.tensor([[1.0, 2.0, 3.0]])
start = time.time()
for _ in range(1000):
linear(single)
single_time = time.time() - start
start = time.time()
for _ in range(1000):
linear(x)
batch_time = time.time() - start
print("\nsingle input time:", single_time)
print("batch input time:", batch_time)
# -----------------------------
# BatchNorm처럼 batch 통계를 쓰는 예시
bn = nn.BatchNorm1d(1)
x_bn = torch.randn(8, 1)
out = bn(x_bn)
print("\nBatchNorm output:")
print(out)
Paraametes 메소드를 호출하면 모듈의 init 생성자에 정의된 서브모듈까지 재귀적으로 호출하며 만나는 모든 파라미터 리스트를 담은 리스트를 반환한다.
import torch
import torch.nn as nn
# -----------------------------
# 중첩된 서브모듈 구조
class Net(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(4, 8)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(8, 2)
# 서브모듈 안의 서브모듈
self.block = nn.Sequential(
nn.Linear(2, 2),
nn.ReLU(),
nn.Linear(2, 1)
)
def forward(self, x):
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
x = self.block(x)
return x
# -----------------------------
# 모델 생성
model = Net()
# -----------------------------
# parameters() : 재귀적으로 모든 파라미터 수집
params = list(model.parameters())
print("number of parameter tensors:", len(params))
for i, p in enumerate(params):
print(i, p.shape)
# 아마 "출력" 요청으로 이해해서 실행 결과 예시를 같이 적어줌
number of parameter tensors: 6
0 torch.Size([8, 4])
1 torch.Size([8])
2 torch.Size([2, 8])
3 torch.Size([2])
4 torch.Size([2, 2])
5 torch.Size([2])
유용한점은 nn자체에 이미 일반적인 손실함수가 들어있다. 평균제곱오차수식의 nn.MSELoss인데 loss_fn과 동일해 서브클래스로 인스턴스처럼 만들어 함수처럼 호출할 수 있다.
import torch
import torch.nn as nn
# -----------------------------
# nn 내장 손실함수
loss_fn = nn.MSELoss()
# -----------------------------
# 예측값 / 정답
y_pred = torch.tensor([2.0, 3.0, 4.0])
y_true = torch.tensor([1.0, 2.0, 5.0])
# -----------------------------
# loss 계산 (함수처럼 호출)
loss = loss_fn(y_pred, y_true)
print("prediction:", y_pred)
print("target:", y_true)
print("MSE loss:", loss.item())
데이터과학을 하는 사람이라면 항상 데이터를 차트에 그려봐야한다.
'Personal > Book' 카테고리의 다른 글
| 파이썬 머신러닝 판다스 데이터 분석 - part1_판다스 입문 (0) | 2026.07.03 |
|---|---|
| 파이토치 딥러닝 마스터 - 1부 - 5장 학습기법 (0) | 2026.06.28 |
| 파이토치 딥러닝 마스터 - 1부 - 4장 실제 데이터를 텐서로 표현해보기 (0) | 2026.06.28 |
| 파이썬으로 따라해보는 딥러닝 AI프로젝트 실사례 (0) | 2026.06.26 |
| 파이토치 딥러닝 마스터 - 1부 - 3장 텐서 구조체 (0) | 2026.06.23 |