惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Fortinet All Blogs
C
Check Point Blog
GbyAI
GbyAI
博客园 - 司徒正美
爱范儿
爱范儿
N
Netflix TechBlog - Medium
H
Hacker News: Front Page
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Security Latest
Security Latest
C
Cyber Attacks, Cyber Crime and Cyber Security
博客园 - Franky
Recent Announcements
Recent Announcements
P
Privacy International News Feed
T
Tor Project blog
Y
Y Combinator Blog
有赞技术团队
有赞技术团队
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
G
GRAHAM CLULEY
The Hacker News
The Hacker News
N
News and Events Feed by Topic
I
Intezer
The GitHub Blog
The GitHub Blog
S
SegmentFault 最新的问题
T
The Blog of Author Tim Ferriss
PCI Perspectives
PCI Perspectives
S
Secure Thoughts
P
Proofpoint News Feed
Microsoft Security Blog
Microsoft Security Blog
IT之家
IT之家
T
Threat Research - Cisco Blogs
J
Java Code Geeks
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
D
DataBreaches.Net
Hacker News - Newest:
Hacker News - Newest: "LLM"
Last Week in AI
Last Week in AI
H
Help Net Security
L
LangChain Blog
大猫的无限游戏
大猫的无限游戏
Help Net Security
Help Net Security
S
Schneier on Security
T
The Exploit Database - CXSecurity.com
Google Online Security Blog
Google Online Security Blog
Cyberwarzone
Cyberwarzone
T
Tailwind CSS Blog
V
Vulnerabilities – Threatpost
Forbes - Security
Forbes - Security
Apple Machine Learning Research
Apple Machine Learning Research
O
OpenAI News
AWS News Blog
AWS News Blog
月光博客
月光博客

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Основы глубокого обучения. Часть 1
PXI · 2026-05-13 · via Все публикации подряд на Хабре

🙂 Приветствую всех! Эта статья будет первой в серии статей про основы глубокого обучения. В этой части я расскажу про то, что такое модели, искусственный интеллект (ИИ), машинное (МО) и глубокое обучение (ГО), про виды этапа обучения моделей, что такое нейронные сети, градиентный спуск и обратное распространение. В конце затронем теорию свёрточных нейронных сетей.

Модель — это функция, закодированная в виде программы (по сути слова синонимы).

Искусственный интеллект — это раздел компьютерных наук, связанный с исследованиями в разных областях, включая доказательства математических теорем, анализ естественного языка, игры, компьютерные программы, способные обучаться на примерах, и нейронные сети. Он включает в себя все направления связанные и с МО, и с ГО, и с другими областями.

Машинное обучение — это одно из направлений ИИ, связанное с разработкой и оцениванием алгоритмов, которые позволяют извлекать функции из набора данных (то есть учатся на этих примерах, раскрытие предсказательной способности компьютеров).

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

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

После определения понятий перейдем к процессу машинного обучения.

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

Сейчас я сосредоточусь на этапе обучения модели.

Здесь есть две проблемы: переобучение и недообучение.

  • Переобучение встречается, когда используется слишком малый индуктивный сдвиг (предложение о функции, то есть даём большую свободу в выборе функции нашему алгоритму МО или нейронной сети , чтобы максимально соответствовать набору данных и больше акцентирует внимание на наборе данных). Из-за переобучения модель слишком сильно акцентирует на данный набор данных, может захватывать шумы данных, ошибки в данных, а также получиться слишком сложной.

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

МО имеет три категории в зависимости от набора данных, функций-кандидатов и функции приспособленности или пригодности модели: обучение с учителем, обучение без учителя и обучение с подкреплением.

  • Обучение с учителем самый распространенный. Он подразумевает, что набор данных имеет не только входные признаки, но и целевой параметр (то есть и входы и выход) для сравнения точности модели. Алгоритм МО будет сравнивать результат своей работы функции с целевым признаком и менять свои веса, если нужно для лучшей приспособленности модели.

  • МО без учителя подходит для кластеризации данных (объединение образцов из набора данных в группы по схожим признакам). Здесь набор данных не имеет целевого выходного признака. Модель сама учиться составлять образцы в правильные группы по схожим признакам. А в качестве функции приспособленности может выступать функция, которая отдает предпочтение кандидатам, которые достигает высокого сходства внутри кластера.

  • МО с подкреплением используется там, где невозможно или очень затратно составить набор данных (например, научить модель играть в змейку). Тогда модель учится непосредственно в среде. Для обучения и оценки приспособленности могут выступать очки(подкрепление).

