[Перевод] FizzBuzz на TensorFlow

интервьюер: Приветствую, хотите кофе или что-нибудь еще? Нужен перерыв?

я: Нет, кажется я уже выпил достаточно кофе!

интервьюер: Отлично, отлично. Как вы относитесь к написанию кода на доске?

я: Я только так код и пишу!

интервьюер: …

я: Это была шутка.

интервьюер: OK, итак, вам знакома задача «fizz buzz»?

я: …

интервьюер: Это было да или нет?

я: Это что-то вроде «Не могу поверить, что вы меня об этом спрашиваете.»

интервьюер: OK, значит, нужно напечатать числа от 1 до 100, только если число делится нацело на 3, напечатать слово «fizz», если на 5 — «buzz», а если делится на 15, то — «fizzbuzz».

я: Я знаю эту задачу.

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

я: …

интервьюер: Вот маркет и губка.

я: [задумался на пару минут]

интервьюер: Вам нужна помощь, чтобы начать?

я: Нет, нет, все в порядке. Итак, начнем с пары стандартных импортов:

import numpy as np
import tensorflow as tf

интервьюер: Эм, вы же правильно поняли проблему в fizzbuzz, верно?

я: Так точно. Давайте обсудим модели. Я думаю тут подойдет простой многослойный перцептрон с одним скрытым слоем.

интервьюер: Перцептрон?

я: Или нейронная сеть, как вам угодно будет называть. Мы ходим чтобы на вход приходило число, а на выходе была корректное «fizzbuzz» представление этого числа. В частности, мы хотим превратить каждый вход в вектор «активаций». Одним из простых способов может быть конвертирование в двоичный вид.

интервьюер: Двоичный вид?

я: Да, ну, знаете, единицы и нули? Что-то вроде:

def binary_encode(i, num_digits):
    return np.array([i >> d & 1 for d in range(num_digits)])

интервьюер: [смотрит на доску с минуту]

я: И нашим выходом будет унитарное кодирование fizzbuzz представления числа, где первая позиция означает «напечатать как есть», второе означает «fizz» и так далее.

def fizz_buzz_encode(i):
    if   i % 15 == 0: return np.array([0, 0, 0, 1])
    elif i % 5  == 0: return np.array([0, 0, 1, 0])
    elif i % 3  == 0: return np.array([0, 1, 0, 0])
    else:             return np.array([1, 0, 0, 0])

интервьюер: OK, этого, кажется, достаточно.

я: Да, вы правы, этого достаточно для настройки. Теперь нам нужно сгенерировать данные для тренировки сети. Это будет нечестно использовать числа от 1 до 100 для тренировки, поэтому давайте натренируем на всех оставшихся числах вплоть до 1024:

NUM_DIGITS = 10
trX = np.array([binary_encode(i, NUM_DIGITS) for i in range(101, 2 ** NUM_DIGITS)])
trY = np.array([fizz_buzz_encode(i)          for i in range(101, 2 ** NUM_DIGITS)])

интервьюер: …

я: Теперь нужно нашу модель нужно адаптировать для tensorflow. Сходу я не сильно уверен сколько скрытых юнитов использовать, может 10?

интервьюер: …

я: Да, пожалуй 100 будет лучше. Мы всегда можем изменить это позже:

NUM_HIDDEN = 100

Нам понадобится входная переменная шириной в NUM_DIGITS, и выходная переменная с шириной в 4:
We’ll need an input variable with width NUM_DIGITS, and an output variable with width 4:

X = tf.placeholder("float", [None, NUM_DIGITS])
Y = tf.placeholder("float", [None, 4])

интервьюер: Как далеко вы планируете зайти с этим?

я: Ах, всего два слоя — один скрытый слой и один слой для вывода. Давайте используем случайно-инициализированные веса для наших нейронов:

def init_weights(shape):
    return tf.Variable(tf.random_normal(shape, stddev=0.01))

w_h = init_weights([NUM_DIGITS, NUM_HIDDEN])
w_o = init_weights([NUM_HIDDEN, 4])

И мы готовы определить нашу модель. Как я сказал ранее, один скрытый слой, и, давайте используем, ну, не знаю, ReLU активацию:

def model(X, w_h, w_o):
    h = tf.nn.relu(tf.matmul(X, w_h))
    return tf.matmul(h, w_o)

Мы можем использоваться softmax кросс-энтропию как нашу функцию стоимости и попробовать минимизировать ее:
We can use softmax cross-entropy as our cost function and try to minimize it:

py_x = model(X, w_h, w_o)

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(py_x, Y))
train_op = tf.train.GradientDescentOptimizer(0.05).minimize(cost)

интервьюер: …

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

predict_op = tf.argmax(py_x, 1)

интервьюер: Пока вы не заблудились, проблема, которую вы должны были решить это генерация fizz buzz для чисел от 1 до 100.

я: Ох, отличное замечание, predict_op функция вернет число от 0 до 3, но мы же хотим «fizz buzz» вывод:

def fizz_buzz(i, prediction):
    return [str(i), "fizz", "buzz", "fizzbuzz"][prediction]

интервьюер: …

я: Теперь мы готовы натренировать модель. Поднимем tensorflow сессию и проинициализируем переменные:

with tf.Session() as sess:
    tf.initialize_all_variables().run()

Запустим, скажем, 1000 эпох тренировки?

интервьюер: …

я: Да, наверное этого будет мало — пусть будет 10000, чтобы наверняка.

Ещё, наши данные для тренировки последовательны, что мне не нравится, так что давайте размешаем их на каждой итерации:

for epoch in range(10000):
    p = np.random.permutation(range(len(trX)))
    trX, trY = trX[p], trY[p]

И, каждая эпоха будет тренировать в пачках по, я не знаю, ну пусть 128 входов.

BATCH_SIZE = 128

В итоге, каждый проход будет выглядеть так:

    for start in range(0, len(trX), BATCH_SIZE):
        end = start + BATCH_SIZE
        sess.run(train_op, feed_dict={X: trX[start:end], Y: trY[start:end]})

и потом мы можем вывести погрешность тренировочных данных, ведь почему бы и нет?

    print(epoch, np.mean(np.argmax(trY, axis=1) ==
                         sess.run(predict_op, feed_dict={X: trX, Y: trY})))

интервьюер: Вы серьёзно?

я: Да, мне кажется это очень полезно видеть, как прогрессирует точность.

интервьюер: …

я: Итак, после того, как модель натренирована, время fizz buzz. Наш вход будет всего лишь двоичное кодирование числе от 1 до 100:

numbers = np.arange(1, 101)
teX = np.transpose(binary_encode(numbers, NUM_DIGITS))

И затем, наше вывод это просто fizz_buzz функция, применённая к выходной модели:
And then our output is just our fizz_buzz function applied to the model output:

teY = sess.run(predict_op, feed_dict={X: teX})
output = np.vectorize(fizz_buzz)(numbers, teY)

print(output)

интервьюер: …

я: И это будет ваш fizz buzz!

интервьюер: Этого достаточно, правда. Мы с вами свяжемся.

я: Свяжемся, звучит многообещающе.

интервьюер: …

Я не получил эту работу. Но я попробовал на самом деле запустить этот код (код на Github), и, оказалось, что он даёт несколько неправильный вывод! Большое спасибо, машинное обучение!

In [185]: output
Out[185]:
array(['1', '2', 'fizz', '4', 'buzz', 'fizz', '7', '8', 'fizz', 'buzz',
       '11', 'fizz', '13', '14', 'fizzbuzz', '16', '17', 'fizz', '19',
       'buzz', '21', '22', '23', 'fizz', 'buzz', '26', 'fizz', '28', '29',
       'fizzbuzz', '31', 'fizz', 'fizz', '34', 'buzz', 'fizz', '37', '38',
       'fizz', 'buzz', '41', '42', '43', '44', 'fizzbuzz', '46', '47',
       'fizz', '49', 'buzz', 'fizz', '52', 'fizz', 'fizz', 'buzz', '56',
       'fizz', '58', '59', 'fizzbuzz', '61', '62', 'fizz', '64', 'buzz',
       'fizz', '67', '68', '69', 'buzz', '71', 'fizz', '73', '74',
       'fizzbuzz', '76', '77', 'fizz', '79', 'buzz', '81', '82', '83',
       '84', 'buzz', '86', '87', '88', '89', 'fizzbuzz', '91', '92', '93',
       '94', 'buzz', 'fizz', '97', '98', 'fizz', 'fizz'],
      dtype='

Наверное, нужно взять более глубокую нейронную сеть.

© Habrahabr.ru