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

Денис Макарцев
Инженер данных в Россельхозбанке, автор канала об IT-курсах.
Консультировал при подготовке этого материала.
Для навигации по статье:
Краткая база: как работают нейросети и зачем анализировать их веса
Что такое веса нейросети
Представим, что нейросеть — это сложная система фильтров. Она получает на вход данные, обрабатывает их, находит закономерности и выдаёт результат. Чтобы понять, какие признаки важны для решения, нейросеть использует веса.

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

Почему важно анализировать веса
Анализ весов помогает понять, правильно ли обучилась модель. Ошибки в распределении весов могут привести к тому, что нейросеть начнёт делать предсказания наугад, запоминать тренировочные примеры вместо поиска закономерностей или выдавать нестабильные результаты.
Какие ошибки можно обнаружить через анализ весов:
-
Модель не научилась искать закономерности.
Если веса почти не изменяются, это значит, что модель не разобралась в данных и не смогла выделить важные признаки. Она просто выдаёт однотипные предсказания, игнорируя реальные зависимости.
Пример: алгоритм, который оценивает кредитный риск, на выходе всегда даёт одинаковый ответ: «Одобрено» или «Отказано». Он не учитывает уровень дохода, наличие задолженностей и кредитную историю клиента. В итоге получается бесполезный прогноз.
-
Модель слишком точно запомнила обучающие данные.
Обратная проблема — переобучение. Когда разброс весов слишком большой, модель запоминает каждую деталь обучающих данных и становится чувствительной к малейшим изменениям.
Пример: алгоритм, обученный только на фото товаров с белым фоном, может воспринимать фон как важный признак. Если в продакшене фон меняется на серый или цветной, модель может ошибочно решить, что перед ней другой объект, или снизить уверенность в предсказании.
-
Веса меняются слишком хаотично.
Если модель не научилась искать закономерности, а просто запомнила обучающие примеры, её предсказания становятся нестабильными. Когда она получает новые данные, веса начинают резко изменяться, потому что алгоритм не понимает, какие признаки действительно важны.
Пример: модель предсказывает, купит ли клиент товар. На тренировочных данных она работала стабильно, но при тестировании на новых пользователях точность резко упала, а веса стали изменяться скачками. Это значит, что модель запомнила конкретные покупки в обучающей выборке, но не научилась прогнозировать поведение новых клиентов.
-
Модель ориентируется на случайные детали.
Иногда нейросеть делает предсказания, основываясь на незначительных или случайных признаках, которые на самом деле не связаны с результатом.
Пример: алгоритм для диагностики заболеваний обучали на снимках пациентов. На всех фотографиях больных людей случайно оказались водяные знаки. Модель решила, что это признак болезни, потому что он встречался во всех таких снимках. Когда ей дали новые изображения без водяных знаков, предсказания стали неточными, потому что алгоритм не научился отличать здоровых и больных по реальным медицинским признакам.
-
В модели есть аномальные веса — выбросы.
Некоторые веса в модели могут быть слишком большими или, наоборот, близкими к нулю. Такие скачки вызывают ошибки в предсказаниях. Алгоритм начинает слишком сильно реагировать на одни признаки и игнорировать другие.
Пример: нейросеть анализирует резюме и предсказывает, насколько кандидат подходит для вакансии. После обновления модели оказалось, что она начала массово отклонять анкеты опытных специалистов. Это случилось потому, что один из весов, отвечающий за стаж работы, резко уменьшился. В результате модель перестала учитывать этот фактор и начала делать неправильные выводы.
Даже если модель показывает хорошие результаты в тестах, это не гарантирует, что она будет работать без ошибок в реальной среде. Данные в продакшене могут отличаться от тех, на которых она обучалась, а малозаметные изменения могут сбить с толку.
Ошибки нейросети могут привести к серьёзным последствиям. Неверный прогноз спроса ведёт к финансовым потерям, сбои в медицинской системе — к неправильному диагнозу, неточности в банковских расчётах — к рискам для бизнеса.
Поэтому перед внедрением её тестируют на реальных сценариях: оценивают, как она справляется с новыми примерами, устойчива ли к небольшим изменениям и не ухудшаются ли предсказания со временем. Если модель не проходит такие проверки, её дорабатывают.
Минимум SQL для анализа весов
Прежде чем перейти к запросам, разберём ключевые SQL-команды, которые понадобятся в статье.
SELECT
— выбор данных
Команда используется для получения нужной информации из таблицы.
Пример 1 → посмотреть все веса нейросети:
SELECT * FROM weights;
Этот запрос покажет всю таблицу weights
: веса, номера слоёв и нейронов.
Пример 2 → посмотреть веса только в одном слое, например, в третьем:
SELECT neuron, weight FROM weights WHERE layer = 3;
Команда выбирает веса нужного слоя, остальные слои не попадают в результат.
WHERE
— фильтрация строк
Команда помогает выбирать только те строки, которые подходят под определённое условие.
Пример → найти нейроны с весами больше 0.5:
SELECT neuron, weight FROM weights WHERE weight > 0.5;
Запрос выбирает только те записи из таблицы weights
, где значение weight больше 0.5. Это полезно, например, если нужно выделить нейроны со значимыми весами, которые могут сильнее влиять на предсказания модели.
ORDER BY
— сортировка данных
Команда упорядочивает строки в таблице по значениям в указанном столбце. Можно сортировать по возрастанию (ASC) или убыванию (DESC).
Пример → найти 10 нейронов с самыми большими весами:
SELECT neuron, weight FROM weights
ORDER BY weight DESC
LIMIT 10;
Запрос сортирует веса в порядке убывания и выбирает 10 записей с самыми высокими значениями.
GROUP BY
— группировка данных
Эта команда используется, когда нужно посчитать что-то для группы строк.
Пример → найти средний вес по слоям:
SELECT layer, AVG(weight) FROM weights GROUP BY layer;
В результате получится таблица, где для каждого слоя будет одно среднее значение.
HAVING
— фильтрация после группировки
Работает как WHERE, но используется для фильтрования данных после команды GROUP BY.
Пример: выбрать только слои с высоким средним весом:
SELECT layer, AVG(weight) FROM weights
GROUP BY layer HAVING AVG(weight) > 0.2;
Считает средний вес в каждом слое и оставляет только те, где это значение больше 0.2.
JOIN
— объединение данные из нескольких таблиц
Команда нужна, когда данные хранятся в связанных таблицах, и их нужно объединить в один результат.
Пример → связать данные о слоях и их весах:
SELECT layers.layer_number, weights.neuron, weights.weight
FROM layers
JOIN weights ON layers.id = weights.layer_id;
Этот запрос объединяет таблицы layers
и weights
по идентификатору слоя (layer_id
). В результате получаем список нейронов, их веса и номер слоя, которому они принадлежат.
Теперь разберём, как применять SQL для решения конкретных задач.
Задача 1. Хранение весов нейросети в базе данных
Веса нейросетей чаще всего сохраняют в файлы .pt
— PyTorch или .h5
— TensorFlow. Это удобный способ, если нужно просто загрузить модель и использовать её, но он не подходит для аналитики. И вот почему.
-
Достать конкретные веса из файла сложно. Их нельзя просто открыть, как таблицу, — сначала нужно загрузить модель в код, выполнить десериализацию, то есть преобразовать файл в удобный для работы формат, а затем с помощью Python-скрипта выбрать нужные веса.
-
Сравнивать веса между разными версиями модели неудобно. Придётся открывать оба файла, загружать их в память и отдельно вычислять разницу.
-
Файлы не поддерживают SQL-запросы. Нельзя выбрать веса только для одного слоя или найти нейроны с самыми большими значениями.
Если хранить веса в базе данных, их легко выбирать, фильтровать, сравнивать и агрегировать. Не нужно загружать всю модель в код или писать сложные скрипты.
Как хранить веса в SQL
Обычно веса хранятся в виде матриц — таблиц, где строки и столбцы представляют связи между нейронами разных слоёв, или многомерных массивов — структур, в которых добавляются дополнительные измерения, например, для хранения весов на разных этапах обучения или в нескольких версиях модели. Эти форматы удобны для вычислений, но с ними сложно выполнять быстрый поиск или фильтрацию.
В SQL веса хранятся иначе — в виде таблицы, где каждая строка представляет отдельный вес с указанием модели, слоя и нейрона:
-
model_id — идентификатор модели, чтобы различать веса разных нейросетей;
-
layer — номер слоя, в котором находится вес;
-
neuron — номер нейрона внутри слоя;
-
weight — значение веса.
Пример SQL-запроса для создания такой таблицы в PostgreSQL:
CREATE TABLE weights (
id SERIAL PRIMARY KEY,
model_id INTEGER NOT NULL,
layer INTEGER NOT NULL,
neuron INTEGER NOT NULL,
weight FLOAT NOT NULL
);
Теперь каждый вес хранится как отдельная строка. Например:
id |
model_id |
layer |
neuron |
weight |
1 |
1 |
1 |
1 |
0.23 |
2 |
1 |
1 |
2 |
-0.45 |
3 |
1 |
2 |
1 |
0.78 |
Какие метаданные полезно сохранять
Один вес сам по себе ни о чём не говорит. Чтобы анализировать модель, нужно знать, при каких условиях она обучалась.
Поэтому полезно хранить метаданные — информацию о модели:
-
id — идентификатор модели;
-
architecture — описание структуры модели: количество слоёв, функции активации;
-
learning_rate — скорость обучения;
-
batch_size — размер мини-батча;
-
trained_at — дата обучения (по умолчанию — текущее время).
Пример запроса:
CREATE TABLE models (
id SERIAL PRIMARY KEY,
architecture TEXT NOT NULL,
learning_rate FLOAT NOT NULL,
batch_size INTEGER NOT NULL,
trained_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Теперь веса можно связывать с конкретной моделью через поле model_id
.
Как загрузить веса в SQL
Мы разобрали, как организовать таблицу для хранения весов. Теперь посмотрим, как загрузить туда данные после обучения модели.
Записывать каждую строку вручную неудобно, особенно если у нейросети тысячи или миллионы параметров. Вместо этого можно автоматически загрузить веса из Pandas в SQL с помощью библиотеки SQLAlchemy.
Она не входит в стандартную поставку Python, но её можно установить через консоль:
pip install sqlalchemy
Если база данных — PostgreSQL, понадобится дополнительный драйвер:
pip install psycopg2
Пример кода для загрузки весов:
import pandas as pd
from sqlalchemy import create_engine
# Подготовка данных: веса нейросети в DataFrame
data = {
"model_id": [1, 1, 1],
"layer": [1, 1, 2],
"neuron": [1, 2, 1],
"weight": [0.23, -0.45, 0.78]
}
df = pd.DataFrame(data)
# Подключение к PostgreSQL
engine = create_engine("postgresql://user:password@localhost/dbname")
# Запись весов в таблицу
df.to_sql("weights", engine, if_exists="append", index=False)
Разбор кода:
-
pd.DataFrame(data)
— создаёт таблицу с весами нейросети; -
create_engine("postgresql://user:password@localhost/dbname")
— устанавливает соединение с базой данных. Нужно заменитьuser
,password
,localhost
,dbname
на реальные параметры; -
df.to_sql("weights", engine, if_exists="append", index=False)
— записывает данные в таблицуweights
; -
if_exists="append"
— добавляет новые данные в таблицу, не перезаписывая её; -
index=False
— исключает индекс Pandas, так как в SQL уже естьid
.
Задача 2. Извлечение и анализ весов с помощью SQL
После того как веса нейросети сохранены в базе данных, их можно анализировать с помощью SQL. Это помогает:
-
извлекать нужные веса — например, для конкретной модели или слоя;
-
находить аномалии — слишком большие или слишком малые значения весов;
-
анализировать распределение весов — усреднять, рассчитывать стандартное отклонение;
-
сравнивать версии моделей — отслеживать, как изменяются веса после обучения.
Извлечение весов по модели и слоям
Часто для анализа требуется не вся таблица, а только отдельные данные — например, веса конкретной модели или слоя. Это помогает:
-
выбрать нужные сведения, если в базе хранится несколько обученных нейросетей;
-
отфильтровать веса по слоям, чтобы сравнить их распределение в разных частях архитектуры модели;
-
подготовить данные для анализа, например, для визуализации.
Задача 1: выбрать все веса для конкретной модели.
SELECT * FROM weights WHERE model_id = 1;
Этот запрос возвращает всю информацию из таблицы weights
, но только для модели с model_id = 1
.
Задача 2: выбрать веса из конкретного слоя модели, например, второго.
SELECT * FROM weights WHERE model_id = 1 AND layer = 2;
Запрос фильтрует веса так, чтобы в результат попали только значения, которые относятся к модели 1 и слою 2.
Анализ распределения весов
После извлечения весов важно понять, как они распределены. Это помогает оценить, насколько сбалансирована модель, нет ли слоев с переобучением или, наоборот, слишком слабым влиянием на результат.
Среднее значение весов по слоям
Показывает, у каких слоёв в модели в среднем более высокие или низкие значения параметров, чтобы оценить вклад каждого слоя.
Задача: посчитать средний вес для каждого слоя модели с model_id = 1
.
SELECT layer, AVG(weight) AS avg_weight
FROM weights
WHERE model_id = 1
GROUP BY layer;
Если средний вес слоя близок к нулю, значит, нейроны в нём почти не вносят вклад в предсказания модели. В случае когда у слоя среднее значение заметно выше или ниже, чем у других, это может означать, что он играет слишком большую роль или модель слишком сильно запомнила тренировочные данные — переобучилась.
Пример результата:
layer |
avg_weight |
1 |
0.15 |
2 |
-0.02 |
3 |
0.45 |
Здесь видно, что у третьего слоя средние веса выше, чем у других, а значит, он может сильнее влиять на предсказания.
Стандартное отклонение
Показывает, насколько сильно отличаются веса в одном слое, чтобы понять, стабильно ли обучение.
Задача: рассчитать стандартное отклонение весов для каждого слоя модели с model_id = 1
.
SELECT layer, STDDEV(weight) AS std_weight
FROM weights
WHERE model_id = 1
GROUP BY layer;
Если стандартное отклонение высокое, значит, что веса слоя сильно различаются. Это может указывать на нестабильное обучение. Если разброс минимальный — слой почти не изменился и, возможно, не играет значимой роли.
Пример результата:
layer |
avg_weight |
1 |
0.05 |
2 |
0.20 |
3 |
0.03 |
У второго слоя (layer = 2
) разброс весов больше, чем у остальных. Это может быть признаком переобучения или того, что слой слишком чувствителен к данным.
Поиск аномально больших и малых весов
Как мы уже разобрали в статье, веса, которые сильно отклоняются от остальных, могут влиять на работу модели и снижать точность предсказаний.
Для поиска выбросов также используем SQL-запросы.
Пример 1: найдём 10 самых больших значений из всей таблицы, без фильтрации по слоям.
-- Найти 10 самых больших весов
SELECT weight
FROM weights
ORDER BY weight DESC
LIMIT 10;
Разбор запроса:
-
ORDER BY weight DESC
— сортирует веса в порядке убывания (от больших к меньшим). -
LIMIT 10
— выбирает 10 наибольших значений.
Пример 2: найдём 10 самых малых значений из всей таблицы. В этом случае заменяем сортировку на ASC
:
SELECT weight
FROM weights
ORDER BY weight ASC
LIMIT 10;
Чтобы вывести сразу 10 наибольших и 10 наименьших значений, используем UNION ALL
:
(SELECT weight FROM weights ORDER BY weight DESC LIMIT 10)
UNION ALL
(SELECT weight FROM weights ORDER BY weight ASC LIMIT 10);
Разбор запроса:
-
Первый
SELECT weight
— выбирает 10 самых больших весов (ORDER BY weight DESC
). -
Второй
SELECT weight
— выбирает 10 самых малых весов (ORDER BY weight ASC
). -
UNION ALL
— объединяет оба списка без удаления дубликатов.
Анализ выбросов
После поиска самых больших и малых значений важно понять, насколько они выбиваются из общего распределения. Для этого используют среднее значение и стандартное отклонение.
Обычно выбросами считают веса, которые:
-
превышают среднее + 3 стандартных отклонения;
-
меньше среднего – 3 стандартных отклонения.
Можно выполнить два отдельных запроса, но удобнее объединить их с UNION ALL
, чтобы получить все выбросы сразу.
Вот как выглядит код:
(SELECT weight
FROM weights
WHERE weight > (SELECT AVG(weight) + 3 * STDDEV(weight) FROM weights))
UNION ALL
(SELECT weight
FROM weights
WHERE weight < (SELECT AVG(weight) - 3 * STDDEV(weight) FROM weights));
Разбор запроса
Как вычисляются пороговые значения:
-
AVG(weight)
— показывает типичный вес. -
STDDEV(weight)
— измеряет разброс значений. -
AVG(weight) + 3 * STDDEV(weight)
— вычисляет верхний порог выбросов. Веса выше этого значения считаются аномальными. -
AVG(weight) - 3 * STDDEV(weight)
— вычисляет нижний порог выбросов. Веса ниже этого значения считаются выбросами.
Что делают основные запросы:
-
Первый
SELECT weight
— находит все веса, превышающие верхний порог. -
Второй
SELECT weight
— ищет веса, которые меньше нижнего порога. -
UNION ALL
— объединяет результаты обоих запросов в один список.
Пример результата:
weight |
2.45 |
2.38 |
2.31 |
-1.92 |
-2.05 |
-2.15 |
Здесь значения 2.45, 2.38, 2.31 — это аномально большие веса, а -1.92, -2.05, -2.15 — аномально малые.
Подзадача. Оптимизация SQL-запросов при анализе весов
При работе с весами нейросети в базе данных важно не только правильно извлекать данные, но и делать это быстро. Если в таблице миллионы записей, неэффективные запросы могут значительно замедлить анализ.
Основные проблемы, которые влияют на скорость работы:
-
фильтрация выполняется в Python, а не в SQL, из-за этого передаётся слишком много данных;
-
нет индексов на часто используемые поля, поэтому база данных сканирует всю таблицу вместо быстрого поиска;
-
использование баз неподходящего типа — реляционные БД работают медленнее при аналитических задачах;
-
повторяющиеся вычисления — запросы каждый раз пересчитывают средние значения, вместо использования предвычисленных данных.
Чтобы избежать этих проблем, можно применить несколько оптимизаций.
Оптимизация 1. Фильтрация и агрегация на уровне SQL
Частая ошибка — загружать всю таблицу в Python, а уже потом фильтровать данные. Это замедляет выполнение и расходует лишнюю память.
Как выглядит запрос без оптимизации (фильтрация в Python):
import pandas as pd
import sqlalchemy
# Подключаемся к базе данных PostgreSQL
engine = sqlalchemy.create_engine("postgresql://user:password@localhost/dbname")
# Загружаем всю таблицу весов в Pandas
df = pd.read_sql("SELECT * FROM weights", engine)
# Фильтруем только веса слоя 2 уже в Python
filtered_df = df[df["layer"] == 2]
Что здесь не так:
-
База отправляет всю таблицу, даже если нужен только один слой.
-
Python выполняет фильтрацию вручную, хотя SQL мог бы сразу вернуть только нужные строки.
-
Если записей миллионы, это перегрузит оперативную память.
Запрос с оптимизацией — фильтрация в SQL
Вместо загрузки всей таблицы сразу отбираем только нужные строки:
# Загружаем только строки, где layer = 2
df = pd.read_sql("SELECT * FROM weights WHERE layer = 2", engine)
Что изменилось:
-
база сразу отфильтровывает данные и передаёт в Python только нужные строки;
-
код выполняется быстрее, потому что в памяти меньше данных;
-
снижается нагрузка на сервер.
Дополнительная оптимизация с HAVING
Иногда фильтровать нужно не отдельные строки, а уже агрегированные данные. Например, выбрать только слои, у которых более 100 нейронов.
Это можно сделать так:
SELECT layer, COUNT(neuron) AS neuron_count
FROM weights
GROUP BY layer
HAVING COUNT(neuron) > 100;
Разбор запроса:
-
GROUP BY layer
— группирует строки по слоям, чтобы посчитать количество нейронов в каждом. -
COUNT(neuron) AS neuron_count
— считает количество нейронов в каждом слое. -
HAVING COUNT(neuron) > 100
— оставляет только те слои, где число нейронов больше 100.
Если делать это в Python, придётся загружать все данные, а SQL сразу отфильтрует лишнее.
Оптимизация 2. Ускорение выборки данных
Если база данных при каждом поиске просматривает всю таблицу, запросы выполняются медленно.
Запрос без оптимизации:
SELECT * FROM weights WHERE model_id = 1 AND layer = 2;
Что здесь не так: если weights
— большая таблица, база прочитает все строки перед тем, как найти нужные. Чем больше данных, тем медленнее работает запрос.
Оптимизированный запрос
Добавим индекс, который ускорит поиск:
CREATE INDEX idx_weights_model_layer
ON weights (model_id, layer);
Теперь база сортирует данные по model_id
и layer
, и поиск выполняется быстрее.
SELECT * FROM weights WHERE model_id = 1 AND layer = 2;
Что изменилось:
-
база не перебирает все строки, а использует индекс для быстрого поиска;
-
уменьшается нагрузка на процессор и память.
Оптимизация 3. Использование DuckDB, ClickHouse и TimescaleDB
Для разных задач подходят конкретные инструменты. Если данных немного и важна скорость, можно использовать DuckDB — он выполняет SQL-запросы прямо в памяти. Для работы с большими объёмами лучше подходит ClickHouse, потому что он оптимизирован для аналитики. А если нужно изучать, как данные меняются со временем, удобнее использовать TimescaleDB.
Разберём каждый инструмент.
1. Обработка в памяти с движком DuckDB
Если данные уже в Pandas DataFrame и нет смысла загружать их в базу, можно выполнять SQL-запросы прямо в памяти с помощью DuckDB. Это удобно для быстрого анализа небольших выборок.
Пример запроса к Pandas DataFrame через DuckDB
Устанавливаем библиотеки:
pip install duckdb pandas
Пишем код:
import duckdb
import pandas as pd
# Создаём DataFrame с весами нейросети
data = {
"model_id": [1, 1, 1, 2, 2],
"layer": [1, 1, 2, 1, 2],
"neuron": [1, 2, 1, 3, 4],
"weight": [0.23, -0.45, 0.78, 1.2, -0.9]
}
df = pd.DataFrame(data)
# Выполняем SQL-запрос к DataFrame через DuckDB
result = duckdb.query("""
SELECT model_id, AVG(weight) AS avg_weight
FROM df
GROUP BY model_id
""").df()
print(result) # Вывод результата
Разбор кода:
-
pd.DataFrame(data)
— создаёт DataFrame с весами нейросети. -
duckdb.query(...)
— выполняет SQL-запрос по данным в памяти, не загружая их в базу. -
GROUP BY model_id
— группирует веса по моделям и вычисляет среднее значение. -
.df()
— преобразует результат запроса в Pandas DataFrame.
Пример результата:
model_id |
avg_weight |
1 |
0.05 |
2 |
0.20 |
Если значения сильно различаются, модели могли обучаться по-разному — с разными параметрами или на других данных. Средний вес, близкий к нулю, указывает на балансировку весов.
2. Работа с большими данными с помощью базы ClickHouse
Когда информации слишком много, чтобы хранить её в оперативной памяти, можно использовать ClickHouse. Это колоночная база данных, которая быстрее реляционных БД за счёт особого способа хранения и сжатия данных. Она хорошо подходит для сложной аналитики и работы с миллионами строк.
Пример запроса для анализа весов в ClickHouse:
SELECT model_id,
layer,
AVG(weight) AS avg_weight
FROM weights
GROUP BY model_id, layer
ORDER BY model_id, layer;
Разбор запроса:
-
model_id
— идентификатор модели, группируем данные по ней. -
layer
— номер слоя, группируем веса по слоям. -
AVG(weight) AS avg_weight
— вычисляет средний вес нейронов в каждом слое. -
GROUP BY model_id, layer
— группирует данные по модели и слою. -
ORDER BY model_id, layer
— сортирует результат по модели и слою для удобного анализа.
Запрос показывает средний вес нейронов в каждом слое каждой модели.
Пример результата:
model_id |
layer |
avg_weight |
1 |
1 |
0.12 |
1 |
2 |
-0.34 |
1 |
3 |
0.78 |
2 |
1 |
-0.22 |
2 |
2 |
0.45 |
2 |
3 |
0.31 |
Как интерпретировать:
-
Средний вес в каждом слое: например, у модели 1 в первом слое средний вес 0.12, а во втором — -0.34. Это может означать, что один слой сильнее влияет на предсказания, а другой — меньше.
-
Отрицательные значения: если средний вес слоя отрицательный, возможно, он активируется иначе, чем остальные, или нейросеть использует его для коррекции других слоёв.
-
Разброс между слоями: например, у модели 2 в первом слое средний вес -0.22, а во втором — 0.45. Это показывает, что один, возможно, подавляет признаки, а другой — их усиливает.
3. Работа с временными рядами с помощью TimescaleDB
TimescaleDB — это расширение для PostgreSQL, которое помогает работать с временными рядами. Если данные меняются со временем, например, веса нейросети во время обучения, обычная база может обрабатывать такие запросы медленно. TimescaleDB ускоряет их за счёт:
-
разделения данных на сегменты (sharding) — таблица разбивается на части по времени, поэтому запросы выполняются быстрее;
-
сжатия данных — экономит место и снижает нагрузку на диск;
-
оптимизированных индексов — поиск по временным диапазонам работает быстрее, чем в стандартном PostgreSQL.
Пример SQL-запроса для TimescaleDB
-- Создаём таблицу для хранения весов с отметкой времени
CREATE TABLE weights_timeseries (
time TIMESTAMPTZ NOT NULL, -- Время записи
model_id INT, -- Идентификатор модели
layer INT, -- Номер слоя
weight FLOAT -- Значение веса
);
-- Превращаем таблицу в гипертаблицу TimescaleDB
SELECT create_hypertable('weights_timeseries', 'time');
-- Добавляем данные (пример для одной модели)
INSERT INTO weights_timeseries (time, model_id, layer, weight)
VALUES
('2024-03-12 10:00:00', 1, 1, 0.23),
('2024-03-12 10:05:00', 1, 1, -0.45),
('2024-03-12 10:10:00', 1, 2, 0.78);
-- Получить средний вес за последние 24 часа
SELECT AVG(weight) AS avg_weight
FROM weights_timeseries
WHERE time > now() - interval '1 day';
Разбор запроса:
-
CREATE TABLE weights_timeseries (...)
— создаёт таблицу с временными метками для хранения весов. -
SELECT create_hypertable('weights_timeseries', 'time')
— превращает таблицу в гипертаблицу TimescaleDB, чтобы ускорить обработку временных данных. -
INSERT INTO weights_timeseries (...)
— записывает веса нейросети с временной отметкой. -
SELECT AVG(weight) FROM weights_timeseries WHERE time > now() - interval '1 day'
— вычисляет средний вес за последние 24 часа.
Пример результата
После выполнения запроса получим таблицу со средним значением весов за последние 24 часа:
avg_weight |
||
0.187 |
Как интерпретировать результат:
-
Если средний вес изменился значительно, это может указывать на изменение модели в процессе обучения.
-
Если среднее значение весов близко к нулю, возможно, нейросеть уменьшает влияние сло1в.
-
Если вес изменился слишком сильно в одну из сторон, это может быть признаком переобучения или ошибок в настройке градиентного спуска.
Градиентный спуск — это способ обучения модели, при котором она постепенно корректирует веса, уменьшая ошибку. Слишком резкие изменения делают модель нестабильной: она начинает подстраиваться под случайные шумы, а не находить закономерности.
Использование TimescaleDB помогает отслеживать динамику весов модели, не загружая всю базу данных.
Оптимизация 4. Использование предвычисленных агрегатов
Если таблица весов (weights
) содержит много строк, каждый запрос будет сканировать всю таблицу и выполнять вычисления заново. Это замедляет работу, особенно если один и тот же запрос выполняется часто.
Запрос без оптимизации
Допустим, нам нужно вычислить средний вес в каждом слое модели:
SELECT
model_id,
layer,
AVG(weight) AS avg_weight
FROM weights
GROUP BY model_id, layer;
Что не так:
-
Каждый раз база сканирует всю таблицу и заново считает среднее значение.
-
Если в таблице миллионы строк, обработка может занять много времени.
-
Такой запрос нагружает сервер, потому что выполняет одни и те же вычисления снова и снова.
Оптимизированный запрос
Чтобы не пересчитывать средние веса при каждом запросе, можно сохранить их в отдельную таблицу. Такой подход называется предвычислением агрегатов (precomputed aggregates).
CREATE TABLE weight_aggregates AS
SELECT
model_id,
layer,
AVG(weight) AS avg_weight,
MAX(weight) AS max_weight,
MIN(weight) AS min_weight
FROM weights
GROUP BY model_id, layer;
Разбор запроса:
-
CREATE TABLE weight_aggregates AS SELECT ...
— создаёт новую таблицу с агрегированными значениями. -
AVG(weight) AS avg_weight
— заранее вычисляет средний вес по слоям и моделям. -
MAX(weight) AS max_weight
— сохраняет максимальный вес в слое. -
MIN(weight) AS min_weight
— сохраняет минимальный вес в слое. -
GROUP BY model_id, layer
— группирует данные, чтобы считать метрики для каждой модели и слоя.
При анализе весов используем уже готовые данные:
-- Получаем предвычисленные значения без пересчёта
SELECT * FROM weight_aggregates WHERE model_id = 1;
Что изменилось:
-
теперь запрос просто берёт данные из заранее подготовленной таблицы;
-
серверу не нужно тратить ресурсы на пересчёт, что ускоряет аналитику.
Задача 3. Визуализация весов нейросети
SQL помогает подготовить данные для графиков: выбрать нужные значения, отфильтровать лишнее и заранее рассчитать метрики. Это удобно, когда веса уже хранятся в базе, и их нужно быстро обработать перед визуализацией.
Например, с помощью SQL можно:
-
выбрать веса отдельных слоев, чтобы сравнить их на графике;
-
сгруппировать данные и найти средние или медианные веса для каждого слоя;
-
подготовить данные для гистограмм, линейных графиков и других визуализаций.
Разберём несколько задач с примерами.
Средние веса по слоям
Рассчитаем средние веса в каждом слое, чтобы сравнить, есть ли значительная разница.
-- Считаем средний вес в каждом слое конкретной модели
SELECT layer, AVG(weight) AS avg_weight
FROM weights
WHERE model_id = 1
GROUP BY layer
ORDER BY layer;
Пример результата:
layer |
avg_weight |
1 |
0.12 |
2 |
-0.25 |
3 |
0.33 |
Эти данные можно представить в виде столбчатой диаграммы: по горизонтали номера слоёв, по вертикали — средний вес слоя.
Как построить диаграмму с помощью Python
После получения данных SQL, мы можем визуализировать их с помощью библиотек Pandas, Matplotlib и Seaborn.
Пример кода:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import sqlalchemy
# Подключаемся к базе данных PostgreSQL
engine = sqlalchemy.create_engine("postgresql://user:password@localhost/dbname")
# Выполняем SQL-запрос и загружаем данные в Pandas DataFrame
df = pd.read_sql("""
SELECT layer, AVG(weight) AS avg_weight
FROM weights
GROUP BY layer
ORDER BY layer;
""", engine)
# Создаём столбчатую диаграмму
plt.figure(figsize=(8, 5))
sns.barplot(x=df["layer"], y=df["avg_weight"], palette="viridis")
# Добавляем подписи
plt.xlabel("Слой")
plt.ylabel("Средний вес")
plt.title("Средние веса по слоям")
# Отображаем график
plt.show()
Разбор кода:
-
sqlalchemy.create_engine("postgresql://user:password@localhost/dbname")
— подключается к базе PostgreSQL. -
pd.read_sql(...)
— выполняет SQL-запрос и загружает данные в DataFrame. -
sns.barplot(x=df["layer"], y=df["avg_weight"], palette="viridis")
— строит столбчатую диаграмму, где X — номер слоя, Y — средний вес. -
plt.xlabel("Слой")
,plt.ylabel("Средний вес")
,plt.title("Средние веса по слоям")
— добавляет подписи к графику. -
plt.show()
— отображает диаграмму.
Как использовать этот код:
Установите библиотеки. Для работы с PostgreSQL дополнительно потребуется psycopg2 для подключения через SQLAlchemy.
pip install pandas matplotlib seaborn sqlalchemy psycopg2
Замените данные в create_engine()
на свои параметры подключения к базе.
Запустите код в Jupyter Notebook, Google Colab или другом Python-окружении.

Расчёт медианы по слоям
Среднее значение может не отражать реальную картину, так как его сильно искажают выбросы. Чтобы получить более точное представление о весах, рассчитаем медиану — значение, которое делит распределение пополам.
Медиана не искажается выбросами, отражает наиболее часто встречающиеся значения и помогает лучше понять, как слои обрабатывают информацию.
SQL-запрос для расчёта медианы:
SELECT layer,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY weight)
AS median_weight
FROM weights
WHERE model_id = 1
GROUP BY layer
ORDER BY layer;
Разбор запроса:
-
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY weight)
— вычисляет медиану (50-й процентиль). -
GROUP BY layer
— рассчитывает медианное значение отдельно для каждого слоя. -
WHERE model_id = 1
— фильтрует данные для конкретной модели. -
ORDER BY layer
— упорядочивает результат по номеру слоя.
Пример результата:
layer |
avg_weight |
1 |
0.14 |
2 |
-0.20 |
3 |
0.30 |
Для анализа разницы между слоями лучше всего подходит линейный график — он наглядно показывает изменение медианных значений между слоями.
Python-код для построения графика
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import sqlalchemy
# Подключаемся к базе PostgreSQL
engine = sqlalchemy.create_engine("postgresql://user:password@localhost/dbname")
# Выполняем SQL-запрос и загружаем данные в Pandas DataFrame
df = pd.read_sql("""
SELECT layer,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY weight) AS median_weight
FROM weights
WHERE model_id = 1
GROUP BY layer
ORDER BY layer;
""", engine)
# Создаём линейный график
plt.figure(figsize=(8, 5))
sns.lineplot(x=df["layer"], y=df["median_weight"], marker="o", linestyle="-", color="b")
# Добавляем подписи
plt.xlabel("Слой")
plt.ylabel("Медианный вес")
plt.title("Медианные веса по слоям")
# Отображаем график
plt.show()
Разбор кода:
-
sqlalchemy.create_engine("postgresql://user:password@localhost/dbname")
— подключается к базе PostgreSQL. -
pd.read_sql(...)
— выполняет SQL-запрос и загружает данные в Pandas DataFrame. -
sns.lineplot(x=df["layer"]
,y=df["median_weight"]
,marker="o", linestyle="-"
,color="b")
— строит линейный график медианных весов. -
plt.xlabel("Слой")
— подписывает ось X. -
plt.ylabel("Медианный вес")
— подписывает ось Y. -
plt.title("Медианные веса по слоям")
— добавляет заголовок. -
plt.show()
— отображает график.
Как использовать этот код:
Установите библиотеки. Для работы с PostgreSQL дополнительно потребуется psycopg2 для подключения через SQLAlchemy.
pip install pandas matplotlib seaborn sqlalchemy psycopg2
Замените данные в create_engine()
на свои параметры подключения к базе.
Запустите код в Jupyter Notebook, Google Colab или другом Python-окружении.