Что такое нейронные сети?

Если говорить простым языком, то нейронные сети (сети глубокого обучения) — это вычислительная математическая модель (или если более понятно, то функция), имеющая отдаленное сходство со структурой головного мозга.

Визуальное представление структуры нейронной сети

Визуальное представление структуры нейронной сети

Самым простым примером модели является линейная модель, где функция есть взвешенная сумма весов и входных переменных + сдвиг (меняя веса мы можем получить нужный нам результат, также сдвиг можно внести в взвешенную сумму, приняв её вес равным 1, так мы позволим модели самой подобрать сдвиг).

Взвешенная сумма весов и входных переменных — это умножение входных значений на их веса с последующим сложением (то есть умножение вектора-строки выходов одного слоя на матрицу весов следующего слоя).

Что из себя представляет семейство моделей нейронных сетей (или просто нейронные сети)?

Нейронная сеть имитирует работу мозга (нейрон имеет несколько дендритов, тело и выход — аксон) и состоит из множества простых элементов обработки информации, которые называются нейронами.

Принято считать, что нейроны организованы в виде слоёв, на рисунке выше их четыре: один входной, два скрытых и один выходной.

Сети глубокого обучения — это нейронные сети со множеством скрытых слоёв. Чтобы сеть считалась глубокой скрытых слоёв должно быть не меньше двух.

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

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

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

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

Функция активации нужна для формирования не только линейных, но и нелинейных моделей. Обычно в одном слое все нейроны имеют одинаковую функцию активации.

Также нейрон с определенной функцией активацией называется модулем, например, нейрон с выпрямляющей функцией активации называют выпрямляющим модулем (ReLU).

Что такое градиентный спуск?

Градиентный спуск — алгоритм оптимизации для поиска функций с минимальным отклонением при моделировании закономерностей в наборе данных. Именно благодаря нему можно подобрать сочетание весов, которое минимизирует погрешность вывода нейрона. Он часто используется не самостоятельно, а вместе с обратном распространением (про него ниже).

Отклонение (погрешность) модели можно считать по-разному. Я буду использовать формулу среднеквадратической ошибки (SSE):

SSE = 0.5 \cdot \sum_{i=1}^{n} (y_i - \hat{y}_i)^2

Где сумма по всем целевым значениям из образцов, вычитая целевое значение, которое предсказала модель.

Куда спускается градиентный спуск?

Чтобы лучше понять эту идею я начну издалека. Пусть у нас есть один нейрон и два входных параметра (признака), обозначим веса для этих параметров w1, w2. Тогда мы можем нарисовать график зависимости SSE от w1 и w2. У этого графика будет точка минимума, указывающая на нужный нам вес модели. В простейшем случае мы инициализируем модель любым весом и считаем градиент функции SSE в этой точке. Напомню, градиент — это скорость изменения функции для заданного ввода, то есть вектор из частных производных по всем параметрам в определенной точке функции. Если градиент большой, то надо двигаться в противоположную от него сторону и с большим шагом, иначе с меньшим шагом.

Теперь давайте в нашей формуле для SSE заменим обозначение предсказанного целевого признака (ŷ) на реальную функцию, которую у нас реализует нейрон. Тогда:

SSE = \frac{1}{2} \sum_{i=1}^{n} \bigl( y_i - (w_1 x_{1i} + w_2 x_{2i}) \bigr)^2

Где сумма по всем образцам.

Или можем обобщить её для m входных параметров:

SSE = \frac{1}{2} \sum_{i=1}^{n} \left( y_i - \sum_{j=1}^{m} x_{ij} w_j \right)^2

Где первая сумма (которая в скобках) по всем признакам в образце, а вторая по всем образцам

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

\frac{\partial SSE}{\partial w_i} = -\sum_{j=1}^{n} (y_j - \hat{y}_j) \, x_{ji}

Где сумма по всем образцам и только по признаку i-тому, так как смотрим отклонение модели относительно веса для i-того параметра

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

На эту формулу можно посмотреть следующим образом: если при изменении веса существенно меняется вывод взвешенной суммы, градиент ошибок по отношению весу будет большим (резким), так как изменение веса сильно влияет на отклонение. Однако этот градиент является восходящим,а мы хотим двигаться вниз по кривой ошибок.

Поэтому в правиле обновления весов методом градиентного спуска знак «-» перед вводом хi опускается. Это правило можно определить так:

w_i = w_i + h \sum_{j=1}^{n} (y_j - \hat{y}_j) \, x_{ji}

Где сумма по всем образцам и значение х именно i-того признака, h - гиперпараметр (то есть выбирается вручную).

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

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

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

Пусть w0 — это сдвиг нейрона, а остальные веса относятся к другим его входным значениям. Вычисление частной производной SSE зависит от структуры функции, которая генерирует ŷ. Чем сложнее эта функция, тем сложнее становится частная производная.

Наличие функции активации добавляет нее еще один член — частную производную функции активации по выводу функции взвешенной суммы. Это вызвано тем, что данный вывод подаётся на вход функции активации. Изменения веса влияют на ее вывод опосредованно, через обновление взвешенной суммы.

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

Правило для логистической функции:

w_i = w_i + h \cdot \sum(y - \hat{y}) \cdot \hat{y}(1 - \hat{y}) \cdot x_i

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

Начну с пояснения задачи

Представьте, что мы в другой стране, где светофор имеет 3 индикатора. Понаблюдав за ними мы вычленили 6 образцов и составили размеченный обучающий набор данных для нашей модели из 5, а 6 оставили на тестирование модели.

import numpy as np

input_data = np.array([
    [1,0,1],
    [0,1,1],
    [0,0,1],
    [1,1,1],
    [0,1,1],
])

goal_pred = np.array([0,1,0,1,1])
weight = np.array([0.1, 0.1, 0.1]) # лучше начинать не с полных нулей
alpha = 0.1

Здесь матрица input_data содержит индикаторы светофора (в одной строке один образец), а вектор goal_pred содержит соответствующие этим столбцам правильные решения. Альфа-коэффициент выбираете на свой вкус (это тот самый h из формул выше, просто он имеет разные названия).

Создадим наш нейрон

def neiron(input_row, weight):
    return input_row.dot(weight)

Да, всё настолько просто! Если вы помните из прошлых постов, то каждый нейрон считает взвешенную сумму и применяет функцию активации, у нас пример простой и функцию активации можно убрать. Функция dot считает скалярное произведение векторов.

Реализуем градиентный спуск

for i in range(40):
    total_error = 0
    for z in range(len(input_data)):
        pred = neiron(input_data[z], weight)
        error = pred - goal_pred[z]
        sse = error**2
        total_error += sse
        
        weight = weight - (alpha * (input_data[z] * error))
        
    if i % 10 == 0:
        print(f"Итерация номер {i}, Суммарная ошибка: {total_error}")

print("\nИтоговые веса:", weight)

Здесь мы используем стохастический градиентный спуск, а не полный как я давал в посте раньше. Разница лишь в том, что тут мы ошибку считаем и обновляем веса после каждого образца, а в полном после всех образцов из набора данных. Обратите внимание на знак (к весу прибавляем или от него вычитаем), он зависит от того, как считалась чистая ошибка error. Так же обычно в реализациях деление на два при подсчёте среднеквадратичной ошибки опускают, так при вычислении производной всё равно сокращается (0.5 * 2).

Как поведёт себя наша модель из одного нейрона на новом образце?

