Optimiser un modèle de machine learning

Logo Colab Exécuter ce tutoriel sous forme de notebook dans Colab Logo GitHub Afficher le notebook sur GitHub

Ce tutoriel explique comment effectuer une optimisation d'objectif conditionnelle à l'aide d'AI Platform Optimizer.

Dataset

Dans cet exemple d'entraînement, l'ensemble de données "Revenu selon le recensement" est hébergé dans le dépôt de machine learning de l'Université de Californie à Irvine.

En s'appuyant sur des données de recensement telles que l'âge, l'éducation, l'état civil et la profession (les caractéristiques), l'objectif consiste à prédire si une personne gagne plus de 50 000 dollars par an (étiquette cible). Vous allez entraîner un modèle de régression logistique qui, en fonction des informations concernant un individu, génère un nombre compris entre 0 et 1. Ce nombre peut être interprété comme la probabilité que l'individu perçoive un revenu annuel supérieur à 50 000 dollars.

Objectif

Ce tutoriel explique comment optimiser la recherche d'hyperparamètres pour des modèles de machine learning à l'aide d'AI Platform Optimizer.

Cet exemple met en œuvre une démonstration d'apprentissage automatique afin d'optimiser un modèle de classification sur un ensemble de données de recensement à l'aide d'AI Platform Optimizer et des algorithmes intégrés à l'API Training d'AI Platform. Vous utilisez AI Platform Optimizer pour obtenir les valeurs suggérées des hyperparamètres et envoyer des tâches d'entraînement de modèle selon ces valeurs via les algorithmes intégrés de l'API Training d'AI Platform.

Coûts

Ce tutoriel utilise des composants facturables de Google Cloud :

  • AI Platform Training
  • Cloud Storage

Découvrez les tarifs d'entraînement d'AI Platform et de Cloud Storage, et utilisez le simulateur de coût pour générer une estimation des coûts en fonction de votre utilisation prévue.

Packages d'installation PIP et dépendances

Installez des dépendances supplémentaires qui ne sont pas déjà installées dans l'environnement du notebook.

  • Utilisez la dernière version principale en disponibilité générale du framework.
! pip install -U google-api-python-client
! pip install -U google-cloud
! pip install -U google-cloud-storage
! pip install -U requests

# Automatically restart kernel after installs
import IPython
app = IPython.Application.instance()
app.kernel.do_shutdown(True)  

Configurer un projet Google Cloud

Les étapes suivantes sont nécessaires, quel que soit l'environnement de votre notebook.

  1. Sélectionnez ou créez un projet Google Cloud.

  2. Assurez-vous que la facturation est activée pour votre projet.

  3. Activez les API AI Platform.

  4. Si vous exécutez ce notebook en local, vous devez installer le SDK Google Cloud.

  5. Saisissez l'ID de votre projet dans la cellule ci-dessous. Ensuite, exécutez la cellule pour vérifier que le SDK Cloud utilise le bon projet pour toutes les commandes de ce notebook.

Remarque : Jupyter exécute des lignes comportant le préfixe ! en tant que commandes d'interface système. Il effectue également une interpolation des variables Python précédées du préfixe $ dans ces commandes.

PROJECT_ID = "[project-id]" #@param {type:"string"}
! gcloud config set project $PROJECT_ID

Authentifier votre compte Google Cloud

Si vous utilisez des notebooks AI Platform, votre environnement est déjà authentifié. Vous pouvez ignorer ces étapes.

La cellule ci-dessous exige de s'authentifier deux fois.

import sys

# If you are running this notebook in Colab, run this cell and follow the
# instructions to authenticate your Google Cloud account. This provides access
# to your Cloud Storage bucket and lets you submit training jobs and prediction
# requests.

if 'google.colab' in sys.modules:
    from google.colab import auth as google_auth
    google_auth.authenticate_user()

# If you are running this tutorial in a notebook locally, replace the string
# below with the path to your service account key and run this cell to
# authenticate your Google Cloud account.
else:
    %env GOOGLE_APPLICATION_CREDENTIALS your_path_to_credentials.json

