Свёрточная нейронная сеть в TensorFlow

TensorFlow – создание компонентов свёрточной нейронной сети

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

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

Итак, вот код для свёртки и агрегирования в TensorFlow:

def convpool(X, W, b):

    # предполагается, что размер пула (2, 2), поскольку нам нужно будет обрамить его единицами

    conv_out = tf.nn.conv2d(X, W, strides=[1, 1, 1, 1], padding=’SAME’

    conv_out = tf.nn.bias_add(conv_out, b)

    pool_out = tf.nn.max_pool(

        conv_out,

        ksize=[1, 2, 2, 1],

        strides=[1, 2, 2, 1],

        padding=’SAME’

    return pool_out

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

Кроме того, мы используем параметр padding для контроля над величиной исходящего изображения.

Как вы помните, свободный член является одномерным вектором, и в Theano нам приходилось использовать функцию dimshuffle, чтобы добавить его в конечный результат свёртки. Теперь же мы можем использовать встроенную функцию TensorFlow, которая называется bias_add.

Далее идёт функция max_pool. Обратите внимание, что параметр ksize, играющий роль параметра poolsize в Theano, теперь четырёхмерный, а не двухмерный, как было ранее – в начале и в конце добавлены единицы. Заметьте также, что данная функция также использует параметр strides. Это значит, что мы можем изменять размер шага при каждом максимизационном агрегировании, хотя и не будет этого делать.

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

def rearrange(X):

    # входные данные имеют вид (32, 32, 3, N)

    # исходящие данные имеют вид (N, 32, 32, 3)

    N = X.shape[-1]

    out = np.zeros((N, 32, 32, 3), dtype=np.float32)

    for i in xrange(N):

        for j in xrange(3):

            out[i, :, :, j] = X[:, :, j, i]

    return out / 255

Следующий этап свойственен только для работы в TensorFlow. Как вы, возможно, помните, TensorFlow позволяет не определять величину каждой размерности на входе. Это замечательная особенность, обеспечивающая большую гибкость, но из-за неё при написании программы я натолкнулся на препятствие, когда у меня при работе переполнилась оперативная память. Если вы ещё не заметили, то база SVHN весьма велика и содержит порядка 73 тысяч образцов.

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

Xtrain = Xtrain[:73000,]

Ytrain = Ytrain[:73000]

Xtest = Xtest[:26000,]

Ytest = Ytest[:26000]

Ytest_ind = Ytest_ind[:26000,]

# или рассчитать по формуле N = N / batch_sz * batch_sz

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

W1_shape = (5, 5, 3, 20)

W1_init = init_filter(W1_shape, poolsz)

b1_init = np.zeros(W1_shape[-1], dtype=np.float32)

W2_shape = (5, 5, 20, 50)

W2_init = init_filter(W2_shape, poolsz)

b2_init = np.zeros(W2_shape[-1], dtype=np.float32)

Что касается непосредственно нейронной сети, то также обратите внимание, что результаты свёртки теперь имеют другой размер – 8 вместо 5.

W3_init = np.random.randn(W2_shape[-1]*8*8, M) / np.sqrt(W2_shape[-1]*8*8 + M)

b3_init = np.zeros(M, dtype=np.float32)

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

b4_init = np.zeros(K, dtype=np.float32)

В части прямого распространения мы делаем почти всё то же самое, что и в Theano, разве что в TensorFlow нет метода flatten, поэтому используется метод reshape. К счастью, тут всё весьма просто.

И последнее – вычисление результата до функции софтмакс. Не забывайте, что функция затрат в TensorFlow требует логитов без использования софтмакс, а потому в данном месте софтмакс не используется.

Z1 = convpool(X, W1, b1)

Z2 = convpool(Z1, W2, b2)

Z2_shape = Z2.get_shape().as_list()

Z2r = tf.reshape(Z2, [Z2_shape[0], np.prod(Z2_shape[1:])])

Z3 = tf.nn.relu( tf.matmul(Z2r, W3) + b3 )

Yish = tf.matmul(Z3, W4) + b4.

TensorFlow – полная свёрточная нейронная сеть и её проверка на SVHN

Соберём вместе все части кода свёрточной нейронной сети в TensorFlow и запустим его.

Итак, первым делом скопируем часть, посвящённую импорту всех необходимых библиотек и служебных функций. Используем функцию datetime, чтобы знать, сколько потребовалось времени для вычислений, а вот функция convolve2d уже не нужна.

import numpy as np

import tensorflow as tf

import matplotlib.pyplot as plt

from datetime import datetime

from scipy.io import loadmat

from sklearn.utils import shuffle

def y2indicator(y):

    N = len(y)

    ind = np.zeros((N, 10))

    for i in xrange(N):

        ind[i, y[i]] = 1

    return ind

def error_rate(p, t):

    return np.mean(p != t)

Следующим делом определим нашу функцию convpool для свёртки и агрегирования. В данном случае мы не указываем размер пула poolsz, поскольку он в любом случае будет неизменным. Параметр padding устанавливаем в значении ‘SAME’, чтобы результат был того же размера, что и входные данные. Обратите внимание, что теперь мы не используем гиперболический тангенс.

def convpool(X, W, b):

    conv_out = tf.nn.conv2d(X, W, strides=[1, 1, 1, 1], padding=’SAME’)

    conv_out = tf.nn.bias_add(conv_out, b)

    pool_out = tf.nn.max_pool(conv_out, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding=’SAME’)

    return pool_out

Далее скопируем функцию инициации фильтра init_filter. Обратите внимание на некоторые отличия. Так, у нас теперь есть -1 и -2 в выражениях. Это связано с тем, что порядок размерностей фильтра отличается от такового в Theano.

def init_filter(shape, poolsz):

    w = np.random.randn(*shape) / np.sqrt(np.prod(shape[:-1]) + shape[-1]*np.prod(shape[:-2]) / np.prod(poolsz)))

    return w.astype(np.float32)

Далее наша функция rearrange для преобразования входных данных. Не забывайте, что цвет теперь идёт последним.

def rearrange(X):

    N = X.shape[-1]

    out = np.zeros((N, 32, 32, 3), dtype=np.float32)

    for i in xrange(N):

        for j in xrange(3):

            out[i, :, :, j] = X[:, :, j, i]

    return out / 255

Теперь вставим код, отвечающий за загрузку данных и инициацию учебной и проверочной матриц, а также параметров градиентного спуска.

def main():

    train = loadmat(‘../large_files/train_32x32.mat’)

    test  = loadmat(‘../large_files/test_32x32.mat’)

    Xtrain = rearrange(train[‘X’])

    Ytrain = train[‘y’].flatten() – 1

    print len(Ytrain)

    del train

    Xtrain, Ytrain = shuffle(Xtrain, Ytrain)

    Ytrain_ind = y2indicator(Ytrain)

    Xtest  = rearrange(test[‘X’])

    Ytest  = test[‘y’].flatten() – 1

    del test

    Ytest_ind = y2indicator(Ytest)

    max_iter = 20

    print_period = 10

    N = Xtrain.shape[0]

    batch_sz = 500

    n_batches = N / batch_sz

А теперь мы должны добавить кое-что новое, чтобы размер входных данных был постоянным. Установим размер обучающего набора данных равным 73 000 образцов, а размер проверочного набора – равным 26 000.

Xtrain = Xtrain[:73000,]

Ytrain = Ytrain[:73000]

Xtest = Xtest[:26000,]

Ytest = Ytest[:26000]

Ytest_ind = Ytest_ind[:26000,]

И устанавливаем те же величины для полносвязной нейронной сети в конце.

M = 500

K = 10

poolsz = (2, 2)

Теперь инициируем наши фильтры. Не забывайте про порядок следования данных.

W1_shape = (5, 5, 3, 20)

W1_init = init_filter(W1_shape, poolsz)

b1_init = np.zeros(W1_shape[-1], dtype=np.float32)

W2_shape = (5, 5, 20, 50)

W2_init = init_filter(W2_shape, poolsz)

b2_init = np.zeros(W2_shape[-1], dtype=np.float32)

И весовые коэффициенты нейронной сети.

W3_init = np.random.randn(W2_shape[-1]*8*8, M) / np.sqrt(W2_shape[-1]*8*8 + M)

b3_init = np.zeros(M, dtype=np.float32)

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

b4_init = np.zeros(K, dtype=np.float32)

Следующим этапом идёт определение переменных TensorFlow.

X = tf.placeholder(tf.float32, shape=(batch_sz, 32, 32, 3), name=’X’)

T = tf.placeholder(tf.float32, shape=(batch_sz, 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))

W4 = tf.Variable(W4_init.astype(np.float32))

b4 = tf.Variable(b4_init.astype(np.float32))

Переходим к части прямого распространения. Тут всё очень похоже с Theano, разве что придётся использовать метод reshape.

Z1 = convpool(X, W1, b1)

Z2 = convpool(Z1, W2, b2)

Z2_shape = Z2.get_shape().as_list()

Z2r = tf.reshape(Z2, [Z2_shape[0], np.prod(Z2_shape[1:])])

Z3 = tf.nn.relu(tf.matmul(Z2r, W3) + b3)

Yish = tf.matmul(Z3, W4) + b4

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

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

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

predict_op = tf.argmax(Yish, 1)

t0 = datetime.now()

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),]

          if len(Xbatch) == batch_sz:

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

            if j % print_period == 0:

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

test_cost = 0

prediction = np.zeros(len(Xtest))

   for k in xrange(len(Xtest) // batch_sz):

   Xtestbatch = Xtest[k*batch_sz:(k*batch_sz + batch_sz),]

   Ytestbatch = Ytest_ind[k*batch_sz:(k*batch_sz + batch_sz),]

   test_cost += session.run(cost, feed_dict={X: Xtestbatch, T: Ytestbatch})

   prediction[k*batch_sz:(k*batch_sz + batch_sz)] = session.run(

   predict_op, feed_dict={X: Xtestbatch})

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

                        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)

    print “Elapsed time:”, (datetime.now() – t0)

    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: :???: :?: :!:

Share via
Copy link