一、神经网络基础与Android NNAPI概述
各位同学,欢迎来到《Android神经网络API开发实战》的第一章。
说实话,每次带新人入门NNAPI,我都要先问一个问题:你了解神经网络吗? 如果答案是否定的,那直接上手Android NNAPI就像没学过加减法就去解微积分——不是不行,但你会摔得很惨。
我自己刚接触移动端AI时,也犯过这个错误。那时候急着调模型,结果连最基本的Input和Output张量形状都搞混了,调试了整整两天。嗯,从那以后我养成了一个习惯:先搞懂原理,再动手编码。
所以这一章,我们先打好基础。我会带你从神经元讲起,一直聊到Android NNAPI的整体架构。别担心,我会用最接地气的方式来讲。
1.1 神经网络基本概念
1.1.1 神经元:AI世界的最小单元
神经网络里的神经元,说白了就是模仿人脑神经细胞的一个数学模型。你想想看,人脑有大约860亿个神经元,而一个最简单的神经网络可能只有几十个。
一个神经元做的事情其实很简单:
- 接收输入:从上一层拿到多个数值
- 加权求和:每个输入乘以对应的权重,再加一个偏置
- 激活输出:通过激活函数决定是否"兴奋",输出结果
用数学公式表达就是:
output = activation( Σ(w_i * x_i) + b )
其中w_i是权重,x_i是输入,b是偏置,activation是激活函数。
1.1.2 层:神经网络的积木
多个神经元组合在一起,就形成了"层"。常见的层有:
| 层类型 | 作用 | NNAPI对应操作 |
|---|---|---|
| 全连接层(Dense) | 每个神经元连接上一层的所有输出 | ANEURALNETWORKS_FULLY_CONNECTED |
| 卷积层(Conv2D) | 提取局部特征,常用于图像 | ANEURALNETWORKS_CONV_2D |
| 池化层(Pooling) | 降采样,减少参数数量 | ANEURALNETWORKS_AVERAGE_POOL_2D |
| 归一化层(BatchNorm) | 加速训练,稳定输出 | 需拆解为多个基础操作 |
我刚开始做模型转换时,最头疼的就是BatchNorm层。因为NNAPI没有直接对应的操作,必须拆成MUL、ADD等基础操作。嗯,这里要提醒你:不是所有模型结构都能直接映射到NNAPI,提前了解支持的算子列表能省很多时间。
1.1.3 激活函数:给网络注入非线性
如果没有激活函数,神经网络就只是线性变换的堆叠——说白了,再深的网络也等价于一个单层模型。那还有什么意义呢?
常见的激活函数有:
- ReLU:
f(x) = max(0, x),简单高效,NNAPI原生支持 - Sigmoid:输出范围(0,1),适合二分类
- Tanh:输出范围(-1,1),适合RNN
- Softmax:输出概率分布,多分类标配
1.2 Android NNAPI架构与设计理念
1.2.1 NNAPI是什么?
Android NNAPI(Neural Networks API)是Google在Android 8.1(API 27)引入的一套底层C API。它的目标很明确:让开发者能在Android设备上高效运行神经网络模型。
说白了,NNAPI就是Android系统里的"AI加速器调度中心"。它不自己算,而是把计算任务派发给最合适的硬件——GPU、DSP、NPU,或者CPU。
1.2.2 架构分层
NNAPI的架构可以分为三层,我画了一张图帮你理解:
从这张图你能看到:
- 应用层:我们写的代码在这一层。比如用TensorFlow Lite加载模型,底层其实调用了NNAPI。
- Runtime层:NNAPI的核心。它负责解析模型、选择最优设备、调度执行。我调试时经常在这一层打日志,看模型被分配到了哪个设备。
- HAL层:厂商实现。高通、联发科、华为各自实现自己的加速器驱动。这也是为什么同一款模型在不同手机上性能差异巨大。
adb shell setprop debug.nn.vlog 1 就能看到NNAPI的设备选择和执行过程。这个技巧帮我定位过至少5个性能问题。
1.2.3 设计理念:一次编写,到处加速
NNAPI的核心设计理念可以概括为三点:
- 硬件无关性:你写的代码不需要关心底层是什么加速器。NNAPI自动选择最优方案。
- 性能优先:优先使用专用硬件(NPU/DSP),其次是GPU,最后才是CPU。
- 向后兼容:通过
Feature Level机制,新版本增加功能,旧版本保持可用。
举个例子,我在华为P40上跑一个图像分类模型,NNAPI会自动选择达芬奇架构NPU加速;换到小米11,就自动切换到Adreno GPU。代码一行都不用改——这就是NNAPI的魅力。
1.3 NNAPI在Android系统中的位置与作用
1.3.1 系统层级定位
NNAPI位于Android系统的应用框架层和硬件抽象层之间。具体来说:
| 层级 | 组件 | 与NNAPI的关系 |
|---|---|---|
| 应用层 | ML Kit, TF Lite, 自定义App | 通过NNAPI API调用底层加速 |
| 框架层 | android.neuralnetworks.* | 提供Java/Kotlin接口封装 |
| Native层 | libneuralnetworks.so | 核心Runtime实现 |
| HAL层 | vendor实现 | 硬件加速器驱动 |
| 内核层 | Linux Kernel | 提供DMA、内存管理等基础服务 |
1.3.2 NNAPI能做什么?
在实际项目中,NNAPI主要承担以下角色:
- 模型推理加速:把训练好的模型在手机上跑起来,利用硬件加速提升速度
- 功耗优化:专用硬件(如DSP)比CPU更省电,适合持续运行的AI应用
- 内存管理:自动管理模型输入输出的内存分配和释放
- 设备兼容:屏蔽不同厂商硬件的差异,提供统一接口
1.3.3 一个简单的NNAPI调用示例
说了这么多理论,我们来看点实际的。下面是一个最简单的NNAPI调用流程:
// 1. 创建ANeuralNetworksMemory(模型内存)
ANeuralNetworksMemory* memory;
ANeuralNetworksMemory_createFromFd(model_size, PROT_READ, model_fd, 0, &memory);
// 2. 创建ANeuralNetworksModel(模型对象)
ANeuralNetworksModel* model;
ANeuralNetworksModel_create(&model);
// 3. 添加操作和参数(这里省略具体添加过程)
// ANEURALNETWORKS_ADD, ANEURALNETWORKS_MUL, ...
// 4. 编译模型
ANeuralNetworksCompilation* compilation;
ANeuralNetworksCompilation_create(model, &compilation);
ANeuralNetworksCompilation_finish(compilation);
// 5. 创建执行对象并运行
ANeuralNetworksExecution* execution;
ANeuralNetworksExecution_create(compilation, &execution);
ANeuralNetworksExecution_setInput(execution, ...);
ANeuralNetworksExecution_setOutput(execution, ...);
ANeuralNetworksExecution_startCompute(execution, nullptr);
// 6. 获取结果
// 从输出缓冲区读取数据
这段代码虽然简单,但包含了NNAPI的核心流程:创建内存 → 构建模型 → 编译 → 执行。后面的章节我们会逐一深入每个步骤。
本章小结
这一章我们聊了三个核心内容:
- 神经网络基础:神经元、层、激活函数——这些是构建任何AI模型的砖块
- NNAPI架构:三层结构(应用层→Runtime→HAL),以及"一次编写,到处加速"的设计理念
- 系统定位:NNAPI在Android系统中的位置,以及它主要负责的推理加速任务
说实话,这些概念看起来有点抽象,但它们是后续所有实战内容的基础。下一章开始,我们会真正动手写代码——从搭建开发环境开始,一步步实现你的第一个NNAPI应用。
到时候见。