在上一篇文章《使用数据增强技术提升模型泛化能力》中,我们针对训练数据不足的问题,提出采用数据增强(data augmentation)技术,提升模型的准确率。最终结果是:在17flowers数据集上,我们将准确率从60%多增加到70%,取得了不错的效果。然而,对于一个商业应用来说,70%多的准确率还是有些拿不出手。我们还有更好的手段吗?
在这篇文章中,我将介绍一种深度学习的利器:迁移学习(transfer learning),来帮助我们提高深度学习的准确率。
迁移学习
对于深度学习而言,迁移学习并不是一个高深的概念和技术。顾名思义,迁移学习就是把已经训练好的模型参数迁移到新的模型上,帮助新模型训练。这也近似于人类的学习过程,即所谓的举一反三。
在我之前写的一篇文章《TensorFlow Hub:探索机器学习组件化》中,我憧憬了未来的机器学习组件化的场景,这其中最核心的就是迁移学习,我们能够在其他人训练的模型的基础上,根据业务需求,训练满足特定需求的机器学习模型。
迁移学习领域有一篇公认的比较好的综述:A Survey on Transfer Learning,有兴趣可以找来看看。当然这篇论文是很早以前的(2013),里面没有介绍最新的研究。如果你对理论没啥兴趣,没有关系,不影响阅读后面的内容。
迁移学习是如何做到改善模型的呢?这要从特征提取说起。
特征提取
所谓特征,就是一事物异于其他事物的特点。比如,我们判断动物是否昆虫,有一个简单的原则:少于三对或多于三对足的动物都不是昆虫。再比如我们识别猫和狗,也一定是从某些特征入手,虽然有些时候我们并不能清晰的描述出特征。
在深度学习流行之前,人们通常手工提取特征,这通常面临着特征提取困难、效率低下等问题。到了深度学习阶段,我们通常采用端到端的训练方式,也就是由计算机自动识别特征( 为了提高效率,训练前,我们也可能会对数据进行预处理,比如归一化、图片缩放等等,但这和以前的特征提取并不是一回事)。
在计算机视觉领域,卷积神经网络是应用得最广泛的模型,了解卷积神经网络的同学可能知道,卷积运算实际上是进行图像特征的提取,到最后一层,才是进行分类(softmax、logistic),所以如果我们获得最后一层的输入,也就得到了提取的特征。
这个在keras中很容易做到,以VGG16为例:
代码语言:javascript复制model = VGG16(weights="imagenet", include_top=False)
以上代码构造VGG16模型,采用imagenet数据集训练出的权重,include_top参数决定是否包含最后的输出层,因为我们的目的是提取特征,所以该参数设为False。
下面的代码对数据集进行特征提取,并保存到hdf5格式的文件中,虽然我们无法形象化的看出到底提取到什么特征,但这个数据对下一步的迁移学习有用。
代码语言:javascript复制image_paths = list(paths.list_images(args["dataset"]))
random.shuffle(image_paths)labels = [p.split(os.path.sep)[-2] for p in image_paths]
le = LabelEncoder()
labels = le.fit_transform(labels)model = VGG16(weights="imagenet", include_top=False)dataset = HDF5DatasetWriter((len(image_paths), 512 * 7 * 7), args["output"], data_key="features", buf_size=args["buffer_size"])
dataset.store_class_labels(le.classes_)for i in np.arange(0, len(image_paths), bs):
batch_paths = image_paths[i : i bs]
batch_labels = labels[i : i bs]
batch_images = [] for (j, image_path) in enumerate(batch_paths):
image = load_img(image_path, target_size=(224, 224))
image = img_to_array(image) image = np.expand_dims(image, axis=0)
image = imagenet_utils.preprocess_input(image)
batch_images.append(image) batch_images = np.vstack(batch_images)
features = model.predict(batch_images, batch_size=bs)
features = features.reshape((features.shape[0], 512 * 7 * 7))
dataset.add(features, batch_labels)dataset.close()
迁移学习实例
在上一步的特征提取中,使用的权重数据是来自imagenet数据集训练出的,imagenet属于超大规模数据集,包含1500万张图片,对应2万多种类别。这样的数据集,和17flowers数据集的差别很大,那我们能否使用VGG16提取17flowers的有效特征,然后进行分类呢?
我们使用上一步骤提取的特征,然后应用简单的Logistic回归算法进行分类:
代码语言:javascript复制db = h5py.File(args["db"], "r")
i = int(db["labels"].shape[0] * 0.75)print("[INFO] tuning hyperparameters ...")
params = {"C": [0.1, 1.0, 10.0, 100.0, 1000.0, 10000.0]}
model = GridSearchCV(LogisticRegression(), params, cv=3, n_jobs=args["jobs"])
model.fit(db["features"][:i], db["labels"][:i])
print("[INFO] best hyperparameters: {}".format(model.best_params_))print("[INFO] evaluating ...")
preds = model.predict(db["features"][i:])
print(classification_report(db["labels"][i:], preds, target_names=db["label_names"]))print("[INFO] saving model ...")
f = open(args["model"], "w")
f.write(pickle.dumps(model.best_estimator_))
f.close()db.close()
结果如下:
有没有感觉到意外,准确率达到了不可思议的93%,要知道我们使用的VGG16模型是使用imagenet数据集训练出的权重,其类别和17flowers有天壤之别,但这种迁移学习效果就是这么明显。这也证明了,诸如VGG之类的网络能够进行迁移学习,将其判别特征编码为输出,我们可以使用它来训练我们自己的自定义图像分类器。
总结
通常,迁移学习应用于深度学习和计算机视觉时,有两种方法:
- 将网络视为特征提取器,将图像向前传播到给定层,将它们视为特征向量。
- 通过向网络头部添加一组全新的全连接层并调整这些FC层以识别新类(同时仍使用相同的基础CONV过滤器)来微调网络。
本文探讨的是第一种方法,我们将VGG、Inception、ResNet作为强大的特征提取器,可以让我们在数量有限的数据集也能训练出效果不错的模型。
以上实例均有完整的代码,点击阅读原文,跳转到我在github上建的示例代码。
另外,我在阅读《Deep Learning for Computer Vision with Python》这本书,在微信公众号后台回复“计算机视觉”关键字,可以免费下载这本书的电子版。