机器学习工程师 - Udacity 项目:实现一个狗品种识别算法App

在这个notebook中,你将迈出第一步,来开发可以作为移动端或 Web应用程序一部分的算法。在这个项目的最后,你的程序将能够把用户提供的任何一个图像作为输入。如果可以从图像中检测到一只狗,它会输出对狗品种的预测。如果图像中是一个人脸,它会预测一个与其最相似的狗的种类。下面这张图展示了完成项目后可能的输出结果。(……实际上我们希望每个学生的输出结果不相同!)

Sample Dog Output

在现实世界中,你需要拼凑一系列的模型来完成不同的任务;举个例子,用来预测狗种类的算法会与预测人类的算法不同。在做项目的过程中,你可能会遇到不少失败的预测,因为并不存在完美的算法和模型。你最终提交的不完美的解决方案也一定会给你带来一个有趣的学习经验!

项目内容

我们将这个notebook分为不同的步骤,你可以使用下面的链接来浏览此notebook。

  • Step 0: 导入数据集
  • Step 1: 检测人脸
  • Step 2: 检测狗狗
  • Step 3: 从头创建一个CNN来分类狗品种
  • Step 4: 使用一个CNN来区分狗的品种(使用迁移学习)
  • Step 5: 建立一个CNN来分类狗的品种(使用迁移学习)
  • Step 6: 完成你的算法
  • Step 7: 测试你的算法

在该项目中包含了如下的问题:


 

步骤 0: 导入数据集

导入狗数据集

在下方的代码单元(cell)中,我们导入了一个狗图像的数据集。我们使用 scikit-learn 库中的 load_files 函数来获取一些变量:

  • train_filesvalid_filestest_files - 包含图像的文件路径的numpy数组
  • train_targetsvalid_targetstest_targets - 包含独热编码分类标签的numpy数组
  • dog_names - 由字符串构成的与标签相对应的狗的种类
In [2]:
 
 
 
 
 
 
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('/data/dog_images/train')
valid_files, valid_targets = load_dataset('/data/dog_images/valid')
test_files, test_targets = load_dataset('/data/dog_images/test')
# load list of dog names
dog_names = [item[20:-1] for item in sorted(glob("/data/dog_images/train/*/"))]
# print statistics about the dataset
print('There are %d total dog categories.' % len(dog_names))
print('There are %s total dog images.\n' % len(np.hstack([train_files, valid_files, test_files])))
print('There are %d training dog images.' % len(train_files))
print('There are %d validation dog images.' % len(valid_files))
print('There are %d test dog images.'% len(test_files))
 
 
 
Using TensorFlow backend.
 
There are 133 total dog categories.
There are 8351 total dog images.

There are 6680 training dog images.
There are 835 validation dog images.
There are 836 test dog images.
 

导入人脸数据集

在下方的代码单元中,我们导入人脸图像数据集,文件所在路径存储在名为 human_files 的 numpy 数组。

In [3]:
 
 
 
 
 
import random
random.seed(8675309)
# 加载打乱后的人脸数据集的文件名
human_files = np.array(glob("/data/lfw/*/*"))
random.shuffle(human_files)
# 打印数据集的数据量
print('There are %d total human images.' % len(human_files))
 
 
 
There are 13233 total human images.
 

 

步骤1:检测人脸

我们将使用 OpenCV 中的 Haar feature-based cascade classifiers 来检测图像中的人脸。OpenCV 提供了很多预训练的人脸检测模型,它们以XML文件保存在 github。我们已经下载了其中一个检测模型,并且把它存储在 haarcascades 的目录中。

在如下代码单元中,我们将演示如何使用这个检测模型在样本图像中找到人脸。

In [4]:
 
 
 
 
 
import cv2                
import matplotlib.pyplot as plt 
%matplotlib inline                               
# 提取预训练的人脸检测模型
face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_alt.xml')
# 加载彩色(通道顺序为BGR)图像
img = cv2.imread(human_files[3])
# 将BGR图像进行灰度处理
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 在图像中找出脸
faces = face_cascade.detectMultiScale(gray)
# 打印图像中检测到的脸的个数
print('Number of faces detected:', len(faces))
# 获取每一个所检测到的脸的识别框
for (x,y,w,h) in faces:
    # 在人脸图像中绘制出识别框
    cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
 
           
# 将BGR图像转变为RGB图像以打印
cv_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 展示含有识别框的图像
plt.imshow(cv_rgb)
plt.show()
 
 
 
Number of faces detected: 1
 
 

在使用任何一个检测模型之前,将图像转换为灰度图是常用过程。detectMultiScale 函数使用储存在 face_cascade 中的的数据,对输入的灰度图像进行分类。

在上方的代码中,faces 以 numpy 数组的形式,保存了识别到的面部信息。它其中每一行表示一个被检测到的脸,该数据包括如下四个信息:前两个元素 xy 代表识别框左上角的 x 和 y 坐标(参照上图,注意 y 坐标的方向和我们默认的方向不同);后两个元素代表识别框在 x 和 y 轴两个方向延伸的长度 w 和 d

写一个人脸识别器

我们可以将这个程序封装为一个函数。该函数的输入为人脸图像的路径,当图像中包含人脸时,该函数返回 True,反之返回 False。该函数定义如下所示。

In [5]:
 
 
 
 
 
# 如果img_path路径表示的图像检测到了脸,返回"True" 
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
 
 
 

【练习】 评估人脸检测模型

 

 

问题 1:

在下方的代码块中,使用 face_detector 函数,计算:

  • human_files 的前100张图像中,能够检测到人脸的图像占比多少?
  • dog_files 的前100张图像中,能够检测到人脸的图像占比多少?

理想情况下,人图像中检测到人脸的概率应当为100%,而狗图像中检测到人脸的概率应该为0%。你会发现我们的算法并非完美,但结果仍然是可以接受的。我们从每个数据集中提取前100个图像的文件路径,并将它们存储在human_files_shortdog_files_short中。

