portaldacalheta.pt
  • Principal
  • Rise Of Remote
  • Outils Et Tutoriels
  • Équipes Distribuées
  • Mode De Vie
Back-End

Recherche en texte intégral des dialogues avec Apache Lucene: un didacticiel



Apache Lucene est une bibliothèque Java utilisée pour la recherche de texte intégral de documents, et est au cœur des serveurs de recherche tels que Solr et Elasticsearch . Il peut également être intégré dans des applications Java, telles que des applications Android ou des backends Web.

Bien que les options de configuration de Lucene soient nombreuses, elles sont destinées à être utilisées par développeurs de bases de données sur un corpus générique de texte. Si vos documents ont une structure ou un type de contenu spécifique, vous pouvez en profiter pour améliorer la qualité de la recherche et la capacité de requête.



recherche plein texte avec apache lucene



A titre d'exemple de ce type de personnalisation, dans ce tutoriel Lucene nous allons indexer le corpus de Projet Gutenberg , qui propose des milliers de livres électroniques gratuits. Nous savons que beaucoup de ces livres sont des romans. Supposons que nous soyons particulièrement intéressés par dialogue dans ces romans. Ni Lucene, Elasticsearch, ni Solr ne fournissent des outils prêts à l'emploi pour identifier le contenu en tant que dialogue. En fait, ils jetteront la ponctuation dès les premières étapes de l'analyse de texte, ce qui va à l'encontre de la capacité d'identifier les parties du texte qui sont des dialogues. C'est donc dans ces premières étapes que doit commencer notre personnalisation.



Éléments du pipeline d'analyse Apache Lucene

La Analyse Lucene JavaDoc fournit une bonne vue d'ensemble de toutes les pièces mobiles dans le pipeline d'analyse de texte.

À un niveau élevé, vous pouvez considérer le pipeline d'analyse comme consommant un flux brut de caractères au début et produisant des «termes», correspondant approximativement à des mots, à la fin.



Le pipeline d'analyse standard peut être visualisé En tant que tel:

Pipeline d



Nous verrons comment personnaliser ce pipeline pour reconnaître les régions de texte marquées par des guillemets, ce que j'appellerai dialogue, puis augmenter les correspondances qui se produisent lors de la recherche dans ces régions.

Lecture de caractères

Lorsque les documents sont initialement ajoutés à l'index, les caractères sont lus à partir d'un Java Flux d'entrée , et ainsi ils peuvent provenir de fichiers, de bases de données, d'appels de service Web, etc. Pour créer un index pour le projet Gutenberg, nous téléchargeons les livres électroniques et créons une petite application pour lire ces fichiers et les écrire dans l'index. La création d'un index Lucene et la lecture de fichiers sont des chemins très fréquentés, nous ne les explorerons donc pas beaucoup. Le code essentiel pour produire un index est:



IndexWriter writer = ...; BufferedReader reader = new BufferedReader(new InputStreamReader(... fileInputStream ...)); Document document = new Document(); document.add(new StringField('title', fileName, Store.YES)); document.add(new TextField('body', reader)); writer.addDocument(document);

On voit que chaque e-book correspondra à un seul Lucene Document Ainsi, plus tard, nos résultats de recherche seront une liste de livres correspondants. Store.YES indique que nous stockons le Titre champ, qui est juste le nom du fichier. Nous ne voulons pas stocker le corps de l'ebook, cependant, car il n'est pas nécessaire lors de la recherche et ne ferait que gaspiller de l'espace disque.

La lecture réelle du flux commence par addDocument. Le IndexWriter tire des jetons de la fin du pipeline. Cette extraction revient dans le tube jusqu'à ce que la première étape, le Tokenizer, lit à partir du InputStream.



Notez également que nous ne fermons pas le flux, car Lucene s'en charge pour nous.

Tokenizing Caractères

Le Lucene StandardTokenizer jette la ponctuation, et donc notre personnalisation commencera ici, car nous devons préserver les guillemets.



La documentation pour StandardTokenizer vous invite à copier le code source et à l'adapter à vos besoins, mais cette solution serait inutilement complexe. Au lieu de cela, nous allons étendre CharTokenizer, ce qui vous permet de spécifier des caractères à «accepter», où ceux qui ne sont pas «acceptés» seront traités comme des délimiteurs entre les jetons et jetés. Puisque nous nous intéressons aux mots et aux citations qui les entourent, notre Tokenizer personnalisé est simplement:

public class QuotationTokenizer extends CharTokenizer { @Override protected boolean isTokenChar(int c) return Character.isLetter(c) }

Étant donné un flux d'entrée de [He said, 'Good day'.], les jetons produits seraient [He], [said], ['Good], [day']

s-corporation vs c-corporation

Notez comment les guillemets sont intercalés dans les jetons. Il est possible d'écrire un Tokenizer qui produit des jetons séparés pour chaque citation, mais Tokenizer est également concerné par des détails fastidieux et faciles à bousiller tels que la mise en mémoire tampon et l'analyse, il est donc préférable de conserver votre Tokenizer simple et nettoyez le flux de jetons plus loin dans le pipeline.

Fractionnement de jetons à l'aide de filtres

Après le tokenizer vient une série de TokenFilter objets. Notez, d'ailleurs, que filtre est un peu trompeur, car a TokenFilter peut ajouter, supprimer ou modifier des jetons.

La plupart des classes de filtres fournies par Lucene attendent des mots uniques, il n'est donc pas nécessaire que nos jetons mixtes de mots et de citations y affluent. Ainsi, la prochaine personnalisation de notre didacticiel Lucene doit être l'introduction d'un filtre qui nettoiera la sortie de QuotationTokenizer.

Ce nettoyage impliquera la production d'un supplément commencer le devis jeton si la citation apparaît au début d'un mot, ou un fin de citation jeton si le devis apparaît à la fin. Nous mettrons de côté le traitement des mots entre guillemets simples pour plus de simplicité.

Créer un TokenFilter La sous-classe implique l'implémentation d'une méthode: incrementToken. Cette méthode doit appeler incrementToken sur le filtre précédent dans le tube, puis manipulez les résultats de cet appel pour effectuer le travail dont le filtre est responsable. Les résultats de incrementToken sont disponibles via Attribute objets, qui décrivent l'état actuel du traitement des jetons. Après notre implémentation de incrementToken renvoie, on s'attend à ce que les attributs aient été manipulés pour configurer le jeton pour le filtre suivant (ou l'index si nous sommes à la fin du tube).

Les attributs qui nous intéressent à ce stade du pipeline sont:

  • CharTermAttribute: Contient un char[] tampon contenant les caractères du jeton actuel. Nous devrons manipuler cela pour supprimer le devis ou pour produire un jeton de devis.

    passer par l'entreprise individuelle vs s corp
  • TypeAttribute: Contient le «type» du jeton actuel. Comme nous ajoutons des guillemets de début et de fin au flux de jetons, nous allons introduire deux nouveaux types à l'aide de notre filtre.

  • OffsetAttribute: Lucene peut éventuellement stocker des références à l'emplacement des termes dans le document d'origine. Ces références sont appelées «décalages», qui ne sont que des indices de début et de fin dans le flux de caractères d'origine. Si nous changeons le tampon dans CharTermAttribute pour ne pointer que sur une sous-chaîne du jeton, nous devons ajuster ces décalages en conséquence.

Vous vous demandez peut-être pourquoi l'API de manipulation des flux de jetons est si compliquée et, en particulier, pourquoi nous ne pouvons pas simplement faire quelque chose comme String#split sur les jetons entrants. En effet, Lucene est conçu pour une indexation à haute vitesse et à faible coût, grâce à laquelle les jetons et filtres intégrés peuvent rapidement mâcher des gigaoctets de texte tout en n'utilisant que des mégaoctets de mémoire. Pour ce faire, peu ou pas d'allocations sont effectuées lors de la tokenisation et du filtrage, et donc Attribute les instances mentionnées ci-dessus sont destinées à être allouées une seule fois et réutilisées. Si vos jetons et filtres sont écrits de cette manière et minimisent leurs propres allocations, vous pouvez personnaliser Lucene sans compromettre les performances.

Avec tout cela à l'esprit, voyons comment implémenter un filtre qui prend un jeton tel que ['Hello], et produit les deux jetons, ['] et [Hello]:

public class QuotationTokenFilter extends TokenFilter { private static final char QUOTE = '''; public static final String QUOTE_START_TYPE = 'start_quote'; public static final String QUOTE_END_TYPE = 'end_quote'; private final OffsetAttribute offsetAttr = addAttribute(OffsetAttribute.class); private final TypeAttribute typeAttr = addAttribute(TypeAttribute.class); private final CharTermAttribute termBufferAttr = addAttribute(CharTermAttribute.class);

Nous commençons par obtenir des références à certains des attributs que nous avons vus précédemment. Nous suffixons les noms de champs avec «Attr» afin que ce soit clair plus tard lorsque nous y ferons référence. Il est possible que certains Tokenizer les implémentations ne fournissent pas ces attributs, nous utilisons donc addAttribute pour obtenir nos références. addAttribute créera une instance d'attribut si elle est manquante, sinon récupérez une référence partagée à l'attribut de ce type. Notez que Lucene n'autorise pas plusieurs instances du même type d'attribut à la fois.

private boolean emitExtraToken; private int extraTokenStartOffset, extraTokenEndOffset; private String extraTokenType;

Étant donné que notre filtre introduira un nouveau jeton qui n'était pas présent dans le flux d'origine, nous avons besoin d'un emplacement pour enregistrer l'état de ce jeton entre les appels à incrementToken. Étant donné que nous divisons un jeton existant en deux, il suffit de connaître uniquement les décalages et le type du nouveau jeton. Nous avons également un indicateur qui nous indique si le prochain appel à incrementToken émettra ce jeton supplémentaire. Lucene fournit en fait une paire de méthodes, captureState et restoreState, qui le fera pour vous. Mais ces méthodes impliquent l'attribution d'un State objet, et peut en fait être plus compliqué que de simplement gérer cet état vous-même, nous éviterons donc de les utiliser.

@Override public void reset() throws IOException { emitExtraToken = false; extraTokenStartOffset = -1; extraTokenEndOffset = -1; extraTokenType = null; super.reset(); }

Dans le cadre de son évitement agressif de l'allocation, Lucene peut réutiliser les instances de filtre. Dans cette situation, on s'attend à ce qu'un appel à reset remettra le filtre dans son état initial. Donc ici, nous réinitialisons simplement nos champs de jetons supplémentaires.

@Override public boolean incrementToken() throws IOException { if (emitExtraToken) { advanceToExtraToken(); emitExtraToken = false; return true; } ...

Nous arrivons maintenant aux éléments intéressants. Lorsque notre implémentation de incrementToken est appelé, nous avons la possibilité de ne pas appeler incrementToken au stade antérieur du pipeline. Ce faisant, nous introduisons effectivement un nouveau jeton, car nous ne tirons pas de jeton depuis Tokenizer.

Au lieu de cela, nous appelons advanceToExtraToken pour configurer les attributs de notre jeton supplémentaire, définissez emitExtraToken à false pour éviter cette branche lors du prochain appel, puis retournez true, ce qui indique qu'un autre jeton est disponible.

@Override public boolean incrementToken() throws IOException { ... (emit extra token) ... boolean hasNext = input.incrementToken(); if (hasNext) { char[] buffer = termBufferAttr.buffer(); if (termBuffer.length() > 1) { if (buffer[0] == QUOTE) { splitTermQuoteFirst(); } else if (buffer[termBuffer.length() - 1] == QUOTE) { splitTermWordFirst(); } } else if (termBuffer.length() == 1) { if (buffer[0] == QUOTE) { typeAttr.setType(QUOTE_END_TYPE); } } } return hasNext; }

Le reste de incrementToken fera une des trois choses différentes. Rappelez-vous que termBufferAttr est utilisé pour inspecter le contenu du jeton passant par le tuyau:

  1. Si nous avons atteint la fin du flux de jetons (c'est-à-dire que hasNext est faux), nous avons terminé et retournons simplement.

  2. Si nous avons un jeton de plus d'un caractère et que l'un de ces caractères est une citation, nous divisons le jeton.

  3. Si le jeton est une citation solitaire, nous supposons qu'il s'agit d'une citation de fin. Pour comprendre pourquoi, notez que les guillemets de début apparaissent toujours à gauche d'un mot (c'est-à-dire sans ponctuation intermédiaire), alors que les guillemets de fin peuvent suivre la ponctuation (comme dans la phrase, [He told us to 'go back the way we came.']). Dans ces cas, le guillemet de fin sera déjà un jeton séparé, et nous n'avons donc qu'à définir son type.

splitTermQuoteFirst et splitTermWordFirst définira des attributs pour que le jeton actuel soit un mot ou une citation, et configurera les champs «extra» pour permettre à l'autre moitié d'être consommée plus tard. Les deux méthodes sont similaires, nous allons donc examiner uniquement splitTermQuoteFirst:

private void splitTermQuoteFirst() { int origStart = offsetAttr.startOffset(); int origEnd = offsetAttr.endOffset(); offsetAttr.setOffset(origStart, origStart + 1); typeAttr.setType(QUOTE_START_TYPE); termBufferAttr.setLength(1); prepareExtraTerm(origStart + 1, origEnd, TypeAttribute.DEFAULT_TYPE); }

Parce que nous voulons diviser ce jeton avec le guillemet apparaissant dans le flux en premier, nous tronquons le tampon en définissant la longueur sur un (c'est-à-dire un caractère; à savoir, le guillemet). Nous ajustons les décalages en conséquence (c'est-à-dire en pointant vers le devis dans le document d'origine) et définissons également le type comme un devis de départ.

prepareExtraTerm définira le extra* champs et définir emitExtraToken à vrai. Il est appelé avec des décalages pointant vers le jeton «supplémentaire» (c'est-à-dire le mot qui suit la citation).

Tutoriel de l'API Web de base .net

L'intégralité de QuotationTokenFilter est disponible sur GitHub .

En passant, bien que ce filtre ne produise qu'un seul jeton supplémentaire, cette approche peut être étendue pour introduire un nombre arbitraire de jetons supplémentaires. Remplacez simplement le extra* champs avec une collection ou, mieux encore, un tableau de longueur fixe s'il y a une limite sur le nombre de jetons supplémentaires qui peuvent être produits. Voir SynonymFilter et ses PendingInput classe interne pour un exemple de ceci.

Consommation de jetons de devis et dialogue de marquage

Maintenant que nous avons déployé tous ces efforts pour ajouter ces guillemets au flux de jetons, nous pouvons les utiliser pour délimiter des sections de dialogue dans le texte.

Étant donné que notre objectif final est d'ajuster les résultats de recherche en fonction du fait que les termes font ou non partie du dialogue, nous devons joindre des métadonnées à ces termes. Lucene fournit PayloadAttribute dans ce but. Les charges utiles sont des tableaux d'octets qui sont stockés à côté des termes dans l'index, et peuvent être lus plus tard lors d'une recherche. Cela signifie que notre indicateur occupera inutilement un octet entier, de sorte que des charges utiles supplémentaires pourraient être implémentées sous forme d'indicateurs de bits pour économiser de l'espace.

Vous trouverez ci-dessous un nouveau filtre, DialoguePayloadTokenFilter, qui est ajouté à la toute fin du pipeline d'analyse. Il attache la charge utile indiquant si le jeton fait ou non partie du dialogue.

public class DialoguePayloadTokenFilter extends TokenFilter { private final TypeAttribute typeAttr = getAttribute(TypeAttribute.class); private final PayloadAttribute payloadAttr = addAttribute(PayloadAttribute.class); private static final BytesRef PAYLOAD_DIALOGUE = new BytesRef(new byte[] { 1 }); private static final BytesRef PAYLOAD_NOT_DIALOGUE = new BytesRef(new byte[] { 0 }); private boolean withinDialogue; protected DialoguePayloadTokenFilter(TokenStream input) { super(input); } @Override public void reset() throws IOException { this.withinDialogue = false; super.reset(); } @Override public boolean incrementToken() throws IOException { boolean hasNext = input.incrementToken(); while(hasNext) { boolean isStartQuote = QuotationTokenFilter .QUOTE_START_TYPE.equals(typeAttr.type()); boolean isEndQuote = QuotationTokenFilter .QUOTE_END_TYPE.equals(typeAttr.type()); if (isStartQuote) { withinDialogue = true; hasNext = input.incrementToken(); } else if (isEndQuote) { withinDialogue = false; hasNext = input.incrementToken(); } else { break; } } if (hasNext) { payloadAttr.setPayload(withinDialogue ? PAYLOAD_DIALOGUE : PAYLOAD_NOT_DIALOGUE); } return hasNext; } }

Puisque ce filtre n'a besoin que de maintenir un seul état, withinDialogue, c'est beaucoup plus simple. Un guillemet de début indique que nous sommes maintenant dans une section de dialogue, tandis qu'un guillemet de fin indique que la section de dialogue est terminée. Dans les deux cas, le jeton de citation est supprimé en effectuant un deuxième appel à incrementToken, donc en effet, commencer le devis ou fin de citation les jetons ne dépassent jamais cette étape du pipeline.

Par exemple, DialoguePayloadTokenFilter transformera le jeton:

[the], [program], [printed], ['], [hello], [world], [']`

dans ce nouveau flux:

[the][0], [program][0], [printed][0], [hello][1], [world][1]

Lier les jetons et les filtres ensemble

Un Analyzer est responsable de l'assemblage du pipeline d'analyse, généralement en combinant un Tokenizer avec une série de TokenFilter s. Analyzer s peut également définir la manière dont ce pipeline est réutilisé entre les analyses. Nous n'avons pas besoin de nous en préoccuper car nos composants ne nécessitent rien d'autre qu'un appel à reset() entre les utilisations, ce que Lucene fera toujours. Nous avons juste besoin de faire l'assemblage en implémentant Analyzer#createComponents(String):

public class DialogueAnalyzer extends Analyzer { @Override protected TokenStreamComponents createComponents(String fieldName) { QuotationTokenizer tokenizer = new QuotationTokenizer(); TokenFilter filter = new QuotationTokenFilter(tokenizer); filter = new LowerCaseFilter(filter); filter = new StopFilter(filter, StopAnalyzer.ENGLISH_STOP_WORDS_SET); filter = new DialoguePayloadTokenFilter(filter); return new TokenStreamComponents(tokenizer, filter); } }

Comme nous l'avons vu précédemment, les filtres contiennent une référence à l'étape précédente du pipeline, c'est ainsi que nous les instancions. Nous glissons également quelques filtres de StandardAnalyzer: LowerCaseFilter et StopFilter. Ces deux doivent venir après QuotationTokenFilter pour vous assurer que les guillemets ont été séparés. Nous pouvons être plus flexibles dans notre placement de DialoguePayloadTokenFilter, puisque n'importe où après QuotationTokenFilter ça ira. Nous le mettons après StopFilter pour éviter de perdre du temps à injecter la charge utile de dialogue dans mots d'arrêt qui sera finalement supprimé.

Voici une visualisation de notre nouveau pipeline en action (moins les parties du pipeline standard que nous avons supprimées ou déjà vues):

Nouvelle visualisation du pipeline dans Apache Lucene

DialogueAnalyzer peut désormais être utilisé comme n'importe quel autre stock Analyzer serait, et maintenant nous pouvons construire l'index et passer à la recherche.

Recherche en texte intégral du dialogue

Si nous voulions uniquement rechercher le dialogue, nous aurions pu simplement supprimer tous les jetons en dehors d'une citation et nous aurions terminé. Au lieu de cela, en laissant tous les jetons d'origine intacts, nous nous sommes donné la possibilité d'effectuer des requêtes qui prennent en compte le dialogue, ou de traiter le dialogue comme n'importe quelle autre partie du texte.

Les bases de l'interrogation d'un index Lucene sont bien documenté . Pour nos besoins, il suffit de savoir que les requêtes sont composées de Term objets collés avec des opérateurs tels que MUST ou SHOULD, ainsi que des documents de correspondance basés sur ces termes. Les documents correspondants sont ensuite notés sur la base d'un Similarity configurable | objet, et ces résultats peuvent être classés par score, filtrés ou limités. Par exemple, Lucene nous permet de faire une requête pour les dix premiers documents qui doivent contenir les deux termes [hello] et [world].

La personnalisation des résultats de recherche en fonction du dialogue peut être effectuée en ajustant le score d’un document en fonction de la charge utile. Le premier point d'extension pour cela sera dans Similarity, qui est responsable de la pondération et de la notation des termes correspondants.

Similitude et notation

Les requêtes utilisent par défaut DefaultSimilarity, qui pondère les termes en fonction de leur fréquence d'apparition dans un document. C'est un bon point d'extension pour l'ajustement des poids, donc nous l'étendons pour noter également les documents en fonction de la charge utile. La méthode DefaultSimilarity#scorePayload est prévu à cet effet:

public final class DialogueAwareSimilarity extends DefaultSimilarity { @Override public float scorePayload(int doc, int start, int end, BytesRef payload) { if (payload.bytes[payload.offset] == 0) { return 0.0f; } return 1.0f; } }

DialogueAwareSimilarity marque simplement les charges utiles sans dialogue comme zéro. Comme chaque Term peut être apparié plusieurs fois, il aura potentiellement plusieurs scores de charge utile. L'interprétation de ces scores jusqu'au Query la mise en oeuvre.

Faites très attention au BytesRef contenant la charge utile: nous devons vérifier l'octet à offset, car nous ne pouvons pas supposer que le tableau d'octets est la même charge utile que nous avons stockée précédemment. Lors de la lecture de l'index, Lucene ne gaspillera pas de mémoire en allouant un tableau d'octets séparé uniquement pour l'appel à scorePayload, nous obtenons donc une référence dans un tableau d'octets existant. Lors du codage avec l'API Lucene, il est utile de garder à l'esprit que la performance est la priorité, bien avant la commodité du développeur.

Maintenant que nous avons notre nouveau Similarity implémentation, il doit alors être défini sur IndexSearcher utilisé pour exécuter des requêtes:

IndexSearcher searcher = new IndexSearcher(... reader for index ...); searcher.setSimilarity(new DialogueAwareSimilarity());

Requêtes et conditions

Maintenant que notre IndexSearcher peut marquer les charges utiles, nous devons également construire une requête qui tient compte de la charge utile. PayloadTermQuery peut être utilisé pour faire correspondre un seul Term tout en vérifiant également les charges utiles de ces correspondances:

PayloadTermQuery helloQuery = new PayloadTermQuery(new Term('body', 'hello'), new AveragePayloadFunction());

Cette requête correspond au terme [hello] dans le corps field (rappelez-vous que c'est ici que nous mettons le contenu du document). Nous devons également fournir une fonction pour calculer le score final de la charge utile à partir de toutes les correspondances de termes, nous connectons donc AveragePayloadFunction, qui fait la moyenne de tous les scores de charge utile. Par exemple, si le terme [hello] se produit à l'intérieur du dialogue deux fois et à l'extérieur du dialogue une fois, le score final de la charge utile sera de ²⁄₃. Ce score final de charge utile est multiplié par celui fourni par DefaultSimilarity pour l'ensemble du document.

Nous utilisons une moyenne parce que nous souhaitons désaccentuer les résultats de recherche où de nombreux termes apparaissent en dehors du dialogue, et produire un score de zéro pour les documents sans aucun terme dans le dialogue.

On peut aussi composer plusieurs PayloadTermQuery objets utilisant un BooleanQuery si nous voulons rechercher plusieurs termes contenus dans le dialogue (notez que l'ordre des termes n'est pas pertinent dans cette requête, bien que les autres types de requêtes soient sensibles à la position):

PayloadTermQuery worldQuery = new PayloadTermQuery(new Term('body', 'world'), new AveragePayloadFunction()); BooleanQuery query = new BooleanQuery(); query.add(helloQuery, Occur.MUST); query.add(worldQuery, Occur.MUST);

Lorsque cette requête est exécutée, nous pouvons voir comment la structure de la requête et l'implémentation de la similarité fonctionnent ensemble:

pipeline d

Exécution de la requête et explication

Pour exécuter la requête, nous la transmettons au IndexSearcher:

TopScoreDocCollector collector = TopScoreDocCollector.create(10); searcher.search(query, new PositiveScoresOnlyCollector(collector)); TopDocs topDocs = collector.topDocs();

Collector les objets sont utilisés pour préparer la collection de documents correspondants.

les collecteurs peuvent être composés pour réaliser une combinaison de tri, de limitation et de filtrage. Pour obtenir, par exemple, les dix meilleurs documents qui contiennent au moins un terme dans le dialogue, nous combinons TopScoreDocCollector et PositiveScoresOnlyCollector. Prendre uniquement des scores positifs garantit que les correspondances de score nul (c'est-à-dire celles qui n'ont pas de termes dans le dialogue) sont filtrées.

Pour voir cette requête en action, nous pouvons l'exécuter, puis utiliser IndexSearcher#explain pour voir comment les documents individuels ont été notés:

for (ScoreDoc result : topDocs.scoreDocs) { Document doc = searcher.doc(result.doc, Collections.singleton('title')); System.out.println('--- document ' + doc.getField('title').stringValue() + ' ---'); System.out.println(this.searcher.explain(query, result.doc)); }

Ici, nous parcourons les ID de document dans le TopDocs obtenu par la recherche. Nous utilisons également IndexSearcher#doc pour récupérer le champ de titre à afficher. Pour notre requête de 'hello', cela se traduit par:

--- Document whelv10.txt --- 0.072256625 = (MATCH) btq, product of: 0.072256625 = weight(body:hello in 7336) [DialogueAwareSimilarity], result of: 0.072256625 = fieldWeight in 7336, product of: 2.345208 = tf(freq=5.5), with freq of: 5.5 = phraseFreq=5.5 3.1549776 = idf(docFreq=2873, maxDocs=24796) 0.009765625 = fieldNorm(doc=7336) 1.0 = AveragePayloadFunction.docScore() --- Document daved10.txt --- 0.061311778 = (MATCH) btq, product of: 0.061311778 = weight(body:hello in 6873) [DialogueAwareSimilarity], result of: 0.061311778 = fieldWeight in 6873, product of: 3.3166249 = tf(freq=11.0), with freq of: 11.0 = phraseFreq=11.0 3.1549776 = idf(docFreq=2873, maxDocs=24796) 0.005859375 = fieldNorm(doc=6873) 1.0 = AveragePayloadFunction.docScore() ...

Bien que la sortie soit chargée de jargon, nous pouvons voir comment notre personnalisé Similarity l'implémentation a été utilisée dans le scoring, et comment MaxPayloadFunction produit un multiplicateur de 1.0 pour ces matchs. Cela implique que la charge utile a été chargée et notée, et toutes les correspondances de 'Hello' eu lieu dans le dialogue, et donc ces résultats sont tout en haut là où nous les attendons.

Il convient également de souligner que l'indice du projet Gutenberg, avec les charges utiles, atteint près de quatre gigaoctets, et pourtant sur ma modeste machine de développement, les requêtes se produisent instantanément. Nous n'avons sacrifié aucune vitesse pour atteindre nos objectifs de recherche.

Emballer

Lucene est une puissante bibliothèque de recherche de texte intégral conçue à cet effet qui prend un flux brut de caractères, les regroupe en jetons et les conserve en tant que termes dans un index. Il peut rapidement interroger cet index et fournir des résultats classés, et offre de nombreuses possibilités d'extension tout en maintenant l'efficacité.

En utilisant Lucene directement dans nos applications, ou dans le cadre d'un serveur, nous pouvons effectuer des recherches en texte intégral en temps réel sur des gigaoctets de contenu. De plus, grâce à une analyse et une notation personnalisées, nous pouvons tirer parti des fonctionnalités spécifiques au domaine dans nos documents pour améliorer la pertinence des résultats ou des requêtes personnalisées.

comment dessiner dans le traitement

Les listes de codes complètes pour ce didacticiel Lucene sont disponible sur GitHub . Le dépôt contient deux applications: LuceneIndexerApp pour construire l'index, et LuceneQueryApp pour effectuer des requêtes.

Le corpus du projet Gutenberg, qui peut être obtenu en tant qu'image disque via BitTorrent , contient beaucoup de livres qui valent la peine d'être lus (soit avec Lucene, soit juste à l'ancienne).

Bonne indexation!

Productivité sur la route: travaillez à temps plein, voyagez en solo, amusez-vous

Mode De Vie

Productivité sur la route: travaillez à temps plein, voyagez en solo, amusez-vous
Tutoriel OpenGL pour Android: Construire un générateur d'ensemble Mandelbrot

Tutoriel OpenGL pour Android: Construire un générateur d'ensemble Mandelbrot

Mobile

Articles Populaires
Création d'une API REST Node.js / TypeScript, partie 2: modèles, middleware et services
Création d'une API REST Node.js / TypeScript, partie 2: modèles, middleware et services
Un didacticiel sur la radio définie par logiciel: images de la Station spatiale internationale et écoute de jambons avec un RTL-SDR
Un didacticiel sur la radio définie par logiciel: images de la Station spatiale internationale et écoute de jambons avec un RTL-SDR
Les drones commerciaux révolutionnent les opérations commerciales
Les drones commerciaux révolutionnent les opérations commerciales
Faire des affaires dans l'Union européenne
Faire des affaires dans l'Union européenne
AI vs BI: différences et synergies
AI vs BI: différences et synergies
 
Stratège de contenu produit
Stratège de contenu produit
Risque vs récompense: un guide pour comprendre les conteneurs logiciels
Risque vs récompense: un guide pour comprendre les conteneurs logiciels
Explorer SMACSS: architecture évolutive et modulaire pour CSS
Explorer SMACSS: architecture évolutive et modulaire pour CSS
Si vous n'utilisez pas de données UX, ce n'est pas de la conception UX
Si vous n'utilisez pas de données UX, ce n'est pas de la conception UX
Simplification de l'utilisation des API RESTful et de la persistance des données sur iOS avec Mantle et Realm
Simplification de l'utilisation des API RESTful et de la persistance des données sur iOS avec Mantle et Realm
Articles Populaires
  • c****** compilation
  • comment utiliser node.js avec html
  • différence entre c ou s corporation
  • qu'est-ce qu'un fichier d'amorçage
  • taux de facturation vs calculateur de salaire
  • comment utiliser les commandes du bot discord
Catégories
  • Rise Of Remote
  • Outils Et Tutoriels
  • Équipes Distribuées
  • Mode De Vie
  • © 2022 | Tous Les Droits Sont Réservés

    portaldacalheta.pt