1. 程式人生 > 實用技巧 >20 行程式碼:Serverless 架構下用 Python 輕鬆搞定影象分類和預測

20 行程式碼:Serverless 架構下用 Python 輕鬆搞定影象分類和預測

簡介:本文將會通過一個有趣的 Python 庫,快速將影象分類的功能搭建在雲函式上,並且和 API 閘道器結合,對外提供 API 功能,實現一個 Serverless 架構的“影象分類 API”。

前言

影象分類是人工智慧領域的一個熱門話題。通俗解釋就是,根據各自在影象資訊中所反映的不同特徵,把不同類別的目標區分開來的影象處理方法。

它利用計算機對影象進行定量分析,把影象或影象中的每個像元或區域劃歸為若干個類別中的某一種,以代替人的視覺判讀。

影象分類在實際生產生活中也是經常遇到的,而且針對不同領域或者需求有著很強的針對性。例如通過拍攝花朵識別花朵資訊、通過人臉比對人物資訊等。

通常情況下,這些影象識別或者分類的工具,都是在客戶端進行資料採集,在服務端進行運算獲得結果,也就是說一般情況下都是有專門的 API 實現影象識別的。例如各大雲廠商都會為我們有償提供類似的能力:

阿里雲圖像識別頁面:

華為雲圖像識別頁面:

本文將會通過一個有趣的 Python 庫,快速將影象分類的功能搭建在雲函式上,並且和 API 閘道器結合,對外提供 API 功能,實現一個 Serverless 架構的“影象分類 API”

首先和大家介紹一下需要的依賴庫:ImageAI。通過該依賴的官方文件我們可以看到這樣的描述:

ImageAI 是一個 python 庫,旨在使開發人員能夠使用簡單的幾行程式碼構建具有包含深度學習和計算機視覺功能的應用程式和系統。
ImageAI 本著簡潔的原則,支援最先進的機器學習演算法,用於影象預測、自定義影象預測、物體檢測、視訊檢測、視訊物件跟蹤和影象預測訓練。ImageAI 目前支援使用在 ImageNet-1000 資料集上訓練的 4 種不同機器學習演算法進行影象預測和訓練。ImageAI 還支援使用在 COCO 資料集上訓練的 RetinaNet 進行物件檢測、視訊檢測和物件跟蹤。最終,ImageAI 將為計算機視覺提供更廣泛和更專業化的支援,包括但不限於特殊環境和特殊領域的影象識別。

也就是說這個依賴庫,可以幫助我們完成基本的影象識別和視訊的目標提取,雖然他給了一些資料集和模型,但是我們也可以根據自身需要對其進行額外的訓練,進行定製化拓展。通過官方給的程式碼,我們可以看到一個簡單的 Demo:

# -*- coding: utf-8 -*-
from imageai.Prediction import ImagePrediction

# 模型載入
prediction = ImagePrediction()
prediction.setModelTypeAsResNet()
prediction.setModelPath("resnet50_weights_tf_dim_ordering_tf_kernels.h5")
prediction.loadModel()

predictions, probabilities = prediction.predictImage("./picture.jpg", result_count=5 )
for eachPrediction, eachProbability in zip(predictions, probabilities):
    print(str(eachPrediction) + " : " + str(eachProbability))

當我們指定的 picture.jpg 圖片為:

我們在執行之後的結果是:

laptop : 71.43893241882324
notebook : 16.265612840652466
modem : 4.899394512176514
hard_disc : 4.007557779550552
mouse : 1.2981942854821682

如果在使用過程中覺得模型 resnet50_weights_tf_dim_ordering_tf_kernels.h5 過大,耗時過長,可以按需求選擇模型:

  • SqueezeNet(檔案大小:4.82 MB,預測時間最短,精準度適中)
  • ResNet50 by Microsoft Research (檔案大小:98 MB,預測時間較快,精準度高)
  • InceptionV3 by Google Brain team (檔案大小:91.6 MB,預測時間慢,精度更高)
  • DenseNet121 by Facebook AI Research (檔案大小:31.6 MB,預測時間較慢,精度最高)

模型下載地址可參考 Github 地址:
https://github.com/OlafenwaMoses/ImageAI/releases/tag/1.0

或者參考 ImageAI 官方文件:
https://imageai-cn.readthedocs.io/zh_CN/latest/ImageAI_Image_Prediction.html

專案 Serverless 化

