Текст книги "Основы глубокого обучения"
Автор книги: Нихиль Будума
Жанр: Управление и подбор персонала, Бизнес-Книги
Возрастные ограничения: +16
сообщить о неприемлемом содержимом
Текущая страница: 6 (всего у книги 18 страниц) [доступный отрывок для чтения: 6 страниц]
Адаптация темпа обучения
Еще одна серьезная проблема при обучении глубоких сетей – выбор правильного темпа. Эта задача уже давно считается одним из самых проблематичных аспектов обучения глубоких сетей, поскольку темп серьезно влияет на эффективность. Слишком низкий не позволит обучаться быстро, а слишком высокий может привести к проблемам со сходимостью при достижении локального минимума или плохо обусловленного участка.
Один из главных новых прорывов в области оптимизации глубоких сетей – возможность адаптации темпа обучения. Смысл в том, что темп модифицируется в процессе для достижения хорошей сходимости. В следующих разделах мы рассмотрим три самых популярных алгоритма адаптации темпа обучения: AdaGrad, RMSProp и Adam.
AdaGrad – суммирование исторических градиентовПервым мы рассмотрим алгоритм AdaGrad, который стремится адаптировать общий темп обучения путем суммирования исторических градиентов. Впервые алгоритм был предложен в работе Джона Дучи и его коллег (2011)[50]50
Duchi J., Hazan E., Singer Y. Adaptive Subgradient Methods for Online Learning and Stochastic Optimization // Journal of Machine Learning Research. 2011. Vol. 12 (Jul.). Pp. 2121–2159.
[Закрыть]. Мы сохраняем изменение темпа обучения для каждого параметра. Темп обратно масштабируется с учетом величины квадратного корня суммы квадратов (среднеквадратичного значения) всех исторических градиентов параметра. Можно выразить это математически. Мы инициализируем вектор суммирования градиента r0 = 0. На каждом шаге мы суммируем квадраты градиентов всех параметров следующим образом (где операция – поэлементное умножение тензоров):
Затем мы обычным путем вычисляем обновление, но теперь глобальный темп обучения делится на квадратный корень вектора сумм градиента:
Заметьте, что мы добавляем в знаменателе небольшую величину δ (~10−7), чтобы избежать деления на 0. Кроме того, операции деления и сложения связаны с размером вектора суммирования градиента и применяются поэлементно. Встроенный в TensorFlow оптимизатор позволяет легко использовать AdaGrad в качестве алгоритма обучения:
tf.train.AdagradOptimizer(learning_rate,
initial_accumulator_value=0.1,
use_locking=False,
name='Adagrad')
Нужно только помнить, что в TensorFlow величина δ и исходный вектор суммирования градиента объединены в аргумент initial_accumulator_value.
На функциональном уровне такой механизм обновления означает, что параметры с наибольшими градиентами будут быстро снижать темп обучения, а с меньшими – незначительно. Можно констатировать, что AdaGrad обеспечивает больший прогресс на более пологих участках поверхности ошибок, помогая преодолеть плохо обусловленные поверхности. Теоретически это обеспечивает хорошие свойства, но на практике обучение глубоких моделей с помощью AdaGrad сопряжено с проблемами. По опыту можно сказать, что у него есть тенденция к преждевременному уменьшению темпа обучения, и он едва ли будет хорошо работать в некоторых глубоких моделях. Поговорим о RMSProp – алгоритме, призванном устранить этот недостаток.
RMSProp – экспоненциально взвешенное скользящее среднее градиентовAdaGrad хорошо работает с простыми выпуклыми функциями, но этот алгоритм не предназначен для сложных поверхностей ошибок глубоких сетей. Плоские области могут поставить AdaGrad в тупик, заставив снизить темп обучения еще до достижения минимума. Получается, простого сложения градиентов недостаточно.
Решением может стать идея, которую мы вводили при использовании импульса для снижения флуктуаций градиента. По сравнению с простым сложением экспоненциально взвешенные скользящие средние позволяют «отбрасывать» измерения, произведенные задолго до текущего момента. Обновление вектора суммирования градиента в этом случае выглядит так:
Коэффициент затухания ρ определяет, как долго мы будем хранить старые градиенты. Чем он ниже, тем меньше рабочее окно. Внеся такую модификацию в AdaGrad, мы получим алгоритм обучения RMSProp, впервые предложенный Джеффри Хинтоном[51]51
Tieleman T., Hinton G. Lecture 6.5-rmsprop: Divide the gradient by a running average of its recent magnitude // COURSERA: Neural Networks for Machine Learning. 2012. Vol. 4. No. 2.
[Закрыть].
В TensorFlow создать оптимизатор RMSProp можно с помощью следующего кода. Отметим, что в этом случае, в отличие от AdaGrad, мы вводим δ отдельно как эпсилон-аргумент конструктора:
tf.train.RMSPropOptimizer(learning_rate, decay=0.9,
momentum=0.0, epsilon=1e-10,
use_locking=False, name='RMSProp')
Как предполагает этот шаблон, можно использовать RMSProp в сочетании с импульсом (особенно нестеровским). В целом он показал себя эффективным средством оптимизации глубоких нейросетей и стал выбором по умолчанию для многих опытных практиков.
Adam – сочетание импульсного метода с RMSPropПрежде чем завершить разговор о современных средствах оптимизации, рассмотрим еще один алгоритм – Adam[52]52
Kingma D., Ba J. Adam: A Method for Stochastic Optimization // arXiv preprint arXiv: 1412.6980 (2014).
[Закрыть]. В принципе, можно считать его вариантом сочетания импульсного метода и RMSProp.
Основная идея такова. Мы хотим записывать экспоненциально взвешенное скользящее среднее градиента (скорость в классическом импульсном подходе), что можно выразить следующим образом:
mi = β1mi – 1 + (1 – β1)gi.
Это аппроксимация так называемого первого импульса градиента, или E[gi]. Как и в RMSProp, можно сохранять экспоненциально взвешенное скользящее среднее исторических градиентов. Это оценка того, что называется вторым импульсом градиента, или E[gi gi]:
vi = β2vi − 1 + (1 − β2)gi gi.
Но оказывается, что эти приближения не соответствуют реальным импульсам, поскольку мы инициализируем оба вектора нулями. Чтобы устранить несоответствие, мы выводим поправочный коэффициент для обоих случаев. Здесь мы описываем вывод определения для второго импульса. Вывод первого импульса аналогичен, и мы оставим его в качестве упражнения для любителей математики.
Начнем с выражения определения второго импульса через все предыдущие градиенты. Для этого достаточно расширить рекуррентное соотношение:
Возьмем ожидаемое значение обеих частей и определим, как наша оценка E[vi] соотносится с истинным значением E[gi gi]:
Можно принять, что E[gk gk] ≈E[gi ≈ gi], поскольку, даже если второй импульс градиента поменял значение по сравнению с историческим, β2 должно быть выбрано так, чтобы прежние вторые импульсы градиентов утратили релевантность. В результате становится возможным такое упрощение:
Последнее упрощение сделано с помощью элементарного алгебраического тождества 1 − xn = (1 − x) (1 + x + … + xn – 1). В результате вывода второго импульса и аналогичного ему вывода первого мы получаем следующие поправочные схемы для борьбы с ошибкой инициализации:
Теперь можно с помощью скорректированных импульсов обновить вектор параметров, что приведет к окончательному обновлению Adam:
В последнее время Adam набрал популярность благодаря возможности исправлять ошибку нулевой инициализации (в RMSProp это слабое место) и способности эффективно сочетать ключевые идеи RMSProp и импульсного метода. В TensorFlow оптимизатор Adam создается с помощью следующего конструктора:
tf.train.AdamOptimizer(learning_rate=0.001, beta1=0.9,
beta2=0.999, epsilon=1e-08,
use_locking=False, name='Adam')
Настройки по умолчанию гиперпараметров для Adam в TensorFlow обычно работают удовлетворительно, но Adam хорошо справляется и с изменениями в них. Единственное исключение: иногда может понадобиться изменить темп обучения (по умолчанию задана величина 0,001).
Философия при выборе метода оптимизации
В этой главе мы обсудили ряд стратегий для упрощения навигации по сложным поверхностям ошибок глубоких сетей. Они нашли свое высшее выражение в нескольких алгоритмах оптимизации, каждый из которых имеет свои достоинства и недостатки. Было бы замечательно заранее знать, когда какой алгоритм использовать, но практикующие эксперты пока не достигли здесь консенсуса. Сейчас самые популярные варианты – мини-пакетный градиентный спуск, мини-пакетный градиент в сочетании с импульсом, RMSProp, RMSProp в сочетании с импульсом, Adam и AdaDelta (об этом алгоритме мы здесь не говорили, а TensorFlow его пока не поддерживает). Мы включили в репозитории этой книги на Github скрипт TensorFlow, чтобы любознательный читатель мог поэкспериментировать с алгоритмами оптимизации на примере построенной модели сети с прямым распространением сигнала:
$ python optimzer_mlp.py <sgd, momentum, adagrad, rmsprop, adam>
Важно отметить, однако, что для большинства практиков глубокого обучения лучший способ достичь вершин в своем деле состоит не в создании более совершенных оптимизаторов. Большинство прорывов в этой области за последние несколько десятилетий связаны с нахождением архитектур, которые проще обучать, а не попытками разобраться с неприятными поверхностями ошибок. Далее мы сосредоточимся на использовании архитектур для более эффективного обучения нейросетей.
Резюме
В этой главе мы поговорили о ряде проблем при попытках обучать глубокие сети со сложными поверхностями ошибок. Мы рассказали, что проблемы сомнительных локальных минимумов, возможно, преувеличены, а седловые точки и плохая обусловленность действительно могут серьезно мешать успеху обычного мини-пакетного градиентного спуска. Мы описали, как использовать импульс для борьбы с плохой обусловленностью, и дали краткий обзор последних исследований методов второго порядка, направленных на аппроксимацию матрицы Гессе. Мы рассказали об эволюции алгоритмов оптимизации с адаптацией темпа обучения, которые настраивают темп в процессе для улучшения сходимости.
В следующей главе мы начнем разговор о более широкой проблеме архитектуры и проектирования сетей. Начнем мы с анализа компьютерного зрения и того, как создавать глубокие сети, эффективно обучающиеся на сложных изображениях.
Глава 5. Сверточные нейросети
Нейроны и зрение человека
Человеческое зрение развито очень хорошо. За доли секунды мы можем распознавать видимые объекты без особых умственных усилий или задержек. Мы не только способны назвать то, на что смотрим, но и ощутить его глубину, четко определить контуры и отделить от фона. Каким-то образом наши глаза получают необработанные воксели[53]53
Воксель (voxel) – элемент трехмерного изображения. Название образовано по аналогии с «пиксел» (picture element, элемент изображения), от англ. volume element – объемный элемент. Прим. науч. ред.
[Закрыть] информации о цвете, но в мозге они перерабатываются в более значимые единицы – линии, кривые и формы, которые могут, например, подсказать, что мы смотрим на домашнюю кошку[54]54
Hubel D. H., Wiesel T. N. Receptive fields and functional architecture of monkey striate cortex // The Journal of Physiology. 1968. Vol. 195. No. 1. Pp. 215–243.
[Закрыть].
Человеческое зрение основано на нейронах. Последние отвечают за доставку световой информации в глаз[55]55
Cohen A. I. Rods and Cones // Physiology of Photoreceptor Organs. Springer Berlin Heidelberg, 1972. Pp. 63–110.
[Закрыть]. Эта информация проходит здесь предварительную обработку, препровождается в зрительную кору мозга и только там анализируется. За все эти функции и отвечают нейроны. Поэтому интуитивно кажется, что стоило бы обобщить наши модели нейронных сетей для создания более эффективных систем компьютерного зрения. В этой главе мы воспользуемся нашим пониманием человеческого зрения для создания эффективных моделей глубокого обучения анализу изображений. Но для начала рассмотрим более традиционные подходы к вопросу и отметим их недостатки.
Недостатки выбора признаков
Начнем с простой проблемы компьютерного зрения. Я дам вам случайно выбранное изображение – например, с рис. 5.1. Ваша задача – ответить, есть ли здесь человеческое лицо. Именно эту проблему затрагивали Пол Виола и Майкл Джонс в своей основополагающей работе в 2001 году[56]56
Viola P., Jones M. Rapid Object Detection using a Boosted Cascade of Simple Features // Computer Vision and Pattern Recognition, 2001. CVPR 2001. Proceedings of the 2001 IEEE Computer Society Conference on. Vol. 1. IEEE, 2001.
[Закрыть].
Рис. 5.1. Гипотетический алгоритм распознавания лиц должен найти на этой фотографии лицо экс-президента Барака Обамы
Для людей это тривиальная задача. А вот для компьютера она очень сложна. Как научить его тому, что на изображении есть лицо? Можно использовать традиционный алгоритм машинного обучения (вроде того, что был описан в главе 1), подавая на вход значения пикселов в надежде на то, что найдется подходящий классификатор. Но работает это плохо, поскольку отношение сигнала к шуму слишком низкое, чтобы удалось хоть чему-то обучиться. Нужна альтернатива.
Со временем появился компромиссный вариант – по сути, нечто среднее между традиционной компьютерной программой, логика которой определяется человеком, и чистым подходом машинного обучения, где все бремя ложится на компьютер. Человек выбирает признаки (возможно, сотни или тысячи), которые, по его мнению, важны для принятия решений по классификации. Так он создает представление той же проблемы, но меньшей размерности. Алгоритм машинного обучения использует новые векторы признаков для принятия решений по классификации. Поскольку процесс извлечения признаков улучшает соотношение сигнала и шума (если выделены действительно важные аспекты), этот подход имел большой успех по сравнению с имевшимися на тот момент. Виола и Джонс отметили, что лица характеризуются определенным соотношением светлых и темных пятен, которое и можно здесь использовать. Например, есть разница в интенсивности света между областью глаз и скулами, переносицей и глазами по обе стороны от нее. Эти показатели приведены на рис. 5.2.
Рис. 5.2. Показатели интенсивности Виолы – Джонса
Каждый из этих признаков сам по себе не особо эффективен при идентификации лица. Но при совместном использовании (в рамках классического алгоритма машинного обучения – бустинга, описанного в оригинальной работе (http://bit.ly/2qMguNT)) их общая эффективность значительно возрастает. На наборе данных из 130 изображений и 507 лиц алгоритм достигает эффективности 91,4 % при 50 ложных срабатываниях. Тогда такие показатели были беспрецедентными, но у этого алгоритма есть фундаментальные ограничения. Если лицо частично затенено, сравнение интенсивности света не срабатывает. Более того, если алгоритм «смотрит» на морщинистое лицо или лицо персонажа мультфильма, неудача почти неизбежна.
Алгоритм на самом деле так и не научился «смотреть». Помимо различий в интенсивности света, наш мозг использует обширный спектр визуальных подсказок для определения того, что мы видим человеческое лицо. Это контуры, относительное расположение черт и цвет. И даже если одна из визуальных подсказок содержит расхождения (например, части лица скрыты или затенены), зрительная кора все равно с высокой надежностью способна вычленять лица.
Чтобы с помощью традиционных методов научить компьютер «видеть», нужно дать программе гораздо больше признаков. Только так можно добиться более точных решений. До эры глубокого обучения огромные коллективы исследователей компьютерного зрения многие годы спорили о пользе разных признаков. Поскольку задачи распознавания становились всё сложнее, ученым часто приходилось тяжело.
Чтобы проиллюстрировать возможности глубокого обучения, рассмотрим соревнование ImageNet – одно из самых престижных в области компьютерного зрения (иногда его даже называют Олимпийскими играми компьютерного зрения)[57]57
Deng J. et al. ImageNet: A Large-Scale Hierarchical Image Database // Computer Vision and Pattern Recognition, 2009. CVPR 2009. IEEE Conference. IEEE, 2009.
[Закрыть]. Каждый год исследователи пытаются разбить изображения по 200 возможным классам на основе обучающего набора данных примерно из 450 тысяч образцов. Алгоритму дается пять попыток на ответ, прежде чем он переходит к следующему изображению из тестового набора. Цель – довести компьютерное зрение до уровня человеческого (примерно 95–96 % точности). В 2011 году победитель соревнования ImageNet показал частоту ошибок 25,7 %, в среднем одно изображение из четырех[58]58
Perronnin F., Sénchez J., Xerox Y. L. Large-scale image categorization with explicit data embedding // Computer Vision and Pattern Recognition (CVPR), 2010 IEEE Conference. IEEE, 2010.
[Закрыть]. Конечно, это большой шаг по сравнению с гаданием, но для коммерческого применения такой результат не подходит.
В 2012 году Алекс Крижевский из лаборатории Джеффри Хинтона в Университете Торонто совершил невозможное. Он впервые применил архитектуру глубокого обучения, ныне известную как сверточная нейросеть, к проблемам особого масштаба и сложности, не оставив соперникам шансов. Второе место занял участник с похвальным результатом 26,1 %. А вот AlexNet всего за несколько месяцев работы побила все рекорды 50-летних исследований в области традиционного компьютерного зрения с частотой появления ошибок примерно 16 %[59]59
Krizhevsky A., Sutskever I., Hinton G. E. ImageNet Classification with Deep Convolutional Neural Networks // Advances in Neural Information Processing Systems. 2012.
[Закрыть]. Не будет преувеличением сказать, что она единолично ввела глубокое обучение в работу над компьютерным зрением. Произошла настоящая революция в этой области.
Обычные глубокие нейросети не масштабируются
Фундаментальная цель применения глубокого обучения к компьютерному зрению – отказ от трудоемкого и имеющего значительные ограничения процесса выбора признаков. Как мы уже говорили в главе 1, глубокие нейросети идеальны для такого процесса, поскольку каждый их слой отвечает за усвоение и создание признаков, представляющих получаемые входные данные. Наивный подход мог бы состоять в использовании обычной глубокой нейросети вроде той, что мы разработали в главе 3, и применении ее к набору данных MNIST для решения проблемы классификации изображений. Но при таком методе мы вскоре столкнемся с очень неприятными трудностями, которые показаны на рис. 5.3.
Рис. 5.3. Плотность связей между слоями неприемлемо возрастает с увеличением размера изображения
В MNIST наши изображения имели масштаб всего 28×28 пикселов и к тому же были черно-белыми. В результате у нейрона в полносвязном скрытом слое было всего 784 входящих веса. Это нормально для работы с MNIST, и наша обычная нейросеть функционировала хорошо. Однако этот метод не масштабируется на более объемные изображения. Например, для полноцветного изображения 200×200 пикселов входной слой будет иметь 200×200×3 = 120 000 весов. А нам нужно будет разместить множество нейронов по разным слоям, и число параметров будет расти очень быстро! Полная связность не только окажется излишней, но и обусловит значительно большие шансы на переобучение.
Сверточная сеть использует преимущество того, что мы анализируем именно изображения, и разумно ограничивает архитектуру глубокой сети. Поэтому мы можем значительно сократить число параметров в нашей модели. Слои сверточной сети, основанные на работе человеческого зрения, организованы в трех измерениях: у каждого слоя есть ширина, высота и глубина, как показано на рис. 5.4[60]60
LeCun Y. et al. Handwritten Digit Recognition with a Back-Propagation Network // Advances in Neural Information Processing Systems. 1990.
[Закрыть]. Как мы позже увидим, нейроны в сверточном слое соединены только с небольшой локальной областью предыдущего слоя. Это позволяет избежать избыточности полносвязных нейронов. Функцию сверточного слоя можно выразить просто: он обрабатывает трехмерный объем информации, порождая новый трехмерный объем информации. Подробнее о том, как это работает, мы поговорим в следующем разделе.
Рис. 5.4. В сверточных слоях нейроны организуются в трех измерениях, эти слои имеют ширину, высоту и глубину
Фильтры и карты признаков
Чтобы объяснить базовые элементы сверточного слоя, рассмотрим, как человеческий мозг сводит воедино необработанную визуальную информацию и понимает окружающий мир. Одно из самых знаковых исследований в этой области было проведено Дэвидом Хьюбелом и Торстеном Визелем. Они обнаружили, что зоны зрительной коры отвечают за определение границ. В 1959 году они вставили электроды в мозг кошки и стали проецировать на экраны черно-белые изображения. Оказалось, некоторые нейроны активируются только при появлении вертикальных линий, другие – только горизонтальных, а третьи – линий под определенными углами[61]61
Hubel D. H., Wiesel T. N. Receptive fields of single neurones in the cat’s striate cortex // The Journal of Physiology. 1959. Vol. 148. No. 3. Pp. 574–591.
[Закрыть].
В дальнейших работах было выяснено, что зрительная кора организована в слои. Каждый отвечает за дополнение признаков, обнаруженных на предыдущих слоях, «прописывая» линии, контуры, формы и, наконец, объекты целиком. Более того, в слое визуальной коры одни и те же детекторы расположены повсюду, что позволяет определять признаки во всех частях изображения. Эти идеи во многом повлияли на разработку сверточных нейронных сетей.
Первой из этого нового знания зародилась идея фильтра, к которой, как оказалось, были довольно близки Виола и Джонс. По сути, это детектор признака, и его работу мы рассмотрим на упрощенном примере на рис. 5.5.
Рис. 5.5. Пример простого черно-белого изображения
Допустим, нам нужно определить вертикальные и горизонтальные линии на этом изображении. Один из вариантов решения – использовать подходящий детектор признаков, как на рис. 5.6. Например, чтобы определить вертикальные линии, воспользуемся детектором сверху, распространив его на все изображение, и на каждом шаге будем проверять совпадения. Запишем ответы в матрицу в правом верхнем углу. Если есть совпадение, мы красим соответствующую ячейку в черный цвет, если нет – оставляем ее белой. В результате получаем карту признаков, которая показывает, где мы нашли нужный признак в исходном изображении. То же можно сделать с детектором горизонтальных линий снизу – получится карта признаков, приведенная в правом нижнем углу.
Рис. 5.6. Применение фильтров для вертикальных и горизонтальных линий в примере
Эта операция называется сверткой. Мы берем фильтр и распространяем его на всю область входящего изображения. Воспользуемся следующей схемой и постараемся выразить эту операцию в виде нейронов сети. Здесь слои в нейросети с прямым распространением сигналов представляют либо исходное изображение, либо карту признаков. Фильтры – сочетания связей (одно из них выделено на рис. 5.7), которые повторяются для всех входных данных. На рис. 5.7 связи одного цвета всегда будут иметь одинаковый вес. Добиться этого можно, инициализировав все связи в группе с идентичными весами и постоянно усредняя обновления весов группы, прежде чем применить их в конце каждой итерации обратного распространения ошибок. Выходной слой – карта признаков, созданная этим фильтром.
Нейрон на карте признаков активируется, если фильтр, отвечающий за эту операцию, обнаруживает подходящий признак в соответствующей позиции в предыдущем слое.
Рис. 5.7. Выражение фильтров и карт признаков в виде нейронов в сверточной сети
Обозначим k-ю карту признаков в слое m как mk. Далее обозначим соответствующий фильтр по значениям его весов W. Предположим, что у нейронов на карте признаков может быть смещение bk (оно идентично для всех нейронов на карте признаков), и теперь мы можем математически выразить карту признаков:
Это математическое выражение, конечно, простое и сжатое, но не до конца описывает применение фильтров в сверточных нейронных сетях. Те работают не только на одной карте признаков. Они действуют по всему объему карт, созданных на конкретном слое. Рассмотрим, например, ситуацию, когда нам нужно определить лицо на конкретном уровне сверточной сети. Мы аккумулируем три карты признаков: одну для глаз, одну для носов, одну для ртов. Мы знаем, что на определенном участке изображения есть лицо, если соответствующие участки на базовых картах содержат подходящие признаки (два глаза, нос и рот). Иными словами, чтобы определить наличие лица на изображении, мы должны свести воедино свидетельства нескольких карт. Это столь же необходимо и для полноцветного изображения. Пикселы в нем представлены в виде значений шкалы RGB, и во входном объеме нам потребуется три сектора (по одному для каждого цвета). Итак, карты признаков должны уметь работать на объемах, а не только плоскостях. Это показано на рис. 5.8. Каждая клетка входного объема – нейрон. Локальная область умножается на фильтр (соответствующий весам в сверточном слое), и образуется нейрон на карте фильтров в объемном слое нейронов.
Рис. 5.8. Представление полноцветного RGB-изображения в виде объема и наложение объемного сверточного фильтра
Как мы уже говорили в предыдущем разделе, сверточный слой (состоящий из набора фильтров) преобразует один набор значений в другой. Глубина фильтра соответствует глубине входного объема. Фильтр может комбинировать информацию обо всех выученных признаках. Глубина исходящего объема сверточного слоя эквивалентна числу фильтров в нем, ведь каждый фильтр порождает свой сектор. Эти отношения показаны на рис. 5.9.
Рис. 5.9. Трехмерная визуализация сверточного слоя, где каждый фильтр соответствует сектору в получившемся выходном объеме
В следующем разделе мы применим эти концепции и заполним ряд пробелов, чтобы дать полное описание сверточного слоя.
Внимание! Это не конец книги.
Если начало книги вам понравилось, то полную версию можно приобрести у нашего партнёра - распространителя легального контента. Поддержите автора!Правообладателям!
Данное произведение размещено по согласованию с ООО "ЛитРес" (20% исходного текста). Если размещение книги нарушает чьи-либо права, то сообщите об этом.Читателям!
Оплатили, но не знаете что делать дальше?