Гистограмма распределения весов нейросети
Чтобы понять, какие веса встречаются чаще всего, можно построить гистограмму. Она показывает, равномерно ли распределены веса или есть отклонения.
Пример SQL-запроса:
SELECT width_bucket(weight, -1, 1, 10) AS bucket, -- делим веса на 10 диапазонов от -1 до 1
COUNT(*) AS weight_count -- количество весов в каждом диапазоне
FROM weights
WHERE model_id = 1
GROUP BY bucket
ORDER BY bucket;
Разбор запроса:
-
width_bucket(weight, -1, 1, 10)
— делит веса на 10 диапазонов в интервале от -1 до 1. -
COUNT(*) AS weight_count
— считает, сколько весов попадает в каждый диапазон. -
WHERE model_id = 1
— анализируем веса конкретной модели. -
GROUP BY bucket
— группируем веса по диапазонам. -
ORDER BY bucket
— сортируем диапазоны по возрастанию.
Пример результата:
layer |
avg_weight |
1 |
5 |
2 |
15 |
3 |
30 |
4 |
50 |
5 |
70 |
6 |
60 |
7 |
40 |
8 |
25 |
9 |
10 |
10 |
5 |
После выполнения SQL-запроса можно построить гистограмму с помощью Matplotlib и Seaborn.
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import sqlalchemy
# Подключаемся к базе PostgreSQL
engine = sqlalchemy.create_engine("postgresql://user:password@localhost/dbname")
# Выполняем SQL-запрос и загружаем данные в Pandas DataFrame
df = pd.read_sql("""
SELECT width_bucket(weight, -1, 1, 10) AS bucket, COUNT(*) AS weight_count
FROM weights
WHERE model_id = 1
GROUP BY bucket
ORDER BY bucket;
""", engine)
# Создаём гистограмму
plt.figure(figsize=(8, 5))
sns.barplot(x=df["bucket"], y=df["weight_count"], palette="magma")
# Добавляем подписи
plt.xlabel("Диапазон весов")
plt.ylabel("Количество весов")
plt.title("Распределение весов нейросети")
# Отображаем график
plt.show()
Разбор кода:
-
sqlalchemy.create_engine("postgresql://user:password@localhost/dbname")
— подключается к базе PostgreSQL. -
pd.read_sql(...)
— выполняет SQL-запрос и загружает данные в Pandas DataFrame. -
sns.barplot(x=df["bucket"]
,y=df["weight_count"]
,palette="magma")
— строит гистограмму распределения весов. -
plt.xlabel("Диапазон весов")
— подписывает ось X. -
plt.ylabel("Количество весов")
— подписывает ось Y. -
plt.title("Распределение весов нейросети")
— добавляет заголовок. -
plt.show()
— отображает график.
Как использовать этот код:
Установите библиотеки. Для работы с PostgreSQL дополнительно потребуется psycopg2 для подключения через SQLAlchemy.
pip install pandas matplotlib seaborn sqlalchemy psycopg2
Замените данные в create_engine()
на свои параметры подключения к базе.
Запустите код в Jupyter Notebook, Google Colab или другом Python-окружении.

Задача 4. Сравнение моделей с помощью SQL
SQL помогает не только анализировать веса внутри одной модели, но и отслеживать, как они меняются в процессе обучения. Один из способов — сравнить веса на разных эпохах.
Это позволяет понять:
-
Не остановилось ли обучение: если веса почти не изменяются, модель перестала учиться и больше не корректирует свои ошибки.
-
Нет ли переобучения: если изменения слишком резкие, модель может слишком сильно подстраиваться не только под закономерности в данных, но и под случайные колебания (шум).
Анализ изменений весов между эпохами обучения
С помощью SQL-запроса можно найти разницу весов между двумя эпохами для одной модели.
SELECT w1.layer, w1.neuron,
w2.weight - w1.weight AS weight_change
FROM weights w1
JOIN weights w2
ON w1.model_id = w2.model_id
AND w1.layer = w2.layer
AND w1.neuron = w2.neuron
WHERE w1.epoch = 10
AND w2.epoch = 20
ORDER BY weight_change DESC;
Как работает этот запрос:
-
JOIN weights w2 ON w1.model_id = w2.model_id ...
— объединяет веса одной модели, чтобы сравнить их на разных эпохах. -
WHERE w1.epoch = 10 AND w2.epoch = 20
— выбирает веса на 10-й и 20-й эпохах. -
w2.weight - w1.weight AS weight_change
— вычисляет разницу в весах между эпохами. -
ORDER BY weight_change DESC
— сортирует от самых больших изменений к самым маленьким.
Пример результата:
layer |
neuron |
weight_change |
1 |
2 |
0.05 |
2 |
5 |
-0.12 |
3 |
1 |
0.30 |
3 |
4 |
-0.50 |
Как интерпретировать:
-
Небольшие изменения (0.05) — модель почти перестала учиться.
-
Резкие скачки (-0.50) — модель неустойчива, возможны проблемы с переобучением.
Если данных много, таблицу сложно анализировать, и можно построить график:
-
гистограмма — показывает, какие изменения встречаются чаще всего;
-
линейный график — помогает увидеть разницу между слоями.
Выявление переобучения по изменению весов
Переобучение можно определить по весам, которые практически не меняются между эпохами. Это может означать, что алгоритм либо достиг предела своего обучения, либо перестал корректировать ошибки.
Пример запроса:
SQL-запрос вычисляет разницу весов между двумя эпохами и сортирует их по наименьшему изменению.
SELECT w1.layer, w1.neuron,
ABS(w2.weight - w1.weight) AS weight_change
FROM weights w1
JOIN weights w2
ON w1.model_id = w2.model_id
AND w1.layer = w2.layer
AND w1.neuron = w2.neuron
WHERE w1.epoch = 10
AND w2.epoch = 20
ORDER BY weight_change ASC;
Как работает этот запрос:
-
ABS(w2.weight - w1.weight) AS weight_change
— вычисляет, насколько изменился вес нейрона. -
ORDER BY weight_change ASC
— сортирует результаты, показывая сначала веса с минимальными изменениями.
Пример результата:
layer |
neuron |
weight_change |
2 |
3 |
0.001 |
1 |
7 |
0.003 |
3 |
1 |
0.005 |
Как интерпретировать:
-
Если веса почти не меняются (0.001), значит, модель перестала учиться.
-
Если разница небольшая (0.005), значит, обучение замедлилось, но ещё продолжается.
-
Если у большинства нейронов изменения минимальные, возможно, обучение завершилось слишком рано или модель не успела как следует настроить веса.
Как можно визуализировать:
-
Линейный график — помогает увидеть среднее изменение весов по слоям, выделяя слои, которые корректируются сильнее или слабее.
-
Гистограмма — показывает, насколько большой разброс изменений весов в модели.
Оценка стабильности модели
Стабильность модели зависит от разброса её весов. Сильные колебания могут указывать на переобучение, а слишком маленький разброс — на недостаточное обучение.
Пример запроса:
Этот запрос вычисляет стандартное отклонение весов, чтобы сравнить модели по степени их стабильности. Чем выше разброс, тем больше веса изменяются во время обучения.
SELECT model_id,
STDDEV(weight) AS weight_stddev
FROM weights
GROUP BY model_id
ORDER BY weight_stddev DESC;
Как работает этот запрос:
-
STDDEV(weight) AS weight_stddev
— считает, насколько разбросаны веса в модели. -
GROUP BY model_id
— группирует данные по моделям. -
ORDER BY weight_stddev DESC
— сортирует модели от самых нестабильных к более стабильным.
Пример результата:
layer |
weight_stddev |
3 |
0.45 |
1 |
0.30 |
2 |
0.15 |
Как интерпретировать:
-
Высокое значение (0.45) — модель нестабильна, возможны переобученные слои.
-
Среднее (0.30) — баланс между гибкостью и стабильностью.
-
Низкое (0.15) — веса почти не меняются, модель может быть недостаточно обученной.
Какой график построить:
-
Гистограмма — показывает, какие модели имеют больший разброс весов.
-
Столбчатая диаграмма — помогает сравнить модели между собой.
Если моделей немного, достаточно просто посмотреть таблицу.
DuckDB для быстрого сравнения моделей
Если модели хранятся в Pandas DataFrame, их можно анализировать без отдельной SQL-базы, используя DuckDB.
Например, можно сравнить средний разброс весов в разных моделях:
import duckdb
import pandas as pd
# Создаём DataFrame с весами моделей
data = {
"model_id": [1, 1, 2, 2, 3, 3],
"layer": [1, 2, 1, 2, 1, 2],
"weight": [0.12, -0.34, 0.22, -0.45, 0.31, -0.28]
}
df = pd.DataFrame(data)
# Выполняем SQL-запрос в памяти с DuckDB
result = duckdb.query("""
SELECT model_id, STDDEV(weight) AS weight_stddev
FROM df
GROUP BY model_id
ORDER BY weight_stddev DESC
""").df()
print(result)
Разбор кода:
-
SELECT model_id, STDDEV(weight)
— вычисляет разброс весов внутри каждой модели. -
FROM df
— обращается к Pandas DataFrame как к SQL-таблице. -
GROUP BY model_id
— сравнивает модели между собой. -
ORDER BY weight_stddev DESC
— показывает модели с наибольшими изменениями весов первыми.
Что делать дальше: тренировки и углубление в тему
После того как разобрались, как хранить и анализировать веса моделей в SQL, можно углубиться в тему и попробовать новые подходы.
Работа с новыми данными
Попробуйте загрузить веса своей модели или использовать готовые из torchvision.models
(PyTorch) или tf.keras.applications
(TensorFlow). Затем изучите, как они распределяются в разных слоях. Можно проверить, есть ли среди них выбросы — например, слои с аномально большими весами.
Автоматизация анализа
Настройте процесс так, чтобы данные загружались в SQL автоматически. Для этого можно написать скрипт на Python, который после обучения модели сохраняет веса через pandas.to_sql()
. В базе данных создайте представление (VIEW
), чтобы статистику не приходилось пересчитывать при каждом запросе.
Визуализация весов
Попробуйте построить графики, чтобы лучше понять распределение весов. Можно сделать гистограмму для отдельных слоёв или сравнить средние веса разных моделей на столбчатой диаграмме. SQL поможет подготовить данные, а для визуализации используйте Matplotlib и Seaborn.
Анализ предсказаний модели
Если хотите глубже разобраться, как модель принимает решения, изучите методы интерпретации:
-
SHAP — анализирует, какие признаки больше всего влияют на предсказание.
-
LIME — объясняет, какие части входных данных критичны для модели.
-
Captum (PyTorch) — показывает градиенты и строит карты важности признаков.
Как SQL помогает в машинном обучении
Изучите, как применять SQL не только для анализа весов, но и в других задачах. Например, можно логировать предсказания модели, сохраняя входные данные и результаты, чтобы анализировать ошибки. Затем использовать SQL для очистки данных перед обучением — находить выбросы, дубликаты и аномалии. А если хранить параметры разных моделей в базе, получится сравнивать их после дообучения и отслеживать, какие настройки дали лучший результат.
SQL — это не только инструмент работы с базами, но и крутой способ разобраться в логике нейросетей. Главное — экспериментировать и находить собственные способы анализа моделей. Начните с простых SQL-запросов, а затем попробуй автоматизировать их и подключить другие инструменты.

Владение SQL делает вас сильнее на рынке: с ним проще войти в аналитику, работать с данными и даже развиваться в разработке. Уверенно писать запросы, освоить SQL на практике и открыть большие перспективы в карьере позволит профессиональное обучение на курсе «SQL и получение данных». С промокодом DSHABR10 цена ещё приятнее.
Автор: kirakirap