Uczenie maszynowe w BigQuery

Krótka prezentacja BigQuery ML, czyli narzędzia "made in Google", które jeszcze bardziej obniża próg wejścia do trenowania własnych modeli uczenia maszynowego.

Sztuczna inteligencja (SI) wydaje się być dzisiaj na językach niemal wszystkich, tym niemniej warto pamiętać, że jest to dziedzina informatyki z naprawdę długą brodą, ponieważ jej początki sięgają co najmniej lat 50 ubiegłego stulecia, Choć siłą rzeczy na przestrzeni tych kilku dekad na jej obszarze powstało wiele różnych podejść do tego zagadnienia, jednak koncepcją, które okazała się naprawdę skuteczna (czego efekty możemy dzisiaj obserwować), jest pomysł wykorzystania naprawdę dużych zestawów danych do trenowania modeli ogólnego przeznaczenia (“ogólnego przeznaczenia”, czyli takich takich jak chociażby drzewa decyzyjne czy sieci neuronowe, które można z powodzeniem zastosować do niemal dowolnego zagadnienia). Pomysł ten również nie jest niczym nowym i właściwie towarzyszy badaczom tego obszaru od samego początku, natomiast wąskim gardłem jeszcze jakieś 25-30 lat temu była przede wszystkim niewystarczająca moc obliczeniowa oraz ilość danych, by móc osiągnąć zadowalające rezultaty w takich dziedzinach jak choćby NLP (Natural Language Processing).

Tym niemniej SI wkroczyła w ostatnich latach pod nasze strzechy i to co najmniej w dwojaki sposób, ponieważ nie tylko coraz częściej mamy okazję korzystać z dobrodziejstwa rozwiązań opartych na uczeniu maszynowym, ale też rynek pracy obrodził w “specjalistów” z tej dziedziny, a przynajmniej tak może się wydawać przeglądając różne oferty pracy, które kuszą w nazwie stanowiska lub opisie obowiązków zaklęciami w rodzaju “uczenie maszynowe” czy “sztuczna inteligencja”. 

Od razu śpieszę wyjaśnić, czemu w poprzednim zdaniu ująłem słowo “specjalista” w cudzysłowy właśnie. Dzieje się to tak dlatego, że po lekturze takich anonsów można odnieść wrażenie, że obecnie próg wejścia do tej tajemniczej i fascynującej krainy jest relatywnie niski, ponieważ wystarczy jako tako ogarniać Pythona (ze szczególnym naciskiem na dobrą znajomość takich bibliotek jak Pandas, Numpy, PyTorch czy scikit-learn), co od razu nasuwa na myśl przypuszczenie, że zadania jakie czekają potencjalnego pracobiorcę niewiele mają wspólnego z tym, co odbywa się chociażby w kazamatach Googla, OpenAI czy inszej Tesli. Na rzecz takiej oceny świadczyć może fakt, że raczej na próżno szukać wśród wymagań stawianych wobec osób aspirujących na dane stanowisko przynajmniej dobrej znajomości algebry macierzy czy bycia wirtuozem rachunku prawdopodobieństwa.

Rzecz jasna nie chcę umniejszać nikomu, kto para się tego rodzaju zadaniami, jakie zwyczajowo przypadają w udziale osobom na stanowisku “Data Scientist”, ponieważ jest to praca żmudna i nierzadko nudna (albo bardziej koncyliacyjnie rzecz ujmując: przynajmniej czasem są to czynności mało ekscytujące). Wystarczy bowiem przekartkować dowolną książkę poświęconą uczeniu maszynowym, by szybko się przekonać, że na tym obszarze największa para (szacunki mówią nawet o 80% czasu) idzie w przygotowanie danych, na których dopiero będziemy uczyć nasz model. To samo zresztą potwierdziło mi tych kilku specjalistów, którzy profesjonalnie zajmują się takimi zagadnieniami, a których to miałem przyjemność poznać.

