学习笔记 | Fast.ai深度学习实战课程Lesson2——带你深入了解CNN

Fast.ai 深度学习是我们此前推出的系列课程,共9节课,并且已经进行了汉化。课程主讲人是资深深度学习研究者Jeremy Howard 教授,他本人连续两年在 Kaggle 竞赛中获得第一名。本课程推出后深受深度学习爱好者和研究者的喜爱。

本文是学员胡智豪针对Lesson 2 课程的学习笔记,主要带我们深入了解CNN

建议在查看学习笔记前,先查看学习下Lesson 2的相关内容:

在线课程地址: https://mp.weixin.qq.com/cgi-bin/appmsg?t=media/appmsg_edit&action=edit&lang=zh_CN&token=1230025920&type=10&appmsgid=100001225&isMul=1

分享者 | 胡智豪

简述

在本节课,Jeremy一开始跟大家分享了有关如何问问题,如何得到帮助以及对上节课的内容进行了一些总结,随后对上节课的内容进行了深入的讲解。在lesson1的时候,相信有很多同学对于里面的一些算法、代码、笔记本的内容感到一头雾水,尤其是对于fine-tuning的理解,感觉更加模糊。其实Jeremy在第一课的时候要求大家只是纯粹在服务器上简单地运行一下笔记本里的代码,感受下每一行代码产生的结果,并对CNN的工作流程有一个感性的认识即可,所以在lesson1的时候Jeremy并没有讲太多细节的东西,在笔记本上的内容也解释不多。

任务流程

本节课编程任务流程可以很简单地归纳为:准备数据-微调训练模型-预测模型,其中包含一些分析的技巧等。

1.准备好data:

利用kaggle-CLI来下载猫狗大战的数据集,因为原始数据集并没有进行分门别类,所以Jeremy在dogs_cats_redux.ipynb上作了非常详细的操作,包括如何创建文件夹,如何复制图片等等,这一步就跟着笔记本走。注意:要严格按照笔记本上要求的目录,否则后面运行部分程序会出问题。

2.finetune and training:

finetune的作用在于,vgg模型在设计之初就被用于识别1000种物体,但我们的任务只要识别猫和狗两种物体,因此我们必须对模型进行修改,变成只输出两个结果的模型。这一步主要有两种方法:

1) 在完整的vgg模型下面,再加一层Dense层,输出两个结果;

2) 把vgg的最后一层调出来删除,改成一个只有两个输出的Dense层。

这两种方法在lesson2.ipynb里面都有详细的操作,大家可以参考看看这两者的不同之处。

3.生成预测结果,提交到kaggle:

我们经过上一步的微调及训练之后,可以用来预测我们的测试集了,预测测试集的方法可以有两种:

1) 利用dogs_cats_redux.ipynb中第85行的vgg.test()方法来对测试集进行预测;

2) 利用lesson2.ipynb中第168行的model.predict()方法来预测。

概念解释

由于课程是全英文,中国学生在学习理解英文内容的时候多多少少都会出现一些理解偏差,甚至一句话都要反复读好几遍才理解意思。这一部分为学习者进行形象的解释,不涉及太深的概念,在于以显浅易懂的语言来解释“这个东西大概是干嘛的”,有利于初学者入门。更详尽的解释可以直接看fastai上的notes。

1.模型的训练

我们在课程当中,常有一个幻觉是:“我已经训练完模型了。”但我们并不是真的“训练”了那个模型,vgg16模型是已经被训练好的了,我们所做的只是“微调”了某一些参数,然后用model.fit()来预测了一遍training set和valid set,然后对比一下这个prediction与原本的标签符合与否。记住,我们只是在“微调”,然后更新了部分参数,而不是从头训练了模型。

2.微调finetune

我们为什么需要微调?首先vgg16模型已经能很好地识别图像中的所有物体特征,这就是模型里面卷积层conv layers所做的事,我们不需要去碰这些层。但是vgg16输出的是1000个不同物体的概率,每次输出有1000个结果,我们在课程里只需要两个结果:猫和狗,所以要对这个模型进行小小的改动。常用的方法是,直接在模型后面加一个只有两个输出的Dense层,这是最简单粗暴了,但细想下来会觉得有点奇怪:在倒数第二层里,已经输出了4096个参数,最后一步直接把这4096个参数“生成”2个结果就好了,为什么要生成1000个结果之后再生成出2个结果?

所以就有了第二个方法,先用model.pop()来调出最后一层,把它删除掉,换成只有2个输出结果的Dense层就好了。

3.载入参数

