如今開始:用你的Mac訓練和部署一個圖片分類模型

文/ 阿里淘系 F(x) Team 蘇川

可能有些同窗學習機器學習的時候比較迷茫,不知道該怎麼上手,看了不少經典書籍介紹的各類算法,但仍是不知道怎麼用它來解決問題,就算知道了,又發現須要準備環境、準備訓練和部署的機器,啊,好麻煩。html


今天,我來給你們介紹一種容易上手的方法,給你現成的樣本和代碼,按照步驟操做,就能夠在本身的 Mac 上體驗運用機器學習的全流程啦~~~python


下面的 Demo, 最終的效果是給定一張圖片,能夠預測圖片的類別。好比咱們訓練模型用的樣本是貓啊狗啊,那模型能學到的認識的就是貓啊狗啊, 若是用的訓練樣本是按鈕啊搜索框啊,那模型能學到的認識的就是這個按鈕啊搜索框啊。git


若是想了解用機器學習是怎麼解決實際問題的,能夠看這篇:如何使用深度學習識別UI界面組件?從問題定義、算法選型、樣本準備、模型訓練、模型評估、模型服務部署、到模型應用都有介紹。github


環境準備

安裝 Anaconda

下載地址: www.anaconda.com/products/in…算法

image.png


安裝成功後,在終端命令行執行如下命令,使環境變量當即生效:docker

$ source ~/.bashrc複製代碼


能夠執行如下命令,查看環境變量macos


$ cat ~/.bashrc複製代碼

能夠看到 anaconda 的環境變量已經自動添加到 .bashrc 文件了json

image.png


執行如下命令:數組

$ conda list複製代碼

能夠看到 Anaconda 中有不少已經安裝好的包,若是有使用到這些包的就不須要再安裝了,python 環境也裝好了。bash

image.png


注意:若是安裝失敗,從新安裝,在提示安裝在哪裏時,選擇「更改安裝位置」,安裝位置選擇其餘地方不是用默認的,安裝在哪裏本身選擇,能夠放在「應用程序」下。


image.png


安裝相關依賴

anaconda 中沒有 keras、tensorflow 和 opencv-python, 須要單獨安裝。

$ pip install keras
$ pip install tensorflow
$ pip install opencv-python複製代碼


樣本準備

這裏只准備了 4 個分類: button、keyboard、searchbar、switch, 每一個分類 200 個左右的樣本。


image.png

image.png

image.png

image.png



模型訓練

開發訓練邏輯

新建一個項目 train-project, 文件結構以下:


.
├── CNN_net.py
├── dataset
├── nn_train.py
└── utils_paths.py複製代碼


入口文件代碼以下,這裏的邏輯是將準備好的樣本輸入給圖像分類算法 SimpleVGGNet, 並設置一些訓練參數,例如學習率、Epoch、Batch Size, 而後執行這段訓練邏輯,最終獲得一個模型文件。

# nn_train.py
from CNN_net import SimpleVGGNet
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

from keras.optimizers import SGD
from keras.preprocessing.image import ImageDataGenerator
import utils_paths
import matplotlib.pyplot as plt
from cv2 import cv2
import numpy as np
import argparse
import random
import pickle

import os

# 讀取數據和標籤
print("------開始讀取數據------")
data = []
labels = []

# 拿到圖像數據路徑,方便後續讀取
imagePaths = sorted(list(utils_paths.list_images('./dataset')))
random.seed(42)
random.shuffle(imagePaths)

image_size = 256
# 遍歷讀取數據
for imagePath in imagePaths:
    # 讀取圖像數據
    image = cv2.imread(imagePath)
    image = cv2.resize(image, (image_size, image_size))
    data.append(image)
    # 讀取標籤
    label = imagePath.split(os.path.sep)[-2]
    labels.append(label)

data = np.array(data, dtype="float") / 255.0
labels = np.array(labels)

# 數據集切分
(trainX, testX, trainY, testY) = train_test_split(data,labels, test_size=0.25, random_state=42)

# 轉換標籤爲one-hot encoding格式
lb = LabelBinarizer()
trainY = lb.fit_transform(trainY)
testY = lb.transform(testY)

# 數據加強處理
aug = ImageDataGenerator(
    rotation_range=30, 
    width_shift_range=0.1,
    height_shift_range=0.1, 
    shear_range=0.2, 
    zoom_range=0.2,
    horizontal_flip=True, 
    fill_mode="nearest")

# 創建卷積神經網絡
model = SimpleVGGNet.build(width=256, height=256, depth=3,classes=len(lb.classes_))

# 設置初始化超參數

# 學習率
INIT_LR = 0.01
# Epoch 
# 這裏設置 5 是爲了能儘快訓練完畢,能夠設置高一點,好比 30
EPOCHS = 5   
# Batch Size
BS = 32

# 損失函數,編譯模型
print("------開始訓練網絡------")
opt = SGD(lr=INIT_LR, decay=INIT_LR / EPOCHS)
model.compile(loss="categorical_crossentropy", optimizer=opt,metrics=["accuracy"])

# 訓練網絡模型
H = model.fit_generator(
    aug.flow(trainX, trainY, batch_size=BS),
    validation_data=(testX, testY), 
    steps_per_epoch=len(trainX) // BS,
    epochs=EPOCHS
)


# 測試
print("------測試網絡------")
predictions = model.predict(testX, batch_size=32)
print(classification_report(testY.argmax(axis=1), predictions.argmax(axis=1), target_names=lb.classes_))

# 繪製結果曲線
N = np.arange(0, EPOCHS)
plt.style.use("ggplot")
plt.figure()
plt.plot(N, H.history["loss"], label="train_loss")
plt.plot(N, H.history["val_loss"], label="val_loss")
plt.plot(N, H.history["accuracy"], label="train_acc")
plt.plot(N, H.history["val_accuracy"], label="val_acc")
plt.title("Training Loss and Accuracy")
plt.xlabel("Epoch #")
plt.ylabel("Loss/Accuracy")
plt.legend()
plt.savefig('./output/cnn_plot.png')

# 保存模型
print("------保存模型------")
model.save('./cnn.model.h5')
f = open('./cnn_lb.pickle', "wb")
f.write(pickle.dumps(lb))
f.close()複製代碼


對於實際應用場景下,數據集很大,epoch 也會設置比較大,並在高性能的機器上訓練。如今要在本機 Mac 上完成訓練任務,咱們只給了不多的樣原本訓練模型,epoch 也很小(爲 5),固然這樣模型的識別準確率也會不好,但咱們此篇文章的目的是爲了在本機完成一個機器學習的任務。


開始訓練

執行如下命令開始訓練:

$ python nn_train.py複製代碼


訓練過程日誌以下:

image.png

訓練結束後,在當前目錄下會生成兩個文件: 模型文件 cnn.model.h5 和 損失函數曲線 output/cnn_plot.png

image.pngimage.png



模型評估

如今,咱們拿到了模型文件 cnn.model.h5, 能夠寫一個預測腳本,本地執行腳本預測一張圖片的分類。

$ python predict.py複製代碼
# predict.py
import allspark
import io
import numpy as np
import json
from PIL import Image
import requests
import threading
import cv2
import os
import tensorflow as tf
from tensorflow.keras.models import load_model
import time

model = load_model('./train/cnn.model.h5')
# pred的輸入應該是一個images的數組,並且圖片都已經轉爲numpy數組的形式
# pred = model.predict(['./validation/button/button-demoplus-20200216-16615.png'])

#這個順序必定要與label.json順序相同,模型輸出是一個數組,取最大值索引爲預測值
Label = [
    "button",
    "keyboard",
    "searchbar",
    "switch"
    ]
testPath = "./test/button.png"

