标准回答
一、先讲模型形式
逻辑回归不是“回归”,它通常用来做二分类:先算线性分数 z = Xw + b,再过 sigmoid 得到正类概率 p = sigmoid(z)。
二、再讲训练目标
损失用二元交叉熵,本质上就是伯努利分布的负对数似然。它比 MSE 更适合配 sigmoid,因为梯度更干净、优化更稳定。
三、最后讲代码关键
推导后最重要的结论是:对 z 的梯度就是 p − y,所以 dw=(1/N)Xᵀ(p−y),db=mean(p−y)。实现时注意 sigmoid 要分段写,防 exp 溢出;log 里要加 eps,防 log(0)。代码如下:
import numpy as np
def sigmoid(z):
# 数值稳定的 sigmoid:按 z 正负分段,避免 exp 溢出
out = np.empty_like(z, dtype=float)
pos = z >= 0
out[pos] = 1.0 / (1.0 + np.exp(-z[pos]))
ez = np.exp(z[~pos])
out[~pos] = ez / (1.0 + ez)
return out
def logistic_regression(X, y, lr=0.1, epochs=1000, l2=0.0):
N, D = X.shape
w = np.zeros(D)
b = 0.0
eps = 1e-12
for _ in range(epochs):
p = sigmoid(X @ w + b) # 前向:预测概率
# 二元交叉熵梯度(化简后等于 p - y)
grad = p - y
dw = (X.T @ grad) / N + l2 * w # 加 L2 正则
db = grad.sum() / N
w -= lr * dw
b -= lr * db
return w, b
def bce_loss(X, y, w, b):
p = sigmoid(X @ w + b)
eps = 1e-12
return float(-np.mean(y * np.log(p + eps) + (1 - y) * np.log(1 - p + eps)))
if __name__ == '__main__':
rng = np.random.default_rng(0)
X0 = rng.normal(-1, 1, (100, 2)); X1 = rng.normal(2, 1, (100, 2))
X = np.vstack([X0, X1]); y = np.array([0.0] * 100 + [1.0] * 100)
w, b = logistic_regression(X, y, lr=0.5, epochs=2000)
pred = (sigmoid(X @ w + b) >= 0.5).astype(int)
print('acc=', round((pred == y).mean(), 3))
print('loss=', round(bce_loss(X, y, w, b), 4))常见误区
⚠️ 常见踩坑
误区一:sigmoid 直接裸写。 z 很大或很小时 exp 可能溢出,要分段实现。误区二:log 不加 eps。 p 接近 0 或 1 时会出现 -inf。误区三:用 MSE 当标准损失。 虽然能跑,但配 sigmoid 优化更差,标准做法是交叉熵。
追问
追问 1:为什么用交叉熵而不是 MSE?
可以说:交叉熵和 sigmoid 是一对更自然的组合。从概率角度,它是伯努利分布的负对数似然;从优化角度,梯度能化成 p−y,比较稳定。MSE 也能算,但和 sigmoid 组合后在饱和区梯度容易很小,训练会慢,也更难优化。
追问 2:如何扩展到多分类?
很自然:把 sigmoid 换成 softmax,把二元交叉熵换成多类交叉熵。权重从一个向量变成 D×C 的矩阵,每个样本输出 C 个类别概率。梯度形式仍然很漂亮,基本就是 p−onehot(y)。
🔗 相似问题
同一考点的不同问法,换着练更稳
延伸学习
按主题分类的相关资源,便于系统复习
