Първи стъпки с текстово търсене

Vespa.ai току-що публикува два урока, за да помогне на хората да започнат с приложения за търсене на текст чрез изграждане на мащабируеми решения с Vespa. Уроците се основават на „пълната задача за класиране на документи“, издадена от екипа на „Набор от данни MS MARCO на Microsoft“.

Първият урок ви помага да създадете и внедрите основно приложение за търсене на текст с Vespa, както и да изтеглите, анализирате и подадете набора от данни към работещ екземпляр на Vespa. Те също така показват колко лесно е да експериментирате с функции за класиране въз основа на вградени функции за класиране, налични във Vespa.

Вторият урок показва как да създадете набор от данни за обучение, съдържащ функции за класиране на Vespa, които ви позволяват да започнете да тренирате ML модели, за да подобрите функцията за класиране на приложението. Той също така илюстрира важността на излизането отвъд точковите функции на загуба, когато се обучават модели в контекст на обучение за класиране.

И двата урока са подробни и идват с наличен код за възпроизвеждане на стъпките. Ето акцентите.

Основно приложение за търсене на текст накратко

Основната задача при създаване на основно приложение с Vespa е да напишете файл с дефиниция на търсене, съдържащ информация за данните, които искате да подадете към приложението и как Vespa трябва да съвпада и да подрежда резултатите, върнати в отговор на заявка.

Освен някои допълнителни подробности, описани в урока, дефиницията за търсене за нашата текстова търсачка изглежда като кодовия фрагмент по-долу. Имаме заглавие и основен текст field, съдържащи информация за наличните документи за търсене. Ключовата дума fieldset показва, че нашата заявка ще съответства на документи чрез търсене на думи за заявка както в полетата за заглавие, така и в основния текст. И накрая, дефинирахме две rank-profile, които контролират как ще бъдат класирани съответстващите документи. Профилът за ранг default използва nativeRank,, което е една от многото вградени функции за ранг, налични във Vespa. Ранг-профилът bm25 използва широко известната функция за ранг BM25.

search msmarco { 
    document msmarco {
        field title type string
        field body type string 
    }
    fieldset default {
        fields: title, body
    }
    rank-profile default {
        first-phase {
            expression: nativeRank(title, body)
        }
    }
    rank-profile bm25 inherits default {
        first-phase {
            expression: bm25(title) + bm25(body)
        }
    }
}

Когато имаме повече от един дефиниран ранг профил, можем да изберем кой да използваме по време на заявка, като включим параметъра ranking в заявката:

curl -s "<URL>/search/?query=what+is+dad+bod"
curl -s "<URL>/search/?query=what+is+dad+bod&ranking=bm25"

Първата заявка по-горе не посочва параметъра ranking и следователно ще използва default ранг-профил. Втората заявка изрично изисква вместо това да се използва bm25 ранг-профил.

Наличието на множество профили за ранг ни позволява да експериментираме с различни функции за класиране. Има един подходящ документ за всяка заявка в набора от данни на MSMARCO. Фигурата по-долу е резултат от скрипт за оценка, който изпрати повече от 5000 заявки към нашето приложение и поиска резултати, използвайки двата профила на ранг, описани по-горе. След това проследихме позицията на съответния документ за всяка заявка и начертахме разпределението за първите 10 позиции.

Ясно е, че ранг-профилът bm25 върши много по-добра работа в този случай. Той поставя съответния документ на първите позиции много по-често от default ранг-профила.

Проверка на надеждността на събирането на данни

След като настроим основно приложение, вероятно искаме да събираме данни за функцията за класиране, за да помогнем за подобряване на нашите функции за класиране. Vespa ни позволява да връщаме функции за ранг заедно с резултати от заявки, което ни позволява да създаваме набори от данни за обучение, които комбинират информация за уместност с информация за ранг в търсачката.

В този случай има различни начини за създаване на набор от данни за обучение. Поради това вярваме, че е добра идея да имаме установена проверка за разумност, преди да започнем да събираме набора от данни. Целта на такава проверка на здравината е да увеличи вероятността да хванем грешки навреме и да създадем набори от данни, съдържащи точната информация, свързана с нашата задача за подобряване на функциите за класиране.

Нашето предложение е да използваме набора от данни, за да обучим модел, използвайки същите функции и функционална форма, използвани от базовата линия, която искате да подобрите. Ако наборът от данни е добре изграден и съдържа полезна информация за задачата, която ви интересува, трябва да можете да получите резултати поне толкова добри, колкото тези, получени от вашата базова линия на отделен набор от тестове.

Тъй като нашата базова линия в този случай е ранговият профил bm25, трябва да напаснем линеен модел, съдържащ само характеристиките на bm25:

a + b * bm25(title) + c * bm25(body)

Наличието на тази проста процедура ни помогна да хванем няколко глупави грешки в нашия код за събиране на данни и ни насочи на правилния път по-бързо, отколкото би се случило иначе. Наличието на грешки във вашите данни е трудно да се хване, когато започнете да експериментирате със сложни модели, тъй като ние никога не знаем дали грешката идва от данните или модела. Така че това е практика, която силно препоръчваме.

Как да създадете набор от данни за обучение с Vespa

Искането на Vespa да върне функции за класиране в набора от резултати е толкова просто, колкото да зададете параметъра ranking.listFeatures на true в заявката. По-долу е тялото на POST заявка, която указва заявката във YQL формат и активира дъмпинга на функциите за ранг.

body = {
    "yql": 'select * from sources * where (userInput(@userQuery));',
    "userQuery": "what is dad bod",
    "ranking": {"profile": "bm25", "listFeatures": "true"},
}

Vespa връща „куп функции за класиране“ по подразбиране, но можем изрично да дефинираме кои функции искаме, като създадем ранг-профил и го помолим да ignore-default-rank-features и изброи функциите, които искаме, като използваме ключовата дума rank-features, както е показано по-долу. Първата random фаза ще се използва при вземане на проби от произволни документи, за да служи като заместител на неподходящи документи.

rank-profile collect_rank_features inherits default {

    first-phase {
        expression: random
    }

    ignore-default-rank-features

    rank-features {
        bm25(title)
        bm25(body)
        nativeRank(title)
        nativeRank(body)
    }

}

Искаме набор от данни, който ще помогне за обучението на модели, които ще обобщават добре, когато работят на екземпляр на Vespa. Това означава, че се интересуваме само от събиране на документи, които съответстват на заявката, защото това са документите, които биха били представени на модела от първа фаза в производствена среда. Ето логиката за събиране на данни:

hits = get_relevant_hit(query, rank_profile, relevant_id)
if relevant_hit:
    hits.extend(get_random_hits(query, rank_profile, n_samples))
    data = annotate_data(hits, query_id, relevant_id)
    append_data(file, data)

За всяка заявка първо изпращаме заявка до Vespa, за да получим съответния документ, свързан със заявката. Ако съответният документ съответства на заявката, Vespa ще го върне и ние ще разширим броя на документите, свързани със заявката, като изпратим втора заявка до Vespa. Втората заявка иска от Vespa да върне определен брой произволни документи, избрани от набора документи, които са били съпоставени от заявката.

След това анализираме попаденията, върнати от Vespa, и организираме данните в таблична форма, съдържаща характеристиките за ранг и двоичната променлива, показваща дали двойката заявка-документ е уместна или не. В края имаме набор от данни със следния формат. Повече подробности можете да намерите в нашият втори урок.

Отвъд точковите функции на загуба

Най-лесният начин за обучение на линейния модел, предложен в нашата проверка за надеждност на събирането на данни, би бил да се използва ванилова логистична регресия, тъй като нашата целева променлива relevant е двоична. Най-често използваната функция на загуба в този случай (двоична кръстосана ентропия) се нарича точкова функция на загуба в LTR литературата, тъй като не взема под внимание относителния ред на документите.

Въпреки това, както описахме в първия ни урок, показателят, който искаме да оптимизираме в този случай, е средният реципрочен ранг (MRR). MRR се влияе от относителния ред на уместността, която приписваме на списъка с документи, генериран от заявка, а не от техните абсолютни величини. Това разминаване между характеристиките на функцията на загубата и показателя, който представлява интерес, може да доведе до неоптимални резултати.

За класиране на резултатите от търсенето е за предпочитане да използвате функция за загуба по списък, когато обучавате нашия модел, която взема под внимание целия класиран списък при актуализиране на параметрите на модела. За да илюстрираме това, ние обучихме линейни модели с помощта на TF-Ranking framework. Рамката е изградена върху TensorFlow и ни позволява да уточним функции за загуба по точки, по двойки и по списък, наред с други неща.

Ние направихме достъпен скрипта, който използвахме за обучение на двата модела, които генерираха резултатите, показани на фигурата по-долу. Скриптът използва прости линейни модели, но може да бъде полезен като отправна точка за изграждане на по-сложни.

Като цяло, средно няма голяма разлика между тези модели (по отношение на MRR), което се очакваше предвид простотата на моделите, описани тук. Въпреки това можем да видим, че модел, базиран на функция за загуба по списък, разпределя повече документи в първите две позиции на класирания списък в сравнение с модела по точки. Очакваме разликата в MRR между точковите и списъчните функции на загуба да се увеличи, когато преминем към по-сложни модели.

Основната цел тук беше просто да се покаже значението на избора на по-добри функции за загуба при работа с LTR задачи и да се даде бърз старт на тези, които искат да опитат в собствените си приложения на Vespa. Сега зависи от вас, разгледайте уроците, изградете нещо и уведомете ни как е минало. Отзивите са добре дошли!