作者:LeanCloud 江宏
前段时间 Trump 的这个采访成为社交媒体焦点的时候,我正好在温习一些 neural network 的资料,于是想到能够用一些新的开源工具做一个辨认 woman、man、camera、TV 的残缺利用试试。这个例子足够小,能够在很短时间实现,很适宜用来阐明如何做一个残缺的深度学习利用。实现的利用部署在 https://trump-sim.jishuq.com(LeanCloud 的一个云引擎实例上)。
做这个利用分为三步:先用一些图片实现模型的训练,而后把模型导出,做一个后端的 API 用来辨认图片,再做一个前端用来上传图片和显示后果。
筹备训练数据
Jupyter notebook 是个很风行的用来做数据分析和机器学习的交互式环境,它能够把 Markdown 文档和 Python 代码放在一个笔记本里,也能够以图表、图片等敌对的形式显示代码的运行后果。这里也会用到 FastAI,它是一个基于 PyTorch,提供了很多网络和文件批量操作便捷接口的开源库。这篇文章就是在 Jupyter notebook 里写的,所以你能够间接 clone 这个 repo、装置依赖、启动 Jupyter notebook。
git clone https://github.com/hjiang/trump-sim-notebook
pip install -r requirements.txt
jupyter notebook
咱们还会用到 Bing image search API 来获取做训练的图片,你须要本人注册并申请一个收费的 API KEY。当然,因为搜寻到的图片是在很多第三方网站上的,所以你须要能无障碍地拜访中国之外的网站。????♂️
把你的 Bing image search API key 放在我的项目目录下的 .env
里,免得在代码里泄露进来:
BING_SEARCH_API_KEY=XXXXXXXX....
而后在 Python 里读进来
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 的图片:
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