TensorFlow и логистическая регрессия

После непродолжительной, но весьма кровавой войны мне все-таки удалось откомпилировать и собрать TensorFlow для GPU с CUDA capability=3.0. Теперь можно погрузиться в него основательно, потому что машинное обучение с GPU — это быстро, легко и приятно, а без GPU — порой лишь огромная потеря времени.

Попробуем запрограммировать самую простейшую логистическую регрессию.
Начнем незамысловато — с импорта tensorflow:

import tensorflow as tf


Далее загружаем данные:

train_X, train_Y, test_X, test_Y = load_data()


Функцию load_data, которая загружает ваши данные, вам необходимо будет реализовать самостоятельно.

Структура данных предельно проста:
— train_X и test_X — это numpy-массивы размерностью NxF, где N — количество анализируемых инстансов (проще говоря, точек данных), а F — количество атрибутов у каждого инстанса.
— train_Y и test_Y — это тоже numpy-массивы, но уже размерностью NxC, где С — количество классов (в самом простом случае, 2)
Таким образом, если train_Y[i] содержит вектор [1 0], это означает, что i-ый инстанс относится к классу 0 (поскольку в нулевом элементе вектора стоит 1).

Для удобства запомним важные размерности:

num_features = train_X.shape[1]
num_classes = train_Y.shape[1]


Теперь определим символьные переменные для будущих вычислений:

X = tf.placeholder("float", [None, num_features])
Y = tf.placeholder("float",[None, num_classes])


Здесь мы создаем символьную переменную X типа «float» размерностью «хоть сколько» на num_features. Количество точек данных никак не влияет на суть вычислений, поэтому можно их не фиксировать в модели. Зато потом мы сможем запускать вычисления на одной и той же модели как со 100 точками, так и с 10 миллионами.

C Y совершенно аналогичная ситуация.

Еще нужно создать переменные для хранения параметров модели:

W = tf.Variable(tf.zeros([num_features,num_classes]))
B = tf.Variable(tf.zeros([num_classes]))


А теперь описываем модель:

pY = tf.nn.softmax(tf.matmul(X, W) + B)


В данном случае мы используем наипростейшую линейную модель вида y=Wx+B, завернутую в softmax. В pY будет храниться результат расчета, то есть предсказанный Y, поэтому он также будет иметь размерность NxC.

Далее определяем функцию потерь:

cost_fn = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(pY, Y))


Можно, конечно, функцию потерь и более явно описать:

cost_fn = -tf.reduce_sum(Y * tf.log(pY))


После чего создаем оптимизатор:

opt = tf.train.AdamOptimizer(0.01).minimize(cost_fn)


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

opt = tf.train.GradientDescentOptimizer(0.01).minimize(cost_fn)


До сих пор мы только описывали модель и ее параметры, но никакие вычисления пока не проводились. И вот, наконец-то, мы переходим к расчетам. Сначала создаем сессию и инициализируем все переменные.

sess = tf.Session()
init = tf.initialize_all_variables()
sess.run(init)


А теперь поехали:

num_epochs = 40
for i in range(num_epochs):
  sess.run(opt, feed_dict={X:train_X, Y:train_Y})


Здесь просто происходит вызов ранее описанного оптимизатора, которому передаются данные: символьная переменная X получает данные из train_X, а Y — из train_Y. Таким образом, в модели больше нет никакой неопределенности и все вычисления выполнимы.

В данном случае, мы проводим 40 итераций, в каждой из которых обсчитываются все имеющиеся тренировочные данные.
Если данных много, то можно разделить их на блоки (batch’и) и учить последовательно, то есть каждая эпоха будет включать цикл по всем блокам.

Для оценки качества модели надо ввести критерий оценки:

accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(pY,1), tf.argmax(Y,1)), "float"))


Здесь сравнивается реальный класс и предсказанный класс, после чего результат сравнения преобразуется из типа boolean в тип float (true — 1, false — 0), а затем по всем этим сравнениям берется среднее.
Обратите внимание, что предыдущая строка только определяет критерий, а не вычисляет его. Вышеописанная формула, также как и оптимизатор, зависят от символьных переменных, которые надо наполнить данными. Поэтому для выполнения расчета снова запускаем run:

accuracy_value = sess.run(accuracy, feed_dict={X:test_X, Y:test_Y})


Однако теперь мы уже используем тестовые, а не тренировочные данные.

Вот и все. Это было совсем несложно.

© Habrahabr.ru