【问题标题】:Multi class sparse_categorical_crossentropy TruePositives metric Incompatible shapes: [2,128] vs. [2,64]多类 sparse_categorical_crossentropy TruePositives 度量不兼容的形状:[2,128] 与 [2,64]
【发布时间】:2020-06-05 01:51:27
【问题描述】:

我正在尝试为每个班级添加各种简单的指标。 tf.keras.metrics.TruePositives, tf.keras.metrics.Precision ... 当最后一个Dense 层为两个或更多时,这会导致崩溃。

InvalidArgumentError: Incompatible shapes: [2,128] vs. [2,64]
     [[{{node metrics_12/fp/LogicalAnd}}]]

如果我只使用 accuracy 作为指标,它就可以工作。我很确定我错过了一些基本的东西。在谈到 TensorFlow 和深度学习时,我只是一个外行。我究竟做错了什么? 如何获取每个类的指标(主要是真/假阳性/阴性)?(示例代码只有 0,1 个类,在实际应用中还有更多)

Colab 链接: https://colab.research.google.com/drive/1aAz1pfN6ttBp8nU6rZgo8OA_Hwdseyg8

#%%

from typing import List, Set, Dict, Tuple, Optional, Any
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, BatchNormalization, CuDNNLSTM, LSTM, Flatten

#%%

# Create random training values for demo purposes.
#
# train_x is [
#   [
#        [0.3, 0.54 ... 0.8],
#        [0.4, 0.6 ... 0.55],
#        ...
#   ],
#   [
#        [0.3, 0.54 ... 0.8],
#        [0.4, 0.6 ... 0.55],
#        ...
#   ],
#   ...
# ]
#
# train_y is corresponding classification of train_x sequences, always 0 or 1
# [0, 1, 0, 1, 0, ... 0]

SAMPLES_CNT = 1000

train_x = np.random.rand(SAMPLES_CNT,5,4)
train_y = np.vectorize(lambda x: int(round(x)))(np.random.rand(SAMPLES_CNT))

val_x = np.random.rand(int(SAMPLES_CNT * 0.1),5,4)
val_y = np.vectorize(lambda x: int(round(x)))(np.random.rand(int(SAMPLES_CNT * 0.1)))

#%%

shape = Tuple[int, int]
model = Sequential()
model.add(LSTM(32,input_shape=train_x.shape[1:], return_sequences=True, stateful=False))
model.add(Dropout(0.2))
model.add(BatchNormalization())
model.add(LSTM(32,input_shape=train_x.shape[1:], return_sequences=False, stateful=False))
model.add(Dropout(0.2))
model.add(BatchNormalization())
model.add(Dense(16, activation="relu"))
model.add(Dropout(0.2))
model.add(Dense(2, activation="softmax"))

metrics = [
    'accuracy',
    tf.keras.metrics.TruePositives(name='tp'),
    tf.keras.metrics.FalsePositives(thresholds=[0.5, 0.5], name='fp'),
    tf.keras.metrics.TrueNegatives(name='tn'),
    tf.keras.metrics.FalseNegatives(name='fn'),
    tf.keras.metrics.Precision(name='precision'),
    tf.keras.metrics.Recall(name='recall'),
    tf.keras.metrics.AUC(name='auc'),
]

model.compile(
    optimizer=tf.keras.optimizers.Adam(lr=0.001, decay=1e-6),
    loss='sparse_categorical_crossentropy',
    metrics=metrics
)

fit = model.fit(
    train_x, train_y,
    batch_size=64,
    epochs=2,
    validation_data=(val_x, val_y),
    shuffle=False,
)

for i, val in enumerate(model.metrics_names):
    print(model.metrics_names[i], fit.history[val][:1])

