58 votes

Comment utiliser Lucene.NET pour faciliter la recherche sur un site comme Stack Overflow ?

J'ai a posé une question similaire sur Meta Stack Overflow mais qui traite spécifiquement de savoir si oui ou non Lucene.NET est utilisé sur Stack Overflow.

L'objectif de la question est plutôt d'ordre hypothétique : quelles seraient les approches à adopter si l'on utilisait Lucene.NET comme base pour la recherche sur site et d'autres facteurs dans un site ? comme Stack Overflow [SO].

Selon l'article du blog de Stack Overflow intitulé " Problèmes de recherche plein texte en SQL 2008 " il y avait un fort que Lucene.NET était envisagé à un moment donné, mais il semble que ce ne soit pas du tout le cas, comme le montre le commentaire de la personne suivante Geoff Dalgas le 19 février 2010 :

Lucene.NET n'est pas utilisé pour Stack Overflow - nous utilisons SQL Server l'indexation en texte intégral. La recherche est un domaine où nous continuons à faire des ajustements mineurs ajustements mineurs.

Ma question est donc la suivante : comment utiliser Lucene.NET dans un site qui a la même sémantique que Stack Overflow ?

Voici le contexte et ce que j'ai fait/réfléchi jusqu'à présent (oui, j'ai mis en œuvre la plupart de ces mesures et la recherche est le dernier aspect que je dois compléter) :

Technologies :

Et bien sûr, la star du spectacle, Lucene.NET.

L'intention est également de passer à .NET/C# 4.0 dès que possible. Bien que je ne pense pas que cela change la donne, il faut le noter.

Avant d'aborder les aspects de Lucene.NET, il est important de souligner les aspects de SQL Server 2008, ainsi que les modèles impliqués.

Modèles

Ce système a plus d'un type de modèle primaire par rapport à Stack Overflow. Voici quelques exemples de ces modèles :

  • Les questions : Il s'agit de questions que les gens peuvent poser. Les gens peuvent répondre aux questions, tout comme sur Stack Overflow.
  • Notes : Il s'agit de projections à sens unique, donc, contrairement à une question, vous faites une déclaration sur le contenu. Les gens ne peuvent pas y répondre.
  • Les événements : Il s'agit de données relatives à un événement en temps réel. Elles contiennent des informations sur la localisation, la date et l'heure.

La chose importante à noter à propos de ces modèles :

  • Ils ont tous une propriété Name/Title (texte) et une propriété Body (HTML) (les formats ne sont pas pertinents, car le contenu sera analysé de manière appropriée).
  • Chaque instance d'un modèle a une URL unique sur le site.

Ensuite, il y a les éléments fournis par Stack Overflow qui, selon l'OMI, sont des décorateurs pour les modèles. Ces décorateurs peuvent avoir différentes cardinalités, soit une à une, soit une à plusieurs :

  • Votes : En fonction de l'utilisateur
  • Réponses : Facultatif, à titre d'exemple, voir le cas des notes ci-dessus.
  • Favorisé : Le modèle figure-t-il parmi les favoris d'un utilisateur ?
  • Commentaires : (facultatif)
  • Associations de balises : Les balises sont dans une table séparée, afin de ne pas répliquer la balise pour chaque modèle. Il existe un lien entre le modèle et la table des associations de balises, puis de la table des associations de balises à la table des balises.

Et il y a les tallies de soutien qui sont elles-mêmes des décorateurs un-à-un pour les modèles qui leur sont associés de la même manière (généralement par un type d'identifiant de modèle et l'identifiant du modèle) :

  • Décompte des votes : Total des votes positifs et négatifs, Intervalle de score Wilson (ceci est important, il va déterminer le niveau de confiance basé sur les votes pour une entrée, pour la plupart, assumer la limite inférieure de l'intervalle de Wilson).

Les réponses sont des modèles qui possèdent la plupart des décorateurs de la plupart des modèles, ils n'ont simplement pas de titre ou d'URL, et le fait qu'un modèle possède ou non une réponse est facultatif. Si les réponses sont autorisées, il s'agit bien sûr d'une relation de type un à plusieurs.

SQL Server 2008

Les tables suivent à peu près la disposition des modèles ci-dessus, avec des tables séparées pour les décorateurs, ainsi que quelques tables et vues de soutien, des procédures stockées, etc.

Il convient de noter que la décision de ne pas utiliser la recherche en texte intégral se fonde principalement sur le fait qu'elle ne normalise pas les résultats comme Lucene.NET. Je suis ouvert aux suggestions sur la façon d'utiliser la recherche en texte intégral, mais je vais devoir effectuer des recherches sur plusieurs types de modèles, alors gardez à l'esprit que je vais devoir normaliser les scores. en quelque sorte .

Lucene.NET

C'est là que se trouve le grand point d'interrogation. Voici ce que j'ai pensé jusqu'à présent de la fonctionnalité Stack Overflow, ainsi que de ce que j'ai déjà fait et comment.

Indexation

Questions/Modèles

Je pense que chaque modèle devrait avoir son propre index contenant un identifiant unique permettant de le rechercher rapidement en fonction d'une instance de Term de cet identifiant (indexé, pas analysé).

Dans ce domaine, j'ai envisagé que Lucene.NET analyse chaque question/modèle et chaque réponse individuellement. Ainsi, s'il y avait une question et cinq réponses, la question et chacune des réponses seraient indexées séparément comme une unité.

L'idée ici est que le score de pertinence que Lucene.NET renvoie serait plus facile à comparer entre des modèles qui projettent de différentes manières (disons, quelque chose sans réponses).

Par exemple, une question définit le sujet, puis la réponse l'approfondit.

Pour une note, qui n'a pas de réponses, il s'agit de présenter le sujet et de le développer ensuite.

Je pense que cela contribuera à rendre les scores de pertinence plus pertinents les uns par rapport aux autres.

Tags

Au départ, j'ai pensé que ces documents devaient être conservés dans un index séparé avec plusieurs champs contenant les identifiants des documents dans l'index du modèle approprié. Ou, si c'est trop grand, il y a un index avec juste les tags et un autre index qui maintient la relation entre l'index des tags et les questions auxquelles ils sont appliqués. De cette façon, lorsque vous cliquez sur une balise (ou utilisez la structure URL), il est facile de voir de manière progressive que vous n'avez qu'à "adhérer" si vous réussissez :

  • Si le tag existe
  • Les questions auxquelles les étiquettes sont associées
  • Les questions elles-mêmes

Cependant, dans la pratique, effectuer une requête de tous les éléments sur la base des balises (comme cliquer sur une balise dans Stack Overflow) est extrêmement facile avec SQL Server 2008. Sur la base du modèle ci-dessus, il suffit d'une requête telle que :

select
     m.Name, m.Body
from
    Models as m
        left outer join TagAssociations as ta on
            ta.ModelTypeId = <fixed model type id> and
            ta.ModelId = m.Id
        left outer join Tags as t on t.Id = ta.TagId
where
    t.Name = <tag>

Et comme certaines propriétés sont partagées par tous les modèles, il est assez facile de faire un UNION entre les différents types de modèles/tables et produire un ensemble cohérent de résultats.

Cela serait analogue à un TermQuery dans Lucene.NET (je me réfère à l'article de la loi sur la protection de l'environnement). Documentation Java car il est complet, et Lucene.NET est censé être une traduction ligne par ligne de Lucene (donc toute la documentation est la même).

Le problème qui se pose en utilisant Lucene.NET ici est celui de l'ordre de tri. Le score de pertinence d'un TermQuery en ce qui concerne les balises n'est pas pertinent. Il est soit 1, soit 0 (soit il est présent, soit il ne l'est pas).

À ce stade, le score de confiance (intervalle de score de Wilson) entre en jeu pour ordonner les résultats.

Ce score pourrait être stocké dans Lucene.NET, mais pour trier les résultats sur ce champ, il faudrait que les valeurs soient stockées dans le cache du champ, ce que je veux vraiment éviter. Pour un grand nombre de documents, le cache du champ peut devenir très grand (le score de Wilson est un double, et vous auriez besoin d'un double pour chaque document, ce qui peut représenter un grand tableau).

Étant donné que je peux modifier l'instruction SQL pour ordonner sur la base de l'intervalle de score Wilson comme ceci :

select
     m.Name, m.Body
from
    Models as m
        left outer join TagAssociations as ta on
            ta.ModelTypeId = <fixed model type id> and
            ta.ModelId = m.Id
        left outer join Tags as t on t.Id = ta.TagId
        left outer join VoteTallyStatistics as s on
            s.ModelTypeId = ta.ModelTypeId and
            s.ModelId = ta.ModelId
where
    t.Name = <tag>
order by
    --- Use Id to break ties.
    s.WilsonIntervalLowerBound desc, m.Id

Il semble facile de l'utiliser pour gérer la fonctionnalité de Stack Overflow "get all items tagged with <tag>".

Réponses

À l'origine, je pensais qu'il s'agissait d'un index distinct, avec une clé pour revenir à l'index des questions.

Je pense qu'il devrait y avoir une combinaison de chaque modèle et de chaque réponse (s'il y en a une) afin que les scores de pertinence des différents modèles soient plus "égaux" les uns par rapport aux autres.

Cela ferait bien sûr gonfler l'index. Je suis plutôt à l'aise avec cela pour le moment.

Ou bien, existe-t-il un moyen de stocker, par exemple, les modèles et les réponses en tant que documents individuels dans Lucene.NET, puis de les prendre tous les deux et d'obtenir le score de pertinence pour une requête qui traite. les deux documents en un seul ? Si oui, alors ce serait idéal .

Il y a bien sûr la question de savoir quels champs seraient stockés, indexés, analysés (toutes les opérations peuvent être des opérations séparées, ou mélangées) ? Quel serait le montant de l'indice ?

Qu'en est-il de l'utilisation d'extracteurs/porters spéciaux pour les fautes d'orthographe (en utilisant Metaphone) ainsi que des synonymes (il y a une terminologie dans la communauté que je vais servir qui a son propre argot/terminologie pour certaines choses qui a des représentations multiples) ?

Boost

Cette question est liée à l'indexation, bien sûr, mais je pense qu'elle mérite une section à part entière.

Est-ce que vous en stimulant des champs et/ou des documents ? Si oui, comment les renforcez-vous ? Le renforcement est-il constant pour certains champs ? Ou bien est-il recalculé pour les champs où des données de vote/visites/favoris/externes sont applicables.

Par exemple, dans le document, le titre bénéficie-t-il d'un coup de pouce par rapport au corps du texte ? Si c'est le cas, quels sont les facteurs de renforcement qui, selon vous, fonctionnent bien ? Qu'en est-il des balises ?

Le raisonnement est ici le même que celui qui prévaut pour Stack Overflow. Les termes contenus dans le document sont pertinents, mais si un document est étiqueté avec le terme, ou s'il est dans le titre, alors il devrait être boosté.

Shashikant Kore suggère une structure de document comme celle-ci :

  • Titre
  • Question
  • Réponse acceptée (ou réponse hautement votée s'il n'y a pas de réponse acceptée)
  • Toutes les réponses combinées

Et ensuite utiliser le boost mais pas sur la base de la valeur brute des votes. Je crois que j'ai couvert cela avec l'intervalle de score Wilson.

La question est de savoir si le coup de pouce doit être appliqué à l'ensemble du document. Je penche pour le non, car cela signifierait que je devrais réindexer le document chaque fois qu'un utilisateur vote sur le modèle.

Recherche d'éléments marqués

Je pensais à l'origine que lors de l'interrogation d'une balise (en cliquant spécifiquement sur une balise ou en utilisant la structure URL pour rechercher du contenu balisé), il s'agissait d'une simple requête TermQuery dans l'index des balises pour la balise, puis dans l'index des associations (si nécessaire) puis de nouveau dans les questions, Lucene.NET gère cela très rapidement.

Cependant, étant donné les notes ci-dessus concernant la facilité avec laquelle il est possible de faire cela dans SQL Server, j'ai opté pour cette voie lorsqu'il s'agit de rechercher des éléments étiquetés.

Recherche générale

Maintenant, la question la plus importante est la suivante : lorsque vous effectuez une recherche générale par expression ou par terme sur le contenu, que faites-vous et comment intégrez-vous les autres informations (comme les votes) afin de déterminer les résultats dans le bon ordre ? Par exemple, en effectuant cette recherche sur ASP.NET MVC sur Stack Overflow Voici le décompte des cinq premiers résultats (en utilisant l'onglet pertinence) :

    q votes answers accepted answer votes asp.net highlights mvc highlights
    ------- ------- --------------------- ------------------ --------------
         21      26                    51                  2              2
         58      23                    70                  2              5
         29      24                    40                  3              4
         37      15                    25                  1              2
         59      23                    47                  2              2

Notez que les surbrillances ne figurent que dans le titre et le résumé de la page de résultats et ne sont que des indicateurs mineurs de la fréquence réelle des termes dans le document, le titre, la balise, la réponse (quelle que soit la façon dont ils sont appliqués, ce qui est une autre bonne question).

Comment tout cela est-il rassemblé ?

À ce stade, je sais que Lucene.NET retournera un score de pertinence normalisé, et que les données de vote me donneront un intervalle de score de Wilson que je peux utiliser pour déterminer le score de confiance.

Comment dois-je envisager de combiner ces deux scores pour indiquer l'ordre de tri de l'ensemble des résultats en fonction de la pertinence et de la confiance ?

Il est évident pour moi qu'il devrait y avoir un peu de relation entre les deux, mais ce que cette relation devrait être m'échappe pour l'instant. Je sais que je dois l'affiner au fil du temps, mais je suis vraiment perdue sur cette partie.

Si le score de pertinence est compris entre 0 et 1 et que le score de confiance est compris entre 0 et 1, je pourrais faire quelque chose comme ceci :

1 / ((e ^ cs) * (e ^ rs))

De cette façon, on obtient une valeur normalisée qui s'approche de 0 plus le résultat est pertinent et fiable, et on peut le trier sur cette base.

Le principal problème est que si le boosting est effectué sur le champ balise et/ou titre, alors le score de pertinence est en dehors des limites de 0 à 1 (l'extrémité supérieure devient alors non bornée, et je ne sais pas comment gérer cela).

De plus, je crois que je vais devoir ajuster le score de confiance pour tenir compte des résultats des votes qui sont complètement négatifs. Puisque les décomptes de votes qui sont complètement négatifs résultent en un intervalle de score de Wilson avec une limite inférieure de 0, quelque chose avec -500 votes a le même score de confiance que quelque chose avec -1 vote, ou 0 vote.

Heureusement, la limite supérieure diminue de 1 à 0 au fur et à mesure que le nombre de votes négatifs augmente. Je pourrais changer le score de confiance pour qu'il soit compris entre -1 et 1, comme ceci :

confidence score = votetally < 0 ? 
    -(1 - wilson score interval upper bound) :
    wilson score interval lower bound

Le problème, c'est qu'en introduisant le chiffre 0 dans l'équation, tous les éléments ayant reçu zéro voix seront classés en dessous de ceux ayant reçu un nombre de voix négatif.

À cette fin, je pense que si le score de confiance doit être utilisé dans une équation réciproque comme ci-dessus (je suis préoccupé par le débordement évidemment), alors il doit être retravaillé pour être toujours positif. Une façon d'y parvenir est la suivante :

confidence score = 0.5 + 
    (votetally < 0 ? 
        -(1 - wilson score interval upper bound) :
        wilson score interval lower bound) / 2

Mes autres préoccupations sont de savoir comment effectuer réellement le calcul avec Lucene.NET et SQL Server. J'hésite à placer le score de confiance dans l'index Lucene car cela nécessite l'utilisation du cache de champ, ce qui peut avoir un impact énorme sur la consommation de mémoire (comme mentionné précédemment).

J'ai eu l'idée d'obtenir le score de pertinence à partir de Lucene.NET, puis d'utiliser une méthode d'évaluation de la pertinence. paramètre de type tableau pour transmettre le score au serveur SQL (avec les identifiants des éléments à sélectionner), à partir duquel j'effectuerais le calcul avec le score de confiance, puis je renverrais les données correctement ordonnées.

Comme indiqué précédemment, j'ai beaucoup d'autres questions à ce sujet, et les réponses ont commencé à encadrer les choses, et continueront à développer les choses au fur et à mesure que la question et les réponses évoluent.

7voto

Mike Glenn Points 1474

Les réponses que vous cherchez ne peuvent vraiment pas être trouvées en utilisant lucene seul. Vous avez besoin d'algorithmes de classement et de regroupement pour filtrer et comprendre les données et leurs relations. Lucene peut vous aider à obtenir des données normalisées, mais il vous faut ensuite le bon algorithme.

Je vous recommande de consulter l'un ou l'autre des livres suivants, qui vous aideront à résoudre les problèmes mathématiques et vous mettront sur la bonne voie :

Algorithmes du Web intelligent

L'intelligence collective en action

Programmation de l'intelligence collective

3voto

Shashikant Kore Points 3419

L'index lucène aura les champs suivants :

  • Titre
  • Question
  • Réponse acceptée (ou réponse hautement votée s'il n'y a pas de réponse acceptée)
  • Toutes les réponses combinées

Tous ces champs sont analysés. La normalisation de la longueur est désactivée pour un meilleur contrôle de la notation.

L'ordre des champs mentionné ci-dessus reflète également leur importance par ordre décroissant. C'est-à-dire que si la correspondance de la requête dans le titre est plus importante que dans la réponse acceptée, tout le reste reste reste identique.

Le nombre de votes positifs est pour la question et la meilleure réponse peut être capturée en boostant ces champs. Mais, le nombre brut de votes positifs ne peut pas être utilisé comme valeurs de boost car il pourrait fausser les résultats de façon spectaculaire. (Une question avec 4 votes positifs obtiendra deux fois le score d'une question avec 2 votes positifs.) Ces valeurs doivent être atténuées de manière agressive avant de pouvoir être utilisées comme facteur de boost. L'utilisation d'un logarithme naturel (pour les votes positifs > 3) semble bonne.

Le titre peut être renforcé par une valeur légèrement supérieure à celle de la question.

Bien que l'interconnexion des questions ne soit pas très courante, le fait de disposer d'un poids de base de type pagette pour une question pourrait donner des résultats intéressants.

Je ne considère pas les balises de la question comme des informations très précieuses pour la recherche. Les balises sont utiles lorsque vous souhaitez simplement parcourir les questions. La plupart du temps, les balises font partie du texte, donc la recherche des balises aboutira à une correspondance avec la question. Mais cela reste ouvert à la discussion.

Une requête de recherche typique sera effectuée sur les quatre champs.

+(title:query question:query accepted_answer:query all_combined:query)

Il s'agit d'une esquisse générale qui nécessitera des ajustements importants pour parvenir aux bonnes valeurs de boost et aux bons poids pour les requêtes, si nécessaire. L'expérience montrera les bonnes pondérations pour les deux dimensions de la qualité - pertinence et importance. Vous pouvez compliquer les choses en introduisant la récence comme paramètre de classement. L'idée est que, si un problème survient dans une version particulière du produit et qu'il est corrigé dans les révisions ultérieures, les nouvelles questions pourraient être plus utiles à l'utilisateur.

On pourrait ajouter des éléments intéressants à la recherche. Une forme de recherche basique de synonymes pourrait être utile si seuls quelques résultats correspondants sont trouvés. Par exemple, "descrease java heap size" est identique à "reduce java heap size". Mais, alors, cela signifie également que "map reduce" commencera à correspondre à "map decrease". (Le correcteur orthographique est évident, mais je suppose que les programmeurs orthographieraient correctement leurs requêtes).

2voto

Paul Points 8943

Vous avez probablement plus réfléchi à ce sujet que la plupart des gens qui essaieront de vous répondre (ce qui explique en partie pourquoi cela fait un jour et que je suis votre première réponse, j'imagine). Je vais juste essayer d'aborder vos trois dernières questions, parce qu'il y a beaucoup de choses que je n'ai pas le temps d'approfondir, et je pense que ces trois-là sont les plus intéressantes (les questions sur la mise en œuvre physique vont probablement se résumer à "choisissez quelque chose, puis modifiez-le au fur et à mesure que vous apprenez").

données de vote Je ne suis pas sûr que les votes rendent quelque chose plus pertinent pour une recherche, franchement, ils les rendent juste plus populaires. Si cela a un sens, j'essaie de dire que la pertinence d'un message donné par rapport à votre question est en grande partie indépendante de sa pertinence pour d'autres personnes. Cela dit, il y a probablement au moins une faible corrélation entre les questions intéressantes et celles que les gens voudraient trouver. Les données de vote sont probablement plus utiles pour effectuer des recherches basées uniquement sur des données, par exemple des recherches de type "les plus populaires". Pour les recherches génériques basées sur du texte, je ne donnerais probablement pas de poids aux votes dans un premier temps, mais j'envisagerais de travailler sur un algorithme qui donnerait peut-être un léger poids au tri (donc, pas les résultats renvoyés, mais un petit coup de pouce à l'ordre de ceux-ci).

répond à Je suis d'accord avec votre approche ici, sous réserve de quelques tests ; n'oubliez pas qu'il s'agira d'un processus itératif basé sur le retour d'information des utilisateurs (vous devrez donc collecter des données pour savoir si les recherches ont donné des résultats positifs pour l'utilisateur).

autre N'oubliez pas non plus le score de l'utilisateur. Ainsi, les utilisateurs obtiennent des points sur SO également, et cela influence leur rang par défaut dans les réponses de chaque question à laquelle ils répondent (on dirait que c'est surtout pour départager les réponses qui ont le même nombre de bosses).

2voto

Epsilon Prime Points 2677

Déterminer la pertinence est toujours délicat. Vous devez déterminer ce que vous essayez d'accomplir. Votre recherche vise-t-elle à fournir une réponse exacte à un problème que quelqu'un pourrait rencontrer ou à fournir une liste d'articles récents sur un sujet ?

Une fois que vous avez déterminé ce que vous voulez retourner, vous pouvez examiner l'effet relatif de chaque fonctionnalité que vous indexez. Cela vous permettra de lancer une recherche approximative. À partir de là, vous pouvez affiner en fonction des commentaires des utilisateurs (je suggère d'utiliser des commentaires implicites plutôt qu'explicites, sinon vous allez ennuyer l'utilisateur).

En ce qui concerne l'indexation, vous devez essayer d'introduire les données de manière à ce que chaque élément dispose de toutes les informations nécessaires à son classement. Cela signifie que vous devrez récupérer les données à partir d'un certain nombre d'endroits pour les construire. Certains systèmes d'indexation ont la capacité d'ajouter des valeurs aux éléments existants, ce qui faciliterait l'ajout de scores aux questions lorsque des réponses ultérieures sont reçues. Pour simplifier, il suffirait de reconstruire la question de temps en temps.

1voto

Yuki Points 408

Je pense que Lucene n'est pas bon pour ce travail. Vous avez besoin de quelque chose de très rapide et de très disponible... comme SQL. Mais vous voulez de l'open source ?

Je vous suggère d'utiliser Sphinx - http://www.sphinxsearch.com/ C'est beaucoup mieux, et je parle en connaissance de cause, j'ai utilisé les deux.

Sphinx est incroyable. Vraiment.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X