【问题标题】:How to create class weighting for multi-label classificication?如何为多标签分类创建类权重?
【发布时间】:2021-11-03 20:10:32
【问题描述】:

我正在处理以下问题:

-我必须创建一个 CNN,它以 3D 图像作为输入并输出 4 个类(详情如下) - 所有 4 个标签必须为 0 或 1:True 或 False,取决于输入图像

例如输出:[0, 1, 0, 1]:这意味着我的预测是 2 类和 4 类对那个图像有好处(应用程序不是 相关)。

因此,我有一个 [X,4] 形式的标签张量,其中 X 是样本(或图像)的数量。

我现在面临的问题是巨大的班级不平衡(例如 第三类几乎 98% 的案例是 1,只有 2% 是 0)。一世 不知道如何解决这个问题?我试着用谷歌搜索了一些 好时光,但根本没有答案。我使用了类权重(来自 sklearn) 以前,不过这次好像也不能用了。

我观察到的使用类权重的问题是它会 加权输入的每个数组(即'什么是权重 [0,1,1,0] 在整个标签矩阵中') 这显然不是 可取的。我希望每个班级都有一个 0 和 1 的权重 1s的权重(总计8个权重)。

我见过有人尝试过这样做,我手动创建了一个 计算权重并输出概率的函数 每个类为 0 或 1(例如 class1 weight0 和 class1 weight1)。

接下来,我必须创建一个权重字典。例如。为一个 单标签分类:{0: 0.9210526315789473, 1: 1.09375}。一世 需要将其作为我的 model.fit() 函数中的参数。

显然,我无法创建包含 4 个不同键的字典 0 和 1 的 4 键。我应该从这里做什么??

我的第一个想法是更改以下标签中的数字 方式:第一类:0=假; 1=真二等:2=假; 3=真正的三等 : 4=假; 5=真第四类:6=假; 7=真

基本上我只是为每个标签添加了一些 2 的倍数,现在 我的标签矩阵的每一行都有 0 到 7 之间的元素。

我能够以以下形式创建字典 {0:w0;1:w1;2:w2,3:w3...} 对我来说似乎是个好主意。

比我还面临一个问题:当我拟合我的模型时,预测 在(0,1) 范围内,因为我使用的是 sigmoid 激活 最后一个神经元上的函数(即Dense(4,activation='sigmoid'))。一世 以前从未使用过不在 0 和 1 之间的数字 但改变激活函数对我来说有点道理 从 sigmoid 到线性。

此时我的权重字典如下所示:

{0: 0.8714285714285714,
 1: 0.12857142857142856,
 2: 0.5428571428571428,
 3: 0.45714285714285713,
 4: 0.02857142857142857,
 5: 0.9714285714285714,
 6: 0.8142857142857143,
 7: 0.18571428571428572}

又在哪里,例如6:表示第4类的权重为0或 1:表示第1类的权重为1,以此类推。

完成所有这些后,我的模型仍然表现得很奇怪。输出是 不太符合预期(例如,第一个值介于 0 和 1 之间 类,第二类的值介于 2 和 3 之间,依此类推)。这 准确度不稳定,变化很大,验证准确度 只是在 0 和 1 之间跳转?

这是输出现在的样子:

array([[ 0.2878278,  1.3507844, -1.563219 ,  0.5500042]]

这显然是完全错误的。

我会将代码与我正在使用的模型和功能一起附加 计算权重(我知道它是嵌套的,不使用任何 矢量化,但它仅用于测试目的)。

我真的希望任何人都可以帮助我诊断这个问题 能够预测每个类别的权利值或计算 以不同的方式加权。

CNN:

from tensorflow.keras import datasets, layers, models
from tensorflow.keras.optimizers import SGD, Adam
from tensorflow.keras.layers import Dropout
from tensorflow.keras.layers import BatchNormalization
from tensorflow.keras.initializers import RandomNormal
from tensorflow.keras.regularizers import l2

callback = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=3)

model=models.Sequential();

model.add(layers.Conv3D(16, (2,2,2) , kernel_regularizer=l2(0.01), strides= (1,1,1),input_shape=images['06S'].shape))
model.add(layers.MaxPooling3D(pool_size=(2,2,2),strides=(1,1,1))) 
model.add(BatchNormalization(epsilon=1e-01,momentum=0.65))
model.add(tf.keras.layers.LeakyReLU(alpha=0.8))
model.add(layers.Dropout(0.7))

model.add(layers.Conv3D(8, (2,2,2) , kernel_regularizer=l2(0.01), strides=(1,1,1)))
model.add(layers.MaxPooling3D(pool_size=(2,2,2),strides=(1,1,1))) 
model.add(BatchNormalization(epsilon=1e-01,momentum=0.65))
model.add(tf.keras.layers.LeakyReLU(alpha=0.8))
model.add(layers.Dropout(0.7))

model.add(layers.Conv3D(4, (2,2,2) , kernel_regularizer=l2(0.01), strides=(1,1,1)))
model.add(layers.MaxPooling3D(pool_size=(2,2,2),strides=(1,1,1))) 
model.add(BatchNormalization(epsilon=1e-01,momentum=0.65))
model.add(tf.keras.layers.LeakyReLU(alpha=0.8))
model.add(layers.Dropout(0.7))


model.add(layers.Conv3D(16, (3,3,3) , kernel_regularizer=l2(0.01),strides=(1,1,1)))
model.add(layers.MaxPooling3D(pool_size=(3,3,3),strides=(1,1,1))) 
model.add(BatchNormalization(epsilon=1e-01,momentum=0.65))
model.add(tf.keras.layers.LeakyReLU(alpha=0.8))
model.add(layers.Dropout(0.7))


model.add(layers.Dense(32,activation=None))
model.add(BatchNormalization(epsilon=1e-04,momentum=0.1))
model.add(tf.keras.layers.LeakyReLU(alpha=0.4))
model.add(layers.Dropout(0.6))


model.add(layers.Dense(16,activation=None))
model.add(BatchNormalization(epsilon=1e-04,momentum=0.1))
model.add(tf.keras.layers.LeakyReLU(alpha=0.4))
model.add(layers.Dropout(0.6))


model.add(layers.Dense(4, activation='linear'))


model.summary()

model.compile(optimizer='adam',
              loss='mse',
              metrics=['accuracy'])
Compute weights:

def class_weighting(arr):
    arr_np=np.array(arr)
    
    for j in range (arr_np.shape[0]):
        ones=0
        zeros=0
        for i in range (arr_np.shape[1]):
            if(j==0):
                if (arr[j][i] == 1):
                    ones+=1
                else:
                    zeros+=1
                PVI0=zeros/arr_np.shape[1];
                PVI1=ones/arr_np.shape[1];
            elif(j==1):
                if (arr[j][i] == 1):
                    ones+=1
                else:
                    zeros+=1
                FIBRO0=zeros/arr_np.shape[1];
                FIBRO1=ones/arr_np.shape[1];
            elif(j==2):
                if (arr[j][i] == 1):
                    ones+=1
                else:
                    zeros+=1
                ROTOR0=zeros/arr_np.shape[1];
                ROTOR1=ones/arr_np.shape[1];
            elif(j==3):
                if (arr[j][i] == 1):
                    ones+=1
                else:
                    zeros+=1
                ROOF0=zeros/arr_np.shape[1];
                ROOF1=ones/arr_np.shape[1]; 
    return PVI0,PVI1,FIBRO0,FIBRO1,ROTOR0,ROTOR1,ROOF0,ROOF1

 Fitting:

PVI0,PVI1,FIBRO0,FIBRO1,ROTOR0,ROTOR1,ROOF0,ROOF1=class_weighting(arr)
classWeight={0:(PVI0),1:(PVI1),2:(FIBRO0),3:(FIBRO1),4:(ROTOR0),5:(ROTOR1),6:(ROOF0),
7:(ROOF1)}
history=model.fit(train_dataset,epochs=10,validation_data=val_dataset,
class_weight=classWeight))

【问题讨论】:

  • 要跟踪这个,我遇到了几乎相同的问题,但从未找到完美的解决方案。

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


【解决方案1】:

您应该调整损失函数以考虑损失权重。这是代码,我在下面有一些注释:

from tensorflow import keras
import tensorflow as tf
import numpy as np

loss_scale_dic = {0: 0.8714285714285714,
 1: 0.12857142857142856,
 2: 0.5428571428571428,
 3: 0.45714285714285713,
 4: 0.02857142857142857,
 5: 0.9714285714285714,
 6: 0.8142857142857143,
 7: 0.18571428571428572}

num_class = 4

#convert the loss scale dic to an indexable array
loss_scale = np.array([[loss_scale_dic[i*2+j] for j in range(2)] for i in range(num_class)])

class WeightedMSE(keras.losses.Loss):
    def __init__(self, weights,
                 reduction=keras.losses.Reduction.AUTO,
                 name='weighted_MSE'):
        super().__init__(reduction=reduction, name=name)
        self.weights = weights

    def call(self, y_true, y_pred):
        se = (y_pred - y_true)**2
        weights = self.weights[np.arange(len(self.weights)),y_true] #this scales the SE loss
        return tf.math.reduce_mean(se*weights,1)

N=5
np.random.seed(1)
y_true = np.zeros([N,num_class],np.int32)
y_true[np.arange(0,N),np.random.randint(0,4,N)] = 1
y_pred = np.random.uniform(0,1,[N,num_class])

loss = WeightedMSE(loss_scale,reduction='none') #scaling
scaled_losses = loss(y_true,y_pred)


loss = WeightedMSE(np.ones_like(loss_scale),reduction='none') #no scaling, standard MSE
losses = loss(y_true,y_pred)

print(losses,'\n',scaled_losses)


Out: tf.Tensor([0.05735777 0.37447205 0.24902505 0.36165863 0.50984228], shape=(5,), dtype=float64) 
tf.Tensor([0.0359915  0.10100629 0.06463426 0.15958925 0.27983722], shape=(5,), dtype=float64)

我的第一个注意事项是,您可以使用具有 NN 的不平衡类,而不会扩大损失。您可能需要调整每个类别的阈值(因此不是与主要类别进行一对一比较),但您可以在不调整模型的情况下进行一些基于输出的微调。

其次,您正在扩展 MSE,当我认为扩展 CE 损失更有意义时:see here

第三,当使用内置的交叉熵损失时,你只需要在类权重中提供正权重,否则你会惩罚权重两次。你想推广稀有的正类,当稀有类为正时,这已经具有减少普通类的效果。

哪个会给你这个版本:

loss_scale_dic = {0: 0.8714285714285714,
 1: 0.12857142857142856,
 2: 0.5428571428571428,
 3: 0.45714285714285713,
 4: 0.02857142857142857,
 5: 0.9714285714285714,
 6: 0.8142857142857143,
 7: 0.18571428571428572}

num_class = 4
model.compile(optimizer='adam',
          loss='categorical_crossentropy',
          metrics=['accuracy'])
#convert the loss scale dic to an indexable array

loss_scale = np.array([loss_scale_dic[i*2+1] for i in range(num_class)])
history=model.fit(train_dataset,epochs=10,validation_data=val_dataset,class_weight=loss_scale))

【讨论】:

    猜你喜欢
    • 2017-03-07
    • 2020-04-25
    • 2017-11-08
    • 2019-10-13
    • 2018-07-07
    • 2019-08-16
    • 2014-10-19
    • 2018-09-07
    • 1970-01-01
    相关资源
    最近更新 更多