三分鐘快速上手TensorFlow 2.0 (下)——模型的部署 、大規模訓練、加速

前文:三分鐘快速上手TensorFlow 2.0 (中)——經常使用模塊和模型的部署

TensorFlow 模型導出

 

使用 SavedModel 完整導出模型

不只包含參數的權值,還包含計算的流程(即計算圖)javascript

tf.saved_model.save(model, "保存的目標文件夾名稱")
將模型導出爲 SavedModel
model = tf.saved_model.load("保存的目標文件夾名稱")
載入 SavedModel 文件
由於 SavedModel 基於計算圖,因此對於使用繼承 tf.keras.Model 類創建的 Keras 模型,其須要導出到 SavedModel 格式的方法(好比 call )都須要使用 @tf.function 修飾
使用繼承 tf.keras.Model 類創建的 Keras 模型 model ,使用 SavedModel 載入後將沒法使用 model() 直接進行推斷,而須要使用 model.call()
import tensorflow as tf
from zh.model.utils import MNISTLoader

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)
tf.saved_model.save(model, "saved/1")
MNIST 手寫體識別的模型 進行導出
import tensorflow as tf
from zh.model.utils import MNISTLoader

batch_size = 50

model = tf.saved_model.load("saved/1")
data_loader = MNISTLoader()
sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
num_batches = int(data_loader.num_test_data // batch_size)
for batch_index in range(num_batches):
    start_index, end_index = batch_index * batch_size, (batch_index + 1) * batch_size
    y_pred = model(data_loader.test_data[start_index: end_index])
    sparse_categorical_accuracy.update_state(y_true=data_loader.test_label[start_index: end_index], y_pred=y_pred)
print("test accuracy: %f" % sparse_categorical_accuracy.result())
MNIST 手寫體識別的模型 進行導入並測試
 
class MLP(tf.keras.Model):
    def __init__(self):
        super().__init__()
        self.flatten = tf.keras.layers.Flatten()
        self.dense1 = tf.keras.layers.Dense(units=100, activation=tf.nn.relu)
        self.dense2 = tf.keras.layers.Dense(units=10)

    @tf.function
    def call(self, inputs):         # [batch_size, 28, 28, 1]
        x = self.flatten(inputs)    # [batch_size, 784]
        x = self.dense1(x)          # [batch_size, 100]
        x = self.dense2(x)          # [batch_size, 10]
        output = tf.nn.softmax(x)
        return output

model = MLP()
使用繼承 tf.keras.Model 類創建的 Keras 模型一樣能夠以相同方法導出,惟須注意 call 方法須要以 @tf.function 修飾,以轉化爲 SavedModel 支持的計算圖
y_pred = model.call(data_loader.test_data[start_index: end_index])
模型導入並測試性能的過程也相同,惟須注意模型推斷時須要顯式調用 call 方法

Keras Sequential save 方法

keras 官方的 mnist 模型訓練樣例html

是基於 keras 的 Sequential 構建了多層的卷積神經網絡,並進行訓練java

curl -LO https://raw.githubusercontent.com/keras-team/keras/master/examples/mnist_cnn.py
使用以下命令拷貝到本地:
model.save('mnist_cnn.h5')
對 keras 訓練完畢的模型進行保存
python mnist_cnn.py
在終端中執行 mnist_cnn.py 文件

執行過程會比較久,執行結束後,會在當前目錄產生 mnist_cnn.h5 文件(HDF5 格式),就是 keras 訓練後的模型,其中已經包含了訓練後的模型結構和權重等信息。node

在服務器端,能夠直接經過 keras.models.load_model("mnist_cnn.h5") 加載,而後進行推理;在移動設備須要將 HDF5 模型文件轉換爲 TensorFlow Lite 的格式,而後經過相應平臺的 Interpreter 加載,而後進行推理。python

 

TensorFlow Serving(服務器端部署模型)

安裝

# 添加Google的TensorFlow Serving源
echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | sudo tee /etc/apt/sources.list.d/tensorflow-serving.list
# 添加gpg key
curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | sudo apt-key add -
設置安裝源
sudo apt-get update
sudo apt-get install tensorflow-model-server
使用 apt-get 安裝 TensorFlow Serving
curl 設置代理的方式爲 -x 選項或設置 http_proxy 環境變量,即

export http_proxy=http://代理服務器IP:端口

或

curl -x http://代理服務器IP:端口 URL
apt-get 設置代理的方式爲 -o 選項,即

sudo apt-get -o Acquire::http::proxy="http://代理服務器IP:端口" ...
可能須要設置代理

模型部署

tensorflow_model_server \
    --rest_api_port=端口號(如8501) \
    --model_name=模型名 \
    --model_base_path="SavedModel格式模型的文件夾絕對地址(不含版本號)"
直接讀取 SavedModel 格式的模型進行部署

支持熱更新模型,其典型的模型文件夾結構以下:android

/saved_model_files /1 # 版本號爲1的模型文件 /assets /variables saved_model.pb ... /N # 版本號爲N的模型文件 /assets /variables saved_model.pb 

上面 1~N 的子文件夾表明不一樣版本號的模型。當指定 --model_base_path 時,只須要指定根目錄的 絕對地址 (不是相對地址)便可。例如,若是上述文件夾結構存放在 home/snowkylin 文件夾內,則 --model_base_path 應當設置爲 home/snowkylin/saved_model_files (不附帶模型版本號)。TensorFlow Serving 會自動選擇版本號最大的模型進行載入。git

 
tensorflow_model_server \
    --rest_api_port=8501 \
    --model_name=MLP \
    --model_base_path="/home/.../.../saved"  # 文件夾絕對地址根據自身狀況填寫,無需加入版本號
Keras Sequential 模式模型的部署
Sequential 模式的輸入和輸出都很固定,所以這種類型的模型很容易部署,無需其餘額外操做。例如,要將 前文使用 SavedModel 導出的 MNIST 手寫體識別模型 (使用 Keras Sequential 模式創建)以 MLP 的模型名在 8501 端口進行部署,能夠直接使用以上命令
 
class MLP(tf.keras.Model):
    ...

    @tf.function(input_signature=[tf.TensorSpec([None, 28, 28, 1], tf.float32)])
    def call(self, inputs):
        ...
自定義 Keras 模型的部署-導出到 SavedModel 格式
不只須要使用 @tf.function 修飾,還要在修飾時指定 input_signature 參數,以顯式說明輸入的形狀。該參數傳入一個由 tf.TensorSpec 組成的列表,指定每一個輸入張量的形狀和類型
例如,對於 MNIST 手寫體數字識別,咱們的輸入是一個 [None, 28, 28, 1] 的四維張量( None表示第一維即 Batch Size 的大小不固定),此時咱們能夠將模型的 call 方法作出上面的修飾
model = MLP()
...
tf.saved_model.save(model, "saved_with_signature/1", signatures={"call": model.call})
自定義 Keras 模型的部署-使用 tf.saved_model.save 導出
將模型使用 tf.saved_model.save 導出時,須要經過 signature 參數提供待導出的函數的簽名(Signature)
須要告訴 TensorFlow Serving 每一個方法在被客戶端調用時分別叫作什麼名字。例如,若是咱們但願客戶端在調用模型時使用 call 這一簽名來調用 model.call方法時,咱們能夠在導出時傳入 signature 參數,以 dict 的鍵值對形式告知導出的方法對應的簽名
tensorflow_model_server \
    --rest_api_port=8501 \
    --model_name=MLP \
    --model_base_path="/home/.../.../saved_with_signature"  # 修改成本身模型的絕對地址
兩步均完成後,便可使用如下命令部署

 

 

 在客戶端調用以 TensorFlow Serving 部署的模型github

支持以 gRPC 和 RESTful API 調用以 TensorFlow Serving 部署的模型。這裏主要介紹較爲通用的 RESTful API 方法。 npm

RESTful API 以標準的 HTTP POST 方法進行交互,請求和回覆均爲 JSON 對象。爲了調用服務器端的模型,咱們在客戶端向服務器發送如下格式的請求:json

服務器 URI: http://服務器地址:端口號/v1/models/模型名:predict

請求內容:

{
    "signature_name": "須要調用的函數簽名(Sequential模式不須要)", "instances": 輸入數據 } 

回覆爲:

{
    "predictions": 返回值 }

 

import json
import numpy as np
import requests
from zh.model.utils import MNISTLoader


data_loader = MNISTLoader()
data = json.dumps({
    "instances": data_loader.test_data[0:3].tolist()
    })
headers = {"content-type": "application/json"}
json_response = requests.post(
    'http://localhost:8501/v1/models/MLP:predict',
    data=data, headers=headers)
predictions = np.array(json.loads(json_response.text)['predictions'])
print(np.argmax(predictions, axis=-1))
print(data_loader.test_label[0:10])
Python 客戶端示例
向本機的 TensorFlow Serving 服務器發送 MNIST 測試集的前 10 幅圖像並返回預測結果,同時與測試集的真實標籤進行比較。
import json
import numpy as np
import requests
from zh.model.utils import MNISTLoader


data_loader = MNISTLoader()



data = json.dumps({
    "signature_name": "call",
    "instances": data_loader.test_data[0:10].tolist()
    })


headers = {"content-type": "application/json"}
json_response = requests.post(
    'http://localhost:8501/v1/models/MLP:predict',
    data=data, headers=headers)
predictions = np.array(json.loads(json_response.text)['predictions'])
print(np.argmax(predictions, axis=-1))
print(data_loader.test_label[0:10])
對於自定義的 Keras 模型,在發送的數據中加入 signature_name 鍵值便可

 

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

const url = 'http://localhost:8501/v1/models/MLP: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')
Node.js 客戶端示例
使用 Node.js 將下圖轉換爲 28*28 的灰度圖,發送給本機的 TensorFlow Serving 服務器,並輸出返回的預測值和機率。(其中使用了 圖像處理庫 jimp 和 HTTP 庫 superagent ,可以使用 npm install jimp 和 npm install superagent 安裝)

 

 

 

 

 

 

 

TensorFlow Lite(移動端部署模型)

目前 TFLite 只提供了推理功能,在服務器端進行訓練後,通過以下簡單處理便可部署到邊緣設備上。

模型轉換

模型轉換:因爲邊緣設備計算等資源有限,使用 TensorFlow 訓練好的模型,模型太大、運行效率比較低,不能直接在移動端部署,須要經過相應工具進行轉換成適合邊緣設備的格式。

轉換方式有兩種:Float 格式和 Quantized 格式

針對 Float 格式的,先使用命令行工具 tflite_convert,在終端執行以下命令:

tflite_convert -h
usage: tflite_convert [-h] --output_file OUTPUT_FILE
                      (--saved_model_dir SAVED_MODEL_DIR | --keras_model_file KERAS_MODEL_FILE)
  --output_file OUTPUT_FILE
                        Full filepath of the output file.
  --saved_model_dir SAVED_MODEL_DIR
                        Full path of the directory containing the SavedModel.
  --keras_model_file KERAS_MODEL_FILE
                        Full filepath of HDF5 file containing tf.Keras model.
命令的使用方法

TF2.0 支持兩種模型導出方法和格式 SavedModel 和 Keras Sequential。

tflite_convert --saved_model_dir=saved/1 --output_file=mnist_savedmodel.tflite
SavedModel 導出模型轉換
tflite_convert --keras_model_file=mnist_cnn.h5 --output_file=mnist_sequential.tflite
Keras Sequential 導出模型轉換
到此,已經獲得兩個 TensorFlow Lite 模型

