Wyzwania inżynierii oprogramowania w środowisku data science

26.06.2019 | Mateusz Kaleta

Wstęp

Po kilku latach spędzonych na rynku pracy jako full-stack software developer, dokonałem niemałej zmiany: zacząłem specjalizować się w uczeniu maszynowym. Jeszcze przed transformacją sądziłem, że największymi wyzwaniami zespołów data science, które wykorzystują machine learning, są konieczność rozeznania algorytmów i zgłębienie dziedziny, w której będą aplikowane.

Realizacja kolejnych projektów zmieniła mój pogląd na tę sprawę. Złożoność projektów data science ma o wiele więcej źródeł, niż mogłoby się wydawać. Prócz problemów tradycyjnego oprogramowania, występują tu również kłopoty znane ze środowisk badawczych oraz, coraz częściej, komplikacje związane z big data. Sytuację w dużym stopniu pogarsza gwałtowny rozwój całego obszaru i brak szeroko pojętych standardów.

Celem niniejszego artykułu jest przybliżenie problemów napotykanych przy rozwoju uczącego się oprogramowania czytelnikom niezwiązanym wprost z data science oraz zwiększenie świadomości wśród osób już praktykujących.

Tradycyjne oprogramowanie

No właśnie, czym jest wspomniane tradycyjne oprogramowanie? W kontekście tego artykułu, tym mianem będę określał systemy o niewielkim stopniu zależności od danych zewnętrznych, deterministyczne (zwracające takie same rezultaty dla tych samych danych wejściowych) i które rozwiązują problemy stosunkowo łatwe do oprogramowania.

Ta ostatnia cecha szczególnie silnie odróżnia tradycyjne oprogramowanie od takiego wykorzystującego uczenie maszynowe. Potencjał uczenia maszynowego wykorzystywany jest właśnie tam, gdzie pokrycie wszystkich możliwych sytuacji instrukcjami (zdefiniowanymi przez człowieka) staje się niepraktyczne. Świetnym przykładem takiej sytuacji jest próba rozpoznawania obiektów na zdjęciu. Jak wyrazić instrukcjami test, czy grupa pikseli reprezentuje postać człowieka, czy małpę?

Projekty data science

Machine learning wkracza do akcji tam, gdzie zawodzą ręcznie stworzone reguły. Tu, modelujemy problem poprzez przykłady, nie poprzez instrukcje. Implikuje to wiele zmian również w organizacji projektów.

W dużym uproszczeniu, projekt data science jest realizowany w następujących krokach:

  1. Istnieje problem, który trudno rozwiązać przy użyciu ręcznie tworzonych instrukcji.
  2. Definiowane są wymagania, najczęściej pochodzące od osób, które nie mają zbyt wielkiego pojęcia o tym, jak działa uczenie maszynowe, albo jakie są praktyczne problemy w przetwarzaniu terabajtów danych.
  3. Zastanawiamy się, czy jesteśmy w stanie sprostać wymaganiom; estymujemy ( :) ).
  4. Ekstrahujemy dane, niejednokrotnie z różnych źródeł (wewnętrznych i zewnętrznych, Hadoop, Oracle SQL, CSV, dane GUS, zewnętrzne API, systemy monitoringu, kolejki).
  5. Tworzymy, wybieramy i analizujemy (wizualizujemy, badamy statystycznie) zmienne, które mogą posłużyć budowie modelu.
  6. Tworzymy kilka modeli (często korzystając z różnych frameworków).
  7. Definiujemy przestrzeń hiperparametrów i szukamy optymalnych parametrów (tak, by model posiadał nie tylko wysoką skuteczność, ale i jakość).
  8. Wdrażamy model na odpowiednie środowiska.
  9. Monitorujemy i walidujemy działanie systemu.

Ponieważ pracujemy zwinnie, wiele kwestii ulega zmianie w trakcie rozwoju oprogramowania, a sam proces wykonuje się iteracyjnie. Powyższe kroki mogą być realizowane w różnej kolejności, często równolegle. Najważniejsze jest jednak to, że na każdym etapie pojawiają się komplikacje, które będziemy za chwilę rozkładać na czynniki pierwsze.

Róznice kulturowe

Zanim przejdziemy do aspektów technicznych, warto zdać sobie sprawę z problemu różnic kulturowych. Data science jest dziedziną interdyscyplinarną. Kompetencje potrzebne do pokonywania problemów z tego obszaru dotyczą programowania, statystyki, algebry, metodologii badań. Powoduje to, że zespoły budowane są najczęściej w oparciu o informatyków, statystyków oraz naukowców.

Jak łatwo się domyślić, są to zwykle osoby o bardzo zróżnicowanych umiejętnościach w zakresie inżynierii oprogramowania oraz poszukujące różnych wyzwań w codziennej pracy. Takie różnice z jednej strony stanowią siłę zespołów data science, z drugiej – narażają na wzrost długu technologicznego i popełnianie błędów metodologicznych.

Dla programisty z wieloletnim doświadczeniem będzie oczywiste, że aplikacja powinna być dobrze pokryta testami oraz logować informacje w trakcie wykonywania programu. Będzie posiadał jednak mniej intuicji co do poprawnego przeprowadzania eksperymentów od statystyka.

Changing anything changes everything

Punktem wyjściowym dla wielu problemów jest sama natura algorytmów uczenia maszynowego. Oczekują one stałego zbioru zmiennych wejściowych. Co więcej, model wytrenowany na danych o pewnym rozkładzie będzie istotnie reagował na zmianę parametrów tego rozkładu. Mowa o znacznym wpływie nawet na miliony parametrów modelu.

Uruchomienie procesu treningu nawet dla tych samych parametrów wejściowych może zakończyć się kompletnie innymi rezultatami z uwagi na niedeterministyczność różnych kroków algorytmu. Optymalizacja hiperparametrów, a więc parametrów takich, jak liczba warstw sieci neuronowej, współczynnik uczenia, czy współczynnik regularyzacji także skutkuje drastyczną zmianą wytrenowanego modelu.

Dodanie lub usunięcie jakiejś zmiennej wejściowej nierzadko powoduje, że algorytm przy kolejnym treningu “skupia się” na zupełnie innych atrybutach, niż przed taką operacją. Taka modyfikacja zbioru zmiennych to zresztą nie tylko kłopot dla modelu - to często konieczność przebudowy sporej części całego systemu. A wypadałoby dodać, że niejednokrotnie wymianie podlega również sam algorytm, który może przyjmować dane w kompletnie odmiennym formacie, aniżeli jego poprzednik.

Problemem stanowi z pewnością śledzenie zmian. Dokonuje ich wiele jednostek, najczęściej w zrównoleglony sposób. Utrzymanie ładu w tych warunkach to nie lada wyzwanie. Porównajmy to z tradycyjnym oprogramowaniem, w przypadku którego modyfikacje (np. wprowadzenie dodatkowego atrybutu w encji użytkownika) mają charakter stosunkowo lokalny.

Warto zapamiętać: changing anything changes everything.

Zależność od kodu ORAZ danych

W dobie open source korzystamy na co dzień z setek zewnętrznych bibliotek, frameworków, czy innych artefaktów - ogółem, zależności. Już ta relacja przyprawia niejednego programistę o ból głowy. Ciężko dziś być konkurencyjnym na rynku bez uzależnienia się od tego typu rozwiązań.

Jak się przed tym bronić? Abstrakcją! A jak się bronić przed zmianą kontraktu w zewnętrznym serwisie? Testami… Tak pokrótce, to tyle, jeżeli chodzi o podstawowe problemy. Bo w przypadku data science mamy jeszcze jeden: dane.

Podstawowa walidacja i testy obronią nas przed zmianą nazwy, czy typu parametru w przesłanym do naszego uczącego się systemu obiekcie JSON. Uchronią przed zmianą wartości z zakresu 0-1 na zakres 0-255. Ale nie uchronią nas przed drobną zmianą rozkładu danych.

