一、核心流程图解

1.1 整体流程

原始数据
    ↓
【步骤1】数据准备
    ↓
【步骤2】数据切分 → 训练集 + 测试集
    ↓
【步骤3】用训练集训练模型
    ↓
【步骤4】用测试集评估模型
    ↓
结果分析

让我们逐步详细拆解每个步骤。


二、步骤1:数据准备

2.1 数据长什么样?

示例:员工离职预测数据

员工ID 年龄 工资 工作年限 满意度 是否离职
1 35 8000 5 7 0(留任)
2 28 6000 2 4 1(离职)
3 42 12000 10 8 0(留任)
1000 31 7500 3 6 0(留任)

数据结构

  • 特征(X):年龄、工资、工作年限、满意度(用来预测)
  • 标签(y):是否离职(要预测的目标)

2.2 Python数据准备

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

# 1. 读取数据
data = pd.read_csv('employees.csv')

# 查看数据
print(data.head())
print(f"数据形状:{data.shape}")  # 例如:(1000, 6)

# 2. 分离特征和标签
X = data[['年龄', '工资', '工作年限', '满意度']]  # 特征
y = data['是否离职']  # 标签

print(f"特征矩阵形状:{X.shape}")  # (1000, 4)
print(f"标签向量形状:{y.shape}")  # (1000,)

关键概念

  • X(大写):特征矩阵,每行是一个样本,每列是一个特征
  • y(小写):标签向量,每个元素对应一个样本的标签

三、步骤2:数据切分

3.1 方法A:简单随机切分(最常用)

原理图解
原始数据(1000条)
[1][2][3][4]...[1000]
        ↓
    随机打乱
        ↓
[523][12][789][456]...[234]
        ↓
    按比例切分
        ↓
├─ 训练集(700条,70%)
│  [523][12][789]...[第700条]
│
└─ 测试集(300条,30%)
   [第701条]...[234]
Python实现
# 方法1:使用sklearn的train_test_split(推荐)
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    X,                    # 特征
    y,                    # 标签
    test_size=0.3,        # 测试集比例30%
    random_state=42,      # 随机种子(保证可重复)
    stratify=y            # 分层抽样(保持类别比例)
)

# 查看切分结果
print(f"训练集特征形状:{X_train.shape}")  # (700, 4)
print(f"训练集标签形状:{y_train.shape}")  # (700,)
print(f"测试集特征形状:{X_test.shape}")   # (300, 4)
print(f"测试集标签形状:{y_test.shape}")   # (300,)

# 检查类别比例
print(f"原始数据离职率:{y.mean():.2%}")           # 例如:15%
print(f"训练集离职率:{y_train.mean():.2%}")       # 15%(相同)
print(f"测试集离职率:{y_test.mean():.2%}")        # 15%(相同)
参数详解
参数 作用 推荐值
test_size 测试集比例 0.2-0.3(即20%-30%)
random_state 随机种子 任意整数(如42),保证结果可重复
stratify 分层抽样 设为y,保持类别比例一致
shuffle 是否打乱 默认True,通常保持

方法2:手动切分(理解原理)
# 手动实现train_test_split的逻辑

# 1. 确定切分点
n_samples = len(X)  # 1000
split_point = int(n_samples * 0.7)  # 700

# 2. 生成随机索引
np.random.seed(42)  # 设置随机种子
indices = np.random.permutation(n_samples)  # 随机打乱0-999

# 3. 切分索引
train_indices = indices[:split_point]  # 前700个索引
test_indices = indices[split_point:]   # 后300个索引

# 4. 根据索引切分数据
X_train = X.iloc[train_indices]
X_test = X.iloc[test_indices]
y_train = y.iloc[train_indices]
y_test = y.iloc[test_indices]

print(f"训练集大小:{len(X_train)}")  # 700
print(f"测试集大小:{len(X_test)}")   # 300

3.2 方法B:交叉验证切分

3折交叉验证示例
from sklearn.model_selection import KFold

# 创建3折交叉验证对象
kf = KFold(n_splits=3, shuffle=True, random_state=42)

# 遍历每一折
fold = 1
for train_index, test_index in kf.split(X):
    print(f"\n===== 第{fold}折 =====")
    
    # 切分数据
    X_train = X.iloc[train_index]
    X_test = X.iloc[test_index]
    y_train = y.iloc[train_index]
    y_test = y.iloc[test_index]
    
    print(f"训练集大小:{len(X_train)}")  # 约667条
    print(f"测试集大小:{len(X_test)}")   # 约333条
    print(f"训练集索引范围:{train_index[:5]}...{train_index[-5:]}")
    print(f"测试集索引范围:{test_index[:5]}...{test_index[-5:]}")
    
    fold += 1

输出示例

===== 第1折 =====
训练集大小:667
测试集大小:333
训练集索引范围:[0 1 2 3 4]...[995 996 997 998 999]
测试集索引范围:[334 335 336 337 338]...[662 663 664 665 666]

===== 第2折 =====
训练集大小:667
测试集大小:333
...

3.3 方法C:时间序列切分

from sklearn.model_selection import TimeSeriesSplit

# 假设数据按时间排序
# 创建时间序列5折交叉验证
tscv = TimeSeriesSplit(n_splits=5)

for fold, (train_index, test_index) in enumerate(tscv.split(X), 1):
    print(f"\n===== 第{fold}折 =====")
    print(f"训练集:索引 {train_index[0]}{train_index[-1]}")
    print(f"测试集:索引 {test_index[0]}{test_index[-1]}")
    
    X_train, X_test = X.iloc[train_index], X.iloc[test_index]
    y_train, y_test = y.iloc[train_index], y.iloc[test_index]

输出示例

===== 第1折 =====
训练集:索引 0 到 166
测试集:索引 167 到 333

===== 第2折 =====
训练集:索引 0 到 333
测试集:索引 334 到 500

===== 第3折 =====
训练集:索引 0 到 500
测试集:索引 501 到 666
...

特点:训练集逐步扩大,测试集始终在训练集之后


四、步骤3:用训练集训练模型

4.1 训练的本质

训练 = 让模型从数据中学习规律

训练前:
  模型:一张白纸,不知道任何规律
  
训练过程:
  模型看到:35岁、8000工资、满意度7 → 留任
  模型看到:28岁、6000工资、满意度4 → 离职
  模型看到:42岁、12000工资、满意度8 → 留任
  ...(看完700条训练数据)
  
训练后:
  模型学到:年轻+低工资+低满意度 → 容易离职
           年长+高工资+高满意度 → 容易留任

4.2 Python训练代码

基础训练
from sklearn.linear_model import LogisticRegression

# 1. 创建模型对象
model = LogisticRegression(random_state=42)

# 2. 训练模型(fit = 拟合 = 学习)
model.fit(X_train, y_train)

print("模型训练完成!")

# 3. 查看模型学到的参数
print(f"截距:{model.intercept_}")
print(f"系数:{model.coef_}")
# 例如:系数 = [-0.05, 0.0003, 0.1, 0.2]
# 含义:年龄每增加1岁,离职概率降低0.05
#       工资每增加1元,离职概率增加0.0003
#       ...

训练过程可视化
# 查看训练集上的表现
train_score = model.score(X_train, y_train)
print(f"训练集准确率:{train_score:.2%}")  # 例如:87%

# 预测几个训练样本
sample_predictions = model.predict(X_train[:5])
sample_true = y_train.iloc[:5].values

print("\n训练集前5个样本:")
for i in range(5):
    print(f"样本{i+1}:预测={sample_predictions[i]}, 真实={sample_true[i]}")

输出示例

训练集准确率:87%

训练集前5个样本:
样本1:预测=0, 真实=0 ✓
样本2:预测=1, 真实=1 ✓
样本3:预测=0, 真实=0 ✓
样本4:预测=0, 真实=1 ✗
样本5:预测=1, 真实=1 ✓

4.3 不同模型的训练

# 逻辑回归
from sklearn.linear_model import LogisticRegression
model1 = LogisticRegression()
model1.fit(X_train, y_train)

# 决策树
from sklearn.tree import DecisionTreeClassifier
model2 = DecisionTreeClassifier(max_depth=5)
model2.fit(X_train, y_train)

# 随机森林
from sklearn.ensemble import RandomForestClassifier
model3 = RandomForestClassifier(n_estimators=100)
model3.fit(X_train, y_train)

# 支持向量机
from sklearn.svm import SVC
model4 = SVC(kernel='rbf')
model4.fit(X_train, y_train)

print("所有模型训练完成!")

关键点

  • 所有模型的训练接口都是.fit(X_train, y_train)
  • 训练只使用训练集,测试集完全不能碰

五、步骤4:用测试集评估模型

5.1 评估的本质

评估 = 用模型从未见过的数据考试

测试前:
  模型:已经用700条训练数据学习完毕
  测试集:300条全新数据,模型从未见过
  
测试过程:
  模型看到测试样本1:30岁、7000工资、满意度5
  模型预测:离职(概率65%)
  真实情况:离职 ✓
  
  模型看到测试样本2:40岁、10000工资、满意度8
  模型预测:留任(概率80%)
  真实情况:留任 ✓
  
  ...(预测完300条)
  
测试后:
  统计:预测对了255条,错了45条
  准确率:255/300 = 85%

5.2 Python评估代码

基础评估
# 1. 计算测试集准确率
test_score = model.score(X_test, y_test)
print(f"测试集准确率:{test_score:.2%}")  # 例如:85%

# 2. 进行预测
y_pred = model.predict(X_test)

# 3. 对比预测结果和真实标签
print("\n前10个测试样本的预测结果:")
comparison = pd.DataFrame({
    '真实值': y_test.iloc[:10].values,
    '预测值': y_pred[:10],
    '是否正确': y_test.iloc[:10].values == y_pred[:10]
})
print(comparison)

输出示例

测试集准确率:85%

前10个测试样本的预测结果:
   真实值  预测值  是否正确
0     0     0    True
1     1     1    True
2     0     0    True
3     1     0   False
4     0     0    True
5     1     1    True
6     0     1   False
7     1     1    True
8     0     0    True
9     0     0    True

详细评估指标
from sklearn.metrics import classification_report, confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt

# 1. 混淆矩阵
cm = confusion_matrix(y_test, y_pred)
print("混淆矩阵:")
print(cm)
print("\n解释:")
print(f"真负例(正确预测留任):{cm[0,0]}")
print(f"假正例(错误预测离职):{cm[0,1]}")
print(f"假负例(错误预测留任):{cm[1,0]}")
print(f"真正例(正确预测离职):{cm[1,1]}")

# 2. 可视化混淆矩阵
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel('预测值')
plt.ylabel('真实值')
plt.title('混淆矩阵')
plt.show()

# 3. 详细分类报告
print("\n分类报告:")
print(classification_report(y_test, y_pred, 
                           target_names=['留任', '离职']))

输出示例

混淆矩阵:
[[215  10]
 [ 35  40]]

解释:
真负例(正确预测留任):215
假正例(错误预测离职):10
假负例(错误预测留任):35
真正例(正确预测离职):40

分类报告:
              precision    recall  f1-score   support

        留任       0.86      0.96      0.90       225
        离职       0.80      0.53      0.64        75

    accuracy                           0.85       300
   macro avg       0.83      0.74      0.77       300
weighted avg       0.84      0.85      0.84       300

预测概率分析
# 获取预测概率
y_proba = model.predict_proba(X_test)

print("前5个测试样本的预测概率:")
for i in range(5):
    print(f"样本{i+1}:")
    print(f"  留任概率:{y_proba[i,0]:.2%}")
    print(f"  离职概率:{y_proba[i,1]:.2%}")
    print(f"  预测结果:{'离职' if y_pred[i]==1 else '留任'}")
    print(f"  真实结果:{'离职' if y_test.iloc[i]==1 else '留任'}")
    print()

输出示例

前5个测试样本的预测概率:
样本1:
  留任概率:85%
  离职概率:15%
  预测结果:留任
  真实结果:留任 ✓

样本2:
  留任概率:30%
  离职概率:70%
  预测结果:离职
  真实结果:离职 ✓

样本3:
  留任概率:92%
  离职概率:8%
  预测结果:留任
  真实结果:留任 ✓
...

5.3 对比训练集和测试集表现

# 计算两个数据集的准确率
train_acc = model.score(X_train, y_train)
test_acc = model.score(X_test, y_test)

print(f"训练集准确率:{train_acc:.2%}")
print(f"测试集准确率:{test_acc:.2%}")
print(f"差异:{abs(train_acc - test_acc):.2%}")

# 判断模型状态
if train_acc > 0.95 and test_acc < 0.70:
    print("\n⚠️ 警告:严重过拟合!")
    print("模型在训练集上表现很好,但在测试集上很差")
    print("建议:简化模型、增加正则化、收集更多数据")
    
elif train_acc < 0.70 and test_acc < 0.70:
    print("\n⚠️ 警告:欠拟合!")
    print("模型在两个数据集上都表现不好")
    print("建议:使用更复杂的模型、增加特征、调整超参数")
    
elif abs(train_acc - test_acc) < 0.05:
    print("\n✓ 模型状态良好!")
    print("训练集和测试集表现接近,泛化能力强")
    
else:
    print("\n⚠️ 轻微过拟合")
    print("可以接受,但有改进空间")

六、完整实战案例

6.1 简单切分完整流程

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report
import matplotlib.pyplot as plt

# ========== 步骤1:数据准备 ==========
print("=" * 50)
print("步骤1:数据准备")
print("=" * 50)

# 模拟数据(实际应用中从文件读取)
np.random.seed(42)
n_samples = 1000

data = pd.DataFrame({
    '年龄': np.random.randint(22, 60, n_samples),
    '工资': np.random.randint(4000, 15000, n_samples),
    '工作年限': np.random.randint(0, 20, n_samples),
    '满意度': np.random.randint(1, 11, n_samples)
})

# 生成标签(简化规则:年轻+低工资+低满意度 → 离职)
data['是否离职'] = (
    (data['年龄'] < 30) & 
    (data['工资'] < 7000) & 
    (data['满意度'] < 5)
).astype(int)

print(f"数据形状:{data.shape}")
print(f"离职率:{data['是否离职'].mean():.2%}")
print("\n前5行数据:")
print(data.head())

# 分离特征和标签
X = data[['年龄', '工资', '工作年限', '满意度']]
y = data['是否离职']

# ========== 步骤2:数据切分 ==========
print("\n" + "=" * 50)
print("步骤2:数据切分(70%训练,30%测试)")
print("=" * 50)

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

print(f"训练集大小:{len(X_train)} ({len(X_train)/len(X)*100:.1f}%)")
print(f"测试集大小:{len(X_test)} ({len(X_test)/len(X)*100:.1f}%)")
print(f"训练集离职率:{y_train.mean():.2%}")
print(f"测试集离职率:{y_test.mean():.2%}")

# ========== 步骤3:训练模型 ==========
print("\n" + "=" * 50)
print("步骤3:训练模型")
print("=" * 50)

model = LogisticRegression(random_state=42, max_iter=1000)
model.fit(X_train, y_train)

print("✓ 模型训练完成!")
print(f"模型参数:")
print(f"  截距:{model.intercept_[0]:.4f}")
for i, feature in enumerate(X.columns):
    print(f"  {feature}系数:{model.coef_[0][i]:.4f}")

# ========== 步骤4:评估模型 ==========
print("\n" + "=" * 50)
print("步骤4:评估模型")
print("=" * 50)

# 训练集表现
train_pred = model.predict(X_train)
train_acc = accuracy_score(y_train, train_pred)
print(f"训练集准确率:{train_acc:.2%}")

# 测试集表现
test_pred = model.predict(X_test)
test_acc = accuracy_score(y_test, test_pred)
print(f"测试集准确率:{test_acc:.2%}")

print(f"\n准确率差异:{abs(train_acc - test_acc):.2%}")

# 详细报告
print("\n测试集详细报告:")
print(classification_report(y_test, test_pred, 
                           target_names=['留任', '离职']))

# ========== 可视化 ==========
from sklearn.metrics import confusion_matrix
import seaborn as sns

cm = confusion_matrix(y_test, test_pred)

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 混淆矩阵
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=axes[0])
axes[0].set_xlabel('预测值')
axes[0].set_ylabel('真实值')
axes[0].set_title('混淆矩阵')

# 准确率对比
metrics = ['训练集', '测试集']
accuracies = [train_acc, test_acc]
axes[1].bar(metrics, accuracies, color=['skyblue', 'lightcoral'])
axes[1].set_ylim([0, 1])
axes[1].set_ylabel('准确率')
axes[1].set_title('训练集 vs 测试集准确率')
for i, v in enumerate(accuracies):
    axes[1].text(i, v + 0.02, f'{v:.2%}', ha='center')

plt.tight_layout()
plt.show()

print("\n✓ 完整流程执行完毕!")

6.2 交叉验证完整流程

from sklearn.model_selection import cross_val_score, cross_validate
import numpy as np

print("=" * 50)
print("5折交叉验证完整流程")
print("=" * 50)

# 创建模型
model = LogisticRegression(random_state=42, max_iter=1000)

# 方法1:简单交叉验证(只返回准确率)
scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')

print("\n5次交叉验证准确率:")
for i, score in enumerate(scores, 1):
    print(f"  第{i}折:{score:.2%}")

print(f"\n平均准确率:{scores.mean():.2%}")
print(f"标准差:{scores.std():.3f}")
print(f"95%置信区间:{scores.mean():.2%} ± {1.96*scores.std():.2%}")

# 方法2:详细交叉验证(返回多个指标)
scoring = {
    'accuracy': 'accuracy',
    'precision': 'precision',
    'recall': 'recall',
    'f1': 'f1'
}

cv_results = cross_validate(model, X, y, cv=5, scoring=scoring)

print("\n详细交叉验证结果:")
for metric_name in ['accuracy', 'precision', 'recall', 'f1']:
    scores = cv_results[f'test_{metric_name}']
    print(f"{metric_name.capitalize()}{scores.mean():.3f} ± {scores.std():.3f}")

# 可视化
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(10, 6))

metrics = ['Accuracy', 'Precision', 'Recall', 'F1']
means = [cv_results[f'test_{m.lower()}'].mean() for m in metrics]
stds = [cv_results[f'test_{m.lower()}'].std() for m in metrics]

x = np.arange(len(metrics))
ax.bar(x, means, yerr=stds, capsize=5, alpha=0.7, color='steelblue')
ax.set_xticks(x)
ax.set_xticklabels(metrics)
ax.set_ylabel('分数')
ax.set_title('5折交叉验证各指标表现')
ax.set_ylim([0, 1])

for i, (m, s) in enumerate(zip(means, stds)):
    ax.text(i, m + s + 0.02, f'{m:.3f}', ha='center')

plt.tight_layout()
plt.show()

七、常见问题与解决方案

问题1:训练集和测试集准确率差异很大

# 诊断代码
train_acc = model.score(X_train, y_train)
test_acc = model.score(X_test, y_test)

print(f"训练集准确率:{train_acc:.2%}")
print(f"测试集准确率:{test_acc:.2%}")
print(f"差异:{abs(train_acc - test_acc):.2%}")

if train_acc - test_acc > 0.15:
    print("\n诊断:过拟合")
    print("解决方案:")
    print("1. 增加训练数据")
    print("2. 简化模型(减少特征、降低复杂度)")
    print("3. 增加正则化")
    print("4. 使用交叉验证")
    
    # 示例:增加正则化
    from sklearn.linear_model import LogisticRegression
    model_regularized = LogisticRegression(C=0.1, random_state=42)
    model_regularized.fit(X_train, y_train)
    
    new_train_acc = model_regularized.score(X_train, y_train)
    new_test_acc = model_regularized.score(X_test, y_test)
    
    print(f"\n正则化后:")
    print(f"训练集准确率:{new_train_acc:.2%}")
    print(f"测试集准确率:{new_test_acc:.2%}")

问题2:数据泄露检查

# 错误示例:在切分前标准化
from sklearn.preprocessing import StandardScaler

# ❌ 错误做法
scaler = StandardScaler()
X_scaled_wrong = scaler.fit_transform(X)  # 用了全部数据的统计量
X_train, X_test = train_test_split(X_scaled_wrong, test_size=0.3)

# ✅ 正确做法
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # 只用训练集统计量
X_test_scaled = scaler.transform(X_test)        # 用训练集的统计量转换测试集

# 更好的做法:使用Pipeline
from sklearn.pipeline import Pipeline

pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('model', LogisticRegression())
])

# Pipeline自动处理,不会泄露
pipeline.fit(X_train, y_train)
test_score = pipeline.score(X_test, y_test)

问题3:保存和加载训练好的模型

import joblib

# 训练模型
model = LogisticRegression()
model.fit(X_train, y_train)

# 保存模型
joblib.dump(model, 'employee_churn_model.pkl')
print("✓ 模型已保存")

# 加载模型
loaded_model = joblib.load('employee_churn_model.pkl')

# 使用加载的模型预测
predictions = loaded_model.predict(X_test)
accuracy = loaded_model.score(X_test, y_test)
print(f"加载模型的准确率:{accuracy:.2%}")

八、总结:训练/测试流程速查表

核心4步骤

# 步骤1:准备数据
X = data[特征列]
y = data[标签列]

# 步骤2:切分数据
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)

# 步骤3:训练模型
model = LogisticRegression()
model.fit(X_train, y_train)

# 步骤4:评估模型
test_score = model.score(X_test, y_test)
print(f"测试集准确率:{test_score:.2%}")

关键原则

原则 说明
训练集专用 只用训练集训练模型
测试集保密 训练时完全不能看测试集
先切分后处理 先切分数据,再做标准化等处理
关注测试集 测试集表现才是真实能力
对比两者 训练集和测试集差异反映过拟合程度

常用参数

train_test_split(
    X, y,
    test_size=0.3,        # 测试集30%
    random_state=42,      # 随机种子
    stratify=y,           # 分层抽样
    shuffle=True          # 打乱数据(默认)
)

从数据准备到模型评估,从简单切分到交叉验证,从基础代码到完整案例,这份指南涵盖了实际应用中的所有关键步骤。记住核心原则:训练集用来学习,测试集用来考试,两者严格分离!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