├── .gitignore ├── Podfile ├── README.md ├── buildozer.spec ├── main.py ├── model.py ├── model.tflite └── notebooks └── Create_TFLite_model.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | .buildozer 2 | .ipynb_checkpoints 3 | .vscode 4 | __pycache__ 5 | bin 6 | Pipfile 7 | 8 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '13.0' 2 | 3 | target 'myapp' do 4 | pod 'TensorFlowLiteObjC' 5 | end 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kivy Tensorflow Hello World 2 | 3 | This is a "Hello World" for running Tensorflow Lite on iOS, Android, MacOS, Windows and Linux using Python and Kivy. 4 | 5 | ## Create a Tensorflow Lite model 6 | 7 | You can use the Jupyter notebook in notebooks to create a Tensorflow Lite model file. A dummy example is provided for testing purposes. 8 | 9 | ## Install buildozer 10 | 11 | Install basic Python requirements (all platforms) 12 | 13 | ```bash 14 | pip install buildozer cython 15 | ``` 16 | 17 | Follow the instructions for your platform [here](https://pypi.org/project/buildozer/). 18 | 19 | ## MacOS, Windows and Linux 20 | 21 | ```bash 22 | pip install tensorflow numpy kivy 23 | python3 main.py 24 | ``` 25 | 26 | ## Android 27 | 28 | Currently you can only build for Android using `buildozer` on Linux. 29 | 30 | Use the included `buildozer.spec` file or make the following changes to one created by `buildozer init` 31 | 32 | ``` 33 | source.include_exts = py,png,jpg,kv,atlas,tflite 34 | requirements = python3,kivy,numpy 35 | android.api = 30 36 | android.minapi = 24 37 | android.gradle_dependencies = org.tensorflow:tensorflow-lite:+,org.tensorflow:tensorflow-lite-support:+ 38 | ``` 39 | 40 | Note that if your `tflite` model file is too big to be packaged with your APK, you will have to find some other way of getting it on to the device. If this is the case then change this line to ensure it is not included in the package. 41 | 42 | ``` 43 | source.include_exts = py,png,jpg,kv,atlas 44 | ``` 45 | 46 | Change the architecture you are building for to match that of your device or emulator 47 | 48 | ``` 49 | android.arch = x86 50 | ``` 51 | 52 | Build the APK 53 | 54 | ```bash 55 | buildozer android debug 56 | ``` 57 | 58 | and install it with 59 | 60 | ```bash 61 | adb install bin/myapp-0.1-x86-debug.apk 62 | ``` 63 | 64 | ## iOS 65 | 66 | Remember that you will need an Apple developer account to be able to install your app on a real iPhone. 67 | 68 | Install prerequisite system packages 69 | 70 | ```bash 71 | brew install cocoapods pkg-config autoconf automake 72 | ``` 73 | 74 | Install additional Python requirements 75 | 76 | ```bash 77 | pip install pbxproj cookiecutter 78 | ``` 79 | 80 | Build your app and install the Tensorflow Lite pod 81 | 82 | ```bash 83 | buildozer ios debug 84 | cd .buildozer/ios/platform/kivy-ios/myapp-ios/ 85 | cp YourApp/Podfile . 86 | pod install 87 | open -a Xcode myapp.xcworkspace 88 | ``` 89 | 90 | As indicated in the warning messages, you will need to make some changes to the project configuration. You can either do this by editing `myapp-ios\myapp.xcodeproj` or by editing the Build Settings for `myapp` in Xcode. Search for `GCC_PREPROCESSOR_DEFINITIONS` and add `$(inherited)` to the Debug target. Then repeat the process for `HEADER_SEARCH_PATHS`, `OTHER_LDFLAGS` and (possibly) `EXCLUDED_ARCHS[sdk=iphonesimulator*]` for all targets. Now you should be able to build and run your app. All being well you should see the following output (give or take rounding errors) 91 | 92 | ```python 93 | [[ 0.01647118, 1.0278152 , -0.7065112 , -1.0278157 , 0.12216613, 0.37980393, 0.5839217 , -0.04283606, -0.04240461, -0.58534086 ]] 94 | ``` 95 | 96 | Every time you build you will need to run `buildozer ios debug` and then build and deploy from Xcode. 97 | -------------------------------------------------------------------------------- /buildozer.spec: -------------------------------------------------------------------------------- 1 | [app] 2 | 3 | # (str) Title of your application 4 | title = My Application 5 | 6 | # (str) Package name 7 | package.name = myapp 8 | 9 | # (str) Package domain (needed for android/ios packaging) 10 | package.domain = org.test 11 | 12 | # (str) Source code where the main.py live 13 | source.dir = . 14 | 15 | # (list) Source files to include (let empty to include all the files) 16 | source.include_exts = py,png,jpg,kv,atlas,tflite 17 | 18 | # (list) List of inclusions using pattern matching 19 | #source.include_patterns = assets/*,images/*.png 20 | 21 | # (list) Source files to exclude (let empty to not exclude anything) 22 | #source.exclude_exts = spec 23 | 24 | # (list) List of directory to exclude (let empty to not exclude anything) 25 | #source.exclude_dirs = tests, bin, venv 26 | 27 | # (list) List of exclusions using pattern matching 28 | # Do not prefix with './' 29 | #source.exclude_patterns = license,images/*/*.jpg 30 | 31 | # (str) Application versioning (method 1) 32 | version = 0.1 33 | 34 | # (str) Application versioning (method 2) 35 | # version.regex = __version__ = ['"](.*)['"] 36 | # version.filename = %(source.dir)s/main.py 37 | 38 | # (list) Application requirements 39 | # comma separated e.g. requirements = sqlite3,kivy 40 | requirements = python3,kivy,numpy 41 | 42 | # (str) Custom source folders for requirements 43 | # Sets custom source for any requirements with recipes 44 | # requirements.source.kivy = ../../kivy 45 | 46 | # (str) Presplash of the application 47 | #presplash.filename = %(source.dir)s/data/presplash.png 48 | 49 | # (str) Icon of the application 50 | #icon.filename = %(source.dir)s/data/icon.png 51 | 52 | # (str) Supported orientation (one of landscape, sensorLandscape, portrait or all) 53 | orientation = portrait 54 | 55 | # (list) List of service to declare 56 | #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY 57 | 58 | # 59 | # OSX Specific 60 | # 61 | 62 | # 63 | # author = © Copyright Info 64 | 65 | # change the major version of python used by the app 66 | osx.python_version = 3 67 | 68 | # Kivy version to use 69 | osx.kivy_version = 1.9.1 70 | 71 | # 72 | # Android specific 73 | # 74 | 75 | # (bool) Indicate if the application should be fullscreen or not 76 | fullscreen = 0 77 | 78 | # (string) Presplash background color (for android toolchain) 79 | # Supported formats are: #RRGGBB #AARRGGBB or one of the following names: 80 | # red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, 81 | # darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, 82 | # olive, purple, silver, teal. 83 | #android.presplash_color = #FFFFFF 84 | 85 | # (string) Presplash animation using Lottie format. 86 | # see https://lottiefiles.com/ for examples and https://airbnb.design/lottie/ 87 | # for general documentation. 88 | # Lottie files can be created using various tools, like Adobe After Effect or Synfig. 89 | #android.presplash_lottie = "path/to/lottie/file.json" 90 | 91 | # (str) Adaptive icon of the application (used if Android API level is 26+ at runtime) 92 | #icon.adaptive_foreground.filename = %(source.dir)s/data/icon_fg.png 93 | #icon.adaptive_background.filename = %(source.dir)s/data/icon_bg.png 94 | 95 | # (list) Permissions 96 | #android.permissions = INTERNET 97 | 98 | # (list) features (adds uses-feature -tags to manifest) 99 | #android.features = android.hardware.usb.host 100 | 101 | # (int) Target Android API, should be as high as possible. 102 | android.api = 30 103 | 104 | # (int) Minimum API your APK / AAB will support. 105 | android.minapi = 24 106 | 107 | # (int) Android SDK version to use 108 | #android.sdk = 20 109 | 110 | # (str) Android NDK version to use 111 | #android.ndk = 23b 112 | 113 | # (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi. 114 | #android.ndk_api = 21 115 | 116 | # (bool) Use --private data storage (True) or --dir public storage (False) 117 | #android.private_storage = True 118 | 119 | # (str) Android NDK directory (if empty, it will be automatically downloaded.) 120 | #android.ndk_path = 121 | 122 | # (str) Android SDK directory (if empty, it will be automatically downloaded.) 123 | #android.sdk_path = 124 | 125 | # (str) ANT directory (if empty, it will be automatically downloaded.) 126 | #android.ant_path = 127 | 128 | # (bool) If True, then skip trying to update the Android sdk 129 | # This can be useful to avoid excess Internet downloads or save time 130 | # when an update is due and you just want to test/build your package 131 | # android.skip_update = False 132 | 133 | # (bool) If True, then automatically accept SDK license 134 | # agreements. This is intended for automation only. If set to False, 135 | # the default, you will be shown the license when first running 136 | # buildozer. 137 | # android.accept_sdk_license = False 138 | 139 | # (str) Android entry point, default is ok for Kivy-based app 140 | #android.entrypoint = org.kivy.android.PythonActivity 141 | 142 | # (str) Full name including package path of the Java class that implements Android Activity 143 | # use that parameter together with android.entrypoint to set custom Java class instead of PythonActivity 144 | #android.activity_class_name = org.kivy.android.PythonActivity 145 | 146 | # (str) Extra xml to write directly inside the element of AndroidManifest.xml 147 | # use that parameter to provide a filename from where to load your custom XML code 148 | #android.extra_manifest_xml = ./src/android/extra_manifest.xml 149 | 150 | # (str) Extra xml to write directly inside the tag of AndroidManifest.xml 151 | # use that parameter to provide a filename from where to load your custom XML arguments: 152 | #android.extra_manifest_application_arguments = ./src/android/extra_manifest_application_arguments.xml 153 | 154 | # (str) Full name including package path of the Java class that implements Python Service 155 | # use that parameter to set custom Java class instead of PythonService 156 | #android.service_class_name = org.kivy.android.PythonService 157 | 158 | # (str) Android app theme, default is ok for Kivy-based app 159 | # android.apptheme = "@android:style/Theme.NoTitleBar" 160 | 161 | # (list) Pattern to whitelist for the whole project 162 | #android.whitelist = 163 | 164 | # (str) Path to a custom whitelist file 165 | #android.whitelist_src = 166 | 167 | # (str) Path to a custom blacklist file 168 | #android.blacklist_src = 169 | 170 | # (list) List of Java .jar files to add to the libs so that pyjnius can access 171 | # their classes. Don't add jars that you do not need, since extra jars can slow 172 | # down the build process. Allows wildcards matching, for example: 173 | # OUYA-ODK/libs/*.jar 174 | #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar 175 | 176 | # (list) List of Java files to add to the android project (can be java or a 177 | # directory containing the files) 178 | #android.add_src = 179 | 180 | # (list) Android AAR archives to add 181 | #android.add_aars = 182 | 183 | # (list) Put these files or directories in the apk assets directory. 184 | # Either form may be used, and assets need not be in 'source.include_exts'. 185 | # 1) android.add_assets = source_asset_relative_path 186 | # 2) android.add_assets = source_asset_path:destination_asset_relative_path 187 | #android.add_assets = 188 | 189 | # (list) Gradle dependencies to add 190 | android.gradle_dependencies = org.tensorflow:tensorflow-lite:+,org.tensorflow:tensorflow-lite-support:+ 191 | 192 | # (bool) Enable AndroidX support. Enable when 'android.gradle_dependencies' 193 | # contains an 'androidx' package, or any package from Kotlin source. 194 | # android.enable_androidx requires android.api >= 28 195 | #android.enable_androidx = False 196 | 197 | # (list) add java compile options 198 | # this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option 199 | # see https://developer.android.com/studio/write/java8-support for further information 200 | # android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8" 201 | 202 | # (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies} 203 | # please enclose in double quotes 204 | # e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }" 205 | #android.add_gradle_repositories = 206 | 207 | # (list) packaging options to add 208 | # see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html 209 | # can be necessary to solve conflicts in gradle_dependencies 210 | # please enclose in double quotes 211 | # e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'" 212 | #android.add_packaging_options = 213 | 214 | # (list) Java classes to add as activities to the manifest. 215 | #android.add_activities = com.example.ExampleActivity 216 | 217 | # (str) OUYA Console category. Should be one of GAME or APP 218 | # If you leave this blank, OUYA support will not be enabled 219 | #android.ouya.category = GAME 220 | 221 | # (str) Filename of OUYA Console icon. It must be a 732x412 png image. 222 | #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png 223 | 224 | # (str) XML file to include as an intent filters in tag 225 | #android.manifest.intent_filters = 226 | 227 | # (str) launchMode to set for the main activity 228 | #android.manifest.launch_mode = standard 229 | 230 | # (list) Android additional libraries to copy into libs/armeabi 231 | #android.add_libs_armeabi = libs/android/*.so 232 | #android.add_libs_armeabi_v7a = libs/android-v7/*.so 233 | #android.add_libs_arm64_v8a = libs/android-v8/*.so 234 | #android.add_libs_x86 = libs/android-x86/*.so 235 | #android.add_libs_mips = libs/android-mips/*.so 236 | 237 | # (bool) Indicate whether the screen should stay on 238 | # Don't forget to add the WAKE_LOCK permission if you set this to True 239 | #android.wakelock = False 240 | 241 | # (list) Android application meta-data to set (key=value format) 242 | #android.meta_data = 243 | 244 | # (list) Android library project to add (will be added in the 245 | # project.properties automatically.) 246 | #android.library_references = 247 | 248 | # (list) Android shared libraries which will be added to AndroidManifest.xml using tag 249 | #android.uses_library = 250 | 251 | # (str) Android logcat filters to use 252 | #android.logcat_filters = *:S python:D 253 | 254 | # (bool) Android logcat only display log for activity's pid 255 | #android.logcat_pid_only = False 256 | 257 | # (str) Android additional adb arguments 258 | #android.adb_args = -H host.docker.internal 259 | 260 | # (bool) Copy library instead of making a libpymodules.so 261 | #android.copy_libs = 1 262 | 263 | # (list) The Android archs to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64 264 | # In past, was `android.arch` as we weren't supporting builds for multiple archs at the same time. 265 | android.archs = x86_64 266 | 267 | # (int) overrides automatic versionCode computation (used in build.gradle) 268 | # this is not the same as app version and should only be edited if you know what you're doing 269 | # android.numeric_version = 1 270 | 271 | # (bool) enables Android auto backup feature (Android API >=23) 272 | android.allow_backup = True 273 | 274 | # (str) XML file for custom backup rules (see official auto backup documentation) 275 | # android.backup_rules = 276 | 277 | # (str) If you need to insert variables into your AndroidManifest.xml file, 278 | # you can do so with the manifestPlaceholders property. 279 | # This property takes a map of key-value pairs. (via a string) 280 | # Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"] 281 | # android.manifest_placeholders = [:] 282 | 283 | # (bool) disables the compilation of py to pyc/pyo files when packaging 284 | # android.no-compile-pyo = True 285 | 286 | # (str) The format used to package the app for release mode (aab or apk or aar). 287 | # android.release_artifact = aab 288 | 289 | # (str) The format used to package the app for debug mode (apk or aar). 290 | # android.debug_artifact = apk 291 | 292 | # 293 | # Python for android (p4a) specific 294 | # 295 | 296 | # (str) python-for-android URL to use for checkout 297 | #p4a.url = 298 | 299 | # (str) python-for-android fork to use in case if p4a.url is not specified, defaults to upstream (kivy) 300 | #p4a.fork = kivy 301 | 302 | # (str) python-for-android branch to use, defaults to master 303 | #p4a.branch = master 304 | 305 | # (str) python-for-android specific commit to use, defaults to HEAD, must be within p4a.branch 306 | #p4a.commit = HEAD 307 | 308 | # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) 309 | #p4a.source_dir = 310 | 311 | # (str) The directory in which python-for-android should look for your own build recipes (if any) 312 | #p4a.local_recipes = 313 | 314 | # (str) Filename to the hook for p4a 315 | #p4a.hook = 316 | 317 | # (str) Bootstrap to use for android builds 318 | # p4a.bootstrap = sdl2 319 | 320 | # (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask) 321 | #p4a.port = 322 | 323 | # Control passing the --use-setup-py vs --ignore-setup-py to p4a 324 | # "in the future" --use-setup-py is going to be the default behaviour in p4a, right now it is not 325 | # Setting this to false will pass --ignore-setup-py, true will pass --use-setup-py 326 | # NOTE: this is general setuptools integration, having pyproject.toml is enough, no need to generate 327 | # setup.py if you're using Poetry, but you need to add "toml" to source.include_exts. 328 | #p4a.setup_py = false 329 | 330 | # (str) extra command line arguments to pass when invoking pythonforandroid.toolchain 331 | #p4a.extra_args = 332 | 333 | 334 | # 335 | # iOS specific 336 | # 337 | 338 | # (str) Path to a custom kivy-ios folder 339 | #ios.kivy_ios_dir = ../kivy-ios 340 | # Alternately, specify the URL and branch of a git checkout: 341 | ios.kivy_ios_url = https://github.com/kivy/kivy-ios 342 | ios.kivy_ios_branch = master 343 | 344 | # Another platform dependency: ios-deploy 345 | # Uncomment to use a custom checkout 346 | #ios.ios_deploy_dir = ../ios_deploy 347 | # Or specify URL and branch 348 | ios.ios_deploy_url = https://github.com/phonegap/ios-deploy 349 | ios.ios_deploy_branch = 1.10.0 350 | 351 | # (bool) Whether or not to sign the code 352 | ios.codesign.allowed = false 353 | 354 | # (str) Name of the certificate to use for signing the debug version 355 | # Get a list of available identities: buildozer ios list_identities 356 | #ios.codesign.debug = "iPhone Developer: ()" 357 | 358 | # (str) The development team to use for signing the debug version 359 | #ios.codesign.development_team.debug = 360 | 361 | # (str) Name of the certificate to use for signing the release version 362 | #ios.codesign.release = %(ios.codesign.debug)s 363 | 364 | # (str) The development team to use for signing the release version 365 | #ios.codesign.development_team.release = 366 | 367 | # (str) URL pointing to .ipa file to be installed 368 | # This option should be defined along with `display_image_url` and `full_size_image_url` options. 369 | #ios.manifest.app_url = 370 | 371 | # (str) URL pointing to an icon (57x57px) to be displayed during download 372 | # This option should be defined along with `app_url` and `full_size_image_url` options. 373 | #ios.manifest.display_image_url = 374 | 375 | # (str) URL pointing to a large icon (512x512px) to be used by iTunes 376 | # This option should be defined along with `app_url` and `display_image_url` options. 377 | #ios.manifest.full_size_image_url = 378 | 379 | 380 | [buildozer] 381 | 382 | # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) 383 | log_level = 2 384 | 385 | # (int) Display warning if buildozer is run as root (0 = False, 1 = True) 386 | warn_on_root = 1 387 | 388 | # (str) Path to build artifact storage, absolute or relative to spec file 389 | # build_dir = ./.buildozer 390 | 391 | # (str) Path to build output (i.e. .apk, .aab, .ipa) storage 392 | # bin_dir = ./bin 393 | 394 | # ----------------------------------------------------------------------------- 395 | # List as sections 396 | # 397 | # You can define all the "list" as [section:key]. 398 | # Each line will be considered as a option to the list. 399 | # Let's take [app] / source.exclude_patterns. 400 | # Instead of doing: 401 | # 402 | #[app] 403 | #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* 404 | # 405 | # This can be translated into: 406 | # 407 | #[app:source.exclude_patterns] 408 | #license 409 | #data/audio/*.wav 410 | #data/images/original/* 411 | # 412 | 413 | 414 | # ----------------------------------------------------------------------------- 415 | # Profiles 416 | # 417 | # You can extend section / key with a profile 418 | # For example, you want to deploy a demo version of your application without 419 | # HD content. You could first change the title to add "(demo)" in the name 420 | # and extend the excluded directories to remove the HD content. 421 | # 422 | #[app@demo] 423 | #title = My Application (demo) 424 | # 425 | #[app:source.exclude_patterns@demo] 426 | #images/hd/* 427 | # 428 | # Then, invoke the command line with the "demo" profile: 429 | # 430 | #buildozer --profile demo android debug 431 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import kivy 3 | import numpy as np 4 | from kivy.app import App 5 | from kivy.uix.label import Label 6 | from model import TensorFlowModel 7 | 8 | 9 | class MyApp(App): 10 | 11 | def build(self): 12 | model = TensorFlowModel() 13 | model.load(os.path.join(os.getcwd(), 'model.tflite')) 14 | np.random.seed(42) 15 | x = np.array(np.random.random_sample((1, 28, 28)), np.float32) 16 | y = model.pred(x) 17 | # result should be 18 | # 0.01647118, 1.0278152 , -0.7065112 , -1.0278157 , 0.12216613, 19 | # 0.37980393, 0.5839217 , -0.04283606, -0.04240461, -0.58534086 20 | return Label(text=f'{y}') 21 | 22 | 23 | if __name__ == '__main__': 24 | MyApp().run() -------------------------------------------------------------------------------- /model.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from kivy.utils import platform 3 | 4 | if platform == 'android': 5 | from jnius import autoclass 6 | 7 | File = autoclass('java.io.File') 8 | Interpreter = autoclass('org.tensorflow.lite.Interpreter') 9 | InterpreterOptions = autoclass('org.tensorflow.lite.Interpreter$Options') 10 | Tensor = autoclass('org.tensorflow.lite.Tensor') 11 | DataType = autoclass('org.tensorflow.lite.DataType') 12 | TensorBuffer = autoclass( 13 | 'org.tensorflow.lite.support.tensorbuffer.TensorBuffer') 14 | ByteBuffer = autoclass('java.nio.ByteBuffer') 15 | 16 | class TensorFlowModel(): 17 | def load(self, model_filename, num_threads=None): 18 | model = File(model_filename) 19 | options = InterpreterOptions() 20 | if num_threads is not None: 21 | options.setNumThreads(num_threads) 22 | self.interpreter = Interpreter(model, options) 23 | self.allocate_tensors() 24 | 25 | def allocate_tensors(self): 26 | self.interpreter.allocateTensors() 27 | self.input_shape = self.interpreter.getInputTensor(0).shape() 28 | self.output_shape = self.interpreter.getOutputTensor(0).shape() 29 | self.output_type = self.interpreter.getOutputTensor(0).dataType() 30 | 31 | def get_input_shape(self): 32 | return self.input_shape 33 | 34 | def resize_input(self, shape): 35 | if self.input_shape != shape: 36 | self.interpreter.resizeInput(0, shape) 37 | self.allocate_tensors() 38 | 39 | def pred(self, x): 40 | # assumes one input and one output for now 41 | input = ByteBuffer.wrap(x.tobytes()) 42 | output = TensorBuffer.createFixedSize(self.output_shape, 43 | self.output_type) 44 | self.interpreter.run(input, output.getBuffer().rewind()) 45 | return np.reshape(np.array(output.getFloatArray()), 46 | self.output_shape) 47 | 48 | elif platform == 'ios': 49 | from pyobjus import autoclass, objc_arr 50 | from ctypes import c_float, cast, POINTER 51 | 52 | NSString = autoclass('NSString') 53 | NSError = autoclass('NSError') 54 | Interpreter = autoclass('TFLInterpreter') 55 | InterpreterOptions = autoclass('TFLInterpreterOptions') 56 | NSData = autoclass('NSData') 57 | NSMutableArray = autoclass("NSMutableArray") 58 | 59 | class TensorFlowModel: 60 | def load(self, model_filename, num_threads=None): 61 | self.error = NSError.alloc() 62 | model = NSString.stringWithUTF8String_(model_filename) 63 | options = InterpreterOptions.alloc().init() 64 | if num_threads is not None: 65 | options.numberOfThreads = num_threads 66 | self.interpreter = Interpreter.alloc( 67 | ).initWithModelPath_options_error_(model, options, self.error) 68 | self.allocate_tensors() 69 | 70 | def allocate_tensors(self): 71 | self.interpreter.allocateTensorsWithError_(self.error) 72 | self.input_shape = self.interpreter.inputTensorAtIndex_error_( 73 | 0, self.error).shapeWithError_(self.error) 74 | self.input_shape = [ 75 | self.input_shape.objectAtIndex_(_).intValue() 76 | for _ in range(self.input_shape.count()) 77 | ] 78 | self.output_shape = self.interpreter.outputTensorAtIndex_error_( 79 | 0, self.error).shapeWithError_(self.error) 80 | self.output_shape = [ 81 | self.output_shape.objectAtIndex_(_).intValue() 82 | for _ in range(self.output_shape.count()) 83 | ] 84 | self.output_type = self.interpreter.outputTensorAtIndex_error_( 85 | 0, self.error).dataType 86 | 87 | def get_input_shape(self): 88 | return self.input_shape 89 | 90 | def resize_input(self, shape): 91 | if self.input_shape != shape: 92 | # workaround as objc_arr doesn't work as expected on iPhone 93 | array = NSMutableArray.new() 94 | for x in shape: 95 | array.addObject_(x) 96 | self.interpreter.resizeInputTensorAtIndex_toShape_error_( 97 | 0, array, self.error) 98 | self.allocate_tensors() 99 | 100 | def pred(self, x): 101 | # assumes one input and one output for now 102 | bytestr = x.tobytes() 103 | # must cast to ctype._SimpleCData so that pyobjus passes pointer 104 | floatbuf = cast(bytestr, POINTER(c_float)).contents 105 | data = NSData.dataWithBytes_length_(floatbuf, len(bytestr)) 106 | print(dir(self.interpreter)) 107 | self.interpreter.copyData_toInputTensor_error_( 108 | data, self.interpreter.inputTensorAtIndex_error_( 109 | 0, self.error), self.error) 110 | self.interpreter.invokeWithError_(self.error) 111 | output = self.interpreter.outputTensorAtIndex_error_( 112 | 0, self.error).dataWithError_(self.error).bytes() 113 | # have to do this to avoid memory leaks... 114 | while data.retainCount() > 1: 115 | data.release() 116 | return np.reshape( 117 | np.frombuffer( 118 | (c_float * np.prod(self.output_shape)).from_address( 119 | output.arg_ref), c_float), self.output_shape) 120 | 121 | else: 122 | import tensorflow as tf 123 | 124 | class TensorFlowModel: 125 | def load(self, model_filename, num_threads=None): 126 | self.interpreter = tf.lite.Interpreter(model_filename, 127 | num_threads=num_threads) 128 | self.interpreter.allocate_tensors() 129 | 130 | def resize_input(self, shape): 131 | if list(self.get_input_shape()) != shape: 132 | self.interpreter.resize_tensor_input(0, shape) 133 | self.interpreter.allocate_tensors() 134 | 135 | def get_input_shape(self): 136 | return self.interpreter.get_input_details()[0]['shape'] 137 | 138 | def pred(self, x): 139 | # assumes one input and one output for now 140 | self.interpreter.set_tensor( 141 | self.interpreter.get_input_details()[0]['index'], x) 142 | self.interpreter.invoke() 143 | return self.interpreter.get_tensor( 144 | self.interpreter.get_output_details()[0]['index']) -------------------------------------------------------------------------------- /model.tflite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teticio/kivy-tensorflow-helloworld/d4946b9f90a13a25b6856a23da0815509ec95bf8/model.tflite -------------------------------------------------------------------------------- /notebooks/Create_TFLite_model.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### Dummy model" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 30, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "import tensorflow as tf" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 36, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "model = tf.keras.models.Sequential([\n", 26 | " tf.keras.layers.Flatten(input_shape=(28, 28)),\n", 27 | " tf.keras.layers.Dense(10)\n", 28 | "])" 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "metadata": {}, 34 | "source": [ 35 | "### Make a random prediction for testing purposes" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 37, 41 | "metadata": {}, 42 | "outputs": [ 43 | { 44 | "data": { 45 | "text/plain": [ 46 | "array([[[0.37454012, 0.9507143 , 0.7319939 , 0.5986585 , 0.15601864,\n", 47 | " 0.15599452, 0.05808361, 0.8661761 , 0.601115 , 0.7080726 ,\n", 48 | " 0.02058449, 0.96990985, 0.83244264, 0.21233912, 0.18182497,\n", 49 | " 0.1834045 , 0.30424225, 0.52475643, 0.43194503, 0.29122913,\n", 50 | " 0.6118529 , 0.13949387, 0.29214466, 0.36636186, 0.45606998,\n", 51 | " 0.785176 , 0.19967379, 0.5142344 ],\n", 52 | " [0.59241456, 0.04645041, 0.60754484, 0.17052412, 0.06505159,\n", 53 | " 0.94888556, 0.965632 , 0.80839735, 0.30461377, 0.09767211,\n", 54 | " 0.684233 , 0.4401525 , 0.12203824, 0.4951769 , 0.03438852,\n", 55 | " 0.9093204 , 0.25877997, 0.66252226, 0.31171107, 0.52006805,\n", 56 | " 0.54671025, 0.18485446, 0.96958464, 0.77513283, 0.93949896,\n", 57 | " 0.89482737, 0.5979 , 0.9218742 ],\n", 58 | " [0.08849251, 0.19598286, 0.04522729, 0.32533032, 0.3886773 ,\n", 59 | " 0.27134904, 0.8287375 , 0.35675332, 0.2809345 , 0.54269606,\n", 60 | " 0.14092423, 0.802197 , 0.07455064, 0.9868869 , 0.77224475,\n", 61 | " 0.19871569, 0.00552212, 0.81546146, 0.7068573 , 0.7290072 ,\n", 62 | " 0.77127033, 0.07404465, 0.35846573, 0.11586906, 0.86310345,\n", 63 | " 0.6232981 , 0.33089802, 0.06355835],\n", 64 | " [0.31098232, 0.32518333, 0.72960615, 0.63755745, 0.88721275,\n", 65 | " 0.47221494, 0.11959425, 0.7132448 , 0.76078504, 0.5612772 ,\n", 66 | " 0.7709672 , 0.4937956 , 0.52273285, 0.42754102, 0.02541913,\n", 67 | " 0.10789143, 0.03142919, 0.6364104 , 0.31435597, 0.5085707 ,\n", 68 | " 0.9075665 , 0.24929222, 0.41038293, 0.75555116, 0.22879817,\n", 69 | " 0.07697991, 0.28975144, 0.16122128],\n", 70 | " [0.92969763, 0.80812037, 0.6334038 , 0.8714606 , 0.8036721 ,\n", 71 | " 0.18657006, 0.892559 , 0.5393422 , 0.80744016, 0.8960913 ,\n", 72 | " 0.31800348, 0.11005192, 0.22793517, 0.42710778, 0.81801474,\n", 73 | " 0.8607306 , 0.00695213, 0.5107473 , 0.417411 , 0.22210781,\n", 74 | " 0.11986537, 0.33761516, 0.9429097 , 0.32320294, 0.5187906 ,\n", 75 | " 0.70301896, 0.3636296 , 0.9717821 ],\n", 76 | " [0.9624473 , 0.2517823 , 0.4972485 , 0.30087832, 0.2848405 ,\n", 77 | " 0.03688695, 0.6095643 , 0.50267905, 0.05147875, 0.27864647,\n", 78 | " 0.9082659 , 0.23956189, 0.14489487, 0.48945275, 0.9856505 ,\n", 79 | " 0.24205527, 0.67213553, 0.7616196 , 0.23763755, 0.72821635,\n", 80 | " 0.36778313, 0.6323058 , 0.6335297 , 0.5357747 , 0.09028977,\n", 81 | " 0.8353025 , 0.32078007, 0.1865185 ],\n", 82 | " [0.04077514, 0.590893 , 0.6775644 , 0.01658783, 0.51209307,\n", 83 | " 0.22649577, 0.6451728 , 0.17436643, 0.69093776, 0.38673535,\n", 84 | " 0.93672997, 0.13752094, 0.34106636, 0.11347352, 0.92469364,\n", 85 | " 0.87733936, 0.25794163, 0.65998405, 0.8172222 , 0.5552008 ,\n", 86 | " 0.52965057, 0.24185228, 0.09310277, 0.8972158 , 0.90041804,\n", 87 | " 0.63310146, 0.3390298 , 0.34920958],\n", 88 | " [0.72595567, 0.8971103 , 0.88708645, 0.7798755 , 0.64203167,\n", 89 | " 0.08413997, 0.16162871, 0.8985542 , 0.60642904, 0.00919705,\n", 90 | " 0.10147154, 0.66350174, 0.00506158, 0.16080806, 0.5487338 ,\n", 91 | " 0.6918952 , 0.65196127, 0.22426932, 0.71217924, 0.23724909,\n", 92 | " 0.3253997 , 0.74649143, 0.6496329 , 0.84922343, 0.6576129 ,\n", 93 | " 0.5683086 , 0.09367477, 0.3677158 ],\n", 94 | " [0.26520237, 0.24398965, 0.97301054, 0.39309773, 0.8920466 ,\n", 95 | " 0.6311386 , 0.7948113 , 0.5026371 , 0.5769039 , 0.49251768,\n", 96 | " 0.19524299, 0.7224521 , 0.28077236, 0.02431597, 0.6454723 ,\n", 97 | " 0.17711067, 0.9404586 , 0.9539286 , 0.91486436, 0.3701587 ,\n", 98 | " 0.01545662, 0.92831856, 0.42818415, 0.96665484, 0.96361995,\n", 99 | " 0.85300946, 0.29444888, 0.38509774],\n", 100 | " [0.8511367 , 0.316922 , 0.16949275, 0.55680126, 0.9361548 ,\n", 101 | " 0.6960298 , 0.57006115, 0.09717649, 0.6150072 , 0.99005383,\n", 102 | " 0.14008401, 0.5183297 , 0.8773731 , 0.7407686 , 0.69701576,\n", 103 | " 0.7024841 , 0.35949114, 0.29359186, 0.80936116, 0.8101134 ,\n", 104 | " 0.86707234, 0.91324055, 0.5113424 , 0.5015163 , 0.7982952 ,\n", 105 | " 0.6499639 , 0.7019669 , 0.7957927 ],\n", 106 | " [0.89000535, 0.33799517, 0.37558296, 0.09398194, 0.57828015,\n", 107 | " 0.03594228, 0.46559802, 0.5426446 , 0.28654125, 0.59083325,\n", 108 | " 0.03050025, 0.03734819, 0.82260054, 0.36019063, 0.12706052,\n", 109 | " 0.52224326, 0.76999354, 0.21582103, 0.6228905 , 0.08534747,\n", 110 | " 0.05168172, 0.5313546 , 0.5406351 , 0.6374299 , 0.7260913 ,\n", 111 | " 0.9758521 , 0.5163003 , 0.32295647],\n", 112 | " [0.7951862 , 0.27083224, 0.43897143, 0.07845638, 0.02535074,\n", 113 | " 0.9626484 , 0.8359801 , 0.69597423, 0.40895295, 0.17329432,\n", 114 | " 0.15643704, 0.2502429 , 0.54922664, 0.7145959 , 0.6601974 ,\n", 115 | " 0.2799339 , 0.9548653 , 0.7378969 , 0.5543541 , 0.61172074,\n", 116 | " 0.41960007, 0.24773099, 0.35597268, 0.7578461 , 0.01439349,\n", 117 | " 0.11607264, 0.04600264, 0.0407288 ],\n", 118 | " [0.8554606 , 0.70365787, 0.47417384, 0.09783416, 0.49161586,\n", 119 | " 0.47347176, 0.17320187, 0.43385166, 0.39850473, 0.6158501 ,\n", 120 | " 0.6350936 , 0.04530401, 0.37461263, 0.6258599 , 0.5031363 ,\n", 121 | " 0.85648984, 0.6586936 , 0.16293442, 0.07056875, 0.6424193 ,\n", 122 | " 0.02651131, 0.58577555, 0.94023025, 0.5754742 , 0.3881699 ,\n", 123 | " 0.6432882 , 0.45825288, 0.5456168 ],\n", 124 | " [0.9414648 , 0.38610265, 0.9611906 , 0.9053506 , 0.19579114,\n", 125 | " 0.0693613 , 0.100778 , 0.01822183, 0.09444296, 0.68300676,\n", 126 | " 0.07118865, 0.31897563, 0.84487534, 0.02327194, 0.8144685 ,\n", 127 | " 0.28185478, 0.11816483, 0.6967372 , 0.62894285, 0.87747204,\n", 128 | " 0.73507106, 0.8034809 , 0.28203458, 0.17743954, 0.75061476,\n", 129 | " 0.80683476, 0.99050516, 0.41261768],\n", 130 | " [0.3720181 , 0.77641296, 0.34080353, 0.93075734, 0.85841274,\n", 131 | " 0.42899403, 0.75087106, 0.7545429 , 0.10312387, 0.9025529 ,\n", 132 | " 0.50525236, 0.82645744, 0.3200496 , 0.89552325, 0.38920167,\n", 133 | " 0.01083765, 0.905382 , 0.09128667, 0.31931365, 0.950062 ,\n", 134 | " 0.9506071 , 0.57343787, 0.6318372 , 0.44844553, 0.29321077,\n", 135 | " 0.32866454, 0.67251843, 0.75237453],\n", 136 | " [0.79157907, 0.78961813, 0.0912061 , 0.4944203 , 0.05755876,\n", 137 | " 0.5495289 , 0.4415305 , 0.8877042 , 0.350915 , 0.11706702,\n", 138 | " 0.14299168, 0.7615106 , 0.61821806, 0.10112268, 0.0841068 ,\n", 139 | " 0.70096916, 0.072763 , 0.8218601 , 0.7062422 , 0.08134878,\n", 140 | " 0.08483771, 0.98663956, 0.3742708 , 0.37064216, 0.8127996 ,\n", 141 | " 0.9472486 , 0.9860011 , 0.7533782 ],\n", 142 | " [0.3762596 , 0.08350071, 0.77714694, 0.55840427, 0.42422202,\n", 143 | " 0.90635437, 0.11119748, 0.49262512, 0.01135364, 0.46866065,\n", 144 | " 0.05630327, 0.11881792, 0.11752625, 0.6492103 , 0.7460449 ,\n", 145 | " 0.5833688 , 0.96217257, 0.37487057, 0.2857121 , 0.8685991 ,\n", 146 | " 0.22359584, 0.96322256, 0.01215447, 0.96987885, 0.04315991,\n", 147 | " 0.89114314, 0.5277011 , 0.9929648 ],\n", 148 | " [0.07379656, 0.5538543 , 0.96930254, 0.5230979 , 0.62939864,\n", 149 | " 0.6957487 , 0.45454106, 0.62755805, 0.5843143 , 0.90115803,\n", 150 | " 0.04544638, 0.28096318, 0.9504115 , 0.8902638 , 0.45565677,\n", 151 | " 0.6201326 , 0.27738118, 0.18812115, 0.46369842, 0.35335222,\n", 152 | " 0.58365613, 0.07773463, 0.9743948 , 0.98621076, 0.6981617 ,\n", 153 | " 0.5360964 , 0.3095276 , 0.81379503],\n", 154 | " [0.6847312 , 0.16261694, 0.9109272 , 0.82253724, 0.9497999 ,\n", 155 | " 0.7257195 , 0.6134152 , 0.41824305, 0.93272847, 0.8660639 ,\n", 156 | " 0.04521867, 0.02636698, 0.37646335, 0.8105533 , 0.98727614,\n", 157 | " 0.1504169 , 0.5941307 , 0.38089085, 0.9699144 , 0.8421189 ,\n", 158 | " 0.8383287 , 0.46869317, 0.4148195 , 0.27340707, 0.0563755 ,\n", 159 | " 0.8647224 , 0.812901 , 0.99971765],\n", 160 | " [0.9966368 , 0.5554317 , 0.7689874 , 0.94476575, 0.8496474 ,\n", 161 | " 0.2473481 , 0.45054415, 0.12915942, 0.954051 , 0.60617465,\n", 162 | " 0.2286428 , 0.67170066, 0.61812824, 0.35816273, 0.11355759,\n", 163 | " 0.6715732 , 0.5203077 , 0.77231836, 0.5201635 , 0.8521815 ,\n", 164 | " 0.5519068 , 0.560938 , 0.8766536 , 0.40348285, 0.13401523,\n", 165 | " 0.02878268, 0.75513726, 0.62030953],\n", 166 | " [0.70407975, 0.21296416, 0.13637148, 0.01454467, 0.35058755,\n", 167 | " 0.58991766, 0.39224404, 0.43747494, 0.9041587 , 0.34825546,\n", 168 | " 0.5139895 , 0.783653 , 0.3965428 , 0.6220867 , 0.8623637 ,\n", 169 | " 0.94952065, 0.14707348, 0.92658764, 0.4921163 , 0.2582444 ,\n", 170 | " 0.45913577, 0.98003256, 0.49261808, 0.32875162, 0.63340086,\n", 171 | " 0.24014562, 0.07586333, 0.12887973],\n", 172 | " [0.12804584, 0.15190269, 0.13882717, 0.64087474, 0.18188009,\n", 173 | " 0.34566727, 0.8967884 , 0.47396165, 0.6675577 , 0.17231987,\n", 174 | " 0.19228902, 0.04086862, 0.16893506, 0.27859035, 0.17701049,\n", 175 | " 0.08870254, 0.12063587, 0.46077877, 0.20633371, 0.36426985,\n", 176 | " 0.50341725, 0.6903948 , 0.03931214, 0.7994104 , 0.62790036,\n", 177 | " 0.08175904, 0.8735786 , 0.9208724 ],\n", 178 | " [0.06107796, 0.27687764, 0.8062013 , 0.74825966, 0.18452102,\n", 179 | " 0.20934932, 0.3704721 , 0.484523 , 0.6182548 , 0.36891365,\n", 180 | " 0.46253473, 0.7474709 , 0.0366832 , 0.25243694, 0.7133496 ,\n", 181 | " 0.8952068 , 0.51167744, 0.5321135 , 0.10717201, 0.44741237,\n", 182 | " 0.5326173 , 0.2424705 , 0.26924324, 0.37728417, 0.0200712 ,\n", 183 | " 0.32207915, 0.21144801, 0.32749736],\n", 184 | " [0.11976213, 0.8905273 , 0.59359246, 0.6791023 , 0.7891712 ,\n", 185 | " 0.4984422 , 0.08692029, 0.5371065 , 0.5868411 , 0.74543947,\n", 186 | " 0.43165955, 0.1275803 , 0.2837759 , 0.3630823 , 0.64591724,\n", 187 | " 0.5707783 , 0.3560967 , 0.9865152 , 0.6057748 , 0.2372268 ,\n", 188 | " 0.10178247, 0.15285914, 0.24595773, 0.16068137, 0.18656702,\n", 189 | " 0.28509516, 0.1733736 , 0.8967654 ],\n", 190 | " [0.08023375, 0.5245114 , 0.4103968 , 0.9823786 , 0.1120389 ,\n", 191 | " 0.3978556 , 0.96947044, 0.8655071 , 0.8170721 , 0.25790283,\n", 192 | " 0.17088759, 0.66864324, 0.929376 , 0.5567629 , 0.5716127 ,\n", 193 | " 0.27997908, 0.7694929 , 0.18704374, 0.32367924, 0.42543644,\n", 194 | " 0.5076104 , 0.24240974, 0.11483683, 0.61062 , 0.28863055,\n", 195 | " 0.5812382 , 0.15436271, 0.4811401 ],\n", 196 | " [0.53258944, 0.05182354, 0.33660427, 0.13441467, 0.06337497,\n", 197 | " 0.98996025, 0.32235384, 0.8098745 , 0.25464067, 0.6815027 ,\n", 198 | " 0.76022786, 0.59563875, 0.47157618, 0.41184092, 0.34886828,\n", 199 | " 0.92952913, 0.8306194 , 0.9650269 , 0.12429722, 0.73086745,\n", 200 | " 0.9383405 , 0.18123306, 0.06649627, 0.74112064, 0.57447314,\n", 201 | " 0.84182876, 0.13977237, 0.7952673 ],\n", 202 | " [0.20162731, 0.16365594, 0.1642658 , 0.8145747 , 0.6651972 ,\n", 203 | " 0.52306545, 0.35883048, 0.87720054, 0.39244512, 0.8165994 ,\n", 204 | " 0.4391349 , 0.37694442, 0.46267977, 0.30137786, 0.7476094 ,\n", 205 | " 0.5027204 , 0.23221269, 0.8995746 , 0.38389122, 0.5435529 ,\n", 206 | " 0.9064721 , 0.624238 , 0.11689804, 0.93983215, 0.6277081 ,\n", 207 | " 0.33490562, 0.13927208, 0.7940252 ],\n", 208 | " [0.6200728 , 0.5334611 , 0.8938926 , 0.7885972 , 0.15167488,\n", 209 | " 0.31172207, 0.24848914, 0.7439463 , 0.03353243, 0.56988966,\n", 210 | " 0.7624587 , 0.8767656 , 0.34208176, 0.8212573 , 0.11063173,\n", 211 | " 0.8464523 , 0.12748866, 0.39728728, 0.7972954 , 0.14991742,\n", 212 | " 0.2292514 , 0.72225255, 0.72003657, 0.6411476 , 0.69394845,\n", 213 | " 0.54272443, 0.25179905, 0.345696 ]]], dtype=float32)" 214 | ] 215 | }, 216 | "execution_count": 37, 217 | "metadata": {}, 218 | "output_type": "execute_result" 219 | } 220 | ], 221 | "source": [ 222 | "import numpy as np\n", 223 | "np.random.seed(42)\n", 224 | "x = np.array(np.random.random_sample((1, 28, 28)), np.float32)\n", 225 | "x" 226 | ] 227 | }, 228 | { 229 | "cell_type": "code", 230 | "execution_count": 40, 231 | "metadata": {}, 232 | "outputs": [ 233 | { 234 | "data": { 235 | "text/plain": [ 236 | "array([[ 0.01647118, 1.0278152 , -0.7065112 , -1.0278157 , 0.12216613,\n", 237 | " 0.37980393, 0.5839217 , -0.04283606, -0.04240461, -0.58534086]],\n", 238 | " dtype=float32)" 239 | ] 240 | }, 241 | "execution_count": 40, 242 | "metadata": {}, 243 | "output_type": "execute_result" 244 | } 245 | ], 246 | "source": [ 247 | "model.predict(x)" 248 | ] 249 | }, 250 | { 251 | "cell_type": "markdown", 252 | "metadata": {}, 253 | "source": [ 254 | "### Create a TFLite model" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": 41, 260 | "metadata": {}, 261 | "outputs": [ 262 | { 263 | "name": "stdout", 264 | "output_type": "stream", 265 | "text": [ 266 | "INFO:tensorflow:Assets written to: /tmp/tmpqx3v7ldz/assets\n" 267 | ] 268 | }, 269 | { 270 | "name": "stderr", 271 | "output_type": "stream", 272 | "text": [ 273 | "INFO:tensorflow:Assets written to: /tmp/tmpqx3v7ldz/assets\n" 274 | ] 275 | } 276 | ], 277 | "source": [ 278 | "converter = tf.lite.TFLiteConverter.from_keras_model(model)\n", 279 | "tflite_model = converter.convert()\n", 280 | "with open('../model.tflite', 'wb') as f:\n", 281 | " f.write(tflite_model)" 282 | ] 283 | }, 284 | { 285 | "cell_type": "code", 286 | "execution_count": null, 287 | "metadata": {}, 288 | "outputs": [], 289 | "source": [] 290 | } 291 | ], 292 | "metadata": { 293 | "kernelspec": { 294 | "display_name": "Python 3", 295 | "language": "python", 296 | "name": "python3" 297 | }, 298 | "language_info": { 299 | "codemirror_mode": { 300 | "name": "ipython", 301 | "version": 3 302 | }, 303 | "file_extension": ".py", 304 | "mimetype": "text/x-python", 305 | "name": "python", 306 | "nbconvert_exporter": "python", 307 | "pygments_lexer": "ipython3", 308 | "version": "3.8.5" 309 | } 310 | }, 311 | "nbformat": 4, 312 | "nbformat_minor": 4 313 | } 314 | --------------------------------------------------------------------------------