图像分类(二)—KNN算法实战之MNIST数据集(附代码)

目录

一、理论基础

1. 图像分类

2. 实现思路

3. 图像预处理

二、数据集准备

1. 数据集结构

2. 加载数据集

3. 图像可视化

三、数据预处理

1. 为什么要进行图像预处理?

2. 图像归一化

2.1  均值方差归一化

2.1.1  定义

2.1.2 实现思路及计算步骤

2.1.3 代码实现

2.2  最值归一化

2.2.1  定义

2.2.2 实现思路及计算步骤

2.2.3 代码实现

四、代码改进

1. KNN分类器

2. 图像可视化

3. 验证效果

五、参考资料


一、理论基础

KNN算法的原理已在上一篇博客详细论述,在这里不再展开。在之前的部分,我们实现了基于二维坐标点的分类,这是图像分类中的一个简单示例。现在,我们将进一步扩展这个概念,应用于更复杂的图像数据集,以实现实际的图像分类任务。

1. 图像分类

图像可以根据多种标准和特性进行分类。不同的分类方法反映了图像的不同属性和用途。常见的图像类型有彩色图像、灰度图像和二值图像。

2. 实现思路

图像分类的任务就是预测给定图像属于各类标签的的可能性,根据这些输出,模型将选择概率最高的标签作为预测结果。

3. 图像预处理

在使用算法前,一般都需要先对图像进行预处理,包括:归一化、灰度变换、滤波变换等等。

二、数据集准备

MNIST数据集包含来自大约250个不同人手写的数字,其中一半是美国人口调查局的员工,另一半是美国高中生。这些数字经过归一化和中心对齐处理。

1. 数据集结构

每张图像都是固定的28x28像素大小的灰度图像。并且,每张图像都有一个与之对应的标签,标签是0到9的数字,表示图像中手写数字的实际值。

训练集:包含60,000个样本,这些样本用于训练模型。

测试集:包含10,000个样本,这些样本用于测试模型的性能。

2. 加载数据集

在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])

3. 图像可视化

从第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个训练图像

三、数据预处理

1. 为什么要进行图像预处理?

如果我们对图像没有进行预处理,而是直接使用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分类器更公平、更准确。

2. 图像归一化

归一化是数据预处理中的一个关键步骤,尤其是在处理图像数据时。它有助于将图像数据的数值范围调整到一个标准化的尺度。通常,归一化可以按照缩放方式分为两类:

2.1  均值方差归一化

2.1.1  定义

均值方差归一化(也称为标准化或Z-score标准化)是一种广泛使用的数据预处理技术,特别是在机器学习和统计建模中。这种方法涉及调整特征的尺度,使其具有零均值和单位方差,这有助于许多算法的性能,特别是那些依赖于数据距离度量的算法,如:KNN算法,以及涉及梯度下降的训练方法,如:神经网络。

2.1.2 实现思路及计算步骤

均值方差归一化将原始数据先减去均值,然后除以标准差,可以得到均值为0,标准差为1的分布(并非一定为正态)。

  1. 减去均值:首先从每个数据点减去整个数据集的均值。这一步是为了中心化数据,将数据的中心移至原点,这样做可以消除数据原本可能存在的偏移。

  2. 除以标准差:然后将上一步得到的结果除以数据集的标准差。这一步是为了规模化数据,使得数据集的标准差为1。这样,数据集中的每个特征都将在同一尺度上,避免某些特征因变化范围大而对结果产生过大影响。

计算步骤:对于一个 M*N 的二维图像进行均值方差归一化时,每个像素点都将根据整个图像的像素值均值和标准差进行调整。

  1. 计算均值(Mean):首先计算整个图像的平均像素值,假设图像的像素值合集为\mathbf{\left \{ x_{ij}\right \}}​​​​​​​,其中\mathbf{i = 1,2,...,M}\mathbf{j = 1,2,...,N}

  2. \mathbf{\mu = \frac{1}{M * N} \sum_{i = 1}^{M}\sum_{j = 1}^{N} x_{ij}}

  3. 其中,\mathbf{\mu }指图像中所有像素值的平均值;\mathbf{M}\boldsymbol{\mathbf{N}}分别是图像的行数和列数。

  4. 计算方差(Variance)接着,计算图像像素值的标准差。

  5. \mathbf{\sigma^2 = \frac{1}{M * N} \sum_{i = 1}^{M}\sum_{j = 1}^{N}( x_{ij} -\mu )^2}

  6. 其中,\mathbf{\sigma ^2}指方差(各数据与其平均数之差的平方和的平均数)。

  7. 计算标准差(Standard Deviation):然后,计算图像像素值相对于均值的标准差。

  8. \mathbf{\sigma = \sqrt{\frac{1}{M * N} \sum_{i = 1}^{M}\sum_{j = 1}^{N}( x_{ij} -\mu )^2}}

  9. 其中,\mathbf{\sigma }指标准差(方差的平方根)。

  10. 均值方差归一化公式:将每个像素值转换为具有零均值和单位方差的新值。

\mathbf{x_{new} = \frac{X_{ij} - \mu }{\sigma }}​​​​​​​

          这里,\mathbf{x_{ij}}指原始数据点的像素值;\mathbf{x_{new}}是归一化后数据的像素值。

2.1.3 代码实现

在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)

2.2  最值归一化

2.2.1  定义

最值归一化(也称为Min-Max Scaling)是一种常用的数据预处理技术,用于将特征的尺度调整到一个指定的范围内,通常是0到1之间,或者是-1到1之间。这种方法特别适用于不假定数据遵循正态分布的情况下。

2.2.2 实现思路及计算步骤

最值归一化通过将特征的实际范围(最小值到最大值)缩放到一个标准范围内,通常是[0,1]或者[-1,1]。这使得不同特征之间的比较和权重更加公平和一致。

计算步骤:对于一个 M*N 的二维8图像,最值归一化将每个像素的值重新缩放,使所有像素值均位于0到1或者-1到1之间。

\mathbf{x_{new} = \frac{X_{ij} - x_{min}}{x_{max}-x_{min}}}

或者:

\mathbf{x_{new} = \frac{X_{ij} - \mu }{x_{max}-x_{min}}}

2.2.3 代码实现

在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来说,由于它对数据的尺度敏感,选择均值方差归一化通常是更好的选择。

1. 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

2. 图像可视化

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,确保数据的尺度一致。

3. 验证效果

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). 深度学习与图像识别: 原理与实践. 北京: 机械工业出版社。

[2]THE MNIST DATABASE.

  • 44
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值