Ale po co ja o tym wszystkim piszę? Ano w poprzednim wpisie na tym blogu odgrażałem się, że opiszę implementację uczenia maszynowego “made in Google”, które jest w zasięgu myszki każdego użytkownika BigQuery. No z tym “każdym” to się trochę zagalopowałem, ponieważ bardziej precyzyjnie rzecz ujmując, chodzi o każdego użytkownika z odpowiednimi uprawnieniami, gdyż – jak to w chmurze – nic nie jest za darmo i Twoja organizacja z różnych powodów może nie za bardzo palić się, by ten Nowak czy tamta Kowalska mogła “Wszystko wszędzie naraz”.

Tym niemniej wspomniane rozwiązanie jest o tyle ciekawe, że próg wejścia do “zabawy” w budowanie modeli opartych na uczeniu maszynowym został w tym wypadku dramatycznie obniżony w stosunku do tego, o czym pisałem wcześniej. Otó w tym wypadku wystarczy bowiem posiadać jako taką znajomość SQL, ponieważ BigQuery umożliwia tworzenie modeli uczenia maszynowego przy użyciu standardowych zapytań SQL.. Nie musisz wówczas pisać żadnego kodu w Pythonie czy innym języku programowania, choć oczywiście BQ wspiera natywnie również takie podejście. 

Rzecz jasna by świadomie skorzystać z tego rozwiązania trzeba mieć chociaż ogólne rozeznanie, z jakimi algorytmami/modelami mamy do czynienia w ramach uczenia maszynowego, ponieważ musimy wiedzieć który z nich należy zastosować do konkretnego problemu, któremu ma zaradzić budowane przez nas rozwiązanie. W tym miejscu od razu zaznaczam, iż choć zakładam, że potencjalny czytelnik tego tekstu jest w tej materii laikiem, tym niemniej nie jest to wpis poświęcony uczeniu maszynowemu jako takiemu. Gdyby ktoś był zainteresowany poszerzeniem wiedzy w tym zakresie w ramce na końcu tekstu zamieściłem kilka pozycji książkowych, z których swego czasu próbowałem się uczyć tego zagadnienia i z czystym sumieniem mogę polecić.

To napisawszy omówię mimo wszystko pokrótce jeden z algorytmów, na przykładzie którego zaprezentuję przykład zastosowania uczenia maszynowego w  BQ. Jest to o tyle konieczne, że bez kilku słów wprowadzenia na temat “regresji logicznej” (właśnie ten algorytm będzie wykorzystany w naszym przykładzie) dalsza część wpisu może być nie do końca zrozumiała dla przeciętnego “hobbysty”.

W dużym skrócie: w przypadku “regresji logistycznej” mamy do czynienia z czymś co określa się mianem uczenia nadzorowanego, czyli takiego, gdzie na potrzeby treningu naszego modelu dostarczamy dane, które zawierają również poprawne odpowiedzi wyrażone wprost w zbiorze informacji. Tym samym jeśli dysponujemy danymi historycznymi, które zawierają poprawne “odpowiedzi” (nazywane fachowo „etykietą”) na interesujący nas problem, możemy użyć ich do trenowania modeli uczenia maszynowego, aby przewidywać wynik w przypadkach, w których etykieta nie jest jeszcze znana (czytaj: w prognozowaniu przyszłych zdarzeń). 

W kontekście “regresji logistycznej” często przywoływanym przykładem jest mechanizm służący do rozróżnienie spamu od “koszernych” maili. Tym samym jeśli dysponujemy odpowiednio dużą bazą wiadomości elektronicznych, które zostały podzielone wg klucza spam vs zwykła wiadomość (podział ustalony chociażby na podstawie decyzji użytkowników skrzynek pocztowych, którzy część korespondencji oflagowali jako spam właśnie), tę wiedzę możemy wykorzystać, by zbudować rozwiązanie, które część mejlozy od razu umieszczać w spamowym niebycie (już bez ingerencji użytkownika). W tym celu przygotowujemy zestawienie, które będzie zawierało jako dane wejściowe (cechy) chociażby cały adres mail lub przynajmniej domenę nadawcy maila, tytuł maila oraz jego treść/zawartość. Ponadto każdy taki rekord zawiera przy tym etykietę, która mówi, że ten konkretny mail od nadawcy x oraz pewnej treści i tytule jest na ten przykład spamem.

