当前位置:   article > 正文

图像分类(三)— KNN算法实战之CIFAR10数据集+交叉验证(附代码)

图像分类(三)— KNN算法实战之CIFAR10数据集+交叉验证(附代码)

目录

一、理论基础

二、数据集准备

三、代码实现

1. 加载数据

2. 图像可视化

3. KNN算法及验证效果

四、模型参数调优(重点)

1.  距离度量的选择

2.  k值的选择

2.1  进行交叉验证

2.2  k值的选取

2.3  总结

六、参考资料


一、理论基础

在图像分类任务中,通常使用的是三维彩色图像,这种图像包含了红色、绿色和蓝色三个颜色通道。这些通道结合在一起,能够形成我们所看到的彩色图像。每个通道本质上是二维的,包含了图像在行和列方向上的像素信息。当这些通道被堆叠在一起时,我们就得到了一个三维数组来表示整个彩色图像。

通过模型读取并计算图像属于多个预定义的类别的概率。具体来说,图像分类任务就是预测一个给定的图像包含了哪个分类标签(或者说是给出属于一系列不同标签的可能性)。

二、数据集准备

CIFAR10数据集是常用的一个标准数据集。它由加拿大高级研究院(Canadian Institute For Advanced Research, CIFAR)开发,包含60000张32*32的彩色图像,这些图像共分为10个类别,每个类别都有6000张图像。包括:飞机(airplane)、汽车(automobile)、鸟类(bird)、猫(cat)、鹿(deer)、狗(dog)、蛙类(frog)、马(horse)、船(ship)和卡车(truck)。整个数据集图像被分为50000张训练图像和10000张测试图像。

图1  CIFAR-10数据集

来源:Alex Krizhevsky​​​​​​​

三、代码实现

1. 加载数据

和之前加载MNIST手写数字数据集方法相同,在PyTorch中,可以直接使用torchvision.datasets.CIFAR10 来加载CIFAR10数据集。在这里,设置batch_size = 100,即每次迭代中使用的样本数量为100。需要注意的是:在加载数据前,需要将图像转化为张量,确保数据与PyTorch库的兼容性,使得可以方便地在图像上应用各种算法和操作。

dataset_loader.py

  1. import torch
  2. from torch.utils.data import DataLoader
  3. import torchvision.datasets as dsets
  4. import torchvision.transforms as transforms
  5. import matplotlib.pyplot as plt
  6. batch_size = 100
  7. transform = transforms.ToTensor()
  8. train_dataset = dsets.CIFAR10(root= '指定路径/pycifar',
  9. train= True,
  10. transform=transforms.ToTensor(),
  11. download=True)
  12. test_dataset = dsets.CIFAR10(root= '指定路径/pycifar',
  13. train= False,
  14. transform=transforms.ToTensor(),
  15. download=True)
  16. #加载数据
  17. train_loader = torch.utils.data.DataLoader(dataset= train_dataset,
  18. batch_size=batch_size,
  19. shuffle= True)
  20. test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
  21. batch_size=batch_size,
  22. shuffle= True)

我们可以发现下载的CIFAR10数据集由多个文件组成,每个文件承担着不同的功能和数据内容。具体包括:

  1. batches.meta #包含关于数据集的元数据
  2. data_batch_1 #当前使用的batch的编号
  3. data_batch_2
  4. data_batch_3
  5. data_batch_4
  6. data_batch_5
  7. test_batch
  8. readme.html

2. 图像可视化

show_dataset.py

  1. import matplotlib.pyplot as plt
  2. import numpy as np
  3. import dataset_loader
  4. # 获取第100张图片和其标签
  5. first_image = dataset_loader.train_dataset.data[99]
  6. first_label = dataset_loader.train_dataset.targets[99]
  7. # CIFAR10 是PIL图像,如果不用转换,直接使用以下方式显示
  8. plt.imshow(np.transpose(first_image, (0, 1, 2))) # 对于 ToTensor 转换后的数据
  9. plt.title(f'Label: {dataset_loader.train_dataset.classes[first_label]}')
  10. plt.show()

结果如下:由于CIFAR10数据集提供的图像尺寸为32*32,在视觉呈现上较为模糊(但我们可以通过标签提示看出是一辆红色汽车)。

图2  CIFAR10数据集第100个训练图像

3. KNN算法及验证效果

KNN.py

  1. import numpy as np
  2. import operator
  3. class KNNClassifier:
  4. def __init__(self):
  5. self.Xtr = None
  6. self.ytr = None
  7. def fit(self, X_train, y_train):
  8. self.Xtr = X_train
  9. self.ytr = y_train
  10. def predict(self, k, dis, X_test):
  11. assert dis == 'E' or dis == 'M' # E代表欧氏距离, M代表曼哈顿距离
  12. num_test = X_test.shape[0]
  13. labellist = []
  14. if dis == 'E':
  15. for i in range(num_test):
  16. distances = np.sqrt(np.sum(((self.Xtr - np.tile(X_test[i], (self.Xtr.shape[0],1))) ** 2), axis=1))
  17. nearest_k = np.argsort(distances)[:k]
  18. classCount = {self.ytr[i]: 0 for i in nearest_k}
  19. for i in nearest_k:
  20. classCount[self.ytr[i]] += 1
  21. sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
  22. labellist.append(sortedClassCount[0][0])
  23. elif dis == 'M':
  24. for i in range(num_test):
  25. distances = np.sum(np.abs(self.Xtr - np.tile(X_test[i], (self.Xtr.shape[0], 1))), axis=1)
  26. nearest_k = np.argsort(distances)[:k]
  27. classCount = {self.ytr[i]: 0 for i in nearest_k}
  28. for i in nearest_k:
  29. classCount[self.ytr[i]] += 1
  30. sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
  31. labellist.append(sortedClassCount[0][0])
  32. return np.array(labellist)
'
运行

test_KNN.py

在使用KNN算法之前先对图像进行了均值方差归一化,设置k值为6,选用曼哈顿距离作为距离度量。

  1. import numpy as np
  2. from KNN import KNNClassifier
  3. import dataset_loader
  4. def standardize_image(image): #均值方差归一化
  5. mean = np.mean(image)
  6. std = np.std(image)
  7. return (image - mean) / std
  8. if __name__ == '__main__':
  9. #训练数据
  10. X_train = dataset_loader.train_dataset.data
  11. X_train = X_train.reshape(X_train.shape[0], 32 * 32 * 3)
  12. print("Before centralization: Mean =", np.mean(X_train))
  13. print("Before centralization: Std Deviation =", np.std(X_train))
  14. X_train = standardize_image(X_train) #均值方差归一化处理
  15. y_train = dataset_loader.train_dataset.targets
  16. print("After centralization: Mean =", np.mean(X_train))
  17. print("After centralization: Std Deviation =", np.std(X_train))
  18. #测试数据
  19. X_test = dataset_loader.test_dataset.data[:100]
  20. X_test = X_test.reshape(X_test.shape[0], 32 * 32 * 3)
  21. X_test = standardize_image(X_test)
  22. y_test = dataset_loader.test_dataset.targets[:100]
  23. num_test = len(y_test)
  24. knn = KNNClassifier()
  25. knn.fit(X_train, y_train)
  26. y_test_pred = knn.predict(6,'M',X_test)
  27. num_correct = np.sum(y_test_pred == y_test)
  28. accuracy = float(num_correct) / num_test
  29. print('Got %d / %d correct => accuracy: %f' % (num_correct,num_test,accuracy))

结果如下:

  1. Before centralization: Mean = 120.70756512369792
  2. Before centralization: Std Deviation = 64.15007589112135
  3. After centralization: Mean = -2.0733044910533257e-17
  4. After centralization: Std Deviation = 1.0
  5. Got 34 / 100 correct => accuracy: 0.340000

打印归一化前后的像素值平均值和标准差可以发现标准化后,数据的平均值为-2.0733044910533257e-17,接近0(浮点数计算的微小误差,这在数值计算中可以视为0),标准差为1.0。然而,模型的准确率并不高,只有34%。

为什么会导致这样的结果?

与之前的MNIST数据集相比,CIFAR10数据集的分类增加了很多的挑战:

  • 图像复杂性:MNIST数据集仅包含手写数字的灰度图像,图像大小为 28x28 像素。这些图像较为简单,只包含单一对象(数字);而CIFAR10数据集包含了 32x32 像素的彩色图像,且图像中通常包含背景和多个对象,彩色图像带来的颜色信息(RGB)是MNIST所没有的,这需要在处理时考虑更多的数据维度。
  • 标签复杂性:MNIST仅包含10个类别,这些类别都是数字从0到9;CIFAR10数据集同样包含10个类别,但这些类别是不同类型的物体,包括飞机、汽车、鸟、猫、鹿、狗、蛙、马、船和卡车。这种多样性使得CIFAR10数据集在图像分类中的应用更为广泛,同时对分类算法的要求也更高。
  • 另外,CIFAR10数据集会受各种自然条件影响,所以图像噪声比较大。
MNIST数据集CIFAR10数据集
图像类型灰度图像(单通道)彩色图像(三通道)
图像尺寸28*2832*32

四、模型参数调优(重点)

那么,现在我们就需要想办法来提高模型的准确率,在前面博客中提到过的直接决定KNN算法的准确性的两个因素:训练数据和测试数据中样本之间的距离和k值(“邻居”数量)的选择。

1.  距离度量的选择

KNN算法的核心是通过选择周围的k个“邻居”,通过分别计算测试数据和这k个“邻居”样本之间的距离,选择所占比例最高的类别赋予预测数据。这里的距离计算仅考虑曼哈顿距离和欧式距离两种情况。那么,我们现在可以尝试选用欧式距离来替换曼哈顿距离作为距离度量。

  1. #仅需把上面的这一行替换即可
  2. y_test_pred = knn.predict(6,'E',X_test)

结果如下:

  1. Before centralization: Mean = 120.70756512369792
  2. Before centralization: Std Deviation = 64.15007589112135
  3. After centralization: Mean = -2.0733044910533257e-17
  4. After centralization: Std Deviation = 1.0
  5. Got 36 / 100 correct => accuracy: 0.360000

可以发现模型的准确率并没有多大的变化(还是较差),这时就可以转向考虑k值选择对准确性的影响。

2.  k值的选择

在机器学习中,涉及很多的参数和超参数,实践中往往需要对于这些参数进行适当地选择和调整。对于KNN算法来说,选择合适的k值是实现最佳性能的关键步骤。那么怎么样才能保证所选取的k值就是最优解呢?

最常见的方法是使用交叉验证:将数据分成若干份,将其中的各份作为验证集(validation)后给出平均准确率,最后将评估得到的适合的参数在测试集中进行测试。

2.1  进行交叉验证

这一步需要做的是:将图像划分为5个部分,每个部分轮流作为验证集。

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. from sklearn.model_selection import cross_val_score, StratifiedKFold
  4. from sklearn.neighbors import KNeighborsClassifier
  5. from sklearn.preprocessing import StandardScaler
  6. import dataset_loader # 确保这个模块已正确配置和可用
  7. # 加载数据
  8. X_train = dataset_loader.train_dataset.data[:1000] #考虑计算,选取部分图像
  9. X_train = X_train.reshape(X_train.shape[0], -1)
  10. y_train = dataset_loader.train_dataset.targets[:1000] #考虑计算,选取部分图像
  11. X_test = dataset_loader.test_dataset.data[:1000] #考虑计算,选取部分图像
  12. X_test = X_test.reshape(X_test.shape[0], -1)
  13. y_test= dataset_loader.test_loader.dataset.targets[:1000] #考虑计算,选取部分图像
  14. # 数据标准化
  15. scaler = StandardScaler()
  16. X_train = scaler.fit_transform(X_train)
  17. X_test = scaler.transform(X_test)
  18. # K值选择
  19. k_choices = [1, 3, 5, 7, 9, 12, 14, 16, 18, 20]
  20. k_to_accuracies = {}
  21. # 5折分层交叉验证
  22. kf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
  23. # 对每个K值进行交叉验证
  24. for k in k_choices:
  25. knn = KNeighborsClassifier(n_neighbors=k)
  26. # 计算交叉验证的准确率
  27. accuracies = cross_val_score(knn, X_train, y_train, cv=kf, scoring='accuracy')
  28. k_to_accuracies[k] = accuracies
  29. # 打印结果
  30. for k in sorted(k_to_accuracies):
  31. accuracies = k_to_accuracies[k]
  32. print(f'k = {k}, accuracy = {accuracies.mean():.2f} ± {accuracies.std():.2f}')

2.2  k值的选取

在很多实际应用中,通过经验发现k值在1到20之间时,模型表现往往较为均衡,能较好地处理不同的数据集和问题。实际上,许多研究和应用案例表明,这个范围内的k值往往能提供一个不错的起点,尤其是在初步探索数据时。

也可使用图形化代码展示k的选取与准确度趋势的关系:

  1. import matplotlib.pyplot as plt
  2. import numpy as np
  3. for k in k_choices:
  4. accurancies = k_to_accurancies[k]
  5. plt.scatter([k] * len(accurancies),accurancies)
  6. accurancies_mean = np.array([np.mean(v) for k,v in sorted(k_to_accurancies.items())])
  7. accurancies_std = np.array([np.std(v) for k,v in sorted(k_to_accurancies.items())])
  8. plt.errorbar(k_choices, accurancies_mean, yerr=accurancies_std)
  9. plt.title('Cross-validation on k')
  10. plt.xlabel('k')
  11. plt.ylabel('Cross-validation accuracy')
  12. plt.show()

输出结果如下:

  1. k = 1, accuracy = 0.24 ± 0.02
  2. k = 3, accuracy = 0.21 ± 0.03
  3. k = 5, accuracy = 0.24 ± 0.02
  4. k = 7, accuracy = 0.23 ± 0.02
  5. k = 9, accuracy = 0.22 ± 0.02
  6. k = 12, accuracy = 0.22 ± 0.01
  7. k = 14, accuracy = 0.22 ± 0.01
  8. k = 16, accuracy = 0.23 ± 0.02
  9. k = 18, accuracy = 0.23 ± 0.01
  10. k = 20, accuracy = 0.23 ± 0.02
图3  交叉验证准确率

从这张图中,我们可以观察到以下几点:

  1. 平均准确率:随着k值的增加,准确率先是略有下降,然后大致稳定。这可能表明对于这个特定的数据集,小的k值不一定能提供最好的预测性能。
  2. 准确率波动:在不同的k值下,准确率的波动程度不同。例如,在k = 3或k = 5时,准确率的波动比k = 10时要大。
  3. 最佳k值:虽然不同的k值得到的平均准确率相差不大,但通常我们会选择那个能提供最高平均准确率并且具有较小变异的k值,因为这意味着模型不仅准确率高,而且对不同的数据折稳定。

但注意的是:根据这张图,没有一个绝对的最优k值。通常情况下,选择“最好”的k值涉及寻找平均交叉验证准确率高且误差小的点。我们希望找到一个准确率相对较高且其对应的误差线(代表准确率变异性的垂直线)相对较短的k 值,因为这表明该k值不仅准确率高,而且模型表现稳定。

但在这张图像中,没有一个k值明显胜出。不过,我们可以观察到,当k值在5到10之间时,准确率的平均值似乎较高,且误差线较短。这可能表明这个范围内的k值能够提供较好的性能和稳定性。如果可以的话,可以进一步分析这些k值的准确率和方差,来确定一个最优的k。此外,如果数据集非常大,计算时间也可能是一个考虑因素。

2.3  总结

使用KNN分类器处理CIFAR10数据集确实可能遇到一些挑战,特别是与一些更现代的、专门为图像识别设计的方法(如卷积神经网络)相比,其准确率效果会差很多。

这主要是因为以下几个原因:

  1. 高维度:CIFAR10数据集图像是32x32像素的彩色图像,每个图像由3072个特征(像素值)组成。在高维空间中,计算距离(无论是欧氏距离还是曼哈顿距离)可以变得非常复杂,而且KNN在处理高维数据时效率通常较低,因为高维空间中距离度量的区分性下降(所谓的“维度诅咒”)。
  2. 计算成本:由于每个分类决策都需要对整个训练集进行搜索,KNN的计算成本在大规模数据集上可能会非常高。对于包含数万图像的CIFAR10数据集,这一点尤其显著。
  3. 准确率限制:由于KNN依赖于距离函数来直接比较原始像素值,它可能无法有效捕捉图像内容的更抽象和复杂的模式,这些模式通常对分类至关重要。此外,KNN对于图像中的小变化和噪声非常敏感。
  4. 模型简单性:与深度学习模型相比,KNN分类器相对简单,没有能力从数据中学习更复杂的特征表示,这在图像分类任务中尤为重要。

改进策略:

尽管KNN分类器可能不是处理CIFAR10数据集的最佳选择,但如果你想使用它,可以考虑以下策略来改善性能:

  1. 特征提取:使用预处理方法或简单的特征提取技术,如:主成分分析(PCA)减少数据的维度。这可以帮助减轻维度的“诅咒”并提高距离计算的有效性。
  2. 更复杂的距离度量:尝试不同的距离度量,如:余弦相似度,这可能对于比较图像更有效。
  3. 数据增强:通过轻微修改图像,如:旋转、缩放和裁剪,来增加训练数据的多样性,这可以帮助模型学习更鲁棒的特征。
  4. 集成方法:使用多个KNN分类器的预测结果进行集成,可能通过不同的k值或距离度量来改进整体性能。

然而,对于图像分类任务,尤其是像CIFAR10数据集这样的复杂数据集,深度学习方法(特别是卷积神经网络CNN)通常会提供更好的结果。CNN能够通过其多层结构学习图像的层次特征表示,通常在图像分类任务中表现优异。

六、参考资料

[1]魏溪含, 涂铭, & 张修鹏. (2020). 深度学习与图像识别: 原理与实践. 北京: 机械工业出版社。

[2]Dataset之CIFAR-10:CIFAR-10数据集的简介、下载、使用方法之详细攻略

[3]The CIFAR-10 dataset.

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/空白诗007/article/detail/953986
推荐阅读
相关标签
  

闽ICP备14008679号