将Keras稀疏分类交叉熵用于按像素分类的多类分类

时间:2019-01-10 20:18:38

标签: python tensorflow machine-learning keras loss-function

首先,我将公开自己是机器学习和Keras的新手,除了一般的CNN二进制分类器之外,对其他内容的了解不多。我正在尝试在许多256x256图像上使用U-Net架构(TF后端)执行逐像素的多类分类。换句话说,我输入一个256x256的图像,并希望它输出一个256x256的“掩码”(或标签图像),其中值是0到30之间的整数(每个整数代表一个唯一的类)。我正在训练2个1080Ti NVIDIA GPU。

当我尝试执行一次热编码时,出现一个OOM错误,这就是为什么我将稀疏分类交叉熵用作损失函数而不是常规分类交叉熵的原因。但是,当训练我的U-Net时,我的损失值从开始到结束都是“ nan”(它初始化为nan,并且永远不会改变)。当我通过将所有值除以30来归一化我的“蒙版”(所以它们从0-1开始)时,我的精度约为0.97,这是因为我图像中的大多数标签都是0(它只是输出)一堆0)。

这是我正在使用的U-Net:

def unet(pretrained_weights = None,input_size = (256,256,1)):
inputs = keras.engine.input_layer.Input(input_size)
conv1 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(inputs)
conv1 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv1)
pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)
conv2 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool1)
conv2 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv2)
pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)
conv3 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool2)
conv3 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv3)
pool3 = MaxPooling2D(pool_size=(2, 2))(conv3)
conv4 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool3)
conv4 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv4)
#drop4 = Dropout(0.5)(conv4)
drop4 = SpatialDropout2D(0.5)(conv4)
pool4 = MaxPooling2D(pool_size=(2, 2))(drop4)

conv5 = Conv2D(1024, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool4)
conv5 = Conv2D(1024, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv5)
#drop5 = Dropout(0.5)(conv5)
drop5 = SpatialDropout2D(0.5)(conv5)

up6 = Conv2D(512, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(drop5))
merge6 = concatenate([drop4,up6], axis = 3)
conv6 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge6)
conv6 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv6)

up7 = Conv2D(256, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv6))
merge7 = concatenate([conv3,up7], axis = 3)
conv7 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge7)
conv7 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv7)

up8 = Conv2D(128, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv7))
merge8 = concatenate([conv2,up8], axis = 3)
conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge8)
conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv8)

up9 = Conv2D(64, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv8))
merge9 = concatenate([conv1,up9], axis = 3)
conv9 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge9)
conv9 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9)
conv9 = Conv2D(32, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9)
conv10 = Conv2D(1, 1, activation = 'softmax')(conv9)
#conv10 = Flatten()(conv10)
#conv10 = Dense(65536, activation = 'softmax')(conv10)
flat10 = Reshape((65536,1))(conv10)
#conv10 = Conv1D(1, 1, activation='linear')(conv10)

model = Model(inputs = inputs, outputs = flat10)

opt = Adam(lr=1e-6,clipvalue=0.01)
model.compile(optimizer = opt, loss = 'sparse_categorical_crossentropy', metrics = ['sparse_categorical_accuracy'])
#model.compile(optimizer = Adam(lr = 1e-6), loss = 'sparse_categorical_crossentropy', metrics = ['accuracy'])
#model.compile(optimizer = Adam(lr = 1e-4),

#model.summary()

if(pretrained_weights):

    model.load_weights(pretrained_weights)

return model

请注意,我需要使输出平坦化,以使稀疏的分类交叉熵正常运行(出于某种原因,它不喜欢我的2D矩阵)。

这是一个训练跑步的示例(因为每次跑步都相同,所以只有1个纪元)

model = unet()
model.fit(x=x_train, y=y_train, batch_size=1, epochs=1, verbose=1, validation_split=0.2, shuffle=True)

训练2308个样本,验证577个样本 时代1/1 2308/2308 [==============================]-191s 83ms / step-损失:nan-sparse_categorical_accuracy:0.9672-val_loss :nan-val_sparse_categorical_accuracy:0.9667 出[18]:

让我知道是否需要更多信息来诊断问题。预先感谢!

4 个答案:

答案 0 :(得分:1)

问题是,对于多类分类,您需要输出一个矢量,每个矢量具有一个维度,该矢量表示对该类别的置信度。如果要识别30个不同的类,则最后一层应为3D张量(256、256、30)。

conv10 = Conv2D(30, 1, activation = 'softmax')(conv9)
flat10 = Reshape((256*256*30,1))(conv10)

opt = Adam(lr=1e-6,clipvalue=0.01)
model.compile(optimizer = opt, loss = 'sparse_categorical_crossentropy', metrics = 
['sparse_categorical_accuracy'])

我假设您的输入是一个(256,256,1)浮点张量,其值介于0和1之间,而您的目标是(256 * 256)个Int张量。

有帮助吗?

答案 1 :(得分:1)

conv10 = Conv2D(nclasses, kernel_size=(1, 1))(up9)
out = BatchNormalization()(conv10)
out = Reshape((img_height*img_width, nclasses), input_shape=(img_height, img_width, nclasses))(out)
out = Activation('softmax')(out)


model = Model(inputs=[inputs], outputs=[out])
model.compile(optimizer = Adam(lr = 1e-4), loss = 'sparse_categorical_crossentropy', metrics = ['sparse_categorical_accuracy'])

x_train:(batch_size,224,224,3)float32(输入图像)
y_train:(batch_size,50176,1)uint8(目标标签)

以上代码似乎适用于多类细分(n类),其中目标标签不是一种热编码。如果您的数据大小和/或模型很大,则一种热编码会导致内存问题。

最后一层的形状为(None,50176,16)(因为nclasses = 16,None不正确)。标签中的元素的值为 0-(nclasses-1)

在类索引(-1)上使用 argmax 重塑似乎是技巧,以防万一。图像输出...

注意:稀疏分类熵在 keras 2.2.2 及更高版本中似乎存在问题!

答案 2 :(得分:0)

OOM:

使自定义函数派生单次编码,而不使用诸如“ to_categorical”之类的预定义函数。

这需要1/4的内存(在我的情况下)。

enter image description here

答案 3 :(得分:0)

现在看来,您可以简单地在最后一个softmax层上进行Conv2D激活,然后指定categorical_crossentropy丢失并在图像上进行训练,而无需任何重塑技巧。我已经测试了一个虚拟数据集,并且效果很好。试试吧〜!

inp = keras.Input(...)
# define your model here
out = keras.layers.Conv2D(classes, (1, 1), activation='softmax') (...)
model = keras.Model(inputs=[inp], outputs=[out], name='unet')
model.compile(loss='categorical_crossentropy',
                      optimizer='adam',
                      metrics=['accuracy'])
model.fit(tensor4d, tensor4d)

您还可以使用sparse_categorical_crossentropy进行编译,然后训练形状为(samples, height, width)的输出,其中输出中的每个像素对应一个类标签:model.fit(tensor4d, tensor3d)

PS。我使用keras中的tensorflow.keras(tensorflow 2)