如何使用Keras计算输出具有4个神经元的类别权重?

3

我看过了如何针对单个分类进行一些类别权重不平衡的矫正。但在我的情况下,我的输出层为:

model.add(Dense(4, activation='sigmoid'))

我的目标是一个拥有以下内容的DataFrame:
       0  1  2  3
0      1  1  0  0
1      0  0  0  0
2      1  1  1  0
3      1  1  0  0
4      1  1  0  0
5      1  1  0  0
6      1  0  0  0
...   .. .. .. ..
14989  1  1  1  1
14990  1  1  1  0
14991  1  1  1  1
14992  1  1  1  0

[14993 rows x 4 columns]

我的预测可以采用5种可能的值之一:

[[0, 0, 0, 0],
[1, 0, 0, 0],
[1, 1, 0, 0],
[1, 1, 1, 0],
[1, 1, 1, 1]]

然而,这些类别肯定不是平衡的。我看过了如何使用softmax计算只有1个目标输出的类别权重,但这里有稍微的不同。
具体来说,
model.fit(..., class_weights=weights)

在这种情况下,我应该如何定义weights呢?

4
你的神经网络有5个独特的输出,为什么不能将它们映射到整数0到4? - Szymon Maszke
2
那将使它成为一个分类问题,并同等地处理不正确的情况。在我的情况下,预测 [1, 1, 1, 0] 更接近于 [1, 1, 1, 1] 而不是 [0, 0, 0, 0] - Shamoon
那么看起来你遇到的是回归问题,而不是分类问题。在分类中,预测之间没有距离(更近)。 - Dr. Snoopy
1
我认为你应该正常计算。 - Daniel Möller
我应该如何实际设置class_weights呢? - Shamoon
显示剩余3条评论
4个回答

3

可能的解决方案

我认为您应该使用几乎标准的,并从网络输出logits,这将使用argmax操作将其映射到值[0,1,2,3,4]中的损失函数(同样的过程将应用于one-hot-encoded标签,有关示例,请参见本答案的最后部分)。

使用加权crossentropy,您可以根据预测值与正确值不同地处理不正确性,就像您在评论中所说的那样。

您需要做的就是取正确和预测值相减的绝对值,并将其乘以损失,请参见以下示例:

让我们将每个编码映射到其一元值(可以使用argmax来完成,如下所示):

[0, 0, 0, 0] -> 0
[1, 0, 0, 0] -> 1
[1, 1, 0, 0] -> 2
[1, 1, 1, 0] -> 3
[1, 1, 1, 1] -> 4

让我们使用模型对一些随机目标和预测进行分析,以展现其本质:

Original Answer翻译成"最初的回答"

   correct  predicted with Softmax
0        0                       4
1        4                       3
2        3                       3
3        1                       4
4        3                       1
5        1                       0

现在,当你从正确的预测的中相减并取绝对值时,你基本上得到了如下的加权列:

最初的回答:


   weights
0        4
1        1
2        0
3        3
4        2
5        1

你可以看到,当真实目标是4时,预测为0的情况将比预测为3的情况被加权4倍,这正是你想要的结果。因此,建议按照Daniel Möller提供的方法创建一个简单的自定义损失函数。
import tensorflow as tf

# Output logits from your network, not the values after softmax activation
def weighted_crossentropy(labels, logits):
    return tf.losses.softmax_cross_entropy(
        labels,
        logits,
        weights=tf.abs(tf.argmax(logits, axis=1) - tf.argmax(labels, axis=1)),
    )

你应该在model.compile中使用这个损失函数,我认为没有必要重复已经讲过的内容。
这种解决方案的缺点有:
  • 对于正确的预测,梯度将等于零,这意味着网络加强连接的难度更大(将logits最大化/最小化到+inf/-inf)。
  • 可以通过向每个加权损失添加随机噪声(额外的正则化)来缓解上述问题。也可以作为一种正则化,可能会有所帮助。
  • 更好的解决方案可能是从加权情况中排除预测相等的情况(或使其为1),这不会向网络优化添加随机性。
这种解决方案的优点有:
  • 您可以轻松地为不平衡的数据集添加加权(例如,某些类别出现得更频繁)。
  • 映射到现有API。
  • 在分类领域中概念简单。
  • 您的模型无法预测不存在的分类值,例如,在多目标情况下,它可以预测[1, 0, 1, 0],但是使用以上方法不会出现这种情况。较少的自由度将有助于训练并消除荒谬的(如果我正确理解您的问题描述)预测机会。
在评论中提供了额外的讨论。
以下是使用自定义损失函数的示例网络。您的标签必须进行one-hot编码才能正确工作。
import keras    
import numpy as np
import tensorflow as tf

# You could actually make it a lambda function as well
def weighted_crossentropy(labels, logits):
    return tf.losses.softmax_cross_entropy(
        labels,
        logits,
        weights=tf.abs(tf.argmax(logits, axis=1) - tf.argmax(labels, axis=1)),
    )


model = keras.models.Sequential(
    [
        keras.layers.Dense(32, input_shape=(10,)),
        keras.layers.Activation("relu"),
        keras.layers.Dense(10),
        keras.layers.Activation("relu"),
        keras.layers.Dense(5),
    ]
)

data = np.random.random((32, 10))
labels = keras.utils.to_categorical(np.random.randint(5, size=(32, 1)))

model.compile(optimizer="rmsprop", loss=weighted_crossentropy)
model.fit(data, labels, batch_size=32)

那是一个有效的 keras 损失函数吗?我认为我们需要将 yTrue, yPred 传递给损失函数。 - Shamoon
此外,我的模型需要从 model.add(Dense(4, activation='sigmoid')) 更改为 model.add(Dense(1, activation='relu')),我猜是这样吧? - Shamoon
如果您传递的是logits而不是y_pred,那应该没问题(例如,您的模型在最后一层没有激活函数)。您的预测已经与softmax所需的形状相同,所以我想您会没问题(可能需要进行一些调整)。顺便说一下,在categorical_crossentropy中有一个名为from_logits的参数,所以没问题。 - Szymon Maszke
不,你应该完全移除激活函数(例如使其线性),并将 model.add(Dense(5)) 作为最后一层。如果您对 logits 的概念不确定,请参见此处的一些解释。 - Szymon Maszke
这是可以预料的,因为你更加强调了错误分类之间的“差距”,所以神经网络很难适应这种假设(0 预测值和 4 目标值相比,比 3 预测值和 4 目标值更糟糕,在常规分类中不会出现这种情况)。监控其他数值而不是“准确率”,例如预测值和目标值之间的平均距离。 - Szymon Maszke
显示剩余3条评论

1
请修复您的独热编码。使用pd.get_dummies(target)。通过计算每个类别出现的次数并除以target.shape[0]来计算每个类别的权重。
target=np.array([0 0 0 0], [1 0 0 0], [1 1 0 0], [1 1 1 0], [1 1 1 1])

proportion=[]
for i in range(0,len(target)):
    proportion.append([i,len(np.where(target==np.unique(target)[i])[0])/target.shape[0]])

class_weight = dict(proportion)


model.fit(..., class_weights=class_weight)

这就是问题所在 - 我没有使用“one hot”尝试。您会注意到我的编码形式不同:[0 0 0 0],[1 0 0 0],[1 1 0 0],[1 1 1 0],[1 1 1 1] - Shamoon
使用np.unique函数处理[0 0 0 0], [1 0 0 0], [1 1 0 0], [1 1 1 0], [1 1 1 1],并比较它们在目标中的数量。这样可以得到每个类别的百分比。 - razimbres
回答得到了改善 @Shamoon - razimbres
“target” 是我的输出吗?意思是所有的预测结果? - Shamoon
是的。你的X训练集映射到训练集中的目标Y。你的预测是测试集中的输出=target。 - razimbres
我遇到了一个错误: [i, len(np.where(Y == np.unique(Y)[i])[0])/Y.shape[0]]) IndexError: 索引2超出了大小为2的轴0的范围 - Shamoon

1

假设您有形状为(样本数,4)的目标(真实值y),您可以简单地执行以下操作:

positives = targetsAsNumpy.sum(axis=0)
totals = len(targetsAsNumpy)

negativeWeights = positives / totals
positiveWeights = 1 - negativeWeights

在fit方法中的类权重是针对分类问题(只有一个正确类)的。我建议您使用这些创建自定义损失函数。假设您正在使用binary_crossentropy。
import keras.backend as K

posWeightsK = K.constant(positiveWeights.reshape((1,4)))
negWeightsK = K.constant(negativeWeights.reshape((1,4)))

def weightedLoss(yTrue, yPred):

    loss = K.binary_crossentropy(yTrue, yPred)
    loss = K.switch(K.greater(yTrue, 0.5), loss * posWeigthsK, loss *  negWeightsK)
    return K.mean(loss) #optionally K.mean(loss, axis=-1) for further customization

请使用此损失函数在模型中:

model.compile(loss = weightedLoss, ...)

0

每个神经元的误差

对于这种值编码(一元编码,也称为“温度计编码”),您可以简单地分别测量每个值的误差并将它们相加,使用例如二进制交叉熵或均方/平均绝对误差指标。鉴于此输出实际上不是分类问题,而是回归任务的离散表示;但在某些情况下,这种表示是有效的 - 如论文Thermometer Encoding: One Hot Way To Resist Adversarial Examples所述。

虽然这样的分开的误差测量不能确保“无效”的输出(例如[1 0 0 0 1])不可能出现,但对于任何良好拟合的网络来说,它们很少出现,并且具有以下属性:如果正确值是[1 1 1 1 0],则预测[1 1 0 0 0]的“错误程度”是预测[1 1 1 0 0]的两倍。您不需要调整“类权重”即可实现这些结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接