Machine Learning

예측 함수 정의 with pytorch

해파리냉채무침 2024. 4. 6. 18:13

출처: 차근차근 실습하며 배우는 파이토치 딥러닝 프로그래밍

신경망과 파이토치 프로그램의 대응 관계는 다음과 같다. 중간 텐서는 은닉층이다. 

 

파이토치 프로그래밍에 필요한 레이어 함수는 다음과 같다.

이중에서 파라미터를 갖는 것을 하이라이트 표시하였다.

 

nn. Linear /선형함수/회귀

nn.Sigmoid/ 시그모이드/ 이진분류

nn.LogSoftmax/ softmax+ log / 다중분류

nn.ReLU/  ReLU 함수

nn.Conv2d/  컨볼루션 함수/ 이미지 인식

nn. MaxPool2d/  풀링 함수 /  이미지 인식

nn.Flatten/ 1계 텐서로 변형/ 이미지 인식

nn.Dropout/  드롭아웃

nn.BatchNorm2d/ 배치 규격화 

nn.AdaptiveAvgPool2d/  적응형 풀링 함수 / pre-train 모델

 

여기서는 학습의 의미를 레이어 함수의 파라미터 값을 조정하는 행위를 의미한다. 

머신러닝 모델의 의미는 여러개의 레이어 함수를 조합해 입력 텐서에 대해 정답 데이터에 되도록 가깝게 되도록 출력 텐서를 출력해주는 합성함수라고 할수 있다. 

 

예측 함수의 내부 구조

import torch.nn as nn
l1 = nn.Linear(784,128)
l2 = nn.Linear(128,10)
relu = nn.ReLU(inplace = True)

여기서 128은 은닉층 노드이다. 이 세함수를 조합하여 입력 텐서로부터 출력 텐서를 계산하면 다음과 같다.

inputs = torch.randn(100,784) #입력
m1= l1(inputs) #중간 텐서 1
m2= relu(m1) #중간 텐서 2
outputs = l2(m2) #출력 텐서
print('입력 텐서', inputs.shape)
print('출력 텐서', outputs.shape)

입력 텐서 torch.Size([100, 784])

출력 텐서 torch.Size([100, 10])

(100*784)(784*128) (128*10) = (100*10) 의 값이 도출된다.

 

입력텐서는 100행 784열의 2계 텐서이다. 머신러닝은 원칙적으로 여러건의 데이터를 동시에 다룬다. 입력텐서의 첫번째 인덱스 (100)은 여러 데이터 가운데 몇번째 데이터 인가를 의미한다. [100,784] 라는 shape는 784개 요소를 갖는 1계 텐서(벡터)가 데이터가 100건이 있다라고 해석한다. inputs 라는 입력에 대해 l1, relu,l2 함수가 순서대로 작용하고, m1,m2 텐서를 거쳐 텐서 outputs를 얻게 된다.

 

nn.Sequential를 통해 간결하게 구현할 수 있다.중간 텐서가 명시적으로 출현하지 않는다.

net2 = nn.Sequential(l1,relu,l2)
outputs2 = net2(inputs)
print('입력 텐서', inputs.shape)
print('출력 텐서', outputs2.shape)

입력 텐서 torch.Size([100, 784])

출력 텐서 torch.Size([100, 10])

 

활성화함수

활성화 함수는 입력값을 출력값을 변환하는 역할을 한다. Sigmoid, softmax, tanh,relu , leaky relu 등이 있다. 활성화 함수는 레이어 사이에 위치하고 있다.

2개의 은닉층을 가진 회귀모델(활성화 함수 응용)

 

예측 모델 비교

np.random.seed(123)
x = np.random.randn(100,1)

# y는 x^2에 난수를 1/10만큼 더한 값
y = x**2 + np.random.randn(100,1) * 0.1

# 데이터를 50건씩 훈련용과 검증용으로 나눔
x_train = x[:50,:]
x_test = x[50:,:]
y_train = y[:50,:]
y_test = y[50:,:]
plt.scatter(x_train, y_train, c='b', label='train')
plt.scatter(x_test, y_test, c='k', marker='x', label='test')
plt.legend()
plt.show()

다음 데이터를 은닉층 x,/활성화 함수 x, 은닉층 2개, 활성화 함수 x/ 은닉층 2개, 활성화함수 o

이렇게 3개로 구분하여 그래프를 구현해보았다.

1) 은닉층 없음 활성화 함수 없음

class Net(nn.Module):
  def __init__(self):
    super().__init__()  #부모 클래스 nn.Modules 초기화
    self.l1 = nn.Linear(1,1) #출력층 정의

  def forward(self,x):
    x1 = self.l1(x)
    return x1
# 학습률
lr = 0.01

# 인스턴스 생성(파라미터 초기화)
net = Net()

