정지홍 2023. 1. 19. 12:47

학습이란?

-훈련데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것

-학습의 목표는 손실함수의 결과값을 가장 작게 만드는 가중치를 찾는 것

-기계학습의 문제에서 학습단계에서 최적의 매개변수(가중치와 편향)를 찾아야한다.

-여기서 최적은 손실 함수가 최솟값이 될 때의 매개변수 값

 

데이터는 훈련 데이터와 시험 데이터로 나뉜다.

훈련데이터->학습하면서 최적의 매개변수를 찾을때 사용하는 데이터

시험데이터->위에서 학습한 모델이 제대로 학습 되었는지 시험할때 사용하는 데이터

 

데이터를 2가지로 나누는 이유는?

-훈련할때 사용한 데이터 이외에 처음 접하는 데이터를 보고도 올바르게 해결하는지 확인하기 위해서

-그리고 한 데이터셋에만 지나치게 최적화되어 있는 오버피팅을 피해야한다.

 

손실 함수(오차함수 or 비용함수)?

-신경망의 결과로 출력되는 값과 정답의 오차를 정의하는 함수이다.

-최적의 매개변수값인지 판단하는 하나의 지표이다.

-오차제곱합과 교차 엔트로피 오차를 주로 사용.

-높은 정확도를 위하여 사용한다.

 

오차제곱합?

-출력값과 정답의 차이를 제곱한 후 모두 더한것

 

교차 엔트로피 오차

-출력값의 자연로그값과 정답의 곱을 모두 더한 후에 마이너스 부호를 붙힌다.

-x가 1일때는 0이며 x가 0에 접근할 수록  -logx는 커진다

-따라서 정답에 가까울 수록 0에 가까우며 정답에서 멀수록 1에 가깝다. 출력값과 정답의 차이가 클수록 학습속도가 빠르다

def cross_entropy_error(y,t):
    delta=1e-7
    return -np.sum(t*np.log(y+delta))

미니배치?

-모든 데이터를 사용하기에는 시간이 너무 걸린다. 그래서 일부분만 뽑아서 전체의 근사치로 이용한다.

import sys,os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
(x_train , t_train) , (x_test , t_test)= load_mnist(one_hot_label=True, normalize=True)
print("훈련데이터 60000개, 입력데이터 784개")
print("정답은 60000개, 10줄짜리")
print(x_train.shape)
print(t_train.shape)

train_size=x_train.shape[0]
batch_size=10
batsh_mask=np.random.choice(train_size,batch_size)#배치는 60000개 데이터중 10개만 뽑는다
x_batch=x_train[batsh_mask]
t_batch=t_train[batsh_mask]
print("랜덤하게 뽑아낸 배치 데이터")
print(x_batch)
print(t_batch)
-------------------------------
훈련데이터 60000개, 입력데이터 784개
정답은 60000개, 10줄짜리
(60000, 784)
(60000, 10)
랜덤하게 뽑아낸 배치 데이터
[[0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 ...
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]]
[[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]]
 

수치미분

 

-미분은 한순간의 변화량을 표시

-미분식에서 h값이 엄청 작은값을 대입하면 반올림 오차를 일으킬 수 있다.

->반올림오차?  작은 값이 생략되서 최종계산에 오차가 생기게 한다.

->->해결하기 위해서 차분을 사용한다.

-변수가 2개일때 미분을 어떻게???-->편미분을 사용한다.

#차분이용 x
def numerical_diff_wrong(f,x):
    h=1e-50
    return (f(x+h)-f(x))/(h)
#차분이용
def numerical_diff(f,x):
    h=1e-4
    return (f(x+h)-f(x-h))/(2*h)
 

 

수치미분 예제

y=0.01x^2 + 0.1x를 수치 미분하는 예제

y=0.01x^2 + 0.1x

x가 5일때의 미분
0.1999999999990898
x가 10일때의 미분
0.2999999999986347

 

 

 


기울기

 기울기란?

-모든 변수의 편미분을 벡터로 정리한 것

def numerical_gradient(f, x):
    h = 1e-4
    grad = np.zeros_like(x) #x와 형상이 같으면서 원소가 모두 0인 배열 생성
   
    for idx in range(x.size):
        tmp_val = x[idx]
       
        #f(x+h)를 계산
        x[idx] = float(tmp_val) + h
        fxh1 = f(x)
       
        #f(x-h)를 계산
        x[idx] = tmp_val - h
        fxh2 = f(x)
       
        grad[idx] = (fxh1 - fxh2) / (2*h)
        x[idx] = tmp_val
       
    return grad

최적의 매개변수 찾기

 

경사 하강법

-기울기를 이용하여 함수의 최솟값을 찾는다.

-매개변수를 얼마나 갱신해야하는지 나타내는 값은 학습률이다.

-기울기가 가르키는 방향으로 가야 함수의 값을 줄일 수 있다.

-학습률을 여러번 갱신하며 함수의 값을 줄여 나간다.

#경사 하강법
#(함수 , 초깃값 , 학습률=0.01 , 반복횟수=100 )
def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x
    x_history = []

    for i in range(step_num):
        x_history.append( x.copy() )

        grad = numerical_gradient(f, x) #미분을 하여 기울기를 저장하고
        x -= lr * grad#초기값에 기울기에 학습률을 곱한 값을 뺀다

    return x
 
#경사법으로 함수의 최솟값 구하기
def function_2(x):
    return x[0]**2 + x[1]**2

 

init_x = np.array([-3.0, 4.0])    
print(gradient_descent(function_2 , init_x=init_x , lr=0.1 , step_num=100))
 
-----------------------------
[-6.11110793e-10  8.14814391e-10]
 
위의 결과를 보면 (0,0)에 가까운 결과가 나왔다. 실제 최소값은 (0,0)이니 경사법으로 거의 정확한 결과를 얻음.

오차역전파법을 이용한 신경망

 
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
from collections import OrderedDict

import numpy as np

def softmax(a):
    exp_a=np.exp(a)
    sum_exp_a=np.sum(exp_a)
    y=exp_a/sum_exp_a
    return y

def cross_entropy_error(y,t):
    delta=1e-7
    return -np.sum(t*np.log(y+delta))
   
class softmaxWithLoss:
    def __init__(self):
        self.loss=None
        self.y=None
        self.t=None
   
    def forward(self , x,t):
        self.t=t
        self.y=softmax(x)
        self.loss=cross_entropy_error(self.y , self.t)
        return self.loss

    def backward(self,dout=1):
        batch_size=self.t.shape[0]
        dx=(self.y-self.t)/batch_size
        return dx

class Relu:
    #mask는 True,False로 구성된 넘파이 배열
    def __init__(self):
        self.mask=None
   
    def forward(self , x):
        self.mask=(x<=0)    #값이 0보다 작으면 True, 0보다 크면 False
        out=x.copy()
        out[self.mask]=0
        return out
   
    def backward(self , dout):
        dout[self.mask]=0
        dx=dout
        return dx
   

class Affine:
    def __init__(self , W , b):
        self.W=W
        self.b=b
        self.x=None
        self.dw=None
        self.db=None

    def forward(self , x):
        self.x=x
        out=np.dot(x , self.w)+self.b
        return out

    def backward(self , dout):
        dx=np.dot(dout , self.W.T)
        self.dw=np.dot(self.x.T , dout)
        self.db=np.sum(dout , axis=0)
        return dx

class TwoLayerNet:
    #(입력층의 뉴런수 , 은닉층의 뉴런수 , 출력층의 뉴런수, 가중치 초기화시 정규분포 크기)
    def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
       
        #params는 신경망의 매개변수(가중치 편향)을 저장하는 딕셔너리
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)#1번째 층의 가중치
        self.params['b1'] = np.zeros(hidden_size)#1번째 층의 편향
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) #2번째 층의 가중치
        self.params['b2'] = np.zeros(output_size)#2번째 층의 편향

        #계층들을 생성한다
        self.layers = OrderedDict()
        self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
        self.layers['Relu1'] = Relu()
        self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
        #신경망의 마지막 계층이다.
        self.lastLayer = softmaxWithLoss()
       
    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)
       
        return x
       

    def loss(self, x, t):
        y = self.predict(x)
        return self.lastLayer.forward(y, t)
   
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        if t.ndim != 1 : t = np.argmax(t, axis=1)
       
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy
       
    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):
        # forward
        self.loss(x, t)

        # backward
        dout = 1
        dout = self.lastLayer.backward(dout)
       
        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)

        # 결과 저장
        grads = {}
        grads['W1'], grads['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db

        return grads