Scikit-learn 非常适合构建传统机器学习任务。它提供了数据切分、特征预处理、模型训练、交叉验证和指标评估等工具。对于表格数据分类任务,先用 Scikit-learn 做一个完整 baseline,通常比直接上复杂深度学习模型更稳。

本文通过一个二分类任务,演示从数据读取到模型保存的完整流程。

准备数据

假设数据文件 churn.csv 用于预测用户是否流失,字段包括年龄、消费金额、登录次数、城市、设备类型和标签:

1
2
3
4
5
import pandas as pd

df = pd.read_csv("churn.csv")
print(df.head())
print(df.info())

分离特征和标签:

1
2
3
target = "is_churn"
X = df.drop(columns=[target])
y = df[target]

如果标签缺失,应该先清理:

1
2
3
mask = y.notna()
X = X[mask]
y = y[mask]

划分训练集和测试集

分类任务建议使用分层切分,保持标签比例:

1
2
3
4
5
6
7
8
9
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.2,
random_state=42,
stratify=y,
)

random_state 保证结果可复现。测试集只用于最终评估,不应该参与调参。

区分数值和类别特征

数值特征和类别特征通常需要不同处理:

1
2
numeric_features = ["age", "amount", "login_count"]
categorical_features = ["city", "device"]

实际项目中可以根据 df.dtypes 自动识别,但手动列出更可控,尤其是某些 ID 字段虽然是数字,但不应该按连续数值处理。

构建预处理流程

使用 ColumnTransformer 分别处理不同特征:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.pipeline import Pipeline

numeric_transformer = Pipeline(steps=[
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler()),
])

categorical_transformer = Pipeline(steps=[
("imputer", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore")),
])

preprocessor = ColumnTransformer(
transformers=[
("num", numeric_transformer, numeric_features),
("cat", categorical_transformer, categorical_features),
]
)

handle_unknown="ignore" 很重要。线上或测试集中可能出现训练集没有见过的新城市、新设备,如果不忽略会直接报错。

训练模型

以逻辑回归为例:

1
2
3
4
5
6
7
8
from sklearn.linear_model import LogisticRegression

model = Pipeline(steps=[
("preprocessor", preprocessor),
("classifier", LogisticRegression(max_iter=1000, class_weight="balanced")),
])

model.fit(X_train, y_train)

把预处理和模型放在同一个 Pipeline 中,可以避免训练和预测时特征处理不一致,也方便保存整体模型。

class_weight="balanced" 适合类别不均衡的初始 baseline,但最终是否保留要根据验证指标决定。

评估模型

预测:

1
y_pred = model.predict(X_test)

输出指标:

1
2
3
4
from sklearn.metrics import classification_report, confusion_matrix

print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred))

如果业务更关注流失用户召回率,就重点观察正类 recall。如果更担心误伤正常用户,就重点观察 precision。

对于支持概率输出的模型,可以调整阈值:

1
2
3
y_proba = model.predict_proba(X_test)[:, 1]
y_pred_custom = (y_proba >= 0.4).astype(int)
print(classification_report(y_test, y_pred_custom))

阈值不是固定必须 0.5,应该根据业务成本选择。

交叉验证

为了评估模型稳定性,可以使用交叉验证:

1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.model_selection import cross_val_score

scores = cross_val_score(
model,
X_train,
y_train,
cv=5,
scoring="f1",
)

print(scores)
print(scores.mean())

如果不同折之间分数差异很大,说明数据分布不稳定、样本量不足或特征存在问题。

保存模型

使用 joblib 保存整个 Pipeline:

1
2
3
import joblib

joblib.dump(model, "churn_model.joblib")

加载并预测:

1
2
3
loaded_model = joblib.load("churn_model.joblib")
pred = loaded_model.predict(X_test.head(5))
print(pred)

保存整个 Pipeline 的好处是预处理步骤也一起保存,部署时不需要重新手写编码和标准化逻辑。

常见问题

训练报错 “could not convert string to float” 通常说明类别特征没有编码。

测试集报错未知类别,通常是 OneHotEncoder 没有设置 handle_unknown="ignore"

指标很好但上线很差,可能是数据泄漏。例如使用了未来信息、标签派生字段,或者训练测试切分方式不符合真实业务时间顺序。

类别严重不均衡时,不要只看 accuracy,应关注 precision、recall、F1 和 PR 曲线。

小结

Scikit-learn 的 Pipeline 和 ColumnTransformer 可以把数据预处理和模型训练串成一个可复现流程。完整分类任务应包含数据清理、分层切分、特征预处理、模型训练、指标评估、阈值选择和模型保存。这个流程可以作为大多数表格分类任务的 baseline。