Hyunn

[KT AIVLE School] AI트랙 5 & 6주차 후기(딥러닝) 본문

Study/KT AIVLE School

[KT AIVLE School] AI트랙 5 & 6주차 후기(딥러닝)

Ravié 2024. 4. 2. 22:57
728x90
반응형

5 & 6주차 일정

03.18 ~ 20: 미니프로젝트 2차
03.21 ~ 26: 딥러닝
03.27 ~ 29: 미니프로젝트 3차

 

이번 주차는 딥러닝 강의가 5주차와 6주차에 걸쳐있어서 두 주차를 한번에 올렸다.

앞으로 미니프로젝트는 "KT AIVLE School 기자단" 카테고리에서 다룰 예정이므로 이 글은 딥러닝만 다룬다.

 

[기자단 카테고리]

https://hyunjin-k.tistory.com/category/Study/KT%20AIVLE%20School%20%EA%B8%B0%EC%9E%90%EB%8B%A8

 


딥러닝 코드 구조

머신러닝과 마찬가지로 딥러닝도 (딥 하게 공부해보고 싶긴 하지만) 시간이 없으니까 코드 구조먼저 살펴보았다. 최소한 코드는 작성할 줄 알아야 하니까! 큰 구조는 아래와 같다.

# 필요한 모듈 불러오기
from keras.models import Sequential
from keras.layers import Dense
from keras.backend import clear_session
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_absolute_error, mean_absolute_percentage_error

# 전처리
## 딥러닝은 머신러닝과 다르게 전처리가 필수
scaler = NinMaxScaler()
x_train = scaler.fit_transform(x_train)
x_val = scaler.transform(x_val)

# features개수 저장
nfeatures = x_train.shape[1]

# 메모리 정리(선택사항)
clear_session()

# Sequential타입 모델 선언
model = Sequential([Dense(12, input_shape=(nfeatures,), activaion='relu',
				    Dense(1)])
                    
# 모델 요약
model.summary()

# 컴파일
## 회귀모델은 mse, 이진분류는 binary_crossentropy, 다중분류는 categorical_crossentropy
model.compile(optimizer=Adam(learning_rate=0.01), loss='mse')

# 학습하기
model.fit(x_train, y_train, epochs=20, validation_split=.2)

# 예측하기
pred = model.predict(x_val)

# 검증하기
print('mae: ', mean_absolute_error(y_val, pred))
print('mape: ', mean_absolute_percentage_error(y_val, pred))

 


딥러닝 개념 이해

가중치

예를들어, 4월의 아이스크림 판매량을 예측한다고 해보자. 아이스크림 판매량은 여러 변수들(features)에 영향을 받을 것이다. 가령, 3월의 판매량, 온도, 습도, 공휴일여부가 변수라고 할 때, 모두 같은 중요도(가중치)를 갖지는 않을 것이다. 4월 판매량 수치에 3월 판매량은 0.2만큼, 온도는 0.7만큼, 습도는 0.5만큼 공휴일여부는 0.4만큼 영향을 미친다고 한다면, 이 숫자들이 "가중치"라고 한다.

 

Layer

앞의 예에서 input은 전월 판매량, 온도, 습도, 공휴일여부였다. 여기에서 온도, 습도를 날씨로 묶고, 전월 판매량, 공휴일 여부를 날짜로 묶을 수도 있다(단순 예시일 뿐이다). 그럼 input다음 Layer에서 2개의 Node로 구성된 Layer가 생성되는 것이다.

위 그림과 같이 여러 feature들을 합치거나 분리시켜 새로운 특징을 가진 노드를 생성할 수 있다. 실제 딥러닝에서는 매우 복잡한 관계를 나타낸다. 이러한 layer들을 쌓아 학습시킬 수 있는 것이다.

이런식으로...


코드분석

모델 선언

model = Sequential([Dense(18, input_shape=(nfeatures,), activation='relu'),
					Dense(1)])

 

머신러닝과 마찬가지로 모델을 선언하는 부분이다. 하지만 머신러닝과 매우 다른 모습을 볼 수 있다.

Dense를 한 번 선언할 때마다 1개의 Layer가 쌓인다고 이해하면 된다. Dense안에 있는 숫자는 해당 Layer에서 Node의 개수이다. input_shape는 입력이 들어올 때의 feature개수이며, activation='relu'는 활성화함수를 relu를 사용한다는 의미이다. 활성화함수에 대해서는 후술하였다.

 

컴파일

model.compile(optimizer=Adam(learning_rate=0.01), loss='mse')

 

머신러닝과 다르게 딥러닝은 모델을 학습시키기 전, 컴파일을 진행한다. 컴파일을 간단하게 설명하자면, 선언된 모델에 대해 몇 가지 설정을 한 후, 컴퓨터가 이해할 수 있는 형태로 변환하는 작업이다.

  • optimizer는 오차를 최소화 하도록 가중치를 조절하는 역할이다. 현재로써는 대부분 Adam알고리즘을 사용한다.
  • learning_rate는 학습률이다. 가중치를 학습을 하며 가중치를 조절하게 되는데, 한 번에 얼마만큼 가중치를 변경할지 결정하는 변수이다. learning rate가 너무 크다면 가중치가 큰 범위로 조정되어 오차를 최소화하는 가중치를 찾지 못할수도 있으며, 너무 작으면 가중치를 아주 조금씩 조절하여 오차를 최소화하는 가중치에 도달하지 못할수도 있으며, 도달했더라고 학습시간이 매우 오래걸릴 수 있다.
  • loss는 오차를 계산하는 방식이다. mse로 설정하면 mse(mean squared error)를 기반으로 오차를 최소화하는 모델을 설계할 것이다.

학습하기

model.fit(x_train, y_train, epochs=20, validation_split=.2)
  • epochs: 모델을 학습하는 전체 횟수이다. 즉, 첫 번째 epoch일 때, 가중치를 랜덤값으로 결정한다(정규분포에서 랜덤으로 결정). 이후 오차(loss)를 계산한다(순전파). 그리고 다시 되돌아오며 가중치를 조절한다(역전파). 이 과정이 epoch 1번이다. 즉, 위 코드는 이를 20번 반복하는 것이다. epoch가 너무 크게 되면 과적합이 발생할 수 있다.

예측하기, 검증하기

학습곡선을 그리며 파라미터를 튜닝한다.

바람직한 곡선
바람직하지 않은 곡선(learning rate를 줄이기)
바람직하지 않은 곡선2(과적합)


회귀 모델링

회귀모델링은 아이스크림 판매량, 주가, 환율같은 연속된 수치를 예측하는 모델을 의미한다.

회귀모델에서 모델을 생성할 때 activation함수는 ReLU를 사용한다. 다른 함수도 있지만 현재로는 거의 대부분이 ReLU를 사용한다.

활성화 함수(Activation Function): 현재 레이어의 결과값을 다음 레이어로 어떻게 전달할지 결정/변환해주는 함수

 

ReLU함수의 수식은 아래와 같다.

$$f(x) = max(0, x)$$

 

출처: 위키백과

 

만약, 활성화함수가 없다면 레이어를 아무리 추가해도 선형회귀이다.

출처: AIVLE School 강의자료 일부 발췌

  • 활성화 함수: ReLU
  • optimizer: Adam
  • 모델평가: MSE, MAE, MAPE etc.

 

반응형

 

이진분류

타이타닉 데이터의 생존여부를 판단하기 위해서는 가중치를 부여해 output을 도출해야 한다. 하지만 output layer에서 활성함수를 거치지 않으면  0과 1의 결과가 나오지 않는다.

 

따라서, 해당 함수의 결과값을 0과 1로 분류해야 한다. 그것을 도와주는 함수를 활성 함수라고 한다. 회귀모델과 다른점은 이진분류에서는 output layer에서도 활성함수를 사용한다.

$$P_+(X) = \frac{1}{1 + e^{-f(X)}}$$
model = Sequential([Dense(10, input_shape=(nfeatures,), activation='relu'),
				    Dense(1, activation='sigmoid')])
                    
model.compile(optimizer=Adam(0.01), loss='binary_crossentropy')
model.fit( ... )

 

Loss Function: binary_crossentropy

이진분류모델을 compile할 때 loss옵션을 binary_crossentropy로 주었는데 이는 로그함수라고 생각하면 된다.

위 함수는  $y=-logx$  함수이다. x가 1일 때 y값은 0이고, x값이 증가할수록 y값은 감소함을 볼 수 있다.

따라서 두 가지 기준으로 나눠 생각할 수 있는데, 실제 값 1을 예측하는 경우와 실제 값 0을 예측하는 경우로 나눌 수 있다.

  • 실제 값 1을 예측하는 경우
    • x축: 예측 값
    • y축: 오차
    • 즉, 실제 값 1을 1로 예측하는 경우 오차는 0이고 예측 값이 1과 멀어질수록 오차는 커진다.
  • 실제 값 0을 예측하는 경우
    • x축: 1 - 예측 값
    • y축: 오차
    • 즉, 실제 값 0을 0으로 예측하는 경우 x는 1이므로 오차(y)는 0이고 예측 값과 멀어질수록 오차는 커진다.

이 오차들의 평균을 구하면 binary_crossentropy이다.

$$-\frac{1}{n}\Sigma{y \cdot log \, \hat{y} + (1-y) \cdot log(1-\hat{y}))}$$

Class Imbalances(클래스 불균형)

클래스 불균형은 일상생활에서 매우 흔한 일이다. 예를들어, AI를 통해 금융 비정상 거래 예측을 하거나 직원의 이직을 예측하거나 설비 오류를 예측하는 등 정상보다는 비정상에 관심이 있지만 비정상인 작동(행동)을 학습시킬 데이터 양이 상대적으로 현저히 적다. 하지만 일반적인 알고리즘은 데이터 클래스 내에서 고르게 분포되어 있다고 가정하고 학습한다. 그리고 accuracy를 높이는데 목적을 둔다. 따라서 비정상적인 작동(행동)을 맞출 확률이 떨어지게 된다. 예를들어, 지진 발생을 예측하는 모델은 항상 0으로 예측하는 것이 accuracy가 높게 나올 것이기 때문이다.

  • 해결방법
    • Resampling: 물리적으로 data 균형을 맞추는 것
    • class weight 조정: 논리적으로 오차의 균형을 맞추는 것

다양한 알고리즘이 있지만 이 글에서는 over sampling만을 다루겠다.

from imblearn.over_sampling import RandomOverSampler

ros = RandomOverSampler()
x_train_ros, y_train_ros = ros.fit_resample(x_train, y_train)

model = Sequential( ...)
model.compile( ... )
model.fit( ... )

다른 알고리즘도 코드 작성법은 알고리즘 명만 다르고 비슷하다.


다중분류

다중분류는 'softmax' 활성화 함수를 사용한다. 이는 각 클래스별로 예측한 값을 하나의 확률 값으로 변환한다.

$$softmax(x)_i = \frac{e^{x_i}}{\Sigma{e^{x_i}}}$$

 

다중분류는 y에 대한 가변수화가 필요하다.

  • 방법1: 정수 인코딩
    ex) setosa -> 0, versicolor -> 1, virginica -> 2
    from sklearn.preprocessing import LabelEncoder
    
    # 선언
    int_encoder = LabelEncoder()
    
    # 인코딩
    data['y_encode'] = int_encoder.fit_transform(data['y'])​
  • 방법2: One-hot Encoding(가변수화와 유사)
from sklearn.preprocessing import OneHotEncoder

# OneHotEncoder 선언
oh_encoder = OneHotEncoder()

# 데이터를 원핫인코딩하여 변환(input: 2차원)
encoded_y1 = oh_encoder.fit_transform(data[['y']])

# 변환된 데이터 확인
print(encoded_y1.toarray())

 

구분 Hidden Layer에서 Activation 함수 Output Layer에서 Activation 함수 Output Layer에서 Node개수 compile에서 optimizer compile에서 loss
회귀 relu X 1 Adam mse
이진분류 relu sigmoid 2 Adam binary_crossentropy
다중분류 relu softmax n개 Adam sparse_categorical_crossentropy

성능관리

과적합

과적합이란 학습 데이터에서만 높은 성능을 보이고 다른 데이터(test data 등)에서는 낮은 성능을 보이는 현상을 의미한다.

위 그림에서 Model3가 과적합된 모델이다.

 

그래프로 확인해보면 위 그래프가 전형적인 과적합 그래프이다. val데이터에 대해서는 loss가 점점 커지고 있는 반면 train데이터는 loss가 거의 0임을 확인할 수 있다.

 

과적합을 방지하는 방법에는 아래와 같이 있다.

  • 복잡도를 조금씩 조절해가면서 train erorr와 validation error를 측정해 비교한다.
  • 딥러닝에서 조절하는 파라미터
    • epoch: 반복학습 횟수
    • learning rate: 학습률
    • 모델 구조 조정(hidden layer, node 개수 등)
    • Early stopping
    • Regularization(규제): L1, L2
    • Dropout

과적합 방지(1)

: 데이터 개수를 늘린다.

과적합 방지(2)

: Early Stopping

early stopping은 epoch가 너무 많으면 과적합될 수 있으므로 ealry stopping을 사용해 train data error는 줄어들고, validation data error가 늘어나는 지점에서 학습을 중단시킬 수 있다.

from keras.callbacks import EarlyStopping
es = EarlyStopping(monitor='val_loss', min_delta=0, patience=0)

... 

model.fit(x_train, y_train, epochs=1000, validation_split=.2, callbacks=[es])
  • min_delta: 오차가 얼마 이상으로 줄어든 것을 줄어들었다고 판단할건지를 지정
  • patience: 오차가 줄어들지 않는 상황을 몇 번 기다려줄 것인지 지정

과적합 방지(3)

: 가중치 규제(Regularization)

가중치 규제란 파라미터가 너무 많은 경우 중요하지 않은 노드(가중치가 상대적으로 작은 노드)에 대해서 가중치를 0혹은 0에 가까운 수로 바꾸는 것이다.

  • L1 규제: Lasso
    • 효과: 가중치 절대값의 합을 최소화 → 가중치가 작은 값들은 0으로 만드는 경향
      $$오차함수 = 오차 + \lambda \times \Sigma{|w|}$$
  • L2 규제: Ridge
    • 효과: 가중치 제곱의 합을 최소화 → 강도가 크면, 가중치가 좀 더 줄어든느 효과, 작은 가중치는 0에 수렴하게 됨
      $$오차함수 = 오차 + \lambda \times \Sigma{w^2}$$

규제가 높을수록 일반화된 모델이 되고 더 높아지면 단순한 모델이 된다. 일반적으로 L1 규제는 0.0001 ~ 0.1, L2규제는 0.001 ~ 0.5 값을 준다.


Functional API

: 다중 입력, 다중 출력이 가능한 API이며, 코드의 구조만 다르다. 각 레이어마다 연결되는 레이어를 지정하고 마지막에 input layer와 output layer만 지정하면 된다.

il = Input(shape=(nfeatures,))

# 히든 레이어
hl = Dense(18, activation='relu')(il) # 입력 레이어와 연결
hl = Dense(4, activation='relu')(hl) # 이전 히든 레이어와 연결

# 출력 레이어
ol = Dense(1)(hl) # 이전 히든 레이어와 연결

# 모델 선언
model = Model(inputs=il, outputs=ol)

model.summary()

# 이후 컴파일과 학습과정은 동일

 

다중입력은 다음과 같이 받는다.

il_1 = Input(shape=(nfeatures1,), name='input_1')
il_2 = Input(shape=(nfeatures2,), name='input_2')

hl_1 = Dense(10, activation='relu')(il_1)
hl_2 = Dense(10, activation='relu')(il_2)

# 두 히든레이어 결합
cbl = concatenate([hl_1, hl_2])

# 추가 히든레이어
hl = Dense(8, activation='relu')(cbl)

# 이후 과정은 동일

딥러닝 기반 시계열 모델링

시계열 데이터