# Log in to your account on Google Cloud
! gcloud auth application-default login
! gcloud auth login

Importer des bibliothèques

import json
import time
import datetime
from googleapiclient import errors

Tutoriel

Prérequis

Cette section définit certains des paramètres et méthodes permettant d'appeler les API AI Platform Optimizer. Pour commencer, veuillez remplir les champs suivants.

# Update to your username
USER = '[user-id]' #@param {type: 'string'}

STUDY_ID = '{}_study_{}'.format(USER, datetime.datetime.now().strftime('%Y%m%d_%H%M%S')) #@param {type: 'string'}
REGION = 'us-central1'
def study_parent():
  return 'projects/{}/locations/{}'.format(PROJECT_ID, REGION)


def study_name(study_id):
  return 'projects/{}/locations/{}/studies/{}'.format(PROJECT_ID, REGION, study_id)


def trial_parent(study_id):
  return study_name(study_id)


def trial_name(study_id, trial_id):
  return 'projects/{}/locations/{}/studies/{}/trials/{}'.format(PROJECT_ID, REGION,
                                                                study_id, trial_id)

def operation_name(operation_id):
  return 'projects/{}/locations/{}/operations/{}'.format(PROJECT_ID, REGION, operation_id)


print('USER: {}'.format(USER))
print('PROJECT_ID: {}'.format(PROJECT_ID))
print('REGION: {}'.format(REGION))
print('STUDY_ID: {}'.format(STUDY_ID))

Créer le client API

La cellule suivante crée le client API généré automatiquement à l'aide du service de découverte d'API Google. Le schéma de l'API au format JSON est hébergé dans un bucket Cloud Storage.

from google.cloud import storage
from googleapiclient import discovery


_OPTIMIZER_API_DOCUMENT_BUCKET = 'caip-optimizer-public'
_OPTIMIZER_API_DOCUMENT_FILE = 'api/ml_public_google_rest_v1.json'


def read_api_document():
  client = storage.Client(PROJECT_ID)
  bucket = client.get_bucket(_OPTIMIZER_API_DOCUMENT_BUCKET)
  blob = bucket.get_blob(_OPTIMIZER_API_DOCUMENT_FILE)
  return blob.download_as_string()


ml = discovery.build_from_document(service=read_api_document())
print('Successfully built the client.')

Configuration de l'étude

Dans ce tutoriel, AI Platform Optimizer crée une étude et demande des essais. Pour chaque essai, vous allez créer une tâche d'algorithme intégré à l'API Training d'AI Platform pour entraîner le modèle à l'aide des hyperparamètres suggérés. Pour chaque essai, une mesure indique la précision du modèle (accuracy).

Les caractéristiques correspondant au paramètre conditionnel, fournies par AI Platform Optimizer, définissent un espace de recherche d'hyperparamètres sous forme d'arborescence. L'hyperparamètre de premier niveau est model_type. Il est défini entre LINEAR et WIDE_AND_DEEP. Chaque type de modèle possède des hyperparamètres de second niveau correspondants qui doivent être réglés :

  • Si model_type est défini sur LINEAR, le paramètre learning_rate est réglé.
  • Si model_type est défini sur WIDE_AND_DEEP, les paramètres learning_rate et dnn_learning_rate sont réglés.

Arbre de décision où "model_type" est défini sur "LINEAR" ou sur "WIDE_AND_DEEP", "LINEAR" pointe vers "learning_rate" et "WIDE_AND_DEEP" pointe à la fois vers "learning_rate" et "dnnnlearning_rate".

Voici un exemple de configuration d'étude, créée sous la forme d'un dictionnaire Python hiérarchique. Il est déjà rempli. Exécutez la cellule pour configurer l'étude.

param_learning_rate = {
    'parameter': 'learning_rate',
    'type' : 'DOUBLE',
    'double_value_spec' : {
        'min_value' : 0.00001,
        'max_value' : 1.0
    },
    'scale_type' : 'UNIT_LOG_SCALE',
    'parent_categorical_values' : {
        'values': ['LINEAR', 'WIDE_AND_DEEP']
    },
}

param_dnn_learning_rate = {
    'parameter': 'dnn_learning_rate',
    'type' : 'DOUBLE',
    'double_value_spec' : {
        'min_value' : 0.00001,
        'max_value' : 1.0
    },
    'scale_type' : 'UNIT_LOG_SCALE',
    'parent_categorical_values' : {
        'values': ['WIDE_AND_DEEP']
    },
}

param_model_type = {
    'parameter': 'model_type',
    'type' : 'CATEGORICAL',
    'categorical_value_spec' : {'values': ['LINEAR', 'WIDE_AND_DEEP']},
    'child_parameter_specs' : [param_learning_rate, param_dnn_learning_rate,]
}

metric_accuracy = {
    'metric' : 'accuracy',
    'goal' : 'MAXIMIZE'
}

study_config = {
    'algorithm' : 'ALGORITHM_UNSPECIFIED',  # Let the service choose the `default` algorithm.
    'parameters' : [param_model_type,],
    'metrics' : [metric_accuracy,],
}

study = {'study_config': study_config}
print(json.dumps(study, indent=2, sort_keys=True))

Créer l'étude

À présent, créez l'étude que vous exécuterez ensuite pour optimiser l'objectif.

# Creates a study
req = ml.projects().locations().studies().create(
    parent=study_parent(), studyId=STUDY_ID, body=study)
try :
  print(req.execute())
except errors.HttpError as e:
  if e.resp.status == 409:
    print('Study already existed.')
  else:
    raise e

Définir les paramètres d'entrée et de sortie

Définissez ensuite les paramètres de sortie suivants.

OUTPUT_BUCKET et OUTPUT_DIR correspondent au bucket Cloud Storage et au répertoire utilisés en tant que "job_dir" pour les tâches de l'API Training AI Platform. OUTPUT_BUCKET doit correspondre à un bucket dans votre projet et OUTPUT_DIR au nom que vous souhaitez attribuer au dossier de sortie de votre bucket.

job_dir doit être défini au format "gs://$OUTPUT_BUCKET/$OUTPUT_DIR/".

TRAINING_DATA_PATH correspond au chemin d'accès à l'ensemble de données d'entrée d'entraînement.

# `job_dir` will be `gs://${OUTPUT_BUCKET}/${OUTPUT_DIR}/${job_id}`
OUTPUT_BUCKET = '[output-bucket-name]' #@param {type: 'string'}
OUTPUT_DIR = '[output-dir]' #@param {type: 'string'}
TRAINING_DATA_PATH = 'gs://caip-optimizer-public/sample-data/raw_census_train.csv' #@param {type: 'string'}

print('OUTPUT_BUCKET: {}'.format(OUTPUT_BUCKET))
print('OUTPUT_DIR: {}'.format(OUTPUT_DIR))
print('TRAINING_DATA_PATH: {}'.format(TRAINING_DATA_PATH))

# Create the bucket in Cloud Storage
! gcloud storage buckets create gs://$OUTPUT_BUCKET/ --project=$PROJECT_ID

Évaluation des métriques

Cette section définit les méthodes d'évaluation des essais.

Pour chaque essai, vous envoyez une tâche d'algorithme intégré à AI Platform pour entraîner le modèle de machine learning à l'aide des hyperparamètres suggérés par AI Platform Optimizer. Une fois terminée, chaque tâche écrit le fichier de résumé du modèle dans Cloud Storage. Vous pouvez récupérer la précision du modèle à partir du répertoire des tâches et l'enregistrer en tant qu'élément final_measurement de l'essai.

import logging
import math
import subprocess
import os
import yaml

from google.cloud import storage

_TRAINING_JOB_NAME_PATTERN = '{}_condition_parameters_{}_{}'
_IMAGE_URIS = {'LINEAR' : 'gcr.io/cloud-ml-algos/linear_learner_cpu:latest',
               'WIDE_AND_DEEP' : 'gcr.io/cloud-ml-algos/wide_deep_learner_cpu:latest'}
_STEP_COUNT = 'step_count'
_ACCURACY = 'accuracy'


