Neo4j - wyszukiwanie podobnych fraz w bazie

Czasami w projekcie mamy potrzebę przeszukania zbioru i znalezienia podobnej frazy/zwrotu. Np. chcemy sprawdzić czy dane które wpisuje klient są poprawne a nie możemy zastosować zamkniętego słownika bo napewno nie obejmie wszystkich potencjalnych nazw. Innym przykładem może być znalezienie podobnego produktu w bazie danych; klient zwrócił się do nas z prośbą o wycenę produktu określonego przez niego jako 'Juoksentelisinkohan Hyppytyynytyydytys'. Szukamy w naszym katalogu produktów czy mamy taki lub podobny produtk. Jeśli nasza baza produktów zawiera więcej niż 100 tysięcy produktów, będzie nam bardzo trudno przeszukać ją ręcznie. Tym bardziej że produkt ''Juoksentelisinkohan Hyppytyynytyydytys'' to tylko jeden ze 150 o których wycenę poprosił klient. Potrzebujemy oprogramowania które wykona tę pracę za nas.

Istnieje całkiem sporo oprogramowania i algorytmów które przeznaczone są do takich właśnie funkcji. Np. wyszukiwanie 'full text' baz danych. Rezultaty są jednak kiepskie. Takie wyszukiwanie zwróci nam jedynie frazy które zawierają część wyszukiwanego słowa/słów. Full text jest zupełnie nieodporny na różny szyk zdania lub znaki narodowe. Istnieją algorytmy jak levenshtein ale i tutaj mamy podobny problem - ten algorytm nie radzi sobie zupełnie z przestawionym szykiem wyrazów. Mamy w końcu gotowe biblioteki jak choćby biblioteka dla Javascript, fuzzyset, która daje bardzo dobre rezultaty ale przeszukanie bazy danych z kilkuset tysiącami produktów jest bardzo problematyczne.

Istnieje jednak produkt który wydaje się oferować zadawalającą jakość i szybkość. Tym oprogramowaniem jest Neo4j.

Przygotowujemy strukturę bazy

Przygotujemy strukturę grafowej bazy danych która będzie przechowywać opisy produktów. Zaczniemy od stworzenia indeksów.

Tworzenie indeksów

Indeksy w grafowych bazach danych nie przyśpieszają wyszukiwania danych. Ich głowna rola to zapewnienie spójności bazy struktury bazy. Komendą poniżej utworzymy contraint który zapewni nam unikalność produktów w bazie. Ten 'constraint' to odpowiednik unikalnego klucza w relacyjnej badzie danych. Constraints, "ograniczenia", to reguły w kolumnach/polach tabel w bazach dancyh. Służą one do narzucania typu, formatu danych, które mogą zostać umieszczone w tabeli. Zapewnia to dokładność i wiarygodność danych w bazie danych. Zauważ jednak że bazy Neo4j nie posiadają tabel. Mowa o relacyjnych bazach danych jednak rola "constraints" jest identyczna.

CREATE CONSTRAINT product_id ON (p:product) ASSERT p.product_id IS UNIQUE

Sprawdzamy utworzony indeks:

CALL db.indexes

Gdybyśmy z jakichś względów chcieli skasować właśnie utworzony indeks, możemy to zrobić komendą:

DROP CONSTRAINT ON (p:product) ASSERT p.product_id IS UNIQUE

Ładowanie artykułów

Poniższa komenda zapewni nam dodatkowe sprawdzenie czy artykuł już istnieje w bazie. Jeśli tak, nie utworzy go ponownie:

MERGE (p:product { product_id: 100, product_name: "opis artykułu" })

Istnienie prdoduktó w grafowej bazie możemy w sprawdzić komendą:

MATCH (n)
RETURN n

Tworzenie indeksu fulltext

Teraz tworzymy indeks „ProductNameIndex”, który zawiera węzły z etykietą 'product' i jednym atrybutem ('product_name'). Pierwszym parametrem jest nazwa indeksu. Drugi parametr definiuje etykiety węzłów, a trzeci parametr atrybuty, które chcemy indeksować. Zauważ że mamy tam jeden atrybut węzła 'product' choć możemy ich wstawić kilka.

CALL db.index.fulltext.createNodeIndex("ProductNameIndex",["product"],["product_name"])

Znajdź podobne produkty

Silnikiem wyszukiwania FullText w Neo4j jest Lucene. Zapoznaj się ze składnią tych zapytań by budować bardziej precyzyjne wyszukiwania.

CALL db.index.fulltext.queryNodes("ProductNameIndex", "product_name: yppytyynytyydytys") YIELD node, score
RETURN node.product_name as opis, score

Rezultatem tego zapytania będzie lista składająca się z dwóch kolumn; nazwy produktu i scoring. Punktacja Lucene wykorzystuje kombinację modelu przestrzeni wektorowej (VSM) wyszukiwania informacji i modelu boolowskiego określenia, na ile dany dokument jest istotny dla zapytania użytkownika. Ogólnie rzecz biorąc, idea VSM polega na tym, że im więcej razy termin zapytania pojawia się w dokumencie w stosunku do liczby razy, gdy termin pojawia się we wszystkich dokumentach w kolekcji, tym bardziej dokument ten jest odpowiedni dla zapytania.

Zwiększenie precyzji wyszukiwania

Składnie zapytań silnika Lucene znajdziesz pod linkiem. Podstawowe pojęcia z którymi musisz się zapoznać:

  • Pojedynczy termin to pojedyncze słowo, na przykład „test” lub „witaj”
  • Fraza to grupa słów w podwójnych cudzysłowach, np. „Witaj dolly”

Modyfikatory terminów

Lucene obsługuje różne modyfikatory terminów, które w razie potrzeby dodają elastyczności lub precyzji wyszukiwania. Modyfikatory te obejmują symbole wieloznaczne, znaki „rozmyte” lub bardziej ogólne, i tak dalej. 

Fuzzy Searches

Dodanie tyldy (~) do słowa spowoduje użycie algorytmu Damerau-Levenshtein. Użyj symbolu tyldy ~ na końcu pojedynczego wyrazu. Na przykład, aby wyszukać hasło o pisowni podobnej do „roam”, użyj wyszukiwania rozmytego (fuzzy):

  roam~

To wyszukiwanie będzie pasować do takich haseł, jak roams, foam i foams. Dopasuje również samo słowo "roam".

Wyszukiwanie zbliżeniowe

Wyszukiwanie zbliżeniowe wyszukuje terminy, które znajdują się w określonej odległości od siebie.

Aby przeprowadzić wyszukiwanie zbliżeniowe, dodaj znak tyldy ~ i wartość liczbową na końcu wyszukiwanej frazy. Na przykład, aby wyszukać „apache” i „jakarta” w dokumencie w obrębie 10 słów, użyj funkcji wyszukiwania:

  „jakarta apache” ~ 10

Odległość, o której tu mowa, to liczba ruchów terminów potrzebnych do dopasowania określonej frazy. W powyższym przykładzie, jeśli „apache” i „dżakarta” były oddalone od siebie o 10 pól w polu, ale „apache” pojawił się przed „jakarta”, potrzeba więcej niż 10 ruchów terminów, aby przesunąć terminy razem i ustawić „apache” na na prawo od „dżakarty” z odstępem pomiędzy.

Wyszukiwania wystąpienia

Wyszukiwanie istnienia pola pasuje do wszystkich dokumentów, w których istnieje wartość dla tego pola. Aby zapytać o istniejące pole, po prostu użyj symbolu wieloznacznego zamiast terminu w wyszukiwaniu.

  pole:*

Pole zostanie uznane za „istniejące”, jeśli ma jakąkolwiek wartość, nawet te, które są często uważane za „nieistniejące”. (np. NaN, „” itp.)

Więcej o skłądni Lucene znajdziesz w dokumentacji. Grafowa baza danych Neo4j zwraca rezultaty niezwykle szybko - milisekudny. Silnik nadaje się do użycia w wszelkimi aplikacjami biznesowymi.