Yet Never Lose Faith

- Good to Great , Jim Collins

How To Preprocess Image Data 자세히보기

Deep Learning/[Books] Do it! 정직하게 코딩하며 배우는 딥러닝 입문

다중분류 다층신경망 구현하기 - 하

Kellyyyy 2020. 10. 29. 08:00

이번 포스팅에서는 다중분류 다층신경망을 Python으로 구현해본다. 입력데이터는 텐서플로에서 제공하는 패션 MNIST 데이터셋을 사용한다. 


Install Tensorflow 

 

! pip install tensorflow_gpu==2.0.0-rc1

 

Import Tensorflow

 

import tensorflow as tf

 

load Data

 

(x_train_all, y_train_all),(x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()
class_names = ['티셔츠/윗도리','바지','스웨터','드레스','코트','샌들','셔츠','스니커즈','가방','앵글부츠']

 

데이터를 로드하고 클래스명을 지정한다. 클래스명은 패션 MNIST 데이터 세트 깃허브(https://github.com/zalandoresearch/fashion-mnist)에 접속하면 확인할 수 있다.

 

패션 MNIST 데이터 세트 클래스명

 

Preprocess Data

 

from sklearn.model_selection import train_test_split
x_train, x_val, y_train, y_val = train_test_split(x_train_all, y_train_all, stratify=y_train_all, test_size=0.2, random_state=42)

# x 전처리
x_train = x_train /255
x_val = x_val / 255
x_train = x_train.reshape(-1,784)
x_val = x_val.reshape(-1,784)

# y 전처리
y_train_encoded = tf.keras.utils.to_categorical(y_train)
y_val_encoded = tf.keras.utils.to_categorical(y_val)

 

x는 이미지 데이터로 픽셀마다 0~255 사이의 값을 가지므로 각 값을 255로 나눠 0~1사이로 맞춘다. 또한 내가 구현할 신경망은 1차원 배열의 샘플을 입력데이터로 사용하므로 reshape() 메소드를 사용하여 훈련세트와 검증세트를 1차원 배열로 바꿔준다. y는 1~9사이의 1개 정수값을 가지므로 10개의 출력층 뉴런값과 비교할 수 있도록 원-핫 인코딩한다.

 

Define Model

 

class MultiClassNetwork : 
    def __init__(self,l1=0, l2=0, learning_rate=0.1, batch_size=32, units=10) :
      self.w1 = None
      self.b1 = None
      self.w2 = None
      self.b2 = None
      self.a1 = None
      self.l1 = l1
      self.l2 = l2
      self.lr = learning_rate
      self.batch_size = batch_size
      self.losses = []
      self.val_losses = []
      self.units = units

    def forpass(self, x) : 
      z1 = np.dot(x, self.w1) + self.b1
      self.a1 = self.sigmoid(z1)
      z2 = np.dot(self.a1, self.w2) + self.b2
      return z2
    
    def sigmoid(self, z) :
      a = 1 / (1+np.exp(-z))
      return a

    def softmax(self, z) :
      exp_z = np.exp(z)
      return exp_z / np.sum(exp_z, axis=1).reshape(-1,1)

    def backprop(self,x,err) :
      m = len(x)
      w2_grad = np.dot(self.a1.T, err) / m
      b2_grad = np.sum(err) / m

      err_to_hidden = np.dot(err, self.w2.T) * self.a1 * (1-self.a1)

      w1_grad = np.dot(x.T, err_to_hidden) / m
      b1_grad = np.sum(err_to_hidden, axis=0) / m
      return w1_grad, b1_grad, w2_grad, b2_grad

    def fit(self,x, y, epochs, x_val=None, y_val=None) :
      np.random.seed(42)
      self.init_weights(x.shape[1], y.shape[1])

      for i in range(epochs) :
        loss = 0
        print('.', end='')
        for x_batch, y_batch in self.gen_batch(x,y) :
          a = self.training(x_batch, y_batch)
          a = np.clip(a, 1e-10, 1-1e-10)
          loss += np.sum(-y_batch*np.log(a))
        self.losses.append((loss + self.reg_loss()) / len(x))
        self.update_val_loss(x_val, y_val)
    
    def init_weights(self, n_features, n_classes) :
      self.w1 = np.random.normal(0,1, (n_features, self.units))
      self.b1 = np.zeros(self.units)
      self.w2 = np.random.normal(0,1, (self.units, n_classes))
      self.b2 = np.zeros(n_classes)

    def update_val_loss(self, x_val, y_val) :
      z = self.forpass(x_val)
      a = self.softmax(z)
      a = np.clip(a, 1e-10, 1-1e-10)
      val_loss = np.sum(-y_val*np.log(a))
      self.val_losses.append((val_loss + self.reg_loss()) /  len(y_val))

    def reg_loss(self) :
      return self.l1 * (np.sum(np.abs(self.w1)) + np.sum(np.abs(self.w2))) + self.l2/2 * (np.sum(self.w1**2) + np.sum(self.w2**2))
    
    def gen_batch(self, x, y) :
      length = len(x)
      bins = length // self.batch_size
      if length % self.batch_size :
        bins += 1
      indexes = np.random.permutation(np.arange(len(x)))
      x = x[indexes]
      y = y[indexes]
      for i in range(bins) :
        start = self.batch_size * i
        end = self.batch_size * (i+1)
        yield x[start:end], y[start:end]
    
    def training(self, x, y) :
      m = len(x)
      z = self.forpass(x)
      a = self.softmax(z)
      err = -(y-a)
      w1_grad, b1_grad, w2_grad, b2_grad = self.backprop(x,err)
      w1_grad += (self.l1*np.sign(self.w1) + self.l2*self.w1) / m
      w2_grad += (self.l1*np.sign(self.w2) + self.l2*self.w2) /m
      self.w1 -= self.lr * w1_grad
      self.b1 -= self.lr * b1_grad
      self.w2 -= self.lr * w2_grad
      self.b2 -= self.lr * b2_grad
      return a

    def predict(self, x) :
        z = self.forpass(x)
        return np.argmax(z, axis=1)
      
    def score(self, x, y) :
        return np.mean(self.predict(x) == np.argmax(y, axis=1))

 

이중분류 모델에서 activation()을 sigmoid()와 softmax()로 나눠서 수정했다. 또한 predict() 메소드에서 argmax()를 이용해 가장 큰 값의 인덱스를 반환할 수 있도록 하고, score() 메소드에서 이를 타깃 열 벡터의 가장 큰 값과 비교하여 True의 비율을 반환했다.

 

Fit Model

 

fc = MultiClassNetwork(batch_size=256, units=100)
fc.fit(x_train, y_train_encoded, x_val = x_val, y_val = y_val_encoded, epochs=40)

 

배치사이즈를 256, 은닉층 뉴런 수를 100, 에포크 횟수를 40으로 세팅하여 학습시켰다.

 

Loss Graph

 

plt.plot(fc.losses)
plt.plot(fc.val_losses)
plt.ylabel('loss')
plt.xlabel('iteration')
plt.legend(['train_loss','val_loss'])
plt.show()

 

다중분류 신경망의 손실함수 그래프

손실 그래프가 초기에는 빠르게 감소하다가 완만하게 수렴하고 있다.

 

Score

 

fc.score(x_val, y_val_encoded)
# 0.8150833333333334

 

검증세트에 대한 이 분류모델의 정확도는 81% 이다. 무작위로 의류 이미지를 예측하면 약 10% 정도의 정확도를 기대할 수 있으므로 확실히 더 높은 점수이다. 하지만 오차가 19%나 되기 때문에 실전에서 사용하기는 어렵다. 정확도를 높이기 위해서는 tensorflow 같은 전문 딥러닝 패키지를 이용해야한다. 다음 포스팅에서는 tensorflow 패키지를 이용하여 다중분류 모델을 구현해본다.

 

끝.


Reference.

 

도서 <Do it! 정직하게 코딩하며 배우는 딥러닝 입문> 이지스 퍼블리싱, 박해선 지음