- BrainTools - https://www.braintools.ru -

Один мозг на миллион пользователей: чему backend-разработчик может научиться у мозга мухи

Один мозг на миллион пользователей: чему backend-разработчик может научиться у мозга мухи - 1

Персонализация почти всегда приводит к одному и тому же архитектурному вопросу: где заканчивается общая модель и начинается модель конкретного пользователя? На небольшом масштабе стоимость такого решения может оставаться незаметной. Но если их становится сто тысяч или миллион, идея хранить и обслуживать отдельную полноценную модель для каждого быстро перестаёт выглядеть привлекательной.

Я столкнулся с этой задачей при работе над системой, которая должна учитывать питание, сон [1], физическую активность и историю поведения [2] человека. Причём меня интересовал не просто очередной классификатор, обученный на общей выборке. Хотелось, чтобы система могла начинать с некоторого общего знания, а затем постепенно приобретать опыт [3] взаимодействия с конкретным человеком. И желательно без постоянного переобучения большой модели.

Неожиданно полезной моделью для размышления оказался мозг [4] плодовой мушки. На фоне новостей об исследованиях её мозга интернет заполнили мемы: муху уже «научили» варить пиво, играть в Доту и программировать на Python. За этим шумом мне стало интересно, могут ли исследования с участием Google дать что-нибудь полезное моему проекту, и я задал этот вопрос ChatGPT. Он предложил неожиданное направление: попробовать использовать принципы обучения [5] мозга мухи для предсказания срывов в питании. До работающего решения здесь было далеко, но идея меня зацепила — и я начал разбираться, какие механизмы стоят за этой аналогией и что из них можно перенести в программную архитектуру.

Архитектура одна, опыт разный

В проекте MaleCNS [6] исследователи HHMI Janelia, Кембриджского университета, MRC LMB и Google Research восстановили коннектом центральной нервной системы самца дрозофилы — карту нейронов и связей между ними. Такая карта позволяет исследовать устройство нейронных цепей, хотя сама по себе ещё не описывает всю динамику их работы.

Для моей задачи особенно интересным оказалось исследование обучения и памяти грибовидного тела [7] — mushroom body (далее буду писать MB), одна из структур мозга дрозофилы, участвующих в обучении и памяти [8]. В нём запахи [9] представлены активностью небольшого подмножества клеток Кеньона. Если определённый запах сопровождается получением пищи, дофаминовые нейроны [10] передают сигнал подкрепления [11], который изменяет эффективность связей между клетками Кеньона и выходными нейронами грибовидного тела.

Здесь появляется полезная для разработчика схема:

представление ситуации → обратная связь → локальное изменение связей.

Чтобы запомнить новую ассоциацию [12], обучение может изменять конкретные связи внутри уже существующей цепи. Это натолкнуло меня на вопрос: можно ли аналогично разделить программную модель на общий механизм кодирования входных данных и небольшой персональный слой, который обучается на событиях конкретного пользователя?

В моей задаче вместо запаха выступает сочетание сна [13], питания, нагрузки и времени суток, а обратной связью становится подтверждённое событие — например, срыв в питании. Общий механизм преобразует этот контекст во внутреннее представление, а персональные веса связывают его с вероятностью события. Это инженерная гипотеза, вдохновлённая устройством грибовидного тела. Её практическая привлекательность в том, что индивидуальный опыт можно накапливать, обновляя небольшую часть параметров и сохраняя общий вычислительный механизм для всех пользователей.

Что именно мы хотим персонализировать

Цель проекта — оценивать риск пищевого срыва с учётом текущего контекста и предыдущего опыта человека. Но текущий эксперимент проверяет эту идею на синтетических историях пользователей. «Срывом» здесь называется положительный исход, заданный симуляцией для ежедневного решения. Связь такой метки с реальными отклонениями от питания ещё предстоит установить: опубликованные результаты описывают поведение [14] модели внутри экспериментальной постановки.

На вход поступают 94 наблюдаемых признака, описывающих питание, сон, активность и доступную историю. Общая модель выдаёт базовую вероятность события, а персональная память учится корректировать её по обратной связи конкретного пользователя. Нас интересует, может ли небольшой индивидуальный слой замечать контексты, в которых общий прогноз систематически завышает или занижает риск.

У нового пользователя поправки равны нулю, поэтому система начинает с общего прогноза. Когда приходит наблюдаемый исход, память обновляется по контексту, сохранённому в момент решения. Если ответ поступил через несколько часов, для обучения используется исходная ситуация. Отсутствие ответа не считается подтверждением, что события не было.

Что в этой конструкции должен дать граф мушки

Для эксперимента я использовал выбранный подграф ALPN → KC из MaleCNS v1.0 [15] — опубликованного коннектома центральной нервной системы самца дрозофилы. Он задаёт структуру связей между обонятельными проекционными нейронами и клетками Кеньона. Признаки питания и сна отображаются на его входы искусственно: биологического соответствия между ними и запахами я не предполагаю.

Здесь важно разделить две гипотезы. Первая — персональная память может улучшать общий прогноз без переобучения всей модели. Вторая — структура биологического графа может оказаться полезной для построения разреженного представления контекста. Первую можно исследовать и без коннектома, поэтому интерес [16] ко второй требует самостоятельного сравнения с более простыми проекциями.

Для этого в эксперимент включён вариант с перемешанными связями. Он помогает исследовать роль топологии, хотя одного такого контроля недостаточно для вывода о преимуществе анатомии. Следующий вопрос — сохраняется ли возможный выигрыш при сопоставимой размерности, разреженности, ёмкости модели и нескольких заранее выбранных seed.

Как я превратил аналогию в эксперимент

Код Craving Engine, зафиксированные синтетические входы и сохранённые результаты опубликованы на GitHub [17]. В README описаны текущая стадия и направления дальнейшей работы. Миллион пользователей в названии — архитектурная мотивация [18]: нагрузочного эксперимента такого масштаба я пока не проводил.

Для эксперимента использовались три непересекающиеся группы синтетических пользователей: 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 — ошибку [19] вероятностного прогноза; для 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 [20].

Сколько памяти получилось на самом деле

Первоначально я ориентировался на 16 КиБ персонального состояния. Эта оценка относилась к иллюстративной схеме с одним массивом из 4096 значений float32. Когда я сопоставил её с реализацией V9, оказалось, что память включает три массива по 2020 элементов типа float64. Их числовые буферы занимают 48 480 байт — примерно 47,34 КиБ на пользователя.

К этой величине добавляются ожидающие снимки решений, история гейта, идентификаторы событий и метаданные. Поэтому 47,34 КиБ — размер числовой части, а полный бюджет персонального состояния ещё нужно измерить и ограничить. Для миллиона пользователей одни эти буферы потребовали бы 48,48 ГБ хранения; это оценка объёма данных, а не результат нагрузочного эксперимента.

Цель в 16 КиБ остаётся отдельной инженерной задачей. Переход на float32 уменьшил бы три массива до 23,67 КиБ, поэтому одной смены точности недостаточно. Придётся исследовать размерность представления и объём вспомогательной истории, каждый раз проверяя, что экономия памяти не уничтожает пользу персонализации.

Что буду проверять дальше

Первый шаг — воспроизвести сохранённый результат из самостоятельного репозитория. Затем я хочу отдельно проверить вклад персональной памяти: сравнить её с простой скалярной поправкой при одинаковой общей модели и заранее выбранном ограничении на ложные предупреждения. Так будет проще понять, оправдывает ли многомерное состояние свою сложность.

Следующий вопрос — вклад графа мушки. Для него нужны сопоставимые разреженные проекции, несколько фиксированных seed и одинаковые условия оценки. Если появится устойчивый выигрыш, подтверждать его придётся на новых пользователях; перенос на реальные данные останется отдельной задачей.

Главный вывод для меня пока архитектурный: общий прогноз и индивидуальный опыт удалось разделить в работающем исследовательском алгоритме. Его практическое преимущество ещё предстоит доказать. Теперь у меня есть реализация, сохранённые результаты и конкретные сравнения, которые помогут решить, что развивать дальше.

P. S. Я backend-разработчик, а в машинном обучении пока начинающий: учусь по ходу этого исследования, и мне очень интересно разобраться в задаче.

Буду благодарен за поправки к терминологии, интерпретации результатов и постановке эксперимента — особенно с объяснением, что стоит проверить и почему.

Код и описание экспериментов доступны в репозитории Craving Engine [17]. Если у вас есть идеи, какой baseline добавить, как изменить персональную память или иначе оценить результаты, пишите в комментариях или issues. Мне особенно помогут предложения, которые можно превратить в конкретный эксперимент.

Если получится продвинуться, подготовлю продолжение: расскажу, что попробовал, какие результаты получил и что пришлось пересмотреть. Надеюсь, обсуждение поможет найти следующий шаг.

Автор: nutrifit

Источник [21]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/36658

URLs in this post:

[1] сон: http://www.braintools.ru/article/9809

[2] поведения: http://www.braintools.ru/article/9372

[3] опыт: http://www.braintools.ru/article/6952

[4] мозг: http://www.braintools.ru/parts-of-the-brain

[5] обучения: http://www.braintools.ru/article/5125

[6] MaleCNS: https://male-cns.janelia.org/

[7] исследование обучения и памяти грибовидного тела: https://www.janelia.org/lab/aso-lab/our-research

[8] памяти: http://www.braintools.ru/article/4140

[9] запахи: http://www.braintools.ru/article/9870

[10] нейроны: http://www.braintools.ru/article/9161

[11] подкрепления: http://www.braintools.ru/article/5528

[12] ассоциацию: http://www.braintools.ru/article/621

[13] сна: http://www.braintools.ru/article/9150

[14] поведение: http://www.braintools.ru/article/5593

[15] MaleCNS v1.0: https://male-cns.janelia.org/download/

[16] интерес: http://www.braintools.ru/article/4220

[17] GitHub: https://github.com/nutrifit-health/craving-engine

[18] мотивация: http://www.braintools.ru/article/9537

[19] ошибку: http://www.braintools.ru/article/4192

[20] отчёте V9: https://github.com/nutrifit-health/craving-engine/blob/main/docs/results-v9.ru.md

[21] Источник: https://habr.com/ru/articles/1091764/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1091764

www.BrainTools.ru

Rambler's Top100