In [6]:
 
 
 
 
 
human_files_short = human_files[:100]
dog_files_short = train_files[:100]
## 请不要修改上方代码
## TODO: 基于human_files_short和dog_files_short
## 中的图像测试face_detector的表现
print(np.mean([face_detector(human) for human in human_files_short]))
print(np.mean([face_detector(dog) for dog in dog_files_short]))
 
 
 
1.0
0.11
 

 

问题 2:

就算法而言,该算法成功与否的关键在于,用户能否提供含有清晰面部特征的人脸图像。 那么你认为,这样的要求在实际使用中对用户合理吗?如果你觉得不合理,你能否想到一个方法,即使图像中并没有清晰的面部特征,也能够检测到人脸?

回答: 不合理;使用CNN;

 

 

选做:

我们建议在你的算法中使用opencv的人脸检测模型去检测人类图像,不过你可以自由地探索其他的方法,尤其是尝试使用深度学习来解决它:)。请用下方的代码单元来设计和测试你的面部监测算法。如果你决定完成这个选做任务,你需要报告算法在每一个数据集上的表现。

In [7]:
 
 
 
 
 
## (选做) TODO: 报告另一个面部检测算法在LFW数据集上的表现
### 你可以随意使用所需的代码单元数
 
 
 

 

步骤 2: 检测狗狗

在这个部分中,我们使用预训练的 ResNet-50 模型去检测图像中的狗。下方的第一行代码就是下载了 ResNet-50 模型的网络结构参数,以及基于 ImageNet 数据集的预训练权重。

ImageNet 这目前一个非常流行的数据集,常被用来测试图像分类等计算机视觉任务相关的算法。它包含超过一千万个 URL,每一个都链接到 1000 categories 中所对应的一个物体的图像。任给输入一个图像,该 ResNet-50 模型会返回一个对图像中物体的预测结果。

In [8]:
 
 
 
 
 
from keras.applications.resnet50 import ResNet50
# 定义ResNet50模型
ResNet50_model = ResNet50(weights='imagenet')
 
 
 
Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.2/resnet50_weights_tf_dim_ordering_tf_kernels.h5
102858752/102853048 [==============================] - 16s 0us/step
 

数据预处理

  • 在使用 TensorFlow 作为后端的时候,在 Keras 中,CNN 的输入是一个4维数组(也被称作4维张量),它的各维度尺寸为 (nb_samples, rows, columns, channels)。其中 nb_samples 表示图像(或者样本)的总数,rowscolumns, 和 channels 分别表示图像的行数、列数和通道数。
  • 下方的 path_to_tensor 函数实现如下将彩色图像的字符串型的文件路径作为输入,返回一个4维张量,作为 Keras CNN 输入。因为我们的输入图像是彩色图像,因此它们具有三个通道( channels 为 3)。
    1. 该函数首先读取一张图像,然后将其缩放为 224×224 的图像。
    2. 随后,该图像被调整为具有4个维度的张量。
    3. 对于任一输入图像,最后返回的张量的维度是:(1, 224, 224, 3)
  • paths_to_tensor 函数将图像路径的字符串组成的 numpy 数组作为输入,并返回一个4维张量,各维度尺寸为 (nb_samples, 224, 224, 3)。 在这里,nb_samples是提供的图像路径的数据中的样本数量或图像数量。你也可以将 nb_samples 理解为数据集中3维张量的个数(每个3维张量表示一个不同的图像。
In [9]:
 
 
 
 
 
from keras.preprocessing import image 
from tqdm import tqdm
def path_to_tensor(img_path):
    # 用PIL加载RGB图像为PIL.Image.Image类型
    img = image.load_img(img_path, target_size=(224, 224))
    # 将PIL.Image.Image类型转化为格式为(224, 224, 3)的3维张量
    x = image.img_to_array(img)
    # 将3维张量转化为格式为(1, 224, 224, 3)的4维张量并返回
    return np.expand_dims(x, axis=0)
def paths_to_tensor(img_paths):
    list_of_tensors = [path_to_tensor(img_path) for img_path in tqdm(img_paths)]
    return np.vstack(list_of_tensors)
 
 
 

基于 ResNet-50 架构进行预测

对于通过上述步骤得到的四维张量,在把它们输入到 ResNet-50 网络、或 Keras 中其他类似的预训练模型之前,还需要进行一些额外的处理:

  1. 首先,这些图像的通道顺序为 RGB,我们需要重排他们的通道顺序为 BGR。
  2. 其次,预训练模型的输入都进行了额外的归一化过程。因此我们在这里也要对这些张量进行归一化,即对所有图像所有像素都减去像素均值 [103.939, 116.779, 123.68](以 RGB 模式表示,根据所有的 ImageNet 图像算出)。

导入的 preprocess_input 函数实现了这些功能。如果你对此很感兴趣,可以在 这里 查看 preprocess_input的代码。

在实现了图像处理的部分之后,我们就可以使用模型来进行预测。这一步通过 predict 方法来实现,它返回一个向量,向量的第 i 个元素表示该图像属于第 i 个 ImageNet 类别的概率。这通过如下的 ResNet50_predict_labels 函数实现。

通过对预测出的向量取用 argmax 函数(找到有最大概率值的下标序号),我们可以得到一个整数,即模型预测到的物体的类别。进而根据这个 清单,我们能够知道这具体是哪个品种的狗狗。

In [10]:
 
 
 
 
 
from keras.applications.resnet50 import preprocess_input, decode_predictions
def ResNet50_predict_labels(img_path):
    # 返回img_path路径的图像的预测向量
    img = preprocess_input(path_to_tensor(img_path))
    return np.argmax(ResNet50_model.predict(img))
 
 
 

完成狗检测模型

在研究该 清单 的时候,你会注意到,狗类别对应的序号为151-268。因此,在检查预训练模型判断图像是否包含狗的时候,我们只需要检查如上的 ResNet50_predict_labels 函数是否返回一个介于151和268之间(包含区间端点)的值。

我们通过这些想法来完成下方的 dog_detector 函数,如果从图像中检测到狗就返回 True,否则返回 False

In [11]:
 
 
 
 
 
def dog_detector(img_path):
    prediction = ResNet50_predict_labels(img_path)
    return ((prediction <= 268) & (prediction >= 151)) 
 
 
 

【作业】评估狗狗检测模型


 

问题 3:

在下方的代码块中,使用 dog_detector 函数,计算:

  • human_files_short中图像检测到狗狗的百分比?
  • dog_files_short中图像检测到狗狗的百分比?
In [12]:
 
 
 
 
 
### TODO: 测试dog_detector函数在human_files_short和dog_files_short的表现
print(np.mean([dog_detector(human) for human in human_files_short]))
print(np.mean([dog_detector(dog) for dog in dog_files_short]))
 
 
 
0.0
1.0
 

 

步骤 3: 从头开始创建一个CNN来分类狗品种

现在我们已经实现了一个函数,能够在图像中识别人类及狗狗。但我们需要更进一步的方法,来对狗的类别进行识别。在这一步中,你需要实现一个卷积神经网络来对狗的品种进行分类。你需要从头实现你的卷积神经网络(在这一阶段,你还不能使用迁移学习),并且你需要达到超过1%的测试集准确率。在本项目的步骤五种,你还有机会使用迁移学习来实现一个准确率大大提高的模型。

在添加卷积层的时候,注意不要加上太多的(可训练的)层。更多的参数意味着更长的训练时间,也就是说你更可能需要一个 GPU 来加速训练过程。万幸的是,Keras 提供了能够轻松预测每次迭代(epoch)花费时间所需的函数。你可以据此推断你算法所需的训练时间。

值得注意的是,对狗的图像进行分类是一项极具挑战性的任务。因为即便是一个正常人,也很难区分布列塔尼犬和威尔士史宾格犬。

布列塔尼犬(Brittany) 威尔士史宾格犬(Welsh Springer Spaniel)

不难发现其他的狗品种会有很小的类间差别(比如金毛寻回犬和美国水猎犬)。

金毛寻回犬(Curly-Coated Retriever) 美国水猎犬(American Water Spaniel)

同样,拉布拉多犬(labradors)有黄色、棕色和黑色这三种。那么你设计的基于视觉的算法将不得不克服这种较高的类间差别,以达到能够将这些不同颜色的同类狗分到同一个品种中。

黄色拉布拉多犬(Yellow Labrador) | 棕色拉布拉多犬(Chocolate Labrador) | 黑色拉布拉多犬(Black Labrador)

  • | -||

我们也提到了随机分类将得到一个非常低的结果:不考虑品种略有失衡的影响,随机猜测到正确品种的概率是1/133,相对应的准确率是低于1%的。

请记住,在深度学习领域,实践远远高于理论。大量尝试不同的框架吧,相信你的直觉!当然,玩得开心!

数据预处理

通过对每张图像的像素值除以255,我们对图像实现了归一化处理。

In [13]:
 
 
 
 
 
from PIL import ImageFile                            
ImageFile.LOAD_TRUNCATED_IMAGES = True                 
# Keras中的数据预处理过程
train_tensors = paths_to_tensor(train_files).astype('float32')/255
valid_tensors = paths_to_tensor(valid_files).astype('float32')/255
test_tensors = paths_to_tensor(test_files).astype('float32')/255
 
 
 
100%|██████████| 6680/6680 [01:14<00:00, 89.11it/s] 
100%|██████████| 835/835 [00:08<00:00, 100.40it/s]
100%|██████████| 836/836 [00:08<00:00, 101.04it/s]
 

【练习】模型架构

创建一个卷积神经网络来对狗品种进行分类。在你代码块的最后,执行 model.summary() 来输出你模型的总结信息。

我们已经帮你导入了一些所需的 Python 库,如有需要你可以自行导入。如果你在过程中遇到了困难,如下是给你的一点小提示——该模型能够在5个 epoch 内取得超过1%的测试准确率,并且能在CPU上很快地训练。

Sample CNN

 

 

问题 4:

在下方的代码块中尝试使用 Keras 搭建卷积网络的架构,并回答相关的问题。

  1. 你可以尝试自己搭建一个卷积网络的模型,那么你需要回答你搭建卷积网络的具体步骤(用了哪些层)以及为什么这样搭建。
  2. 你也可以根据上图提示的步骤搭建卷积网络,那么请说明为何如上的架构能够在该问题上取得很好的表现。

回答: 我使用上图提示的步骤搭建卷积网络,该架构能取得很好表现的原因有: 1)卷积层对图片中的特征进行局部感知,以便后续从更高层次对局部进行综合操作,从而得到全局信息; 2)池化层用于特征降维,压缩数据和参数的数量,减小过拟合,同时提高模型的容错性;MaxPooling能够保留最强的特征,抛弃其他弱的此类特征。GlobalAveragePooling将最后一层的特征进行均值池化,形成一个特征点,将这些特征点组成最后的特征向量以进行softmax计算; 3)密集层里是高度提纯的特征,它将进行最后的分类;sigmoid的梯度在饱和区域非常平缓,接近于0,很容易造成梯度消失的问题。而Relu的梯度大多数情况下是常数,有助于解决深层网络的收敛问题。