將專案按照函式計算的需求,編寫好入口方法,以及做好專案初始化,同時在當前專案下建立資料夾 model,並將模型檔案拷貝到該資料夾:

專案整體流程:

實現程式碼:

# -*- coding: utf-8 -*-

from imageai.Prediction import ImagePrediction
import json
import uuid
import base64
import random

# Response
class Response:
    def __init__(self, start_response, response, errorCode=None):
        self.start = start_response
        responseBody = {
            'Error': {"Code": errorCode, "Message": response},
        } if errorCode else {
            'Response': response
        }
        # 預設增加uuid,便於後期定位
        responseBody['ResponseId'] = str(uuid.uuid1())
        print("Response: ", json.dumps(responseBody))
        self.response = json.dumps(responseBody)

    def __iter__(self):
        status = '200'
        response_headers = [('Content-type', 'application/json; charset=UTF-8')]
        self.start(status, response_headers)
        yield self.response.encode("utf-8")

# 隨機字串
randomStr = lambda num=5: "".join(random.sample('abcdefghijklmnopqrstuvwxyz', num))

# 模型載入
print("Init model")
prediction = ImagePrediction()
prediction.setModelTypeAsResNet()
print("Load model")
prediction.setModelPath("/mnt/auto/model/resnet50_weights_tf_dim_ordering_tf_kernels.h5")
prediction.loadModel()
print("Load complete")

def handler(environ, start_response):
    try:
        request_body_size = int(environ.get('CONTENT_LENGTH', 0))
    except (ValueError):
        request_body_size = 0
    requestBody = json.loads(environ['wsgi.input'].read(request_body_size).decode("utf-8"))

    # 圖片獲取
    print("Get pucture")
    imageName = randomStr(10)
    imageData = base64.b64decode(requestBody["image"])
    imagePath = "/tmp/" + imageName
    with open(imagePath, 'wb') as f:
        f.write(imageData)

    # 內容預測
    print("Predicting ... ")
    result = {}
    predictions, probabilities = prediction.predictImage(imagePath, result_count=5)
    print(zip(predictions, probabilities))
    for eachPrediction, eachProbability in zip(predictions, probabilities):
        result[str(eachPrediction)] = str(eachProbability)

    return Response(start_response, result)

所需要的依賴:

tensorflow==1.13.1
numpy==1.19.4
scipy==1.5.4
opencv-python==4.4.0.46
pillow==8.0.1
matplotlib==3.3.3
h5py==3.1.0
keras==2.4.3
imageai==2.1.5

編寫部署所需要的配置檔案:

ServerlessBookImageAIDemo:
  Component: fc
  Provider: alibaba
  Access: release
  Properties:
    Region: cn-beijing
    Service:
      Name: ServerlessBook
      Description: Serverless圖書案例
      Log: Auto
      Nas: Auto
    Function:
      Name: serverless_imageAI
      Description: 圖片目標檢測
      CodeUri:
        Src: ./src
        Excludes:
          - src/.fun
          - src/model
      Handler: index.handler
      Environment:
        - Key: PYTHONUSERBASE
          Value: /mnt/auto/.fun/python
      MemorySize: 3072
      Runtime: python3
      Timeout: 60
      Triggers:
        - Name: ImageAI
          Type: HTTP
          Parameters:
            AuthType: ANONYMOUS
            Methods:
              - GET
              - POST
              - PUT
            Domains:
              - Domain: Auto

在程式碼與配置中,可以看到有目錄:/mnt/auto/ 的存在,該部分實際上是 nas 掛載之後的地址,只需提前寫入到程式碼中即可,下一個環節會進行 nas 的建立以及掛載點配置的具體操作。

專案部署與測試

在完成上述步驟之後,可以通過:

s deploy

進行專案部署,部署完成可以看到結果:

完成部署之後,可以通過:

s install docker

進行依賴的安裝:

依賴安裝完成可以看到在目錄下生成了 .fun 的目錄,該目錄就是通過 docker 打包出來的依賴檔案,這些依賴正是我們在 requirements.txt 檔案中宣告的依賴內容。

完成之後,我們通過:

s nas sync ./src/.fun

將依賴目錄打包上傳到 nas,成功之後再將 model 目錄打包上傳:

s nas sync ./src/model

完成之後可以通過:

s nas ls --all

檢視目錄詳情:

完成之後,我們可以編寫指令碼進行測試,同樣適用剛才的測試圖片,通過程式碼:

import json
import urllib.request
import base64
import time

with open("picture.jpg", 'rb') as f:
    data = base64.b64encode(f.read()).decode()

url = 'http://35685264-1295939377467795.test.functioncompute.com/'

timeStart = time.time()
print(urllib.request.urlopen(urllib.request.Request(
    url=url,
    data=json.dumps({'image': data}).encode("utf-8")
)).read().decode("utf-8"))
print("Time: ", time.time() - timeStart)

可以看到結果:

{"Response": {"laptop": "71.43893837928772", "notebook": "16.265614330768585", "modem": "4.899385944008827", "hard_disc": "4.007565602660179", "mouse": "1.2981869280338287"}, "ResponseId": "1d74ae7e-298a-11eb-8374-024215000701"}
Time:  29.16020894050598

可以看到,函式計算順利地返回了預期結果,但是整體耗時卻超乎想象,有近 30s,此時我們再次執行一下測試指令碼:

{"Response": {"laptop": "71.43893837928772", "notebook": "16.265614330768585", "modem": "4.899385944008827", "hard_disc": "4.007565602660179", "mouse": "1.2981869280338287"}, "ResponseId": "4b8be48a-298a-11eb-ba97-024215000501"}
Time:  1.1511380672454834

可以看到,再次執行的時間僅有 1.15 秒,比上次整整提升了 28 秒之多。

專案優化

在上一輪的測試中可以看到,專案首次啟動和二次啟動的耗時差距,其實這個時間差,主要是函式在載入模型的時候浪費了極長的時間。

即使在本地,我們也可以簡單測試:

# -*- coding: utf-8 -*-

import time

timeStart = time.time()

# 模型載入
from imageai.Prediction import ImagePrediction

prediction = ImagePrediction()
prediction.setModelTypeAsResNet()
prediction.setModelPath("resnet50_weights_tf_dim_ordering_tf_kernels.h5")
prediction.loadModel()
print("Load Time: ", time.time() - timeStart)
timeStart = time.time()

predictions, probabilities = prediction.predictImage("./picture.jpg", result_count=5)
for eachPrediction, eachProbability in zip(predictions, probabilities):
    print(str(eachPrediction) + " : " + str(eachProbability))
print("Predict Time: ", time.time() - timeStart)

執行結果:

Load Time:  5.549695014953613
laptop : 71.43893241882324
notebook : 16.265612840652466
modem : 4.899394512176514
hard_disc : 4.007557779550552
mouse : 1.2981942854821682
Predict Time:  0.8137111663818359

可以看到,在載入 imageAI 模組以及載入模型檔案的過程中,一共耗時 5.5 秒,在預測部分僅有不到 1 秒鐘的時間。而在函式計算中,機器效能本身就沒有我本地的效能高,此時為了避免每次裝載模型導致的響應時間過長,在部署的程式碼中,可以看到模型裝載過程實際上是被放在了入口方法之外。這樣做的一個好處是,專案每次執行的時候,不一定會有冷啟動,也就是說在某些複用的前提下是可以複用一些物件的,即無需每次都重新載入模型、匯入依賴等。

所以在實際專案中,為了避免頻繁請求,例項重複裝載、建立某些資源,我們可以將部分資源放在初始化的時候進行。這樣可以大幅度提高專案的整體效能,同時配合廠商所提供的預留能力,可以基本上杜絕函式冷啟動帶來的負面影響。

總結

近年來,人工智慧與雲端計算的發展突飛猛進,在 Serverless 架構中,如何執行傳統的人工智慧專案已經逐漸成為很多人所需要了解的事情。本文主要介紹了通過一個已有的依賴庫(ImageAI)實現一個影象分類和預測的介面。藉助這個例子,其實有幾個事情是可以被明確的:

  • Serverless 架構可以執行人工智慧相關專案;
  • Serverless 可以很好地相容 Tensorflow 等機器學習/深度學習的工具;
  • 雖然說函式計算本身有空間限制,但是實際上增加了硬碟掛載能力之後,函式計算本身的能力將會得到大幅度的拓展。

當然,本文也算是拋磚引玉,希望讀者在本文之後,可以發揮自己的想象,將更多的 AI 專案與 Serverless 架構進行進一步結合。

作者:江昱

原文連結

本文為阿里雲原創內容,未經允許不得轉載