TensorFlow em aplicativos Android
1. Introdução
editarTensorFlow é 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
editarA 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
editarNessa 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
editarO 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)
3.2 Portabilidade do modelo para mobile
editarTendo 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.
Após indicar o caminho para o arquivo .tflite criado anteriormente, o android studio irá gerar um template para utilizar o modelo importado.
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.