: Sequential 데이터 중에서도 시간의 간격이 등간격인 데이터, 시간의 흐름에 따른 패턴을 분석하는 것이 중요!

ex) 음성데이터, 주가데이터

시간의 흐름을 측정하는 방법

  1. 통계적 시계열 모델링: y를 보고 확인(분석단위는 1차원)
  2. ML기반 시계열 모델링: x축을 보고 확인(시간의 흐름을 x변수로 도출하는 것이 중요)
    ex) 7일전 주가, 5일전 주가 등을 feature로 생성
  3. DL기반 시계열 모델링: 시간 흐름 구간 데이터들과 예측 대상 시점과의 관계로부터 패턴 추출
    1. 시간 흐름 구간을 정하는 것이 포인트
    2. 분석단위는 2차원
    3. 데이터 셋은 3차원이 된다(2차원 데이터들이 여러개 모이기 때문)

딥러닝 기반 시계열 모델링(RNN)

: 시간흐름 구간(timesteps) 데이터들과 예측대상시점과의 관계로부터 패턴을 추출한다.

위 그림에서는 4개의 행, 4개의 열(2차원 데이터)로 target을 예측한다. 빨간색 테두리 행렬이 1개의 데이터가 되는 것이다. 이전에는 1차원 데이터가 입력으로 주어졌다면 RNN은 2차원 데이터가 입력으로 주어지는 것이다. 빨간색 테두리의 행렬은 한칸씩 이동하며 입력으로 주어져 학습을 한다.  즉, 빨간색 테두리 행렬과 하늘색 데이터가 세트로 학습이 된다.

 

출처:  https://colah.github.io/posts/2015-08-Understanding-LSTMs/

위 그림은 RNN의 내부이다. timesteps가 3일 경우 x0~x2가 입력으로 주어진다. x1이 입력으로 주어졌을 때, 이전 결과물은 h0이다. 따라서 x1과 h0이 입력으로 들어와 tanh함수를 거쳐 o1, h1이 도출된다. 이때, o1과 h1은 동일한 값이지만 쓰임새가 다르다. h1은 다음 데이터(x2)를 학습시킬 때 사용되고 o1은 현재 데이터까지의 결과물로써 남겨둔다. 즉, 정리하자면 RNN은 과거의 정보를 현재에 반영해 학습하도록 설계되어 있다.

 

RNN을 위한 데이터 전처리

  1. 데이터분할1: x와 y로 데이터를 분할한다.
  2. 스케일링
  3. 3차원 데이터셋 만들기
  4. 데이터분할2: train과 validation으로 데이터를 분할한다.

cf)스케일링을 먼저하는 이유: 스케일러가 3차원 데이터를 지원하지 않는다. 데이터가 많으면 스케일링을 먼저해도 크게 문제되지 않는다. 조금 더 엄밀하게 하기 위해서는 직접 스케일링을 해주면 된다.

( ex) (x_train - x_train.min()) / (x_train.max() - x_train.min())

 

# 시계열 데이터 전처리 2차원 --> 3차원으로 변환
def temporalize(x, y, timesteps):
    nfeature = x.shape[1]
    output_x = []
    output_y = []
    for i in range(len(x) - timesteps + 1):
        t = []
        for j in range(timesteps):
            t.append(x[[(i + j)], :])
        output_x.append(t)
        output_y.append(y[i + timesteps - 1])
    return np.array(output_x).reshape(-1,timesteps, nfeature), np.array(output_y)
# 스케일링
scaler = MinMaxScaler()
x = scaler.fit_transform(x)

# 3차원 구조 만들기
x2, y2 = temporalize(x, y, 4) # 4: timestep

# 데이터 분할
x_train, x_val, y_train, y_val = train_test_split(x2, y2, test_size=.2, shuffle=False)

timesteps = x_train.shape[1]
nfeatures = x_train.shape[2]

# 모델 구조 설계
model = Sequential([SimpleRNN(8, input_shape=(timesteps, nfeatures)),
					Dense(1)])
                    
model.compile(optimizer='adam', loss='mse')
hist = model.fit(...)
728x90
반응형