网格搜索、管道、决策树、支持向量机、Hyperparameter调优、混淆矩阵、ROC曲线,如果你三个月前向我提起这些术语,我绝对不知道你在说什么。
然而我刚刚完成了一个项目,使用了所有这些技术、方法和工具。我的大脑已经接受了每天都会被新概念淹没的事实,并期待着能够立即将它们投入使用。
Flatiron School数据科学项目的第三个主要项目是一个专门关注分类的监督机器学习项目。与之前的其他项目不同,我们被要求构建预测离散目标变量的模型,而不是连续或数值目标变量。
与预测票房、电影价值或房屋销售价格不同,我们可以在几个不同的分类数据集中选择,也可以选择我们自己的一个。在搜索存储库和数据收集数天之后,我最终确定了一个符合我兴趣的,并提出了一个需要解决的现实问题。
当我听同伴们讨论话题时,我意识到我错过了一个难得的机会,为这个项目选择一些积极向上、引人入胜的东西来进行调查。
我听到学生们谈论他们很想做出的与体育相关的不同预测,还有人计划使用算法来预测电子游戏中的音乐类型或评分。非常多有趣的想法!轮到我介绍我的主题时,我深深地叹了口气,笨拙地解释说,我选择了一个有关胎儿健康结果和死亡率的数据集。是的。不知何故,无忧无虑的我挑了一个最严肃、最令人沮丧的话题。
然而,我的项目本身和数据的目标实际上是相当有希望和乐观的。这一切的前提是美国持续存在的公共健康问题即胎儿死亡率。这是我非常关心的一个话题,这促使我花了几个小时寻找用于这个目的的正确数据。
我发现这个数据集包含超过2000行患者的心电图(CTG)记录,包含诸如胎儿心率、子宫收缩、胎儿运动等特征。然后,每一份记录都由“专业产科医生”分类为正常、可疑或病理胎儿健康结果。
我将其调整为一个二元分类问题,将可疑结果和病理结果合并到一个类别中,并将其标记为“危险”。我打算证明,仅凭CTG检查的指标就可以预测胎儿健康结果,这意味着这种预测可以自动化,而医疗服务提供商只需通过阅读报告,就可以采取更积极的挽救生命措施。
我在着手这个项目时首先意识到的一件事是,管道基本上是一种神奇的东西,它简化了将分类器与数据匹配的过程。
管道承担了扩展、预处理和设置分类器的所有步骤,并将它们组合到单个对象中,从而干净地完成流程并消除一些人为错误。
因为有很多不同的算法我想探索,所以我写了一个函数。这个函数输入一个分类器并且返回一个管道对象。(请注意,我class_weight参数设置为‘balanced’,因为我的目标变量不平衡;在我的数据中,健康状况正常的婴儿比危险婴儿要多得多。)
from sklearn.pipeline import Pipeline
# 创建带有分类器输入的管道
def pipe_maker(classifier):
'''接受一个分类器,返回一个管道'''
pipe = Pipeline([('scl', StandardScaler()),
('clf', classifier(class_weight= 'balanced', random_state=42))])
return pipe
现在,可以说比管道搜索更令人惊奇的是网格搜索的能力。
GridSearchCV允许你为一个模型检查许多不同的超参数,并通过选择最佳参数为所选的度量优化你的模型。
在这种情况下,我优先考虑的指标是召回率。通过对召回率进行优化,我最小化了模型预测中错误否定或第二类错误的数量。
虽然我对模型有时错误地预测婴儿处于危险中没有意见,但我对模型有时错误地预测婴儿是健康的就不那么满意了。召回率指标是这个项目最重要的指标,远远超过精度、准确性或其他任何评估指标。
为此我编写了一个函数,它接受一个管道对象(例如,来自上面的函数)和一组要调优的超参数,并返回一个网格搜索对象。如果你要优化的不是召回率,比如准确性,那么你只需要更改评分参数来反映这一点。
from sklearn.model_selection import GridSearchCV
# 创建包含管道的网格搜索
def gridsearch_maker(pipeline, params):
'''接收管道和参数网格,返回GridSearchCV对象'''
return GridSearchCV(estimator=pipeline,
param_grid=params,
scoring='recall',
cv=10,
n_jobs=-1)
最后,我编写了一个函数来接收网格搜索对象(如上面的函数所创建的对象)并返回用于评估的相关信息。这才是真正神奇的地方。最后一个函数接受上面的网格搜索对象,并对模型进行优化,结果:
- 基于你选择的评估指标,为你的模型提供最好的超参数
- 一个分类报告,其中包含这个优化模型的精度、召回率、F1得分和准确度
- 一个彩色编码的混淆矩阵告诉你真正例,真反例,假正例,假反例的数量
from sklearn.metrics import recall_score, confusion_matrix, classification_report, plot_confusion_matrix
def find_best_recall(gridsearch):
"""
运行网格搜索,遍历预定义的网格参数,并返回最佳参数以优化召回分数。
将分类器与X_train和y_train拟合。
确定并打印召回的最佳参数。
确定并打印最佳训练集召回率。
使用最佳参数对测试数据进行预测。
打印最好的测试集召回率。
打印最佳模型的分类报告。
最佳模型的混淆矩阵。
参数:
gridsearch: 预定义的GridsearchCV实例,已经设置了参数和估计器/管道。
Returns:
最佳参数为特定的网格搜索,基于召回率分数。
最佳训练集召回率。
最好的测试集召回率。
最佳模型分类报告。
最佳模型的混淆矩阵。
"""
# 匹配网格搜索对象
best_recall = 0.0
# 匹配网格搜索
gridsearch.fit(X_train, y_train)
# 最佳参数
print('Best params: %s' % gridsearch.best_params_)
# 最好的训练数据召回率
print('Best training recall: %.3f' % gridsearch.best_score_)
# 用最好的参数预测测试数据
y_pred = gridsearch.predict(X_test)
# 测试数据召回率与最好的参数
print('Test set recall score for best params: %.3f ' % recall_score(y_test, y_pred))
# 混淆矩阵和分类报告
print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred))
print('Recall score: ',recall_score(y_test, y_pred))
# 绘制混淆矩阵
plot_confusion_matrix(gridsearch, X_test, y_test,cmap="RdPu")
plt.show()
然后,一旦你有了这个,你就可以再做一次!一遍又一遍,尽可能多地重复,直到得到最好的模型。
就我个人而言,我尝试了一些决策树、一些逻辑回归、一个随机森林、一个支持向量机,甚至一些带有TPOT分类器的AutoML(结果并不像我希望的那样好)。
上面这张图是我使用极限树(Extra Trees )分类器完成的最好的模型。它的召回分数是97%,虽然不是很完美,但是比我的基准模型的召回分数77%好多了。
极限树分类器到底是什么?
如果你熟悉随机森林,那么你就会知道它是一种由决策树组成的算法。
极限树是一种非常类似的算法,它使用一系列决策树来对数据点所属的类或类别做出最终预测。然而,极限树与随机森林的不同之处在于,它使用了整个原始样本,而不是像随机森林那样对数据进行子抽样并进行替换。
另一个区别是节点的分割方式。虽然随机森林总是选择可能的最佳分割,但极限树选择随机分割。极限树和随机森林都被编程来优化最终结果。
我很惊讶,我可以用极限树建立一个比随机森林更好的模型。虽然我的随机森林模型和我的网格搜索的最佳参数返回了94%的不错的召回率分数,我的极限树模型返回了一个更好的召回率分数。
这里的教训是,使用不同的算法和分类器可以获得很多有价值的东西,所以不断迭代不同的模型和算法总是一个好主意,直到你找到最优的一个适合你的特定数据集和评估指标。
在经历了线性回归和处理多重线性和异方差等令人头疼的问题之后,这些分类模型真的很棒。看到这些模型能做多少事情是很有启发性的;他们如何在不完善的数据下工作,仍然表现出色。
我特别喜欢能够直观地看到决策树模型是如何做出决策的!了解所有这些不同的模型是如何运作的,它们背后的数学和科学,以及它们所表现出的直觉和可解释性是很吸引人的。
如果你对本文使用的方法、详细的结果和可视化、以及结论和建议感到好奇,请查看这个GitHub项目:
https://github.com/dtunnicliffe/fetal-health-classification。