本文为 Python 60 Days Challenge 课程学习整理,原作者与原 Notebook 链接见「课程资料」。代码按章节顺序运行;语法示意和故意报错的反例已单独标注。
数据准备: 将课程仓库中的 data.csv/data.xlsx 放在 Notebook 的当前工作目录,路径已移除原作者电脑的盘符。读取 Excel 需要 openpyxl。各节额外依赖按文中安装说明准备。
文中保留的截图、性能表和数值属于原讲义的历史示例,不是本次修订后的重新训练结果。实际输出受数据、版本、随机种子和训练预算影响。
本次校订: 阈值只在验证集选择,选定后固定用于独立测试集。过采样只作用于训练集;普通 SMOTE 对独热类别插值可能产生无实际含义的分数类别,混合类型数据应考虑 SMOTENC,或采用随机过采样/类别权重。比较多个策略时不得用测试报告反复选型。
打卡任务
- 阅读本节讲义或 Notebook
- 跑通示例代码
- 整理 3 个关键点
- 完成当日打卡输出
课程资料
- 训练营讲义:https://docs.qq.com/doc/DTFhndExSRGNHS3B6?nlc=1
- GitHub 仓库:https://github.com/cheer932041235/Python60DaysChallenge
- 本日 Notebook:Day15 不平衡数据集的处理 copy.ipynb
学习内容
一、数据准备与基线模型
1.1 数据预处理
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.base import clone
from sklearn.compose import ColumnTransformer, make_column_selector
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import train_test_split, cross_val_score, cross_validate
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix, precision_score, recall_score
data = pd.read_csv('data.csv').drop(columns=['Id'], errors='ignore')
# 标签不能作为特征或用填补器补造;这里只排除缺失标签的记录。
data = data.dropna(subset=['Credit Default'])
preprocessor = ColumnTransformer([
('numeric', SimpleImputer(strategy='median', keep_empty_features=True),
make_column_selector(dtype_include=np.number)),
('categorical', Pipeline([
('imputer', SimpleImputer(strategy='most_frequent', keep_empty_features=True)),
('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False))]),
make_column_selector(dtype_exclude=np.number))
])
def make_classifier(model):
# 把填补和编码放进模型流水线,每个交叉验证折只在本折训练数据上拟合。
return Pipeline([('preprocess', clone(preprocessor)), ('model', model)])
X = data.drop(columns=['Credit Default'])
y = data['Credit Default']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y)
X_train, X_val_raw, y_train, y_val = train_test_split(
X_train, y_train, test_size=0.25, random_state=42, stratify=y_train)
# 本节不做交叉验证;预处理器仅在训练集拟合。
X_train_raw, X_test_raw = X_train.copy(), X_test.copy()
train_array = preprocessor.fit_transform(X_train_raw)
names = preprocessor.get_feature_names_out()
X_train = pd.DataFrame(train_array, columns=names, index=X_train_raw.index)
X_test = pd.DataFrame(preprocessor.transform(X_test_raw), columns=names, index=X_test_raw.index)
X_val = pd.DataFrame(preprocessor.transform(X_val_raw), columns=names, index=X_val_raw.index)
1.2 基准模型结果
from sklearn.ensemble import RandomForestClassifier #随机森林分类器
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score # 用于评估分类器性能的指标
from sklearn.metrics import classification_report, confusion_matrix #用于生成分类报告和混淆矩阵
import warnings #用于忽略警告信息
# 保留警告,便于发现弃用接口和数值问题。 # 忽略所有警告信息
# --- 1. 默认参数的随机森林 ---
# 评估基准模型,这里确实不需要验证集
print("--- 1. 默认参数随机森林 (训练集 -> 测试集) ---")
rf_model = RandomForestClassifier(random_state=42)
rf_model.fit(X_train, y_train) # 在训练集上训练
rf_pred = rf_model.predict(X_test) # 在测试集上预测
print("\n默认随机森林 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred))
print("默认随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred))
二、数据层面处理方法
数据层面方法通过调整训练集的类别分布来缓解不平衡问题。所有方法都使用相同的测试集进行评估,确保公平对比。
2.1 过采样方法
- 过采样一般包含2种做法:随机采样和SMOTE
- 过采样是把少的类别补充和多的类别一样多,欠采样是把多的类别减少和少的类别一样
- 一般都是缺数据,所以很少用欠采样
2.1.1 随机过采样ROS
随机过采样是从少数类中随机选择样本,并将其复制后添加到训练集。
随机过采样的步骤如下:
- 确定少数类的样本数。
- 从少数类中随机选择样本,并将其复制。
- 将复制的样本添加到训练集。
随机过采样的优点是,它可以增加少数类的样本数,从而提高模型的泛化能力。小。
随机过采样的缺点是,它可能会增加训练集的大小,从而增加训练时间。此外,它可能会增加噪声,并且可能会增加模型的偏差。
做难题,复制n份,不如做新的题目。
# 需要安装imbalanced-learn库
# 这个库是专门用于处理不平衡数据集的,提供了多种重采样方法
# !pip install -U imbalanced-learn
## --- 2. 随机过采样 (Random Oversampling) ---
print("\n--- 2. 随机过采样 (Random Oversampling) ---")
# 导入随机过采样器
from imblearn.over_sampling import RandomOverSampler
import time
# 实例化随机过采样器,设置 sampling_strategy='minority' 表示只对少数类进行采样
# random_state 用于确保结果可复现
ros = RandomOverSampler(sampling_strategy='minority', random_state=42)
# 对训练集进行过采样。注意:只对训练集进行操作,测试集保持不变
start_time = time.time()
X_resampled_ros, y_resampled_ros = ros.fit_resample(X_train, y_train)
end_time = time.time()
print(f"随机过采样耗时: {end_time - start_time:.4f} 秒")
# 检查采样后的类别分布
print("\n随机过采样后训练集类别分布:")
print(pd.Series(y_resampled_ros).value_counts())
# 使用过采样后的数据训练随机森林模型
rf_model_ros = RandomForestClassifier(random_state=42)
rf_model_ros.fit(X_resampled_ros, y_resampled_ros) # 在过采样后的训练集上训练
rf_pred_ros = rf_model_ros.predict(X_test) # 在原始测试集上预测
print("\n随机过采样后模型 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred_ros))
print("随机过采样后模型 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred_ros))
2.2 smote过采样
smote过采样是合成样本的方法。
- 对于少数类中的每个样本,计算它与少数类中其他样本的距离,得到其$k$近邻(一般$k$取5或其他合适的值)。
- 从$k$近邻中随机选择一个样本。
- 计算选定的近邻样本与原始样本之间的差值。
- 生成一个在0到1之间的随机数。
- 将差值乘以随机数,然后加到原始样本上,得到一个新的合成样本。
- 重复上述步骤,直到合成出足够数量的少数类样本,使得少数类和多数类样本数量达到某种平衡。
- 使用过采样后的数据集训练模型并评估模型性能。
SMOTE的核心思想是通过在少数类样本的特征空间中进行插值来合成新的样本
## --- 3. SMOTE 过采样 (Synthetic Minority Over-sampling Technique) ---
print("\n--- 3. SMOTE 过采样 (Synthetic Minority Over-sampling Technique) ---")
# 导入 SMOTE 过采样器
from imblearn.over_sampling import SMOTE
# 实例化 SMOTE 过采样器
smote = SMOTE(random_state=42)
# 对训练集进行 SMOTE 过采样
start_time = time.time()
X_resampled_smote, y_resampled_smote = smote.fit_resample(X_train, y_train)
end_time = time.time()
print(f"SMOTE 过采样耗时: {end_time - start_time:.4f} 秒")
# 检查采样后的类别分布
print("\nSMOTE 过采样后训练集类别分布:")
print(pd.Series(y_resampled_smote).value_counts())
# 使用 SMOTE 过采样后的数据训练随机森林模型
rf_model_smote = RandomForestClassifier(random_state=42)
rf_model_smote.fit(X_resampled_smote, y_resampled_smote) # 在过采样后的训练集上训练
rf_pred_smote = rf_model_smote.predict(X_test) # 在原始测试集上预测
print("\nSMOTE 过采样后模型 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred_smote))
print("SMOTE 过采样后模型 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred_smote))
2.2 欠采样方法
2.2.1 随机欠采样
随机删除多数类样本。可能丢失重要信息。
## --- 6. 欠采样:随机欠采样 (Random Under-Sampling) ---
print("\n--- 6. 欠采样:随机欠采样 (Random Under-Sampling) ---")
# 导入随机欠采样器
from imblearn.under_sampling import RandomUnderSampler
# 实例化随机欠采样器,sampling_strategy='majority' 表示只对多数类进行采样
rus = RandomUnderSampler(sampling_strategy='majority', random_state=42)
# 对训练集进行欠采样
start_time = time.time()
# X_resampled_rus 和 y_resampled_rus 是欠采样后的训练集
X_resampled_rus, y_resampled_rus = rus.fit_resample(X_train, y_train)
end_time = time.time()
print(f"随机欠采样耗时: {end_time - start_time:.4f} 秒")
# 检查采样后的类别分布
print("\n随机欠采样后训练集类别分布:")
# 欠采样后,多数类样本数量将等于少数类样本数量
print(pd.Series(y_resampled_rus).value_counts())
# 使用欠采样后的数据训练随机森林模型
rf_model_rus = RandomForestClassifier(random_state=42)
rf_model_rus.fit(X_resampled_rus, y_resampled_rus)
rf_pred_rus = rf_model_rus.predict(X_test) # 在原始测试集上预测
print("\n随机欠采样后模型 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred_rus))
print(confusion_matrix(y_test, rf_pred_rus))
随机欠采样可能会大幅提高少数类的召回率 (Recall),但可能会因为丢失了太多多数类信息而导致整体准确率 (Accuracy)下降
2.2.2 ENN
ENN 是一种基于近邻的欠采样技术,它的核心思想不是为了平衡数据集,而是作为一种数据清洁(Data Cleaning)或噪声移除的手段。ENN 的作用是清洗训练集中的噪声和边界模糊的多数类样本,从而减少模型对这些模糊区域的学习,专注于更明确的类别特征。
核心思想:
- 识别“坏”样本: ENN 遍历数据集中的每一个多数类样本。
- 近邻判断: 对于某个多数类样本 $x$,它检查 $x$ 的 $K$ 个最近邻居(通常 $K=3$)。
- 移除原则: 如果 $x$ 的多数邻居数量少于其所有 $K$ 个邻居中的一半(即,如果 $x$ 被它的邻居误分类了),那么 $x$ 就会被认为是噪声或边界模糊的样本,并被移除。
简单来说:ENN 删除了那些与自身类别不一致的多数类样本。这样做的目的是平滑分类边界,使多数类和少数类之间的界限更加清晰,有助于后续训练的模型(如随机森林)做出更准确的决策。
## --- 7. 欠采样:Edited Nearest Neighbors (ENN) ---
print("\n--- 7. 欠采样:Edited Nearest Neighbors (ENN) ---")
from imblearn.under_sampling import EditedNearestNeighbours
# 实例化 ENN (默认参数通常用于清理多数类样本)
# sampling_strategy='all' 表示对所有类别应用规则,但实际主要移除多数类中的噪声
enn = EditedNearestNeighbours(sampling_strategy='all', n_neighbors=3, kind_sel='all')
# 对训练集进行 ENN 欠采样(数据清洗)
start_time = time.time()
X_resampled_enn, y_resampled_enn = enn.fit_resample(X_train, y_train)
end_time = time.time()
print(f"ENN 欠采样耗时: {end_time - start_time:.4f} 秒")
# 检查采样后的类别分布
print("\nENN 欠采样后训练集类别分布:")
# 注意:ENN 是数据清洗,不会完全平衡数据集,多数类样本会减少,但仍多于少数类
print(pd.Series(y_resampled_enn).value_counts())
# 使用 ENN 欠采样后的数据训练随机森林模型
rf_model_enn = RandomForestClassifier(random_state=42)
rf_model_enn.fit(X_resampled_enn, y_resampled_enn)
rf_pred_enn = rf_model_enn.predict(X_test)
print("\nENN 欠采样后模型 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred_enn))
print(confusion_matrix(y_test, rf_pred_enn))
ENN 的目标是清洗,它对少数类召回率的提升可能不如随机欠采样明显,但它通常能提供更稳健的性能提升,特别是当数据中存在大量噪声时。
2.3 混合采样
混合采样结合了过采样和欠采样的优点,旨在:
- 增加少数类的代表性(通过合成样本)。
- 清理/删除多数类的噪声和冗余样本。
SMOTE + ENN 是最经典、最有效的混合采样组合之一,因为它结合了:
- SMOTE:在少数类周围生成新的合成样本,解决样本数量不足的问题。
- ENN:随后应用 ENN 规则来清理数据集,移除那些位于分类边界上的模糊样本(包括 SMOTE 生成的样本和原始多数类样本),使分类边界更加清晰。
## --- 8. 混合采样:SMOTE + ENN (SMOTENN) ---
print("\n--- 8. 混合采样:SMOTE + ENN (SMOTENN) ---")
# 导入 SMOTENN
from imblearn.combine import SMOTEENN
# 实例化 SMOTEENN,它内部集成了 SMOTE 和 ENN
# random_state 用于确保结果可复现
smote_enn = SMOTEENN(random_state=42)
# 对训练集进行混合采样
start_time = time.time()
# X_resampled_smotenn 和 y_resampled_smotenn 是混合采样后的训练集
X_resampled_smotenn, y_resampled_smotenn = smote_enn.fit_resample(X_train, y_train)
end_time = time.time()
print(f"SMOTE + ENN 混合采样耗时: {end_time - start_time:.4f} 秒")
# 检查采样后的类别分布
print("\nSMOTE + ENN 混合采样后训练集类别分布:")
# 经过 SMOTE 和 ENN 清理后,数据集通常会接近平衡,但多数类会略有减少
print(pd.Series(y_resampled_smotenn).value_counts())
# 使用混合采样后的数据训练随机森林模型
rf_model_smotenn = RandomForestClassifier(random_state=42)
rf_model_smotenn.fit(X_resampled_smotenn, y_resampled_smotenn)
rf_pred_smotenn = rf_model_smotenn.predict(X_test) # 在原始测试集上预测
print("\nSMOTE + ENN 混合采样后模型 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred_smotenn))
print("SMOTE + ENN 混合采样后模型 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred_smotenn))
采样后结果并没有变好,可能是:
- 没有调整超参数
- 删除了太多有效样本
- 该方法不适合该数据
三、 算法层面
不改变数据,而是调整模型训练过程或使用对不平衡数据鲁棒的算法。
在 RandomForestClassifier 中应用 (class_weight 参数)
Scikit-learn 中的 RandomForestClassifier 提供了 class_weight 参数来实现代价敏感学习:
class_weight=None (默认值):- 所有类别被赋予相同的权重 (1) 。
- 算法在构建树和计算分裂标准(如基尼不纯度)时,不区分多数类和少数类。
- 在不平衡数据上,这自然导致模型偏向多数类。
class_weight='balanced' :- 算法自动根据训练数据
y中各类别的频率来调整权重。 - 权重计算方式与类别频率成反比:
weight = n_samples / (n_classes * np.bincount(y))。 - 这意味着少数类样本获得更高的权重,多数类样本获得较低的权重。
- 目的是在训练中“放大”少数类的重要性,促使模型提升对少数类的识别能力。
- 算法自动根据训练数据
class_weight={dict} (手动设置):- 可以提供一个字典,手动为每个类别标签指定权重,例如
class_weight={0: 1, 1: 10}表示类别 1 的权重是类别 0 的 10 倍。
- 可以提供一个字典,手动为每个类别标签指定权重,例如
优点:
- 从模型学习的根本上解决问题。
- 可能得到泛化能力更强的模型。
- 许多常用算法内置支持,实现方便。
注意: 使用
class_weight 时,推荐结合交叉验证(特别是StratifiedKFold)来可靠地评估其效果和模型的稳定性。
print("\n--- 代价敏感学习:设置 class_weight='balanced' ---")
# 使用原始训练集 X_train, y_train
# 检查原始训练集类别分布 (多数类: 0, 少数类: 1)
print("原始训练集类别分布:")
print(y_train.value_counts())
start_time = time.time()
# 实例化随机森林,设置 class_weight='balanced'
rf_model_weighted = RandomForestClassifier(
random_state=42,
class_weight='balanced' # 核心参数:根据类别频率自动调整权重
)
rf_model_weighted.fit(X_train, y_train) # 在原始训练集上训练
rf_pred_weighted = rf_model_weighted.predict(X_test) # 在测试集上预测
end_time = time.time()
print(f"权重调整模型训练与预测耗时: {end_time - start_time:.4f} 秒")
print("\n权重调整随机森林 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred_weighted))
print("权重调整随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred_weighted))
四、评估指标层面
修改分类阈值
这种方法在模型训练完成之后介入,通过调整最终分类的决策规则来平衡不同类型的错误。
核心思想: 改变将模型输出的概率(或得分)映射到最终类别标签的门槛。
作用机制: 模型通常输出一个样本属于正类(通常设为少数类)的概率
p。默认情况下,如果p > 0.5,则预测为正类。修改阈值意味着改变这个0.5,例如,如果要求更高的召回率,可以将阈值降低(如p > 0.3就预测为正类)。目的: 在不改变已训练好的模型的情况下,根据业务需求调整精确率(Precision)和召回率(Recall)之间的权衡。通常用于提高少数类的召回率(但可能会牺牲精确率)。
影响: 不改变模型学到的参数或决策边界本身,只改变如何解释模型的输出。
优点:
- 实现简单,无需重新训练模型。
- 非常直观,可以直接在 PR 曲线或 ROC 曲线上选择操作点。
- 适用于任何输出概率或分数的模型。
缺点:
- 治标不治本。如果模型本身就没学好如何区分少数类(概率输出普遍很低),单纯降低阈值可能效果有限或导致大量误报(低精确率)。
核心差异总结
| 特性 | 修改类别权重 (class_weight) |
修改分类阈值 |
|---|---|---|
| 作用阶段 | 模型训练时 | 模型预测(或评估)时 |
| 作用对象 | 模型的损失函数、参数学习过程 | 模型输出概率/分数到最终预测的决策规则 |
| 对模型影响 | 改变学习到的模型本身和决策边界 | 不改变已学习到的模型 |
| 性质 | 根本性调整,代价敏感学习 | 后处理性质的调整 |
| 目标侧重 | 学习一个内在区分能力更强的模型 | 在现有模型上调整性能指标的权衡 |
| 实现方式 | 设置算法的参数(如 class_weight='balanced') |
在预测后应用不同的概率门槛 |
实践建议
- 评估指标先行: 明确你的目标,使用适合不平衡数据的指标(Recall, F1-Score, AUC-PR, Balanced Accuracy, MCC)来评估模型。
- 优先尝试根本方法: 通常建议首先尝试修改权重 (
class_weight='balanced' ) 或 数据采样方法 (如 SMOTE) ,因为它们试图从源头改善模型学习。 - 交叉验证评估: 在使用
class_weight 或采样方法时,务必使用分层交叉验证 (Stratified K-Fold) 来获得对模型性能的可靠估计。 - 阈值调整作为补充: 修改阈值可以作为一种补充手段或最后的微调。即使使用了权重调整,有时仍需根据具体的业务需求(如必须达到某个召回率水平)来调整阈值,找到最佳的操作点。
- 组合策略: 有时结合多种方法(如 SMOTE +
class_weight)可能会产生更好的结果。
总之,修改权重旨在训练一个“更好”的模型,而修改阈值是在一个“已有”模型上调整其表现。理解它们的差异有助于你选择更合适的策略来应对不平衡数据集的挑战。
print("\n--- 阈值调整:计算概率并寻找 F1-Score 最佳阈值 ---")
from sklearn.metrics import precision_recall_curve, f1_score, confusion_matrix
import numpy as np # 确保导入 numpy
# 1. 确保我们使用的是基准模型 (rf_model) 在验证集上的概率预测
y_proba = rf_model.predict_proba(X_val)[:, 1] # 获取类别 1 的预测概率
# 2. 计算所有可能的精确率、召回率和对应的阈值
precisions, recalls, thresholds = precision_recall_curve(y_val, y_proba)
# 3. 计算所有阈值对应的 F1-Score
# 注意:fscores 比 thresholds 多一个值,因此在计算 ix 时只看 thresholds 的范围
fscores = np.divide(2 * precisions * recalls, precisions + recalls,
out=np.zeros_like(precisions), where=(precisions + recalls) != 0)
# 4. 找到 F1-Score 最大的索引 (ix)
# np.argmax 返回最大值的索引。我们使用 [:-1] 来匹配 thresholds 的长度
ix = np.argmax(fscores[:-1])
best_threshold = thresholds[ix]
best_fscore = fscores[ix]
# 5. 打印找到的最佳阈值和性能
print(f"最佳 F1-Score 对应的阈值: {best_threshold:.4f}")
print(f"该阈值下的最大 F1-Score (类别 1): {best_fscore:.4f}")
print(f"该阈值下的精确率 (类别 1): {precisions[ix]:.4f}")
print(f"该阈值下的召回率 (类别 1): {recalls[ix]:.4f}")
# 6. 使用最佳阈值生成最终预测结果
test_proba = rf_model.predict_proba(X_test)[:, 1]
y_pred_best_threshold = (test_proba >= best_threshold).astype(int)
# 7. 打印最终评估报告
print(f"\n基准模型,使用最佳 F1 阈值 ({best_threshold:.4f}) 时的分类报告:")
print(classification_report(y_test, y_pred_best_threshold))
print("该阈值下的混淆矩阵:")
print(confusion_matrix(y_test, y_pred_best_threshold))
print("\n--- 可视化不同阈值下的 F1-Score 变化 ---")
# 1. 绘制 F1-Score 随阈值变化的曲线
plt.figure(figsize=(10, 6))
# thresholds 比 fscores 少一个点,我们使用 fscores[:-1] 匹配
plt.plot(thresholds, fscores[:-1], label='F1-Score', linewidth=2)
# 绘制精确率和召回率,供参考
plt.plot(thresholds, precisions[:-1], label='精确率 (Precision)', linestyle='--')
plt.plot(thresholds, recalls[:-1], label='召回率 (Recall)', linestyle=':')
# 2. 标记 F1-Score 的最佳点
# best_threshold 和 best_fscore 来自上一步骤的计算结果
plt.scatter(best_threshold, best_fscore, marker='o', color='red', s=100,
label=f'最佳 F1 阈值 ({best_threshold:.4f})')
plt.annotate(f'Max F1: {best_fscore:.4f}',
xy=(best_threshold, best_fscore),
xytext=(best_threshold + 0.05, best_fscore - 0.1),
arrowprops=dict(facecolor='black', shrink=0.05),
fontsize=12)
# 3. 设置图表属性
plt.title('不同分类阈值下的性能指标变化', fontsize=16)
plt.xlabel('分类阈值 (Threshold)', fontsize=14)
plt.ylabel('性能指标值', fontsize=14)
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend(fontsize=12)
plt.show()
# 为了演示步长细致的效果,我们不再手动取步长,
# 因为 precision_recall_curve 已经计算了所有数据点对应的阈值。
# 如果想看特定范围的F1变化,可以只绘制该范围的曲线。
F1-Score 曲线的最高点: 这就是我们选择的最佳阈值,它平衡了精确率和召回率。
精确率-召回率的消长关系:
- 当阈值很低时(靠近 0),模型将大量样本预测为 1,此时召回率高(不错过任何 1),但精确率低(有很多误判)。
- 当阈值很高时(靠近 1),模型只对那些极有把握的样本预测为 1,此时精确率高,但召回率低(错过了大量 1)。
如何根据业务需求选择: 如果业务更看重避免误报(精确率),则应将阈值向右(大于 $0.5$)移动;如果更看重不错过任何少数类(召回率),则应将阈值向左(小于 $0.5$)移动。
Day15 不平衡数据集的处理
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法