본문 바로가기

ML,DL

밑러닝 - (2)

퍼셉트론의 조합만으로도 어떤 복잡한 함수도 표현 가능하지만('Universal Approximation Theorem'),

퍼셉트론의 조합만을 사용하기에는 적절한 parameter(가중치, 임계치)를 일일이 설정해줘야 한다는 단점이 존재한다.

 

이러한 단점에 대해, 신경망은 적절한 parameter값을 학습을 통해 스스로 찾아가는 성질을 가져 대처 가능하다.

신경망

신경망은 크게 세가지 구조로 이루어져 있다.

  • 입력층(Input Layer)
    • Input으로 들어오는 데이터에 해당되는 Layer
  • 은닉층(Hidden Layer)
    • Input 데이터에 대한 가중합(+Activation Fucntion) 과정이 거쳐 만들어진 결과에 해당되는 Layer
  • 출력층(Output Layer)
    • 하나의 신경망에서 모든 과정을 거쳐 최종적으로 계산된 결과에 해당되는 Layer

활성화 함수(Activation Function)

입력 데이터의 가중합을 변환하는 함수를 의미한다.

예를 들어 위와 같은 퍼셉트론이 있다고 할 때, Input과 Bias의 가중합인 $w_{1}x_{1}+w_{2}x_{2}+b$에 대하여

임계값인 0을 기준으로 0과 1의 값으로 mapping 시키는 함수 $h(x)$를 활성화 함수의 개념으로 생각할 수 있다.

시그모이드 함수(Sigmoid Function)

활성화 함수로 자주 쓰이는 함수 중 하나로, 시그모이드 함수에 대해서는 다음 포스트를 참고하길...

https://sdsf1225.tistory.com/66

 

Logit & Sigmoid

Logit Logit함수에 대해 설명하기 전에, Odds와 Probability의 개념에 대해 알아보겠습니다. Probability(확률) 우선, Probability(확률)는 다들 알고 계시겠지만, 전체 시행(S) 중 어떠한 사건($X$)가 일어나는 경

sdsf1225.tistory.com

계단 함수(Step Function)

계단 함수란 말 그대로 계단의 모양으로 생긴 함수이다.

https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Dirac_distribution_CDF.svg/1200px-Dirac_distribution_CDF.svg.png

위의 예시에서 사용한 $h(x)$함수가 바로 임계값을 기준으로 값이 불연속적으로 변하는 계단 함수의 한 종류이다.

비선형 함수

위에서 언급한 시그모이드 함수와 계단 함수의 가장 핵심적인 공통점은 두 함수 모두 비선형 함수라는 점이다.

신경망에서는 활성화 함수로 반드시 비선형 함수를 사용해야 하는데, 그 이유는 간단하다.

 

선형 함수를 사용해 여러개의 층을 쌓는다고 가정했을때, 선형 활성화 함수의 적층은 결국 하나의 활성화 함수로 표현가능하기 때문에

신경망의 층을 쌓는 의미가 없기 때문이다.

 

예를 들어, $h(x) = cx$를 활성화 함수로 사용하는 3층 신경망이 있다고 하자. 

이렇게 되면, 출력값 $y(x) = h(h(h(x))) = c\times c\times c\times x = ax$로 하나의 활성화 함수로 표현이 가능하게 된다.

ReLU 함수

ReLU 함수는 다음과 같은 수식의 비선형 함수이다.

 

$h(x) = \begin{cases} 
x & (x>0) \\ 
         0 & (x \leq 0)
     \end{cases}$

소프트맥스 함수(Softmax Function)

Softmax 함수는 주로 분류 Task를 목적으로 만들어진 Neural Network에 사용되는 함수이다.

 

Softmax 함수의 수식은 다음과 같다.

$y_{k} = \frac{exp(a_{k})}{\sum_{i=1}^{n} exp(a_{i})}$

 

