Suchvorschläge (sjest) sind nicht nur ein Benutzerdienst, sondern auch ein sehr leistungsfähiges Sprachmodell, das Milliarden von Suchanfragen speichert, Fuzzy-Suche, Personalisierung und vieles mehr unterstützt. Wir haben gelernt, wie man sujest verwendet, um die endgültige Abfrage des Benutzers vorherzusagen und Suchergebnisse zu laden, bevor wir auf die Schaltfläche "Suchen" klicken.
Die Einführung dieser Technologie - des Pre-Renderers - erforderte viele interessante Lösungen für die mobile Entwicklung, die Entwicklung von Suchlaufzeiten, Protokollen und Metriken. Und natürlich brauchten wir einen coolen Klassifikator, der im Voraus festlegt, ob eine Suchabfrage geladen werden soll: Dieser Klassifikator muss ein Gleichgewicht zwischen Downloadbeschleunigung, zusätzlichem Datenverkehr und Suchlast herstellen. Heute werde ich darüber sprechen, wie wir es geschafft haben, einen solchen Klassifikator zu erstellen.

1. Funktioniert die Idee?
Bei Forschungsaufgaben ist selten im Voraus klar, dass es eine gute Lösung gibt. Und auch in unserem Fall wussten wir zunächst nicht, welche Daten benötigt wurden, um einen ausreichend guten Klassifikator zu erstellen. In einer solchen Situation ist es hilfreich, mit einigen sehr einfachen Modellen zu beginnen, mit denen Sie die potenziellen Vorteile der Entwicklung bewerten können.
Die einfachste Idee stellte sich wie folgt heraus: Wir laden die Suchergebnisse bei der ersten Eingabeaufforderung aus dem Suchvorschlag. Wenn sich die Eingabeaufforderung ändert, wird der vorherige Download verworfen und ein neuer Kandidat heruntergeladen. Es stellte sich heraus, dass ein solcher Algorithmus gut funktioniert und fast alle Anforderungen vorinstalliert werden können. Die Belastung der Such-Backends steigt jedoch entsprechend und der Benutzerverkehr entsprechend. Es ist klar, dass eine solche Lösung nicht implementiert werden kann.
Die folgende Idee war ebenfalls recht einfach: Es ist notwendig, wahrscheinliche Suchhinweise nicht in allen Fällen zu laden, sondern nur, wenn wir sicher genug sind, dass sie wirklich benötigt werden. Die einfachste Lösung ist ein Klassifikator, der direkt zur Laufzeit gemäß den Daten arbeitet, über die der Sajest bereits verfügt.
Der erste Klassifikator wurde unter Verwendung von nur zehn Faktoren gebaut. Diese Faktoren hingen von der Wahrscheinlichkeitsverteilung über die Eingabeaufforderungen ab (Idee: Je größer das „Gewicht“ der ersten Eingabeaufforderung ist, desto wahrscheinlicher ist die Eingabe) und von der Länge der Eingabe (Idee: Je weniger Buchstaben der Benutzer eingeben muss, desto sicherer ist das Vorladen). Das Schöne an diesem Klassifikator war auch, dass es für den Bau nicht erforderlich war, etwas freizugeben. Die notwendigen Faktoren für den Kandidaten können gesammelt werden, indem eine http-Anfrage an den sajest-Dämon gesendet wird, und die Ziele werden gemäß den einfachsten Protokollen erstellt: Der Kandidat wird als „gut“ angesehen, wenn die Gesamtanforderung des Benutzers vollständig mit dieser übereinstimmt. Es war möglich, in wenigen Stunden einen solchen Pool zusammenzustellen, mehrere logistische Regressionen zu trainieren und ein Streudiagramm zu erstellen.
Die Metriken für den Pre-Renderer sind nicht vollständig wie in der üblichen binären Klassifizierung angeordnet. Es sind nur zwei Parameter wichtig, dies ist jedoch keine Richtigkeit oder Vollständigkeit.
Lass - Gesamtzahl der Anfragen, - die Gesamtzahl aller Prerender, - die Gesamtzahl der erfolgreichen Vorrender, d.h. diejenigen, die letztendlich mit Benutzereingaben übereinstimmen. Dann werden zwei interessante Eigenschaften wie folgt berechnet:
Wenn beispielsweise genau ein Vorrender pro Anforderung ausgeführt wird und die Hälfte der Vorrender erfolgreich ist, beträgt die Effizienz des Vorrenders 50%. Dies bedeutet, dass das Laden der Hälfte der Anforderungen beschleunigt werden konnte. Gleichzeitig wurde für die Anforderungen, bei denen der Prerender erfolgreich funktioniert hat, kein zusätzlicher Datenverkehr erstellt. Für die Anforderungen, bei denen der Prerender fehlgeschlagen ist, musste eine zusätzliche Anforderung festgelegt werden. Die Gesamtzahl der Anfragen ist also eineinhalb Mal größer als die des Originals. "Zusätzliche" Anfragen machen daher 50% der ursprünglichen Anzahl aus .
In diesen Koordinaten habe ich das erste Streudiagramm gezeichnet. Er sah so aus:

Diese Werte enthielten eine ganze Reihe von Annahmen, aber es war zumindest bereits klar, dass höchstwahrscheinlich ein guter Klassifikator funktionieren würde: Es ist eine interessante Änderung, das Laden für mehrere zehn Prozent der Anforderungen zu beschleunigen und die Last um einige zehn Prozent zu erhöhen.
Es war interessant zu beobachten, wie der Klassifikator funktioniert. In der Tat stellte sich heraus, dass die Länge der Anfrage ein sehr starker Faktor war: Wenn der Benutzer den ersten Hinweis bereits fast eingegeben hat und es gleichzeitig sehr wahrscheinlich ist, können Sie ihn vorab abrufen. Die Vorhersage des Klassifikators nimmt also gegen Ende der Abfrage stark zu.
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
Ein Prerender ist auch dann nützlich, wenn er zum Zeitpunkt der Eingabe des allerletzten Buchstabens der Anfrage aufgetreten ist. Tatsache ist, dass Benutzer nach Eingabe der Anfrage noch einige Zeit damit verbringen, auf die Schaltfläche „Suchen“ zu klicken. Diese Zeit kann auch gespeichert werden.
2. Erste Implementierung
Nach einiger Zeit war es möglich, ein voll funktionsfähiges Design zusammenzustellen: Die Anwendung suchte nach Suchhinweisen für den Dämon der Sajest. Er schickte unter anderem Informationen darüber, ob der erste Hinweis vorgeladen werden sollte. Nachdem die Anwendung das entsprechende Flag erhalten hatte, lud sie die Ergebnisse herunter und lieferte die Ergebnisse, wenn die Benutzereingabe letztendlich mit dem Kandidaten übereinstimmte.
In diesem Moment erwarb der Klassifikator neue Faktoren, und das Modell war keine logistische Regression mehr, sondern ein ziemlicher CatBoost . Anfangs haben wir ziemlich konservative Schwellenwerte für den Klassifikator eingeführt, aber selbst damit konnten wir fast 10% der Suchergebnisse sofort laden. Es war eine sehr erfolgreiche Veröffentlichung, weil Wir haben es geschafft, die kleinen Quantile der Suchladegeschwindigkeit signifikant zu verschieben, und die Benutzer haben dies bemerkt und sind statistisch signifikant zur Suche zurückgekehrt: Eine signifikante Beschleunigung der Suche hat sich darauf ausgewirkt, wie oft Benutzer Suchsitzungen durchführen!
3. Weitere Verbesserungen
Obwohl die Implementierung erfolgreich war, war die Lösung immer noch äußerst unvollständig. Eine sorgfältige Untersuchung der Betriebsprotokolle ergab, dass mehrere Probleme vorliegen.
Der Klassifikator ist instabil. Beispielsweise kann sich herausstellen, dass das Yandex-Präfix die Yandex-Anforderung vorhersagt, das Yandex-Präfix nichts vorhersagt und das Yandex-Präfix die Yandex-Anforderung erneut vorhersagt. Dann stellt unsere erste einfache Implementierung zwei Anforderungen, obwohl sie mit einer gut zurechtkommen könnte.
Der Prerender weiß nicht, wie er Wort-für-Wort-Hinweise verarbeitet. Durch Klicken auf eine Wort-zu-Wort-Eingabeaufforderung wird ein zusätzliches Leerzeichen in der Anforderung angezeigt. Wenn ein Benutzer beispielsweise Yandex eingibt, lautet seine erste Eingabeaufforderung Yandex. Wenn der Benutzer jedoch den wortweisen Hinweis verwendet, lautet die Eingabe bereits die Zeichenfolge "Yandex", und die erste Eingabeaufforderung lautet "Yandex-Karten". Dies führt zu katastrophalen Folgen: Die bereits heruntergeladene Yandex-Anfrage wird verworfen, stattdessen wird die Yandex-Kartenanfrage geladen. Danach klickt der Benutzer auf die Schaltfläche "Suchen" und ... wartet auf Anfrage von Yandex auf die vollständige Zustellung der Ausgabe.
In einigen Fällen haben Kandidaten keine Chance, erfolgreich zu werden. Eine Suche nach ungenauem Zufall funktioniert im Sujest, so dass der Kandidat beispielsweise nur ein Wort von den vom Benutzer eingegebenen Wörtern enthalten kann. oder der Benutzer kann einen Tippfehler machen, und dann stimmt die erste Eingabeaufforderung nie genau mit ihrer Eingabe überein.
Natürlich war es eine Schande, einen Prerender mit solchen Unvollkommenheiten zu belassen, auch wenn es nützlich war. Ich war besonders beleidigt über das Problem mit Wort-für-Wort-Hinweisen. Ich betrachte die Einführung von Wort-in-Wort-Eingabeaufforderungen in der mobilen Suche von Yandex als eine meiner besten Implementierungen für die gesamte Zeit, in der ich im Unternehmen gearbeitet habe, aber hier weiß der Prerender nicht, wie er damit arbeiten soll! Schade, sonst nicht.
Zunächst haben wir das Problem der Instabilität des Klassifikators behoben. Die Lösung wurde sehr einfach gewählt: Selbst wenn der Klassifikator eine negative Vorhersage zurückgegeben hat, hören wir nicht auf, den vorherigen Kandidaten zu laden. Dies hilft, zusätzliche Anfragen zu speichern, da Sie das entsprechende Problem nicht erneut herunterladen müssen, wenn derselbe Kandidat das nächste Mal zurückkehrt. Gleichzeitig können Sie so die Ergebnisse schneller laden, da der Kandidat zu dem Zeitpunkt heruntergeladen wird, als der Klassifikator zum ersten Mal für ihn gearbeitet hat.
Dann kamen sprichwörtliche Hinweise. Ein sagest Server ist ein zustandsloser Daemon, daher ist es schwierig, die Logik zu implementieren, die mit der Verarbeitung des vorherigen Kandidaten für denselben Benutzer verbunden ist. Die gleichzeitige Suche nach Eingabeaufforderungen für eine Benutzeranforderung und für eine Benutzeranforderung ohne nachgestellten Leerzeichen bedeutet, dass der RPS durch einen sajest-Daemon verdoppelt wird. Dies war also auch keine gute Option. Infolgedessen haben wir Folgendes getan: Der Client übergibt den Text des Kandidaten, der gerade geladen wird, in einem speziellen Parameter. Wenn dieser Kandidat bis auf Leerzeichen der Benutzereingabe ähnlich ist, geben wir ihn weiter, auch wenn sich der Kandidat für die aktuelle Eingabe geändert hat.
Nach dieser Version war es endlich möglich, Abfragen mit Wort-für-Wort-Eingabeaufforderungen einzugeben und den Prefetch zu genießen! Es ist lustig genug, dass vor dieser Version nur diejenigen Benutzer den Prefetch verwendeten, die ihre Anfrage über die Tastatur ohne sjest eingegeben hatten.
Schließlich haben wir das dritte Problem mit Hilfe von ML gelöst: Faktoren über die Quellen von Hinweisen hinzugefügt, Übereinstimmung mit Benutzereingaben; Darüber hinaus konnten wir dank des ersten Starts mehr Statistiken sammeln und aus monatlichen Daten lernen.
4. Was ist das Ergebnis?
Jede dieser Versionen führte zu einem Anstieg der Sofort-Downloads um mehrere zehn Prozent. Das Beste daran ist, dass wir die Leistung des Prerenders um mehr als das Zweifache verbessern konnten, praktisch ohne den Teil über maschinelles Lernen zu berühren, sondern nur die Physik des Prozesses zu verbessern. Dies ist eine wichtige Lehre: Oft ist die Qualität des Klassifikators nicht der Engpass im Produkt, aber seine Verbesserung ist die interessanteste Aufgabe, und daher wird die Entwicklung dadurch abgelenkt.
Leider sind sofort geladene SERPs kein vollständiger Erfolg. Die geladene Ausgabe muss noch gerendert werden , was nicht sofort geschieht. Wir müssen also noch daran arbeiten, sofortige Daten-Downloads besser in sofortige Renderings von Suchergebnissen umzuwandeln.
Glücklicherweise erlauben uns die implementierten Implementierungen bereits, über den Pre-Renderer als eine ziemlich stabile Funktion zu sprechen. Zusätzlich haben wir die in Abschnitt 2 beschriebenen Implementierungen getestet: Alle zusammen führen auch dazu, dass Benutzer selbst häufiger Suchsitzungen durchführen. Dies ist eine weitere nützliche Lektion: Signifikante Verbesserungen der Geschwindigkeit des Dienstes können seine Beibehaltung statistisch signifikant beeinflussen.
Im Video unten können Sie sehen, wie der Prerender jetzt auf meinem Telefon funktioniert.