Интересное что-то
517 subscribers
2.71K photos
253 videos
138 files
4.51K links
Материалы и мысли, понадерганные отовсюду
Блог: https://t.me/asisakov_channel
Чат: https://t.me/youknowds_chat
Download Telegram
Анатомия ИИ-агентов. Часть 1 - Истоки и архитектура. [1/2]

Подходит концу первая рабочая неделя этого года. Дабы провести выходные с пищей для ума, самое время с двух ног запрыгнуть в устройство ИИ-агентов. Начнем с истоков. ⚡️

Первыми практическими предшественниками современных ИИ-агентов стали экспертные системы, появившиеся в 1960-х годах. Экспертная система — это система искусственного интеллекта (весьма ограниченного), которая на основании знаний и опыта эксперта-человека может решать задачи в определенной области. В 1965 году в Стэнфордском университете Эдвард Фейгенбаум создал DENDRAL — первую в истории экспертную систему для определения структуры химических веществ.

Прорыв в понимании ИИ-агентов произошел в 1973 году, когда Карл Хьюитт разработал модель актора — подход, позволяющий создавать системы, где независимые агенты взаимодействуют друг с другом через обмен сообщениями. Одной из первых таких систем стала Distributed Problem Solver, созданная в 1981 году. В 1986 году Марвин Минский в книге “Society of Mind” предложил представлять сложные задачи как результат взаимодействия множества отдельных агентов, работающих в “сообществе”. Почему это важно? Модель актора обеспечила сдвиг ментальной модели программирования от систем с общей памятью и блокировками к архитектуре, основанной на передаче сообщений и изоляции состояния.

Современный ИИ-агент, следуя принципам акторной модели и построенный поверх большой лингвистической модели, отличителен 3-мя ключевыми свойствами:

Свойство 1. Автономность и независимое выполнение задач.

Многие проводят равенство между автономностью и самостоятельностью, мол, агент живет сам по себе и делает работу, как человек, то нет. Самостоятельность - способность не только выполнять действия без надзора, но и ставить подцели, адаптироваться к неизвестным заранее условиям. Дело не в технических ограничениях. Самостоятельность (и его объем) - производное от доверия, а доверие - краеугольный камень любых внешних, не только агентских систем.


В понимании современных ИИ-агентов речь идет о способности агента к планированию следующего шага. В отличие от “голой” LLM, где мы работаем в режиме “запрос-ответ”, агент действует в, так называемом, агентском цикле: Наблюдение → Планирование → Действие. Агентский цикл конечен. Независимо от его сложности, агент на вход получает запрос, запускает цикл и его цель вернуть ожидаемый результат. Вот, что делают шаги цикла:

1. Наблюдение. Агент анализирует результаты своих предыдущих действий, собирает данные из окружения, выполняет контекстное обогащение.

2. Планирование. Агент использует различные методы рассуждений для определения наилучшего способа действий. Модель начинает думать над решением запроса пользователя, разрабатывает план для дальнейших действий и определяет, какие инструменты можно использовать.

3. Действие. Агент выбирает необходимые инструменты и начинает их использовать в соответствии с задачами, сформулированными на этапе планирования.

Свойство 2. Интеграция с инструментами и окружением

В шаге планирования и действия агенту доступно мета-описание его окружения: команд, которые может выбрать LLM, для взаимодействия с окружающим миром. Между командой и LLM - тонкий слой управляющего кода, интерпретирующего текстовые ответы в вызов кода самой команды. Именно поэтому к LLM выдвигается требование к способности отвечать структурированно (Structured output). Действуя, агент делает 1 или множество запросов к LLM, получая структурированные ответы, вызывает инструменты - обычный код в функциях и классах с поведением, исполняемый процессором, выполняет работу, а также сверяется с исходным планом.

продолжение...
Please open Telegram to view this post
VIEW IN TELEGRAM
Анатомия ИИ-агентов. Часть 1 - Истоки и архитектура. [2/2]

В начало

Свойство 3. Память

LLM не обладает собственной памятью (или состоянием) между запросами - каждый запрос обрабатывается независимо, как в первый раз. То, что мы называем агентом, является обычной программой с собственным окружением. Как и любая другая программа, она может хранить состояние в оперативной памяти, обращаться к базам данных, собирать необходимый контекст для выполнения пользовательской задачи. Простейший вид памяти - сохранение всей последовательности запросов к LLM и ее ответов.

