本案例基于公开的乳腺癌诊断数据,使用sklearn 中的KNeighborsClassifier,即K近邻算法来建立乳腺癌自动诊断模型,并介绍了该模型性能提升的简单方法。 本案例主要包括以下内容: 1. 数据源 2. 数据探索和预处理 2.1 数据标准化 2.2 划分训练集和测试集 3. 模型训练 4. 模型性能评估 5. 模型性能提升 5.1 测试不同k取值对模型效果的影响 5.2 采用Z-score标准化方法

使用K近邻算法诊断乳腺癌

乳腺癌已成为当前社会的重大公共卫生问题。全球乳腺癌发病率自20世纪70年代末开始一直呈上升趋势。美国8名妇女一生中就会有1人患乳腺癌。中国不是乳腺癌的高发国家,但不宜乐观,近年我国乳腺癌发病率的增长速度却高出高发国家1~2个百分点。据国家癌症中心和卫生部疾病预防控制局2012年公布的2009年乳腺癌发病数据显示:全国肿瘤登记地区乳腺癌发病率位居女性恶性肿瘤的第1位,女性乳腺癌发病率(粗率)全国合计为42.55/10万,城市为51.91/10万,农村为23.12/10万。

早期的乳腺癌检测主要检查乳腺组织的异常肿块。如果发现一个肿块,那么就需要进行细针抽吸活检,然后在显微镜下检查细胞,从而确定肿块是良性还是恶性。如果能够通过机器学习建模,通过乳腺肿块的检测数据自动进行诊断,将会给医疗系统带来很大的益处。一方面,自动诊断能够大大提高检测效率。另一方面,结合大量不同历史案例的自动诊断能够使辅助医生进行决策,降低误判的风险。

本案例中,基于公开的乳腺癌诊断数据,我们将使用机器学习中一种简单的算法--K近邻算法来构建乳腺癌自动诊断模型。

1 数据源

我们将使用来自UCI的乳腺癌诊断数据集,该数据集的详细描述见网址: http://archive.ics.uci.edu/ml/datasets/Breast+Cancer+Wisconsin+%28Diagnostic%29。 该乳腺癌数据包括569例乳腺细胞活检样本,每个样本包含32个变量。其中id变量是样本识别ID,diagnosis变量是目标变量(M代表恶性,B代表良性)。其他30个变量都是由10个数字化细胞核的10个不同特征的均值、标准差和最大值构成。这10个基本特征为:

  • radius (半径)
  • texture (质地)
  • perimeter (周长)
  • area (面积)
  • smoothness (光滑度)
  • compactness (致密性=perimeter^2 / area - 1.0)
  • concavity (凹度)
  • concave points (凹点)
  • symmetry (对称性)
  • fractal dimension (分形维数)

2 数据探索和预处理

首先,使用pandas的read_csv()函数将CSV格式的乳腺癌数据集导入数据框中。

In [2]:
import pandas as pd
breast_cancer = pd.read_csv("./input/wisc_bc_data.csv")
print breast_cancer.shape
breast_cancer.head(5)
(569, 32)
Out[2]:
id diagnosis radius_mean texture_mean perimeter_mean area_mean smoothness_mean compactness_mean concavity_mean concave points_mean ... radius_worst texture_worst perimeter_worst area_worst smoothness_worst compactness_worst concavity_worst concave points_worst symmetry_worst fractal_dimension_worst
0 842302 M 17.99 10.38 122.80 1001.0 0.11840 0.27760 0.3001 0.14710 ... 25.38 17.33 184.60 2019.0 0.1622 0.6656 0.7119 0.2654 0.4601 0.11890
1 842517 M 20.57 17.77 132.90 1326.0 0.08474 0.07864 0.0869 0.07017 ... 24.99 23.41 158.80 1956.0 0.1238 0.1866 0.2416 0.1860 0.2750 0.08902
2 84300903 M 19.69 21.25 130.00 1203.0 0.10960 0.15990 0.1974 0.12790 ... 23.57 25.53 152.50 1709.0 0.1444 0.4245 0.4504 0.2430 0.3613 0.08758
3 84348301 M 11.42 20.38 77.58 386.1 0.14250 0.28390 0.2414 0.10520 ... 14.91 26.50 98.87 567.7 0.2098 0.8663 0.6869 0.2575 0.6638 0.17300
4 84358402 M 20.29 14.34 135.10 1297.0 0.10030 0.13280 0.1980 0.10430 ... 22.54 16.67 152.20 1575.0 0.1374 0.2050 0.4000 0.1625 0.2364 0.07678

5 rows × 32 columns

第一个变量为ID变量,无法为实际的模型构建提供有用的信息,所以需要将其删除。

In [3]:
del breast_cancer["id"]

diagnosis变量是我们的目标变量,我们首先统计一下其取值分布。观察在我们的数据集中,恶性(M)和良性样本(B)的分布情况。可以使用pandas中Series的value_counts()分别输出不同类别样本数量和占比。 diagnosis变量为字符串格式,在正式建模之前需要将其进行整数编码,将良性(B)编码为0,将恶性(M)编码为1。

In [4]:
print breast_cancer.diagnosis.value_counts()
print breast_cancer.diagnosis.value_counts()/len(breast_cancer)

dignosis_dict = {"B":0,"M":1}
breast_cancer["diagnosis"] = breast_cancer["diagnosis"].map(dignosis_dict)
B    357
M    212
Name: diagnosis, dtype: int64
B    0.627417
M    0.372583
Name: diagnosis, dtype: float64

可见,在我们的569个样本中,良性样本和恶性样本分别有357和212个,占比分别为62.7%和37.3%。 作为示例,我们详细观察30个自变量中的三个变量:radius_mean, area_mean和smoothness_mean。

In [5]:
breast_cancer[["radius_mean", "area_mean","smoothness_mean"]].describe()
Out[5]:
radius_mean area_mean smoothness_mean
count 569.000000 569.000000 569.000000
mean 14.127292 654.889104 0.096360
std 3.524049 351.914129 0.014064
min 6.981000 143.500000 0.052630
25% 11.700000 420.300000 0.086370
50% 13.370000 551.100000 0.095870
75% 15.780000 782.700000 0.105300
max 28.110000 2501.000000 0.163400

观察这三个变量的统计信息,发现它们的取值范围不大一致。 radius_mean取值范围为6.981~28.110,area_mean取值范围为143.5~2501.0,smoothness_mean取值范围为0.05263~0.16340。不同变量的测量尺度不一致会影响K近邻算法中的样本距离计算。 例如,如果上述三个变量直接参与距离计算,则area_mean变量将会对距离计算影响最大,从而会导致我们构建的分类模型过分依赖于area_mean变量。在应用K近邻等涉及距离计算的算法构建预测模型之前,我们需要对变量取值进行标准化。 常见的标准化方法有min-max标准化和Z-score标准化等。本案例中,我们将采用min-max标准化方法将我们自变量取值调整到一个标准的范围内。

2.1 数据标准化

为了将自变量进行min-max标准化,我们需要实现一个min_max_normalize()函数。该函数输入为数值型向量x,对于x中的每一个取值,减去x的最小值,再除以x中数值的取值范围。具体实现代码如下:

In [6]:
def min_max_normalize(x):
    return (x - x.min())/(x.max() - x.min())

现在,我们可以将实现的min_max_normalize()函数对数据集进行标准化。

In [7]:
for col in breast_cancer.columns[1:31]:
    breast_cancer[col] = min_max_normalize(breast_cancer[col])
