Sådan kan du træne en AI til at konvertere dine designmodeller til HTML og CSS

Inden for tre år vil dyb læring ændre frontend-udviklingen. Det øger prototypehastigheden og sænker barrieren for bygningssoftware.

Feltet startede sidste år, da Tony Beltramelli introducerede pix2code-papiret og Airbnb lancerede sketch2code.

Foto af Wesson Wang på Unsplash

I øjeblikket er den største barriere for at automatisere frontend-udvikling computerkraft. Vi kan dog bruge aktuelle dybe læringsalgoritmer sammen med syntetiserede træningsdata til at begynde at udforske kunstig front-end automatisering lige nu.

I dette indlæg lærer vi et neuralt netværk, hvordan man koder et grundlæggende HTML- og CSS-websted baseret på et billede af en designmockup. Her er en hurtig oversigt over processen:

1) Giv et designbillede til det træne neurale netværk

2) Det neurale netværk konverterer billedet til HTML-markering

3) gengivet output

Vi bygger det neurale netværk i tre iterationer.

Først laver vi en ren minimumsversion for at få et hængende på de bevægelige dele. Den anden version, HTML, vil fokusere på at automatisere alle trinene og forklare de neurale netværkslag. I den endelige version, Bootstrap, opretter vi en model, der kan generalisere og udforske LSTM-laget.

Al koden er klargjort på GitHub og FloydHub i Jupyter notebooks. Alle FloydHub-notebooks er inde i floydhub-biblioteket, og de lokale ækvivalenter er under lokale.

Modellerne er baseret på Beltramellis pix2code-papir og Jason Brownlee's billedtekstvejledninger. Koden er skrevet i Python og Keras, en ramme oven på TensorFlow.

Hvis du er ny med dyb læring, vil jeg anbefale at få en fornemmelse af Python, backpropagation og indviklede neurale netværk. Mine tre tidligere indlæg på FloydHubs blog vil komme i gang:

  • Min første weekend med dyb læring
  • Kodning af Deep Learnings historie
  • Farvelægning af sort / hvid-fotos med neurale netværk

Kerne logik

Lad os sammenfatte vores mål. Vi ønsker at opbygge et neuralt netværk, der genererer HTML / CSS-markering, der svarer til et skærmbillede.

Når du træner det neurale netværk, giver du det flere skærmbilleder med matchende HTML.

Det lærer ved at forudsige alle de matchende HTML-markeringskoder én efter én. Når det forudsiger det næste markup-tag, modtager det skærmbillede såvel som alle de korrekte markup-tags, indtil dette punkt.

Her er et simpelt træningsdataeksempel i en Google Sheet.

Oprettelse af en model, der forudsiger ord for ord, er den mest almindelige tilgang i dag. Der er andre tilgange, men det er den metode, vi vil bruge i hele denne tutorial.

Bemærk, at det for hver forudsigelse får det samme skærmbillede. Så hvis det skal forudsige 20 ord, vil det få den samme designmodel tyve gange. I øjeblikket skal du ikke bekymre dig om, hvordan det neurale netværk fungerer. Fokus på at fange input og output fra det neurale netværk.

Lad os fokusere på den forrige markering. Lad os sige, at vi træner netværket til at forudsige sætningen "Jeg kan kode." Når den modtager "jeg", så forudsiger den "kan." Næste gang den modtager "jeg kan" og forudsige "kode." Den modtager alle de tidligere ord og behøver kun at forudsige det næste ord.

Det neurale netværk opretter funktioner ud fra dataene. Netværket bygger funktioner til at forbinde inputdataene med outputdataene. Det skal oprette repræsentationer for at forstå, hvad der er i hvert skærmbillede, HTML-syntaks, som det har forudsagt. Dette bygger viden til at forudsige det næste tag.

Når du vil bruge den uddannede model til brug i den virkelige verden, ligner den, når du træner modellen. Teksten genereres en efter en med det samme skærmbillede hver gang. I stedet for at fodre den med de rigtige HTML-tags, modtager den den markering, den hidtil har genereret. Derefter forudsiger det det næste markeringskode. Forudsigelsen indledes med et "starttag" og stopper, når den forudsiger et "sluttag" eller når en maksimalgrænse. Her er et andet eksempel i en Google Sheet.

“Hello World” -version

Lad os opbygge en "hej verden" version. Vi giver et neuralt netværk et skærmbillede med et websted, der viser "Hello World!" Og lærer det at generere markeringen.

Først kortlægger det neurale netværk designmodellen til en liste over pixelværdier. Fra 0–255 i tre kanaler - rød, blå og grøn.

For at repræsentere markeringen på en måde, som det neurale netværk forstår, bruger jeg en varm kodning. Således kan sætningen "Jeg kan kode" kortlægges som nedenunder.

I ovenstående grafik inkluderer vi start- og sluttagget. Disse tags er signaler til, hvornår netværket starter sine forudsigelser, og hvornår de skal stoppe.

Til inputdataene bruger vi sætninger, der starter med det første ord og derefter tilføjer hvert ord en efter en. Outputdataene er altid et ord.

Setninger følger den samme logik som ord. De har også brug for den samme inputlængde. I stedet for at blive afkortet af ordforrådet, er de bundet af maksimal sætningslængde. Hvis det er kortere end den maksimale længde, udfylder du det med tomme ord, et ord med bare nuller.

Som du ser, udskrives ord fra højre til venstre. Dette tvinger hvert ord til at ændre position for hver træningsrunde. Dette gør det muligt for modellen at lære sekvensen i stedet for at huske placeringen af ​​hvert ord.

I nedenstående grafik er der fire forudsigelser. Hver række er en forudsigelse. Til venstre er billederne repræsenteret i deres tre farvekanaler: rød, grøn og blå og de foregående ord. Uden for parenteserne er forudsigelserne en efter en, der slutter med en rød firkant for at markere slutningen.

grønne blokke = startmærker | rød blok = slut token
# Længde på den længste sætning
max_caption_len = 3
# Størrelse af ordforråd
vokab_størrelse = 3
# Ilæg et skærmbillede for hvert ord, og vend dem til cifre
billeder = []
for jeg inden for rækkevidde (2):
    images.append (img_to_array (load_img ('screenshot.jpg', target_size = (224, 224))))
billeder = np.array (billeder, dtype = float)
# Forprocesindgang til VGG16-modellen
billeder = preprocess_input (billeder)
#Turn start-tokens til en varm kodning
html_input = np.array (
            [[[0., 0., 0.], # start
             [0., 0., 0.],
             [1., 0., 0.]],
             [[0., 0., 0.], #start  Hej verden! 
             [1., 0., 0.],
             [0., 1., 0.]]])
#Vend næste ord til en varm kodning
next_words = np.array (
            [[0., 1., 0.], #  Hej verden! 
             [0., 0., 1.]]) # ende
# Indlæs VGG16-modellen, der er trænet på imagenet, og udsæt klassificeringsfunktionen
VGG = VGG16 (vægt = 'imagenet', inkluderer_top = sandt)
# Uddrag funktionerne fra billedet
features = VGG.predict (billeder)
#Læg funktionen på netværket, påfør et tæt lag, og gentag vektoren
vgg_feature = input (form = (1000,))
vgg_feature_dense = Tæt (5) (vgg_feature)
vgg_feature_repeat = RepeatVector (max_caption_len) (vgg_feature_dense)
# Uddrag information fra indgangssekvensen
language_input = Input (form = (vokab_størrelse, vokab_størrelse))
language_model = LSTM (5, return_sequences = True) (language_input)
# Sammenkæd informationen fra billedet og input
dekoder = sammenkædet ([vgg_feature_repeat, language_model])
# Uddrag information fra den sammenkoblede output
dekoder = LSTM (5, return_sequences = False) (dekoder)
# Forudsig hvilket ord der kommer næste
dekoder_output = Tæt (vokab_størrelse, aktivering = 'softmax') (dekoder)
# Kompilér og kør det neurale netværk
model = Model (input = [vgg_feature, language_input], output = decoder_output)
model.compile (loss = 'categorical_crossentropy', optimizer = 'rmsprop')
# Træne det neurale netværk
model.fit ([features, html_input], next_words, batch_size = 2, shuffle = False, epoker = 1000)

I Hello World-versionen bruger vi tre tokens: start,

Hello World!

og slut. Et symbol kan være hvad som helst. Det kan være et tegn, et ord eller en sætning. Tegnversioner kræver et mindre ordforråd, men begrænser det neurale netværk. Word-symboler har en tendens til at være bedst.

Her laver vi forudsigelsen:

# Opret en tom sætning, og indsæt starttoken
sætning = np.zeros ((1, 3, 3)) # [[0,0,0], [0,0,0], [0,0,0]]
start_token = [1., 0., 0.] # start
sætning [0] [2] = start_token # sted start i tom sætning
    
# Foretag den første forudsigelse med starttoken
second_word = model.predict ([np.array ([features [1]]), sætning])
    
# Sæt det andet ord i sætningen og lav den endelige forudsigelse
sætning [0] [1] = starttoken
sætning [0] [2] = np.round (second_word)
Third_word = model.predict ([np.array ([features [1]]), sætning])
    
# Placer starttoken og vores to forudsigelser i sætningen
sætning [0] [0] = starttoken
sætning [0] [1] = np.round (second_word)
sætning [0] [2] = np.round (tredje_ord)
    
# Transformer vores one-hot forudsigelser til de endelige tokens
vocabulary = ["start", " 

Hej verden!

", "slut"] for i i sætning [0]: print (ordforråd [np.argmax (i)], end = '')

Produktion

  • 10 epoker: start start start
  • 100 epoker: start

    Hello World!

    Hello World!

  • 300 epoker: start

    Hej verden!

    slut

Fejl, jeg lavede:

  • Byg den første arbejdsversion, inden du indsamler dataene. Tidligt i dette projekt lykkedes det mig at få en kopi af et gammelt arkiv på webstedet Geocities hosting. Det havde 38 millioner websteder. Blindet af potentialet ignorerede jeg den enorme arbejdsbelastning, der ville være påkrævet for at reducere ordforrådet på 100K.
  • At håndtere en terabyte-værdi af data kræver god hardware eller meget tålmodighed. Efter at have fået min mac ind i flere problemer endte jeg med at bruge en stærk fjernserver. Forvent at leje en rig med 8 moderne CPU-kerner og en 1GPS internetforbindelse for at have en anstændig arbejdsgang.
  • Intet gav mening, før jeg forstod input- og outputdataene. Input, X, er et skærmbillede og de forrige markup tags. Outputet, Y, er det næste markeringskode. Da jeg fik dette, blev det lettere at forstå alt mellem dem. Det blev også lettere at eksperimentere med forskellige arkitekturer.
  • Vær opmærksom på kaninhullerne. Fordi dette projekt krydser en masse felter i dyb læring, blev jeg fast i masser af kaninhuller undervejs. Jeg tilbragte en uges programmering af RNN'er fra bunden, blev for fascineret af indlejring af vektorrum og blev forført af eksotiske implementeringer.
  • Billed-til-kode netværk er billedtekstmodeller i forklædning. Selv når jeg lærte dette, ignorerede jeg stadig mange af billedtekstpapirerne, simpelthen fordi de var mindre seje. Når jeg fik et perspektiv, fremskyndte jeg min indlæring af problemområdet.

Kører koden på FloydHub

FloydHub er en træningsplatform for dyb læring. Jeg stødte på dem, da jeg først begyndte at lære dyb læring, og jeg har brugt dem siden til træning og styring af mine dybe læringseksperimenter. Du kan køre din første model inden for 30 sekunder ved at klikke her.

Det åbner et arbejdsområde på FloydHub, hvor du finder det samme miljø og datasæt, der blev brugt til Bootstrap-versionen. Du kan også finde de uddannede modeller til test.

Eller du kan udføre en manuel installation ved at følge disse trin: 2-minutters installation eller min 5-minutters gennemgang.

Klon depotet

git klon https://github.com/emilwallner/Screenshot-to-code-in-Keras.git

Log ind og start FloydHub kommandolinjeværktøj

cd-skærmbillede-til-kode-i-Keras
floyd login
floyd init s2c

Kør en Jupyter-notebook på en FloydHub-sky GPU-maskine:

floyd run --gpu --env tensorflow-1.4 - data emilwallner / datasæt / imagetocode / 2: data - mode jupyter

Alle notebooks er forberedt inde i FloydHub-biblioteket. De lokale ækvivalenter er under lokale. Når den kører, kan du finde den første notebook her: floydhub / Helloworld / helloworld.ipynb.

Hvis du vil have mere detaljerede instruktioner og en forklaring på flagene, skal du tjekke mit tidligere indlæg.

HTML-version

I denne version automatiserer vi mange af trinnene fra Hello World-modellen. Dette afsnit fokuserer på at skabe en skalerbar implementering og bevægelige stykker i det neurale netværk.

Denne version vil ikke være i stand til at forudsige HTML fra tilfældige websteder, men det er stadig en god opsætning at udforske dynamikken i problemet.

Oversigt

Hvis vi udvider komponenterne i den forrige grafik, ser det sådan ud.

Der er to hovedafsnit. Først koderen. Det er her vi opretter billedfunktioner og tidligere markeringsfunktioner. Funktioner er de byggesten, som netværket opretter for at forbinde designmodellerne med markeringen. I slutningen af ​​koderen limer vi billedfunktionerne til hvert ord i den forrige markering.

Dekoderen tager derefter den kombinerede design- og markeringsfunktion og opretter en næste tagfunktion. Denne funktion køres gennem et fuldt tilsluttet neuralt netværk for at forudsige det næste tag.

Design mockup-funktioner

Da vi skal indsætte et skærmbillede for hvert ord, bliver dette en flaskehals ved træning af netværket (eksempel). I stedet for at bruge billederne, trækker vi ud de oplysninger, vi har brug for for at generere markeringen.

Oplysningerne er kodet til billedfunktioner. Dette gøres ved at bruge et allerede foruddannet indviklet neuralt netværk (CNN). Modellen er foruddannet på Imageet.

Vi uddrager funktionerne fra laget før den endelige klassificering.

Vi ender med 1536 otte af otte pixelbilleder kendt som funktioner. Selvom de er svære at forstå for os, kan et neuralt netværk udtrække elementernes objekter og placering fra disse funktioner.

Markup-funktioner

I Hello World-versionen brugte vi en one-hot kodning til at repræsentere markeringen. I denne version bruger vi et ordindlejring til inputen og beholder kodningen one-hot til output.

Måden vi strukturerer hver sætning forbliver den samme, men hvordan vi kortlægger hvert token ændres. En varm kodning behandler hvert ord som en isoleret enhed. I stedet konverterer vi hvert ord i inputdataene til lister med cifre. Disse repræsenterer forholdet mellem markeringskoder.

Dimensionen af ​​dette ordindlejring er otte, men varierer ofte mellem 50–500 afhængigt af ordforrådets størrelse.

De otte cifre for hvert ord er vægte, der ligner et vanilje-neuralt netværk. De er indstillet til at kortlægge, hvordan ordene forholder sig til hinanden (Mikolov et al., 2013).

Sådan begynder vi at udvikle markupfunktioner. Funktioner er det, det neurale netværk udvikler til at forbinde inputdataene med outputdataene. I øjeblikket skal du ikke bekymre dig om, hvad de er. Vi vil uddybe dette i det næste afsnit.

Koderen

Vi tager ordindlejringer og kører dem gennem en LSTM og returnerer en række markupfunktioner. Disse køres gennem et tidsfordelt tæt lag - tænk på det som et tæt lag med flere input og output.

Parallelt udflades først billedfunktionerne. Uanset hvordan cifrene blev struktureret, omdannes de til en stor liste over numre. Derefter anvender vi et tæt lag på dette lag for at danne en funktion på højt niveau. Disse billedfunktioner samles derefter til markeringsfunktionerne.

Dette kan være svært at ombryde dit sind - så lad os nedbryde det.

Markup-funktioner

Her kører vi ordindlejringer gennem LSTM-laget. I denne grafik er alle sætninger polstret for at nå den maksimale størrelse på tre symboler.

For at blande signaler og finde mønstre på højere niveau anvender vi et TimeDistribueret tæt lag til markeringsfunktionerne. TimeDistribueret tæt er det samme som et tæt lag, men med flere indgange og output.

Billedfunktioner

Parallelt forbereder vi billederne. Vi tager alle mini-billedfunktioner og omdanner dem til en lang liste. Oplysningerne ændres ikke, bare omorganiseres.

For at blande signaler og udtrække forestillinger på højere niveau anvender vi igen et tæt lag. Da vi kun har at gøre med en inputværdi, kan vi bruge et normalt tæt lag. For at forbinde billedfunktionerne til markeringsfunktionerne kopierer vi billedfunktionerne.

I dette tilfælde har vi tre markeringsfunktioner. Således ender vi med en lige stor mængde billedfunktioner og markupfunktioner.

Sammenføjning af billed- og markeringsfunktioner

Alle sætninger er polstret for at oprette tre markeringsfunktioner. Da vi har forberedt billedfunktionerne, kan vi nu tilføje en billedfunktion til hver markeringsfunktion.

Når vi har klistret en billedfunktion til hver markeringsfunktion, ender vi med tre billedmarkeringsfunktioner. Dette er input, som vi indfører i dekoderen.

Dekoderen

Her bruger vi de kombinerede billedmarkeringsfunktioner til at forudsige det næste tag.

I nedenstående eksempel bruger vi tre billedmarkeringsfunktionspar og udsender en næste tagfunktion.

Bemærk, at LSTM-laget har sekvensen indstillet til usand. I stedet for at returnere længden af ​​indgangssekvensen, forudsiger den kun en funktion. I vores tilfælde er det en funktion til det næste tag. Det indeholder oplysningerne til den endelige forudsigelse.

Den endelige forudsigelse

Det tætte lag fungerer som et traditionelt feedforward neuralt netværk. Det forbinder 512 cifre i den næste tagfunktion med de 4 endelige forudsigelser. Lad os sige, at vi har 4 ord i vores ordforråd: start, hej, verden og slut.

Ordforrådsprognosen kunne være [0,1, 0,1, 0,1, 0,7]. Softmax-aktiveringen i det tætte lag fordeler en sandsynlighed fra 0–1, med summen af ​​alle forudsigelser lig med 1. I dette tilfælde forudsiger det, at det fjerde ord er det næste tag. Derefter oversætter du den one-hot kodning [0, 0, 0, 1] til den kortlagte værdi, siger ”slut”.

# Indlæs billederne og forarbejd dem til start-resnet
billeder = []
all_filenames = listdir ('billeder /')
all_filenames.sort ()
til filnavn i alle_filnavn:
    images.append (img_to_array (load_img ('images /' + filnavn, target_size = (299, 299))))
billeder = np.array (billeder, dtype = float)
billeder = preprocess_input (billeder)
# Kør billederne gennem inception-resnet og ekstraher funktionerne uden klassificeringslaget
IR2 = InceptionResNetV2 (vægt = 'imagenet', inkluderer_top = falsk)
features = IR2.predict (billeder)
# Vi sætter hver indgangssekvens på 100 tokens
max_caption_len = 100
# Initialiser den funktion, der skaber vores ordforråd
tokenizer = Tokenizer (filtre = '', split = "", lavere = falsk)
# Læs et dokument, og returner en streng
def load_doc (filnavn):
    fil = åben (filnavn, 'r')
    tekst = file.read ()
    file.close ()
    returnere tekst
# Indlæs alle HTML-filer
X = []
all_filenames = listdir ('html /')
all_filenames.sort ()
til filnavn i alle_filnavn:
    X.append (load_doc (html / '+ filnavn))
# Opret ordforrådet fra html-filerne
tokenizer.fit_on_texts (X)
# Tilføj +1 for at efterlade plads til tomme ord
vocab_size = len (tokenizer.word_index) + 1
# Oversæt hvert ord i tekstfilen til det matchende ordforrådsindeks
sekvenser = tokenizer.texts_to_sequences (X)
# Den længste HTML-fil
max_length = max (len (s) for s i sekvenser)
# Intialize vores endelige input til modellen
X, y, image_data = liste (), liste (), liste ()
for img_no, seq i enumerate (sekvenser):
    for i inden for rækkevidde (1, len (seq)):
        # Føj hele sekvensen til inputen og gem kun det næste ord for output
        in_seq, out_seq = seq [: i], seq [i]
        # Hvis sætningen er kortere end max_length, skal du udfylde den med tomme ord
        in_seq = pad_sequences ([in_seq], maxlen = max_length) [0]
        # Kortlæg output til en varm kodning
        out_seq = to_categorical ([out_seq], num_classes = vocab_size) [0]
        # Tilføj og billede svarende til HTML-filen
        image_data.append (funktioner [img_no])
        # Klip indgangssætningen til 100 tokens, og tilføj den til inputdataene
        X.append (in_seq [-100:])
        y.append (out_seq)
X, y, image_data = np.array (X), np.array (y), np.array (image_data)
# Opret koderen
image_features = input (form = (8, 8, 1536,))
image_flat = Flad () (image_features)
image_flat = Tæt (128, aktivering = 'relu') (image_flat)
ir2_out = RepeatVector (max_caption_len) (image_flat)
language_input = Input (form = (max_caption_len,))
language_model = Indlejring (vokab_størrelse, 200, input_length = max_caption_len) (language_input)
language_model = LSTM (256, return_sequences = True) (language_model)
language_model = LSTM (256, return_sequences = True) (language_model)
language_model = TimeDistribueret (tæt (128, aktivering = 'relu')) (language_model)
# Opret dekoderen
dekoder = sammenkædet ([ir2_out, language_model])
dekoder = LSTM (512, return_sequences = False) (dekoder)
dekoder_output = Tæt (vokab_størrelse, aktivering = 'softmax') (dekoder)
# Kompilér modellen
model = Model (input = [image_features, language_input], output = decoder_output)
model.compile (loss = 'categorical_crossentropy', optimizer = 'rmsprop')
# Træne det neurale netværk
model.fit ([image_data, X], y, batch_size = 64, shuffle = False, epoker = 2)
# kortlægge et heltal til et ord
def word_for_id (heltal, tokenizer):
    for word, index i tokenizer.word_index.items ():
        hvis indeks == heltal:
            returner ord
    retur Ingen
# generer en beskrivelse af et billede
def generere_desc (model, tokenizer, foto, max_length):
    # frø genereringsprocessen
    in_text = 'START'
    # iterere over hele længden af ​​sekvensen
    for i inden for rækkevidde (900):
        # heltalkodning input sekvens
        rækkefølge = tokenizer.texts_to_sequences ([in_text]) [0] [- 100:]
        # pad input
        rækkefølge = pad_sequences ([sekvens], maxlen = max_length)
        # forudsige næste ord
        yhat = model.predict ([foto, sekvens], verbose = 0)
        # konverter sandsynlighed til heltal
        yhat = np.argmax (yhat)
        # kort heltal til ord
        word = word_for_id (yhat, tokenizer)
        # stop, hvis vi ikke kan kortlægge ordet
        hvis ordet er Ingen:
            pause
        # tilføj som input til generering af det næste ord
        in_text + = '' + ord
        # Udskriv forudsigelse
        print ('' + ord, ende = '')
        # stop, hvis vi forudsiger slutningen af ​​sekvensen
        hvis ord == 'END':
            pause
    Vend tilbage
# Indlæs og billede, forarbejd det til IR2, udpak funktioner og generer HTML
test_image = img_to_array (load_img ('images / 87.jpg', target_size = (299, 299)))
test_image = np.array (test_image, dtype = float)
test_image = preprocess_input (test_image)
test_features = IR2.predict (np.array ([test_image]))
generere_desc (model, tokenizer, np.array (test_features), 100)

Produktion

Links til genererede websteder

  • 250 epoker
  • 350 epoker
  • 450 epoker
  • 550 epoker

Hvis du ikke kan se noget, når du klikker på disse links, kan du højreklikke og klikke på "Vis sidekilde." Her er det originale websted til reference.

Fejl, jeg lavede:

  • LSTM'er er meget tungere for min kognition sammenlignet med CNN'er. Da jeg rullede alle LSTM'erne ud, blev de lettere at forstå. Fast.ais video om RNN'er var super nyttige. Fokuser også på input- og outputfunktionerne, før du prøver at forstå, hvordan de fungerer.
  • Det er meget lettere at opbygge et ordforråd fra bunden end at indsnævre et stort ordforråd. Dette inkluderer alt fra skrifttyper, div-størrelser og hexfarver til variabelnavne og normale ord.
  • De fleste af bibliotekerne er oprettet til at analysere tekstdokumenter og ikke kode. I dokumenter er alt adskilt af et mellemrum, men i kode skal du brugerdefineret parsing.
  • Du kan udtrække funktioner med en model, der er trænet på Imagenet. Dette kan virke counterintuitive, da Imaget har få webbilleder. Imidlertid er tabet 30% højere sammenlignet med en pix2code-model, der trænes fra bunden. Det ville være interessant at bruge en pre-train inception-resnet-type model baseret på web-skærmbilleder.

Bootstrap version

I vores endelige version bruger vi et datasæt med genererede bootstrap-websteder fra pix2code-papiret. Ved at bruge Twitter's bootstrap kan vi kombinere HTML og CSS og mindske størrelsen på ordforrådet.

Vi aktiverer den til at generere markeringen for et skærmbillede, det ikke har set før. Vi undersøger også, hvordan det bygger viden om skærmbillede og markering.

I stedet for at træne den i bootstrap-markeringen, bruger vi 17 forenklede tokens, som vi derefter oversætter til HTML og CSS. Datasættet inkluderer 1500 testskærmbilleder og 250 valideringsbilleder. For hvert skærmbillede er der gennemsnitligt 65 tokens, hvilket resulterer i 96925 træningseksempler.

Ved at finjustere modellen i pix2code-papiret kan modellen forudsige webkomponenterne med 97% nøjagtighed (BLEU 4-gram grådig søgning, mere om dette senere).

En ende til ende tilgang

Ekstraktion af funktioner fra foruddannede modeller fungerer godt i billedtekstmodeller. Men efter et par eksperimenter indså jeg, at pix2codes end-to-end-tilgang fungerer bedre til dette problem. De foruddannede modeller er ikke blevet trænet i webdata og tilpasses til klassificering.

I denne model erstatter vi de foruddannede billedfunktioner med et let indviklet neuralt netværk. I stedet for at bruge max-pooling for at øge informationstætheden, øger vi skridt. Dette opretholder positionen og farven på frontendelementerne.

Der er to kernemodeller, der muliggør dette: indviklede neurale netværk (CNN) og tilbagevendende neurale netværk (RNN). Det mest almindelige tilbagevendende neurale netværk er langtidshukommelse (LSTM), så det er det, jeg vil henvise til.

Der er masser af gode CNN-tutorials, og jeg dækkede dem i min tidligere artikel. Her vil jeg fokusere på LSTM'erne.

Forståelse af tidspunkter i LSTM'er

En af de sværere ting at forstå om LSTM'er er tidspor. Et vanilje-neuralt netværk kan betragtes som to tidspunkter. Hvis du giver det "Hej", forudsiger det "Verden." Men det ville kæmpe for at forudsige flere tidspunkter. I nedenstående eksempel har indgangen fire tidspunkter, en for hvert ord.

LSTM'er er lavet til input med tidspunkter. Det er et neuralt netværk, der er tilpasset til information i rækkefølge. Hvis du rullerer vores model, ser den sådan ud. For hvert nedadgående trin holder du de samme vægte. Du anvender et sæt vægte på den forrige output og et andet sæt til den nye input.

Den vægtede input og output samles og tilføjes sammen med en aktivering. Dette er output for det tidspunkt. Da vi genbruger vægtene, trækker de information fra flere input og bygger viden om sekvensen.

Her er en forenklet version af processen for hvert tidsinterval i en LSTM.

For at få en fornemmelse af denne logik vil jeg anbefale at bygge et RNN fra bunden med Andrew Trask's strålende tutorial.

Forstå enhederne i LSTM-lag

Antallet af enheder i hvert LSTM-lag bestemmer dets evne til at huske. Dette svarer også til størrelsen på hver outputfunktion. Igen er en funktion en lang liste over numre, der bruges til at overføre information mellem lag.

Hver enhed i LSTM-laget lærer at holde styr på forskellige aspekter af syntaks. Nedenfor er en visualisering af en enhed, der holder styr på informationen i rækkeinddelingen. Dette er den forenklede markering, vi bruger til at træne bootstrap-modellen.

Hver LSTM-enhed opretholder en celletilstand. Tænk på celletilstanden som hukommelsen. Vægtene og aktiveringerne bruges til at ændre tilstanden på forskellige måder. Dette gør det muligt for LSTM-lagene at finjustere, hvilke oplysninger der skal opbevares og kasseres for hver input.

Ud over at passere gennem en outputfunktion for hver input fremsender den også celletilstandene, en værdi for hver enhed i LSTM. For at få en fornemmelse af, hvordan komponenterne i LSTM interagerer, anbefaler jeg Colahs tutorial, Jayasiris Numpy-implementering og Karphays forelæsning og opskrivning.

dir_name = 'ressourcer / eval_light /'
# Læs en fil, og returner en streng
def load_doc (filnavn):
    fil = åben (filnavn, 'r')
    tekst = file.read ()
    file.close ()
    returnere tekst
def load_data (data_dir):
    tekst = []
    billeder = []
    # Indlæs alle filerne, og bestil dem
    all_filenames = listdir (data_dir)
    all_filenames.sort ()
    til filnavn i (alle_filnavn):
        hvis filnavn [-3:] == "npz":
            # Indlæs de allerede forberedte billeder i matriser
            image = np.load (data_dir + filnavn)
            images.append (billede [ 'features'])
        andet:
            # Læg boostrap-symbolerne, og rap dem i et start- og slutmærke
            syntax = '' + load_doc (data_dir + filnavn) + ''
            # Adskil alle ordene med et enkelt mellemrum
            syntax = '' .join (syntax.split ())
            # Tilføj et mellemrum efter hvert komma
            syntax = syntax.replace (',', ',')
            text.append (syntaks)
    billeder = np.array (billeder, dtype = float)
    returner billeder, tekst
train_features, tekster = load_data (dir_name)
# Initialiser funktionen for at oprette ordforrådet
tokenizer = Tokenizer (filtre = '', split = "", lavere = falsk)
# Opret ordforrådet
tokenizer.fit_on_texts ([load_doc (bootstrap.vocab ')])
# Tilføj et sted for det tomme ord i ordforrådet
vocab_size = len (tokenizer.word_index) + 1
# Kortlægge indgangssætningerne i ordforrådsindekserne
train_sequences = tokenizer.texts_to_sequences (tekster)
# Det længste sæt boostrap-symboler
max_sequence = max (len (s) for s i train_sequences)
# Angiv, hvor mange tokens der skal være i hver input sætning
max_length = 48
def preprocess_data (sekvenser, funktioner):
    X, y, image_data = liste (), liste (), liste ()
    for img_no, seq i enumerate (sekvenser):
        for i inden for rækkevidde (1, len (seq)):
            # Tilføj sætningen indtil det aktuelle antal (i), og tilføj det aktuelle antal til output
            in_seq, out_seq = seq [: i], seq [i]
            # Pude alle input-token-sætninger til max_sequence
            in_seq = pad_sequences ([in_seq], maxlen = max_sequence) [0]
            # Drej output til en hot-kodning
            out_seq = to_categorical ([out_seq], num_classes = vocab_size) [0]
            # Føj det tilsvarende billede til boostrap-token-filen
            image_data.append (funktioner [img_no])
            # Sæt indgangssætningen på 48 tegn, og tilføj den
            X.append (in_seq [-48:])
            y.append (out_seq)
    return np.array (X), np.array (y), np.array (image_data)
X, y, image_data = preprocess_data (train_sequences, train_features)
# Opret koderen
image_model = Sekventiel ()
image_model.add (Conv2D (16, (3, 3), polstring = 'gyldig', aktivering = 'relu', input_shape = (256, 256, 3,)))
image_model.add (Conv2D (16, (3,3), aktivering = 'relu', polstring = 'same', skridt = 2))
image_model.add (Conv2D (32, (3,3), aktivering = 'relu', polstring = 'same'))
image_model.add (Conv2D (32, (3,3), aktivering = 'relu', polstring = 'same', skridt = 2))
image_model.add (Conv2D (64, (3,3), aktivering = 'relu', polstring = 'same'))
image_model.add (Conv2D (64, (3,3), aktivering = 'relu', polstring = 'same', skridt = 2))
image_model.add (Conv2D (128, (3,3), aktivering = 'relu', polstring = 'same'))
image_model.add (Samkopier ())
image_model.add (Tæt (1024, aktivering = 'relu'))
image_model.add (Dropout (0,3))
image_model.add (Tæt (1024, aktivering = 'relu'))
image_model.add (Dropout (0,3))
image_model.add (RepeatVector (MAX_LENGTH))
visual_input = input (form = (256, 256, 3,))
encoded_image = image_model (visual_input)
language_input = input (form = (max_length,))
language_model = Indlejring (vokab_størrelse, 50, input_length = max_length, mask_zero = True) (language_input)
language_model = LSTM (128, return_sequences = True) (language_model)
language_model = LSTM (128, return_sequences = True) (language_model)
# Opret dekoderen
dekoder = sammenkædet ([kodet_billede, sprogmodel])
dekoder = LSTM (512, return_sequences = True) (dekoder)
dekoder = LSTM (512, return_sequences = False) (dekoder)
dekoder = tæt (vokab_størrelse, aktivering = 'softmax') (dekoder)
# Kompilér modellen
model = Model (input = [visual_input, language_input], output = dekoder)
optimizer = RMSprop (lr = 0,0001, clipvalue = 1,0)
model.compile (loss = 'categorical_crossentropy', optimizer = optimizer)
# Gem modellen for hver 2. epoke
filepath = "org-vægte-epoch- {epoch: 04D} - val_loss- {val_loss: .4f} - underskudsgivende {tab: .4f} .hdf5"
checkpoint = ModelCheckpoint (filepath, monitor = 'val_loss', verbose = 1, save_weights_only = True, period = 2)
callbacks_list = [checkpoint]
# Træn modellen
model.fit ([image_data, X], y, batch_size = 64, shuffle = False, validation_split = 0,1, callbacks = callbacks_list, verbose = 1, epochs = 50)

Testnøjagtighed

Det er vanskeligt at finde en fair måde at måle nøjagtigheden på. Sig, at du sammenligner ord for ord. Hvis din forudsigelse er et ord, der ikke er synkroniseret, har du muligvis 0% nøjagtighed. Hvis du fjerner et ord, der synkroniserer forudsigelsen, kan du ende med 99/100.

Jeg brugte BLEU-score, bedste praksis i maskinoversættelse og billedtekstmodeller. Det opdeler sætningen i fire n-gram fra 1-4 ordssekvenser. I nedenstående forudsigelse antages "kat" at være "kode."

For at få den endelige score multiplicerer du hver score med 25%, (4/5) * 0,25 + (2/4) * 0,25 + (1/3) * 0,25 + (0/2) * 0,25 = 0,2 + 0,125 + 0,083 + 0 = 0,408. Summen ganges derefter med en sætningslængde. Da længden er korrekt i vores eksempel, bliver det vores endelige score.

Du kan øge antallet af n-gram for at gøre det sværere. En fire-n-model er den model, der bedst svarer til menneskelige oversættelser. Jeg vil anbefale at køre et par eksempler med nedenstående kode og læse wikisiden.

# Opret en funktion til at læse en fil og returnere dens indhold
def load_doc (filnavn):
    fil = åben (filnavn, 'r')
    tekst = file.read ()
    file.close ()
    returnere tekst
def load_data (data_dir):
    tekst = []
    billeder = []
    files_in_folder = os.listdir (data_dir)
    files_in_folder.sort ()
    til filnavn i tqdm (files_in_folder):
        #Tilføj et billede
        hvis filnavn [-3:] == "npz":
            image = np.load (data_dir + filnavn)
            images.append (billede [ 'features'])
        andet:
        # Tilføj tekst og indpak det i et start- og sluttag
            syntax = '' + load_doc (data_dir + filnavn) + ''
            # Adskil hvert ord med et mellemrum
            syntax = '' .join (syntax.split ())
            #Tilføj et mellemrum mellem hvert komma
            syntax = syntax.replace (',', ',')
            text.append (syntaks)
    billeder = np.array (billeder, dtype = float)
    returner billeder, tekst
# Identificer funktionen for at oprette ordforrådet
tokenizer = Tokenizer (filtre = '', split = "", lavere = falsk)
# Opret ordforrådet i en bestemt rækkefølge
tokenizer.fit_on_texts ([load_doc (bootstrap.vocab ')])
dir_name = '../../../../eval/'
train_features, tekster = load_data (dir_name)
#load model og vægte
json_file = åben ('../../../../ model.json', 'r')
load_model_json = json_file.read ()
json_file.close ()
load_model = model_from_json (load_model_json)
# indlæse vægte i ny model
loaded_model.load_weights (" ../../../../ weights.hdf5" )
print ("Indlæst model fra disk")
# kortlægge et heltal til et ord
def word_for_id (heltal, tokenizer):
    for word, index i tokenizer.word_index.items ():
        hvis indeks == heltal:
            returnere ord
    retur Ingen
print (word_for_id (17, tokenizer))
# generer en beskrivelse af et billede
def generere_desc (model, tokenizer, foto, max_length):
    photo = np.array ([foto])
    # frø genereringsprocessen
    in_text = ''
    # iterere over hele længden af ​​sekvensen
    print ('\ nPriktion ----> \ n \ n ', end = '')
    for jeg inden for rækkevidde (150):
        # heltalkodning input sekvens
        rækkefølge = tokenizer.texts_to_sequences ([in_text]) [0]
        # pad input
        rækkefølge = pad_sequences ([sekvens], maxlen = max_length)
        # forudsige næste ord
        yhat = load_model.predict ([foto, rækkefølge], verbose = 0)
        # konverter sandsynlighed til heltal
        yhat = argmax (yhat)
        # kort heltal til ord
        word = word_for_id (yhat, tokenizer)
        # stop, hvis vi ikke kan kortlægge ordet
        hvis ordet er Ingen:
            pause
        # tilføj som input til generering af det næste ord
        in_text + = ord + ''
        # stop, hvis vi forudsiger slutningen af ​​sekvensen
        print (ord + '', end = '')
        hvis ord == '':
            pause
    vende tilbage i_text
max_length = 48
# evaluere modellens evne
def evalu_model (model, beskrivelser, fotos, tokenizer, max_length):
    faktisk, forudsagt = liste (), liste ()
    # trin over hele sættet
    for i inden for rækkevidde (len (tekster)):
        yhat = generere_desc (model, tokenizer, fotos [i], max_length)
        # lagre faktiske og forudsagte
        print ('\ n \ nReal ----> \ n \ n' + tekster [i])
        actual.append ([tekster [i] .split ()])
        predicted.append (yhat.split ())
    # beregne BLEU-score
    bleu = corpus_bleu (faktisk, forudsagt)
    return bleu, faktisk, forudsagt
bleu, faktisk, forudsagt = evalu_model (load_model, tekster, train_features, tokenizer, max_length)
#Kompilér tokenerne i HTML og css
dsl_path = "kompilator / aktiver / web-dsl-mapping.json"
compiler = Compiler (dsl_path)
compiled_website = compiler.compile (forudsagt [0], 'index.html')
print (compiled_website)
print (bleu)

Produktion

Links til prøveudgang

  • Genereret hjemmeside 1 - Original 1
  • Genereret websted 2 - Original 2
  • Genereret hjemmeside 3 - Original 3
  • Genereret hjemmeside 4 - Original 4
  • Genereret hjemmeside 5 - Original 5

Fejl, jeg lavede:

  • Forstå modellenes svaghed i stedet for at teste tilfældige modeller. Først anvendte jeg tilfældige ting som batchnormalisering og tovejsnetværk og prøvede at implementere opmærksomhed. Efter at have set på testdataene og set, at de ikke kunne forudsige farve og position med høj nøjagtighed, indså jeg, at der var en svaghed i CNN. Dette førte mig til at erstatte maxpooling med øgede skridt. Valideringstabet gik fra 0,12 til 0,02 og øgede BLEU-score fra 85% til 97%.
  • Brug kun foruddannede modeller, hvis de er relevante. I betragtning af det lille datasæt tænkte jeg, at en foruddannet billedmodel ville forbedre ydelsen. Fra mine eksperimenter og ende til ende model er langsommere at træne og kræver mere hukommelse, men er 30% mere nøjagtig.
  • Planlæg for let afvigelse, når du kører din model på en ekstern server. På min mac læser den filerne i alfabetisk rækkefølge. På serveren var den imidlertid tilfældigt placeret. Dette skabte et misforhold mellem skærmbillederne og koden. Det konvergerede stadig, men valideringsdataene var 50% værre end da jeg fikste dem.
  • Sørg for, at du forstår biblioteksfunktioner. Medtag plads til det tomme token i dit ordforråd. Da jeg ikke tilføjede det, omfattede det ikke en af ​​symbolerne. Jeg har kun bemærket det efter at have set på den endelige output flere gange og bemærket, at den aldrig forudsagde et “enkelt” token. Efter en hurtig kontrol indså jeg, at det ikke engang var i ordforrådet. Brug også den samme rækkefølge i ordforrådet til træning og test.
  • Brug lettere modeller, når du eksperimenterer. Brug af GRU'er i stedet for LSTM'er reducerede hver epokecyklus med 30% og havde ikke nogen stor effekt på ydelsen.

Næste skridt

Front-end-udvikling er et ideelt rum til anvendelse af dyb læring. Det er let at generere data, og de nuværende dybe indlæringsalgoritmer kan kortlægge det meste af logikken.

Et af de mest spændende områder er at være opmærksom på LSTM'er. Dette forbedrer ikke bare nøjagtigheden, men gør det muligt for os at visualisere, hvor CNN sætter sit fokus, når det genererer markeringen.

Opmærksomhed er også nøglen til at kommunikere mellem markup, stilark, scripts og til sidst backend. Opmærkslag kan holde styr på variabler, så netværket kan kommunikere mellem programmeringssprog.

Men i den nærmeste funktion vil den største virkning komme fra at opbygge en skalerbar måde at syntetisere data på. Derefter kan du tilføje skrifttyper, farver, ord og animationer trin for trin.

Indtil videre sker de fleste fremskridt med at tage skitser og omdanne dem til skabelon-apps. Om mindre end to år kan vi tegne en app på papir og have den tilsvarende frontend på mindre end et sekund. Der er allerede to fungerende prototyper bygget af Airbnb's designteam og Uizard.

Her er nogle eksperimenter for at komme i gang.

Eksperimenter

Kom godt i gang

  • Kør alle modeller
  • Prøv forskellige hyperparametre
  • Test en anden CNN-arkitektur
  • Tilføj tovejs LSTM-modeller
  • Implementere modellen med et andet datasæt. (Du kan nemt montere dette datasæt i dine FloydHub-job med dette flag - data emilwallner / datasets / 100k-html: data)

Yderligere eksperimenter

  • Oprettelse af en solid tilfældig app / webgenerator med den tilsvarende syntaks.
  • Data til en skitse til app-model. Konverter automatisk app / web-skærmbilleder til skitser og brug en GAN til at skabe variation.
  • Anvend et opmærksomhedslag for at visualisere fokus på billedet for hver forudsigelse, der ligner denne model.
  • Opret rammer for en modulær tilgang. Sig, at have kodermodeller til skrifttyper, en til farve, en anden til layout og kombiner dem med en dekoder. En god start kan være solide billedfunktioner.
  • Feed netværkets enkle HTML-komponenter, og lær det at generere animationer ved hjælp af CSS. Det ville være fascinerende at have en opmærksomhedsstrategi og visualisere fokus på begge inputkilder.

Enorm tak til Tony Beltramelli og Jon Gold for deres forskning og ideer og for at besvare spørgsmål. Tak til Jason Brownlee for hans fremragende Keras-tutorials (jeg inkluderede et par uddrag fra hans tutorial i Keras-implementeringen), og Beltramelli for at levere dataene. Tak også til Qingping Hou, Charlie Harrington, Sai Soundararaj, Jannes Klaas, Claudio Cabral, Alain Demenet og Dylan Djian for at have læst udkast til dette.

Om Emil Wallner

Dette er den fjerde del af en flerdelt blogserie fra Emil, når han lærer dyb læring. Emil har brugt et årti på at undersøge menneskelig læring. Han har arbejdet på Oxford's handelsskole, investeret i startups med uddannelse og opbygget en uddannelsesteknologi-forretning. Sidste år tilmeldte han sig på Ecole 42 for at anvende sin viden om menneskelig læring til maskinlæring.

Hvis du bygger noget eller sidder fast, så ping mig nedenfor eller på twitter: emilwallner. Jeg ville meget gerne se, hvad du bygger.

Dette blev først offentliggjort som et community-indlæg på Floydhubs blog.