Как на самом деле работают нейросети (без магии)
Если отбросить маркетинговую шелуху про «искусственный разум» и «биологическое подобие», нейросеть — это статическая математическая структура. Это не мозг. Это сложная функция $f(x)$, которая переводит входной вектор данных в вектор выходных решений.
Для разработчика нейросеть — это конвейер обработки данных, состоящий из линейной алгебры и нелинейной отсечки. Чтобы понять, как она работает, мы спроектируем систему принятия решений с нуля, столкнемся с математическим ограничением и решим его, построив архитектуру.
1. Нейросети — это способ принимать решения

Давайте забудем про распознавание котиков. В основе любой ML-задачи лежит классификация: нужно отнести входной набор данных к одной из групп.
Рассмотрим прикладную задачу. Вы — разработчик, и вы пишете скрипт, который должен решить за вас: идти сегодня на вечеринку или нет.
Это задача бинарной классификации. Выход системы ($y$):
1(True) — Идти.0(False) — Остаться дома.
На входе у нас есть набор факторов (фичей), описывающих текущую ситуацию. Допустим, их три:
- Vodka ($x_1$): Будет ли там бесплатный алкоголь? (1 — да, 0 — нет).
- Rain ($x_2$): Идет ли сейчас дождь? (1 — да, 0 — нет).
- Friend ($x_3$): Будет ли там ваш лучший друг? (1 — да, 0 — нет).
Наша цель — создать алгоритм, который берет эти три $x$ и выдает верный $y$. Если бы мы писали это на обычном Python, это было бы дерево if-else. Но нейросеть решает это иначе: через взвешивание важности.
2. Один нейрон = одно взвешенное решение
Базовый строительный блок нейросети — нейрон. Инженерно — это сумматор. Он не «думает», он взвешивает аргументы.
У каждого входного сигнала есть свой вес ($w$). Вес — это важность фактора для принятия решения.
- Если фактор повышает желание пойти, вес положительный.
- Если фактор снижает желание (например, дождь), вес отрицательный.
- Чем больше модуль веса, тем фактор важнее.
Допустим, логика нашего персонажа такая:
- Алкоголь — это хорошо ($w_1 = 0.5$).
- Дождь — это неприятно ($w_2 = -0.5$).
- Друг — это очень важно ($w_3 = 0.5$).
Механика взвешивания

Нейрон делает простую операцию: он умножает сигнал на его важность и складывает результаты.
$$\text{Сумма} = (x_1 \cdot 0.5) + (x_2 \cdot -0.5) + (x_3 \cdot 0.5) $$
Но одной суммы недостаточно. Если сумма равна 0.8 — это «да» или «нет»? Нужен порог принятия решения. В простейшем случае мы можем сказать: если взвешенная сумма больше или равна некоторому порогу (например, 0.5), то результат 1, иначе 0.
Пример работы: Идет дождь ($x_2=1$), водки нет ($x_1=0$), но друг идет ($x_3=1$).
$$\text{Сумма} = (0 \cdot 0.5) + (1 \cdot -0.5) + (1 \cdot 0.5) = 0 $$
Результат 0. Это меньше порога 0.5. Нейрон выдаст 0. Мы не идем. Дождь нивелировал радость от встречи с другом.
3. Формализация: от интуиции к формуле
Теперь переведем это на язык математики, который мы будем использовать в коде.
Процесс обработки данных в нейроне состоит из двух шагов:
- Линейная комбинация (dot product): скалярное произведение вектора входов $X$ на вектор весов $W$.
- Нелинейность (activation function): применение правила отсечки.
Общая формула одного нейрона:
$$y = f\left( \sum_{i=1}^{n} x_i \cdot w_i \right) $$
Где:
- $x_i$ — входные сигналы.
- $w_i$ — веса (параметры модели).
- $f(z)$ — функция активации.
Функция активации

