Machine Learning,  網站相關

初探 TensorFlow Serving on Windows 10

前言

這是前一陣子要嘗試使用 TensorFlow Serving 的實作,但是過程踩不少坑,許多資料都是基於 Linux,而 Windows 的架設文章,我都無法完全照做,因此紀錄一下自己的過程,給想在 Windows 上練習的人能多一點參考XD。

內容其實是從其他網站參考出來,包括範例程式之類的,但是最重要的是自己實作過程,以及其中躍過的坑,這才是我要記錄的主要原因。

極簡介紹 TensorFlow Serving

顧名思義,就是將我們訓練好的模型,建置一個服務器 (伺服器) 來供用戶使用其功能,這也意味著,將會同時開放給多位用戶,甚至需要同時處理這些用戶的請求,正因如此,如果只是用一般的網路框架去佈署 ML 推理,很難達成上述需求。

為了更容易佈署訓練好的 ML 模型,TensorFlow 提供 TensorFlow Serving 來讓開發者可以建立網路服務的接口。

小弟不是專業網路服務開發者,相關概念極大可能有誤,如有錯誤麻煩請不吝指教,感謝各路大神 ORZ

超不專業介紹 Docker

一種針對應用程式的虛擬化 Container 技術,能夠將環境配置包裝起來,讓別人可以通過這個 Container 快速搭建原有的應用程式功能及環境。

其實只是因為要用 TensorFlow Serving 才去裝 Docker,之前完全沒用過,而且 Docker 以前好像只有支援 Linux,導致參考資料大多都是 Linux 為主。(應該說,真的在搞程式相關的,不會 Linux 其實很麻煩啊,像我就是 QAQ)


~~收拾一下心情,迎接複雜的安裝過程~~


在 Win10 建置 TensorFlow Serving Docker Image

一、啟動 Windows 的 WSL 功能

WSL 是 Windows Subsystem for Linux 的縮寫。我的理解就是在 Windows 中設定 Linux 子系統,就不需要額外安裝虛擬機。

a) 先到控制台的 "程式和功能",然後點進 "開啟或關閉Windows功能",把打開下圖的選項打開

file

b) 安裝 Windows 的 Ububtu 子系統

file

c) 裝好之後啟動程式

file

d) 照著步驟安裝即可

file

e) 成功會出現類似下圖

file

將 WSL 2 設定為預設版本
開啟 CMD,然後執行下列命令,以將 WSL 2 設定為預設版本:wsl --set-default-version 2

二、安裝 Docker for Desktop

先去去官網下載程式,然後直接安裝

a) 一定要勾 WSL2

file

b) 開始安裝軟體

file

c) 完成安裝,重新啟動

file

d) 如果安裝完就開啟 Docker,軟體是會出錯的,因為還沒安裝 WSL 2 更新套件

file
file

e) 前往微軟官方文件下載 WSL2 Linux 核心更新套件

直接下載安裝即可,我就不附圖了

f) 重新啟動 Docker for Desktop 軟體

看到以下畫面才代表完成 Docker 的安裝
file

g) 檢查是否可以正常執行

接著我們打開 "命令提示字元",輸入圖中指令
file
這時候 Docker for Desktop 就會出現新的 Container 出來
file

三、建立 TensorFlow Serving 環境

打開 "命令提示字元",分別輸入下面指令

a) 用 docker pull 下載所需要的 image 檔

docker pull tensorflow/serving

執行完會在 Images 中出現 tensorflow/serving
file

b) 用 git cloneTensorFlow Serving 相關範例檔案抓下來

git clone https://github.com/tensorflow/serving

等等要用官方範例檢查是否有安裝成功

c) 用 docker run 啟動 docker image,開啟 TensorFlow Serving 服務

docker run -p 8501:8501 --mount type=bind,source=D:/serving/tensorflow_serving/servables/tensorflow/testdata/saved_model_half_plus_two_cpu,target=/models/half_plus_two -e MODEL_NAME=half_plus_two -t tensorflow/serving '&'

file

d) 檢查是否能夠連到 TensorFlow Serving 服務,並印出結果

curl -XPOST http://localhost:8501/v1/models/half_plus_two:predict -d "{\"instances\":[1.0, 2.0, 5.0]}"

file

Note: 這要開另一個 "命令提示字元" 喔,原本的 "命令提示字元" 視窗已經用來啟動服務了

e) 關閉 tf-serving

等等要執行自己的模型,所以確認完範例可以執行之後,便可以關掉這個 Container 了
file

其實第二點有成功時,Docker for Desktop 就會自動添加這個 Container,之後要開啟這個 Container 可以從這裡去執行即可;反之,執行失敗就不會加入。


~~休息一下~~


由自己訓練出模型檔案並佈署成 TensorFlow Serving

數字分類就不多說了,太多資料了,這邊就簡單弄個神經網路,然後把 Weights 存下來。

訓練、儲存數字分類模型

import tensorflow as tf
import numpy as np

class MNISTLoader():
    def __init__(self):
        mnist = tf.keras.datasets.mnist
        (self.train_data, self.train_label), (self.test_data, self.test_label) = mnist.load_data()
        # MNIST中的圖片預設為uint8(0-255的數字)。以下程式碼將其正規化到0-1之間的浮點數,並在最後增加一維作為顏色通道
        self.train_data = np.expand_dims(self.train_data.astype(np.float32) / 255.0, axis=-1)      # [60000, 28, 28, 1]
        self.test_data = np.expand_dims(self.test_data.astype(np.float32) / 255.0, axis=-1)        # [10000, 28, 28, 1]
        self.train_label = self.train_label.astype(np.int32)    # [60000]
        self.test_label = self.test_label.astype(np.int32)      # [10000]
        self.num_train_data, self.num_test_data = self.train_data.shape[0], self.test_data.shape[0]

    def get_batch(self, batch_size):
        # 從資料集中隨機取出batch_size個元素並返回
        index = np.random.randint(0, self.num_train_data, batch_size)
        return self.train_data[index, :], self.train_label[index]

num_epochs = 1
batch_size = 50
learning_rate = 0.001

model = tf.keras.models.Sequential([
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(100, activation=tf.nn.relu),
    tf.keras.layers.Dense(10),
    tf.keras.layers.Softmax()
])

data_loader = MNISTLoader()
model.compile(
    optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
    loss=tf.keras.losses.sparse_categorical_crossentropy,
    metrics=[tf.keras.metrics.sparse_categorical_accuracy]
)
model.fit(data_loader.train_data, data_loader.train_label, epochs=num_epochs, batch_size=batch_size)

model.save("saved/1")

程式碼是摳網路上的,這邊重點其實是 model.save("saved/1") 這個指令。

將會產生 saved 資料夾,裡面會存放 serving 所需要的權重格式,其檔案結構如下所示。

saved
└─ 1
   ├─ assets
   │  └─ ...
   ├─ variables
   │  └─ ...
   ├─ keras_metadata.pb
   └─ saved_model.pb

啟用剛剛的數字分類的 TensorFlow Serving

前面已經用官方範例驗證過 TensorFlow Serving 是可以成功執行,再來就是把上面訓練好的模型部屬成一個 可供客戶使用的 Web API。

開啟 "命令提示字元" ,輸入下面指令 (記得 source 改成自己 saved 路徑)

docker run -p 8501:8501 --name tfserving_classifier --mount type=bind,source=D:/GitHub/tf2-serving/mnist/saved,target=/models/saved -e MODEL_NAME=saved -t tensorflow/serving

是否服務有上線可以看下圖兩條紅線

file

Tensorflow Serving 允許兩種類型的 API —— REST 和 gRPC。

  • REST 是 Web API 所用的一種 "傳輸協議 (communication protocol)”。它定義了用戶端如何與 Web 服務通信的通信方式,一般會稱其為 REST API。 REST 用戶端使用 HTTP 方法(如 GET、POST 等)與 Server 進行資料傳遞,大多以 JSON 格式編碼。
  • gRPC 是最初由 Google 開發的傳輸協議。 gRPC 使用的資料格式稱為協議緩衝區 (protocol buffer)。 gRPC 提供低延遲和比 REST 更小的有效負載 (白話就是比較高效)。

後面我將使用 REST 形式 API,因為它更易於使用和檢查,因為我壓根沒搞懂 gRPC 在幹嘛ㄏㄏ

Tensorflow Serving 在執行時會提供兩個 API 端點,所以其實我都不用去設置 API 相關的東西。

用 Python 來連接 數字分類的 Tensorflow Serving

  • predict.py: 裡面發送 MNIST 資料給 tf-serving,並回傳預測結果。
    • json 格式傳遞數字資料
    • requests 用來發送 (post) 請求,並回傳預測結果
    • 其結果也是 json 格式,將其解碼出來並 print 印出來

requests 是 Python 套件,如果提示缺少此模組,可以通過 pip install requests 來安裝

