Sklearn分类模型Pickle序列化后结果不一致问题求助
首先看你的代码,发现一个关键bug:第二个绘制ROC曲线的回调RocPlot没有把model下拉框的选中值作为输入参数,导致回调里的mod变量要么未定义,要么使用的是全局的旧值,这会让你加载错误的模型,自然评估结果和原模型完全不符!这大概率是AUC差异巨大的直接原因。
先修正这个回调的参数问题,然后我们再解决序列化和模型保存的其他潜在问题:
1. 修复回调的参数传递问题
修改RocPlot回调,把Input('model', 'value')加入输入列表,确保加载的模型和用户选择的一致:
@app.callback( Output('my-graph', 'figure'), [Input('roc','n_clicks'), Input('model', 'value')] # 新增model输入参数 ) def RocPlot(button, mod): # 新增mod参数接收下拉框值 if button is None: return {} # Dash的figure需要字典格式,原代码返回的[{}]会报错 else: ### loading models if mod == 'logreg': with open("python_logreg_model.pkl", "rb") as file_handler: model = pickle.load(file_handler) elif mod == 'mlp': with open("python_mlp_model.pkl", "rb") as file_handler: model = pickle.load(file_handler) # 其他模型的加载逻辑... # 后续ROC绘制代码不变 fp, tp, threshold= metrics.roc_curve(y_test, model.predict_proba(x_test)[:,1]) AUC= metrics.auc(fp, tp) lw = 2 trace1 = go.Scatter(x=fp, y=tp, mode='lines', line=dict(color='darkorange', width=lw), name='ROC curve (area = %0.2f)' % AUC ) trace2 = go.Scatter(x=[0, 1], y=[0, 1], mode='lines', line=dict(color='navy', width=lw, dash='dash'), showlegend=False) layout = go.Layout(title='Receiver operating characteristic example', xaxis=dict(title='False Positive Rate'), yaxis=dict(title='True Positive Rate')) print('plot done') return{ 'data': [trace1, trace2], 'layout': layout }
2. 固定模型的随机状态
Sklearn的很多模型(比如MLPClassifier、部分solver下的LogisticRegression)包含随机初始化过程,如果不固定random_state,每次训练的模型参数会有差异,导致评估结果波动。训练模型时一定要指定这个参数:
# 训练LogisticRegression时 logreg = LogisticRegression(random_state=42) # 训练MLPClassifier时 mlp = MLPClassifier(random_state=42)
3. 使用Joblib替代Pickle(Sklearn官方推荐)
对于Sklearn模型,官方更推荐用joblib来序列化,因为它对NumPy数组和稀疏矩阵的处理更高效,能避免一些pickle可能出现的序列化异常:
# 保存模型 from joblib import dump, load dump(logreg, "python_logreg_model.joblib") # 加载模型 model = load("python_logreg_model.joblib")
4. 保存完整的预处理Pipeline(如果有数据预处理步骤)
如果你的数据集在训练前做了标准化、编码等预处理(比如StandardScaler、OneHotEncoder),只保存模型会导致加载后用不同的预处理参数处理测试集,结果完全错误。应该把预处理步骤和模型打包成Pipeline,然后保存整个Pipeline:
from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler # 构建包含预处理的Pipeline pipe = Pipeline([ ('scaler', StandardScaler()), # 预处理步骤 ('logreg', LogisticRegression(random_state=42)) # 模型 ]) pipe.fit(x_train, y_train) # 保存Pipeline dump(pipe, "logreg_pipeline.joblib") # 加载后直接使用,无需单独处理预处理 loaded_pipe = load("logreg_pipeline.joblib") fp, tp, threshold = metrics.roc_curve(y_test, loaded_pipe.predict_proba(x_test)[:,1])
5. 确保训练和评估用的数据集一致
检查x_train、y_train、x_test、y_test在两个回调中是否是完全相同的数据集,避免在回调之间不小心修改了数据集(比如重新划分了训练测试集)。可以把数据集的划分放在回调外部,确保全局唯一。
内容的提问来源于stack exchange,提问作者Nawres Jguirim

