Как работает алгоритм HyperLogLog?

Недавно я изучал разные алгоритмы в свое свободное время, и тот, с которым я сталкивался, который кажется очень интересным, называется алгоритмом HyperLogLog, который оценивает количество уникальных элементов в списке.

Это было особенно интересно для меня, потому что оно вернуло меня в мои дни MySQL, когда я увидел это значение "Cardinality" (которое я всегда предполагал до недавнего времени, что он был рассчитан не оценен).

Итак, я знаю, как написать алгоритм в O (n), который рассчитает количество уникальных элементов в массиве. Я написал это в JavaScript:

function countUniqueAlgo1(arr) {
    var Table = {};
    var numUnique = 0;
    var numDataPoints = arr.length;
    for (var j = 0; j < numDataPoints; j++) {
        var val = arr[j];
        if (Table[val] != null) {
            continue;
        }
        Table[val] = 1;
        numUnique++;
    }
    return numUnique;
}

Но проблема в том, что мой алгоритм, в то время как O (n), использует много памяти (сохраняя значения в Table).

Я читал эту статью о том, как подсчитывать дубликаты в списке в O (n) и использовать минимальную память.

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

Я прочитал газету, но я не могу понять ее. Может ли кто-нибудь дать объяснение более простой? Я знаю, что такое хэши, но я не понимаю, как они используются в этом алгоритме HyperLogLog.

Ответ 1

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

То есть в случайном потоке целых чисел ~ 50% чисел (в двоичном виде) начинается с "1", 25% начинается с "01", 12,5% начинается с "001". Это означает, что если вы наблюдаете случайный поток и видите "001", есть большая вероятность того, что этот поток имеет мощность 8.

(Префикс "00..1" не имеет особого значения. Он существует только потому, что в большинстве процессоров легко найти старший значащий бит двоичного числа)

Конечно, если вы наблюдаете только одно целое число, вероятность того, что это значение неверно, высока. Поэтому алгоритм делит поток на "m" независимых подпотоков и сохраняет максимальную длину видимого префикса "00... 1" каждого подпотока. Затем оценивает окончательное значение, принимая среднее значение каждого подпотока.

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

Ответ 2

A HyperLogLog - вероятностная структура данных. Он подсчитывает количество отдельных элементов в списке. Но по сравнению с простым способом сделать это (имея набор и добавляя элементы к множеству), он делает это примерно так.

Прежде чем смотреть, как это делает алгоритм HyperLogLog, нужно понять, зачем вам это нужно. Проблема с простым способом состоит в том, что он потребляет O(distinct elements) пространства. Почему здесь существует большая нотация O, а не только отдельные элементы? Это потому, что элементы могут быть разных размеров. Один элемент может быть 1 другим элементом "is this big string". Поэтому, если у вас есть огромный список (или огромный поток элементов), это займет много памяти.


Вероятностный подсчет

Как можно получить разумную оценку целого ряда уникальных элементов? Предположим, что у вас есть строка длины m, которая состоит из {0, 1} с равной вероятностью. Какова вероятность того, что он начнется с 0, с двумя нулями, с k нулями? Это 1/2, 1/4 и 1/2^k. Это означает, что если вы столкнулись с строкой с k нулями, вы примерно просмотрели элементы 2^k. Так что это хорошая отправная точка. Имея список элементов, которые распределены равномерно между 0 и 2^k - 1, вы можете подсчитать максимальное число самого большого префикса нулей в двоичном представлении, и это даст вам разумную оценку.

Проблема заключается в том, что предположение о равномерно распределенных числах из 0 t 2^k-1 слишком сложно достичь (данные, с которыми мы столкнулись, в основном не являются числами, почти никогда не распределены равномерно и могут быть между любыми значениями. используя хорошую хэш-функцию, вы можете предположить, что выходные биты будут равномерно распределены, а большинство функций хэширования имеют выходы между 0 и 2^k - 1 (SHA1 дает вам значения между 0 и 2^160). Итак, до сих пор мы достигли того, что мы можем оценить количество уникальных элементов с максимальной мощностью бит k путем хранения только одного бита размера log(k). Недостатком является то, что мы имеем огромную дисперсию в нашей оценке. Прохладная вещь, которую мы почти создали 1984 вероятностный подсчет (он немного умнее оценки, но все же мы близки).

LogLog

Прежде чем двигаться дальше, мы должны понять, почему наша первая оценка не так уж велика. Причина этого в том, что одно случайное появление высокочастотного 0-префиксного элемента может испортить все. Один из способов улучшить его - использовать множество хеш-функций, подсчитывать max для каждой из хеш-функций и, в конце концов, усреднять их. Это отличная идея, которая улучшит оценку, но Журнал LogLog использовал несколько иной подход (возможно, потому, что хеширование является дорогостоящим).

Они использовали один хэш, но разделили его на две части. Один из них называется ведром (общее количество ведер - 2^x), а другое - в основном такое же, как и наш хеш. Мне было трудно получить то, что происходило, поэтому я приведу пример. Предположим, что у вас есть два элемента и ваша хеш-функция, которая дает значения формы 0 to 2^10, генерирует 2 значения: 344 и 387. Вы решили иметь 16 ведер. Итак, у вас есть:

0101 011000  bucket 5 will store 1
0110 000011  bucket 6 will store 4

Если у вас больше ведер, вы уменьшаете дисперсию (вы используете немного больше места, но она по-прежнему крошечная). Используя математические навыки, они смогли количественно определить ошибку (которая равна 1.3/sqrt(number of buckets)).

HyperLogLog

HyperLogLog не вводит никаких новых идей, но в основном использует много математики для улучшения предыдущей оценки. Исследователи обнаружили, что если вы удалите 30% самых больших чисел из ведер, вы значительно улучшите оценку. Они также использовали другой алгоритм для усреднения чисел. Бумага тяжелая.


И я хочу закончить с недавней статьи, которая показывает улучшенную версию алгоритма hyperLogLog (до сих пор у меня не было времени чтобы полностью понять это, но, возможно, позже я улучшу этот ответ).

Ответ 3

Интуиция заключается в том, что ваш вход представляет собой большой набор случайных чисел (например, хэшированных значений), они должны распределяться равномерно по диапазону. Пусть говорят, что диапазон составляет до 10 бит, чтобы представить значение до 1024. Затем наблюдалось минимальное значение. Пусть говорят, что это 10. Тогда мощность будет оцениваться примерно 100 (10 × 100 ≈ 1024).

Конечно, прочитайте статью для реальной логики.

Еще одно хорошее объяснение с примером кода можно найти здесь:
Проклятые крутые алгоритмы: оценка кардинальности - блог Ник