среда, 31 октября 2018 г.

TensorFlow Estimators: колонки свойств (feature columns)

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

Среди предсозданных Estimators мы будем использовать tf.estimator.DNNClassifier для тренировки модели прогнозировать различные виды цветов ириса по четырем вводным свойствам. В этом примере создаются только числовые колонки свойств (тип tf.feature_column.numeric_column). Хотя числовые колонки свойств модели измеряют лепестки и чашелистики эффективно, в реальных данных наборы содержат все виды свойств, многие из которых нечисловые.

Некоторые свойства из реальных данных числовые (такие как, долгота), но многие нет.


Ввод в глубокую нейронную сеть

На каком типе данных может оперировать глубокая нейронная сеть? Ответ - числа (например, tf.float32). Каждый нейрон в нейронной сети выполняет операции умножения и сложения с весами и вводными данными. Вводные данные из реальной жизни, однако, часто содержат нечисловые (категориальные) значения. Например, предположим свойство product_class, который может содержать следующие три нечисловые значения:

  • kitchenware
  • electronics
  • sports

Модели машинного обучения в основном представляют категориальные значения как простые вектора, в которых 1 представляет присутствие значения, а 0 означает отсуствие значения. Например, когда product_class установлен в sports, модель машинного обучения представляет product_class как [0, 0, 1], что означает:

  • 0: kitchenware
  • 0: electronics
  • 1: sports

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

Колонки свойств (Feature Columns)

Как показано на следующем графике, ввод данных в модель определяется через feature_columns аргумент Estimator'а (DNNClassifier для ирисов). Колонки свойств (feature columns) - это мост между вводными данными (которые возвращает input_fn) и моделью.

Колонки свойств создают мост между сырыми данными и данными, которые требуются модели.


Для создания колонок свойств необходимо вызвать функции из tf.feature_column модуля. В этом посте мы затронем 9 функций из этого модуля. Как показано на следующей иллюстрации, все 9 функций возвращают либо Categorical Column, либо Dense Column объект, исключая bucketized_column, который наследуется из обоих этих классов:

Методы колонок свойств делятся на две категории и одну гибридную категорию.


Рассмотрим эти функции более детально.

Числовая колонка

Классификатор ирисов вызывает tf.feature_column.numeric_column функцию для всех вводных свойств:

  • SepalLength
  • SepalWidth
  • PetalLength
  • PetalWidth

Хотя для tf.numeric_column доступны опциональные аргументы, вызов tf.numeric_column без аргументов, как показано ниже, это отличный способ определить числовое значение с типом данных по умолчанию (tf.float32) как ввод для модели:

# По умолчанию tf.float32 scalar.
numeric_feature_column = tf.feature_column.numeric_column(
                                             key="SepalLength")

Чтобы определить числовой тип данных не по умолчанию, используйте dtype аргумент. Например:

# Предаставляет tf.float64 scalar.
numeric_feature_column = tf.feature_column.numeric_column(
                                              key="SepalLength",
                                              dtype=tf.float64)

По умолчанию числовая колонка создает единственное значение (скаляр). Используйте shape аргумент, чтобы определить другую форму. Например:

# Представляем 10-элементный вектор, 
# в котором каждая ячейка содержит tf.float32.
vector_feature_column = tf.feature_column.numeric_column(key="Bowling",
                                                         shape=10)

# Представляем 10x5 матрицу, в которой каждая ячейка содержит tf.float32
matrix_feature_column = tf.feature_column.numeric_column(key="MyMatrix",
                                                         shape=[10,5])


Пакетная колонка (Bucketized column)

Часто нет желания отправлять отдельное число напрямую в модель, вместо этого можно использовать пакетизацию и разделить значения на различные категории на основе числовых диапазонов. Чтобы выполнить это, создайте tf.feature_column.bucketized_column. Например, предположим сырые данные, представляющие год, в котором был построен дом. Вместо представления этого года как отдельной числовой колонки, разделим года на следующие четыре пакета:

Разделение данных по годам на четыре пакета.


Модель будет представлять пакеты следующим образом:

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

Следующий код демонстрирует как создать пакетизированное свойство:

# Во-первых, конвертируем сырой ввод в числовую колонку.
numeric_feature_column = tf.feature_column.numeric_column("Year")

# Затем пакетизируем числовую колонку по годам 1960, 1980, и 2000.
bucketized_feature_column = tf.feature_column.bucketized_column(
    source_column = numeric_feature_column,
    boundaries = [1960, 1980, 2000])

Отметим, что определение вектора по границам из трех элементов создает четырех-элементный пакетизированный вектор.


Категориальная индивидуальная колонка (Categorical identity column)

Категориальные индивидуальные колонки могут быть рассмотрены как специальный случай пакетизированных колонок. В традиционных пакетизированных колонках каждый пакет представляет диапазон значений (например, от 1960 до 1979). В категриальной индивидуальной колонке каждый пакет представляет единственное уникальное целое число. Например, представим числовой диапазон [0,4]. То есть представим числа 0, 1, 2, или 3. В этом случае, категориальное индивидуальное картирование выглядит следующим образом:

Картирование категориальной индивидуальной колонки. Следует отметить, что это одноразовое кодирование, не бинарное числовое кодирование.