In [14]:
 
 
 
 
 
 
from keras.layers import Conv2D, MaxPooling2D, GlobalAveragePooling2D
from keras.layers import Dropout, Flatten, Dense
from keras.models import Sequential
model = Sequential()
### TODO: 定义你的网络架构
model.add(Conv2D(filters=16, kernel_size=2, padding='valid', activation='relu',input_shape=(224,224,3)))
model.add(MaxPooling2D(pool_size=2))
model.add(Conv2D(filters=32, kernel_size=2, padding='valid', activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Conv2D(filters=64, kernel_size=2, padding='valid', activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(GlobalAveragePooling2D(data_format='channels_last'))
model.add(Dense(133, activation='softmax'))
model.summary()
 
 
 
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 223, 223, 16)      208       
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 111, 111, 16)      0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 110, 110, 32)      2080      
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 55, 55, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 54, 54, 64)        8256      
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 27, 27, 64)        0         
_________________________________________________________________
global_average_pooling2d_1 ( (None, 64)                0         
_________________________________________________________________
dense_1 (Dense)              (None, 133)               8645      
=================================================================
Total params: 19,189
Trainable params: 19,189
Non-trainable params: 0
_________________________________________________________________
In [15]:
 
 
 
 
 
## 编译模型
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
 
 
 

 

【练习】训练模型


 

问题 5:

在下方代码单元训练模型。使用模型检查点(model checkpointing)来储存具有最低验证集 loss 的模型。

可选题:你也可以对训练集进行 数据增强,来优化模型的表现。

In [16]:
 
 
 
 
 
from keras.callbacks import ModelCheckpoint 
### TODO: 设置训练模型的epochs的数量
epochs = 5
### 不要修改下方代码
checkpointer = ModelCheckpoint(filepath='saved_models/weights.best.from_scratch.hdf5', 
                               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=1)
 
 
 
Train on 6680 samples, validate on 835 samples
Epoch 1/5
6660/6680 [============================>.] - ETA: 0s - loss: 4.8842 - acc: 0.0074Epoch 00001: val_loss improved from inf to 4.86834, saving model to saved_models/weights.best.from_scratch.hdf5
6680/6680 [==============================] - 21s 3ms/step - loss: 4.8843 - acc: 0.0073 - val_loss: 4.8683 - val_acc: 0.0108
Epoch 2/5
6660/6680 [============================>.] - ETA: 0s - loss: 4.8681 - acc: 0.0104Epoch 00002: val_loss improved from 4.86834 to 4.85930, saving model to saved_models/weights.best.from_scratch.hdf5
6680/6680 [==============================] - 21s 3ms/step - loss: 4.8681 - acc: 0.0103 - val_loss: 4.8593 - val_acc: 0.0084
Epoch 3/5
6660/6680 [============================>.] - ETA: 0s - loss: 4.8457 - acc: 0.0132Epoch 00003: val_loss improved from 4.85930 to 4.82723, saving model to saved_models/weights.best.from_scratch.hdf5
6680/6680 [==============================] - 21s 3ms/step - loss: 4.8458 - acc: 0.0135 - val_loss: 4.8272 - val_acc: 0.0228
Epoch 4/5
6660/6680 [============================>.] - ETA: 0s - loss: 4.7885 - acc: 0.0197Epoch 00004: val_loss improved from 4.82723 to 4.78026, saving model to saved_models/weights.best.from_scratch.hdf5
6680/6680 [==============================] - 21s 3ms/step - loss: 4.7880 - acc: 0.0196 - val_loss: 4.7803 - val_acc: 0.0204
Epoch 5/5
6660/6680 [============================>.] - ETA: 0s - loss: 4.7402 - acc: 0.0230Epoch 00005: val_loss improved from 4.78026 to 4.75329, saving model to saved_models/weights.best.from_scratch.hdf5
6680/6680 [==============================] - 21s 3ms/step - loss: 4.7400 - acc: 0.0231 - val_loss: 4.7533 - val_acc: 0.0275
Out[16]:
<keras.callbacks.History at 0x7f2554b5cd68>
In [17]:
 
 
 
 
 
## 加载具有最好验证loss的模型
model.load_weights('saved_models/weights.best.from_scratch.hdf5')
 
 
 

测试模型

在狗图像的测试数据集上试用你的模型。确保测试准确率大于1%。

In [18]:
 
 
 
 
 
 
# 获取测试数据集中每一个图像所预测的狗品种的index
dog_breed_predictions = [np.argmax(model.predict(np.expand_dims(tensor, axis=0))) for tensor in test_tensors]
# 报告测试准确率
test_accuracy = 100*np.sum(np.array(dog_breed_predictions)==np.argmax(test_targets, axis=1))/len(dog_breed_predictions)
print('Test accuracy: %.4f%%' % test_accuracy)
 
 
 
Test accuracy: 2.2727%
 

 

步骤 4: 使用一个CNN来区分狗的品种

使用 迁移学习(Transfer Learning)的方法,能帮助我们在不损失准确率的情况下大大减少训练时间。在以下步骤中,你可以尝试使用迁移学习来训练你自己的CNN。

 

得到从图像中提取的特征向量(Bottleneck Features)

In [19]:
 
 
 
 
 
bottleneck_features = np.load('/data/bottleneck_features/DogVGG16Data.npz')
train_VGG16 = bottleneck_features['train']
valid_VGG16 = bottleneck_features['valid']
test_VGG16 = bottleneck_features['test']
 
 
 

模型架构

该模型使用预训练的 VGG-16 模型作为固定的图像特征提取器,其中 VGG-16 最后一层卷积层的输出被直接输入到我们的模型。我们只需要添加一个全局平均池化层以及一个全连接层,其中全连接层使用 softmax 激活函数,对每一个狗的种类都包含一个节点。

In [20]:
 
 
 
 
 
VGG16_model = Sequential()
VGG16_model.add(GlobalAveragePooling2D(input_shape=train_VGG16.shape[1:]))
VGG16_model.add(Dense(133, activation='softmax'))
VGG16_model.summary()
 
 
 
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
global_average_pooling2d_2 ( (None, 512)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 133)               68229     
=================================================================
Total params: 68,229
Trainable params: 68,229
Non-trainable params: 0
_________________________________________________________________
In [21]:
 
 
 
 
 
 
## 编译模型
VGG16_model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
 
 
In [22]:
 
 
 
 
 
## 训练模型
checkpointer = ModelCheckpoint(filepath='saved_models/weights.best.VGG16.hdf5', 
                               verbose=1, save_best_only=True)
VGG16_model.fit(train_VGG16, train_targets, 
          validation_data=(valid_VGG16, valid_targets),
          epochs=20, batch_size=20, callbacks=[checkpointer], verbose=1)
 
 
 
Train on 6680 samples, validate on 835 samples
Epoch 1/20
6620/6680 [============================>.] - ETA: 0s - loss: 12.5324 - acc: 0.1166Epoch 00001: val_loss improved from inf to 11.01066, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 305us/step - loss: 12.5234 - acc: 0.1175 - val_loss: 11.0107 - val_acc: 0.2156
Epoch 2/20
6600/6680 [============================>.] - ETA: 0s - loss: 10.1875 - acc: 0.2744Epoch 00002: val_loss improved from 11.01066 to 10.11762, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 258us/step - loss: 10.1996 - acc: 0.2741 - val_loss: 10.1176 - val_acc: 0.2838
Epoch 3/20
6520/6680 [============================>.] - ETA: 0s - loss: 9.6710 - acc: 0.3396Epoch 00003: val_loss improved from 10.11762 to 9.94522, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 252us/step - loss: 9.6696 - acc: 0.3397 - val_loss: 9.9452 - val_acc: 0.3138
Epoch 4/20
6500/6680 [============================>.] - ETA: 0s - loss: 9.4556 - acc: 0.3694Epoch 00004: val_loss improved from 9.94522 to 9.83009, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 252us/step - loss: 9.4765 - acc: 0.3680 - val_loss: 9.8301 - val_acc: 0.3174
Epoch 5/20
6560/6680 [============================>.] - ETA: 0s - loss: 9.3029 - acc: 0.3910Epoch 00005: val_loss did not improve
6680/6680 [==============================] - 2s 250us/step - loss: 9.3141 - acc: 0.3900 - val_loss: 9.8684 - val_acc: 0.3257
Epoch 6/20
6640/6680 [============================>.] - ETA: 0s - loss: 9.2075 - acc: 0.4062Epoch 00006: val_loss improved from 9.83009 to 9.70799, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 253us/step - loss: 9.2107 - acc: 0.4058 - val_loss: 9.7080 - val_acc: 0.3377
Epoch 7/20
6540/6680 [============================>.] - ETA: 0s - loss: 9.1585 - acc: 0.4135Epoch 00007: val_loss did not improve
6680/6680 [==============================] - 2s 250us/step - loss: 9.1740 - acc: 0.4124 - val_loss: 9.7425 - val_acc: 0.3377
Epoch 8/20
6640/6680 [============================>.] - ETA: 0s - loss: 9.0282 - acc: 0.4191Epoch 00008: val_loss improved from 9.70799 to 9.48671, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 248us/step - loss: 9.0345 - acc: 0.4189 - val_loss: 9.4867 - val_acc: 0.3485
Epoch 9/20
6460/6680 [============================>.] - ETA: 0s - loss: 8.8790 - acc: 0.4350Epoch 00009: val_loss improved from 9.48671 to 9.41755, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 252us/step - loss: 8.8642 - acc: 0.4359 - val_loss: 9.4176 - val_acc: 0.3533
Epoch 10/20
6580/6680 [============================>.] - ETA: 0s - loss: 8.8107 - acc: 0.4397Epoch 00010: val_loss improved from 9.41755 to 9.35908, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 251us/step - loss: 8.8061 - acc: 0.4398 - val_loss: 9.3591 - val_acc: 0.3581
Epoch 11/20
6620/6680 [============================>.] - ETA: 0s - loss: 8.7330 - acc: 0.4470Epoch 00011: val_loss improved from 9.35908 to 9.32864, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 250us/step - loss: 8.7199 - acc: 0.4479 - val_loss: 9.3286 - val_acc: 0.3593
Epoch 12/20
6500/6680 [============================>.] - ETA: 0s - loss: 8.6650 - acc: 0.4529Epoch 00012: val_loss improved from 9.32864 to 9.32506, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 248us/step - loss: 8.6645 - acc: 0.4528 - val_loss: 9.3251 - val_acc: 0.3545
Epoch 13/20
6620/6680 [============================>.] - ETA: 0s - loss: 8.5946 - acc: 0.4597Epoch 00013: val_loss improved from 9.32506 to 9.28472, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 250us/step - loss: 8.5922 - acc: 0.4599 - val_loss: 9.2847 - val_acc: 0.3665
Epoch 14/20
6520/6680 [============================>.] - ETA: 0s - loss: 8.5362 - acc: 0.4633Epoch 00014: val_loss improved from 9.28472 to 9.17651, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 251us/step - loss: 8.5406 - acc: 0.4629 - val_loss: 9.1765 - val_acc: 0.3725
Epoch 15/20
6460/6680 [============================>.] - ETA: 0s - loss: 8.4475 - acc: 0.4684Epoch 00015: val_loss did not improve
6680/6680 [==============================] - 2s 246us/step - loss: 8.4575 - acc: 0.4677 - val_loss: 9.2232 - val_acc: 0.3725
Epoch 16/20
6640/6680 [============================>.] - ETA: 0s - loss: 8.4250 - acc: 0.4733Epoch 00016: val_loss improved from 9.17651 to 9.11673, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 249us/step - loss: 8.4373 - acc: 0.4726 - val_loss: 9.1167 - val_acc: 0.3796
Epoch 17/20
6640/6680 [============================>.] - ETA: 0s - loss: 8.4017 - acc: 0.4729Epoch 00017: val_loss improved from 9.11673 to 9.02865, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 251us/step - loss: 8.4070 - acc: 0.4726 - val_loss: 9.0287 - val_acc: 0.3844
Epoch 18/20
6620/6680 [============================>.] - ETA: 0s - loss: 8.2901 - acc: 0.4757Epoch 00018: val_loss improved from 9.02865 to 8.99691, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 250us/step - loss: 8.2868 - acc: 0.4756 - val_loss: 8.9969 - val_acc: 0.3832
Epoch 19/20
6560/6680 [============================>.] - ETA: 0s - loss: 8.2155 - acc: 0.4837Epoch 00019: val_loss improved from 8.99691 to 8.91711, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 247us/step - loss: 8.1890 - acc: 0.4853 - val_loss: 8.9171 - val_acc: 0.3880
Epoch 20/20
6580/6680 [============================>.] - ETA: 0s - loss: 8.1681 - acc: 0.4884Epoch 00020: val_loss improved from 8.91711 to 8.91488, saving model to saved_models/weights.best.VGG16.hdf5
6680/6680 [==============================] - 2s 247us/step - loss: 8.1724 - acc: 0.4882 - val_loss: 8.9149 - val_acc: 0.3916
Out[22]:
<keras.callbacks.History at 0x7f254ea69e80>
In [23]:
 
 
 
 
 
## 加载具有最好验证loss的模型
VGG16_model.load_weights('saved_models/weights.best.VGG16.hdf5')
 
 
 

测试模型

现在,我们可以测试此CNN在狗图像测试数据集中识别品种的效果如何。我们在下方打印出测试准确率。

In [24]:
 
 
 
 
 
 
# 获取测试数据集中每一个图像所预测的狗品种的index
VGG16_predictions = [np.argmax(VGG16_model.predict(np.expand_dims(feature, axis=0))) for feature in test_VGG16]
# 报告测试准确率
test_accuracy = 100*np.sum(np.array(VGG16_predictions)==np.argmax(test_targets, axis=1))/len(VGG16_predictions)
print('Test accuracy: %.4f%%' % test_accuracy)
 
 
 
Test accuracy: 41.5072%
 

使用模型预测狗的品种

In [25]:
 
 
 
 
 
from extract_bottleneck_features import *
def VGG16_predict_breed(img_path):
    # 提取bottleneck特征
    bottleneck_feature = extract_VGG16(path_to_tensor(img_path))
    # 获取预测向量
    predicted_vector = VGG16_model.predict(bottleneck_feature)
    # 返回此模型预测的狗的品种
    return dog_names[np.argmax(predicted_vector)]
 
 
 

 

步骤 5: 建立一个CNN来分类狗的品种(使用迁移学习)

现在你将使用迁移学习来建立一个CNN,从而可以从图像中识别狗的品种。你的 CNN 在测试集上的准确率必须至少达到60%。

在步骤4中,我们使用了迁移学习来创建一个使用基于 VGG-16 提取的特征向量来搭建一个 CNN。在本部分内容中,你必须使用另一个预训练模型来搭建一个 CNN。为了让这个任务更易实现,我们已经预先对目前 keras 中可用的几种网络进行了预训练:

这些文件被命名为为:

Dog{network}Data.npz

其中 {network} 可以是 VGG19Resnet50InceptionV3 或 Xception 中的一个。选择上方网络架构中的一个,他们已经保存在目录 /data/bottleneck_features/ 中。

【练习】获取模型的特征向量

在下方代码块中,通过运行下方代码提取训练、测试与验证集相对应的bottleneck特征。

bottleneck_features = np.load('/data/bottleneck_features/Dog{network}Data.npz')
train_{network} = bottleneck_features['train']
valid_{network} = bottleneck_features['valid']
test_{network} = bottleneck_features['test']
In [26]:
 
 
 
 
 
### TODO: 从另一个预训练的CNN获取bottleneck特征
bottleneck_features = np.load('/data/bottleneck_features/DogXceptionData.npz')
train_Xception = bottleneck_features['train']
valid_Xception = bottleneck_features['valid']
test_Xception = bottleneck_features['test']
 
 
 

【练习】模型架构

建立一个CNN来分类狗品种。在你的代码单元块的最后,通过运行如下代码输出网络的结构:

    <your model's name>.summary()

 

问题 6:

在下方的代码块中尝试使用 Keras 搭建最终的网络架构,并回答你实现最终 CNN 架构的步骤与每一步的作用,并描述你在迁移学习过程中,使用该网络架构的原因。

回答: 1)将Xception最后一层的输出作为全局平均池化层的输入; 2)增加一个全连接层,使用softmax激活函数,节点数设置为狗的种类数133。 选用Xception是因为该模型在狗狗分类中准确率较高; 这一架构会在这一分类任务中成功的原因: 1)利用迁移学习获取了优良的模型和参数; 2)训练次数提高到了20次; 早期(第三步)尝试不成功的原因: 1)模型相对来说没有迁移学习中的好; 2)训练次数只有5次,太少了。

In [27]:
 
 
 
 
 
### TODO: 定义你的框架
Xception_model = Sequential()
Xception_model.add(GlobalAveragePooling2D(input_shape=train_Xception.shape[1:]))
Xception_model.add(Dense(133, activation='softmax'))
Xception_model.summary()
 
 
 
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
global_average_pooling2d_3 ( (None, 2048)              0         
_________________________________________________________________
dense_3 (Dense)              (None, 133)               272517    
=================================================================
Total params: 272,517
Trainable params: 272,517
Non-trainable params: 0
_________________________________________________________________
In [28]:
 
 
 
 
 
 
### TODO: 编译模型
Xception_model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
 
 
 

【练习】训练模型

 

问题 7:

在下方代码单元中训练你的模型。使用模型检查点(model checkpointing)来储存具有最低验证集 loss 的模型。

当然,你也可以对训练集进行 数据增强 以优化模型的表现,不过这不是必须的步骤。

In [29]:
 
 
 
 
 
 
### TODO: 训练模型
checkpointer = ModelCheckpoint(filepath='saved_models/weights.best.Xception.hdf5', verbose=1, save_best_only=True)
Xception_model.fit(train_Xception, train_targets, validation_data=(valid_Xception, valid_targets), epochs=20, batch_size=20, callbacks=[checkpointer], verbose=1)
 
 
 
Train on 6680 samples, validate on 835 samples
Epoch 1/20
6620/6680 [============================>.] - ETA: 0s - loss: 1.0631 - acc: 0.7332Epoch 00001: val_loss improved from inf to 0.50966, saving model to saved_models/weights.best.Xception.hdf5
6680/6680 [==============================] - 3s 481us/step - loss: 1.0575 - acc: 0.7340 - val_loss: 0.5097 - val_acc: 0.8383
Epoch 2/20
6620/6680 [============================>.] - ETA: 0s - loss: 0.3952 - acc: 0.8725Epoch 00002: val_loss improved from 0.50966 to 0.47452, saving model to saved_models/weights.best.Xception.hdf5
6680/6680 [==============================] - 3s 428us/step - loss: 0.3936 - acc: 0.8723 - val_loss: 0.4745 - val_acc: 0.8539
Epoch 3/20
6620/6680 [============================>.] - ETA: 0s - loss: 0.3211 - acc: 0.8968Epoch 00003: val_loss did not improve
6680/6680 [==============================] - 3s 411us/step - loss: 0.3207 - acc: 0.8972 - val_loss: 0.4911 - val_acc: 0.8539
Epoch 4/20
6620/6680 [============================>.] - ETA: 0s - loss: 0.2746 - acc: 0.9153Epoch 00004: val_loss did not improve
6680/6680 [==============================] - 3s 410us/step - loss: 0.2760 - acc: 0.9144 - val_loss: 0.5240 - val_acc: 0.8395
Epoch 5/20
6640/6680 [============================>.] - ETA: 0s - loss: 0.2464 - acc: 0.9241Epoch 00005: val_loss did not improve
6680/6680 [==============================] - 3s 413us/step - loss: 0.2460 - acc: 0.9241 - val_loss: 0.5318 - val_acc: 0.8491
Epoch 6/20
6620/6680 [============================>.] - ETA: 0s - loss: 0.2167 - acc: 0.9326Epoch 00006: val_loss did not improve
6680/6680 [==============================] - 3s 402us/step - loss: 0.2184 - acc: 0.9322 - val_loss: 0.5178 - val_acc: 0.8539
Epoch 7/20
6640/6680 [============================>.] - ETA: 0s - loss: 0.1972 - acc: 0.9364Epoch 00007: val_loss did not improve
6680/6680 [==============================] - 3s 415us/step - loss: 0.1974 - acc: 0.9364 - val_loss: 0.5171 - val_acc: 0.8611
Epoch 8/20
6540/6680 [============================>.] - ETA: 0s - loss: 0.1793 - acc: 0.9453Epoch 00008: val_loss did not improve
6680/6680 [==============================] - 3s 414us/step - loss: 0.1790 - acc: 0.9454 - val_loss: 0.5040 - val_acc: 0.8611
Epoch 9/20
6620/6680 [============================>.] - ETA: 0s - loss: 0.1611 - acc: 0.9508Epoch 00009: val_loss did not improve
6680/6680 [==============================] - 3s 413us/step - loss: 0.1612 - acc: 0.9509 - val_loss: 0.5656 - val_acc: 0.8527
Epoch 10/20
6540/6680 [============================>.] - ETA: 0s - loss: 0.1459 - acc: 0.9552Epoch 00010: val_loss did not improve
6680/6680 [==============================] - 3s 411us/step - loss: 0.1481 - acc: 0.9546 - val_loss: 0.5843 - val_acc: 0.8611
Epoch 11/20
6620/6680 [============================>.] - ETA: 0s - loss: 0.1386 - acc: 0.9574Epoch 00011: val_loss did not improve
6680/6680 [==============================] - 3s 415us/step - loss: 0.1382 - acc: 0.9573 - val_loss: 0.5724 - val_acc: 0.8575
Epoch 12/20
6560/6680 [============================>.] - ETA: 0s - loss: 0.1265 - acc: 0.9620Epoch 00012: val_loss did not improve
6680/6680 [==============================] - 3s 418us/step - loss: 0.1267 - acc: 0.9620 - val_loss: 0.5951 - val_acc: 0.8539
Epoch 13/20
6540/6680 [============================>.] - ETA: 0s - loss: 0.1147 - acc: 0.9661Epoch 00013: val_loss did not improve
6680/6680 [==============================] - 3s 419us/step - loss: 0.1154 - acc: 0.9654 - val_loss: 0.5964 - val_acc: 0.8551
Epoch 14/20
6660/6680 [============================>.] - ETA: 0s - loss: 0.1096 - acc: 0.9679Epoch 00014: val_loss did not improve
6680/6680 [==============================] - 3s 409us/step - loss: 0.1093 - acc: 0.9680 - val_loss: 0.5988 - val_acc: 0.8587
Epoch 15/20
6600/6680 [============================>.] - ETA: 0s - loss: 0.0992 - acc: 0.9721Epoch 00015: val_loss did not improve
6680/6680 [==============================] - 3s 417us/step - loss: 0.1005 - acc: 0.9720 - val_loss: 0.6315 - val_acc: 0.8539
Epoch 16/20
6660/6680 [============================>.] - ETA: 0s - loss: 0.0906 - acc: 0.9736Epoch 00016: val_loss did not improve
6680/6680 [==============================] - 3s 428us/step - loss: 0.0919 - acc: 0.9734 - val_loss: 0.6578 - val_acc: 0.8587
Epoch 17/20
6640/6680 [============================>.] - ETA: 0s - loss: 0.0864 - acc: 0.9755Epoch 00017: val_loss did not improve
6680/6680 [==============================] - 3s 417us/step - loss: 0.0865 - acc: 0.9754 - val_loss: 0.6622 - val_acc: 0.8647
Epoch 18/20
6580/6680 [============================>.] - ETA: 0s - loss: 0.0815 - acc: 0.9775Epoch 00018: val_loss did not improve
6680/6680 [==============================] - 3s 420us/step - loss: 0.0820 - acc: 0.9774 - val_loss: 0.6719 - val_acc: 0.8611
Epoch 19/20
6600/6680 [============================>.] - ETA: 0s - loss: 0.0790 - acc: 0.9759Epoch 00019: val_loss did not improve
6680/6680 [==============================] - 3s 422us/step - loss: 0.0783 - acc: 0.9762 - val_loss: 0.7340 - val_acc: 0.8491
Epoch 20/20
6580/6680 [============================>.] - ETA: 0s - loss: 0.0754 - acc: 0.9781Epoch 00020: val_loss did not improve
6680/6680 [==============================] - 3s 416us/step - loss: 0.0752 - acc: 0.9780 - val_loss: 0.7066 - val_acc: 0.8575
Out[29]:
<keras.callbacks.History at 0x7f254e793b38>
In [30]:
 
 
 
 
 
### TODO: 加载具有最佳验证loss的模型权重
Xception_model.load_weights('saved_models/weights.best.Xception.hdf5')
 
 
 

【练习】测试模型

 

问题 8:

在狗图像的测试数据集上试用你的模型。确保测试准确率大于60%。

In [31]:
 
 
 
 
 
 
### TODO: 在测试集上计算分类准确率
Xception_predictions = [np.argmax(Xception_model.predict(np.expand_dims(feature, axis=0))) for feature in test_Xception]
test_accuracy = 100*np.sum(np.array(Xception_predictions)==np.argmax(test_targets, axis=1))/len(Xception_predictions)
print('Test accuracy: %.4f%%' % test_accuracy)
 
 
 
Test accuracy: 83.9713%
 

【练习】使用模型测试狗的品种

实现一个函数,它的输入为图像路径,功能为预测对应图像的类别,输出为你模型预测出的狗类别(AffenpinscherAfghan_hound 等)。

与步骤5中的模拟函数类似,你的函数应当包含如下三个步骤:

  1. 根据选定的模型载入图像特征(bottleneck features)
  2. 将图像特征输输入到你的模型中,并返回预测向量。注意,在该向量上使用 argmax 函数可以返回狗种类的序号。
  3. 使用在步骤0中定义的 dog_names 数组来返回对应的狗种类名称。

提取图像特征过程中使用到的函数可以在 extract_bottleneck_features.py 中找到。同时,他们应已在之前的代码块中被导入。根据你选定的 CNN 网络,你可以使用 extract_{network} 函数来获得对应的图像特征,其中 {network} 代表 VGG19Resnet50InceptionV3, 或 Xception 中的一个。


 

问题 9:

In [32]:
 
 
 
 
 
### TODO: 写一个函数,该函数将图像的路径作为输入
### 然后返回此模型所预测的狗的品种
def Xception_predict_breed(img_path):
    bottleneck_feature = extract_Xception(path_to_tensor(img_path))
    predicted_vector = Xception_model.predict(bottleneck_feature)
    return dog_names[np.argmax(predicted_vector)]
 
 
 

 

步骤 6: 完成你的算法

实现一个算法,它的输入为图像的路径,它能够区分图像是否包含一个人、狗或两者都不包含,然后:

  • 如果从图像中检测到一只,返回被预测的品种。
  • 如果从图像中检测到,返回最相像的狗品种。
  • 如果两者都不能在图像中检测到,输出错误提示。

我们非常欢迎你来自己编写检测图像中人类与狗的函数,你可以随意地使用上方完成的 face_detector 和 dog_detector 函数。你需要在步骤5使用你的CNN来预测狗品种。

下面提供了算法的示例输出,但你可以自由地设计自己的模型!

Sample Human Output

 

问题 10:

在下方代码块中完成你的代码。

 
 
 
 
 
### TODO: 设计你的算法
### 自由地使用所需的代码单元数吧
def dog_check(img_path):
    if dog_detector(img_path):
        print('It''s a dog. It looks like ')
        print(Xception_predict_breed(img_path))
    elif face_detector(img_path):
        print('It''s human. It looks like ')
        print(Xception_predict_breed(img_path))
    else:
        print('Error!')
 
          
 
 
 

 

步骤 7: 测试你的算法

在这个部分中,你将尝试一下你的新算法!算法认为看起来像什么类型的狗?如果你有一只狗,它可以准确地预测你的狗的品种吗?如果你有一只猫,它会将你的猫误判为一只狗吗?

 

问题 11:

在下方编写代码,用至少6张现实中的图片来测试你的算法。你可以使用任意照片,不过请至少使用两张人类图片(要征得当事人同意哦)和两张狗的图片。 同时请回答如下问题:

  1. 输出结果比你预想的要好。
  2. 三点改进模型的想法: 1)增加epochs; 2)增加卷积层和池化层; 3)增加过滤器数量。
In [34]:
 
 
 
 
 
## TODO: 在你的电脑上,在步骤6中,至少在6张图片上运行你的算法。
## 自由地使用所需的代码单元数吧
dog_check('01.jpg')
dog_check('02.jpg')
dog_check('03.jpg')
dog_check('04.jpg')
dog_check('05.jpg')
dog_check('01.jpg')
 
 
 
Its a dog. It looks like 
Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.4/xception_weights_tf_dim_ordering_tf_kernels_notop.h5
83689472/83683744 [==============================] - 13s 0us/step
in/005.Alaskan_malamute
Its a dog. It looks like 
in/005.Alaskan_malamute
Its human. It looks like 
in/082.Havanese
Its human. It looks like 
in/082.Havanese
Error!
Its a dog. It looks like 
in/005.Alaskan_malamute

猜你喜欢

转载自www.cnblogs.com/paulonetwo/p/10078961.html