Wyobraźmy sobie, że budujemy system, którego jedną ze zmiennych wejściowych jest wyjście innego modelu. W międzyczasie, zespół tworzący ten inny system postanowił wzbogacić go o kolejne zmienne. Nowa wersja modelu upstreamowego dokonuje teraz predykcji o nieco zmienionym rozkładzie, co kaskadowo wpływa na nasz system (nawet jeżeli wciąż otrzymujemy dane o wartościach tego samego typu, z tego samego zakresu i do tego wyglądających całkiem w porządku)! Pamiętajmy jednak o zasadzie changing anything changes everything.

W przypadku projektów data science, nie wystarczy statyczna analiza kodu, nie istnieje odpowiednik abstrakcji dla danych, a standardu ich testowania w zasadzie brak.

Problem sprzężenia zwrotnego

Jeszcze jeden przykład, dlaczego data science bawi. We wcześniejszej części artykułu przedstawiłem sytuację, w której model A wpływa na model B poprzez bezpośrednie włączenie wyjścia modelu A jako zmiennej wejściowej modelu B.

Wyobraźmy sobie teraz dwa niepołączone bezpośrednio systemy. System A jest odpowiedzialny za rekomendację pożyczek gotówkowych, kart kredytowych i innych produktów klientom. System B ma natomiast za zadanie ocenić, prawdopodobieństwo odejścia klienta z banku.

Łatwo sobie wyobrazić, że twórcy tego drugiego systemu chętnie włączą do zbioru atrybutów liczbę zaciągniętych przez klienta kredytów, czy posiadanych przez niego produktów w ogóle. Aktywniejszy klient, intuicyjnie, powinien pozostać lojalny.

Z czasem, system rekomendacyjny jest usprawniany i powoduje zwiększenie liczby produktów u przeciętnego klienta. Model oceniający prawdopodobieństwo rezygnacji z usług banku stałby się przez to w dużym stopniu nieaktualny.

Jeszcze o zmiennych...

Koszt zmiany zestawu atrybutów w systemach wykorzystujących uczenie maszynowe jest duży. Wymaga to przetrenowania modelu, aktualizacji procesów ETL, stworzenia lub modyfikacji testów.

Utrzymywanie niewykorzystywanych fragmentów kodu nie jest zgodne z dobrymi praktykami tworzenia oprogramowania. Problem tkwi w tym, że nieużywane zmienne mogą pozostać niezauważone, a ich detekcja kosztuje - właściwie, to należałoby wytrenować model pozbawiony danej zmiennej i zweryfikować, czy obniżyła się jego skuteczność w stosunku do modelu z pełnym zestawem atrybutów. Albo skorzystać z którejś z miar istotności cech - te jednak często zawodzą. W praktyce można więc założyć, że większość zespołów data science jest skazana na dożywotnie utrzymywanie wszystkich dodanych w czasie trwania projektu zmiennych i związanego z nimi kodu.

Należy tu sobie uświadomić, że specjalistę data science kusi dodawanie nowych zmiennych, bo 90,1% skuteczność modelu jest lepsza, niż 89,9%. Zwłaszcza na prezentacjach. Tyle, że dodanie pięciu atrybutów z trzech różnych źródeł danych może spowodować duże opóźnienia w dostarczeniu systemu, a przede wszystkim zwiększyć dług technologiczny projektu.

Dług konfiguracji

Liczne wartości konfiguracyjne dotyczące hiperparametrów, zmiennych, czy ręcznie dobranych thresholdów zmieniają się w warunkach data science nader często. Szczególnie te ostatnie utrudniają utrzymywanie uczących się systemów i narażają na kłopoty. Znów odwołam się do reguły CACE. Jakiekolwiek ręcznie dobrane parametry bardzo szybko się dezaktualizują w przypadku wprowadzania zmian w modelu, należy więc unikać tego typu sytuacji.

Sama konfiguracja również powinna podlegać w miarę możliwości testom, minimalizując prawdopodobieństwo dokonania niewielkiej, ale mającej duże konsekwencje pomyłki.

Odtwarzalność i powtarzalność

Konfiguracja łączy się poniekąd z ogromnym wyzwaniem towarzyszącym pracy w obszarze data science: odtwarzalnością i powtarzalnością (ang. reproducibility & repeatability).

Rzecz w tym, że wyniki eksperymentów powinny być co najmniej podobne (jeżeli nie takie same) dla takich samych parametrów wejściowych eksperymentu. Wypadałoby również, żeby powtórzenie eksperymentu nie wymagało wielu ręcznych kroków i było stosunkowo łatwe nawet po upływie pewnego czasu.

Dlaczego tak jest lepiej? Bo zamiast ślepo błądzić w ogromnej przestrzeni możliwych rozwiązań, pewnie kroczymy w stronę zwiększenia jakości modelu, a więc i całego systemu.

Nie jest jednak łatwo dojść do takiego stanu. Augmentacja danych, mieszanie danych wejściowych, równoległe i rozproszone uczenie, losowa inicjalizacja parametrów modelu, a nawet różnice w architekturze kart graficznych - to wszystko ma wpływ na powtarzalność oraz odtwarzalność eksperymentów. Można (i należy!) jednak próbować osiągnąć te cele wykorzystując dostępne flagi używanych frameworków.

W pewnych warunkach powtarzalność oraz odtwarzalność jest jeszcze ciężej osiągnąć - dużą trudność sprawia zmieniający się rozkład danych, zwłaszcza bardzo dużych danych, na których przechowywanie (i/lub przetworzenie) nie zawsze możemy sobie pozwolić.

Celem zwiększenia odtwarzalności, warto wraz z wytrenowanym modelem przechowywać metadane. Użyteczne z nich, to m.in.:

  • nazwy, typy, opisy i zakres zmiennych;
  • wersje bibliotek;
  • dane o sprzęcie / hoście na którym przeprowadzono trening modelu;
  • przestrzeń hiperparametrów;
  • najlepsze znalezione hiperparametry;
  • wartość ziarna generatora liczb pseudolosowych;
  • referencję do zbioru treningowego / testowego;
  • rozkład zmiennych;
  • metryki jakości.

Jednocześnie warto rozważyć w swoim zespole zastosowanie Data Version Control, systemu kontroli wersji opartego o Git, który powstał właśnie w celu zwiększenia odtwarzalności eksperymentów i zarządzania zmianami w projektach data science.

Monitoring

Różnica między błędami popełnianymi przez wytrenowany model, a najczęściej występującymi błędami w tradycyjnych systemach jest taka, że model "zdaje się" pracować poprawnie. Popełniane przez niego błędy mogą być niezauważalne gołym okiem i - podobnie jak podatności - nie wpływać szczególnie na działanie całego systemu, a jednak stanowić zagrożenie.

Wiele czynników może wpłynąć na zmianę rozkładu danych wejściowych. Może to być na przykład nieuchwycona wcześniej sezonowość, dokonana kalibracja sygnału, modyfikacja upstreamowego modelu, zmiana zachowania klientów... Najczęściej powstanie w takich sytuacjach konieczność przetrenowania modelu. Najprostszą, ale niezłą metodą monitoringu będzie tu okresowe porównywanie rozkładu dokonywanych predykcji do rozkładu, jaki posiadały tuż po etapie treningu.

Nie dowiemy się jeszcze, co dokładnie nie działa, ale będziemy chociaż wiedzieć, że kłopot występuje.

Debugging

Gdy w systemie wykorzystującym uczenie maszynowe zaczyna dziać się źle, napotkać można kolejny problem charakterystyczny dla tej dziedziny - trudne debugowanie.

