前端也能玩 Deepleaning - 以 p5-deeplearn-js 為例

『ETH 好穩誒,應該可以買個買個潛艇 堡來吃了』

前言

還記得約莫是 2012 年我還在唸研究所的時候,有位清大教授(原諒我忘了是哪位…)來系上演講關於類神經網路的應用,當時的我聽得霧煞煞,覺得是離我很遙遠的一門學問,而如今 Machine Learning、Deep Learning 滿天飛,說你沒聽過 KNN 就像說你沒背過 99 乘法表一樣驚人。

對於一個成天在網頁打滾的前端工程師,可能真的沒太多機會碰觸到相關實作或研究。
但小心了!AI 的觸角也慢慢伸到前端的領域 - Screenshot to code in Keras,從圖片就能轉成 HTML,以後該怎麼辦啊…

你可能會想,身處於變化快速的前端領域,我們最擅長的不就是快速學習應對嗎?現在已經有很多 js 版本的機器學習函式庫啦,像是 Keras.js、Deeplearn.js 等等,讓你可以在 browser 上透過 WebGL 的幫助來使用 GPU 做運算。

但是這些 Library 的宗旨比較是拓展機器學習的應用層面,對於沒接觸過 Deeplearning 的前端工程師來說,要做出 teachablemachine 這樣的東西,其實沒這麼簡單。

而今天要介紹的這款 library 就是想提供一個 Higher level 的 js library,降低採用 Machine learning 實作產品的門檻。

先來看個 Demo:

p5ML-Deeplearnjs-demo

Demo 裡是一個簡單的 Chrome extension,透過 WebCam 擷取圖片來分析,利用 KNN image classifier model,來判斷出不同動作,並對應到網頁上的互動,像是 scroll dow, scroll up 或是修改 DOM 元件( Demo 中的開關燈效果 )。主要參考自 deeplearn-chrome_extension

沒有,我的另一隻手絕對沒有在下面控制滑鼠。

這一切的實現都是依靠 p5MLdeeplearnjs 的 knn image classifier。

p5ML

p5ML 還持續在開發中,主要是提供一系列的 API wrapper,讓你能忽略掉一些直接使用 deeplearn.js 需要知道的細節,像是 NDArrayMath, Scalar, Array3D 等 deeplearn 提供的物件。

使用方式很簡單,直接在 html 中載入:

<script src="p5ML.min.js"></script>

或是 npm install p5ML --save 下載皆可。

p5ML.min.js 會 expose 一個 p5ml 的全域變數,裡面提供以下幾種目前實作的 Modal 演算法:

除了 Neura Network 外,上述其餘每個 Modal 在 p5ML 的 github 上都有對應的 Demo,以及簡短的使用方式。

實作範例

以剛剛開頭看到的例子來說,我們使用到的是 KNNImgae 這個 API:

let knn = new p5ml.KNNImageClassifier(callback);

建立出 KNNImageClassifier 的物件 knn 後,我們可以透過 knn.addImage(video, index); 來加入 example(video 變數),並告知其 class 類別(index 變數)。
當加入的 example 足夠多以後,就能透過 knn.predict(input, callback) 來預測 input 是屬於哪種類別:

1
2
3
4
5
knn.predict(video, function(data) {
if (data.classIndex == 1) {
// 屬於類別 1
}
});

使用起來就是這麼簡單。

整合 Extension - popup.js

要整合到 Chrome extension 中的話,需要使用到的是 popup.js, popup.html, content.js 以及 option.html

疑?為什麼需要 option.html?這次的範例應該還用不著需要使用者設定什麼參數吧?

原因是為了取得使用者的攝影機權限

一般 Web 上是呼叫 navigator.mediaDevices.getUserMedia(options, callback)來取得使用者 WebCam 權限:

1
2
3
4
5
navigator.mediaDevices.getUserMedia({ audio: true, video: true }, function() {
console.log('ok');
}, function(e) {
console.log('webcam not ok');
});

但要讓 Chrome extension 也拿到權限的話,你的這段程式碼,必須放置在由 extension 本身開啟的 html 內才可以,popup.html 不算。
因此,利用設置頁面 option.html 是最為適合的,只要在你的 manifest.json 中設定:

manifest.json
1
2
3
4
5
"options_ui": {
"page": "options.html",
"chrome_style": true,
"open_in_tab": true
}

之後就能透過開啟 Extension 的設定頁面,來徵求使用者的攝影權限。

取得權限後,在我們的 popup.html 中要繪製一些頁面元件,以供之後我們在前端進行 Image 的分類:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<video crossorigin="anonymous" width="227" height="227" id="video"></video>
<button id="still">Do nothing</button>
<button id="up">Up</button>
<button id="down">Down</button>
<button id="turnoff">Turn Off Light</button>
<script src="scripts/p5ml.js"></script>
<script src="scripts/popup.js"></script>
</body>
</html>

popup.js 中則是放入最主要的 knn 相關程式碼:

popup.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let knn = new p5ml.KNNImageClassifier(modelLoaded);
let turnoffButton = document.getElementById('turnoff');
// turnoffButton
turnoffButton.addEventListener('click', function () {
knn.addImage(video, 4);
times++;
})
setInterval(function() {
if (times > 10) {
knn.predict(video, function(data) {
if (data.classIndex == 1) {
console.log('response', 'still');
} else if (data.classIndex == 2) {
// ...
} else if (data.classIndex == 3) {
// ...
} else if (data.classIndex == 4) {
chrome.runtime.sendMessage({ direction: "turn off" }, function (response) {
console.log('response', 'turn off');
});
}
});
}
}, 1500);

Line 1 我們初始化 p5ml.KNNImageClassifier 物件 knn,接著在 turnoffButton 按下時加入範例 knn.addImage(video, 4),並設定該範例類別為 4。

接著在 Line 8 開始,利用 setInterval() 不斷的進行 knn.predict(),這樣就能盡量即時分辨 WebCam 傳來的資料。

其他部分,像是如何讓 WebCam 的影像顯示在 <video> tag 中的程式碼也是在 popup.js 中實作。

完整 popup.js 程式碼可以看 這裏 或是原作者的

實作到這裡以後,基本上你就可以開啟 Extension 然後進行一些影片的分類,像是這樣:

教育你的 extension

可以從上面的片段發現,你需要點擊對應分類的按鈕,並且做出你想要的動作來教導你的 extension,讓他認得你的手勢!
當你給予的 example 越多,就愈有機會判斷得更好。

整合 Extension - content.js

這邊假定大家都知道 Extension 的實作方式(如果不知道可以從這邊學習)。

在 Extension 中,可以透過 chrome.runtime.sendMessage()chrome.runtime.onMessage.addListener() 來針對 Popup page 與 當前頁面的 content script 進行溝通,我們就是利用這點來將辨識完的手勢,轉換成頁面上的互動操作:

content.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
chrome.runtime.onMessage.addListener(gotMessage)
function gotMessage(message, sender, sendResponse){
let direction = 0;
if(message.direction == 'up'){
direction = -500;
} else if(message.direction == 'down'){
direction = +500;
} else if (message.direction == 'turn off') {
const mask = document.getElementById('body-maskDiv');
if (mask) {
removeMask();
} else {
addMask();
}
}
window.scrollBy({
top: direction,
left: 0,
behavior: 'smooth'
});
}

到這邊為止基本上就完成了範例的所有功能,對完整程式碼(或是遮罩實作方式 XD)有興趣的人可以從這邊取得。

結論

這次算是初步嘗試在前端上應用 ML 相關的功能,介紹大家有像是 p5-deeplearn-js 這樣有趣的 library 可以使用,希望可以有多一點的前端高手來發揮創意,並分享作品出來,不然真的很難找到相關的經驗分享。
不過當然,這只是個非常粗淺的應用,更是用非常 High level 的 API 來實作,還是需要真正去了解 ML 相關的演算法,才是比較正確的學習方向,接下來我也會繼續學習,p5-deeplearn-js 會是一個不錯的起點,加上 deeplearn.js 的 KNN 演算法程式碼都算蠻簡潔的,以 Typescript 實作,閱讀起來比起純 JS 好理解一些(多了 Type 很有幫助啊!),推薦給各位!

資料來源

  1. p5-deeplearn-js
  2. deeplearn-chrome_extension
  3. deeplearnjs