Так же как человеческий мозг имеет полушария и специализированные отделы, обеспечивающие нам интеллектуальные способности во всем их многообразии, ИИ-агент может быть разделен на части для пущей интеллектуальности. Программную архитектуру можно представить в виде фрактала - узора, обладающего свойством самоподобия: его части в уменьшенном масштабе повторяют структуру целого, где основной узор - агентский цикл. Агентский цикл, как архитектурная единица, в том же виде используется для создания под-модулей: планировщика, рефлексии, цензора, интерпретатора и тд. Когда и как эти подмодули-микроагенты (не путать с суб-агентами) будут вступать в работу определяет разработчик, склеивая их в процесс всё тем же обычным кодом (как, увидим в следующих статьях).

——

Подытоживая, архитектура ИИ-агента удивляет своей простотой и масштабируемостью, являясь кирпичиком системы любой сложности. Агент может быть как простейшим SingleRun-вызовом к LLM с остановкой после ответа, так и ReAct-агентом, самостоятельно принимающим решение как действовать дальше и когда заканчивать. Их и будем разбирать далее.

Подписывайтесь на MachineHead и делитесь с друзьями! Stay tuned! ✌️
Please open Telegram to view this post
VIEW IN TELEGRAM
Привет всем!👋

Сегодня поговорим про небольшую оптимизацию кода по памяти.
Известный факт, что неизменяемые структуры, которые имеют фиксированные размеры, имеют некоторые преимущества в сравнении с изменяемыми.
В рамках проектов, где часто вызываются миллионы мелких объектов (например, узлы в графе, события в симуляции, кастомные токены) проблема утечки памяти встает очень остро. Отчасти, это касается и атрибутов объектов.

По умолчанию 🐍 хранит атрибуты объектов в словаре, включенном в метод __dict__ (появился он в языке 8 февраля 2012 года), который, как и обычный словарь имеет динамическое выделение памяти и может быть расширяем, что при всех своих плюсах создает большую нагрузку на память.

Для решения проблемы предлагается использовать метод __slots__.
Он отключает создание __dict__ и резервирует память только под конкретные атрибуты, что помогает оптимизировать код по памяти, но жестко фиксирует набор атрибутов объекта, что является одновременно и плюсом и минусом.

Рассмотрим на примере:
🔸Определим классический класс CoordinatesDict`и класс `CoordinatesSlots с использованием __slots__
import sys

class CoordinatesDict:
def __init__(self, x, y,z):
self.x = x
self.y = y
self.z = z

class CoordinatesSlots:
__slots__ = ['x', 'y', 'z'] # Фиксируем атрибуты
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z


🔸Теперь определим объекты классов obj1 и obj2:
obj1 = CoordinatesDict(1, 2, 3)
obj2 = CoordinatesSlots(1, 2, 3)


- Какие разницы в этих объектах?
🔵В obj1 можно дополнительно объявить новый атрибут, скажем, dist и присвоить ему значение, он пополнит ряды __dict__. В obj2 аналогичная операция вернет AttributeError
Пример:
obj1.dist = 2
obj1.__dict__

>>>{'x': 1, 'y': 2, 'z': 3, 'dist': 2}

obj2.dist = 2

>>>AttributeError: 'CoordinatesSlots' object has no attribute 'dist'

Причина такого поведения лежит именно в формировании класса с использованием __slots__. Такой класс, как говорилось ранее, хранит атрибуты в массиве фиксированной размерности, отключая возможность работы со словарем атрибутов (то есть у вас не появляется __dict__), а все атрибуты, не записанные в массив, поднимают ошибку.

🔵В obj2 нет словаря атрибутов __dict__, а следовательно на него не требуется память.
Проверить это просто:
print(sys.getsizeof(obj1), sys.getsizeof(obj1.__dict__)) 
print(sys.getsizeof(obj2))
print(sys.getsizeof(obj2.__dict__))

Вывод следующий (до добавления атрибута):
48 272
56
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
/tmp/ipython-input-390784945.py in <cell line: 0>()
18 print(sys.getsizeof(p1), sys.getsizeof(p1.__dict__))
19 print(sys.getsizeof(p2))
---> 20 print(sys.getsizeof(p2.__dict__))

