Questa pagina è stata tradotta dall'API Cloud Translation.
Switch to English

Vincoli di forma con Tensorflow Lattice

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza sorgente su GitHub Scarica notebook

Panoramica

Questo tutorial è una panoramica dei vincoli e dei regolarizzatori forniti dalla libreria TensorFlow Lattice (TFL). Qui utilizziamo gli stimatori TFL predefiniti su set di dati sintetici, ma nota che tutto in questo tutorial può essere fatto anche con modelli costruiti da strati TFL Keras.

Prima di procedere, assicurati che il tuo runtime abbia tutti i pacchetti richiesti installati (come importati nelle celle di codice seguenti).

Impostare

Installazione del pacchetto TF Lattice:

pip install -q tensorflow-lattice

Importazione dei pacchetti richiesti:

import tensorflow as tf

from IPython.core.pylabtools import figsize
import itertools
import logging
import matplotlib
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
import sys
import tensorflow_lattice as tfl
logging.disable(sys.maxsize)

Valori predefiniti utilizzati in questa guida:

NUM_EPOCHS = 500
BATCH_SIZE = 64
LEARNING_RATE=0.001

Set di dati di formazione per la classifica dei ristoranti

Immagina uno scenario semplificato in cui vogliamo determinare se gli utenti faranno o meno clic sui risultati di una ricerca di un ristorante. Il compito è prevedere le caratteristiche di input fornite dalla percentuale di clic (CTR):

  • Valutazione media ( avg_rating ): una caratteristica numerica con valori nell'intervallo [1,5].
  • Numero di recensioni ( num_reviews ): una funzione numerica con valori limitati a 200, che utilizziamo come misura della tendenza.
  • Dollar rating ( dollar_rating ): una caratteristica categoriale con valori di stringa nel set {"D", "DD", "DDD", "DDDD"}.

Qui creiamo un dataset sintetico in cui il vero CTR è dato dalla formula:

$$ CTR = 1 / (1 + exp\{\mbox{b(dollar_rating)}-\mbox{avg_rating}\times log(\mbox{num_reviews}) /4 \}) $$

dove $ b (\ cdot) $ traduce ogni dollar_rating in un valore di base:

$$ \mbox{D}\to 3,\ \mbox{DD}\to 2,\ \mbox{DDD}\to 4,\ \mbox{DDDD}\to 4.5. $$

Questa formula riflette i modelli tipici dell'utente. ad esempio, dato che tutto il resto è stato risolto, gli utenti preferiscono i ristoranti con valutazioni a stelle più elevate e i ristoranti "\ $ \ $" riceveranno più clic di "\ $", seguito da "\ $ \ $ \ $" e "\ $ \ $ \ $ \ $ ".

def click_through_rate(avg_ratings, num_reviews, dollar_ratings):
  dollar_rating_baseline = {"D": 3, "DD": 2, "DDD": 4, "DDDD": 4.5}
  return 1 / (1 + np.exp(
      np.array([dollar_rating_baseline[d] for d in dollar_ratings]) -
      avg_ratings * np.log1p(num_reviews) / 4))

Diamo un'occhiata ai grafici di contorno di questa funzione CTR.

def color_bar():
  bar = matplotlib.cm.ScalarMappable(
      norm=matplotlib.colors.Normalize(0, 1, True),
      cmap="viridis",
  )
  bar.set_array([0, 1])
  return bar


def plot_fns(fns, split_by_dollar=False, res=25):
  """Generates contour plots for a list of (name, fn) functions."""
  num_reviews, avg_ratings = np.meshgrid(
      np.linspace(0, 200, num=res),
      np.linspace(1, 5, num=res),
  )
  if split_by_dollar:
    dollar_rating_splits = ["D", "DD", "DDD", "DDDD"]
  else:
    dollar_rating_splits = [None]
  if len(fns) == 1:
    fig, axes = plt.subplots(2, 2, sharey=True, tight_layout=False)
  else:
    fig, axes = plt.subplots(
        len(dollar_rating_splits), len(fns), sharey=True, tight_layout=False)
  axes = axes.flatten()
  axes_index = 0
  for dollar_rating_split in dollar_rating_splits:
    for title, fn in fns:
      if dollar_rating_split is not None:
        dollar_ratings = np.repeat(dollar_rating_split, res**2)
        values = fn(avg_ratings.flatten(), num_reviews.flatten(),
                    dollar_ratings)
        title = "{}: dollar_rating={}".format(title, dollar_rating_split)
      else:
        values = fn(avg_ratings.flatten(), num_reviews.flatten())
      subplot = axes[axes_index]
      axes_index += 1
      subplot.contourf(
          avg_ratings,
          num_reviews,
          np.reshape(values, (res, res)),
          vmin=0,
          vmax=1)
      subplot.title.set_text(title)
      subplot.set(xlabel="Average Rating")
      subplot.set(ylabel="Number of Reviews")
      subplot.set(xlim=(1, 5))

  _ = fig.colorbar(color_bar(), cax=fig.add_axes([0.95, 0.2, 0.01, 0.6]))


figsize(11, 11)
plot_fns([("CTR", click_through_rate)], split_by_dollar=True)

png

Preparazione dei dati

Ora dobbiamo creare i nostri set di dati sintetici. Iniziamo generando un set di dati simulato dei ristoranti e delle loro caratteristiche.

def sample_restaurants(n):
  avg_ratings = np.random.uniform(1.0, 5.0, n)
  num_reviews = np.round(np.exp(np.random.uniform(0.0, np.log(200), n)))
  dollar_ratings = np.random.choice(["D", "DD", "DDD", "DDDD"], n)
  ctr_labels = click_through_rate(avg_ratings, num_reviews, dollar_ratings)
  return avg_ratings, num_reviews, dollar_ratings, ctr_labels


np.random.seed(42)
avg_ratings, num_reviews, dollar_ratings, ctr_labels = sample_restaurants(2000)

figsize(5, 5)
fig, axs = plt.subplots(1, 1, sharey=False, tight_layout=False)
for rating, marker in [("D", "o"), ("DD", "^"), ("DDD", "+"), ("DDDD", "x")]:
  plt.scatter(
      x=avg_ratings[np.where(dollar_ratings == rating)],
      y=num_reviews[np.where(dollar_ratings == rating)],
      c=ctr_labels[np.where(dollar_ratings == rating)],
      vmin=0,
      vmax=1,
      marker=marker,
      label=rating)
plt.xlabel("Average Rating")
plt.ylabel("Number of Reviews")
plt.legend()
plt.xlim((1, 5))
plt.title("Distribution of restaurants")
_ = fig.colorbar(color_bar(), cax=fig.add_axes([0.95, 0.2, 0.01, 0.6]))

png

Produciamo i set di dati di addestramento, convalida e test. Quando un ristorante viene visualizzato nei risultati di ricerca, possiamo registrare il coinvolgimento dell'utente (clic o nessun clic) come punto campione.

In pratica, gli utenti spesso non esaminano tutti i risultati di ricerca. Ciò significa che gli utenti vedranno probabilmente solo i ristoranti già considerati "buoni" dall'attuale modello di ranking in uso. Di conseguenza, i ristoranti "buoni" sono più frequentemente colpiti e sovrarappresentati nei set di dati di formazione. Quando si utilizzano più funzionalità, il set di dati di addestramento può presentare grandi lacune in parti "cattive" dello spazio delle funzionalità.

Quando il modello viene utilizzato per la classificazione, viene spesso valutato su tutti i risultati rilevanti con una distribuzione più uniforme che non è ben rappresentata dal set di dati di addestramento. Un modello flessibile e complicato potrebbe fallire in questo caso a causa dell'overfitting dei punti dati sovrarappresentati e quindi della mancanza di generalizzabilità. Gestiamo questo problema applicando la conoscenza del dominio per aggiungere vincoli di forma che guidano il modello a fare previsioni ragionevoli quando non può prenderle dal set di dati di addestramento.

In questo esempio, il set di dati di formazione consiste principalmente nelle interazioni dell'utente con ristoranti buoni e famosi. Il set di dati di test ha una distribuzione uniforme per simulare l'impostazione di valutazione discussa sopra. Si noti che tale set di dati di test non sarà disponibile in un'impostazione di problemi reali.

def sample_dataset(n, testing_set):
  (avg_ratings, num_reviews, dollar_ratings, ctr_labels) = sample_restaurants(n)
  if testing_set:
    # Testing has a more uniform distribution over all restaurants.
    num_views = np.random.poisson(lam=3, size=n)
  else:
    # Training/validation datasets have more views on popular restaurants.
    num_views = np.random.poisson(lam=ctr_labels * num_reviews / 50.0, size=n)

  return pd.DataFrame({
      "avg_rating": np.repeat(avg_ratings, num_views),
      "num_reviews": np.repeat(num_reviews, num_views),
      "dollar_rating": np.repeat(dollar_ratings, num_views),
      "clicked": np.random.binomial(n=1, p=np.repeat(ctr_labels, num_views))
  })


# Generate datasets.
np.random.seed(42)
data_train = sample_dataset(2000, testing_set=False)
data_val = sample_dataset(1000, testing_set=False)
data_test = sample_dataset(1000, testing_set=True)

# Plotting dataset densities.
figsize(12, 5)
fig, axs = plt.subplots(1, 2, sharey=False, tight_layout=False)
for ax, data, title in [(axs[0], data_train, "training"),
                        (axs[1], data_test, "testing")]:
  _, _, _, density = ax.hist2d(
      x=data["avg_rating"],
      y=data["num_reviews"],
      bins=(np.linspace(1, 5, num=21), np.linspace(0, 200, num=21)),
      density=True,
      cmap="Blues",
  )
  ax.set(xlim=(1, 5))
  ax.set(ylim=(0, 200))
  ax.set(xlabel="Average Rating")
  ax.set(ylabel="Number of Reviews")
  ax.title.set_text("Density of {} examples".format(title))
  _ = fig.colorbar(density, ax=ax)

png

Definizione di input_fns utilizzato per l'addestramento e la valutazione:

train_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(
    x=data_train,
    y=data_train["clicked"],
    batch_size=BATCH_SIZE,
    num_epochs=NUM_EPOCHS,
    shuffle=False,
)

# feature_analysis_input_fn is used for TF Lattice estimators.
feature_analysis_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(
    x=data_train,
    y=data_train["clicked"],
    batch_size=BATCH_SIZE,
    num_epochs=1,
    shuffle=False,
)

val_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(
    x=data_val,
    y=data_val["clicked"],
    batch_size=BATCH_SIZE,
    num_epochs=1,
    shuffle=False,
)

test_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(
    x=data_test,
    y=data_test["clicked"],
    batch_size=BATCH_SIZE,
    num_epochs=1,
    shuffle=False,
)

Adattamento di alberi a gradiente potenziato

Cominciamo con solo due caratteristiche: avg_rating e num_reviews .

Creiamo alcune funzioni ausiliarie per tracciare e calcolare la convalida e le metriche di test.

def analyze_two_d_estimator(estimator, name):
  # Extract validation metrics.
  metric = estimator.evaluate(input_fn=val_input_fn)
  print("Validation AUC: {}".format(metric["auc"]))
  metric = estimator.evaluate(input_fn=test_input_fn)
  print("Testing AUC: {}".format(metric["auc"]))

  def two_d_pred(avg_ratings, num_reviews):
    results = estimator.predict(
        tf.compat.v1.estimator.inputs.pandas_input_fn(
            x=pd.DataFrame({
                "avg_rating": avg_ratings,
                "num_reviews": num_reviews,
            }),
            shuffle=False,
        ))
    return [x["logistic"][0] for x in results]

  def two_d_click_through_rate(avg_ratings, num_reviews):
    return np.mean([
        click_through_rate(avg_ratings, num_reviews,
                           np.repeat(d, len(avg_ratings)))
        for d in ["D", "DD", "DDD", "DDDD"]
    ],
                   axis=0)

  figsize(11, 5)
  plot_fns([("{} Estimated CTR".format(name), two_d_pred),
            ("CTR", two_d_click_through_rate)],
           split_by_dollar=False)

Possiamo adattare alberi decisionali potenziati con gradiente TensorFlow sul set di dati:

feature_columns = [
    tf.feature_column.numeric_column("num_reviews"),
    tf.feature_column.numeric_column("avg_rating"),
]
gbt_estimator = tf.estimator.BoostedTreesClassifier(
    feature_columns=feature_columns,
    # Hyper-params optimized on validation set.
    n_batches_per_layer=1,
    max_depth=3,
    n_trees=20,
    min_node_weight=0.1,
    config=tf.estimator.RunConfig(tf_random_seed=42),
)
gbt_estimator.train(input_fn=train_input_fn)
analyze_two_d_estimator(gbt_estimator, "GBT")
Validation AUC: 0.7248634099960327
Testing AUC: 0.6980501413345337

