Introducción a Vertex Pipelines

1. Descripción general

En este lab, aprenderás a crear y ejecutar canalizaciones de AA con Vertex Pipelines.

Qué aprenderá

Aprenderás a hacer lo siguiente:

  • Aprenderás a usar el SDK de canalizaciones de Kubeflow para compilar canalizaciones de AA escalables.
  • Aprenderás a crear y ejecutar una canalización de introducción en 3 pasos que admite entradas de texto.
  • Aprenderás a crear y ejecutar una canalización que entrene, evalúe y, luego, implemente un modelo de clasificación de AutoML.
  • Aprenderás a usar componentes predefinidos para interactuar con los servicios de Vertex AI que se proporcionan mediante la biblioteca google_cloud_pipeline_components.
  • Aprenderás a programar un trabajo de canalización con Cloud Scheduler.

El costo total de la ejecución de este lab en Google Cloud es de aproximadamente $25.

2. Introducción a Vertex AI

En este lab, se utiliza la oferta de productos de IA más reciente de Google Cloud. Vertex AI integra las ofertas de AA de Google Cloud en una experiencia de desarrollo fluida. Anteriormente, se podía acceder a los modelos personalizados y a los entrenados con AutoML mediante servicios independientes. La nueva oferta combina ambos en una sola API, junto con otros productos nuevos. También puedes migrar proyectos existentes a Vertex AI.

Además del entrenamiento de modelos y los servicios de implementación, Vertex AI también incluye una variedad de productos de MLOps, incluidos Vertex Pipelines (el producto en el que se enfoca este lab), Model Monitoring, Feature Store y muchos más. Puedes ver todas las ofertas de productos de Vertex AI en el diagrama que se muestra a continuación.

Descripción general del producto Vertex

Si tienes comentarios, consulta la página de asistencia.

¿Por qué son útiles las canalizaciones de AA?

Antes de comenzar, primero debes comprender por qué deberías usar canalizaciones. Imagina que estás creando un flujo de trabajo de AA que incluye procesar datos, entrenar un modelo, ajustar hiperparámetros y realizar evaluaciones, así como implementar modelos. Es posible que cada uno de estos pasos tenga dependencias diferentes, lo que podría ser difícil de manejar si tratas todo el flujo de trabajo como una aplicación monolítica. A medida que empiezas a escalar tu proceso de AA, es posible que quieras compartir el flujo de trabajo de AA con otras personas de tu equipo para que puedan ejecutarlo y agregar más código. Pero esto puede ser complicado sin un proceso confiable y reproducible. Con las canalizaciones, cada paso en tu proceso de AA tiene su propio contenedor. Así, podrás desarrollar pasos de forma independiente y hacer un seguimiento de la entrada y salida en cada paso de manera reproducible. Además, puedes programar o activar ejecuciones para tu canalización en función de otros eventos de tu entorno de Cloud, como iniciar la ejecución de una canalización cuando hay nuevos datos de entrenamiento disponibles.

Resumen: Las canalizaciones te ayudan a automatizar y reproducir tu flujo de trabajo de AA.

3. Configura el entorno de Cloud

Para ejecutar este codelab, necesitarás un proyecto de Google Cloud Platform que tenga habilitada la facturación. Para crear un proyecto, sigue estas instrucciones.

Paso 1: Inicia Cloud Shell

En este lab, trabajarás con una sesión de Cloud Shell, que es un intérprete de comandos alojado en una máquina virtual que se ejecuta en la nube de Google. Podrías ejecutar fácilmente esta sección de forma local, en tu computadora, pero Cloud Shell brinda una experiencia reproducible en un entorno coherente para todo el mundo. Después de este lab, puedes volver a probar esta sección en tu computadora.

Cómo autorizar Cloud Shell

Activar Cloud Shell

En la parte superior derecha de la consola de Cloud, haz clic en el siguiente botón para Activar Cloud Shell:

Activar Cloud Shell

Si nunca iniciaste Cloud Shell, aparecerá una pantalla intermedia (mitad inferior de la página) que describe en qué consiste. Si ese es el caso, haz clic en Continuar (y no volverás a verla). Así es como se ve la pantalla única:

Configuración de Cloud Shell

El aprovisionamiento y la conexión a Cloud Shell solo tomará unos minutos.

Inicio de Cloud Shell

Esta máquina virtual está cargada con todas las herramientas de desarrollo que necesitarás. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Gran parte de tu trabajo en este codelab, si no todo, se puede hacer simplemente con un navegador o tu Chromebook.

Una vez conectado a Cloud Shell, debería ver que ya se autenticó y que el proyecto ya se configuró con tu ID del proyecto.

En Cloud Shell, ejecuta el siguiente comando para confirmar que tienes la autenticación:

gcloud auth list

