Recherche et priorisation des résultat


#1

Bonjour,
j'ai des données dans elasticsearch qui ressemblent à ça:

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 4,
    "max_score": 1,
    "hits": [
      {
        "_index": "doc_1",
        "_type": "doc",
        "_id": "1",
        "_score": 1,
        "_source": {
          "id": "1",
          "label": "Développeur",
          "activity": [
            {
              "name": "immobilier",
              "id": "1"
            }
          ]
        }
      },
      {
        "_index": "doc_1",
        "_type": "doc",
        "_id": "2",
        "_score": 1,
        "_source": {
          "id": "2",
          "label": "Développeur",
          "activity": [
            {
              "name": "informatique industriel",
              "id": "2"
            }
          ]
        }
      },
      {
        "_index": "doc_1",
        "_type": "doc",
        "_id": "3",
        "_score": 1,
        "_source": {
          "id": "3",
          "label": "Développeur",
          "activity": [
            {
              "name": "photo de paysage",
              "id": "3"
            }
          ]
        }
      },
      {
        "_index": "doc_1",
        "_type": "doc",
        "_id": "4",
        "_score": 1,
        "_source": {
          "id": "4",
          "label": "Développeur",
          "activity": []
        }
      }
    ]
  }
}

ce que je cherche à faire c'est de prioriser les résultats, quand on cherche

  • "Développeur" l'ordre n'est pas important
  • "Développeur informatique" c'est le document 2 qui doit sortir en premier

là la priorisation est sur 2 champs mais on réalité j'ai plusieurs champs sur lesquels je dois faire la priorisation.

la requête que je fais:

GET job/_search
{
    "query": {
        "multi_match": {
            "query":       "Développur",
            "type":        "cross_fields",
            "fields":      [ "label^3", "activity.name^1" ] 
        }
    }
}

Parmi les champs sur lesquels je dois faire la priorisation, c'est un champs contenant des coordonnées et je dois prioriser, pas filtrer, par les plus proches à une coordonnée.

quelqu'un à une idée pour pouvoir une priorisation qui prend en compte des champs de type différents: text, date, boolean, coordonnées, ...

Merci


(David Pilato) #2

En combinant des clauses should tu devrais pouvoir faire quelque chose à mon avis.
Voici un script complet (trop complet? :slight_smile: ) qui décrit cela:

En espérant que ça t'aide.


#3

Merci pour la réponse.
je vais faire des tests revienir vers vous


#4

Bonjour,

en se basant sur le document gist, j'ai fabriqué une requête qui donnes des résultats pas mal. merci pour l'aide.
Par contre maintenant j'ai champ de plus qui contient coordonées (un point) et je veux bien le prendre en compte la priorisation, vous avez une idée ?
Merci


(David Pilato) #5

Exemple ?


#6

je vais reprendre l'exemple d'avant en y ajoutant une coordonnée:

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 4,
    "max_score": 1,
    "hits": [
      {
        "_index": "doc_1",
        "_type": "doc",
        "_id": "1",
        "_score": 1,
        "_source": {
          "id": "1",
          "label": "Développeur",
          "coord": {
            "lon": 1.1,
            "lat": 1.1
          },
          "activity": [
            {
              "name": "immobilier",
              "id": "1"
            }
          ]
        }
      },
      {
        "_index": "doc_1",
        "_type": "doc",
        "_id": "2",
        "_score": 1,
        "_source": {
          "id": "2",
          "label": "Développeur",
          "coord": {
            "lon": 1.3,
            "lat": 1.3
          },
          "activity": [
            {
              "name": "informatique industriel",
              "id": "2"
            }
          ]
        }
      },
      {
        "_index": "doc_1",
        "_type": "doc",
        "_id": "3",
        "_score": 1,
        "_source": {
          "id": "3",
          "label": "Développeur",
          "coord": {
            "lon": 1.6,
            "lat": 1.6
          },
          "activity": [
            {
              "name": "photo de paysage",
              "id": "3"
            }
          ]
        }
      },
      {
        "_index": "doc_1",
        "_type": "doc",
        "_id": "4",
        "_score": 1,
        "_source": {
          "id": "4",
          "coord": {
            "lon": 1.5,
            "lat": 1.5
          },
          "label": "Développeur",
          "activity": []
        }
      }
    ]
  }
}

si je cherche "Développeur" en passant la coordonnée "lon=1.3 lat=1.3" c'est le document "id=2" qui doit être en premier et le reste de plus proche de "lon=1.3 lat=1.3" au plus loin.


#7

j'ai essayé de faire un sort global mais ce n'ai pas bon !


(David Pilato) #8

Peux-tu fabriquer un exemple complet qu'on puisse rejouer facilement (copier-coller) dans Kibana ?

Le plus simple possible comme:

DELETE index
PUT index/_doc/1
{
  "foo": "bar"
}
GET index/_search
{
  "query": {
    "match": {
      "foo": "bar"
    }
  }
}

