逻辑回归到底在干嘛?用 “判断今天要不要带伞” 举例

你每天出门前会想:

“今天会下雨吗?” → 这是一个二分类问题(带伞 / 不带伞)。

逻辑回归就是帮你做这个决定的 “小助手”,它的思考过程是:

  1. 收集线索:比如看天气预报的 “湿度”“云层厚度”(相当于输入特征 x)。
  2. 算一个 “下雨指数”:把线索加权相加(比如湿度 ×0.6 + 云层 ×0.4 + 0.1)。
  3. 把指数转成概率:比如 “下雨指数” 越高,越可能下雨(用 sigmoid 函数转成 0-1 的概率)。
  4. 做决定:如果概率≥50%,就判断 “会下雨”,建议带伞。

核心步骤拆解:像拼积木一样简单

1. 输入特征(x)

比如判断邮件是否为垃圾邮件,用 2 个线索:

  • x₁:邮件里有没有 “免费” 这个词(出现次数)
  • x₂:邮件里有没有 “优惠” 这个词(出现次数)
2. 算一个 “垃圾指数”(z)

z = w₁×x₁ + w₂×x₂ + b

  • w₁和 w₂:是 “权重”,比如 “免费” 这个词更重要,w₁就大一点。
  • b:是 “基础分”,比如默认有 10% 概率是垃圾邮件。

例子
如果一封邮件里 “免费” 出现 3 次,“优惠” 出现 2 次,
假设 w₁=0.5,w₂=0.5,b=0.1,
则 z = 0.5×3 + 0.5×2 + 0.1 = 2.6

3. 把指数转成概率(p)

用 sigmoid 函数,公式是:p = 1/(1 + e^(-z))

  • 不管 z 多大,p 都会变成 0 到 1 之间的数,代表 “是垃圾邮件的概率”。
  • 上面的例子中 z=2.6,算出来 p≈0.93(93% 概率是垃圾邮件)。

直观理解 sigmoid

  • z=0 → p=0.5(50% 概率)
  • z 越大 → p 越接近 1(比如 z=10 → p≈1)
  • z 越小 → p 越接近 0(比如 z=-10 → p≈0)
4. 定一个 “门槛” 做分类

一般用 0.5 作为门槛:

  • p≥0.5 → 预测为 “垃圾邮件”(类别 1)
  • p<0.5 → 预测为 “正常邮件”(类别 0)

⚖️ 损失函数:用 “考试打分” 理解 BCE Loss

假设老师给你出了 10 道判断题(答案只有对 / 错),你每道题猜一个概率(比如第 1 题你猜 70% 是对的)。
BCE Loss 就是老师给你打分的规则:

  • 如果正确答案是 “对”(1)
    • 你猜 70%(p=0.7)→ 得分高(损失小)
    • 你猜 30%(p=0.3)→ 得分低(损失大)
  • 如果正确答案是 “错”(0)
    • 你猜 30%(p=0.3)→ 得分高(损失小)
    • 你猜 70%(p=0.7)→ 得分低(损失大)

公式白话版
损失 = - [真实答案 ×log (你猜的概率) + (1 - 真实答案)×log (1 - 你猜的概率)]

  • 简单说:你猜的概率越接近真实答案,损失越小。

代码实战:用逻辑回归判断 “是不是垃圾邮件”

import torch
import matplotlib.pyplot as plt
import torch.nn as nn
import numpy as np
# 1. 生成数据(保持不变)
x = torch.randn(100, 2) * 3
y_true = ((x[:, 0] + x[:, 1] > 0) + 0.1 * torch.randn(100)).clamp(0, 1)
# 修正:调整y_true形状为[100, 1],与模型输出匹配
y_true = y_true.view(-1, 1)  # 将一维张量转为二维张量
# 2. 定义模型(保持不变)
class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = torch.nn.Linear(2, 1)
    def forward(self, x):
        z = self.linear(x)
        p = torch.sigmoid(z)
        return p
# 3. 训练模型(其余代码保持不变)
model = Model()
criterion = torch.nn.BCELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
losses = []
for i in range(500):
    y_pred = model(x)
    loss = criterion(y_pred, y_true)
    losses.append(loss.item())
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    if (i + 1) % 100 == 0:
        print(f'第{i + 1}次猜,扣分:{loss.item():.4f}')