Deberías ver algo como esto en el resultado del comando:

Resultado de Cloud Shell

En Cloud Shell, ejecuta el siguiente comando para confirmar que el comando gcloud conoce tu proyecto:

gcloud config list project

Resultado del comando

[core]
project = <PROJECT_ID>

De lo contrario, puedes configurarlo con el siguiente comando:

gcloud config set project <PROJECT_ID>

Resultado del comando

Updated property [core/project].

Cloud Shell tiene algunas variables de entorno, incluida GOOGLE_CLOUD_PROJECT, que contiene el nombre de nuestro proyecto de Cloud actual. La usaremos en varias secciones de este lab. Para verla, debes ejecutar lo siguiente:

echo $GOOGLE_CLOUD_PROJECT

Paso 2: Habilitar las API

En pasos posteriores, verás en qué momento se necesitan estos servicios y por qué. Por ahora, ejecuta este comando para que tu proyecto pueda acceder a los servicios de Compute Engine, Container Registry y Vertex AI:

gcloud services enable compute.googleapis.com         \
                       containerregistry.googleapis.com  \
                       aiplatform.googleapis.com  \
                       cloudbuild.googleapis.com \
                       cloudfunctions.googleapis.com

Si se realizó correctamente, se mostrará un mensaje similar a este:

Operation "operations/acf.cc11852d-40af-47ad-9d59-477a12847c9e" finished successfully.

Paso 3: Crea un bucket de Cloud Storage

Para ejecutar un trabajo de entrenamiento en Vertex AI, necesitaremos un bucket de almacenamiento para almacenar los elementos del modelo guardados. El bucket debe ser regional. Aquí usaremos us-central, pero puedes utilizar otra región (solo reemplázala donde corresponda en el lab). Si ya tiene un bucket, puede omitir este paso.

Ejecuta los siguientes comandos en la terminal de Cloud Shell para crear un bucket:

BUCKET_NAME=gs://$GOOGLE_CLOUD_PROJECT-bucket
gsutil mb -l us-central1 $BUCKET_NAME

Luego, debemos dar a la cuenta de servicio de Compute acceso a este bucket. Esto garantizará que Vertex Pipelines tenga los permisos necesarios para escribir archivos en el bucket. Ejecute el siguiente comando para agregar este permiso:

gcloud projects describe $GOOGLE_CLOUD_PROJECT > project-info.txt
PROJECT_NUM=$(cat project-info.txt | sed -nre 's:.*projectNumber\: (.*):\1:p')
SVC_ACCOUNT="${PROJECT_NUM//\'/}-compute@developer.gserviceaccount.com"
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT --member serviceAccount:$SVC_ACCOUNT --role roles/storage.objectAdmin

Paso 4: Crea una instancia de Vertex AI Workbench

En la sección Vertex AI de Cloud Console, haz clic en Workbench:

Menú Vertex AI

Allí, en Notebooks administrados por el usuario, haz clic en Nuevo notebook:

Crear notebook nuevo

Luego, selecciona el tipo de instancia TensorFlow Enterprise 2.3 (con LTS) sin GPUs:

Instancia de TFE

Usa las opciones predeterminadas y, luego, haz clic en Crear.

Paso 5: Abre tu notebook

Una vez que se crea la instancia, selecciona Abrir JupyterLab:

Abrir notebook

4. Configuración de Vertex Pipelines

Existen algunas bibliotecas adicionales que debemos instalar para usar Vertex Pipelines:

  • Kubeflow Pipelines: Este es el SDK que usaremos para compilar nuestra canalización. Vertex Pipelines admite canalizaciones en ejecución compiladas con Kubeflow Pipelines o TFX.
  • Componentes de canalizaciones de Google Cloud: Esta biblioteca proporciona componentes predefinidos que facilitan la interacción con los servicios de Vertex AI durante los pasos de tu canalización.

Paso 1: Crea un notebook de Python y, luego, instala bibliotecas

Primero, en el menú Selector de tu instancia de notebook, selecciona Python 3 para crear un notebook:

Crear notebook de Python 3

Para acceder al menú Selector, haz clic en Acceder + en la esquina superior izquierda de tu instancia de notebook.

Para instalar los dos servicios que usaremos en este lab, primero hay que establecer la marca de usuario en una celda del notebook:

USER_FLAG = "--user"

Luego, ejecuta el siguiente comando en tu notebook:

!pip3 install {USER_FLAG} google-cloud-aiplatform==1.7.0 --upgrade
!pip3 install {USER_FLAG} kfp==1.8.9 google-cloud-pipeline-components==0.2.0

Luego de instalar estos paquetes, deberá reiniciar el kernel:

import os

if not os.getenv("IS_TESTING"):
    # Automatically restart kernel after installs
    import IPython

    app = IPython.Application.instance()
    app.kernel.do_shutdown(True)

Finalmente, verifique que instaló los paquetes de forma correcta. La versión del SDK de KFP debe ser igual o superior a 1.8:

!python3 -c "import kfp; print('KFP SDK version: {}'.format(kfp.__version__))"
!python3 -c "import google_cloud_pipeline_components; print('google_cloud_pipeline_components version: {}'.format(google_cloud_pipeline_components.__version__))"

Paso 2: Configura tu ID del proyecto y bucket

Durante este lab, podrá hacer referencia al ID del proyecto de Cloud y al bucket que creó anteriormente. A continuación, crearemos variables para cada uno de ellos.

Si desconoce el ID de su proyecto, probablemente logre obtenerlo mediante la ejecución del siguiente comando:

import os
PROJECT_ID = ""

# Get your Google Cloud project ID from gcloud
if not os.getenv("IS_TESTING"):
    shell_output=!gcloud config list --format 'value(core.project)' 2>/dev/null
    PROJECT_ID = shell_output[0]
    print("Project ID: ", PROJECT_ID)

De lo contrario, configúralo aquí:

if PROJECT_ID == "" or PROJECT_ID is None:
    PROJECT_ID = "your-project-id"  # @param {type:"string"}

Luego, cree una variable para almacenar el nombre de su bucket. Si lo creó en este lab, lo siguiente funcionará. De lo contrario, deberá configurarlo de forma manual:

BUCKET_NAME="gs://" + PROJECT_ID + "-bucket"

Paso 3: Importa bibliotecas

Agrega lo siguiente para importar las bibliotecas que utilizaremos durante este codelab:

import kfp

from kfp.v2 import compiler, dsl
from kfp.v2.dsl import component, pipeline, Artifact, ClassificationMetrics, Input, Output, Model, Metrics

from google.cloud import aiplatform
from google_cloud_pipeline_components import aiplatform as gcc_aip
from typing import NamedTuple

Paso 4: Define las constantes

Lo último que debemos hacer antes de crear nuestra canalización es definir algunas variables constantes. PIPELINE_ROOT es la ruta de Cloud Storage en la que se escribirán los artefactos que cree nuestra canalización. Aquí usaremos us-central1 como la región, pero si usaste una región diferente cuando creaste tu bucket, actualiza la variable REGION en el siguiente código:

PATH=%env PATH
%env PATH={PATH}:/home/jupyter/.local/bin
REGION="us-central1"

PIPELINE_ROOT = f"{BUCKET_NAME}/pipeline_root/"
PIPELINE_ROOT

Luego de ejecutar el código anterior, debería ver impreso el directorio raíz de su canalización. Esta es la ubicación de Cloud Storage en la que se escribirán los artefactos de su canalización, Tendrá el formato gs://YOUR-BUCKET-NAME/pipeline_root/.

5. Cree su primera canalización

Para familiarizarnos con el funcionamiento de Vertex Pipelines, primero crearemos una canalización corta con el SDK de KFP. Esta canalización no ejecuta ninguna acción relacionada con el AA (no se preocupe, ya llegaremos a ese paso). Mediante este ejercicio, buscamos que aprenda los siguientes procesos:

  • Cómo crear componentes personalizados en el SDK de KFP
  • Cómo ejecutar y supervisar una canalización en Vertex Pipelines

Crearemos una canalización que imprima una oración mediante dos salidas: el nombre de un producto y una descripción en forma de emoji. Esta canalización consistirá en tres componentes:

  • product_name: Este componente tomará un nombre de producto (o el sustantivo que quieras) como entrada y mostrará esa cadena como salida.
  • emoji: Este componente tomará la descripción de texto de un emoji y lo convertirá en un emoji. Por ejemplo, el código de texto para ✨ es “sparkles”. Este componente usa una biblioteca de emojis para mostrarte cómo administrar dependencias externas en tu canalización
  • build_sentence: Este componente final consumirá la salida de los dos componentes anteriores para crear una oración que utilice el emoji. Por ejemplo, la salida sería “Vertex Pipelines es ✨”.

Comencemos a programar.

Paso 1: Crea un componente basado en una función de Python

Con el SDK de KFP, podemos crear componentes basados en las funciones de Python. Lo usaremos para los 3 componentes de nuestra canalización. Primero, compilaremos el componente product_name, que simplemente toma una cadena como entrada y la muestra. Agregue el siguiente comando a su notebook:

@component(base_image="python:3.9", output_component_file="first-component.yaml")
def product_name(text: str) -> str:
    return text

