赞
踩
目录
KNN算法的原理已在上一篇博客详细论述,在这里不再展开。在之前的部分,我们实现了基于二维坐标点的分类,这是图像分类中的一个简单示例。现在,我们将进一步扩展这个概念,应用于更复杂的图像数据集,以实现实际的图像分类任务。
图像可以根据多种标准和特性进行分类。不同的分类方法反映了图像的不同属性和用途。常见的图像类型有彩色图像、灰度图像和二值图像。
图像分类的任务就是预测给定图像属于各类标签的的可能性,根据这些输出,模型将选择概率最高的标签作为预测结果。
在使用算法前,一般都需要先对图像进行预处理,包括:归一化、灰度变换、滤波变换等等。
MNIST数据集包含来自大约250个不同人手写的数字,其中一半是美国人口调查局的员工,另一半是美国高中生。这些数字经过归一化和中心对齐处理。
每张图像都是固定的28x28像素大小的灰度图像。并且,每张图像都有一个与之对应的标签,标签是0到9的数字,表示图像中手写数字的实际值。
训练集:包含60,000个样本,这些样本用于训练模型。
测试集:包含10,000个样本,这些样本用于测试模型的性能。
在PyTorch中,可以直接使用torchvision.datasets.MNIST
来加载MNIST手写数字数据集。在这里,设置batch_size = 100,即每次迭代中使用的样本数量为100。需要注意的是:在加载数据前,需要将图像转化为张量,确保数据与PyTorch库的兼容性,使得可以方便地在图像上应用各种算法和操作。
MNIST_dataset_loader.py
- import torch
- from torch.utils.data import DataLoader
- import torchvision.datasets as dsets
- import torchvision.transforms as transforms
-
- #指定每次训练迭代的样本数量
- batch_size = 100
- transform = transforms.ToTensor() #将图片转化为PyTorch张量
-
- train_dataset = dsets.MNIST(root= '指定路径/pymnist',
- train= True,
- transform=transforms.ToTensor(),
- download=True)
- test_dataset = dsets.MNIST(root= '指定路径/pymnist',
- 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)
通过打印训练和测试数据集的数据和标签的尺寸,输出数据集及其对应标签的维度信息,这对于数据结构的把握非常重要。
- print("train data:",train_dataset.train_data.size())
- print("train labels:",train_dataset.train_labels.size())
- print("test data:",test_dataset.test_data.size())
- print("test labels:",test_dataset.test_labels.size())
输出结果为:
- train data: torch.Size([60000, 28, 28])
- train labels: torch.Size([60000])
- test data: torch.Size([10000, 28, 28])
- test labels: torch.Size([10000])
从第100个训练图像(因为索引从0开始),并且打印出该图像对应的标签:
MNIST_show.py
- import matplotlib.pyplot as plt
- import MNIST_dataset_loader
-
- train = MNIST_dataset_loader.train_loader.dataset.train_data[99]
- plt.imshow(train, cmap=plt.cm.binary)
- plt.show()
- print(MNIST_dataset_loader.train_loader.dataset.train_labels[99])
输出结果:
tensor(1)
图1 MNIST数据集中第100个训练图像 |
如果我们对图像没有进行预处理,而是直接使用KNN分类器来验证MNIST数据集的准确性,得到的结果为:
KNNClassifier.py
- import numpy as np
- import operator
-
- # KNN分类器构建
- 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
- import numpy as np
- import MNIST_dataset_loader
- from KNNClassifier import KNNClassifier
-
- if __name__ == '__main__':
- X_train = MNIST_dataset_loader.train_loader.dataset.train_data.numpy() # 转化为numpy
- X_train = X_train.reshape(X_train.shape[0], 28 * 28)
- y_train = MNIST_dataset_loader.train_loader.dataset.train_labels.numpy()
-
- X_test = MNIST_dataset_loader.test_loader.dataset.test_data[:1000].numpy()
- X_test = X_test.reshape(X_test.shape[0], 28 * 28)
- y_test = MNIST_dataset_loader.test_loader.dataset.test_labels[:1000].numpy()
-
- num_test = y_test.shape[0]
-
- knn = KNNClassifier()
- knn.fit(X_train, y_train)
-
-
- y_test_pred = knn.predict(5, '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))
运行结果如下:
Got 368 / 1000 correct => accuracy: 0.368000
在这个任务中,分类器在1000个样本的测试集上只能正确分类了368个样本,从而得到了36.8%的准确率。(当然如果选择不同的k值和距离度量会带来不同的结果,但总体的准确率都不会太高。)KNN算法在计算距离时对特征的尺度非常敏感。如果图像的尺寸或像素值范围(亮度或颜色深度)不统一,可能会导致距离计算偏向于尺度较大的特征。如:将所有图像归一化到同一尺寸和/或将像素值标准化到同一范围(如0到1),可以确保不同的特征对距离的贡献均衡,从而使KNN分类器更公平、更准确。
归一化是数据预处理中的一个关键步骤,尤其是在处理图像数据时。它有助于将图像数据的数值范围调整到一个标准化的尺度。通常,归一化可以按照缩放方式分为两类:
均值方差归一化(也称为标准化或Z-score标准化)是一种广泛使用的数据预处理技术,特别是在机器学习和统计建模中。这种方法涉及调整特征的尺度,使其具有零均值和单位方差,这有助于许多算法的性能,特别是那些依赖于数据距离度量的算法,如:KNN算法,以及涉及梯度下降的训练方法,如:神经网络。
均值方差归一化将原始数据先减去均值,然后除以标准差,可以得到均值为0,标准差为1的分布(并非一定为正态)。
减去均值:首先从每个数据点减去整个数据集的均值。这一步是为了中心化数据,将数据的中心移至原点,这样做可以消除数据原本可能存在的偏移。
除以标准差:然后将上一步得到的结果除以数据集的标准差。这一步是为了规模化数据,使得数据集的标准差为1。这样,数据集中的每个特征都将在同一尺度上,避免某些特征因变化范围大而对结果产生过大影响。
计算步骤:对于一个 M*N 的二维图像进行均值方差归一化时,每个像素点都将根据整个图像的像素值均值和标准差进行调整。
计算均值(Mean):首先计算整个图像的平均像素值,假设图像的像素值合集为,其中,。
其中,指图像中所有像素值的平均值; 和分别是图像的行数和列数。
计算方差(Variance):接着,计算图像像素值的标准差。
其中,指方差(各数据与其平均数之差的平方和的平均数)。
计算标准差(Standard Deviation):然后,计算图像像素值相对于均值的标准差。
其中,指标准差(方差的平方根)。
均值方差归一化公式:将每个像素值转换为具有零均值和单位方差的新值。
这里,指原始数据点的像素值;是归一化后数据的像素值。
在Python中,可以使用NumPy库来非常简单地实现这一过程:
- import numpy as np
-
- # 假设data是一个NumPy数组,包含了我们需要标准化的数据
- data = np.array([1, 2, 3, 4, 5])
-
- # 计算均值
- mean = np.mean(data)
-
- # 计算标准差
- std = np.std(data)
-
- # 进行标准化
- normalized_data = (data - mean) / std
-
- print("原始数据:", data)
- print("标准化数据:", normalized_data)
最值归一化(也称为Min-Max Scaling)是一种常用的数据预处理技术,用于将特征的尺度调整到一个指定的范围内,通常是0到1之间,或者是-1到1之间。这种方法特别适用于不假定数据遵循正态分布的情况下。
最值归一化通过将特征的实际范围(最小值到最大值)缩放到一个标准范围内,通常是[0,1]或者[-1,1]。这使得不同特征之间的比较和权重更加公平和一致。
计算步骤:对于一个 M*N 的二维8图像,最值归一化将每个像素的值重新缩放,使所有像素值均位于0到1或者-1到1之间。
或者:
在Python中,也可以使用NumPy库来非常简单地实现这一过程:
- import numpy as np
-
- # [0,1]最值归一化公式实现
- def min_max_scaling(image):
- min_val = np.min(image)
- max_val = np.max(image)
- scaled_image = (image - min_val) / (max_val - min_val)
- return scaled_image
-
- # 假设 image 是一个 M*N 的二维图像矩阵
- image = np.array([[100, 150, 100], [120, 100, 130], [110, 120, 90]])
- scaled_image = min_max_scaling(image)
-
- print("Scaled Image:\n", scaled_image)
或者:
- import numpy as np
-
- #[-1,1]最值归一化公式实现
- def min_max_mean_scaling(image):
- min_val = np.min(image)
- max_val = np.max(image)
- mean_val = np.mean(image)
- scaled_image = (image - mean_val) / (max_val - min_val)
- return scaled_image
-
- # 假设 image 是一个 M*N 的二维图像矩阵
- image = np.array([[100, 150, 100], [120, 100, 130], [110, 120, 90]])
- scaled_image = min_max_mean_scaling(image)
-
- print("Scaled Image:\n", scaled_image)
KNN算法是基于距离度量(如欧氏距离或曼哈顿距离)来确定每个测试点的“邻居”,因此确保所有特征具有相似的尺度是至关重要的。对于KNN来说,由于它对数据的尺度敏感,选择均值方差归一化通常是更好的选择。
下面是在先前的代码基础上添加了均值方差归一化模块的实现过程:
KNNClassifier.py
- import numpy as np
- import operator
-
- # KNN分类器构建
- class KNNClassifier:
- --snip--
-
- def standardize_image(image): #均值方差归一化
- mean = np.mean(image)
- std = np.std(image)
- return (image - mean) / std
MNIST_show.py
- import matplotlib.pyplot as plt
- import numpy as np
- import MNIST_dataset_loader
- from KNNClassifier import standardize_image
-
- # 图像(归一化)
- train = MNIST_dataset_loader.train_loader.dataset.train_data[:1000].numpy()
- digit_01 = train[33]
- digit_02 = standardize_image(digit_01)
- plt.imshow(digit_01, cmap=plt.cm.binary)
- plt.show()
- plt.imshow(digit_02, cmap=plt.cm.binary)
- plt.show()
- print(MNIST_dataset_loader.train_loader.dataset.train_labels[33])
- print("Before standardization: mean = {}, std = {}".format(np.mean(digit_01), np.std(digit_01)))
- print("After standardization: mean = {}, std = {}".format(np.mean(digit_02), np.std(digit_02)))
输出结果:
- tensor(9)
- Before standardization: mean = 27.007653061224488, std = 70.88341925375865
- After standardization: mean = 5.890979314337566e-17, std = 1.0
图2 第34个训练图像(归一化前) | 图2 第34个训练图像(归一化后) |
虽然归一化前后的图像在视觉上似乎并无明显的差异,但通过打印归一化前后的像素值平均值和标准差可以发现标准化后,数据的平均值为5.890979314337566e-17,接近0(浮点数计算的微小误差,这在数值计算中可以视为0),这是标准化的预期结果,旨在将数据的均值中心化到0;标准差为1.0,确保数据的尺度一致。
test_KNN.py
- import numpy as np
- from KNNClassifier import KNNClassifier,standardize_image
- import MNIST_dataset_loader
-
- if __name__ == '__main__':
- #训练数据
- X_train = MNIST_dataset_loader.train_loader.dataset.train_data.numpy() #转化为numpy
- X_train = X_train.reshape(X_train.shape[0], 28 * 28)
- X_train = standardize_image(X_train) #均值方差归一化处理
- y_train = MNIST_dataset_loader.train_loader.dataset.train_labels.numpy()
-
- #测试数据
- X_test = MNIST_dataset_loader.test_loader.dataset.test_data[:1000].numpy()
- X_test = X_test.reshape(X_test.shape[0], 28 * 28)
- X_test = standardize_image(X_test)
- y_test = MNIST_dataset_loader.test_loader.dataset.test_labels[:1000].numpy()
-
- num_test = y_test.shape[0]
-
- knn = KNNClassifier()
- knn.fit(X_train,y_train)
-
- # y_test_pred = kNN_classify(5,'M',X_train,y_train,X_test)
- y_test_pred = knn.predict(5,'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))
运行结果如下:
Got 950 / 1000 correct => accuracy: 0.950000
改进后的KNN算法在MNIST测试集上正确识别了1000个样本中的950个,模型的准确率为95%。这是一个很高的准确率(相比未归一化处理),通常表明分类器表现得相当好。
[1]魏溪含, 涂铭, & 张修鹏. (2020). 深度学习与图像识别: 原理与实践. 北京: 机械工业出版社。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。