Taką bazę następnie dzielimy na dwie części (podręcznikowo zazwyczaj jest mowa o trzech grupach, ale w praktyce nierzadko pomija się tzw. zbiór walidacyjny):

  • dane treningowe czyli te, które posłużą do wyuczenia modelu,
  • dane testowe, które pozwolą nam zweryfikować adekwatność naszego modelu, czyli umożliwią określenia jak dobrze idzie naszemu modelowi “zgadywanie” poprawnego wyniku w przypadku nowych danych, kiedy to jeszcze nie znamy finalnego wyniku.

Przekładając to na przykład z mailami: na pierwszym zbiorze trenujemy model, który stara się rozpoznać wzór w danych pozwalający oszacować do której kategorii przydzielić daną korespondencję (na tym etapie do tego właśnie służą etykiety). Następnie weryfikujemy przy pomocy drugiego zbioru, czy nasz model posiada odpowiedni poziom “jasnowidzenia”, czyli na podstawie cech drugiego zbioru model stara się przypisać odpowiednią etykietę do każdego maila (rzecz jasna w oparciu o wiedzę, jaką nabył na etapie treningu)i następnie dochodzi do weryfikacji prognozowanego wyniku ze znaną post factum etykietą. Jeśli poziom błędu jest odpowiednio niski (rzecz jasna to ostatnie do pewnego stopnia jest rzeczą umowną i w dużym stopniu zależy od materii problemu) możemy zacząć wdrażać nasz model na produkcję. W przeciwnym razie wracamy do trenowania modelu i staramy się to zrobić trochę inaczej niż poprzednim razem (np. poprzez zmianę algorytmu albo jakąś modyfikację na danych składających się na cechy).

To byłoby wszystko jeśli chodzi o teorię, czas na praktyczną demonstrację. Od razu zaznaczam, że jest to przykład wzięty wprost z googlowskiej dokumentacji, ponieważ celem tego wpisu nie jest wyczerpujące omówienie tego rozwiązania, a jedynie zaprezentowanie jak banalnie prosto przy pomocy BQ można zbudować model uczenia maszynowego. 

W przykładzie tym korzystamy z gotowego zestawu danych pochodzenia bliżej mi nieznanego (znaczy się przygotowali je ludzie z Google, ale specjalnie nie wgłębiałem się w tę materię, czego faktycznie one dotyczą i czy są to rzeczywiste dane). Mamy tutaj do czynienia po prostu z danymi, które zostały wygenerowane przy pomocy Google Analytics prawdopodobnie dla jakiegoś sklepu internetowego. W związku z tym potencjalny scenariusz biznesowy mógłbym z grubsza wyglądać tak: otóż mamy firmę X, która prowadzi witrynę internetową oferujący pewne dobra (dajmy na to sprzęt elektroniczny). Strona ta cieszy się dużym zainteresowaniem, ponieważ dziennie jest odwiedzana przez dziesiątki tysięcy użytkowników (a niech im będzie). Tym niemniej kierownictwo firmy nie jest zainteresowane samą liczbą odwiedzin (to wyłącznie generuje koszty, ponieważ wymaga utrzymanie odpowiednio mocnego serwera, by ten ruch obsłużyć), ale chciałoby osiągnąć jak najwyższy poziom konwersji, czyli sprawić, by jak maksymalnie wielu odwiedzających finalnie stało się klientami, czyli opuszczało stronę już po dokonaniu zakupu. W tym celu chce przygotować odpowiednią komunikację dedykowaną osobno pod grupę potencjalnych kupujących, coby kupili jeszcze więcej oraz inną dla tych, którzy z dużym prawdopodobieństwem w normalnych warunkach nie wydadzą ani grosza.

