FizzBuzz на TensorFlow

інтерв'юер: Вітаю, хочете кави або що-небудь ще? Потрібна перерва?
я: Ні, здається я вже випив досить кави!
інтерв'юер: Відмінно, відмінно. Як ви ставитеся до написання коду на дошці?
я: Я тільки так код і пишу!
інтерв'юер: ...
я: Це був жарт.
інтерв'юер: OK, отже, вам знайома завдання "fizz buzz"?
я: ...
інтерв'юер: Це було так чи ні?
я: Це щось на кшталт "Не можу повірити, що ви мене про це питаєте."
інтерв'юер: ДОБРЕ, значить, потрібно надрукувати числа від 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 width NUM_DIGITS, and an output variable 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 the epoch in range(10000):
p = np.random.перестановка(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(the 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='<U8')

Напевно, потрібно взяти більш глибоку нейронну мережу.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.