AttributeError: 'CoordinatesSlots' object has no attribute '__dict__'


Таким образом, если мы имеет часто используемый класс и нам не нужна свобода в изменении состава атрибутов, то правильнее использовать конструкцию со __slots__



Ставь 🔥, если не знал.
Ставь👍, если знал.

#ds_лайфхаки
Please open Telegram to view this post
VIEW IN TELEGRAM
Контекст тимлида

Недавно записывал один подкаст, где одним из вопросов мы затронули контекст тимлида.

Это полезно понимать как для текущего тимлида в разрезе его нагрузки в настоящий момент, так и для тимлида, который приходит в новую команду/компанию, в разрезе того, что ему необходимо добрать, чтобы нормально работать.

Члены команды
Все люди разные и имеют свои особенности, мотивацию, текущие цели, особенности характера, темперамента, разные жизненные ценности, разные жизненные ситуации. На это накладывается, как они взаимодействуют между собой. Этот комбинаторный взрыв нужно знать и следить за ним регулярно, ибо, как говорил Гераклит, «всё течёт, всё меняется».

Рабочие процессы
Понятное дело, тут про всякую организационную и операционную деятельность, которая сейчас в команде присутствует, НО…
Есть еще исторический контекст того, что уже пробовали, что работало хорошо, а что плохо и почему.
Есть еще процессный долг, когда что-то сейчас как-то работает не лучшим образом и/или на ручной тяге, а надо бы это улучшить, но пока не было времени точить пилу, ведь дедлайн скоро и надо пилить.

Проекты и продукты
В вотчине тимлида есть набор проектов и продуктов. Это можно разбить на две части:
- Менеджерская. Проектный менеджмент, планирование, контроль, сроки, дедлайны, риски, KPI, OKR, договоренности с заказчиками и смежниками, ценность самих продуктов.
- Техническая. Само устройство, архитектура, технический долг.

Стратегия
Сейчас как-то всё работает, а как и для чего оно всё будет работать в будущем? Тут тоже 2 стула:
- Менеджерский. Басфактор, подготовка преемника, обучение и развитие команды. Цели и ценность самой команды и как она должна выглядеть в будущем.
- Технический. Технологическая стратегия тоже важна. Не просто «нам нальют х2 пользователей, мы железом ливанем, да и всё».

Наём
Кто-то из команды уходит, или открываются новые вакансии (про которые надо вообще понять, зачем они нужны, сформулировать портрет кандидата и дальше отбором заниматься), надо кого-то прособеседовать, или надо кого-то удержать, или надо, наоборот, кого-то уволить. Всё это тоже в контексте тимлида загружено.

Руководство
Надо хорошо понимать своё руководство: его ожидание, его личность, его особенности работы, то, как и когда по каким вопросам репортить, как просить помощи, как и когда спорить, как не спорить и вот это вот всё.

Политика
Есть хорошая картинка про то, как НА САМОМ ДЕЛЕ выглядит оргструктура https://ic.pics.livejournal.com/talent_a/45400893/2597/2597_original.jpg, и вот понимание этого контекста тоже очень важно. Всегда у людей есть куча неформальных историй, амбиций, притязаний, фаворитизма, протекции, обид, претензий и т. д. При непонимании этих негласных связей можно наступить на невидимые грабли, которых на официальной карте местности не должно быть.

Итог
У тимлида чертова гора контекста, которые ему нужно знать, держать в голове, в планах и регулярно использовать.

Поэтому я всегда диву даюсь, когда слышу «да у него там 2-3 человека, это ж на полдня в неделю менеджерской работы».

Или когда приходит новый тимлид, и от него сразу ждут такой же объем выполнения планов, как от прошлого, в которого этот контекст уже был вгружен и у которого все социальные связи установлены.
Важные секции, которые должны быть в наших PRD

Плиз проверьте, что эти блоки точно есть, и давайте их располагать в след структуре, чтобы было проще читать:

1. Назначение сервиса

Формат: один абзац, который коротко описывает суть и назначение сервиса, какую задачу он решает на большом ландшафте всей системы.

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

2. Глоссарий основных сущностей сервиса

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

Task - задача для агента на общение с пользователем с конкретной целью. Агент должен ее стартануть в определенное время, и довести до завершения

Session - сессия разговора агент-пользователь, начинается с сообщения агент
а и заканчивается спустя последнее сообщение человека/агента через 2 часа

3. Как внешний мир видит этот компонент

И на каком языке с ним общается. Тут детально расписывается вся внешняя апишка сервиса и другие входящие потоки данных.

POST /api/v1/offers/create    - добавление нового оффера
{"offer_type": "bonus100", "offer_name": "xyz", ...}

PUT /api/v1/campaign/start - ручной запуск компании
{"id": "ieur-er4r4r-4r4"}


4. Модель БД сервиса

Детальное описание всех таблиц, полей, смысла полей, foreign keys constraints и индексов. Пишем все - nullable/not null/default итд. Самая важная секция, max attention - самая дорогая цена ошибки

== tasks (задачи на общение с пользователями) ==
id: bigint - pk
user_uuid: text - index
created_at: timestamp
...

== messages (сообщения чатов) ==
id: bigint - pk
task_id: bigint (fk -> tasks.id) - index
text: text // текст сообщения
role: test // автор сообщения, человек или агент
created_at: timestamp
...

id_task_id - unique constarint


5. Sequence flow всех основных процессов

По всем процессам есть расписанная по шагам последовательность действий, которые происходят в сервисе, в четкой структуре

1. Горутина обработки ответа на сообщение
- подтягиваем прошлую историю переписки из кеша переписок, если кеш пуст, достаем из messages)
- достаем заранее созданные инстанс агента из мапы агентов
- достаем информацию (user context) по пользователю из кеша пользователей (если нет, идем в avalon за данными)
- динамически собираем промт агента, добавляя туда историю, данные пользователя
- запускаем цикл агента, получаем новое сообщение
- сообщение сохраняем в messages + кеш
- отправляем сообщение в whatsup gateway

2. Создание новой кампании
...


6. Внешние сервисы, в которые мы ходим изнутри сервиса

Все внешние системы, в которых мы дергаем ручки/отправляем файлы/пишем в чужие очереди итд.

Главное отличие от пункта 3 - в нем наш сервис как черный ящик, и мы описываем как внешний мир его видит и использует. Тут - наоборот.

- хранилище офферов - забираем оффер в json описании при заведении новой кампании

7. Мониторинги, за которыми мы будем следить и которые будут нам звонить если что-то идет не так

Пишем только криты, которые если происходят, мы в любое время дня и ночи сразу собираем звонок и решаем инцидент

- в табличке messages скопилось больше 500 сообщений от пользователей в статусе NEW, которые мы не разгребаем более 10 минут (то есть процессинг упал)


Зачем такая духота?

Важно, чтобы до программирования (передачи PRD в клод код) мы детально понимали логику будущего сервиса на всех уровнях, без участков "ну тут хуле делать, пусть клод сам затащит"

Второй момент - верю, что при идеально написанном PRD клод будет писать сервис за один промт, а потом потребуется лишь незначительная шлифовка. Если не пишет, будем править claude.md, пока не напишет))

И последнее, ваш PRD может иметь другие доп секции, но убедитесь что текущие присутствуют обязательно и оформлены один в один в том же формате

———
Сидел потел, пробовал оцифровать правила наших PRD для будущих сервисов, без помощи нейронок 🫠

Есть что-то важное, что забыл? За лучший комментарий отправлю Кабанчика бесплатно по России (без шуток)

#prd
Forwarded from SimpleAGI
Собеседование на AI-инженера в банк: три вещи, которые реально проверяют

Собрал в кучу инфу по теме AI-инженера. "Горячая" тема, судя по рилсам)
Типичная вакансия: Python, LLM, RAG, агенты, production. Но на собесе не проверяют знание этих слов. Проверяют три вещи:
1. Trade-off мышление - не "лучший подход", а "лучший для этой ситуации"
2. Production-фокус - как это будет жить, ломаться и стоить денег
3. Язык домена - говоришь ли ты на языке бизнеса, а не только на языке ML
___
1. Trade-off мышление
Нет "лучшего" решения. Есть решение, оптимальное для конкретных ограничений.
Chunking в RAG
| Стратегия       | Плюс               | Минус              | Когда выбирать           |
|----------------|--------------------|--------------------|--------------------------|
| Мелкие чанки | Точнее поиск | Теряем контекст | Фактовые вопросы |
| Крупные чанки | Больше контекста | Шум в retrieval | Аналитические вопросы |
| Parent-child | И точность, и контекст | Два индекса, сложнее | Когда критично качество |

Зрелый ответ: "Зависит от типа вопросов. Для фактовых - мельче, для аналитических - крупнее."
Retrieval
| Метод             | Плюс                 | Минус                                   |
|------------------|----------------------|------------------------------------------|
| Dense (векторный) | Понимает семантику | Может пропустить exact match |
| Sparse (BM25) | Точный match | "РКО" ≠ "расчётно-кассовое обслуживание" |
| Hybrid | Лучшее из двух | Сложнее настройка |

В проде почти всегда hybrid - потому что dense пропускает точные совпадения (аббревиатуры, коды), а sparse не понимает семантику.
Агент vs Граф
| Подход              | Плюс                          | Минус                                           |
|---------------------|-------------------------------|-------------------------------------------------|
| Свободный агент | Гибкость | Непредсказуемость, дорого, сложно тестировать |
| Граф (state machine) | Воспроизводимость, аудируемость | Нужно продумать все пути заранее |

Для банка граф почти всегда лучше - регулятор любит предсказуемость.
Зрелый ответ: "Сначала смотрю, можно ли графом. Агент - когда реально нужна гибкость, а не красивая архитектура."

Треугольник оптимизации
QUALITY

/|\
/ | \
▽──┴──▽
LATENCY COST

- Streaming - реальная latency та же, но UX кардинально лучше
- Кэширование мгновенные ответы, но риск устаревших данных
- Роутинг по сложности простые вопросы на дешёвую модель. 80% запросов обычно простые - экономия существенная
- Reranking - quality +, но latency -
___
2. Production-фокус
Сделать прототип — легко. Поддерживать систему, которая не деградирует - сложно.
Что может пойти не так
| Проблема           | Что происходит                          | Как заметить                         |
|-------------------|------------------------------------------|--------------------------------------|
| Устаревший индекс | Регламенты обновились, база старая | Рост ответов "информации нет" |
| Изменение модели | Провайдер обновил модель | Скачок метрик после апдейта |
| Падение интеграций | CRM или бэкенд недоступен | Рост таймаутов |
| Смена паттернов | Пользователи спрашивают о новом | Незнакомые вопросы в логах |

Безопасность агентов
| Механизм           | Зачем                                           |
|-------------------|--------------------------------------------------|
| Allowlist tools | Только разрешённые инструменты |
| Loop guard | Лимит шагов, времени, стоимости |
| Human-in-the-loop | Подтверждение на чувствительных действиях |

Loop guard - мастхэв. Агент может решить, что ему нужно 50 вызовов API на простой вопрос.
Verifier - обязательный компонент
Generate → Verify → Respond
This media is not supported in your browser
VIEW IN TELEGRAM
Кто на стажировку в команду Claude Cowork к Борису Черни?

Только не в Claude Cowork, а в LocalDesk.
И не к Борису, а к Валере @neuraldeep

Если вдруг кто пропустил, Валера Ковальский и куча народа уже 10 дней пилят свой Cowork, который работает на вашем компе на любых моделях.

https://github.com/vakovalskii/LocalDesk

https://t.me/neuraldeep/1858
https://t.me/neuraldeep/1874

Как задрот агентских циклов, давно хотел закопаться, как все устроено, только сегодня с утра дошли руки:

- в каждый запрос к LLM передаётся 15-45 tool definitions в зависимости от настроек - модель видит все доступные инструменты сразу (удачи слабым моделькам на 45 тулах ☕️)

- цикл работает до тех пор, пока в ответе есть tool_calls - если массив пустой, выводим финальный ответ и завершаемся

- ответ читаем в режиме стриминга: текст пушим в UI сразу, но tool_calls накапливаем - аргументы приходят чанками, JSON парсим только после полного ответа

- модель может вернуть несколько tool calls в одном ответе (parallel_tool_calls: true), но выполняем их строго последовательно через for await - параллелизация не реализована 💡

- после выполнения каждого tool результат добавляется в массив messages как { role: 'tool', content: output }, на следующей итерации модель видит полный контекст

- контекст растёт монотонно: system prompt + все user messages + все assistant messages + все tool results, truncation и summarization отсутствуют 💡

- tool results не обрезаются - если read_file вернул 5000 строк, все 5000 строк попадают в контекст 💡

- loop detection: sliding window из 5 последних вызовов, если 5 подряд одинаковых tool names - инжектим hint "Попробуй другой подход", даём 5 retry, потом принудительный стоп

- batch tool calls (2+ инструмента в одном ответе) сбрасывают счётчик loop detection - считаются намеренным поведением модели

- не можем отправить новое сообщение, чтобы дать подсказку агенту в середине цикла, должны отменить выполнение или дожидаться конца 💡


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

А LocalDesk - похоже.

Смотрите, в тех местах, где стоят 💡 можно придти и сделать лучше - оптимизировать расход токенов, увеличить скорость за счет параллельности, сделать компакшен контекста.

Хотите более серьезных приключений - можно сделать субагентов, поддержать MCP, прикрутить донаты в конце концов!

Кстати, именно так и выглядит реальная разработка агентов в проде, на само ядро не тратится много времени, а на управление памятью, оптимизации контекста, детализацию в логах, трассировки - полно. А потом еще начинаются корзинки и evals, и совсем потрачено…

Так что если кто-то хотел "поизучать агентов пару часиков на выхах", вот самый лучший способ.

Еще раз ссылка на GitHub
И чатик, где идет движуха: @neuraldeepchat

———
Пойду тоже запилю что-то, а то Валера здороваться перестанет...
Please open Telegram to view this post
VIEW IN TELEGRAM
Привет всем!👋

Когда мы говорим про длительные вычисления, всегда заходит речь о кешировании результатов выполнения.
И лучшим решением является сохранение кэша не в оперативной памяти (как это делает functools.lru_cache), а запись на диск с помощью joblib.Memory.

Итак, joblib.Memory - декоратор, который оборачивает функцию, сохраняя кэш напрямую на диск.
При вызове декоратор хэширует аргументы функции и проверяет, есть ли результат для хэша на диске. Если есть, то моментально загружает с диска и возвращает результат. В противном случае декоратор сохраняет результат на диск и возвращает результат.

Разберем на примере:
🔸Создаем объект памяти, куда будем сохранять наш кэш. За путь к кэшу отвечает атрибут location
from joblib import Memory
import time
import numpy as np

memory = Memory(location='./cache_store', verbose=0)

🔸Декорирование функции происходит с помощью декоратора @memory.cache. Для примера обернем очень "тяжелую" функцию, которая спит 10 секунд.
@memory.cache
def foo(x, y):
time.sleep(10)
return x**2 + y**2


🔸Запустим тест 1 раз
x,y = np.ones((1000, 1)), np.ones((1000, 1))
res1 = foo(x,y)

🔸Код будет исполняться 10 секунд. Второй запуск уже будет использовать результаты кеширования:
x,y = np.ones((1000, 1)), np.ones((1000, 1))
res2 = foo(x,y)

🔸Код будет только разгружать уже предварительно рассчитанный результат работы модели.

✔️В качестве дополнительных техник можно использовать сжатие с помощью compress, задавая параметр от 1 до 9, который держит баланс между скоростью чтения и размером.
memory = Memory(location='./.cache_store', verbose=0, compress=3)

✔️Также, часто мы передаем в функцию аргументы, которые не влияют на результат вычислений, но меняют хеш. Например, флаг verbose или количество потоков n_jobs. Если ты изменишь verbose=True на verbose=False, joblib подумает, что аргументы изменились, и пересчитает всё заново. Это нам не надо.

Например для какой-то функции process_data
@memory.cache(ignore=['verbose', 'n_jobs'])
def process_data(data, param=1, verbose=False, n_jobs=4):
...

Однако, есть и неприятные эффекты.
Например, если внутри функции есть np.random без фиксации сида, кэширование "заморозит" первое случайное значение навсегда. Это может убить эксперимент.

Кэш можно удалить классическим rm -rf, либо программно memory.clear(warn=False)

По традиции, ставь 🔥, если понравилось!

#ds_лайфхаки
Please open Telegram to view this post
VIEW IN TELEGRAM