不能只靠feature_names_in_保存特征名称,因其为训练后只读属性,仅在DataFrame输入时存在,不随模型序列化保存,且Pipeline中ColumnTransformer会改变特征名,需显式提取并单独保存。

保存特征名称时为什么不能只靠 feature_names_in_?
因为 feature_names_in_ 是训练后模型的只读属性,只在部分预处理器(如 StandardScaler、OneHotEncoder)和估计器(如 LogisticRegression)中存在,且仅在用 pd.DataFrame 训练时自动捕获——如果传入 np.ndarray,这个属性直接为空。更关键的是,它不随模型一起被 pickle 或 joblib 序列化,加载模型后就丢失了。
所以必须显式保存,推荐方式是:把特征名和模型分开存,或打包进一个字典再序列化。
- 训练前从
pd.DataFrame提取df.columns.tolist(),别依赖训练后的feature_names_in_ - 避免用
model.feature_names_in_.tolist()作为保存来源,除非你 100% 确保输入是 DataFrame 且没做列删减 - 如果 pipeline 中有
ColumnTransformer,特征名会变复杂,需用get_feature_names_out()获取最终名,而不是原输入列名
用 joblib 同时保存模型和特征名最稳妥
joblib 比 pickle 更适合保存 scikit-learn 对象,且支持压缩和跨版本兼容性更好。把模型和特征名塞进一个字典,一次性保存,加载时也一起取出,不会错位。
import joblib
from sklearn.ensemble import RandomForestClassifier
import pandas as pd
<h1>假设 X_train 是 pd.DataFrame</h1><p>X_train = pd.DataFrame([[1, 2], [3, 4]], columns=["age", "income"])
y_train = [0, 1]</p><p>model = RandomForestClassifier().fit(X_train, y_train)
feature_names = X_train.columns.tolist() # 显式提取,可靠</p><h1>打包保存</h1><p>joblib.dump({
"model": model,
"feature_names": feature_names
}, "model_with_features.joblib")</p><h1>加载时直接解包</h1><p>data = joblib.load("model_with_features.joblib")
loaded_model = data["model"]
loaded_features = data["feature_names"] # 保证和模型同步
</p>
导出为 JSON 或 CSV 用于非 Python 环境怎么办?
如果下游是 Java、R 或前端系统,需要纯文本格式交换特征名,就不能依赖 joblib。这时应单独导出特征名列表,注意两点:一是保留顺序(和模型输入维度严格一致),二是处理特殊字符(如空格、斜杠、点号)——scikit-learn 本身不限制列名,但 JSON/CSV 解析可能出问题。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 用
json.dump(feature_names, open("features.json", "w"))导出,比 CSV 更安全(无分隔符歧义) - 如果必须用 CSV,写成单列、无 header:
pd.Series(feature_names).to_csv("features.csv", index=False, header=False) - 避免在特征名里用
、\、控制字符;导出前可加简单清洗:[name.replace(" ", "_").replace(".", "_") for name in feature_names]
Pipeline + ColumnTransformer 场景下怎么拿到最终特征名?
当 pipeline 包含 ColumnTransformer(比如数值列缩放 + 类别列 one-hot),原始列名会扩展成几十个新名(如 income → income_scaled,city → city_Tokyo, city_NYC)。此时 feature_names_in_ 仍是原始列名,完全没用。
必须调用 pipeline.named_steps["preprocessor"].get_feature_names_out()(scikit-learn ≥ 1.0)或兼容写法:
# 兼容旧版(推荐统一用 get_feature_names_out(),并传入原始列名作输入<p>final_names = preprocessor.get_feature_names_out(input_features=original_columns) </p>
记住:这个输出名顺序必须和 pipeline.predict() 的输入列顺序完全一致,否则预测结果错位——这也是最容易被忽略的隐性 bug 来源。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