Od razu zaznaczę, że punktu widzenia tak sformułowanego celu dane będące cechami wykorzystane do uczenia modelu są trochę bez sensu, ponieważ jedną z nich znamy dopiero post factum, czyli w momencie kiedy klient opuści naszą witrynę (liczba odwiedzonych stron w czasie sesji). Tym niemniej zostawmy na boku część merytoryczną i załóżmy, że zestawienie cech wyjątkowo celnie adresuje czynniki, które pozwolą możliwie celnie przewidzieć zakładane zachowanie. Na te cechy składają się natomiast następujące informacje:

  • device.operatingSystem: system operacyjny urządzenia, z której łączy się z witryną osoba odwiedzająca witrynę,
  • device.isMobile: flaga wskazująca czy mamy do czynienia z urządzeniem mobilnym czy nie.
  • geoNetwork.country: kraj, z którego łączy się odwiedzajacy (ustalany na podstawie adresu IP),
  • totals.pageviews: liczba odwiedzonych stron.

Oczywiście danych w tej tabeli jest znacznie więcej (niżej widać schemę dla tej tabeli) niż te 4 wymienione wyżej. 

Przy czym mamy tu kilka pól typu rekordowego, czyli takich które zawierają w sobie kolejne pola (coś w rodzaju słownika znanego z języków programowania). Niżej widać część z nich (dla pól totals oraz device).

Co więcej wszystkie interesujące nas wartości są zapisane właśnie wewnątrz takich pól, co można rozpoznać przyglądając się poniższemu zapytaniu, gdzie każde z pól poprzedzone jest nazwą pola nadrzędnego oraz kropką je rozdzielającą. 

CREATE OR REPLACE MODEL `borciugner.sample_model`

OPTIONS(model_type='logistic_reg') AS

SELECT

IF(totals.transactions IS NULL, 0, 1) AS label,

IFNULL(device.operatingSystem, "") AS os,

device.isMobile AS is_mobile,

IFNULL(geoNetwork.country, "") AS country,

IFNULL(totals.pageviews, 0) AS pageviews

FROM

`bigquery-public-data.google_analytics_sample.ga_sessions_*`

WHERE

_TABLE_SUFFIX BETWEEN '20160801' AND '20170630'

Od razu też można zauważyć, że w roli etykiety (pole “label”) występuje tutaj liczba wykonanych transakcji (cokolwiek to miałby znaczyć: może chodzi o liczbę zakupionych towarów?). Przy czym mniejsze znaczenie ma to, ile faktycznie tych transakcji miało miejsce, ponieważ w polu tym dopuszczalne są dwie wartości, czyli 0 (zero) dla odwiedzających, którzy nie dokonali żadnego zakupu, oraz 1 (jedynka) dla tych, którzy dokonali choć jednej płatności. Tym samym mamy komplet, na którym możemy ćwiczyć nasz model oparty na “regresji logistycznej”. Tej ostatniej użyjemy dlatego, że interesuje nas proste rozróżnienie “kupił – nie kupił” a nie chociażby liczba transakcji czy łączny koszt zakupów, bo wówczas musielibyśmy skorzystać z innego algorytmu (zapewne z “regresji liniowej”)

Teraz czas na krótką egzegezę samego kodu. Otóż całość zaczyna się od formuły CREATE OR REPLACE MODEL, po którym następuje podanie miana naszego modelu składającej się z nazwy datasetu dostępnego w danym projekcie oraz nazwy modelu (tutaj mamy pełną dowolność, oczywiście w ramach dopuszczalnych przez BQ). Rzecz jasna słowo REPLACE można pominąć, zwłaszcza jeśli nie chcemy przypadkiem nadpisać już istniejącego modelu. Dalej mamy opcje (OPTIONS), gdzie w zasadzie jedynym obowiązkowym parametrem jest nazwa algorytmu, z którego chcemy skorzystać (tutaj “regresja logistyczna”). Tym niemniej możliwości jest znacznie więcej, o czym można doczytać w dokumentacji

Na marginesie tylko dodam, że w przykładach, które widziałem w różnego rodzaju poradnikach zazwyczaj występował jeszcze parametr INPUT_LABEL_COLS, który wskazywał wprost pole/pola spełniające rolę etykiety (oczywiście dotyczy tylko uczenia nadzorowanego). Mi osobiście bardziej odpowiada takie podejście, gdzie explicite wskazujemy miejsce, w którym znajdują się etykiety.

