Способы взаимодействия сервисов друг с другом. Пулинг/пуш. Достоинства/недостатки. Выбор

Не всегда более производительное решение — решение, требующее меньше ресурсов при своей работе, — является лучшим. Часто сопутствующие факторы являются более значимыми: предсказуемость поведения при сбоях, скорость восстановления работоспособности после сбоев и т.п. Рассмотрим это на примере систем межсервисного взаимодействия.
- Курьер доставил заказ. По смене статуса заказа надо уведомить заинтересованные стороны об этих событиях.
- Клиент отправляет сообщение в чат поддержки. Нужно уведомить сервисы поддержки о поступивших данных от клиента.
- Построение отчёта завершено. Ожидающий отчёт пользователь может его загрузить. Надо его уведомить об этом.
Знакомые/типовые ситуации. Одному сервису надо уведомить другой (другие) о происшедших событиях.
Давайте немного усложним:
Сервер — находится в нашей юрисдикции. Мы следим за тем, чтоб ресурсов ему на всё хватало. Добавляем ноды в кластер и т.п.
Заинтересованные в событиях стороны — находятся «где-то». На серверах клиентов. Которые экспериментируют, экономят на железе. Используют программные решения, не предназначенные для нагрузки, и т.п.
Какие способы уведомления есть?
Активность со стороны сервера
Это, в общем-то, типовое решение. Сервер держит список заинтересованных сторон. По мере появления событий выполняет HTTP-запросы к клиентам.
Подвариант этого решения: Websocket. Сервер отправляет события в сокеты всем подписанным сторонам.

Повторы, обработка ошибок
Рано или поздно любой TCP/HTTP-канал сталкивается с недоступностью другой стороны. Что делать после возникновения ошибки? Повторять запросы? Что делать с вновь поступающими запросами? Ждать, пока успешно выполнятся предыдущие?
Рассмотрим виды ошибок:
- Сетевые
- Устранимые (после повтора могут исчезнуть) HTTP ( 500 , 502 , 504 , и т.п.)
- Неустранимые ( 4xx )
Получив неустранимую ошибку, клиент может только записать её в лог. То есть, если полная остановка доставки сообщений не приемлема, то, получив неустранимую ошибку, типовым решением будет считать, что «уведомление доставлено», и переходить к доставке следующих уведомлений. Вероятно, это единственный нормальный путь.
Идя по этому пути, надо постоянно и внимательно следить за мониторами таких ошибок. Анализировать трафик на тему «почему возникла неустранимая ошибка?» и «можно ли жить дальше с этой ошибкой».
Но это не самая большая проблема.
Более интересными являются проблемы:
500-е ошибки
Мы выполняем запрос-передачу данных для сервера X. Происходит 500-я ошибка. Что это?
Возможны два варианта:
- Сервис-приёмник данных по какой-то причине именно сейчас не работает (перегружается, переключается БД итп). В этом случае повтор запроса в дальнейшем приведёт нас к успеху.
- В сервисе допущена ошибка, приводящая к 500. В этом случае, сколько бы повторов мы ни сделали, до исправления кода в приёмнике ситуация не изменится.
То есть, по повторяемости запросов ошибки у нас делятся на три вида:
- Те, которым повтор поможет (сетевые, устранимые 500-ки).
- Те, которым повтор не поможет, но выглядят как те, которым поможет (неустранимые 500-ки).
- Те, которым повтор не поможет (например 40x-ки).
Разрабатывая политику повторов, помимо указанной проблемы, имеем ещё множество других проблем:
- Как часто повторять запросы?
- Не будем ли мы «укладывать» внешний сервис, повторяя запросы?
- Не будем ли сами «укладываться», если одна из внешних систем по какой-то причине имеет некорректный TCP-стек ( iptables DROP )?
Если посмотреть на систему повторов запросов, то обнаружится, что практически в каждом случае она выбирается индивидуально.
Если сервис, генерирующий событие, и занимается доставкой его до заинтересованных сторон, то имеем
- минимальный лаг доставки
- минимальная нагрузка на хранилище сообщений;
- необходимость повторов в случае неуспеха доставок
- необходимость ведения реестра, кому что доставлено и кому что нужно доставить
- двусмысленность некоторых ошибок: непонятно, можно (нужно) ли повторять, или нет
- зависимость от стека TCP на стороне клиентов ( iptables -j DROP занимает слот отправки вплоть до таймаута)
- система повторов может быть причиной DDoS для клиентских сервисов.
Также есть некоторое количество организационных минусов:
- После того, как клиент прекратил де-факто работу (тут два варианта: сервера выключены, сервера не выключены), система продолжает доставлять ему уведомления.
Вебсокет в режиме клиент-сервер
Часть описанных проблем решает постоянное соединение, инициируемое клиентом. Однако именно часть.
Необходимость повторов и двусмысленность ошибок — снимаются. Однако, необходимость ведения реестра, кому и что нужно доставить (если мы говорим о системе сообщений «без потерь») остаётся. Зависимость от стека TCP на стороне клиента снижается, но не до нуля. Система также может быть причиной DDoS для клиентских сервисов.
Пулинг

