人工智能概念之六:分类任务的评估指标(用逻辑回归演示回归效果评估)

在机器学习领域,构建分类模型只是完成了任务的一半,另一半关键在于如何科学评估模型性能。不同的业务场景对模型有着不同的要求,例如医疗诊断需要尽可能减少漏诊(高召回率),而广告投放则希望降低无效展示(高精确率)。本文将系统讲解分类任务的核心评估指标,并通过电信客户流失案例进行实战演示。

一、分类评估的核心指标体系

1.1 混淆矩阵:分类结果的全景透视

混淆矩阵
混淆矩阵示意图 混淆矩阵示意图 混淆矩阵示意图
绘图代码:

import matplotlib.pyplot as plt  # 导入Matplotlib的pyplot模块,用于绘图,约定别名为plt

# 设置中文字体显示(解决中文乱码问题)
plt.rcParams['font.sans-serif'] = ['SimHei']  # 将默认字体设置为"SimHei(黑体)",确保中文标签正常显示
plt.rcParams['axes.unicode_minus'] = False  # 修复负号显示为方块的问题(确保特殊符号渲染正常)


def plot_text_confusion_matrix():
    """
    绘制「混淆矩阵概念示意图」(纯文本+表格形式,用于教学演示混淆矩阵的四个核心指标)
    """
    # 1. 创建画布与坐标轴
    # fig:画布对象;ax:坐标轴对象;figsize=(10,5)设置画布宽10英寸、高5英寸(教学场景需清晰展示文字)
    fig, ax = plt.subplots(figsize=(10, 5))
    ax.axis('off')  # 关闭坐标轴(因为要画表格,不需要传统坐标轴)

    # 2. 定义混淆矩阵的单元格内容(二维列表,每行对应「实际类别」,每列对应「预测类别」)
    # 结构:[实际正例行, 实际负例行],每个单元格包含「术语+公式+解释」(换行符\n实现多行显示)
    cell_text = [
        ['TP (真正例)\n真实为正,预测为正\n(真正例)', 'FN (假负例)\n真实为正,预测为负\n(假负例)'],
        ['FP (假正例)\n真实为负,预测为正\n(假正例)', 'TN (真负例)\n真实为负,预测为负\n(真负例)']
    ]

    # 3. 创建表格(核心步骤)
    # loc='center':表格居中放置;cellLoc='center':单元格内容水平+垂直居中;
    # bbox=[x, y, width, height]:控制表格在画布中的位置和大小(x=0.2, y=0.2是左下起点,宽0.6、高0.6,占画布中间区域)
    tb = plt.table(
        cellText=cell_text,  # 传入单元格文本
        loc='center',  # 表格在画布中的对齐方式
        cellLoc='center',  # 单元格内文本的对齐方式
        bbox=[0.2, 0.2, 0.6, 0.6]  # 精细调整表格位置(相对于画布0-1坐标系)
    )

    # 4. 设置表格字体大小(覆盖自动缩放,确保教学场景文字清晰)
    tb.auto_set_font_size(False)  # 关闭自动字体大小调整
    tb.set_fontsize(12)  # 手动设置字体大小为12号

    # 5. 美化单元格样式(增加背景色区分,提升可读性)
    # 遍历所有单元格(key是(row, col)元组,cell是单元格对象)
    for key, cell in tb.get_celld().items():
        cell.set_edgecolor('black')  # 设置单元格边框为黑色(清晰区分边界)
        # 通过行列索引和的奇偶性,交替设置背景色(增强视觉层次)
        if (key[0] + key[1]) % 2 == 0:
            cell.set_facecolor('#F0F8FF')  # 浅蓝色背景(实际正例行+预测正例列、实际负例行+预测负例列)
        else:
            cell.set_facecolor('#F5F5DC')  # 浅米色背景(实际正例行+预测负例列、实际负例行+预测正例列)

    # 6. 添加列标签(标注「预测方向」:预测为正例 / 预测为负例)
    # plt.text(x, y, text, ...):在画布指定坐标(x,y)处添加文本(x,y范围0-1,对应画布比例)
    plt.text(0.35, 0.85, '预测为正例', fontsize=14, fontweight='bold', ha='center', va='center')
    plt.text(0.65, 0.85, '预测为负例', fontsize=14, fontweight='bold', ha='center', va='center')

    # 7. 添加行标签(标注「实际方向」:实际为正例 / 实际为负例)
    plt.text(0.1, 0.7, '实际为正例', fontsize=14, fontweight='bold', ha='center', va='center')
    plt.text(0.1, 0.3, '实际为负例', fontsize=14, fontweight='bold', ha='center', va='center')

    # 8. 添加主标题(突出图表主题,pad=20增加标题与表格的间距)
    plt.title('混淆矩阵(Confusion Matrix)', fontsize=16, pad=20)

    # 9. 自动调整布局(避免元素重叠)并显示图形
    plt.tight_layout()  # 智能调整边距、间距
    plt.show()  # 弹出窗口显示绘制的混淆矩阵示意图


# 调用函数,生成并显示混淆矩阵教学图
plot_text_confusion_matrix()

混淆矩阵是评估分类模型的基础工具,以二分类为例,它通过四个核心指标展现模型的预测全貌:

  • TP (True Positive):真实为正,预测为正的样本数(真正例)
  • FP (False Positive):真实为负,预测为正的样本数(假正例)
  • FN (False Negative):真实为正,预测为负的样本数(假负例)
  • TN (True Negative):真实为负,预测为负的样本数(真负例)

以客户流失场景为例,假设真实有200个流失客户(正例)和800个未流失客户(负例):

  • 若模型正确预测120个流失客户,误判80个未流失客户为流失:TP=120, FP=80, FN=80, TN=720
  • 混淆矩阵可直观展现模型在正负类上的预测偏差

1.2 精确率、召回率与F1-score:多维度评估模型

lr.score(nx_test, y_test)  # 获取准确率,等同于 (TP+TN)/(TP+TN+FP+FN)
classification_report(y_test, pred_y, target_names=['正例', '反例'])	# 获取分类评估报告
  1. 精确率(Precision):预测正例的准确性
    P r e c i s i o n = T P T P + F P Precision = \frac{TP}{TP + FP} Precision=TP+FPTP

    • 表示模型预测为正例的样本中,实际为正例的比例
    • 应用场景:广告推荐(减少无效展示)、垃圾邮件过滤(降低误判)
  2. 召回率(Recall):捕捉正例的能力
    R e c a l l = T P T P + F N Recall = \frac{TP}{TP + FN} Recall=TP+FNTP

    • 表示实际正例中被模型正确识别的比例
    • 应用场景:疾病筛查(不漏掉患者)、灾害预警(减少漏报)
  3. F1-score:精确率与召回率的平衡
    F 1 = 2 × P r e c i s i o n × R e c a l l P r e c i s i o n + R e c a l l F1 = 2 \times \frac{Precision \times Recall}{Precision + Recall} F1=2×Precision+RecallPrecision×Recall

    • 当精确率和召回率一方极高而另一方极低时,F1-score会显著降低
    • 适用场景:类别不平衡问题(如正负样本比1:100),避免单一指标的片面性

3.3 ROC曲线与AUC:阈值无关的评估标准

roc_auc_score(y_test, pred_proba)  # 计算ROC曲线下面积
  1. ROC曲线:真正例率与假正例率的权衡

    • 真正例率(TPR) T P R = T P T P + F N TPR = \frac{TP}{TP + FN} TPR=TP+FNTP(正例的捕捉能力)
    • 假正例率(FPR) F P R = F P F P + T N FPR = \frac{FP}{FP + TN} FPR=FP+TNFP(负例的误判程度)
    • ROC曲线通过绘制不同阈值下的(TPR, FPR)点,展现模型在正负例间的权衡关系
  2. AUC值:ROC曲线下的面积

    • 取值范围:[0, 1],值越大模型分类能力越强
    • 关键意义
      • AUC=1:完美分类器(所有正例排序高于负例)
      • AUC=0.5:随机分类(模型无区分能力)
      • AUC<0.5:性能比随机还差(可能标签反转)
  3. AUC 相比精确率的核心优势

    • 抗类别不平衡:不受正负样本比例影响,适合罕见事件(如流失、疾病)评估。
    • 阈值无关:整合所有阈值下的表现,不依赖单一决策点。
    • 评估排序能力:衡量正例得分高于负例的概率,聚焦模型区分本质。
    • 跨模型可比:无需统一阈值或数据分布,直接对比不同模型性能。

3.4 AUC计算过程详解:一个简单实例

  1. 准备数据:真实标签与预测分数
    假设我们有一个包含6个样本的二分类问题,真实标签(1为正例,0为负例)和模型预测分数如下:
样本索引真实标签(y_true)预测分数(pred_score)
110.8
200.7
310.6
400.5
510.4
600.3
  1. 按预测分数降序排序数据
    为了后续计算阈值变化对分类结果的影响,首先将样本按预测分数从高到低排序:
排序后索引真实标签(sorted_y_true)预测分数(sorted_score)
110.8
200.7
310.6
400.5
510.4
600.3
  1. 统计正负例总数
  • 正例总数(num_pos):真实标签为1的样本数,即3个(索引1、3、5)。
  • 负例总数(num_neg):真实标签为0的样本数,即3个(索引2、4、6)。
  1. 遍历所有可能的阈值,计算TPR和FPR
    阈值的可能取值包括:大于最大分数、每个预测分数本身、小于最小分数。对于6个样本,共有7个阈值点(n+1,n为样本数)。以下是每个阈值的计算过程:

AUC计算示意图
A U C 计算示意图 AUC计算示意图 AUC计算示意图

阈值1:threshold > 0.8(所有样本预测为负例)
- 预测正例数:0
- 混淆矩阵:
- TP(真正例)= 0(预测正例且真实正例的数量)
- FP(假正例)= 0(预测正例且真实负例的数量)
- FN(假负例)= 3(预测负例且真实正例的数量)
- TN(真负例)= 3(预测负例且真实负例的数量)
- TPR(真正例率)= TP / num_pos = 0 / 3 = 0
- FPR(假正例率)= FP / num_neg = 0 / 3 = 0
- 坐标点:(0, 0)

阈值2:0.7 < threshold ≤ 0.8(仅样本1预测为正例)
- 预测正例:样本1(分数0.8 ≥ 阈值)
- 混淆矩阵:
- TP = 1(样本1为正例)
- FP = 0(预测正例中无负例)
- FN = 2(样本3、5为正例但预测为负例)
- TN = 3(样本2、4、6预测为负例且为真)
- TPR = 1 / 3 ≈ 0.33
- FPR = 0 / 3 = 0
- 坐标点:(0, 0.33)

阈值3:0.6 < threshold ≤ 0.7(样本1、2预测为正例)
- 预测正例:样本1(0.8)、样本2(0.7)
- 混淆矩阵:
- TP = 1(样本1为正例)
- FP = 1(样本2为负例)
- FN = 2(样本3、5为正例)
- TN = 2(样本4、6为负例)
- TPR = 1 / 3 ≈ 0.33
- FPR = 1 / 3 ≈ 0.33
- 坐标点:(0.33, 0.33)

