Neural Networks API
NNAPI是一個(gè) Android C API,專門為在移動(dòng)設(shè)備上對(duì)機(jī)器學(xué)習(xí)運(yùn)行計(jì)算密集型運(yùn)算而設(shè)計(jì)。 NNAPI 旨在為構(gòu)建和訓(xùn)練神經(jīng)網(wǎng)絡(luò)的更高級(jí)機(jī)器學(xué)習(xí)框架(例如 (TensorFlow Lite、Caffe2 或其他)提供一個(gè)基礎(chǔ)的功能層。 API 適用于運(yùn)行 Android 8.1(API 級(jí)別 27)或更高版本的所有設(shè)備
NNAPI 支持通過以下方式進(jìn)行推理:將 Android 設(shè)備中的數(shù)據(jù)應(yīng)用到先前訓(xùn)練的開發(fā)者定義模型。 推理的示例包括分類圖像、預(yù)測(cè)用戶行為以及選擇對(duì)搜索查詢的適當(dāng)響應(yīng)。
設(shè)備上推理具有許多優(yōu)勢(shì):
- 延遲時(shí)間:不需要通過網(wǎng)絡(luò)連接發(fā)送請(qǐng)求并等待響應(yīng)。 這對(duì)處理從攝像頭傳入的連續(xù)幀的視頻應(yīng)用至關(guān)重要。
- 可用性:應(yīng)用甚至可以在沒有網(wǎng)絡(luò)覆蓋的條件下運(yùn)行。
- 速度:與單純的通用 CPU 相比,特定于神經(jīng)網(wǎng)絡(luò)處理的新硬件可以提供顯著加快的計(jì)算速度。
- 隱私:數(shù)據(jù)不會(huì)離開設(shè)備。
- 費(fèi)用:所有計(jì)算都在設(shè)備上執(zhí)行,不需要服務(wù)器場(chǎng)。 還存在一些開發(fā)者應(yīng)考慮的利弊:
- 系統(tǒng)利用率:評(píng)估神經(jīng)網(wǎng)絡(luò)涉及許多計(jì)算,這會(huì)增加電池消耗。 如果應(yīng)用需要注意耗電量,應(yīng)當(dāng)考慮監(jiān)視電池運(yùn)行狀況,尤其是針對(duì)長時(shí)間運(yùn)行的計(jì)算進(jìn)行監(jiān)視。
- 應(yīng)用大小:注意模型的大小。 模型可能會(huì)占用很多兆字節(jié)的空間。 如果在APK中綁定較大的模型會(huì)過度地影響用戶,則您需要考慮在應(yīng)用安裝后下載模型、使用較小的模型或在云中運(yùn)行您的計(jì)算。 NNAPI 未提供在云中運(yùn)行模型的功能。
Neural Networks API 運(yùn)行時(shí)
NNAPI 將通過機(jī)器學(xué)習(xí)庫、框架和工具調(diào)用,這些工具可以讓開發(fā)者脫離設(shè)備訓(xùn)練他們的模型并將其部署在 Android 設(shè)備上。 應(yīng)用一般不會(huì)直接使用 NNAPI,但會(huì)直接使用更高級(jí)的機(jī)器學(xué)習(xí)框架。 這些框架反過來可以使用 NNAPI 在受支持的設(shè)備上執(zhí)行硬件加速的推理運(yùn)算。
根據(jù)應(yīng)用的要求和設(shè)備上的硬件能力,Android 的神經(jīng)網(wǎng)絡(luò)運(yùn)行時(shí)可以在可用的設(shè)備上處理器(包括專用的神經(jīng)網(wǎng)絡(luò)硬件、圖形處理單元 (GPU) 和數(shù)字信號(hào)處理器 (DSP))之間有效地分配計(jì)算工作負(fù)載。
對(duì)于缺少專用的供應(yīng)商驅(qū)動(dòng)程序的設(shè)備,NNAPI 運(yùn)行時(shí)將依賴優(yōu)化的代碼在 CPU 上執(zhí)行請(qǐng)求。
NNAPI編程模型
要使用 NNAPI 執(zhí)行計(jì)算,首先需要構(gòu)建一個(gè)可以定義要執(zhí)行的計(jì)算的有向圖。 此計(jì)算圖與您的輸入數(shù)據(jù)(例如,從機(jī)器學(xué)習(xí)框架傳遞過來的權(quán)重和偏差)相結(jié)合,構(gòu)成 NNAPI 運(yùn)行時(shí)評(píng)估的模型。
NNAPI 使用四種主要抽象:
-
模型:數(shù)學(xué)運(yùn)算和通過訓(xùn)練過程學(xué)習(xí)的常量值的計(jì)算圖。 這些運(yùn)算特定于神經(jīng)網(wǎng)絡(luò), 并且包括二維 (2D) 卷積、邏輯 (sigmoid)) 激活和整流線性 (ReLU) 激活等。 創(chuàng)建模型是一個(gè)同步操作,但是一旦成功創(chuàng)建,就可以在線程和編譯之間重用模型。 在 NNAPI 中,一個(gè)模型表示為一個(gè)
ANeuralNetworksModel
實(shí)例。 -
編譯:表示用于將 NNAPI 模型編譯到更低級(jí)別代碼中的配置。 創(chuàng)建編譯是一個(gè)同步操作,但是一旦成功創(chuàng)建,就可以在線程和執(zhí)行之間重用編譯。 在 NNAPI 中,每個(gè)編譯表示為一個(gè)
ANeuralNetworksCompilation
實(shí)例。 -
內(nèi)存:表示共享內(nèi)存、內(nèi)存映射文件和類似的內(nèi)存緩沖區(qū)。 使用內(nèi)存緩沖區(qū)可以讓 NNAPI 運(yùn)行時(shí)將數(shù)據(jù)更高效地傳輸?shù)津?qū)動(dòng)程序。 一個(gè)應(yīng)用一般會(huì)創(chuàng)建一個(gè)共享內(nèi)存緩沖區(qū),其中包含定義模型所需的每一個(gè)張量。 還可以使用內(nèi)存緩沖區(qū)存儲(chǔ)執(zhí)行實(shí)例的輸入和輸出。 在 NNAPI 中,每個(gè)內(nèi)存緩沖區(qū)表示為一個(gè)
ANeuralNetworksMemory
實(shí)例。 -
執(zhí)行:用于將 NNAPI 模型應(yīng)用到一組輸入并采集結(jié)果的接口。 執(zhí)行是一種異步操作。 多個(gè)線程可以在相同的執(zhí)行上等待。 當(dāng)執(zhí)行完成時(shí),所有的線程都將釋放。 在 NNAPI 中,每一個(gè)執(zhí)行表示為一個(gè)
ANeuralNetworksExecution
實(shí)例。
提供訓(xùn)練數(shù)據(jù)訪問權(quán)限
訓(xùn)練權(quán)重和偏差數(shù)據(jù)可能存儲(chǔ)在一個(gè)文件中。 要讓 NNAPI 運(yùn)行時(shí)有效地獲取此數(shù)據(jù),請(qǐng)調(diào)用 ANeuralNetworksMemory_createFromFd()
函數(shù)并傳入已打開數(shù)據(jù)文件的文件描述符,創(chuàng)建一個(gè) ANeuralNetworksMemory
實(shí)例。
也可以在共享內(nèi)存區(qū)域于文件中開始的位置指定內(nèi)存保護(hù)標(biāo)志和偏移。
// Create a memory buffer from the file that contains the trained data.
ANeuralNetworksMemory* mem1 = NULL;
int fd = open("training_data", O_RDONLY);
ANeuralNetworksMemory_createFromFd(file_size, PROT_READ, fd, 0, &mem1);
盡管在此示例中我們僅為所有權(quán)重使用了一個(gè) ANeuralNetworksMemory
實(shí)例,但是可以為多個(gè)文件使用一個(gè)以上的 ANeuralNetworksMemory
實(shí)例
模型
模型是 NNAPI 中的基本計(jì)算單位。 每個(gè)模型都由一個(gè)或多個(gè)操作數(shù)和運(yùn)算定義。
操作數(shù)
操作數(shù)是定義計(jì)算圖時(shí)使用的數(shù)據(jù)對(duì)象。 其中包括模型的輸入和輸出、包含從一個(gè)運(yùn)算流向另一個(gè)運(yùn)算的數(shù)據(jù)的中間節(jié)點(diǎn),以及傳遞到這些運(yùn)算的常量。
可以向 NNAPI 模型中添加兩種類型的操作數(shù):標(biāo)量和張量。
標(biāo)量表示一個(gè)數(shù)字。 NNAPI 支持 32 位浮點(diǎn)、32 位整數(shù)和無符號(hào) 32 位整數(shù)格式的標(biāo)量值。
NNAPI 的大多數(shù)運(yùn)算都涉及張量。 張量是 N 維數(shù)組。 NNAPI 支持具有 32 位整數(shù)、32 位浮點(diǎn)和 8 位量化值的張量。
上面的模型有七個(gè)操作數(shù)。 這些操作數(shù)按照它們添加到模型中的順序索引顯式標(biāo)識(shí)。 添加的第一個(gè)操作數(shù)的索引為 0,第二個(gè)操作數(shù)的索引為 1,依此類推。
添加操作數(shù)的順序不重要。 例如,模型輸出操作數(shù)可以是添加的第一個(gè)操作數(shù)。 重要的部分是在引用操作數(shù)時(shí)使用正確的索引值。
操作數(shù)具有類型。 這些類型在添加到模型中時(shí)指定。 一個(gè)操作數(shù)無法同時(shí)用作模型的輸入和輸出
運(yùn)算
運(yùn)算指定要執(zhí)行的計(jì)算。 每個(gè)運(yùn)算都包含下面這些元素:
- 運(yùn)算類型(例如,加法、乘法、卷積),
- 運(yùn)算用于輸入的操作數(shù)索引列表,以及
- 運(yùn)算用于輸出的操作數(shù)索引列表。
操作數(shù)在這些列表中的順序非常重要;請(qǐng)針對(duì)每個(gè)運(yùn)算查閱 NNAPI API 參考,了解預(yù)期輸入和輸出。
在添加運(yùn)算之前,必須先將運(yùn)算消耗或生成的操作數(shù)添加到模型中。
添加運(yùn)算的順序不重要。 NNAPI 依賴操作數(shù)和運(yùn)算的計(jì)算圖建立的依賴關(guān)系來確定運(yùn)算的執(zhí)行順序。
下表匯總了 NNAPI 支持的運(yùn)算:
已知問題: 將
ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
張量傳遞到ANEURALNETWORKS_PAD
運(yùn)算(在 Android 9(API 級(jí)別 28)及更高版本中提供)時(shí),NNAPI 的輸出可能與較高級(jí)別機(jī)器學(xué)習(xí)框架(如 TensorFlow Lite)的輸出不匹配。 應(yīng)只傳遞ANEURALNETWORKS_TENSOR_FLOAT32
直到問題得到解決。
構(gòu)建模型
1 .調(diào)用 ANeuralNetworksModel_create()
函數(shù)來定義一個(gè)空模型。
ANeuralNetworksModel* model = NULL;
ANeuralNetworksModel_create(&model);
2 . 調(diào)用 ANeuralNetworks_addOperand()
,將操作數(shù)添加到您的模型中。 它們的數(shù)據(jù)類型使用 ANeuralNetworksOperandType
數(shù)據(jù)結(jié)構(gòu)定義。
// In our example, all our tensors are matrices of dimension [3][4].
ANeuralNetworksOperandType tensor3x4Type;
tensor3x4Type.type = ANEURALNETWORKS_TENSOR_FLOAT32;
tensor3x4Type.scale = 0.f; // These fields are useful for quantized tensors.
tensor3x4Type.zeroPoint = 0; // These fields are useful for quantized tensors.
tensor3x4Type.dimensionCount = 2;
uint32_t dims[2] = {3, 4};
tensor3x4Type.dimensions = dims;
// We also specify operands that are activation function specifiers.
ANeuralNetworksOperandType activationType;
activationType.type = ANEURALNETWORKS_INT32;
activationType.scale = 0.f;
activationType.zeroPoint = 0;
activationType.dimensionCount = 0;
activationType.dimensions = NULL;
// Now we add the seven operands, in the same order defined in the diagram.
ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 0
ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 1
ANeuralNetworksModel_addOperand(model, &activationType); // operand 2
ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 3
ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 4
ANeuralNetworksModel_addOperand(model, &activationType); // operand 5
ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 6
3 . 對(duì)于具有常量值的操作數(shù),例如應(yīng)用從訓(xùn)練過程獲取的權(quán)重和偏差,請(qǐng)使用 ANeuralNetworksModel_setOperandValue()
和 ANeuralNetworksModel_setOperandValueFromMemory()
函數(shù)。
// In our example, operands 1 and 3 are constant tensors whose value was
// established during the training process.
const int sizeOfTensor = 3 * 4 * 4; // The formula for size calculation is dim0 * dim1 * elementSize.
ANeuralNetworksModel_setOperandValueFromMemory(model, 1, mem1, 0, sizeOfTensor);
ANeuralNetworksModel_setOperandValueFromMemory(model, 3, mem1, sizeOfTensor, sizeOfTensor);
// We set the values of the activation operands, in our example operands 2 and 5.
int32_t noneValue = ANEURALNETWORKS_FUSED_NONE;
ANeuralNetworksModel_setOperandValue(model, 2, &noneValue, sizeof(noneValue));
ANeuralNetworksModel_setOperandValue(model, 5, &noneValue, sizeof(noneValue));
4 .對(duì)于有向圖中您想要計(jì)算的每個(gè)運(yùn)算,請(qǐng)調(diào)用 ANeuralNetworksModel_addOperation()
函數(shù),將運(yùn)算添加到您的模型中。
應(yīng)用必須以此調(diào)用的參數(shù)形式提供以下各項(xiàng):
- 運(yùn)算類型,
- 輸入值計(jì)數(shù),
- 輸入操作數(shù)索引的數(shù)組,
- 輸出值計(jì)數(shù),以及
- 輸出操作數(shù)索引的數(shù)組。
請(qǐng)注意,一個(gè)操作數(shù)無法同時(shí)用作同一個(gè)運(yùn)算的輸入和輸出
// We have two operations in our example.
// The first consumes operands 1, 0, 2, and produces operand 4.
uint32_t addInputIndexes[3] = {1, 0, 2};
uint32_t addOutputIndexes[1] = {4};
ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_ADD, 3, addInputIndexes, 1, addOutputIndexes);
// The second consumes operands 3, 4, 5, and produces operand 6.
uint32_t multInputIndexes[3] = {3, 4, 5};
uint32_t multOutputIndexes[1] = {6};
ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_MUL, 3, multInputIndexes, 1, multOutputIndexes);
5 .調(diào)用 ANeuralNetworksModel_identifyInputsAndOutputs()
函數(shù),確定模型應(yīng)將哪些操作數(shù)視為其輸入和輸出。 此函數(shù)可以將模型配置為使用上面的第 4 步中指定的輸入和輸出操作數(shù)子集
// Our model has one input (0) and one output (6).
uint32_t modelInputIndexes[1] = {0};
uint32_t modelOutputIndexes[1] = {6};
ANeuralNetworksModel_identifyInputsAndOutputs(model, 1, modelInputIndexes, 1 modelOutputIndexes);
6 . 可選)通過調(diào)用 ANeuralNetworksModel_relaxComputationFloat32toFloat16()
,指定是否允許 ANEURALNETWORKS_TENSOR_FLOAT32
使用低至 IEEE 754 16 位浮點(diǎn)格式的范圍或精度計(jì)算。
7 .調(diào)用 ANeuralNetworksModel_finish()
來最終確定模型的定義。 如果沒有錯(cuò)誤,此函數(shù)將返回 ANEURALNETWORKS_NO_ERROR
的結(jié)果代碼。
ANeuralNetworksModel_finish(model);
編譯
編譯步驟確定模型將在哪些處理器上執(zhí)行,并要求對(duì)應(yīng)的驅(qū)動(dòng)程序準(zhǔn)備其執(zhí)行。 這可能包括生成機(jī)器代碼,此代碼特定于模型將在其上面運(yùn)行的處理器。
要編譯模型,請(qǐng)按以下步驟操作:
- 調(diào)用
ANeuralNetworksCompilation_create()
函數(shù)來創(chuàng)建一個(gè)新的編譯實(shí)例。
// Compile the model.
ANeuralNetworksCompilation* compilation;
ANeuralNetworksCompilation_create(model, &compilation);
2.可以選擇性地影響運(yùn)行時(shí)如何在電池消耗與執(zhí)行速度之間權(quán)衡。 為此,可以調(diào)用 ANeuralNetworksCompilation_setPreference()
。
// Ask to optimize for low power consumption.
ANeuralNetworksCompilation_setPreference(compilation, ANEURALNETWORKS_PREFER_LOW_POWER);
可以指定的有效首選項(xiàng)包括:
* [`ANEURALNETWORKS_PREFER_LOW_POWER`](https://developer.android.com/ndk/reference/group/neural-networks#group___neural_networks_1gga034380829226e2d980b2a7e63c992f18a370c42db64448662ad79116556bcec01): 傾向于以最大程度減小電池消耗的方式執(zhí)行。 此首選項(xiàng)適合將要經(jīng)常執(zhí)行的編譯。
* [`ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER`](https://developer.android.com/ndk/reference/group/neural-networks#group___neural_networks_1gga034380829226e2d980b2a7e63c992f18af7fff807061a3e9358364a502691d887): 傾向于盡快返回單個(gè)回答,即使這會(huì)導(dǎo)致耗電量增加。
* [`ANEURALNETWORKS_PREFER_SUSTAINED_SPEED`](https://developer.android.com/ndk/reference/group/neural-networks#group___neural_networks_1gga034380829226e2d980b2a7e63c992f18af727c25f1e2d8dcc693c477aef4ea5f5): 傾向于最大化連續(xù)幀的吞吐量,例如,在處理來自攝像頭的連續(xù)幀時(shí)。
- 調(diào)用
ANeuralNetworksCompilation_finish()
,最終確定編譯定義。 如果沒有錯(cuò)誤,此函數(shù)將返回ANEURALNETWORKS_NO_ERROR
的結(jié)果代碼。
ANeuralNetworksCompilation_finish(compilation);
執(zhí)行
執(zhí)行步驟會(huì)將模型應(yīng)用到一組輸入,并將計(jì)算輸出存儲(chǔ)到一個(gè)或多個(gè)用戶緩沖區(qū)或者應(yīng)用分配的內(nèi)存空間中。
要執(zhí)行編譯的模型,請(qǐng)按以下步驟操作:
- 調(diào)用
ANeuralNetworksExecution_create()
函數(shù)來創(chuàng)建一個(gè)新的執(zhí)行實(shí)例。
// Run the compiled model against a set of inputs.
ANeuralNetworksExecution* run1 = NULL;
ANeuralNetworksExecution_create(compilation, &run1);
2 指定應(yīng)用為計(jì)算讀取輸入值的位置。 通過分別調(diào)用 ANeuralNetworksExecution_setInput()
或 ANeuralNetworksExecution_setInputFromMemory()
,應(yīng)用可以從用戶緩沖區(qū)或分配的內(nèi)存空間讀取輸入值。
// Set the single input to our sample model. Since it is small, we won’t use a memory buffer.
float32 myInput[3][4] = { ..the data.. };
ANeuralNetworksExecution_setInput(run1, 0, NULL, myInput, sizeof(myInput));
3.指定應(yīng)用寫入輸出值的位置。 通過分別調(diào)用 ANeuralNetworksExecution_setOutput()
或 ANeuralNetworksExecution_setOutputFromMemory()
,應(yīng)用可以將輸出值分別寫入用戶緩沖區(qū)或分配的內(nèi)存空間。
// Set the output.
float32 myOutput[3][4];
ANeuralNetworksExecution_setOutput(run1, 0, NULL, myOutput, sizeof(myOutput));
- 調(diào)用
ANeuralNetworksExecution_startCompute()
函數(shù),計(jì)劃要開始的執(zhí)行。 如果沒有錯(cuò)誤,此函數(shù)將返回ANEURALNETWORKS_NO_ERROR
的結(jié)果代碼。
// Starts the work. The work proceeds asynchronously.
ANeuralNetworksEvent* run1_end = NULL;
ANeuralNetworksExecution_startCompute(run1, &run1_end);
- 調(diào)用
ANeuralNetworksEvent_wait()
函數(shù)以等待執(zhí)行完成。 如果執(zhí)行成功,此函數(shù)將返回ANEURALNETWORKS_NO_ERROR
的結(jié)果代碼。 等待可以在不同于開始執(zhí)行的線程上完成。
// For our example, we have no other work to do and will just wait for the completion.
ANeuralNetworksEvent_wait(run1_end);
ANeuralNetworksEvent_free(run1_end);
ANeuralNetworksExecution_free(run1);
6.或者,也可以使用同一個(gè)編譯實(shí)例來創(chuàng)建一個(gè)新的 ANeuralNetworksExecution
實(shí)例,將一組不同的輸入應(yīng)用到編譯的模型。
// Apply the compiled model to a different set of inputs.
ANeuralNetworksExecution* run2;
ANeuralNetworksExecution_create(compilation, &run2);
ANeuralNetworksExecution_setInput(run2, ...);
ANeuralNetworksExecution_setOutput(run2, ...);
ANeuralNetworksEvent* run2_end = NULL;
ANeuralNetworksExecution_startCompute(run2, &run2_end);
ANeuralNetworksEvent_wait(run2_end);
ANeuralNetworksEvent_free(run2_end);
ANeuralNetworksExecution_free(run2);
清理
清理步驟可以處理計(jì)算所用內(nèi)部資源的釋放。
// Cleanup
ANeuralNetworksCompilation_free(compilation);
ANeuralNetworksModel_free(model);
ANeuralNetworksMemory_free(mem1);
操作數(shù)的更多主題
下面一部分介紹了有關(guān)使用操作數(shù)的高級(jí)主題。
量化張量
量化張量是一種表示 N 維浮點(diǎn)值數(shù)組的緊湊型方式。
NNAPI 支持 8 位非對(duì)稱量化張量。 對(duì)于這些張量,每個(gè)單元格的值都通過一個(gè) 8 位整數(shù)表示。 與張量關(guān)聯(lián)的是一個(gè)比例和一個(gè)零點(diǎn)值。 這些用于將 8 位整數(shù)轉(zhuǎn)換成要表示的浮點(diǎn)值。
公式為:
(cellValue - zeroPoint) * scale
其中,zeroPoint 值是一個(gè) 32 位整數(shù),scale 是一個(gè) 32 位浮點(diǎn)值。
與 32 位浮點(diǎn)值的張量相比,8 位量化張量具有兩個(gè)優(yōu)勢(shì):
- 應(yīng)用將更小,因?yàn)橛?xùn)練的權(quán)重占 32 位張量大小的四分之一。
- 計(jì)算通常可以更快地執(zhí)行。 這是因?yàn)閮H需要從內(nèi)存提取少量數(shù)據(jù),并且 DSP 等處理器進(jìn)行整數(shù)數(shù)學(xué)運(yùn)算的效率更高。
盡管可以將浮點(diǎn)值模型轉(zhuǎn)換成量化模型,但我們的經(jīng)驗(yàn)表明,直接訓(xùn)練量化模型可以取得更好的結(jié)果。 事實(shí)上,神經(jīng)網(wǎng)絡(luò)會(huì)通過學(xué)習(xí)來補(bǔ)償每個(gè)值增大的粒度。 對(duì)于量化張量,scale 和 zeroPoint 值在訓(xùn)練過程中確定。
在 NNAPI 中,需要將 ANeuralNetworksOperandType
數(shù)據(jù)結(jié)構(gòu)的類型字段設(shè)置為 ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
,定義量化張量類型。 還需要在該數(shù)據(jù)結(jié)構(gòu)中指定張量的 scale 和 zeroPoint 值。
可選操作數(shù)
一些運(yùn)算(例如 ANEURALNETWORKS_LSH_PROJECTION
)會(huì)采用可選操作數(shù)。 要在模型中指示忽略可選操作數(shù),請(qǐng)調(diào)用 ANeuralNetworksModel_setOperandValue()
函數(shù),為 buffer 傳遞 NULL
,為 length 傳遞 0。
如果是否使用操作數(shù)的決定因執(zhí)行而異,應(yīng)通過以下方式指示忽略操作數(shù):使用 ANeuralNetworksExecution_setInput()
或 ANeuralNetworksExecution_setOutput()
函數(shù),同時(shí)為 buffer 傳遞 NULL
,為 length 傳遞 0。