在本节课中,经常会看见有load_array载入参数的操作,这里要详细说明一下流程,并不是一打开程序之后直接load_array就能把参数导入到模型当中去的,要遵循几个步骤:

1) import必要的库,如果连库都不存在,怎么调用里面的函数呢?

2) get_batches和get_data,要把准备用于预测或训练的数据导入;

3) 定义好必要的函数,notebook里面有一些临时定义的函数,如果不运行一下定义的函数,例如fit_model(),到后面就使用不了该函数;

4) 导入vgg模型,model = Vgg16(),告诉电脑这个模型是调用了vgg16的模型。

5) 修改模型部分结构。比如前一天保存了模型参数,其模型是最后一层改为输出数为2的Dense层,那么今天想要导入这些参数,就必须先把模型的最后一层也改成输出数为2的Dense层,否则出现参数错误。形象地理解为:一位身高1米9的顾客试了一件尺码合适的衣服,那就不能把这件衣服给一位身高1米7的顾客来试,否则显得太大不合适。

6) 最后就是导入参数。

代码解释

Fastai提供的lesson.ipynb有点类似于老师的教案或者详尽资料,因此在一些地方为了解释说明概念用了很大的篇幅,读者在阅读的时候可能有时会混乱到底哪些是实际需要运行的代码、哪些是老师为了说明概念所用的代码,所以在这里,笔者重新敲了一遍代码,挑选出一些最核心的内容,并用中文对每一个部分进行了解释,希望大家能感受到本节课想要传达的意义。

请使用jupyter notebook编辑代码,完整代码链接: http://geek.ai100.com.cn/wp-content/uploads/2017/04/fasta_ai_lesson2_jto.ipynb

%matplotlib inline from __future__ import division, print_function import os, json from glob import glob import numpy as np import scipy from sklearn.preprocessing import OneHotEncoder from sklearn.metrics import confusion_matrix np.set_printoptions(precision=4, linewidth=100) from matplotlib import pyplot as plt import utils; reload(utils) from utils import plots, get_batches, plot_confusion_matrix, get_data

from utils import * from numpy.random import random, permutation from scipy import misc, ndimage from scipy.ndimage.interpolation import zoom import keras from keras import backend as K from keras.utils.data_utils import get_file from keras.models import Sequential from keras.layers import Input from keras.layers.core import Dense, Flatten, Dropout, Lambda from keras.layers.convolutional import Convolution2D, MaxPooling2D, ZeroPadding2D from keras.optimizers import SGD, RMSprop from keras.preprocessing import image

目录准备

文件目录按照dogs_cats_redux.ipynb中所给出的目录,在本节课新增了一个model文件夹,您可以自行创建或者用以下代码创建:

utils/ vgg16.py utils.py lesson1/ redux.ipynb data/ redux/ train/ cat.437.jpg dog.9924.jpg cat.1029.jpg dog.4374.jpg test/ 231.jpg 325.jpg 1235.jpg 9923.jpg model/

%cd /home/ubuntu/lesson1/data/redux %mkdir model

path = '/home/ubuntu/lesson1/data/redux/' trn_path = path + 'train' val_path = path + 'valid' test_path = path + 'test' model_path = path + 'model'

batch_size = 64

创建vgg16模型

from vgg16 import Vgg16 vgg = Vgg16() model = vgg.model

先获取训练集、验证集、测试集的batches以及data,储存在文件中,以备后续调用。这里的batches和val_batches用的是vgg里面的函数,test_batches用的是keras中的函数,两者都能用。

batches = vgg.get_batches(path=trn_path, batch_size=batch_size, shuffle=False) val_batches = vgg.get_batches(path=val_path, batch_size=batch_size, shuffle=False)

test_batches = get_batches(test_path, batch_size=1, shuffle=False)

利用bcolz储存图片array

import bcolz def save_array(fname,arr): c = bcolz.carray(arr, rootdir=fname, mode='w'); c.flush() def load_array(fname): return bcolz.open(fname)[:]

从路径中获取图片的数据,并放在内存中,用于后续的预测。get_data这一步要慢慢载入数据到内存,会持续大概4分钟左右,请耐心等待。

trn_data = get_data(trn_path) val_data = get_data(val_path)

test_data = get_data(test_path)

保存常用数据是一个好习惯,以后就可以反复调用了。

save_array(model_path + 'train_data.bc', trn_data) save_array(model_path + 'valid_data.bc', val_data) save_array(model_path + 'test_data.bc', test_data)

保存完数据后,下一次使用时可以直接载入数据(但现在不用载入,因为data已经在内存里了,再load的话可能会造成内存不足)

