As dicas de pesquisa (sjest) não são apenas um serviço ao usuário, mas também um modelo de linguagem muito poderoso que armazena bilhões de consultas de pesquisa, suporta pesquisa difusa, personalização e muito mais. Aprendemos como usar a sujest para prever a consulta final do usuário e carregar os resultados da pesquisa antes de clicar no botão "Localizar".
A introdução dessa tecnologia - o pré-renderizador - exigiu muitas soluções interessantes no desenvolvimento móvel, no desenvolvimento do tempo de execução da pesquisa, nos logs e nas métricas. E, é claro, precisávamos de um classificador bacana que determinasse se uma consulta de pesquisa deveria ser carregada antecipadamente: esse classificador deve encontrar um equilíbrio entre a aceleração do download, o tráfego adicional e o carregamento da Pesquisa. Hoje vou falar sobre como conseguimos criar esse classificador.

1. A idéia funcionará?
Nas tarefas de pesquisa, raramente é claro de antemão que existe uma boa solução. E no nosso caso, nós também inicialmente não sabíamos quais dados eram necessários para criar um classificador suficientemente bom. Em tal situação, é útil começar com alguns modelos muito simples que permitirão avaliar os benefícios potenciais do desenvolvimento.
A ideia mais simples acabou sendo a seguinte: carregaremos os resultados da pesquisa no primeiro prompt da sugestão de pesquisa; quando o prompt muda, descartamos o download anterior e começamos a baixar um novo candidato. Verificou-se que esse algoritmo funciona bem e quase todas as solicitações podem ser pré-carregadas, no entanto, a carga nos back-end de pesquisa aumenta de acordo e o tráfego do usuário aumenta de acordo. É claro que essa solução não pode ser implementada.
A idéia a seguir também era bastante simples: é necessário carregar sugestões de pesquisa prováveis, não em todos os casos, mas apenas quando estivermos confiantes o suficiente para que sejam realmente necessárias. A solução mais simples será um classificador que funcione diretamente em tempo de execução, de acordo com os dados que o sajest já possui.
O primeiro classificador foi construído usando apenas dez fatores. Esses fatores dependiam da distribuição de probabilidade sobre o conjunto de prompts (idéia: quanto maior o "peso" do primeiro prompt, mais provável ele seria inserido) e o comprimento da entrada (idéia: quanto menos letras o usuário digitar, mais seguro será o pré-carregamento). A beleza desse classificador também foi que, para construí-lo, não foi necessário liberar nada. Os fatores necessários para o candidato podem ser coletados fazendo uma solicitação http para o demônio mais sajest e os destinos são construídos de acordo com os logs mais simples: o candidato é considerado "bom" se a solicitação total do usuário corresponder completamente a ele. Foi possível montar esse pool, treinar várias regressões logísticas e criar um diagrama de dispersão em apenas algumas horas.
As métricas para o pré-renderizador não são completamente organizadas como na classificação binária usual. Apenas dois parâmetros são importantes, mas isso não é precisão ou integridade.
Vamos - número total de pedidos, - o número total de todos os pré-candidatos, - o número total de pré-candidatos bem-sucedidos, ou seja, aqueles que finalmente correspondem à entrada do usuário. Em seguida, duas características interessantes são calculadas da seguinte forma:
Por exemplo, se exatamente um pré-candidato por solicitação for feito e metade dos pré-candidatos tiver êxito, a eficiência do pré-candidato será de 50%, e isso significa que foi possível acelerar o carregamento de metade dos pedidos. Ao mesmo tempo, para as solicitações nas quais o pré-fornecedor trabalhou com êxito, não foi criado tráfego adicional; para os pedidos em que o pré-fornecedor falhou, um pedido adicional teve que ser definido; portanto, o número total de solicitações é uma vez e meia maior que o original, "extra" solicita 50% do número original; portanto, .
Nestas coordenadas, desenhei o primeiro gráfico de dispersão. Ele ficou assim:

Esses valores continham uma quantidade razoável de suposições, mas pelo menos já estava claro que, provavelmente, um bom classificador funcionaria: acelerar o carregamento para várias dezenas de por cento das solicitações, aumentar a carga em várias dezenas de por cento é uma mudança interessante.
Foi interessante observar como o classificador funciona. De fato, a duração da solicitação acabou sendo um fator muito forte: se o usuário já inseriu a primeira dica e é ao mesmo tempo bastante provável, é possível pré-buscar. Portanto, a previsão do classificador aumenta acentuadamente no final da consulta.
margin prefix candidate -180.424 -96.096 -67.425 -198.641 -138.851 -123.803 -109.841 -96.805 -146.568 -135.725 -125.448 -58.615 31.414 -66.754 1.716
Um pré-renderizador será útil, mesmo que tenha ocorrido no momento da inserção da última letra da solicitação. O fato é que os usuários ainda passam algum tempo clicando no botão "Localizar" após inserir a solicitação. Este tempo também pode ser salvo.
2. Primeira implementação
Depois de algum tempo, foi possível montar um design totalmente funcional: o aplicativo buscava dicas de busca no demônio do mais esperto. Ele enviou, entre outras coisas, informações sobre a pré-carga da primeira pista. O aplicativo, após receber o sinalizador apropriado, baixou os resultados e, se a entrada do usuário finalmente coincidir com o candidato, renderizou os resultados.
Naquele momento, o classificador adquiriu novos fatores, e o modelo não era mais regressão logística, mas um CatBoost . Inicialmente, lançamos limites bastante conservadores para o classificador, mas mesmo eles nos permitiram carregar instantaneamente quase 10% dos resultados da pesquisa. Foi um lançamento muito bem sucedido, porque conseguimos alterar significativamente os quantis menores da velocidade de download do SERP, e os usuários perceberam isso e começaram a retornar estatisticamente de forma significativa à pesquisa: um aumento significativo da pesquisa afetou a frequência dos usuários que realizavam sessões de pesquisa!
3. Outras melhorias
Embora a implementação tenha sido bem-sucedida, a solução ainda era extremamente imperfeita. Um estudo cuidadoso dos logs de operação mostrou que existem vários problemas.
O classificador é instável. Por exemplo, pode acontecer que, pelo prefixo Yandex, ele preveja a solicitação Yandex, pelo prefixo Yandex não preveja nada e pelo prefixo Yandex preveja novamente a solicitação Yandex. Então, nossa primeira implementação direta faz duas solicitações, embora possa muito bem gerenciar com uma.
O pré-renderizador não sabe como processar dicas de palavra por palavra. Clicar em um prompt de palavra-de-palavra leva à aparência de um espaço adicional na solicitação. Por exemplo, se um usuário inserisse o Yandex, seu primeiro prompt seria o Yandex; mas se o usuário usou a dica palavra por palavra, a entrada já será a sequência "Yandex" e o primeiro prompt será "cartões Yandex". Isso levará a conseqüências desastrosas: a solicitação do Yandex já baixada será descartada; a solicitação do cartão Yandex será carregada. Depois disso, o usuário clica no botão "Localizar" e ... aguarda a entrega completa da emissão a pedido do Yandex.
Em alguns casos, os candidatos não têm chance de obter sucesso. Uma pesquisa por coincidência imprecisa funciona na sujest, de modo que o candidato pode, por exemplo, conter apenas uma palavra das que foram inseridas pelo usuário; ou o usuário pode digitar um erro de digitação e o primeiro prompt nunca corresponderá exatamente à sua entrada.
É claro que deixar um pré-candidato com tais imperfeições era uma pena, mesmo que fosse útil. Fiquei especialmente ofendido com o problema com pistas palavra por palavra. Considero a introdução de dicas do word-of-word na pesquisa móvel Yandex uma das minhas melhores implementações durante todo o tempo em que trabalhei na empresa, mas aqui o pré-candidato não sabe como trabalhar com elas! Vergonha, não de outra forma.
Primeiro, corrigimos o problema da instabilidade do classificador. A solução foi escolhida de maneira muito simples: mesmo que o classificador retorne uma previsão negativa, não paramos de carregar o candidato anterior. Isso ajuda a salvar solicitações adicionais, porque quando o mesmo candidato retornar na próxima vez, você não precisará baixar o problema correspondente novamente. Ao mesmo tempo, isso permite que você carregue resultados mais rapidamente, pois o candidato é baixado no momento em que o classificador trabalhou para ele pela primeira vez.
Então chegou a vez de pistas proverbiais. Um servidor mais sagrado é um daemon sem estado, por isso é difícil implementar a lógica associada ao processamento do candidato anterior para o mesmo usuário. Procurar prompts simultaneamente para uma solicitação do usuário e uma solicitação do usuário sem um espaço à direita significa, na verdade, dobrar o RPS por um daemon mais sajest, portanto, essa também não era uma boa opção. Como resultado, fizemos o seguinte: o cliente passa em um parâmetro especial o texto do candidato, que está carregando no momento; se esse candidato, até espaços, for semelhante à entrada do usuário, a distribuiremos, mesmo que o candidato à entrada atual tenha sido alterado.
Após este lançamento, finalmente se tornou possível inserir consultas usando os prompts palavra por palavra e aproveitar a pré-busca! É engraçado o suficiente que, antes desta versão, apenas os usuários que terminaram de inserir sua solicitação usando o teclado, sem brincadeira, usassem a pré-busca.
Finalmente, resolvemos o terceiro problema com a ajuda do ML: fatores adicionais sobre as fontes de dicas, coincidência com a entrada do usuário; além disso, graças ao primeiro lançamento, conseguimos coletar mais estatísticas e aprender com os dados mensais.
4. Qual é o resultado
Cada uma dessas versões gerou dezenas de por cento de aumento nos downloads instantâneos. A melhor parte é que fomos capazes de melhorar o desempenho do pré-renderizador por mais de duas vezes, praticamente sem tocar na parte sobre aprendizado de máquina, mas apenas melhorando a física do processo. Essa é uma lição importante: geralmente a qualidade do classificador não é o gargalo do produto, mas sua melhoria é a tarefa mais interessante e, portanto, o desenvolvimento é distraído por ele.
Infelizmente, os SERPs carregados instantaneamente não são um sucesso completo; a saída carregada ainda precisa ser renderizada , o que não acontece instantaneamente. Portanto, ainda precisamos trabalhar para converter melhor os downloads instantâneos de dados em renderizações instantâneas dos resultados da pesquisa.
Felizmente, as implementações implementadas já nos permitem falar sobre o pré-renderizador como um recurso bastante estável; Além disso, testamos as implementações descritas na cláusula 2: todas juntas também levam ao fato de que os próprios usuários começam a fazer sessões de pesquisa com mais frequência. Essa é outra lição útil: melhorias significativas na velocidade do serviço podem afetar estatisticamente a retenção.
No vídeo abaixo, você pode ver como o pré-candidato está trabalhando no meu telefone.