[class weight] multi-output model에 적용하는 방법

김고은·2022년 5월 2일
0
# a,b = class_weight_func(dataset_path,classes,mode ='train',extension='png')
# class_weights = [{0:a, 1:b}, None] # AttributeError: 'list' object has no attribute 'keys'
# class_weights = {'clf':{0:a, 1:b}} # ValueError: Expected `class_weight` to be a dict with keys from 0 to one less than the number of classes
# class_weights={'clf':{0:a, 1:b}} # ValueError: Expected `class_weight` to be a dict with keys from 0 to one less than the number of classes
# class_weights={{0:a, 1:b}, {0:1.0, 1:1.0}} # typeError: unhashable type: 'dict'

참고: https://gist.github.com/wassname/ce364fddfc8a025bfab4348cf5de852d

1. 함수 정의

from keras import backend as K
def weighted_categorical_crossentropy(weights):
"""
A weighted version of keras.objectives.categorical_crossentropy

Variables:
    weights: numpy array of shape (C,) where C is the number of classes

Usage:
    weights = np.array([0.5,2,10]) # Class one at 0.5, class 2 twice the normal weights, class 3 10x.
    loss = weighted_categorical_crossentropy(weights)
    model.compile(loss=loss,optimizer='adam')
"""

weights = K.variable(weights)
    
def loss(y_true, y_pred):
    # scale predictions so that the class probas of each sample sum to 1
    y_pred /= K.sum(y_pred, axis=-1, keepdims=True)
    # clip to prevent NaN's and Inf's
    y_pred = K.clip(y_pred, K.epsilon(), 1 - K.epsilon())
    # calc
    loss = y_true * K.log(y_pred) * weights
    loss = -K.sum(loss, -1)
    return loss

return loss

2. weight 지정

weights = np.array([class_weight_0_Class, class_weight_1_class])

3. loss 설정

loss_custom = weighted_categorical_crossentropy(weights)

4. compile

model.compile(loss = loss1)

아래 방법은 시도했으나 실패한 방법이다.

I. keras(tensorflow)에서 일반적으로 class weight를 줄 때, (single output model)

일반적으로 단일 출력단을 갖는 모델일 때는, 각 class에 대해 label이 one-hot encoding 되어있든, integer-encoding 되어있든 다음과 같이 dict 형태로 class_weight를 작성한 다음 fit함수 안에 넣어주면 된다.

import keras

class_weight = {"buy": 0.75,
                "don't buy": 0.25}

model.fit(X_train, Y_train, epochs=10, batch_size=32, class_weight=class_weight)

출처: https://3months.tistory.com/414 [Deep Play]

II. multi-output model 에서 class weight를 줄 때,

하지만 내가 학습시키는 모델은 출력단에 classification layer와 regression layer 를 함께 갖고 있는 모델이다.

base_model = model_function(input_shape=INPUT_SHAPE,include_top=False,weights='imagenet')
x = GlobalAveragePooling2D()(base_model.output)
x = Dense(160, activation='relu')(x)
x = Dropout(0.3)(x)

reg = Dense(1,activation='sigmoid',name = 'reg')(x)
output = Dense(class_num, activation=last_activation,name = 'clf')(reg)

model = Model(inputs = [base_model.input], outputs = [output,reg])

이런 multi ouput model 에 일반적인 방법으로 class weight을 주면, 다음과 같은 여러 에러들이 발생한다.

#class_weights = [{0:a, 1:b}, None] # AttributeError: 'list' object has no attribute 'keys'
#class_weights = {'clf':{0:a, 1:b}} # ValueError: Expected `class_weight` to be a dict with keys from 0 to one less than the number of classes
#class_weights={'clf':{0:a, 1:b}} # ValueError: Expected `class_weight` to be a dict with keys from 0 to one less than the number of classes
#class_weights={{0:a, 1:b}, {0:1.0, 1:1.0}} # typeError: unhashable type: 'dict'

구글링해서 찾은 방법

출처: https://datascience.stackexchange.com/questions/41698/how-to-apply-class-weight-to-a-multi-output-model

1. categorical_crossentropy 작성

나는 함수 이름을 w_categorical_crossentropy라고 했다.
weight는 cxc의 matrix다. (c는 class 수이다)

More precisely, weights[i, j] 에서 i(=column, 행렬에서 세로기준으로 볼 때)는 실제 class label이고, j(=rows, 행렬에서 가로기준으로 볼 때)는 모델이 예측한 class이다.

def w_categorical_crossentropy(y_true, y_pred, weights):
  
    nb_cl = len(weights)
    final_mask = K.zeros_like(y_pred[:, 0])
    y_pred_max = K.max(y_pred, axis=1)
    y_pred_max = K.reshape(y_pred_max, (K.shape(y_pred)[0], 1))
    y_pred_max_mat = K.cast(K.equal(y_pred, y_pred_max), K.floatx())
    
    for c_p, c_t in product(range(nb_cl), range(nb_cl)):
        final_mask += (weights[c_t, c_p] * y_pred_max_mat[:, c_p] * y_true[:, c_t])
    return K.categorical_crossentropy(y_pred, y_true) * final_mask

2. weight matrix 정의

multi-output 모델에서 regression layer는 keras에서 제공하는 loss function(ex. 'mse')를 사용하고, classification layer의 output에 대해서만 class weight를 주고 싶다면, w2는 필요하지 않다. (w1만 작성하면 된다.)
1) 먼저 모두 1로 채워진 class num x class_num의 행렬을 만든다.
2) 찾고자 하는 (목표로 하는) class label(
여기선 1) 의 실제 label 축에 주려고 하는 class weight값(여기선 10)을 준다.

w1 = np.ones((2, 2))
w1[1, 0] = 10
w1[1, 1] = 10

w2 = np.ones((3, 3))
w2[0, 0] = 5
w2[0, 1] = 5
w2[0, 2] = 5
w2[2, 0] = 10
w2[2, 1] = 10
w2[2, 2] = 10 

3. weight matrix를 적용한 class weight function = loss function 정의

마찬가지로, 한 출력단에만 customized class weight을 적용할 거라면, loss 2는 필요하지 않다

from functools import partial
loss1 = partial(weighted_categorical_crossentropy, weights=w1)
loss2 = partial(weighted_categorical_crossentropy, weights=w2)

4. loss function에 대한 name 정의

loss function은 반드시 고유한 이름을 가져야 하므로 다음과 같이 name을 정의해준다.


loss1.__name__ = 'loss1'
loss2.__name__ = 'loss2'

5. model compile

customizing하게 작성한 loss function(사실 class weight)를 model compile 시 dictionary 형태로 넣어준다.

model.compile(optimizer=tf.keras.optimizers.Adam(lr=lr), loss={'clf': loss1, 'reg': 'mse'}, metrics=[tf.keras.metrics.CategoricalAccuracy(name="categorical_accuracy", dtype=None)])
profile
veloger

0개의 댓글