In [8]:
breast_cancer.iloc[:,1:].describe()
Out[8]:
radius_mean texture_mean perimeter_mean area_mean smoothness_mean compactness_mean concavity_mean concave points_mean symmetry_mean fractal_dimension_mean ... radius_worst texture_worst perimeter_worst area_worst smoothness_worst compactness_worst concavity_worst concave points_worst symmetry_worst fractal_dimension_worst
count 569.000000 569.000000 569.000000 569.000000 569.000000 569.000000 569.000000 569.000000 569.000000 569.000000 ... 569.000000 569.000000 569.000000 569.000000 569.000000 569.000000 569.000000 569.000000 569.000000 569.000000
mean 0.338222 0.323965 0.332935 0.216920 0.394785 0.260601 0.208058 0.243137 0.379605 0.270379 ... 0.296663 0.363998 0.283138 0.170906 0.404138 0.220212 0.217403 0.393836 0.263307 0.189596
std 0.166787 0.145453 0.167915 0.149274 0.126967 0.161992 0.186785 0.192857 0.138456 0.148702 ... 0.171940 0.163813 0.167352 0.139932 0.150779 0.152649 0.166633 0.225884 0.121954 0.118466
min 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
25% 0.223342 0.218465 0.216847 0.117413 0.304595 0.139685 0.069260 0.100944 0.282323 0.163016 ... 0.180719 0.241471 0.167837 0.081130 0.300007 0.116337 0.091454 0.223127 0.185098 0.107700
50% 0.302381 0.308759 0.293345 0.172895 0.390358 0.224679 0.144189 0.166501 0.369697 0.243892 ... 0.250445 0.356876 0.235320 0.123206 0.397081 0.179110 0.181070 0.343402 0.247782 0.163977
75% 0.416442 0.408860 0.416765 0.271135 0.475490 0.340531 0.306232 0.367793 0.453030 0.340354 ... 0.386339 0.471748 0.373475 0.220901 0.494156 0.302520 0.305831 0.554639 0.318155 0.242949
max 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 ... 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000

8 rows × 30 columns

可见,30个自变量都已经正确地标准化到0和1之间。

2.2 划分训练集和测试集

在实际应用中,检验模型最好的方法是将模型应用到未来的数据中。 由于缺少这样的未来数据,我们采用一种模拟的方法:将我们的数据集划分为训练集和测试集两部分。 训练集用来构建模型,而测试集在模型构建中不能使用,只用来评估模型的性能。 在本案例中,我们将数据的70%(398个样本)用来训练模型,30%(171个样本)则用来测试模型。

In [9]:
from sklearn import cross_validation
y = breast_cancer['diagnosis']
del breast_cancer['diagnosis']
X  = breast_cancer
breast_cancer_minmax_train, breast_cancer_minmax_test,\
breast_cancer_train_labels, breast_cancer_test_labels \
= cross_validation.train_test_split(X, y, test_size=0.3, random_state=0)
/explorer/pyenv/jupyter/lib/python2.7/site-packages/sklearn/cross_validation.py:41: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.
  "This module will be removed in 0.20.", DeprecationWarning)

通过value_counts()函数,我们可以对比训练集和测试集中,恶性样本和良性样本分布是否接近:

In [10]:
print breast_cancer_train_labels.value_counts()/len(breast_cancer_train_labels)
print breast_cancer_test_labels.value_counts()/len(breast_cancer_test_labels)
0    0.625628
1    0.374372
Name: diagnosis, dtype: float64
0    0.631579
1    0.368421
Name: diagnosis, dtype: float64

可见,训练集和测试集中不同类别的样本分布趋向一致。现在,我们可以开始构建乳腺癌诊断模型了。

3 模型训练

对于K近邻算法,模型训练阶段实际上不包括模型建立,只需要将训练数据及其标签存储即可。 为了使用训练数据进行分类,我们使用sklearn.neighbors包的K近邻算法实现类KNeighborsClassifier。 对于测试集中的每一个样本,将使用特定的距离计算方法找出k个近邻,其中k是一个可以指定的参数。 然后,通过这k个近邻样本的类标签进行投票,从而对测试样本进行分类。 如果不同类的票数相等,则测试样本会被随机分类。 当然,为了避免不同类票数相等这种情况的发生,对于二分类问题,通常将k设置成奇数。

sklearn.neighbors.KNeighborsClassifier类有几个主要的参数,含义如下:

  • n_neighbors: k
  • weights: k个近邻投票权重计算方式
  • metric: 距离计算方式

各参数的详细取值及设置方式参考网址:http://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html

下面,我们将k设置为21,使用K近邻算法对测试数据进行预测,预测结果保存在breast_cancer_test_pred向量中。

In [11]:
from sklearn.neighbors import KNeighborsClassifier
knn_model = KNeighborsClassifier(n_neighbors = 21)
knn_model.fit(breast_cancer_minmax_train, breast_cancer_train_labels)

breast_cancer_test_pred = knn_model.predict(breast_cancer_minmax_test)

4 模型性能评估

通过对比我们构建的K近邻模型在测试数据上的预测结果breast_cancer_test_pred和测试样本真实的类标签,我们可以评估我们构建的乳腺癌诊断模型的应用效果。我们使用sklern.metrics包中的相关函数来计算评估结果。

In [12]:
from sklearn import metrics
print metrics.classification_report(breast_cancer_test_labels, breast_cancer_test_pred)
print metrics.confusion_matrix(breast_cancer_test_labels, breast_cancer_test_pred)
print metrics.accuracy_score(breast_cancer_test_labels, breast_cancer_test_pred)
             precision    recall  f1-score   support

          0       0.94      0.98      0.96       108
          1       0.97      0.89      0.93        63

avg / total       0.95      0.95      0.95       171

[[106   2]
 [  7  56]]
0.947368421053

可见,对于108个良性样本,模型正确预测了106个,只有2个样本预测失败。 对于63个恶性样本,模型预测正确的有56个,正确预测比例为88.9%。 模型的整体预测正确率为 (106 + 56)/ 171 = 94.7%。 虽然这个正确率看起来很高,但是假阳性样本还有7个,这在实际医疗诊断中是严重的失误,因为将一个患有乳腺癌的患者诊断为良性会给患者带来严重的后果。 下一步,我们需要进一步寻找提升模型效果的方法。

5 模型性能提升

对于K近邻算法,K的不同取值选取会影响模型的效果。此外,不同的数据标准化方法也会对模型效果产生影响。 本节我们将分别探索K取值的改变和数据标准化方法的改变是否能够提升模型的预测结果。

5.1 测试不同k取值对模型效果的影响

我们将分别测试 k = 1,5,9,11,15,21,27 时模型的效果。由于模型整体预测准确率已经很高。 我们通过观察假阴性(False Negative)假阳性(False Positive)的数目和正确率(Accuracy)来对比不同k取值下模型的效果。

In [13]:
k_list = (1,5,9,11,15,21,27)
for k in k_list:
    knn_model = KNeighborsClassifier(n_neighbors = k)
    knn_model.fit(breast_cancer_minmax_train, breast_cancer_train_labels)

    breast_cancer_test_pred = knn_model.predict(breast_cancer_minmax_test)
    accuracy = metrics.accuracy_score(breast_cancer_test_labels, breast_cancer_test_pred)
    confusion_mat = metrics.confusion_matrix(breast_cancer_test_labels, breast_cancer_test_pred)
    
    print "k = ",k
    print "\t正确率: ", '%.2f'%(accuracy*100) + "%"
    print "\t假阴性:",confusion_mat[0,1]
    print "","\t假阳性:",confusion_mat[1,0]
k =  1
	正确率:  92.98%
	假阴性: 7
 	假阳性: 5
k =  5
	正确率:  97.08%
	假阴性: 1
 	假阳性: 4
k =  9
	正确率:  96.49%
	假阴性: 1
 	假阳性: 5
k =  11
	正确率:  95.91%
	假阴性: 2
 	假阳性: 5
k =  15
	正确率:  96.49%
	假阴性: 1
 	假阳性: 5
k =  21
	正确率:  94.74%
	假阴性: 2
 	假阳性: 7
k =  27
	正确率:  94.74%
	假阴性: 2
 	假阳性: 7

可见,当 k = 5 时,假阳性数量最少,且假阴性数量仅为1,正确率达到最高。当然,这也只是在171个测试样本上的结果。

5.2 采用Z-score标准化方法

在上述K近邻算法构建模型的过程中,我们使用min-max方法对数据进行标准化。 现在,现在我们尝试Z-score标准化方法,探索它能够提高我们的乳腺癌诊断模型的预测性能。 可以使用sklearn.preprocessing包中的scale()函数来对数据进行Z-score标准化。

In [14]:
from sklearn import preprocessing
breast_cancer_zscore = pd.DataFrame(preprocessing.scale(breast_cancer),\
                                   columns = breast_cancer.columns)
breast_cancer_zscore.head(5)
Out[14]:
radius_mean texture_mean perimeter_mean area_mean smoothness_mean compactness_mean concavity_mean concave points_mean symmetry_mean fractal_dimension_mean ... radius_worst texture_worst perimeter_worst area_worst smoothness_worst compactness_worst concavity_worst concave points_worst symmetry_worst fractal_dimension_worst
0 1.097064 -2.073335 1.269934 0.984375 1.568466 3.283515 2.652874 2.532475 2.217515 2.255747 ... 1.886690 -1.359293 2.303601 2.001237 1.307686 2.616665 2.109526 2.296076 2.750622 1.937015
1 1.829821 -0.353632 1.685955 1.908708 -0.826962 -0.487072 -0.023846 0.548144 0.001392 -0.868652 ... 1.805927 -0.369203 1.535126 1.890489 -0.375612 -0.430444 -0.146749 1.087084 -0.243890 0.281190
2 1.579888 0.456187 1.566503 1.558884 0.942210 1.052926 1.363478 2.037231 0.939685 -0.398008 ... 1.511870 -0.023974 1.347475 1.456285 0.527407 1.082932 0.854974 1.955000 1.152255 0.201391
3 -0.768909 0.253732 -0.592687 -0.764464 3.283553 3.402909 1.915897 1.451707 2.867383 4.910919 ... -0.281464 0.133984 -0.249939 -0.550021 3.394275 3.893397 1.989588 2.175786 6.046041 4.935010
4 1.750297 -1.151816 1.776573 1.826229 0.280372 0.539340 1.371011 1.428493 -0.009560 -0.562450 ... 1.298575 -1.466770 1.338539 1.220724 0.220556 -0.313395 0.613179 0.729259 -0.868353 -0.397100

5 rows × 30 columns

在进行Z-score标准化后,变量的均值为0,标准差为1。 以area_mean变量为例,我们对我们标准化后的结果进行统计,以检验标准化是否正确。

In [15]:
print breast_cancer_zscore.area_mean.mean()
print breast_cancer_zscore.area_mean.std()
-8.11692053153e-17
1.00087989458

可见,标准化后均值接近0,标准差为1。现在,我们可以训练模型,并测试模型性能了。

In [16]:
breast_cancer_zscore_train, breast_cancer_zscore_test,\
breast_cancer_train_labels, breast_cancer_test_labels \
= cross_validation.train_test_split(breast_cancer_zscore, y, test_size=0.3, random_state=0)

#模型训练
knn_model_z = KNeighborsClassifier(n_neighbors = 5)
knn_model_z.fit(breast_cancer_zscore_train, breast_cancer_train_labels)
#模型预测
breast_cancer_test_pred_z = knn_model_z.predict(breast_cancer_zscore_test)
#性能评估
accuracy_z = metrics.accuracy_score(breast_cancer_test_labels, breast_cancer_test_pred_z)
confusion_mat_z = metrics.confusion_matrix(breast_cancer_test_labels, breast_cancer_test_pred_z)
    
print "k = 5"
print "\t正确率: ", '%.2f'%(accuracy_z*100) + "%"
print "\t假阴性:",confusion_mat_z[0,1]
print "","\t假阳性:",confusion_mat_z[1,0]
k = 5
	正确率:  95.91%
	假阴性: 1
 	假阳性: 6

当k=5时,新的模型假阳性数量反而增加了2个。可见在我们的数据集中,使用Z-score标准化方法的模型效果比使用min-max标准化方法的模型效果更差。