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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 04:02:05