从逻辑值(from_logits)为True或False的角度,使用tf.losses.CategoricalCrossentropy训练UNet会得到不同的训练结果。

19

我正在使用unet进行图像语义分割任务,如果我像这样为最后一层设置Softmax Activation:

...
conv9 = Conv2D(n_classes, (3,3), padding = 'same')(conv9)
conv10 = (Activation('softmax'))(conv9)
model = Model(inputs, conv10)
return model
...

然后使用loss = tf.keras.losses.CategoricalCrossentropy(from_logits=False)。即使只有一张训练图像,训练也不会收敛

但是如果我不像这样设置最后一层的Softmax激活函数

...
conv9 = Conv2D(n_classes, (3,3), padding = 'same')(conv9)
model = Model(inputs, conv9)
return model
...

然后使用loss = tf.keras.losses.CategoricalCrossentropy(from_logits=True),训练将会收敛于一张训练图片。

我的Groundtruth数据集是这样生成的:

X = []
Y = []
im = cv2.imread(impath)
X.append(im)
seg_labels = np.zeros((height, width, n_classes))
for spath in segpaths:
    mask = cv2.imread(spath, 0)
    seg_labels[:, :, c] += mask
Y.append(seg_labels.reshape(width*height, n_classes))

为什么?我的用法有问题吗?

这是我关于git的实验代码:https://github.com/honeytidy/unet 你可以checkout和运行(可以在CPU上运行)。你可以改变Activation层和CategoricalCrossentropy的from_logits参数,看看我说的是什么。


计算两个模型对单张图像的像素级输出和损失。两个模型的损失应该相同。 - mdaoust
你是使用 channels_first 还是 channels_last - Daniel Möller
你的路径是独占的吗?(每个像素只有一条正确的路径吗?) - Daniel Möller
通道最后。是的,路径是独占的(ground truth 是 one-hot)。@Daniel Möller - tidy
5个回答

15
将“softmax”激活函数推入交叉熵损失层中可以显著简化损失计算并使其更加数值稳定。
也许在您的示例中,数值问题足以使得对于from_logits=False选项的训练过程无效。
您可以在此帖子中找到交叉熵损失(“信息增益”损失的特殊情况)的推导。该推导说明了当将softmax与交叉熵损失相结合时避免的数值问题。

1
是的,数值稳定性在这里很可能发挥了作用。这也已经在源代码文档中提到:注意:使用 from_logits=True 可能更具数值稳定性。 - today
据我所知,Keras 通过使用一个 epsilon 处理这个问题,可以关闭分类非常糟糕的点。 - mdaoust

7

默认情况下,Tensorflow中针对分类问题实现的所有损失函数都使用from_logits=False。记住,在分类问题中,通常希望在预测结束时产生概率输出。

仅看下面的图像,网络的最后一层(就在softmax函数之前)

“输入图像描述”src

因此,顺序是神经网络⇒最后一层输出⇒Softmax或Sigmoid函数⇒每个类的概率。

例如,在多类分类问题的情况下,输出可以是y1、y2、....yn,希望为每个输出产生一定的概率。(请参见输出层)。现在,这个输出层将与真实标签在交叉熵损失函数中进行比较。

让我们举个例子,假设您的神经网络已经为分类任务生成了输出。然后,您使用softmax函数将该输出转换为概率,并使用交叉熵损失函数计算损失。

# output produced by the last layer of NN
nn_output_before_softmax = [3.2, 1.3, 0.2, 0.8]

# converting output of last layer of NN into probabilities by applying softmax
nn_output_after_softmax = tf.nn.softmax(nn_output_before_softmax)

# output converted into softmax after appling softmax
print(nn_output_after_softmax.numpy())
[0.77514964 0.11593805 0.03859243 0.07031998]

y_true = [1.0, 0.0, 0.0, 0.0]

现在有两种情况:
  1. 明确使用softmax(或sigmoid)函数。
  2. 不单独使用softmax函数,但要在损失函数的计算中包括它。

1) 明确使用softmax(或sigmoid)函数

如果明确使用softmax(或sigmoid)函数,则对于分类任务,在TensorFlow损失函数中有一个默认选项即from_logits=False。因此,TensorFlow假定您将馈入损失函数的任何输入都是概率,因此无需应用softmax函数。
# By default from_logits=False
loss_taking_prob = tf.keras.losses.CategoricalCrossentropy(from_logits=False) 

loss_1 = loss_taking_prob(y_true, nn_output_after_softmax)
print(loss_1)
tf.Tensor(0.25469932, shape=(), dtype=float32)

2) 如果没有单独使用softmax函数并希望将其包含在损失函数的计算中,那么这意味着您提供给损失函数的任何输入都没有进行缩放处理(意味着输入仅为从-inf到+inf的数字而不是概率)。这里您可以让TensorFlow为您执行softmax操作。

loss_taking_logits = tf.keras.losses.CategoricalCrossentropy(from_logits=True)

loss_2 = loss_taking_logits(y_true, nn_output_before_softmax)
print(loss_2)
tf.Tensor(0.2546992, shape=(), dtype=float32)

请记住,当from_logits=False应该为True时使用会导致对概率进行softmax并生成错误的模型。

5

from_logits = True表示模型得到的损失值未被标准化,通常在模型中没有softmax函数时使用。例如,在https://www.tensorflow.org/tutorials/generative/dcgan模型中,他们没有使用softmax激活函数,或者换句话说,它有助于数值稳定性。


我理解的是,softmax 的输入被称为 logits,而 softmax 的输出是多项式概率。因此,from_logits=True 意味着来自输出层的值没有通过 softmax 函数传递(如您所说,未经过归一化),而是将它们视为实际值,而不是概率。我的理解正确吗? - tbhaxor

0
为了使softmax正常工作,您必须确保:
  • 您正在使用 'channels_last' 作为 Keras 默认通道配置。

    • 这意味着模型中的形状将是像 (None, height, width, channels) 这样的。
    • 这似乎是您的情况,因为您把 n_classes 放在了最后一个轴上。但也很奇怪,因为您正在使用 Conv2D,而您的输出 Y 应该是 (1, height, width, n_classes) 而不是您正在使用的那种奇怪的形状。
  • 您的 Y 只有零和一(通常发生在图像中的是 0 和 255)

    • 请检查 Y.max() == 1Y.min() == 0
    • 您可能需要将 Y = Y / 255.
  • 只有一个类是正确的(您的数据没有多于一个值为 1 的路径/通道)。

    • 请检查 (Y.sum(axis=-1) == 1).all() 是否为 True

0

我猜问题出在softmax激活函数上。根据doc中的说明,softmax默认应用于最后一个轴。请查看model.summary()并检查是否符合您的期望。


从他的代码来看,他正在沿着图像通道维度堆叠二进制图像。这是CategoricalCrossEntropy所期望的。 - mdaoust

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