Tiếp tục bài viết trong series Tự học và phát triển ứng dụng thực tế AI, ML, DL, DS hôm nay mình mang đến cho các bạn cách Tự động tạo code HTML & CSS từ hình ảnh với Deep Learning.
Với sự phát triển của Deep Learning, ngành công nghiệp phần mềm và cụ thể là front-end development đã được hỗ trợ và cải thiện đáng kể, mọi việc đã có thể được làm một cách tự động nhờ các thuật toán học sâu.
Trong bài bài này, chúng ta sẽ xây dựng mạng nơ-ron (Neural network) để tự động tạo ra mã code một trang web HTML và CSS cơ bản dựa trên hình ảnh của một bản thiết kế (Mockup). Chương trình được viết bằng Python và Keras, một framework mạnh mẽ của TensorFlow. Các bạn cũng sẽ sử dụng Google Colab để huấn luyện mô hình nhờ GPU mạnh mẽ được cung cấp miễn phí nhé.
Quy trình như sau:
- Đưa vào hình ảnh cho mạng nơ-ron đã được huấn luyện (The trained neural network)
- Mạng nơ-ron sẽ tiến hành chuyển đổi hình ảnh sang html (Convert image into HTML markup)
- Xuất ra kết quả
Xây dựng mạng nơ-ron
Đầu tiên, chúng ta sẽ tạo ra một phiên bản đơn giản.
Phiên bản thứ hai, sẽ tập trung vào việc tự động hóa tất cả các bước.
Trong phiên bản cuối cùng, chúng ta sẽ tạo một mô hình có thể tổng quát hóa và khám phá lớp LSTM.
Khi bạn đào tạo mạng nơ-ron này, bạn cung cấp cho nó một số ảnh chụp màn hình với HTML phù hợp. Nó học bằng cách dự đoán từng thẻ HTML phù hợp. Khi nó dự đoán thẻ tiếp theo, nó sẽ nhận được ảnh chụp màn hình cũng như tất cả các thẻ đánh dấu chính xác cho đến thời điểm đó.
Phiên bản đơn giản
Chúng ta sẽ cung cấp cho mạng nơ-ron một ảnh chụp màn hình có trang web hiển thị “Hello World!” và dạy nó để tạo ra thẻ HTML.
Đầu tiên, mạng nơ-ron ánh xạ mô hình thiết kế thành một danh sách các giá trị pixel. Từ 0–255 ở ba kênh đỏ, xanh dương và xanh lục. Để biểu diễn đánh dấu theo cách mà mạng nơ-ron hiểu được, mình sử dụng one-hot encoding.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
from keras.layers import Embedding, TimeDistributed, RepeatVector, LSTM, concatenate , Input, Reshape, Dense from keras.preprocessing.image import array_to_img, img_to_array, load_img import numpy as np from keras.applications.vgg16 import VGG16, preprocess_input from keras.models import Model from IPython.core.display import display, HTML #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) start_token = [1., 0., 0.] # start sentence = np.zeros((1, 3, 3)) # [[0,0,0], [0,0,0], [0,0,0]] 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]) 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"] html = "" for i in sentence[0]: html += vocabulary[np.argmax(i)] + ' ' from IPython.core.display import display, HTML display(HTML(html[6:49])) |
Phiên bản nâng cao
Trong phiên bản này, chúng ta sẽ tự động hóa nhiều bước từ mô hình đầu tiên. Phần này sẽ tập trung vào việc tạo ra một phiên bản triển khai có thể mở rộng. Nó sẽ không thể dự đoán HTML từ các trang web ngẫu nhiên, nhưng nó vẫn là một thiết kế tuyệt vời.
Đầu tiên, bộ mã hóa (Encoder). Đây là nơi chúng ta tạo các tính năng hình ảnh và các tính năng đánh dấu trước đó. Các tính năng là các khối xây dựng mà mạng tạo ra để kết nối các mô hình thiết kế với đánh dấu. Ở cuối bộ mã hóa, ta dán các đặc điểm hình ảnh vào từng từ trong phần đánh dấu trước đó.
Sau đó, bộ giải mã (Decoder) sẽ sử dụng tính năng đánh dấu và thiết kế kết hợp và tạo ra một tính năng thẻ tiếp theo. Tính năng này được chạy thông qua một mạng nơ-ron được kết nối đầy đủ để dự đoán thẻ tiếp theo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
from os import listdir from numpy import array from keras.preprocessing.text import Tokenizer, one_hot from keras.preprocessing.sequence import pad_sequences from keras.models import Model from keras.utils import to_categorical from keras.layers import Embedding, TimeDistributed, RepeatVector, LSTM, concatenate , Input, Reshape, Dense, Flatten from keras.preprocessing.image import array_to_img, img_to_array, load_img from keras.applications.inception_resnet_v2 import InceptionResNetV2, preprocess_input import numpy as np # 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/test.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) |
Phiên bản cuối cùng
Trong phiên bản cuối cùng, chúng ta sẽ sử dụng tập dữ liệu các trang web bootstrap được tạo từ bài báo pix2code. Bằng cách sử dụng Bootstrap kết hợp HTML và CSS và giảm kích thước của từ vựng.
Các tính năng trích xuất từ các mô hình được đào tạo trước hoạt động tốt trong các mô hình chú thích hình ảnh. Các mô hình được đào tạo trước chưa được đào tạo về dữ liệu web và được tùy chỉnh để phân loại. Trong mô hình này, chúng ta thay thế các tính năng hình ảnh được đào tạo trước bằng một mạng nơ-ron khác. Thay vì sử dụng tổng hợp tối đa để tăng mật độ thông tin, mình sẽ tăng các bước tiến. Điều này duy trì vị trí và màu sắc của các phần tử cho front-end.
Có hai mô hình cốt lõi: mạng tích chập (CNN) và mạng hồi quy (RNN). Và một mạng nơ-ron phổ biến, cải tiến của mạng RNN đó là LSTM (Long Short Term Memory network).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
from os import listdir from numpy import array from keras.preprocessing.text import Tokenizer, one_hot from keras.preprocessing.sequence import pad_sequences from keras.models import Model, Sequential, model_from_json from keras.utils import to_categorical from keras.layers.core import Dense, Dropout, Flatten from keras.optimizers import RMSprop from keras.layers.convolutional import Conv2D from keras.callbacks import ModelCheckpoint from keras.layers import Embedding, TimeDistributed, RepeatVector, LSTM, concatenate , Input, Reshape, Dense from keras.preprocessing.image import array_to_img, img_to_array, load_img import numpy as np 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('resources/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=1, shuffle=False, validation_split=0.1, callbacks=callbacks_list, verbose=1, epochs=50) |
Bây giờ chúng ta xem kết quả
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
from os import listdir from keras.models import model_from_json from keras.preprocessing.text import Tokenizer from keras.preprocessing.sequence import pad_sequences from nltk.translate.bleu_score import sentence_bleu from tqdm import tqdm import numpy as np import h5py as h5py from compiler.classes.Compiler import * # 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)[-2:]: 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 # Initialize the function to create the vocabulary tokenizer = Tokenizer(filters='', split=" ", lower=False) # Create the vocabulary tokenizer.fit_on_texts([load_doc('resources/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 = 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 + ' ' # 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) |
Leave a Reply