Основы Theano и Tensorflow. Обзор

Основы Theano (обзор)

Здравствуйте и вновь добро пожаловать на занятия по теме «Обработка данных: глубокое обучение на языке Python, часть 2».

В этой лекции мы рассмотрим основы библиотеки Theano и Tensorflow. Мы рассмотрим Theano достаточно подробно, чтобы вы без особых проблем смогли создать собственную нейронную сеть, построим нейронную сеть с использованием Theano и проверим её на наших данных.

Если вы хотите получить используемый в этой лекции код, перейдите по адресу https://github.com/lazyprogrammer/machine_learning_examples, найдите папку ann_class2 и в ней файл theano1.py.

Будем работать построчно в Iphyton, чтобы вы могли видеть, что происходит, так как переход от обычного Python к Numpy или Pandas – это дело, требующее меньше усилий, чем переход от Numpy к Theano, ведь множество новых понятий и вещей будут работать не так, как можно было бы ожидать в обычном Python. Я не буду показывать, как установить Theano, поскольку это весьма несложно – просто зайдите на сайт и следуйте инструкциям. До сих пор я ни разу не слышал, чтобы у кого-то при этом возникали проблемы.

После этого первое, о чём мы поговорим, – это переменные Theano. Поскольку мы настолько часто будем использовать тензоры Theano, то будем обозначать библиотеку Theano через большое Т:

import theano.tensor as T

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

c = T.scalar(‘c’)

То же самое мы можем сделать для вектора:

v = T.vector(‘v’)

И для матрицы:

A = T.matrix(‘A’)

В Theano есть и тензоры с размерностью три. Вы можете столкнуться с ними, если будете работать с не сглаженными изображениями вроде тех, которые мы уже использовали. В таком случае у нас будет вектор длиной в 784 элемента, который должен быть переформатирован в размерность 28х28, чтобы иметь возможность просматривать изображения. Если вы хотите сохранить изображения в виде квадратов и у вас есть N изображений, то получится размерность Nx28x28, что и является трёхмерным тензором. Если же у вас есть три цветовых канала, например красный, зелёный и синий, то получится тензор с размерностью Nx3x28x28, являющийся четырёхмерным тензором. В этом курсе мы не будем работать с тензорами, поэтому это всё, что я хотел о них рассказать.

Обратите внимание, что все созданные мною переменные не имеют текущих значений. Все созданные переменные являются лишь символами, но мы можем проводить над ними алгебраические операции. В качестве примера давайте проведём операцию скалярного произведения между A и v:

w = A.dot(v)

На самом деле никакой операции умножения мы не провели, поскольку она невозможна ввиду того, что ни A, ни v до сих пор не имеют никаких значений. Как же установить значения для A и v и найти результат w? Вот тут-то и вступают в игру функции Theano. Импортируем высокоуровневый модуль Theano, используя который, мы можем создавать функции Theano:

import theano

Каждое создание функции определяет входные и исходящие величины:

matrix_times_vector = theano.function(inputs=[A,v], outputs=w)

Импортируем теперь библиотеку Numpy, чтобы создать действительные массивы и вызвать функцию:

import numpy as np

A_val = np.array([[1,2], [3,4]])

v_val = np.array([5,6])

А вот так мы вызываем функцию и получаем ожидаемый результат:

w_val = matrix_times_vector(A_val, v_val)

print w_val

И получаем ожидаемый результат. Пока что ничего особенного, не так ли?

Одним из наибольших преимуществ Theano является то обстоятельство, что эта библиотека связывает все переменные в граф, который мы можем использовать для вычисления градиента, применяя цепное правило, обсуждавшееся в первом курсе.

В Theano обычные переменные являются необновляемыми. Для создания обновляемой переменной нам необходимо создать то, что называется общей переменной (shared variable). Так и сделаем.

x = theano.shared(20.0, ‘x’)

Здесь первым параметром является первоначальное значение переменной, а вторым – её имя.

Давайте также создадим простенькую функцию затрат, решение для которой мы можем найти самостоятельно и о которой известно, что она имеет глобальный минимум:

cost = x*x + x +1

Теперь давайте объясним Theano, как мы хотим обновить значение x, предложив выражение для обновления:

x_update = x – 0.3*T.grad(cost, x)

Что замечательно в Theano, так это то, что она автоматически вычисляет градиент. Функция grad имеет два параметра: первый – это функция, чей градиент мы хотим найти, а второй – переменная, относительно которой мы ищем градиент. При этом в качестве второго параметра вы можете подставить несколько переменных в виде списка.

Теперь давайте создадим простенькую обучающую функцию, похожую на предыдущую, но с тем исключением, что добавим в неё новый аргумент updates. Аргумент updates содержит список кортежей; каждый из кортежей включает два элемента: первый – обновляемая общая переменная, а второй – используемое для обновления выражение.

train = theano.function(inputs=□, outputs=cost, updates=[(x, x_update)])

Таким образом, в функции нет входных величин, и позже вы увидите, почему.

Итак, мы создали функцию для обучения, но на самом деле ещё не вызвали. Обратите внимание, что x является не входной величиной, а величиной, которая будет обновляться. В дальнейших примерах входящими величинами будут данные и метки. Таким образом, параметр inputs использует данные и метки, а параметр updates использует параметры модели с их обновлениями.

Теперь нам необходимо написать цикл для вызова обучающей функции:

for i in xrange(25):

    cost_val = train()

    print cost_val

Всё сошлось довольно быстро. Обратите внимание, что мы пришли к ожидаемому значению функции затрат и можем вывести на экран оптимальное значение x, использовав функцию get_value:

x.get_value()

Это значение и ожидалось.

Нейронная сеть в Theano, код (обзор)

Давайте создадим нейронную сеть в Theano, используя основы, которые мы изучили.

Если вы не хотите писать код самостоятельно, а лишь просмотреть его, перейдите по адресу https://github.com/lazyprogrammer/machine_learning_examples и найдите папку ann_class2. Соответствующий файл называется theano2.py.

Итак, первым делом мы импортируем все обычные нужные нам библиотеки, включая на этот раз и Theano, а также Matplotlib, чтобы иметь возможность построить график логарифма функции правдоподобия. Кроме того, из файла util.py импортируем функции get_normalized_data и y2indicator.

import numpy as np

import theano

import theano.tensor as T

import matplotlib.pyplot as plt

from util import get_normalized_data, y2indicator

Далее мы определим функцию error_rate, просто возвращающую среднее значение случаев, когда p ≠ t.

def error_rate(p, t):

    return np.mean(p != t)

Новые версии библиотеки Theano имеют встроенную функцию relu, но на случай, если у вас такой версии нет, мы определим её сами.

def relu(a):

    return a * (a > 0)

Обе эти функции используют значения «истина» и «ложь» и преобразуют их в числа.

Первым этапом, как обычно, идёт получение данных. Мы используем все те же параметры, которые установили в предыдущих примерах, поэтому на самом деле их можно просто скопировать из предыдущего файла.

def main():

    X, Y = get_normalized_data()

    max_iter = 20

    print_period = 10

    lr = 0.00004

    reg = 0.01

    Xtrain = X[:-1000,]

    Ytrain = Y[:-1000]

    Xtest  = X[-1000:,]

    Ytest  = Y[-1000:]

    Ytrain_ind = y2indicator(Ytrain)

    Ytest_ind = y2indicator(Ytest)

    N, D = Xtrain.shape

    batch_sz = 500

    n_batches = N // batch_sz

    M = 300

    K = 10

    W1_init = np.random.randn(D, M) / 28

    b1_init = np.zeros(M)

    W2_init = np.random.randn(M, K) / np.sqrt(M)

    b2_init = np.zeros(K)

Следующий этап – создание переменных и выражений Theano, причём все значения весовых коэффициентов и свободных членов будут общими переменными.

    thX = T.matrix(‘X’)

    thT = T.matrix(‘T’)

    W1 = theano.shared(W1_init, ‘W1’)

    b1 = theano.shared(b1_init, ‘b1’)

    W2 = theano.shared(W2_init, ‘W2’)

    b2 = theano.shared(b2_init, ‘b2’)

