文章目录
在机器学习领域,构建分类模型只是完成了任务的一半,另一半关键在于如何科学评估模型性能。不同的业务场景对模型有着不同的要求,例如医疗诊断需要尽可能减少漏诊(高召回率),而广告投放则希望降低无效展示(高精确率)。本文将系统讲解分类任务的核心评估指标,并通过电信客户流失案例进行实战演示。
一、分类评估的核心指标体系
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=['正例', '反例']) # 获取分类评估报告
-
精确率(Precision):预测正例的准确性
P r e c i s i o n = T P T P + F P Precision = \frac{TP}{TP + FP} Precision=TP+FPTP- 表示模型预测为正例的样本中,实际为正例的比例
- 应用场景:广告推荐(减少无效展示)、垃圾邮件过滤(降低误判)
-
召回率(Recall):捕捉正例的能力
R e c a l l = T P T P + F N Recall = \frac{TP}{TP + FN} Recall=TP+FNTP- 表示实际正例中被模型正确识别的比例
- 应用场景:疾病筛查(不漏掉患者)、灾害预警(减少漏报)
-
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曲线下面积
-
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)点,展现模型在正负例间的权衡关系
-
AUC值:ROC曲线下的面积
- 取值范围:[0, 1],值越大模型分类能力越强
- 关键意义:
- AUC=1:完美分类器(所有正例排序高于负例)
- AUC=0.5:随机分类(模型无区分能力)
- AUC<0.5:性能比随机还差(可能标签反转)
-
AUC 相比精确率的核心优势
- 抗类别不平衡:不受正负样本比例影响,适合罕见事件(如流失、疾病)评估。
- 阈值无关:整合所有阈值下的表现,不依赖单一决策点。
- 评估排序能力:衡量正例得分高于负例的概率,聚焦模型区分本质。
- 跨模型可比:无需统一阈值或数据分布,直接对比不同模型性能。
3.4 AUC计算过程详解:一个简单实例
- 准备数据:真实标签与预测分数
假设我们有一个包含6个样本的二分类问题,真实标签(1为正例,0为负例)和模型预测分数如下:
样本索引 | 真实标签(y_true) | 预测分数(pred_score) |
---|---|---|
1 | 1 | 0.8 |
2 | 0 | 0.7 |
3 | 1 | 0.6 |
4 | 0 | 0.5 |
5 | 1 | 0.4 |
6 | 0 | 0.3 |
- 按预测分数降序排序数据
为了后续计算阈值变化对分类结果的影响,首先将样本按预测分数从高到低排序:
排序后索引 | 真实标签(sorted_y_true) | 预测分数(sorted_score) |
---|---|---|
1 | 1 | 0.8 |
2 | 0 | 0.7 |
3 | 1 | 0.6 |
4 | 0 | 0.5 |
5 | 1 | 0.4 |
6 | 0 | 0.3 |
- 统计正负例总数
- 正例总数(num_pos):真实标签为1的样本数,即3个(索引1、3、5)。
- 负例总数(num_neg):真实标签为0的样本数,即3个(索引2、4、6)。
- 遍历所有可能的阈值,计算TPR和FPR
阈值的可能取值包括:大于最大分数、每个预测分数本身、小于最小分数。对于6个样本,共有7个阈值点(n+1,n为样本数)。以下是每个阈值的计算过程:
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)
- 整理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)
-
用梯形法计算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×(x2−x1)×(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
- 累加所有梯形面积得到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.33≈0.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 核心流程:
-
数据准备:
- 导入必要的库(Pandas, NumPy, Matplotlib, Seaborn, Scikit-learn)
- 加载CSV数据集(churn.csv)
- 中文显示设置
-
数据预处理:
- 分类变量独热编码(
pd.get_dummies
) - 列名重命名为中文(提高可读性)
- 分离特征和目标变量(客户流失)
- 分类变量独热编码(
-
可视化分析(新增内容):
- 客户流失分布柱状图
- 混淆矩阵热力图
- ROC曲线(含AUC值)
- 月费分布箱线图
-
建模与评估:
- 数据分割(80%训练集/20%测试集)
- 特征归一化(MinMaxScaler)
- 逻辑回归模型训练
- 输出评估指标:
- 准确率
- AUC值
- 混淆矩阵(TP/FP/FN/TN)
- 分类报告(精确率/召回率/F1值)
-
特征重要性分析:
- 基于模型系数绝对值排序
- 可视化Top 10重要特征
2.3 与原始代码的主要区别:
功能模块 | 原始代码 | 当前代码 | 差异说明 |
---|---|---|---|
可视化分析 | |||
混淆矩阵热力图 | × | √ | 新增直观的矩阵可视化 |
ROC曲线 | × | √ | 新增模型性能评估图 |
月费分布箱线图 | × | √ | 新增关键特征分析 |
模型评估 | |||
混淆矩阵详细解析 | × | √ | 新增TP/FP/FN/TN输出 |
分类报告标签命名 | 数字标签 | 中文标签 | 提高可读性 |
其他 | |||
图表保存功能 | 部分保存 | 全部保存 | 保存所有可视化结果 |
公式解释 | × | √ | ROC图中添加TPR/FPR公式 |
输出术语准确性 | “精确率” | “准确率” | 修正评估指标名称 |
2.4 关键改进点:
- 更完整的可视化:新增3种专业图表,直观展示模型性能和数据分布
- 更详细的评估:增加混淆矩阵解析和中文分类报告
- 更强的解释性:
- 特征重要性分析
- ROC图中的数学公式说明
- 中文标签提升可读性
- 结果保存:所有图表自动保存为图片文件
- 术语修正:将不准确的"精确率"改为正确的"准确率"
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 根据评估指标优化代码
-
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。
-
模型参数调整
# 原代码: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
-
类别不平衡处理(SMOTE过采样):
- 流失类召回率从 52%跃升至80%(分类报告),混淆矩阵中 漏判流失客户(FN=77)大幅减少,解决了原模型“对少数类捕捉不足”的核心问题。
- 代价:误判未流失客户(FP=306)略有增加,准确率微降至0.7282,但符合“优先挽留流失客户”的业务优先级。
-
模型区分能力稳定:
- 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() # 显示图表
四、总结
本文主要讲解分类模型评估体系,以电信客户流失为案例说明评估指标的作用:
-
核心指标:
- 混淆矩阵拆解TP/FP/FN/TN,直观展现预测偏差;
- 精确率、召回率、F1-score多维度评估模型,F1平衡两者矛盾;
- ROC曲线通过TPR/FPR权衡展示模型性能,AUC值量化区分能力,优势在于抗类别不平衡、阈值无关。
-
评估流程:
- 数据预处理:独热编码、特征归一化;
- 模型训练:逻辑回归处理客户流失预测;
- 评估优化:原始模型流失类召回率52%,通过SMOTE过采样提升至80%,AUC保持0.83稳定。
-
关键结论:
- 类别不平衡导致模型漏判流失客户(FN=184),过采样有效缓解;
- 特征重要性显示“总费用”“月付合同”是流失主因,可针对性运营;
- 优化后模型虽牺牲部分准确率(0.73),但流失捕捉能力显著提升,符合业务需求。