728x90
반응형

 이전에 학습했던 모델은 Input 되는 Node의 수가 2개인 모델이었다. Input Node의 수를 3개 이상으로 하는 방법도 크게 다르지 않다. 이번 포스트에서는 Output Node의 수를 2개로 해보도록 하겠다.

 

 

학습 목표

  • 연속형 데이터를 이용하여 Input Node가 3개이고, Output Node가 2개인 데이터를 컴퓨터가 맞추도록 해보자.
  • 패턴:

$$h(x)=\begin{cases}y_1=0.3x_1+0.2x_2-0.4x_3+0.1x_4+2 \\  y_2=0.5x_1-0.1x_2+0.3x_3+0x_4-2\end{cases}$$

 

 

 

구현해보자.

1. 데이터셋 생성

# import mudule
import pandas as pd
import numpy as np
from tensorflow import keras
from tensorflow.keras.layers import Dense
# Dataset 생성
def f1(x1, x2, x3, x4):
    return 0.3*x1 + 0.2*x2 - 0.4*x3 + 0.1*x4 + 2


def f2(x1, x2, x3, x4):
    return 0.5*x1 - 0.1*x2 + 0.3*x3 - 2


def make_dataset(start_N, end_N):
    
    x1 = np.arange(start_N, end_N)
    x2 = x1 + 1
    x3 = x1 + 2
    x4 = x1 + 3
    
    y1 = f1(x1, x2, x3, x4)
    y2 = f2(x1, x2, x3, x4)
    
    append_for_shuffle = np.c_[x1, x2, x3, x4, y1, y2]
    np.random.shuffle(append_for_shuffle)
    
    X = append_for_shuffle[:,[0,1,2,3]]
    y = append_for_shuffle[:,[4,5]]
    
    return X, y
X, y = make_dataset(0, 1000)
X_train, X_test = X[:800], X[800:]
y_train, y_test = y[:800], y[800:]
  • 생성된 Dataset을 보자.
>>> X_train
array([[491., 492., 493., 494.],
       [ 47.,  48.,  49.,  50.],
       [755., 756., 757., 758.],
       ...,
       [445., 446., 447., 448.],
       [429., 430., 431., 432.],
       [881., 882., 883., 884.]])
       
>>> y_train
array([[ 99.9, 342.2],
       [ 11.1,  31.4],
       [152.7, 527. ],
       ...,
       [ 90.7, 310. ],
       [ 87.5, 298.8],
       [177.9, 615.2]])

 

 

 

2. 모델 생성 및 학습

# 모델 생성 및 Compile 실시
model = keras.Sequential()
model.add(Dense(128, activation = "relu"))
model.add(Dense(64, activation = "relu"))
model.add(Dense(32, activation = "relu"))
model.add(Dense(16, activation = "relu"))
model.add(Dense(2, activation = "linear"))

opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer = opt, loss = "mse")

# 표준화
min_key = np.min(X_train)
max_key = np.max(X_train)

X_std_train = (X_train - min_key)/(max_key - min_key)
y_std_train = (y_train - min_key)/(max_key - min_key)
X_std_test = (X_test - min_key)/(max_key - min_key)
>>> model.fit(X_std_train, y_std_train, epochs = 100)

Epoch 1/100
25/25 [==============================] - 1s 1ms/step - loss: 0.0181
Epoch 2/100
25/25 [==============================] - 0s 1ms/step - loss: 1.9474e-04
Epoch 3/100
25/25 [==============================] - 0s 1ms/step - loss: 2.2225e-05
Epoch 4/100
25/25 [==============================] - 0s 1ms/step - loss: 3.8012e-06
Epoch 5/100
25/25 [==============================] - 0s 1ms/step - loss: 7.8100e-07
Epoch 6/100
25/25 [==============================] - 0s 1ms/step - loss: 3.6994e-07

...

Epoch 96/100
25/25 [==============================] - 0s 1ms/step - loss: 1.1601e-07
Epoch 97/100
25/25 [==============================] - 0s 1ms/step - loss: 1.8677e-07
Epoch 98/100
25/25 [==============================] - 0s 956us/step - loss: 3.6037e-07
Epoch 99/100
25/25 [==============================] - 0s 878us/step - loss: 3.3609e-07
Epoch 100/100
25/25 [==============================] - 0s 873us/step - loss: 1.9744e-07
<tensorflow.python.keras.callbacks.History at 0x1d068dd0670>
>>> def MAE(x, y):
>>>     return np.mean(np.abs(x - y))

>>> pred = model.predict(X_std_test) * (max_key - min_key) + min_key
>>> print("Accuracy:", MAE(pred, y_test))
Accuracy: 0.1379637644290885
  • 정확도(Accuracy)는 0.137로 만족스러울 정도는 아니지만 그리 나쁘진 않게 나왔다.
  • 정확도는 평균 절댓값 오차(MAE)로 구하였으므로, RMSE보다 실제 편차에 더 가깝다고 할 수 있다.
  • 실제 데이터의 생김새를 보자.
DF = pd.DataFrame(pred, columns=["y1_pred", "y2_pred"])
DF[["y1_label", "y2_label"]] = y_test
DF["y1_gap"] = DF["y1_label"]-DF["y1_pred"]
DF["y2_gap"] = DF["y2_label"]-DF["y2_pred"]
DF[["y1_pred", "y1_label", "y1_gap", "y2_pred", "y2_label", "y2_gap"]]

  • 만족스러운 수준은 아니지만, 실제 데이터와 예측 데이터가 꽤 유사하게 나왔다.

 

 

 

 

 지금까지 가볍게 연속형 데이터를 이용해서 숨겨진 패턴을 찾아보았다. 다음 포스트에서는 머신러닝을 할 때, 가장 처음 사용하게 되는 실제 데이터인 타이타닉 데이터를 이용해서 생존 여부를 분류해보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서 변수가 1개인 Input이 들어가 Output이 1개인 모델을 만들어보았다. 이번 포스트에서는 Input이 2개고, Output이 1개인 모델을 만들어보도록 하겠다.

 

 

학습 목표

  • 이전 패턴보다 컴퓨터가 인지하기 어려운 패턴을 컴퓨터가 찾아내도록 해보자.
  • 패턴: $ f(x)=\frac{1}{2}x_1^2-3x_2+5 $

 

 

 

 

1. 이전 방식대로 모델을 만들고 평가해보자.

  • 이전 모델을 생성했던 방법대로 데이터셋을 생성하고 학습을 시켜서 패턴을 찾는지 확인해보자.
# Import Module
import pandas as pd
import numpy as np
from tensorflow import keras
from tensorflow.keras.layers import Dense
# Dataset 만들기
np.random.seed(1234)

def f2(x1, x2):
    
    return 0.5*x1**2 - 3*x2 + 5

X0_1 = np.random.randint(0, 100, (1000))
X0_2 = np.random.randint(0, 100, (1000))
X_train = np.c_[X0_1, X0_2]
y_train = f2(X0_1, X0_2)

X1_1 = np.random.randint(100, 200, (300))
X1_2 = np.random.randint(100, 200, (300))
X_test = np.c_[X1_1, X1_2]
y_test = f2(X1_1, X1_2)
# make model
model = keras.Sequential()
model.add(Dense(16, activation = 'relu'))
model.add(Dense(32, activation = 'relu'))
model.add(Dense(16, activation = 'relu'))
model.add(Dense(1, activation = 'linear'))


# Compile
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer=opt, loss='mse')


# Standardization
mean_key = np.mean(X_train)
std_key = np.std(X_train)

X_train_std = (X_train - mean_key)/std_key
y_train_std = (y_train - mean_key)/std_key

X_test_std = (X_test - mean_key)/std_key
>>> model.fit(X_train_std, y_train_std, epochs = 100)

Epoch 1/100
32/32 [==============================] - 1s 972us/step - loss: 4486.5587
Epoch 2/100
32/32 [==============================] - 0s 1ms/step - loss: 2577.3394
Epoch 3/100
32/32 [==============================] - 0s 974us/step - loss: 135.0658
Epoch 4/100
32/32 [==============================] - 0s 1ms/step - loss: 39.6805
Epoch 5/100
32/32 [==============================] - 0s 1ms/step - loss: 26.0182
Epoch 6/100
32/32 [==============================] - 0s 1ms/step - loss: 23.2357

...

Epoch 96/100
32/32 [==============================] - ETA: 0s - loss: 0.870 - 0s 730us/step - loss: 0.9306
Epoch 97/100
32/32 [==============================] - 0s 835us/step - loss: 0.4291
Epoch 98/100
32/32 [==============================] - 0s 792us/step - loss: 0.5671
Epoch 99/100
32/32 [==============================] - 0s 856us/step - loss: 0.3809
Epoch 100/100
32/32 [==============================] - 0s 708us/step - loss: 0.4041
<tensorflow.python.keras.callbacks.History at 0x21cdb6c0b80>
>>> pred = (model.predict(X_test_std) * std_key) + mean_key
>>> pred = pred.reshape(pred.shape[0])
>>> print("Accuracy:", np.sqrt(np.sum((y_test - pred)**2))/len(y_test))
Accuracy: 209.2436541220142
  • 이전 포스트처럼 시험 데이터 셋과 학습 데이터 셋을 전혀 겹치지 않는 영역으로 만들어보았다.
  • 손실 값은 0에 가깝게 줄어들었으나, 정확도(Accuracy)가 209.243으로 매우 낮은 것을 알 수 있다.
  • 예측값과 라벨의 차이가 어느 정도인지 확인해보자.
result_DF = pd.DataFrame({"predict":pred, "label":y_test})
result_DF["gap"] = result_DF["label"] - result_DF["predict"]
result_DF

  • 위 데이터를 보면, 실제(label)와 예측값(predict)의 차이가 매우 크게 나는 것을 볼 수 있다.
  • 대체 왜 이런 현상이 발생한 것일까?

 

 

 

 

2. 학습에 맞는 데이터셋 만들기

  • 이전 학습에서 숨겨져 있던 패턴은 다음과 같다.
  • $h(x) = x + 10 $
  • 위 패턴은 아주 단순한 선형 함수이므로, 학습 데이터 셋과 거리가 있는 데이터라 할지라도, 쉽게 예측할 수 있다.
  • 그러나, 이번에 숨겨진 패턴인 $f(x)=\frac{1}{2}x_1^2-3x_2+5$은 $x^2$의 존재로 인해 선형 함수가 아니며, 해가 2개이므로, 이전에 비해 꽤 복잡해졌다.
  • 이번엔 train Dataset에서 test Dataset을 분리해서 학습해보자.
  • 단, train Dataset과 test Dataset은 절대 중복되선 안 된다.
# Dataset 만들기
np.random.seed(1234)

def f2(x1, x2):
    
    return 0.5*x1**2 - 3*x2 + 5

X1 = np.random.randint(0, 100, (1000))
X2 = np.random.randint(0, 100, (1000))
X = np.c_[X1, X2]
y = f2(X1, X2)

# 데이터셋을 중복되지 않게 만든다.
Xy = np.c_[X, y]
Xy = np.unique(Xy, axis = 0)
np.random.shuffle(Xy)
test_len = int(np.ceil(len(Xy)*0.3))
X = Xy[:, [0,1]]
y = Xy[:, 2]

# test Dataset과 train Dataset으로 나누기
X_test = X[:test_len]
y_test = y[:test_len]

X_train = X[test_len:]
y_train = y[test_len:]
  • np.c_[array1, array2]: 두 array를 열 기준으로 붙인다.
  • np.unique(array, axis = 0): array에서 unique 한 값만 추출한다(axis를 어떻게 잡느냐에 따라 다른 결과를 가지고 올 수 있다).
  • np.random.shuffle(array): array를 랜덤 하게 섞는다
  • np.ceil(float): float을 올림 한다.
  • 데이터셋을 중복되지 않게 만들어, test set과 train set이 중복되어 Accuracy가 낮게 나오는 현상을 피한다.
# make model
model = keras.Sequential()
model.add(Dense(16, activation = 'relu'))
model.add(Dense(32, activation = 'relu'))
model.add(Dense(16, activation = 'relu'))
model.add(Dense(1, activation = 'linear'))


# Compile
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer=opt, loss='mse')


# Standardization
mean_key = np.mean(X_train)
std_key = np.std(X_train)

X_train_std = (X_train - mean_key)/std_key
y_train_std = (y_train - mean_key)/std_key

X_test_std = (X_test - mean_key)/std_key
# Model Learning
>>> model.fit(X_train_std, y_train_std, epochs = 100)

Epoch 1/100
139/139 [==============================] - 1s 912us/step - loss: 2999.6784
Epoch 2/100
139/139 [==============================] - 0s 943us/step - loss: 26.4051
Epoch 3/100
139/139 [==============================] - 0s 1ms/step - loss: 14.5395
Epoch 4/100
139/139 [==============================] - 0s 1ms/step - loss: 9.9778
Epoch 5/100
139/139 [==============================] - 0s 814us/step - loss: 7.2809
Epoch 6/100
139/139 [==============================] - 0s 777us/step - loss: 5.1137
Epoch 7/100

...

Epoch 96/100
139/139 [==============================] - 0s 1ms/step - loss: 0.0378
Epoch 97/100
139/139 [==============================] - 0s 931us/step - loss: 0.0468
Epoch 98/100
139/139 [==============================] - 0s 821us/step - loss: 0.0808
Epoch 99/100
139/139 [==============================] - 0s 745us/step - loss: 0.1535
Epoch 100/100
139/139 [==============================] - 0s 793us/step - loss: 0.0493
<tensorflow.python.keras.callbacks.History at 0x260b7b33c70>
>>> pred = (model.predict(X_test_std) * std_key) + mean_key
>>> pred = pred.reshape(pred.shape[0])
>>> print("Accuracy:", np.sqrt(np.sum((y_test - pred)**2))/len(y_test))
Accuracy: 0.9916198414587479
  • 데이터 셋만 바꿨는데, 이전 데이터 셋의 정확도(Accuracy)가 209.243에서 0.9916으로 큰 폭으로 떨어진 것을 볼 수 있다.
  • 실제 예측 결과가 어떻게 생겼는지 확인해보자.
result_DF = pd.DataFrame({"predict":pred, "label":y_test})
result_DF["gap"] = result_DF["label"] - result_DF["predict"]
result_DF

  • 차이가 있긴 하지만, 실제 데이터와 상당히 가까워졌다.
  • 이번엔 데이터의 양을 늘려서 학습시켜보자.

 

 

 

 

3. 데이터의 양을 늘려보자.

# Import Module
import pandas as pd
import numpy as np
from tensorflow import keras
from tensorflow.keras.layers import Dense




# Dataset 만들기
np.random.seed(1234)

def f2(x1, x2):
    
    return 0.5*x1**2 - 3*x2 + 5

X1 = np.random.randint(0, 100, (30000))
X2 = np.random.randint(0, 100, (30000))
X = np.c_[X1, X2]
y = f2(X1, X2)

# 데이터셋을 중복되지 않게 만든다.
Xy = np.c_[X, y]
Xy = np.unique(Xy, axis = 0)
np.random.shuffle(Xy)
test_len = int(np.ceil(len(Xy)*0.2))
X = Xy[:, [0,1]]
y = Xy[:, 2]

# test Dataset과 train Dataset으로 나누기
X_test = X[:test_len]
y_test = y[:test_len]

X_train = X[test_len:]
y_train = y[test_len:]




# make model
model = keras.Sequential()
model.add(Dense(32, activation = 'elu'))
model.add(Dense(32, activation = 'elu'))
model.add(Dense(1, activation = 'linear'))


# Compile
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer=opt, loss='mse')


# min-max scaling
min_key = np.min(X_train)
max_key = np.max(X_train)

X_train_std = (X_train - min_key)/(max_key - min_key)
y_train_std = (y_train - min_key)/(max_key - min_key)

X_test_std = (X_test - min_key)/(max_key - min_key)
>>> model.fit(X_train_std, y_train_std, epochs = 100)

Epoch 1/100
238/238 [==============================] - 1s 970us/step - loss: 168.8257
Epoch 2/100
238/238 [==============================] - 0s 1ms/step - loss: 4.6773A: 0s - loss: 5.8
Epoch 3/100
238/238 [==============================] - 0s 821us/step - loss: 1.2054
Epoch 4/100
238/238 [==============================] - 0s 842us/step - loss: 0.4222
Epoch 5/100
238/238 [==============================] - 0s 781us/step - loss: 0.1056
Epoch 6/100
238/238 [==============================] - 0s 851us/step - loss: 0.0459

...

Epoch 96/100
238/238 [==============================] - 0s 736us/step - loss: 4.2894e-04
Epoch 97/100
238/238 [==============================] - 0s 741us/step - loss: 5.0023e-04
Epoch 98/100
238/238 [==============================] - 0s 720us/step - loss: 0.0046
Epoch 99/100
238/238 [==============================] - 0s 749us/step - loss: 0.0036
Epoch 100/100
238/238 [==============================] - 0s 812us/step - loss: 0.0189
<tensorflow.python.keras.callbacks.History at 0x24611ae5910>
>>> pred = (model.predict(X_test_std) * (max_key - min_key)) + min_key
>>> pred = pred.reshape(pred.shape[0])
>>> print("Accuracy:", np.sqrt(np.sum((y_test - pred)**2))/len(y_test))
Accuracy: 0.03539701825569002
result_DF = pd.DataFrame({"predict":pred, "label":y_test})
result_DF["gap"] = result_DF["label"] - result_DF["predict"]
result_DF

  • 중복을 제거하여 데이터의 양을 953개에서 9,493개로 늘렸다.
  • 그로 인해 Accuracy가 0.9916에서 0.0353으로 감소하여, 정확도가 보다 올라갔다.
  • 이상치가 존재하지 않는 데이터이므로, 최소-최대 스케일 변환(min-max scaling)을 이용해 표준화를 시켰다. 그로 인해, Accuracy가 크게 변하지는 않았으나, 이전에 비해 손실 값이 빠르게 0에 수렴하는 것을 볼 수 있다.
  • 활성화 함수를 relu가 아닌 elu를 사용하였다. 성능 차이가 그리 크지는 않으나, 손실 값과 Accuracy에 긍정적인 영향을 미쳤다.
  • 네트워크의 노드 수와 Layer의 수를 바꿨다.

 

 

 

 지금까지 변수가 2개인 데이터 셋을 학습시키는 과정을 해보았다. 숨어있는 패턴이 복잡하고 변수의 수가 늘어났더니, 처음 보는 영역에 있는 데이터를 제대로 분류하지 못하는 현상이 발생하였다.

 이 때는 학습 데이터셋에 시험 데이터셋과 유사한 데이터 셋을 포함시키는 것이 가장 좋은 해결 방법이다. 위처럼 시험 데이터 셋과 학습 데이터 셋이 중복되지 않는다 할지라도, 유사한 영역에 있는 경우 제대로 예측하는 것을 볼 수 있다.

728x90
반응형
728x90
반응형

 이전 포스트에서 데이터 셋을 표준 정규분포로 만들어 더 쉽게 데이터셋을 모델에 학습시켜보았다. 그러나, 패턴의 단순함에 비해 여전히 정확도(Accuracy)가 원하는 수준까지 나오질 않는다. 대체 왜 그럴까?

 이번 포스트에서는 경험적 하이퍼 파라미터 튜닝 방법을 사용하여, 하이퍼 파라미터를 튜닝해보도록 하겠다. 제대로 된 하이퍼 파라미터 튜닝은 추후 자세히 다루도록 하겠다.

 

 

하이퍼 파라미터 튜닝(HyperParameter Tuning)

  • 머신러닝을 공부하다 보면 하이퍼 파라미터라는 단어와 파라미터라는 단어가 반복해서 등장하는 것을 볼 수 있다. 
  • 파라미터(Parmeter)라는 단어는 코딩을 하다 보면 자주 보이는, 수정할 수 있는 값인데, 갑자기 왜 하이퍼 파라미터라는 값이 등장할까? 또, 왜 파라미터는 수정할 수 없는 값이라고 할까?
  • 머신러닝에서의 파라미터는 가중치(Weight), 편향(Bias) 같은 학습 과정에서 모델이 자동으로 업그레이드하며 갱신하는 값을 가리킨다.
  • 파라미터는 학습 도중 머신이 알아서 바꿔가는 것이므로, 연구자가 손 델 수 있는 값이 아니다.
  • 머신러닝에서 하이퍼 파라미터는 그 외 연구자가 수정할 수 있는 값으로, 학습률, Optimizer, 활성화 함수, 손실 함수 등 다양한 인자들을 가리킨다.
  • 이 값들을 손보는 이유는 모델이 학습에 사용한 데이터 셋의 형태를 정확히 알지 못하고, 데이터 셋의 형태에 따라 이들을 사용하는 방법이 바뀌기 때문이다.

 

 

 

 

1. 하이퍼 파라미터 튜닝을 해보자.

  • 우리는 이미 우리가 만들어낸 데이터 셋의 형태를 알고 있다.
  • 우리가 만들어낸 데이터셋은 선형 데이터셋인데, 우리는 활성화 함수로 은닉층에서 ReLU를 사용하였다.
  • 이번엔 모든 활성화 함수를 linear로 만들어 학습시켜보자.
# Import Module
import pandas as pd
import numpy as np
from tensorflow import keras
from tensorflow.keras.layers import Dense



# Dataset Setting
def f(x):
    return x + 10
    
# Data set 생성
np.random.seed(1234)   # 동일한 난수가 나오도록 Seed를 고정한다.
X_train = np.random.randint(0, 100, (100, 1))
X_test = np.random.randint(100, 200, (20, 1))

# Label 생성
y_train = f(X_train)
y_test = f(X_test)


# Model Setting
model = keras.Sequential()
model.add(Dense(16, activation='linear'))
model.add(Dense(1, activation='linear'))


# Compile: 학습 셋팅
opt = keras.optimizers.Adam(learning_rate=0.01)
model.compile(optimizer=opt, loss = 'mse')


# 특성 스케일 조정
mean_key = np.mean(X_train)
std_key = np.std(X_train)

X_train_std = (X_train - mean_key)/std_key
y_train_std = (y_train - mean_key)/std_key
X_test_std = (X_test - mean_key)/std_key
# 학습
>>> model.fit(X_train_std, y_train_std, epochs = 100)

Epoch 1/100
4/4 [==============================] - 0s 2ms/step - loss: 2.5920
Epoch 2/100
4/4 [==============================] - 0s 997us/step - loss: 1.5766
Epoch 3/100
4/4 [==============================] - 0s 2ms/step - loss: 0.7499
Epoch 4/100
4/4 [==============================] - 0s 2ms/step - loss: 0.3371
Epoch 5/100
4/4 [==============================] - 0s 2ms/step - loss: 0.0817
Epoch 6/100
4/4 [==============================] - 0s 2ms/step - loss: 0.0059

...

Epoch 95/100
4/4 [==============================] - 0s 1ms/step - loss: 6.0676e-15
Epoch 96/100
4/4 [==============================] - 0s 1ms/step - loss: 6.2039e-15
Epoch 97/100
4/4 [==============================] - 0s 2ms/step - loss: 6.4773e-15
Epoch 98/100
4/4 [==============================] - 0s 2ms/step - loss: 5.6185e-15
Epoch 99/100
4/4 [==============================] - 0s 1ms/step - loss: 6.5939e-15
Epoch 100/100
4/4 [==============================] - 0s 1ms/step - loss: 6.7939e-15
<tensorflow.python.keras.callbacks.History at 0x26e75c29e80>
# label과 test set을 비교해보자.
pred = model.predict(X_test_std.reshape(X_test_std.shape[0]))
pred_restore = pred * std_key + mean_key
predict_DF = pd.DataFrame({"predict":pred_restore.reshape(pred_restore.shape[0]), "label":y_test.reshape(y_test.shape[0])})
predict_DF["gap"] = predict_DF["predict"] - predict_DF["label"]
predict_DF

# 정확도(Accuracy)를 보자
>>> print("Accuracy:", np.sqrt(np.mean((pred_restore - y_test)**2)))
Accuracy: 1.0789593218788873e-05
  • 고작, 은닉층의 활성화 함수만 바꿨을 뿐인데, 이전보다 훨씬 좋은 결과가 나왔다.
  • 패턴을 거의 완벽하게 찾아내었으며, 정확도(Accuracy) 역시 0.000010789(e-05는 $10^{-5}$을 하라는 소리다.)로 거의 0에 근사하게 나왔다.

 

 

 

 

2. 정리

  • 위 결과를 보면, 아무리 단순한 패턴이라 할지라도, 그 데이터 셋의 형태를 반영하지 못한다면, 정확히 그 결과를 찾아내지 못할 수 있다는 것을 알 수 있다.
  • 인공지능은 흔히들 생각하는 빅데이터를 넣으면, 그 안에 숨어 있는 패턴이 자동으로 나오는 마법의 상자가 아니라, 연구자가 그 데이터에 대한 이해를 가지고 여러 시도를 해, 제대로 된 설계를 해야만 내가 원하는 제대로 된 패턴을 찾아낼 수 있는 도구다.
  • 그러나, 실전에서는 지금처럼 우리가 이미 패턴을 알고 있는 경우는 없기 때문에 다양한 도구를 이용해서, 데이터를 파악하고, 적절한 하이퍼 파라미터를 찾아낸다.
  • 넣을 수 있는 모든 하이퍼 파라미터를 다 넣어보는 "그리드 서치(Greed search)"나 랜덤 한 값을 넣어보고 지정한 횟수만큼 평가하는 "랜덤 서치(Random Search)", 순차적으로 값을 넣어보고, 더 좋은 해들의 조합에 대해서 찾아가는 "베이지안 옵티마이제이션(Bayesian Optimization)" 등 다양한 방법이 있다.
  • 같은 알고리즘이라 할지라도, 데이터를 어떻게 전처리하느냐, 어떤 활성화 함수를 쓰느냐, 손실 함수를 무엇을 쓰느냐 등과 같은 다양한 요인으로 인해 다른 결과가 나올 수 있으므로, 경험을 많이 쌓아보자.
728x90
반응형
728x90
반응형

 이전 포스트에서 만든 모델의 결과는 그리 나쁘진 않았으나, 패턴이 아주 단순함에도 쉽게 결과를 찾아내지 못했고, 학습에 자원 낭비도 많이 되었다.

 왜 그럴까?

 

 

특성 스케일 조정

  • 특성 스케일 조정을 보다 쉽게 말해보면, 표준화라고 할 수 있다.
  • 이번에 학습한 대상은 변수(다른 정보에 대한 벡터 성분)가 1개밖에 없어서 그나마 나았으나, 만약, 키와 몸무게가 변수로 주어져 벡터의 원소로 들어갔다고 생각해보자.
  • 키나 몸무게는 그 자리 수가 너무 큰 값이다 보니, 파라미터 역시 그 값의 변화가 지나치게 커지게 되고, 그로 인해 제대로 된 결과를 찾지 못할 수 있다.
  • 또한 키와 몸무게는 그 단위마저도 크게 다르다 보니, 키에서 160이 몸무게에서의 160과 같다고 볼 수 있다. 그러나 모두가 알다시피 키 160은 대한민국 남녀 성인 키 평균에 못 미치는 값이며, 몸무게 160은 심각한 수준의 비만이다. 전혀 다른 값임에도 이를 같게 볼 위험이 있다는 것이다.
  • 이러한 표준화가 미치는 영향은 손실 함수에서 보다 이해하기 쉽게 볼 수 있는데, 이로 인해 발생하는 문제가 바로 경사 하강법의 zigzag 문제다.

  • $w_1$과 $w_2$의 스케일 크기가 동일하다면(값의 범위가 동일), 손실 함수가 보다 쉽게 최적해에서 수렴할 수 있다.

  • $w_1$과 $w_2$의 스케일 크기가 많이 다르다면, 손실 함수는 쉽게 최적해에 수렴하지 못한다.

 

 

 

 

1. 특성 스케일 조정 방법

  • 특성 스케일 조정 방법은 크게 2가지가 있다.
  • 첫 번째는 특성 스케일 범위 조정이고, 두 번째는 표준 정규화를 하는 것이다.

 

A. 특성 스케일 범위 조정

  • 특성 스케일 범위 조정은 말 그대로, 값의 범위를 조정하는 것이다.
  • 바꿀 범위는 [0, 1]이다.
  • 이 방법에는 최솟값과 최댓값이 사용되므로 "최소-최대 스케일 변환(min-max scaling)"이라고도 한다.
  • 공식은 다음과 같다.

$$ x_{norm} = \frac{x_i-x_{min}}{x_{max}-x_{min}} $$

  • 위 공식에서 $x_i$는 표준화를 할 대상 array다.
  • 범위 축소에 흔히들 사용되는 해당 방법은, 가장 쉽게 표준화하는 방법이지만, 값이 지나치게 축소되어 존재하던 이상치가 사라져 버릴 수 있다.
  • 특히나, 이상치가 존재한다면, 이상치보다 작은 값들을 지나치게 좁은 공간에 모아버리게 된다.

 

B. 표준 정규분포

  • 표준 정규분포는 평균 = 0, 표준편차 = 1로 바꾸는 가장 대표적인 표준화 방법이다.
  • 공식은 다음과 같다.

$$ x_{std} = \frac{x_i - \mu_x}{\sigma_x} $$

  • 위 공식에서 $x_i$는 표준화 대상 array다.
  • 표준 정규분포로 만들게 되면, 평균 = 0, 표준편차 = 1로 값이 축소되게 되지만, 여전히 이상치의 존재가 남아 있기 때문에 개인적으론 표준 정규분포로 만드는 것을 추천한다.

 

 특성 스케일 조정에서 가장 중요한 것은, 조정의 기준이 되는 최솟값, 최댓값, 평균, 표준편차는 Train Dataset의 값이라는 것이다. 해당 방법 사용 시, Train Dataset을 기준으로 하지 않는다면, Test Dataset의 값이 Train Dataset과 같아져 버릴 수 있다.

 

 

 

 

2. 표준 정규분포를 이용해서 특성 스케일을 조정해보자.

# Import Module
import pandas as pd
import numpy as np
from tensorflow import keras
from tensorflow.keras.layers import Dense



# Dataset Setting
def f(x):
    return x + 10
    
# Data set 생성
np.random.seed(1234)   # 동일한 난수가 나오도록 Seed를 고정한다.
X_train = np.random.randint(0, 100, (100, 1))
X_test = np.random.randint(100, 200, (20, 1))

# Label 생성
y_train = f(X_train)
y_test = f(X_test)


# Model Setting
model = keras.Sequential()
model.add(Dense(16, activation='relu'))
model.add(Dense(1, activation='linear'))


# Compile: 학습 셋팅
opt = keras.optimizers.Adam(learning_rate=0.01)
model.compile(optimizer=opt, loss = 'mse')
mean_key = np.mean(X_train)
std_key = np.std(X_train)

X_train_std = (X_train - mean_key)/std_key
y_train_std = (y_train - mean_key)/std_key
X_test_std = (X_test - mean_key)/std_key
  • 앞의 모델 생성 및 Compile 단계까진 동일하나, 뒤에 표준화 과정이 추가된다.
  • Train Dataset의 평균과 표준편차는 test의 Dataset이 나중에 주어져 현재 할 수 없거나, predict의 결과 원상 복귀에 사용되므로, 따로 Scalar 값을 빼놓자.
>>> model.fit(X_train_std, y_train_std, epochs = 100)

Epoch 1/100
4/4 [==============================] - 0s 1ms/step - loss: 0.5749
Epoch 2/100
4/4 [==============================] - 0s 1ms/step - loss: 0.2483
Epoch 3/100
4/4 [==============================] - 0s 3ms/step - loss: 0.0814
Epoch 4/100
4/4 [==============================] - 0s 2ms/step - loss: 0.0217
Epoch 5/100
4/4 [==============================] - 0s 2ms/step - loss: 0.0378
Epoch 6/100
4/4 [==============================] - 0s 1ms/step - loss: 0.0402

...

Epoch 95/100
4/4 [==============================] - 0s 2ms/step - loss: 4.5394e-06
Epoch 96/100
4/4 [==============================] - 0s 1ms/step - loss: 5.2252e-06
Epoch 97/100
4/4 [==============================] - 0s 2ms/step - loss: 5.7370e-06
Epoch 98/100
4/4 [==============================] - 0s 2ms/step - loss: 5.9242e-06
Epoch 99/100
4/4 [==============================] - 0s 2ms/step - loss: 5.8228e-06
Epoch 100/100
4/4 [==============================] - 0s 2ms/step - loss: 5.6276e-06
<tensorflow.python.keras.callbacks.History at 0x234ff82a520>
  • 이전에 비해 적은 epochs(=100)로 빠르게 손실 값이 0에 수렴하는 것을 볼 수 있다.
  • 결과를 보도록 하자.
pred = model.predict(X_test_std.reshape(X_test_std.shape[0]))

# 원상복구
pred_restore = pred * std_key + mean_key
predict_DF = pd.DataFrame({"predict":pred_restore.reshape(pred_restore.shape[0]), "label":y_test.reshape(y_test.shape[0])})
predict_DF["gap"] = predict_DF["predict"] - predict_DF["label"]
predict_DF

# RMSE로 Accuracy를 확인해보자.
>>> print("Accuracy:", np.sqrt(np.mean((pred_restore - y_test)**2)))
Accuracy: 0.07094477537881977
  • 이전에 비해 확실히 빠르게 최적화가 되었으나, 여전히 예측값은 원하는 수준에 미치지 못한다.
  • 굉장히 단순한 패턴임에도 불구하고, 아직까지 약간 다르다.

 

 

  이 정도로 단순한 패턴이라면, 예측값과 실제값의 차이가 거의 없어야 하나, 아직까지 차이가 크다는 생각이 든다. 다음 포스트에서는 최종적으로 한 가지를 수정하고, 해당 코드를 최종적으로 정리해보도록 하자.

728x90
반응형
728x90
반응형

 지난 포스트에서 작성한 코드들을 간략히 정리해보고, 본격적으로 학습 및 결과 평가를 해보자.

 

 

학습 목표

  • 분석가가 알고 있는 패턴$f(x) = x + 10$에 대한 데이터를 생성하고, 그 패턴을 찾아내는 모델을 만들어보자.
  • Input은 Node 1개, Output도 Node 1개인 연속형 데이터를 생성한다.

 

 

 

1. 지난 코드 정리

# Import Module
import pandas as pd
import numpy as np
from tensorflow import keras
from tensorflow.keras.layers import Dense



# Dataset Setting
def f(x):
    return x + 10
    
# Data set 생성
np.random.seed(1234)   # 동일한 난수가 나오도록 Seed를 고정한다.
X_train = np.random.randint(0, 100, (100, 1))
X_test = np.random.randint(100, 200, (20, 1))

# Label 생성
y_train = f(X_train)
y_test = f(X_test)



# Model Setting
model = keras.Sequential()
model.add(Dense(16, activation='relu'))
model.add(Dense(1, activation='linear'))



# Compile: 학습 셋팅
opt = keras.optimizers.Adam(learning_rate=0.01)
model.compile(optimizer=opt, loss = 'mse')

 

 

 

2. 학습 시작

>>> model.fit(X_train, y_train, epochs = 100)
  • model.fit(): model에 대해 학습을 시작한다.
  • fit() 안에는 train dataset, train data label, validation dataset 등이 들어갈 수 있다.
  • validation dataset은 성능 향상에 도움이 되나, 꼭 필요한 것은 아니다.
  • epochs은 전체 train set을 몇 번 학습할 것인가를 의미한다.
  • 해당 코드를 실행하면 다음과 같은 문자들이 출력된다.
Epoch 1/100
4/4 [==============================] - 0s 2ms/step - loss: 955.4686
Epoch 2/100
4/4 [==============================] - 0s 998us/step - loss: 342.0951
Epoch 3/100
4/4 [==============================] - 0s 2ms/step - loss: 51.7757
Epoch 4/100
4/4 [==============================] - 0s 1ms/step - loss: 43.6929
Epoch 5/100
4/4 [==============================] - 0s 2ms/step - loss: 95.3333
Epoch 6/100
4/4 [==============================] - 0s 2ms/step - loss: 76.1808
Epoch 7/100
4/4 [==============================] - 0s 1ms/step - loss: 29.2552
Epoch 8/100
4/4 [==============================] - 0s 2ms/step - loss: 21.1532

...

Epoch 94/100
4/4 [==============================] - 0s 2ms/step - loss: 4.9562
Epoch 95/100
4/4 [==============================] - 0s 1ms/step - loss: 5.3142
Epoch 96/100
4/4 [==============================] - 0s 996us/step - loss: 5.0884
Epoch 97/100
4/4 [==============================] - 0s 2ms/step - loss: 4.9754
Epoch 98/100
4/4 [==============================] - 0s 2ms/step - loss: 5.3013
Epoch 99/100
4/4 [==============================] - 0s 1ms/step - loss: 5.0656
Epoch 100/100
4/4 [==============================] - 0s 1ms/step - loss: 4.4677
<tensorflow.python.keras.callbacks.History at 0x12fe8f0f520>
  • 위 내용을 history라고 하며, 따로 history를 지정하지 않아도 출력된다.
  • loss는 손실 값을 의미하며, 해당 값이 최소화되는 위치를 찾는 것이 목적이다.
  • 일반적으로 loss가 0에 근사 해지는 것을 목적으로 한다.
  • 만약 loss가 0에서 지나치게 먼 값에서 수렴한다면, 모델에 들어간 인자들(HyperParameter)이 잘못 들어간 것일 가능성이 매우 높으므로, 모델을 수정하길 바란다.
  • loss가 지금처럼 0에 가깝게 내려 가긴 했으나, 그 정도가 0에 미치지 못한 경우 단순하게 epoch를 늘려보자.
>>> model.fit(X_train, y_train, epochs = 500)

Epoch 1/500
4/4 [==============================] - 1s 2ms/step - loss: 9528.2801
Epoch 2/500
4/4 [==============================] - 0s 2ms/step - loss: 7191.2032
Epoch 3/500
4/4 [==============================] - 0s 2ms/step - loss: 4662.3104
Epoch 4/500
4/4 [==============================] - 0s 1ms/step - loss: 2927.8638
Epoch 5/500
4/4 [==============================] - 0s 2ms/step - loss: 1738.3485
Epoch 6/500
4/4 [==============================] - 0s 2ms/step - loss: 877.1409

...

Epoch 495/500
4/4 [==============================] - 0s 2ms/step - loss: 0.0126
Epoch 496/500
4/4 [==============================] - 0s 1ms/step - loss: 0.0139
Epoch 497/500
4/4 [==============================] - 0s 1ms/step - loss: 0.0183
Epoch 498/500
4/4 [==============================] - 0s 1ms/step - loss: 0.0180
Epoch 499/500
4/4 [==============================] - 0s 2ms/step - loss: 0.0168
Epoch 500/500
4/4 [==============================] - 0s 2ms/step - loss: 0.0229
  • Epochs를 500까지 올렸으나, loss 값이 원하는 만큼 나오지 않는 것을 볼 수 있다.

 

 

 

 

3. 결과를 확인해보자.

  • 결과 확인은 상당히 단순하면서도 새로운 알고리즘을 만들어내야 할 필요성이 있는 영역이다.
>>>  model.predict(X_test.reshape(X_test.shape[0]))

array([[195.04504 ],
       [151.02899 ],
       [111.01437 ],
       [124.019135],
       [113.015114],
       [140.02496 ],
       [122.0184  ],
       [183.04066 ],
       [129.02095 ],
       [136.02351 ],
       [206.04909 ],
       [178.03883 ],
       [174.03737 ],
       [132.02205 ],
       [166.03447 ],
       [194.0447  ],
       [118.01694 ],
       [154.03008 ],
       [134.02278 ],
       [204.04832 ]], dtype=float32)
  • model.predict(array): 들어간 array에 대하여 모델의 파라미터(가중치)들이 순방향으로 연산되어 나온 결과가 출력된다.
  • 모델에 Input되는 데이터와 predict에 들어가는 데이터의 모양은 조금 다르다.
# 모델 Input 시
>>> X_test.shape
(20, 1)

# Predict Input 시
>>> X_test.reshape(X_test.shape[0]).shape
(20,)
  • 모델 학습 시엔 데이터를 행 단위로 떨어뜨려 넣었다면, predict에선 위와 같이 넣어줘야 한다.

 

test set의 Label과 비교해보자.

  • predict 결과와 Label 데이터인 y_test를 비교해보자.
pred = model.predict(X_test.reshape(X_test.shape[0]))
predict_DF = pd.DataFrame({"predict":pred.reshape(pred.shape[0]), "label":y_test.reshape(y_test.shape[0])})
predict_DF["gap"] = predict_DF["predict"] - predict_DF["label"]
predict_DF

  • predict와 label이 어느 정도 근사하게 나오긴 하였으나, 얼마나 근사하게 나왔는지 보기가 어렵다.
  • 모델을 평가하기 쉽도록, RMSE를 사용하여 Scalar값(숫자 1개)으로 바꿔주자.
>>> print("Accuracy:", np.sqrt(np.mean((pred - y_test)**2)))
Accuracy: 0.10477323661232778
  • 0.1047로 나름 나쁘지 않은 결과가 나오긴 하였으나, $f(x) = x + 10$ 같이 굉장히 단순한 패턴을 만족스러운 수준으로 찾아내지 못했다.
  • 게다가 패턴도 지나치게 단순한데, epochs가 500이나 사용되어, 생각보다 많은 자원이 낭비되었다.

 

 

 

 이번 포스트에서는 널리 알려진 방식대로 학습을 시켜보았다. 그러나, 아주 단순한 패턴임에도 불구하고, 쉽게 찾아내질 못하였으며, 그 결과도 원하는 것에 미치지 못했다.

 다음 포스트에서는 어디가 잘못되었는지 찾아내 이를 수정해보도록 하자.

728x90
반응형
728x90
반응형

 지난 포스트에서 데이터 셋에 대해 간략히 설명해보았다. 이번 포스트부터 본격적으로 텐서플로우를 사용해서, 내가 찾아내고 싶은 알고리즘을 찾아내 보자.

 

 

학습 목표

  • 분석가가 알고 있는 패턴으로 데이터를 생성하고, 그 패턴을 찾아내는 모델을 만들어보자.
  • Input이 1개, Output이 1개인 연속형 데이터에서 패턴을 찾아보자.

 

 

 

1. 데이터 셋 생성

  • 패턴: $f(x) = x + 10$
# Module 설정
import pandas as pd
import numpy as np
from tensorflow import keras
from tensorflow.keras.layers import Dense
def f(x):
    return x + 10
    
# Data set 생성
np.random.seed(1234)   # 동일한 난수가 나오도록 Seed를 고정한다.
X_train = np.random.randint(0, 100, (100, 1))
X_test = np.random.randint(100, 200, (20, 1))

# Label 생성
y_train = f(X_train)
y_test = f(X_test)

 

데이터 셋 생성 코드의 함수 설명

  1. np.random.seed(int):  난수(랜덤 한 데이터) 생성 시, 그 값은 생성할 때마다 바뀌게 된다. 데이터 셋이 바뀌게 되면, 일관된 결과를 얻기가 힘들어, 제대로 된 비교가 힘들어지므로, 난수를 생성하는 방식을 고정시킨다. 이를 시드 결정(Set seed)이라 하며, 숫자는 아무 숫자나 넣어도 상관없다.
  2. np.random.randint(시작 int, 끝 int, shape): 시작 숫자(포함)부터 끝 숫자(미포함)까지 shape의 형태대로 array를 생성한다.

 

데이터 셋 생성 코드 설명

  1. Train set은 0~100까지의 숫자를 랜덤으로 (100, 1)의 형태로 추출하였다.
  2. Test set은 100~200까지의 숫자로 랜덤으로 (20, 1)의 형태로 추출했다. 여기서 값은 Train set과 절대 겹쳐선 안된다.
  3. Label 데이터인 y_train과 y_test는 위에서 설정된 함수 f(x)에 의해 결정되었다.

 

  • train 데이터 생김새(가시성을 위해 10개까지만 출력)
# train Dataset을 10개까지만 가져와보자
>>> X_train[:10]

array([[47],
       [83],
       [38],
       [53],
       [76],
       [24],
       [15],
       [49],
       [23],
       [26]])
       
>>> X_train.shape
(100, 1)
  • 생성된 데이터 셋의 형태는 "(데이터 셋 수, 변수의 수)"라고 인지해도 좋다.
  • 여기서 "변수의 수"는 "데이터 하나의 벡터 크기"라고 생각하는 것이 더 적합하다.
  • 기본적으로 Tensorflow에 Input 되고 Output 되는 데이터의 형태는 이렇다고 생각하자.

 

 

 

 

2. 모델 생성하기

  • tensorflow를 사용해 모델을 생성하는 경우, tensorflow가 아닌 keras를 사용하게 된다.
  • 위에서 tensorflow의 기능을 가져올 때, 아래와 같은 코드로 가져왔다.
  • from tensorflow import keras
  • 이는, tensorflow라는 프레임워크에서 keras라는 모듈을 가지고 온다는 의미이다.
  • keras는 추후 설명하게 될지도 모르지만, 모델 생성 및 학습에 있어 직관적으로 코드를 짤 수 있게 해 주므로, 쉽게 tensorflow를 사용할 수 있게 해 준다.
  • 물론, keras와 tensorflow는 태생적으로 서로 다른 프레임워크이므로, 이 둘이 따로 에러를 일으켜, 에러 해결을 어렵게 한다는 단점이 있긴 하지만, 그걸 감안하고 쓸만한 가치가 있다.
model = keras.Sequential()
model.add(Dense(16, activation='relu'))
model.add(Dense(1, activation='linear'))
  • keras를 사용해서 모델을 만드는 방법은 크게 2가지가 있다.
  • 하나는 위 같이 add를 이용해서 layer를 하나씩 추가해 가는 방법이 있고
model = keras.Sequential([
    Dense(16, activation='relu'),
    Dense(1, activation='linear')
])
  • 이렇게 keras.Sequential([]) 안에 층(layer)을 직접 넣는 방법이 있다.
  • 처음 방법처럼 add를 사용하는 방법은 API 사용 방법이고, 아래와 같이 층을 Sequential([])에 직접 넣는 방식은 Layer 인스턴스를 생성자에게 넘겨주는 방법이라 하는데, 전자인 API를 사용하는 방법을 개인적으로 추천한다.
  • 그 이유는 다중-아웃풋 모델, 비순환 유향 그래프, 레이어 공유 모델 같이 복잡한 모델 정의 시, 매우 유리하기 때문으로, 이는 나중에 다루겠으나, 이 것이 Tensorflow의 장점이다.

 

모델 생성 코드 함수 설명

  1. keras.Sequential(): 순차 모델이라 하며, 레이어를 선형으로 연결해 구성한다. 일반적으로 사용하는 모델로 하나의 텐서가 입력되고 출력되는 단일 입력, 단일 출력에 사용된다. 다중 입력, 다중 출력을 하는 경우나, 레이어를 공유하는 등의 경우엔 사용하지 않는다.
  2. model.add(layer): layer를 model에 층으로 쌓는다. 즉, 위 모델은 2개의 층을 가진 모델이다.
  3. Dense(노드 수, 활성화 함수): 완전 연결 계층으로, 전, 후 층을 완전히 연결해주는 Layer다. 가장 일반적으로 사용되는 Layer다.

 

모델 생성 코드 설명

  1. 해당 모델은 Input 되는 tensor도 1개 Output 되는 tensor도 1개이므로, Sequential()로 모델을 구성했다.
  2. 은닉층에는 일반적으로 ReLU 활성화 함수가 사용된다고 하니, ReLU를 넣었다.
  3. 출력층에는 출력 결과가 입력 값과 같은 노드 1개이므로, 노드 1개로 출력층을 만들었다. 
  4. 일반적으로 Node의 수를 $2^n$으로 해야 한다고 하지만, 크게 상관없다는 말이 있으므로, 굳이 신경 쓰지 않아도 된다. 처음엔 자기가 넣고 싶은 값을 넣다가, 성능이 안 나온다 싶으면 바꿔보는 수준이니 크게 신경 쓰지 말자.
  5. 사용된 활성화 함수(activation)는 일반적으로 은닉층에 ReLU를 넣고, 연속형 데이터이므로 출력층에 Linear를 넣어보았다.

 

 

 

 

3. 모델 컴파일하기

  • 컴파일은 모델을 학습시키기 전에 어떤 방식으로 학습을 시킬지를 설정하는 과정이다.
opt = keras.optimizers.Adam(learning_rate=0.01)
model.compile(optimizer=opt, loss = 'mse')

 

코드 설명

  1. keras.optimizers.Adam(): 최적화에 사용할 함수를 위처럼 외부에서 만들어서 넣는 경우, 학습률, 모멘텀 같은 인자들을 입맛에 맞게 바꿀 수 있다.
  2. model.complie(): 학습 방식을 설정한다.

 

compile은 기본적으로 3가지 인자를 입력으로 받는다.

  1. optimizer: 최적화하는 방법으로, 경사 하강법(GD)을 어떤 방법을 통해 사용할지를 결정한다. 일반적으로 Adam이 많이 사용된다.
  2. loss: 손실 함수를 설정한다. 일반적으로 연속형 데이터라면 제곱 오차 시리즈를, 분류 데이터라면 교차 엔트로피 오차 시리즈를 사용한다.
  3. metric: 기준이 되는 것으로, 분류를 할 때 주로 사용한다.
  • 손실 함수와 최적화에 관심이 있다면 다음 포스트(손실 함수, 최적화)를 참고하길 바란다.

 

 

 

 자, 지금까지 학습을 위한 모델 세팅을 완료하였다. 다음 포스트에서는 위 코드들을 깔끔하게 정리하고, 실제 학습을 해보겠다.

728x90
반응형
728x90
반응형

 Tensorflow를 사용하는 사람 중 상당 수가 Github에서 다른 사람들이 어떤 목적을 위해 만들어놓은 모델을 그저 가져오거나, 남이 만든 모델에서 노드 크기를 수정하거나, 상황에 맞게 레이어를 바꿔보고, 내가 인공지능을 사용할 수 있다고 생각하는 경우가 많다.

 마치 통계 분석을 할 때, "서로 다른 두 집단이 있고, 그 집단에 대한 평균을 비교해보고 싶다면, t-test를 사용해야한다."라 생각하듯, 머신러닝에 접근하면, 인공지능을 단순한 마법의 상자로 생각해버릴 수 있다.

 흔히들 인공지능을 "내가 무언가를 넣으면, 원리는 잘 모르겠지만, 정답이 나오는 마법의 상자"라고 생각하는 경향이 있는데, 인공지능은 단순한 블랙박스가 아닌, 사용자가 의도를 가지고 설계한 것에 맞는 결과를 도출해주는 알고리즘이다.

 그러므로, 제대로 인공지능을 다루고자 한다면, 인공지능이 할 수 있는 영역 안에서 내가 원하는 결과를 이끌어낼 수 있어야 한다.

 

 

 

 

1. 데이터셋 생성하기

  • 분석가라면, 상황에 맞는 실험용 데이터 셋 만들기는 기본 중 기본이다.
  • 데이터셋은 크게 훈련(Train), 시험(Tes), 검증(Validation) 데이터 셋으로 나뉜다.

 

훈련 데이터 셋(Train Dataset)

  • 신경망 훈련 시 사용되는, 모델 학습 용 데이터 셋으로, 수능을 보기 위해 공부하는 문제집에 해당한다.
  • 과도하게 훈련 데이터셋을 학습시키는 경우, 과적합(Overfitting) 현상이 발생하여, 훈련 데이터 셋은 잘 분류하나, 시험 데이터 셋이나 실제 데이터에는 적합하지 않을 수 있다.
  • 훈련 데이터 셋은 모델의 기준이 된다!

 

시험 데이터 셋(Test Dataset)

  • 모델의 성능을 최종적으로 평가하기 위한 데이터 셋으로 실제 데이터 셋이다. 고등학교의 최종 목적 시험인 수능에 해당한다.
  • 훈련 데이터 셋과 시험 데이터 셋은 중첩되지 않는 것이 좋다.
  • 예를 들어, 데이터를 날짜별로 뽑아낼 수 있다면, 시험 데이터 셋은 다른 날짜의 데이터 셋을 사용하는 것이 좋다.
  • 시험 데이터 셋과 모델이 예측한 결과를 비교해 정확도(Accuarcy), 정밀도(Precision), 재현율(Recall), F1 점수를 계산하여, 모델이 얼마나 잘 만들어졌는지를 확인해볼 수 있다.

 

검증 데이터 셋(Validation Dataset)

  • 학습을 할 때, 학습이 얼마나 잘 돼는지를 평가하는 수단으로, 공부가 잘되었는지를 평가하는 모의고사에 해당 한다.
  • Development Dataset이라고도 불린다.
  • 검증 데이터 셋은 학습 시, 학습된 모델의 성능 평가에 사용되며, 그 결과가 파라미터에 반영된다.
  • 즉, 검증 데이터 셋의 목적은 학습 데이터에 의해 학습된 파라미터 중, 실제 데이터에도 잘 맞을 수 있도록 최적의 파라미터를 찾아낼 수 있도록, 파라미터를 튜닝하기 위해 존재한다고 할 수 있다.
  • 검증 데이더 셋은 학습 데이터 셋에서 분리되며, 때에 따라 검증 데이터 셋을 만들지 않고, 전부 훈련 데이터에 사용할 수도 있다.
  • 물론, 검증 데이터 셋을 사용하는 경우 성능이 더 좋다고 한다.
  • 학습 데이터 셋과 검증 데이터 셋은 그 내용이 중첩되지 말아야 한다. 만약 중첩되는 경우, 이 현상을 leakage라고 한다(학습 데이터 셋과 검증 데이터셋에 교집합 존재).

 

 

 

 

 

2. 과대 적합 피하기

  • 모델이 훈련(Train) 데이터 셋에 대해선 분류가 잘되었으나, 시험(Test) 데이터 셋에 대해 구분을 지나치게 못한다면, 과대 적합일 가능성이 있다.
  • 이는, 훈련 데이터 셋에 모델이 지나치게 맞춰져, 새로운 데이터에 대해 일반화가 되지 못한다는 소리로, 모델이 지나치게 훈련 데이터 셋에만 맞춰진, 모델의 분산이 큰 상태라고 할 수 있다.
  • 이를 해결하는 방법은 다음과 같다.
  1. 훈련 데이터를 늘린다.
  2. Dropout과 같은 규제를 실시하여 복잡도를 줄인다.
  3. 데이터의 차원을 줄인다.
  4. 모델을 보다 간략화시켜 파라미터의 수를 줄인다.
  • 위 내용은 꽤 심도 깊은 영역이므로, 이는 추후 자세히 다루도록 하겠다.

 

 

 

 

3. 데이터 셋의 비율

  • 일반적으로 훈련(Train) 데이터셋과 시험(Test) 데이터셋의 비율은 7:3으로 나누며, 훈련 데이터의 안에서도 학습 도중 모델을 평가할 검증(Validation) 셋을 학습 데이터 셋에서 떼어내기도 한다. 이 경우, 일반적으로 훈련 데이터셋과 검증 데이터셋의 비율을 8:2로 한다고 한다.

  • 그러나, 위 비율은 절대로 절대적인 것이 아니며, 총데이터의 양과 훈련 데이터 셋과 시험 데이터 셋의 형태 차이 등에 따라 그 비율은 위와 크게 다를 수 있다.
  • 학습 데이터는 내가 원하는 특징이 잘 들어가 있는 깔끔한 데이터일 수 있으나, 실제 이 모델을 이용해 분류될 대상인 시험 데이터 셋엔 상당한 노이즈가 들어가 있을 수 있다.
  • 예를 들어, 우리가 학습에 사용한 데이터는 증명사진이지만, 실제 사람들이 이 인공지능에 사용할 사진은 온갖 바탕과 포토샵 등 우리가 학습 시 고려하지 않은 노이즈가 들어가 있을 수 있다.
  • 이를 방지하기 위해, 때에 따라 시험 데이터 셋이 없이 모두 학습 데이터로 사용하거나, 학습 데이터에 의도적으로 노이즈를 부여하기도 한다.
  • 데이터 셋의 양이 매우 적다면(예를 들어, 데이터의 수가 1만에도 못 미친다면), 위 비율대로 나눠도 상관없으나, 데이터의 양이 매우 많다면(데이터의 수가 100만 이상이라면), 테스트 데이터 셋이나 검증 데이터 셋의 비율을 0.1~1%로 잡기도 한다.
  • 즉, Valid Dataset과 Test Datset의 목적은 생소한 데이터를 이용해 모델을 일반화시키기 위한 것이므로, 그 비중이 그리 크지 않아도 된다(물론 Test Dataset은 최종 평가지만, 간접적으로 영향을 미치므로).

 

 

 

 다음 포스트에서는 실제로 데이터 셋을 생성하고, 이를 이용해서 학습을 해보고, 그 성능을 평가해보자!

728x90
반응형
728x90
반응형

 이전 포스트에서 확률적 경사 하강법(SGD)에 대해 알아보았다. 해당 포스트에서 경사 하강법 함수 자체는 단순하므로, 이것만 구현하는 것은 쉬우나, 그 성능을 시각적으로 보기 위해선 학습에 대한 모든 알고리즘을 넣어야 하기 때문에 코드가 꽤 어려워지므로, 시간 낭비라고는 하였다.

 그러나, 이에 대하여 관심 있는 사람이 있을 수 있고, 눈으로 직접 코드가 돌아가는 과정을 본다면, 내용을 이해하기 더 쉬울 수 있으므로, 이를 다룬 책을 찾아 코드를 약간 수정하여, 이해하기 쉽도록 풀어보도록 하겠다. 딥러닝에서 사용되는 다층 퍼셉트론을 사용한 예시는 아니지만, 시각적으로 결과를 볼 수 있으므로 좋은 예시라고 생각한다.

 이번 포스트는 세바스찬 라시카, 바히드 미자리의 "머신러닝 교과서 with 파이썬, 사이킷런, 텐서플로"를 참고하여 작성하였다. 해당 책은 딥러닝을 구성하는 알고리즘에 대해 하나하나 다루고 있는 아주 좋은 책이므로, 꼭 읽어보기 바란다.

 

 

 

아달린 확률적 경사 하강법(AdalineSGD)

1. 아달린(ADAptive LInear NEuron, ADALINE)이란?

  • 스탠퍼드의 Bernard Widrow가 개발한 초기 신경망 모델 중 하나인 아달린은 적응형 선형 뉴런이라고 불리며, 연속 함수(Continous Function)로 손실 함수를 정의하고 최소화한다.
  • 아달린과 퍼셉트론의 차이는 가중치 업데이트를 위한 활성화 함수가 다르다.
    • A. 퍼셉트론: 실제값과 예측값의 활성 함수 출력 값이 다르면, 가중치 업데이트
    • B. 아달린: 실제값과 예측값이 다르면 경사 하강법으로 가중치 업데이트
  • Adaline은 퍼셉트론과 달리 선형 활성화 함수라는 것을 통해, 가중치를 업데이트하는 과정이 들어 있다. 활성화 함수는 초기 퍼셉트론과 마찬가지로 계단 함수를 사용한다.

선형 활성화 함수: $ \phi(w^Tx)=w^Tx $

  • 그러나, Adaline 역시 선형 분리가 가능한 논리 함수(AND, NAND, OR)는 실현할 수 있으나, 비선형 논리 함수(XOR)는 실현 불가능하다.
  • 다층 퍼셉트론처럼 다량의 Adaline으로 네트워크를 구성하는 Madaline을 사용하여 이를 해결하긴 하였으나, 계단 함수를 사용하기 때문에 미분이 불가능해 학습이 불가능하다는 단점이 있어, 다층 퍼셉트론(Multilayer Perceptron)에 밀려 요즘은 쓰지 않는다.
    (선형 분리 문제를 해결한 다층 퍼셉트론이 나오기 전엔 Madaline이 최고의 신경망 모델이었다고 한다.)

  • 아달린은 손실 함수로 앞서 학습하였던 제곱 오차합(SSE)을 사용한다.
    2021/01/29 - [Machine Learning/Basic] - 머신러닝-5.0. 손실함수(1)-제곱오차(SE)와 오차제곱합(SSE)
 

머신러닝-5.0. 손실함수(1)-제곱오차(SE)와 오차제곱합(SSE)

 이전 포스트에서 신경망 학습이 어떠한 원리에 의해 이루어지는지 간략하게 살펴보았다. 이번 포스트에서는 제곱 오차(Square Error)와 제곱 오차를 기반으로 만든 손실 함수 오차제곱합(SSE)에 대

gooopy.tistory.com

 

 

 

 

 

2. 구현해보자!

import numpy as np


class AdalineSGD(object):
    """ADAptive LInear NEuron 분류기
    
    매개변수
    -----------------------
    eta : float
    >>> 학습률 (0.0과 1.0 사이)
    n_iter : int
    >>> 훈련 데이터셋 반복 횟수
    shuffle : bool (default: True)
    >>> True로 설정하면 같은 반복이 되지 않도록 에포크마다 훈련 데이터를 섞는다.
    random_state : int
    >>> 가중치 무작위 초기화를 위한 난수 생성기 시드
    
    속성
    -----------------------
    w_ : 1d-array
    >>> 학습된 가중치
    cost_ : list
    >>> 모든 훈련 샘플에 대해 에포크마다 누적된 평균 비용 함수의 제곱합
    """
    def __init__(self, eta=0.01, n_iter=10, shuffle=True, random_state=None):
        self.eta = eta
        self.n_iter = n_iter
        self.w_initialized = False
        self.shuffle = shuffle
        self.random_state = random_state
        
    def fit(self, X, y):
        """훈련 데이터 학습
        
        매개변수
        -----------------------
        X : {array-like}, shape = [n_samples, n_features]
        >>> n_samples개의 샘플과 n_features개의 특성으로 이루어진 훈련 데이터
        y : array-like, shape = [n_samples]
        >>> 타깃 벡터
        
        
        반환값
        -----------------------
        self : object
        """
        self._initialize_weights(X.shape[1])
        self.cost_ = []
        for i in range(self.n_iter):
            if self.shuffle:
                X, y = self._shuffle(X, y)
            cost = []
            for xi, target in zip(X, y):
                cost.append(self._update_weights(xi, target))
            avg_cost = sum(cost) / len(y)
            self.cost_.append(avg_cost)
        return self
    
    def partial_fit(self, X, y):
        """가중치를 다시 초기화하지 않고 훈련 데이터를 학습"""
        if not self.w_initialized:
            self._initialize_weights(X.shape[1])
        if y.ravel().shape[0] > 1:
            for xi, target in zip(X, y):
                self._update_weights(xi, target)
        else:
            self._update_weights(X, y)
        return self
    
    def _shuffle(self, X, y):
        """훈련 데이터를 섞는다."""
        r = self.rgen.permutation(len(y))
        return X[r], y[r]
    
    def _initialize_weights(self, m):
        """랜덤한 작은 수로 가중치를 초기화합니다."""
        self.rgen = np.random.RandomState(self.random_state)
        self.w_ = self.rgen.normal(loc=0.0, scale=0.01, size=1+m)
        self.w_initialized = True
        
    def _update_weights(self, xi, target):
        """아달린 학습 규칙을 적용해 가중치 업데이트"""
        output = self.activation(self.net_input(xi))
        error = (target - output)
        self.w_[1:] += self.eta * xi.dot(error)
        self.w_[0] += self.eta * error
        cost = 0.5 * error**2
        return cost
    
    def net_input(self, X):
        """최종 입력 계산"""
        return np.dot(X, self.w_[1:]) + self.w_[0]
    
    def activation(self, X):
        """선형 활성화 계산"""
        return X
    
    def predict(self, X):
        """단위 계단 함수를 사용하여 클래스 레이블을 반환"""
        return np.where(self.activation(self.net_input(X)) >= 0.0, 1, -1)
  • 위 코드는 아달린으로 SGD를 구현한 것이다.
  • 아달린은 역전파를 통해 가중치 업데이트가 이루어지는 다층 퍼셉트론과 달리 층 자체에서 가중치를 업데이트하므로, 다층 퍼셉트론에 비해 개념이 단순하므로, 아달린을 사용했다.
# iris Data Import
from sklearn.datasets import load_iris
import pandas as pd

# Data Handling
X = pd.DataFrame(load_iris()["data"]).iloc[0:100, [0,2]].values
y = load_iris()["target"][0:100]
y = np.where(y==0, -1, 1)

# 변수 2개만 분석의 대상으로 사용할 것이므로, 이 2개만 표준화시키자.
X_std = np.copy(X)
X_std[:,0] = (X[:,0] - X[:,0].mean()) / X[:,0].std()
X_std[:,1] = (X[:,1] - X[:,1].mean()) / X[:,1].std()
  • 학습에 사용될 데이터 셋은 붓꽃에 대한 정보가 담긴 iris로 데이터 분석을 해본 사람이라면 꽤 친숙한 데이터일 것이다.
  • 해당 데이터에 대한 자세한 내용을 보고 싶다면, load_iris().keys()를 입력하여, dictionary에 있는 key들을 확인하고, 데이터를 살펴보도록 하자.
# 시각화 함수
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap

def plot_decision_regions(X, y, classifier, resolution=0.02):
    
    # 마커와 컬러맵 설정
    markers = ('s', 'x', 'o', '^', 'v')
    colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    cmap = ListedColormap(colors[:len(np.unique(y))])
    
    # 결정 경계를 그린다.
    x1_min, x1_max = X[:,0].min() - 1, X[:,0].max() + 1
    x2_min, x2_max = X[:,1].min() - 1, X[:,1].max() + 1
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
                           np.arange(x2_min, x2_max, resolution))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    Z = Z.reshape(xx1.shape)
    plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)
    plt.xlim(xx1.min(), xx1.max())
    plt.xlim(xx2.min(), xx2.max())
    
    # 샘플의 산점도를 그린다.
    for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x=X[y == cl, 0],
                    y=X[y == cl, 1],
                    alpha = 0.8,
                    c=colors[idx], 
                    marker=markers[idx], 
                    label=cl,
                    edgecolor='black')
  • 위 학습 코드만으로는 그 결과를 인지하기 어려우므로, 그 과정을 시각화해주는 코드를 생성하였다.
ada = AdalineSGD(n_iter=15, eta=0.01, random_state=1)
ada.fit(X_std, y)

plot_decision_regions(X_std, y, classifier=ada)
plt.title('Adaline - Stochastic Gradient Descent')
plt.xlabel('sepal length [standardized]')
plt.ylabel('petal length [standardized]')
plt.legend(loc='upper left')
plt.show()
plt.plot(range(1, len(ada.cost_) + 1), ada.cost_, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Average Cost')
plt.show()

  • 출력된 결과를 보면, 두 집단(-1, 1)을 선으로 잘 분리한 것을 볼 수 있다(아달린은 선형 분리에 특화되어 있다.)
  • Epoch별 평균 비용(미니 배치 손실 함수의 평균값)이 빠르게 최솟값에 수렴하는 것을 볼 수 있다.

 

 

 

 이번 포스트는 어떤 주제에 대해 설명하기보다는 소개를 목적으로 글을 적었다 보니, 내용상 부족함이 많다. 위 코드는 꽤나 복잡하고, 이해하기가 힘든데, 개인적으로는 굳이 이해하려고 노력하지 않기를 바란다.

 머신러닝에서 굉장히 많이 사용되는 프레임워크인 텐서플로우의 케라스를 사용하여 코드를 작성하면, 코드가 보다 직관적이고, 내가 원하는 형태로 수정하기도 쉽기 때문에 굳이 위 코드를 이해하려 시간을 낭비할 필요는 없다.

 다만, 인공지능 역사에서 아달린이 차지했던 비중이 꽤 되고, 확률적 경사 하강법을 가장 손쉽게 실제 학습에 적용하여, 그 효과를 볼 수 있는 예시로는 위 코드가 가장 좋다고 생각되어 소개해보았다. 

 다음 포스트에서는 이전에 말했던 모멘텀(Momentum)에 대해 다뤄보도록 하겠다.

 

 

[ 참고 자료 ]

www.aistudy.com/neural/model_kim.htm#_bookmark_1a77358

 

초기의 신경망 이론과 모델 : 김대수

< 퍼셉트론 학습 과정 > [단계 1] 연결강도들과 임계값을 초기화한다. wi(0)(0 ≤ i ≤ N - 1) 와 θ 를 임의수 (random number) 로 정한다.  여기에서 wi(t) 는 시각 t 일 때 입력 i 로부터의 연결강도를 나타

www.aistudy.com

blog.naver.com/samsjang/220959562205

 

[6편] 아달라인(Adaline)과 경사하강법(Gradient Descent)

​아달라인(Adaline) 단층 인공신경망인 퍼셉트론이 발표된지 몇 년 후인 1960년에 Bernard Widrow와 T...

blog.naver.com

 

728x90
반응형
728x90
반응형

 이전 포스트에서는 학습 단위에 대한 단어인 에포크(Epoch), 배치 크기(Batch size), 이터레이션(Iteration)에 대해 알아보았다. 이번 포스트에서 알아볼 확률적 경사 하강법(SGD)의 키는 배치 크기와 랜덤 추출이다.

 경사 하강법에 다른 식을 붙이지 않고 바로 사용하는 방법은 크게 두 가지인 배치 경사 하강법(BGD)과 확률적 경사 하강법(SGD)이 있는데, 이 둘은 손실 함수의 기울기 계산에 사용되는 데이터 셋의 규모만 제외하고 같다.

 중요한 것은 손실 함수의 경사를 구하는 대상이다!

 

 

1. 배치 경사 하강법(Batch Gradient Descent, BGD)

  • 배치 경사 하강법(BGD)은 경사 하강법의 손실 함수의 기울기 계산에 전체 학습 데이터셋의 크기와 동일하게 잡는 방법이다.
  • 즉, 경사 하강법 대상이 배치 크기와 동일하다는 것이다.
  • 데이터셋 모두를 대상으로 하다 보니 파라미터가 한번 이동할 때마다, 계산해야 할 값이 지나치게 많으므로, 계산 시간도 엄청 길어지고, 소모되는 메모리도 엄청나다.
  • mini batch 안 모든 데이터를 대상으로 경사 하강법을 실시하므로, 안정적으로 수렴한다.

  • 안정적으로 수렴하므로, 수렴까지 발생하는 총 파라미터 업데이트 수는 매우 적다.
  • 안정적으로 수렴하는 것은 좋으나, 안정적으로 움직이기 때문에 지역 최소해(Local Minimum)에 빠지더라도 안정적으로 움직이므로 빠져나오기 힘들다. 즉, Local Optima(minimum) 문제가 발생할 가능성이 높다.
  • 학습 데이터셋이 커지면 커질수록 시간과 리소스 소모가 지나치게 크다.

 

 

 

 

2. 확률적 경사 하강법(Stochastic Gradient Descent, SGD)

  • 전체 훈련 데이터셋을 대상으로 학습하는 것은 한정된 리소스를 가지고 있는 우리의 분석 환경에서 매우 비효율적이며, 파라미터 업데이트 수가 적다는 것은 랜덤 하게 뽑힌 시작 위치의 가중치 수도 적으므로, Local minimum 현상이 발생할 확률도 높다는 것이다.
  • 그래서 나온 방법이 학습 데이터셋에서 무작위로 한 개의 샘플 데이터 셋을 추출하고, 그 샘플에 대해서만 기울기를 계산하는 것이다.
  • 샘플 데이터 셋에 대해서만 경사(Gradient)를 계산하므로, 매 반복에서 다뤄야 할 데이터 수가 매우 적어, 학습 속도가 매우 빠르다.
  • 하나의 샘플만 대상으로 경사를 계산하므로, 메모리 소모량이 매우 낮으며, 매우 큰 훈련 데이터 셋이라 할지라도 학습 가능하다.
  • 그러나, 무작위로 추출된 샘플에 대해서 경사를 구하므로, 배치 경사 하강법보다 훨씬 불안정하게 움직인다.

  • 손실 함수가 최솟값에 다다를 때까지 위아래로 요동치며 움직이다 보니, 학습이 진행되다 보면, 최적해에 매우 근접하게 움직이긴 하겠으나, 최적해(Global minimum)에 정확히 도달하지 못할 가능성이 있다.
  • 그러나, 이렇게 요동치며 움직이므로, 지역 최솟값(Local minimum)에 빠진 다할지라도, 지역 최솟값에서 쉽게 빠져나올 수 있으며, 그로 인해 전역 최솟값(Global minimum)을 찾을 가능성이 BGD에 비해 더 높다.
  • 즉, 확률적 경사 하강법(SGD)은 속도가 매우 빠르고 메모리를 적게 먹는다는 장점이 있으나, 경사를 구할 때, 무작위성을 띄므로 지역 최솟값에서 탈출하기 쉬우나, 전역 최솟값에 다다르기 힘들다는 단점을 가지고 있다.
  • 이 문제를 해결하기 미니 배치 경사 하강법(mini-Batch gradient descent)이 등장하였다.

 

학습률 스케줄(Learning rate schedule)

  • 전역 최솟값에 도달하기 어렵다는 문제를 해결하기 위한 방법으로, 학습률을 천천히 줄여 전역 최솟값에 다다르게 하는 방법이 있다.
  • 학습률은 작아질수록 이동하는 양이 줄어들기 때문에 전역 최솟값에 안정적으로 수렴할 수 있다.
  • 만약 학습률이 너무 급격하게 감소하면, Local Optima 문제나 Plateau 현상이 발생할 가능성이 높아진다.
  • 그렇다고 학습률을 너무 천천히 줄이면 최적해 주변을 맴돌 수 있다.

 

 

 

 

3. 미니 배치 경사 하강법(mini-Batch gradient descent)

  • 앞서 이야기한 배치 경사 하강법(BGD)나 확률적 경사 하강법(SGD)은 모두 배치 크기가 학습 데이터 셋 크기와 동일하였으나, 미니 배치 경사 하강법은 배치 크기를 줄이고, 확률적 경사 하강법을 사용하는 기법이다.
  • 예를 들어, 학습 데이터가 1000개고, batch size를 100으로 잡았다고 할 때, 총 10개의 mini batch가 나오게 된다. 이 mini batch 하나당 한 번씩 SGD를 진행하므로, 1 epoch당 총 10번의 SGD를 진행한다고 할 수 있다.
  • 일반적으로 우리가 부르는 확률적 경사 하강법(SGD)은 실제론 미니 배치 경사 하강법(mini-BGD)이므로, 지금까지 학습했던 차이들은 기억하되, 앞으로 SGD를 말하면, 미니 배치 경사 하강법을 떠올리면 된다.

  • 미니 배치 경사 하강법은 앞서 이야기했던, 전체 데이터셋을 대상으로 한 SGD보다 파라미터 공간에서 Shooting이 줄어들게 되는데, 이는 한 미니 배치의 손실 값 평균에 대해 경사 하강을 진행하기 때문이다.
  • 그로 인해, 최적해에 더 가까이 도달할 수 있으나, Local optima 현상이 발생할 수 있다. 그러나, 앞서 말했듯 Local optima 문제는 무수히 많은 임의의 파라미터로부터 시작되면, 해결되는 문제이며, 학습 속도가 빠른 SGD의 장점을 사용하여, 학습량을 늘리면 해결되는 문제다.
  • 배치 크기는 총 학습 데이터셋의 크기를 배치 크기로 나눴을 때, 딱 떨어지는 크기로 하는 것이 좋다.
  • 만약, 1050개의 데이터에 대하여 100개로 배치 크기를 나누면, 마지막 50개 데이터셋에 대해 과도한 평가를 할 수 있기 때문이다.
  • 그러나, 만약 배치 크기로 나누기 애매한 경우라면, 예를 들어 총 학습 데이터 셋이 1,000,050개가 있고, 배치 크기를 1,000개로 나누고 싶은 경우라면, 나머지인 50개는 버리도록 하자(물론 완전 무작위 하게 50개를 선택해서 버려야 한다.).

 

 

 

  지금까지 확률적 경사 하강법(SGD)에 대해 알아보았다. 본래의 SGD는 "배치 크기 = 학습 데이터 셋 크기"이지만, 일반적으로 통용되는 SGD는 "배치 크기 < 학습 데이터 셋 크기"인 미니 배치를 만들어 학습시키는 미니 배치 경사 하강법이다. 

 경사 하강법의 파이썬 코드화는 경사 하강법 함수 자체는 단순하지만, 학습에서 발생하는 모든 알고리즘이 복합적으로 작동하므로, 코드화시키는 것은 시간 낭비로 판단된다. Optimizer 파트부턴 그 개념과 특징을 이해하고, 텐서플로우로 학습을 해보도록 하자.

 다음 포스트에서는 경사 하강법의 한계점을 보완하기 위한 시도 중 하나인 모멘텀(Momentum)에 대해 학습해보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서는 경사 하강법의 한계점에 대해 학습해보았다. 이번 포스트에서는 경사 하강법의 실행 과정을 살펴보고, 기본 사용 방법인 배치 경사 하강법(Batch Gradient Descent)이 어떤 단점을 가지고 있기에 최적화 기법의 기반이 되는 경사 하강법인 확률적 경사 하강법(Stochastic Gradient Descent, SGD)이 나오게 되었는지를 알아보고자 하였으나, 이 과정을 쉽게 이해하려면 먼저 학습이 일어나는 구조와 학습 단위에 대한 개념을 알아야 한다.

 

 

1. 학습의 구조

  • 학습은 기본적으로 다음과 같은 구조로 움직인다.
  1. 임의의 매개변수(가중치)를 정한다.
  2. 선택된 매개변수로 손실 값을 구하고, 손실 함수의 기울기(Gradient)를 계산한다.
  3. 계산된 기울기와 학습률(Learning Rate)을 이용해 다음 가중치의 위치로 이동하여, 파라미터를 업데이트한다.
    이때, 이동 거리는 경사 하강법 공식을 통해 구해진다.
    $$ \theta_{n+1} = \theta_n - \eta \bigtriangledown f(\theta_n) $$
  4. 이동된 지점에서 손실 함수의 기울기(Gradient)를 계산하고, 3.과정을 다시 실시한다.
  5. 손실함수의 기울기가 최솟값에 도달하면, 파라미터 업데이트를 멈춘다.

 

 

 

 

2. 학습 단위

  • 그런데, 위 과정을 보다 보면 한 가지 의문이 든다.
  • 바로, 기울기 계산이 엄청 많이 일어난다는 것인데, 우리가 기계를 학습시킬 때 사용하는 빅 데이터는 일반적으로 최소 1,000만 건 이상을 가리키며, 1억, 10억 건 이상 데이터도 심심치 않게 등장한다는 것이다.
  • 이렇게 많은 데이터를 한 번에 모델에 태우게 된다면, 아무리 좋은 컴퓨터라도 버티지 못할 것이다.
  • 한 번의 학습에 모든 학습 데이터셋을 사용한다면, 여러 문제를 일으킨다.
  1. 데이터의 크기가 너무 큰 경우, 메모리가 너무 많이 필요해진다.
  2. 학습 한 번에 계산돼야 할 파라미터(가중치) 수가 지나치게 많아지므로 계산 시간이 너무 오래 걸린다.
  • 여기서 Epoch, Batch size, iteration라는 개념이 등장하게 된다.

 

 

 

 

3. Epoch(에포크)

  • Epoch의 네이버 영어 사전 뜻은, "(중요한 사건·변화들이 일어난) 시대"라는 뜻이다.
  • 훈련 데이터셋에 포함된 모든 데이터들이 한 번씩 모델을 통과한 횟수로, 모든 학습 데이터셋을 학습하는 횟수를 의미한다.
  • 1 epoch는 전체 학습 데이터셋이 한 신경망에 적용되어 순전파와 역전파를 통해 신경망을 한 번 통과했다는 의미가 된다.
  • 즉 epoch가 10회라면, 학습 데이터 셋 A를 10회 모델에 학습시켰다는 것이다.
  • epoch를 높일수록, 다양한 무작위 가중치로 학습을 해보므로, 적합한 파라미터를 찾을 확률이 올라간다.
    (즉, 손실 값이 내려가게 된다.)
  • 그러나, 지나치게 epoch를 높이게 되면, 그 학습 데이터셋에 과적합(Overfitting)되어 다른 데이터에 대해선 제대로 된 예측을 하지 못할 수 있다.

 

 

 

 

4. Batch size(배치 사이즈)

  • Batch의 네이버 영어 사전 뜻은 "(일괄적으로 처리되는) 집단", "한 회분(한 번에 만들어 내는 음식 기계 등의 양)", "(일괄 처리를 위해) 함께 묶다"라는 의미가 있다.
  • 즉, 연산 한 번에 들어가는 데이터의 크기를 가리킨다.
  • 1 Batch size에 해당하는 데이터 셋을 mini Batch라고 한다.
  • 1회 epoch 안에 m 개($m \geq 1$)의 mini Batch가 들어가게 되며, 만약, m = 1인 경우, 배치 학습법이라고 한다.
  • 배치 사이즈가 너무 큰 경우 한 번에 처리해야 할 데이터의 양이 많아지므로, 학습 속도가 느려지고, 메모리 부족 문제가 발생할 위험이 있다.
  • 반대로, 배치 사이즈가 너무 작은 경우 적은 데이터를 대상으로 가중치를 업데이트하고, 이 업데이트가 자주 발생하므로, 훈련이 불안정해진다.

 

 

 

 

5. Iteration(이터레이션)

  • Iteration은 네이버 영어사전에서 "(계산·컴퓨터 처리 절차의) 반복"이라는 뜻이다.
  • 전체 데이터를 모델에 한번 학습시키는데 필요한 배치의 수를 말한다.
  • 즉, 1 epoch를 마치는데 필요한 파라미터 업데이트 횟수라 할 수 있다.
  • 각 배치마다 파라미터 업데이트가 한 번씩 이루어지므로, Iteration은 "파라미터 업데이트 횟수 = 배치의 수"가 된다.

 

 

 

 

※ 참고

 만약, 데이터셋이 너무 거대해서 전체를 메모리에 올리는 것만으로도 부하가 걸릴 정도라면, 배치 학습 방법을 하되, 한 번에 학습할 학습 데이터 셋의 크기를 줄이고, for문으로 실제 batch를 만들고, pickle로 파일로 만들어 놓은 데이터 셋을 일부씩 불러와 batch에 학습시키고, 모든 데이터 셋을 불러와 한번 학습하는 것을 epoch로 잡는 방식도 있다.

 위 글만으로는 이해가 가지 않을 수 있으므로, 나중에 기회가 된다면 이를 자세히 다뤄보도록 하겠다.

 

 

 

 

 이번 포스트에서는 학습 단위로 사용되는 단어인 Epoch, Batch size, mini batch, Iteration에 대해 알아보았다. 다음 포스트에서는 배치 경사 하강법(BGD)과 확률적 경사 하강법(SGD)에 대해 학습해보도록 하겠다.

 
728x90
반응형

+ Recent posts