《机器学习与R语言(原书第2版)》一3.2 例子—用kNN算法诊断乳腺癌

简介: 定期的乳腺癌检查使得疾病在引起明显的症状之前就得到诊断与治疗。早期的检测过程包括检查乳腺组织的异常肿块。如果发现一个肿块,那么就需要进行细针抽吸活检,即利用一根空心针从肿块中提取细胞的一个小样品,然后临床医生在显微镜下检查细胞,从而确定肿块可能是恶性的还是良性的。

本节书摘来自华章出版社《机器学习与R语言(原书第2版)》一书中的第3章,第3.2节,美] 布雷特·兰茨(Brett Lantz) 著,李洪成 许金炜 李舰 译更多章节内容可以访问云栖社区“华章计算机”公众号查看。

3.2 例子—用kNN算法诊断乳腺癌

定期的乳腺癌检查使得疾病在引起明显的症状之前就得到诊断与治疗。早期的检测过程包括检查乳腺组织的异常肿块。如果发现一个肿块,那么就需要进行细针抽吸活检,即利用一根空心针从肿块中提取细胞的一个小样品,然后临床医生在显微镜下检查细胞,从而确定肿块可能是恶性的还是良性的。
如果机器学习能够自动识别癌细胞,那么它将为医疗系统提供相当大的益处。自动化的过程很有可能提高检测过程的效率,从而可以让医生在诊断上花更少的时间,而在治疗疾病上花更多的时间。自动化筛查系统还可能通过去除该过程中的内在主观人为因素来提供更高的检测准确性。
从带有异常乳腺肿块的女性身上的活检细胞的测量数据入手,应用kNN算法,从而研究机器学习用于检测癌症的功效。

3.2.1 第1步—收集数据

我们将使用来自UCI机器学习数据仓库(UCI Machine Learning Repository)的威斯康星乳腺癌诊断数据集(Wisconsin Breast Cancer Diagnostic dataset),该数据可以从网站http://archive.ics.uci.edu/ml获得。该数据是由威斯康星大学的研究者捐赠的,包括乳房肿块细针抽吸活检图像的数字化的多项测量值,这些值代表出现在数字化图像中的细胞核的特征。
想要阅读更多关于该数据集的信息,可参考:Mangasarian OL, Street WN, Wolberg WH. Breast cancer diagnosis and prognosis via linear programming. Operations Research. 1995; 43:570-577。
乳腺癌数据包括569例细胞活检案例,每个案例有32个特征。一个特征是识别号码,一个特征是癌症诊断结果,其他30个特征是数值型的实验室测量结果。癌症诊断结果用编码“M”表示恶性,用编码“B”表示良性。
其他30个数值型测量结果由数字化细胞核的10个不同特征的均值、标准差和最差值(即最大值)构成。这些特征包括:
Radius(半径)
Texture(质地)
Perimeter(周长)
Area(面积)
Smoothness(光滑度)
Compactness(致密性)
Concavity(凹度)
Concave points(凹点)
Symmetry(对称性)
Fractal dimension(分形维数)
根据这些名字,所有特征似乎都与细胞核的形状和大小有关。除非你是一个癌症医师,否则你不大可能知道每个特征如何与良性或者恶性肿块联系在一起。在我们继续机器学习的过程中,这些模式将会被揭示。

3.2.2 第2步—探索和准备数据

