科普 | 從TensorFlow.js入手了解機(jī)器學(xué)習(xí)
對(duì)前端開(kāi)發(fā)者來(lái)說(shuō)了解機(jī)器學(xué)習(xí)是一件有挑戰(zhàn)性的事情。我學(xué)習(xí)機(jī)器學(xué)習(xí)沒(méi)有多久,在這個(gè)領(lǐng)域是個(gè)新手,在本文里我將嘗試用自己的理解去解釋一些概念。
不過(guò),在使用已有的 AI 模型的時(shí)候我們并不需要很深的機(jī)器學(xué)習(xí)知識(shí)。我們可以使用現(xiàn)有的一些工具比如 Keras、TensorFlow 或 TensorFlow.js。這里我們將看看如何創(chuàng)建 AI 模型并且使用 TensorFlow.js 中的一些復(fù)雜的模型。
雖說(shuō)不需要很深的知識(shí),不過(guò)還是讓我來(lái)解釋一些基本概念。
什么是模型?
或者更好的問(wèn)題是:什么是現(xiàn)實(shí)?是的,這很難回答,我們必須簡(jiǎn)化問(wèn)題以便理解。
表現(xiàn)部分簡(jiǎn)化版現(xiàn)實(shí)的一個(gè)方法就是使用模型。所以,你可以認(rèn)為有無(wú)數(shù)個(gè)模型,如世界地圖、圖表等等。
沒(méi)有機(jī)器參與其中的模型我們更易于理解。比如,如果我們想要?jiǎng)?chuàng)建一個(gè)模型來(lái)表示隨房間數(shù)變化 Barcelona 房子價(jià)格的變化。
首先,我們需要收集一些數(shù)據(jù):
Number of rooms | Prices |
---|---|
3 | 131.000€ |
3 | 125.000€ |
4 | 235.000€ |
4 | 265.000€ |
5 | 535.000€ |
然后,我們將這兩個(gè)數(shù)據(jù)使用一個(gè) 2D 圖形展示,每個(gè)坐標(biāo)軸對(duì)應(yīng)一個(gè)參數(shù)。
然后...Duang! 我們現(xiàn)在可以畫(huà)一條線并且預(yù)測(cè)有 6 個(gè)以上房間的房子價(jià)格。
這個(gè)模型叫做線性回歸,這是機(jī)器學(xué)習(xí)里最簡(jiǎn)單的模型之一。
當(dāng)然,這個(gè)模型還不夠好:
- 只有 5 個(gè)樣本,結(jié)果不夠可信
- 只有兩個(gè)參數(shù),但其實(shí)影響房子價(jià)格有更多的因素,如地理位置、房子年齡等
對(duì)于***個(gè)問(wèn)題,我們可以添加樣本數(shù)來(lái)解決,比如添加 100 萬(wàn)個(gè)數(shù)據(jù)。
對(duì)第二個(gè)問(wèn)題,我們可以添加更多的坐標(biāo)軸。在 2D 圖形上我們可以畫(huà)一條直線,在 3D 坐標(biāo)軸里我們可以畫(huà)一個(gè)平面。
但是,如何處理 3D 以上的情形,比如 4D 甚至是 1000000D 呢?
我們的大腦無(wú)法想象多維下的圖表,不過(guò)好消息是,我們可以用數(shù)學(xué)和計(jì)算超平面來(lái)處理這種情況,而神經(jīng)網(wǎng)絡(luò)是一個(gè)很好的處理工具。
順便,使用 TensorFlow.js 不需要成為數(shù)學(xué)專(zhuān)家。
什么是神經(jīng)網(wǎng)絡(luò)?
在理解神經(jīng)網(wǎng)絡(luò)之前,讓我們先來(lái)看看什么是神經(jīng)。
現(xiàn)實(shí)世界里的神經(jīng)差不多長(zhǎng)這樣:
神經(jīng)中最重要的部分包括:
- 樹(shù)狀突(Dendrites):數(shù)據(jù)輸入的地方。
- 軸突(Axon):輸出端。
- 突觸(Synapse):神經(jīng)之間進(jìn)行交流的結(jié)構(gòu)。它負(fù)責(zé)將電信號(hào)從神經(jīng)軸突的末端傳遞到附近神經(jīng)的樹(shù)狀突。這些突觸結(jié)構(gòu)是學(xué)習(xí)的關(guān)鍵,因?yàn)樗鼈冊(cè)谑褂弥袝?huì)增減電信號(hào)的活動(dòng)。
在機(jī)器學(xué)習(xí)中的神經(jīng)則是 (簡(jiǎn)化后):
- 輸入(Input):輸入的參數(shù)。
- 權(quán)值(Weight):和突觸一樣,它們以增減來(lái)調(diào)整神經(jīng)的活動(dòng)來(lái)達(dá)成更好的線性回歸。
- 線性函數(shù) (Linear function):每個(gè)神經(jīng)就像一個(gè)線性回歸函數(shù),目前為止一個(gè)線性回歸函數(shù)只需要一個(gè)神經(jīng)。
- 激活函數(shù) (Activation function):我們能提供一些激活函數(shù)來(lái)改變從一個(gè)標(biāo)量 (Scalar) 到另一個(gè)非線性的函數(shù)。比如:sigmoid、RELU、tanh。
- 輸出 (Output):經(jīng)過(guò)激活函數(shù)計(jì)算后的輸出結(jié)果。
激活函數(shù)的使用非常有用,它是神經(jīng)網(wǎng)絡(luò)的精髓所在。沒(méi)有激活函數(shù)的話(huà)神經(jīng)網(wǎng)絡(luò)不可能很智能。原因是盡管在網(wǎng)絡(luò)中你可能有很多神經(jīng),神經(jīng)網(wǎng)絡(luò)的輸出總會(huì)是一個(gè)線性回歸。我們需要一些機(jī)制來(lái)改變這個(gè)獨(dú)立的線性回歸為非線性的以解決非線性的問(wèn)題。
感謝這些激活函數(shù),我們可以將這些線性函數(shù)轉(zhuǎn)換到非線性函數(shù):
訓(xùn)練模型
在上面的 2D 線性回歸示例里,在圖表中畫(huà)條線就足以讓我們開(kāi)始預(yù)測(cè)新數(shù)據(jù)了。然而,“深度學(xué)習(xí)”的概念是要讓我們的神經(jīng)網(wǎng)絡(luò)學(xué)著畫(huà)這條線。
畫(huà)一條簡(jiǎn)單的線我們只需要包括一條神經(jīng)的非常簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò),但其它的模型做的要復(fù)雜的多,比如歸類(lèi)兩組數(shù)據(jù)。在這種情況下,“訓(xùn)練”將會(huì)學(xué)習(xí)如何畫(huà)出下面的圖像:
這還不算復(fù)雜的,因?yàn)檫@只是 2D 范疇內(nèi)。
每個(gè)模型都是一個(gè)世界,所有這些模型的訓(xùn)練的概念都差不多。首先是畫(huà)一條隨機(jī)的線,然后在一個(gè)循環(huán)算法中改進(jìn)它,修復(fù)每個(gè)循環(huán)中的錯(cuò)誤。這種優(yōu)化算法又叫做梯度下降法 (Gradient Descent),還有更多復(fù)雜的算法如 SGD、ADAM,概念都類(lèi)似。
為了理解梯度下降法,我們需要知道每個(gè)算法 (線性回歸、邏輯回歸等) 有不同的代價(jià)函數(shù) (cost function) 來(lái)度量這些錯(cuò)誤。
代價(jià)函數(shù)總會(huì)收斂于某個(gè)點(diǎn),它可能是凸或非凸函數(shù)。***的收斂點(diǎn)將在 0% 錯(cuò)誤時(shí)被發(fā)現(xiàn),我們的目標(biāo)就是到達(dá)這個(gè)點(diǎn)。
但我們使用梯度下降算法時(shí),我們開(kāi)始于一個(gè)隨機(jī)的點(diǎn),但是我們不知道它在哪。想象一下你在一座山上,完全失明,然后你需要一步一步的下山,走到***的位置。如果地形復(fù)雜 (像非凸函數(shù)),下降過(guò)程將更加復(fù)雜。
我不會(huì)深入的解釋什么是梯度下降算法。你只需要記住它是一種優(yōu)化算法,用來(lái)訓(xùn)練 AI 模型以最小化預(yù)測(cè)產(chǎn)生的錯(cuò)誤。這個(gè)算法需要時(shí)間和 GPU 來(lái)計(jì)算矩陣乘法。收斂點(diǎn)通常在***輪執(zhí)行中難以達(dá)到,所以我們需要對(duì)一些超參數(shù) (hyperparameter) 如學(xué)習(xí)率(learning rate)進(jìn)行調(diào)優(yōu),或者添加一些正則化 (regularization)。
經(jīng)過(guò)反復(fù)的梯度下降法,我們達(dá)到了離收斂點(diǎn)很近的地方,錯(cuò)誤率也接近 0%。這時(shí)候,我們的模型就創(chuàng)建成功,可以開(kāi)始進(jìn)行預(yù)測(cè)了。
使用 TensorFlow.js 來(lái)訓(xùn)練模型
TensorFlow.js 給我們提供了一個(gè)簡(jiǎn)單的辦法來(lái)創(chuàng)建神經(jīng)網(wǎng)絡(luò)。
首先,我們將先創(chuàng)建一個(gè) LinearModel 類(lèi),添加trainModel方法。
對(duì)這類(lèi)模型我將使用一個(gè)序列模型 (sequential model),序列模型指的是某一層的輸出是下一層的輸入,比如當(dāng)模型的拓?fù)浣Y(jié)構(gòu)是一個(gè)簡(jiǎn)單的棧,不包含分支和跳過(guò)。
在trainModel方法里我們將定義層 (只需要使用一個(gè),這對(duì)于線性回歸問(wèn)題來(lái)說(shuō)足夠了):
- 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
- }
該類(lèi)的使用方法:
- const model = new LinearModel();
- // xs and ys -> array of numbers (x-axis and y-axis)
- await model.trainModel(xs, ys);
訓(xùn)練結(jié)束后,我們可以開(kāi)始進(jìn)行預(yù)測(cè)了!
使用 TensorFlow.js 進(jìn)行預(yù)測(cè)
預(yù)測(cè)的部分通常會(huì)簡(jiǎn)單些。訓(xùn)練模型需要定義一些超參數(shù),相比之下,進(jìn)行預(yù)測(cè)很簡(jiǎn)單。我們將在 LinearRegressor 類(lèi)里添加該方法:
- 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()
- )
- }
- }
現(xiàn)在,我們?cè)诖a里使用預(yù)測(cè)方法
- const prediction = model.predict(500); // Predict for the number 500
- console.log(prediction) // => 420.423
你可以在線運(yùn)行一下這段代碼:
https://stackblitz.com/edit/linearmodel-tensorflowjs-react
在 TensorFlow.js 中使用訓(xùn)練好的模型
學(xué)習(xí)如何創(chuàng)建模型是最難的部分,正?;?xùn)練數(shù)據(jù),正確選擇所有的超參數(shù),等等。如果你是新手并且想使用某些模型玩玩,你可以使用訓(xùn)練好的模型。
有很多模型都可以在 TensorFlow.js 中使用,而且,你可以使用 TensorFlow 或 Keras 創(chuàng)建模型,然后導(dǎo)入到 TensorFlow.js。
比如,你可以使用 posenet 模型 (實(shí)時(shí)人類(lèi)姿態(tài)模擬) 來(lái)做些好玩的事情:
代碼在: https://github.com/aralroca/posenet-d3
它的使用非常簡(jiǎn)單:
- 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
- );
poses 變量在這個(gè) JSON 文件里:
- {
- "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
- ]
- }
想象一下僅僅這個(gè)模型就可以做多少有趣的事情??!
上面這個(gè)示例代碼在: https://github.com/aralroca/fishFollow-posenet-tfjs
從 Keras 導(dǎo)入模型
我們可以從外部導(dǎo)入模型到 TensorFlow.js,在下面的例子里,我們將使用一個(gè) Keras 的模型來(lái)進(jìn)行數(shù)字識(shí)別 (文件格式為 h5)。為了達(dá)到目的,我們需要使用 tfjs_converter。
- pip install tensorflowjs
然后,使用轉(zhuǎn)換工具:
- tensorflowjs_converter --input_format keras keras/cnn.h5 src/assets
現(xiàn)在,你可以將模型導(dǎo)入到 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 中的數(shù)字識(shí)別模型。當(dāng)然,我們還可以加入一些更好玩的邏輯,比如,添加一個(gè) canvas 來(lái)畫(huà)一個(gè)數(shù)字,然后捕捉圖像來(lái)識(shí)別數(shù)字。
代碼: https://github.com/aralroca/MNIST_React_TensorFlowJS
為何在瀏覽器運(yùn)行 AI?
如果硬件不行,在瀏覽器上訓(xùn)練模型可能效率非常低下。TensorFlow.js 借助了 WebGL 的接口來(lái)加速訓(xùn)練,但即使這樣它也比 TensorFlow Python 版本要慢 1.5-2 倍。
但是,在 TensorFlow.js 之前,我們基本不可能不靠 API 交互在瀏覽器使用機(jī)器學(xué)習(xí)模型。現(xiàn)在我們可以在我們的應(yīng)用里 離線的 訓(xùn)練和使用模型。并且,無(wú)需與服務(wù)端交互讓預(yù)測(cè)變得更快。
另一個(gè)好處是在瀏覽器執(zhí)行這些計(jì)算可以降低服務(wù)器開(kāi)銷(xiāo),節(jié)省成本。
結(jié)論
- 模型是我們用于展現(xiàn)現(xiàn)實(shí)某一部分的簡(jiǎn)化手段,可以用來(lái)進(jìn)行預(yù)測(cè)。
- 創(chuàng)建模型的一個(gè)好方式是使用神經(jīng)網(wǎng)絡(luò)。
- 創(chuàng)建神經(jīng)網(wǎng)絡(luò)的簡(jiǎn)單易用方式是 TensorFlow.js。