Tutorial: Uso de Personalizer en Azure Notebook

Importante

A partir del 20 de septiembre de 2023, no podrá crear nuevos recursos de Personalizer. El servicio Personalizer se va a retirar el 1 de octubre de 2026.

En este tutorial se ejecuta un bucle de Personalizer en una instancia de Azure Notebook y se muestra el ciclo de vida completo de uno de estos bucles.

El bucle sugiere qué tipo de café debe solicitar un cliente. Los usuarios y sus preferencias están almacenados en un conjunto de datos de usuario. La información sobre el café está almacenada en un conjunto de datos de café.

Usuarios y café

El cuaderno, que simula una interacción del usuario con un sitio web, selecciona una combinación aleatoria de usuario, hora del día e información meteorológica del conjunto de datos. A continuación se muestra un resumen de la información de usuario:

Clientes: características de contexto Horas del día Tipos de condición meteorológica
Alice
Bob
Cathy
Dave
Mañana
Tarde
Noche
Soleado
Lluvioso
Nevado

Para ayudar a que Personalizer aprenda, con el tiempo, el sistema también conoce detalles sobre la selección de café de cada persona.

Café: características de acción Tipos de temperatura Lugares de origen Tipos de tueste Orgánico
Cappucino Acceso frecuente Kenia Oscuro Orgánico
Infusión fría Frío Brasil Ligero Orgánico
Moca con hielo Frío Etiopía Ligero No orgánico
Latte Acceso frecuente Brasil Oscuro No orgánico

La finalidad del bucle de Personalizer es encontrar la mejor correspondencia entre los usuarios y el café tantas veces como sea posible.

El código de este tutorial está disponible en el repositorio de ejemplos de GitHub para Personalizer.

Funcionamiento de la simulación

Cuando el sistema comienza a funcionar, las sugerencias de Personalizer solo aciertan entre el 20 y el 30 % de las veces. Este éxito se indica mediante la recompensa que se envía de vuelta a la API de recompensas de Personalizer, con una puntuación de 1. Después de algunas llamadas a Rank and Reward, el sistema mejora.

Después de las solicitudes iniciales, ejecute una evaluación sin conexión. Esto permite que Personalizer revise los datos y sugiera una directiva de aprendizaje mejor. Aplique la nueva directiva de aprendizaje y vuelva a ejecutar el cuaderno con un 20 % del total de solicitudes anteriores. El bucle funcionará mejor con la nueva directiva de aprendizaje.

Llamadas a Rank y Reward

Para cada una de las pocas miles de llamadas al servicio Personalizer, Azure Notebook envía la solicitud Rank a la API REST:

  • Un identificador único para el evento Rank/Reward
  • Características de contexto: una combinación aleatoria de usuario, condición meteorológica y hora del día que simula a un usuario en un sitio web o un dispositivo móvil
  • Acciones con características: todos los datos de café a partir de los cuales Personalizer realiza una sugerencia

El sistema recibe la solicitud y compara esa predicción con la opción conocida del usuario para la misma hora del día y condición meteorológica. Si la opción conocida es la misma que la opción de la predicción, se devuelve a Personalizer un valor de Reward de 1. En caso contrario, la recompensa que se envía de vuelta es 0.

Nota

Se trata de una simulación, por lo que el algoritmo de la recompensa es sencillo. En un escenario real, para determinar la puntuación de la recompensa el algoritmo deberá utilizar lógica de negocios, posiblemente con pesos para los diversos aspectos de la experiencia del cliente.

Prerrequisitos

Descripciones de archivo:

Configuración del recurso Personalizer

En Azure Portal, configure el recurso Personalizer con el valor de Frecuencia de actualización del modelo establecido en 15 segundos y el valor de Tiempo de espera de recompensa establecido también en 10 minutos. Estos valores se encuentran en la página Configuración .

Configuración Value
Frecuencia de actualización del modelo 15 segundos
Tiempo de espera de recompensa 10 minutos

Estos valores tienen una duración muy corta para mostrar los cambios en este tutorial. No se deben usar en un escenario de producción sin comprobar que logran el objetivo buscado con el bucle de Personalizer.

Configuración de Azure Notebook

  1. Cambie el kernel a Python 3.6.
  2. Abra el archivo Personalizer.ipynb .

Ejecución de celdas del cuaderno

Procese cada celda ejecutable y espere a que finalice. Sabrá que ha terminado cuando los corchetes que aparecen junto a la celda muestren un número en lugar de *. En las secciones siguientes se explica qué hace cada celda de programación y qué esperar como salida.

Inclusión de los módulos de Python

Incluya los módulos de Python necesarios. La celda no tiene ninguna salida.

import json
import matplotlib.pyplot as plt
import random
import requests
import time
import uuid

Establecimiento de la clave y el nombre del recurso Personalizer

En Azure Portal, busque la clave y el punto de conexión en la página Inicio rápido del recurso Personalizer. Cambie el valor de <your-resource-name> al nombre del recurso Personalizer. Cambie el valor de <your-resource-key> a la clave de Personalizer.

# Replace 'personalization_base_url' and 'resource_key' with your valid endpoint values.
personalization_base_url = "https://<your-resource-name>.cognitiveservices.azure.com/"
resource_key = "<your-resource-key>"

Use esta función para anotar las horas de inicio y finalización de la función iterativa, iterations.

Estas celdas no tienen ninguna salida. Cuando se llama a la función, la salida indica la fecha y hora actuales.

# Print out current datetime
def currentDateTime():
    currentDT = datetime.datetime.now()
    print (str(currentDT))

Obtención de la última hora de actualización del modelo

Cuando se llama a la función, get_last_updated, esta imprime la fecha y la hora de la última actualización del modelo.

Estas celdas no tienen ninguna salida. Cuando se llama a la función, la salida indica la última fecha de entrenamiento del modelo.

La función usa una API REST GET para obtener las propiedades del modelo.

# ititialize variable for model's last modified date
modelLastModified = ""
def get_last_updated(currentModifiedDate):

    print('-----checking model')

    # get model properties
    response = requests.get(personalization_model_properties_url, headers = headers, params = None)

    print(response)
    print(response.json())

    # get lastModifiedTime
    lastModifiedTime = json.dumps(response.json()["lastModifiedTime"])

    if (currentModifiedDate != lastModifiedTime):
        currentModifiedDate = lastModifiedTime
        print(f'-----model updated: {lastModifiedTime}')

Obtención de la configuración de la directiva y el servicio

Compruebe el estado del servicio con estas dos llamadas a REST.

Estas celdas no tienen ninguna salida. Cuando se llama a la función, la salida indica los valores del servicio.

def get_service_settings():

    print('-----checking service settings')

    # get learning policy
    response = requests.get(personalization_model_policy_url, headers = headers, params = None)

    print(response)
    print(response.json())

    # get service settings
    response = requests.get(personalization_service_configuration_url, headers = headers, params = None)

    print(response)
    print(response.json())

Construcción de las direcciones URL y lectura de los archivos de datos JSON

Esta celda realiza lo siguiente:

  • compila las direcciones URL usadas en las llamadas a REST;
  • establece el encabezado de seguridad mediante la clave del recurso Personalizer;
  • establece la inicialización aleatoria para el identificador del evento Rank;
  • lee los archivos de datos JSON;
  • llama al método get_last_updated (se ha quitado la directiva de aprendizaje de la salida del ejemplo);
  • llama al método get_service_settings.

La celda tiene la salida de la llamada a las funciones get_last_updated y get_service_settings.

# build URLs
personalization_rank_url = personalization_base_url + "personalizer/v1.0/rank"
personalization_reward_url = personalization_base_url + "personalizer/v1.0/events/" #add "{eventId}/reward"
personalization_model_properties_url = personalization_base_url + "personalizer/v1.0/model/properties"
personalization_model_policy_url = personalization_base_url + "personalizer/v1.0/configurations/policy"
personalization_service_configuration_url = personalization_base_url + "personalizer/v1.0/configurations/service"

headers = {'Ocp-Apim-Subscription-Key' : resource_key, 'Content-Type': 'application/json'}

# context
users = "users.json"

# action features
coffee = "coffee.json"

# empty JSON for Rank request
requestpath = "example-rankrequest.json"

# initialize random
random.seed(time.time())

userpref = None
rankactionsjsonobj = None
actionfeaturesobj = None

with open(users) as handle:
    userpref = json.loads(handle.read())

with open(coffee) as handle:
    actionfeaturesobj = json.loads(handle.read())

with open(requestpath) as handle:
    rankactionsjsonobj = json.loads(handle.read())

get_last_updated(modelLastModified)
get_service_settings()

print(f'User count {len(userpref)}')
print(f'Coffee count {len(actionfeaturesobj)}')

Compruebe que el valor de rewardWaitTime de la salida está establecido en 10 minutos y que modelExportFrequency está establecido en 15 segundos.

-----checking model
<Response [200]>
{'creationTime': '0001-01-01T00:00:00+00:00', 'lastModifiedTime': '0001-01-01T00:00:00+00:00'}
-----model updated: "0001-01-01T00:00:00+00:00"
-----checking service settings
<Response [200]>
{...learning policy...}
<Response [200]>
{'rewardWaitTime': '00:10:00', 'defaultReward': 0.0, 'rewardAggregation': 'earliest', 'explorationPercentage': 0.2, 'modelExportFrequency': '00:00:15', 'logRetentionDays': -1}
User count 4
Coffee count 4

Solución de problemas de la primera llamada a REST

La celda anterior es la primera celda que llama a Personalizer. Asegúrese de que el código de estado REST de la salida es <Response [200]>. Si recibe un error, como 404, pero está seguro de que la clave y el nombre del recurso son correctos, vuelva a cargar el cuaderno.

Asegúrese de que el número de cafés y de usuarios es 4 en ambos casos. Si recibe un error, compruebe que ha cargado los tres archivos JSON.

Configuración de un gráfico de métricas en Azure Portal

Más adelante en este tutorial se puede ver el proceso de larga duración de las 10 000 solicitudes desde el explorador, con un cuadro de texto de actualización. Cuando finalice ese proceso, puede que resulte más fácil verlo en un gráfico o como suma total. Para ver esta información, use las métricas proporcionadas con el recurso. Puede crear el gráfico ahora que ha completado una solicitud al servicio y, después, actualizar el gráfico periódicamente mientras continúa el proceso de larga duración.

  1. En Azure Portal, seleccione el recurso Personalizer.

  2. En la navegación del recurso, seleccione Métricas bajo Supervisión.

  3. En el gráfico, seleccione Agregar métrica.

  4. El espacio de nombres del recurso y la métrica ya están establecidos. Solo tiene que seleccionar la métrica de Llamadas correctas y la agregación Suma.

  5. Cambie el filtro de tiempo a las últimas 4 horas.

    Set up metric chart in Azure portal, adding metric for successful calls for the last 4 hours.

    El gráfico debería mostrar tres llamadas correctas.

Generación de un identificador de evento único

Esta función genera un identificador único para cada llamada a Rank. El identificador se usa para identificar la información de la llamada a Rank y Reward. Este valor puede proceder de un proceso de negocio, como un identificador de vista web o un identificador de transacción.

La celda no tiene ninguna salida. Cuando se llama a la función, la salida indica el identificador único.

def add_event_id(rankjsonobj):
    eventid = uuid.uuid4().hex
    rankjsonobj["eventId"] = eventid
    return eventid

Obtención del usuario, la condición meteorológica y la hora del día aleatorios

Esta función selecciona una combinación única de usuario, condición meteorológica y hora del día, y agrega esos elementos al objeto JSON que se va a enviar a la solicitud Rank.

La celda no tiene ninguna salida. Cuando se llama a la función, devuelve los valores aleatorios de nombre del usuario, condición meteorológica y hora del día.

Se muestra a continuación la lista de los cuatro usuarios y sus preferencias; por brevedad, solo se muestran algunas preferencias:

{
  "Alice": {
    "Sunny": {
      "Morning": "Cold brew",
      "Afternoon": "Iced mocha",
      "Evening": "Cold brew"
    }...
  },
  "Bob": {
    "Sunny": {
      "Morning": "Cappucino",
      "Afternoon": "Iced mocha",
      "Evening": "Cold brew"
    }...
  },
  "Cathy": {
    "Sunny": {
      "Morning": "Latte",
      "Afternoon": "Cold brew",
      "Evening": "Cappucino"
    }...
  },
  "Dave": {
    "Sunny": {
      "Morning": "Iced mocha",
      "Afternoon": "Iced mocha",
      "Evening": "Iced mocha"
    }...
  }
}
def add_random_user_and_contextfeatures(namesoption, weatheropt, timeofdayopt, rankjsonobj):
    name = namesoption[random.randint(0,3)]
    weather = weatheropt[random.randint(0,2)]
    timeofday = timeofdayopt[random.randint(0,2)]
    rankjsonobj['contextFeatures'] = [{'timeofday': timeofday, 'weather': weather, 'name': name}]
    return [name, weather, timeofday]

Incorporación de todos los datos de café

Esta función agrega la lista completa de café al objeto JSON que se va a enviar a la solicitud Rank.

La celda no tiene ninguna salida. Cuando se llama a la función, cambia el elemento rankjsonobj.

A continuación se muestra el ejemplo de las características de un solo café:

{
    "id": "Cappucino",
    "features": [
    {
        "type": "hot",
        "origin": "kenya",
        "organic": "yes",
        "roast": "dark"

    }
}
def add_action_features(rankjsonobj):
    rankjsonobj["actions"] = actionfeaturesobj

Comparación de la predicción con la preferencia de usuario conocida

Se llama a esta función después de llamar a la API Rank para cada iteración.

Esta función compara las preferencia de café del usuario, según la condición meteorológica y la hora del día, con la sugerencia de Personalizer para el usuario con esos filtros. Si la sugerencia coincide, se devuelve una puntuación de 1; de lo contrario, la puntuación es 0. La celda no tiene ninguna salida. Cuando se llama a la función, la salida indica la puntuación.

def get_reward_from_simulated_data(name, weather, timeofday, prediction):
    if(userpref[name][weather][timeofday] == str(prediction)):
        return 1
    return 0

Bucle mediante llamadas a Rank y Reward

La siguiente celda es el principal trabajo del cuaderno: obtener un usuario aleatorio, obtener la lista de cafés y enviar ambos a la API Rank. Después, comparar la predicción con las preferencias conocidas del usuario y enviar la recompensa de vuelta al servicio Personalizer.

El bucle se ejecuta las veces indicadas en num_requests. Personalizer necesita unos pocos miles de llamadas a Rank y Reward para crear un modelo.

A continuación se muestra un ejemplo de los datos JSON que se envían a la API Rank. Por brevedad, la lista de cafés no está completa. Puede ver el JSON completo de los cafés en coffee.json.

JSON enviado a la API Rank:

{
   'contextFeatures':[
      {
         'timeofday':'Evening',
         'weather':'Snowy',
         'name':'Alice'
      }
   ],
   'actions':[
      {
         'id':'Cappucino',
         'features':[
            {
               'type':'hot',
               'origin':'kenya',
               'organic':'yes',
               'roast':'dark'
            }
         ]
      }
        ...rest of coffee list
   ],
   'excludedActions':[

   ],
   'eventId':'b5c4ef3e8c434f358382b04be8963f62',
   'deferActivation':False
}

Respuesta JSON de la API Rank:

{
    'ranking': [
        {'id': 'Latte', 'probability': 0.85 },
        {'id': 'Iced mocha', 'probability': 0.05 },
        {'id': 'Cappucino', 'probability': 0.05 },
        {'id': 'Cold brew', 'probability': 0.05 }
    ],
    'eventId': '5001bcfe3bb542a1a238e6d18d57f2d2',
    'rewardActionId': 'Latte'
}

Por último, cada bucle muestra la selección aleatoria de usuario, condición meteorológica y hora del día, y determina la recompensa. La recompensa de 1 indica que el recurso Personalizer seleccionó el tipo de café correcto para el usuario, la condición meteorológica y la hora del día indicados.

1 Alice Rainy Morning Latte 1

La función usa:

def iterations(n, modelCheck, jsonFormat):

    i = 1

    # default reward value - assumes failed prediction
    reward = 0

    # Print out dateTime
    currentDateTime()

    # collect results to aggregate in graph
    total = 0
    rewards = []
    count = []

    # default list of user, weather, time of day
    namesopt = ['Alice', 'Bob', 'Cathy', 'Dave']
    weatheropt = ['Sunny', 'Rainy', 'Snowy']
    timeofdayopt = ['Morning', 'Afternoon', 'Evening']


    while(i <= n):

        # create unique id to associate with an event
        eventid = add_event_id(jsonFormat)

        # generate a random sample
        [name, weather, timeofday] = add_random_user_and_contextfeatures(namesopt, weatheropt, timeofdayopt, jsonFormat)

        # add action features to rank
        add_action_features(jsonFormat)

        # show JSON to send to Rank
        print('To: ', jsonFormat)

        # choose an action - get prediction from Personalizer
        response = requests.post(personalization_rank_url, headers = headers, params = None, json = jsonFormat)

        # show Rank prediction
        print ('From: ',response.json())

        # compare personalization service recommendation with the simulated data to generate a reward value
        prediction = json.dumps(response.json()["rewardActionId"]).replace('"','')
        reward = get_reward_from_simulated_data(name, weather, timeofday, prediction)

        # show result for iteration
        print(f'   {i} {currentDateTime()} {name} {weather} {timeofday} {prediction} {reward}')

        # send the reward to the service
        response = requests.post(personalization_reward_url + eventid + "/reward", headers = headers, params= None, json = { "value" : reward })

        # for every N rank requests, compute total correct  total
         total =  total + reward

        # every N iteration, get last updated model date and time
        if(i % modelCheck == 0):

            print("**** 10% of loop found")

            get_last_updated(modelLastModified)

        # aggregate so chart is easier to read
        if(i % 10 == 0):
            rewards.append( total)
            count.append(i)
             total = 0

        i = i + 1

    # Print out dateTime
    currentDateTime()

    return [count, rewards]

Ejecución durante 10 000 iteraciones

Ejecute el bucle de Personalizer durante 10 000 iteraciones. Se trata de un evento de larga duración. No cierre el explorador que ejecuta el cuaderno. Actualice el gráfico de métricas en Azure Portal periódicamente para ver el número total de llamadas al servicio. Cuando tenga en torno a 20 000 llamadas y una llamada a Rank y Reward para cada iteración del bucle, las iteraciones habrán terminado.

# max iterations
num_requests = 200

# check last mod date N% of time - currently 10%
lastModCheck = int(num_requests * .10)

jsonTemplate = rankactionsjsonobj

# main iterations
[count, rewards] = iterations(num_requests, lastModCheck, jsonTemplate)

Resultados del gráfico para ver la mejora

Cree un gráfico a partir de los valores de count y rewards.

def createChart(x, y):
    plt.plot(x, y)
    plt.xlabel("Batch of rank events")
    plt.ylabel("Correct recommendations per batch")
    plt.show()

Ejecución del gráfico para 10 000 solicitudes Rank

Ejecute la función createChart.

createChart(count,rewards)

Lectura del gráfico

Este gráfico muestra el éxito del modelo para la directiva de aprendizaje predeterminada actual.

This chart shows the success of the current learning policy for the duration of the test.

El objetivo ideal es que, al final de la prueba, el bucle ofrezca una tasa de éxito media próxima al 100 % menos la exploración. El valor predeterminado de exploración es 20 %.

100-20=80

Este valor de exploración se encuentra en la página Configuración del recurso Personalizer de Azure Portal.

Si quiere encontrar una directiva de aprendizaje mejor, en función de los datos de la API Rank, ejecute una evaluación sin conexión en el portal para el bucle de Personalizer.

Ejecución de una evaluación sin conexión

  1. En Azure Portal, abra la página Evaluaciones del recurso Personalizer.

  2. Seleccione Crear evaluación.

  3. Escriba los datos necesarios del nombre de evaluación y el intervalo de fechas para la evaluación del bucle. El intervalo de fechas debe incluir solo los días en los que se centra para la evaluación. In the Azure portal, open the Personalizer resource's Evaluations page. Select Create Evaluation. Enter the evaluation name and date range.

    La finalidad de la ejecución de esta evaluación sin conexión es determinar si hay una directiva de aprendizaje mejor para las características y las acciones que se usan en este bucle. Para encontrar esa directiva de aprendizaje mejor, asegúrese de que la opción Detección de optimización está activada.

  4. Seleccione Aceptar para iniciar la evaluación.

  5. En esta página Evaluaciones se muestra la nueva evaluación y su estado actual. En función de la cantidad de datos que tenga, puede tardar algún tiempo. Puede volver a esta página después de unos minutos para ver los resultados.

  6. Una vez finalizada la evaluación, selecciónela y, a continuación, seleccione Comparison de directivas de aprendizaje distintas. Se muestran las directivas de aprendizaje disponibles y cómo se comportarían con los datos.

  7. Seleccione la directiva de aprendizaje de nivel superior de la tabla y seleccione Aplicar. Esto aplica la mejor directiva de aprendizaje al modelo y vuelve a entrenar.

Cambio de la frecuencia de actualización del modelo a 5 minutos

  1. En Azure Portal, todavía en el recurso Personalizer, seleccione la página Configuración.
  2. Cambie los valores de Frecuencia de actualización del modelo y Tiempo de espera de recompensa a 5 minutos y seleccione Guardar.

Más información sobre Tiempo de espera de recompensa y Frecuencia de actualización del modelo.

#Verify new learning policy and times
get_service_settings()

Compruebe que los valores de rewardWaitTime y modelExportFrequency de la salida están establecidos en 5 minutos.

-----checking model
<Response [200]>
{'creationTime': '0001-01-01T00:00:00+00:00', 'lastModifiedTime': '0001-01-01T00:00:00+00:00'}
-----model updated: "0001-01-01T00:00:00+00:00"
-----checking service settings
<Response [200]>
{...learning policy...}
<Response [200]>
{'rewardWaitTime': '00:05:00', 'defaultReward': 0.0, 'rewardAggregation': 'earliest', 'explorationPercentage': 0.2, 'modelExportFrequency': '00:05:00', 'logRetentionDays': -1}
User count 4
Coffee count 4

Comprobación de la nueva directiva de aprendizaje

Vuelva al archivo Azure Notebooks y continúe ejecutando el mismo bucle, pero solo durante 2000 iteraciones. Actualice el gráfico de métricas en Azure Portal periódicamente para ver el número total de llamadas al servicio. Cuando tenga en torno a 4000 llamadas y una llamada a Rank y Reward para cada iteración del bucle, las iteraciones habrán terminado.

# max iterations
num_requests = 2000

# check last mod date N% of time - currently 10%
lastModCheck2 = int(num_requests * .10)

jsonTemplate2 = rankactionsjsonobj

# main iterations
[count2, rewards2] = iterations(num_requests, lastModCheck2, jsonTemplate)

Ejecución del gráfico de 2000 solicitudes Rank

Ejecute la función createChart.

createChart(count2,rewards2)

Revisión del segundo gráfico

El segundo gráfico debe mostrar un aumento visible en las predicciones de Rank, en correspondencia con las preferencias del usuario.

The second chart should show a visible increase in Rank predictions aligning with user preferences.

Limpieza de recursos

Si no desea continuar con la serie de tutoriales, limpie los siguientes recursos:

  • Elimine el proyecto de Azure Notebook.
  • Elimine el recurso Personalizer.

Pasos siguientes

El cuaderno de Jupyter Notebook y los archivos de datos usados en este ejemplo están disponibles en el repositorio de GitHub para Personalizer.