Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/es-419/hub/tutorials/cropnet_on_device.ipynb
25118 views
Kernel: Python 3

Licensed under the Apache License, Version 2.0 (the "License");

#@title Copyright 2021 The TensorFlow Hub Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ==============================================================================

Ajuste fino de modelos para detección de enfermedades en plantas

En estas anotaciones se muestra cómo realizar el ajuste fino de modelos CropNet de TensorFlow Hub en un conjunto de datos de TFDS o en el propio conjunto de datos de detección de enfermedades agrícolas.

Haremos lo siguiente:

  • Cargar el conjunto de datos de mandioca de TFDS o un conjunto de datos propio.

  • Enriquecer los datos con ejemplos desconocidos (negativos) para obtener un modelo más sólido.

  • Aplicar los aumentos de imágenes a los datos.

  • Cargar y realizarle el ajuste fino al modelo CropNet de TF Hub.

  • Exportar un modelo TFLite, listo para ser implementado directamente en la aplicación con la biblioteca de tareas, MLKit o TFLite.

Importaciones y dependencias

Antes de empezar, deberemos instalar algunas de las dependencias que necesitaremos, como un Model Maker y la última versión del conjunto de datos de TensorFlow.

!sudo apt install -q libportaudio2 ## image_classifier library requires numpy <= 1.23.5 !pip install "numpy<=1.23.5" !pip install --use-deprecated=legacy-resolver tflite-model-maker-nightly !pip install -U tensorflow-datasets ## scann library requires tensorflow < 2.9.0 !pip install "tensorflow<2.9.0" !pip install "tensorflow-datasets~=4.8.0" # protobuf>=3.12.2 !pip install tensorflow-metadata~=1.10.0 # protobuf>=3.13 ## tensorflowjs requires packaging < 20.10 !pip install "packaging<20.10"
import matplotlib.pyplot as plt import os import seaborn as sns import tensorflow as tf import tensorflow_datasets as tfds from tensorflow_examples.lite.model_maker.core.export_format import ExportFormat from tensorflow_examples.lite.model_maker.core.task import image_preprocessing from tflite_model_maker import image_classifier from tflite_model_maker import ImageClassifierDataLoader from tflite_model_maker.image_classifier import ModelSpec

Carga de un conjunto de datos de TFDS para realizarle el ajuste fino

Utilicemos el conjunto de datos de enfermedades de hoja de la mandioca que está disponible en TFDS.

tfds_name = 'cassava' (ds_train, ds_validation, ds_test), ds_info = tfds.load( name=tfds_name, split=['train', 'validation', 'test'], with_info=True, as_supervised=True) TFLITE_NAME_PREFIX = tfds_name

Como alternativa, la carga de los datos propios para el ajuste fino

En vez de usar un conjunto de datos de TFDS, puede entrenar su propio conjunto de datos. En este fragmento de código mostramos cómo cargar el propio conjunto de datos personalizado. Para saber más acerca de las estructuras de datos posibles, consulte este enlace. A continuación, usamos el ejemplo del conjunto de datos sobre enfermedades de hoja de la mandioca, que es de acceso público.

# data_root_dir = tf.keras.utils.get_file( # 'cassavaleafdata.zip', # 'https://storage.googleapis.com/emcassavadata/cassavaleafdata.zip', # extract=True) # data_root_dir = os.path.splitext(data_root_dir)[0] # Remove the .zip extension # builder = tfds.ImageFolder(data_root_dir) # ds_info = builder.info # ds_train = builder.as_dataset(split='train', as_supervised=True) # ds_validation = builder.as_dataset(split='validation', as_supervised=True) # ds_test = builder.as_dataset(split='test', as_supervised=True)

Visualización de muestras de una división de entrenamiento

Echemos un vistazo a algunos ejemplos del conjunto de datos que incluyen el ID y el nombre de la clase para las muestras de imágenes y sus etiquetas.

_ = tfds.show_examples(ds_train, ds_info)

Imágenes agregadas para ser usadas como ejemplos desconocidos de conjuntos de datos de TFDS

Agreguemos más ejemplos desconocidos (negativos) al conjunto de datos de entrenamiento y asignémosle un nuevo número de etiqueta de clase desconocida. El objetivo es tener un modelo que, puesto en práctica, tenga la opción de predecir lo "desconocido" cuando nota algo inesperado.

A continuación, podremos ver una lista de conjuntos de datos que se usará como muestra para el resto de las imágenes desconocidas. Incluye 3 conjuntos diferentes de datos para aumentar la diversidad. Uno de ellos es un conjunto de datos de enfermedades de hoja en frijoles, para que el modelo esté expuesto a otras plantas con enfermedades que no sean la mandioca.

UNKNOWN_TFDS_DATASETS = [{ 'tfds_name': 'imagenet_v2/matched-frequency', 'train_split': 'test[:80%]', 'test_split': 'test[80%:]', 'num_examples_ratio_to_normal': 1.0, }, { 'tfds_name': 'oxford_flowers102', 'train_split': 'train', 'test_split': 'test', 'num_examples_ratio_to_normal': 1.0, }, { 'tfds_name': 'beans', 'train_split': 'train', 'test_split': 'test', 'num_examples_ratio_to_normal': 1.0, }]

Los conjuntos de datos DESCONOCIDOS (UNKNOWN) también se cargan desde TFDS.

# Load unknown datasets. weights = [ spec['num_examples_ratio_to_normal'] for spec in UNKNOWN_TFDS_DATASETS ] num_unknown_train_examples = sum( int(w * ds_train.cardinality().numpy()) for w in weights) ds_unknown_train = tf.data.Dataset.sample_from_datasets([ tfds.load( name=spec['tfds_name'], split=spec['train_split'], as_supervised=True).repeat(-1) for spec in UNKNOWN_TFDS_DATASETS ], weights).take(num_unknown_train_examples) ds_unknown_train = ds_unknown_train.apply( tf.data.experimental.assert_cardinality(num_unknown_train_examples)) ds_unknown_tests = [ tfds.load( name=spec['tfds_name'], split=spec['test_split'], as_supervised=True) for spec in UNKNOWN_TFDS_DATASETS ] ds_unknown_test = ds_unknown_tests[0] for ds in ds_unknown_tests[1:]: ds_unknown_test = ds_unknown_test.concatenate(ds) # All examples from the unknown datasets will get a new class label number. num_normal_classes = len(ds_info.features['label'].names) unknown_label_value = tf.convert_to_tensor(num_normal_classes, tf.int64) ds_unknown_train = ds_unknown_train.map(lambda image, _: (image, unknown_label_value)) ds_unknown_test = ds_unknown_test.map(lambda image, _: (image, unknown_label_value)) # Merge the normal train dataset with the unknown train dataset. weights = [ ds_train.cardinality().numpy(), ds_unknown_train.cardinality().numpy() ] ds_train_with_unknown = tf.data.Dataset.sample_from_datasets( [ds_train, ds_unknown_train], [float(w) for w in weights]) ds_train_with_unknown = ds_train_with_unknown.apply( tf.data.experimental.assert_cardinality(sum(weights))) print((f"Added {ds_unknown_train.cardinality().numpy()} negative examples." f"Training dataset has now {ds_train_with_unknown.cardinality().numpy()}" ' examples in total.'))

Aplicación de aumentos

En todas las imágenes, para hacerlas más diversas, aplicaremos algo de aumento. Como cambios en lo siguiente:

  • Brillo

  • Contraste

  • Saturación

  • Tonalidad

  • Recorte

Estos tipos de "aumentos" ayudan a hacer al modelo más sólido para las variaciones en entradas de imágenes.

def random_crop_and_random_augmentations_fn(image): # preprocess_for_train does random crop and resize internally. image = image_preprocessing.preprocess_for_train(image) image = tf.image.random_brightness(image, 0.2) image = tf.image.random_contrast(image, 0.5, 2.0) image = tf.image.random_saturation(image, 0.75, 1.25) image = tf.image.random_hue(image, 0.1) return image def random_crop_fn(image): # preprocess_for_train does random crop and resize internally. image = image_preprocessing.preprocess_for_train(image) return image def resize_and_center_crop_fn(image): image = tf.image.resize(image, (256, 256)) image = image[16:240, 16:240] return image no_augment_fn = lambda image: image train_augment_fn = lambda image, label: ( random_crop_and_random_augmentations_fn(image), label) eval_augment_fn = lambda image, label: (resize_and_center_crop_fn(image), label)

Para aplicar el aumento, se usa el método map de la clase del conjunto de datos.

ds_train_with_unknown = ds_train_with_unknown.map(train_augment_fn) ds_validation = ds_validation.map(eval_augment_fn) ds_test = ds_test.map(eval_augment_fn) ds_unknown_test = ds_unknown_test.map(eval_augment_fn)

Encapsulamiento de los datos en un formato adecuado para Model Maker

Para usar estos conjuntos de datos con Model Maker, deben estar en una clase ImageClassifierDataLoader.

label_names = ds_info.features['label'].names + ['UNKNOWN'] train_data = ImageClassifierDataLoader(ds_train_with_unknown, ds_train_with_unknown.cardinality(), label_names) validation_data = ImageClassifierDataLoader(ds_validation, ds_validation.cardinality(), label_names) test_data = ImageClassifierDataLoader(ds_test, ds_test.cardinality(), label_names) unknown_test_data = ImageClassifierDataLoader(ds_unknown_test, ds_unknown_test.cardinality(), label_names)

Ejecución del entrenamiento

