卷积神经网络模型部署到移动设备
截止到今年,已經有超過 20 億活躍的安卓設備。安卓手機的迅速普及很大程度上是因為各式各樣的智能 app,從地圖到圖片編輯器應有盡有。隨著深度學習的出現,我們的手機 app 將變得更加智能。下一代由深度學習驅動的手機 app 將可以學習并為你定制功能。一個很顯著的例子是「Microsoft Swiftkey」,這是一個鍵盤 app, 能通過學習你常用的單詞和詞組來幫助你快速打字。?
計算機視覺,自然語言處理,語音識別和語音合成等技術能夠大大改善用戶在移動應用方面的體驗。幸運的是,在移動應用方面,有很多工具開發成可以簡化深度學習模型的部署和管理。在這篇文章中,我將闡釋如何使用 TensorFlow mobile 將 PyTorch 和 Keras 部署到移動設備。
用 TensorFlow mobile 部署模型到安卓設備分為三個步驟:
- 將你的訓練模式轉換到 TensorFlow
- 在安卓應用中添加 TensorFlow mobile 作為附加功能
- 在你的應用中使用 TensorFlow 模式寫 Java 代碼執行推理。
在這篇文章中,我將介紹整個過程,最后完成一個植入圖像識別功能的安卓應用。
??安裝
本教程會用到 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 。這是一種很小但具備合理精確度的移動架構。在這兒下載預訓練模式(只有5M!)。
在轉權值之前,我們需要在 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_module(input=network, input_channel_small=32, input_channel_large=128)
? ?network = squeezenet_fire_module(input=network, input_channel_small=32, input_channel_large=128)
? ?network = MaxPool2D(pool_size=(3,?3), strides=(2,?2))(network)
? ?network = squeezenet_fire_module(input=network, input_channel_small=48, input_channel_large=192)
? ?network = squeezenet_fire_module(input=network, input_channel_small=48, input_channel_large=192)
? ?network = squeezenet_fire_module(input=network, input_channel_small=64, input_channel_large=256)
? ?network = squeezenet_fire_module(input=network, input_channel_small=64, input_channel_large=256)
? ?#Remove layers like Dropout and BatchNormalization, they are only needed in training
? ?#network = Dropout(0.5)(network)
? ?network = Conv2D(1000, kernel_size=(1,1), padding="valid", name="last_conv")(network)
? ?network = Activation("relu")(network)
? ?network = GlobalAvgPool2D()(network)
? ?network = Activation("softmax",name="output")(network)
? ?input_image = image_input
? ?model = Model(inputs=input_image, outputs=network)
? ?return?model
keras_model = SqueezeNet()
#Lucky for us, PyTorch includes a predefined Squeezenet
pytorch_model = squeezenet1_1()
#Load the pretrained model
pytorch_model.load_state_dict(torch.load("squeezenet.pth"))
#Time to transfer weights
converter = PytorchToKeras(pytorch_model,keras_model)
converter.convert((3,224,224))
#Save the weights of the converted keras model for later use
converter.save_weights("squeezenet.h5")
上面是已經轉好權值的,你所需要做的是將 Keras 模型保存為 squeezenet.h5。到這一步,我們可以拋棄 PyTorch 模型,繼續下一步了。
?
??將 Keras 轉成 TensorFlow 模式
到這一步,你已經有了 Keras 模式,無論是從 PyTorch 轉化而來的還是直接用 Keras 訓練而獲得的。你可以在這兒下載預訓練的 Keras Squeezenet 模式。下一步是將我們整個的模型架構和權值轉成可運行的 TensorFlow 模型。
創建一個新文件 ConvertToTensorflow.py 并添加以下代碼。
from?keras.models?import?Model
from?keras.layers?import?*
import?os
import?tensorflow?as?tf
def?keras_to_tensorflow(keras_model, output_dir, model_name,out_prefix="output_", log_tensorboard=True):
? ?if?os.path.exists(output_dir) ==?False:
? ? ? ?os.mkdir(output_dir)
? ?out_nodes = []
? ?for?i?in?range(len(keras_model.outputs)):
? ? ? ?out_nodes.append(out_prefix + str(i +?1))
? ? ? ?tf.identity(keras_model.output[i], out_prefix + str(i +?1))
? ?sess = K.get_session()
? ?from?tensorflow.python.framework?import?graph_util, graph_io
? ?init_graph = sess.graph.as_graph_def()
? ?main_graph = graph_util.convert_variables_to_constants(sess, init_graph, out_nodes)
? ?graph_io.write_graph(main_graph, output_dir, name=model_name, as_text=False)
? ?if?log_tensorboard:
? ? ? ?from?tensorflow.python.tools?import?import_pb_to_tensorboard
? ? ? ?import_pb_to_tensorboard.import_to_tensorboard(
? ? ? ? ? ?os.path.join(output_dir, model_name),
? ? ? ? ? ?output_dir)
"""
We explicitly redefine the Squeezent architecture since Keras has no predefined Squeezenet
"""
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_module(input=network, input_channel_small=32, input_channel_large=128)
? ?network = squeezenet_fire_module(input=network, input_channel_small=32, input_channel_large=128)
? ?network = MaxPool2D(pool_size=(3,?3), strides=(2,?2))(network)
? ?network = squeezenet_fire_module(input=network, input_channel_small=48, input_channel_large=192)
? ?network = squeezenet_fire_module(input=network, input_channel_small=48, input_channel_large=192)
? ?network = squeezenet_fire_module(input=network, input_channel_small=64, input_channel_large=256)
? ?network = squeezenet_fire_module(input=network, input_channel_small=64, input_channel_large=256)
? ?#Remove layers like Dropout and BatchNormalization, they are only needed in training
? ?#network = Dropout(0.5)(network)
? ?network = Conv2D(1000, kernel_size=(1,1), padding="valid", name="last_conv")(network)
? ?network = Activation("relu")(network)
? ?network = GlobalAvgPool2D()(network)
? ?network = Activation("softmax",name="output")(network)
? ?input_image = image_input
? ?model = Model(inputs=input_image, outputs=network)
? ?return?model
keras_model = SqueezeNet()
keras_model.load_weights("squeezenet.h5")
output_dir = os.path.join(os.getcwd(),"checkpoint")
keras_to_tensorflow(keras_model,output_dir=output_dir,model_name="squeezenet.pb")
print("MODEL SAVED")
上面的代碼將我們的 squeezenet.pb 保存到了 output_dir 中。并在同一文件夾中創建 了 TensorBoard 事件。
為了更加清晰地理解你的模型,你可以用 TensorBoard 將它可視化。
打開命令行并輸入
tensorboard –logdir=output_dir_path
output_dir_path would?be?the path?to?your output_dir.
一旦?TensorBoard 成功啟動,你將看到提示讓你打開如下 url?COMPUTER_NAME:6006
?
將 URL 地址輸入到瀏覽器中,將顯示以下界面。?
?
為了可視化你的模式,雙擊?IMPORT.
仔細看下該模型并記下輸入和輸出節點的名字(框架中的第一個和最后一個)。
如果你的命名和我之前代碼一樣的話,他們就應該是?input_1?和output_1??。
到這一步, 我們的模型就可以調用了。
?
??將 TensorFlow Mobile 添加到你的項目中
TensorFlow 有 2 個針對移動設備的庫,分別是「TensorFlow Mobile」和「TensorFlow Lite.」Lite 版本設計得非常小,所有的依賴庫大約只有 1M。它的模型也更優化。另外,在安卓 8 以上的設備中,還可以用神經網絡 API 加速。與「TensorFlow Mobile」不同,「TensorFlow Lite.」目前還不太完善,有些層并不能實現預期的效果。此外,windows 系統還不支持編譯庫和將模式轉成原生格式的操作。因此,在這個教程里,我堅持用 TensorFlow Mobile。
如果沒有現存項目的話,使用 Android Studio,創建一個新的安卓項目。然后添加TensorFlow Mobile 依賴庫到你的build.gradle?文件。
implementation?‘org.tensorflow:tensorflow-android:+’
Android studio ?將提示你同步 gradle,點擊?Sync Now?等待同步完成。
到這一步項目就創建完成了。
?
??在你的移動 App 上執行推理
在用代碼執行推理前,你需要將轉化的模式 (squeezenet.pb) ?添加到你的應用的資源文件夾里。
在 Android Studio 中右擊你的項目,鼠標移到「添加文件夾」選項,然后選擇「資源文件夾」。這時會在你的 app 目錄下創建一個資源文件夾。然后,拷貝你的模式到此目錄下。
點擊 here 下載標簽類,并拷貝文件到資源目錄。
現在你的項目包含了分類圖像的所有工具。
添加一個新的 Java 類到你的項目的主包中,取名為 ImageUtils , 然后將以下代碼拷貝到其中。
package?com.specpal.mobileai;
import?android.content.res.AssetManager;
import?android.graphics.Bitmap;
import?android.graphics.Canvas;
import?android.graphics.Matrix;
import?android.os.Environment;
import?java.io.File;
import?java.io.FileOutputStream;
import?java.io.InputStream;
import?org.json.*;
/**
* Utility class for manipulating images.
**/
public?class?ImageUtils?{
? ?/**
? ? * Returns a transformation matrix from one reference frame into another.
? ? * Handles cropping (if maintaining aspect ratio is desired) and rotation.
? ? *
? ? *?@param?srcWidth Width of source frame.
? ? *?@param?srcHeight Height of source frame.
? ? *?@param?dstWidth Width of destination frame.
? ? *?@param?dstHeight Height of destination frame.
? ? *?@param?applyRotation Amount of rotation to apply from one frame to another.
? ? * ?Must be a multiple of 90.
? ? *?@param?maintainAspectRatio If true, will ensure that scaling in x and y remains constant,
? ? * cropping the image if necessary.
? ? *?@return?The transformation fulfilling the desired requirements.
? ? */
? ?public?static?Matrix?getTransformationMatrix(
? ? ? ? ? ?final?int?srcWidth,
? ? ? ? ? ?final?int?srcHeight,
? ? ? ? ? ?final?int?dstWidth,
? ? ? ? ? ?final?int?dstHeight,
? ? ? ? ? ?final?int?applyRotation,
? ? ? ? ? ?final?boolean?maintainAspectRatio)?{
? ? ? ?final?Matrix matrix =?new?Matrix();
? ? ? ?if?(applyRotation !=?0) {
? ? ? ? ? ?// Translate so center of image is at origin.
? ? ? ? ? ?matrix.postTranslate(-srcWidth /?2.0f, -srcHeight /?2.0f);
? ? ? ? ? ?// Rotate around origin.
? ? ? ? ? ?matrix.postRotate(applyRotation);
? ? ? ?}
? ? ? ?// Account for the already applied rotation, if any, and then determine how
? ? ? ?// much scaling is needed for each axis.
? ? ? ?final?boolean?transpose = (Math.abs(applyRotation) +?90) %?180?==?0;
? ? ? ?final?int?inWidth = transpose ? srcHeight : srcWidth;
? ? ? ?final?int?inHeight = transpose ? srcWidth : srcHeight;
? ? ? ?// Apply scaling if necessary.
? ? ? ?if?(inWidth != dstWidth || inHeight != dstHeight) {
? ? ? ? ? ?final?float?scaleFactorX = dstWidth / (float) inWidth;
? ? ? ? ? ?final?float?scaleFactorY = dstHeight / (float) inHeight;
? ? ? ? ? ?if?(maintainAspectRatio) {
? ? ? ? ? ? ? ?// Scale by minimum factor so that dst is filled completely while
? ? ? ? ? ? ? ?// maintaining the aspect ratio. Some image may fall off the edge.
? ? ? ? ? ? ? ?final?float?scaleFactor = Math.max(scaleFactorX, scaleFactorY);
? ? ? ? ? ? ? ?matrix.postScale(scaleFactor, scaleFactor);
? ? ? ? ? ?}?else?{
? ? ? ? ? ? ? ?// Scale exactly to fill dst from src.
? ? ? ? ? ? ? ?matrix.postScale(scaleFactorX, scaleFactorY);
? ? ? ? ? ?}
? ? ? ?}
? ? ? ?if?(applyRotation !=?0) {
? ? ? ? ? ?// Translate back from origin centered reference to destination frame.
? ? ? ? ? ?matrix.postTranslate(dstWidth /?2.0f, dstHeight /?2.0f);
? ? ? ?}
? ? ? ?return?matrix;
? ?}
? ?public?static?Bitmap?processBitmap(Bitmap source,int?size){
? ? ? ?int?image_height = source.getHeight();
? ? ? ?int?image_width = source.getWidth();
? ? ? ?Bitmap croppedBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
? ? ? ?Matrix frameToCropTransformations = getTransformationMatrix(image_width,image_height,size,size,0,false);
? ? ? ?Matrix cropToFrameTransformations =?new?Matrix();
? ? ? ?frameToCropTransformations.invert(cropToFrameTransformations);
? ? ? ?final?Canvas canvas =?new?Canvas(croppedBitmap);
? ? ? ?canvas.drawBitmap(source, frameToCropTransformations,?null);
? ? ? ?return?croppedBitmap;
? ?}
? ?public?static?float[] normalizeBitmap(Bitmap source,int?size,float?mean,float?std){
? ? ? ?float[] output =?new?float[size * size *?3];
? ? ? ?int[] intValues =?new?int[source.getHeight() * source.getWidth()];
? ? ? ?source.getPixels(intValues,?0, source.getWidth(),?0,?0, source.getWidth(), source.getHeight());
? ? ? ?for?(int?i =?0; i < intValues.length; ++i) {
? ? ? ? ? ?final?int?val = intValues[i];
? ? ? ? ? ?output[i *?3] = (((val >>?16) &?0xFF) - mean)/std;
? ? ? ? ? ?output[i *?3?+?1] = (((val >>?8) &?0xFF) - mean)/std;
? ? ? ? ? ?output[i *?3?+?2] = ((val &?0xFF) - mean)/std;
? ? ? ?}
? ? ? ?return?output;
? ?}
? ?public?static?Object[] argmax(float[] array){
? ? ? ?int?best = -1;
? ? ? ?float?best_confidence =?0.0f;
? ? ? ?for(int?i =?0;i < array.length;i++){
? ? ? ? ? ?float?value = array[i];
? ? ? ? ? ?if?(value > best_confidence){
? ? ? ? ? ? ? ?best_confidence = value;
? ? ? ? ? ? ? ?best = i;
? ? ? ? ? ?}
? ? ? ?}
? ? ? ?return?new?Object[]{best,best_confidence};
? ?}
? ?public?static?String?getLabel( InputStream jsonStream,int?index){
? ? ? ?String label =?"";
? ? ? ?try?{
? ? ? ? ? ?byte[] jsonData =?new?byte[jsonStream.available()];
? ? ? ? ? ?jsonStream.read(jsonData);
? ? ? ? ? ?jsonStream.close();
? ? ? ? ? ?String jsonString =?new?String(jsonData,"utf-8");
? ? ? ? ? ?JSONObject object =?new?JSONObject(jsonString);
? ? ? ? ? ?label = object.getString(String.valueOf(index));
? ? ? ?}
? ? ? ?catch?(Exception e){
? ? ? ?}
? ? ? ?return?label;
? ?}
}
如果不理解上面的代碼也沒關系,這是一些未在核心 TensorFlow-Mobile 庫中實現的功能。因此,在參考了一些官方樣例后,我寫了這些代碼以方便后續工作。
在你的主活動中,創建一個 ImageView?和一個 TextView ,這將被用來顯示圖像和其預測結果。
在你的主活動中,你需要加載 TensorFlow-inference ?庫和初始化一些類變量。在你的 onCreate 方法前添加以下內容:
//Load the tensorflow inference library
? ?static?{
? ? ? ?System.loadLibrary("tensorflow_inference");
? ?}
? ?//PATH TO OUR MODEL FILE AND NAMES OF THE INPUT AND OUTPUT NODES
? ?private?String MODEL_PATH =?"file:///android_asset/squeezenet.pb";
? ?private?String INPUT_NAME =?"input_1";
? ?private?String OUTPUT_NAME =?"output_1";
? ?private?TensorFlowInferenceInterface tf;
? ?//ARRAY TO HOLD THE PREDICTIONS AND FLOAT VALUES TO HOLD THE IMAGE DATA
? ?float[] PREDICTIONS =?new?float[1000];
? ?private?float[] floatValues;
? ?private?int[] INPUT_SIZE = {224,224,3};
? ?ImageView imageView;
? ?TextView resultView;
? ?Snackbar progressBar;
添加一個計算預測類的函數:
//FUNCTION TO COMPUTE THE MAXIMUM PREDICTION AND ITS CONFIDENCE
? ?public?Object[]?argmax(float[] array){
? ? ? ?int?best =?-1;
? ? ? ?float?best_confidence =?0.0f;
? ? ? ?for(int?i =?0;i < array.length;i++){
? ? ? ? ? ?float?value?= array[i];
? ? ? ? ? ?if?(value?> best_confidence){
? ? ? ? ? ? ? ?best_confidence =?value;
? ? ? ? ? ? ? ?best = i;
? ? ? ? ? ?}
? ? ? ?}
? ? ? ?return?new?Object[]{best,best_confidence};
? ?}
添加函數來接收 Image Bitmap 并在其上執行推理:
public?void?predict(final?Bitmap bitmap){
? ? ? ?//Runs inference in background thread
? ? ? ?new?AsyncTask<Integer,Integer,Integer>(){
? ? ? ? ? ?@Override
? ? ? ? ? ?protected?Integer?doInBackground(Integer ...params){
? ? ? ? ? ? ? ?//Resize the image into 224 x 224
? ? ? ? ? ? ? ?Bitmap resized_image = ImageUtils.processBitmap(bitmap,224);
? ? ? ? ? ? ? ?//Normalize the pixels
? ? ? ? ? ? ? ?floatValues = ImageUtils.normalizeBitmap(resized_image,224,127.5f,1.0f);
? ? ? ? ? ? ? ?//Pass input into the tensorflow
? ? ? ? ? ? ? ?tf.feed(INPUT_NAME,floatValues,1,224,224,3);
? ? ? ? ? ? ? ?//compute predictions
? ? ? ? ? ? ? ?tf.run(new?String[]{OUTPUT_NAME});
? ? ? ? ? ? ? ?//copy the output into the PREDICTIONS array
? ? ? ? ? ? ? ?tf.fetch(OUTPUT_NAME,PREDICTIONS);
? ? ? ? ? ? ? ?//Obtained highest prediction
? ? ? ? ? ? ? ?Object[] results = argmax(PREDICTIONS);
? ? ? ? ? ? ? ?int?class_index = (Integer) results[0];
? ? ? ? ? ? ? ?float?confidence = (Float) results[1];
? ? ? ? ? ? ? ?try{
? ? ? ? ? ? ? ? ? ?final?String conf = String.valueOf(confidence *?100).substring(0,5);
? ? ? ? ? ? ? ? ? ?//Convert predicted class index into actual label name
? ? ? ? ? ? ? ? ??final?String label = ImageUtils.getLabel(getAssets().open("labels.json"),class_index);
? ? ? ? ? ? ? ? ??//Display result on UI
? ? ? ? ? ? ? ? ? ?runOnUiThread(new?Runnable() {
? ? ? ? ? ? ? ? ? ? ? ?@Override
? ? ? ? ? ? ? ? ? ? ? ?public?void?run()?{
? ? ? ? ? ? ? ? ? ? ? ? ? ?progressBar.dismiss();
? ? ? ? ? ? ? ? ? ? ? ? ? ?resultView.setText(label +?" : "?+ conf +?"%");
? ? ? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? ?});
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?catch?(Exception e){
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?return?0;
? ? ? ? ? ?}
? ? ? ?}.execute(0);
? ?}
以上代碼在后臺線程中運行預測,并將預測的類和它的評估得分寫到我們之前定義的?TextView?中。
注意在主 UI 線程運行推理時可能會掛起。記住一個原則 :“永遠在你的后臺線程運行推理!”
本教程的重點是圖像識別,為此我在資源文件夾中添加了一只小鳥的圖像。在標準應用程序中,你要用代碼從文件系統加載圖像。
添加任何你想做預測的圖像到資源文件夾中。為了方便的運行算法,在下列的代碼中,我們在一個按鈕上添加了一個點擊監聽。該監聽可以加載該圖像并調用預測功能。
@Override
? ?protected?void?onCreate(Bundle savedInstanceState)?{
? ? ? ?super.onCreate(savedInstanceState);
? ? ? ?setContentView(R.layout.activity_main);
? ? ? ?Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
? ? ? ?setSupportActionBar(toolbar);
? ? ? ?//initialize tensorflow with the AssetManager and the Model
? ? ? ?tf =?new?TensorFlowInferenceInterface(getAssets(),MODEL_PATH);
? ? ? ?imageView = (ImageView) findViewById(R.id.imageview);
? ? ? ?resultView = (TextView) findViewById(R.id.results);
? ? ? ?progressBar = Snackbar.make(imageView,"PROCESSING IMAGE",Snackbar.LENGTH_INDEFINITE);
? ? ? ?final?FloatingActionButton predict = (FloatingActionButton) findViewById(R.id.predict);
? ? ? ?predict.setOnClickListener(new?View.OnClickListener() {
? ? ? ? ? ?@Override
? ? ? ? ? ?public?void?onClick(View view)?{
? ? ? ? ? ? ? ?try{
? ? ? ? ? ? ? ? ? ?//READ THE IMAGE FROM ASSETS FOLDER
? ? ? ? ? ? ? ? ? ?InputStream imageStream = getAssets().open("testimage.jpg");
? ? ? ? ? ? ? ? ? ?Bitmap bitmap = BitmapFactory.decodeStream(imageStream);
? ? ? ? ? ? ? ? ? ?imageView.setImageBitmap(bitmap);
? ? ? ? ? ? ? ? ? ?progressBar.show();
? ? ? ? ? ? ? ? ? ?predict(bitmap);
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?catch?(Exception e){
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ?});
? ?}
很好!所有步驟都已完成!雙擊檢驗一下,如果都沒有問題。點擊「Bulid APK.」按鈕
APK很快就創建完成了,之后在設備上安裝并運行App.
結果如下圖所示:
為了得到更新奇的體驗,你的 App 應當從安卓文件系統加載圖像或用攝像頭抓取圖像,而不是從資源文件夾加載。
?
??總結
移動端的深度學習框架將最終轉變我們開發和使用 app 的方式。使用上述代碼,你能輕松導出你訓練的 PyTorch 和 Keras 模型到 TensorFlow。運用 TensorFlow Mobile 和這篇文章中介紹的步驟,你可以將卓越的 AI 功能完美的植入到你的移動端應用中。
安卓項目的完整代碼和模型轉換工具在我的?GitHub?上可以找到:
https://github.com/paulpanwang/YOLO-Android
下一篇:YOLO 與 增強現實
總結
以上是生活随笔為你收集整理的卷积神经网络模型部署到移动设备的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言编程基础第四版电子教案,《C语言编
- 下一篇: 基于卷积神经网络(cnn)的手写数字识别