Chapter 5. 반려동물의 품종 분류하기

2023. 12. 17. 14:21딥러닝 모델: fastai

♣ 데이터셋 준비 및 DataLoaders 만들기

 

딥러닝에서 데이터는 일반적으로 두 가지 방식으로 제공됨. 

- 개별 파일이 텍스트 문서, 이미지 같은 개별 데이터 요소에 대응함. 그리고 폴더나 파일명에 각 데이터의 추가 정보가 있음

- 테이블 형식의 데이터(csv 같은)로 제공됨. 파일명은 테이블로 표현된 데이터를 실제 텍스트 문서나 이미지 같은 데이터로 연결하는 역할. 

 

 

 

ls 메서드를 사용해서 데이터셋에 포함된 내용 확인하기

파일명은 품종, 언더바, 숫자로 구성되어 있음. 여기서 품종만 뽑아내어야 한다. 

 

이미지 파일 하나를 뽑아서 정규표현식(regex)를 이용하여 품종 뽑기를 시도해보자. 

 

위의 정규표현식은 마지막 밑줄 문자 다음에 하나 이상의 숫자와 JPEG 확장자가 등장하면 마지막 밑줄 문자 이전의 모든 문자열을 추출함. 

 

이제 전체 데이터셋 레이블링에도 적용해보자. fastai는 정규표현식을 이용해 레이블링 작업을 할 수 있도록 RegexLabeller 클래스를 제공함. 이 클래스는 데이터블록 API와 함께 사용할 수 있음. 

 

 

<사전 크기 조절>

 

텐서로 포장된 이미지를 GPU로 전달하려면 이미지가 모두 같은 크기여야 함. 수행될 데이터 증강의 계산 횟수도 최소화하는 편이 좋음. 가능한 적은 변형 작업으로 데이터 증강을 수행하고, 이미지를 같은 크기로 변형할 것을 권장함. 

 

하지만 크기를 증강된 크기로 조정한 후, 다양한 데이터 증강을 수행하면 불필요하게 채워진 빈 영역이나 데이터의 저하가 발생할 수 있음. 이러한 부분을 채우는 보간법이 필요하지만 보간된 픽셀은 품질이 좋지 않음. 

사전 크기 조절은 두 가지 전략으로 이 문제를 해결함

 

1. 이미지 크기를 상대적으로 크게 만듦. 즉 실제로 원하는 이미지보다 훨씬 더 크게 만듦. (item_tfms에 해당. GPU로 복사되기 전 개별 이미지에 적용하는 부분)
첫 번째 단계는 크기 조절에서는 이미지를 충분히 크게 키움. 이로써 빈 영역을 만들지 않고 데이터 증강을 내부 영역에서 수행하도록 여분의 여백을 보장함. 구체적으로는 정사각형으로 이미지 일부를 잘라내어 크기를 조절함. 학습용 데이터셋에서 잘라낼 영역은 임의로 정해지고, 잘라낼 크기는 이미지 너비와 높이 중 작은 쪽이 선택됨. 

검증용 데이터셋에서는 항상 이미지의 정중앙 정사각형 영역이 선택됨. 

 

2. 이미지에 공통적으로 적용할 모든 증강 연산을 하나로 구성하고, 처리 마지막 단계에서 조합된 연산들을 GPU가 단 한 번만 수행함. 모든 잠재적인 파괴 작업을 함께 수행하고, 마지막에 보간법을 한 번 적용함. (batch_tfms에 해당. 즉 GPU에서 한 번에 처리할 배치 단위의 데이터에 적용되어 빠르게 처리됨.)

검증용 데이터셋에서는 모델에 필요한 최종 크기로 조절하는 작업만 수행함.   

 

 

♣ 데이터블록 검사와 디버깅

fastai 라이브러리는 모델을 학습시키기 전에 데이터의 올바른 형태를 검사하는 간단한 방법을 제공함. 

데이터블록이 정상적으로 작동하는지 점검하기

데이터블록을 구축할 때 저지른 실수를 바로 파악하는 것이 쉽지 않기 때문에 summary 메서드를 사용할 것을 권장함. 

이 메서드는 제공된 데이터의 여러 세부사항을 모두 포함한 한 배치를 생성하는 것을 시도함. 만약 메서드 호출이 실패하면 에러가 발생한 정확한 지점을 확인할 수 있음. 

 

 

