逻辑回归实战:基于鸢尾花数据集的两种实现方式对比(sklearn vs 自定义梯度下降)

1次阅读
没有评论

共计 3594 个字符,预计需要花费 9 分钟才能阅读完成。

image.webp

背景与痛点

逻辑回归是机器学习中最基础的二元分类算法,尽管名字里有 ” 回归 ”,但它实际上是通过 Sigmoid 函数将线性回归结果映射到 [0,1] 区间,实现概率预测。很多初学者在使用时直接调用 sklearn 的LogisticRegression,虽然能快速得到结果,但往往对算法原理、参数调优等缺乏深入理解。

逻辑回归实战:基于鸢尾花数据集的两种实现方式对比(sklearn vs 自定义梯度下降)

鸢尾花数据集是经典的机器学习入门数据集,包含 150 个样本,每个样本有 4 个特征。为了简化问题,我们只使用前两个特征(花萼长度和宽度),并且只保留类别 0 和 1(Setosa 和 Versicolor),这样可以将问题转化为清晰的二维空间可视化问题。

数据预处理

首先我们需要加载数据并进行预处理:

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 实现逻辑回归非常简单:

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)

# 创建模型
# penalty 参数指定正则化类型,'l2' 表示 L2 正则化,'none' 表示不使用正则化
model = LogisticRegression(penalty='l2', random_state=42)

# 训练模型
model.fit(X_train, y_train)

# 预测
sklearn_pred = model.predict(X_test)

自定义梯度下降实现

为了更好理解逻辑回归的原理,我们手动实现梯度下降算法:

# 定义 Sigmoid 函数
def sigmoid(z):
    return 1 / (1 + np.exp(-z))

# 定义损失函数(对数损失)def compute_loss(y, y_pred):
    return -np.mean(y * np.log(y_pred) + (1-y) * np.log(1-y_pred))

# 初始化参数
np.random.seed(42)
theta = np.random.randn(3)  # 两个特征加一个偏置项
X_with_bias = np.c_[np.ones((len(X_train), 1)), X_train]  # 添加偏置项

# 梯度下降参数
learning_rate = 0.1
n_iterations = 1000
loss_history = []

# 梯度下降
for iteration in range(n_iterations):
    z = np.dot(X_with_bias, theta)
    y_pred = sigmoid(z)

    # 计算梯度
    gradients = np.dot(X_with_bias.T, (y_pred - y_train)) / len(y_train)

    # 更新参数
    theta -= learning_rate * gradients

    # 记录损失
    loss = compute_loss(y_train, y_pred)
    loss_history.append(loss)

    # 提前终止条件
    if len(loss_history) > 1 and np.abs(loss_history[-1] - loss_history[-2]) < 1e-6:
        break

# 预测函数
def predict(X):
    X_with_bias = np.c_[np.ones((len(X), 1)), X]
    return (sigmoid(np.dot(X_with_bias, theta)) > 0.5).astype(int)

custom_pred = predict(X_test)

模型对比与评估

我们可以通过多个角度来比较两种实现方式:

  1. 准确率对比
from sklearn.metrics import accuracy_score

print(f"sklearn 准确率: {accuracy_score(y_test, sklearn_pred):.4f}")
print(f"自定义实现准确率: {accuracy_score(y_test, custom_pred):.4f}")
  1. 决策边界可视化
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap

# 定义绘制决策边界的函数
def plot_decision_boundary(X, y, model, title):
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.01),
                         np.arange(y_min, y_max, 0.01))

    if hasattr(model, 'predict'):  # sklearn 模型
        Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    else:  # 自定义模型
        Z = predict(np.c_[xx.ravel(), yy.ravel()])

    Z = Z.reshape(xx.shape)

    plt.figure(figsize=(10, 6))
    plt.contourf(xx, yy, Z, alpha=0.4, cmap=ListedColormap(['#FFAAAA', '#AAAAFF']))
    plt.scatter(X[:, 0], X[:, 1], c=y, s=20, edgecolor='k', cmap=ListedColormap(['#FF0000', '#0000FF']))
    plt.title(title)
    plt.xlabel('花萼长度')
    plt.ylabel('花萼宽度')
    plt.show()

plot_decision_boundary(X_test, y_test, model, "sklearn 决策边界")
plot_decision_boundary(X_test, y_test, predict, "自定义实现决策边界")
  1. 训练过程损失曲线
plt.figure(figsize=(10, 6))
plt.plot(loss_history)
plt.xlabel('迭代次数')
plt.ylabel('损失值')
plt.title('自定义实现的训练损失曲线')
plt.grid(True)
plt.show()

关键知识点解析

  1. 特征选择的重要性

我们故意只选择了前两个特征,这样可以将数据可视化在二维平面上,便于理解。实际上,加入更多特征通常会提高模型性能,但也会增加复杂度。

  1. 梯度下降的关键参数

  2. 学习率(learning_rate): 太大可能导致震荡或发散,太小则收敛缓慢

  3. 迭代次数(n_iterations): 需要足够多,但也要设置提前终止条件
  4. 初始化(theta): 随机初始化可以打破对称性

  5. 正则化选择

sklearn 默认使用 L2 正则化(penalty=’l2′),这有助于防止过拟合,特别是在特征较多的情况下。

避坑指南

  1. 学习率设置

如果发现损失值波动很大或不降反升,可能是学习率太大。可以尝试减小学习率,如从 0.1 降到 0.01。

  1. 特征标准化

梯度下降对特征的尺度很敏感,因此我们使用了 StandardScaler 进行标准化。如果不标准化,不同特征的梯度大小差异很大,导致收敛困难。

  1. 迭代终止条件

除了固定迭代次数,我们还设置了提前终止条件:当损失变化很小时停止训练。这可以避免不必要的计算。

拓展思考

  1. 挑战题:尝试增加第三个特征(花瓣长度),观察模型性能的变化
  2. 思考题:为什么 sklearn 默认使用 L2 正则化而不是 L1?
  3. 进阶题:尝试实现带 L2 正则化的自定义梯度下降

总结

通过这次实验,我们对比了 sklearn 和自定义实现的逻辑回归。sklearn 实现简单快捷,适合快速原型开发;自定义实现虽然麻烦,但能深入理解算法原理。在实际项目中,通常先用 sklearn 快速验证想法,再根据需求考虑自定义实现。

完整的代码和更多可视化结果可以在我的 GitHub 仓库中找到。希望这篇文章能帮助你更好地理解逻辑回归的实现原理!

正文完
 0
评论(没有评论)