Aspiring Data Science
371 subscribers
425 photos
11 videos
10 files
1.88K links
Заметки экономиста о программировании, прогнозировании и принятии решений, научном методе познания.
Контакт: @fingoldo

I call myself a data scientist because I know just enough math, economics & programming to be dangerous.
Download Telegram
#probabilities #outcomes #synthetic #calibration #decisionmaking #python

Зная вероятности событий, сгенерировать по ним возможные бинарные исходы тривиально. Проще всего сравнить вероятность со случайным числом из интервала [0;1], если число оказалось меньше исходной вероятности, записываем в исход 1, иначе 0.

Внезапно возникла обратная задача: мы знаем исходы (они взяты из реального процесса), но интересно, какие ground truth вероятности могли бы привести к таким исходам? Для чего это может понадобиться: я тестирую систему принятия решений по ML модели, и хочется понять границы возможностей такой связки для конкретной задачи. Допустим, нам удалось построить "идеальную" в вероятностном плане модель, т.е. прекрасно откалиброванную (раз она сказала в какие-то моменты, что вероятность положительного исхода 60%, то примерно в 60% случаев такой исход и случился) - чего тогда можно ожидать от неё в плане онлайн метрик? Понятно, что получить такую модель сложно, часто и невозможно, но нам же хочется знать, каких результатов вообще возможно достичь?

И вот тут оказалось сложнее, чем кажется. Попробуйте поставит текст на паузу и предложить решение )

На удивление, ИИ в лице Чат ГПТ оказался совершенно бессилен, и пришлось думать. (Если сможете получить от него ответ, напишите.)

@njit()
def generate_probs_from_outcomes(
outcomes: np.ndarray, chunk_size: int = 20, scale: float = 0.1, nbins: int = 10, bins_std: float = 0.1, flip_percent: float = 0.6
) -> np.ndarray:
"""Can we generate hypothetical ground truth probs knowing the outcomes in advance?
Our model probs will (hopefully) be calibrated. So, we need synthetic probs to be calibrated, too. With some degree of fitness.
We also need to cover broad range of probs.
So, how to achieve this?

0) if flip_percent is specified, for a random portion of data zeroes and ones are flipped. this will lower ROC AUC.
1) we can work with small random chunks/subsets of data
2) for every chunk, its real freq is computed.
3) for every observation, 'exact' prob is drawn from some distribution (uniform or, say, gaussian) with center in real freq.
then, if bins_std is specified, constant bin noise is applied to all observations of the chunk.

final result is clipped to [0,1]
"""
n = len(outcomes)
indices = np.arange(n)
np.random.shuffle(indices)

probs = np.empty(n, dtype=np.float32)
bin_offsets = (np.random.random(size=nbins) - 0.5) * bins_std

if flip_percent:
# flip some bits to worsen our so far perfect predictive power
flip_size = int(n * flip_percent)
if flip_size:
outcomes = outcomes.copy()
flip_indices = np.random.choice(indices, size=flip_size)
outcomes[flip_indices] = 1 - outcomes[flip_indices]

l = 0 # left border
for idx in range(n // chunk_size): # traverse randomly selected chunks/subsets of original data
r = (idx + 1) * chunk_size # right border
freq = outcomes[l:r].mean() # find real event occuring frequency in current chunk of observation

# add pregenerated offset for particular bin
bin_idx = int(freq * nbins)
freq = freq + bin_offsets[bin_idx]

# add small symmetric random noise. it must be higher when freq approaches [0;1] borders.
probs[l:r] = freq + (np.random.random(size=chunk_size) - 0.5) * scale * np.abs(freq - 0.5)

l = r

return np.clip(probs, 0.0, 1.0)
#programming #perfection #decisionmaking

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

Так у меня было с ансамблированием в самописной системе mlops: я добавил простые ансамбли, стал добавлять стэкинг, там возникли сложности с оверфитом, я увидел, что процесс затягивается, ну и зарелизил хотя бы простые ансамбли. И вот уже полгода пользуюсь, и это приносит пользу. А иначе бы застрял неизвестно насколько.

Так и сейчас с отборщиком признаков, Диогеном. Модуль wrappers потребовал решать задачу одномерной целочисленной оптимизации, я сначала потестил распространённые пакеты типа optuna/skopt/hyperopt, потом написал свой модуль с реализациями гауссова процесса и прям совсем своей идеей, квантильной регрессией+эвристиками. И вот уже 2 месяца я туплю с этим модулем. Работает он по виду хорошо, но до тестирования и уж тем более до реального внедрения никак не доведу.

И вот смотрю я на свои задачи по этому модулю: то мне статические графики не нравятся в matplotlib и я хочу пределать в живые plotly, то мне не нравится, что оптимизатор не поддерживает категориальные входы и вообще множественные входы, и я хочу переделать структуры данных. То я придрался, что сортировки там неоптимальны, хотя по итогам профилирования они ни на что не влияют. А так как в планах создание своего полноценного оптимизатора гиперпараметров, однобокость текущей версии вносит когнитивынй диссонанс и не позволяет продвигаться работе - думаю над оптимальным способом унификации и прочими философскими вещами, в то время как у меня есть прямо сейчас реальные ML проекты, которым очень нужен хороший feature selector.

Ну и вот сегодня додумался, это же как раз тот случай, когда надо выводить в бой уже написанное решение, и не тупить над бесконечными улучшениями ) Завидую я, в общем, программистам, которые с лёгким сердцем особо не думая могут херак, херак, и в production. Так что не затягивайте проекты, релизьте вовремя.
👍1