Skip to content

Bug: StackingRegressor serialization error with custom neural network regressors (TabTransformer, ANN, DNN) #31811

@DrMohsenJahan

Description

@DrMohsenJahan

Describe the bug

Bug Report: StackingRegressor in scikit-learn Fails with Custom Neural Network Regressors
Dear scikit-learn Maintainers,
I am Dr. Mohsen Jahan, Professor of Agroecology and Instructor of Artificial Intelligence and Digital Transformation at Ferdowsi University of Mashhad, Iran. While conducting a research project on multi-objective feature selection using the NSGA-III algorithm and stacking models, I encountered an issue with the StackingRegressor implementation in scikit-learn (version 1.5.2). Specifically, this module exhibits compatibility issues with custom regression models, particularly those based on neural networks such as TabTransformerRegressor, ANNRegressor, and DNNRegressor.
Issue Description
When using StackingRegressor in scikit-learn with custom regression models that adhere to the standard scikit-learn API (e.g., implementing fit and predict methods) but rely on complex internal structures (e.g., based on tensorflow or pytorch), serialization or cloning errors occur. These errors manifest particularly when such models are used as regressors or meta_regressor in StackingRegressor, affecting processes like GridSearchCV or model persistence with joblib. For instance, in our project, employing a custom SimpleDNNRegressor (built with tensorflow) as the meta-regressor in StackingRegressor resulted in serialization errors. This issue was not observed when using mlxtend.regressor.StackingRegressor (version 0.23.1), which handles custom models more robustly due to its more flexible cloning/serialization mechanisms.
Technical Details

scikit-learn Version: 1.5.2
Affected Models: TabTransformerRegressor, ANNRegressor, DNNRegressor, and likely other neural network-based regressors
Affected Module: sklearn.ensemble.StackingRegressor
Observed Errors:
Serialization errors during GridSearchCV or model saving with joblib.
Incompatibility with custom models leveraging external libraries (e.g., tensorflow).

Workaround: Using mlxtend.regressor.StackingRegressor, which does not exhibit these limitations.

Suggestions for Resolution
To address this issue, I recommend the following:

Enhance the sklearn.base.clone mechanism to better support complex models (e.g., those based on tensorflow or pytorch).
Introduce an option in StackingRegressor to disable model cloning (similar to refit=False in mlxtend), allowing users to work with custom models without serialization requirements.
Improve documentation to clearly outline the current limitations of StackingRegressor with non-standard models.

Additional Information
This issue was identified during a research project focused on multi-objective feature selection for agroecological data, where we aimed to optimize feature sets for multiple agricultural traits. Switching to mlxtend resolved the issue, enabling successful model training and evaluation. I am happy to provide further details, including a minimal reproducible example or additional logs, to assist in resolving this bug.
Thank you for your attention to this matter.
Sincerely,Dr. Mohsen JahanProfessor of Agroecology and Instructor of Artificial Intelligence and Digital TransformationFerdowsi University of Mashhad, IranEmail: [Your Email]Date: July 22, 2025

Steps/Code to Reproduce

-- coding: utf-8 --

"""
Created on Sun Jul 20 13:36:14 2025

@author: jahan
"""
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.multioutput import MultiOutputRegressor
from sklearn.ensemble import StackingRegressor
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
from sklearn.preprocessing import StandardScaler
from sklearn.feature_selection import RFE, mutual_info_regression
from lightgbm import LGBMRegressor
import matplotlib.pyplot as plt
import seaborn as sns
import joblib
import os
from tqdm import tqdm
from pymoo.algorithms.moo.nsga3 import NSGA3
from pymoo.util.ref_dirs import get_reference_directions
from pymoo.optimize import minimize
from pymoo.core.problem import Problem
from shap import TreeExplainer
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.callbacks import EarlyStopping
from sklearn.base import BaseEstimator, RegressorMixin

تعریف کلاس سفارشی DNNRegressor

class DNNRegressor(BaseEstimator, RegressorMixin):
_estimator_type = "regressor" # مشخص کردن نوع رگرسور

def __init__(self, hidden_layer_sizes=(100, 50), learning_rate=0.001, dropout_rate=0.2, epochs=1000, batch_size=32):
    self.hidden_layer_sizes = hidden_layer_sizes
    self.learning_rate = learning_rate
    self.dropout_rate = dropout_rate
    self.epochs = epochs
    self.batch_size = batch_size
    self.model = None

