逻辑回归:从Sigmoid函数到梯度下降,手把手实现分类模型 1. 从分类难题到逻辑回归为什么我们需要它在数据分析、机器学习乃至很多业务场景里分类问题无处不在。比如银行要判断一笔贷款申请是否会违约是/否电商平台要预测用户是否会点击某个商品点击/不点击医生需要根据检查指标判断患者是否患病阳性/阴性。这些问题都有一个共同点我们想要预测的结果是一个离散的类别而不是一个连续的数值。最初人们很自然地会想到用线性回归来解决。给每个特征乘以一个权重加上一个偏置项得到一个连续的预测值然后设定一个阈值比如0.5高于阈值算一类低于阈值算另一类。听起来很合理对吧但实际操作中这条路很快就走不通了。线性回归的输出值域是整个实数范围-∞, ∞而我们希望得到的概率值应该在0到1之间。更糟糕的是线性回归对异常值非常敏感并且其预测值无法直接解释为“属于某一类的概率”。强行使用线性回归做分类得到的“概率”可能小于0或大于1这显然不符合常识。这时逻辑回归Logistic Regression就登场了。别被它的名字误导它虽然叫“回归”但本质上是一个经典的分类模型尤其是二分类问题。它的核心思想非常巧妙既然线性回归的输出范围不对那我们就把线性回归的结果“压缩”一下映射到(0,1)这个区间内。这个“压缩”函数就是大名鼎鼎的Sigmoid函数也叫Logistic函数。所以逻辑回归可以看作是在线性回归的基础上套了一个Sigmoid激活函数。线性部分z w₁x₁ w₂x₂ ... wₙxₙ b负责学习特征与结果的对数几率关系而Sigmoid函数负责将这个关系转化为一个优雅的、介于0和1之间的概率值。这个概率直观地表示了样本属于正类通常标记为1的可能性。对于任何刚入门机器学习的朋友来说逻辑回归都是你必须深刻理解的第一个分类器它概念清晰、可解释性强并且是很多更复杂模型如神经网络的基础构件。2. Sigmoid函数概率的“压缩器”与模型的数学核心要理解逻辑回归必须彻底搞懂Sigmoid函数。它的数学形式很简单σ(z) 1 / (1 e^{-z})其中z就是我们线性组合的结果z wᵀx b。w是权重向量x是特征向量b是偏置项。这个函数有什么魔力我们可以从几个角度来看首先看它的图形。当z趋向于正无穷大时e^{-z}趋向于0因此σ(z)趋向于1。当z趋向于负无穷大时e^{-z}趋向于正无穷大因此σ(z)趋向于0。函数曲线是一条从0平滑增长到1的S形曲线以点(0, 0.5)为中心对称。这意味着线性部分z的值决定了概率σ(z)的大小。z越大属于正类的概率越高z越小越负属于正类的概率越低。当z0时概率恰好为0.5这是决策的边界。其次看它的输出解释。逻辑回归模型的直接输出是P(y1 |x;w, b) σ(wᵀx b)。这个条件概率的物理意义非常明确在给定特征x和模型参数w, b的条件下样本标签y为1的概率。例如在金融风控中这个输出可以解释为“该客户违约的概率”在医疗诊断中可以解释为“该检查结果呈阳性的概率”。这种直接的概率输出是逻辑回归最大的优势之一使得模型的决策过程具有可解释性。最后看它与“几率”的关系。几率Odds定义为事件发生概率与不发生概率的比值Odds P / (1-P)。逻辑回归的线性部分z实际上是在预测“对数几率”Log-Odds也称为Logit。让我们推导一下 P σ(z) 1 / (1 e^{-z}) 那么1 - P e^{-z} / (1 e^{-z}) 因此几率 Odds P / (1-P) (1 / (1e^{-z})) / (e^{-z} / (1e^{-z})) e^{z} 两边取自然对数就得到ln(Odds) z wᵀx b这个等式至关重要它意味着逻辑回归模型假设特征的对数几率是特征的线性组合。权重wᵢ的大小和正负直接反映了特征xᵢ对“对数几率”的贡献程度。例如如果wᵢ是正的那么xᵢ增加会使对数几率增加从而使属于正类的概率P增加。这为我们分析特征重要性提供了直接的数学依据。注意Sigmoid函数求导有一个非常好的性质σ‘(z) σ(z) * (1 - σ(z))。这个性质在后续的参数估计梯度计算中会大大简化运算是逻辑回归能够高效求解的关键之一。3. 交叉熵损失函数衡量预测与现实的差距模型有了带Sigmoid的线性函数接下来我们需要一个标准来评价模型预测的好坏并指导我们找到最好的参数w, b。这个标准就是损失函数Loss Function。对于分类问题最常用且最适合逻辑回归的损失函数是交叉熵损失Cross-Entropy Loss。为什么不用线性回归里的均方误差MSE因为MSE损失函数在用于逻辑回归的Sigmoid输出时会变成一个非凸函数。非凸函数有多个局部极小值点使用梯度下降等优化方法时很容易陷入一个不好的局部最优解而找不到全局最优。交叉熵损失函数则是凸函数能保证我们通过梯度下降找到全局最优解在逻辑回归的语境下。对于单个样本(x⁽ⁱ⁾, y⁽ⁱ⁾)其中y⁽ⁱ⁾是真实标签0或1模型预测概率为ŷ⁽ⁱ⁾ σ(z⁽ⁱ⁾)。交叉熵损失定义为L(ŷ⁽ⁱ⁾, y⁽ⁱ⁾) - [ y⁽ⁱ⁾ log(ŷ⁽ⁱ⁾) (1 - y⁽ⁱ⁾) log(1 - ŷ⁽ⁱ⁾) ]这个公式直观上理解就是对于真实标签为1的样本我们希望预测概率ŷ越接近1越好此时 -log(ŷ) 越小对于真实标签为0的样本我们希望预测概率ŷ越接近0越好此时 -log(1-ŷ) 越小。任何预测概率偏离真实标签的情况都会导致损失值增大。对于包含m个样本的整个训练集我们的目标是最小化所有样本损失的平均值即成本函数Cost FunctionJ(w, b) (1/m) * Σᵢ₌₁ᵐ L(ŷ⁽ⁱ⁾, y⁽ⁱ⁾) -(1/m) * Σᵢ₌₁ᵐ [ y⁽ⁱ⁾ log(ŷ⁽ⁱ⁾) (1 - y⁽ⁱ⁾) log(1 - ŷ⁽ⁱ⁾) ]我们的任务就是找到一组参数(w, b)使得这个成本函数J的值最小。这个过程就是参数估计。4. 最大似然估计参数估计的统计视角从统计学的角度看逻辑回归的参数估计可以通过最大似然估计Maximum Likelihood Estimation, MLE来理解并且最终会导出与最小化交叉熵损失完全等价的形式。这个视角能帮助我们更深刻地理解模型。我们假设每个样本的预测服从伯努利分布因为结果是二元的。对于参数为θ(w,b)的模型给定特征x样本标签y为1的概率是ŷ P(y1|x;θ)为0的概率是1-ŷ。那么这个样本的似然函数Likelihood可以写成P(y|x;θ) ŷ^y * (1-ŷ)^(1-y)。你可以验证一下当y1时上式等于ŷ当y0时上式等于1-ŷ。这完美地统一了两种情况。对于m个独立同分布的样本整个数据集的似然函数就是所有样本似然的乘积 L(θ) Πᵢ₌₁ᵐ P(y⁽ⁱ⁾|x⁽ⁱ⁾;θ) Πᵢ₌₁ᵐ (ŷ⁽ⁱ⁾)^(y⁽ⁱ⁾) * (1-ŷ⁽ⁱ⁾)^(1-y⁽ⁱ⁾)最大似然估计的思想是找到一组参数θ使得当前观测到的这组数据y⁽ⁱ⁾出现的可能性似然最大。直接连乘计算和优化比较麻烦我们通常取其对数得到对数似然函数因为对数函数是单调的最大化似然等价于最大化对数似然ℓ(θ) log L(θ) Σᵢ₌₁ᵐ [ y⁽ⁱ⁾ log(ŷ⁽ⁱ⁾) (1 - y⁽ⁱ⁾) log(1 - ŷ⁽ⁱ⁾) ]看到这里是不是很眼熟对比上一节的成本函数J(θ) -(1/m) * ℓ(θ)。最大化对数似然函数ℓ(θ)完全等价于最小化交叉熵损失函数J(θ)。因此从MLE角度我们寻找的是最可能产生现有数据的模型参数从优化角度我们寻找的是让预测分布与真实分布差异最小的参数。两者殊途同归。实操心得理解MLE对于诊断模型问题很有帮助。如果模型效果很差从MLE角度可以思考是不是我们的模型假设特征的对数几率是线性的本身就不符合数据的真实生成过程这引出了特征工程的重要性比如可能需要引入特征交叉项或多项式特征。5. 梯度下降通往最优参数的迭代之路现在目标明确了最小化成本函数 J(w, b)。由于逻辑回归的损失函数是凸的我们可以使用梯度下降Gradient Descent及其变种来求解。梯度下降的核心思想非常直观想象你站在一座山上成本函数曲面想要以最快的速度下到山谷最小值点。你环顾四周找到最陡峭的下山方向负梯度方向然后朝那个方向走一小步学习率。重复这个过程直到你走到最低点。我们需要计算成本函数J关于每个参数wⱼ和b的偏导数梯度。这里直接给出推导结果推导过程会用到Sigmoid函数的导数性质令 z⁽ⁱ⁾ wᵀx⁽ⁱ⁾ b, ŷ⁽ⁱ⁾ σ(z⁽ⁱ⁾)对于单个样本损失函数L对z的导数为dL/dz ŷ - y 这是一个非常简洁优美的结果那么根据链式法则∂L/∂wⱼ (∂L/∂z) * (∂z/∂wⱼ) (ŷ - y) * xⱼ∂L/∂b (∂L/∂z) * (∂z/∂b) ŷ - y对于整个训练集成本函数J的梯度就是所有样本梯度的平均值∂J/∂wⱼ (1/m) * Σᵢ₌₁ᵐ (ŷ⁽ⁱ⁾ - y⁽ⁱ⁾) * xⱼ⁽ⁱ⁾ ∂J/∂b (1/m) * Σᵢ₌₁ᵐ (ŷ⁽ⁱ⁾ - y⁽ⁱ⁾)有了梯度梯度下降的参数更新公式就出来了同时更新所有参数wⱼ : wⱼ - α * (∂J/∂wⱼ) b : b - α * (∂J/∂b)其中α是一个超参数称为学习率Learning Rate它控制着我们每一步走多大。学习率太小收敛速度会非常慢学习率太大可能会在最小值点附近震荡甚至发散无法收敛。在实际应用中我们很少使用标准的“批量梯度下降”每次更新都用全部数据计算梯度因为当数据量巨大时计算一次梯度的开销太大。更常用的是随机梯度下降SGD每次随机使用一个样本计算梯度并更新参数。更新速度快但梯度方向波动大。小批量梯度下降Mini-batch GD每次使用一个小的、随机抽取的样本子集比如32、64、128个样本计算梯度。这是深度学习中的标配在速度和稳定性之间取得了很好的平衡。踩坑记录学习率的设置是个艺术。我常用的策略是从一个较大的值如0.1开始尝试如果发现损失函数在训练过程中震荡或爆炸变成NaN就调小一个数量级如0.01。更高级的方法是使用学习率衰减随着训练步数增加逐渐减小α或自适应优化器如Adam它们能自动调整每个参数的学习率通常收敛更快、更稳定。6. 从理论到代码手撕一个逻辑回归模型理解了所有原理之后最好的巩固方式就是亲手实现一遍。下面我们用Python和NumPy来从头构建一个逻辑回归模型并配上详细的注释。我们会实现训练梯度下降和预测两个核心方法。import numpy as np class LogisticRegressionFromScratch: 从零实现逻辑回归 def __init__(self, learning_rate0.01, n_iters1000): 初始化模型 :param learning_rate: 学习率 :param n_iters: 梯度下降迭代次数 self.lr learning_rate self.n_iters n_iters self.weights None self.bias None def _sigmoid(self, z): Sigmoid激活函数防止溢出 # 对z进行裁剪防止exp(-z)过大导致溢出 z np.clip(z, -500, 500) return 1 / (1 np.exp(-z)) def fit(self, X, y): 使用梯度下降训练模型 :param X: 训练特征形状 (n_samples, n_features) :param y: 训练标签形状 (n_samples,) n_samples, n_features X.shape # 1. 参数初始化 # 权重初始化为小随机数偏置初始化为0是常见做法 self.weights np.random.randn(n_features) * 0.01 self.bias 0 # 2. 梯度下降迭代 for i in range(self.n_iters): # 前向传播计算线性组合和预测概率 linear_model np.dot(X, self.weights) self.bias # z w^T x b y_pred self._sigmoid(linear_model) # ŷ σ(z) # 计算梯度 # dw (1/m) * X^T (ŷ - y) # db (1/m) * sum(ŷ - y) 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 # 可选每100次迭代打印一次损失用于监控训练过程 if i % 100 0: # 计算交叉熵损失 loss -np.mean(y * np.log(y_pred 1e-15) (1 - y) * np.log(1 - y_pred 1e-15)) print(fIteration {i}, Loss: {loss:.4f}) def predict_proba(self, X): 预测属于正类y1的概率 linear_model np.dot(X, self.weights) self.bias return self._sigmoid(linear_model) def predict(self, X, threshold0.5): 根据阈值进行类别预测 :param threshold: 决策阈值默认0.5 :return: 预测的类别标签 (0 or 1) proba self.predict_proba(X) return (proba threshold).astype(int) # 示例使用鸢尾花数据集进行二分类将类别0和1作为两类 from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 加载数据只取前两类Setosa和Versicolor做二分类 iris datasets.load_iris() X iris.data[:100, :2] # 只取前100个样本前两个特征便于可视化 y iris.target[:100] # 数据标准化梯度下降对特征的尺度敏感标准化可以加速收敛 scaler StandardScaler() X_scaled scaler.fit_transform(X) # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split(X_scaled, y, test_size0.2, random_state42) # 创建并训练模型 model LogisticRegressionFromScratch(learning_rate0.1, n_iters1000) model.fit(X_train, y_train) # 在测试集上预测 y_pred model.predict(X_test) print(Test predictions:, y_pred) print(True labels:, y_test) print(Accuracy:, np.mean(y_pred y_test))这段代码清晰地展示了逻辑回归训练的核心循环前向传播计算预测值 - 计算梯度 - 更新参数。np.clip操作是为了防止在计算exp(-z)时遇到极大的数值导致溢出。在计算对数损失时我们给log函数输入加了一个极小的数1e-15这是为了防止预测概率恰好为0或1时对数值为负无穷大-inf导致计算错误。7. 特征工程与TF-IDF当逻辑回归遇上文本分类网络热词中提到了“tf-idf和逻辑回归做分类”这指向了逻辑回归一个非常经典且强大的应用场景文本分类。比如新闻分类、情感分析、垃圾邮件过滤等。文本数据是非结构化的不能直接扔给逻辑回归模型。我们需要通过特征工程将其转化为数值型特征向量。TF-IDF正是文本特征提取中最常用、最有效的方法之一。TF-IDF代表“词频-逆文档频率”。它评估一个词对于一个文档集或一个语料库中的其中一份文档的重要程度。词频TF一个词在当前文档中出现的频率。频率越高通常认为它对该文档越重要。但像“的”、“是”这样的停用词在所有文档频率都很高却没什么区分度。逆文档频率IDF衡量一个词的普遍重要性。如果一个词在语料库的很多文档中都出现那么它的IDF值就低比如“的”因为它缺乏区分不同文档的能力。反之如果一个词只在少数文档中出现它的IDF值就高可能是关键主题词。TF-IDF值 TF * IDF。通过这种方式TF-IDF可以过滤掉常见的停用词保留重要的、有区分度的词语作为特征。逻辑回归 TF-IDF的工作流程文本预处理对文档进行分词、去除停用词、词干化或词形还原。特征提取使用TF-IDF向量化器将所有文档转换为一个巨大的稀疏矩阵。矩阵的每一行代表一个文档每一列代表一个词特征矩阵中的值就是该词在该文档中的TF-IDF权重。模型训练将这个特征矩阵和对应的文档标签如“体育”、“科技”输入逻辑回归模型进行训练。预测对新文档进行同样的预处理和TF-IDF转换然后用训练好的模型预测其类别。逻辑回归在这个场景下表现优异原因有三首先它能够很好地处理高维稀疏特征TF-IDF矩阵维度通常上万其次训练和预测速度非常快最后模型具有可解释性我们可以查看每个特征词的权重系数正权重大的词是支持该类的证据负权重大的词是反对该类的证据。# 示例使用TF-IDF和逻辑回归进行新闻分类使用sklearn库 from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import make_pipeline from sklearn.datasets import fetch_20newsgroups # 加载一个经典的文本分类数据集 categories [sci.med, comp.graphics, talk.politics.guns] newsgroups_train fetch_20newsgroups(subsettrain, categoriescategories, shuffleTrue, random_state42) newsgroups_test fetch_20newsgroups(subsettest, categoriescategories, shuffleTrue, random_state42) # 创建管道先做TF-IDF向量化再接入逻辑回归分类器 # 这里使用sklearn的LogisticRegression它功能更完善支持正则化、多分类等 text_clf make_pipeline( TfidfVectorizer(stop_wordsenglish, max_features5000), # 限制最大特征数防止维度爆炸 LogisticRegression(random_state42, max_iter1000) ) # 训练模型 text_clf.fit(newsgroups_train.data, newsgroups_train.target) # 评估模型 predicted text_clf.predict(newsgroups_test.data) accuracy np.mean(predicted newsgroups_test.target) print(f模型在测试集上的准确率: {accuracy:.4f}) # 查看某个特征词单词的权重需要从管道中取出模型 lr_model text_clf.named_steps[logisticregression] feature_names text_clf.named_steps[tfidfvectorizer].get_feature_names_out() # 对于第一个类别索引0查看权重最高的10个词 coef lr_model.coef_[0] top10_indices np.argsort(coef)[-10:] # 权重最大的10个索引 print(\n支持‘sci.med’类别的最重要词语权重最高) for idx in top10_indices[::-1]: # 从高到低打印 print(f {feature_names[idx]}: {coef[idx]:.4f})这个例子展示了如何用几行代码构建一个强大的文本分类器。max_features参数很重要它控制了特征维度避免维度过高导致计算缓慢和过拟合。逻辑回归的coef_属性让我们能“读懂”模型知道它主要依赖哪些词来做判断这对于许多需要模型解释性的业务场景至关重要。8. 模型评估、正则化与多分类扩展训练好模型之后我们不能只看准确率尤其是当数据类别不平衡时。对于二分类逻辑回归我们需要一套更细致的评估工具。混淆矩阵这是所有评估的基础。它将预测结果分为四类真正例TP实际为正预测为正。假正例FP实际为负预测为正。误报真负例TN实际为负预测为负。假负例FN实际为正预测为负。漏报基于混淆矩阵可以计算出关键指标精确率Precision TP / (TP FP)。在所有预测为正的样本中有多少是真的正样本。关注预测的准确性。召回率Recall TP / (TP FN)。在所有真实为正的样本中我们找出了多少。关注预测的覆盖率。F1分数精确率和召回率的调和平均数是两者的综合考量。ROC曲线与AUC通过不断改变分类阈值默认0.5计算不同阈值下的真正例率TPR Recall和假正例率FPR FP / (FPTN)绘制成的曲线。曲线下的面积AUC越接近1模型整体性能越好且对类别不平衡不敏感。过拟合与正则化逻辑回归同样会过拟合特别是当特征很多而数据量不足时。解决方案是正则化即在损失函数中增加一个对模型复杂度的惩罚项。L1正则化Lasso在成本函数中加入权重向量w的L1范数绝对值之和乘以一个正则化系数λ。它倾向于产生稀疏的权重向量即让许多特征的权重直接变为0从而实现特征选择。L2正则化Ridge在成本函数中加入权重向量w的L2范数平方和乘以一个正则化系数λ。它倾向于让所有权重都变小但不会精确为0使得模型更平滑抗干扰能力更强。在sklearn的LogisticRegression中通过penalty参数选择‘l1’或‘l2’通过C参数控制正则化强度注意C是正则化系数的倒数C越小正则化越强。多分类扩展逻辑回归本质是二分类器但可以通过以下策略处理多分类问题OvROne-vs-Rest为每个类别训练一个二分类器将该类视为正类其余所有类视为负类。预测时选择输出概率最高的那个分类器对应的类别。这是sklearn默认采用的策略。OvOOne-vs-One为每两个类别训练一个二分类器。对于K个类别需要训练K(K-1)/2个分类器。预测时通过“投票”机制决定最终类别。当类别很多时这种方法训练成本较高。多项逻辑回归Multinomial Logistic Regression直接使用Softmax函数作为输出层将二分类的Sigmoid推广到多分类。它一次性输出样本属于所有类别的概率分布。这在神经网络中很常见在传统的逻辑回归实现中也支持sklearn中设置multi_class‘multinomial’。选择哪种方法取决于具体问题。OvR训练更快模型更少当类别间不平衡或数据集很大时多项逻辑回归可能更优。