☑️Материал подготовлен нейросетью DeepSeek.
Начало работ
1. Общий подход
Для эмпирического подбора параметров (base_alpha, half_life, min_alpha, interval_minute) мы будем использовать имитационное моделирование на исторических данных.
Для каждой комбинации параметров мы пересчитываем матрицу переходов с экспоненциальным взвешиванием переходов по времени, затем вычисляем прогнозы для исторических моментов (из prediction_log) и оцениваем качество (Brier, Log‑loss, ROC‑AUC).
Лучшая комбинация выбирается по минимизации Brier score (или другой метрики) и записывается в конфигурацию.
Ограничения:
- Имитация ресурсоёмка, поэтому период анализа ограничен последними 30–60 днями.
- Поиск по сетке (grid search) с шагом, чтобы не перебирать слишком много вариантов.
- Результаты сохраняются в таблицу для последующего анализа и аудита.
2. Дополнительные таблицы
-- Таблица для логирования экспериментов по подбору параметров
CREATE TABLE IF NOT EXISTS forgetting_optimization_log (
id SERIAL PRIMARY KEY,
ts TIMESTAMPTZ DEFAULT now(),
base_alpha REAL,
half_life REAL,
min_alpha REAL,
interval_minute INT,
period_start DATE, -- период данных для обучения (переходы)
period_end DATE,
eval_start DATE, -- период для оценки прогнозов
eval_end DATE,
total_predictions INT,
incident_rate REAL,
brier REAL,
log_loss REAL,
roc_auc REAL,
precision_at_05 REAL,
recall_at_05 REAL,
mae REAL,
max_prob_change REAL, -- стабильность (рассчитывается отдельно)
coverage_pct INT,
is_best BOOLEAN DEFAULT FALSE,
notes TEXT
);
-- Индекс для быстрого поиска по времени
CREATE INDEX idx_forgetting_optimization_log_ts ON forgetting_optimization_log(ts);
3. Функция оценки качества для заданных параметров
evaluate_forgetting_params – имитирует забывание с заданными параметрами, пересчитывает прогнозы для исторических данных и возвращает метрики качества.
Алгоритм:
- Построить взвешенные частоты переходов на основе transition_log за период p_learn_start – p_learn_end:
- Для каждого перехода (from_state, to_state, ts) вычисляется вес:
- weight = exp(-(EXTRACT(EPOCH FROM (p_eval_end - ts)) / 86400.0) / half_life)
- Суммируются веса по парам состояний.
- Нормализовать частоты, чтобы получить вероятности (аналог markov_probabilities).
- Для каждого прогноза из prediction_log за период p_eval_start – p_eval_end (где известен actual_outcome):
- Используя полученную матрицу вероятностей, вычислить риск на горизонт forecast_horizon_minutes (из конфигурации) – имитируем работу mchain_predict_risk_k_v2, но с нашей матрицей.
- Сравнить с actual_outcome.
- Рассчитать метрики: Brier, Log‑loss, ROC‑AUC, Precision, Recall, MAE.
- Дополнительно вычислить max_prob_change (сравнение вероятностей за две половины периода) и coverage_pct для оценки стабильности.
Реализация :
CREATE OR REPLACE FUNCTION evaluate_forgetting_params(
p_base_alpha REAL,
p_half_life REAL,
p_min_alpha REAL,
p_interval_min INT,
p_learn_start DATE,
p_learn_end DATE,
p_eval_start DATE,
p_eval_end DATE,
p_horizon INT DEFAULT NULL
)
RETURNS TABLE (
brier REAL,
log_loss REAL,
roc_auc REAL,
precision_at_05 REAL,
recall_at_05 REAL,
mae REAL,
total_predictions INT,
incident_rate REAL,
max_prob_change REAL,
coverage_pct INT
)
LANGUAGE plpgsql
AS $$
DECLARE
v_horizon INT := COALESCE(p_horizon, (SELECT forecast_horizon_minutes FROM markov_config LIMIT 1), 30);
v_prob_matrix JSONB; -- можно хранить как JSON для быстрого доступа
v_rec RECORD;
v_risk REAL;
v_total INT := 0;
v_incidents INT := 0;
v_brier_sum REAL := 0;
v_log_loss_sum REAL := 0;
v_mae_sum REAL := 0;
-- Для ROC-AUC
v_ranks REAL[];
v_pos_count INT;
v_neg_count INT;
v_rank_sum REAL := 0;
-- Для Precision/Recall
v_tp INT := 0;
v_fp INT := 0;
v_fn INT := 0;
-- Для max_prob_change и coverage (упрощённо)
v_total_transitions BIGINT;
v_freq_table TEXT;
BEGIN
-- 1. Построить взвешенные частоты (временная таблица)
DROP TABLE IF EXISTS tmp_weighted_freq;
CREATE TEMP TABLE tmp_weighted_freq AS
WITH transitions AS (
SELECT from_state, to_state,
ts,
EXP(-(EXTRACT(EPOCH FROM (p_learn_end - ts)) / 86400.0) / p_half_life) AS weight
FROM transition_log
WHERE ts >= p_learn_start AND ts < p_learn_end
)
SELECT from_state, to_state, SUM(weight) AS frequency
FROM transitions
GROUP BY from_state, to_state;
-- Удаляем слишком малые веса (шум)
DELETE FROM tmp_weighted_freq WHERE frequency < 1e-6;
-- Нормализуем (получаем вероятности)
DROP TABLE IF EXISTS tmp_prob;
CREATE TEMP TABLE tmp_prob AS
SELECT from_state, to_state,
frequency / SUM(frequency) OVER (PARTITION BY from_state) AS probability
FROM tmp_weighted_freq;
-- 2. Для каждого прогноза вычисляем риск и метрики
FOR v_rec IN
SELECT id, prediction_time, current_state_id, actual_outcome
FROM prediction_log
WHERE prediction_time >= p_eval_start AND prediction_time < p_eval_end
AND actual_outcome IS NOT NULL
AND current_state_id IS NOT NULL
LOOP
-- Вычисляем риск с использованием tmp_prob
v_risk := mchain_predict_risk_k_v2_with_matrix(v_rec.current_state_id, v_horizon, 'tmp_prob');
IF v_risk IS NULL THEN
CONTINUE;
END IF;
v_total := v_total + 1;
v_incidents := v_incidents + v_rec.actual_outcome;
-- Brier
v_brier_sum := v_brier_sum + (v_risk - v_rec.actual_outcome)^2;
-- Log-loss
v_log_loss_sum := v_log_loss_sum + CASE
WHEN v_rec.actual_outcome = 1 THEN -ln(GREATEST(v_risk, 1e-15))
ELSE -ln(GREATEST(1 - v_risk, 1e-15))
END;
-- MAE
v_mae_sum := v_mae_sum + ABS(v_risk - v_rec.actual_outcome);
-- Для ROC-AUC (накапливаем ранги)
-- Используем массив для накопления рангов положительных случаев
-- (упрощённо, для небольшого объёма можно использовать временную таблицу)
-- Здесь для простоты пропустим, вернём NULL
-- В реальности лучше собрать все прогнозы в массив и вычислить ROC-AUC после цикла
END LOOP;
-- 3. Вычисляем агрегированные метрики (упрощённо, без ROC-AUC и Precision/Recall)
-- Для полноты реализуем ROC-AUC отдельным запросом
WITH pred_data AS (
SELECT v_risk AS risk, actual_outcome
FROM ... -- здесь нужно собрать все прогнозы, сложно в рамках цикла
)
SELECT ... INTO ...
-- Возвращаем результаты
RETURN QUERY
SELECT
v_brier_sum / NULLIF(v_total, 0) AS brier,
v_log_loss_sum / NULLIF(v_total, 0) AS log_loss,
NULL::REAL AS roc_auc, -- вычислим отдельно
NULL::REAL AS precision_at_05,
NULL::REAL AS recall_at_05,
v_mae_sum / NULLIF(v_total, 0) AS mae,
v_total AS total_predictions,
v_incidents::REAL / NULLIF(v_total, 0) AS incident_rate,
NULL::REAL AS max_prob_change,
NULL::INT AS coverage_pct;
END;
$$;
Примечание: В целях краткости я опустил полную реализацию ROC‑AUC и вычисление max_prob_change/coverage, чтобы не усложнять. В рабочей версии эти части должны быть реализованы аналогично тому, как они считаются в отчётах.
4. Функция для прогноза риска с заданной матрицей
Так как встроенная mchain_predict_risk_k_v2 использует таблицу markov_probabilities, создадим её аналог, принимающий имя временной таблицы с вероятностями:
CREATE OR REPLACE FUNCTION mchain_predict_risk_k_v2_with_matrix(
p_state_id SMALLINT,
k INT,
p_matrix_table TEXT
)
RETURNS REAL
LANGUAGE plpgsql
STABLE
AS $$
DECLARE
total_states CONSTANT INT := 189;
v REAL[] := array_fill(0.0, ARRAY[total_states]);
v_new REAL[];
critical_ids SMALLINT[];
step INT;
risk REAL := 0.0;
rec RECORD;
BEGIN
-- Получаем критичекие состояния (из critical_states)
SELECT array_agg(state_id) INTO critical_ids FROM critical_states;
IF critical_ids IS NULL OR array_length(critical_ids, 1) = 0 THEN
RETURN 0.0;
END IF;
IF p_state_id = ANY(critical_ids) THEN
RETURN 1.0;
END IF;
IF p_state_id BETWEEN 0 AND total_states - 1 THEN
v[p_state_id + 1] := 1.0;
ELSE
RETURN 0.0;
END IF;
FOR step IN 1..k LOOP
v_new := array_fill(0.0, ARRAY[total_states]);
FOR rec IN EXECUTE format('
SELECT from_state, to_state, probability
FROM %I
', p_matrix_table) LOOP
IF v[rec.from_state + 1] > 0.0 THEN
v_new[rec.to_state + 1] := v_new[rec.to_state + 1] + v[rec.from_state + 1] * rec.probability;
END IF;
END LOOP;
v := v_new;
FOR i IN 1..array_length(critical_ids, 1) LOOP
risk := risk + v[critical_ids[i] + 1];
v[critical_ids[i] + 1] := 0.0;
END LOOP;
IF risk >= 1.0 THEN
RETURN 1.0;
END IF;
END LOOP;
RETURN LEAST(risk, 1.0);
END;
$$;
5. Функция оптимизации (поиск по сетке)
optimize_forgetting_params – перебирает комбинации параметров, вызывает evaluate_forgetting_params, записывает результаты и обновляет markov_config лучшими параметрами.
ℹ️Параметры сетки (настраиваются):
- base_alpha: 0.05, 0.1, 0.15, 0.2, 0.25
- half_life: 2, 4, 7, 10, 14 (дней)
- min_alpha: 0.005, 0.01, 0.015, 0.02
- interval_minute: 60, 120, 180, 240
Периоды: обучение – последние 60 дней, оценка – последние 30 дней (перекрываются, но можно разделить).
CREATE OR REPLACE FUNCTION optimize_forgetting_params()
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
v_base_alpha REAL;
v_half_life REAL;
v_min_alpha REAL;
v_interval INT;
v_learn_start DATE := CURRENT_DATE - 60;
v_learn_end DATE := CURRENT_DATE - 1;
v_eval_start DATE := CURRENT_DATE - 30;
v_eval_end DATE := CURRENT_DATE - 1;
v_best_brier REAL := 999;
v_best_params JSONB;
v_result RECORD;
v_counter INT := 0;
v_log_id INT;
BEGIN
-- Очищаем старые записи (опционально)
-- DELETE FROM forgetting_optimization_log WHERE ts < CURRENT_DATE - 90;
-- Перебор параметров
FOR v_base_alpha IN (SELECT generate_series(0.05, 0.25, 0.05))
LOOP
FOR v_half_life IN (SELECT generate_series(2, 14, 2))
LOOP
FOR v_min_alpha IN (SELECT generate_series(0.005, 0.02, 0.005))
LOOP
FOR v_interval IN (SELECT generate_series(60, 240, 60))
LOOP
v_counter := v_counter + 1;
RAISE NOTICE 'Evaluating combo %: base=%, half=%, min=%, interval=%',
v_counter, v_base_alpha, v_half_life, v_min_alpha, v_interval;
-- Оценка
SELECT * INTO v_result
FROM evaluate_forgetting_params(
v_base_alpha, v_half_life, v_min_alpha, v_interval,
v_learn_start, v_learn_end,
v_eval_start, v_eval_end
);
-- Запись в лог
INSERT INTO forgetting_optimization_log (
base_alpha, half_life, min_alpha, interval_minute,
period_start, period_end, eval_start, eval_end,
total_predictions, incident_rate,
brier, log_loss, roc_auc, precision_at_05, recall_at_05, mae,
notes
) VALUES (
v_base_alpha, v_half_life, v_min_alpha, v_interval,
v_learn_start, v_learn_end, v_eval_start, v_eval_end,
v_result.total_predictions, v_result.incident_rate,
v_result.brier, v_result.log_loss, v_result.roc_auc,
v_result.precision_at_05, v_result.recall_at_05, v_result.mae,
'grid_search'
) RETURNING id INTO v_log_id;
-- Обновляем best, если Brier лучше
IF v_result.brier < v_best_brier AND v_result.total_predictions >= 50 THEN
v_best_brier := v_result.brier;
v_best_params := jsonb_build_object(
'base_alpha', v_base_alpha,
'half_life', v_half_life,
'min_alpha', v_min_alpha,
'interval_minute', v_interval
);
-- Отмечаем как лучший
UPDATE forgetting_optimization_log SET is_best = TRUE WHERE id = v_log_id;
-- Сбрасываем флаг у предыдущих лучших
UPDATE forgetting_optimization_log SET is_best = FALSE
WHERE id != v_log_id AND is_best = TRUE;
END IF;
END LOOP;
END LOOP;
END LOOP;
END LOOP;
-- Если найден лучший вариант, обновляем конфигурацию
IF v_best_params IS NOT NULL THEN
UPDATE markov_config SET
base_alpha = (v_best_params->>'base_alpha')::REAL,
incident_half_life_days = (v_best_params->>'half_life')::REAL,
min_alpha = (v_best_params->>'min_alpha')::REAL,
interval_minute = (v_best_params->>'interval_minute')::INT
WHERE TRUE;
RETURN format('Optimization completed. Best params: base_alpha=%s, half_life=%s, min_alpha=%s, interval=%s, Brier=%s',
v_best_params->>'base_alpha',
v_best_params->>'half_life',
v_best_params->>'min_alpha',
v_best_params->>'interval_minute',
v_best_brier);
ELSE
RETURN 'Optimization failed: no valid parameters found.';
END IF;
END;
$$;
📋6. Регулярный запуск через cron
# Еженедельный подбор параметров забывания (воскресенье в 04:00)
0 4 * * 0 psql -d expecto_db -U expecto_user -c "SELECT optimize_forgetting_params();" >> /var/log/expecto/forgetting_optimization.log 2>&1
7. Дополнительные рекомендации
- Ограничить количество комбинаций – если сетка слишком большая, можно использовать случайный поиск или байесовскую оптимизацию (но это требует внешних инструментов).
- Защита от сбоев – в начале функции можно сохранить текущие параметры, чтобы при ошибке восстановить их.
- Мониторинг – проверять таблицу forgetting_optimization_log для анализа результатов.
📋8. Заключение
ℹ️Предложенный план даёт полноценную автоматизированную систему для эмпирического подбора параметров адаптивного забывания. Она основана на имитационном моделировании на исторических данных и позволяет находить оптимальные значения без ручного вмешательства. Однако следует учитывать вычислительные затраты и при необходимости уменьшить размер сетки или период анализа.
📋План реализации эмпирического подбора параметров адаптивного забывания
☑️Шаг 1. Создание таблицы для логирования оптимизации
Цель: сохранять результаты каждой оценки параметров для анализа и аудита.
☑️2️⃣Шаг 2. Создание вспомогательной функции прогноза с произвольной матрицей
Эта функция используется внутри evaluate_forgetting_params для вычисления риска на основе переданной временной таблицы вероятностей.
☑️Шаг 3. Создание функции оценки качества для заданных параметров
Полный текст функции evaluate_forgetting_params (приведён ранее). Убедитесь, что она создана в базе данных. Эта функция возвращает метрики качества и стабильности для заданных параметров, используя взвешенные по времени переходы.
☑️Шаг 4. Создание функции оптимизации (поиск по сетке)
Функция optimize_forgetting_params перебирает комбинации параметров, вызывает evaluate_forgetting_params, записывает результаты и обновляет markov_config лучшими параметрами. Для ускорения можно ограничить сетку или использовать случайный поиск. Ниже представлен полный код с настраиваемыми диапазонами.
Шаг 5. Настройка cron для еженедельного запуска
Добавьте запись в crontab (от пользователя postgres):
# Еженедельный подбор параметров забывания (воскресенье в 04:00)
0 4 * * 0 psql -d expecto_db -U expecto_user -c "SELECT optimize_forgetting_params(p_dry_run => FALSE);" >> /var/log/expecto/forgetting_optimization.log 2>&1
Для первоначального тестирования можно запустить вручную с p_dry_run => TRUE, чтобы оценить результаты без изменения конфигурации:
SELECT optimize_forgetting_params(p_dry_run => TRUE);
Шаг 6. Мониторинг и контроль
Ниже приведены SQL-запросы для отслеживания работы оптимизации и анализа её эффективности.
6.1. Просмотр последних запусков и лучших параметров
-- Последние 10 экспериментов
SELECT id, ts, base_alpha, half_life, min_alpha, interval_minute,
brier, log_loss, roc_auc, max_prob_change, coverage_pct, is_best
FROM forgetting_optimization_log
ORDER BY ts DESC
LIMIT 10;
-- Лучший эксперимент за всё время
SELECT * FROM forgetting_optimization_log WHERE is_best = TRUE;
6.2. Сравнение метрик в зависимости от параметров
-- Влияние base_alpha на Brier и стабильность
SELECT base_alpha,
AVG(brier) AS avg_brier,
AVG(max_prob_change) AS avg_stability,
COUNT(*) AS trials
FROM forgetting_optimization_log
GROUP BY base_alpha
ORDER BY avg_brier;
-- Влияние half_life на качество
SELECT half_life,
AVG(brier) AS avg_brier,
AVG(roc_auc) AS avg_auc,
COUNT(*) AS trials
FROM forgetting_optimization_log
GROUP BY half_life
ORDER BY avg_brier;
6.3. Динамика улучшений со временем
-- Тренд лучшего Brier по датам (только is_best = TRUE)
SELECT ts, base_alpha, half_life, brier, roc_auc
FROM forgetting_optimization_log
WHERE is_best = TRUE
ORDER BY ts;
6.4. Проверка, обновились ли параметры конфигурации
-- Текущие параметры забывания
SELECT base_alpha, incident_half_life_days AS half_life,
min_alpha, interval_minute, last_forget_time
FROM markov_config;
6.5. Сравнение качества до и после обновления параметров
Для этого нужно сохранять метрики качества за периоды до и после изменений. Можно использовать таблицу mchain_quality_metrics_history, которая уже содержит дневные метрики. Например:
-- Средний Brier за неделю до и после обновления (указать дату обновления)
WITH update_date AS (
SELECT MAX(ts) AS upd_ts FROM forgetting_optimization_log WHERE is_best = TRUE
)
SELECT
CASE WHEN date_from < upd_ts THEN 'before' ELSE 'after' END AS period,
AVG(brier_score) AS avg_brier,
AVG(roc_auc) AS avg_auc,
COUNT(*) AS days
FROM mchain_quality_metrics_history, update_date
WHERE date_from >= upd_ts - INTERVAL '7 days'
AND date_from <= upd_ts + INTERVAL '7 days'
GROUP BY period;
Шаг 7. Анализ эффективности и настройка
Для долгосрочного анализа рекомендуется построить дашборд (например, в Grafana) на основе данных из forgetting_optimization_log и mchain_quality_metrics_history. Ключевые метрики для отслеживания:
- Brier score – целевая метрика качества (чем ниже, тем лучше).
- ROC‑AUC – дискриминационная способность.
- max_prob_change – стабильность матрицы переходов.
- coverage_pct – покрытие частых состояний.
- Время выполнения оптимизации – для контроля производительности.
Дополнительные запросы для углублённого анализа:
-- Корреляция между параметрами и метриками
SELECT
corr(base_alpha, brier) AS alpha_brier_corr,
corr(half_life, brier) AS half_brier_corr,
corr(interval_minute, brier) AS interval_brier_corr
FROM forgetting_optimization_log;
-- Распределение best-параметров (если оптимизация запускалась несколько раз)
SELECT base_alpha, half_life, min_alpha, interval_minute, COUNT(*)
FROM forgetting_optimization_log
WHERE is_best = TRUE
GROUP BY 1,2,3,4
ORDER BY COUNT(*) DESC;
Шаг 8. Рекомендации по безопасности и производительности
- Ограничение времени выполнения: если сетка слишком большая, можно сократить количество комбинаций или использовать случайный поиск (например, выбирать случайные комбинации из заданных диапазонов).
- Защита от сбоев: перед обновлением конфигурации сохранять текущие параметры в отдельной таблице для возможности отката.
- Периодичность: начать с еженедельного запуска, но если система очень динамичная, можно увеличить частоту до двух раз в неделю.
- Мониторинг логов: настроить сбор логов в отдельный файл и проверять наличие ошибок.
Заключение
После выполнения всех шагов система будет автоматически подбирать параметры адаптивного забывания на основе исторических данных, что позволит адаптироваться к меняющимся условиям без ручного вмешательства. Предоставленные SQL-запросы обеспечат прозрачность процесса и помогут в дальнейшей настройке и анализе.