tensorflow lite on arduino nano 33 BLE : Didn't find op for builtin opcode 'EXPAND_DIMS' version '1'

Viewed 960

I used Tensorflow lite 2.1.1-ALPHA-PRECOMPILED for arduino nano 33 ble with headers

Import

import numpy as np
from tensorflow import keras
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Flatten, Dense, Dropout, Conv1D, MaxPooling1D

Model Definition

def get_model(n_timesteps, n_features, n_outputs): 
    model = Sequential()
    model.add(Conv1D(filters=32, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features)))
    model.add(Conv1D(filters=32, kernel_size=3, activation='relu'))
    model.add(Dropout(0.5))
    model.add(MaxPooling1D(pool_size=2))
    model.add(Conv1D(filters=16, kernel_size=5, activation='relu'))
    model.add(MaxPooling1D(pool_size=2))
    model.add(Flatten())  
    model.add(Dense(n_outputs, activation='softmax')) 
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy', tf.keras.metrics.Precision()])
    # fit network
    return model 

model = get_model(128, 6, num_class=4)

Model Summary

enter image description here

TF Lite Converter Work but add expandsdims operation

# Convert the model to the TensorFlow Lite format without quantization

converter = tf.lite.TFLiteConverter.from_keras_model(model)
def representative_dataset():
    for _, samp in enumerate(trainX):
        yield [samp.astype(np.float32).reshape(1, 128, 6)]
    # Set the optimization flag.
    converter.optimizations = [tf.lite.Optimize.DEFAULT]
    # Enforce integer only quantization
    converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
    converter.inference_input_type = tf.int8
    converter.inference_output_type = tf.int8
    # Provide a representative dataset to ensure we quantize correctly.
    converter.representative_dataset = representative_dataset
    model_tflite = converter.convert()

   # Save the model to disk
   open('default_tf/model0_1.tflite', "wb").write(model_tflite)

When i check tflite structure via netron i found that the exapandsDims operation is included as shown in the following image enter image description here

I already try to include

#include "tensorflow/lite/micro/all_ops_resolver.h"

to my sketch But did not resolve the problem and io also tried to include

#include "tensorflow/lite/micro/micro_mutable_op_resolver.h"
#include "tensorflow/lite/micro/kernels/micro_ops.h"
static tflite::MicroMutableOpResolver<1> micro_op_resolver;
void setup(){
  micro_op_resolver.AddExpandDims();
}

in this case i get a error:

   micro_op_resolver.AddExpandDims();
                     ^~~~~~~~~~~~~
exit status 1
'class tflite::MicroMutableOpResolver<1>' has no member named 'AddExpandDims'
3 Answers

I resolve the problem to downgrade the python version of tensorflow from 2.3.0 to 2.1.1 where i trained the model, in 2.1.1 for convert the model intessorflow-lite use reshape ops where its replaced in 2.3.0 with exapandsdims ops.

We ran into the same problem but found a solution that avoids downgrading Tensorflow (so we can use the latest Tensorflow 2.4.1). The Tensorflow 1D operations, e.g., Conv1D, are specialized versions of their higher order counterparts, e.g., Conv2D. Therefore, you can "upgrade" your 1D operations without sacrificing accuracy. The Tensorflow Lite Converter does the same thing but replaces the Conv1D with Conv2D AND an additional ExpandDims layer before that - hence the problem.

Here is the procedure:

  1. Adjust the input_shape for your network by adding another dimension of size 1, e.g., input_shape=(3,3,1) instead of input_shape=(3,3)
  2. Replace the 1D operations with their 2D counterpart (Conv1D to Conv2D, MaxPooling1D to MaxPooling2D)
  3. Adjust the kernel_size for your Conv2D layers, e.g., kernel_size=(3,1) instead of kernel_size=3
  4. Adjust the pool_size for the pooling layers (such as MaxPooling2D or GlobalAveragePooling2D), e.g., pool_size=(2,1) instead of pool_size=2
  5. Retrain the network
  6. Convert your model to Tensorflow Lite Micro

Your model after conversion

def get_model(n_timesteps, n_features, n_outputs): 
    model = Sequential()
    model.add(Conv2D(filters=32, kernel_size=(3,1), activation='relu', input_shape=(n_timesteps,n_features,1)))
    model.add(Conv2D(filters=32, kernel_size=(3,1), activation='relu'))
    model.add(Dropout(0.5))
    model.add(MaxPooling2D(pool_size=(2,1)))
    model.add(Conv2D(filters=16, kernel_size=(5,1), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2,1)))
    model.add(Flatten())  
    model.add(Dense(n_outputs, activation='softmax')) 
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy', tf.keras.metrics.Precision()])
    # fit network
    return model 

Downsides

  • You have to adjust your training data to account for the extra dimension
  • You have to adjust the input data for your network whenever you run inference
  • You have to retrain your network

We tried to avoid the adjustments of our training data and input data for inference by adding a Reshape layer right after the Input layer of your model. Unfortunately, this lead to another problem after pruning, int8 quantitating, and converting our model to Tensorflow Lite Micro. Without the reshaping everything works just fine.

Related