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

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

前几天接到一个任务,需要从我们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曲线寻找一个相对理想的值。

小结

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

扩展阅读

What a May Day

周四晚上跟生日的父母吃过晚饭后,傻乎乎的带着小梦梦在孩子王逛,一点没有意识到已经是五月的最后一天。有时候,记录的习惯更多的就是提醒自己,时间的昼夜不舍。

这个月有意识的接触了挺多人,对于鄙人这种社交贫瘠的人来说,这个月花在这方面的时间算是奢侈的了。有老友也有新朋友。一次跟新朋友印象深刻的夜谈。已经很久没有跟新认识的人如此没有负担的沟通和交流了。对了,上一次有意思的聊天也是去年的这个时候。初夏,真是一个神奇的时节,一切都开始要变得明亮而耀眼。

五月在做和要做的事情越来越多,一种此情无计可消除,才下眉头,却上心头之感。一部分算是甜蜜的负担,而立之年,一些事情逐渐跟自己是否准备好已经没有必然关系,而是直接去解决它就对了。一部分是多过去时光的辜负,亡羊补牢,希望犹未晚矣。前天看韩老师的5X兴趣社区,看到李伟龙一个视频的幕后花絮,对话挺走心的:时间只会让你老去,其他什么都不会带来;只有你想改变的时候,你才能改变。

神奇的五月,居然达成了跑渣的第一次5公里(一个都不好意思提的配速)。从去年4月份参加跑团开始跑步,到现在已经一年多了,跑步成绩上没有任何提升,也是我预料之中的。对于这件事,我其实想得很明白:我一点都不喜欢运动,但是要支撑我的情怀和要做的事情,我必须要有这个练习和准备。显然,如果保持当前的做事的节奏,也许一周一次的跑步很快就无法支撑自己在做的事情,但是只要保持这件事情的惯性,我相信这股力量不会让自己失望。

这个月最喜欢的书是吴军老师的《智能时代》。因为一直在订阅吴军老师在得到的专栏,因此书中的很多内容其实都在专栏中听过了(如此说来,维护专栏及时高产如母猪,也是需要有存货当备份的?)。有两点体会最深:

  1. 人类文明发展是一个不短加速的过程,每一次加速都会让已有产业与新结束结合形成形成新的产业,赶上这个浪潮的会以数量级的优势领先,赶不上或者不愿拥抱变革的则会被无情的淘汰。
  2. 大数据和AI是当前最有可能成为下一个时代的蒸汽机和电。超越时代是困难的,但是从思维方式上则是可以刻意练习大数据和AI思维的。对于程序员而言,这尤为重要——有很大可能性,这决定了当前的你是成为为工业时代的码农,还是智能时代的工程师。

六月会迎来自己在两个月前设定的一个deadline, 从目前看来,不容乐观。可能当时在设定这个目标的时候,其实内心的真实独白就已经是法乎其中则得其下,法乎其上则得其中。但是,总的来说,过去的两个月无论是还在发生还是已经发生的事,多少带来了一丝丝改变。

期待六月,不负好时光。

热眼旁观:老罗与锤子

第一次对老罗的全面了解是本科时候的那趟南戴河毕业旅行:出发的时候随便抓了一本书塞书包里——《我的奋斗》。一群毕业屌丝自然坐的是最便宜,但如今想来却最有意思的绿皮火车去的南戴河。在路上和车站上看完了这本书。

与其说这是一本书,不如说这是老罗在新东方几年上课和演讲的语录。即使按照当时的认知,也不会把它归为推荐一类的书。但这并不重要,重要的是,对这个彪悍且还在继续活跃的胖子有了一个相对完整的了解。如今依然不知道那本书是从哪里来,后来遗失到了哪里去。很多事情,奇妙的偶然性让我知道了这个世界上有个有趣并值得持续关注的灵魂。

一共参加过两次老罗的现场演讲。

第一次是研究生一年级,老罗来学校做一个理想主义者的创业故事II的演讲。那时候老罗还没有开始做手机,演讲中用来插科打诨的公司主体是他的英语培训机构。那时候的自己对商业也没有清晰认识和完整理解。只是自己当时也在参与创业,对「理想主义」和「创业者」这两个词是没有任何抵抗力的。仿佛找到了一起参与一场改变世界革命的战友,惺惺相惜,感同身受。当然,也可见当时万众创业对在校学生荼毒之深?。

那次分享的很多故事依然记忆犹新,但是要说最深刻的应该是:学会站着赚钱。理所当然的道理遇到浮躁的年代,往往知难行易。感谢老罗的那次演讲,深刻地影响了自己后来创业、工作时候处理棘手事情的底限和原则。

第二次是锤子科技在成都举行的 2017 秋季新品发布会。刚得知消息的时候,还是非常意外的,因为的确没有想到在从帝都回到成都工作的第四个年头,能够在成都听一场老罗的现场相声。

也许近两年成都的热度太高了吧,偌大的成都大魔方演艺厅票务放出即售罄。最后还是因为前东家是锤子科技的股东,因此老同事给置办了一张内部票才得以入场,也算是听相声路上的一段小插曲。那次的发布会发布了两个产品,一个是坚果 Pro 2——目的非常明确,坚果 Pro 是锤子迄今为止最畅销的手机,Pro 2 意图百尺竿头,更进一步; 一个是与成都雾霾非常应景的空气净化器——畅呼吸,算是在产品品类上横向扩充的尝试。