让我们来探索数据并且看看是否能让数据之间的关系明朗化一些。为此,我们要准备使用kNN学习算法所要用到的数据。
如果你计划跟着一起学习,那么你需要从Packt网站下载wisc_bc_data.csv文件,并将它保存到你的R工作目录中。本书对该数据集原来的形式做了非常轻微的修改。具体地讲,增加了一个标题行,对行数据进行了随机排序。
与我们先前所做的一样,我们将从导入CSV数据文件开始,把威斯康星乳腺癌数据保存到数据框wbcd中:
d1
正如我们所预期的那样,使用str(wbcd)命令可以确认数据是由569个案例和32个特征构成的。前几行的输出结果如下所示:
d2
第一个变量是一个名为id的整型变量。由于这仅仅是每个病人在数据中唯一的标识符(ID),它并不能提供有用的信息,所以我们需要把它从模型中剔除。
不管是什么机器学习方法,ID变量总是要被剔除的,不这样做会导致错误的结果,因为ID可以用来独一无二地“预测”每一个案例。因此,包括标识符的模型将受到过度拟合的影响,并且不太可能很好地推广到其他数据。
首先将id特征完全剔除。由于它位于第一列,所以d4
我们可以通过复制一个不包括列1的wbcd数据框来剔除它:
d3
接下来的变量是diagnosis,它是我们特别感兴趣的,因为它是我们希望预测的结果。这个特征表示案例是来自于良性肿块还是恶性肿块。函数table()的输出结果表示357个肿块是良性的,而212个肿块是恶性的:
d4
许多R机器学习分类器要求将目标属性编码为因子类型,所以我们需要重新编码diagnosis变量。同时,我们也会用labels参数对"B"值和"M"值给出含有更多信息的标签:
d5
现在,当我们观察函数prop.table()的输出结果时,我们注意到,输出值被标记为Benign和Malignant,分别有62.7%的良性肿块和37.3%的恶性肿块:
d6
其余的30个特征都是数值型的,与预期一样,它们由10个细胞核特征的3种不同测量构成。作为示例,我们这里详细地观察3个特征:
d7
纵观这些并排的特征,你注意到了关于数值的一些问题吗?我们知道,kNN的距离计算在很大程度上依赖于输入特征的测量尺度。由于光滑度的范围是0.05~0.16,且面积的范围是143.5~2501.0,所以在距离计算中,面积的影响比光滑度的影响大很多,这可能潜在地导致我们的分类器出现问题,所以我们应用min-max标准化方法将特征值重新调整到一个标准范围内。
1.转换—min-max标准化数值数据
为了将这些特征进行min-max标准化,我们需要在R中创建一个normalize()函数,该函数接受一个数值向量x作为输入参数,并且对于x中的每一个值,减去x中的最小值再除以x的极差。最后,返回结果向量。该函数的代码如下:
d8
运行上面的代码后,函数normalize()就可以在R中使用了。让我们用几个向量来测试这个函数:
d9
该函数看来是能正确运行。事实上,尽管第二个向量中的值是第一个向量中的值的10倍,但是在min-max标准化以后,这两个向量返回的结果是完全一样的。
现在,我们可以将normalize()函数应用于我们数据框中的数值特征。我们并不需要对这30个数值变量逐个进行min-max标准化,这里可以使用R中的一个函数来自动完成此过程。
lapply()函数接受一个列表作为输入参数,然后把一个具体函数应用到每一个列表元素。因为数据框是一个含有等长度向量的列表,所以我们可以使用lapply()函数将normalize()函数应用到数据框中的每一个特征。最后一个步骤是,应用函数as.data.frame()把lapply()返回的列表转换成一个数据框。全部过程如下所示:
d10
以通俗的语言来讲,该命令把normalize()函数应用到数据框wbcd的第2~31列,把产生的结果列表转换成一个数据框,并给该数据框赋予一个名称wbcd_n。这里使用的后缀_n是一个提示,即wbcd中的值已经被min-max标准化了。
为了确认转换是否正确应用,让我们来看看其中一个变量的汇总统计量:
d11
正如预期的那样,area_mean变量的原始范围是143.5~2501.0,而现在的范围是0~1。
2.数据准备—创建训练数据集和测试数据集
尽管所有的569个活检的良性或者恶性情形都已被标记,但是预测我们已经知道的结果并不是特别令人感兴趣。此外,我们在训练期间得到的算法分类好坏的衡量指标可能是有误的,因为我们不知道数据发生过度拟合的程度,或者说,不知道推广到未知情形时学习算法效果怎样。一个更有趣的问题是,对于一个无标记数据的数据集,学习算法的性能怎样。如果我们有机会使用实验室,那么我们可以将学习算法应用到接下来的100个未知癌症情形的肿块的测量数据,并且看看与用传统方法得到的诊断结果相比,机器学习算法的预测怎样。
由于缺少这样的数据,所以我们可以通过把数据划分成两部分来模拟这种方案:一部分是用来建立kNN模型的训练数据集;另一部分是用来估计模型预测准确性的测试数据集。我们使用前469条记录作为训练数据集,剩下的100条记录用来模拟新的病人。
使用第2章给出的数据提取方法,我们将把wbcd_n数据框划分为wbcd_train和wbcd_test:
d12
如果上面的命令让你困惑了,那么记住从数据框中提取数据使用的是[row, column]语法。如果行值或者列值是空的,就表明所有的行或者列都应该包含在内。因此,第一行代码取的是第1~469行的所有列,第二行取的是470~569行的100行的所有列。
当构造训练数据集和测试数据集时,保证每一个数据集都是数据全集的一个有代表性的子集是很重要的。wbcd记录已经随机排序,所以我们可以简单地提取100个连续的记录来创建一个测试数据集。如果数据是按时间顺序或者以具有相似值的组的顺序排列的,那么这将是不恰当的。在这些情况下,需要用到随机抽样方法。随机抽样将在第5章中讨论。
当我们构建标准化的训练数据集和测试数据集时,我们剔除了目标变量diagnosis。为了训练kNN模型,我们需要把这些类的标签存储在一个因子向量中,然后把该向量在训练数据集和测试数据集之间划分:
d13
该代码使用wbcd数据框第一列的diagnosis因子,并且创建了wbcd_train_labels和wbcd_test_labels两个向量。我们将在下面的分类器的训练和评估步骤中使用这些向量。

