Разумеется, можно вычислять не только константы. Рассмотрим пример использования переменных.
import tensorflow as tf
k = tf.constant(2)
a = tf.placeholder(tf.int32)
b = tf.placeholder(tf.int32)
m = tf.multiply(k, a)
task = tf.add(m, b)
session = tf.Session()
print(session.run(task, feed_dict={a: 2, b: 3}))
print(session.run(task, feed_dict={a: [10,11], b: [3,4]}))
session.close()
Здесь мы задаем граф вычислений по формуле a*k + b, где a и b - это переменные, для указания этого используется специальный тип placeholder. Теперь, при вычислении командой session.run мы можем передать различные параметры, например a=2 и b=3:
print(session.run(task, feed_dict={a: 2, b: 3}))
>> 7
Можно даже передать массивы в качестве параметра:
print(session.run(task, feed_dict={a: [10,11], b: [3,4]}))
> [23, 26]
Библиотека TensorFlow поддерживает множество различных операций, например, вычисления с матрицами, ну и разумеется, большое количество функций предназначено специально для обработки данных и машинного обучения. Вернемся к предыдущей главе, и перепишем нашу нейронную сеть, распознающую текст. За основу исходного кода был взят готовый пример из https://github.com/aymericdamien/TensorFlow-Examples/tree/master/examples желающие могут изучить приведенные там примеры более подробно.
Код целиком приведен ниже:
import tensorflow as tf
import _pickle as cPickle
# Prepare training data
training_epochs = 500
learning_rate = 0.001
inputs = []
targets = []
with open("digitsMnist.txt", 'rb') as fp:
digits = cPickle.load(fp)
print("Digits loaded:", len(digits))
inputs = list(map(lambda x: x["data"], digits))
def arrayFromDigit(data):
result = data["result"]
# Convert value to array: "3" => [0,0,0,1,0,0,0,0,0,0]
result_flat = [0.0]*10
result_flat[result] = 1.0
return result_flat
targets = list(map(arrayFromDigit, digits))
# Network Parameters
n_hidden_1 = 48 # 1st layer number of neurons
n_input = 784 # MNIST data input (img shape: 28*28)
n_classes = 10 # MNIST total classes (0-9 digits)
model_path = "model_mlp.ckpt"
# tf Graph input
X = tf.placeholder("float", [None, n_input])
Y = tf.placeholder("float", [None, n_classes])
# Store layers weight & bias
weights = {
'h1': tf.Variable(tf.random_normal([n_input, n_hidden_1])),
'out': tf.Variable(tf.random_normal([n_hidden_1, n_classes]))
}
biases = {
'b1': tf.Variable(tf.random_normal([n_hidden_1])),
'out': tf.Variable(tf.random_normal([n_classes]))
}
# Create model
def multilayer_perceptron(x):
# Hidden fully connected layer with 256 neurons
layer_1 = tf.add(tf.matmul(x, weights['h1']), biases['b1'])
# Output fully connected layer with a neuron for each class
out_layer = tf.matmul(layer_1, weights['out']) + biases['out']
return out_layer
# Construct model
logits = multilayer_perceptron(X)
# Define loss and optimizer
loss_op = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits, labels=Y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
train_op = optimizer.minimize(loss_op)
# Initializing the variables
init = tf.global_variables_initializer()
# Save/restore model
saver = tf.train.Saver()
session = tf.Session()
session.run(init)
# Training cycle
for p in range(training_epochs):
_, c = session.run([train_op,loss_op], feed_dict={X:inputs, Y:targets})
if p % 50 == 0:
print("Step {} of {}".format(p, training_epochs))
print("Training Finished!")
# Save model (optional)
# save_path = saver.save(sess, './' + model_path)
# print("Model saved in file: %s" % save_path)
# Restore model weights from previously saved model (optional)
# saver.restore(sess, model_path)
# print("Model restored from file: %s" % save_path)
# Use model
pred = tf.nn.softmax(logits) # Apply softmax to logits
print("")
t = [ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,
0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,
0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,
0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ]
res = session.run(pred, feed_dict={X: [t]})
print("Result:", res[0])
session.close()
Как можно видеть, суть практически не отличается от описанного в главе 19. Мы создаем модель в функции multilayer_perceptron, состоящую из входных данных x, скрытого слоя layer_1 и выходного слоя out_layer. Мы также имеем массивы weights и biases. Далее, весь громоздкий код тренировки сети, который был написан в главе 19, здесь не нужен - он уже реализован в TensorFlow, нам достаточно вызвать функцию optimizer.minimize в цикле тренировки.
Опционально, готовую модель можно сохранить и загрузить, чтобы не тренировать ее каждый раз, этот код закомментирован. И наконец, мы берем цифру “3” из предыдущего примера, и запускаем уже готовую сеть для распознавания.
Результаты работы программы показаны на рисунке:

Обучение сети заняло … 20 секунд вместо 2х часов - что показывает кардинальный прирост в быстродействии по сравнению с неоптимизированным кодом на Python, выходные данные соответствуют “нарисованной” цифре “3”.
22. Фракталы
Фракталы - интересные математические объекты, которые долгое время не замечались математиками. В 1872 году, объекты такого типа впервые описал немецкий математик Карл Вейерштрасс, “придумавший” функцию следующего вида:
Это непрерывная функция, но при этом не являющаяся гладкой, для нее невозможно вычислить производную ни в одной из точек.
В Википедии можно найти ее график:

Во времена Вейерштрасса математики назвали такую функцию “оскорблением здравого смысла”, практической применимости не мог найти никто. Лишь через 100 лет, в историческом плане совсем недавно, в 1975 году, математик Мандельброт ввел термин “фрактал”. Фракталы стали широко известны после выхода в 1977 году его книги “Фрактальная геометрия природы” - как оказалось, такие структуры распространены в природе гораздо чаще, чем это можно представить. От ветвей деревьев или рисунка береговой линии, до сосудов человека или колебаний курса акций - все эти процессы имеют природу, схожую с фрактальной.
Основное свойство фракталов - самоподобие, увеличенный фрагмент повторяется большое (в идеале бесконечное) число раз. Простейший пример, знакомый каждому - ветви дерева. Возьмем линию, которая через определенное расстояние делится надвое, для каждой из половинок повторим вышеуказанное правило. Получим знакомый рисунок:

Другим примером служит так называемый “парадокс береговой линии”. При измерении длины государственной границы между Испанией и Португалией, выяснилось, что по измерениям из Португалии, длина границы составила 987км, а по измерениям с Испанской стороны - 1214км. Т.к. противоречие было налицо, им заинтересовался английский математик Льюис Ричардсон, который выяснил, что все дело в масштабе - чем меньше масштаб, тем больше получается длина. Поразительным оказалось то, что при теоретически бесконечном масштабе, длина линии также будет бесконечной!
Впрочем, фигуры такого типа были известны и раньше (хотя их рассматривали скорее как математический курьез), например описанная в 1904 году кривая Коха. Построить ее весьма просто: возьмем линию, разделим ее в центре равносторонним треугольником, для каждой новой линии повторим процесс:
Эта фигура теоретически также имеет конечную площадь, но при этом бесконечную длину края. Также как и функция Вейерштрасса, кривая Коха нигде не дифференцируема. Также очевидно, что при увеличении любого фрагмента кривой Коха, он не будет отличаться от “оригинала”, деление будет продолжаться и дальше.
Если замкнуть 3 такие линии, получится так называемая “снежинка Коха”. Нарисовать ее несложно на языке Python, благодаря встроенной библиотеке рисования Turtle. Она позволяет управляя виртуальной “черепашкой”, рисовать примитивы простыми командами, типа “повернуть на угол” или “сдвинуться на расстояние”.
Для рисования линии Коха, нам потребуются всего 3 команды: сдвинуться вперед, повернуть на 60 градусов, и повернуть на -60 градусов. Повторив процедуру 3 раза, мы получим “снежинку Коха”.
Код на языке Python выглядит так:
import turtle
def koch(size, order):
if order > 0:
koch(size/3, order - 1)
turtle.left(60)
koch(size/3, order - 1)
turtle.right(120)
koch(size/3, order - 1)
turtle.left(60)
koch(size/3, order - 1)
else:
turtle.forward(size)
turtle.ht()
turtle.speed(20)
for i in range(3):
koch(size=400, order=4)
turtle.right(120)
turtle.exitonclick()
В результате работы программы мы получаем следующую картинку:

Можно увеличив количество итераций и размер картинки, убедиться что шаблон повторяется.
Может возникнуть вопрос, как длина линии может быть бесконечной при конечной длине. Примерной аналогией может быть сумма ряда:
S = 0.9 + 0.09 + 0.009 + …
Как нетрудно догадаться, сам по себе ряд бесконечный, но его сумма стремится ко вполне определенному числу 0.999999… = 1.
“Снежинка Коха” интересна своей простотой, но есть и более сложные варианты фракталов. Рассмотрим множество Мандельброта, которое строится по следующему алгоритму: для каждой точки c=x + j*y на комплексной плоскости, эта точка попадает в множество при условии что ряд Zn+1 = Zn2 + c не расходится при z0 = 0.
Рассмотрим пример.
При c = (0,0) = 0 + 0j, ряд очевидно, всегда равен нулю, т.е. не расходится, значит точка (0,0) попадает в множество.
При с = (0.5, 0.5) = 0.5 + 0.5j, получаем следующие значения:
Z0 = 0
Z1 = Z0 + c = 0.5+0.5j
Z2 = Z1 + c = 0.5+1j
Z3 = Z2 + c = -0.25+1.5j
Z4 = Z3 + c = -1.6875-0.25j
Z5 = Z4 + c = 3.28515625+1.34375j
Z6 = Z5 + c = 9.48658752+9.32885j