#У меня остался ещё один набор на котором модель не обучалась на [1, 0, 0] должна выдать 0
print("Проверка модели на наборе 1,0,0 где ответ верный 0")
pred = neiron(np.array([1,0,0]),weight)
a = 0 if pred < 0.01 else 1 # по сути тут мы применили пороговую функцию активации
print("Модель дала ответ: " + str(a) )
Результат нашей модели на тестовом образце

Результат нашей модели на тестовом образце

Как вы можете видеть на скриншоте выше, наша модель справилась на отлично!

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

В чем суть обратного распространения?

При прямом проходе (от входа до получения результата) мы записываем взвешенную сумму вводов и выход каждого нейрона.

Мы это делаем для того, чтобы смогли просчитать градиент ошибки для нейронов (дельта, насколько сильно меняется отклонение сети относительно взвешенной суммы нейрона) всех слоёв при обратном проходе (от выходного ко входному слою). Главная проблема тут в том, что градиент ошибок уменьшается с каждым слоем.

На основе значения градиента нейронов и алгоритма обновления весов (пусть градиентный спуск) считаем градиенты ошибок для весов сети, затем обновляем веса. Теперь рассмотрим подробнее.

Передача дельт в обратном направлении

Напомню, что дельта у нас — это скорость изменения отклонения сети относительно взвешенной суммы какого-то (пусть далее будет k-того) нейрона:

δ = dE / dz = dE / da ⋅ da / dz

Где здесь и далее E — отклонение сети, а — вывод нейрона, z — взвешенная сумма вводов нейрона

Для выходного слоя справедливо: dE/da = t - a, где t — ожидаемый вывод

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

dE / da = ∑ w ⋅ δ

Где сумма по всем нейронам следующего слоя.

da/dz вычисляется проще, но она включает в себя производную функции активации, поэтому и используют функции различные, одни проще дифференцируются, а другие не так сильно уменьшают нашу дельту.

Обновление весов

Основной принцип состоит в том, что вес обновляется прямо пропорционально чувствительности отклонения сети относительно этого веса. Возьмём вес между нейроном j и k, частная производная отклонения сети будет: dE/dw = dz/dw × dE/dz

Понятнее будет объяснять на рисунке выше. Начинаем с вывода всей сети, вычисляем дельту для нейрона l по формуле выше, далее умножаем на производную dz/dw (она всегда константа, так как входит только в одну сумму вес) и получаем градиент для этого веса. Далее считаем дельту уже для нейрона k (по формуле выше):

δₖ = daₖ / dzₖ ⋅ (w(k,l) ⋅ δₗ)

Умножая на dzk/dwj,k (то есть на aj), получаем градиент для нашего веса wj,k.

Для градиентного спуска правило обновления будет таким:

w(j,k) = w(j,k) + h ⋅ δₖ ⋅ aⱼ

Теперь мы с вами создадим простую двухслойную нейронную сеть (3 нейрона во входном, 3 в скрытом и 1 в выходном слое), обновлять веса будем с помощью стохастического градиентного спуска.

Начнём с задания весов и входных данных

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

import numpy as np

data = np.array([
    [1,0,1],
    [1,1,1],
    [0,0,1],
    [0,1,1]
])

def relu(x):
    return (x > 0) * x

def relu2deriv(output):
    return output > 0

alpha = 0.2
size = 3 #количество нейронов в нашем скрытом слое
real_pred = np.array([1,1,0,0])
weight_1 = 2 * np.random.random((3, size)) - 1
weight_2 = 2 * np.random.random((size)) - 1 

Важно, что random.random заполняет матрицу значениями от 0 до 1, я же увеличил этот диапазон до 2, а затем центрировал. В итоге матрицу заполнило числами в диапазоне [-1,+1].

Далее реализуем обратное распространение и градиентный спуск

