zoukankan      html  css  js  c++  java
  • 将训练好的tensorflow模型移植到android应用中

    具体步骤如下:

    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, 可以正常运行。

  • 相关阅读:
    课后作业2
    软工团队项目度量反馈平台需求分析文档-北航软工暑期培训班团队项目1组
    《构建之法》教学笔记——Python中的效能分析与几个问题
    高职院校软件技术专业程序设计课程体系探索(一)
    network_node:host解决Docker容器化部署微服务出现的Dobbo通信问题
    微服务中的健康监测以及其在ASP.NET Core服务中实现运行状况检查
    词频统计
    消息队列
    Linux文件扩展思考随笔
    如何在Mirth Connect中创建和调用自定义Java代码
  • 原文地址:https://www.cnblogs.com/scarecrow-blog/p/11481764.html
Copyright © 2011-2022 走看看