Android 部署

邊緣設備部署:本節以 android 爲例,簡單介紹如何在 android 應用中部署轉化後的模型,完成 Mnist 圖片的識別。

國內的讀者,由於獲取 SDK 和 gradle 編譯環境等資源,須要先給 Android Studio 配置 proxy 或者使用國內的鏡像。 
buildscript {

    repositories {
        maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' }
        maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.1'
    }
}

allprojects {
    repositories {
        maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' }
        maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' }
    }
}
配置 build.gradle
將 build.gradle 中的 maven 源 google() 和 jcenter() 分別替換爲國內鏡像
android {
    aaptOptions {
        noCompress "tflite" // 編譯apk時,不壓縮tflite文件
    }
}

dependencies {
    implementation 'org.tensorflow:tensorflow-lite:1.14.0'
}
配置 app/build.gradle
新建一個 Android Project,打開 app/build.gradle 添加信息

其中,

  1. aaptOptions 設置 tflite 文件不壓縮,確保後面 tflite 文件能夠被 Interpreter 正確加載。

  2. org.tensorflow:tensorflow-lite 的最新版本號能夠在這裏查詢 https://bintray.com/google/tensorflow/tensorflow-lite

 

 

設置好後,sync 和 build 整個工程,若是 build 成功說明,配置成功。

添加 tflite 文件到 assets 文件夾

在 app 目錄先新建 assets 目錄,並將 mnist_savedmodel.tflite 文件保存到 assets 目錄。從新編譯 apk,檢查新編譯出來的 apk 的 assets 文件夾是否有 mnist_cnn.tflite 文件。

點擊菜單 Build->Build APK (s) 觸發 apk 編譯,apk 編譯成功點擊右下角的 EventLog。點擊最後一條信息中的 analyze 連接,會觸發 apk analyzer 查看新編譯出來的 apk,若在 assets 目錄下存在 mnist_savedmodel.tflite ,則編譯打包成功,以下:

assets
     |__mnist_savedmodel.tflite
 
/** Memory-map the model file in Assets. */
private MappedByteBuffer loadModelFile(Activity activity) throws IOException {
    AssetFileDescriptor fileDescriptor = activity.getAssets().openFd(mModelPath);
    FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
    FileChannel fileChannel = inputStream.getChannel();
    long startOffset = fileDescriptor.getStartOffset();
    long declaredLength = fileDescriptor.getDeclaredLength();
    return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
}
加載模型
使用如上函數將 mnist_savedmodel.tflite 文件加載到 memory-map 中,做爲 Interpreter 實例化的輸入
mTFLite = new Interpreter(loadModelFile(activity));
實例化 Interpreter,其中 acitivity 是爲了從 assets 中獲取模型
由於把模型編譯到 assets 中,只能經過 getAssets() 打開。
memory-map 後的 MappedByteBuffer 直接做爲 Interpreter 的輸入, mTFLite ( Interpreter )就是轉換後模型的運行載體。
 
//Float模型相關參數
// com/dpthinker/mnistclassifier/model/FloatSavedModelConfig.java
protected void setConfigs() {
    setModelName("mnist_savedmodel.tflite");

    setNumBytesPerChannel(4);

    setDimBatchSize(1);
    setDimPixelSize(1);

    setDimImgWeight(28);
    setDimImgHeight(28);

    setImageMean(0);
    setImageSTD(255.0f);
}

// 初始化
// com/dpthinker/mnistclassifier/classifier/BaseClassifier.java
private void initConfig(BaseModelConfig config) {
    this.mModelConfig = config;
    this.mNumBytesPerChannel = config.getNumBytesPerChannel();
    this.mDimBatchSize = config.getDimBatchSize();
    this.mDimPixelSize = config.getDimPixelSize();
    this.mDimImgWidth = config.getDimImgWeight();
    this.mDimImgHeight = config.getDimImgHeight();
    this.mModelPath = config.getModelName();
}
運行輸入

使用 MNIST test 測試集中的圖片做爲輸入,mnist 圖像大小 28*28,單像素

// 將輸入的Bitmap轉化爲Interpreter能夠識別的ByteBuffer
// com/dpthinker/mnistclassifier/classifier/BaseClassifier.java
protected ByteBuffer convertBitmapToByteBuffer(Bitmap bitmap) {
    int[] intValues = new int[mDimImgWidth * mDimImgHeight];
    scaleBitmap(bitmap).getPixels(intValues,
            0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());

    ByteBuffer imgData = ByteBuffer.allocateDirect(
            mNumBytesPerChannel * mDimBatchSize * mDimImgWidth * mDimImgHeight * mDimPixelSize);
    imgData.order(ByteOrder.nativeOrder());
    imgData.rewind();

    // Convert the image toFloating point.
    int pixel = 0;
    for (int i = 0; i < mDimImgWidth; ++i) {
        for (int j = 0; j < mDimImgHeight; ++j) {
            //final int val = intValues[pixel++];
            int val = intValues[pixel++];
            mModelConfig.addImgValue(imgData, val); //添加把Pixel數值轉化並添加到ByteBuffer
        }
    }
    return imgData;
}

