【一統江湖的大前端(9)】TensorFlow.js 開箱即用的深度學習工具

来源:https://www.cnblogs.com/dashnowords/archive/2020/04/18/12726956.html
-Advertisement-
Play Games

示例代碼托管在: "http://www.github.com/dashnowords/blogs" 博客園地址: "《大史住在大前端》原創博文目錄" [TOC] TensorFlow是Google推出的開源機器學習框架,並針對瀏覽器、移動端、IOT設備及大型生產環境均提供了相應的擴展解決方案,Te ...


示例代碼托管在:http://www.github.com/dashnowords/blogs

博客園地址:《大史住在大前端》原創博文目錄

目錄


TensorFlow是Google推出的開源機器學習框架,並針對瀏覽器、移動端、IOT設備及大型生產環境均提供了相應的擴展解決方案,TensorFlow.js就是JavaScript語言版本的擴展,在它的支持下,前端開發者就可以直接在瀏覽器環境中來實現深度學習的功能,嘗試過配置環境的讀者都知道這意味著什麼。瀏覽器環境在構建交互型應用方面有著天然優勢,而端側機器學習不僅可以分擔部分雲端的計算壓力,也具有更好的隱私性,同時還可以藉助Node.js在服務端繼續使用JavaScript進行開發,這對於前端開發者而言非常友好。除了提供統一風格的術語和API,TensorFlow的不同擴展版本之間還可以通過遷移學習來實現模型的復用(許多知名的深度學習模型都可以找到python版本的源代碼),或者在預訓練模型的基礎上來定製自己的深度神經網路,為了能夠讓開發者儘快熟悉相關知識,TensorFlow官方網站還提供了一系列有關JavaScript版本的教程、使用指南以及開箱即用的預訓練模型,它們都可以幫助你更好地瞭解深度學習的相關知識。對深度學習感興趣的讀者推薦閱讀美國量子物理學家Michael Nielsen編寫的《神經網路與深度學習》(英文原版名為《Neural Networks and Deep Learning》),它對於深度學習基本過程和原理的講解非常清晰。

一. 上手TensorFlow.js

Tensor(張量)是TensorFlow中的基本數據結構,它是向量和矩陣向更高維度的推廣,從編程的角度來看,它的核心數據不過就是多維數組。或許你還記得在【帶著canvas去流浪(9)】粒子動畫一文中為了方便向量計算而定義的二維向量類Vector2,事實上它就可以被看作是Tensor在二維空間的簡化形式。Tensor數據類型可以很方便地構造各種維度的張量,支持切片、變形、合併分割等結構操作,同時也定義了各類線性代數運算的操作符,這樣做的好處是可以將開發者在應用層編寫的程式和不同平臺的底層實現之間解耦。這樣,神經網路中的信息傳遞就通過張量(Tensor)的流動(Flow)表現出來了。在2018年Google I/O大會上,TensorFlow.js小組的工程師就介紹了該框架分層的結構設計,除了最底層為瞭解決編程語言和平臺差異的層次外,為了對不同的工作性質的開發者實現更好地支持,TensorFlow.js在應用層還提供了兩種不同的API:高階API被稱為Keras API(Keras是一個python編寫的開源人工神經網路庫)或Layer API,用於快速實現深度學習模型的構建、訓練、評估和應用,軟體和應用開發者大多情況下會使用它;低階API也被稱為Core API,通常用於支持研究人員對神經網路實現更底層的細節定製,使用起來難度也更高。

TensorFlow.js的工作依然是圍繞神經網路展開的,基本的工作過程包含瞭如下幾個典型步驟:

下麵我們將通過TensorFlow.js官方網站提供的數據擬合的示例來瞭解整個流程。

Define階段是使用TensorFlow.js的第一步,這個階段中需要初始化神經網路模型,你可以在TensorFlow的tf.layers對象上找到具備各種功能和特征的隱藏層,通過模型實例的add方法將其逐層添加到神經網路中,從而實現張量變形處理、捲積神經網路、迴圈神經網路等複雜模型,當內置模型無法滿足需求時,還可以自定義模型層,TensorFlow的高階API可以幫助開發者以聲明式的編碼來完成神經網路的結構搭建,示例代碼如下:

/*創建模型*/
function createModel() {
   const model = tf.sequential(); 
   model.add(tf.layers.dense({inputShape: [1], units: 1, useBias: true}));
   model.add(tf.layers.dense({units: 1, useBias: true}));
   return model;
}

Compile階段需要對訓練過程進行一些參數預設,你可以先溫習一下上一章中介紹過的BP神經網路的工作過程,然後再來理解下麵的示例代碼:

model.compile({
   optimizer: tf.train.adam(),
   loss: tf.losses.meanSquaredError,
   metrics: ['mse'],
});

loss(損失)用於定義損失函數,它是神經網路的實際輸出和期望輸出之間偏差的量化評估標準,最常用的損失函數就是均方差損失(tf.losses.meanSquaredError),其他損失函數可以在TensorFlow的API文檔中進行查看;optimizer(優化器)是指誤差反向傳播結束後,神經網路進行權重調整時所使用的的演算法。權重調整的目的就是為了使損失函數達到極小值,所以通常採用“梯度下降”的思想來進行逼近,梯度方向是指函數在某一點變化最顯著的方向,但實際的情況往往並沒有這麼簡單,假設下圖是一個神經網路的損失函數曲線:

可以看到損失函數的形態、初始參數的位置以及優化過程的步長等都可能對訓練過程和訓練結果產生影響,這就需要在optimizer配置項中指定優化演算法來達到較好的訓練效果;metrics配置項用於指定模型的度量指標,大多數情況下可以直接使用損失函數來作為度量標準。

Fit階段執行的是模型訓練的工作(fit本身是擬合的意思),通過調用模型的fit方法就可以啟動訓練迴圈,官方示例代碼如下(fit方法接收的參數分別為輸入張量集、輸出張量集和配置參數):

const batchSize = 32;
const epochs = 50;

await model.fit(inputs, labels, {
   batchSize,
   epochs,
   shuffle: true,
   callbacks: tfvis.show.fitCallbacks(
      { name: 'Training Performance' },
      ['loss', 'mse'], 
      { height: 200, callbacks: ['onEpochEnd'] }
   )
});

相關參數說明如下(其他參數可參考官方開發文檔):

  • batchSize(批大小)指每個迴圈中使用的樣本數,通常取值為32~512

  • epochs指定整個訓練集上的數據的總迴圈次數

  • shuffle指是否在每個epochs中打亂訓練樣本的次序

  • callbacks指定了訓練過程中的回調函數

神經網路的訓練是迴圈進行的,假設總訓練樣本大小為320個,那麼上面的示例代碼所描述的訓練過程是:先使用下標為031的樣本來訓練神經網路,然後使用optimizer來更新一次權重,再使用下標為3263的樣本進行訓練,再更新權重,直到總樣本中所有數據均被使用過一次,上述過程被稱為一個epoch,接著打亂整個訓練樣本的次序,再重覆共計50輪,callbacks回調函數參數直接關聯了tfvis庫,它是TensorFlow提供的專用可視化工具模塊。

Evaluate階段需要對模型的訓練結果進行評估,調用模型實例的evaluate方法就可以使用測試數據來獲得損失函數和度量標準的數值。你可能已經註意到TensorFlow在定製訓練過程時更加關註如何使用樣本數據,而並沒有將“度量指標小於給定閾值”作為訓練終止的條件(例如brain.js中就可以通過設置errorthresh參數),在複雜神經網路的構建和設計中,開發者很可能需要一邊構建一邊進行非正式的訓練測試,度量指標最終並不一定能夠降低到給定的閾值以下,以此作為訓練終止條件很可能會使訓練過程陷入無限迴圈,所以使用固定的訓練次數配合可視化工具來觀察訓練過程就更為合理。

Predict階段是使用神經網路模型進行預測的階段,這也是前端工程師參與度最高的部分,畢竟模型輸出的結果只是數據,如何利用這些預測結果來製作一些更有趣或者更加智能化的應用或許才是前端工程師更應該關註的問題。從前文的過程中不難看出,TensorFlow.js提供的能力是圍繞神經網路模型展開的,應用層很難直接使用,開發者通常都需要藉助官方模型倉庫中提供的預訓練模型或者使用其他基於TensorFlow.js構建的第三方應用,例如人臉識別框架face-api.js(它可以在瀏覽器端和Node.js中實現快速的人臉追蹤和身份識別),語義化更加明確的機器學習框架ml5.js(可以直接調用API來實現圖像分類、姿勢估計、人物摳圖、風格遷移、物體識別等更加具體的任務),可以實現手部跟蹤的handtrack.js等等,如果TensorFlow的相關知識讓你覺得過於晦澀,也可以先嘗試使用這些更高層的框架來構建一些有趣的程式。

二. 使用TensorFlow.js構建捲積神經網路

捲積神經網路

捲積神經網路(Convolutional Neural Networks,簡稱CNN)是計算視覺領域應用非常廣泛的深度學習模型,它在處理圖片或其他具有網格狀特征的數據時具有非常好的表現。在信息處理時,捲積神經網路會先保持像素的行列空間結構,通過多個數學計算層來進行特征提取,然後再將信號轉換為特征向量將其接入傳統神經網路的結構中,經過特征提取的圖像所對應的特征向量在提供給傳統神經網路時體積更小,需要訓練的參數數量也會相應減少。捲積神經網路的基本工作原理圖如下(圖中各個層的數量並不是固定的):

為了搞清楚捲積網路的工作流程,需要先瞭解捲積池化這兩個術語的含義。

捲積層需要對輸入信息進行捲積計算,它使用一個網格狀的視窗區(也被稱為捲積核或過濾器)對輸入圖像進行遍歷加工,過濾器的每個視窗單元通常都具有自己的權重,從輸入圖像的左上角開始,將權重和視窗覆蓋區域的數值相乘並累加後得到一個新的結果,這個結果就是該區域映射後的值,接著將過濾器視窗向右滑動固定的距離(通常為1個像素),然後重覆前面的過程,當過濾器視窗的右側和輸入圖像的右邊界重合後,視窗向下移動同樣的距離,再次從左向右重覆前面的過程,直到所有的區域遍歷完成後就可以得到新的行列數據。每將一個不同的過濾器應用於輸入圖像後,捲積層就會增加一個輸出,真實的深度網路中可能會使用多個過濾器,所以在捲積神經網路的原理圖中通常會看到捲積層有多個層疊的圖像。不難計算,對於一個輸入尺寸為MM的圖像,使用NN的過濾器處理後,新圖像的單邊尺寸為M-N+1。例如一個輸入尺寸是88的灰度圖,使用33過濾器對其進行捲積計算後,就會得到一個6*6的新圖片,如下圖所示:

不同的過濾器可以識別出圖像中不同的微小特征,例如上圖中的過濾器,對於一個33大小的純色區域,捲積計算的結果均為0,假設現在有一個上白下黑的邊界,那麼過濾器中上側的計算結果會非常小,而中間一行和下麵一行的結果都接近0,捲積計算的累加結果也會映射為一個很小的負數,相當於過濾器將一個33區域內的典型特征記錄在1個像素中,也就達到了特征提取的目的,很明顯,如果將上面的過濾器旋轉90°,就可以用來識別圖像中的垂直邊界。由於捲積計算會將一個區域內的特征縮小到一個點上,所以捲積層的輸出信息也被稱為特征映射圖。本章的代碼倉中筆者基於canvas實現了一個簡單的捲積計算程式,你可以在源碼中修改過濾器的參數來觀察處理後的圖像,這就好像是在給圖片添加各種有趣的濾鏡一樣:

上圖分別展示了水平邊緣檢測、垂直邊緣檢測和斜線邊緣檢測處理後的效果。

再來看看池化層(也被稱為混合層、合併層或下採樣層),它通常緊接著捲積層之後來使用。圖像中相鄰像素的值通常比較接近,這會導致捲積層輸出結果的產生大量信息冗餘,比如一個水平邊緣在捲積層中周圍的像素可能也檢測到了水平邊緣,但事實上它們表示的是原圖中的同一個特征,池化層的目的是就是簡化捲積層的輸出信息,它輸出的每個單元可以被認為概括了前一層中一個區域的特征,常用的最大池化層就是在區域內選取一個最大值來作為整個區域在池化層的映射(這並不是唯一的池化計算方法),假設前文示例中的66的捲積層輸出後緊接著一個使用22大小的視窗來進行區域映射的最大池化層,那麼最終將得到一個3*3的圖像輸出,過程如下圖所示:

可以看到,在不考慮深度影響時,示例中8*8的輸入圖像經過捲積層和池化層的處理後已經變成3*3大小了,對於後續的全連接神經網路而言,輸入特征的數量已經大幅減少了。本章代碼倉庫中也提供了經過“捲積層+最大池化層”處理後圖像變化的可視化示例,直觀效果其實就是圖片縮放,可以看到縮放後的圖片仍然保持了池化前的典型特征:

在對複雜畫面進行分析時,“捲積+池化”的模式可能會在網路中進行多次串聯,以便可以從圖像中逐級提取特征。在實際開發過程中,為瞭解決具體的計算視覺問題,開發者很可能需要自己去查閱相關學術論文並搭建相關的深度學習網路,它們通常使用非常簡潔的符號來表示,下一節中我們將以經典的LeNet-5模型為例來學習相關的知識。

搭建LeNet-5模型

LeNet-5是一種高效的捲積神經網路模型,幾乎在所有以MNIST手寫數字圖像識別為例的教程中都會介紹它,LeNet-5是論文《Gradient-Based Learning Applied to Document Recognition》中提出的,論文中給出的結構示意圖如下:

可以看到模型中一共有7層,其含義和相關解釋如下表所示:

序號 類別 標記 細節
/ 輸入層 INPUT 32X32 輸入為32x32像素的圖片
C1 捲積層 C1:feature maps 6@28x28 捲積層,輸出特征圖共6個,每個尺寸為28x28(捲積核尺寸為5x5)
S2 池化層 S2:f.maps6@14x14 池化層,對前一層的輸出進行降採樣,輸出特征映射圖共6個,每個尺寸14x14(降採樣視窗尺寸為2x2)
C3 捲積層 C3:f.maps16@10x10 捲積層,輸出特征圖共16個,每個尺寸為10x10(捲積核尺寸為5x5)
S4 池化層 S4:f.maps16@5x5 池化層,對前一層的輸出進行降採樣,輸出特征映射圖共16個,每個尺寸5x5(降採樣視窗尺寸為2x2)
C5 捲積層 C5:layer 120 捲積層,輸出特征圖共120個,每個尺寸為1x1(捲積核尺寸為5x5)
F6 全連接層 F6:layer 84 全連接層,使用84個神經元
/ 輸出層 OUTPUT 10 輸出層,10個節點,代表0~9共10個數字

在完成類似的圖片分類任務時,構建的捲積神經網路並不需要完全與LeNet-5模型保持完全一致,只需要根據實際需求對它進行微調或擴展即可,例如在TensorFlow.js官方的“利用CNN識別手寫數字”教程中,就在C1層使用了8個捲積核,並去掉了整個F6全連接層,即便這樣依然能夠獲得不錯的識別率。TensorFlow.js提供的layers API可以很方便地生成定製的捲積層和池化層,示例代碼如下:

model = tf.sequential();

//添加LeNet-5中的 C1層
model.add(tf.layers.conv2d({
   inputShape: [32, 32, 1],//輸入張量的形狀
   kernelSize: 5, //捲積核尺寸
   filters: 6, //捲積核數量
   strides: 1, //捲積核移動步長
   activation: 'relu', //激活函數
   kernelInitializer: 'varianceScaling' //捲積核權重初始化方式
}));

//生成LeNet-5中的 S2層
model.add(tf.layers.maxPooling2d({
   poolSize: [2, 2],//滑動視窗尺寸
   strides: [2, 2]//滑動視窗移動步長
}));

官方教程提供的示例代碼使用tfjs-vis庫對訓練過程進行了可視化,你可以很清楚地看到神經網路的結構、訓練過程中度量指標的變化以及測試數據的預測結果彙總等信息:

三. 基於遷移學習的語音指令識別

複雜的深度學習模型通常具有上百萬的參數,即便能夠重新搭建起整個神經網路,中小型開發者也沒有足夠的數據和機器資源來從頭訓練它,這就需要開發者將已經在相關任務中訓練過的模型復用到新的模型中,從而降低深度學習模型搭建和訓練的天然門檻,讓更多的應用層開發這可以參與進來。

遷移學習是指一個使用數據集A完成訓練的模型,被用於解決和另一個數據集B相關的任務,這通常需要對模型進行一些調整並使用數據集B重新訓練它。幸運的是,有了A數據集訓練結果的基礎,重新訓練模型時需要的新樣本數和訓練的時間都會大幅減少。調整預訓練模型的基本方法是將它的輸出層替換為自己需要的形式,而保留其他特征提取網路的部分,對於同類型的任務而言,被保留的部分依然可以完成特征提取的任務,並對類似的信號進行分類,但如果數據集A和數據集B的特征差異過大,新的模型仍有可能無法達到期望的效果,就需要對預訓練模型進行更多的定製和改造(比如調整捲積神經網路中的捲積層和池化層的數量或參數),相關的理論和方法本章中不再展開。TensorFlow.js官方提供了的預訓練模型可以實現圖像分類、對象檢測、姿勢估計、面部追蹤、文本惡意檢測、句子編碼、語音指令識別等等非常豐富的功能,本節中就以“語音指令識別”功能為例來瞭解遷移學習相關的技術。

TensorFlow.js官方語音識別模型speech-commands每次可以針對長度為1秒的音頻片段進行分類,它已經使用近5萬個聲音樣本進行過訓練,直接使用時可以識別英文發音的數字(如zero ~ nine)、方向(up,down,right,left)和一些簡單指令(如yes,no等),在這個預訓練模型的基礎上,只要通過少量的新樣本就可以將它改造為一個中文指令識別器,是不是很方便?一段音頻信號在處理時,會先通過快速傅里葉變換將其轉換為頻域信號,然後提取特征將其送入深度學習網路進行分析,對於簡易指令的使用場景而言,只需要對若幹個聲音指令進行分類就可以了,並不需要電腦進行語種或真實語義分析,所以一個英文指令識別器才可以方便地改造為中文指令識別工具。語音指令功能的本質是對短語音進行分類,例如訓練中將“向左”的聲音片段標記為“右”,訓練後的神經網路在聽到“向左”時就會將其歸類為“右”,使用預訓練模型speech-command實現遷移學習的基本步驟如下:

官方提供的擴展庫將具體的實現封裝起來,提供給開發者的應用層API已經非常易用,本章代碼倉中提供了一個完整的示例,你可以通過採集自己的聲音樣本來生成中文指令,然後重新訓練遷移模型,並嘗試用它來控制《吃豆人》游戲中的角色:

推薦課程


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 網路載入視圖,在一個聯網的APP上可以講得上是必須要的組件,在SwiftUI中它並沒有提供如 UIKit 中的UIActivityIndicatorView直接提供給我們調用,但是我們可以通過 SwiftUI 中的UIViewRepresentable協議封裝UIActivityIndicatorV... ...
  • 新建一個空的項目,確保你的空項目能運行起來 如何新建Android項目?https://www.cnblogs.com/xqz0618/p/first_app.html 導入其他項目 什麼情況下需要這樣去改配置? import 導入其他項目,載入完成之後,如果你的其他項目在Android視圖下顯示卻 ...
  • 什麼是MMKV? MMKV 是基於 mmap 記憶體映射的移動端通用 key-value 組件,底層序列化/反序列化使用 protobuf 實現,性能高,穩定性強。 https://github.com/Tencent/MMKV 為什麼要替代SharedPreferences? 首先 安全性好。 SP ...
  • 使用USB線把手機和電腦連接,最好是使用手機原裝的線,有些線可能不支持文件傳輸。 確保你手機的開發者模式打開 如何打開開發者模式? ①打開手機 設置 --> 系統 --> 關於手機 --> 找到版本號,點擊版本號 “七次”,直到頁面跳轉,輸入密碼後開發者模式打開 ②返回到 設置裡面的系統,打開開發者 ...
  • ES6支持在定義函數的時候為其設置預設值: function foo(height = 50, color = 'red') { console.log(height); console.log(color); } foo(0, ""); 普通函數: function foo(height, col ...
  • 普通函數:this 永遠指向調用它的對象,new的時候,指向new出來的對象。 箭頭函數:箭頭函數沒有自己的 this,當在內部使用了 this時,它會指向最近一層作用域內的 this。 //例1 var obj = { name: 'latency', sayName: function(){ c ...
  • 構造函數: function Foo(name,age){ this.name=name; this.age=age; this.class='class-1'; } var f=new Foo('cyy',18); 構造函數--擴展: 所有的引用類型都是構造函數 var a={} 是 var a= ...
  • 現在有四個主功能變數名稱的網站,需要共用cookie的invite_id PHP 通過後臺介面設置cookie和存儲cookies,後臺操作的cookie是介面功能變數名稱底下的cookie【pass】 Javascript: 主功能變數名稱a.com b.com 【其他的功能變數名稱同理】 a.com獲取到b.com底下存儲的c ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...