суббота, 22 февраля 2020 г.

Перевод Feature Spec interface

Feature Spec interface

Перевод https://tensorflow.rstudio.com/guide/tfdatasets/feature_spec/

В этом руководстве будут рассмотрены основы использования интерфейса feature_spec() пакета tfdatasets. Перед прочтением полезно ознакомиться с R interface to TensorFlow Dataset API.

feature_spec() в R представляет собой дружественный интерфейс к модулю tf.feature_column в Python, который позволяет задавать преобразования и представления столбцов при работе с табличными данными. Реализация в R выполнена в едином стиле с пакетом recipes, краткий обзор возможностей которого был рассмотрен в публикации Инфраструктура для обучения моделей на R: rsample и recipes.

Мы будем использовать набор данных hearts, загрузив его при помощи data(hearts).

library(tfdatasets)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
data(hearts)

head(hearts)
## # A tibble: 6 x 14
##     age   sex    cp trestbps  chol   fbs restecg thalach exang oldpeak slope
##   <int> <int> <int>    <int> <int> <int>   <int>   <int> <int>   <dbl> <int>
## 1    63     1     1      145   233     1       2     150     0     2.3     3
## 2    67     1     4      160   286     0       2     108     1     1.5     2
## 3    67     1     4      120   229     0       2     129     1     2.6     2
## 4    37     1     3      130   250     0       0     187     0     3.5     3
## 5    41     0     2      130   204     0       2     172     0     1.4     1
## 6    56     1     2      120   236     0       0     178     0     0.8     1
## # ... with 3 more variables: ca <int>, thal <chr>, target <int>

Мы хотим при помощи Keras обучить модель для предсказания целевой переменной, но сначала нужно подготовить данные. Требуется преобразовать категориальные переменные в некоторый набор признаков; как правило, нужно также выполнить нормализацию всех количественных переменных.

Интерфейс feature_spec() работает с таблицами (data.frame) или наборами данных tfdatasets.

ids_train <- sample.int(nrow(hearts), 
                        size = 0.75 * nrow(hearts))
hearts_train <- hearts[ids_train, ]
hearts_test <- hearts[-ids_train, ]

Сперва создадим спецификацию признаков:

spec <- feature_spec(hearts_train, target ~ .)

spec
## -- Feature Spec --------------------------------------------------------------------------------- 
## A feature_spec with 0 steps.
## Fitted: FALSE 
## -- Steps ---------------------------------------------------------------------------------------- 
## -- Dense features ------------------------------------------------------------------------------- 
## Feature spec must be fitted before we can detect the dense features.
class(spec)
## [1] "FeatureSpec" "R6"

После создания спецификации (объект класса "FeatureSpec") нужно задать типы переменных. Это реализуется посредством добавления “шагов” к спецификации (“рецепту”):

spec <- spec %>% 
  step_numeric_column(
    all_numeric(), -cp, -restecg, -exang, -sex, -fbs,
    normalizer_fn = scaler_standard()
  ) %>% 
  step_categorical_column_with_vocabulary_list(thal)

Для определения типов переменных доступны следующие “шаги”:

  • step_numeric_column() для количественных переменных. Параметр normalizer_fn позволяет задать функцию для преобразования данных, работающую в графе вычислений TensorFlow (то есть она должна состоят из операторов TensorFlow);

  • step_categorical_column_with_vocabulary_list() для категориальных переменных с фиксированным набором значений. Если не задавать vocabulary_list, в качестве списка будут использованы найденные в наборе данных уникальные значения;

  • step_categorical_column_with_hash_bucket() для категориальных переменных с использованием хеширования;

  • step_categorical_column_with_identity() для целочисленного кодирования категориальных переменных (label encoding);

  • step_categorical_column_with_vocabulary_file() - аналог step_categorical_column_with_vocabulary_list() для случая, когда набор возможных значений хранится в файле.

Также можно использовать селекторы:

  • starts_with(), ends_with(), matches() и другие из tidyselect;

  • all_numeric() для выбора всех количественных переменных;

  • all_nominal() для выбора всех категориальных переменных;

  • has_type("float32") для выбора на основе типа данных TensorFlow.

Готовый “рецепт” выглядит следующим образом:

spec
## -- Feature Spec --------------------------------------------------------------------------------- 
## A feature_spec with 8 steps.
## Fitted: FALSE 
## -- Steps ---------------------------------------------------------------------------------------- 
## StepCategoricalColumnWithVocabularyList: thal 
## StepNumericColumn: age, trestbps, chol, thalach, oldpeak, slope, ca 
## -- Dense features ------------------------------------------------------------------------------- 
## Feature spec must be fitted before we can detect the dense features.

После указания типов данных можно добавить необходимые преобразования, например, выполнить биннинг количественной переменной age:

spec <- spec %>% 
  step_bucketized_column(age, 
                         boundaries = c(18, 25, 30, 35, 40, 
                                        45, 50, 55, 60, 65))

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

spec <- spec %>% 
  step_indicator_column(thal) %>% 
  step_embedding_column(thal, dimension = 2)

Взаимодействия между переменными добавляются при помощи step_crossed_column():

spec <- spec %>% 
  step_crossed_column(thal_and_age = c(thal, bucketized_age), 
                      hash_bucket_size = 1000) %>% 
  step_indicator_column(thal_and_age)

Отметим, что thal_and_age является категориальной переменной, поэтому требуется задать ее преобразование в числовой вид. bucketized_age - имя по умолчанию, заданное для результата применения step_bucketized_column к переменной age.

“Рецепт” может быть задан путем объединения в цепочку всех “шагов”:

spec <- feature_spec(hearts_train, target ~ .) %>% 
  step_numeric_column(
    all_numeric(), -cp, -restecg, -exang, -sex, -fbs,
    normalizer_fn = scaler_standard()
  ) %>% 
  step_categorical_column_with_vocabulary_list(thal) %>% 
  step_bucketized_column(age, 
                         boundaries = c(18, 25, 30, 35, 40, 
                                        45, 50, 55, 60, 65)) %>% 
  step_indicator_column(thal) %>% 
  step_embedding_column(thal, dimension = 2) %>% 
  step_crossed_column(c(thal, bucketized_age), 
                      hash_bucket_size = 10) %>%
  step_indicator_column(crossed_thal_bucketized_age)

После создания “рецепта” нужно выполнить оценку параметров заданных в нем преобразований, например, составить список значений категориальных переменных или найти среднее значение и стандартное отклонение для нормализации. Оценка параметров выполняется на всем наборе данных, использованном при создании “рецепта” или переданном в вызов функции fit().

spec_prep <- fit(spec)

After preparing we can see the list of dense features that were defined:

str(spec_prep$dense_features())
## List of 11
##  $ age                                  :NumericColumn(key='age', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=<function make_python_function.<locals>.python_function at 0x00000000634198C8>)
##  $ trestbps                             :NumericColumn(key='trestbps', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=<function make_python_function.<locals>.python_function at 0x0000000063419950>)
##  $ chol                                 :NumericColumn(key='chol', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=<function make_python_function.<locals>.python_function at 0x0000000063419598>)
##  $ thalach                              :NumericColumn(key='thalach', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=<function make_python_function.<locals>.python_function at 0x00000000634199D8>)
##  $ oldpeak                              :NumericColumn(key='oldpeak', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=<function make_python_function.<locals>.python_function at 0x0000000063419A60>)
##  $ slope                                :NumericColumn(key='slope', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=<function make_python_function.<locals>.python_function at 0x0000000063419730>)
##  $ ca                                   :NumericColumn(key='ca', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=<function make_python_function.<locals>.python_function at 0x0000000063419510>)
##  $ bucketized_age                       :BucketizedColumn(source_column=NumericColumn(key='age', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=<function make_python_function.<locals>.python_function at 0x00000000634198C8>), boundaries=(18.0, 25.0, 30.0, 35.0, 40.0, 45.0, 50.0, 55.0, 60.0, 65.0))
##  $ indicator_thal                       :IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='thal', vocabulary_list=('1', '2', 'fixed', 'normal', 'reversible'), dtype=tf.string, default_value=-1, num_oov_buckets=0))
##  $ embedding_thal                       :EmbeddingColumn(categorical_column=VocabularyListCategoricalColumn(key='thal', vocabulary_list=('1', '2', 'fixed', 'normal', 'reversible'), dtype=tf.string, default_value=-1, num_oov_buckets=0), dimension=2, combiner='mean', initializer=<tensorflow.python.ops.init_ops.TruncatedNormal>, ckpt_to_load_from=None, tensor_name_in_ckpt=None, max_norm=None, trainable=True)
##  $ indicator_crossed_thal_bucketized_age:IndicatorColumn(categorical_column=CrossedColumn(keys=(VocabularyListCategoricalColumn(key='thal', vocabulary_list=('1', '2', 'fixed', 'normal', 'reversible'), dtype=tf.string, default_value=-1, num_oov_buckets=0), BucketizedColumn(source_column=NumericColumn(key='age', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=<function make_python_function.<locals>.python_function at 0x00000000634198C8>), boundaries=(18.0, 25.0, 30.0, 35.0, 40.0, 45.0, 50.0, 55.0, 60.0, 65.0))), hash_bucket_size=10.0, hash_key=None))

Теперь мы можем создать подходящую модель в Keras. Для этого используем специальный слой layer_dense_features, который умеет обрабатывать признаки, созданные согласно “рецепту”. Также используется новый входной слой layer_input_from_dataset для подачи в модель данных из таблицы или набора данных tfdatasets.

library(keras)

input <- layer_input_from_dataset(hearts_train %>% select(-target))

output <- input %>% 
  layer_dense_features(dense_features(spec_prep)) %>% 
  layer_dense(units = 32, activation = "relu") %>% 
  layer_dense(units = 1, activation = "sigmoid")

model <- keras_model(input, output)

model %>% compile(
  loss = loss_binary_crossentropy, 
  optimizer = "adam", 
  metrics = "binary_accuracy"
)

Обучение модели:

history <- model %>% 
  fit(
    x = hearts_train %>% select(-target),
    y = hearts_train$target, 
    epochs = 15, 
    validation_split = 0.2
  )
plot(history)

Выполним предсказания для тестовых данных и посчитаем AUC в качестве метрики качества:

hearts_test$pred <- predict(model, hearts_test)
Metrics::auc(hearts_test$target, hearts_test$pred)
## [1] 0.8956602

Комментариев нет:

Отправить комментарий