В статье поговорим как обучить несложную CNN сеть с помощью tensorflow, конвертировать готовое с помощью tensoflow-lite и перенести на мобильное устройство под управлением android.
Описывается личный опыт автора, поэтому нет претензий на всеохватывающее руководство.
Для старта возьмем не слишком сложную нейросеть, код которой приведен ниже:
Краткое пояснение по коду. После импорта пакетов tensorflow,numpy мы формируем модель нейросети. Модель представляет из себя только один слой и только один нейрон.
Сама структура модели представляет из себя два набора данных, в которых:
Данные наборы «укладываются» в формулу Y = 2X – 1. Таким образом, для каждого Y из набора прослеживается зависимость.
После формирования модели производится ее компиляция с оптимизатором sgd и функцией потерь mean_squared_error.
Далее в модель попадают наборы данных и производится обучение на протяжении 500 эпох
(model.fit(xs, ys, epochs=500).
*Нейросеть лучше обучать на железе помощнее либо в Google colab для ускорения процесса, так как 500 эпох могут занять до 2-3 часов времени.
И, наконец, модель проверяется путем подачи ей на вход числа 10.0. То есть мы пытаемся выяснить с помощью модели значение Y при X=10.0. В идеале, согласно формуле, Y должен быть 19.0. Но в итоге будет число, сильно приближенное по значению к 19.0.
Так работает созданная нейросеть. Именно ее мы и будем конвертировать и переносить на android устройство.
Для целей переноса модели на мобильное устройство воспользуемся tensorflow-lite. Сам по себе tensorflow-lite представляется из себя набор утилит, преследующий две основные цели. Первая из которых — сделать из нейросети модель, приемлемую для мобильного устройства. Обычно под этим подразумевается уменьшение размера и сложности сети, что, в свою очередь, приводит к небольшому падению точности работы. Тем не менее это необходимый компромисс между аккуратностью работы нейросети и ее размерами на мобильном устройстве. Вторая цель — создание среды выполнения для различных мобильных платформ, включая android, ios, микроконтроллеры.
Важной чертой tensorflow-lite является то, что с ее помощью невозможно тренировать модель. Нейросеть должна быть сначала обучена с помощью tensorflow и далее конвертирована в формат tensorflow-lite.
Зачем вообще переносить нейросеть на мобильное устройство?
Вопрос риторический, но из аргументов «за» — приватность используемых данных и отсутствие необходимости использования сетей связи при работе, например, с облачными решениями.
Говоря о конвертации в приемлемый tensorflow-lite формат, вернемся к коду нейросети из предыдущего блока.
Необходимо сперва сохранить готовую модель:
В результате получится файл «model.pb».
Теперь очередь конвертации:
Далее сохраним сконвертированную модель в формате .tflite используя pathlib:
Готовую model.tflite модель можно использовать в различных окружениях, например, android или ios.
Загрузим модель в интерпретатор, выделим тензоры, которые будут использоваться для ввода данных в модель для прогнозирования, а затем прочитаем то, что выводит модель.
Здесь проявляется главное отличие tensorflow-lite от Tensorflow. Если во втором случае возможно просто использовать model.predict(что-то) и сразу получить результат, то в tensorflow-lite необходимо поработать с сходными и выходными тензорами, приводя свои данные для соответствия их формату.
Посмотрим как это выглядит:
В результате вывод будет следующим:
В выводе input_details необходимо обратить внимание на shape(размерность) — массив 1,1 и класс данных — numpy.float32. Эти параметры будут определять входные данные и их формат.
Используя данную информацию, определим Y для X=10.0:
В выводе output_details формат данных схож, поэтому ответ нейросети будет в виде [[y]], также как и [[x]]:
Чтобы заставить интерпретатор выполнить прогнозирование, необходимо задать входной тензор со значением для прогнозирования:
Так как у нас очень простая модель, в которой есть только один параметр ввода, поэтому это input_details[0], к нему мы и обращаемся в индексе.
Затем мы вызываем интерпретатор с помощью метода invoke.
Теперь мы осуществим прогнозирование с помощью модели, используя выражение get_tensor:
Опять же, существует только один выходной тензор, поэтому он будет выглядеть как output_details[0].
В целом код выглядит следующим образом:
Вывод:
Где 10 — это входное значение или X, а 18.97 — предсказанная величина, близкая к 19 (формула нейросети та же Y=2X – 1).
После создания модели нейросети и ее конвертации в формат tensorflow-lite,
создадим приложение для Android.
Для этих целей воспользуемся IDE - Android Studio, которая в особом представлении не нуждается.
Скачивание и установка Android Studio может занять время, поэтому, если с этим возникнут сложности, лучше обратиться к сторонним мануалам. Язык, который будет использоваться при создании нашего приложения — Kotlin.
Приложение, которое будет перенесено на android — это простая нейросеть, которая была создана и конвертирована выше по тексту. При вводе значения Х пользователем, нейросеть будет предсказывать Y, выводя результат на экране смартфона.
Цепочка действий после запуска Android Studio следующая:
File → New → New Project→ Empty Activity
Далее заполним поля проекта, не забыв указать язык — Kotlin:
После нажатия Finish, Android Studio создаст проект.
*На картинке уже готовый проект, при создании дизайн выглядит иначе.
Правим activity_main.xml:
Заменим код, который там приведен на
Это сформирует внешний вид приложения.
Добавим зависимости Tensorflow lite
Нам нужны build.gradle файлы. Их в проекте два, и нужен тот, который содержит app в названии и расположен в директории app:
В этом файле, в конце, надо добавить зависимость tensorflow lite согласно установленной версии на ПК. В моем случае установлен «древний» tensorflow 1.9.0, поэтому добавлена эта зависимость —
*Как узнать версию tensorflow lite? Выполнить в cmd — pip freeze и посмотреть версию tensorflow либо через idle python — import tensorflow as tf; print(tf.__version__) .
В этом же файле также необходимо добавить:
Полностью файл build.gradle будет выглядеть так:
В папке main создадим папку assets и перенесем туда ранее сконвертированную модель —
Откроем файл MainActivity.kt:
Перенесем в него
После этого, можно протестировать готовое приложение, нажав на кнопку Run 'app'
Тестирование проведем в эмуляторе android устройства:
После ввода X, приложение выдаст значение Y, приближенное к расчетному значению по формуле Y=2X-1, то есть нейросеть работает.
Выберем Build ->Build bundle.
Android Studio сформирует приложение с расширением .apk, которое можно забрать из ~\app\build\outputs\apk\debug и, подключив смартфон к ПК, перенести на смартфон.
В принципе, все, теперь простая нейросеть живет на смартфоне, занимая всего 10Мб.
Приложения:
Может быть интересно:
Описывается личный опыт автора, поэтому нет претензий на всеохватывающее руководство.
Нейросеть до конвертации.
Для старта возьмем не слишком сложную нейросеть, код которой приведен ниже:
код нейросети
import tensorflow as tf
import numpy as np
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential([Dense(units=1, input_shape=[1])])
model.compile(optimizer='sgd', loss='mean_squared_error')
xs = np.array([-1.0, 0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)
ys = np.array([-3.0, -1.0, 1.0, 3.0, 5.0, 7.0], dtype=float)
model.fit(xs, ys, epochs=500)
print(model.predict([10.0]))
Краткое пояснение по коду. После импорта пакетов tensorflow,numpy мы формируем модель нейросети. Модель представляет из себя только один слой и только один нейрон.
Сама структура модели представляет из себя два набора данных, в которых:
X = –1, 0, 1, 2, 3, 4
Y = –3, –1, 1, 3, 5, 7
Данные наборы «укладываются» в формулу Y = 2X – 1. Таким образом, для каждого Y из набора прослеживается зависимость.
После формирования модели производится ее компиляция с оптимизатором sgd и функцией потерь mean_squared_error.
Далее в модель попадают наборы данных и производится обучение на протяжении 500 эпох
(model.fit(xs, ys, epochs=500).
*Нейросеть лучше обучать на железе помощнее либо в Google colab для ускорения процесса, так как 500 эпох могут занять до 2-3 часов времени.
И, наконец, модель проверяется путем подачи ей на вход числа 10.0. То есть мы пытаемся выяснить с помощью модели значение Y при X=10.0. В идеале, согласно формуле, Y должен быть 19.0. Но в итоге будет число, сильно приближенное по значению к 19.0.
Так работает созданная нейросеть. Именно ее мы и будем конвертировать и переносить на android устройство.
Конвертация нейросети.
Для целей переноса модели на мобильное устройство воспользуемся tensorflow-lite. Сам по себе tensorflow-lite представляется из себя набор утилит, преследующий две основные цели. Первая из которых — сделать из нейросети модель, приемлемую для мобильного устройства. Обычно под этим подразумевается уменьшение размера и сложности сети, что, в свою очередь, приводит к небольшому падению точности работы. Тем не менее это необходимый компромисс между аккуратностью работы нейросети и ее размерами на мобильном устройстве. Вторая цель — создание среды выполнения для различных мобильных платформ, включая android, ios, микроконтроллеры.
Важной чертой tensorflow-lite является то, что с ее помощью невозможно тренировать модель. Нейросеть должна быть сначала обучена с помощью tensorflow и далее конвертирована в формат tensorflow-lite.
Зачем вообще переносить нейросеть на мобильное устройство?
Вопрос риторический, но из аргументов «за» — приватность используемых данных и отсутствие необходимости использования сетей связи при работе, например, с облачными решениями.
Говоря о конвертации в приемлемый tensorflow-lite формат, вернемся к коду нейросети из предыдущего блока.
Необходимо сперва сохранить готовую модель:
export_dir = 'model'
tf.saved_model.save(model,'model')
В результате получится файл «model.pb».
Теперь очередь конвертации:
converter = tf.lite.TFLiteConverter.from_saved_model(export_dir)
tflite_model = converter.convert()
Далее сохраним сконвертированную модель в формате .tflite используя pathlib:
import pathlib
tflite_model_file = pathlib.Path('model.tflite')
tflite_model_file.write_bytes(tflite_model)
Готовую model.tflite модель можно использовать в различных окружениях, например, android или ios.
Как работает модель в tensorflow-lite
Загрузка модели, выделение тензоров
Загрузим модель в интерпретатор, выделим тензоры, которые будут использоваться для ввода данных в модель для прогнозирования, а затем прочитаем то, что выводит модель.
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
Здесь проявляется главное отличие tensorflow-lite от Tensorflow. Если во втором случае возможно просто использовать model.predict(что-то) и сразу получить результат, то в tensorflow-lite необходимо поработать с сходными и выходными тензорами, приводя свои данные для соответствия их формату.
Посмотрим как это выглядит:
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
print(input_details)
print(output_details)
В результате вывод будет следующим:
[{'name': 'dense_input', 'index': 0,<b> 'shape': array([1, 1]</b>, dtype=int32),
'shape_signature': array([1, 1], dtype=int32),
'dtype': class 'numpy.float32', 'quantization': (0.0, 0),
'quantization_parameters':
{'scales': array([], dtype=float32), 'zero_points': array([], dtype=int32),
'quantized_dimension': 0}, 'sparsity_parameters': {}}]
В выводе input_details необходимо обратить внимание на shape(размерность) — массив 1,1 и класс данных — numpy.float32. Эти параметры будут определять входные данные и их формат.
Используя данную информацию, определим Y для X=10.0:
to_predict = np.array([[10.0]], dtype=np.float32)
print(to_predict)
В выводе output_details формат данных схож, поэтому ответ нейросети будет в виде [[y]], также как и [[x]]:
[{'name': 'Identity', 'index': 3, 'shape': array([1, 1], dtype=int32),
'shape_signature': array([1, 1], dtype=int32), 'dtype': <class
'numpy.float32'>, 'quantization': (0.0, 0), 'quantization_parameters':
{'scales': array([], dtype=float32), 'zero_points': array([], dtype=int32),
'quantized_dimension': 0}, 'sparsity_parameters': {}}]
Прогнозирование, использование модели
Чтобы заставить интерпретатор выполнить прогнозирование, необходимо задать входной тензор со значением для прогнозирования:
interpreter.set_tensor(input_details[0]['index'], to_predict)
interpreter.invoke()
Так как у нас очень простая модель, в которой есть только один параметр ввода, поэтому это input_details[0], к нему мы и обращаемся в индексе.
Затем мы вызываем интерпретатор с помощью метода invoke.
Теперь мы осуществим прогнозирование с помощью модели, используя выражение get_tensor:
tflite_results = interpreter.get_tensor(output_details[0]['index'])
print(tflite_results)
Опять же, существует только один выходной тензор, поэтому он будет выглядеть как output_details[0].
В целом код выглядит следующим образом:
to_predict = np.array([[10.0]], dtype=np.float32)
print(to_predict)
interpreter.set_tensor(input_details[0]['index'], to_predict)
interpreter.invoke()
tflite_results = interpreter.get_tensor(output_details[0]['index'])
print(tflite_results)
Вывод:
[[10.]]
[[18.975412]]
Где 10 — это входное значение или X, а 18.97 — предсказанная величина, близкая к 19 (формула нейросети та же Y=2X – 1).
Перенос модели на android устройство (мобильный телефон)
После создания модели нейросети и ее конвертации в формат tensorflow-lite,
создадим приложение для Android.
Для этих целей воспользуемся IDE - Android Studio, которая в особом представлении не нуждается.
Скачивание и установка Android Studio может занять время, поэтому, если с этим возникнут сложности, лучше обратиться к сторонним мануалам. Язык, который будет использоваться при создании нашего приложения — Kotlin.
Приложение, которое будет перенесено на android — это простая нейросеть, которая была создана и конвертирована выше по тексту. При вводе значения Х пользователем, нейросеть будет предсказывать Y, выводя результат на экране смартфона.
Создаем новый проект в Android Studio
Цепочка действий после запуска Android Studio следующая:
File → New → New Project→ Empty Activity
Далее заполним поля проекта, не забыв указать язык — Kotlin:
После нажатия Finish, Android Studio создаст проект.
*На картинке уже готовый проект, при создании дизайн выглядит иначе.
Правим activity_main.xml:
Заменим код, который там приведен на
следующий
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/lblEnter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enter X: "
android:textSize="18sp"></TextView>
<EditText
android:id="@+id/txtValue"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:inputType="number"
android:text="1"></EditText>
<Button
android:id="@+id/convertButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Convert">
</Button>
</LinearLayout>
</LinearLayout>
Это сформирует внешний вид приложения.
Добавим зависимости Tensorflow lite
Нам нужны build.gradle файлы. Их в проекте два, и нужен тот, который содержит app в названии и расположен в директории app:
В этом файле, в конце, надо добавить зависимость tensorflow lite согласно установленной версии на ПК. В моем случае установлен «древний» tensorflow 1.9.0, поэтому добавлена эта зависимость —
*Как узнать версию tensorflow lite? Выполнить в cmd — pip freeze и посмотреть версию tensorflow либо через idle python — import tensorflow as tf; print(tf.__version__) .
В этом же файле также необходимо добавить:
Полностью файл build.gradle будет выглядеть так:
код
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.example.firsttflite"
minSdkVersion 23
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
aaptOptions {
noCompress "tflite"
}
}
dependencies {
implementation 'org.tensorflow:tensorflow-lite:1.9.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
Теперь добавим в проект саму модель нейросети tf-lite
В папке main создадим папку assets и перенесем туда ранее сконвертированную модель —
Привяжем модель нейросети к android приложению
Откроем файл MainActivity.kt:
Перенесем в него
код
package com.example.firsttflite
import android.app.AlertDialog
import android.content.DialogInterface
import android.content.res.AssetManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import org.tensorflow.lite.Interpreter
import java.io.FileInputStream
import java.lang.Exception
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.MappedByteBuffer
import java.nio.channels.FileChannel
class MainActivity : AppCompatActivity() {
private lateinit var tflite : Interpreter
private lateinit var tflitemodel : ByteBuffer
private lateinit var txtValue : EditText
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
try{
tflitemodel = loadModelFile(this.assets, "model.tflite")
tflite = Interpreter(tflitemodel)
} catch(ex: Exception){
ex.printStackTrace()
}
var convertButton: Button = findViewById<Button>(R.id.convertButton)
convertButton.setOnClickListener{
doInference()
}
txtValue = findViewById<EditText>(R.id.txtValue)
}
private fun loadModelFile(assetManager: AssetManager, modelPath: String): ByteBuffer {
val fileDescriptor = assetManager.openFd(modelPath)
val inputStream = FileInputStream(fileDescriptor.fileDescriptor)
val fileChannel = inputStream.channel
val startOffset = fileDescriptor.startOffset
val declaredLength = fileDescriptor.declaredLength
return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength)
}
private fun doInference(){
var userVal: Float = txtValue.text.toString().toFloat()
var inputVal: FloatArray = floatArrayOf(userVal)
var outputVal: ByteBuffer = ByteBuffer.allocateDirect(4)
outputVal.order(ByteOrder.nativeOrder())
tflite.run(inputVal, outputVal)
outputVal.rewind()
var f:Float = outputVal.getFloat()
val builder = AlertDialog.Builder(this)
with(builder)
{
setTitle("TFLite Interpreter")
setMessage("Your Value is:$f")
setNeutralButton("OK", DialogInterface.OnClickListener {
dialog, id -> dialog.cancel()
})
show()
}
}
}
После этого, можно протестировать готовое приложение, нажав на кнопку Run 'app'
Тестирование проведем в эмуляторе android устройства:
После ввода X, приложение выдаст значение Y, приближенное к расчетному значению по формуле Y=2X-1, то есть нейросеть работает.
Создадим .apk и перенесем его на мобильный телефон
Выберем Build ->Build bundle.
Android Studio сформирует приложение с расширением .apk, которое можно забрать из ~\app\build\outputs\apk\debug и, подключив смартфон к ПК, перенести на смартфон.
вид на телефоне
В принципе, все, теперь простая нейросеть живет на смартфоне, занимая всего 10Мб.
Приложения:
- не конвертированная нейросеть — скачать
- конвертированная в tf-lite нейросеть — скачать
- apk приложение для android — скачать
Может быть интересно:
Tarson
"В принципе, все, теперь простая нейросеть живет на смартфоне, занимая всего 10Мб. "
Так, а если обученная нейросеть (простая) - это просто набор подобранных весов, то не проще ли их напрямую скопировать? И пусть себе вычисляется отдельным кодом. Оно там и 10 кб не займет тогда. На смартфоне же мы ее не обучаем.
zoldaten Автор
Верно, проще. Но здесь была интересна цепочка tf->tf lite->android.