2022年7月20日 星期三

前端也能玩Machine Learning:TensorFlow.js初體驗

 TensorFlow.js是Google將機器學習(Machine Learning、ML)框架TensorFlow的JavaScript版本,透過TensorFlow.js,讓JavaScript開發人員也有機會加入機器學習的領域。加上前端領域的生態圈支持,讓機器學習在瀏覽器上有了更多發揮的空間!例如結合攝影機、行動裝置的陀螺儀等等,只要裝置與瀏覽器支援,都能夠發會更多不同的變化,同時藉由在客戶端瀏覽器上執行的優勢,節省後端訓練的成本。

今天我們就來簡單介紹一下TensorFlow.js,以及簡單的機器學習訓練方式吧!

關於 TensorFlow.js 的基礎知識

這是一篇很基礎很間單的TensorFlow.js介紹與範例,因此我們還是先簡單的介紹一下 TensorFlow.js。

TensorFlow.js的特色與基本組成

由於TensorFlow.js是由JavaScript撰寫而成,因此只要與瀏覽器相關的應用,都可以與TensorFlow.js直接整合,這意味著我們可以將瀏覽器功能與機器學習搭配起來,組合成更多元的web application。

TensorFlow.js也支援WebGL,因此即使在瀏覽器上,我們也能使用GPU來加快運算結果,不用擔心在瀏覽器上的效能限制。

TensorFlow.js分成低階與高階兩組API。

低階的API是由deeplearn.js衍生,負責處理一些低階如線性代數的資料運算等等,來協助我們處理機器學習中的數學運算部分。

而高階的API則是用來包裝一些常用的機器學習演算法,同時允許我們載入訓練好的模型,像是由Keras訓練的模型等等。

TensorFlow.js的限制

目前TensorFlow.js不支援Node.js開發,因此我們只能在瀏覽器上使用,未來會支援Node.js,但時程未定。

開始使用 TensorFlow.js

直接使用CDN

接下來我們就可以開始使用TensorFlow.js啦!由於TensorFlow.js目前只能在瀏覽器上執行,為了簡化前置準備,我們直接建立一個index.html,並加入TensorFlow.js的CDN,如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <title>TensorFlow.js DEMO</title>
</head>

<body>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest"></script>
    <script src="index.js"></script>
</body>

</html>

之後我們就能在index.js中開始使用TensorFlow.js進行機器學習啦!

使用 npm 或 yarn

當然,身為熟練的前端工程師,我們也能使用npmyarn來安裝,如下:

npm install @tensorflow/tfjs

之後再用import的方式加入TensorFlow即可

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

當然,你就需要使用babel或webpack等工具來轉換程式囉。

機器學習基本概念

先來簡單講講一般機器學習是怎麼做的,在機器學習中,通常我們會針對一個題目,給予一組訓練資料清單,這些資料包含了問題與答案,接著透過機器學習的各種演算法,來訓練出一個針對這個題目的模型

這個模型通常就代表了一個公式,只要將題目帶進去公式,就能夠算出答案,而這個公式怎麼來的呢?就是透過機器學習演算法,這些演算法通常會先隨機產生一個公式(也就是模型),接著將訓練資料帶進去計算出預測值,並與正確答案比較,並透過不斷的調整模型內容,不斷想辦法降低預測值與正確答案的差距,直到預測值與足夠接近正確答案為止。

簡單來說,就是經驗法則啦!剛開始學一項知識的時候,得到的結果會與預期落差很大,藉由不斷學到正確知識後,就會與預期越來越接近!

有了這樣的基本概念後,就讓我們來訓練一個簡單的模型吧!

另外,有時間的話建議可以先看過Core Concepts in TensorFlow.js,對於TensorFlow.js的低階API有基礎的認知,會比較好理解後續的程式。(或是有機會再來簡單介紹一個TensorFlow.js的低階API)

產生第一個待訓練模型

首先我們先看一個簡單的數學公式代表我們的正確模型。

y = 2x

x 代表輸入值,y代表輸出結果,2是這個模型的重點參數,也是我們在訓練過程中期望達到的目標!

但目前我們還沒有任何模型可用,因此我們先在程式中加入一個全域變數,把它當做要被訓練的模型參數

// 要被訓練的參數,這個參數隨著訓練次數增加會越來越準確
const trainingAnswer = tf.variable(tf.scalar(Math.random()));

上述程式中,tf是載入CDN程式後,用來放置TensorFlow.js相關程式的全域變數。

我們透過tf.scalar()Math.random()的隨機值當作TensorFlow的數值資料,再使用tf.variable()將這個數值當作是一個變數,因此上面程式我們可以解讀為:在TensorFlow中宣告一個變數,並給予一個隨機數值

接下來,我們再來以這個參數來產生一個模型公式

function predict(x) {
    return tf.tidy(() => {
        return trainingAnswer.mul(x)
    });
}

tf.tidy()是用來避免變數在TensorFlow運算中站用過多記憶體的一種管理機制,在這裡我們不用想太多,大部分情境下,關於運算的都放在tf.tidy()中就對了,剩下的TensorFlow.js會幫我們處理!

.mul(),則是TensorFlow.js低階運算API的一種,主要用來進行乘法運算。

透過這個predict()函式,我們能運算出目前模式產生出來的預測值

定義損失函式(Loss Function)

損失函式是用來評估預測值與正確地案的差距,在訓練過程中,這個損失函式的輸出應該會越來越小,在這邊我們使用Mean Square Error函式作為評估的公式,代表的是預測結果與訓練資料中所有答案平方差的平均

function loss(predictions, labels) {
    const meanSquareError = predictions.sub(labels).square().mean();
    return meanSquareError;
}

定義訓練函式

接下來我們要定義訓練的方法,TensorFlow.js中提供了幾種訓練的演算法,我們選擇使用Stochastic Gradient Descent(SGD)演算法,這種演算法會根據訓練結果來隨機調升或調降參數的值,另外我們也需要設定一個學習率(learning rate),這代表學習的跳耀程度,數值越低代表學習速度越慢,越高代表學習速度越快,但也可能會造成太過跳耀式的學習,導致學習成果走鐘

function train(xs, ys, numIterations) {
    const learningRate = 0.5;
    const optimizer = tf.train.sgd(0.5);

    for (let iter = 0; iter < numIterations; iter++) {
        optimizer.minimize(() => {
            const predsYs = predict(xs);
            return loss(predsYs, ys);
        });
    }
}

參數xs代表訓練用問題資料集,ys代表答案資料集,numIterations代表訓練次數,理論上訓練次數越多,就越接近結果,當然也越花時間,同時在學習率太高的情況下,訓練走鐘的機率也會增加。

tf.train.sgd(0.5)代表使用SGD訓練演算法,並以0.5的學習率進行訓練。

最後看到optimizer.minimize()方法,來調整參數,方法的callback中,我們將透過損失函式算出誤差值並回傳,minimize()方法會自動幫助我們調整損失函式中關聯到的參數,並透過調整參數把誤差值降到最低。

執行訓練

有了損失函式與訓練函式後,我們就可以將這些內容組合起來執行訓練啦!

async function learnCoefficients(dataCount, iterations) {
    const correctAnswer = 2; // 正確答案
    const trainingData = generateData(dataCount, 2);

    console.log('Before Training: ', await trainingAnswer.data());

    // Train the model!
    await train(trainingData.xs, trainingData.ys, iterations);

    // 印出訓練結果
    console.log('After TRaining: ', await trainingAnswer.data());
}

learnCoefficients(100, 1000);

前兩行是利用正確的模型產生訓練資料,現實中則可能是準備好文字檔並讀取進來generateData內容如下:

function generateData(numPoints, answer) {
    return tf.tidy(() => {
        // 產生常態分佈的隨機資料
        const xs = tf.randomNormal([numPoints], -1, 1);
        // 套用正確模型產生答案
        const ans = tf.scalar(answer);
        const ys = ans.mul(xs);
        // 回傳訓練資料與答案
        return {
            xs,
            ys
        };
    })
}

接著透過訓練函式,把問題、答案和訓練次數傳進去,讓訓練函式去把結果訓練出來,就可以得到一個正確的模型啦!

我們可以試著打開瀏覽器的console看一下執行結果:

訓練的結果就與我們的正確答案十分接近啦!

前端人員要進入機器學習領域,門檻又降低不少了呢!

本日小結

今天我們介紹了TensorFlow.js這關機器學習的架構,並且做了一個簡單的訓練程式,透過機器學習幫助我們解決一個數學問題。

透過TensorFlow.js,前端JavaScript開發人員可以在不需要了解其他程式語言的前提下,進入機器學習的領域,當然啦,我們還是要有機器學習相關的知識,才能夠正確的使用TensorFlow.js;同時也能搭配各種前端應用,組合出各式各樣的有趣應用,TensorFlow.js上也有一些結合web產生的DEMO程式,有興趣的話也可以上去玩玩看。

希望這篇文章可以幫助對於機器學習有興趣的前端朋友更容易入門,之後有機會的話,再來寫TensorFlow.js與神經網路等等的相關應用吧!

今天的程式碼在這裡:https://github.com/wellwind/tensorflow-js-practice

相關資源

資料來源:https://fullstackladder.dev/blog/2018/04/07/tensorflow-js-basic/

沒有留言: