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})
Однако теперь мы уже используем тестовые, а не тренировочные данные.
Вот и все. Это было совсем несложно.