Как и с пакетизированными клонками, модель может обучаться отдельному весу для каждого класса в категориальной индивидуальной колонке. Например, вместо использования строки для представления product_class, представим каждый класс уникальным числовым значением.

  • 0="kitchenware"
  • 1="electronics"
  • 2="sport"

Вызываем tf.feature_column.categorical_column_with_identity для создания категориальной индивидуальной колонки. Например:

# Создаем категориальный вывод 
# для числового свойства с именем "my_feature_b",
# значения my_feature_b должны быть >= 0 и < num_buckets
identity_feature_column = \
    tf.feature_column.categorical_column_with_identity(
        key='my_feature_b',
        num_buckets=4) # Значения [0, 4]

# Для того чтобы предыдущий вызов заработал, 
# input_fn() должна возвращать словарь, 
# содержащий 'my_feature_b' в качестве ключа. Более того, значения,
# присвоенные к 'my_feature_b', должны быть от 0 до 4.
def input_fn():
    ...
    return ({ 'my_feature_a':[7, 9, 5, 2], 
              'my_feature_b':[3, 1, 2, 2] },
            [Label_values])


Категориальная словарная колонка (Categorical vocabulary column)

Мы не можем вводить строки напрямую в модель. Вместо этого, сначала мы должны картировать строки на числовые или категориальные значения. Категориальные словарные колонки предоставляют удобный способ представить строку как одноразовый вектор. Например:

Картирование строковых значений в словарные колонки.


Как видно, категориальные словарные колонки - это enum версия категориальных индивидуальных колонок. TensorFlow предоставляет две функции для создания категориальных словарных колонок:

  • tf.feature_column.categorical_column_with_vocabulary_list
  • tf.feature_column.categorical_column_with_vocabulary_file

categorical_column_with_vocabulary_list картирует каждую строку к числу, основываясь на явно заданном словарном списке. Например:

# Дан ввод "feature_name_from_input_fn" являющийся строкой,
# создаем категориальное свойство, картируя ввод
# к одному из элементов в словарном списке.
vocabulary_feature_column =
    tf.feature_column.categorical_column_with_vocabulary_list(
        key=feature_name_from_input_fn,
        vocabulary_list=["kitchenware", "electronics", "sports"])

Предыдущая функция простая и ясная, но имеет значительный недостаток. А именно, необходимо слишком много печатать, когда словарный лист достаточно длинный. Для этих случаев вызываем метод tf.feature_column.categorical_column_with_vocabulary_file, который позволяет размещать словарные слова в отдельном файле. Например:

# Дан ввод "feature_name_from_input_fn" являющийся строкой,
# создаем категориальное свойство для нашей модели, картируя ввод
# к одному из элементов в словарном файле
vocabulary_feature_column =
    tf.feature_column.categorical_column_with_vocabulary_file(
        key=feature_name_from_input_fn,
        vocabulary_file="product_class.txt",
        vocabulary_size=3)

product_class.txt должен содержать по одной строке для каждого словарного элемента. В нашем случае:

kitchenware
electronics
sports


Хэшированная колонка (Hashed Column)

До сих пор мы работали с очень малым количеством категорий. Например, наш product_class пример имеет всего лишь 3 категории. Однако, зачастую количество категорий может быть настолько большим, что невозможно иметь индивидуальные категории для каждого словарного слова или числа, посколько это потребует слишком большого количества памяти. Для этих случаев можно взглянуть на проблему с другой стороны и задать себе вопрос: "Сколько категорий мне необходимо для моего ввода?" На деле tf.feature_column.categorical_column_with_hash_bucket функция позволяет определить такое количество категорий. Для этого типа колонки свойств модель высчитывает значение хэша для ввода и затем складывает его в одну из hash_bucket_size категорий, используя modulo оператор, как в следующем псевдокоде:

# pseudocode
feature_id = hash(raw_feature) % hash_bucket_size

Код создания feature_column может выглядеть следующим образом:

hashed_feature_column =
    tf.feature_column.categorical_column_with_hash_bucket(
        key = "some_feature",
        hash_bucket_size = 100) # количество категорий

Мы переводим различные значения ввода в меньший набор категорий. Это означает, что два вероятно не связанных ввода будут картированы к одной и той же категории, и следовательно означать одну и ту же категорию в нейронной сети. Следующая иллюстрация показывает такую ситуацию, kitchenware и sports оба назначены к категории 12 (хэш пакет):

Представление данных с хэш пакетами.


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


Пересеченная колонка (Crossed column)

Комбинирование свойств в единственное свойство, известное как пересечение свойств (feature cross), позволяет модели обучаться отдельным весам для каждой комбинации свойств.

Конкретней, предположим мы хотим, чтобы наша модель вычисляла цены на недвижимость в Атланте. Цены на недвижимость в рамках города значительно различаются в зависимости от расположения. Представление широты и долготы отдельными свойствами не очень удобно для определения зависимости от расположения, однако, пересечение широты и долготы в единое свойство может указать расположение. Предположим мы представим Атланту как решетку из 100x100 квадратных участков, определяя каждый из 10000 участков пересечением свойств широты и долготы. Это пересечение свойств позволит модели тренироваться на ценовых условиях, относящихся к каждому участку, что является намного более эффективным методом, чем широта и долгота по отдельности.

Следующая иллюстрация показывает план, с значениями широты и долготы по углам города:

Карта Атланты. Представим эту карту, разделенной на 10000 участков одинакового размера.


Для решения, мы используем комбинацию bucketized_column, которую мы рассматривали выше, и tf.feature_column.crossed_column функции.

def make_dataset(latitude, longitude, labels):
    assert latitude.shape == longitude.shape == labels.shape

    features = {'latitude': latitude.flatten(),
                'longitude': longitude.flatten()}
    labels=labels.flatten()

    return tf.data.Dataset.from_tensor_slices((features, labels))


# Пакетизируем широту и долготу, используя 'края'
latitude_bucket_fc = tf.feature_column.bucketized_column(
    tf.feature_column.numeric_column('latitude'),
    list(atlanta.latitude.edges))

longitude_bucket_fc = tf.feature_column.bucketized_column(
    tf.feature_column.numeric_column('longitude'),
    list(atlanta.longitude.edges))

# Создаем пересечения пакетизированных колонок, 
# используя 5000 хэш ящиков.
crossed_lat_lon_fc = tf.feature_column.crossed_column(
    [latitude_bucket_fc, longitude_bucket_fc], 5000)

fc = [
    latitude_bucket_fc,
    longitude_bucket_fc,
    crossed_lat_lon_fc]

# Создаем и тренируем Estimator.
est = tf.estimator.LinearRegressor(fc, ...)

Мы можем создавать пересечения свойств по любому из следующего:

  • Имена свойств; то есть имена из dict, возвращаемого из input_fn.
  • Любая категориальная колонка, исключая categorical_column_with_hash_bucket (ввиду того что crossed_column хэширует ввод).

Когда колонки свойств latitude_bucket_fc и longitude_bucket_fc пересечены, TensorFlow создаст (latitude_fc, longitude_fc) пары для каждого примера. Это произведет полную таблицу возможных вариантов, как следующее:

(0,0),  (0,1)...  (0,99)
 (1,0),  (1,1)...  (1,99)
   ...     ...       ...
(99,0), (99,1)...(99, 99)

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

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

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

Индикаторные и встроенные колонки (Indicator and embedding columns)

Индикаторные колонки и встроенные колонки никогда не работают на свойствах напрямую, но вместо этого принимают категориальные колонки как ввод.

Когда мы используем индикаторную колонку, мы говорим TensorFlow выполнять то, что мы видели в категориальном product_class примере. То есть, индикаторная колонка оценивает каждую категорию как элемент в одноразовом векторе, где соотвествующая категория имеет значение 1, а остальные имеют значение 0:

Представление данных в индикаторных колонках.


Вот как создавать индикаторную колонку, вызывая tf.feature_column.indicator_column:

categorical_column = ... # Создаем любой вид категориальной колонки.

# Представляем категориальную колонку как индикаторную колонку
indicator_column = tf.feature_column.indicator_column(
                                          categorical_column)

Теперь предположим, что мы имеем не просто три возможных класса, а миллион возможных классов. Или миллиард. По ряду причин с ростом количества категорий становится невозможно тренировать нейронную сеть, используя индикаторные колонки.

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

Взглянем на пример, сравнивающий индикаторные и встроенные колонки. Предположим вводные примеры состоят из различных слов из ограниченного набора 81 слова. Далее предположим, что набор данных предоставляет следующие вводные слова в 4 отдельных примерах:

  • "dog"
  • "spoon"
  • "scissors"
  • "guitar"

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

Встроенная колонка сохраняет категориальные данные в векторе с меньшим количеством измерений, чем индикаторная колонка. (Мы просто разместим случайные числа в встроенных векторах; тренировка определяет актуальные числа.)

Когда пример орабатывается, одна из categorical_column_with... функций картирует строку примера к числовому категориальному свойству. Например, функция картирует "spoon" к [32]. (32 мы просто придумали - актуальное значение зависит от картирующей функции.) Затем можно представить эти числовые категориальные свойства любым из следующих способов:

  • Как индикаторная колонка. Функция преобразует каждое числовое категориальное значение в 81-элементный вектор (поскольку мы ипользуем ограничение из 81 слова), размещая 1 для индекса категориального значения (0, 32, 79, 80) и 0 для всех остальных позиций.
  • Как встроенная колонка. Функция использует числовые категориальные значения (0, 32, 79, 80) как индексы в поисковой таблице. Каждый слот в этой поисковой таблице состоит из 3-элементного вектора.

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

Почему встроенный вектор имеет размер 3 в нашем примере? Следующая "формула" предоставляет общее правило насчет количества измерений во встроенном векторе:

embedding_dimensions =  number_of_categories**0.25

Ввиду того, что размер нашего словаря в этом примере 81, рекомендуемое количество измерений равно 3:

3 =  81**0.25

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

Вызываем tf.feature_column.embedding_column для создания embedding_column, как видно в следующем участке кода:

categorical_column = ... # Создаем любую категориальную колонку

# Представляем категориальную колонку как встроенную колонку.
# Это означает создание встроенного вектора поисковой таблицы 
# с одним элементом для каждой категории.
embedding_column = tf.feature_column.embedding_column(
    categorical_column=categorical_column,
    dimension=embedding_dimensions)

Как показывает следующий список, не все Estimators принимают все типы колонок свойств.

  • tf.estimator.LinearClassifier и tf.estimator.LinearRegressor: принимают все типы колонок свойств.
  • tf.estimator.DNNClassifier и tf.estimator.DNNRegressor: только тесносвязанные (dense) колонки. Другие типы колонок должны быть обернуты в indicator_column или embedding_column.
  • tf.estimator.DNNLinearCombinedClassifier и tf.estimator.DNNLinearCombinedRegressor:
    • linear_feature_columns аргумент принимает любые типы колонок свойств.
    • dnn_feature_columns принимают только тесносвязанные колонки.

четверг, 11 октября 2018 г.

TensorFlow Estimators: сохранение и восстановление моделей

В этом посте мы рассмотрим как сохранять и восстанавливать TensorFlow модели, построенные с Estimators. TensorFlow предоставляет два формата моделей:

  • Контрольные точки - это формат, зависимый от кода, создающего модель.
  • SavedModel - формат, независимый от кода, создающего модель.

В этом посте мы будем использовать формат контрольных точек.

Код примера

Мы будем использовать пример классификации ирисов из предыдущего поста. Чтобы загрузить этот пример выполните следующее:

git clone https://github.com/tensorflow/models/
cd models/samples/core/get_started

Большинство отрывков кода в этом посте - это минимальные вариации premade_estimator.py.

Сохранение частично-тренированных моделей.

Estimators автоматически записывают следующее на диск:

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

Чтобы определить директорию верхнего уровня, в которую Estimator сохраняет эту информацию, назначим значение для опционального model_dir аргумента любого Estimator конструктора. Для DNNClassifier, например, следующий код устанавливает model_dir аргумент равным models/iris директории:

classifier = tf.estimator.DNNClassifier(
    feature_columns=my_feature_columns,
    hidden_units=[10, 10],
    n_classes=3,
    model_dir='models/iris')

Предположим был вызван train метод Estimator'а. Например:

classifier.train(
        input_fn=lambda:train_input_fn(train_x, train_y, batch_size=100),
                steps=200)

Как изображено на следующей диаграмме, первый вызов train добавляет контрольные точки и другие файлы в model_dir директорию:

Первый вызов train()


Чтобы увидеть объекты в созданной model_dir директории в системе UNIX семейства просто вызовете ls команду:

$ ls -1 models/iris
checkpoint
events.out.tfevents.timestamp.hostname
graph.pbtxt
model.ckpt-1.data-00000-of-00001
model.ckpt-1.index
model.ckpt-1.meta
model.ckpt-200.data-00000-of-00001
model.ckpt-200.index
model.ckpt-200.meta

Предыдущая ls команда показывает, что Estimator создал контрольные точки на шаге 1 (начало тренировки) и 200 (конец тренировки).

Директория контрольных точек по умолчанию

Если не определить model_dir в конструкторе Estimator'а, Estimator запишет файлы контрольной точки во временную директорию, выбранную Python tempfile.mkdtemp фукнцией. Например, следующий Estimator конструктор не определяет model_dir аргумент:

classifier = tf.estimator.DNNClassifier(
    feature_columns=my_feature_columns,
    hidden_units=[10, 10],
    n_classes=3)

print(classifier.model_dir)

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

/var/folders/0s/5q9kfzfj3gx2knj0vj8p68yc00dhcr/T/tmpYm1Rwa

Частота создания контрольных точек

По умолчанию, Estimator сохраняет контрольные точки в model_dir согласно следующему расписанию:

  • Записывает контрольную точку каждые 10 минут (600 секунд).
  • Записывает контрольную точку когда метод train начинается (первая итерация) и завершается (последняя итерация).
  • Оставляет только 5 наиболее недавних контрольных точек в директории.

Можно изменить расписание по умолчанию, выполнив следующие шаги:

  1. Создать tf.estimator.RunConfig объект, который определяет желаемое расписание.
  2. При создании инстанса Estimator передать этот RunConfig объект в качестве Estimator config аргумента.

Например, следующий код изменяет расписание создания контрольных точек - записывать каждые 20 минут и оставлять 10 последних контрольных точек:

my_checkpointing_config = tf.estimator.RunConfig(
    # Сохраняем контрольные точки каждые 20 минут
    save_checkpoints_secs = 20*60,  
    # Оставляем 10 последних контрольных точек
    keep_checkpoint_max = 10,       
)

classifier = tf.estimator.DNNClassifier(
    feature_columns=my_feature_columns,
    hidden_units=[10, 10],
    n_classes=3,
    model_dir='models/iris',
    config=my_checkpointing_config)

Восстановление модели

При первом вызове Estimator train метода, TensorFlow сохраняет контрольную точку в model_dir. Каждый последующий вызов Estimator train, evaluate, или predict метода вызывает следующее:

  1. Estimator строит граф модели, выполняя model_fn().
  2. Estimator инициализирует веса новой модели из данных сохраненных в наиболее недавней контрольной точке.

Другими словами, как показано на следующем графике, если контрольная точка существует TensorFlow перестраивает модель каждый раз когда вызывается train(), evaluate(), или predict().

Последовательные вызовы train(), evaluate(), или predict()


Как избежать ошибок при восстановлении.

Восстановление состояния модели из контрольной точки работает только если модель и контрольная точка совместимы. Например, возьмем тренировку DNNClassifier Estimator, содержащего два скрытых слоя, каждый имеющий 10 узлов:

classifier = tf.estimator.DNNClassifier(
    feature_columns=feature_columns,
    hidden_units=[10, 10],
    n_classes=3,
    model_dir='models/iris')

classifier.train(
    input_fn=lambda:train_input_fn(train_x, train_y, batch_size=100),
        steps=200)

После тренировки (и, следовательно, после создания контрольных точек в models/iris), представим, что было изменено количество нейронов в каждом скрытом слое с 10 на 20, и затем попытаемся восстановить модель:

classifier2 = tf.estimator.DNNClassifier(
    feature_columns=my_feature_columns,
    hidden_units=[20, 20],  # Изменяем количество нейронов в модели.
    n_classes=3,
    model_dir='models/iris')

classifier.train(
    input_fn=lambda:train_input_fn(train_x, train_y, batch_size=100),
        steps=200)

Ввиду того, что состояние в контрольной точки несовместимо с моделью, описанной в classifier2, повторная тренировка проваливается со следующей ошибкой:

...
InvalidArgumentError (see above for traceback): tensor_name =
dnn/hiddenlayer_1/bias/t_0/Adagrad; shape in shape_and_slice spec [10]
does not match the shape stored in checkpoint: [20]

Для выполнения экспериментов, в которых происходит тренировка и сравнение незначительно отличающихся версий модели, сохраняйте копии кода, каждая из которых создает свою model_dir, например, это возможно созданием отдельных git веток для каждой версии. Такое разделение сохранит контрольные точки восстанавливаемыми.

Резюме

Контрольные точки предоставляют простой автоматизированный метод для сохранения и восстановления моделей, созданных с помощью Estimators.

понедельник, 8 октября 2018 г.

TensorFlow Estimators: задача классификации Iris

В этом посте мы покажем решение классической задачи классификации Iris с помощью TensorFlow.

Предпосылки

Для использования примеров кода в этом руководстве необходимо следующее:

  • Установить TensorFlow
  • Если TensorFlow установлен вместе virtualenv или Anaconda, тогда необходимо активировать TensorFlow окружение.
  • Установить или обновить pandas, используя следующую команду:
  • pip install pandas
    

Получение кода из примеров

Для получения примеров кода в этом руководстве выполните следующее:

  1. Склонируйте TensorFlow Models репозиторий с GitHub следующей командой:

    git clone https://github.com/tensorflow/models
    

  2. Смените директорию в этой ветке на место, содержащее примеры:

    cd models/samples/core/get_started/
    

Программа, описанная в этом руководстве - это premade_estimator.py. Эта программа использует iris_data.py для получения тренировочных данных.

Выполнение программы

TensorFlow программы можно запускать как любые другие Python программы. Например:

python premade_estimator.py

Программа должна вывести тренировочные логи, затем прогнозы относительно тестового набора. Например, первая линия следующего вывода показывает, что модель оценила с 99.6% вероятностью, что первый пример в тестовом наборе - это вид Setosa. Ввиду того, что тестовый набор ожидал Setosa, этот прогноз верен.

...
Prediction is "Setosa" (99.6%), expected "Setosa"

Prediction is "Versicolor" (99.8%), expected "Versicolor"

Prediction is "Virginica" (97.9%), expected "Virginica"

Если программа выдала ошибку вместо ответов, необходимо проверить следующие пункты:

  • Правильно ли установлен TensorFlow?
  • Используется ли правильная версия TensorFlow? (использован 1.9)
  • Активировано ли окружение, в котором установлен TensorFlow? (если использовался virtualenv или Anaconda)

Программный стек

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

Рекомендуется писать TensorFlow программы со следующими API:

  • Estimators, которые представляют полностью готовые модели. Estimator API предоставляет методы для тренировки модели, оценки аккуратности модели и вывода прогнозов.
  • Datasets для Estimators, которые строят потоки вводных данных. Dataset API имеет методы для загрузки и манипулирования данными, и передачи их в модель. Dataset API тесно связано с Estimators API.

Классификация ирисов: обзор

Пример программы в этом руководстве создает и тестирует модель, которая классифицирует цветы Ириса на три разных вида, основываясь на размере их чашелистиков и лепестков.

Слева направо Iris setosa, Iris versicolor, Iris virginica.

Набор данных

Iris набор данных сожержит 4 свойства и одну метку. Четыре свойства идентифицируют ботанические характеристики цветов ириса:

  • длина чашелистика
  • ширина чашелистика
  • длина лепестка
  • ширина лепестка

Наша модель будет представлять эти свойства как нецельночисловые 32-битовые данные.

Метка идентифицирует вид Iris. один из следующих:

  • Iris setosa (0)
  • Iris versicolor (1)
  • Iris virginica (2)

Наша модель будет представлять метку как цельночисловые 32-битовые данные.

Следующая таблица показывает три примера в наборе данных:

Алгоритм

