TensorFlow em aplicativos Android

1. Introdução editar

TensorFlow é uma biblioteca open-source criada pela Google™ Brain Team de Machine e Deep Learning desenvolvida em C++, Python e CUDA. Ele é amplamente utilizado para treinamento de redes neurais, e oferece diversas ferramentas para auxiliar no processamento dos dados a serem utilizados na criação do modelo.

O TensorFlow Lite é um conjunto de ferramentas para fornecer suporte a desenvolvedores que precisam executar modelos em dispositivos móveis, ele basicamente realiza a conversão e otimização de um modelo gerado pelo TensorFlow para que eles rodem em aplicações mobile.

Python e JavaScript são as linguagens mais popularmente usadas na utilização do TensorFlow, apesar do fato de que a biblioteca também tem suporte para Swift, C, Go, C# e Java. Nos exemplos mostrados nessa página utilizaremos Python.

2. Como a ferramenta funciona editar

A biblioteca é tem esse nome devido ao fato de aceitar entradas denominadas de Tensors, que são vetores multidimensionais. O TensorFlow pode ser executado no seu computador pessoal, cloud cluster, celulares iOS e Android, CPUs e GPUs. Também é possível utilizar o Google cloud.

Tensors são objetos em Python; apesar disso, operações matemáticas dentro das aplicações são realizadas binariamente em outras linguagens e o python atua como intermediário no fluxo de informações.

A arquitetura do TensorFlow consiste (majoritariamente) em:

Processamento dos dados → Criação do modelo → Aprimoramento e teste do modelo

É importante mencionar o Keras (uma API de deep learning), que pode ser usado para aplicações mais sofisticadas, construindo e ligando várias layers, e mesmo assim possibilita manter o código sucinto.

3. Exemplo de Uso: Classificador de Imagens editar

Nessa seção, será demonstrado o uso de uma rede neural presente na biblioteca TensorFlow para ser utilizada em uma aplicação mobile Android que classifica imagens de Pokemons. Os códigos utilizados estão disponíveis aqui .

3.1. Criando o modelo editar

O modelo do exemplo será treinado em um Jupyter Notebook, a fim de facilitar a documentação do processo, mas poderia ser realizado em um script Python. Ressaltamos novamnte que o arquivo .ipynb referente ao modelo está em no repositório linkado anteriormente.

Primeiramente, queremos instalar a biblioteca TensorFlow:

!python3 -m pip install tensorflow

E então podemos prosseguir para as dependências:

import tensorflow as tf # importando a biblioteca TensorFlow


# Funções utilizadas para criar a rede neural
from tensorflow.keras.models import Sequential
from tensorflow.keras.applications import DenseNet201
from tensorflow.python.keras.layers import Dense, Flatten, GlobalAveragePooling2D, Conv2D, MaxPooling2D

# Funções utilizadas para manipular as imagens do dataset de treino
from tensorflow.keras.preprocessing.image import load_img, img_to_array, ImageDataGenerator
from tensorflow.python.keras.layers import Dense, Flatten, GlobalAveragePooling2D, Conv2D, MaxPooling2D

Para a próxima etapa é necessário ter definido qual será o dataset que será utilizado para treinar e avaliar o modelo classificador. Nesse caso foi utilizado o dataset disponível em:  https://www.kaggle.com/datasets/mikoajkolman/pokemon-images-first-generation17000-files

# Para utlizar as imagens na rede neural DenseNet201, precisamos modificar os tamanhos e normalizar cada imagem:
	
stored = {}
def normalize_ds(path,labels, size=(128,128)):
    dataset = []
    count = 0
    for label in labels:
        folder = os.path.join(path,label)
        for image in os.listdir(folder):
            try:
                img=load_img(os.path.join(folder,image), target_size=size)
                img=img_to_array(img)
                img=img/255.0 # Normalizacao
                dataset.append((img,count))
            except:
                pass
        stored[label] = count
        count+=1
    random.shuffle(dataset)
    X, y = zip(*dataset)
    return np.array(X),np.array(y)
    		
# PATH é o caminho para o dataset e labels é uma lista com as categorias de cada imagem 
X, y = normalize_ds(PATH,labels)

Com as imagens pré-processadas podemos dividir o dataset em um conjunto de treino e outro de teste, da seguinte forma:

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30, random_state=42)

Agora é possível construir a rede neural a ser utliizada:

base_model = DenseNet201(include_top = False, weights = 'imagenet', input_shape = (h,w,3))
model = Sequential()
model.add(base_model)
model.add(tf.keras.layers.GlobalAveragePooling2D())
model.add(tf.keras.layers.Dense(len(labels), activation=tf.nn.softmax))
model.compile(optimizer = tf.keras.optimizers.Adam(learning_rate = 0.001), loss = 'categorical_crossentropy', metrics=['accuracy'])

Finalmente, podemos treinar a rede neural:

history = model.fit(datagen.flow(X_train,y_train,batch_size=32), validation_data=testgen.flow(X_test,y_test,batch_size=32), epochs=10)
 
Saída referente ao treinamento da rede neural

3.2 Portabilidade do modelo para mobile editar

Tendo o modelo pronto, temos que convertê-lo com a ajuda do TensorFlowLite para viabilizar sua utilização em um aplicativo mobile:

converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
open("mighty_model.tflite" , "wb").write(tflite_model)

Dessa forma o modelo será convertido no formato compatível com TensorFlowLite e um arquivo .tflite será criado.

