自定义sklearn模型.fit()方法在Pipeline中触发NotFittedError问题
我正在用多个Pipeline做交叉验证对比,想加一个固定系数的基准模型——不依赖训练数据,直接加载预存的模型。于是我继承了sklearn的LinearRegression,重写了.fit()方法,让它不读取训练数据,直接加载预存模型。单独用这个自定义模型一切正常,但放进Pipeline后就触发了NotFittedError。
我的代码细节
1. 创建并存储基准模型
import numpy as np import pickle from sklearn.linear_model import LinearRegression from sklearn.pipeline import Pipeline X = np.array([[1],[2],[3]]) y = [10,20,30] model = LinearRegression(fit_intercept=False).fit(X,y) pickle.dump(model, open('benchmark_model.txt', 'wb')) print(model.coef_) # 输出: [10.]
2. 定义自定义基准模型
class benchmark_model(LinearRegression): def fit(self, X, y = None): self = pickle.load(open('benchmark_model.txt', 'rb')) return self
3. 单独测试自定义模型(运行正常)
X=np.array([[1],[2],[3]]) y=[5,10,15] model = benchmark_model() model = model.fit(X,y) print(model.coef_) print(model.predict(X)) # 输出: # [10.] # [10. 20. 30.]
4. 普通LinearRegression在Pipeline中正常运行
pipe = Pipeline([('model',LinearRegression())]) pipe.fit(X,y).predict(X) # 输出: array([ 5., 10., 15.])
5. 自定义模型加入Pipeline报错
pipe = Pipeline([('model',benchmark_model())]) pipe.fit(X,y).predict(X) # 报错: NotFittedError: This benchmark_model instance is not fitted yet. Call 'fit' with appropriate arguments before using this method.
问题根源
你的.fit()方法犯了一个关键错误:你在方法里把self替换成了从pickle加载的新实例,然后返回这个新实例,但Pipeline仍然持有最初创建的那个benchmark_model实例。
当Pipeline调用pipe.fit(X,y)时,它会调用benchmark_model.fit(),但它并不会接收你返回的新实例——而是继续使用原来的那个未被拟合的实例。所以当后续调用predict()时,原实例并没有被正确初始化(没有设置coef_等拟合后的属性),自然就触发了NotFittedError。
单独测试时没问题,是因为你手动把model变量重新赋值为fit()返回的新实例,但Pipeline不会这么做,它会保持对初始实例的引用。
正确的实现方式
不要替换self,而是把加载的模型的所有必要属性复制到当前的self实例上。这样,Pipeline持有的原实例就会被正确“拟合”(其实是加载预存的参数)。
修改后的自定义模型代码:
class benchmark_model(LinearRegression): def fit(self, X, y=None): # 加载预存模型 loaded_model = pickle.load(open('benchmark_model.txt', 'rb')) # 复制加载模型的核心拟合属性到当前self for attr_name in ['coef_', 'intercept_', 'fit_intercept']: if hasattr(loaded_model, attr_name): setattr(self, attr_name, getattr(loaded_model, attr_name)) # 标记模型已拟合(sklearn内部判断拟合状态的标志) self._is_fitted = True return self
测试修改后的代码:
pipe = Pipeline([('model',benchmark_model())]) pipe.fit(X,y).predict(X) # 输出: array([10., 20., 30.])
这样就能正常运行了,因为现在fit()方法是在原实例上设置了拟合后的属性,而不是返回一个新实例,Pipeline持有的实例已经被正确初始化。
如果想要更通用的方式,也可以遍历加载模型的所有属性并复制,但上面的方式更明确,避免复制不必要的内部属性。
内容的提问来源于stack exchange,提问作者Fredz0r

