使用 Tensorflow.js 為前端開發人員提供機器學習
已發表: 2022-03-10機器學習通常感覺它屬於數據科學家和 Python 開發人員的領域。 然而,在過去的幾年中,已經創建了開源框架,以使其更易於在不同的編程語言中訪問,包括 JavaScript。 在本文中,我們將使用 Tensorflow.js 通過幾個示例項目來探索在瀏覽器中使用機器學習的不同可能性。
什麼是機器學習?
在我們開始深入研究一些代碼之前,讓我們簡單地談談什麼是機器學習以及一些核心概念和術語。
定義
一個常見的定義是計算機無需明確編程即可從數據中學習的能力。
如果我們將其與傳統編程進行比較,這意味著我們讓計算機識別數據中的模式並生成預測,而無需我們確切地告訴它要尋找什麼。
讓我們以欺詐檢測為例。 沒有確定的標準可以知道是什麼使交易具有欺詐性; 欺詐可以在任何國家、任何賬戶、針對任何客戶、任何時間等進行。 手動跟踪所有這些幾乎是不可能的。
但是,使用多年來收集的有關欺詐費用的先前數據,我們可以訓練機器學習算法來理解這些數據中的模式,從而生成一個模型,該模型可以給出任何新交易並預測它是否為欺詐的可能性,而無需準確地告訴它要尋找什麼。
核心概念
要理解以下代碼示例,我們需要先介紹一些常用術語。
模型
當您使用數據集訓練機器學習算法時,模型是此訓練過程的輸出。 它有點像一個將新數據作為輸入並產生預測作為輸出的函數。
標籤和功能
標籤和特徵與您在訓練過程中提供給算法的數據相關。
標籤表示您將如何對數據集中的每個條目進行分類以及如何標記它。 例如,如果我們的數據集是一個描述不同動物的 CSV 文件,我們的標籤可以是“貓”、“狗”或“蛇”之類的詞(取決於每種動物代表什麼)。
另一方面,特徵是數據集中每個條目的特徵。 對於我們的動物示例,它可能是“鬍鬚、喵喵”、“頑皮、吠叫”、“爬行動物、猖獗”等。
使用這一點,機器學習算法將能夠找到特徵與其標籤之間的某種相關性,並將用於未來的預測。
神經網絡
神經網絡是一組機器學習算法,它試圖通過使用人工神經元層來模仿大腦的工作方式。
我們不需要在本文中深入了解它們的工作原理,但是如果您想了解更多信息,這裡有一個非常好的視頻:
現在我們已經定義了一些機器學習中常用的術語,讓我們來談談使用 JavaScript 和 Tensorflow.js 框架可以做什麼。
特徵
目前提供三個功能:
- 使用預訓練模型,
- 遷移學習,
- 定義、運行和使用您自己的模型。
讓我們從最簡單的開始。
1. 使用預訓練模型
根據您嘗試解決的問題,可能已經有一個模型已經使用特定數據集和用於特定目的進行了訓練,您可以在代碼中加以利用和導入。
例如,假設我們正在構建一個網站來預測一張圖片是否是一張貓的圖片。 一種流行的圖像分類模型稱為MobileNet ,可作為帶有 Tensorflow.js 的預訓練模型使用。
代碼如下所示:
<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Cat detection</title> <script src="https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]"> </script> <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/[email protected]"> </script> </head> <body> <img alt="cat laying down" src="cat.jpeg"/> <script> const img = document.getElementById('image'); const predictImage = async () => { console.log("Model loading..."); const model = await mobilenet.load(); console.log("Model is loaded!") const predictions = await model.classify(img); console.log('Predictions: ', predictions); } predictImage(); </script> </body> </html>
我們首先在 HTML 的頭部導入 Tensorflow.js 和 MobileNet 模型:
<script src="https://cdnjs.cloudflare.com/ajax/libs/tensorflow/1.0.1/tf.js"> </script> <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/[email protected]"> </script>
然後,在 body 內部,我們有一個用於預測的圖像元素:
<img alt="cat laying down" src="cat.jpeg"/>
最後,在script
標籤內,我們有 JavaScript 代碼,它加載預訓練的 MobileNet 模型並對在image
標籤中找到的圖像進行分類。 它返回一個由 3 個預測組成的數組,這些預測按概率分數排序(第一個元素是最佳預測)。
const predictImage = async () => { console.log("Model loading..."); const model = await mobilenet.load(); console.log("Model is loaded!") const predictions = await model.classify(img); console.log('Predictions: ', predictions); } predictImage();
就是這樣! 這是您可以在瀏覽器中通過 Tensorflow.js 使用預訓練模型的方式!
注意:如果你想看看 MobileNet 模型還能分類什麼,你可以在 Github 上找到可用的不同類的列表。
需要了解的重要一點是,在瀏覽器中加載預訓練模型可能需要一些時間(有時長達 10 秒),因此您可能需要預加載或調整界面,以免影響用戶。
如果您更喜歡使用 Tensorflow.js 作為 NPM 模塊,您可以通過以下方式導入模塊:
import * as mobilenet from '@tensorflow-models/mobilenet';
隨意在 CodeSandbox 上玩這個例子。
現在我們已經了解瞭如何使用預訓練模型,讓我們看看第二個可用的功能:遷移學習。
2. 遷移學習
遷移學習是將預訓練模型與自定義訓練數據相結合的能力。 這意味著您可以利用模型的功能並添加自己的樣本,而無需從頭開始創建所有內容。
例如,一種算法已經用數千張圖像進行了訓練以創建圖像分類模型,而不是創建自己的圖像分類模型,遷移學習允許您將新的自定義圖像樣本與預先訓練的模型相結合以創建新的圖像分類器。 這個特性使得擁有一個更加定制化的分類器變得非常快速和容易。
為了提供代碼中的示例,讓我們重新利用之前的示例並對其進行修改,以便我們可以對新圖像進行分類。
注意:最終結果是下面的實驗,您可以在這裡嘗試。