def EvaluateTrials(trials):
  """Evaluates trials by submitting training jobs to AI Platform Training service.

     Args:
       trials: List of Trials to evaluate

     Returns: A dict of <trial_id, measurement> for the given trials.
  """
  trials_by_job_id = {}
  mesurement_by_trial_id = {}

  # Submits a AI Platform Training job for each trial.
  for trial in trials:
    trial_id = int(trial['name'].split('/')[-1])
    model_type = _GetSuggestedParameterValue(trial, 'model_type', 'stringValue')
    learning_rate = _GetSuggestedParameterValue(trial, 'learning_rate',
                                                'floatValue')
    dnn_learning_rate = _GetSuggestedParameterValue(trial, 'dnn_learning_rate',
                                                    'floatValue')
    job_id = _GenerateTrainingJobId(model_type=model_type, 
                                    trial_id=trial_id)
    trials_by_job_id[job_id] = {
        'trial_id' : trial_id,
        'model_type' : model_type,
        'learning_rate' : learning_rate,
        'dnn_learning_rate' : dnn_learning_rate,
    }
    _SubmitTrainingJob(job_id, trial_id, model_type, learning_rate, dnn_learning_rate)

  # Waits for completion of AI Platform Training jobs.
  while not _JobsCompleted(trials_by_job_id.keys()):
    time.sleep(60)

  # Retrieves model training result(e.g. global_steps, accuracy) for AI Platform Training jobs.
  metrics_by_job_id = _GetJobMetrics(trials_by_job_id.keys())
  for job_id, metric in metrics_by_job_id.items():
    measurement = _CreateMeasurement(trials_by_job_id[job_id]['trial_id'],
                                     trials_by_job_id[job_id]['model_type'],
                                     trials_by_job_id[job_id]['learning_rate'],
                                     trials_by_job_id[job_id]['dnn_learning_rate'],
                                     metric)
    mesurement_by_trial_id[trials_by_job_id[job_id]['trial_id']] = measurement
  return mesurement_by_trial_id


def _CreateMeasurement(trial_id, model_type, learning_rate, dnn_learning_rate, metric):
  if not metric[_ACCURACY]:
    # Returns `none` for trials without metrics. The trial will be marked as `INFEASIBLE`.
    return None
  print(
      'Trial {0}: [model_type = {1}, learning_rate = {2}, dnn_learning_rate = {3}] => accuracy = {4}'.format(
          trial_id, model_type, learning_rate,
          dnn_learning_rate if dnn_learning_rate else 'N/A', metric[_ACCURACY]))
  measurement = {
      _STEP_COUNT: metric[_STEP_COUNT],
      'metrics': [{'metric': _ACCURACY, 'value': metric[_ACCURACY]},]}
  return measurement


def _SubmitTrainingJob(job_id, trial_id, model_type, learning_rate, dnn_learning_rate=None):
  """Submits a built-in algo training job to AI Platform Training Service."""
  try:
    if model_type == 'LINEAR':
      subprocess.check_output(_LinearCommand(job_id, learning_rate), stderr=subprocess.STDOUT)
    elif model_type == 'WIDE_AND_DEEP':
      subprocess.check_output(_WideAndDeepCommand(job_id, learning_rate, dnn_learning_rate), stderr=subprocess.STDOUT)
    print('Trial {0}: Submitted job [https://console.cloud.google.com/ai-platform/jobs/{1}?project={2}].'.format(trial_id, job_id, PROJECT_ID))
  except subprocess.CalledProcessError as e:
    logging.error(e.output)


def _GetTrainingJobState(job_id):
  """Gets a training job state."""
  cmd = ['gcloud', 'ai-platform', 'jobs', 'describe', job_id,
         '--project', PROJECT_ID,
         '--format', 'json']
  try:
    output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, timeout=3)
  except subprocess.CalledProcessError as e:
    logging.error(e.output)
  return json.loads(output)['state']


def _JobsCompleted(jobs):
  """Checks if all the jobs are completed."""
  all_done = True
  for job in jobs:
    if _GetTrainingJobState(job) not in ['SUCCEEDED', 'FAILED', 'CANCELLED']:
      print('Waiting for job[https://console.cloud.google.com/ai-platform/jobs/{0}?project={1}] to finish...'.format(job, PROJECT_ID))
      all_done = False
  return all_done