# 4. 评估模型(保持不变)
y_pred_guess = (y_pred >= 0.5).float()
accuracy = (y_pred_guess == y_true).float().mean()
print(f'\n模型猜的准确率:{accuracy.item() * 100:.2f}%')
# 5. 可视化(保持不变)
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.scatter(x[y_true.view(-1) == 0, 0], x[y_true.view(-1) == 0, 1], c='blue', label='正常邮件')
plt.scatter(x[y_true.view(-1) == 1, 0], x[y_true.view(-1) == 1, 1], c='red', label='垃圾邮件')
x1_min, x1_max = x[:, 0].min() - 1, x[:, 0].max() + 1
x2_min, x2_max = x[:, 1].min() - 1, x[:, 1].max() + 1
xx1, xx2 = np.meshgrid(np.linspace(x1_min, x1_max, 100), np.linspace(x2_min, x2_max, 100))
grid = torch.tensor(np.c_[xx1.ravel(), xx2.ravel()], dtype=torch.float32)
with torch.no_grad():
    probs = model(grid).reshape(xx1.shape)
plt.contour(xx1, xx2, probs, levels=[0.5], linewidths=2, colors='black')
plt.xlabel('"免费"出现次数')
plt.ylabel('"优惠"出现次数')
plt.title('模型怎么判断垃圾邮件')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(range(500), losses)
plt.xlabel('猜的次数')
plt.ylabel('扣分多少')
plt.title('模型越学越准啦!')
plt.show()

 代码拆解

一、准备工具(导入库)

import torch
import matplotlib.pyplot as plt
import torch.nn as nn
import numpy as np
  • 翻译
    我们要开一家 “垃圾邮件识别公司”,先准备工具:
    • torch:超级计算机,能自动帮我们算复杂的数学题
    • matplotlib.pyplot:画画工具,用来画报表给老板看
    • torch.nn:组装机器人的零件库(比如齿轮、电线)
    • numpy:电子表格软件,能快速处理大量数据

二、生成假数据(模拟邮件特征)

x = torch.randn(100, 2) * 3
  • 翻译
    我们要训练机器人识别垃圾邮件,先准备 100 封 “假邮件”:
    • 每封邮件有 2 个特征:
      • 特征 1:“免费” 这个词出现的次数(比如 0 次、3 次)
      • 特征 2:“优惠” 这个词出现的次数(比如 1 次、5 次)
    • torch.randn(100, 2):随机生成 100 行 2 列的数字(可能是负数,但先不管)
    • * 3:把数字放大 3 倍,让它们更像真实的次数(比如 - 3 到 3 之间)
y_true = ((x[:, 0] + x[:, 1] > 0) + 0.1 * torch.randn(100)).clamp(0, 1)

  • 翻译
    给每封邮件打 “真实标签”(是不是垃圾邮件):
    • x[:, 0] + x[:, 1] > 0:如果 “免费” 和 “优惠” 的次数加起来 > 0,就标记为 1(垃圾邮件),否则标记为 0(正常邮件)
    • + 0.1 * torch.randn(100):加一点随机小错误(模拟现实中偶尔判断错)
    • clamp(0, 1):确保标签在 0 到 1 之间(不能是负数或大于 1)
y_true = y_true.view(-1, 1)  # 修正形状
  • 翻译
    把标签的格式调整一下,从 “一行数字” 变成 “一列数字”,这样机器人更容易看懂。
    (就像把表格从横排变成竖排,方便阅读)

三、搭建模型(造一个判断垃圾邮件的机器人)

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = torch.nn.Linear(2, 1)
  • 翻译
    我们要造一个机器人,它能根据 “免费” 和 “优惠” 的次数判断邮件是不是垃圾:
    • class Model:定义机器人的设计图
    • self.linear = torch.nn.Linear(2, 1)
      • 机器人有一个 “大脑”(线性层)
      • 这个大脑接收 2 个输入(“免费” 和 “优惠” 的次数)
      • 输出 1 个结果(0~1 之间的概率,表示是垃圾邮件的可能性)

    def forward(self, x):
        z = self.linear(x)
        p = torch.sigmoid(z)
        return p

  • 翻译
    机器人的 “工作流程”:
    1. z = self.linear(x):大脑计算z = 权重1 * “免费”次数 + 权重2 * “优惠”次数 + 偏移量
      (权重和偏移量是机器人自己学的,就像人通过经验总结规律)
    2. p = torch.sigmoid(z):把 z 的值压缩到 0~1 之间,得到概率 p
      • 如果 p 接近 1,说明很可能是垃圾邮件
      • 如果 p 接近 0,说明很可能是正常邮件

model = Model()  # 创建机器人实例
  • 翻译
    根据设计图,造一个真正的机器人!现在它已经可以工作了,只是还没 “训练”(权重是随机的)。

四、训练模型(教机器人学会判断)

criterion = torch.nn.BCELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

  • 翻译
    训练机器人需要 2 个工具:
    • criterion = torch.nn.BCELoss()
      “打分器”,用来评价机器人判断得有多准。分数越低,说明判断越准确。
    • optimizer = torch.optim.SGD(...)
      “教练”,根据打分器的结果,教机器人调整自己的 “大脑”(权重和偏移量)。
      • lr=0.01:学习速度,不能太快也不能太慢(就像教小孩,太快学不会,太慢浪费时间)
losses = []  # 记录每次训练的“扣分”
for i in range(500):  # 训练500次
    y_pred = model(x)  # 让机器人判断所有100封邮件
    loss = criterion(y_pred, y_true)  # 用打分器评价机器人的判断
    losses.append(loss.item())  # 记录这次的分数
  • 翻译
    训练机器人的第一步:
    1. 让机器人判断 100 封邮件,得到预测结果y_pred
    2. 用打分器对比预测结果和真实标签,得到一个分数(损失值)
    3. 把这个分数记录下来,看看机器人有没有进步
    optimizer.zero_grad()  # 清空上次的“训练记录”
    loss.backward()  # 计算“怎么调整大脑”能让下次判断更准
    optimizer.step()  # 教练指导机器人调整大脑
  • 翻译
    训练机器人的第二步:
    1. optimizer.zero_grad():清空上次的训练记录(就像擦黑板,准备重新计算)
    2. loss.backward():根据当前的损失值,计算应该怎么调整机器人的 “大脑”(权重和偏移量)
    3. optimizer.step():教练告诉机器人:“把权重 1 增加 0.01,把权重 2 减少 0.005……”
    if (i+1) % 100 == 0:
        print(f'第{i+1}次训练,扣分:{loss.item():.4f}')

  • 翻译
    每训练 100 次,就打印一次当前的分数(损失值)。分数越低,说明机器人越厉害!

五、评估模型(看看机器人学的怎么样)

y_pred_guess = (y_pred >= 0.5).float()  # 把概率转成0或1的判断
accuracy = (y_pred_guess == y_true).float().mean()  # 计算准确率
print(f'模型猜的准确率:{accuracy.item() * 100:.2f}%')
  • 翻译
    考试时间!让机器人判断 100 封邮件,看看它有多厉害:
  • 就像抛硬币猜正反:
  • 模型说 “正面概率是 0.6” → 我们猜 “正面”(1)
  • 模型说 “正面概率是 0.3” → 我们猜 “反面”(0)
  • (y_pred >= 0.5)就是这个 “猜” 的过程,结果是True/False,转成.float()后变成1.0/0.0
  • accuracy = (y_pred_guess == y_true).float().mean()
  • (y_pred_guess == y_true):对比模型的猜测和真实标签,得到一堆True/False(猜对了 / 猜错了)
  • .float():把True转成 1,False转成 0(1 表示猜对,0 表示猜错)
  • .mean():计算这些 1 和 0 的平均值,就是 “猜对的比例”
  • print(f'模型猜的准确率:{accuracy.item() * 100:.2f}%')
  • accuracy.item():从张量中取出数值(比如 0.8)
  • * 100:转成百分比(0.8 → 80%)
  • :.2f:保留两位小数(更美观)

六、可视化(画两张图,让结果更直观)

plt.figure(figsize=(12, 5))  # 创建一个画布(宽12厘米,高5厘米)
  • 翻译
    准备一张大纸,用来画两张图给老板看。