images = []
image = cv2.imread(testPath)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

image = cv2.resize(image,(256,256))
images.append(image)
images = np.asarray(images)

pred = model.predict(images)

print(pred)

max_ = np.argmax(pred)
print('預測結果爲:',Label[max_])
複製代碼

若是想要知道這個模型的準確率,也能夠給模型輸入一批帶有已知分類的數據,經過模型預測後,將模型預測的分類與真實的分類比較,計算出準確率和召回率。



模型服務部署

開發模型服務

但在實際應用中,咱們預測一張圖片的類別, 是經過給定一張圖片,請求一個 API 來拿到返回結果的。咱們須要編寫一個模型服務,而後部署到遠端,拿到一個部署以後的模型服務 API。


如今,咱們能夠編寫一個模型服務,而後在本地部署。


# 模型服務 app.py
import allspark
import io
import numpy as np
import json
from PIL import Image
import requests
import threading
import cv2
import tensorflow as tf
from tensorflow.keras.models import load_model


with open('label.json') as f:
    mp = json.load(f)
labels = {value:key for key,value in mp.items()}

def create_opencv_image_from_stringio(img_stream, cv2_img_flag=-1):
  img_stream.seek(0)
  img_array = np.asarray(bytearray(img_stream.read()), dtype=np.uint8)
  image_temp = cv2.imdecode(img_array, cv2_img_flag)
  if image_temp.shape[2] == 4:
    image_channel3 = cv2.cvtColor(image_temp, cv2.COLOR_BGRA2BGR)
    image_mask = image_temp[:,:,3] #.reshape(image_temp.shape[0],image_temp.shape[1], 1)
    image_mask = np.stack((image_mask, image_mask, image_mask), axis = 2)
    index_mask = np.where(image_mask == 0)
    image_channel3[index_mask[0], index_mask[1], index_mask[2]] = 255
    return image_channel3
  else:
    return image_temp

def get_string_io(origin_path):
  r = requests.get(origin_path, timeout=2)
  stringIo_content = io.BytesIO(r.content)
  return stringIo_content

def handleReturn(pred, percent, msg_length):
  result = {
    "content":[]
  }
  argm = np.argsort(-pred, axis = 1)
  for i in range(msg_length):
      label = labels[argm[i, 0]]
      index = argm[i, 0]
      if(pred[i, index] > percent):
        confident = True
      else:
        confident = False
      result['content'].append({'isConfident': confident, 'label': label})
  return result


def process(msg, model):
  msg_dict = json.loads(msg)
  percent = msg_dict['threshold']
  msg_dict = msg_dict['images']
  msg_length = len(msg_dict)
  desire_size = 256
  
  images = []
  for i in range(msg_length):
    image_temp = create_opencv_image_from_stringio(get_string_io(msg_dict[i]))
    image_temp = cv2.cvtColor(image_temp, cv2.COLOR_BGR2RGB)
    image = cv2.resize(image_temp, (256, 256))  
    images.append(image)
  images = np.asarray(images)
  pred = model.predict(images)
  return bytes(json.dumps(handleReturn(pred, percent, msg_length)) ,'utf-8')  


def worker(srv, thread_id, model):
  while True:
    msg = srv.read()
    try:
      rsp = process(msg, model)
      srv.write(rsp)
    except Exception as e:
      srv.error(500,bytes('invalid data format', 'utf-8'))

if __name__ == '__main__':
    desire_size = 256
    model = load_model('./cnn.model.h5')
    
    context = allspark.Context(4)
    queued = context.queued_service()

    workers = []
    for i in range(10):
        t = threading.Thread(target=worker, args=(queued, i, model))
        t.setDaemon(True)
        t.start()
        workers.append(t)
    for t in workers:
        t.join()


複製代碼


部署模型服務

模型服務編寫完成後,在本地部署,須要安裝環境。首先建立一個模型服務項目: deploy-project, 將 cnn.model.h5 拷貝到此項目中, 並在此項目下安裝環境。

.
├── app.py
├── cnn.model.h5
└── label.json複製代碼


安裝環境

能夠看下阿里雲的模型服務部署文檔:三、Python語言-3.2 構建開發環境-3.2.3 使用預構建的開發鏡像(推薦)


安裝 Docker

能夠直接查看 Mac Docker 安裝文檔

# 用 Homebrew 安裝 須要先現狀 Homebrew: https://brew.sh
$ brew cask install docker複製代碼

安裝完以後,桌面上會出現 Docker 的圖標。


建立 anaconda 的虛擬環境


# 使用conda建立python環境,目錄需指定固定名字:ENV
$ conda create -p ENV python=3.7

# 安裝EAS python sdk
$ ENV/bin/pip install http://eas-data.oss-cn-shanghai.aliyuncs.com/sdk/allspark-0.9-py2.py3-none-any.whl

# 安裝其它依賴包
$ ENV/bin/pip install tensorflow keras opencv-python

# 激活虛擬環境
$ conda activate ./ENV

# 退出虛擬環境(不使用時)
$ conda deactivate複製代碼


運行 Docker 環境

/Users/chang/Desktop/ml-test/deploy-project 換成本身的項目路徑

sudo docker run -ti -v  /Users/chang/Desktop/ml-test/deploy-project:/home -p 8080:8080  
registry.cn-shanghai.aliyuncs.com/eas/eas-python-base-image:py3.6-allspark-0.8複製代碼


本地部署

如今能夠本地部署了,執行如下命令:

cd /home
./ENV/bin/python app.py複製代碼

下面的日誌能夠看到部署成功。

image.png

部署成功後,能夠經過 localhost:8080/predict 訪問模型服務了。


咱們用 curl 命令來發一個 post 請求, 預測圖片分類:


curl -X POST 'localhost:8080/predict' \
-H 'Content-Type: application/json' \
-d '{ "images": ["https://img.alicdn.com/tfs/TB1W8K2MeH2gK0jSZJnXXaT1FXa-638-430.png"], "threshold": 0.5 }'複製代碼


獲得預測結果:

{"content": [{"isConfident": true, "label": "keyboard"}]}複製代碼


完整代碼

能夠直接 clone 代碼倉庫:github.com/imgcook/ml-…


在安裝好環境後,直接按如下命令運行。

# 一、訓練模型
$ cd train-project
$ python nn_train.py

# 生成模型文件:cnn.model.h5

# 二、將模型文件拷貝到 deploy-project 中,部署模型服務
# 先安裝模型服務運行環境
$ conda activate ./ENV
$ sudo docker run -ti -v  /Users/chang/Desktop/ml-test/deploy-project:/home -p 8080:8080  registry.cn-shanghai.aliyuncs.com/eas/eas-python-base-image:py3.6-allspark-0.8
$ cd /home
$ ./ENV/bin/python app.py

# 獲得模型服務 API: localhost:8080/predict

# 三、訪問模型服務
curl -X POST 'localhost:8080/predict' \
-H 'Content-Type: application/json' \
-d '{ "images": ["https://img.alicdn.com/tfs/TB1W8K2MeH2gK0jSZJnXXaT1FXa-638-430.png"], "threshold": 0.5 }'複製代碼


最後

好啦,總結一下這裏使用深度學習的流程。咱們選用了 SimpleVGGNet 做爲圖像分類算法(至關於一個函數),將準備好的數據傳給這個函數,運行這個函數(學習數據集的特徵和標籤)獲得一個輸出,就是模型文件 model.h5。


image.png

這個模型文件能夠接收一張圖片做爲輸入,並預測這張圖片是什麼,輸出預測結果。但若是想要讓模型能夠在線上跑,須要寫一個模型服務(API)並部署到線上以獲得一個 HTTP API,咱們能夠在生產環境直接調用。

相關文章
相關標籤/搜索