模型测试与基准结果
我们将使用重复的分层k折交叉验证来评估候选模型。
k-fold交叉验证程序可以提供一个良好的模型性能总体估计值,与单次的留出验证相比,这种方法不容易带来过高的偏差。我们取k=10,这意味着每折将包含约11183/10或约1118个示例。
分层意味着每折的数据分布将与整体数据分布保持一致,即大约98%的无癌症对象与2%的有癌症对象。重复则意味着会进行多次重复实验,以帮助避免侥幸结果并更好地确定所选模型的方差。我们将重复进行三次实验。这意味着将对单个模型进行10×3即30次拟合和评估,并统计运行结果的平均值和标准差。这可以通过使用重复的scikit-learn库的RepeatedStratifiedKFold类来实现。
我们将使用roc_auc_score()函数计算的AUC来评估和比较模型效果。
我们可以定义一个函数来加载数据集,并将列拆分为输入和输出变量。我们将类标签重新编码为0和1。下面的load_dataset()
函数实现了这一点。
# load the datasetdef load_dataset(full_path): # load the dataset as a numpy array data = read_csv(full_path, header=None) # retrieve numpy array data = data.values # split into input and output elements X, y = data[:, :-1], data[:, -1] # label encode the target variable to have the classes 0 and 1 y = LabelEncoder().fit_transform(y) return X, y
然后,我们可以定义一个函数,用于在数据集上评估给定模型,并返回每折和与每次重复实验的AUC列表。
下面的evaluate_model()
函数实现了这一点,将数据集和模型作为参数并返回分数列表。
# evaluate a modeldef evaluate_model(X, y, model): # define evaluation procedure cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) # evaluate model scores = cross_val_score(model, X, y, scoring='roc_auc', cv=cv, n_jobs=-1) return scores
最后,我们可以使用这个测试工具评估数据集上的基准模型。对每个样本进行随机预测的分类器的AUC期望值为0.5,这是该数据集性能的基线。这个随机预测的分类器一个所谓的“无效”分类器。
这可以通过scikit-learn库的DummyClassifier类来实现,将参数“strategy”设置为‘stratified‘。
...# define the reference modelmodel = DummyClassifier(strategy='stratified')
一旦模型评估完成,我们就可以直接获得AUC得分的平均值和标准差。
...# evaluate the modelscores = evaluate_model(X, y, model)# summarize performanceprint('Mean ROC AUC: %.3f (%.3f)' % (mean(scores), std(scores)))
将这几部分结合起来,下面列出了加载数据集、评估基准模型和报告分类器性能的完整示例。
# test harness and baseline model evaluationfrom collections import Counterfrom numpy import meanfrom numpy import stdfrom pandas import read_csvfrom sklearn.preprocessing import LabelEncoderfrom sklearn.model_selection import cross_val_scorefrom sklearn.model_selection import RepeatedStratifiedKFoldfrom sklearn.dummy import DummyClassifier # load the datasetdef load_dataset(full_path): # load the dataset as a numpy array data = read_csv(full_path, header=None) # retrieve numpy array data = data.values # split into input and output elements X, y = data[:, :-1], data[:, -1] # label encode the target variable to have the classes 0 and 1 y = LabelEncoder().fit_transform(y) return X, y # evaluate a modeldef evaluate_model(X, y, model): # define evaluation procedure cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) # evaluate model scores = cross_val_score(model, X, y, scoring='roc_auc', cv=cv, n_jobs=-1) return scores # define the location of the datasetfull_path = 'mammography.csv'# load the datasetX, y = load_dataset(full_path)# summarize the loaded datasetprint(X.shape, y.shape, Counter(y))# define the reference modelmodel = DummyClassifier(strategy='stratified')# evaluate the modelscores = evaluate_model(X, y, model)# summarize performanceprint('Mean ROC AUC: %.3f (%.3f)' % (mean(scores), std(scores)))
运行示例,首先加载并汇总数据集。我们可以看到加载的行数是正确的,并且有6个输入变量。重要的是,我们可以看到类标签具有到整数的正确映射,多数类记为0,少数类记为1,通常用于不平衡的二分类数据集。
接下来,报告AUC得分的平均值。
如预期的那样,无效分类器获得了平均AUC约为0.5的最坏性能。这为性能提供了一个基线,在这个基线之上,可以认为模型在这个数据集是有效的。
(11183, 6) (11183,) Counter({0: 10923, 1: 260})Mean ROC AUC: 0.503 (0.016)
现在我们有了测试工具和基准性能,我们可以开始在这个数据集上评估一些模型。
模型评估
在本节中,我们将使用上一节中开发的测试工具在数据集上评估不同的分类算法。
我们的目的是演示如何系统地解决问题,并展示某些专门为不平衡分类问题设计的算法的效果。我们获得的模型性能良好,但是仍未高度优化(例如,我们没有优化模型超参数)。
你能做得更好吗?我很乐意看到读者们能用同样的测试工具获得更好的AUC,欢迎在评论区留言。
机器学习算法评估
首先,我们在这个数据集上评估一些普通的机器学习模型。
我们可以在数据集上检测一系列不同的线性或非线性算法,这会很有用:这样我们可以快速了解到哪些算法在数据集上表现良好,而哪些算法则不值得我们关注。我们将在乳腺摄影数据集上评估以下机器学习模型:
- 逻辑回归(LR)
- 支持向量机(SVM)
- Bagging算法(BAG)
- 随机森林(RF)
- 梯度提升机(GBM)
我们将主要使用默认的模型超参数,除了集成学习算法中的n_estimator,我们将其设置为1000。
我们将依次定义每个模型并将它们添加到列表中,以便可以按顺序对它们进行评估。我们定义下面的*get_models()*函数来评估模型效果并绘图。
# define models to testdef get_models(): models, names = list(), list() # LR models.append(LogisticRegression(solver='lbfgs')) names.append('LR') # SVM models.append(SVC(gamma='scale')) names.append('SVM') # Bagging models.append(BaggingClassifier(n_estimators=1000)) names.append('BAG') # RF models.append(RandomForestClassifier(n_estimators=1000)) names.append('RF') # GBM models.append(GradientBoostingClassifier(n_estimators=1000)) names.append('GBM') return models, names
然后,我们可以依次列举模型列表中的每个模型并进行评估,记录AUC并存储在列表中以供以后绘制。
...# define modelsmodels, names = get_models()results = list()# evaluate each modelfor i in range(len(models)): # evaluate the model and store results scores = evaluate_model(X, y, models[i]) results.append(scores) # summarize and store print('>%s %.3f (%.3f)' % (names[i], mean(scores), std(scores)))
在运行结束时,我们可以绘制模型表现的箱线图,以便直接比较模型表现。
# plot the resultspyplot.boxplot(results, labels=names, showmeans=True)pyplot.show()
将这些结合起来,下面列出了评估乳腺摄影数据集上一套机器学习算法的完整示例。
# spot check machine learning algorithms on the mammography datasetfrom numpy import meanfrom numpy import stdfrom pandas import read_csvfrom matplotlib import pyplotfrom sklearn.preprocessing import LabelEncoderfrom sklearn.model_selection import cross_val_scorefrom sklearn.model_selection import RepeatedStratifiedKFoldfrom sklearn.linear_model import LogisticRegressionfrom sklearn.svm import SVCfrom sklearn.ensemble import RandomForestClassifierfrom sklearn.ensemble import GradientBoostingClassifierfrom sklearn.ensemble import BaggingClassifier # load the datasetdef load_dataset(full_path): # load the dataset as a numpy array data = read_csv(full_path, header=None) # retrieve numpy array data = data.values # split into input and output elements X, y = data[:, :-1], data[:, -1] # label encode the target variable to have the classes 0 and 1 y = LabelEncoder().fit_transform(y) return X, y # evaluate a modeldef evaluate_model(X, y, model): # define evaluation procedure cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) # evaluate model scores = cross_val_score(model, X, y, scoring='roc_auc', cv=cv, n_jobs=-1) return scores # define models to testdef get_models(): models, names = list(), list() # LR models.append(LogisticRegression(solver='lbfgs')) names.append('LR') # SVM models.append(SVC(gamma='scale')) names.append('SVM') # Bagging models.append(BaggingClassifier(n_estimators=1000)) names.append('BAG') # RF models.append(RandomForestClassifier(n_estimators=1000)) names.append('RF') # GBM models.append(GradientBoostingClassifier(n_estimators=1000)) names.append('GBM') return models, names # define the location of the datasetfull_path = 'mammography.csv'# load the datasetX, y = load_dataset(full_path)# define modelsmodels, names = get_models()results = list()# evaluate each modelfor i in range(len(models)): # evaluate the model and store results scores = evaluate_model(X, y, models[i]) results.append(scores) # summarize and store print('>%s %.3f (%.3f)' % (names[i], mean(scores), std(scores)))# plot the resultspyplot.boxplot(results, labels=names, showmeans=True)pyplot.show()
运行该示例以依次评估每个算法并报告AUC的平均值和标准差。由于学习算法的随机性,您的特定结果会有所不同;您可以考虑多次运行这一程序。
我们看到,我们评估的所有算法都是有效的,都实现了高于基准值0.5的AUC。
结果表明,基于决策树的集成学习算法(包括Bagging算法与随机森林)在这个数据集上表现得更好,其中随机森林表现最好,AUC约为0.950。
值得注意的是,我们获取的结果好于论文中描述的结果(0.93),虽然我们的评估流程略有不同。
评估结果对LR和支持向量机算法有点不公平,因为我们在拟合模型之前没有缩放输入变量。我们可以在下一节探讨这个问题。
>LR 0.919 (0.040)>SVM 0.880 (0.049)>BAG 0.941 (0.041)>RF 0.950 (0.036)>GBM 0.918 (0.037)
我们为每一个算法创建一个箱线图。箱线图中的“箱子”显示了数据的中间50%的分布范围,每个框中间的橙色线显示样本的中位数,每个框中的绿色三角形显示样本的平均值。
我们可以看到,BAG和RF的分布都很紧密,平均值和中位数也很接近,这意味着得分的分布可能为无偏的高斯分布,如稳定分布。
箱型图
现在我们已经有了一组很好的结果,让我们看看是否可以使用代价敏感的分类器来改进它们。
评估代价敏感算法
一些机器学习算法在拟合模型时可以更注意其中的某一类,这些模型被称为代价敏感的机器学习模型,通过指定与类分布成反比的代价值,它们可以用于不平衡分类。例如,对于多数类和少数类,它们的比例分别为98%和2%,因此我们可以指定少数类分类错误的代价为98,多数类分类错误的代价为2。
能够实现此功能的三种算法是:
- 逻辑回归(LR)
- 支持向量机(SVM)
- 随机森林(RF)
这可以在scikit-learn中实现,方法是将*"class_weight"参数设置为“balanced”*,赋予这些算法代价敏感性。
例如,下面更新的get_models()
函数定义了要在数据集上进行计算的三种算法的代价敏感版本。
# define models to testdef get_models(): models, names = list(), list() # LR models.append(LogisticRegression(solver='lbfgs', class_weight='balanced')) names.append('LR') # SVM models.append(SVC(gamma='scale', class_weight='balanced')) names.append('SVM') # RF models.append(RandomForestClassifier(n_estimators=1000)) names.append('RF') return models, names
此外,在探索数据集时,我们注意到许多变量的数据分布呈指数分布。有时我们可以通过对每个变量使用幂变换来取得更好的数据分布。这将特别有助于LR和SVM算法,也可能有助于RF算法。
我们可以使用Pipeline类在交叉验证模型评估的每一折上中实现它。Pipeline的第一步定义PowerTransformer,然后应用于每一折的训练集和测试集;第二步则是我们正在评估的模型。然后可以使用evaluate_model()
函数直接对Pipeline进行计算,示例代码如下:
...# defines pipeline stepssteps = [('p', PowerTransformer()), ('m',models[i])]# define pipelinepipeline = Pipeline(steps=steps)# evaluate the pipeline and store resultsscores = evaluate_model(X, y, pipeline)
把这一步结合在我们之前的代码中,下面列出了在乳腺摄影数据集上评估进行幂变换后的代价敏感型机器学习算法的完整示例。
# cost-sensitive machine learning algorithms on the mammography datasetfrom numpy import meanfrom numpy import stdfrom pandas import read_csvfrom matplotlib import pyplotfrom sklearn.preprocessing import LabelEncoderfrom sklearn.preprocessing import PowerTransformerfrom sklearn.pipeline import Pipelinefrom sklearn.model_selection import cross_val_scorefrom sklearn.model_selection import RepeatedStratifiedKFoldfrom sklearn.linear_model import LogisticRegressionfrom sklearn.svm import SVCfrom sklearn.ensemble import RandomForestClassifier # load the datasetdef load_dataset(full_path): # load the dataset as a numpy array data = read_csv(full_path, header=None) # retrieve numpy array data = data.values # split into input and output elements X, y = data[:, :-1], data[:, -1] # label encode the target variable to have the classes 0 and 1 y = LabelEncoder().fit_transform(y) return X, y # evaluate a modeldef evaluate_model(X, y, model): # define evaluation procedure cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) # evaluate model scores = cross_val_score(model, X, y, scoring='roc_auc', cv=cv, n_jobs=-1) return scores # define models to testdef get_models(): models, names = list(), list() # LR models.append(LogisticRegression(solver='lbfgs', class_weight='balanced')) names.append('LR') # SVM models.append(SVC(gamma='scale', class_weight='balanced')) names.append('SVM') # RF models.append(RandomForestClassifier(n_estimators=1000)) names.append('RF') return models, names # define the location of the datasetfull_path = 'mammography.csv'# load the datasetX, y = load_dataset(full_path)# define modelsmodels, names = get_models()results = list()# evaluate each modelfor i in range(len(models)): # defines pipeline steps steps = [('p', PowerTransformer()), ('m',models[i])] # define pipeline pipeline = Pipeline(steps=steps) # evaluate the pipeline and store results scores = evaluate_model(X, y, pipeline) results.append(scores) # summarize and store print('>%s %.3f (%.3f)' % (names[i], mean(scores), std(scores)))# plot the resultspyplot.boxplot(results, labels=names, showmeans=True)pyplot.show()
运行该示例以依次评估每个算法并报告AUC的平均值和标准差。由于学习算法的随机性,您的特定结果会有所不同;您可以考虑多次运行这一程序。
在这种情况下,我们可以看到,相比进行幂变换与添加代价敏感性之前,所有三个被测试的算法在AUC上都实现了提升。读者还可以试着在不进行幂变换的情况下进行重复实验,来探索算法性能提升的原因是幂变换,还是代价敏感算法,亦或是二者皆有。
在这种情况下,我们可以看到支持向量机取得了最好的性能,在本节和上一节中的性能优于RF,并且实现了大约0.957的平均AUC。
>LR 0.922 (0.036)>SVM 0.957 (0.024)>RF 0.951 (0.035)
我们绘制了箱线图来对比不同算法AUC的分布。与其它两种模型相比,支持向量机的分布更为紧凑。因此,它的性能可能是最稳定的,可以作为最终模型的一个良好候选。
代价敏感箱线图
接下来,让我们看看如何使用最终模型对新数据进行预测。
对新数据进行预测
在本节中,我们将拟合一个最终模型,并使用它对单行数据进行预测。
我们将使用代价敏感的支持向量机模型作为最终模型,在对模型进行拟合和预测之前对数据进行幂变换。使用pipeline将确保始终正确地对输入数据执行转换。
首先,我们可以将模型定义为pipeline。
...# define model to evaluatemodel = SVC(gamma='scale', class_weight='balanced')# power transform then fit modelpipeline = Pipeline(steps=[('t',PowerTransformer()), ('m',model)])
定义之后,我们可以在整个训练集上拟合模型。
...# fit the modelpipeline.fit(X, y)
拟合完成后,我们可以调用*predict()*函数来对新数据进行预测。函数将返回0(意味着没有癌症)或1(意味着癌症)。示例代码如下:
...# define a row of datarow = [...]# make predictionyhat = model.predict([row])
为了演示上述流程,我们使用拟合后的模型对我们已经知道癌症与否的部分数据进行预测。示例代码如下:
# fit a model and make predictions for the on the mammography datasetfrom pandas import read_csvfrom sklearn.preprocessing import LabelEncoderfrom sklearn.preprocessing import PowerTransformerfrom sklearn.svm import SVCfrom sklearn.pipeline import Pipeline # load the datasetdef load_dataset(full_path): # load the dataset as a numpy array data = read_csv(full_path, header=None) # retrieve numpy array data = data.values # split into input and output elements X, y = data[:, :-1], data[:, -1] # label encode the target variable to have the classes 0 and 1 y = LabelEncoder().fit_transform(y) return X, y # define the location of the datasetfull_path = 'mammography.csv'# load the datasetX, y = load_dataset(full_path)# define model to evaluatemodel = SVC(gamma='scale', class_weight='balanced')# power transform then fit modelpipeline = Pipeline(steps=[('t',PowerTransformer()), ('m',model)])# fit the modelpipeline.fit(X, y)# evaluate on some no cancer cases (known class 0)print('No Cancer:')data = [[0.23001961,5.0725783,-0.27606055,0.83244412,-0.37786573,0.4803223], [0.15549112,-0.16939038,0.67065219,-0.85955255,-0.37786573,-0.94572324], [-0.78441482,-0.44365372,5.6747053,-0.85955255,-0.37786573,-0.94572324]]for row in data: # make prediction yhat = pipeline.predict([row]) # get the label label = yhat[0] # summarize print('>Predicted=%d (expected 0)' % (label))# evaluate on some cancer (known class 1)print('Cancer:')data = [[2.0158239,0.15353258,-0.32114211,2.1923706,-0.37786573,0.96176503], [2.3191888,0.72860087,-0.50146835,-0.85955255,-0.37786573,-0.94572324], [0.19224721,-0.2003556,-0.230979,1.2003796,2.2620867,1.132403]]for row in data: # make prediction yhat = pipeline.predict([row]) # get the label label = yhat[0] # summarize print('>Predicted=%d (expected 1)' % (label))
运行该示例,首先会在整个培训数据集上拟合模型。接下来,从数据集中选择一些没有癌症的数据进行预测,我们可以看到所有的情况都被正确地预测了;然后我们输入一些癌症数据再对标签进行预测,正如我们所希望的那样,在这两种情况下数据的标签都得到了正确的预测。
No Cancer:>Predicted=0 (expected 0)>Predicted=0 (expected 0)>Predicted=0 (expected 0)Cancer:>Predicted=1 (expected 1)>Predicted=1 (expected 1)>Predicted=1 (expected 1)
延伸阅读
如果您想深入了解,本节将提供有关此主题的更多资源。
论文
- Comparative Evaluation Of Pattern Recognition Techniques For Detection Of Microcalcifications In Mammography, 1993.
- SMOTE: Synthetic Minority Over-sampling Technique, 2002.
API
- sklearn.model_selection.RepeatedStratifiedKFold API.
- sklearn.metrics.roc_auc_score API.
- sklearn.dummy.DummyClassifier API.
- sklearn.svm.SVC API.
数据集
- Mammography Dataset.
- Mammography Dataset Description
总结
在本教程中,您学习了如何开发和评估乳腺摄影数据集的不平衡分类模型。具体来说,您学到了:
- 如何加载和探索数据集,并从中获得预处理数据与选择模型的灵感。
- 如何使用代价敏感算法评估一组机器学习模型并提高其性能。
- 如何拟合最终模型并使用它预测特定情况下的类标签。
还有什么问题吗?欢迎在评论区提出你的问题,我会尽力回答。
代码较多,需要pdf版本请关注后索取百度网盘下载地址