1. Antes de comenzar
En este codelab, actualizarás la app que creaste en los codelabs anteriores de Comienza a usar la clasificación de texto para dispositivos móviles.
Requisitos previos
- Este codelab se diseñó para desarrolladores experimentados que no han usado el aprendizaje automático.
- El codelab forma parte de una ruta de aprendizaje secuenciada. Si aún no completaste Compila una app básica de estilo de mensajería o Compila un modelo de aprendizaje automático de spam en comentarios, detente y hazlo ahora.
Qué [compilarás o aprenderás]
- Aprenderás a integrar tu modelo personalizado en la app que creaste en los pasos anteriores.
Requisitos
- Android Studio o CocoaPods para iOS
2. Abre la app para Android existente
Para obtener el código, sigue el codelab 1 o clona este repositorio y carga la app desde TextClassificationStep1.
git clone https://github.com/googlecodelabs/odml-pathways
Puedes encontrarlo en la ruta de acceso TextClassificationOnMobile->Android.
El código terminado también está disponible para ti como TextClassificationStep2.
Una vez que se abra, podrás continuar con el paso 2.
3. Importa el archivo del modelo y los metadatos
En el codelab Compila un modelo de aprendizaje automático de comentarios spam, creaste un modelo .TFLITE.
Deberías haber descargado el archivo del modelo. Si no lo tienes, puedes obtenerlo del repo de este codelab, y el modelo está disponible aquí.
Para agregarlo a tu proyecto, crea un directorio de recursos.
- Con el navegador de proyectos, asegúrate de que Android esté seleccionado en la parte superior.
- Haz clic con el botón derecho en la carpeta app. Selecciona New > Directory.

- En el diálogo New directory, selecciona src/main/assets.

Verás que ahora hay una nueva carpeta assets disponible en la app.

- Haz clic con el botón derecho en assets.
- En el menú que se abre, verás (en Mac) Mostrar en Finder. Selecciónala. (En Windows, dirá Mostrar en el Explorador y, en Ubuntu, Mostrar en Archivos).

Se iniciará Finder para mostrar la ubicación de los archivos (Explorador de archivos en Windows y Archivos en Linux).
- Copia los archivos
labels.txt,model.tfliteyvocaben este directorio.

- Regresa a Android Studio y verás que están disponibles en la carpeta assets.

4. Actualiza tu archivo build.gradle para usar TensorFlow Lite
Para usar TensorFlow Lite y las bibliotecas de tareas de TensorFlow Lite que lo admiten, deberás actualizar tu archivo build.gradle.
Los proyectos de Android suelen tener más de uno, así que asegúrate de encontrar el de nivel app. En el explorador de proyectos en la vista de Android, búscalo en la sección Gradle Scripts. La correcta se etiquetará con .app, como se muestra aquí:

Deberás realizar dos cambios en este archivo. La primera se encuentra en la sección dependencies en la parte inferior. Agrega un texto implementation para la biblioteca de tareas de TensorFlow Lite, de la siguiente manera:
implementation 'org.tensorflow:tensorflow-lite-task-text:0.1.0'
Es posible que el número de versión haya cambiado desde que se escribió este texto, así que asegúrate de consultar https://www.tensorflow.org/lite/inference_with_metadata/task_library/nl_classifier para obtener la versión más reciente.
Las bibliotecas de tareas también requieren una versión mínima del SDK de 21. Encuentra este parámetro de configuración en android > default config y cámbialo a 21:

Ahora tienes todas las dependencias, así que es hora de comenzar a programar.
5. Agrega una clase auxiliar
Para separar la lógica de inferencia, en la que tu app usa el modelo, de la interfaz de usuario, crea otra clase para controlar la inferencia del modelo. Llama a esta clase "helper".
- Haz clic con el botón derecho en el nombre del paquete en el que se encuentra tu código
MainActivity. - Selecciona New > Package.

- Verás un diálogo en el centro de la pantalla que te pedirá que ingreses el nombre del paquete. Agrégalo al final del nombre del paquete actual. (Aquí se llaman ayudantes).

- Una vez que termines, haz clic con el botón derecho en la carpeta helpers en el explorador de proyectos.
- Selecciona New > Java Class y llámala
TextClassificationClient. Editarás el archivo en el siguiente paso.
Tu clase de ayuda TextClassificationClient se verá de la siguiente manera (aunque el nombre de tu paquete puede ser diferente).
package com.google.devrel.textclassificationstep1.helpers;
public class TextClassificationClient {
}
- Actualiza el archivo con este código:
package com.google.devrel.textclassificationstep2.helpers;
import android.content.Context;
import android.util.Log;
import java.io.IOException;
import java.util.List;
import org.tensorflow.lite.support.label.Category;
import org.tensorflow.lite.task.text.nlclassifier.NLClassifier;
public class TextClassificationClient {
private static final String MODEL_PATH = "model.tflite";
private static final String TAG = "CommentSpam";
private final Context context;
NLClassifier classifier;
public TextClassificationClient(Context context) {
this.context = context;
}
public void load() {
try {
classifier = NLClassifier.createFromFile(context, MODEL_PATH);
} catch (IOException e) {
Log.e(TAG, e.getMessage());
}
}
public void unload() {
classifier.close();
classifier = null;
}
public List<Category> classify(String text) {
List<Category> apiResults = classifier.classify(text);
return apiResults;
}
}
Esta clase proporcionará un wrapper para el intérprete de TensorFlow Lite, cargará el modelo y abstraerá la complejidad de administrar el intercambio de datos entre tu app y el modelo.
En el método load(), se creará una instancia de un nuevo tipo NLClassifier a partir de la ruta del modelo. La ruta de acceso del modelo es simplemente el nombre del modelo, model.tflite. El tipo NLClassifier forma parte de las bibliotecas de tareas de texto y te ayuda a convertir tu cadena en tokens, usar la longitud de secuencia correcta, pasarla al modelo y analizar los resultados.
(Para obtener más detalles sobre estos, consulta Cómo crear un modelo de aprendizaje automático para detectar comentarios spam).
La clasificación se realiza en el método classify, en el que le pasas una cadena y este devolverá un List. Cuando se usan modelos de aprendizaje automático para clasificar contenido y se desea determinar si una cadena es spam o no, es común que se devuelvan todas las respuestas con probabilidades asignadas. Por ejemplo, si le pasas un mensaje que parece spam, recibirás una lista de 2 respuestas: una con la probabilidad de que sea spam y otra con la probabilidad de que no lo sea. Las categorías Spam/No spam son categorías, por lo que el List que se devuelve contendrá estas probabilidades. Lo analizarás más adelante.
Ahora que tienes la clase de ayuda, vuelve a tu MainActivity y actualízala para que la use y clasifique tu texto. Lo verás en el siguiente paso.
6. Clasifica el texto
En tu MainActivity, primero querrás importar los asistentes que acabas de crear.
- En la parte superior de
MainActivity.kt, junto con las otras importaciones, agrega lo siguiente:
import com.google.devrel.textclassificationstep2.helpers.TextClassificationClient
import org.tensorflow.lite.support.label.Category
- A continuación, deberás cargar los asistentes. En
onCreate, inmediatamente después de la líneasetContentView, agrega estas líneas para crear una instancia y cargar la clase de ayuda:
val client = TextClassificationClient(applicationContext)
client.load()
Por el momento, el onClickListener de tu botón debería verse así:
btnSendText.setOnClickListener {
var toSend:String = txtInput.text.toString()
txtOutput.text = toSend
}
- Actualízalo para que se vea así:
btnSendText.setOnClickListener {
var toSend:String = txtInput.text.toString()
var results:List<Category> = client.classify(toSend)
val score = results[1].score
if(score>0.8){
txtOutput.text = "Your message was detected as spam with a score of " + score.toString() + " and not sent!"
} else {
txtOutput.text = "Message sent! \nSpam score was:" + score.toString()
}
txtInput.text.clear()
}
Esto cambia la funcionalidad de solo generar la entrada del usuario a clasificarla primero.
- Con esta línea, tomarás la cadena que ingresó el usuario y la pasarás al modelo, y obtendrás resultados:
var results:List<Category> = client.classify(toSend)
Solo hay 2 categorías: False y True
. (TensorFlow los ordena alfabéticamente, por lo que False será el elemento 0 y True será el elemento 1).
- Para obtener la puntuación de la probabilidad de que el valor sea
True, puedes consultar results[1].score de la siguiente manera:
val score = results[1].score
- Elegiste un valor de umbral (en este caso, 0.8), en el que indicas que, si la puntuación de la categoría Verdadero está por encima del valor de umbral (0.8), el mensaje es spam. De lo contrario, no es spam y el mensaje es seguro para enviarse:
if(score>0.8){
txtOutput.text = "Your message was detected as spam with a score of " + score.toString() + " and not sent!"
} else {
txtOutput.text = "Message sent! \nSpam score was:" + score.toString()
}
- Mira el modelo en acción aquí. El mensaje "Visita mi blog para comprar cosas" se marcó como spam con una alta probabilidad:

Y, a la inversa, se observó que "¡Hola! Gracias por el divertido instructivo" tenía una probabilidad muy baja de ser spam:

7. Actualiza tu app para iOS para usar el modelo de TensorFlow Lite
Para obtener el código, sigue el codelab 1 o clona este repositorio y carga la app desde TextClassificationStep1. Puedes encontrarlo en la ruta de acceso TextClassificationOnMobile->iOS.
El código terminado también está disponible para ti como TextClassificationStep2.
En el codelab Compila un modelo de aprendizaje automático de comentarios spam, creaste una app muy simple que permitía al usuario escribir un mensaje en un UITextView y pasarlo a un resultado sin ningún filtro.
Ahora actualizarás esa app para que use un modelo de TensorFlow Lite y detecte comentarios spam en el texto antes de enviarlo. Solo simula el envío en esta app renderizando el texto en una etiqueta de salida (pero una app real podría tener un tablón de anuncios, un chat o algo similar).
Para comenzar, necesitarás la app del paso 1, que puedes clonar desde el repo.
Para incorporar TensorFlow Lite, usarás CocoaPods. Si aún no los tienes instalados, puedes hacerlo siguiendo las instrucciones en https://cocoapods.org/.
- Una vez que hayas instalado CocoaPods, crea un archivo con el nombre Podfile en el mismo directorio que el
.xcprojectde la app de TextClassification. El contenido de este archivo debería verse de la siguiente manera:
target 'TextClassificationStep2' do
use_frameworks!
# Pods for NLPClassifier
pod 'TensorFlowLiteSwift'
end
El nombre de tu app debe estar en la primera línea, en lugar de "TextClassificationStep2".
Con la terminal, navega a ese directorio y ejecuta pod install. Si se ejecuta correctamente, tendrás un directorio nuevo llamado Pods y un archivo .xcworkspace nuevo creado para ti. Lo usarás en el futuro en lugar de .xcproject.
Si falló, asegúrate de tener el archivo Podfile en el mismo directorio en el que estaba .xcproject. Por lo general, el problema se debe a que el archivo Podfile está en el directorio incorrecto o a que el nombre del destino es incorrecto.
8. Agrega los archivos del modelo y el vocabulario
Cuando creaste el modelo con Model Maker de TensorFlow Lite, pudiste generar el modelo (como model.tflite) y el vocabulario (como vocab.txt).
- Para agregarlos a tu proyecto, arrástralos desde Finder hasta la ventana del proyecto. Asegúrate de que la opción Agregar a destinos esté marcada:

Cuando termines, deberías verlos en tu proyecto:

- Para verificar que se hayan agregado al paquete (de modo que se implementen en un dispositivo), selecciona tu proyecto (en la captura de pantalla anterior, es el ícono azul TextClassificationStep2) y consulta la pestaña Build Phases:

9. Carga el vocabulario
Cuando se realiza la clasificación de PNL, el modelo se entrena con palabras codificadas en vectores. El modelo codifica palabras con un conjunto específico de nombres y valores que se aprenden a medida que se entrena el modelo. Ten en cuenta que la mayoría de los modelos tendrán diferentes vocabularios, y es importante que uses el vocabulario de tu modelo que se generó en el momento del entrenamiento. Este es el archivo vocab.txt que acabas de agregar a tu app.
Puedes abrir el archivo en Xcode para ver las codificaciones. Las palabras como "canción" se codifican como 6 y "amor" como 12. En realidad, el orden es orden de frecuencia, por lo que "I" fue la palabra más común en el conjunto de datos, seguida de "check".
Cuando el usuario escriba palabras, deberás codificarlas con este vocabulario antes de enviarlas al modelo para que las clasifique.
Exploremos ese código. Comienza por cargar el vocabulario.
- Define una variable a nivel de la clase para almacenar el diccionario:
var words_dictionary = [String : Int]()
- Luego, crea un
funcen la clase para cargar el vocabulario en este diccionario:
func loadVocab(){
// This func will take the file at vocab.txt and load it into a has table
// called words_dictionary. This will be used to tokenize the words before passing them
// to the model trained by TensorFlow Lite Model Maker
if let filePath = Bundle.main.path(forResource: "vocab", ofType: "txt") {
do {
let dictionary_contents = try String(contentsOfFile: filePath)
let lines = dictionary_contents.split(whereSeparator: \.isNewline)
for line in lines{
let tokens = line.components(separatedBy: " ")
let key = String(tokens[0])
let value = Int(tokens[1])
words_dictionary[key] = value
}
} catch {
print("Error vocab could not be loaded")
}
} else {
print("Error -- vocab file not found")
}
}
- Puedes ejecutarlo llamándolo desde
viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
txtInput.delegate = self
loadVocab()
}
10. Convierte una cadena en una secuencia de tokens
Tus usuarios escribirán palabras como una oración que se convertirá en una cadena. Cada palabra de la oración, si está presente en el diccionario, se codificará en el par clave-valor de la palabra tal como se define en el vocabulario.
Por lo general, un modelo de PNL acepta una longitud de secuencia fija. Hay excepciones con los modelos creados con ragged tensors, pero, en su mayoría, verás que se corrigió. Especificaste esta longitud cuando creaste el modelo. Asegúrate de usar la misma longitud en tu app para iOS.
El valor predeterminado en Colab para Model Maker de TensorFlow Lite que usaste anteriormente era 20, así que configúralo aquí también:
let SEQUENCE_LENGTH = 20
Agrega este func que tomará la cadena, la convertirá a minúsculas y quitará cualquier signo de puntuación:
func convert_sentence(sentence: String) -> [Int32]{
// This func will split a sentence into individual words, while stripping punctuation
// If the word is present in the dictionary it's value from the dictionary will be added to
// the sequence. Otherwise we'll continue
// Initialize the sequence to be all 0s, and the length to be determined
// by the const SEQUENCE_LENGTH. This should be the same length as the
// sequences that the model was trained for
var sequence = [Int32](repeating: 0, count: SEQUENCE_LENGTH)
var words : [String] = []
sentence.enumerateSubstrings(
in: sentence.startIndex..<sentence.endIndex,options: .byWords) {
(substring, _, _, _) -> () in words.append(substring!) }
var thisWord = 0
for word in words{
if (thisWord>=SEQUENCE_LENGTH){
break
}
let seekword = word.lowercased()
if let val = words_dictionary[seekword]{
sequence[thisWord]=Int32(val)
thisWord = thisWord + 1
}
}
return sequence
}
Ten en cuenta que la secuencia será de tipo Int32. Esto se elige deliberadamente porque, cuando se trata de pasar valores a TensorFlow Lite, se trabaja con memoria de bajo nivel, y TensorFlow Lite trata los números enteros en una secuencia de cadenas como números enteros de 32 bits. Esto te facilitará (un poco) la vida cuando se trate de pasar cadenas al modelo.
11. Realiza la clasificación
Para clasificar una oración, primero se debe convertir en una secuencia de tokens basada en las palabras de la oración. Esto se habrá hecho en el paso 9.
Ahora tomarás la oración y la pasarás al modelo, harás que el modelo realice la inferencia en la oración y analizarás los resultados.
Esto usará el intérprete de TensorFlow Lite, que deberás importar:
import TensorFlowLite
Comienza con un func que tome tu secuencia, que era un array de tipos Int32:
func classify(sequence: [Int32]){
// Model Path is the location of the model in the bundle
let modelPath = Bundle.main.path(forResource: "model", ofType: "tflite")
var interpreter: Interpreter
do{
interpreter = try Interpreter(modelPath: modelPath!)
} catch _{
print("Error loading model!")
return
}
Esto cargará el archivo del modelo desde el paquete y, luego, invocará un intérprete con él.
El siguiente paso será copiar la memoria subyacente almacenada en la secuencia en un búfer llamado myData, para que se pueda pasar a un tensor. Cuando implementaste el pod de TensorFlow Lite, así como el intérprete, obtuviste acceso a un tipo de tensor.
Comienza el código de la siguiente manera (aún en el func de clasificación):
let tSequence = Array(sequence)
let myData = Data(copyingBufferOf: tSequence.map { Int32($0) })
let outputTensor: Tensor
No te preocupes si recibes un error en copyingBufferOf. Esto se implementará como una extensión más adelante.
Ahora es el momento de asignar tensores en el intérprete, copiar el búfer de datos que acabas de crear en el tensor de entrada y, luego, invocar el intérprete para realizar la inferencia:
do {
// Allocate memory for the model's input `Tensor`s.
try interpreter.allocateTensors()
// Copy the data to the input `Tensor`.
try interpreter.copy(myData, toInputAt: 0)
// Run inference by invoking the `Interpreter`.
try interpreter.invoke()
Una vez que se complete la invocación, puedes consultar el resultado del intérprete para ver los resultados.
Estos serán valores sin procesar (4 bytes por neurona) que luego deberás leer y convertir. Como este modelo en particular tiene 2 neuronas de salida, deberás leer 8 bytes que se convertirán en Float32 para el análisis. Estás trabajando con memoria de bajo nivel, por lo que se usa unsafeData.
// Get the output `Tensor` to process the inference results.
outputTensor = try interpreter.output(at: 0)
// Turn the output tensor into an array. This will have 2 values
// Value at index 0 is the probability of negative sentiment
// Value at index 1 is the probability of positive sentiment
let resultsArray = outputTensor.data
let results: [Float32] = [Float32](unsafeData: resultsArray) ?? []
Ahora es relativamente fácil analizar los datos para determinar la calidad del spam. El modelo tiene 2 resultados: el primero con la probabilidad de que el mensaje no sea spam y el segundo con la probabilidad de que sí lo sea. Por lo tanto, puedes consultar results[1] para encontrar el valor de spam:
let positiveSpamValue = results[1]
var outputString = ""
if(positiveSpamValue>0.8){
outputString = "Message not sent. Spam detected with probability: " + String(positiveSpamValue)
} else {
outputString = "Message sent!"
}
txtOutput.text = outputString
Para mayor comodidad, aquí se incluye el método completo:
func classify(sequence: [Int32]){
// Model Path is the location of the model in the bundle
let modelPath = Bundle.main.path(forResource: "model", ofType: "tflite")
var interpreter: Interpreter
do{
interpreter = try Interpreter(modelPath: modelPath!)
} catch _{
print("Error loading model!")
Return
}
let tSequence = Array(sequence)
let myData = Data(copyingBufferOf: tSequence.map { Int32($0) })
let outputTensor: Tensor
do {
// Allocate memory for the model's input `Tensor`s.
try interpreter.allocateTensors()
// Copy the data to the input `Tensor`.
try interpreter.copy(myData, toInputAt: 0)
// Run inference by invoking the `Interpreter`.
try interpreter.invoke()
// Get the output `Tensor` to process the inference results.
outputTensor = try interpreter.output(at: 0)
// Turn the output tensor into an array. This will have 2 values
// Value at index 0 is the probability of negative sentiment
// Value at index 1 is the probability of positive sentiment
let resultsArray = outputTensor.data
let results: [Float32] = [Float32](unsafeData: resultsArray) ?? []
let positiveSpamValue = results[1]
var outputString = ""
if(positiveSpamValue>0.8){
outputString = "Message not sent. Spam detected with probability: " +
String(positiveSpamValue)
} else {
outputString = "Message sent!"
}
txtOutput.text = outputString
} catch let error {
print("Failed to invoke the interpreter with error: \(error.localizedDescription)")
}
}
12. Agrega las extensiones de Swift
El código anterior usó una extensión del tipo de datos para permitirte copiar los bits sin procesar de un array Int32 en un Data. Este es el código de esa extensión:
extension Data {
/// Creates a new buffer by copying the buffer pointer of the given array.
///
/// - Warning: The given array's element type `T` must be trivial in that it can be copied bit
/// for bit with no indirection or reference-counting operations; otherwise, reinterpreting
/// data from the resulting buffer has undefined behavior.
/// - Parameter array: An array with elements of type `T`.
init<T>(copyingBufferOf array: [T]) {
self = array.withUnsafeBufferPointer(Data.init)
}
}
Cuando trabajas con memoria de bajo nivel, usas datos "inseguros", y el código anterior requiere que inicialices un array de datos inseguros. Esta extensión lo hace posible:
extension Array {
/// Creates a new array from the bytes of the given unsafe data.
///
/// - Warning: The array's `Element` type must be trivial in that it can be copied bit for bit
/// with no indirection or reference-counting operations; otherwise, copying the raw bytes in
/// the `unsafeData`'s buffer to a new array returns an unsafe copy.
/// - Note: Returns `nil` if `unsafeData.count` is not a multiple of
/// `MemoryLayout<Element>.stride`.
/// - Parameter unsafeData: The data containing the bytes to turn into an array.
init?(unsafeData: Data) {
guard unsafeData.count % MemoryLayout<Element>.stride == 0 else { return nil }
#if swift(>=5.0)
self = unsafeData.withUnsafeBytes { .init($0.bindMemory(to: Element.self)) }
#else
self = unsafeData.withUnsafeBytes {
.init(UnsafeBufferPointer<Element>(
start: $0,
count: unsafeData.count / MemoryLayout<Element>.stride
))
}
#endif // swift(>=5.0)
}
}
13. Ejecuta la app para iOS
Ejecuta y prueba la app.
Si todo salió bien, deberías ver la app en tu dispositivo de la siguiente manera:

En el caso del mensaje "¡Compra mi libro para aprender a operar en línea!", la app envía una alerta de spam detectado con una probabilidad del 99%.
14. ¡Felicitaciones!
Ahora creaste una app muy simple que filtra texto para detectar comentarios spam con un modelo que se entrenó con datos que se usaron para enviar spam a blogs.
El siguiente paso en el ciclo de vida típico del desarrollador es explorar lo que se necesitaría para personalizar el modelo en función de los datos que se encuentran en tu propia comunidad. Verás cómo hacerlo en la próxima actividad de la ruta de aprendizaje.