Woman、man、camera、TV:如何作一个完整的深度学习应用

做者:LeanCloud 江宏html

前段时间 Trump 的这个采访成为社交媒体焦点的时候,我正好在复习一些 neural network 的材料,因而想到能够用一些新的开源工具作一个识别 woman、man、camera、TV 的完整应用试试。这个例子足够小,能够在很短期完成,很适合用来讲明如何作一个完整的深度学习应用。完成的应用部署在 https://trump-sim.jishuq.comLeanCloud的一个云引擎实例上)。前端

作这个应用分为三步:先用一些图片完成模型的训练,而后把模型导出,作一个后端的 API 用来识别图片,再作一个前端用来上传图片和显示结果。python

准备训练数据

Jupyter notebook 是个很流行的用来作数据分析和机器学习的交互式环境,它能够把 Markdown 文档和 Python 代码放在一个笔记本里,也能够以图表、图片等友好的方式显示代码的运行结果。这里也会用到 FastAI,它是一个基于 PyTorch,提供了不少网络和文件批量操做便捷接口的开源库。这篇文章就是在 Jupyter notebook 里写的,因此你能够直接 clone 这个 repo、安装依赖、启动 Jupyter notebook。git

git clone https://github.com/hjiang/trump-sim-notebook
pip install -r requirements.txt
jupyter notebook

咱们还会用到 Bing image search API 来获取作训练的图片,你须要本身注册并申请一个免费的 API KEY。固然,由于搜索到的图片是在不少第三方网站上的,因此你须要能无障碍地访问中国以外的网站。🤷‍♂️github

把你的 Bing image search API key 放在项目目录下的 .env 里,以避免在代码里泄露出去:web

BING_SEARCH_API_KEY=XXXXXXXX....

而后在 Python 里读进来json

import os
from dotenv import load_dotenv
load_dotenv()
key = os.getenv('BING_SEARCH_API_KEY')

写一个函数用来搜索图片:后端

from azure.cognitiveservices.search.imagesearch import ImageSearchClient
from msrest.authentication import CognitiveServicesCredentials
from fastcore.foundation import L

def search_images_bing(key, term, min_sz=128):
    client = ImageSearchClient('https://api.cognitive.microsoft.com', CognitiveServicesCredentials(key))
    return L(client.images.search(query=term, count=150, min_height=min_sz, min_width=min_sz).value)

实际验证一下, 搜一张 Artemis 的图片:api

from torchvision.datasets.utils import download_url
from PIL import Image
import fastai2.vision.widgets

results = search_images_bing(key, 'Artemis')
urls = results.attrgot('content_url')
download_url(urls[0], 'images/', 'artemis.jpg')
image = Image.open('images/artemis.jpg')
image.to_thumb(128, 128)

确认图片下载没问题后,咱们把关心的四类图片下载到 /objects 下面的四个目录里。浏览器

from fastai2.vision.utils import download_images
from pathlib import Path

object_types = 'woman','man','camera', 'TV'
path = Path('objects')

if not path.exists():
    path.mkdir()
    for o in object_types:
        dest = (path/o)
        dest.mkdir(exist_ok=True)
        results = search_images_bing(key, o)
        download_images(dest, urls=results.attrgot('content_url'))

你可能会看到一些图片下载失败的信息,只要不是太多均可以忽略。网络上有的图片是损坏的,或者是 Python image library 不支持的格式,须要把它们删除。

from fastai2.vision.utils import get_image_files
from fastai2.vision.utils import verify_images

fns = get_image_files(path)
failed = verify_images(fns)
failed.map(Path.unlink);

预处理

在开始训练前,须要告诉 FastAI 如何标注图片,并加载到它的数据结构中。下面的代码完成如下几件事:

  • 使用父目录名(parent_label)来标注每一个图片。
  • 保留 20% 的图片做为验证集(validation set),其它的做为训练集(training set)。训练集就是用来训练神经网络的数据,验证集用于衡量训练好的模型在遇到新数据时的准确度。这两个集合不能有重叠。
  • 把图片缩小以提升效率

最后一行代码会显示验证集的前三个图片。

from fastai2.data.block import DataBlock, CategoryBlock
from fastai2.vision.data import ImageBlock
from fastai2.data.transforms import RandomSplitter, parent_label
from fastai2.vision.augment import Resize

objects = DataBlock(
    blocks=(ImageBlock, CategoryBlock),
    get_items=get_image_files,
    splitter=RandomSplitter(valid_pct=0.2, seed=42),
    get_y=parent_label,
    item_tfms=Resize(128))

dls = objects.dataloaders(path)
dls.valid.show_batch(max_n=3, nrows=1)

在作图像识别的时候每每还会对图片作一些随机的缩放、裁剪等变换,以便产生足够多的数据来提升训练效果。能够从下面代码的结果看到对同一个图片作不一样变换的结果。

from fastai2.vision.augment import aug_transforms, RandomResizedCrop

objects = objects.new(
    item_tfms=RandomResizedCrop(224, min_scale=0.5),
    batch_tfms=aug_transforms())
dls = objects.dataloaders(path)
dls.train.show_batch(max_n=6, nrows=2, unique=True)

训练数据

接下来终于能够开始训练了。对于图像识别这样的应用场景来讲,每每不会从零开始训练一个新的模型,由于有大量的特征是几乎全部应用都须要识别的,好比物体的边缘、阴影、不一样颜色造成的模式等。一般的作法是以一个预先训练好的模型为基础(好比这里的 resnet18),用本身的新数据对最后几层进行训练(术语为 fine tune)。在一个多层的神经网络里,越靠前(靠近输入)的层负责识别的特征越具体,而越靠后的层识别的特征越抽象、越接近目的。下面的最后一行代码指训练 4 轮(epoch)。