Вызовем также функции relu и софтмакс – Theano имеет собственную встроенную функцию софтмакс.

    thZ = relu( thX.dot(W1) + b1 )

    thY = T.nnet.softmax( thZ.dot(W2) + b2 )

Кроме того, определим в Theano нашу функцию затрат, включая и выражение для регуляризации. Мы можем так записать, поскольку Theano автоматически выполняет дифференцирование и нам нет нужды проводить его вручную.

    cost = -(thT * T.log(thY)).sum() + reg*((W1*W1).sum() + (b1*b1).sum() + (W2*W2).sum() + (b2*b2).sum())

Нам также понадобится переменная prediction, чтобы вычислить коэффициент ошибок.

    prediction = T.argmax(thY, axis=1)

Далее запишем выражения для обновления значений весовых коэффициентов и свободных членов.

    update_W1 = W1 – lr*T.grad(cost, W1)

    update_b1 = b1 – lr*T.grad(cost, b1)

    update_W2 = W2 – lr*T.grad(cost, W2)

    update_b2 = b2 – lr*T.grad(cost, b2)

Теперь создадим функцию train, являющуюся функцией Theano. Обратите внимание, что эта функция не имеет исходящих величин.

    train = theano.function(

        inputs=[thX, thT],

        updates=[(W1, update_W1), (b1, update_b1), (W2, update_W2), (b2, update_b2)],

    )

И заодно создадим функцию get_prediction, поскольку мы всегда так делаем для проверочного набора данных.

    get_prediction = theano.function(

        inputs=[thX, thT],

        outputs=[cost, prediction],

    )

Далее используем цикл из предыдущего примера, но с использованием написанных нами функций Theano, и выведём всё на экран.

    LL = []

    for i in xrange(max_iter):

        for j in xrange(n_batches):

            Xbatch = Xtrain[j*batch_sz:(j*batch_sz + batch_sz),]

            Ybatch = Ytrain_ind[j*batch_sz:(j*batch_sz + batch_sz),]

            train(Xbatch, Ybatch)

            if j % print_period == 0:

                cost_val, prediction_val = get_prediction(Xtest, Ytest_ind)

                err = error_rate(prediction_val, Ytest)

                print(“Cost / err at iteration i=%d, j=%d: %.3f / %.3f” % (i, j, cost_val, err))

                LL.append(cost_val)

    plt.plot(LL)

    plt.show()

if __name__ == ‘__main__’:

main()

Итак, попробуем запустить программу. Похоже, всё работает, как ожидалось.

Основы Tensorflow (обзор)

Теперь рассмотрим библиотеку TensorFlow и разберёмся в её основах, как до этого разбирались с основами Theano. Мы познакомимся с основными переменными, функциями и выражениями и узнаем, как оптимизировать простейшие функции. При этом мы множество раз будем сравнивать их с аналогами из файла theano1.py, так что просмотрите его, если вы этого ещё не делали.

Итак, импортируем наши обычные библиотеки.

import numpy as np

import tensorflow as tf

Тут возникает маленькая проблемка, поскольку библиотека Numpy оказалась не последней версии. Когда я использовал pip, последняя версия была 1.8RC, а когда использовал EasyUnstall, последней версией была 1.11. Поэтому, вероятно, вам следует воспользоваться EasyInstall, если у вас текущая версия Numpy не является последней.

Итак, установим новую версию и импортируем библиотеки ещё раз.

В библиотеке TensorFlow заполнители (placeholders) похожи на переменные Theano. Давайте создадим матрицу. Первое, что мы при этом должны указать, – это тип. Мы также можем указать её форму, хотя это необязательно, и имя, хотя это также необязательно.

A = tf.placeholder(tf.float32, shape=(5, 5), name=’A’)

Мы также можем создать вектор, не указывая при этом размерность и имя:

v = tf.placeholder(tf.float32)

И мы можем произвести матричное умножение, аналогичное тому, которое мы делали в Theano, и, как мне кажется, функция matmul несколько удобнее, чем функция dot.

w = tf.matmul(A, v)

Так же, как и в Theano, нам необходимо указать значения переменных – пока что мы их ещё не указывали. В TensorFlow текущая работа идёт в так называемой сессии. Поэтому откроем сессию и запустим матричное умножение. Сессия использует параметр feed_dict, с помощью которой мы объясняем ей, что есть А и что есть v, при этом чтобы указать значения, можно использовать Numpy. Выведем сразу результат на экран, чтобы увидеть, что получилось.

with tf.Session() as session:

    output = session.run(w, feed_dict={A: np.random.randn(5, 5), v: np.random.randn(5, 1)})

    print(output, type(output))

В результате мы получаем массив Numpy. Обратите внимание, что здесь есть отличие от Numpy, в котором можно опустить последнюю размерность в случае вектора, то есть TensorFlow действительно выполняет матричное умножение.

Теперь возьмём переменную, значение которой будем обновлять с помощью градиентного спуска, и вначале я покажу вам один пример. Мы можем создать переменную TensorFlow, используя функцию random_normal или с помощью массива Numpy, а также можем сделать нашу переменную скаляром.

shape = (2, 2)

x = tf.Variable(tf.random_normal(shape))

t = tf.Variable(0)

В TensorFlow переменные надо инициировать. Для этого можно использовать функцию initialize_all_variables. Она возвращает всё то, что будет вызвано далее в сессии.

init = tf.initialize_all_variables()

Итак, откроем сессию, запустим инициацию и выведем на экран, что инициируется. Получилось, что пока что ничего не инициируется.

Мы также можем вывести на экран значения переменных TensorFlow, используя функцию eval, но только после её запуска.

with tf.Session() as session:

    session.run(init)

    print x.eval()

    print t.eval()

Теперь давайте попробуем найти минимум простенькой функции затрат, как мы делали это в Theano. Создадим переменную, присвоив ей значение 20, как это было в случае с Theano, и создадим такую же функцию затрат.

u = tf.Variable(20.0)

cost = u*u + u + 1

Между Theano и TensorFlow есть одно различие, заключающееся в том, что в TensorFlow мы не пишем сами обновление для переменной. Вместо этого мы выбираем оптимизатор, которых в TensorFlow есть целый ряд и который реализует желаемый вами алгоритм. Возьмём, к примеру, GradientDescentOptimizer, который реализует обычный градиентный спуск. 0,3 – это значение коэффициента обучения. Если вы хотите узнать больше о возможных параметрах, обратитесь к документации. После этого мы указываем выражение, значение которого хотим минимизировать.

train_op = tf.train.GradientDescentOptimizer(0.3).minimize(cost)

Ещё раз инициируем наши переменные и откроем сессию. Правда, цикл нам всё же придётся написать самим, что и сделаем с выводом на экран значения функции затрат и значения параметра при каждой итерации.

init = tf.initialize_all_variables()

with tf.Session() as session:

    session.run(init)

    for i in xrange(12):

        session.run(train_op)

print “i = %d, cost = %.3f, u = %.3f” % (i, cost.eval(), u.eval())

И в результате получаем ожидаемый результат.

Нейронная сеть в Tensorflow, код (обзор)

Теперь применим изученные прежде основы TensorFlow для построения нейронной сети.

Если вы желаете лишь просмотреть код, а не писать его самостоятельно, перейдите по адресу https://github.com/lazyprogrammer/machine_learning_examples в папку ann_class2. Соответствующий файл называется tensorflow2.py.

Итак, первым делом импортируем все обычные библиотеки и функции, включая Matplotlib, чтобы иметь возможность построить график.

import numpy as np

import tensorflow as tf

import matplotlib.pyplot as plt

from util import get_normalized_data, y2indicator

Функцию error_rate мы также используем старую, из примера с библиотекой Theano.

def error_rate(p, t):

    return np.mean(p != t)

Далее, как обычно, загружаем наши данные. Все параметры и гиперпараметры, вновь-таки, берём те же, что и в предыдущем примере с Theano, но в данном случае мы введём в нашу нейронную сеть ещё один скрытый слой с 100 узлов.

def main():

    X, Y = get_normalized_data()

    max_iter = 30

    print_period = 10

    lr = 0.00004

    reg = 0.01

    Xtrain = X[:-1000,]

    Ytrain = Y[:-1000]

    Xtest  = X[-1000:,]

    Ytest  = Y[-1000:]

    Ytrain_ind = y2indicator(Ytrain)

    Ytest_ind = y2indicator(Ytest)

    N, D = Xtrain.shape

    batch_sz = 500

    n_batches = N / batch_sz

    M1 = 300

    M2 = 100

    K = 10

    W1_init = np.random.randn(D, M1) / 28

    b1_init = np.zeros(M1)

    W2_init = np.random.randn(M1, M2) / np.sqrt(M1)

    b2_init = np.zeros(M2)

    W3_init = np.random.randn(M2, K) / np.sqrt(M2)

    b3_init = np.zeros(K)

Теперь, по аналогии с Theano, создадим переменные и выражения TensorFlow.

    X = tf.placeholder(tf.float32, shape=(None, D), name=’X’)

    T = tf.placeholder(tf.float32, shape=(None, K), name=’T’)

    W1 = tf.Variable(W1_init.astype(np.float32))

    b1 = tf.Variable(b1_init.astype(np.float32))

    W2 = tf.Variable(W2_init.astype(np.float32))

    b2 = tf.Variable(b2_init.astype(np.float32))

    W3 = tf.Variable(W3_init.astype(np.float32))

    b3 = tf.Variable(b3_init.astype(np.float32))

Теперь используем функции TensorFlow для определения модели. В качестве функции активации первого и второго скрытых слоёв используем функцию relu. Третью переменную назовём Yish, поскольку это не «настоящее» Y, а лишь матричное умножение Z2 и W3 плюс b3 без использования функции софтмакс, так как функция софтмакс по ряду причин включена в расчёт функции затрат.

    Z1 = tf.nn.relu( tf.matmul(X, W1) + b1 )

    Z2 = tf.nn.relu( tf.matmul(Z1, W2) + b2 )

    Yish = tf.matmul(Z2, W3) + b3

Саму же функцию затрат мы можем записать с использованием несколько необычной функции softmax_cross_entropy_with_logits и переменной Yish, а также целевой переменной.

    cost = tf.reduce_sum(tf.nn.softmax_cross_entropy_with_logits(Yish, T))

Теперь нам нужно написать выражения для функций train и predict – для расчёта коэффициента ошибок. В данном примере мы используем RMSPropOptimizer, поскольку мы с ним уже знакомы, тем более что RMSPropOptimizer включает в себя импульс.

    train_op = tf.train.RMSPropOptimizer(lr, decay=0.99, momentum=0.9).minimize(cost)

    predict_op = tf.argmax(Yish, 1)

Далее вам всё, вероятно, будет знакомо – мы инициируем наши переменные, открываем сессию и пишем обычный уже цикл. Заметьте, в Theano мы вызываем функцию, тогда как в TensorFlow мы вызываем сессию для запуска функции, а данные передаём через параметр feed_dict.

    LL = []

    init = tf.initialize_all_variables()

    with tf.Session() as session:

        session.run(init)

        for i in xrange(max_iter):

            for j in xrange(n_batches):

                Xbatch = Xtrain[j*batch_sz:(j*batch_sz + batch_sz),]

                Ybatch = Ytrain_ind[j*batch_sz:(j*batch_sz + batch_sz),]

                session.run(train_op, feed_dict={X: Xbatch, T: Ybatch})

                if j % print_period == 0:

                    test_cost = session.run(cost, feed_dict={X: Xtest, T: Ytest_ind})

                    prediction = session.run(predict_op, feed_dict={X: Xtest})

                    err = error_rate(prediction, Ytest)

                    print(“Cost / err at iteration i=%d, j=%d: %.3f / %.3f” % (i, j, test_cost, err))

                    LL.append(test_cost)

Ну, и после всего этого выводим на экран наши данные.

    plt.plot(LL)

    plt.show()

if __name__ == ‘__main__’:

main()

Запустим нашу программу.

Обратите внимание, что наш коэффициент ошибок особо не улучшился с добавлением дополнительного слоя, но мы по-прежнему получили сравнимый ответ. Заметьте также, как через некоторое время возрастает ошибка нашего проверочного набора. Это может свидетельствовать о переобучении модели.

Андрей Никитенко
Андрей Никитенко
Задать вопрос эксперту
Понравилась статья? Поделить с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: