使用迁移学习快速训练识别特定风格的图片

使用迁移学习快速训练识别特定风格的图片

前几天接到一个任务,需要从我们app的feed流中的筛选一些「优质」图片,作为运营同学的精选feed候选池。这里「优质」的参考就是以前运营同学手工筛序的精选feed图片。问题并不难,最容易想到的方向有两个:

  1. 机器学习方向,训练一个能够识别这种「优质」风格图片的模型。
  2. 过滤推荐方向,利用用户来测试feed图片质量(根据点赞、评论、观看张数、停留时间等指标),使用用户来筛选优质feed图片(用户的偏好千奇百怪,筛选结果可能未必如你所想,典型如今日头条……)。

今天我们介绍如何使用机器学习解决这个问题。具体来讲,由于时间紧,任务重,我们决定使用迁移学习来完成这个任务。后面如果有时间,我们也会尝试一下使用用户来过滤和筛选优质图片。

什么是迁移学习

迁移学习 (Transfer learning) 顾名思义就是就是把已学训练好的模型参数迁移到新的模型来帮助新模型训练。考虑到大部分数据或任务是存在相关性的,所以通过迁移学习我们可以将已经学到的模型参数(也可理解为模型学到的知识)通过某种方式来分享给新模型从而加快并优化模型的学习效率不用像大多数网络那样从零学习。

为什么使用迁移学习

  • 很多时候,你可能并没有足够大的数据集来训练模型,更不用说带有高质量标签的数据集了。使用已经训练好的网络,可以降低用于训练的数据集大小要求。
  • 从零开始训练一个深度网络是非常消耗算力和时间的。如果再将模型调整、超参数调整等有点玄学的流程加进去,消耗的时间会更多。对于创业公司来说,很多时候是很难给出这么多的时间预算来解决一个模型问题的。
  • 基于迁移学习训练一个模型往往只需要训练有限的几层网络,或者使用已有网络作为特征生成器,使用常规机器学习方法(如svm)来训练分类器。整体训练时间大幅降低。效果可能不是最好的,但是往往能够在短时间内帮你训练出一个够用的模型,解决当前的实际问题。

也就是说,近几年深度学习的各种突破本质上还是建立在数据集的完善和算力的提升。算法方面的提升带来的突破其实不如前两者明显。如果你是一个开发者,具体到要使用机器学习解决特定问题的时候,你一定想清楚你能否搞定数据集和算力的问题,如果不能,不妨尝试一下迁移学习。

如何进行迁移学习

我们的任务是筛选优质feed图片,其实就是一个优质图片与普通图片的二分类问题。

运营给出的「优质」参考图片:

直观感受是,健身摆拍图、美食图和少量风光照是她们眼中的优质图片?

运营给出的「普通」参考图片:

直观感受是,屏幕截图和没什么特点的图片被认为是普通图片。

我们迁移学习的过程就是复用训练好的(部分)网络和权重,然后构建我们自己的模型进行训练:

迁移学习在选择预训练网络时有一点需要注意:预训练网络与当前任务差距不大,否则迁移学习的效果会很差。这里根据我们的任务类型,我们选择了深度残差网络 ResNet50, 权重选择imagenet数据集。选择 RetNet 的主要原因是之前我们训练的图片鉴黄模型是参考雅虎开源的 open NSFW , 而这个模型使用的就是残差网络,模型效果让我们影响深刻。完整代码如下(keras + tensorflow):

  • 这里我们仅重新训练了输出层,你也可以根据自己需要添加多个自定义层。
  • 整个训练过程非常快,在Macbook late 2013仅使用CPU训练的情况下,不到一个小时收敛到了82%的准确率。考虑到我们的「优质」图片标签质量不太高的实际情况,这个准确率是可以接受的。
  • 完成训练后,我们使用该模型对生产环境的2000张实时图片进行了筛选,得到85张图片,运营主观打分结果是~50%可用,~25%需要结合多图考虑,其他不符合要求。考虑到我们的任务是辅助他们高效发现和筛选潜在优质图片,这个结果他们还是认可的。部分筛选结果如下:

还可以更简单一点吗?

如果你觉得上面重新训练网络还是太慢、太繁琐,我们还有更简单的迁移学习的方法:将预训练网络作为特征提取器,然后使用机器学习方法来训练分类器。以SVM为例,完成迁移学习只需要两个步骤:

  • 将预训练网络最后一层输出作为特征提取出来:
resnet_model = None

def extract_resnet(x):
  '''
  :param x: images numpy array 
  :return: features
  '''
  global resnet_model
  if resnet_model is None:
    resnet_model = ResNet50(include_top=False,
                            weights='imagenet',
                            input_shape=(image_h, image_w, 3))
  features_array = resnet_model.predict(x)
  return np.squeeze(features_array)

  • 使用特征训练SVM分类器:
def train(positive_feature_file, negative_feature_file):

  p_x = np.load(positive_feature_file)
  n_x = np.load(negative_feature_file)

  p_y = np.ones((len(p_x),), dtype=int)
  n_y = np.ones((len(n_x),), dtype=int) * -1

  x = np.concatenate((p_x, n_x), axis=0)
  y = np.concatenate((p_y, n_y), axis=0)

  x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
  logging.info("train shape:%s", x_train.shape)


  pca = PCA(n_components=512, whiten=True)
  pca = pca.fit(x)
  x_train = pca.transform(x_train)
  x_test = pca.transform(x_test)

  logging.info("train shape:%s", x_train.shape)


  # train
  svm_clf = svm.SVC(kernel='rbf', probability=True, decision_function_shape='ovr')
  svm_clf.set_params(C=0.4)
  svm_clf.fit(x_train, y_train)
  preds = svm_clf.predict(x_train)
  logging.info('train preds %d items, train accuracy:%.04f', len(preds), accuracy_score(preds, y_train))

  preds = svm_clf.predict(x_test)
  logging.info('test preds %d items, test accuracy:%.04f', len(preds), accuracy_score(preds, y_test))

  # joblib.dump(ss, './normal-ss.pkl')
  joblib.dump(pca, './normal-pca.pkl')
  joblib.dump(svm_clf, './normal-clf.pkl')

这个方法之所以有效是因为,迁移学习要求预训练网络与当前任务是相似的,那么最后一层网络的输出可以解释为特征的高度抽象,因此可以使用其作为特征进行分类。

这个方法虽然有效,但是需要准备两个数据集:正样本和负样本。很多时候,我们的任务是识别出我们关心的类别,这个类别我们可以花时间和精力来进行数据集的标注,但是对于我们不关心的类别的数据往往是不易收集的。那么,我们可以只准备一个数据集来训练一个只识别我们关心类别的模型吗?答案是可以的,使用One-class classification即可,一般翻译为异常检测或离群点检测。

如果你熟悉sklearn, 你可以使用svm.OneClassSVM:

  oc_svm_clf = svm.OneClassSVM(gamma=0.011, kernel='rbf', nu=0.08)
  oc_svm_clf.fit(x_train)

  preds = oc_svm_clf.predict(x_train)
  expects = np.ones((len(preds)), dtype=int)
  logging.info('train preds %d items, train accuracy:%.04f', len(preds), accuracy_score(preds, expects))

需要注意的是,One-class classification是一种无监督学习,从实验效果看,使用该方法筛选出来的图片「稳定性」相比前面两个方法稳定性要差。如果要在实际业务中使用该方法,需要仔细调整gamma参数,根据ROC曲线寻找一个相对理想的值。

小结

大多数场景下,受限于数据集、算力和时间限制,很少人是从零开始训练一个深度神经网络的。如果你的任务是解决工程中的某个特定问题,那么迁移学习可能是一个有效的高性价比解决方案。你可以使用通过添加或移除若干预训练网络层来实现迁移学习,也可以将预训练网络作为特征提取器,然后使用其他分类方法进行机器学习。迁移学习的效果往往不如完全训练整个网络的效果好,因此,你需要结合具体任务来权衡准确率和成本。

扩展阅读