Dalej mamy zwykłe zapytanie SQL, które wybierana dane, które mają nam posłużyć do wytrenowania modelu. Jedyne na co warto zwrócić uwagę w tym miejscu, to klauzula WHERE, gdzie wskazany jest zakres “dat”, ponieważ uczenie modelu nie może odbywać się na całości danych, tylko na jakiejś jej odpowiednio dużej części. Wskazując interesujący nas przedział czasu dokonujemy takiego podziału bazy.

Po uruchomieniu tego kodu po dłuższej lub krótszym czasie ( w moim przypadku – jak widać niżej – były to niespełna 3 minuty, ale im więcej danych tym dłużej taki trening będzie trwał) utworzony zostanie model, który następnie możemy zwalidować pod kątem jego dokładności.

W celu określenia mocy predykcyjnej nowo powstałego modelu musimy użyć funkcji ML.EVALUATE, przy czym rzecz jasna taki test przeprowadzać będziemy na innym zbiorze danych niż te, które wykorzystaliśmy do przyuczenia naszego modelu. Całość wygląda tak:

SELECT

*

FROM

ML.EVALUATE(MODEL `borciugner.sample_model`, (

SELECT

IF(totals.transactions IS NULL, 0, 1) AS label,

IFNULL(device.operatingSystem, "") AS os,

device.isMobile AS is_mobile,

IFNULL(geoNetwork.country, "") AS country,

IFNULL(totals.pageviews, 0) AS pageviews

FROM

`bigquery-public-data.google_analytics_sample.ga_sessions_*`

WHERE

_TABLE_SUFFIX BETWEEN '20170701' AND '20170801'))

Wydaje mi się, że widoczny wyżej kod jest na tyle oczywisty, że nie ma potrzeby dodatkowych wyjaśnień, co najwyżej warto zwrócić uwagę na fakt, że zbiór testujący jest dużo mniejszy niż ten, który był wykorzystany do uczenia modelu, ponieważ tutaj badamy jeden miesiąc (lipiec 2017), podczas gdy dane uczące obejmowały niemal cały rok (od sierpnia 2016 do końca czerwca 2017). 

W efekcie wykonania następującego kodu otrzymamy szereg wskaźników (oczywiście w klauzuli SELECT możemy wybrać tylko te nas interesujące).

Oczywiście nie będę pozował na znawcę tematu i próbował wyjaśnić chociażby do czego służy i jak wyliczana jest krzywa roc. Na potrzeby naszego ćwiczenia wystarczy, że skupimy się na accurency, który wyraża stosunek trafnych prognoz w stosunku do wszystkich rekordów/przypadków w zbiorze uczącym. Jak widać nasz model “pomylił się” jedynie w 1,5% przypadków, czyli jest naprawdę dobrze. Tym samym możemy śmiało wykorzystać ten model na produkcji do predykcji.

Do tego celu służy kolejna funkcja ML.PREDICT, której zastosowanie jest bardzo podobne (co do sposobu zapisu kodu) do tej poprzednio omówionej, z tą jednakże różnicą, że w zapytaniu SQL nie przekazujemy wartości etykiety, ponieważ co do zasady nie posiadamy jej w tym momencie i chcemy ją niejako “odkryć” na podstawie samych cech. Niżej zaprezentowany został kod i rezultat takiej prognozy.

SELECT

country,

SUM(predicted_label) as total_predicted_purchases

FROM

ML.PREDICT(MODEL `borciugner.sample_model`, (

SELECT

IFNULL(device.operatingSystem, "") AS os,

device.isMobile AS is_mobile,

IFNULL(totals.pageviews, 0) AS pageviews,

IFNULL(geoNetwork.country, "") AS country

FROM

`bigquery-public-data.google_analytics_sample.ga_sessions_*`

WHERE

_TABLE_SUFFIX BETWEEN '20170701' AND '20170801'))

GROUP BY country

ORDER BY total_predicted_purchases DESC

LIMIT 10