Программа тренирует модель классификатор глубокой нейронной сети (Deep Neural Network (DNN)), имеющую следующую топологию:

  • 2 скрытых слоя
  • каждый скрытый слой имеет 10 узлов (нод)

Следующий график иллюстрирует свойства, скрытые слои, прогнозы (не все узлы в скрытых слоях показаны):

Вывод

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

  • 0.03 для Iris Setosa
  • 0.95 для Iris Versicolor
  • 0.02 для Iris Virginica

Этот прогноз показывает, что с 95% вероятностью переданный пример является Iris Versicolor.

Обзор программирования с Estimators

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

Любой Estimator - это производный класс от tf.estimator.Estimator. TensorFlow предоставляет коллекцию tf.estimator (например, LinearRegressor) для реализации алгоритмов машинного обучения. Кроме того можно писать собственные кастомные Estimators. В первую очередь рекомендуется использовать предсозданные Estimators.

Для написания TensorFlow программы, основанной на предсозданных Estimators необходимо выполнить следующие шаги:

  • Создать одну или несколько функций ввода.
  • Определить колонки свойств модели.
  • Создать инстанс Estimator, задав колонки свойств и различные гиперпараметры.
  • Вызвать один или несколько методов на Estimator объекте, передав подходящую функцию ввода как источник данных.

Рассмотрим как эти шаги реализованы для Iris классификации.

Создание функций ввода

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

Функция ввода - это функция, которая возвращает tf.data.Dataset объект, который выводит следующий двухэлементный тапл (tuple):

  • features - это Python словарь в котором:
    • Каждый ключ - это имя свойства.
    • Каждое значение - это массив, содержащий все значения свойства.
  • label - это массив, содержащий значения метки для каждого примера.

Для демонстрации формата функции ввода, вот простой пример:

def input_evaluation_set():
    features = {'SepalLength': np.array([6.4, 5.0]),
                'SepalWidth':  np.array([2.8, 2.3]),
                'PetalLength': np.array([5.6, 3.3]),
                'PetalWidth':  np.array([2.2, 1.0])}
    labels = np.array([2, 1])
    return features, labels

Функция ввода может производить лист словаря свойств и метки любым предпочитаемым образом. Однако, рекомендуется использование TensorFlow Dataset API, которое может обрабатывать все виды данных. На высоком уровне Dataset API состоит из следующих классов:

Где отдельные участники это:

  • Dataset - основной класс, содержащий методы для создания и трансформирования датасетов. Также позволяет инициализировать датасет из данных в памяти, или из Python генератора.
  • TextLineDataset - читает строки из текстовых файлов.
  • TFRecordDataset - читает записи из TFRecord файлов.
  • FixedLengthRecordDataset - читает записи фиксированного размера из бинарных файлов.
  • Iterator - предоставляет путь для доступа к одному элементу набора данных за один раз.

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

Чтобы сохранить вещи простыми в этом примере загрузим данные с использованием pandas, и построим поток ввода из данных в памяти.

Вот функция ввода, использованная для тренировки в этой программе, которая доступна в iris_data.py:

def train_input_fn(features, labels, batch_size):
    """Функция ввода для тренировки"""
    # Преобразует вводы в Dataset.
    dataset = tf.data.Dataset.from_tensor_slices(
                                (dict(features), labels))

    # Перемешивание, повтор, и пакетизация примеров.
    return dataset.shuffle(1000).repeat().batch(batch_size)

Определение колонок свойств

Колонка свойства - это объект, описывающий как модель должна использовать сырые данные ввода из словаря свойств. При создании Estimator модели передается лист колонок свойств, который описывает каждое свойство, которое будет использовать модель. tf.feature_column модуль предоставляет много настроек для представления данных модели.

Для Iris примера, 4 сырых свойства - это числовые значения, поэтому строим лист колонок свойств информируя Estimator модель представлять каждое и четырех свойств как 32-битные нецельночисловые значения. Вот код для создание колонки свойств:

# Колонки свойств описывают как использовать ввод
my_feature_columns = []
for key in train_x.keys():
    my_feature_columns.append(tf.feature_column.numeric_column(key=key))

Колонки свойств могут быть намного более сложными, чем показано в примере выше.

Теперь, когда есть описание того как модель должна представлять сырые свойства, можно построить estimator.

Создание инстанса estimator

Iris задача - это классическая задача классификации. К счастью, TensorFlow предоставляет несколько предсозданных Estimators классификаторов, включая:

  • tf.estimator.DNNClassifier для глубоких моделей, которые выполняют мультиклассовую классификацию.
  • tf.estimator.DNNLinearCombinedClassifier для широких и глубоких моделей.
  • tf.estimator.LinearClassifier для классификаторов, основанных на линейных моделях.

Для Iris задачи, tf.estimator.DNNClassifier подходит лучше всего. Вот создание инстанса этого Estimator:

# Строим DNN с 2 скрытыми слоями и 10 узлами в каждом скрытом слое.
classifier = tf.estimator.DNNClassifier(
    feature_columns=my_feature_columns,
    # Два скрытых слоя из 10 узлов каждый.
    hidden_units=[10, 10],
    # Модель должна делать выбор из 3 классов.
    n_classes=3)

Тренировка, оценка, и прогнозирование.

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

  • Тренируют модель.
  • Оценивают тренированную модель.
  • Используют тренированную модель для выполнения прогнозов.

Тренировка модели.

Тренируем модель, вызывая тренировочный метод Estimator'а:

# Тренируем модель.
classifier.train(
    input_fn=lambda:iris_data.train_input_fn(
                            train_x, train_y, args.batch_size),
    steps=args.train_steps)

Здесь мы обернули input_fn вызов в лямбда для захвата аргументов, предоставляя функцию ввода, которая не принимает аргументов, как ожидает Estimator. Аргумент steps сообщает методу остановить тренировку после ряда тренировочных шагов.

Оценка тренированной модели.

Теперь модель натренирована и можно получить статистику ее производительности. Следующий код оценивает аккуратность тренированной модели на тестовых данных:

# Оцениваем модель.
eval_result = classifier.evaluate(
    input_fn=lambda:iris_data.eval_input_fn(
                                    test_x, test_y, args.batch_size))

print('\nTest set accuracy: {accuracy:0.3f}\n'.format(**eval_result))

В отличие от вызова метода тренировки, мы не передаем steps аргумент для оценки. eval_input_fn выводит только единственную эпоху данных.

Выполнение кода выше выводит следующий результат:

Test set accuracy: 0.967

eval_result словарь также содержит average_loss (среднюю потерю на пример), loss (среднюю потрею на мини-пакет) и значение global_step estimator'а (количество тренировочных итераций, которые были проведены).

Выполнение прогнозов (вывод) на тренированной модели.

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

# Генерируем прогнозы на модели 
expected = ['Setosa', 'Versicolor', 'Virginica']
predict_x = {
    'SepalLength': [5.1, 5.9, 6.9],
    'SepalWidth': [3.3, 3.0, 3.1],
    'PetalLength': [1.7, 4.2, 5.4],
    'PetalWidth': [0.5, 1.5, 2.1],
}

predictions = classifier.predict(
    input_fn=lambda:iris_data.eval_input_fn(predict_x,
                                            batch_size=args.batch_size))

predict метод возвращает python итерируемый объект, выводящий словарь результатов прогноза для каждого примера. Следующий код печатает несколько прогнозов и их вероятности:

template = ('\nPrediction is "{}" ({:.1f}%), expected "{}"')

for pred_dict, expec in zip(predictions, expected):
    class_id = pred_dict['class_ids'][0]
    probability = pred_dict['probabilities'][class_id]

    print(template.format(iris_data.SPECIES[class_id],
                          100 * probability, expec))

Выполнение предыдущего кода дает следующий вывод:

...
Prediction is "Setosa" (99.6%), expected "Setosa"

Prediction is "Versicolor" (99.8%), expected "Versicolor"

Prediction is "Virginica" (97.9%), expected "Virginica"

Резюме

Предсозданные Estimators - это эффективный путь для быстрого создания стандартных моделей.

среда, 3 октября 2018 г.

TensorFlow: введение в Estimators API

В этом посте мы кратко осветим tf.estimator (оценщик) - высокоуровневое TensorFlow API, которое значительно упрощает программирование машинного обучения. Estimators заключают в себе следующие действия:

  • тренировка
  • оценка
  • прогнозирование
  • экспорт для сохранения

Можно использовать предсозданные Estimators либо писать собственные кастомные Estimators. Все Estimators - предсозданные или кастомные - это классы, основанные на tf.estimator.Estimator классе.

Преимущества использования Estimators

Estimators предоставляют следующие преимущества:

  • Можно исполнять, основанные на Estimators, модели на локальном хосте или в распределенном мульти-серверном окружении без изменения модели. Более того, можно исполнять, основанные на Estimators, модели на CPU, GPU, или TPU без записи модели.
  • Estimators упрощают обмен реализациями между разработчиками моделей.
  • Можно разрабатывать модели, реализующие самые последние достижения, с высокоуровневым интуитивно понятным кодом. Вкратце, намного проще создавать модели с Estimators, чем с низкоуровневыми TensorFlow API.
  • Estimators сами постороены на tf.keras.layers, которые упрощают кастомизацию.
  • Estimators сами создают графы.
  • Estimators предоставляют безопасный распределенный тренировочный цикл, который контролирует как и когда:
    • постороить граф
    • инициализировать переменные
    • загружать данные
    • обрабатывать исключения
    • создавать файлы контрольных точек и восстанавливать при провалах
    • сохранять резюме для TensorBoard

При написании приложения с Estimators необходимо разделять пайплайн ввода данных от модели. Такое разделение упрощает эксперименты с различными наборами данных.

Предсозданные Estimators

Предсозданные Estimators позволяют работать на более высоком концептуальном уровне, чем основные TensorFlow API. Больше не нужно беспокоиться о создании вычислительного графа или сессий, поскольку Estimators делают все это сами. То есть предсозданный Estimator создает и управляет tf.Graph и tf.Session объектами. Более того предсозданные Estimators позволяют экспериментировать с различными архитектурами моделей, выполняя только минимальные изменения в коде. tf.estimator.DNNClassifier, например, - это предсозданный Estimator класс, который тренирует классификационные модели, основанные на тесносвязанных, опережающих нейронных сетях.

Структура программы предсозданных Estimators

TensorFlow программа основанная на предсозданном Estimator обычно состоит из четырех этапов:

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

    • словарь, в котором ключи - это имена свойств, а значения - это Tensors (или SparseTensors), содержащие соответствующие данные свойств
    • Tensor, содержащий одну или несколько меток

    Например, следующий код иллюстрирует основной скелет для функции ввода:

    def input_fn(dataset):
       # манипулирование датасетом, извлечение словаря свойств и метки
       ...  
       return feature_dict, label
    

  2. Определение колонок свойств. Каждый tf.feature_column идентифицирует имя свойства, его тип, и любую предобработку ввода. Например, следующий код создает три колонки свойств, которые хранят цельночисловые или нецельночисловые данные. Первые две колонки свойств просто идентифицируют имя свойства и тип. Третья колонка свойства также определяет лямбда программу, которая будет вызываться для масштабирования сырых данных:

    # Определяем три числовые колонки свойств.
    population = tf.feature_column.numeric_column('population')
    crime_rate = tf.feature_column.numeric_column('crime_rate')
    median_education = tf.feature_column.numeric_column(
                   'median_education',
                   normalizer_fn=lambda x: x - global_education_mean)
    

  3. Создание инстанса соответствующего предсозданного Estimator. Например, вот пример создания инстанса предсозданного Estimator, называющегося LinearClassifier:

    # Создаем инстанс оценщика (estimator), передавая колонки свойств
    estimator = tf.estimator.LinearClassifier(
        feature_columns=[population, crime_rate, median_education],
        )
    

  4. Вызов метода тренировки, оценки, или вывода (метод производственного использования). Например, все Estimators предоставляют тренировочный метод, который тренирует модель.

    # my_training_set - это функция, созданная на первом шаге
    estimator.train(input_fn=my_training_set, steps=2000)
    

Преимущества предсозданных Estimators

Предсозданные Estimators воплощают лучшие практики, предоставляя следующие преимущества:

  • Лучшие практики для определения того, где различные части вычислительного графа должны выполняться, воплощая исполнение на одной машине или в кластере
  • Лучшие практики для записи отдельных событий (резюме) и универсально полезных резюме

Если не использовать Estimators, то необходимо самому реализовывать все вышеперечисленное.

Кастомные Estimators

Сердце любого Estimator - предсозданного или кастомного - это функция модели, которая представляет собой метод, который строит граф для тренировки, оценки, и прогнозирования. При использовании предсозданного Estimator, задействуется уже реализованная функция модели. При использовании кастомного Estimator необходимо самому писать функцию модели.

Рекомендуемый рабочий поток

Рекомендуется использовать следующий рабочий поток:

  1. Предположим подходящий предсозданный Estimator существует - используем его для построения первой модели и используем ее результаты для определения базовой линии.
  2. Создаем и тестируем весь пайплайн, включая целостность и надежность данных с этим предсозданным Estimator.
  3. Если доступны подходящие альтернативные предсозданные Estimators, тогда выполняем эксперименты для определения, который предсозданный Estimator производит лучшие результаты.
  4. Возможно дальше улучшаем модель, строя собственный кастомный Estimator.

Создание Estimators из Keras моделей

Можно конвертировать существующие Keras модели в Estimators. Это позволяет Keras модели получить доступ к преимуществам Estimator, таким как распределенная тренировка. Вызываем tf.keras.estimator.model_to_estimator как в следующем примере:

# Создаем инстанс Keras inception v3 модели
keras_inception_v3 = \ 
  tf.keras.applications.inception_v3.InceptionV3(weights=None)

# Компилируем модель с оптимизатором, функцией потери, 
# и метриками, которые необходимы для тренировки
keras_inception_v3.compile(optimizer=tf.keras.optimizers.SGD(
                               lr=0.0001, momentum=0.9),
                          loss='categorical_crossentropy',
                          metric='accuracy')

# Создаем Estimator из компилированной Keras модели.
# Следует отметить, что состояние первоначальной keras модели
# сохраняется в созданном Estimator.
est_inception_v3 = tf.keras.estimator.model_to_estimator(
                              keras_model=keras_inception_v3)

# Используем произведенный Estimator как любой другой Estimator.
# Во-первых, восстанавливаем имя(имена) ввода Keras модели,
# так мы можем использовать их как имя(имена) колонок свойств
# фунции ввода Estimator:
keras_inception_v3.input_names  # выдает: ['input_1']

# Как только у нас есть имя(имена) ввода, 
# мы можем создать функцию ввода,
# например, для ввода в формате numpy ndarray:
train_input_fn = tf.estimator.inputs.numpy_input_fn(
    x={"input_1": train_data},
    y=train_labels,
    num_epochs=1,
    shuffle=False)

# Для тренировки вызываем функцию тренировки Estimator:
est_inception_v3.train(input_fn=train_input_fn, steps=2000)

Следует отметить, что имена колонок свойств и метки keras estimator приходят из соотвествующей скомпилированной keras модели. Например, ключи имен ввода для train_input_fn выше могут быть получены из keras_inception_v3.input_names, и аналогично, прогнозируемые выводные имена могут быть получены из keras_inception_v3.output_names.

Подробней о TensorFlow Estimators читайте в последующих постах.