[Intel] 2주차 회고

LHW·2023년 7월 21일
0

Intel교육과정

목록 보기
2/15

Intel AI for Future Workforce 2주차 회고록

Affine 계층

저번주는 1x2x1 신경망을 통해서 간단하게 신경망에 대한 감을 잡고, 이번주는 입력층과 은닉층, 출력층의 개수를 늘려가면서 연습했다.
기하학에서 순전파 계산 시 실행하는 행렬곱을 Affine Transformation(이하 affine 변환)이라고 하는데, 기계학습에서의 Affine 계층이라 함은 순전파와 역전파를 구현할 수 있는 구조를 일컫는다고 한다. 이러한 Affine 계층과 특정 계층들이 합쳐지면 3주차에 실시할 CNN(Convolutional neural network)이 된다고 한다. 3주차 두근두근..

아주 중요한 공식

역전파 계산 시 우리는 항상 복잡한 미분식을 마주했다. 컴퓨터는 이해하지 못하는 미분을 이해시키기 위해 Chain Rule을 적용했고 적용하고 나서 확인해보니 sigmoid, identity, tanh function들은 미분 시 간단한 형태로 표현할 수 있다지만 미분도 이해못하고 Chain Rule 형태로 쪼갤줄도 모르면 어쩌죠

가 아니라 그나마 수학과를 재학하면서 남아있던 지식으로 어찌저찌 이해는 했다. 하지만 이해가 힘든 인원들을 위해서 교수님이 특별한 공식을 만들어주셨다.

오미입(오차, 미분, 입력), 오메(미)가미입(오차, 미분, 가중치, 미분, 입력)

편미분인걸 떠나서 미분 공식이 머릿속에서 아른아른하던 찰나 이런 공식을 만들어주셔서 감사했다..

취업특강

자소서의 경우 Saffy와 KT 에이블스쿨을 준비하면서 지겹도록 썼고 둘 다 떨어졌던 기억에 진짜 너무 쓰기 싫었는데... 흑흑 막상 강의 형식으로 들어보니까 좀 괜찮았던 것 같다. 내가 알던 STAR 기법이 나와서 반갑기도 했고, 내가 작성한걸 기반으로 피드백까지 들으니 더욱 좋았다. 자소서를 작성하면서 생겼던 나쁜 습관들을 고칠 수 있던 계기가 되었던 것 같다.

모델의 일반화

일반적인 기계학습에서의 일반화는 trian set, test set을 기반으로 학습한 모델이 외부 데이터에도 잘 적응할 수 있게 하는 과정을 일반화라고 하지만, 우리가 진행한 일반화는 약간 달랐다.

입력층, 은닉층, 출력층의 개수를 달리 해도 하나의 코드에서 역전파 계산식을 수정하거나 할 필요 없이 층의 개수만 수정하면 돌아가게끔 하는 의미에서 일반화를 진행했다.

import matplotlib.pyplot as plt # 그래프 시각화를 위한 라이브러리를 임포트
import numpy as np

alpha = 0.1 # 학습률
epochs = 3000 # 학습 횟수
n_hidden = 4 # 히든 레이어의 수

wt = [] # 가중치를 저장할 빈 배열
bs = [] # 편향값을 저장할 빈 배열
def init_weight(n_input, n_output):
    global wt, bs # wt, bs를 전역변수화 시킴으로서 함수 안에서 발생하는 가중치 배열, 편향값 배열의 변동사항을 외부에도 적용시킴.
    for _ in range(n_input * n_hidden + n_hidden * n_output): # input값의 개수*은닉층의 개수 + 은닉층의 개수*출력층의 개수 = 필요한 가중치의 개수
        wt.append(np.random.rand()) # 랜덤한 값을 가중치 배열에 추가한다.
    for _ in range(n_hidden + n_output): # 은닉층의 개수 + 출력층의 개수 = 필요한 편향값의 개수
        bs.append(np.random.rand()) # 랜덤한 값을 편향값 배열에 추가한다.
    
def sigmoid(x): # 활성화 함수 정의
    return 1/(1+np.exp(-x))

def forward(x, n_output):
    u = []; y = [] # 컴퓨터 언어에서 문장의 끝에 ; 을 붙이는 것은 해당 줄의 코드가 끝났음을 의미합니다. 
                   # 다행히도 파이썬의 경우 해당 줄의 코드가 끝나면 줄바꿈(엔터)하는 행위로 줄의 코드가 끝났음을 알릴 수 있지만,
                   # 한 줄에 여러개의 변수를 정의하고 싶다면 u = 12; y = 12식으로 변수를 선언할 수 있습니다.
                   # u : 입력층 -> 은닉층 값들을 저장할 빈 배열
                   # y : 은닉층 -> 출력층 값들을 저장할 빈 배열
                   
    n_input = len(x) # 입력층 개수
                         
    # 입력층 -> 은닉층 계산
    for j in range(n_hidden):
        sum = 0 
        for n in range(n_input): # 각 훈련 데이터에 대해서, 
            tmp = wt[n*n_input + j] * x[n] # 가중합 공식 중, w*x 부분에 해당
            sum += tmp # w*x 부분을 모두 더하고
        u.append(sigmoid(sum + bs[j])) # n개의 입력으로부터 받은 가중치*입력값 + 편향값을 활성화해서 저장
    
    # 은닉층 -> 출력층 계산(출력 -> 은닉 과정과 동일)
    for k in range(n_output):
        sum = 0 
        for n in range(n_hidden):
            tmp = wt[n_input*n_hidden + n*n_output + k] * u[n]
            sum += tmp
        y.append(sigmoid(sum+bs[n_hidden+k]))
    return u, y

# 역전파 계산(계산 그래프를 이용하면 이해하기 더 쉬움)
def backpropagate(x, u, y, t):
    dE_dw = [] # 가중치 기울기 값의 빈 리스트
    dE_db = [] # 편향값 기울기 값의 빈 리스트
    n_input = len(x); n_output = len(t) # 입력값의 개수, 출력값의 개수 정의
    
    # 출력층 -> 입력층 역전파 계산
    for i in range(n_input):
        for j in range(n_hidden):  
            sum = 0
            for n in range(n_output):
                tmp = (y[n]-t[n])*(1-y[n])*y[n]*wt[n_input*n_hidden + j + n_hidden*n] # 오 메 가 부분을 계산해서
                sum += tmp # 모두 더하고, 
            dE_dw.append(sum*(1-u[j])*u[j]*x[i]) # 더한 결과에 공통되는 부분인 미*입을 한꺼번에 곱해서 계산
    
    # 출력 -> 은닉층 역전파 계산
    for j in range(n_hidden):
        sum = 0
        for k in range(n_output):
            dE_dw.append((y[k]-t[k])*(1-y[k])*y[k]*u[j]) # 출력 -> 은닉층의 공식은 오미입. 계산해서 가중치 기울기 리스트에 저장
            tmp = (y[k]-t[k])*(1-y[k])*y[k]*wt[n_input*n_hidden+j+n_hidden*k] # 출력 -> 은닉층 편향값에 대한 오 메 가 부분을 계산해서
            sum += tmp # 모두 더하고
        dE_db.append(sum*(1-u[j])*u[j]) # 모두 더한 결과에 미*입(입력값의 경우, 1이기에 식 상에는 반영하지 않음)을 한꺼번에 곱해서 편향값 기울기 리스트에 저장
        
    # 출력층 편향값에 대한 역전파 계산
    for i in range(n_output):
        tmp = (y[i]-t[i])*(1-y[i])*y[i]
        dE_db.append(tmp)

    return dE_dw, dE_db

# 가중치 업데이트
def update_weight(dE_dw, dE_db):
    global wt, bs
    for i in range(len(wt)):
        wt[i] = wt[i] - alpha * dE_dw[i]
    for i in range(len(bs)):
        bs[i] = bs[i] - alpha * dE_db[i]

# 손실함수(MSE)로 손실값 계산
def calc_error(y, t):
    err = 0
    for i in range(len(t)):
        tmp = 0.5*(y[i]-t[i])**2 # loss function
        err = err + tmp
    return err

def error_graph(error): # 학습이 진행됨(epoch)에 따른 Error값 변화를 가시화
    plt.ylim(0.0, 1.0) # 오차 그래프의 y축(오차값)의 표시 범위 설정
    plt.plot(np.arange(0, error.shape[0]), error)
    plt.show()

def train(X, T):
    error = np.zeros(epochs) # 손실함수값(오차) 초기화
    n_input = X.shape[1] # 입력 노드 수
    n_output = T.shape[1] # 출력 노드 수
    # 가중치 초기화
    init_weight(n_input, n_output)
    ###### 입력과 정답으로 학습(train with input and teaching datum) ######
    for n in range(epochs): # epoch수 만큼 반복
        for i in range(X.shape[0]): # 입력 데이터 개수
            x = X[i, :] # x: 입력값 처음부터 끝까지
            t = T[i, :] # t: 출력값 처음부터 끝까지
            ### 신경망 순방향 계산(forward) ##########
            u, y = forward(x, n_output)
            ### 오차역전파 역방향 계산(backpropagation) ##########
            dE_dw, dE_db = backpropagate(x, u, y, t)
            ### 경사하강법, 가중치 업데이트(weight update) #####
            update_weight(dE_dw, dE_db)
            ### 에러 계산(calculate error) #####
            error[n] = calc_error(y, t)
        print("{} EPOCH-ERROR: {}".format(n, error[n]))
    error_graph(error)
    
def predict(x, n_output):
    u, y = forward(x, n_output)
    return u, y # 예측 = 신경망의 가중치가 업데이트된 후의 순방향 계산

if __name__ == '__main__':
    X = np.array([[1,0,0,1], [1,0,0,0], [0,0,1,1], [0,1,0,0], [1,1,0,0], [0,1,0,1], [0,0,1,0]]) # 학습용 입력 데이터
    T = np.array([[0, 1], [0.5, 0.5], [1, 0], [0, 0], [0, 1], [0, 0.5], [1, 0]]) # 정답 데이터
    train(X, T)

    x = np.array([1, 0, 1, 0]) # 테스트용 입력 데이터(발열과 기침)
    u, y = predict(x, T.shape[1]) # 테스트용 입력 데이터에 의한 예측
    print("")
    print("Cold or Covid 19?")
    print("Fever, Cough -> Cold? : {:.2f} %, Covid19 ? : {:.2f} % ".format(y[0]*100, y[1]*100))
    print("")

느낀점

이전 교육 과정에서는 라이브러리 위주로 코드를 돌리는 게 주가 되는 수업이었지만, 이론은 역시나 어렵다. 이해하기 어려운 부분도 많고 "아 이게 이런내용이었구나" 하고 이해하는 부분도 많아 흥미롭다. 아직까지도 많은 부족함을 느낀다. 정진하자 !

profile
하루가 다르게 성장하기

0개의 댓글