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

TensorFlow自定义损失含输入导数约束报错AttributeError: 'NoneType' has no attribute 'op'

报错根本原因
  • 核心问题是tf.gradients(y_pred, model.input[:,1])返回None:Keras在编译阶段构建损失计算图时,损失函数内直接引用外部model对象的输入切片,无法正确追踪y_pred和输入切片之间的微分路径,因此梯度计算返回空值,后续调用.op属性就触发了NoneType报错。
  • 其次标准Keras损失函数的入参只有y_true和y_pred,没有传入模型输入张量,也会导致梯度计算链路断裂。
约束项逻辑校验

你当前的约束项实现意图是限制神经网络输出对第2个输入(Time to Maturity,索引1)的偏导数≥0:

  • pen函数逻辑是输入≥0时触发惩罚,你传入的参数是-∂C_ANN/∂T,也就是当-∂C_ANN/∂T ≥0(即∂C_ANN/∂T ≤0)时会产生惩罚值,符合美式/欧式看涨期权对到期时间的单调性约束,逻辑是正确的,只需要修复梯度计算的链路问题即可。
修复方案

推荐采用「自定义训练步骤」的方案,用tf.GradientTape显式追踪计算链路,保证梯度计算正常:

import pandas as pd
from tensorflow import keras
import tensorflow as tf
from tensorflow.keras import layers
import numpy as np


# 超参数
n_hidden_layers = 2 # 隐藏层数量
n_units = 128 # 隐藏层神经元数量
n_batch = 64 # 每次梯度更新使用的样本数
n_epochs = 30


# 示例数据
x_train = {'strike':  [200, 2925], 'Time to Maturity': [0.312329, 0.0356164], 
        "RF Rate": [0.08, 2.97], 
        "Sigma 20 Days Annualized": [0.123251, 0.0837898], 
        "Underlying Price": [1494.82, 2840.69]
        }

call_X_train = pd.DataFrame (x_train, columns = ['strike', "Time to Maturity", 
                                                  "RF Rate", 
                                                  "Sigma 20 Days Annualized", 
                                                  "Underlying Price"]
                             ).values.astype('float32')

x_test = {'strike':  [200], 'Time to Maturity': [0.0356164], 
        "RF Rate": [2.97], 
        "Sigma 20 Days Annualized": [0.0837898], 
        "Underlying Price": [2840.69]
        }

call_X_test = pd.DataFrame (x_test, columns = ['strike', "Time to Maturity", 
                                                  "RF Rate", 
                                                  "Sigma 20 Days Annualized", 
                                                  "Underlying Price"]
                            ).values.astype('float32')

y_train = np.array([1285.25, 0.8]).astype('float32')
y_test = np.array([0.8]).astype('float32')


# 生成隐藏层
def hl(tensor, n_units):
    hl_output = layers.Dense(n_units, activation = layers.LeakyReLU())(tensor)
    return hl_output

# 搭建基础MLP模型
def mlp3_call(n_hidden_layers, n_units, input_dim):
    inputs = keras.Input(shape = (input_dim,))
    x = layers.LeakyReLU(alpha = 1)(inputs)
    for _ in range(n_hidden_layers):
        x = hl(x, n_units)
    outputs = layers.Dense(1, activation = keras.activations.softplus)(x)
    return keras.Model(inputs=inputs, outputs=outputs)


# 自定义损失使用的惩罚函数
def pen(x, lamb, m):
    return tf.where(x < 0, 0.0, lamb * x**m)

# 自定义带约束的训练模型
class ConstrainedMLP(keras.Model):
    def __init__(self, base_model, lamb=10, m=4):
        super().__init__()
        self.base_model = base_model
        self.lamb = lamb
        self.m = m

    def train_step(self, data):
        x, y_true = data
        with tf.GradientTape() as tape:
            tape.watch(x)
            y_pred = self.base_model(x, training=True)
            # 计算MSE损失
            mse_loss = tf.reduce_mean(tf.square(y_pred - y_true))
            # 计算输出对第2个输入(索引1)的梯度
            dC_dT = tf.gradients(y_pred, x)[0][:, 1]
            # 计算约束项损失
            constraint_loss = tf.reduce_mean(pen(-dC_dT, self.lamb, self.m))
            # 总损失
            total_loss = mse_loss + constraint_loss
        
        # 梯度更新
        grads = tape.gradient(total_loss, self.base_model.trainable_weights)
        self.optimizer.apply_gradients(zip(grads, self.base_model.trainable_weights))
        
        return {"loss": total_loss, "mse_loss": mse_loss, "constraint_loss": constraint_loss}
    
    def test_step(self, data):
        x, y_true = data
        y_pred = self.base_model(x, training=False)
        mse_loss = tf.reduce_mean(tf.square(y_pred - y_true))
        dC_dT = tf.gradients(y_pred, x)[0][:, 1]
        constraint_loss = tf.reduce_mean(pen(-dC_dT, self.lamb, self.m))
        total_loss = mse_loss + constraint_loss
        return {"loss": total_loss, "mse_loss": mse_loss, "constraint_loss": constraint_loss}

# 初始化模型
base_model = mlp3_call(n_hidden_layers, n_units, call_X_train.shape[1])
model = ConstrainedMLP(base_model)
model.compile(optimizer = keras.optimizers.Adam())

# 训练
history = model.fit(call_X_train, y_train, batch_size = n_batch, 
                    epochs = n_epochs, verbose = 1)

如果需要添加其他两个约束项,只需要在train_step里额外计算对应输入维度的梯度,再加到总损失里即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 08:39:04