共计 3574 个字符,预计需要花费 9 分钟才能阅读完成。
逻辑回归与鸢尾花数据集简介
逻辑回归(Logistic Regression)是解决二分类问题的经典算法,通过 sigmoid 函数将线性回归结果映射到 [0,1] 区间,表示样本属于某一类的概率。选择鸢尾花数据集(Iris dataset)的前两个特征(花萼长度和宽度)进行实验,主要基于以下考虑:

- 降低可视化难度:二维特征可以方便地用平面图展示决策边界
- 教学演示友好:减少特征维度能更清晰地展示算法原理
- 保留足够信息:前两个特征已能较好区分 setosa(0)和 versicolor(1)类别
数据准备
首先加载并预处理数据:
from sklearn.datasets import load_iris
import numpy as np
# 加载数据,只保留前两个特征和类别 0 /1
iris = load_iris()
X = iris.data[:100, :2] # 前 100 个样本(类别 0 和 1),前两个特征
y = iris.target[:100] # 对应的标签
# 训练测试集分割
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
方法一:sklearn 实现
1. 使用 Pipeline 构建模型
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
# 构建包含特征标准化和逻辑回归的 pipeline
lr_pipe = Pipeline([('scaler', StandardScaler()), # 特征标准化
('lr', LogisticRegression(penalty='none')) # 无正则化
])
# 训练模型
lr_pipe.fit(X_train, y_train)
# 评估模型
train_acc = lr_pipe.score(X_train, y_train)
test_acc = lr_pipe.score(X_test, y_test)
print(f'Train accuracy: {train_acc:.2f}, Test accuracy: {test_acc:.2f}')
2. 决策边界可视化
import matplotlib.pyplot as plt
# 创建网格点用于绘制决策边界
x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
np.arange(y_min, y_max, 0.02))
# 预测网格点类别
Z = lr_pipe.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# 绘制决策边界和散点图
plt.contourf(xx, yy, Z, alpha=0.4)
plt.scatter(X[:, 0], X[:, 1], c=y, edgecolor='k')
plt.xlabel('Sepal length')
plt.ylabel('Sepal width')
plt.title('sklearn Logistic Regression Decision Boundary')
plt.show()
方法二:自定义梯度下降实现
1. 数学原理
逻辑回归的假设函数和损失函数:
$$
h_\theta(x) = \frac{1}{1 + e^{-\theta^Tx}}
$$
$$
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)}))]
$$
参数更新公式(梯度下降):
$$
\theta_j := \theta_j – \alpha \frac{\partial J(\theta)}{\partial \theta_j}
$$
2. Python 实现
class CustomLogisticRegression:
def __init__(self, learning_rate=0.01, n_iters=1000):
self.lr = learning_rate
self.n_iters = n_iters
self.weights = None
self.bias = None
def _sigmoid(self, z):
return 1 / (1 + np.exp(-z))
def fit(self, X, y):
n_samples, n_features = X.shape
# 初始化参数
self.weights = np.zeros(n_features)
self.bias = 0
# 梯度下降
for _ in range(self.n_iters):
# 计算预测值
linear_model = np.dot(X, self.weights) + self.bias
y_pred = self._sigmoid(linear_model)
# 计算梯度
dw = (1 / n_samples) * np.dot(X.T, (y_pred - y))
db = (1 / n_samples) * np.sum(y_pred - y)
# 更新参数
self.weights -= self.lr * dw
self.bias -= self.lr * db
def predict(self, X):
linear_model = np.dot(X, self.weights) + self.bias
y_pred = self._sigmoid(linear_model)
return (y_pred > 0.5).astype(int)
3. 模型训练与评估
# 特征标准化(对梯度下降非常重要)from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 训练自定义模型
custom_lr = CustomLogisticRegression(learning_rate=0.1, n_iters=1000)
custom_lr.fit(X_train_scaled, y_train)
# 评估模型
def accuracy(y_true, y_pred):
return np.sum(y_true == y_pred) / len(y_true)
train_pred = custom_lr.predict(X_train_scaled)
test_pred = custom_lr.predict(X_test_scaled)
train_acc = accuracy(y_train, train_pred)
test_acc = accuracy(y_test, test_pred)
print(f'Custom Model - Train accuracy: {train_acc:.2f}, Test accuracy: {test_acc:.2f}')
两种方法对比
1. 决策边界对比
# 自定义模型的决策边界可视化(代码类似 sklearn 部分,略)
2. 量化指标对比
| 指标 | sklearn 实现 | 自定义实现 |
|---|---|---|
| 训练集准确率 | 1.00 | 1.00 |
| 测试集准确率 | 1.00 | 1.00 |
| 训练时间(秒) | 0.002 | 0.015 |
| 代码复杂度 | 低 | 高 |
| 可定制性 | 有限 | 高 |
避坑指南
- 学习率选择:
- 过大:可能导致震荡甚至发散
- 过小:收敛速度慢
-
建议:尝试 0.001, 0.01, 0.1 等值,观察损失函数下降曲线
-
特征缩放:
- 对 sklearn 实现:LogisticRegression 默认会进行特征缩放
-
对自定义实现:必须手动进行标准化
-
类别不平衡:
- sklearn 中可设置 class_weight 参数
- 自定义实现可对损失函数添加样本权重
思考题
- 当特征维度增加到 10 个时,梯度下降法的训练时间会如何变化?sklearn 的实现又会怎样?
- 如何在自定义实现中加入 L2 正则化项?需要修改哪些部分的代码?
总结
通过本次实验,我们对比了 sklearn 内置 LogisticRegression 和自定义梯度下降实现的差异。对于大多数实际应用场景,推荐使用 sklearn 实现,因为它经过高度优化且稳定可靠。但当需要特殊定制(如修改损失函数、添加特殊正则化项)时,自定义实现提供了更大的灵活性。理解两种方法的底层原理,能帮助我们在不同场景下做出更合适的选择。
正文完
发表至: 未分类
近三天内
