# 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
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
weights = np.array([class_weight_0_Class, class_weight_1_class])
loss_custom = weighted_categorical_crossentropy(weights)
model.compile(loss = loss1)
아래 방법은 시도했으나 실패한 방법이다.
일반적으로 단일 출력단을 갖는 모델일 때는, 각 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]
하지만 내가 학습시키는 모델은 출력단에 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'
나는 함수 이름을 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
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
마찬가지로, 한 출력단에만 customized class weight을 적용할 거라면, loss 2는 필요하지 않다
from functools import partial
loss1 = partial(weighted_categorical_crossentropy, weights=w1)
loss2 = partial(weighted_categorical_crossentropy, weights=w2)
loss function은 반드시 고유한 이름을 가져야 하므로 다음과 같이 name을 정의해준다.
loss1.__name__ = 'loss1'
loss2.__name__ = 'loss2'
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)])