逻辑回归:从Sigmoid函数到实战应用,掌握二分类核心算法
1. 从分类问题到逻辑回归:为什么它不只是“回归”
在数据分析和建模的实战中,我们常常会遇到一个核心问题:预测一个事件是否会发生。比如,一封邮件是不是垃圾邮件?一个客户明天会不会流失?一个病人根据各项指标,患某种疾病的概率有多大?这些问题都有一个共同点:我们想要预测的结果,不是一个连续的数值(比如房价、销售额),而是一个“是”或“否”的类别标签,或者更精确地说,是“是”这个事件发生的概率。
很多初学者一听到“回归”,第一反应是线性回归——那条试图用一条直线去拟合数据点的经典方法。线性回归确实强大,但它有一个致命的弱点:当你用它去预测一个概率时,它可能会给你一个小于0或者大于1的荒谬结果,比如预测出“患病概率是-0.2或1.5”,这显然不符合概率的定义(概率必须在0到1之间)。这就是分类问题直接套用线性回归的尴尬。
于是,逻辑回归(Logistic Regression)登场了。别看名字里带着“回归”,它其实是解决二分类问题的王者级算法。它的核心思想非常巧妙:我们不直接用线性方程去预测概率,而是先预测一个“可能性”的度量(称为对数几率),再通过一个特殊的函数,将这个度量“压缩”到0到1之间。这个函数,就是大名鼎鼎的Sigmoid函数,也叫Logistic函数。
你可以把它想象成一个“概率转换器”。线性部分(和线性回归一样,是特征的加权和)负责评估事件的倾向性,数值越大,倾向性越强。Sigmoid函数则像一个温和的裁判,把任何倾向性分数,无论正负、大小,都温柔地映射到(0,1)这个概率区间内。这样一来,模型的输出就天然是一个合理的概率值了。
在数学建模竞赛和实际业务中,逻辑回归几乎是处理二分类问题的首选基线模型。它模型清晰、可解释性强、计算效率高,而且作为广义线性模型的一员,其参数具有明确的统计意义。更重要的是,理解了逻辑回归,就为理解更复杂的神经网络(尤其是分类任务的输出层)打下了坚实的基础。接下来,我们就深入这个“概率转换器”的内部,看看它的数学心脏是如何跳动的。
2. Sigmoid函数:逻辑回归的“概率压缩”核心
要理解逻辑回归,必须彻底搞懂Sigmoid函数。它不仅是数学上的一个变换,更是整个模型逻辑的基石。
2.1 函数形式与直观理解
Sigmoid函数的数学表达式如下:
[ \sigma(z) = \frac{1}{1 + e^{-z}} ]
这里的 ( z ) 就是我们线性组合的结果,即 ( z = \theta_0 + \theta_1 x_1 + \theta_2 x_2 + ... + \theta_n x_n = \theta^T x )。其中,( \theta ) 是模型参数(权重),( x ) 是输入特征。
这个函数有什么特性呢?我们拆开来看:
- 输出范围在(0,1):无论输入 ( z ) 是正无穷大、负无穷大还是任何实数,( e^{-z} ) 永远大于0。因此,分母 ( 1+e^{-z} > 1 ),整个分数的值就严格在0到1之间。完美符合概率的定义。
- 单调递增:函数是单调递增的。这意味着 ( z ) 越大,( \sigma(z) ) 越接近1;( z ) 越小,( \sigma(z) ) 越接近0。这符合直觉:特征加权和越大,说明正向证据越强,属于正类(标签为1)的概率就越高。
- 以0.5为分界点:当 ( z = 0 ) 时,( \sigma(z) = 0.5 )。这是一个非常自然的决策边界。在实际预测时,我们通常设定一个阈值(默认为0.5),当预测概率 ( \geq 0.5 ) 时,我们判定为正类(1),否则为负类(0)。
- 函数形状:它的图像是一条平滑的“S”形曲线。在 ( z=0 ) 附近,曲线变化相对陡峭,概率对 ( z ) 的变化很敏感;而当 ( z ) 的绝对值很大时(无论正负),曲线变得非常平缓,概率趋近于0或1,对 ( z ) 的变化不再敏感。这个特性使得模型对极端值的预测不会过于“疯狂”。
注意:Sigmoid函数也叫Logistic函数,但“Logistic”这个词更泛指整个模型和其背后的逻辑分布。在沟通时,说“Sigmoid激活函数”或“Logistic函数”通常指同一个东西,但在严谨的数学表述中需注意上下文。
2.2 从几率(Odds)到对数几率(Logit)
逻辑回归的“逻辑”二字,来源于它和“几率”概念的紧密联系,这是理解其参数意义的关键。
首先,定义几率(Odds):它是指事件发生的概率与不发生的概率的比值。即 ( Odds = \frac{p}{1-p} ),其中 ( p ) 是事件发生的概率。如果 ( p=0.75 ),那么 ( Odds = 0.75 / 0.25 = 3 ),意思是“发生的可能性是不发生的3倍”。
逻辑回归模型做了一个关键的假设:对数几率(Logit)是输入特征的线性组合。
[ \log(\frac{p}{1-p}) = \theta^T x = z ]
这个等式就是逻辑回归的核心模型。左边是事件发生概率 ( p ) 的对数几率,右边是特征的线性组合。我们对这个等式进行简单的变换:
[ \frac{p}{1-p} = e^{\theta^T x} ] [ p = \frac{e^{\theta^T x}}{1 + e^{\theta^T x}} = \frac{1}{1 + e^{-\theta^T x}} ]
看,我们就这样从“对数几率线性”的假设,推导出了Sigmoid函数的形式。这解释了为什么逻辑回归属于广义线性模型:它是对响应变量(这里是概率 ( p ) )进行一个单调可微的链接函数(Sigmoid函数)变换后,使得变换后的结果(对数几率)与特征呈线性关系。
2.3 参数的解释:一个实战案例
这个线性关系赋予了逻辑回归参数极其直观的解释。假设我们在建立一个预测客户流失的模型,其中一个特征是“上月客服投诉次数” ( x_1 ),其对应的参数(权重) ( \theta_1 ) 被训练为 0.8。
参数 ( \theta_1 = 0.8 ) 意味着什么?它意味着:在保持其他所有特征不变的情况下,“上月客服投诉次数”每增加1次,客户流失的“对数几率”将增加0.8。
更进一步,我们可以解释为对“几率”的影响。因为: [ \log(Odds_{new}) - \log(Odds_{old}) = \theta_1 * 1 = 0.8 ] 所以: [ \frac{Odds_{new}}{Odds_{old}} = e^{0.8} \approx 2.225 ]
这意味着,投诉次数每增加一次,客户流失的几率将变为原来的约2.225倍。这种解释在业务上非常有力,可以直接告诉业务方:“你看,客户每多投诉一次,他流失的风险就会翻一倍还多。” 这正是逻辑回归在需要模型可解释性的场景(如金融风控、医疗诊断)中备受青睐的原因。
3. 模型训练:如何找到那组最优参数
模型的形式定了,接下来的问题就是:给定一堆已知标签的数据,我们如何找到那组最优的参数 ( \theta ) ?这个过程就是模型训练,其本质是一个优化问题。
3.1 损失函数:交叉熵损失(Cross-Entropy Loss)
线性回归用均方误差(MSE)作为损失函数,但这对逻辑回归不适用,因为MSE在用于概率输出时会导致损失函数非凸,存在很多局部最优解,不利于优化。
逻辑回归使用的是交叉熵损失函数,它源于信息论,衡量的是真实概率分布与预测概率分布之间的差异。对于单个样本 ( (x^{(i)}, y^{(i)}) ),其中 ( y^{(i)} ) 是真实标签(0或1),( h_\theta(x^{(i)}) = \sigma(\theta^T x^{(i)}) ) 是模型预测为正类的概率,其损失定义为:
[ Cost(h_\theta(x^{(i)}), y^{(i)}) = -[y^{(i)} \log(h_\theta(x^{(i)})) + (1 - y^{(i)}) \log(1 - h_\theta(x^{(i)}))] ]
这个公式设计得非常巧妙:
- 当真实标签 ( y^{(i)}=1 ) 时,损失变为 ( -\log(h_\theta(x^{(i)})) )。如果模型预测概率 ( h_\theta(x^{(i)}) ) 接近1(预测正确),那么 ( \log(1) = 0 ),损失接近0;如果预测概率接近0(预测错误),那么 ( -\log(0) ) 会趋向无穷大,给予模型极大的惩罚。
- 当真实标签 ( y^{(i)}=0 ) 时,损失变为 ( -\log(1 - h_\theta(x^{(i)})) )。逻辑同理。
整个训练集上的损失函数(成本函数) ( J(\theta) ) 就是所有样本损失的平均: [ 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) ) 最小化。
3.2 优化算法:梯度下降的实战细节
交叉熵损失函数是凸函数,这意味着只要学习率设置得当,梯度下降法一定能找到全局最优解(或接近最优的解)。参数更新的核心公式如下:
[ \theta_j := \theta_j - \alpha \frac{\partial J(\theta)}{\partial \theta_j} ]
其中 ( \alpha ) 是学习率。关键在于求梯度。经过推导(这是一个经典的推导,建议手动做一遍),对于单个样本,损失函数对参数 ( \theta_j ) 的偏导数为: [ \frac{\partial Cost}{\partial \theta_j} = (h_\theta(x^{(i)}) - y^{(i)}) x_j^{(i)} ]
这个结果非常简洁优美!预测值与真实值的误差,乘以对应的特征值。对于整个训练集,梯度就是所有样本梯度的平均: [ \frac{\partial J(\theta)}{\partial \theta_j} = \frac{1}{m} \sum_{i=1}^{m} (h_\theta(x^{(i)}) - y^{(i)}) x_j^{(i)} ]
在实际编程实现(如Python中使用NumPy)或调用库(如scikit-learn)时,算法就是基于这个梯度进行迭代更新。这里有几个至关重要的实战要点:
特征缩放(Feature Scaling):虽然逻辑回归的损失函数不受特征尺度影响最终收敛,但特征缩放能极大加快梯度下降的收敛速度。如果特征尺度差异巨大(如“年龄”范围0-100,“收入”范围0-1000000),梯度下降的路径会非常曲折,需要很多次迭代。通常使用标准化(Standardization):( x' = \frac{x - \mu}{\sigma} ),让每个特征均值为0,标准差为1。这是建模前几乎必做的预处理步骤。
学习率 ( \alpha ) 的选择:学习率太小,收敛慢,训练时间长;学习率太大,可能会在最小值附近震荡甚至发散,无法收敛。一个实用的方法是绘制损失函数值随迭代次数变化的曲线(学习曲线)。如果曲线下降平滑,说明学习率合适;如果曲线震荡或上升,说明学习率太大;如果曲线下降极其缓慢,说明学习率可能太小。通常可以从0.01、0.03、0.1、0.3等值开始尝试。
迭代终止条件:通常有两种:(1) 设置最大迭代次数。(2) 设定一个阈值,当两次迭代之间损失函数值的下降小于这个阈值时,认为已经收敛。在实际中,常结合两者使用。
实操心得:在数学建模或自己实现时,不要急于一开始就写复杂的优化算法。先用小批量数据,把梯度计算、参数更新的代码写对,并手动计算前几次迭代的结果进行验证。确保基础单元正确,再扩展到全数据集和更复杂的优化器(如带动量的梯度下降)。
4. 从理论到代码:手把手实现与sklearn应用
理解了原理,我们来看看如何用代码实现。这里会展示两种方式:纯NumPy实现(有助于彻底理解)和scikit-learn应用(实战首选)。
4.1 纯NumPy实现:深入每一步
import numpy as np class LogisticRegression_FromScratch: 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): """Sigmoid函数,防止溢出""" # 对z进行裁剪,防止exp(-z)过大导致溢出 z = np.clip(z, -500, 500) 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 # 通过sigmoid得到预测概率 y_predicted = self._sigmoid(linear_model) # 计算梯度 (推导出的简洁形式) dw = (1 / n_samples) * np.dot(X.T, (y_predicted - y)) db = (1 / n_samples) * np.sum(y_predicted - y) # 更新参数 self.weights -= self.lr * dw self.bias -= self.lr * db def predict_proba(self, X): """预测概率""" linear_model = np.dot(X, self.weights) + self.bias return self._sigmoid(linear_model) def predict(self, X, threshold=0.5): """预测类别""" probabilities = self.predict_proba(X) return (probabilities >= threshold).astype(int)代码关键点解析:
_sigmoid函数中的np.clip:这是一个非常重要的防溢出技巧。当z是一个非常小的负数时,np.exp(-z)会变成一个极大的数,可能导致计算溢出(得到inf)。裁剪到[-500, 500]是一个经验值,能保证计算稳定。- 梯度计算
dw和db:直接对应我们之前推导的公式(1/m) * X^T * (h - y)。这里利用NumPy的广播和点乘,一次性计算所有权重的梯度,非常高效。 predict方法中的threshold:展示了如何将概率转换为类别。0.5是默认阈值,但可以根据业务需求调整(例如,在疾病筛查中,为了不漏诊,可能降低阈值以提高召回率)。
4.2 scikit-learn实战:高效与全面
在实际项目和数学建模中,我们几乎总是使用成熟的库,如scikit-learn。它高效、稳定且功能全面。
import pandas as pd from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, confusion_matrix, classification_report, roc_auc_score # 1. 加载数据(示例:使用鸢尾花数据集二分类) from sklearn.datasets import load_breast_cancer data = load_breast_cancer() X = pd.DataFrame(data.data, columns=data.feature_names) y = data.target # 0: malignant, 1: benign # 2. 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 3. 特征标准化(非常重要!) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # 注意:使用训练集的均值和标准差来转换测试集 # 4. 创建并训练逻辑回归模型 # 参数说明: # penalty: 正则化类型,'l2'是默认,防止过拟合。 # C: 正则化强度的倒数,C越小,正则化越强。默认1.0。 # solver: 优化算法,对于小数据集或L1正则,'liblinear'是好的选择;对于大数据集,'sag'或'saga'更快。 # max_iter: 最大迭代次数,如果数据复杂或特征多,需要调大。 model = LogisticRegression(penalty='l2', C=1.0, solver='liblinear', max_iter=1000, random_state=42) model.fit(X_train_scaled, y_train) # 5. 预测与评估 y_pred = model.predict(X_test_scaled) y_pred_proba = model.predict_proba(X_test_scaled)[:, 1] # 取正类(1)的概率 print("准确率:", accuracy_score(y_test, y_pred)) print("\n混淆矩阵:\n", confusion_matrix(y_test, y_pred)) print("\n分类报告:\n", classification_report(y_test, y_pred)) print("ROC-AUC分数:", roc_auc_score(y_test, y_pred_proba)) # 6. 查看模型参数(可解释性) print(f"\n模型截距 (bias): {model.intercept_[0]:.4f}") # 可以将权重与特征名对应查看 feature_importance = pd.DataFrame({ 'feature': data.feature_names, 'coefficient': model.coef_[0] }).sort_values(by='coefficient', key=abs, ascending=False) print("\n特征权重(绝对值)排序:\n", feature_importance.head(10))scikit-learn使用精髓:
solver选择:这是新手最容易困惑的参数之一。liblinear适用于小数据集,支持L1和L2正则化;lbfgs是默认选项,对大多数情况表现良好且稳定;sag和saga在样本量很大(数万以上)时速度更快。如果用了L1正则化,必须选择liblinear或saga。C参数:这是正则化强度的倒数。C越小,正则化惩罚越重,模型越简单,越可能欠拟合;C越大,惩罚越轻,模型越复杂,越可能过拟合。通常需要通过交叉验证在一个范围(如[0.001, 0.01, 0.1, 1, 10, 100])内进行网格搜索来寻找最佳值。fit_transform与transform:务必记住,标准化器(StandardScaler)要在训练集上fit(计算均值和标准差),然后在训练集和测试集上分别transform。绝对不能用测试集的数据去fit标准化器,这会引入数据泄露,导致模型评估结果虚高。
5. 模型评估:准确率远远不够
模型训练好了,预测也做了,但怎么知道它好不好?对于分类模型,尤其是像逻辑回归这样输出概率的模型,评估需要多维度进行,不能只看一个准确率。
5.1 混淆矩阵与衍生指标
混淆矩阵是评估分类模型的基础,它统计了真实类别和预测类别的所有组合情况。
| 预测为负 (0) | 预测为正 (1) | |
|---|---|---|
| 真实为负 (0) | TN (真阴性) | FP (假阳性) |
| 真实为正 (1) | FN (假阴性) | TP (真阳性) |
从混淆矩阵可以计算出多个关键指标:
- 准确率 (Accuracy):
(TP+TN) / (TP+TN+FP+FN)。最直观,但在不平衡数据集中具有欺骗性。例如,如果99%的客户都不流失,那么一个永远预测“不流失”的模型准确率也有99%,但这毫无用处。 - 精确率 (Precision):
TP / (TP+FP)。在所有被预测为正的样本中,真正为正的比例。它关注的是预测的“准度”。在垃圾邮件过滤中,我们追求高精确率,因为把正常邮件误判为垃圾邮件(FP)的代价很高。 - 召回率 (Recall):
TP / (TP+FN)。在所有真实为正的样本中,被成功预测出来的比例。它关注的是模型的“查全率”。在疾病筛查中,我们追求高召回率,因为漏诊(FN)的代价很高。 - F1分数 (F1-Score):
2 * Precision * Recall / (Precision + Recall)。精确率和召回率的调和平均数,在两者需要权衡时是一个综合指标。
实操心得:永远不要只依赖准确率。第一步永远是看混淆矩阵,了解错误的具体类型(是FP多还是FN多)。然后根据业务目标选择侧重精确率还是召回率。例如,在金融反欺诈中,初期可能更看重召回率(宁可错杀,不可放过),随着模型成熟,再优化精确率(减少误伤)。
5.2 ROC曲线与AUC:衡量模型排序能力
逻辑回归输出的是概率,我们通过设定阈值(如0.5)将其转换为类别。但阈值是可以调整的。调整阈值,会改变混淆矩阵,进而改变精确率和召回率。
ROC曲线描绘了当阈值从1到0变化时,真正例率 (TPR = Recall)和假正例率 (FPR = FP / (FP+TN))的变化关系。一个完美的模型,其ROC曲线会紧贴左上角(TPR=1, FPR=0)。而一条对角线(从(0,0)到(1,1))代表一个随机猜测的模型。
AUC (Area Under Curve)就是ROC曲线下的面积。AUC的取值范围在0.5到1之间。
- AUC = 0.5:模型没有区分能力,和随机猜测一样。
- 0.5 < AUC < 0.7:模型有较弱的区分能力。
- 0.7 ≤ AUC < 0.8:模型有一定的区分能力,可以接受。
- 0.8 ≤ AUC < 0.9:模型有良好的区分能力。
- AUC ≥ 0.9:模型有非常优秀的区分能力。
AUC有一个更直观的概率解释:随机选取一个正样本和一个负样本,模型对正样本给出的预测概率高于负样本的概率,就是AUC值。因此,AUC衡量的是模型对样本的排序能力,而不依赖于具体的分类阈值,这对于评估概率输出模型非常关键。
5.3 概率校准:你的概率可信吗?
逻辑回归理论上输出的是校准过的概率(即预测概率为0.7的样本中,应该有70%确实是正类)。但在实际中,特别是样本不平衡或使用了强正则化时,模型的概率输出可能会“失真”,变得过于自信或过于保守。
检查概率校准性的常用工具是校准曲线(Calibration Curve),也叫可靠性曲线。其方法是:
- 将预测概率区间[0,1]分成若干个桶(如10个)。
- 计算每个桶内所有样本的平均预测概率。
- 计算每个桶内所有样本的真实正例比例。
- 以平均预测概率为横坐标,真实正例比例为纵坐标作图。
一条完美的校准曲线应该是一条对角线(y=x)。如果曲线在对角线之上,说明模型预测概率偏低(过于保守);如果在对角线之下,说明预测概率偏高(过于自信)。
在scikit-learn中,可以使用CalibrationDisplay.from_estimator来绘制校准曲线。如果发现概率校准性不好,可以考虑使用CalibratedClassifierCV对模型进行事后校准,这对于需要精确概率输出的场景(如风险定价)至关重要。
6. 进阶话题与实战避坑指南
掌握了基础,我们来看看逻辑回归在实际应用中那些容易踩坑和需要深入思考的地方。
6.1 过拟合与正则化:给模型加上“紧箍咒”
当模型参数过多或训练数据不足时,逻辑回归很容易记住训练数据中的噪声,导致在训练集上表现极好,但在未知数据(测试集)上表现糟糕,这就是过拟合。
对抗过拟合最有效的手段之一就是正则化。它在损失函数中增加一个惩罚项,用于约束参数的大小,迫使模型变得“简单”。
- L1正则化 (Lasso):在损失函数中加入参数绝对值之和的惩罚项 ( \lambda \sum_{j=1}^{n} |\theta_j| )。L1正则化有一个神奇的特性:它倾向于将一些不重要的特征的权重精确地压缩到0,从而实现特征选择。如果你的特征维度很高,且怀疑很多特征无关,L1正则化是首选。
- L2正则化 (Ridge):在损失函数中加入参数平方和的惩罚项 ( \frac{\lambda}{2} \sum_{j=1}^{n} \theta_j^2 )。L2正则化会让所有参数的权重都均匀地缩小,但不会完全为0。它更擅长处理特征间存在多重共线性的情况,使模型更稳定。
在scikit-learn的LogisticRegression中,通过penalty参数选择l1或l2(注意solver的兼容性),通过C参数控制正则化强度(C = 1 / \lambda,C越小,惩罚越重)。
如何选择?一个实用的流程是:先使用L2正则化,因为它更稳定。如果模型特征非常多(例如成百上千),并且你需要一个稀疏模型(即只有少数特征起作用)以便解释或部署,那么可以尝试L1正则化。也可以使用ElasticNet(penalty='elasticnet'),它是L1和L2的混合,通过l1_ratio参数控制混合比例。
6.2 特征工程:模型性能的上限
逻辑回归是一个线性模型,它的表达能力直接受限于特征。可以说,特征工程的质量决定了逻辑回归性能的上限。以下是一些针对逻辑回归的特征工程技巧:
- 数值特征处理:除了标准化,还可以考虑分箱(Binning)。将连续年龄分为“青年”、“中年”、“老年”,然后进行独热编码。这可以捕捉非线性关系,因为逻辑回归在分箱后的每个区间内是线性的,但整体上变成了分段线性模型。
- 类别特征编码:必须将文字型类别(如“城市”)转化为数字。独热编码(One-Hot Encoding)是最常用且安全的方法,为每个类别创建一个新的二值特征。避免使用简单的标签编码(如北京=1,上海=2,深圳=3),因为这会给模型强加一个不存在的顺序关系。
- 交叉特征:这是提升逻辑回归能力的关键。既然模型本身是线性的,我们可以手动创建非线性特征。例如,预测贷款违约,不仅有“收入”和“负债”两个特征,还可以加入“负债收入比”(负债/收入)这个交叉特征。更复杂的可以用多项式特征(如
PolynomialFeatures),但需警惕维度爆炸。 - 处理缺失值:逻辑回归本身不能处理缺失值。常用方法包括:删除缺失样本(如果缺失很少)、用中位数/众数/均值填充、或者使用“是否缺失”作为一个新的二值特征。
踩坑实录:我曾在一个用户流失预测项目中,直接使用了用户的“最近一次登录时间戳”(Unix时间戳)作为特征。模型效果很差。后来意识到,时间戳的绝对值对模型来说没有意义。将其转化为“距离今天的天数”,并进一步分箱为“近1天”、“近3天”、“近7天”、“7天以上”,模型效果显著提升。这个教训是:对于特征,要始终思考其业务意义,并将其转化为模型能理解的、有信息量的形式。
6.3 多分类问题:从“一对多”到“多项式”
逻辑回归本质是二分类器。那如何处理多分类问题(如鸢尾花分类,有3个品种)?有两种主流策略:
一对多(One-vs-Rest, OvR):这是最常用的方法。假设有K个类别,我们就训练K个独立的二分类逻辑回归模型。对于第i个模型,将类别i的样本作为正例,其他所有类别的样本作为负例。预测时,让K个模型都给出样本属于其正类的概率,然后选择概率最高的那个类别作为最终预测。
- 优点:简单,训练K个模型即可,每个模型只用到部分数据。
- 缺点:当类别不平衡时,每个二分类器的正负样本比例可能严重失调,影响性能。而且可能存在“灰色区域”,即多个模型给出的概率都较高或都较低。
多项式逻辑回归(Multinomial Logistic Regression):这是一种更“原生”的方法。它直接修改模型,使其输出一个K维向量,向量每个元素代表样本属于对应类别的概率(通过Softmax函数保证和为1)。它一次性考虑所有类别之间的关系。
- 优点:理论更优雅,通常能获得比OvR略好一点的效果,特别是当类别互斥且特征空间有重叠时。
- 缺点:计算复杂度稍高。
在scikit-learn中,设置LogisticRegression的multi_class参数即可选择。对于大多数情况,multi_class='ovr'(默认)已经足够好且更快。如果类别不多且你追求最优性能,可以尝试multi_class='multinomial'并配合合适的solver(如lbfgs或saga)。
6.4 样本不平衡:当99%都是负样本
这是实际中最常见也最棘手的问题之一。例如在欺诈检测中,正常交易占99.9%,欺诈交易只占0.1%。如果直接用原始数据训练,模型会倾向于把所有样本都预测为“正常”,因为这样就能获得99.9%的准确率,但对欺诈的召回率为0。
解决方法:
- 调整类别权重:这是最简单有效的方法。在
LogisticRegression中设置class_weight='balanced'。算法会自动根据类别频率调整损失函数中每个类别的权重,让模型更关注少数类。其权重通常与类别频率成反比。 - 重采样:
- 过采样:增加少数类样本的副本。最简单的方法是随机复制,但容易导致过拟合。更高级的方法是SMOTE(Synthetic Minority Over-sampling Technique),它通过插值在少数类样本之间生成新的合成样本。
- 欠采样:随机丢弃一部分多数类样本。这会损失信息,只适用于数据量非常大的情况。
- 通常建议先尝试
class_weight,如果效果不佳再考虑复杂的重采样技术。
- 调整决策阈值:如前所述,默认0.5的阈值是基于类别平衡假设的。在不平衡时,可以降低阈值(如降到0.1),让模型更“敏感”地预测正类,从而提高召回率。但这会降低精确率,需要在两者间权衡。可以通过P-R曲线或ROC曲线来帮助选择最佳阈值。
一个黄金法则:在处理不平衡数据时,永远不要用准确率作为评估指标!必须使用混淆矩阵、精确率、召回率、F1分数,特别是ROC-AUC。AUC对类别不平衡相对不敏感,是一个更可靠的模型排序能力指标。同时,在测试集上评估时,务必确保测试集的类别分布与真实业务场景一致。
