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

推荐订阅源

Help Net Security
Help Net Security
Recent Announcements
Recent Announcements
A
About on SuperTechFans
N
News and Events Feed by Topic
I
Intezer
B
Blog
GbyAI
GbyAI
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Project Zero
Project Zero
T
The Blog of Author Tim Ferriss
Blog — PlanetScale
Blog — PlanetScale
阮一峰的网络日志
阮一峰的网络日志
T
Troy Hunt's Blog
TaoSecurity Blog
TaoSecurity Blog
IT之家
IT之家
MyScale Blog
MyScale Blog
The Register - Security
The Register - Security
Cyberwarzone
Cyberwarzone
Y
Y Combinator Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
酷 壳 – CoolShell
酷 壳 – CoolShell
Stack Overflow Blog
Stack Overflow Blog
P
Privacy & Cybersecurity Law Blog
S
Secure Thoughts
MongoDB | Blog
MongoDB | Blog
N
Netflix TechBlog - Medium
Hacker News - Newest:
Hacker News - Newest: "LLM"
G
Google Developers Blog
罗磊的独立博客
博客园 - 聂微东
G
GRAHAM CLULEY
AWS News Blog
AWS News Blog
Google Online Security Blog
Google Online Security Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Hacker News: Front Page
C
Check Point Blog
L
Lohrmann on Cybersecurity
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Spread Privacy
Spread Privacy
O
OpenAI News
T
Tor Project blog
P
Privacy International News Feed
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Attack and Defense Labs
Attack and Defense Labs
L
LINUX DO - 最新话题
Forbes - Security
Forbes - Security
云风的 BLOG
云风的 BLOG
C
Cisco Blogs
S
Securelist
博客园_首页

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

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет 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 миллионов точек без потерь
Что такое нейросети и как они устроены под капотом (на пальцах, с примерами на python)
Кретов Артем · 2026-06-26 · via Все публикации подряд на Хабре

Простой

10 мин

0

Привет, Хабр! Недавно я писал статью "Что такое искусственный интеллект (не нейросети) и какие у него есть виды", в которой кратко рассказал, какие разновидности искусственного интеллекта существуют и чем они отличаются. В этой статье я хочу более подробно рассмотреть нейросети: как они обучаются, как работают и из чего состоят.

Архитектура нейросети

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

Посмотрите на эти цифры: 

Рукописные цифры из датасета MNIST

Рукописные цифры из датасета MNIST

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

Две рукописные двойки, наложенные друг на друга

Две рукописные двойки, наложенные друг на друга

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

Что ж, давайте разберёмся, из чего состоит нейросеть. Как ни странно, нейросеть состоит из нейронов (neurons). У каждого нейрона есть набор входных весов (weights) и коэффициент сдвига (bias). Пока что мы просто объявим класс, а как именно нейрон считает – добавим чуть позже:

import random

class Neuron:
    def __init__(self, num_inputs):
        self.weights = [random.uniform(-0.5, 0.5) for _ in range(num_inputs)]
        self.bias = random.uniform(-0.5, 0.5)

Для этого примера я буду использовать архитектуру многослойного перцептрона с двумя скрытыми слоями по 12 нейронов.

Перцептрон – это архитектура нейронной сети, в которой нейроны каждого слоя соединены со всеми нейронами следующего слоя. В классическом перцептроне три слоя, но в многослойном перцептроне (MLP) скрытых слоёв может быть несколько – в нашем случае их два. А что это вообще за слои такие?

  1. Входной слой. В эти нейроны записываются исходные данные. В нашем примере рассматриваются картинки размером 28×28 пикселей в чёрно-белом формате. Это значит, что на входном слое у нас будет 28×28 = 784 нейрона, соответствующих каждому пикселю изображения: белый пиксель – 1, чёрный – 0, серый – градация серого от 0 до 1.

  2. Скрытый слой. Слой, расположенный между входным и выходным, помогает извлекать более сложные признаки и закономерности. Нейронов на этом слое может быть столько, сколько пожелается.

  3. Выходной слой. Каждый нейрон этого слоя соответствует одному из ответов, который может дать нейросеть. В нашем примере их будет 10, по числу цифр. Ответом нейросети считается нейрон с самым большим значением – чем оно больше, тем выше уверенность сети в том, что на картинке изображена именно эта цифра. Чтобы превратить выходные значения в вероятности, сумма которых равна 1, применяют функцию softmax, но о ней чуть позже.