TensorFlow Hub tiene varios modelos disponibles para aprendizaje por transferencia.

Puede elegir uno solo o varios para experimentar con otros e intentar obtener mejores resultados.

Si quiere incluso más modelos para probar, puede agregarlos desde esta colección.

#@title Choose a base model model_name = 'mobilenet_v3_large_100_224' #@param ['cropnet_cassava', 'cropnet_concat', 'cropnet_imagenet', 'mobilenet_v3_large_100_224'] map_model_name = { 'cropnet_cassava': 'https://tfhub.dev/google/cropnet/feature_vector/cassava_disease_V1/1', 'cropnet_concat': 'https://tfhub.dev/google/cropnet/feature_vector/concat/1', 'cropnet_imagenet': 'https://tfhub.dev/google/cropnet/feature_vector/imagenet/1', 'mobilenet_v3_large_100_224': 'https://tfhub.dev/google/imagenet/mobilenet_v3_large_100_224/feature_vector/5', } model_handle = map_model_name[model_name]

Para efectuar el ajuste fino del modelo, usaremos Model Maker. La solución general resulta más fácil porque después de entrenar el modelo, lo convierte para TFLite.

Model Maker hace que esta conversión sea la mejor posible y que tenga además toda la información necesaria para implementar sin problemas el modelo en dispositivos, más adelante.

Las especificaciones del modelo indican cómo decirle a Model Maker qué modelo base le gustaría usar.

image_model_spec = ModelSpec(uri=model_handle)

Un detalle importante reside en la configuración del train_whole_model que realizará el ajuste fino del modelo base durante el entrenamiento. De este modo, el proceso se vuelve más lento, pero el modelo final tiene mayor exactitud. Con la definición de shuffle se garantizará que el modelo verá los datos en orden aleatorio, una buena práctica para el aprendizaje con modelos.

model = image_classifier.create( train_data, model_spec=image_model_spec, batch_size=128, learning_rate=0.03, epochs=5, shuffle=True, train_whole_model=True, validation_data=validation_data)

Evaluación del modelo en una división de prueba

model.evaluate(test_data)

Para entender mejor un modelo al que se le ha realizado el ajuste fino, nos convendrá analizar la matriz de confusión. Esto nos permitirá ver con qué frecuencia se puede predecir una clase con otra.

def predict_class_label_number(dataset): """Runs inference and returns predictions as class label numbers.""" rev_label_names = {l: i for i, l in enumerate(label_names)} return [ rev_label_names[o[0][0]] for o in model.predict_top_k(dataset, batch_size=128) ] def show_confusion_matrix(cm, labels): plt.figure(figsize=(10, 8)) sns.heatmap(cm, xticklabels=labels, yticklabels=labels, annot=True, fmt='g') plt.xlabel('Prediction') plt.ylabel('Label') plt.show()
confusion_mtx = tf.math.confusion_matrix( list(ds_test.map(lambda x, y: y)), predict_class_label_number(test_data), num_classes=len(label_names)) show_confusion_matrix(confusion_mtx, label_names)

Evaluación del modelo con datos de prueba desconocidos

En esta evaluación esperamos que el modelo tenga una precisión de al menos 1. Ninguna de las imágenes con las que se prueba el modelo está relacionada con el conjunto de datos normal y, por lo tanto, esperamos que el modelo prediga una etiqueta de clase "desconocida".

model.evaluate(unknown_test_data)

Imprimimos la matriz de confusión.

unknown_confusion_mtx = tf.math.confusion_matrix( list(ds_unknown_test.map(lambda x, y: y)), predict_class_label_number(unknown_test_data), num_classes=len(label_names)) show_confusion_matrix(unknown_confusion_mtx, label_names)

Exportación del modelo como TFLite y SavedModel

Ahora podemos exportar los modelos entrenados en formatos TFLite y SavedModel para su implementación en dispositivos y, además, podemos usarlos para inferencias en TensorFlow.

tflite_filename = f'{TFLITE_NAME_PREFIX}_model_{model_name}.tflite' model.export(export_dir='.', tflite_filename=tflite_filename)
# Export saved model version. model.export(export_dir='.', export_format=ExportFormat.SAVED_MODEL)

Próximos pasos

El modelo que acabamos de entrenar se puede usar en dispositivos móviles (¡incluso en el campo mismo!).

Descargue el modelo, haga clic en el ícono de la carpeta del menú de archivos (Files) a la izquierda en Colab y elija la opción a descargar.

La misma técnica que aquí usamos se podría aplicar a otras tareas de enfermedades de plantas que podrían ser más adecuadas para sus casos de uso o para cualquier otro tipo de tarea de clasificación de imágenes. Si desea continuar con lo que hicimos y realizar una implementación con una aplicación Android, puede seguir leyendo la guía de inicio rápido para Android.