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

相關文章
相關標籤/搜索