#9

bonjour,
voila l'exemple de donnée que j'ai dans ES:


DELETE test
PUT test/job/1
{
  "label": "Développeur",
  "coord": {
    "lon": 1.1,
    "lat": 1.1
  },
  "activity": [
    {
      "name": "immobilier",
      "id": "1"
    }
  ]
}

PUT test/job/2
{
  "label": "Développeur",
  "coord": {
    "lon": 1.3,
    "lat": 1.3
  },
  "activity": [
    {
      "name": "informatique industriel",
      "id": "2"
    }
  ]
}


PUT test/job/3
{
  "label": "Développeur",
  "coord": {
    "lon": 1.6,
    "lat": 1.6
  },
  "activity": [
    {
      "name": "photo de paysage",
      "id": "3"
    }
  ]
}


PUT test/job/4
{
  "label": "Développeur",
  "activity": []
}


PUT test/job/5
{
  "label": "Développeur informatique",
    "coord": {
    "lon": 1.7,
    "lat": 1.7
  },
  "activity": []
}

et la requête de recherche et la suivante:

GET test/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match_phrase": {
            "label": {
              "query": "developpeur",
              "boost": 8
            }
          }
        },
        {
          "match": {
            "label": {
              "query": "developpeur",
              "operator": "and",
              "boost": 2
            }
          }
        },
        {
          "match": {
            "label": {
              "query": "developpeur",
              "operator": "or",
              "boost": 1.8
            }
          }
        },
        {
          "match": {
            "label": {
              "query": "developpeur",
              "fuzziness": 1,
              "boost": 2
            }
          }
        }
      ]
    }
  }
}

En faisant la requête comme ça j'ai en premier ""Développeur informatique" je m'attendis "Développeur" !
je ne sais pas pourquoi.
et ma dexième question c'est de prioriser par l'activité et les coordonées.
Merci


(David Pilato) #10

Alors le problème est que tu as 5 shards par défaut. Donc la distribution de tes documents a un impact.

Si tu fais:

DELETE test
PUT test
{
  "settings": {
    "number_of_shards": 1
  }
}
PUT test/job/1
{
  "label": "Développeur",
  "coord": {
    "lon": 1.1,
    "lat": 1.1
  },
  "activity": [
    {
      "name": "immobilier",
      "id": "1"
    }
  ]
}

PUT test/job/2
{
  "label": "Développeur",
  "coord": {
    "lon": 1.3,
    "lat": 1.3
  },
  "activity": [
    {
      "name": "informatique industriel",
      "id": "2"
    }
  ]
}


PUT test/job/3
{
  "label": "Développeur",
  "coord": {
    "lon": 1.6,
    "lat": 1.6
  },
  "activity": [
    {
      "name": "photo de paysage",
      "id": "3"
    }
  ]
}


PUT test/job/4
{
  "label": "Développeur",
  "activity": []
}


PUT test/job/5
{
  "label": "Développeur informatique",
    "coord": {
    "lon": 1.7,
    "lat": 1.7
  },
  "activity": []
}

GET test/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match_phrase": {
            "label": {
              "query": "developpeur",
              "boost": 8
            }
          }
        },
        {
          "match": {
            "label": {
              "query": "developpeur",
              "operator": "and",
              "boost": 2
            }
          }
        },
        {
          "match": {
            "label": {
              "query": "developpeur",
              "operator": "or",
              "boost": 1.8
            }
          }
        },
        {
          "match": {
            "label": {
              "query": "developpeur",
              "fuzziness": 1,
              "boost": 2
            }
          }
        }
      ]
    }
  }
}

Tu obtiens:

{
  "took": 4,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 5,
    "max_score": 0.16977827,
    "hits": [
      {
        "_index": "test",
        "_type": "job",
        "_id": "1",
        "_score": 0.16977827,
        "_source": {
          "label": "Développeur",
          "coord": {
            "lon": 1.1,
            "lat": 1.1
          },
          "activity": [
            {
              "name": "immobilier",
              "id": "1"
            }
          ]
        }
      },
      {
        "_index": "test",
        "_type": "job",
        "_id": "2",
        "_score": 0.16977827,
        "_source": {
          "label": "Développeur",
          "coord": {
            "lon": 1.3,
            "lat": 1.3
          },
          "activity": [
            {
              "name": "informatique industriel",
              "id": "2"
            }
          ]
        }
      },
      {
        "_index": "test",
        "_type": "job",
        "_id": "3",
        "_score": 0.16977827,
        "_source": {
          "label": "Développeur",
          "coord": {
            "lon": 1.6,
            "lat": 1.6
          },
          "activity": [
            {
              "name": "photo de paysage",
              "id": "3"
            }
          ]
        }
      },
      {
        "_index": "test",
        "_type": "job",
        "_id": "4",
        "_score": 0.16977827,
        "_source": {
          "label": "Développeur",
          "activity": []
        }
      },
      {
        "_index": "test",
        "_type": "job",
        "_id": "5",
        "_score": 0.124301955,
        "_source": {
          "label": "Développeur informatique",
          "coord": {
            "lon": 1.7,
            "lat": 1.7
          },
          "activity": []
        }
      }
    ]
  }
}

