Czym jest Project Maven armii amerykańskiej? (Big Data at War)

Czym jest Project Maven armii amerykańskiej? (Big Data at War)

USA kojarzą nam się z potęgą zarówno technologiczną jak i militarną. Nie bez powodu. To tutaj zrodziła się branża Big Data. To ten kraj ma najpotężniejszą armię na świecie. Pytanie jednak, czy zawsze te dwie rzeczy idą w parze? Dziś poznamy jeden z przykładów tego jak Big Data i sztuczna inteligencja (AI) wykorzystywane są w amerykańskiej armii. Bierzmy więc kubek żołnierskiej czarnej kawy w dłoń i przejdźmy przez drugi odcinek z serii “Big Data na wojnie”!

Drony, dominacja USA i… absurdy rodem z parodii państwowości

Hegemonia zobowiązuje

USA to nie jest “normalny kraj”. Nie, nie mam na myśli tego, że to stan umysłu. Nie należy jednak porównywać jakiegokolwiek państwa do Amerykanów z jednego prostego powodu: Stany Zjednoczone rządzą światem. To imperium, które ustawiło pod siebie cały glob. Teraz co prawda ulega to pewnym zmianom, ale to rozmowa na inny artykuł. Na innym blogu;-).

Skutkuje to nie tylko profitami, ale i zobowiązaniami. Podstawowym zobowiązaniem jest to, że Amerykanie muszą militarnie “obstawiać” cały świat. Oznacza to nie tylko obecność sił zbrojnych na określonych terenach, ale także stały monitoring miejsc, w których Hegemon ma swoje interesy. W siłach zbrojnych Stanów Zjednoczonych służy ok. 1.3 mln żołnierzy nie licząc rezerwistów oraz Gwardii Narodowej (mniej więcej odpowiednik naszych Wojsko Obrony Terytorialnej). Każdy żołnierz kosztuje swoje i jego wyszkolenie oraz – co jasne – życie, jest na wagę złota.

Z tego powodu wojska amerykańskie od dłuższego czasu prowadzą wiele bardzo intensywnych prac badawczych (których skutkiem jest m.in. Internet) mających na celu rozwój nowoczesnych technologii czy robotyzację pola walki. Jednym ze skutków takich prac są drony. To samoloty bezzałogowe, które nie tylko są tańsze w produkcji od myśliwców i prostsze w użyciu. Co najważniejsze – pilot drona nie jest bezpośrednio narażony, siedzi w ciepłym i przyjemnym zakątku, sterując przez komputer swoją maszyną.

Monitoring najważniejszych miejsc na świecie

To właśnie drony są wykorzystywane do uderzenia z powietrza. Pełnią jednak także inną rolę – zwiadowczą. Dzięki nim można “podglądać” w bardzo solidnej jakości wiele miejsc na Ziemi. Poza stałym monitoringiem, zapisy z latających czujników stanowią znakomity materiał do analizy wstecznej. Jeśli dochodzi do jakiegoś ataku terrorystycznego czy podejrzanych ruchów, można bez problemu odszukać nagranie z tamtego momentu i przeanalizować minuta po minucie, sekunda po sekundzie.

Także Polska może pochwalić się sukcesami na rynku dronowym. Widoczny na zdjęciu FlyEye to polski samolot bezzałogowy służący celom zwiadowczym. Niezwykle popularny na wojnie za naszą wschodnią granicą – w służbie armii Ukraińskiej oczywiście.

Pentagon wydał dziesiątki miliardów dolarów na tego typu systemu obserwacji. Dzięki temu, jeśli ktokolwiek podłożył bombę, można po prostu przewinąć wideo do tyłu i sprawdzić kto to był, dokąd poszedł itd. Tego typu system obserwacji to fascynujący pomysł, dający gigantyczne możliwości analityczne. Skoro więc mamy całą flotę dronów uzbrojonych w czujniki, nadających do nas obrazy wideo, co jest oczywistym kolejnym krokiem? Warto wskazać, że pojedyncze drony z tego typu czujnikami gromadzą wiele terabajtów danych… dziennie.

W związku z powyższym nie będzie chyba zbyt kontrowersyjne stwierdzenie, że oczywistym jest zbudowanie systemu który tego typu dane kataloguje i automatycznie analizuje prawda?

Biurokratyczne absurdy także za oceanem

Otóż, jak się okazuje – niekoniecznie. Żaden inteligentny system nie powstał. Co prawda był pewien storage, w którym umieszczano nagrane wideo. Znakomita większość jednak… nigdy nie była przeanalizowana. Jak to się mogło stać? Otóż, rozwiązaniem według DoD (Departamentu Obrony – ang. Department of Defense) wcale nie było zbudowanie inteligentnego systemu analitycznego, ale… utworzenie odpowiednio dużego sztabu ludzi. Ludzi, którzy siedzieli przez 24h na dobę (praca zmianowa), patrzyli w ekran i… liczyli. Liczyli auta, ludzi, budynki itd. Następnie sprawnie tego typu dane przepisywali do… excela lub powerpointa i wysyłali dalej.

Brzmi absurdalnie? Takie właśnie jest! I dzieje się to w Stanach Zjednoczonych Ameryki. Nie w małej firmie pod Białymstokiem, ale w najpotężniejszym kraju na świecie, o ugruntowanej państwowości

Project Maven, czyli jak wynieść organizację na wyższy poziom

Aby powyższy stan rzeczy zakończyć, podjęta została decyzja o zbudowaniu “Project Maven”. To repozytorium, które miało stać się systemem do inteligentnej analizy materiałów z czujników bezzałogowców. To jednak nie jest jedyna rola Mavena. Projekt ten miał w zamierzeniu stać się przyczółkiem dla metodycznego wykorzystania Big Data oraz sztucznej inteligencji (AI) w armii amerykańskiej. Chociaż USA są synonimem postępu i nowoczesności, w wojsku ciągle wiele elementów działało jak podczas II Wojny Światowej. Wpuszczenie systemów przetwarzania dużych danych miało to zmienić.

Maven skupia się na analizie danych wideo z różnych platform dronowych:

  • Scan Eagle
  • MQ-1C Gray Eagle
  • MQ-9 Reaper.

