共计 3300 个字符,预计需要花费 9 分钟才能阅读完成。
背景与痛点
逻辑回归是机器学习中最基础的分类算法之一,它通过 sigmoid 函数将线性回归的输出映射到 (0,1) 区间,从而解决二分类问题。虽然原理简单,但初学者常常面临一个困惑:是直接使用 Sklearn 等库提供的现成实现,还是自己动手编写梯度下降等优化算法?

前者虽然方便,但容易成为 ” 黑箱 ”,不利于深入理解算法本质;后者虽然能加深理解,但实现过程中又会遇到各种细节问题。本文将通过鸢尾花数据集的二分类任务,对比这两种实现方式,帮助读者做出更明智的选择。
数据预处理
首先我们需要对原始数据进行处理,只保留前两个特征和类别 0、1 的数据:
from sklearn.datasets import load_iris
import numpy as np
# 加载数据
iris = load_iris()
X = iris.data[:, :2] # 只取前两个特征
Y = iris.target
# 只保留类别 0 和 1
mask = (Y == 0) | (Y == 1)
X = X[mask]
Y = Y[mask]
# 数据标准化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
Sklearn 实现
Sklearn 的 LogisticRegression 类提供了高度优化的逻辑回归实现,使用起来非常简单:
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
# 划分训练测试集
X_train, X_test, y_train, y_test = train_test_split(X_scaled, Y, test_size=0.2, random_state=42)
# 创建并训练模型
sk_model = LogisticRegression()
sk_model.fit(X_train, y_train)
# 评估模型
sk_score = sk_model.score(X_test, y_test)
print(f"Sklearn 模型准确率: {sk_score:.4f}")
Sklearn 实现的主要优势在于:
- 自动处理了很多底层细节(如学习率调整、迭代终止条件等)
- 提供了丰富的参数调优选项
- 计算效率高,适合大型数据集
自定义梯度下降实现
为了深入理解逻辑回归的工作原理,我们手动实现梯度下降算法。首先回顾几个关键数学概念:
-
Sigmoid 函数:将线性输出转换为概率
$$\sigma(z) = \frac{1}{1+e^{-z}}$$ -
损失函数(对数似然损失):
$$J(\theta) = -\frac{1}{m}\sum_{i=1}^m [y^{(i)}\log(h_\theta(x^{(i)})) + (1-y^{(i)})\log(1-h_\theta(x^{(i)}))]$$ -
梯度计算:
$$\frac{\partial J(\theta)}{\partial \theta_j} = \frac{1}{m}\sum_{i=1}^m (h_\theta(x^{(i)})-y^{(i)})x_j^{(i)}$$
基于这些公式,我们可以实现自定义逻辑回归:
class CustomLogisticRegression:
def __init__(self, lr=0.01, n_iter=1000):
self.lr = lr
self.n_iter = n_iter
def sigmoid(self, z):
return 1 / (1 + np.exp(-z))
def fit(self, X, y):
m, n = X.shape
self.theta = np.zeros(n)
self.loss_history = []
for _ in range(self.n_iter):
z = np.dot(X, self.theta)
h = self.sigmoid(z)
gradient = np.dot(X.T, (h - y)) / m
self.theta -= self.lr * gradient
# 计算损失
loss = (-y * np.log(h) - (1-y)*np.log(1-h)).mean()
self.loss_history.append(loss)
return self
def predict(self, X):
return (self.sigmoid(np.dot(X, self.theta)) >= 0.5).astype(int)
# 训练自定义模型
custom_model = CustomLogisticRegression(lr=0.1, n_iter=1000)
custom_model.fit(X_train, y_train)
# 评估
custom_score = (custom_model.predict(X_test) == y_test).mean()
print(f"自定义模型准确率: {custom_score:.4f}")
模型对比与可视化
我们可以通过多种方式对比两个模型的性能:
- 准确率对比:
- Sklearn 模型: 1.0
-
自定义模型: 1.0
-
决策边界可视化:
import matplotlib.pyplot as plt
# 创建网格点
x_min, x_max = X_scaled[:, 0].min() - 1, X_scaled[:, 0].max() + 1
y_min, y_max = X_scaled[:, 1].min() - 1, X_scaled[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
np.arange(y_min, y_max, 0.02))
# 预测每个网格点
Z_sk = sk_model.predict(np.c_[xx.ravel(), yy.ravel()])
Z_sk = Z_sk.reshape(xx.shape)
Z_custom = custom_model.predict(np.c_[xx.ravel(), yy.ravel()])
Z_custom = Z_custom.reshape(xx.shape)
# 绘制决策边界
plt.figure(figsize=(12, 5))
plt.subplot(121)
plt.contourf(xx, yy, Z_sk, alpha=0.4)
plt.scatter(X_scaled[:, 0], X_scaled[:, 1], c=Y, edgecolor='k')
plt.title('Sklearn 决策边界')
plt.subplot(122)
plt.contourf(xx, yy, Z_custom, alpha=0.4)
plt.scatter(X_scaled[:, 0], X_scaled[:, 1], c=Y, edgecolor='k')
plt.title('自定义模型决策边界')
plt.tight_layout()
plt.show()
- 损失函数收敛过程:
plt.plot(custom_model.loss_history)
plt.xlabel('迭代次数')
plt.ylabel('损失值')
plt.title('自定义模型损失函数收敛过程')
plt.show()
避坑指南
在实现自定义逻辑回归时,有几个常见陷阱需要注意:
- 学习率选择:
- 过大:可能导致损失函数震荡甚至发散
- 过小:收敛速度慢
-
建议:从 0.01 开始尝试,观察损失函数变化
-
特征缩放:
- 梯度下降对特征尺度敏感
-
务必进行标准化处理(如我们前面所做)
-
迭代终止:
- 可以设置损失变化阈值或最大迭代次数
- 观察损失曲线判断是否收敛
延伸思考
读者可以尝试以下扩展实验:
- 使用所有 4 个特征进行训练,观察性能变化
- 实现 L1/L2 正则化版本
- 尝试不同的优化算法(如随机梯度下降、Adam 等)
- 处理多分类问题(使用 softmax 回归)
总结
通过这次实验,我们对比了 Sklearn 和自定义实现的逻辑回归。对于实际项目,如果追求开发效率和稳定性,Sklearn 是更好的选择;而如果想深入理解算法原理,自己实现一遍梯度下降会很有帮助。无论哪种方式,理解背后的数学原理都是至关重要的。
