kaggle季军新手笔记:利用fast.ai对油棕林的图像分类(附代码)
作者:Mercy Markus
翻译:陈之炎
校对:丁楠雅
本文约3700字,建议阅读10+分钟。
一支深度学习的新手队如何在kaggle竞赛中获得第三名?
图:亚历山大·纳格斯塔德 (AlexanderNaglestad)
WiDS数据马拉松由女性数据科学工作者与她们的伙伴联合发起,她们面临的挑战是需要建立一个模型,来预测一批卫星图像中存在油棕人工林种植园的情况。
数据集是称为“行星”的人造卫星新近拍摄的经加注后的卫星图像数据集,该图像数据集具有3米的空间分辨率,每幅图像都是基于图像中是否存在油棕种植园进行标记的(0表示没有油棕种植园,1表示有油棕种植园)。任务是需要训练一个模型,该模型以卫星图像为输入,并输出对油棕种植园的图像预测的概率。竞赛创建者为模型开发提供了经过标记后的训练和测试数据集。
详情见这里:https://www.kaggle.com/c/widsdatathon2019
我的队友(Abundshakur,Halimah,和IfeomaOkoh)和我采用了fast.ai框架来应对这一挑战。感谢托马斯·卡贝尔(ThomasCapelle)提供了Kaggle上的入门内核,它给出了如何处理这个问题的深刻见解,同时也为fast.ai团队创造了一门神奇的深度学习课程,从而简化了许多复杂的深度学习的概念。现在,深度学习的初学者也可以赢得kaggle比赛了。
我们从一个简单易学的关于深度学习的教程开始吧
目前,无需为理解所有的东西而担心,文中会伴随着大量的练习。本教程旨在展示fast.ai对于深度学习的初学者来说的神奇之处,前提是假设读者会python语言,并且接触过一些ML(机器学习)。如果你已经具备上述技能,那我们就可以走上正轨了。
文中的所有代码都可以在Google Colaboratory(https://colab.research.google.com/notebooks/welcome.ipynb)上获得,这是一个免费的Jupyter笔记本环境,无需安装,运行在云上。可以采用协作的方式编写和执行代码,存盘和共享分析结果,访问功能强大的计算资源,所有这些都是免费的。点击这里可以访问到我们将要使用到的代码。
导入fast.ai和将要用到的其他库
# This ensures that any edits to libraries you make are reloaded here automatically, # and also that any charts or images displayed are shown in this notebook. %reload_ext autoreload %autoreload 2 %matplotlib inline导入库
# Import libraries from fastai import * from fastai.vision import * from fastai.callbacks import CSVLogger, SaveModelCallback import seaborn as sns import warnings warnings.filterwarnings('ignore')
获取竞赛数据
为了尽可能地直观,Abdishakur将竞赛数据文件上传到dropbox.com,可以在这个竞赛页面上找到竞赛数据文件,而且只有接受竞争规则之后才能访问到这些数据文件。
# Get the data from dropbox link !wget https://www.dropbox.com/s/6kltw0kqynlijxv/widsdatathon2019.zip # The downloaded competition data is zipped, let us unzip it !unzip widsdatathon2019.zip # The training and testing data have already been seperated, Unzip them as well !unzip train_images.zip !unzip leaderboard_holdout_data.zip !unzip leaderboard_test_data.zip
查看数据
在处理这个问题之前,首先要做的是查看可用的数据。在知道如何解决问题之前,我们首先需要了解问题和数据是什么样子。查看数据意味着了解数据目录的结构,标签是什么,以及一些示例图像是什么。
# Overview of the labels of the training data; df = pd.read_csv('data/traininglabels.csv') df.head()
使用pandas库读取数据
训练模型过程中使用到的数据标签
处理图像分类数据集和表格数据集的主要区别在于标签的存储方式,这里的标签指的是图像中的内容。在这个特定的数据集中,标签存储在CSV文件中。
要了解如何计算分数列的详细信息,请访问此链接:https://success.figure-eight.com/hc/en-us/articles/201855939-How-to-Calculate-a-Confidence-Score
利用countplot函数来查看培训数据的分布情况,从图中可以看到:大约有14,300幅图片没有油棕榈种植园,而只有942幅图片有油棕榈种植园,这可以称为非均衡数据集,关于非均衡数据集这个深度学习问题,我们不打算在此讨论;目前,可以从这里开始起步:
sns.countplot(df.has_oilpalm)
对两类进行计数
训练数据集的分布情况
准备数据
所提供的测试数据位于两个独立的文件夹中,即排行榜预留数据(leaderboard holdout data)和排行榜测试数据(leaderboard test data)这两个文件夹。由于竞赛要求对两个数据集提交预测结果,所以我们将二者组合起来,这样一共得到6534张图片。
test_imgs = [i for i in test.iterdir()] hold_imgs = [i for i in lb_test.iterdir()] combined_test = test_imgs + hold_imgs len(combined_test)
将排行榜预留数据(leaderboard holdout data )与排行榜测试数据(leaderboard test data)组合起来
采用fast.ai的DataBlock API来构造数据,这是向模型输送数据集的一种简易的方法。
src = (ImageList.from_df(df, path, folder='train_images') .random_split_by_pct(0.2, seed=14) .label_from_df('has_oilpalm') .add_test(combined_test)) data = (src.transform(get_transforms(flip_vert=True), size=164) .databunch() .normalize(imagenet_stats))
创建一个ImageList来保存数据
此步骤要点:
- 利用ImageList的from_df方法来保存训练数据,这样做是因为将有关训练集的信息存储在名字为df的数据帧中,让它能找到训练图像所在的路径和保存图像的文件夹名称,train_images.
- 接下来,使用随机分割来对训练集进行分割,留出20%的数据来监控模型在训练过程中的性能。选择一颗种子,以确保再次检查时能得到同样的结果,我们必须知道什么在起作用,什么不起作用。
- 告诉ImageList在训练集中的数据的标签所在地,利用has_oilpalm方法将组合后的数据添加到测试数据中。
- 最后,对数据进行转换,使用flip_vert = True翻转图像有助于模型识别图像。利用imagenet_stats对图像归一化处理。注意:这是一种转移学习技术,我要说的是需要保持操作尽可能简单。
图像预览
不管有没有油棕种植园,卫星图像是这样的:
data.show_batch(2)
显示2批图像
有油棕的图像标记为1,无油棕的图像标记为0
训练模型
现在开始训练模型,采用卷积神经网络骨干,并使用预先训练的权重,这个权重从一个已经训练好的图像分类的resnet模型中直接获得,无须担心这种方法的细节。目前为止,我们正在构建一个以卫星图像为输入并输出这两种分类的预测概率模型。
learn = create_cnn(data, models.resnet50, metrics=[accuracy, error_rate], callback_fns=[ShowGraph, SaveModelCallback]) # View model architecture learn.model()
卷积神经网络
learn.lr_find() learn.recorder.plot()
找出最优的模型学习率
接下来,使用lr_find()找到理想的学习率,并利用recorder.plot().对它可视化。
找出最优的模型学习率
选择一个接近坡度最陡之处的学习速率,在这个示例中是1e-2。
learn.fit_one_cycle(5, slice(1e-2))
学习率为1e-2的5个循环的训练模型
利用fit_one_cycle函数对模型训练5个周期 (对所有数据训练5个周期)。
训练和验证损失
注意到显示出来的结果,如training_loss 和valid_loss没有?后续,会用它们来监控模型的改进。
在第四个循环,得到了最佳的模型。
训练模型的输出;训练和验证损失的过程
fast.ai在运行训练和验证数据集时,内部自动选取和保存最优的那个模型。
评估模型
竞赛组委会根据预测概率与观测目标has_oilpalm之间的工作特性曲线下的面积对参赛作品进行评价。通过以下开发者速成班、视频或Kaggle学习论坛的帖子,可以了解到更多关于AUC 的咨询。
开发者速成班: https://developers.google.com/machine-learning/crash-course/classification/roc-and-auc 视频: https://www.dataschool.io/roc-curves-and-auc-explained/ Kaggle学习论坛: https://www.kaggle.com/learn-forum/5378默认情况下,Fast.ai没有提供这个评价标准的指标度量,所以我们将用到Scikit-Learning库。
from sklearn.metrics import roc_auc_score def auc_score(y_score,y_true): return torch.tensor(roc_auc_score(y_true,y_score[:,1])) probs,val_labels = learn.get_preds(ds_type=DatasetType.Valid) print('Accuracy',accuracy(probs,val_labels)), print('Error Rate', error_rate(probs, val_labels)) print('AUC', auc_score(probs,val_labels))
打印出验证指标
使用预训练模型和fast.ai的优点是,可以得到一个非常好的预测精度,在这个示例中,在没有多做其他工作的情况下,获得了99.44%精确度。
训练第一阶段的指标
将模型存盘,绘制出预测的混淆矩阵。
learn.save('resnet50-stg1')
利用混淆矩阵查看结果
interp = ClassificationInterpretation.from_learner(learn) interp.plot_confusion_matrix(dpi=120)
绘制混淆矩阵
混淆矩阵是一种图形化的方法,用来查看模型准确或不准确预测的图像数量。
第一阶段训练的混淆矩阵
从这幅图中可以看出,模型准确地预测了2863幅没有油棕人工林的图像,对168幅油棕人工林的图像进行了正确的分类。将10幅含有油棕人工林的图像分类为无油棕人工林图像,并将7幅无油棕人工林图像分类为有油棕人工林图像。
对于一个简单的模型来说这个结果还不错。
接下来,找出这个训练迭代理想的学习率。
learn.lr_find() learn.recorder.plot()
找出理想的学习率
选择介于1e-6和1e-4之间的一个学习率
利用介于1e-6和1e-4之间的一个学习率最大值对模型进行拟合。
learn.fit_one_cycle(7, max_lr=slice(1e-6,1e-4))
学习率在1e-6和1e-4的范围范围内,对模型进行7次循环训练
训练和验证损失
在每个训练周期后,以图形的方式观察训练指标,从而监测模型的性能。
训练模型的输出;训练和验证损失的进度
保存第二阶段的模型训练结果。
learn.save('resnet50-stg2')
probs,val_labels = learn.get_preds(ds_type=DatasetType.Valid) print('Accuracy',accuracy(probs,val_labels)), print('Error Rate', error_rate(probs, val_labels)) print('AUC', auc_score(probs,val_labels))
准确度、误差率和AUC评分
打印出模型的精度、错误率和曲线下面的面积。
第二阶段训练指标
你会注意到,此时,模型的准确度从99.44%提高到99.48%,错误率从0.0056降低到0.0052,AUC也有改善,从99.82%提高到99.87%。
interp = ClassificationInterpretation.from_learner(learn) interp.plot_confusion_matrix(dpi=120)
绘制混淆矩阵
通过与我们绘制的上一个混淆矩阵的比较,可以发现模型做出了更精准的预测。
第二阶段训练的混淆矩阵
先前没有油棕种植园的7张图片被错误分类,现在降到了3张,性能有所提高。
你会注意到在训练过程中遵循了一个模式,在这个过程中调整了一些参数,这便是所谓的精调,绝大多数深度学习训练均遵循类似的迭代模式。
图像变换
我们将对数据执行更多的图像变换,通过这些变换对模型进行改进。
关于每种变换的详细描述,可以在fast.ai的相关文档中找到:https://docs.fast.ai/vision.transform.html
tfms = get_transforms(flip_vert=True, max_lighting=0.1, max_zoom=1.05, max_warp=0.) data = (src.transform(tfms, size=200) .databunch().normalize(imagenet_stats)) learn.data = data data.train_ds[0][0].shape
应用不同的变换改进模型
- hting:如果非空 None,则在概率p_lighting下使用由max_light控制的随机亮度和对比度变化。
- max_zoom:如果非1,或是一个比1更小的数,则在概率p_affine下使用max_zoom到1之间的随机缩放比
- max_warp:如果非空None,则使用概率p_affine应用-max_warp和max_warp之间的随机对称翘曲。
再次找出最优学习率:
learn.lr_find() learn.recorder.plot()
找出理想的学习率
选择学习率为1e-6
循环训练模型5次:
learn.fit_one_cycle(5, 1e-6)
循环训练模型5次
训练和验证损失
将训练指标与先前的指标进行比较,模型在0.0169与0.0163之间的迭代稍差,但是不要失望。
训练模型的输出结果,在第3次迭代时获得了最佳模型
将第三阶段的训练模型保存,并打印出指标。可以注意到,目前,模型的准确度是99.38%,在前一阶段是99.48%,AUC评分从99.87%提高到99.91%,达到了比赛评分的标准。
learn.save('resnet50-stg3')
probs,val_labels = learn.get_preds(ds_type=DatasetType.Valid) print('Accuracy',accuracy(probs,val_labels)), print('Error Rate', error_rate(probs, val_labels)) print('AUC', auc_score(probs,val_labels))
准确度、误差率和AUC评分
第三阶段训练指标
最终训练阶段
可以注意到,我们从最初图像大小size = 164的图像开始,然后逐渐递增,最终达到了 size = 256 。这样做是为了利用FAST.ai的累进图像大小进行分类,即在训练开始时使用小图像,并随着训练的进展逐渐增大大小。这样,即便模型早期训练时非常不准确,它也可以快速地看到大量图像并加快进度,在后续的训练中,可以看到更大的图像,从而了解更细粒度的区别。
若要阅读更多有关此信息,请访问此链接:https://www.fast.ai/2018/08/10/fastai-diu-imagenet/
tfms = get_transforms(flip_vert=True, max_lighting=0.1, max_zoom=1.05, max_warp=0.) data = (src.transform(tfms, size=256) .databunch().normalize(imagenet_stats)) learn.data = data data.train_ds[0][0].shape
应用不同的变换来改进模型
将图像大小增加到256
再次找出优化后的学习率:
learn.lr_find() learn.recorder.plot()
找出理想的学习率
找出理想的学习率
learn.fit_one_cycle(5, slice(1e-4))
学习率设置为1e-4,训练模型5次循环
训练和验证损失
来看一下训练指标,并与过去的指标进行比较,模型略有改进,从0.0169提高到0.0168。
训练模型的输出,在第2次循环时获得最优的模型
将最后阶段的模型训练结果保存,并打印出指标:
learn.save('resnet50-stg4')
probs,val_labels = learn.get_preds(ds_type=DatasetType.Valid) print('Accuracy',accuracy(probs,val_labels)), print('Error Rate', error_rate(probs, val_labels)) print('AUC', auc_score(probs,val_labels))
准确度、误差率和AUC评分
你会注意到,模型的准确度现在是99.44%,比上一阶段的99.38%有所提高:
第四阶段训练指标
准备竞赛提交文件
现在可以看到模型是如何对数据进行预测的:
p,t = learn.get_preds(ds_type=DatasetType.Test) p = to_np(p); p.shape ids = np.array([f.name for f in (combined_test)]); ids.shape sample_sub = Path('data/SampleSubmission.csv') df_sample = pd.read_csv(sample_sub) sub = pd.DataFrame(np.stack([ids, p[:,1]], axis=1), columns=df_sample.columns) sub.to_csv(path/'wids-notebook.csv', index=False)
准备CSV格式的提交文件
向WiDS数据马拉松提交文件
现在可以参加WiDS的竞赛,并提交参赛文件了,请转到此处(https://www.kaggle.com/c/widsdatathon2019)的竞赛页面,单击“加入竞赛”并接受竞赛规则,便可以提交参赛内容,如果你参加,看看你会排名第几。
我提交了自己的模型的预测之后
获得的私有和公开分数
免责声明:按照文章中的说明操作后,不会像我那样位列第三,为确保过程尽可能简单,请参看链接(https://www.kaggle.com/c/widsdatathon2019/discussion/82252)中Abdishakur的帖子。
原文标题:
How a team of deep learning newbies came 3rd place in a kaggle contest——Classifying images of oil palm plantations using fast.ai
原文链接:
https://towardsdatascience.com/how-a-team-of-deep-learning-newbies-came-3rd-place-in-a-kaggle-contest-644adcc143c8译者简介
陈之炎,北京交通大学通信与控制工程专业毕业,获得工学硕士学位,历任长城计算机软件与系统公司工程师,大唐微电子公司工程师,现任北京吾译超群科技有限公司技术支持。目前从事智能化翻译教学系统的运营和维护,在人工智能深度学习和自然语言处理(NLP)方面积累有一定的经验。业余时间喜爱翻译创作,翻译作品主要有:IEC-ISO 7816、伊拉克石油工程项目、新财税主义宣言等等,其中中译英作品“新财税主义宣言”在GLOBAL TIMES正式发表。能够利用业余时间加入到THU 数据派平台的翻译志愿者小组,希望能和大家一起交流分享,共同进步
— 完 —
关注清华-青岛数据科学研究院官方微信公众平台“THU数据派”及姊妹号“数据派THU”获取更多讲座福利及优质内容。