티스토리 뷰

혼공머신

[혼공머신] 7장. 딥러닝을 시작합니다

게으른 the lazy 2022. 8. 21. 22:56

 

 

(한빛미디어에서 모집하는 혼공학습단 활동의 일환으로 혼자 공부하는 머신러닝+딥러닝 책을 공부하고 작성한 글입니다. 책은 제 돈으로 샀습니다. 본문의 코드는 책의 소스코드를 기반으로 하되 글 흐름에 맞게 수정한 것입니다. 원본 코드는 저자 박해선 님의 깃허브에서 보실 수 있습니다. 책에 나오는 넘파이, 판다스 등의 내용은 본 글에는 자세히 넣지 않았습니다. 본 글의 코드는 깃허브에서 보실 수 있습니다.)

 

패션 MNIST

드디어 딥러닝입니다. 7장에서 이용할 데이터는 6장에 다룬 과일 이미지와 비슷한 패션잡화 이미지입니다. 패션 MNIST 데이터셋이라고 부릅니다. 딥러닝에서 많이 사용하는 데이터셋으로, 워낙 유명하기 때문에 케라스에는 이미 데이터를 불러올 메서드가 준비되어 있습니다. 데이터를 불러와서 어떻게 생겼는지 보겠습니다.

 

 

 

신발, 셔츠, 드레스, 가방 등이 보이는군요. 각 이미지의 크기는 28 x 28이고 총 60,000장의 이미지가 준비되어 있습니다. 패션 MNIST에는 이미지마다 정답이 들어있습니다.

 

https://pravarmahajan.github.io/fashion/

 

10개의 레이블이 있고 각 레이블마다 6,000장의 이미지가 들어있습니다. 정답이 있고 레이블대로 분류한다? 4장에서 배운 로지스틱 회귀가 바로 떠오르네요.

 


 

로지스틱 회귀와 인공 신경망

이미지를 정규화하고, 1차원 벡터로 변환하고, 확률적 경사 하강법과 교차 검증까지 한방에 가겠습니다.

 

 

로지스틱 회귀를 그림으로 표현하자면 아래와 같을 겁니다.

 

https://medium.com/walmartglobaltech/neural-networks-part-1-logistic-regression-least-square-error-f78c79159cb7

 

왼쪽의 $x$는 이미지의 각 픽셀입니다. 28 x 28 이미지이니 n784겠군요. 각 픽셀값마다 가중치를 곱하여 모두 더하고, 위 그림에는 표시되어 있지 않지만 절편까지 더합니다. 이걸 각 레이블마다 계산하고 활성화 함수를 적용하면 하나의 이미지에 대해 각 레이블 확률이 나옵니다. 로지스틱 회귀의 동작 방식이었죠. 그런데 사실 인공 신경망 그림을 찾아보면 위 그림과 똑같이 생겼습니다.

 

https://www.researchgate.net/figure/Architecture-of-a-neural-network-without-any-hidden-layer-perceptron-x-jk-are-the_fig2_229880200

 

, 로지스틱 회귀와 기본적인 인공 신경망은 생긴 것이 같습니다. 그럼 이제 인공 신경망은 배울 게 없나? 그건 아닙니다. 우선 케라스를 이용해 인공 신경망 모델을 만드는 방법을 알아보겠습니다. 패션 MNIST 데이터셋을 훈련/검증 세트로 나눕니다.

 

 

위 그림은 7개의 입력이 하나의 출력으로 모이는 것처럼 생겼지만, 레이블이 10개인 패션 MNIST라면 784개의 입력이 10개의 출력으로 모이는 형태여야 할 겁니다. 두 층을 연결하는 거미줄 같은 형태를 밀집층이라고 부릅니다. 밀집층은 keras.layers.Dense 메서드를 사용하여 만듭니다. Dense 객체를 keras.Sequential에 입력으로 전달하면 신경망 모델이 만들어집니다. 이 모델에 compile() 메서드로 손실 함수와 평가 지표를 설정하고, fit()으로 훈련시킵니다.

 

 

코드를 보며 몇 가지 중요한 내용을 정리해보겠습니다.

  • Dense 메서드의 첫 번째 인자는 출력단의 크기입니다.
  • activation 파라미터에 활성화 함수를 전달합니다. 다중 분류이므로 ‘softmax’를 입력했습니다.
  • compile()에서 손실 함수를 설정할 수 있습니다. 다중 분류이므로 기본적으로 ‘categorical_crossentropy’입니다. 패션 MNIST의 타겟은 정수의 배열입니다. 타겟이 정수 배열일 때에도 원-핫 인코딩을 사용하고 싶다면 손실 함수로  ‘sparse_categorical_crossentropy’를 지정하면 됩니다. 원-핫 인코딩은 몇 개의 레퍼런스로 대신하겠습니다. 딥러닝을 이용한 자연어 처리 입문, AI4SCHOOL, 자비스가 필요해
  • 케라스는 기본적으로 에포크마다 손실 값을 출력합니다. 여기에 정확도 값도 같이 출력하기 위해 metrics에 ‘accuracy’를 전달했습니다.

 

훈련이 어느 정도 된 것 같으니 검증 세트 평가를 해보죠.

 

 

그럼 이제 끝인가? 아닙니다. 진정한 신경망은 이제부터 시작입니다.

 


 

심층 신경망 또는 딥러닝

왜 심층deep 신경망일까요? 층이 깊어지기 때문입니다. 그럼 층이 깊어진다는 게 무슨 말이냐? 앞에서 본 신경망은 노드가 입력과 출력에만 있었죠. 밀집층은 하나였습니다. 여기에 밀집층을 하나 더 추가하면

 

https://www.databricks.com/kr/glossary/neural-network

 

이런 모양이 되겠죠. 2층짜리 신경망 모델이 되었습니다. 이렇게 층이 깊어진다deep고 해서 심층 신경망 또는 딥러닝이라고 부릅니다.

 

바로 이 지점에서 딥러닝의 난해함이 발생합니다. 뒤에서 보여드리겠지만 층을 추가하면 모델의 성능이 좋아집니다. 그런데 왜 좋아지는지 설명하기가 어렵습니다. 위 그림의 가운데 층의 역할은 무엇일까요? 가운데 층의 노드 개수는 몇 개로 해야 할까요? 층을 2개 추가하면 성능이 더 좋아질까요? 성능이 좋아진다면 왜 좋아지는 거죠? 이런 질문들에 답을 하기가 좀 어렵습니다. 사람이든 컴퓨터든 둘 다 이미지의 패턴을 보고 분류한다는 점은 똑같습니다. 다만 방식이 다르기 때문에 설명이 어렵다고 말합니다. 앞에서 다룬 머신러닝, 지금 다루는 딥러닝을 잘게잘게 분해해보면 사실 3가지 요소의 결합입니다. 어떤 수를 곱하는 노드(가중치), 어떤 수를 더하는 노드(절편), 그리고 값에 따라 확률을 계산해주는 노드(활성화 함수)입니다. 로지스틱 회귀, 결정 트리, 군집화 등은 어떤 방향성을 가지고 노드들을 연결합니다. 즉 각 연결에는 명확한 의미와 역할이 있습니다. 그런데 딥러닝은 좀 다릅니다. 아 모르겠고 그냥 다 연결시켜보자입니다. 그런데 그게 됩니다. 그것도 다른 방식들보다 잘 됩니다. 그래서 통계를 오래 공부하신 분들은 딥러닝을 믿지 못하겠다고 얘기하시기도 합니다. 되긴 되는데 왜 되는지 모르니까요. 설명의 난해함이 이미지 분류에서는 큰 문제가 되지 않을 수도 있습니다. 어쨌든 잘되면 장땡이니까요. 그런데 만약 회사의 정책을 결정해야 한다면? 큰 투자를 앞두고 의사결정을 해야 한다면? 아무리 쳐다봐도 이해도 되지 않고 설명도 할 수 없는 방향을 딥러닝 모델이 추천한다면 여러분은 그 결정을 그대로 따라가야 할까요? 투자자들에겐 어떻게 설명할 건가요? 모델의 결정을 무시한다면 뭣하러 그 오랜 시간 컴퓨터 앞에서 시간과 돈을 버렸냐는 소리를 들을 것이고, 사람이 받아들일 수 있는 결정만 하는 모델이라면 필요가 없습니다. 쉽지 않은 문제입니다.

 

잡설이 길었군요. 딥러닝은 위 그림과 같이 입력층과 출력층 사이에 층을 더하는 것입니다. 밖에서 보이지 않는다는 의미로 은닉층hidden layer이라고 부릅니다. 은닉층을 만드는 방법은 세 가지가 있습니다.

 

첫 번째는 각 밀집층 객체를 Dense()로부터 만들고 keras.Sequential에 밀집층 객체의 리스트를 전달하는 방법입니다.

 

 

위 코드에서 dense1이 입력층과 은닉층을 연결하는 밀집층입니다. dense2는 은닉층과 출력층을 연결하는 밀집층입니다. keras.Sequential.summary() 메서드를 통해 모델의 정보를 볼 수 있습니다. Output Shape의 첫 번째 차원이 None인 데에는 이유가 있습니다. 입력 데이터에 따라 배치 크기가 달라지기 때문입니다. 케라스 모델은 미니 배치 경사 하강법을 사용하거든요. Param #는 각 층의 모델 파라미터 개수입니다. 예를 들어 78,500은 입력층 노드 784, 은닉층 노드 100개의 곱에 절편 100개를 더한 값입니다.

 

두 번째 방법은 밀집층 객체를 keras.Sequential에 입력인자로 바로 생성하는 방법입니다.

 

 

세 번째 방법은 keras.Sequentialadd() 메서드를 사용하는 방법입니다. 은닉층이 여러 개일 때 편리한 점이 있습니다.

 

 

손실 함수, metrics를 설정하고 훈련해보겠습니다.

 

 

정확도가 조금씩 오르는 게 보이는군요. 여기서 정확도를 더 올릴 방법과 전처리 기법을 하나 소개하겠습니다.

 


 

FlattenReLU

Flatten은 데이터를 1차원 배열로 바꿔주는 메서드입니다. 케라스에서는 마치 층을 하나 추가하는 것처럼 add()를 통해 더할 수 있습니다. 이렇게요.

 

 

은닉층의 활성화 함수를 ‘sigmoid’가 아닌 ‘relu’로 설정했습니다. [렐루]라고 부르며 아래처럼 생겼습니다.

 

https://pytorch.org/docs/stable/generated/torch.nn.ReLU.html

 

초창기 인공 신경망의 은닉층에 많이 사용된 활성화 함수는 시그모이드 함수였습니다. 하지만 이 함수는 단점이 있습니다. 이 함수의 오른쪽과 왼쪽 끝으로 갈수록 그래프가 누워있기 때문에 올바른 출력을 만드는데 신속하게 대응하지 못합니다. …(중략)… 이를 개선하기 위해 다른 종류의 활성화 함수가 제안되었습니다. 바로 렐루 함수입니다.

박해선, 혼자 공부하는 머신러닝 + 딥러닝, 한빛미디어, 377쪽

 

아래는 데이터를 불러오고 훈련/검증 세트를 나누고 컴파일 및 훈련하는 과정입니다. Flatten을 이용하므로 reshape은 필요하지 않습니다.

 

 

활성화 함수로 sigmoid를 사용했을 때보다 약간 더 좋아졌네요. (accuracy: 0.8797 -> 0.8847) 검증 세트 평가를 해보겠습니다.

 

 

