文章内容来源:《PyTorch编程技术与深度学习》 作者:袁梅宇

一、逻辑回归是什么?和线性回归有啥不一样?

很多刚接触机器学习的同学会疑惑:“逻辑回归带‘回归’二字,为啥用来做分类?”其实这正是它的特别之处——

  • 线性回归:解决回归问题,目标是预测连续数值(比如房价、股票涨跌幅度),目标属性是连续的数值类型。
  • 逻辑回归:解决分类问题,目标是把数据分到离散的类别里(比如判断邮件是不是垃圾邮件、用户会不会点击广告),目标属性是离散的标称类型。

简单说,线性回归是“预测数值多少”,逻辑回归是“判断属于哪一类”。

更妙的是,逻辑回归可以看作神经网络的“最小单元”——一个神经元。搞懂它,后面学复杂神经网络就轻松多了!

二、逻辑回归的核心:Sigmoid函数与概率思维

逻辑回归能做分类,全靠一个叫Sigmoid(也叫Logistic函数)的“神器”:
g(z)=11+e−z g(z) = \frac{1}{1 + e^{-z}} g(z)=1+ez1
它长这样(图3.4的曲线):

  • 输入很大的正数,输出接近1;
  • 输入很大的负数,输出接近0;
  • 输入为0时,输出刚好是0.5。

这个特性让Sigmoid完美适配“概率”的概念——我们可以把逻辑回归的输出 h(x;θ)h(x;\theta)h(x;θ) 理解为“样本属于正类的概率”:

  • 如果 h(x;θ)≥0.5h(x;\theta) \geq 0.5h(x;θ)0.5,就预测为正类(比如标签1);
  • 如果 h(x;θ)<0.5h(x;\theta) < 0.5h(x;θ)<0.5,就预测为负类(比如标签0)。

三、从零开始:用PyTorch手写逻辑回归

光懂原理不够,动手实现才是硬道理。我们分步骤搭建一个逻辑回归模型,解决二元分类问题。

步骤1:定义Sigmoid激活函数和模型结构

先写最基础的函数和模型框架:

import torch

def sigmoid(z):
    """S型激活函数,也就是Logistic函数"""
    g = 1 / (1 + torch.exp(-z))
    return g

def model(x, w, b):
    """逻辑回归模型:y_hat = g(w^T x + b)"""
    return sigmoid(x.mv(w) + b)

这里的x是输入数据矩阵,w是权重向量,b是偏置标量。

步骤2:定义损失函数(负对数似然)

逻辑回归用负对数似然损失来衡量预测误差,公式对应代码:

def loss_fn(y_pred, y):
    """损失函数:负对数似然"""
    loss = - y.mul(torch.log(y_pred)) - (1 - y).mul(torch.log(1 - y_pred))
    return loss.mean()

def grad_loss_fn(y_pred, y):
    """损失函数对预测值的导数"""
    return y_pred - y

步骤3:梯度计算与参数更新

要让模型学习,就得用梯度下降法更新参数(权重w和偏置b):

def grad_fn(x, y, y_pred):
    """计算权重和偏置的梯度"""
    grad_w = grad_loss_fn(y_pred, y) * x
    grad_b = grad_loss_fn(y_pred, y)
    return torch.cat((grad_w.mean(dim=0), grad_b.mean().unsqueeze(0)), 0)

def model_training(x, y, n_epochs, learning_rate, params, print_params=True):
    """模型训练函数:用梯度下降更新参数"""
    for epoch in range(1, n_epochs + 1):
        w, b = params[:-1], params[-1]
        # 前向传播
        y_pred = model(x, w, b)
        # 计算损失
        loss = loss_fn(y_pred, y)
        # 计算梯度
        grad = grad_fn(x, y, y_pred)
        # 更新参数
        params -= learning_rate * grad
        
        if epoch == 1 or epoch % 10 == 1:
            print(f'轮次:{epoch}\t损失:{float(loss):.4f}')
            if print_params:
                print(f'参数:{params.detach().numpy()}')
                print(f'梯度:{grad.detach().numpy()}\n')
    return params

步骤4:生成数据并训练模型

最后生成模拟数据,初始化参数,启动训练:

# 随机生成二元分类数据
def generate_data():
    # 这里省略数据生成逻辑,实际可根据需求构造
    x = torch.randn(100, 2)
    y = (x[:, 0] + x[:, 1] > 0).float().unsqueeze(1)
    return x, y

x, y = generate_data()
# 初始化参数:权重w(2个特征)和偏置b
w = torch.zeros(2)
b = torch.zeros(1)
params = torch.tensor([0.0, 0.0, 0.0])

# 训练模型
params = model_training(
    x=x,
    y=y,
    n_epochs=500,
    learning_rate=0.1,
    params=params
)

print(f'梯度下降找到的w和b:{params.numpy()}')

训练完成后,模型会学到一条“决策边界”(直线),把正例和负例分开,就像图3.5展示的那样。

四、偷懒版:用PyTorch官方API快速实现

如果觉得手写太麻烦,PyTorch的nn模块能让我们更简洁地实现逻辑回归:

import torch.nn as nn
import torch.optim as optim

# 定义模型:线性层 + Sigmoid激活
seg_model = nn.Sequential(
    nn.Linear(2, 1),  # 2个输入特征,1个输出(对应二分类的概率)
    nn.Sigmoid()
)

# 定义优化器(SGD)和损失函数(二元交叉熵)
learning_rate = 0.1
optimizer = optim.SGD(seg_model.parameters(), lr=learning_rate)
loss_fn = nn.BCELoss()  # 二元交叉熵损失,和我们手写的loss_fn等价

# 模型训练函数
def concise_model_training(x, y, n_epochs, optimizer, model, loss_fn):
    for epoch in range(1, n_epochs + 1):
        # 前向传播
        y_pred = model(x)
        # 计算损失
        loss = loss_fn(y_pred, y)
        # 梯度清零 + 反向传播 + 更新参数
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        if epoch == 1 or epoch % 10 == 1:
            print(f'轮次:{epoch}\t损失:{float(loss):.4f}')

# 调用训练函数
concise_model_training(
    x=x,
    y=y,
    n_epochs=500,
    optimizer=optimizer,
    model=seg_model,
    loss_fn=loss_fn
)

# 查看训练后的参数
print('优化后的模型参数:')
for name, param in seg_model.named_parameters():
    print(name, param)

对比手写版本,官方API把“梯度计算、参数更新”等细节封装好了,大大减少了代码量。

五、逻辑回归的延伸:从二分类到多分类?

逻辑回归天生是解决二元分类的,但我们可以通过“一对多”等策略扩展到多分类任务。不过更优雅的方法是后续要学的Softmax回归(下一篇博客预告!)。

另外,逻辑回归作为“神经网络的最小单元”,学好它能帮我们更顺畅地进入深度学习世界——毕竟复杂神经网络就是无数个“逻辑回归式神经元”的组合。

写在最后

逻辑回归看似简单,却是理解分类任务、神经网络的关键跳板。从原理到手写实现,再到用框架简化开发,每一步都值得细细琢磨。下一篇我们会聊聊多分类任务的解决方案——Softmax回归,不见不散~

(完整代码可参考书中的logistic_regression_from_scratch.pylogistic_regression_concise.py

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