// mModelConfig.addImgValue定義
// com/dpthinker/mnistclassifier/model/FloatSavedModelConfig.java
public void addImgValue(ByteBuffer imgData, int val) {
    imgData.putFloat(((val & 0xFF) - getImageMean()) / getImageSTD());
}
將 MNIST 圖片轉化成 ByteBuffer ,並保持到 imgData ( ByteBuffer )中
convertBitmapToByteBuffer 的輸出即爲模型運行的輸入。
 
privateFloat[][] mLabelProbArray = newFloat[1][10];
運行輸出

定義一個 1*10 的多維數組,由於咱們只有 10 個 label

運行結束後,每一個二級元素都是一個 label 的機率。

mTFLite.run(imgData, mLabelProbArray);
運行及結果處理
針對某個圖片,運行後 mLabelProbArray 的內容就是各個 label 識別的機率。對他們進行排序,找出 Top 的 label 並界面呈現給用戶.
使用了 View.OnClickListener() 觸發 "image/*" 類型的 Intent.ACTION_GET_CONTENT ,進而獲取設備上的圖片(只支持 MNIST 標準圖片)。而後,經過 RadioButtion 的選擇狀況,確認加載哪一種轉換後的模型,並觸發真正分類操做。

Quantization 模型轉換

在 TF1.0 上,可使用命令行工具轉換 Quantized 模型。在筆者嘗試的狀況看在 TF2.0 上,命令行工具目前只能轉換爲 Float 模型,Python API 只能轉換爲 Quantized 模型。

import tensorflow as tf

converter = tf.lite.TFLiteConverter.from_saved_model('saved/1')
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_quant_model = converter.convert()
open("mnist_savedmodel_quantized.tflite", "wb").write(tflite_quant_model)
Python API 轉換方法
最終轉換後的 Quantized 模型即爲同級目錄下的 mnist_savedmodel_quantized.tflite

在 TF2.0 上,提供了新的一步到位的工具 visualize.py ,直接轉換爲 html 文件,除了模型結構,還有更清晰的關鍵信息總結。

 visualize.py 目前看應該仍是開發階段,使用前須要先從 github 下載最新的 TensorFlow 和 FlatBuffers 源碼,而且二者要在同一目錄,由於 visualize.py 源碼中是按二者在同一目錄寫的調用路徑。

 

git clone git@github.com:tensorflow/tensorflow.git
下載 TensorFlow:
git clone git@github.com:google/flatbuffers.git
下載 FlatBuffers:

編譯 FlatBuffers:(筆者使用的 Mac,其餘平臺請你們自行配置,應該不麻煩)

  1. 下載 cmake:執行 brew install cmake

  2. 設置編譯環境:在 FlatBuffers 的根目錄,執行 cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release

  3. 編譯:在 FlatBuffers 的根目錄,執行 make

編譯完成後,會在跟目錄生成 flatc,這個可執行文件是 visualize.py 運行所依賴的。

python visualize.py mnist_savedmodel_quantized.tflite mnist_savedmodel_quantized.html
visualize.py 使用方法
在 tensorflow/tensorflow/lite/tools 目錄下執行

跟 Float 模型對比,Input/Output 格式是一致的,因此能夠複用 Float 模型 Android 部署過程當中的配置。 

// Quantized模型相關參數
// com/dpthinker/mnistclassifier/model/QuantSavedModelConfig.java
public class QuantSavedModelConfig extends BaseModelConfig {
    @Override
    protected void setConfigs() {
        setModelName("mnist_savedmodel_quantized.tflite");

        setNumBytesPerChannel(4);

        setDimBatchSize(1);
        setDimPixelSize(1);

        setDimImgWeight(28);
        setDimImgHeight(28);

        setImageMean(0);
        setImageSTD(255.0f);
    }

    @Override
    public void addImgValue(ByteBuffer imgData, int val) {
        imgData.putFloat(((val & 0xFF) - getImageMean()) / getImageSTD());
    }
}
具體配置
本節 Android 相關代碼存放路徑:https://github.com/snowkylin/tensorflow-handbook/tree/master/source/android

 

 

 

TensorFlow.js

TensorFlow 的 JavaScript 版本,支持 GPU 硬件加速,能夠運行在 Node.js 或瀏覽器環境中。它不但支持徹底基於 JavaScript 從頭開發、訓練和部署模型,也能夠用來運行已有的 Python 版 TensorFlow 模型,或者基於現有的模型進行繼續訓練。

基於 TensorFlow.js 1.0,向你們簡單地介紹如何基於 ES6 的 JavaScript 進行 TensorFlow.js 的開發

相關代碼,使用說明,和訓練好的模型文件及參數,均可以在做者的 GitHub 上找到。地址: https://github.com/huan/tensorflow-handbook-javascript

瀏覽器中進行機器學習,相對比與服務器端來說,將擁有如下四大優點:

  • 不須要安裝軟件或驅動(打開瀏覽器便可使用);

  • 能夠經過瀏覽器進行更加方便的人機交互;

  • 能夠經過手機瀏覽器,調用手機硬件的各類傳感器(如:GPS、電子羅盤、加速度傳感器、攝像頭等);

  • 用戶的數據能夠無需上傳到服務器,在本地便可完成所需操做。

Move Mirror 地址:https://experiments.withgoogle.com/move-mirror

Move Mirror 所使用的 PoseNet 地址:https://github.com/tensorflow/tfjs-models/tree/master/posenet

環境配置

在瀏覽器中使用 TensorFlow.js

<html>
<head>
    <script src="http://unpkg.com/@tensorflow/tfjs/dist/tf.min.js"></script>
在 HTML 中直接引用 TensorFlow.js 發佈的 NPM 包中已經打包安裝好的 JavaScript 代碼。

在 Node.js 中使用 TensorFlow.js

首先須要按照 NodeJS.org 官網的說明,完成安裝最新版本的 Node.js 。而後,完成如下四個步驟便可完成配置:

$ node --verion
v10.5.0

$ npm --version
6.4.1
確認 Node.js 版本(v10 或更新的版本)
$ mkdir tfjs
$ cd tfjs
創建 TensorFlow.js 項目目錄
# 初始化項目管理文件 package.json
$ npm init -y

# 安裝 tfjs 庫,純 JavaScript 版本
$ npm install @tensorflow/tfjs

# 安裝 tfjs-node 庫,C Binding 版本
$ npm install @tensorflow/tfjs-node

# 安裝 tfjs-node-gpu 庫,支持 CUDA GPU 加速
$ npm install @tensorflow/tfjs-node-gpu
安裝 TensorFlow.js:
$ node
> require('@tensorflow/tfjs').version
{
    'tfjs-core': '1.3.1',
    'tfjs-data': '1.3.1',
    'tfjs-layers': '1.3.1',
    'tfjs-converter': '1.3.1',
    tfjs: '1.3.1'
}
>
確認 Node.js 和 TensorFlow.js 工做正常
若是你看到了上面的 tfjs-coretfjs-datatfjs-layers 和 tfjs-converter 的輸出信息,那麼就說明環境配置沒有問題了。
import * as tf from '@tensorflow/tfjs'
console.log(tf.version.tfjs)
// Output: 1.3.1
在 JavaScript 程序中,經過如下指令,便可引入 TensorFlow.j
import 是 JavaScript ES6 版本新開始擁有的新特性。粗略能夠認爲等價於 require。好比:import as tf from '@tensorflow/tfjs' 和 const tf require('@tensorflow/tfjs') 對上面的示例代碼是等價的。

在微信小程序中使用 TensorFlow.js

首先要在小程序管理後臺的 「設置 - 第三方服務 - 插件管理」 中添加插件。開發者可登陸小程序管理後臺,經過 appid _wx6afed118d9e81df9_ 查找插件並添加。本插件無需申請,添加後可直接使用。

例子能夠看 TFJS Mobilenet: 物體識別小程序

TensorFlow.js 微信小程序官方文檔地址

有興趣的讀者能夠前往 NEXT 學院,進行後續深度學習。課程地址:https://ke.qq.com/course/428263

模型部署

經過 TensorFlow.js 加載 Python 模型

$ pip install tensorflowjs
安裝 tensorflowjs_converter

使用細節,能夠經過 --help 參數查看程序幫助:

$ tensorflowjs_converter --help

以 MobilenetV1 爲例,看一下如何對模型文件進行轉換操做,並將能夠被 TensorFlow.js 加載的模型文件,存放到 /mobilenet/tfjs_model 目錄下。

tensorflowjs_converter \
    --input_format=tf_saved_model \
    --output_node_names='MobilenetV1/Predictions/Reshape_1' \
    --saved_model_tags=serve \
    /mobilenet/saved_model \
    /mobilenet/tfjs_model
轉換 SavedModel:將 /mobilenet/saved_model 轉換到 /mobilenet/tfjs_model

轉換完成的模型,保存爲了兩類文件:

  • model.json:模型架構

  • group1-shard*of*:模型參數

舉例來講,咱們對 MobileNet v2 轉換出來的文件,以下:

/mobilenet/tfjs_model/model.json /mobilenet/tfjs_model/group1-shard1of5 … /mobilenet/tfjs_model/group1-shard5of5

$ npm install @tensorflow/tfjs
爲了加載轉換完成的模型文件,咱們須要安裝 tfjs-converter 和 @tensorflow/tfjs 模塊:
import * as tf from '@tensorflow/tfjs'

const MODEL_URL = '/mobilenet/tfjs_model/model.json'

const model = await tf.loadGraphModel(MODEL_URL)

const cat = document.getElementById('cat')
model.execute(tf.browser.fromPixels(cat))
而後,咱們就能夠經過 JavaScript 來加載 TensorFlow 模型了

使用 TensorFlow.js 模型庫

模型庫 GitHub 地址:https://github.com/tensorflow/tfjs-models,其中模型分類包括圖像識別、語音識別、人體姿態識別、物體識別、文字分類等。

在程序內使用模型 API 時要提供 modelUrl 的參數,能夠指向谷歌中國的鏡像服務器。

谷歌雲的 base url 是 https://storage.googleapis.com

中國鏡像的 base url 是 https://www.gstaticcnapps.cn 

模型的 url path 是一致的。以 posenet 模型爲例:

  • 谷歌雲地址是:https://storage.googleapis.com/tfjs-models/savedmodel/posenet/mobilenet/float/050/model-stride16.json

  • 中國鏡像地址是:https://www.gstaticcnapps.cn/tfjs-models/savedmodel/posenet/mobilenet/float/050/model-stride16.json

在瀏覽器中使用 MobileNet 進行攝像頭物體識別

<head>
    <script src="https://unpkg.com/@tensorflow/tfjs"></script>
    <script src="https://unpkg.com/@tensorflow-models/mobilenet"> </script>
</head>
咱們創建一個 HTML 文件,在頭信息中,經過將 NPM 模塊轉換爲在線能夠引用的免費服務 unpkg.com,來加載 @tensorflow/tfjs 和 @tensorflow-models/mobilenet 兩個 TFJS 模塊
<video width=400 height=300></video>
<p></p>
<img width=400 height=300 />
咱們聲明三個 HTML 元素:用來顯示視頻的<video>,用來顯示咱們截取特定幀的 <img>,和用來顯示檢測文字結果的 <p>
 const video = document.querySelector('video')
    const image = document.querySelector('img')
    const status = document.querySelector("p")

    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')

    let model
咱們經過 JavaScript ,將對應的 HTML 元素進行初始化:video, image, status 三個變量分別用來對應 <video><img><p> 三個 HTML 元素,canvas 和 ctx 用來作從攝像頭獲取視頻流數據的中轉存儲。model 將用來存儲咱們從網絡上加載的 MobileNet:
async function main () {
        status.innerText = "Model loading..."
        model = await mobilenet.load()
        status.innerText = "Model is loaded!"

        const stream = await navigator.mediaDevices.getUserMedia({ video: true })
        video.srcObject = stream
        await video.play()
        
        canvas.width = video.videoWidth
        canvas.height = video.videoHeight

        refresh()
    }
main() 用來初始化整個系統,完成加載 MobileNet 模型,將用戶攝像頭的數據綁定 <video> 這個 HTML 元素上,最後觸發 refresh() 函數,進行按期刷新操做
async function refresh(){
        ctx.drawImage(video, 0,0)
        image.src = canvas.toDataURL('image/png')
        
        await model.load()
        const predictions = await model.classify(image)
        
        const className = predictions[0].className
        const percentage = Math.floor(100 * predictions[0].probability)
        
        status.innerHTML = percentage + '%' + ' ' + className
        
        setTimeout(refresh, 100)
    }
refresh() 函數,用來從視頻中取出當前一幀圖像,而後經過 MobileNet 模型進行分類,並將分類結果,顯示在網頁上。而後,經過 setTimeout,重複執行本身,實現持續對視頻圖像進行處理的功能
 
<html>

<head>
    <script src="https://unpkg.com/@tensorflow/tfjs"></script>
    <script src="https://unpkg.com/@tensorflow-models/mobilenet"> </script>
</head>

<video width=400 height=300></video>
<p></p>
<img width=400 height=300 />

<script>
    const video = document.querySelector('video')
    const image = document.querySelector('img')
    const status = document.querySelector("p")

    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')

    let model

    main()

    async function main () {
        status.innerText = "Model loading..."
        model = await mobilenet.load()
        status.innerText = "Model is loaded!"

        const stream = await navigator.mediaDevices.getUserMedia({ video: true })
        video.srcObject = stream
        await video.play()
        
        canvas.width = video.videoWidth
        canvas.height = video.videoHeight

        refresh()
    }

    async function refresh(){
        ctx.drawImage(video, 0,0)
        image.src = canvas.toDataURL('image/png')
        
        await model.load()
        const predictions = await model.classify(image)
        
        const className = predictions[0].className
        const percentage = Math.floor(100 * predictions[0].probability)
        
        status.innerHTML = percentage + '%' + ' ' + className
        
        setTimeout(refresh, 100)
    }

</script>

</html>
完整的 HTML 代碼

 

 

TensorFlow.js 模型訓練 *

與 TensorFlow Serving 和 TensorFlow Lite 不一樣,TensorFlow.js 不只支持模型的部署和推斷,還支持直接在 TensorFlow.js 中進行模型訓練、

基礎章節中,咱們已經用 Python 實現過,針對某城市在 2013-2017 年的房價的任務,經過對該數據進行線性迴歸,即便用線性模型 y = ax + b 來擬合上述數據,此處 a 和 b 是待求的參數。

下面咱們改用 TensorFlow.js 來實現一個 JavaScript 版本。

const xsRaw = tf.tensor([2013, 2014, 2015, 2016, 2017])
    const ysRaw = tf.tensor([12000, 14000, 15000, 16500, 17500])

    // 歸一化
    const xs = xsRaw.sub(xsRaw.min())
                    .div(xsRaw.max().sub(xsRaw.min()))
    const ys = ysRaw.sub(ysRaw.min())
                    .div(ysRaw.max().sub(ysRaw.min()))
首先,咱們定義數據,進行基本的歸一化操做。
const a = tf.scalar(Math.random()).variable()
    const b = tf.scalar(Math.random()).variable()

    // y = a * x + b.
    const f = (x) => a.mul(x).add(b)
    const loss = (pred, label) => pred.sub(label).square().mean()

    const learningRate = 1e-3
    const optimizer = tf.train.sgd(learningRate)

    // 訓練模型
    for (let i = 0; i < 10000; i++) {
        optimizer.minimize(() => loss(f(xs), ys))
    }

    // 預測
    console.log(`a: ${a.dataSync()}, b: ${b.dataSync()}`)
    const preds = f(xs).dataSync()
    const trues = ys.arraySync()
    preds.forEach((pred, i) => {
        console.log(`x: ${i}, pred: ${pred.toFixed(2)}, true: ${trues[i].toFixed(2)}`)
    })
接下來,咱們來求線性模型中兩個參數 a 和 b 的值。
使用 loss() 計算損失; 使用 optimizer.minimize() 自動更新模型參數。
使用箭頭函數(=>)來簡化函數的聲明和書寫
dataSync()同步函數
支持 tf.sub(a, b) 和 a.sub(b) 兩種方法的數學函數調用。其效果是等價的

 

<html>
<head>
    <script src="http://unpkg.com/@tensorflow/tfjs/dist/tf.min.js"></script>
    <script>
    const xsRaw = tf.tensor([2013, 2014, 2015, 2016, 2017])
    const ysRaw = tf.tensor([12000, 14000, 15000, 16500, 17500])

    // 歸一化
    const xs = xsRaw.sub(xsRaw.min())
                    .div(xsRaw.max().sub(xsRaw.min()))
    const ys = ysRaw.sub(ysRaw.min())
                    .div(ysRaw.max().sub(ysRaw.min()))

    const a = tf.scalar(Math.random()).variable()
    const b = tf.scalar(Math.random()).variable()

    // y = a * x + b.
    const f = (x) => a.mul(x).add(b)
    const loss = (pred, label) => pred.sub(label).square().mean()

    const learningRate = 1e-3
    const optimizer = tf.train.sgd(learningRate)

    // 訓練模型
    for (let i = 0; i < 10000; i++) {
        optimizer.minimize(() => loss(f(xs), ys))
    }

    // 預測
    console.log(`a: ${a.dataSync()}, b: ${b.dataSync()}`)
    const preds = f(xs).dataSync()
    const trues = ys.arraySync()
    preds.forEach((pred, i) => {
        console.log(`x: ${i}, pred: ${pred.toFixed(2)}, true: ${trues[i].toFixed(2)}`)
    })
    </script>
</head>
</html>
能夠直接在瀏覽器中運行,完整的 HTML 代碼

TensorFlow.js 性能對比

基於 MobileNet 的評測

與 TensorFlow Lite 代碼基準相比,手機瀏覽器中的 TensorFlow.js 在 IPhoneX 上的運行時間爲基準的 1.2 倍,在 Pixel3 上運行的時間爲基準的 1.8 倍。

與 Python 代碼基準相比,瀏覽器中的 TensorFlow.js 在 CPU 上的運行時間爲基準的 1.7 倍,在 GPU (WebGL) 上運行的時間爲基準的 3.8 倍。

與 Python 代碼基準相比,Node.js 的 TensorFlow.js 在 CPU 上的運行時間與基準相同,在 GPU(CUDA) 上運行的時間是基準的 1.6 倍。

 

 

TensorFlow 分佈式訓練

TensorFlow 在 tf.distribute.Strategy 中爲咱們提供了若干種分佈式策略,使得咱們可以更高效地訓練模型。

單機多卡訓練: MirroredStrategy

strategy = tf.distribute.MirroredStrategy()
實例化一個 MirroredStrategy 策略:

能夠在參數中指定設備,如:

strategy = tf.distribute.MirroredStrategy(devices=["/gpu:0", "/gpu:1"]) 

即指定只使用第 0、1 號 GPU 參與分佈式策略。

with strategy.scope():
    # 模型構建代碼
將模型構建的代碼放入 strategy.scope() 的上下文環境中

 

import tensorflow as tf
import tensorflow_datasets as tfds

num_epochs = 5
batch_size_per_replica = 64
learning_rate = 0.001

strategy = tf.distribute.MirroredStrategy()
print('Number of devices: %d' % strategy.num_replicas_in_sync)  # 輸出設備數量
batch_size = batch_size_per_replica * strategy.num_replicas_in_sync

# 載入數據集並預處理
def resize(image, label):
    image = tf.image.resize(image, [224, 224]) / 255.0
    return image, label

# 使用 TensorFlow Datasets 載入貓狗分類數據集,詳見「TensorFlow Datasets數據集載入」一章
dataset = tfds.load("cats_vs_dogs", split=tfds.Split.TRAIN, as_supervised=True)
dataset = dataset.map(resize).shuffle(1024).batch(batch_size)

with strategy.scope():
    model = tf.keras.applications.MobileNetV2()
    model.compile(
        optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate),
        loss=tf.keras.losses.sparse_categorical_crossentropy,
        metrics=[tf.keras.metrics.sparse_categorical_accuracy]
    )

model.fit(dataset, epochs=num_epochs)
在 TensorFlow Datasets 中的部分圖像數據集上使用 Keras 訓練 MobileNetV2 的過程

多機訓練: MultiWorkerMirroredStrategy

將 MirroredStrategy 更換爲適合多機訓練的 MultiWorkerMirroredStrategy 便可。不過,因爲涉及到多臺計算機之間的通信,還須要進行一些額外的設置。具體而言,須要設置環境變量 TF_CONFIG

os.environ['TF_CONFIG'] = json.dumps({
    'cluster': {
        'worker': ["localhost:20000", "localhost:20001"]
    },
    'task': {'type': 'worker', 'index': 0}
})
示例

TF_CONFIG 由 cluster 和 task 兩部分組成:

  • cluster 說明了整個多機集羣的結構和每臺機器的網絡地址(IP + 端口號)。對於每一臺機器,cluster 的值都是相同的;

  • task 說明了當前機器的角色。例如, {'type': 'worker', 'index': 0} 說明當前機器是 cluster 中的第 0 個 worker(即 localhost:20000 )。每一臺機器的 task 值都須要針對當前主機進行分別的設置。

請在各臺機器上均注意防火牆的設置,尤爲是須要開放與其餘主機通訊的端口。如上例的 0 號 worker 須要開放 20000 端口,1 號 worker 須要開放 20001 端口。

import tensorflow as tf
import tensorflow_datasets as tfds
import os
import json

num_epochs = 5
batch_size_per_replica = 64
learning_rate = 0.001

num_workers = 2
os.environ['TF_CONFIG'] = json.dumps({
    'cluster': {
        'worker': ["localhost:20000", "localhost:20001"]
    },
    'task': {'type': 'worker', 'index': 0}
})
strategy = tf.distribute.experimental.MultiWorkerMirroredStrategy()
batch_size = batch_size_per_replica * num_workers

def resize(image, label):
    image = tf.image.resize(image, [224, 224]) / 255.0
    return image, label

