在这个notebook文件中,有些模板代码已经提供给你,但你还需要实现更多的功能来完成这个项目。除非有明确要求,你无须修改任何已给出的代码。以'(练习)'开始的标题表示接下来的代码部分中有你需要实现的功能。这些部分都配有详细的指导,需要实现的部分也会在注释中以'TODO'标出。请仔细阅读所有的提示。
除了实现代码外,你还需要回答一些与项目及代码相关的问题。每个需要回答的问题都会以 '问题 X' 标记。请仔细阅读每个问题,并且在问题后的 '回答' 部分写出完整的答案。我们将根据 你对问题的回答 和 撰写代码实现的功能 来对你提交的项目进行评分。
提示:Code 和 Markdown 区域可通过 Shift + Enter 快捷键运行。此外,Markdown可以通过双击进入编辑模式。
项目中显示为_选做_的部分可以帮助你的项目脱颖而出,而不是仅仅达到通过的最低要求。如果你决定追求更高的挑战,请在此 notebook 中完成_选做_部分的代码。
在这个notebook中,你将迈出第一步,来开发可以作为移动端或 Web应用程序一部分的算法。在这个项目的最后,你的程序将能够把用户提供的任何一个图像作为输入。如果可以从图像中检测到一只狗,它会输出对狗品种的预测。如果图像中是一个人脸,它会预测一个与其最相似的狗的种类。下面这张图展示了完成项目后可能的输出结果。(……实际上我们希望每个学生的输出结果不相同!)
在现实世界中,你需要拼凑一系列的模型来完成不同的任务;举个例子,用来预测狗种类的算法会与预测人类的算法不同。在做项目的过程中,你可能会遇到不少失败的预测,因为并不存在完美的算法和模型。你最终提交的不完美的解决方案也一定会给你带来一个有趣的学习经验!
我们将这个notebook分为不同的步骤,你可以使用下面的链接来浏览此notebook。
在该项目中包含了如下的问题:
在下方的代码单元(cell)中,我们导入了一个狗图像的数据集。我们使用 scikit-learn 库中的 load_files 函数来获取一些变量:
train_files, valid_files, test_files - 包含图像的文件路径的numpy数组train_targets, valid_targets, test_targets - 包含独热编码分类标签的numpy数组dog_names - 由字符串构成的与标签相对应的狗的种类
In [7]:
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))
In [8]:
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))
我们将使用 OpenCV 中的 Haar feature-based cascade classifiers 来检测图像中的人脸。OpenCV 提供了很多预训练的人脸检测模型,它们以XML文件保存在 github。我们已经下载了其中一个检测模型,并且把它存储在 haarcascades 的目录中。
在如下代码单元中,我们将演示如何使用这个检测模型在样本图像中找到人脸。
In [9]:
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()
在使用任何一个检测模型之前,将图像转换为灰度图是常用过程。detectMultiScale 函数使用储存在 face_cascade 中的的数据,对输入的灰度图像进行分类。
在上方的代码中,faces 以 numpy 数组的形式,保存了识别到的面部信息。它其中每一行表示一个被检测到的脸,该数据包括如下四个信息:前两个元素 x、y 代表识别框左上角的 x 和 y 坐标(参照上图,注意 y 坐标的方向和我们默认的方向不同);后两个元素代表识别框在 x 和 y 轴两个方向延伸的长度 w 和 d。
我们可以将这个程序封装为一个函数。该函数的输入为人脸图像的路径,当图像中包含人脸时,该函数返回 True,反之返回 False。该函数定义如下所示。
In [10]:
# 如果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
In [11]:
human_files_short = human_files[:100]
dog_files_short = train_files[:100]
## 请不要修改上方代码
## TODO: 基于human_files_short和dog_files_short
## 中的图像测试face_detector的表现
human_files_short_detect = 0
dog_files_short_detect = 0
for i in range(100):
if (face_detector(human_files_short[i])):
human_files_short_detect += 1
if (face_detector(dog_files_short[i])):
dog_files_short_detect += 1
print("The percentage of detecting human faces in human files is:", human_files_short_detect/human_files_short.size)
print("The percentage of detecting human faces in dog files is:", dog_files_short_detect/dog_files_short.size)
In [12]:
## (选做) TODO: 报告另一个面部检测算法在LFW数据集上的表现
### 你可以随意使用所需的代码单元数
在这个部分中,我们使用预训练的 ResNet-50 模型去检测图像中的狗。下方的第一行代码就是下载了 ResNet-50 模型的网络结构参数,以及基于 ImageNet 数据集的预训练权重。
ImageNet 这目前一个非常流行的数据集,常被用来测试图像分类等计算机视觉任务相关的算法。它包含超过一千万个 URL,每一个都链接到 1000 categories 中所对应的一个物体的图像。任给输入一个图像,该 ResNet-50 模型会返回一个对图像中物体的预测结果。
In [13]:
from keras.applications.resnet50 import ResNet50
# 定义ResNet50模型
ResNet50_model = ResNet50(weights='imagenet')
(nb_samples, rows, columns, channels)。其中 nb_samples 表示图像(或者样本)的总数,rows, columns, 和 channels 分别表示图像的行数、列数和通道数。path_to_tensor 函数实现如下将彩色图像的字符串型的文件路径作为输入,返回一个4维张量,作为 Keras CNN 输入。因为我们的输入图像是彩色图像,因此它们具有三个通道( channels 为 3)。(1, 224, 224, 3)。paths_to_tensor 函数将图像路径的字符串组成的 numpy 数组作为输入,并返回一个4维张量,各维度尺寸为 (nb_samples, 224, 224, 3)。 在这里,nb_samples是提供的图像路径的数据中的样本数量或图像数量。你也可以将 nb_samples 理解为数据集中3维张量的个数(每个3维张量表示一个不同的图像。
In [14]:
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 网络、或 Keras 中其他类似的预训练模型之前,还需要进行一些额外的处理:
[103.939, 116.779, 123.68](以 RGB 模式表示,根据所有的 ImageNet 图像算出)。导入的 preprocess_input 函数实现了这些功能。如果你对此很感兴趣,可以在 这里 查看 preprocess_input的代码。
在实现了图像处理的部分之后,我们就可以使用模型来进行预测。这一步通过 predict 方法来实现,它返回一个向量,向量的第 i 个元素表示该图像属于第 i 个 ImageNet 类别的概率。这通过如下的 ResNet50_predict_labels 函数实现。
通过对预测出的向量取用 argmax 函数(找到有最大概率值的下标序号),我们可以得到一个整数,即模型预测到的物体的类别。进而根据这个 清单,我们能够知道这具体是哪个品种的狗狗。
In [15]:
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 [16]:
def dog_detector(img_path):
prediction = ResNet50_predict_labels(img_path)
return ((prediction <= 268) & (prediction >= 151))
In [17]:
### TODO: 测试dog_detector函数在human_files_short和dog_files_short的表现
human_files_short_detect = 0
dog_files_short_detect = 0
for i in range(100):
if (dog_detector(human_files_short[i])):
human_files_short_detect += 1
if (dog_detector(dog_files_short[i])):
dog_files_short_detect += 1
print("The percentage of detecting dogs in human files is:", human_files_short_detect/human_files_short.size)
print("The percentage of detecting dogs in dog files is:", dog_files_short_detect/dog_files_short.size)
现在我们已经实现了一个函数,能够在图像中识别人类及狗狗。但我们需要更进一步的方法,来对狗的类别进行识别。在这一步中,你需要实现一个卷积神经网络来对狗的品种进行分类。你需要从头实现你的卷积神经网络(在这一阶段,你还不能使用迁移学习),并且你需要达到超过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 [18]:
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
In [19]:
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, input_shape=(224, 224, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Dropout(0.2))
model.add(Conv2D(filters=32, kernel_size=2, activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Dropout(0.2))
model.add(Conv2D(filters=64, kernel_size=2, activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Dropout(0.2))
model.add(GlobalAveragePooling2D())
model.add(Dense(133, activation='softmax'))
model.summary()
In [20]:
## 编译模型
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
在下方代码单元训练模型。使用模型检查点(model checkpointing)来储存具有最低验证集 loss 的模型。
可选题:你也可以对训练集进行 数据增强,来优化模型的表现。
In [21]:
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)
Out[21]:
In [22]:
## 加载具有最好验证loss的模型
model.load_weights('saved_models/weights.best.from_scratch.hdf5')
In [23]:
# 获取测试数据集中每一个图像所预测的狗品种的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)
In [24]:
bottleneck_features = np.load('/data/bottleneck_features/DogVGG16Data.npz')
train_VGG16 = bottleneck_features['train']
valid_VGG16 = bottleneck_features['valid']
test_VGG16 = bottleneck_features['test']
In [25]:
VGG16_model = Sequential()
VGG16_model.add(GlobalAveragePooling2D(input_shape=train_VGG16.shape[1:]))
VGG16_model.add(Dense(133, activation='softmax'))
VGG16_model.summary()
In [26]:
## 编译模型
VGG16_model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
In [27]:
## 训练模型
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)
Out[27]:
In [33]:
## 加载具有最好验证loss的模型
VGG16_model.load_weights('saved_models/weights.best.VGG16.hdf5')
In [34]:
# 获取测试数据集中每一个图像所预测的狗品种的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)
In [35]:
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)]
现在你将使用迁移学习来建立一个CNN,从而可以从图像中识别狗的品种。你的 CNN 在测试集上的准确率必须至少达到60%。
在步骤4中,我们使用了迁移学习来创建一个使用基于 VGG-16 提取的特征向量来搭建一个 CNN。在本部分内容中,你必须使用另一个预训练模型来搭建一个 CNN。为了让这个任务更易实现,我们已经预先对目前 keras 中可用的几种网络进行了预训练:
这些文件被命名为为:
Dog{network}Data.npz
其中 {network} 可以是 VGG19、Resnet50、InceptionV3 或 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 [36]:
### 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()
在下方的代码块中尝试使用 Keras 搭建最终的网络架构,并回答你实现最终 CNN 架构的步骤与每一步的作用,并描述你在迁移学习过程中,使用该网络架构的原因。
回答:
Xception_model = Sequential() 这一步是调用Xception的预训练模型
Xception_model.add(GlobalAveragePooling2D(input_shape=train_Resnet50.shape[1:])) 这一步添加一个全局平均池化层避免过拟合
Xception_model.add(Dropout(0.2)) 这一步是添加Dropout层避免过拟合
Xception_model.add(Dense(133, activation='softmax')) 这一步添加133个节点的全连接层,使用softmax激活函数输出每个狗狗品种的概率
使用该网络架构的原因是由于Xception具有如下优点: 1.相比传统的卷积神经网络如VGG复杂度降低,需要的参数数量下降。 2.可以做到更深,不会出现梯度消失的问题。 3.优化简单,分类准确度加深由于使用更深的网络。 4.Xception在众多图像识别领域中拔得头筹。 因此,选取Xception网络可以比之前的VGG网络取得更好的预测效果。
为什么这一架构会在这一分类任务中成功?
这四个架构都是经过反复多次实验确定的,非常有效果的架构。以Inception net为例,inception net是多层特征提取器,通过分别多次同时提取特征,然后叠加,就可以学到不同层次的特征,所以效果非常好。
为什么早期(第三步 )的尝试不成功?
第三步中,第一,使用的网络在架构上,非常浅,学到的特征非常少,其次学习库非常小,上面四个网络是在Imagenet上经过大量训练在不同种类的训练集上得来的,这是这个小库无法比拟的。
In [37]:
### TODO: 定义你的框架
# 调用Xception的预训练模型
Xception_model = Sequential()
#加一个全局平均池化层避免过拟合
Xception_model.add(GlobalAveragePooling2D(input_shape=train_Xception.shape[1:]))
#添加Dropout层避免过拟合
Xception_model.add(Dropout(0.2))
#添加133个节点的全连接层,使用softmax激活函数输出每个狗狗品种的概率
Xception_model.add(Dense(133, activation='softmax'))
Xception_model.summary()
In [38]:
### TODO: 编译模型
Xception_model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
在下方代码单元中训练你的模型。使用模型检查点(model checkpointing)来储存具有最低验证集 loss 的模型。
当然,你也可以对训练集进行 数据增强 以优化模型的表现,不过这不是必须的步骤。
In [39]:
### TODO: 训练模型
checkpointer = ModelCheckpoint(filepath='saved_models/weights.best.Xception1.hdf5',
verbose=1, save_best_only=True)
history = Xception_model.fit(train_Xception, train_targets,
validation_data=(valid_Xception, valid_targets),
epochs=20, batch_size=20, callbacks=[checkpointer], verbose=1)
In [40]:
### TODO: 加载具有最佳验证loss的模型权重
Xception_model.load_weights('saved_models/weights.best.Xception1.hdf5')
In [41]:
### 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)
实现一个函数,它的输入为图像路径,功能为预测对应图像的类别,输出为你模型预测出的狗类别(Affenpinscher, Afghan_hound 等)。
与步骤5中的模拟函数类似,你的函数应当包含如下三个步骤:
dog_names 数组来返回对应的狗种类名称。提取图像特征过程中使用到的函数可以在 extract_bottleneck_features.py 中找到。同时,他们应已在之前的代码块中被导入。根据你选定的 CNN 网络,你可以使用 extract_{network} 函数来获得对应的图像特征,其中 {network} 代表 VGG19, Resnet50, InceptionV3, 或 Xception 中的一个。
In [42]:
### TODO: 写一个函数,该函数将图像的路径作为输入
### 然后返回此模型所预测的狗的品种
def Xception_predict_breed(img_path):
# extract bottleneck features
bottleneck_feature = extract_Xception(path_to_tensor(img_path))
# obtain predicted vector
predicted_vector = Xception_model.predict(bottleneck_feature)
# return dog breed that is predicted by the model
return dog_names[np.argmax(predicted_vector)]
In [43]:
### TODO: 设计你的算法
### 自由地使用所需的代码单元数吧
from IPython.core.display import Image, display
def dog_breed_algorithm(img_path):
if dog_detector(img_path) == 1:
print("hello, dog!")
display(Image(img_path,width=200,height=200))
print("Your predicted breed is ... ")
return print(Xception_predict_breed(img_path))
elif face_detector(img_path) == 1:
print("hello, human!")
display(Image(img_path,width=200,height=200))
print("You look like a ... ")
return print(Xception_predict_breed(img_path))
else:
display(Image(img_path,width=200,height=200))
return print("Could not identify a human or dog in the chosen image. Please try again.")
在这个部分中,你将尝试一下你的新算法!算法认为你看起来像什么类型的狗?如果你有一只狗,它可以准确地预测你的狗的品种吗?如果你有一只猫,它会将你的猫误判为一只狗吗?
上传方式:点击左上角的Jupyter回到上级菜单,你可以看到Jupyter Notebook的右上方会有Upload按钮。
在下方编写代码,用至少6张现实中的图片来测试你的算法。你可以使用任意照片,不过请至少使用两张人类图片(要征得当事人同意哦)和两张狗的图片。 同时请回答如下问题:
1.结果比我预想的好。该算法可以准确识别出图片中是否含有狗或者人
In [44]:
## TODO: 在你的电脑上,在步骤6中,至少在6张图片上运行你的算法。
## 自由地使用所需的代码单元数吧
for i in range(1, 7):
filename = 'images/' + str(i) + '.jpg'
print('filename = ' + filename)
dog_breed_algorithm(filename)
print('\n')
注意: 当你写完了所有的代码,并且回答了所有的问题。你就可以把你的 iPython Notebook 导出成 HTML 文件。你可以在菜单栏,这样导出File -> Download as -> HTML (.html)把这个 HTML 和这个 iPython notebook 一起做为你的作业提交。