使用k-NN实现一个简单图像分类器

此次图像分类器是用于猫狗分类,数据量比较小;分类器采用k-Nearest Neighbors(k-NN)方法。

1. 数据

从Kaggle Dogs vs. Cats挑战中抽取狗和猫图像各1000张。这个数据集包含2000个图片。

2. 代码

2.1 代码结构


knnClassifier
│ ├── pycache
│ ├── animals
│ ├── dataset.py
│ ├── knn.py
│ ├── simpledatasetloader.py
│ └── simplepreprocessor.py


2.2 图像预处理器:调整图像大小

在开始之前,构建调整图像大小的图像预处理器,忽略纵横比:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2018-12-21 02:17:37
# @Author  : Chen Cjv ([email protected])
# @Link    : https://blog.csdn.net/weixin_44049128
# @Version : $Id$

import cv2		# OpenCV-Python库

class SimplePreprocessor:
	# 使用像素区域关系进行重采样。它可能是图像抽取的首选方法,因为它会产生无云纹理的结果
     def __init__(self, width, height, inter=cv2.INTER_AREA):
          # 存储调整大小时使用的目标图像宽度,高度和插值方法
          self.width = width
          self.height = height
          self.inter = inter
          
     def preprocess(self, image):
          # 将图像调整为固定大小,忽略纵横比
          return cv2.resize(image, (self.width, self.height), interpolation=self.inter)

参数说明:

  • width:调整大小后输入图像的目标宽度。
  • height:调整大小后输入图像的目标高度。
  • inter:一个可选参数,用于控制调整大小时使用的插值算法。(使用像素区域关系进行重采样。 它可能是图像抽取的首选方法,因为它会产生无云纹理的结果。)

函数preprocess需要需要一个参数——我们要预处理的输入图像。通过cv2.resize将图像调整为固定大小的宽度和高度来预处理图像,然后返回到调用函数。

2.3 构建图片加载器

定义好图像处理器之后,开始定义图像加载器:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2018-12-21 02:16:19
# @Author  : Chen Cjv ([email protected])
# @Link    : https://blog.csdn.net/weixin_44049128
# @Version : $Id$

import numpy as np
import cv2
import os

class SimpleDatasetLoader:
	def __init__(self, preprocessors=None):
		# 存储图像预处理器
		self.preprocessors = preprocessors

		# 如果预处理器为None,则将它们初始化为空列表
		if self.preprocessors is None:
			self.preprocessors = []

	def load(self, imagePaths, verbose=-1):
		# 初始化特征和标签列表
		data = []
		labels = []

		# 循环输入图像
		for (i, imagePath) in enumerate(imagePaths):
			# 加载图像并提取类标签,假设我们的路径具有以下格式:
			# /path/to/dataset/{class}/{image}.jpg
			image = cv2.imread(imagePath)
			label = imagePath.split(os.path.sep)[-2]
			
			# 检查我们的预处理器是否不是None
			if self.preprocessors is not None:
				# 循环预处理器并将每个应用于图像
				for p in self.preprocessors:
					image = p.preprocess(image)
					
			# 通过更新数据列表后跟标签,将处理后的图像视为“特征向量”
			data.append(image)
			labels.append(label)

			# 显示每个'verbose'图像的更新
			if verbose > 0 and i > 0 and (i + 1) % verbose == 0:
				print("[INFO] processed {}/{}".format(
					i + 1, len(imagePaths)))

		# 返回数据和标签的元组
		return (np.array(data), np.array(labels))

2.4 实现k-NN

k-Nearest Neighbor 分类器是迄今为止最简单的机器学习和图像分类算法。事实上,它很简单,实际上并没有“学习”任何东西。相反,该算法直接依赖于特征向量之间的距离(在我们的例子中,是图像的原始RGB像素强度)

简而言之,k-NN算法通过在k个最近的例子中找到最常见的类来对未知数据点进行分类。 k个最接近的数据点中的每个数据点投票,并且具有最高投票数的类别获胜。如图1所示,对狗进行分类:
使用k-NN实现一个简单图像分类器_第1张图片

图1 狗的k-NN 分类

为了应用k-NN分类器,我们首先需要选择距离度量或相似度函数。 常见的选择
包括欧几里德距离(通常称为L2距离):
(1) d ( p , q ) = ∑ i = 1 N ( q i − p i ) 2 d(p, q)=\sqrt{\displaystyle{\sum_{i=1}^N(q_i-p_i)^2}}\tag{1} d(p,q)=i=1N(qipi)2 (1)
但是,也可以使用其他距离指标,例如北京城市街区(通常称为L1距离):
(2) d ( p , q ) = ∑ i = 1 N ∣ q i − p i ∣ d(p, q)={\displaystyle{\sum_{i=1}^N|q_i-p_i|}}\tag{2} d(p,q)=i=1Nqipi(2)
最流行的是采用:欧几里德距离。
下面是knn.py代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2018-12-21 11:12:19
# @Author  : Chen Cjv ([email protected])
# @Link    : https://blog.csdn.net/weixin_44049128
# @Version : $Id$

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 simplepreprocessor import SimplePreprocessor
from simpledatasetloader import SimpleDatasetLoader
from imutils import paths
import argparse

if __name__ == '__main__':
	# 构造参数解析并解析参数
	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())

	# 抓住我们将要描述的图像列表
	print("[INFO] loading images...")
	imagePaths = list(paths.list_images(args["dataset"]))

	# 初始化图像预处理器,从磁盘加载数据集,并重塑数据矩阵
	sp = SimplePreprocessor(32, 32)
	sdl = SimpleDatasetLoader(preprocessors=[sp])
	(data, labels) = sdl.load(imagePaths, verbose=100)
	data = data.reshape((data.shape[0], 3072))

	# 显示有关图内存消耗的一些信息
	print("[INFO] features matrix: {:.1f}MB".format(
		data.nbytes / (1024 * 1000.0)))

	# 将标签编码为整数
	le = LabelEncoder()
	labels = le.fit_transform(labels)

	# 使用75%的数据进行训练并将剩余的25%用于测试,将数据划分为训练和测试分组
	(trainX, testX, trainY, testY) = train_test_split(data, labels,
		test_size=0.25, random_state=42)
	 
	# 在原始像素强度上训练和评估k-NN分类器
	print("[INFO] evaluating k-NN classifier...")
	
	model = KNeighborsClassifier(n_neighbors=3)
	model.fit(trainX, trainY)
	print(classification_report(testY, model.predict(testX),
		target_names=le.classes_))

执行knn.py,需要三个参数:

  • –dataset:输入图像数据集在磁盘上的路径。
  • –neighbors:可选,使用k-NN算法时应用的邻居数k。
  • –jobs:可选,可选,计算输入数据点与训练集之间距离时要运行的并发作业数。-1将使用所有可用核心处理器。

输入以下命令,执行:

$ python knn.py --dataset ./animals

输出如下图2所示:

图2 结果显示平均识别准确率是54%,对猫的识别准确度 是55%,对狗是52%。结果可归因于:狗和猫可以具有非常相似的皮毛涂层,并且它们的涂层的颜色不能用于区分它们。

3. k-NN优缺点

3.1 优点

  • k-NN实现和理解起来非常简单。 此外,分类器完全没有花时间进行训练,因为我们需要做的就是存储我们的数据,以便以后计算距离并获得最终分类。
  • k-NN算法更适合于低维特征空间(图像不是)。高维特征空间中的距离通常是不直观的。

3.2 缺点

  • 分类新的测试点需要与我们的训练数据中的每个单个数据点进行比较,该数据点按O(N)进行缩放,使得不适合用于较大的数据集。
  • 可以通过使用近似最近邻(ANN)算法克服这个时间开销。然而,使用这些算法需要我们用空间/时间复杂度来交换最近邻算法的“正确性”。
  • k-NN算法实际上并没有“学习”任何东西——如果它犯了错误,那么算法不能使自己更聪明;它只是依靠n维空间中的距离来进行分类。

4. 总结

参考:

  1. 《Deep.Learning.for.Computer.Vision.with.Python.Starter.Bundle》
    此书所写,原文是对cat、dog、panda三种动物进行分类的。
  2. https://blog.csdn.net/qq_27158179/article/details/82829934
    该篇博客也是根据DL4CV所写。

你可能感兴趣的:(机器学习)