# 左图:数据点和模型的判断边界
plt.subplot(1, 2, 1)  # 把纸分成左右两半,画左边的图
plt.scatter(x[y_true.view(-1) == 0, 0], x[y_true.view(-1) == 0, 1], c='blue', label='正常邮件')
plt.scatter(x[y_true.view(-1) == 1, 0], x[y_true.view(-1) == 1, 1], c='red', label='垃圾邮件')
  • 翻译
    画第一张图(散点图):
    • 蓝色点:正常邮件,横坐标是 “免费” 次数,纵坐标是 “优惠” 次数
    • 红色点:垃圾邮件,同样用横纵坐标表示两个特征

# 画模型的判断边界(黑色线)
x1_min, x1_max = x[:, 0].min() - 1, x[:, 0].max() + 1
x2_min, x2_max = x[:, 1].min() - 1, x[:, 1].max() + 1
xx1, xx2 = np.meshgrid(np.linspace(x1_min, x1_max, 100), np.linspace(x2_min, x2_max, 100))
grid = torch.tensor(np.c_[xx1.ravel(), xx2.ravel()], dtype=torch.float32)
with torch.no_grad():
    probs = model(grid).reshape(xx1.shape)
plt.contour(xx1, xx2, probs, levels=[0.5], linewidths=2, colors='black')
  • 翻译
    画一条 “分界线”,帮助我们理解机器人的判断逻辑:
    1. 在整个区域内密密麻麻地撒 100×100 个点(就像在地图上标很多位置)
    2. 让机器人判断每个点是垃圾邮件的概率
    3. 画一条线,线上的点概率正好是 0.5(机器人 “不确定” 的位置)
      • 线的左边:机器人更可能判断为正常邮件
      • 线的右边:机器人更可能判断为垃圾邮件
plt.xlabel('"免费"出现次数')
plt.ylabel('"优惠"出现次数')
plt.title('模型怎么判断垃圾邮件')
plt.legend()  # 显示图例(蓝色和红色点代表什么)
  • 翻译
    给图加上标签:
    • x 轴:“免费” 出现的次数
    • y 轴:“优惠” 出现的次数
    • 标题:模型怎么判断垃圾邮件
    • 图例:说明蓝色点和红色点分别代表什么
# 右图:扣分越来越少
plt.subplot(1, 2, 2)  # 画右边的图
plt.plot(range(500), losses)  # x轴是训练次数,y轴是每次的损失值
plt.xlabel('训练次数')
plt.ylabel('损失值(扣分)')
plt.title('模型越学越准啦!')
plt.show()  # 显示这两张图
  • 翻译
    画第二张图(折线图):
    • x 轴:训练次数(从 1 到 500)
    • y 轴:每次训练的损失值(扣分)
    • 理想情况下,随着训练次数增加,损失值会越来越低,说明机器人越来越厉害!

总结:整个流程像什么?

  1. 准备材料
    开一家 “垃圾邮件识别公司”,准备电脑、画图工具、零件库和电子表格。

  2. 制造机器人
    设计一个机器人,它能根据 “免费” 和 “优惠” 的次数判断邮件是不是垃圾。

  3. 训练机器人

    • 给机器人 100 封 “训练邮件”,每封邮件都标好是不是垃圾
    • 让机器人判断,然后告诉它 “你错了多少”(损失值)
    • 机器人根据错误调整自己的 “大脑”(权重和偏移量)
    • 重复这个过程 500 次,直到机器人越来越准
  4. 考试
    让机器人判断 100 封新邮件,计算它的准确率(比如 85%)。

  5. 画报表
    画两张图给老板看:

    • 左图:机器人的 “判断逻辑”(分界线)
    • 右图:机器人的 “学习进度”(损失值越来越低)

终极总结(3 句话搞定)

  1. 逻辑回归是做什么的?
    回答 “是不是” 的问题(比如是不是垃圾邮件),输出一个概率(0-1 之间)。

  2. 核心步骤是什么?

    • 算一个 “指数”(z = 特征 × 权重 + 偏置)
    • 把指数转成概率(用 sigmoid 函数)
    • 根据概率判断类别(比如≥0.5 就是 “是”)
  3. 怎么让模型学的准?
    用 BCE 损失函数当 “老师”,告诉模型猜的有多错,再用梯度下降调整参数,让错误越来越小。