我们都知道,神经网络可以在执行某些任务时复制人脑的功能。神经网络在计算机视觉和自然语言生成方面的应用已经非常引人注目。
本文将介绍神经网络的一个这样的应用,并让读者了解如何使用CNNs和RNNs (LSTM)的混合网络实际为图像生成标题(描述)。我们在这个任务中使用的数据集是流行的flickr 8k图像数据集,它是这个任务的基准数据,可以通过下面的链接访问。
Kaggle — https://www.kaggle.com/adityajn105/flickr8k
注意:我们将把数据集分割为7k用于训练,1k用于测试。
我们将首先讨论在我们的混合神经网络中不同的组件(层)和它们的功能。与此同时,我们还将研究使用Tensorflow、Keras和Python开发混合神经网络的实际实现。
神经网络的总体结构
让我们来看看我们将用于生成字幕的神经网络的总体架构。
简单地说,上述神经网络有3个主要组成部分(子网络),每个子网络都有一个特定的任务,即卷积网络(用于从图像中提取特征)、rstm(用于生成文本)和解码器(用于合并两种网络)。
现在让我们详细讨论每个组件并了解它们的工作原理。
图像特征提取器
为了从图像中生成特征,我们将使用卷积神经网络,只需稍加修改。让我们来看看一个用于图像识别的卷积神经网络。
一般的CNN分类模型有两个子网络
Feature Learning Network—负责从图像中生成Feature map的网络(多卷积和池化层的网络)。
分类网络——负责图像分类的全连通深度神经网络(多稠密层、单输出层网络)。
由于我们只对从图像中提取特征感兴趣,而对其分类不感兴趣,所以我们只对CNN的Feature Learning部分进行处理,这就是我们从图像中提取特征的方法。
下面的代码可以用来从任何一组图像提取特征:
import tensorflow as tf from keras.preprocessing import image import numpy as np # function to extract features from image def extract_image_features(): model = tf.keras.models.Sequential() # adding first layers of convolution and pooling layers to network model.add(tf.keras.layers.Conv2D(filters=64, kernel_size=(3,3), input_shape=(90,90,3), padding="VALID", activation="relu")) model.add(tf.keras.layers.Conv2D(filters=64, kernel_size=(3,3), activation="relu")) model.add(tf.keras.layers.MaxPool2D(pool_size=2, strides=2)) # adding second layers of convolution and pooling layers to network model.add(tf.keras.layers.Conv2D(filters=32, kernel_size=(3,3), padding="VALID", activation="relu")) model.add(tf.keras.layers.Conv2D(filters=32, kernel_size=(3,3), activation="relu")) model.add(tf.keras.layers.AveragePooling2D(pool_size=2, strides=1)) # flattening the output using flatten layer, since the input to neural net has to be flat model.add(tf.keras.layers.Flatten()) # model summary model.summary() return model for file in os.listdir(image_path): path = image_path + "//" + file img = image.load_img(path, target_size=(90, 90)) img_data = image.img_to_array(img) img_data = np.expand_dims(img_data, axis=0) img_data = preprocess_input(img_data) feature = extract_image_features.predict(img_data) feature = np.reshape(feature, feature.shape[1])
任何人都可以使用上述代码构建自己的图像特征提取器,但有一个问题…
上面的模型太过简单,无法从我们的一组图像中提取出每一个重要的细节,因此会影响整个模型的性能。此外,由于高性能gpu和系统的不可用性,使得模型过于复杂(具有大量神经元的多层密集层)也具有挑战性。
为了解决这个问题,我们在Tensorflow中有非常流行的预训练CNN模型(VGG-16, ResNet50等,由不同大学和组织的科学家开发),可以用于从图像中提取特征。记住,在使用输出层进行特征提取之前,要将它从模型中移除。
下面的代码将让您了解如何使用Tensorflow中这些预先训练好的模型从图像中提取特征。
import tensorflow as tf from keras.preprocessing import image from keras.applications.resnet50 import ResNet50 from keras.applications.resnet50 import preprocess_input from keras.models import Model # load the ResNet50 Model feature_extractor = ResNet50(weights='imagenet', include_top=False) feature_extractor_new = Model(feature_extractor.input, feature_extractor.layers[-2].output) feature_extractor_new.summary() for file in os.listdir(image_path): path = image_path + "//" + file img = image.load_img(path, target_size=(90, 90)) img_data = image.img_to_array(img) img_data = np.expand_dims(img_data, axis=0) img_data = preprocess_input(img_data) feature = feature_extractor_new.predict(img_data) feature_reshaped = np.array(feature).flatten()
正如您在下面看到的,如果执行上面的代码,您将看到我们的图像特性只是一个形状-(18432,)的numpy数组。
image_feature_dictionary[list(image_feature_dictionary. Keys())[0]].shape (18432,)
接下来,我们将开发用于为图像生成标题的LSTM网络(RNN)。
用于生成标题的LSTM
文本生成是LSTM网络中最流行的应用之一。LSTM单元格(LSTM网络的基本构建块)能够根据前一层的输出生成输出,即它保留前一层(内存)的输出,并使用该内存生成(预测)序列中的下一个输出。
对于我们的数据集,我们为每张图片设置了5个标题,即总共40k个标题。
让我们看看我们的数据集-
- *A child in a pink dress is climbing up a set of stairs in an entry way.*
- *A girl going into a wooden building.*
- *A little girl climbing into a wooden playhouse.*
- *A little girl climbing the stairs to her playhouse.*
- *A little girl in a pink dress going into a wooden cabin.*
正如所见,所有的字幕都很好地描述了图片。我们现在的任务是设计一个RNN,它可以为任何相似的图像集复制这个任务。
回到最初的任务,我们首先必须看看LSTM网络是如何生成文本的。对于LSTM来说,网络标题只不过是一长串单独的单词(编码为数字)放在一起。利用这些信息,它试图根据前面的单词预测序列中的下一个单词(记忆)。
在我们的例子中,由于标题可以是可变长度的,所以我们首先需要指定每个标题的开始和结束。我们看看-是什么意思
首先,我们将把<start>和<end>添加到数据集中的每个标题中。在创建最终词汇表之前,我们将对训练数据集中的每个标题进行标记。为了训练我们的模型,我们将从词汇表中删除频率小于或等于10的单词。增加这一步是为了提高我们的模型的一般性能,并防止它过拟合训练数据集。
代码如下:
# loading captions from captions file import pandas as pd # loading captions.txt captions = pd.read_csv('/kaggle/input/flickr8k/captions.txt', sep=",") captions = captions.rename(columns=lambda x: x.strip().lower()) captions['image'] = captions['image'].apply(lambda x: x.split(".")[0]) captions = captions[['image', 'caption']] # adding <start> and <end> to every caption captions['caption'] = "<start> " + captions['caption'] + " <end>" # in case we have any missing caption/blank caption drop it print(captions.shape) captions = captions.dropna() print(captions.shape) # training and testing image captions split train_image_captions = {} test_image_captions = {} # list for storing every caption all_captions = [] # storing training data for image in train_data_images: tempDf = captions[captions['image'] == image] list_of_captions = tempDf['caption'].tolist() train_image_captions[image] = list_of_captions all_captions.append(list_of_captions) # store testing data for image in test_data_images: tempDf = captions[captions['image'] == image] list_of_captions = tempDf['caption'].tolist() test_image_captions[image] = list_of_captions all_captions.append(list_of_captions) print("Data Statistics") print(f"Training Images Captions {len(train_image_captions.keys())}") print(f"Testing Images Captions {len(test_image_captions.keys())}")
上面的代码将生成下面的输出
train_image_captions[list(train_image_captions. Keys())[150]] ['<start> A brown dog chases a tattered ball around the yard . <end>', '<start> A brown dog is chasing a tattered soccer ball across a low cut field . <end>', '<start> Large brown dog playing with a white soccer ball in the grass . <end>', '<start> Tan dog chasing a ball . <end>', '<start> The tan dog is chasing a ball . <end>']
一旦我们加载了标题,我们将首先使用spacy和Tokenizer(来自tensorflow.preprocessing.)对所有内容进行标记。文本类)。
令牌化就是将一个句子分解成不同的单词,同时删除特殊字符,所有内容都小写。结果是我们在句子中有了一个有意义的单词(记号)的语料库,我们可以在将其用作模型的输入之前对其进行进一步编码。
import spacy nlp = spacy.load('en', disable=['tagger', 'parser', 'ner']) # tokenize evry captions, remove punctuations, lowercase everything for key, value in train_image_captions.items(): ls = [] for v in value: doc = nlp(v) new_v = " " for token in doc: if not token.is_punct: if token.text not in [" ", "\n", "\n\n"]: new_v = new_v + " " + token.text.lower() new_v = new_v.strip() ls.append(new_v) train_image_captions[key] = ls # create a vocabulary of all the unique words present in captions # flatten the list all_captions = [caption for list_of_captions in all_captions for caption in list_of_captions] # use spacy to convert to lowercase and reject any special characters tokens = [] for captions in all_captions: doc = nlp(captions) for token in doc: if not token.is_punct: if token.text not in [" ", "\n", "\n\n"]: tokens.append(token.text.lower()) # get tokens with frequency less than 10 import collections word_count_dict = collections.Counter(tokens) reject_words = [] for key, value in word_count_dict.items(): if value < 10: reject_words.append(key) reject_words.append("<") reject_words.append(">") # remove tokens that are in reject words tokens = [x for x in tokens if x not in reject_words] # convert the token to equivalent index using Tokenizer class of Keras from keras.preprocessing.text import Tokenizer tokenizer = Tokenizer() tokenizer.fit_on_texts(tokens)