做者:LeanCloud 江宏html
前段時間 Trump 的這個採訪成爲社交媒體焦點的時候,我正好在複習一些 neural network 的材料,因而想到能夠用一些新的開源工具作一個識別 woman、man、camera、TV 的完整應用試試。這個例子足夠小,能夠在很短期完成,很適合用來講明如何作一個完整的深度學習應用。完成的應用部署在 https://trump-sim.jishuq.com (LeanCloud的一個雲引擎實例上)。前端
作這個應用分爲三步:先用一些圖片完成模型的訓練,而後把模型導出,作一個後端的 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
)來標註每一個圖片。最後一行代碼會顯示驗證集的前三個圖片。
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 是這個項目最簡單的一部分,只有一個 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 用戶。不久後咱們會公佈更多信息,並邀請一些用戶試用新產品。