web-dev-qa-db-fra.com

ElasticSearch - Recherche de noms humains

J'ai une grande base de données de noms, principalement d'Ecosse. Nous produisons actuellement un prototype pour remplacer un logiciel existant qui effectue la recherche. Ceci est toujours en production et nous visons à obtenir nos résultats aussi proches que possible des résultats actuels de la même recherche.

J'espérais que quelqu'un pourrait m'aider, j'entre dans une recherche dans Elastic Search, la requête est "Michael Heaney", j'obtiens des résultats extravagants. La recherche actuelle renvoie deux noms de famille principaux, ce sont - "Heaney" et "Heavey" tous avec le prénom de "Michael", je peux obtenir les résultats "Heaney" dans la recherche élastique mais je ne peux pas obtenir "Heavey" et ES aussi renvoie des personnes sans le nom de famille "Michael" mais j'apprécie que cela soit dû au fait qu'il fait partie de la requête floue. Je sais que c'est un cas d'utilisation étroit, car ce n'est qu'une seule recherche, mais obtenir ce résultat et savoir comment l'obtenir m'aidera.

Merci.

Cartographie

{
   "jr": {
    "_all": {
        "enabled": true,
        "index_analyzer": "index_analyzer",
        "search_analyzer": "search_analyzer"
    },
    "properties": {
        "pty_forename": {
            "type": "string",
            "index": "analyzed",
            "boost": 2,
            "index_analyzer": "index_analyzer",
            "search_analyzer": "search_analyzer",
            "store": "yes"
        },
        "pty_full_name": {
            "type": "string",
            "index": "analyzed",
            "boost": 4,
            "index_analyzer": "index_analyzer",
            "search_analyzer": "search_analyzer",
            "store": "yes"
        },
        "pty_surname": {
            "type": "string",
            "index": "analyzed",
            "boost": 4,
            "index_analyzer": "index_analyzer",
            "search_analyzer": "search_analyzer",
            "store": "yes"
        }
     }
   }
}'

Paramètres d'index

{
  "settings": {
    "number_of_shards": 2,
    "number_of_replicas": 0,
    "analysis": {
        "analyzer": {
            "index_analyzer": {
                "tokenizer": "standard",
                "filter": [
                    "standard",
                    "my_delimiter",
                    "lowercase",
                    "stop",
                    "asciifolding",
                    "porter_stem",
                    "my_metaphone"
                ]
            },
            "search_analyzer": {
                "tokenizer": "standard",
                "filter": [
                    "standard",
                    "my_metaphone",
                    "synonym",
                    "lowercase",
                    "stop",
                    "asciifolding",
                    "porter_stem"
                ]
            }
        },
        "filter": {
            "synonym": {
                "type": "synonym",
                "synonyms_path": "synonyms/synonyms.txt"
            },
            "my_delimiter": {
                "type": "Word_delimiter",
                "generate_Word_parts": true,
                "catenate_words": false,
                "catenate_numbers": false,
                "catenate_all": false,
                "split_on_case_change": false,
                "preserve_original": false,
                "split_on_numerics": false,
                "stem_english_possessive": false
            },
            "my_metaphone": {
                "type": "phonetic",
                "encoder": "metaphone",
                "replace": false
            }
        }
     }
   }
}'

flo

{
"from":0, "size":100,
"query": {
    "bool": {
        "should": [
            {
                "fuzzy": {
                    "pty_surname": {
                        "min_similarity": 0.2,
                        "value": "Heaney",
                        "prefix_length": 0,
                        "boost": 5
                    }
                }
            },
            {
                "fuzzy": {
                    "pty_forename": {
                        "min_similarity": 1,
                        "value": "Michael",
                        "prefix_length": 0,
                        "boost": 1
                    }
                }
            }
        ]
     }
  }
}

Tout d'abord, j'ai recréé votre configuration actuelle dans Play: https://www.found.no/play/Gist/867785a709b4869c554

Si vous y allez, passez à l'onglet "Analyse" pour voir comment le texte est transformé:

Notez, par exemple, que Heaney finit par être symbolisé par [hn, heanei] avec le search_analyzer et comme [HN, heanei] avec le index_analyzer. Notez la différence de casse pour le terme métaphone. Ainsi, celui-ci ne correspond pas.