png

Anche se il modello ha catturato la forma generale del vero CTR e ha metriche di convalida decenti, ha un comportamento controintuitivo in diverse parti dello spazio di input: il CTR stimato diminuisce all'aumentare della valutazione media o del numero di recensioni. Ciò è dovuto alla mancanza di punti campione in aree non ben coperte dal set di dati di addestramento. Il modello semplicemente non ha modo di dedurre il comportamento corretto esclusivamente dai dati.

Per risolvere questo problema, applichiamo il vincolo di forma secondo cui il modello deve produrre valori monotonicamente crescenti rispetto sia alla valutazione media che al numero di revisioni. Vedremo in seguito come implementarlo in TFL.

Montaggio di un DNN

Possiamo ripetere gli stessi passaggi con un classificatore DNN. Possiamo osservare un modello simile: non avere abbastanza punti campione con un numero limitato di revisioni si traduce in un'estrapolazione senza senso. Si noti che anche se la metrica di convalida è migliore della soluzione ad albero, la metrica di test è molto peggiore.

feature_columns = [
    tf.feature_column.numeric_column("num_reviews"),
    tf.feature_column.numeric_column("avg_rating"),
]
dnn_estimator = tf.estimator.DNNClassifier(
    feature_columns=feature_columns,
    # Hyper-params optimized on validation set.
    hidden_units=[16, 8, 8],
    optimizer=tf.keras.optimizers.Adam(learning_rate=LEARNING_RATE),
    config=tf.estimator.RunConfig(tf_random_seed=42),
)
dnn_estimator.train(input_fn=train_input_fn)
analyze_two_d_estimator(dnn_estimator, "DNN")
Validation AUC: 0.7518489956855774
Testing AUC: 0.745200514793396

png

Vincoli di forma

TensorFlow Lattice (TFL) si concentra sull'applicazione di vincoli di forma per salvaguardare il comportamento del modello oltre i dati di addestramento. Questi vincoli di forma vengono applicati ai livelli TFL Keras. I loro dettagli possono essere trovati nel nostro documento JMLR .

In questo tutorial utilizziamo gli stimatori in scatola TF per coprire vari vincoli di forma, ma nota che tutti questi passaggi possono essere eseguiti con modelli creati da strati TFL Keras.

Come con qualsiasi altro stimatore TensorFlow, gli stimatori predefiniti TFL utilizzano le colonne delle caratteristiche per definire il formato di input e utilizzano un input_fn di addestramento per passare i dati. L'utilizzo degli stimatori predefiniti TFL richiede anche:

  • una configurazione del modello : definizione dell'architettura del modello e vincoli e regolarizzatori di forma per caratteristica.
  • un'analisi delle caratteristiche input_fn : un TF input_fn che passa i dati per l'inizializzazione TFL.

Per una descrizione più completa, fare riferimento al tutorial sugli stimatori predefiniti o ai documenti dell'API.

Monotonicità

Per prima cosa affrontiamo i problemi di monotonicità aggiungendo vincoli di forma di monotonicità a entrambe le caratteristiche.

Per istruire TFL ad applicare i vincoli di forma, specifichiamo i vincoli nelle configurazioni delle caratteristiche . Il codice seguente mostra come si possa richiedere che l'output sia monotonicamente crescente rispetto sia a num_reviews che a avg_rating impostando monotonicity="increasing" .

feature_columns = [
    tf.feature_column.numeric_column("num_reviews"),
    tf.feature_column.numeric_column("avg_rating"),
]
model_config = tfl.configs.CalibratedLatticeConfig(
    feature_configs=[
        tfl.configs.FeatureConfig(
            name="num_reviews",
            lattice_size=2,
            monotonicity="increasing",
            pwl_calibration_num_keypoints=20,
        ),
        tfl.configs.FeatureConfig(
            name="avg_rating",
            lattice_size=2,
            monotonicity="increasing",
            pwl_calibration_num_keypoints=20,
        )
    ])
tfl_estimator = tfl.estimators.CannedClassifier(
    feature_columns=feature_columns,
    model_config=model_config,
    feature_analysis_input_fn=feature_analysis_input_fn,
    optimizer=tf.keras.optimizers.Adam(learning_rate=LEARNING_RATE),
    config=tf.estimator.RunConfig(tf_random_seed=42),
)
tfl_estimator.train(input_fn=train_input_fn)
analyze_two_d_estimator(tfl_estimator, "TF Lattice")
Validation AUC: 0.740505039691925
Testing AUC: 0.7356159687042236

png

L'utilizzo di un CalibratedLatticeConfig crea un classificatore in scatola che prima applica un calibratore a ogni input (una funzione lineare a pezzi per le caratteristiche numeriche) seguito da uno strato reticolare per fondere in modo non lineare le caratteristiche calibrate. Possiamo usare tfl.visualization per visualizzare il modello. In particolare, il grafico seguente mostra i due calibratori addestrati inclusi nel classificatore in scatola.

def save_and_visualize_lattice(tfl_estimator):
  saved_model_path = tfl_estimator.export_saved_model(
      "/tmp/TensorFlow_Lattice_101/",
      tf.estimator.export.build_parsing_serving_input_receiver_fn(
          feature_spec=tf.feature_column.make_parse_example_spec(
              feature_columns)))
  model_graph = tfl.estimators.get_model_graph(saved_model_path)
  figsize(8, 8)
  tfl.visualization.draw_model_graph(model_graph)
  return model_graph

_ = save_and_visualize_lattice(tfl_estimator)

png

Con l'aggiunta dei vincoli, il CTR stimato aumenterà sempre all'aumentare della valutazione media o all'aumentare del numero di recensioni. Questo viene fatto assicurandosi che i calibratori e il reticolo siano monotoni.

Rendimenti decrescenti

Rendimenti decrescenti significa che il guadagno marginale dell'aumento di un certo valore di caratteristica diminuirà all'aumentare del valore. Nel nostro caso, ci aspettiamo che la funzione num_reviews segua questo schema, quindi possiamo configurare il suo calibratore di conseguenza. Si noti che possiamo scomporre i rendimenti decrescenti in due condizioni sufficienti:

  • il calibratore sta aumentando in modo monotonico e
  • il calibratore è concavo.
feature_columns = [
    tf.feature_column.numeric_column("num_reviews"),
    tf.feature_column.numeric_column("avg_rating"),
]
model_config = tfl.configs.CalibratedLatticeConfig(
    feature_configs=[
        tfl.configs.FeatureConfig(
            name="num_reviews",
            lattice_size=2,
            monotonicity="increasing",
            pwl_calibration_convexity="concave",
            pwl_calibration_num_keypoints=20,
        ),
        tfl.configs.FeatureConfig(
            name="avg_rating",
            lattice_size=2,
            monotonicity="increasing",
            pwl_calibration_num_keypoints=20,
        )
    ])
tfl_estimator = tfl.estimators.CannedClassifier(
    feature_columns=feature_columns,
    model_config=model_config,
    feature_analysis_input_fn=feature_analysis_input_fn,
    optimizer=tf.keras.optimizers.Adam(learning_rate=LEARNING_RATE),
    config=tf.estimator.RunConfig(tf_random_seed=42),
)
tfl_estimator.train(input_fn=train_input_fn)
analyze_two_d_estimator(tfl_estimator, "TF Lattice")
_ = save_and_visualize_lattice(tfl_estimator)
Validation AUC: 0.7382562756538391
Testing AUC: 0.7399928569793701

png

png

Notare come la metrica di test migliora aggiungendo il vincolo di concavità. Anche la trama della previsione assomiglia meglio alla verità fondamentale.

Vincolo di forma 2D: fiducia

Una valutazione a 5 stelle per un ristorante con solo una o due recensioni è probabilmente una valutazione inaffidabile (il ristorante potrebbe non essere effettivamente buono), mentre una valutazione a 4 stelle per un ristorante con centinaia di recensioni è molto più affidabile (il ristorante è probabilmente buono in questo caso). Possiamo vedere che il numero di recensioni di un ristorante influisce sulla fiducia che riponiamo nella sua valutazione media.

Possiamo esercitare i vincoli di fiducia TFL per informare il modello che il valore maggiore (o minore) di una caratteristica indica maggiore dipendenza o fiducia di un'altra caratteristica. Questo viene fatto impostando la configurazione reflects_trust_in nella configurazione della funzione.

feature_columns = [
    tf.feature_column.numeric_column("num_reviews"),
    tf.feature_column.numeric_column("avg_rating"),
]
model_config = tfl.configs.CalibratedLatticeConfig(
    feature_configs=[
        tfl.configs.FeatureConfig(
            name="num_reviews",
            lattice_size=2,
            monotonicity="increasing",
            pwl_calibration_convexity="concave",
            pwl_calibration_num_keypoints=20,
            # Larger num_reviews indicating more trust in avg_rating.
            reflects_trust_in=[
                tfl.configs.TrustConfig(
                    feature_name="avg_rating", trust_type="edgeworth"),
            ],
        ),
        tfl.configs.FeatureConfig(
            name="avg_rating",
            lattice_size=2,
            monotonicity="increasing",
            pwl_calibration_num_keypoints=20,
        )
    ])
tfl_estimator = tfl.estimators.CannedClassifier(
    feature_columns=feature_columns,
    model_config=model_config,
    feature_analysis_input_fn=feature_analysis_input_fn,
    optimizer=tf.keras.optimizers.Adam(learning_rate=LEARNING_RATE),
    config=tf.estimator.RunConfig(tf_random_seed=42),
)
tfl_estimator.train(input_fn=train_input_fn)
analyze_two_d_estimator(tfl_estimator, "TF Lattice")
model_graph = save_and_visualize_lattice(tfl_estimator)
Validation AUC: 0.7382063269615173
Testing AUC: 0.73994380235672

png

png

Il grafico seguente presenta la funzione reticolo addestrato. A causa del vincolo di fiducia, ci aspettiamo che valori più grandi di num_reviews Calibrato num_reviews una pendenza maggiore rispetto a avg_rating calibrato, determinando uno spostamento più significativo nell'output reticolare.

lat_mesh_n = 12
lat_mesh_x, lat_mesh_y = tfl.test_utils.two_dim_mesh_grid(
    lat_mesh_n**2, 0, 0, 1, 1)
lat_mesh_fn = tfl.test_utils.get_hypercube_interpolation_fn(
    model_graph.output_node.weights.flatten())
lat_mesh_z = [
    lat_mesh_fn([lat_mesh_x.flatten()[i],
                 lat_mesh_y.flatten()[i]]) for i in range(lat_mesh_n**2)
]
trust_plt = tfl.visualization.plot_outputs(
    (lat_mesh_x, lat_mesh_y),
    {"Lattice Lookup": lat_mesh_z},
    figsize=(6, 6),
)
trust_plt.title("Trust")
trust_plt.xlabel("Calibrated avg_rating")
trust_plt.ylabel("Calibrated num_reviews")
trust_plt.show()

png

Calibratori leviganti

Diamo ora uno sguardo al calibratore di avg_rating . Sebbene aumenti in modo monotono, i cambiamenti nelle sue pendenze sono bruschi e difficili da interpretare. Ciò suggerisce che potremmo prendere in considerazione l'ottimizzazione di questo calibratore utilizzando una configurazione di regolarizzatore in regularizer_configs .

Qui applichiamo un regolarizzatore delle wrinkle per ridurre i cambiamenti nella curvatura. Puoi anche usare il regolarizzatore laplacian per appiattire il calibratore e il regolarizzatore hessian per renderlo più lineare.

feature_columns = [
    tf.feature_column.numeric_column("num_reviews"),
    tf.feature_column.numeric_column("avg_rating"),
]
model_config = tfl.configs.CalibratedLatticeConfig(
    feature_configs=[
        tfl.configs.FeatureConfig(
            name="num_reviews",
            lattice_size=2,
            monotonicity="increasing",
            pwl_calibration_convexity="concave",
            pwl_calibration_num_keypoints=20,
            regularizer_configs=[
                tfl.configs.RegularizerConfig(name="calib_wrinkle", l2=1.0),
            ],
            reflects_trust_in=[
                tfl.configs.TrustConfig(
                    feature_name="avg_rating", trust_type="edgeworth"),
            ],
        ),
        tfl.configs.FeatureConfig(
            name="avg_rating",
            lattice_size=2,
            monotonicity="increasing",
            pwl_calibration_num_keypoints=20,
            regularizer_configs=[
                tfl.configs.RegularizerConfig(name="calib_wrinkle", l2=1.0),
            ],
        )
    ])
tfl_estimator = tfl.estimators.CannedClassifier(
    feature_columns=feature_columns,
    model_config=model_config,
    feature_analysis_input_fn=feature_analysis_input_fn,
    optimizer=tf.keras.optimizers.Adam(learning_rate=LEARNING_RATE),
    config=tf.estimator.RunConfig(tf_random_seed=42),
)
tfl_estimator.train(input_fn=train_input_fn)
analyze_two_d_estimator(tfl_estimator, "TF Lattice")
_ = save_and_visualize_lattice(tfl_estimator)
Validation AUC: 0.7512326836585999
Testing AUC: 0.754392147064209

png

png

I calibratori ora sono fluidi e il CTR stimato complessivo corrisponde meglio alla verità di base. Ciò si riflette sia nella metrica di test che nei grafici di contorno.

Monotonicità parziale per la calibrazione categoriale

Finora abbiamo utilizzato solo due delle caratteristiche numeriche nel modello. Qui aggiungeremo una terza funzionalità utilizzando un livello di calibrazione categoriale. Anche in questo caso iniziamo impostando le funzioni di supporto per la stampa e il calcolo metrico.

def analyze_three_d_estimator(estimator, name):
  # Extract validation metrics.
  metric = estimator.evaluate(input_fn=val_input_fn)
  print("Validation AUC: {}".format(metric["auc"]))
  metric = estimator.evaluate(input_fn=test_input_fn)
  print("Testing AUC: {}".format(metric["auc"]))

  def three_d_pred(avg_ratings, num_reviews, dollar_rating):
    results = estimator.predict(
        tf.compat.v1.estimator.inputs.pandas_input_fn(
            x=pd.DataFrame({
                "avg_rating": avg_ratings,
                "num_reviews": num_reviews,
                "dollar_rating": dollar_rating,
            }),
            shuffle=False,
        ))
    return [x["logistic"][0] for x in results]

  figsize(11, 22)
  plot_fns([("{} Estimated CTR".format(name), three_d_pred),
            ("CTR", click_through_rate)],
           split_by_dollar=True)

Per coinvolgere la terza caratteristica, dollar_rating , dovremmo ricordare che le caratteristiche categoriali richiedono un trattamento leggermente diverso in TFL, sia come colonna delle caratteristiche che come configurazione delle caratteristiche. Qui applichiamo il vincolo di monotonicità parziale che gli output per i ristoranti "DD" dovrebbero essere maggiori dei ristoranti "D" quando tutti gli altri input sono fissi. Questo viene fatto usando l'impostazione monotonicity nella configurazione della funzione.

feature_columns = [
    tf.feature_column.numeric_column("num_reviews"),
    tf.feature_column.numeric_column("avg_rating"),
    tf.feature_column.categorical_column_with_vocabulary_list(
        "dollar_rating",
        vocabulary_list=["D", "DD", "DDD", "DDDD"],
        dtype=tf.string,
        default_value=0),
]
model_config = tfl.configs.CalibratedLatticeConfig(
    feature_configs=[
        tfl.configs.FeatureConfig(
            name="num_reviews",
            lattice_size=2,
            monotonicity="increasing",
            pwl_calibration_convexity="concave",
            pwl_calibration_num_keypoints=20,
            regularizer_configs=[
                tfl.configs.RegularizerConfig(name="calib_wrinkle", l2=1.0),
            ],
            reflects_trust_in=[
                tfl.configs.TrustConfig(
                    feature_name="avg_rating", trust_type="edgeworth"),
            ],
        ),
        tfl.configs.FeatureConfig(
            name="avg_rating",
            lattice_size=2,
            monotonicity="increasing",
            pwl_calibration_num_keypoints=20,
            regularizer_configs=[
                tfl.configs.RegularizerConfig(name="calib_wrinkle", l2=1.0),
            ],
        ),
        tfl.configs.FeatureConfig(
            name="dollar_rating",
            lattice_size=2,
            pwl_calibration_num_keypoints=4,
            # Here we only specify one monotonicity:
            # `D` resturants has smaller value than `DD` restaurants
            monotonicity=[("D", "DD")],
        ),
    ])
tfl_estimator = tfl.estimators.CannedClassifier(
    feature_columns=feature_columns,
    model_config=model_config,
    feature_analysis_input_fn=feature_analysis_input_fn,
    optimizer=tf.keras.optimizers.Adam(learning_rate=LEARNING_RATE),
    config=tf.estimator.RunConfig(tf_random_seed=42),
)
tfl_estimator.train(input_fn=train_input_fn)
analyze_three_d_estimator(tfl_estimator, "TF Lattice")
_ = save_and_visualize_lattice(tfl_estimator)
Validation AUC: 0.8137159943580627
Testing AUC: 0.8294374942779541