Достоинства пулинга
- Если у клиента проблемы со связью, нагрузкой — он просто не делает запросы
- После того, как клиент отключается (организационный момент), — он перестаёт делать запросы
- Максимально быстрое восстановление работоспособности после факапов.
Недостатки пулинга
- минимальный лаг доставки сообщений равен интервалу пулинга, который обычно выбирается ненулевым
- множество сервисов пулящих один создают существенно бОльшую нагрузку, нежели случай с активным сервисом. Сервисы, для которых нет сейчас никаких сообщений, всё равно создают нагрузку на подсистему доставки сообщений.
Ещё один неочевидный, организационный недостаток пулинга: часто способ получения новой порции данных связан со структурой хранения данных.
Если говорить о межсервисном взаимодействии (невысокое количество клиентов), то получается, что пулинг клиентом — наиболее выгодное решение. Почему?
- отсутствие двусмысленности, описанной выше
- наиболее быстрое восстановление работоспособности после сбоя
- максимальная независимость от сетевого стека TCP на клиенте
- нет необходимости хранить/майнтенить список клиентов.
Лаг доставки
Для чего вводят интервал пулинга? Каждый клиент, делающий 1 запрос за данными в секунду, — это 1RPS нагрузки. Почему нельзя пулить, не используя интервал (делать запрос сразу после получения результатов предыдущего)? Потому что обычно запрос за данными является сравнительно дорогим. А дорогим он является потому, что, как правило, некорректно спроектирован.
Как правило, запросы для пулинга формулируются как «есть ли данные для меня; если есть, то какие?». Такие запросы (в случае, если они некорректно спроектированы) зачастую имеют следующие проблемы:
- запрос неиндексирован
- при перегрузках количество данных в ответе может расти, или время выполнения запроса ухудшаться.
В случае, если получение очередной порции данных сопровождается простой выборкой из BTREE индекса, то и ответ на вопрос «есть ли данные?», как правило, сравнительно бесплатен. Об индексах поговорим ниже.
А сейчас давайте рассмотрим алгоритм работы традиционного пулера.

- Первичная инциализация пулера. index := 0 . index — это обобщённая переменная, указывающая на позицию запрашиваемых данных.
- Выполняется запрос limit данных с позиции index .
- Обрабатываем полученные данные
- index := index + 1
- Пауза соответствующая интервалу
- Перейти к шагу 2
Если рассматривать этот алгоритм, то видим, что переменная index и есть то, что связывает нас со структурой хранения данных.
Такой алгоритм, как правило, используют новички и. приводят себя к трудноустранимой проблеме: запросы с большими значениями index сделать индексируемыми крайне сложно. Почти невозможно.
Почему разработчик попадает в такую ситуацию? Потому что проектирует БД и API отдельно друг от друга. А нужно посмотреть на все компоненты в целом и на влияние их друг на друга.
Проблема состоит в том, что в БД, как правило, данные хранятся в виде плоских таблиц. Когда мы получаем очередную порцию данных с одними и теми же условиями фильтрации, то приходится делать что-то вроде следующего:
SELECT * FROM "table" WHERE "somefield" = $1 LIMIT 100 OFFSET $2
То есть, index из алгоритма пересчитывается в смещение ( $2 ). Такой запрос из БД имеет всё более ухудшающийся план выполнения по мере роста смещения (которое растёт с ростом index ).
Как сделать план независящим от положения смещения? Использовать вместо смещения выборку из индекса:
SELECT * FROM "table" WHERE "id" > $1 ORDER BY "id" LIMIT 100
В этом случае алгоритм клиента должен поменяться таким образом, чтобы каждой итерации цикла использовать значение index , взятое от предыдущего шага. Перепишем алгоритм:

- Первичная инициализация. index := 0
- Выполняем запрос limit данных, передавая в запрос index
- Вычисляем новое значение index , как максимум от id в ответе
- Обрабатываем данные
- Пауза, соответствующая интервалу
- Перейти к шагу 2
В системе с такой архитектурой, как правило, уже нет существенных препятствий к снижению интервала до минимальных значений (вплоть до нуля).
Но давайте ещё порефлексируем над архитектурой. Что плохого в ней?
- Алгоритм привязан к структуре данных
- Выполняется практически полностью на стороне клиента
- Вследствие предыдущей проблемы сложно, например, централизованно модифицировать его на иную работу после факапов/проблем.
- Пользователь может сам подставлять в index произвольные значения. Иногда это может быть нежелательно или приводить к багам, которые разработчику сервера сложно понять.
Давайте ещё раз модифицируем алгоритм. Заменим index на state и управлять им будем с сервера:

- Первичная инициализация. state := null .
- Выполняем запрос limit данных, передавая в запрос значение state
- В каждом ответе, помимо данных, сервер возвращает new_state . state := new_state
- Обрабатываем данные
- Пауза-интервал
- Перейти к шагу 2
Что мы получили? Гибкость.
- Переменная state определяется только сервером и не обязана быть привязанной к числу смещения. При желании в этой переменной можем хранить JSON со многими полями.
- При желании можем ограничить возможности пользователя «хачить» запросы (использовать другие значения index , помните выше мы об этом говорили?). Этого можно достичь, например криптоподписывая state .
Если в переменной state хранится не только позиция окна, а, например, и значения фильтров и криптоподпись, то эту переменную имеет смысл называть курсором. Переименуем переменную ещё раз и избавимся от постоянных задержек:

