13.1 모델의 최대 성능을 끌어내기
13.1.1 하이퍼파라미터 최적화
딥러닝 모델을 만들 때 무작위로 보이는 결정을 많이 하곤 한다. 얼마나 많은 층을 쌓아야 할까? 층마다 얼마나 많은 유닛이나 필터를 두어야 할까? relu 활성화 함수를 사용해야 할까? 아니면 다른 함수를 사용해야 할까? 어떤 층 뒤에 BatchNormalization을 사용해야 할까? 드롭아웃은 얼마나 해야 할까? 등이다. 이런 구조에 관련된 파라미터를 역전파로 훈련되는 모델 파라미터와 구분하여 하이퍼파라미터(hyperparameter)라고 부다.
실제로 경험 많은 머신 러닝 엔지니어와 연구자는 하이퍼파라미터에 따라 작동하는 것과 작동하지 않는 것에 대한 직관을 가지고 있다. 하이퍼파라미터 튜닝에 관한 기술을 가지고 있는 셈이지만 공식적인 규칙은 없다. 주어진 문제에서 최대의 성능을 얻고 싶다면 임의로 선택한 결정에 만족해서는 안 된다. 직감이 좋다고 하더라도 첫 번째 선택은 거의 항상 최적치가 아니다. 옵션을 수정하고 모델을 반복적으로 다시 훈련하여 선택 사항을 개선해야 한다. 이것이 머신 러닝 엔지니어와 연구자들이 대부분의 시간을 쓰는 일이다. 하지만 하루 종일 하이퍼파라미터를 수정하는 것은 사람이 할 일은 아니다. 기계에 위임하는 것이 더 낫다.
가능한 결정 공간을 자동적, 조직적, 규칙적 방법으로 탐색해야 한다. 가능성 있는 구조를 탐색해서 실제 가장 높은 성능을 내는 구조를 찾아야 한다. 하이퍼파라미터 자동 최적화가 이에 관련된 분야다. 이는 하나의 연구 분야이며 중요한 분야다.
전형적인 하이퍼파라미터 최적화 과정은 다음과 같다.
- 일련의 하이퍼파라미터를 (자동으로) 선택한다.
- 선택된 하이퍼파라미터로 모델을 만든다.
- 훈련 데이터에 학습하고 검증 데이터에서 성능을 측정한다.
- 다음으로 시도할 하이퍼파라미터를 (자동으로) 선택한다.
- 이 과정을 반복한다.
- 마지막으로 테스트 데이터에서 성능을 측정한다.
검증 성능과 다양한 하이퍼파라미터 사이의 관계를 분석하여 다음 번에 시도할 하이퍼파라미터를 선택하는 알고리즘이 이 과정의 핵심이다. 베이즈 최적화(bayesian optimization), 유전 알고리즘(genetic algorithms), 간단한 랜덤 탐색(random search) 등.
KerasTuner 사용하기
KerasTuner를 사용하면 units=32와 같은 하드코딩된 하이퍼파라미터 값을 Int(name="units", min_value=16, max_value=64, step=16)과 같이 가능한 선택 범위로 바꿀 수 있다. 어떤 모델에 대한 이런 선택의 집합을 하이퍼파라미터 튜닝 과정의 탐색 공간(search space)이라고 부른다.
다음 단계는 ‘튜너(tuner)’를 정의하는 것이다. 튜너를 다음 과정을 반복하는 for 루프로 생각할 수 있다.
- 일련의 하이퍼파라미터 값을 선택한다.
- 이런 값으로 모델 구축 함수를 호출하여 모델을 만든다.
- 모델을 훈련하고 평가 결과를 기록한다.
KerasTuner는 몇 가지 내장 튜너를 제공한다. RandomSearch, BayesianOptimization, Hyperband이다. 이전 선택의 결과를 바탕으로 최상의 하이퍼파라미터 값을 스마트하게 예측하는 BayesianOptimization 튜너를 사용해 본다.
올바른 검색 공간을 만드는 기술
전체적으로 보았을 때 하이퍼파라미터 최적화는 어느 작업에서 최고의 모델을 얻거나 머신 러닝 경연 대회에서 우승하기 위한 강력한 도구다. 하지만 하이퍼파라미터 튜닝으로 모델 아키텍처의 모범 사례를 대체할 수 없다. 탐색 공간은 선택 항목의 조합 크기에 따라 늘어나므로 모든 것을 하이퍼파라미터로 설정하여 튜너가 찾도록 하려면 너무 많은 비용이 든다. 따라서 올바른 탐색 공간을 설계할 필요가 있다. 하이퍼파라미터 튜닝은 자동화이지 마법이 아니다. 이를 사용해서 수동으로 할 실험을 자동화할 수 있지만 여전히 좋은 결과를 낼 수 있는 잠재적인 실험 설정을 직접 골라야 다.
좋은 소식은 하이퍼파라미터 튜닝을 사용해서 설정에 대한 결정을 미시적 결정(이 층의 유닛 개수를 얼마로 해야 하나요?)에서 높은 수준의 아키텍처 결정(이 모델에 잔차 연결을 사용해야 하나요?)으로 바꿀 수 있다는 것이다. 미시적 결정은 특정 모델이나 특정 데이터셋에 따라 다르지만 고수준 결정은 여러 작업과 데이터셋에 걸쳐 일반화가 더 잘된다. 예를 들어 거의 모든 이미지 분류 문제는 같은 종류의 탐색 공간 템플릿으로 풀 수 있다.
이런 논리를 따라 KerasTuner는 이미지 분류와 같이 넓은 범위를 가진 문제에 관련된 사전에 정의된 탐색 공간(premade search space)을 제공한다. 데이터를 추가하고 탐색을 실행하면 꽤 좋은 모델을 얻을 수 있다. 튜닝 가능한 케라스 애플리케이션 모델인 kt.applications.HyperXception과 kt.applications.HyperResNet 하이퍼모델(HyperModel)을 시도해 보면 된다.
하이퍼파라미터 튜닝의 미래: 자동화된 머신 러닝
현재 딥러닝 엔지니어로서 대부분의 일은 파이썬 스크립트로 데이터를 정리하고 심층 신경망의 아키텍처와 하이퍼파라미터를 오래 튜닝하여 작동하는 모델을 만드는 것이다. 용감하다면 현존하는 최상의 모델을 얻기 위해 튜닝할 수도 있다. 말할 필요도 없이 이는 최선이 아니다. 하지만 자동화가 도움이 될 수 있다. 이는 하이퍼파라미터 튜닝에 그치지 않는다.
학습률이나 층 크기의 가능한 조합을 탐색하는 것은 첫 단계일 뿐이다. 강화 학습이나 유전 알고리즘을 통해 가능한 제약을 두지 않고 처음부터 모델 아키텍처 자체를 생성할 수 있다. 미래에는 완전한 엔드-투-엔드 머신 러닝 파이프라인이 전문 엔지니어의 손이 아니라 자동으로 생성될 것이다. 이를 자동화된 머신 러닝(automated machine learning) 또는 AutoML이라고 부른다. 이미 AutoKeras(https://github.com/keras-team/autokeras) 같은 라이브러리를 활용하여 수동 작업을 거의 거치지 않고 기초적인 머신 러닝 문제를 풀 수 있다.
오늘날 AutoML은 아직 초창기고 대규모 문제에 적용하기 어렵다. 하지만 AutoML이 널리 적용될 정도로 충분히 성숙해지면 머신 러닝 엔지니어의 직업이 사라지는 것이 아니라 엔지니어의 일이 가치 창조 사슬(value-creation chain)의 위로 이동할 것이다. 데이터 큐레이션, 비즈니스 목표를 잘 반영한 복잡한 손실 함수 생성, 모델이 배포되는 디지털 생태계(예를 들어 모델의 예측을 소비하고 모델의 훈련 데이터를 생성하는 사용자)에 미치는 영향 이해하기 등에 더 많은 노력을 기울이기 시작할 것이다. 현재로서는 규모가 매우 큰 기업들만 생각할 수 있는 문제들이다.
13.1.2 모델 앙상블
모델 앙상블(model ensemble)은 가장 좋은 결과를 얻을 수 있는 또 다른 강력한 기법이다. 앙상블은 여러 개 다른 모델의 예측을 합쳐서 더 좋은 예측을 만든다. 캐글 같은 머신 러닝 경연 대회에서는 우승자들이 대규모 모델 앙상블을 사용한다. 이런 앙상블은 아주 뛰어난 단일 모델보다도 성능이 좋다.
앙상블은 독립적으로 훈련된 다른 종류의 잘 동작하는 모델이 각기 다른 장점을 가지고 있다는 가정을 바탕으로 한다. 각 모델은 예측을 만들기 위해 조금씩 다른 측면을 바라본다. 데이터의 모든 면이 아니고 부분 특징이다. 이들이 훈련 데이터의 매니폴드를 이해하려는 머신 러닝 모델이라고 할 수 있다. 각자의 가정(고유한 모델 구조와 랜덤 가중치 초기화)을 이용하고 각자의 관점으로 이해한다. 각 모델은 데이터의 일부분에 맞는 정답을 찾지만 완전한 정답은 아니다. 이들의 관점을 모으면 데이터를 훨씬 더 정확하게 묘사할 수 있다.
분류기 예측을 (앙상블하기 위해) 합치는 가장 쉬운 방법은 추론할 때 나온 예측의 평균을 내는 것이다.
하지만 이 방식은 분류기들이 어느 정도 비슷하게 좋을 때 잘 작동한다. 분류기 중 하나가 다른 모델보다 월등히 나쁘면 최종 예측은 앙상블에 있는 가장 좋은 분류기만큼 좋지 않을 수 있다.
분류기를 앙상블하는 더 나은 방법은 검증 데이터에서 학습된 가중치를 사용하여 가중 평균하는 것이다. 전형적으로 분류기가 좋을수록 높은 가중치를 가지고 나쁜 분류기일수록 낮은 가중치를 갖는다. 좋은 앙상블 가중치를 찾기 위해 랜덤 서치나 넬더-미드(Nelder-Mead) 알고리즘 같은 간단한 최적화 알고리즘을 사용할 수 있다.
이외에도 여러 가지 변종이 있다. 예를 들어 예측의 지수 값을 평균할 수 있다. 일반적으로 검증 데이터에서 찾은 최적의 가중치로 단순하게 가중 평균하는 방법이 좋은 기본값이다.
앙상블이 잘 작동하게 만드는 핵심은 분류기의 다양성이다. 머신 러닝 방식으로 말하면, 모든 모델이 같은 방향으로 편향되어 있다면 앙상블은 동일한 편향을 유지할 것이다. 모델이 서로 다른 방향으로 편향되어 있다면 편향은 서로 상쇄되고 앙상블이 더 견고하고 정확해질 것이다.
이런 이유 때문에 가능한 최대한 다르면서 좋은 모델을 앙상블해야 한다. 일반적으로 매우 다른 구조를 가지거나 다른 종류의 머신 러닝 방법을 말한다. 랜덤 초기화를 다르게 하여 같은 네트워크를 따로따로 여러 번 훈련해서 앙상블하는 것은 거의 해 볼 가치가 없다. 모델 간 차이점이 랜덤 초기화와 모델에 주입되는 훈련 데이터의 순서라면 이 앙상블은 다양성이 낮고 하나의 모델보다 아주 조금만 성능이 향상될 것이다.
모든 문제에 적용하지는 못하지만 실전에서 잘 동작하는 한 가지 방법은 트리 기반 모델(랜덤 포레스트나 그레이디언트 부스팅 트리)이나 심층 신경망을 앙상블하는 것이다.
13.2 대규모 모델 훈련하기
모델을 빠르게 훈련할 수 있는 세 가지 방법
- 하나의 GPU에서도 사용할 수 있는 혼합 정밀도(mixed-precision) 훈련
- 여러 개의 GPU를 사용한 훈련하기
- TPU를 사용한 훈련
13.2.1 혼합 정밀도로 GPU에서 훈련 속도 높이기
공짜로 거의 모든 모델의 훈련 속도를 3배까지 높일 수 있는 간단한 방법이 있다면 어떨까? 진짜라고 하기에 너무 좋은 것 같지만 실제 이런 트릭이 존재한다. 혼합 정밀도 훈련(mixed-precision training)이다. 이 방식을 이해하기 위해 먼저 컴퓨터 과학 분야의 ‘정밀도’ 개념을 살펴보아야 다.
부동 소수점 정밀도 이해하기
- 숫자를 16비트에 저장하는 반정밀도(half precision) 또는 float16
- 숫자를 32비트에 저장하는 단정밀도(single precision) 또는 float32
- 숫자를 64비트에 저장하는 배정밀도(double precision) float64
이 책에서 본 모든 모델은 단정밀도 숫자를 사용한다. float32 가중치 값으로 상태를 저장하고 float32 입력에 대해 계산을 수행한다. 정보를 잃지 않고 모델의 정방향 패스와 역방향 패스를 실행하는 데 충분한 정밀도이다. 특히 그레이디언트 업데이트가 작은 경우에 그렇다(일반적인 학습률은 1e-3이고 가중치 업데이트는 1e-6 크기가 일반적).
혼합 정밀도로 훈련하기
GPU로 훈련할 때 다음과 같이 혼합 정밀도를 활성화할 수 있다.
일반적으로 모델의 정방향 패스는 (소프트맥스와 같이 수치적으로 불안정한 연산을 제외하고) 대부분 float16으로 수행된다. 반면 모델의 가중치는 float32로 저장되고 업데이트된다.
케라스 층에는 variable_dtype과 compute_dtype 속성이 있다. 기본적으로 두 속성은 float32로 설정되어 있다. 혼합 정밀도를 활성화하면 대부분 층의 compute_dtype 속성이 float16으로 바뀐다(가중치의 반정밀도 복사본을 사용한다). 하지만 variable_dtype은 여전히 float32이므로 가중치는 옵티마이저로부터 반정밀도가 아니라 정확한 float32 업데이트를 받을 수 있다.
동일한 연산이 float16에서 수치적으로 불안정할 수 있다(특히 소프트맥스와 크로스엔트로피). 특정 층에서 혼합 정밀도를 사용하지 않으려면 층의 생성자에 dtype="float32" 매개변수를 지정하면 다.
13.2.2 다중 GPU 훈련
GPU가 매년 더 강력해지고 있지만 딥러닝 모델은 점점 더 커지고 더 많은 계산 자원을 필요로 한다. 하나의 GPU에서 훈련하는 것은 실험을 빠르게 반복하는 데 큰 제한 요소가 되는데 해결책은 간단하게 GPU를 더 추가하고 다중 GPU 분산 훈련을 시작하면 된다.
여러 개의 장치에 연산을 분산시키는 방법은 두 가지이다.
데이터 병렬화(data parallelism)와 모델 병렬화(model parallelism)
데이터 병렬화에서는 하나의 모델이 여러 개의 장치 또는 여러 대의 머신에 복제된다. 모델의 복제본은 각기 다른 데이터 배치를 처리한 후 결과를 합친다. 모델 병렬화에서는 한 모델의 각기 다른 부분이 여러 장치에서 실행되면서 동시에 하나의 데이터 배치를 처리한다. 이 방식은 여러 브랜치(branch)가 있는 모델처럼 태생적으로 병렬 구조를 지원하는 모델에 잘 맞다.
2개 이상의 GPU 활용하기
먼저 여러 개의 GPU를 사용할 수 있어야 한다. 현재 구글 코랩은 하나의 GPU만 제공하므로 다음 중 하나의 방법을 사용해야 합니다.
- 2~4개의 GPU를 준비하여 한 컴퓨터에 설치한다(강력한 전원 공급 장치가 필요할 것). 그다음 CUDA 드라이버, cuDNN 등을 설치한다. 대부분의 경우 이는 최선이 아니다.
- 구글 클라우드, 애저(Azure), AWS에서 다중 GPU 가상 머신(Virtual Machine, VM)을 임대한다. 드라이버와 소프트웨어가 미리 설치된 VM 이미지를 사용할 수 있으며 설정 작업에 많은 노력이 들지 않을 것이다. 연중무휴로 모델을 훈련하지 않는 경우 가장 좋은 방법일 것이다.
단일 호스트, 다중 장치 동기 훈련
다중 GPU가 장착된 단일 머신에서는 import tensorflow 명령을 실행한 후 분산 모델을 훈련하는 데 몇 초밖에 걸리지 않는다.
여기에서 구현한 몇 줄의 코드가 가장 일반적인 훈련 설정이다. 단일 호스트 다중 장치 동기 훈련(single-host multi-device synchronous training)이며 텐서플로에서는 ‘미러링된 분산 전략(mirrored distribution strategy)’이라고도 부른다. ‘단일 호스트’는 여러 개의 GPU가 한 머신에 설치되어 있다는 뜻이다(이와 반대는 각자 GPU를 가지고 네트워크로 통신하는 여러 대의 머신으로 구성된 클러스터). ‘동기 훈련’은 각 GPU에 복제된 모델의 상태가 항상 모두 동일하다는 의미다. 이와 다른 분산 훈련 전략도 있다.
MirroredStrategy로 with 문을 시작하여 그 안에서 모델을 만들 때 MirroredStrategy 객체가 가능한 GPU마다 복제 모델(replica)을 하나씩 만든다. 그다음 훈련의 각 스텝이 다음과 같은 방식으로 전개된다.
- 데이터셋에서 배치 데이터(글로벌 배치(global batch))가 추출된다.
- 이를 4개의 서브 배치(로컬 배치(local batch))로 분할한다. 예를 들어 글로벌 배치가 512개의 샘플로 구성된다면 4개의 로컬 배치는 128개의 샘플을 가지게 될 것이다. GPU가 바쁘게 실행되도록 로컬 배치가 충분히 커야 하므로 글로벌 배치 크기는 일반적으로 매우 커야 한다.
- 4개의 복제 모델 각각은 하나의 로컬 배치를 자신의 장치에서 독립적으로 처리한다. 정방향 패스와 역방향 패스를 실행한다. 각 복제 모델은 로컬 배치에서 손실에 대한 이전 가중치의 그레이디언트가 주어지면 모델의 가중치 변수가 얼마나 업데이트되어야 하는지 나타내는 ‘가중치 델타(delta)’를 만든다.
- 로컬 그레이디언트에서 만들어진 가중치 델타는 4개의 복제 모델로부터 효율적으로 수집되어 글로벌 델타를 만든다. 이 글로벌 델타가 모든 복제 모델에 적용된다. 이 과정이 매 스텝의 끝에서 수행되기 때문에 복제 모델은 항상 동기화된 상태이다. 즉, 모든 복제 모델의 가중치가 항상 같다.
이상적으로는 N개의 GPU에서 훈련하면 N배 속도가 빨라진다. 하지만 실제로는 분산 처리에 약간의 오버헤드(overhead)가 있다. 특히 여러 장치에서 가중치 델타를 합치는 데 약간의 시간이 걸린다. 실제 속도 향상은 사용하는 GPU 개수에 따라 결정된다.
- 2개의 GPU를 사용하는 경우 속도는 거의 2배에 가깝게 빨라집니다.
- 4개의 GPU를 사용하는 경우 속도는 약 3.8배 빨라집니다.
- 8개의 GPU를 사용하는 경우 속도는 약 7.3배 빨라집니다.
이는 각 GPU를 최대로 활용하도록 충분히 큰 글로벌 배치를 사용한다고 가정한다. 글로벌 배치 크기가 너무 작으면 로컬 배치 크기가 충분하지 않아 GPU 성능을 최대로 활용하지 못할 것다.
13.2.3 TPU 훈련
GPU뿐만 아니라 딥러닝 워크플로를 위해 특별하게 설계된 전문 하드웨어(예를 들어 ASIC(Application-Specific Integrated Circuits)로 알려진 단일 목적의 칩(chip))로 딥러닝 분야가 점점 이동하는 경향이 있다. 크고 작은 많은 회사가 새로운 칩을 만들고 있지만 오늘날 가장 앞서 있는 것은 구글의 TPU(Tensor Processing Unit)다. TPU는 구글 클라우드와 구글 코랩에서 사용할 수 있다.
TPU에서 훈련하려면 몇 가지 작업을 처리해야 하지만 그럴 만한 가치가 있다. TPU는 정말 매우 빠르다. TPU V2는 NVIDIA P100 GPU에서 훈련하는 것보다 15배 빠르다. 대부분의 모델에서 TPU는 평균적으로 GPU보다 3배 이상 비용 효율적이다.
구글 코랩에서 TPU 사용하기
코랩에서 8 코어 TPU를 무료로 사용할 수 있다. 코랩 런타임 메뉴 아래에 있는 런타임 유형 변경을 선택하면 하드웨어 가속기로 GPU 외에도 TPU를 선택할 수 있다.
GPU를 사용할 때는 특별한 작업을 하지 않더라도 모델이 바로 GPU를 사용할 수 있다. TPU의 경우는 다르다. 모델을 만들기 전에 추가적인 단계가 필요한데, TPU 클러스터에 연결해야 한다.
이제 훈련을 시작할 준비를 거의 마쳤다. 하지만 코랩의 TPU에는 조금 특이한 점이 있다. VM이 2개다. 노트북 런타임을 호스팅하는 VM은 TPU가 있는 VM과 다르다. 이 때문에 로컬 디스크(노트북이 호스팅된 VM에 링크된 디스크)에 저장된 파일에서 훈련할 수 없다. TPU 런타임이 이 디스크를 읽을 수 없기 때문이다. 데이터를 로딩하는 두 가지 방법이 있다.
- (디스크가 아니라) VM의 메모리에 있는 데이터로 훈련한다. 데이터가 넘파이 배열이라면 이미 이렇게 하고 있는 것이다.
- 데이터를 GCS(Google Cloud Storage) 버킷(bucket)에 저장하고, 데이터를 로컬로 내려받지 않고 바로 버킷에서 읽어 들이는 데이터셋을 만든다. TPU 런타임은 GCS에서 데이터를 읽을 수 있다. 데이터를 모두 메모리에 로드하기에 너무 큰 경우 이것이 유일한 방법이다.
스텝 융합을 활용하여 TPU 활용도 높이기
TPU의 컴퓨팅 성능이 높기 때문에 TPU 코어를 잘 활용하려면 매우 큰 배치로 훈련해야 한다. 작은 모델의 경우 배치 크기가 너무 커질 수 있다. 예를 들어 배치에 1만 개 샘플까지 들어 갈 수 있다. 이렇게 배치가 큰 경우에는 옵티마이저의 학습률을 높여야 한다. 가중치를 더 적게 업데이트하겠지만 각 업데이트는 더 정확할 것이다(더 많은 데이터를 사용하여 그레이디언트를 계산하기 때문). 따라서 각 업데이트마다 가중치를 큰 폭으로 이동해야 한다.
하지만 TPU를 최대한 활용하면서 합리적인 크기의 배치를 유지하는 간단한 트릭이 있다. 스텝 융합(step fusing)이다. 이 아이디어는 TPU 실행 스텝마다 여러 훈련 스텝을 실행하는 것이다. 요컨대 VM 메모리에서 TPU로 두 번 왕복하는 사이에 더 많은 작업이 수행된다. 이렇게 하려면 compile() 메서드에 steps_per_execution 매개변수를 지정하면 된다. 예를 들어 steps_per_execution=8은 TPU 실행마다 훈련 스텝을 여덟 번 실행한다. TPU를 최대로 활용하지 못하는 작은 모델의 경우 이렇게 하면 속도를 극적으로 향상시킬 수 있다.
13.3 요약
- 하이퍼파라미터 튜닝과 KerasTuner를 사용하여 최상의 모델 설정을 찾는 과정을 자동화할 수 있습니다. 하지만 검증 세트 과대적합을 주의하세요!
- 다양한 모델의 앙상블은 종종 예측 품질을 크게 향상시킬 수 있습니다.
- 혼합 정밀도로 바꾸면 GPU에서 모델 훈련 속도를 높일 수 있습니다. 사실상 비용을 들이지 않고 속도가 향상됩니다.
- 워크플로의 규모를 늘리려면 tf.distribute.MirroredStrategy API를 사용하여 여러 개의 GPU에서 모델을 훈련할 수 있습니다.
- TPUStrategy를 사용하여 (코랩에서 사용 가능한) 구글 TPU에서 훈련할 수도 있습니다. 작은 모델의 경우 TPU 코어를 최대한 활용하기 위해 (compile(…, steps_per_execution=N)으로) 스텝 융합을 사용하세요.
'AI 입문 > Deep Learning' 카테고리의 다른 글
12장 - 생성 모델을 위한 딥러닝 (0) | 2023.02.23 |
---|---|
11장 - 텍스트를 위한 딥러닝 (1) | 2023.02.12 |
10장 - 시계열을 위한 딥러닝 (0) | 2023.02.08 |
9장 - 컴퓨터 비전을 위한 고급 딥러닝 (0) | 2023.01.22 |
8장 - 컴퓨터 비전을 위한 딥러닝 (0) | 2023.01.22 |