Logistic Regression 原理探讨:从概率、Logit 到 Python 实战




2018-12-25

blog_main_img

Logistic Regression,中文通常叫逻辑回归。名字里带“回归”,但它最常见的用途其实是分类,尤其是二分类。

比如:

  • 用户是否会点击广告
  • 邮件是否是垃圾邮件
  • 用户是否会流失
  • 交易是否存在风险
  • 样本是否属于某个类别

为什么分类问题不能直接用线性回归

线性回归的形式很简单:

y = w1x1 + w2x2 + ... + wnxn + b

也可以写成向量形式:

y = w·x + b

如果目标是预测房价、销量、温度这类连续值,线性回归很自然。

但如果目标是二分类,比如:

0 = 不点击
1 = 点击

直接用线性回归就会有问题。

因为线性模型输出范围是整个实数轴:

(-∞, +∞)

而概率必须落在:

[0, 1]

所以我们需要一个函数,把任意实数压到 0 到 1 之间。

这个函数就是 sigmoid。

Sigmoid 函数

Sigmoid 函数定义为:

σ(z) = 1 / (1 + e^(-z))

其中:

z = w·x + b

它的输出范围是:

0 < σ(z) < 1

因此可以把它解释成概率。

Sigmoid 函数

z = 0 时:

σ(0) = 1 / (1 + e^0) = 1 / 2 = 0.5

z 很大时,σ(z) 接近 1。

z 很小时,σ(z) 接近 0。

于是逻辑回归的预测形式就是:

p(y = 1 | x) = σ(w·x + b)

如果概率大于某个阈值,比如 0.5,就预测为 1;否则预测为 0。

Odds:从概率到几率

理解逻辑回归时,一个很关键的概念是 odds,中文可以叫“几率”。

如果某件事发生的概率是 p,不发生的概率是 1 - p,那么 odds 定义为:

odds = p / (1 - p)

举个例子,如果点击概率是:

p = 0.8

那么不点击概率是:

1 - p = 0.2

odds 就是:

0.8 / 0.2 = 4

意思是:发生与不发生的比例是 4:1。

如果:

p = 0.5

那么:

odds = 0.5 / 0.5 = 1

发生和不发生一样可能。

Logit:对 odds 取对数

odds 的取值范围是:

(0, +∞)

对 odds 取自然对数,就得到 logit:

logit(p) = log(p / (1 - p))

logit 的取值范围是整个实数:

(-∞, +∞)

这就很妙了。

线性模型 w·x + b 的输出也是整个实数,所以逻辑回归可以理解为:

log(p / (1 - p)) = w·x + b

也就是说,逻辑回归不是直接对概率 p 做线性建模,而是对概率对应的 logit 做线性建模。

这也是为什么 Logistic Regression 可以看成对 logit 的线性拟合。

从 Logit 推回 Sigmoid

从下面这个式子开始:

log(p / (1 - p)) = z

两边取指数:

p / (1 - p) = e^z

整理:

p = e^z(1 - p)
p = e^z - e^z p
p(1 + e^z) = e^z
p = e^z / (1 + e^z)

上下同时除以 e^z

p = 1 / (1 + e^(-z))

这就是 sigmoid 函数。

所以 sigmoid 不是凭空出现的,它可以从 logit 线性建模自然推导出来。

决策边界

逻辑回归预测的是概率:

p = σ(w·x + b)

如果使用 0.5 作为分类阈值:

p >= 0.5 -> 预测为 1
p < 0.5  -> 预测为 0

由于:

σ(0) = 0.5

所以分类边界对应:

w·x + b = 0

这就是逻辑回归的决策边界。

Logistic Regression 决策边界

在二维特征下,边界是一条直线。

在更高维特征下,边界是一个超平面。

这也说明了逻辑回归的一个特点:它本质上是线性分类器。

如果数据本身不是线性可分,可以通过特征工程、多项式特征,或者换用更复杂的模型来处理。

损失函数:为什么不用平方误差

线性回归常用平方误差:

MSE = 1/n × Σ(y_i - y_hat_i)^2

但逻辑回归通常不使用平方误差,而是使用对数损失,也叫交叉熵损失。

对于单个样本:

Loss = -[y log(p) + (1 - y) log(1 - p)]

其中:

  • y 是真实标签,取 0 或 1
  • p 是模型预测为 1 的概率

如果真实标签 y = 1

Loss = -log(p)

此时 p 越接近 1,损失越小。

如果真实标签 y = 0

Loss = -log(1 - p)

此时 p 越接近 0,损失越小。

把两种情况合在一个公式里,就是交叉熵。

对于整个数据集:

J(w, b) = -1/n × Σ[y_i log(p_i) + (1 - y_i) log(1 - p_i)]

训练逻辑回归,就是寻找一组 wb,让这个损失尽可能小。

正则化:别让权重太放飞

逻辑回归虽然简单,但也会过拟合。

如果特征很多,或者特征之间存在复杂组合,模型可能在训练集上表现很好,但泛化能力不好。

常见做法是在损失函数中加入正则化。

L2 正则化:

J = Loss + λΣw_j²

它会惩罚过大的权重,让模型更平滑。

L1 正则化:

J = Loss + λΣ|w_j|

