2022年7月28日 星期四

初探 WebRTC - 手把手建立線上視訊

前言

本系列主要介紹使用 WebRTC 搭配 Socket.io 建立一對一的視訊。

連結:
1. 初探 WebRTC — 手把手建立線上視訊 (1)
2. 初探 WebRTC — 手把手建立線上視訊 (2)
3. 初探 WebRTC — 手把手建立線上視訊 (3)

目錄

  • 什麼是 WebRTC
  • 取得本地影像聲音
  • 輸出畫面

什麼是 WebRTC

Web Real-Time Communication 網頁即時通訊 的簡稱,在 2011 年被 Google 收購並開源,是個可以直接使用瀏覽器建立視訊的 API。 


資料來源:https://medium.com/@jedy05097952/%E5%88%9D%E6%8E%A2-webrtc-%E6%89%8B%E6%8A%8A%E6%89%8B%E5%BB%BA%E7%AB%8B%E7%B7%9A%E4%B8%8A%E8%A6%96%E8%A8%8A-1-5e9d4702e8e8

2022年7月22日 星期五

ml5.js 訓練Model實做

 

前情提要

最近工作又做到了一個撞牆期,對於自己寫出來的程式,或是用來解決問題的思考方式覺得有點乏味,於是,就在深夜學習跟找尋新題材的過程中,發現了之前嘗試過的ml5.js,終於出現了model的save/load的功能,對於前端開法者來說,是一個很好的消息。

不知道ml5.js是什麼的人,簡單來說,它就是一個Tensorflow.js的wrapper framework,它把Tensorflow中的艱深字彙與邏輯包裝起來,引出成簡單易懂的functions,讓你可以幾乎無腦的進行基礎的machine learning實做,在ml5.js的首頁上,就直接有一個image classifier的demo function讓你上傳照片進行照片內容的判別。想要先看看ml5.js能做到什麼的人,可以先到這裡看看它的Examples範例。

另外要先提醒一下,由於ml5.js是由p5.js的組織成員所發起的專案,所以在官方的文件或是範例中,它都是基於使用p5.js的程式邏輯進行撰寫,不過不用擔心,使用ml5.js時,是完全不需使用p5.js的,在等等的實做範例中,我也只會用純Javascript進行。

為了快速讓沒用過p5.js的人,還是能夠快速理解ml5.js官方網站上的範例,我再簡短的說明一下;p5.js是一個Javascript的前端框架,整體的框架架構和風格,都是借鏡另一個Java的Processing framework。而這兩個框架的產生,都是為了降低程式語言學習的門檻,讓藝術家、設計師、教育體系的人或是任何初心者都能夠容易的上手。

p5.js雖然是以繪圖為核心價值,但你上手後,其實你也可以拿來做一般網頁的開發,因為它把許多DOM元素的操作都包成了很直觀的function name。也因為如此,它的程式邏輯與生命週期會稍微和一般傳統的前端開發不太一樣。但要看懂ml5.js上的範例,你其實只要知道兩個重點就好: setup() 和 draw() function。

有寫過Arduino程式的人一定可以馬上了解,因為它就等同於Arduino程式邏輯中的setup()和loop()。在setup() function裡面的程式碼,會在網頁loading完後,先執行過一次,然後就不再執行,接著在draw() funciton 裡面的程式碼,就會像一個大while迴圈一樣,一直不斷地被重複執行。

講了這麼多前情提要,接著就該進入這次的主題了,那就是實做出一個能夠能夠讓你今天立刻帶著走的Image Classification Model。這個Model並不是從零做起,而是借用AI領域中,巨人的肩膀,對已經存在的Model進行Transfer learning的model。

Transfer Learning 的方法就是把 Pre-trained Model 最後一層拔掉 (註:最後一層是用來分類的),加入新的一層,然後用新資料訓練新層的參數。(引用)

而這次的實做,將會使用較為輕量的MobileNet這個Model進行。

開始實做- 阿ㄆ一ㄚˇ、阿尼分類器

1. 引用v0.1.3版本以上的ml5.js

https://unpkg.com/ml5@0.1.3/dist/ml5.min.js

2. 接著先在頁面上放上必要的按鈕和選擇框

說明一下,最上面的兩個file input是要用來讓我們可以選擇特定資料夾路徑用的,方便在測試階段可以比較方便替換拿來訓練的材料,因此會需要加入 webkitdirectory, dierctory, mutiple這些屬性,讓我們可以載入多個檔案,當然你也可以不用這樣做,直接在javascript的部分指定好路徑。(只是我當初在自己動手做的時候,那個moment就是想這樣寫…..) 接著在每個項目上加上id屬性,方便接下來的javascript能夠取得這些DOM元素進行操作。

3. 開始撰寫Javascrip,先從取得DOM元素開始、開始使用ml5.js的FeatureExtractor去載入MobileNet,然後產生一個classifier實例。

ml5.featureExtracture()這個function接受兩個參數:第一個是我們要retrain的model名稱,第二個則是載入後的callback function。

如果順利,這時候應該可以在開啟網頁後經過幾秒,看見”model is ready!!”出現在console中。

這邊要注意一下,ml5.js本身是不包含model檔案的,所以每一次的載入,ml5.js都會幫我們跑去google的CND下載下來,因此才會有callback執行前那幾秒鐘的等待下載時間。至於有沒有可以離線載入model的方法? 目前我還沒去找跟嘗試,所以還不知道,如果你已經有試過的人知道方法,也歡迎在文章後面留言指導。

4.開始撰寫載入素材圖片檔案的部分,同時為檔案標上正確的分類標籤

加入要訓練的圖片,並加上對應的標籤這兩個動作,我是利用在檔案的input element上綁定change event來fire處理事件,而處理方法,就是透過去得input element上的className來做為訓練圖檔的對應標籤(也就是照片的標準答案),接著就歷遍整個file清單,並將檔案圖檔和標籤餵進classifier.addImage()這個function裡面。

在接著try之前,必須要先多做一個動作,由於我們目前所做的環境都是在瀏覽器中,因此在處理local檔案時,會出現Cross-Origin Resource Sharing的問題,chrome會基於安全性的問題不讓你輕易的存取local檔案,最簡單快速的方式就是直接把這份html丟到伺服器環境中,這邊順便推薦一下在npm上的http-server這個套件,安裝好後,你只要在command line中下http-server指令,就會立刻在你當前跟目錄下創建一個伺服器環境,這時只要在localhost的8080 port瀏覽我們在做的html檔案,便可解決cross-origin的問題了。

接著,我們就可以分別載入要拿來訓練的素材圖片了,選完後應該只會先看見input框多出了已經載入圖片的數量,如果這時候你的console裡面沒有出現任何錯誤,那我們應該就已經完成了訓練前的準備動作了。

5.將訓練model匯出model的動作分別綁上Train Button和Save Button

Model的訓練和匯出非常的容易,只需要分別使用classifier.train()和classifier.save()這兩個非常直觀的function就可以做到。而在classifier.train()中,我們可以加入一個callback function來監看當前訓練的loss狀況,這個loss值,膚淺的來講,可以把它視為ml5.js/tensorflow.js在幫你處理訓練過程中的錯誤值。在正常的狀況下,你的loss值會越跑越收斂,最後趨近於零。如果你在訓練過程中loss一值出現大幅度的跳動,無法收斂,那可以嘗試加入更多照片或是重新定義訓練的方法。以下為按下Train Button後應該要成功的結果:

另外要注意的點是,你訓練時的標籤定義項目數量必須大於等於2。

6. 匯出訓練好的model

點擊Save button之後,你就會看到瀏覽器下載了兩個檔案

一個是model.json,裡面記錄了一些model的資訊,像是用了哪些方法、權重檔案的位置、分類標籤….等,而另外一個model.weights.bin則是model訓練過程中tensorflow.js所訓練調整的那些參數值,也就是訓練後的成果。得到了這兩個檔案後,我們之後只要引用他們,就可以開始使用自己的做出來的分類器了。

7.引用訓練好的model

由於我們是使用Transfer Learning,其原理就是把Mobilenet的最後一層分類階層,抽換成我們自己訓練的標籤結果,因此,在引用我們自己的model前,還是得載入MobileNet,所以,我們載入自己model的時機點,就是在載入modelnet之後,也就是modelReady()裡面。

做到這裡就已經大致完成囉,只要重新載入頁面等個幾秒,這時候應該就會看到兩個Model 皆已經載入的訊息。

接著只要頁面上多新增一個file input標籤讓我們可以選擇要分類的照片,再綁定change事件與分類動作,就大功告成囉!

我只用了這些照片進行訓練

接著只要透過最後一個新增的file input選擇欲拿來分類的照片,就可以在console中看到我們的model判定出來的標籤結果囉!

我選用這張來當作測試照片

結果如下….

結果還算不錯!而且重點是從訓練過程,我都沒有對照片進行任何前處理,連圖片的大小我也都沒有一致化,所以能有這樣的結果已經算很不錯了,當然,這次實作我是用背景相對簡單的圖片來做訓練,如果你要用的照片顏色跟圖案是很複雜的,那訓練出來的結果應該不會這麼好了。

以上就是整個ml5.js的基本實作,希望你看完之後,也能夠立刻自己做出一個屬於你自己的分類器!

結論

大概在幾個月前看到ml5.js時其實就覺得滿新奇的了,只是試完之後才發現ml5.js在v0.1.3版本之前並沒有輸出model的功能實在可惜,沒想到過沒多久後,就終於等到了,相信之後一定會有更多相關有趣的應用出現。

不過其實目前的ml5.js還有些可惜的缺點:

  1. 只能在瀏覽器裡執行,無法轉移到node.js的server環境中執行。這部分可能目前還是得使用純Tensorflow.js來達成。
  2. 針對訓練過程中的調整,好像沒有地方可以處理。這部分若要認真做,可能也還是一樣得仰賴Tensorflow.js先進行訓練後,再餵給ml5.js做後續的前端應用。

資料來源: https://dopeorion.medium.com/%E5%85%A8%E7%AB%AF%E7%94%9F%E6%B4%BB-ml5-js-%E4%B8%8D%E5%98%B4%E7%A0%B2%E5%AF%A6%E5%81%9A-77ac79a28773

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/

2022年7月14日 星期四

前端常常用到的UI framework、library

 

資料來源:https://ithelp.ithome.com.tw/users/20020617/ironman/988