O aplicativo mobile utilizado no exemplo foi desenvolvido no AndroidStudio (e também pode ser encontrado no repositório em /App/).

Queremos importar o modelo criado anteriormente e utilizá-lo no nosso aplicativo. Para importar a rede neural, basta: Selecionar a pasta app > Selecionar New > Selecionar Other > Selecionar TensorFlow Lite Model, conforme imagem a seguir.

 
Passo-a-passo para importar a rede neural resultante

Após indicar o caminho para o arquivo .tflite criado anteriormente, o android studio irá gerar um template para utilizar o modelo importado.

 
Template gerado pelo AndroidStudio para utilização do modelo gerado pelo TensorFlow Lite

A partir do código sugerido, podemos criar uma função que recebe uma imagem e retorna a inferência realizada pelo modelo.

Há duas etapas importantes a serem implementadas: redimensionar e normalizar a imagem recebida — pois a rede neural DenseNet201 recebe dados em um formato específico — e executar a inferência do modelo para se obter o resultado.

fun classifyImage(image: Bitmap) {
    try {
        val model = MightyModelV2.newInstance(applicationContext)

        // Creates inputs for reference.
        val inputFeature0 =
            TensorBuffer.createFixedSize(intArrayOf(1, 128, 128, 3), DataType.FLOAT32)
        val byteBuffer = ByteBuffer.allocateDirect(4 * imageSize * imageSize * 3)
        byteBuffer.order(ByteOrder.nativeOrder())
        val intValues = IntArray(imageSize * imageSize)
        image.getPixels(intValues, 0, image.getWidth(), 0, 0, image.getWidth(), image.getHeight())
        var pixel = 0
        for (i in 0 until imageSize) {
            for (j in 0 until imageSize) {
                val `val` = intValues[pixel++]
                byteBuffer.putFloat((`val` shr 16 and 0xFF) * (1f / 255))
                byteBuffer.putFloat((`val` shr 8 and 0xFF) * (1f / 255))
                byteBuffer.putFloat((`val` and 0xFF) * (1f / 255))
            }
        }
        inputFeature0.loadBuffer(byteBuffer)

        // Runs model inference and gets result
        val outputs = model.process(inputFeature0)
        val outputFeature0 = outputs.getOutputFeature0AsTensorBuffer()
        val confidences = outputFeature0.floatArray
        var maxPos = 0
        var maxConfidence = 0f
        
        for (i in confidences.indices) {
            if (confidences[i] > maxConfidence) {
                maxConfidence = confidences[i]
                maxPos = i
            }
        }
        
        val classes = arrayOf("Scyther", "Gastly", "Arbok", "Drowzee", "Voltorb", "Oddish", "Abra", "Alakazam", "Vaporeon", "Ivysaur", "Exeggutor", "Raticate", "MrMime", "Ponyta", "Squirtle", "Clefairy", "Magmar", "Zapdos", "Dugtrio", "Kakuna", "Geodude", "Blastoise", "Farfetchd", "Mankey", "Bulbasaur", "Seel", "Weedle", "Pinsir", "Primeape", "Beedrill", "Hitmonchan", "Dragonair", "Zubat", "Machop", "Magikarp", "Pikachu", "Aerodactyl", "Kingler", "Metapod", "Vileplume", "Tentacool", "Jigglypuff", "Charmeleon", "Fearow", "Slowpoke", "Weezing", "Pidgeotto", "Machamp", "Seadra", "Rattata", "Wigglytuff", "Pidgey", "Jolteon", "Diglett", "Electabuzz", "Charizard", "Bellsprout", "Poliwag", "Slowbro", "Venonat", "Gloom", "Rapidash", "Grimer", "Tangela", "Chansey", "Nidoqueen", "Moltres", "Eevee", "Articuno", "Weepinbell", "Nidorino", "Poliwhirl", "Ekans", "Lickitung", "Dodrio", "Cubone", "Seaking", "Tauros", "Kabutops", "Venusaur", "Charmander", "Wartortle", "Dratini", "Porygon", "Clefable", "Ninetales", "Sandslash", "Dragonite", "Poliwrath", "Spearow", "Exeggcute", "Horsea", "Magneton", "Magnemite", "Machoke", "Caterpie", "Vulpix", "Psyduck", "Flareon", "Gyarados", "Electrode", "Pidgeot", "Staryu", "Ditto", "Starmie", "Omastar", "Parasect", "Tentacruel", "Hitmonlee", "Mew", "Lapras", "Nidoking", "Golduck", "Hypno", "Golbat", "Venomoth", "Graveler", "Mewtwo", "Jynx", "Dewgong", "Nidorina", "Sandshrew", "Koffing", "Shellder", "Victreebel", "Haunter", "Cloyster", "Goldeen", "Marowak", "Kadabra", "Mr. Mime", "Rhydon", "Rhyhorn", "Meowth", "Butterfree", "Doduo", "Snorlax", "Gengar", "Omanyte", "Kangaskhan", "Arcanine", "Growlithe", "Raichu")
        result!!.text = classes[maxPos]
        // Releases model resources if no longer used.
        model.close()
    } catch (e: IOException) {
        // TODO Handle the exception
    }
}

Dessa forma, é possível utilizar as inferências realizadas pelo modelo em qualquer parte do código da aplicação.

4. Referências editar

  1. https://www.tensorflow.org/lite?hl=pt-br
  2. https://www.simplilearn.com/tutorials/deep-learning-tutorial/
  3. https://www.spiceworks.com/tech/devops/articles/what-is-tensorflow/