trn_data = load_array(model_path + 'train_data.bc') val_data = load_array(model_path + 'valid_data.bc') test_data = load_array(model_path + 'test_data.bc')

对输出数据进行OneHotEncoder处理,这一步jeremy解释得很清楚,不懂的看看视频吧。

def onehot(x): return np.array(OneHotEncoder().fit_transform(x.reshape(-1, 1)).todense())

trn_classes = batches.classes val_classes = val_batches.classes

trn_labels = onehot(trn_classes) val_labels = onehot(val_classes)

预测模型

对模型进行预测,并把预测结果储存到文件中,方便下次调用。

trn_features = model.predict(trn_data, batch_size = batch_size) val_features = model.predict(val_data, batch_size = batch_size)

save_array(path + 'train_lastlayer_features.bc', trn_features) save_array(path + 'valid_lastlayer_features.bc', val_features)

trn_features = load_array(path + 'train_lastlayer_features.bc') val_features = load_array(path + 'valid_lastlayer_features.bc')

Fine tune 微调模型

微调fine-tuning,把最后一层替换成只有两个输出的Dense层。 切记:到了这一步之后就严禁运行上面的任何一行代码,只能继续往下运行。

model.pop() for layer in model.layers : layer.trainable=False

model.summary()

model.add(Dense(2, activation='softmax'))

图像增强 Image Data Augmentation

利用Image Data Generator来对训练集和验证集进行图像变换,以增强模型的泛化能力。对每一个batch的图片进行变换后,输出此batch(图片总数不变)。也就是相当于把所有图片都进行了旋转、缩放、变形等操作,生成一个新的图片集,并把此图片集输入到模型中训练,增强模型的识别能力。这个内容会在lesson3详细解释,同学们可以先不用深究。

gen = image.ImageDataGenerator() batches = gen.flow(trn_data, trn_labels, batch_size=batch_size, shuffle=True) val_batches = gen.flow(val_data, val_labels, batch_size= batch_size, shuffle=False)

现在使用fit_generator定义一个fit_model()函数,配合上面的ImageDataGenerator,可以得到“一边生成新图片集,一边把新图片集用于拟合模型”的效果。

def fit_model(model, batches, val_batches, nb_epoch=1): model.fit_generator(batches, samples_per_epoch=batches.N, nb_epoch=nb_epoch, validation_data=val_batches, nb_val_samples=val_batches.N)

设置优化器,这里用RMSprop,learning rate为0.1,然后编译一下模型,最后使用fit_model()来训练模型。

opt = RMSprop(lr=0.1) model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])

# 在这一步里,每一轮训练大概需要10分钟。 fit_model(model, batches, val_batches, nb_epoch=2)

到这一步就已经完成fine_tuning的效果,现在保存这个模型的所有参数,以备后期调用。

model.save_weights(model_path + 'finetune1.h5')

回调参数出来,看看评估如何。

