
目錄示例代碼托管在:http://www.github.com/dashnowords/blogs
博客園地址:《大史住在大前端》原創博文目錄
- 一. 上手TensorFlow.js
- 二. 使用TensorFlow.js構建卷積神經網路
- 卷積神經網路
- 搭建LeNet-5模型
- 三. 基于遷移學習的語音指令識別
- 推薦課程
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已經非常易用,本章代碼倉中提供了一個完整的示例,你可以通過采集自己的聲音樣本來生成中文指令,然后重新訓練遷移模型,并嘗試用它來控制《吃豆人》游戲中的角色:

推薦課程
-
李宏毅 《深度學習》課程 (地址: http://speech.ee.ntu.edu.tw/~tlkagk/index.html)
-
吳恩達 《機器學習》在線教程(地址: https://www.coursera.org/learn/machine-learning)
-
MIT 6.S191《深度學習導論》(地址:http://introtodeeplearning.com/)
-
Stanford CS231.n《卷積神經網路與計算視覺》 (地址http://cs231n.stanford.edu/)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/117353.html
標籤:JavaScript
下一篇:cookie共享
