Comment rechercher des enregistrements classés par similarité?
Par exemple. la recherche de "Stock Overflow" reviendrait
Par exemple. la recherche de "LO" retournerait:
Utiliser un moteur de recherche pour indexer et rechercher une table MySQL, pour de meilleurs résultats
Utilisation de l'indexation de texte intégral pour rechercher des chaînes similaires/contenant
LIKE
renvoie de meilleurs résultats, mais ne renvoie rien pour les longues requêtes bien que des chaînes similaires existent J'ai découvert que la distance Levenshtein peut être bonne lorsque vous recherchez une chaîne complète par rapport à une autre chaîne complète, mais lorsque vous recherchez des mots clés dans une chaîne, cette méthode ne renvoie pas (parfois) les résultats souhaités. De plus, la fonction SOUNDEX ne convient pas aux langues autres que l'anglais, elle est donc assez limitée. Vous pourriez vous en tirer avec LIKE, mais c'est vraiment pour les recherches de base. Vous voudrez peut-être examiner d'autres méthodes de recherche pour savoir ce que vous voulez réaliser. Par exemple:
Vous pouvez utiliser Lucene comme base de recherche pour vos projets. Il est implémenté dans la plupart des principaux langages de programmation et il est assez rapide et polyvalent. Cette méthode est probablement la meilleure, car elle recherche non seulement les sous-chaînes, mais aussi la transposition des lettres, les préfixes et les suffixes (tous combinés). Cependant, vous devez conserver un index séparé (utiliser CRON pour le mettre à jour à partir d'un script indépendant de temps en temps fonctionne cependant).
Ou, si vous voulez une solution MySQL, la fonctionnalité de texte intégral est assez bonne, et certainement plus rapide qu'une procédure stockée. Si vos tables ne sont pas MyISAM, vous pouvez créer une table temporaire, puis effectuer votre recherche plein texte:
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 ;
Utilisez un générateur de données pour générer des données aléatoires si vous ne voulez pas vous embêter à les créer vous-même ...
** [~ # ~] note [~ # ~] **: le type de colonne doit être latin1_bin
pour effectuer une casse recherchez au lieu de ne pas respecter la casse avec latin1
. Pour les chaînes unicode, je recommanderais utf8_bin
Pour les recherches sensibles à la casse et utf8_general_ci
Pour les recherches qui ne respectent pas la casse.
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`;
En savoir plus à ce sujet sur la page de référence de l'API MySQL
L'inconvénient est qu'il ne recherchera pas la transposition de lettres ou des mots "similaires, comme des".
** [~ # ~] mise à jour [~ # ~] **
En utilisant Lucene pour votre recherche, vous aurez simplement besoin de créer un travail cron (tous les hôtes Web ont cette "fonctionnalité") où ce travail exécutera simplement un script PHP (ig "cd/path/to/script; php searchindexer.php ") qui mettra à jour les index. La raison en est que l'indexation de milliers de "documents" (lignes, données, etc.) peut prendre plusieurs secondes, voire quelques minutes, mais c'est pour s'assurer que toutes les recherches sont effectuées aussi rapidement que possible. Par conséquent, vous souhaiterez peut-être créer un travail de retard à exécuter par le serveur. Cela peut être du jour au lendemain, ou dans l'heure suivante, cela dépend de vous. Le script PHP devrait ressembler à ceci:
$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
Ensuite, voici essentiellement comment vous effectuez une recherche (recherche de base):
$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.
}
Voici d'excellents sites sur Lucene en Java , PHP et . Net .
En conclusion chaque méthode de recherche a ses avantages et ses inconvénients:
N'hésitez pas à commenter si j'ai oublié/oublié quelque chose.
1. Similitude
Pour Levenshtein dans MySQL, je l'ai trouvé, à partir de www.codejanitor.com/wp/2007/02/10/levenshtein-distance-as-a-mysql-stored-function
SELECT
column,
LEVENSHTEIN(column, 'search_string') AS distance
FROM table
WHERE
LEVENSHTEIN(column, 'search_string') < distance_limit
ORDER BY distance DESC
2. Contenant, insensible à la casse
Utilisez l'instruction LIKE
de MySQL, qui est insensible à la casse par défaut. Le %
est un caractère générique, il peut donc y avoir n'importe quelle chaîne avant et après search_string
.
SELECT
*
FROM
table
WHERE
column_name LIKE "%search_string%"
3. Contenant, sensible à la casse
Le MySQL Manual aide:
Le jeu de caractères et le classement par défaut sont latin1 et latin1_swedish_ci, donc les comparaisons de chaînes non binaires ne respectent pas la casse par défaut. Cela signifie que si vous recherchez avec col_name LIKE 'a%', vous obtenez toutes les valeurs de colonne qui commencent par A ou a. Pour rendre cette recherche sensible à la casse, assurez-vous que l'un des opérandes a un classement sensible à la casse ou binaire. Par exemple, si vous comparez une colonne et une chaîne qui ont toutes deux le jeu de caractères latin1, vous pouvez utiliser l'opérateur COLLATE pour que l'un ou l'autre opérande ait le classement latin1_general_cs ou latin1_bin ...
Ma configuration MySQL ne prend pas en charge latin1_general_cs
ou latin1_bin
, mais cela a bien fonctionné pour moi d'utiliser le classement utf8_bin
car l'utf8 binaire est sensible à la casse:
SELECT
*
FROM
table
WHERE
column_name LIKE "%search_string%" COLLATE utf8_bin
2./3. triés par distance de 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
Il semble que votre définition de la similitude soit une similitude sémantique. Donc, pour construire une telle fonction de similitude, vous devez utiliser des mesures de similitude sémantique. Notez que la portée du travail sur la question peut varier de quelques heures à plusieurs années, il est donc recommandé de décider de la portée avant de commencer à travailler. Je n'ai pas compris de quelles données disposez-vous pour établir la relation de similitude. Je suppose que vous avez accès à un ensemble de données de documents et à un ensemble de données de requêtes. Vous pouvez commencer par la co-occurrence des mots (par exemple, probabilité conditionnelle). Vous découvrirez rapidement que vous obtenez la liste des mots vides comme liés le plus des mots simplement parce qu'ils sont très populaires. L'utilisation de la levée de la probabilité conditionnelle prendra soin des mots vides mais rendra la relation sujette à erreur en petit nombre (la plupart de vos cas). Vous pourriez essayer Jacard mais comme il est symétrique, il y aura de nombreuses relations qu'il ne trouvera pas. Ensuite, vous pourriez envisager des relations qui n'apparaissent qu'à courte distance du mot de base. Vous pouvez (et devriez) envisager des relations basées sur des corpus généraux (par exemple, Wikipedia) et spécifiques à l'utilisateur (par exemple, ses e-mails).
Très bientôt, vous aurez de nombreuses mesures de similitude, lorsque toutes les mesures sont bonnes et ont un certain avantage sur les autres.
Afin de combiner de telles mesures, j'aime réduire le problème en un problème de classification.
Vous devez créer un ensemble de données de paris de mots et les étiqueter comme "est lié". Pour créer un grand ensemble de données étiqueté, vous pouvez:
Utilisez ensuite toutes les mesures que vous avez comme caractéristiques des paires. Vous êtes maintenant dans le domaine des problèmes de classification supervisée. Construisez un classificateur sur l'ensemble de données, évalué en fonction de vos besoins et obtenez une mesure de similitude qui correspond à vos besoins.