dataset = tfds.load("cats_vs_dogs", split=tfds.Split.TRAIN, as_supervised=True)
dataset = dataset.map(resize).shuffle(1024).batch(batch_size)

with strategy.scope():
    model = tf.keras.applications.MobileNetV2()
    model.compile(
        optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate),
        loss=tf.keras.losses.sparse_categorical_crossentropy,
        metrics=[tf.keras.metrics.sparse_categorical_accuracy]
    )

model.fit(dataset, epochs=num_epochs)
假設咱們有兩臺機器,即首先在兩臺機器上均部署下面的程序,惟一的區別是 task 部分,第一臺機器設置爲 {'type': 'worker', 'index': 0} ,第二臺機器設置爲 {'type': 'worker', 'index': 1} 。接下來,在兩臺機器上依次運行程序,待通信成功後,即會自動開始訓練流程。

 

在全部機器性能接近的狀況下,訓練時長與機器的數目接近於反比關係。

 

 

 

使用TPU訓練TensorFlow模型

TPU 表明 Tensor Processing Unit (張量處理單元)

免費 TPU:Google Colab

最方便使用 TPU 的方法,就是使用 Google 的 Colab ,不但經過瀏覽器訪問直接能夠用,並且還免費。

在 Google Colab 的 Notebook 界面中,打開界面中,打開主菜單 Runtime ,而後選擇 Change runtime type,會彈出 Notebook settings 的窗口。選擇裏面的 Hardware accelerator爲 TPU 就能夠了。

 

import os
import pprint
import tensorflow as tf

if 'COLAB_TPU_ADDR' not in os.environ:
    print('ERROR: Not connected to a TPU runtime')
else:
    tpu_address = 'grpc://' + os.environ['COLAB_TPU_ADDR']
    print ('TPU address is', tpu_address)

    with tf.Session(tpu_address) as session:
      devices = session.list_devices()

    print('TPU devices:')
    pprint.pprint(devices)
確認 Colab Notebook 中的確分配了 TPU 資源,咱們能夠運行如下測試代碼
若是輸出 ERROR 信息,則表示目前的 Runetime 並無分配到 TPU;若是輸出 TPU 地址及設備列表,則表示 Colab 已經分配了 TPU。

Cloud TPU

在 Google Cloud 上,咱們能夠購買所需的 TPU 資源,用來按需進行機器學習訓練。爲了使用 Cloud TPU ,須要在 Google Cloud Engine 中啓動 VM 併爲 VM 請求 Cloud TPU 資源。請求完成後,VM 就能夠直接訪問分配給它專屬的 Cloud TPU了。

Source: TPUs for Developers

在使用 Cloud TPU 時,爲了免除繁瑣的驅動安裝,咱們能夠經過直接使用 Google Cloud 提供的 VM 操做系統鏡像。

TPU 基礎使用

在 TPU 上進行 TensorFlow 分佈式訓練的核心API是 tf.distribute.TPUStrategy ,能夠簡單幾行代碼就實如今 TPU 上的分佈式訓練,同時也能夠很容易的遷移到 GPU單機多卡、多機多卡的環境。

resolver = tf.distribute.resolver.TPUClusterResolver(
    tpu='grpc://' + os.environ['COLAB_TPU_ADDR'])
tf.config.experimental_connect_to_host(resolver.master())
tf.tpu.experimental.initialize_tpu_system(resolver)
strategy = tf.distribute.experimental.TPUStrategy(resolver)
實例化 TPUStrategy
在上面的代碼中,首先咱們經過 TPU 的 IP 和端口實例化 TPUClusterResolver;而後,咱們經過 resolver 鏈接到 TPU 上,並對其進行初始化;最後,完成實例化 TPUStrategy

 

如下使用 Fashion MNIST 分類任務展現 TPU 的使用方式。本小節的源代碼能夠在 https://github.com/huan/tensorflow-handbook-tpu 找到。

更方便的是在 Google Colab 上直接打開本例子的 Jupyter 直接運行,地址:https://colab.research.google.com/github/huan/tensorflow-handbook-tpu/blob/master/tensorflow-handbook-tpu-example.ipynb (推薦)

import tensorflow as tf
import numpy as np
import os

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()

# add empty color dimension
x_train = np.expand_dims(x_train, -1)
x_test = np.expand_dims(x_test, -1)

def create_model():
    model = tf.keras.models.Sequential()

    model.add(tf.keras.layers.Conv2D(64, (3, 3), input_shape=x_train.shape[1:]))
    model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2,2)))
    model.add(tf.keras.layers.Activation('relu'))

    model.add(tf.keras.layers.Flatten())
    model.add(tf.keras.layers.Dense(10))
    model.add(tf.keras.layers.Activation('softmax'))

    return model

resolver = tf.distribute.resolver.TPUClusterResolver(
    tpu='grpc://' + os.environ['COLAB_TPU_ADDR'])
tf.config.experimental_connect_to_host(resolver.master())
tf.tpu.experimental.initialize_tpu_system(resolver)
strategy = tf.distribute.experimental.TPUStrategy(resolver)

with strategy.scope():
    model = create_model()
    model.compile(
        optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3),
        loss=tf.keras.losses.sparse_categorical_crossentropy,
        metrics=[tf.keras.metrics.sparse_categorical_accuracy])

model.fit(
    x_train.astype(np.float32), y_train.astype(np.float32),
    epochs=5,
    steps_per_epoch=60,
    validation_data=(x_test.astype(np.float32), y_test.astype(np.float32)),
    validation_freq=5
)
View Code
相關文章
相關標籤/搜索