线性回归与逻辑回归
本教程共 60 篇 · 第 18 篇 · 更新于 2026-08-17 · 约 3 分钟阅读
本节目标:从最简单的模型出发,搞懂回归和分类两大任务,并会用
nn.Linear写出完整的线性模型。
两个最基础的任务
机器学习任务分两大类。预测房价、温度这种连续数值,叫回归(regression)。判断邮件是不是垃圾、图片里是猫还是狗,叫分类(classification)。
这章的两个模型正好各管一个:线性回归管回归,逻辑回归管分类。它们也是深度学习的地基,后面所有网络都从这里长出来。
线性回归:画一条线
线性回归(linear regression)假设输出和输入是线性关系。一个特征时就是一条直线:
y = wx + b
w 是权重(weight),决定线的斜率;b 是偏置(bias),决定线的位置。模型要学的,就是找出让预测最准的那组 w 和 b。
特征多起来之后,直线变成平面,再变成高维空间里的超平面。名字变来变去,本质还是「每个特征乘一个权重,加起来再加偏置」这一件事。
在 PyTorch 里,这活交给 nn.Linear。它自动创建权重和偏置,帮你算 x @ w.T + b:
import torch.nn as nn
model = nn.Linear(2, 1) # 输入 2 个特征,输出 1 个值
Note
nn.Linear会把权重、偏置自动注册成可训练参数,不用手动定义,更不用手动初始化。
怎么衡量「准不准」
预测值和真实值的差距,用均方误差(MSE,Mean Squared Error)衡量:每个样本差值的平方,再取平均。平方有两个作用:去掉正负号,让大误差受更重的惩罚。
PyTorch 里现成可用:
criterion = nn.MSELoss()
loss = criterion(pred, target)
有了损失,就靠梯度下降让它变小。顺便说一句,最小二乘是线性回归的解析解,适合小问题;梯度下降是迭代逼近,才是深度学习的标准做法。
学习率在这步很关键。太大,参数来回横跳不收敛;太小,几百轮都挪不动。先试试 0.01,再根据 loss 曲线的样子微调。
完整小例子
造一批带噪声的线性数据,让模型把真实的 w 和 b 学回来:
import torch
import torch.nn as nn
torch.manual_seed(42)
# 1. 造数据:真实规律是 y = 2*x1 + 3*x2 + 4,再加一点噪声
X = torch.randn(100, 2)
true_w = torch.tensor([2.0, 3.0])
true_b = 4.0
Y = X @ true_w + true_b + torch.randn(100) * 0.1
# 2. 模型、损失、优化器
model = nn.Linear(2, 1)
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
# 3. 训练
for epoch in range(500):
pred = model(X).squeeze() # (100, 1) -> (100)
loss = criterion(pred, Y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 100 == 0:
print(f"epoch {epoch + 1}, loss = {loss.item():.4f}")
# 4. 看学到的参数
print("学到的 w:", model.weight.data)
print("学到的 b:", model.bias.data)
跑完你会发现,学到的 w 接近 [2.0, 3.0],b 接近 4.0。噪声没难倒模型,线性关系被完整复原。
这个例子麻雀虽小,五脏俱全。换个真实数据集,把 nn.Linear(2, 1) 的输入维度改成真实特征数,剩下的代码几乎不用动。
Note
pred.squeeze()把形状从 (100, 1) 压成 (100),才能和 Y 对上。形状不匹配是新手最常踩的坑,报错先查形状。
逻辑回归:换一层皮
分类问题不能直接输出连续值。逻辑回归(logistic regression)的思路:还是算线性组合,但用一个激活函数把结果压到 0 到 1 之间,当作「属于正类的概率」。
这个函数叫 sigmoid。数值很大时它接近 1,很小时接近 0,中间平滑过渡:
import torch
z = torch.tensor([-2.0, 0.0, 2.0])
p = torch.sigmoid(z)
print(p) # 输出接近 0、0.5、接近 1
预测时取 0.5 当阈值:概率大于它判正类,小于它判负类。
Tipsigmoid 的输出天然是 0 到 1,正好当概率用。多分类问题不逐个用 sigmoid,而是用 softmax 一次算出一组概率,第 13 章讲过,忘了可以回头翻。
逻辑回归的损失
分类不再用 MSE,改用交叉熵。二分类用 BCEWithLogitsLoss,它把 sigmoid 和损失合并成一步,数值上更稳:
import torch.nn as nn
criterion = nn.BCEWithLogitsLoss() # 内部自带 sigmoid
pred = model(X) # 直接传原始分数 logits
loss = criterion(pred.squeeze(), Y) # Y 必须是 0/1 的 float 张量
Warning用了
BCEWithLogitsLoss,就别再手动torch.sigmoid(pred)再算损失。双重 sigmoid 会破坏训练,loss 会异常。
另外注意,标签 Y 必须是 float 类型,不能是整数张量。分类标签平时习惯用整数,这一步经常被忽略。
逻辑回归小例子
造两类点,让模型学一条分界线。这是逻辑回归最经典的玩法:
import torch
import torch.nn as nn
torch.manual_seed(0)
# 1. 造数据:两个坐标之和为正的算 1,否则算 0
X = torch.randn(200, 2)
Y = (X[:, 0] + X[:, 1] > 0).float()
# 2. 模型、损失、优化器
model = nn.Linear(2, 1)
criterion = nn.BCEWithLogitsLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.05)
# 3. 训练
for epoch in range(300):
pred = model(X).squeeze()
loss = criterion(pred, Y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 100 == 0:
print(f"epoch {epoch + 1}, loss = {loss.item():.4f}")
# 4. 评估:sigmoid 转概率,0.5 定类别
with torch.no_grad():
prob = torch.sigmoid(model(X).squeeze())
pred_class = (prob > 0.5).float()
acc = (pred_class == Y).float().mean()
print(f"准确率: {acc.item() * 100:.1f}%")
训练完准确率会非常高。因为这两类点本来就是被一条直线分开的,逻辑回归学这条线,天经地义。
两个模型一张表
| 对比项 | 线性回归 | 逻辑回归 |
|---|---|---|
| 任务 | 回归(连续值) | 分类(0/1) |
| 输出 | 任意实数 | 0 到 1 的概率 |
| 损失 | MSELoss | BCEWithLogitsLoss |
| 典型场景 | 房价、温度预测 | 垃圾邮件、患病判断 |
逻辑回归名字带「回归」,干的却是分类的活。别被名字骗了,记住:看输出,不看名字。
怎么选
拿到一个问题,先问一句:输出是什么?输出是连续数值,从线性回归起步;输出是类别,从逻辑回归起步。
两个模型都搞不定时,说明数据不是线性的。恭喜,该上第 19 章的 MLP 了。这两个模型就像新手村的木剑,先拿它们练手,再换高级装备。
常见坑
下面这几个坑,新手十有八九都会踩一遍。先打预防针,真遇到时知道往哪看。
- 特征尺度差太大:一个特征在 0 到 1,另一个到上万,梯度会被大尺度特征牵着走。先做标准化(归一化到均值 0、方差 1),训练会稳很多
- loss 不降:先试学习率 0.1、0.01、0.001 三档;还不行就检查数据有没有归一化
- 输出形状对不上:模型输出是 (100, 1),标签是 (100,),用
squeeze()或unsqueeze()对齐,别硬算
这几个坑在 MLP 上同样适用,现在记住,后面少踩。