Перцептрон и многослойный перцептрон

Перцептрон и многослойный перцептрон

Теперь, когда мы знаем, из каких слоёв состоит сеть, соберём нейроны в слой, а слои – в сеть:

class Layer:
    def __init__(self, num_neurons, num_inputs):
        self.neurons = [Neuron(num_inputs) for _ in range(num_neurons)]


class NeuralNetwork:
    def __init__(self, sizes):
        self.layers = []
        for i in range(len(sizes) - 1):
            self.layers.append(Layer(sizes[i + 1], sizes[i]))

Параметр sizes – это список чисел, где каждое число – количество нейронов в соответствующем слое. Например, [784, 12, 12, 10] означает: входной слой (784 нейрона), два скрытых слоя (по 12), выходной слой (10 нейронов). Конструктор NeuralNetwork проходит по парам соседних размеров и создаёт слой за слоем.

Как нейросеть считает: прямой проход

Разберём, как работает одна пара слоёв. Возьмём входной и скрытый слой: во входном уже есть исходные данные изображения, надо получить что-то на выходе скрытого слоя.

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

Взвешенная сумма

Взвешенная сумма

Активация нейрона должна быть в определённом интервале, а взвешенная сумма может выходить за его пределы. Значит, нужно применить функцию, которая приведёт значение в нужный интервал. Эту функцию называют функцией активации (activation function) нейрона. Перед применением функции активации к взвешенной сумме прибавляется коэффициент сдвига (bias), который позволяет сдвигать функцию влево или вправо. Итого: функция активации применяется к сумме взвешенной суммы и коэффициента сдвига.

Наиболее распространены сигмоида, ReLU, Leaky ReLU, ELU, SiLU, гиперболический тангенс. Важно помнить, что область значений у них разная: сигмоида выдаёт числа от 0 до 1, tanh – от –1 до 1, а ReLU и её варианты – от 0 до +∞.

Функции активации нейрона

Функции активации нейрона

Возьмём для нашего примера сигмоиду и сразу напишем её на Python:

def sigmoid(x):
    return 1 / (1 + math.exp(-x))

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

class Neuron:
    def __init__(self, num_inputs):
        self.weights = [random.uniform(-0.5, 0.5) for _ in range(num_inputs)]
        self.bias = random.uniform(-0.5, 0.5)

    def forward(self, inputs):
        total = self.bias
        for w, i in zip(self.weights, inputs):
            total += w * i
        return sigmoid(total)

Добавим forward и в слой, и в сеть:

class Layer:
    def __init__(self, num_neurons, num_inputs):
        self.neurons = [Neuron(num_inputs) for _ in range(num_neurons)]

    def forward(self, inputs):
        return [n.forward(inputs) for n in self.neurons]


class NeuralNetwork:
    def __init__(self, sizes):
        self.layers = []
        for i in range(len(sizes) - 1):
            self.layers.append(Layer(sizes[i + 1], sizes[i]))

    def forward(self, inputs):
        for layer in self.layers:
            inputs = layer.forward(inputs)
        return inputs

Всё вместе теперь работает так: NeuralNetwork.forward передаёт данные в первый слой, тот – во второй, и так до самого выхода. Каждый нейрон внутри слоя делает свою взвешенную сумму и применяет сигмоиду.

Проверим, как это работает (пусть даже со случайными весами предсказание будет бессмысленным – главное, что структура верна):

nn = NeuralNetwork([784, 12, 12, 10])

# Сгенерируем случайные пиксели вместо реальной картинки
pixels = [random.random() for _ in range(784)]

output = nn.forward(pixels)
predicted_digit = output.index(max(output))

print(f"Предсказанные вероятности: {[round(p, 3) for p in output]}")
print(f"Нейросеть считает, что это цифра: {predicted_digit}")

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

Для матричных операций используют библиотеку NumPy. Она позволяет записать всю сеть гораздо компактнее и быстрее:

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

