如何将类别权重应用于多输出模型?

数据挖掘 神经网络 喀拉斯 多类分类 初学者 加权数据
2021-09-24 09:16:04

我有一个具有 2 个分类输出的模型。
第一个输出层可以预测 2 个类别:[0, 1]
,第二个输出层可以预测 3 个类别:[0, 1, 2]

如何为每个输出应用不同的类权重字典?

例如,我如何将字典{0: 1, 1: 10}应用于第一个输出
{0: 5, 1: 1, 2: 10}第二个输出?

我尝试使用以下类权重字典
weight_class={'output1': {0: 1, 1: 10}, 'output2': {0: 5, 1: 1, 2: 10}}
但代码失败并出现错误。

当我删除class_weight参数时,我的脚本也正常运行

代码示例

我创建了一个重现错误的最小示例

from tensorflow.python.keras.models import Model
from tensorflow.python.keras.layers import Input, Dense
from tensorflow.python.data import Dataset
import tensorflow as tf
import numpy as np


def preprocess_sample(features, labels):
    label1, label2 = labels
    label1 = tf.one_hot(label1, 2)
    label2 = tf.one_hot(label2, 3)
    return features, (label1, label2)


batch_size = 32

num_samples = 1000
num_features = 10

features = np.random.rand(num_samples, num_features)
labels1 = np.random.randint(2, size=num_samples)
labels2 = np.random.randint(3, size=num_samples)

train = Dataset.from_tensor_slices((features, (labels1, labels2))).map(preprocess_sample).batch(batch_size).repeat()

# Model
inputs = Input(shape=(num_features, ))
output1 = Dense(2, activation='softmax', name='output1')(inputs)
output2 = Dense(3, activation='softmax', name='output2')(inputs)
model = Model(inputs, [output1, output2])

model.compile(loss='categorical_crossentropy', optimizer='adam')
class_weights = {'output1': {0: 1, 1: 10}, 'output2': {0: 5, 1: 1, 2: 10}}
model.fit(train, epochs=10, steps_per_epoch=num_samples // batch_size,
          # class_weight=class_weights
          )

此代码在没有class_weight参数的情况下成功运行。
但是,当您class_weight通过取消注释该行来添加参数时
# class_weight=class_weights,脚本将失败并出现以下错误:

Traceback (most recent call last):
  File "test.py", line 35, in <module>
    class_weight=class_weights
  File "venv/lib/python3.6/site-packages/tensorflow/python/keras/engine/training.py", line 1536, in fit
    validation_split=validation_split)
  File "venv/lib/python3.6/site-packages/tensorflow/python/keras/engine/training.py", line 992, in _standardize_user_data
    class_weight, batch_size)
  File "venv/lib/python3.6/site-packages/tensorflow/python/keras/engine/training.py", line 1165, in _standardize_weights
    feed_sample_weight_modes)
  File "venv/lib/python3.6/site-packages/tensorflow/python/keras/engine/training.py", line 1164, in <listcomp>
    for (ref, sw, cw, mode) in zip(y, sample_weights, class_weights,
  File "venv/lib/python3.6/site-packages/tensorflow/python/keras/engine/training_utils.py", line 717, in standardize_weights
    y_classes = np.argmax(y, axis=1)
  File "venv/lib/python3.6/site-packages/numpy/core/fromnumeric.py", line 1004, in argmax
    return _wrapfunc(a, 'argmax', axis=axis, out=out)
  File "venv/lib/python3.6/site-packages/numpy/core/fromnumeric.py", line 62, in _wrapfunc
    return _wrapit(obj, method, *args, **kwds)
  File "venv/lib/python3.6/site-packages/numpy/core/fromnumeric.py", line 42, in _wrapit
    result = getattr(asarray(obj), method)(*args, **kwds)
numpy.core._internal.AxisError: axis 1 is out of bounds for array of dimension 1

编辑

我还在 Keras github 页面中打开了一个问题,但我想在这里问同样的问题,看看我是否遗漏了什么并且做错了什么。

4个回答

我还不能使用该class_weight参数,但与此同时,我找到了另一种将类权重应用于每个输出层的方法。

当前解决方案

这个keras 问题中,他们提供了一种简单的方法来通过实现所需类权重的自定义损失来应用类权重。

def weighted_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

其中weights是定义类权重的CxC矩阵(其中C是类数)。
更准确地说,定义了被错误归类为j的类iweights[i, j]的示例的权重

那么我们如何使用它呢?

Keras 允许为每个输出分配一个损失函数。
所以我们可以为每个输出分配一个带有正确weights矩阵的损失函数。

例如,为了满足我在问题中提出的要求,我们可以建议以下代码。

# Define the weight matrices
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    

# Define the weighted loss functions
from functools import partial
loss1 = partial(weighted_categorical_crossentropy, weights=w1)
loss2 = partial(weighted_categorical_crossentropy, weights=w2)

# Finally, apply the loss functions to the outputs
model.compile(loss={'output1': loss1, 'output2': loss2}, optimizer='adam')

这完成了请求:)

编辑

有一个小版本需要制作。
损失函数必须有一个名称,因此我们可以提供以下内容:

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

将以下格式的字典传递给 fit_generator 中的 class_weight 参数:

{ 'output1': {0: ratio_1 , 1: ratio_2} , 'output2': {0: ratio_3 , 1: ratio_4}}

您可以使用class_weightfromsklearn.utils从您的数据中计算类别权重

编辑:此方法仅适用于 TF 2.1.0 及更早版本。感谢您的回复。

参考:

https://github.com/keras-team/keras/issues/4735#issuecomment-267473722 https://scikit-learn.org/stable/modules/generated/sklearn.utils.class_weight.compute_class_weight.html

你有一个输出列表。您可以简单地为每个输出传递一个 class_weight 列表,如下所示:

class_weight = [{0: 1, 1: 10},{0: 5, 1: 1, 2: 10}]

您可以按如下方式传递权重:

class_weight={'Output_1':None,'Output_2':[1,2,1]}

哪里Output_2是一个有 3 个类的 softmax。