基于Python推导log loss梯度下降算法及基础实现代码
对数损失(Log Loss)梯度下降推导与Python实现
基础符号定义
二分类任务下的符号约定如下:
- 训练集总样本数:$n$
- 单样本特征向量:$x_i \in \mathbb{R}^d$,$d$为特征维度
- 单样本真实标签:$y_i \in {0,1}$,1代表正类,0代表负类
- 模型待学习参数:权重向量$w \in \mathbb{R}^d$,偏置项$b$
- 模型输出的正类预测概率:$\hat{y}_i = \sigma(w^T x_i + b)$,其中$\sigma$为sigmoid激活函数,定义为$\sigma(z) = \frac{1}{1+e^{-z}}$,输出范围固定在(0,1)之间
对数损失定义
二分类场景下的对数损失(即二元交叉熵损失),训练集平均损失公式为:
$$L(w,b) = -\frac{1}{n}\sum_{i=1}^n \left[ y_i \log(\hat{y}_i) + (1-y_i)\log(1-\hat{y}_i) \right]$$
对数损失是典型的凸函数,梯度下降求解时可以收敛到全局最优值,不会陷入局部极小点
梯度推导过程
推导核心用链式法则,先利用sigmoid函数的导数性质简化计算:sigmoid的导数满足$\sigma'(z) = \sigma(z)(1-\sigma(z))$,推导步骤如下:
- 先计算单样本损失对预测概率$\hat{y}_i$的偏导:
$$\frac{\partial L_i}{\partial \hat{y}_i} = -\frac{y_i}{\hat{y}_i} + \frac{1-y_i}{1-\hat{y}_i}$$ - 引入线性输出项$z_i = w^T x_i + b$,结合sigmoid导数,计算损失对$z_i$的偏导:
$$
\begin{align*}
\frac{\partial L}{\partial z_i} &= \frac{\partial L_i}{\partial \hat{y}_i} \cdot \frac{\partial \hat{y}_i}{\partial z_i} \
&= \left( -\frac{y_i}{\hat{y}_i} + \frac{1-y_i}{1-\hat{y}_i} \right) \cdot \hat{y}_i(1-\hat{y}_i) \
&= -y_i(1-\hat{y}_i) + (1-y_i)\hat{y}_i \
&= \hat{y}_i - y_i
\end{align*}
$$
可以看到化简后梯度形式非常简洁,就是预测概率与真实标签的差值,这是逻辑回归实现高效的核心原因 - 进一步推导对参数$w$和$b$的平均梯度:
- 对权重$w$的梯度:$\frac{\partial z_i}{\partial w} = x_i$,因此全训练集平均梯度为$\frac{\partial L}{\partial w} = \frac{1}{n}\sum_{i=1}^n (\hat{y}_i - y_i)x_i$
- 对偏置$b$的梯度:$\frac{\partial z_i}{\partial b} = 1$,因此全训练集平均梯度为$\frac{\partial L}{\partial b} = \frac{1}{n}\sum_{i=1}^n (\hat{y}_i - y_i)$
- 梯度下降参数更新规则:
每轮迭代沿梯度的反方向更新参数,抵消梯度上升的影响,其中$\alpha$为预设的学习率:
$$w = w - \alpha \cdot \frac{\partial L}{\partial w}$$
$$b = b - \alpha \cdot \frac{\partial L}{\partial b}$$
基础Python实现
实现仅依赖numpy做矩阵运算,完全对应上述推导逻辑,无框架黑盒:
import numpy as np def sigmoid(z): # 数值稳定版sigmoid,避免z为绝对值极大的负数时出现指数溢出 return np.where(z >= 0, 1 / (1 + np.exp(-z)), np.exp(z) / (1 + np.exp(z))) def compute_log_loss(y_true, y_pred): # 计算平均对数损失,加1e-8避免log(0)的数值错误 n = len(y_true) loss = -1/n * np.sum( y_true * np.log(y_pred + 1e-8) + (1 - y_true) * np.log(1 - y_pred + 1e-8) ) return loss def gradient_descent_train(X, y, lr=0.1, epochs=1000): """ 梯度下降训练最小化对数损失 参数说明: X: 特征矩阵,shape=(样本数n, 特征维度d) y: 真实标签数组,shape=(n,) lr: 学习率 epochs: 迭代轮数 返回:训练得到的w、b,以及每轮迭代的损失记录 """ n, d = X.shape # 参数全0初始化即可 w = np.zeros(d) b = 0.0 loss_history = [] for epoch in range(epochs): # 前向计算预测概率 z = np.dot(X, w) + b y_pred = sigmoid(z) # 记录当前轮损失 current_loss = compute_log_loss(y, y_pred) loss_history.append(current_loss) # 计算梯度 dw = 1/n * np.dot(X.T, (y_pred - y)) db = 1/n * np.sum(y_pred - y) # 更新参数 w -= lr * dw b -= lr * db # 每100轮打印训练状态 if (epoch + 1) % 100 == 0: print(f"Epoch {epoch+1}/{epochs}, Log Loss: {current_loss:.4f}") return w, b, loss_history def predict_proba(X, w, b): # 输出正类预测概率 z = np.dot(X, w) + b return sigmoid(z) def predict(X, w, b, threshold=0.5): # 按指定阈值输出分类结果 proba = predict_proba(X, w, b) return (proba >= threshold).astype(int)
测试示例
可以用简单的线性可分数据集验证实现效果:
from sklearn.datasets import make_classification from sklearn.metrics import accuracy_score # 生成1000个二分类样本,特征维度为2 X, y = make_classification(n_samples=1000, n_features=2, n_redundant=0, random_state=42) # 模型训练 w, b, loss_history = gradient_descent_train(X, y, lr=0.1, epochs=1000) # 预测与评估 y_pred = predict(X, w, b) print(f"训练集分类准确率: {accuracy_score(y, y_pred):.4f}")
核心实现注意点
- 没有直接使用原生sigmoid公式,而是做了数值稳定性处理,避免大数值输入下的溢出问题
- 计算对数项时加入了1e-8的极小值,防止预测值完全为0或1时出现
log(0)的非法运算 - 梯度计算完全对应推导公式,用numpy矩阵运算替代Python原生循环,执行效率更高
- 全0初始化即可满足训练要求,不需要复杂的参数初始化策略
内容的提问来源于stack exchange,提问作者ashfaq ali
相关产品推荐
相关产品推荐

