如何用SVM进行乳腺癌检测

上篇文章中,详细介绍了如何创建和使用SVM分类器后,我们来看一个实际的项目,数据集来自美国威斯康星州的乳腺癌诊断数据集。

医疗人员采集了患者乳腺肿块经过细针穿刺(FNA)后的数字化图像,并且对这些数字图像进行了特征提取,这些特征可以描述图像中的细胞核呈现。肿瘤可以分成良性和恶性。部分数据截屏如下所示:

1564362878499

数据表一共包括了32个字段,代表的含义如下:

字段 含义
id ID标识
diagnosis M/B(M:恶性,B:良性)
radius_mean 半径平均值
texture_mean 文理(灰度值的标准差)平均值
perimeter_mean 周长平均值
area_mean 面积平均值
smoothness_mean 平滑程度(半径内的局部变化)平均值
compactness_mean 紧密度(=周长*周长/面积-1.0)平均值
concavity_mean 凹度(轮廓凹部的严重程度)平均值
concave points_mean 凹缝(轮廓的凹部分)平均值
symmetry_mean 对称性平均值
fractal_dimension_mean 分形维数(=海岸线近似-1)平均值
radius_se 半径标准差
texture_se 文理(灰度值的标准差)标准差
perimeter_se 周长标准差
area_se 面积标准差
smoothness_se 平滑程度(半径内的局部变化)标准差
compactness_se 紧密度(=周长*周长/面积-1.0)标准差
concavity_se 凹度(轮廓凹部的严重程度)标准差
concave points_se 凹缝(轮廓的凹部分)标准差
symmetry_se 对称性标准差
fractal_dimension_se 分形维数(=海岸线近似-1)标准差
radius_worst 半径最大值
texture_worst 文理(灰度值的标准差)最大值
perimeter_worst 周长最大值
area_worst 面积最大值
smoothness_worst 平滑程度(半径内的局部变化)最大值
compactness_worst 紧密度(=周长*周长/面积-1.0)最大值
concavity_worst 凹度(轮廓凹部的严重程度)最大值
concave points_worst 凹缝(轮廓的凹部分)最大值
symmetry_worst 对称性最大值
fractal_dimension_worst 分形维数(=海岸线近似-1)最大值

上面的表格中,mean代表平均值,se代表标准差,worst代表最大值(3个最大值的平均值)。每张图像都计算了相应的特征,得出了这30个特征值(不包括ID字段和分类标识结果字段diagnosis),实际上是10个特征值(radius、texture、perimeter、area、smoothness、compactness、concavity、concave points、symmetry和fractal_dimension_mean)的3个维度,平均、标准差和最大值。这些特征值都保留了4位数字。字段中没有缺失的值。在569个患者中,一共有357个是良性,212个是恶性。

好了,我们的目标是生成一个乳腺癌诊断的SVM分类器,并计算这个分类器的准确率。首先设定项目的执行流程:

  1. 首先我们需要加载数据源;
  2. 在准备阶段,需要对加载的数据源进行探索,查看样本特征和特征值,这个过程你也可以使用数据可视化,它可以方便我们对数据及数据之间的关系进一步加深了解。然后按照“完全合一”的准则来评估数据的质量,如果数据质量不高就需要做数据清洗。数据清洗之后,你可以做特征选择,方便后续的模型训练;
  3. 在分类阶段,选择核函数进行训练,如果不知道数据是否为线性,可以考虑使用SVC(kernel=‘rbf’) ,也就是高斯核函数的SVM分类器。然后对训练好的模型用测试集进行评估。

按照上面的流程,我们来编写下代码,加载数据并对数据做部分的探索:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn import svm
from sklearn import metrics
from sklearn.preprocessing import StandardScaler

# 加载数据集,你需要把数据放到目录中
data = pd.read_csv("./data.csv")

# 数据探索
# 因为数据集中列比较多,我们需要把dataframe中的列全部显示出来
pd.set_option('display.max_columns', None)
print(data.columns)
print(data.head(5))
print(data.describe())

探索结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Index(['id', 'diagnosis', 'radius_mean', 'texture_mean', 'perimeter_mean',
'area_mean', 'smoothness_mean', 'compactness_mean', 'concavity_mean',
'concave points_mean', 'symmetry_mean', 'fractal_dimension_mean',
'radius_se', 'texture_se', 'perimeter_se', 'area_se', 'smoothness_se',
'compactness_se', 'concavity_se', 'concave points_se', 'symmetry_se',
'fractal_dimension_se', 'radius_worst', 'texture_worst',
'perimeter_worst', 'area_worst', 'smoothness_worst',
'compactness_worst', 'concavity_worst', 'concave points_worst',
'symmetry_worst', 'fractal_dimension_worst'],
dtype='object')
id diagnosis radius_mean texture_mean perimeter_mean area_mean \
0 842302 M 17.99 10.38 122.80 1001.0
1 842517 M 20.57 17.77 132.90 1326.0
2 84300903 M 19.69 21.25 130.00 1203.0
3 84348301 M 11.42 20.38 77.58 386.1
4 84358402 M 20.29 14.34 135.10 1297.0

下一步,是对数据进行清洗。

运行结果中,你能看到32个字段里,id是没有实际含义的,可以去掉。diagnosis字段的取值为B或者M,我们可以用0和1来替代。另外其余的30个字段,其实可以分成三组字段,下划线后面的mean、se和worst代表了每组字段不同的度量方式,分别是平均值、标准差和最大值。

1
2
3
4
5
6
7
8
9
10
# 将特征字段分成3组
features_mean= list(data.columns[2:12])
features_se= list(data.columns[12:22])
features_worst=list(data.columns[22:32])

# 数据清洗
# ID列没有用,删除该列
data.drop("id",axis=1,inplace=True)
# 将B良性替换为0,M恶性替换为1
data['diagnosis']=data['diagnosis'].map({'M':1,'B':0})

然后我们要做特征选择,首先需要观察下features_mean各变量之间的关系,这里我们可以用DataFrame的corr()函数,然后用热力图帮我们可视化呈现。同样,我们也会看整体良性、恶性肿瘤的诊断情况。

1
2
3
4
5
6
7
8
9
# 将肿瘤诊断结果可视化
sns.countplot(data['diagnosis'],label="Count")
plt.show()
# 用热力图呈现features_mean字段之间的相关性
corr = data[features_mean].corr()
plt.figure(figsize=(14,14))
# annot=True显示每个方格的数据
sns.heatmap(corr, annot=True)
plt.show()

具体运行的结果如下:

1564365378988

1564365591711

热力图中对角线上的为单变量自身的相关系数是1。颜色越浅代表相关性越大。分析热力图可以得出:radius_mean、perimeter_mean和area_mean相关性非常大,compactness_mean、concavity_mean、concave_points_mean这三个字段也是相关的,因此我们可以取其中的一个作为代表。

那么如何进行特征选择呢?

特征选择的目的是降维,用少量的特征代表数据的特性,这样也可以增强分类器的泛化能力,避免数据过拟合。

我们能看到mean、se和worst这三组特征是对同一组内容的不同度量方式,我们可以保留mean这组特征,在特征选择中忽略掉se和worst。同时我们能看到mean这组特征中,radius_mean、perimeter_mean、area_mean这三个属性相关性大,compactness_mean、daconcavity_mean、concave points_mean这三个属性相关性大。我们分别从这2类中选择1个属性作为代表,比如radius_mean和compactness_mean。

这样我们就可以把原来的10个属性缩减为6个属性,代码如下:

1
2
# 特征选择
features_remain = ['radius_mean','texture_mean', 'smoothness_mean','compactness_mean','symmetry_mean', 'fractal_dimension_mean']

对特征进行选择之后,我们就可以准备训练集和测试集:

1
2
3
4
5
6
7
# 抽取30%的数据作为测试集,其余作为训练集
train, test = train_test_split(data, test_size = 0.3)# in this our main data is splitted into train and test
# 抽取特征选择的数值作为训练和测试数据
train_X = train[features_remain]
train_y=train['diagnosis']
test_X= test[features_remain]
test_y =test['diagnosis']

在正式开始建模前,需要对数据进行规范化处理。采用Z-Score规范化数据,保证每个特征维度的数据均值为0,方差为1,具体代码如下:

1
2
3
4
# 采用Z-Score规范化数据,保证每个特征维度的数据均值为0,方差为1
ss = StandardScaler()
train_X = ss.fit_transform(train_X)
test_X = ss.transform(test_X)

最后,可以用SVM模型对规范化后的数据进行训练和预测,具体如下:

1
2
3
4
5
6
7
# 创建SVM分类器
model = svm.SVC()
# 用训练集做训练
model.fit(train_X,train_y)
# 用测试集做预测
prediction=model.predict(test_X)
print('准确率: ', metrics.accuracy_score(prediction,test_y))

得到的结果如下:

1
准确率:  0.9005847953216374

准确率大于90%,说明模型训练的结果还不错。