본문 바로가기

Study/class note

딥러닝 / 날코딩으로 2층 신경망 클래스 구현

10 파이썬 날코딩으로 2층 신경망 클래스 구현하기 - p.137

1. 텐서플로우를 이용했을 때  -> 구현이 쉽고 빠름

2. 텐서플로우를 이용하지 않았을 때  -> 신경망을 만드는게 어렵지만 원리를 이해하는데 좋음

 

아래와 같이 신경망 설계도를 클래스로 생성하는 이유는?

-> 객체로 찍어내기 편해서

#1. 필요한 패키지를 불러옵니다
import  sys, os 
sys.path.append(os.pardir) 
import  numpy  as  np
from common.functions  import  softmax, cross_entropy_error   #필요한 패키지(common)를 홈디렉토리에 미리 가져다 놔야함

#2. 단층신경망 클래스 생성
class  simpleNet:
    def  __init__(self):
        self.W = np.random.randn(2,3)  # 정규분포로 2x3 행렬의 가중치 생성(랜덤 초기화) - 임의의 가중치

    def  predict( self, x ):  #예측함수 : 입력값을 받아 가중치 행렬과 내적
        return  np.dot( x, self.W ) 

    def  loss( self, x, t ):  #오차함수
        z = self.predict(x)   #입력값,가중치 내적결과를 z에 넣고
        y = softmax(z)   #z를 받아서 softmax함수에 넣고 확률벡터를 y에 넣고
        loss = cross_entropy_error(y, t)  #크로스 엔트로피 오차함수에 예측값 y와 정답 t를 넣고 loss출력
        return  loss 

net = simpleNet()
print(f'가중치 행렬 : {net.W}')  # 가중치 행렬 확인 

# 입력값 생성
x = np.array( [0.6, 0.9] )

# 예측값 
y = net.predict(x)
print(f'예측값 :{y}')

# 최대값 원소의 인덱스 확인
print (f'예측값 인덱스 : { np.argmax(y)}' ) 

# 오차확인
t = np.array([ 0, 0, 1])  #one hot encoding 형태로 정답 생성
print(f'label 인덱스 : {np.argmax(t)}')
print (f'오차 : { net.loss(x,t)}'  )

 

 

ㅇ개와 고양이를 분류하는 신경망의 원리

개와 고양이를 분류하는 위의 3층 신경망에서 x1에 들어오는 픽셀은 고양이의 귀이고 x2에 들어오는 픽셀은 고양이의 수염. 그런데 이 고양이의 귀와 수염이 고양이와 개를 분류하는데 있어서 정말 중요한 신호라고 한다면 그 입력신호에 따른 가중치값이 학습되면서 점점 조정될 것.

 

고양이 얼굴이 없는 벽쪽의 픽셀들은 중요도가 낮은 신호로 처리될 것이고 고양이 사진에서 고양이의 특징을 설명하는 픽셀은 점점 신호가 커질 것. 그래서 점점 오차가 줄어들 수 있도록 가중치가 갱신될 것.

 

 

ㅇ1층 신경망을 텐서 플로우 없이 구현한 코드

 

 

ㅇ러닝 레이트(학습률)가 너무 작을 때와 너무 클 때의 차이 실험

 

 

ㅇ2층 신경망을 텐서 플로우 없이 구현하기

오차역전파를 이용한 코드

# coding: utf-8

import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
from common.functions import *
from common.gradient import numerical_gradient  # 만들었던 편미분 함수 임포트(common 패키지 안에)

class TwoLayerNet:
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):  
        # 가중치 초기화
        self.params = {}  #가중치와 바이어스를 관리할 딕셔너리 자료구조 생성
                           # 학습률 ↓
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)  #(784,100)
        self.params['b1'] = np.zeros(hidden_size) #(1,100) 나중에 브로드 캐스트 됨
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) #(100,10)행렬의 0으로 채워진 행렬
        self.params['b2'] = np.zeros(output_size)
        # 2층 신경망이 생성될 때 만들어지는 가중치와 바이어스 초기값이 params 딕셔너리에 만들어짐
        
    # 입력값 행렬과 가중치 행렬을 내적하고 바이어스 행렬을 더해서 결과적으로 확률벡터를 출력하는 함수
    def predict(self, x):
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)
        return y  # 확률벡터가 100가 출력(batch size = 100), 결국 (100,10)으로 출력됨
        
    # x : 입력 데이터, t : 정답 레이블
    def loss(self, x, t):  #(100,784)의 입력 데이터가 들어와서
        y = self.predict(x)  #(100,10)의 확률 벡터가 출력
        return cross_entropy_error(y, t)  #(100,10)의 확률벡터와 (100,10)의 정답행렬이 오차함수로 들어감
                                          #100개 입력값에 대한 오차 평균 1개 출력됨

    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        t = np.argmax(t, axis=1)

        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy


    # x : 입력 데이터, t : 정답 레이블
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)

        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])

        return grads

    def gradient(self, x, t): #5장에서 배울 오차 역전파 함수, 텐서플로우도 오차역전파 함수로 구현됨
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
        grads = {}
     
        batch_num = x.shape[0]

        # forward
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)

        # backward
        dy = (y - t) / batch_num
        grads['W2'] = np.dot(z1.T, dy)
        grads['b2'] = np.sum(dy, axis=0)

        da1 = np.dot(dy, W2.T)
        dz1 = sigmoid_grad(a1) * da1
        grads['W1'] = np.dot(x.T, dz1)
        grads['b1'] = np.sum(dz1, axis=0)
        return grads

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist

# 데이터 읽기
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

network = TwoLayerNet(input_size=784, hidden_size=100, output_size=10)

# 하이퍼파라미터
iters_num = 10000  # 반복 횟수를 적절히 설정한다.
train_size = x_train.shape[0]
batch_size = 100   # 미니배치 크기
learning_rate = 0.1

train_loss_list = []
train_acc_list = []
test_acc_list = []


# 1에폭당 반복 수
iter_per_epoch = max(train_size / batch_size, 1)

for i in range(iters_num):
    # 미니배치 획득
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]

    # 기울기 계산
    #grad = network.numerical_gradient(x_batch, t_batch)  #수치미분을 이용한 기울기
    grad = network.gradient(x_batch, t_batch)  #오차역전파를 이용한 기울기

    # 매개변수 갱신
    for key in ('W1', 'b1', 'W2', 'b2'):
        network.params[key] -= learning_rate * grad[key]

    # 학습 경과 기록
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)

    # 1에폭당 정확도 계산
    if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)
        print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))

 

loss 함수 이후의 나머지 코드는 5장 끝에서 설명

 

 

 

반응형