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
相关产品推荐
相关产品推荐

