3. 파이토치로 구현하는 ANN
3.3 신경망 모델 구현하기
3.3.1 인공 신경망(ANN)
- 입력층 : 자극 입력받는 감각 기관
- 은닉층(=중간층)
- 출력층 : 마지막 뉴런
각 층에 존재하는 한 단위의 인공뉴런 = 노드
생물학적 신경세포는 인접 신경세포에 자극을 전달하기 전, 입력받은 자극에 여러 화학적 처리를 가한다.
이와 유사하게 인공 신경망에서는 화학적 처리 대신 특정 수학 연산을 시행한다.
각 층에 존재하는 매개변수인 가중치에 행렬곱시키고, 편향을 더해주는 것.
- 가중치 = 입력 신호가 출력에 주는 영향을 계산하는 매개변수
- 편향 = 각 노드가 얼마나 데이터에 민감한지 알려주는 매개변수
이 행렬곱의 결과는 활성화 함수를 거쳐 인공뉴런의 결괏값 산출.
- 활성화 함수 = 입력에 적절한 처리를 해서 출력 신호로 변환하는 함수. 입력 신호의 합이 활성화를 일으키는지 아닌지를 정하는 역할.
💡 참고 링크 : 활성화 함수 참고 링크
이 작업을 반복해서 마지막 출력층에서 결괏값을 만들어낸 후,
인공 신경망의 출력층이 낸 결괏값과 정답을 비교해 오차 계산해야함.
오차를 기반으로 신경망 전체를 학습시키려면, 겹겹이 쌓인 가중치를 뒤에서부터 차례대로 조정하고 최적화하는 알고리즘 = 역전파 알고리즘
3.3.2 간단한 분류 모델 구현하기
지도학습, 그 중에서도 분류를 하는 간단한 인공 신경망 구현.
# 1. 라이브러리 import
import torch
import numpy
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
import torch.nn.functional as F
# 2. 학습과 평가에 사용할 데이터셋 만들기
n_dim = 2
# make_blobs() 사용하여
# 각 데이터 한 점이 몇 번째 클러스터에 속해 있는지 알려주는 인덱스 레이블 생성
x_train, y_train = make_blobs(n_samples = 80, n_features = n_dim,
centers = [[1, 1], [-1, -1], [1, -1], [-1, 1]],
shuffle = True, cluster_std = 0.3)
x_test, y_test = make_blobs(n_samples = 20, n_features = n_dim,
centers = [[1, 1], [-1, -1], [1, -1], [-1, 1]],
shuffle = True, cluster_std = 0.3)
make_blobs()
: 예제용 데이터셋을 만들어주는 함수. 실행 때마다 랜덤한 데이터셋 생성.
n_samples
: 표본 데이터 수, 기본값 100n_features
: 독립 변수 수, 기본값 20centers
: 클러스터 수 혹은 중심, 기본값 3shuffle
: 숫자 랜덤으로 섞을 것인지cluster_std
: 클러스터 표준 편차, 기본값 1.0center_box
: 클러스터 바운딩 박스, 기본값 (-10.0, 10.0))
💡 참고링크 : make_blobs()
# 0, 1 => 전부 0번 레이블을 갖도록 바꾸기
# 2, 3 => 전부 1번 레이블 갖도록 바꾸기
def label_map(y_, from_, to_):
y = numpy.copy(y_)
for f in from_:
y[y_ == f] = to_
return y
y_train = label_map(y_train, [0, 1], 0)
y_train = label_map(y_train, [2, 3], 1)
y_test = label_map(y_test, [0, 1], 0)
y_test = label_map(y_test, [2, 3], 1)
# 시각화
def vis_data(x, y = None, c = 'r'):
if y is None:
y = [None] * len(x)
for x_, y_ in zip(x, y):
if y_ is None:
plt.plot(x_[0], x_[1], '*', markerfacecolor = 'none',
markeredgecolor = c)
else:
plt.plot(x_[0], x_[1], c+'o' if y_ == 0 else c+'+')
plt.figure()
vis_data(x_train, y_train, c = 'r')
plt.show()
이렇게 생긴 애는 y = wx + b 는 선형식이라 얘를 구분할 수 없다.
이를 해결하기 위해선 Single layer 가 아닌 Multiple layer 를 사용해야한다. (멀티플 레이어의 Error BackPropagation 을 사용해야한다고 하는데 일단 넘어가고 나중에 다시 와서 찾아보기)
hidden layer를 중간에 두어 깊이를 깊게 만들어보자 해서 만들어진 것이 Deep Neural Netwark 이다.
# 생성한 데이터를 넘파이 벡터에서 파이토치 텐서로 바꿔주기
x_train = torch.FloatTensor(x_train)
x_test = torch.FloatTensor(x_test)
y_train = torch.FloatTensor(y_train)
y_test = torch.FloatTensor(y_test)
# 신경망 모델을 구현.
# 파이토치에서 신경망은 보통 `torch.nn.Module`을 상속 받는 파이썬 클래스로 정의.
class NeuralNet(torch.nn.Module):
# 생성자
def __init__(self, input_size, hidden_size):
super(NeuralNet, self).__init__() # nn.Module 상속받은 걸로 초기화하기
self.input_size = input_size # 신경망에 입력되는 데이터의 차원
self.hidden_size = hidden_size # 신경망의 은닉층 크기
# 입력된 데이터가 인공 신경망 통과하면서 거치는 연산들
self.linear_1 = torch.nn.Linear(self.input_size, self.hidden_size) # input_size 개를 받아서 hidden_size 개를 은닉층에 출력
self.relu = torch.nn.ReLU() # 활성화함수로는 relu 를 쓴다
self.linear_2 = torch.nn.Linear(self.hidden_size, 1) # hidden_size 개를 입력받아서 1개 출력한다
self.sigmoid = torch.nn.Sigmoid() # 활성화함수로는 sigmoid 쓴다
# 위에서 만들어 놓은 애들 차례대로 실행하기
def forward(self, input_tensor):
# 입력데이터에 [input_size, hidden_size] 크기의 가중치를 행렬곱하고 편향 더해서 [1, hidden_size] 꼴의 텐서 반환.
linear1 = self.linear_1(input_tensor) # 위에서 정의한 linear_1에 input_tensor 넣음
relu = self.relu(linear1)
linear2 = self.linear_2(relu)
output = self.sigmoid(linear2)
return output
차례대로
[input_size, hidden_size]
- (linear1) ->[1, hidden_size]
- (relu) ->[1, hidden_size]
- (linear2) ->[1, 1]
이 마지막 텐서를 sigmoid에 넣으면 값 하나(0에서 1사이 값)을 반환해준다.
💡 참고링크 : 파이토치 튜토리얼
# 신경망 객체 생성
model = NeuralNet(2, 5) # input_size = 2, hidden_size = 5
lr = 0.03
# 오차 함수
# BCELoss() = 이진 교차 엔트로피
criterion = torch.nn.BCELoss()
epochs = 2000
# 학습에 사용할 최적화 알고리즘으로는 확률적 경사하강법(SGD) 선택
opt = torch.optim.SGD(model.parameters(), lr = lr)
# 모델 성능 평가
model.eval() # 평가 모드로 변경
# 모델의 결괏값과 레이블(y)의 차원 맞춰주기 위해 squueze로 펴줌.
test_loss_before = criterion(model(x_test).squeeze(), y_test)
print(f"Before Training, test loss is {test_loss_before.item()}")
출력값
Before Training, test loss is 0.7325138449668884
# 학습을 통한 성능 개선
for epoch in range(epochs):
model.train() # 학습 모드로 변경
opt.zero_grad() # epoch마다 새로운 경사값 계산해야하기 때문에 zero_grad() 사용해서 경삿값 0으로 설정.
train_output = model(x_train) # 학습 데이터로 학습
train_loss = criterion(train_output.squeeze(), y_train) # 결과값과 실제 정답과 비교해서 오차 계산
# 학습 잘 되고 있는지 확인
if epoch % 100 == 0:
print(f"Train loss at {epoch} is {train_loss.item()}")
# 오차 함수를 가중치로 미분해서 오차가 최소가 되는 방향으로 학습률만큼 이동하도록 하기
# = 역전파
train_loss.backward()
opt.step()
Train loss at 0 is 0.7333998084068298
Train loss at 100 is 0.6719678640365601
Train loss at 200 is 0.6174373626708984
Train loss at 300 is 0.538774311542511
Train loss at 400 is 0.446634441614151
Train loss at 500 is 0.3636690378189087
Train loss at 600 is 0.29485586285591125
Train loss at 700 is 0.24171140789985657
Train loss at 800 is 0.20189854502677917
Train loss at 900 is 0.17150433361530304
Train loss at 1000 is 0.14800572395324707
Train loss at 1100 is 0.12949980795383453
Train loss at 1200 is 0.1147788017988205
Train loss at 1300 is 0.10291127860546112
Train loss at 1400 is 0.09321969002485275
Train loss at 1500 is 0.0852084830403328
Train loss at 1600 is 0.07850663363933563
Train loss at 1700 is 0.0728372186422348
Train loss at 1800 is 0.06799028813838959
Train loss at 1900 is 0.06380561739206314
# 학습 완료된 모델 성능 시험
model.eval() # 평가 모드로 변경
test_loss = criterion(torch.squeeze(model(x_test)), y_test)
print(f"After Training, test loss is {test_loss.item()}")
출력값
After Training, test loss is 0.061986129730939865
학습 완료된 모델 state_dict()
함수 형태로 바꾸고 .pt
파일로 저장하기.
state_dict()
: 모델 내 가중치들이 딕셔너리 형태로{연산이름: 가중치 텐서와 편향 텐서}
로 표현한 데이터
torch.save(model.state_dict(), './model.pt')
print(f"state_dict format of the model : {model.state_dict()}")
state_dict format of the model : OrderedDict([('linear_1.weight', tensor([[ 0.3503, 0.1310],
[-1.7319, -1.8814],
[ 0.1165, 0.0564],
[ 1.9831, 1.8379],
[-0.2900, -0.3082]])), ('linear_1.bias', tensor([ 1.0002, -0.5978, 0.9001, -0.2932, -0.0174])), ('linear_2.weight', tensor([[ 0.8015, -2.4831, 0.8068, -2.5721, -0.1969]])), ('linear_2.bias', tensor([2.1503]))])
# 새 모델 생성해서 학습한 모델 가중치 입력해보기
new_model = NeuralNet(2, 5)
new_model.load_state_dict(torch.load('./model.pt'))
출력값
<All keys matched successfully>
new_model.eval()
print(f"벡터 [-1 ,1]이 레이블 1을 가질 확률은 { new_model( torch.FloatTensor( [-1, 1] )).item()}")
출력값
벡터 [-1 ,1]이 레이블 1을 가질 확률은 0.9693503975868225
💡 해당 포스팅은 펭귄브로의 3분 딥러닝, 파이토치맛 교재를 통해 학습한 내용을 정리한 글입니다.