Честная статистика вместо «магии ИИ»: как я построил прогнозный движок без нейросетей#
Каждое второе описание в App Store сегодня обещает «AI-powered insights». Открываешь приложение — а там либо промпт к внешней LLM, либо чёрный ящик, который выдаёт «риск высокий» без единого объяснения почему. Когда я делал MeteoHealth — приложение, которое ищет связи между погодой, сном, активностью и самочувствием, — я сознательно пошёл другим путём. Внутри не нейросеть и не Core ML, а классическая статистика: корреляции Пирсона, дисперсионный анализ с p-значениями, поправка Бенджамини–Хохберга на множественные сравнения. Всё считается на устройстве, офлайн, и главное — каждый вывод можно объяснить одним предложением. В этой статье — как устроен этот движок и почему я считаю честную статистику более сильным продуктовым решением, чем модный «AI-powered» ярлык.
Хайп «AI-powered» и почему я насторожился#
Проблема не в том, что нейросети плохие — для распознавания изображений или генерации текста они незаменимы. Проблема в том, что «AI» стало маркетинговым словом, которое клеят на что угодно, включая простейшие if-else правила. В здравоохранении это особенно опасно: пользователь видит «риск обострения 78%» и не может спросить у приложения «почему». Регуляторы это тоже заметили — например, EU AI Act относит медицинские ИИ-системы к категориям риска и с августа 2026 требует от систем высокого риска объяснимости результатов для тех, кто ими пользуется. В академической литературе это называют «black box problem»: модель предсказывает, но не объясняет ход рассуждений, а пост-хок объяснения (LIME, SHAP и подобные) лишь аппроксимируют реальную логику, а не воспроизводят её — критики прямо пишут, что это может не решать проблему доверия, а маскировать её.
Для приложения о самочувствии это неприемлемо вдвойне. Если MeteoHealth говорит пользователю, что у него повысится риск головной боли, человек должен понимать: это потому, что упало атмосферное давление и одновременно сократился сон — а не потому, что «так решила модель». Отсюда и решение: строить движок на статистике, где объяснимость встроена в саму математику, а не пришита сверху отдельным модулем.
Корреляция Пирсона: первый инструмент движка#
Первый и самый простой инструмент в движке — коэффициент корреляции Пирсона. Он показывает, насколько линейно связаны две величины: например, атмосферное давление и оценка самочувствия за день. Значение от −1 до 1: ноль — связи нет, около единицы по модулю — связь сильная.
/// Pearson correlation coefficient between two data series
func pearsonCorrelation(_ x: [Double], _ y: [Double]) -> Double? {
guard x.count == y.count, x.count > 1 else { return nil }
let n = Double(x.count)
let meanX = x.reduce(0, +) / n
let meanY = y.reduce(0, +) / n
var numerator = 0.0
var sumSqX = 0.0
var sumSqY = 0.0
for i in 0..<x.count {
let dx = x[i] - meanX
let dy = y[i] - meanY
numerator += dx * dy
sumSqX += dx * dx
sumSqY += dy * dy
}
let denominator = (sumSqX * sumSqY).squareRoot()
guard denominator != 0 else { return nil }
return numerator / denominator
}Ничего магического: суммы, средние, квадратный корень. Но у этого простого кода есть важное свойство — я могу открыть код в отладчике, подставить реальные данные пользователя и увидеть ровно те же числа, которые видит движок. Ни одна нейросеть такой прозрачности не даёт: даже небольшая полносвязная сеть с парой скрытых слоёв — это уже тысячи весов, из которых невозможно вывести человекочитаемое «почему».
Дисперсионный анализ, p-значения и проблема множественных сравнений#
Корреляции недостаточно — нужно понимать, насколько ей вообще можно доверять на конкретном объёме данных. Здесь в дело вступает дисперсионный анализ (ANOVA) и p-значения: они отвечают на вопрос «а не мог ли этот результат получиться случайно, если на самом деле никакой связи нет?». Чем меньше p-значение, тем менее вероятно, что найденная закономерность — просто шум.
Но здесь кроется классическая статистическая ловушка. MeteoHealth одновременно проверяет десятки пар «фактор — самочувствие»: давление, влажность, сон, шаги, кофеин, HRV и так далее. Если проверять каждую пару по отдельности с порогом p < 0.05, то при 20 проверках в среднем одна окажется «значимой» просто по случайности — это и есть проблема множественных сравнений. Без поправки на неё приложение начнёт выдавать пользователю ложные закономерности, которые красиво выглядят, но не воспроизводятся на новых данных.
Представьте: у пользователя было пасмурно и одновременно болела голова три дня подряд. По отдельности этой корреляции может хватить, чтобы пройти порог p < 0.05 — а с учётом ещё пятнадцати проверенных факторов это почти наверняка совпадение. Именно поэтому одного p-значения недостаточно — нужен следующий шаг, который посмотрит на всю картину сразу, а не на одну пару в отрыве от остальных.
Поправка Бенджамини–Хохберга: как отсеять случайные совпадения#
Поправка Бенджамини–Хохберга контролирует долю ложных открытий (false discovery rate) среди всех «значимых» результатов, когда проверяется много гипотез одновременно. Идея простая: отсортировать p-значения по возрастанию и найти самое большое из них, которое всё ещё укладывается в скорректированный порог.
/// Benjamini–Hochberg correction for multiple comparisons.
/// Returns the indices of hypotheses that remain significant
/// after controlling the false discovery rate at `alpha`.
func benjaminiHochberg(pValues: [Double], alpha: Double = 0.05) -> Set<Int> {
let m = Double(pValues.count)
let indexed = pValues.enumerated().sorted { $0.element < $1.element }
var lastSignificantRank = -1
for (rank, item) in indexed.enumerated() {
let k = Double(rank + 1)
let threshold = (k / m) * alpha
if item.element <= threshold {
lastSignificantRank = rank
}
}
guard lastSignificantRank >= 0 else { return [] }
return Set(indexed.prefix(lastSignificantRank + 1).map { $0.offset })
}На практике это означает: если движок проверил связь самочувствия с 15 факторами и три из них показали p < 0.05 «в лоб», после поправки может остаться только один — тот, который действительно устойчив. Пользователю показывается именно он, с формулировкой вроде «у вас статистически значимая связь между влажностью выше 80% и снижением энергии» — и это утверждение выдержало проверку на случайность.
Персональные базовые уровни и адаптация после ~10 записей#
Абсолютные значения бесполезны без контекста: для одного человека пульс 68 — это норма, для другого — уже отклонение. Поэтому движок строит персональный базовый уровень (baseline) для каждой метрики и обновляет его по мере поступления новых записей.
/// Updates a personal baseline using an exponentially weighted moving average,
/// so the model adapts as new daily entries arrive.
struct PersonalBaseline {
private(set) var mean: Double
private(set) var sampleCount: Int
private let smoothing: Double
init(initialMean: Double = 0, smoothing: Double = 0.2) {
self.mean = initialMean
self.sampleCount = 0
self.smoothing = smoothing
}
mutating func update(with value: Double) {
sampleCount += 1
if sampleCount <= 10 {
// Cold start: simple running average for the first ~10 entries
mean = mean + (value - mean) / Double(sampleCount)
} else {
// Warmed up: exponentially weighted average reacts to recent trends
mean = mean + smoothing * (value - mean)
}
}
}Первые примерно десять записей движок использует простое скользящее среднее — «холодный старт», когда данных мало и резкие скачки лучше сглаживать поровну. После этого он переключается на экспоненциально взвешенное среднее, которое чуть сильнее реагирует на последние записи, оставаясь устойчивым к разовым выбросам. Именно на этих базовых уровнях строятся прогнозы самочувствия на 24 и 72 часа: движок сравнивает текущие условия (погоду, сон, активность) с личной нормой пользователя и с найденными для него значимыми корреляциями, а не с усреднёнными данными по всем пользователям сразу.
Статистика против нейросетей: честное сравнение#
Я мог бы обучить небольшую модель на агрегированных данных или подключить облачный LLM для «умных инсайтов». Сознательно этого не сделал — и вот по каким причинам.
| Критерий | Статистический движок | Нейросеть / облачный ИИ |
|---|---|---|
| Объяснимость | Каждый вывод сводится к формуле и p-значению | Часто black box, нужны пост-хок объяснения |
| Приватность | Данные не покидают устройство, анализ офлайн | Обычно требует отправки данных на сервер |
| Объём данных для старта | Работает уже после ~10 записей на одного человека | Нужны тысячи размеченных примеров для обучения |
| Работа офлайн | Да, всегда | Обычно нет без предустановленной модели |
| Предсказуемость поведения | Детерминированная, воспроизводимая | Может отличаться между запусками и версиями модели |
Второе сравнение — по типу решаемой задачи:
| Задача | Подходящий инструмент |
|---|---|
| Найти линейную связь между двумя метриками | Корреляция Пирсона |
| Распознать объект на фото | Нейросеть (Core ML, Vision) |
| Проверить статистическую значимость находки | ANOVA + p-значение |
| Сгенерировать текст на естественном языке | LLM |
| Отсеять случайные совпадения среди десятков гипотез | Поправка Бенджамини–Хохберга |
Из таблиц видно: статистика и нейросети решают разные классы задач, и одно не отменяет другое. Но для конкретно этой задачи — найти персональные, объяснимые и приватные закономерности в данных о здоровье одного человека — классическая статистика оказалась точнее нейросети по всем практическим критериям: она работает на маленьких выборках, не требует сервера, не «галлюцинирует» и позволяет мне как разработчику показать пользователю ровно ту причину, из-за которой прогноз стал тревожным. Core ML отлично подошёл бы для распознавания эмоций по фото или классификации активности по акселерометру — но не для задачи «объясни человеку, почему ему завтра, вероятно, станет хуже».
Весь этот движок работает внутри MeteoHealth — и именно поэтому все прогнозы там сопровождаются объяснением, а не просто числом. Хайп вокруг «AI-powered» пройдёт, а доверие пользователей строится на том, можно ли объяснить, почему приложение сказало именно это. Иногда самый честный ответ на вопрос «где у вас ИИ» — это «его здесь нет, зато есть статистика, которая работает».
Мне кажется, индустрия health-tech сейчас проходит тот же путь, что веб-разработка прошла с JavaScript-фреймворками: сначала все хотят самый модный инструмент, потом — инструмент, который реально решает задачу. Классическая статистика скучнее, чем «нейросеть предсказывает ваше здоровье», зато она воспроизводима, объяснима и не требует от пользователя доверять чёрному ящику. Для приложения, которое касается самочувствия конкретного человека, это не компромисс, а единственно разумный выбор.



