Стриминговые алгоритмы
Слайд 1
В
На сегодняшний день такие алгоритмы используются при анализе данных постоянно включенных систем (например роутер, веб-сервер и тд), составлении планов запросов к базе данных, подсчета статистик при подготовке текстовых данных в машинном обучении и тд
Слайд 2
Скетч(дословно набросок) - алгоритм вычисляющий некоторую статистику при помощи оценки данных, а не самих данных.
Для того что бы получать контроллируемую погрешность в этой оценке, нам необходимо формализовать подход к определению того, чего мы ожидаем от алгоритма.
Предлагается использовать
Count-min sketch
Для второго создателя не знаю какое фото лучше)
Идея моментов и гарантии независимости
Слайд 1
Ранее мы определяли универсальное семейство хэш-функций(recall)
Однако для гарантирования корректности следующих алгоритмов нам потербуется более сильный вариант этого свойства, называемый
Пусть
для любой функции
Слайд 2
Это эквивалентно обладанию
Для фиксированного хэшируемого объекта
x значения хэш-функцийh(x) равномерно распределены надM Для
k различных объектов(x_1),⋯,(x_k) , для любой хэш-функцииh значенияh((x_1)),⋯,h((x_k)) являются независимыми случайными величинами
Второе свойство является принципиально важным для многих алгоритмов при анализе смещения оценки
Слайд 3
Если для универсального семейства мы рассматривали линейные функции над конечным полем, то теперь рассмотрим полиномы
Выберем некоторое простое число
M Сгенерируем равномерно случайные коэффиценты многочлена из диапазона
[0,M)-(a_0),⋯,(a_k-1) В качестве функции вернем
h(x)=mod((a_k-1)*x(k-1)+⋯+(a_1)*x+(a_0),M)
Интуитивно понятно почему этот алгоритм верен:
Согласно теореме Лагранжа чере
А вероятность правильно выбрать все такие пары точек для фиксированной функции
Слайд 4
Так как далее мы будем решать задачи связанные с оценками частотности, то давайте зафиксируем, что мы работаем с векторами
При этом определяются
"Tug-of-war" AMS sketch (F2 estimator)
Слайд 1
https://www.alibabanews.com/wp-content/uploads/sites/default/files/inline-images/suzhou1.jpeg
Авторы с этим алгоритмом получили премию Гёделя в
Слайд 2
Задача состоит в том, что бы найти приблизительную оценкую join запросов в базу данных, источников DDOS атак, а так же имеет приложение в машинном обучении, так как оценка при помощи этого алгоритма оказывается создает случайную проекцию исходных данных в пространство меньшей размерности, при этом сохраняя ее внутреннюю структуру
Слайд 3
Пусть у нас имеется семейство хэш-функций
ℋ={h:U→{1,-1}} . Выберем оттуда случайную хэш-функцию функциюh Пока поступают данные будем поддерживать счетчик
C :Запрос добавление элемента
𝓊∈U :C⟼C+h(𝓊) Запрос удаления элемента
𝓊 :C⟼C-h(𝓊) Узнать текущую оценку: вернуть
C2
Слайд 4
Нам нужно показать что этот скетч возвращает несмещенную оценку момента
В любой момент времени алгоритма
Так как семейство
Но
Слайд 5
Что бы получить
Опять воспользовавшись
Слайд 6
Очевидно что такое ожидание будет ненулевым только если
Слайд 7
Исходя из ограничения на дисперсию оценки, которое мы получили на предыдущем шаге - само по себе оно может быть достаточно большим. Довольно распространненая техника уменьшения разброса оценки состоит в том что бы сделать несколько независимых счетчиков(те случайным образом равномерно сгенерировать
Теперь мы можем записать неравенство Чебышёва для средней оценки:
Те что бы получить точность ответа не хуже
Тем самым мы можем достичь приемлемой точности, храня всего лишь набор счетчиков счетчик, а не сам вектор частот из чего финальная оценка по памяти равна
Слайд 8
void AMS(std::istream& is, std::ostream& os, double eps, double delta) {
int k = 2. / (eps * eps * delta);
std::vector<std::hash<element_t>> h(k);
std::vector<ll> c(k);
for (auto& i : h) {
h = get_4_independent_hash({1, -1});
}
while (!is.eof()) {
char type;
is >> type;
if (type == '?') {
double c_avg = 0.0;
for (const auto& i : c) {
c_avg += i * i;
}
c_avg /= k * 1.0;
os << c_avg << "\n";
}
else {
element_t x;
is >> x;
for (ll i = 0; i < k; ++i) {
auto hash = h[i](x);
if (type == '+') {
c[i] += hash;
} else {
c[i] -= hash;
}
}
}
}
}Слайд 9
Пример
Пусть мы имеем поток вида:
Мы имеем вектор
Возьмем произвольную хэш-функцию из семейства
В качестве хэшируемых значений будем брать номера типов элементов:
Теперь определим итоговый хэш таким образом:
Тогда оценка для этого потока при помощи алгоритма AMS будет рассчитываться как:
Если мы сгенерируем еще несколько хэш-функций из
Функция | |||
|---|---|---|---|
5 | 5 | ||
1 | 4 | 0 | |
5 | 4 |
Flajolet-Martin sketch (F0 estimator)
Слайд 1(можно вынести как общий рассказ перед count-min sketch а потом уже тогда рассказать FM)
Другой важной метрикой является число уникальных элементов
Эта задача возникает тогда когда нужно оценить число уникальных действий, например таковыми могут быть число просмотров, уникальных пользователей/посетителей веб-сайта, записей в базу данных и тд
Слайд 2
https://en.wikipedia.org/wiki/Philippe_Flajolet#/media/File:PhilippeFlajolet.jpg
У второго нет подтвержденных фотографий(возможно эта https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQqtr-BI_K-d2x2eHyB03FuQ6D4mCA2rjgrqA&s)
Второй подход о котором сегодня мы поговорим, предложил Филлип Флажолет в соавторстве с Нигелем Мартином в
Идея состоит в том, что если у вас есть какое-то маловероятное событие, то что бы получить этот исход на практике, вам потребуется провести очень много экспериментов.
Например пусть вы подбрасываете монетку и хотите получить ровно
Будем проводить эксперименты по подбрасыванию монетки пока нам не выпадет решка, и считать сколько до этого было орлов. Теперь можно обозначить в качестве успешного эксперимента тот, в котором этот исход реализовался - выпало
Мы получим исходы которые описываются геометрическим распределением, а всего в среднем нам придется провести:
Слайд 3
Нетрудно заметить, что эксперимент с монеткой легко перенести на реальные данные. Пусть у нас есть достаточно хорошее семейство хэш-функций
У алгоритма существует множество вариантов, приведем одну из них:
Перед обработкой потока заведем битовый вектор
bitmap длиныL и проинициализируем его нулямиДля нового добавленного объекта
x получить хэшh(x) Найти индекс первой единицы в бинарном представлении числа
h(x) справа. Обозначим эту функцию заρ . Например:ρ(1101012)=0 аρ(10010002)=3. Обратите внимание что тут нумерация битов происходит в порядке LittleEndian(от младшего к старешму биту)Установим значение
(bitmap_ρ) равным 1Если нужно предъявить ответ, то вывести
N=(2l)/𝜑 , где𝜑≈0.77351 аl - наименьший такой индекс что(bitmap_l)=0
Слайд 4н
Строгое доказательство довольно сложно, поэтому здесь посмотрим на основную идею.
Так как мы сказали что наше семейство
Что бы проставить
Ожидать такого события можно раз в:
Если
Так же отметим, что для идеальных гарантий что у этого алгоритма, что у его модификаций нужно потребовать что бы
Слайд 5
Тут пример подсчета
Слайд 6
Нетрудно заметить какие у такого подхода имеются минусы.
Во-первых константу
Во-вторых алгоритм имеет тендецию завышать оценку. Обновляя
Можно обратиться к подходу усреднения ответов как в AMS скетче, и тем самым уменьшить разборс, но проблема состоит в том среднее арифметическое чувствительно к выбросам, и
Слайд 7
Брать медиану нескольких независимых оценок оказывается тоже неэффективно, так как в одной и той же степенью двойки может оцениваться довольно широкий промежуток разных значений
Для решения подобных проблем используют технику Median of means. Пусть у нас есть
В такой конфигурации алгоритма сложность по памяти составит
Слайд 8
void FM(std::istream& is, std::ostream& os, int d, int q) {
const double corr = 0.77351;
const ll L = 64;
int k = d * q;
std::vector<std::hash<element_t>> h(k);
for (auto& i : h) {
h = get_good_hash(L);
}
std::vector<std::bitset<64>> bitmap(k);
while (!is.eof()) {
char type;
is >> type;
if (type == '?') {
std::vector<double> estimators(d, 0.0);
for (ll i = 0; i < d; ++i) {
for (ll j = i; j < k; j += d) {
int maxpow = find_max_2pow_index(bitmap[j]);
estimators[i] += std::pow(2, maxpow) / corr;
}
estimators[i] /= q;
}
std::nth_element(estimators.begin(), estimators.begin() + d / 2, estimators.end());
os << estimators[d / 2] << "\n";
} else {
element_t x;
is >> x;
for (ll i = 0; i < k; ++i) {
auto hash = h[i](x);
int maxzero = find_max_2pow_index(h[i]);
bitmap[i][maxzero] = 1;
}
}
}
}KMV sketch (F0 estimator)
LogLog algorithm
Слайд 1
Развитием идеи FM-скетча стал алгоритм LogLog предложенный в
Слайд 2
Идея состоит в том, что бы разделить значение хэш-функции
Например, пусть:
Сам алгоритм строится следующим образом:
Завести несколько счетчиков
(ρ_max^j) , гдеj пробегает от0 до2B-1 Далее начать обработку поступающих элементов. Получая новый объект
x вычислить его хэшh(x) Определить индекс
(j_h(x)) по первымB битам и ранг(ρ_h(x)) по остаткуВ случае если
(ρ_h(x)) превышает(ρ_max^(j_h(x))) то обновить(ρ_max^(j_h(x))) значением(ρ_h(x)) .Если поступает запрос вывести оценку то предъявить
N=(α_q)⋅q⋅2(ρ_max) , где(ρ_max) это среднее арифметическое по всем субпотокам, а(α_q) - сложная константа которая определяется в зависимости от числа субпотоков:(α_q)={[0.673,,,q=16],[0.697,,,q=32],[0.709,,,q=64],[0.7213/(1+1.079/q),,,q⩾128])
Слайд 3
Алгоритм по-прежнему опирается на ожидаемую редкость события, когда у числа подряд идет несколько одинаковых битов, но тепрь меняется то, что для стохастического усреднения мы используем сами значения хэш-функции
Здесь уместно отметить почему алгоритм называется LogLog: мы храним лишь ранги чисел. Ранг это индекс первой единицы
Хотя FM-sketch имеет лучшую дисперсию:
Слайд 4
Пример
HyperLogLog algorithm
Éric Fusy - https://www.cnrs.fr/sites/default/files/image/Fusy_0.jpg
Oliver Gandoulet - https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQloXxv0GHew7mjpJbONradm5O5ufknla6kCQ&s
Frédéric Meunier - https://dimag.ibs.re.kr/cms/wp-content/uploads/2019/11/frederic-2000x1200.jpg
Слайд 1
Наконец финальной модификацией которую мы рассмотрим будет HyperLogLog алгоритм. Разработан в
Этот алгоритм сейчас наиболее активно используется в продуктовых решениях.
Его используют крупные компании для анализа потока данных(числа уникальных пользователей / действий): Google BigQuery, Meta, Reddit и др
Он интегрирован во многие базы, компьют-енджины, и аналитические фреймворки: Redis, Amazon Redshift, Elasticsearch, Apache Spark и др
Слайд 2
В структуре алгоритма LogLog предлагается сделать
Первое заключается в замене первоначальной оценки(или сырой оценки)
Предлагается формула
Тут мы перешли от оценки через среднее геометрическое, к оценке через среднее гармоническое, которое более устойчиво к выбросам. Как уже мы ранее выяснили
Тогда:
Откуда
Слайд 3
Второе важное отличие - работа с граничными кейсами:
N<5/2*q : ПустьV - число субпотоков в которых не попал ни один элемент.Если
V≠0 , что в качестве корректировки результата будем использовать Linear Counting метод, вместо нашего исходного метода:N=q*log(q/V) N>M/L : Если мы практически исчерпали мощность хэш-функции, то алгоритм будет давать все больше коллизий, что искажает оценку. Поэтому есть смысл так же внести поправку:N=-M*log(1-N/M) Иначе
N=N
Изначальные оценки(как было показано в секции с FM алгоритмом) для ответа являются несмещенными только асимптотически, поэтому при небольших значениях
В тоже время так как, хэш-функция у нас в любом случае не идеальная, то будут возникать коллизии, и чем ближе
Поэтому мы заменяем для этих случаев оценки на более точные, и возвращаем
Слайд 4
Рассказать про Linear Counting(?)
Слайд 5
void HLL(std::istream& is, std::ostream& os, int b) {
ll q = std::pow(2, q);
const ll m = std::pow(2, 64);
std::hash<element_t> h = get_good_hash(m);
std::vector<int> subs_rho(q, 0);
while (!is.eof()) {
char type;
is >> type;
if (type == '?') {
double n_raw = 0.0;
for (const auto& i : subs_rho) {
n_raw += std::pow(2.0, -i);
}
n_raw = 1.0 / n_raw;
n_raw *= alpha(q) * q * q;
double n_good = n_raw;
if (2 * n_raw < 5 * q) {
ll v = 0;
for (const auto& i : subs_rho) {
if (i > 0) {
++v;
}
}
if (v != 0) {
n_good = q * std::log(q / v);
}
} else if (n_raw > m / 64) {
n_good = -m * std::log(1.0 - n_raw / m);
}
os << n_good << "\n";
} else {
element_t x;
is >> x;
ll hash = h(x);
ll index = get_index(h(x));
ll first_one = find_max_2pow_index(bitstring_reverse(hash) >> index);
subs_rho[index] = std::max(subs_rho[index], first_one + 1);
}
}
}