1.4 实现鱼类识别/检测模型
为了介绍机器学习(特别是深度学习)的能力,本节将实现鱼类识别的例子。这并不需要读者了解代码的内部细节。本节重点讲述典型机器学习的流程。
该任务的知识库是一系列图像,其中每张图像都贴上了月鱼或者金枪鱼的标签。在这次实现中,读者将使用其中一种深度学习架构,该架构在图像和计算机视觉领域取得了突破。这种架构叫作卷积神经网络(CNN)。卷积神经网络是一类深度学习架构,它通过图像处理的卷积运算来从图像当中提取特征,以解释需要分类的对象。现在,我们可以把它当成一个魔术盒,它将读取图像,从中学习如何区分这两类对象(月鱼和金枪鱼),然后向这个盒子输入未贴标签的图像以测试它的学习过程,并观察它是否能够分辨图像中是哪一类鱼。
不同类型的学习将在后面的章节中讨论,因此我们将在后面了解为什么上述鱼类识别任务属于监督学习类别。
这个例子中将会使用Keras。现在我们可以把Keras当成一个使得搭建和使用深度学习更加简单的API。从Keras网站上可以看到:
Keras是一个高级神经网络API,用Python编写,能够在TensorFlow、CNTK或Theano之上运行。它旨在实现快速实验,能够以最短的时间把想法转换为结果是做好研究的关键。
1.4.1 知识库/数据集
正如前文所提到的,开发需要以历史数据库作为基础,这将用来使学习算法了解其后期应该完成的任务。但是我们还需要另一个数据集来测试学习算法在学习过程后执行任务的能力。总而言之,在学习过程中需要两类数据集。
第一类数据集是拥有输入数据及其对应标签的知识库,例如鱼的图像和它们的对应标签(月鱼和金枪鱼)。把这类数据输入学习算法中,学习算法从中学习并发现之后将用于分类未标记图像的模式/趋势。
第二类数据集主要用于测试模型将从知识库中学到的知识应用于未标记的图像或未查看的数据上的能力,并检查模型是否运行良好。
正如我们所看到的,开发人员只拥有用来当作学习算法知识库的数据。我们所拥有的所有数据都带有相应的正确的输出。现在需要以某种方法来准备这种不带正确输出的数据(模型将应用于这些数据上)。
在数据科学中,将进行以下操作。
- 训练阶段:在这个阶段,知识库将提供数据,并把输入数据和正确输出一起提供给模型来训练学习算法/模型。
- 验证/测试阶段:在这个阶段,我们将度量训练好的模型的效果,还将使用不同的模型属性技术,并通过使用回归的R2分数、分类器的分类错误、IR模型的召回率和精确率等来度量训练好的模型的性能。
验证/测试阶段通常分为如下两步。
(1)使用不同的学习算法/模型,并基于验证数据选择性能最好的学习算法/模型,此步骤为验证步骤。
(2)度量和报告所选模型在测试集上的准确率,此步骤是测试步骤。
现在我们将学习如何获取这些模型将会用到的数据,并了解它能训练得多好。
既然没有不带正确输出的训练样本,我们就可以从将要使用到的原始训练样本中生成一个。我们可以将数据样本分成3个不同的集合(如图1.10所示)。
- 训练集:这个数据集将作为模型的知识库,通常选取原始数据样本中的70%。
- 验证集:这个数据集将用来从一系列模型中选择性能最好的模型,通常选取原始数据样本中的10%。
- 测试集:这个数据集将用来度量和报告所选模型的准确率,通常它和验证集一样大。
图1.10 将数据分为训练集、验证集和测试集
这个例子只使用了一种学习算法,因此可以取消验证集,并重新把数据只分为训练集和测试集。通常,数据科学家采用75/25的百分比,或者70/30。
1.4.2 数据分析预处理
本节将对输入图像进行分析和预处理,将它变成这里的卷积神经网络学习算法所接受的格式。
接下来,从导入这个应用所需要的包开始。
import numpy as np
np.random.seed(2018)
import os
import glob
import cv2
import datetime
import pandas as pd
import time
import warnings
warings.filterwarnings("ignore")
from sklearn.cross_validation import KFold
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Flatten
from keras.layers.convolutional import Convolution2D, MaxPooling2D,
ZeroPadding2D
from keras.optimizers import SGD
from keras.callbacks import EarlyStopping
from keras.utils import np_utils
from sklearn.metrics import log_loss
from keras import __version__ as keras_version
为了使用数据集所提供的图片,我们需要通过以下代码把它们调整为同样大小。从OpenCV网站上可以得知,OpenCV是完成这项工作的一个好选择。
开源计算机视觉库(Open Source Computer Vision Library,OpenCV)是在BSD许可证下发行的,它在学术和商业应用当中都是免费的。它有C++、C、Python和Java接口,并且支持Windows、Linux、MacOS、iOS和Android系统。OpenCV是为高效计算而设计的,并着重于实时应用。OpenCV用优化的C/C++写成,这使得它可以利用多核处理器资源。启用OpenCL后,它可以利用底层异构计算平台的硬件加速。
可以通过使用Python软件包管理器和命令pip install opencv-python来安装OpenCV。
# Parameters
# ---------------
# img_path : path
# path of the image to be resized
def resize_image(img_path):
#reading image file
img = cv2.imread(img_path)
#Resize the image to be 32 by 32
img_resized = cv2.resize(img, (32,32), cv2.INTER_LINEAR)
return img_resized
现在需要加载所有数据集中的训练样本,并根据前面的函数改变每幅图像的大小。下面将实现一个从保存不同鱼类的不同文件夹中加载训练样本的函数。
# Loading the training samples and their corresponding labels
def load_training_samples():
#Variables to hold the training input and output variables
train_input_variables = []
train_input_variables_id = []
train_label = []
# Scanning all images in each folder of a fish type
print('Start Reading Train Images')
folders = ['ALB', 'BET', 'DOL', 'LAG', 'NoF', 'OTHER', 'SHARK', 'YFT']
for fld in folders:
folder_index = folders.index(fld)
print('Load folder {} (Index: {})'.format(fld, folder_index))
imgs_path = os.path.join('..', 'input', 'train', fld, '*.jpg')
files = glob.glob(imgs_path)
for file in files:
file_base = os.path.basename(file)
# Resize the image
resized_img = rezize_image(file)
# Appending the processed image to the input/output variables of the classifier
train_input_variables.append(resized_img)
train_input_variables_id.append(file_base)
train_label.append(folder_index)
return train_input_variables, train_input_variables_id, train_label
正如前面所讨论的,有一个测试集将作为未见过的数据来测试模型的泛化能力,所以我们必须对测试图片进行相同的操作:加载它们并改变它们的大小。
def load_testing_samples():
# Scanning images from the test folder
imgs_path = os.path.join('..', 'input', 'test_stg1', '*.jpg')
files = sorted(glob.glob(imgs_path))
# Variables to hold the testing samples
testing_samples = []
testing_samples_id = []
#Processing the images and appending them to the array that we have
for file in files:
file_base = os.path.basename(file)
# Image resizing
resized_img = rezize_image(file)
testing_samples.append(resized_img)
testing_samples_id.append(file_base)
return testing_samples, testing_samples_id
现在需要在另一个函数里调用前一个函数,这个函数将会用load_training_samples()函数来加载和改变训练样本的大小。同时,添加几行代码来将训练数据转换为Numpy格式,调整数据形状以适应分类器模型,最后把数据转换为浮点数类型。
def load_normalize_training_samples():
# Calling the load function in order to load and resize the training samples
training_samples, training_label, training_samples_id =load_training_samples()
# Converting the loaded and resized data into Numpy format
training_samples = np.array(training_samples, dtype=np.uint8)
training_label = np.array(training_label, dtype=np.uint8)
# Reshaping the training samples
training_samples = training_samples.transpose((0, 3, 1, 2))
# Converting the training samples and training labels into float format
training_samples = training_samples.astype('float32')
training_samples = training_samples / 255
training_label = np_utils.to_categorical(training_label, 8)
return training_samples, training_label, training_samples_id
对测试集也需要进行相同的操作。
def load_normalize_testing_samples():
# Calling the load function in order to load and resize the testing samples
testing_samples, testing_samples_id = load_testing_samples()
# Converting the loaded and resized data into Numpy format
testing_samples = np.array(testing_samples, dtype=np.uint8)
# Reshaping the testing samples
testing_samples = testing_samples.transpose((0, 3, 1, 2))
# Converting the testing samples into float format
testing_samples = testing_samples.astype('float32')
testing_samples = testing_samples / 255
return testing_samples, testing_samples_id
1.4.3 搭建模型
下面开始创建模型了。正如前文提到的,我们将一种名为CNN的深度学习架构(如图1.11所示)作为鱼类识别任务的学习算法。同样,因为本节只演示在Keras和TensorFlow深度学习平台的帮助下如何仅使用几行代码来解决复杂的数据科学任务,所以读者不需要了解本章中以前出现过的或即将出现的代码。
图1.11 CNN架构
CNN和其他深度学习架构的更多详细知识将在后面的章节中介绍。
现在继续构造一个函数以创建鱼类识别任务中使用的CNN架构。
def create_cnn_model_arch():
pool_size = 2 # we will use 2x2 pooling throughout
conv_depth_1 = 32 # we will initially have 32 kernels per conv.layer...
conv_depth_2 = 64 # ...switching to 64 after the first pooling layer
kernel_size = 3 # we will use 3x3 kernels throughout
drop_prob = 0.5 # dropout in the FC layer with probability 0.5
hidden_size = 32 # the FC layer will have 512 neurons
num_classes = 8 # there are 8 fish types
# Conv [32] ->Conv [32] -> Pool
cnn_model = Sequential()
cnn_model.add(ZeroPadding2D((1, 1), input_shape=(3, 32, 32),
dim_ordering='th'))
cnn_model.add(Convolution2D(conv_depth_1, kernel_size, kernel_size,
activation='relu',
dim_ordering='th'))
cnn_model.add(ZeroPadding2D((1, 1), dim_ordering='th'))
cnn_model.add(Convolution2D(conv_depth_1, kernel_size, kernel_size,
activation='relu',
dim_ordering='th'))
cnn_model.add(MaxPooling2D(pool_size=(pool_size, pool_size),
strides=(2, 2),
dim_ordering='th'))
# Conv [64] ->Conv [64] -> Pool
cnn_model.add(ZeroPadding2D((1, 1), dim_ordering='th'))
cnn_model.add(Convolution2D(conv_depth_2, kernel_size, kernel_size,
activation='relu',
dim_ordering='th'))
cnn_model.add(ZeroPadding2D((1, 1), dim_ordering='th'))
cnn_model.add(Convolution2D(conv_depth_2, kernel_size, kernel_size,
activation='relu',
dim_ordering='th'))
cnn_model.add(MaxPooling2D(pool_size=(pool_size, pool_size),
strides=(2, 2),
dim_ordering='th'))
# Now flatten to 1D, apply FC then ReLU (with dropout) and finally softmax(output layer)
cnn_model.add(Flatten())
cnn_model.add(Dense(hidden_size, activation='relu'))
cnn_model.add(Dropout(drop_prob))
cnn_model.add(Dense(hidden_size, activation='relu'))
cnn_model.add(Dropout(drop_prob))
cnn_model.add(Dense(num_classes, activation='softmax'))
# initiating the stochastic gradient descent optimiser
stochastic_gradient_descent = SGD(lr=1e-2, decay=1e-6, momentum=0.9,
nesterov=True) cnn_model.compile(optimizer=stochastic_gradient_descent,
# using the stochastic gradient descent optimiser
loss='categorical_crossentropy') # using the cross-entropy loss function
return cnn_model
在开始训练模型之前,我们需要使用模型评估和验证方法来帮助评估模型,并检测它的泛化能力。因此,接下来需要用到k折交叉验证法。读者不需要理解这种方法或它的工作原理,因为本书将在稍后详细解释这种方法。
下面开始并构造一个帮助评估和验证模型的函数。
def create_model_with_kfold_cross_validation(nfolds=10):
batch_size = 16 # in each iteration, we consider 32 training examples at once
num_epochs = 30 # we iterate 200 times over the entire training set
random_state = 51 # control the randomness for reproducibility of the results on the same platform
# Loading and normalizing the training samples prior to feeding it to the created CNN model
training_samples, training_samples_target, training_samples_id =
load_normalize_training_samples()
yfull_train = dict()
# Providing Training/Testing indices to split data in the training samples
# which is splitting data into 10 consecutive folds with shuffling
kf = KFold(len(train_id), n_folds=nfolds, shuffle=True,
random_state=random_state)
fold_number = 0 # Initial value for fold number
sum_score = 0 # overall score (will be incremented at each iteration)
trained_models = [] # storing the modeling of each iteration over the folds
# Getting the training/testing samples based on the generated training/testing indices by
Kfold
for train_index, test_index in kf:
cnn_model = create_cnn_model_arch()
training_samples_X = training_samples[train_index] # Getting the training input variables
training_samples_Y = training_samples_target[train_index] # Getting the training output/label variable
validation_samples_X = training_samples[test_index] # Getting the validation input variables
validation_samples_Y = training_samples_target[test_index] # Getting the validation output/label variable
fold_number += 1
print('Fold number {} from {}'.format(fold_number, nfolds))
callbacks = [
EarlyStopping(monitor='val_loss', patience=3, verbose=0),
]
# Fitting the CNN model giving the defined settings
cnn_model.fit(training_samples_X, training_samples_Y,
batch_size=batch_size,
nb_epoch=num_epochs,
shuffle=True, verbose=2,
validation_data=(validation_samples_X,
validation_samples_Y),
callbacks=callbacks)
# measuring the generalization ability of the trained model based on the validation set
predictions_of_validation_samples =
cnn_model.predict(validation_samples_X.astype('float32'),
batch_size=batch_size, verbose=2)
current_model_score = log_loss(Y_valid,
predictions_of_validation_samples)
print('Current model score log_loss: ', current_model_score)
sum_score += current_model_score*len(test_index)
# Store valid predictions
for i in range(len(test_index)):
yfull_train[test_index[i]] =
predictions_of_validation_samples[i]
# Store the trained model
trained_models.append(cnn_model)
# incrementing the sum_score value by the current model calculated score
overall_score = sum_score/len(training_samples)
print("Log_loss train independent avg: ", overall_score)
#Reporting the model loss at this stage
overall_settings_output_string = 'loss_' + str(overall_score) +
'_folds_' + str(nfolds) +
'_ep_' + str(num_epochs)
return overall_settings_output_string, trained_models
在构建好了模型并使用k折交叉验证法来评估和验证模型之后,我们需要在测试集上报告训练模型的结果。为了做到这一点,将使用k折交叉验证法,但是这次是在测试集中观察训练模型的效果。
接下来,需要以受过训练的CNN模型作为输入的函数,然后使用现有的测试集来测试它:
def test_generality_crossValidation_over_test_set(
overall_settings_output_string, cnn_models):
batch_size = 16 # in each iteration, we consider 32 training examples at once
fold_number = 0 # fold iterator
number_of_folds = len(cnn_models) # Creating number of folds based on the value used in the training step
yfull_test = [] # variable to hold overall predictions for the test set
#executing the actual cross validation test process over the test set
for j in range(number_of_folds):
model = cnn_models[j]
fold_number += 1
print('Fold number {} out of {}'.format(fold_number,
number_of_folds))
#Loading and normalizing testing samples
testing_samples, testing_samples_id =
load_normalize_testing_samples()
#Calling the current model over the current test fold
test_prediction = model.predict(testing_samples,
batch_size=batch_size, verbose=2)
yfull_test.append(test_prediction)
test_result = merge_several_folds_mean(yfull_test, number_of_folds)
overall_settings_output_string = 'loss_' +
overall_settings_output_string \ + '_folds_' +
str(number_of_folds)
format_results_for_types(test_result, testing_samples_id,overall_settings_output_string)
1.模型训练与测试
下面可以通过调用主函数create_model_with_kfold_cross_validation()来创建和训练CNN模型了,该CNN模型使用了10折交叉验证法。然后调用测试函数来度量模型在测试集上的泛化能力。
if __name__ == '__main__':
info_string, models = create_model_with_kfold_cross_validation()
test_generality_crossValidation_over_test_set(info_string, models)
2.鱼类识别——完整的代码
在解释完鱼识别示例的主要构建模块之后,就可以将所有代码片段连接在一起,并查看如何通过几行代码来构建这样一个复杂的系统。完整的代码放在本书附录A中。