for i in range(60):
    layer_2_sse = 0
    for z in range(len(data)):
        layer_0 = data[z]
        layer_1 = relu(np.dot(layer_0, weight_1))
        layer_2 = np.dot(layer_1, weight_2)
        layer_2_error = layer_2 - real_pred[z]
        layer_2_sse += 0.5*(layer_2_error ** 2)
        layer_2_delta = layer_2_error
        layer_1_delta = layer_2_delta * weight_2 * relu2deriv(layer_1)
        weight_2 -= alpha * layer_1 * layer_2_delta
        weight_1 -= alpha * np.outer(layer_0, layer_1_delta)
    if i % 9 == 0:
        print(f"Iteration: {i} SSE: {layer_2_sse}")

Тут всё достаточно очевидно, используются формулы, которые я объяснял в посте про обратное распространение. Обратите внимание только на знак, тут используется минус, так как ошибка = предсказанное значение - реальное значение. А в посте было наоборот.

Протестируем нашу модель на новом примере

print("Тестируем на новом наборе [1,0,0], где модель должна выдать 1")
ans = relu(np.array([1,0,0]).dot(weight_1)).dot(weight_2)
print(f"Модель дала ответ:  {1 if ans > 0.7 else 0}")
Тестирование модели на новом образце

Тестирование модели на новом образце

Из скриншота видно, что модель справилась.

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

Почему возникает переобучение?

Начнём с повторения, что такое переобучение и когда встречается.

Переобучение встречается, когда используется слишком малый индуктивный сдвиг (предложение о функции, то есть даём большую свободу в выборе функции нашему алгоритму МО или нейронной сети , чтобы максимально соответствовать набору данных и больше акцентирует внимание на наборе данных). Из-за переобучения модель слишком сильно акцентирует на данный набор данных, может захватывать шумы данных, ошибки в данных, а также получиться слишком сложной.

Что такое регуляризация?

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

Регуляризация — это подмножество методов, способствующих обобщению изучаемых моделей, часто за счёт препятствования изучению мелких деталей. Самые простые из них: ранняя остановка, прореживание (дропаут) и пакетный градиентный спуск.

Ранняя остановка

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

Прореживание (Дропаут)

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

Это можно использовать, что и сделано в этом методе. Мы будем отключать случайную часть нейронов, тем самым не давая ей захватить шумы.

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

Если мы просто так выключим часть нейронов во время обучения, веса сети подстроятся под эту уменьшенную сумму. Но когда мы включим все нейроны для предсказания на реальных данных, сумма на входе в следующий слой резко вырастет, и сеть выдаст неверный результат.

Суть этого процесса заключается в сохранении математического ожидания (среднего значения) взвешенных сумм между обучением и тестированием.

Пакетный градиентный спуск

Пакетный градиентный спуск корректирует веса не после каждого примера (стохастический) или всего набора данных (полный), а после просмотра пакета с указанным числом примеров (обычно от 8 до 256).

Этот метод основан на усреднении корректирующих воздействий на весовые коэффициенты в процессе обучения.

Эти методы не единственные, существуют и другие. Также на практике методы могут объединять для лучшего результата (пакетный градиентный спуск и дропаут).

Рассмотрим на практике методы регуляризации (пакетный градиентный спуск и дропаут). Показывать буду на примере нейронный сети для распознавания изображения чисел (датасет mnist) с двумя слоями, скрытый слой состоит из 100 нейронов с функцией активации tanh, а выходной слой из 10 нейронов с функцией активации softmax.

Начнём с обучающего и тестового набора данных

Первым делом мы для одинаковой инициализации весов при каждом запуске вызываем np.random.seed(1).

В качестве набора данных я использую изображения из датасета mnist. Метод mnist.load_data() возвращает кортеж с двумя двухэлеметными кортежами. В x_train и x_test изображения представлены в виде трёхмерного тензора. Обучающий набор состоит из 60000 изображений по 28 пикселей на строку и столбец. А тестовый тоже самое но 10000.

Поэтому для удобства мы меняем размерность на обычную матрицу или двухмерный тензор, где будет 1000 строк и 28*28 столбцов.

Также создаём две матрицы 1000 на 10, где в нужном месте будет стоять 1 (то есть если в 5 строке и в 6 столбце стоит 1, то 6 картинка содержит цифру 7). Делим значения на 255, чтобы ужать в диапазон яркости от 0 до 1.

