截至 2018 年,全球活跃的安卓设备已经超过了 20 亿部。安卓手机的迅速普及在很大程度上得益于各种各样的智能应用,从地图到图片编辑器无所不有。随着深度学习技术的兴起,移动应用注定会变得更加智能。深度学习加持下的下一代移动应用将专门为你学习和定制功能。微软的「SwiftKey」就是一个很好的例子,它能够通过学习你常用的单词和短语来帮助你更快地打字。
计算机视觉、自然语言处理、语音识别以及语音合成等技术可以极大地提高移动应用程序各个方面的用户体验。幸运的是,人们现在已经开发出了大量工具,用于简化在移动应用中部署和管理深度学习模型的过程。在本文中,作者将向大家介绍如何使用 TensorFlow Mobile 将 Pytorch 和 Keras 模型部署到移动设备上。
使用 TensorFlow Mobile 将模型部署到安卓设备上包括三个步骤:
将训练好的模型转换成 TensorFlow 格式;
向安卓应用添加 TensorFlow Mobile 依赖项;
编写相关的 Java 代码,在你的应用中使用 TensorFlow 模型执行推断。
在本文中,我将带你熟悉以上的整个流程,最终完成一个嵌入图像识别功能的安卓应用。
环境设置
在本教程中,我们将使用 Pytorch 和 Keras,选择你偏好的机器学习框架,并按照说明进行操作。因此,你的环境设置取决于你选择的框架。
第一步,安装 TensorFlow:
pip3 install tensorflow
如果你是 PyTorch 开发者,请确保你已经安装了最新版本的 PyTorch。关于安装 PyTorch 的说明,请查阅我早前编写的这篇文章(https://heartbeat.fritz.ai/basics-of-image-classification-with-pytorch-2f8973c51864)。
如果你是一名 Keras 开发者,你可以使用下面的命令安装相关开发环境:
pip3 install keras
pip3 install h5py
Android Studio(精简版 3.0)
https://developer.android.com/studio
将 PyTorch 模型转换为 Keras 模型
本节仅针对于 PyTorch 开发者。如果你使用的是 Keras 框架,你可以直接跳到「将 Keras 模型转换为 TensorFlow 模型」这一节。
我们需要做的第一件事就是将 PyTorch 模型的参数转化为其在 Keras 框架下等价的参数。为了简化这个过程,我编写了一个脚本来自动化地进行这个转换工作。在这篇教程中,我将使用 Squeezenet,这是一种准确率还不错且规模非常小的移动架构。你可以通过这个链接下载预训练好的模型(大小仅仅只有 5mb!):https://download.pytorch.org/models/squeezenet1_1-f364aa15.pth。
在转换权重之前,我们需要在 PyTorch 和 Keras 中定义 Squeezenet 模型。
在两个框架中都定义 Squeezenet,然后使用下面的方法将 PyTorch 框架的权重迁移到 Keras 框架中。
创建一个 convert.py 文件,引入下面的代码,并且运行脚本。
import torch
import torch.nn as nn
from torch.autograd import Variable
import keras.backend as K
from keras.models import *
from keras.layers import *
import torch
from torchvision.models import squeezenet1_1
class PytorchToKeras(object):
def __init__(self,pModel,kModel):
super(PytorchToKeras,self)
self.__source_layers = []
self.__target_layers = []
self.pModel = pModel
self.kModel = kModel
K.set_learning_phase(0)
def __retrieve_k_layers(self):
for i,layer in enumerate(self.kModel.layers):
if len(layer.weights) > 0:
self.__target_layers.append(i)
def __retrieve_p_layers(self,input_size):
input = torch.randn(input_size)
input = Variable(input.unsqueeze(0))
hooks = []
def add_hooks(module):
def hook(module, input, output):
if hasattr(module,"weight"):
self.__source_layers.append(module)
if not isinstance(module, nn.ModuleList) and not isinstance(module,nn.Sequential) and module != self.pModel:
hooks.append(module.register_forward_hook(hook))
self.pModel.apply(add_hooks)
self.pModel(input)
for hook in hooks:
hook.remove()
def convert(self,input_size):
self.__retrieve_k_layers()
self.__retrieve_p_layers(input_size)
for i,(source_layer,target_layer) in enumerate(zip(self.__source_layers,self.__target_layers)):
weight_size = len(source_layer.weight.data.size())
transpose_dims = []
for i in range(weight_size):
transpose_dims.append(weight_size - i - 1)
self.kModel.layers[target_layer].set_weights([source_layer.weight.data.numpy().transpose(transpose_dims), source_layer.bias.data.numpy()])
def save_model(self,output_file):
self.kModel.save(output_file)
def save_weights(self,output_file):
self.kModel.save_weights(output_file)
"""
We explicitly redefine the Squeezent architecture since Keras has no predefined Squeezent
"""
def squeezenet_fire_module(input, input_channel_small=16, input_channel_large=64):
channel_axis = 3
input = Conv2D(input_channel_small, (1,1), padding="valid" )(input)
input = Activation("relu")(input)
input_branch_1 = Conv2D(input_channel_large, (1,1), padding="valid" )(input)
input_branch_1 = Activation("relu")(input_branch_1)
input_branch_2 = Conv2D(input_channel_large, (3, 3), padding="same")(input)
input_branch_2 = Activation("relu")(input_branch_2)
input = concatenate([input_branch_1, input_branch_2], axis=channel_axis)
return input
def SqueezeNet(input_shape=(224,224,3)):
image_input = Input(shape=input_shape)
network = Conv2D(64, (3,3), strides=(2,2), padding="valid")(image_input)
network = Activation("relu")(network)
network = MaxPool2D( pool_size=(3,3) , strides=(2,2))(network)
network = squeezenet_fire_module(input=network, input_channel_small=16, input_channel_large=64)
network = squeezenet_fire_module(input=network, input_channel_small=16, input_channel_large=64)
network = MaxPool2D(pool_size=(3,3), strides=(2,2))(network)
network = squeezenet_fire_mod