(一)、选题背景
机器学习作为人工智能领域的重要分支,在图像分类和识别方面发挥着巨大的作用。而在自然界中,猫科动物一直以来都是备受人们关注和喜爱的对象。本文将介绍机器学习在十大大型猫科动物分类上的应用。
猫科动物是一类身体结构相似、体型较大的哺乳动物,其中包括了非洲豹(African Leopard)、山猫(Caracal)、猎豹(Cheetah)、云豹(Cloud Leopard)、美洲豹(Jaguar)、狮子(Lion)、豹猫(Ocelot)、美洲狮(Puma)、雪豹(Snow Leopard)和老虎(Tiger)等各具特色的物种。这些猫科动物在外貌上存在一定的相似性,但也有着明显的区别,如皮肤颜色、斑点大小和身体特征等。
通过机器学习算法,我们可以利用计算机对这些猫科动物的图像进行分析和分类,并准确地将其归类到对应的物种中。这对于野生动物保护、生态研究和动物行为观察等领域具有重要的意义。通过机器学习技术,我们可以更好地了解这些大型猫科动物的生态习性、栖息地以及数量统计等重要信息,从而为它们的保护提供科学依据。
本文将介绍使用机器学习算法对十大大型猫科动物进行分类的方法和步骤,包括数据集的获取和准备、模型的选择和训练、图像特征提取和分类预测等内容。同时,也将探讨在实际应用中可能遇到的挑战和解决方案,并展望机器学习在野生动物研究和保护中的未来发展趋势。
通过本文的阅读,读者可以深入了解机器学习在猫科动物分类方面的应用,了解现有的技术和成果,并对其在实际应用中的潜力和局限性有所认识。同时,也可以进一步拓展机器学习在其他生物分类和图像识别领域的应用前景,为相关研究和工作提供参考和启示。
(二)、分析设计方案(10 分)
1.本数据集的数据内容与数据特征分析
数据集被分为训练集、验证集和测试集。它包含10个类别,分别是非洲豹(African Leopard)、山猫(Caracal)、猎豹(Cheetah)、云豹(Cloud Leopard)、美洲豹(Jaguar)、狮子(Lion)、豹猫(Ocelot)、美洲狮(Puma)、雪豹(Snow Leopard)和老虎(Tiger)。每个类别有10张图像,并且被划分为训练集、验证集和测试集。
非洲豹: 非洲豹的长度约为1.9米。通常体重在60至90公斤之间,而雌性仅约40公斤。非洲豹的主要特征是其皮肤颜色。它呈黄色,上面装饰着几乎完美圆形的小斑点,这些斑点的颜色比皮肤要深。这些动物的纹理相当粗壮,并在身体各处分布着斑点。这些斑点在整个身体上的大小并不稳定。它们的大小会因为吻部相对较小而大大不同。身体的下半部是白色的,其他部分是非常浅的黄色。这些颜色有助于它们在非洲草原中隐藏起来。
山猫: 山猫有着短而浓密的毛皮,颜色包括黄褐色(最常见)、红砖色和(很少见)黑色。山猫最独特的特征是它的耳朵。每只耳朵顶部都有一束长长的黑色毛发,大约长1.5英寸(4厘米)。山猫的下腹部、喉咙和下巴是白色的,在内侧大腿和腹部上有淡红色的斑点。从眼睛到鼻子的位置有一条窄窄的黑色条纹,额头中间也有一条。山猫的眼睛是黄褐色的,不像其他野生猫科动物那样呈缝隙状。雄性山猫的体重可达40磅(18公斤),而雌性的平均体重稍轻,约为35磅(16公斤)。从肩膀开始,这些猫的身高约为16-20英寸(40-50厘米),长度可达35-39英寸(88-99厘米)。
云豹 云豹,又称为云豹、长叶豹或科学名称雾豹(Neofelis nebulosa),是一种与其他猫科动物非常相似的哺乳动物。在中国,它也被称为心灵豹,因为它的斑点与植物的叶子相似。云豹的身长在60至110厘米之间,体重在11至20公斤(雌性)或65公斤(雄性)之间变化。云豹的皮毛有着不规则的黑色斑点,内部呈棕色,非常适合融入其所生活的环境。事实上,正是这些斑点赋予了它名字,因为它们看起来就像是一团团色彩的云朵。它有着狭长的头骨,但相当长,耳朵和眼睛也很显著,耳朵是圆形而短小的,眼睛的颜色在绿色、黄色和灰色之间变化。
美洲豹 这种掠食性的猫科动物是一系列豪华汽车的名字的由来,它是一种优雅、迅捷和致命精准的捕食者。这些壮丽的动物有着独特而美丽的皮毛花纹,很容易与其他大型猫科动物区分开来。它们令人叹为观止的狩猎能力是一道奇妙的景象。美洲豹是浅褐色的猫,身上有着独特的黑色斑纹。它们的底色是淡黄色或橙色,腹部是白色的。它们的黑色斑点由腹部上的实心黑色斑纹和背部的“空心”黑色圆圈组成。它们有着大牙齿、大眼睛、四肢强健的腿和一个能给予它们在捕猎时平衡的长尾巴。
狮子 狮子是一种肌肉发达的猫科动物,身体长,头大,腿短。雄性狮子最显著的特征是它的鬃毛,在不同个体和种群之间有所变异。有些雄狮完全没有鬃毛;有些雄狮的鬃毛仅在面部形成一圈边缘;而有些则具有浓密而蓬乱的鬃毛,覆盖了头后部、颈部和肩部,并延伸到喉咙和胸部,与腹部的边缘相连。一些狮子的鬃毛和边缘非常黑,几乎是黑色的,给它们带来了威严的外观。鬃毛使得雄性狮子看起来更大,并可能用于威慑竞争对手或吸引潜在的伴侣。成年雄狮的身长约为1.8-2.1米(不包括1米的尾巴);肩高约为1.2米,体重为170-230千克(370-500磅)。雌性狮子较小,身长约为1.5米,肩高0.9-1.1米,体重为120-180千克。狮子的毛发短,颜色从黄褐色、橙褐色或银灰色到深棕色不等,尾巴尖端有一簇毛,通常比其它部位的毛色更深。
豹猫 豹猫的外观与家猫类似。豹猫优雅而纤细,体长约为2-3英尺(65-100厘米)(不包括尾巴),平均体重约为9-14千克。它们的尾巴长度为1.5英尺(50厘米)。豹猫的毛发与美洲豹类似,呈暗褐色,具有不规则的斑点和条纹。条纹边缘带有黑色,在黄色/黄褐色背景上形成鲜明的外观。豹猫的底色因其栖息地而异,在更干旱的地区,毛发的基本颜色为丰富的黄色/奶油色,而在森林生境中为较深的黄褐色/棕色。
美洲狮 美洲狮是继狮子、豹和虎之后,新世界猫科动物中体型第四大的物种。成年美洲狮身材纤细、敏捷,从鼻尖到尾巴长约2.4米(8英尺),尾长为80厘米(33英寸)。它们肩高在60-76厘米(2-2.5英尺)之间,雄性体重约为53-72千克(115-160磅),雌性体重约为34-48千克(75-105磅)。美洲狮的毛发颜色呈单一色调,从褐黄色到银灰色或红褐色不等。它们的下部包括颚部、下颌和喉部有浅色斑块。美洲狮头部圆形,耳朵直立。
雪豹 雪豹,又称雪狐或其学名Panthera uncia,是一种中等体型的动物。它们的体重可达到75千克,尽管平均体重通常在30至60千克之间。它们的身体与同科的其他动物不同,更为粗壮、较短,但作为交换,它们有着长长的尾巴。这种动物的毛发非常浓密,因为它们生活在寒冷的地区,通常呈灰色(不同的色调)和黄褐色或棕色,全身布满黑色斑点,只有胸部和下腹部是白色的。至于眼睛,通常是灰色或浅绿色。它们的耳朵较小,并被毛发覆盖以保护它们免受寒冷的侵害。
老虎 成年老虎的体长可达到13英尺(4米),体重可达650磅(296千克)。老虎有两种类型的毛发,一种是护毛,一种是底毛。护毛较长,保护皮肤,底毛较短,可以囤积空气以起到保温作用。毛发的颜色提供了伪装效果。老虎的毛发和皮肤上还有明显的黑色斑纹图案,每只老虎都有其独特的斑纹。它们通常呈浅橙色至红褐色。若父母双方都有突变基因的老虎可能呈白色,但会有棕色条纹,成年老虎的尾巴可长达3.3英尺(1米)。
2.数据分析的课程设计方案概述(包括实现思路与技术难点)
-
数据收集与准备:收集包含不同猫科动物类别的大量图像数据,并进行标注,确保数据集具有多样性和代表性。
-
数据预处理与增强:对图像数据进行预处理,包括调整大小、裁剪、归一化等操作,以便输入到模型中。此外,可以应用数据增强技术,如旋转、翻转、平移等,扩充数据集的多样性。
-
特征提取与选择:使用卷积神经网络(CNN)等深度学习模型,经过预训练的特征提取层可以提取出图像的高级特征。可以选择合适的预训练模型,如ResNet、Inception等。
-
模型训练与优化:使用已提取的特征作为输入,将其与标签一起输入到分类器中进行训练。可以选择交叉熵损失函数作为训练的目标函数,并使用梯度下降等优化算法进行模型参数的更新。
-
模型评估与调优:使用验证集对训练好的模型进行评估,并根据评估结果进行模型的调优,如调整超参数、增加模型复杂度等。
技术难点包括:
-
数据获取和标注:收集大量高质量的猫科动物图像数据,并进行正确的标注是一项具有挑战性的任务。
-
模型选择和设计:选择合适的模型架构和网络结构,并确定合适的超参数和优化算法,以达到较好的分类性能。
-
类别不平衡问题:某些猫科动物类别可能具有较少的样本数量,处理类别不平衡问题以避免模型过度拟合常见类别是具有挑战性的。
-
模型泛化能力和鲁棒性:确保训练好的模型能够对未见过的数据进行准确分类,并具有对噪声、遮挡等干扰因素的鲁棒性。
-
模型解释和可解释性:解释模型的决策过程和结果,使用户能够理解模型的分类依据。
-
模型部署和性能优化:将训练好的模型部署到实际应用中,需要考虑模型大小、计算资源限制和推理速度等因素。
通过克服以上技术难点,可以实现对大型猫科动物分类的准确识别。
(三)、数据分析步骤
数据源:
http://www.kaggle.com/input/cats-in-the-wild-image
运行环境:jupyter(python3.10)
1.引入第三方库
import itertools import numpy as np import pandas as pd import os import matplotlib.pyplot as plt from sklearn.preprocessing import LabelEncoder from sklearn.model_selection import train_test_split from PIL import Image from sklearn.metrics import classification_report, f1_score , confusion_matrix import tensorflow as tf from tensorflow import keras from keras.layers import Dense, Dropout , BatchNormalization from tensorflow.keras.optimizers import Adam from tensorflow.keras import layers,models,Model from keras.preprocessing.image import ImageDataGenerator from tensorflow.keras.layers.experimental import preprocessing from tensorflow.keras.callbacks import Callback, EarlyStopping, ModelCheckpoint, ReduceLROnPlateau from tensorflow.keras import mixed_precision mixed_precision.set_global_policy('mixed_float16')
2.加载数据集
# 数据集路径 dataset = { "train_data": "/kaggle/input/cats-in-the-wild-image-classification/train", "valid_data": "/kaggle/input/cats-in-the-wild-image-classification/valid", "test_data": "/kaggle/input/cats-in-the-wild-image-classification/test" } all_data = [] # 存储所有数据的列表 for path in dataset.values(): # 遍历数据集路径 data = {"imgpath": [], "labels": []} # 创建存储图像路径和标签的字典 category = os.listdir(path) # 获取当前路径下的分类目录列表 for folder in category: # 遍历每个分类目录 folderpath = os.path.join(path, folder) # 组合出分类目录的完整路径 filelist = os.listdir(folderpath) # 获取分类目录下的文件列表 for file in filelist: # 遍历每个文件 fpath = os.path.join(folderpath, file) # 组合出文件的完整路径 data["imgpath"].append(fpath) # 将文件路径添加到"imgpath"列表中 data["labels"].append(folder) # 将标签添加到"labels"列表中 all_data.append(data.copy()) # 将当前数据字典的副本添加到all_data列表中 data.clear() # 清空当前数据字典 # 将数据转换为DataFrame对象 train_df = pd.DataFrame(all_data[0], index=range(len(all_data[0]['imgpath']))) valid_df = pd.DataFrame(all_data[1], index=range(len(all_data[1]['imgpath']))) test_df = pd.DataFrame(all_data[2], index=range(len(all_data[2]['imgpath']))) # 将标签转换为数字 lb = LabelEncoder() train_df['encoded_labels'] = lb.fit_transform(train_df['labels']) # 将训练数据集的标签转换为数字形式 valid_df['encoded_labels'] = lb.fit_transform(valid_df['labels']) # 将验证数据集的标签转换为数字形式 test_df['encoded_labels'] = lb.fit_transform(test_df['labels']) # 将测试数据集的标签转换为数字形式
3.每个类别的训练数据图片数量
# 统计每个类别的训练数据图片数量 train = train_df["labels"].value_counts() # 获取类别标签和对应的图片数量 label = train.tolist() index = train.index.tolist() # 颜色列表,用于给每个条形显示不同的颜色 colors = [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf", "#aec7e8", "#ffbb78", "#98df8a", "#ff9896", "#c5b0d5", "#c49c94", "#f7b6d2", "#c7c7c7", "#dbdb8d", "#9edae5", "#5254a3", "#6b6ecf", "#bdbdbd", "#8ca252", "#bd9e39", "#ad494a", "#8c6d31", "#6b6ecf", "#e7ba52", "#ce6dbd", "#9c9ede", "#cedb9c", "#de9ed6", "#ad494a", "#d6616b", "#f7f7f7", "#7b4173", "#a55194", "#ce6dbd" ] # 创建绘图窗口,并设置尺寸和标题 plt.figure(figsize=(30, 30)) plt.title("Training data images count per class", fontsize=38) # 设置坐标轴标签 plt.xlabel('Number of images', fontsize=35) plt.ylabel('Classes', fontsize=35) # 使用水平条形图绘制每个类别的图片数量 plt.barh(index, label, color=colors) # 开启网格线 plt.grid(True) # 显示图形 plt.show()
# 从 train_df 中随机抽取 15 条样本数据,并设置随机种子为 1,以确保结果可复现 train_df.sample(n=15, random_state=1)
# 打印训练数据集的头部样本 print("----------Train-------------") print(train_df[["imgpath", "labels"]].head(5)) # 打印训练数据集的形状(行数和列数) print(train_df.shape) # 打印验证数据集的头部样本 print("--------Validation----------") print(valid_df[["imgpath", "labels"]].head(5)) # 打印验证数据集的形状(行数和列数) print(valid_df.shape) # 打印测试数据集的头部样本 print("----------Test--------------") print(test_df[["imgpath", "labels"]].head(5)) # 打印测试数据集的形状(行数和列数) print(test_df.shape)
4.从数据中展示样本
# 设置图像大小为 15x12 plt.figure(figsize=(15, 12)) # 随机抽取验证数据集中的 16 条样本并进行展示 for i, row in valid_df.sample(n=16).reset_index().iterrows(): plt.subplot(4, 4, i+1) # 设置子图布局为 4 行 4 列,并指定当前子图位置 image_path = row['imgpath'] # 获取图片路径 image = Image.open(image_path) # 打开并加载图片 plt.imshow(image) # 在子图中显示图片 plt.title(row['labels']) # 设置子图标题为样本的标签 plt.axis('off') # 关闭坐标轴 # 展示所有子图 plt.show()
5.创建数据加载器
%%time BATCH_SIZE = 10 # 批量大小 IMAGE_SIZE = (224, 224) # 图片大小 # 创建带有预处理函数的图像生成器 generator = ImageDataGenerator( preprocessing_function=tf.keras.applications.efficientnet.preprocess_input, # 这里也可以添加图像增强操作 ) # 将数据集分为三部分并分别进行准备 train_images = generator.flow_from_dataframe( dataframe=train_df, # 训练数据集 DataFrame 对象 x_col='imgpath', # 图片路径列名 y_col='labels', # 标签列名 target_size=IMAGE_SIZE, # 缩放图片大小 color_mode='rgb', # 图片颜色模式:RGB class_mode='categorical', # 标签为多分类标签 batch_size=BATCH_SIZE, # 批量大小 shuffle=True, # 是否随机乱序 seed=42, # 随机种子,用于重复实验结果 ) val_images = generator.flow_from_dataframe( dataframe=valid_df, # 验证数据集 DataFrame 对象 x_col='imgpath', # 图片路径列名 y_col='labels', # 标签列名 target_size=IMAGE_SIZE, # 缩放图片大小 color_mode='rgb', # 图片颜色模式:RGB class_mode='categorical', # 标签为多分类标签 batch_size=BATCH_SIZE, # 批量大小 shuffle=False, # 不需要随机乱序 ) test_images = generator.flow_from_dataframe( dataframe=test_df, # 测试数据集 DataFrame 对象 x_col='imgpath', # 图片路径列名 y_col='labels', # 标签列名 target_size=IMAGE_SIZE, # 缩放图片大小 color_mode='rgb', # 图片颜色模式:RGB class_mode='categorical', # 标签为多分类标签 batch_size=BATCH_SIZE, # 批量大小 shuffle=False, # 不需要随机乱序 )
图片的内容翻译如下:
发现了2339个验证的图像文件名,属于10个类别。
发现了50个验证的图像文件名,属于10个类别。
发现了50个验证的图像文件名,属于10个类别。
总时间: 用户时间59.6毫秒,系统时间40.8毫秒,总计时间100毫秒;
墙上时间1.02秒。
# 加载预训练模型 pretrained_model = tf.keras.applications.EfficientNetB0( input_shape=(224, 224, 3), # 输入图像的形状为(224, 224, 3) include_top=False, # 不包含顶层 (输出层) weights='imagenet', # 使用 ImageNet 数据集预训练的权重 pooling='max' # 使用最大池化操作作为模型的输出 ) # 冻结预训练神经网络的层 for i, layer in enumerate(pretrained_model.layers): pretrained_model.layers[i].trainable = False
6.训练:迁移学习
num_classes = len(set(train_images.classes)) # 计算训练集中的类别数量 # 数据增强步骤 augment = tf.keras.Sequential([ layers.experimental.preprocessing.RandomFlip("horizontal"), # 随机水平翻转 layers.experimental.preprocessing.RandomRotation(0.13), # 随机旋转 layers.experimental.preprocessing.RandomZoom(0.12), # 随机缩放 layers.experimental.preprocessing.RandomContrast(0.12), # 随机对比度调整 ], name='AugmentationLayer') inputs = layers.Input(shape=(224, 224, 3), name='inputLayer') # 输入层 x = augment(inputs) # 应用数据增强到输入数据上 pretrain_out = pretrained_model(x, training=False) # 使用预训练模型提取特征,设置 training=False 表示在推理阶段使用预训练权重 x = layers.Dense(256)(pretrain_out) # 添加一个全连接层,节点数为256 x = layers.Activation(activation="relu")(x) # 使用 ReLU 激活函数 x = BatchNormalization()(x) # 批归一化 x = layers.Dropout(0.45)(x) # 添加一个 Dropout 层,丢弃率为0.45 x = layers.Dense(num_classes)(x) # 添加一个输出层,节点数为类别数量 outputs = layers.Activation(activation="softmax", dtype=tf.float32, name='activationLayer')(x) # 使用 softmax 激活函数作为输出层,用于多分类问题 model = Model(inputs=inputs, outputs=outputs) # 定义模型的输入和输出 model.compile( optimizer=Adam(0.0005), # 使用 Adam 优化器,学习率为0.0005 loss='categorical_crossentropy', # 使用交叉熵损失函数 metrics=['accuracy'] # 评估指标为准确率 ) print(model.summary()) # 打印模型的概要信息
7.训练:微调
history = model.fit( train_images, # 训练集数据 steps_per_epoch=len(train_images), # 每个epoch的步数,这里设置为训练集样本数量 validation_data=val_images, # 验证集数据 validation_steps=len(val_images), # 验证集每个epoch的步数,这里设置为验证集样本数量 epochs=3, # 迭代的轮数 callbacks=[ EarlyStopping( monitor="val_loss", # 监控验证集损失值 patience=3, # 如果连续3个epoch验证集损失值没有改善,则停止训练 restore_best_weights=True # 在停止训练后恢复到验证集损失最低的权重 ), ReduceLROnPlateau( monitor='val_loss', # 监控验证集损失值 factor=0.2, # 学习率下降的因子,即将学习率乘以0.2 patience=2, # 如果连续2个epoch验证集损失值没有改善,则降低学习率 mode='min' # 监控损失值减小的模式 ) ] ) model.save_weights('./checkpoints/my_checkpoint') # 保存模型权重到指定路径
pretrained_model.trainable = True # 设置预训练模型为可训练 for layer in pretrained_model.layers: if isinstance(layer, layers.BatchNormalization): # 将BatchNorm层设置为不可训练 layer.trainable = False # 查看前10个层的训练情况 for l in pretrained_model.layers[:10]: print(l.name, l.trainable) model.compile( optimizer=Adam(0.00001), # 微调需要非常小的学习率 loss='categorical_crossentropy', metrics=['accuracy'] ) # model.load_weights('./checkpoints/my_checkpoint') # 加载之前保存的模型权重 print(model.summary()) # 打印模型概述信息 history = model.fit( train_images, steps_per_epoch=len(train_images), validation_data=val_images, validation_steps=len(val_images), epochs=2, callbacks=[ EarlyStopping(monitor="val_loss", # 监控验证集损失指标 patience=3, restore_best_weights=True), # 如果连续5个轮次验证集损失都在增加,则停止训练 ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=2, mode='min') # 如果连续2个轮次验证集损失没有减少,则降低学习率 ] ) model.save_weights('./checkpoints/my_checkpoint') # 保存模型权重
8.显示模型性能
# 定义所需变量 tr_acc = history.history['accuracy'] # 训练准确率 tr_loss = history.history['loss'] # 训练损失 val_acc = history.history['val_accuracy'] # 验证准确率 val_loss = history.history['val_loss'] # 验证损失 index_loss = np.argmin(val_loss) # 最小验证损失的索引 val_lowest = val_loss[index_loss] # 最小验证损失 index_acc = np.argmax(val_acc) # 最大验证准确率的索引 acc_highest = val_acc[index_acc] # 最大验证准确率 Epochs = [i + 1 for i in range(len(tr_acc))] # 轮次 loss_label = f'最佳轮次= {str(index_loss + 1)}' # 损失标签 acc_label = f'最佳轮次= {str(index_acc + 1)}' # 准确率标签 # 绘制训练历史记录图 plt.figure(figsize=(20, 8)) plt.style.use('fivethirtyeight') plt.subplot(1, 2, 1) plt.plot(Epochs, tr_loss, 'r', label='训练损失') plt.plot(Epochs, val_loss, 'g', label='验证损失') plt.scatter(index_loss + 1, val_lowest, s=150, c='blue', label=loss_label) plt.title('训练和验证损失') plt.xlabel('轮次') plt.ylabel('损失') plt.legend() plt.subplot(1, 2, 2) plt.plot(Epochs, tr_acc, 'r', label='训练准确率') plt.plot(Epochs, val_acc, 'g', label='验证准确率') plt.scatter(index_acc + 1, acc_highest, s=150, c='blue', label=acc_label) plt.title('训练和验证准确率') plt.xlabel('轮次') plt.ylabel('准确率') plt.legend() plt.tight_layout plt.show()
9.模型评估
results = model.evaluate(test_images, verbose=0) print(" Test Loss: {:.5f}".format(results[0])) # 打印测试损失 print("Test Accuracy: {:.2f}%".format(results[1] * 100)) # 打印测试准确率
10.F1 (得分)/ (召回率)/ (精确率)
# 获取真实标签和预测标签 y_true = test_images.classes # 真实标签 y_pred = np.argmax(model.predict(test_images), axis=1) # 预测标签 # 计算F1得分 f1 = f1_score(y_true, y_pred, average='macro') print("F1 Score:", f1) # 打印分类报告 print(classification_report(y_true, y_pred, target_names=test_images.class_indices.keys()))
11.获得预测
# 将索引和类别名称做对应关系 classes = dict(zip(test_images.class_indices.values(), test_images.class_indices.keys())) # 构建包含真实标签、预测标签和其他信息的DataFrame Predictions = pd.DataFrame({ "Image Index": list(range(len(test_images.labels))), # 图像索引 "Test Labels": test_images.labels, # 真实标签 "Test Classes": [classes[i] for i in test_images.labels], # 真实类别 "Prediction Labels": y_pred, # 预测标签 "Prediction Classes": [classes[i] for i in y_pred], # 预测类别 "Path": test_images.filenames, # 图像路径 "Prediction Probability": [x for x in np.asarray(tf.reduce_max(model.predict(test_images), axis=1))] # 预测类别的概率 }) # 打印部分预测结果 Predictions.head(8)
12.打印最可信的错误
# 创建一个大图,设置图像大小为20x20英寸 plt.figure(figsize=(20, 20)) # 获取预测错误的最高概率结果的前20个样本,并按照概率值进行排序 subset = Predictions[Predictions["Test Labels"] != Predictions["Prediction Labels"]].sort_values("Prediction Probability").tail(20).reset_index() # 遍历每个样本并绘制子图 for i, row in subset.iterrows(): # 在大图上创建子图,每行显示5个子图,共4行 plt.subplot(5, 4, i+1) # 加载图像并显示 image_path = row['Path'] image = Image.open(image_path) plt.imshow(image) # 设置子图标题,包括真实类别和预测类别信息 plt.title(f'TRUE: {row["Test Classes"]} | PRED: {row["Prediction Classes"]}', fontsize=8) # 关闭子图的坐标轴 plt.axis('off') # 显示图像 plt.show()
13.混淆矩阵和分类报告
preds = model.predict_generator(test_images) # 对测试图像进行预测 y_pred = np.argmax(preds, axis=1) # 获取预测结果中概率最高的类别索引 g_dict = test_images.class_indices # 获取类别名称和对应索引的字典 classes = list(g_dict.keys()) # 将类别名称转换为列表 # 计算混淆矩阵 cm = confusion_matrix(test_images.classes, y_pred) # 创建一个大图,设置图像大小为30x30英寸 plt.figure(figsize=(30, 30)) # 在图像上显示混淆矩阵 plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues) plt.title('Confusion Matrix') plt.colorbar() # 设置刻度标签 tick_marks = np.arange(len(classes)) plt.xticks(tick_marks, classes, rotation=45) plt.yticks(tick_marks, classes) # 设置阈值,用于在混淆矩阵中显示数值 thresh = cm.max() / 2. for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])): plt.text(j, i, cm[i, j], horizontalalignment='center', color='white' if cm[i, j] > thresh else 'black') # 调整图像布局并设置标签 plt.tight_layout() plt.ylabel('True Label') plt.xlabel('Predicted Label') # 显示图像 plt.show()
(四)、总结
这篇博客的功能主要是提供关于机器学习和大型猫科动物分类的知识。通过阅读这篇博客,您可以了解不同种类的大型猫科动物,它们的特征和生态习性。此外,您还可以了解机器学习在猫科动物分类和识别中的应用场景和方法。
博客的目的是增加读者对自然界生物多样性的认识并激发对保护生物多样性的兴趣。以及学到一些列相关的技术,以及细节的实现,应用到训练模型和微调让代码更完整性,有可观性。
敲代码的过程中,可以学到以下的一些东西:
-
解决问题的能力:编写代码就是为了解决问题,因此在编写代码的过程中,你需要不断地思考和分析问题,并尝试找到最优解决方案。
-
逻辑思维能力:编写代码需要有一定的逻辑思维能力,需要对问题进行拆分、分析和整合,然后再把各个部分组合起来实现功能。
-
技术能力:编写代码需要掌握一定的编程语言、工具和技术,因此在编写代码的过程中,你会不断地学习新的技术和知识,提升自己的技术水平。
-
耐心和毅力:编写代码可能会遇到很多困难和问题,需要有足够的耐心和毅力去排查和解决这些问题。
总代码
import itertools import numpy as np import pandas as pd import os import matplotlib.pyplot as plt from sklearn.preprocessing import LabelEncoder from sklearn.model_selection import train_test_split from PIL import Image from sklearn.metrics import classification_report, f1_score , confusion_matrix import tensorflow as tf from tensorflow import keras from keras.layers import Dense, Dropout , BatchNormalization from tensorflow.keras.optimizers import Adam from tensorflow.keras import layers,models,Model from keras.preprocessing.image import ImageDataGenerator from tensorflow.keras.layers.experimental import preprocessing from tensorflow.keras.callbacks import Callback, EarlyStopping, ModelCheckpoint, ReduceLROnPlateau from tensorflow.keras import mixed_precision mixed_precision.set_global_policy('mixed_float16')
# 数据集路径 dataset = { "train_data": "/kaggle/input/cats-in-the-wild-image-classification/train", "valid_data": "/kaggle/input/cats-in-the-wild-image-classification/valid", "test_data": "/kaggle/input/cats-in-the-wild-image-classification/test" } all_data = [] # 存储所有数据的列表 for path in dataset.values(): # 遍历数据集路径 data = {"imgpath": [], "labels": []} # 创建存储图像路径和标签的字典 category = os.listdir(path) # 获取当前路径下的分类目录列表 for folder in category: # 遍历每个分类目录 folderpath = os.path.join(path, folder) # 组合出分类目录的完整路径 filelist = os.listdir(folderpath) # 获取分类目录下的文件列表 for file in filelist: # 遍历每个文件 fpath = os.path.join(folderpath, file) # 组合出文件的完整路径 data["imgpath"].append(fpath) # 将文件路径添加到"imgpath"列表中 data["labels"].append(folder) # 将标签添加到"labels"列表中 all_data.append(data.copy()) # 将当前数据字典的副本添加到all_data列表中 data.clear() # 清空当前数据字典 # 将数据转换为DataFrame对象 train_df = pd.DataFrame(all_data[0], index=range(len(all_data[0]['imgpath']))) valid_df = pd.DataFrame(all_data[1], index=range(len(all_data[1]['imgpath']))) test_df = pd.DataFrame(all_data[2], index=range(len(all_data[2]['imgpath']))) # 将标签转换为数字 lb = LabelEncoder() train_df['encoded_labels'] = lb.fit_transform(train_df['labels']) # 将训练数据集的标签转换为数字形式 valid_df['encoded_labels'] = lb.fit_transform(valid_df['labels']) # 将验证数据集的标签转换为数字形式 test_df['encoded_labels'] = lb.fit_transform(test_df['labels']) # 将测试数据集的标签转换为数字形式
# 统计每个类别的训练数据图片数量 train = train_df["labels"].value_counts() # 获取类别标签和对应的图片数量 label = train.tolist() index = train.index.tolist() # 颜色列表,用于给每个条形显示不同的颜色 colors = [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf", "#aec7e8", "#ffbb78", "#98df8a", "#ff9896", "#c5b0d5", "#c49c94", "#f7b6d2", "#c7c7c7", "#dbdb8d", "#9edae5", "#5254a3", "#6b6ecf", "#bdbdbd", "#8ca252", "#bd9e39", "#ad494a", "#8c6d31", "#6b6ecf", "#e7ba52", "#ce6dbd", "#9c9ede", "#cedb9c", "#de9ed6", "#ad494a", "#d6616b", "#f7f7f7", "#7b4173", "#a55194", "#ce6dbd" ] # 创建绘图窗口,并设置尺寸和标题 plt.figure(figsize=(30, 30)) plt.title("Training data images count per class", fontsize=38) # 设置坐标轴标签 plt.xlabel('Number of images', fontsize=35) plt.ylabel('Classes', fontsize=35) # 使用水平条形图绘制每个类别的图片数量 plt.barh(index, label, color=colors) # 开启网格线 plt.grid(True) # 显示图形 plt.show()
# 从 train_df 中随机抽取 15 条样本数据,并设置随机种子为 1,以确保结果可复现 train_df.sample(n=15, random_state=1)
# 打印训练数据集的头部样本 print("----------Train-------------") print(train_df[["imgpath", "labels"]].head(5)) # 打印训练数据集的形状(行数和列数) print(train_df.shape) # 打印验证数据集的头部样本 print("--------Validation----------") print(valid_df[["imgpath", "labels"]].head(5)) # 打印验证数据集的形状(行数和列数) print(valid_df.shape) # 打印测试数据集的头部样本 print("----------Test--------------") print(test_df[["imgpath", "labels"]].head(5)) # 打印测试数据集的形状(行数和列数) print(test_df.shape)
# 设置图像大小为 15x12 plt.figure(figsize=(15, 12)) # 随机抽取验证数据集中的 16 条样本并进行展示 for i, row in valid_df.sample(n=16).reset_index().iterrows(): plt.subplot(4, 4, i+1) # 设置子图布局为 4 行 4 列,并指定当前子图位置 image_path = row['imgpath'] # 获取图片路径 image = Image.open(image_path) # 打开并加载图片 plt.imshow(image) # 在子图中显示图片 plt.title(row['labels']) # 设置子图标题为样本的标签 plt.axis('off') # 关闭坐标轴 # 展示所有子图 plt.show()
%%time BATCH_SIZE = 10 # 批量大小 IMAGE_SIZE = (224, 224) # 图片大小 # 创建带有预处理函数的图像生成器 generator = ImageDataGenerator( preprocessing_function=tf.keras.applications.efficientnet.preprocess_input, # 这里也可以添加图像增强操作 ) # 将数据集分为三部分并分别进行准备 train_images = generator.flow_from_dataframe( dataframe=train_df, # 训练数据集 DataFrame 对象 x_col='imgpath', # 图片路径列名 y_col='labels', # 标签列名 target_size=IMAGE_SIZE, # 缩放图片大小 color_mode='rgb', # 图片颜色模式:RGB class_mode='categorical', # 标签为多分类标签 batch_size=BATCH_SIZE, # 批量大小 shuffle=True, # 是否随机乱序 seed=42, # 随机种子,用于重复实验结果 ) val_images = generator.flow_from_dataframe( dataframe=valid_df, # 验证数据集 DataFrame 对象 x_col='imgpath', # 图片路径列名 y_col='labels', # 标签列名 target_size=IMAGE_SIZE, # 缩放图片大小 color_mode='rgb', # 图片颜色模式:RGB class_mode='categorical', # 标签为多分类标签 batch_size=BATCH_SIZE, # 批量大小 shuffle=False, # 不需要随机乱序 ) test_images = generator.flow_from_dataframe( dataframe=test_df, # 测试数据集 DataFrame 对象 x_col='imgpath', # 图片路径列名 y_col='labels', # 标签列名 target_size=IMAGE_SIZE, # 缩放图片大小 color_mode='rgb', # 图片颜色模式:RGB class_mode='categorical', # 标签为多分类标签 batch_size=BATCH_SIZE, # 批量大小 shuffle=False, # 不需要随机乱序 )
# 加载预训练模型 pretrained_model = tf.keras.applications.EfficientNetB0( input_shape=(224, 224, 3), # 输入图像的形状为(224, 224, 3) include_top=False, # 不包含顶层 (输出层) weights='imagenet', # 使用 ImageNet 数据集预训练的权重 pooling='max' # 使用最大池化操作作为模型的输出 ) # 冻结预训练神经网络的层 for i, layer in enumerate(pretrained_model.layers): pretrained_model.layers[i].trainable = False
num_classes = len(set(train_images.classes)) # 计算训练集中的类别数量 # 数据增强步骤 augment = tf.keras.Sequential([ layers.experimental.preprocessing.RandomFlip("horizontal"), # 随机水平翻转 layers.experimental.preprocessing.RandomRotation(0.13), # 随机旋转 layers.experimental.preprocessing.RandomZoom(0.12), # 随机缩放 layers.experimental.preprocessing.RandomContrast(0.12), # 随机对比度调整 ], name='AugmentationLayer') inputs = layers.Input(shape=(224, 224, 3), name='inputLayer') # 输入层 x = augment(inputs) # 应用数据增强到输入数据上 pretrain_out = pretrained_model(x, training=False) # 使用预训练模型提取特征,设置 training=False 表示在推理阶段使用预训练权重 x = layers.Dense(256)(pretrain_out) # 添加一个全连接层,节点数为256 x = layers.Activation(activation="relu")(x) # 使用 ReLU 激活函数 x = BatchNormalization()(x) # 批归一化 x = layers.Dropout(0.45)(x) # 添加一个 Dropout 层,丢弃率为0.45 x = layers.Dense(num_classes)(x) # 添加一个输出层,节点数为类别数量 outputs = layers.Activation(activation="softmax", dtype=tf.float32, name='activationLayer')(x) # 使用 softmax 激活函数作为输出层,用于多分类问题 model = Model(inputs=inputs, outputs=outputs) # 定义模型的输入和输出 model.compile( optimizer=Adam(0.0005), # 使用 Adam 优化器,学习率为0.0005 loss='categorical_crossentropy', # 使用交叉熵损失函数 metrics=['accuracy'] # 评估指标为准确率 ) print(model.summary()) # 打印模型的概要信息
history = model.fit( train_images, # 训练集数据 steps_per_epoch=len(train_images), # 每个epoch的步数,这里设置为训练集样本数量 validation_data=val_images, # 验证集数据 validation_steps=len(val_images), # 验证集每个epoch的步数,这里设置为验证集样本数量 epochs=3, # 迭代的轮数 callbacks=[ EarlyStopping( monitor="val_loss", # 监控验证集损失值 patience=3, # 如果连续3个epoch验证集损失值没有改善,则停止训练 restore_best_weights=True # 在停止训练后恢复到验证集损失最低的权重 ), ReduceLROnPlateau( monitor='val_loss', # 监控验证集损失值 factor=0.2, # 学习率下降的因子,即将学习率乘以0.2 patience=2, # 如果连续2个epoch验证集损失值没有改善,则降低学习率 mode='min' # 监控损失值减小的模式 ) ] ) model.save_weights('./checkpoints/my_checkpoint') # 保存模型权重到指定路径
pretrained_model.trainable = True # 设置预训练模型为可训练 for layer in pretrained_model.layers: if isinstance(layer, layers.BatchNormalization): # 将BatchNorm层设置为不可训练 layer.trainable = False # 查看前10个层的训练情况 for l in pretrained_model.layers[:10]: print(l.name, l.trainable) model.compile( optimizer=Adam(0.00001), # 微调需要非常小的学习率 loss='categorical_crossentropy', metrics=['accuracy'] ) # model.load_weights('./checkpoints/my_checkpoint') # 加载之前保存的模型权重 print(model.summary()) # 打印模型概述信息 history = model.fit( train_images, steps_per_epoch=len(train_images), validation_data=val_images, validation_steps=len(val_images), epochs=2, callbacks=[ EarlyStopping(monitor="val_loss", # 监控验证集损失指标 patience=3, restore_best_weights=True), # 如果连续5个轮次验证集损失都在增加,则停止训练 ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=2, mode='min') # 如果连续2个轮次验证集损失没有减少,则降低学习率 ] ) model.save_weights('./checkpoints/my_checkpoint') # 保存模型权重
# 定义所需变量 tr_acc = history.history['accuracy'] # 训练准确率 tr_loss = history.history['loss'] # 训练损失 val_acc = history.history['val_accuracy'] # 验证准确率 val_loss = history.history['val_loss'] # 验证损失 index_loss = np.argmin(val_loss) # 最小验证损失的索引 val_lowest = val_loss[index_loss] # 最小验证损失 index_acc = np.argmax(val_acc) # 最大验证准确率的索引 acc_highest = val_acc[index_acc] # 最大验证准确率 Epochs = [i + 1 for i in range(len(tr_acc))] # 轮次 loss_label = f'最佳轮次= {str(index_loss + 1)}' # 损失标签 acc_label = f'最佳轮次= {str(index_acc + 1)}' # 准确率标签 # 绘制训练历史记录图 plt.figure(figsize=(20, 8)) plt.style.use('fivethirtyeight') plt.subplot(1, 2, 1) plt.plot(Epochs, tr_loss, 'r', label='训练损失') plt.plot(Epochs, val_loss, 'g', label='验证损失') plt.scatter(index_loss + 1, val_lowest, s=150, c='blue', label=loss_label) plt.title('训练和验证损失') plt.xlabel('轮次') plt.ylabel('损失') plt.legend() plt.subplot(1, 2, 2) plt.plot(Epochs, tr_acc, 'r', label='训练准确率') plt.plot(Epochs, val_acc, 'g', label='验证准确率') plt.scatter(index_acc + 1, acc_highest, s=150, c='blue', label=acc_label) plt.title('训练和验证准确率') plt.xlabel('轮次') plt.ylabel('准确率') plt.legend() plt.tight_layout plt.show()
results = model.evaluate(test_images, verbose=0) print(" Test Loss: {:.5f}".format(results[0])) # 打印测试损失 print("Test Accuracy: {:.2f}%".format(results[1] * 100)) # 打印测试准确率
# 获取真实标签和预测标签 y_true = test_images.classes # 真实标签 y_pred = np.argmax(model.predict(test_images), axis=1) # 预测标签 # 计算F1得分 f1 = f1_score(y_true, y_pred, average='macro') print("F1 Score:", f1) # 打印分类报告 print(classification_report(y_true, y_pred, target_names=test_images.class_indices.keys()))
# 将索引和类别名称做对应关系 classes = dict(zip(test_images.class_indices.values(), test_images.class_indices.keys())) # 构建包含真实标签、预测标签和其他信息的DataFrame Predictions = pd.DataFrame({ "Image Index": list(range(len(test_images.labels))), # 图像索引 "Test Labels": test_images.labels, # 真实标签 "Test Classes": [classes[i] for i in test_images.labels], # 真实类别 "Prediction Labels": y_pred, # 预测标签 "Prediction Classes": [classes[i] for i in y_pred], # 预测类别 "Path": test_images.filenames, # 图像路径 "Prediction Probability": [x for x in np.asarray(tf.reduce_max(model.predict(test_images), axis=1))] # 预测类别的概率 }) # 打印部分预测结果 Predictions.head(8)
# 创建一个大图,设置图像大小为20x20英寸 plt.figure(figsize=(20, 20)) # 获取预测错误的最高概率结果的前20个样本,并按照概率值进行排序 subset = Predictions[Predictions["Test Labels"] != Predictions["Prediction Labels"]].sort_values("Prediction Probability").tail(20).reset_index() # 遍历每个样本并绘制子图 for i, row in subset.iterrows(): # 在大图上创建子图,每行显示5个子图,共4行 plt.subplot(5, 4, i+1) # 加载图像并显示 image_path = row['Path'] image = Image.open(image_path) plt.imshow(image) # 设置子图标题,包括真实类别和预测类别信息 plt.title(f'TRUE: {row["Test Classes"]} | PRED: {row["Prediction Classes"]}', fontsize=8) # 关闭子图的坐标轴 plt.axis('off') # 显示图像 plt.show()
preds = model.predict_generator(test_images) # 对测试图像进行预测 y_pred = np.argmax(preds, axis=1) # 获取预测结果中概率最高的类别索引 g_dict = test_images.class_indices # 获取类别名称和对应索引的字典 classes = list(g_dict.keys()) # 将类别名称转换为列表 # 计算混淆矩阵 cm = confusion_matrix(test_images.classes, y_pred) # 创建一个大图,设置图像大小为30x30英寸 plt.figure(figsize=(30, 30)) # 在图像上显示混淆矩阵 plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues) plt.title('Confusion Matrix') plt.colorbar() # 设置刻度标签 tick_marks = np.arange(len(classes)) plt.xticks(tick_marks, classes, rotation=45) plt.yticks(tick_marks, classes) # 设置阈值,用于在混淆矩阵中显示数值 thresh = cm.max() / 2. for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])): plt.text(j, i, cm[i, j], horizontalalignment='center', color='white' if cm[i, j] > thresh else 'black') # 调整图像布局并设置标签 plt.tight_layout() plt.ylabel('True Label') plt.xlabel('Predicted Label') # 显示图像 plt.show()