Python 3.x中带L2正则化的批量逻辑回归梯度下降实现求助
嘿,我来帮你搞定这个实现!先把你提到的核心公式明确下来(毕竟你说的公式1和2没贴出来,我按标准的「标签y∈{-1,1}、带L2正则化」的逻辑回归来梳理),然后一步步写代码,把每个细节讲清楚。
核心公式回顾
(1)逻辑回归假设函数
因为你的标签是-1和+1,我们用sigmoid函数结合标签定义预测逻辑:
$h_\theta(x) = \sigma(\theta^T x)$,其中$\sigma(z) = \frac{1}{1+e^{-z}}$
分类时等价于:$\theta^T x > 0$则预测+1,否则预测-1,这样计算更高效。
(2)带L2正则化的损失函数
损失包含经验损失(拟合数据)+ L2正则项(防止过拟合),注意偏置项$\theta_0$不参与正则化:
$J(\theta) = \frac{1}{N} \sum_{i=1}^N \log\left(1 + e^{-y_i \theta^T x_i}\right) + \frac{\lambda}{2N} \sum_{j=1}^d \theta_j^2$
这里d是特征维度(你的情况d=2,加偏置后θ是3维),λ是正则化强度。
(3)批量梯度下降的梯度公式
批量GD每次用全量数据计算梯度,参数更新的核心依据:
$\nabla J(\theta) = -\frac{1}{N} \sum_{i=1}^N \frac{y_i x_i}{1 + e^{y_i \theta^T x_i}} + \frac{\lambda}{N} \tilde{\theta}$
其中$\tilde{\theta}$是和θ同维度的向量:$\tilde{\theta}_0=0$,$\tilde{\theta}_j=\theta_j$(j≥1),也就是偏置项的梯度不加正则化。
Python代码实现
用numpy做矩阵运算,效率更高,避免冗余循环:
import numpy as np class LogisticRegressionGD: def __init__(self, learning_rate=0.01, lambda_reg=0.01, max_iter=1000, tol=1e-6): self.learning_rate = learning_rate # 学习率 self.lambda_reg = lambda_reg # L2正则化强度 self.max_iter = max_iter # 最大迭代次数 self.tol = tol # 收敛阈值(损失变化小于该值则停止) self.theta = None # 模型参数 def _sigmoid(self, z): # 解决数值溢出问题的sigmoid实现 return np.where(z >= 0, 1 / (1 + np.exp(-z)), np.exp(z) / (1 + np.exp(z))) def _add_bias(self, X): # 给特征矩阵添加偏置项(第一列全1,对应θ₀) return np.hstack([np.ones((X.shape[0], 1)), X]) def fit(self, X, y): # X: (N, 2)的样本矩阵,y: (N, 1)的标签向量(取值-1或+1) X_bias = self._add_bias(X) N, d = X_bias.shape self.theta = np.zeros((d, 1)) # 初始化参数为全零 prev_loss = float('inf') for i in range(self.max_iter): # 计算线性组合 z = θ^T · X z = np.dot(X_bias, self.theta) # 计算经验损失的梯度部分 gradient = -np.dot(X_bias.T, y * self._sigmoid(-y * z)) / N # 给非偏置项添加L2正则化梯度 gradient[1:] += (self.lambda_reg / N) * self.theta[1:] # 更新参数 self.theta -= self.learning_rate * gradient # 监控收敛:损失变化小于阈值则提前停止 current_loss = self._compute_loss(X_bias, y) if np.abs(current_loss - prev_loss) < self.tol: print(f"提前收敛,迭代次数: {i+1}") break prev_loss = current_loss print("训练完成!") def _compute_loss(self, X_bias, y): # 计算带L2正则化的总损失 z = np.dot(X_bias, self.theta) loss = np.mean(np.log(1 + np.exp(-y * z))) # 正则项只计算非偏置参数 reg_loss = (self.lambda_reg / (2 * len(y))) * np.sum(self.theta[1:] ** 2) return loss + reg_loss def predict(self, X): # 预测类别(-1或+1) X_bias = self._add_bias(X) z = np.dot(X_bias, self.theta) return np.where(z > 0, 1, -1) def predict_proba(self, X): # 可选:预测正类的概率 X_bias = self._add_bias(X) z = np.dot(X_bias, self.theta) return self._sigmoid(z)
关键细节说明
- 数值稳定性:
_sigmoid做了分支处理,避免z过大时exp(-z)下溢、z过小时exp(z)溢出的问题。 - 偏置项处理:特意给特征矩阵加全1列对应偏置参数,正则化时排除它——因为偏置项不会导致过拟合。
- 收敛监控:迭代中计算损失值,当两次迭代的损失差小于阈值时提前停止,避免无效计算。
- 批量GD的特点:每次用全量数据算梯度,比随机GD更稳定,适合样本量不大的场景(你的情况刚好适用)。
使用示例
# 生成模拟测试数据 np.random.seed(42) X = np.random.randn(100, 2) y = np.where(np.dot(X, [1, -2]) + 0.5 > 0, 1, -1).reshape(-1, 1) # 初始化并训练模型 model = LogisticRegressionGD(learning_rate=0.1, lambda_reg=0.001, max_iter=5000) model.fit(X, y) # 预测并计算准确率 y_pred = model.predict(X) accuracy = np.mean(y_pred == y.flatten()) print(f"训练集准确率: {accuracy:.4f}")
常见问题排查
- 模型不收敛:试试调大学习率(比如从0.01调到0.1),或者对X做标准化(
X = (X - X.mean(axis=0)) / X.std(axis=0))——特征尺度差异大会拖慢梯度下降速度。 - 过拟合:增大
lambda_reg的值,增强正则化强度。 - 维度报错:确保y的形状是(N,1),如果是一维数组,用
y.reshape(-1,1)转换。
内容的提问来源于stack exchange,提问作者Mike