【问题讨论】:

    标签: tensorflow machine-learning keras deep-learning lstm


    【解决方案1】:

    其中一些指标应该只适用于一个类。

    您需要Dense(1, activation='sigmoid')'binary_crossentropy'

    注意这两个问题是完全一样的:

    • Dense(1, activation='sigmoid'),与loss='binary_crossentropy'
    • Dense(2, activation='softmax'),与loss='sparse_categorical_crossentropy'

    【讨论】:

    • 谢谢你的回复我明白你的意思了。如果我有多个课程并且我想知道每个课程的回忆和真/假阳性怎么办。什么是最直截了当的方式?我虽然这就是开箱即用的指标。我错过了什么? (谢谢!)
    • @stringCode,在您的问题中,您不需要多类。带有'categorical_crossentropy'Dense(2, activation='softmax')与带有'binary_crossentropy'Dense(1, activation=sigmoid)完全相同。至于地面实况数据(目标),您无需更改任何内容。值 0 代表第一类,值 1 代表第二类。
    【解决方案2】:

    但是,如果您确实想知道如何解决多类问题,那么我们需要创建自定义指标。

    首先,我必须说,我认为将这些指标放在分类问题中没有多大意义(在众多类别中只有一个正确的类别 - 'softmax' + 'categorical_crossentropy')。这样的问题不是“二元”的,所以没有真正的“正面”和“负面”,而是众多正确的。

    如果您将其视为单独的类并将每个类视为二进制类,您会得到如下内容:

    • 每次模型得到一个正确的类,这意味着 1 TP + 4 TN(注意有多少真正的否定,因为不止两个结果)
    • 每次模型得到一个类错误,就意味着 1 FP + 1 FN + 3 TN

    如果你把这些数字加起来,它们根本没有多大意义。 (也许我错过了一些特殊的方法来计算这些分类问题......但是你也可以使用下面的所有东西,知道上面的内容)。

    现在,另一方面,您可以获得多个二进制类的良好指标(其中每个类独立于其他类,并且多个类可以是正确的 - 'sigmoid' + 'binary_crossentropy')。

    在这种情况下,您可以采用两种方法:

    • 获取“每个班级”的指标
    • 以某种方式平均所有类的指标(您可以在 sklearn 文档中查看某些类型的平均值)

    每类指标

    这些对应于 sklearn 文档中的 'binary' 平均模式。

    备选方案 1:

    将每个类作为单独的模型输出,在编译时为每个输出设置所有这些指标。 Tensorflow 将单独查看每个输出并毫无问题地计算所有内容。

    备选方案 2:

    这应该作为每个类的单独指标来完成。因此,考虑到类索引,我们可以为此创建一个包装器。

    我会举一些例子。请注意,它们都不能是“稀疏的”,因为不止一个类可以是正确的,因此,在这种情况下,地面 true 数据将具有 (samples, classes) 形状,就像预测的 pred 值一样。

    对于每个指标,您都可以像这样创建一个包装器:

    #class getter
    def get_class(true, pred, index):
        #get the class
        true = true[:, index]
        pred = pred[:, index]
    
        #round pred - you can choose different thresholds
        pred = K.cast(K.greater(pred, 0.5), K.floatx())
    
        return true, pred
    
    #class wrapper
    def some_metric_per_class(class_index):
        def the_actual_metric(true, pred):
            true, pred = get_class(class_index)
    
            return calculations
    
        return the_actual_metric
    

    这样的包装器可以像这样使用:

    metrics = [some_metric_per_class(i) for i in range(n_classes)]
    metrics += [some_other_metric_per_class(i) for i in range(n_classes)]
    model.compile(metrics = metrics, ...)
    

    这里

    现在,以下每个指标都应该有自己的包装器(为了避免不必要的重复,我没有在这里写):

    def TP(true, pred):
        true, pred = get_class(class_index)
        return K.sum(true * pred)
    
    def FP(true, pred):
        true, pred = get_class(class_index)
        return K.sum(pred * (1 - true))
    
    def TN(true, pred):
        true, pred = get_class(class_index)
        return K.sum((1-true) * (1-pred))
    
    def FN(true, pred):
        true, pred = get_class(class_index)
        return K.sum((1-pred) * true)
    
    def precision(true, pred):
        true, pred = get_class(class_index)
    
        TP = K.sum(true * pred)
        TP_and_FP = K.sum(pred)
    
        return K.switch(K.equal(TP_and_FP, 0), 1, TP / TP_and_FP)
    
    def recall(true, pred):
        true, pred = get_class(class_index)
    
        TP = K.sum(true * pred)
        TP_and_FN = K.sum(true)
    
        return K.switch(K.equal(TP_and_FN, 0), 1, TP / TP_and_FN)
    
    def AUC(true, pred):
        true, pred = get_class(class_index)
    
         #We want strictly 1D arrays - cannot have (batch, 1), for instance
        true= K.flatten(true)
        pred = K.flatten(pred)
    
            #total number of elements in this batch
        totalCount = K.shape(true)[0]
    
            #sorting the prediction values in descending order
        values, indices = tf.nn.top_k(pred, k = totalCount)   
            #sorting the ground truth values based on the predictions above         
        sortedTrue = K.gather(true, indices)
    
            #getting the ground negative elements (already sorted above)
        negatives = 1 - sortedTrue
    
            #the true positive count per threshold
        TPCurve = K.cumsum(sortedTrue)
    
            #area under the curve
        auc = K.sum(TPCurve * negatives)
    
           #normalizing the result between 0 and 1
        totalCount = K.cast(totalCount, K.floatx())
        positiveCount = K.sum(true)
        negativeCount = totalCount - positiveCount
        totalArea = positiveCount * negativeCount
        return  auc / totalArea
    

    Explanation about AUC

    重要:精度、召回率和 AUC 不会是精确值,因为 Keras 会按批次计算指标,然后对每个批次的结果进行平均。

    平均指标

    这些可能只对“精确”和“召回”有意义。所以我是为这两个做的。

    这里不需要包装器,也不需要单独的输出。 truepred 数据与前面的示例类似,形状为 (samples, classes)

    在这里,我们使用相同的计算,但现在我们将所有类放在一起并决定如何平均它们。

    def base_metrics(true, pred):
        #round pred - you can choose different thresholds
        pred = K.cast(K.greater(pred, 0.5), K.floatx())
    
        TP = K.sum(true * pred, axis=0)
        TP_and_FP = K.sum(pred, axis=0)
        TP_and_FN = K.sum(true, axis=0)
    
        return TP, TP_and_FP, TP_and_FN
    
    def precision_micro(true, pred):
        TP, TP_FP, TP_FN = base_metrics(true, pred)
        TP = K.sum(TP)
        TP_FP = K.sum(TP_FP)
    
        return K.switch(K.equal(TP_FP, 0), 1, TP / TP_FP)
    
    def precision_macro(true, pred):
        TP, TP_FP, TP_FN = base_metrics(true, pred)
    
        precision = K.switch(K.equal(TP_FP, 0), 1, TP / TP_FP)
        return K.mean(precision)
    

    您可以对recall_microrecall_macro 执行相同的操作,但使用TP_FN 而不是TP_FP

    【讨论】:

    • 感谢您的解释!对此,我真的非常感激。我会试一试。我会在赏金到期之前接受正确的答案。再次感谢您。
    猜你喜欢
    • 1970-01-01
    • 2021-09-07
    • 2020-10-03
    • 2020-11-16
    • 1970-01-01
    • 2021-12-31
    • 2019-12-16
    • 2021-09-18
    • 2021-06-11
    相关资源
    最近更新 更多