본문 바로가기
ML & DL/tensorflow

[tensorflow] Custom Loss (Huber Loss, Contrastive Loss 구현)

by 별준 2021. 1. 11.

(tensorflow v2.4.0)

 

tensorflow에서 기본적으로 Loss function은 아래와 같은 방법들로 사용할 수 있습니다. mean spuare error loss를 예시로 살펴보도록 하죠.

# 1
model.compile(loss='mse', optimizer='sgd')

from tensorflow.keras.losses import mean_squared_error
# 2-1
model.compile(loss=mean_squared_error, optimizer='sgd')
# 2-2 using hyperparameters
model.compile(loss=mean_squared_error(param=value), optimizer='sgd')

2-2번의 경우에는 제공되는 loss function의 hyperparameter를 적용하는 경우입니다. 이 경우에는 hyperparameter를 튜닝해서 적절한 loss를 사용할 수 있도록 만들어줍니다.

 

하지만, 복잡한 모델을 구성하거나, 특별한 Loss function이 필요한 경우에는 직접 Loss function을 구현해서 사용할 수도 있습니다.

custome loss function은 기본적으로 아래의 형태로 정의할 수 있습니다.

def my_loss_function(y_true, y_pred):
	return losses

y_true는 dataset의 true label이며, y_pred는 현재 예측한 값을 의미합니다. 이렇게 함수의 형태를 가지고, loss는 함수 내에서 계산되어서 return 되어집니다.

 

Huber Loss를 예제로 custom loss를 구현해보도록 합시다.

Custom Loss Function 구현(Huber Loss)

Huber Loss는 squared error loss보다 data의 outlier에 덜 민감한 loss function으로, 여기서 \(\delta\)는 threshold이며, a는 두 값(y_true와 y_pred)의 차이(error)입니다. error의 절대값이 threshold보다 작으면 L2 loss의 형태로 계산되고, 크다면 L1 loss의 꼴로 계산됩니다.

Huber Loss는 아래와 같이 계산됩니다.

그럼 Huber Loss function을 바로 구현해보도록 하겠습니다.

import tensorflow as tf

def my_huber_loss(y_true, y_pred):
    threshold = 1
    error = y_true - y_pred
    is_small_error = tf.abs(error) <= threshold
    small_error_loss = tf.square(error) / 2
    big_error_loss = threshold * (tf.abs(error) - (0.5 * threshold))
    return tf.where(is_small_error, small_error_loss, big_error_loss)

is_small_error를 통해서 threshold보다 작은 값의 부울값을 가지고, tf.where 함수를 통해 True라면 small_error_loss를 사용하고 False라면 big_error_loss를 사용하고 있습니다.

 

위 loss를 가지고, 간단한 regression 문제를 풀어보도록 하겠습니다.

import numpy as np

# inputs
xs = np.array([-1.0,  0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)
# labels
ys = np.array([-3.0, -1.0, 1.0, 3.0, 5.0, 7.0], dtype=float)

model = tf.keras.Sequential([keras.layers.Dense(units=1, input_shape=[1])])
model.compile(optimizer='sgd', loss=my_huber_loss)
model.fit(xs, ys, epochs=500,verbose=0)
print(model.predict([10.0]))

x와 y는 y=2x-1을 가지며, 실제값은 19이지만, 18.418222로 예측하고 있습니다. 'mean_squared_error'로 학습한 것과 큰 차이가 없기 때문에 이 경우에는 Huber Loss가 그리 큰 효과는 없는 것으로 보입니다.

 

Hyperparameter를 적용한 Custom Loss Function

위에서 custom loss 함수를 통해서 Huber loss를 구현해보았습니다. 하지만 위 함수를 살펴보면 threshold는 언제든지 변할 수 있는 파라미터라는 것을 확인할 수 있습니다. 매번 새롭게 함수를 정의하는 것이 아닌, threshold와 같은 hyperparameter를 쉽게 변경하기 위해서는 다음과 같은 방법을 사용해서 custom loss function을 구현할 수 있습니다.

def my_huber_loss_with_threshold(threshold):
    def my_huber_loss(y_true, y_pred):
        threshold = 1
        error = y_true - y_pred
        is_small_error = tf.abs(error) <= threshold
        small_error_loss = tf.square(error) / 2
        big_error_loss = threshold * (tf.abs(error) - (0.5 * threshold))
        return tf.where(is_small_error, small_error_loss, big_error_loss)
    return my_huber_loss

실제로 model.compile의 loss parameter에서 사용되는 loss function은 인자로 y_true와 y_pred만을 가져야하기 때문

처음 함수 형태로는 함수 인자에 parameter를 추가할 수 없습니다.

따라서, 위와 같이 함수 안에 loss를 계산하는 함수를 감싸고, 바깥 함수의 인자를 통해서 hyperparameter를 입력받아서 유연하게 loss function을 적용할 수 있습니다. 

 

또한, loss function은 Loss Class를 상속받아서 클래스의 형태로도 구현이 가능합니다.

Loss Class를 통해 Custom Loss Function 구현

from tensorflow.keras.losses import Loss

class MyHuberLoss(Loss):
    def __init__(self, threshold = 1):
        super().__init__()
        self.threshold = threshold
    
    def call(self, y_true, y_pred):
        error = y_true - y_pred
        is_small_error = tf.abs(error) <= self.threshold
        small_error_loss = tf.square(error) / 2
        big_error_loss = self.threshold * (tf.abs(error) - (0.5 * self.threshold))
        return tf.where(is_small_error, small_error_loss, big_error_loss)

Class를 상속받은 custom loss는 위와 같이 구현할 수 있습니다.

__init__ 함수를 통해서 base class의 속성을 상속받고, 필요한 hyperparameter를 인자로 입력받을 수 있습니다. 

그리고, call(self, y_true, y_pred) 함수를 오버라이딩하여 실제 loss를 계산해서 그 값을 리턴합니다.

 

이렇게 custom loss function을 구현하는 3가지 방법에 대해서 알아보았습니다.

이제, 위 방법들을 모두 사용해서 이전 게시글에서 사용한 Contrastive loss function을 직접 구현해보도록 하겠습니다.

2021/01/11 - [ML & DL/tensorflow] - [tensorflow] Siamese Network (Fashion MNIST 비교 모델 구현)

 

Contrastive Loss 구현

Constrastive loss는 두 이미지의 유사도를 측정하기위해 사용되며, 자세한 내용은 다음 논문을 참조하시길 바랍니다. (paper)

Constrastive Loss는 다음의 공식으로 계산됩니다.

\[Loss = Y_{true}\star Y_{pred}^2 + (1 - Y_{true})\star max(\text{margin} - Y_{pred}, 0)^2\]

여기서 margin은 hyperparameter가 되겠죠.

 

첫번째는 margin이 고정된 loss function입니다.

import tensorflow.keras.backend as K

def contrastive_loss(y_true, y_pred):
    margin = 1
    square_pred = K.square(y_pred)
    margin_square = K.square(K.maximum(margin - y_pred, 0))
    return K.mean(y_true * square_pred + (1 - y_true) * margin_square)

 

두번째는 margin을 파라미터로 입력받을 수 있도록 loss function을 외부 함수로 감싼 형태의 loss function입니다.

def contrastive_loss_with_margin(margin):
    def contrastive_loss(y_true, y_pred):
        square_pred = K.square(y_pred)
        margin_square = K.square(K.maximum(margin - y_pred, 0))
        return K.mean(y_true * square_pred + (1 - y_true) * margin_square)
    return contrastive_loss

 

마지막으로 Loss class를 상속받은 custom loss class입니다.

class ContrastiveLoss(Loss):
    def __init__(self, margin=1):
        super().__init__()
        self.margin = margin
    
    def call(self, y_true, y_pred):
        square_pred = K.square(y_pred)
        margin_square = K.square(K.maximum(self.margin - y_pred, 0))
        return K.mean(y_true * square_pred + (1 - y_true) * margin_square)

 

 

지금까지 tensorflow의 custom loss function에 대해서 알아보았습니다.

 

 

 

- 참조

Coursera - Custom Models, Layers, and Loss Functions with TensorFlow : week 2

댓글