Как перенести нейросеть на мобильное устройство

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
В статье поговорим как обучить несложную CNN сеть с помощью tensorflow, конвертировать готовое с помощью tensoflow-lite и перенести на мобильное устройство под управлением android.
Описывается личный опыт автора, поэтому нет претензий на всеохватывающее руководство.



Нейросеть до конвертации.


Для старта возьмем не слишком сложную нейросеть, код которой приведен ниже:
код нейросети
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 — скачать

Может быть интересно:
— Как обойти капчу Гугл
— Как прикрутить нейросеть к сайту по-быстрому
— Как обойти капчу: нейросеть на Tensorflow,Keras,python v числовая зашумленная капча
Источник: https://habr.com/ru/post/570052/


Интересные статьи

Интересные статьи

Меня зовут Иванов Александр, я тестирую приемники цифрового телевидения. В статье расскажу о том, как наша команда GS Labs набралась смелости и сменила неудобный и д...
Зачем это вообще нужно?! Резонный вопрос. Иногда бывает нужно. Всегда, когда надо подключить один источник звука, например сабвуфер. Или, как в моём случае стояла задача ...
Такси должно быть комфортным и безопасным. А это зависит не только от качества автомобиля и сервиса, но и от концентрации внимания водителя, которая падает при переутомлении. Поэтому на уровн...
Александр Епанешников, 19-летний российский студент, формально является слепым *. Недавно он решил, что хочет быть более независимым и меньше полагаться на маму при поездках в школу. Путь до школ...
Заголовок статьи может показаться странным и это неспроста — он прекрасен именно тем, что написал его не я, а LSTM-нейросеть (а точнее его часть перед "или"). (схема LSTM взята из Understandin...