def fit(self, X, y):
    # تبدیل داده‌ها به float32 برای سازگاری با TensorFlow
    X = np.asarray(X, dtype=np.float32)
    y = np.asarray(y, dtype=np.float32).reshape(-1, 1)  # اطمینان از شکل صحیح y
    
    self.model = Sequential()
    # لایه ورودی
    self.model.add(Dense(self.hidden_layer_sizes[0], activation='relu', input_shape=(X.shape[1],)))
    self.model.add(Dropout(self.dropout_rate))
    # لایه‌های مخفی
    for units in self.hidden_layer_sizes[1:]:
        self.model.add(Dense(units, activation='relu'))
        self.model.add(Dropout(self.dropout_rate))
    # لایه خروجی
    self.model.add(Dense(1))
    # کامپایل مدل
    self.model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=self.learning_rate), loss='mse')
    # Early Stopping
    early_stopping = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)
    # آموزش مدل
    self.model.fit(X, y, epochs=self.epochs, batch_size=self.batch_size, validation_split=0.1, 
                   callbacks=[early_stopping], verbose=0)
    return self

def predict(self, X):
    X = np.asarray(X, dtype=np.float32)
    return self.model.predict(X, verbose=0).flatten()

def score(self, X, y):
    # متد score برای سازگاری با sklearn
    y_pred = self.predict(X)
    return r2_score(y, y_pred)

def get_params(self, deep=True):
    return {
        "hidden_layer_sizes": self.hidden_layer_sizes,
        "learning_rate": self.learning_rate,
        "dropout_rate": self.dropout_rate,
        "epochs": self.epochs,
        "batch_size": self.batch_size
    }

def set_params(self, **params):
    for param, value in params.items():
        setattr(self, param, value)
    return self

تعریف تابع Willmott’s d

def willmott_d(y_true, y_pred):
mean_obs = np.mean(y_true)
numerator = np.sum((y_true - y_pred) ** 2)
denominator = np.sum((np.abs(y_pred - mean_obs) + np.abs(y_true - mean_obs)) ** 2)
return 1 - numerator / denominator if denominator != 0 else 0

مسیر فایل‌ها

input_path = r"D:\1Grok\Input\MetaLearner\GrokData_with_Full_InteractionsCTD.csv"
output_path = r"D:\1Grok\Output\MetaLearner"
os.makedirs(output_path, exist_ok=True)
print(f"Output path exists: {os.path.exists(output_path)}")

بررسی دسترسی نوشتن

try:
with open(os.path.join(output_path, "test_write.txt"), "w") as f:
f.write("Test")
os.remove(os.path.join(output_path, "test_write.txt"))
print("Write permission confirmed for output path")
except Exception as e:
print(f"Error in write permission: {e}")

خواندن داده‌ها

print("خواندن داده‌ها...")
data = pd.read_csv(input_path)
print("ستون‌های داده:", data.columns.tolist())

حذف ستون‌های غیرمرتبط

data = data.drop(columns=["Year", "Replication", "MainPlotFactor", "SubPlotFactor"], errors="ignore")

تعریف هدف‌ها

targets = [
"Specific Root Length", "% Root Colonization", "Leaf Area Index",
"SPAD_Mean", "CanopyTempDeficit_Mean", "Dry Matter Yield"
]

ویژگی‌ها

features = [col for col in data.columns if col not in targets]

استانداردسازی ویژگی‌ها

scaler = StandardScaler()
X_scaled = scaler.fit_transform(data[features])
data_scaled = pd.DataFrame(X_scaled, columns=features, index=data.index)
y = data[targets]

محاسبه Mutual Information

print("محاسبه Mutual Information...")
mi_scores = {}
for target in tqdm(targets, desc="محاسبه MI برای هدف‌ها"):
mi = mutual_info_regression(data_scaled[features], y[target].values.ravel())
mi_scores[target] = pd.Series(mi, index=features)

ذخیره MI

mi_df = pd.DataFrame(mi_scores)
mi_df.to_csv(os.path.join(output_path, "mi_scores_per_target.csv"))

نمودار MI برای هر هدف

for target in targets:
plt.figure(figsize=(10, 6))
mi_scores[target].sort_values(ascending=False).head(10).plot(kind="bar")
plt.title(f"Mutual Information Scores for {target}")
plt.tight_layout()
plt.savefig(os.path.join(output_path, f"mi_scores_per_target_{target}.png"))
plt.close()

ماتریس همبستگی MI

