逻辑回归:从Sigmoid函数到Scikit-learn实战,掌握机器学习经典分类模型
1. 从“分类”这个核心需求说起
如果你刚开始接触机器学习,可能会觉得“逻辑回归”这个名字有点误导人。它名字里带着“回归”,干的却是“分类”的活儿。这其实是一个历史遗留问题,它的核心思想确实源于线性回归,但通过一个巧妙的“激活函数”,把原本预测连续值的输出,硬生生地“挤压”成了一个0到1之间的概率值,用来判断“是”或“否”。所以,理解逻辑回归,本质上就是理解机器学习里最经典、最基础的二分类问题怎么解决。
我刚开始学的时候,也犯过迷糊,总想着用线性回归那条直线去硬分两类点,结果发现效果奇差,因为线性回归的预测值可以跑到负无穷到正无穷,根本不适合用来表示概率。直到搞明白了那个关键的“Sigmoid函数”,整个逻辑才通顺了。它就像一个裁判,把线性方程计算出的“得分”送进一个标准化的打分器,输出一个“属于正类的可能性”分数。这个分数如果超过0.5,我们就判它为正类;低于0.5,就判为负类。简单,直观,而且数学性质非常好,这大概就是为什么逻辑回归历经几十年依然是工业界和学术界的宠儿,是无数数据科学项目的“第一基准模型”。
2. 逻辑回归的数学心脏:Sigmoid函数与决策边界
逻辑回归的魔力,几乎全部来自于Sigmoid函数(也叫Logistic函数)。我们得把这个函数掰开揉碎了看,因为它决定了模型如何“思考”。
2.1 Sigmoid函数:从任意值到概率的桥梁
Sigmoid函数的公式长这样: σ(z) = 1 / (1 + e^{-z}) 。这里的 z 就是我们熟悉的线性组合: z = w_1*x_1 + w_2*x_2 + ... + w_n*x_n + b ,也就是权重 w 和特征 x 的点积加上偏置 b 。
这个函数有什么特性呢?你可以把它想象成一个“压扁器”。无论你输入的 z 是正一百万还是负一百万, σ(z) 的输出都会被牢牢地限制在0和1之间。当 z=0 时, σ(z)=0.5 ,这是决策的临界点。当 z 趋向正无穷大时, σ(z) 无限趋近于1,表示“几乎肯定是正类”;当 z 趋向负无穷大时, σ(z) 无限趋近于0,表示“几乎肯定不是正类”。它的图像是一条平滑的、从0增长到1的S形曲线。
注意:这里
e是自然常数。这个平滑且处处可导的特性,是后续使用梯度下降等优化算法的基础。如果它是一个阶跃函数(比如直接以0为界输出0或1),数学上就不好处理了。
2.2 决策边界:模型划下的那条“线”
理解了Sigmoid函数,决策边界就很好理解了。模型最终做决策的规则是:如果 σ(z) >= 0.5 ,则预测为正类(记作1);否则预测为负类(记作0)。由于 σ(z) >= 0.5 等价于 z >= 0 ,所以我们的决策规则实际上简化为:看线性组合 z 是否大于等于0。
那么,在特征空间里,所有使得 z = 0 的点,就构成了一条线(对于二维特征)或一个面(对于高维特征),这就是 决策边界 。它的方程就是 w·x + b = 0 。对于二维情况,这就是一条直线,它将平面分成了两个区域,一侧预测为正类,另一侧预测为负类。
这里有个关键点: 决策边界永远是线性的 ,因为它就是 z=0 这个线性方程定义的。这意味着,逻辑回归本质上是一个线性分类器。它只能学习用一条直线(或平面、超平面)来分割数据。如果实际数据不是线性可分的(比如两类点呈环形分布),那么单纯用逻辑回归效果就会很差。这时就需要引入特征工程,比如构造多项式特征( x1^2 , x1*x2 等),让模型在变换后的特征空间里变得线性可分。
3. 模型如何学习:损失函数与梯度下降
模型有了( σ(z) ),决策规则也有了,接下来最关键的问题是:我们怎么找到那一组最优的权重 w 和偏置 b ?这就是训练过程,核心是定义一个衡量模型预测有多“差”的标尺(损失函数),然后想办法把这个“差”降到最低(优化算法)。
3.1 交叉熵损失函数:为什么是它?
对于分类问题,最直观的损失函数可能是“分类错误率”,但它在数学上不是连续可导的,无法用梯度下降优化。逻辑回归使用的是 交叉熵损失函数 ,它衡量的是模型预测的概率分布与真实标签的概率分布之间的差异。
对于单个样本 (x, y) ,其中 y 是真实标签(0或1), ŷ = σ(z) 是模型预测为正类的概率,其交叉熵损失定义为: L(ŷ, y) = -[y * log(ŷ) + (1-y) * log(1-ŷ)]
我们来仔细品味一下这个公式:
- 当
y=1(真实是正类)时,损失函数简化为L = -log(ŷ)。这意味着,模型预测的概率ŷ越接近1,-log(ŷ)的值就越小(因为log(1)=0),损失越小。如果ŷ预测得很小(比如0.1),那么-log(0.1)就会很大,惩罚就很重。 - 当
y=0(真实是负类)时,损失函数简化为L = -log(1-ŷ)。同理,模型预测ŷ越接近0(即1-ŷ越接近1),损失越小。
这个函数的设计非常巧妙,它对于“自信的错误预测”施加了非常严厉的惩罚。比如模型以99%的概率预测了一个错误答案,其损失会非常大。这符合我们的直觉。
对于整个训练集 m 个样本,我们通常计算平均损失,即 成本函数 J(w,b) : J(w,b) = (1/m) * Σ L(ŷ^{(i)}, y^{(i)})
我们的目标就是找到一组参数 (w,b) ,使得这个平均成本 J 最小。
3.2 梯度下降:沿着最陡的下坡路走
有了要最小化的目标 J ,我们怎么找最小值点呢?想象你站在一个山谷(成本函数曲面)的某个山坡上,蒙着眼睛,想走到谷底。最有效的策略就是感受脚下哪个方向坡度最陡,然后往那个方向下一小步。梯度下降就是这个思路的数学实现。
梯度就是多元函数的导数,它指向函数值增长最快的方向。因此,负梯度方向就是函数值下降最快的方向。参数更新公式如下: w = w - α * (∂J/∂w) b = b - α * (∂J/∂b)
其中 α 是 学习率 ,它控制着我们每一步走多大。学习率太小,收敛会非常慢;学习率太大,可能会在谷底附近震荡,甚至无法收敛。
对于逻辑回归,损失函数关于参数的导数有一个非常简洁优美的形式(经过推导): ∂J/∂w_j = (1/m) * Σ (ŷ^{(i)} - y^{(i)}) * x_j^{(i)} ∂J/∂b = (1/m) * Σ (ŷ^{(i)} - y^{(i)})
你会发现,导数就是所有样本的 预测误差 (ŷ - y) 与对应特征 x_j 的乘积的平均值。这个形式使得计算非常高效。在实际操作中,我们很少自己写这个推导和更新代码,像Scikit-learn这样的库已经高度优化了。但理解这个过程,对于调试模型(比如学习率设置是否合理)、理解更复杂的神经网络反向传播都至关重要。
实操心得:初始化权重时,不要全部设为0。这会导致对称性破坏问题,所有神经元(如果扩展到神经网络)学习到的东西都一样。通常采用小的随机数初始化,比如从均值为0、方差为0.01的正态分布中采样。
4. 从理论到代码:用Python和Scikit-learn实现逻辑回归
理论说得再多,不动手都是空的。我们用一个经典的鸢尾花数据集(Iris)的二分类简化版来走一遍流程。这里假设你已经安装了Python、NumPy、Pandas和Scikit-learn。
4.1 数据准备与探索
首先,我们加载数据,并为了简化,只取其中两个类别(Setosa和Versicolor)以及两个特征(萼片长度和宽度)来构造一个清晰的二分类问题。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
# 加载鸢尾花数据集
iris = datasets.load_iris()
# 取前100个样本(对应Setosa和Versicolor两类),以及第0,1列特征(萼片长宽)
X = iris.data[:100, :2]
y = iris.target[:100]
# 划分训练集和测试集,7:3比例,设置随机种子确保结果可复现
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
print(f"训练集形状: {X_train.shape}, 测试集形状: {X_test.shape}")
4.2 模型训练与核心参数解读
接下来,我们创建逻辑回归模型并训练它。Scikit-learn的 LogisticRegression 封装了大量细节,但我们仍需关注几个关键参数。
# 创建逻辑回归模型实例
# penalty: 正则化类型,'l2'是默认的岭回归,有助于防止过拟合
# C: 正则化强度的倒数,C值越小,正则化越强。默认是1.0。
# solver: 优化算法,对于小数据集,'liblinear'是个好选择。大数据集可用'sag'或'saga'。
# max_iter: 最大迭代次数,确保优化算法能收敛。
model = LogisticRegression(penalty='l2', C=1.0, solver='liblinear', max_iter=100)
model.fit(X_train, y_train)
# 查看学习到的参数
print(f"模型权重 (w): {model.coef_}")
print(f"模型偏置 (b): {model.intercept_}")
这里重点说一下 C 参数和 solver :
-
C参数 :它是正则化项λ的倒数(C = 1/λ)。C越大,正则化越弱,模型越可能拟合训练数据中的噪声,导致过拟合;C越小,正则化越强,模型会更简单,但可能欠拟合。这通常是一个需要通过交叉验证来调优的超参数。 -
solver参数 :指定用于优化问题的算法。liblinear:适用于小数据集,支持L1和L2正则化。lbfgs:默认选项,适用于中小型数据集,对内存友好。sag和saga:随机平均梯度下降,对于样本量很大、特征数也很多的数据集效率更高。saga还支持L1正则化。
4.3 模型评估与可视化
训练好后,我们需要在测试集上评估模型,并可视化它的决策边界。
# 在测试集上进行预测
y_pred = model.predict(X_test)
y_pred_proba = model.predict_proba(X_test) # 获取预测概率
# 评估指标
print("准确率:", accuracy_score(y_test, y_pred))
print("\n分类报告:")
print(classification_report(y_test, y_pred))
print("\n混淆矩阵:")
print(confusion_matrix(y_test, y_pred))
# 可视化决策边界
def plot_decision_boundary(X, y, model):
# 创建网格点
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
h = 0.02 # 网格步长
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
# 预测整个网格
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# 绘制等高线和散点
plt.figure(figsize=(8, 6))
plt.contourf(xx, yy, Z, alpha=0.8, cmap=plt.cm.RdYlBu)
scatter = plt.scatter(X[:, 0], X[:, 1], c=y, edgecolors='k', cmap=plt.cm.RdYlBu)
plt.xlabel(iris.feature_names[0])
plt.ylabel(iris.feature_names[1])
plt.title('逻辑回归决策边界')
plt.legend(*scatter.legend_elements(), title='Classes')
plt.show()
plot_decision_boundary(X_train, y_train, model)
运行这段代码,你会看到一条清晰的直线将两类点分开。这就是我们之前讨论的线性决策边界。准确率应该会很高(接近100%),因为这两类花在这个二维特征空间里是线性可分的。
踩坑记录:
predict_proba返回的是每个样本属于各个类别的概率,对于二分类,其形状是(n_samples, 2),两列之和为1。如果你只需要正类的概率,通常取第二列(model.predict_proba(X)[:, 1])。这在需要设定不同分类阈值(比如不是0.5)的场景下非常有用。
5. 处理多分类问题:从OvR到Softmax
我们之前的例子是二分类。但现实世界的问题常常不止两类,比如鸢尾花数据集本身就有三类。逻辑回归如何扩展到多分类呢?主要有两种策略:
5.1 一对多(One-vs-Rest, OvR)
这是最常用的策略,Scikit-learn默认采用。假设有K个类别,我们会训练K个独立的二分类逻辑回归模型。对于第 i 个模型,我们将类别 i 的样本作为正例,其他所有类别的样本作为负例进行训练。在预测时,将新样本输入这K个模型,得到K个“属于该类别”的概率,然后选择概率最大的那个类别作为最终预测结果。
这种方法简单有效,尤其当类别数K不是特别大时。它的一个潜在问题是,如果类别分布极度不均衡,或者类别间并非完全互斥,每个二分类器的训练数据都会是高度不平衡的(一个类 vs 其他所有类),可能影响性能。
5.2 多项逻辑回归与Softmax函数
另一种更“原生”的方法是直接使用多项逻辑回归,它使用Softmax函数作为输出层的激活函数。Softmax可以看作是Sigmoid函数在多分类上的推广。
对于K个类别,模型会为每个类别 j 计算一个得分 z_j (同样是线性函数: z_j = w_j·x + b_j )。然后,Softmax函数将所有这些得分转化为一个概率分布: P(y=j | x) = e^{z_j} / Σ_{k=1}^{K} e^{z_k}
这样,对于每个样本,模型会输出一个K维的概率向量,所有元素之和为1,其中最大的概率对应的类别就是预测结果。它的损失函数也相应扩展为 多类交叉熵损失 。
在Scikit-learn中,当我们将 LogisticRegression 的 multi_class 参数设置为 'multinomial' ,并配合使用 solver='lbfgs' 或 'saga' 等支持它的算法时,使用的就是这种方法。
# 使用完整的鸢尾花数据集(3类)
X_multi = iris.data[:, :2] # 仍用两个特征便于可视化
y_multi = iris.target
X_train_m, X_test_m, y_train_m, y_test_m = train_test_split(X_multi, y_multi, test_size=0.3, random_state=42)
# 使用多项逻辑回归 (Softmax)
model_multi = LogisticRegression(multi_class='multinomial', solver='lbfgs', max_iter=200)
model_multi.fit(X_train_m, y_train_m)
# 可视化,此时决策边界会是两条直线(两两分类的边界)
plot_decision_boundary(X_train_m, y_train_m, model_multi)
此时的可视化图中,你会看到决策区域被两条直线划分成了三个部分。这印证了逻辑回归的线性本质:在多分类中,它学习的是多个线性边界来划分空间。
6. 特征工程与模型评估的深入思考
逻辑回归作为一个线性模型,其性能极大地依赖于输入的特征。特征工程在这里扮演着比在复杂模型(如树模型、神经网络)中更关键的角色。
6.1 特征缩放:为什么重要?
逻辑回归虽然不像K近邻或支持向量机那样对尺度极度敏感,但进行特征缩放(标准化或归一化)仍然是一个好习惯,尤其是当你使用了正则化时。因为正则化项会对所有权重进行惩罚,如果特征尺度差异巨大,大尺度特征对应的权重自然会被调整得很小,这可能会扭曲特征的真实重要性,也使得优化过程(梯度下降)的路径更加曲折,收敛更慢。
常用的方法有:
- 标准化(Z-Score) :
x' = (x - mean) / std,使特征均值为0,方差为1。这是最常用的方法。 - 归一化(Min-Max) :
x' = (x - min) / (max - min),将特征缩放到[0,1]区间。
在Scikit-learn中,可以使用 StandardScaler 轻松实现。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test) # 注意:使用训练集的均值和方差来转换测试集
# 用缩放后的数据重新训练模型
model_scaled = LogisticRegression()
model_scaled.fit(X_train_scaled, y_train)
# 通常能获得更稳定、有时更好的性能
6.2 评估指标:不止看准确率
在分类任务中,准确率(Accuracy)是最直观的指标,但在类别不平衡的数据集上,它可能具有极大的误导性。例如,在一个99%是负例,1%是正例的疾病检测数据集中,一个把所有样本都预测为负例的“笨模型”,准确率也能达到99%,但它完全检测不出病人。
因此,我们需要更细致的评估工具:
- 混淆矩阵 :真正例(TP)、假正例(FP)、真负例(TN)、假负例(FN)。这是一切评估指标的基础。
- 精确率(Precision) :
TP / (TP + FP)。在所有预测为正的样本中,有多少是真的正例。关注预测的“准确性”。 - 召回率(Recall) :
TP / (TP + FN)。在所有真实为正的样本中,我们找出了多少。关注模型的“查全率”。 - F1-Score :精确率和召回率的调和平均数,
2 * (Precision * Recall) / (Precision + Recall)。是两者之间的一个平衡。 - ROC曲线与AUC :通过不断调整分类阈值,计算真正例率(TPR)和假正例率(FPR)绘制的曲线。曲线下的面积(AUC)衡量模型整体排序能力的好坏,AUC越接近1越好,0.5相当于随机猜测。
对于逻辑回归,我们可以通过调整 predict 函数背后的阈值(默认0.5)来在精确率和召回率之间进行权衡。提高阈值,预测为正类更严格,精确率上升,召回率下降;降低阈值则相反。
from sklearn.metrics import precision_recall_curve, roc_curve, auc
# 获取预测为正类的概率
y_scores = model.predict_proba(X_test)[:, 1]
# 计算ROC曲线
fpr, tpr, thresholds_roc = roc_curve(y_test, y_scores)
roc_auc = auc(fpr, tpr)
# 计算精确率-召回率曲线
precision, recall, thresholds_pr = precision_recall_curve(y_test, y_scores)
# 绘制图形
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].plot(fpr, tpr, label=f'ROC curve (AUC = {roc_auc:.2f})')
axes[0].plot([0, 1], [0, 1], 'k--') # 随机猜测线
axes[0].set_xlabel('False Positive Rate')
axes[0].set_ylabel('True Positive Rate')
axes[0].set_title('ROC Curve')
axes[0].legend()
axes[1].plot(recall, precision)
axes[1].set_xlabel('Recall')
axes[1].set_ylabel('Precision')
axes[1].set_title('Precision-Recall Curve')
plt.show()
7. 正则化:对抗过拟合的利器
当特征很多,或者某些特征与标签存在复杂的非线性关系而我们强行用线性模型去拟合时,就容易发生过拟合。模型在训练集上表现极好,但在未见过的测试集上表现糟糕。正则化是解决过拟合的核心技术之一,它在损失函数中增加一个惩罚项,用来约束模型权重的大小。
逻辑回归常用的正则化有两种:
- L1正则化(Lasso) :在损失函数中加入权重绝对值的和(
λ * Σ|w_i|)。它倾向于产生稀疏的权重向量,即让许多特征的权重直接变为0。这相当于进行了一种特征选择,模型只保留最重要的特征。 - L2正则化(Ridge) :在损失函数中加入权重平方的和(
λ * Σw_i^2)。它倾向于让所有权重都变小,但不会精确为0。这使得模型对所有特征都有所考虑,但影响力被平滑地削弱。
在Scikit-learn的 LogisticRegression 中,通过 penalty 参数指定( 'l1' 或 'l2' ),正则化强度由 C 参数控制( C 越小,正则化越强)。需要注意的是,不是所有的 solver 都支持L1正则化, 'liblinear' 和 'saga' 是常用的支持L1的选择。
# 比较不同正则化强度的影响
C_values = [0.001, 0.01, 0.1, 1, 10, 100]
train_acc = []
test_acc = []
for C in C_values:
model_reg = LogisticRegression(C=C, penalty='l2', solver='liblinear', max_iter=1000)
model_reg.fit(X_train_scaled, y_train)
train_acc.append(model_reg.score(X_train_scaled, y_train))
test_acc.append(model_reg.score(X_test_scaled, y_test))
plt.figure(figsize=(8,5))
plt.plot(C_values, train_acc, 'o-', label='Training Accuracy')
plt.plot(C_values, test_acc, 's-', label='Test Accuracy')
plt.xscale('log') # C值跨度大,用对数坐标
plt.xlabel('C (Inverse of regularization strength)')
plt.ylabel('Accuracy')
plt.title('Effect of Regularization Strength on Performance')
plt.legend()
plt.grid(True)
plt.show()
运行这段代码,你通常会观察到:当 C 很大(正则化很弱)时,模型可能过拟合,训练准确率很高但测试准确率较低;随着 C 减小(正则化增强),训练准确率会下降,但测试准确率可能先上升后下降,中间存在一个最优的 C 值使得测试性能最好。这个最优值需要通过交叉验证来寻找。
8. 逻辑回归的局限与进阶方向
尽管逻辑回归强大且实用,但我们必须清楚它的边界。
核心局限:线性 。这是它最大的“天花板”。它只能学习特征与对数几率之间的线性关系。对于像“异或”(XOR)这样简单的非线性问题,单层逻辑回归完全无能为力。解决这个问题主要有两条路:
- 特征工程 :手动或自动地构造非线性特征,如多项式特征、交互项等,将数据映射到更高维的空间,使其在新空间中线性可分。这是传统机器学习中非常关键的一步。
- 核方法 :通过核函数隐式地将数据映射到高维空间,但计算仍在原空间进行。这在支持向量机中很常见,逻辑回归也有核化版本,但计算成本较高。
- 转向非线性模型 :当问题非常复杂时,直接使用决策树、随机森林、梯度提升树(如XGBoost)或神经网络等非线性模型可能是更直接有效的选择。
另一个实践中的常见问题是处理不平衡数据 。逻辑回归本身对类别不平衡比较敏感,因为它的目标是最大化整体概率的似然,多数类会主导损失函数。处理方法包括:
- 在
LogisticRegression中设置class_weight='balanced',让算法自动调整类别权重。 - 使用上采样(如SMOTE)或下采样来人工平衡训练数据。
- 使用更适合的评估指标(如AUC-PR),而不是准确率。
逻辑回归的“线性”特质,既是它的弱点,也是它的优点。它简单、高效、可解释性强。我们可以轻松地查看每个特征对应的权重系数,理解特征对预测结果的贡献方向和大小(前提是特征已经标准化)。这种可解释性在金融风控、医疗诊断等需要模型提供决策依据的领域,是无比珍贵的。
从我个人的经验来看,逻辑回归永远应该是你尝试解决分类问题的第一个模型。它为你建立了一个性能基准。如果它的表现已经足够好,那么恭喜你,你得到了一个快速、可解释且稳定的方案。如果表现不佳,那么它揭示的问题(是线性假设不成立?还是特征不够好?)会为你后续选择更复杂的模型提供明确的方向。在追求酷炫的深度学习之前,先把逻辑回归及其背后的逻辑吃透,绝对是稳赚不赔的投资。
更多推荐


所有评论(0)