阈值4:0.5 < threshold ≤ 0.6(样本1、2、3预测为正例)
- 预测正例:样本1(0.8)、样本2(0.7)、样本3(0.6)
- 混淆矩阵:
- TP = 2(样本1、3为正例)
- FP = 1(样本2为负例)
- FN = 1(样本5为正例)
- TN = 2(样本4、6为负例)
- TPR = 2 / 3 ≈ 0.67
- FPR = 1 / 3 ≈ 0.33
- 坐标点:(0.33, 0.67)

阈值5:0.4 < threshold ≤ 0.5(样本1-4预测为正例)
- 预测正例:样本1-4(分数≥0.5)
- 混淆矩阵:
- TP = 2(样本1、3为正例)
- FP = 2(样本2、4为负例)
- FN = 1(样本5为正例)
- TN = 1(样本6为负例)
- TPR = 2 / 3 ≈ 0.67
- FPR = 2 / 3 ≈ 0.67
- 坐标点:(0.67, 0.67)

阈值6:0.3 < threshold ≤ 0.4(样本1-5预测为正例)
- 预测正例:样本1-5(分数≥0.4)
- 混淆矩阵:
- TP = 3(样本1、3、5为正例)
- FP = 2(样本2、4为负例)
- FN = 0(所有正例均预测为正)
- TN = 1(样本6为负例)
- TPR = 3 / 3 = 1
- FPR = 2 / 3 ≈ 0.67
- 坐标点:(0.67, 1)

阈值7:threshold ≤ 0.3(所有样本预测为正例)
- 预测正例:全部6个样本
- 混淆矩阵:
- TP = 3(所有正例)
- FP = 3(所有负例)
- FN = 0
- TN = 0
- TPR = 3 / 3 = 1
- FPR = 3 / 3 = 1
- 坐标点:(1, 1)

  1. 整理ROC曲线坐标点
    将所有阈值对应的FPR和TPR按顺序排列,得到ROC曲线的坐标点:
(0, 0) → (0, 0.33) → (0.33, 0.33) → (0.33, 0.67) → (0.67, 0.67) → (0.67, 1) → (1, 1)
  1. 用梯形法计算AUC(曲线下面积)
    AUC的本质是计算ROC曲线与x轴之间的面积,可通过累加每个相邻点形成的梯形面积得到。梯形面积公式为:
    面积 = 1 2 × ( x 2 − x 1 ) × ( y 1 + y 2 ) 面积 = \frac{1}{2} \times (x_2 - x_1) \times (y_1 + y_2) 面积=21×(x2x1)×(y1+y2)

    分步骤计算:
    (1) 点1→点2:(0, 0) → (0, 0.33)

    • 底(x差):0 - 0 = 0
    • 面积:0.5 × 0 × (0 + 0.33) = 0

(2) 点2→点3:(0, 0.33) → (0.33, 0.33)

  • 底:0.33 - 0 = 0.33
  • 高(y平均):(0.33 + 0.33) = 0.66
  • 面积:0.5 × 0.33 × 0.66 ≈ 0.1089

(3) 点3→点4:(0.33, 0.33) → (0.33, 0.67)

  • 底:0.33 - 0.33 = 0
  • 面积:0

(4) 点4→点5:(0.33, 0.67) → (0.67, 0.67)

  • 底:0.67 - 0.33 = 0.34
  • 高:(0.67 + 0.67) = 1.34
  • 面积:0.5 × 0.34 × 1.34 ≈ 0.2278

(5) 点5→点6:(0.67, 0.67) → (0.67, 1)

  • 底:0.67 - 0.67 = 0
  • 面积:0

(6) 点6→点7:(0.67, 1) → (1, 1)

  • 底:1 - 0.67 = 0.33
  • 高:(1 + 1) = 2
  • 面积:0.5 × 0.33 × 2 = 0.33
  1. 累加所有梯形面积得到AUC
    A U C = 0 + 0.1089 + 0 + 0.2278 + 0 + 0.33 ≈ 0.6667 AUC = 0 + 0.1089 + 0 + 0.2278 + 0 + 0.33 ≈ 0.6667 AUC=0+0.1089+0+0.2278+0+0.330.6667

总结:AUC的意义
在这个例子中,AUC≈0.67,说明模型的分类能力略高于随机猜测(随机猜测的AUC=0.5)。AUC的本质是正例预测分数高于负例的概率,计算过程通过ROC曲线下面积量化了模型在不同阈值下的整体表现。

绘图代码:

import numpy as np  # 导入NumPy库,用于数值计算(如数组操作、矩阵运算)
import matplotlib.pyplot as plt  # 导入Matplotlib库,用于数据可视化(绘制ROC曲线)
from sklearn.metrics import auc  # 导入sklearn的auc函数,用于验证手动计算的AUC值

# 设置中文字体显示(解决中文乱码问题)
plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置默认字体为黑体,确保中文标签正常显示
plt.rcParams['axes.unicode_minus'] = False  # 确保坐标轴负号正常显示(避免显示为方块)

# 示例数据(简化版二分类问题,便于教学理解)
y_true = np.array([1, 0, 1, 0, 1, 0])  # 真实标签(1为正例,0为负例)
pred_scores = np.array([0.8, 0.7, 0.6, 0.5, 0.4, 0.3])  # 预测分数(已按降序排列,便于阈值遍历)

# 初始化ROC曲线的起点(0,0)
fpr_list = [0]  # 假正例率列表(初始为0)
tpr_list = [0]  # 真正例率列表(初始为0)

