天在小破站看到一個(gè)前端生成隨機(jī)驗(yàn)證碼的視頻,感覺(jué)很有意思,就跟著操作了一下,成功了。
后來(lái)自己又想給它加一個(gè)提交按鈕,輸入并判斷驗(yàn)證碼的正確性,也可以正常運(yùn)行,但是我的代碼好像還是存在一些bug:
1.canvas標(biāo)簽是繪圖容器,自帶屬性使它是一個(gè)默認(rèn)300*150的容器,縮小canvs容器時(shí),里面的繪圖也會(huì)變小,我沒(méi)有找到合適的方法去調(diào)整它的大小。
2.對(duì)于輸入框input,我使用了float浮動(dòng),為了使input輸入框和canvas里面的驗(yàn)證碼并排,但是被vscode警告了。
這次的實(shí)戰(zhàn)練習(xí)也經(jīng)歷了很多坎坷,但是也收獲很大。學(xué)習(xí)到了canvas標(biāo)簽的用法、JS全局變量和局部變量、以及有關(guān)context的一些屬性和方法。
最后,希望路過(guò)的大佬,幫我看看bug,幫幫菜鳥(niǎo)。
附上源碼
選自Floydhub
作者:Emil Wallner
機(jī)器之心編譯
如何用前端頁(yè)面原型生成對(duì)應(yīng)的代碼一直是我們關(guān)注的問(wèn)題,本文作者根據(jù) pix2code 等論文構(gòu)建了一個(gè)強(qiáng)大的前端代碼生成模型,并詳細(xì)解釋了如何利用 LSTM 與 CNN 將設(shè)計(jì)原型編寫(xiě)為 HTML 和 CSS 網(wǎng)站。
項(xiàng)目鏈接:github.com/emilwallner…
在未來(lái)三年內(nèi),深度學(xué)習(xí)將改變前端開(kāi)發(fā)。它將會(huì)加快原型設(shè)計(jì)速度,拉低開(kāi)發(fā)軟件的門(mén)檻。
Tony Beltramelli 在去年發(fā)布了論文《pix2code: Generating Code from a Graphical User Interface Screenshot》,Airbnb 也發(fā)布Sketch2code(airbnb.design/sketching-i…)。
目前,自動(dòng)化前端開(kāi)發(fā)的最大阻礙是計(jì)算能力。但我們已經(jīng)可以使用目前的深度學(xué)習(xí)算法,以及合成訓(xùn)練數(shù)據(jù)來(lái)探索人工智能自動(dòng)構(gòu)建前端的方法。在本文中,作者將教神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)基于一張圖片和一個(gè)設(shè)計(jì)模板來(lái)編寫(xiě)一個(gè) HTML 和 CSS 網(wǎng)站。以下是該過(guò)程的簡(jiǎn)要概述:
1)向訓(xùn)練過(guò)的神經(jīng)網(wǎng)絡(luò)輸入一個(gè)設(shè)計(jì)圖
2)神經(jīng)網(wǎng)絡(luò)將圖片轉(zhuǎn)化為 HTML 標(biāo)記語(yǔ)言
3)渲染輸出
我們將分三步從易到難構(gòu)建三個(gè)不同的模型,首先,我們構(gòu)建最簡(jiǎn)單地版本來(lái)掌握移動(dòng)部件。第二個(gè)版本 HTML 專(zhuān)注于自動(dòng)化所有步驟,并簡(jiǎn)要解釋神經(jīng)網(wǎng)絡(luò)層。在最后一個(gè)版本 Bootstrap 中,我們將創(chuàng)建一個(gè)模型來(lái)思考和探索 LSTM 層。
代碼地址:
所有 FloydHub notebook 都在 floydhub 目錄中,本地 notebook 在 local 目錄中。
本文中的模型構(gòu)建基于 Beltramelli 的論文《pix2code: Generating Code from a Graphical User Interface Screenshot》和 Jason Brownlee 的圖像描述生成教程,并使用 Python 和 Keras 完成。
核心邏輯
我們的目標(biāo)是構(gòu)建一個(gè)神經(jīng)網(wǎng)絡(luò),能夠生成與截圖對(duì)應(yīng)的 HTML/CSS 標(biāo)記語(yǔ)言。
訓(xùn)練神經(jīng)網(wǎng)絡(luò)時(shí),你先提供幾個(gè)截圖和對(duì)應(yīng)的 HTML 代碼。網(wǎng)絡(luò)通過(guò)逐個(gè)預(yù)測(cè)所有匹配的 HTML 標(biāo)記語(yǔ)言來(lái)學(xué)習(xí)。預(yù)測(cè)下一個(gè)標(biāo)記語(yǔ)言的標(biāo)簽時(shí),網(wǎng)絡(luò)接收到截圖和之前所有正確的標(biāo)記。
這里是一個(gè)簡(jiǎn)單的訓(xùn)練數(shù)據(jù)示例:docs.google.com/spreadsheet…。
創(chuàng)建逐詞預(yù)測(cè)的模型是現(xiàn)在最常用的方法,也是本教程使用的方法。
注意:每次預(yù)測(cè)時(shí),神經(jīng)網(wǎng)絡(luò)接收的是同樣的截圖。也就是說(shuō)如果網(wǎng)絡(luò)需要預(yù)測(cè) 20 個(gè)單詞,它就會(huì)得到 20 次同樣的設(shè)計(jì)截圖。現(xiàn)在,不用管神經(jīng)網(wǎng)絡(luò)的工作原理,只需要專(zhuān)注于神經(jīng)網(wǎng)絡(luò)的輸入和輸出。
我們先來(lái)看前面的標(biāo)記(markup)。假如我們訓(xùn)練神經(jīng)網(wǎng)絡(luò)的目的是預(yù)測(cè)句子「I can code」。當(dāng)網(wǎng)絡(luò)接收「I」時(shí),預(yù)測(cè)「can」。下一次時(shí),網(wǎng)絡(luò)接收「I can」,預(yù)測(cè)「code」。它接收所有之前單詞,但只預(yù)測(cè)下一個(gè)單詞。
神經(jīng)網(wǎng)絡(luò)根據(jù)數(shù)據(jù)創(chuàng)建特征。神經(jīng)網(wǎng)絡(luò)構(gòu)建特征以連接輸入數(shù)據(jù)和輸出數(shù)據(jù)。它必須創(chuàng)建表征來(lái)理解每個(gè)截圖的內(nèi)容和它所需要預(yù)測(cè)的 HTML 語(yǔ)法,這些都是為預(yù)測(cè)下一個(gè)標(biāo)記構(gòu)建知識(shí)。把訓(xùn)練好的模型應(yīng)用到真實(shí)世界中和模型訓(xùn)練過(guò)程差不多。
我們無(wú)需輸入正確的 HTML 標(biāo)記,網(wǎng)絡(luò)會(huì)接收它目前生成的標(biāo)記,然后預(yù)測(cè)下一個(gè)標(biāo)記。預(yù)測(cè)從「起始標(biāo)簽」(start tag)開(kāi)始,到「結(jié)束標(biāo)簽」(end tag)終止,或者達(dá)到最大限制時(shí)終止。
Hello World 版
現(xiàn)在讓我們構(gòu)建 Hello World 版實(shí)現(xiàn)。我們將饋送一張帶有「Hello World!」字樣的截屏到神經(jīng)網(wǎng)絡(luò)中,并訓(xùn)練它生成對(duì)應(yīng)的標(biāo)記語(yǔ)言。
首先,神經(jīng)網(wǎng)絡(luò)將原型設(shè)計(jì)轉(zhuǎn)換為一組像素值。且每一個(gè)像素點(diǎn)有 RGB 三個(gè)通道,每個(gè)通道的值都在 0-255 之間。
為了以神經(jīng)網(wǎng)絡(luò)能理解的方式表征這些標(biāo)記,我使用了 one-hot 編碼。因此句子「I can code」可以映射為以下形式。
在上圖中,我們的編碼包含了開(kāi)始和結(jié)束的標(biāo)簽。這些標(biāo)簽?zāi)転樯窠?jīng)網(wǎng)絡(luò)提供開(kāi)始預(yù)測(cè)和結(jié)束預(yù)測(cè)的位置信息。以下是這些標(biāo)簽的各種組合以及對(duì)應(yīng) one-hot 編碼的情況。
我們會(huì)使每個(gè)單詞在每一輪訓(xùn)練中改變位置,因此這允許模型學(xué)習(xí)序列而不是記憶詞的位置。在下圖中有四個(gè)預(yù)測(cè),每一行是一個(gè)預(yù)測(cè)。且左邊代表 RGB 三色通道和之前的詞,右邊代表預(yù)測(cè)結(jié)果和紅色的結(jié)束標(biāo)簽。
#Length of longest sentence max_caption_len=3 #Size of vocabulary vocab_size=3 # Load one screenshot for each word and turn them into digits images=[] for i in range(2): images.append(img_to_array(load_img('screenshot.jpg', target_size=(224, 224)))) images=np.array(images, dtype=float) # Preprocess input for the VGG16 model images=preprocess_input(images) #Turn start tokens into one-hot encoding html_input=np.array( [[[0., 0., 0.], #start [0., 0., 0.], [1., 0., 0.]], [[0., 0., 0.], #start <HTML>Hello World!</HTML> [1., 0., 0.], [0., 1., 0.]]]) #Turn next word into one-hot encoding next_words=np.array( [[0., 1., 0.], # <HTML>Hello World!</HTML> [0., 0., 1.]]) # end # Load the VGG16 model trained on imagenet and output the classification feature VGG=VGG16(weights='imagenet', include_top=True) # Extract the features from the image features=VGG.predict(images) #Load the feature to the network, apply a dense layer, and repeat the vector vgg_feature=Input(shape=(1000,)) vgg_feature_dense=Dense(5)(vgg_feature) vgg_feature_repeat=RepeatVector(max_caption_len)(vgg_feature_dense) # Extract information from the input seqence language_input=Input(shape=(vocab_size, vocab_size)) language_model=LSTM(5, return_sequences=True)(language_input) # Concatenate the information from the image and the input decoder=concatenate([vgg_feature_repeat, language_model]) # Extract information from the concatenated output decoder=LSTM(5, return_sequences=False)(decoder) # Predict which word comes next decoder_output=Dense(vocab_size, activation='softmax')(decoder) # Compile and run the neural network model=Model(inputs=[vgg_feature, language_input], outputs=decoder_output) model.compile(loss='categorical_crossentropy', optimizer='rmsprop') # Train the neural network model.fit([features, html_input], next_words, batch_size=2, shuffle=False, epochs=1000) 復(fù)制代碼
在 Hello World 版本中,我們使用三個(gè)符號(hào)「start」、「Hello World」和「end」。字符級(jí)的模型要求更小的詞匯表和受限的神經(jīng)網(wǎng)絡(luò),而單詞級(jí)的符號(hào)在這里可能有更好的性能。
以下是執(zhí)行預(yù)測(cè)的代碼:
# Create an empty sentence and insert the start token sentence=np.zeros((1, 3, 3)) # [[0,0,0], [0,0,0], [0,0,0]] start_token=[1., 0., 0.] # start sentence[0][2]=start_token # place start in empty sentence # Making the first prediction with the start token second_word=model.predict([np.array([features[1]]), sentence]) # Put the second word in the sentence and make the final prediction sentence[0][1]=start_token sentence[0][2]=np.round(second_word) third_word=model.predict([np.array([features[1]]), sentence]) # Place the start token and our two predictions in the sentence sentence[0][0]=start_token sentence[0][1]=np.round(second_word) sentence[0][2]=np.round(third_word) # Transform our one-hot predictions into the final tokens vocabulary=["start", "<HTML><center><H1>Hello World!</H1></center></HTML>", "end"] for i in sentence[0]: print(vocabulary[np.argmax(i)], end=' ') 復(fù)制代碼
輸出
我走過(guò)的坑:
在 FloydHub 上運(yùn)行代碼
FloydHub 是一個(gè)深度學(xué)習(xí)訓(xùn)練平臺(tái),我自從開(kāi)始學(xué)習(xí)深度學(xué)習(xí)時(shí)就對(duì)它有所了解,我也常用它訓(xùn)練和管理深度學(xué)習(xí)試驗(yàn)。我們能安裝它并在 10 分鐘內(nèi)運(yùn)行第一個(gè)模型,它是在云 GPU 上訓(xùn)練模型最好的選擇。若果讀者沒(méi)用過(guò) FloydHub,可以花 10 分鐘左右安裝并了解。
FloydHub 地址:www.floydhub.com/
復(fù)制 Repo:
https://github.com/emilwallner/Screenshot-to-code-in-Keras.git 復(fù)制代碼
登錄并初始化 FloydHub 命令行工具:
cd Screenshot-to-code-in-Keras floyd login floyd init s2c 復(fù)制代碼
在 FloydHub 云 GPU 機(jī)器上運(yùn)行 Jupyter notebook:
floyd run --gpu --env tensorflow-1.4 --data emilwallner/datasets/imagetocode/2:data --mode jupyter 復(fù)制代碼
所有的 notebook 都放在 floydbub 目錄下。一旦我們開(kāi)始運(yùn)行模型,那么在 floydhub/Helloworld/helloworld.ipynb 下可以找到第一個(gè) Notebook。更多詳情請(qǐng)查看本項(xiàng)目早期的 flags。
HTML 版本
在這個(gè)版本中,我們將關(guān)注與創(chuàng)建一個(gè)可擴(kuò)展的神經(jīng)網(wǎng)絡(luò)模型。該版本并不能直接從隨機(jī)網(wǎng)頁(yè)預(yù)測(cè) HTML,但它是探索動(dòng)態(tài)問(wèn)題不可缺少的步驟。
概覽
如果我們將前面的架構(gòu)擴(kuò)展為以下右圖展示的結(jié)構(gòu),那么它就能更高效地處理識(shí)別與轉(zhuǎn)換過(guò)程。
該架構(gòu)主要有兩個(gè)部,即編碼器與解碼器。編碼器是我們創(chuàng)建圖像特征和前面標(biāo)記特征(markup features)的部分。特征是網(wǎng)絡(luò)創(chuàng)建原型設(shè)計(jì)和標(biāo)記語(yǔ)言之間聯(lián)系的構(gòu)建塊。在編碼器的末尾,我們將圖像特征傳遞給前面標(biāo)記的每一個(gè)單詞。隨后解碼器將結(jié)合原型設(shè)計(jì)特征和標(biāo)記特征以創(chuàng)建下一個(gè)標(biāo)簽的特征,這一個(gè)特征可以通過(guò)全連接層預(yù)測(cè)下一個(gè)標(biāo)簽。
設(shè)計(jì)原型的特征
因?yàn)槲覀冃枰獮槊總€(gè)單詞插入一個(gè)截屏,這將會(huì)成為訓(xùn)練神經(jīng)網(wǎng)絡(luò)的瓶頸。因此我們抽取生成標(biāo)記語(yǔ)言所需要的信息來(lái)替代直接使用圖像。這些抽取的信息將通過(guò)預(yù)訓(xùn)練的 CNN 編碼到圖像特征中,且我們將使用分類(lèi)層之前的層級(jí)輸出以抽取特征。
我們最終得到 1536 個(gè) 8*8 的特征圖,雖然我們很難直觀地理解它,但神經(jīng)網(wǎng)絡(luò)能夠從這些特征中抽取元素的對(duì)象和位置。
標(biāo)記特征
在 Hello World 版本中,我們使用 one-hot 編碼以表征標(biāo)記。而在該版本中,我們將使用詞嵌入表征輸入并使用 one-hot 編碼表示輸出。我們構(gòu)建每個(gè)句子的方式保持不變,但我們映射每個(gè)符號(hào)的方式將會(huì)變化。one-hot 編碼將每一個(gè)詞視為獨(dú)立的單元,而詞嵌入會(huì)將輸入數(shù)據(jù)表征為一個(gè)實(shí)數(shù)列表,這些實(shí)數(shù)表示標(biāo)記標(biāo)簽之間的關(guān)系。
上面詞嵌入的維度為 8,但一般詞嵌入的維度會(huì)根據(jù)詞匯表的大小在 50 到 500 間變動(dòng)。以上每個(gè)單詞的八個(gè)數(shù)值就類(lèi)似于神經(jīng)網(wǎng)絡(luò)中的權(quán)重,它們傾向于刻畫(huà)單詞之間的聯(lián)系(Mikolov alt el., 2013)。這就是我們開(kāi)始部署標(biāo)記特征(markup features)的方式,而這些神經(jīng)網(wǎng)絡(luò)訓(xùn)練的特征會(huì)將輸入數(shù)據(jù)和輸出數(shù)據(jù)聯(lián)系起來(lái)。
編碼器
我們現(xiàn)在將詞嵌入饋送到 LSTM 中,并期望能返回一系列的標(biāo)記特征。這些標(biāo)記特征隨后會(huì)饋送到一個(gè) Time Distributed 密集層,該層級(jí)可以視為有多個(gè)輸入和輸出的全連接層。
和嵌入與 LSTM 層相平行的還有另外一個(gè)處理過(guò)程,其中圖像特征首先會(huì)展開(kāi)成一個(gè)向量,然后再饋送到一個(gè)全連接層而抽取出高級(jí)特征。這些圖像特征隨后會(huì)與標(biāo)記特征相級(jí)聯(lián)而作為編碼器的輸出。
標(biāo)記特征
如下圖所示,現(xiàn)在我們將詞嵌入投入到 LSTM 層中,所有的語(yǔ)句都會(huì)用零填充以獲得相同的向量長(zhǎng)度。
為了混合信號(hào)并尋找高級(jí)模式,我們運(yùn)用了一個(gè) TimeDistributed 密集層以抽取標(biāo)記特征。TimeDistributed 密集層和一般的全連接層非常相似,且它有多個(gè)輸入與輸出。
圖像特征
對(duì)于另一個(gè)平行的過(guò)程,我們需要將圖像的所有像素值展開(kāi)成一個(gè)向量,因此信息不會(huì)被改變,它們只會(huì)用來(lái)識(shí)別。
如上,我們會(huì)通過(guò)全連接層混合信號(hào)并抽取更高級(jí)的概念。因?yàn)槲覀儾⒉恢皇翘幚硪粋€(gè)輸入值,因此使用一般的全連接層就行了。
級(jí)聯(lián)圖像特征和標(biāo)記特征
所有的語(yǔ)句都被填充以創(chuàng)建三個(gè)標(biāo)記特征。因?yàn)槲覀円呀?jīng)預(yù)處理了圖像特征,所以我們能為每一個(gè)標(biāo)記特征添加圖像特征。
如上,在復(fù)制圖像特征到對(duì)應(yīng)的標(biāo)記特征后,我們得到了新的圖像-標(biāo)記特征(image-markup features),這就是我們饋送到解碼器的輸入值。
解碼器
現(xiàn)在,我們使用圖像-標(biāo)記特征來(lái)預(yù)測(cè)下一個(gè)標(biāo)簽。
在下面的案例中,我們使用三個(gè)圖像-標(biāo)簽特征對(duì)來(lái)輸出下一個(gè)標(biāo)簽特征。注意 LSTM 層不應(yīng)該返回一個(gè)長(zhǎng)度等于輸入序列的向量,而只需要預(yù)測(cè)預(yù)測(cè)一個(gè)特征。在我們的案例中,這個(gè)特征將預(yù)測(cè)下一個(gè)標(biāo)簽,它包含了最后預(yù)測(cè)的信息。
最后的預(yù)測(cè)
密集層會(huì)像傳統(tǒng)前饋網(wǎng)絡(luò)那樣工作,它將下一個(gè)標(biāo)簽特征中的 512 個(gè)值與最后的四個(gè)預(yù)測(cè)連接起來(lái),即我們?cè)谠~匯表所擁有的四個(gè)單詞:start、hello、world 和 end。密集層最后采用的 softmax 函數(shù)會(huì)為四個(gè)類(lèi)別產(chǎn)生一個(gè)概率分布,例如 [0.1, 0.1, 0.1, 0.7] 將預(yù)測(cè)第四個(gè)詞為下一個(gè)標(biāo)簽。
# Load the images and preprocess them for inception-resnet images=[] all_filenames=listdir('images/') all_filenames.sort() for filename in all_filenames: images.append(img_to_array(load_img('images/'+filename, target_size=(299, 299)))) images=np.array(images, dtype=float) images=preprocess_input(images) # Run the images through inception-resnet and extract the features without the classification layer IR2=InceptionResNetV2(weights='imagenet', include_top=False) features=IR2.predict(images) # We will cap each input sequence to 100 tokens max_caption_len=100 # Initialize the function that will create our vocabulary tokenizer=Tokenizer(filters='', split=" ", lower=False) # Read a document and return a string def load_doc(filename): file=open(filename, 'r') text=file.read() file.close() return text # Load all the HTML files X=[] all_filenames=listdir('html/') all_filenames.sort() for filename in all_filenames: X.append(load_doc('html/'+filename)) # Create the vocabulary from the html files tokenizer.fit_on_texts(X) # Add +1 to leave space for empty words vocab_size=len(tokenizer.word_index) + 1 # Translate each word in text file to the matching vocabulary index sequences=tokenizer.texts_to_sequences(X) # The longest HTML file max_length=max(len(s) for s in sequences) # Intialize our final input to the model X, y, image_data=list(), list(), list() for img_no, seq in enumerate(sequences): for i in range(1, len(seq)): # Add the entire sequence to the input and only keep the next word for the output in_seq, out_seq=seq[:i], seq[i] # If the sentence is shorter than max_length, fill it up with empty words in_seq=pad_sequences([in_seq], maxlen=max_length)[0] # Map the output to one-hot encoding out_seq=to_categorical([out_seq], num_classes=vocab_size)[0] # Add and image corresponding to the HTML file image_data.append(features[img_no]) # Cut the input sentence to 100 tokens, and add it to the input data X.append(in_seq[-100:]) y.append(out_seq) X, y, image_data=np.array(X), np.array(y), np.array(image_data) # Create the encoder image_features=Input(shape=(8, 8, 1536,)) image_flat=Flatten()(image_features) image_flat=Dense(128, activation='relu')(image_flat) ir2_out=RepeatVector(max_caption_len)(image_flat) language_input=Input(shape=(max_caption_len,)) language_model=Embedding(vocab_size, 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=TimeDistributed(Dense(128, activation='relu'))(language_model) # Create the decoder decoder=concatenate([ir2_out, language_model]) decoder=LSTM(512, return_sequences=False)(decoder) decoder_output=Dense(vocab_size, activation='softmax')(decoder) # Compile the model model=Model(inputs=[image_features, language_input], outputs=decoder_output) model.compile(loss='categorical_crossentropy', optimizer='rmsprop') # Train the neural network model.fit([image_data, X], y, batch_size=64, shuffle=False, epochs=2) # map an integer to a word def word_for_id(integer, tokenizer): for word, index in tokenizer.word_index.items(): if index==integer: return word return None # generate a description for an image def generate_desc(model, tokenizer, photo, max_length): # seed the generation process in_text='START' # iterate over the whole length of the sequence for i in range(900): # integer encode input sequence sequence=tokenizer.texts_to_sequences([in_text])[0][-100:] # pad input sequence=pad_sequences([sequence], maxlen=max_length) # predict next word yhat=model.predict([photo,sequence], verbose=0) # convert probability to integer yhat=np.argmax(yhat) # map integer to word word=word_for_id(yhat, tokenizer) # stop if we cannot map the word if word is None: break # append as input for generating the next word in_text +=' ' + word # Print the prediction print(' ' + word, end='') # stop if we predict the end of the sequence if word=='END': break return # Load and image, preprocess it for IR2, extract features and generate the 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])) generate_desc(model, tokenizer, np.array(test_features), 100) 復(fù)制代碼
輸出
訓(xùn)練不同輪數(shù)所生成網(wǎng)站的地址:
我走過(guò)的坑:
Bootstrap 版本
在最終版本中,我們使用 pix2code 論文中生成 bootstrap 網(wǎng)站的數(shù)據(jù)集。使用 Twitter 的 Bootstrap 庫(kù)(getbootstrap.com/),我們可以結(jié)合 HTML 和 CSS,降低詞匯表規(guī)模。
我們將使用這一版本為之前未見(jiàn)過(guò)的截圖生成標(biāo)記。我們還深入研究它如何構(gòu)建截圖和標(biāo)記的先驗(yàn)知識(shí)。
我們不在 bootstrap 標(biāo)記上訓(xùn)練,而是使用 17 個(gè)簡(jiǎn)化 token,將其編譯成 HTML 和 CSS。數(shù)據(jù)集(github.com/tonybeltram…)包括 1500 個(gè)測(cè)試截圖和 250 個(gè)驗(yàn)證截圖。平均每個(gè)截圖有 65 個(gè) token,一共有 96925 個(gè)訓(xùn)練樣本。
我們稍微修改一下 pix2code 論文中的模型,使之預(yù)測(cè)網(wǎng)絡(luò)組件的準(zhǔn)確率達(dá)到 97%。
端到端方法
從預(yù)訓(xùn)練模型中提取特征在圖像描述生成模型中效果很好。但是幾次實(shí)驗(yàn)后,我發(fā)現(xiàn) pix2code 的端到端方法效果更好。在我們的模型中,我們用輕量級(jí)卷積神經(jīng)網(wǎng)絡(luò)替換預(yù)訓(xùn)練圖像特征。我們不使用最大池化來(lái)增加信息密度,而是增加步幅。這可以保持前端元素的位置和顏色。
存在兩個(gè)核心模型:卷積神經(jīng)網(wǎng)絡(luò)(CNN)和循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)。最常用的循環(huán)神經(jīng)網(wǎng)絡(luò)是長(zhǎng)短期記憶(LSTM)網(wǎng)絡(luò)。我之前的文章中介紹過(guò) CNN 教程,本文主要介紹 LSTM。
理解 LSTM 中的時(shí)間步
關(guān)于 LSTM 比較難理解的是時(shí)間步。我們的原始神經(jīng)網(wǎng)絡(luò)有兩個(gè)時(shí)間步,如果你給它「Hello」,它就會(huì)預(yù)測(cè)「World」。但是它會(huì)試圖預(yù)測(cè)更多時(shí)間步。下例中,輸入有四個(gè)時(shí)間步,每個(gè)單詞對(duì)應(yīng)一個(gè)時(shí)間步。
LSTM 適合時(shí)序數(shù)據(jù)的輸入,它是一種適合順序信息的神經(jīng)網(wǎng)絡(luò)。模型展開(kāi)圖示如下,對(duì)于每個(gè)循環(huán)步,你需要保持同樣的權(quán)重。
加權(quán)后的輸入與輸出特征在級(jí)聯(lián)后輸入到激活函數(shù),并作為當(dāng)前時(shí)間步的輸出。因?yàn)槲覀冎貜?fù)利用了相同的權(quán)重,它們將從一些輸入獲取信息并構(gòu)建序列的知識(shí)。下面是 LSTM 在每一個(gè)時(shí)間步上的簡(jiǎn)化版處理過(guò)程:
理解 LSTM 層級(jí)中的單元
每一層 LSTM 單元的總數(shù)決定了它記憶的能力,同樣也對(duì)應(yīng)于每一個(gè)輸出特征的維度大小。LSTM 層級(jí)中的每一個(gè)單元將學(xué)習(xí)如何追蹤句法的不同方面。以下是一個(gè) LSTM 單元追蹤標(biāo)簽行信息的可視化,它是我們用來(lái)訓(xùn)練 bootstrap 模型的簡(jiǎn)單標(biāo)記語(yǔ)言。
每一個(gè) LSTM 單元會(huì)維持一個(gè)單元狀態(tài),我們可以將單元狀態(tài)視為記憶。權(quán)重和激活值可使用不同的方式修正狀態(tài)值,這令 LSTM 層可以通過(guò)保留或遺忘輸入信息而得到精調(diào)。除了處理當(dāng)前輸入信息與輸出信息,LSTM 單元還需要修正記憶狀態(tài)以傳遞到下一個(gè)時(shí)間步。
dir_name='resources/eval_light/' # Read a file and return a string def load_doc(filename): file=open(filename, 'r') text=file.read() file.close() return text def load_data(data_dir): text=[] images=[] # Load all the files and order them all_filenames=listdir(data_dir) all_filenames.sort() for filename in (all_filenames): if filename[-3:]=="npz": # Load the images already prepared in arrays image=np.load(data_dir+filename) images.append(image['features']) else: # Load the boostrap tokens and rap them in a start and end tag syntax='<START> ' + load_doc(data_dir+filename) + ' <END>' # Seperate all the words with a single space syntax=' '.join(syntax.split()) # Add a space after each comma syntax=syntax.replace(',', ' ,') text.append(syntax) images=np.array(images, dtype=float) return images, text train_features, texts=load_data(dir_name) # Initialize the function to create the vocabulary tokenizer=Tokenizer(filters='', split=" ", lower=False) # Create the vocabulary tokenizer.fit_on_texts([load_doc('bootstrap.vocab')]) # Add one spot for the empty word in the vocabulary vocab_size=len(tokenizer.word_index) + 1 # Map the input sentences into the vocabulary indexes train_sequences=tokenizer.texts_to_sequences(texts) # The longest set of boostrap tokens max_sequence=max(len(s) for s in train_sequences) # Specify how many tokens to have in each input sentence max_length=48 def preprocess_data(sequences, features): X, y, image_data=list(), list(), list() for img_no, seq in enumerate(sequences): for i in range(1, len(seq)): # Add the sentence until the current count(i) and add the current count to the output in_seq, out_seq=seq[:i], seq[i] # Pad all the input token sentences to max_sequence in_seq=pad_sequences([in_seq], maxlen=max_sequence)[0] # Turn the output into one-hot encoding out_seq=to_categorical([out_seq], num_classes=vocab_size)[0] # Add the corresponding image to the boostrap token file image_data.append(features[img_no]) # Cap the input sentence to 48 tokens and add it 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) #Create the encoder image_model=Sequential() image_model.add(Conv2D(16, (3, 3), padding='valid', activation='relu', input_shape=(256, 256, 3,))) image_model.add(Conv2D(16, (3,3), activation='relu', padding='same', strides=2)) image_model.add(Conv2D(32, (3,3), activation='relu', padding='same')) image_model.add(Conv2D(32, (3,3), activation='relu', padding='same', strides=2)) image_model.add(Conv2D(64, (3,3), activation='relu', padding='same')) image_model.add(Conv2D(64, (3,3), activation='relu', padding='same', strides=2)) image_model.add(Conv2D(128, (3,3), activation='relu', padding='same')) image_model.add(Flatten()) image_model.add(Dense(1024, activation='relu')) image_model.add(Dropout(0.3)) image_model.add(Dense(1024, activation='relu')) image_model.add(Dropout(0.3)) image_model.add(RepeatVector(max_length)) visual_input=Input(shape=(256, 256, 3,)) encoded_image=image_model(visual_input) language_input=Input(shape=(max_length,)) language_model=Embedding(vocab_size, 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) #Create the decoder decoder=concatenate([encoded_image, language_model]) decoder=LSTM(512, return_sequences=True)(decoder) decoder=LSTM(512, return_sequences=False)(decoder) decoder=Dense(vocab_size, activation='softmax')(decoder) # Compile the model model=Model(inputs=[visual_input, language_input], outputs=decoder) optimizer=RMSprop(lr=0.0001, clipvalue=1.0) model.compile(loss='categorical_crossentropy', optimizer=optimizer) #Save the model for every 2nd epoch filepath="org-weights-epoch-{epoch:04d}--val_loss-{val_loss:.4f}--loss-{loss:.4f}.hdf5" checkpoint=ModelCheckpoint(filepath, monitor='val_loss', verbose=1, save_weights_only=True, period=2) callbacks_list=[checkpoint] # Train the model model.fit([image_data, X], y, batch_size=64, shuffle=False, validation_split=0.1, callbacks=callbacks_list, verbose=1, epochs=50) 復(fù)制代碼
測(cè)試準(zhǔn)確率
找到一種測(cè)量準(zhǔn)確率的優(yōu)秀方法非常棘手。比如一個(gè)詞一個(gè)詞地對(duì)比,如果你的預(yù)測(cè)中有一個(gè)詞不對(duì)照,準(zhǔn)確率可能就是 0。如果你把百分百對(duì)照的單詞移除一個(gè),最終的準(zhǔn)確率可能是 99/100。
我使用的是 BLEU 分值,它在機(jī)器翻譯和圖像描述模型實(shí)踐上都是最好的。它把句子分解成 4 個(gè) n-gram,從 1-4 個(gè)單詞的序列。在下面的預(yù)測(cè)中,「cat」應(yīng)該是「code」。
為了得到最終的分值,每個(gè)的分值需要乘以 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。然后用總和乘以句子長(zhǎng)度的懲罰函數(shù)。因?yàn)樵谖覀兊氖纠校L(zhǎng)度是正確的,所以它就直接是我們的最終得分。
你可以增加 n-gram 的數(shù)量,4 個(gè) n-gram 的模型是最為對(duì)應(yīng)人類(lèi)翻譯的。我建議你閱讀下面的代碼:
#Create a function to read a file and return its content def load_doc(filename): file=open(filename, 'r') text=file.read() file.close() return text def load_data(data_dir): text=[] images=[] files_in_folder=os.listdir(data_dir) files_in_folder.sort() for filename in tqdm(files_in_folder): #Add an image if filename[-3:]=="npz": image=np.load(data_dir+filename) images.append(image['features']) else: # Add text and wrap it in a start and end tag syntax='<START> ' + load_doc(data_dir+filename) + ' <END>' #Seperate each word with a space syntax=' '.join(syntax.split()) #Add a space between each comma syntax=syntax.replace(',', ' ,') text.append(syntax) images=np.array(images, dtype=float) return images, text #Intialize the function to create the vocabulary tokenizer=Tokenizer(filters='', split=" ", lower=False) #Create the vocabulary in a specific order tokenizer.fit_on_texts([load_doc('bootstrap.vocab')]) dir_name='../../../../eval/' train_features, texts=load_data(dir_name) #load model and weights json_file=open('../../../../model.json', 'r') loaded_model_json=json_file.read() json_file.close() loaded_model=model_from_json(loaded_model_json) # load weights into new model loaded_model.load_weights("../../../../weights.hdf5") print("Loaded model from disk") # map an integer to a word def word_for_id(integer, tokenizer): for word, index in tokenizer.word_index.items(): if index==integer: return word return None print(word_for_id(17, tokenizer)) # generate a description for an image def generate_desc(model, tokenizer, photo, max_length): photo=np.array([photo]) # seed the generation process in_text='<START> ' # iterate over the whole length of the sequence print('\nPrediction---->\n\n<START> ', end='') for i in range(150): # integer encode input sequence sequence=tokenizer.texts_to_sequences([in_text])[0] # pad input sequence=pad_sequences([sequence], maxlen=max_length) # predict next word yhat=loaded_model.predict([photo, sequence], verbose=0) # convert probability to integer yhat=argmax(yhat) # map integer to word word=word_for_id(yhat, tokenizer) # stop if we cannot map the word if word is None: break # append as input for generating the next word in_text +=word + ' ' # stop if we predict the end of the sequence print(word + ' ', end='') if word=='<END>': break return in_text max_length=48 # evaluate the skill of the model def evaluate_model(model, descriptions, photos, tokenizer, max_length): actual, predicted=list(), list() # step over the whole set for i in range(len(texts)): yhat=generate_desc(model, tokenizer, photos[i], max_length) # store actual and predicted print('\n\nReal---->\n\n' + texts[i]) actual.append([texts[i].split()]) predicted.append(yhat.split()) # calculate BLEU score bleu=corpus_bleu(actual, predicted) return bleu, actual, predicted bleu, actual, predicted=evaluate_model(loaded_model, texts, train_features, tokenizer, max_length) #Compile the tokens into HTML and css dsl_path="compiler/assets/web-dsl-mapping.json" compiler=Compiler(dsl_path) compiled_website=compiler.compile(predicted[0], 'index.html') print(compiled_website ) print(bleu) 復(fù)制代碼
輸出
樣本輸出的鏈接:
我走過(guò)的坑:
下一步
前端開(kāi)發(fā)是深度學(xué)習(xí)應(yīng)用的理想空間。數(shù)據(jù)容易生成,并且當(dāng)前深度學(xué)習(xí)算法可以映射絕大部分邏輯。一個(gè)最讓人激動(dòng)的領(lǐng)域是注意力機(jī)制在 LSTM 上的應(yīng)用。這不僅會(huì)提升精確度,還可以使我們可視化 CNN 在生成標(biāo)記時(shí)所聚焦的地方。注意力同樣是標(biāo)記、可定義模板、腳本和最終端之間通信的關(guān)鍵。注意力層要追蹤變量,使網(wǎng)絡(luò)可以在編程語(yǔ)言之間保持通信。
但是在不久的將來(lái),最大的影響將會(huì)來(lái)自合成數(shù)據(jù)的可擴(kuò)展方法。接著你可以一步步添加字體、顏色和動(dòng)畫(huà)。目前為止,大多數(shù)進(jìn)步發(fā)生在草圖(sketches)方面并將其轉(zhuǎn)化為模版應(yīng)用。在不到兩年的時(shí)間里,我們將創(chuàng)建一個(gè)草圖,它會(huì)在一秒之內(nèi)找到相應(yīng)的前端。Airbnb 設(shè)計(jì)團(tuán)隊(duì)與 Uizard 已經(jīng)創(chuàng)建了兩個(gè)正在使用的原型。下面是一些可能的試驗(yàn)過(guò)程:
實(shí)驗(yàn)
開(kāi)始
進(jìn)一步實(shí)驗(yàn)
原文鏈接:blog.floydhub.com/turning-des…
本文為機(jī)器之心編譯,轉(zhuǎn)載請(qǐng)聯(lián)系本公眾號(hào)獲得授權(quán)。
第一種:內(nèi)置函數(shù)方法
Math.random(); //該方法產(chǎn)生一個(gè)0到1之間的浮點(diǎn)數(shù)。 延伸: Math.floor(Math.random()*10+1); //生成1-10的隨機(jī)數(shù) Math.floor(Math.random()*10);//生成0-9的隨機(jī)數(shù) 函數(shù)方法: function rand ( n ) { return ( Math.floor ( Math.random ( ) * n + 1 ) ); } // 調(diào)用方法 rand(100);生成1-100的隨機(jī)數(shù)。
第二種:奇妙的寫(xiě)法(最近湊的解決方案)
優(yōu)雅的獲取數(shù)字+字母組成的字符串 Math.random().toString(36).slice(-8) 或者: (+new Date).toString(36).slice(-5); Math.random(); // 結(jié)果為0-1間的一個(gè)隨機(jī)數(shù)(包括0,不包括1) .toString(36) // 將數(shù)字轉(zhuǎn)換為基數(shù)為36的字符串,即使用字符0-9和az。 .slice(-8); // 截取最后八位 : "yo82mvyr" 36進(jìn)制包含的字符為 0-9,a-z 10進(jìn)制包含的字符為為 0-9 16進(jìn)制包含的字符為 0-9,a-f 缺點(diǎn): 只能生成有 0-9、a-z字符組成的字符串 由于 Math.random()生成的18位小數(shù),可能無(wú)法填充36位,最后幾個(gè)字符串,只能在指定的幾個(gè)字符中選擇。導(dǎo)致隨機(jī)性降低。 某些情況下會(huì)返回空值。例如,當(dāng)隨機(jī)數(shù)為 0, 0.5, 0.25, 0.125...時(shí),返回為空值。 在我的電腦上執(zhí)行這種方式生成字符串。1000萬(wàn)次中出現(xiàn)重復(fù)的概率為 0。 改進(jìn): (Math.random().toString(36)+'00000000000000000').slice(2, N+2) 其次,上面的原始和解決方案都將字符串大小N限制為16個(gè)字符。 以下將返回任意N的大小為N的字符串(但請(qǐng)注意,使用N> 16不會(huì)增加隨機(jī)性或降低碰撞概率): Array(N+1).join((Math.random().toString(36)+'00000000000000000').slice(2, 18)).slice(0, N) 說(shuō)明: 1、在[0,1)范圍內(nèi)選取一個(gè)隨機(jī)數(shù),即在0(包含)和1(不包括)之間。 將數(shù)字轉(zhuǎn)換為基數(shù)為36的字符串,即使用字符0-9和az。 2、用零填充(解決第一個(gè)問(wèn)題)。 3、切掉領(lǐng)先的'0'。 前綴和額外填充零。 4、重復(fù)該字符串足夠多的時(shí)間以使其中至少有N個(gè)字符(通過(guò)使用較短的隨機(jī)字符串作為分隔符加入空字符串)。 5、從字符串中切分N個(gè)字符。 更多的想法: 1、這個(gè)解決方案不使用大寫(xiě)字母,但幾乎在所有情況下(沒(méi)有雙關(guān)語(yǔ)意思),這并不重要。 2、在Chrome中測(cè)量原始答案中N=16時(shí)的最大字符串長(zhǎng)度。 在Firefox中,它的N=11。但正如所解釋的,第二種解決方案是支持任何請(qǐng)求的字符串長(zhǎng)度,而不是添加隨機(jī)性,所以它沒(méi)有太大區(qū)別。 3、所有返回的字符串返回的概率相等,至少就Math.random()返回的結(jié)果均勻分布(在任何情況下,這不是密碼強(qiáng)度隨機(jī)性)。 4、并非所有可能的大小為N的字符串都可能被返回。 在第二種解決方案中,這是顯而易見(jiàn)的(因?yàn)檩^小的字符串僅被復(fù)制),但在原始答案中也是如此,因?yàn)樵谵D(zhuǎn)換為基數(shù)-36時(shí),最后幾位可能不是原始隨機(jī)位的一部分。 具體來(lái)說(shuō),如果您查看Math.random()。toString(36)的結(jié)果,您會(huì)注意到最后一個(gè)字符不是均勻分布的。 同樣,在幾乎所有情況下,它都沒(méi)有關(guān)系,但是我們從最開(kāi)始的字符串開(kāi)始,而不是隨機(jī)字符串的結(jié)尾,以便短字符串(例如N=1)不受影響。
第三種:自定義函數(shù)(生成指定位數(shù)的數(shù)字字母混合的字符串)
/** * randomWord 產(chǎn)生任意長(zhǎng)度隨機(jī)字母數(shù)字組合 * @param randomFlag 是否任意長(zhǎng)度 min-任意長(zhǎng)度最小位[固定位數(shù)] max-任意長(zhǎng)度最大位 * @param min * @param max * @returns {string} */ function randomWord(randomFlag, min, max){ var str="", range=min, // 字母,數(shù)字 arr=['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; // 隨機(jī)產(chǎn)生 if(randomFlag){ range=Math.round(Math.random() * (max-min)) + min; } for(var i=0; i<range; i++){ pos=Math.round(Math.random() * (arr.length-1)); str +=arr[pos]; } return str; } 調(diào)用方法 // 生成 3 - 32 位隨即字符串 randomWord(true,3,32); // 例如:olyOXUF5oDsuMmXl3Mi48 // 生成 32 位隨機(jī)字符串 randomWord(false,32); // 例如:fjpnWj29Bb8boiXbLeDF0nxkR4aYcLRl
第四種:不需要輸入字符集合
function randomstring(L){ var s=''; var randomchar=function(){ var n=Math.floor(Math.random()*62); if(n<10) return n; //1-10 if(n<36) return String.fromCharCode(n+55); //A-Z return String.fromCharCode(n+61); //a-z } while(s.length< L) s+=randomchar(); return s; } // 調(diào)用方法 randomstring(5);
第五種:支持自定義字符長(zhǎng)度和特征字符集合
function randomString(len, charSet) { charSet=charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; var randomString=''; for (var i=0; i < len; i++) { var randomPoz=Math.floor(Math.random() * charSet.length); randomString +=charSet.substring(randomPoz,randomPoz+1); } return randomString; } // 調(diào)用方法 var randomValue=randomString(5); var randomValue=randomString(5, 'PICKCHARSFROMTHISSET');
第六種:最輕量級(jí)的方式(代碼輕,而不是CPU)
function rand(length, current) { current=current ? current : ''; return length ? rand(--length, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".charAt(Math.floor(Math.random() * 60)) + current) : current; } console.log(rand(5));
第七種:在循環(huán)中使用它,它可能不是5個(gè)字符,但保證每個(gè)ID是唯一的
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。