머신러닝/딥러닝 공부

분류 (7) - MNIST 숫자 손글씨 분류 모델 (다층 퍼셉트론) 본문

AI 공부/Machine Learning

분류 (7) - MNIST 숫자 손글씨 분류 모델 (다층 퍼셉트론)

호사린가마데라닌 2021. 12. 2. 17:34

 

 

 

필요한 모듈을 import해준 뒤에 이전 포스팅에서 구현했던 다층 퍼셉트론 모델을 가져오겠습니다. 

https://yhyun225.tistory.com/23

 

분류 (6) - 다층 퍼셉트론 코드 (파이썬 구현)

이번 포스팅에서는 파이썬으로 2개의 층을 가진 다층 퍼셉트론을 구현해보도록 하겠습니다. 또한 다음 포스팅에서는 이 코드를 이용하여 MNIST 숫자 손글씨 데이터 세트를 학습시켜 보고 단층 퍼

yhyun225.tistory.com

 

 

- import

import numpy as np
import matplotlib.pyplot as plt
from tensorflow import keras
import math
from sklearn.utils import shuffle

 

 

- 다층 퍼셉트론 코드 

class MultiLayer:
  def __init__(self,learning_rate=0.01,batch_size=32,hidden_node=10):
    self.w1=None #은닉층 가중치 행렬
    self.b1=None #은닉층 바이어스 배열
    self.w2=None #출력층 가중치 행렬
    self.b2=None #출력층 바이어스 배열
    self.lr=learning_rate #학습률
    self.batch_size=batch_size
    self.node=hidden_node #은닉층 노드 개수
    self.losses=[] # 손실(훈련세트)
    self.val_losses=[] # 손실(검증세트)
    self.accuracy=[] # 정확도(훈련세트)
    self.val_accuracy=[] # 정확도(검증세트)

  def init_weights(self,m,n): # 가중치 초기화 함수 
    k=self.node
    self.w1=np.random.normal(0,1,(m,k)) # 가우시안 분포로 초기화 (m x k) 행렬
    self.b1=np.zeros(k) # 0으로 초기화
    self.w2=np.random.normal(0,1,(k,n)) # 가우시안 분포로 초기화 (k x n) 행렬
    self.b2=np.zeros(n) #0으로 초기화

  def sigmoid(self,z1): #sigmoid 함수
    z1=np.clip(z1,-100,None) 
    a1=1/(1+np.exp(-z1)) 
    return a1

  def softmax(self,z2): #softmax 함수
    z2=np.clip(z2,-100,None)
    exp_z2=np.exp(z2)
    a2=exp_z2/np.sum(exp_z2,axis=1).reshape(-1,1)
    return a2

  def forward1(self,x): # 입력층 -> 은닉층 순전파
    z1=np.dot(x,self.w1)+self.b1
    return z1

  def forward2(self,a1): # 은닉층 -> 출력층 순전파
    z2=np.dot(a1,self.w2)+self.b2
    return z2

  def loss(self,x,y): # 손실 계산
    z1=self.forward1(x)
    a1=self.sigmoid(z1)
    z2=self.forward2(a1)
    a2=self.softmax(z2)
    
    a2=np.clip(a2,1e-10,1-1e-10)
    return -np.sum(y*np.log(a2)) #크로스 엔트로피

  def backpropagation(self,x,y): # 역전파 알고리즘
    z1=self.forward1(x)
    a1=self.sigmoid(z1)
    z2=self.forward2(a1)
    a2=self.softmax(z2)
  
    w2_grad=np.dot(a1.T,-(y-a2)) # 은닉층 가중치의 기울기 계산
    b2_grad=np.sum(-(y-a2)) # 은닉층 바이어스의 기울기 계산

    #은닉층으로 전파된 오차 계산
    hidden_err=np.dot(-(y-a2),self.w2.T)*a1*(1-a1)

    w1_grad=np.dot(x.T,hidden_err) # 입력층 가중치의 기울기 계산
    b1_grad=np.sum(hidden_err,axis=0) #입력층 바이어스의 기울기 계산

    return w1_grad,b1_grad,w2_grad,b2_grad

  def minibatch(self,x,y):
    iter=math.ceil(len(x)/self.batch_size)
    
    x,y=shuffle(x,y)
    
    for i in range(iter):
      start=self.batch_size*i
      end=self.batch_size*(i+1)
      yield x[start:end],y[start:end]

  def fit(self,x_data,y_data,epochs=40,x_val=None,y_val=None):
    self.init_weights(x_data.shape[1],y_data.shape[1]) # 가중치 초기화
    for epoch in range(epochs):
      l=0 # 손실 누적할 변수
      #가중치 누적할 변수들 초기화
      for x,y in self.minibatch(x_data,y_data):
      
        l+=self.loss(x,y) # 손실 누적
        # 역전파 알고리즘을 이용하여 각 층의 가중치와 바이어스 기울기 계산
        w1_grad,b1_grad,w2_grad,b2_grad=self.backpropagation(x,y)

        # 은닉층 가중치와 바이어스 업데이트  
        self.w2-=self.lr*(w2_grad)/len(x)
        self.b2-=self.lr*(b2_grad)/len(x)

        # 입력층 가중치와 바이어스 업데이트
        self.w1-=self.lr*(w1_grad)/len(x)
        self.b1-=self.lr*(b1_grad)/len(x)
      
      #검증 손실 계산
      val_loss=self.val_loss(x_val,y_val)
      
      self.losses.append(l/len(y_data))
      self.val_losses.append(val_loss)
      self.accuracy.append(self.score(x_data,y_data))
      self.val_accuracy.append(self.score(x_val,y_val))

      print(f'epoch({epoch+1}) ===> loss : {l/len(y_data):.5f} | val_loss : {val_loss:.5f}',\
            f' | accuracy : {self.score(x_data,y_data):.5f} | val_accuracy : {self.score(x_val,y_val):.5f}')

  def predict(self,x_data):
    z1=self.forward1(x_data)
    a1=self.sigmoid(z1)
    z2=self.forward2(a1)
    return np.argmax(z2,axis=1) #가장 큰 인덱스 반환

  def score(self,x_data,y_data):
    return np.mean(self.predict(x_data)==np.argmax(y_data,axis=1))

  def val_loss(self,x_val,y_val): # 검증 손실 계산
    val_loss=self.loss(x_val,y_val)
    return val_loss/len(y_val)

 

 

모델을 학습시킬 mnist 숫자 손글씨 데이터 세트를 받아오겠습니다. mnist 숫자 손글씨 세트에 대한 설명은 밑의 포스팅에서 확인할 수 있습니다. 

https://yhyun225.tistory.com/19

 

분류(5) - MNIST 숫자 손글씨 분류 모델( 다중 선형 분류 )

1) 다중 분류 모델 코드 이전 포스팅에서 수정했던 다중 분류 코드를 가져오겠습니다. https://yhyun225.tistory.com/15?category=964332 분류 (4) - 다중 분류 (Multiclass Classification) 코드 구현 다중 분류..

yhyun225.tistory.com

 

 

받아온 데이터 세트의 전처리를 해주겠습니다. 

# mnist 데이터를 받아옴
(x_train,y_train),(x_test,y_test)=keras.datasets.mnist.load_data()

# 검증 세트 생성
x_train,x_val,y_train,y_val=train_test_split(x_train,y_train,stratify=y_train,test_size=0.2)

# 28 x 28 배열을 1 x 784 배열로 바꿈
x_train = x_train.reshape(x_train.shape[0],28*28)
x_val=x_val.reshape(x_val.shape[0],28*28)
x_test = x_test.reshape(x_test.shape[0],28*28)

# 0~255 값 분포를 0~1 사이에 분포하도록 바꿈
x_train = x_train.astype('float32') / 255.
x_val=x_val.astype('float32')/255.
x_test = x_test.astype('float32') / 255.

# one-hot encoding
y_train = keras.utils.to_categorical(y_train)
y_val=keras.utils.to_categorical(y_val)
y_test = keras.utils.to_categorical(y_test)
print(math.ceil(len(x_train)/256))

 

 

이제 모델을 생성한 뒤 학습을 시켜보겠습니다. 학습률은 0.1, 배치의 크기는 256, 은닉층은 100개의 노드를 갖도록 설정하고 40 에포크 동안 학습을 시켰습니다. 결과는 아래와 같습니다.

model=MultiLayer(learning_rate=0.1,batch_size=256,hidden_node=100)
model.fit(x_train,y_train,epochs=40,x_val=x_val,y_val=y_val)

 

 

훈련 세트에 대한 정확도는 약 91%, 검증 세트에 대한 정확도는 약 90%가 나오는 것을 확인할 수 있습니다. 

 

 

이제 이 모델의 성능을 테스트해보겠습니다.

model.score(x_test,y_test)

 

테스트 세트에 대한 정확도는 90% 정도로, 이전에 구현했던 단층 퍼셉트론에 비해 높은 정확도를 보여줍니다. 

 

 

다층 퍼셉트론은 기존의 모델들로는 학습이 힘들거나 불가능했던 비선형 데이터들을 분류할 수 있는 기회를 마련하여 좀 더 높은 정확도를 보였습니다. 하지만 다층 퍼셉트론은 이미지 데이터를 분류할 때 각 픽셀의 값을 일렬로 나열하여 입력값으로 사용하기 때문에 이미지에서 정보가 누실되는 단점이 있습니다. 

 

 

다음 포스팅에서는 이미지 데이터에서 높은 효과를 보이는 합성곱 신경망(Convolutional Neural Network, CNN)에 대해 정리해보겠습니다. 

 

Comments