model.load_weights(model_path + 'finetune1.h5)

model.evaluate(val_data, val_labels)

preds=model.predict_classes(val_data, batch_size=batch_size) probs=model.predict_proba(val_data, batch_size=batch_size)[:,0] probs[:8]

cm = confusion_matrix(val_classes, preds)

plot_confusion_matrix(cm, {'cat':0, 'dog':1})

训练更多的层

这里主要使用一个叫做Backprop(后向传播)的算法,把改动应用到所有相同的层当中,这里遵循的原则是“chain rule”。至于背后的数学原理,大家若是尊崇Jeremy的教学理念,可以暂时不去纠结背后的数学细节,一开始作为应用者,我们只需知道如何使用它,没必要去钻牛角尖浪费时间。等到自己的水平已经到一定程度了想开创一些算法的话,可以再慢慢学习。吴恩达的课程里有详细的数学推导,有兴趣的同学可以去看看。

注意:做到这一步之前,不要漏掉上面fine-tuning最后一层的步骤,不然的话会得到一个只有随机weights的层,可能会导致模型收敛缓慢或者难以收敛。

layers = model.layers # Get the index of the first dense layer... first_dense_idx = [index for index,layer in enumerate(layers) if type(layer) is Dense][0] # ...and set this and all subsequent layers to trainable for layer in layers[first_dense_idx:]: layer.trainable=True

因为我们对模型的架构没有作任何改动,所以我们不需要重新编译模型,只需要改动其中的学习率learning rate即可。

K.set_value(opt.lr, 0.01) fit_model(model, batches, val_batches, 3)

model.save_weights(model_path+'finetune2.h5')

Jeremy在这一步对最后的几层卷几层conv layers也进行了改动,原因是修改这些层估计能得到更大的提升(单纯修改dense层的学习率能够提升的空间已经不大),即使准确率下降了,也可以调用回之前的参数。试试无妨。

for layer in layers[12:]: layer.trainable=True K.set_value(opt.lr, 0.001)

fit_model(model, batches, val_batches, 2)

model.save_weights(model_path+'finetune3.h5')

model.load_weights(model_path+'finetune3.h5')

model.load_weights(model_path+'finetune2.h5') model.evaluate_generator(get_batches('valid', gen, False, batch_size*2), val_batches.N)

预测测试集,生成submission

对测试集进行预测,以下这一步大概持续10分钟:

t_probs=model.predict_proba(test_data, batch_size=batch_size)[:,1]

t_probs[:8]

results_path=path + 'results'

save_array(results_path + 't_preds.dat', t_preds) save_array(results_path + 't_probs.dat', t_probs)

isdog = t_probs

isdog = isdog.clip(min=0.05, max=0.95)

isdog[:8]

len(isdog)

filenames=load_array(results_path + '/filenames.dat') #Extract imageIds from the filenames in our test/unknown directory filenames = test_batches.filenames ids=np.array([int(f[8:f.find('.')]) for f in filenames])

subm = np.stack([ids,isdog], axis=1) subm[:5]

%cd /home/ubuntu/lesson1/ submission_file_name = 'submission3.csv' np.savetxt(submission_file_name, subm, fmt='%d,%.5f', header='id,label', comments='')

from IPython.display import FileLink %cd /home/ubuntu/lesson1/ FileLink(submission_file_name)

好了,以上就是本节课的全部内容,大家吸收核心内容之后就马上进入lesson 3吧!

每日荐文

点击图片查看更多精彩内容

Fast.ai 最实战深度学习在线课程 Lesson 3

深度学习(Deep Learning)是否已经让传统的机器学习无用了?

版权申明:该文章版权归AI100所有,如需转载、摘编、复制等,请后台留言征得同意。若有直接抄袭,AI100将追究其责任。

原文发布于微信公众号 - AI科技大本营(rgznai100)

原文发表时间:2017-05-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏iOSDevLog

Python机器学习:Scikit-Learn教程

一个易于理解的scikit-learn教程,可以帮助您开始使用Python机器学习。

63460
来自专栏数据科学与人工智能

【DS】Doc2Vec和Logistic回归的多类文本分类

Doc2vec是一个NLP工具,用于将文档表示为向量,是word2vec方法的推广。 为了理解doc2vec,最好理解word2vec方法。但是,完整的数学细节...

32940
来自专栏AI研习社

手把手教你用Python 和 Scikit-learn 实现垃圾邮件过滤

文本挖掘(Text Mining,从文字中获取信息)是一个比较宽泛的概念,这一技术在如今每天都有海量文本数据生成的时代越来越受到关注。目前,在机器学习模型的帮助...

43780
来自专栏专知

【干货】利用NLTK和Gensim进行主题建模(附完整代码)

51140
来自专栏机器之心

学界 | 斯坦福提出神经任务编程NTP:让机器人从层级任务中学习

选自arXiv 机器之心编译 参与:朱乾树、蒋思源 斯坦福视觉与学习实验室与加州大学提出神经任务编程(NTP),它可以将指定任务作为输入,并递归地将该任务分解成...

38090
来自专栏机器之心

学界 | 谷歌提出基于强化学习的优化配置方法:可让TensorFlow更充分利用CPU和GPU

选自arXiv 作者:Azalia Mirhoseini等 机器之心编译 参与:吴攀、李泽南 众所周知,深度学习是非常计算密集的,合理分配计算资源对于提升运算速...

418100
来自专栏数说工作室

【分类战车SVM】附录:用Python做SVM模型

分类战车SVM (附录:用Python做SVM模型) 回复“SVM”查看本《分类战车SVM》系列的内容: 第一话:开题话 第二话:线性分类 第三话:最大间隔分类...

45950
来自专栏AI科技大本营的专栏

AI 技术讲座精选:菜鸟学深度学习(二)

【AI100 导读】如何才能创建出自己的卷积神经网络呢?在本篇文章中我们会一起来探讨一下这个问题。我们将会继续处理在该系列第一部分谈到的图像分割问题。 ? 可...

39770
来自专栏机器之心

专栏 | Detectron精读系列之一:学习率的调节和踩坑

544120
来自专栏机器之心

业界 | OpenAI提出新型神经网络:自动计算词对象,实现实体消岐

31070

扫码关注云+社区

领取腾讯云代金券