两个产品都很棒,但是即使凭借门票有优惠和优先购买权、手上的 iPhone 6 Plus 廉颇老矣的情况下,自己也没有购买这次发布会上的任何一款产品。很多人说,锤子的产品历来都是叫好不叫座,也许是有原因的?

回到几天前的鸟巢发布会。有人说坚果 R1 中规中矩,毫无亮点;与之结合的 TNT 工作站就是一个笑话。我不想参与这个结论的辩论,一来自己还没有体验过这两个产品,讨论结论为时过早,毫无意义;二来我想我可能是一群人的典型代表:关注老罗和锤子,只因为这是一家老罗牵头的公司。

R1 是创新吗?当然不是。在手机如此同质化的今天,无论是小厂锤子科技还是巨厂苹果,这几年出的产品整体上都达无法满足大家的心理预期。R1 不过是众多改进型产品中的一员。至于老罗说的全世界第一,哎哟喂,都是成年人了,大家平时都在吹牛X,你不能自己吹完不让别人吹不是?

TNT 是创新吗?当然是。我会购买吗?现在当然不会。

Touch and Talk 的交互方式会是次时代的主流人机沟通方式吗?我不确定,这里没有和稀泥的意思。这个问题可以反过来看:如今嘴里含着触屏手机出生的一代,他们对于 touch 的交互几乎是与生俱来的一种操作直觉,如今的键鼠操作一定不是这一代人所有场景下的最终交互归宿,在很多场景下,一定会有新的交互方式来代替当前的这种操作交互。那么它会是 Touch and Talk 吗?这个真的不知道。超越时代是困难的,而在超越时代的产品真正出现的时候,在大众眼中,这种超越往往又是荒诞的。

我知道很多程序员都会调侃说 TNT 写代码可能会是这样的:

关于这一点,我想说,也许应用在现实的生产工具场景中,也许它……真的就是这样的。但是,这其实杠精了。回想一下老罗介绍的几个 TNT 应用场景:表格数据自动处理、keynote 语音辅助编辑、闪念胶囊自动找图和幻灯片生成…… 没有一个是要取代如今的密集生产工具场景。虽然现场 demo 状况频出,老罗也是汗如雨下,但是,我想你不会否认这么几点:老罗的敏感使其非常善于发现问题,他提到的问题和场景你一定不会陌生,他给出的解决方案也许不是你会选择的方法,但是一定是解决了特定用户群的痛点。

有人说,TNT 工作站这种手机 + Dock 的方式根本不是老罗首创,因为好几年前的摩托罗拉和当前的三星都有对应的产品。单从产品形态来说,TNT 的确没有重新发明轮子。甚至加上 Touch 和语音,整个解决方案也必然不是业界首创。

在这一点上,我比较同意吴军老师的第三眼美女以及发明是以最后一个发明家的名字命名的理论。老罗和他的 TNT 会是这个垂直品类的最后一个发明者以及第三眼美女吗?说实话,我并不确定,但是我愿意将一半的可能性投给老罗。

因为,对于老罗,我想我可能是关注老罗若干群体中的某个群体的典型代表:每次老罗的相声都不会错过,但是却几乎未曾真正触动自己的钱包;表面是老罗的粉丝,但是却自诩为独立思考,不是任何人的拥趸;关注老罗,更多的是他所言所行符合了某一群理想主义者的价值观,视其位精神的知己,行动的马前卒;将老罗及其挂帅的锤子科技看做浮躁创业环境下的一股清流,并由衷乐见其成;经常感怀自己,如果老罗失败了,是这个社会对价值的多元筛选和宽容失败了。

TNT 最终会怎样 boom 我们交给时间评判。但是,老罗一直都是那个埋头做事的人,同时更是平地吹B三丈起的奇才。我依然会是一个旁观者,如同观看一场带有完全主观倾向的比赛,向他投与这场比赛所有的希冀和祝福。

:吴军老师第三眼美女理论

第三眼美女当然是相对第一眼美女和第二眼的美女。第一眼美女有什么特点呢?首先,一眼看上去就很漂亮,但是不属于大众。这里面有很多原因,或许是因为她们本身就认为自己是精英人群而非主流人群,抑或是这些人光芒太扎眼,一般人想接近也接近不了她们。总之结果是,大众只能在远距离去欣赏她们了。其次,人有时会看走眼,乍一看很漂亮,接近以后如果发现没有内涵,看到第二眼、第三眼时,未必还能有最初的好印象了。

第二眼美女未必有第一眼的那么天生丽质,因此她们常常需要更懂得时尚细节才能引来周围人欣赏的眼光,但是这样一来,和一个第二眼美女交往的成本就比较高,大众即使心里痒痒的,未必能得到。即使得到了,第二眼美女的脾气未必好,因此双方的蜜月期一过,可能也就形同陌路了。

第三眼美女是属于大众的,她们未必那么显眼,但是如果仔细观察,她们还是不错的。更重要的是,正是因为她们可能没有光鲜的外表,如果依然能够吸引人,那么必定有某种美德或者价值。而对于欣赏这种美德,或者看重这种价值的人来讲,他们对第三眼美女的喜欢会持续很久,除非这种美德和价值不再存在或者过时了。