机器学习大师课 第 4 课:分类问题入门 —— 逻辑回归(垃圾邮件分类实战)
课程承诺:1 个核心概念(逻辑回归)+1 个核心思想(概率预测与决策边界)+1 段完整分类项目代码。学完你能解决所有二分类问题,彻底搞懂分类任务的评估体系,亲手写出第一个实用的 AI 分类器。
本节课目标:从 "预测连续值"(回归)升级到 "预测类别"(分类),用逻辑回归实现一个能自动识别垃圾邮件的 AI,理解为什么分类问题不能用线性回归,以及如何正确评估分类模型的好坏。
🧩 先回答上一课的思考题
-
除了正则化,解决过拟合的方法还有:
- 增加训练数据量(最根本的方法)
- 降低模型复杂度(比如把 9 次多项式改成 3 次)
- 早停(训练过程中,当测试集误差开始上升时就停止训练)
- 集成学习(多个弱模型组合成一个强模型,比如随机森林)
-
L1 正则化(Lasso)和 L2 正则化(Ridge)的区别:
- L2:让所有参数都变小,但不会变成 0
- L1:会让很多不重要的参数直接变成 0,自带特征选择功能
🧠 第一个核心概念:逻辑回归(Logistic Regression)
先澄清一个最大的误区
逻辑回归不是回归算法,是分类算法!
- 回归:预测连续值(房价、分数、温度)
- 分类:预测离散类别(是 / 不是垃圾邮件、是 / 不是癌症、猫 / 狗)
为什么不能用线性回归做分类?
我们用一个最简单的例子说明:判断是不是垃圾邮件
- 标签:0 = 正常邮件,1 = 垃圾邮件
- 特征:邮件中包含 "免费" 这个词的次数
如果用线性回归,我们会得到一条直线:y = w*x + b
- 当 x=0 时,y≈0.1 → 预测正常邮件
- 当 x=5 时,y≈0.9 → 预测垃圾邮件
看起来没问题,但如果有一个极端的垃圾邮件,包含了 100 个 "免费":
- 线性回归会预测 y≈10 → 远远大于 1,完全无法解释为概率
核心问题:线性回归的输出是 (-∞, +∞),而我们需要的是 [0,1] 之间的概率。
💡 第一个核心思想:Sigmoid 函数与概率预测
神奇的 Sigmoid 函数
我们需要一个 "转换器",把任意实数映射到 (0,1) 区间,这个转换器就是Sigmoid 函数:σ(z) = 1 / (1 + e^(-z))

它的图像是一个完美的 S 形曲线:
- 当 z=0 时,σ(z)=0.5
- 当 z→+∞时,σ(z)→1
- 当 z→-∞时,σ(z)→0
逻辑回归的完整公式
- 先用线性回归计算出一个分数 z:
z = w1*x1 + w2*x2 + ... + b - 把 z 输入 Sigmoid 函数,得到概率 p:
p = σ(z) - 这个 p 就是 "样本属于正类的概率"
决策边界
有了概率之后,我们就可以做预测了:
- 如果 p > 0.5 → 预测为正类(1,垃圾邮件)
- 如果 p < 0.5 → 预测为负类(0,正常邮件)
这个 0.5 就是我们的决策阈值,它对应的那条线就叫决策边界。
重要提示:0.5 不是固定的!我们可以根据业务需求调整阈值,这是分类问题最灵活也最有价值的地方。
💻 代码实战:亲手写一个垃圾邮件分类器
我们将训练一个 AI,根据邮件中是否包含 5 个关键词,自动判断是不是垃圾邮件。
完整代码(复制粘贴就能运行)
python
运行
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
# 1. 准备数据集
# 特征:[包含"免费", 包含"中奖", 包含"点击", 包含"领取", 包含"紧急"]
# 1=包含该词,0=不包含
X = np.array([
[0,0,0,0,0], [0,0,0,0,1], [0,0,0,1,0], [0,0,1,0,0], [0,1,0,0,0],
[1,0,0,0,0], [1,1,0,0,0], [1,0,1,0,0], [1,0,0,1,0], [1,1,1,0,0],
[1,1,0,1,0], [1,1,1,1,0], [1,1,1,1,1], [0,1,1,1,1], [0,0,1,1,1],
[0,0,0,1,1], [0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0]
])
# 标签:0=正常邮件,1=垃圾邮件
y = np.array([0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0])
# 2. 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 3. 创建并训练逻辑回归模型
model = LogisticRegression()
model.fit(X_train, y_train)
# 4. 查看每个关键词的权重
print("各关键词对垃圾邮件的影响权重:")
keywords = ["免费", "中奖", "点击", "领取", "紧急"]
for word, coef in zip(keywords, model.coef_[0]):
print(f"{word}: {coef:.2f}")
print(f"截距:{model.intercept_[0]:.2f}\n")
# 5. 模型预测
y_pred = model.predict(X_test)
y_pred_proba = model.predict_proba(X_test)[:, 1] # 得到是垃圾邮件的概率
# 6. 模型评估
print("="*50)
print(f"模型准确率:{accuracy_score(y_test, y_pred):.2f}")
print("\n混淆矩阵:")
print(confusion_matrix(y_test, y_pred))
print("\n详细分类报告:")
print(classification_report(y_test, y_pred))
# 7. 测试新邮件
print("="*50)
test_emails = [
[1,1,0,0,0], # "免费中奖,快来参与!"
[0,0,0,0,0], # "明天下午开会"
[1,0,0,1,1], # "免费领取紧急通知"
[0,1,0,0,0] # "恭喜你中奖了"
]
for i, email in enumerate(test_emails):
prob = model.predict_proba([email])[0][1]
result = "垃圾邮件" if prob > 0.5 else "正常邮件"
print(f"测试邮件{i+1}:是垃圾邮件的概率={prob:.2f} → {result}")
🔍 逐行解读核心知识点
1. 权重的意义
你会看到类似这样的输出:
plaintext
各关键词对垃圾邮件的影响权重:
免费: 1.23
中奖: 1.15
点击: 0.98
领取: 0.87
紧急: 0.72
截距: -2.34
- 权重越大,说明这个词越能说明邮件是垃圾邮件
- "免费" 和 "中奖" 是垃圾邮件最明显的特征
- 截距为负,说明默认情况下,一封邮件更可能是正常邮件
2. 分类问题最重要的评估指标
准确率(Accuracy):预测正确的样本占总样本的比例
- 优点:简单直观
- 缺点:在样本不平衡的情况下完全失效(比如 1000 封邮件里只有 1 封垃圾邮件,全预测为正常邮件准确率也有 99.9%)
混淆矩阵:
表格
| 预测为正常 | 预测为垃圾 | |
|---|---|---|
| 实际正常 | 真阴性 (TN) | 假阳性 (FP) |
| 实际垃圾 | 假阴性 (FN) | 真阳性 (TP) |
- 精确率(Precision):预测为垃圾邮件的邮件中,真正是垃圾邮件的比例
- 意义:不误删正常邮件
- 召回率(Recall):所有真正的垃圾邮件中,被正确识别出来的比例
- 意义:不漏掉垃圾邮件
黄金法则:精确率和召回率是此消彼长的关系,你永远不可能同时提高两者。
- 垃圾邮件过滤:优先保证精确率(宁愿漏过垃圾邮件,也不要误删正常邮件)
- 癌症诊断:优先保证召回率(宁愿误诊正常人,也不要漏掉癌症患者)
3. 调整决策阈值
这是本节课最有价值的技巧!我们可以通过修改阈值来平衡精确率和召回率:
python
运行
# 把阈值从0.5改成0.3,提高召回率(不漏掉垃圾邮件)
y_pred_low_threshold = (y_pred_proba > 0.3).astype(int)
print("阈值=0.3时的分类报告:")
print(classification_report(y_test, y_pred_low_threshold))
# 把阈值改成0.8,提高精确率(不误删正常邮件)
y_pred_high_threshold = (y_pred_proba > 0.8).astype(int)
print("阈值=0.8时的分类报告:")
print(classification_report(y_test, y_pred_high_threshold))
运行后你会清楚地看到:
- 阈值降低 → 召回率上升,精确率下降
- 阈值升高 → 精确率上升,召回率下降
✨ 神奇的实验:可视化决策边界
我们用两个特征来可视化逻辑回归的决策边界,直观感受它是怎么划分两类数据的。
python
运行
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
# --- 1. 解决中文显示问题 (关键步骤) ---
# 设置字体为黑体 (SimHei) 或者其他支持中文的字体
# 如果你的系统没有 SimHei,可以尝试 'Microsoft YaHei'
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False # 解决负号 '-' 显示为方块的问题
# --- 2. 构造更丰富的数据 ---
# 之前的错误是因为数据太少。这里我们手动构造一些更典型的点:
# 正常邮件 (0): 不含"免费"也不含"中奖" -> (0,0)
# 垃圾邮件 (1): 含"免费"或者含"中奖" -> (1,0), (0,1), (1,1)
X_data = np.array([
[0, 0], [0, 0], [0, 0], # 正常邮件 (0,0)
[1, 0], [1, 0], # 含"免费" (1,0)
[0, 1], [0, 1], # 含"中奖" (0,1)
[1, 1] # 既含"免费"又含"中奖" (1,1)
])
y_data = np.array([0, 0, 0, 1, 1, 1, 1, 1])
# --- 3. 训练模型 ---
model = LogisticRegression()
model.fit(X_data, y_data)
# --- 4. 绘制决策边界 ---
# 生成网格点
h = 0.02 # 网格步长
x_min, x_max = X_data[:, 0].min() - 0.5, X_data[:, 0].max() + 0.5
y_min, y_max = X_data[:, 1].min() - 0.5, X_data[:, 1].max() + 0.5
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
# 预测网格点的类别
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# 画图
plt.figure(figsize=(8, 6))
# 绘制背景颜色区域 (决策边界)
# 使用 cmap=plt.cm.RdYlBu 会更清晰:红色代表垃圾邮件,蓝色代表正常邮件
plt.pcolormesh(xx, yy, Z, cmap=plt.cm.RdYlBu, shading='auto', alpha=0.3)
# 绘制散点
# c=y_data 会根据标签自动上色
scatter = plt.scatter(X_data[:, 0], X_data[:, 1], c=y_data, edgecolors='k', cmap=plt.cm.RdYlBu, s=100)
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.xlabel('是否包含 "免费" (1=是, 0=否)')
plt.ylabel('是否包含 "中奖" (1=是, 0=否)')
plt.title('逻辑回归:垃圾邮件分类决策边界')
plt.grid(True)
# 添加图例
# 手动创建图例句柄,因为 scatter 的图例有时候需要手动处理
legend_elements = [
plt.Line2D([0], [0], marker='o', color='w', label='正常邮件 (0)', markerfacecolor='#3B528B', markersize=10),
plt.Line2D([0], [0], marker='o', color='w', label='垃圾邮件 (1)', markerfacecolor='#B12834', markersize=10)
]
plt.legend(handles=legend_elements, loc='upper right')
plt.show()

你会看到一条直线把平面分成了两部分,直线左边是正常邮件,右边是垃圾邮件。这就是逻辑回归的线性决策边界。
📝 本节课总结
- 核心概念:逻辑回归是二分类算法,不是回归算法,输出是样本属于正类的概率
- 核心思想:用 Sigmoid 函数把线性回归的输出映射到 (0,1) 区间,通过决策阈值进行分类
- 核心评估:分类问题不能只用准确率,要结合精确率和召回率,根据业务需求调整决策阈值
- 你已经做到了:写出了第一个实用的 AI 分类器,能根据业务需求优化模型性能
🎯 课后作业(必须做)
- 运行上面的所有代码,观察不同阈值下精确率和召回率的变化
- 自己添加几个新的关键词和邮件样本,重新训练模型,看看效果
- 思考:如果有三个类别(正常邮件、垃圾邮件、广告邮件),逻辑回归怎么处理?
- 尝试用逻辑回归解决鸢尾花分类问题(scikit-learn 自带的经典数据集)
更多推荐
所有评论(0)