如何在Keras中创建可微分的自定义Charbonnier损失函数?
解决Keras中Charbonnier损失函数的可微分实现问题
咱们先理清你代码里的核心问题:你的损失函数直接引用了外部的U和V变量,但这些变量并没有作为模型的输出张量参与计算图构建,Keras无法追踪它们的梯度,才会抛出"None values not supported"的错误。另外,无监督场景下,U和V应该是你的CNN模型要输出的结果,而不是提前定义的外部参数。
第一步:调整模型结构,让模型输出U和V
你的CNN需要直接输出光流场的U(x方向)和V(y方向),所以最后一层要输出2个通道的特征图(对应U和V)。修改后的模型结构示例:
from keras.models import Model from keras.layers import Input, Conv2D, MaxPooling2D def build_cnn(input_shape): # 不要固定batch维度,除非你有特殊需求 inp = Input(shape=input_shape) conv1 = Conv2D(256, (7, 7), activation='relu', padding='same')(inp) conv2 = Conv2D(256, (5, 5), activation='relu', padding='same')(conv1) pool1 = MaxPooling2D(pool_size=(2, 2))(conv2) conv3 = Conv2D(512, (5, 5), activation='relu', padding='same')(pool1) # 这里继续补充你原本的卷积层逻辑 # 最后一层输出2个通道,不要加激活(光流值可正可负) output = Conv2D(2, (3, 3), activation=None, padding='same')(conv3) model = Model(inputs=inp, outputs=output) return model
输入形状设为(height, width, 2)即可,对应两帧拼接后的通道数。
第二步:正确实现可微分的Charbonnier损失函数
Charbonnier损失本身是可微分的(sqrt(x²+ε²)在x=0处导数为0),问题出在你传递张量的方式上。我们需要把预计算的I_x、I_y、I_t转为Keras常量张量,同时基于模型输出的U和V来计算损失:
import keras.backend as K def charbonnier_loss(I_x, I_y, I_t, epsilon=1e-6): # 把numpy格式的导数转为Keras可追踪的常量张量 I_x = K.constant(I_x) I_y = K.constant(I_y) I_t = K.constant(I_t) def loss(y_true, y_pred): # 从模型输出中提取U和V通道:y_pred形状为(batch, h, w, 2) U = y_pred[..., 0] V = y_pred[..., 1] # 计算光流约束项:U*I_x + V*I_y + I_t flow_constraint = U * I_x + V * I_y + I_t # 计算Charbonnier损失并取均值 charb_term = K.sqrt(K.square(flow_constraint) + epsilon**2) return K.mean(charb_term) return loss
这里的关键是所有计算都用Keras后端函数(K.*),确保操作能被自动微分引擎追踪,不会断开计算图。
第三步:调整训练流程,适配无监督场景
因为是无监督学习,y_true只是占位符,我们传一个和模型输出形状一致的全零数组即可:
import numpy as np # 假设frame1、frame2是你的输入帧,形状为(height, width) frame1 = ... frame2 = ... # 拼接输入并添加batch维度,最终形状(1, h, w, 2) input_data = np.dstack((frame1, frame2)) input_data = np.expand_dims(input_data, axis=0) # 计算帧导数,确保输出是numpy数组 I_x, I_y, I_t = compute_derivatives(input_data) # 构建并编译模型 model = build_cnn(input_shape=(frame1.shape[0], frame1.shape[1], 2)) loss_fn = charbonnier_loss(I_x, I_y, I_t) model.compile(optimizer='adam', loss=loss_fn) # 开始训练:y传全零占位符 model.fit( x=input_data, y=np.zeros_like(input_data[..., :2]), # 和模型输出形状匹配 epochs=10, batch_size=1 )
额外注意事项
- 确保
compute_derivatives的输出形状和输入帧的像素维度一致,才能和U、V做元素级运算 - 绝对不要在损失函数中使用numpy原生操作,必须用Keras后端函数,否则会导致梯度无法追踪
- 如果做批量训练,要保证
I_x、I_y、I_t的batch维度和输入数据一致
内容的提问来源于stack exchange,提问作者bbb
相关产品推荐
相关产品推荐

