整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          瀏覽器中實現深度學習?有人分析了7個基于JS語言的DL框架

          器之心分析師網絡

          作者:仵冀穎

          編輯:H4O

          本文中,作者基于WWW’19 論文提供的線索,詳細解讀了在瀏覽器中實現深度學習的可能性、可行性和性能現狀。具體而言,作者重點分析了 7 個最近出現的基于JavaScript 的 DL 框架,并對比了具體框架支持哪些 DL 任務。

          深度學習(Deep Learning,DL)是一類利用多層非線性處理單元(稱為神經元)進行特征提取和轉換的機器學習算法。每個連續(xù)層使用前一層的輸出作為輸入。近十年來,深度學習技術的進步極大地促進了人工智能的發(fā)展。大量的人工智能應用,如圖像處理、目標跟蹤、語音識別和自然語言處理,都對采用 DL 提出了迫切的要求。因此,各種 DL 框架(Frameworks)和庫(Libraries),如 TensorFlow、Caffe、CNTK 等,被提出并應用于實踐。目前,這些應用程序可以在諸如 Windows、Linux、MacOS/iOS 和 Android 等異構開發(fā)環(huán)境上運行,此外,可以使用各種命令式編程語言開發(fā),例如 Windows 上的 C/C++、iOS 和 MacOS 上的 Objective-C,以及 Android 上的 Java。不過,受限于 DL 框架和庫的特點,例如訓練數據量大、網絡結構復雜、網絡層級多、參數多等,通過本機程序調用運行 DL 的 AI 算法或模型的運算量非常大。

          最近,關于 DL 的一種應用趨勢是應用程序直接在客戶端中執(zhí)行 DL 任務,以實現更好的隱私保護和獲得及時的響應。其中,在 Web 瀏覽器中實現 DL,成為了人工智能社區(qū)關于客戶端 DL 支持的重要研究目標。瀏覽器中的 DL 是用 JavaScript 實現的,依靠瀏覽器引擎來執(zhí)行。基于 DL 的 Web 應用程序可以部署在所有平臺的瀏覽器中,而不管底層硬件設備類型(PC、智能手機和可穿戴設備)和操作系統(tǒng)(Windows、Mac、iOS 和 Android)。這就使得在瀏覽器中實現 DL 具有非常良好的適配性能和普適性能,不會對客戶端的選擇有諸多限制。另外,HTML5、CSS3,特別是 JavaScript 語言的進步,使得支持創(chuàng)建 DL 驅動的 Web 應用程序具有良好的性能。得益于 WebGL 的發(fā)展,目前主流瀏覽器如 Google Chrome、Mozilla FireFox、Safari 等,都可以更好地利用顯卡來加速 DL 任務。

          不過,是不是我們現在就可以隨心所欲的在瀏覽器中運行 DL 的模型或算法了?我們已經成功邁入在瀏覽器中實現深度學習的時代了么?盡管上面介紹的內容似乎意味著使在瀏覽器中運行 DL 任務成為可能,但是目前對于可以執(zhí)行哪些 DL 任務以及 DL 在瀏覽器中的工作效果卻缺少深入的研究和分析。更重要的是,考慮到 Web 應用程序與本機應用程序性能的長期爭論,開發(fā)基于 DL 的 Web 應用程序時也存在同樣的問題。在這篇文章中,我們以北京大學研究人員發(fā)表在 WWW’19(The World Wide Web Conference 2019)中的文章《Moving Deep Learning intoWeb Browser: How Far CanWe Go?》[1]作為參考主線,具體分析和探討在瀏覽器中實現深度學習的問題。

          1、瀏覽器中支持的深度學習功能

          在這一章節(jié)中,我們以文獻 [1] 為參考主線,重點探討現有的框架提供了哪些特性來支持在瀏覽器中實現各種 DL 任務。我們首先介紹了進行分析的幾個框架,然后從兩個方面比較了這些框架的特性:提供的功能和開發(fā)人員的支持。對于所提供的功能,主要檢查每個框架是否支持 DL 應用程序開發(fā)中常用的一些基本功能。而對于開發(fā)人員支持,主要討論一些可能影響開發(fā)和部署 DL 應用程序效率的因素。

          1.1 選擇的框架

          為了選擇最新的瀏覽器支持的 DL 框架,作者在 GitHub 上搜索關鍵字“deep learning framework”,并用 JavaScript 語言過濾結果。然后選擇了 GitHub 上星數超過 1000 的前 7 個框架[1]。對每個框架的具體介紹如下:

          TensorFlow.js[2] :2018 年 3 月由 Google 發(fā)布,是一個 inbrowser 機器學習庫,支持使用 JavaScript 在瀏覽器中定義、訓練和運行模型。TensorFlow.js 由 WebGL 提供支持,并提供用于定義模型的高級 API。TensorFlow.js 支持所有 Keras 層(包括 Dense、CNN、LSTM 等)。因此,很容易將原生 TensorFlow 和 Keras 預先訓練的模型導入到瀏覽器中并使用 Tensorflow.js。

          ConvNetJS[3] :是一個 Javascript 庫,最初由斯坦福大學的 Andrej Karpathy 編寫。ConvNetJS 目前支持用于分類和回歸的常用神經網絡模型和代價函數。此外,它還支持卷積網絡和強化學習。然而遺憾的是,盡管 ConvNetJS 可能是在 TensorFlow.js 之前最著名的框架,但其在 2016 年 11 月后已經不再維護了。

          Keras.js[4]:抽象出許多框架作為后端支撐,包括 TensorFlow、CNTK 等。它支持導入 Keras 預先訓練的模型進行推理。在 GPU 模式下,Keras.js 的計算由 WebGL 執(zhí)行。然而,這個項目也已經不再活躍。

          WebDNN[5]:由東京大學發(fā)布的 WebDNN 號稱是瀏覽器中最快的 DNN 執(zhí)行框架。它只支持推理(訓練)任務。該框架支持 4 個執(zhí)行后端:WebGPU、WebGL、WebAssembly 和 Fallback pure JavaScript 實現。WebDNN 通過壓縮模型數據來優(yōu)化 DNN 模型,以加快執(zhí)行速度。

          brain.js[6]:是一個用于神經網絡的 JavaScript 庫,它取代了不推薦使用的 “brain” 庫。它為訓練任務提供 DNN、RNN、LSTM 和 GRU。該庫支持將訓練好的 DL 模型的狀態(tài)用 JSON 序列化和加載。

          synaptic[7]:這是一個不依賴于 JavaScript 架構的神經網絡庫,基本上支持任何類型的一階甚至二階 RNN。該庫還包括一些內置的 DL 架構,包括多層感知器、LSTM、液態(tài)機(Liquid state machines)和 Hopfield 網絡。

          Mind[8]:這是一個靈活的神經網絡庫。核心框架只有 247 行代碼,它使用矩陣實現來處理訓練數據。它支持自定義網絡拓撲和插件,以導入 mind 社區(qū)創(chuàng)建的預訓練模型。然而,這個項目也已經不再活躍。

          1.2 所提供的功能

          訓練支持

          大多數框架都支持在瀏覽器中完成訓練和推理任務。然而,Keras.js 和 WebDNN 不支持在瀏覽器中訓練 DL 模型,它們只支持加載預訓練的模型來執(zhí)行推理任務。

          支持的網絡類型

          有些框架并不是針對通用 DL 任務的,所以它們支持的網絡類型有所不同。具體來說,TensorFlow.js、Keras.js 和 WebDNN 支持三種網絡類型:DNN、CNN 和 RNN。但 ConvNetJS 主要支持 CNN 任務,不支持 RNN。brain.js 和 synaptic 主要支持 RNN 任務,不支持 CNN 網絡中使用的卷積和池化操作。Mind 只支持基本的 DNN。

          支持的層類型

          所有框架都支持以層(Layer)為單位構建神經網絡。TensorFlow.js 的層 API 支持 49 種不同的層,包括密集層、卷積層、池化層、RNN、歸一化等。其他框架支持的層類型較少,這也與它們所支持的網絡類型有關。需要注意的是,TensorFlow.js 的核心 API 是 以類似于原生 TensorFlow 的方式實現的,它結合了各種操作來構建計算圖。synaptic 是 一個架構無關的框架,支持構建任何類型的一階甚至二階 RNN 網絡。

          支持的激活 / 優(yōu)化類型

          TensorFlow.js 為開發(fā)者提供了關于激活 / 優(yōu)化器的最多種類的選擇。對于激活函數,其他框架只支持基本的 sigmoid 或 ReLU。對于優(yōu)化器,其他框架主要支持基本的隨機梯度下降(SGD)。

          支持 GPU 加速

          TensorFlow.js 是唯一支持 GPU 加速訓練任務的框架。TensorFlow.js、Keras.js 和 WebDNN 支持使用 GPU 來加速推理任務。WebDNN 還支持更先進的技術—WebGPU,但 WebGPU 只被 Safari 的技術預覽版所支持。

          1.3 開發(fā)者支持

          文檔。TensorFlow.js、ConvNetJS、WebDNN 和 synaptic 提供的文檔已經完成。Keras.js 的文檔并不完整,brain.js 只有幾篇教程。

          演示。所有的框架都為開發(fā)者提供了入門的 demo。TensorFlow.js 提供了最豐富的 demo,涵蓋了廣泛的用例。

          從其他框架導入模型。TensorFlow.js、Keras.js 和 WebDNN 支持在 Python 中從原生 DL 框架中導入模型,并且它們都提供了用于轉換模型的 Python 腳本。TensorFlow.js 支持由 TensorFlow 和 Keras 訓練的模型。Keras.js 支持 Keras 模型。WebDNN 支持從 TensorFlow、Keras、Caffe 和 Pytorch 導入模型。在支持使用其他 DL 框架的預訓練模型的情況下,可以大大減少開發(fā)工作量。

          API 保存 / 加載模型。所有支持瀏覽器中訓練任務的框架都有保存模型的 API。所有框架都有用于加載模型的 API。

          支持服務器端(Node.js)。所有框架都支持 Node.js。這樣的功能使得瀏覽器內部的計算可以卸載到遠程服務器上。

          庫大小。表 1 中列出了需要加載到瀏覽器中的庫文件的大小。ConvNetJS 是最小的,只有 33KB。TensorFlow.js 和 brain.js 的文件大小非常大,分別為 732KB 和 819KB。小型庫更適合在瀏覽器中加載應用,因為所有的文件都要按需下載。

          表 1. 基于 JavaScript 的框架在瀏覽器中支持深度學習的情況

          2、瀏覽器中深度學習框架的性能

          本節(jié)重點討論現有框架的復雜度和后端處理器(CPU 或 GPU)對瀏覽器運行訓練和推理任務時性能的影響。

          2.1 實驗準備

          如前所述,不同框架支持的網絡類型是不一樣的。在實驗中采用最基本的全連接神經網絡作為模型。對于運行 DL 任務的數據集,使用經典的 MNIST 手寫數字識別數據庫。待訓練的模型有 784 個輸入節(jié)點和 10 個輸出節(jié)點。為了研究模型復雜度對性能的影響,對模型進行了不同的配置設置。參數包括:1)神經網絡的隱藏層數(深度),范圍在 [1,2,4,8];2)每個隱藏層的神經元數(寬度),范圍在[64,128,256]。深度和寬度的范圍是基于“客戶端 DL 模型應該是小尺寸,以便能夠在客戶端上運行” 的假設而設定的。在訓練過程中,批處理大小始終設置為 64。

          為了研究 CPU 和 GPU 的性能差異,使用一臺 Hasee T97E 筆記本電腦,它的獨立顯卡是 Nvidia 1070 Max-Q(配備 8GB GPU 內存)。CPU 為 Intel i7-8750H,其中包含 Intel HD Graphics 630,使得實驗中也能夠使用集成顯卡來驗證性能。在下文中,用 nGPU 和 iGPU 分別表示獨立 Nvidia 顯卡和集成 Intel 顯卡的 GPU。所有的實驗都在 Ubuntu 18.04.01 LTS(64 位)上的 Chrome 瀏覽器(版本:71.0.3578.10 dev 64 位)上運行,且使用各個框架最新發(fā)布的版本。

          對于每個 DL 任務,實驗中構建了一個網頁,可以通過 URL 中的參數來改變 DL 模型的配置。作者在 Chrome 瀏覽器上運行每個 DL 任務,并記錄完成任務的時間。由于每個實驗通常需要運行不同配置下的幾十個任務,作者開發(fā)了一個 Chrome 瀏覽器擴展版本:可以迭代運行所有頁面,并在一個任務完成之后更改配置。該擴展版本的瀏覽器還能夠監(jiān)控網頁的系統(tǒng)資源使用情況。

          2.2 訓練性能

          作者選取了支持在瀏覽器中進行訓練的 brain.js、ConvNetJS、synaptic 和 TensorFlow.js 四個 JavaScript 框架進行實驗,比較它們完成訓練任務的性能。除了 Tensor-Flow.js 能通過 WebGL 使用 GPU 外,其他框架都是基于 CPU 訓練模型的。作者使用每個框架對定義的模型進行訓練,獲得訓練一批模型的平均時間。圖 1 給出了不同模型復雜度的結果。由于 synaptic 的訓練時間大約是其他框架的幾十倍到幾百倍,為了更好的展示,作者在圖中省略了 synaptic 的結果,但實際上它的結果與其他框架是相似的。

          一般來說,訓練時間會隨著網絡規(guī)模的增大而增加,因為對于較大的網絡,需要更多的計算量來完成訓練過程。比較不同框架在 CPU 后臺的訓練時間,我們可以看到 ConvNetJS 是所有框架中所有網絡配置下速度最快的。作者分析,可能的原因是 ConvNetJS 的設計比較簡單,這也可以從它的庫文件大小反映出來。在速度方面 Brain.js 緊隨其后,與 ConvNetJS 的性能差距約為 2 倍,而 Tensorflow.js 與 ConvNetJS 的性能差距約為兩到三倍。在對比 ConvNetJS 與 TensorFlow.js 的訓練時間時,作者發(fā)現當深度和寬度增加時,性能差距會逐漸縮小,這說明與 ConvNetJS 相比 Tensor-Flow.js 的計算之外的開銷相對較大。此外,隨著網絡寬度的增加而導致的性能差距要比隨著網絡深度增加而導致的性能差距要大,這意味著 TensorFlow.js 比 ConvNetJS 更適合于處理大規(guī)模矩陣計算。

          CPU 后端的訓練時間隨著網絡規(guī)模的增大而變長,但 GPU(iGPU 和 nGPU)后端的訓練結果卻不盡相同。對于計算能力較弱的 iGPU 和能夠滿足較大規(guī)模矩陣計算的 nGPU,訓練時間并沒有顯著增加。但在從 4 個隱層、每層 128 個神經元到 8 個隱層、每層 256 個神經元的過程中,iGPU 的訓練時間明顯增加。作者分析,其原因可能是在本實驗設定的網絡規(guī)模下,訓練過程沒有達到 GPU 的能力瓶頸。雖然 nGPU 的矩陣計算能力優(yōu)于 iGPU,但 nGPU 的訓練時間比 iGPU 長。作者分析這種結果可能是由于調用 WebGL 訪問 GPU 的時間開銷過大造成的,實際上單純 GPU 的實時計算時間要短得多。

          圖 1. 在不同模型復雜度下,一個批次的平均訓練時間(ms),Y 軸為對數刻度。

          表 2 中給出了在訓練過程中每個框架的 CPU 利用率的統(tǒng)計數據。110% 是 CPU 利用率的上限。由于 JavaScript 引擎是單線程的,因此不能使用多核處理器的功能,它只能最大限度地提高單核的使用率。之所以 CPU 利用率超過 100%,是因為其他內核和用戶空間組件偶爾會在其他線程中同時運行。在 CPU 后端,TensorFlow.js 有時無法最大化單個核心的利用率,其平均 CPU 利用率僅為 82.1%。同時,作者發(fā)現在 GPU(iGPU 和 nGPU)后端運行訓練任務時,由于大部分計算都在 GPU 上,所以 CPU 的利用率并不高。iGPU 上的訓練比 nGPU 上訓練的 CPU 利用率要高 5-7% 左右。

          表 2. 訓練過程的 CPU 利用率(%)

          2.3 推理性能

          作者選擇了 6 個 JavaScript 框架來比較它們運行推理任務的性能。TensorFlow.js、Keras.js 和 WebDNN 支持使用 GPU 進行加速,但 brain.js、ConvNetJS 和 synaptic 只支持使用 CPU 進行推理。在模型使用方面,brain.js、ConvNetJS、synaptic 和 TensorFlow.js 支持保存自己訓練的模型,而 Keras.js 和 WebDNN 只支持從其他深度學習框架導入預訓練的模型。因此,對于 brain.js、ConvNetJS、synaptic 和 TensorFlow.js 直接使用框架本身保存的模型。對于 Keras.js 和 WebDNN,則需要使用 Keras 訓練的模型,然后將模型轉換為相應的格式。理論上,訓練得到的 DL 模型的參數值應該是不同的,但絕對值不會影響推理時間。所以只需要給不同框架的所有模型分配相同的參數值即可。推理任務包括加載一個預先訓練好的模型,然后給定一個樣本輸入,模型輸出結果。此外,在 GPU 后端,有一個預熱過程,推理的第一個樣本通常用于激活 GPU 處理器。因此,作者將推理過程分解為模型加載、預熱和推理三個階段,并研究性能細節(jié)。

          由于篇幅所限,作者在下面的分析中省略了模型深度為 8 的結果,因為隨著深度的增加,趨勢是相似的。另外,由于 synaptic 的模型加載時間和推理時間仍然比其他框架長很多,為了更好的展示,作者沒有在圖中給出 synaptic 的結果。

          首先研究不同框架使用的模型文件的大小。由于通常需要從遠程服務器下載用于推理的模型,模型文件的大小越小意味著下載時間越短。表 3 給出了所有推理實驗中使用的模型文件的大小。ConvNetJS 和 brain.js 使用類似的 JSON 編碼,所以它們的模型文件大小幾乎相同。synaptic 的模型文件也使用 JSON 編碼,但其大小是所有框架中最大的。由于 TensorFlow.js、Keras.js 和 WebDNN 使用的模型文件都是由 Keras 模型轉換而來,所以它們的模型文件大小是一樣的,作者只在表 3 中顯示 TensorFlow.js。由于從 Keras 轉換而來的模型被壓縮后保存為二進制文件,所以大小可以大大縮小,只有 JSON 中模型文件的 1/7 左右。

          然后,我們比較加載不同框架的模型所花費的時間,如圖 2 所示。對于 CPU 后端,同一框架的不同模型的加載時間與表 3 中描述的模型文件的大小成正比。但是,不同框架的模型加載時間有顯著差異。ConvNetJS 是最快的。Brain.js、TensorFlow.js 和 Keras.js 的模型加載時間在幅度上是一致的。有趣的是,當寬度增加時,ConvNetJS、brain.js 和 synaptic 的加載時間增加特別明顯。這個結果是由它們選擇使用 JSON 來編碼模型所造成的。在所有框架中,synaptic 的模型加載時間最慢,比 ConvNetJS 長 100 多倍到 1000 多倍。無論模型大小,TensorFlow.js 的模型加載時間幾乎沒有變化。

          在不同的模型復雜度下,GPU(iGPU 和 nGPU)后端的加載時間變化不大。但是,不同框架之間的差異還是很大的。TensorFlow.js 是最快的。與在 CPU 后端加載模型相比,Keras.js 加載大型模型的速度較快,但 WebDNN 的加載時間較長。此外,可以看到 iGPU 和 nGPU 的模型加載時間沒有區(qū)別。

          表 3. 模型文件的大小(MB)

          接下來,我們檢查 GPU(iGPU 和 nGPU)后端預熱時間的差異。如圖 3 所示,Keras.js 還是遙遙領先,可以在 3 毫秒內完成所有任務的預熱。Tensorflow.js 第二,WebDNN 最差。整體而言,iGPU 后端的預熱時間比 nGPU 要短。

          圖 3. 不同模型復雜度下 GPU 上的模型預熱時間(ms),y 軸為對數刻度。

          圖 4 給出了對一個樣本進行推理的平均時間。幾乎所有的推理任務都能在 1.5ms 內完成(除了 synaptic,其中時間最短的為 6.68ms)。在作者設定的模型大小范圍內,GPU 強大的計算能力并沒有造成任何影響。在所有的模型大小中,ConvNetJS 占據了所有的第一位,其次是 CPU 后臺的 WebDNN。

          圖 4. 在不同的模型復雜度下,一個樣本的平均推理時間(ms)。

          WebDNN 在 GPU(nGPU 和 iGPU)后端的推理時間比 CPU 后端的推理時間長。至于 TensorFlow.js,在 CPU 后端運行對于較小模型的推理速度更快,而 GPU 后端對于較大模型的推理速度更快。Keras.js 在 CPU 和 GPU 后端的推理時間基本一致。我們可以觀察到,對于 CPU 后端的所有框架,當模型變得復雜時,推理時間會增加。特別是當寬度增加時,時間會急劇增加(隨著模型寬度增加一倍,時間增加約 2 倍)。訓練任務情況類似。這樣的結果也反映出這些框架在 CPU 后端的前向傳播過程中沒有對大規(guī)模矩陣運算進行優(yōu)化。GPU 后端的 TensorFlow.js 和 WebDNN 并沒有出現這個問題,但后端為 GPU 的 Keras.js 仍然存在這個問題。

          2.4 其它收獲

          根據以上結果我們可以看到,在瀏覽器能夠勝任的小規(guī)模全連接神經網絡中,ConvNetJS 在訓練和推理方面的表現都是最好的。不過,由于 ConvNetJS 已經不再維護,功能較少,開發(fā)者可能還是需要尋找替代品。Tensorflow.js 是唯一可以利用 GPU(nGPU 和 iGPU)加速訓練過程的框架。它功能豐富,性能與 ConvNetJS 相當。所以 TensorFlow.js 對于訓練和推理都是一個不錯的選擇。作者不建議在小規(guī)模的模型上使用 GPU 作為后端,因為 GPU 計算能力的優(yōu)勢并沒有得到充分利用。

          最后,為什么 ConvNetJS 在這些框架中的所有任務都有最好的性能。對于流程邏輯相同的同一模型,性能差異很可能由不同的實現細節(jié)來解釋。為此,作者在完成相同訓練任務時比較了 ConvNetJS 和 TensorFlow.js 的函數調用堆棧(Function Call Stack)。令人驚訝的是,ConvNetJS 的調用堆棧深度只有 3,而 TensorFlow.js 是 48。這樣的結果表明,不同框架之間性能差異的一個可能原因是深度調用堆棧,這會消耗大量的計算資源。

          3、瀏覽器 DL 框架與原生 DL 框架的比較

          我們在上文已經分析了目前主要的瀏覽器 DL 框架結構、特點,也通過實驗證明了不同框架的效果。那么,在瀏覽器中運行 DL 和原生平臺上運行 DL 的性能差距有多大?作者比較了 TensorFlow.js 和 Python 中的原生 TensorFlow 的性能,兩者都是由 Google 發(fā)布和維護的,并且有類似的 API,因此所比較的結果是足夠公平的。

          3.1 基于預訓練模型的推理

          作者使用 Keras 官方提供的預訓練模型來衡量 TensorFlow.js 和原生 TensorFlow 在這些經典模型上完成推理任務時的性能。

          3.1.1 TensorFlow.js 的局限性和瀏覽器約束

          Keras 官方提供了 11 個預訓練模型。雖然在原生 TensorFlow 上運行這些模型時是一切正常的,但當作者在瀏覽器中使用 TensorFlow.js 運行它們時卻遇到了一系列錯誤。作者認為,這些錯誤是由于 TensorFlow.js 本身的限制以及瀏覽器施加的限制所造成的。例如,對于 NasNet Large 模型,瀏覽器會拋出以下錯誤信息 “truncatedNormal is not a valid Distribution "。對于 ResNet V3 模型,瀏覽器會拋出錯誤信息"Unknown layer: Lambda"。出現這兩個錯誤的原因是 TensorFlow.js 仍在開發(fā)中,到目前為止只提供了對有限的轉換模型的支持。許多用戶定義的操作算子 TensorFlow.js 是不支持的,例如,TensorFlow.js 不支持使用 RNN 中控制流操作算子的模型。當嘗試使用 VGG16 或 VGG19 時,瀏覽器會拋出"GL OUT OF MEMORY " 的錯誤信息,這意味著 GPU 內存溢出。VGG16 模式適用于超過 1GB 的 GPU 內存,不過實驗中使用筆記本的 GPU 內存是 8GB(Nvidia 1070 Max-Q),所以應該不是由內存所導致的問題。作者分析,這樣的錯誤是由于瀏覽器的限制造成的。

          表 4. 部分 Keras 預訓練模型

          3.1.2 結論

          圖 5 給出了每個模型的推理時間。可以看出,TensorFlow.js 在 nGPU 上的推理時間與原生 TensorFlow 的相當(慢 1 倍 - 2 倍)。iGPU 后端的 TensorFlow.js 的性能比 CPU 后端的原生 TensorFlow 還要好。作者分析,考慮到集成顯卡和 CPU 的計算能力,這一結果并不奇怪。然而,由于原生 DL 框架不支持集成顯卡加速,通過瀏覽器執(zhí)行的 DL 框架可以在集成顯卡的應用中獲益良多,畢竟,現在的設備中集成顯卡已經是非常常見的了。

          在客戶端 DL 的實時性要求下,如果用戶想要達到 10FPS(每秒幀數)的體驗,就需要考慮使用更強大的獨立顯卡。而通過 iGPU 加速的移動網絡模型也能滿足要求。如果要求是達到 1FPS,iGPU 也完全可以滿足。但如果只能使用 CPU,那么在瀏覽器中運行這些很常見的模型看起來就太過于沉重了。

          圖 5. 預訓練的 Keras 模型的推理時間,y 軸為對數刻度。

          3.2 決策樹分析

          最后,為了深入探索不同因素是如何影響 DL 在瀏覽器和原生框架上的性能差距的,作者建立了一個基于決策樹的預測模型,具體研究各種因素的重要性。

          3.2.1 實驗設置

          作者考慮 4 個影響 DL 在瀏覽器和原生平臺上性能差距的因素,如表 5 所示,包括后端(CPU 或 GPU)、任務類型(訓練或推理)以及模型的深度和寬度。在 DNN 和 RNN 模型中,寬度是指每層神經元的數目。在 CNN 模型中,寬度是指卷積層中使用的核數。對于 DNN、CNN 和 RNN,作者從 Tensorflow.js 官方樣本示例中選取模型。DNN 和 CNN 模型用于識別 MNIST 數據集上的手寫數字,RNN 模型用于從尼采的著作中生成文本。根據 Tensorflow.js 官方樣本的數值集合確定深度和寬度的范圍。

          表 5. 造成性能差距的主要因素

          在本文實驗中,作者使用不同配置的 TensorFlow.js 和原生 TensorFlow 運行 DNN、CNN 和 RNN 模型。作者將每個配置的執(zhí)行時間衡量為兩個平臺上訓練任務的每批平均時間和推理任務的每個樣本平均時間。作者使用 TensorFlow.js 上的執(zhí)行時間與原生 TensorFlow 上的執(zhí)行時間之比來量化性能差距。

          3.2.2 方法

          使用 sklearn 運行決策樹算法來預測 TensorFlow.js 和原生 TensorFlow 之間的執(zhí)行時間比。使用決策樹描述貢獻因素的相對重要性。直觀地講,靠近決策樹根部的因素比靠近葉子的因素對時間比的影響更大,這是因為決策樹是根據熵 - 信息增益標準(the Entropy-Information Gain criterion)選擇對節(jié)點進行分割的。

          作者首先為所有因素生成一棵完全生長的、未經修剪的決策樹。這樣一來,每個葉子只包含一個配置。然后,將樹的深度設置為因子的數量,以防止在一條路徑上多次使用一個因子。圖 6 顯示了 DNN、CNN 和 RNN 的決策樹。

          圖 6. 使用決策樹來分析 TensorFlow.js 與原生 TensorFlow 在 DNN、CNN 和 RNN 模型上的時間比。

          3.2.3 結果

          作者使用決策樹算法來預測的總的結論是:在幾乎所有的配置中,TensorFlow.js 的執(zhí)行時間都比原生 TensorFlow 長

          首先,后端是造成不同的模型、平臺性能差距的最重要因素。由圖 6 中的實驗結果可以看出,總的來說 CPU 后端的執(zhí)行時間比 GPU 后端要長得多。例如,當深度超過 3、寬度超過 192 的 DNN 模型運行在 GPU 后端而不是 CPU 后端時,訓練任務的比率從 44.7 下降到 4.4。最極端的例子發(fā)生在 CNN 上,在 CPU 后端,比率范圍從低于 5 到超過 2200(當深度小于 7.5,寬度超過 600 時)。但是,當在 GPU 后端執(zhí)行深度超過 12、寬度超過 600 的推理任務時,TensorFlow.js 執(zhí)行速度與原生 TensorFlow 一樣快。這是因為當模型足夠大時,CNN 能夠有效利用 GPU 強大的計算能力。

          第二個重要因素是三個模型的任務類型。執(zhí)行訓練任務的比率較高,而推理任務的表現差距較小。例如,對于 CPU 后端的 DNN 模型,訓練任務 TensorFlow.js 平均比原生 TensorFlow 慢 33.9 倍,推理任務 TensorFlow.js 執(zhí)行速度比原生 TensorFlow 平均慢 5.8 倍。

          最后,DNN 和 RNN 的決策樹都表明,深度和寬度的重要性取決于任務在哪個后端執(zhí)行。在 CPU 后端,寬度的重要性大于深度的重要性,而深度在 GPU 后端扮演更重要的角色。然而,在 CNN 的情況下,對于訓練任務來說,寬度比深度對性能差距的影響更大。表 6 總結了上述實驗的研究結果。

          表 6. 瀏覽器中 DL 的主要影響因素

          4. 示例

          4.1 TensorFlow.js [2]

          TensorFlow.js 是一個使用 JavaScript 進行機器學習開發(fā)的庫,TensorFlow.js 的中文教程網址如下:https://tensorflow.google.cn/js,教程通過完整的端到端示例詳細展示了如何使用 TensorFlow.js。TensorFlow.js 提供了圖像分類、對象檢測、姿態(tài)估計、文本惡意檢測、語音指令識別、人臉識別、語義分割等豐富的開箱即用的模型。

          TensorFlow.js 的 API 主要是以 TensorFlow 為藍本,并添加一些針對 JS 環(huán)境的例外。和 TensorFlow 一樣,其核心數據結構是 Tensor。TensorFlow.js API 提供了從 JS 數組創(chuàng)建張量的方法,以及對張量進行操作的數學函數。圖 7 給出 TensorFlow.js 的結構示意圖。TensorFlow.js 由兩組 API 組成:提供了低層次的線性代數操作(如矩陣乘法、張量加法等)的 Ops API 和被設計為在瀏覽器和服務器端運行的 TensorFlow.js。在瀏覽器內部運行時,它通過 WebGL 利用設備的 GPU 來實現快速并行化浮點計算。在 Node.js 中,TensorFlow.js 與 TensorFlow C 庫綁定,可以完全訪問 TensorFlow。TensorFlow.js 還提供了一個較慢的 CPU 實現作為后備(為簡單起見圖 7 中省略),用純 JS 實現。這個后備設置可以在任何執(zhí)行環(huán)境中運行,當環(huán)境無法訪問 WebGL 或 TensorFlow 二進制時,會自動使用。

          圖 7. TensorFlow.js 的結構示意圖。

          TensorFlow.js 提供了 Layers API,它盡可能地鏡像了 Keras API,包括序列化格式。這實現了 Keras 和 TensorFlow.js 之間的雙向門:用戶可以在 TensorFlow.js 中加載一個預先訓練好的 Keras 模型,對其進行修改、序列化,然后在 Keras Python 中加載回來。

          和 TensorFlow 一樣,TensorFlow.js 的一個操作代表了一個抽象的計算 (例如矩陣乘法),它獨立于它所運行的物理設備。操作調用到內核,內核是數學函數的特定設備實現。為了支持特定設備的內核實現,Tensor-Flow.js 有一個 Backend 的概念。Backend 實現了內核以及方法,如 read() 和 write(),這些方法用于存儲支持張量的 TypedArray。張量與支持它們的數據是解耦的,因此像 reshape 和 clone 這樣的操作實際上是自由的。

          此外,TensorFlow.js 還支持自動微分,提供一個 API 來訓練模型和計算梯度。在討論 TensorFlow.js 中的自動微分問題之前,讓我們先來回顧一下常見的自動微分方法。目前有兩種常見的自動微分方法:Graph-based 和 eager。Graph-based 引擎提供了一個 API 來構造一個計算圖,并在之后執(zhí)行。在計算梯度時,引擎會靜態(tài)分析 Graph 來創(chuàng)建一個額外的梯度計算 Graph。這種方式的性能比較好,而且容易實現序列化。而 eager 微分引擎則采取了不同的方式。在 eager 模式下,當一個操作被調用時,立即進行計算,這可以很容易的通過 Print 或使用 debugger 來檢查結果。另一個好處是,當模型在執(zhí)行時,主機語言的所有功能都是可用的。用戶可以使用原生的 if 和 while 循環(huán),而不需要使用專門的控制流 API。TensorFlow.js 的設計目標是優(yōu)先考慮易用性而不是性能,所以 TensorFlow.js 支持 eager 自動微分方法。

          TensorFlow.js 實現了平衡同步函數的簡單性和異步函數的優(yōu)點。例如,像 tf.matMul()這樣的操作是同步執(zhí)行的并返回一個可能未計算的張量。這樣用戶就可以寫出常規(guī)的同步代碼,便于調試。當用戶需要檢索支持張量的數據時,TensorFlow.js 提供了一個異步的 tensor.data()函數,該函數返回一個承諾(promise),當操作完成后,該承諾會被解析。因此,異步代碼的使用可以本地化到一個 data()的調用。用戶還可以選擇調用 tensor.dataSync(),這是一個阻塞調用(Blocking Call)。圖 8 和圖 9 分別說明了在瀏覽器中調用 tensor.dataSync()和 tensor.data()的時間線。

          圖 8. 瀏覽器中同步和阻塞張量的時間軸,dataSync(),主線程阻塞,直到 GPU 完成執(zhí)行操作。

          圖 9. 瀏覽器中對 data()的異步調用的時間軸。當 GPU 執(zhí)行操作時,主線程被釋放,當張量準備好并下載時,解析 data()承諾。

          TensorFlow.js 的官網上提供了多個在瀏覽器中運行的在線演示和示例。比如,下面是一個通過網絡攝像頭玩 Pacman 游戲,使用一個預先訓練好的 MobileNet 模型,并使用內部的 MobileNet 激活訓練另一個模型,從用戶定義的網絡攝像頭中預測 4 個不同的類。

          下面的示例在 TensorFlow 中對鋼琴演奏的 MIDI 進行了 Performance RNN 訓練。然后,它被移植到瀏覽器中,在 TensorFlow.js 環(huán)境中僅使用 Javascript 運行。鋼琴樣本來自 Salamander Grand Piano。TensorFlow.js 的中文網站上還有其它有趣的示例,大家可以直接上去嘗試一下。

          4.2 ConvNetJS [3]

          由斯坦福大學開發(fā)的 ConvNetJS 是一個可在瀏覽器中運行的 Javascript 深度學習框架。盡管它已經不在更新了,我們還是把它放在這里做一個示例性介紹,因為它真的是一款接口好用、簡便可操作的 DL 框架,非常適合正在入門深度學習的人。在 PC 端的瀏覽器中,直接下載 js 即可,convnet.js (http://cs.stanford.edu/people/karpathy/convnetjs/build/convnet.js)或 convnet-min.js (http://cs.stanford.edu/people/karpathy/convnetjs/build/convnet-min.js),二選一。

          ConvNetJS 的官網上提供了分類 (Classifier)、回歸(Regression)、自編碼器(AutoEncoder) 等示例。

          這些示例并不需要消耗很多資源,在普通 PC 的瀏覽器中,運行一兩分鐘就可以初步進行分類等操作。我們從網站上選擇了一些分類示例如下。更多的示例可以直接到網站上下載,大家也可以嘗試直接用自己的瀏覽器搭建一個 DL 模型。

          4.3 Paddle.js [9]

          最后,我們來介紹一下 Paddle.js。2020 年 5 月,百度飛槳聯手百度 APP 技術團隊開源了飛槳前端推理引擎 Paddle.js。Paddle.js 是百度 Paddle 的 web 方向子項目,是一個運行在瀏覽器中的開源深度學習框架,也是國內首個開源的機器學習 Web 在線預測方案。Paddle.js 可以加載訓練好 Paddle.js 模型,或者將 Paddle.fluid 模型通過 Paddle.js 的模型轉換工具變成瀏覽器友好的模型進行在線預測使用。

          目前,Paddle.js 支持在 webGL2.0 和 webGL1.0 的瀏覽器上運行。包括 PC 端的 chrome, firefox, safari 以及移動端的 Baidu App, QQ 瀏覽器等。支持 NCHW 與 NHWC 格式的模型數據計算。Paddle.js 提供了豐富的模型資源庫,同時也提供了模型轉換工具可將 Paddle 模型變成 Web 可用模型(https://github.com/PaddlePaddle/Paddle.js/blob/master/tools/ModelConverter/README_cn.md)。Paddle.js 支持有限的算子,如果模型中使用了不支持的操作,那么 Paddle.js 將運行失敗并提示模型中有哪些算子目前還不支持。

          Paddle.js 使用現成的 JavaScript 模型或將其轉換為可在瀏覽器中運行的 paddel 模型。目前,小型 Yolo 模型可在 30ms 內運行完畢,可以滿足一般的實時場景需求。Paddel.js 的部署示例如下,Github 上有完整的部署過程說明,大家也可以去試用一下。

          5、文章小結

          本文我們基于 WWW’19 論文提供的線索,詳細地了解了在瀏覽器中實現深度學習的可能性、可行性和性能現狀。具體地,重點分析了 7 個最近出現的基于 JavaScript 的 DL 框架,并對比了具體框架支持哪些 DL 任務。文中的實驗將不同任務中瀏覽器 DL 和原生 DL 的性能進行比較。雖然,目前關于瀏覽器中實現 DL 的研究仍然處于早期階段,但是,從本文的研究中仍然可以看出瀏覽器 DL 框架有很多很好的表現。例如,在完成某些任務時 JavaScript 的 DL 性能要優(yōu)于原生 DL,瀏覽器 DL 框架能夠有效利用集成顯卡等等。

          當然,目前瀏覽器中的 DL 框架還有著很大的改進空間,距離大規(guī)模的推廣和應用還有很大差距。但是,隨著硬件設備的不斷改進、性能的不斷提升,瀏覽器本身的性能和智能化水平都在不斷的提高。相應的,瀏覽器中的 DL 框架的性能也會越來越好。由上文的分析,我們認為,未來瀏覽器中 DL 框架的發(fā)展可以沿著多個方向:一是,可以提升瀏覽器中 DL 框架的執(zhí)行性能,使其能夠滿足實時性的應用需求;二是,可以不斷完善和增加瀏覽器中 DL 框架所適用的任務場景;三是,可以繼續(xù)完善上下游的工具鏈,提升瀏覽器中 DL 框架的易用性,降低使用成本。

          最后,無論對于 AI 還是對于 web 瀏覽器,瀏覽器中的 DL 框架的發(fā)展都會對其產生深遠的影響。我們希望,關于瀏覽器中實現深度學習的探索工作可以為人工智能時代的 Web 應用的未來提供一點啟示。

          本文參考引用的文獻:

          [1] Yun Ma, Dongwei Xiang, Shuyu Zheng, Deyu Tian, Xuanzhe Liu, Moving Deep Learning into Web Browser: How Far Can We Go? WWW '19: The World Wide Web Conference, May 2019 Pages 1234–1244, https://dl.acm.org/doi/abs/10.1145/3308558.3313639

          [2] 2018. TensorFlow.js. https://js.tensorflow.org/. Smilkov et al., TensorFlow.js: Machine Learning for the Web and Beyond, SysML’19.

          [3] 2020. ConvNetJS. https://cs.stanford.edu/people/karpathy/convnetjs/, https://github.com/karpathy/convnetjs.

          [4] 2020. Keras.js. https://github.com/transcranial/keras-js.

          [5] 2020. MIL WebDNN Benchmark. https://mil-tokyo.github.io/webdnn/#benchmar.

          [6] 2020. brain.js. https://github.com/BrainJS.

          [7] 2020. synaptic.js. https://github.com/cazala/synaptic.

          [8] 2020. Mind. https://github.com/stevenmiller888/mind.

          [9] 2020. Paddle.js. https://github.com/paddlepaddle/paddle.js/releases.

          分析師介紹:

          仵冀穎,工學博士,畢業(yè)于北京交通大學,曾分別于香港中文大學和香港科技大學擔任助理研究員和研究助理,現從事電子政務領域信息化新技術研究工作。主要研究方向為模式識別、計算機視覺,愛好科研,希望能保持學習、不斷進步。

          關于機器之心全球分析師網絡 Synced Global Analyst Network

          機器之心全球分析師網絡是由機器之心發(fā)起的全球性人工智能專業(yè)知識共享網絡。在過去的四年里,已有數百名來自全球各地的 AI 領域專業(yè)學生學者、工程專家、業(yè)務專家,利用自己的學業(yè)工作之余的閑暇時間,通過線上分享、專欄解讀、知識庫構建、報告發(fā)布、評測及項目咨詢等形式與全球 AI 社區(qū)共享自己的研究思路、工程經驗及行業(yè)洞察等專業(yè)知識,并從中獲得了自身的能力成長、經驗積累及職業(yè)發(fā)展。

          eact18內核探秘:手寫React高質量源碼邁向高階開發(fā)

          download:https://www.sisuoit.com/4316.html


          預備階段-基層環(huán)境

          裝置node.js(官網下載即可)

          node -v (檢查是否裝置node完結)

          裝置yarn新一代的包管理工具facebook開發(fā),你也可以挑選cnpm

          yarn的速度會比npm快

          裝置版別統(tǒng)一,更安全

          更簡潔的輸出

          更好的語義化

          sudo cnpm install yarn -gyarn -v(檢查是否裝置yarn完結)

          運用git在馬云上進行托管,并在本地clone下來項目

          git clone 你項意圖地址cd 你的項目

          裝備gitignore(git命令忽視)

          vim .gitignorei(修改命令).DS_Store(Mac自帶)node_modules(node包)dist(打包的壓縮文件)*.log(錯誤信息等)

          初始化項目

          yarninit/cnpminit

          提交項目

          gitadd.(保存到暫存區(qū))git commit-m'補白信息'(把暫存區(qū)內容保存到分支)git pull(拉取其他分支代碼)git push(將更新內容提交進入遠程分支)

          裝置webpack打包工具(裝備規(guī)矩檢查webpack章節(jié))

          yarn add webpack--dev/cnpm install webpack--dev在根目錄下創(chuàng)立一個webpack.config.js文件1.需求處理的文件類型 html=>html-webpack=plugin js es6=>babel+babel-preset-react css=>css-loader+sass-loader img=>url-loader+file-loader2.常用模塊 html-webpack-plugin=>html獨自打包成文件 extract-text-webpack-plugin=>款式打包成獨自文件 CommonsChunkPlugin=>提出通用模塊3.webpack-dev-server(1)為webpack項目提供web服務(2)更改代碼主動改寫,路徑轉發(fā)(3)yarn add webpack-dev-server--dev(4)解決多版別共存

          1.裝備webpack,在創(chuàng)立好的webpack.config.js中裝備

          增加模塊輸出口

          constpath=require('path')module.exports={entry:'./src/app.js',output:{path:path.resolve(__dirname,'dist'),// __dirname代表根目錄filename:'你想輸出的文件姓名.js'}}

          增加html插件

          yarn add html-webpack-plugin--dev// 生成html5文件插件在webpack.config.js中設置constpath=require('path')constHtmlWebpackPlugin=require('html-webpack-plugin')// 引進插件module.exports={entry:'./src/app.js',output:{path:path.resolve(__dirname,'dist'),filename:'你想輸出的文件姓名.js'},plugins:[// 運用插件newHtmlWebpackPlugin({template:'.src/index.html'// 引證模板自定義html文件,使打包出來的html與此文件共同})]}

          增加babel插件 (將es6言語轉換成es5)

          yarn add babel-core@6.26.0babel-preset-env@1.6.1babel-loader@7.1.2--dev// 裝置在webpack.config.js裝備constpath=require('path')constHtmlWebpackPlugin=require('html-webpack-plugin')module.exports={entry:'./src/app.js',output:{path:path.resolve(__dirname,'dist'),filename:'app.js'},module:{rules:[{test:/\.js$/,exclude:/(node_modules)/,// 將不需求裝換的文件夾掃除use:{loader:'babel-loader',options:{presets:['env']}}}]},plugins:[newHtmlWebpackPlugin({template:'.src/index.html'// 引證自定義html文件})// 生成html5文件]}

          裝置react的插件

          yarn add babel-preset-react --dev / cnpm install babel-preset-react --dev

          yarn add html-webpack-plugin--dev// 生成html5文件插件在webpack.config.js中設置constpath=require('path')constHtmlWebpackPlugin=require('html-webpack-plugin')// 引進插件module.exports={entry:'./src/app.js',output:{path:path.resolve(__dirname,'dist'),filename:'你想輸出的文件姓名.js'},plugins:[// 運用插件newHtmlWebpackPlugin({template:'.src/index.html'// 引證模板自定義html文件,使打包出來的html與此文件共同})]}

          增加babel插件 (將es6言語轉換成es5)

          yarn add babel-core@6.26.0babel-preset-env@1.6.1babel-loader@7.1.2--dev// 裝置在webpack.config.js裝備constpath=require('path')constHtmlWebpackPlugin=require('html-webpack-plugin')module.exports={entry:'./src/app.js',output:{path:path.resolve(__dirname,'dist'),filename:'app.js'},module:{rules:[{test:/\.js$/,exclude:/(node_modules)/,// 將不需求裝換的文件夾掃除use:{loader:'babel-loader',options:{presets:['env','react']// 只需求在這兒引證react}}}]},plugins:[newHtmlWebpackPlugin({template:'.src/index.html'// 引證自定義html文件})// 生成html5文件]}

          裝置款式的插件

          >裝置cssyarn add style-loader@0.19.1css-loader@0.28.8--dev在項目src中新建一個index.css頁面,并在app.jsx中引進頁面importReactfrom'react'importReactDOMfrom'react-dom'import'./index.css'裝置webpack打包css成獨立文件的插件yarn add extract-text-webpack-plugin@3.0.2--dev在webpack.config.js中更改對css的裝備裝備引進constExtractTextPlugin=require('extract-text-webpack-plugin'){test:/\.css$/,use:ExtractTextPlugin.extract({// 這兒改動fallback:'style-loader',use:'css-loader'})}由于這是一個插件,需求在plugin中裝備>裝置sassyarn add sass-loader--devyarn add node-sass--dev在webpack.config.js中rules css裝備下增加{test:/\.scss$/,use:ExtractTextPlugin.extract({fallback:'style-loader',use:['css-loader','sass-loader']})}

          對圖片的處理

          yarn add url-loader --sev

          在webpack.config.js中裝備{test: /\.(png|jpg|gif)$/,

          use: [{loader: 'url-loader',

          options:{limit:8192 // 文件大于8k被作為文件}}]}

          對字體圖標的處理

          yarn add font-awesome{test: /\.(eot|svg|ttf|woff|woff2|otf)$/,

          use: [{loader: 'url-loader',

          options:{limit:8192 // 文件大于8k被作為文件}}]}

          對公共模塊的處理

          在plugin中處理

          constpath=require('path')constwebpack=require('webpack')//為了引證webpack自帶辦法constHtmlWebpackPlugin=require('html-webpack-plugin')constExtractTextPlugin=require('extract-text-webpack-plugin')module.exports={entry:'./src/app.jsx',output:{path:path.resolve(__dirname,'dist'),filename:'js/app.js'},module:{rules:[{test:/\.jsx$/,exclude:/(node_modules)/,// 將不需求裝換的文件夾掃除use:{loader:'babel-loader',options:{presets:['env','react']// 主動根據環(huán)境打包}}},{test:/\.css$/,use:ExtractTextPlugin.extract({fallback:'style-loader',use:['css-loader']})},{test:/\.scss$/,use:ExtractTextPlugin.extract({fallback:'style-loader',use:['css-loader','sass-loader']})},{test:/\.(png|jpg|gif|jpeg)$/,use:[{loader:'url-loader',options:{limit:8192,// 文件大于8k被作為文件name:'resource/[name].[ext]'}}]},{test:/\.(eot|svg|ttf|woff|woff2|otf)$/,use:[{loader:'url-loader',options:{limit:8192,// 文件大于8k被作為文件name:'resource/[name].[ext]'}}]}]},plugins:[newHtmlWebpackPlugin({template:'./src/index.html'// 引證自定義html文件}),// 生成html5文件newExtractTextPlugin('css/[name].css'),// 將款式獨自打包出來生成新的css頁面// 提出公共模塊,webpack自帶newwebpack.optimize.CommonsChunkPlugin({name:'common',// 手動指定的通用木塊filename:'js/base.js'})]}

          webpack 主動改寫處理webpack-dev-server

          yarn add webpack-dev-server

          在config下與plugin同級加上

          devServer:{}為了防止打包后的圖片在根目錄下找不到

          output:{path:path.resolve(__dirname,'dist'),publicPath:'/dist/',//在這兒增加路徑filename:'js/app.js'},編譯打包就用webpack-dev-server就可以了

          運用react環(huán)境搭建項目

          yarn add react react-dom 在app.js中引證react結構,并將app.js的后綴更改為jsx,webpack.config.js中的js裝備也要變成jsx,進口文件的js也要更改為jsximportReactfrom'react'importReactDOMfrom'react-dom'

          package.json的裝備

          "scripts": { "dev": "node_modules/.bin/webpack-dev-server", "dist": "node_modules/.bin/webpack -p" },

          這樣就可以運用yarn dev發(fā)動項目

          yarn dist 打包項目

          react正式開始咯(上訴辦法均為自己手打搭建一個react項目,了解每一步,接下來是react提供的項目創(chuàng)立辦法)

          react => 視圖層結構 組件化 JSX表達式 虛擬DOM

          Facebook 開源的一個JavaScript庫

          React結合生態(tài)庫構成一個MVC結構(vue也是mvc)

          特色:Declarative(聲明式編碼:只需求聲明在哪里做什么,無需關心如何實現);Component-Based(組件化編碼);高效-高效的DOM Diff算法,最小化頁面重繪

          單向數據流

          生態(tài)介紹

          vue生態(tài):vue + vue-router + vuex + axios + babel + webpack

          react生態(tài): react + react-router + redux + axios + babel + webpack

          項意圖創(chuàng)立(另一種方法,但是本人喜愛上面的手動裝備,看得懂)

          yarnadd/cnpm installglobal/-g create-react-app(大局裝置)在作業(yè)區(qū)創(chuàng)立項目 create-react-app 你的項目稱號 cd 你的項目稱號 cnpm/yarn start 發(fā)動

          JSX語法

          增加html

          letjsx=

          thisisa jsx programmer
          ReactDOM.render(jsx,// 將jsx組件放到烘托的dom下document.getElementById('app))
          增加款式

          行內款式letstyle={color:'red'}letjsx=

          引進class款式在index.scss中設置body{.jsx{font-size:16px;}}letjsx=
          // react中運用className引證款式稱號
          增加邏輯

          (1)運用變量

          letname="pig"letjsx=

          I am a {pig}
          ReactDom.render(jsx,document.getElementById('app'))
          (2)條件判別

          lettruth=trueletjsx=(//加括號的意圖是為了換行的時候,修改器不會給我們主動填補分號

          {// 條件判別需求加上{}truth?
          I am a pig

          :
          I am not a pig

          }
          )
          (3)jsx中刺進注釋

          {/*我是一個注釋*/}

          (4)數組的運用

          letnames=['pig','dog','chicken']letjsx=({names.map((name,index)=>I am {name}

          )})
          增加組件

          (1)基礎

          functionComponent(){return

          I am a pig
          }ReactDom.render(,// 如果是變量直接飲用,如果是組件需求加上標簽document.getElementById('app'))
          (2)es6組件寫法

          classES6ComponentextendsReact.Component{render(){return

          I am a pig in es6
          }}ReactDom.render(
          // 兩個組件共存需求包裹在一個div中


          ,
          document.getElementById('app')

          )

          (3)組件初始化變量

          classComponentextendsReact.Component{constructor(props){super(props);this.state={name:'pig'}}render(){return

          I am a {this.state.pig}
          }}
          (4)更改組件初始化變量

          classComponentextendsReact.Component{constructor(props){// props在子組件中只能被運用不能被改動super(props);this.state={name:'pig'}}render(){setTimeOut(()=>{this.setState({name:'Pi g Pig'})},2000)return

          I am a {this.state.pig}
          }}
          (5)父組件傳值

          classComponentextendsReact.Component{constructor(props){super(props)}render(){return

          I am a {this.props.name}
          }}ReactDom.render(,docuement.getElementById('app'))
          (6)組件增加點擊事情(辦法一)

          classComponent extends React.Component{constructor(props){super(props);this.state={age:18}this.addAge=this.addAge.bind(this)}addAge(){this.setState({age:this.state.age+1})}render(){return(

          I am{this.state.age}years old
          )}}ReactDom.render(,docuement.getElementById('app'))
          (7)組件增加點擊事情(辦法二)

          classComponentextendsReact.Component{constructor(props){super(props);this.state={age:18}}addAge(){this.setState({age:this.state.age+1})}render(){return(


          I am {this.state.age} years old
          {this.addAge(e)}}>

          }

          }

          ReactDom.render(

          ,

          docuement.getElementById('app')

          )

          (8)組件增加輸入框更改事情

          classComponentextendsReact.Component{constructor(props){super(props);this.state={age:18}}changeValue(e){this.setState({age:e.target.value})}render(){return(


          I am {this.state.age} years old
          {this.changeValue(e)}}/>
          )}}ReactDom.render(,docuement.getElementById('app'))
          (9)容器性組件嵌套組件

          classComponent extends React.Component{constructor(props){super(props);this.state={age:18}}changeValue(e){this.setState({age:e.target.value})}render(){return(

          I am{this.state.age}years old
          {this.changeValue(e)}}/>
          )}}classTitle extends React.Component{constuctor(props){super(props)}render(props){return
          {this.props.title}
          }}classApp extends React.Component{render(){return(
          // 在這兒引證小組件component
          )}}ReactDom.render(,// 這兒換成Appdocuement.getElementById('app'))
          (10)組件嵌套組件

          classComponentextendsReact.Component{constructor(props){super(props);this.state={age:18}}changeValue(e){this.setState({age:e.target.value})}render(){return(


          I am {this.state.age} years old
          {this.changeValue(e)}}/>
          )}}classAppextendsReact.Component{render(){return(

          app
          // 在這兒引證小組件component


          )}}ReactDom.render(,// 這兒換成Appdocuement.getElementById('app'))
          (11)容器性組件嵌套組件,傳值可以為任何html方法

          classComponent extends React.Component{constructor(props){super(props);this.state={age:18}}changeValue(e){this.setState({age:e.target.value})}render(){return(

          I am{this.state.age}years old
          {this.changeValue(e)}}/>
          )}}classTitle extends React.Component{constuctor(props){super(props)}render(props){return
          {this.props.children}
          // 這兒變成獲取子children}}classApp extends React.Component{render(){return(
          我是spanspanlink//更改為html方法
          )}}ReactDom.render(,// 這兒換成Appdocuement.getElementById('app'))
          (12)子組件給父組件傳值

          classFather extends React.Component{constructor(props){super(props);this.state={bgColor:'red'}}changeMyBgColors(color){this.setState({bgColor:color})}render(){return(

          我是爸爸
          {this.changeMyBgColors(color)}}/>
          )}}classChild extends React.Component{constructor(props){super(props)}changeMyBgColor(){this.props.changeColor('blue')}render(){return(
          我是baby
          {this.changeMyBgColor(e)}}>我想改動我爸爸的背景顏色
          )}}ReactDOM.render(,document.getElementById('app'))
          (13)兄弟之間的組件通訊(子1傳父,父在傳子2)

          classChild1extendsReact.Component{constructor(props){super(props)this.state={color1:'red'}}changeMyBrotherColor(props){this.props.change2Color(this.state.color1)}render(){return(


          我是孩子1
          {this.changeMyBrotherColor(e)}}>我要改動我弟弟的顏色咯
          )}}classChild2extendsReact.Component{constructor(props){super(props)}render(){return(
          我是孩子2
          )}}classFatherextendsReact.Component{constructor(props){super(props)this.state={color2:'yellow'}}changColor(colorsss){this.setState({color2:colorsss})}render(){return(

          這是我的孩子們
          {this.changColor(color)}}/>
          )}}ReactDOM.render(,document.getElementById('app'))
          react生命周期

          getDefaultProps // 初始化props特點,props來自其他組件

          getInitialState // 初始化組件的狀況

          componentWillMount // 組件加載之前

          render // 烘托

          componentDidMount // 組件dom刺進之后

          componentWillReceiveProps // 承受父組件的傳遞

          shouldComponentUpdate // 組件的更新處罰

          componentWillUpdate // 組件要更新前

          componentDidUpdate // 組件更新后

          componentWillUnmount // 組件的銷毀

          Mounting : 掛載階段

          Updating: 運行時階段

          Unmounting: 卸載階段

          Error Handling: 錯誤處理

          importReactfrom'react'importReactDOMfrom'react-dom'import'./index.scss'classChildextendsReact.Component{// 結構函數constructor(props){super(props)this.state={data:'oldzhuzhu'}console.log('這兒是初始化數據constructor')}componentWillMount(){// 組件烘托前console.log('componentWillMount')}componentDidMount(){// 組件烘托完畢console.log('componentDidMount')}componentWillReceiveProps(){// 即將承受父組件傳來的props觸發(fā)console.log('componentWillReceiveProps')}shouldComponentUpdate(){// 子組件是不是應該更新console.log('shouldComponentUpdate')returntrue// 如果是false,后面的update就不會跟著更新}componentWillUpdate(){// 組件即將更新console.log('componentWillUpdate')}componentDidUpdate(){// 組件更新完結console.log('componentDidUpdate')}componentWillUnmount(){// 組件即將銷毀調用console.log('componentWillUnmount')}// 點擊事情handleClick(){console.log('這兒是更新數組')this.setState({data:'zhuzhuzhu'})}// 烘托render(){console.log('render')return(

          {this.state.data} 接收到的props: {this.props.data} {this.handleClick()}}>更新組件
          )}}classFatherextendsReact.Component{constructor(props){super(props)this.state={data:'old props'}}changeData(){this.setState({data:'new props',show:true})}byeChild(){this.setState({show:false})}render(){return(
          {this.state.show?:null} {this.changeData()}}>改動子組件的props {this.byeChild()}}>
          )}}ReactDOM.render(,document.getElementById('app'))
          按次序輸出值:

          constructor// 結構函數初始化componentWillMount// 組件烘托前render// 組價烘托componentDidMount// 選件烘托完結componentWillReceiveProps// 子組件接收到父組件傳來的propsshouldComponentUpdate// 是否組件進行更新,true更新,false接下來的周期不觸發(fā)componentWillUpdate// 組件更新前render// 更新componentDidUpdate// 組件更新完畢componentWillUnmount// 組件被摧毀之前

          react-router

          1.router幾種方法

          頁面路由

          window.location.href='地址'// www.baidu.comhistory.back()//回退

          hash 路由

          window.location='#hash'window.onhashchange=function(){console.log('current hash'+window.location.hash)}

          h5路由

          history.pushState('name','title','/path')// 推進一個狀況history.replaceState('name','title','/path')// 替換一個狀況window.onpopstate=function(){console.log(window.location.href)console.log(window.location.pathname)console.log(window.location.hash)console.log(window.location.search)}

          2. react-router幾種方法

          hash

          importReactfrom'react'importReactDOMfrom'react-dom'import{HashRouterasRouter,Route,Link}from'react-router-dom'// 這兒是Hashimport'./index.scss'classAextendsReact.Component{constructor(props){super(props)}render(){return(

          Component A
          )}}classBextendsReact.Component{constructor(props){super(props)}render(){return(
          Component B
          )}}classWrapperextendsReact.Component{constructor(props){super(props)}render(){return(
          組件A
          組件B {this.props.children}
          )}}ReactDOM.render( ,document.getElementById('app'))

          試?直是程序員跳槽時期?常熱?的話題,雖然現在已經過了跳槽的旺季,下?輪跳槽季需要到明年才會出現,但是當跳槽季的時候你再看這篇?章可能已經晚了,過冬的糧?永遠不是冬天準備的,?是秋收的時候。

          今天小編給大家分享一份可?于快速突擊前端?試的知識點,希望可以幫助到大家。

          知識點目錄

          篇幅限制,只展示了部分,完整版見文末

          HTML基礎

          • doctype(?檔類型) 的作?是什么??
          • 這三種模式的區(qū)別是什么?(接上?問追問)
          • HTML、XML 和 XHTML 有什么區(qū)別?
          • 什么是data-屬性?
          • 你對HTML語義化的理解??
          • HTML5與HTML4的不同之處
          • 有哪些常?的meta標簽?
          • src和href的區(qū)別?
          • 知道img的srcset的作?是什么?(追問)
          • 還有哪?個標簽能起到跟srcset相似作??(追問)
          • script標簽中defer和async的區(qū)別??
          • 有?種前端儲存的?式??
          • 這些?式的區(qū)別是什么?(追問)

          CSS基礎

          • CSS選擇器的優(yōu)先級是怎樣的??
          • link和@import的區(qū)別?
          • 有哪些?式(CSS)可以隱藏??元素?
          • em\px\rem區(qū)別?
          • 塊級元素?平居中的?法?
          • CSS有?種定位?式?
          • 如何理解z-index??
          • 如何理解層疊上下???
          • 清除浮動有哪些?法?
          • 你對css-sprites的理解
          • 你對媒體查詢的理解?
          • 你對盒模型的理解??
          • 標準盒模型和怪異盒模型有什么區(qū)別??
          • 談談對BFC(Block Formatting Context)的理解? ?
          • 為什么有時候?們?translate來改變位置?不是定位?
          • 偽類和偽元素的區(qū)別是什么?
          • 你對flex的理解??
          • 關于CSS的動畫與過渡問題

          JavaScript基礎

          HTTP協議

          TCP?試題

          DOM

          瀏覽器與新技術

          • 常?的瀏覽器內核有哪些?
          • 瀏覽器的主要組成部分是什么?
          • 瀏覽器是如何渲染UI的?
          • 瀏覽器如何解析css選擇器?
          • DOM Tree是如何構建的?
          • 瀏覽器重繪與重排的區(qū)別?
          • 如何觸發(fā)重排和重繪?
          • 如何避免重繪或者重排?
          • 前端如何實現即時通訊?
          • 什么是瀏覽器同源策略?

          前端?程化

          React?試題

          Vue?試題

          前端安全?試題

          webpack?試題

          算法?試題

          字符串類?試題

          如何通過HR?

          ?試回答問題的技巧

          對這份面試指南感興趣的小伙伴可以私信小編【111】免費獲取啦

          內容展示到這里就結束啦。歸根到底候選?的基本功和豐富的項?經驗才是硬道理,如果你看完了整篇?章,并進?了精?的準備,他是可以讓你從75分到85分的實?技巧,?不是讓你從55到85的什么秘籍。

          最后祝愿大家都能拿到心儀的offer。


          主站蜘蛛池模板: 日本精品高清一区二区| 人妻天天爽夜夜爽一区二区| 无码人妻视频一区二区三区| 国产福利电影一区二区三区久久老子无码午夜伦不 | 亚洲一区精彩视频| 精品视频无码一区二区三区| 无码人妻精品一区二区在线视频| 国产91一区二区在线播放不卡| 人妻无码一区二区三区四区| 日本一区二区不卡视频| 精品国产免费一区二区| 中文字幕AV一区二区三区人妻少妇| 日本在线视频一区二区三区 | 日韩高清国产一区在线| 亚洲福利视频一区二区| 国产一区高清视频| 国产一区二区成人| 精品视频在线观看你懂的一区| 日本亚洲成高清一区二区三区| 久久青青草原一区二区| 人妻无码久久一区二区三区免费 | 午夜影视日本亚洲欧洲精品一区| av无码一区二区三区| 国产福利电影一区二区三区久久久久成人精品综合 | 亚洲一区精品无码| 精品aⅴ一区二区三区| 中文字幕一区二区三| 又硬又粗又大一区二区三区视频 | 亚洲综合av永久无码精品一区二区| 亚洲AV成人精品日韩一区18p| 视频一区视频二区在线观看| 精品一区二区高清在线观看| 欧洲精品码一区二区三区免费看| 国产一区在线视频观看| 中文字幕在线观看一区二区三区| av无码人妻一区二区三区牛牛| 久久久久久人妻一区精品| 国产精品高清一区二区人妖| 国产精品熟女视频一区二区| 久久国产三级无码一区二区| 精品人无码一区二区三区|