前端如何學習機器學習之TensorFlow.js

xiangzhihong發表於2019-03-03

TensorFlow.js簡介

TensorFlow是谷歌基於DistBelief進行研發的第二代人工智慧學習系統,其命名來源於本身的執行原理。Tensor(張量)意味著N維陣列,Flow(流)意味著基於資料流圖的計算,TensorFlow為張量從流圖的一端流動到另一端計算過程。

TensorFlow是一個將複雜的資料結構傳輸至人工智慧神經網中進行分析和處理過程的系統。目前被廣泛的運用在語音識別或影像識別等多項機器學習和深度學習領域,它可在小到一部智慧手機、大到數千臺資料中心伺服器的各種裝置上執行。

TensorFlow的資料流運作模型如下圖:

這裡寫圖片描述

TensorFlow.js 是一個開源的用於開發機器學習專案的 WebGL-accelerated javascript 庫。使用它可以在瀏覽器上建立CNN(卷積神經網路)、RNN(迴圈神經網路)等等,且可以使用終端的GPU處理能力訓練這些模型。

#機器學習研究的範圍
那麼,學習機器學習之前,對於其中的一些概念我們需要有所瞭解。

我們解決一個問題有兩種模式:一種叫做模型驅動(model driven),通過研究物件的物理、化學等機理模型,對物件進行建模,從而解決問題,比如我們熟知的牛頓三定律,對於上面那個公式就是我們已知輸入x和機理模型f(),需要求解我們想要得到的y;而另外一種叫做資料驅動(data driven),隨著人們遇到的問題越來越複雜,尋找物件機理模型的代價越來越大,反之資料獲取的代價越來越小,於是科研工作者開始從另外角度思考問題,是否可以通過這些資料來分析得到我想要的東西,即我知道一些的樣本(x,y)或者我只知道x,我想分析這些來得到物件的模型f(),進而當我再次擁有一個x的時候,我就可以得到我想要的y,如果不是那麼嚴格的來講,所有這種資料分析的方法都可以算作機器學習的範疇。

所以一個機器學習通常應該包括的基本要素有:訓練資料,帶引數的模型,損失函式,訓練演算法。訓練資料作用自不必說;帶引數的模型是用來逼近f();損失函式是衡量模型優劣的一個指標,比如模型識別分類的準確度;訓練演算法也可以叫做優化函式,用於不斷更新模型的引數來最小化損失函式,得到一個較好的模型,或者叫做學習機。接下來將介紹一些機器學習中的基本概念,可能沒有很強的連貫性。

模型

模型是對真實世界中問題域內的事物的描述,而不是對軟體設計的描述。在機器學習中,模型的的具體還以有點類似於:帶有一些待訓練引數,用於逼近前文提到的f()的引數集合。在引數空間,f()只是一個點,而我提到的模型也是一個點,並且由於引數可以變,所以我要做的只是讓我模型的這個點儘可能的接近真實f()的那個點。

器學習的模型演算法有很多,但是比較常用的模型可以概括為三種:

  • 基於網路的模型:最典型的就是神經網路,模型有若干層,每一層都有若干個節點,每兩個節點之間都有一個可以改變的引數,通過大量非線性的神經元,神經網路就可以逼近任何函式。
  • 基於核方法的模型:典型的是SVM和gaussian process,SVM把輸入向量通過一個核對映到高維空間,然後找到幾個超平面把資料分成若干個類別,SVM的核是可以調整。
  • 基於統計學習的模型:最簡單的例子就是貝葉斯學習機,統計學習方法是利用數理統計的數學工具來實現學習機的訓練,通常模型中的引數是一些均值方差等統計特徵,最終使得預測正確概率的期望達到最大。

現實生活中,模型無處不在,如世界地圖、圖表等等都可以被認為是模型。為了說明模型是什麼,我們舉一個例子:Barcelona 房子價格隨房間數的變化。

這裡寫圖片描述

然後,我們將這兩個資料使用一個 2D 圖形展示,每個座標軸對應一個引數。

這裡寫圖片描述

現在,我們在此基礎上預測下第 6 個或者以上房間的房子價格。
當然,這個模型還不夠好,主要體現在以下方面:

  • 只有 5 個樣本,結果不夠可信;
  • 只有兩個引數,但其實影響房子價格有更多的因素,如地理位置、房子年齡等;

對於第一個問題,我們可以新增樣本數來解決,比如新增 100 萬個資料。
對第二個問題,我們可以新增更多的座標軸。在 2D 圖形上我們可以畫一條直線,在 3D 座標軸裡我們可以畫一個平面。

這裡寫圖片描述

對於如何處理 3D 以上的情形,比如 4D 甚至是 1000000D 呢,我們可以用數學和計算超平面來處理這種情況,而神經網路是一個很好的處理工具。

神經網路

在生物學中,一個典型的神經網路結構主要由:樹狀突、軸突和突觸構成。

  • 樹狀突(Dendrites):資料輸入的地方。
  • 軸突(Axon):輸出端。
  • 突觸(Synapse):神經之間進行交流的結構。它負責將電訊號從神經軸突的末端傳遞到附近神經的樹狀突。這些突觸結構是學習的關鍵,因為它們在使用中會增減電訊號的活動。
這裡寫圖片描述

在機器學習中。簡化後的神經則是下面這樣的:

這裡寫圖片描述

在機器學習中,以下部分是必須的:

  • 輸入(Input):輸入的引數。
  • 權值(Weight):和突觸一樣,它們以增減來調整神經的活動來達成更好的線性迴歸。
  • 線性函式 (Linear function):每個神經就像一個線性迴歸函式,目前為止一個線性迴歸函式只需要一個神經。
  • 啟用函式 (Activation function):我們能提供一些啟用函式來改變從一個標量 (Scalar)到另一個非線性的函式。比如:sigmoid、RELU、tanh。
  • 輸出 (Output):經過啟用函式計算後的輸出結果。

啟用函式的使用非常有用,它是神經網路的精髓所在。沒有啟用函式的話神經網路不可能很智慧。原因是儘管在網路中你可能有很多神經,神經網路的輸出總會是一個線性迴歸。我們需要一些機制來改變這個獨立的線性迴歸為非線性的以解決非線性的問題。
下圖展示了線性函式轉換到非線性函式的過程:

這裡寫圖片描述

訓練模型

在上面的 2D 線性迴歸示例裡,在圖表中畫條線就足以讓我們開始預測新資料了。然而,“深度學習”的目的是要讓我們的神經網路學著畫這條線。可以參考下面的連結來學習:訓練模型(Training Models)

畫一條簡單的線我們只需要包括一條神經的非常簡單的神經網路對於訓練模型來說是相對簡單的情況,但其它的模型做的要複雜的多,比如歸類兩組資料就比較難了。例如,“訓練學習”如何畫出下面的影像:

這裡寫圖片描述

對於上面的情況來說並不是很複雜,每個模型都是一個世界,所有這些模型的訓練的概念都差不多。首先是畫一條隨機的線,然後在一個迴圈演算法中改進它,修復每個迴圈中的錯誤。這種優化演算法又叫做梯度下降法 (Gradient Descent),還有更多複雜的演算法如 SGD、ADAM,概念都類似。

為了理解梯度下降法,我們需要知道每個演算法 (線性迴歸、邏輯迴歸等) 有不同的代價函式 (cost function) 來度量這些錯誤。

代價函式總會收斂於某個點,它可能是凸或非凸函式。最低的收斂點將在 0% 錯誤時被發現,我們的目標就是到達這個點。

這裡寫圖片描述

但我們使用梯度下降演算法時,我們開始於一個隨機的點,但是我們不知道它在哪。想象一下你在一座山上,完全失明,然後你需要一步一步的下山,走到最低的位置。如果地形複雜 (像非凸函式),下降過程將更加複雜。

我不會深入的解釋什麼是梯度下降演算法。你只需要記住它是一種優化演算法,用來訓練 AI 模型以最小化預測產生的錯誤。這個演算法需要時間和 GPU 來計算矩陣乘法。收斂點通常在第一輪執行中難以達到,所以我們需要對一些超引數 (hyperparameter) 如學習率(learning rate)進行調優,或者新增一些正則化 (regularization)。

經過反覆的梯度下降法,我們達到了離收斂點很近的地方,錯誤率也接近 0%。這時候,我們的模型就建立成功,可以開始進行預測了。

這裡寫圖片描述

TensorFlow.js使用

##1,建立神經網路
TensorFlow.js 給我們提供了一個簡單的辦法來建立神經網路。首先,我們將先建立一個 LinearModel 類,新增trainModel方法。對這類模型我將使用一個序列模型 (sequential model),序列模型指的是某一層的輸出是下一層的輸入,比如當模型的拓撲結構是一個簡單的棧,不包含分支和跳過。

在trainModel方法裡我們將定義層 (只需要使用一個,這對於線性迴歸問題來說足夠了):

import * as tf from `@tensorflow/tfjs`;

/**
* Linear model class
*/
export default class LinearModel {
  /**
  * Train model
  */
  async trainModel(xs, ys){
    const layers = tf.layers.dense({
      units: 1, // Dimensionality of the output space
      inputShape: [1], // Only one param
    });
    const lossAndOptimizer = {
      loss: `meanSquaredError`,
      optimizer: `sgd`, // Stochastic gradient descent
    };

    this.linearModel = tf.sequential();
    this.linearModel.add(layers); // Add the layer
    this.linearModel.compile(lossAndOptimizer);

    // Start the model training!
    await this.linearModel.fit(
      tf.tensor1d(xs),
      tf.tensor1d(ys),
    );
  }

  ...more
}
複製程式碼

該類的使用方法如下:

const model = new LinearModel();

// xs and ys -> array of numbers (x-axis and y-axis)
複製程式碼

##2,使用 TensorFlow.js 進行預測
預測的部分通常會簡單些,訓練模型需要定義一些超引數,相比之下,進行預測很簡單。我們將在 LinearRegressor 類裡新增該方法:

import * as tf from `@tensorflow/tfjs`;

export default class LinearModel {
  ...trainingCode

  predict(value){
    return Array.from(
      this.linearModel
      .predict(tf.tensor2d([value], [1, 1]))
      .dataSync()
    )
  }
}
複製程式碼

然後我們呼叫該函式填入引數進行預測:

const prediction = model.predict(500); // Predict for the number 500
console.log(prediction) // => 420.423
複製程式碼
這裡寫圖片描述

當然,我們提供了線上執行環境:stackblitz.com/edit/linear…

3,使用訓練好的模型

有很多模型都可以在 TensorFlow.js 中使用,而且,你可以使用 TensorFlow 或 Keras 建立模型,然後匯入到 TensorFlow.js。比如,你可以使用 posenet 模型 (實時人類姿態模擬) 來做些好玩的事情:

這裡寫圖片描述

專案地址: github.com/aralroca/po…
它的使用也非常簡單,首先匯入這個模型:

import * as posenet from `@tensorflow-models/posenet`;

// Constants
const imageScaleFactor = 0.5;
const outputStride = 16;
const flipHorizontal = true;
const weight = 0.5;

// Load the model
const net = await posenet.load(weight);

// Do predictions
const poses = await net
      .estimateSinglePose(
          imageElement, 
          imageScaleFactor, 
          flipHorizontal, 
          outputStride
      );
複製程式碼

然後為模型新增測試資料:

{
  "score": 0.32371445304906,
  "keypoints": [
    {
      "position": {
        "y": 76.291801452637,
        "x": 253.36747741699
      },
      "part": "nose",
      "score": 0.99539834260941
    },
    {
      "position": {
        "y": 71.10383605957,
        "x": 253.54365539551
      },
      "part": "leftEye",
      "score": 0.98781454563141
    },
    // ...And for: rightEye, leftEar, rightEar, leftShoulder, rightShoulder
    // leftElbow, rightElbow, leftWrist, rightWrist, leftHip, rightHip,
    // leftKnee, rightKnee, leftAnkle, rightAnkle
  ]
}
複製程式碼

下面是示例程式碼: github.com/aralroca/fi…
##4,從 Keras 匯入模型
們可以從外部匯入模型到 TensorFlow.js,在下面的例子裡,我們將使用一個 Keras 的模型來進行數字識別 (檔案格式為 h5)。為了達到目的,我們需要使用 tfjs_converter。

pip install tensorflowjs
複製程式碼

然後,使用轉換工具:

tensorflowjs_converter --input_format keras keras/cnn.h5 src/assets
複製程式碼

現在,你可以將模型匯入到 JS 程式碼裡了:

// Load model
const model = await tf.loadModel(`./assets/model.json`);

// Prepare image
let img = tf.fromPixels(imageData, 1);
img = img.reshape([1, 28, 28, 1]);
img = tf.cast(img, `float32`);

// Predict
const output = model.predict(img);
複製程式碼

僅需幾行程式碼,你就可以使用 Keras 中的數字識別模型。當然,我們還可以加入一些更好玩的邏輯,比如,新增一個 canvas 來畫一個數字,然後捕捉影像來識別數字。

這裡寫圖片描述

專案地址:github.com/aralroca/MN…

如果硬體不行,在瀏覽器上訓練模型可能效率非常低下。TensorFlow.js 藉助了 WebGL 的介面來加速訓練,但即使這樣它也比 TensorFlow Python 版本要慢 1.5-2 倍。

但是,在 TensorFlow.js 之前,我們基本不可能不靠 API 互動在瀏覽器使用機器學習模型。現在我們可以在我們的應用裡 離線的 訓練和使用模型。並且,無需與服務端互動讓預測變得更快。

原文連結:aralroca.com/2018/08/24/…
參考:機器學習基本概念
TensorFlow.js基本概念

相關文章