(David Pilato) #11

Pour le problème des coordonnées, je te recommande d'utiliser des vrais geo_point et de faire du tri par score et par distance.


#12

Bonjour,
merci pour la réponse,
j'ai ajouté un mapping pour tenir compte de des coordonées et pour "label" et "activity.name" j'utilse Ngram pour le "filter".
pour la recherche, j'utilise une recheche ngram en piorisant les champs.
le mapping:

{
  "settings": {
    "number_of_shards": "1",
    "analysis": {
      "filter": {
        "ngram_filter": {
          "type": "nGram",
          "min_gram": "3",
          "max_gram": "20",
          "token_chars": [
            "letter",
            "digit"
          ]
        }
      },
      "analyzer": {
        "word": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "asciifolding"
          ],
          "char_filter": []
        },
        "ngram": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "asciifolding",
            "ngram_filter"
          ],
          "char_filter": []
        }
      }
    }
  },
  "mappings": {
    "job": {
      "properties": {
        "id": {
          "type": "text",
          "index": false
        },
        "label": {
          "type": "text",
          "analyzer": "word",
          "fields": {
            "ngram": {
              "type": "text",
              "analyzer": "ngram",
              "search_analyzer": "ngram"
            }
          }
        },
        "coord": {
          "type": "geo_point"
        },
        "activity": {
          "properties": {
            "id": {
              "type": "text",
              "analyzer": "keyword"
            },
            "label": {
              "type": "text",
              "analyzer": "keyword",
              "fields": {
                "ngram": {
                  "type": "text",
                  "analyzer": "ngram",
                  "search_analyzer": "ngram"
                }
              }
            }
          }
        }
      }
    }
  }
}

la requête:

GET test/_search
{
  "query": {
    "function_score": {
      "query": {
        "bool": {
          "should": [
            {
              "multi_match": {
                "query": "Développeur",
                "fields": [
                  "label.ngram",
                  "activity.label.ngram",
                  "label",
                  "activity.label"                  
                ]
              }
            }
          ]
        }
      },
      "functions": [
        {
          "filter": {
            "multi_match": {
              "query": "Développeur",
              "fields": ["label.ngram", "label"]
            }
          },
          "weight": 5
        },
        {
          "filter": {
            "multi_match": {
              "query": "Développeur",
              "fields": ["activity.label.ngram", "activity.label"]
            }
          },
          "weight": 3
        },
        {
          "gauss": {
            "coord": {
              "scale": "50km",
              "origin": {
                "lon": 1.6,
                "lat": 1.6
              }
            }
          },
          "weight": 2
        }
      ],
      "score_mode": "sum"
    }
  },
  "size": 20,
  "from": 0
}

le résultat me va bien.
je ne sais pas si on peut faire mieu.
par contre, j'ai un message lors de la création de l'index en me disant que la différence min_gram et max_gram est grosse !! je ne sais si on faire autrement.
merci


(David Pilato) #13

Je me demande si tu ne devrais pas virer la partie

        {
          "filter": {
            "multi_match": {
              "query": "Développeur",
              "fields": ["label.ngram", "label"]
            }
          },
          "weight": 5
        },
        {
          "filter": {
            "multi_match": {
              "query": "Développeur",
              "fields": ["activity.label.ngram", "activity.label"]
            }
          },
          "weight": 3
        },

Et remplacer ça par

          "should": [
            {
              "multi_match": {
                "query": "Développeur",
                "fields": [
                  "label.ngram^3.0",
                  "activity.label.ngram^3.0",
                  "label^5.0",
                  "activity.label^5.0"                  
                ]
              }
            }
          ]

Je n'ai pas testé mais ça pourrait simplifier et rendre plus rapide peut-être la requête.
Pour la rendre encore plus rapide, peut-être ajouter un filtre par distance pour ne pas présenter des jobs au delà de 200 km peut-être ? Ca permettrait éventuellement de réduire le resultset.


(Gabriel Tessier) #14

A propos de ca:

par contre, j'ai un message lors de la création de l'index en me disant que la différence min_gram et max_gram est grosse !! je ne sais si on faire autrement.

D'apres la doc c'est pas tres explicite il disent juste de mettre la meme valeur.
https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-ngram-tokenizer.html

Ici t'as une meilleur explication (vers le milieu sous ce titre "Mingram/Maxgram Size"):

En gros ca depends de ton besoin et il explique bien les conséquences que ca peux engendrer.

Perso j'utilise la meme valeur 3, apres faut tester, si t'as le temps.


#15

Bonjour
merci pour vos réponses.
je vais tester ça et revenir vers vous.


(system) #16

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.