# 统计正负例总数(用于后续指标标准化)
num_pos = np.sum(y_true == 1)  # 正例数量(3个)
num_neg = np.sum(y_true == 0)  # 负例数量(3个)

# 定义所有可能的阈值点(共7个,覆盖所有可能的分类边界)
# 包含:大于最大分数、每个预测分数、小于最小分数
thresholds = [1.0, 0.8, 0.7, 0.6, 0.5, 0.4, -1.0]

# 遍历每个阈值,计算对应的TPR和FPR
for i in range(len(thresholds)):
    threshold = thresholds[i]  # 当前阈值

    # 根据阈值生成预测标签(>=阈值为正例,否则为负例)
    y_pred = (pred_scores >= threshold).astype(int)

    # 计算混淆矩阵核心指标
    tp = np.sum((y_pred == 1) & (y_true == 1))  # 真正例:预测正且实际正
    fp = np.sum((y_pred == 1) & (y_true == 0))  # 假正例:预测正但实际负
    fn = np.sum((y_pred == 0) & (y_true == 1))  # 假负例:预测负但实际正
    tn = np.sum((y_pred == 0) & (y_true == 0))  # 真负例:预测负且实际负

    # 计算真正例率(TPR)和假正例率(FPR)
    tpr = tp / num_pos if num_pos > 0 else 0  # TPR = TP/总正例(衡量正例捕捉能力)
    fpr = fp / num_neg if num_neg > 0 else 0  # FPR = FP/总负例(衡量负例误判程度)

    # 保存当前阈值下的指标,用于绘制ROC曲线
    fpr_list.append(fpr)
    tpr_list.append(tpr)

# 创建画布(8英寸宽,6英寸高)
plt.figure(figsize=(8, 6))

# 绘制每个(TPR, FPR)点并标注坐标
for i in range(len(fpr_list)):
    fpr, tpr = fpr_list[i], tpr_list[i]

    # 绘制蓝色圆点
    plt.plot(fpr, tpr, 'bo', markersize=6)

    # 动态调整文本位置(避免重叠)
    text_x, text_y = fpr, tpr
    if fpr < 0.5:
        text_x += 0.03  # 左侧点右移
    else:
        text_x -= 0.03  # 右侧点左移
    if tpr < 0.5:
        text_y += 0.03  # 下侧点上移
    else:
        text_y -= 0.03  # 上侧点下移

    # 标注坐标信息
    plt.text(text_x, text_y, f'({fpr:.2f}, {tpr:.2f})', fontsize=9)

# 绘制ROC曲线(红色实线)和随机猜测线(黑色虚线)
plt.plot(fpr_list, tpr_list, 'r-', lw=2, label='ROC曲线')  # 连接所有点形成曲线
plt.plot([0, 1], [0, 1], 'k--', lw=1, label='随机猜测')  # 对角线(AUC=0.5)

# 手动计算AUC(梯形面积法)
auc_score_manual = 0
areas = []  # 存储每个梯形面积

for i in range(1, len(fpr_list)):
    x1, y1 = fpr_list[i - 1], tpr_list[i - 1]  # 前一个点坐标
    x2, y2 = fpr_list[i], tpr_list[i]  # 当前点坐标

    # 计算梯形面积:(上底+下底)*高/2
    area = (x2 - x1) * (y1 + y2) / 2
    areas.append(area)
    auc_score_manual += area  # 累加面积

    # 可视化梯形区域(绿色半透明填充)
    plt.fill_between([x1, x2], [y1, y2], color='green', alpha=0.2)

    # 标注该段面积值
    xc, yc = (x1 + x2) / 2, (y1 + y2) / 2  # 梯形中心点
    plt.text(xc, yc, f'{area:.4f}', fontsize=8, ha='center')

# 设置图表样式
plt.xlim([-0.05, 1.05])  # x轴范围(略超出0-1,避免边缘被裁剪)
plt.ylim([-0.05, 1.05])  # y轴范围
plt.xlabel('假正例率 (FPR)')  # x轴标签
plt.ylabel('真正例率 (TPR)')  # y轴标签
plt.title('ROC 曲线构建过程可视化\n绿色区域为每段AUC积分面积')  # 标题
plt.legend(loc="lower right")  # 图例位置
plt.grid(True)  # 显示网格线,便于观察坐标
plt.tight_layout()  # 自动调整布局,避免元素重叠

# 显示总AUC值(左上角白色背景标注)
plt.text(0.02, 0.95, f'AUC 值为:{auc_score_manual:.4f}',
         fontsize=12, ha='left', va='top',
         bbox=dict(facecolor='white', alpha=0.8, edgecolor='black'))

# 显示图表
plt.show()

二、实战演示:电信客户流失评估

原始代码及数据集下载网页链接

2.1 代码说明及差异分析

这段代码是一个完整的客户流失预测分析流程,使用逻辑回归模型。与原始代码相比,主要区别在于可视化分析部分更加完整,新增了混淆矩阵热力图、ROC曲线和月费分布对比图。

