Как найти похожие результаты и отсортировать по сходству? - PullRequest
67 голосов
/ 27 июля 2010

Как запросить записи, упорядоченные по сходству?

Например.при поиске «Переполнение запасов» будет возвращено

  1. Переполнение стека
  2. Переполнение SharePoint
  3. Математическое переполнение
  4. Политическое переполнение
  5. VFXПереполнение

Например.поиск «LO» вернет:

  1. pabLO picasso
  2. michelangeLO
  3. Джексон ПолЛок

Что мне нужно помочьс:

  1. Использование поисковой системы для индексации и поиска в таблице MySQL, для улучшения результатов

    • Использование Sphinx поисковая система с PHP

    • с использованием Lucene с PHP

  2. с использованием полнойиндексирование текста, чтобы найти похожие / содержащие строки


Что не работает хорошо

  • Расстояние Левенштейна очень неустойчиво.( UDF , Запрос )
    Поиск "собака" дает мне:
    1. собака
    2. болото
    3. назад
    4. большой
    5. эхо
  • LIKE возвращает лучшие результаты, но ничего не возвращает для длинных запросов, хотя подобные строки существуют
    1. собака
    2. Dogid
    3. Dogaral
    4. Dogma

Ответы [ 3 ]

82 голосов
/ 27 июля 2010

Я обнаружил, что расстояние Левенштейна может быть хорошим, когда вы ищете полную строку по другой полной строке, но когда вы ищете ключевые слова внутри строки, этот метод не возвращает (иногда) требуемые результаты. Более того, функция SOUNDEX не подходит для языков, отличных от английского, поэтому она довольно ограничена. Вы можете сойти с рук как, но это действительно для базовых поисков. Возможно, вы захотите посмотреть на другие методы поиска того, чего вы хотите достичь. Например:

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

Или, если вам нужно решение MySQL, полнотекстовая функциональность довольно хорошая и, безусловно, быстрее, чем хранимая процедура. Если ваши таблицы не MyISAM, вы можете создать временную таблицу, а затем выполнить полнотекстовый поиск:

CREATE TABLE IF NOT EXISTS `tests`.`data_table` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(2000) CHARACTER SET latin1 NOT NULL,
  `description` text CHARACTER SET latin1 NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;

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

** ПРИМЕЧАНИЕ **: тип столбца должен быть latin1_bin, чтобы выполнять поиск с учетом регистра, а не без учета регистра с latin1. Для строк в кодировке Unicode я бы порекомендовал utf8_bin для регистрозависимых символов и utf8_general_ci для регистрозависимых поисков.

DROP TABLE IF EXISTS `tests`.`data_table_temp`;
CREATE TEMPORARY TABLE `tests`.`data_table_temp`
   SELECT * FROM `tests`.`data_table`;

ALTER TABLE `tests`.`data_table_temp`  ENGINE = MYISAM;

ALTER TABLE `tests`.`data_table_temp` ADD FULLTEXT `FTK_title_description` (
  `title` ,
  `description`
);

SELECT *,
       MATCH (`title`,`description`)
       AGAINST ('+so* +nullam lorem' IN BOOLEAN MODE) as `score`
  FROM `tests`.`data_table_temp`
 WHERE MATCH (`title`,`description`)
       AGAINST ('+so* +nullam lorem' IN BOOLEAN MODE)
 ORDER BY `score` DESC;

DROP TABLE `tests`.`data_table_temp`;

Подробнее об этом можно узнать на справочной странице MySQL API

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

** ОБНОВЛЕНИЕ **

Используя Lucene для поиска, вам просто нужно будет создать задание cron (у всех веб-хостов есть эта «особенность»), где это задание будет просто выполнять скрипт PHP (ig «cd / path / to / script; php searchindexer»). .php "), которая обновит индексы. Причина в том, что индексация тысяч «документов» (строк, данных и т. Д.) Может занять несколько секунд, даже минут, но это необходимо для того, чтобы все поиски выполнялись максимально быстро. Поэтому вы можете захотеть создать задание задержки, которое будет запускаться сервером. Это может произойти в одночасье или в следующий час, это зависит от вас. PHP-скрипт должен выглядеть примерно так:

$indexer = Zend_Search_Lucene::create('/path/to/lucene/data');

Zend_Search_Lucene_Analysis_Analyzer::setDefault(
  // change this option for your need
  new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive()
);

$rowSet = getDataRowSet();  // perform your SQL query to fetch whatever you need to index
foreach ($rowSet as $row) {
   $doc = new Zend_Search_Lucene_Document();
   $doc->addField(Zend_Search_Lucene_Field::text('field1', $row->field1, 'utf-8'))
       ->addField(Zend_Search_Lucene_Field::text('field2', $row->field2, 'utf-8'))
       ->addField(Zend_Search_Lucene_Field::unIndexed('someValue', $someVariable))
       ->addField(Zend_Search_Lucene_Field::unIndexed('someObj', serialize($obj), 'utf-8'))
  ;
  $indexer->addDocument($doc);
}

// ... you can get as many $rowSet as you want and create as many documents
// as you wish... each document doesn't necessarily need the same fields...
// Lucene is pretty flexible on this

$indexer->optimize();  // do this every time you add more data to you indexer...
$indexer->commit();    // finalize the process

Тогда, в основном, вы ищете (основной поиск):

$index = Zend_Search_Lucene::open('/path/to/lucene/data');

// same search options
Zend_Search_Lucene_Analysis_Analyzer::setDefault(
   new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive()
);

Zend_Search_Lucene_Search_QueryParser::setDefaultEncoding('utf-8');

$query = 'php +field1:foo';  // search for the word 'php' in any field,
                                 // +search for 'foo' in field 'field1'

$hits = $index->find($query);

$numHits = count($hits);
foreach ($hits as $hit) {
   $score = $hit->score;  // the hit weight
   $field1 = $hit->field1;
   // etc.
}

Вот отличные сайты о Lucene в Java , PHP и .Net .

В заключение У каждого метода поиска есть свои плюсы и минусы:

  • Вы упомянули Поиск по сфинксу , и это выглядит очень хорошо, если вы можете заставить демона работать на вашем веб-хосте.
  • Zend Lucene требует задания cron для повторной индексации базы данных. Хотя он достаточно прозрачен для пользователя, это означает, что любые новые данные (или удаленные данные!) Не всегда синхронизируются с данными в вашей базе данных и, следовательно, не будут сразу отображаться при поиске пользователя.
  • Поиск в MySQL FULLTEXT хорош и быстр, но не даст вам всей силы и гибкости первых двух.

Пожалуйста, не стесняйтесь комментировать, если я что-то забыл / пропустил.

21 голосов
/ 27 июля 2010

1.Сходство

Для Левенштейна в MySQL я обнаружил это с www.codejanitor.com / wp / 2007/02/10 / levenshtein-distance-as-a-mysql-storage-function

SELECT 
    column, 
    LEVENSHTEIN(column, 'search_string') AS distance 
FROM table 
WHERE 
    LEVENSHTEIN(column, 'search_string') < distance_limit
ORDER BY distance DESC

2.Содержит регистронезависимый

Используйте оператор MySQL LIKE, который по умолчанию не учитывает регистр.% является подстановочным знаком, поэтому может быть любая строка до и после search_string.

SELECT 
    *
FROM 
    table
WHERE 
    column_name LIKE "%search_string%"

3.Содержит, чувствителен к регистру

Справка MySQL помогает:

Набор символов по умолчанию и сопоставление: latin1 и latin1_swedish_ci, поэтому недвоичные сравнения строк выполняютсянечувствителен по умолчанию.Это означает, что если вы ищете с col_name LIKE 'a%', вы получите все значения столбцов, которые начинаются с A или a.Чтобы сделать этот поиск чувствительным к регистру, убедитесь, что один из операндов имеет чувствительность к регистру или двоичное сопоставление.Например, если вы сравниваете столбец и строку, в которых оба имеют набор символов latin1, вы можете использовать оператор COLLATE, чтобы у любого из операндов были параметры сортировки latin1_general_cs или latin1_bin ...

MyНастройка MySQL не поддерживает latin1_general_cs или latin1_bin, но я прекрасно использовал сортировку utf8_bin, так как двоичный файл utf8 чувствителен к регистру:

SELECT 
    *
FROM 
    table
WHERE 
    column_name LIKE "%search_string%" COLLATE utf8_bin

2./ 3. отсортировано по Levenshtein Расстояние

SELECT 
    column, 
    LEVENSHTEIN(column, 'search_string') AS distance // for sorting
FROM table 
WHERE 
    column_name LIKE "%search_string%"
    COLLATE utf8_bin // for case sensitivity, just leave out for CI
ORDER BY
    distance
    DESC
3 голосов
/ 06 ноября 2015

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

Очень скоро у вас будет множество мер сходства, когда все меры хороши ииметь некоторые преимущества перед другими.

Чтобы объединить такие меры, я хотел бы свести проблему к проблеме классификации.

Вам следует создать набор данных из слов и обозначить ихкак "связано".Чтобы построить большой помеченный набор данных, вы можете:

  • Использовать источники известных связанных слов (например, старые добрые категории Википедии) для позитивов
  • Большинство слов не известны как связанныене связаны.

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

...