Ограничения формы с решеткой Tensorflow

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

Обзор

Это руководство представляет собой обзор ограничений и регуляризаторов, предоставляемых библиотекой TensorFlow Lattice (TFL). Здесь мы используем стандартные оценщики TFL для синтетических наборов данных, но обратите внимание, что все в этом руководстве также можно сделать с моделями, построенными из слоев TFL Keras.

Прежде чем продолжить, убедитесь, что в вашей среде выполнения установлены все необходимые пакеты (импортированные в ячейки кода ниже).

Настраивать

Установка пакета TF Lattice:

pip install -q tensorflow-lattice

Импорт необходимых пакетов:

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)

Значения по умолчанию, используемые в этом руководстве:

NUM_EPOCHS = 1000
BATCH_SIZE = 64
LEARNING_RATE=0.01

Набор обучающих данных для ранжирования ресторанов

Представьте себе упрощенный сценарий, в котором мы хотим определить, будут ли пользователи нажимать на результат поиска ресторана. Задача состоит в том, чтобы предсказать рейтинг кликов (CTR) с учетом входных характеристик:

  • Средняя оценка ( avg_rating ): числовая характеристика со значениями в диапазоне [1,5].
  • Количество отзывов ( num_reviews ): числовая функция со значениями, ограниченными 200, которые мы используем как меру модности.
  • Рейтинг в долларах ( dollar_rating ): категориальная функция со строковыми значениями в наборе {"D", "DD", "DDD", "DDDD"}.

Здесь мы создаем синтетический набор данных, в котором истинный CTR задается по формуле:

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

где $ b (\ cdot) $ переводит каждый dollar_rating в базовое значение:

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

Эта формула отражает типичные пользовательские шаблоны. например, если все остальное исправлено, пользователи предпочитают рестораны с более высоким рейтингом, и рестораны "\ $ \ $" получат больше кликов, чем "\ $", за которым следуют "\ $ \ $ \ $" и "\ $ \ $ \ $" \ $ ".

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

Давайте посмотрим на контурные графики этой функции 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

Подготовка данных

Теперь нам нужно создать наши синтетические наборы данных. Начнем с создания смоделированного набора данных о ресторанах и их характеристиках.

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

Создадим наборы данных для обучения, проверки и тестирования. Когда ресторан просматривается в результатах поиска, мы можем записать участие пользователя (щелчок или отсутствие щелчка) в качестве точки выборки.

На практике пользователи часто просматривают не все результаты поиска. Это означает, что пользователи, скорее всего, будут видеть только те рестораны, которые уже считаются «хорошими» в соответствии с текущей используемой моделью рейтинга. В результате «хорошие» рестораны чаще поражаются и чрезмерно представлены в наборах данных для обучения. При использовании большего количества функций обучающий набор данных может иметь большие пробелы в «плохих» частях пространства функций.

Когда модель используется для ранжирования, она часто оценивается по всем релевантным результатам с более равномерным распределением, которое недостаточно хорошо представлено в обучающем наборе данных. В этом случае гибкая и сложная модель может потерпеть неудачу из-за переобучения избыточно представленных точек данных и, следовательно, отсутствия возможности обобщения. Мы решаем эту проблему, применяя знания предметной области для добавления ограничений формы, которые помогают модели делать разумные прогнозы, когда она не может извлечь их из обучающего набора данных.

В этом примере набор обучающих данных в основном состоит из взаимодействия пользователей с хорошими и популярными ресторанами. Набор данных тестирования имеет равномерное распределение для имитации настройки оценки, описанной выше. Обратите внимание, что такой набор данных тестирования не будет доступен в условиях реальной проблемы.

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

Определение input_fns, используемого для обучения и оценки:

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

Установка деревьев с градиентным усилением

Начнем всего с двух функций: avg_rating и num_reviews .

Мы создаем несколько вспомогательных функций для построения и расчета показателей валидации и тестирования.

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)

Мы можем поместить деревья решений с градиентным усилением 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

Несмотря на то, что модель отражает общую форму истинного CTR и имеет приличные показатели валидации, она имеет противоречивое поведение в нескольких частях входного пространства: расчетный CTR уменьшается по мере увеличения среднего рейтинга или количества отзывов. Это связано с отсутствием точек выборки в областях, не охваченных набором обучающих данных. Модель просто не имеет возможности вывести правильное поведение исключительно на основе данных.

Чтобы решить эту проблему, мы применяем ограничение формы, согласно которому модель должна выводить значения, монотонно возрастающие как по средней оценке, так и по количеству отзывов. Позже мы увидим, как реализовать это в TFL.

Установка DNN

Мы можем повторить те же шаги с классификатором DNN. Мы можем наблюдать аналогичную картину: нехватка точек выборки при небольшом количестве обзоров приводит к бессмысленной экстраполяции. Обратите внимание, что даже несмотря на то, что метрика проверки лучше, чем древовидное решение, метрика тестирования намного хуже.

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

Ограничения формы

TensorFlow Lattice (TFL) ориентирован на соблюдение ограничений формы для защиты поведения модели за пределами обучающих данных. Эти ограничения формы применяются к слоям TFL Keras. Их подробности можно найти в нашей статье JMLR .

В этом руководстве мы используем стандартные оценщики TF для покрытия различных ограничений формы, но обратите внимание, что все эти шаги могут быть выполнены с моделями, созданными из слоев TFL Keras.

Как и любой другой оценщик TensorFlow, стандартные оценщики TFL используют столбцы функций для определения входного формата и используют обучающий input_fn для передачи данных. Использование стандартных оценщиков TFL также требует:

  • конфигурация модели : определение архитектуры модели, ограничений и регуляризаторов формы для каждого объекта.
  • анализ функции input_fn : TF input_fn, передающий данные для инициализации TFL.

Для более подробного описания см. Руководство по стандартным оценкам или документацию по API.

Монотонность

Сначала мы решаем проблемы монотонности, добавляя ограничения монотонности формы к обеим функциям.

Чтобы указать TFL принудительно применять ограничения формы, мы указываем ограничения в конфигурациях функций . В следующем коде показано, как мы можем потребовать монотонного увеличения вывода по отношению к num_reviews и avg_rating , установив avg_rating 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

Использование CalibratedLatticeConfig создает стандартный классификатор, который сначала применяет калибратор к каждому входу (кусочно-линейная функция для числовых функций), а затем слой решетки для нелинейного слияния калиброванных функций. Мы можем использовать tfl.visualization для визуализации модели. В частности, на следующем графике показаны два обученных калибратора, включенных в стандартный классификатор.

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

С добавленными ограничениями расчетный CTR всегда будет увеличиваться по мере увеличения среднего рейтинга или количества отзывов. Для этого нужно убедиться, что калибраторы и решетка монотонны.

Уменьшение прибыли

Уменьшение отдачи означает, что предельный выигрыш от увеличения определенного значения функции будет уменьшаться по мере того, как мы увеличиваем значение. В нашем случае мы ожидаем, что функция num_reviews следует этому шаблону, поэтому мы можем соответствующим образом настроить его калибратор. Обратите внимание, что мы можем разложить убывающую отдачу на два достаточных условия:

  • калибратор монотонно увеличивается, и
  • калибратор вогнутый.
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

Обратите внимание, как улучшается метрика тестирования, добавляя ограничение вогнутости. Сюжет предсказания также больше напоминает наземную истину.

Ограничение 2D-формы: доверие

5-звездочный рейтинг ресторана с одним или двумя отзывами, скорее всего, ненадежный (ресторан на самом деле может быть не очень хорошим), тогда как 4-звездочный рейтинг ресторана с сотнями отзывов гораздо более надежен скорее всего, хорошо в этом случае). Мы видим, что количество отзывов о ресторане влияет на то, насколько мы доверяем его средней оценке.

Мы можем применить ограничения доверия TFL, чтобы сообщить модели, что большее (или меньшее) значение одной функции указывает на большее доверие к другой функции. Это делается путем установки конфигурации reflects_trust_in конфигурации функции.

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

На следующем графике представлена ​​обученная функция решетки. Из-за ограничения доверия мы ожидаем, что большие значения откалиброванных num_reviews вызовут более высокий наклон по avg_rating с откалиброванным avg_rating , что приведет к более значительному avg_rating в выходных данных решетки.

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

Сглаживающие калибраторы

Теперь посмотрим на калибратор avg_rating . Хотя он монотонно увеличивается, изменения его наклона резкие и трудно интерпретируемые. Это говорит о том, что мы могли бы захотеть рассмотреть возможность сглаживания этого калибратора с помощью настройки регуляризатора в regularizer_configs .

Здесь мы применяем регуляризатор wrinkle чтобы уменьшить изменения кривизны. Вы также можете использовать laplacian регуляризатор, чтобы сгладить калибратор, и hessian регуляризатор, чтобы сделать его более линейным.

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

Калибраторы теперь работают плавно, а общий оценочный CTR лучше соответствует действительности. Это отражается как в метрике тестирования, так и в контурных графиках.

Частичная монотонность для категориальной калибровки

До сих пор мы использовали только две числовые функции в модели. Здесь мы добавим третью функцию, используя категориальный калибровочный слой. Мы снова начинаем с настройки вспомогательных функций для построения графиков и расчета показателей.

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)

Чтобы задействовать третью функцию, dollar_rating , мы должны вспомнить, что категориальные функции требуют немного другого обращения в TFL, как как столбец функции, так и как конфигурация функции. Здесь мы применяем ограничение частичной монотонности, согласно которому выходные данные для ресторанов «DD» должны быть больше, чем для ресторанов «D», когда все остальные входы фиксированы. Это делается с помощью параметра monotonicity в конфигурации функции.

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

Этот категориальный калибратор показывает предпочтение вывода модели: DD> D> DDD> DDDD, что согласуется с нашей настройкой. Обратите внимание, что есть также столбец с пропущенными значениями. Хотя в наших данных обучения и тестирования нет отсутствующих функций, модель предоставляет нам вменение недостающего значения, если это произойдет во время последующего обслуживания модели.

Здесь мы также dollar_rating прогнозируемый CTR этой модели с dollar_rating . Обратите внимание, что все необходимые ограничения выполняются в каждом из срезов.

Калибровка выхода

Для всех моделей TFL, которые мы обучили до сих пор, слой решетки (обозначенный как «Решетка» на графике модели) напрямую выводит прогноз модели. Иногда мы не уверены, нужно ли масштабировать выход решетки для получения выходных данных модели:

  • функции - это $ log $ counts, а метки - counts.
  • решетка сконфигурирована так, чтобы иметь очень мало вершин, но распределение меток относительно сложно.

В этих случаях мы можем добавить еще один калибратор между выходом решетки и выходом модели, чтобы повысить гибкость модели. Здесь давайте добавим слой калибратора с 5 ключевыми точками к модели, которую мы только что построили. Мы также добавляем регуляризатор для выходного калибратора, чтобы функция оставалась гладкой.

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

Окончательная метрика тестирования и графики показывают, как использование ограничений здравого смысла может помочь модели избежать неожиданного поведения и лучше экстраполировать на все пространство ввода.