2.2 核心流程:

  1. 数据准备

    • 导入必要的库(Pandas, NumPy, Matplotlib, Seaborn, Scikit-learn)
    • 加载CSV数据集(churn.csv)
    • 中文显示设置
  2. 数据预处理

    • 分类变量独热编码(pd.get_dummies
    • 列名重命名为中文(提高可读性)
    • 分离特征和目标变量(客户流失)
  3. 可视化分析(新增内容):

    • 客户流失分布柱状图
    • 混淆矩阵热力图
    • ROC曲线(含AUC值)
    • 月费分布箱线图
  4. 建模与评估

    • 数据分割(80%训练集/20%测试集)
    • 特征归一化(MinMaxScaler)
    • 逻辑回归模型训练
    • 输出评估指标:
      • 准确率
      • AUC值
      • 混淆矩阵(TP/FP/FN/TN)
      • 分类报告(精确率/召回率/F1值)
  5. 特征重要性分析

    • 基于模型系数绝对值排序
    • 可视化Top 10重要特征

2.3 与原始代码的主要区别:

功能模块原始代码当前代码差异说明
可视化分析
混淆矩阵热力图×新增直观的矩阵可视化
ROC曲线×新增模型性能评估图
月费分布箱线图×新增关键特征分析
模型评估
混淆矩阵详细解析×新增TP/FP/FN/TN输出
分类报告标签命名数字标签中文标签提高可读性
其他
图表保存功能部分保存全部保存保存所有可视化结果
公式解释×ROC图中添加TPR/FPR公式
输出术语准确性“精确率”“准确率”修正评估指标名称

2.4 关键改进点:

  1. 更完整的可视化:新增3种专业图表,直观展示模型性能和数据分布
  2. 更详细的评估:增加混淆矩阵解析和中文分类报告
  3. 更强的解释性
    • 特征重要性分析
    • ROC图中的数学公式说明
    • 中文标签提升可读性
  4. 结果保存:所有图表自动保存为图片文件
  5. 术语修正:将不准确的"精确率"改为正确的"准确率"
import pandas as pd                # 导入Pandas库,用于读取、处理表格数据(如CSV文件)
import numpy as np                 # 导入NumPy库,用于数值计算和矩阵操作
import matplotlib.pyplot as plt    # 导入Matplotlib库,用于基础数据可视化
import seaborn as sns              # 导入Seaborn库,用于高级统计数据可视化(如热力图、箱线图)

# 导入机器学习相关模块
from sklearn.preprocessing import MinMaxScaler  # 导入特征缩放工具,用于归一化特征
from sklearn.linear_model import LogisticRegression  # 导入逻辑回归模型,用于二分类任务
from sklearn.metrics import classification_report, roc_auc_score, confusion_matrix, roc_curve  # 导入评估指标
from sklearn.model_selection import train_test_split  # 导入数据分割工具,用于划分训练集和测试集

# 设置中文字体显示(解决中文乱码问题)
plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置中文字体为黑体
plt.rcParams['axes.unicode_minus'] = False  # 确保负号正常显示

# 1. 加载数据集
base_data = pd.read_csv('churn.csv')  # 从CSV文件读取客户流失数据
print("数据基本信息:")
base_data.info()  # 查看数据结构(列名、数据类型、非空值数量等)

# 2. 数据预处理
# 2.1 分类特征编码与列名重命名
data = pd.get_dummies(base_data, drop_first=True)  # 独热编码:将分类变量转为0/1数值,drop_first避免多重共线性
data.rename(
    columns={
        'gender_Male': '性别', 'Partner_att': '配偶状态', 'Dependents_att': '受抚养人状态',
        'landline': '固定电话',
        'internet_att': '互联网服务', 'internet_other': '其他互联网服务', 'StreamingTV': '电视流媒体服务',
        'StreamingMovies': '电影流媒体服务',
        'Contract_Month': '月付合同', 'Contract_1YR': '一年期合同', 'PaymentBank': '银行支付',
        'PaymentCreditcard': '信用卡支付',
        'PaymentElectronic': '电子支付', 'MonthlyCharges': '月费', 'TotalCharges': '总费用'
    },
    inplace=True  # 直接修改原DataFrame,无需创建新对象
)

# 2.2 分离特征与目标变量
data['客户流失'] = data['Churn_Yes']  # 将原始目标变量重命名为"客户流失"
data.drop(['Churn_Yes'], axis=1, inplace=True)  # 删除原始目标列,避免重复
data_x = data.iloc[:, :-1]  # 提取所有特征列(除最后一列)
data_y = data.iloc[:, -1]   # 提取目标列(最后一列)

# 2.3 可视化:客户流失分布
plt.figure(figsize=(10, 6))  # 设置图表尺寸(宽10英寸,高6英寸)
sns.countplot(x='客户流失', data=data)  # 绘制流失/未流失客户的数量对比图
plt.title('客户流失分布情况')  # 设置图表标题
plt.savefig('churn_distribution.png')  # 保存图表为图片
plt.show()  # 显示图表

# 3. 数据分割:80%训练集,20%测试集
x_train, x_test, y_train, y_test = train_test_split(
    data_x, data_y, test_size=0.2, random_state=44  # test_size=0.2表示20%数据作为测试集,random_state固定随机种子确保可复现
)

# 4. 特征工程:归一化处理(将特征缩放到[0,1]区间)
ss = MinMaxScaler()  # 初始化MinMaxScaler
nx_train = ss.fit_transform(x_train)  # 对训练集进行拟合和转换
nx_test = ss.transform(x_test)  # 对测试集仅进行转换(使用训练集的缩放参数)

# 5. 模型训练:逻辑回归
lr = LogisticRegression(max_iter=1000)  # 初始化模型,max_iter=1000防止迭代次数不足
lr.fit(nx_train, y_train)  # 用训练数据训练模型

# 6. 模型预测
pred_y = lr.predict(nx_test)  # 预测测试集的类别标签(0或1)
pred_proba = lr.predict_proba(nx_test)[:, 1]  # 获取正类(流失)的概率值

# 7. 模型评估
print(f"\n模型准确率:{lr.score(nx_test, y_test):.4f}")  # 计算并打印准确率
print(f"AUC值:{roc_auc_score(y_test, pred_proba):.4f}")  # 计算并打印AUC值(模型区分能力指标)

# 解析混淆矩阵获取核心指标
tn, fp, fn, tp = confusion_matrix(y_test, pred_y).ravel()  # 提取混淆矩阵的四个核心值
print(f"TP(真正例): {tp}, FP(假正例): {fp}, FN(假负例): {fn}, TN(真负例): {tn}")

# 打印详细分类报告(包含精确率、召回率、F1-score)
print("\n分类评估报告:")
print(classification_report(y_test, pred_y, target_names=['未流失', '流失']))

# 8. 可视化分析
# 8.1 混淆矩阵热力图
plt.figure(figsize=(8, 6))  # 设置图表尺寸
cm = confusion_matrix(y_test, pred_y)  # 计算混淆矩阵
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',  # 绘制热力图,annot=True显示数值,fmt='d'显示整数
            xticklabels=['未流失', '流失'],  # x轴标签
            yticklabels=['未流失', '流失'])  # y轴标签
plt.title('混淆矩阵')  # 设置标题
plt.xlabel('预测标签')  # x轴标题
plt.ylabel('真实标签')  # y轴标题
plt.savefig('confusion_matrix.png')  # 保存图片
plt.show()  # 显示图表

# 8.2 ROC曲线(评估模型在不同阈值下的表现)
plt.figure(figsize=(8, 6))  # 设置图表尺寸
fpr, tpr, thresholds = roc_curve(y_test, pred_proba)  # 计算ROC曲线所需的FPR、TPR和阈值
plt.plot(fpr, tpr,  # 绘制ROC曲线
         label=f'逻辑回归 (AUC = {roc_auc_score(y_test, pred_proba):.4f})\n'
               r'TPR=$\frac{TP}{TP+FN}$, FPR=$\frac{FP}{FP+TN}$')  # 添加图例(包含AUC值和公式)
plt.plot([0, 1], [0, 1], 'k--')  # 绘制随机猜测线(对角线)
plt.xlabel('假正例率(FPR)')  # x轴标题
plt.ylabel('真正例率(TPR)')  # y轴标题
plt.title('ROC曲线')  # 设置标题
plt.legend(loc='lower right')  # 设置图例位置
plt.savefig('roc_curve.png')  # 保存图片
plt.show()  # 显示图表

# 8.3 特征重要性分析(基于模型系数绝对值)
plt.figure(figsize=(12, 8))  # 设置图表尺寸
importance = pd.DataFrame({
    '特征': data_x.columns,
    '重要性': np.abs(lr.coef_[0])  # 取系数的绝对值作为重要性指标
}).sort_values('重要性', ascending=False)  # 按重要性降序排序

sns.barplot(x='重要性', y='特征', data=importance.head(10))  # 绘制前10个重要特征的条形图
plt.title('Top 10特征重要性')  # 设置标题
plt.tight_layout()  # 自动调整布局
plt.savefig('feature_importance.png')  # 保存图片
plt.show()  # 显示图表

# 8.4 流失与未流失客户的月费分布对比
plt.figure(figsize=(10, 6))  # 设置图表尺寸
sns.boxplot(x='客户流失', y='月费', data=data)  # 绘制箱线图对比两组数据分布
plt.title('流失与非流失客户的月费分布')  # 设置标题
plt.savefig('monthly_charges_comparison.png')  # 保存图片
plt.show()  # 显示图表

三、评估结果分析与模型调整

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.1 混淆矩阵真实解读(基于实际运行结果)

通过 confusion_matrix 计算得到:
[ TN=932 FP=95 FN=184 TP=198 ] \begin{bmatrix} \text{TN=932} & \text{FP=95} \\ \text{FN=184} & \text{TP=198} \end{bmatrix} [TN=932FN=184FP=95TP=198]

  • TN(真负例)=932:932名实际未流失的客户,模型正确预测为“未流失”(多数类预测稳定)。
  • FP(假正例)=95:95名实际未流失的客户,被误判为“流失”(可能导致无效营销,浪费运营资源)。
  • FN(假负例)=184:184名实际流失的客户,被漏判为“未流失”(最核心的业务痛点——错失挽留机会)。
  • TP(真正例)=198:仅198名实际流失的客户被正确识别(占比仅 ( \frac{198}{198+184} \approx 52% ),召回率偏低)。

3.2 分类报告深度分析

              precision    recall  f1-score   support
         未流失       0.84      0.91      0.87      1027  # 多数类(未流失)预测稳定
          流失       0.68      0.52      0.59       382  # 少数类(流失)捕捉能力弱
  • 未流失类
    • 精确率(0.84):模型预测为“未流失”的客户中,84%确实未流失(误判少)。
    • 召回率(0.91):91%的实际未流失客户被正确识别(覆盖全面)。
  • 流失类
    • 精确率(0.68):模型预测为“流失”的客户中,仅68%真的流失(误判多,营销资源可能浪费)。
    • 召回率(0.52):仅52%的实际流失客户被识别(漏判严重,直接影响挽留策略的效果)。

3.3 ROC曲线与AUC解读(AUC=0.8331)

  • AUC值:0.8331 > 0.5,说明模型具备区分“流失”和“未流失”客户的能力(优于随机猜测)。
  • ROC曲线形态
    • 低FPR区间(左半段):曲线快速上升,表明模型对高置信度的流失客户识别准确(如“总费用极高+月付合同”的客户)。
    • 高FPR区间(右半段):曲线增长放缓,说明对**边缘案例(如低费用+长期合同客户)**的区分能力有限。