它可能把部分权重压到 0,从而产生特征选择效果。

在 sklearn 中,逻辑回归默认会带正则化,所以参数解释时要注意这一点。

用 NumPy 手写一个简单逻辑回归

下面用 NumPy 写一个最小版本,帮助理解训练过程。

import numpy as np


def sigmoid(z):
    return 1 / (1 + np.exp(-z))


def binary_cross_entropy(y, p):
    eps = 1e-12
    p = np.clip(p, eps, 1 - eps)
    return -np.mean(y * np.log(p) + (1 - y) * np.log(1 - p))


class SimpleLogisticRegression:
    def __init__(self, lr=0.1, n_iter=1000):
        self.lr = lr
        self.n_iter = n_iter
        self.w = None
        self.b = 0.0

    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.w = np.zeros(n_features)
        self.b = 0.0

        for _ in range(self.n_iter):
            z = X @ self.w + self.b
            p = sigmoid(z)

            dw = X.T @ (p - y) / n_samples
            db = np.mean(p - y)

            self.w -= self.lr * dw
            self.b -= self.lr * db

        return self

    def predict_proba(self, X):
        return sigmoid(X @ self.w + self.b)

    def predict(self, X, threshold=0.5):
        return (self.predict_proba(X) >= threshold).astype(int)

造一组简单数据测试:

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

X, y = make_classification(
    n_samples=500,
    n_features=2,
    n_redundant=0,
    n_informative=2,
    random_state=42
)

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42
)

model = SimpleLogisticRegression(lr=0.1, n_iter=2000)
model.fit(X_train, y_train)

y_pred = model.predict(X_test)

print("accuracy:", accuracy_score(y_test, y_pred))
print("weights:", model.w)
print("bias:", model.b)

这个版本没有做各种工程优化,但核心逻辑已经包含:

  • 线性部分 z = Xw + b
  • sigmoid 概率映射
  • 交叉熵思想
  • 梯度下降更新参数

用 sklearn 训练逻辑回归

实际项目中,更常用 sklearn。

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix

data = load_breast_cancer()
X = data.data
y = data.target

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y
)

model = Pipeline([
    ("scaler", StandardScaler()),
    ("clf", LogisticRegression(max_iter=1000))
])

model.fit(X_train, y_train)

y_pred = model.predict(X_test)

print("accuracy:", accuracy_score(y_test, y_pred))
print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred, target_names=data.target_names))

这里用了 StandardScaler

逻辑回归对特征尺度比较敏感,尤其在带正则化时,标准化通常是一个好习惯。

查看预测概率

逻辑回归不只是给类别,也可以给概率。

proba = model.predict_proba(X_test[:5])

print(proba)

输出形状一般是:

n_samples × n_classes

二分类时,每行通常类似:

[预测为 0 的概率, 预测为 1 的概率]

如果只想看正类概率:

positive_proba = model.predict_proba(X_test)[:, 1]

这在风控、推荐、排序、阈值调整里很常用。

阈值不一定非得是 0.5

默认情况下:

p >= 0.5 -> 预测为正类

但很多业务场景中,阈值可以调整。

比如风控系统更怕漏掉风险交易,可能会降低阈值,让更多样本被判为风险。

positive_proba = model.predict_proba(X_test)[:, 1]

threshold = 0.3
y_pred_custom = (positive_proba >= threshold).astype(int)

阈值降低后,通常召回率会上升,但误报也可能增加。

所以阈值不是数学上固定的,而是要结合业务成本来选。

逻辑回归适合什么场景

逻辑回归适合这些场景:

  • 二分类任务
  • 需要概率输出的任务
  • 需要可解释性的业务
  • 特征和目标之间关系近似线性的任务
  • 作为复杂模型前的基线模型

典型例子包括:

  • 点击率预估
  • 用户流失预测
  • 信用风险判断
  • 医疗辅助筛查
  • 文本二分类
  • 垃圾邮件识别

它的优点是简单、稳定、可解释。

权重 w 可以反映特征对 logit 的影响方向:

  • 权重大于 0,特征增大时更倾向正类
  • 权重小于 0,特征增大时更倾向负类

但要注意,前提是特征尺度和特征含义要合理。

逻辑回归的优点和局限

逻辑回归的优点:

  • 模型简单
  • 训练速度快
  • 输出概率
  • 可解释性强
  • 不容易像复杂模型那样难以排查

它的局限也很明显:

  • 决策边界本质是线性的
  • 对复杂非线性关系表达能力有限
  • 对异常值和特征尺度比较敏感
  • 特征工程会显著影响效果

如果数据明显非线性,可以考虑:

  • 增加多项式特征
  • 做特征交叉
  • 使用树模型
  • 使用核方法
  • 使用神经网络

Logistic Regression 虽然名字叫回归,但常用于分类。

它的核心思想是:

先用线性模型得到 z = w·x + b
再通过 sigmoid 把 z 映射成概率

从 logit 角度看,它等价于:

log(p / (1 - p)) = w·x + b

也就是对事件发生概率的 log odds 做线性建模。

训练时通常使用交叉熵损失,通过优化参数让真实标签对应的预测概率更高。

逻辑回归不复杂,但很经典。它既能帮助我们理解概率分类模型,也能作为很多业务问题的可靠基线。