[파이토치 딥러닝] 1단계: 딥러닝 입문하기
딥러닝 이론 공부 후에 프로젝트를 진행하며 실습의 부족함을 느껴, 파이토치 실습 위주의 학습을 진행하려고 한다.
교재는 “텐초의 파이토치 딥러닝 특강”을 참고하였다. 더 자세히 학습하고 싶은 사람은, 이론 부분을 따로 참고해서 읽길 권한다.
주석을 상세히 적으며 설명하였지만, 초반부분에 이미 설명한 부분은 설명을 생략하였다.
1단계: 딥러닝 입문하기
- 핵심 용어
-
인공 뉴런(퍼셉트론): 입력값과 가중치, 편향을 이용해 출력값을 내는 수학적 모델이다.
-
단층 인공 신경망: 퍼셉트론을 하나만 사용하는 인공 신경망이다.
-
다층 인공 신경망: 퍼셉트론을 여러 개 사용하는 인공 신경망이다.
-
가중치: 입력의 중요도를 나타낸다.
-
편향: 활성화의 경계가 원점으로부터 얼마나 이동할지를 결정한다.
-
활성화 함수: 해당 뉴런의 출력을 다음 뉴런으로 넘길지를 결정한다.
-
손실 함수: 정답과 신경망의 예측의 차이를 나타내는 함수이다.
-
경사 하강법: 손실을 가중치에 대해 미분한 다음, 기울기의 반대 방향으로 학습률만큼 이동시키는 알고리즘이다.
-
오차 역전파: 올바른 가중치를 찾기 위해 오차를 출력층으로부터 입력층까지 전파하는 방법이다.
-
오버 피팅: 학습에 사용한 데이터에 최적화되게 학습되어서 다른 데이터에 대한 예측 성능이 떨어지는 경우를 의미한다.
-
기울기 소실: 출력층으로부터 멀어질 수록 역전파되는 오차가 0에 가까워지는 현상이다.
-
1. 파이토치 권고 코딩 스타일
# 두 텐서의 합 구하기
import torch
# 텐서 만들기
a = torch.tensor([1, 2, 3])
b = torch.tensor([4, 5, 6])
# 두 텐서의 합 구하기
c = a + b
# 텐서 출력하기
print(c)
tensor([5, 7, 9])
-
파이토치는 클래스를 사용하도록 권고한다.
-
모듈 클래스: 신경망의 동작 정의
-
데이터셋 클래스: 데이터를 다룸
-
1.1 모듈 클래스
# 모듈 클래스 (기본적인 뼈대)
class Net(nn.Module):
def __init__(self):
'''
# 신경망 구성 요소 정의
'''
def forward(self, input):
'''
# 신경망의 동작 원리
'''
return output
-
__init__(): 신경망의 구성 요소를 정의하는 함수
-
forward(): 신경망의 동작을 정의하는 함수
1.2 데이터셋 클래스
# 데이터셋 클래스 (기본적인 뼈대)
class Dataset():
def __init__(self):
'''
필요한 데이터 불러오기
'''
def __len__(self):
'''
데이터의 개수 반환
'''
return len(data)
def __getitem__(self, i):
'''
i번째 입력 데이터와
i번째 정답을 반환
'''
return data[i], label[i]
-
__init__(): 학습에 필요한 데이터 불러오기
-
__len__(): 데이터 개수 반환하기
-
__getitem__(): i번째 입력 데이터와 정답을 반환하기
1.3 모듈 클래스와 데이터 클래스를 이용하여 학습하기
# 데이터로더로부터 데이터와 정답을 받아오기
for data, label in DataLoader():
# 모델의 예측값 계산
prediction = model(data)
# 손실 함수를 이용해 오차 계산하기
loss = LossFunction(prediction, label)
# 오차 역전파
loss.backward()
# 신경망 가중치 수정
optimizer.step()
- DataLoader: 데이터셋 클래스를 입력으로 받아 학습에 필요한 양 만큼의 데이터를 불러오는 역할을 수행한다.
-
데이터로더로부터 데이터와 정답을 불러와 신경망의 예측값을 계산한다.
-
손실 함수를 이용해 신경망의 오차를 계산한다.
-
backward() 메서드를 이용해 오차를 역전파한다.
-
step() 메서드를 이용해 신경망의 가중치를 수정한다.
2. 딥러닝 문제 해결 프로세스
-
해결할 문제 정의
-
데이터 수집
-
데이터 가공
-
딥러닝 모델 설계
-
딥러닝 모델 학습
-
성능 평가
3. 딥러닝 문제 해결 체크리스트
-
풀어야할 문제 이해하기
[ ] 정확한 값을 예측하는 회귀 문제인가?
[ ] 입력이 속한 범주를 예측하는 분류 문제인가?
-
데이터 파악하기
[ ] 입력 자료형과 정답 확인하기
[ ] 클래스 간의 불균형은 없는지 확인하기
[ ] 누락된 데이터 혹은 자료형에 맞지 않는 데이터가 포함되어 있는지 확인하기
-
데이터 전처리
[ ] 학습에 필요한 데이터가 부족하다면 데이터 증강하기
[ ] 데이터를 정규화해서 값의 범위 맞추기
-
신경망 설계
[ ] 데이터의 공간 정보가 중요하면 합성곱 적용하기
[ ] 데이터의 순서 정보가 중요하면 RNN, Transformer 계열 적용하기
-
신경망 학습
[ ] 적합한 손실 함수 찾기
[ ] 가중치 수정을 위한 최적화 정하기
[ ] 신경망의 성능을 평가하기 위한 평가 지표 정하기
-
손실이 무한대로 발산하면
[ ] 손실 함수 바꿔보기
[ ] 데이터에 이상한 값이 섞여 있는지 확인하기
[ ] 학습률 줄이기
-
손실이 0으로 수렴한다면
[ ] 데이터가 부족하지 않은지 확인하기
[ ] 신경망 크기 줄여보기
4. 직관적 분석에 유용한 시각화
import numpy as np
import matplotlib.pyplot as plt
# 데이터 생성
# 0부터 5까지 0.2씩 증가시킨 값을 저장
t = np.arange(0, 5, 0.2)
t2 = t ** 2
t3 = t ** 3
# 평균은 mu, 표준편차를 sigma로 갖는 정규분포로부터 랜덤하기 10000개 추출
mu, sigma = 100, 15
x = mu + sigma * np.random.randn(10000)
4.1 선형 그래프
# 두 개의 그래프 한 figure에 그리기
plt.plot(t, t2, label='t2')
plt.plot(t, t3, label='t3')
plt.title('show 2 plos in 1 figure')
plt.legend()
plt.show()
4.2 서브 플롯
# 서브 플롯
plt.subplot(1, 2, 1) # (행, 열, 순서)
plt.plot(t, t)
plt.subplot(1, 2, 2)
plt.plot(t, t3)
plt.show()
4.3 히스토그램
# 히스토그램
plt.xlabel('x')
plt.ylabel('y')
plt.title('Histogram of Normal distribution')
plt.hist(x, 50, density=1, facecolor='g', alpha=0.75) # (데이터, 구간개수, 1이면 y축 값이 1이 되도록 정규화(면적1인 밀도 함수)), 색상, 불투명도)
plt.show()
5. 사인 함수 예측하기
- 핵심 용어
-
모듈: 신경망을 구성하는 기본 객체이다. 모듈에는 구성요소를 정의하는 __init__() 함수와 순전파의 동작을 정의하는 forward() 함수가 있다.
-
MSE(평균 제곱 오차): 값의 차이의 제곱의 평균이다. 회귀에서 MSE 손실을 사용한다.
-
CE(크로스 엔트로피): 두 확률 분포의 차이이다. 분류에서 CE 손실을 사용한다.
-
다중분류: 신경망의 입력을 여러 범주로 분류하는 알고리즘이다.
-
피처: 신경망의 입력으로 들어오는 값으로 데이터가 갖고 있는 특징이다.
-
배치: 데이터셋의 일부로 신경망의 입력으로 들어가는 단위이다.
-
에포크: 전체 데이터를 모두 한 번씩 사용했을 때의 단위이다.
-
이터레이션: 하나의 에포크에 들어있는 배치 수이다.
-
Adam: 모멘텀과 RMSprop을 섞어놓은 가장 흔하게 사용되는 최적화 알고리즘이다.
-
모멘텀: 기울기를 계산할 때 관성을 고려하는 최적화 알고리즘이다.
-
RMSprop: 이동평균을 이용해 이전 기울기보다 현재의 기울기에 더 가중치를 두는 최적화 알고리즘이다.
-
-
신경망 학습 순서
-
모델 정의
-
모델 순전파
-
오차 계산
-
오차 역전파 (가중치 업데이트)
-
2~4 원하는 만큼 반복
-
학습 종료
-
5.1 랜덤하게 가중치를 적용하여 사인 곡선 그리기
import math
import torch
import matplotlib.pyplot as plt
x = torch.linspace(-math.pi, math.pi, 1000) # -pi부터 pi 사이에서 점을 1000개 추출
y = torch.sin(x)
# 임의의 가중치를 뽑아 y 만들기
a = torch.randn(())
b = torch.randn(())
c = torch.randn(())
d = torch.randn(())
# 사인 함수를 근사할 3차 다항식 정의
y_random = a * x**3 + b * x**2 + c * x + d
# 실제 사인 곡선을 실제 y값으로 만들기
plt.subplot(2, 1, 1)
plt.title('y true')
plt.plot(x, y)
# 예측 사인 곡선을 임의의 가중치로 만든 y값으로 만들기
plt.subplot(2, 1, 2)
plt.title('y pred')
plt.plot(x, y_random)
# 실제와 예측된 사인 곡선 출력하기
plt.show()
-
linspace(a, b, c): 시작점a부터 종료점b까지 데이터 c개를 반환하기
-
sin(a): 입력a에 대한 사인 함수 값을 반환하기
-
randn(): 정규분포를 따르는 랜덤한 값을 반환하기
-
subplot(pos): 그림의 위치 pos에 그래프를 지정하기
5.2 가중치를 학습시켜 사인 곡선 그리기
learning_rate = 1e-6 # 학습률 정의
# 학습 2000번 진행
for epoch in range(2000):
y_pred = a*x**3 + b*x**2 + c*x + d
loss = (y_pred - y).pow(2).sum().item() # 손실 정의
if epoch % 100 == 0:
print(f'epoch{epoch+1} loss:{loss}')
grad_y_pred = 2.0 * (y_pred - y) # 기울기의 미분값
grad_a = (grad_y_pred * x**3).sum()
grad_b = (grad_y_pred * x**2).sum()
grad_c = (grad_y_pred * x).sum()
grad_d = grad_y_pred.sum()
a -= learning_rate * grad_a # 가중치 업데이트
b -= learning_rate * grad_b
c -= learning_rate * grad_c
d -= learning_rate * grad_d
# 실제 사인 곡선 그리기
plt.subplot(3, 1, 1)
plt.title('y true')
plt.plot(x, y)
# 예측한 사인 곡선 그리기
plt.subplot(3, 1, 2)
plt.title('y pred')
plt.plot(x, y_pred)
# 랜덤한 가중치의 사인 곡선 그리기
plt.subplot(3, 1, 3)
plt.plot(x, y_random)
plt.title('y_random')
plt.show()
6. 캘리포니아 집값 예측하기: 회귀 분석
6.1 데이터 불러오기
import pandas as pd
from sklearn.datasets import fetch_california_housing
dataset = fetch_california_housing()
df = pd.DataFrame(dataset['data']) # 데이터 불러오기
df.columns = dataset['feature_names'] # 특징 이름 불러오기
df['target'] = dataset['target'] # 데이터프레임에 정답 추가
print(df.head())
MedInc HouseAge AveRooms AveBedrms Population AveOccup Latitude Longitude target 0 8.3252 41.0 6.984127 1.023810 322.0 2.555556 37.88 -122.23 4.526 1 8.3014 21.0 6.238137 0.971880 2401.0 2.109842 37.86 -122.22 3.585 2 7.2574 52.0 8.288136 1.073446 496.0 2.802260 37.85 -122.24 3.521 3 5.6431 52.0 5.817352 1.073059 558.0 2.547945 37.85 -122.25 3.413 4 3.8462 52.0 6.281853 1.081081 565.0 2.181467 37.85 -122.25 3.422
6.2 모델 정의 및 학습하기
# 선형회귀 MLP 모델 설계
import torch
import torch.nn as nn
from torch.optim.adam import Adam
# 모델 정의
model = nn.Sequential(
nn.Linear(8, 100),
nn.ReLU(),
nn.Linear(100, 1)
)
X = df.iloc[:, :-1].values
y = df['target'].values
batch_size = 100
learning_rate = 0.001
# 가중치를 수정하는 최적화 함수 정의
optim = Adam(model.parameters(), lr=learning_rate)
# 에포크 반복
for epoch in range(200):
# 배치 반복
for i in range(len(x)//batch_size):
start = i*batch_size
end = start + batch_size
# 파이토치 실수형 텐서로 변환
x = torch.FloatTensor(X[start:end])
y = torch.FloatTensor(y[start:end])
# 가중치의 기울기를 0으로 초기화
optim.zero_grad()
# 모델의 예측값 계산
preds = model(x)
# MSE 손실 계산
loss = nn.MSELoss()(preds, y)
# 오차 역전파
loss.backward()
# 최적화 진행
optim.step()
if epoch % 20 == 0:
print(f'epoch{epoch} loss:{loss.item()}')
epoch0 loss:28.34756851196289 epoch20 loss:2.6354379653930664 ... epoch160 loss:0.6834787130355835 epoch180 loss:0.6697102189064026
6.3 모델 성능 평가하기
prediction = model(torch.FloatTensor(X[0, :13]))
real = y[0]
print(f'prediction:{prediction.item()} real:{real}')
prediction:2.0168309211730957 real:4.526000022888184
7. 손글씨 분류하기: 다중 분류
7.1 데이터 로드하기
import matplotlib.pyplot as plt
from torchvision.datasets.mnist import MNIST
from torchvision.transforms import ToTensor
# 학습용 데이터와 평가용 데이터 분리
training_data = MNIST(root='./', train=True, download=True, transform=ToTensor())
test_data = MNIST(root='./', train=False, download=True, transform=ToTensor())
print(len(training_data))
print(len(test_data))
# 샘플 이미지 9개 출력
for i in range(9):
plt.subplot(3, 3, i+1)
plt.imshow(training_data.data[i])
plt.show()
60000 10000
- 모든 데이터가 파이썬 이미지 파일로 저장되어 있기에 ToTensor() 함수를 이용해 파이토치 텐서로 바꿔준다.
from torch.utils.data.dataloader import DataLoader
train_loader = DataLoader(training_data, batch_size=32, shuffle=True)
test_loader = DataLoader(test_data, batch_size=32, shuffle=False)
-
학습용 데이터는 데이터를 섞어 모든 범주의 데이터가 골고루 나오도록한다.
-
평가용 데이터는 섞을 필요가 없다.
-
DataLoader(A): 데이터셋 A를 원하는 배치 크기로 나누어 반환한다.
-
batch_size: 배치 크기 결정
-
shuffle: 데이터 섞을지에 대한 여부 결정
-
7.2 모델 정의 및 학습하기
import torch
import torch.nn as nn
from torch.optim.adam import Adam
# 학습에 사용할 프로세서 지정
device = 'cuda' if torch.cuda.is_available() else 'cpu'
model = nn.Sequential(
nn.Linear(784, 64),
nn.ReLU(),
nn.Linear(64, 64),
nn.ReLU(),
nn.Linear(64, 10)
)
model.to(device) # 모델의 파라미터를 GPU로 보냄
lr = 1e-3
optim = Adam(model.parameters(), lr=lr)
for epoch in range(20):
for data, label in train_loader:
optim.zero_grad()
# 입력 데이터의 모양을 모델의 입력에 맞게 변환
data = torch.reshape(data, (-1, 784)).to(device)
preds = model(data)
loss = nn.CrossEntropyLoss()(preds, label.to(device)) # 손실 계산
loss.backward() # 오차 역전파
optim.step() # 최적화 진행
print(f'epoch{epoch+1} loss:{loss.item()}')
# 모델을 MNIST.pth라는 이름으로 저장
torch.save(model.state_dict(), "MNIST.pth")
epoch1 loss:0.0890120267868042 epoch2 loss:0.07549810409545898 ... epoch19 loss:0.0012714057229459286 epoch20 loss:0.0012847029138356447
-
is_available(): GPU 사용 가능 여부를 반환한다.
-
to(device): 텐서를 device로 보낸다. (모델 파라미터, 데이터)
-
reshape(A, shape): 텐서A를 shape 모양으로 변형시킨다.
-
save(A): 객체를 A에 저장한다.
-
A.state_dict(pth): A 모델의 가중치를 딕셔너리 형태로 반환한 뒤, pth에 저장한다.
7.3 모델 성능 평가하기
# 모델 가중치 불러오기
model.load_state_dict(torch.load("MNIST.pth", map_location=device))
num_corr = 0 # 분류에 성공한 전체 개수
with torch.no_grad(): # 기울기를 계산하기 않음
for data, label in test_loader:
data = torch.reshape(data, (-1, 784)).to(device)
output = model(data.to(device))
preds = output.data.max(1)[1] # 모델의 예측값 계산
# 올바르게 분류한 개수
corr = preds.eq(label.to(device).data).sum().item()
num_corr += corr
print(f"Accuracy:{num_corr/len(test_data)}") # 분류 정확도 출력
Accuracy:0.9769
-
map_location: 모델 파일을 불러올 위치
-
no_grad(): 기울기를 계산하지 않는다.
-
max() 최대 예측값과 인덱스를 리스트로 묶어서 반환한다.
-
max(1)[1]: 가장 높은 값을 갖는 위치를 반환한다.
-
max(0): 배치에서 가장 높은 값
-
max(1): 클래스 차원에서 가장 높은 값
-
-
eq(): 값이 같으면 1, 다르면 0 반환한다.
-
A.item(): 텐서A의 값만을 반환한다.
댓글남기기