import numpy as np 
from keras.datasets import mnist
np.random.seed(1)

(x_train,y_train),(x_test,y_test) = mnist.load_data()
images, labels = (x_train[0:1000].reshape(1000,28*28)/255, y_train[0:1000])
one_hot_labels = np.zeros((len(labels),10))
for i,j in enumerate(labels):
    one_hot_labels[i][j] = 1
labels = one_hot_labels
x_test = x_test[0:1000].reshape(1000,28*28)/255
test_labels = np.zeros((len(y_test),10))
for i,j in enumerate(y_test):
    test_labels[i][j] = 1

Реализуем функции активации

def tanh(x): return np.tanh(x)

def tanh2deriv(x):
    return 1 - x**2

def softmax(x):
    temp = np.exp(x)
    return temp/np.sum(temp, axis=1, keepdims=True)

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

Зададим начальные значения

Здесь мы зададим начальные гиперпараметры для нашей сети.

alpha, iteration, neirons = (0.07,300,100)
batch_size = 100
image_size, labels_num = 784, 10

weight_1 = 0.02*np.random.random((image_size, neirons)) - 0.01
weight_2 = 0.2*np.random.random((neirons,labels_num)) - 0.1

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

Обучение и тестирование

Тут мне было тяжело если честно, поэтому постараюсь донести всё максимально подробно.

Начинаем проходиться по всему обучающему набору изображений пакетами по 100 штук. Их подаём на вход первого слоя, то есть подали матрицу 100 на 784.

Первый слой считает взвешенную сумму сразу для всех примеров, применяет tanh и записывает в матрицу 100 на 100, где строки это примеры, а столбцы — вывод нейрона для этого примера.

Далее применяем дропаут, отключая случайную половину нейронов, не забывая усилить сигнал (сохранить математическое ожидание). И передаём матрицу 100 на 100 на вход второго слоя, применяем функцию softmax и получаем матрицу 100 на 10. Где строки это примеры, а в столбцах будет стоять число и чем оно больше тем вероятнее, что именно эта цифра на картинке (то есть если в строке 3 и столбце 6 стоит число близкое к 1, то на 4 картинке нарисована 7).

После этого мы считаем количество правильно отгаданных изображений, сравнивая в каком индексе у нас стоит наибольшее число. Тут важное отличие от прошлых наших сетей, я не прописываю в коде расчёт ошибки, потому что здесь уже не простая среднеквадратическая ошибка (SSE), а я использую перекрёстную энтропию (о ней во второй части). Она нам облегчит производную для функции softmax, производная перекрёстной энтропии по входам функции softmax это просто истина - предсказание, как в SSE вообще без функций активации на выходном слое.

#прямое распространение 

for j in range(iteration):
    correct_cnt = 0
    for i in range(int(len(images)/batch_size)):
        start, end = ((i*batch_size),((i+1)*batch_size))
        layer_0 = images[start:end]
        layer_1 = tanh(np.dot(layer_0,weight_1))
        dropout = np.random.randint(2,size=layer_1.shape)
        layer_1 *= dropout*2
        layer_2 = softmax(np.dot(layer_1,weight_2))

        for k in range(batch_size):
            correct_cnt += int(np.argmax(layer_2[k:k+1]) == np.argmax(labels[start+k:start+k+1]))

#обратное распространение 

        layer_2_delta = (labels[start:end] - layer_2)/batch_size
        layer_1_delta = np.dot(layer_2_delta,weight_2.T)*tanh2deriv(layer_1)
        layer_1_delta *= dropout
        weight_2 += alpha*layer_1.T.dot(layer_2_delta)
        weight_1 += alpha*layer_0.T.dot(layer_1_delta)

    test_correct_cnt = 0
    for i in range(len(x_test)):
        layer_0 = x_test[i:i+1]
        layer_1 = tanh(layer_0.dot(weight_1))
        layer_2 = softmax(np.dot(layer_1,weight_2))
        test_correct_cnt += int(np.argmax(layer_2) == np.argmax(test_labels[i:i+1]))

    if(j %10 == 0):
        print(f"iteration: {j} Test_acc: {test_correct_cnt/float(len(x_test))} Train_acc: {correct_cnt/float(len(images))}")