若是你有 Nvidia 的显卡,在 Linux 下,而且安装了合适的驱动程序的话,下面的代码只须要几秒到十几秒,不然的话就要等待几分钟了。

from fastai2.vision.learner import cnn_learner
from torchvision.models.resnet import resnet18
from fastai2.metrics import error_rate
import fastai2.vision.all as fa_vision

learner = cnn_learner(dls, resnet18, metrics=error_rate)
learner.fine_tune(4)
epoch train_loss valid_loss error_rate time
0 1.928001 0.602853 0.163793 01:16
epoch train_loss valid_loss error_rate time
0 0.550757 0.411835 0.120690 01:42
1 0.463925 0.363945 0.103448 01:46
2 0.372551 0.336122 0.094828 01:44
3 0.314597 0.321349 0.094828 01:44

最后输出的表格里是每一轮里训练集的 loss,验证集的 loss,以及错误率(error rate)。错误率是咱们关心的指标,而 loss 是控制训练过程的指标(训练的目标就是让 loss 愈来愈接近于 0)。须要这两个不一样的指标是由于 loss 要知足一些错误率不必定知足的条件,好比对全部参数可导,而错误率不是一个连续函数。loss 越低错误率也越低,但他们之间没有线性关系。这里错误率有差很少 10%,也就是准确率是 90% 左右。

接下来咱们要看看验证集里到底有哪些图片识别错了,下面的代码会打印出 confusion matrix。在这个矩阵里,对角线的数字是正确识别的图片数,其它地方的是识别错误的图片数。

from fastai2.interpret import ClassificationInterpretation

interp = ClassificationInterpretation.from_learner(learner)
interp.plot_confusion_matrix()

从输出的矩阵能够看到一共有 11 个错误,其中男女性别错误有 4 个,此外电视和其它几类的混淆也不少。🤔

下面咱们把 loss 最高的图片显示出来看看具体有什么问题。

interp.plot_top_losses(12, nrows=4)

输出的结果反映出了从互联网上抓来的数据存在的典型问题:噪声太多。好比电视的搜索结果里有电视遥控器、电视盒子、电视剧海报,还有一些是彻底无关的结果。

FastAI 提供了一个 cleaner 能够帮助咱们对比较小的数据集作手动清洗。它能够把整个数据集中 loss 最高的图片列出来让用户能够手动修改标签或者删除。

from fastai2.vision.widgets import ImageClassifierCleaner

cleaner = ImageClassifierCleaner(learner)
cleaner

注意 cleaner 只是作标记,你须要用 Python 代码来作实际处理。我一般就直接把有问题的图片标记为 delete 而后删除。

for idx in cleaner.delete(): cleaner.fns[idx].unlink()

清理完以后重复训练的过程。

objects = DataBlock(
    blocks=(ImageBlock, CategoryBlock),
    get_items=get_image_files,
    splitter=RandomSplitter(valid_pct=0.2, seed=42),
    get_y=parent_label,
    item_tfms=Resize(128))

objects = objects.new(
    item_tfms=RandomResizedCrop(224, min_scale=0.5),
    batch_tfms=aug_transforms())
dls = objects.dataloaders(path)
learner = cnn_learner(dls, resnet18, metrics=error_rate)
learner.fine_tune(3)
epoch train_loss valid_loss error_rate time
0 1.663555 0.510397 0.201835 01:11
epoch train_loss valid_loss error_rate time
0 0.458212 0.226866 0.091743 01:32
1 0.358364 0.145286 0.036697 01:31
2 0.281517 0.146477 0.036697 01:32

若是你注意到 error_rate 在后面的 epoch 有上升的话,能够下降 fine_tune 的参数以达到最好的效果。由于若是训练轮数过多,模型会对训练集 over fit,在遇到新数据时错误率会变高。从上面的输出能够看到准确率提升到了 96% 以上。

达到满意的准确率后就能够把模型导出用到线上了。下面这行代码会把模型保存到 export.pkl

learner.export()

后端 API

后端 API 是这个项目最简单的一部分,只有一个 endpoint。加载前面导出的模型,收到新图片时用模型来预测分类就能够。

trump = load_learner('model.pkl')

@app.route('/api/1.0/classify-image', methods=['POST'])
def classify():
    image = request.files['image']
    res = trump.predict(image.read())
    response = jsonify({'result': res[0]})
    response.status_code = 200
    return response

完整的代码在 GitHub 上。按照文档部署到 LeanCloud 云引擎就行。

前端网站

前端也比较简单,只须要一个页面让用户上传照片,在浏览器里把照片缩小而后发送给后端 API 就能够。完整的 React 项目在 GitHub,主要的代码在 App.js。限于篇幅就不详细说明了,只附上一张运行的截图:

给读者的做业

你可能已经注意到上面的后端 API 服务是无状态的,没有存储任何数据,因此其实识别的过程能够在前端完成。若是你有兴趣的话,能够调研一下如何把 PyTorch 模型转化为 JavaScript 可用的模型,尝试在浏览器里直接识别照片。在真实的应用中,这样的方式因为不须要向服务端传输任何数据,能够完美地保护用户隐私,这也是 Apple 在推进的 on-device machine learning 的方向。

图片识别是机器学习能够解决的最简单的一类问题,由于有不少现成的结果能够重用,新的应用即便只有少许训练数据也能达到比较好的效果。还有不少其它类型的问题没有那么容易获得让人满意的结果。LeanCloud 目前正在开发机器学习方面的新产品,以帮助开发者更容易地发掘数据的价值。你若是对此感兴趣,能够关注咱们的微博、微信公众号、Twitter,或者注册成为 LeanCloud 用户。不久后咱们会公布更多信息,并邀请一些用户试用新产品。

题图 Charles Deluvio

相关文章
相关标签/搜索