以下是此設置中最重要部分的一些代碼示例,但如果您需要查看整個代碼,可以在此 CodeSandbox 上找到它。
我們仍然需要從導入 Tensorflow.js 和 MobileNet 開始,但是這次我們還需要添加一個 KNN(k-nearest neighbor)分類器:
<!-- Load TensorFlow.js --> <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script> <!-- Load MobileNet --> <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/mobilenet"></script> <!-- Load KNN Classifier --> <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/knn-classifier"></script>
我們需要分類器的原因是(而不是僅使用 MobileNet 模塊)我們正在添加以前從未見過的自定義樣本,因此 KNN 分類器將允許我們將所有內容組合在一起並對組合的數據進行預測。
然後,我們可以用video
標籤替換貓的圖像,以使用來自攝像頭的圖像。
<video autoplay width="227" height="227"></video>
最後,我們需要在頁面上添加一些按鈕,我們將用作標籤來記錄一些視頻樣本並開始預測。
<section> <button class="button">Left</button> <button class="button">Right</button> <button class="test-predictions">Test</button> </section>
現在,讓我們轉到 JavaScript 文件,我們將從設置幾個重要變量開始:
// Number of classes to classify const NUM_CLASSES = 2; // Labels for our classes const classes = ["Left", "Right"]; // Webcam Image size. Must be 227. const IMAGE_SIZE = 227; // K value for KNN const TOPK = 10; const video = document.getElementById("webcam");
在這個特定的示例中,我們希望能夠在我們的頭部向左或向右傾斜之間對網絡攝像頭輸入進行分類,因此我們需要兩個標記為left
和right
的類。

設置為 227 的圖像大小是視頻元素的大小(以像素為單位)。 根據 Tensorflow.js 示例,該值需要設置為 227 以匹配用於訓練 MobileNet 模型的數據格式。 為了能夠對我們的新數據進行分類,後者需要適應相同的格式。
如果你真的需要它更大,這是可能的,但你必須在將數據輸入 KNN 分類器之前轉換和調整數據大小。
然後,我們將 K 的值設置為 10。KNN 算法中的 K 值很重要,因為它代表了我們在確定新輸入的類別時考慮的實例數。
在這種情況下,值 10 意味著,在預測一些新數據的標籤時,我們將查看訓練數據中的 10 個最近鄰,以確定如何對新輸入進行分類。
最後,我們得到了video
元素。 對於邏輯,讓我們從加載模型和分類器開始:
async load() { const knn = knnClassifier.create(); const mobilenetModule = await mobilenet.load(); console.log("model loaded"); }
然後,讓我們訪問視頻源:
navigator.mediaDevices .getUserMedia({ video: true, audio: false }) .then(stream => { video.srcObject = stream; video.width = IMAGE_SIZE; video.height = IMAGE_SIZE; });
接下來,讓我們設置一些按鈕事件來記錄我們的示例數據:
setupButtonEvents() { for (let i = 0; i < NUM_CLASSES; i++) { let button = document.getElementsByClassName("button")[i]; button.onmousedown = () => { this.training = i; this.recordSamples = true; }; button.onmouseup = () => (this.training = -1); } }
讓我們編寫我們的函數,它將獲取網絡攝像頭圖像樣本,重新格式化它們並將它們與 MobileNet 模塊結合起來:
// Get image data from video element const image = tf.browser.fromPixels(video); let logits; // 'conv_preds' is the logits activation of MobileNet. const infer = () => this.mobilenetModule.infer(image, "conv_preds"); // Train class if one of the buttons is held down if (this.training != -1) { logits = infer(); // Add current image to classifier this.knn.addExample(logits, this.training); }
最後,一旦我們收集了一些網絡攝像頭圖像,我們就可以使用以下代碼測試我們的預測:
logits = infer(); const res = await this.knn.predictClass(logits, TOPK); const prediction = classes[res.classIndex];
最後,您可以處理我們不再需要的網絡攝像頭數據:
// Dispose image when done image.dispose(); if (logits != null) { logits.dispose(); }
再一次,如果您想查看完整的代碼,您可以在前面提到的 CodeSandbox 中找到它。
3. 在瀏覽器中訓練模型
最後一個功能是完全在瀏覽器中定義、訓練和運行模型。 為了說明這一點,我們將構建識別鳶尾花的經典示例。
為此,我們將創建一個神經網絡,基於開源數據集將鳶尾花分為三類:Setosa、Virginica 和 Versicolor。
在我們開始之前,這裡有一個現場演示的鏈接,如果你想使用完整的代碼,這裡是 CodeSandbox。
每個機器學習項目的核心都是數據集。 我們需要採取的第一步是將這個數據集拆分為訓練集和測試集。
這樣做的原因是我們將使用我們的訓練集來訓練我們的算法和我們的測試集來檢查我們的預測的準確性,以驗證我們的模型是否可以使用或需要調整。
注意:為方便起見,我已經將訓練集和測試集拆分為兩個 JSON 文件,您可以在 CodeSanbox 中找到它們。
訓練集包含 130 個項目,測試集 14 個。如果您查看這些數據的樣子,您會看到如下內容:
{ "sepal_length": 5.1, "sepal_width": 3.5, "petal_length": 1.4, "petal_width": 0.2, "species": "setosa" }
我們可以看到萼片和花瓣的長度和寬度的四個不同特徵,以及物種的標籤。
為了能夠將它與 Tensorflow.js 一起使用,我們需要將這些數據塑造成框架能夠理解的格式,在這種情況下,對於訓練數據,它將是[130, 4]
的 130 個樣本,每個樣本有四個特徵虹膜。
import * as trainingSet from "training.json"; import * as testSet from "testing.json"; const trainingData = tf.tensor2d( trainingSet.map(item => [ item.sepal_length, item.sepal_width, item.petal_length, item.petal_width ]), [130, 4] ); const testData = tf.tensor2d( testSet.map(item => [ item.sepal_length, item.sepal_width, item.petal_length, item.petal_width ]), [14, 4] );
接下來,我們還需要對輸出數據進行整形:
const output = tf.tensor2d(trainingSet.map(item => [ item.species === 'setosa' ? 1 : 0, item.species === 'virginica' ? 1 : 0, item.species === 'versicolor' ? 1 : 0 ]), [130,3])
然後,一旦我們的數據準備就緒,我們就可以繼續創建模型:
const model = tf.sequential(); model.add(tf.layers.dense( { inputShape: 4, activation: 'sigmoid', units: 10 } )); model.add(tf.layers.dense( { inputShape: 10, units: 3, activation: 'softmax' } ));
在上面的代碼示例中,我們首先實例化一個順序模型,添加一個輸入和輸出層。
您可以看到內部使用的參數( inputShape
、 activation
和units
)超出了本文的範圍,因為它們可能會根據您創建的模型、使用的數據類型等而有所不同。
一旦我們的模型準備就緒,我們就可以使用我們的數據對其進行訓練:
async function train_data(){ for(let i=0;i<15;i++){ const res = await model.fit(trainingData, outputData,{epochs: 40}); } } async function main() { await train_data(); model.predict(testSet).print(); }
如果這運作良好,您可以開始用自定義用戶輸入替換測試數據。
一旦我們調用我們的 main 函數,預測的輸出將看起來像以下三個選項之一:
[1,0,0] // Setosa [0,1,0] // Virginica [0,0,1] // Versicolor
預測返回一個由三個數字組成的數組,表示數據屬於三個類別之一的概率。 最接近 1 的數字是最高預測值。
例如,如果分類的輸出為[0.0002, 0.9494, 0.0503]
,則數組的第二個元素最高,因此模型預測新的輸入很可能是 Virginica。
這就是 Tensorflow.js 中的簡單神經網絡!
我們只討論了 Irises 的一個小數據集,但如果您想繼續使用更大的數據集或處理圖像,步驟將是相同的:
- 收集數據;
- 在訓練集和測試集之間拆分;
- 重新格式化數據以便 Tensorflow.js 可以理解它;
- 選擇你的算法;
- 擬合數據;
- 預測。
如果您想保存創建的模型以便能夠在另一個應用程序中加載它並預測新數據,您可以使用以下行來執行此操作:
await model.save('file:///path/to/my-model'); // in Node.js
注意:有關如何保存模型的更多選項,請查看此資源。
限制
而已! 我們剛剛介紹了目前使用 Tensorflow.js 可用的三個主要功能!
在我們結束之前,我認為有必要簡要提一下在前端使用機器學習的一些限制。
1. 性能
從外部源導入預訓練模型可能會對您的應用程序產生性能影響。 例如,某些對象檢測模型超過 10MB,這會大大降低您的網站速度。 確保考慮您的用戶體驗並優化資產的加載以提高您的感知性能。
2. 輸入數據的質量
如果您從頭開始構建模型,您將不得不收集自己的數據或找到一些開源數據集。
在進行任何類型的數據處理或嘗試不同的算法之前,請務必檢查輸入數據的質量。 例如,如果您嘗試構建情緒分析模型來識別文本中的情緒,請確保用於訓練模型的數據準確且多樣化。 如果使用的數據質量低,那麼您的訓練輸出將毫無用處。
3. 責任
使用開源預訓練模型可以非常快速且輕鬆。 但是,這也意味著您並不總是知道它是如何生成的、數據集是由什麼組成的,甚至不知道使用了哪種算法。 有些模型被稱為“黑匣子”,這意味著你並不真正知道它們是如何預測某個輸出的。
根據您要構建的內容,這可能是一個問題。 例如,如果您使用機器學習模型來幫助根據掃描圖像檢測某人患有癌症的概率,以防假陰性(模型預測一個人實際上沒有患癌症),有可能是一些真正的法律責任,你必須能夠解釋為什麼模型做出了某種預測。
概括
總之,使用 JavaScript 和 Tensorflow.js 等框架是入門和了解更多機器學習的好方法。 即使一個生產就緒的應用程序可能應該使用 Python 之類的語言構建,JavaScript 讓開發人員可以真正使用不同的功能並更好地理解基本概念,然後最終繼續前進並投入時間學習另一種語言語言。
在本教程中,我們只介紹了使用 Tensorflow.js 的可能性,但是,其他庫和工具的生態系統正在增長。 還提供了更多指定的框架,允許您使用機器學習探索其他領域,例如使用 Magenta.js 進行音樂,或使用guess.js 預測網站上的用戶導航!
隨著工具的性能越來越高,在 JavaScript 中構建支持機器學習的應用程序的可能性可能會越來越令人興奮,現在是了解更多關於它的好時機,因為社區正在努力使其易於訪問。
更多資源
如果您有興趣了解更多信息,這裡有一些資源:
其他框架和工具
- ml5.js
- 毫升.js
- 大腦.js
- Keras.js
- 姿勢網
- 張量流遊樂場
示例、模型和數據集
- TensorFlow.js 模型
- TensorFlow.js 示例
- 數據集
靈感
- 可示教機器
- 人工智能實驗
- AIJS.rocks
- 可創造性
謝謝閱讀!