class NeuralNetwork:
    def __init__(self, sizes):
        self.weights = [np.random.randn(y, x) * 0.01
                       for x, y in zip(sizes[:-1], sizes[1:])]
        self.biases = [np.random.randn(y, 1) * 0.01
                      for y in sizes[1:]]

    def forward(self, X):
        for w, b in zip(self.weights, self.biases):
            X = sigmoid(w @ X + b)
        return X

Оператор @ в Python – это матричное умножение. Теперь вместо цикла по нейронам мы просто умножаем матрицу весов на вектор входов. Результат получается мгновенно.

Обучение нейросети

Откуда берутся веса и сдвиги?

Представьте, расставить все веса и сдвиги вручную! В нашем примере 784×12 + 12×12 + 12×10 = 9672 веса и 12 + 12 + 10 = 34 сдвига, суммарно 9706 параметров! Понятно, что столько параметров вручную подобрать невозможно – значит, нужно как-то заставить нейросеть самостоятельно подбирать эти параметры.

Для начала возьмём случайные значения всех параметров – результат нейросети будет отвратителен. В идеальном случае на выходном нейроне, соответствующем правильной цифре, должен быть 1, а на остальных – 0. Разницу между предсказанным результатом нейросети и идеальным значением измеряет функция потерь (loss function). В нашем примере, где мы хотим, чтобы выходные значения были 0 или 1, можно использовать среднеквадратичную ошибку (Mean Squared Error, MSE). Напишем её на Python:

def mse_loss(predicted, expected):
    return sum((p - e) ** 2 for p, e in zip(predicted, expected)) / len(predicted)

# Пример: ожидаем, что на картинке цифра 3
expected = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
loss = mse_loss(output, expected)
print(f"Ошибка (MSE): {loss}")

Для задач классификации, таких как распознавание цифр, часто более эффективна кросс-энтропия (cross-entropy), которая лучше отражает «уверенность» модели. Чем меньше значение функции потерь, тем точнее работает нейросеть.

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

Если немного упростить, то нейросеть – это функция, которая принимает 784 аргумента и возвращает 10 значений. А функция потерь принимает на вход 9706 параметров (веса и сдвиги) и возвращает число – ошибку. Оптимальны те параметры, при которых ошибка меньше, значит, лучшими весами будет минимум функции потерь. Но как же найти минимум такой сложной функции с 9706 параметрами?

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

Здесь в силу вступает алгоритм обратного распространения ошибки (backpropagation). В нашей функции потерь 9706 параметров – такое тяжело представить, так что давайте упростим и представим, что у нас всего 3 параметра. Визуализировать трёхмерное пространство уже не так сложно. Представьте, что функция потерь, которую мы хотим минимизировать, – это ландшафт с горами и долинами. Наша цель – найти самую глубокую точку в этом ландшафте. Алгоритм обратного распространения, по сути, работает как слепой человек, который шагает по этому ландшафту: он делает маленький шаг в направлении наибольшего спуска, то есть туда, где склон самый крутой вниз.

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

Давайте добавим в наш класс на NumPy метод train, который делает один шаг градиентного спуска:

def sigmoid_derivative(x):
    return x * (1 - x)


class NeuralNetwork:
    def __init__(self, sizes):
        self.weights = [np.random.randn(y, x) * 0.01
                       for x, y in zip(sizes[:-1], sizes[1:])]
        self.biases = [np.random.randn(y, 1) * 0.01
                      for y in sizes[1:]]

    def forward(self, X):
        self.activations = [X]
        for w, b in zip(self.weights, self.biases):
            X = sigmoid(w @ X + b)
            self.activations.append(X)
        return X

    def train(self, X, y, learning_rate=0.1):
        # Прямой проход
        output = self.forward(X)

        # Обратное распространение
        delta = (output - y) * sigmoid_derivative(output)

        for i in reversed(range(len(self.weights))):
            # Корректируем веса и сдвиги
            self.weights[i] -= learning_rate * delta @ self.activations[i].T
            self.biases[i] -= learning_rate * delta

            if i > 0:
                # Считаем ошибку для предыдущего слоя
                delta = self.weights[i].T @ delta * sigmoid_derivative(self.activations[i])

Разберёмся, что здесь происходит. В forward мы теперь сохраняем все промежуточные активации (входы и выходы каждого слоя) – они понадобятся, чтобы понять, какой вклад каждый вес внёс в ошибку. В train мы сначала делаем прямой проход, затем вычисляем дельту на выходном слое (насколько сильно ответ отличается от правильного) и последовательно идём назад, корректируя веса пропорционально их влиянию на общую ошибку.

Попробуем обучить сеть на простейшем примере – логическом XOR. Это классическая задача, с которой не справляется однослойный перцептрон:

# Функция потерь для NumPy
def mse_loss(predicted, expected):
    return np.mean((predicted - expected) ** 2)

# Обучающие данные: XOR
X_train = [
    np.array([[0], [0]]),
    np.array([[0], [1]]),
    np.array([[1], [0]]),
    np.array([[1], [1]]),
]
y_train = [
    np.array([[0]]),
    np.array([[1]]),
    np.array([[1]]),
    np.array([[0]]),
]

nn = NeuralNetwork([2, 4, 1])

for epoch in range(10000):
    for X, y in zip(X_train, y_train):
        nn.train(X, y, learning_rate=0.5)

    if epoch % 1000 == 0:
        loss = sum(mse_loss(nn.forward(X), y)
                   for X, y in zip(X_train, y_train))
        print(f"Эпоха {epoch}, ошибка: {loss:.4f}")

print("\nРезультаты после обучения:")
for X, y in zip(X_train, y_train):
    pred = nn.forward(X)[0, 0]
    print(f"{X[0, 0]} XOR {X[1, 0]} = {pred:.3f} (ожидалось {y[0, 0]})")

Как это всё вместе работает на практике?

  1. Прямой проход (forward pass): Нейросеть берёт входные данные (например, картинку с цифрой), пропускает их через все слои, вычисляет взвешенные суммы и применяет функции активации, пока не получит результат на выходном слое. Затем сравнивает этот результат с истинным ответом и вычисляет общую ошибку через функцию потерь.

  2. Обратный проход (backward pass / backpropagation): Зная ошибку на выходе, алгоритм начинает двигаться назад – от выходного слоя к входному. Он вычисляет, как изменение каждого веса и сдвига на каждом слое повлияло на эту ошибку. Чем больше параметр вложился в большую ошибку, тем сильнее его нужно скорректировать.

  3. Коррекция весов и сдвигов: На основе вычисленных градиентов каждый вес и сдвиг немного изменяются в сторону уменьшения ошибки. Величина этого изменения контролируется скоростью обучения (learning rate) – это как размер шага нашего слепого человека. Если шаг слишком большой, можно проскочить минимум; если слишком маленький – идти будем очень долго.

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

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

Возможные проблемы при обучении

Иногда в процессе обучения могут возникать проблемы. Например, нейросеть может застрять в локальном минимуме (local minimum) – это как маленький овраг на нашем ландшафте: самая низкая точка на небольшом участке, но не на всём ландшафте. Или ошибка может начать расти, если скорость обучения (learning rate) слишком велика, и наш человек делает слишком большие шаги, перепрыгивая через минимум. Для решения этих проблем существуют различные оптимизаторы (например, Stochastic Gradient Descent (SGD), Adam, RMSprop) и техники регуляризации (regularization), но их детальное рассмотрение – тема для более глубокого погружения.

Переобучение (Overfitting)

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

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

В чём проблема?

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

Как это заметить?

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

Что с этим делать?

Для борьбы с переобучением существует несколько стратегий:

  • Больше данных: Чем больше разнообразных примеров увидит нейросеть, тем сложнее ей будет запомнить их все досконально.

  • Регуляризация (regularization): Методы, которые штрафуют нейросеть за слишком сложные модели, заставляя её быть «ленивее» и не подгонять каждый шум.

  • Досрочная остановка (early stopping): Мы следим за ошибкой не только на обучающих данных, но и на валидационных. Как только ошибка на валидационных данных перестаёт падать, мы останавливаем обучение.

Заключение

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