def _RetrieveAccuracy(job_id):
  """Retrices the accuracy of the trained model for a built-in algorithm job."""
  storage_client = storage.Client(project=PROJECT_ID)
  bucket = storage_client.get_bucket(OUTPUT_BUCKET)
  blob_name = os.path.join(OUTPUT_DIR, job_id, 'model/deployment_config.yaml')
  blob = storage.Blob(blob_name, bucket)
  try: 
    blob.reload()
    content = blob.download_as_string()
    accuracy = float(yaml.safe_load(content)['labels']['accuracy']) / 100
    step_count = int(yaml.safe_load(content)['labels']['global_step'])
    return {_STEP_COUNT: step_count, _ACCURACY: accuracy}
  except:
    # Returns None if failed to load the built-in algo output file.
    # It could be due to job failure and the trial will be `INFEASIBLE`
    return None


def _GetJobMetrics(jobs):
  accuracies_by_job_id = {}
  for job in jobs:
    accuracies_by_job_id[job] = _RetrieveAccuracy(job)
  return accuracies_by_job_id


def _GetSuggestedParameterValue(trial, parameter, value_type):
  param_found = [p for p in trial['parameters'] if p['parameter'] == parameter]
  if param_found:
    return param_found[0][value_type]
  else:
    return None


def _GenerateTrainingJobId(model_type, trial_id):
  return _TRAINING_JOB_NAME_PATTERN.format(STUDY_ID, model_type, trial_id)


def _GetJobDir(job_id):
  return os.path.join('gs://', OUTPUT_BUCKET, OUTPUT_DIR, job_id)


def _LinearCommand(job_id, learning_rate):
  return ['gcloud', 'ai-platform', 'jobs', 'submit', 'training', job_id,
          '--scale-tier', 'BASIC',
          '--region', 'us-central1',
          '--master-image-uri', _IMAGE_URIS['LINEAR'],
          '--project', PROJECT_ID,
          '--job-dir', _GetJobDir(job_id),
          '--',
          '--preprocess',
          '--model_type=classification',
          '--batch_size=250',
          '--max_steps=1000',
          '--learning_rate={}'.format(learning_rate),
          '--training_data_path={}'.format(TRAINING_DATA_PATH)]


def _WideAndDeepCommand(job_id, learning_rate, dnn_learning_rate):
  return ['gcloud', 'ai-platform', 'jobs', 'submit', 'training', job_id,
          '--scale-tier', 'BASIC',
          '--region', 'us-central1',
          '--master-image-uri', _IMAGE_URIS['WIDE_AND_DEEP'],
          '--project', PROJECT_ID,
          '--job-dir', _GetJobDir(job_id),
          '--',
          '--preprocess',
          '--test_split=0',
          '--use_wide',
          '--embed_categories',
          '--model_type=classification',
          '--batch_size=250',
          '--learning_rate={}'.format(learning_rate),
          '--dnn_learning_rate={}'.format(dnn_learning_rate),
          '--max_steps=1000',
          '--training_data_path={}'.format(TRAINING_DATA_PATH)]

Configuration pour l'envoi de requêtes de suggestions/essais

client_id : identifiant du client qui envoie la requête de suggestion. Si plusieurs requêtes d'essai de suggestion partagent le même élément client_id, le service renvoie le même essai suggéré si cet essai est dans l'état PENDING et fournit un nouvel essai si le dernier essai suggéré est terminé.

suggestion_count_per_request : nombre de suggestions (essais) demandées dans une seule requête.

max_trial_id_to_stop : nombre d'essais à explorer avant d'arrêter l'étude. Il est défini sur 4 afin d'écourter le délai d'exécution du code. Ne vous attendez donc pas à atteindre un état de convergence. Pour cela, il faudrait probablement le définir sur 20 (une bonne règle de base est de multiplier la dimensionnalité totale par 10).

client_id = 'client1' #@param {type: 'string'}
suggestion_count_per_request =   2 #@param {type: 'integer'}
max_trial_id_to_stop =   4 #@param {type: 'integer'}

