Поиск с гнездом не дает ожидаемого результата - PullRequest
0 голосов
/ 16 октября 2018

Я создаю индекс со следующим кодом:

        var ElasticSettings = new ConnectionSettings(new Uri(ConnectionString))
            .DefaultIndex(_IndexName)
        .DefaultMappingFor<PictureObject>(M => M
            .Ignore(_ => _._id)
            .Ignore(_ => _.Log))
            .DefaultFieldNameInferrer(_ => _);

    _ElasticClient = new ElasticClient(ElasticSettings);

    if (!_ElasticClient.IndexExists(_IndexName).Exists)
    {
        var I = _ElasticClient.CreateIndex(_IndexName, Ci => Ci
            .Settings(S => S
                .Analysis(A => A
                    .CharFilters(Cf => Cf.Mapping("expressions",
                        E => E.Mappings(ExpressionsList))
                    )
                    .TokenFilters(Tf => Tf.Synonym("synonyms",
                        Descriptor => new SynonymTokenFilter
                        {
                            Synonyms = SynonymsList,
                            Tokenizer = "whitespace"
                        })
                    )
                    .Analyzers(Analyzer => Analyzer
                        .Custom("index", C => C
                            .CharFilters("expressions")
                            .Tokenizer("standard")
                            .Filters("synonyms", "standard", "lowercase", "stop")
                        )
                        .Custom("search", C => C
                            .CharFilters("expressions")
                            .Tokenizer("standard")
                            .Filters("synonyms", "standard", "lowercase", "stop")
                        )
                    )
                )
            )
            .Mappings(Mapping => Mapping
                .Map<PictureObject>(Map => Map
                    .AutoMap()
                    .Properties(P => P
                        .Text(T => T
                            .Name(N => N.Title)
                            .Analyzer("index")
                            .SearchAnalyzer("search")
                        )
                        .Text(T => T
                            .Name(N => N.Tags)
                            .Analyzer("index")
                            .SearchAnalyzer("search")
                        )
                    )
                )
            )
        );

Поля, которые я хочу найти, это «заголовок» и «теги»

Мои синонимы в этом формате:

["большой => большой, огромный", "маленький => крошечный, крошечный",]

и мои выражения выглядят так:

["штормовая погода => шторм", "счастливый день => радость",]

Я делаю тесты с этими двумя методами:

var Test1 = _ElasticClient.Search<PictureObject>(S => S
        .From(From)
        .Size(Take)
        .Query(_ => _.Fuzzy(Fuzz => Fuzz.Field(F => F.Tags).Field(T => T.Title).Value(Terms).MaxExpansions(2)))).Documents;

var resTest2 = _ElasticClient.Search<PictureObject>(S => S
        .Query(_ => _.QueryString(F => F.Query(Terms)))
        .From(From)
        .Size(Take));

При попыткеточно совпадают термины в поле тегов, две функции возвращают разные результаты.При попытке использовать синонимы результаты снова меняются.

(В конечном счете, я тоже хочу обрабатывать орфографические ошибки, но сейчас я просто тестирую с помощью дословных строк)

Чего мне не хватает?(У меня все еще есть хитрое понимание API, поэтому ошибки могут быть очень очевидными)

Редактировать: Вот полный рабочий пример, который можно скомпилировать.

namespace Test
{
    using System;
    using System.Collections.Generic;
    using Nest;

    public class MyData
    {
        public string Id;
        public string Title;
        public string Tags;
    }

    public static class Program
    {
        public static void Main()
        {
            const string INDEX_NAME = "testindex";

            var ExpressionsList = new[]
            {
                "bad weather => storm",
                "happy day => sun"
            };

            var SynonymsList = new[]
            {
                "big => large, huge",
                "small => tiny, minuscule",
                "sun => sunshine, shiny, sunny"
            };

            // connect
            var ElasticSettings = new ConnectionSettings(new Uri("http://elasticsearch:9200"))
                .DefaultIndex(INDEX_NAME)
                .DefaultFieldNameInferrer(_ => _) // stop the camel case
                .DefaultMappingFor<MyData>(M => M.IdProperty("Id"));

            var Client = new ElasticClient(ElasticSettings);

            // erase the old index, if any
            if (Client.IndexExists(INDEX_NAME).Exists) Client.DeleteIndex(INDEX_NAME);

            // create the index
            var I = Client.CreateIndex(INDEX_NAME, Ci => Ci
                .Settings(S => S
                    .Analysis(A => A
                        .CharFilters(Cf => Cf.Mapping("expressions",
                            E => E.Mappings(ExpressionsList))
                        )
                        .TokenFilters(Tf => Tf.Synonym("synonyms",
                            Descriptor => new SynonymTokenFilter
                            {
                                Synonyms = SynonymsList,
                                Tokenizer = "whitespace"
                            })
                        )
                        .Analyzers(Analyzer => Analyzer
                            .Custom("index", C => C
                                .CharFilters("expressions")
                                .Tokenizer("standard")
                                .Filters("synonyms", "standard", "lowercase", "stop")
                            )
                            .Custom("search", C => C
                                .CharFilters("expressions")
                                .Tokenizer("standard")
                                .Filters("synonyms", "standard", "lowercase", "stop")
                            )
                        )
                    )
                )
                .Mappings(Mapping => Mapping
                    .Map<MyData>(Map => Map
                        .AutoMap()
                        .Properties(P => P
                            .Text(T => T
                                .Name(N => N.Title)
                                .Analyzer("index")
                                .SearchAnalyzer("search")
                            )
                            .Text(T => T
                                .Name(N => N.Tags)
                                .Analyzer("index")
                                .SearchAnalyzer("search")
                            )
                        )
                    )
                )
            );

            // add some data
            var Data = new List<MyData>
            {
                new MyData { Id = "1", Title = "nice stormy weather", Tags = "storm nice" },
                new MyData { Id = "2", Title = "a large storm with sunshine", Tags = "storm large sunshine" },
                new MyData { Id = "3", Title = "a storm during a sunny day", Tags = "sun storm" }
            };

            Client.IndexMany(Data);
            Client.Refresh(INDEX_NAME);


            // do some queries
            var TestA1 = Client.Search<MyData>(S => S.Query(_ => _.Fuzzy(Fuzz => Fuzz.Field(F => F.Tags).Field(T => T.Title).Value("stormy sunny").MaxExpansions(2)))).Documents;
            var TestA2 = Client.Search<MyData>(S => S.Query(_ => _.Fuzzy(Fuzz => Fuzz.Field(F => F.Tags).Field(T => T.Title).Value("stromy sunny").MaxExpansions(2)))).Documents;

            var TestB1 = Client.Search<MyData>(S => S.Query(_ => _.QueryString(F => F.Query("stormy sunny")))).Documents;
            // expected to return documents 1, 2, 3 because of synonyms: sun => sunny, shiny, sunshine

            var TestB2 = Client.Search<MyData>(S => S.Query(_ => _.QueryString(F => F.Query("bad weather")))).Documents;
            var TestB3 = Client.Search<MyData>(S => S.Query(_ => _.QueryString(F => F.Query("a large happy day")))).Documents;

            /*
             * I'm expecting the fuzzy queries to handle misspellings
             * Also, I'm expecting the expressions and synonyms to do the substitutions as they're written
             *
             * Ideally I'd like to handle:
             *  - expressions
             *  - synonyms
             *  - misspellings
             *
             * all together
             *
             * I have tried a lot of string examples while debugging and it's really hit or miss.
             * Unfortunately, I haven't kept the strings, but it was enough to see that there is something
             * wrong with my approach in this code.
             */
        }
    }
}

1 Ответ

0 голосов
/ 18 октября 2018

Вот несколько указателей, которые помогут вам выбрать правильный путь

Фильтры символов

var ExpressionsList = new[]
{
    "bad weather => storm",
    "happy day => sun"
};

Подумайте, должны ли они быть фильтрами символов;они могут быть, но, как правило, символьные фильтры используются в местах, где токенизатор может некорректно токенизироваться, например,

  • удаление тегов HTML перед токенизацией
  • стандартный токенизатор, удаляющий &, когда мы в идеале хотелисохранить и заменить на and в символьном фильтре
  • Стандартный токенизатор, токенизирующий c# как c, когда в идеале мы хотели сохранить и заменить на csharp в символьном фильтре

Возможно, вы хотите использовать символьный фильтр, но лучше использовать синонимы или график синонимов в случае нескольких слов.

Пользовательские анализаторы

The *Пользовательские анализаторы 1027 * и search одинаковы, их можно удалить.Точно так же, если явно не установлено, search_analyzer для поля типа данных text будет настроено analyzer, так что это немного упрощает.

Синонимы

var SynonymsList = new[]
{
    "big => large, huge",
    "small => tiny, minuscule",
    "sun => sunshine, shiny, sunny"
};

Это направленная синонимная карта , то есть совпадения с левой стороны будут заменены всеми альтернативами с правой стороны.Если все должны рассматриваться как равные синонимы друг для друга, вам, скорее всего, не нужна карта направления, т.е.

var SynonymsList = new[]
{
    "big, large, huge",
    "small, tiny, minuscule",
    "sun, sunshine, shiny, sunny"
};

. Это вернет все 3 документа для

var TestB1 = Client.Search<MyData>(S => S.Query(_ => _.QueryString(F => F.Query("stormy sunny")))).Documents;
// expected to return documents 1, 2, 3 because of synonyms: sun => sunny, shiny, sunshine

Фильтры токенов

.Custom("index", C => C
    .CharFilters("expressions")
    .Tokenizer("standard")
    .Filters("synonyms", "standard", "lowercase", "stop")
)
.Custom("search", C => C
    .CharFilters("expressions")
    .Tokenizer("standard")
    .Filters("synonyms", "standard", "lowercase", "stop")
)

Порядок фильтров токенов имеет значение, поэтому вы хотите запустить фильтр синонимов после фильтр нижнего регистра

Нечеткие запросы

Нечеткие запросы являются запросами уровня термина, поэтому входные данные запроса не подвергаются анализу, то есть, если вы запускаете его для поля, которое анализируется во время индекса, входные данные для нечеткого запросапотребуется сопоставить условия вывода для документа из анализа во время индекса.Скорее всего, это не даст правильных результатов, если входные данные запроса будут разбиты на несколько терминов во время индексации, то есть ввод нечеткого запроса будет рассматриваться как один полный термин, но значение времени индекса для поля целевого документа может иметьразделен на несколько терминов.

Взгляните на раздел Fuzziness из «Полного руководства» - он предназначен для Elasticsearch 2.x, но в значительной степени все еще актуален для более поздних версий.Вы, вероятно, захотите использовать полнотекстовый запрос, который поддерживает нечеткость и выполняет анализ во время запроса, например query_string, match или multi_match запросов.

Пример

Размещение этихвместе, вот пример для работы при разработке

public class MyData
{
    public string Id;
    public string Title;
    public string Tags;
}

public static void Main()
{
    const string INDEX_NAME = "testindex";

    var expressions = new[]
    {
            "bad weather => storm",
            "happy day => sun"
    };

    var synonyms = new[]
    {
            "big, large, huge",
            "small, tiny, minuscule",
            "sun, sunshine, shiny, sunny"
    };

    // connect
    var settings = new ConnectionSettings(new Uri("http://localhost:9200"))
        .DefaultIndex(INDEX_NAME)
        .DefaultFieldNameInferrer(s => s) // stop the camel case
        .DefaultMappingFor<MyData>(m => m.IdProperty("Id"))
        .DisableDirectStreaming()
        .PrettyJson()
        .OnRequestCompleted(callDetails =>
        {
            if (callDetails.RequestBodyInBytes != null)
            {
                Console.WriteLine(
                    $"{callDetails.HttpMethod} {callDetails.Uri} \n" +
                    $"{Encoding.UTF8.GetString(callDetails.RequestBodyInBytes)}");
            }
            else
            {
                Console.WriteLine($"{callDetails.HttpMethod} {callDetails.Uri}");
            }

            Console.WriteLine();

            if (callDetails.ResponseBodyInBytes != null)
            {
                Console.WriteLine($"Status: {callDetails.HttpStatusCode}\n" +
                         $"{Encoding.UTF8.GetString(callDetails.ResponseBodyInBytes)}\n" +
                         $"{new string('-', 30)}\n");
            }
            else
            {
                Console.WriteLine($"Status: {callDetails.HttpStatusCode}\n" +
                         $"{new string('-', 30)}\n");
            }
        });

    var Client = new ElasticClient(settings);

    // erase the old index, if any
    if (Client.IndexExists(INDEX_NAME).Exists) Client.DeleteIndex(INDEX_NAME);

    // create the index
    var createIndexResponse = Client.CreateIndex(INDEX_NAME, c => c
        .Settings(s => s
            .Analysis(a => a
                .CharFilters(cf => cf
                    .Mapping("expressions", E => E
                        .Mappings(expressions)
                    )
                )
                .TokenFilters(tf => tf
                    .Synonym("synonyms", sy => sy
                        .Synonyms(synonyms)
                        .Tokenizer("whitespace")
                    )
                )
                .Analyzers(an => an
                    .Custom("index", ca => ca
                        .CharFilters("expressions")
                        .Tokenizer("standard")
                        .Filters("standard", "lowercase", "synonyms",  "stop")
                    )
                )
            )
        )
        .Mappings(m => m
            .Map<MyData>(mm => mm
                .AutoMap()
                .Properties(p => p
                    .Text(t => t
                        .Name(n => n.Title)
                        .Analyzer("index")
                    )
                    .Text(t => t
                        .Name(n => n.Tags)
                        .Analyzer("index")
                    )
                )
            )
        )
    );

    // add some data
    var data = new List<MyData>
        {
            new MyData { Id = "1", Title = "nice stormy weather", Tags = "storm nice" },
            new MyData { Id = "2", Title = "a large storm with sunshine", Tags = "storm large sunshine" },
            new MyData { Id = "3", Title = "a storm during a sunny day", Tags = "sun storm" }
        };

    Client.IndexMany(data);
    Client.Refresh(INDEX_NAME);

    //var query = "stormy sunny";
    var query = "stromy sunny";
    // var query = "bad weather";
    // var query = "a large happy day";

    var testA1 = Client.Search<MyData>(s => s
        .Query(q => q
            .MultiMatch(fu => fu
                .Fields(f => f
                    .Field(ff => ff.Tags)
                    .Field(ff => ff.Title)
                )           
                .Query(query)
                .Fuzziness(Fuzziness.EditDistance(2))
            )
        )
    ).Documents;
}

Я добавил .DisableDirectStreaming(), .PrettyJson() и обработчик .OnRequestCompleted(...) в настройки соединения, чтобы вы могли видеть запросы и ответы, записанные вконсоль.Они полезны при разработке, но вы, вероятно, захотите удалить их для производства, поскольку они увеличивают накладные расходы.Небольшое приложение, такое как Linqpad, поможет при разработке здесь:)

В этом примере используется запрос multi_match с нечеткостью, включенной с расстоянием редактирования 2 (здесь может потребоваться просто использовать автоматическую нечеткость, она делает разумную работу), работает в полях Tags и Title.Все три документа возвращаются по запросу (опечатка) "stromy sunny"

...