수식이 의미하는 바를 곰곰히 생각해보면, 전체 sum중에서 k에 해당하는 값의 비율을 의미하는 것을 알 수 있다.

따라서 분류 문제에서, 전체 경우의 수 중에서 k라는 값이 나올 확률로 해석해 볼 수도 있다.

 

Softmax를 직접 구현할 경우에, 다음 예시처럼 수식을 직접 대입해 직관적으로 구현할 수도 있지만, 컴퓨터의 자료형이 수용할 수 있는 범위가 제한되어 있기 때문에 큰 값에 대한 연산을 할 경우에는 Overflow문제가 발생할 수 있다.

def softmax(x): # 직관적인 구현
    exp_x = np.exp(x)
    sum_exp = np.sum(exp_x)
    y = exp_x / sum_exp
    
    return y
    
# Overflow 문제 생길 수 있음

 

 

 

수식을 변형시킴으로써 Oveflow문제를 방지하도록 할 수 있는데, 방법은 다음과 같다.

$y_{k} = \frac{exp(a_{k})}{\sum_{i=1}^{n} exp(a_{i})} = \frac{C exp(a_{k})}{C \sum_{i=1}^{n} exp(a_{i})} = \frac{exp(a_{k} + logC)}{\sum_{i=1}^{n} exp(a_{i} + logC)} = \frac{exp(a_{k} + C^{'})}{\sum_{i=1}^{n} exp(a_{i} +C^{'})}$

 

위의 식이 의미하는 바는, C에 어떤 값을 대입하더라도 최초의 Softmax수식의 값과 동일한 값이 나온다는 것이다.

그렇기 때문에 C에 음수를 대입해 계산되는 exp값을 최소화해 Overflow를 방지할 수 있는 것이다.

 

따라서, Softmax에 대입할 x중에서 최대값의 빼주는 방식으로 Softmax함수를 수정해 구현할 수 있다.

def softmax(x): # 수정된 Softmax함수(Overflow 방지)
    C = np.max(x)
    exp_x = np.exp(x-c)
    sum_exp = np.sum(exp_x)
    y = exp_x/sum_exp
    
    return y

예제 : MNIST Data 분류 문제 (순전파)

MNIST 데이터에 대한 분류 문제에서 순전파 과정만을 구현한 코드를 작성해보자.

# MNIST 데이터 준비
import os
import sys
import math
import gzip
import torchvision
import numpy as np
import matplotlib.pyplot as plt 

# normalize
def normalize_img(x):
  return x.astype(np.float32)/255.0

# one hot encoding(label) ex) 5 -> [0,0,0,0,0,1,0,0,0,0]
def _change_one_hot_label(X):
    T = np.zeros((X.size, 10))
    for idx, row in enumerate(T):
        row[X[idx]] = 1
        
    return T

# flatten
def unflatten_img(x):
  return x.reshape(-1,1,28,28)

# load_mnist
def load_mnist(normalize=True, flatten=True, one_hot_label=False):
  torchvision.datasets.MNIST(root='.', train=True, download=True) # download MNIST
  
  dataset = {}
  dataset_dir = os.path.abspath('./MNIST/raw')
  key_file = {
      'train_img':'train-images-idx3-ubyte.gz',
      'train_label':'train-labels-idx1-ubyte.gz',
      'test_img':'t10k-images-idx3-ubyte.gz',
      'test_label':'t10k-labels-idx1-ubyte.gz'
  }

  train_num = 60000
  test_num = 10000
  img_dim = (1,28,28)
  img_size = 784
  # load img
  with gzip.open(os.path.join(dataset_dir, key_file['train_img']), 'rb') as f:
    dataset['train_img'] = np.frombuffer(f.read(), np.uint8, offset=16)
    dataset['train_img'] = dataset['train_img'].reshape(-1, img_size)

  with gzip.open(os.path.join(dataset_dir, key_file['test_img']), 'rb') as f:
    dataset['test_img'] = np.frombuffer(f.read(), np.uint8, offset=16)
    dataset['test_img'] = dataset['test_img'].reshape(-1, img_size)

  # load label
  with gzip.open(os.path.join(dataset_dir, key_file['train_label']), 'rb') as f:
    dataset['train_label'] = np.frombuffer(f.read(), np.uint8, offset=8)

  with gzip.open(os.path.join(dataset_dir, key_file['test_label']), 'rb') as f:
    dataset['test_label'] = np.frombuffer(f.read(), np.uint8, offset=8)

  if normalize:
    dataset['train_img'] = normalize_img(dataset['train_img'])
    dataset['test_img'] = normalize_img(dataset['test_img'])
  
  if one_hot_label:
    dataset['train_label'] = _change_one_hot_label(dataset['train_label'])
    dataset['test_label'] = _change_one_hot_label(dataset['test_label'])

  if not flatten:
    dataset['train_img'] = unflatten_img(dataset['train_img'])
    dataset['test_img'] = unflatten_img(dataset['test_img'])

  return dataset['train_img'], dataset['train_label'], dataset['test_img'], dataset['test_label']
  
x_train, t_train, x_test, t_test = load_mnist(normalize=True, one_hot_label=False, flatten=True)

print(x_train.shape)
print(t_train.shape)
print(x_test.shape)
print(t_test.shape)
# MNIST Image 시각화

from PIL import Image
def img_show(img):
  img = Image.fromarray(np.uint8(img))
  plt.imshow(img, cmap='grey')

x_train, t_train, x_test, t_test = load_mnist(flatten=False,normalize=False, one_hot_label=False)

img = x_train[0]
label = t_train[0]
print(label) # 5
img = np.squeeze(img)
print(img.shape) # 28,28

img_show(img)

이를 실행하면 다음과 같이 '5'에 대한 MNIST 이미지를 볼 수 있다.

이어서, 순전파 과정은 다음과 같다.

# forward propagation(순전파)

# sigmoid function
def sigmoid(x):
    return 1/(1+np.exp(-x))

# softmax
def softmax(x):
    C = np.max(x)
    exp_x = np.exp(x-C)
    sum_exp = np.sum(exp_x)
    return exp_x/sum_exp

# 3-layer network 선언
def init_network(): 
    network = {}
    network['W1'] = np.random.randn(784,50)
    network['B1'] = np.random.randn(50,1)
    network['W2'] = np.random.randn(50,100)
    network['B2'] = np.random.randn(100,1)
    network['W3'] = np.random.randn(100,10)
    network['B3'] = np.random.randn(10,1)
    
    return network

# 순전파 과정
def forward(network,x):
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['B1'], network['B2'], network['B3']
    
    a1 = np.dot(W1.T, x)+b1
    z1 = sigmoid(a1) # 50,1
    
    a2 = np.dot(W2.T, z1) + b2 
    z2 = sigmoid(a2) # 100,1
    
    a3 = np.dot(W3.T, z2) + b3 # 10,1
    y = softmax(a3)
    
    return y
x_train, t_train, x_test, t_test = load_mnist(normalize=True, one_hot_label=False, flatten=True)
network = init_network()
acc = 0

for i in range(len(x_test)):
  y = forward(network, x_test[i].reshape(-1,1))
  p = np.argmax(y) # 가장 softmax값이 높은 원소의 index
  if p == t_test[i]:
    acc+=1

print("Accuracy: ", float(acc/len(x_test)))

 

parameter(weight / bias)가 학습되지 않았기 때문에, 당연히 정확도는 낮게 출력된다.

'ML,DL' 카테고리의 다른 글

Self-Supervised Learning  (1) 2025.06.15
밑러닝 - (3)  (0) 2025.01.16
Gradient Clipping  (0) 2024.08.12
밑러닝 (1) - CH.2  (0) 2024.04.22
Ensemble - Bagging, Boosting, Stacking  (0) 2023.03.08