HOWTO · TensorFlow

TensorFlow를 사용하여 이진 교차 엔트로피 손실 값 찾기

이 기사에서는 이진 교차 엔트로피 손실을 찾는 두 가지 방법인 TensorFlow 프레임워크와 이론적 계산에 대해 설명합니다.

이 페이지의 내용

이진 교차 엔트로피 손실은 두 가지 방법으로 계산할 수 있습니다. NumPy에서 수식을 직접 구현하는 방법과 TensorFlow의 내장 tf.keras.losses.BinaryCrossentropy를 사용하는 방법입니다. 이 문서에서는 두 방법을 모두 살펴보고, 두 방법이 거의 동일한 손실 값을 만들어 낸다는 것을 보여 줍니다.

이진 교차 엔트로피가 측정하는 것

손실 함수는 모델의 예측이 실제 레이블과 얼마나 떨어져 있는지를 측정합니다. 이진 교차 엔트로피(로그 손실이라고도 함)는 이진 분류 문제의 표준 손실입니다. 이진 분류에서는 각 실제 레이블이 0 또는 1이고, 모델은 해당 샘플이 클래스 1에 속할 확률을 출력합니다.

이진 교차 엔트로피는 보정된 예측 확률의 로그에 대한 음의 평균입니다.

N개 샘플로 이루어진 배치에 대한 이진 교차 엔트로피는 다음과 같습니다.

$$
\text { Log loss }=\frac{1}{N} \sum_{i=1}^{N}-\left(y_{i} * \log \left(p_{i}\right)+\left(1-y_{i}\right) * \log \left(1-p_{i}\right)\right)
$$

여기서 y_i는 실제 레이블(0 또는 1)이고 p_i는 해당 샘플에 대한 모델의 예측 확률입니다. 각 샘플에서는 두 로그 항 중 하나만 기여합니다. y_i = 1이면 손실은 -log(p_i)이고, y_i = 0이면 -log(1 - p_i)입니다. 확신을 가지고 맞힌 예측은 거의 기여하지 않는 반면, 확신을 가지고 틀린 예측은 큰 값을 기여합니다. 합계를 N으로 나누기 때문에 결과는 샘플당 평균 손실이 됩니다.

NumPy로 이진 교차 엔트로피 계산하기

이 수식은 NumPy 함수로 바로 옮길 수 있습니다. y_true에는 실제 레이블이, y_pred에는 모델의 예측 확률이 들어 있으며 둘 다 열 벡터입니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

import numpy as np


def BinaryCrossEntropy(y_true, y_pred):
    y_pred = np.clip(y_pred, 1e-7, 1 - 1e-7)
    term_0 = y_true * np.log(y_pred + 1e-7)
    term_1 = (1 - y_true) * np.log(1 - y_pred + 1e-7)
    return -np.mean(term_0 + term_1, axis=0)


print(
    BinaryCrossEntropy(
        np.array([1, 0, 1]).reshape(-1, 1), np.array([0, 0, 1]).reshape(-1, 1)
    )
)
[5.14164949]

np.clip(y_pred, 1e-7, 1 - 1e-7)은 모든 예측을 [1e-7, 1 - 1e-7] 구간 안으로 제한하므로 [0, 0, 1]은 [1e-7, 1e-7, 0.9999999]가 됩니다. 수식에 log(p)와 log(1 - p)가 포함되어 있기 때문에 이 처리가 필요합니다. 0의 자연로그는 정의되지 않으므로 정확히 0 또는 1인 예측은 손실을 계산할 수 없게 만듭니다. 클리핑은 모든 로그의 인수를 유한한 값으로 유지하며, 각 log 호출 안에 더해지는 작은 1e-7도 같은 방식으로 보호합니다. 이어서 np.mean(..., axis=0)이 샘플별 손실의 평균을 구하는데, 이것이 수식의 1/N 계수에 해당합니다.

이 실행에서는 첫 번째 샘플이 확신을 가지고 틀렸습니다(실제 레이블 1, 예측 0). 따라서 이 샘플의 큰 손실이 평균을 지배하여 결과는 약 5.14가 됩니다.

TensorFlow로 이진 교차 엔트로피 계산하기

TensorFlow는 같은 손실을 내장 클래스 tf.keras.losses.BinaryCrossentropy로 제공합니다. 이를 사용하려면 tensorflow 패키지가 필요하며 pip install tensorflow로 설치할 수 있습니다. 여기서는 이 의존성이 설치되어 있다고 가정하지 않으므로, 이 호출은 실행된 출력 블록 대신 인라인 코드로 보여 줍니다. 로컬 환경에서 직접 실행해 결과를 재현해 보세요.

import tensorflow as tf 다음에 bce = tf.keras.losses.BinaryCrossentropy(from_logits=False, reduction=tf.keras.losses.Reduction.SUM_OVER_BATCH_SIZE)로 손실 객체를 만들고, NumPy 예제에서 사용한 것과 동일한 y_true 및 y_pred 배열로 호출합니다. bce(y_true, y_pred) 호출은 두 개의 인수를 받습니다.

  1. y_true(실제 레이블): 각 샘플에 대해 0 또는 1입니다.
  2. y_pred(예측 값): 모델의 출력으로, from_logits=True일 때 사용되는 [-inf, inf] 범위의 값인 logit이거나, from_logits=False일 때 사용되는 [0., 1.] 범위의 확률입니다.

두 설정이 호출이 이 입력을 해석하는 방식을 결정합니다. from_logits=False는 y_pred에 이미 확률이 들어 있음을 선언하고, reduction=tf.keras.losses.Reduction.SUM_OVER_BATCH_SIZE는 샘플별 손실을 배치에 대해 평균합니다. 이는 수식의 1/N 계수와 같습니다. 이 호출은 텐서를 반환하며 loss.numpy()로 스칼라 값을 가져올 수 있습니다. 동일한 입력에서는 약 5.37이 되며, NumPy 결과와 가깝지만 정확히 같지는 않습니다. 나머지 인수는 공식 tf.keras.losses.BinaryCrossentropy API 레퍼런스를 참조하세요.

손실 해석 및 확률과 로짓 중 입력 선택

입력 형식을 from_logits에 맞추세요. from_logits=False이면 [0, 1] 범위의 확률, 보통 시그모이드 레이어의 출력을 전달합니다. from_logits=True이면 로짓, 즉 손실이 내부적으로 시그모이드를 거쳐 매핑하는 [-inf, inf] 범위의 제한 없는 원시 점수를 전달합니다. from_logits=False인 상태에서 로짓을 넣거나 True인 상태에서 확률을 넣으면 경고 없이 의미 없는 손실 값이 나옵니다.

동일한 입력에서는 두 구현이 모두 같은 샘플별 평균 손실을 계산하지만, 결과는 대략적으로만 일치합니다. 출력된 5.14164949는 클리핑한 뒤 각 log 호출 안에 1e-7을 더해 계산된 값인 반면, tf.keras.losses.BinaryCrossentropy는 자체 내부 엡실론으로 예측을 클리핑합니다. 리덕션 설정과 부동 소수점 정밀도도 끝자리 숫자를 바꿀 수 있습니다.

손실이 낮다는 것은 해당 배치에서 예측이 실제 레이블에 더 가깝다는 뜻이며, 이것이 손실이 학습 신호로 유용한 이유입니다. 다만 낮은 값 하나만으로 모델이 전반적으로 좋다고 증명되는 것은 아니므로, 홀드아웃 데이터에 대한 메트릭으로도 품질을 함께 판단하세요.