Android 神經(jīng)網(wǎng)絡(luò)API

Neural Networks API

NNAPI是一個 Android C API,專門為在移動設(shè)備上對機器學(xué)習(xí)運行計算密集型運算而設(shè)計。 NNAPI 旨在為構(gòu)建和訓(xùn)練神經(jīng)網(wǎng)絡(luò)的更高級機器學(xué)習(xí)框架(例如 (TensorFlow Lite、Caffe2 或其他)提供一個基礎(chǔ)的功能層。 API 適用于運行 Android 8.1(API 級別 27)或更高版本的所有設(shè)備

NNAPI 支持通過以下方式進行推理:將 Android 設(shè)備中的數(shù)據(jù)應(yīng)用到先前訓(xùn)練的開發(fā)者定義模型。 推理的示例包括分類圖像、預(yù)測用戶行為以及選擇對搜索查詢的適當(dāng)響應(yīng)。

設(shè)備上推理具有許多優(yōu)勢:

  • 延遲時間:不需要通過網(wǎng)絡(luò)連接發(fā)送請求并等待響應(yīng)。 這對處理從攝像頭傳入的連續(xù)幀的視頻應(yīng)用至關(guān)重要。
  • 可用性:應(yīng)用甚至可以在沒有網(wǎng)絡(luò)覆蓋的條件下運行。
  • 速度:與單純的通用 CPU 相比,特定于神經(jīng)網(wǎng)絡(luò)處理的新硬件可以提供顯著加快的計算速度。
  • 隱私:數(shù)據(jù)不會離開設(shè)備。
  • 費用:所有計算都在設(shè)備上執(zhí)行,不需要服務(wù)器場。 還存在一些開發(fā)者應(yīng)考慮的利弊:
  • 系統(tǒng)利用率:評估神經(jīng)網(wǎng)絡(luò)涉及許多計算,這會增加電池消耗。 如果應(yīng)用需要注意耗電量,應(yīng)當(dāng)考慮監(jiān)視電池運行狀況,尤其是針對長時間運行的計算進行監(jiān)視。
  • 應(yīng)用大小:注意模型的大小。 模型可能會占用很多兆字節(jié)的空間。 如果在APK中綁定較大的模型會過度地影響用戶,則您需要考慮在應(yīng)用安裝后下載模型、使用較小的模型或在云中運行您的計算。 NNAPI 未提供在云中運行模型的功能。

Neural Networks API 運行時

NNAPI 將通過機器學(xué)習(xí)庫、框架和工具調(diào)用,這些工具可以讓開發(fā)者脫離設(shè)備訓(xùn)練他們的模型并將其部署在 Android 設(shè)備上。 應(yīng)用一般不會直接使用 NNAPI,但會直接使用更高級的機器學(xué)習(xí)框架。 這些框架反過來可以使用 NNAPI 在受支持的設(shè)備上執(zhí)行硬件加速的推理運算。

根據(jù)應(yīng)用的要求和設(shè)備上的硬件能力,Android 的神經(jīng)網(wǎng)絡(luò)運行時可以在可用的設(shè)備上處理器(包括專用的神經(jīng)網(wǎng)絡(luò)硬件、圖形處理單元 (GPU) 和數(shù)字信號處理器 (DSP))之間有效地分配計算工作負(fù)載。

對于缺少專用的供應(yīng)商驅(qū)動程序的設(shè)備,NNAPI 運行時將依賴優(yōu)化的代碼在 CPU 上執(zhí)行請求。

NNAPI 的高級系統(tǒng)架構(gòu)

NNAPI編程模型

要使用 NNAPI 執(zhí)行計算,首先需要構(gòu)建一個可以定義要執(zhí)行的計算的有向圖。 此計算圖與您的輸入數(shù)據(jù)(例如,從機器學(xué)習(xí)框架傳遞過來的權(quán)重和偏差)相結(jié)合,構(gòu)成 NNAPI 運行時評估的模型。

NNAPI 使用四種主要抽象:

  • 模型:數(shù)學(xué)運算和通過訓(xùn)練過程學(xué)習(xí)的常量值的計算圖。 這些運算特定于神經(jīng)網(wǎng)絡(luò), 并且包括二維 (2D) 卷積、邏輯 (sigmoid)) 激活和整流線性 (ReLU) 激活等。 創(chuàng)建模型是一個同步操作,但是一旦成功創(chuàng)建,就可以在線程和編譯之間重用模型。 在 NNAPI 中,一個模型表示為一個 ANeuralNetworksModel 實例。
  • 編譯:表示用于將 NNAPI 模型編譯到更低級別代碼中的配置。 創(chuàng)建編譯是一個同步操作,但是一旦成功創(chuàng)建,就可以在線程和執(zhí)行之間重用編譯。 在 NNAPI 中,每個編譯表示為一個 ANeuralNetworksCompilation 實例。
  • 內(nèi)存:表示共享內(nèi)存、內(nèi)存映射文件和類似的內(nèi)存緩沖區(qū)。 使用內(nèi)存緩沖區(qū)可以讓 NNAPI 運行時將數(shù)據(jù)更高效地傳輸?shù)津?qū)動程序。 一個應(yīng)用一般會創(chuàng)建一個共享內(nèi)存緩沖區(qū),其中包含定義模型所需的每一個張量。 還可以使用內(nèi)存緩沖區(qū)存儲執(zhí)行實例的輸入和輸出。 在 NNAPI 中,每個內(nèi)存緩沖區(qū)表示為一個 ANeuralNetworksMemory 實例。
  • 執(zhí)行:用于將 NNAPI 模型應(yīng)用到一組輸入并采集結(jié)果的接口。 執(zhí)行是一種異步操作。 多個線程可以在相同的執(zhí)行上等待。 當(dāng)執(zhí)行完成時,所有的線程都將釋放。 在 NNAPI 中,每一個執(zhí)行表示為一個 ANeuralNetworksExecution實例。