3.2.3 第3步—基于数据训练模型

有了训练数据集和标签向量后,我们现在准备好对未知记录进行分类。对于kNN算法,训练阶段实际上不包括模型的建立,训练一个懒惰学习算法的过程,就像kNN算法仅涉及以结构化格式存储输入数据。
为了将我们的测试实例进行分类,我们使用一个来自class添加包的kNN算法来实现,该添加包提供了一组用于分类的基本R函数。如果该添加包尚未安装到你的系统上,你可以通过输入以下命令来安装它:
d14

为了在你希望使用这些函数的任何会话期间载入该添加包,你只需要输入library (class)命令。
class添加包中的knn()函数提供了一个标准的kNN算法实现。对于测试数据中的每一个实例,该函数将使用欧氏距离标识k个近邻,其中k是用户指定的一个数。于是,通过k个近邻的“投票”来对测试案例进行分类—确切地说,该过程涉及将实例归类到k个近邻中的大多数所在的那个类。如果各个类的票数相等,该测试实例会被随机分类。
在其他R添加包中,还有几个其他的kNN函数提供了更加复杂或者更加高效的算法实现。如果你受到knn()函数的限制,你可以在R综合文档网络(Comprehensive R Archive Network,CRAN)中搜索kNN。
使用knn()函数进行训练和分类是在一个单一的函数调用中执行的,它包含4个参数,如下表所示。
d15

现在我们有了把kNN算法应用到该数据集中的几乎所有参数。我们已经把数据划分成训练数据集和测试数据集,每个数据集都有完全相同的数值特征。训练数据中的标签存储在一个单独的因子向量中,唯一剩下的参数是k,它指定投票中所包含的邻居数。
由于训练数据集含有469个实例,所以我们可能尝试k = 21,它是一个大约等于469的平方根的奇数。根据二分类的结果,使用奇数将消除各个类票数相等这一情况发生的可能性。
现在,我们可以使用knn()函数对测试数据进行分类:
d16
函数knn()返回一个因子向量,为测试数据集中的每一个案例返回一个预测标签,我们将该因子向量命名为wbcd_test_pred。

3.2.4 第4步—评估模型的性能

该过程的下一步就是评估wbcd_test_pred向量中预测的分类与wbcd_test_labels向量中已知值的匹配程度如何。为了做到这一点,我们可以使用gmodels添加包中的CrossTable()函数,它在第2章中介绍过。如果你还没有安装该添加包,可以使用install.packages("gmodels")命令进行安装。
在使用library(gmodels)命令载入该添加包后,可以创建一个用来标识两个向量之间一致性的交叉表。指定参数prop.chisq = FALSE,将从输出中去除不需要的卡方(chi-square)值,如下所示:
d16
由此产生的表如下所示。

表格中单元格的百分比表示落在4个分类中的值所占的比例。左上角的单元格表示真阴性(True Negative)的结果。100个值中有61个值标识肿块是良性的,而kNN算法也正确地把它们标识为良性的。右下角的单元格表示真阳性(True Positive)的结果,这里表示分类器和临床确定的标签一致认为肿块是恶性的情形。100个预测值中有37个是真阳性(True Positive)的。
落在另一条对角线上的单元格包含了kNN算法与真实标签不一致的案例计数。位于左下角单元格的2个案例是假阴性(False Negative)的结果。在这种情况下,预测的值是良性的,但肿瘤实际上是恶性的。这个方向上的错误可能会产生极其高昂的代价,因为它们可能导致一位病人认为自己没有癌症,而实际上这种疾病可能会继续蔓延。如果右上角单元格里有值,它包含的是假阳性(False Positive)的结果。当模型把肿块标识为恶性的,而事实上它是良性的时就会产生这里的值。尽管这类错误没有假阴性(False Negative)的结果那么危险,但这类错误也应该避免,因为它们可能会导致医疗系统的额外财政负担,或者病人的额外压力,毕竟这需要提供额外的检查或者治疗。
如果需要,可以通过将每一个肿块分类为恶性肿块来完全排除假阴性(False Negative)的结果。显然,这是一个不切实际的策略。然而,它说明了一个事实,即预测涉及假阳性(False Positive)比率和假阴性(False Negative)比率之间的一个平衡。在第10章中,你将学习更复杂的方法来度量预测的准确性,根据每种错误类型的成本,这些方法可以用来找出那些错误率可以被优化的地方。
一共有2%,即根据kNN算法,100个肿块中有2个是被错误分类的。虽然对于仅用几行R代码就得到98%的准确度似乎令人印象深刻,但是我们可以尝试一些其他的模型迭代方法来看看我们是否可以提高性能并减少错误分类值的数量,特别当错误是危险的假阴性(False Negative)结果时。

