От решения уравнений к глубокому обучению: учебник по TensorFlow Python
Опубликовано: 2022-03-11В последнее время в мире искусственного интеллекта произошли некоторые замечательные события: от получившего широкую огласку прогресса в создании беспилотных автомобилей до машин, которые теперь сочиняют подражания Шопену или просто действительно хороши в видеоиграх.
Центральное место в этих достижениях занимает ряд инструментов, помогающих создавать модели глубокого обучения и других моделей машинного обучения, среди которых Torch, Caffe и Theano. Однако с тех пор, как в ноябре 2015 года компания Google Brain выпустила собственный фреймворк TensorFlow с открытым исходным кодом, мы увидели, как популярность этой программной библиотеки резко возросла, став самой популярной фреймворком для глубокого обучения.
Почему это произошло? Причинами этого являются доступная поддержка и документация, готовность к работе, простота распределения вычислений по целому ряду устройств и отличный инструмент визуализации: TensorBoard.
В конечном счете, TensorFlow удается сочетать всеобъемлющий и гибкий набор технических функций с большой простотой использования.
В этой статье вы получите представление о механике этого инструмента, используя его для решения общей числовой задачи, совершенно выходящей за рамки того, что обычно включает в себя машинное обучение, прежде чем представить его использование в глубоком обучении с простой реализацией нейронной сети.
Прежде чем вы начнете
Предполагается базовое знание методов машинного обучения. Если вам нужно наверстать упущенное, ознакомьтесь с этим очень полезным постом.
Поскольку мы будем демонстрировать Python API, понимание Numpy также полезно.
Чтобы настроить TensorFlow, следуйте инструкциям, приведенным здесь.
Если вы используете Windows, следует отметить, что на момент написания вы должны использовать Python 3.4+, а не 2.7.
Затем, когда вы будете готовы, вы сможете импортировать библиотеку с помощью:
import tensorflow as tf
Шаг 1 из 2 к решению TensorFlow: создание графика
Построение программ TensorFlow обычно состоит из двух основных шагов, первый из которых — построение вычислительного графа, который будет описывать вычисления, которые вы хотите выполнить, но не выполнять их на самом деле или хранить какие-либо значения.
Как и в любом графе, у нас есть узлы и ребра. Ребра представляют тензоры, а тензор представляет n-мерный массив. Например, тензор с размерностью (или рангом в TensorFlow) 0 является скаляром, ранг 1 — вектор, ранг 2 — матрица и так далее.
Узлы представляют операции, которые создают выходной тензор, при необходимости используя тензоры в качестве входных данных. К таким операциям относятся сложения ( tf.add
), умножения матриц ( tf.matmul
), а также создание констант ( tf.constant
).
Итак, давайте объединим несколько из них для нашего первого графика.
a = tf.constant([2.5, 2]) b = tf.constant([3, 6], dtype=tf.float32) total = tf.add(a, b)
Здесь мы создали три операции, две из них для создания константных одномерных массивов.
Типы данных выводятся из переданного аргумента values, или вы можете указать их с помощью аргумента dtype
. Если бы я не сделал этого для b
, то было бы выведено значение int32
и возникла бы ошибка, поскольку tf.add
пытался определить дополнение для двух разных типов.
Шаг 2 из 2 к решению TensorFlow: выполнение операций
Граф определен, но для того, чтобы на нем (или на любой его части) действительно выполнять какие-либо вычисления, нам нужно настроить сеанс TensorFlow.
sess = tf.Session()
В качестве альтернативы, если мы запускаем сеанс в интерактивной оболочке, такой как IPython, мы используем:
sess = tf.InteractiveSession()
Метод run
объекта сеанса — это один из способов оценить Tensor.
Поэтому, чтобы оценить вычисление сложения, определенное выше, мы передаем total, тензор для извлечения, который представляет результат tf.add
.
print(sess.run(total)) # [ 5.5 8. ]
На этом этапе мы представляем класс Variable TensorFlow. В то время как константы являются фиксированной частью определения графа, переменные могут быть обновлены. Конструктору класса требуется начальное значение, но даже в этом случае переменным требуется операция для их явной инициализации до того, как над ними будут выполнены какие-либо другие операции.
Переменные хранят состояние графа в конкретном сеансе, поэтому мы должны наблюдать за тем, что происходит с несколькими сеансами, использующими один и тот же граф, чтобы лучше понять переменные.
# Create a variable with an initial value of 1 some_var = tf.Variable(1) # Create op to run variable initializers init_op = tf.global_variables_initializer() # Create an op to replace the value held by some_var to 3 assign_op = some_var.assign(3) # Set up two instances of a session sess1 = tf.Session() sess2 = tf.Session() # Initialize variables in both sessions sess1.run(init_op) sess2.run(init_op) print(sess1.run(some_var)) # Outputs 1 # Change some_var in session1 sess1.run(assign_op) print(sess1.run(some_var)) # Outputs 3 print(sess2.run(some_var)) # Outputs 1 # Close sessions sess1.close() sess2.close()
Мы настроили график и две сессии.
После выполнения инициализации в обоих сеансах (если мы не запустим это, а затем оценим переменную, мы столкнемся с ошибкой) мы выполняем операцию назначения только в одном сеансе. Как видно, значение переменной сохраняется, но не между сессиями.
Подача графика для решения числовых задач
Другой важной концепцией TensorFlow является заполнитель. В то время как переменные сохраняют состояние, заполнители используются для определения входных данных, которые может ожидать график, и их типа данных (и, возможно, их формы). Затем мы можем вводить данные в график через эти заполнители при выполнении вычислений.
Граф TensorFlow начинает напоминать нейронные сети, которые мы хотим в конечном итоге обучить, но перед этим давайте воспользуемся концепциями для решения общей числовой задачи из финансового мира.
Предположим, мы хотим найти y
в таком уравнении:
для данного v
(с постоянными C
и P
).
Это формула для расчета доходности к погашению ( y
) облигации с рыночной стоимостью v
, основной P
и купоном C
, выплачиваемыми раз в полгода, но с дисконтированием денежных потоков с непрерывным начислением сложных процентов.
По сути, нам нужно решить подобное уравнение методом проб и ошибок, и мы выберем метод деления пополам, чтобы обнулить наше окончательное значение для y
.
Во-первых, мы смоделируем эту проблему как граф TensorFlow.
C
и P
являются фиксированными константами и составляют часть определения нашего графа. Мы хотим иметь процесс, который уточняет нижнюю и верхнюю границы y
. Следовательно, эти границы (обозначенные a
и b
) являются хорошими кандидатами для переменных, которые необходимо изменять после каждого предположения y
(взятого за среднюю точку a
и b
).
# Specify the values our constant ops will output C = tf.constant(5.0) P = tf.constant(100.0) # We specify the initial values that our lower and upper bounds will be when initialised. # Obviously the ultimate success of this algorithm depends on decent start points a = tf.Variable(-10.0) b = tf.Variable(10.0) # We expect a floating number to be inserted into the graph v_target = tf.placeholder("float") # Remember the following operations are definitions, # none are carried out until an operation is evaluated in a session! y = (a+b)/2 v_guess = C*tf.exp(-0.5*y) + C*tf.exp(-y) + C*tf.exp(-1.5*y) + (C + P)*tf.exp(-2*y) # Operations to set temporary values (a_ and b_) intended to be the next values of a and b. # eg if the guess results in av greater than the target v, # we will set a_ to be the current value of y a_ = tf.where(v_guess > v_target, y, a) b_ = tf.where(v_guess < v_target, y, b) # The last stage of our graph is to assign the two temporary vals to our variables step = tf.group( a.assign(a_), b.assign(b_) )
Итак, теперь у нас есть список операций и переменных, любую из которых можно оценить для конкретного сеанса. Некоторые из этих операций полагаются на выполнение других операций, поэтому запуск, скажем, v_guess
вызовет цепную реакцию, чтобы другие тензоры, такие как C
и P
, были оценены в первую очередь.