Вот мы подошли к ключевому термину. Функция активации $f(z)$ решает, «загорится» нейрон или нет. Без нее нейросеть осталась бы просто набором умножений.
В примере выше мы использовали пороговую функцию (ступеньку):
$$f(z) = \begin{cases} 1, & \text{если } z \ge \text{threshold} \ 0, & \text{если } z < \text{threshold} \end{cases} $$
В современных сетях (и в коде позже) часто используют Sigmoid или ReLU, чтобы сделать переход более плавным и дифференцируемым, но для понимания логики пороговая функция подходит идеально. Она превращает абстрактную важность в конкретное бинарное решение.
4. Ограничение линейного нейрона (Проблема XOR)
Кажется, что одного нейрона достаточно. Просто подбери правильные веса $w$, и он решит любую задачу, так? Нет.
Рассмотрим более сложную жизненную ситуацию, с которой сталкиваются нейросети. Это проблема нелинейных зависимостей.
Пусть у нашего героя есть странный принцип (назовем это «Синдром плохого приключения»):
- Если есть Только Водка — прекрасно (идем).
- Если идет Только Дождь — плохо (не идем).
- Но если есть Водка И Дождь одновременно — герой категорически не идет. Он знает, что напьется, промокнет и заболеет. Это «опасная комбинация».
- Однако, если при этом есть Лучший Друг, он все равно пойдет, потому что друг спасет ситуацию.
Попробуем подобрать веса для одного нейрона, чтобы описать правило «Водка ($x_1$) + Дождь ($x_2$) = Плохо».
- $x_1=1, x_2=0 \rightarrow$ должно быть
1. Значит $w_1$ должен быть большим. - $x_1=0, x_2=1 \rightarrow$ должно быть
0. Значит $w_2$ должен быть маленьким или отрицательным. - $x_1=1, x_2=1 \rightarrow$ должно быть
0.
Математически невозможно подобрать два таких числа $w_1$ и $w_2$, чтобы их сумма по отдельности превышала порог, а вместе — нет (при условии линейного сложения). Это вариация классической проблемы XOR (исключающего ИЛИ). Один линейный нейрон может провести только одну прямую линию, разделяющую «да» и «нет». Он не может выделить область, где «плюс» на «плюс» дает «минус».
Здесь нам и нужна сеть.
5. Добавляем скрытый слой (Hidden Layer)

Чтобы решить проблему сложной зависимости, нам нужно разбить задачу на подзадачи. Нам нужны промежуточные нейроны, которые будут детектировать специфические состояния, а не принимать решение целиком.
Этот промежуточный слой называется скрытым (hidden layer), потому что пользователь не видит его входов и выходов. Он видит только вечеринку и факт присутствия.
Давайте построим архитектуру для нашего придирчивого героя. Слой 1 (Input): Водка, Дождь, Друг. Слой 2 (Hidden): 2 нейрона. Слой 3 (Output): 1 нейрон (Идти/Не идти).
Логика скрытых нейронов
Спроектируем скрытый слой так, чтобы он выделял ключевые паттерны:
Нейрон А (Детектор плохой комбинации):
Его задача — загореться (1), только если есть и Водка, и Дождь.
Веса: Водка = 0.25, Дождь = 0.25, Друг = 0. Порог = 0.5.
- Если только Водка (1): $0.25 < 0.5$ (Выход 0).
- Если только Дождь (1): $0.25 < 0.5$ (Выход 0).
- Если Оба (1, 1): $0.25 + 0.25 = 0.5$ (Выход 1). Смысл: Этот нейрон кричит «Алярм!», когда условия рискованные.
Нейрон B (Детектор социального спасения): Его задача — быть активным, если друг рядом, даже если идет дождь. Друг должен перевешивать дождь. Веса: Водка = 0, Дождь = -0.4, Друг = 0.9. Порог = 0.5.
- Только Дождь: $-0.4 < 0.5$ (Выход 0).
- Дождь + Друг: $-0.4 + 0.9 = 0.5$ (Выход 1). Смысл: Этот нейрон сигнализирует: «Спокойно, мы с другом, можно рисковать».
Финальный нейрон (Принятие решения)
Теперь выходной нейрон принимает сигналы не от сырых данных, а от наших детекторов (А и B).
Его логика:
- Если кричит детектор «Алярм» (Нейрон А), надо сильно хотеть остаться дома. Вес от А должен быть отрицательным и большим. Например, $w_A = -1.0$.
- Если активен детектор «Друг» (Нейрон B), это повод пойти. Вес положительный. $w_B = 1.0$.
Давайте проверим на примере "Водка + Дождь + Друг":
- Входы: $x=[1, 1, 1]$.
- Скрытый слой:
- Нейрон А: $1\cdot0.25 + 1\cdot0.25 + 0 = 0.5$. Порог пройден $\rightarrow$ Выход 1.
- Нейрон B: $0 + 1\cdot(-0.4) + 1\cdot0.9 = 0.5$. Порог пройден $\rightarrow$ Выход 1.
- Выходной слой:
- Вход от А: $1$, вес $-1$.
- Вход от B: $1$, вес $1$.
- Сумма: $(1 \cdot -1) + (1 \cdot 1) = 0$.
- Результат: $0 < 0.5$. Решение — 0 (Не идти).
Даже наличие друга не перевесило тот факт, что сработал детектор «Опасная пьянка в дождь» с его мощным отрицательным весом. Система приняла сложное, нелинейное решение.
6. Что такое обобщение (Generalization)
То, что мы сейчас сделали, добавив скрытый слой, называется повышением размерности пространства признаков.
В реальных сетях (как в ChatGPT или Computer Vision) скрытых слоев десятки.
- Первый слой видит пиксели.
- Второй слой (как наш Нейрон А) складывает пиксели и видит «линии».
- Третий слой складывает линии и видит «глаз» или «ухо».
- Последний слой решает: «это кот».
Обобщение — это способность сети создавать новые, более абстрактные признаки из комбинации простых. Глубина сети (Deep Learning) нужна именно для этого: чем глубже сеть, тем более сложные и абстрактные комбинации («водка с дождем», «интонация сарказма», «текстура шерсти») она может распознать и использовать для решения.
7. Где появляется обучение
В нашем примере мы подобрали веса ($0.25, -0.4$ и т.д.) вручную, потому что знали логику героя. Это называется «экспертная система», а не ИИ.
В реальной нейросети веса инициализируются случайным образом (мусором). Сеть в начале «тупая». Обучение — это автоматический процесс подбора этих чисел.
Как это работает (на интуитивном уровне):
- Мы подаем данные (Водка+Дождь) в сеть со случайными весами.
- Сеть выдает случайный ответ: «Идти».
- Мы говорим: «Ошибка! Правильный ответ — Не идти».
- Мы вычисляем разницу (Ошибку).
- Мы используем алгоритм Backpropagation (Обратное распространение ошибки). Мы идем от конца к началу и смотрим: какой нейрон внес больший вклад в эту ошибку?
- С помощью математического трюка (производной, которая показывает направление роста функции) мы немного подкручиваем веса в сторону уменьшения ошибки. Это Градиентный спуск.
Мы буквально скатываемся с «холма ошибок» в «низину правильных ответов», меняя $w_i$ на миллионную долю на каждой итерации.
8. Мини-реализация на Python
Давайте напишем описанную сеть. Мы будем использовать матричное умножение, так как оно позволяет вычислить слои за одно действие.
Помните:
- Вход $X$ — это вектор (1x3).
- Веса скрытого слоя $W_{hidden}$ — это матрица (3x2), так как у нас 3 входа и 2 нейрона в скрытом слое.
import numpy as np
# Активационная функция (Пороговая)
def activation_function(x):
# Если x >= 0.5 возвращаем 1, иначе 0
return np.where(x >= 0.5, 1, 0)
def predict(vodka, rain, friend):
# 1. Формируем входной вектор
inputs = np.array([vodka, rain, friend]) # Shape (3,)
# 2. Веса скрытого слоя
# Нейрон А (столбец 0): [0.25, 0.25, 0] - детектор водки и дождя
# Нейрон B (столбец 1): [0, -0.4, 0.9] - детектор друга и дождя
weights_hidden = np.array([
[0.25, 0.0], # Веса для Vodka
[0.25, -0.4], # Веса для Rain
[0.0, 0.9] # Веса для Friend
])
# 3. Веса выходного слоя
# Нейрон А дает -1 (запрет), Нейрон B дает +1 (разрешение)
weights_output = np.array([-1.0, 1.0])
# --- Прямой проход (Forward Pass) ---
# Вычисляем входы скрытого слоя: Inputs * Weights
# Интуитивно: каждый вход умножается на веса для каждого скрытого нейрона
hidden_input = np.dot(inputs, weights_hidden)
# Применяем активацию к скрытому слою
hidden_output = activation_function(hidden_input)
print(f"Скрытый слой (активации): {hidden_output}")
# Например, [1, 1] если сработали оба детектора
# Вычисляем вход финального нейрона
final_input = np.dot(hidden_output, weights_output)
# Применяем активацию к выходу
final_prediction = activation_function(final_input)
return final_prediction == 1
# Тест: Водка(1), Дождь(1), Друга НЕТ(0)
# Нейрон А (Bad combo) должен сработать. Нейрон B нет.
# Итог должен быть False (не идти).
result = predict(vodka=1, rain=1, friend=0)
print(f"Идем на пати? -> {result}")
Разбор матричного умножения np.dot
Почему матрицы? Потому что формула $x_1 w_1 + x_2 w_2 ...$ — это и есть строка, умноженная на столбец.
Когда мы делаем np.dot(inputs, weights_hidden), Python параллельно считает взвешенные суммы для обоих скрытых нейронов сразу.
- Если бы у нас было 1000 нейронов, цикл
forработал бы вечность. - Матричное умножение на GPU (в реальных библиотеках типа PyTorch) делает это мгновенно.
9. Заключение
Мы прошли путь от идеи "взвесить за и против" до работающей нейросети.
Что важно запомнить инженеру:
- Нейросеть — это слои арифметики. Здесь нет сознания, только сложение и умножение матриц.
- Глубина = Сложность. Один нейрон — это линейная линия. Много слоев — это сложная фигура, огибающая любые данные. Скрытые слои создают новые логические правила (фичи) на лету.
- Архитектура — это дизайн. Выбор количества слоев и нейронов определяет, насколько сложные зависимости сможет "понять" ваша модель.
- Обучение — это поиск. Веса ($w$), которые мы прописали кодом, в реальности находятся методом оптимизации (градиентным спуском), чтобы минимизировать ошибку.
Нейросети — это не замена логике, это способ автоматизировать поиск сложной логики там, где if-else писать слишком долго.