深度学习与图像识别-原理与实践1【图像分类之KNN算法】


前言

本文章将讲解一种最简单的图像分类算法,即K-最近邻算法(K-NearestNeighbor,KNN)。
主要包括KNN算法的算法逻辑以及代码的实现。


一、算法逻辑

1、KNN算法的计算逻辑:
1)给定测试对象,计算它与训练集中每个对象的距离。
2)圈定距离最近的k个训练对象,作为测试对象的邻居。
3)根据这k个近邻对象所属的类别,找到占比最高的那个类别作为测试对象的预测类别。
具体操作如图所示:
2、在KNN算法中,我们发现有两个方面的因素会影响KNN算法的准确度:一个是计算测试对象与训练集中各个对象的距离,另一个因素就是k的选择(k的选择后面小节讲解)。
本小节先讲解距离度量:
(1)曼哈顿距离(Manhattan distance)
假设先只考虑两个点,第一个点的坐标为(x1,y1),第二个点的坐标为(x2,y2),那么,它们之间的曼哈顿距离就是|x1-x2|+|y1-y2|。
曼哈顿距离类似于从街道一端到街道另一端如:
下图中从A到B的三条路线都是曼哈顿距离,且三条距离相同。
在这里插入图片描述

(2)欧式距离(Euclidean Metric)
以空间为基准的两点之间的最短距离。还是假设只有两个点,第一个点的坐标为(x1,y1),第二个点的坐标为(x2,y2),那么它们之间的欧式距离就是√((x₁-x₂)²+(y₁-y₂)²)。
欧式距离是空间中的两点之间的直线距离


二、KNN实现MNIST数据分类

1、MNIST数据集
MNIST数据集来自美国国家标准与技术研究所(National Institute of Standards and Technolo,
NIST)。训练集由250个人手写的数字构成,其中50%是高中学生,50%是人口普查的工作人员。测试数据集也是同样比例的手写数字数据。MNIST数据集是一个很经典且很常用的数据集(类似于图像处理中的“Hello World!”)。
我们可以使用PyTorch框架进行MNIST数据集的下载与读取,代码如下:

import torch
import torchvision.datasets as dsets
import numpy as np
batch_size = 100
#Cifar10 dataset
train_dataset = dsets.CIFAR10(root = '/ml/pycifar', #选择数据的根目录
                           train = True, # 选择训练集
                           download = True) # 从网络上download图片
test_dataset = dsets.CIFAR10(root = '/ml/pycifar', #选择数据的根目录
                           train = False, # 选择测试集
                           download = True) # 从网络上download图片
#加载数据

train_loader = torch.utils.data.DataLoader(dataset = train_dataset,
                                           batch_size = batch_size,
                                           shuffle = True)  # 将数据打乱
test_loader = torch.utils.data.DataLoader(dataset = test_dataset,
                                          batch_size = batch_size,
                                          shuffle = True)

train_dataset与test_dataset可以返回训练集数据、训练集标签、测试集数据以及测试集标签,训练集数据以及测试集数据都是n×m维的矩阵,这里的n是样本数(行数),m是特征数(列数)。训练数据集包含60000个样本,测试数据集包含10000个样本。在MNIST数据集中,每张图片均由28×28个像素点构成,每个像素点使用一个灰度值表示。在这里,我们将28×28的像素展开为一个一维的行矢量,这些行矢量就是图片数组里的行(每行784个值,或者说每行就代表了一张图片)。训练集标签以及测试标签包含了相应的目标变量,也就是手写数字的类标签(整数0~9)。
2、完善fit方法,fit方法主要是通过训练数据集来训练模型,在Knn类中,我们的实现思路是将训练集的数据与其对应的标签存储于内存中。代码如下:

def fit(self,x_train,y_train):# x_train代表的是训练数据集,而y_train代表的是对应的训练集数据的标签
    self.x_train = x_train
    self.y_train = y_train

3、完善predict方法,predict方法可用于预测测试集的标签。具体的实现代码与之前的代码类似,只不过输入的参数只有k(代表的是k的选值),dis代表使用的是欧拉公式还是曼哈顿公式,X_test代表的是测试数据集;predict方法返回的是预测的标签集合。代码如下:

def predict(self,k,dis,x_test):
    assert dis == 'E'or dis == 'M','dis must E or M'
    num_test = x_test.shape[0] #测试样本的数量
    labellist = []
    '''
    使用欧式距离作为距离度量
    '''
    if (dis == 'E'):
        for i in range(num_test):
            # 实现欧拉距离公式
            distances = np.sqrt(np.sum(((X_train - np.tile(y_test[i], (X_train.shape[0], 1))) ** 2), axis=1))
            nearest_k = np.argsort(distances)  # 距离由小到大进行排序,并返回index值
            topK = nearest_k[:k]  # 选取前k个距离
            classCount = {}
            for i in topK:  # 统计每个类别的个数
                classCount[X_train[i]] = classCount.get(X_train[i], 0) + 1
            sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
            labellist.append(sortedClassCount[0][0])
        return np.array(labellist)
    '''
       使用曼哈顿距离作为距离度量
       '''
    if (dis == 'M'):
        for i in range(num_test):
            # 按照列的方向相加,其实就是行相加
            distances = np.sum(np.abs(X_train - np.tile(y_test[i], (X_train.shape[0], 1))), axis=1)
            nearest_k = np.argsort(distances)
            topK = nearest_k[:k]
            classCount = {}
            for i in topK:
                classCount[X_train[i]] = classCount.get(X_train[i], 0) + 1
            sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
            labellist.append(sortedClassCount[0][0])
        return np.array(labellist)

4、归一化:在进行数据加载的时候尝试使用归一化,能够使分类的准确度提升。代码如下:

def getXmean(X_train):
    X_train = np.reshape(X_train, (X_train.shape[0], -1))  # 将图片从二维展开为一维
    mean_image = np.mean(X_train, axis=0)  # 求出训练集所有图片每个像素位置上的平均值
    return mean_image

def centralized(X_test,mean_image):
    X_test = np.reshape(X_test, (X_test.shape[0], -1))  # 将图片从二维展开为一维
    X_test = X_test.astype(np.float64)
    X_test -= mean_image  # 减去均值图像,实现零均值化
    return X_test

5、进行分类并输出准确度,代码如下:
未使用归一化代码:

if __name__ == '__main__':

    X_train = train_loader.dataset.data.numpy()  # 需要转为numpy矩阵
    X_train = X_train.reshape(X_train.shape[0], 28 * 28)  # 需要reshape之后才能放入knn分类器
    y_train = train_loader.dataset.targets.numpy()
    X_test = test_loader.dataset.data[:1000].numpy()
    X_test = X_test.reshape(X_test.shape[0], 28 * 28)
    y_test = test_loader.dataset.targets[:1000].numpy()
    num_test = y_test.shape[0]
    y_test_pred = kNN_classify(5, 'E', X_train, y_train, X_test)
    num_correct = np.sum(y_test_pred == y_test)
    accuracy = float(num_correct) / num_test
    print('Got %d / %d correct => accuracy: %f' % (num_correct, num_test, accuracy))

输出结果:
在这里插入图片描述

使用归一化后代码:

if __name__ == '__main__':

    X_train = train_loader.dataset.data.numpy()  # 需要转为numpy矩阵
    mean_image = getXmean(X_train)
    X_train = centralized(X_train,mean_image)
    y_train = train_loader.dataset.targets.numpy()
    X_test = test_loader.dataset.data[:1000].numpy()
    X_test = centralized(X_test,mean_image)
    y_test = test_loader.dataset.targets[:1000].numpy()
    num_test = y_test.shape[0]
    y_test_pred = kNN_classify(5, 'M', X_train, y_train, X_test)
    num_correct = np.sum(y_test_pred == y_test)
    accuracy = float(num_correct) / num_test
    print('Got %d / %d correct => accuracy: %f' % (num_correct, num_test, accuracy))

输出结果:
在这里插入图片描述
通过以上得到的准确度可知,归一化能够提升准确度。


三、KNN实现Cifar10数据分类

1、CIfar10数据集
获取Cifar10数据集代码如下:

import numpy as np
import torch
import KNN_Test
from torch.utils.data import DataLoader
import torchvision.datasets as dsets
batch_size = 100
#Cifar10 dataset
train_dataset = dsets.CIFAR10(root = '/ml/pycifar', #选择数据的根目录
                           train = True, # 选择训练集
                           download = True) # 从网络上download图片
test_dataset = dsets.CIFAR10(root = '/ml/pycifar', #选择数据的根目录
                           train = False, # 选择测试集
                           download = True) # 从网络上download图片
#加载数据

train_loader = torch.utils.data.DataLoader(dataset = train_dataset,
                                           batch_size = batch_size,
                                           shuffle = True)  # 将数据打乱
test_loader = torch.utils.data.DataLoader(dataset = test_dataset,
                                          batch_size = batch_size,
                                          shuffle = True)

2、查看Cifar10数据集中图片代码如下:

classes = ('plane', 'car', 'bird', 'cat', 'deer','dog', 'frog', 'horse', 'ship', 'truck')
digit = train_loader.dataset.data[0]
import matplotlib.pyplot as plt
plt.imshow(digit,cmap = plt.cm.binary)
plt.show()
print(classes[train_loader.dataset.targets[0]])

3、fit方法、predict方法以及归一化方法同上,这里不再给出代码,读者可以去上一节查看
4、进行分类以及准确度的输出代码如下:

X_train = train_loader.dataset.data
mean_image = getXmean(X_train)
X_train = centralized(X_train,mean_image)
y_train = train_loader.dataset.targets
X_test = test_loader.dataset.data[:100]
X_test = centralized(X_test,mean_image)
y_test = test_loader.dataset.targets[:100]
num_test = len(y_test)

y_test_pred = KNN_Test.kNN_classify(6, 'E', X_train, y_train, X_test) #这里并没有使用封装好的类
num_correct = np.sum(y_test_pred == y_test)
accuracy = float(num_correct) / num_test
print('Got %d / %d correct => accuracy: %f' % (num_correct, num_test, accuracy))

输出结果:
在这里插入图片描述

四、模型参数调优

对于KNN算法来说,k就是需要调整的超参数。对于k的调整并没有很好的方法,我们一般会尝试不同的值,看哪个值表现最好就选哪个。有一种更专业的穷举调参方法称为GridSearch,即在所有候选的参数中,通过循环遍历,尝试每一种的可能性,表现最好的参数就是最终的结果。
以下使用交叉验证的数据拆分方法来划分我们的数据集(读者不知道这个方法的话可以去看周志华老师的西瓜书)
1、使用之前所写的KNN分类器,代码如下:

import operator
import torch
import torchvision.datasets as dsets
import numpy as np
batch_size = 100
#Cifar10 dataset
train_dataset = dsets.CIFAR10(root = '/ml/pycifar', #选择数据的根目录
                           train = True, # 选择训练集
                           download = True) # 从网络上download图片
test_dataset = dsets.CIFAR10(root = '/ml/pycifar', #选择数据的根目录
                           train = False, # 选择测试集
                           download = True) # 从网络上download图片
#加载数据

train_loader = torch.utils.data.DataLoader(dataset = train_dataset,
                                           batch_size = batch_size,
                                           shuffle = True)  # 将数据打乱
test_loader = torch.utils.data.DataLoader(dataset = test_dataset,
                                          batch_size = batch_size,
                                          shuffle = True)
class Knn:

    def __init__(self):
        pass

    def fit(self,X_train,y_train):
        self.Xtr = X_train
        self.ytr = y_train

    def predict(self,k, dis, X_test):
        assert dis == 'E' or dis == 'M', 'dis must E or M'
        num_test = X_test.shape[0]  # 测试样本的数量
        labellist = []
        # 使用欧拉公式作为距离度量
        if (dis == 'E'):
            for i in range(num_test):
                distances = np.sqrt(np.sum(((self.Xtr - np.tile(X_test[i], (self.Xtr.shape[0], 1))) ** 2), axis=1))
                nearest_k = np.argsort(distances)
                topK = nearest_k[:k]
                classCount = {}
                for i in topK:
                    classCount[self.ytr[i]] = classCount.get(self.ytr[i], 0) + 1
                sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
                labellist.append(sortedClassCount[0][0])
            return np.array(labellist)

        # 使用曼哈顿公式作为距离度量
        if (dis == 'M'):
            for i in range(num_test):
                # 按照列的方向相加,其实就是行相加
                distances = np.sum(np.abs(self.Xtr - np.tile(X_test[i], (self.Xtr.shape[0], 1))), axis=1)
                nearest_k = np.argsort(distances)
                topK = nearest_k[:k]
                classCount = {}
                for i in topK:
                    classCount[self.ytr[i]] = classCount.get(self.ytr[i], 0) + 1
                sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
                labellist.append(sortedClassCount[0][0])
            return np.array(labellist)
        
#归一化
def getXmean(X_train):
    X_train = np.reshape(X_train, (X_train.shape[0], -1))  # 将图片从二维展开为一维
    mean_image = np.mean(X_train, axis=0)  # 求出训练集所有图片每个像素位置上的平均值
    return mean_image

def centralized(X_test,mean_image):
    X_test = np.reshape(X_test, (X_test.shape[0], -1))  # 将图片从二维展开为一维
    X_test = X_test.astype(np.float64)
    X_test -= mean_image  # 减去均值图像,实现零均值化
    return X_test

2、准备测试数据与验证数据,代码如下:

X_train = train_loader.dataset.data
X_train = X_train.reshape(X_train.shape[0],-1)
mean_image = getXmean(X_train)
X_train = centralized(X_train,mean_image)
y_train = train_loader.dataset.targets
y_train = np.array(y_train)
X_test = test_loader.dataset.data
X_test = X_test.reshape(X_test.shape[0],-1)
X_test = centralized(X_test,mean_image)
y_test = test_loader.dataset.targets
y_test = np.array(y_test)

3、将训练数据分成5个部分,每个部分轮流作为验证集,代码如下:

num_folds = 5
k_choices = [1, 3, 5, 8, 10, 12, 15, 20]  # k的值一般选择1~20以内
num_training = X_train.shape[0]
X_train_folds = []
y_train_folds = []
indices = np.array_split(np.arange(num_training), indices_or_sections=num_folds)  # 把下标分成5个部分
for i in indices:
    X_train_folds.append(X_train[i])
    y_train_folds.append(y_train[i])
k_to_accuracies = {}
for k in k_choices:
    # 进行交叉验证
    acc = []
    for i in range(num_folds):
        x = X_train_folds[0:i] + X_train_folds[i + 1:]  # 训练集不包括验证集
        x = np.concatenate(x, axis=0)  # 使用concatenate将4个训练集拼在一起
        y = y_train_folds[0:i] + y_train_folds[i + 1:]
        y = np.concatenate(y)  # 对label使用同样的操作
        test_x = X_train_folds[i]  # 单独拿出验证集
        test_y = y_train_folds[i]

        classifier = Knn()  # 定义model
        classifier.fit(x, y)  # 将训练集读入
        # dist = classifier.compute_distances_no_loops(test_x)  # 计算距离矩阵
        y_pred = classifier.predict(k, 'M', test_x)  # 预测结果
        # print(y_pred)
        accuracy = np.mean(y_pred == test_y)  # 计算准确率
        acc.append(accuracy)
k_to_accuracies[k] = acc  # 计算交叉验证的平均准确率
# 输出准确度
for k in sorted(k_to_accuracies):
    for accuracy in k_to_accuracies[k]:
        print('k = %d, accuracy = %f' % (k, accuracy))

4、图形化展示k的选取与准确度趋势,代码如下:

import matplotlib.pyplot as plt
for k in k_choices:
    accuracies = k_to_accuracies[k]
    plt.scatter([k] * len(accuracies), accuracies)

# plot the trend line with error bars that correspond to standard deviation
accuracies_mean = np.array([np.mean(v) for k,v in sorted(k_to_accuracies.items())])
accuracies_std = np.array([np.std(v) for k,v in sorted(k_to_accuracies.items())])
plt.errorbar(k_choices, accuracies_mean, yerr=accuracies_std)
plt.title('Cross-validation on k')
plt.xlabel('k')
plt.ylabel('Cross-validation accuracy')
plt.show()

这个我运行代码跑了很久也没跑出结果,大家可以自己试一下。


总结

对于KNN算法,我们需要理解算法的原理,掌握距离度量的规则,以及归一化的影响。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值