Zapisz datę! Google I / O powraca w dniach 18-20 maja Zarejestruj się teraz
Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Wiązania kształtu za pomocą kraty Tensorflow

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło w serwisie GitHub Pobierz notatnik

Przegląd

Ten samouczek zawiera omówienie ograniczeń i regularyzatorów udostępnianych przez bibliotekę TensorFlow Lattice (TFL). Tutaj używamy estymatorów puszkowych TFL na syntetycznych zestawach danych, ale pamiętaj, że wszystko w tym samouczku można również zrobić z modelami zbudowanymi z warstw TFL Keras.

Przed kontynuowaniem upewnij się, że środowisko wykonawcze ma zainstalowane wszystkie wymagane pakiety (zaimportowane w komórkach kodu poniżej).

Ustawiać

Instalowanie pakietu TF Lattice:

pip install -q tensorflow-lattice

Importowanie wymaganych pakietów:

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)

Wartości domyślne użyte w tym przewodniku:

NUM_EPOCHS = 1000
BATCH_SIZE = 64
LEARNING_RATE=0.01

Zestaw danych szkoleniowych dla restauracji rankingowych

Wyobraź sobie uproszczony scenariusz, w którym chcemy określić, czy użytkownicy będą klikać wynik wyszukiwania restauracji. Zadanie polega na przewidywaniu współczynnika klikalności (CTR) przy danych wejściowych cechach:

  • Średnia ocena ( avg_rating ): cecha liczbowa z wartościami z zakresu [1,5].
  • Liczba recenzji ( num_reviews ): funkcja liczbowa z wartościami ograniczonymi do 200, której używamy jako miary trendów.
  • Rating dolara ( dollar_rating ): funkcja kategorialna z wartościami ciągów w zestawie {"D", "DD", "DDD", "DDDD"}.

Tutaj tworzymy syntetyczny zbiór danych, w którym prawdziwy CTR jest określony wzorem:

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

gdzie $ b (\ cdot) $ tłumaczy każdą wartość dollar_rating na wartość bazową:

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

Ta formuła odzwierciedla typowe wzorce użytkowników. np. biorąc pod uwagę, że wszystko inne zostało naprawione, użytkownicy wolą restauracje z wyższą liczbą gwiazdek, a restauracje „\ $ \ $” otrzymają więcej kliknięć niż „\ $”, po których następują „\ $ \ $ \ $” i „\ $ \ $ \ $ \ $ ".

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

Rzućmy okiem na wykresy konturowe tej funkcji 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

Przygotowywanie danych

Teraz musimy stworzyć nasze syntetyczne zestawy danych. Zaczynamy od wygenerowania symulowanego zbioru danych restauracji i ich funkcji.

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

Przygotujmy zestawy danych do szkolenia, walidacji i testowania. Kiedy restauracja jest wyświetlana w wynikach wyszukiwania, możemy zarejestrować zaangażowanie użytkownika (kliknięcie lub brak kliknięcia) jako punkt próbny.

W praktyce użytkownicy często nie przeglądają wszystkich wyników wyszukiwania. Oznacza to, że użytkownicy prawdopodobnie zobaczą tylko restauracje, które zostały już uznane za „dobre” zgodnie z obecnie stosowanym modelem rankingowym. W rezultacie „dobre” restauracje są częściej pod wrażeniem i są nadreprezentowane w zestawach danych szkoleniowych. W przypadku korzystania z większej liczby funkcji w zestawie danych szkoleniowych mogą występować duże luki w „złych” częściach przestrzeni funkcji.

Gdy model jest używany do rankingu, często jest oceniany na podstawie wszystkich odpowiednich wyników z bardziej jednolitym rozkładem, który nie jest dobrze reprezentowany przez zbiór danych szkoleniowych. Elastyczny i skomplikowany model może w tym przypadku zawieść z powodu nadmiernego dopasowania do nadmiernie reprezentowanych punktów danych, a tym samym brak możliwości uogólnienia. Rozwiązujemy ten problem, stosując wiedzę o domenie w celu dodania ograniczeń kształtu, które prowadzą model do tworzenia rozsądnych prognoz, gdy nie może ich pobrać z zestawu danych szkoleniowych.

W tym przykładzie zbiór danych szkoleniowych składa się głównie z interakcji użytkowników z dobrymi i popularnymi restauracjami. Zbiór danych testowych ma jednolity rozkład, aby zasymulować ustawienie oceny omówione powyżej. Należy pamiętać, że taki zestaw danych testowych nie będzie dostępny w przypadku rzeczywistego problemu.

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

Definiowanie input_fns używanego do szkolenia i oceny:

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

Dopasowywanie drzew ze wzmocnieniem gradientowym

Zacznijmy od tylko dwóch funkcji: avg_rating i num_reviews .

Tworzymy kilka pomocniczych funkcji do kreślenia i obliczania metryk walidacyjnych i testowych.

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)

W zestawie danych możemy dopasować drzewa decyzyjne wzmocnione gradientem TensorFlow:

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

Mimo że model uchwycił ogólny kształt prawdziwego CTR i ma przyzwoite wskaźniki walidacji, w kilku częściach przestrzeni wejściowej zachowuje się sprzecznie z intuicją: szacowany CTR spada wraz ze wzrostem średniej oceny lub liczby recenzji. Wynika to z braku punktów próbkowania w obszarach nieobjętych dobrze zbiorem danych szkoleniowych. Model po prostu nie ma możliwości wywnioskowania prawidłowego zachowania wyłącznie na podstawie danych.

Aby rozwiązać ten problem, wymuszamy ograniczenie kształtu, zgodnie z którym model musi generować wartości rosnące monotonicznie zarówno w odniesieniu do średniej oceny, jak i liczby recenzji. Później zobaczymy, jak zaimplementować to w TFL.

Montaż DNN

Możemy powtórzyć te same kroki z klasyfikatorem DNN. Możemy zaobserwować podobny wzorzec: brak wystarczającej liczby punktów próbkowania przy małej liczbie recenzji skutkuje bezsensowną ekstrapolacją. Zauważ, że chociaż metryka walidacji jest lepsza niż rozwiązanie drzewiaste, metryka testowania jest znacznie gorsza.

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

Wiązania kształtu

TensorFlow Lattice (TFL) koncentruje się na wymuszaniu ograniczeń kształtu w celu ochrony zachowania modelu poza danymi uczącymi. Te ograniczenia kształtu są stosowane do warstw TFL Keras. Ich szczegóły można znaleźć w naszym artykule JMLR .

W tym samouczku używamy estymatorów bazowych TF do pokrycia różnych ograniczeń kształtu, ale pamiętaj, że wszystkie te kroki można wykonać z modelami utworzonymi z warstw TFL Keras.

Podobnie jak w przypadku każdego innego estymatora TensorFlow, estymatory bazowe TFL używają kolumn funkcji do definiowania formatu wejściowego i używają szkolenia input_fn do przekazywania danych. Korzystanie z estymatorów bazowych TFL wymaga również:

  • konfiguracja modelu : definiowanie architektury modelu oraz ograniczeń kształtu i regularyzatorów dla poszczególnych funkcji.
  • analiza cech input_fn : a TF input_fn przekazująca dane do inicjalizacji TFL.

Aby uzyskać dokładniejszy opis, zapoznaj się z samouczkiem gotowych estymatorów lub dokumentacją API.

Monotoniczność

Najpierw zajmiemy się kwestiami monotoniczności, dodając ograniczenia kształtu monotoniczności do obu cech.

Aby poinstruować TFL, aby wymuszał ograniczenia kształtu, określamy ograniczenia w konfiguracjach funkcji . Poniższy kod pokazuje, w jaki sposób możemy wymagać monotonicznego wzrostu wyniku w odniesieniu zarówno do num_reviews jak i avg_rating , ustawiając 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

Użycie CalibratedLatticeConfig tworzy klasyfikator bazowy, który najpierw stosuje kalibrator do każdego wejścia (fragmentaryczna funkcja liniowa dla cech numerycznych), po czym następuje warstwa siatki , aby nieliniowo połączyć skalibrowane cechy. Do wizualizacji modelu możemy użyć tfl.visualization . W szczególności poniższy wykres przedstawia dwa wyszkolone kalibratory zawarte w klasyfikatorze w puszkach.

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

Po dodaniu ograniczeń szacowany CTR będzie zawsze wzrastał wraz ze wzrostem średniej oceny lub liczby recenzji. Odbywa się to poprzez upewnienie się, że kalibratory i krata są monotoniczne.

Malejące zyski

Malejące zyski oznaczają, że krańcowy zysk zwiększania określonej wartości cechy będzie malał wraz ze wzrostem wartości. W naszym przypadku oczekujemy, że funkcja num_reviews zgodna z tym wzorcem, więc możemy odpowiednio skonfigurować jego kalibrator. Zauważ, że możemy rozłożyć malejące zwroty na dwa wystarczające warunki:

  • kalibrator rośnie monotonicznie, i
  • kalibrator jest wklęsły.
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

Zwróć uwagę, jak metryka testowania poprawia się po dodaniu ograniczenia wklęsłości. Fabuła przewidywań również lepiej przypomina podstawową prawdę.

Wiązanie kształtu 2D: zaufanie

5 gwiazdek dla restauracji, która ma tylko jedną lub dwie recenzje, jest prawdopodobnie oceną niewiarygodną (restauracja może nie być w rzeczywistości dobra), podczas gdy 4 gwiazdki dla restauracji z setkami recenzji jest znacznie bardziej wiarygodna (restauracja jest prawdopodobnie dobre w tym przypadku). Widzimy, że liczba recenzji restauracji wpływa na to, jak bardzo ufamy jej średniej ocenie.

Możemy zastosować ograniczenia zaufania TFL, aby poinformować model, że większa (lub mniejsza) wartość jednej cechy wskazuje na większe zaufanie lub zaufanie do innej cechy. Odbywa się to poprzez ustawienie konfiguracji reflects_trust_in konfiguracji funkcji.

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

Poniższy wykres przedstawia wytrenowaną funkcję sieci. Ze względu na ograniczenie zaufania spodziewamy się, że większe wartości skalibrowanych num_reviews wymuszą większe nachylenie w stosunku do skalibrowanej avg_rating , powodując bardziej znaczący ruch na wyjściu sieciowym.

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

Kalibratory wygładzające

Przyjrzyjmy się teraz kalibratorowi avg_rating . Choć narasta monotonicznie, zmiany na jego zboczach są gwałtowne i trudne do zinterpretowania. To sugeruje, że moglibyśmy rozważyć wygładzenie tego kalibratora za pomocą konfiguracji regulatora w pliku regularizer_configs .

Tutaj stosujemy regulator wrinkle aby zmniejszyć zmiany krzywizny. Możesz również użyć regulatora laplacian aby spłaszczyć kalibrator i regulatora hessian aby uczynić go bardziej liniowym.

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

Kalibratory działają teraz płynnie, a ogólny szacowany CTR lepiej odpowiada rzeczywistości. Znajduje to odzwierciedlenie zarówno w mierniku testowym, jak i na wykresach konturowych.

Częściowa monotoniczność dla kalibracji kategorialnej

Do tej pory używaliśmy tylko dwóch cech numerycznych w modelu. Tutaj dodamy trzecią funkcję za pomocą warstwy kalibracji kategorycznej. Ponownie zaczynamy od skonfigurowania funkcji pomocniczych do kreślenia i obliczania metrycznego.

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)

Aby uwzględnić trzecią cechę, dollar_rating , powinniśmy pamiętać, że cechy kategorialne wymagają nieco innego traktowania w TFL, zarówno jako kolumna funkcji, jak i konfiguracja funkcji. Tutaj wymuszamy ograniczenie częściowej monotoniczności, zgodnie z którym wyniki dla restauracji „DD” powinny być większe niż dla restauracji „D”, gdy wszystkie inne dane wejściowe są ustalone. Odbywa się to za pomocą ustawienia monotonicity w konfiguracji funkcji.

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

Ten kalibrator jakościowy pokazuje preferencje wyniku modelu: DD> D> DDD> DDDD, co jest zgodne z naszą konfiguracją. Zauważ, że istnieje również kolumna dla brakujących wartości. Chociaż nie ma brakującej funkcji w naszych danych treningowych i testowych, model zapewnia nam przypisanie brakującej wartości, jeśli zdarzy się to podczas udostępniania modelu podrzędnego.

Tutaj również wykreślamy przewidywany CTR tego modelu w dollar_rating od dollar_rating . Zauważ, że wszystkie wymagane przez nas ograniczenia są spełnione w każdym z wycinków.

Kalibracja wyjściowa

Dla wszystkich modeli TFL, które do tej pory wytrenowaliśmy, warstwa sieci (oznaczona na wykresie modelu jako „Krata”) bezpośrednio przekazuje prognozę modelu. Czasami nie jesteśmy pewni, czy dane wyjściowe sieci powinny zostać przeskalowane, aby emitować wyjścia modelu:

  • funkcje to $ log $ zliczeń, podczas gdy etykiety to zliczenia.
  • krata jest skonfigurowana tak, aby mieć bardzo mało wierzchołków, ale dystrybucja etykiet jest stosunkowo skomplikowana.

W takich przypadkach możemy dodać kolejny kalibrator między wyjściem sieciowym a wyjściem modelu, aby zwiększyć elastyczność modelu. Tutaj dodajmy warstwę kalibratora z 5 punktami kluczowymi do właśnie zbudowanego modelu. Dodajemy również regularyzator do kalibratora wyjściowego, aby zachować płynność działania.

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

Końcowa miara testowa i wykresy pokazują, w jaki sposób użycie ograniczeń zdroworozsądkowych może pomóc modelowi uniknąć nieoczekiwanego zachowania i lepiej ekstrapolować na całą przestrzeń wejściową.