尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

逻辑回归从原理到实践:二分类与多分类的Python实现与调优指南

逻辑回归从原理到实践:二分类与多分类的Python实现与调优指南 1. 项目概述从线性到非线性的分类跃迁在机器学习的入门旅程中线性回归往往是我们的第一站它教会我们如何用一条直线去拟合数据预测一个连续的数值。但当我们面对“是或否”、“A类或B类”这样的离散选择问题时那条笔直的线就显得力不从心了。比如医生需要根据病人的各项指标判断是否患病是/否银行需要根据客户信息评估贷款违约风险会/不会电商平台需要预测用户是否会点击某个商品点击/不点击。这时逻辑回归Logistic Regression就闪亮登场了。别看名字里带着“回归”它可是解决分类问题的中流砥柱尤其是二分类问题。逻辑回归的核心思想非常巧妙它并不直接预测类别而是预测样本属于某个类别的概率。为了实现从线性到概率的映射它引入了一个神奇的“S”型函数——Sigmoid函数。你可以把这个函数想象成一个“概率转换器”无论线性方程z w*x b计算出的值z是多大正无穷或负无穷经过Sigmoid函数的加工输出都会被稳稳地压缩到0到1之间。这个0到1之间的数就是我们认为该样本属于正类比如“患病”、“点击”的概率。通常我们设定一个阈值如0.5概率大于0.5则判定为正类否则为负类。这就完美地将一个回归问题转化为了一个分类决策。对于多分类问题逻辑回归也有成熟的扩展方案最常见的是“一对多”One-vs-Rest, OvR和“多项逻辑回归”Multinomial Logistic Regression。前者将多分类拆解成多个二分类问题后者则使用Softmax函数直接输出样本属于每个类别的概率分布。本文将手把手带你深入逻辑回归的数学原理并用Python从零开始实现二分类与多分类模型最后结合Scikit-learn库进行实战对比。我们会重点剖析模型评估、参数调优以及那些教科书里不会写的“踩坑”经验。2. 逻辑回归的数学原理与核心思想拆解2.1 Sigmoid函数概率的“压舱石”逻辑回归的基石是Sigmoid函数其数学表达式为σ(z) 1 / (1 e^{-z})其中z就是我们熟悉的线性组合z w^T * x bw是权重向量b是偏置项x是特征向量。这个函数有什么特性呢我们来看当z趋向于正无穷时e^{-z}趋近于0因此σ(z)趋近于1当z趋向于负无穷时e^{-z}趋向于正无穷因此σ(z)趋近于0。它的函数图像是一条从0平滑增长到1的S形曲线以点(0, 0.5)为中心对称。这意味着线性方程z的值越大模型预测为正类的概率就越高z的值越小预测为正类的概率就越低。z0时概率恰好为0.5这正是我们常设定的决策边界。注意Sigmoid函数求导有一个非常优美的性质σ(z) σ(z) * (1 - σ(z))。这个性质在后续使用梯度下降法求解参数时会大大简化计算是逻辑回归能够高效训练的关键之一。2.2 从概率出发构建损失函数既然模型输出的是概率那么如何衡量预测概率与真实标签之间的差距呢这里不能再用线性回归的均方误差MSE了因为MSE在分类问题上通常是非凸的存在多个局部最优解不利于优化。逻辑回归采用交叉熵损失函数Cross-Entropy Loss它源于信息论衡量的是两个概率分布之间的差异。对于单个样本的二分类问题损失函数定义如下L(y, ŷ) -[y * log(ŷ) (1-y) * log(1-ŷ)]其中y是真实标签0或1ŷ是模型预测为正类的概率σ(z)。我们来直观理解一下这个公式当真实标签y1时损失函数变为-log(ŷ)。这意味着如果模型预测概率ŷ越接近1预测正确log(ŷ)越接近0损失就越小如果ŷ越接近0预测错误log(ŷ)会趋向负无穷损失就会变得非常大。当真实标签y0时损失函数变为-log(1-ŷ)。同理预测概率ŷ越接近0损失越小越接近1损失越大。这个函数完美地表达了我们的诉求鼓励模型对正确类别做出高置信度的预测并对错误预测施加严厉的惩罚。对于包含m个样本的整个训练集我们的目标就是找到一组参数w和b使得所有样本的平均交叉熵损失最小化即最小化代价函数J(w, b) (1/m) * Σ L(y^(i), ŷ^(i))。2.3 参数求解梯度下降法损失函数构建好了接下来就是如何找到使损失最小的w和b。由于交叉熵损失函数是凸函数我们可以使用梯度下降法来找到全局最优解。梯度下降的核心思想是“沿最陡的下坡方向走”。我们需要计算损失函数J关于每个参数w_j和b的偏导数即梯度然后让参数朝着梯度反方向即下降方向更新一小步。这个“一小步”由学习率α控制。经过推导这里利用了Sigmoid导数性质我们可以得到非常简洁的梯度公式∂J/∂w_j (1/m) * Σ (ŷ^(i) - y^(i)) * x_j^(i)∂J/∂b (1/m) * Σ (ŷ^(i) - y^(i))你会发现这个梯度公式在形式上与线性回归的梯度惊人地相似只是预测值ŷ的计算方式不同线性回归是w^T*xb逻辑回归是σ(w^T*xb)。参数更新公式为w_j : w_j - α * ∂J/∂w_jb : b - α * ∂J/∂b通过迭代执行成百上千次这样的更新参数会逐渐收敛到使损失函数最小化的值。2.4 多分类扩展Softmax回归当类别数K大于2时二分类的逻辑回归就需要升级。最直接的方法是多项逻辑回归或称Softmax回归。它不再使用一个Sigmoid函数而是使用Softmax函数为每个类别计算一个概率。Softmax函数定义如下对于样本i它属于类别j的概率为ŷ_j^(i) e^{z_j^(i)} / Σ_{k1}^{K} e^{z_k^(i)}其中z_j^(i) w_j^T * x^(i) b_j是为类别j单独计算的一个线性分数。Softmax函数将所有类别的分数z通过指数函数映射到正数域然后进行归一化确保所有类别的预测概率之和为1。此时的损失函数也扩展为多分类交叉熵损失L - Σ_{j1}^{K} y_j * log(ŷ_j)这里y是一个独热编码One-hot向量只有真实类别对应的位置为1其余为0。梯度下降的求解过程与二分类类似只是需要对每个类别j的参数w_j和b_j分别计算梯度和更新。另一种策略是一对多为每个类别训练一个二分类器该分类器负责判断“是当前类”还是“不是当前类”。预测时让所有分类器对样本进行打分选择概率最高的那个类别作为最终预测。这种方法实现简单但当类别很多时需要训练大量分类器且可能存在类别不平衡和决策模糊的问题。3. 二分类逻辑回归的Python从零实现理解了原理我们开始动手实现。从零实现能让你对每一个细节都了然于胸。我们将使用一个简单的模拟数据集。3.1 数据准备与特征工程首先我们生成一个线性可分的数据集。在实际项目中这一步可能是数据清洗、缺失值处理、特征缩放标准化或归一化等。特征缩放对于基于梯度下降的算法非常重要能加速收敛。import numpy as np import matplotlib.pyplot as plt # 设置随机种子以保证结果可复现 np.random.seed(42) # 生成两类数据 num_samples_per_class 100 # 类别0的数据均值为[2, 2]协方差矩阵为[[2, 0.5], [0.5, 1]] X0 np.random.multivariate_normal(mean[2, 2], cov[[2, 0.5], [0.5, 1]], sizenum_samples_per_class) y0 np.zeros(num_samples_per_class) # 标签为0 # 类别1的数据均值为[6, 6]协方差矩阵为[[1, -0.5], [-0.5, 2]] X1 np.random.multivariate_normal(mean[6, 6], cov[[1, -0.5], [-0.5, 2]], sizenum_samples_per_class) y1 np.ones(num_samples_per_class) # 标签为1 # 合并数据 X np.vstack((X0, X1)) y np.hstack((y0, y1)) # 打乱数据顺序 shuffle_index np.random.permutation(len(X)) X, y X[shuffle_index], y[shuffle_index] # 划分训练集和测试集 (8:2) split_idx int(0.8 * len(X)) X_train, X_test X[:split_idx], X[split_idx:] y_train, y_test y[:split_idx], y[split_idx:] # 特征标准化 (非常重要) from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 注意使用训练集的均值和方差来转换测试集 print(f训练集形状: {X_train_scaled.shape}, 测试集形状: {X_test_scaled.shape})3.2 核心算法实现接下来我们实现逻辑回归类包含初始化、Sigmoid函数、前向传播预测概率、计算损失、计算梯度以及训练批量梯度下降等方法。class LogisticRegressionFromScratch: def __init__(self, learning_rate0.01, n_iters1000): 初始化逻辑回归模型。 参数: learning_rate: 学习率控制梯度下降的步长。 n_iters: 梯度下降的迭代次数。 self.lr learning_rate self.n_iters n_iters self.weights None self.bias None self.loss_history [] # 记录每次迭代的损失用于可视化 def _sigmoid(self, z): Sigmoid激活函数将输入z映射到(0,1)区间。 # 为了防止数值溢出当z为很大的负数时e^z可能下溢使用稳定的实现 # 将计算拆分为正负部分 return 1 / (1 np.exp(-np.clip(z, -250, 250))) # clip防止数值溢出 def _initialize_parameters(self, n_features): 初始化权重和偏置。 # 权重初始化为接近0的小随机数偏置初始化为0 self.weights np.random.randn(n_features) * 0.01 self.bias 0.0 def _compute_loss(self, y_true, y_pred): 计算平均交叉熵损失。 # 避免log(0)导致无穷大给y_pred一个很小的偏移量epsilon epsilon 1e-15 y_pred np.clip(y_pred, epsilon, 1 - epsilon) # 二分类交叉熵损失 loss -np.mean(y_true * np.log(y_pred) (1 - y_true) * np.log(1 - y_pred)) return loss def fit(self, X, y): 使用批量梯度下降法训练模型。 参数: X: 训练特征形状为 (n_samples, n_features) y: 训练标签形状为 (n_samples,) n_samples, n_features X.shape self._initialize_parameters(n_features) # 梯度下降迭代 for i in range(self.n_iters): # 1. 线性组合 linear_model np.dot(X, self.weights) self.bias # 2. 应用Sigmoid得到预测概率 y_pred self._sigmoid(linear_model) # 3. 计算损失 loss self._compute_loss(y, y_pred) self.loss_history.append(loss) # 4. 计算梯度 (推导出的简洁公式) dw (1 / n_samples) * np.dot(X.T, (y_pred - y)) db (1 / n_samples) * np.sum(y_pred - y) # 5. 更新参数 self.weights - self.lr * dw self.bias - self.lr * db # 可选每100次迭代打印一次损失 if i % 100 0: print(fIteration {i}: loss {loss:.4f}) def predict_proba(self, X): 预测样本属于正类(1)的概率。 linear_model np.dot(X, self.weights) self.bias return self._sigmoid(linear_model) def predict(self, X, threshold0.5): 根据阈值将概率转换为类别预测。 y_proba self.predict_proba(X) y_pred (y_proba threshold).astype(int) return y_pred3.3 模型训练与评估现在我们用自己实现的数据和模型进行训练并评估其性能。# 实例化并训练模型 model LogisticRegressionFromScratch(learning_rate0.1, n_iters2000) model.fit(X_train_scaled, y_train) # 绘制损失下降曲线 plt.plot(range(model.n_iters), model.loss_history) plt.xlabel(Iterations) plt.ylabel(Loss) plt.title(Training Loss over Iterations) plt.grid(True) plt.show() # 在测试集上进行预测 y_test_pred model.predict(X_test_scaled) y_test_proba model.predict_proba(X_test_scaled) # 评估模型性能 from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, confusion_matrix import seaborn as sns print( 测试集性能评估 ) print(f准确率 (Accuracy): {accuracy_score(y_test, y_test_pred):.4f}) print(f精确率 (Precision): {precision_score(y_test, y_test_pred):.4f}) print(f召回率 (Recall): {recall_score(y_test, y_test_pred):.4f}) print(fF1分数: {f1_score(y_test, y_test_pred):.4f}) print(fAUC-ROC分数: {roc_auc_score(y_test, y_test_proba):.4f}) # 绘制混淆矩阵 cm confusion_matrix(y_test, y_test_pred) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabels[Predicted 0, Predicted 1], yticklabels[Actual 0, Actual 1]) plt.title(Confusion Matrix) plt.show() # 绘制决策边界 (由于我们做了标准化需要在原始尺度或标准化后的尺度上画) def plot_decision_boundary(model, X, y, scalerNone): # 创建网格点 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.02), np.arange(y_min, y_max, 0.02)) # 如果提供了scaler需要将网格点标准化后再预测 if scaler is not None: grid np.c_[xx.ravel(), yy.ravel()] grid_scaled scaler.transform(grid) Z model.predict(grid_scaled) else: Z model.predict(np.c_[xx.ravel(), yy.ravel()]) Z Z.reshape(xx.shape) # 绘制等高线图和散点图 plt.contourf(xx, yy, Z, alpha0.3, cmapplt.cm.RdYlBu) scatter plt.scatter(X[:, 0], X[:, 1], cy, edgecolorsk, cmapplt.cm.RdYlBu) plt.xlabel(Feature 1) plt.ylabel(Feature 2) plt.title(Decision Boundary) plt.legend(*scatter.legend_elements(), titleClasses) plt.show() # 注意绘制时使用未标准化的原始测试集特征X_test但模型预测时需要标准化。 # 我们的plot_decision_boundary函数已经处理了这一点。 plot_decision_boundary(model, X_test, y_test, scalerscaler)实操心得在实现Sigmoid函数时我使用了np.clip(z, -250, 250)。这是一个非常重要的数值稳定性技巧。因为当z是一个非常小的负数如-1000时np.exp(-z)会变成一个天文数字导致溢出inf当z非常大时np.exp(-z)又会下溢为0。裁剪到一个合理的范围如[-250, 250]可以保证计算稳定且在这个范围内Sigmoid函数的输出已经无限接近0或1精度损失可忽略不计。这是工业级代码中常见的做法。4. 多分类逻辑回归的Python实现我们使用经典的鸢尾花Iris数据集它包含3类鸢尾花Setosa, Versicolor, Virginica每类50个样本每个样本有4个特征萼片和花瓣的长度与宽度。4.1 一对多OvR策略实现首先我们用从零开始的方式实现OvR策略。核心思想是为每个类别训练一个二分类器。from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split # 加载数据 iris load_iris() X_multi iris.data y_multi iris.target # 标签为0, 1, 2 # 划分训练集和测试集 X_train_multi, X_test_multi, y_train_multi, y_test_multi train_test_split( X_multi, y_multi, test_size0.2, random_state42, stratifyy_multi ) # 特征标准化 scaler_multi StandardScaler() X_train_multi_scaled scaler_multi.fit_transform(X_train_multi) X_test_multi_scaled scaler_multi.transform(X_test_multi) class OneVsRestLogisticRegression: def __init__(self, learning_rate0.1, n_iters1000): self.lr learning_rate self.n_iters n_iters self.classifiers {} # 存储每个类别的二分类器 self.classes_ None def _train_binary_classifier(self, X, y_binary): 训练一个二分类逻辑回归模型复用之前的类。 n_samples, n_features X.shape weights np.random.randn(n_features) * 0.01 bias 0.0 for i in range(self.n_iters): linear_model np.dot(X, weights) bias y_pred 1 / (1 np.exp(-np.clip(linear_model, -250, 250))) # 计算梯度 dw (1 / n_samples) * np.dot(X.T, (y_pred - y_binary)) db (1 / n_samples) * np.sum(y_pred - y_binary) # 更新参数 weights - self.lr * dw bias - self.lr * db return weights, bias def fit(self, X, y): 为每个类别训练一个二分类器。 self.classes_ np.unique(y) for cls in self.classes_: # 将当前类别视为正类(1)其他所有类别视为负类(0) y_binary np.where(y cls, 1, 0) print(fTraining classifier for class {cls}...) w, b self._train_binary_classifier(X, y_binary) self.classifiers[cls] {weights: w, bias: b} def predict_proba(self, X): 预测每个样本属于各个类别的概率OvR下概率可能不归一化和为1。 n_samples X.shape[0] probas np.zeros((n_samples, len(self.classes_))) for idx, cls in enumerate(self.classes_): w self.classifiers[cls][weights] b self.classifiers[cls][bias] linear_score np.dot(X, w) b probas[:, idx] 1 / (1 np.exp(-np.clip(linear_score, -250, 250))) # OvR的概率不是真正的概率分布但可以用于比较 return probas def predict(self, X): 预测类别选择概率最高的类别。 probas self.predict_proba(X) return self.classes_[np.argmax(probas, axis1)] # 训练OvR模型 ovr_model OneVsRestLogisticRegression(learning_rate0.1, n_iters2000) ovr_model.fit(X_train_multi_scaled, y_train_multi) # 评估 y_test_pred_ovr ovr_model.predict(X_test_multi_scaled) from sklearn.metrics import classification_report print( One-vs-Rest (从零实现) 分类报告 ) print(classification_report(y_test_multi, y_test_pred_ovr, target_namesiris.target_names))4.2 使用Scikit-learn实现多分类逻辑回归在实际工作中我们更倾向于使用成熟、优化过的库。Scikit-learn中的LogisticRegression默认就支持多分类。from sklearn.linear_model import LogisticRegression from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay # 创建模型。注意multi_class参数 # ovr: 一对多策略 # multinomial: 多项逻辑回归Softmax回归需要指定求解器如lbfgs, sag, saga, newton-cg # auto: 根据数据和求解器自动选择 model_sklearn LogisticRegression(multi_classmultinomial, solverlbfgs, max_iter1000, random_state42) # 也可以尝试 multi_classovr model_sklearn.fit(X_train_multi_scaled, y_train_multi) # 评估 y_test_pred_sk model_sklearn.predict(X_test_multi_scaled) y_test_proba_sk model_sklearn.predict_proba(X_test_multi_scaled) print( Scikit-learn Multinomial Logistic Regression 分类报告 ) print(classification_report(y_test_multi, y_test_pred_sk, target_namesiris.target_names)) # 绘制多分类混淆矩阵 cm_multi confusion_matrix(y_test_multi, y_test_pred_sk) disp ConfusionMatrixDisplay(confusion_matrixcm_multi, display_labelsiris.target_names) disp.plot(cmapviridis) plt.title(Confusion Matrix for Multiclass Classification) plt.show() # 查看预测概率前5个样本 print(\n预测概率前5个测试样本:) print(y_test_proba_sk[:5]) print(预测类别:, y_test_pred_sk[:5]) print(真实类别:, y_test_multi[:5])注意事项Scikit-learn的LogisticRegression默认使用L2正则化参数penaltyl2来防止过拟合强度由C控制C是正则化强度的倒数C越小正则化越强。在多分类问题上选择solver求解器很重要。对于较小的数据集lbfgs是一个很好的默认选择。对于大数据集sag或saga可能更快。newton-cg适用于中小型数据集。如果遇到收敛警告可以尝试增加max_iter或调整tol容忍度参数。5. 模型评估、调优与实战避坑指南构建模型只是第一步评估其表现并优化它才是机器学习工作的核心。5.1 二分类评估指标深度解读对于二分类问题不能只看准确率特别是当数据类别不平衡时。准确率所有预测正确的样本占总样本的比例。在不平衡数据上如99%负例1%正例一个总是预测负例的模型也能有99%的准确率但这毫无意义。精确率在所有预测为正的样本中实际为正的比例。它关注的是预测的“准不准”。在垃圾邮件检测中我们追求高精确率因为把正常邮件误判为垃圾邮件假正例代价很高。召回率在所有实际为正的样本中被正确预测为正的比例。它关注的是“找得全不全”。在疾病筛查中我们追求高召回率因为漏诊假负例的代价很高。F1分数精确率和召回率的调和平均数F1 2 * (Precision * Recall) / (Precision Recall)。它是一个综合指标在两者需要权衡时很有用。AUC-ROC这个指标不依赖于具体的分类阈值。ROC曲线描绘了在不同阈值下真正例率召回率和假正例率的变化。AUC是曲线下的面积越接近1模型越好。AUC衡量的是模型将正样本排在负样本前面的能力。# 绘制ROC曲线 from sklearn.metrics import roc_curve, auc fpr, tpr, thresholds roc_curve(y_test, y_test_proba) # y_test_proba是正类的概率 roc_auc auc(fpr, tpr) plt.figure() plt.plot(fpr, tpr, colordarkorange, lw2, labelfROC curve (area {roc_auc:.2f})) plt.plot([0, 1], [0, 1], colornavy, lw2, linestyle--, labelRandom Guess) plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.05]) plt.xlabel(False Positive Rate) plt.ylabel(True Positive Rate) plt.title(Receiver Operating Characteristic (ROC) Curve) plt.legend(loclower right) plt.grid(True) plt.show() # 寻找最佳阈值根据Youdens J statistic youden_j tpr - fpr best_idx np.argmax(youden_j) best_threshold thresholds[best_idx] print(f基于Youden指数的建议阈值: {best_threshold:.4f}) print(f在该阈值下TPR{tpr[best_idx]:.4f}, FPR{fpr[best_idx]:.4f})5.2 多分类评估与混淆矩阵分析对于多分类除了宏观平均如macro avg和加权平均weighted avg的精确率、召回率、F1值混淆矩阵是极其重要的可视化工具。它能清晰展示模型在哪些类别上容易混淆。从我们上面绘制的鸢尾花混淆矩阵中你可能会发现Virginica和Versicolor这两个类别有时会被误判因为它们的花瓣尺寸比较接近而Setosa通常能被完美区分。这提示我们特征工程比如构造花瓣长宽比这样的新特征可能有助于提升模型表现。5.3 正则化与超参数调优逻辑回归容易过拟合尤其是特征多而样本少的时候。正则化通过在损失函数中添加惩罚项来约束模型参数的大小。L1正则化惩罚项是权重的绝对值之和。它倾向于产生稀疏的权重向量即很多权重会变成0相当于自动进行了特征选择。L2正则化惩罚项是权重的平方和。它倾向于让所有权重都变小但不会完全为0使模型更平滑。在Scikit-learn中通过penalty参数设置强度由C控制C1/λλ是正则化系数。我们可以使用网格搜索来寻找最优超参数。from sklearn.model_selection import GridSearchCV # 定义参数网格 param_grid [ {penalty: [l1], solver: [liblinear, saga], C: [0.001, 0.01, 0.1, 1, 10, 100]}, {penalty: [l2], solver: [lbfgs, liblinear, sag, saga], C: [0.001, 0.01, 0.1, 1, 10, 100]}, {penalty: [elasticnet], solver: [saga], l1_ratio: [0.1, 0.5, 0.9], C: [0.01, 0.1, 1, 10]} ] # 创建基础模型 log_reg LogisticRegression(max_iter5000, random_state42) # 增加max_iter确保收敛 # 创建网格搜索对象使用5折交叉验证以准确率为评分标准 grid_search GridSearchCV(log_reg, param_grid, cv5, scoringaccuracy, n_jobs-1, verbose1) grid_search.fit(X_train_multi_scaled, y_train_multi) # 输出最佳参数和最佳得分 print(f最佳参数: {grid_search.best_params_}) print(f最佳交叉验证准确率: {grid_search.best_score_:.4f}) # 在测试集上评估最佳模型 best_model grid_search.best_estimator_ y_test_pred_best best_model.predict(X_test_multi_scaled) print(f测试集准确率: {accuracy_score(y_test_multi, y_test_pred_best):.4f})实操心得进行网格搜索时n_jobs-1可以并行使用所有CPU核心大大加快搜索速度。verbose1会打印进度让你知道程序还在运行。注意某些求解器solver只支持特定的正则化类型。例如lbfgs只支持L2正则化。如果参数网格组合无效Scikit-learn会跳过并给出警告。另外当数据标准化后正则化的效果会更加公平因为所有特征都在同一尺度上。5.4 常见问题与排查技巧实录在实际编码和调试中你肯定会遇到各种问题。这里记录几个我踩过的坑和解决方法。问题1模型不收敛损失函数震荡或变成NaN。可能原因1学习率太大。步子迈得太大在最优解附近来回跳跃甚至发散。解决尝试减小学习率如从0.1降到0.01、0.001。可能原因2特征未标准化。如果特征尺度差异巨大如一个特征范围是0-1另一个是10000-100000梯度下降的路径会非常曲折难以收敛。解决务必使用StandardScaler或MinMaxScaler进行特征标准化。可能原因3Sigmoid函数数值溢出。如前所述使用np.clip限制线性组合z的范围。可能原因4数据本身有问题。检查是否有缺失值或无穷值。使用np.isfinite(X).all()进行检查。问题2预测概率全部非常接近0.5模型没有区分能力。可能原因1特征与标签真的不相关。需要重新进行特征工程或选择其他模型。可能原因2权重初始化全为0。如果权重初始化为0那么所有样本的z都等于偏置b梯度下降更新时所有权重的梯度方向相同导致对称性无法打破。解决像我们代码中那样用小的随机数初始化权重。可能原因3正则化强度C太大即λ太小导致模型过于简单欠拟合。解决减小C的值增大正则化强度或者检查是否错误地使用了太大的正则化系数。问题3Scikit-learn模型训练时出现ConvergenceWarning。原因求解器在指定的最大迭代次数max_iter内未收敛。解决增加max_iter参数如设为5000。也可以尝试调整tol参数默认1e-4降低收敛容忍度但可能会影响精度。或者换一个求解器如saga通常收敛性较好。问题4多分类时OvR和Multinomial结果差异很大。原因对于某些数据集OvR和Softmax的假设不同可能导致性能差异。此外如果类别严重不平衡OvR中每个二分类器面临的正负样本比可能差异巨大。解决优先尝试multi_classmultinomialSoftmax这通常是更“自然”的多分类扩展。如果效果不好再尝试OvR。同时确保评估指标选用加权平均weighted avg来考虑类别不平衡。问题5如何解释逻辑回归模型的系数逻辑回归的系数w有很好的可解释性。对于一个特征x_j其系数w_j的正负表示该特征与预测为正类的概率是正相关还是负相关。系数的大小绝对值反映了特征的重要性。但是前提是特征已经标准化否则系数量纲不同无法直接比较。可以通过model.coef_查看系数。例如在鸢尾花数据集中如果花瓣长度petal length的系数很大且为正说明花瓣越长模型越可能将其预测为Virginica假设该类编码为2且系数对应此类。逻辑回归是一个强大而直观的模型它不仅是许多分类任务的实用基线其概率输出、可解释性以及作为神经网络基础单元的地位都使其值得每一位机器学习从业者深入掌握。从数学原理推导到一行行代码实现再到调参评估的实战这个过程能帮你打下坚实的根基。希望这篇长文能成为你探索更复杂模型世界的一块坚实跳板。在实际项目中别忘了结合业务理解进行特征工程那往往是提升模型效果最有效的一环。
返回列表