png

png

Questo calibratore categoriale mostra la preferenza dell'output del modello: DD> D> DDD> DDDD, che è coerente con la nostra configurazione. Notare che c'è anche una colonna per i valori mancanti. Sebbene non vi siano funzionalità mancanti nei nostri dati di addestramento e test, il modello ci fornisce un'imputazione per il valore mancante nel caso si verificasse durante l'elaborazione del modello a valle.

Qui tracciamo anche il CTR previsto di questo modello condizionato al dollar_rating . Si noti che tutti i vincoli che abbiamo richiesto sono soddisfatti in ciascuna delle sezioni.

Calibrazione dell'uscita

Per tutti i modelli TFL che abbiamo addestrato finora, il livello del reticolo (indicato come "Lattice" nel grafico del modello) fornisce direttamente la previsione del modello. A volte non siamo sicuri se l'output del reticolo debba essere ridimensionato per emettere gli output del modello:

  • le caratteristiche sono $ log $ counts mentre le etichette sono counts.
  • il reticolo è configurato per avere pochissimi vertici ma la distribuzione delle etichette è relativamente complicata.

In questi casi possiamo aggiungere un altro calibratore tra l'output del reticolo e l'output del modello per aumentare la flessibilità del modello. Qui aggiungiamo uno strato di calibratore con 5 punti chiave al modello che abbiamo appena costruito. Aggiungiamo anche un regolarizzatore per il calibratore di output per mantenere la funzione fluida.

feature_columns = [
    tf.feature_column.numeric_column("num_reviews"),
    tf.feature_column.numeric_column("avg_rating"),
    tf.feature_column.categorical_column_with_vocabulary_list(
        "dollar_rating",
        vocabulary_list=["D", "DD", "DDD", "DDDD"],
        dtype=tf.string,
        default_value=0),
]
model_config = tfl.configs.CalibratedLatticeConfig(
    output_calibration=True,
    output_calibration_num_keypoints=5,
    regularizer_configs=[
        tfl.configs.RegularizerConfig(name="output_calib_wrinkle", l2=0.1),
    ],
    feature_configs=[
    tfl.configs.FeatureConfig(
        name="num_reviews",
        lattice_size=2,
        monotonicity="increasing",
        pwl_calibration_convexity="concave",
        pwl_calibration_num_keypoints=20,
        regularizer_configs=[
            tfl.configs.RegularizerConfig(name="calib_wrinkle", l2=1.0),
        ],
        reflects_trust_in=[
            tfl.configs.TrustConfig(
                feature_name="avg_rating", trust_type="edgeworth"),
        ],
    ),
    tfl.configs.FeatureConfig(
        name="avg_rating",
        lattice_size=2,
        monotonicity="increasing",
        pwl_calibration_num_keypoints=20,
        regularizer_configs=[
            tfl.configs.RegularizerConfig(name="calib_wrinkle", l2=1.0),
        ],
    ),
    tfl.configs.FeatureConfig(
        name="dollar_rating",
        lattice_size=2,
        pwl_calibration_num_keypoints=4,
        # Here we only specify one monotonicity:
        # `D` resturants has smaller value than `DD` restaurants
        monotonicity=[("D", "DD")],
    ),
])
tfl_estimator = tfl.estimators.CannedClassifier(
    feature_columns=feature_columns,
    model_config=model_config,
    feature_analysis_input_fn=feature_analysis_input_fn,
    optimizer=tf.keras.optimizers.Adam(learning_rate=LEARNING_RATE),
    config=tf.estimator.RunConfig(tf_random_seed=42),
)
tfl_estimator.train(input_fn=train_input_fn)
analyze_three_d_estimator(tfl_estimator, "TF Lattice")
_ = save_and_visualize_lattice(tfl_estimator)
Validation AUC: 0.8130996227264404
Testing AUC: 0.8300802707672119

png

png

La metrica di test finale e i grafici mostrano come l'utilizzo di vincoli di buon senso può aiutare il modello a evitare comportamenti inaspettati ed estrapolare meglio all'intero spazio di input.