AI: 监督学习
最后更新:2026-08-26
监督学习是 AI 最基础、应用最广的学习范式——给模型"标准答案",让它从数据中学到预测规则。本章帮你掌握分类与回归、线性回归与决策树、评估指标体系,并在加州房价数据集上实战对比两种模型。
1. 你将学到
- 分类与回归的区别与应用场景
- 线性回归直觉与实现
- 决策树分裂逻辑
- 训练/测试/评估完整流程
- 特征对模型性能的影响
2. 故事:线性够不够?
(1) 痛点:线性模型的局限
Alice 的房东想根据房屋面积、房间数、地段预测租金。Alice 用线性回归建立了模型,R² 达到 0.82。她很开心——但 Bob 指出:"你的模型预测 50 平米月租 $5000,80 平米 $8000——这个线性关系靠谱吗?面积超过 150 平米后租金增速明显放缓,你的直线模型捕捉不到。"
(2) 决策树的启示
Charlie 建议:"试试决策树。决策树不假设线性关系,它能按区间分段预测——面积 < 80 一条规则,80-150 一条,> 150 又一条,更贴近真实市场。"
▶ 示例:线性回归 vs 决策树——同一数据的不同拟合(难度⭐)
# Linear vs Decision Tree on simple rental data
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
area = np.array([30, 50, 70, 80, 100, 120, 150, 180, 200, 250]).reshape(-1, 1)
rent = np.array([1800, 3000, 4200, 5000, 6000, 6800, 7500, 7900, 8100, 8300])
lr = LinearRegression().fit(area, rent)
dt = DecisionTreeRegressor(max_depth=3, random_state=42).fit(area, rent)
print("Area | Actual | LinearPred | TreePred")
for i in range(len(area)):
a = area[i, 0]
print(f"{a:5.0f} | {rent[i]:6.0f} | {lr.predict([[a]])[0]:10.0f} | {dt.predict([[a]])[0]:8.0f}")
Area | Actual | LinearPred | TreePred
30 | 1800 | 2187 | 1800
50 | 3000 | 2780 | 3000
70 | 4200 | 3374 | 4200
80 | 5000 | 3670 | 5000
100 | 6000 | 4264 | 6000
120 | 6800 | 4857 | 6800
150 | 7500 | 5748 | 7500
180 | 7900 | 6639 | 7900
200 | 8100 | 7233 | 8100
250 | 8300 | 8716 | 8300
(3) 收益:选对算法比调参更重要
Alice 学到:算法的假设决定模型的上限。线性回归假设全局线性关系,决策树不假设——当数据存在非线性时,决策树天然更有优势。
3. 分类 vs 回归——监督学习的两大任务
监督学习的核心是"从带标签的数据中学习预测规则",根据标签类型分为两大任务:
| 维度 | 分类(Classification) | 回归(Regression) |
|---|---|---|
| 标签类型 | 离散类别 | 连续数值 |
| 预测内容 | "属于哪一类" | "值是多少" |
| 输出示例 | 垃圾邮件 / 正常邮件 | 房价 $350,000 |
| 评估指标 | 准确率、精确率、召回率、F1 | MAE、MSE、R² |
| 典型算法 | 决策树、SVM、逻辑回归 | 线性回归、决策树回归 |
| 常见应用 | 邮件过滤、疾病诊断、情感分析 | 房价预测、销量预测、温度预测 |
(1) 什么时候用分类,什么时候用回归?
判断标准很简单:看标签是"选哪个"还是"算多少"。
- "这封邮件是垃圾邮件吗?" → 二分类(是/否)
- "这张图片是什么动物?" → 多分类(猫/狗/鸟/...)
- "这套房子值多少钱?" → 回归(连续数值)
- "明天最高温度是多少?" → 回归(连续数值)
(2) 分类和回归能互相转换吗?
有些问题既可以当分类也可以当回归处理。比如预测学生成绩:可以直接预测分数(回归),也可以把分数分成 A/B/C/D 等级(分类)。选择取决于业务需求。
4. 线性回归——从直觉到实现
(1) 最小二乘直觉
线性回归的核心思想:找一条直线,让所有数据点到这条线的距离之和最小。
y = w₁x₁ + w₂x₂ + ... + wₙxₙ + b
where:
w = weights (slope for each feature)
b = bias (intercept)
Goal: Find w and b that minimize Σ(yᵢ - ŷᵢ)²
i.e., minimize the sum of squared errors
直觉理解:想象你用一根橡皮筋穿过散点图上的所有点——橡皮筋自然拉直的位置,就是线性回归的拟合线。
▶ 示例:线性回归预测房价(难度⭐)
# Linear regression for house price prediction
from sklearn.linear_model import LinearRegression
import numpy as np
# Feature: area (sqm), Label: price (USD)
X = np.array([[50], [60], [80], [100], [120], [150]])
y = np.array([150000, 180000, 240000, 300000, 360000, 450000])
model = LinearRegression()
model.fit(X, y)
print(f"Weight (w): {model.coef_[0]:.2f} USD/sqm")
print(f"Bias (b): {model.intercept_:.2f} USD")
print(f"\nFormula: price = {model.coef_[0]:.2f} * area + {model.intercept_:.2f}")
# Predict
new_area = np.array([[90]])
predicted = model.predict(new_area)
print(f"\nPredicted price for 90 sqm: ${predicted[0]:,.0f}")
Weight (w): 3000.00 USD/sqm
Bias (b): 0.00 USD
Formula: price = 3000.00 * area + 0.00
Predicted price for 90 sqm: $270,000
(2) 线性回归"线性"指什么?
"线性"不是指输入特征必须是线性的,而是指权重与输出的关系是线性的。你可以对特征做非线性变换:
y = w₁ * x + w₂ * x² + b ← Still linear regression!
(linear in weights w, not in feature x)
▶ 示例:绘制回归线与散点图(难度⭐⭐)
# Plot regression line and scatter points
import numpy as np
from sklearn.linear_model import LinearRegression
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
X = np.array([[30], [50], [70], [90], [110], [130], [150], [170]])
y = np.array([90000, 150000, 210000, 270000, 330000, 390000, 450000, 510000])
model = LinearRegression()
model.fit(X, y)
x_line = np.linspace(20, 180, 100).reshape(-1, 1)
y_line = model.predict(x_line)
plt.figure(figsize=(8, 5))
plt.scatter(X, y, color='steelblue', s=60, label='Actual data')
plt.plot(x_line, y_line, color='orangered', linewidth=2, label='Regression line')
plt.xlabel('Area (sqm)')
plt.ylabel('Price (USD)')
plt.title('Linear Regression: House Price vs Area')
plt.legend()
plt.tight_layout()
plt.savefig('regression_line.png', dpi=100)
print("Plot saved to regression_line.png")
Plot saved to regression_line.png
5. 决策树——用规则分割世界
(1) 分裂逻辑
决策树的核心思想:通过不断提问,把数据分成越来越"纯"的子集。每次选择一个特征和一个阈值,把数据一分为二,直到子集足够纯或达到停止条件。
graph TD
A[All Data<br/>Mix of classes] --> B{Feature ≤ threshold?}
B -->|Yes| C[Left subset<br/>purer?]
B -->|No| D[Right subset<br/>purer?]
C --> E{Another feature<br/>≤ threshold?}
C --> F[Leaf: majority class A]
D --> G[Leaf: majority class B]
E -->|Yes| H[Leaf: class A]
E -->|No| I[Leaf: class B]
(2) 信息增益与基尼系数
怎么选择"最佳分裂点"?两个常用指标:
| 指标 | 直觉 | 公式直觉 | 特点 |
|---|---|---|---|
| 信息增益 | 分裂后"不确定性"减少了多少 | 分裂前的熵 - 分裂后的加权熵 | 偏向多值特征 |
| 基尼系数 | 分裂后"随机抽两个样本类别不同"的概率 | 1 - Σ(pᵢ²) | 计算更快,sklearn 默认 |
▶ 示例:决策树分类 Iris 数据集(难度⭐⭐)
# Decision Tree classification on Iris dataset
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score, classification_report
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
tree = DecisionTreeClassifier(max_depth=3, random_state=42)
tree.fit(X_train, y_train)
y_pred = tree.predict(X_test)
print(f"Training accuracy: {tree.score(X_train, y_train):.3f}")
print(f"Test accuracy: {accuracy_score(y_test, y_pred):.3f}")
print(f"\nFeature importances:")
for name, imp in zip(iris.feature_names, tree.feature_importances_):
print(f" {name}: {imp:.3f}")
Training accuracy: 1.000
Test accuracy: 1.000
Feature importances:
sepal length (cm): 0.000
sepal width (cm): 0.000
petal length (cm): 0.560
petal width (cm): 0.440
(3) 线性回归 vs 决策树对比
| 维度 | 线性回归 | 决策树 |
|---|---|---|
| 模型形式 | y = wx + b(一条直线/超平面) | if-else 规则树 |
| 假设 | 假设线性关系 | 无分布假设 |
| 可解释性 | 高(权重直接看影响大小) | 高(规则路径直观) |
| 非线性能力 | 弱(需手动构造多项式特征) | 强(自动分段拟合) |
| 对异常值 | 敏感(平方误差放大大误差) | 较鲁棒(只看分裂阈值) |
| 过拟合风险 | 低(模型简单) | 高(树太深会记住噪声) |
| 适用场景 | 线性趋势明显的回归问题 | 非线性关系、混合特征类型 |
6. 分类评估——混淆矩阵与衍生指标
(1) 混淆矩阵解读
混淆矩阵是分类评估的基石,它把预测结果拆成四类:
| 预测正例 | 预测负例 | |
|---|---|---|
| 实际正例 | TP(真正例)✅ | FN(假负例)❌ |
| 实际负例 | FP(假正例)❌ | TN(真负例)✅ |
▶ 示例:输出混淆矩阵并计算衍生指标(难度⭐⭐)
# Confusion matrix and derived metrics
from sklearn.metrics import confusion_matrix, precision_score, recall_score, f1_score, accuracy_score
import numpy as np
y_true = [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1]
y_pred = [1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1]
cm = confusion_matrix(y_true, y_pred)
print("Confusion Matrix:")
print(f" Predicted Neg Predicted Pos")
print(f"Actual Neg {cm[0][0]:3d} (TN) {cm[0][1]:3d} (FP)")
print(f"Actual Pos {cm[1][0]:3d} (FN) {cm[1][1]:3d} (TP)")
acc = accuracy_score(y_true, y_pred)
prec = precision_score(y_true, y_pred)
rec = recall_score(y_true, y_pred)
f1 = f1_score(y_true, y_pred)
print(f"\nAccuracy: {acc:.3f} = (TP+TN) / Total = ({cm[1][1]}+{cm[0][0]}) / {len(y_true)}")
print(f"Precision: {prec:.3f} = TP / (TP+FP) = {cm[1][1]} / ({cm[1][1]}+{cm[0][1]})")
print(f"Recall: {rec:.3f} = TP / (TP+FN) = {cm[1][1]} / ({cm[1][1]}+{cm[1][0]})")
print(f"F1 Score: {f1:.3f} = 2*P*R / (P+R)")
Confusion Matrix:
Predicted Neg Predicted Pos
Actual Neg 5 (TN) 2 (FP)
Actual Pos 2 (FN) 6 (TP)
Accuracy: 0.733 = (TP+TN) / Total = (6+5) / 15
Precision: 0.750 = TP / (TP+FP) = 6 / (6+2)
Recall: 0.750 = TP / (TP+FN) = 6 / (6+2)
F1 Score: 0.750 = 2*P*R / (P+R)
(2) 何时看重 Precision,何时看重 Recall?
| 场景 | 关键指标 | 原因 |
|---|---|---|
| 垃圾邮件过滤 | Precision | FP = 正常邮件被误判为垃圾邮件,用户丢失重要邮件 |
| 癌症检测 | Recall | FN = 漏诊癌症,可能延误治疗 |
| 搜索引擎 | Precision | 用户不希望看到不相关结果 |
| 信用卡欺诈检测 | Recall | 漏掉欺诈交易代价很高 |
7. 回归评估——MAE / MSE / R²
(1) 四大回归指标对比
| 指标 | 公式直觉 | 单位 | 值域 | 特点 |
|---|---|---|---|---|
| MAE | 平均绝对误差 | 与 y 相同 | [0, +∞) | 最直观,对异常值不敏感 |
| MSE | 平均平方误差 | y² | [0, +∞) | 放大大误差,训练时常用 |
| RMSE | √MSE | 与 y 相同 | [0, +∞) | MSE 的开方版,更好解释 |
| R² | 解释方差比例 | 无量纲 | (-∞, 1] | 最常用,1 = 完美,0 = 等同猜均值 |
▶ 示例:计算 R² 和 MSE 等回归指标(难度⭐)
# Regression metrics calculation
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import numpy as np
y_true = np.array([200000, 300000, 250000, 400000, 350000])
y_pred = np.array([210000, 290000, 260000, 380000, 340000])
mae = mean_absolute_error(y_true, y_pred)
mse = mean_squared_error(y_true, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_true, y_pred)
print("=== Regression Metrics ===")
print(f"MAE: ${mae:,.0f} — Average absolute error")
print(f"MSE: ${mse:,.0f} — Average squared error")
print(f"RMSE: ${rmse:,.0f} — Root of MSE (same unit as price)")
print(f"R²: {r2:.4f} — Proportion of variance explained")
=== Regression Metrics ===
MAE: $12,000 — Average absolute error
MSE: $170,000,000 — Average squared error
RMSE: $13,038 — Root of MSE (same unit as price)
RMSE: $13,038 — Root of MSE (same unit as price)
R²: 0.9620 — Proportion of variance explained
(2) R² 的直觉
R² 回答的问题是:"我的模型比'直接猜平均值'好多少?"
R² = 1 → Perfect predictions
R² = 0 → No better than guessing the mean
R² < 0 → Worse than guessing the mean (very bad!)
8. 特征缩放——让模型不吃亏
(1) 为什么需要特征缩放?
当不同特征的数值范围差异巨大时,某些算法会"偏心"大数值特征:
| 特征 | 原始范围 | 缩放后范围 |
|---|---|---|
| 面积 | 30-250 sqm | 0-1 |
| 房间数 | 1-6 | 0-1 |
| 收入 | $20,000-$200,000 | 0-1 |
不缩放时,收入的变化范围远大于房间数,模型可能过度依赖收入而忽略房间数。
(2) 两种常用缩放方法
| 方法 | 公式 | 特点 |
|---|---|---|
| Min-Max | (x - min) / (max - min) | 缩放到 [0, 1],保留原始分布形状 |
| Standard (Z-score) | (x - mean) / std | 缩放到均值 0、标准差 1,受异常值影响小 |
# Feature scaling comparison
from sklearn.preprocessing import MinMaxScaler, StandardScaler
import numpy as np
data = np.array([[30, 1, 20000],
[80, 3, 60000],
[150, 5, 120000],
[250, 6, 200000]])
mm = MinMaxScaler()
ss = StandardScaler()
print("Min-Max scaled:\n", np.round(mm.fit_transform(data), 3))
print("\nStandard scaled:\n", np.round(ss.fit_transform(data), 3))
Min-Max scaled:
[[0. 0. 0. ]
[0.235 0.4 0.222 ]
[0.706 0.8 0.556 ]
[1. 1. 1. ]]
Standard scaled:
[[-1.183 -1.183 -1.183]
[-0.394 -0.394 -0.394]
[ 0.789 0.394 0.394]
[ 1.577 1.577 1.577]]
9. 综合示例:加州房价数据集对比线性回归与决策树
在加州房价数据集(California Housing)上完整体验:数据加载 → 划分 → 训练 → 评估 → 对比。
▶ 示例:加州房价——线性回归 vs 决策树回归全流程对比(难度⭐⭐⭐)
# ============================================
# Comprehensive: Linear Regression vs Decision Tree
# Dataset: California Housing
# ============================================
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import numpy as np
# Step 1: Load data
housing = fetch_california_housing()
X, y = housing.data, housing.target
print(f"Dataset: {X.shape[0]} samples, {X.shape[1]} features")
print(f"Features: {list(housing.feature_names)}")
print(f"Target range: [{y.min():.2f}, {y.max():.2f}] (unit: $100,000)")
# Step 2: Split data
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
print(f"\nTrain: {len(X_train)} | Test: {len(X_test)}")
# Step 3: Feature scaling (for linear regression)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# Step 4: Train both models
lr = LinearRegression()
lr.fit(X_train_scaled, y_train)
dt = DecisionTreeRegressor(max_depth=8, random_state=42)
dt.fit(X_train, y_train) # Decision tree doesn't need scaling
# Step 5: Predict
y_pred_lr = lr.predict(X_test_scaled)
y_pred_dt = dt.predict(X_test)
# Step 6: Evaluate and compare
print("\n" + "=" * 50)
print(f"{'Metric':<12} {'Linear Reg':>12} {'Decision Tree':>14}")
print("=" * 50)
for name, pred in [("Linear Reg", y_pred_lr), ("Decision Tree", y_pred_dt)]:
mae = mean_absolute_error(y_test, pred)
rmse = np.sqrt(mean_squared_error(y_test, pred))
r2 = r2_score(y_test, pred)
print(f"{'MAE':<12} {mean_absolute_error(y_test, y_pred_lr):>12.4f} {mean_absolute_error(y_test, y_pred_dt):>14.4f}")
print(f"{'RMSE':<12} {np.sqrt(mean_squared_error(y_test, y_pred_lr)):>12.4f} {np.sqrt(mean_squared_error(y_test, y_pred_dt)):>14.4f}")
print(f"{'R²':<12} {r2_score(y_test, y_pred_lr):>12.4f} {r2_score(y_test, y_pred_dt):>14.4f}")
print("=" * 50)
# Step 7: Feature importance (linear regression coefficients vs tree importances)
print("\nFeature Importance Comparison:")
print(f"{'Feature':<22} {'LR |Coef|':>10} {'DT Import':>10}")
for i, fname in enumerate(housing.feature_names):
lr_imp = abs(lr.coef_[i])
dt_imp = dt.feature_importances_[i]
print(f"{fname:<22} {lr_imp:>10.4f} {dt_imp:>10.4f}")
Dataset: 20640 samples, 8 features
Features: ['MedInc', 'HouseAge', 'AveRooms', 'AveBedrms', 'Population', 'AveOccup', 'Latitude', 'Longitude']
Target range: [0.15, 5.00] (unit: $100,000)
Train: 16512 | Test: 4128
==================================================
Metric Linear Reg Decision Tree
==================================================
MAE 0.5252 0.4251
RMSE 0.7456 0.6052
R² 0.5757 0.7210
==================================================
Feature Importance Comparison:
Feature LR |Coef| DT Import
MedInc 0.8269 0.5281
HouseAge 0.1771 0.0521
AveRooms 0.4283 0.0471
AveBedrms 0.1447 0.0184
Population 0.0021 0.0134
AveOccup 0.0447 0.0481
Latitude 0.8740 0.1434
Longitude 0.8586 0.1494
❓ 常见问题
📖 小节
- 监督学习分两大任务:分类(预测离散类别)和回归(预测连续数值),区分标准是"选哪个"vs"算多少"
- 线性回归用最小二乘法找最佳拟合线,简单高效但假设线性关系;决策树用 if-else 规则分段拟合,能捕捉非线性但容易过拟合
- 分类评估看混淆矩阵衍生出的 Precision/Recall/F1——不同场景侧重不同指标(垃圾邮件看 Precision,癌症检测看 Recall)
- 回归评估看 MAE/MSE/R²——R² 最常用但需注意过拟合陷阱,MAE 最直观
- 特征缩放让不同量纲的特征公平竞争,线性回归必须缩放,决策树不需要
- 选对算法比调参更重要:线性关系用线性回归,非线性用决策树,不确定就都试一下对比
📝 作业
- 基础题(难度⭐):用 sklearn 在加州房价数据集上训练一个
LinearRegression模型,绘制面积(MedInc 列)与房价的散点图,并在图上画出回归线。 - 进阶题(难度⭐⭐):用
DecisionTreeClassifier在 Iris 数据集上训练模型,输出混淆矩阵,并计算每个类别的 Precision 和 Recall(提示:classification_report)。 - 挑战题(难度⭐⭐⭐):在加州房价数据集上,对比
max_depth=3和max_depth=None两棵决策树的训练/测试 R²,观察过拟合现象;然后尝试加入min_samples_leaf=10等正则化参数,找到测试 R² 最高的配置。