- Первичная инициализация. cursor := null, filters = значения_фильтров .
- Выполняем запрос limit данных, передавая в запрос значение cursor , filters .
- В каждом ответе, помимо данных, сервер возвращает cursor . cursor := response.cursor
- Обрабатываем данные
- Если данные были, перейти к шагу 2
- Пауза-интервал
- Перейти к шагу 2
Таким образом, получаем алгоритм, минимизирующий число запросов, если данных для клиента нет, и запрашивающий данные с максимальной производительностью, если таковые имеются.
Рекомендации по работе с курсорами:
- Поскольку хранением курсора между запросами озадачен клиент, то имеет смысл хранить в курсоре и версию ПО сервера. В этом случае можно написать дополнительный код, обеспечивающий обратную совместимость (конвертацию форматов курсоров).
- Во избежание трудных багов весь набор фильтров, полученных в первом запросе, хорошо хранить в курсоре и в последующих запросах игнорировать параметры фильтрации не из курсора. Перфекционисты могут даже выделить инициализацию курсора в отдельный запрос.
- Во избежание введения в соблазн пользователей использовать в своём коде какие-то данные из курсора, лучше не использовать человекочитаемую строку в значении курсора. JSON, пропущенный через base64-кодирование (и криптоподписанный) подходит идеально.
Пример. Изменение алгоритма после сбоя.
Любая система гарантированной доставки сообщений из точки А в точку B в случае факапов будет накапливать пул недоставленных сообщений. После восстановления работоспособности будет период времени, когда приёмник данных сильно отстаёт от источника.
В случае, если порядок доставки сообщений возможно нарушать, то обработчик запроса с курсором может (продетектировав значительное отставание) начать возвращать два потока данных: тот, на который подписан клиент, и тот же, но с более актуальными данными.
То есть, limit делим, например, пополам. Половину limit’а заполняем данными из обычного курсора. А во второй половине начинаем передавать данные, начиная от id , с небольшим отставанием.
Таким образом, пользователи, запросившие отчёт прямо во время факапа, продолжат его ждать (и дождутся). А пользователи, запросившие отчёт после факапа, получат его с небольшой задержкой.
Пример алгоритма серверной стороны, включающего второй поток в случае сильного отставания, приведён на рисунке.

Пофантазировав, схему можно дополнить не одним, а несколькими фолбеками.
Курсорная репликация
Описанные курсоры можно использовать для репликации данных с сервиса на сервис.
Часто один сервис должен иметь у себя кеш/реплику части данных другого сервиса. При этом требований синхронности к этой реплике нет. Поменялись данные в сервисе A. Они должны максимально быстро поменяться и в сервисе B.
Например, мы хотим реплицировать табицу пользователей с сервиса на сервис.
Для такой репликации можно использовать что-то готовое из инструментария баз данных, а можно сделать небольшой «велосипед». Предположим, что пользователи хранятся в БД PostgreSQL. Тогда делаем следующее:
- создаём дополнительный столбик SERIAL/BIGSERIAL в таблице users , назовём его lsn (Last sequence number).
- модифицируем изменяющие пользователей запросы, чтобы на каждое изменение записи пользователя значение lsn устанавливалось бы из растущей последовательности
- строим по полю lsn (уникальный) BTREE индекс.
В этом случае обновление записи пользователя будет выглядеть примерно так:
UPDATE "users" SET "name" = $1, . "lsn" = DEFAULT /* последовательность */ WHERE "user_id" = $21
А запрос для работы курсора будет выглядеть как-то так:
SELECT * FROM "users" WHERE "lsn" > $1 ORDER BY "lsn" LIMIT $2
Каждое обновление пользовательской записи будет перемещать её в конец списка lsn . При этом общий размер отставания никогда не превысит размер таблицы пользователей.
Итоги
- Почти во всех случаях, когда применяется активная система уведомлений зависимых сервисов, её можно заменить описанной курсорной подпиской.
- При этом проблемы доступности клиентов, настроек, работоспособности TCP-стека останутся у клиентов
- Максимально быстрое и простое восстановление после простоя/сбоев. Отсутствие двусмысленностей в кодах ошибок.
- архитектура
- события
- межсерверное взаимодействие
- микросервисы
- push-уведомления
- пулинг
- выбор
Сверточная нейронная сеть: как компьютеры учатся видеть и понимать мир
Сверточная нейронная сеть – мощный инструмент обработки и анализа изображений, который позволяет автоматически выделять и распознавать особенности объектов, обладающий преимуществами высокой эффективности и точности при работе с большими объемами данных.
Сверточная нейронная сеть: как компьютеры учатся видеть и понимать мир обновлено: 2 октября, 2023 автором: Научные Статьи.Ру
Помощь в написании работы
Введение
В данной лекции мы рассмотрим сверточные нейронные сети – мощный инструмент машинного обучения, который широко применяется в обработке изображений, распознавании образов и других задачах компьютерного зрения. Сверточные нейронные сети основаны на идее свертки и пулинга, которые позволяют сети автоматически извлекать признаки из входных данных. В этой лекции мы рассмотрим структуру сверточной нейронной сети, ее основные компоненты, принцип работы, а также преимущества и недостатки данного подхода. Также мы рассмотрим примеры применения сверточных нейронных сетей в различных областях. Давайте начнем!
Нужна помощь в написании работы?
Мы — биржа профессиональных авторов (преподавателей и доцентов вузов). Наша система гарантирует сдачу работы к сроку без плагиата. Правки вносим бесплатно.
Определение сверточной нейронной сети
Сверточная нейронная сеть (Convolutional Neural Network, CNN) – это тип искусственной нейронной сети, который специализируется на обработке и анализе данных с пространственной структурой, таких как изображения или видео. Она является одним из наиболее эффективных и широко используемых методов в области компьютерного зрения и распознавания образов.
Основная идея сверточной нейронной сети заключается в использовании сверточных слоев, которые позволяют автоматически извлекать иерархические признаки из входных данных. Эти слои применяют фильтры (ядра свертки) к входным данным, чтобы выделить локальные особенности и структуры. Затем полученные признаки подаются на следующие слои для дальнейшей обработки и классификации.
Сверточные нейронные сети также включают пулинговые слои, которые уменьшают размерность данных, сохраняя важные признаки. Это позволяет сети быть более устойчивой к масштабированию и сдвигам входных данных.
Одной из ключевых особенностей сверточных нейронных сетей является их способность к автоматическому изучению признаков из больших объемов данных. Это достигается путем обучения сети на размеченных данных, где она сама находит наиболее информативные признаки для решения задачи классификации или распознавания.
Структура сверточной нейронной сети
Сверточная нейронная сеть (Convolutional Neural Network, CNN) состоит из нескольких слоев, каждый из которых выполняет определенные операции над входными данными. Основные слои, которые встречаются в структуре сверточной нейронной сети, включают:
Сверточные слои (Convolutional Layers)
Сверточные слои являются основным строительным блоком сверточной нейронной сети. Они выполняют операцию свертки, которая позволяет сети извлекать локальные признаки из входных данных. Каждый сверточный слой состоит из нескольких фильтров, которые скользят по входным данным и вычисляют свертку. Результатом свертки является карта признаков, которая представляет собой активации фильтров на разных местоположениях входных данных.
Пулинговые слои (Pooling Layers)
Пулинговые слои уменьшают размерность данных, сохраняя важные признаки. Они выполняют операцию пулинга, которая объединяет соседние активации в одну, усредняя или выбирая максимальное значение. Это позволяет сети быть более устойчивой к масштабированию и сдвигам входных данных.
Полносвязные слои (Fully Connected Layers)
Полносвязные слои являются последними слоями в сверточной нейронной сети. Они соединяют все активации предыдущего слоя с каждым нейроном текущего слоя. Полносвязные слои выполняют классификацию или регрессию на основе изученных признаков.
Структура сверточной нейронной сети может включать несколько сверточных слоев, пулинговых слоев и полносвязных слоев, которые последовательно соединяются друг с другом. Количество и размерность слоев зависит от конкретной задачи и размера входных данных.
Основные компоненты сверточной нейронной сети
Сверточная нейронная сеть (СНС) состоит из нескольких основных компонентов, которые выполняют различные функции в процессе обработки данных. Вот основные компоненты сверточной нейронной сети:
Сверточные слои (Convolutional Layers)
Сверточные слои являются основным строительным блоком сверточной нейронной сети. Они выполняют операцию свертки между входными данными и набором фильтров (ядер), чтобы извлечь признаки из изображений или других типов данных. Каждый фильтр применяется к различным областям входных данных, чтобы выделить различные признаки, такие как границы, текстуры или формы. Результатом свертки является карта признаков, которая представляет собой активации фильтров на разных местоположениях входных данных.
Пулинговые слои (Pooling Layers)
Пулинговые слои используются для уменьшения размерности карты признаков, полученной после сверточных слоев. Они выполняют операцию пулинга, которая суммирует или выбирает максимальное значение из небольших областей карты признаков. Это позволяет уменьшить количество параметров и вычислений в сети, а также сделать представление признаков более инвариантным к малым изменениям входных данных.
Полносвязные слои (Fully Connected Layers)
Полносвязные слои являются последними слоями в сверточной нейронной сети. Они соединяют все активации предыдущего слоя с каждым нейроном текущего слоя. Полносвязные слои выполняют классификацию или регрессию на основе изученных признаков.
Структура сверточной нейронной сети может включать несколько сверточных слоев, пулинговых слоев и полносвязных слоев, которые последовательно соединяются друг с другом. Количество и размерность слоев зависит от конкретной задачи и размера входных данных.
Принцип работы сверточной нейронной сети
Сверточная нейронная сеть (СНС) – это тип искусственной нейронной сети, который эффективно обрабатывает данные с пространственной структурой, такими как изображения. Она основана на принципе свертки, который позволяет автоматически извлекать иерархические признаки из входных данных.
Основной принцип работы сверточной нейронной сети состоит из следующих шагов:
Свертка
Первый шаг – это свертка, где входные данные проходят через фильтры (ядра свертки), чтобы выделить различные признаки. Фильтры применяются к различным областям входных данных, чтобы выделить локальные особенности, такие как границы, текстуры и формы. Результатом свертки является карта признаков.
Нелинейность (активация)
После свертки карта признаков проходит через функцию активации, такую как ReLU (Rectified Linear Unit), которая добавляет нелинейность в сеть. Это позволяет модели улавливать более сложные и абстрактные признаки.
Пулинг
После нелинейности следует операция пулинга, которая уменьшает размер карты признаков, сохраняя важные признаки. Обычно используется операция максимального пулинга, где выбирается максимальное значение из каждой области. Это позволяет сети быть инвариантной к небольшим сдвигам искомых признаков.
Полносвязные слои
После нескольких сверточных и пулинговых слоев, данные проходят через полносвязные слои, которые выполняют классификацию или регрессию на основе изученных признаков. Полносвязные слои соединяют все активации предыдущего слоя с каждым нейроном текущего слоя.
Таким образом, сверточная нейронная сеть позволяет автоматически извлекать иерархические признаки из входных данных, что делает ее особенно эффективной для обработки изображений и других данных с пространственной структурой.
Преимущества и недостатки сверточной нейронной сети
Преимущества:
1. Автоматическое извлечение признаков: Сверточные нейронные сети способны автоматически извлекать иерархические признаки из входных данных. Это позволяет сети самостоятельно находить важные характеристики, такие как границы, текстуры и формы, без необходимости ручного определения этих признаков.
2. Способность к обработке пространственной структуры: Сверточные нейронные сети особенно эффективны для обработки данных с пространственной структурой, таких как изображения. Они учитывают локальные зависимости и иерархическую структуру данных, что позволяет им лучше понимать и анализировать такие данные.
3. Устойчивость к вариациям: Сверточные нейронные сети обладают устойчивостью к различным вариациям входных данных, таким как изменение размера, повороты, сдвиги и искажения. Это делает их эффективными для обработки реальных данных, которые могут быть разнообразными и неидеальными.
4. Меньшее количество параметров: Сверточные нейронные сети имеют меньшее количество обучаемых параметров по сравнению с полносвязными нейронными сетями. Это позволяет им быть более эффективными в обучении и использовании ресурсов.
Недостатки:
1. Высокая вычислительная сложность: Обучение и использование сверточных нейронных сетей требует значительных вычислительных ресурсов, особенно при работе с большими наборами данных или сложными моделями. Это может быть проблемой для устройств с ограниченными вычислительными возможностями.
2. Необходимость большого объема данных: Для эффективного обучения сверточных нейронных сетей требуется большой объем размеченных данных. Без достаточного количества данных сеть может не смочь достичь высокой точности и обобщающей способности.
3. Чувствительность к выбору гиперпараметров: Сверточные нейронные сети имеют множество гиперпараметров, таких как количество слоев, размеры фильтров, шаги свертки и т. д. Неправильный выбор этих параметров может привести к плохим результатам или переобучению модели.
4. Отсутствие интерпретируемости: Сверточные нейронные сети являются черными ящиками, то есть их внутренние механизмы и принятие решений не всегда понятны и интерпретируемы для человека. Это может быть проблемой в некоторых областях, где требуется объяснение принятых решений.
Применение сверточной нейронной сети
Сверточные нейронные сети (СНС) широко применяются в различных областях, где требуется анализ и обработка изображений или видео. Вот некоторые из основных областей применения СНС:
Компьютерное зрение
СНС являются основным инструментом в области компьютерного зрения. Они используются для распознавания объектов, классификации изображений, сегментации изображений, детектирования лиц и многих других задач. Например, СНС могут быть использованы для автоматического распознавания номерных знаков на фотографиях или для определения наличия определенных объектов на изображении.
Медицина
СНС применяются в медицине для анализа медицинских изображений, таких как рентгеновские снимки, МРТ и КТ. Они могут помочь в диагностике различных заболеваний, обнаружении опухолей и аномалий, а также в анализе медицинских данных.
Автомобильная промышленность
СНС используются в автомобильной промышленности для различных задач, связанных с автономным вождением. Они могут помочь в распознавании дорожных знаков, обнаружении пешеходов и других транспортных средств, а также в анализе окружающей среды для принятия решений в реальном времени.
Робототехника
СНС применяются в робототехнике для обработки и анализа данных с камер и других сенсоров. Они могут помочь роботам в навигации, распознавании объектов, планировании движения и выполнении различных задач.
Нейрокомпьютерные интерфейсы
СНС могут быть использованы в нейрокомпьютерных интерфейсах для анализа и интерпретации сигналов мозга. Они могут помочь людям с ограниченными возможностями в управлении компьютером или другими устройствами только с помощью мыслей.
Это лишь некоторые из областей, где сверточные нейронные сети находят свое применение. СНС продолжают развиваться и находить новые применения в различных сферах жизни.
Таблица сравнения сверточных нейронных сетей
| Характеристика | Сверточная нейронная сеть | Обычная нейронная сеть |
|---|---|---|
| Структура | Состоит из сверточных слоев, пулинг слоев и полносвязных слоев | Состоит из входного слоя, скрытых слоев и выходного слоя |
| Архитектура | Локальные связи и разделяемые веса | Полносвязные связи и независимые веса |
| Обработка данных | Использует операцию свертки для извлечения признаков | Использует линейные комбинации для обработки данных |
| Распознавание образов | Эффективно распознает образы с инвариантностью к сдвигам и искажениям | Менее эффективно распознает образы с инвариантностью |
| Применение | Часто используется в компьютерном зрении и распознавании образов | Широко применяется в различных задачах машинного обучения |
Заключение
Сверточная нейронная сеть – это мощный инструмент в области обработки и анализа изображений. Она обладает специальной структурой и компонентами, которые позволяют ей эффективно распознавать и классифицировать объекты на изображениях. Сверточные нейронные сети широко применяются в различных областях, таких как компьютерное зрение, распознавание речи, медицинская диагностика и другие. Они имеют свои преимущества и недостатки, но их эффективность и точность делают их незаменимыми инструментами в современном мире.
Сверточная нейронная сеть: как компьютеры учатся видеть и понимать мир обновлено: 2 октября, 2023 автором: Научные Статьи.Ру
Сверточные нейронные сети: основы и принцип работы
Что это? Сверточные нейронные сети (Convolutional Neural Networks, CNN) – класс алгоритмов машинного обучения. С их помощью удается достичь впечатляющих результатов в области распознавания образов, классификации изображений, а также обработки и анализа видеоданных.
Как влияют? Сверточные нейронные сети разработаны специально для работы с данными, имеющими пространственную или растровую структуру, такими как изображения. Отдельно стоит отметить их способность к автоматическому изучению признаков. Вместо ручного выбора сверточные нейронные сети используют обратное распространение ошибки и градиентный спуск для настройки весовых коэффициентов.
В статье рассказывается:
- Что собой представляют сверточные нейронные сети
- Структура свёрточной нейронной сети
- Где используются сверточные нейронные сети
- Часто задаваемые вопросы о сверточных нейронных сетях
Пройди тест и узнай, какая сфера тебе подходит:
айти, дизайн или маркетинг.
Бесплатно от Geekbrains
Что собой представляют сверточные нейронные сети
Сверточной называют вид нейронной сети, специализирующейся на обработке изображений и видео. Такие сети подходят для улавливания локального контекста, когда поток информации в пространстве не прерывается, т.е. ее носители находятся в непосредственной близости друг от друга.
Например, на изображении пиксели расположены рядом и содержат такие визуальные данные, как яркость и цвет. Если нейронная сеть видит кошку в одном пикселе на фотографии или рисунке, это означает, что она видит ее и в соседних пикселях.
Зачастую сверточные нейронные сети используются для решения двух задач: распознавания и классификации изображений. Так, они могут быть использованы на изображении кошки для определения ее цвета по большим фрагментам изображения или для определения оттенка ее глаз по маленьким фрагментам, а также для различения собак и кошек.
AlexNet является первой глубокой сверточной нейронной сетью и основой современной сетевой архитектуры. Ее разработчики победили в конкурсе LSVRC-2012 по классификации изображений с использованием набора данных ImageNet.
Существует несколько наборов данных (датасетов), на которых новички, используя сверточные нейронные сети, могут обучаться решению задач по классификации. К примеру, MNIST является базой для начала изучения. Вы можете найти этот инструмент в наборе Google Colab. Так, в течение получаса можно попытаться обучить нейронную сеть распознавать рукописные цифры.
Узнай, какие ИТ — профессии
входят в ТОП-30 с доходом
от 210 000 ₽/мес
Павел Симонов
Исполнительный директор Geekbrains
Команда GeekBrains совместно с международными специалистами по развитию карьеры подготовили материалы, которые помогут вам начать путь к профессии мечты.
Подборка содержит только самые востребованные и высокооплачиваемые специальности и направления в IT-сфере. 86% наших учеников с помощью данных материалов определились с карьерной целью на ближайшее будущее!
Скачивайте и используйте уже сегодня:

Павел Симонов
Исполнительный директор Geekbrains
Топ-30 самых востребованных и высокооплачиваемых профессий 2023
Поможет разобраться в актуальной ситуации на рынке труда
Подборка 50+ бесплатных нейросетей для упрощения работы и увеличения заработка
Только проверенные нейросети с доступом из России и свободным использованием
ТОП-100 площадок для поиска работы от GeekBrains
Список проверенных ресурсов реальных вакансий с доходом от 210 000 ₽
Получить подборку бесплатно
Уже скачали 25504
Другим вариантом является ImageNet. Этот набор данных содержит 1000 различных классов, включая кошек и собак, а также, например, вишню, дерево и фотокамеру.
Как работают сверточные нейронные сети? Аналогично полносвязной нейронной сети, сверточная в своем обучении использует алгоритм обратного распространения. Вначале происходит прямое распространение от первого слоя к последнему, затем вычисляется ошибка в выходном слое и осуществляется обратное распространение. При этом в каждом слое вычисляются градиенты обучаемых параметров, а в конце обратного распространения происходит обновление весов с помощью градиентного спуска.
Структура свёрточной нейронной сети
По своей структуре сверточные нейронные сети – это воронка: все начинается с картины в целом, затем внимание переключается на отдельные детали. Мозг работает точно так же: человек на улице видит сначала кошку и лишь потом начинает рассматривать цвет ее шерсти и глаз. Это называется репрезентативным обучением (обучение представлениям).
В состав сверточной нейронной сети входит несколько слоев. От количества слоев зависит мощность архитектуры и эффективность обучения. Вот схема основных составляющих свёрточной нейронной сети:
- сверточный слой;
- пулинг;
- нормализация по батчу;
- полносвязный слой.
Для того чтобы нейронная сеть смогла распознать кошку, необходимо выполнить несколько типичных операций на каждом слое изображения. Ключевым моментом в этих операциях является свертка.
Для вас подарок! В свободном доступе до 14.01 —>
Скачайте ТОП-10 нейросетей, которые помогут облегчить
вашу работу
Чтобы получить подарок, заполните информацию в открывшемся окне
В процессе свертки нейронная сеть удаляет ненужное и оставляет полезное, т.е. то, что нужно для анализа изображения. В качестве примера можно привести линии, края и плоские области. Свертка может быть создана для каждого признака. Нейронная сеть сама подбирает их, когда выполняет распознавание и классификацию в каждом слое свертки.
Следующим после слоя свертки идет слой пулинга. В нем из признаков, отобранных слоем свертки, выбираются наиболее важные и удаляются несущественные. Слой свертки может быть снова применен к результатам, полученным в процессе пулинга, и повторен несколько раз. Это необходимо для построения иерархии признаков, начиная с самых примитивных (фрагменты контуров) и заканчивая сложными (кошачьи глаза или форма ушей).
Задача первых слоев сверточной нейронной сети – анализ мельчайших элементов изображения (ворсинки, трещинки и т.д.), размер которого может составлять 2 x 2 или 3 x 3 пикселя. Такой маленький формат не позволяет определить глаза или уши кошки или деревья, на которых она сидит. Однако можно найти различия в цвете и свете – грани между различными объектами. Для следующих слоев характерны сложные объекты, такие как круги и другие фигуры.
Чем глубже уровень слоя сети, тем более сложные признаки она позволяет извлечь. На последнем слое нейронная сеть выделяет признаки, связывает их с конкретными картинками и пытается определить, что на них изображено.
Полученные в последнем слое комплексные признаки классифицируются нейронной сетью с использованием полного связывания для поиска ответа на вопрос, чье изображение находится на картинке – совы или кошки.
В большинстве случаев нейронные сети обучаются на цветных изображениях RGB. Передача пикселями яркости происходит по трем каналам: красному, зеленому и синему. Различные комбинации этих цветов дают любой цвет из спектра.
Сверточной нейронной сетью изображения классифицируются представляются по-особому – как трехмерные массивы чисел или матриц. В математике они носят название «тензоры». RGB цвета кодируются как три целых числа в диапазоне от 0 до 255. Все пиксели в матричном массиве представлены в виде чисел, отражающих яркость в этом диапазоне.
Где используются сверточные нейронные сети
В 2010-е годы искусственные сверточные нейронные сети имели широкое использование в медицине и государственных учреждениях при:
- распознавании рукописного текста;
- классификации документов, таких как СНИЛС или паспорт;
- распознавании на снимках новообразований и иных патологий.
В настоящее время сверточные нейронные сети применяются в системах видеонаблюдения, видеоаналитики и беспилотных автомобилях. Например, камеры в комплексе с такими сетями помогают роботам компании «Яндекс» и беспилотным такси понимать, что происходит на дороге.
Другой популярной областью применения принципа работы сверточных нейронных сетей является генеративное искусство. Когда человек вводит запрос, нейронная сеть генерирует на его основе изображение. Преобразование текста является задачей различных алгоритмов, а генерация изображения – сверточных нейронных сетей. Наиболее распространенными моделями для генерации изображений являются такие сверточные нейронные сети, как Midjourney, Stable Diffusion, Dream, DALL-E 2 и ruDALL-E.
Свёрточная нейронная сеть с нуля. Часть 2. Слой подвыборки

В прошлой статье мы создали свёрточный слой, научились считать градиенты при обратном распространении ошибки и обновлять весовые коэффициенты для уменьшения функции потерь. Сегодня мы разберёмся с тем, как уменьшается пространственная размерность входной карты признаков и как рассчитываются градиенты слоя пулинга. Помимо этого мы рассмотрим различные типы пулинга и решим, в какой ситуации лучше использовать каждый из них. Но для начала, давайте разберёмся, как вообще работает слой пулинга и какие его версии существуют.
Зачем нужен пулинг
Основной задачей слоя подвыборки является снижение размерности входного тензора, что позволяет сократить количество дальнейших вычислений. Помимо этого, подобное уменьшение размерности позволяет сети быть более устойчивой к небольшим сдвигам объектов на изображении.
Какой бывает пулинг
Способов уменьшить размерность пространства карт особенностей на самом деле может быть весьма много: можно выбирать максимальные значения (макспулинг), можно усреднять значения (средний пулинг), брать сумму (пулинг суммы) или вовсе взять минимум. Чаще всего в свёрточных сетях в слое подвыборки используется макспулинг, как хорошо зарекомендовавший себя в многочисленных исследованиях. Чтобы лучше понимать, как влияет на выходное изображение различные типы пулинга, мы предлагаем самостоятельно выбрать тип подвыборки в интерактивном примере ниже:
Тип: Коэффициент:
Если же пример с изображением кажется слишком неочевидным, то, надеемся, расположенная ниже гифка поможет разобраться с работой слоя подвыборки:
Различные типы пулинга
Пишем слой пулинга
Мы подробно опишем только класс макспулинга, поскольку другие типы уменьшения пространственной размерности работают полностью аналогично, и вам не составит труда создать их самостоятельно. Как было сказано выше, слой подвыборки уменьшает пространственную размерность входных карт признаков в несколько раз (обычно в 2), а значит нам потребуется хранить в классе коэффициент уменьшения. Создадим заготовку для класса макспулинга:
class MaxPoolingLayer < TensorSize inputSize; // размер входа TensorSize outputSize; // размер выхода int scale; // во сколько раз уменьшается размерность public: MaxPoolingLayer(TensorSize size, int scale = 2); // создание слоя Tensor Forward(const Tensor &X); // прямое распространение Tensor Backward(const Tensor &dout, const Tensor &X); // обратное распространение >;
Как и свёрточный слой, слой макспулинга создаётся крайне просто: нужно всего лишь запомнить входной размер и коэффициент уменьшения размерности, а затем расчитать размер выходного тензора:
MaxPoolingLayer::MaxPoolingLayer(TensorSize size, int scale) < // запоминаем входной размер inputSize.width = size.width; inputSize.height = size.height; inputSize.depth = size.depth; // вычисляем выходной размер outputSize.width = size.width / scale; outputSize.height = size.height / scale; outputSize.depth = size.depth; this->scale = scale; // запоминаем коэффициент уменьшения >
Прямое распространение сигнала
Описав создание слоя, перейдём к написанию прямого распространения. Для получения выходного тензора необходимо пройтись по всем подматрицам размера scale x scale входного объёма, найти максимум в них и записать его в выходной тензор:
// прямое распространение Tensor MaxPoolingLayer::Forward(const Tensor &X) < Tensor output(outputSize); // создаём выходной тензор // проходимся по каждому из каналов for (int d = 0; d < inputSize.depth; d++) < for (int i = 0; i < inputSize.height; i += scale) < for (int j = 0; j < inputSize.width; j += scale) < double max = X(d, i, j); // начальное значение максимума - значение первой клетки подматрицы // проходимся по подматрице и ищем максимум и его координаты for (int y = i; y < i + scale; y++) < for (int x = j; x < j + scale; x++) < double value = X(d, y, x); // получаем значение входного тензора // если очередное значение больше максимального if (value >max) max = value; // обновляем максимум > > output(d, i / scale, j / scale) = max; // записываем в выходной тензор найденный максимум > > > return output; // возвращаем выходной тензор >
Обратное распространение ошибки
Обратное распространение в слое макс пулинга производится довольно просто: так как при прямом проходе на выход пошёл лишь один элемент, то при обратном распространении полученное значение градиента следующего слоя нужно записать в место расположения максимального элемента. Получается, нам нужно знать, в каком месте находился максимальный элемент. Есть несколько способов сделать это: можно сравнивать полученные значения при прямом распространении со значениями на входе, а можно создать бинарную маску, где каждый элемент будет равен единице, если в заданной клетке расположен максимум, и нулю в противном случае. Данную маску можно посчитать при прямом проходе, а при обратном распространении ошибки умножить её на градиенты следующего слоя.
Поскольку каждый канал обрабатывается независимо, то и масок у нас должно получиться столько же, сколько и каналов во входном тензоре. Поэтому добавим в наш класс пулинга тензор mask и при создании класса зададим создадим его по размеру входной карты признаков. Метод прямого распространения будет слегка модифицирован:
// прямое распространение с использованием маски Tensor MaxPoolingLayer::Forward(const Tensor &X) < Tensor output(outputSize); // создаём выходной тензор // проходимся по каждому из каналов for (int d = 0; d < inputSize.depth; d++) < for (int i = 0; i < inputSize.height; i += scale) < for (int j = 0; j < inputSize.width; j += scale) < int imax = i; // индекс строки максимума int jmax = j; // индекс столбца максимума double max = X(d, i, j); // начальное значение максимума - значение первой клетки подматрицы // проходимся по подматрице и ищем максимум и его координаты for (int y = i; y < i + scale; y++) < for (int x = j; x < j + scale; x++) < double value = X(d, y, x); // получаем значение входного тензора mask(d, y, x) = 0; // обнуляем маску // если входное значение больше максимального if (value >max) < max = value; // обновляем максимум imax = y; // обновляем индекс строки максимума jmax = x; // обновляем индекс столбца максимума >> > output(d, i / scale, j / scale) = max; // записываем в выходной тензор найденный максимум mask(d, imax, jmax) = 1; // записываем 1 в маску в месте расположения максимального элемента > > > return output; // возвращаем выходной тензор >
Как мы выяснили выше, обратное распространение ошибки для слоя макспулинга сводится к умножению градиентов следующего слоя на рассчитанную маску. Обратное распространение ошибки будет выглядеть следующим образом:
// обратное распространение Tensor MaxPoolingLayer::Backward(const Tensor &dout, const Tensor &X) < Tensor dX(inputSize); // создаём тензор для градиентов for (int d = 0; d < inputSize.depth; d++) for (int i = 0; i < inputSize.height; i++) for (int j = 0; j < inputSize.width; j++) dX(d, i, j) = dout(d, i / scale, j / scale) * mask(d, i, j); // умножаем градиенты на маску return dX; // возвращаем посчитанные градиенты >
Заметим, что для пулинга среднего и пулинга суммы не требуется добавление бинарной маски, так как в них при обратном распространении градиенты следующего слоя просто копируются во все элементы соответствующих подматриц (для среднего градиенты делятся на общее количество элементов подматрицы).
Полный код слоя
#include "Tensor.hpp" class MaxPoolingLayer < TensorSize inputSize; // размер входа TensorSize outputSize; // размер выхода int scale; // во сколько раз уменьшается размерность Tensor mask; // маска для максимумов public: MaxPoolingLayer(TensorSize size, int scale = 2); // создание слоя Tensor Forward(const Tensor &X); // прямое распространение Tensor Backward(const Tensor &dout, const Tensor &X); // обратное распространение >; // создание слоя MaxPoolingLayer::MaxPoolingLayer(TensorSize size, int scale) : mask(size) < // запоминаем входной размер inputSize.width = size.width; inputSize.height = size.height; inputSize.depth = size.depth; // вычисляем выходной размер outputSize.width = size.width / scale; outputSize.height = size.height / scale; outputSize.depth = size.depth; this->scale = scale; // запоминаем коэффициент уменьшения > // прямое распространение с использованием маски Tensor MaxPoolingLayer::Forward(const Tensor &X) < Tensor output(outputSize); // создаём выходной тензор // проходимся по каждому из каналов for (int d = 0; d < inputSize.depth; d++) < for (int i = 0; i < inputSize.height; i += scale) < for (int j = 0; j < inputSize.width; j += scale) < int imax = i; // индекс строки максимума int jmax = j; // индекс столбца максимума double max = X(d, i, j); // начальное значение максимума - значение первой клетки подматрицы // проходимся по подматрице и ищем максимум и его координаты for (int y = i; y < i + scale; y++) < for (int x = j; x < j + scale; x++) < double value = X(d, y, x); // получаем значение входного тензора mask(d, y, x) = 0; // обнуляем маску // если входное значение больше максимального if (value >max) < max = value; // обновляем максимум imax = y; // обновляем индекс строки максимума jmax = x; // обновляем индекс столбца максимума >> > output(d, i / scale, j / scale) = max; // записываем в выходной тензор найденный максимум mask(d, imax, jmax) = 1; // записываем 1 в маску в месте расположения максимального элемента > > > return output; // возвращаем выходной тензор > // обратное распространение Tensor MaxPoolingLayer::Backward(const Tensor &dout, const Tensor &X) < Tensor dX(inputSize); // создаём тензор для градиентов for (int d = 0; d < inputSize.depth; d++) for (int i = 0; i < inputSize.height; i++) for (int j = 0; j < inputSize.width; j++) dX(d, i, j) = dout(d, i / scale, j / scale) * mask(d, i, j); // умножаем градиенты на маску return dX; // возвращаем посчитанные градиенты >
Итоги
Мы рассмотрели принципы работы различных типов пулинга и реализовали класс для макспулинга с возможностью задавать коэффициент уменьшения размерности. Теперь в нашем распоряжении уже два основных слоя из четырёх. В следующей части мы расскажем, как создать слой активации, а также рассмотрим наиболее часто используемые активационные функции.

Программист, сооснователь programforyou.ru, в постоянном поиске новых задач и алгоритмов
Языки программирования: Python, C, C++, Pascal, C#, Javascript
Выпускник МГУ им. М.В. Ломоносова
Programforyou — это сообщество, в котором Вы можете подтянуть свои знания по программированию, узнать, как эффективно решать те или иные задачи, а также воспользоваться нашими онлайн сервисами.
Copyright © 2017 — 2023 Programforyou — помощь с программированием | programforyou.ru