You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Monte-Carlo Dropout时模型预测无方差的问题排查

问题

我用Keras(PyTorch后端)开发图像分割CNN,基于UNET实现,尝试用预测阶段的Monte-Carlo Dropout来近似模型预测的不确定性。训练模型时没加Dropout,导出权重后在预测阶段重新加载,但对同一张自制的256×512灰度测试图多次预测时,明明设置了50%的Dropout率,结果却完全一致。我已经改用model.load_weights()而非加载整个模型,但问题还是没解决,请问怎么验证Dropout是否真正生效?

相关代码及测试逻辑如下:

drop_rate = 0.5
drop_train = True  # MC dropout at inference
# drop_train=False #normal (no) dropout at inference
# downsize the UNET for this example.
# the smaller network is faster to train
# and produces excellent results on the dataset at hand
nfilters = (N_filters / 8).astype("int")

# input
input_tensor = Input(shape=frames_test_set.shape[1:], name="input_tensor")

## Encoder
# Encoder block 0
e0 = Conv2D(filters=nfilters[0], kernel_size=(3, 3), padding="same")(input_tensor)
e0 = BatchNormalization(axis=batch_normalization_axis)(e0)
e0 = Activation("relu")(e0)
e0 = Conv2D(filters=nfilters[0], kernel_size=(3, 3), padding="same")(e0)
e0 = BatchNormalization(axis=batch_normalization_axis)(e0)
e0 = Activation("relu")(e0)

# Encoder block 1
e1 = MaxPooling2D((2, 2))(e0)
e1 = Conv2D(filters=nfilters[1], kernel_size=(3, 3), padding="same")(e1)
e1 = BatchNormalization(axis=batch_normalization_axis)(e1)
e1 = Activation("relu")(e1)
e1 = Conv2D(filters=nfilters[1], kernel_size=(3, 3), padding="same")(e1)
e1 = BatchNormalization(axis=batch_normalization_axis)(e1)
e1 = Activation("relu")(e1)

# Encoder block 2
e2 = Dropout(drop_rate)(e1, training=drop_train)
e2 = MaxPooling2D((2, 2))(e2)
e2 = Conv2D(filters=nfilters[2], kernel_size=(3, 3), padding="same")(e2)
e2 = BatchNormalization(axis=batch_normalization_axis)(e2)
e2 = Activation("relu")(e2)
e2 = Conv2D(filters=nfilters[2], kernel_size=(3, 3), padding="same")(e2)
e2 = BatchNormalization(axis=batch_normalization_axis)(e2)
e2 = Activation("relu")(e2)

# Encoder block 3
e3 = Dropout(drop_rate)(e2, training=drop_train)
e3 = MaxPooling2D((2, 2))(e3)
e3 = Conv2D(filters=nfilters[3], kernel_size=(3, 3), padding="same")(e3)
e3 = BatchNormalization(axis=batch_normalization_axis)(e3)
e3 = Activation("relu")(e3)
e3 = Conv2D(filters=nfilters[3], kernel_size=(3, 3), padding="same")(e3)
e3 = BatchNormalization(axis=batch_normalization_axis)(e3)
e3 = Activation("relu")(e3)

# Encoder block 4
e4 = Dropout(drop_rate)(e3, training=drop_train)
e4 = MaxPooling2D((2, 2))(e4)
e4 = Conv2D(filters=nfilters[4], kernel_size=(3, 3), padding="same")(e4)
e4 = BatchNormalization(axis=batch_normalization_axis)(e4)
e4 = Activation("relu")(e4)
e4 = Conv2D(filters=nfilters[4], kernel_size=(3, 3), padding="same")(e4)
e4 = BatchNormalization(axis=batch_normalization_axis)(e4)
e4 = Activation("relu")(e4)
# e4 = MaxPooling2D((2, 2))(e4)

## Decoder
# Decoder block 3
d3 = Dropout(drop_rate)(e4, training=drop_train)
d3 = UpSampling2D(
    (2, 2),
)(d3)
d3 = concatenate([e3, d3], axis=-1)  # skip connection
d3 = Conv2DTranspose(nfilters[3], (3, 3), padding="same")(d3)
d3 = BatchNormalization(axis=batch_normalization_axis)(d3)
d3 = Activation("relu")(d3)
d3 = Conv2DTranspose(nfilters[3], (3, 3), padding="same")(d3)
d3 = BatchNormalization(axis=batch_normalization_axis)(d3)
d3 = Activation("relu")(d3)

# Decoder block 2
d2 = Dropout(drop_rate)(d3, training=drop_train)
d2 = UpSampling2D(
    (2, 2),
)(d2)
d2 = concatenate([e2, d2], axis=-1)  # skip connection
d2 = Conv2DTranspose(nfilters[2], (3, 3), padding="same")(d2)
d2 = BatchNormalization(axis=batch_normalization_axis)(d2)
d2 = Activation("relu")(d2)
d2 = Conv2DTranspose(nfilters[2], (3, 3), padding="same")(d2)
d2 = BatchNormalization(axis=batch_normalization_axis)(d2)
d2 = Activation("relu")(d2)

# Decoder block 1
d1 = UpSampling2D(
    (2, 2),
)(d2)
d1 = concatenate([e1, d1], axis=-1)  # skip connection
d1 = Conv2DTranspose(nfilters[1], (3, 3), padding="same")(d1)
d1 = BatchNormalization(axis=batch_normalization_axis)(d1)
d1 = Activation("relu")(d1)
d1 = Conv2DTranspose(nfilters[1], (3, 3), padding="same")(d1)
d1 = BatchNormalization(axis=batch_normalization_axis)(d1)
d1 = Activation("relu")(d1)

# Decoder block 0
d0 = UpSampling2D(
    (2, 2),
)(d1)
d0 = concatenate([e0, d0], axis=-1)  # skip connection
d0 = Conv2DTranspose(nfilters[0], (3, 3), padding="same")(d0)
d0 = BatchNormalization(axis=batch_normalization_axis)(d0)
d0 = Activation("relu")(d0)
d0 = Conv2DTranspose(nfilters[0], (3, 3), padding="same")(d0)
d0 = BatchNormalization(axis=batch_normalization_axis)(d0)
d0 = Activation("relu")(d0)

# output
# out_class = Dense(1)(d0)
out_class = Conv2D(1, (1, 1), padding="same")(d0)
out_class = Activation("sigmoid", name="output")(out_class)

# create and compile the model
model = Model(inputs=input_tensor, outputs=out_class)
model.compile(
    loss={"output": "binary_crossentropy"},
    metrics={"output": "accuracy"},
    optimizer="adam",
)

model.load_weights(f"{filepath}.keras")
Y_ts_hat = model.predict(frames_test_set, batch_size=1)

T = 10

Y_ts_hat_variance = np.zeros(
    (Y_ts_hat.shape[0], Y_ts_hat.shape[1], Y_ts_hat.shape[2], 1, T)
)

Y_ts_hat_variance[:, :, :, :, 0] = Y_ts_hat

for t in range(T - 1):
    print(f"Model {t+1}/{T-1}")
    Y_ts_hat_variance[:, :, :, :, t + 1] = model.predict(frames_test_set, batch_size=1)

arrays_indentical = (
    Y_ts_hat_variance[25, :, :, 0, 0] == Y_ts_hat_variance[25, :, :, 0, -1]
).all()
print(f"Arrays identical: {arrays_indentical}")

我已经做了20次预测,对比首次和末次结果始终完全一致,正常MC Dropout生效的话应该有随机性差异。

解决方案

一、先排查Dropout未生效的核心原因

  1. training参数被predict()覆盖
    Keras的model.predict()默认会强制所有层进入推理模式(training=False),会覆盖你手动给Dropout层设置的training=drop_train。改用函数式调用模型,强制开启训练模式:

    # 替换原predict调用
    Y_ts_hat = model(frames_test_set, training=True)
    # 循环内同样修改
    Y_ts_hat_variance[:, :, :, :, t + 1] = model(frames_test_set, training=True)
    

    若不想影响BatchNorm层(保持其推理模式),可以单独控制Dropout层的状态:

    def mc_predict(model, x):
        for layer in model.layers:
            if isinstance(layer, Dropout):
                layer.training = True
            elif isinstance(layer, BatchNormalization):
                layer.training = False
        return model(x)
    

    之后用mc_predict(model, frames_test_set)替代predict()。

  2. 随机种子固定导致掩码重复
    检查代码中是否固定了全局随机种子(如np.random.seed()、torch.manual_seed()),固定种子会让每次Dropout的掩码完全一致。可以在每次预测前重新设置不同种子:

    import torch
    import numpy as np
    
    for t in range(T - 1):
        print(f"Model {t+1}/{T-1}")
        torch.manual_seed(t)
        np.random.seed(t)
        Y_ts_hat_variance[:, :, :, :, t + 1] = model(frames_test_set, training=True)
    

二、验证Dropout是否生效的方法

  1. 直接检查Dropout层输出
    创建中间模型提取Dropout层的输出,对比两次预测结果:

    # 替换index为你要观察的Dropout层在模型中的索引
    dropout_layer = model.get_layer(index=6)
    intermediate_model = Model(inputs=model.input, outputs=dropout_layer.output)
    
    out1 = intermediate_model(frames_test_set[25:26], training=True)
    out2 = intermediate_model(frames_test_set[25:26], training=True)
    
    # 返回False则说明Dropout生效
    print((out1 == out2).all())
    
  2. 统计多次预测的方差
    计算同一像素点在多次预测中的方差,若方差大于0则Dropout生效:

    mean_pred = np.mean(Y_ts_hat_variance, axis=-1)
    var_pred = np.var(Y_ts_hat_variance, axis=-1)
    
    print(f"最大方差: {np.max(var_pred)}")
    print(f"最小方差: {np.min(var_pred)}")
    
  3. 极端参数测试
    临时将drop_rate设为1.0,此时所有神经元会被丢弃,输出应接近全0。若两次预测结果有差异(或符合全0逻辑),说明Dropout层在工作。

内容的提问来源于stack exchange,提问作者ZebraEagle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 19:05:55