print('client_id: {}'.format(client_id))
print('suggestion_count_per_request: {}'.format(suggestion_count_per_request))
print('max_trial_id_to_stop: {}'.format(max_trial_id_to_stop))

Demander et exécuter des essais AI Platform Optimizer

Exécutez les essais.

current_trial_id = 0
while current_trial_id < max_trial_id_to_stop:
  # Request trials
  resp = ml.projects().locations().studies().trials().suggest(
    parent=trial_parent(STUDY_ID), 
    body={'client_id': client_id, 'suggestion_count': suggestion_count_per_request}).execute()
  op_id = resp['name'].split('/')[-1]

  # Polls the suggestion long-running operations.
  get_op = ml.projects().locations().operations().get(name=operation_name(op_id))
  while True:
      operation = get_op.execute()
      if 'done' in operation and operation['done']:
        break
      time.sleep(1)

  # Featches the suggested trials.
  trials = []
  for suggested_trial in get_op.execute()['response']['trials']:
    trial_id = int(suggested_trial['name'].split('/')[-1])
    trial = ml.projects().locations().studies().trials().get(name=trial_name(STUDY_ID, trial_id)).execute()
    if trial['state'] not in ['COMPLETED', 'INFEASIBLE']:
      print("Trial {}: {}".format(trial_id, trial))
      trials.append(trial)

  # Evaluates trials - Submit model training jobs using AI Platform Training built-in algorithms.
  measurement_by_trial_id = EvaluateTrials(trials)

  # Completes trials.
  for trial in trials:
    trial_id = int(trial['name'].split('/')[-1])
    current_trial_id = trial_id
    measurement = measurement_by_trial_id[trial_id]
    print(("=========== Complete Trial: [{0}] =============").format(trial_id))
    if measurement:
      # Completes trial by reporting final measurement.
      ml.projects().locations().studies().trials().complete(
        name=trial_name(STUDY_ID, trial_id), 
        body={'final_measurement' : measurement}).execute()
    else:
      # Marks trial as `infeasbile` if when missing final measurement.
      ml.projects().locations().studies().trials().complete(
        name=trial_name(STUDY_ID, trial_id), 
        body={'trial_infeasible' : True}).execute()

[FACULTATIF] Créer des essais en utilisant vos propres paramètres

L'API AI Platform Optimizer permet aux utilisateurs de demander des suggestions (méthode suggest) de paramètres au service, mais également de créer des essais (méthode create) à l'aide de leurs propres paramètres. AI Platform Optimizer vous permet de conserver une trace des tests effectués par les utilisateurs et d'exploiter ces informations pour générer de nouvelles suggestions.

Par exemple, si vous exécutez une tâche d'entraînement de modèle en utilisant vos propres paramètres model_type et learning_rate au lieu de ceux suggérés par AI Platform Optimizer, vous pouvez créer un essai afin de tester cette tâche dans le cadre de l'étude.

# User has to leave `trial.name` unset in CreateTrial request, the service will
# assign it.
custom_trial = {
  "clientId": "client1",
  "finalMeasurement": {
    "metrics": [
      {
        "metric": "accuracy",
        "value": 0.86
      }
    ],
    "stepCount": "1000"
  },
  "parameters": [
    {
      "parameter": "model_type",
      "stringValue": "LINEAR"
    },
    {
      "floatValue": 0.3869103706121445,
      "parameter": "learning_rate"
    }
  ],
  "state": "COMPLETED"
}

trial = ml.projects().locations().studies().trials().create(
    parent=trial_parent(STUDY_ID), body=custom_trial).execute()

print(json.dumps(trial, indent=2, sort_keys=True))

Répertorier les essais

Répertoriez les résultats de chaque essai d'optimisation d'entraînement.

resp = ml.projects().locations().studies().trials().list(parent=trial_parent(STUDY_ID)).execute()
print(json.dumps(resp, indent=2, sort_keys=True))

Nettoyer

Pour nettoyer toutes les ressources Google Cloud utilisées dans ce projet, vous pouvez supprimer le projet Google Cloud que vous avez utilisé dans ce tutoriel.