
Персонализация почти всегда приводит к одному и тому же архитектурному вопросу: где заканчивается общая модель и начинается модель конкретного пользователя? На небольшом масштабе стоимость такого решения может оставаться незаметной. Но если их становится сто тысяч или миллион, идея хранить и обслуживать отдельную полноценную модель для каждого быстро перестаёт выглядеть привлекательной.
Я столкнулся с этой задачей при работе над системой, которая должна учитывать питание, сон, физическую активность и историю поведения человека. Причём меня интересовал не просто очередной классификатор, обученный на общей выборке. Хотелось, чтобы система могла начинать с некоторого общего знания, а затем постепенно приобретать опыт взаимодействия с конкретным человеком. И желательно без постоянного переобучения большой модели.
Неожиданно полезной моделью для размышления оказался мозг плодовой мушки. На фоне новостей об исследованиях её мозга интернет заполнили мемы: муху уже «научили» варить пиво, играть в Доту и программировать на Python. За этим шумом мне стало интересно, могут ли исследования с участием Google дать что-нибудь полезное моему проекту, и я задал этот вопрос ChatGPT. Он предложил неожиданное направление: попробовать использовать принципы обучения мозга мухи для предсказания срывов в питании. До работающего решения здесь было далеко, но идея меня зацепила — и я начал разбираться, какие механизмы стоят за этой аналогией и что из них можно перенести в программную архитектуру.
Архитектура одна, опыт разный
В проекте MaleCNS исследователи HHMI Janelia, Кембриджского университета, MRC LMB и Google Research восстановили коннектом центральной нервной системы самца дрозофилы — карту нейронов и связей между ними. Такая карта позволяет исследовать устройство нейронных цепей, хотя сама по себе ещё не описывает всю динамику их работы.
Для моей задачи особенно интересным оказалось исследование обучения и памяти грибовидного тела — mushroom body (далее буду писать MB), одна из структур мозга дрозофилы, участвующих в обучении и памяти. В нём запахи представлены активностью небольшого подмножества клеток Кеньона. Если определённый запах сопровождается получением пищи, дофаминовые нейроны передают сигнал подкрепления, который изменяет эффективность связей между клетками Кеньона и выходными нейронами грибовидного тела.
Здесь появляется полезная для разработчика схема:
представление ситуации → обратная связь → локальное изменение связей.
Чтобы запомнить новую ассоциацию, обучение может изменять конкретные связи внутри уже существующей цепи. Это натолкнуло меня на вопрос: можно ли аналогично разделить программную модель на общий механизм кодирования входных данных и небольшой персональный слой, который обучается на событиях конкретного пользователя?
В моей задаче вместо запаха выступает сочетание сна, питания, нагрузки и времени суток, а обратной связью становится подтверждённое событие — например, срыв в питании. Общий механизм преобразует этот контекст во внутреннее представление, а персональные веса связывают его с вероятностью события. Это инженерная гипотеза, вдохновлённая устройством грибовидного тела. Её практическая привлекательность в том, что индивидуальный опыт можно накапливать, обновляя небольшую часть параметров и сохраняя общий вычислительный механизм для всех пользователей.
Что именно мы хотим персонализировать
Цель проекта — оценивать риск пищевого срыва с учётом текущего контекста и предыдущего опыта человека. Но текущий эксперимент проверяет эту идею на синтетических историях пользователей. «Срывом» здесь называется положительный исход, заданный симуляцией для ежедневного решения. Связь такой метки с реальными отклонениями от питания ещё предстоит установить: опубликованные результаты описывают поведение модели внутри экспериментальной постановки.
На вход поступают 94 наблюдаемых признака, описывающих питание, сон, активность и доступную историю. Общая модель выдаёт базовую вероятность события, а персональная память учится корректировать её по обратной связи конкретного пользователя. Нас интересует, может ли небольшой индивидуальный слой замечать контексты, в которых общий прогноз систематически завышает или занижает риск.
У нового пользователя поправки равны нулю, поэтому система начинает с общего прогноза. Когда приходит наблюдаемый исход, память обновляется по контексту, сохранённому в момент решения. Если ответ поступил через несколько часов, для обучения используется исходная ситуация. Отсутствие ответа не считается подтверждением, что события не было.
Что в этой конструкции должен дать граф мушки
Для эксперимента я использовал выбранный подграф ALPN → KC из MaleCNS v1.0 — опубликованного коннектома центральной нервной системы самца дрозофилы. Он задаёт структуру связей между обонятельными проекционными нейронами и клетками Кеньона. Признаки питания и сна отображаются на его входы искусственно: биологического соответствия между ними и запахами я не предполагаю.
Здесь важно разделить две гипотезы. Первая — персональная память может улучшать общий прогноз без переобучения всей модели. Вторая — структура биологического графа может оказаться полезной для построения разреженного представления контекста. Первую можно исследовать и без коннектома, поэтому интерес ко второй требует самостоятельного сравнения с более простыми проекциями.
Для этого в эксперимент включён вариант с перемешанными связями. Он помогает исследовать роль топологии, хотя одного такого контроля недостаточно для вывода о преимуществе анатомии. Следующий вопрос — сохраняется ли возможный выигрыш при сопоставимой размерности, разреженности, ёмкости модели и нескольких заранее выбранных seed.
Как я превратил аналогию в эксперимент
Код Craving Engine, зафиксированные синтетические входы и сохранённые результаты опубликованы на GitHub. В README описаны текущая стадия и направления дальнейшей работы. Миллион пользователей в названии — архитектурная мотивация: нагрузочного эксперимента такого масштаба я пока не проводил.
Для эксперимента использовались три непересекающиеся группы синтетических пользователей: 60 для обучения, 20 для выбора порогов и 40 для оценки на development-выборке. На каждого приходилось 180 ежедневных решений. Эта development-выборка уже использовалась в предыдущих исследованиях, поэтому её результаты нельзя считать независимой слепой проверкой.
Симуляция задавала задержку обратной связи в 20 часов. О событии сообщалось с вероятностью 80%, об отсутствии события — с вероятностью 70%. Таким образом, пропуски зависели от исхода. Это допущения симуляции; соответствующие частоты у реальных пользователей пока не установлены.
Обучение MB и обновление персональной памяти использовали доступную обратную связь. Для расчёта метрик и выбора порогов были доступны полные синтетические исходы, включая события без сообщения. Это упрощает оценку по сравнению с реальным продуктом, где такие исходы обычно неизвестны.
Что общее, а что принадлежит пользователю
Общая часть преобразует 94 входных признака в разреженный вектор. После нормировки положительные и отрицательные отклонения кодируются отдельными ON/OFF-каналами. Затем они искусственно отображаются на входы выбранного графа ALPN → KC.
В выбранном подграфе 2019 клеток Кеньона. Для одного контекста сохраняются до 101 положительной активации — примерно 5% координат. Их масштаб определяется по обучающей выборке; затем добавляется постоянная опорная координата и вектор приводится к единичной длине. В результате представление для памяти имеет 2020 координат.
Поверх этого представления обучается логистическая регрессия — выходной классификатор, который превращает вектор в прогноз вероятности. Вот соответствующий фрагмент MBClassifier.fit:
transform = ContextTransform.fit(values, self.schema)q = transform.transform(values)representation = FixedContextRepresentation.fit( q, self.graph, MBCalibrationConfig().mapping_seed,)readout = LogisticRegression(**asdict(MBReadoutConfig()))with warnings.catch_warnings(): warnings.simplefilter("error", ConvergenceWarning) readout.fit(representation.encode(q), labels)
Это фрагмент метода: входные values и labels подготовлены выше. В полном протоколе используется ансамбль из трёх моделей с сигмоидальной калибровкой вероятностей. Пользователи разделяются между обучающими и калибрующими частями, а параметры нормировки каждой модели оцениваются только на её обучающих пользователях.
Для персональной памяти отдельно строится представление по всей обучающей матрице признаков. После этого оно фиксируется, чтобы индивидуальный опыт накапливался в неизменной системе координат.
Личное состояние нового пользователя содержит три нулевых массива:
self.fast = np.zeros(dimension)self.slow = np.zeros(dimension)self.exposure = np.zeros(dimension)
fast и slow накапливают поправки с разными скоростями обучения и затухания. exposure учитывает, насколько часто встречались активные координаты. Общее знание уже содержится в базовом прогнозе, поэтому копировать обученный классификатор каждому пользователю не требуется.
Персональная поправка ограничена по величине. Её влияние также зависит от знакомости контекста и гейта — коэффициента, который оценивает прошлую пользу персонального прогноза по наблюдаемым исходам. При недостаточных данных или отсутствии положительного свидетельства гейт обнуляет поправку. Это ограничение алгоритма, а не гарантия улучшения для каждого пользователя.
Обучаться нужно на контексте решения
В исследовательском API прогноз и обратная связь разделены на predict и observe. Следующий пример предполагает, что общая модель, преобразование признаков и память пользователя уже загружены:
p0 = float(population_model.predict_proba(X_now)[0, 1])h = online_representation.encode(online_transform.transform(X_now))[0]snapshot = memory.predict(event_id, decision_at, h, p0)prediction = snapshot.p_final# Позже поступает наблюдаемый исход именно этого события.memory.observe(event_id, feedback_at, outcome, confidence=1.0)checkpoint = memory.export_state()
predict сохраняет активные координаты и прогноз в момент решения. Когда приходит исход, observe использует этот снимок, а не новые признаки после события. Отсутствие ответа не становится отрицательной меткой; повторные идентификаторы решений и повторная обратная связь отклоняются.
При воспроизведении истории обновления выполняются только по обратной связи, поступившей раньше следующего решения. Добавка к весам использует активные координаты, но затухание и L2-регуляризация воздействуют и на остальные элементы. Поэтому всё обновление нельзя назвать изменением исключительно 5% массива.
Полная динамика мозга и дофаминовая физиология в этой реализации не симулируются. Биологическая структура здесь используется как фиксированный компонент инженерного представления.
Что показало сравнение
Я сравнивал шесть вариантов: градиентный бустинг по деревьям (GBDT) с персональной скалярной поправкой и без неё, анатомическую MB с памятью и без неё, а также MB с перемешанными связями в тех же режимах.
Для каждой модели порог выбирался на калибровочной выборке при средней по пользователям FPR не выше 20%, после чего фиксировался для development. Это ограничение на калибровке не гарантирует такую же частоту ложных тревог на новых данных.
В таблице приведены средние метрики по пользователям. Recall показывает долю обнаруженных событий, FPR — долю ложных срабатываний среди решений без события. AUC оценивает качество ранжирования, Brier — ошибку вероятностного прогноза; для Brier меньше означает лучше.
|
Модель |
Recall |
FPR |
AUC |
Brier |
|---|---|---|---|---|
|
GBDT |
39,65% |
15,40% |
0,7391 |
0,1732 |
|
GBDT + скалярная поправка |
39,51% |
15,12% |
0,7362 |
0,1709 |
|
Анатомическая MB |
43,24% |
19,70% |
0,7307 |
0,1820 |
|
Анатомическая MB + память |
42,77% |
19,59% |
0,7280 |
0,1780 |
|
MB с перемешанными связями |
38,01% |
14,13% |
0,7330 |
0,1776 |
|
MB с перемешанными связями + память |
37,94% |
14,29% |
0,7306 |
0,1751 |
Внутри анатомической MB персональная память уменьшила Brier примерно на 0,0041. Парный 95% доверительный интервал, рассчитанный бутстрэпом по пользователям, составил от −0,0083 до −0,0009. Это обнадёживающее свидетельство улучшения ошибки вероятностей на данной синтетической выборке. Однако средние AUC и recall немного снизились, а простой GBDT со скалярной поправкой показал меньший Brier.
Сравнение MB с GBDT тоже неоднозначно: более высокий recall сопровождался более высокой FPR. Поэтому эти рабочие точки не устанавливают превосходство MB. При ограничении в два предупреждения за скользящие семь дней персональная MB обнаружила 992 события и выдала 175 ложных предупреждений; GBDT — 968 и 148 соответственно. Дополнительные 24 обнаруженных события сопровождались 27 дополнительными ложными предупреждениями. Предотвращение событий этим экспериментом не оценивалось.
Заранее назначенный кандидат — анатомическая MB с памятью — не прошёл условия допуска. Прирост recall относительно GBDT оказался меньше требуемых пяти процентных пунктов, увеличение FPR превысило допустимый один пункт, а Brier ухудшился. Поэтому новый слепой тест для этого кандидата не проводился.
Для меня результат полезен тем, что разделяет вопросы дальнейшей работы. Персональная память смогла улучшить одну характеристику собственного MB-прогноза, но преимущества всей конструкции над простыми моделями пока нет. Полные результаты и критерии доступны в отчёте V9.
Сколько памяти получилось на самом деле
Первоначально я ориентировался на 16 КиБ персонального состояния. Эта оценка относилась к иллюстративной схеме с одним массивом из 4096 значений float32. Когда я сопоставил её с реализацией V9, оказалось, что память включает три массива по 2020 элементов типа float64. Их числовые буферы занимают 48 480 байт — примерно 47,34 КиБ на пользователя.
К этой величине добавляются ожидающие снимки решений, история гейта, идентификаторы событий и метаданные. Поэтому 47,34 КиБ — размер числовой части, а полный бюджет персонального состояния ещё нужно измерить и ограничить. Для миллиона пользователей одни эти буферы потребовали бы 48,48 ГБ хранения; это оценка объёма данных, а не результат нагрузочного эксперимента.
Цель в 16 КиБ остаётся отдельной инженерной задачей. Переход на float32 уменьшил бы три массива до 23,67 КиБ, поэтому одной смены точности недостаточно. Придётся исследовать размерность представления и объём вспомогательной истории, каждый раз проверяя, что экономия памяти не уничтожает пользу персонализации.
Что буду проверять дальше
Первый шаг — воспроизвести сохранённый результат из самостоятельного репозитория. Затем я хочу отдельно проверить вклад персональной памяти: сравнить её с простой скалярной поправкой при одинаковой общей модели и заранее выбранном ограничении на ложные предупреждения. Так будет проще понять, оправдывает ли многомерное состояние свою сложность.
Следующий вопрос — вклад графа мушки. Для него нужны сопоставимые разреженные проекции, несколько фиксированных seed и одинаковые условия оценки. Если появится устойчивый выигрыш, подтверждать его придётся на новых пользователях; перенос на реальные данные останется отдельной задачей.
Главный вывод для меня пока архитектурный: общий прогноз и индивидуальный опыт удалось разделить в работающем исследовательском алгоритме. Его практическое преимущество ещё предстоит доказать. Теперь у меня есть реализация, сохранённые результаты и конкретные сравнения, которые помогут решить, что развивать дальше.
P. S. Я backend-разработчик, а в машинном обучении пока начинающий: учусь по ходу этого исследования, и мне очень интересно разобраться в задаче.
Буду благодарен за поправки к терминологии, интерпретации результатов и постановке эксперимента — особенно с объяснением, что стоит проверить и почему.
Код и описание экспериментов доступны в репозитории Craving Engine. Если у вас есть идеи, какой baseline добавить, как изменить персональную память или иначе оценить результаты, пишите в комментариях или issues. Мне особенно помогут предложения, которые можно превратить в конкретный эксперимент.
Если получится продвинуться, подготовлю продолжение: расскажу, что попробовал, какие результаты получил и что пришлось пересмотреть. Надеюсь, обсуждение поможет найти следующий шаг.
ссылка на оригинал статьи https://habr.com/ru/articles/1091764/