Pierwsza kwestia to stosunkowo duży czas reakcji systemu na zmiany. W przypadku algorytmów uczenia maszynowego zwykle nie wystarczy uruchomienie kilku prostych testów jednostkowych, by odkryć przyczynę problemu. Często, należy dokonać modyfikacji, wytrenować model i dopiero wtedy można stwierdzić, czy wszystko jest już w porządku. Proces treningu, choć jest to bardzo indywidualna sprawa, nierzadko trwa wiele godzin. Taki proces wymaga też znacznie większej ilości danych, niż w przypadku testowania tradycyjnego oprogramowania.

Kwestia druga to liczba rzeczy, które mogą pójść nie tak. Do typowych błędów znanych z tradycyjnego oprogramowania, należy doliczyć liczne związane z hiperparametrami, trudnościami optymalizacji funkcji celu, jakością i ilością danych. Sporo problemów przysparzają rozproszone obliczenia oraz lazy evaluation (patrz Apache Spark, TensorFlow 1).

Światełkiem w tunelu zdają się być próby stworzenia ogólnej (nie specyficznej dla danego algorytmu) metody interpretacji modeli uczenia maszynowego. Algorytmy interpretacji pozwalają na zrozumienie, dlaczego model podjął taką, a nie inną decyzję. Obecnie nie istnieje jednak żaden "standard" interpretacji, a w zasadzie wszystkie metody posiadają szereg założeń, które interpretowany model musi spełniać, by w ogóle interpretacje można było uznać za poprawne. Obiecujące metody, jak np. wartości Shapleya, są z kolei bardzo wymagające obliczeniowo.

Zbyt ambitne rozwiązania

Jest jeszcze jedno. Ludzie lubią komplikować.

Czasem jest to konieczne, ale myślę, że zgodzimy się, że własna, zoptymalizowana w C implementacja niszowego algorytmu, opublikowanego w miesiąc temu, podparta elementami mało znanej biblioteki rozwijanej na Githubie przez trzech kontrybutorów to w 99% przypadków przepis na katastrofę.

Należy pamiętać, że rotacja na rynku IT jest spora, a osobnik odpowiedzialny za wspomnianą implementację, z dużym prawdopodobieństwem, będzie za półtora roku komplikował sprawy już w innej instytucji. Może czasem jednak warto poświęcić te 2% skuteczności i zastosować rozwiązanie, o którym słyszał chociaż co trzeci przyszły kandydat do zespołu? Pragmatyzm powinien być w zespołach data science cechą pożądaną.

Na wynos

Liczba wyzwań przytłacza. Ciężko jest jednak oszacować ilościowo, jak dobrze radzimy sobie z nimi w naszym zespole. Aby jednak uzyskać jakąkolwiek intuicję o aktualnej sytuacji, proponuję zadać sobie kilka pytań:

  1. Jak szybko potrafię wprowadzić nową zmienną do systemu? Co należałoby zrobić?
  2. Czy mam pewność, że model działający obecnie na produkcji działa poprawnie?
  3. Czy łatwo jest wprowadzić do mojego zespołu nową osobę?
  4. Czy potrafię odtwarzać wyniki eksperymentów?

Podsumowując: w moim odczuciu, projekty data science są z punktu widzenia inżynierii oprogramowania bardziej skomplikowane od tradycyjnego oprogramowania. Dziedzina jest stosunkowo młoda, przyciąga osoby o zróżnicowanych kompetencjach i poglądach na organizację pracy. Gdzie nie spojrzeć - piętrzą się wyzwania, a standardów zwykle brak.

Nie mam zamiaru jednak Czytelnika jakkolwiek do wykorzystywania machine learning, czy zdobycia kompetencji w tym obszarze zniechęcać. To prawdziwie fascynująca dziedzina, w której jeszcze dużo się wydarzy. Sądzę, że to wartościowe doświadczenie.

Dla głodnych wiedzy wrzucam garść linków:

  1. Hidden Technical Debt in Machine Learning Systems
  2. Why is machine learning 'hard'?
  3. Software Engineering Challenges of Deep Learning
  4. Interpretable Machine Learning