♣ 데이터로 간단한 모델 학습시키기

디버깅 과정을 거쳐 데이터가 올바르다고 판단한 다음에는 데이터로 간단한 모델을 학습시켜본다. 

 

 

한 epoch는 데이터에 포함된 이미지를 모두 한 번씩 처리했다는 의미임. 

손실은 모델 파라미터를 최적화하는 데 사용하기로 한 어떤 함수도 될 수 있음. 내부적으로는 fastai가 사용 중인 데이터와 모델의 종류에 따라 적절한 손실 함수를 고름. 이미지 데이터를 입력 받아서 범주형 출력을 내놓을 때는 기본적으로 교차 엔트로피 손실을 선택함. 

 

♣ 교차 엔트로피 손실

교차 엔트로피 손실은 앞 장에서 사용한 손실 함수와 유사하지만 다음과 같은 추가 장점이 있음

 

1. 종속 변수에 범주가 둘 이상이더라도 작동함. 

2. 더 빠르고 안정적인 학습 결과를 도출함.  

 

교차 엔트로피 손실은 두 부분으로 구성됨 = 소프트 맥스 + 로그 가능도

 

<활성 및 레이블 확인>

모델의 활성 살펴보기. DataLoaders의 one_batch 메서드는 실제 배치를 하나 가져와서 반환함. 즉 종속변수와 독립변수를 미니배치 형태로 반환하는 것. 

배치 크기가 64이므로 해당 텐서는 행이 64개임. 각 행의 값은 0~36 사이의 정수로, 37개 품종이 있다는 것을 알 수 있음. 

 

Learner.get_preds 메서드로는 예측값(신경망 마지막 계층의 활성)을 확인할 수 있음. 독립변수와 종속변수로 구성된 배치로 간단한 리스트를 만들어서 입력하면 예측값을 얻을 수 있음. 

예측은 0~1 범위의 확률 37개로 구성됨. 이 값 37개를 모두 더하면 1이 되어야 함. 

 

내부적으로는 소프트맥스 활성화 함수를 적용해서 모델이 내놓은 활성을 이런 형식의 출력으로 변형함. 즉, 위처럼 모든 범주를 더했을 때 1이 나오도록 하기 위해 소프트맥스 활성화 함수를 사용한다는 것. 

 

 

<소프트맥스>

분류 모델에서는 마지막 계층의 모든 활성값이 0~1이 되도록 조정하고, 모두 더하면 1이 되도록 하기 위해 소프트맥스 활성화 함수를 사용함. 

소프트맥스는 앞서 본 시그모이드 함수와 유사함. 시그모이드 함수를 신경망의 활성값으로 채운 단일 열에 적용하면 0과 1 사이의 숫자로 채워진 열을 만들 수 있음. 따라서 마지막 계층에서 매우 유용한 활성화 함수임. 

 

다중 분류에서는 단일 열보다 더 많은 범주가 존재하고, 범주 당 활성 하나(확률)가 필요함. 

범주 당 활성에 시그모이드 함수를 적용하여 모든 범주의 활성을 더했을 때 1이 되도록 해보자. 

임의의 2개 범주와 활성

 

행을 더한 값이 1이 아니기 때문에 여기에 시그모이드를 바로 적용하면 안 됨. 

 

 

대신 두 활성 간의 차이에 시그모이드 함수를 씌워보자.

 

위의 값은 첫 번째 열에 해당할 확률임. 1에서 이 확률을 빼면 두 번째 열에 해당할 확률도 구할 수 있음. 

이와 같은 계산을 둘 이상의 열로 확장할 수 있음 => 소프트맥스의 역할!

 

 

 

acts에 소프트맥스를 적용해서  시그모이드와 같은 값을 반환하는지 확인하기. 

소프트맥스는 시그모이드의 다중 범주 버전쯤으로 볼 수 잇음. 범주가 둘 이상이고 각 범주 확률의 합이 1이 되어야 하는 상황에서는 언제나 사용 가능함. 범주가 두 개 뿐일 때도 일관성을 유지할 목적으로 많이 사용함. 

 

소프트맥스는 범주들 가운데 하나를 고르는 일을 수행함. 따라서 각 입력에 레이블이 단 하나인 데이터를 분류하는 모델을 학습시키는 데 소프트맥스를 사용하는 것이 일반적임. 

 

<로그 가능도>

시그모이드에서 소프트맥스로 변경했으므로 손실 함수도 이진 분류 이상에서 작동하는 형태로 확장해야 함. 즉 범주위 개수와 무관하게 분류할 수 있어야 함. 

 

인위적으로 만든 숫자 3과 7의 레이블이 다음과 같다고 가정하자. 

아래는 소프트 맥스를 적용한 결과임. 

 

텐서의 색인 기능을 사용하여 targ의 각 요소에 대해 sm_acts의 적절한 열 선택하기

 

파이토치는 sm_acts[range(n), targ]와 정확히 같은 일을 하는 nll_loss 함수를 제공함. 단, 음수를 수용한다는 점이 다름. 나중에 로그를 적용하면 음수가 생기기 때문임. 여기서 NLL은 음의 로그 기능도를 뜻함. 

 

이름에 '로그'가 있지만 파이토치가 제공하는 nll_loss 함수는 로그를 적용하진 않음. 

 

 

<로그 취하기>

nll_loss는 손실함수로서 꽤 잘 작동함. 

문제는 확률값이 0보다 작거나 1보다 클 수 없다는 점임. 모델은 예측값이 0.99일 때와 0.999일 때의 차이를 잘 다루지 못함. 이를 해결하려면 0~1 사이의 값을 음의 무한대와 양의 무한대로 변환해야 함. 로그가 이런 변환을 수행하는 함수이며 파이토치에서는 torch.log로 제공됨. 

 

0보다 작은 수에서 정의되지 않는 로그는 다음과 같은 모양을 가짐

 

소프트맥스 다음에 로그 가능도를 적용하는 조합을 교차 엔트로피 손실이라고 함. 실제로는 log_softmax 다음에 nll_loss를 수행함. 파이토치에서는 nn.CrossEntropyLoss 클래스로 교차 엔트로피 손실을 사용할 수 있음.

 

 

또는 F 네임스페이스에서 사용할 수 있는 일반 함수로도 제공됨. 

 

 

둘 다 어느 상황에서도 사용할 수 있지만 대부분 클래스 사용을 선호함. 

기본적으로 파이토치의 손실 함수는 모든 항목의 형균 손실을 계산함. reduction = 'none' 옵션을 사용하면 이런 행동을 비활성화할 수 있음. 

 

손실은 모델이 학습을 잘하고 있는지를 나타내는 숫자를 부여할 뿐이고, 이 정보만으로 모델의 좋고 나쁨을 파악할 수 없음. 

 

 

♣ 모델 해석

손실 함수는 사람이 보려고 만든 것이 아니라 컴퓨터가 미분을 구하고 최적화할 수 있도록 설계됨. 따라서 사람의 이해를 돕기 위해 평가 지표가 필요함. 

 

오차행렬로 모델이 잘 예측한 부분과 그렇지 못한 부분 확인하기

 

품종이 37개라서 37x37 크기의 거대한 행렬이 구성됨. 이럴 때는 most_confused 메서드를 사용할 수 있음. 

이 메서드는 오차 행렬 중 가장 올바르지 못한 예측만 보여줌. 

 

 

min_val=5는 최소 5번 잘못 예측한 부분만 추출한다는 의미임. 앞부분이 실제 label, 뒷부분이 prediction

 

 

♣ 모델 향상하기

<학습률 발견자>

매우 작은 학습률부터 사용하자는 아이디어. 미니배치 하나에 선택된 학습률을 사용하고 손실을 측정. 

 

미니배치에 학습률 사용하고 손실 측정 -><- 특정 비율로 학습률 늘림

 

손실이 나빠지기 직전까지 위의 과정을 반복함. 그 지점보다 약간 작은 학습률을 선택하면 됨. 

 

일반적으로 아래 규칙 중 하나로 학습률을 선택하기를 권장함. 

- 최소 손실이 발생한 지점보다 한 자릿수 작은 학습률(최소 손실이 발생한 학습률을 10으로 나눈 값)

- 손실이 명확히 감소하는 마지막 지점

 

 

 

 

<동결 해제 및 전이 학습>

 

합성곱 신경망은 여러 선형 계층과 이들을 잇는 비선형 계층의 조합과 그 끝에는 하나 이상의 선형 계층이 위치함. 

가장 마지막 선형계층에는 소프트맥스 같은 활성화 함수가 쓰임. 

마지막 선형계층은 모델이 분류하는 범주의 개수와 열의 크기가 같은 행렬을 사용함(분류 문제라고 가정했을 때)

 

전이 학습에서는 마지막 선형 계층을 모델 구조에서 제거하고 새로운 문제에 맞는 출력 개수로 구성한 새로운 선형 계층으로 교체해야 함. 새로 추가된 선형 계층의 가중치는 완전히 임의로 지정됨. 그렇기 때문에 미세 조정 전까지 모델은 완전히 임의의 출력을 함. 하지만 마지막 계층 이전의 모든 계층은 이미 학습된 형태이기 때문에 모델 자체가 완전히 임의적인 것은 아님. 

 

미세 조정은 신중하게 사전 학습된 가중치를 망가뜨리지 않으면서 추가된 선형 계층의 임의 가중치를 새로운 작업을 올바르게 푸는 가중치로 바꾸는 작업임.

이를 위해 옵티마이저는 추가된 계층의 임의 가중치만 갱신하고, 그 외의 가중치는 바꾸지 않도록 강제할 수 있음. 

이를 사전 학습된 계층을 동결시키는 기법이라고 함. 

 

사전 학습된 신경망으로 모델을 만들 때, fastai는 자동으로 사전 학습된 모든 계층을 동결시킴. 그리고 fine_tune 메서드를 호출하면 다음 두 가지 작업을 수행함. 

 

- 한 epoch 동안 추가된 계층의 임의 가중치만 갱신함. 그 외 계층은 동결 상태 유지

- 그 다음 모든 계층의 동결을 해제하고 요청된 epoch 수만큼 학습을 진행

 

 

  1. 모델 동결 (Freeze the Model): self.freeze()는 모델의 초기 레이어를 동결시킵니다. 이는 처음에는 최종 레이어만을 학습시키도록 하여, 전이 학습(transfer learning)에서 흔한 방법입니다. 초기 레이어는 일반적인 특징을 캡처하고 나중 레이어는 작업 특정 특징을 캡처합니다.
  2. 원 사이클 학습 (Fit One Cycle - Frozen Layers): self.fit_one_cycle은 1cycle 학습률 정책을 사용하여 지정된 epoch 동안 모델을 훈련시키는 fastai 메서드입니다. 학습률을 서서히 증가시키고 최대치에 도달한 후 감소시키는 방식으로 동작하여 신경망을 효과적으로 훈련시키는 데 효과적입니다.
  3. 학습률 감소 (Reduce Learning Rate): 학습률 (base_lr)을 fine-tuning 중인 동안 사전 훈련된 가중치를 불안정하게 만들지 않기 위해 절반으로 줄입니다.
  4. 모델 동결 해제 (Unfreeze the Model): self.unfreeze()는 모델의 모든 레이어를 동결 해제합니다. 이로써 모든 레이어가 fine-tuning될 수 있게 됩니다.
  5. 원 사이클 학습 (Fit One Cycle - All Layers): slice를 사용하여 서로 다른 레이어에 서로 다른 학습률이 적용된 상태로 지정된 epoch 동안 모델을 훈련합니다. 이는 전이 학습을 효과적으로 수행하기 위한 기법 중 하나입니다.

 

<fine_tune의 하는 일을 직접 실시하기>

 

fit_one_cycle 메서드로 추가된 계층에 대해 세 번의 epoch 동안 학습 수행하기.

fit_one_cycle은 fine_tune을 사용하지 않는 상황에서 모델 학습에 권장되는 메서드임. 

 

fit_one_cycle 에서는 전체 학습을 두 부분으로 나눔

- 낮은 학습률로 학습을 시작하여 첫 번째 부분까지 점진적으로 학습률을 증가시킴

- 두 번째 부분에서는 다시 점진적으로 학습률을 감소시킴

 

  • 추가된 계층에 대해 세 번의 epoch 동안 학습 수행

  • 모델 동결 해제

 

  • lr_find를 다시 수행하기. 학습할 계층이 더 많아졌고 세 epoch 동안 학습된 일부 가중치가 있기 때문에 앞서 찾은 학습률은 더는 적절치 못함. 

 

가파르게 상승하는 지점 이전에 평평한 부분 중 한 부분을 학습률로 선택해야 함. 그레이디언트가 최대인 지점을 찾는 것이 목적이 아니므로 그 부분은 무시하기. 

 

  • 적당한 학습률로 학습

 

<차별적 학습률>

서로 다른 계층에 서로 다른 학습률을 적용하는 방식.

사전 학습된 파라미터를 위한 최적의 학습률이 임의로 추가된 파라미터 학습에서만큼 크지는 않을 것. 

 

fastai에서는 차별적 학습률의 사용을 기본 접근법으로 채택함. 신경망의 초기 계층에는 작은 학습률을 사용하고 후기 계층(특히 임의의 가중치로 추가된 계층)에는 높은 학습률을 사용하는 기법임. 

 

fastai는 학습률을 지정할 수 있는 모든 곳에서 파이썬의 slice 객체를 수용함. slice 객체에 명시된 첫 번째 값은 신경망의 가장 초기 계층의 학습률이며, 두 번째 값은 최종 계층의 학습률임. 그리고 두 계층 사이의 계층들은 정해진 범위에서 일정한 크기로 곱한 학습률을 할당받음. 

 

 

가장 초기 계층은 학습률을 1e-6으로 설정하고, 다른 계층은 학습률이 1e-6에서 1e-4까지 일정하게 증가하여 설정함. 

 

fastai는 학습용과 검증용 데이터셋의 손실 그래프를 그리는 기능도 제공함. 

 

 

학습용 데이터셋 손실은 계속해서 나아지는데, 검증용 데이터셋 손실의 개선은 매우 더딘 편임. 때로는 검증용 데이터셋 손실의 개선이 나빠지기도 하는데, 그 곳이 과적합이 발생하는 지점임. 하지만 과적합이 발생한다고 해서 반드시 정확도가 떨어지는 것은 아님. 

 

 

<epoch 횟수 선택하기>

원사이클 학습 방법이 개발되기 전에는 보통 epoch마다 모델을 저장하고, 저장된 모델 중 정확도가 가장 높은 모델을 선택했음. 이런 방법을 조기 종료(early stopping)이라고 함. 

 

하지만 중간 epoch쯤에서 선택한 모델은 최적의 학습률에 도달하기 전에 발견된 모델일 수도 있음. 즉 최고의 모델을 얻을 가능성이 낮음. 그래서 새로운 학습률을 적용하여 모델 전체를 처음부터 다시 학습시켜야만 함. 

 

많은 epoch를 진행할 시간이 충분하다면 조기 종료보다는 더 많은 파라미터 학습에 시간을 투자하는 것이 유리할 수 있음. 

=> 더 깊은 구조의 모델

 

 

<더 깊은 구조의 모델>

일반적으로 파라미터가 더 많은 모델은 데이터를 더 정확하게 모델링할 수 있음. 

모델 구조에 단순히 계층을 더 많이 추가해서 더 큰 모델을 만들 수도 있으나, 사전 학습된 모델을 활용할 때는 사전 학습된 계층 수가 사용할 모델의 크기를 결정함. 

 

일반적으로 구조가 더 큰 모델은 데이터의 실제 관계를 더 잘 포착함. 또한 개별 이미지에 특화된 세부 사항을 포착하고 기억하는 능력도 더 뛰어남. 하지만 더 심층적인 구조의 모델은 GPU 메모리를 더 많이 요구하므로 '메모리 부족 오류'를 피하려면 배치 크기를 줄여줘야 함. 즉 모델에 한 번에 전달하는 이미지 개수를 줄여야 함. 

 

또한 더 큰 구조의 모델을 사용하면 학습에 시간이 오래 소요됨. 속도를 끌어올리는 기법 중 '혼합 정밀도 학습'이 있음. 

이는 학습 시, 가능한 한 덜 정밀한 숫자를 사용함을 의미함. (to_fp16)

혼합 정밀도를 사용한 ResNet-50 모델

 

fine_tune 메서드에서 freeze_epochs 인잣값으로 동결 상태에서의 학습 epoch 횟수를 지정할 수 있음. 그리고 대부분의 데이터셋에 적절하도록 학습률을 자동으로 바꿔주기도 함. 

 

더 깊은 모델이 항상 더 좋은 것은 아님. 모델의 크기를 키우기 전, 작은 모델부터 시도해볼 것!

 

♣ 관련 코드

https://www.kaggle.com/code/polljjaks/classifying-pets/edit/run/155505930

 

classifying pets

Explore and run machine learning code with Kaggle Notebooks | Using data from No attached data sources

www.kaggle.com