La requête fuzzy- ne fait pas d'analyse de texte de temps de requête. Ainsi, vous finissez par comparer Heavey avec heanei. Cela a une distance Damerau-Levenshtein plus longue que ce que vos paramètres permettent.

Ce que vous voulez vraiment faire, c'est utiliser la fonctionnalité floue de match. Match effectue une analyse de texte de temps de requête, et a un paramètre flou - .

Quant au fuzziness, cela a un peu changé dans Lucene 4. Avant, il était généralement spécifié comme un flottant. Maintenant, il doit être spécifié comme la distance autorisée. Il y a une demande d'extraction en suspens pour clarifier que: https://github.com/elasticsearch/elasticsearch/pull/4332/files

La raison pour laquelle vous obtenez des personnes sans le prénom Michael est que vous faites un bool.should. Cela a une sémantique OR. Il suffit que l'on corresponde, mais en termes de score, mieux c'est, plus il correspond.

Enfin, combiner tout ce filtrage dans le même terme n'est pas nécessairement la meilleure approche. Par exemple, vous ne pouvez pas connaître et améliorer l'orthographe exacte. Ce que vous devriez considérer est d'utiliser un multi_field pour traiter le champ de plusieurs façons.

Voici un exemple avec lequel vous pouvez jouer , avec les commandes curl pour le recréer ci-dessous. Je sauterais en utilisant le stemmer "porter" entièrement pour cela, cependant. Je l'ai gardé juste pour montrer comment fonctionne multi_field. En utilisant une combinaison de correspondance, la correspondance avec le flou et la correspondance phonétique devraient vous mener loin. (Assurez-vous de ne pas autoriser le flou lorsque vous effectuez une correspondance phonétique - ou vous obtiendrez une correspondance inutilement floue. :-)

#!/bin/bash

export ELASTICSEARCH_ENDPOINT="http://localhost:9200"

# Create indexes

curl -XPUT "$ELASTICSEARCH_ENDPOINT/play" -d '{
    "settings": {
        "analysis": {
            "text": [
                "Michael",
                "Heaney",
                "Heavey"
            ],
            "analyzer": {
                "metaphone": {
                    "type": "custom",
                    "tokenizer": "standard",
                    "filter": [
                        "my_metaphone"
                    ]
                },
                "porter": {
                    "type": "custom",
                    "tokenizer": "standard",
                    "filter": [
                        "lowercase",
                        "porter_stem"
                    ]
                }
            },
            "filter": {
                "my_metaphone": {
                    "encoder": "metaphone",
                    "replace": false,
                    "type": "phonetic"
                }
            }
        }
    },
    "mappings": {
        "jr": {
            "properties": {
                "pty_surename": {
                    "type": "multi_field",
                    "fields": {
                        "pty_surename": {
                            "type": "string",
                            "analyzer": "simple"
                        },
                        "metaphone": {
                            "type": "string",
                            "analyzer": "metaphone"
                        },
                        "porter": {
                            "type": "string",
                            "analyzer": "porter"
                        }
                    }
                }
            }
        }
    }
}'


# Index documents
curl -XPOST "$ELASTICSEARCH_ENDPOINT/_bulk?refresh=true" -d '
{"index":{"_index":"play","_type":"jr"}}
{"pty_surname":"Heaney"}
{"index":{"_index":"play","_type":"jr"}}
{"pty_surname":"Heavey"}
'

# Do searches

curl -XPOST "$ELASTICSEARCH_ENDPOINT/_search?pretty" -d '
{
    "query": {
        "bool": {
            "should": [
                {
                    "bool": {
                        "should": [
                            {
                                "match": {
                                    "pty_surname": {
                                        "query": "heavey"
                                    }
                                }
                            },
                            {
                                "match": {
                                    "pty_surname": {
                                        "query": "heavey",
                                        "fuzziness": 1
                                    }
                                }
                            },
                            {
                                "match": {
                                    "pty_surename.metaphone": {
                                        "query": "heavey"
                                    }
                                }
                            },
                            {
                                "match": {
                                    "pty_surename.porter": {
                                        "query": "heavey"
                                    }
                                }
                            }
                        ]
                    }
                }
            ]
        }
    }
}
'
34
Alex Brasetvik