3.4 根据评估指标优化代码

  1. SMOTE过采样技术

    # 新增代码
    from imblearn.over_sampling import SMOTE
    smote = SMOTE(random_state=44)
    x_res, y_res = smote.fit_resample(nx_train, y_train)  # 对训练集进行过采样
    
    • 作用:通过合成少数类样本(流失客户),平衡训练数据分布,提升模型对流失类的识别能力。
    • 效果:流失类召回率从52%提升至80%(见分类报告),漏判流失客户数量(FN)从184降至77。
  2. 模型参数调整

    # 原代码:lr = LogisticRegression(max_iter=1000)
    lr = LogisticRegression(max_iter=1000, C=0.99)  # 新增正则化参数C
    
    • 作用C=0.99 略微增强L2正则化,防止过拟合(默认C=1.0)。

3.5 核心指标变化

在这里插入图片描述

在这里插入图片描述

  • 模型准确率:0.7282

  • AUC值:0.8332

  • TP(真正例): 305, FP(假正例): 306, FN(假负例): 77, TN(真负例): 721

  • 分类评估报告:

            precision    recall  f1-score   support
    
      	  未流失       0.90      0.70      0.79      1027
      		流失       0.50      0.80      0.61       382
         accuracy                           0.73      1409
        macro avg       0.70      0.75      0.70      1409
     weighted avg       0.79      0.73      0.74      1409
    
  1. 类别不平衡处理(SMOTE过采样)

    • 流失类召回率从 52%跃升至80%(分类报告),混淆矩阵中 漏判流失客户(FN=77)大幅减少,解决了原模型“对少数类捕捉不足”的核心问题。
    • 代价:误判未流失客户(FP=306)略有增加,准确率微降至0.7282,但符合“优先挽留流失客户”的业务优先级。
  2. 模型区分能力稳定

    • AUC值保持 0.8332,ROC曲线形态未明显变化(仍远离对角线),证明过采样未损害模型对正负样本的区分能力。

3.6 优化后完整代码:

import pandas as pd  # 导入Pandas库,用于读取、处理表格数据(如CSV文件)
import numpy as np  # 导入NumPy库,用于数值计算和矩阵操作
import matplotlib.pyplot as plt  # 导入Matplotlib库,用于基础数据可视化
import seaborn as sns  # 导入Seaborn库,用于高级统计数据可视化(如热力图、箱线图)

# 导入机器学习相关模块
from sklearn.preprocessing import MinMaxScaler  # 导入特征缩放工具,用于归一化特征
from sklearn.linear_model import LogisticRegression  # 导入逻辑回归模型,用于二分类任务
from sklearn.metrics import classification_report, roc_auc_score, confusion_matrix, roc_curve  # 导入评估指标
from sklearn.model_selection import train_test_split  # 导入数据分割工具,用于划分训练集和测试集

from imblearn.over_sampling import SMOTE	# 从imblearn库中导入SMOTE类,用于处理数据集中的类别不平衡问题

# 设置中文字体显示(解决中文乱码问题)
plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置中文字体为黑体
plt.rcParams['axes.unicode_minus'] = False  # 确保负号正常显示

# 1. 加载数据集
base_data = pd.read_csv('churn.csv')  # 从CSV文件读取客户流失数据
print("数据基本信息:")
base_data.info()  # 查看数据结构(列名、数据类型、非空值数量等)

# 2. 数据预处理
# 2.1 分类特征编码与列名重命名
data = pd.get_dummies(base_data, drop_first=True)  # 独热编码:将分类变量转为0/1数值,drop_first避免多重共线性
data.rename(
    columns={
        'gender_Male': '性别', 'Partner_att': '配偶状态', 'Dependents_att': '受抚养人状态',
        'landline': '固定电话',
        'internet_att': '互联网服务', 'internet_other': '其他互联网服务', 'StreamingTV': '电视流媒体服务',
        'StreamingMovies': '电影流媒体服务',
        'Contract_Month': '月付合同', 'Contract_1YR': '一年期合同', 'PaymentBank': '银行支付',
        'PaymentCreditcard': '信用卡支付',
        'PaymentElectronic': '电子支付', 'MonthlyCharges': '月费', 'TotalCharges': '总费用'
    },
    inplace=True  # 直接修改原DataFrame,无需创建新对象
)

# 2.2 分离特征与目标变量
data['客户流失'] = data['Churn_Yes']  # 将原始目标变量重命名为"客户流失"
data.drop(['Churn_Yes'], axis=1, inplace=True)  # 删除原始目标列,避免重复
data_x = data.iloc[:, :-1]  # 提取所有特征列(除最后一列)
data_y = data.iloc[:, -1]  # 提取目标列(最后一列)

# 2.3 可视化:客户流失分布
plt.figure(figsize=(10, 6))  # 设置图表尺寸(宽10英寸,高6英寸)
sns.countplot(x='客户流失', data=data)  # 绘制流失/未流失客户的数量对比图
plt.title('客户流失分布情况')  # 设置图表标题
plt.savefig('churn_distribution.png')  # 保存图表为图片
plt.show()  # 显示图表

# 3. 数据分割:80%训练集,20%测试集
x_train, x_test, y_train, y_test = train_test_split(
    data_x, data_y, test_size=0.2, random_state=44  # test_size=0.2表示20%数据作为测试集,random_state固定随机种子确保可复现
)

# 4. 特征工程:归一化处理(将特征缩放到[0,1]区间)
ss = MinMaxScaler()  # 初始化MinMaxScaler
nx_train = ss.fit_transform(x_train)  # 对训练集进行拟合和转换
nx_test = ss.transform(x_test)  # 对测试集仅进行转换(使用训练集的缩放参数)

# 实例化SMOTE对象,用于处理数据不平衡问题
# 参数random_state用于确保每次运行时生成的结果一致
smote = SMOTE(random_state=44)

# 使用SMOTE的fit_resample方法对训练数据进行重采样
# nx_train是特征数据,y_train是对应标签
# 该方法返回重采样后的特征数据x_res和标签数据y_res
# 重采样旨在通过合成少数类样本,平衡数据集,提高模型的泛化能力
x_res, y_res = smote.fit_resample(nx_train, y_train)


# 5. 模型训练:逻辑回归
lr = LogisticRegression(max_iter=1000, C=0.99)  # 初始化模型,max_iter=1000防止迭代次数不足,C=0.99略微启用正则化
lr.fit(x_res, y_res)  # 用训练数据训练模型

# 6. 模型预测
pred_y = lr.predict(nx_test)  # 预测测试集的类别标签(0或1)
pred_proba = lr.predict_proba(nx_test)[:, 1]  # 获取正类(流失)的概率值

# 7. 模型评估
print(f"\n模型准确率:{lr.score(nx_test, y_test):.4f}")  # 计算并打印准确率
print(f"AUC值:{roc_auc_score(y_test, pred_proba):.4f}")  # 计算并打印AUC值(模型区分能力指标)

# 解析混淆矩阵获取核心指标
tn, fp, fn, tp = confusion_matrix(y_test, pred_y).ravel()  # 提取混淆矩阵的四个核心值
print(f"TP(真正例): {tp}, FP(假正例): {fp}, FN(假负例): {fn}, TN(真负例): {tn}")

# 打印详细分类报告(包含精确率、召回率、F1-score)
print("\n分类评估报告:")
print(classification_report(y_test, pred_y, target_names=['未流失', '流失']))

# 8. 可视化分析
# 8.1 混淆矩阵热力图
plt.figure(figsize=(8, 6))  # 设置图表尺寸
cm = confusion_matrix(y_test, pred_y)  # 计算混淆矩阵
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',  # 绘制热力图,annot=True显示数值,fmt='d'显示整数
            xticklabels=['未流失', '流失'],  # x轴标签
            yticklabels=['未流失', '流失'])  # y轴标签
plt.title('混淆矩阵')  # 设置标题
plt.xlabel('预测标签')  # x轴标题
plt.ylabel('真实标签')  # y轴标题
plt.savefig('confusion_matrix.png')  # 保存图片
plt.show()  # 显示图表

# 8.2 ROC曲线(评估模型在不同阈值下的表现)
plt.figure(figsize=(8, 6))  # 设置图表尺寸
fpr, tpr, thresholds = roc_curve(y_test, pred_proba)  # 计算ROC曲线所需的FPR、TPR和阈值
plt.plot(fpr, tpr,  # 绘制ROC曲线
         label=f'逻辑回归 (AUC = {roc_auc_score(y_test, pred_proba):.4f})\n'
               r'TPR=$\frac{TP}{TP+FN}$, FPR=$\frac{FP}{FP+TN}$')  # 添加图例(包含AUC值和公式)
plt.plot([0, 1], [0, 1], 'k--')  # 绘制随机猜测线(对角线)
plt.xlabel('假正例率(FPR)')  # x轴标题
plt.ylabel('真正例率(TPR)')  # y轴标题
plt.title('ROC曲线')  # 设置标题
plt.legend(loc='lower right')  # 设置图例位置
plt.savefig('roc_curve.png')  # 保存图片
plt.show()  # 显示图表

# 8.3 特征重要性分析(基于模型系数绝对值)
plt.figure(figsize=(12, 8))  # 设置图表尺寸
importance = pd.DataFrame({
    '特征': data_x.columns,
    '重要性': np.abs(lr.coef_[0])  # 取系数的绝对值作为重要性指标
}).sort_values('重要性', ascending=False)  # 按重要性降序排序

sns.barplot(x='重要性', y='特征', data=importance.head(10))  # 绘制前10个重要特征的条形图
plt.title('Top 10特征重要性')  # 设置标题
plt.tight_layout()  # 自动调整布局
plt.savefig('feature_importance.png')  # 保存图片
plt.show()  # 显示图表

# 8.4 流失与未流失客户的月费分布对比
plt.figure(figsize=(10, 6))  # 设置图表尺寸
sns.boxplot(x='客户流失', y='月费', data=data)  # 绘制箱线图对比两组数据分布
plt.title('流失与非流失客户的月费分布')  # 设置标题
plt.savefig('monthly_charges_comparison.png')  # 保存图片
plt.show()  # 显示图表

四、总结

本文主要讲解分类模型评估体系,以电信客户流失为案例说明评估指标的作用:

  1. 核心指标

    • 混淆矩阵拆解TP/FP/FN/TN,直观展现预测偏差;
    • 精确率、召回率、F1-score多维度评估模型,F1平衡两者矛盾;
    • ROC曲线通过TPR/FPR权衡展示模型性能,AUC值量化区分能力,优势在于抗类别不平衡、阈值无关。
  2. 评估流程

    • 数据预处理:独热编码、特征归一化;
    • 模型训练:逻辑回归处理客户流失预测;
    • 评估优化:原始模型流失类召回率52%,通过SMOTE过采样提升至80%,AUC保持0.83稳定。
  3. 关键结论

    • 类别不平衡导致模型漏判流失客户(FN=184),过采样有效缓解;
    • 特征重要性显示“总费用”“月付合同”是流失主因,可针对性运营;
    • 优化后模型虽牺牲部分准确率(0.73),但流失捕捉能力显著提升,符合业务需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值