은닉층이 없을 때보다 꽤 좋아졌습니다. (accuracy: 0.8430 -> 0.8611)

 


 

옵티마이저optimizer

신경망에서는 조절할 수 있는, 또는 조절해야 하는 하이퍼파라미터가 많습니다.

  • 은닉층의 개수
  • 은닉층의 뉴런 개수
  • 은닉층의 활성화 함수
  • 층의 종류 (밀집층, 합성곱 등)
  • 배치 크기 (batch_size)
  • 반복 횟수 (epochs)
  • 옵티마이저의 종류
  • 옵티마이저의 학습률

 

너무 많네요. 많아도 적당히 많아야죠. 저 중에서 옵티마이저는 최적의 모델 파라미터를 어떤 방식으로 찾아갈지에 대한 설계입니다. 옵티마이저는 크게 기본 경사 하강법 옵티마이저와 적응적 학습률adaptive learning rate 옵티마이저가 있습니다.

  • 기본 경사 하강법
    • keras.optimizer.SGD 클래스를 사용합니다.
    • momentum 값을 0보다 크게 하면 이전 그레디언트를 가속도처럼 사용할 수 있습니다. 모멘텀 최적화라고 불리는 기법입니다.
    • nesterov=True로 두면 네스테로프 최적화라고 부르며 모멘텀 최적화를 2번 반복합니다.
  • 적응적 학습률 옵티마이저
    • 모델이 최적점에 가까이 갈수록 학습률을 낮춰 수렴 안정성을 높이는 방법입니다.
    • 학습률 매개변수를 튜닝하지 않아도 된다는 장점이 있습니다.
    • RMSprop: keras.optimizer.RMSprop()을 compile()의 optimizer로 설정합니다.
    • Adagrad: keras.optimizer.Adagrad()를 compile()의 optimizer로 설정합니다.
    • Adam: 모멘텀 최적화와 RMSprop의 장점을 접목한 것입니다.
    • RMSprop, Adagrad, Adam의 learning_rate 기본값은 모두 0.001입니다.

 

응 아니야 (출처: https://www.econovill.com/news/articleView.html?idxno=283370)

 

 

옵티마이저에 대한 조금 더 자세한 내용은 에서 볼 수 있습니다. Adam을 이용해서 딥러닝 모델을 만들고 훈련, 검증까지 돌려보겠습니다.

 

 

 

훈련 세트와 (0.8847 -> 0.8875) 검증 세트 (0.8611 -> 0.8803) 모두 약간 더 좋아졌습니다.

 


 

히스토리와 손실 곡선

keras.Sequential.fit() 메서드는 데이터를 훈련만 하지 않습니다. 훈련 과정을 모두 기록하는 기특한 녀석입니다. fit()의 반환값이 바로 그 기록이거든요. 우선 데이터를 준비하고

 

 

모델 생성을 편하게 만들어주는 함수를 하나 작성합니다.

 

 

모델을 생성하고 컴파일 후, 훈련의 반환값을 history에 저장합니다.

 

 

verbose=0으로 두면 훈련 과정이 출력되지 않습니다. history.history에는 lossaccuracy가 딕셔너리 형태로 들어가 있습니다. 각각을 plot 해보겠습니다.

 

 

 

에포크를 더 늘리면 더 학습을 잘 하겠죠? 화끈하게 20까지 늘려봅니다.

 

 

확실히 손실이 줄어드는군요. 하지만 이 값은 훈련 세트임을 잊지 말아야 합니다. 우리에게 중요한 건 검증 세트의 점수죠. 케라스로 만든 모델의 fit()validation_data로 검증 세트를 전달하면 검증 세트도 한방에 평가해줍니다. 완전 편하네요.

 

 

역시 과대적합이 일어났군요. 과대적합을 방지하는 가장 간단한 방법은 옵티마이저를 바꿔보는 겁니다. Adam을 써보도록 하죠. 이번엔 정확도도 같이 plot 해보겠습니다.

 

 

 

좀 나아졌군요. 이번엔 학습률을 조정해보겠습니다. 너무 빨리 적합하는게 문제이니까 학습률을 낮춰보죠.

 

 

 

손실이 비슷해지긴 했는데, 검증 세트는 그대로에 훈련 세트 손실이 올라간 거라서 더 좋아졌다고 말하기가 좀 어려울 것 같네요. 정확도도 오히려 떨어졌습니다. 학습률을 더 낮춰볼까요?

 

 

 

으악! 이건 아닌거 같습니다. 학습률이 1e-4일 때 20 에포크까지 손실은 감소, 정확도는 증가하고 있었으니 혹시 에포크를 늘리면…? 여백이 부족하여(…) 여기까지만 하겠습니다.

 


 

드롭아웃

드롭아웃dropout은 과대적합을 방지하는 또 다른 방법입니다. 결정 트리에서 일부러 개별 모델의 성능을 나쁘게 한 것 기억나시나요? 비슷한 기법이 딥러닝에도 사용됩니다. 훈련 샘플마다 은닉층의 노드를 일부러 몇 개씩 없애는 겁니다. 이를 통해 모델이 특정 뉴런에 과하게 의존하지 못하지 방지할 수 있습니다. 샘플마다 랜덤하게 은닉층 노드를 제거한다는 점에서 앙상블의 개념으로 생각할 수도 있겠군요. 드롭아웃은 keras.layers.Dropout 클래스를 사용합니다.

 

 

컴파일, 훈련 과정은 동일합니다.

 

 

과대적합이 줄어든 것을 볼 수 있습니다. 대충 13 에포크 쯤에서 손실이 최소인 듯하니 13 에포크에서의 모델을 저장해보도록 하죠.

 


 

모델 저장하고 불러오기

훈련시킨 모델은 어딘가에 저장해야 나중에 불러와서 사용하기 편하겠죠. 모델 파라미터만 저장하려면 keras.Sequential.save_weights() 메서드를, 모델 구조와 모델 파라미터를 통째로 저장하려면 keras.Sequential.save() 메서드를 사용합니다.

 

 

각 메서드로 저장한 파일은 불러오는 방법도 조금 다릅니다. save_weights()로 저장한 파일은 load_weights()로 불러옵니다.

 

 

save()로 저장한 파일은 load_model로 불러옵니다.

 

 


 

콜백

이번 장의 마지막 소주제입니다. 앞에서 딥러닝 모델을 20 에포크까지 훈련시키고, 결과로부터 13 에포크까지만 저장해서 사용했습니다. 이왕이면 이걸 자동으로 해주면 좋지 않을까요? 손실이 더 이상 감소하지 않으면 알아서 멈춰주는 거죠. 그 역할을 담당하는 것이 바로 콜백callback입니다. 훈련 과정 중간에 작업을 수행할 수 있는 객체입니다. 두 가지를 이용해보겠습니다.

  • ModelCheckpoint (keras.callbacks.ModelCheckpoint 클래스)
    • 에포크마다 모델을 저장합니다.
    • save_best_only=True로 설정하면 손실이 가장 낮은 하나만 저장합니다.
  • EarlyStopping (keras.callbacks.EarlyStopping 클래스)
    • 손실이 낮아지지 않으면 훈련을 멈춥니다.
    • 손실이 증가하면 바로 멈추는 것은 아니고, patience에 지정한 횟수만큼 기다렸다가 멈춥니다.
    • restore_best_weights=True로 두면 손실이 가장 낮은 모델 파라미터로 되돌립니다.

 

코드는 아래와 같습니다.

 

 

13에서 멈췄군요. 인덱스가 0부터 시작하므로 14 에포크까지 사용한 것입니다. 그래프로 확인도 해보고

 

 

검증 세트 평가까지 해봅니다.

 

 


 

기본 미션: 07-1 확인 문제 풀고, 풀이 과정 정리

1. 어떤 인공 신경망의 입력 특성이 100개이고 밀집층에 있는 뉴런 개수가 10개일 때 필요한 모델 파라미터의 개수는 몇 개인가요?

정답: 1,010

해설: 우선 100개의 입력과 10개의 출력을 연결하는 1,000개의 파라미터가 필요합니다. 여기에 잊지 말아야 할 것이 절편입니다. 절편은 출력층의 뉴런 개수만큼 필요합니다.

 

2. 케라스의 Dense 클래스를 사용해 신경망의 출력층을 만들려고 합니다. 이 신경망이 이진 분류 모델이라면 activation 매개변수에 어떤 활성화 함수를 지정해야 하나요?

정답: ‘sigmoid’

해설: activation은 활성화 함수를 지정하는 파라미터입니다. 활성화 함수는 출력층이 계산한 값을 확률로 바꿔주는 함수입니다. 4장의 로지스틱 회귀를 생각해보면 됩니다. 따라서 이진 분류라면 activation‘sigmoid’를 입력해야 합니다. 다중 분류라면 ‘softmax’를 입력합니다.

 

3. 케라스 모델에서 손실 함수와 측정 지표 등을 지정하는 메서드는 무엇인가요?

정답: compile()

해설: Dense()는 밀집층을 만드는 메서드입니다. Sequential()은 신경망 모델을 만드는 메서드이고 Dense 객체를 전달합니다. compile()은 훈련 전에 손실 함수와 측정 지표metrics를 설정하는 메서드입니다. 훈련은 fit()으로 진행합니다.

 

 

4. 정수 레이블을 타깃으로 가지는 다중 분류 문제일 때 케라스 모델의 compile() 메서드에 지정할 손실 함수로 적절한 것은 무엇인가요?

정답: ‘sparse_categorical_crossentropy’

해설: 이진 분류일 때에는 ‘binary_crossentropy’를 지정합니다. 타깃이 원-핫 인코딩으로 준비되어 있다면 ‘categorical_crossentropy’를 입력합니다. 정수 레이블인 경우에는 ‘sparse_categorical_crossentropy’입니다.

 

선택 미션: 07-2 확인 문제 풀고, 풀이 과정 정리

1. 다음 중 모델의 add() 메서드의 사용법이 올바른 것은?

1) model.add(keras.layers.Dense)

2) model.add(keras.layers.Dense(10, activation=’relu’))

3) model.add(keras.layers.Dense, 10, activation=’relu’)

4) model.add(keras.layers.Dense)(10, activation=’relu’)

정답: 2번입니다.

해설: 아래는 tf.keras.Sequential 공식 문서 중 일부입니다.

 

 

2. 크기가 300 x 300인 입력을 케라스 층으로 펼치려고 합니다. 다음 중 어떤 층을 사용해야 하나요?

1) Plate

2) Flatten

3) Normalize

4) Dense

정답: 2번입니다.

해설: 아래는 tf.keras.layers.Flatten 공식 문서 앞부분입니다.

 

 

3. 다음 중 이미지 분류를 위한 심층 신경망에 널리 사용되는 케라스의 활성화 함수는 무엇인가요?

1) linear

2) sigmoid

3) relu

4) tanh

정답: 3번입니다.

해설:

렐루 함수는 특히 이미지 처리에서 좋은 성능을 낸다고 알려져 있습니다.

박해선, 혼자 공부하는 머신러닝 + 딥러닝, 한빛미디어, 377쪽

 

4. 다음 중 적응적 학습률을 사용하지 않는 옵티마이저는 무엇인가요?

1) SGD

2) Adagrad

3) RMSprop

4) Adam

정답: 1번입니다.

해설: 좋은 그림으로 대신하겠습니다.

 

https://www.slideshare.net/yongho/ss-79607172

 

 

 

7장 내용정리는 여기서 마치겠습니다. 끝까지 읽어주셔서 고맙습니다.

댓글