Hallo zusammen! Ich bin Sascha, CTO & Co-Founder von LoyaltyLab. Vor zwei Jahren bin ich mit Freunden, wie alle anderen armen Studenten, abends zum nĂ€chsten GeschĂ€ft gegangen, um Bier zu kaufen. Es hat uns sehr frustriert, dass der EinzelhĂ€ndler, der wusste, dass wir nach Bier kommen wĂŒrden, keinen Rabatt auf Chips oder Snacks angeboten hat, obwohl das so logisch ist! Wir konnten nicht verstehen, warum das so ist, und beschlossen, unser eigenes Unternehmen zu grĂŒnden. Und als Bonus konnten wir uns jeden Freitag Rabatte auf genau diese Chips ausstellen.

Und es kam so weit, dass ich mit dem Material zur technischen Seite des Produkts auf der spechen werde. Wir freuen uns, unsere Entwicklungen mit der Community zu teilen, deshalb veröffentliche ich meinen Vortrag als Artikel.
EinfĂŒhrung
Wie viele am Anfang, haben wir mit einer Ăbersicht darĂŒber angefangen, wie Empfehlungssysteme erstellt werden. Und die beliebteste Architektur ist die folgende:

Sie besteht aus zwei Teilen:
- Die Kandidatensampling fĂŒr Empfehlungen erfolgt durch ein einfaches und schnelles Modell, normalerweise kollaborativ.
- Die Rangordnung der Kandidaten erfolgt durch ein komplexeres und langsameres Inhaltsmodell unter BerĂŒcksichtigung aller möglichen Merkmale in den Daten.
Hier und in der Folge werde ich folgende Begriffe verwenden:
- candidate/Kandidat fĂŒr Empfehlungen â ein Paar aus User und Produkt, das potenziell in die Empfehlungen im Produktionsbereich aufgenommen werden kann.
- candidates extraction/extractor/Methode zur Extraktion von Kandidaten â der Prozess oder die Methode zur Extraktion von "Kandidaten fĂŒr Empfehlungen" aus den vorhandenen Daten.
Im ersten Schritt verwendet man normalerweise verschiedene Varianten der kollaborativen Filterung. Die beliebteste ist . Es ist bemerkenswert, dass die meisten Artikel ĂŒber Empfehlungssysteme nur verschiedene Verbesserungen kollaborativer Modelle im ersten Schritt behandeln, aber ĂŒber andere Sampling-Methoden spricht niemand wirklich. FĂŒr uns hat der Ansatz, der nur kollaborative Modelle und verschiedene Optimierungen damit verwendet, nicht die QualitĂ€t geliefert, die wir erwartet hatten, weshalb wir uns intensiv mit der Forschung in diesem Bereich beschĂ€ftigt haben. Am Ende des Artikels zeige ich, wie sehr wir ALS verbessern konnten, das fĂŒr uns die Basislinie war.
Bevor ich zu unserem Ansatz komme, ist es wichtig zu erwĂ€hnen, dass es bei Echtzeitempfehlungen, bei denen wir Daten berĂŒcksichtigen mĂŒssen, die vor 30 Minuten entstanden sind, tatsĂ€chlich nicht viele AnsĂ€tze gibt, die innerhalb der benötigten Zeit funktionieren können. In unserem Fall mĂŒssen Empfehlungen jedoch nicht hĂ€ufiger als einmal pro Tag gesammelt werden, und in den meisten FĂ€llen nur einmal pro Woche, was uns die Möglichkeit gibt, komplexe Modelle zu verwenden und die QualitĂ€t erheblich zu steigern.
Nehmen wir fĂŒr die Baseline die Kennzahlen, die nur durch ALS bei der Kandidatenauswahl erzielt werden. Die wichtigsten Kennzahlen, auf die wir achten, sind die folgenden:
- PrĂ€zision â der Anteil der korrekt ausgewĂ€hlten Kandidaten aus den sampelten.
- Recall â der Anteil der tatsĂ€chlichen Kandidaten aus denen, die sich tatsĂ€chlich im Zielbereich befanden.
- F1-Score â F-MaĂ, berechnet aus den vorherigen beiden Punkten.
Wir werden auch die Kennzahlen des finalen Modells nach dem Training des Gradientenboosting mit zusÀtzlichen Inhaltsmerkmalen betrachten. Auch hier gibt es 3 Hauptkennzahlen:
- precision@5 â der durchschnittliche Anteil an Produkten aus den Top 5 nach Wahrscheinlichkeit fĂŒr jeden KĂ€ufer.
- response-rate@5 â die Konversionsrate der KĂ€ufer vom Besuch im GeschĂ€ft zum Kauf eines personalisierten Angebots (in einem Angebot sind 5 Produkte).
- avg roc-auc per user â der Durchschnitt fĂŒr jeden KĂ€ufer.
Es ist wichtig zu beachten, dass alle genannten Kennzahlen auf einer , das heiĂt, das Training erfolgt in den ersten k Wochen, und als Testdaten wird die k+1 Woche verwendet. So spiegeln saisonale Höhen und Tiefen die Interpretation der ModellqualitĂ€t minimal wider. Auf allen Grafiken wird die x-Achse die Nummer der Woche in der Kreuzvalidierung darstellen, wĂ€hrend die y-Achse den Wert der angegebenen Kennzahl darstellt. Alle Grafiken basieren auf Transaktionsdaten eines einzelnen Kunden, um einen korrekten Vergleich untereinander zu gewĂ€hrleisten.
Bevor wir damit beginnen, unseren Ansatz zu beschreiben, schauen wir uns zunÀchst die Baseline an, die ein trainiertes ALS-Modell darstellt.
Kennzahlen zur Auswahl von Kandidaten:

Finale Kennzahlen:

Ich betrachte alle Implementierungen von Algorithmen als eine Art GeschÀftshypothese. Grob gesagt können kollaborative Modelle als die Hypothese angesehen werden, dass "Menschen dazu neigen, das zu kaufen, was andere Menschen, die ihnen Àhnlich sind, kaufen." Wie bereits erwÀhnt, haben wir uns jedoch nicht nur auf diese Semantik beschrÀnkt, und hier sind einige Hypothesen, die auch gut mit Daten im Offline-Einzelhandel funktionieren:
- Was zuvor gekauft wurde.
- Ăhnliches zu dem, was zuvor gekauft wurde.
- Zeitraum des vergangenen Kaufs.
- Beliebtes nach Kategorien/Marken.
- Wechselnde KĂ€ufe verschiedener Produkte von Woche zu Woche (Markov-Ketten).
- Ăhnliche Produkte zu KĂ€ufern basierend auf Eigenschaften, die von verschiedenen Modellen erstellt wurden (Word2Vec, DSSM usw.).
Was frĂŒher gekauft wurde
Die offensichtlichste Heuristik, die im Lebensmitteleinzelhandel sehr gut funktioniert. Hier nehmen wir alle Produkte, die der Inhaber der LoyalitĂ€tskarte in den letzten K Tagen (ĂŒblich 1-3 Wochen) gekauft hat, oder K Tage im Jahr davor. Bei der Anwendung nur dieses Ansatzes erhalten wir folgende Metriken:

Es ist offensichtlich, dass je lĂ€nger wir den Zeitraum wĂ€hlen, desto höher ist unser Recall und desto niedriger die Precision und umgekehrt. Die besten Ergebnisse im Durchschnitt fĂŒr die Kunden erzielt "die letzten 2 Wochen".
Ăhnliches zu dem, was zuvor gekauft wurde
Es ist nicht ĂŒberraschend, dass im Lebensmitteleinzelhandel "was vorher gekauft wurde" gut funktioniert, aber nur Kandidaten aus dem zu extrahieren, was der Benutzer bereits gekauft hat, ist nicht besonders effektiv, da es schwer ist, den KĂ€ufer mit einem neuen Produkt zu ĂŒberraschen. Daher schlagen wir vor, diese Heuristik mit Hilfe der gleichen kollaborativen Modelle zu verfeinern. Aus den Vektoren, die wir wĂ€hrend des ALS-Trainings erhalten haben, können wir Ă€hnliche Produkte zu dem finden, was der Benutzer bereits gekauft hat. Diese Idee erinnert stark an "Ă€hnliche Videos" in Video-Streaming-Diensten, aber da wir nicht wissen, was der Benutzer gerade isst/kauft, bleibt uns nur, nach Ă€hnlichem zu suchen, was er bereits gekauft hat, zumal wir bereits wissen, wie gut das funktioniert. Wenn wir diese Methode mit den Transaktionen der Benutzer in den letzten 2 Wochen anwenden, erhalten wir folgende Metriken:

Hier k â die Anzahl Ă€hnlicher Produkte, die fĂŒr jedes vom KĂ€ufer in den letzten 14 Tagen gekaufte Produkt abgeleitet werden.
Dieser Ansatz hat bei uns besonders gut bei einem Kunden funktioniert, dem es entscheidend war, auf keinen Fall das zu empfehlen, was bereits in der Kaufhistorie des Nutzers war.
Zeitraum vergangener KĂ€ufe
Wie wir bereits festgestellt haben, funktioniert der erste Ansatz aufgrund der hohen Kaufrate von Produkten gut in unserer speziellen Situation. Aber wie verhĂ€lt es sich mit Produkten wie Waschmittel/Shampoo/usw., also solchen Artikeln, die wahrscheinlich nicht alle ein bis zwei Wochen benötigt werden und die vorherigen Methoden nicht erfassen können. Daraus ergibt sich folgende Idee â es wird vorgeschlagen, den Kaufzeitraum jedes Produkts im Durchschnitt auf Basis der KĂ€ufer zu berechnen, die das Produkt mehr als k einmal gekauft haben. Und dann extrahieren wir, was mit hoher Wahrscheinlichkeit beim KĂ€ufer bereits aufgebraucht ist. Die berechneten ZeitrĂ€ume fĂŒr Produkte können visuell auf Angemessenheit ĂŒberprĂŒft werden:

Dann werden wir erkennen, ob das Ende des Produktzeitraums in den Zeitraum fÀllt, wenn die Empfehlungen in die Produktion gehen, und das, was passt, sampeln. Der Ansatz lÀsst sich wie folgt veranschaulichen:

Hier haben wir zwei HauptfÀlle, die wir betrachten können:
- Sollten wir Produkte an KĂ€ufer sampeln, die das Produkt weniger als K Mal gekauft haben.
- Sollten wir das Produkt sampeln, wenn das Ende seines Zeitraums vor Beginn des Zielintervalls liegt.
Im nÀchsten Diagramm sind die Ergebnisse dargestellt, die diese Methode mit verschiedenen Hyperparametern erreicht:

ft â Nur KĂ€ufer ansprechen, die das Produkt mindestens K (hier K=5) Mal gekauft haben
tm â Nur Kandidaten berĂŒcksichtigen, die in das Zielintervall fallen
Es ist nicht ĂŒberraschend, dass im Zustand (0, 0) die gröĂte Recall und die geringste Precision, da unter diesen Bedingungen am meisten Kandidaten extrahiert werden. Die besten Ergebnisse erzielt man jedoch, wenn wir keine Produkte fĂŒr KĂ€ufer sampeln, die ein bestimmtes Produkt weniger als k Mal gekauft haben, und auch Produkte einbeziehen, deren Zeitraum vor das Zielintervall fĂ€llt.
Beliebt in der Kategorie
Eine weitere eher offensichtliche Idee ist, beliebte Produkte aus verschiedenen Kategorien oder Marken zu sampeln. Hier berechnen wir fĂŒr jeden KĂ€ufer Top-k Wir extrahieren die âbeliebtestenâ Kategorien/Marken aus dieser Kategorie/Marke. In unserem Fall definieren wir âlieblingsâ und âbeliebtâ anhand der Anzahl der KĂ€ufe eines Produkts. Ein zusĂ€tzliches Plus dieses Ansatzes ist die Anwendbarkeit im Fall des Cold Starts. Das heiĂt fĂŒr KĂ€ufer, die entweder sehr wenige KĂ€ufe getĂ€tigt haben, lange nicht im GeschĂ€ft waren oder ĂŒberhaupt nur eine Treuekarte beantragt haben. FĂŒr sie ist es einfacher und am besten, Produkte aus den bei KĂ€ufern mit vorhandener Historie beliebten Produkten anzubieten. Die Metriken ergeben sich wie folgt:

Hier bedeutet die Zahl nach dem Wort âcategoryâ die Tiefe der Kategorie.
Es ist auch nicht ĂŒberraschend, dass engere Kategorien die besten Ergebnisse liefern, da sie genauere âbeliebteâ Produkte fĂŒr KĂ€ufer extrahieren.
Wechselnde KĂ€ufe verschiedener Produkte von Woche zu Woche
Ein interessanter Ansatz, den ich in Artikeln ĂŒber Empfehlungssysteme nicht gesehen habe â eine ziemlich einfache und gleichzeitig funktionierende statistische Methode der Markov-Ketten. Hier nehmen wir 2 verschiedene Wochen und bauen fĂŒr jeden KĂ€ufer Produktpaare auf. [gekauft in Woche i]-[gekauft in Woche j], wobei j > i, und von hier aus berechnen wir fĂŒr jedes Produkt die Wahrscheinlichkeit, dass in der nĂ€chsten Woche zu einem anderen Produkt gewechselt wird. Das heiĂt fĂŒr jedes Paar von Produkten producti-productj wir zĂ€hlen deren Anzahl in den gefundenen Paaren und teilen durch die Anzahl der Paare, in denen producti in der ersten Woche war. Um die Kandidaten zu extrahieren, nehmen wir den letzten Beleg des KĂ€ufers und ziehen die wahrscheinlichsten folgenden Produkte aus der Ăbergangsmatrix, die wir erhalten haben. Der Prozess zum Aufbau der Ăbergangsmatrix sieht folgendermaĂen aus: Top-k Aus realen Beispielen in der Ăbergangswahrscheinlichkeitsmatrix sehen wir folgende interessante PhĂ€nomene:

Hier kann man interessante AbhĂ€ngigkeiten bemerken, die im Konsumverhalten auftauchen: zum Beispiel Liebhaber von ZitrusfrĂŒchten oder eine Milchmarke, von der man mit hoher Wahrscheinlichkeit zu einer anderen wechselt. Es ist auch nicht ĂŒberraschend, dass Produkte mit hoher Wiederkaufrate, wie Ăl, ebenfalls hier auftauchen.

Die Metriken in der Methode mit Markov-Ketten ergeben sich wie folgt:
â die Anzahl der Produkte, die fĂŒr jedes gekaufte Produkt aus der letzten Transaktion des KĂ€ufers extrahiert werden.

k â die Anzahl der Artikel, die fĂŒr jeden gekauften Artikel aus der letzten Transaktion des KĂ€ufers abgezogen werden.
Wie wir sehen, zeigt die Konfiguration mit k=4 das beste Ergebnis. Der Anstieg in Woche 4 kann durch saisonale Verhaltensweisen wĂ€hrend der Feiertage erklĂ€rt werden.Â
Ăhnliche Produkte fĂŒr KĂ€ufer, basierend auf Merkmalen, die von verschiedenen Modellen erstellt wurden
Hier sind wir am schwierigsten und interessantesten Teil angekommen â der Suche nach den nĂ€chsten Nachbarn anhand von KĂ€ufern- und Produktvektoren, die mit verschiedenen Modellen erstellt wurden. In unserer Arbeit verwenden wir dafĂŒr 3 Modelle:
- ALS
- Word2Vec (Item2Vec fĂŒr solche Aufgaben)
- DSSM
Mit ALS haben wir uns bereits befasst, wer mehr ĂŒber das Training erfahren möchte, kann lesen . Im Fall von Word2Vec verwenden wir die allgemein bekannte Implementierung des Modells aus gensim. Analog zu Texten definieren wir eine Transaktion als einen Einkaufsbeleg. Auf diese Weise lernt das Modell beim Erstellen des Produktvektors, den "Kontext" des Produkts (die anderen Produkte im Einkaufsbeleg) vorherzusagen. Bei E-Commerce-Daten ist es besser, anstelle des Belegs die Sitzung des KĂ€ufers zu verwenden, hierzu haben die Jungs von toll geschrieben. DSSM zu zerlegen, ist interessanter. UrsprĂŒnglich wurde es von den Jungs von Microsoft als Modell fĂŒr die Suche entwickelt, . Die Architektur des Modells sieht so aus:

Hier Q â query, die Benutzerabfrage, D[i] â document, die Internetseite. Bei der Eingabe des Modells werden die Merkmale der Anfrage und der Seiten verwendet. Nach jeder Eingangsschicht geht eine gewisse Anzahl von vollstĂ€ndig verbundenen Schichten (multilayer perceptron) einher. Danach lernt das Modell, den Kosinus zwischen den Vektoren, die in den letzten Schichten des Modells entstehen, zu minimieren.
In Empfehlungsaufgaben wird genau die gleiche Architektur verwendet, nur dass anstelle der Anfrage der Benutzer und anstelle der Seiten die Produkte kommen. In unserem Fall wird diese Architektur in Folgendes transformiert:

Jetzt bleibt fĂŒr die ĂberprĂŒfung der Ergebnisse nur noch ein letzter Punkt zu klĂ€ren â wĂ€hrend bei ALS und DSSM die Benutzervektoren klar definiert sind, haben wir bei Word2Vec nur die Produktvektoren. Hier haben wir fĂŒr den Aufbau des Benutzervektors 3 HauptansĂ€tze definiert:
- Einfach die Vektoren addieren, dann ergibt sich fĂŒr den kosinus-Ă€hnlichen Abstand, dass wir die Produkte in der Einkaufsgeschichte einfach gemittelt haben.
- Die Vektoren mit einer gewissen zeitlichen Gewichtung summieren.
- Gewichtung der Produkte mit dem TF-IDF-Koeffizienten.
Im Falle der linearen Gewichtung des KĂ€ufervektors gehen wir davon aus, dass das Produkt, das der Nutzer gestern gekauft hat, stĂ€rker sein Verhalten beeinflusst, als das Produkt, das er vor einem halben Jahr gekauft hat. Daher betrachten wir die letzte Woche des KĂ€ufers mit einem Koeffizienten von 1 und was davor war mit Koeffizienten von œ, â
usw.:

FĂŒr die TF-IDF-Koeffizienten machen wir genau dasselbe wie bei TF-IDF fĂŒr Texte, nur dass wir den KĂ€ufer als Dokument betrachten und den Kassenbon als Angebot, entsprechend ist das Wort das Produkt. So wird der Vektor des Nutzers stĂ€rker in Richtung seltener Produkte verschoben, wĂ€hrend hĂ€ufige und fĂŒr den KĂ€ufer vertraute Produkte ihn nicht besonders verĂ€ndern werden. Dieser Ansatz lĂ€sst sich folgendermaĂen veranschaulichen:

Schauen wir uns nun die Metriken an. So sehen die Ergebnisse von ALS aus:

Metriken fĂŒr Item2Vec mit verschiedenen Varianten der Erstellung des KĂ€ufervektors:

In diesem Fall wird genau dasselbe Modell verwendet, das auch in unserem Baseline verwendet wird. Der einzige Unterschied besteht darin, welches k wir verwenden werden. Um nur kollaborative Modelle zu verwenden, mĂŒssen wir etwa 50-70 nĂ€chstgelegene Produkte fĂŒr jeden KĂ€ufer nehmen.
Und die Metriken fĂŒr DSSM:

Wie kombiniert man all diese Methoden?
Toll, werden Sie sagen, aber was machen wir mit einem so groĂen Set an Werkzeugen zur Kandidatenauswahl? Wie findet man die optimale Konfiguration fĂŒr seine Daten? Hier ergeben sich mehrere Aufgaben:
- Wir mĂŒssen den Suchraum der Hyperparameter in jeder Methode irgendwie einschrĂ€nken. Es ist natĂŒrlich ĂŒberall diskret, aber die Anzahl möglicher Punkte ist sehr groĂ.
- Wie kann man aus einer kleinen, begrenzten Stichprobe spezifischer Methoden mit spezifischen Hyperparametern die beste Konfiguration fĂŒr seine Metrik auswĂ€hlen?
Auf die erste Frage haben wir bisher keine eindeutig richtige Antwort gefunden, daher gehen wir von Folgendem aus: FĂŒr jede Methode wurde ein Begrenzung des Suchraums der Hyperparameter in AbhĂ€ngigkeit von bestimmten Statistiken der uns vorliegenden Daten erstellt. So können wir, wenn wir den durchschnittlichen Zeitraum zwischen KĂ€ufen der Menschen kennen, annehmen, mit welchem Zeitraum wir die Methode 'was bereits gekauft wurde' und 'Zeitraum seit einem langen vergangenen Kauf' verwenden sollten.
Nachdem wir eine angemessene Anzahl an Variationen verschiedener Methoden durchgesehen haben, stellen wir Folgendes fest: Jede Implementierung extrahiert eine bestimmte Anzahl an Kandidaten und hat einen bestimmten Wert fĂŒr unsere wichtige Kennzahl (Recall). Wir möchten insgesamt eine bestimmte Anzahl an Kandidaten erhalten, abhĂ€ngig von unseren verfĂŒgbaren Rechenressourcen, mit der höchstmöglichen Kennzahl. Hier wird die Aufgabe elegant zu einem Rucksackproblem.

Hierbei entspricht die Anzahl der Kandidaten dem Gewicht des Goldbarrens, und der Recall der Methode entspricht seinem Wert. Es gibt jedoch noch zwei weitere Aspekte, die bei der Implementierung des Algorithmus berĂŒcksichtigt werden sollten:
- Bei den Methoden kann es zu Ăberschneidungen bei den Kandidaten kommen, die sie extrahieren.
- In einigen FĂ€llen kann es sinnvoll sein, eine Methode zweimal mit unterschiedlichen Parametern anzuwenden, und die Kandidaten aus der ersten Methode sind kein Untermenge der zweiten.
Wenn man zum Beispiel die Implementierung der Methode "Was bereits gekauft wurde" mit unterschiedlichen Intervallen zur Extraktion verwendet, werden ihre Mengen an Kandidaten ineinander verschachtelt sein. Gleichzeitig liefern unterschiedliche Parameter bei "periodischen KĂ€ufen" jedoch kein vollstĂ€ndiges Ăberlappen. Daher teilen wir die Sampling-Methoden mit unterschiedlichen Parametern in Blöcke auf, sodass wir aus jedem Block nicht mehr als einen Extraktionsansatz mit bestimmten Hyperparametern ĂŒbernehmen möchten. Dazu muss man bei der Implementierung des Rucksackproblems etwas tricksen, aber die Asymptotik und das Ergebnis bleiben davon unberĂŒhrt.
Diese geschickte Kombination ermöglicht es uns, die folgenden Kennzahlen im Vergleich zu einfachen kollaborativen Modellen zu erhalten:

Bei den finalen Kennzahlen sehen wir folgendes Bild:

Hierbei ist jedoch zu beachten, dass ein Punkt fĂŒr Empfehlungen, die fĂŒr Unternehmen nĂŒtzlich sind, noch nicht abgedeckt ist. Derzeit haben wir einfach gelernt, genau vorherzusagen, was der Benutzer beispielsweise in der nĂ€chsten Woche kaufen wird. Doch einfach Rabatt auf das zu geben, was er sowieso kaufen wird, ist nicht besonders spannend. Viel spannender ist es, das Erwartungswert zu maximieren, beispielsweise der folgenden Kennzahlen:
- Die Marge/Umsatz durch personalisierte Empfehlungen.
- Der durchschnittliche Einkaufswert der Kunden.
- Die BesuchshÀufigkeit.
So, we multiply the obtained probabilities by different coefficients and rearrange them so that products influencing the metrics more are at the top. There is no ready-made solution for which approach is better to use. Even we experiment with such coefficients directly in production. But here are some interesting techniques that most often yield the best results for us:
- Multiply by the price/margin of the product.
- Multiply by the average check in which the product appears. This way, products that are usually bought together with something else will rise to the top.
- Multiply by the average visit frequency of customers for this product, based on the hypothesis that this product encourages them to return more often.
After experimenting with the coefficients, we obtained the following metrics in production:

Hier overall products conversion â the share of purchased products from all products in the recommendations that we generated.
The attentive reader will notice a significant difference between offline and online metrics. This behavior can be explained by the fact that not all dynamic filters for products recommended can be taken into account when training the model. It is normal for us that half of the extracted candidates can be filtered out; this specificity is characteristic of our industry.
In terms of revenue, the following story emerges; it is clear that after launching the recommendations, the revenue for the test group significantly increases, and currently, the average increase in revenue with our recommendations is 3-4%.

In conclusion, I want to say that if you need non-realtime recommendations, there is a significant increase in quality when experimenting with candidate extraction for recommendations. A lot of time spent on their generation allows for the combination of many good methods, which together produce great results for the business.
I would be happy to discuss in the comments with anyone who finds the material interesting. Questions can be directed to me personally at . I also share my thoughts on AI/startups in my â welcome đ
Quelle: habr.com