基本的編程流

提供訓(xùn)練數(shù)據(jù)訪問權(quán)限

訓(xùn)練權(quán)重和偏差數(shù)據(jù)可能存儲在一個文件中。 要讓 NNAPI 運行時有效地獲取此數(shù)據(jù),請調(diào)用 ANeuralNetworksMemory_createFromFd() 函數(shù)并傳入已打開數(shù)據(jù)文件的文件描述符,創(chuàng)建一個 ANeuralNetworksMemory 實例。

也可以在共享內(nèi)存區(qū)域于文件中開始的位置指定內(nèi)存保護標(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)重使用了一個 ANeuralNetworksMemory 實例,但是可以為多個文件使用一個以上的 ANeuralNetworksMemory 實例

模型

模型是 NNAPI 中的基本計算單位。 每個模型都由一個或多個操作數(shù)運算定義。

操作數(shù)

操作數(shù)是定義計算圖時使用的數(shù)據(jù)對象。 其中包括模型的輸入和輸出、包含從一個運算流向另一個運算的數(shù)據(jù)的中間節(jié)點,以及傳遞到這些運算的常量。

可以向 NNAPI 模型中添加兩種類型的操作數(shù):標(biāo)量張量

標(biāo)量表示一個數(shù)字。 NNAPI 支持 32 位浮點、32 位整數(shù)和無符號 32 位整數(shù)格式的標(biāo)量值。

NNAPI 的大多數(shù)運算都涉及張量。 張量是 N 維數(shù)組。 NNAPI 支持具有 32 位整數(shù)、32 位浮點和 8 位量化值的張量。

表示一個具有兩種運算的模型:先加法后乘法。 模型獲取輸入張量并生成一個輸出張量

上面的模型有七個操作數(shù)。 這些操作數(shù)按照它們添加到模型中的順序索引顯式標(biāo)識。 添加的第一個操作數(shù)的索引為 0,第二個操作數(shù)的索引為 1,依此類推。

添加操作數(shù)的順序不重要。 例如,模型輸出操作數(shù)可以是添加的第一個操作數(shù)。 重要的部分是在引用操作數(shù)時使用正確的索引值。

操作數(shù)具有類型。 這些類型在添加到模型中時指定。 一個操作數(shù)無法同時用作模型的輸入和輸出

運算

運算指定要執(zhí)行的計算。 每個運算都包含下面這些元素:

  • 運算類型(例如,加法、乘法、卷積),
  • 運算用于輸入的操作數(shù)索引列表,以及
  • 運算用于輸出的操作數(shù)索引列表。

操作數(shù)在這些列表中的順序非常重要;請針對每個運算查閱 NNAPI API 參考,了解預(yù)期輸入和輸出。

在添加運算之前,必須先將運算消耗或生成的操作數(shù)添加到模型中。

添加運算的順序不重要。 NNAPI 依賴操作數(shù)和運算的計算圖建立的依賴關(guān)系來確定運算的執(zhí)行順序。

下表匯總了 NNAPI 支持的運算:


運算