mi_matrix = mi_df.corr()
mi_matrix.to_csv(os.path.join(output_path, "mi_scores_matrix.csv"))
plt.figure(figsize=(8, 6))
sns.heatmap(mi_matrix, annot=True, cmap="coolwarm")
plt.title("MI Correlation Matrix")
plt.savefig(os.path.join(output_path, "mi_scores_matrix.png"))
plt.close()

ماتریس همبستگی ویژگی‌ها

corr_matrix = data_scaled[features].corr()
corr_matrix.to_csv(os.path.join(output_path, "correlation_matrix.csv"))
plt.figure(figsize=(10, 8))
sns.heatmap(corr_matrix, annot=False, cmap="coolwarm")
plt.title("Feature Correlation Matrix")
plt.savefig(os.path.join(output_path, "correlation_matrix.png"))
plt.close()

پیش‌انتخاب ویژگی‌ها با RFE برای هر هدف

print("پیش‌انتخاب ویژگی‌ها با RFE...")
base_estimator = LGBMRegressor(n_estimators=100, learning_rate=0.03, max_depth=5, random_state=42)
selected_features = set()
for target in tqdm(targets, desc="اجرای RFE برای هر هدف"):
rfe = RFE(estimator=base_estimator, n_features_to_select=8)
rfe.fit(data_scaled[features], y[target].values.ravel())
selected_features.update([features[i] for i in range(len(features)) if rfe.support_[i]])

selected_features = list(selected_features)[:12]
print(f"ویژگی‌های انتخاب‌شده با RFE: {selected_features}")

تعریف مسئله NSGA-III

class FeatureSelectionProblem(Problem):
def init(self, X, y, features, targets):
super().init(n_var=len(features), n_obj=len(targets) + 1, n_constr=0, xl=0, xu=1, type_var=np.bool_)
self.X = X
self.y = y
self.features = features
self.targets = targets
self.model = MultiOutputRegressor(LGBMRegressor(n_estimators=100, learning_rate=0.03, max_depth=5, random_state=42), n_jobs=1)

def _evaluate(self, X, out, *args, **kwargs):
    f = np.zeros((X.shape[0], len(self.targets) + 1))
    for i in range(X.shape[0]):
        selected = [self.features[j] for j in range(len(self.features)) if X[i, j]]
        if len(selected) == 0 or len(selected) > 12:
            f[i, :] = 1e5
            continue
        X_selected = self.X[selected]
        X_train, X_test, y_train, y_test = train_test_split(X_selected, self.y, test_size=0.2, random_state=42, shuffle=True)
        self.model.fit(X_train, y_train)
        y_pred = self.model.predict(X_test)
        for j, target in enumerate(self.targets):
            f[i, j] = -r2_score(y_test[target], y_pred[:, j])
        f[i, -1] = len(selected)
    out["F"] = f

اجرای NSGA-III

print("اجرای NSGA-III...")
ref_dirs = get_reference_directions("das-dennis", len(targets) + 1, n_partitions=12)
algorithm = NSGA3(pop_size=100, ref_dirs=ref_dirs, mutation_rate=0.7)
problem = FeatureSelectionProblem(data_scaled[selected_features], y, selected_features, targets)
res = minimize(problem, algorithm, ('n_gen', 50), verbose=True)

استخراج و تحلیل جبهه پارتو

pareto_solutions = res.F
num_features = pareto_solutions[:, -1]
mean_r2 = -np.mean(pareto_solutions[:, :len(targets)], axis=1)

گروه‌بندی بر اساس تعداد ویژگی‌ها و محاسبه میانگین R²

pareto_dict = {}
for nf, mr2 in zip(num_features, mean_r2):
nf = int(nf)
if nf not in pareto_dict:
pareto_dict[nf] = []
pareto_dict[nf].append(mr2)

pareto_summary = {nf: np.mean(scores) for nf, scores in pareto_dict.items() if 3 <= nf <= 12}

ذخیره جبهه پارتو

with open(os.path.join(output_path, "pareto_front_num_features_vs_mean_r2.txt"), "w") as f:
f.write("Number of Features\tMean R²\n")
for nf in sorted(pareto_summary.keys()):
f.write(f"{nf}\t{pareto_summary[nf]:.4f}\n")

نمودار جبهه پارتو

plt.figure(figsize=(10, 6))
plt.scatter(num_features, mean_r2, alpha=0.5)
plt.xlabel("Number of Features")
plt.ylabel("Mean R²")
plt.title("Pareto Front: Number of Features vs. Mean R²")
plt.savefig(os.path.join(output_path, "pareto_front_num_features_vs_mean_r2.png"))
plt.close()