import matplotlib.pyplot as plt
import requests
import base64
import json
import numpy as np
from tensorflow.keras.datasets.mnist import load_data

#load MNIST dataset
(_, _), (x_test, y_test) = load_data()
# reshape data to have a single channel
x_test = x_test.reshape((x_test.shape[0], x_test.shape[1], x_test.shape[2], 1))
# normalize pixel values
x_test = x_test.astype('float32') / 255.0

#server URL
url = 'http://localhost:8501/v1/models/saved:predict'

def make_prediction(instances):
   data = json.dumps({"signature_name": "serving_default", "instances": instances.tolist()})
   headers = {"content-type": "application/json"}
   json_response = requests.post(url, data=data, headers=headers)
   predictions = json.loads(json_response.text)['predictions']
   return predictions

predictions = make_prediction(x_test[0:4])

for i, pred in enumerate(predictions):
   print(f"True Value: {y_test[i]}, Predicted Value: {np.argmax(pred)}")
  • 結果
True Value: 7, Predicted Value: 7
True Value: 2, Predicted Value: 2
True Value: 1, Predicted Value: 1
True Value: 0, Predicted Value: 0

用 node.js 來連接 數字分類的 Tensorflow Serving

a) 安裝 node.js

前往官網下載 node.js

b) 用 npm 安裝 node.js 相關套件

npm install jimp
npm install superagent

jimp: 用來圖像處理
superagent: 用來發送 HTTP 協定

c) demo.js: 用 javascript 傳送資料到 TensorFlow Serving

        const Jimp = require('jimp')
        const superagent = require('superagent')

        const url = 'http://localhost:8501/v1/models/saved:predict'

        const getPixelGrey = (pic, x, y) => {
          const pointColor = pic.getPixelColor(x, y)
          const { r, g, b } = Jimp.intToRGBA(pointColor)
          const gray =  +(r * 0.299 + g * 0.587 + b * 0.114).toFixed(0)
          return [ gray / 255 ]
        }

        const getPicGreyArray = async (fileName) => {
          const pic = await Jimp.read(fileName)
          const resizedPic = pic.resize(28, 28)
          const greyArray = []
          for ( let i = 0; i< 28; i ++ ) {
            let line = []
            for (let j = 0; j < 28; j ++) {
              line.push(getPixelGrey(resizedPic, j, i))
            }
            console.log(line.map(_ => _ > 0.3 ? ' ' : '1').join(' '))
            greyArray.push(line)
          }
          return greyArray
        }

        const evaluatePic = async (fileName) => {
          const arr = await getPicGreyArray(fileName)
          const result = await superagent.post(url)
            .send({
              instances: [arr]
            })
          result.body.predictions.map(res => {
            const sortedRes = res.map((_, i) => [_, i])
            .sort((a, b) => b[0] - a[0])
            console.log(`我們猜這個數字是${sortedRes[0][1]},機率是${sortedRes[0][0]}`)
          })
        }

        evaluatePic('test_pic_tag_5.png')

d) 用 node 執行 demo.js

node demo.js

其結果如下:

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1     1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1     1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1     1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1               1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1                 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1     1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1     1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1       1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1     1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1       1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1     1                 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1                         1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1         1 1 1 1 1 1     1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1     1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1     1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1       1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1     1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1         1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1         1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1     1 1 1         1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1                 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1         1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
我們猜這個數字是5,機率是0.805307329

~~終於弄完了,嗚嗚嗚~~


結語

這篇原本沒要用這麼多篇幅的,後來是我在安裝中遇到太多問題,又找了其他電腦重新搞一遍,只用 Windows 果然很難搞啊,確定可以實現之後,才開始寫這篇文章,然後寫了才發現又多了許多不懂的地方,例如:WSL、Docker 基本概念、Web Mirco Serve 概念、Web API、REST/gPRC 通信協定、node.js、curl …,一堆都是我不會的東西,所以花了一些時間稍微認識這些名詞,雖然還是不太懂

除了認識新名詞,其他東西都是 我從其他網站整理並實踐一遍做出來的 (參考網站都整理在下方了),這些東西基於以前的知識,像是神經網路訓練,存模型並預測結果,都是已經會的東西,反而安裝佈署模型花一堆時間,總之這次嘗試架設 TensorFlow Serving 也算是學到了不少東西,希望給想在 Windows 嘗試練習的人參考。

相關程式碼: Github

Refer

留下一個回覆

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *