Создание сопоставимого и гибкого отпечатка объекта

Моя ситуация

Скажем, у меня есть тысячи объектов, которые в этом примере могут быть фильмами.

Я разбираю эти фильмы по-разному, собирая параметры, ключевые слова и статистику по каждому из них. Позвольте называть их ключами. Я также назначаю вес каждому ключу в диапазоне от 0 до 1, в зависимости от частоты, релевантности, силы, оценки и т.д.

В качестве примера, вот несколько клавиш и весов для фильма Armageddon:

"Armageddon"
------------------
disaster       0.8
bruce willis   1.0
metascore      0.2
imdb score     0.4
asteroid       1.0
action         0.8
adventure      0.9
...            ...

Там может быть пара тысяч этих ключей и весов, и для ясности здесь есть еще один фильм:

"The Fast and the Furious"
------------------
disaster       0.1
bruce willis   0.0
metascore      0.5
imdb score     0.6
asteroid       0.0
action         0.9
adventure      0.6
...            ...

Я называю это отпечатком фильма, и я хочу использовать их для поиска похожих фильмов в моей базе данных.

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

Моя проблема

Итак, я зашел так далеко, но теперь эта часть мне кажется сложной. Я хочу взять отпечаток пальца вверх и превратить его во что-то легко сравнимое и быстрое. Я попытался создать массив, где index 0= disaster, 1= bruce willis, 2= metascore и их значение - вес.

Это выглядит примерно так для моих двух фильмов выше:

[ 0.8 , 1.0 , 0.2 , ... ]
[ 0.1 , 0.0 , 0.5 , ... ]

Который я пробовал сравнивать по-разному, просто умножая:

public double CompareFingerprints(double[] f1, double[] f2)
{
    double result = 0;

    if (f1.Length == f2.Length)
    {
        for (int i = 0; i < f1.Length; i++)
        {
            result += f1[i] * f2[i];
        }
    }

    return result;
}

или сравнения:

public double CompareFingerprints(double[] f1, double[] f2)
{
    double result = 0;

    if (f1.Length == f2.Length)
    {
        for (int i = 0; i < f1.Length; i++)
        {
            result += (1 - Math.Abs(f1[i] - f2[i])) / f1.Length;
        }
    }

    return result;
}

и т.д.

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

Мой вопрос

Есть ли у меня правильный подход или я изобретаю колесо действительно неэффективным способом? Надеюсь, мой вопрос не будет шире для Stack Overflow, но я сузил его с помощью нескольких мыслей ниже.

Несколько мыслей

  • Должен ли мой отпечаток действительно быть массивом весов?
  • Должен ли я заглянуть в хэширование моего отпечатка пальца? Это может помочь с хранением отпечатков пальцев, но затрудняет сравнение. Я нашел некоторые подсказки, что это может быть действительный подход, используя чувствительность к местоположению, но математика немного не в моих силах.
  • Должен ли я извлекать все тысячи фильмов из SQL и работать с результатом, или есть способ реализовать мое сравнение в SQL-запросе и вернуть только 100 лучших просмотров?
  • Является редким представлением данных, чтобы посмотреть на него? (Спасибо Speed8ump)
  • Могу ли я применять методы, используемые при сравнении фактических отпечатков пальцев или для OCR?
  • Я слышал, что есть программное обеспечение, которое обнаруживает обман на экзамене, обнаруживая сходство в тысячах опубликованных статей и предыдущих тестов. Какой метод они используют?

Ура!

Ответ 1

Альтернатива: вектор функций

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

При обработке таких векторов вы должны применять нечеткую логику для расчетов. С нечеткой логикой вам нужно немного поиграть, пока не найдете лучших операторов numericla в соответствии с вашими нечеткими операциями. Например. нечеткие И и ИЛИ могут быть вычислены с помощью "мин" и "макс" или с "*" и "+" или даже с более сложными экспоненциальными операциями. Вы должны найти правильный баланс между хорошими результатами и быстрыми вычислениями.

К сожалению, нечеткая логика не очень хорошо вписывается в базы данных SQL. Если вы идете нечетким образом, вам следует рассмотреть возможность хранения всех ваших данных в памяти и использования своего рода ускорения цифровой обработки (инструкции процессора SIMD, CUDA/OpenCL, FPGA и т.д.).

Альтернатива: схема звезд/снежинок

Другой подход заключается в создании классической схемы хранилища данных. Это хорошо сочетается с современными базами данных SQL. У них есть хорошие ускорения для извлечения данных из хранилища данных среднего размера (до нескольких миллиардов записей):

  • Материализованные представления (для сокращения данных)
  • (сжатый) растровые индексы (для быстрого объединения нескольких функций)
  • Сжатое хранилище (для быстрой передачи огромных дат)
  • Перфекционирование (физическое разделение данных в соответствии с их функциями)

Чтобы использовать эти оптимизации, вы должны сначала подготовить дату.

Иерархические размеры

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

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

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

Индекс растрового изображения будет сохранен в сжатом двоичном формате. Для базы данных очень просто объединить несколько растровых индексов, используя быструю двоичную обработку (посредством ANDing или ORing двоичных значений, используя инструкции процессора SIMD или даже OpenCL/CUDA и т.д.).

Существуют специальные типы растровых индексов, которые могут охватывать несколько таблиц, так называемые индексы битмап-соединения. Они специально созданы для данных, организованных в схеме снежинок.

Уменьшение размеров

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

Элементы дискретного размера

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

Альтернатива 3: Гибридный подход

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

Ответ 2

Идея классная, таким образом, я могу найти все хорошие фильмы (imdb > 5.5) с Брюсом, где он играет главную роль (bruce willis > 0.9), которые являются действиями (action > 0.5) и не являются ужасами (ужас < 0,1). Я ненавижу ужасы.

Ваши мысли:

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

Я думаю, что здесь вам нужна система Tag (например, SO one), где вы можете легко добавлять новые теги (например, для новых актеров или когда будет что-то лучше, чем сине-луча или HD, и т.д). Итак, таблица с тегом [id] - [name].

Затем ваши фильмы должны иметь поле, в котором хранится словарь [id] - [score] от нуля до миллиона тегов. Это должен быть blob (или есть способ удерживать словарь или массив в базе данных SQL?) Или массив (если ваш идентификатор тега начинается с 0 и увеличивается на 1, вам не нужен ключ, а индекс).

Когда вы ищете фильмы, соответствующие условиям отпечатков пальцев, вам нужно будет прочитать отпечаток пальца из базы данных для каждого фильма. Это должно быть медленнее, чем если бы SQL-запрос выполнял его, но все равно нормально (у вас будет, возможно, 100-1000 тегов на фильм, что заставляет читать только несколько килобайт), если вам не нужно передавать эти данные по сети, а затем подумайте для использования серверного приложения. Возможно, хранимые процедуры могут помочь.

Ответ 3

Я думаю, что хеширование - это то, что вы ищете, хеш-таблица дает вам O(1) для вставки, удаления и поиска.
У меня была аналогичная ситуация, когда мне приходилось хешировать массив из восьми отличных целых чисел. Я использовал следующий код из библиотеки ускорения С++.

size_t getHashValue ()const{

        size_t seed = 0;
        for (auto  v : board)
            seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);

        return seed;


    }

мой массив был вызван board, и это синтаксис цикла foreach в C++, size_t - это просто целое число без знака, а остальное - то же, что и в C#.
обратите внимание, поскольку у меня были разные значения, я могу легко использовать это значение как хеш-функцию, поэтому я могу гарантировать отличное хеш-значение для каждого элемента в моем массиве.

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

foreach (float entry in array)
    // hashOf is something you would need to do 
    seed ^= hashOf(entry) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 

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

EDIT:

см. этот вопрос для хэширования десятичных значений: С# Decimal.GetHashCode() и Double.GetHashCode() равно.

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

Ответ 4

Формат отпечатков пальцев
Что касается вашего 1-го вопроса, следует ли использовать массив весов, который сводится к уровню детализации, который вы хотите. Массив весов будет предлагать наивысшее "разрешение" отпечатка пальца из-за отсутствия лучшего термина; он позволяет намного более мелкозернистое измерение того, насколько похожи любые два указанных фильма. Предложение Sinatr использовать теги вместо весов имеет большой потенциал оптимизации, но это существенно ограничивает вас весом 0 или 1 и, следовательно, имеет проблемы с представлением существующих весов в диапазоне 0,3-0,7. Вам нужно будет решить, будет ли выигрыш в производительности при представлении с меньшими деталями перевешивает уменьшенную точность сравнения, представленную этими представлениями.

Хэш
Что касается вашего второго вопроса, я боюсь, что не могу предложить много рекомендаций. Я не знаком с использованием хэширования в подобном контексте, но я не вижу, как вы могли бы легко их сравнить; весь смысл хэшей в большинстве применений заключается в том, что они не могут быть легко отменены, чтобы узнать об исходном вводе.

Оптимизация SQL
Для вашего 3-го вопроса SQL-запрос, который вы используете для получения кандидатов сравнения, вероятно, является большим источником потенциала оптимизации производительности, особенно если вы знаете некоторые характеристики ваших отпечатков пальцев. В частности, если высокие веса или малые веса относительно редки, то вы можете использовать их для отсечения многих бедных кандидатов. Например, если вы использовали фильмы, вы ожидали бы, что большая часть весов будет равна 0 (большинство фильмов не содержат Брюса Уиллиса). Вы можете посмотреть на какие-либо веса в вашем кандидатском фильме, которые выше, чем 0,8 или около того (вам нужно будет выполнить точную настройку, чтобы определить точные значения, которые хорошо работают для вашего набора данных), а затем ваш SQL-запрос исключает результаты которые имеют 0, по крайней мере, в некоторой части этих ключей (опять же, фракция нуждается в тонкой настройке). Это позволяет быстро отбрасывать результаты, которые вряд ли будут хорошими совпадениями на этапе запросов SQL, а не выполнять полное (дорогое) сравнение с ними.

Другие параметры
Другой подход, который может работать в зависимости от того, как часто изменяются отпечатки ваших объектов, заключается в предварительном вычислении значений сравнения отпечатков пальцев. Тогда получение лучших кандидатов - это один запрос из индексированной таблицы: SELECT id1, id2, comparison FROM precomputed WHERE (id1 = foo OR id2 = foo) AND comparison > cutoff ORDER BY comparison DESC. Предварительное вычисление сравнений для нового объекта будет частью процесса его добавления, поэтому, если возможность быстрого добавления объектов является приоритетом, тогда этот подход может не работать. В качестве альтернативы вы можете просто кэшировать значения, как только вы их вычислили, а не предварительно их вычислять. Это не делает ничего для первоначального поиска, но позже поиски пожинают плоды, а добавление объектов остается дешевым.