انتخاب بهترین ویژگی‌ها

best_features = []
for i, x in enumerate(res.X):
selected = [selected_features[j] for j in range(len(selected_features)) if x[j]]
mean_r2 = -np.mean(res.F[i][:-1])
if len(selected) <= 12 and mean_r2 > 0.5:
best_features = selected
break
if not best_features:
best_features = [selected_features[j] for j in range(len(selected_features)) if res.X[0][j]][:12]
print(f"ویژگی‌های نهایی: {best_features}")

ذخیره ویژگی‌های انتخاب‌شده

with open(os.path.join(output_path, "selected_features.txt"), "w") as f:
f.write(f"Selected Features: {best_features}\n")

ماتریس همبستگی برای 15 ویژگی نهایی

if len(best_features) > 15:
best_features_corr = best_features[:15]
else:
best_features_corr = best_features
corr_matrix_final = data_scaled[best_features_corr].corr()
corr_matrix_final.to_csv(os.path.join(output_path, "correlation_matrix_final.csv"))
plt.figure(figsize=(10, 8))
sns.heatmap(corr_matrix_final, annot=True, cmap="coolwarm")
plt.title("Correlation Matrix for Final Features")
plt.savefig(os.path.join(output_path, "correlation_matrix_final.png"))
plt.close()

آماده‌سازی داده‌ها برای متالرنر

X = data_scaled[best_features]
y = data[targets]
X_train, X_temp, y_train, y_temp = train_test_split(X, y, train_size=0.7, random_state=42, shuffle=True)
X_test, X_val, y_test, y_val = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42, shuffle=True)

بررسی ابعاد داده‌ها

print(f"ابعاد X_train: {X_train.shape}, y_train: {y_train.shape}")
print(f"ابعاد X_test: {X_test.shape}, y_test: {y_test.shape}")
print(f"ابعاد X_val: {X_val.shape}, y_val: {y_val.shape}")

تعریف مدل‌های پایه

estimators = [
("lgbm", LGBMRegressor(n_estimators=100, learning_rate=0.03, max_depth=5, subsample=0.8, colsample_bytree=0.8, min_child_samples=10, random_state=42)),
("ann", DNNRegressor(hidden_layer_sizes=(100, 50), learning_rate=0.001, dropout_rate=0.2, epochs=1000, batch_size=32))
]
stacking_model = StackingRegressor(estimators=estimators, final_estimator=DNNRegressor(hidden_layer_sizes=(100, 50), learning_rate=0.001, dropout_rate=0.2, epochs=1000, batch_size=32))
multi_output_stacking = MultiOutputRegressor(stacking_model, n_jobs=1) # غیرفعال کردن موازی‌سازی

گرید سرچ با کراس ولیدیشن 5 فولدی

param_grid = {
"estimator__lgbm__num_leaves": [15, 20],
"estimator__ann__hidden_layer_sizes": [(100,), (100, 50), (50, 50)],
"estimator__ann__learning_rate": [0.001, 0.01],
"estimator__ann__dropout_rate": [0.1, 0.2]
}
grid_search = GridSearchCV(multi_output_stacking, param_grid, cv=5, scoring="neg_mean_squared_error", n_jobs=1, verbose=1)
print("اجرای گرید سرچ...")
try:
grid_search.fit(X_train, y_train)
except Exception as e:
print(f"خطا در گرید سرچ: {e}")
print("اجرای مدل بدون گرید سرچ...")
multi_output_stacking.fit(X_train, y_train)
best_model = multi_output_stacking
else:
best_model = grid_search.best_estimator_
print(f"بهترین پارامترها: {grid_search.best_params_}")

پیش‌بینی

y_pred_train = best_model.predict(X_train)
y_pred_test = best_model.predict(X_test)
y_pred_val = best_model.predict(X_val)

ارزیابی معیارها

print(f"Metrics saved to: {os.path.join(output_path, 'metrics.txt')}")
with open(os.path.join(output_path, "metrics.txt"), "w") as f:
for i, target in enumerate(targets):
f.write(f"\nResults for {target}:\n")
f.write(f"Train RMSE: {np.sqrt(mean_squared_error(y_train[target], y_pred_train[:, i])):.4f}\n")
f.write(f"Test RMSE: {np.sqrt(mean_squared_error(y_test[target], y_pred_test[:, i])):.4f}\n")
f.write(f"Validation RMSE: {np.sqrt(mean_squared_error(y_val[target], y_pred_val[:, i])):.4f}\n")
f.write(f"Train R²: {r2_score(y_train[target], y_pred_train[:, i]):.4f}\n")
f.write(f"Test R²: {r2_score(y_test[target], y_pred_test[:, i]):.4f}\n")
f.write(f"Validation R²: {r2_score(y_val[target], y_pred_val[:, i]):.4f}\n")
f.write(f"Train MAE: {mean_absolute_error(y_train[target], y_pred_train[:, i]):.4f}\n")
f.write(f"Test MAE: {mean_absolute_error(y_test[target], y_pred_test[:, i]):.4f}\n")
f.write(f"Validation MAE: {mean_absolute_error(y_val[target], y_pred_val[:, i]):.4f}\n")
f.write(f"Train Willmott’s d: {willmott_d(y_train[target], y_pred_train[:, i]):.4f}\n")
f.write(f"Test Willmott’s d: {willmott_d(y_test[target], y_pred_test[:, i]):.4f}\n")
f.write(f"Validation Willmott’s d: {willmott_d(y_val[target], y_pred_val[:, i]):.4f}\n")

تحلیل TreeExplainer

print("اجرای TreeExplainer...")
for i, target in enumerate(tqdm(targets, desc="تحلیل TreeExplainer")):
try:
stacking_for_target = best_model.estimators_[i]
lgbm_model = stacking_for_target.named_estimators_["lgbm"]
explainer = TreeExplainer(lgbm_model)
shap_values = explainer.shap_values(X_test.values)
plt.figure(figsize=(10, 6))
sns.barplot(x=np.abs(shap_values).mean(0), y=best_features)
plt.title(f"SHAP Values for {target} (LGBM)")
plt.savefig(os.path.join(output_path, f"shap_bar_{target}lgbm.png"))
plt.close()
with open(os.path.join(output_path, f"shap_bar
{target}_lgbm.txt"), "w") as f:
f.write(str(pd.Series(np.abs(shap_values).mean(0), index=best_features)))
except Exception as e:
print(f"خطا در تحلیل SHAP برای هدف {target}: {e}")
continue

نمودارهای اضافی

هیستوگرام برای CanopyTempDeficit_Mean

if "CanopyTempDeficit_Mean" in data.columns:
plt.figure(figsize=(10, 6))
plt.hist(data["CanopyTempDeficit_Mean"], bins=20)
plt.title("Histogram of CanopyTempDeficit_Mean")
plt.savefig(os.path.join(output_path, "histogram_CanopyTempDeficit_Mean.png"))
plt.close()
with open(os.path.join(output_path, "histogram_CanopyTempDeficit_Mean.txt"), "w") as f:
f.write(str(data["CanopyTempDeficit_Mean"].describe()))
else:
print("ستون 'CanopyTempDeficit_Mean' در داده‌ها پیدا نشد. هیستوگرام تولید نمی‌شود.")

Scatter Plot با معادله خط و معیارها

for target in targets:
for dataset, y_true, y_pred in [("Test", y_test[target], y_pred_test[:, targets.index(target)]),
("Validation", y_val[target], y_pred_val[:, targets.index(target)])]:
plt.figure(figsize=(10, 6))
plt.scatter(y_true, y_pred, alpha=0.5)
plt.plot([y_true.min(), y_true.max()], [y_true.min(), y_true.max()], "r--", label="1:1 Line")

    # خط رگرسیون
    m, b = np.polyfit(y_true, y_pred, 1)
    plt.plot(y_true, m*y_true + b, "b-", label=f"y={m:.2f}x+{b:.2f}")
    
    # معیارها
    r2 = r2_score(y_true, y_pred)
    rmse = np.sqrt(mean_squared_error(y_true, y_pred))
    plt.legend(title=f"R²: {r2:.4f}\nRMSE: {rmse:.4f}")
    
    plt.xlabel("Actual")
    plt.ylabel("Predicted")
    plt.title(f"Scatter Plot for {target} ({dataset})")
    plt.savefig(os.path.join(output_path, f"scatter_plot_{target}_{dataset}.png"))
    plt.close()
    
    with open(os.path.join(output_path, f"scatter_data_{target}_{dataset}.txt"), "w") as f:
        f.write(f"Actual: {y_true.values}\n")
        f.write(f"Predicted: {y_pred}\n")
        f.write(f"R²: {r2:.4f}\n")
        f.write(f"RMSE: {rmse:.4f}\n")

ذخیره مدل

joblib.dump(best_model, os.path.join(output_path, "multi_target_stacking_model.pkl"))
print("مدل ذخیره شد.")

Expected Results

Expected Results
The StackingRegressor in scikit-learn (version 1.5.2) should seamlessly support custom regression models, such as neural network-based regressors (e.g., TabTransformerRegressor, ANNRegressor, DNNRegressor, or SimpleDNNRegressor built with tensorflow), as long as they adhere to the scikit-learn API (i.e., implement fit, predict, and optionally score methods). Specifically:

When using a custom neural network regressor as the meta_regressor or one of the regressors in StackingRegressor, the model should be properly serialized and cloned during operations like GridSearchCV or model persistence with joblib, without raising serialization errors.
The StackingRegressor should integrate with GridSearchCV to perform hyperparameter tuning for both base and meta-regressors, even when these models rely on external libraries like tensorflow or pytorch.
The behavior should be consistent with that of mlxtend.regressor.StackingRegressor (version 0.23.1), which successfully handles such custom models without serialization or cloning issues.

In summary, StackingRegressor should allow users to combine custom neural network regressors with other base models (e.g., LGBMRegressor) in a stacking ensemble, enabling robust multi-output regression workflows without compatibility errors.

Actual Results

Actual Results
Instead of the expected seamless integration of custom neural network regressors with StackingRegressor in scikit-learn (version 1.5.2), the module raises serialization or cloning errors when used with models such as TabTransformerRegressor, ANNRegressor, or SimpleDNNRegressor (a custom regressor built with tensorflow). Specifically, when attempting to use these models as the meta_regressor or as one of the regressors in StackingRegressor, the following issues occur:

During hyperparameter tuning with GridSearchCV, the process fails due to serialization errors related to the cloning mechanism in sklearn.base.clone.
When attempting to save the trained StackingRegressor model using joblib.dump, a similar serialization error is raised.
The errors prevent the successful execution of the stacking ensemble, forcing a switch to mlxtend.regressor.StackingRegressor (version 0.23.1), which handles these custom models without issues.

Observed Error Example
Below is a representative error traceback observed when using a custom SimpleDNNRegressor (based on tensorflow) as the meta_regressor in StackingRegressor with GridSearchCV:
Traceback (most recent call last):
File ".../script.py", line 320, in
grid_search.fit(X_train, y_train)
File ".../site-packages/sklearn/model_selection/_search.py", line 874, in fit
self._run_search(evaluate_candidates)
File ".../site-packages/sklearn/model_selection/_search.py", line 1388, in _run_search
evaluate_candidates(ParameterGrid(self.param_grid))
File ".../site-packages/sklearn/model_selection/_search.py", line 821, in evaluate_candidates
out = parallel(
File ".../site-packages/joblib/parallel.py", line 1043, in call
if self.dispatch_one_batch(iterator):
File ".../site-packages/joblib/parallel.py", line 861, in dispatch_one_batch
self._dispatch(tasks)
File ".../site-packages/joblib/parallel.py", line 779, in _dispatch
job = self._backend.apply_async(batch, callback=cb)
File ".../site-packages/joblib/_parallel_backends.py", line 208, in apply_async
result = ImmediateResult(func)
File ".../site-packages/joblib/_parallel_backends.py", line 572, in init
self.results = batch()
File ".../site-packages/joblib/parallel.py", line 262, in call
return [func(*args, **kwargs)
File ".../site-packages/sklearn/utils/fixes.py", line 216, in call
return self.function(*args, **kwargs)
File ".../site-packages/sklearn/model_selection/_validation.py", line 680, in _fit_and_score
estimator = estimator.set_params(**cloned_parameters)
File ".../site-packages/sklearn/base.py", line 243, in set_params
raise ValueError(
ValueError: Cannot clone object '<main.SimpleDNNRegressor object at ...>' (type <class 'main.SimpleDNNRegressor'>): it does not seem to be a scikit-learn estimator as it does not implement a 'get_params' method.

Additional Notes

The same code runs successfully when using mlxtend.regressor.StackingRegressor, indicating that the issue is specific to scikit-learn's implementation of StackingRegressor.
The error persists across multiple custom neural network regressors, suggesting a general limitation in handling models that rely on external libraries like tensorflow.
A complete code example reproducing this issue has been provided in the issue description for further investigation.

Versions

1.6.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions