这章主要讲了图像预处理的代码、图像加载的代码、K-NN算法和使用K-NN的例子。
作者:我们将讨论k-最近邻(k-NN)分类器,这是您第一次接触使用机器学习进行图像分类。事实上,这个算法非常简单,根本没有进行任何实际的“学习”——但它仍然是一个需要回顾的重要算法,所以我们可以在以后的章节中了解神经网络如何从数据中学习。
Starter篇中的数据集都足够小,我们可以将它们全部加载到内存中,而不必担心内存管理;然而,Practitioner篇和ImageNet篇中更大的数据集需要我们开发一些聪明的方法来有效地处理加载图像,我们可以训练图像分类器(不会耗尽内存)。也就是说,在开始使用图像分类算法之前,您应该始终了解数据集的大小。
“Animals”数据集是一个简单的示例数据集,我将其放在一起以演示如何使用简单的机器学习技术以及高级深度学习算法来训练图像分类器。Animals数据集中的图像属于三个不同的类别:狗、猫和熊猫,每个类别有1,000个示例图像。狗和猫的图像是从KaggleDogsvs.Cats挑战赛(http://pyimg.co/ogx37)中采样的,而熊猫图像是从ImageNet数据集中采样的。
本章的目标是利用k-NN分类器尝试仅使用原始像素强度(即不进行特征提取)来识别图像中的每个物种。正如我们将看到的,原始像素强度不适合k-NN算法。尽管如此,这是一个重要的基准测试,因此我们可以理解为什么卷积神经网络能够在原始像素强度上获得如此高的准确性,而传统的机器学习算法却无法做到这一点。
让我们继续并开始定义我们工具包的项目结构:
如您所见,我们有一个名为pyimagesearch的模块。我们开发的所有代码都将存在于pyimagesearch模块中。为了本章的目的,我们需要定义两个子模块:
在接下来的两节中,我们将手动实现SimplePreprocessor和SimpleDatasetLoader。
代码如下:
# import the necessary packages import cv2 class SimplePreprocessor: def __init__(self, width, height, inter=cv2.INTER_AREA): # store the target image width, height, and interpolation # method used when resizing self.width = width self.height = height self.inter = inter def preprocess(self, image): # resize the image to a fixed size, ignoring the aspect # ratio return cv2.resize(image, (self.width, self.height), interpolation=self.inter)
宽度:调整大小后输入图像的目标宽度。
高度:调整大小后我们输入图像的目标高度。
inter:一个可选参数,用于控制调整大小时使用哪种插值算法。
代码如下:
# import the necessary packages import numpy as np import cv2 import os class SimpleDatasetLoader: def __init__(self, preprocessors=None): # store the image preprocessor self.preprocessors = preprocessors # if the preprocessors are None, initialize them as an # empty list if self.preprocessors is None: self.preprocessors = [] def load(self, imagePaths, verbose=-1): # initialize the list of features and labels data = [] labels = [] # loop over the input images for (i, imagePath) in enumerate(imagePaths): # load the image and extract the class label assuming # that our path has the following format: # /path/to/dataset/{class}/{image}.jpg image = cv2.imread(imagePath) label = imagePath.split(os.path.sep)[-2] # check to see if our preprocessors are not None if self.preprocessors is not None: # loop over the preprocessors and apply each to # the image for p in self.preprocessors: image = p.preprocess(image) # treat our processed image as a "feature vector" # by updating the data list followed by the labels data.append(image) labels.append(label) # show an update every `verbose` images if verbose > 0 and i > 0 and (i + 1) % verbose == 0: print("[INFO] processed {}/{}".format(i + 1, len(imagePaths))) # return a tuple of the data and labels return (np.array(data), np.array(labels))
如您所见,我们的数据集加载器设计简单;然而,它使我们能够轻松地将任意数量的图像处理器应用于数据集中的每个图像。这个数据集加载器的唯一警告是它假设数据集中的所有图像都可以一次放入内存。
对于太大而无法放入系统RAM的数据集,我们需要设计一个更复杂的数据集加载器——我在Practitioner篇中介绍了这些更高级的数据集加载器。
k-最近邻分类器是迄今为止最简单的机器学习和图像分类算法。事实上,它是如此简单以至于它实际上并没有“学习”任何东西。相反,该算法直接依赖于特征向量之间的距离(在我们的例子中,是图像的原始RGB像素强度)。
机器学习笔记 - 使用K近邻(k-Nearest Neighbo)算法进行手写识别_bashendixie5的博客-CSDN博客图灵程序设计丛书.机器学习实战 第一章https://www.cnblogs.com/stoneandatao/p/11555350.htmlhttps://blog.csdn.net/bashendixie5/article/details/110918601
在运行k-NN算法时,有两个明确的超参数是我们关注的。第一个很明显:k的值。k的最佳值是多少?如果它太小(例如k=1),那么我们会提高效率,但容易受到噪声和异常数据点的影响。然而,如果k太大,那么我们就有可能过度平滑我们的分类结果并增加偏差。
我们应该考虑的第二个参数是实际距离度量。欧几里得距离是最佳选择吗?曼哈顿距离呢?
在下一节中,我们将在Animals数据集上训练我们的k-NN分类器,并在我们的测试集上评估模型。我鼓励您使用不同的k值以及不同的距离度量,注意性能如何变化。
代码和参数如下:
--dataset:输入图像数据集在磁盘上的路径。
--neighbors:可选,使用k-NN算法时要应用的邻居数k。
--jobs:可选,计算输入数据点与训练集之间的距离时要运行的并发作业数。值为-1将使用处理器上的所有可用内核。
# import the necessary packages from sklearn.neighbors import KNeighborsClassifier from sklearn.preprocessing import LabelEncoder from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report from pyimagesearch.preprocessing import SimplePreprocessor from pyimagesearch.datasets import SimpleDatasetLoader from imutils import paths import argparse # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-d", "--dataset", required=True, help="path to input dataset") ap.add_argument("-k", "--neighbors", type=int, default=1, help="# of nearest neighbors for classification") ap.add_argument("-j", "--jobs", type=int, default=-1, help="# of jobs for k-NN distance (-1 uses all available cores)") args = vars(ap.parse_args()) # grab the list of images that we'll be describing print("[INFO] loading images...") imagePaths = list(paths.list_images(args["dataset"])) # initialize the image preprocessor, load the dataset from disk, # and reshape the data matrix sp = SimplePreprocessor(32, 32) sdl = SimpleDatasetLoader(preprocessors=[sp]) (data, labels) = sdl.load(imagePaths, verbose=500) data = data.reshape((data.shape[0], 3072)) # show some information on memory consumption of the images print("[INFO] features matrix: {:.1f}MB".format( data.nbytes / (1024 * 1024.0))) # encode the labels as integers le = LabelEncoder() labels = le.fit_transform(labels) # partition the data into training and testing splits using 75% of # the data for training and the remaining 25% for testing (trainX, testX, trainY, testY) = train_test_split(data, labels, test_size=0.25, random_state=42) # train and evaluate a k-NN classifier on the raw pixel intensities print("[INFO] evaluating k-NN classifier...") model = KNeighborsClassifier(n_neighbors=args["neighbors"], n_jobs=args["jobs"]) model.fit(trainX, trainY) print(classification_report(testY, model.predict(testX), target_names=le.classes_))
评估我们的分类器,我们看到我们获得了52%的准确率——这个准确率对于一个根本不做任何真正“学习”的分类器来说还不错,因为随机猜测正确答案的概率是1/3。
但是,检查每个类别标签的准确性很有趣。“panda”类别的正确分类率为79%,这可能是因为熊猫主要是黑白的,因此这些图像在我们的3072暗空间中靠得更近。
狗和猫的分类准确率分别低得多,分别为39%和36%。背景噪声(例如后院的草、动物休息的沙发的颜色等)也会“混淆”k-NN算法,因为它无法学习这些物种之间的任何区分模式。这种混淆是k-NN算法的主要缺点之一:虽然它很简单,但也无法从数据中学习。
我们的下一章将讨论参数化学习的概念,我们可以从图像本身中实际学习模式,而不是假设具有相似内容的图像将在n维空间中组合在一起。
k-NN算法的一个主要优点是它的实现和理解极其简单。此外,分类器绝对不需要训练时间,因为我们需要做的就是存储我们的数据点,以便以后计算到它们的距离并获得我们的最终分类。
然而,我们在分类时为这种简单性付出了代价。对新的测试点进行分类需要与我们训练数据中的每个数据点进行比较,其比例为O(N),这使得处理更大的数据集在计算上变得令人望而却步。
最后,k-NN算法更适合低维特征空间(图像不是)。高维特征空间中的距离通常是不直观的,您可以在PedroDomingo的优秀论文[70]中阅读更多相关信息。
同样重要的是要注意,k-NN算法实际上并没有“学习”任何东西——如果算法出错,它就无法让自己变得更聪明;它只是依靠n维空间中的距离来进行分类。
鉴于这些缺点,为什么还要研究k-NN算法呢?原因是算法很简单。这很容易理解。最重要的是,它为我们提供了一个基线,我们可以用它来比较神经网络和卷积神经网络,以便我们在本书的其余部分进行学习。