已知問題:ANEURALNETWORKS_TENSOR_QUANT8_ASYMM 張量傳遞到 ANEURALNETWORKS_PAD運算(在 Android 9(API 級別 28)及更高版本中提供)時,NNAPI 的輸出可能與較高級別機器學(xué)習(xí)框架(如 TensorFlow Lite)的輸出不匹配。 應(yīng)只傳遞 ANEURALNETWORKS_TENSOR_FLOAT32直到問題得到解決。

構(gòu)建模型

1 .調(diào)用 ANeuralNetworksModel_create() 函數(shù)來定義一個空模型。

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 . 對于具有常量值的操作數(shù),例如應(yīng)用從訓(xùn)練過程獲取的權(quán)重和偏差,請使用 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 .對于有向圖中您想要計算的每個運算,請調(diào)用 ANeuralNetworksModel_addOperation() 函數(shù),將運算添加到您的模型中。

應(yīng)用必須以此調(diào)用的參數(shù)形式提供以下各項:

  • 運算類型,
  • 輸入值計數(shù),
  • 輸入操作數(shù)索引的數(shù)組,
  • 輸出值計數(shù),以及
  • 輸出操作數(shù)索引的數(shù)組。

請注意,一個操作數(shù)無法同時用作同一個運算的輸入和輸出

// 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 位浮點格式的范圍或精度計算。

7 .調(diào)用 ANeuralNetworksModel_finish() 來最終確定模型的定義。 如果沒有錯誤,此函數(shù)將返回 ANEURALNETWORKS_NO_ERROR 的結(jié)果代碼。

ANeuralNetworksModel_finish(model);

編譯

編譯步驟確定模型將在哪些處理器上執(zhí)行,并要求對應(yīng)的驅(qū)動程序準(zhǔn)備其執(zhí)行。 這可能包括生成機器代碼,此代碼特定于模型將在其上面運行的處理器。

要編譯模型,請按以下步驟操作:

  1. 調(diào)用 ANeuralNetworksCompilation_create() 函數(shù)來創(chuàng)建一個新的編譯實例。
// Compile the model.
ANeuralNetworksCompilation* compilation;
ANeuralNetworksCompilation_create(model, &compilation);

2.可以選擇性地影響運行時如何在電池消耗與執(zhí)行速度之間權(quán)衡。 為此,可以調(diào)用 ANeuralNetworksCompilation_setPreference()。

// Ask to optimize for low power consumption.
ANeuralNetworksCompilation_setPreference(compilation, ANEURALNETWORKS_PREFER_LOW_POWER);

可以指定的有效首選項包括:

*   [`ANEURALNETWORKS_PREFER_LOW_POWER`](https://developer.android.com/ndk/reference/group/neural-networks#group___neural_networks_1gga034380829226e2d980b2a7e63c992f18a370c42db64448662ad79116556bcec01): 傾向于以最大程度減小電池消耗的方式執(zhí)行。 此首選項適合將要經(jīng)常執(zhí)行的編譯。
*   [`ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER`](https://developer.android.com/ndk/reference/group/neural-networks#group___neural_networks_1gga034380829226e2d980b2a7e63c992f18af7fff807061a3e9358364a502691d887): 傾向于盡快返回單個回答,即使這會導(dǎo)致耗電量增加。
*   [`ANEURALNETWORKS_PREFER_SUSTAINED_SPEED`](https://developer.android.com/ndk/reference/group/neural-networks#group___neural_networks_1gga034380829226e2d980b2a7e63c992f18af727c25f1e2d8dcc693c477aef4ea5f5): 傾向于最大化連續(xù)幀的吞吐量,例如,在處理來自攝像頭的連續(xù)幀時。
  1. 調(diào)用 ANeuralNetworksCompilation_finish(),最終確定編譯定義。 如果沒有錯誤,此函數(shù)將返回 ANEURALNETWORKS_NO_ERROR 的結(jié)果代碼。
ANeuralNetworksCompilation_finish(compilation);

執(zhí)行

執(zhí)行步驟會將模型應(yīng)用到一組輸入,并將計算輸出存儲到一個或多個用戶緩沖區(qū)或者應(yīng)用分配的內(nèi)存空間中。

要執(zhí)行編譯的模型,請按以下步驟操作:

  1. 調(diào)用 ANeuralNetworksExecution_create() 函數(shù)來創(chuàng)建一個新的執(zhí)行實例。
// Run the compiled model against a set of inputs.
ANeuralNetworksExecution* run1 = NULL;
ANeuralNetworksExecution_create(compilation, &run1);

2 指定應(yīng)用為計算讀取輸入值的位置。 通過分別調(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));

  1. 調(diào)用 ANeuralNetworksExecution_startCompute() 函數(shù),計劃要開始的執(zhí)行。 如果沒有錯誤,此函數(shù)將返回 ANEURALNETWORKS_NO_ERROR 的結(jié)果代碼。
// Starts the work. The work proceeds asynchronously.
ANeuralNetworksEvent* run1_end = NULL;
ANeuralNetworksExecution_startCompute(run1, &run1_end);
  1. 調(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.或者,也可以使用同一個編譯實例來創(chuàng)建一個新的 ANeuralNetworksExecution 實例,將一組不同的輸入應(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);

清理

清理步驟可以處理計算所用內(nèi)部資源的釋放。

// Cleanup
ANeuralNetworksCompilation_free(compilation);
ANeuralNetworksModel_free(model);
ANeuralNetworksMemory_free(mem1);

操作數(shù)的更多主題

下面一部分介紹了有關(guān)使用操作數(shù)的高級主題。

量化張量
量化張量是一種表示 N 維浮點值數(shù)組的緊湊型方式。

NNAPI 支持 8 位非對稱量化張量。 對于這些張量,每個單元格的值都通過一個 8 位整數(shù)表示。 與張量關(guān)聯(lián)的是一個比例和一個零點值。 這些用于將 8 位整數(shù)轉(zhuǎn)換成要表示的浮點值。

公式為:

(cellValue - zeroPoint) * scale

其中,zeroPoint 值是一個 32 位整數(shù),scale 是一個 32 位浮點值。

與 32 位浮點值的張量相比,8 位量化張量具有兩個優(yōu)勢:

  • 應(yīng)用將更小,因為訓(xùn)練的權(quán)重占 32 位張量大小的四分之一。
  • 計算通??梢愿斓貓?zhí)行。 這是因為僅需要從內(nèi)存提取少量數(shù)據(jù),并且 DSP 等處理器進行整數(shù)數(shù)學(xué)運算的效率更高。

盡管可以將浮點值模型轉(zhuǎn)換成量化模型,但我們的經(jīng)驗表明,直接訓(xùn)練量化模型可以取得更好的結(jié)果。 事實上,神經(jīng)網(wǎng)絡(luò)會通過學(xué)習(xí)來補償每個值增大的粒度。 對于量化張量,scale 和 zeroPoint 值在訓(xùn)練過程中確定。

在 NNAPI 中,需要將 ANeuralNetworksOperandType 數(shù)據(jù)結(jié)構(gòu)的類型字段設(shè)置為 ANEURALNETWORKS_TENSOR_QUANT8_ASYMM,定義量化張量類型。 還需要在該數(shù)據(jù)結(jié)構(gòu)中指定張量的 scale 和 zeroPoint 值。

可選操作數(shù)
一些運算(例如 ANEURALNETWORKS_LSH_PROJECTION)會采用可選操作數(shù)。 要在模型中指示忽略可選操作數(shù),請調(diào)用 ANeuralNetworksModel_setOperandValue() 函數(shù),為 buffer 傳遞 NULL,為 length 傳遞 0。

如果是否使用操作數(shù)的決定因執(zhí)行而異,應(yīng)通過以下方式指示忽略操作數(shù):使用 ANeuralNetworksExecution_setInput()ANeuralNetworksExecution_setOutput() 函數(shù),同時為 buffer 傳遞 NULL,為 length 傳遞 0。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,691評論 1 32
  • 一、溫故而知新 1. 內(nèi)存不夠怎么辦 內(nèi)存簡單分配策略的問題地址空間不隔離內(nèi)存使用效率低程序運行的地址不確定 關(guān)于...
    SeanCST閱讀 8,146評論 0 27
  • ORA-00001: 違反唯一約束條件 (.) 錯誤說明:當(dāng)在唯一索引所對應(yīng)的列上鍵入重復(fù)值時,會觸發(fā)此異常。 O...
    我想起個好名字閱讀 6,026評論 0 9
  • 所有知識點已整理成app app下載地址 J2EE 部分: 1.Switch能否用string做參數(shù)? 在 Jav...
    侯蛋蛋_閱讀 2,716評論 1 4
  • 女兒高考后開始搬家,從來不知道家里有如此多的東西,理解了嫂子告訴我的話,破家值萬貫的道理。 這是最后一車要拉的東西...
    悠然_3c09閱讀 505評論 1 4

友情鏈接更多精彩內(nèi)容