Теперь переходим к ещё более сложному этапу. Обратное распространение начинается у нас с расчёта дельты для выходного слоя. Мы получаем вектор дельт, полученных для каждого примера и делим их значение на количество примеров в пакете (то есть в итоге у нас матрица 100 на 10, где строки это примеры, а столбцы это дельты для соответствующего нейрона). Эту дельту мы получили очень просто благодаря тому, что производная перекрёстной энтропии к взвешенной суммы подаваемой на вход softmax — это просто истина - результат.

Тут странным может показаться зачем нам целых 100 значений дельт (по одной дельте от каждого примера на каждый нейрон) для одного нейрона, но мы их поделили на количество примеров, и потом при градиентном спуске всё равно все перемножаются и складываются, то есть усреднились в одну для каждого веса.

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

Теперь вычисляем новые веса. Как обычно умножаем дельту слоя на входные значения для этого слоя и на альфу.

В конце проводим тестирование нашей модели после каждой итерации (прохода по всему набору изображений).

 Тестирование модели

Тестирование модели

Теперь поговорим про свёрточные нейронные сети (CNN), которые являются результатом адаптации структуры нейронной сети к определённым характеристикам данных.

Что такое свёрточная нейронная сеть?

Свёрточные нейронные сети были созданы для распознавания образов. Основная задача этой архитектуры состояла создании сети, в которой начальный слой извлекает локальные признаки,а последующие слои объединяют их в признаки более высокого уровня.

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

Для достижения этой пространственной независимости при обнаружении признаков нейроны в сетях CNN используют общие веса.

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

Например, нейроны в первом скрытом слое сети получают на вход набор пиксельных значений, в случае присутствия в них определенной закономерности (локального визуального элемента) сильно активируются.

То, что функция, реализованная нейроном, определяется его весами, означает: два нейрона с одинаковым сочетанием весов реализуют одну и ту же функцию (механизм обнаружения признаков).

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

Основные операции для CNN

Основные этапы работы свёрточной нейронной сети

Основные этапы работы свёрточной нейронной сети

На рисунке выше проиллюстрированы разные этапы обработки, которые часто можно встретить CNN.

Матрица размером 6x6 в левой части представляет изображение, которое подаётся на вход сети. Справа от ввода находится матрица размером 4x4, представляющая слой нейронов, которые вместе прочёсывают изображение в поисках конкретного локального признака. Каждый из них соединен со своим рецептивным полем изображения размером 3х3, все они применяют к своему вводу одну и ту же матрицу весов 3х3.

Здесь рецептивные поля пересекаются, потому что имеют сдвиг равный единице, сдвиг является тут гиперпараметром (его выбирает сам человек).

По сути реализуется операция свёртки, мы последовательно применяем одну и ту же матрицу весов (матрицу свёртки или ядро) к различным частям входных данных. Результатом скалярного умножения матриц является число, которое мы записываем в одну из ячеек карты признаков.

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

После заполнения карты признаков применяется нелинейная функция активации к её элементам.

Операция пуллинга (объединения)

После того как свёрточный слой нашёл признаки (например, края, углы, текстуры), приступаем к пуллингу.

Что делает пуллинг?

  • Уменьшает размерность карты признаков.

  • Делает сеть устойчивее к небольшим смещениям и искажениям изображения.

  • Выделяет самые важные признаки из каждой области.

По сути мы делим нашу карту признаков на квадраты меньшего размера (это называется окном пуллинга). Для каждого квадрата берём максимальное значение (Max Pooling) или среднее значение (Average Pooling) и записываем его в новую, уменьшенную карту.

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

Все нейроны плотного слоя принимают на вход выходы всех нейронов предыдущего слоя и именно плотный слой генерирует окончательный ответ.

Пишите в комментариях, если нашли ошибки или стоит разобрать какой-то момент подробнее. Стоит ли делать 2 часть?