Keras 시작하기 - PART 2 : 데이터 적재와 이미지 분류기 - 2
(핸즈 온 머신러닝 4판을 보고 직접 해보면서 정리한 내용이 포함되어 있습니다.)
이제 신경망을 구성한다. 두 개의 은닉층으로 이루어진 분류용 다층 퍼셉트론을 구성한다.
model = keras.models.Sequential() # Sequential 모델 생성
model.add(keras.layers.Flatten(input_shape=[60, 60])) # 첫 번째 층
model.add(keras.layers.Dense(300, activation="relu")) # 두 번째 층(Dense 은닉층)
model.add(keras.layers.Dense(100, activation="relu")) # 세 번째 층(Dense 은닉층)
model.add(keras.layers.Dense(2, activation="sigmoid")) # 네 번째 층(Dense 출력층) ---
가장 기본적인 Keras의 신경망 모델인 Sequential API 는 순서대로 연결된 층을 일렬로 쌓아서 구성한다. 첫 번째 층인 Flastten 층은 입력층이고 input_shape를 지정해서 2D 배열을 1D 배열로 변환시켜 준다. Sequential API 에서는 Flastten 이 자동으로 input_layer 를 추가 시켜준다. 두 번재 층은 300개의 뉴런을 가진 Dense 은닉층이다. 이 층에서는 ReLU 활성화 함수를 사용한다. Dense 층마다 각자 가중치 행렬을 관리하게된다. 이 행렬에는 층의 뉴런과 입력 사이의 모든 연결 가중치가 포함된다. 또한 뉴런마다 하나씩 가지고 있는 편향을 벡터로 관리한다. 세 번째 층은 100개의 뉴런을 가진 Dense 은닉층이고 ReLU 활성화 함수를 사용한다. 네 번째 층은 2개(레이블이 0 또는 1이므로) 의 뉴런을 가진 Dense 출력층이다. 이진 분류이므로 시그모이드 함술르 사용
Sequential 모델하면서 파라미터로 층 리스트를 전해 생성할 수 도 있다.
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[60, 60]),
keras.layers.Dense(300, activation="relu"),
keras.layers.Dense(100, activation="relu"),
keras.layers.Dense(2, activation="softmax")
]) ---
summary 메서드로 모든 모델에 있는 모든 층에대한 요약정보를 볼 수 있다.
model.summary()
>>
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
flatten (Flatten) (None, 3600) 0
dense (Dense) (None, 300) 1080300
dense_1 (Dense) (None, 100) 30100
dense_2 (Dense) (None, 2) 202
=================================================================
Total params: 1,110,602
Trainable params: 1,110,602
Non-trainable params: 0
_________________________________________________________________ ---
인덱스로 각 레이어에 접근하는 것도 가능하다.
hidden1 = model.layers[1]
hidden1.name >>
'dense' ---
model.get_layer('dense') is hidden1
>>
True ---
각 층의 파라미터는 get_weights() 메서드와 set_weights() 메서드를 사용해서 접근하고 수정이 가능하다. Dense 층의 경우에는 연결 가중치와 편향이 모두 포함이 되어있다. 그리고 Dense층은
weights, biases = hidden1.get_weights()
weights
>>
array([[ 2.4732668e-02, -2.9293036e-02, 3.6767948e-02, ...,
1.6396020e-02, 1.4383238e-02, 2.7493160e-02],
[-1.1954159e-03, 1.7265685e-02, -1.6323984e-02, ...,
-3.6148448e-02, 3.8136426e-02, 3.8305335e-03],
[-1.7189320e-02, 2.6539642e-02, 1.1621568e-02, ...,
-1.1945739e-02, 2.8465912e-03, 2.3969997e-02],
...,
[ 2.9213052e-02, 1.7641336e-02, -1.8800050e-02, ...,
-1.9194825e-02, 3.0572381e-02, 3.1801570e-02],
[-3.7126042e-02, -6.5319240e-05, -5.9127286e-03, ...,
-1.2455147e-02, 3.5346273e-02, -1.3756068e-02],
[ 2.1826956e-02, 1.3147049e-02, 4.4034086e-03, ...,
-2.2559782e-02, -1.5801411e-02, -3.9097778e-02]], dtype=float32)
weights.shape
>>
(3600, 300) ---
biases
>>
array([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., 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., 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., 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., 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., 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., 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., 0., 0., 0., 0., 0., 0.], dtype=float32)
biases.shape
>>
(300,) ---
이제 데이터도 적재하였고 모델구성도 완료하였으니 compile() 매서드를 호출해서 학습에 사용할 손실함수(loss function)와 옵티마이저(optimizer)를 지정하고 모델을 컴파일해야한다. 부가적으로 훈련과 평가 시에 계산할 지표를 지정할 수도 있다.
model.compile(loss="binary_crossentropy",
optimizer="sgd",
metrics=["accuracy"]) ---
이진분류이므로 손실함수를 binary_crossentropy로 지정해주었다. Optimizer는 기본적 확률 경사 하강법인 SGD 를 사용한다. SGD를 사용하여 모델을 훈련한다는 의미. SGD 옵티마이저를 사용할 떄는 학습률을 튜닝하는 것이 중요하다. accuracy는 훈련과 평사에 정확도를 측정한다는 뜻이다. 여기서 수정해줘야할 부분이 생겼는데 바로 y_all 이다. 즉 레이블링 데이터의 shape을 바꾸어 줘야한다. 이진분류이기 때문에 0 또는 1의 값을 가지는 건 맞지만 출력층은 두 개이다. 따라서 출력층에는 0과 1, 두 개의 뉴런이 필요하고 각 뉴런의 활성화 정도에 따라서 0인지 1인지가 정해지는 것이다. 따라서 2D 배열로 0과1 두개의 자리가 있고 0또는1로 뉴런의 상태를 표시하는 원-핫 인코딩을 적용 시켜야 한다. 이제 fit매써드를 통해 훈련을 시켜보자
history = model.fit(X_train, y_train, epochs=30,
validation_data=(X_valid, y_valid))
>>
Epoch 1/30
108/108 [==============================] - 2s 4ms/step - loss: 0.6863 - accuracy: 0.5754 - val_loss: 0.6711 - val_accuracy: 0.7126
Epoch 2/30
108/108 [==============================] - 0s 2ms/step - loss: 0.6729 - accuracy: 0.6233 - val_loss: 0.6512 - val_accuracy: 0.7103
Epoch 3/30
108/108 [==============================] - 0s 2ms/step - loss: 0.6598 - accuracy: 0.6607 - val_loss: 0.6318 - val_accuracy: 0.7300
Epoch 4/30
108/108 [==============================] - 0s 2ms/step - loss: 0.6455 - accuracy: 0.6859 - val_loss: 0.6149 - val_accuracy: 0.7648
Epoch 5/30
108/108 [==============================] - 0s 2ms/step - loss: 0.6300 - accuracy: 0.7108 - val_loss: 0.5975 - val_accuracy: 0.7659
Epoch 6/30
108/108 [==============================] - 0s 2ms/step - loss: 0.6144 - accuracy: 0.7155 - val_loss: 0.5795 - val_accuracy: 0.7636
Epoch 7/30
108/108 [==============================] - 0s 2ms/step - loss: 0.6004 - accuracy: 0.7164 - val_loss: 0.5667 - val_accuracy: 0.7659
Epoch 8/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5881 - accuracy: 0.7224 - val_loss: 0.5575 - val_accuracy: 0.7671
Epoch 9/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5781 - accuracy: 0.7259 - val_loss: 0.5483 - val_accuracy: 0.7659
Epoch 10/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5701 - accuracy: 0.7274 - val_loss: 0.5462 - val_accuracy: 0.7578
Epoch 11/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5634 - accuracy: 0.7306 - val_loss: 0.5453 - val_accuracy: 0.7578
Epoch 12/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5579 - accuracy: 0.7306 - val_loss: 0.5450 - val_accuracy: 0.7532
Epoch 13/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5531 - accuracy: 0.7332 - val_loss: 0.5413 - val_accuracy: 0.7509
Epoch 14/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5485 - accuracy: 0.7338 - val_loss: 0.5399 - val_accuracy: 0.7509
Epoch 15/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5445 - accuracy: 0.7355 - val_loss: 0.5356 - val_accuracy: 0.7555
Epoch 16/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5406 - accuracy: 0.7393 - val_loss: 0.5392 - val_accuracy: 0.7532
Epoch 17/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5369 - accuracy: 0.7425 - val_loss: 0.5395 - val_accuracy: 0.7532
Epoch 18/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5333 - accuracy: 0.7456 - val_loss: 0.5418 - val_accuracy: 0.7462
Epoch 19/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5296 - accuracy: 0.7448 - val_loss: 0.5463 - val_accuracy: 0.7439
Epoch 20/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5263 - accuracy: 0.7503 - val_loss: 0.5501 - val_accuracy: 0.7393
Epoch 21/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5231 - accuracy: 0.7509 - val_loss: 0.5413 - val_accuracy: 0.7462
Epoch 22/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5194 - accuracy: 0.7558 - val_loss: 0.5473 - val_accuracy: 0.7381
Epoch 23/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5164 - accuracy: 0.7558 - val_loss: 0.5435 - val_accuracy: 0.7393
Epoch 24/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5132 - accuracy: 0.7590 - val_loss: 0.5403 - val_accuracy: 0.7416
Epoch 25/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5099 - accuracy: 0.7633 - val_loss: 0.5501 - val_accuracy: 0.7358
Epoch 26/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5065 - accuracy: 0.7587 - val_loss: 0.5392 - val_accuracy: 0.7312
Epoch 27/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5034 - accuracy: 0.7651 - val_loss: 0.5515 - val_accuracy: 0.7323
Epoch 28/30
108/108 [==============================] - 0s 2ms/step - loss: 0.5004 - accuracy: 0.7703 - val_loss: 0.5535 - val_accuracy: 0.7323
Epoch 29/30
108/108 [==============================] - 0s 2ms/step - loss: 0.4970 - accuracy: 0.7723 - val_loss: 0.5479 - val_accuracy: 0.7277
Epoch 30/30
108/108 [==============================] - 0s 2ms/step - loss: 0.4938 - accuracy: 0.7732 - val_loss: 0.5532 - val_accuracy: 0.7277 ---
Epoch가 많아질수록 무조건 accuraccy가 커지는건 아니다. 완전한 분류가 불가능한 주가데이터의 특성이라고도 할 수 있겠다.