用KNN分类器进行猫狗分类

1. KNN简单介绍

KNN名字是K-nearest neighbors。Nearest neighbors是最邻近的,K是指数量。其思想大概是,在空间中先放置好所有用于训练的样品,把测试样品置于该空间中。用距离公式计算出离测试样品最近的K个样品,假如K个样品中属于A类的最多,那测试样品也算输入A类。

下图中,白色框图片是已经正确识别的。红色框内的图片是需要进行分类的。这里取K=4,与目标图片最接近的有两张熊猫和两张猫。

用于计算距离的公式有两个,第一个是欧拉公式,也称为L2-距离。

d\left ( p,q \right )=\sqrt{\sum_{i=1}^{N}\left ( q_i-p_i \right )^2}

第二个是the Manhattan/city block,也称为L1-距离。

d\left ( p,q \right )= \sum_{i=1}^{N}\left |q_i-p_i \right |
公式计算完成,就是根据这K个样品做判断了。在这里例子中,结果会比较争议,因为该样品和两个种类集合的距离一样近……

 

2. KNN猫狗分类试验

2.1 环境搭建

我的电脑是台式机
系统:Ubuntu18.04  64bit
CPU: i3-6100
无外接显卡
RAM:8G

需要使用OpenCV+Python
OpenCV因为事前已经安装好了,是从源代码编译安装的。这里不提供步骤介绍了。

但需要安装python的必备模块。假如电脑未安装(如笔者的电脑……)scipy、numpy、sklearn和imutils,请执行:

pip install scipy
# 自动下载了numpy
pip install sklearn
pip install imutils

不过也没大碍。这几个模块假如没安装,在执行本文代码时候,系统会提醒缺少这些包的。

2.1 下载猫狗数据集


因为下载速度快,就选择了这个微软的下载包。必应一下子就搜到了这个包,而且不用注册即可下载。感觉快于Kaggle。
Kaggle Cats and Dogs Dataset

不过使用起来其实这是个坑。我下载完成解压后,执行代码,用命令行调试才发现有部分图片打不开。可以手动浏览文件,看图片有无损坏,另外也可以在python读取图片时,把无法识别的图片删除。下文会提到。

2.2 源代码

本试验在knnClassifier文件夹中创建了三个py文件
|--- knnClassifier
| |--- simpledatasetloader.py
| |--- simplepreprocessor.py
| |--- knn.py

2.2.1 simplepreprocessor.py

本文件提供了SimplePreprocessor这个class供外部文件使用。功能是根据尺寸,把输入图像压缩。因为KNN要把多个样品读取到RAM中,以便对测试样品进行分类。因此,先把所有读取的图像进行压缩,便于读取到有限的RAM中,同时也能减少判断算法的执行时间。

import cv2
 
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)
                # print(image.size)
 
 
if __name__ == '__main__':
 
        s = SimplePreprocessor(32, 32)
        img = cv2.imread('/home/xxjian/DeepLearningMaterial/kagglecatsanddogs_3367a/PetImages/Cat/9759.jpg')
        # print(img)
        cv2.imshow('src', img)
        cv2.imshow("resize", s.preprocess(img))
        #print(img.size)
        cv2.waitKey(0)
        # cv2.destroyallWindows()

文件中带有main函数,就是当使用python simplepreprocessor.py指令执行本文件时,会执行main函数内的代码。可以这样先测试本文件的函数功能。

2.2.2 simpledatasetloader.py



import numpy as np
import cv2
import os

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)

class SimpleDatasetLoader:
        def __init__(self, preprocessors=None):
                self.preprocessors = preprocessors
 
                if self.preprocessors is None:
                        self.preprocessors = []
 
        def load(self, imagePaths, verbose=-1):
                data = []
                labels = []
                # print(imagePaths)
                for (i, imagePath) in enumerate(imagePaths):
                        image = cv2.imread(imagePath)
                        label = imagePath.split(os.path.sep)[-2]
                        if self.preprocessors is not None:
                                for p in self.preprocessors:
                                        if(image is None):
                                                print(i)
                                                os.remove(imagePaths[i])
                                                print('file: ')
                                                print(imagePaths[i])
                                                print('is removed.')
                                                continue
                                        image = p.preprocess(image)
                        data.append(image)
                        labels.append(label)
                        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))
                 
if __name__ == '__main__':
        imagePaths = '/home/xxjian/DeepLearningMaterial/pet_sample/'
        sp = SimplePreprocessor(32, 32)
        sdl = SimpleDatasetLoader(preprocessors=[sp])
        #(data, labels) = sdl.load(imagePaths, verbose=10)
        #data = data.reshape((data.shape[0], 3072))

上面的这几行代码:

if(image is None):
    print(i)
    os.remove(imagePaths[i])
    print('file: ')
    print(imagePaths[i])
    print('is removed.')
    continue

用于删除数据集中无法识别的文件,可能是由于网站提供的包有问题,也可能是我下载时用下载程序在数据传输或者重构文件时遇到了错误,总之里面有损坏的格式文件。如果不加这几行,在读取到错误文件时会有NoneType错误(本程序中cv2.read()函数读取损坏的文件会输出NoneType变量)。

2.2.3 knn.py



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, help="of jobs for K-NN distance (-1 uses all variables 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)

        (trainX, testX, trainY, testY) = train_test_split(data, labels, test_size=0.25, random_state=42)
  
        print("[INFO] evaluating K-NN classifier...")

        #model = KNeighborsClassifier(n_neighbors=args["neighbors"], n_jobs=args["jobs"])
        model=KNeighborsClassifier(n_neighbors=3)
        model.fit(trainX, trainY)
        print(classification_report(testY, model.predict(testX), target_names=le.classes_))

        #knn=KNeighborsClassifier(n_neighbors=3)
        #knn.fit(trainX,trainY)
        #prediction=model.predict(testX)        
        #print(classification_report(testY, prediction, target_names=le.classes_))

我把源代码注释掉了,本试验中的代码源自pyimagesearch作者出的一本书。但是我的环境中不能直接运行,故做了小量修改。

3 试验结果

在knnClassifier文件夹内执行下面的命令:

python3 knn.py --dataset /home/xxjian/DeepLearningMaterial/kagglecatsanddogs_3367a/PetImages/

会输出以下的信息:

[INFO] loading images...
[INFO] processed 100/24944
[INFO] processed 200/24944
[INFO] processed 300/24944
[INFO] processed 400/24944
[INFO] processed 500/24944
[INFO] processed 600/24944
[INFO] processed 700/24944
[INFO] processed 800/24944
[INFO] processed 900/24944
[INFO] processed 1000/24944
...

[INFO] processed 24600/24944
[INFO] processed 24700/24944
[INFO] processed 24800/24944
[INFO] processed 24900/24944
[INFO] features matrix:74.8MB
[INFO] evaluating K-NN classifier...

执行到evaluating K-NN classifier...这里会比价耗时,可以打开gnone-system-monitor可以看到,CPU4完全被python3这程序占用了。耐心等候,最后输出了以下结果:

             precision    recall  f1-score   support

        Cat       0.56      0.65      0.60      3124
        Dog       0.58      0.49      0.53      3112

avg / total       0.57      0.57      0.57      6236

这里比较有用的信息是precision精度。support是样品数目。

代码设置了样品中的0.25作为测试样。24944的1/4就是6236。精度是正确识别的样品除以测试样品。

这是KNN的性能了。对本数据集,识别猫狗、平均精度0.57。一半对一半错。

参考:

1. 机器学习笔记--classification_report&精确度/召回率/F1值

2. Deep.Learning.for.Computer.Vision.with.Python.Starter.Bundle.2017.9.pdf的第七章

猜你喜欢

转载自blog.csdn.net/qq_27158179/article/details/82829934