赞
踩
目录
在图像分类任务中,通常使用的是三维彩色图像,这种图像包含了红色、绿色和蓝色三个颜色通道。这些通道结合在一起,能够形成我们所看到的彩色图像。每个通道本质上是二维的,包含了图像在行和列方向上的像素信息。当这些通道被堆叠在一起时,我们就得到了一个三维数组来表示整个彩色图像。
通过模型读取并计算图像属于多个预定义的类别的概率。具体来说,图像分类任务就是预测一个给定的图像包含了哪个分类标签(或者说是给出属于一系列不同标签的可能性)。
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 |
和之前加载MNIST手写数字数据集方法相同,在PyTorch中,可以直接使用torchvision.datasets.CIFAR10
来加载CIFAR10数据集。在这里,设置batch_size = 100,即每次迭代中使用的样本数量为100。需要注意的是:在加载数据前,需要将图像转化为张量,确保数据与PyTorch库的兼容性,使得可以方便地在图像上应用各种算法和操作。
dataset_loader.py
- import torch
- from torch.utils.data import DataLoader
- import torchvision.datasets as dsets
- import torchvision.transforms as transforms
- import matplotlib.pyplot as plt
-
- batch_size = 100
- transform = transforms.ToTensor()
-
- train_dataset = dsets.CIFAR10(root= '指定路径/pycifar',
- train= True,
- transform=transforms.ToTensor(),
- download=True)
- test_dataset = dsets.CIFAR10(root= '指定路径/pycifar',
- train= False,
- transform=transforms.ToTensor(),
- download=True)
- #加载数据
- 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)
我们可以发现下载的CIFAR10数据集由多个文件组成,每个文件承担着不同的功能和数据内容。具体包括:
- batches.meta #包含关于数据集的元数据
- data_batch_1 #当前使用的batch的编号
- data_batch_2
- data_batch_3
- data_batch_4
- data_batch_5
- test_batch
- readme.html
show_dataset.py
- import matplotlib.pyplot as plt
- import numpy as np
- import dataset_loader
-
- # 获取第100张图片和其标签
- first_image = dataset_loader.train_dataset.data[99]
- first_label = dataset_loader.train_dataset.targets[99]
-
- # CIFAR10 是PIL图像,如果不用转换,直接使用以下方式显示
- plt.imshow(np.transpose(first_image, (0, 1, 2))) # 对于 ToTensor 转换后的数据
- plt.title(f'Label: {dataset_loader.train_dataset.classes[first_label]}')
- plt.show()
结果如下:由于CIFAR10数据集提供的图像尺寸为32*32,在视觉呈现上较为模糊(但我们可以通过标签提示看出是一辆红色汽车)。
图2 CIFAR10数据集第100个训练图像 |
KNN.py
- import numpy as np
- import operator
-
- class KNNClassifier:
- def __init__(self):
- self.Xtr = None
- self.ytr = None
-
- 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' # E代表欧氏距离, 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)[:k]
- classCount = {self.ytr[i]: 0 for i in nearest_k}
- for i in nearest_k:
- classCount[self.ytr[i]] += 1
- sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
- labellist.append(sortedClassCount[0][0])
-
- elif 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)[:k]
- classCount = {self.ytr[i]: 0 for i in nearest_k}
- for i in nearest_k:
- classCount[self.ytr[i]] += 1
- sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
- labellist.append(sortedClassCount[0][0])
-
- return np.array(labellist)
'运行
test_KNN.py
在使用KNN算法之前先对图像进行了均值方差归一化,设置k值为6,选用曼哈顿距离作为距离度量。
- import numpy as np
- from KNN import KNNClassifier
- import dataset_loader
-
- def standardize_image(image): #均值方差归一化
- mean = np.mean(image)
- std = np.std(image)
- return (image - mean) / std
-
- if __name__ == '__main__':
- #训练数据
- X_train = dataset_loader.train_dataset.data
- X_train = X_train.reshape(X_train.shape[0], 32 * 32 * 3)
- print("Before centralization: Mean =", np.mean(X_train))
- print("Before centralization: Std Deviation =", np.std(X_train))
- X_train = standardize_image(X_train) #均值方差归一化处理
- y_train = dataset_loader.train_dataset.targets
- print("After centralization: Mean =", np.mean(X_train))
- print("After centralization: Std Deviation =", np.std(X_train))
-
- #测试数据
- X_test = dataset_loader.test_dataset.data[:100]
- X_test = X_test.reshape(X_test.shape[0], 32 * 32 * 3)
- X_test = standardize_image(X_test)
- y_test = dataset_loader.test_dataset.targets[:100]
-
- num_test = len(y_test)
-
- knn = KNNClassifier()
- knn.fit(X_train, y_train)
-
- y_test_pred = knn.predict(6,'M',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))
结果如下:
- Before centralization: Mean = 120.70756512369792
- Before centralization: Std Deviation = 64.15007589112135
- After centralization: Mean = -2.0733044910533257e-17
- After centralization: Std Deviation = 1.0
-
- Got 34 / 100 correct => accuracy: 0.340000
打印归一化前后的像素值平均值和标准差可以发现标准化后,数据的平均值为-2.0733044910533257e-17,接近0(浮点数计算的微小误差,这在数值计算中可以视为0),标准差为1.0。然而,模型的准确率并不高,只有34%。
为什么会导致这样的结果?
与之前的MNIST数据集相比,CIFAR10数据集的分类增加了很多的挑战:
MNIST数据集 | CIFAR10数据集 | |
图像类型 | 灰度图像(单通道) | 彩色图像(三通道) |
图像尺寸 | 28*28 | 32*32 |
那么,现在我们就需要想办法来提高模型的准确率,在前面博客中提到过的直接决定KNN算法的准确性的两个因素:训练数据和测试数据中样本之间的距离和k值(“邻居”数量)的选择。
KNN算法的核心是通过选择周围的k个“邻居”,通过分别计算测试数据和这k个“邻居”样本之间的距离,选择所占比例最高的类别赋予预测数据。这里的距离计算仅考虑曼哈顿距离和欧式距离两种情况。那么,我们现在可以尝试选用欧式距离来替换曼哈顿距离作为距离度量。
- #仅需把上面的这一行替换即可
- y_test_pred = knn.predict(6,'E',X_test)
结果如下:
- Before centralization: Mean = 120.70756512369792
- Before centralization: Std Deviation = 64.15007589112135
- After centralization: Mean = -2.0733044910533257e-17
- After centralization: Std Deviation = 1.0
-
- Got 36 / 100 correct => accuracy: 0.360000
可以发现模型的准确率并没有多大的变化(还是较差),这时就可以转向考虑k值选择对准确性的影响。
在机器学习中,涉及很多的参数和超参数,实践中往往需要对于这些参数进行适当地选择和调整。对于KNN算法来说,选择合适的k值是实现最佳性能的关键步骤。那么怎么样才能保证所选取的k值就是最优解呢?
最常见的方法是使用交叉验证:将数据分成若干份,将其中的各份作为验证集(validation)后给出平均准确率,最后将评估得到的适合的参数在测试集中进行测试。
这一步需要做的是:将图像划分为5个部分,每个部分轮流作为验证集。
- import numpy as np
- import matplotlib.pyplot as plt
- from sklearn.model_selection import cross_val_score, StratifiedKFold
- from sklearn.neighbors import KNeighborsClassifier
- from sklearn.preprocessing import StandardScaler
- import dataset_loader # 确保这个模块已正确配置和可用
-
- # 加载数据
- X_train = dataset_loader.train_dataset.data[:1000] #考虑计算,选取部分图像
- X_train = X_train.reshape(X_train.shape[0], -1)
- y_train = dataset_loader.train_dataset.targets[:1000] #考虑计算,选取部分图像
- X_test = dataset_loader.test_dataset.data[:1000] #考虑计算,选取部分图像
- X_test = X_test.reshape(X_test.shape[0], -1)
- y_test= dataset_loader.test_loader.dataset.targets[:1000] #考虑计算,选取部分图像
- # 数据标准化
- scaler = StandardScaler()
- X_train = scaler.fit_transform(X_train)
- X_test = scaler.transform(X_test)
-
- # K值选择
- k_choices = [1, 3, 5, 7, 9, 12, 14, 16, 18, 20]
- k_to_accuracies = {}
-
- # 5折分层交叉验证
- kf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
-
- # 对每个K值进行交叉验证
- for k in k_choices:
- knn = KNeighborsClassifier(n_neighbors=k)
- # 计算交叉验证的准确率
- accuracies = cross_val_score(knn, X_train, y_train, cv=kf, scoring='accuracy')
- k_to_accuracies[k] = accuracies
-
- # 打印结果
- for k in sorted(k_to_accuracies):
- accuracies = k_to_accuracies[k]
- print(f'k = {k}, accuracy = {accuracies.mean():.2f} ± {accuracies.std():.2f}')
在很多实际应用中,通过经验发现k值在1到20之间时,模型表现往往较为均衡,能较好地处理不同的数据集和问题。实际上,许多研究和应用案例表明,这个范围内的k值往往能提供一个不错的起点,尤其是在初步探索数据时。
也可使用图形化代码展示k的选取与准确度趋势的关系:
- import matplotlib.pyplot as plt
- import numpy as np
-
- for k in k_choices:
- accurancies = k_to_accurancies[k]
- plt.scatter([k] * len(accurancies),accurancies)
-
- accurancies_mean = np.array([np.mean(v) for k,v in sorted(k_to_accurancies.items())])
- accurancies_std = np.array([np.std(v) for k,v in sorted(k_to_accurancies.items())])
- plt.errorbar(k_choices, accurancies_mean, yerr=accurancies_std)
- plt.title('Cross-validation on k')
- plt.xlabel('k')
- plt.ylabel('Cross-validation accuracy')
- plt.show()
输出结果如下:
- k = 1, accuracy = 0.24 ± 0.02
- k = 3, accuracy = 0.21 ± 0.03
- k = 5, accuracy = 0.24 ± 0.02
- k = 7, accuracy = 0.23 ± 0.02
- k = 9, accuracy = 0.22 ± 0.02
- k = 12, accuracy = 0.22 ± 0.01
- k = 14, accuracy = 0.22 ± 0.01
- k = 16, accuracy = 0.23 ± 0.02
- k = 18, accuracy = 0.23 ± 0.01
- k = 20, accuracy = 0.23 ± 0.02
图3 交叉验证准确率 |
从这张图中,我们可以观察到以下几点:
但注意的是:根据这张图,没有一个绝对的最优k值。通常情况下,选择“最好”的k值涉及寻找平均交叉验证准确率高且误差小的点。我们希望找到一个准确率相对较高且其对应的误差线(代表准确率变异性的垂直线)相对较短的k 值,因为这表明该k值不仅准确率高,而且模型表现稳定。
但在这张图像中,没有一个k值明显胜出。不过,我们可以观察到,当k值在5到10之间时,准确率的平均值似乎较高,且误差线较短。这可能表明这个范围内的k值能够提供较好的性能和稳定性。如果可以的话,可以进一步分析这些k值的准确率和方差,来确定一个最优的k。此外,如果数据集非常大,计算时间也可能是一个考虑因素。
使用KNN分类器处理CIFAR10数据集确实可能遇到一些挑战,特别是与一些更现代的、专门为图像识别设计的方法(如卷积神经网络)相比,其准确率效果会差很多。
这主要是因为以下几个原因:
改进策略:
尽管KNN分类器可能不是处理CIFAR10数据集的最佳选择,但如果你想使用它,可以考虑以下策略来改善性能:
然而,对于图像分类任务,尤其是像CIFAR10数据集这样的复杂数据集,深度学习方法(特别是卷积神经网络CNN)通常会提供更好的结果。CNN能够通过其多层结构学习图像的层次特征表示,通常在图像分类任务中表现优异。
[1]魏溪含, 涂铭, & 张修鹏. (2020). 深度学习与图像识别: 原理与实践. 北京: 机械工业出版社。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。