Sådan automatiseres Analytics UX med panel

Datavidenskabs- og analyseteam skaber forudsigelige modeller, der hjælper med at løse forretningsspørgsmål, men de fleste af disse modeller implementeres ikke i produkter eller indlejres i applikationer, så hvor bor de? Hvad sker der, når slutbrugerne kræver output fra modeller, men der er ikke noget produkt, som et datavidenskabsteam alene kan opbygge for at gemme modeloutputene?

I et forsøg på at lade modeller eller analyser oprettet af datavidenskabsteam let udsættes for brugere gennem fristående visualiseringer eller produktionskvalitet webapplikationer, har Anaconda-teamet oprettet PyViz-platformen. Denne platform indeholder mange biblioteker, der gør både visualisering og dataanalyse i Python kort, ensartet og vigtigst gentaget.

Nogle udviklere af data science & analytics kender måske Bokeh og Datashader, men et nyt værktøj i PyViz-verdenen, Panel, blev introduceret på AnacondaCon 2019.

I denne artikel går vi nærmere ind på:

  1. Panel funktionalitet
  2. Panelforbrugssager
  3. Serverer en app ved hjælp af panelserver
  4. Produktionsinstallationer

Introduktion

Vi er Analytics- og Data Science-teamet under Under Armour, der arbejder med globalt forbrugerengagement. Mange af vores brugssager drejer sig om at forstå vores kunders behov gennem vores data fra første part og effektivt forudsige brugernes reaktion over for markedsføringstest via algoritmeudvikling. Et almindeligt problem, vi løber ind i, er, at vi ikke har en enkel måde at udsætte vores forudsigelige eller forklarende modeller for slutbrugere uden at involvere softwareteknikhold og bygge interne applikationer for at udsætte vores modeller. Med vores mål om at levere pålidelige forudsigelser hurtigt ved hjælp af vores algoritmer, havde vi brug for et værktøj, der ville afsløre output fra vores generaliserede modeller, og hvordan de ændrer sig baseret på brugerinput.

Panelfunktionalitet

Det, der adskiller Panel fra andre værktøjer i PyViz-økosystemet, er muligheden for at samle flere visualiseringsværktøjer og deres output til en centraliseret visualisering - plotte bibliotekets agnostik - og give slutbrugere mulighed for at definere data eller modelparametre via interaktive widgets. Alt det ovenstående leveres også med en letvægtsmetodologi til at distribuere Panel-applikationer til en webserver, som dine slutbrugere kan få adgang til. Python-scripts såvel som Jupyter-notebooks kan bruges til at udvikle og distribuere interaktive applikationer.

Brug sager

Nogle eksempler bruger sager, hvor panelet kunne bruges:

  1. Lad os sige, at du har trænet en prognosemodel baseret på din virksomheds salgsdata og har trukket modelvægterne. Panel giver mulighed for, at en slutbruger derefter kan indtaste nye datasalgstidsseriedata fra din virksomhed og forudsige dem med et variabelt antal perioder baseret på din model, og derudover kunne vise usikkerhedsintervaller.
  2. Du har trænet verdens bedste klassificering på MNIST-datasættet og gemt modellen. En bruger kan derefter medbringe sit eget billede af et nummer, der er tegnet for hånd, uploade det via din webapplikation, og din model viser dem forudsigelsen og dens sandsynligheder.
  3. Du har geospatiale data, der kan forudsige, hvornår der vil være mest trafik omkring busterminaler. Din slutbruger ville være i stand til at flytte en times skyder fra venstre til højre og se på forudsagt trafik eller efterspørgsel omkring busterminaler for at se, hvornår de skal købe deres billetter.

Hvis du gerne vil have flere potentielle brugssager, opfordrer vi dig til at tjekke i panelgalleriet.

Viser en app

Nu tager vi dig en ende til en ende ved at køre en grundlæggende analyse og distribuere den app til en panelserver på din lokale maskine. Med henblik på dette indlæg vil vi bruge vejledningsdata og oprette en ARIMA-model, med ringe eller ingen forudsigelsesevne, og implementere denne model. Formålet med dette er at vise, hvordan man implementerer en model, ikke hvordan man opretter en model, der effektivt kan forudsige noget.

Åbn din terminal, og sørg for, at det conda-miljø, du vil bruge, er aktiveret.

Installer panel ved hjælp af pyviz-kanalen med kommandoen nedenfor.

conda installere -c pyviz-panel

Åbn derefter en notesbog eller Python-editor og få data.

  • Vi bruger et af Seaborns datasæt om flypassagerer fra 1950'erne.

Du vil bemærke selvnotationen i denne metode - dette er fordi vi bygger dette ud som en del af en større panelklasse.

def get_data (self): df = pd.read_csv ('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/flights.csv') df ['month'] = df ['month']. \ Apply (lambda x: strptime (x, '% B'). tm_mon) df ['date'] = df [['year', 'month']]. \ Apply (lambda x: '-'. tilmeld ( x.astype (str)), axis = 1) + '- 01' df ['date'] = pd.to_datetime (df ['date']). dt.date df = df [['date', 'passagerer ']] self.data_frame = df.copy ()
  • Vi indstiller datarammen som en klasseattribut til senere brug.

Tilføj en skyder for datointerval til filtrering af modeldata

  • Nu går vi ind i paneldekoratorerne, der giver mulighed for tilbagekald, afhængigt af brugerinteraktion med widgets.
  • Der er mange forskellige typer widgets, der kan bruges gennem panelet, og hvis der ikke i øjeblikket er en, der understøttes, kan du gøre det selv med javascript og få det også tilbagekald til panelapplikationen! Se listen over aktuelt understøttede widgets.
  • I dette tilfælde tilføjer vi en skyder for datointerval for at give brugerne mulighed for at filtrere de data, der indføres i modellen.
  • Det er så let som dette:
date_slider = pn.widgets.DateRangeSlider (navn = 'Date Range Slider', start = dt.datetime (1949, 1, 1), end = dt.datetime (1960, 12, 1), værdi = (dt.datetime (1949 , 1, 1), dt.datetime (1960, 12, 1)), bredde = 300, bar_color = '# D15555')
  • Når du nu har funktioner, der afhænger af skyderen, behøver du kun at placere følgende dekoratør over din metodedefinition. @ Param.depends (date_slider.value ')

Opret en ARIMA-model på dataene

  • Nu kan vi definere modellen og plottet, der er forudsagt vs faktiske værdier.
  • Dette er en grundlæggende ARIMA eksponentiel udjævningsmodel.
@ param.depends ('date_slider.value') def arima_model (self): df_model = self.data_frame.copy () df_model = df_model. \ set_index (pd.DatetimeIndex (df_model ['date'], freq = 'MS') ). \ drop ('date', axis = 1) df_model = df_model [df_model.index> self.date_slider.value [0]] df_model = df_model [df_model.index 
  • Når modellen er trænet, oprettes en Bokeh-plot, der sammenligner faktiske kontra monterede værdier og tilføjer værktøjstip til diagrammet.

Opret programlayoutets panel

  • Nu kan vi vælge, hvordan vi vil layout vores applikation.
  • Da vi kun har et diagram, er det forholdsvis enkelt, men du kan også oprette kolonner og rækker og indsætte flere diagrammer i samme format.
  • Vi kalder også get_data () -metoden her for at sikre, at dataattributten er indstillet.
def-panel (selv): logo = "" " "" "self.get_data () return pn.Row (pn.Column (logo, self.date_slider), pn.Column (self.header, self.arima_model))

Den fulde fil skal se ud som følgende blok.

  • Bemærk nederst, vi kalder klasseobjektet og panelmetoden (), så det er klar til at blive vist.
import pandaer som pd import param import datetime som dt import panel som pn fra statsmodels.tsa.arima_model import ARIMA fra bokeh.models import HoverTool, SaveTool, PanTool, CrosshairTool fra bokeh.models.formatters importerer NumeralTickFormatter, DatetimeTickFormatter fra bokeh.plotting fra tiden importer strptime pn.extension () klasse panel_class (param.Parameterized): title = 'Forecasting Airline Passengers' undertekst = pn.pane.Str ('Faktisk vs ARIMA Model', style = {'font-size': '15pt '}) date_slider = pn.widgets.DateRangeSlider (navn =' Date Range Slider ', start = dt.datetime (1949, 1, 1), end = dt.datetime (1960, 12, 1), værdi = (dt. datetime (1949, 1, 1), dt.datetime (1960, 12, 1)), bredde = 300, bar_color = '# D15555') def get_data (self): df = pd.read_csv ('https: // raw .githubusercontent.com / mwaskom / seaborn-data / master / Flights.csv ') df [' month '] = df [' month ']. \ Apply (lambda x: strptime (x,'% B '). tm_mon) df ['date'] = df [['year', 'month']]. \ gælder (lambda x: '-'. join (x.astype (str)), axis = 1) + '- 01' df [ 'd ate '] = pd.to_datetime (df [' date ']). dt.date df = df [[' date ',' passagerer ']] self.data_frame = df.copy () @ param.depends (' date_slider. værdi ') def arima_model (self): df_model = self.data_frame.copy () df_model = df_model. \ set_index (pd.DatetimeIndex (df_model [' date '], freq =' MS ')). \ drop (' date ') , akse = 1) df_model = df_model [df_model.index> self.date_slider.value [0]] df_model = df_model [df_model.index 
# grundlæggende eksponentiel udjævningsmodel model = ARIMA (df_model, rækkefølge = (0,1,1), freq = 'MS') model_fit = model.fit (disp = 0) monteret_df = df_model.copy (). \ rename ({' passagerer ':' faktisk '}, akse = 1) monteret_df [' monteret '] = model_fit.predict (typ =' niveauer ') monteret_df [' fejl '] = abs (monteret_df [' faktisk '] - monteret_df [' monteret ' ]) fit_df ['perc_error'] = Fit_df ['error'] / Fit_df ['actual'] Fit_df.reset_index (inplace = True, drop = False) p = figur (plot_width = 800, plot_height = 266) p.line ( 'dato', 'faktisk', kilde = monteret_df, legend = 'Faktisk', farve = 'rød') glyph = p.line ('dato', 'monteret', kilde = monteret_df, legend = 'Tilpasset', farve = 'blå') p.yaxis.formatter = NumeralTickFormatter (format = "0,0") p.xaxis.formatter = DatetimeTickFormatter (måneder = "% m /% Y") p.title.text = 'Faktisk vs monterede ARIMA-værdier 'p.xaxis.axis_label =' Dato 'p.yaxis.axis_label =' Passagerer 'p.xgrid.grid_line_color = Ingen p.ygrid.grid_line_color = Ingen hover = HoverTool (renderers = [glyph], tooltips = [("Dato" , "@ date {% F}"), ("Monteret", "@ monteret"), ("Actua l "," @ actual ")], formatters = {" date ":" datetime "}, mode = 'vline') p.tools = [SaveTool (), PanTool (), hover, CrosshairTool ()] return p @ param.depends ('title') def header (self): title_panel = pn.pane.Str (self.title, style = {'font-size': '20pt', 'font-family': "Times New Roman" }) return title_panel @ param.depends ('subtitle') def subheader (self): return self.subtitle def panel (self): logo = "" " "" self.get_data () returnere pn.Row (pn.Column (logo, self.date_slider), pn.Column (self.header, self.subheader, self.arima_model)) panel_class (). panel (). servable ()

Når din app er klar, skal du gå videre og implementere. Gå til kommandolinjen, flyt til det bibliotek, der indeholder dit script. Når du er der, skal du sikre dig, at du har det rigtige conda-miljø, med panelet installeret og aktiveret. Når du er der, skal du køre følgende kommando:

panel tjene tutorial.py

Dette vil oprette en Bokeh-server på din lokale maskine på standardport 5006. Hvis du går http: // localhost: 5006 / tutorial, skal du se visualiseringen.

Produktionsdistribution på AWS EC2

Nu, hvor vi har gennemgået at oprette en lokal server til at vise vores app, kan vi gå videre til implementering af apps til produktionsmiljøer.

Med henblik på denne artikel bruger vi AWS EC2-forekomster. Selvom ingen af ​​panelkoderne er direkte afhængige af EC2, er vores beskrivelse af opsætning af forekomster og åbning af porte alt sammen baseret på EC2. Hvis du bruger en anden skyudbyder, bedes du henvise til deres dokumentation for opsætning af infrastrukturen.

Opret først en EC2-instans med et Ubuntu OS. Hvis du kun vil begrænse dine applikationer til brugere bag din organisations private VPC eller andet private netværk - især hvis du overhovedet håndterer personlige brugerdata - skal du sørge for at distribuere din EC2-instans på et privat undernet.

Installer derefter Anaconda på EC2-forekomsten. Når Anaconda-basismiljøet er klar, kan du dele miljøfiler (manuelt kopiere eller trække fra et kildekontrollager) for at oprette dine specifikke miljøer mellem din lokale maskine og EC2-instansen for at sikre, at implementeringsmiljøer er så tæt som muligt på eksperimentering og udviklingsmiljøer.

Ændring af dit script til at servere indhold

Nu kan vi udforske en af ​​de mest nyttige hjælpeprogrammer leveret af Bokeh-serveren: serverklassen. Serverklassen udsættes gennem bokeh.server-biblioteket og giver brugerne mulighed for at definere et serverobjektets applikationer og alle andre kommandolinjekonfigurationer gennem ren Python-kode, samt starte og stoppe en server inde i selve scriptet. Dette betyder, at vi kan vælge at eksponere flere applikationer til serveren på en ensartet og automatiseret måde og distribuere dem alle på den samme server og port.

Den følgende chunk finder alle Python-scripts inden for det samme bibliotek og undermapper som den originale fil og returnerer en liste med de indlæste panelapplikationer som objekter og en anden liste over deres respektive filnavne til hvert projekt.

import os import json import sys fra importlib.util import spec_from_loader, module_from_spec fra importlib.machinery import SourceFileLoader def get_modules (self, set_attribs = False): path = os.path.dirname (os.path.abspath (__ file__)) subdir = [ ] for i i os.scandir (sti): hvis i.is_dir (): subdir.append (i.path) subdir = list (filter (lambda k: 'ipynb' ikke i k og 'spy' ikke i k, subdir )) deposir = [] for i i undermappe: for x i os.scandir (i): hvis x.is_file () og x.path.endswith ('. py') == True og x.path.endswith (' main.py ') == Falsk: deposir.append (x.path) filenir_name = [x.rsplit (' / ', 1) [1] for x in deposir] # alt efter sidste skråstykke filenir_name = [x.rsplit ( '.py', 1) [0] for x in deposir_name] # alt før .py modules = [] for i inden for rækkevidde (0, len (deposir)): spec = spec_from_loader (name = deposir_name [i], loader = SourceFileLoader (fuldnavn = filenir_name [i], sti = filenir [i])) mod = module_from_spec (spec) # evaluering af modul spec.loader.exec_module (mod) # udførelse af modulmoduler.append (mod) # tilføj ing modul til at liste objekter = [] for i inden for rækkevidde (0, len (moduler)): x = moduler [i]. \ panel_class (name = str.replace (filen_navn [i], '_', '')). title ()) objects.append (x) hvis set_attribs == True: self.applications = objects self.filedir_name = deposir_name else: return objekter, filenir_name

Panel giver mulighed for, at enten Python-scripts, Jupyter-notebooks eller komplette mapper kan tjene som applikationer. En kortfattet måde at implementere hurtigt er at bruge Python-scripts, så i koden ovenfor filtrerer vi .ipynb-filer og alle andre ikke .py-filer, men dette kan let skiftes for at se efter disse filer eksplicit.

Når alle dine panelobjekter er læst på listen, kan vi fortsætte med at oprette serverobjektet. Lige før skal vi dog specificere en hjælperfunktion, der giver os mulighed for at ændre den indre funktionalitet af panelobjekterne, vi arbejder med:

def modify_doc (self, panel, doc): panel_obj = panel.panel () title = panel.title return panel_obj.server_doc (title = title, doc = doc)

Nu kan vi oprette serverobjektet.

I den følgende funktion tager vi applikationer og listen over applikationsnavne, der er returneret af funktionen get_modules ().

Afhængigt af hvor mange applikationer der blev fundet i biblioteket opretter vi derefter Bokeh-applikationsobjekter ved hjælp af en funktionshåndterer, da panelet giver mulighed for tilbagekald i dens applikationer.

I stedet for at skrive selve koden her, opretter vi et strengobjekt med den kode, som vi senere vurderer, for at være i stand til at automatisere processen med at definere applikationsobjekter i denne funktion.

Når løkken er færdig med at oprette strengen, opretter vi et serverobjekt, og det første argument til serverobjektet er den evaluerede strengkode, der indeholder vores applikationsobjekter i et format, som Bokeh-serveren kan implementere.

Bagefter defineres portparameteren, tilføjes et præfix om nødvendigt, og websocket-oprindelsen defineres.

  • Parameteren til websocket-oprindelse er vigtig, da den definerer IP-adresserne, som serveren skal lytte til og acceptere anmodninger fra. Flere oprindelser kan specificeres her.

Du skal indstille din EC2-forekomsts IP-adresse her, så serveren kan lytte til forespørgsler på den.

Hvis du vil tillade, at flere brugere eller flere sessioner serveres på én gang, skal du ændre parameteren num_procs for at tillade flere trådede processer på serveren.

Den sidste linje starter serveren og sørger for, at den kører, indtil selve serveren er lukket ned.

fra bokeh.server.server import server fra bokeh.application import Ansøgning fra bokeh.application.handlers.function import FunctionHandler fra functools import delvis def start_server (self, prefix_param = '', port_param = 5006): objekter = self.applications deposir_name = self.filedir_name base_command = '{' for i inden for rækkevidde (0, len (objekter)): hvis i 

Når vi sætter det hele sammen for fuldstændighed, har vi den programmatiske_serverklasse vist nedenfor.

  • I bunden har vi tilføjet kode for at kunne køre denne fil fra kommandolinjen.
fra bokeh.server.server import server fra bokeh.application import Ansøgning fra bokeh.application.handlers.function import FunktionHandler fra functools import delvis fra importlib.util import spec_from_loader, module_from_spec fra importlib.machinery import SourceFileLoader import os import sys klasse programmatic_server ( : def get_modules (self, set_attribs = False): path = os.path.dirname (os.path.abspath (__ file__)) subdir = [] for i i os.scandir (path): if i.is_dir (): subdir .append (i.path) subdir = liste (filter (lambda k: 'ipynb' ikke i k og 'spy' ikke i k, subdir)) filenir = [] for i i subdir: for x i os.scandir (i ): hvis x.is_file () og x.path.endswith ('. py') == True og x.path.endswith ('main.py') == False: deposir.append (x.path) filenir_name = [x.rsplit ('/', 1) [1] for x in deposir] deposir_name = [x.rsplit ('. py', 1) [0] for x in deposir_name] modules = [] for i inden for rækkevidde ( 0, len (deposir)): spec = spec_from_loader (name = deposir_name [i], loader = SourceFileLoader (fullname = deposir_name [i], pa th = deposir [i])) mod = module_from_spec (spec) # evaluering af module spec.loader.exec_module (mod) # udførelse af module modules.append (mod) # tilføjelse af modul til listeobjekter = [] for i inden for rækkevidde (0, len (moduler)): x = moduler [i] .panel_class (name = str.replace (filenir_name [i], '_', '') .title ()) objects.append (x) hvis set_attribs == True: self.applications = objekter self.filedir_name = deposir_name else: return objekter, filenir_name def modify_doc (self, panel, doc): panel_obj = panel.panel () title = panel.title return panel_obj.server_doc (title = title, doc = doc ) def start_server (self, prefix_param = '', port_param = 5006): objekter = self.applications deposir_name = self.filedir_name base_command = '{' for i inden for rækkevidde (0, len (objekter)): hvis jeg 

Gem dette script som programmatic_server.py.

Teoretisk set kan du nu placere N antal applikationer i dit bibliotek, og Bokeh-serveren kan betjene dem alle.

Du kan starte din server med følgende kommando på terminalen og sikre dig, at du bruger det rigtige conda-miljø:

python programmatic_server.py

Sørg for, at den valgte EC2-forekomstport er åben. Dette gøres ved at redigere de sikkerhedsgrupper, der er konfigureret på din instans til at acceptere indgående anmodninger på porten gennem TCP-protokollen.

Når det er klar, skal du navigere til http: // : / / og hvis du har flere applikationer, skal du se en Bokeh HTML-side, der viser alle de applikationer, der i øjeblikket serveres.

Hvis du kun har en ansøgning, vil dette indeks ikke være til stede, men hvis du navigerer til http: // : / / så skal du se din ansøgning.

Indekset kan deaktiveres med indstillingen --disibel-indeks og kan også tilpasses, men det er uden for denne artikels rækkevidde.

Struktur af apps til implementering

Metoden til implementering vist ovenfor fungerer kun, hvis alle dine apps er struktureret i samme format.

En klar måde at navngive din applikation er at bruge scriptnavnet som navnet på applikationen (som vises som fanenavn og hovedtitel på selve siden). Hvert script inkluderer også en panel_klasse, der indeholder al den relevante kode til at køre visualiseringen.

Hver panel_klasse kan have så mange hjælperfunktioner, som det er nødvendigt, det eneste krav er en metode med navnet panel, der inkluderer logikken til gengivelse af selve applikationen. Denne metode skal udføre alle dataindtagelses- og transformationsfunktioner samt modelleringsmetoder fra klassen.

Denne struktur giver os mulighed for at automatisere visningen af ​​apps på en kortfattet måde.

Strukturen, vi bruger, er den samme som vist i tutorial tidligere, den eneste forskel er, at vi fjerner den sidste linje panel_class (). Panel (). Servable (), da serveren tager sig af denne funktionalitet.

Al den kode, der bruges i dette indlæg, kan findes her: https://github.com/hrzumaeta/panel_post_tutorial

Nu har vi en intern server, som kan udsættes for brugere og kan oprettes og automatiseres rent i Python.

Det er det! Forhåbentlig kunne vi illustrere værdien af ​​et værktøj som Panel til datavidenskabsteam, og hvordan det kan gøre det muligt at anvende struktur til modeller, der driver beslutningstagning.

Hvis du har fundet lignende problemer og har tanker om denne tilgang, ville vi meget gerne høre dem!

Mange tak til resten af ​​det fulde team Phil Kim, Kate Sullivan, Tanvi Kode, Calvin Lau, Kameron Wells og Amol Sachdeva, som alle har bidraget til vores nuværende panelimplementering og til dette indlæg.

Hvis du nød denne tutorial og ønsker at lære mere om dette arbejde eller er interesseret i at blive medlem af et af vores datateams, kan du besøge vores Karriere-side og følge os på LinkedIn eller Instagram.