Некоторые из этих операций полагаются на заполнитель, для которого необходимо указать значение, так как же мы на самом деле вводим это значение?
Это делается с помощью аргумента feed_dict
в самой функции run
.
Если мы хотим оценить a_
, мы подставляем значение для нашего заполнителя v_target
, например:
sess.run(a_, feed_dict={v_target: 100})
дает нам 0,0.
v_target
130 и получаем -10.0.
Это наша «шаговая» операция, которая на самом деле требует выполнения всех остальных операций в качестве предварительного условия и, по сути, выполняет весь граф. Это также операция, которая фактически изменяет фактическое состояние во время нашего сеанса. Следовательно, чем больше мы выполняем шаг, тем больше мы постепенно подталкиваем наши переменные a
и b
к фактическому значению y
.
Итак, предположим, что наше значение v
в нашем уравнении равно 95. Давайте настроим сеанс и выполним на нем наш график 100 раз.
# Set up a session and initialize variables sess = tf.Session() tf.global_variables_initializer().run() # Run the step operation (and therefore whole graph) 100 times for i in range (100): sess.run(step, feed_dict={v_target:95})
Если мы оценим тензор y
сейчас, мы получим что-то похожее на желаемый ответ
print(sess.run(y)) # 0.125163
Нейронные сети
Теперь, когда у нас есть понимание механики TensorFlow, мы можем объединить это с некоторыми дополнительными операциями машинного обучения, встроенными в TensorFlow, для обучения простой нейронной сети.
Здесь мы хотели бы классифицировать точки данных в двухмерной системе координат в зависимости от того, попадают ли они в определенную область — круг радиусом 0,5 с центром в начале координат.
Конечно, это можно конкретно проверить, просто проверив заданную точку (a,b)
если a^2 + b^2 < 0.5
, но для целей этого эксперимента по машинному обучению мы хотели бы вместо этого передать обучающий набор: серия случайных точек и то, попадают ли они в нашу предполагаемую область. Вот один из способов его создания:
import numpy as np NO_OF_RANDOM_POINTS = 100 CIRCLE_RADIUS = 0.5 random_spots = np.random.rand(NO_OF_RANDOM_POINTS, 2) * 2 - 1 is_inside_circle = (np.power(random_spots[:,0],2) + np.power(random_spots[:,1],2) < CIRCLE_RADIUS).astype(int)
Мы создадим нейронную сеть со следующими характеристиками:
- Он состоит из входного слоя с двумя узлами, в которые мы загружаем нашу серию двумерных векторов, содержащихся в «random_spots». Это будет представлено заполнителем, ожидающим данных обучения.
- Выходной слой также будет иметь два узла, поэтому нам нужно передать нашу серию обучающих меток («is_inside_circle») в заполнитель для скаляра, а затем преобразовать эти значения в двумерный вектор.
- У нас будет один скрытый слой, состоящий из трех узлов, поэтому нам нужно будет использовать переменные для наших матриц весов и векторов смещения, так как это значения, которые необходимо уточнять при выполнении обучения.
INPUT_LAYER_SIZE = 2 HIDDEN_LAYER_SIZE = 3 OUTPUT_LAYER_SIZE = 2 # Starting values for weights and biases are drawn randomly and uniformly from [-1, 1] # For example W1 is a matrix of shape 2x3 W1 = tf.Variable(tf.random_uniform([INPUT_LAYER_SIZE, HIDDEN_LAYER_SIZE], -1, 1)) b1 = tf.Variable(tf.random_uniform([HIDDEN_LAYER_SIZE], -1, 1)) W2 = tf.Variable(tf.random_uniform([HIDDEN_LAYER_SIZE, OUTPUT_LAYER_SIZE], -1, 1)) b2 = tf.Variable(tf.random_uniform([OUTPUT_LAYER_SIZE], -1, 1)) # Specifying that the placeholder X can expect a matrix of 2 columns (but any number of rows) # representing random spots X = tf.placeholder(tf.float32, [None, INPUT_LAYER_SIZE]) # Placeholder Y can expect integers representing whether corresponding point is in the circle # or not (no shape specified) Y = tf.placeholder(tf.uint8) # An op to convert to a one hot vector onehot_output = tf.one_hot(Y, OUTPUT_LAYER_SIZE)
Чтобы завершить определение нашего графа, мы определяем несколько операций, которые помогут нам обучить переменные для достижения лучшего классификатора. К ним относятся матричные вычисления, функции активации и оптимизатор.
LEARNING_RATE = 0.01 # Op to perform matrix calculation X*W1 + b1 hidden_layer = tf.add(tf.matmul(X, W1), b1) # Use sigmoid activation function on the outcome activated_hidden_layer = tf.sigmoid(hidden_layer) # Apply next weights and bias (W2, b2) to hidden layer and then apply softmax function # to get our output layer (each vector adding up to 1) output_layer = tf.nn.softmax(tf.add(tf.matmul(activated_hidden_layer, W2), b2)) # Calculate cross entropy for our loss function loss = -tf.reduce_sum(onehot_output * tf.log(output_layer)) # Use gradient descent optimizer at specified learning rate to minimize value given by loss tensor train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(loss)
Настроив наш граф, пришло время настроить сеанс и запустить «train_step» (который также запускает все необходимые операции). Некоторые из этих операций используют заполнители, поэтому для них необходимо указать значения. Этот шаг обучения представляет собой одну эпоху в нашем алгоритме обучения и, как таковой, зацикливается на количестве эпох, которые мы хотим запустить. Мы можем запустить другие части графика, такие как тензор «потерь», для информационных целей.
EPOCH_COUNT = 1000 sess = tf.Session() tf.global_variables_initializer().run() for i in range(EPOCH_COUNT): if i%100 == 0: print('Loss after %d runs: %f' % (i, sess.run(loss, feed_dict={X: random_spots, Y: is_inside_circle}))) sess.run(train_step, feed_dict={X: random_spots, Y: is_inside_circle}) print('Final loss after %d runs: %f' % (i, sess.run(loss, feed_dict={X: random_spots, Y: is_inside_circle})))
После того, как мы обучили алгоритм, мы можем передать точку и получить вывод нейронной сети следующим образом:
sess.run(output_layer, feed_dict={X: [[1, 1]]}) # Hopefully something close to [1, 0] sess.run(output_layer, feed_dict={X: [[0, 0]]}) # Hopefully something close to [0, 1]
Мы можем классифицировать точку как вне круга, если первый член выходного вектора больше 0,5, иначе внутри.
Запустив тензор output_layer
для многих точек, мы можем получить представление о том, как учащийся представляет себе область, содержащую положительно классифицированные точки. Стоит поиграть с размером тренировочного набора, скоростью обучения и другими параметрами, чтобы увидеть, насколько близко мы можем приблизиться к намеченному кругу.
Скорость обучения: 0,01
Эпохи: 1000
Скорость обучения: 0,01
Эпохи: 1000
Скорость обучения: 0,01
Эпохи: 10000
Скорость обучения: 0,001
Эпохи: 10000
Заворачивать
Это хороший урок, заключающийся в том, что увеличение тренировочного набора или количества эпох не является гарантией хорошего ученика — скорость обучения должна быть соответствующим образом скорректирована.
Надеюсь, эти демонстрации дали вам хорошее представление об основных принципах TensorFlow и обеспечили прочную основу для реализации более сложных методов.
Мы не рассмотрели такие понятия, как Tensorboard или обучение наших моделей на графических процессорах, но они хорошо описаны в документации TensorFlow. В документации можно найти ряд рецептов, которые помогут вам быстрее справиться с увлекательными задачами глубокого обучения с использованием этой мощной среды!