Przewidywanie uczenia maszynowego w Pythonie z interfejsem Flask REST API
Opublikowany: 2022-03-11Ten artykuł dotyczy używania Pythona w kontekście systemu uczenia maszynowego lub sztucznej inteligencji (AI) do tworzenia prognoz w czasie rzeczywistym za pomocą interfejsu Flask REST API. Przedstawiona tutaj architektura może być postrzegana jako sposób na przejście od weryfikacji koncepcji (PoC) do minimalnie opłacalnego produktu (MVP) dla aplikacji uczenia maszynowego.
Python nie jest pierwszym wyborem, jaki można sobie wyobrazić, projektując rozwiązanie działające w czasie rzeczywistym. Ale ponieważ Tensorflow i Scikit-Learn to jedne z najczęściej używanych bibliotek uczenia maszynowego obsługiwanych przez Pythona, jest on wygodnie używany w wielu PoC dla notebooków Jupyter.
To, co sprawia, że to rozwiązanie jest wykonalne, to fakt, że szkolenie zajmuje dużo czasu w porównaniu z przewidywaniem. Jeśli myślisz o szkoleniu jako procesie oglądania filmu i przewidywaniu odpowiedzi na pytania na jego temat, to wydaje się całkiem skuteczne, aby nie musieć ponownie oglądać filmu po każdym nowym pytaniu.
Trening to rodzaj skompresowanego widoku tego „filmu”, a przewidywanie to pobieranie informacji ze skompresowanego widoku. Powinno być naprawdę szybkie, niezależnie od tego, czy film był złożony, czy długi.
Zaimplementujmy to za pomocą szybkiego przykładu [Flask] w Pythonie!
Ogólna architektura uczenia maszynowego
Zacznijmy od nakreślenia ogólnego przepływu architektury szkolenia i prognozowania:
Najpierw tworzony jest potok treningowy w celu poznania danych z przeszłości zgodnie z funkcją celu.
Powinno to dać dwa kluczowe elementy:
- Funkcje inżynierii cech : przekształcenia użyte w czasie szkolenia powinny być ponownie użyte w czasie przewidywania.
- Parametry modelu : ostatecznie wybrany algorytm i hiperparametry należy zapisać, aby można je było ponownie wykorzystać w czasie prognozy
Należy pamiętać, że inżynieria funkcji wykonana w czasie szkolenia powinna być starannie zapisana, aby można ją było zastosować do przewidywania. Jednym z typowych problemów, które mogą się pojawić po drodze, jest skalowanie cech, które jest niezbędne dla wielu algorytmów.
Jeśli cecha X1 jest skalowana od wartości 1 do 1000 i jest przeskalowana do zakresu [0,1] za pomocą funkcji f(x) = x/max(X1) , co by się stało, gdyby zbiór predykcji miał wartość 2000?
Należy wcześniej pomyśleć o pewnych ostrożnych korektach, aby funkcja mapowania zwracała spójne dane wyjściowe, które zostaną poprawnie obliczone w czasie przewidywania.
Szkolenie w zakresie uczenia maszynowego a przewidywanie
W tym miejscu należy odpowiedzieć na główne pytanie. Dlaczego na początek oddzielamy trening od przewidywania?
Absolutną prawdą jest, że w kontekście przykładów i kursów uczenia maszynowego, gdzie wszystkie dane są znane z góry (w tym dane do przewidzenia), bardzo prostym sposobem na zbudowanie predyktora jest ułożenie w stos danych uczących i predykcyjnych (zwykle nazywanych zestaw testowy).
Następnie konieczne jest trenowanie na „zestawie uczącym” i przewidywanie na „zestawie testowym”, aby uzyskać wyniki, jednocześnie wykonując inżynierię funkcji na danych dotyczących trenowania i testowania, trenując i przewidując w tym samym i unikalnym potoku .
Jednak w rzeczywistych systemach zwykle masz dane treningowe, a dane, które mają być przewidywane, pojawiają się dokładnie w trakcie ich przetwarzania. Innymi słowy, oglądasz film w jednym czasie, a później masz kilka pytań na jego temat, co oznacza, że odpowiedzi powinny być łatwe i szybkie.
Co więcej, zwykle nie jest konieczne ponowne uczenie całego modelu za każdym razem, gdy pojawiają się nowe dane, ponieważ uczenie zajmuje trochę czasu (w przypadku niektórych zestawów obrazów może trwać tygodnie) i powinno być wystarczająco stabilne w czasie.
Dlatego uczenie i przewidywanie mogą być, a nawet powinny być wyraźnie oddzielone w wielu systemach, co również lepiej odzwierciedla sposób uczenia się inteligentnego systemu (sztucznego lub nie).
Połączenie z overfitting
Oddzielenie treningu i przewidywania to również dobry sposób na rozwiązanie problemu nadmiernego dopasowania.
W statystyce nadmierne dopasowanie to „tworzenie analizy, która zbyt ściśle lub dokładnie odpowiada określonemu zestawowi danych, a zatem może nie pasować do dodatkowych danych lub niezawodnie przewidywać przyszłe obserwacje”.
Nadmierne dopasowanie jest szczególnie widoczne w zestawach danych z wieloma funkcjami lub zestawach danych z ograniczonymi danymi treningowymi. W obu przypadkach dane zawierają zbyt dużo informacji w porównaniu do tego, co może zostać zweryfikowane przez predyktor, a niektóre z nich mogą nawet nie być powiązane z przewidywaną zmienną. W takim przypadku sam szum mógłby zostać zinterpretowany jako sygnał.
Dobrym sposobem kontrolowania overfittingu jest trenowanie na części danych i przewidywanie na innej części, na której mamy podstawową prawdę. Dlatego oczekiwany błąd na nowych danych jest w przybliżeniu błędem zmierzonym na tym zbiorze danych, pod warunkiem, że dane, na których uczymy, są reprezentatywne dla rzeczywistości systemu i jego przyszłych stanów.
Jeśli więc zaprojektujemy odpowiedni potok uczenia i predykcji wraz z poprawnym podziałem danych, nie tylko rozwiążemy problem nadmiernego dopasowania, ale także będziemy mogli ponownie wykorzystać tę architekturę do przewidywania nowych danych.
Ostatnim krokiem byłoby sprawdzenie, czy błąd w nowych danych jest taki sam, jak oczekiwany. Zawsze występuje przesunięcie (rzeczywisty błąd jest zawsze poniżej oczekiwanego) i należy określić, co jest akceptowalnym przesunięciem — ale to nie jest temat tego artykułu.
REST API do przewidywania
Tutaj przydaje się wyraźne oddzielenie treningu od przewidywania. Jeśli zapisaliśmy nasze metody inżynierii funkcji i parametry naszego modelu, możemy zbudować proste REST API z tymi elementami.

Kluczem jest tutaj załadowanie modelu i parametrów podczas uruchamiania API. Po uruchomieniu i zapisaniu w pamięci, każde wywołanie API wyzwala obliczenia inżynierii funkcji i metodę „przewidywania” algorytmu ML. Oba są zwykle wystarczająco szybkie, aby zapewnić odpowiedź w czasie rzeczywistym.
Interfejs API można zaprojektować tak, aby akceptował unikalny przykład do przewidzenia lub kilka różnych (prognozy wsadowe).
Oto minimalny kod Pythona/Flask, który implementuje tę zasadę, z JSON in i JSON out (pytanie wchodzące, odpowiedź wychodząca):
app = Flask(__name__) @app.route('/api/makecalc/', methods=['POST']) def makecalc(): """ Function run at each API call No need to re-load the model """ # reads the received json jsonfile = request.get_json() res = dict() for key in jsonfile.keys(): # calculates and predicts res[key] = model.predict(doTheCalculation(key)) # returns a json file return jsonify(res) if __name__ == '__main__': # Model is loaded when the API is launched model = pickle.load(open('modelfile', 'rb')) app.run(debug=True)Zauważ, że API może być używane do przewidywania na podstawie nowych danych, ale nie polecam używania go do trenowania modelu. Może być używany, ale komplikuje to kod treningowy modelu i może być bardziej wymagający pod względem zasobów pamięci.
Przykład wdrożenia — udostępnianie rowerów
Weźmy jako przykład zbiór danych Kaggle, udostępnianie rowerów. Załóżmy, że jesteśmy firmą wypożyczającą rowery, która chce codziennie prognozować liczbę wypożyczeń rowerów, aby lepiej zarządzać konserwacją rowerów, logistyką i innymi aspektami działalności.
Wypożyczenia zależą głównie od warunków pogodowych, więc dzięki prognozie pogody, firma może uzyskać lepszy pomysł, kiedy czynsze osiągną szczyt, i starać się unikać konserwacji w te dni.
Najpierw trenujemy model i zapisujemy go jako obiekt pikle, który można zobaczyć w notatniku Jupytera.
Nie zajmujemy się tutaj szkoleniem modeli i wydajnością, jest to tylko przykład zrozumienia pełnego procesu.
Następnie piszemy transformację danych, która będzie wykonywana przy każdym wywołaniu API:
import numpy as np import pandas as pd from datetime import date def doTheCalculation(data): data['dayofyear']=(data['dteday']- data['dteday'].apply(lambda x: date(x.year,1,1)) .astype('datetime64[ns]')).apply(lambda x: x.days) X = np.array(data[['instant','season','yr','holiday','weekday','workingday', 'weathersit','temp','atemp','hum','windspeed','dayofyear']]) return XTo jest tylko obliczenie zmiennej (dzień roku), aby uwzględnić zarówno miesiąc, jak i dokładny dzień. Istnieje również wybór kolumn i ich kolejność do zachowania.
Musimy zatem napisać REST API z Flask:
from flask import Flask, request, redirect, url_for, flash, jsonify from features_calculation import doTheCalculation import json, pickle import pandas as pd import numpy as np app = Flask(__name__) @app.route('/api/makecalc/', methods=['POST']) def makecalc(): """ Function run at each API call """ jsonfile = request.get_json() data = pd.read_json(json.dumps(jsonfile),orient='index',convert_dates=['dteday']) print(data) res = dict() ypred = model.predict(doTheCalculation(data)) for i in range(len(ypred)): res[i] = ypred[i] return jsonify(res) if __name__ == '__main__': modelfile = 'modelfile.pickle' model = pickle.load(open(modelfile, 'rb')) print("loaded OK") app.run(debug=True)Uruchom ten program, będzie on domyślnie obsługiwał API na porcie 5000.
Jeśli testujemy żądanie lokalnie, nadal w Pythonie:
import requests, json url = '[http://127.0.0.1:5000/api/makecalc/](http://127.0.0.1:5000/api/makecalc/)' text = json.dumps({"0":{"instant":1,"dteday":"2011-01-01T00:00:00.000Z","season":1,"yr":0,"mnth":1,"holiday":0,"weekday":6,"workingday":0,"weathersit":2,"temp":0.344167,"atemp":0.363625,"hum":0.805833,"windspeed":0.160446}, "1":{"instant":2,"dteday":"2011-01-02T00:00:00.000Z","season":1,"yr":0,"mnth":1,"holiday":0,"weekday":3,"workingday":0,"weathersit":2,"temp":0.363478,"atemp":0.353739,"hum":0.696087,"windspeed":0.248539}, "2":{"instant":3,"dteday":"2011-01-03T00:00:00.000Z","season":1,"yr":0,"mnth":1,"holiday":0,"weekday":1,"workingday":1,"weathersit":1,"temp":0.196364,"atemp":0.189405,"hum":0.437273,"windspeed":0.248309}})Żądanie zawiera wszystkie informacje, które zostały przekazane do modelu. Dlatego nasz model odpowie prognozą wypożyczeń rowerów w określonych terminach (tu mamy trzy z nich).
headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'} r = requests.post(url, data=text, headers=headers) print(r,r.text) <Response [200]> { "0": 1063, "1": 1028, "2": 1399 }Otóż to! Ta usługa może być łatwo wykorzystana w dowolnej aplikacji firmy, do planowania konserwacji lub aby użytkownicy byli świadomi ruchu rowerowego, popytu i dostępności wypożyczonych rowerów.
Kładąc wszystko razem
Główną wadą wielu systemów uczenia maszynowego, a zwłaszcza PoC, jest mieszanie treningu i przewidywania.
Jeśli są one starannie oddzielone, prognozy w czasie rzeczywistym można wykonać dość łatwo dla MVP, przy dość niskich kosztach i wysiłku programowania w Python/Flask, zwłaszcza jeśli dla wielu PoC zostały one początkowo opracowane przy użyciu Scikit-learn, Tensorflow, lub dowolna inna biblioteka uczenia maszynowego Pythona.
Jednak może to nie być możliwe dla wszystkich aplikacji, zwłaszcza aplikacji, w których inżynieria funkcji jest ciężka, lub aplikacji pobierających najbliższe dopasowanie, które muszą mieć dostęp do najnowszych danych przy każdym wywołaniu.
W każdym razie, czy musisz oglądać filmy w kółko, aby odpowiadać na pytania na ich temat? Ta sama zasada dotyczy uczenia maszynowego!