3.2.5 第5步—提高模型的性能

对于前面的分类器,我们将尝试两种简单的改变。第一,我们将使用另一种方法重新调整数值特征;第二,我们将尝试几个不同的k值。
1.转换—z分数标准化
虽然min-max标准化是传统上用于kNN分类的方式,但它并不一定总是最合适的调整特征的方法。因为z分数标准化后的值没有预定义的最小值和最大值,所以极端值不会被压缩到中心。有人可能怀疑在有一个恶性肿瘤的情况下,可能得到一些非常极端的异常值,因为肿瘤的生长不受控制。然而,让异常值在距离计算中占有更大的权重可能是合理的。让我们来看看z分数标准化是否能够提高预测的准确性。
为了标准化一个向量,我们可以使用R内置的scale()函数,该函数默认使用z分数标准化来重新调整特征的值。scale()函数提供的一个额外好处就是它能够直接应用于数据框,这样我们可以避免使用lapply()函数。为了创建一个wbcd数据的z分数标准化版本,我们可以使用下面的命令:
d17
该命令重新调整除了diagnosis以外的所有特征,并把结果存储在wbcd_z数据框中,后缀_z作为一个提示,即特征值已经进行了z分数标准化。
为了确认变换是否正确,我们可以看一看汇总统计量:
d21
一个z分数标准化变量的均值应该始终为0,而且其值域应该非常紧凑,一个大于3或者小于-3的z分数表示一个极其罕见的值,考虑到这一点,变换似乎已经奏效。
正如我们之前所做的那样,我们需要将数据划分为训练数据集和测试数据集,然后使用knn()函数对测试实例进行分类,最后使用CrossTable()函数来比较预测的标签与实际的标签:
d18
不幸的是,在下面的表格中,应用新变换得到的结果的准确性略有降低。之前,我们正确分类了98%的案例,而这一次我们仅正确分类了95%的案例。更糟糕的是,我们并没有在假阴性(False Negative)的分类结果上做得更好:
d19
2.测试其他的k值
或许,我们可以做得更好,即检验应用不同k值的模型的性能。使用标准化的训练数据集和测试数据集,然后用几个不同的k值来对相同的100个记录进行分类。对于每次迭代,假阴性(False Negative)和假阳性(False Positive)的数量如下表所示。
k值 假阴性的数量 假阳性的数量 错误分类的百分比


8fa4b3193dfa1a2fdc0cfb74a54e07c513fe4fa8



虽然分类器永远不会很完美,但是1NN算法能够避免一些假阴性(False Negative)的结果,不过它是以增加假阳性(False Positive)的结果为代价。然而,重要的是记住,为了过于准确地预测测试数据来调整我们的方法是不明智的,毕竟,一组不同的100位病人的记录很可能与那些用来测量模型性能的记录有所不同。
如果你需要确认一个学习算法能推广到未来的数据,那么你可能需要随机地创建几组100位病人的记录,并且在这些数据上重复测试结果。第10章将深入讨论评估机器学习模型性能的方法。
相关文章
|
2天前
|
机器学习/深度学习 运维 算法
【Python机器学习专栏】异常检测算法在Python中的实践
【4月更文挑战第30天】本文介绍了异常检测的重要性和在不同领域的应用,如欺诈检测和网络安全。文章概述了四种常见异常检测算法:基于统计、距离、密度和模型的方法。在Python实践中,使用scikit-learn库展示了如何实现这些算法,包括正态分布拟合、K-means聚类、局部异常因子(LOF)和孤立森林(Isolation Forest)。通过计算概率密度、距离、LOF值和数据点的平均路径长度来识别异常值。
|
2天前
|
机器学习/深度学习 数据可视化 算法
【Python机器学习专栏】t-SNE算法在数据可视化中的应用
【4月更文挑战第30天】t-SNE算法是用于高维数据可视化的非线性降维技术,通过最小化Kullback-Leibler散度在低维空间保持数据点间关系。其特点包括:高维到二维/三维映射、保留局部结构、无需预定义簇数量,但计算成本高。Python中可使用`scikit-learn`的`TSNE`类实现,结合`matplotlib`进行可视化。尽管计算昂贵,t-SNE在揭示复杂数据集结构上极具价值。
|
2天前
|
机器学习/深度学习 算法 数据挖掘
【Python机器学习专栏】关联规则学习:Apriori算法详解
【4月更文挑战第30天】Apriori算法是一种用于关联规则学习的经典算法,尤其适用于购物篮分析,以发现商品间的购买关联。该算法基于支持度和置信度指标,通过迭代生成频繁项集并提取满足阈值的规则。Python中可借助mlxtend库实现Apriori,例如处理购物篮数据,设置支持度和置信度阈值,找出相关规则。
|
2天前
|
机器学习/深度学习 算法 数据挖掘
【Python机器学习专栏】层次聚类算法的原理与应用
【4月更文挑战第30天】层次聚类是数据挖掘中的聚类技术,无需预设簇数量,能生成数据的层次结构。分为凝聚(自下而上)和分裂(自上而下)两类,常用凝聚层次聚类有最短/最长距离、群集平均和Ward方法。优点是自动确定簇数、提供层次结构,适合小到中型数据集;缺点是计算成本高、过程不可逆且对异常值敏感。在Python中可使用`scipy.cluster.hierarchy`进行实现。尽管有局限,层次聚类仍是各领域强大的分析工具。
|
2天前
|
机器学习/深度学习 算法 数据挖掘
【Python 机器学习专栏】K-means 聚类算法在 Python 中的实现
【4月更文挑战第30天】K-means 是一种常见的聚类算法,用于将数据集划分为 K 个簇。其基本流程包括初始化簇中心、分配数据点、更新簇中心并重复此过程直到收敛。在 Python 中实现 K-means 包括数据准备、定义距离函数、初始化、迭代和输出结果。虽然算法简单高效,但它需要预先设定 K 值,且对初始点选择敏感,可能陷入局部最优。广泛应用在市场分析、图像分割等场景。理解原理与实现对应用聚类分析至关重要。
|
2天前
|
机器学习/深度学习 算法 前端开发
【Python机器学习专栏】集成学习算法的原理与应用
【4月更文挑战第30天】集成学习通过组合多个基学习器提升预测准确性,广泛应用于分类、回归等问题。主要步骤包括生成基学习器、训练和结合预测结果。算法类型有Bagging(如随机森林)、Boosting(如AdaBoost)和Stacking。Python中可使用scikit-learn实现,如示例代码展示的随机森林分类。集成学习能降低模型方差,缓解过拟合,提高预测性能。
|
2天前
|
机器学习/深度学习 算法 Python
【Python 机器学习专栏】随机森林算法的性能与调优
【4月更文挑战第30天】随机森林是一种集成学习方法,通过构建多棵决策树并投票或平均预测结果,具有高准确性、抗过拟合、处理高维数据的能力。关键性能因素包括树的数量、深度、特征选择和样本大小。调优方法包括调整树的数量、深度,选择关键特征和参数优化。Python 示例展示了使用 GridSearchCV 进行调优。随机森林广泛应用于分类、回归和特征选择问题,是机器学习中的重要工具。
|
2天前
|
机器学习/深度学习 算法 数据可视化
【Python机器学习专栏】决策树算法的实现与解释
【4月更文挑战第30天】本文探讨了决策树算法,一种流行的监督学习方法,用于分类和回归。文章阐述了决策树的基本原理,其中内部节点代表特征判断,分支表示判断结果,叶节点代表类别。信息增益等标准用于衡量特征重要性。通过Python的scikit-learn库展示了构建鸢尾花数据集分类器的示例,包括训练、预测、评估和可视化决策树。最后,讨论了模型解释和特征重要性评估在优化中的作用。
|
2天前
|
算法 数据可视化 数据挖掘
R语言平滑算法LOESS局部加权回归、三次样条、变化点检测拟合电视节目《白宫风云》在线收视率
R语言平滑算法LOESS局部加权回归、三次样条、变化点检测拟合电视节目《白宫风云》在线收视率
|
3天前
|
机器学习/深度学习 数据采集 SQL
R语言K-Means(K均值聚类)和层次聚类算法对微博用户特征数据研究
R语言K-Means(K均值聚类)和层次聚类算法对微博用户特征数据研究