Formbeschränkungen mit Tensorflow-Gitter

Ansicht auf TensorFlow.org In Google Colab ausführen Quelle auf GitHub anzeigen Notizbuch herunterladen

Überblick

Dieses Tutorial bietet einen Überblick über die Einschränkungen und Regularisierer, die von der TensorFlow Lattice (TFL) -Bibliothek bereitgestellt werden. Hier verwenden wir TFL-Schätzer in Dosen für synthetische Datensätze. Beachten Sie jedoch, dass alles in diesem Lernprogramm auch mit Modellen ausgeführt werden kann, die aus TFL-Keras-Ebenen erstellt wurden.

Bevor Sie fortfahren, stellen Sie sicher, dass auf Ihrer Laufzeit alle erforderlichen Pakete installiert sind (wie in den folgenden Codezellen importiert).

Einrichten

Installieren des TF-Gitterpakets:

pip install -q tensorflow-lattice

Erforderliche Pakete importieren:

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)

In diesem Handbuch verwendete Standardwerte:

NUM_EPOCHS = 1000
BATCH_SIZE = 64
LEARNING_RATE=0.01

Trainingsdatensatz für das Ranking von Restaurants

Stellen Sie sich ein vereinfachtes Szenario vor, in dem wir bestimmen möchten, ob Benutzer auf ein Suchergebnis für ein Restaurant klicken. Die Aufgabe besteht darin, die Klickrate (Click-through-Rate - CTR) bei gegebenen Eingabefunktionen vorherzusagen:

  • Durchschnittliche Bewertung ( avg_rating ): Ein numerisches Merkmal mit Werten im Bereich [1,5].
  • Anzahl der Bewertungen ( num_reviews ): Ein numerisches Merkmal mit Werten von maximal 200, das wir als Maß für die Trendigkeit verwenden.
  • Dollar-Bewertung ( dollar_rating ): Eine kategoriale Funktion mit Zeichenfolgenwerten in der Menge {"D", "DD", "DDD", "DDDD"}.

Hier erstellen wir einen synthetischen Datensatz, in dem die wahre Klickrate durch die Formel angegeben wird:

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

Dabei übersetzt $ b (\ cdot) $ jede dollar_rating in einen Basiswert:

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

Diese Formel spiegelt typische Benutzermuster wider. Wenn alles andere behoben ist, bevorzugen Benutzer Restaurants mit höheren Sternebewertungen, und "\ $ \ $" -Restaurants erhalten mehr Klicks als "\ $", gefolgt von "\ $ \ $ \ $" und "\ $ \ $ \ $" \ $ ".

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))

Werfen wir einen Blick auf die Konturdiagramme dieser CTR-Funktion.

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

Daten vorbereiten

Wir müssen jetzt unsere synthetischen Datensätze erstellen. Wir beginnen mit der Generierung eines simulierten Datensatzes von Restaurants und ihren Funktionen.

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

Lassen Sie uns die Trainings-, Validierungs- und Testdatensätze erstellen. Wenn ein Restaurant in den Suchergebnissen angezeigt wird, können wir das Engagement des Benutzers (Klicken oder kein Klicken) als Beispielpunkt aufzeichnen.

In der Praxis gehen Benutzer häufig nicht alle Suchergebnisse durch. Dies bedeutet, dass Benutzer wahrscheinlich nur Restaurants sehen, die vom derzeit verwendeten Ranking-Modell bereits als "gut" eingestuft wurden. Infolgedessen sind "gute" Restaurants in den Trainingsdatensätzen häufiger beeindruckt und überrepräsentiert. Wenn Sie mehr Funktionen verwenden, kann der Trainingsdatensatz große Lücken in "fehlerhaften" Teilen des Funktionsbereichs aufweisen.

Wenn das Modell für das Ranking verwendet wird, wird es häufig anhand aller relevanten Ergebnisse mit einer gleichmäßigeren Verteilung bewertet, die im Trainingsdatensatz nicht gut dargestellt wird. Ein flexibles und kompliziertes Modell kann in diesem Fall aufgrund einer Überanpassung der überrepräsentierten Datenpunkte fehlschlagen und daher nicht verallgemeinerbar sein. Wir behandeln dieses Problem, indem wir Domänenwissen anwenden, um Formbeschränkungen hinzuzufügen, die das Modell leiten, um vernünftige Vorhersagen zu treffen, wenn es sie nicht aus dem Trainingsdatensatz übernehmen kann.

In diesem Beispiel besteht der Trainingsdatensatz hauptsächlich aus Benutzerinteraktionen mit guten und beliebten Restaurants. Der Testdatensatz hat eine gleichmäßige Verteilung, um die oben diskutierte Bewertungseinstellung zu simulieren. Beachten Sie, dass ein solcher Testdatensatz in einer echten Problemeinstellung nicht verfügbar ist.

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(500, testing_set=False)
data_val = sample_dataset(500, testing_set=False)
data_test = sample_dataset(500, 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

Definieren von input_fns für Training und Evaluierung:

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,
)

Anpassen von gradientenverstärkten Bäumen

Beginnen wir mit nur zwei Funktionen: avg_rating und num_reviews .

Wir erstellen einige Zusatzfunktionen zum Zeichnen und Berechnen von Validierungs- und Testmetriken.

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)

Wir können TensorFlow-Gradienten-verstärkte Entscheidungsbäume in den Datensatz einfügen:

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=2,
    n_trees=50,
    learning_rate=0.05,
    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.6333084106445312
Testing AUC: 0.774649977684021

png

Obwohl das Modell die allgemeine Form der tatsächlichen Klickrate erfasst und über anständige Validierungsmetriken verfügt, weist es in mehreren Teilen des Eingabebereichs ein kontraintuitives Verhalten auf: Die geschätzte Klickrate nimmt mit zunehmender durchschnittlicher Bewertung oder Anzahl von Überprüfungen ab. Dies ist auf einen Mangel an Stichprobenpunkten in Bereichen zurückzuführen, die vom Trainingsdatensatz nicht gut abgedeckt werden. Das Modell hat einfach keine Möglichkeit, das korrekte Verhalten ausschließlich aus den Daten abzuleiten.

Um dieses Problem zu lösen, erzwingen wir die Formbeschränkung, dass das Modell Werte ausgeben muss, die sowohl in Bezug auf die durchschnittliche Bewertung als auch auf die Anzahl der Überprüfungen monoton ansteigen. Wir werden später sehen, wie dies in TFL implementiert wird.

DNN montieren

Wir können die gleichen Schritte mit einem DNN-Klassifikator wiederholen. Wir können ein ähnliches Muster beobachten: Wenn nicht genügend Stichprobenpunkte mit einer geringen Anzahl von Überprüfungen vorhanden sind, führt dies zu einer unsinnigen Extrapolation. Beachten Sie, dass die Testmetrik zwar viel besser als die Baumlösung ist, die Testmetrik jedoch viel schlechter.

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.6696228981018066
Testing AUC: 0.750156044960022

png

Formbeschränkungen

TensorFlow Lattice (TFL) konzentriert sich auf die Durchsetzung von Formbeschränkungen, um das Modellverhalten über die Trainingsdaten hinaus zu schützen. Diese Formbeschränkungen werden auf TFL-Keras-Ebenen angewendet. Ihre Details finden Sie in unserem JMLR-Papier .

In diesem Lernprogramm verwenden wir TF-Schätzer, um verschiedene Formbeschränkungen abzudecken. Beachten Sie jedoch, dass alle diese Schritte mit Modellen ausgeführt werden können, die aus TFL-Keras-Ebenen erstellt wurden.

Wie bei jedem anderen TensorFlow-Schätzer verwenden vordefinierte TFL-Schätzer Feature-Spalten , um das Eingabeformat zu definieren, und verwenden ein Training input_fn, um die Daten zu übergeben. Die Verwendung von TFL-Schätzern in Dosen erfordert außerdem:

  • Eine Modellkonfiguration : Definieren der Modellarchitektur sowie der Formbeschränkungen und Regularisierer pro Feature.
  • eine Merkmalsanalyse input_fn : eine TF input_fn, die Daten für die TFL-Initialisierung übergibt.

Eine ausführlichere Beschreibung finden Sie im Tutorial für vordefinierte Schätzer oder in den API-Dokumenten.

Monotonie

Wir gehen zunächst auf die Bedenken hinsichtlich der Monotonie ein, indem wir beiden Merkmalen Einschränkungen hinsichtlich der Monotonieform hinzufügen.

Um TFL anzuweisen, Formbeschränkungen durchzusetzen, geben wir die Einschränkungen in den Feature-Konfigurationen an . Der folgende Code zeigt, wie wir verlangen können, dass die Ausgabe sowohl in Bezug auf num_reviews als auch in Bezug auf num_reviews monotonicity="increasing" avg_rating indem 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.6565533876419067
Testing AUC: 0.7784258723258972

png

Durch die Verwendung einer CalibratedLatticeConfig wird ein vordefinierter Klassifikator erstellt, der zuerst einen Kalibrator auf jeden Eingang anwendet (eine stückweise lineare Funktion für numerische Merkmale), gefolgt von einer Gitterschicht , um die kalibrierten Merkmale nicht linear zu verschmelzen. Wir können tfl.visualization , um das Modell zu visualisieren. Das folgende Diagramm zeigt insbesondere die beiden trainierten Kalibratoren, die im Dosenklassifikator enthalten sind.

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

Mit den hinzugefügten Einschränkungen erhöht sich die geschätzte Klickrate immer, wenn die durchschnittliche Bewertung oder die Anzahl der Bewertungen steigt. Dies geschieht, indem sichergestellt wird, dass die Kalibratoren und das Gitter monoton sind.

Abnehmende Renditen

Eine Verringerung der Rendite bedeutet, dass der marginale Gewinn beim Erhöhen eines bestimmten Merkmalswerts abnimmt, wenn wir den Wert erhöhen. In unserem Fall erwarten wir, dass die Funktion num_reviews diesem Muster folgt, sodass wir den Kalibrator entsprechend konfigurieren können. Beachten Sie, dass wir abnehmende Renditen in zwei ausreichende Bedingungen zerlegen können:

  • der Kalibrator nimmt monoton zu und
  • Der Kalibrator ist konkav.
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.6409633755683899
Testing AUC: 0.7891247272491455

png

png

Beachten Sie, wie sich die Testmetrik durch Hinzufügen der Konkavitätsbeschränkung verbessert. Das Vorhersage-Diagramm ähnelt auch besser der Grundwahrheit.

2D-Formbeschränkung: Vertrauen

Eine 5-Sterne-Bewertung für ein Restaurant mit nur einer oder zwei Bewertungen ist wahrscheinlich eine unzuverlässige Bewertung (das Restaurant ist möglicherweise nicht wirklich gut), während eine 4-Sterne-Bewertung für ein Restaurant mit Hunderten von Bewertungen viel zuverlässiger ist (das Restaurant ist es) wahrscheinlich gut in diesem Fall). Wir können sehen, dass die Anzahl der Bewertungen eines Restaurants Einfluss darauf hat, wie viel Vertrauen wir in seine durchschnittliche Bewertung setzen.

Wir können TFL-Vertrauensbeschränkungen anwenden, um das Modell darüber zu informieren, dass der größere (oder kleinere) Wert eines Features mehr Vertrauen oder Vertrauen in ein anderes Feature anzeigt. Dies erfolgt durch Festlegen der reflects_trust_in Konfiguration in der Feature-Konfiguration.

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.6409633755683899
Testing AUC: 0.7891043424606323

png

png

Das folgende Diagramm zeigt die trainierte Gitterfunktion. Aufgrund der Vertrauensbeschränkung erwarten wir, dass größere Werte von kalibrierten num_reviews eine höhere Steigung in Bezug auf kalibriertes avg_rating , was zu einer signifikanteren Verschiebung der Gitterausgabe führt.

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

Glättungskalibratoren

Schauen wir uns nun den Kalibrator von avg_rating . Obwohl es monoton zunimmt, sind die Änderungen in seinen Steigungen abrupt und schwer zu interpretieren. Dies legt nahe, dass wir in Betracht ziehen sollten, diesen Kalibrator mithilfe eines Regularizer-Setups in den regularizer_configs zu glätten.

Hier wenden wir einen wrinkle Regularisierer an, um Änderungen in der Krümmung zu reduzieren. Sie können auch den laplacian Regularisierer verwenden, um den Kalibrator zu glätten, und den hessian Regularisierer, um ihn linearer zu machen.

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.6465646028518677
Testing AUC: 0.7948372960090637

png

png

Die Kalibratoren sind jetzt glatt und die geschätzte Gesamtklickrate entspricht besser der Grundwahrheit. Dies spiegelt sich sowohl in der Testmetrik als auch in den Konturdiagrammen wider.

Partielle Monotonie für die kategoriale Kalibrierung

Bisher haben wir nur zwei der numerischen Merkmale im Modell verwendet. Hier fügen wir ein drittes Feature hinzu, das eine kategoriale Kalibrierungsschicht verwendet. Wieder beginnen wir mit dem Einrichten von Hilfsfunktionen für das Zeichnen und die Metrikberechnung.

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)

Um das dritte Feature, dollar_rating , dollar_rating , sollten wir daran erinnern, dass kategoriale Features in TFL eine etwas andere Behandlung erfordern, sowohl als Feature-Spalte als auch als Feature-Konfiguration. Hier erzwingen wir die teilweise Monotonieeinschränkung, dass die Ausgaben für "DD" -Restaurants größer sein sollten als für "D" -Restaurants, wenn alle anderen Eingaben festgelegt sind. Dies erfolgt über die monotonicity in der Funktionskonfiguration.

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.7699775695800781
Testing AUC: 0.8594040274620056

png

png

Dieser kategoriale Kalibrator zeigt die Präferenz der Modellausgabe: DD> D> DDD> DDDD, was mit unserem Setup übereinstimmt. Beachten Sie, dass es auch eine Spalte für fehlende Werte gibt. Obwohl in unseren Trainings- und Testdaten keine Funktion fehlt, liefert uns das Modell eine Imputation für den fehlenden Wert, falls er während der Bereitstellung des nachgeschalteten Modells auftritt.

Hier zeichnen wir auch die vorhergesagte Klickrate dieses Modells auf, die von dollar_rating . Beachten Sie, dass alle erforderlichen Einschränkungen in jedem der Slices erfüllt sind.

Ausgangskalibrierung

Für alle TFL-Modelle, die wir bisher trainiert haben, gibt die Gitterschicht (im Modelldiagramm als "Gitter" angegeben) direkt die Modellvorhersage aus. Manchmal sind wir uns nicht sicher, ob die Gitterausgabe neu skaliert werden soll, um Modellausgaben auszugeben:

  • Die Funktionen sind $ log $ count, während die Labels count sind.
  • Das Gitter ist so konfiguriert, dass es nur sehr wenige Eckpunkte hat, aber die Etikettenverteilung ist relativ kompliziert.

In diesen Fällen können wir einen weiteren Kalibrator zwischen der Gitterausgabe und der Modellausgabe hinzufügen, um die Modellflexibilität zu erhöhen. Hier fügen wir dem Modell, das wir gerade erstellt haben, eine Kalibratorschicht mit 5 Schlüsselpunkten hinzu. Wir fügen auch einen Regularisierer für den Ausgangskalibrator hinzu, um die Funktion reibungslos zu halten.

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.7697908878326416
Testing AUC: 0.861327052116394

png

png

Die endgültige Testmetrik und die Diagramme zeigen, wie die Verwendung von Einschränkungen des gesunden Menschenverstandes dem Modell helfen kann, unerwartetes Verhalten zu vermeiden und besser auf den gesamten Eingaberaum zu extrapolieren.