前几天Google发布了Android 9,给广大开发者带来许多新特性,最令人振奋的是Android Neural Network API。
神经网络结合TensorFlow,让Android移动设备更加智能,拥抱人工智能时代!
Android神经网络API,基于Android C开发,在移动设备运行密集型计算的机器学习。NNAPI为高级的机器学习
框架的构建与训练神经网络(比如TensorFlow Lite,Caffe2或者其他框架),提供基础功能层而设计。API可运行于
Android 8.1(API level 27)或者更高的所有设备。
NNAPI支持将Android设备的数据应用于预先训练的、开发者定义的模型推断。推断案例包括:图片分类、用户
行为预测、为搜索查询选择合适结果。
在线推断有许多优势:
延时:你不必要通过网络连接来发送请求,等待应答。这个对于视频应用:处理来自摄像头的连续帧,有着重要意义。
有效:应用在断网情况下也可以运行。
速度:新硬件针对神经网络的处理提供比独立CPU更快的计算能力。
私密:数据不会存储在设备中。
成本:在设备上执行的任意计算,不需要服务器。
有些权衡,开发者需要牢记:
系统利用:运行神经网络需要大量运算,会增加电量消耗。如果这是你APP一个关注点,你应该监测电池健康状态,尤其
是长期运行的运算。
应用大小:注意你模型的大小。模型可能会占用数MB内存空间。如果在你的APK中捆绑过大的模型,可能会带来不良用户
体验,你可以考虑在APP安装后再下载模型,或者使用更小的模型,或者在云中进行运算。NNAPI不提供在云中运行模型的
能力。
理解神经网络的运行期
NNAPI会被机器学习库、框架和工具调用,让开发人员在设备上训练他们的模型,并部署到设备中。应用程序通常不会直接
调用NNAPI,而是直接调用高级的机器学习框架。这些框架在支持的设备上,调用NNAPI来执行硬件加速的推测操作。
基于应用程序的需求与设备的硬件能力,Android神经网络运行时,可以有效地利用处理器分配计算工作量,包括专用的
神经网络硬件、图像处理单元(GPUs)与数字信号处理器(DSPs)。
由于设备缺少特定制造商的驱动,NNAPI需要依赖优化代码块来执行CPU的请求。
图1展示的是NNAPI的高级系统结构:
神经网络API的编程模型
使用NNAPI执行运算,你首先需要构造一个有向图来定义要执行的运算。这个运算图,结合你的输入数据(例如,
从机器学习传递下来的权重与偏差),构成NNAPI运行时的评估模型。
NNAPI使用四个抽象概念
模型:数学运算图与训练过程学习的常量值。这些运算针对神经网络。它们包括2维卷积、逻辑激活、线性校正
激活等。创建模型是一个同步操作,一旦成功创建,它可以在线程与编译期间复用。在NNAPI中,模型代表一个
编译:代表把NNAPI模型编译成低级代码的配置。创建编译是一个同步过程,一旦成功创建,它可以在线程与执行
期间复用。在NNAPI中,每个编译代表一个ANeuralNetworksCompilation的实例。
内存:代表共享内存,内存映射文件与相似的内存缓存。使用内存缓存可以更加高效地把运行数据传输到驱动层。
一个应用程序通常会创建一个共享内存缓存,包含每个tensor定义的模型。你也可以使用内存缓存来存储执行实例的
输入与输出。在NNAPI,每个内存缓存代表一个ANeuralNetworksMemory的实例。
执行:用于NNAPI输入与收集结果的接口。执行是个异步操作,多线程可以在相同执行中等待。当执行完成,所有
线程都被释放。在NNAPI中,每个执行代表一个ANeuralNetworksExecution的实例。
图2展示的是基本编程流:
提供训练数据的访问
你训练的权重和偏差数据可能存储于文件中。为了提供NNAPI运行时能够访问数据,通过调用ANeuralNetworksMemory_createFromFd()方法来
创建一个ANeuralNetworksMemory实例,传递已打开的数据文件的文件描述符。
你也可以指定内存保护标志和文件中共享内存区域的偏移值。
// 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);
虽然在这个例子中,我们所有权重只创建一个ANeuralNetworksMemory实例,但是可能在多个文件中使用多个实例。
模型
模型是NNAPI的基础运算单元。每个模型右一个或多个操作数/操作定义。
操作数
操作数是图中定义的数据对象。这些包括输入输出模型,中间节点包括数据从一个操作到另一个操作的流向,常量也由这些操作传递。
有两类操作数可以添加到NNAPI中模型:标量和张量。
一个标量代表一个单一的数字,NNAPI提供的标量类型包括:32位浮点数、32位整型数和32位无符号整型数格式。
NNAPI的大多数操作包含张量。张量是一个n维数组。NNAPI支持的张量类型:32位整型数、32位浮点型和8位量化数。
例如,图3代表有两个操作的模型:先乘后加。模型输入一个张量,输出一个标量。
上述模型有7个操作数。这些操作数由添加到模型的索引顺序作标识。第一个操作索引是0,第二个是1,依此类推。
你添加操作数的顺序没影响。例如,模型输出的操作数可以是第一个添加的。关键是使用操作数对应的索引。
操作数有类型,由添加到模型时指定。一个操作数不能同时用于输入与输出。
操作
一个操作指定运算的执行。每个操作由以下元素组成:
·操作类型(例如:加法、乘法、卷积)
·操作时使用的输入操作数索引列表
·操作时使用的输出操作数索引列表
列表的顺序会影响到操作执行。请看NNAPI API reference关于每个操作期望的输入输出。
你必须在添加操作之前,添加操作产生或消费的操作数到模型里。
你添加操作的顺序没影响的。NNAPI取决于由操作数与操作的运算图创建的依赖,并决定操作执行的顺序。
NNAPI所支持的操作如下表所概述:
Category | Operations |
---|---|
Element-wise mathematical operations | |
Array operations |
|
Image operations | |
Lookup operations | |
Normalization operations | |
Convolution operations | |
Pooling operations | |
Activation operations | |
Other operations |
构建模型
构建一个模型,需要如下步骤:
1、调用ANeuralNetworksModel_create()方法来定义一个空模型。如下例子,我们创建具有两个操作的模型。
ANeuralNetworksModel* model = NULL;
ANeuralNetworksModel_create(&model);
2、通过调用ANeuralNetworks_addOperand()方法把操作数添加到你的模型里。它们的数据类型使用
ANeuralNetworksOperandType的数据结构来定义。
// 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、由于操作数有常量,例如权重和偏差,你的应用程序应该包含训练过程,使用ANeuralNetworksModel_setOperandValue()与
ANeuralNetworksModel_setOperandValueFromMemory()
方法。
在下面例子,我们为我们创建的内存缓存从训练数据文件中设置常量。
// 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、为每个你想计算的有向图操作,调用ANeuralNetworksModel_addOperation()方法把操作
添加到你的模型。
调用这个方法,你的应用程序需要提供如下参数:
·操作类型;
·输入参数的个数;
·输入操作数的索引数组;
·输出操作数的个数;
·输出操作数的索引数组;
注意操作数不能在同一个操作中同时使用输入和输出。
// 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、调用ANeuralNetworksModel_identifyInputsAndOutputs()方法来识别模型中输入输出的操作数。在步骤4之前,这个方法让你配置模型使用到的输入输出操作数子集。
// 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、可选地,通过调用ANeuralNetworksModel_relaxComputationFloat32toFloat16()
方法,
指定ANEURALNETWORKS_TENSOR_FLOAT32
是否可以以IEEE 754 16位浮点格式的范围或精度来计算。
7、调用ANeuralNetworksModel_finish()方法来释放你模型中定义的变量。如果没有错误,会返回结果码ANEURALNETWORKS_NO_ERROR
。
ANeuralNetworksModel_finish(model);
编译
编译决定你的模型在哪个处理器执行,并要求驱动程序为其执行做准备。
编译一个模型,需要以下步骤:
1、调用ANeuralNetworksCompilation_create()方法来创建一个编译实例。
// Compile the model.
ANeuralNetworksCompilation* compilation;
ANeuralNetworksCompilation_create(model, &compilation);
2、你可以选择地影响运行时间如何在电池电量使用与执行速度之间进行权衡。你也可以调用ANeuralNetworksCompilation_setPreference()方法来执行。
// Ask to optimize for low power consumption.
ANeuralNetworksCompilation_setPreference(compilation, ANEURALNETWORKS_PREFER_LOW_POWER);
3、调用ANeuralNetworksCompilation_finish()方法来释放编译器的定义。如果没有错误,会返回结果码
ANEURALNETWORKS_NO_ERROR
。
ANeuralNetworksCompilation_finish(compilation);
执行
执行步骤将模型应用于输入集,并将计算输出存储到应用程序分配的一个或多个用户存储区。
执行一个编译模型,需要以下步骤:
1、调用ANeuralNetworksExecution_create()方法来创建一个实例。
// Run the compiled model against a set of inputs.
ANeuralNetworksExecution* run1 = NULL;
ANeuralNetworksExecution_create(compilation, &run1);
2、指定在哪儿存储计算所需的读入值,你的应用程序可以从用户缓冲区或者分配的存储空间读取,调用
ANeuralNetworksExecution_setInput()
或者ANeuralNetworksExecution_setInputFromMemory()方法。
// Set the single input to our sample model.
float32 myInput[3][4] = { ..the data.. };
ANeuralNetworksExecution_setInput(run1, 0, NULL, myInput, sizeof(myInput));
3、指定在哪儿存储你应用程序写出值,你的应用程序可以向用户缓冲区或者分配的存储空间写入,调用
ANeuralNetworksExecution_setOutput()
或者ANeuralNetworksExecution_setOutputFromMemory()方法。
// Set the output.
float32 myOutput[3][4];
ANeuralNetworksExecution_setOutput(run1, 0, NULL, myOutput, sizeof(myOutput));
4、调用ANeuralNetworksExecution_startCompute()方法来启动执行。如果没有错误,会返回结果码
ANEURALNETWORKS_NO_ERROR
// Starts the work. The work proceeds asynchronously.
ANeuralNetworksEvent* run1_end = NULL;
ANeuralNetworksExecution_startCompute(run1, &run1_end);
5、调用ANeuralNetworksEvent_wait()
方法来等待执行完成。如果执行成功,将返回结果码ANEURALNETWORKS_NO_ERROR。可以在其他线程等待执行。
// 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、可选,你可以通过同一编译实例来创建一个新的ANeuralNetworksExecution实例,将不同输入集应用到编译模型中。
// 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);
清除
清除步骤是释放用于计算的内部资源。
// Cleanup
ANeuralNetworksCompilation_free(compilation);
ANeuralNetworksModel_free(model);
ANeuralNetworksMemory_free(mem1);
参看Google提供的API文档:https://developer.android.google.cn/ndk/guides/neuralnetworks/