Tensorflow.js 入門學習指南
官方地址TensorFlow.js (google.cn)
Tensorflowjs是一個機器學習框架,使用 TensorFlowJS 可以創建生產級機器學習模型
安裝包
瀏覽器設置
您可以通過兩種主要方式在瀏覽器項目中獲取 TensorFlow.js:
- 使用腳本代碼。
- 從 NPM 安裝并使用諸如 Parcel、WebPack 或 Rollup 的構建工具。
如果您是 Web 開發新手,或者從未聽說過諸如 Webpack 或 Parcel 的工具,建議您使用腳本代碼。如果您比較有經驗或想編寫更大的程序,則最好使用構建工具進行探索。
使用腳本代碼
將以下腳本代碼添加到您的主 HTML 文件中。
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.0.0/dist/tf.min.js"></script>
請參見代碼示例了解腳本代碼設置
model.compile({loss: 'meanSquaredError', optimizer: 'sgd'});?// Generate some synthetic data for training. const xs = tf.tensor2d([1, 2, 3, 4], [4, 1]); const ys = tf.tensor2d([1, 3, 5, 7], [4, 1]);?// Train the model using the data. model.fit(xs, ys, {epochs: 10}).then(() => { // Use the model to do inference on a data point the model hasn't seen before: model.predict(tf.tensor2d([5], [1, 1])).print(); // Open the browser devtools to see the output });
從 NPM 安裝
您可以使用 npm cli 工具或 yarn 安裝 TensorFlow.js。
yarn add @tensorflow/tfjs
或
npm install @tensorflow/tfjs
請參見示例代碼以通過 NPM 安裝
// Define a model for linear regression. const model = tf.sequential(); model.add(tf.layers.dense({units: 1, inputShape: [1]}));?model.compile({loss: 'meanSquaredError', optimizer: 'sgd'});?// Generate some synthetic data for training. const xs = tf.tensor2d([1, 2, 3, 4], [4, 1]); const ys = tf.tensor2d([1, 3, 5, 7], [4, 1]);?// Train the model using the data. model.fit(xs, ys, {epochs: 10}).then(() => { // Use the model to do inference on a data point the model hasn't seen before: model.predict(tf.tensor2d([5], [1, 1])).print(); // Open the browser devtools to see the output });
Node.js 設置
您可以使用 npm cli 工具或 yarn 安裝 TensorFlow.js。
選項 1:使用原生 C++ 綁定安裝 TensorFlow.js。
yarn add @tensorflow/tfjs-node
或
npm install @tensorflow/tfjs-node
選項 2:(僅限 Linux)如果您的系統搭載具備 CUDA 支持的 NVIDIA? GPU,請使用 GPU 軟件包以獲得更高性能。
yarn add @tensorflow/tfjs-node-gpu
或
npm install @tensorflow/tfjs-node-gpu
選項 3:安裝純凈版 JavaScript。這是性能最慢的選項。
yarn add @tensorflow/tfjs
或
npm install @tensorflow/tfjs
請參見示例代碼以了解 Node.js 用法
// Optional Load the binding: // Use '@tensorflow/tfjs-node-gpu' if running with GPU. require('@tensorflow/tfjs-node');?// Train a simple model: const model = tf.sequential(); model.add(tf.layers.dense({units: 100, activation: 'relu', inputShape: [10]})); model.add(tf.layers.dense({units: 1, activation: 'linear'})); model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});?const xs = tf.randomNormal([100, 10]); const ys = tf.randomNormal([100, 1]);?model.fit(xs, ys, { epochs: 100, callbacks: { onEpochEnd: (epoch, log) => console.log(`Epoch ${epoch}: loss = ${log.loss}`) } });
TypeScript
使用 TypeScript 時,如果您的項目使用嚴格的 null 檢查,則可能需要在 tsconfig.json
文件中設置 skipLibCheck: true
,否則在編譯過程中會遇到錯誤。
張量和運算
Tensorflow.js是一種框架,用于以Javascript定義和運行使用張量的計算。張量是向量和矩陣向更高緯度的泛畫。
張量
TensorFlow.js 中數據的中央單元為 tf.Tensor
:一組形狀為一維或多維數組的值。tf.Tensor
與多維數組非常相似。
tf.Tensor
還包含以下屬性:
rank
:定義張量包含的維數shape
:定義數據每個維度的大小dtype
:定義張量的數據類型
注:我們會將“維度”一詞與秩互換使用。有時在機器學習中,張量的“維數”也可以指特定維度的大小(例如,形狀為 [10, 5] 的矩陣為 2 秩張量或二維張量。第一維的維數為 10。這可能會造成混淆,但由于您可能會遇到該術語的這兩種說法,因此我們在此提供了此注釋)。
可以使用 tf.tensor()
方法從數組創建 tf.Tensor
:
// Create a rank-2 tensor (matrix) matrix tensor from a multidimensional array.const a = tf.tensor([[1, 2], [3, 4]]);console.log('shape:', a.shape);a.print();?// Or you can create a tensor from a flat array and specify a shape.const shape = [2, 2];const b = tf.tensor([1, 2, 3, 4], shape);console.log('shape:', b.shape);b.print();
默認情況下,tf.Tensor
將具有 float32
dtype
。也可以使用 bool、int32、complex64 和字符串數據類型創建 tf.Tensor
:
const a = tf.tensor([[1, 2], [3, 4]], [2, 2], 'int32');console.log('shape:', a.shape);console.log('dtype', a.dtype);a.print();
TensorFlow.js 還提供了一組便捷的方法,用于創建隨機張量、填充特定值的張量、HTMLImageElement
中的張量,以及此處所列的更多張量。
更改張量的形狀
tf.Tensor
中元素的數量是其形狀大小的乘積。由于通常可以有多個具有相同大小的形狀,因此將 tf.Tensor
重塑為具有相同大小的其他形狀通常非常實用。這可以通過 reshape()
方法實現:
const a = tf.tensor([[1, 2], [3, 4]]);console.log('a shape:', a.shape);a.print();?const b = a.reshape([4, 1]);console.log('b shape:', b.shape);b.print();
從張量獲取值
您還可以使用 Tensor.array()
或 Tensor.data()
方法從 tf.Tensor
中獲取值:
const a = tf.tensor([[1, 2], [3, 4]]);// Returns the multi dimensional array of values.a.array().then(array => console.log(array));// Returns the flattened data that backs the tensor.a.data().then(data => console.log(data));
我們還提供了這些方法的同步版本,這些版本更易于使用,但會在您的應用中引起性能問題。在生產應用中,您應始終優先使用異步方法。
const a = tf.tensor([[1, 2], [3, 4]]);// Returns the multi dimensional array of values.console.log(a.arraySync());// Returns the flattened data that backs the tensor.console.log(a.dataSync());
運算
張量可用于存儲數據,而運算則可用于操作該數據。TensorFlow.js 還提供了可對張量執行的適用于線性代數和機器學習的多種運算。
示例:對 tf.Tensor
中的所有元素執行 x2 計算:
const x = tf.tensor([1, 2, 3, 4]);const y = x.square(); // equivalent to tf.square(x)y.print();
示例:對兩個 tf.Tensor
的元素執行逐元素相加:
const a = tf.tensor([1, 2, 3, 4]);const b = tf.tensor([10, 20, 30, 40]);const y = a.add(b); // equivalent to tf.add(a, b)y.print();
由于張量是不可變的,因此這些運算不會改變其值。相反,return 運算總會返回新的 tf.Tensor
。
注:大多數運算都會返回
tf.Tensor
,但結果實際上可能并未準備就緒。這意味著您獲得的tf.Tensor
實際上是計算的句柄。當您調用Tensor.data()
或Tensor.array()
時,這些方法將返回僅在計算完成時才解析值的 promise。在界面上下文(例如瀏覽器應用)中運行時,應始終首選這些方法的異步版本而非同步版本,以免在計算完成之前阻塞界面線程。
您可以在此處找到 TensorFlow.js 所支持運算的列表。
內存
使用 WebGL 后端時,必須顯式管理 tf.Tensor
內存(即使 tf.Tensor
超出范圍也不足以釋放其內存)。
要銷毀 tf.Tensor 的內存,您可以使用 dispose()
方法或 tf.dispose()
:
const a = tf.tensor([[1, 2], [3, 4]]);a.dispose(); // Equivalent to tf.dispose(a)
在應用中將多個運算鏈接在一起十分常見。保持對用于處置這些運算的所有中間變量的引用會降低代碼的可讀性。為了解決這個問題,TensorFlow.js 提供了 tf.tidy()
方法,可清理執行函數后未被該函數返回的所有 tf.Tensor
,類似于執行函數時清理局部變量的方式:
const a = tf.tensor([[1, 2], [3, 4]]);const y = tf.tidy(() => {const result = a.square().log().neg();return result;});
在此示例中,square()
和 log()
的結果將被自動處置。neg()
的結果不會被處置,因為它是 tf.tidy() 的返回值。
您還可以獲取 TensorFlow.js 跟蹤的張量數量:
console.log(tf.memory());
tf.memory()
打印的對象將包含有關當前分配了多少內存的信息。您可以在此處查找更多信息。
平臺和環境
TensorFlow.js 可以在瀏覽器和 Node.js 中運行,并且在兩個平臺中都具有許多不同的可用配置。每個平臺都有一組影響應用開發方式的獨特注意事項。
在瀏覽器中,TensorFlow.js 支持移動設備以及桌面設備。每種設備都有一組特定的約束(例如可用 WebGL API),系統會自動為您確定和配置這些約束。
在 Node.js 中,TensorFlow.js 支持直接綁定到 TensorFlow API 或搭配較慢的普通 CPU 實現運行。
環境
執行 TensorFlow.js 程序時,特定配置稱為環境。環境由單個全局后端以及一組控制 TensorFlow.js 細粒度功能的標志構成。
后端
TensorFlow.js 支持可實現張量存儲和數學運算的多種不同后端。在任何給定時間內,均只有一個后端處于活動狀態。在大多數情況下,TensorFlow.js 會根據當前環境自動為您選擇最佳后端。但是,有時必須要知道正在使用哪個后端以及如何進行切換。
要確定您使用的后端,請運行以下代碼:
console.log(tf.getBackend());
如果要手動更改后端,請運行以下代碼:
tf.setBackend('cpu');console.log(tf.getBackend());
WebGL 后端
WebGL 后端 ‘webgl’ 是當前適用于瀏覽器的功能最強大的后端。此后端的速度比普通 CPU 后端快 100 倍。張量將存儲為 WebGL 紋理,而數學運算將在 WebGL 著色器中實現。以下為使用此后端時需要了解的一些實用信息:
避免阻塞界面線程
當調用諸如 tf.matMul(a, b) 等運算時,生成的 tf.Tensor 會被同步返回,但是矩陣乘法計算實際上可能還未準備就緒。這意味著返回的 tf.Tensor 只是計算的句柄。當您調用 x.data()
或 x.array()
時,這些值將在計算實際完成時解析。這樣,就必須對同步對應項 x.dataSync()
和 x.arraySync()
使用異步 x.data()
和 x.array()
方法,以避免在計算完成時阻塞界面線程。
內存管理
請注意,使用 WebGL 后端時需要顯式內存管理。瀏覽器不會自動回收 WebGLTexture(最終存儲張量數據的位置)的垃圾。
要銷毀 tf.Tensor
的內存,您可以使用 dispose()
方法:
const a = tf.tensor([[1, 2], [3, 4]]);a.dispose();
在應用中將多個運算鏈接在一起十分常見。保持對用于處置這些運算的所有中間變量的引用會降低代碼的可讀性。為了解決這個問題,TensorFlow.js 提供了 tf.tidy()
方法,可清理執行函數后未被該函數返回的所有 tf.Tensor
,類似于執行函數時清理局部變量的方式:
const a = tf.tensor([[1, 2], [3, 4]]);const y = tf.tidy(() => {const result = a.square().log().neg();return result;});
注:在具有自動垃圾回收功能的非 WebGL 環境(例如 Node.js 或 CPU 后端)中使用
dispose()
或tidy()
沒有弊端。實際上,與自然發生垃圾回收相比,釋放張量內存的性能可能會更勝一籌。
精度
在移動設備上,WebGL 可能僅支持 16 位浮點紋理。但是,大多數機器學習模型都使用 32 位浮點權重和激活進行訓練。這可能會導致為移動設備移植模型時出現精度問題,因為 16 位浮點數只能表示 [0.000000059605, 65504]
范圍內的數字。這意味著您應注意模型中的權重和激活不超出此范圍。要檢查設備是否支持 32 位紋理,請檢查 tf.ENV.getBool('WEBGL_RENDER_FLOAT32_CAPABLE')
的值,如果為 false,則設備僅支持 16 位浮點紋理。您可以使用 tf.ENV.getBool('WEBGL_RENDER_FLOAT32_ENABLED')
來檢查 TensorFlow.js 當前是否使用 32 位紋理。
著色器編譯和紋理上傳
TensorFlow.js 通過運行 WebGL 著色器程序在 GPU 上執行運算。當用戶要求執行運算時,這些著色器會遲緩地進行匯編和編譯。著色器的編譯在 CPU 主線程上進行,可能十分緩慢。TensorFlow.js 將自動緩存已編譯的著色器,從而大幅加快第二次調用具有相同形狀輸入和輸出張量的同一運算的速度。通常,TensorFlow.js 應用在應用生命周期內會多次使用同一運算,因此第二次通過機器學習模型的速度會大幅提高。
TensorFlow.js 還會將 tf.Tensor 數據存儲為 WebGLTextures。創建 tf.Tensor
時,我們不會立即將數據上傳到 GPU,而是將數據保留在 CPU 上,直到在運算中使用 tf.Tensor
為止。第二次使用 tf.Tensor
時,數據已位于 GPU 上,因此不存在上傳成本。在典型的機器學習模型中,這意味著在模型第一次預測期間會上傳權重,而第二次通過模型則會快得多。
如果您在意通過模型或 TensorFlow.js 代碼執行首次預測的性能,我們建議您在使用實際數據之前先通過傳遞相同形狀的輸入張量來預熱模型。
例如:
const model = await tf.loadLayersModel(modelUrl);?// Warmup the model before using real data.const warmupResult = model.predict(tf.zeros(inputShape));warmupResult.dataSync();warmupResult.dispose();?// The second predict() will be much fasterconst result = model.predict(userData);
Node.js TensorFlow 后端
在 TensorFlow Node.js 后端 ‘node’ 中,使用 TensorFlow C API 加速運算。這將在可用情況下使用計算機的可用硬件加速(例如 CUDA)。
在這個后端中,就像 WebGL 后端一樣,運算會同步返回 tf.Tensor
。但與 WebGL 后端不同的是,運算在返回張量之前就已完成。這意味著調用 tf.matMul(a, b)
將阻塞界面線程。
因此,如果打算在生產應用中使用,則應在工作線程中運行 TensorFlow.js 以免阻塞主線程。
有關 Node.js 的更多信息,請參閱本指南。
WASM 后端
TensorFlow.js 提供了 WebAssembly 后端 (wasm
),可實現 CPU 加速,并且可以替代普通的 JavaScript CPU (cpu
) 和 WebGL 加速 (webgl
) 后端。用法如下:
// Set the backend to WASM and wait for the module to be ready.tf.setBackend('wasm');tf.ready().then(() => {...});
如果您的服務器在不同的路徑上或以不同的名稱提供 .wasm
文件,則在初始化后端前請使用 setWasmPath
。有關更多信息,請參閱自述文件中的“使用 Bundler”部分:
import {setWasmPath} from '@tensorflow/tfjs-backend-wasm';setWasmPath(yourCustomPath);tf.setBackend('wasm');tf.ready().then(() => {...});
注:TensorFlow.js 會為每個后端定義優先級并為給定環境自動選擇支持程度最高的后端。要顯式使用 WASM 后端,我們需要調用
tf.setBackend('wasm')
。
為何使用 WASM?
WASM 于 2015 年作為一種基于 Web 的新型二進制格式面世,提供以 JavaScript、C、C++ 等語言編寫的程序。WASM 自 2017 年起受到 Chrome、Safari、Firefox 和 Edge 支持,并獲得全球 90% 設備的支持。
性能
WASM 后端利用 XNNPACK 庫來優化神經網絡算子的實現。
對比 JavaScript:瀏覽器加載、解析和執行 WASM 二進制文件通常比 JavaScript 軟件包要快得多。JavaScript 的動態鍵入和垃圾回收功能可能會導致運行時速度緩慢。
對比 WebGL:WebGL 對于大多數模型而言速度均快于 WASM,但 WASM 針對小型模型的性能則會比 WebGL 更勝一籌,原因是執行 WebGL 著色器存在固定的開銷成本。下文中的“應在何時使用 WASM”部分討論了做此決定的啟發法。
可移植性和穩定性
WASM 具有可移植的 32 位浮點運算,可在所有設備之間提供精度奇偶校驗。另一方面,WebGL 特定于硬件,不同的設備可能具有不同的精度(例如,在 iOS 設備上回退到 16 位浮點)。
與 WebGL 一樣,WASM 也受到所有主流瀏覽器的官方支持。與 WebGL 的不同之處為,WASM 可以在 Node.js 中運行,并且無需編譯原生庫即可在服務器端使用。
應在何時使用 WASM?
模型大小和計算需求
通常,當模型較小或您在意不具備 WebGL 支持(OES_texture_float
擴展)或 GPU 性能較弱的的低端設備時,WASM 是一種不錯的選擇。下表顯示了在 2018 款 MacBook Pro 上使用 Chrome 基于 WebGL、WASM 和 CPU 后端針對官方支持的 5 種模型的推斷時間(自 TensorFlow.js 1.5.2 起):
較小的模型
模型 | WebGL | WASM | CPU | 內存 |
---|---|---|---|---|
BlazeFace | 22.5 ms | 15.6 ms | 315.2 ms | 0.4 MB |
FaceMesh | 19.3 ms | 19.2 ms | 335 ms | 2.8 MB |
較大的模型
模型 | WebGL | WASM | CPU | 內存 |
---|---|---|---|---|
PoseNet | 42.5 ms | 173.9 ms | 1514.7 ms | 4.5 MB |
BodyPix | 77 ms | 188.4 ms | 2683 ms | 4.6 MB |
MobileNet v2 | 37 ms | 94 ms | 923.6 ms | 13 MB |
上表顯示,針對這些模型,WASM 比普通的 JS CPU 后端快 10-30 倍;并且針對 BlazeFace(輕量化 (400KB) 但運算數量尚可 (~140))之類的較小模型,則可與 WebGL 抗衡。考慮到 WebGL 程序每執行一次運算的固定開銷成本,這就解釋了像 BlazeFace 這樣的模型在 WASM 上速度更快的原因。
這些結果將因您的具體設備而異。確定 WASM 是否適合您的應用的最佳方式是在我們不同的后端上對其進行測試。
推斷與訓練
為解決部署預訓練模型的主要用例,WASM 后端的開發工作在推斷方面的支持將優先于訓練。請參見 WASM 所支持運算的最新列表,如果您的模型具有不受支持的運算,請告訴我們。對于訓練模型,建議使用 Node (TensorFlow C++) 后端或 WebGL 后端。
CPU 后端
CPU 后端 ‘cpu’ 是性能最低且最簡單的后端。所有運算均在普通的 JavaScript 中實現,這使它們的可并行性較差。這些運算還會阻塞界面線程。
此后端對于測試或在 WebGL 不可用的設備上非常有用。
標志
TensorFlow.js 具有一組可自動評估的環境標志,這些標志可以確定當前平臺中的最佳配置。大部分標志為內部標志,但有一些可以使用公共 API 控制的全局標志。
tf.enableProdMode()
:啟用生產模式,在此模式下將移除模型驗證、NaN 檢查和其他有利于性能的正確性檢查。tf.enableDebugMode()
:啟用調試模式,在此模式下會將執行的每項運算以及運行時性能信息(如內存占用量和總內核執行時間)記錄到控制臺。請注意,這將大幅降低您應用的速度,請勿在生產中使用。
注:這兩個方法應在使用任何 TensorFlow.js 代碼之前使用,因為它們會影響將緩存的其他標志的值。出于相同的原因,沒有“disable”模擬函數。
注:您可以通過將
tf.ENV.features
記錄到控制臺來查看所有已評估的標志。盡管它們不是公共 API 的一部分(因此不能保證版本之間的穩定性),但它們對于跨平臺和設備進行調試或微調行為而言非常實用。您可以使用tf.ENV.set
重寫標志的值。
自定義操作,內核和梯度
不是專業人士,學不明白這一章,但是不影響后續學習
Overview
This guide outlines the mechanisms for defining custom operations (ops), kernels and gradients in TensorFlow.js. It aims to provide an overview of the main concepts and pointers to code that demonstrate the concepts in action.
Who is this guide for?
This is a fairly advanced guide that touches on some internals of TensorFlow.js, it may be particularly useful for the following groups of people:
- Advanced users of TensorFlow.js interested in customizing behaviour of various mathematical operations (e.g. researchers overriding existing gradient implementations or users who need to patch missing functionality in the library)
- Users building libraries that extend TensorFlow.js (e.g. a general linear algebra library built on top of TensorFlow.js primitives or a new TensorFlow.js backend).
- Users interested in contributing new ops to tensorflow.js who want to get a general overview of how these mechanisms work.
This is not a guide to general use of TensorFlow.js as it goes into internal implementation mechanisms. You do not need to understand these mechanisms to use TensorFlow.js
You do need to be comfortable with (or willing to try) reading TensorFlow.js source code to make the most use of this guide.
Terminology
For this guide a few key terms are useful to describe upfront.
Operations (Ops) — A mathematical operation on one or more tensors that produces one or more tensors as output. Ops are ‘high level’ code and can use other ops to define their logic.
Kernel — A specific implementation of an op tied to specific hardware/platform capabilities. Kernels are ‘low level’ and backend specific. Some ops have a one-to-one mapping from op to kernel while other ops use multiple kernels.
Gradient / GradFunc — The ‘backward mode’ definition of an op/kernel that computes the derivative of that function with regards to some input. Gradients are ‘high level’ code (not backend specific) and can call other ops or kernels.
Kernel Registry - A map from a (kernel name, backend name) tuple to a kernel implementation.
Gradient Registry — A map from a kernel name to a gradient implementation.
Code organization
Operations and Gradients are defined in tfjs-core.
Kernels are backend specific and are defined in their respective backend folders (e.g. tfjs-backend-cpu).
Custom ops, kernels and gradients do not need to be defined inside these packages. But will often use similar symbols in their implementation.
Implementing Custom Ops
One way to think of a custom op is just as a JavaScript function that returns some tensor output, often with tensors as input.
- Some ops can be completely defined in terms of existing ops, and should just import and call these functions directly. Here is an example.
- The implementation of an op can also dispatch to backend specific kernels. This is done via
Engine.runKernel
and will be described further in the “implementing custom kernels” section. Here is an example.
Implementing Custom Kernels
Backend specific kernel implementations allow for optimized implementation of the logic for a given operation. Kernels are invoked by ops calling tf.engine().runKernel()
. A kernel implementations is defined by four things
- A kernel name.
- The backend the kernel is implemented in.
- Inputs: Tensor arguments to the kernel function.
- Attributes: Non-tensor arguments to the kernel function.
Here is an example of a kernel implementation. The conventions used to implement are backend specific and are best understood from looking at each particular backend’s implementation and documentation.
Generally kernels operate at a level lower than tensors and instead directly read and write to memory that will be eventually wrapped into tensors by tfjs-core.
Once a kernel is implemented it can be registered with TensorFlow.js by using registerKernel
function from tfjs-core. You can register a kernel for every backend you want that kernel to work in. Once registered the kernel can be invoked with tf.engine().runKernel(...)
and TensorFlow.js will make sure to dispatch to the implementation in the current active backend.
Implementing Custom Gradients
Gradients are generally defined for a given kernel (identified by the same kernel name used in a call to tf.engine().runKernel(...)
). This allows tfjs-core to use a registry to look up gradient definitions for any kernel at runtime.
Implementing custom gradients are useful for:
- Adding a gradient definition that may not be present in the library
- Overriding an existing gradient definition to customize the gradient computation for a given kernel.
You can see examples of gradient implementations here.
Once you have implemented a gradient for a given call it can be registered with TensorFlow.js by using registerGradient
function from tfjs-core.
The other approach to implementing custom gradients that by-passes the gradient registry (and thus allows for computing gradients for arbitrary functions in arbitrary ways is using tf.customGrad.
Here is an example of an op within the library of using customGrad
模型和層
本頁內容使用 Layers API 創建模型序貫模型函數式模型驗證模型摘要序列化自定義層使用 Core API 創建模型
在機器學習中,模型是一個帶有可學習參數的函數,可將輸入映射至輸出。通過在數據上訓練模型獲得最佳參數。訓練好的模型可以提供從輸入到所需輸出的準確映射。
在 TensorFlow.js 中,您可以通過兩種方式創建機器學習模型:
- 使用 Layers API(使用層構建模型)
- 使用 Core API(借助低級運算,例如
tf.matMul()
、tf.add()
等)
首先,我們會了解 Layers API,Layers API 是用于構建模型的高級 API。然后,我們將演示如何使用 Core API 構建相同的模型。
使用 Layers API 創建模型
你可以通過兩種方式使用 Layers API 創建模型:序貫模型和函數式模型。下面兩部分將詳細介紹兩種類型。
序貫模型
最常見的模型是 [Sequential](https://js.tensorflow.org/api/0.15.1/#class:Sequential)
模型,序貫模型是層的線性堆疊。您可以通過將層列表傳遞到 [sequential()](https://js.tensorflow.org/api/0.15.1/#sequential)
函數來創建 Sequential
模型:
const model = tf.sequential({layers: [tf.layers.dense({inputShape: [784], units: 32, activation: 'relu'}),tf.layers.dense({units: 10, activation: 'softmax'}),]});
inputShape: [784]
: 指定輸入數據的形狀為一個長度為784的一維數組,表示輸入數據的維度為784。units: 32
: 指定該層的神經元數量為32。activation: 'relu'
: 指定該層的激活函數為ReLU(Rectified Linear Unit),用于增加模型的非線性表達能力。units: 10
: 指定該層的神經元數量為10。activation: 'softmax'
: 指定該層的激活函數為Softmax,用于將輸出轉換為概率分布,通常用于多分類問題的最后一層。
或通過 add()
方法:
const model = tf.sequential();model.add(tf.layers.dense({inputShape: [784], units: 32, activation: 'relu'}));model.add(tf.layers.dense({units: 10, activation: 'softmax'}));
重要提示:模型的第一層需要
inputShape
。提供inputShape
時請確保排除批次大小。例如,創建模型時,如果您計劃饋送形狀為[B, 784]
(其中B
可為任何批次大小)的模型張量,請將inputShape
指定為[784]
。
您可以通過 model.layers
訪問模型的層,更具體而言為 model.inputLayers
和 model.outputLayers
。
函數式模型
創建 LayersModel
的另一種方式是通過 tf.model()
函數。tf.model()
和 tf.sequential()
的主要區別為,tf.model()
可用于創建層的任意計算圖,前提是層沒有循環。
以下代碼段可以使用 tf.model()
API 定義與上文相同的模型:
// Create an arbitrary graph of layers, by connecting them// via the apply() method.const input = tf.input({shape: [784]});const dense1 = tf.layers.dense({units: 32, activation: 'relu'}).apply(input);const dense2 = tf.layers.dense({units: 10, activation: 'softmax'}).apply(dense1);const model = tf.model({inputs: input, outputs: dense2});
我們在每一層調用 apply()
以將其連接到另一個層的輸出。在這種情況下,apply()
的結果是一個 SymbolicTensor
,后者類似于 Tensor
,但不包含任何具體值。
請注意,與序貫模型不同,我們通過 tf.input()
創建 SymbolicTensor
,而非向第一層提供 inputShape
。
如果您向 apply()
傳遞一個具體 Tensor
,它也會為您提供一個具體 Tensor
:
const t = tf.tensor([-2, 1, 0, 5]);const o = tf.layers.activation({activation: 'relu'}).apply(t);o.print(); // [0, 1, 0, 5]
這對于單獨測試層并查看它們的輸出非常有用。
與在序貫模型中一樣,您可以通過 model.layers
訪問模型的層,更具體而言為 model.inputLayers
和 model.outputLayers
。
驗證
序貫模型和函數式模型都是 LayersModel
類的實例。使用 LayersModels
的一個主要優勢是驗證:它會強制您指定輸入形狀,并稍后將其用于驗證您的輸入。LayersModel
還會在數據流經層時自動推斷形狀。提前了解形狀后,模型就可以自動創建它的參數,并告知您兩個相鄰的層是否相互兼容。
模型摘要
調用 model.summary()
以打印模型的實用摘要,其中包括:
- 模型中所有層的名稱和類型
- 每個層的輸出形狀
- 每個層的權重參數數量
- 每個層接收的輸入(如果模型具有一般拓撲,下文將討論)
- 模型的可訓練和不可訓練參數總數
對于上面定義的模型,我們在控制臺上獲取以下輸出:
層(類型) | 輸出形狀 | 參數數量 |
---|---|---|
dense_Dense1(密集) | [null,32] | 25120 |
dense_Dense2(密集) | [null,10] | 330 |
參數總數:25450 可訓練參數:25450 不可訓練參數:0 |
注意層的輸出形狀中的 null
值:這表示模型希望輸入的批次大小為最外層維度,在這種情況下,由于 null
值,批次大小比較靈活。
序列化
在較低級別的 API 上使用 LayersModel
的一個主要優勢是能夠保存和加載模型。LayersModel
了解:
- 模型的架構,讓您可以創新創建模型
- 模型的權重
- 訓練配置(損失、優化器和指標)
- 優化器的狀態,讓您可以恢復訓練
保存或加載模型只需要 1 行代碼:
const saveResult = await model.save('localstorage://my-model-1');const model = await tf.loadLayersModel('localstorage://my-model-1');
上面的示例可將模型保存到瀏覽器的本地存儲空間中。請參閱 [model.save() 文檔](https://js.tensorflow.org/api/latest/#tf.Model.save)
和保存并加載指南,了解如何保存到不同的媒介(例如,文件存儲空間、IndexedDB
、觸發瀏覽器下載等)。
自定義層
層是模型的基本要素。如果您的模型需要進行自定義計算,您可以定義一個自定義層,它可以與層的其他部分很好地交互。我們在下面定義的自定義層可以計算平方總數:
class SquaredSumLayer extends tf.layers.Layer {constructor() {super({});}// In this case, the output is a scalar.computeOutputShape(inputShape) { return []; }?// call() is where we do the computation.call(input, kwargs) { return input.square().sum();}?// Every layer needs a unique name.getClassName() { return 'SquaredSum'; }}
要對其進行測試,我們可以調用包含具體張量的 apply()
方法:
const t = tf.tensor([-2, 1, 0, 5]);const o = new SquaredSumLayer().apply(t);o.print(); // prints 30
重要提示:如果添加自定義層,將無法序列化模型。
使用 Core API 創建模型
在本指南開頭處,我們提到可以通過兩種方式在 TensorFlow.js 中創建機器學習模型。
一般來說,您始終應當先嘗試使用 Layers API,因為它基于被廣泛使用的 Keras API,后者遵循最佳做法并降低了認知負擔。Layers API 還提供了各種現成的解決方案,如權重初始化、模型序列化、訓練監視、概率和安全檢查。
在以下情況下,您可能需要使用 Core API:
- 您需要最大程度的靈活性和控制
- 您不需要序列化或可以實現自己的序列化邏輯
使用 Core API 創建的模型是以一個或多個 Tensor
作為輸入并輸出 Tensor
的函數。使用 Core API 編寫的上面同一個模型如下所示:
// The 權重 and 偏差 for the two dense layers.const w1 = tf.variable(tf.randomNormal([784, 32]));const b1 = tf.variable(tf.randomNormal([32]));const w2 = tf.variable(tf.randomNormal([32, 10]));const b2 = tf.variable(tf.randomNormal([10]));?function model(x) {return x.matMul(w1).add(b1).relu().matMul(w2).add(b2).softmax();}
w1
: 這是一個權重矩陣,用于連接輸入層和第一個隱藏層。它的形狀是[784, 32],表示輸入層有784個神經元,第一個隱藏層有32個神經元。這個權重矩陣是通過隨機正態分布初始化的。b1
: 這是一個偏置向量,用于添加到第一個隱藏層的輸出上。它的形狀是[32],表示第一個隱藏層有32個神經元。這個偏置向量也是通過隨機正態分布初始化的。w2
: 這是一個權重矩陣,用于連接第一個隱藏層和輸出層。它的形狀是[32, 10],表示第一個隱藏層有32個神經元,輸出層有10個神經元。這個權重矩陣也是通過隨機正態分布初始化的。b2
: 這是一個偏置向量,用于添加到輸出層的輸出上。它的形狀是[10],表示輸出層有10個神經元。這個偏置向量也是通過隨機正態分布初始化的。model(x)
: 這是定義的模型函數,接受一個輸入張量x
作為參數。在函數內部,首先將輸入張量與權重矩陣w1
相乘,然后加上偏置向量b1
,接著應用ReLU激活函數,再將結果與權重矩陣w2
相乘,最后加上偏置向量b2
,并應用softmax激活函數。最終返回經過這些操作后的輸出張量。
請注意,在 Core API 中,我們需要創建和初始化模型的權重。每個權重都由一個 Variable
支持,變量可以告知 TensorFlow.js 這些張量是可學習張量。您可以使用 tf.variable() 并傳入現有 Tensor
來創建 Variable
。
本文介紹了如何使用 Layers API 和 Core API 創建模型。接下來,請參閱訓練模型指南了解如何訓練模型。
訓練模型
本頁內容簡介模型參數優化器、損失和指標訓練model.fit()model.fitDataset()預測新數據Core API
本指南假定您已閱讀模型和層指南。
在 TensorFlow.js 中,您可以通過以下兩種方式訓練機器學習模型:
- 使用 Layers API 與
[LayersModel.fit()](https://js.tensorflow.org/api/latest/#tf.Model.fit)
或[LayersModel.fitDataset()](https://js.tensorflow.org/api/latest/#tf.Model.fitDataset)
。 - 使用 Core API 與
[Optimizer.minimize()](https://js.tensorflow.org/api/latest/#tf.train.Optimizer.minimize)
。
首先,我們將了解 Layers API,它是一種用于構建和訓練模型的高級 API。然后,我們將展示如何使用 Core API 訓練相同的模型。
簡介
機器學習模型是一種具有可學習參數的函數,可將輸入映射到所需輸出。基于數據訓練模型可以獲得最佳參數。
訓練涉及多個步驟:
- 獲取一批次數據來訓練模型。
- 讓模型做出預測。
- 將該預測與“真實”值進行對比。
- 確定每個參數的更改幅度,使模型在未來能夠針對該批次數據做出更好的預測。
訓練得當的模型將提供從輸入到所需輸出的準確映射。
模型參數
讓我們使用 Layers API 來定義一個簡單的 2 層模型:
const model = tf.sequential({layers: [tf.layers.dense({inputShape: [784], units: 32, activation: 'relu'}),tf.layers.dense({units: 10, activation: 'softmax'}),]});
模型以可學習參數(常稱為權重)為基礎,基于數據進行訓練。讓我們打印與此模型及其形狀關聯的權重的名稱:
model.weights.forEach(w => {console.log(w.name, w.shape);});
我們得到以下輸出:
> dense_Dense1/kernel [784, 32]> dense_Dense1/bias [32]> dense_Dense2/kernel [32, 10]> dense_Dense2/bias [10]
共有 4 個權重,每個密集層 2 個。這是可以預期的,因為密集層表示一個函數,通過等式 y = Ax + b
將輸入張量 x
映射到輸出張量 y
,其中 A
(內核)和 b
(偏差)為密集層參數。
注:默認情況下,密集層將包含偏差,但您可以通過在創建密集層時的選項中指定
{useBias: false}
將其排除。
如果您想簡要了解模型并查看參數總數,model.summary()
是一種實用的方法:
層(類型) | 輸出形狀 | 參數數量 |
---|---|---|
dense_Dense1(密集) | [null,32] | 25120 |
dense_Dense2(密集) | [null,10] | 330 |
參數總數:25450 可訓練參數:25450 不可訓練參數:0 |
模型中的每個權重均由 [Variable](https://js.tensorflow.org/api/0.14.2/#class:Variable)
對象提供支持。在 TensorFlow.js 中,Variable
為浮點型 Tensor
,具有一個用于更新值的附加方法 assign()
。Layers API 會使用最佳做法自動初始化權重。出于演示目的,我們可以通過在基礎變量上調用 assign()
來覆蓋權重:
model.weights.forEach(w => {const newVals = tf.randomNormal(w.shape);// w.val is an instance of tf.Variablew.val.assign(newVals);});
優化器、損失和指標
進行任何訓練之前,您需要確定以下三項內容:
- 優化器。優化器的作用是在給定當前模型預測的情況下,決定對模型中每個參數實施更改的幅度。使用 Layers API 時,您可以提供現有優化器的字符串標識符(例如
'sgd'
或'adam'
),也可以提供[Optimizer](https://js.tensorflow.org/api/latest/#Training-Optimizers)
類的實例。 - 損失函數。模型將以最小化損失作為目標。該函數旨在將模型預測的“誤差程度”量化為具體數字。損失以每一批次數據為基礎計算,因此模型可以更新其權重。使用 Layers API 時,您可以提供現有損失函數的字符串標識符(例如
'categoricalCrossentropy'
),也可以提供任何采用預測值和真實值并返回損失的函數。請參閱我們的 API 文檔中的可用損失列表。 - 指標列表。 與損失類似,指標也會計算一個數字,用于總結模型的運作情況。通常要在每個周期結束時基于整體數據來計算指標。至少,我們要監控損失是否隨著時間推移而下降。但是,我們經常需要準確率等更人性化的指標。使用 Layers API 時,您可以提供現有指標的字符串標識符(例如
'accuracy'
),也可以提供任何采用預測值和真實值并返回分數的函數。請參閱我們的 API 文檔中的可用指標列表。
確定后,使用提供的選項調用 model.compile()
來編譯 LayersModel
:
model.compile({optimizer: 'sgd',loss: 'categoricalCrossentropy',metrics: ['accuracy']});
在編譯過程中,模型將進行一些驗證以確保您所選擇的選項彼此兼容。
訓練
您可以通過以下兩種方式訓練 LayersModel
:
- 使用
model.fit()
并以一個大型張量形式提供數據。 - 使用
model.fitDataset()
并通過Dataset
對象提供數據。
model.fit()
如果您的數據集適合裝入主內存,并且可以作為單個張量使用,則您可以通過調用 fit()
方法來訓練模型:
// Generate dummy data.const data = tf.randomNormal([100, 784]);const labels = tf.randomUniform([100, 10]);?function onBatchEnd(batch, logs) {console.log('Accuracy', logs.acc);}?// Train for 5 epochs with batch size of 32.model.fit(data, labels, {epochs: 5,batchSize: 32,callbacks: {onBatchEnd}}).then(info => {console.log('Final accuracy', info.history.acc);});
model.fit()
在后臺可以完成很多操作:
- 將數據拆分為訓練集和驗證集,并使用驗證集衡量訓練期間的進度。
- 打亂數據順序(僅在拆分后)。為了安全起見,您應該在將數據傳遞至
fit()
之前預先打亂數據順序。 - 將大型數據張量拆分成大小為
batchSize
的小型張量。 - 在計算相對于一批次數據的模型損失的同時,調用
optimizer.minimize()
。 - 可以在每個周期或批次的開始和結尾為您提供通知。我們的示例使用
callbacks.onBatchEnd
選項在每個批次的結尾提供通知。其他選項包括:onTrainBegin
、onTrainEnd
、onEpochBegin
、onEpochEnd
和onBatchBegin
。 - 受制于主線程,確保 JS 事件循環中排隊的任務可以得到及時處理。
有關更多信息,請參閱 fit()
的文檔。請注意,如果您選擇使用 Core API,則必須自行實現此邏輯。
model.fitDataset()
如果您的數據不能完全裝入內存或進行流式傳輸,則您可以通過調用 fitDataset()
來訓練模型,它會獲取一個 Dataset
對象。以下為相同的訓練代碼,但具有包裝生成器函數的數據集:
function* data() {for (let i = 0; i < 100; i++) {// Generate one sample at a time.yield tf.randomNormal([784]);}}?function* labels() {for (let i = 0; i < 100; i++) {// Generate one sample at a time.yield tf.randomUniform([10]);}}?const xs = tf.data.generator(data);const ys = tf.data.generator(labels);// We zip the data and labels together, shuffle and batch 32 samples at a time.const ds = tf.data.zip({xs, ys}).shuffle(100 /* bufferSize */).batch(32);?// Train the model for 5 epochs.model.fitDataset(ds, {epochs: 5}).then(info => {console.log('Accuracy', info.history.acc);});
有關數據集的更多信息,請參閱 model.fitDataset()
文檔。
預測新數據
在模型完成訓練后,您可以調用 model.predict()
,基于未見過的數據進行預測:
// Predict 3 random samples.const prediction = model.predict(tf.randomNormal([3, 784]));prediction.print();
注:正如我們在模型和層指南中所講,LayersModel
期望輸入的最外層維度為批次大小。在上例中,批次大小為 3。
Core API
之前,我們提到您可以通過兩種方式在 TensorFlow.js 中訓練機器學習模型。
根據常規經驗法則,可以首先嘗試使用 Layers API,因為它是由廣為采用的 Keras API 建模而成。Layers API 還提供了各種現成的解決方案,例如權重初始化、模型序列化、監控訓練、可移植性和安全性檢查。
在以下情況下,您可以使用 Core API:
- 您需要最大的靈活性或控制力。
- 并且您不需要序列化,或者可以實現自己的序列化邏輯。
有關此 API 的更多信息,請參閱模型和層指南中的“Core API”部分。
使用 Core API 編寫上述相同模型,方法如下:
// The weights and biases for the two dense layers.const w1 = tf.variable(tf.randomNormal([784, 32]));const b1 = tf.variable(tf.randomNormal([32]));const w2 = tf.variable(tf.randomNormal([32, 10]));const b2 = tf.variable(tf.randomNormal([10]));?function model(x) {return x.matMul(w1).add(b1).relu().matMul(w2).add(b2);}
除了 Layers API 以外,Data API 也可與 Core API 無縫協作。讓我們重用先前在 model.fitDataset() 部分中定義的數據集,該數據集已完成打亂順序和批處理操作:
const xs = tf.data.generator(data);const ys = tf.data.generator(labels);// Zip the data and labels together, shuffle and batch 32 samples at a time.const ds = tf.data.zip({xs, ys}).shuffle(100 /* bufferSize */).batch(32);
讓我們訓練模型:
const optimizer = tf.train.sgd(0.1 /* learningRate */);// Train for 5 epochs.for (let epoch = 0; epoch < 5; epoch++) {await ds.forEachAsync(({xs, ys}) => {optimizer.minimize(() => {const predYs = model(xs);const loss = tf.losses.softmaxCrossEntropy(ys, predYs);loss.data().then(l => console.log('Loss', l));return loss;});});console.log('Epoch', epoch);}
以上代碼是使用 Core API 訓練模型時的標準方法:
- 循環周期數。
- 在每個周期內,循環各批次數據。使用
Dataset
時,[dataset.forEachAsync()](https://js.tensorflow.org/api/0.15.1/#tf.data.Dataset.forEachAsync)
可方便地循環各批次數據。 - 針對每個批次,調用
[optimizer.minimize(f)](https://js.tensorflow.org/api/latest/#tf.train.Optimizer.minimize)
,它可以執行f
并通過計算相對于我們先前定義的四個變量的梯度來最小化其輸出。 f
可計算損失。它使用模型的預測和真實值調用預定義的損失函數之一。
保存和加載模型
本頁內容保存 tf.Model本地存儲空間(僅限瀏覽器)IndexedDB(僅限瀏覽器)文件下載(僅限瀏覽器)HTTP(S) 請求原生文件系統(僅限 Node.js)加載 tf.Model本地存儲空間(僅限瀏覽器)
TensorFlow.js 提供了保存和加載模型的功能,這些模型可以使用 Layers
API 創建或從現有 TensorFlow 模型轉換而來。可能是您自己訓練的模型,也可能是其他人訓練的模型。使用 Layers API 的一個主要好處是,使用它創建的模型是可序列化模型,這就是我們將在本教程中探討的內容。
本教程將重點介紹如何保存和加載 TensorFlow.js 模型(可通過 JSON 文件識別)。我們也可以導入 TensorFlow Python 模型。以下兩個教程介紹了如何加載這些模型:
- 導入 Keras 模型
- 導入 Graphdef 模型
保存 tf.Model
tf.Model
和 tf.Sequential
都提供了 model.save
函數,您可以借助該函數保存模型的拓撲和權重 。
- 拓撲:這是一個描述模型架構的文件(例如模型使用了哪些運算)。它包含對外部存儲的模型權重的引用。
- 權重:這些是以有效格式存儲給定模型權重的二進制文件。它們通常存儲在與拓撲相同的文件夾中。
我們來看看用于保存模型的代碼:
const saveResult = await model.save('localstorage://my-model-1');
一些需要注意的地方:
save
方法采用以協議名稱開頭的類網址字符串參數。它描述了我們想保存模型的地址的類型。在上例中,協議名稱為localstorage://
。- 協議名稱之后是路徑。在上例中,路徑是
my-model-1
。 save
方法是異步的。model.save
的返回值是一個 JSON 對象,包含模型的拓撲和權重的字節大小等信息。- 用于保存模型的環境不會影響可以加載模型的環境。在 node.js 中保存模型不會阻礙在瀏覽器中加載模型。
我們將在下面查看不同協議名稱。
本地存儲空間(僅限瀏覽器)
協議名稱: localstorage://
await model.save('localstorage://my-model');
這會在瀏覽器的本地存儲空間中以名稱 my-model
保存模型。這樣能夠在瀏覽器刷新后保持不變,而當存儲空間成為問題時,用戶或瀏覽器本身可以清除本地存儲。每個瀏覽器還可為給定域設置本地存儲空間中可以存儲的數據量。
IndexedDB(僅限瀏覽器)
協議名稱: indexeddb://
await model.save('indexeddb://my-model');
這會將模型保存到瀏覽器的 IndexedDB 存儲空間中。與本地存儲一樣,它在刷新后仍然存在,同時所存儲對象大小的上限更高。
文件下載(僅限瀏覽器)
協議名稱: downloads://
await model.save('downloads://my-model');
這會讓瀏覽器將模型文件下載至用戶的機器上。將生成兩個文件:
- 一個名為
[my-model].json
的 JSON 文本文件,其中包含模型拓撲和對下文所述權重文件的引用。 - 一個二進制文件,其中包含名為
[my-model].weights.bin
的權重值。
您可以更改 [my-model]
名稱以獲得一個名稱不同的文件。
由于 .json
文件使用相對路徑指向 .bin
,因此兩個文件應位于同一個文件夾中。
注:某些瀏覽器要求用戶先授予權限,然后才能同時下載多個文件。
HTTP(S) 請求
協議名稱:http://
或 https://
await model.save('http://model-server.domain/upload')
這將創建一個 Web 請求,以將模型保存到遠程服務器。您應該控制該遠程服務器,確保它能夠處理該請求。
模型將通過 POST 請求發送至指定的 HTTP 服務器。POST 主體采用 multipart/form-data
格式并包含兩個文件:
- 一個名為
model.json
的 JSON 文本文件,其中包含模型拓撲和對下文所述權重文件的引用。 - 一個二進制文件,其中包含名為
model.weights.bin
的權重值。
請注意,這兩個文件的名稱需要始終與上面所指定的完全相同(因為名稱內置于函數中)。此 API 文檔包含一個 Python 代碼段,演示了如何使用 Flask Web 框架處理源自 save
的請求。
通常,您必須向 HTTP 服務器傳遞更多參數或請求頭(例如,用于身份驗證,或者如果要指定應保存模型的文件夾)。您可以通過替換 tf.io.browserHTTPRequest
中的網址字符串參數來獲得對來自 save
的請求在這些方面的細粒度控制。此 API 在控制 HTTP 請求方面提供了更大的靈活性。
例如:
await model.save(tf.io.browserHTTPRequest('http://model-server.domain/upload',{method: 'PUT', headers: {'header_key_1': 'header_value_1'} }));
原生文件系統(僅限 Node.js)
協議名稱: file://
await model.save('file:///path/to/my-model');
在 Node.js 上運行時,我們還可以直接訪問文件系統并保存模型。上面的命令會將兩個文件保存到在 scheme
后指定的 path
中。
- 一個名為
[model].json
的 JSON 文本文件,其中包含模型拓撲和對下文所述權重文件的引用。 - 一個二進制文件,其中包含名為
[model].weights.bin
的權重值。
請注意,這兩個文件的名稱需要始終與上面所指定的完全相同(因為名稱內置于函數中)。
加載 tf.Model
給定一個使用上述方法之一保存的模型,我們可以使用 tf.loadLayersModel
API 加載它。
我們來看看加載模型的代碼:
const model = await tf.loadLayersModel('localstorage://my-model-1');
一些需要注意的地方:
- 與
model.save()
類似,loadLayersModel
函數也采用以協議名稱開頭的類網址字符串參數。它描述了我們想要從中加載模型的目標類型。 - 協議名稱之后是路徑。在上例中,路徑是
my-model-1
。 - 類網址字符串可以替換為與 IOHandler 接口匹配的對象。
tf.loadLayersModel()
函數是異步的。tf.loadLayersModel
的返回值為tf.Model
。
我們將在下面查看不同協議名稱。
本地存儲空間(僅限瀏覽器)
協議名稱: localstorage://
const model = await tf.loadLayersModel('localstorage://my-model');
這將從瀏覽器的本地存儲空間加載一個名為 my-model
的模型。
IndexedDB(僅限瀏覽器)
協議名稱: indexeddb://
const model = await tf.loadLayersModel('indexeddb://my-model');
這將從瀏覽器的 IndexedDB 存儲空間加載一個模型。
HTTP(S)
協議名稱:http://
或 https://
const model = await tf.loadLayersModel('http://model-server.domain/download/model.json');
這將從 HTTP 端點加載模型。加載 json
文件后,函數將請求 json
文件引用的對應 .bin
文件。
注:此實現依賴于
fetch
方法,如果您的環境沒有提供原生 fetch 方法,您可以提供滿足接口要求的全局方法名稱fetch
,或者使用類似于 (node-fetch
)[https://www.npmjs.com/package/node-fetch] 的庫。
原生文件系統(僅限 Node.js)
協議名稱: file://
const model = await tf.loadLayersModel('file://path/to/my-model/model.json');
在 Node.js 上運行時,我們還可以直接訪問文件系統并加載模型。請注意,在上面的函數調用中,我們引用 model.json 文件本身(在保存時,我們指定一個文件夾)。對應的 .bin
文件應與 json
文件位于同一個文件夾中。
使用 IOHandler 加載模型
如果上述協議名稱沒有滿足您的需求,您可以使用 IOHandler
實現自定義加載行為。Tensorflow.js 提供的一個 IOHandler
是 tf.io.browserFiles
,它允許瀏覽器用戶在瀏覽器中上傳模型文件。請參閱文檔了解更多信息。
使用自定義 IOHandler 保存或加載模型
如果上述協議名稱沒有滿足您的保存或加載需求,您可以通過實現 IOHandler
來實現自定義序列化行為。
IOHandler
是一個包含 save
和 load
方法的對象。
save
函數采用一個與 ModelArtifacts 接口匹配的參數,應返回一個解析為 SaveResult 對象的 promise。
load
函數不采用參數,應返回一個解析為 ModelArtifacts 對象的 promise。這是傳遞給 save
的同一對象。
請參閱 BrowserHTTPRequest 獲取如何實現 IOHandler 的示例。
模型轉換
本頁內容轉換您的模型最佳做法運行您的模型
TensorFlow.js 附帶各種預訓練模型,這些模型可以在瀏覽器中使用,您可以在我們的模型倉庫中找到它們。但是,您可能已經在其他地方找到或創建了一個 TensorFlow 模型,并希望在網絡應用中使用該模型。TensorFlow.js 為此目的提供了一個模型轉換器。TensorFlow.js 轉換器有兩個組件:
- 一個命令行實用工具,用于轉換 Keras 和 TensorFlow 模型以在 TensorFlow.js 中使用。
- 一個 API ,用于在瀏覽器中使用 TensorFlow.js 加載和執行模型。
轉換您的模型
TensorFlow.js 轉換器可以轉換以下幾種格式的模型:
SavedModel:保存 TensorFlow 模型的默認格式。有關 SavedModel 格式的詳細信息,請參閱此處。
Keras 模型:Keras 模型通常保存為 HDF5 文件。有關保存 Keras 模型的更多信息,請訪問此處。
TensorFlow Hub 模塊:這些是打包后用于在 TensorFlow Hub 上分發的模型,TensorFlow Hub 是一個共享和發現模型的平臺。模型庫位于此處。
根據您嘗試轉換的模型的類型,您需要將不同的參數傳遞給轉換器。例如,假設您將一個名為 model.h5
的 Keras 模型保存到 tmp/
目錄中。要使用 TensorFlow.js 轉換器轉換模型,您可以運行以下命令:
$ tensorflowjs_converter --input_format=keras /tmp/model.h5 /tmp/tfjs_model
這會轉換 /tmp/model.h5
下的模型并將 model.json
文件及二進制權重文件輸出到 tmp/tfjs_model/
目錄中。
有關不同模型格式對應的命令行參數的更多詳細信息,請參閱 TensorFlow.js 轉換器自述文件。
在轉換過程中,我們會遍歷模型計算圖并檢查 TensorFlow.js 是否支持每個運算。如果支持,我們會將計算圖轉換成瀏覽器可以使用的格式。我們嘗試通過將權重分成 4MB 的文件(這樣它們可以被瀏覽器緩存)來優化模型以便在網絡上應用。我們也嘗試使用開放源代碼 Grappler 項目簡化模型計算圖。計算圖簡化包括折疊相鄰運算,從而消除常見子計算圖等。這些變更對模型的輸出沒有影響。要進行進一步優化,用戶可以傳入參數以指示轉換器將模型量化到特定的字節大小。量化是一種縮減模型大小的技術,它使用更少的位來表示權重。用戶必須謹慎操作,以確保量化后模型的準確率保持在可接受范圍內。
如果在轉換過程中遇到不支持的運算,該過程將失敗,我們將為用戶打印該運算的名稱。請在我們的 GitHub 下提交議題告訴我們相關信息,我們會嘗試根據用戶需求實現新運算。
最佳做法
雖然我們會在轉換過程中盡力優化您的模型,但通常確保您的模型高效運行的最佳方式是在構建時考慮資源受限的環境。這意味著避免過于復雜的架構和盡可能減少參數(權重)的數量。
運行您的模型
成功轉換模型之后,您將得到一組權重文件和一個模型拓撲文件。TensorFlow.js 提供了模型加載 API,您可以使用這些 API 提取模型資源并在瀏覽器中運行推斷。
以下是適用于轉換后的 TensorFlow SavedModel 或 TensorFlow Hub 模塊的 API:
const model = await tf.loadGraphModel(‘path/to/model.json’);
以下是適用于轉換后的 Keras 模型的 API:
const model = await tf.loadLayersModel(‘path/to/model.json’);
tf.loadGraphModel
API 返回 tf.FrozenModel
,這意味著參數已被固定并且您無法使用新數據微調模型。tf.loadLayersModel
API 返回可訓練的 tf.Model。有關如何訓練 tf.Model 的信息,請參閱訓練模型指南。
轉換后,建議您運行幾次推斷并對模型的速度進行基準測試。為此,我們提供了一個獨立的基準測試頁面:https://tensorflow.github.io/tfjs/e2e/benchmarks/local-benchmark/index.html。您可能注意到我們丟棄了初始預熱運行中的測量值,這是因為(通常情況下),由于創建紋理和編譯著色器的開銷,您的模型的首次推斷將比后續推斷慢幾倍。
適用于 Keras 用戶的 TensorFlow.js 層 API
本頁內容構造函數將 JavaScript 對象作為配置Model.fit() 是異步方法TensorFlow.js 中沒有 NumPy使用工廠方法,而不是構造函數選項字符串值為小駝峰式命名法,而不是蛇形命名法
TensorFlow.js 的 Layers API 以 Keras 為模型。考慮到 JavaScript 與 Python 之間的差異,我們努力使 Layers API 與 Keras 類似。這樣,具有使用 Python 開發 Keras 模型經驗的用戶可以更輕松地遷移到使用 JavaScript 編寫的 TensorFlow.js 層。例如,以下 Keras 代碼可以轉換為 JavaScript:
# Python:
import keras
import numpy as np# Build and compile model.
model = keras.Sequential()
model.add(keras.layers.Dense(units=1, input_shape=[1]))
model.compile(optimizer='sgd', loss='mean_squared_error')# Generate some synthetic data for training.
xs = np.array([[1], [2], [3], [4]])
ys = np.array([[1], [3], [5], [7]])# Train model with fit().
model.fit(xs, ys, epochs=1000)# Run inference with predict().
print(model.predict(np.array([[5]])))
// JavaScript:
import * as tf from '@tensorlowjs/tfjs';// Build and compile model.
const model = tf.sequential();
model.add(tf.layers.dense({units: 1, inputShape: [1]}));
model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});// Generate some synthetic data for training.
const xs = tf.tensor2d([[1], [2], [3], [4]], [4, 1]);
const ys = tf.tensor2d([[1], [3], [5], [7]], [4, 1]);// Train model with fit().
await model.fit(xs, ys, {epochs: 1000});// Run inference with predict().
model.predict(tf.tensor2d([[5]], [1, 1])).print();
但是,我們希望在本文檔中說明并解釋一些差異。一旦理解了這些差異及其背后的基本原理,將您的程序從Python 遷移到JavaScript(或反向遷移)應該會是一種相對平穩的體驗。
構造函數將 JavaScript 對象作為配置
比較上例中的以下 Python 和 JavaScript 代碼:它們都可以創建一個密集層。
# Python:
keras.layers.Dense(units=1, inputShape=[1])
// JavaScript:
tf.layers.dense({units: 1, inputShape: [1]});
JavaScript 函數在 Python 函數中沒有等效的關鍵字參數。我們希望避免在 JavaScript 中將構造函數選項作為位置參數實現,這對于記憶和使用具有大量關鍵字參數的構造函數(例如 LSTM)來說尤其麻煩。這就是我們使用 JavaScript 配置對象的原因。這些對象提供與 Python 關鍵字參數相同的位置不變性和靈活性。
Model 類的一些方法(例如 Model.compile()
)也將 JavaScript 配置對象作為輸入。但是請記住,Model.fit()
、Model.evaluate()
和 Model.predict()
略有不同。因為這些方法將強制 x
(特征)和 y
(標簽或目標)數據作為輸入;x
和 y
是與后續配置對象分開的位置參數,屬于關鍵字參數。例如:
// JavaScript:
await model.fit(xs, ys, {epochs: 1000});
Model.fit() 是異步方法
Model.fit()
是用戶在 TensorFlow.js 中執行模型訓練的主要方法。此方法通常可以長時間運行(持續數秒或數分鐘)。因此,我們利用 JavaScript 語言的 async
特性,因此在瀏覽器中運行時,能夠以不阻塞主界面線程的方式使用此函數。這與 JavaScript 中其他可能長時間運行的函數類似,例如 async
獲取。請注意,async
是一個在 Python 中不存在的構造。Keras 中的 fit()
方法返回一個 History 對象,而 fit()
方法在 JavaScript 中的對應項則返回 History 的 Promise,這個響應可以等待(如上例中所示),也可與 then() 方法一起使用。
TensorFlow.js 中沒有 NumPy
Python Keras 用戶經常使用 NumPy 來執行基本的數值和數組運算,例如在上例中生成二維張量。
# Python:
xs = np.array([[1], [2], [3], [4]])
在 TensorFlow.js 中,這種基本的數值運算是使用軟件包本身完成的。例如:
// JavaScript:
const xs = tf.tensor2d([[1], [2], [3], [4]], [4, 1]);
tf.*
命名空間還為數組和線性代數運算(例如矩陣乘法)提供了大量其他函數。有關更多信息,請參閱 TensorFlow.js Core 文檔。
使用工廠方法,而不是構造函數
Python 中的這一行(來自上例)是一個構造函數調用:
# Python:
model = keras.Sequential()
如果嚴格轉換為 JavaScript,則等效構造函數調用將如下所示:
// JavaScript:
const model = new tf.Sequential(); // !!! DON'T DO THIS !!!
不過,我們決定不使用“new”構造函數,因為 1)“new”關鍵字會使代碼更加膨脹;2)“new”構造函數被視為 JavaScript 的“不良部分”:一個潛在的陷阱,如 JavaScript: the Good Parts 中所討論。要在 TensorFlow.js 中創建模型和層,可以調用具有 lowerCamelCase(小駝峰式命名法)名稱的工廠方法,例如:
// JavaScript:
const model = tf.sequential();const layer = tf.layers.batchNormalization({axis: 1});
選項字符串值為小駝峰式命名法,而不是蛇形命名法
在 JavaScript 中,更常見的是為符號名稱使用駝峰命名法(例如,請參閱 Google JavaScript 樣式指南),而在 Python 中,蛇形命名法很常見(例如,在 Keras 中)。因此,我們決定使用小駝峰式命名法作為選項的字符串值,包括:
- DataFormat,例如,channelsFirst 而不是 channels_first
- 初始值設定項,例如,
glorotNormal
而不是glorot_normal
- 損失和指標,例如,
meanSquaredError
而不是mean_squared_error
,categoricalCrossentropy
而不是categorical_crossentropy
。
例如,如上例所示:
// JavaScript:
model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});
對于模型序列化和反序列化,請放心。TensorFlow.js 的內部機制可以確保正確處理 JSON 對象中的蛇形命名法,例如,在從 Python Keras 加載預訓練模型時。
使用 apply() 運行 Layer 對象,而不是將其作為函數調用
在 Keras 中,Layer 對象定義了 __call__
方法。因此,用戶可以通過將對象作為函數調用來調用層的邏輯,例如:
# Python:
my_input = keras.Input(shape=[2, 4])
flatten = keras.layers.Flatten()print(flatten(my_input).shape)
這個 Python 語法糖在 TensorFlow.js 中作為 apply() 方法實現:
// JavaScript:
const myInput = tf.input({shape: [2, 4]});
const flatten = tf.layers.flatten();console.log(flatten.apply(myInput).shape);
Layer.apply() 支持對具體張量進行命令式 (Eager) 執行
目前,在 Keras 中,調用方法只能在 (Python) TensorFlow 的 tf.Tensor
對象上運行(假設 TensorFlow 是后端),這些對象是符號對象并且不包含實際數值。這就是上一部分中的示例所顯示的內容。但是,在 TensorFlow.js 中,層的 apply()
方法可以在符號和命令模式下運行。如果使用 SymbolicTensor(類似于 tf.Tensor)調用 apply()
,返回值將為 SymbolicTensor。這通常發生在模型構建期間。但是,如果使用實際的具體張量值調用 apply()
,將返回一個具體的張量。例如:
// JavaScript:
const flatten = tf.layers.flatten();flatten.apply(tf.ones([2, 3, 4])).print();
這個特性讓人聯想到 (Python) TensorFlow 的 Eager Execution。它在模型開發期間提供了更出色的交互性和可調試性,并且為構建動態神經網絡打開了大門。
優化器在 train. 下,而不是在 optimizers. 下
在 Keras 中,Optimizer 對象的構造函數位于 keras.optimizers.
命名空間下。在 TensorFlow.js Layers 中,Optimizer 的工廠方法位于 tf.train.
命名空間下。例如:
# Python:
my_sgd = keras.optimizers.sgd(lr=0.2)
// JavaScript:
const mySGD = tf.train.sgd({lr: 0.2});
loadLayersModel() 從網址而不是 HDF5 文件加載
在 Keras 中,模型通常保存為 HDF5 (.h5) 文件,然后可以使用 keras.models.load_model()
方法加載。該方法采用 .h5 文件的路徑。load_model()
在 TensorFlow.js 中的對應項是 tf.loadLayersModel()
。由于 HDF5 文件格式對瀏覽器并不友好,因此 tf.loadLayersModel()
采用 TensorFlow.js 特定的格式。tf.loadLayersModel()
將 model.json 文件作為其輸入參數。可以使用 tensorflowjs 的 pip 軟件包從 Keras HDF5 文件轉換 model.json。
// JavaScript:
const model = await tf.loadLayersModel('https://foo.bar/model.json');
還要注意,tf.loadLayersModel()
返回 tf.Model
的 Promise
。
一般來說,在 TensorFlow.js 中分別使用 tf.Model.save
和 tf.loadLayersModel
方法保存和加載 tf.Model
。我們將這些 API 設計為類似于 Keras 的 save_model 和 load_model API。但是,瀏覽器環境與 Keras 等主要深度學習框架運行的后端環境完全不同,特別是在用于持久化和傳輸數據的路由數組中。因此,TensorFlow.js 和 Keras 中的保存/加載 API 之間存在一些有趣的差異。有關更多詳細信息,請參閱我們有關保存和加載 tf.Model 的教程。
利用 fitDataset()
訓練使用 tf.data.Dataset
對象的模型
在 Python TensorFlow 的 tf.keras 中,模型可以使用 Dataset 對象進行訓練。模型的 fit()
方法直接接受此類對象。TensorFlow.js 模型可以使用 Dataset 對象的 JavaScript 對應項進行訓練(請參閱 TensorFlow.js 中的 tf.data API 文檔。不過,與 Python 不同,基于 Dataset 的訓練是通過一個名為 fitDataset 的專用方法完成的。fit() 方法僅適用于基于張量的模型訓練。
Layer 對象和 Model 對象的內存管理
TensorFlow.js 在瀏覽器中的 WebGL 上運行,其中 Layer 對象和 Model 對象的權重由 WebGL 紋理支持。不過,WebGL 不支持內置垃圾收集。在推斷和訓練調用過程中,Layer 對象和 Model 對象為用戶在內部管理張量內存。但是,它們也允許用戶清理以釋放占用的 WebGL 內存。對于在單頁加載中創建和釋放許多模型實例的情況,這樣做很有用。要想清理 Layer 對象或 Model 對象,請使用 dispose()
方法。
Node 中的 TensorFlow.js
本頁內容TensorFlow CPUTensorFlow GPU普通 CPU生產考量因素APItf.browsertf.node
TensorFlow CPU
TensorFlow CPU 軟件包可以按如下方式導入:
import * as tf from '@tensorflow/tfjs-node'
從此軟件包導入 TensorFlow.js 時,您導入的模塊將由 TensorFlow C 二進制文件加速并在 CPU 上運行。CPU 上的 TensorFlow 使用硬件加速來加速后臺的線性代數運算。
此軟件包可以在支持 TensorFlow 的 Linux、Windows 和 Mac 平臺上運行。
注:您不必導入 ‘@tensorflow/tfjs’ 或者將其添加到您的 package.json 中。它由 Node 庫間接導入。
TensorFlow GPU
TensorFlow GPU 軟件包可以按如下方式導入:
import * as tf from '@tensorflow/tfjs-node-gpu'
與 CPU 軟件包一樣,您導入的模塊將由 TensorFlow C 二進制文件加速,但是它將在支持 CUDA 的 GPU 上運行張量運算,因此只能在 Linux 平臺上運行。此綁定比其他綁定選項至少快一個數量級。
注:此軟件包目前僅適用于 CUDA。在選擇本方案之前,您需要在帶有 NVIDIA 顯卡的的計算機上安裝 CUDA。
注:您不必導入 ‘@tensorflow/tfjs’ 或者將其添加到您的 package.json 中。它由 Node 庫間接導入。
普通 CPU
使用普通 CPU 運算運行的 TensorFlow.js 版本可以按如下方式導入:
import * as tf from '@tensorflow/tfjs'
此軟件包與您在瀏覽器中使用的軟件包相同。在此軟件包中,運算在 CPU 上以原生 JavaScript 運行。此軟件包比其他軟件包小得多,因為它不需要 TensorFlow 二進制文件,但是速度要慢得多。
由于此軟件包不依賴于 TensorFlow,因此它可用于支持 Node.js 的更多設備,而不僅僅是 Linux、Windows 和 Mac 平臺。
生產考量因素
Node.js 綁定為 TensorFlow.js 提供了一個同步執行運算的后端。這意味著當您調用一個運算(例如 tf.matMul(a, b)
)時,它將阻塞主線程,直到運算完成。
因此,綁定當前非常適合腳本和離線任務。如果您要在正式應用(例如網絡服務器)中使用 Node.js 綁定,應設置一個作業隊列或設置一些工作進程線程,以便您的 TensorFlow.js 代碼不會阻塞主線程。
API
一旦您在上面的任何選項中將軟件包作為 tf 導入,所有普通的 TensorFlow.js 符號都將出現在導入的模塊上。
tf.browser
在普通的 TensorFlow.js 軟件包中,tf.browser.*
命名空間中的符號將在 Node.js 中不可用,因為它們使用瀏覽器特定的 API。
目前,存在以下 API:
- tf.browser.fromPixels
- tf.browser.toPixels
tf.node
兩個 Node.js 軟件包還提供了一個名為 tf.node
的命名空間,其中包含 Node 特定的 API。
TensorBoard 是一個值得注意的 Node.js 特定的 API 示例。
在 Node.js 中將摘要導出到 TensorBoard 的示例:
const model = tf.sequential();
model.add(tf.layers.dense({ units: 1, inputShape: [200] }));
model.compile({loss: 'meanSquaredError',optimizer: 'sgd',metrics: ['MAE']
});// Generate some random fake data for demo purpose.
const xs = tf.randomUniform([10000, 200]);
const ys = tf.randomUniform([10000, 1]);
const valXs = tf.randomUniform([1000, 200]);
const valYs = tf.randomUniform([1000, 1]);// Start model training process.
async function train() {await model.fit(xs, ys, {epochs: 100,validationData: [valXs, valYs],// Add the tensorBoard callback here.callbacks: tf.node.tensorBoard('/tmp/fit_logs_1')});
}
train();