[ 머신러닝 순한 맛] 전이학습은 어떻게 이뤄질까? with Code
어제보다 나은 사람이 되기

걱정보단 실행을, 그러나 계획적으로

Box World 자세히보기

AI/Hands-On Machine Learning 2판

[ 머신러닝 순한 맛] 전이학습은 어떻게 이뤄질까? with Code

Box형 2021. 5. 11. 01:38
반응형

큰 야망을 품었을 때
커다란 결실을 얻을 수 있다.
- 힐러리 클린턴미국 상원의원 -

 이 글을 읽는 여러분의 AI 공부를 어렵게 하는 것은 무엇인가요? 어려운 개념이나 수식도 한몫하겠지만, 저는 높은 연산량을 감당하기 위해, 값비싼 GPU가 필요하다는 것입니다.

 이런 맥락에서 대부분의 AI를 초기에 공부하는 우리들이 아주 큰 규모의 DNN을 처음부터 훈련시킨다는 건 매우 어려운 일입니다. 이럴 경우 생각해볼 수 있는 방법이 비슷한 유형의 문제를 처리한 신경망이 있는지 찾아보고 이를 이용하는 전이 학습입니다.

 우리는 이를 통해 훈련 속도도 크게 높일 수 있으며 필요한 훈련 데이터도 크게 줄어듭니다. 이번 포스팅에서는 실무에서도 많이 쓰이는 전이학습에 대해 공부해보겠습니다.

 

 

 

 

 


사전 훈련된 층 재사용하기

 여기 동물, 식물, 자동차 등 100개의 구분된 이미지를 분류하도록 훈련된 DNN이 있다고 가정하겠습니다. 그리고 우리는 지금부터 구체적인 자동차의 종류를 분류하는 DNN을 훈련시키고자 합니다. 바로 이때 이렇게 두 DNN에 대해서 비슷한 점이 많고, 일부 겹치는 부분이 있을 때 신경망을 재사용하는 것을 고려해봐야합니다.

 그렇다면 이제부터 어떤 기준을 가지고 기존 신경망을 재사용해야할지 알아보겠습니다.

 첫번째로 해야할 일은 재사용하려는 모델의 출력층을 바꿔줘야 합니다. 물건을 구분하던 기존 신경망과 자동차의 종류를 구분하고자 하는 우리의 신경망은 그 출력이 다를 것이기 때문에 바꿔주는게 당연히 맞을 것입니다.

 비슷한 맥락에서 재사용하려는 신경망의 상위 은닉층(출력층에 가까운 층들)은 하위 은닉층보다 덜 유용합니다. 왜냐하면 상위 은닉층이라 하면 구체적이고 세부적이고 지엽적인 특성을 학습하고 있기 때문에 이런 특성들은 도움되지 않을것입니다. 쉽게 말해서 우리는 이야기의 대략적인 개요나 줄거리 정도가 필요하지, 그 안에 등장인물의 이름이나 생김새와 같은 정보는 필요하지 않습니다.

 그렇다면 우리가 방금 단계에서 재사용할 층의 개수까지 정하고 나면 무엇을 해야할까요? 우선은 재사용하는 층의 가중치는 모두 동결합니다. 즉 이들은 경사 하강법으로 가중치가 바뀌지 않습니다. 그 다음 모델을 훈련하고 평가합니다.

 이후에 맨 위에 있는 가장 깊은 층부터 동결을 해제하면서 역전파 알고리즘을 이용하여 가중치를 조정하여 성능을 확인합니다. 이때 데이터가 많을 수록 더 많은 층을 동결 해제할 수 있습니다.

 한 가지 주의할 점은 가중치를 세밀하게 튜닝하기 위해 학습률은 줄이는게 좋습니다. 만약 여전히 좋은 성능을 내지 못한다면 상위 은닉층을 제거하거나 추가하면서 재사용할 층의 개수를 적절히 찾아야 합니다.

 

 

 

 

 


 Keras를 이용한 전이 학습

 이번엔 직접 코드와 함께 전이 학습을 살펴보겠습니다. 우리가 여기서 활용한 데이터셋은 8개의 클래스로 나뉜 Fashion MNIST 데이터셋입니다. 누군가 이 데이터의 클래스를 90%이상의 정확도로 분류시키는 모델 'A'를 생성했다고 하겠습니다. 

 우리가 하고자하는 작업은 Fashion MNIST 데이터셋에는 없는 '샌들'과 '셔츠' 이미지를 구분하는 Binary Classifier를 구현하고자 합니다(True = 셔츠, False = 샌들). 레이블된 이미지는 200개 정도로 매우 적습니다. 그래서 우리는 모델 A와 구조가 매우 비슷한 모델 B를 만들었고 97.2%의 좋은 성능을 냈습니다. 

 하지만 클래스가 두개인 쉬운 문제이기 때문에 이 정확도를 더 높이고 싶고, 이를 위해 전이 학습을 하고자 합니다. 과연 도움이 될까요?

 우선 모델 A를 로드하여 출력층을 제외하고 모든 층을 재사용해보겠습니다.

model_A = keras.models.load_model("my_model_A.h5")
model_B_on_A = keras.models.Sequential(model_A.layers[:-1])
model_B_on_A.add(keras.layers.Dense(1, activation="sigmoid"))

 model_A와 model_B_on_A는 일부 층을 공유합니다. 즉 model_B_on_A가 훈련할 때 model_A도 함께 학습됩니다. 이를 원하지 않는다면, 훈련 전 클론시켜놔야합니다. 이를 위해 clone_model() 메서드를 사용하여 모델 A의 구조를 복제한 후 가중치를 복사합니다.

model_A_clone = keras.models.clone_model(model_A)
model_A_clone.set_weights(model_A.get_weights())

 이제 재사용할 층과 가중치까지 가져왔기 때문에 훈련만 하면 됩니다. 그렇지만 바로 훈련으로 들어간다면 새로운 출력층이 랜덤하게 초기화되어있어 큰 오차를 만들 것이고 이로 인한 큰 오차 그레디언트는 최적화를 방해할 것입니다.

 이를 피하기 위해 처음 몇번의 에포크 동안에는 재사용된 층을 동결하고 새로운 층에게 적절한 가중치를 학습할 시간을 줘야합니다. 이를 위해 모든 층의 trainable 속성을 False로 지정하겠습니다.

for layer in model_B_on_A.layers[:-1]:
    layer.trainable = False

model_B_on_A.compile(loss="binary_crossentropy",
                     optimizer=keras.optimizers.SGD(lr=1e-3),
                     metrics=["accuracy"])

 이제 몇번의 에포크 동안 훈련을 하고 나면 동결을 해제하고 작업 B에 맞게 이 재사용된 층들을 세밀하게 튜닝해야합니다. 이때 학습률을 낮춰 가중치가 망가지는 것을 막아야합니다.

history = model_B_on_A.fit(X_train_B, y_train_B, epochs=4,
                           validation_data=(X_valid_B, y_valid_B))

for layer in model_B_on_A.layers[:-1]:
    layer.trainable = True

model_B_on_A.compile(loss="binary_crossentropy",
                     optimizer=keras.optimizers.SGD(lr=1e-3),
                     metrics=["accuracy"])
history = model_B_on_A.fit(X_train_B, y_train_B, epochs=16,
                           validation_data=(X_valid_B, y_valid_B))

 최종점수를 확인해보면 그 정확도는 99.25%로 오차율을 2.8%에서 0.7%로 네 배나 낮췄습니다,

model_B.evaluate(X_test_B, y_test_B)
>> [0.1408407837152481, 0.9704999923706055]

 전이 학습은 작은 규모의 Fully-Connected 네트워크에서는 잘 동작하지 않습니다. 작은 규모는 패턴 수를 적게 학습한다는 뜻이고, Fully-Connected 라는 것은 특정 패턴을 학습하기 때문일 것입니다. 실제로 이러한 특정 패턴이 다른 작업에서도 유용하게 쓰일 확률은 적다고 봐야합니다.

 반대로 규모가 어느정도 있는 심층 합성곱 신경망은 일반적인 특성을 훨씬 잘 파악하기 때문에 잘 동작할 것입니다.

 

 

 

 

 

 


 다음 포스팅에서는 최적화를 위해 일반적으로 우리가 사용하는 고속 옵티마이저의 종류에 대해서 알아보겠습니다. 오늘도 읽어주셔서 감사합니다. 행복한 하루 보내세요 :)

 

반응형