动手机器学习之乳腺癌检测

忙着写毕业论文,终于有时间整理这两年学习生信时接触的机器学习项目……

背景

机器学习在医疗行业被广泛地应用,尤其是癌症的早期检测和预测领域。传统方法如影像学分析依赖于经验丰富的放射科医生,但其主观性和有限的时间精力,使得一些早期症状可能被遗漏。而机器学习算法的出现,则提供了更加系统和高效的解决方案。

今天这个案例旨在向初学者展示如何利用机器学习算法,处理乳腺癌检测的问题。基于患者的细胞特征数据,建立一个机器学习模型来判断肿瘤的性质(良性或恶性)。

数据的载入与预处理

项目使用经典的威斯康星州乳腺癌数据集,数据包括结块厚度、细胞大小、形状的均匀性、边缘黏附力等特征。

这些变量经过处理后,输入到机器学习模型进行训练和预测。

import numpy as np
import pandas as pd
from pandas.plotting import scatter_matrix
import matplotlib.pyplot as plt

# 案例使用 Python2 和 0.19.1 的 sklearn,如今在 API 的调用上已经有许多不同之处
# from sklearn import preprocessing, cross_validation

# 更新
from sklearn import preprocessing, model_selection

from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.metrics import classification_report
from sklearn.metrics import accuracy_score

# 载入数据集
names = ['id', 'clump_thickness', 'uniform_cell_size', 'uniform_cell_shape',
       'marginal_adhesion', 'single_epithelial_size', 'bare_nuclei',
       'bland_chromatin', 'normal_nucleoli', 'mitoses', 'class']
df = pd.read_csv('data.csv', names=names)

数据的预处理并不复杂,主要为数据的清洗和划分。

# 数据清洗

## 替换缺失数据
df.replace('?',-99999, inplace=True)
## 删除无关特征
df.drop(['id'], axis=1, inplace=True)

## 检查数据
print(df.loc[10])
## 查看维度
print(df.shape)
# 描述性统计
print(df.describe())

输出:

clump_thickness           1
uniform_cell_size         1
uniform_cell_shape        1
marginal_adhesion         1
single_epithelial_size    1
bare_nuclei               1
bland_chromatin           3
normal_nucleoli           1
mitoses                   1
class                     2
Name: 10, dtype: object

(699, 10)

       clump_thickness  uniform_cell_size  uniform_cell_shape  \
count       699.000000         699.000000          699.000000   
mean          4.417740           3.134478            3.207439   
std           2.815741           3.051459            2.971913   
min           1.000000           1.000000            1.000000   
25%           2.000000           1.000000            1.000000   
50%           4.000000           1.000000            1.000000   
75%           6.000000           5.000000            5.000000   
max          10.000000          10.000000           10.000000   

       marginal_adhesion  single_epithelial_size  bland_chromatin  \
count         699.000000              699.000000       699.000000   
mean            2.806867                3.216023         3.437768   
std             2.855379                2.214300         2.438364   
min             1.000000                1.000000         1.000000   
25%             1.000000                2.000000         2.000000   
50%             1.000000                2.000000         3.000000   
75%             4.000000                4.000000         5.000000   
max            10.000000               10.000000        10.000000   

       normal_nucleoli     mitoses       class  
count       699.000000  699.000000  699.000000  
mean          2.866953    1.589413    2.689557  
std           3.053634    1.715078    0.951273  
min           1.000000    1.000000    2.000000  
25%           1.000000    1.000000    2.000000  
50%           1.000000    1.000000    2.000000  
75%           4.000000    1.000000    4.000000  
max          10.000000   10.000000    4.000000  

接下来,我们通过直方图(histogram)和散点图(scatter)对数据进行可视化。

scatter_matrix(df, figsize = (18,18))
plt.show()

我们从图中更为直接地观察数据的分布,以及特征变量之间的关系,方便选择适用的机器学习算法。

sklearn 为我们提供划分数据集的 API。(train_test_split 现为 model_selection 的子模块)

X = np.array(df.drop(['class'], 1))
y = np.array(df['class'])

# sklearn 0.19.1
# X_train, X_test, y_train, y_test = cross_validation.train_test_split(X, y, test_size=0.2)

X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y, test_size=0.2)

模型训练

这个案例仅仅是入门级的展示,我们使用 SVM(support vector machine)和 KNN(K-Nearest Neighbors)两种经典算法来建模,并进行比较分析。

seed = 8
scoring = 'accuracy'

# 定义模型
models = []
models.append(('KNN', KNeighborsClassifier(n_neighbors = 5)))

# scikit-learn 在 0.22 版本中对 SVC 的默认参数进行了更改
# gamma 参数的默认值从 'auto' 改为 'scale'
# models.append(('SVM', SVC()))
models.append(('SVM', SVC(gamma='auto')))

# 记录模型表现
results = []
names = []

for name, model in models:
    # kfold = model_selection.KFold(n_splits=10, random_state = seed)
    # 指定 seed 需要令参数 shuffle = True
    kfold = model_selection.KFold(n_splits=10, shuffle = True, random_state = seed)
    cv_results = model_selection.cross_val_score(model, X_train, y_train, cv=kfold, scoring=scoring)
    results.append(cv_results)
    names.append(name)
    msg = "%s: %f (%f)" % (name, cv_results.mean(), cv_results.std())
    print(msg)

根据以上代码,训练用的数据被随机分为 10 等分(折,folds),模型会重复训练 10 次,每次选择 1 折 作为测试集,其余 9 折 作为训练集,最终以 10 次验证结果的准确率来评估模型的表现。10 折交叉检验的结果如下:

KNN: 0.966039 (0.029270)
SVM: 0.960649 (0.032726)

模型应用

利用事先划分好的非训练数据进行验证:

for name, model in models:
    model.fit(X_train, y_train)
    predictions = model.predict(X_test)
    print(name)
    print(accuracy_score(y_test, predictions))
    print(classification_report(y_test, predictions))

输出结果如下:

KNN
0.9785714285714285
              precision    recall  f1-score   support

           2       0.99      0.98      0.98        95
           4       0.96      0.98      0.97        45

    accuracy                           0.98       140
   macro avg       0.97      0.98      0.98       140
weighted avg       0.98      0.98      0.98       140

SVM
0.9642857142857143
              precision    recall  f1-score   support

           2       1.00      0.95      0.97        95
           4       0.90      1.00      0.95        45

    accuracy                           0.96       140
   macro avg       0.95      0.97      0.96       140
weighted avg       0.97      0.96      0.96       140

classification_report 评估报告包含每个类别的主要评价指标:

Precision(精确率): 表示模型预测为某类别的样本中,有多少比例是真正属于该类别的。适合关注 减少误报 的场景(如癌症诊断中避免误诊为癌症)。

Recall(召回率/灵敏度):表示真实属于某类别的样本中,有多少被模型正确识别。 高召回率:模型能捕获更多正样本。适合关注 减少漏报 的场景(如癌症筛查中尽量避免漏诊)。

F1-Score(F1值):Precision 和 Recall 的调和平均值,用于权衡二者。

以 SVM 的报告结果为例,