Podstawowym celem Projektu miała być automatyczna identyfikacja obiektów rejestrowanych przez kamery Dronów. Co warte podkreślenia – w tworzenie całego systemu zaangażowane były podmioty prywatne. Jedną z firm tworzących repozytorium było Google. Ciężko o bardziej trafną decyzję – to od tej firmy zaczęła się “prawdziwa Big Data” i spod jej skrzydeł wyszły niezwykle istotne technologie, będące właściwie fundamentem branży. W związku z ujawnieniem wewnątrz Google współpracy z Pentagonem, wybuchł protest pracowników. Ci rządali wycofania się z procesu, tłumacząc to niezgodnością z linią etyczną firmy i ich hasłem przewodnim “Don’t be evil” (nie bądź zły). To jednak zupełnie na marginesie.

Projekt Maven miał rozwiązać jeszcze jeden problem: biurokrację. Dotychczas jedynie ludzkie oko (i oczywiście mózg) były wyznacznikami tego czy cel widziany przez bezzałogowiec jest wrogiem, czy nie. W związku z tragicznymi pomyłkami, procedury dotyczące możliwości podjęcia ostrzału bardzo mocno spowolniły czas między obserwacją, a ogniem. Znakomicie wyuczone mechanizmy mają na celu przyspieszenie całego procesu.

Jak mógłby wyglądać “Project Maven”?

Na końcu proponuję zabawę. Skoro jesteśmy na blogu stricte poświęconym Big Data – spróbujmy zastanowić się jak mogłoby wyglądać Maven pod kątem technicznym, a przynajmniej architektonicznym – w najbardziej ogólnym rozumieniu tego słowa. Nie próbujemy domyślić się jak było to robione w Pentagonie, ale jak analogiczny system mógłby być zbudowany u nas, na potrzeby Wojska Polskiego.

Nasza specyfika jest oczywiście zupełnie inna. Nie musimy obserwować połowy świata. Załóżmy jednak, że chcemy bardzo precyzyjnie monitorować całą granicę wschodnią i otrzymywać alerty, jeśli coś niewłaściwego się tam dzieje. Ponieważ nasza granica jest dość długa, przygotujemy system który automatycznie powiadamia o podejrzanych ruchach oraz pozwala przeszukiwać informacje o aktualnym stanie oraz stanie z określonego momentu w historii.

Przykładowy slajd wysłany do zatwierdzenia w procesie uczenia oryginalnego projektu Maven.

Zastanówmy się więc jak to może wyglądać.

  1. Storage – tak nazwijmy ogólną część, w której składujemy dane.
  2. Moduł do uczenia
  3. System alertów
  4. Moduł do analizy

Jaka infrastruktura?

Zacznijmy od bardzo ważnej kwestii! Konkretnie od powiedzenia sobie wprost: taki system absolutnie nie może być zbudowany z wykorzystaniem rozwiązań chmurowych. Być może to kontrowersyjna teza, ale obecnie najwięksi dostawcy to firmy zagraniczne. W przypadku tego typu produktu podstawową cechą musi być bezpieczeństwo. Nie owijając w bawełnę – nie tylko bezpieczeństwo przed włamaniami rosyjskich hakerów. To są dane, które po prostu nie mogą być zależne od zagranicznej infrastruktury (nawet jeśli jest położona na terenie Polski). Rozumiem, że wiele osób może mieć odmienne zdanie, szanuję to, ale się z nim nie zgadzam. Zakładam więc budowę systemu na własnej infrastrukturze (on-premise).

Nic nie stoi jednak na przeszkodzie, abyś Ty rozpisał/a podobną architekturę dla rozwiązań chmurowych;-). Napisz i wyślij, a ja z pewnością opublikuję.

Storage

W tym miejscu musimy się zastanowić w jaki sposób składować dane. To tutaj będą trafiać w pierwszym kroku, ale nie tylko. Oczywiście taki moduł może składać się z więcej niż jednej technologii do składowania danych!

Co dokładnie moglibyśmy tutaj umieścić? Zacznijmy od pierwszej przestrzeni, gdzie lądować miałyby surowe dane. Proponuję tutaj jedną z dwóch technologii:

  1. HDFS, wraz z resztą technologii hadoopowych
  2. Ozone – czyli object store, który jest (teoretycznie) następcą HDFSa, przy czym pozbawiony jest jego wad. Na temat Ozone napisałem parę artykułów.

W takim miejscu możemy przede wszystkim składować wszystkie możliwe pliki wideo, które będą przesyłane przez drony. Następnie pliki te byłyby odczytywane i zapisywane do którejś bazy danych w formie metadanych (np. jakie obiekty są w jakim momencie nagrania, co to za samolot itd). Może to być HBase (który współgra zarówno z HDFS jak i z Ozone).

Moduł do uczenia

Oczywiście w “naszym Mavenie”  musimy mieć modele, dzięki którym będziemy mogli rozpoznawać konkretne obiekty, ludzi, broń itd. Aby to zrobić, musimy utworzyć moduł uczący. W jego ramach możemy zrobić tak jak w amerykańskim odpowiedniku – najpierw trzeba przejrzeć bardzo bardzo wiele materiałów, a następnie otagować co tam się znajduje, jak to wygląda itd. W kolejnym etapie utworzymy klasyczny zbiór uczący i testowy, a następnie wytrenujemy konkretne modele dzięki uczeniu nadzorowanemu (co możemy zrobić dzięki otagowanym materiałom).

Jakie technologie możemy tutaj zastosować? Możemy pójść w stronę wykorzystania bibliotek pythonowych – i wtedy próbować swoich sił z TensorFlow. Możemy także popracować z Apache Spark ML i deep learning, który oferują na coraz lepszym poziomie jego twórcy.

System Alertów

Następny moduł który powinniśmy omówić to system alertów. Chodzi o to, aby nasi żołnierze z Wojsk Obrony Cyberprzestrzeni nie ślęczeli przed widokami przekazywanymi z każdego z dronów, ale by byli powiadamiani o potencjalnych anomaliach zawczasu przez zautomatyzowany system.

Tutaj moja propozycja jest prosta:

  1. Kafka, na którą trafiają obrazy wideo
  2. Consumer przygotowany przez Spark Structured Streaming, który przetwarza te obrazy z wykorzystaniem wcześniejszych modeli i rozbiera je na części pierwsze (podobnie jak to się dzieje w punkcie pierwszym – Storage). Następnie, w formie lżejszych informacji (metadane) przesyła na kolejny endpoind kafki.
  3. Consumer znów przygotowany przez Spark Structured Streaming, ale nasłuchujący na drugim endpoincie – z metadanymi. Jeśli informacje, które się tam pojawią są podejrzane, wysyłany jest alert do przygotowanej aplikacji, przed którą siedzą nasi żołnierze WOC.

Moduł do analizy

Ostatnim elementem który został nam do ogrania jest moduł do analizy. tutaj pomijamy system streamingowy i niejako zataczamy koło, trafiając do naszego Storage. Z tego miejsca musimy zbudować job, który pozwoli nam sprawnie indeksować dane z bazy danych do technologii full-text search. Oto co proponuję:

  1. Spark, który wyciąga dane z HBase i umieszcza je (być może w odpowiednio okrojonej formie) w Elasticsearch
  2. Elasticsearch, który przechowuje dane.
  3. Kibana, która pozwala nam analizować dane umieszczone w Elasticsearch.

Podsumowanie

Oczywiście powyższe zalecenia to raczej intelektualna zabawa, nie poważna analiza i architektura. Służy jednak pobudzeniu myślenia o tym jak można patrzeć na systemy oraz wskazać, że nie są one poza naszym zasięgiem.

Podsumujmy: Amerykanie także mają swoje miejsca wstydu, które wyglądają jakby były rodem z II Wojny Światowej. Rozwiązaniem części z nich, oraz zalążkiem Big Data w Pentagonie, miał (ma) być “Project Maven” który byłby pewnym repozytorium materiałów dronowych. W jego ramach odbywałoby się uczenie i analiza obiektów widzianych przez bezzałogowce.

Jak pokazałem, my także możemy rozwijać nasze siły zbrojne w kontekście Big Data oraz AI. Mam nadzieję, że tak się dzieje – jednym z symptomów zmian jest utworzenie Wojsk Obrony Cyberprzestrzeni. Oby nie były to puste etaty, bo fachowców mamy w Polsce wspaniałych.

Daj znać w komentarzu jak się podobało. Zapraszam też na profil RDF na LinkedIn oraz do newslettera. Pozostańmy w kontakcie!

UWAGA! Już niedługo ukaże się pierwszy polski ebook o Big Data. Całkowicie za darmo dla subskrybentów bloga RDF. Zapisując się na newsletter TERAZ – masz niepowtarzalną okazję dostawać kolejne wersje książki i zgłaszać swoje poprawki, a nawet stać się jednym z autorów. Więcej tutaj.

 

Loading
Apache Spark: Jak napisać prosty mechanizm porównywania tekstów?

Apache Spark: Jak napisać prosty mechanizm porównywania tekstów?

Machine Learning w Sparku? Jak najbardziej! W poprzednim artykule pokazałem efekty prostego mechanizmu do porównywania tekstów, który zbudowałem. Całość jest zrobiona w Apache Spark, co niektórych może dziwić. Dzisiaj chcę się podzielić tym jak dokładnie zbudować taki mechanizm. Kubki w dłoń i lecimy zanurzyć się w kodzie!

Założenia

Jeśli chodzi o założenia, które dotyczą Ciebie – zakładam że umiesz tu Scalę oraz Sparka. Oba w stopniu podstawowym;-). W kontekście sparka polecam mój cykl “zrozumieć Sparka” czy generalnie wszystkie wpisy dotyczące tej technologii.

Jeśli chodzi o założenia naszego “projektu” – to są one dość proste:

  1. Bazujemy na zbiorze, który ma ~204 tysiące krótkich tekstów – konkretnie tweetów.
  2. Tweety dotyczą trzech dziedzin tematycznych:
    • COVID – znakomita większość (166543 – 81,7%)
    • Finanse – pewna część (28874 – 14,1%)
    • Grammy’s – margines (8490 – 4,2%)
  3. W ramach systemu przekazujemy tekst od użytkownika. W odpowiedzi dostajemy 5 najbardziej podobnych tweetów.

Pobieranie datasetów (wszystkie dostępne na portalu Kaggle): covid19_tweets, financial, GRAMMYs_tweets

Powiem jeszcze, że tutaj pokazuję jak zrobić to w prostej, batchowej wersji. Po prostu uruchomimy cały job sparkowy wraz z tekstem i dostaniemy odpowiedzi. W innym artykule jednak pokażę jak zrobić także joba streamingowego. Dzięki temu stworzymy mechanizm, który będzie nasłuchiwał i naprawdę szybko będzie zwracał wyniki w czasie rzeczywistym (mniej więcej, w zależności od zasobów – czas ocekiwania to kilka, kilkanaście sekund). Jeśli chcesz dowiedzieć się jak to zrobić – nie zapomnij zasubskrybować bloga RDF!

 

Loading

Spark MlLib

Zacznijmy od pewnej rzeczy, żeby nam się nie pomyliło. Spark posiada bibliotekę, która służy do pracy z machine learning. Nazywa się Spark MlLib. Problem polega na tym, że wewnątrz rozdziela się na dwie pod-biblioteki (w scali/javie są to po prostu dwa pakiety):

  1. Spark MlLib – metody, które pozwalają na prace operując bezpośrednio na RDD. Starsza część, jednak nadal wspierana.
  2. Spark Ml – metody, dzięki którym pracujemy na Datasetach/Dataframach. Jest to zdecydowanie nowocześniejszy kawałek biblioteki i to z niego właśnie korzystam.

Spark MlLib możemy pobrać z głównego repozytorium mavena tutaj.

Dodawanie dependencji jeśli korzystamy z Mavena (plik pom.xml):

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-mllib_2.12</artifactId>
    <version>3.0.0</version>
    <scope>provided</scope>
</dependency>

Oczywiście scope “provided” podajemy tylko w przypadku wysyłania później na klaster. Jeśli chcemy eksperymentować lokalnie, nie dodajemy go.

Dodawanie dependencji jeśli korystamy z SBT (plik build.sbt):

libraryDependencies += "org.apache.spark" %% "spark-mllib" % "3.0.0" % "provided"

Ta sama uwaga odnośnie “provided” co w przypadku mavena.

Spark NLP od John Snow Labs

Chociaż Spark posiada ten znakomity moduł SparkMlLib, to niestety brak w nim wielu algorytmów. Zawierają się w tych brakach nasze potrzeby. Na szczęście, luka została wypełniona przez niezależnych twórców. Jednym z takich ośrodków jest John Snow Labs (można znaleźć tutaj). Samą bibliotekę do przetwarzania tekstu, czyli Spark-NLP zaciągniemy bez problemu z głównego repozytorium Mavena

Dodawanie dependencji, jeśli korzystamy z Mavena (plik pom.xml):

<dependency>
    <groupId>com.johnsnowlabs.nlp</groupId>
    <artifactId>spark-nlp_2.12</artifactId>
    <version>3.3.4</version>
    <scope>test</scope>
</dependency>

Dodawanie dependencji jeśli korystamy z SBT (plik build.sbt):

libraryDependencies += "com.johnsnowlabs.nlp" %% "spark-nlp" % "3.3.4" % Test

Dane

Dane same w sobie pochodzą z 3 różnych źródeł. I jak to bywa w takich sytuacjach – są po prostu inne, pomimo że teoretycznie dotyczą tego samego (tweetów). W związku z tym musimy zrobić to, co zwykle robi się w ramach ETLów: sprowadzić do wspólnej postaci, a następnie połączyć.

Dane zapisane są w plikach CSV. Ponieważ do porównywania będziemy używać tylko teksty, z każdego zostawiamy tą samą kolumnę – text. Poza tą jedną kolumną dorzucimy jednak jeszcze jedną. To kolumna “category”, która będzie zawierać jedną z trzech klas (“covid”“finance”“grammys”). Nie będą to oczywiście klasy służące do uczenia, natomiast dzięki nim będziemy mogli sprawdzić potem na ile dobrze nasze wyszukiwania się “wstrzeliły” w oczekiwane grupy tematyczne. Na koniec, gdy już mamy identyczne struktury danych, możemy je połączyć zwykłą funkcją union”.

Całość upakowałem w metodę zwracającą Dataframe:

def prepareTwitterData(sparkSession: SparkSession): Dataset[Row] ={
  val covidDF: Dataset[Row] = sparkSession.read
    .option("header", "true")
    .csv("covid19_tweets.csv")
    .select("text")
    .withColumn("category", lit("covid"))
    .na.drop()
  val financialDF: Dataset[Row] = sparkSession.read
    .option("header", "true")
    .csv("financial.csv")
    .select("text")
    .withColumn("category", lit("finance"))
    .na.drop()

  val grammysDF: Dataset[Row] = sparkSession.read
    .option("header", "true")
    .csv("GRAMMYs_tweets.csv")
    .select("text")
    .withColumn("category", lit("grammys"))
    .na.drop()

  covidDF.union(financialDF)
    .union(grammysDF)
}

Przygotowanie tekstu do treningu

Gdy pracujemy z NLP, bazujemy oczywiście na tekście. Niestety, komputer nie rozumie tekstu. A co rozumie komputer? No jasne, liczby. Musimy więc sprowadzić tekst do poziomu liczb. Konkretnie wektorów, a jeszcze konkretniej – embeddingów. Embeddingi to nisko-wymiarowe reprezentacje czegoś wysoko-wymiarowego. W naszym przypadku będzie to tekst. Czym dokładnie są embeddingi, dobrze wyjaśnione jest na tej stronie. Na nasze, uproszczone potrzeby musimy jednak wiedzieć jedno: embeddingi pozwalają zachować kontekst. Oznacza to w dużym skrócie, że słowo “pizza” będzie bliżej słowa “spaghetti” niż słowa “sedan”.

Sprowadzanie do postaci liczbowej może się odbyć jednak dopiero wtedy, gdy odpowiednio przygotujemy tekst. Bardzo często w skład takiego przygotowania wchodzi oczyszczenie ze “śmieciowych znaków” (np. @, !, ” itd) oraz tzw. “stop words”, czyli wyrazów, które są spotykane na tyle często i wszędzie, że nie opłaca się ich rozpatrywać (np. I, and, be). Oczywiście może to rodzić różne problemy – np. jeśli okroimy frazy ze standardowych “stop words”, wyszukanie “To be or not to be” będzie… puste. To jednak już problem na inny czas;-).

Do przygotowania często wprowadza się także tokenizację, czyli podzielenie tekstu na tokeny. Bardzo często to po prostu wyciągnięcie wyrazów do osobnej listy, aby nie pracować na stringu, a na kolekcji wyrazów (stringów). Spotkamy tu także lemmatyzację, stemming (obie techniki dotyczą sprowadzenia różnych słów do odpowiedniej postaci, aby móc je porównywać).

W naszym przypadku jednak nie trzeba będzie robić tego wszystkiego. Jedyne co musimy, to załączyć DocumentAssembler. Jest to klasa, która przygotowuje dane do formatu zjadliwego przez Spark NLP.

Po zastosowaniu dostajemy kolumnę, która ma następującą strukturę:

root
 |-- document: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- annotatorType: string (nullable = true)
 |    |    |-- begin: integer (nullable = false)
 |    |    |-- end: integer (nullable = false)
 |    |    |-- result: string (nullable = true)
 |    |    |-- metadata: map (nullable = true)
 |    |    |    |-- key: string
 |    |    |    |-- value: string (valueContainsNull = true)
 |    |    |-- embeddings: array (nullable = true)
 |    |    |    |-- element: float (containsNull = false)

W naszym kodzie najpierw inicjalizujemy DocumentAssembler, wykorzystamy go nieco później. Przy inicjalizacji podajemy kolumnę wejściową oraz nazwę kolumny wyjściowej:

val docAssembler: DocumentAssembler = new DocumentAssembler().setInputCol("text")
      .setOutputCol("document")

Zastosowanie USE oraz budowa Pipeline

Jak już napisałem, my wykorzystamy Universal Sentence Encoder (USE). Dzięki tym embeddingom całe frazy (tweety) będą mogły nabrać konktekstu. Niestety, sam “surowy” Spark MlLib nie zapewnia tego algorytmu. Musimy tu zatem sięgnąć po wspomniany już wcześniej Spark NLP od John Snow Labs (podobnie jak przy DocumentAssembler). Zainicjalizujmy najpierw sam USE.

val use: UniversalSentenceEncoder = UniversalSentenceEncoder.pretrained()
      .setInputCols("document")
      .setOutputCol("sentenceEmbeddings")

Skoro mamy już obiekty dosAssembler oraz use, możemy utworzyć pipeline. Pipeline w Spark MlLib to zestaw powtarzających się kroków, które możemy razem “spiąć” w całość, a następnie wytrenować, używać. Wyjście jednego kroku jest wejściem kolejnego. Wytrenowany pipeline (funkcja fit) udostępnia nam model, który możemy zapisać, wczytać i korzystać z niego.

Nasz pipeline będzie bardzo prosty:

val pipeline: Pipeline = new Pipeline().setStages(Array(docAssembler, use))
val fitPipeline: PipelineModel = pipeline.fit(tweetsDF)

Gdy dysponujemy już wytrenowanym modelem, możemy przetworzyć nasze dane (funkcja transform). Po tym kroku otrzymamy gotowe do użycia wektory. Niestety, USE zagnieżdża je w swojej strukturze – musimy więc je sobie wyciągnąć. Oba kroki przedstawiam poniżej:

val vectorizedTweetsDF: Dataset[Row] = fitPipeline.transform(tweetsDF)
      .withColumn("sentenceEmbeddings", org.apache.spark.sql.functions.explode(col("sentenceEmbeddings.embeddings")))

Znakomicie! Mamy już tweety w formie wektorów. Teraz należy jeszcze zwektoryzować tekst użytkownika. Tekst będzie przechowywany w Dataframe z jednym wierszem (właśnie owym tekstem) w zmiennej sampleTextDF. Po wektoryzacji usunę zbędne kolumny i zmienię nazwy tak, aby było wiadomo, że te wektory dotyczą tekstu użytkownika, a nie tweetów (przyda się później, gdy będziemy łączyć ze sobą oba Dataframy).

val vectorizedUserTextDF: Dataset[Row] = fitPipeline.transform(sampleTextDF)
      .drop("document")
      .withColumn("userEmbeddings", org.apache.spark.sql.functions.explode(col("sentenceEmbeddings.embeddings")))
      .drop("sentenceEmbeddings")

Implementacja cosine similarity

Uff – sporo roboty za nami, gratuluję! Mamy już tweety oraz tekst użytkownika w formie wektorów. Czas zatem porównać, aby znaleźć te najbardziej podobne! Tylko pytanie, jak to najlepiej zrobić? Muszę przyznać że trochę czasu zajęło mi szukanie algorytmów, które mogą w tym pomóc. Finalnie wybór padł na cosine similarity. Co ważne – nie jest to żaden super-hiper-ekstra algorytm NLP. To zwykły wzór matematyczny, znany od dawna, który porównuje dwa wektory. Tak – dwa najzwyklejsze, matematyczne wektory. Jego wynik zawiera się między -1 a 1. -1 to skrajnie różne, 1 to identyczne. Nas zatem będą interesować wyniki możliwie blisko 1.

Problem? A no jest. Spark ani scala czy java nie mają zaimplementowanego CS. Tu pokornie powiem, że być może po prostu do tego nie dotarłem. Jeśli znasz gotową bibliotekę do zaimportowania – daj znać w komentarzu! Nie jest to jednak problem prawdziwy, bowiem możemy rozwiązać go raz dwa. Samodzielnie zaimplementujemy cosine similarity w sparku, dzięki UDFom (User Defined Function).

Najpierw zacznijmy od wzoru matematycznego:

{\displaystyle {\text{cosine similarity}}=S_{C}(A,B):=\cos(\theta )={\mathbf {A} \cdot \mathbf {B} \over \|\mathbf {A} \|\|\mathbf {B} \|}={\frac {\sum \limits _{i=1}^{n}{A_{i}B_{i}}}{{\sqrt {\sum \limits _{i=1}^{n}{A_{i}^{2}}}}{\sqrt {\sum \limits _{i=1}^{n}{B_{i}^{2}}}}}},}

Następnie utwórzmy klasę CosineSimilarityUDF, która przyjmuje dwa WrappedArrays (dwa wektory), natomiast zwraca zwykłą liczbę zmiennoprzecinkową Double. Wewnątrz konwertuję tablice na wektory, wykorzystuję własną metodę magnitude i zwracam odległość jednego wektora od drugiego.

Klasa CosineSimilarityUDF

import org.apache.spark.ml.linalg.{Vector, Vectors}
import org.apache.spark.sql.api.java.UDF2

import scala.collection.mutable

class CosinSimilarityUDF extends UDF2[mutable.WrappedArray[Float], mutable.WrappedArray[Float], Double]{
  override def call(arr1: mutable.WrappedArray[Float], arr2: mutable.WrappedArray[Float]): Double = {
    val vec1 = Vectors.dense(arr1.map(_.toDouble).toArray)
    val vec2 = Vectors.dense(arr2.map(_.toDouble).toArray)
    val mgnt1 = magnitude(vec1)
    val mgnt2 = magnitude(vec2)

    vec1.dot(vec2)/(mgnt1*mgnt2)
  }

  def magnitude(vector: Vector): Double={
    val values = vector.toArray
    Math.sqrt(values.map(i=>i*i).sum)
  }
}

Wykorzystanie Cosine Similarity – sprawdzamy podobieństwo tekstów!

Znakomicie – po utworzeniu tego UDFa, możemy śmiało wykorzystać go do obliczenia podobieństw między każdym z tweetów a tekstem użytkownika. Aby to uczynić, najpierw rejestrujemy naszego UDFa. Polecam to co zawsze polecam na szkoleniach ze Sparka – zrobić to zaraz po inicjalizacji SparkSession. Dzięki temu utrzymamy porządek i nie będziemy się martwić, jeśli w przyszłości w projekcie ktoś będzie również chciał użyć UDFa w nieznanym obecnie miejscu (inaczej może dojść do próby użycia UDFa zanim zostanie zarejestrowany).

val cosinSimilarityUDF: CosinSimilarityUDF = new CosinSimilarityUDF()
sparkSession.udf.register("cosinSimilarityUDF", cosinSimilarityUDF, DataTypes.DoubleType)

Wróćmy jednak na sam koniec, do punktu w którym mamy już zwektoryzowane wszystkie teksty. Najpierw sprawimy, że każdy tweet będzie miał dołączony do siebie tekst użytkownika. W tym celu zastosujemy crossjoin (artykuł o sposobach joinów w Sparku znajdziesz tutaj). Następnie użyjemy funkcji withColumn, dzięki której utworzymy nową kolumnę – właśnie z odległością. Wykorzystamy do jej obliczenia oczywiście zarejestrowany wcześniej UDF.

val dataWithUsersPhraseDF: Dataset[Row] = vectorizedTweetsDF.crossJoin(vectorizedUserTextDF)
val afterCosineSimilarityDF: Dataset[Row] = dataWithUsersPhraseDF.withColumn("cosineSimilarity", callUDF("cosinSimilarityUDF", col("sentenceEmbeddings"), col("userEmbeddings"))).cache()

Na sam koniec pokażemy 20 najbliższych tekstów, wraz z kategoriami. Aby uniknąć problemów z potencjalnymi “dziurami”, odfiltrowujemy rekordy, które w cosineSimilarity nie mają liczb. Następnie ustawiamy kolejność na desc, czyli malejącą. Dzięki temu dostaniemy wyniki od najbardziej podobnych do najmniej podobnych.

afterCosineSimilarityDF.filter(isnan(col("cosineSimilarity")) =!= true)
      .orderBy(col("cosineSimilarity").desc)
      .show(false)

I to koniec! Wynik dla hasła “The price of lumber is down 22% since hitting its YTD highs. The Macy’s $M turnaround is still happening” można zaobserwować poniżej. Więcej wyników – przypominam – można zaobserwować w poprzednim artykule;-).

Wyniki dla mechanizmu text similarity w Apache Spark.

Podsumowanie

Mam nadzieję, że się podobało! Daj znać koniecznie w komentarzu i prześlij ten artykuł dalej. Z pewnością to nie koniec przygody z Machine Learning w Sparku na tym blogu. Zostań koniecznie na dłużej i razem budujmy polskie środowisko Big Data;-). Jeśli chcesz pozostać z nami w kontakcie – zapisz się na newsletter lub obserwuj RDF na LinkedIn.

Pamiętaj także, że prowadzimy szkolenia z Apache Spark. Jakie są? Przede wszystkim bardzo mięsiste i tak bardzo zbliżone do rzeczywistości jak tylko się da. Pracujemy na prawdziwych danych, prawdziwym klastrze. Co więcej – wszystko to robimy w znakomitej atmosferze, a na koniec dostajesz garść materiałów! Kliknij tutaj i podrzuć pomysł swojemu szefowi;-).

 

Loading
Szukanie podobieństw w tekstach przy pomocy Spark ML – efekty

Szukanie podobieństw w tekstach przy pomocy Spark ML – efekty

Co ty na to, żeby zbudować system, dzięki któremu wyszukujemy podobne wypowiedzi polityków? Tak dla sprawdzenia – jeśli jeden coś powiedział, poszukamy czy jego oponenci nie mówili przypadkiem podobnie. Dzięki temu być może oczyścimy trochę debatę – świadomość, że nasi przedstawiciele nie różnią się aż tak bardzo, może być bardzo orzeźwiająca. Jednak taki mechanizm to dużo danych do przetworzenia i zinterpretowania. Dodatkowo tekstowych.

Dziś w artykule o podobnym problemie przy wykorzystaniu Apache Spark. Porozmawiamy więc o sztucznej inteligencji – a konkretniej machine learning, natural language processing (NLP) oraz text similarity. Wyjątkowo jednak nie w kontekście pythona, a właście Scali i Sparka.

Text Similarity (AI) w Apache Spark

Wróćmy do problemu podobieństw wypowiedzi polityków. Oczywiście musimy najpierw zebrać dane. Ile może ich być? Jeśli bazujemy na krótkich wypowiedziach – ogromne ilości. Wszystko zależy od tego jak bardzo chcemy się cofać w czasie i jak wiele osób wziąć pod lupę (Sejm? Senat? Rząd? Polityków lokalnych? A może zagranicznych?). Sumarycznie można się pokusić jednak o miliony, dziesiątki a nawet setki milionów krótkich wypowiedzi. A im bardziej w używaniu jest Twitter, tym więcej.

Pytanie, czy do tak dużych zbiorów można użyć bibliotek Pythonowych? Wiele z nich bazuje na jednej maszynie, bez możliwości naturalnego przetwarzania rozproszonego. Oczywiście nie wszystkie i z pewnością jest tam najmocniej rozwinięte środowisko do NLP. Na tym blogu skupimy się dziś jednak na mało popularnym pomyśle. Sprawdzimy na ile naprawdę poważnym rozwiązaniem może być Apache Spark w świecie machine learning. Dziś pokażę owoc eksperymentu nad przetwarzaniem tekstu w Apache Spark.

Po pierwsze: efekt

Zanim wskażę jakie techniki można zastosować, spójrzmy co udało się osiągnąć.

Zacznijmy od podstawowej rzeczy

  1. Bazujemy na zbiorze, który ma ~204 tysiące krótkich tekstów – konkretnie tweetów.
  2. Tweety dotyczą trzech dziedzin tematycznych:
    • COVID – znakomita większość (166543 – 81,7%)
    • Finanse – pewna część (28874 – 14,1%)
    • Grammy’s – margines (8490 – 4,2%)
  3. W ramach systemu przekazujemy tekst od użytkownika. W odpowiedzi dostajemy 5 najbardziej podobnych tweetów.

Efekty

Poniżej kilka efektów. Chcę zauważyć, że sporą rolę odgrywa tutaj kwestia nierówności zbiorów. Dane związane z ceremonią przyznania nagród Grammy’s są właściwie marginalne (nieco ponad 4%). Tweety COVIDowe zapełniają natomiast nasz zbiór w ponad 80%. Jest to istotne, gdyż przy sprawdzaniu efektywności najbardziej naturalnym odniesieniem jest zwykłe prawdopodobieństwo. W zbiorze 100 “najbardziej podobnych” tekstów (do jakiegokolwiek), ok 80 powinno być związanych z COVID-19, nieco ponad 10 to najpewniej finansowe, natomiast muzyczne będą w liczbie kilku sztuk.

Text Similarity w Apache Spark na przykładzie wywołania tweetów związanych z COVID-19

Fraza covidowa, najprostsza

Wyszukiwania zacznijmy od najprostszego podejścia: frazą wyszukiwaną niech będzie podobna do tej, o której wiemy, że istnieje w podanym zbiorze. Liczba w nawiasie to stopień podobieństwa – od -1 do 1 (gdzie 1 to identyczne).

Fraza: Praying for health and recovery of @ChouhanShivraj . #covid #covidPositive (zmiany są bardzo drobne).

Podobne wykryte frazy:

  1. Praying for good health and recovery of @ChouhanShivraj (0.9456217146059263)
  2. Prayers needed for our vascular surgeon fighting Covid @gpianomd #COVID19 #Prayers #frontlinedoctors (0.8043357071420172)
  3. Prayers for your good health and speedy recovery from #COVID19 @BSYBJP (0.801822609000082)
  4. Hon’ble @CMMadhyaPradesh Shri @ChouhanShivraj Ji tested #COVID19 positive. Praying for his speedy recovery. (0.7740378229093525)
  5. I pray for Former President Shri @CitiznMukherjee speedy recovery. Brain tumor wounds ji a lot, God may heal his p…  (0.7626450268959205)

Jak widać każda z tych fraz pochodzi z grupy COVIDowych. Dodatkowo dotyczy pragnień szybkiego powrotu do zdrowia oraz modlitwy za cierpiących.

Fraza finansowa, trudniejsza

Przejdźmy do czegoś odrobinę trudniejszego – sprawdźmy coś finansowego. Niech będzie to fraza, którą absolutnie wymyślę od początku do końca.

Fraza: Ford’s earnings grow another quarter

Podobne wykryte frazy:

  1. XLE Goes Positive Brent UP Big &amp; WTI UP Big Rally $XOM ExxonMobil Buy Now for the Rest of 2018 GASOLINE INVENTORIE… (0.7579525402567442)
  2. Morgan Stanley Begins Covering Murphy Oil $MUR Stock. “Shares to Hit $26.0” (0.7211353533183933)
  3. Seaport Global Securities Lowers Cabot Oil &amp; Gas Q2 2018 Earnings Estimates to $0.15 EPS (Previously $0.17).… (0.7211353533183933)
  4. William E. Ford Purchases 1000 Shares of BlackRock Inc. $BLK Stock (0.7195004202231048)
  5. Anadarko Petroleum Is On A Buyback Binge $APC (0.7187907206133348)

W tym przypadku podobieństwa są znacznie mniejsze. Warto zauważyć jednak dwie rzeczy: Po pierwsze – system wskazuje, że podobieństwa są mniejsze (0.76 to dużo mniej niż 0.95). Prawdopodobnie bardzo podobne po prostu więc nie istnieją. Druga rzecz – wszystkie podobne tweety pochodzą ze zbioru finansowych! Zbioru, który stanowi ok 14% całości danych. Pozwala to nabrać przekonania, że odpowiedzi nie są przypadkowe.

Fraza muzyczna, najtrudniejsza

Na koniec – najtrudniejsze zadanie ze wszystkich. Wybierzemy zdanie, które teoretycznie powinno pasować do zbioru będącego marginesem całości – do Grammy’s. Dodatkowo zdanie to wymyślę całkowicie. A niech tam – niech dotyczy najwspanialszej piosenkarki w dziejach! Oczywiście moim, zupełnie subiektywnym i amatorskim okiem;-).

Fraza: Amy Lee is the greatest singer of all time!

  1. Christina Aguilera &amp; Norah Jones were the only multiple recipients for ‘Best Female Pop Vocal Performance’ in the 2000s. (0.7306395709876714)
  2. @billboardcharts @justinbieber @billieeilish @oliviarodrigo @taylorswift13 @kanyewest Taylor the only real queen let’s be honest she deserves the Grammy for evermore but the #GRAMMYs wont give her. (0.7019156211438091)
  3. #GRAMMYs keep doing dirty to Lana Del Rey? Even though her talent is among the best this world has to offer (0.6868772967554752)
  4. Kylie Minogue deserved at least one nomination for Magic #GRAMMYs (0.6820704278110573)
  5. The answer will always be YES. #GRAMMYs #TwitterSpaces #SmallBusinesses #BlackOwned #adele #bts (0.6816903814884498)

I to właśnie te wyniki, przyznam, najmocniej wprowadziły mnie w euforię i ekscytację, gdy je zobaczyłem. I to nie tylko z powodu mojego niekłamanego uczucia do wokalistki Evanescence. Gdy spojrzymy na to “zdrowym, chłopskim okiem”, nie ma tutaj słowa o Grammy’s. Nie ma też szczególnego podobieństwa w słowach między pięcioma wymienionymi tweetami. Jest za to… kontekst. Jest podobieństwo tematyczne, jest znaczenie sensu.

A to wszystko naprawdę niedużym kosztem:-).

Text Similarity w Apache Spark na przykładzie wywołania tweetów muzycznych (z Grammy’s)

Apache Spark a text similarity – wykorzystane techniki

No dobrze, ale przejdźmy do konkretów – co należy zrobić, aby dostać takie wyniki? Tu zaproszę od razu do następnego artykułu, w którym pokażę dokładniej jak to zrobić. Dzisiejszy potraktujmy jako zajawkę. Żeby nie przeoczyć następnego – zapisz się na newsletter;-).

 

Loading

Po dość długich staraniach i wyeliminowaniu kilku ewidentnie beznadziejnych podejść, za sprawą kolegi Adama (za co ukłony w jego stronę) zacząłem czytać o embeddingach USE (Universal Sentence Encoder). Od razu powiem, że moją podstawową działką jest Big Data rozumiane jako składowanie i przetwarzanie danych. Sztuczna inteligencja to dopiero temat, który badam i definitywnie nie jestem w nim specem (choć parę kursów w tym kierunku ukończyłem i coś niecoś działałem). Liczę jednak, że obcowanie ze specami w tej działce (takimi jak właśnie Adam;-)) pomoże w eksploracji tego ciekawego gruntu.

Wróćmy jednak do USE. To była istne objawienie. Warto zaznaczyć, dla tych którzy nie do końca są zaznajomieni z tematyką machine learning, że komputer oczywiście tak naprawdę nie rozumie tekstu. Żeby mógł wyszukiwać podobieństwa, dzielić na grupy, uczyć się klas itd – potrzebne są liczby. Stąd wziął się pomysł sprowadzania tekstów do wektorów i różnego rodzaju wectorizerów – mechanizmów, które sprowadzają tekst do wektorów. Wektorów, czyli tablic jednowymiarowych o określonej długości (tu można się pomylić. Wielowymiarowe wektory dalej są jednowymiarowymi tablicami). Nieco bardziej rozbudowaną wersją są embeddingi, które mogą przechowywać w sobie wektory, natomiast które posiadają dodatkowe cechy pomocne. Jedną z nich (kluczową?) jest to, że słowa które chcemy zamienić na liczby, nabierają kontekstu. Pomaga to szczególnie mocno w niektórych przypadkach – na przykład naszych tweetów, które zawierają krótkie, czasami niezbyt treściwe przekazy. Jeśli będziemy je porównywali w prosty, czysto “statystyczny” sposób zestawiając wyrazy, nie uzyskamy odpowiedniego efektu.

Machine Learning w Apache Spark

Aby korzystać z dobrodziejstw ludzkości w zakresie machine learning, w tym text similarity w Apache Spark, należy wykorzystać bibliotekę Spark MlLib (w repozytorium Mavena dostępna tutaj). Tylko tutaj UWAGA! Wewnątrz biblioteki MlLib dostępne są dwa “rozgałęzienia”:

  1. Spark MlLib – starsza (choć wciąż utrzymywana) wersja, operująca bezpośrednio na RDD.
  2. Spark ML – nowocześniejsza część biblioteki. Możemy tutaj pisać operując na Datasetach i Dataframe’ach.

Wracając do technik – jednym z embeddingów jest właśnie USE. Jest on znacznie znacznie lepszym rozwiązaniem niż nieco podstarzały word2Vec, o innych, prostszych (np. Count Vectorizer) nie wspominając. Problem? Ano właśnie – nie wchodzi on w skład podstawowej biblioteki MlLib. Nie jest to jednak problem nie do przeskoczenia. Istnieje w Internecie gotowy zestaw rozwiązań, które poszerzają podstawowe biblioteki Sparkowe. Mam tu na myśli John Snow Labs. Udostępniają oni naprawdę imponująca liczbę algorytmów, które po prostu możemy wykorzystać – i to z całkiem niezłym skutkiem. Omówienie poszczególnych algorytmów można znaleźć tutaj. Samą bibliotekę do przetwarzania tekstu, czyli Spark-NLP zaciągniemy bez problemu z głównego repozytorium Mavena. To dzięki niej możemy rozwiązać bardzo wiele problemów, m.in. text-similarity w Apache Spark;-)

Jak technicznie dokładnie to zrobić, pokażę w kolejnym artykule. Już teraz zapraszam do subskrybowania;-).

Cosine Similarity

Skoro tylko udało mi się już porządnie sprowadzić tekst do jakiś ludzkich kształtów (czyli liczb;-)), należało znaleźć najlepszy z nich. Najlepszy – czyli najbardziej podobny do takiego, który wprowadzę. Dość dużo spędziłem czasu na szukaniu różnych gotowych rozwiązań. W końcu zdecydowałem się na zastosowanie czystej matematyki. Mowa tu o cosine similarity. Nie mam pojęcia jak to się tłumaczy na polski, a “podobieństwo kosinusowe” mi po prostu nie brzmi (ani nie znalazłem żeby tak się mówiło).

Z grubsza sprawa jest dość prosta – chodzi o to, żeby znaleźć podobieństwo między dwoma (niezerowymi) wektorami osadzonymi na jakiejś płaszczyźnie. Nie jest to żadna technika rakietowa i nie dotyczy ani NLP, ani nawet machine learning. Dotyczy zwykłej, prostej, nudnej matmy i można się zapoznać nawet na wikipedii.

Wzór na cosine similarity wygląda następująco:

{\displaystyle {\text{cosine similarity}}=S_{C}(A,B):=\cos(\theta )={\mathbf {A} \cdot \mathbf {B} \over \|\mathbf {A} \|\|\mathbf {B} \|}={\frac {\sum \limits _{i=1}^{n}{A_{i}B_{i}}}{{\sqrt {\sum \limits _{i=1}^{n}{A_{i}^{2}}}}{\sqrt {\sum \limits _{i=1}^{n}{B_{i}^{2}}}}}},}

Efekt jest prosty: wynik jest od -1 do 1. Im bliżej 1 tym bliższe są oba wektory. Problem? Oczywiście – w Sparku nie ma implementacji;-). Na szczęście jest to wzór na tyle prosty, że można go sobie zaimplementować samemu. Ja to zrobiłem w ramach UDF.

Podsumowanie

I to tyle! Tak więc można sprawę uprościć: zebrałem tweety, użyłem USE (od John Snow Labs) oraz cosine similarity. W efekcie dostałem naprawdę solidne wyniki podobieństwa. I to nie tylko jeśli chodzi o sam tekst, ale przede wszystkim jego znaczenie.

Już w najbliższym artykule pokażę jak dokładnie napisałem to w Sparku. Jeśli interesują Cię zagadnienia dotyczące Sparka, pamiętaj, że prowadzimy bardzo ciekawe szkolenia – od podstaw. Pracujemy z prawdziwymi danymi, na prawdziwych klastrach no i… cóż, uczymy się prawdziwego fachu;-). Jeśli interesuje Cię to – Zajrzyj tutaj!

Zostań z nami na dłużej. Razem budujmy polskie środowisko Big Data;-). Jeśli chcesz pozostać z nami w kontakcie – zapisz się na newsletter lub obserwuj RDF na LinkedIn. Koniecznie, zrób to i razem twórzmy polską społeczność Big Data!

 

Loading