tek4

Tạo Mô Tả Cho Hình Ảnh (Image Captioning) Với CNN & LSTM

by - September. 21, 2021
Học
Machine Learning
<p style="text-align: justify;"><em>Bạn nh&igrave;n thấy một h&igrave;nh ảnh v&agrave; n&atilde;o của bạn c&oacute; thể dễ d&agrave;ng biết h&igrave;nh ảnh đ&oacute; m&ocirc; tả về c&aacute;i g&igrave;, nhưng m&aacute;y t&iacute;nh c&oacute; thể biết được điều đ&oacute; hay kh&ocirc;ng? C&aacute;c chuy&ecirc;n gia nghi&ecirc;n cứu về thị gi&aacute;c m&aacute;y t&iacute;nh (Computer vision) đ&atilde; t&igrave;m hiểu vấn đề n&agrave;y rất nhiều v&agrave; họ coi đ&oacute; l&agrave; điều kh&ocirc;ng thể cho tới hiện tại. Với sự tiến bộ của Deep Learning, sự sẵn c&oacute; của những bộ dữ liệu khổng lồ v&agrave; sức mạnh của m&aacute;y vi t&iacute;nh, ch&uacute;ng ta ho&agrave;n to&agrave;n c&oacute; thể x&acirc;y dựng được c&aacute;c m&ocirc; h&igrave;nh c&oacute; thể tạo m&ocirc; tả cho h&igrave;nh ảnh.</em></p> <p style="text-align: justify;"><em>Project n&agrave;y sẽ sử dụng m&ocirc; h&igrave;nh CNN kết hợp LSTM để x&acirc;y dựng một tr&igrave;nh tạo m&ocirc; tả cho h&igrave;nh ảnh. B&acirc;y giờ, h&atilde;y c&ugrave;ng bắt đầu th&ocirc;i!</em></p> <h3 style="text-align: justify;"><strong>Giới Thiệu Về Project&nbsp;</strong></h3> <p style="text-align: justify;">Tr&igrave;nh tạo m&ocirc; tả cho h&igrave;nh ảnh li&ecirc;n quan đến thị gi&aacute;c m&aacute;y t&iacute;nh&nbsp;(Computer vision) v&agrave; xử l&yacute; ng&ocirc;n ngữ tự nhi&ecirc;n (natural language processing) để nhận ra được ngữ cảnh của h&igrave;nh ảnh, tiến tới m&ocirc; tả ch&uacute;ng bằng ng&ocirc;n ngữ tự nhi&ecirc;n như tiếng Anh...</p> <p style="text-align: justify;">Project n&agrave;y sử dụng CNN (Convolutional Neural Networks) v&agrave; LSTM (Long Short Term Memory) để triển khai. C&aacute;c features h&igrave;nh ảnh sẽ được tr&iacute;ch xuất từ Xception - một kiểu kiến tr&uacute;c của CNN, được đ&agrave;o tạo tr&ecirc;n tập dữ liệu imagenet dataset v&agrave; sau đ&oacute; đưa c&aacute;c features v&agrave;o m&ocirc; h&igrave;nh LSTM, m&ocirc; h&igrave;nh n&agrave;y sẽ chịu tr&aacute;ch nhiệm tạo m&ocirc; tả cho h&igrave;nh ảnh.</p> <h3 style="text-align: justify;"><strong>Bộ Dữ Liệu Sử Dụng</strong></h3> <p style="text-align: justify;">Bộ dữ liệu&nbsp;Flickr_8K sẽ được sử dụng trong project n&agrave;y, ngo&agrave;i bộ dữ liệu n&agrave;y c&ograve;n c&oacute; c&aacute;c bộ kh&aacute;c lớn hơn như bộ&nbsp;Flickr_30K v&agrave; bộ&nbsp;MSCOCO nhưng sẽ mất rất nhiều tuần để c&oacute; thể training tr&ecirc;n những bộ lớn như vậy, ch&iacute;nh v&igrave; thế trong b&agrave;i n&agrave;y sẽ chỉ sử dụng bộ dữ liệu nhỏ l&agrave;&nbsp;Flickr_8K. Lợi thế của một bộ dữ liệu lớn l&agrave; c&oacute; thể x&acirc;y dựng được c&aacute;c m&ocirc; h&igrave;nh tốt hơn, tối ưu hơn.</p> <p style="text-align: justify;">Download bộ dữ liệu tại đ&acirc;y:</p> <ul style="list-style-type: circle; text-align: justify;"> <li><a href="https://github.com/jbrownlee/Datasets/releases/download/Flickr8k/Flickr8k_Dataset.zip" target="_blank" rel="noopener">Flicker8k_Dataset&nbsp;</a></li> <li><a href="https://github.com/jbrownlee/Datasets/releases/download/Flickr8k/Flickr8k_text.zip" target="_blank" rel="noopener">Flickr_8k_text&nbsp;</a></li> </ul> <p style="text-align: justify;">Thư mục Flickr_8k_text chứa tệp Flickr8k.token đ&acirc;y l&agrave; tệp ch&iacute;nh của bộ dữ liệu, chứa t&ecirc;n h&igrave;nh ảnh v&agrave; c&aacute;c m&ocirc; tả tương ứng của ảnh, được ph&acirc;n t&aacute;ch bằng newline(&ldquo;\n&rdquo;).</p> <h3 style="text-align: justify;"><strong>Y&ecirc;u Cầu Cho Project</strong></h3> <p style="text-align: justify;">Project n&agrave;y y&ecirc;u cầu bạn cần c&oacute; kiến thức tốt về Deep learning, lập tr&igrave;nh Python, l&agrave;m việc tr&ecirc;n&nbsp;Jupyter notebooks, thư viện Keras, Numpy v&agrave; kiến thức về xử l&yacute; ng&ocirc;n ngữ tự nhi&ecirc;n.</p> <p style="text-align: justify;">H&atilde;y chắc chắn rằng bạn đ&atilde; c&agrave;i đặt tất cả những thư viện cần thiết dưới đ&acirc;y:</p> <ul style="list-style-type: disc; text-align: justify;"> <li>pip install tensorflow</li> <li>keras</li> <li>pillow</li> <li>numpy</li> <li>tqdm</li> <li>jupyterlab</li> </ul> <h2 style="text-align: justify;"><strong>Nội Dung Ch&iacute;nh</strong></h2> <h3 style="text-align: justify;"><strong>1. CNN l&agrave; g&igrave;?</strong></h3> <p style="text-align: justify;">CNN (Convolutional Neural networks) - Mạng nơ-ron t&iacute;ch chập l&agrave; mạng nơ-ron s&acirc;u chuy&ecirc;n biệt c&oacute; thể xử l&yacute; c&aacute;c dữ liệu c&oacute; dạng đầu v&agrave;o giống ma trận 2D. C&aacute;c dữ liệu h&igrave;nh ảnh rất dễ d&agrave;ng để biểu diễn dưới dạng ma trận 2D điều đ&oacute; l&agrave;m cho CNN trở l&ecirc;n hữu &iacute;ch khi l&agrave;m việc với dữ liệu h&igrave;nh ảnh.</p> <p style="text-align: justify;">CNN về cơ bản được sử dụng để ph&acirc;n loại h&igrave;nh ảnh v&agrave; x&aacute;c định xem h&igrave;nh ảnh l&agrave; chim, m&aacute;y bay, hay cũng c&oacute; thể l&agrave; Superman...v.v</p> <p style="text-align: justify;"><img style="width: 100%;" src="../../../public_files/e9500b1b-7359-4042-b60e-157b4b075456" alt="tao-mo-ta-cho-hinh-anh-image-captioning-voi-cnn-lstm-1" /></p> <p style="text-align: justify;">CNN qu&eacute;t h&igrave;nh ảnh từ tr&aacute;i sang phải v&agrave; từ tr&ecirc;n xuống dưới để tr&iacute;ch xuất ra c&aacute;c&nbsp;features quan trọng từ h&igrave;nh ảnh v&agrave; kết hợp feature ph&acirc;n loại h&igrave;nh ảnh. N&oacute; c&oacute; thể xử l&yacute; c&aacute;c h&igrave;nh ảnh đ&atilde; được dịch, xoay, thu nhỏ v&agrave; thay đổi g&oacute;c nh&igrave;n.</p> <h3 style="text-align: justify;"><strong>2. LSTM l&agrave; g&igrave;?</strong></h3> <p style="text-align: justify;">LSTM (Long short term memory) l&agrave; một loại RNN (Recurrent Neural Network) - mạng nơ-ron hồi quy, rất th&iacute;ch hợp với c&aacute;c b&agrave;i to&aacute;n dự đo&aacute;n tr&igrave;nh tự. Dựa tr&ecirc;n văn bản trước đ&oacute;, ch&uacute;ng c&oacute; thể dự đo&aacute;n được từ tiếp theo l&agrave; g&igrave;.&nbsp;N&oacute; đ&atilde; tự chứng minh t&iacute;nh hiệu quả từ RNN truyền thống bằng c&aacute;ch khắc phục những hạn chế của RNN vốn c&oacute; bộ nhớ ngắn hạn. LSTM c&oacute; thể thực thi c&aacute;c th&ocirc;ng tin c&oacute; &yacute; nghĩa trong suốt qu&aacute; tr&igrave;nh xử l&yacute; đầu v&agrave;o v&agrave; với forget gate, n&oacute; sẽ loại bỏ mọi th&ocirc;ng tin kh&ocirc;ng c&oacute; &yacute; nghĩa.</p> <p style="text-align: justify;">LSTM cell được thể hiện như h&igrave;nh dưới đ&acirc;y.</p> <p style="text-align: justify;"><img style="width: 100%;" src="../../../public_files/ed41af0a-3f12-486c-a2ab-3e5b0c9f43cb" alt="tao-mo-ta-cho-hinh-anh-image-captioning-voi-cnn-lstm-2" /></p> <h3 style="text-align: justify;"><strong>3. Tổng Quan M&ocirc; H&igrave;nh</strong></h3> <p style="text-align: justify;">Để x&acirc;y dựng m&ocirc; h&igrave;nh <em><strong>tạo m&ocirc; tả cho h&igrave;nh ảnh</strong></em> ch&uacute;ng ta sẽ hợp nhất c&aacute;c kiến tr&uacute;c n&ecirc;u tr&ecirc;n. N&oacute; c&ograve;n được gọi l&agrave; m&ocirc; h&igrave;nh CNN-RNN.</p> <ul style="list-style-type: disc; text-align: justify;"> <li>CNN được sử dụng để tr&iacute;ch xuất c&aacute;c đặc trưng từ h&igrave;nh ảnh. ch&uacute;ng ta sẽ sử dụng m&ocirc; h&igrave;nh Xception được đ&agrave;o tạo trước.</li> <li>LSTM sử dụng th&ocirc;ng tin từ CNN để gi&uacute;p tạo m&ocirc; tả cho h&igrave;nh ảnh.</li> </ul> <p style="text-align: justify;"><img style="width: 100%;" src="../../../public_files/60712675-f66d-466e-9123-f900d758d6bb" alt="tao-mo-ta-cho-hinh-anh-image-captioning-voi-cnn-lstm-3" /></p> <h3 style="text-align: justify;"><strong>4. Cấu Tr&uacute;c Tệp Của Project</strong></h3> <p style="text-align: justify;">Tải xuống c&aacute;c tập dữ liệu:</p> <p style="text-align: justify;"><strong><em>Flicker8k_Dataset:</em> </strong>C&oacute; chứa 8091 h&igrave;nh ảnh</p> <p style="text-align: justify;"><em><strong>Flickr_8k_text: </strong></em>C&oacute; chứa c&aacute;c tệp văn bản v&agrave; ch&uacute; th&iacute;ch của h&igrave;nh ảnh.</p> <p style="text-align: justify;">C&aacute;c tệp dưới đ&acirc;y sẽ được tạo ra trong qu&aacute; tr&igrave;nh thực hiện project</p> <p style="text-align: justify;"><strong><em>Models</em>&nbsp;</strong>- Chứa c&aacute;c m&ocirc; h&igrave;nh training</p> <p style="text-align: justify;"><strong><em>Descriptions.txt</em>&nbsp;</strong>- Chứa tất cả t&ecirc;n h&igrave;nh ảnh v&agrave; m&ocirc; tả của ch&uacute;ng sau khi được tiền xử l&yacute; (preprocessing)</p> <p style="text-align: justify;"><strong><em>Features.p</em>&nbsp;</strong>- Đối tượng pickle chứa h&igrave;nh ảnh v&agrave; vector đặc trưng của ch&uacute;ng tr&iacute;ch xuất từ Xception được pre-trained từ CNN model</p> <p style="text-align: justify;"><strong><em>Tokenizer.p</em>&nbsp;</strong>-&nbsp;chứa c&aacute;c token được tham chiếu với một gi&aacute; trị&nbsp;index</p> <p style="text-align: justify;"><strong><em>Model.png</em>&nbsp;</strong>- Tr&igrave;nh b&agrave;y trực quan k&iacute;ch thước project</p> <p style="text-align: justify;"><strong><em>Testing_caption_generator.py</em>&nbsp;</strong>- File python để tạo m&ocirc; tả cho h&igrave;nh ảnh</p> <p style="text-align: justify;"><strong><em>Training_caption_generator.ipynb</em>&nbsp;</strong>- M&ocirc; h&igrave;nh đ&agrave;o tạo để tạo m&ocirc; tả cho h&igrave;nh ảnh</p> <p style="text-align: justify;">Bạn c&oacute; thể tải xuống tất cả c&aacute;c tệp n&ecirc;u tr&ecirc;n bằng li&ecirc;n kết dưới đ&acirc;y</p> <p style="text-align: justify;"><a href="https://drive.google.com/file/d/13oJ_9jeylTmW7ivmuNmadwraWceHoQbK/view" target="_blank" rel="noopener">Python file project</a></p> <p style="text-align: justify;"><img style="width: 100%;" src="../../../public_files/f2fe628e-ff51-40b8-9386-a82a97b0979c" alt="tao-mo-ta-cho-hinh-anh-image-captioning-voi-cnn-lstm-4" /></p> <h3 style="text-align: justify;"><strong>5. X&acirc;y dựng project</strong></h3> <p style="text-align: justify;">H&atilde;y bắt đầu khởi chạy&nbsp;jupyter notebook&nbsp;bằng c&aacute;ch g&otilde; jupyter lab v&agrave;o cửa sổ command từ thư mục của project. Tạo&nbsp;Python3 notebook v&agrave; đặt t&ecirc;n l&agrave;&nbsp;<strong>training_caption_generator.ipynb.</strong></p> <p style="text-align: justify;"><img style="width: 100%;" src="../../../public_files/648c04df-e7ff-44ee-94f2-d14d00b2d935" alt="tao-mo-ta-cho-hinh-anh-image-captioning-voi-cnn-lstm-5" /></p> <h4 style="text-align: justify;"><strong>5.1. Đầu ti&ecirc;n h&atilde;y import tất cả c&aacute;c g&oacute;i v&agrave; thư viện cần thiết</strong></h4> <pre class="language-python"><code>import string import numpy as np from PIL import Image import os from pickle import dump, load import numpy as np from keras.applications.xception import Xception, preprocess_input from keras.preprocessing.image import load_img, img_to_array from keras.preprocessing.text import Tokenizer from keras.preprocessing.sequence import pad_sequences from keras.utils import to_categorical from keras.layers.merge import add from keras.models import Model, load_model from keras.layers import Input, Dense, LSTM, Embedding, Dropout # small library for seeing the progress of loops. from tqdm import tqdm_notebook as tqdm tqdm().pandas()</code></pre> <h4 style="text-align: justify;"><strong>5.2. Cleaning data</strong></h4> <p style="text-align: justify;">Tệp văn bản chứa tất cả m&ocirc; tả cho h&igrave;nh ảnh l&agrave;<em>&nbsp;<strong>Flickr8k.token</strong>&nbsp;</em>nằm trong thư mục&nbsp;<em><strong>Flickr_8k_text.</strong></em></p> <p style="text-align: justify;">H&atilde;y xem tập tin:</p> <p style="text-align: justify;"><img style="width: 100%;" src="../../../public_files/b9d77e8f-2e5c-46b3-9c09-04d98a23f759" alt="tao-mo-ta-cho-hinh-anh-image-captioning-voi-cnn-lstm-6" /></p> <p style="text-align: justify;">Nội dung của tệp l&agrave; t&ecirc;n h&igrave;nh ảnh v&agrave; m&ocirc; tả tương ứng được ph&acirc;n t&aacute;ch với nhau bằng c&aacute;c d&ograve;ng mới.</p> <p style="text-align: justify;">Mỗi h&igrave;nh ảnh c&oacute; 5 m&ocirc; tả v&agrave; ch&uacute;ng ta c&oacute; thể thấy rằng số 0-4 được g&aacute;n cho mỗi m&ocirc; tả.</p> <p style="text-align: justify;">C&oacute; 5&nbsp;functions:</p> <ul style="list-style-type: disc; text-align: justify;"> <li><em><strong>load_doc( filename ) - </strong></em>Để load tệp t&agrave;i liệu v&agrave; đọc nội dung của tệp th&agrave;nh một chuỗi</li> <li><em><strong>all_img_captions( filename )&nbsp;</strong></em>- Function n&agrave;y sẽ tạo từ điển m&ocirc; tả, &aacute;nh xạ h&igrave;nh ảnh với 5 m&ocirc; tả của n&oacute;. Từ điển m&ocirc; tả sẽ tr&ocirc;ng giống như sau.</li> </ul> <p style="text-align: justify;"><img style="width: 100%;" src="../../../public_files/bbf8c0a2-c478-4375-95b0-437fbf467eac" alt="tao-mo-ta-cho-hinh-anh-image-captioning-voi-cnn-lstm-7" /></p> <ul style="list-style-type: disc; text-align: justify;"> <li><em><strong>cleaning_text( descriptions)&nbsp;</strong></em>- H&agrave;m n&agrave;y nhận tất cả m&ocirc; tả v&agrave; thực hiện cleaning data. Đ&acirc;y l&agrave; một bước quan trọng khi l&agrave;m việc với dữ liệu văn bản, trong trường hợp n&agrave;y ch&uacute;ng ta sẽ loại bỏ c&aacute;c dấu chấm c&acirc;u, chuyển đổi tất cả văn bản th&agrave;nh chữ thường v&agrave; loại bỏ c&aacute;c từ c&oacute; chứa số. V&igrave; v&acirc;y, c&aacute;c m&ocirc; tả như "A man riding on a three-wheeled wheelchair" sẽ được chuyển th&agrave;nh "man riding on three wheeled wheelchair".</li> <li><em><strong>text_vocabulary( descriptions )&nbsp;</strong></em>-</li> <li><em><strong>save_descriptions( descriptions, filename )&nbsp;</strong></em>- Function n&agrave;y sẽ tạo một danh s&aacute;ch tất cả c&aacute;c m&ocirc; tả đ&atilde; được tiền xử l&yacute; v&agrave; lưu trữ ch&uacute;ng v&agrave;o một tệp. Ch&uacute;ng ta sẽ tạo tệp&nbsp;descriptions.txt&nbsp; để lưu trữ tất cả c&aacute;c m&ocirc; tả. N&oacute; sẽ tr&ocirc;ng giống như sau:</li> </ul> <p style="text-align: justify;"><img style="width: 100%;" src="../../../public_files/5025dc20-7ea9-48b2-ba40-94a0f76348a2" alt="tao-mo-ta-cho-hinh-anh-image-captioning-voi-cnn-lstm-8" /></p> <p style="text-align: justify;"><strong><em>Code:&nbsp;</em></strong></p> <pre class="language-python"><code># Loading a text file into memory def load_doc(filename): # Opening the file as read only file = open(filename, 'r') text = file.read() file.close() return text # get all imgs with their captions def all_img_captions(filename): file = load_doc(filename) captions = file.split('\n') descriptions ={} for caption in captions[:-1]: img, caption = caption.split('\t') if img[:-2] not in descriptions: descriptions[img[:-2]] = [ caption ] else: descriptions[img[:-2]].append(caption) return descriptions #Data cleaning- lower casing, removing puntuations and words containing numbers def cleaning_text(captions): table = str.maketrans('','',string.punctuation) for img,caps in captions.items(): for i,img_caption in enumerate(caps): img_caption.replace("-"," ") desc = img_caption.split() #converts to lowercase desc = [word.lower() for word in desc] #remove punctuation from each token desc = [word.translate(table) for word in desc] #remove hanging 's and a desc = [word for word in desc if(len(word)&gt;1)] #remove tokens with numbers in them desc = [word for word in desc if(word.isalpha())] #convert back to string img_caption = ' '.join(desc) captions[img][i]= img_caption return captions def text_vocabulary(descriptions): # build vocabulary of all unique words vocab = set() for key in descriptions.keys(): [vocab.update(d.split()) for d in descriptions[key]] return vocab #All descriptions in one file def save_descriptions(descriptions, filename): lines = list() for key, desc_list in descriptions.items(): for desc in desc_list: lines.append(key + '\t' + desc ) data = "\n".join(lines) file = open(filename,"w") file.write(data) file.close() # Set these path according to project folder in you system dataset_text = "D:\dataflair projects\Project - Image Caption Generator\Flickr_8k_text" dataset_images = "D:\dataflair projects\Project - Image Caption Generator\Flicker8k_Dataset" #we prepare our text data filename = dataset_text + "/" + "Flickr8k.token.txt" #loading the file that contains all data #mapping them into descriptions dictionary img to 5 captions descriptions = all_img_captions(filename) print("Length of descriptions =" ,len(descriptions)) #cleaning the descriptions clean_descriptions = cleaning_text(descriptions) #building vocabulary vocabulary = text_vocabulary(clean_descriptions) print("Length of vocabulary = ", len(vocabulary)) #saving each description to file save_descriptions(clean_descriptions, "descriptions.txt")</code></pre> <h4 style="text-align: justify;"><strong>5.3. Tr&iacute;ch xuất vector đặc trưng từ c&aacute;c h&igrave;nh ảnh</strong></h4> <p style="text-align: justify;">Kỹ thuật n&agrave;y c&ograve;n được gọi l&agrave; học chuyển giao (&nbsp;transfer learning). Ch&uacute;ng ta sử dụng m&ocirc; h&igrave;nh được&nbsp;pre-trained tr&ecirc;n c&aacute;c tập dữ liệu lớn v&agrave; tr&iacute;ch xuất c&aacute;c đặc trưng từ m&ocirc; h&igrave;nh n&agrave;y để sử dụng ch&uacute;ng trong project, cụ thể l&agrave; m&ocirc; h&igrave;nh&nbsp;&nbsp;Xception đ&atilde; được pre-trained&nbsp;tr&ecirc;n tập dữ liệu imagenet c&oacute; 1000 lớp kh&aacute;c nhau để ph&acirc;n loại. Ta c&oacute; thể import trực tiếp m&ocirc; h&igrave;nh n&agrave;y bằng&nbsp;keras.applications.&nbsp;V&igrave; m&ocirc; h&igrave;nh Xception ban đầu được x&acirc;y dựng cho imagenet n&ecirc;n ch&uacute;ng ta cần thực hiện một số thay đổi nhỏ để ph&ugrave; hợp với project n&agrave;y.</p> <p style="text-align: justify;">H&agrave;m extract_features() sẽ tr&iacute;ch xuất c&aacute;c đặc trưng cho tất cả c&aacute;c h&igrave;nh ảnh v&agrave; ch&uacute;ng ta sẽ thực hiện &aacute;nh xạ c&aacute;c t&ecirc;n h&igrave;nh ảnh với mảng đặc trưng tương ứng của ch&uacute;ng.&nbsp;Sau đ&oacute;, ch&uacute;ng ta sẽ kết xuất từ ​​điển features v&agrave;o tệp &ldquo;features.p&rdquo;.</p> <p style="text-align: justify;"><strong><em>Code:&nbsp;</em></strong></p> <pre class="language-python"><code>def extract_features(directory): model = Xception( include_top=False, pooling='avg' ) features = {} for img in tqdm(os.listdir(directory)): filename = directory + "/" + img image = Image.open(filename) image = image.resize((299,299)) image = np.expand_dims(image, axis=0) #image = preprocess_input(image) image = image/127.5 image = image - 1.0 feature = model.predict(image) features[img] = feature return features #2048 feature vector features = extract_features(dataset_images) dump(features, open("features.p","wb"))</code></pre> <p style="text-align: justify;"><img style="width: 100%;" src="../../../public_files/eb15d5a5-581b-4fe8-86e6-373f141c6e98" alt="tao-mo-ta-cho-hinh-anh-image-captioning-voi-cnn-lstm-9" /></p> <p style="text-align: justify;">Qu&aacute; tr&igrave;nh n&agrave;y c&oacute; thể mất rất nhiều thời gian, t&ugrave;y thuộc v&agrave;o hệ thống của bạn. Bạn c&oacute; thể sử dụng trực tiếp tệp&nbsp;features.p được tải xuống từ phần giới thiệu b&ecirc;n tr&ecirc;n.</p> <pre class="language-python"><code>features = load(open("features.p","rb"))</code></pre> <h4 style="text-align: justify;"><strong>5.4. Load dataset để training m&ocirc; h&igrave;nh</strong></h4> <p style="text-align: justify;">Trong thư mục Flickr8k_text c&oacute; tệp Flickr_8k.trainImages.txt chứa danh s&aacute;ch 6000 t&ecirc;n h&igrave;nh ảnh sẽ sử dụng để training.</p> <p style="text-align: justify;">Để load tệp dataset training, ta cần x&acirc;y dựng c&aacute;c h&agrave;m sau:</p> <p style="text-align: justify;"><strong>load_photos( filename )&nbsp;</strong>- H&agrave;m n&agrave;y sẽ load tệp văn bản v&agrave; trả về danh s&aacute;ch t&ecirc;n h&igrave;nh ảnh.</p> <p style="text-align: justify;"><strong>load_clean_descriptions( filename, photos )&nbsp;</strong>- H&agrave;m n&agrave;y sẽ tạo ra từ điển chứa m&ocirc; tả cho từng h&igrave;nh ảnh từ danh s&aacute;ch ảnh. C&aacute;c m&ocirc; tả n&agrave;y sẽ được th&ecirc;m c&aacute;c chỉ số để nhận dạng&nbsp;&lt;start&gt; and &lt;end&gt; cho mỗi m&ocirc; tả, cần l&agrave;m điều n&agrave;y để&nbsp;m&ocirc; h&igrave;nh LSTM c&oacute; thể x&aacute;c định phần bắt đầu v&agrave; phần kết th&uacute;c của phụ đề.</p> <p style="text-align: justify;"><strong>load_features(photos)</strong>&nbsp;-&nbsp;H&agrave;m n&agrave;y sẽ cung cấp cho ch&uacute;ng ta từ điển t&ecirc;n ảnh v&agrave; vectơ đặc trưng của ch&uacute;ng m&agrave; ch&uacute;ng ta đ&atilde; tr&iacute;ch xuất trước đ&oacute; từ m&ocirc; h&igrave;nh Xception.</p> <p style="text-align: justify;"><strong><em>Code:&nbsp;</em></strong></p> <pre class="language-python"><code>#load the data def load_photos(filename): file = load_doc(filename) photos = file.split("\n")[:-1] return photos def load_clean_descriptions(filename, photos): #loading clean_descriptions file = load_doc(filename) descriptions = {} for line in file.split("\n"): words = line.split() if len(words)&lt;1 : continue image, image_caption = words[0], words[1:] if image in photos: if image not in descriptions: descriptions[image] =[] desc = '&lt;start&gt; ' + " ".join(image_caption) + '&lt;end&gt; ' descriptions[image].append(desc) return descriptions def load_features(photos): #loading all features all_features = load(open("features.p","rb")) #selecting only needed features features = {k:all_features[k] for k in photos} return features filename = dataset_text + "/" + "Flickr_8k.trainImages.txt" #train = loading_data(filename) train_imgs = load_photos(filename) train_descriptions = load_clean_descriptions("descriptions.txt", train_imgs) train_features = load_features(train_imgs)</code></pre> <h4 style="text-align: justify;"><strong>5.5.&nbsp;Tokenizing the vocabulary&nbsp;</strong></h4> <p style="text-align: justify;">M&aacute;y t&iacute;nh kh&ocirc;ng hiểu c&aacute;c từ ngữ tự nhi&ecirc;n v&igrave; vậy ch&uacute;ng ta cần phải biểu diễn c&aacute;c từ bằng c&aacute;c con số. Ch&uacute;ng ta sẽ thực hiện &aacute;nh xạ từng từ với 1 chỉ mục duy nhất.&nbsp;Thư viện Keras cung cấp chức năng tokenizer để thực hiện điều n&agrave;y v&agrave; lưu ch&uacute;ng v&agrave;o tệp "tokenizer.p".</p> <p style="text-align: justify;"><strong><em>Code:&nbsp;</em></strong></p> <pre class="language-python"><code>#converting dictionary to clean list of descriptions def dict_to_list(descriptions): all_desc = [] for key in descriptions.keys(): [all_desc.append(d) for d in descriptions[key]] return all_desc #creating tokenizer class #this will vectorise text corpus #each integer will represent token in dictionary from keras.preprocessing.text import Tokenizer def create_tokenizer(descriptions): desc_list = dict_to_list(descriptions) tokenizer = Tokenizer() tokenizer.fit_on_texts(desc_list) return tokenizer # give each word an index, and store that into tokenizer.p pickle file tokenizer = create_tokenizer(train_descriptions) dump(tokenizer, open('tokenizer.p', 'wb')) vocab_size = len(tokenizer.word_index) + 1 vocab_size</code></pre> <p style="text-align: justify;">Tiếp theo thực hiện t&iacute;nh to&aacute;n độ d&agrave;i tối đ&atilde; của c&aacute;c m&ocirc; tả, điều n&agrave;y rất quan trọng&nbsp;cho việc quyết định c&aacute;c th&ocirc;ng số cấu tr&uacute;c m&ocirc; h&igrave;nh.&nbsp;Độ d&agrave;i tối đa của m&ocirc; tả l&agrave; 32.</p> <pre class="language-python"><code>#calculate maximum length of descriptions def max_length(descriptions): desc_list = dict_to_list(descriptions) return max(len(d.split()) for d in desc_list) max_length = max_length(descriptions) max_length</code></pre> <h4 style="text-align: justify;"><strong>5.6. Create Data generator</strong></h4> <p style="text-align: justify;">Trước ti&ecirc;n, ch&uacute;ng ta cần phải xem x&eacute;t &nbsp;input and output của m&ocirc; h&igrave;nh.&nbsp;M&ocirc; h&igrave;nh được đ&agrave;o tạo tr&ecirc;n bộ dữ liệu 6000 h&igrave;nh ảnh, mỗi h&igrave;nh ảnh sẽ chứa 2048 vector đặc trưng độ d&agrave;i v&agrave; ch&uacute; th&iacute;ch cũng được biểu thị dưới dạng số.&nbsp;Kh&ocirc;ng thể lưu trữ lượng dữ liệu cho 6000 h&igrave;nh ảnh n&agrave;y trong bộ nhớ, v&igrave; vậy cần phải ph&acirc;n t&aacute;ch tập dữ liệu lớn th&agrave;nh từng tập con dữ liệu.</p> <p style="text-align: justify;"><strong>V&iacute; dụ:</strong></p> <p style="text-align: justify;">Đầu v&agrave;o cho m&ocirc; h&igrave;nh l&agrave; [x1, x2] v&agrave; đầu ra sẽ l&agrave; y, trong đ&oacute; x1 l&agrave;&nbsp;2048 vectơ đặc trưng của h&igrave;nh ảnh đ&oacute;, x2 l&agrave; chuỗi văn bản đầu v&agrave;o v&agrave; y l&agrave; chuỗi văn bản đầu ra m&agrave; m&ocirc; h&igrave;nh phải dự đo&aacute;n.</p> <table style="border-collapse: collapse; width: 100%;" border="1"> <tbody> <tr> <td style="width: 27.9915%;">x1(feature vector)</td> <td style="width: 43.2692%;">x2(Text sequence)</td> <td style="width: 28.6325%;">y(word to predict)</td> </tr> <tr> <td style="width: 27.9915%;">feature</td> <td style="width: 43.2692%;">start</td> <td style="width: 28.6325%;">two</td> </tr> <tr> <td style="width: 27.9915%;">feature</td> <td style="width: 43.2692%;">start, two</td> <td style="width: 28.6325%;">dogs</td> </tr> <tr> <td style="width: 27.9915%;">feature</td> <td style="width: 43.2692%;">start, two, dogs</td> <td style="width: 28.6325%;">drink</td> </tr> <tr> <td style="width: 27.9915%;">feature</td> <td style="width: 43.2692%;">start, two, dogs, drink</td> <td style="width: 28.6325%;">water</td> </tr> <tr> <td style="width: 27.9915%;">feature</td> <td style="width: 43.2692%;">start, two, dogs, drink, water</td> <td style="width: 28.6325%;">end</td> </tr> </tbody> </table> <pre class="language-python"><code>#create input-output sequence pairs from the image description. #data generator, used by model.fit_generator() def data_generator(descriptions, features, tokenizer, max_length): while 1: for key, description_list in descriptions.items(): #retrieve photo features feature = features[key][0] input_image, input_sequence, output_word = create_sequences(tokenizer, max_length, description_list, feature) yield [[input_image, input_sequence], output_word] def create_sequences(tokenizer, max_length, desc_list, feature): X1, X2, y = list(), list(), list() # walk through each description for the image for desc in desc_list: # encode the sequence seq = tokenizer.texts_to_sequences([desc])[0] # split one sequence into multiple X,y pairs for i in range(1, len(seq)): # split into input and output pair in_seq, out_seq = seq[:i], seq[i] # pad input sequence in_seq = pad_sequences([in_seq], maxlen=max_length)[0] # encode output sequence out_seq = to_categorical([out_seq], num_classes=vocab_size)[0] # store X1.append(feature) X2.append(in_seq) y.append(out_seq) return np.array(X1), np.array(X2), np.array(y) #You can check the shape of the input and output for your model [a,b],c = next(data_generator(train_descriptions, features, tokenizer, max_length)) a.shape, b.shape, c.shape #((47, 2048), (47, 32), (47, 7577))</code></pre> <h4 style="text-align: justify;">5.7. Define m&ocirc; h&igrave;nh CNN-RNN</h4> <p style="text-align: justify;">Để x&aacute;c định cấu tr&uacute;c của m&ocirc; h&igrave;nh, ch&uacute;ng ta sẽ sử dụng M&ocirc; h&igrave;nh Keras, n&oacute; sẽ bao gồm ba phần ch&iacute;nh:</p> <ul style="list-style-type: disc; text-align: justify;"> <li><strong>Feature Extractor&nbsp;</strong>- Giảm k&iacute;ch thước xuống c&ograve;n&nbsp;256 nodes.</li> <li><strong>Sequence Processor&nbsp;</strong>-&nbsp;Bộ xử l&yacute; tr&igrave;nh tự</li> <li><strong>Decoder&nbsp;</strong>&nbsp;-&nbsp;Bộ giải m&atilde;</li> </ul> <p style="text-align: justify;">Tr&igrave;nh b&agrave;y trực quan của m&ocirc; h&igrave;nh:</p> <p style="text-align: justify;"><img style="width: 100%;" src="../../../public_files/7bbafbae-eca3-4477-8c1c-fae5a20777b8" alt="tao-mo-ta-cho-hinh-anh-image-captioning-voi-cnn-lstm-10" /></p> <pre class="language-python"><code>from keras.utils import plot_model # define the captioning model def define_model(vocab_size, max_length): # features from the CNN model squeezed from 2048 to 256 nodes inputs1 = Input(shape=(2048,)) fe1 = Dropout(0.5)(inputs1) fe2 = Dense(256, activation='relu')(fe1) # LSTM sequence model inputs2 = Input(shape=(max_length,)) se1 = Embedding(vocab_size, 256, mask_zero=True)(inputs2) se2 = Dropout(0.5)(se1) se3 = LSTM(256)(se2) # Merging both models decoder1 = add([fe2, se3]) decoder2 = Dense(256, activation='relu')(decoder1) outputs = Dense(vocab_size, activation='softmax')(decoder2) # tie it together [image, seq] [word] model = Model(inputs=[inputs1, inputs2], outputs=outputs) model.compile(loss='categorical_crossentropy', optimizer='adam') # summarize model print(model.summary()) plot_model(model, to_file='model.png', show_shapes=True) return model</code></pre> <h4 style="text-align: justify;"><strong>5.8.&nbsp;Training the model</strong></h4> <pre class="language-python"><code># train our model print('Dataset: ', len(train_imgs)) print('Descriptions: train=', len(train_descriptions)) print('Photos: train=', len(train_features)) print('Vocabulary Size:', vocab_size) print('Description Length: ', max_length) model = define_model(vocab_size, max_length) epochs = 10 steps = len(train_descriptions) # making a directory models to save our models os.mkdir("models") for i in range(epochs): generator = data_generator(train_descriptions, train_features, tokenizer, max_length) model.fit_generator(generator, epochs=1, steps_per_epoch= steps, verbose=1) model.save("models/model_" + str(i) + ".h5")</code></pre> <h4 style="text-align: justify;"><strong>5.9.&nbsp;Testing the model</strong></h4> <p style="text-align: justify;">M&ocirc; h&igrave;nh sau khi được training, ch&uacute;ng ta sẽ tạo 1 tệp l&agrave;&nbsp;<strong>testing_caption_generator.py</strong> tệp n&agrave;y sẽ load m&ocirc; h&igrave;nh v&agrave; tạo c&aacute;c dự đo&aacute;n</p> <pre class="language-python"><code>import numpy as np from PIL import Image import matplotlib.pyplot as plt import argparse ap = argparse.ArgumentParser() ap.add_argument('-i', '--image', required=True, help="Image Path") args = vars(ap.parse_args()) img_path = args['image'] def extract_features(filename, model): try: image = Image.open(filename) except: print("ERROR: Couldn't open image! Make sure the image path and extension is correct") image = image.resize((299,299)) image = np.array(image) # for images that has 4 channels, we convert them into 3 channels if image.shape[2] == 4: image = image[..., :3] image = np.expand_dims(image, axis=0) image = image/127.5 image = image - 1.0 feature = model.predict(image) return feature def word_for_id(integer, tokenizer): for word, index in tokenizer.word_index.items(): if index == integer: return word return None def generate_desc(model, tokenizer, photo, max_length): in_text = 'start' for i in range(max_length): sequence = tokenizer.texts_to_sequences([in_text])[0] sequence = pad_sequences([sequence], maxlen=max_length) pred = model.predict([photo,sequence], verbose=0) pred = np.argmax(pred) word = word_for_id(pred, tokenizer) if word is None: break in_text += ' ' + word if word == 'end': break return in_text #path = 'Flicker8k_Dataset/111537222_07e56d5a30.jpg' max_length = 32 tokenizer = load(open("tokenizer.p","rb")) model = load_model('models/model_9.h5') xception_model = Xception(include_top=False, pooling="avg") photo = extract_features(img_path, xception_model) img = Image.open(img_path) description = generate_desc(model, tokenizer, photo, max_length) print("\n\n") print(description) plt.imshow(img)</code></pre> <h3 style="text-align: justify;">Kết Quả</h3> <p style="text-align: justify;"><img style="width: 100%;" src="../../../public_files/ced9bec9-9519-47f6-b311-c8c057a618c5" alt="tao-mo-ta-cho-hinh-anh-image-captioning-voi-cnn-lstm-11" /></p> <p style="text-align: justify;"><img style="width: 100%;" src="../../../public_files/047c20bb-4643-4005-97fe-16f1604160f0" alt="tao-mo-ta-cho-hinh-anh-image-captioning-voi-cnn-lstm-12" /></p> <p style="text-align: justify;"><img style="width: 100%;" src="../../../public_files/596a2532-eb01-4ff6-8c4c-17c1ab6691f5" alt="tao-mo-ta-cho-hinh-anh-image-captioning-voi-cnn-lstm-13" /></p> <h3 style="text-align: justify;">Kết Luận</h3> <p style="text-align: justify;">Trong project n&agrave;y, ch&uacute;ng ta&nbsp;đ&atilde; triển khai m&ocirc; h&igrave;nh CNN-RNN bằng c&aacute;ch x&acirc;y dựng tr&igrave;nh tạo m&ocirc; tả h&igrave;nh ảnh. Một điểm cần lưu &yacute; đ&oacute; l&agrave;&nbsp;m&ocirc; h&igrave;nh của ch&uacute;ng ta phụ thuộc v&agrave;o dữ liệu, do đ&oacute; n&oacute; kh&ocirc;ng thể dự đo&aacute;n c&aacute;c từ nằm ngo&agrave;i vốn từ vựng của n&oacute;. Trong b&agrave;i n&agrave;y ch&uacute;ng ta chỉ sử dụng bộ dữ liệu nhỏ gồm 8000 h&igrave;nh ảnh, đối với c&aacute;c m&ocirc; h&igrave;nh&nbsp;production-level cần phải training tr&ecirc;n tập dữ liệu lớn hơn 100.000 h&igrave;nh ảnh để c&oacute; thể tạo ra c&aacute;c m&ocirc; h&igrave;nh c&oacute; độ ch&iacute;nh x&aacute;c tốt hơn.</p> <hr /> <p style="text-align: center;"><em><strong>Fanpage Facebook:</strong>&nbsp;<a href="https://www.facebook.com/tek4.vn/">TEK4.VN</a></em>&nbsp;</p> <p style="text-align: center;"><em><strong>Tham gia cộng đồng để chia sẻ, trao đổi v&agrave; thảo luận:</strong>&nbsp;<a href="https://www.facebook.com/groups/tek4.vn/">TEK4.VN - Học Lập Tr&igrave;nh Miễn Ph&iacute;</a></em></p>