1、肿瘤预测案例
数据描述
(1)699条样本,共11列数据,第一列用语检索的id,后9列分别是与肿瘤
相关的医学特征,最后一列表示肿瘤类型的数值。
(2)包含16个缺失值,用”?”标出。
(3)2 表示良性、4 表示恶性
代码:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
if __name__ == "__main__":
names = ['Sample code number',
'Clump Thickness',
'Uniformity of Cell Size',
'Uniformity of Cell Shape',
'Marginal Adhesion',
'Single Epithelial Cell Size',
'Bare Nuclei',
'Bland Chromatin',
'Normal Nucleoli',
'Mitoses',
'Class']
# 读取数据
data = pd.read_csv("./data/breast-cancer-wisconsin.data", names=names)
# 缺失值处理
data = data.replace(to_replace="?", value=np.nan)
data = data.dropna()
# 确定特征值、目标值
x = data.iloc[:, 1:-1]
y = data["Class"]
# 分割数据集
x_train, x_test, y_train, y_test = \
train_test_split(x, y, random_state=5, test_size=0.2)
# 标准化
transfer = StandardScaler()
x_train = transfer.fit_transform(x_train)
x_test = transfer.fit_transform(x_test)
# 模型训练
estimator = LogisticRegression()
estimator.fit(x_train, y_train)
# 预测评估
# estimator.classes_[1] 当做正类(1类)
# estimator.classes_
y_pred = estimator.predict(x_test)
print("预测结果:\n", y_pred)
accuracy = estimator.score(x_test, y_test)
print("准确率是:\n", accuracy)
输出结果:
预测结果:
[2 2 2 2 4 4 2 2 4 4 2 2 2 2 2 4 2 2 2 2 4 4 4 2 2 2 2 4 2 4 2 4 2 4 2 2 4
2 4 2 2 2 2 2 2 4 2 4 4 2 2 2 4 2 2 2 2 4 4 2 2 2 2 2 2 4 2 4 2 2 2 4 4 2
4 4 4 2 4 4 2 2 4 2 2 2 2 4 4 4 2 2 2 2 2 2 2 2 4 2 2 2 4 2 2 2 2 2 4 4 2
4 4 2 2 2 4 2 4 4 4 2 2 2 4 2 2 2 2 2 4 2 4 2 2 2 2]
准确率是:
0.9927007299270073
2、分类评估方法介绍
在《癌症分类预测》案例中,我们通过 score 函数可以得到模型预测的准确率为 99%。这是一个很简单的指标,但是这个指标并不能满足我们对某些场景下对模型的评估。例如:我们希望模型能够把所有的恶性肿瘤预测出来,因为这关系到人的生死大事。
假设:样本中有 6 个恶性肿瘤样本,4 个良性肿瘤样本。
模型 A:预测对了 5 个恶性肿瘤样本,4 个良性肿瘤样本,其准确率为:90%
模型 B:预测对了 6 个恶性肿瘤样本,3 个良性肿瘤样本,其准确率为:90%
但是,模型 B 把所有潜在的恶性肿瘤患者全部预测出来,模型 A 只能把 5/6 的恶性肿瘤患者预测出来,哪一个模型对于当前问题场景更好呢?显然是前者。
所以,对于分类效果的评估只使用准确率去衡量并不能适应所有场景。所以,我们需要引入其他的模型评估指标。
2.1 混淆矩阵
想要获得更加精细的分类评估指标,我们得将预测结果进行更为精细的划分,这就是混淆矩阵。
混淆矩阵作用就是看一看在测试集样本集中:
- 真实值是 正例 的样本中,被分类为 正例 的样本数量有多少,这部分样本叫做真正例(TP,True Positive)
- 真实值是 正例 的样本中,被分类为 假例 的样本数量有多少,这部分样本叫做伪反例(FN,False Negative)
- 真实值是 假例 的样本中,被分类为 正例 的样本数量有多少,这部分样本叫做伪正例(FP,False Positive)
- 真实值是 假例 的样本中,被分类为 假例 的样本数量有多少,这部分样本叫做真反例(TN,True Negative)
True Positive :表示样本真实的类别
Positive :表示样本被预测为的类别
例子:
样本集中有 6 个恶性肿瘤样本,4 个良性肿瘤样本,我们假设恶性肿瘤为正例,则:
-
模型 A:预测对了 3 个恶性肿瘤样本,4 个良性肿瘤样本
- 真正例 TP 为:3
- 伪反例 FN 为:3
- 伪正例 FP 为:0
- 真反例 TN:4
-
模型 B:预测对了 6 个恶性肿瘤样本,1个良性肿瘤样本
- 真正例 TP 为:6
- 伪反例 FN 为:0
- 伪正例 FP 为:3
- 真反例 TN:1
我们会发现:TP+FN+FP+TN = 总样本数量
示例代码:
from sklearn.metrics import confusion_matrix
import pandas as pd
if __name__ == "__main__":
# 样本集中共有6个恶性肿瘤样本, 4个良性肿瘤样本
y_true = ["恶性", "恶性", "恶性", "恶性", "恶性","恶性",
"良性", "良性", "良性", "良性"]
labels = ["恶性", "良性"]
dataframe_labels = ["恶性(正例)", "良性(反例)"]
# 1. 模型 A: 预测对了3个恶性肿瘤样本, 4个良性肿瘤样本
print("模型A:")
print("-" * 13)
y_pred = ["恶性", "恶性", "恶性", "良性", "良性", "良性",
"良性", "良性", "良性", "良性"]
result = confusion_matrix(y_true, y_pred, labels=labels)
print(pd.DataFrame(result,
columns=dataframe_labels,
index=dataframe_labels))
print()
# 2. 模型 B: 预测对了6个恶性肿瘤样本, 1个良性肿瘤样本
print("模型B:")
print("-" * 13)
y_pred = ["恶性", "恶性", "恶性", "恶性", "恶性", "恶性",
"恶性", "恶性", "恶性", "良性"]
result = confusion_matrix(y_true, y_pred, labels=labels)
print(pd.DataFrame(result,
columns=dataframe_labels,
index=dataframe_labels))
输出结果:
模型A:
-------------
恶性(正例) 良性(反例)
恶性(正例) 3 3
良性(反例) 0 4
模型B:
-------------
恶性(正例) 良性(反例)
恶性(正例) 6 0
良性(反例) 3 1
2.2 精度(查准率)
精度也叫做查准率,指的是对正例样本的预测准确率。比如:我们把恶性肿瘤当做正例样本,则我们就需要知道模型对恶性肿瘤的预测准确率。
由于,我们预测结果中,有两种正例:原来就是正例被预测为正例,原来是假例被预测为正例,所以,我们想知道真正正例占预测结果中所有正例的比例,即:恶性肿瘤的查准率。
例子:
样本集中有 6 个恶性肿瘤样本,4 个良性肿瘤样本,我们假设恶性肿瘤为正例,则:
-
模型 A:预测对了 3 个恶性肿瘤样本,4 个良性肿瘤样本
- 真正例 TP 为:3
- 伪反例 FN 为:3
- 假正例 FP 为:0
- 真反例 TN:4
- 精度:3/(3+0) = 100%
-
模型 B:预测对了 6 个恶性肿瘤样本,1个良性肿瘤样本
- 真正例 TP 为:6
- 伪反例 FN 为:0
- 假正例 FP 为:3
- 真反例 TN:1
- 精度:6/(6+3) = 67%
示例代码:
from sklearn.metrics import precision_score
if __name__ == "__main__":
# 样本集中共有6个恶性肿瘤样本, 4个良性肿瘤样本
y_true = ["恶性", "恶性", "恶性", "恶性", "恶性","恶性",
"良性", "良性", "良性", "良性"]
# 1. 模型 A: 预测对了3个恶性肿瘤样本, 4个良性肿瘤样本
y_pred = ["恶性", "恶性", "恶性", "良性", "良性", "良性",
"良性", "良性", "良性", "良性"]
result = precision_score(y_true, y_pred, pos_label="恶性")
print("模型A精度:", result)
# 2. 模型 B: 预测对了6个恶性肿瘤样本, 1个良性肿瘤样本
y_pred = ["恶性", "恶性", "恶性", "恶性", "恶性", "恶性",
"恶性", "恶性", "恶性", "良性"]
result = precision_score(y_true, y_pred, pos_label="恶性")
print("模型B精度:", result)
输出结果:
模型A精度: 1.0
模型B精度: 0.67
通过精度指标,可以评估模型对正例样本的预测准确率的能力。
2.3 召回率(查全率)
召回率也叫做查全率,指的是预测为真正例样本占所有真实正例样本的比重。例如:我们把恶性肿瘤当做正例样本,则我们想知道模型是否能把所有的恶性肿瘤患者都预测出来。
例子:
样本集中有 6 个恶性肿瘤样本,4 个良性肿瘤样本,我们假设恶性肿瘤为正例,则:
-
模型 A:预测对了 3 个恶性肿瘤样本,4 个良性肿瘤样本
- 真正例 TP 为:3
- 伪反例 FN 为:3
- 假正例 FP 为:0
- 真反例 TN:4
- 精度:3/(3+0) = 100%
- 召回率:3/(3+3)=50%
-
模型 B:预测对了 6 个恶性肿瘤样本,1个良性肿瘤样本
- 真正例 TP 为:6
- 伪反例 FN 为:0
- 假正例 FP 为:3
- 真反例 TN:1
- 精度:6/(6+3) = 67%
- 召回率:6/(6+0)= 100%
示例代码:
from sklearn.metrics import recall_score
if __name__ == "__main__":
# 样本集中共有6个恶性肿瘤样本, 4个良性肿瘤样本
y_true = ["恶性", "恶性", "恶性", "恶性", "恶性","恶性",
"良性", "良性", "良性", "良性"]
# 1. 模型 A: 预测对了3个恶性肿瘤样本, 4个良性肿瘤样本
y_pred = ["恶性", "恶性", "恶性", "良性", "良性", "良性",
"良性", "良性", "良性", "良性"]
result = recall_score(y_true, y_pred, pos_label="恶性")
print("模型A精度:", result)
# 2. 模型 B: 预测对了6个恶性肿瘤样本, 1个良性肿瘤样本
y_pred = ["恶性", "恶性", "恶性", "恶性", "恶性", "恶性",
"恶性", "恶性", "恶性", "良性"]
result = recall_score(y_true, y_pred, pos_label="恶性")
print("模型B精度:", result)
输出结果:
模型A精度: 0.5
模型B精度: 1.0
通过召回率,我们可以评估模型对所有正样本是否能全部预测出来的能力。
2.4 F1-score
如果我们对模型的精度、召回率都有要求,希望知道模型在这两个评估方向的综合预测能力如何?则可以使用 F1-score 指标。
需要注意的是:
- 如果我们想要提高模型的精度,则需要减少样本的数量。
- 如果我们想要提高模型的召回率,则需要增加样本数量。
模型的精度、召回率是一对矛盾的存在。所以,模型在考量模型的精度、召回率的话,只能在两者之间取得一个平衡,即:精度差不多的情况,召回率也差不多。
此处,F1-使用的是精度、召回率的调和平均数。
- P 和 R 中的最小值会影响到 F1 score ,例如:如果一个指标接近于 0,另一个指标则接近于 1,则 F1-score 接近于 0。
- 两个都高则 F1-score 也高,比较重视较小值的重要性。
样本集中有 6 个恶性肿瘤样本,4 个良性肿瘤样本,我们假设恶性肿瘤为正例,则:
-
模型 A:预测对了 3 个恶性肿瘤样本,4 个良性肿瘤样本
- 真正例 TP 为:3
- 伪反例 FN 为:3
- 假正例 FP 为:0
- 真反例 TN:4
- 精度:3/(3+0) = 100%
- 召回率:3/(3+3)=50%
- F1-score:(2*3)/(2*3+3+0)=67%
-
模型 B:预测对了 6 个恶性肿瘤样本,1个良性肿瘤样本
- 真正例 TP 为:6
- 伪反例 FN 为:0
- 假正例 FP 为:3
- 真反例 TN:1
- 精度:6/(6+3) = 67%
- 召回率:6/(6+0)= 100%
- F1-score:(2*6)/(2*6+0+3)=80%
示例代码:
from sklearn.metrics import f1_score
if __name__ == "__main__":
# 样本集中共有6个恶性肿瘤样本, 4个良性肿瘤样本
y_true = ["恶性", "恶性", "恶性", "恶性", "恶性","恶性",
"良性", "良性", "良性", "良性"]
# 1. 模型 A: 预测对了3个恶性肿瘤样本, 4个良性肿瘤样本
y_pred = ["恶性", "恶性", "恶性", "良性", "良性", "良性",
"良性", "良性", "良性", "良性"]
result = f1_score(y_true, y_pred, pos_label="恶性")
print("模型Af1-score:", result)
# 2. 模型 B: 预测对了6个恶性肿瘤样本, 1个良性肿瘤样本
y_pred = ["恶性", "恶性", "恶性", "恶性", "恶性", "恶性",
"恶性", "恶性", "恶性", "良性"]
result = f1_score(y_true, y_pred, pos_label="恶性")
print("模型Bf1-score:", result)
输出结果:
模型A精度: 0.67
模型B精度: 0.8
综合来考量精度、召回率,模型 B 优于 模型 A。
2.5 AUC(模型排序能力)
2.5.1 AUC 理解
在前面,我们提到了正样本和负样本,如何去划分正负样本呢?
设定一个阈值,比如某个样本归为 1 类的概率超过 50%,则将其视为正样本,反之则将其视为负样本,这个阈值也可以调整为 30%、40%、80% 等等。此时,阈值的调整最终会影响到正样本、负样本的数量,进而影响到查全率、查准率、F1-score 等分类评估指标。
例如:班里同学都已经考试完了,拿到了考试分数。此时,如果及格分数调整为 20,则就会有 80 个人及格(正样本),如果及格分数线调整为 80,则就会有 20 个人及格(正样本)。
也就是说,查全率、查准率、F1-score 这些评估指标的结果是受到阈值的影响。
**AUC 指标则是不直接考虑阈值的一种评估模型分类性能的指标,**其主要考虑的是模型对正例样本、负例样本的辨别能力,主要是通过对正负样本预测为正例的概率进行排序来识别的:
比如:我们使用逻辑回归输出对 7 个样本(3正4负)进行分类为正例的预测概率:
**说明:**正(90%) 表示:原来是正样本,被预测为正样本的概率为:90%
模型A:正(90%) 正(80%) 负(78%) 正(51%) 负(48%) 负(40%) 负(23%)
模型B:正(90%) 正(80%) 正(78%) 负(49%) 负(48%) 负(40%) 负(23%)
从排序结果来看,模型 B 能够将正例的样本预测为正例概率都很高,负类样本预测为正例的概率都很低,说明模型对正、负样本的辨别能力很强。
我们也可以说,任意从样本集中选择一对正负样本,如果正样本的预测概率高于负样本的预测概率,则说明该模型的对正负样本的辨别能力很强。
也可以说,真正正例的样本越靠近左侧,则说明,模型预测能力就越强。
示例代码:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
if __name__ == "__main__":
names = ['Sample code number',
'Clump Thickness',
'Uniformity of Cell Size',
'Uniformity of Cell Shape',
'Marginal Adhesion',
'Single Epithelial Cell Size',
'Bare Nuclei',
'Bland Chromatin',
'Normal Nucleoli',
'Mitoses',
'Class']
# 读取数据
data = pd.read_csv("./data/breast-cancer-wisconsin.data", names=names)
# 缺失值处理
data = data.replace(to_replace="?", value=np.nan)
data = data.dropna()
# 确定特征值、目标值
x = data.iloc[:, 1:-1]
y = data["Class"]
# 分割数据集
x_train, x_test, y_train, y_test = \
train_test_split(x, y, random_state=5, test_size=0.2)
# 标准化
transfer = StandardScaler()
x_train = transfer.fit_transform(x_train)
x_test = transfer.fit_transform(x_test)
# 模型训练
estimator = LogisticRegression()
estimator.fit(x_train, y_train)
# 预测评估
y_test_pred_proba = estimator.predict_proba(x_test)
# 参数1:测试样本的真实目标值
# 参数2:测试样本的预测概率值,取第二列值
auc = roc_auc_score(y_test, y_test_pred_proba[:, 1])
print("auc:", auc)
2.6 ROC曲线绘制
2.6.1 ROC曲线理解
ROC 曲线:我们分别考虑正负样本的情况:
- 正样本中被预测为正样本的概率,即:TPR (True Positive Rate)
- 负样本中被预测为正样本的概率,即:FPR (False Positive Rate)
我们希望 TPR 尽可能的大,FPR 尽可能小。这样的话,就可以将正样本排序到左侧,负样本排序到右侧。我们接下来,在考虑不同阈值情况下,TPR 和 FPR 的变化情况,构成了 ROC 曲线。如下图所示:
假设:恶性肿瘤为正例
横轴:负样本中预测为正样本的概率,比如:良性肿瘤中预测为恶性肿瘤的概率
纵轴:正样本中预测为正样本的概率,比如:恶性肿瘤中预测为恶性肿瘤的概率
- ROC 曲线图像中,4 个特殊点的含义:
- (0, 0) 表示所有的正样本都预测为错误,所有的负样本都预测正确
- (1, 0) 表示所有的正样本都预测错误,所有的负样本都预测错误
- (1, 1) 表示所有的正样本都预测正确,所有的负样本都预测错误
- (0, 1) 表示所有的正样本都预测正确,所有的负样本都预测正确
- 我们发现:
- 我们发现:图像越靠近 (0,1) 点则模型对正负样本的辨别能力就越强
- 我们发现:图像越靠近 (0, 1) 点则 ROC 曲线下面的面积就会越大
- AUC 是 ROC 曲线下面的面积,该值越大,则模型的辨别能力就越强
- AUC 范围在 [0, 1] 之间
- 当 AUC= 1 时,该模型被认为是完美的分类器,但是几乎不存在完美分类器
- 当 AUC <= 0.5 时,模型区分正负样本的就会变得模棱两可,近似于随机猜测
2.6.2 ROC曲线绘制
假设:在网页某个位置有一个广告图片或者文字,该广告共被展示了 6 次,有 2 次被浏览者点击了。每次点击的概率如下:
样本 | 是否被点击 | 预测点击概率 |
---|---|---|
1 | 1 | 0.9 |
2 | 0 | 0.7 |
3 | 1 | 0.8 |
4 | 0 | 0.6 |
5 | 0 | 0.5 |
6 | 0 | 0.4 |
根据预测点击概率排序之后:
样本 | 是否被点击 | 预测点击概率 |
---|---|---|
1 | 1 | 0.9 |
3 | 1 | 0.8 |
2 | 0 | 0.7 |
4 | 0 | 0.6 |
5 | 0 | 0.5 |
6 | 0 | 0.4 |
绘制 ROC 曲线:
- 阈值:0.9
- 原本为正例的 1、3 号的样本中 3 号样本被分类错误,则 TPR = 1/2 = 0.5
- 原本为负例的 2、4、5、6 号样本没有一个被分为正例,则 FPR = 0
- 阈值:0.8
- 原本为正例的 1、3 号样本被分类正确,则 TPR = 2/2 = 1
- 原本为负例的 2、4、5、6 号样本没有一个被分为正例,则 FPR = 0
- 阈值:0.7
- 原本为正例的 1、3 号样本被分类正确,则 TPR = 2/2 = 1
- 原本为负类的 2、4、5、6 号样本中 2 号样本被分类错误,则 FPR = 1/4 = 0.25
- 阈值:0.6
- 原本为正例的 1、3 号样本被分类正确,则 TPR = 2/2 = 1
- 原本为负类的 2、4、5、6 号样本中 2、4 号样本被分类错误,则 FPR = 2/4 = 0.5
- 阈值:0.5
- 原本为正例的 1、3 号样本被分类正确,则 TPR = 2/2 = 1
- 原本为负类的 2、4、5、6 号样本中 2、4、5 号样本被分类错误,则 FPR = 3/4 = 0.75
- 阈值 0.4
- 原本为正例的 1、3 号样本被分类正确,则 TPR = 2/2 = 1
- 原本为负类的 2、4、5、6 号样本全部被分类错误,则 FPR = 4/4 = 1
(0, 0.5)、(0, 1)、(0.25, 1)、(0.5, 1)、(0.75, 1)、(1, 1)
由 TPR 和 FPR 构成的 ROC 图像为: