아래를 스키밍한다.
DAY2. 딥러닝 네트워크 신경망 알고리즘 계층구조
DAY3. 딥러닝 데이터 처리와 데이터셋
DAY4. 딥러닝 네트워크 모델 설계
DAY5. 딥러닝 모델 훈련 및 평가
DAY2. 딥러닝 네트워크 신경망 알고리즘 계층구조
약 40분 정리했던 내용을 다시 흝어가며 정리해보자.
https://standout.tistory.com/1800
SK 네트웍스 AI 캠프 - 3_딥러닝 - Day25_딥러닝 네트워크 신경망 알고리즘 계층 구조
인공신경망뉴런 신경세포 구조를 단순화한것. 입력 - 계산 - 출력: 다른 뉴런에서 신호받아 가중치/편향으로 계산 - 다음 뉴런으로 전달 퍼센트론직선으로만 구분가능한 1개의 층, AND OR은 가능하
standout.tistory.com
퍼셉트론
인공뉴런, 현재 딥러닝의 기원, 여러개의 입려글 받고 하나의 출력을 내보낼 수 있다.
입력값에 가중치를 곱한 뒤 모두 더하고 임계값과 비교해 0또는 1을 출력한다.
단순한 인공신경망 모델.
def perceptron(x1, x2, w1, w2, b):
result = x1 * w1 + x2 * w2 + b
if result > 0:
return 1
else:
return 0
https://standout.tistory.com/1528
인공지능의 역사: 기원(계산 기계 및 지능). 퍼셉트론, 인공지능의 겨울, 전문가 시스템, 신경망
로봇과 인공지능https://standout.tistory.com/881 로봇을 프로그래밍하다, Flexible Work HoldingFlexible Work Holding 로봇에 프로그래밍해 부품이 바뀌어도 다르게 움직이게 하며 차를 만드는 방식을 업그레이드
standout.tistory.com
퍼셉트론데는 AND OR NAND 게이트가 있다.
https://standout.tistory.com/65
참인가 거짓인가, 진리표
진리표 사과는 사람이거나 과일이다 사과는 사람이자 과일이다 사과는 사람 = 거짓 사과는 과일 = 진실 사과는 사람이거나 과일이다 = 진실 사과는 사람이자 과일이다 = 거짓 위처럼 or(이거나), a
standout.tistory.com
퍼셉트론 함수는 아래와같다.
입력 * 가중치 + 편향
가중치 +-0.5를 곱하고 편향 n을 더한다.
def perceptron(x1, x2, w1, w2, b):
tmp = x1*w1 + x2*w2 + b
AND 둘다 1일때만 1을 출력.
AND(1,1)
1*0.5 + 1*0.5 - 0.7
def AND(x1, x2):
return perceptron(x1, x2, 0.5, 0.5, -0.7)
print(AND(0,0))
print(AND(0,1))
print(AND(1,0))
print(AND(1,1))
OR 하나라도 1이면 1출력
OR(0,1)
0*0.5 + 1*0.5 - 0.2
def OR(x1, x2):
return perceptron(x1, x2, 0.5, 0.5, -0.2)
print(OR(0,0))
print(OR(0,1))
print(OR(1,0))
print(OR(1,1))
NAND 둘다 0 일때 1을 출력
NAND(0,0)
0*-0.5 + 0*-0.5 + 0.7
def NAND(x1, x2):
return perceptron(x1, x2, -0.5, -0.5, 0.7)
print(NAND(0,0))
print(NAND(0,1))
print(NAND(1,0))
print(NAND(1,1))
XOR 서로 다를때만 1을 출력
아래를좌표평면에 그려보면 직선하나로 분리할 수 없고, 퍼센트론은직선하나를 만들 수 있기에 '선형 분리불가능'하다 라고 말한다.
XOR는 다중 퍼셉트론 MLP로 구현한다.
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
def XOR(x1, x2):
s1 = NAND(x1, x2)
s2 = OR(x1, x2)
return AND(s1, s2)
활성화 함수
여러층의 신경망에 비선형성을 넣는것. 0근처나 1근처에서 기울기가 거의 0이 된다는 단점.
Step Fumction 계단함수, 초창기 퍼셉트론 0보다 크면 1, 0보다 작으면 0, 현재는 거의 안쓴다. gradient가 없어 미분도 불가능하고 backpropagaion사용도 불가하다.
if x > 0:
return 1
else:
return 0
Sigmoid 0 ~ 1, 확률출력, 이진분류 출력층 Vanishing Gradient 단점
import torch
x = torch.tensor([-5., -1., 0., 1., 5.])
print(torch.sigmoid(x))
tensor([0.0067, 0.2689, 0.5000, 0.7311, 0.9933])
Tanh -1 ~ 1 sigmoid 개선형, 평균이 0이라 학습이 더 안정적임, Vanishing Gradient
import torch
x = torch.tensor([-5., -1., 0., 1., 5.])
print(torch.tanh(x))
tensor([-0.9999, -0.7616, 0.0000, 0.7616, 0.9999])
ReLU max(0, x) 빠르고 현재표준, 음수는 0으로 양수는 그대로. 다만 음수영역에 갇히면 계속 0만 출력됨. Dead ReLU (0만 나오는 뉴런),
import torch
import torch.nn.functional as F
x = torch.tensor([-3., -1., 0., 2., 5.])
print(F.relu(x))
tensor([0., 0., 0., 2., 5.])
Leaky ReLU Dead ReLU 해결, 음수도 조금 통과한다. ReLU보다 뉴런이 죽을 확률이 감소한다.
import torch
import torch.nn as nn
act = nn.LeakyReLU(0.01)
x = torch.tensor([-5., -1., 0., 2.])
print(act(x))
tensor([-0.0500, -0.0100, 0.0000, 2.0000])
PReLU Leaky ReLU는 음수는 무조건 1%만 살리는 등의 값이 고정되지만 데이터마다 1%가 좋은지, 10%가 좋은지는 다를 수 있다. PRELU는 데이터에 맞춰 최적화시킨다. 음수를 얼마나 살릴지를 모델이 스스로 결정하는것
import torch
import torch.nn as nn
act = nn.PReLU()
x = torch.tensor([-5., -1., 0., 2.])
print(act(x))
tensor([-1.2500, -0.2500, 0.0000, 2.0000])
ELU Exponential Linear Unit ReLU에서는 음수는 무조건 0이었다. 이때 gradient가 0이 되버리는데 Deep ReLU 가 발생한다. 이때 음수도 부드럽게 살리자는 아이디어로 사용된다. 음수구간이 부드럽다. gradient의 흐름이 좋으나 exp계산이 필요하거 느리다는 단점이있다.
import torch
import torch.nn as nn
act = nn.ELU()
x = torch.tensor([-5., -1., 0., 2.])
print(act(x))
tensor([-0.9933, -0.6321, 0.0000, 2.0000])
SELU Scaled ELU 딥러닝에서 가장 이상적인상태는 평균이 0 이고 분산이 1인 상태이다. 그래야 gradient폭발 가능성이 적어지고, gradient 반대로 소실될 가능성도 줄기 때문이다.
self Normalizing ELU, ELU를 개선한다. 자동으로 평균 0, 분산 1을 유지해 깊은 네트워크를 안정화하나 특정 초기화 조건이 필요하다는 단점이 있다.
이때 ElU와 SELU의 차이가 있다면 [-0.9933, -0.6321, 0, 2], [-1.7463, -1.1113, 0, 2.1014]로 SELU가 좀더 크게 스케일링된다 .핵심 장점은 학습중 자동으로 평균 0 분산 1 근처를 유지한다는것. 깊은 네트워크에 안정적이나 이 자기정규화라는 핵심은 특정 조건에서만 효과가 제대로 나온다. SELU 가 다음레이어에서도 수행되려면 가중치 초기화, dropout방식, 네트워크 구조가 특정 조건을 만족해야한다.
ReLU 에서 사용하는 nn.init.kaiming_normal()는 음수절반을 제거해버려 평균과분산에 영향을 준다. 논문에서는 LeCun Normal Initialization을 권장하고 PyTorch에는 LeCun 초기화가 없다. PyTorch는 기본적으로 nn.init.xavier.normal_, nn.init.xavier_uniform_, nn.init.kaiming_normal_ nn.init.kaiming_uniform_을 제공한다. so SELU를엄격히 쓰려면 직접 구현하는 경우가 많다 ㅜ
또 일반 nn.dropout으로 랜덤 제거해버리면 평균과분산이 변해버리기 때문에 nn.AlphaDropout()을 사용한다.
또 BatchNorm이 없어도 SELU는 정규화를 잘 유지함으로 굳이 함께 쓰지않는다 .
하지만 현재 ReLU + BatchNorm이나 GELU + LayerNorm 조합이 워낙 강력해 실무에서 SELU는 잘사용하지않느다.
import torch
import torch.nn as nn
act = nn.SELU()
x = torch.tensor([-5., -1., 0., 2.])
print(act(x))
tensor([-1.7463, -1.1113, 0.0000, 2.1014])
import torch.nn as nn
model = nn.Sequential(
nn.Linear(100, 256),
nn.SELU(),
nn.AlphaDropout(0.1),
nn.Linear(256, 128),
nn.SELU(),
nn.AlphaDropout(0.1),
nn.Linear(128, 10)
)
import math
import torch.nn as nn
def lecun_normal(layer):
if isinstance(layer, nn.Linear):
nn.init.normal_(
layer.weight,
mean=0,
std=math.sqrt(1 / layer.in_features)
)
model.apply(lecun_normal)
GELU ReLU보다 Gaussian Error Linear Unit 가우시안 오차 선형 유닛, 부드러움 성능 좋음, 현재 transformer 계열 표준. ReLU처럼 자르지않고 부드럽게 통과시킨다. 큰 음수들은 거의 0, 큰 양수들은 거의 x 중간은 부드럽게 통과시킴. ReLU보다 표현력이 좋아 NLP 모델의 표준이다. 이 값을 완전히 버릴지 조금만 살릴지를 부드럽게 결정하는 느낌이기에 transformer 계열에서는 reLU보다 더 많이 사용됨
import torch
import torch.nn as nn
gelu = nn.GELU()
x = torch.tensor([-3., -1., 0., 1., 3.])
print(gelu(x))
tensor([-0.0040,
-0.1587,
0.0000,
0.8413,
2.9960])
Swish Google 개발 ReLU보다 성능 좋음, sigmoid와 입력을 곱한것. ReLU보다 부드럽다. 일부모델에서 더 좋은 성능을 보인다.
import torch
x = torch.tensor([-3., -1., 0., 1., 3.])
y = x * torch.sigmoid(x)
print(y)
tensor([-0.1423,
-0.2689,
0.0000,
0.7311,
2.8577])
Mish
최신계열의 활성화함수. 2019년에 제안되었다. 매우 부드럽다. ReLU 처럼 정보를 너무 많이 버리지 말고 GELU나 Swish 처럼 부드럽게 통과시키자는 아이디어로 sofrplus + tanh이 수행된다.
gradient의 흐름이 우수하고 정보보존이 좋으나 계산량이 증가한다는 단점이 있다.
ReLU엿다면 -5는 즉시 정보가 삭제된다. Mish는 남겨두는것을 확인할 수 있다. 즉 음수 정보를 더 많이 보존한다. 큰 양수는 거의 그대로 통과한다.
딥러닝에서는 gradient가 잘 흘러야하고 ReLU는 음수영역에서 gradient가 0, Mish는 음수영역에도 gradient가 존재하니 장점이 있다. 하지만계산량이 증가하기에 작은모델에는 차이가 거의 없으나 초대형 모델에서 연산량이 꽤 증가한다는 단점이 있다. 실무적으로는 잘 사용하지않고 연구나 실험목적으로 자주 사용된다.
import torch
import torch.nn as nn
act = nn.Mish()
x = torch.tensor([-5., -1., 0., 2.])
print(act(x))
tensor([-0.0336, -0.3034, 0.0000, 1.9440])
import torch.nn as nn
model = nn.Sequential(
nn.Linear(128, 256),
nn.Mish(),
nn.Linear(256, 128),
nn.Mish(),
nn.Linear(128, 10)
)
Softmax 각 클래스 확률로 변환한다. 고양이 강아지 토끼중 하나 선택하는 경우.
import torch
x = torch.tensor([2.0, 1.0, 0.1])
print(torch.softmax(x, dim=0))
tensor([0.6590, 0.2424, 0.0986])
| Sigmoid | 0~1 | 확률 해석 쉬움 | 기울기 소실 | 이진분류 출력층 |
| Tanh | -1~1 | 0 중심 | 기울기 소실 | 과거 RNN |
| ReLU | 0~∞ | 빠름, 표준 | Dead ReLU | 은닉층 |
| Leaky ReLU | -∞~∞ | Dead ReLU 완화 | 추가 파라미터 | 은닉층 |
| GELU | -∞~∞ | 부드럽고 성능 좋음 | 계산량 증가 | Transformer |
| Swish | -∞~∞ | 부드러움 | 계산량 증가 | 일부 최신 모델 |
| Softmax | 0~1 (합=1) | 확률 변환 | 출력층 전용 | 다중분류 |
현재 기준으로 은닉층은 ReLU(릴루), GELU(젤루), 출력층은 문제종류에 따라
실무에서 가장 많이 보는 조합은 이진분류에서 LInear ReLU.
nn.Linear(...)
nn.ReLU()
...
nn.Linear(...)
nn.Sigmoid()
다중분류에서 Linear ReLU softmax()
nn.Linear(...)
nn.ReLU()
...
nn.Linear(...)
nn.Softmax(dim=1)
transformer나 gpt계열에서는 linear GELU linear
nn.Linear(...)
nn.GELU()
nn.Linear(...)
함수현재 사용도
| Step | 거의 안 씀 |
| Sigmoid | 출력층만 |
| Tanh | 가끔 |
| ReLU | 매우 많음 |
| LeakyReLU | 종종 |
| PReLU | 드묾 |
| ELU | 드묾 |
| SELU | 드묾 |
| GELU | Transformer 표준 |
| Swish(SiLU) | 많이 사용 |
| Mish | 연구용 위주 |
손실 함수
모델이 얼마나 틀렸는지 계산하는 함수
이진분류 nn.BCELoss() Binary Cross Entropy Loss, sigmoid()를 거친 확률값이어야한다.
import torch
import torch.nn as nn
loss_fn = nn.BCELoss()
pred = torch.tensor([0.8])
target = torch.tensor([1.0])
loss = loss_fn(pred, target)
print(loss)
model = nn.Sequential(
nn.Linear(10, 1),
nn.Sigmoid()
)
criterion = nn.BCELoss()
이진분류 nn.BCEWithLogitsLoss() 실무에서 더 많이 사용한다. 내부적으로 sigmoid와 BCE를 한번에 계산해 코드간결 + 안정적 + overflow 방지 + 학습도 잘됨
import torch
import torch.nn as nn
criterion = nn.BCEWithLogitsLoss()
logits = torch.tensor([2.0])
target = torch.tensor([1.0])
loss = criterion(logits, target)
print(loss)
model = nn.Sequential(
nn.Linear(10, 1)
)
criterion = nn.BCEWithLogitsLoss()
다중분류 nn.CrossEntropyLoss(), 내부에 softmax 와 nllloss 가 포함되어있다.
import torch
import torch.nn as nn
criterion = nn.CrossEntropyLoss()
logits = torch.tensor([[2.5, 0.3, -1.2]])
target = torch.tensor([0])
loss = criterion(logits, target)
print(loss)
model = nn.Sequential(
nn.Linear(10, 3)
)
criterion = nn.CrossEntropyLoss()
| 이진 분류 | 1 | Sigmoid (또는 없음) | BCEWithLogitsLoss 권장 |
| 다중 분류 | 클래스 수만큼 | 없음 | CrossEntropyLoss |
| 다중 라벨 분류 | 클래스 수만큼 | 없음 | BCEWithLogitsLoss |
회귀용으로는 MSELoss를 사용한다. 집값예측, 온도예측, 주가예측 등에 사용. 출력이 연속적인 숫자일때 사용한다.
Mean Squared Error 평균제곱오차.
import torch
import torch.nn as nn
criterion = nn.MSELoss()
pred = torch.tensor([8.0])
target = torch.tensor([10.0])
loss = criterion(pred, target)
print(loss)
실무에서 가장 많이 쓰이는 조합은
이진분류에 linear + BCEwithLogistsloss() - 스팸여부 질병유무..
다중분류에 linear + nn.CrossEntroyLoss() 0~9 숫자분류, 동물종류분류, 감정분류
Transformer나 LLM에는 linear() + GELU() linear() + crossentroypyloss()
PyTorch 학습 3단계, 핵심루프는 아래와같다. 이전 gradient를 삭제해 gradient가 누적되지않도록매 step마다 초기화하고loss.backward()로 loss를 줄이기 위해 w b를 얼마나 바꿔야하는지를 계산해 반영함.
optimizer.zero_grad()
loss.backward()
optimizer.step()
Optimizer, 가중치를 어떻게 수정할 것인지 결정하는 알고리즘
Optimizer - SGD Stochastic Gradient Descent loss 줄어드는 방향으로 조금 이동한다. 단순하고 계산이 안정적인대신 느리고 흔들림이 크다. loss가 지그재그로 내려가며 learning rate 설정에 매우 민감하다.
import torch
import torch.nn as nn
import torch.optim as optim
x = torch.tensor([[1.0], [2.0], [3.0]])
y = torch.tensor([[2.0], [4.0], [6.0]])
model = nn.Linear(1, 1)
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
for epoch in range(100):
pred = model(x)
loss = criterion(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print("SGD 학습 완료")
Optimizer - SGD + Momentum, 관성을 추가한다. 이전 gradient 방향을 기억하는것. 경사면 굴러가는 공처럼 더 빠르게 내려가고 흔들림이 줄어든다. SGD보다 안정적이고 local noice가 감소된다.
import torch
import torch.nn as nn
import torch.optim as optim
model = nn.Linear(1, 1)
criterion = nn.MSELoss()
optimizer = optim.SGD(
model.parameters(),
lr=0.01,
momentum=0.9
)
for epoch in range(100):
pred = model(x)
loss = criterion(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print("Momentum SGD 완료")
Optimizer - RMSProp, gradient크기에 따라서 step이 자동조절된다 .gradient가 큰 방향으로 작게이동하고 gradient가 작은 방향으로 크게 이동하는 등 RNN 시절에 많이 사용했다. 비정상적인 graient를 안정화하기 좋다.
import torch
import torch.nn as nn
import torch.optim as optim
model = nn.Linear(1, 1)
criterion = nn.MSELoss()
optimizer = optim.RMSprop(
model.parameters(),
lr=0.001
)
for epoch in range(100):
pred = model(x)
loss = criterion(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print("RMSProp 완료")
Optimizer - Adam. 가장 많이 쓴다. momentum + RSAProp를 합친 것으로 방향도 기억하고 스케일도 자동 조절한다. 거이 항상 잘되고 튜닝이 거의 필요없는 빠른방법.
import torch
import torch.nn as nn
import torch.optim as optim
model = nn.Linear(1, 1)
criterion = nn.MSELoss()
optimizer = optim.Adam(
model.parameters(),
lr=0.001
)
for epoch in range(100):
pred = model(x)
loss = criterion(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print("Adam 완료")
Optimizer - AdamW 기존 Adam에서는 L2 regularization이 이상하게 섞일수있는데 AdamW에서는 weight decay를 진짜 weight decay로 처리해 일반화성능이좋아져 transformer에서 필수적이다.
import torch
import torch.nn as nn
import torch.optim as optim
model = nn.Linear(1, 1)
criterion = nn.MSELoss()
optimizer = optim.AdamW(
model.parameters(),
lr=0.001,
weight_decay=0.01
)
for epoch in range(100):
pred = model(x)
loss = criterion(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print("AdamW 완료")
실무기준으로는 Adam, AdamW를 기본적으로 90%, 연구 및 cnn 일부에서는 sgd + momentum을 쓴다.
튜닝이필요없다는 것이 가장 큰 장점. 초기학습도 빨라 초반 loss가 빠르게 감소하며 자동 scaling 덕에 noisy data에 강하고 매우 깊고 gradient noisy가 있는 transformer에 adamw가 아주 안정적이다 .
반대로 sgd + momentum가 아직도 쓰이는 이유는 놀랍게도 SGD가 더 좋은 최종 성능이 나오는 경우가 있기 때문인데 noise가 있고 흔들림이 있는 SGD가 오히려 좋은 일반화를 찾는 경우가 있는것. CNN 에서는 아직강하고 연구논문 기준비교 공정성으로 채택되어있으며 adam은 너무 빨리 맞춰버려 overfit이 가능하나 SGD는 느리지만 일반화가 더 잘되 overfitting이 적은 경우가 있기 때문이다. 실무에서는 결과를 빨리 뽑아야하기때문에 adam이 더 인기가 많음.
DNN Deep Neural Network, Fully Connected Network 기본 신경망, 모든 입력이 모든 뉴런과 연결되는 완전연결.
Input → Linear → ReLU → Linear → ReLU → Output
표 데이터, 수치예측, 간단한 분류 및 회귀에 사용된다. 구조가 단순하고 계산이 빠른대신에 이미지나 텍스트에 약하다
집값 예측, 고객이탈 예측 점수예측.. 엑셀 데이터를 잘 처리하는 모델.
import torch.nn as nn
model = nn.Sequential(
nn.Linear(10, 64),
nn.ReLU(),
nn.Linear(64, 32),
nn.ReLU(),
nn.Linear(32, 1)
)
CNN Convolutional Neural Network
이미지적용 신경망 픽셀전체가 아닌 부분 특징을 본다.
합성곱 conv 아이디어, 필터로 특징을 추출하는것이 특징.
pooling 중요한 정보만 압축해 크기를 줄이고 계산량이 감소하는것이 특징
Image → Conv → ReLU → Pooling → Flatten → Linear
이미지분류, 얼굴인식, 의료영상에 사용.
import torch.nn as nn
model = nn.Sequential(
nn.Conv2d(3, 16, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(16, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(32 * 8 * 8, 10)
)
RNN Recurrent Neural Network, 순서데이터 처리. 이전 정보와 현재입력을 처리한다 .
시계열 데이터나 문장처리, 음성에 쓰인다. 장기기억을 못하고 앞문장을 기억못하는 문제점이있다. 메모리가 있는 순차 처리 모델.
주가예측, 문장생성(옛날), 음성인식..
x_t + h_(t-1) → h_t
import torch.nn as nn
model = nn.RNN(
input_size=10,
hidden_size=20,
batch_first=True
)
LSTM / GRU
RNN 개선 버전으로오래 기억하는 RNN. 긴문장시 앞내용을 기억못하는 RNN의 핵심문제를 해결했다.
3개의 gate Foregetgate, input gate, output gate를 사용한다.
GRU 는 LSTM 보다 단순해 rest gate, update gate를 사용해 더 빠르고 가볍다.
긴문장이나 번역, 시계열 분석에 사용된다.
model = nn.LSTM(
input_size=10,
hidden_size=20,
batch_first=True
)
model = nn.GRU(
input_size=10,
hidden_size=20,
batch_first=True
)
Transformer 문장전체를 한번에 보는 모델, 각 단어가 다른 단어를 얼마나 참고할지를 계산하는 self-attention 기술을 사용한다. '나는 밥을 먹었다' 에서 먹었다는 나(주어) 와 밥(목적어)를 둘다 참고함을 이해해보자.
RNN 은 순서대로 처리하나 transformer는 전체를 한번에처리한다.
Input → Embedding → Self-Attention → Feed Forward → Output
gpt, bert(이해모델), 번역기, 챗봇, 문서요약 등에 사용된다
import torch.nn as nn
model = nn.Transformer(
d_model=512,
nhead=8,
num_encoder_layers=6
)
| DNN | 표 데이터 | 완전 연결 |
| CNN | 이미지 | 공간 특징 |
| RNN | 순서 데이터 | 시간 흐름 |
| LSTM/GRU | 긴 순서 | 기억 개선 |
| Transformer | 텍스트/멀티모달 | 전체 관계 |
딥러닝의 발전방향으로는 DNN → CNN → RNN → LSTM → Transformer으로 기억 + 병렬처리 + 전체관계 포커스로 발전했다.
실무기준으로는 표 데이터가있다? DNN
이미지가 있다? CNN
텍스트는 Transformer로,
시계열데이터다? Transformer혹은 LSTM을 사용한다.
DAY3. 딥러닝 데이터 처리와 데이터셋
데이터셋의 종류
- 학습을 위한 훈련 데이터셋
- 검증을 위한 검증 데이터셋
- 테스트를 위한 시험/테스트 데이터셋
언더피팅 underfitting 모델이 충분히 학습되지않은 상태, 반복해서 학습시키면 성능이 더 좋아지는것.
오버피팅 overfitting 검증셋은 어느순간부터 안좋아지기 시작하는. 훈련데이터에서는 매주 좋은 결과가 나오지만 새로운데이터에서 성능이 떨어지는 것.
딥러닝 모델 MLP
MLP 다충 퍼셉트론, sequential 모델. 가장 기본적인 인공뉴런 퍼셉트론 Single Layer Perceptron과 달리 레이어가 여러층을 쌓는것.
Dense Layer 풀커넥트 레이어, Linear()가 Dense Layer이다. 모든 입력이 모든 뉴런과 연결되는것.
XOR, 직선으로만 분리가능했던 퍼셉트론으로 인해 시작된 Ai Winter, MLP 등장으로 은닉층 추가로 해결함.
import torch.nn as nn
model = nn.Sequential(
nn.Linear(10, 64),
nn.ReLU(),
nn.Linear(64, 32),
nn.ReLU(),
nn.Linear(32, 1)
)
Batch SIze
Batch란 전체 데이터를 한번에 넣지않고 나눠서 학습시키며 메모리가 터지지않도록한다.
Epoch는 데이터 10000개를 배치 100개로 돌릴때 step을 epoch라한다.
과적합이 감소되고, 메모리를 적게 사용한다는 장점, 반대로 gradient가 흔들린다는 단점이 있다.
큰 batch일수록 gradient가 매우 안정적이고 GPU 효율이 좋지만 일반화성능이 감소해 훈련 데이터에 너무맞춰진다는 단점이 있다. 보통 batchsize는 34, 64로 진행됨. transformer시 128, 256, 512, 1024. GPU 메모리에 따라 결정됨.
from torch.utils.data import DataLoader
train_loader = DataLoader(
dataset,
batch_size=32,
shuffle=True
)
* gradient 즉 기울기는 현재 위치에서 손실이 얼마나, 어느 방향으로 줄여하하는지 알려주는 값
Loss는 얼마나 틀렸는가. 그래프가 아래로 떨어질수록 loss가감소해 학습이 잘된것.
Loss
10 |\
| \
5 | \
| \
1 | \__
+-------->
Epoch
직선과 가까울경우 loss가 변화가없다. 즉 학습이 안되고이다는 의미이다.
10 |----------
|
|
|
+-------->
오히려 그래프가 올라가고있다면 loss가 증가 즉 learning rate가 너무 크다고 분석한다.
10 |
20 | /
30 |/
40 |
loss: 2.1
loss: 1.8
loss: 5.7
loss: 20.3
loss: nan
lr = 0.001
lr = 0.0005
lr = 0.0001
* learning rate는 한번에 얼마나 크게 이동할지를 결정하는 값으로 너무 크다는 것은 최저점을 지나쳐버렸다는 의미이다.
lr 값을 줄이며 조절해야한다.
정리해보자. gradient가 불안정할때
1. learning rate을 줄이고
2. gradient clipting 큰 그라디언트를 강제로 잘라내거나
torch.nn.utils.clip_grad_norm_(
model.parameters(),
max_norm=1.0
)
3. batch size를 늘린다. 작은 배치는 grdient 노이즈가 크기 때문이다.
batch_size = 8
-> batch_size = 32
4. AdamW을 사용해 SGD 보다 안정적으로 될 수 있다.
optimizer = AdamW(model.parameters(), lr=1e-4)
Accuracy 정답을 맞춘 비율, loss와는 반대로 그래프가 위로 솓아올라갈수록 정답을 잘 맞췄다고 해석할 수 있다 .
pred = outputs.argmax(dim=1)
correct += (pred == labels).sum().item()
accuracy = correct / total
Accuracy
100|
90| /
80| /
70| /
60| /
+-------->
Epoch
MNIST 데이터셋
손글씨 숫자 데이터셋 0~9 까지의 숫자 10개의 클래스 흑백이미지 28*28픽셀. 학습용 6만장 테스트용 1만장 총 7만장의 데이터가 있다. 이미지가 작고 cpu에서도 학습이 가능할정도로 빠르다. CNN 연습이나 MLP연습, 분류연습에 적합해 입문용으로 좋다. 하지만 자율주행,의료영상, 얼굴인식등과 같은 실제 문제와는 너무 자르고 쉬워 실제 AI와는 큰 관련성이 없다는것이 단점.
from torchvision import datasets
from torchvision import transforms
transform = transforms.ToTensor()
train_dataset = datasets.MNIST(
root="./data",
train=True,
download=True,
transform=transform
)
from torch.utils.data import DataLoader
train_loader = DataLoader(
train_dataset,
batch_size=32,
shuffle=True
)
image, label = train_dataset[0]
print(image.shape)
print(label)
torch.Size([1, 28, 28])
5
logits는 마지막 Linear 층의 원시 출력값을 의미한다. 확률이아니다.
,softmax는이 정답인 클래스 하나 logits를 확률로 바꿔주는것, <-> sigmoid는 여러개 동시에 정답이 가능하다.
import torch
logits = torch.tensor([
2.1, 1.0, 0.3, 5.8
])
prob = torch.softmax(
logits,
dim=0
)
print(prob)
tensor([
0.023,
0.008,
0.004,
0.965
])
CIFAT-10 데이터셋
이미지분류 데이터셋, 10개의 클래스, 32*32*3 이미지 크기 3은 rgb채널.
MNIST 와 비교했을때 컬러라는것, 숫자이미지만 모아놓은것이아닌 자연이미지인것 사이즈가 좀더 크고 난이도가 어려운 복잡한 패턴이다. MNIST는 숫자 모양만 보면 되지만 CIFAR-10은 배경 + 색 + 형태 + 위치를 봐야하기 때문.
from torchvision.datasets import CIFAR10
from torchvision import transforms
transform = transforms.ToTensor()
train_data = CIFAR10(
root="./data",
train=True,
download=True,
transform=transform
)
행렬 Matrix 숫자를 표처럼 배열한것.
가로를 행백터, 세로를 열백터라 한다.
정사각 행렬은 행백터수와 열백터수가 같을떄.
대각행렬은 대각선에 값이 있고 나머지가 0일때를 말한다.
이때 대각행렬중 대각선이 정부 1인, 어떤 행렬에 곱해도 그대로 유지되는 특별한 경우 단위행렬 Identity Matrix 라 부른다
[1 2]
[3 4]
[5 0 0]
[0 3 0]
[0 0 1]
[1 0 0]
[0 1 0]
[0 0 1]
행렬 연산
덧셈은 같은 크기끼리만 가능하다.
[1 2] + [3 4] = [4 6]
숫자 하나만 있는, 스칼라 곱 가능
2 × [1 2] = [2 4]
행렬곱셈시 앞 행렬의 열과 뒤행렬의 행이 곱해지며 가운데가 같아야가능하다.
행렬곱셈시 교환법칙 A × B ≠ B × A은 불가하되 결합법칙 (A×B)×C = A×(B×C), 분배법칙 A(B+C)=AB+AC은 가능하다.
(2×3) × (3×2) = (2×2)
행렬의 거듭제곱은 정사각행렬만 가능하다.
A² = A × A
전치 행렬 transpose 행과 열 바꾸기 이때 행렬이 하나밖에 없을때 전치를 반복하면 (Aᵀ)ᵀ = A 동일하고, 분배범칙으로 (A+B)ᵀ = Aᵀ + Bᵀ 가능하나 곱에 전치시 (AB)ᵀ = Bᵀ Aᵀ 행과 열의 순서가 바뀌니 주의한다.
[1 2]
[3 4]
[1 3]
[2 4]
행렬분할 Block Matrix
큰 행렬을 작은 블록 부분 행렬으로 쪼개서 보는 것. 계산을 쉽게하거나 구조를 이해할 때 많이 쓴다.
A(1000×1000) × x(1000×1) 이러한 행렬의 곱을 보면 복잡하다. 블록으로 나누면 동시에 계산이 가능하다.
블록별로 따로 계산해서 합치는 구조가 핵심. LU분해, QR분해, 역행렬 계산 전부 블록 기반이다. 연산을 바꾸는 것이 아니라 연산을 쪼개서 효율적으로 만드는 방법.
A =
[
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
]
A =
[ A11 A12
A21 A22 ]
x =
[ x1
x2 ]
Ax =
[ A11 x1 + A12 x2
A21 x1 + A22 x2 ]
연립방정식에서 x, y를 동시에 만족하는 값을 찾는것처럼
딥러닝에서도 똑같이 가중치 w를 찾아서 정답 y를 맞춘다.
즉 본질은 하나. 모르는 값을 맞추는 문제.
행렬은 왜쓰는것인가? 연립방정식은 식이 여러개라복잡하다. 이를 한줄로 묶어서 계산하기에? 행렬이적합하다는 말씀!
so 연립방정식이 인간용표현이라면 컴퓨터용 표현으론 행렬이 되는것이고.
5를 나눠서 없애 x값을 구하는 것처럼 행렬도 역행렬을 이용해 제거해 값을 구한다.
하지만 딥러닝에서는 계산이 비싸고 데이터 크기가 너무 큼으로 실제로는 경사하강법 gradient descent, 틀린정도 loss를 줄이기 위해 조금씩 방향을 바꿔가면서 최적값을 찾아가는 방법으로 푼다.
5x = 10
→ x = 2
DAY4. 딥러닝 네트워크 모델 설계
딥러닝 네트워크 모델 설계는 단순한 층을 여러개 쌓는 작업이 아니라 해결하고자 하는 문제를 분석하고 특성을 고려하여 최적의 신경망 구조를 구성하는 과정이다.
1. 분류인지, 회귀인지, 객체탐지인지, 자연어처리인지, 이미지 생성인지에 따른 네트워크 구조를 달리해라.
1. 분류
강아지, 고양이 분류
import torch
import torch.nn as nn
model = nn.Sequential(
nn.Linear(10, 16),
nn.ReLU(),
nn.Linear(16, 1)
)
criterion = nn.BCEWithLogitsLoss()
x = torch.randn(8, 10)
y = torch.randint(0, 2, (8, 1)).float()
output = model(x)
loss = criterion(output, y)
print(loss.item())
2. 회귀
집값 에측
import torch
import torch.nn as nn
model = nn.Sequential(
nn.Linear(5, 16),
nn.ReLU(),
nn.Linear(16, 1)
)
criterion = nn.MSELoss()
x = torch.randn(32, 5)
y = torch.randn(32, 1)
output = model(x)
loss = criterion(output, y)
print(loss.item())
3. 객체탐지
from torchvision.models.detection import fasterrcnn_resnet50_fpn
model = fasterrcnn_resnet50_fpn(weights="DEFAULT")
print(model)
4. 자연어처리 NLP
간단한 문장분류 LSTM
import torch
import torch.nn as nn
class TextModel(nn.Module):
def __init__(self):
super().__init__()
self.embedding = nn.Embedding(1000, 64)
self.lstm = nn.LSTM(64, 128, batch_first=True)
self.fc = nn.Linear(128, 2)
def forward(self, x):
x = self.embedding(x)
_, (h, _) = self.lstm(x)
return self.fc(h[-1])
model = TextModel()
x = torch.randint(0, 1000, (32, 20))
output = model(x)
print(output.shape)
5. 이미지생성
GAN간단생성
import torch
import torch.nn as nn
class Generator(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Linear(100, 128),
nn.ReLU(),
nn.Linear(128, 784),
nn.Tanh()
)
def forward(self, z):
return self.net(z)
model = Generator()
z = torch.randn(16, 100)
fake_image = model(z)
print(fake_image.shape)
2. 입력데이터 특성에 따라 숫자데이터나 표형식은 다층 퍼셉트로, 이미지데이터는 합성곱 신경망 CNN을 사용, 문장이나 시계열은순환신경망 RNN LSTM Transformer 구조를 사용한다.
다중퍼셉트론 MLP
학생성적 예측
import torch
import torch.nn as nn
model = nn.Sequential(
nn.Linear(4, 16),
nn.ReLU(),
nn.Linear(16, 1)
)
x = torch.randn(10, 4)
output = model(x)
print(output.shape)
이미지 데이터 CNN
MNIST 데이터의 활용
import torch
import torch.nn as nn
class CNN(nn.Module):
def __init__(self):
super().__init__()
self.conv = nn.Conv2d(1, 16, kernel_size=3)
self.pool = nn.MaxPool2d(2)
self.fc = nn.Linear(16 * 13 * 13, 10)
def forward(self, x):
x = self.pool(torch.relu(self.conv(x)))
x = x.view(x.size(0), -1)
return self.fc(x)
model = CNN()
x = torch.randn(8, 1, 28, 28)
output = model(x)
print(output.shape)
시계열데이터 RNN
주가예측
import torch
import torch.nn as nn
class RNNModel(nn.Module):
def __init__(self):
super().__init__()
self.rnn = nn.RNN(1, 32, batch_first=True)
self.fc = nn.Linear(32, 1)
def forward(self, x):
out, _ = self.rnn(x)
return self.fc(out[:, -1])
model = RNNModel()
x = torch.randn(16, 20, 1)
output = model(x)
print(output.shape)
시계열 뎅터 LSTM
import torch
import torch.nn as nn
class LSTMModel(nn.Module):
def __init__(self):
super().__init__()
self.lstm = nn.LSTM(1, 32, batch_first=True)
self.fc = nn.Linear(32, 1)
def forward(self, x):
_, (h, _) = self.lstm(x)
return self.fc(h[-1])
model = LSTMModel()
x = torch.randn(16, 20, 1)
output = model(x)
print(output.shape)
Transformer
import torch
import torch.nn as nn
class LSTMModel(nn.Module):
def __init__(self):
super().__init__()
self.lstm = nn.LSTM(1, 32, batch_first=True)
self.fc = nn.Linear(32, 1)
def forward(self, x):
_, (h, _) = self.lstm(x)
return self.fc(h[-1])
model = LSTMModel()
x = torch.randn(16, 20, 1)
output = model(x)
print(output.shape)
3. 입력층 노드수는 데이터 특성 개수와 동일하게 설정한다.
특성이 5개라면
model = nn.Sequential(
nn.Linear(5, 32),
nn.ReLU(),
nn.Linear(32, 1)
)
4. 단순한 문제는 은닉층1~2개로 충분하나 복잡한 이미지나 자연어는 수십 수백개 층을 사용할 수 있다. 대부분의 딥러닝 모델에서는 계산이 간단하고 기울기 소실 문제가 적은 ReLU 함수를 주로 사용한다.
model = nn.Sequential(
nn.Linear(10, 64),
nn.ReLU(),
nn.Linear(64, 32),
nn.ReLU(),
nn.Linear(32, 1)
)
relu = nn.ReLU()
x = torch.tensor([-3.0, -1.0, 0.0, 2.0])
print(relu(x))
5. 출력층의 구조는 문제 유형에 따라 달라지며 이진분류의 경우 출력노드가 1개, sigmoid 함수를 사용하고
다중분류의 경우 클래스 수만큼 출력노드가 출력되고 sofrmax 함수를 적용한다.
이때 다중 라벨 분류는 다중 분류와 달라 여러개중 여러 항목이 동시에서택될 수 있어 sigmoid 함수를 사용한다.
이진분류 출력층
model = nn.Sequential(
nn.Linear(10, 16),
nn.ReLU(),
nn.Linear(16, 1)
)
다중 분류 추력층
클래스가 5개일때
model = nn.Sequential(
nn.Linear(10, 16),
nn.ReLU(),
nn.Linear(16, 5)
)
다중라벨 분류
영화 3장르를 뽑을떄
model = nn.Sequential(
nn.Linear(20, 32),
nn.ReLU(),
nn.Linear(32, 3)
)
criterion = nn.BCEWithLogitsLoss()
8. 모델의 예측 결과와 실제 정답의 차이를 계산하기 위해 이진분류에서는 binary cross entropy,
다중 분류에서는 categoirical cross entropy,
import torch
import torch.nn as nn
model = nn.Sequential(
nn.Linear(10, 16),
nn.ReLU(),
nn.Linear(16, 1)
)
criterion = nn.BCEWithLogitsLoss()
x = torch.randn(32, 10)
y = torch.randint(0, 2, (32, 1)).float()
output = model(x)
loss = criterion(output, y)
print(loss)
criterion = nn.CrossEntropyLoss()
logits = torch.tensor([
[0.1, 2.5, 0.3]
])
target = torch.tensor([1])
loss = criterion(logits, target)
print(loss)
회귀에서는 MES 를 주로사용한다. MES는 평균 제곱 오차의 약자로 오차에 매우 강한 벌점을 줘 집값예측, 온도예측, 매출 예측 등에 적합하며 criterion = nn.MSELoss(),
import torch
import torch.nn as nn
criterion = nn.MSELoss()
pred = torch.tensor([2.5, 3.0])
target = torch.tensor([3.0, 4.0])
loss = criterion(pred, target)
print(loss)
평균절대오차는 MAE로 실제값과 에측값에 차이에 절대값을 씌운뒤 평균을 내 오차를 제곱하지앉아 MSE보다 이상치에 덜 민감해 데이터에 극단적으로 크거나 작은 값이 있을때 MSE보다 안정적이다. nn.L1Loss(),
import torch
import torch.nn as nn
criterion = nn.L1Loss()
pred = torch.tensor([2.5, 3.0])
target = torch.tensor([3.0, 4.0])
loss = criterion(pred, target)
print(loss)
Huber Loss 라는 것도 있는데 이 MSE오 MAE 장점을 결합한 손실함수로 오차가 작을때는 MSE처럼 제곱 오차를 사용하고 오차가 클때는 MAE 처럼 절대값 오차를 사용해 작은 오차는 민감하게, 큰 이상치에는 과하게 흔들리지않도록 하며 회귀 문제에서 이상치가 섞여있을때 유용하다. criterion = nn.HuberLoss(),
import torch
import torch.nn as nn
criterion = nn.HuberLoss()
pred = torch.tensor([2.5, 3.0])
target = torch.tensor([3.0, 4.0])
loss = criterion(pred, target)
print(loss)
이진 분류모델에서는 BCE 가 사용되는데 단순히 맞고 틀림을 보는것이 아니라 모델이 얼마나 확신있게 맞췄는지도 평가해 Pytorch에서 nn.BCEWithLogitsLoss()를 권장하며 BCEWidhLogitsLoss()는 Sigmoid와 BCE를 함께 계산해 출력층에 Sigmoid를 따로 넣지않아도 된다.
import torch
import torch.nn as nn
criterion = nn.BCEWithLogitsLoss()
logits = torch.tensor([[1.5]])
target = torch.tensor([[1.0]])
loss = criterion(logits, target)
print(loss)
다중 분류모델에서는 Cross Entropy를 가장 많이 사용한다. nn.CrossEntropyLoss() 내부적으로 이또한 Softmax를 포함해 모델 출력층에 softmax를 직접 넣지않는다.
import torch
import torch.nn as nn
criterion = nn.CrossEntropyLoss()
logits = torch.tensor([
[2.0, 1.0, 0.5]
])
target = torch.tensor([0])
loss = criterion(logits, target)
print(loss)
Nagative Log Likelihood Loss NLLLoss
NLLLoss와 KL Divergence Loss는 지식 증류와 같은 고급 딥러닝에서 자주 등장한다.
NLLLoss, 음의 로그 기능도 손실, 다중 분류모델에 사용되며 각 클래스에서이 정답 클래스의 확률을 계산해 확률이 높을 수록 loss가 줄어든다. 입력으로 softmax 결과를 받아 사용한다. 일반적으로는 logsoftmax+Nllloss 대신 crossentropyloss를 사용하는데 이가 내부적으로 두개 과정을 수행하기 때둔이다.
NLLLoss 는 사실상 CrossEntropyLoss의 부품으로 대부분 따로 쓸 이유가 없으나 중간 결과를 직접 제어하거나 예전에 NLP 모델들에 연구코드듸 오래된 코드에서는 자주쓰였기에 자주 보일 수 있다.
import torch
import torch.nn as nn
class Net(nn.Module):
def __init__(self):
super().__init__()
self.fc = nn.Linear(10, 3)
def forward(self, x):
x = self.fc(x)
return torch.log_softmax(x, dim=1)
model = Net()
criterion = nn.NLLLoss()
x = torch.randn(32, 10)
y = torch.randint(0, 3, (32,))
output = model(x)
loss = criterion(output, y)
print(loss)
KL Divergence Loss
kulback - leibler divergence, kl divergence. 두 확률 분ㅍ가 얼마나 다른지를 측정한다. 생성모델, 강화학습 NNLP등에서 사용한다. NLLoss와 CrossEntropyLoss는 정답을 맞추기 위한 Loss라면 KL Divergence는 두확률분포를 닮게 만들기 위한 Loss로 정답 뿐만 아닌 전체 분포를 맞추는것이 목표이다. 정답만을 목표로 학습하는것이 아닌 나머지도 신경쓰는것. 지식증류의 핵심이다. 이미지 생성기등과 같은 경우에는 정답 클래스가 존재하지않아 crossentropy 를 사용할 수 없을떄나 정책을 바꿧을떄 새정책과 기존 정책의 차이등을 측정할때도 쓰인다.
import torch
import torch.nn as nn
criterion = nn.KLDivLoss(
reduction="batchmean"
)
pred = torch.log_softmax(
torch.tensor([[2.0, 1.0, 0.1]]),
dim=1
)
target = torch.tensor([
[0.7, 0.2, 0.1]
])
loss = criterion(pred, target)
print(loss)
9. 손실함수가 계산되면 역전파를 수행해 오차를 각 층으로 전달하며 출력층에서 입력층 방향으로 경사하강법을 이용해 가중치를 수정한다. 여기서의학습률 learning rate는 너무 크면 학습이 불안정해지고 너무 작으면 학습 속도가 느려진다.
import torch
import torch.nn as nn
model = nn.Linear(1, 1)
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(
model.parameters(),
lr=0.01
)
x = torch.tensor([[1.0]])
y = torch.tensor([[2.0]])
pred = model(x)
loss = criterion(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(loss.item())
optimizer = torch.optim.Adam(
model.parameters(),
lr=0.001
)
10. 손실함수를 최소화하기 위해 최적화 알고리즘을사용하며 대표적으로 SGD, Momentum, RMSProp, Adam이 있다. Adam은 Momentum과 RMSProp 장점을 결합해 빠르고 안정적인 학습을 수행한다.
기본적인 최적화 알고리즘
optimizer = torch.optim.SGD(
model.parameters(),
lr=0.01
)
이전 기울긱방향을 기억한다.
optimizer = torch.optim.SGD(
model.parameters(),
lr=0.01,
momentum=0.9
)
학습률 자동조정
optimizer = torch.optim.RMSprop(
model.parameters(),
lr=0.001
)
Momentum + RMSProp 실무에서 가장 많이 사용
optimizer = torch.optim.Adam(
model.parameters(),
lr=0.001
)
11. 모델 설계시 overfiting 과적합 방지 기법을 함께 고려하며 대표적으로 dropout, batch nomalization, early stopping, data augmentation이 있다. dropout은 학습 과정엥서 일부 뉴런을 무작위로 제거해 특정 뉴런에 과도하게 의존하는 현상을 방지한다.
학습중 일부 뉴런 랜덤제거
import torch
import torch.nn as nn
model = nn.Sequential(
nn.Linear(10, 64),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(64, 1)
)
x = torch.randn(8, 10)
output = model(x)
print(output.shape)
각 출력을 정규화
import torch
import torch.nn as nn
model = nn.Sequential(
nn.Linear(10, 64),
nn.BatchNorm1d(64),
nn.ReLU(),
nn.Linear(64, 1)
)
검증손실이 증가하면 학습 종료
best_loss = float('inf')
for epoch in range(100):
val_loss = validate()
if val_loss < best_loss:
best_loss = val_loss
else:
print("학습 중단")
break
실무에서 많이 쓰이느 조합.
| 이진분류 | Linear(...,1) | BCEWithLogitsLoss |
| 다중분류 | Linear(...,클래스수) | CrossEntropyLoss |
| 다중라벨분류 | Linear(...,라벨수) | BCEWithLogitsLoss |
| 회귀 | Linear(...,1) | MSELoss |
| 이상치 있는 회귀 | Linear(...,1) | HuberLoss |
| 이미지 분류 | CNN + CrossEntropyLoss | |
| NLP 분류 | LSTM/Transformer + CrossEntropyLoss | |
| 객체 탐지 | YOLO/Faster R-CNN 전용 Loss | |
| 이미지 생성 | GAN/VAE/Diffusion 전용 Loss |
딥러닝 모델 종류
퍼셉트론 가장 기본적인인공신경망 모델
import torch
import torch.nn as nn
model = nn.Linear(2, 1)
x = torch.tensor([[1.0, 2.0]])
output = model(x)
print(output)
다중퍼셉트론 MSL 은닉층을 추가한 모델
import torch
import torch.nn as nn
model = nn.Sequential(
nn.Linear(4, 8),
nn.ReLU(),
nn.Linear(8, 1)
)
x = torch.randn(5, 4)
output = model(x)
print(output.shape)
심층 신경망 DNN MLP 은닉층을 여러개 추가한 구조
import torch
import torch.nn as nn
model = nn.Sequential(
nn.Linear(10, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 32),
nn.ReLU(),
nn.Linear(32, 1)
)
x = torch.randn(8, 10)
output = model(x)
print(output.shape)
합성곱 신경망 CNN 이미지 처리에 특화한 모델
import torch
import torch.nn as nn
class CNN(nn.Module):
def __init__(self):
super().__init__()
self.conv = nn.Conv2d(1, 16, 3)
self.pool = nn.MaxPool2d(2)
self.fc = nn.Linear(16 * 13 * 13, 10)
def forward(self, x):
x = self.pool(torch.relu(self.conv(x)))
x = x.view(x.size(0), -1)
return self.fc(x)
model = CNN()
x = torch.randn(4, 1, 28, 28)
output = model(x)
print(output.shape)
순환 신경망 RNN 순서 정보가 중요한 데이터를 처리하기 위해 개발되었다
import torch
import torch.nn as nn
class RNNModel(nn.Module):
def __init__(self):
super().__init__()
self.rnn = nn.RNN(
input_size=1,
hidden_size=16,
batch_first=True
)
self.fc = nn.Linear(16, 1)
def forward(self, x):
out, _ = self.rnn(x)
return self.fc(out[:, -1])
model = RNNModel()
x = torch.randn(8, 20, 1)
output = model(x)
print(output.shape)
LSTM RNN 문제를 개선한 모델 장기기억이 가능하다.
import torch
import torch.nn as nn
class LSTMModel(nn.Module):
def __init__(self):
super().__init__()
self.lstm = nn.LSTM(
input_size=1,
hidden_size=32,
batch_first=True
)
self.fc = nn.Linear(32, 1)
def forward(self, x):
_, (h, _) = self.lstm(x)
return self.fc(h[-1])
model = LSTMModel()
x = torch.randn(8, 20, 1)
output = model(x)
print(output.shape)
GRU LSTM을 단순화한 모델
import torch
import torch.nn as nn
class GRUModel(nn.Module):
def __init__(self):
super().__init__()
self.gru = nn.GRU(
input_size=1,
hidden_size=32,
batch_first=True
)
self.fc = nn.Linear(32, 1)
def forward(self, x):
_, h = self.gru(x)
return self.fc(h[-1])
model = GRUModel()
x = torch.randn(8, 20, 1)
output = model(x)
print(output.shape)
AuttoEncoder 입력 데이터를 압축했다가 다시 복원하는 모델
import torch
import torch.nn as nn
class AutoEncoder(nn.Module):
def __init__(self):
super().__init__()
self.encoder = nn.Sequential(
nn.Linear(784, 128),
nn.ReLU(),
nn.Linear(128, 32)
)
self.decoder = nn.Sequential(
nn.Linear(32, 128),
nn.ReLU(),
nn.Linear(128, 784)
)
def forward(self, x):
z = self.encoder(x)
x_hat = self.decoder(z)
return x_hat
model = AutoEncoder()
x = torch.randn(16, 784)
output = model(x)
print(output.shape)
VAE Variational AutoEncoder, Autoencoder를 발전시킨 생성 모델
AutoEncoder는 압축후 복원이 목적이고 VAE는 압축 공간에서 새로운 데이터를 생성하는것이 목적이다.
autoecoder에서 만든 잠재 벡터가 어디에 어떻게 분포하는지 보장되지않는다. 간혹 제멋대로 흩어져버리면 쓰레기 이미지가 나올 수 있는데 VAE는 여기서 한단계를 발전해 잠재공간을 정리하기 위해 샘플링을 통해 규칙적으로 잘 정려리킨다. 그래서 그럴듯한 이미지가 잘 나온다.
Autoencoder는 복원오차 MSE만 사용한다면 VAE는 복원오차 + KL Divergence를 사용한다.
잠재공간이 정규분포를 따르도록 한다는 것.
autoencoder가압축과 복원이 가능한대신 생성하지 못한다면 VAE는 이 모든게 가능하다.
GAN 과 비교했을때 품질은 보통인 안정성 좋고 학습이 쉽다면 GAN은 품질이 매우 좋고 학습이 어렵고 불안정하다.
생성 모델의 역사는 Gaussian Mixture Model, Hidden Markov Model, Restrticted Boltzmann Machine, Deep Belief Network 나지만 딥러닝 시대에 이미지 생성 모델로 크게 성공한것은 없다. VAE, GAN, Diffusion정도이며 흐름은 VAE - GAN - Diffusion으로 VAE는 안정적이나 이미지가 흐릿하고 Gan은 매우 선ㄴ명한 이미지를 가졌지만 학습이 불안정하며 튜닝이 어렵지만 한동안 이미지 생성의 왕이었다 .diffusion모델은 이미지에 점점 노이즈가 추가되는것을 제거하는 법을 학습해 품질이 매우 높고 학습이 안정적인 모드 붕괴도 거의 없으나 생성 속도가 느린 모델이다.
import torch
import torch.nn as nn
class VAE(nn.Module):
def __init__(self):
super().__init__()
self.fc_mu = nn.Linear(784, 20)
self.fc_logvar = nn.Linear(784, 20)
def forward(self, x):
mu = self.fc_mu(x)
logvar = self.fc_logvar(x)
return mu, logvar
model = VAE()
x = torch.randn(8, 784)
mu, logvar = model(x)
print(mu.shape)
GAN 생성자와 판별자가 경재아며 학습하는 모델
import torch
import torch.nn as nn
class Generator(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Linear(100, 128),
nn.ReLU(),
nn.Linear(128, 784),
nn.Tanh()
)
def forward(self, z):
return self.net(z)
generator = Generator()
z = torch.randn(16, 100)
fake = generator(z)
print(fake.shape)
Transformer 현재 AI 발전의 핵심모데로 RNN 없이 Attention만 사용한다.
import torch
import torch.nn as nn
class Generator(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Linear(100, 128),
nn.ReLU(),
nn.Linear(128, 784),
nn.Tanh()
)
def forward(self, z):
return self.net(z)
generator = Generator()
z = torch.randn(16, 100)
fake = generator(z)
print(fake.shape)
BERT Transformer encoder 기반 모델 양방향 문맥을 이해하해 문서분류 챗봇 검색엔진에사용된다.
from transformers import BertTokenizer
from transformers import BertModel
tokenizer = BertTokenizer.from_pretrained(
"bert-base-uncased"
)
model = BertModel.from_pretrained(
"bert-base-uncased"
)
inputs = tokenizer(
"Hello world",
return_tensors="pt"
)
outputs = model(**inputs)
print(outputs.last_hidden_state.shape)
GPT Transformer Decoder 기반 생성형 ai모델
VIsion Transformer ViT , Transformer를 이미지 처리에 적용한 모델 CNN 없이 이미지를 분석하고 대규모 데이터에서 우수하다.
Diffusion Model 현재 이미지 생성 AI의 주류 기술. 대표모델에는 stable diffusion, dall-e가 있다.
from transformers import BertTokenizer
from transformers import BertModel
tokenizer = BertTokenizer.from_pretrained(
"bert-base-uncased"
)
model = BertModel.from_pretrained(
"bert-base-uncased"
)
inputs = tokenizer(
"Hello world",
return_tensors="pt"
)
outputs = model(**inputs)
print(outputs.last_hidden_state.shape)
DAY5. 딥러닝 모델 훈련 및 평가
딥러닝 모델 훈련과 평가는 인공지능이 데이터를 학습해 문제를 해결 할 수 있도록 만드는 핵심 과정.
데이터를 입력하고 순전파 forward신경망의 각 층을 차례대로 통과한다.
계산된 결과는 활성화 함수 sigmoid, tanh,reLu, leakly relu, sofrmax를 통과하여 예측값을 생성하고 손실계산이후
역전파로 가중치를 갱신할수있도록 한다.
import torch
import torch.nn as nn
import torch.optim as optim
# 가상 데이터
X = torch.randn(100, 10)
y = torch.randint(0, 2, (100, 1)).float()
# 신경망
class Net(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(10, 32)
self.fc2 = nn.Linear(32, 16)
self.fc3 = nn.Linear(16, 1)
self.relu = nn.ReLU() # 활성화 함수
self.dropout = nn.Dropout(0.3)
def forward(self, x):
# 순전파(Forward)
x = self.relu(self.fc1(x))
x = self.dropout(x)
x = self.relu(self.fc2(x))
# 최종 예측값
x = torch.sigmoid(self.fc3(x))
return x
model = Net()
# 손실함수
criterion = nn.BCELoss()
# 최적화 알고리즘
optimizer = optim.Adam(model.parameters(), lr=0.001)
import torch.nn as nn
# Sigmoid
# 출력값을 0~1 사이로 변환
# 이진 분류(Binary Classification) 출력층에서 사용
sigmoid = nn.Sigmoid()
# Tanh
# 출력값을 -1~1 사이로 변환
# 과거 RNN 계열 모델에서 자주 사용
tanh = nn.Tanh()
# ReLU (가장 많이 사용)
# 음수는 0, 양수는 그대로 출력
# 은닉층(Hidden Layer)의 기본 활성화 함수
relu = nn.ReLU()
# Leaky ReLU
# 음수도 아주 작은 값으로 통과
# ReLU의 Dead ReLU 문제를 완화할 때 사용
leaky_relu = nn.LeakyReLU(0.01)
# Softmax
# 여러 클래스의 확률 합을 1로 변환
# 다중 분류(Multi-Class Classification) 출력층에서 사용
softmax = nn.Softmax(dim=1)
이때 epoch만큼 batch하며 가중치를 업데이트 하는 방법을 최적화알고리즘이라 한다.
* .zero_grad()는 Optimizer가 제공하는 유틸리티 함수, 기울기 비운다.
sgd, momentum, adagrad, rmsprop, adam, adamw 이후
epochs = 5
batch_size = 20
for epoch in range(epochs):
for i in range(0, len(X), batch_size):
batch_x = X[i:i+batch_size]
batch_y = y[i:i+batch_size]
# 순전파
outputs = model(batch_x)
# 손실 계산
loss = criterion(outputs, batch_y)
# 기존 기울기 제거
optimizer.zero_grad()
# 역전파(Backpropagation)
loss.backward()
# 가중치 업데이트
optimizer.step()
print(f"Epoch {epoch+1}, Loss={loss.item():.4f}")
# SGD 가장 기본적인 경사하강법
optimizer = optim.SGD(
model.parameters(),
lr=0.01
)
# Momentum 이전 기울기 방향을 기억하여 관성을 부여
optimizer = optim.SGD(
model.parameters(),
lr=0.01,
momentum=0.9
)
# Adagrad 자주 업데이트되는 파라미터는 학습률을 줄이고, 적게 업데이트되는 파라미터는 크게 학습
optimizer = optim.Adagrad(
model.parameters(),
lr=0.01
)
# RMSprop Adagrad의 학습률이 너무 빨리 감소하는 문제를 개선
optimizer = optim.RMSprop(
model.parameters(),
lr=0.001,
alpha=0.99
)
# Adam Momentum + RMSprop를 결합한 방식
optimizer = optim.Adam(
model.parameters(),
lr=0.001
)
# AdamW Adam의 Weight Decay를 올바르게 분리한 버전, Transformer, LLM에서 가장 많이 사용됨
optimizer = optim.AdamW(
model.parameters(),
lr=0.001,
weight_decay=0.01
)
Optimizer특징현재 사용 빈도
| SGD | 가장 기본 | 낮음 |
| Momentum | SGD 개선 | 보통 |
| Adagrad | 희소 데이터에 강함 | 낮음 |
| RMSprop | RNN 학습에 사용 | 보통 |
| Adam | 범용적으로 강력 | 높음 |
| AdamW | Adam 개선판 | 매우 높음 |
딥러닝 모델 평가가 이어지며 정확도 accuracy 정밀도 precision 재현율 f1score 를 사용한다.
from sklearn.metrics import (
accuracy_score, # 정확도(전체 중 맞춘 비율)
precision_score, # 정밀도(양성이라 예측한 것 중 실제 양성 비율)
recall_score, # 재현율(실제 양성 중 맞게 예측한 비율)
f1_score # 정밀도와 재현율의 조화평균
)
# 실제 정답값
y_true = [1, 0, 1, 1, 0]
# 모델 예측값
y_pred = [1, 0, 1, 0, 0]
# 정확도(Accuracy)
Accuracy : 전체 중 얼마나 맞췄는가
print("Accuracy :", accuracy_score(y_true, y_pred))
# 정밀도(Precision)
Precision : 양성이라고 예측한 것 중 실제 양성 비율
print("Precision:", precision_score(y_true, y_pred))
# 재현율(Recall)
Recall : 실제 양성 중 찾아낸 비율
print("Recall :", recall_score(y_true, y_pred))
# F1 Score
F1 Score : Precision과 Recall의 균형 지표
print("F1 Score :", f1_score(y_true, y_pred))
회귀모델의 평가지표로는 mae, rmse, 결정계수가 있고
from sklearn.metrics import (
mean_absolute_error, # MAE: 예측값과 실제값의 절대 오차 평균
mean_squared_error, # MSE: 오차를 제곱해서 평균 (큰 오차에 더 민감)
r2_score # R²: 모델이 데이터를 얼마나 잘 설명하는지 (1에 가까울수록 좋음)
)
import numpy as np
# 실제 값 (정답)
y_true = [10, 20, 30, 40]
# 예측 값
y_pred = [12, 18, 33, 39]
# MAE: 평균 절대 오차
mae = mean_absolute_error(y_true, y_pred)
# RMSE: MSE에 루트를 씌운 값 (실제 오차 크기 해석 쉬움)
rmse = np.sqrt(
mean_squared_error(y_true, y_pred)
)
# 결정계수 (R²)
r2 = r2_score(y_true, y_pred)
# 결과 출력
print("MAE :", mae)
print("RMSE:", rmse)
print("R² :", r2)
훈련성능은 높지만 새로운 데이터에서 낮은 현상을 과적합이라고 한다.
이때 데이터 증강 , dropout, batch nomalization, early stopping, 정규화 l1, l2 더 많은 데이터 확보의 방법으로 해결한다.
반대로 모델이 충분히 학습하지 못한 상태를 과소적합이라고하다.
# Dropout 일부 뉴런을 랜덤하게 제거하여 특정 뉴런에 과하게 의존하는 것을 방지
import torch.nn as nn
model = nn.Sequential(
nn.Linear(100, 64),
nn.ReLU(),
nn.Dropout(p=0.5), # 50% 확률로 뉴런 제거
nn.Linear(64, 10)
)
# Batch Normalization 각 배치의 평균과 분산을 정규화 평균 = 0 분산 = 1 근처로 맞춤
model = nn.Sequential(
nn.Linear(100, 64),
nn.BatchNorm1d(64), # Batch Normalization
nn.ReLU(),
nn.Linear(64, 10)
)
# Early Stopping 검증 성능이 더 이상 좋아지지 않으면 학습 중단
best_loss = float('inf')
patience = 5
counter = 0
for epoch in range(100):
train()
val_loss = validate()
if val_loss < best_loss:
best_loss = val_loss
counter = 0
else:
counter += 1
if counter >= patience:
print("Early Stopping")
break
# L1 정규화 가중치 절댓값에 패널티 부여
l1_lambda = 0.001
l1_norm = sum(
p.abs().sum()
for p in model.parameters()
)
loss = loss + l1_lambda * l1_norm
#L2 정규화 (Weight Decay)가장 많이 사용한다. 가중치가 너무 커지는 것 방지
optimizer = torch.optim.Adam(
model.parameters(),
lr=0.001,
weight_decay=0.0001
)
딥러닝 모델은 손실함수의 기울기를 미분을 통해 계산하고 손실이 감소하는 방향으로 가중치를 수정한다.
모델 내부에는 수많은 가중치 weight와 편향bias 가 존재하는데 어떤값을 사용해야 예측오차 loss가 최소화될지를 찾아야한다. 이때 핵심이 되는 개념이 기울기. 경사하강법을 사용한다. 기울기를 계산한뒤 오차가 줄어드는 방향으로 조금씩 이동하는 방법.
CNN 모델 분석
1. 합성곱 연산의 이해
입력 영상에 작은 필터(커널)를 이동시키면서 곱셈-덧셈을 수행한다.
import torch
import torch.nn as nn
# 3x3 Conv 레이어 (입력 채널 1개 → 출력 채널 1개)
conv = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3)
# (batch_size=1, channel=1, height=28, width=28)
x = torch.randn(1, 1, 28, 28)
# Conv 연산
y = conv(x)
# 출력: [1, 1, 26, 26]
print(y.shape)
이동평균합성곱의 연산
주변 픽셀의 평균을 계산한다. 원본이 선명한 얼굴이라면 저역통과로 부드러운 얼굴, 노이즈감소 블러효과가 난다.
1 1 1 1 1
1 5 5 5 1
1 5 9 5 1
1 5 5 5 1
1 1 1 1 1
import torch
import torch.nn as nn
# 평균 필터(3x3)
avg_kernel = torch.tensor([
[1/9, 1/9, 1/9],
[1/9, 1/9, 1/9],
[1/9, 1/9, 1/9]
], dtype=torch.float32)
# Conv2d 생성
conv = nn.Conv2d(
in_channels=1,
out_channels=1,
kernel_size=3,
bias=False
)
# 필터 가중치를 평균 필터로 설정
with torch.no_grad():
conv.weight[:] = avg_kernel.view(1, 1, 3, 3)
# 예제 이미지 (5x5)
x = torch.tensor([
[1, 1, 1, 1, 1],
[1, 5, 5, 5, 1],
[1, 5, 9, 5, 1],
[1, 5, 5, 5, 1],
[1, 1, 1, 1, 1]
], dtype=torch.float32)
x = x.view(1, 1, 5, 5)
# 평균 필터 적용
y = conv(x)
print("입력")
print(x[0, 0])
print("\n출력")
print(y[0, 0])
3.22 4.11 3.22
4.11 5.00 4.11
3.22 4.11 3.22
# 가장 기본적인 평균 블러(Average Blur)
# padding=1을 사용했기 때문에 입력 이미지와 출력 이미지의 크기가 동일하게 유지.
from PIL import Image
from torchvision import transforms
img = Image.open("face.jpg").convert("L")
transform = transforms.ToTensor()
x = transform(img).unsqueeze(0)
conv = nn.Conv2d(1, 1, kernel_size=3, padding=1, bias=False)
with torch.no_grad():
conv.weight[:] = torch.ones(1, 1, 3, 3) / 9
y = conv(x)
# 결과 확인
blurred = transforms.ToPILImage()(y.squeeze(0))
blurred.show()
주파수 관점에서는 아래와같이 판단한다.
저주파(Low Frequency) → 천천히 변하는 정보 (얼굴의 전체 윤곽, 밝기)
고주파(High Frequency) → 급격히 변하는 정보 (주름, 잡티, 노이즈, 경계선)
실제로 차이는 연산방식이 아닌 커널 값에 있다. 저역통과의 차이는 주변과 비슷하게, 고역통과는 주변이랑 다르게를 계산한다고본다.
코드를 봤을떄 커널이 전부 양수이면 저역통과일 가능성이 매우 높고 가운데만 크고 주변이 음수라면 고역통과일 가능성이 매우 높다.
저역통과 필터(Low Pass Filter)
낮은 주파수만 통과하고 높은 주파수 제거해 영상이 흐릿해짐.
피부보정앱에서 주름제거, 잡티 제거에 활용한다.
import torch
import torch.nn as nn
# 3x3 평균 필터 = 저역통과 필터
lpf = nn.Conv2d(
in_channels=1,
out_channels=1,
kernel_size=3,
padding=1,
bias=False
)
with torch.no_grad():
lpf.weight[:] = torch.ones(1, 1, 3, 3) / 9
# 랜덤 이미지
x = torch.randn(1, 1, 28, 28)
# 저역통과 필터 적용
y = lpf(x)
print(y.shape)
from PIL import Image
from torchvision import transforms
import torch
import torch.nn as nn
# 이미지 읽기
img = Image.open("face.jpg").convert("L")
# Tensor 변환
x = transforms.ToTensor()(img).unsqueeze(0)
# 저역통과 필터
lpf = nn.Conv2d(1, 1, kernel_size=5, padding=2, bias=False)
with torch.no_grad():
lpf.weight[:] = torch.ones(1, 1, 5, 5) / 25
# 적용
y = lpf(x)
# 결과 저장
result = transforms.ToPILImage()(y.squeeze(0))
result.save("blur_face.jpg")
고역통과 필터(High Pass Filter)
경계(Edge)를 강조한다. 가로/세로 방향 엣지 검출한다. 저역통과필터와 반대로 경계 윤곽선 세부 특징을 강조하고 부드러운 영역을 제거한다.
import torch
import torch.nn as nn
# 고역통과 필터
kernel = torch.tensor([
[-1., -1., -1.],
[-1., 8., -1.],
[-1., -1., -1.]
])
conv = nn.Conv2d(
in_channels=1,
out_channels=1,
kernel_size=3,
bias=False
)
with torch.no_grad():
conv.weight[:] = kernel.view(1, 1, 3, 3)
# 예제 이미지
x = torch.tensor([
[10,10,10,10,10],
[10,10,10,10,10],
[10,10,100,10,10],
[10,10,10,10,10],
[10,10,10,10,10]
], dtype=torch.float32)
x = x.view(1,1,5,5)
y = conv(x)
print(y[0,0])
import cv2
import numpy as np
img = cv2.imread("face.jpg", 0)
kernel = np.array([
[-1,-1,-1],
[-1, 8,-1],
[-1,-1,-1]
])
edge = cv2.filter2D(img, -1, kernel)
cv2.imshow("edge", edge)
cv2.waitKey(0)
흑백영상과 컬러영상
흑백은 채널수가 1개. (1, H, W)
nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3)
컬러는 rgb 채널수 3개, (3, H, W)
nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3)
채널수는 너무 적으면 특징을 충분히 검출하지 못하고 너무 많으면 계산량이 증가하고 과적합이가능하다.
일반적으로 32, 64, 128, 256, 512를 사용한다.
커널 수는 출력 채널수로 CNN 이 만드는 특징맵의 개수이다.
nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3)
Feature Map 특징맵 feature map 1개 = (H, W) 이미지 1장 = 2D 이미지 한 장 = 어떤 필터가 이미지에서 얼마나 강하게 반응했는지 나타낸 2D 결과 이미지
batch 1개
└ feature map 32장
├ map1 (H×W)
├ map2 (H×W)
├ ...
└ map32 (H×W)
한 개의 필터(커널)가 입력 이미지에 적용된 결과, forward의 실행결과.
서로 다른 32개의 특징 검출기로 수평선, 수직선, 곡선, 모서리, 색상변화를 알수 있다.
32개의 필터는 얼굴을 감치할때 눈, 코, 입 얼굴 윤곽... 등의 선, 질감, 밝기변화, 모서리, 복잡한 조합이 동시에 존재할때 다양한 특징을 확보할 수있다.
nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3)
conv = nn.Conv2d(3, 32, 3)
x = torch.randn(1, 3, 224, 224)
y = conv(x)
y.shape = (1, 32, H, W)
CNN 합성곱층 연산 구조
Conv2d = “모든 위치에서 작은 필터를 반복 적용하는 연산”
실제로는 6중 반복문(정확히는 6차원 루프 구조)이다. Pytorch가 gpu에서 최적화를 수행하며 y = conv(x) 한줄로 처리할뿐.. 입력이미지 전체를 작은 커널로 흝으면서 곱하고 더하는 작업을 반복하는 것이다. 내부에서는 6중 루프가 돌아가지만 병렬로 처리하고 최적화 메모리 벡터화를 해 한줄처럼 보일뿐이다.
이런식으로 픽셀마다 반복, 채널마다, 필터마다 반복하면 계산량이 폭발하는것은 당연하다! GPU가 왜 필요한지를 체감해보자.
for batch
for output_channel
for input_channel
for height
for width
for kernel_h
for kernel_w
import torch
def conv2d_manual(x, weight):
"""
x: (B, C_in, H, W)
weight: (C_out, C_in, K, K)
"""
B, C_in, H, W = x.shape
C_out, _, K, _ = weight.shape
H_out = H - K + 1
W_out = W - K + 1
y = torch.zeros(B, C_out, H_out, W_out)
for b in range(B): # batch
for oc in range(C_out): # output channel
for ic in range(C_in): # input channel
for i in range(H_out): # height
for j in range(W_out): # width
for ki in range(K): # kernel height
for kj in range(K): # kernel width
y[b, oc, i, j] += (
x[b, ic, i + ki, j + kj]
* weight[oc, ic, ki, kj]
)
return y
잠시 정리해보자.
✔ Conv2d = 반복문 기반 연산
✔ feature map = 그 결과
✔ GPU = 이 반복문을 병렬로 실행하는 장치
Fully Connected Layer 일반 신경망, 모든 뉴런이 모든 입력과 연결된 층
CNN과 차이가 있다면 CNN은 이미지 일부로 특징을 추출하지만 Fully Connected 는 전체를 한번에보고 모든 픽셀을 다 연결시킨다. CNN에 끝부분에 활용된다.
이미지 - Conv (특징 추출) - Conv (더 복잡한 특징) - Flatten - FC (결정)
마치 최종판단과 같다. Conv가 특징을 찾는것을 완료했다면 fc는 특징들을 보고 결론을 내리는 것.
입력 출력
x1 ─┬────→ o1
x2 ─┼────→ o2
x3 ─┼────→ o3
x4 ─┘────→ o4
단.
Fully Coinnected Layer
Conv출력 (512, 7, 7)을 flatten하면 512 × 7 × 7 = 25088, 이것을 FC로 연결하면 nn.Linear(25088, 1000) 즉 파라미터수가 25088 × 1000 = 25,088,000개.
메모리가 많이 먹고 학습이 느리며 과적합이 쉽게 발생할 수가있다. 그래서 사실 요즘에는
GAP Global Average Pooling, featuremap을 평균으로 하나의 숫자로 줄여 사용한다.
Conv → Conv → Conv → GAP → Softmax
FC : (512×7×7) → 1000
GAP: (512×7×7) → (512)
FC는 완전히안쓰인다기보다 작은 모델이나 MLP head, Transformer hybrid, detection head 일부로 사용하나 대형 cnn 분류기에서는 거의 GAP으로 대체 되었다고 하겠다.
FC "모든 픽셀을 다 연결해서 “외워버리는 구조”
GAP “각 특징이 얼마나 있는지 요약해서 판단”
Pooling layer
feature map크기를 줄이면저 중요한 정보만 남기는 연산.
Gap를 이해했다면 더 잘 연상 할 수 있다.
이미지 - Conv (특징 추출) - ReLU - Pooling (크기 줄이기) - Conv - Pooling - Flatten / GAP - FC or Classifier
Max Pooling “가장 큰 값만 가져온다” 가장 강한 특징만 남겨 edge나 텍스쳐를 강조한다.
Average Pooling" 평균값을 가져온다" 전체적으로 부드럽게 요약해 정보를 평균화한다.
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
import torch
import torch.nn as nn
# 입력 feature map (batch=1, channel=1, 4x4 이미지)
x = torch.tensor([[
[1., 2., 3., 4.],
[5., 6., 7., 8.],
[9., 10., 11., 12.],
[13., 14., 15., 16.]
]]).unsqueeze(0)
# Max Pooling (2x2 영역에서 최대값 선택)
max_pool = nn.MaxPool2d(kernel_size=2)
# Average Pooling (2x2 영역 평균)
avg_pool = nn.AvgPool2d(kernel_size=2)
# 적용
y_max = max_pool(x)
y_avg = avg_pool(x)
print("Max Pooling 결과:")
print(y_max)
print("\nAverage Pooling 결과:")
print(y_avg)
6 8
14 16
3.5 5.5
11.5 13.5
그렇다면 pooling이있는데 왜 flatten이 아닌 gap를 쓰게 되었을까?
pooling은 사실상 줄이기만 하고 완전히 1개로 줄어들 수는 없엇고 flatten은 여전히 필요했다. fc에서 파라미터가 너무 많은 것이 문제엿고 각 fateuremap을 하나의 값으로 요약하자라는 아이디어 로 탄생한것.
즉 pooling의 발전형이 아니라 flatten + fc를 대체 하기 위해 나온 완전히 다른 요약 방식이다.
Conv → Pool → Conv → Pool → Flatten → FC
Conv → Conv → Conv → GAP → Softmax
Convolution vsPooling
| 연산 | 가중치 있음 | 없음 |
| 학습 | O (학습됨) | X (고정) |
| 역할 | 특징 추출 | 크기 줄이기 |
| 결과 | 새로운 feature | 요약된 feature |
Pooling vs GAP
* 사실상 fc때문에 flatten이 필요했다. gap이 나오면서 구조가 바뀌어 flatten이 사실상 사라질 수 있었다.
| 범위 | 부분 영역 | 전체 영역 |
| 출력 | 작은 feature map | 숫자 1개/채널 |
| 목적 | 다운샘플링 | 완전 요약 |
Stride 합성곱의 이동간격, 커널이 한번에 얼마나이동하는가 .
기본 COnv에서는 stride1칸씩 이동했다. 이는 정보를 많이 유지하는 대신에 출력 크기가 큰데 stride가 2만 되어도 2칸 씩 점프하며 이동을 하니 출력 크기 절반 정도로 감소된다.
nn.Conv2d(
3,
32,
kernel_size=3,
stride=2
)
conv하면 보통 커널로 잘라 슬라이드해 보기때문에 커널 사이즈 ( 예 3*3)에 맞춰 완전히 이미지 안에 있어야하기 때문에 삐져나가는 픽셀이 있을경우 못하고 넘어간다. 그러기에 계속해서 줄어들수가있는데 padding을 주면 이미지의 값을 유지할 수있다. 테두리 정보 손실 방지를 위한 방법. 창문 바깥에 벽 0 을 붙여 끝까지 닦게 해준다고 상상해보자.
이 과정은 계산을 더하는것이 아니라 다음단계 conv가 처리해야할 데이터 자체를 줄여버리는 것으로 이해하자. pooling은 연산이 목적이 아니다 데이터 개수를 줄이는 필터의 용도이다.
Input
↓
Conv (연산 많음)
↓
Feature Map (크기 감소 or 유지)
↓
Pooling (크기 확 줄임)
↓
다음 Conv (더 작은 입력만 처리)
풀링은 설정값에 따라 줄일수도, 유지할 수도있다. padding 기본값 = 0 (없음)
nn.MaxPool2d(kernel_size=2, stride=2)
nn.MaxPool2d(kernel_size=2, stride=1, padding=1)
* 참고 pytorch maxpool2d 기본값
nn.MaxPool2d(
kernel_size,
stride=None,
padding=0,
dilation=1,
return_indices=False,
ceil_mode=False
)
CNN에서 Pooling을사용하면 계산량이 줄어들고 maxpooling으로 중요한 특징을 남길수있다.
고양이검출 모델에서 귀가 어딘가에 존재하고,
0 0 0 0
0 0 9 0
0 0 0 0
0 0 0 0
maxfoling을 통해 간추려진 결과로는 위치값이 아닌 '9가 있다!' 라는 판단.
0 9
0 0
즉 pooling 이후에는 정확한 위치보다는 잇냐없냐만 보기때위치변화에 강해짐으로 고양이가 왼쪽에있든 오른쪽에 있든 pooling결과는 동일하여 결과가 거의 같아져 위치변화에 강한 모델이 된다.
즉 . Max pooling은 공간 해상도를 줄여 특징의 위치 정보를 희석시키기 때문에 translation invariance(위치 변화 강인성)를 만든다.
Bias 편향, 뉴런 출력에 더해지는 상수.
뉴런이 아무입력이 없어도 활성화되리 수있게 만드는 기준점이다.
뉴런은 y = wx + b로 계산되는데 b, bias강수가 0일때 출력이 무조건 0기준으로 고정되어버린다. 이상태로 relu를 통과하면? 음수로 죽어버린다. 이때 bias가 있어 작은 수를 더해지면? relu에서도 살아남을 수있는것. !
합성곱도 bias 존재한다. reLU 전에 값이 너무 작으면 bias로 올려준다는 의미로 우리가 봐왔다.
nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, bias=True)
Batch Normalization, 내부 공변량 변화(Internal Covariate Shift) BatchNorm
미니배치 단위로 출력값을 정규화해 학습을 안정화한다.
학습중에 layer1이학습되면 layer2입력 분포가 게속 변해 학습이 불안정해진것을 각 mini-batch 마다 평균 0, 분산 1로 맞처 loss 진동이 감소해 학습이 안정화되고 더 큰 learning rate를 사용가능하니 학습 속도가 증가하며 값이 너무 작아지는 문제를 완화하니 과적합 일부가 감소하고 gradient vanishing이 감소한다.
따라서 BN이 없으면 값이 점점 커지거나 작아지는등에 학습이 불안정하나 bn이있으면 항상 비슷한 스케일을 유지해 안정적인 학습이가능하게 된다.
nn.Conv2d(
3,
32,
3,
bias=True
)
import torch.nn as nn
model = nn.Sequential(
nn.Conv2d(3,32,3,padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(32,64,3,padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(64*56*56,128),
nn.ReLU(),
nn.Linear(128,10)
)
즉 bias는 출력값을 조정하는 단순한 상수,
batchnorm은 학습중 데이터 분포를 안정적으로유지하는 정규화 기술이라하겠다.
Bias vs BatchNorm
| 역할 | 값 이동 | 분포 정규화 |
| 범위 | 하나의 값 | 전체 배치 |
| 목적 | 표현력 증가 | 학습 안정화 |
'Personal > SK 네트웍스 AI 캠프' 카테고리의 다른 글
| [SK네트웍스 Family AI 캠프] 32기 7주차 회고: Day24 ~ Day26 (0) | 2026.06.15 |
|---|---|
| SK 네트웍스 AI 캠프 - 3_딥러닝 - Day27_딥러닝 모델 저장 및 활용 (0) | 2026.06.15 |
| SK 네트웍스 AI 캠프 - 3_딥러닝 - Day25_딥러닝 네트워크 신경망 알고리즘 계층 구조 (0) | 2026.06.12 |
| SK 네트웍스 AI 캠프 - 3 _딥러닝 - Day24_딥러닝개념_프레임워크설치 (0) | 2026.06.08 |
| [SK네트웍스 Family AI 캠프] 32기 6주차 회고: Day20 ~ Day23 + 5월 종합 (0) | 2026.06.05 |