Analicemos con mayor detalle esta sintaxis:

  • El decorador @component compila esta función en un componente cuando se ejecuta la canalización. Lo utilizarás cada vez que escribas un componente personalizado.
  • El parámetro base_image especifica la imagen de contenedor que usará este componente.
  • El parámetro output_component_file es opcional y especifica el archivo yaml en el que se escribirá el componente compilado. Luego de ejecutar la celda, deberías ver que ese archivo se escribió en tu instancia de notebook. Si quieres compartir este componente con otra persona, puedes enviar el archivo yaml generado para que lo cargue con el siguiente comando:
product_name_component = kfp.components.load_component_from_file('./first-component.yaml')
  • El -> str que aparece después de la definición de la función especifica el tipo de resultado de este componente.

Paso 2: Crea dos componentes adicionales

Crearemos dos componentes más para completar nuestra canalización. El primero toma una string como entrada y la convierte en su emoji correspondiente, si es que tiene alguno. De esta forma, devolverá una tupla con el texto de entrada proporcionado, y el emoji resultante:

@component(packages_to_install=["emoji"])
def emoji(
    text: str,
) -> NamedTuple(
    "Outputs",
    [
        ("emoji_text", str),  # Return parameters
        ("emoji", str),
    ],
):
    import emoji

    emoji_text = text
    emoji_str = emoji.emojize(':' + emoji_text + ':', language='alias')
    print("output one: {}; output_two: {}".format(emoji_text, emoji_str))
    return (emoji_text, emoji_str)

Este componente es un poco más complejo que el anterior. Desglosémoslo de nuevo:

  • El parámetro packages_to_install indica al componente si hay dependencias de bibliotecas externas para este contenedor. En este caso, usaremos una biblioteca llamada emoji.
  • Este componente muestra un NamedTuple llamado Outputs. Ten en cuenta que cada cadena de esta tupla tiene claves: emoji_text y emoji. Utilizaremos estas claves en el próximo componente para acceder al resultado.

El componente final de esta canalización consumirá el resultado de las dos primeras y los combinará para mostrar una string:

@component
def build_sentence(
    product: str,
    emoji: str,
    emojitext: str
) -> str:
    print("We completed the pipeline, hooray!")
    end_str = product + " is "
    if len(emoji) > 0:
        end_str += emoji
    else:
        end_str += emojitext
    return(end_str)

Quizás te estés preguntando, ¿cómo sabe este componente que debe utilizar el resultado de los pasos anteriores que definió? Buena pregunta. Veremos esto en el próximo paso.

Paso 3: Junta los componentes en una canalización

Las definiciones indicadas de los componentes crearon funciones de fábrica que se pueden utilizar en una definición de canalización para crear pasos. Para configurar una canalización, usa el decorador @pipeline, asógale un nombre y una descripción, y proporciona la ruta raíz en la que se deben escribir los artefactos de tu canalización. Con los artefactos, hacemos referencia a los archivos de salida que genera su canalización. Si bien esta canalización de introducción no genera ninguno, la siguiente lo hará.

En el siguiente bloque de código, definimos una función intro_pipeline. Aquí es donde especificamos las entradas a nuestros pasos iniciales de la canalización y cómo los pasos se conectan entre sí:

  • product_task toma un nombre de producto como entrada. Aquí transferimos canalizaciones de “Vertex Pipelines”, pero puedes cambiar esta configuración según tus necesidades.
  • emoji_task toma el código de texto de un emoji como entrada. También puedes cambiarlo según tus necesidades. Por ejemplo, “party_face” hace referencia al emoji 🥳. Ten en cuenta que, como ni este componente ni el product_task tienen pasos que les otorguen entradas, debemos especificarlas de forma manual cuando definamos la canalización.
  • El último paso de nuestra canalización, consumer_task, tiene tres parámetros de entrada:
    • El resultado de product_task Dado que en este paso solo se produce un resultado, podemos hacer referencia a él mediante product_task.output.
    • El resultado emoji de nuestro paso emoji_task. Consulta el componente emoji que se definió anteriormente en el que mencionamos los parámetros del resultado.
    • De manera similar, el resultado con nombre emoji_text del componente emoji. En caso de que a nuestra canalización se le transfiera texto que no corresponde con el emoji, lo utilizará para construir una oración.
@pipeline(
    name="hello-world",
    description="An intro pipeline",
    pipeline_root=PIPELINE_ROOT,
)

# You can change the `text` and `emoji_str` parameters here to update the pipeline output
def intro_pipeline(text: str = "Vertex Pipelines", emoji_str: str = "sparkles"):
    product_task = product_name(text)
    emoji_task = emoji(emoji_str)
    consumer_task = build_sentence(
        product_task.output,
        emoji_task.outputs["emoji"],
        emoji_task.outputs["emoji_text"],
    )

Paso 4: Compila y ejecuta la canalización

Luego de que definas la canalización, la podrás compilar. El siguiente comando generará un archivo JSON que utilizará para ejecutarla:

compiler.Compiler().compile(
    pipeline_func=intro_pipeline, package_path="intro_pipeline_job.json"
)

A continuación, crea una variable TIMESTAMP. Lo usaremos en nuestro ID de trabajo:

from datetime import datetime

TIMESTAMP = datetime.now().strftime("%Y%m%d%H%M%S")

Luego, define tu trabajo de canalización:

job = aiplatform.PipelineJob(
    display_name="hello-world-pipeline",
    template_path="intro_pipeline_job.json",
    job_id="hello-world-pipeline-{0}".format(TIMESTAMP),
    enable_caching=True
)

Por último, ejecuta el trabajo para crear una nueva ejecución de canalización:

job.submit()

Después de ejecutar esta celda, deberías ver registros con un vínculo para ver la ejecución de la canalización en tu consola:

Registros de trabajos de canalización

Navega a ese vínculo. Tu canalización debería verse así cuando se complete:

Canalización de introducción completada

Esta canalización demorará de 5 a 6 minutos en ejecutarse. Cuando se complete, puedes hacer clic en el componente build-sentence para ver el resultado final:

Resultado de la canalización de introducción

Ahora que sabes cómo funcionan el SDK de KFP y Vertex Pipelines, ya puedes compilar una canalización que cree y, luego, implemente un modelo de AA con otros servicios de Vertex AI. ¡Comencemos!

6. Crea una canalización de AA de extremo a extremo

Llegó la hora de compilar su primera canalización de AA. En esta canalización, usaremos el conjunto de datos de frijoles secos de UCI Machine Learning de KOKLU, M. y OZKAN, I.A., (2020), “Multiclass Classification of Dry Beans Using Computer Vision and Machine Learning Techniques”.En Computers and Electronics in Agriculture, 174, 105507. DOI.

Este es un conjunto de datos tabular que, en nuestra canalización, lo usaremos para entrenar, evaluar y, luego, implementar un modelo de AutoML que clasifique frijoles en uno de 7 tipos, según sus características.

Con esta canalización podrás realizar las siguientes acciones:

  • Crea un conjunto de datos en .
  • Entrena un modelo de clasificación tabular con AutoML
  • Obtener métricas de evaluación sobre este modelo
  • Según las métricas de evaluación, decidir si quieres implementar el modelo mediante una lógica condicional en Vertex Pipelines
  • Implementa el modelo en un extremo con Vertex Prediction

Cada paso descrito será un componente. La mayoría de los pasos de la canalización usarán componentes predefinidos para los servicios de Vertex AI a través de la biblioteca google_cloud_pipeline_components que importamos antes en este codelab. En esta sección, primero definiremos un componente personalizado. Luego, definiremos el resto de los pasos de la canalización mediante componentes predefinidos. Estos componentes facilitan el acceso a los servicios de Vertex AI, como el entrenamiento y la implementación de modelos.

Paso 1: Un componente personalizado para la evaluación de modelos

El componente personalizado que definamos se utilizará al final de nuestra canalización, una vez que se complete el entrenamiento de modelos. Este componente realizará algunas acciones:

  • Obtener las métricas de evaluación del modelo de clasificación entrenado de AutoML
  • Analizar las métricas y renderizarlas en la IU de Vertex Pipelines
  • Comparar las métricas con un umbral para determinar si se debe implementar el modelo

Antes de definir el componente, comprendamos sus parámetros de entrada y salida. Como entrada, esta canalización toma algunos metadatos sobre nuestro proyecto de Cloud, el modelo entrenado resultante (definiremos este componente más adelante), las métricas de evaluación del modelo y un thresholds_dict_str. El thresholds_dict_str es algo que definiremos cuando ejecutemos nuestra canalización. En el caso de este modelo de clasificación, será el valor del área bajo la curva ROC para el que deberíamos implementar el modelo. Por ejemplo, si pasamos 0.95, significa que solo queremos que nuestra canalización implemente el modelo si esta métrica es superior al 95%.

Nuestro componente de evaluación devuelve una cadena que indica si implementar el modelo o no. Agrega el siguiente comando a una celda de notebook para crear este componente personalizado:

@component(
    base_image="gcr.io/deeplearning-platform-release/tf2-cpu.2-3:latest",
    output_component_file="tabular_eval_component.yaml",
    packages_to_install=["google-cloud-aiplatform"],
)
def classification_model_eval_metrics(
    project: str,
    location: str,  # "us-central1",
    api_endpoint: str,  # "us-central1-aiplatform.googleapis.com",
    thresholds_dict_str: str,
    model: Input[Artifact],
    metrics: Output[Metrics],
    metricsc: Output[ClassificationMetrics],
) -> NamedTuple("Outputs", [("dep_decision", str)]):  # Return parameter.

    import json
    import logging

    from google.cloud import aiplatform as aip

    # Fetch model eval info
    def get_eval_info(client, model_name):
        from google.protobuf.json_format import MessageToDict

        response = client.list_model_evaluations(parent=model_name)
        metrics_list = []
        metrics_string_list = []
        for evaluation in response:
            print("model_evaluation")
            print(" name:", evaluation.name)
            print(" metrics_schema_uri:", evaluation.metrics_schema_uri)
            metrics = MessageToDict(evaluation._pb.metrics)
            for metric in metrics.keys():
                logging.info("metric: %s, value: %s", metric, metrics[metric])
            metrics_str = json.dumps(metrics)
            metrics_list.append(metrics)
            metrics_string_list.append(metrics_str)

        return (
            evaluation.name,
            metrics_list,
            metrics_string_list,
        )

    # Use the given metrics threshold(s) to determine whether the model is
    # accurate enough to deploy.
    def classification_thresholds_check(metrics_dict, thresholds_dict):
        for k, v in thresholds_dict.items():
            logging.info("k {}, v {}".format(k, v))
            if k in ["auRoc", "auPrc"]:  # higher is better
                if metrics_dict[k] < v:  # if under threshold, don't deploy
                    logging.info("{} < {}; returning False".format(metrics_dict[k], v))
                    return False
        logging.info("threshold checks passed.")
        return True

    def log_metrics(metrics_list, metricsc):
        test_confusion_matrix = metrics_list[0]["confusionMatrix"]
        logging.info("rows: %s", test_confusion_matrix["rows"])

        # log the ROC curve
        fpr = []
        tpr = []
        thresholds = []
        for item in metrics_list[0]["confidenceMetrics"]:
            fpr.append(item.get("falsePositiveRate", 0.0))
            tpr.append(item.get("recall", 0.0))
            thresholds.append(item.get("confidenceThreshold", 0.0))
        print(f"fpr: {fpr}")
        print(f"tpr: {tpr}")
        print(f"thresholds: {thresholds}")
        metricsc.log_roc_curve(fpr, tpr, thresholds)

        # log the confusion matrix
        annotations = []
        for item in test_confusion_matrix["annotationSpecs"]:
            annotations.append(item["displayName"])
        logging.info("confusion matrix annotations: %s", annotations)
        metricsc.log_confusion_matrix(
            annotations,
            test_confusion_matrix["rows"],
        )

        # log textual metrics info as well
        for metric in metrics_list[0].keys():
            if metric != "confidenceMetrics":
                val_string = json.dumps(metrics_list[0][metric])
                metrics.log_metric(metric, val_string)
        # metrics.metadata["model_type"] = "AutoML Tabular classification"

    logging.getLogger().setLevel(logging.INFO)
    aip.init(project=project)
    # extract the model resource name from the input Model Artifact
    model_resource_path = model.metadata["resourceName"]
    logging.info("model path: %s", model_resource_path)

    client_options = {"api_endpoint": api_endpoint}
    # Initialize client that will be used to create and send requests.
    client = aip.gapic.ModelServiceClient(client_options=client_options)
    eval_name, metrics_list, metrics_str_list = get_eval_info(
        client, model_resource_path
    )
    logging.info("got evaluation name: %s", eval_name)
    logging.info("got metrics list: %s", metrics_list)
    log_metrics(metrics_list, metricsc)

    thresholds_dict = json.loads(thresholds_dict_str)
    deploy = classification_thresholds_check(metrics_list[0], thresholds_dict)
    if deploy:
        dep_decision = "true"
    else:
        dep_decision = "false"
    logging.info("deployment decision is %s", dep_decision)

    return (dep_decision,)

Paso 2: Agrega componentes predefinidos de Google Cloud

En este paso, definiremos el resto de los componentes de nuestra canalización y veremos cómo funcionan todos juntos. Primero, define el nombre visible de la ejecución de tu canalización con una marca de tiempo:

import time
DISPLAY_NAME = 'automl-beans{}'.format(str(int(time.time())))
print(DISPLAY_NAME)

Luego, copia el siguiente comando en una celda de notebook nueva:

@pipeline(name="automl-tab-beans-training-v2",
                  pipeline_root=PIPELINE_ROOT)
def pipeline(
    bq_source: str = "bq://aju-dev-demos.beans.beans1",
    display_name: str = DISPLAY_NAME,
    project: str = PROJECT_ID,
    gcp_region: str = "us-central1",
    api_endpoint: str = "us-central1-aiplatform.googleapis.com",
    thresholds_dict_str: str = '{"auRoc": 0.95}',
):
    dataset_create_op = gcc_aip.TabularDatasetCreateOp(
        project=project, display_name=display_name, bq_source=bq_source
    )

    training_op = gcc_aip.AutoMLTabularTrainingJobRunOp(
        project=project,
        display_name=display_name,
        optimization_prediction_type="classification",
        budget_milli_node_hours=1000,
        column_transformations=[
            {"numeric": {"column_name": "Area"}},
            {"numeric": {"column_name": "Perimeter"}},
            {"numeric": {"column_name": "MajorAxisLength"}},
            {"numeric": {"column_name": "MinorAxisLength"}},
            {"numeric": {"column_name": "AspectRation"}},
            {"numeric": {"column_name": "Eccentricity"}},
            {"numeric": {"column_name": "ConvexArea"}},
            {"numeric": {"column_name": "EquivDiameter"}},
            {"numeric": {"column_name": "Extent"}},
            {"numeric": {"column_name": "Solidity"}},
            {"numeric": {"column_name": "roundness"}},
            {"numeric": {"column_name": "Compactness"}},
            {"numeric": {"column_name": "ShapeFactor1"}},
            {"numeric": {"column_name": "ShapeFactor2"}},
            {"numeric": {"column_name": "ShapeFactor3"}},
            {"numeric": {"column_name": "ShapeFactor4"}},
            {"categorical": {"column_name": "Class"}},
        ],
        dataset=dataset_create_op.outputs["dataset"],
        target_column="Class",
    )
    model_eval_task = classification_model_eval_metrics(
        project,
        gcp_region,
        api_endpoint,
        thresholds_dict_str,
        training_op.outputs["model"],
    )

    with dsl.Condition(
        model_eval_task.outputs["dep_decision"] == "true",
        name="deploy_decision",
    ):

        endpoint_op = gcc_aip.EndpointCreateOp(
            project=project,
            location=gcp_region,
            display_name="train-automl-beans",
        )

        gcc_aip.ModelDeployOp(
            model=training_op.outputs["model"],
            endpoint=endpoint_op.outputs["endpoint"],
            dedicated_resources_min_replica_count=1,
            dedicated_resources_max_replica_count=1,
            dedicated_resources_machine_type="n1-standard-4",
        )

Veamos qué sucede en este código:

  • Primero, al igual que en nuestra canalización anterior, definimos los parámetros de entrada que utiliza esta canalización. Debemos configurarlos de forma manual, ya que no dependen del resultado de otros pasos de la canalización.
  • El resto de la canalización usa algunos componentes compilados previamente para interactuar con los servicios de Vertex AI:
    • TabularDatasetCreateOp crea un conjunto de datos tabular en Vertex AI a partir de una fuente de conjunto de datos en Cloud Storage o BigQuery. En esta canalización, pasamos los datos a través de una URL de tabla de BigQuery.
    • AutoMLTabularTrainingJobRunOp inicia un trabajo de entrenamiento de AutoML para un conjunto de datos tabular. Le pasamos algunos parámetros de configuración a este componente, incluidos el tipo de modelo (en este caso, la clasificación), algunos datos sobre las columnas, cuánto tiempo nos gustaría que se ejecute el entrenamiento y un puntero al conjunto de datos. Ten en cuenta que, para pasar el conjunto de datos a este componente, proporcionamos el resultado del componente anterior mediante dataset_create_op.outputs["dataset"].
    • EndpointCreateOp crea un extremo en Vertex AI. El extremo creado a partir de este paso se pasará como entrada al siguiente componente.
    • ModelDeployOp implementa un modelo determinado en un extremo en Vertex AI. En este caso, usamos el extremo creado en el paso anterior. Aunque existen opciones de configuración adicionales, aquí proporcionamos el tipo de máquina y el modelo del extremo que queremos implementar. Para pasar el modelo, accedemos a los resultados del paso de entrenamiento de nuestra canalización.
  • Esta canalización también hace uso de una lógica condicional, una función de Vertex Pipelines que te permite definir una condición, junto con diferentes ramas basadas en el resultado de esa condición. Recuerda que, cuando definimos nuestra canalización, pasamos un parámetro thresholds_dict_str. Este es el umbral de precisión que utilizamos para determinar si queremos implementar nuestro modelo en un extremo. Para implementarlo, usamos la clase Condition del SDK de KFP. La condición que pasamos es el resultado del componente de evaluación personalizado que definimos anteriormente en este codelab. Si esta condición es verdadera, la canalización seguirá ejecutando el componente deploy_op. Si la precisión no cumple con nuestro umbral predefinido, se detendrá la canalización y no se implementará el modelo.

Paso 3: Compila y ejecuta la canalización de AA de extremo a extremo

Luego de definir la canalización completa, es momento de compilarla:

compiler.Compiler().compile(
    pipeline_func=pipeline, package_path="tab_classif_pipeline.json"
)

A continuación, define el trabajo:

ml_pipeline_job = aiplatform.PipelineJob(
    display_name="automl-tab-beans-training",
    template_path="tab_classif_pipeline.json",
    pipeline_root=PIPELINE_ROOT,
    parameter_values={"project": PROJECT_ID, "display_name": DISPLAY_NAME},
    enable_caching=True
)

Por último, ejecuta la tarea:

ml_pipeline_job.submit()

Navega al vínculo que aparece en los registros después de ejecutar la celda anterior para ver tu canalización en la consola. Esta canalización demorará aproximadamente una hora en ejecutarse. La mayor parte del tiempo se dedica a la etapa de entrenamiento de AutoML. La canalización completa se verá de forma similar a esta:

Canalización completada de AutoML

Si presionas el botón de activación “Expand artifacts” que aparece en la parte superior, podrás ver los detalles de los diferentes artefactos que creó tu canalización. Por ejemplo, si haces clic en el artefacto dataset, verás los detalles del conjunto de datos de Vertex AI que se creó. Puedes hacer clic en este vínculo para ir a la página de ese conjunto de datos:

Conjunto de datos de la canalización

De forma similar, para ver las visualizaciones de las métricas resultantes de nuestro componente de evaluación personalizado, haz clic en el artefacto llamado metricsc. En el lado derecho del panel, podrás ver la matriz de confusión de este modelo:

Visualización de métricas

Para ver el modelo y el extremo creados a partir de la ejecución de esta canalización, ve a la sección de modelos y haz clic en el modelo llamado automl-beans. Ahí encontrarás este modelo implementado en un extremo:

Model-endpoint

También puede acceder a esta página si hace clic en el artefacto endpoint del gráfico de su canalización.

Además de mirar el gráfico de la canalización en la consola, también puedes utilizar Vertex Pipelines para hacer un seguimiento del linaje. Cuando hablamos de seguimiento del linaje, nos referimos a realizar un seguimiento de los artefactos creados durante la canalización. Esto puede ayudarnos a comprender dónde se crearon los artefactos y cómo se usan mediante un flujo de trabajo de AA. Por ejemplo, para ver el seguimiento del linaje del conjunto de datos que se creó en esta canalización, haga clic en el artefacto del conjunto de datos y, luego, en Ver linaje:

Ver linaje

Aquí se muestran todos los lugares en los que se está utilizando este artefacto:

Detalles del linaje

Paso 4: Compare las métricas en las ejecuciones de las canalizaciones

Si ejecutas esta canalización varias veces, es probable que quieras comparar las métricas de las ejecuciones. Puedes usar el método aiplatform.get_pipeline_df() para acceder a los metadatos de la ejecución. En este paso, obtendrás los metadatos de todas las ejecuciones de esta canalización y los cargarás al DataFrame de Pandas:

pipeline_df = aiplatform.get_pipeline_df(pipeline="automl-tab-beans-training-v2")
small_pipeline_df = pipeline_df.head(2)
small_pipeline_df

Con eso, habrás terminado el lab.

🎉 ¡Felicitaciones! 🎉

Aprendiste a usar Vertex AI para hacer lo siguiente:

  • Usa el SDK de Kubeflow Pipelines para compilar canalizaciones de extremo a extremo con componentes personalizados
  • Ejecuta tus canalizaciones en Vertex Pipelines y, luego, inicia las ejecuciones de estas con el SDK
  • Ver y analizar tu gráfico de Vertex Pipelines en la consola
  • Usar componentes de canalizaciones compilados previamente para agregar servicios de Vertex AI a tu canalización
  • Programar trabajos de canalización recurrentes

Para obtener más información sobre las distintas partes de Vertex, consulte la documentación.

7. Limpieza

Para que no se te cobre, te recomendamos que borres los recursos que creaste a lo largo de este lab.

Paso 1: Detén o borra tu instancia de Notebooks

Si quieres continuar usando el notebook que creaste en este lab, te recomendamos que lo desactives cuando no lo utilices. En la IU de Notebooks de la consola de Cloud, selecciona el notebook y, luego, haz clic en Detener. Si quieres borrar la instancia por completo, selecciona Borrar:

Detener instancias

Paso 2: Borra tu extremo

Para borrar el extremo que implementaste, navega a la sección Extremos de la consola de Vertex AI y haz clic en el ícono de borrar:

Borrar extremo

Luego, haz clic en Anular implementación desde el siguiente mensaje:

Anular implementación de modelo

Por último, navega a la sección Modelos en la consola, busca ese modelo y, en el menú de tres puntos de la derecha, haz clic en Borrar modelo:

Borrar modelo

Paso 3: Borra el bucket de Cloud Storage

Para borrar el bucket de almacenamiento, en el menú de navegación de la consola de Cloud, navega a Almacenamiento, selecciona tu bucket y haz clic en Borrar (Delete):

Borrar almacenamiento