用Keras在TensorFlow上进行迁移学习示例详解
本文将展示如何通过实现图像分类应用程序在TensorFlow上使用Keras进行迁移学习。当您实施卷积神经网络(CNN)来对某些图像进行分类时,转移学习是一种有用的方法。通过使用该方法,您可以节省大量宝贵时间!
简介
我们将迈出开发可用作移动或Web应用程序一部分的算法的第一步。目标是根据狗的品种对狗的图像进行分类。如果在图像中检测到狗,它将提供狗的品种的估计。如果检测到人类,它将提供对应的相似估计。
实现步骤
- 第0步:导入数据集
- 第1步:检测人
- 第2步:检测狗
- 第3步:创建CNN以对狗品种进行分类(来自Scratch)
- 步骤4:创建CNN以对狗品种进行分类(使用迁移学习)
- 第5步:测试算法
第0步:导入数据集
首先,我们导入狗狗图像的数据集。我们通过使用scikit-learn库中的load_files函数来填充一些变量:
from sklearn.datasets import load_files from keras.utils import np_utils import numpy as np from glob import glob # define function to load train, test, and validation datasets def load_dataset(path): data = load_files(path) dog_files = np.array(data['filenames']) dog_targets = np_utils.to_categorical(np.array(data['target']), 133) return dog_files, dog_targets # load train, test, and validation datasets train_files, train_targets = load_dataset('dog_images/train') valid_files, valid_targets = load_dataset('dog_images/valid') test_files, test_targets = load_dataset('dog_images/test') # load list of dog names dog_names = [item[20:-1] for item in sorted(glob("dog_images/train/*/"))]
下载狗数据集(https://s3-us-west-1.amazonaws.com/udacity-aind/dog-project/dogImages.zip)。解压缩文件,路径示例`path / to / dog_images`。
接下来,我们导入一个人图像数据集,其中文件路径存储在numpy数组human_files中。
import random random.seed(8675309) # load filenames in shuffled human dataset human_files = np.array(glob("lfw/*/*")) random.shuffle(human_files)
下载人数据集(https://s3-us-west-1.amazonaws.com/udacity-aind/dog-project/lfw.zip)。解压缩文件,路径示例:`path / to / lfw`。
第1步:检测人
我们使用OpenCV实现的基于Haar特征的级联分类器来检测图像中的人脸。OpenCV提供了许多预训练好的面部检测器,作为XML文件存储在github上(https://github.com/opencv/opencv/tree/master/data/haarcascades)。我们下载其中一个探测器并将其存储在haarcascades目录中。
我们可以使用Python编写一个函数,如果在图像中检测到人脸返回True,否则返回False。
# returns "True" if face is detected in image stored at img_path def face_detector(img_path): img = cv2.imread(img_path) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray) return len(faces) > 0
测试face_detector函数的性能。
human_files_short = human_files[:100] dog_files_short = train_files[:100] print("human_files: " + str(sum([1 for human_file in human_files_short if face_detector(human_file) == True])) + "%") print("dog_files: " + str(sum([1 for dog_file in dog_files_short if face_detector(dog_file) == True])) + "%")
输出为human_files:100%和dog_files:11%
注:人脸检测使用基于Haar特征的级联分类器时,使用了大量的Haar特征,但这些特征都是经验定义的。如果我们有大量的人脸图像数据,并且有时间搜索超参数,则卷积神经网络(CNN)是检测人脸的最佳选择。因为卷积神经网络(CNN)会自动查找特征。
第2步:检测狗
我们使用预训练的ResNet-50模型来检测图像中的狗。我们的第一行代码下载了ResNet-50模型,以及在ImageNet上训练的权重,ImageNet是一个非常大的,非常受欢迎的数据集,用于图像分类和其他视觉任务。ImageNet包含超过1000万个URL,每个URL链接到包含1000个类别之一的对象的图像。给定图像,该预训练的ResNet-50模型返回包含在图像中的对象的预测(从ImageNet中的可用类别导出)。
from keras.applications.resnet50 import ResNet50 # define ResNet50 model ResNet50_model = ResNet50(weights='imagenet')
当使用TensorFlow作为后端时,Keras CNN需要一个4D数组(我们也将其称为4D张量)作为输入,具有形状
(nb_samples,rows,columns,channels)
其中nb_samples对应于图像的总数,rows,columns和channels分别对应每个图像的行,列,和通道的数量。
下面的函数path_to_tensor将图像文件路径作为输入,并返回适合提供给Keras CNN的4D张量。该函数首先加载图像并将其大小调整为224×224像素的正方形图像。接下来,将图像转换为数组,然后将其调整为4D张量。在这种情况下,由于我们正在处理彩色图像,因此每个图像都有三个通道。同样,由于我们正在处理单个图像(或样本),因此返回的张量将始终具有形状
(nb_samples,224,224,3)
这里nb_samples是所提供的图像路径阵列中的样本数或图像数。最好将其nb_samples视为数据集中3D张量的数量!
from keras.preprocessing import image from tqdm import tqdm def path_to_tensor(img_path): """The path_to_tensor function below takes a string-valued file path to a color image as input and returns a 4D tensor suitable for supplying to a Keras CNN. Args: img_path: string. a file path to a color images. Retruns: numpy.array, a 4D tensor suitable for supplying to a Keras CNN. Output shape is (1,224,224,3). """ # loads RGB image as PIL.Image.Image type img = image.load_img(img_path, target_size=(224, 224)) # convert PIL.Image.Image type to 3D tensor with shape (224, 224, 3) x = image.img_to_array(img) # convert 3D tensor to 4D tensor with shape (1, 224, 224, 3) and return 4D tensor return np.expand_dims(x, axis=0) def paths_to_tensor(img_paths): """The paths_to_tensor function takes a numpy array of string-valued image paths as input and returns a 4D tensor with shape. Args: img_path: string. a file path to a color images. Retruns: numpy.array, a 4D tensor. Output shape is (n_samples,224,224,3). """ list_of_tensors = [path_to_tensor(img_path) for img_path in tqdm(img_paths)] return np.vstack(list_of_tensors)
要使4D张量为ResNet-50以及Keras中任何其他预先训练的模型做好准备,需要一些额外的处理。首先,通过重新排序通道将RGB图像转换为BGR。所有预训练的模型都具有额外的归一化步骤,即必须从每个图像中的每个像素中减去平均像素(以RGB表示为[103.939,116.779,123.68],从ImageNet中的所有图像中的所有像素计算出)。这是在导入函数preprocess_input中实现的。
现在我们有了一种格式化图像以供应给ResNet-50的方法,现在我们已经准备好使用该神教网络模型来提取预测。这是通过该predict方法实现的,该方法返回一个数组,其第i个条目是模型的预测概率,即图像属于第i个ImageNet类别。这是在下面的resnet50_predict_tags函数中实现的。
from keras.applications.resnet50 import preprocess_input, decode_predictions def ResNet50_predict_labels(img_path): """This is accomplished with the predict method, which returns an array whose i-th entry is the model's predicted probability that the image belongs to the i-th ImageNet category. Args: img_path: string. a file path to a color images. Returns: string, an ImageNet category. """ # returns prediction vector for image located at img_path img = preprocess_input(path_to_tensor(img_path)) return np.argmax(ResNet50_model.predict(img))
在查看字典时,您会注意到与狗对应的类别以不间断的顺序出现,并与字典键151-268对应,包括从“Chihuahua”到“Mexican hairless”的所有类别。因此,为了检查预训练的ResNet-50模型是否预测图像包含狗,我们只需要检查上面的resnet50_predict_label函数是否返回151到268之间的值(包括)。
我们使用这些想法来完成下面的dog_detector函数,如果在图像中检测到狗,则返回True(如果没有检测到,则返回False)。
### returns "True" if a dog is detected in the image stored at img_path def dog_detector(img_path): """The function returns True if a dog is detected in an image (and False if not). In order to check to see if an image is predicted to contain a dog by the pre-trained ResNet-50 model, we need only check if the ResNet50_predict_labels function above returns a value between 151 and 268 (inclusive). Args: img_path: string. a file path to a color images. Returns: boolean, images show a dog or not. """ prediction = ResNet50_predict_labels(img_path) return ((prediction <= 268) & (prediction >= 151))
测试dog_detector函数的性能
human_files_short = human_files[:100] dog_files_short = train_files[:100] print('human_files: ' + str(sum([1 for human_file in human_files_short if dog_detector(human_file) == True])) + '%') print('dog_files: ' + str(sum([1 for dog_file in dog_files_short if dog_detector(dog_file) == True])) + '%')
输出为human_files:0%和dog_files:100%
第3步:创建卷积神经网络(CNN)以对狗品种进行分类(from Scratch)
现在我们已经有了在图像中检测人和狗的函数,我们需要一种方法来预测图像的品种。在此步骤中,我们将创建一个对狗品种进行分类的卷积神经网络(CNN)。
from keras.layers import Conv2D, MaxPooling2D, GlobalAveragePooling2D from keras.layers import Dropout, Flatten, Dense from keras.models import Sequential model = Sequential() model.add(Conv2D(32, 3, padding='same', activation='relu', input_shape=(224, 224, 3))) model.add(Conv2D(32, 3, padding='same', activation='relu')) model.add(MaxPooling2D(pool_size=2)) model.add(Dropout(0.2)) model.add(Conv2D(64, 3, padding='same', activation='relu')) model.add(Conv2D(64, 3, padding='same', activation='relu')) model.add(MaxPooling2D(pool_size=2)) model.add(Dropout(0.2)) model.add(GlobalAveragePooling2D()) model.add(Dense(512, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(133, activation='softmax'))
架构如下
定义了Keras CNN的训练和测试
from keras.callbacks import ModelCheckpoint def train_model(model, train_tensors, train_targets, valid_tensors, valid_targets, save_filepath, epochs=20, batch_size=20): """Fit model to train dataset, and check accuracy for valid dataset. Args: model: complied keras model train_tensors: train datatset train_targets: train targets valid_tensors: valid dataset valid_targets: valid targts save_filepath: save filepath epochs: epochs, default 20 batch_size: batch size, default 20 """ checkpointer = ModelCheckpoint(filepath=save_filepath, verbose=1, save_best_only=True) model.fit( train_tensors, train_targets, validation_data=(valid_tensors, valid_targets), epochs=epochs, batch_size=20, callbacks=[checkpointer], verbose=2) def test_model(model, test_tensors, test_targets): """Test model for test datasets. Args: model: complied keras model test_tensors: test datasets test_targets: test targets """ # get index of predicted dog breed for each image in test set predictions = [np.argmax(model.predict(np.expand_dims(tensor, axis=0))) for tensor in test_tensors] # report test accuracy test_accuracy = 100*np.sum(np.array(predictions)==np.argmax(test_targets, axis=1))/len(predictions) print('Test accuracy: %.4f%%' % test_accuracy)
为了编译Keras模型,我们执行编译方法。
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
我们对模型进行了训练,测试精度为7.4163%。这种精度优于随机的,但不是很好。通过使用迁移学习,这种准确性得到了惊人的提高!
步骤4:创建卷积神经网络(CNN)以对狗品种进行分类(使用迁移学习)
首先,定义了提取ResNet-50的bottleneck 特征以用作我们的狗品种分类模型的输入的函数。该include_top参数是是否包含在该网络的顶部的全连接层,这里是False。因为在我们的应用程序中,网络顶部的全连接层是唯一用来对犬种进行分类的。
注意:没有全连接层的ResNet50会返回由ImageNet数据集预先训练的bottleneck 特征。
from keras.applications.resnet50 import ResNet50, preprocess_input def extract_Resnet50(tensor): """Extracting bottleneck features of ResNet-50 to use as input our classifying model. Args: tensor: numpy.array. a 4D tensor suitable for supplying to a Keras CNN. Retruns: numpy.array, bottleneck features of ResNet-50 """ return ResNet50(weights='imagenet', include_top=False).predict(preprocess_input(tensor))
然后,我们提取bottleneck 特征
train_Resnet50 = extract_Resnet50(paths_to_tensor(train_files).astype('float32')) valid_Resnet50 = extract_Resnet50(paths_to_tensor(valid_files).astype('float32')) test_Resnet50 = extract_Resnet50(paths_to_tensor(test_files).astype('float32'))
我们的狗品种分类模型被定义为网络顶部的全连接层
Resnet50_model = Sequential() Resnet50_model.add(GlobalAveragePooling2D(input_shape=train_Resnet50.shape[1:])) Resnet50_model.add(Dense(133, activation='softmax'))
架构如下
这就是我们使用迁移学习所做的一切!然后我们编译我们的狗品种分类模型,并用bottleneck 特征训练和测试模型。尽管在步骤3中使用相同参数进行了训练,但最终测试精度为82.1770%。
注意:ResNet-50迁移学习比以前的卷积神经网络(CNN)架构要好得多。ResNet-50是长时间训练的,而之前的CNN在短时间内接受训练。ResNet-50通过在ImageNet上使用包含大量类别的各种图像进行训练,而之前的CNN仅使用狗和人类图像进行训练。它只能提取一些图像模式。因此通过使用迁移学习具有最佳性能。
第5步:测试算法
我们将算法编写在一起。
def Resnet50_predict_breed(img_path): """Return the dog breed that is predicted by the transer learing model. Args: img_path: string. a file path to a color images. Returns: dog breed: string, and probability: float. """ bottleneck_feature = extract_Resnet50(path_to_tensor(img_path)) predicted_vector = Resnet50_model.predict(bottleneck_feature) idx = np.argmax(predicted_vector) return dog_names[idx], (predicted_vector.flatten())[idx] def img_show(img_path): """Show images by using matplotlib. Args: img_path: string. a file path to a color images. """ cv_rgb = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB) plt.imshow(cv_rgb) plt.show() def show_dog(img_path): """Show dogs with breed by using ResNet-50. Args: img_path: string. a file path to a color images. """ name, prob = Resnet50_predict_breed(img_path) print("hello, dog!") img_show(img_path) print("The dog looks like a ...") print(name + " (Probability: " + str(round(prob, 2)) + ")") def show_human(img_path): """Show humans with dog breed by using ResNet-50. Args: img_path: string. a file path to a color images. """ name, prob = Resnet50_predict_breed(img_path) print("hello, human!") img_show(img_path) print("You look like a ...") print(name + " (Probability: " + str(round(prob, 2)) + ")") def show_error(): """Show error. """ print("sorry, you look like neither dog or human...") def main(img_path): """Main function of our apllication. Args: img_path: string. a file path to a color images. """ if dog_detector(img_path): show_dog(img_path) elif face_detector(img_path): show_human(img_path) else: show_error()
测试一些图像,
在TensorFlow上使用Keras实现卷积神经网络(CNN)作为迁移学习非常容易。当您实施卷积神经网络(CNN)来对真实世界的图像进行分类时,迁移学习是一种有用的方法。