参考这篇文章
http://t.csdnimg.cn/59vrG
使用的数据集Kaggle Cats and Dogs Dataset
小白写文章
基于机器学习的动物图像分类处理
基于机器学习的动物图像分类是一种利用机器学习算法和技术来自动识别和分类不同动物图像的方法。该方法可以通过训练一个机器学习模型来学习动物的特征和模式,并根据这些特征和模式来判断输入图像属于哪种动物。
动物图像分类通常包括以下步骤:
1.数据收集:收集包含不同动物类别的大量图像数据集,这些图像数据集应涵盖各种动物种类和样本。
2.数据预处理:对图像数据进行预处理,如调整图像大小、裁剪、归一化等,以便于后续的特征提取和学习。
3.特征提取:使用计算机视觉技术和特征提取算法,从图像数据中提取有代表性的特征。这些特征可以包括颜色直方图、纹理特征、形状特征等。
4.模型训练:将提取的特征作为输入,使用机器学习算法来训练一个分类模型。常用的机器学习算法包括支持向量机(SVM)、随机森林(Random Forest)、决策树(Decision Tree)和K近邻算法(KNN)。
5.模型评估:使用预留的测试数据集来评估模型的准确性和性能。常用的评估指标包括准确率、精确率、召回率、F1分数等。
6.模型应用:将训练好的模型应用到新的未知图像上,通过判断其属于哪种动物类别来实现动物图像的自动分类。值得注意的是,机器学习的结果取决于训练数据的质量和数量,因此收集充足和多样化的图像数据对于获得准确的分类结果非常重要。同时,由于动物图像具有一定的复杂性和变化性,可能需要使用更高级的模型或采用深度学习方法,如卷积神经网络(CNN)来提升分类的准确性。
一.KNN算法介绍
K近邻算法(K-Nearest Neighbors,KNN)是一种基本的机器学习算法,也是一种分类和回归方法。它的原理很简单,即根据特征相似度来进行分类或回归预测。在KNN算法中,首先需要给定一个训练集,其中包含了已知分类标签的样本数据。然后,通过计算待预测样本与训练集中各个样本的特征距离,选择与待预测样本最相似的K个训练样本(即K个最近邻),并根据这K个并据这K个最近邻样本的分类标签来对待预测样本进行分类或回归预测。
使用步骤
1.收集数据:收集训练样本数据,包括输入特征和对应的类别标签。
2.选择距离度量:确定距离的度量方式,如欧氏距离、曼哈顿距离等。这取决于具体问题和数据的性质。
3.处理数据:对数据进行预处理,包括归一化、标准化或其他必要的数据清洗步骤,以确保不同特征对距离计算的影响相同。
4.选择K值:
K值的选择是KNN算法的关键决策。较小的K值会使模型更复杂,对噪声更敏感;较大的K值会使模型更平滑,但可能忽略局部特征。通常,K值的选择通过交叉验证等方法确定。
5.计算距离: 对于每个未知样本,计算它与训练集中每个样本的距离。
常用的距离度量包括欧氏距离(Euclidean Distance)、曼哈顿距离(Manhattan Distance)、闵可夫斯基距离等。
(1)欧氏距离是最常用的距离度量,它衡量的是两个点之间的直线距离。
公式:
其中,x和y分别是两个样本点,xi和yi分别是样本点的特征值,n是特征的数量。
(2)曼哈顿距离(Manhattan Distance):
曼哈顿距离是沿着坐标轴的网格线测量的距离,也被称为“城市街区距离”。
公式:
(3)闵可夫斯基距离(Minkowski Distance):
闵可夫斯基距离是欧氏距离和曼哈顿距离的一种泛化形式,其公式为:
这些距离度量的选择通常取决于具体问题的性质和数据的特征。在实际应用中,我们需要根据数据的分布和特点选择适当的距离度量方式。
在这个公式中,p是一个可调参数,当p=2时即为欧氏距离,当p=1时为曼哈顿距离。
6.找到最近邻:根据计算的距离,选择与未知样本距离最近的K个样本。
7.投票决策:统计这K个最近邻样本中各类别的数量,选择数量最多的类别作为未知样本的预测类别。
8.输出结果:输出预测类别作为未知样本的分类结果。
二.
数据集Kaggle Cats and Dogs Dataset
开发环境
开发环境 | 开发配置 |
操作系统 | Window 11 |
CPU | AMD Ryzen 5 5600H with Radeon Graphics 3.30 GHz |
编程语言 | Python |
开发环境 | pycharm-community-2023.3.2 |
三. Knn实现动物图像分类
我建立了SimplePreprocessor.py,Simpledatasetloader.py,knn.py三个文件
1.SimplePreprocessor.py文件:
根据尺寸,把输入图像压缩,因为KNN要把多个样品读取到RAM中,以便对测试样品进行分类。因此,先把所有读取的图像进行压缩,便于读取到有限的RAM中,同时也能减少判断算法的执行时间。
2.Simpledatasetloader.py:用于加载图像数据集并应用预处理
3.knn.py是核心代码,使用KNN算法进行动物图像分类,并包含了对分类结果的可视化。
(1)使用 K-NN 分类器对图像数据集进行训练和评估。
(2)调用 SimplePreprocessor 类和 SimpleDatasetLoader 类对图像进行预处理和加载。
(3)将图像数据划分为训练集和测试集。
(4)使用 K-NN 分类器(KNeighborsClassifier)对训练集进行训练。
(5)针对测试集生成分类性能的指标,包括准确度(accuracy)、精确度(precision)、召回率(recall)和 F1 分数。
(6)将这些性能指标保存为表格图片,其中每一列对应一个类别,每一行对应一个性能指标。
SimplePreprocessor.py文件
SimplePreprocessor.py文件
#这段代码是根据尺寸,把输入图像压缩。因为KNN要把多个样品读取到RAM中,以便对测试样品进行分类。
# 因此,先把所有读取的图像进行压缩,便于读取到有限的RAM中,同时也能减少判断算法的执行时间。
import cv2
# 定义 SimplePreprocessor 类
class SimplePreprocessor:
# 初始化方法,设置图像预处理的宽度、高度和插值方法,默认插值方法为 cv2.INTER_AREA
def __init__(self, width, height, inter=cv2.INTER_AREA):
self.width = width # 将宽度参数赋值给对象的属性 self.width
self.height = height # 将高度参数赋值给对象的属性 self.height
self.inter = inter # 将插值方法参数赋值给对象的属性 self.inter
# 定义预处理方法,接受一个图像作为输入,调用 OpenCV 的 resize 函数对图像进行调整大小
def preprocess(self, image):
return cv2.resize(image, (self.width, self.height), interpolation=self.inter)
# print(image.size) # (注释掉的代码)打印图像的大小
# 如果脚本作为主程序运行
if __name__ == '__main__':
# 创建 SimplePreprocessor 类的实例,设置图像预处理的宽度和高度为 32 像素
s = SimplePreprocessor(32, 32)
# 读取一张图像文件
img = cv2.imread(r'E:\project\pythonProject\knnClassifier\kagglecatsanddogs\Cat\1.jpg')
# 使用 OpenCV 的 imshow 函数显示原始图像,窗口名称为 'src'
cv2.imshow('src', img)
# 使用 SimplePreprocessor 类的 preprocess 方法对图像进行预处理,然后使用 imshow 函数显示处理后的图像,窗口名称为 'resize'
cv2.imshow("resize", s.preprocess(img))
# 等待按键事件
cv2.waitKey(0)
# 关闭所有 OpenCV 窗口的注释代码
# cv2.destroyallWindows()
SimpleDatasetLoader文件
#SimpleDatasetLoader.py文件
import numpy as np
import cv2
import os
#用于加载图像数据集并应用预处理
class SimplePreprocessor:
# 初始化方法,接受 width、height 和 inter 三个参数,默认插值方法为 cv2.INTER_AREA
def __init__(self, width, height, inter=cv2.INTER_AREA):
self.width = width # 将参数 width 赋值给对象的属性 self.width
self.height = height
self.inter = inter
# 定义名为 preprocess 的方法,用于图像预处理,接受 image 作为输入
def preprocess(self, image):
# 使用 OpenCV 的 resize 函数调整图像大小,参数为 image、(width, height) 和插值方法
return cv2.resize(image, (self.width, self.height), interpolation=self.inter)
class SimpleDatasetLoader:
# 初始化方法,接受 preprocessors 参数,默认为 None
def __init__(self, preprocessors=None):
self.preprocessors = preprocessors
# 如果 self.preprocessors 为 None,则将其设置为空列表 []
if self.preprocessors is None:
self.preprocessors = []
# 定义 load 方法,用于加载图像数据集
def load(self, imagePaths, verbose=-1):
data = [] # 存储图像数据的列表
labels = [] # 存储图像标签的列表
# print(imagePaths)
for (i, imagePath) in enumerate(imagePaths):
image = cv2.imread(imagePath) # 读取图像文件
label = imagePath.split(os.path.sep)[-2]# 从图像路径中提取标签信息
# 如果存在预处理器,则对图像进行预处理
#用于删除数据集中无法识别的文件,
if self.preprocessors is not None:
for p in self.preprocessors:
if(image is None):# 如果图像读取失败
print(i)
os.remove(imagePaths[i])# 移除读取失败的图像文件
print('file: ')
print(imagePaths[i])
print('is removed.')
continue
image = p.preprocess(image)# 应用预处理器对图像进行处理
data.append(image)# 将处理后的图像添加到数据列表
labels.append(label) # 将标签添加到标签列表
# 如果启用了输出信息,并且达到了指定的 verbose 阈值,则打印处理进度信息
if verbose > 0 and i > 0 and (i + 1 ) %verbose == 0:
print('[INFO] processed {}/{}'.format( i +1, len(imagePaths)))
return (np.array(data), np.array(labels)) # 将数据列表和标签列表转换为 NumPy 数组并返回
if __name__ == '__main__':
# 设置图像文件夹的路径
imagePaths = r'E:\project\pythonProject\knnClassifier'
# 创建 SimplePreprocessor 类的实例,设置图像预处理的宽度和高度为 32 像素
sp = SimplePreprocessor(32, 32)
# 创建 SimpleDatasetLoader 类的实例,传入预处理器列表,该列表包含上面创建的 SimplePreprocessor 实例
sdl = SimpleDatasetLoader(preprocessors=[sp])
# (data, labels) = sdl.load(imagePaths, verbose=10)
# data = data.reshape((data.shape[0], 3072))
knn.py文件(核心代码)
#knn.py文件
#构建一个K-NN(k最近邻)分类器,并使用该分类器对输入的图像数据集进行分类。
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from simplepreprocessor import SimplePreprocessor
from simpledatasetloader import SimpleDatasetLoader
from imutils import paths
import argparse
if __name__ == '__main__':
# 创建 ArgumentParser 对象,用于解析命令行参数
ap = argparse.ArgumentParser()
# 添加命令行参数,指定输入数据集路径
ap.add_argument("-d", "--dataset", required=True, help="path to input dataset")
# 添加命令行参数,指定 K-NN 分类器中的最近邻个数,默认为 1
ap.add_argument("-k", "--neighbors", type=int, default=1, help="of nearest neighbors for classification")
# 添加命令行参数,指定 K-NN 分类器中的并行任务数,默认使用所有可用的 CPU 核心
ap.add_argument("-j", "--jobs", type=int, help="of jobs for K-NN distance (-1 uses all variables cores)")
# 解析命令行参数
args = vars(ap.parse_args())
# 打印提示信息,开始加载图像数据
print("[INFO] loading images...")
# 获取输入数据集中的图像路径列表
imagePaths = list(paths.list_images(args["dataset"]))
# 创建 SimplePreprocessor 类的实例,设置图像预处理的宽度和高度为 32 像素
sp = SimplePreprocessor(32, 32)
# 创建 SimpleDatasetLoader 类的实例,传入预处理器列表,该列表包含上面创建的 SimplePreprocessor 实例
sdl = SimpleDatasetLoader(preprocessors=[sp])
# 调用 SimpleDatasetLoader 类的 load 方法,加载图像数据,并打印加载进度信息
(data, labels) = sdl.load(imagePaths, verbose=100)
# 将数据的形状调整为 (样本数, 特征数)
data = data.reshape((data.shape[0], 3072))
# 打印提示信息,显示特征矩阵占用的内存大小
print("[INFO] features matrix:{:.1f}MB".format(data.nbytes / (1024*1000.0)))
# 创建 LabelEncoder 类的实例,用于对标签进行编码
le = LabelEncoder()
# 调用 LabelEncoder 类的 fit_transform 方法,对标签进行编码
labels = le.fit_transform(labels)
# 使用 train_test_split 函数划分训练集和测试集
(trainX, testX, trainY, testY) = train_test_split(data, labels, test_size=0.25, random_state=42)
# 打印提示信息,评估 K-NN 分类器
print("[INFO] evaluating K-NN classifier...")
#创建了一个 KNeighborsClassifier 类的实例,设定了最近邻个数为3,然后使用训练集 (trainX, trainY) 对分类器进行训练。
model = KNeighborsClassifier(n_neighbors=3)
model.fit(trainX, trainY)
# 打印分类报告
print(classification_report(testY, model.predict(testX), target_names=le.classes_))
1.时间复杂度:
(1)训练阶段
时间复杂度:O(1)(实际上是存储训练数据),因为 K-NN 在训练阶段只需要存储训练数据,而不进行显式的训练计算。
(2)预测阶段
时间复杂度:O(N*M*K*D),其中N是训练集的大小,M是特征数,K是最近邻的数量,D是特征的维度。计算与所有训练样本的距离需要O(N*M* D),选择最近的K个邻居需要O(K*logN)(使用优先队列等数据结构进行加速)。因此,总体时间复杂度是O(N*M*D+K*logN)。
2.空间复杂度:
(1)训练阶段
空间复杂度:O(N*M),其中N是训练集的大小,M是特征数。需要存储训练样本的特征。
(2)预测阶段
空间复杂度:O(K*M),因为需要存储最近的K个邻居的特征。在实践中,通常K远小于N。
结合上述时间复杂度和空间复杂度,可以从以下几个方面进行改进:
1.图像加载和预处理阶段:
通过使用 SimplePreprocessor 类,你对图像进行了调整大小的预处理,这在一定程度上降低了图像处理的时间复杂度。然而,如果处理大规模数据集,仍可以考虑实现更加灵活的惰性加载机制,以降低初始内存占用。
2.数据集加载与预处理的并行化:
在数据集加载和预处理阶段,没有引入明显的多线程或异步加载。对于大规模数据集,你可以考虑使用并行加载机制以提高整体处理效率,同时需要注意可能引入的额外内存开销。
3.K-NN 模型训练和评估阶段:
代码使用了KNeighborsClassifier进行模型训练和评估。为了进一步优化时间复杂度,可以考虑使用基于树结构的 K-NN 方法,如 KD-Tree 或 Ball Tree,以降低搜索时间复杂度。
4.异常处理和日志记录:
代码中有对于异常的处理,尽管在实际应用中,可能需要更详细的异常类型和处理方式,以确保在异常情况下不会对性能产生显著影响。
5.模型评估结果可视化:
使用 save_metrics_as_image 函数将分类报告以表格形式保存为图像。这一部分代码已经比较清晰,但在保存图像时,可能可以进一步优化文件格式或使用更高效的图像保存方式。
6.内存占用和资源释放:
代码中有释放资源的部分,但在处理大规模数据时,需要确保及时释放不再需要的资源,防止内存泄漏。
总体来说,代码已经很好地考虑了大规模数据集的处理,并且在模型评估结果可视化方面也有不错的实现。进一步的优化可以根据具体场景的需求和性能要求进行,可能的改进点主要集中在数据加载的并行化、K-NN模型的进一步优化,以及更详细的异常处理。
实现结果图
KNN算法性能指标
图像压缩
K近邻算法实验问题与改善方法
1.遇到无法成功安装OpenCV的问题,用国内镜像源解决了安装问题,成功安装了OpenCV。
查找资料得到:如果通过使用国内镜像源成功安装了OpenCV,那么这说明是通过清华大学的镜像源解决了与国际服务器通信的问题。这是一个常见的解决方案,特别是在中国或其他地区,由于网络问题,可能导致从国际服务器下载包时出现连接问题。
通过运行以下命令使用清华大学的镜像源进行安装:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-python
此命令将pip配置为使用清华大学的镜像源而不是默认的国际源。这通常能够提高安装成功的概率,因为国内镜像源通常在网络连接方面更加稳定。
2.k近邻算法对动物图像分类很难可视化,分析了一下原因:K近邻算法在图像分类中难以直观可视化的主要原因是图像数据通常是高维的,而K近邻算法是基于样本之间的距离进行分类的。在高维空间中,直接展示数据点并理解它们之间的关系变得非常困难。
以下是一些导致K近邻算法在图像分类中难以可视化的原因:
(1).高维度的特征空间: 图像通常由大量像素组成,每个像素都可以被视为一个特征。这导致了高维的特征空间,对于人类来说难以直观理解。
(2)计算成本: 在高维空间中计算距离变得更加昂贵,而K近邻算法的效率受到样本数和特征数的影响。因此,在高维空间中使用K近邻算法可能会变得非常耗时。
(3)维度灾难: 在高维空间中,数据点之间的距离可能会变得非常稀疏,这被称为维度灾难。这会导致K近邻算法失去其在低维空间中的效果。
为了在图像分类中更好地可视化K近邻算法的效果,通常需要采用一些降维技术,比如主成分分析(PCA)或 t-分布邻域嵌入(t-SNE),将数据降维到二维或三维空间,使得我们能够更容易地观察到数据点之间的关系。此外,对于图像数据,还可以使用卷积神经网络(CNN)等更高级的模型,它们在学习特征表示方面表现更好。
实验补充
1.优点:
KNN是一种直观的算法,易于理解和实现,它没有复杂的模型结构和参数调整过程,可用于解决多类别问题,而不需要进行复杂的修改;无需显式的训练过程。在类别分布较为平衡的情况下,KNN表现良好。
2.缺点:
在预测时,KNN需要计算未知样本与所有训练样本的距离,因此计算复杂度较高,尤其是在大规模数据集上,KNN对异常值非常敏感,异常值可能对预测结果产生较大影响;KNN需要保存整个训练数据集,因此对存储空间的要求较高;KNN的性能受K值的选择影响较大,选择较小的K值可能对噪声敏感,而选择较大的K值可能导致模型过于平滑;在高维空间中,KNN的性能可能下降。高维数据中的距离计算变得复杂,且样本稀疏性增加,影响了邻近性的准确度。