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

带自定义损失函数的Keras模型在Sklearn Pipeline中深拷贝失败求助

问题描述

我定义了一个自定义损失函数:当预测值小于真实值时,将MAE(平均绝对误差)乘以2,否则直接返回MAE。在Sklearn Pipeline中训练该Keras模型时,尝试深拷贝包含模型与自定义对象的Pipeline,出现ValueError,提示无法恢复类型为_tf_keras_metric的自定义对象。必须使用Pipeline完成前置操作,需要解决深拷贝问题。

相关代码

自定义损失函数

def custom_loss(y_true, y_pred):
        mae = tf.keras.losses.MeanAbsoluteError()
        penalty = 2
        # penalize the loss heavily if the prediction is smaller than true
        loss = tf.where(
            condition=tf.greater(y_true, y_pred),
            x=mae(y_true, y_pred) * penalty,
            y=mae(y_true, y_pred)
        )
        return loss

深拷贝代码

regr = deepcopy(regr)
temp = RegressionRecords([], regr, r2_score(np.array(predict_df["true_data"]), np.array(predict_df["predictions"])), predict_df, None)

PredictionTransformer类

class PredictionTransformer(BaseEstimator, TransformerMixin):
    def __init__(self, estimator):
        self.estimator = estimator # Keras model passed in as estimator
    @property
    def history(self):
        return self.estimator.history

    @property
    def model(self):
        return self.estimator.model

    def fit(self, X, y):
        self.estimator.train(X, y)

    def predict(self, X):
        return self.estimator.transform(X)

错误信息

File "/usr/lib/python3.8/copy.py", line 172, in deepcopy
    y = _reconstruct(x, memo, *rv)
  File "/usr/lib/python3.8/copy.py", line 270, in _reconstruct
    state = deepcopy(state, memo)
  File "/usr/lib/python3.8/copy.py", line 146, in deepcopy
    y = copier(x, memo)
  File "/usr/lib/python3.8/copy.py", line 230, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "/usr/lib/python3.8/copy.py", line 146, in deepcopy
    y = copier(x, memo)
  File "/usr/lib/python3.8/copy.py", line 205, in _deepcopy_list
    append(deepcopy(a, memo))
  File "/usr/lib/python3.8/copy.py", line 146, in deepcopy
    y = copier(x, memo)
  File "/usr/lib/python3.8/copy.py", line 210, in _deepcopy_tuple
    y = [deepcopy(a, memo) for a in x]
  File "/usr/lib/python3.8/copy.py", line 210, in <listcomp>
    y = [deepcopy(a, memo) for a in x]
  File "/usr/lib/python3.8/copy.py", line 172, in deepcopy
    y = _reconstruct(x, memo, *rv)
  File "/usr/lib/python3.8/copy.py", line 270, in _reconstruct
    state = deepcopy(state, memo)
  File "/usr/lib/python3.8/copy.py", line 146, in deepcopy
    y = copier(x, memo)
  File "/usr/lib/python3.8/copy.py", line 230, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "/usr/lib/python3.8/copy.py", line 172, in deepcopy
    y = _reconstruct(x, memo, *rv)
  File "/usr/lib/python3.8/copy.py", line 270, in _reconstruct
    state = deepcopy(state, memo)
  File "/usr/lib/python3.8/copy.py", line 146, in deepcopy
    y = copier(x, memo)
  File "/usr/lib/python3.8/copy.py", line 230, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "/usr/lib/python3.8/copy.py", line 153, in deepcopy
    y = copier(memo)
  File "/usr/local/lib/python3.8/dist-packages/keras/engine/training.py", line 337, in __deepcopy__
    new = pickle_utils.deserialize_model_from_bytecode(
  File "/usr/local/lib/python3.8/dist-packages/keras/saving/pickle_utils.py", line 48, in deserialize_model_from_bytecode
    model = save_module.load_model(temp_dir)
  File "/usr/local/lib/python3.8/dist-packages/keras/utils/traceback_utils.py", line 67, in error_handler
    raise e.with_traceback(filtered_tb) from None
  File "/usr/local/lib/python3.8/dist-packages/keras/saving/saved_model/load.py", line 994, in revive_custom_object
    raise ValueError(
ValueError: Unable to restore custom object of type _tf_keras_metric. Please make sure that any custom layers are included in the `custom_objects` arg when calling `load_model()` and make sure that all layers implement `get_config` and `from_config`
解决方案

方法1:修改自定义损失函数,避免实例化Metric类

当前损失函数直接实例化MeanAbsoluteError Metric类,导致序列化时无法正确恢复。改用TensorFlow基础操作计算MAE:

def custom_loss(y_true, y_pred):
    mae = tf.abs(y_true - y_pred)
    penalty = 2
    loss = tf.where(
        condition=tf.greater(y_true, y_pred),
        x=mae * penalty,
        y=mae
    )
    return tf.reduce_mean(loss)  # 确保返回标量损失

这种方式不依赖Metric实例,从根源避免自定义对象序列化问题。

方法2:为PredictionTransformer实现自定义深拷贝逻辑

重写__deepcopy__方法,手动处理模型的保存与加载,传入自定义损失函数:

class PredictionTransformer(BaseEstimator, TransformerMixin):
    def __init__(self, estimator):
        self.estimator = estimator # Keras model passed in as estimator
        
    @property
    def history(self):
        return self.estimator.history

    @property
    def model(self):
        return self.estimator.model

    def fit(self, X, y):
        self.estimator.train(X, y)

    def predict(self, X):
        return self.estimator.transform(X)
    
    def __deepcopy__(self, memo):
        # 手动保存模型到临时文件
        import tempfile
        from tensorflow.keras.models import load_model
        with tempfile.TemporaryDirectory() as temp_dir:
            self.model.save(temp_dir)
            # 加载模型时传入自定义损失
            copied_model = load_model(temp_dir, custom_objects={'custom_loss': custom_loss})
            # 根据你的estimator实际构造方式调整
            copied_estimator = type(self.estimator)(model=copied_model)
            copied_estimator.history = deepcopy(self.estimator.history, memo)
            return PredictionTransformer(copied_estimator)

方法3:手动复制Pipeline组件,替代deepcopy

放弃整体深拷贝,逐个复制Pipeline组件,对Keras模型单独处理:

from sklearn.pipeline import Pipeline
import tempfile
from tensorflow.keras.models import load_model

copied_steps = []
for name, step in regr.steps:
    if isinstance(step, PredictionTransformer):
        # 单独复制Keras模型
        with tempfile.TemporaryDirectory() as temp_dir:
            step.model.save(temp_dir)
            copied_model = load_model(temp_dir, custom_objects={'custom_loss': custom_loss})
            copied_estimator = type(step.estimator)(model=copied_model)
            copied_estimator.history = deepcopy(step.estimator.history)
            copied_step = PredictionTransformer(copied_estimator)
    else:
        # Sklearn原生组件直接深拷贝
        copied_step = deepcopy(step)
    copied_steps.append((name, copied_step))

regr = Pipeline(copied_steps)

这种方式更可控,避免Pipeline整体深拷贝触发的Keras序列化问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 02:05:23