-
Loss functions: MSE, BCE, CCE | BLAS 문제를 만나다 | 패스트캠퍼스 챌린지 03일차딥러닝 2022. 1. 26. 21:32
지도학습(Supervised Learning)일 경우, 딥러닝 모델에는 input 값 X와 정답이라고 할 수 있는 output 값 Y으로 구성된 데이터셋이 들어가게 된다. 딥러닝 모델은 X의 값 x를 가지고 예측값 y_hat을 내보내게 된다. 딥러닝의 과정은 y와 y_hat을 최대한 '비슷하게' 만드는 과정인데, 이 '비슷하지 않음'을 수학적으로 정의한느 것이 loss function이다. 즉, loss function은 지도학습에서 정답과 예측값 사이의 차이를 수학적으로 구현한 것이다.
이 때, y_hat은 회귀문제(regression)일 경우 실수에 해당하는 연속변수를 가지게 되며, 분류문제(classification)일 경우 카태고리를 집합으로 가지는 이산변수(즉, 연속이 아닌 숫자)를 가지게 된다. 여기에 더 나아가 회귀문제의 경우 y나 y_hat 값 사이의 대소 비교를 진행할 수 있다. 가령, 시험 점수를 예측하는 모델의 y와 y_hat은 점수이므로, y(_hat) 값을 가지고 비교하는 것이 의미가 있다. 하지만 분류문제의 경우 카테고리 변수이다. 카테고리는 흔히 0, 1, 2, 3, ... K까지의 자연수로 정의가 되는데, 가령 이진분류에서 고양이를 0, 개를 1이라고 정의했다고 하자. 이 경우 1 > 0이지만 그렇다고 (개) > (고양이)와 같이 비교할 수는 없다.
회귀문제에서는 Mean Squared Error(MSE) loss function을 사용한다. 이 loss function은 말 그대로 y와 y_hat 사이의 차(error)를 제곱(squared)해 이 평균(mean)을 낸 모습을 하고 있다. 이 loss function은 0에서 무한대 까지의 값을 가지며, 당연히 y와 y_hat 사이의 차가 적을수록 0에 가깝고, y에 대해 정리하면 2차식 모습을 하므로 그래프는 포물선 모양을 띈다. 이는 아마 경사하강법을 설명할 때 쓰이기 떄문에 짚고 넘어간 듯 하다.
분류문제에서는 Sigmoid를 통하는 이진분류의 경우 Binary Cross Entropy(BCE)를 사용한다. Binary classification의 속성을 생각했을 때, loss값은 y=1일 때 y_hat이 1에 가까울수록 0이어야 하며, 0에 가까울수록 무한대에 가까워야 한다. 노트 정리에 그렸듯 이러한 조건을 충족하는 함수는 y=-log(x)이다. 마찬가지로, y=0일 때에는 반대 상황이 되어서 y = -log(1-x)함수가 해당 조건을 충족한다. 그렇다면 loss function은 이 두 경우를 모두 포함할 수 있도록 설계해야 하는데, 이에 해당하는게 -[ylog(y_hat) + (1-y)log(1-y_hat)]이다. 긴 수식이지만 잘 보면 y=0일 경우 뒷부분의 수식인 y = -log(1-x)만 삼아남는 것을, y=1일 경우 앞 부분인 y = -log(x)만 살아남는 것을 살펴볼 수 있다. 비슷한 개념으로 Softmax를 통하는 다항분류를 위한 Categorical Cross Entropy(CCE)를 정의할 수 있다.
이러한 cross entropy가 batch에 적용될때는 cross entropy의 평균을 낸다.
참고로 MSE에서 y_hat은 activation을 거치지 않은 affine transformation 자체의 결과이며, Cross Entropy에서는 Sigmoid나 Softmax를 거친 확률 값이 y_hat이 된다.
사실 이렇게 수식으로 정리하지만 이 모든 내용은 tensorflow 등의 라이브러리 내부에 구현되어있다. 가령, CCE 같은 경우 from tensorflow.keras.losses import CategoricalCrossentropy로 불러와서 사용하면 그만이다. 다만, 그 뒤에 있는 수식을 살펴보는 습관이 있어야 loss function을 설계할 때에도 적용할 수 있을 것이라는 생각이 든다. Loss function이나 activation function을 살펴본 소감은 복잡하게 설계하기 보다는 중, 고등학교 수학에서 배우는 간단한 함수를 가지고 추상적으로 정의된 것에 수학적 의미를 부여한 것에 불과하다는 것을 알 수 있다. 가령, Sigmoid의 경우 Logit의 수식을 확률 p에 대해 정리하여 이와 똑같이 생긴 Sigmoid 함수를 확률에 대한 함수라 정의하였다. 이런식으로 간단한 함수지만 추상적인 개념을 잘 대변하는 함수를 가져와서 사용하면 되는 것이다.
해당 부분 구현 강의에서 하나의 dense layer로 이루어진 모델을 tensorflow로 구현하여 실행하였을 떄 model을 호출하여 데이터 x를 집어넣는 과정에서 오류가 발생했다. 에러메시지의 핵심은 Blas xGEMV launch failed였다. BLAS는 Basic Linear Algebra Subroutines로, 행렬 및 벡터 연산에 필요한 서브루틴들이 정리되어 있는 것이며, xGEMV는 general matrix-vector multiplication을 위한 routine이다(https://www.tat.physik.uni-tuebingen.de/~kley/lehre/ftn77/tutorial/blas.html). 이 에러 메세지 자체를 검색하면 나오는 건 없지만, 유사하게 Blas GEMM launch failed라는 에러는 종종 사람들이 맞딱드리는 것 같다. (GEMM은 BLAS의 다른 연산으로, 일반 행렬 곲 루틴이다; https://velog.io/@springkim/BLAS-Matrix-Multiplication-Library). 그렇게 도움이 되는 에러메세지는 아니지만(인터넷에서도 정작 이것의 의미에 대한 논의는 거의 없다) 해결책은 몇가지 돌아다니고 있다. 우선 NVIDIA-SMI로 메모리가 점유되지 않았음을 확인하라는 조언이 있었는데, 나의 경우 여기에 해당하지 않았다.
정작 문제를 해결한 건 아래 코드 3줄이었다.
import tensorflow as tf gpus = tf.config.experimental.list_physical_devices(device_type='GPU') tf.config.experimental.set_memory_growth(gpus[0], True)
이 코드를 셀로 가장 위에 넣어준 뒤, Restart Kernel 한 다음 이 셀을 문제가 되는 model 호출 이전에 실행시키니 문제가 해결되었다. 이러한 오류가 생기는 궁극적인 이유로는 CUDA, cuDNN, Tensorflow 세 개 간의 버전 충돌인 듯 한데, 나중에 시간을 두고 모든걸 새로 설치해야 할 듯 하다. 일단 임시방편으로 위의 코드를 통해 해결할 수 있었으며, 이 원리는 GPU 장치에 대해 메모리가 동적으로 할당될 수 있도록 명시해 주는 것이다(https://programmerah.com/solved-tensorflow-error-failed-to-create-cublas-handle-cublas_status_alloc_failed-42532/). 참고로 위 코드 자체는 한 개의 GPU에만 적용되는 방법이다. 해당 문제가 발생한 환경은 3060 Laptop GPU를 사용하는 컴퓨터로, CUDA Version 11.5, cuDNN 8.3.2, Tensorflow 2.7.0이 설치되어 있었다.
'딥러닝' 카테고리의 다른 글
Convolution Layers with Multiple Channel | Pooling, Striding, Padding | 패스트캠퍼스 챌린지 05일차 (0) 2022.01.28 Convolution Layers | 패스트캠퍼스 챌린지 04일차 (0) 2022.01.27 Activation Functions: Sigmoid & Softmax | 패스트캠퍼스 챌린지 02일차 (0) 2022.01.25 인공 뉴런과 Dense Layer | 패스트캠퍼스 챌린지 01일차 (0) 2022.01.24 딥러닝 강의를 수강하기 앞서 : 컴퓨터 환경 구축하기 (0) 2022.01.23