# 최적화 알고리즘 : 경사 하강법
optimizer = optim.SGD(net.parameters(), lr=lr)

# 손실 함수: 평균 제곱 오차
criterion = nn.MSELoss()

# 반복 횟수
num_epochs = 10000

#  history 기록을 위한 배열 초기화(손실 함수 값 만을 기록)
history = np.zeros((0,2))
# 반복 계산 메인 루프

for epoch in range(num_epochs):
    
    # 경사 값 초기화
    optimizer.zero_grad()

    # 예측 계산
    outputs = net(inputs)
  
    # 오차 계산
    loss = criterion(outputs, labels)

    # 경사 계산
    loss.backward()

    # 경사 하강법 적용
    optimizer.step()

    # 100회 마다 도중 경과를 기록
    if ( epoch % 100 == 0):
        history = np.vstack((history, np.array([epoch, loss.item()])))
        print(f'Epoch {epoch} loss: {loss.item():.5f}')
labels_pred = net(inputs_test)

plt.title('no hidden,no activation function')
plt.scatter(inputs_test[:,0].data, labels_pred[:,0].data, c='b', label='predict') # 예측값
plt.scatter(inputs_test[:,0].data, labels_test[:,0].data, c='k', marker='x',label='answer') #정답값
plt.legend()
plt.show()

2) 은닉층 2개, 활성화 함수 사용하지 않음

class Net2(nn.Module):
  def __init__(self):
    super().__init__() #부모 클래스 nn.Modules 초기화
    #출력층
    self.l1 = nn.Linear(1,10)
    self.l2 = nn.Linear(10,10)
    self.l3  = nn.Linear(10,1)

  def forward(self,x): #예측함수 
    x1 = self.l1(x)
    x2 = self.l2(x1)
    x3= self.l3(x2)
    return x3
# 학습률
lr = 0.01

# 인스턴스 생성(파라미터 초기화)
net2 = Net2()

# 최적화 알고리즘 : 경사 하강법
optimizer = optim.SGD(net2.parameters(), lr=lr)

# 손실 함수 : 평균 제곱 오차
criterion = nn.MSELoss()

# 반복 횟수
num_epochs = 10000

# history 기록을 위한 배열 초기화(손실 함수 값 만을 기록)
history = np.zeros((0,2))
labels_pred2 = net2(inputs_test)

plt.title('2 hidden layer, no activation function')
plt.scatter(inputs_test[:,0].data, labels_pred2[:,0].data, c='b', label='predict')
plt.scatter(inputs_test[:,0].data, labels_test[:,0].data, c='k', marker='x',label='answer')
plt.legend()
plt.show()

3)  은닉층 2개, 활성화 함수 사용

class Net3(nn.Module):
  def __init__(self):
    super().__init__()
    self.l1 = nn.Linear(1,10)
    self.l2 = nn.Linear(10,10)
    self.l3 = nn.Linear(10,1)
    self.relu = nn.ReLU(inplace=True) #활성화 함수

  def forward(self,x):
      x1 = self.relu(self.l1(x))
      x2 = self.relu(self.l2(x1))
      x3 = self.l3(x2)
      return x3
# 학습률
lr = 0.01

# 인스턴스 생성(파라미터 초기화)
net3 = Net3()

# 최적화 알고리즘 : 경사 하강법
optimizer = optim.SGD(net3.parameters(), lr=lr)

# 손실 함수: 평균 제곱 오차
criterion = nn.MSELoss()

# 반복 횟수
num_epochs = 10000

# history 기록을 위한 배열 초기화(손실 함수 값 만을 기록)
history = np.zeros((0,2))
for epoch in range(num_epochs):
    
    # 경사 값 초기화
    optimizer.zero_grad()

    # 예측 계산
    outputs = net3(inputs)
  
    # 오차 계산
    loss = criterion(outputs, labels)

    # 경사 계산
    loss.backward()

    # 경사 하강법 적용
    optimizer.step()

    # 100회 마다 도중 경과를 기록
    if ( epoch % 100 == 0):
        history = np.vstack((history, np.array([epoch, loss.item()])))
        print(f'Epoch {epoch} loss: {loss.item():.5f}')
labels_pred3 = net3(inputs_test)

plt.title('two hidden layers, activation function')
plt.scatter(inputs_test[:,0].data, labels_pred3[:,0].data, c='b', label='predict')
plt.scatter(inputs_test[:,0].data, labels_test[:,0].data, c='k', marker='x',label='answer')
plt.legend()
plt.show()

선형 함수 사이 활성화 함수를 넣은 것이 정답에 근사된 것을 알 수 있다. 비선형 함수로 불리는 활성화 함수를 선형 함수의 사이에 넣어야 딥러닝 모델의 의미를 갖게 된다.