Jak zbudować wydajny system rekomendacji produktów?

Zazwyczaj wiemy co kupują nasi obecni klienci. Jakie artykuły kupili, kiedy je kupili, jakie kategorie kupują najczęściej, jakiej wartości były to zakupy. Jednak dużo trudniej znaleźć odpowiedź co mogli by jeszcze od nas kupić. Najczęstszą próbą poszukiwania odpowiedzi na to pytanie jest segmentacja/klasyfikacja klientów; klienci o podobnych cechach mają zbliżone potrzeby zakupowe. W tym artykulu stworzymy system rekomendacji produktów bazujący na zakupach osób które dokonały podobnych wyborów jak klient któremu będziemy rekomendować produkty (collaborative filtering). Nasz system z dużą skutecznością podpowie produkty, którymi naprawdę może być zaintersowany nasz klient.

Ten system rekomendacji może zostać wykorzystany do różnych celów. Możemy tworzyć rekomendacje w sklepie internetowym. Możemy użyć go do budowy grupy odbiorców naszego e-mailingu lub wykorzystywać ten mechanizm do budowy ofert, w programie lojalnościowym czy tematu do rozmów na temat zakupów klienta.

Do budowy tego systemu użyjemy ogólnie dostępnego, darmoweo oprogramowania - jego wydajność zapewni wsparcie nawet dużych firm.

Nasz system bedzie składac się z dwóch warstw.  Do przechowywania historii zakupów, relacji między klientami a produktami i do propozycji rekomendacji użyjemy oprogramowania które jest stworzone do takich celów - grafowej bazy danych. Do przygotowania danych, ich załadowania do bazy oraz jako interfejsu naszych zapytań i ich łatwej interpretacji użyjemy ETL.

Przygotowujemy grafową bazę danych

Do przechowywania historii zakupów naszych klientów, relacji między klientami a produktami i finalnej rekomendacji użyjemy Neo4j. Program, Community Server, pobieramy ze strony neo4j.com. Następnie uruchamiamy odpowiednią dla naszego systemu operacyjnego wersję. W naszym przypadku Neo4j uruchomione będzie w Windows.

Uruchomienie bazy danych

Po pobraniu pliku zip rozpakowujemy prgram do docelowego katalogu. Następnie w katalogu neo4j\neo4j-community-X.X.X\bin wykonujemy komendę (instalujemy serwis w Windows):

neo4j.bat install-service

Neo4j wymaga Javy w wersji 11. Jeśli mamy niższa wersję, nie uruchomi się i może wyświetlić błąd:

WARNING: ERROR! Neo4j cannot be started using java version 1.8.0_181
WARNING: * Please use Oracle(R) Java(TM) 11, OpenJDK(TM) 11 to run Neo4j Server.

Jeśli instalacja serwisu powiedzie się, zobaczymy komunikat 'Neo4j service installed'.

By uruchomić serwis, domyślnie wyłączony, wchodzimy do okna zarządzania serwisami i szukamy serwisu nazwanego 'Neo4j Graph Database - neo4j'. Uruchamiamy go. Następnie w przeglądarce wpisujemy adres:

http://localhost:7474

Domyślny użytkownik i hasło to 'neo4j' (hasło takie samo jak nazwa użytkownika). Przy pierwszym logowaniu jesteśmy proszeni o zmianę hasła. Wprowadzamy sobie znane. Et voila, mamy działającą bazę Neo4j!

Uruchomienie ETL

Przygotowanie danych, ładowanie danych, tworzenie zapytań do bazy i interpretacja wyniku będzie zdecydowanie łatwiejsza i szybsza w narzędziu wyposażonym w GUI. Co więcej, możemy w łatwy sposób zautomatyzować te czynności. Użyjemy do tego celu Pentaho.

Wersje community Pentaho PDI (darmowa wersja nazywa sie Kettle) pobieramy ze strony Hitachi Vantara w sekcji 'Data Integration' (nie trzeba się rejestrować by pobrać plik). Pobrany zip rozpakowujemy do docelowego katalogu, np C:\Pentaho. Obecna wersja Pentaho funkcjonuje poprawnie tylko z wersją Java 8. Mamy tu zatem konflikt z Neo4j. By zarządzić różnymi wersjami Java dla Neo4j i Kettle (Pentaho) tworzymy zmienna środowiskową PENTAHO_JAVA która odwołuje się do wersji 8 Javy. JAVA_HOME, wykorzystywana przez Neo4j, prowadzi do wersji 11.

Mając gotowe środowisko, uruchamiamy program:

pdi-ce-9.0.0.0-423\data-integration\spoon.bat

Pentaho Kettle pobrane ze strony Hitachi Vantara nie zawiera modułów komunikacji z Neo4j. Musimy je rozszerzyć o plugin który nam to umożliwi. Aktualna wersję pobieramy z Github i rozpakowujemy w katalogu 'plugins'. W wyniku tego powinniśmy mieć katalog 'plugins\Neo4JOutput' lub podobny. Restartujemy Pentaho. Jeśli plugin został pomyślnie uruchomiony w menu Pentaho pojawi się 'Neo4j' (górna belka).

Łączymy się z Neo4j

Z menu 'Neo4j' wybierz 'create connection' i wpisz niezbędne dane do połączenia z bazą:

My korzystamy z Neo4j które rezyduje na serwerze. Jeśli zainstalowałeś(aś) bazę na swoim PC, zmień adres na 'localhost'. OK, mając wpisane dane logowania przed zapisaniem wciśnij 'Test'. Jęli dane logowania są poprawne, ujrzysz komunikat 'Connection successful!'. Zapisz i zamknij okno.

Przygotowujemy dane

W pentaho tworzymy transformacje i zadania. Transformacja to sieć logicznych zadań zwanych krokami. Transformacje to zasadniczo przepływy danych. Nazwy plików transformacji mają rozszerzenie '.ktr'. Często transformacje zawierają tak dużo kroków iż uzasadniony jest podział na logiczne części - wtedy łączymy je w zadanie. Dobrym pomysłem jest odpowiednia organizacja struktury Twoich plików; transformacji, zadań, plilków z danymi, rezultatów obliczeń Pentaho. Utwórz katalog 'Workspace' w ktorym będą znajdować się projekty. I tak na potrzeby tego projektu stwórzmy tam folder 'Neo4j_recommendations'. W tym katalogu utwórz dwa podkatalogi; 'in' (na dane wejściowe) i 'out' (na rezultaty).

OK, przygotowujemy dane. W tym ćwiczeniu korzystamy z plilków. Ale oczywiście możesz pobierać dane z bazy danych lub innego źródła. Utworzymy trzy transformacje; ładującą klientów do bazy, ładującą produkty, tworzącą relacje między klientami a produktami.

Tworzenie indeksów

Zanim zaczniesz tworzyć węzły dla klientów, produktów i relacje między nimi, utwórz indeksy w bazie danych [czytaj więcej]. Indeksy będą mieć pozytywny wpływ na szybkość wyszukiwania jednak mają zazwyczaj negatywny wpływ na prędkość zapisu. Dlatego twórz indeksy tam gdzie jest to naprawdę wymagane. W naszej bazie utworzymy indeksy dla client_id i product_it:

CREATE INDEX client_id FOR (n:client)
ON (n.client_id)

CREATE INDEX product_id FOR (p:product)
ON (p.product_id)

Możesz sprawdzić utworzone właśnie indeksy poleceniem:

CALL db.indexes

Jeśli chcesz zmienić nazwę indeksu, skasuj go poleceniem jak niżej i utwórz raz jeszcze:

DROP INDEX ON :product(product_id)

Utwórzmy klientów w bazie

Nasza pierwsza transformacja tworzyć będzie klientów w bazie i będzie wyglądać następująco:

Text file input => Add constants => Neo4j Output

Tworzenie transformacji rozpoczynamy od wybrania File=>New=>Transformation (lub kliknięcia ikony + na belce z narzędziami). Nastepnie na przestrzeń transformacji przeciągamy 'kroki' z menu 'Design'. W polu 'Search' zacznij pisać 'text'. Wyświetlą się wszystkie kroki zawierające to słowo. Wybierz 'Text file input' i przeciągnij ten krok do przestrzeni transformacji (lub po prostu kliknij dwukrotnie na ikonę - przeskoczy samodzielnie do przestrzeni transformacji). Następnie wybierz kroki jak wyżej. Połącz je. Przykład pliku z klientami:

client_id;client_name    
100;Noah
101;Liam
102;Emma
103;Olivier
104;Dorian
105;Victoria
106;Sophia

Drugi krok, 'Add constants' zawiera zmienną 'client' przechowującą nazwę węzła określiającego klienta. Ten węzeł nazywać się będzie ... 'client'. Ostatnim, trzecim, krokiem jest 'Neo4j Output'. Ten komponent połączy się z bazą i utworzy nody 'client'. Jego ustawienia wyglądają następująco:

 W polu 'Neo4j connection' wybierasz wcześniej zdefiniowane połączenie. W sekcji 'From label' definiujesz etyketę dla wezłów. Niżej określasz właściwości wezłów 'client' - 'client_id' oraz 'client_name'. Zapisz transformację - nazwij ją np. 'clients.ktr'. OK, uruchm ją klikając na strzałkę 'run'. Jeśli wszystko poszło dobrze, masz utworzonych klientów w Neo4j! Sprawdźmy to!

Sprawdzanie wykonania transformacji

W przegladarce wpisz 'localhost:7474/browser/' i zaloguj się do konsoli Neo4j. W konsoli wpisz:

MATCH (n)
RETURN n

W rezultacie wykonania tych polecen zobaczysz 'kuleczki' pięciu węzłów; pieciu klientów ktorych przesłaliśmy z pliku do Neo4j. Klikając na nie zobaczysz ich właściwości; każdy ma unikalny client_name i client_id. Węzły sa wolnymi elektronami. Nic je nie łączy. Dodajmy teraz produkty.

Dodawanie produktów do bazy

Nasza transformacja będzie rónież zawierać trzy kroki:

Text file input => Add constants => Neo4j Output

Pierwszy krok odczytuje dane o produktach. Jego przykładowa zawartość:

product_id;product_name
200;Webcam
201;SD card
202;Wireless mouse
203;Mouse pad
204;USB 3.0 hub
205;USB headset
206;Speakers
207;Gaming microphone

Ostatni krok tej transformacji, Neo4J Output, zapisuje dane do bazy. Jak w w transformacji przesyłającej klientów tak i tutaj ustaw nazwę dla wezła, 'product', oraz właściwości produktów; 'product_id' oraz 'product_name'. Zapisz transformację (nazwijmy ją 'products.ktr') i wykonaj ją. OK, mamy dodane węzły produktów. Jeśli w konsoli Neo4j wykonasz ponownie zapytanie jak poprzednio, zobaczysz więcej 'kuleczek' w dwóch kolorach. Nadal są to wolne elektrony. Nic tych węzłów nie łączy.

Dodajmy relacje między klientami a produktami

W naszej trzeciej transformacji połączymy wezły 'client' z węzłami 'product'. Nasza transformacja będzie składać się z trzech kroków:

Text file input => Add constants => Neo4j Cypher

Przykładowa zawartość pliku ze sprzedażą; kto kupił co:

client_id;client_name;product_id;product_name;purchase_date
100;Noah;200;Webcam;2020-01-01
100;Noah;201;SD card;2019-01-01
100;Noah;206;Speakers;2019-01-01
101;Liam;200;Webcam;2000-02-02
101;Liam;201;SD card;2012-04-01
101;Liam;202;Wireless mouse;2020-01-01
101;Liam;204;USB 3.0 hub;2020-01-01
102;Emma;200;Webcam;2020-01-01
102;Emma;202;Wireless mouse;2020-01-01
102;Emma;205;USB headset;2020-01-01
103;Olivier;203;Mouse pad;2020-01-01
103;Olivier;204;USB 3.0 hub;2020-01-01
104;Dorian;203;Mouse pad;2020-01-01
105;Victoria;205;USB headset;2020-01-01
106;Sophia;206;Speakers;2020-02-15
105;Victoria;207;Gaming microphone;2020-03-15

Ostatni krok to komponent 'Neo4j Cypher'. By powiązać węzły 'client' z węzłami 'product' możemy użyć kroku 'Neo4j Output'. Jednak by utworzyć dokładniejszy mechanizm rekomendacji, chcemy znać iloć zakupów każdego z produktów. Dlatego też relacja 'PURCHASED' między klientem a produktem bedzie mieć właściwość 'no_of_purchases' przechowując ilość zakupów takiego artykułu przez każdego z klientów. Artykuły z większą częstotliwością zakupów będą promowane. Zapytanie tworzące relacje będzie mieć postać: 

MATCH (n:client)
WITH n
MATCH (p:product)
WHERE n.client_id = id_klienta AND p.product_id = id_produktu
MERGE (n)-[r:PURCHASED]->(p)
ON CREATE SET r.no_of_purchases = 1
ON MATCH SET r.no_of_purchases = r.no_of_purchases + 1

Za każdym razem kiedy będziemy dogrywać nowe informacje o sprzedaży do bazy danych, właściwość 'no_of_purchases' będzie powiększana o ostatnio dokonane zakupy.

Jeśli chcemy sprawdzić jak wygląda drzewo klientów i powiązanych z nimi produktów wykonajmy polecenie:

MATCH (n)
RETURN n

Uruchamiamy silnik rekomendacji produktów

Chcemy się dowiedzieć co innego kupili klienci którzy nabyli takie same produkty jak nasz klient. Mają prawdopodobnie podobne potrzeby i zainteresowania wiec na przykładzie ich historii zakupów możemy zaproponować produkty naszemu klientowi. W systemach rekomendacji produktów nazywa się to Collaborative Filtering - rekomendujemy produkty klientów którzy mają takie same lub podobne preferencje. Listę proponowanych produktów uzyskamy zapytaniem:

MATCH (n:client {client_id: 100})-[:PURCHASED]->(p:product)<-[:PURCHASED]-(n2:client)
WITH n2, collect(p) as productsInCommon
WHERE size(productsInCommon) >= 2
OPTIONAL MATCH (n2)-[:PURCHASED]->(p2:product)
WHERE NOT p2 IN productsInCommon
RETURN DISTINCT p2.product_id, p2.product_name

W naszym zapytaniu użyliśmy warunku 'WHERE size()' by pominąć zakupy/rekomendacje tych klientów którzy mają tylko jeden wspólny produkt. Taka rekomendacja miałaby niską, przypadkową wiarygodność.

Powyższe zapytanie zwróci listę produktów w formie tabelarycznej w konsoli Neo4j. Produkcyjnie, na potrzeby innych systemów, potrzebować będziesz danych w innej postaci. W Pentaho zbudować można tranformację która wykona zapytanie i sparsuje odpowiedź do porządanej postaci - może to być plik CSV, plik Excel'a lub zapisanie danych do tabeli bazy danych.

Doskonalenie i filtrowanie rekomendacji

Tworząc relację między klientem a produktem przy ładowaniu danych do Neo4j dodaliśmy do relacji informacje o ilości zakupów danego produktu przez klienta. Tę właściwość możemy użyć przy tworzeniu listy rekomendacji - im wyższa częstotliwość zakupu produktu X, tym wyższa pozycję powinien zająć na liście rekomendacji. Historię podobnych zakupów możemy połączyć z historią przeglądania lub lajkowania.

Rzadko tak uzyskaną listę sugerowanych produktów zaproponujemy klientowi wprost. Najczęściej lista ta poddana zostanie manipulacji. Produkty z listy mogą zostać zastąpione innymi, podobnymi produktami (np. z wyższą marzą). Niektóre pozycje mogą zostać usunięte z listy gdyż wiemy, z wyniku klasyfikacji, że klient nie będzie zainteresowany takim produktem - nie ma takich potrzeb.

Dlaczego warto rekomendować produkty klientom?

Według serwisu Barilliance podczas nastepnych wizyt klientów na stronie dodają oni do koszyka 65,16% więcej pozycji niż odwiedzając stronę po raz pierwszy, wydają o 16,15% więcej na każde zakupy.
Powód jest prosty. Nie wiesz, co lubią odwiedzający po raz pierwszy, co utrudnia tworzenie odpowiednich ofert. Wg serwisu invespcro, 59% klientów preferuje zakupy na personalizowanych stronach. Wg firmy Accenture 91% konsumentów chętniej robi zakupy u marek, które rozpoznają, zapamiętują i dostarczają trafne oferty i rekomendacje.
83% konsumentów jest skłonnych udostępniać swoje dane, aby umożliwić im spersonalizowane doświadczenie.