Oczywiście z punktu widzenia celu biznesowego, który wcześniej nakreśliłem te dane mają średni sens, ponieważ pokazują prognozowaną liczbę odwiedzających, którzy dokonają prawdopodobnie zakupu, w podziale na poszczególne kraje. Natomiast tak jak wspomniałem skorzystałem w tym wpisie z przykładu zaczerpniętego z googlowskiej dokumentacji i nie chodziło mi o biznesową adekwatności tego przykładu do wymyślonego przypadku, a jedynie zaprezentowanie jak banalnie prostym zadaniem staje się 

John Mueller, Luca Massaron, “Sztuczna inteligencja dla bystrzaków: Wbrew tytułowi (odnoszę się do oryginalnego brzmienia “for dummies”) to całkiem solidna pozycja dla osób całkiem zielonych lub z niewielką wiedzą w tym temacie. Omawia kwestie SI w szerszym kontekście niż tylko technologiczny. Tym niemniej jedna z części omawia dość przystępnie podstawy uczenia maszynowego, tym niemniej warto przekartkować całość. 

Paul J. Deitel, Harvey Deitel, “Python dla programistów. Big Data i AI. Studia przypadków”: Jeśli miałbym coś polecić osobom, które opanowały Pythona i mają już jakieś ogólne rozeznanie w tematyce uczenia maszynowego, jedną pozycję (z tych co znam rzecz jasna), to książka to chyba byłaby pierwszą pozycja, którą mógłbym zaanonsować do przejrzenia, choć mojego serca nie zdobyła. Otóż zostały tutaj omówione – czy raczej zaprezentowane – najpopularniejsze biblioteki uczenia maszynowego w szerszym kontekście “teoretycznym”. Czemu więc nie zdobyła ona mojego serca? Pewnie dlatego, że autor wprawdzie prezentuje kod i jakoś z grubsza omawia, co się w nim zadziało, to raczej nie pali się do bardziej szczegółowej egzegezy poszczególnych linijek. No i jak na moje możliwości przyswajania była po prostu zbyt obszerna, podczas gdy szukałem w tym zakresie czegoś bardziej bazowego oraz o bardziej praktycznym charakterze. 

Matt Harrison, “Uczenie maszynowe w Pythonie. Leksykon kieszonkowy: W przeciwieństwe do książki, o kórej była mowa wyżej, jest to propozycja, która w iście ekspresowym tempie przelatuje przez cały proces budowania modelu uczenia maszynowego (włącznie z etapem przygotowania danych) oraz prezentuje sposoby użycia różnych algorytmów zaimplementowanych w popularnych bibliotekach Pythona. Takie podejście wymaga od czytającego dodatkowego wysiłku polegającego na konieczności poszukiwania bardziej szczegółowej wiedzy w tym zakresie, ale ja osobiście preferuję takie podejście, jeśli poruszany jest dość szeroki obszar dziedzinowy (inaczej niż w przypadku nauki jakiegoś języka programowania czy konkretnego frameworka, kiedy to chciałbym mieć wówczas wszystko w jednym miejscu). 

Joel Grus, “Data science od podstaw. Analiza danych w Pythonie.: Była to jedna z pierwszych pozycji poświęcona – w jakieś tam części – uczeniu maszynowemu, tym niemniej nie polecam jej na początkową lekturę dla osób zupełnie zielonych w temacie, a zwłaszcza bez przynajmniej niezłej znajomości Pythona. Mnie zmylił nieco tytułu, a konkretnie fraza “od podstaw”, która w oryginale brzmi “from scratch”, co nieco lepiej oddaje charakter tej książki, ponieważ autor nie zamierza przeprowadzić czytelnika przez praktyczne zastosowanie najpopularniejszych bibliotek Pythona, wykorzystywanych w obszarze przetwarzania danych i budowy na nich modeli predykcyjnych. W to miejsce proponuje implementację od zera tego typu algorytmów, czyli najpierw omawia – czasami zbyt skrótowo – na czym dana technika polega, a potem po prostu koduje ten czy inny algorytm w Pythonie.  

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *