具体步骤如下:

1.  TFLiteConverter保存模型

修改网络模型代码,将模型通过TFLiteConverter转化成为 TensorFlow Lite FlatBuffer即为.tflite的备份文件。参考官网说明https://tensorflow.google.cn/lite/convert/python_api

  这里我选择的模型是tensorflow tutorial里面的mnist代码,原因是比较简单,方便实验。具体路径models-master/tutorials/image/mnist/

  对于输入、输出,我做了一下修改,简化为一张图片的预测。

     输入节点:

  eval_data1 = tf.placeholder(
data_type(),
shape=(1, IMAGE_SIZE, IMAGE_SIZE, 1))

    输出节点:

  eval_prediction1 = tf.nn.softmax(model(eval_data1))

用converter保存模型:

        if FLAGS.save_tflite:
# save tflite
converter = tf.lite.TFLiteConverter.from_session(sess, [eval_data1], [eval_prediction1])
tflite_model = converter.convert()
open("converted_model.tflite", "wb").write(tflite_model)
print('===========save tflite over =============')

    完整代码:

# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================== """Simple, end-to-end, LeNet-5-like convolutional MNIST model example. This should achieve a test error of 0.7%. Please keep this model as simple and
linear as possible, it is meant as a tutorial for simple convolutional models.
Run with --self_test on the command line to execute a short self-test.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function import argparse
import gzip
import os
import sys
import time import numpy
from six.moves import urllib
from six.moves import xrange # pylint: disable=redefined-builtin
import tensorflow as tf # CVDF mirror of http://yann.lecun.com/exdb/mnist/
SOURCE_URL = 'https://storage.googleapis.com/cvdf-datasets/mnist/'
WORK_DIRECTORY = 'data'
IMAGE_SIZE = 28
NUM_CHANNELS = 1
PIXEL_DEPTH = 255
NUM_LABELS = 10
VALIDATION_SIZE = 5000 # Size of the validation set.
SEED = 66478 # Set to None for random seed.
BATCH_SIZE = 64
NUM_EPOCHS = 10
EVAL_BATCH_SIZE = 64
EVAL_FREQUENCY = 100 # Number of steps between evaluations. FLAGS = None def data_type():
"""Return the type of the activations, weights, and placeholder variables."""
if FLAGS.use_fp16:
return tf.float16
else:
return tf.float32 def maybe_download(filename):
"""Download the data from Yann's website, unless it's already here."""
if not tf.gfile.Exists(WORK_DIRECTORY):
tf.gfile.MakeDirs(WORK_DIRECTORY)
filepath = os.path.join(WORK_DIRECTORY, filename)
if not tf.gfile.Exists(filepath):
filepath, _ = urllib.request.urlretrieve(SOURCE_URL + filename, filepath)
with tf.gfile.GFile(filepath) as f:
size = f.size()
print('Successfully downloaded', filename, size, 'bytes.')
return filepath def extract_data(filename, num_images):
"""Extract the images into a 4D tensor [image index, y, x, channels]. Values are rescaled from [0, 255] down to [-0.5, 0.5].
"""
print('Extracting', filename)
with gzip.open(filename) as bytestream:
bytestream.read(16)
buf = bytestream.read(IMAGE_SIZE * IMAGE_SIZE * num_images * NUM_CHANNELS)
data = numpy.frombuffer(buf, dtype=numpy.uint8).astype(numpy.float32)
data = (data - (PIXEL_DEPTH / 2.0)) / PIXEL_DEPTH
data = data.reshape(num_images, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS)
return data def extract_labels(filename, num_images):
"""Extract the labels into a vector of int64 label IDs."""
print('Extracting', filename)
with gzip.open(filename) as bytestream:
bytestream.read(8)
buf = bytestream.read(1 * num_images)
labels = numpy.frombuffer(buf, dtype=numpy.uint8).astype(numpy.int64)
return labels def fake_data(num_images):
"""Generate a fake dataset that matches the dimensions of MNIST."""
data = numpy.ndarray(
shape=(num_images, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS),
dtype=numpy.float32)
labels = numpy.zeros(shape=(num_images,), dtype=numpy.int64)
for image in xrange(num_images):
label = image % 2
data[image, :, :, 0] = label - 0.5
labels[image] = label
return data, labels def error_rate(predictions, labels):
"""Return the error rate based on dense predictions and sparse labels."""
return 100.0 - (
100.0 *
numpy.sum(numpy.argmax(predictions, 1) == labels) /
predictions.shape[0]) def main(_):
if FLAGS.self_test:
print('Running self-test.')
train_data, train_labels = fake_data(256)
validation_data, validation_labels = fake_data(EVAL_BATCH_SIZE)
test_data, test_labels = fake_data(EVAL_BATCH_SIZE)
num_epochs = 1
else:
# Get the data.
train_data_filename = maybe_download('train-images-idx3-ubyte.gz')
train_labels_filename = maybe_download('train-labels-idx1-ubyte.gz')
test_data_filename = maybe_download('t10k-images-idx3-ubyte.gz')
test_labels_filename = maybe_download('t10k-labels-idx1-ubyte.gz') # Extract it into numpy arrays.
train_data = extract_data(train_data_filename, 60000)
train_labels = extract_labels(train_labels_filename, 60000)
test_data = extract_data(test_data_filename, 10000)
test_labels = extract_labels(test_labels_filename, 10000) # Generate a validation set.
validation_data = train_data[:VALIDATION_SIZE, ...]
validation_labels = train_labels[:VALIDATION_SIZE]
train_data = train_data[VALIDATION_SIZE:, ...]
train_labels = train_labels[VALIDATION_SIZE:]
num_epochs = NUM_EPOCHS
train_size = train_labels.shape[0] # This is where training samples and labels are fed to the graph.
# These placeholder nodes will be fed a batch of training data at each
# training step using the {feed_dict} argument to the Run() call below.
train_data_node = tf.placeholder(
data_type(),
shape=(BATCH_SIZE, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS))
train_labels_node = tf.placeholder(tf.int64, shape=(BATCH_SIZE,))
eval_data = tf.placeholder(
data_type(),
shape=(EVAL_BATCH_SIZE, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS))
eval_data1 = tf.placeholder(
data_type(),
shape=(1, IMAGE_SIZE, IMAGE_SIZE, 1)) # The variables below hold all the trainable weights. They are passed an
# initial value which will be assigned when we call:
# {tf.global_variables_initializer().run()}
conv1_weights = tf.Variable(
tf.truncated_normal([5, 5, NUM_CHANNELS, 32], # 5x5 filter, depth 32.
stddev=0.1,
seed=SEED, dtype=data_type()))
conv1_biases = tf.Variable(tf.zeros([32], dtype=data_type()))
conv2_weights = tf.Variable(tf.truncated_normal(
[5, 5, 32, 64], stddev=0.1,
seed=SEED, dtype=data_type()))
conv2_biases = tf.Variable(tf.constant(0.1, shape=[64], dtype=data_type()))
fc1_weights = tf.Variable( # fully connected, depth 512.
tf.truncated_normal([IMAGE_SIZE // 4 * IMAGE_SIZE // 4 * 64, 512],
stddev=0.1,
seed=SEED,
dtype=data_type()))
fc1_biases = tf.Variable(tf.constant(0.1, shape=[512], dtype=data_type()))
fc2_weights = tf.Variable(tf.truncated_normal([512, NUM_LABELS],
stddev=0.1,
seed=SEED,
dtype=data_type()))
fc2_biases = tf.Variable(tf.constant(
0.1, shape=[NUM_LABELS], dtype=data_type())) # We will replicate the model structure for the training subgraph, as well
# as the evaluation subgraphs, while sharing the trainable parameters.
def model(data, train=False):
"""The Model definition."""
# 2D convolution, with 'SAME' padding (i.e. the output feature map has
# the same size as the input). Note that {strides} is a 4D array whose
# shape matches the data layout: [image index, y, x, depth].
conv = tf.nn.conv2d(data,
conv1_weights,
strides=[1, 1, 1, 1],
padding='SAME')
# Bias and rectified linear non-linearity.
relu = tf.nn.relu(tf.nn.bias_add(conv, conv1_biases))
# Max pooling. The kernel size spec {ksize} also follows the layout of
# the data. Here we have a pooling window of 2, and a stride of 2.
pool = tf.nn.max_pool(relu,
ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1],
padding='SAME')
conv = tf.nn.conv2d(pool,
conv2_weights,
strides=[1, 1, 1, 1],
padding='SAME')
relu = tf.nn.relu(tf.nn.bias_add(conv, conv2_biases))
pool = tf.nn.max_pool(relu,
ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1],
padding='SAME')
# Reshape the feature map cuboid into a 2D matrix to feed it to the
# fully connected layers.
pool_shape = pool.get_shape().as_list()
reshape = tf.reshape(
pool,
[pool_shape[0], pool_shape[1] * pool_shape[2] * pool_shape[3]])
# Fully connected layer. Note that the '+' operation automatically
# broadcasts the biases.
hidden = tf.nn.relu(tf.matmul(reshape, fc1_weights) + fc1_biases)
# Add a 50% dropout during training only. Dropout also scales
# activations such that no rescaling is needed at evaluation time.
if train:
hidden = tf.nn.dropout(hidden, 0.5, seed=SEED)
return tf.matmul(hidden, fc2_weights) + fc2_biases # Training computation: logits + cross-entropy loss.
logits = model(train_data_node, True)
loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(
labels=train_labels_node, logits=logits)) # L2 regularization for the fully connected parameters.
regularizers = (tf.nn.l2_loss(fc1_weights) + tf.nn.l2_loss(fc1_biases) +
tf.nn.l2_loss(fc2_weights) + tf.nn.l2_loss(fc2_biases))
# Add the regularization term to the loss.
loss += 5e-4 * regularizers # Optimizer: set up a variable that's incremented once per batch and
# controls the learning rate decay.
batch = tf.Variable(0, dtype=data_type())
# Decay once per epoch, using an exponential schedule starting at 0.01.
learning_rate = tf.train.exponential_decay(
0.01, # Base learning rate.
batch * BATCH_SIZE, # Current index into the dataset.
train_size, # Decay step.
0.95, # Decay rate.
staircase=True)
# Use simple momentum for the optimization.
optimizer = tf.train.MomentumOptimizer(learning_rate,
0.9).minimize(loss,
global_step=batch) # Predictions for the current training minibatch.
train_prediction = tf.nn.softmax(logits) # Predictions for the test and validation, which we'll compute less often.
eval_prediction = tf.nn.softmax(model(eval_data)) eval_prediction1 = tf.nn.softmax(model(eval_data1)) # Small utility function to evaluate a dataset by feeding batches of data to
# {eval_data} and pulling the results from {eval_predictions}.
# Saves memory and enables this to run on smaller GPUs.
def eval_in_batches(data, sess):
"""Get all predictions for a dataset by running it in small batches."""
size = data.shape[0]
if size < EVAL_BATCH_SIZE:
raise ValueError("batch size for evals larger than dataset: %d" % size)
predictions = numpy.ndarray(shape=(size, NUM_LABELS), dtype=numpy.float32)
for begin in xrange(0, size, EVAL_BATCH_SIZE):
end = begin + EVAL_BATCH_SIZE
if end <= size:
predictions[begin:end, :] = sess.run(
eval_prediction,
feed_dict={eval_data: data[begin:end, ...]})
else:
batch_predictions = sess.run(
eval_prediction,
feed_dict={eval_data: data[-EVAL_BATCH_SIZE:, ...]})
predictions[begin:, :] = batch_predictions[begin - size:, :]
return predictions # Create a local session to run the training.
start_time = time.time()
with tf.Session() as sess:
# Run all the initializers to prepare the trainable parameters.
tf.global_variables_initializer().run()
print('Initialized!') if FLAGS.use_tflite:
interpreter = tf.lite.Interpreter(model_path="converted_model.tflite")
interpreter.allocate_tensors() input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
#eval_data = test_data[0:EVAL_BATCH_SIZE, ...]
#print(test_data[0, ..., 0])
print("===========")
# eval_data = numpy.ndarray(
# shape=(EVAL_BATCH_SIZE, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS),
# dtype=numpy.float32)
# #eval_data = test_data[0, ...]
# interpreter.set_tensor(input_details[0]['index'], test_data[0:EVAL_BATCH_SIZE, ...]) my_data = numpy.ndarray(
shape=(1, IMAGE_SIZE, IMAGE_SIZE, 1),
dtype=numpy.float32)
my_data[0, :, :, 0] = test_data[1, :, :, 0]
interpreter.set_tensor(input_details[0]['index'], my_data) interpreter.invoke()
eval_prediction = interpreter.get_tensor(output_details[0]['index'])
print(eval_prediction)
print(numpy.argmax(eval_prediction, 1))
print(test_labels[0:EVAL_BATCH_SIZE, ...])
else:
# #restore checkpoint
# saver = tf.train.Saver()
# ckpt = tf.train.get_checkpoint_state("./mnist-model/")
# print('===================')
# print(ckpt)
# print('===================')
# '''saver.restore(sess, ckpt.all_model_checkpoint_paths[0])
# print (ckpt.all_model_checkpoint_paths[0])'''
#
# saver.restore(sess, ckpt.model_checkpoint_path)
# print(ckpt.model_checkpoint_path)
# print('===================') # Loop through training steps.
for step in xrange(int(num_epochs * train_size) // BATCH_SIZE):
# Compute the offset of the current minibatch in the data.
# Note that we could use better randomization across epochs.
offset = (step * BATCH_SIZE) % (train_size - BATCH_SIZE)
batch_data = train_data[offset:(offset + BATCH_SIZE), ...]
batch_labels = train_labels[offset:(offset + BATCH_SIZE)]
# This dictionary maps the batch data (as a numpy array) to the
# node in the graph it should be fed to.
feed_dict = {train_data_node: batch_data,
train_labels_node: batch_labels}
# Run the optimizer to update weights.
sess.run(optimizer, feed_dict=feed_dict)
# print some extra information once reach the evaluation frequency
if step % EVAL_FREQUENCY == 0: # #save checkpoint
# saver.save(sess, "./mnist-model/model.ckpt", global_step=step) # fetch some extra nodes' data
l, lr, predictions = sess.run([loss, learning_rate, train_prediction], feed_dict=feed_dict)
elapsed_time = time.time() - start_time
start_time = time.time()
print('Step %d (epoch %.2f), %.1f ms' %
(step, float(step) * BATCH_SIZE / train_size,
1000 * elapsed_time / EVAL_FREQUENCY))
print('Minibatch loss: %.3f, learning rate: %.6f' % (l, lr))
print('Minibatch error: %.1f%%' % error_rate(predictions, batch_labels))
print('Validation error: %.1f%%' % error_rate(
eval_in_batches(validation_data, sess), validation_labels))
sys.stdout.flush()
# Finally print the result!
test_error = error_rate(eval_in_batches(test_data, sess), test_labels)
print('Test error: %.1f%%' % test_error)
if FLAGS.save_tflite:
# save tflite, if we want to save with multiple inputs and outputs, the format is like [eval_data, eval_data1], [eval_predictiion, eval_prediction1].
# you must notice that each output must can be worked out by inputs, or there may be segment fault.
converter = tf.lite.TFLiteConverter.from_session(sess, [eval_data1], [eval_prediction1])
tflite_model = converter.convert()
open("converted_model.tflite", "wb").write(tflite_model)
print('===========save tflite over =============')
if FLAGS.self_test:
print('test_error', test_error)
assert test_error == 0.0, 'expected 0.0 test_error, got %.2f' % (
test_error,) if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument(
'--use_fp16',
default=False,
help='Use half floats instead of full floats if True.',
action='store_true')
parser.add_argument(
'--self_test',
default=False,
action='store_true',
help='True if running a self test.')
parser.add_argument(
'--use_tflite',
default=False,
action='store_true',
help='True if running by tflite.')
parser.add_argument(
'--save_tflite',
default=False,
action='store_true',
help='True if running by tflite.') FLAGS, unparsed = parser.parse_known_args()
tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)

2.  创建Android工程

  创建一个空的android项目,在main下创建assets文件夹,将上一步保存的converted_model.tflite文件拷贝到assets下。

  修改app下的build.gradle, 加入tensorflow-lite 依赖。

apply plugin: 'com.android.application'

android {
compileSdkVersion 29
buildToolsVersion "29.0.1"
defaultConfig {
applicationId "com.example.testtflite"
minSdkVersion 28
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
} android.applicationVariants.all {
variant ->
variant.outputs.all {
outputFileName = "app_test.apk"
}
}
} dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
//tflite
implementation 'org.tensorflow:tensorflow-lite:0.0.0-nightly'
}

  简单修改MainActivity.java

  

package com.example.testtflite;

import android.content.res.AssetFileDescriptor;
import android.os.Bundle;
import android.util.Log; import androidx.appcompat.app.AppCompatActivity; import org.tensorflow.lite.Interpreter; import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; public class MainActivity extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("YY", "begin");
try {
AssetFileDescriptor fileDescriptor = getAssets().openFd("converted_model.mp3");
FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
FileChannel fileChannel = inputStream.getChannel();
long startOffset = fileDescriptor.getStartOffset();
long declaredLength = fileDescriptor.getDeclaredLength();
ByteBuffer tfliteModel = fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
Log.i("YY", "get model");
Interpreter interpreter = new Interpreter(tfliteModel);
Log.i("YY", "load model");
float[][][][] input = new float[1][28][28][1];
for (int i=0; i<28; i++) {
for (int j=0; j<28; j++) { if ((i == 7 || i == 8) && (j > 2 && j<14)) {
input[0][i][j][0] = 0.5f;
}else if (i > 7 && (j == 12 || j== 13)) {
input[0][i][j][0] = 0.5f;
} else {
input[0][i][j][0] = -0.5f;
}
}
}
float[][] output = new float[1][10];
interpreter.run(input, output);
float out, maxOut=0;
int max_index = -1;
for (int i=0; i<10; i++) {
out = output[0][i];
if(maxOut < out) {
maxOut = out;
max_index = i;
}
// Log.i("YY", Float.toString(out)); }
Log.i("YY", "predict number : " + max_index); Log.i("YY", "finish"); } catch (IOException e) {
e.printStackTrace();
Log.e("YY", e.getMessage());
} }
}

  这中间启动app报了一个错误,java.io.FileNotFoundException: This file can not be opened as a file descriptor; it is probably compressed, 查了一下,意思是文件没有找到,或者文件被压缩了,原来是打包apk的时候,相当于将assets的文件添加到了一个.zip文件里,进行了一些压缩,导致openFd方法找不到此文件的描述。参考http://ponystyle.com/blog/2010/03/26/dealing-with-asset-compression-in-android-apps/

  修改文件后缀名为mp3, 可以正常运行。

最新文章

  1. 1.初识Linux
  2. DOS命令大全!
  3. IT在线学习网站总结
  4. 基于Bootstrap简单实用的tags标签插件
  5. java程序
  6. Windows下USB磁盘开发系列三:枚举系统中U盘、并获取其设备信息
  7. 2013杭州现场赛B题-Rabbit Kingdom
  8. [设计模式] 18 备忘录模式Memento Pattern
  9. HDU-4952 Number Transformation
  10. QtXML 举例
  11. JFS与JFS2的区别
  12. code_analyzer(代码分析助手)
  13. MySQL 索引 总结
  14. MySQL管理命令
  15. typedef和define具体的详细区别
  16. Jenkins安装和配置
  17. Mysql Group by 使用解析
  18. Swift编程语言学习1.7——断言
  19. NSObject之一
  20. Hibernate(十)

热门文章

  1. Linux下Discuz!7.2 LAMP环境搭建
  2. python广度遍历文件夹
  3. css中&quot;.&quot;,&quot;,&quot;,“~”和“&gt;”符号的意义
  4. Oracle存储结构-段区块
  5. 51 Nod 1086 多重背包问题(单调队列优化)
  6. 【BZOJ2459】 [BeiJing2011]神秘好人
  7. Python字典里的5个黑魔法
  8. 「HAOI2016」食物链
  9. CodeForces 349B--Color the Fence(贪心)
  10. AcWing:139. 回文子串的最大长度(字符串Hash + 前缀和 + 后缀和 + 二分)