当前位置: 首页 > news >正文

使用Tensorflow和CNN进行猫狗图片训练的实战总结

关于Tensorflow可参考:认识Scikit-learn/PyTorch/TensorFlow这几个AI框架_python ai框架-CSDN博客

关于CNN可参考:

认识神经网络和深度学习-CSDN博客

CNN和图像识别有什么关系

CNN(卷积神经网络,Convolutional Neural Network)是图像识别领域中最核心、最主流的技术,二者的关系可以概括为:CNN 通过其独特的卷积机制,解决了图像识别中的关键难题,成为现代图像识别系统的 “引擎”。

一、图像识别的核心挑战

图像识别的目标是让计算机 “看懂” 图像(如判断图像中的物体类别、位置等),但原始图像存在两个核心难题:

  1. 数据维度高:一张普通的彩色图像(如 224×224 像素)包含 224×224×3=150,528 个像素值,直接用传统神经网络处理会导致参数爆炸(计算量过大)。

  2. 空间关联性强:图像的语义信息(如 “猫的耳朵”“汽车轮子”)依赖像素的局部空间关系(相邻像素共同构成特征),而非孤立的像素值。

二、CNN 如何适配图像识别

CNN 通过三大核心机制,完美解决了上述问题,使其成为图像识别的 “量身定制” 工具:

卷积操作:提取局部特征,保留空间关联

  • 卷积层通过卷积核滑动,对图像的局部区域(如 3×3、5×5 像素块)进行特征提取(如边缘、纹理、形状,如前文所述)。

  • 这种操作天然保留了像素的空间位置关系(相邻像素的特征会被同时处理),符合图像中 “局部像素共同构成特征” 的规律(例如 “眼睛” 由相邻的肤色、黑色像素组成)。

池化操作:降低维度,增强鲁棒性

  • 池化层(如最大池化、平均池化)通过对局部区域的特征进行聚合(如取最大值、平均值),在减少特征维度的同时,保留关键信息。

  • 例如,2×2 最大池化可将特征图尺寸缩小一半,参数和计算量大幅降低,同时让模型对微小的位置变化(如物体轻微位移)更鲁棒(不敏感)。

层级特征提取:从 “像素” 到 “语义” 的递进

  • CNN 的多层结构(浅层→深层)会自动学习层级化的特征:

    • 浅层(如前几层卷积)提取基础特征(边缘、纹理、颜色块);

    • 中层(如中间卷积层)组合基础特征,形成部件级特征(如 “猫的耳朵轮廓”“汽车的车窗”);

    • 深层(如全连接层前的卷积层)进一步组合,得到抽象的语义特征(如 “猫”“汽车” 的整体特征)。

  • 这种层级特征与人类视觉系统的认知规律一致(人类先看到局部细节,再整合为整体)。

三、CNN 推动图像识别的突破

在 CNN 出现前,图像识别依赖人工设计特征(如 SIFT、HOG 等),识别准确率低(如 2012 年 ImageNet 竞赛的传统方法准确率仅 50% 左右)。

2012 年,以 AlexNet 为代表的 CNN 首次在 ImageNet 竞赛中超越传统方法,将错误率从 26% 降至 15%,此后 CNN 推动图像识别准确率持续突破:

  • 如今主流 CNN 模型(如 ResNet、EfficientNet)在 ImageNet 等大型数据集上的准确率已接近人类水平(97% 以上);

  • 基于 CNN 的图像识别技术已广泛应用于人脸识别、自动驾驶(识别行人 / 红绿灯)、医学影像诊断(识别肿瘤)等领域。

四、总结:CNN 是图像识别的 “核心技术支柱”

  • 依赖关系:现代图像识别系统(尤其是高精度系统)几乎都以 CNN 为基础架构,没有 CNN,就没有当前图像识别的实用化水平。

  • 本质作用:CNN 通过卷积、池化等操作,将高维度、强关联的图像数据转化为可用于分类 / 识别的抽象特征,让计算机能高效 “理解” 图像的语义信息。

简单来说:图像识别是目标,CNN 是实现这一目标的最有效工具。

Tensorflow和CNN之间的关系

TensorFlow 和 CNN(卷积神经网络)是深度学习领域中工具与技术的关系:

  • TensorFlow是一个开源的深度学习框架,提供了构建、训练和部署神经网络的工具和 API;
  • CNN是一种专门为处理具有网格结构数据(如图像)而设计的神经网络架构。

TensorFlow 可以用来实现 CNN,而 CNN 则是 TensorFlow 在计算机视觉任务中最常见的应用场景之一。下面从技术细节、应用场景和实践案例三个维度展开说明:

一、技术细节:TensorFlow 如何支持 CNN?

TensorFlow 为实现 CNN 提供了以下核心功能:

1. 卷积层实现

TensorFlow 内置了多种卷积操作,如:

  • tf.nn.conv2d:二维卷积,用于处理图像(最常用);
  • tf.nn.conv3d:三维卷积,用于处理视频或体积数据;
  • 支持自定义卷积核大小(如 3×3、5×5)、步长(stride)、填充方式(padding)等参数。

示例代码(简化版):

import tensorflow as tf# 输入:批量大小=32,高度=224,宽度=224,通道数=3(RGB)
x = tf.random.normal([32, 224, 224, 3])
# 定义32个3×3的卷积核
filters = tf.Variable(tf.random.normal([3, 3, 3, 32]))
# 执行卷积操作
conv_output = tf.nn.conv2d(x, filters, strides=[1, 1, 1, 1], padding='SAME')

2. 池化层实现

支持最大池化(Max Pooling)、平均池化(Average Pooling)等操作:

  • tf.nn.max_pool2d:二维最大池化;
  • tf.nn.avg_pool2d:二维平均池化。

示例代码

pool_output = tf.nn.max_pool2d(conv_output, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID')

3. 高级 API 封装

TensorFlow 的 Keras 接口进一步简化了 CNN 的构建:

from tensorflow.keras import layers, modelsmodel = models.Sequential([layers.Conv2D(32, (3, 3), activation='relu', input_shape=(224, 224, 3)),layers.MaxPooling2D((2, 2)),layers.Conv2D(64, (3, 3), activation='relu'),layers.MaxPooling2D((2, 2)),layers.Flatten(),layers.Dense(64, activation='relu'),layers.Dense(10, activation='softmax')  # 假设10分类任务
])

4. 预训练模型支持

TensorFlow 提供了多种预训练的 CNN 模型(如 ResNet、VGG、MobileNet),可直接用于迁移学习

from tensorflow.keras.applications import ResNet50base_model = ResNet50(weights='imagenet', include_top=False)

二、应用场景:TensorFlow+CNN 的典型案例

  1. 图像分类:如 CIFAR-10、ImageNet 等数据集上的分类任务。
  2. 目标检测:结合 Faster R-CNN、YOLO 等算法,在 TensorFlow 中实现物体定位与识别。
  3. 语义分割:使用 U-Net、DeepLab 等模型对图像进行像素级分类。
  4. 人脸识别:通过 CNN 提取人脸特征,实现身份验证。
  5. 自动驾驶:处理车载摄像头图像,识别道路、车辆和行人。

三、实践案例:用 TensorFlow 实现 CNN 图像分类

以下是一个完整的 TensorFlow+CNN 图像分类示例(基于 MNIST 手写数字数据集):

import tensorflow as tf
from tensorflow.keras import layers, models# 1. 加载数据
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()# 2. 数据预处理
x_train, x_test = x_train / 255.0, x_test / 255.0
x_train = x_train.reshape(-1, 28, 28, 1)  # 添加通道维度
x_test = x_test.reshape(-1, 28, 28, 1)# 3. 构建CNN模型
model = models.Sequential([layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),layers.MaxPooling2D((2, 2)),layers.Conv2D(64, (3, 3), activation='relu'),layers.MaxPooling2D((2, 2)),layers.Flatten(),layers.Dense(64, activation='relu'),layers.Dense(10, activation='softmax')
])# 4. 编译模型
model.compile(optimizer='adam',loss='sparse_categorical_crossentropy',metrics=['accuracy'])# 5. 训练模型
model.fit(x_train, y_train, epochs=5, validation_data=(x_test, y_test))# 6. 评估模型
test_loss, test_acc = model.evaluate(x_test, y_test)
print(f"Test accuracy: {test_acc * 100:.2f}%")

四、总结

TensorFlow 是实现 CNN 的强大工具,它提供了底层的张量运算和高层的 API 封装,使研究者和工程师能够高效地构建、训练和部署各种 CNN 模型。而 CNN 则是 TensorFlow 在计算机视觉领域的核心应用方向,二者结合推动了图像识别、目标检测等任务的快速发展。

安装相关python库

直接参考:认识NumPy/Scipy/Pandas/Matplotlib这几个Python库-CSDN博客

安装Tensorflow

直接参考:

Tensorflow的安装记录-CSDN博客

实战操作

    下面是一个使用 TensorFlow 和 CNN 进行猫狗识别的完整示例,使用国内镜像源加载数据,并确保代码可以直接执行。这个示例包含数据加载、模型构建、训练和评估的完整流程,同时添加了详细的注释以便理解。

    这个代码示例具有以下特点:

    1. 国内源加载:使用 try-except 结构尝试多种加载方式,优先使用国内镜像源。

    2. 完整流程:包含数据加载、预处理、模型构建、训练、评估和可视化的完整流程。

    3. 兼容性:移除了可能导致兼容性问题的参数,确保代码可直接执行。

    4. 可视化:包含数据样本和模型预测结果的可视化,方便理解。

    5. 模型优化:使用 dropout 层防止过拟合,并添加早停和模型检查点回调。

    你可以直接运行这个脚本,它会自动尝试从国内源加载数据并开始训练。如果遇到下载问题,脚本会提供相应的错误信息和解决建议。训练完成后,模型会保存为cat_dog_model.h5,同时会显示训练过程和预测结果的可视化图表。

    第一版程序如下:

    import tensorflow as tf
    import tensorflow_datasets as tfds
    from tensorflow.keras import layers, models, callbacks
    import matplotlib.pyplot as plt
    import os# 设置中文显示
    plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]# 定义常量
    IMAGE_SIZE = (150, 150)
    BATCH_SIZE = 32
    EPOCHS = 15
    NUM_CLASSES = 2# 尝试从国内镜像源加载数据集
    try:print("正在从国内镜像源加载数据集...")(ds_train, ds_val, ds_test), ds_info = tfds.load('cats_vs_dogs',split=['train[:80%]', 'train[80%:90%]', 'train[90%:]'],shuffle_files=True,as_supervised=True,with_info=True,try_gcs=True  # 尝试从Google Cloud Storage下载)
    except Exception as e:print(f"自动下载失败: {e}")print("尝试使用替代方法加载数据集...")# 替代方法:手动指定下载URLconfig = tfds.download.DownloadConfig(# 使用Hugging Face镜像源manual_dir=None,download_mode=tfds.GenerateMode.REUSE_DATASET_IF_EXISTS)try:(ds_train, ds_val, ds_test), ds_info = tfds.load('cats_vs_dogs',split=['train[:80%]', 'train[80%:90%]', 'train[90%:]'],shuffle_files=True,as_supervised=True,with_info=True,download_and_prepare_kwargs={'download_config': config})except Exception as e2:print(f"仍然无法加载数据集: {e2}")print("请确保tensorflow_datasets版本 >= 4.0,或手动下载数据集")print("数据集下载地址: https://www.kaggle.com/c/dogs-vs-cats/data")exit(1)# 数据预处理函数
    def preprocess_image(image, label):image = tf.cast(image, tf.float32)image = tf.image.resize(image, IMAGE_SIZE)image = image / 255.0  # 归一化到[0,1]return image, label# 构建数据加载流水线
    ds_train = ds_train.map(preprocess_image, num_parallel_calls=tf.data.AUTOTUNE)
    ds_train = ds_train.cache()
    ds_train = ds_train.shuffle(ds_info.splits['train'].num_examples * 0.8)
    ds_train = ds_train.batch(BATCH_SIZE)
    ds_train = ds_train.prefetch(tf.data.AUTOTUNE)ds_val = ds_val.map(preprocess_image, num_parallel_calls=tf.data.AUTOTUNE)
    ds_val = ds_val.batch(BATCH_SIZE)
    ds_val = ds_val.cache()
    ds_val = ds_val.prefetch(tf.data.AUTOTUNE)ds_test = ds_test.map(preprocess_image, num_parallel_calls=tf.data.AUTOTUNE)
    ds_test = ds_test.batch(BATCH_SIZE)
    ds_test = ds_test.prefetch(tf.data.AUTOTUNE)# 可视化样本
    def visualize_samples(dataset):plt.figure(figsize=(10, 10))for images, labels in dataset.take(1):for i in range(9):ax = plt.subplot(3, 3, i + 1)plt.imshow(images[i].numpy())plt.title("猫" if labels[i].numpy() == 0 else "狗")plt.axis("off")plt.tight_layout()plt.show()print("数据样本可视化:")
    visualize_samples(ds_train)# 构建CNN模型
    def build_model():model = models.Sequential([# 卷积层和池化层layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)),layers.MaxPooling2D((2, 2)),layers.Conv2D(64, (3, 3), activation='relu'),layers.MaxPooling2D((2, 2)),layers.Conv2D(128, (3, 3), activation='relu'),layers.MaxPooling2D((2, 2)),# 全连接层layers.Flatten(),layers.Dense(128, activation='relu'),layers.Dropout(0.5),  # 防止过拟合layers.Dense(NUM_CLASSES, activation='softmax')])# 编译模型model.compile(optimizer='adam',loss='sparse_categorical_crossentropy',metrics=['accuracy'])return model# 创建模型
    model = build_model()
    print("模型结构:")
    model.summary()# 设置回调函数
    callbacks_list = [callbacks.EarlyStopping(monitor='val_loss',patience=3,  # 如果验证集损失3个epoch不下降则停止训练restore_best_weights=True),callbacks.ModelCheckpoint(filepath='cat_dog_model.h5',monitor='val_accuracy',save_best_only=True,verbose=1),callbacks.TensorBoard(log_dir='./logs')
    ]# 训练模型
    print("开始训练模型...")
    history = model.fit(ds_train,epochs=EPOCHS,validation_data=ds_val,callbacks=callbacks_list
    )# 评估模型
    print("在测试集上评估模型...")
    test_loss, test_acc = model.evaluate(ds_test)
    print(f"测试集准确率: {test_acc:.4f}")# 绘制训练历史
    def plot_training_history(history):plt.figure(figsize=(12, 4))# 绘制准确率曲线plt.subplot(1, 2, 1)plt.plot(history.history['accuracy'], label='训练准确率')plt.plot(history.history['val_accuracy'], label='验证准确率')plt.xlabel('Epoch')plt.ylabel('准确率')plt.legend()plt.title('训练和验证准确率')# 绘制损失曲线plt.subplot(1, 2, 2)plt.plot(history.history['loss'], label='训练损失')plt.plot(history.history['val_loss'], label='验证损失')plt.xlabel('Epoch')plt.ylabel('损失')plt.legend()plt.title('训练和验证损失')plt.tight_layout()plt.show()plot_training_history(history)# 预测示例
    def predict_example(model, dataset):plt.figure(figsize=(12, 12))for images, labels in dataset.take(1):predictions = model.predict(images)for i in range(9):ax = plt.subplot(3, 3, i + 1)plt.imshow(images[i].numpy())# 获取预测结果predicted_class = "猫" if tf.argmax(predictions[i]) == 0 else "狗"confidence = tf.reduce_max(predictions[i]) * 100# 获取真实标签true_class = "猫" if labels[i].numpy() == 0 else "狗"plt.title(f"预测: {predicted_class} ({confidence:.1f}%)\n真实: {true_class}")plt.axis("off")plt.tight_layout()plt.show()print("模型预测示例:")
    predict_example(model, ds_test)print("训练完成! 模型已保存为 'cat_dog_model.h5'")    

    这个代码执行时有问题,一直无法从tensorflow_datasets中下载猫狗数据集。

    解决数据集问题

    我们看看怎么能解决这个问题。

    首先确认下tensorflow_datasets的版本

    这个版本还算挺高的,应该不是版本的问题吧。

    既然网上下载的不行,那么就只能手动下载这个数据集,然后再手动加载了。

    从 Kaggle 下载

    • 访问 Dogs vs. Cats
    • 下载 train.zip 和 test1.zip(约 800MB)
    • 这个网站下载要登录外网,然后还下不下来,不推荐;

    从 Hugging Face 下载(备选):

    • 访问 cats_vs_dogs
    • 下载 data.tar.gz

    这两个地方下载数据集都挺费劲的,看来这些数据集也是要收费的。

    咋弄呢?直接百度搜索kaggle的猫狗数据集,然后找一找。

    终于是找到了一份

    可以看到,有两份文件,一份是训练集,一份是测试集,不得不说,数据挺大的,800多M。

    里面啥样的?打开看看

    我们先把这个数据集解压到某个目录下:

    训练集里就是一大堆猫狗的图片,一共2.5万张

    附:电脑的图片不能预览怎么办?

    每张图片都标注了cat或者dog,这就是一种标签的方式,后续程序可以通过读取文件名来知道对应图片的标签。

    测试集里也是猫狗的图片,但是没有标签(难道不应该也有吗),都是序号。

    猫狗数据集里为什么train里面的图片有标签,但是test1里面的图片没有标签?

    在猫狗数据集(如 Kaggle 的经典 “Dogs vs. Cats” 数据集)中,train文件夹的图片有标签,而test1文件夹的图片无标签,这是由数据集的设计目的和机器学习的流程决定的,具体原因如下:

    1. 数据集的核心用途:训练与评估

    • train文件夹(带标签)
      用于模型训练。机器学习模型需要 “已知答案” 的样本(即标签)来学习规律。例如,train中的图片命名为cat.0.jpgdog.1.jpg,通过文件名就能明确标签(猫 / 狗),模型通过这些样本学习 “猫和狗的视觉特征差异”。
    • test1文件夹(无标签)
      用于模拟真实场景的预测。在实际应用中,模型面对的是 “未知标签” 的数据(比如用户上传的一张图片,模型需要判断是猫还是狗)。test1的设计正是为了检验模型在 “无标签数据” 上的泛化能力,相当于 “期末考试题”,答案(标签)不会提前给出。

    2. 竞赛场景的设计(以 Kaggle 为例)

    经典的猫狗数据集源自 Kaggle 竞赛,竞赛的目标是让参赛者训练模型后,对test1中的图片进行预测,然后提交预测结果(如每张图片是猫的概率),由官方通过隐藏的标签来评判模型性能。因此:

    • test1的标签被官方保留,用于公平地排名参赛者的模型效果;
    • 参赛者无法直接获取test1的标签,只能通过模型预测来验证自己的训练成果。

    3. 如何使用test1数据?

    如果你不是为了参加竞赛,而是自己做练习,可以:

    • 手动划分验证集:从train中拆分一部分数据作为 “验证集”(带标签),用于评估模型在训练过程中的效果(类似test但非最终考核);
    • 忽略test1的无标签特性:如果需要 “有标签的测试集”,可以手动给test1的图片打标签(但工作量大),或直接用train拆分出的验证集替代。

    总结

    • train带标签:供模型学习 “输入(图片)与输出(标签)的关系”;
    • test1无标签:模拟真实预测场景,检验模型对未知数据的判断能力,符合机器学习 “训练 - 预测” 的完整流程。

    这种设计是机器学习数据集的常见模式,目的是更贴近实际应用场景,避免模型 “记住答案” 而非 “学习规律”。

    由此可见,训练和验证阶段,只需要用到train这个目录即可,test1这个目录可用可不用,后续测试也可以自己去找一些图片来进行。

    我们就先使用train这个目录

    那么,如何加载这个本地数据呢?

    由于手动下载的数据集结构与 tensorflow_datasets 期望的不同,我们要用 tf.keras.preprocessing 加载数据:

    import tensorflow as tf
    from tensorflow.keras import layers, models, callbacks
    import matplotlib.pyplot as plt
    import os# 设置中文显示
    plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]# 定义常量
    IMAGE_SIZE = (150, 150)
    BATCH_SIZE = 32
    EPOCHS = 15
    NUM_CLASSES = 2# 数据路径(根据实际修改)
    data_dir = r"C:\Users\admin\Desktop\cat_dog_datasets\train"# 创建数据加载器
    train_ds = tf.keras.preprocessing.image_dataset_from_directory(data_dir,validation_split=0.2,subset="training",seed=123,image_size=IMAGE_SIZE,batch_size=BATCH_SIZE,label_mode='binary'  # 二分类问题
    )val_ds = tf.keras.preprocessing.image_dataset_from_directory(data_dir,validation_split=0.2,subset="validation",seed=123,image_size=IMAGE_SIZE,batch_size=BATCH_SIZE,label_mode='binary'
    )# 数据增强(可选)
    data_augmentation = tf.keras.Sequential([layers.RandomFlip('horizontal'),layers.RandomRotation(0.2),
    ])# 构建模型
    def build_model():model = models.Sequential([data_augmentation,  # 数据增强层layers.Rescaling(1./255),  # 归一化layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)),layers.MaxPooling2D((2, 2)),layers.Conv2D(64, (3, 3), activation='relu'),layers.MaxPooling2D((2, 2)),layers.Conv2D(128, (3, 3), activation='relu'),layers.MaxPooling2D((2, 2)),layers.Flatten(),layers.Dense(128, activation='relu'),layers.Dropout(0.5),layers.Dense(1, activation='sigmoid')  # 二分类问题用sigmoid])model.compile(optimizer='adam',loss='binary_crossentropy',  # 二分类损失函数metrics=['accuracy'])return model# 其余代码(训练、评估、可视化)保持不变...

    运行时依然报错

    错误描述如下:

    ValueError: When passing `label_mode="binary"`, there must be exactly 2 class_names. Received: class_names=[]

    根据搜索和测试,发现上面的这种加载数据的方式,是识别目录名为类别的,也就是说,本来需要有cat和dog这两个目录,但是现在train里面没有这两个目录,所以没有标签,于是,就在train里面将猫和狗分成两个子目录存放。

    这样操作之后重新加载数据OK了,可以看到,20000用来训练,5000用来验证。

    处理模型构建的问题

    又遇到问题了

    ValueError: This model has not yet been built. Build the model first by calling `build()` or by calling the model on a batch of data.

    这个错误 ValueError: This model has not yet been built 通常在你尝试在模型未完全构建前访问其属性或方法时出现。在 TensorFlow 中,模型需要通过输入数据来推断各层的形状(shape),才能完成构建。以下是详细解释和解决方案:

    问题原因

    在 TensorFlow 中,模型的构建分为两个阶段:

    1. 定义结构:通过 Sequential() 或函数式 API 定义网络层。
    2. 构建模型:通过输入数据(或手动调用 build())让模型确定各层的输入输出形状。

    如果你在模型构建前(例如调用 model.summary() 或保存模型时)访问模型属性,就会触发此错误。

    常见触发场景

    1. 直接调用 model.summary():在未输入任何数据前调用。
    2. 保存空模型:尝试保存未训练的模型。
    3. 访问模型参数:如 model.layers 或 model.weights

    解决方案

    1. 先让模型处理一批数据

    在调用 model.summary() 或其他需要模型构建的操作前,先给模型喂一批数据:

    model = build_model()  # 定义模型# 构建模型:通过输入一批数据让模型推断形状
    dummy_input = tf.random.normal((1, 150, 150, 3))  # 批次大小1,图像尺寸150x150
    model(dummy_input)  # 关键:让模型处理数据# 现在可以安全地调用summary()
    model.summary()
    

    2. 手动调用 build() 方法(仅适用于 Sequential 模型)

    如果你使用 Sequential 模型,可以手动指定输入形状:

    model = build_model()# 手动构建模型,指定输入形状
    model.build(input_shape=(None, 150, 150, 3))  # None表示批次大小可变# 现在可以调用summary()
    model.summary()
    

    这里我们选择第一种方案。

    解决之后,最终的程序如下:

    import tensorflow as tf
    from tensorflow.keras import layers, models, callbacks
    import matplotlib.pyplot as plt
    import os# 设置中文显示
    plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]# 定义常量
    IMAGE_SIZE = (150, 150)
    BATCH_SIZE = 32
    EPOCHS = 15# 数据路径 - 修改为你解压数据集的路径
    data_dir = r"C:\Users\admin\Desktop\cat_dog\train"# 检查数据目录是否存在
    if not os.path.exists(data_dir):print(f"错误: 数据目录 '{data_dir}' 不存在!")print("请确保已将数据集解压到正确路径,目录结构应为:")print(r"C:\datasets\cats_vs_dogs\train\cat.0.jpg")print(r"C:\datasets\cats_vs_dogs\train\dog.0.jpg")exit(1)# 创建数据加载器
    print("正在加载数据集...")
    train_ds = tf.keras.preprocessing.image_dataset_from_directory(data_dir,validation_split=0.2,subset="training",seed=123,image_size=IMAGE_SIZE,batch_size=BATCH_SIZE,label_mode='binary'  # 二分类问题
    )val_ds = tf.keras.preprocessing.image_dataset_from_directory(data_dir,validation_split=0.2,subset="validation",seed=123,image_size=IMAGE_SIZE,batch_size=BATCH_SIZE,label_mode='binary'
    )# 数据增强
    data_augmentation = tf.keras.Sequential([layers.RandomFlip('horizontal'),layers.RandomRotation(0.2),layers.RandomZoom(0.2),
    ])# 构建模型
    def build_model():model = models.Sequential([data_augmentation,# 归一化layers.Rescaling(1./255),# 卷积层layers.Conv2D(32, (3, 3), activation='relu', padding='same'),layers.MaxPooling2D((2, 2)),layers.Conv2D(64, (3, 3), activation='relu', padding='same'),layers.MaxPooling2D((2, 2)),layers.Conv2D(128, (3, 3), activation='relu', padding='same'),layers.MaxPooling2D((2, 2)),# 全连接层layers.Flatten(),layers.Dense(128, activation='relu'),layers.Dropout(0.5),  # 防止过拟合layers.Dense(1, activation='sigmoid')  # 二分类问题])# 编译模型model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy'])return model# 创建模型
    model = build_model()# 构建模型:通过输入数据推断形状
    dummy_input = tf.random.normal((1, 150, 150, 3))
    model(dummy_input)# 现在可以安全地查看模型结构
    print("模型结构:")
    model.summary()# 可视化样本
    def visualize_samples(dataset):plt.figure(figsize=(10, 10))for images, labels in dataset.take(1):for i in range(9):ax = plt.subplot(3, 3, i + 1)plt.imshow(images[i].numpy().astype("uint8"))plt.title("猫" if labels[i].numpy() == 0 else "狗")plt.axis("off")plt.tight_layout()plt.show()print("数据样本可视化:")
    visualize_samples(train_ds)# 设置回调函数
    callbacks_list = [callbacks.EarlyStopping(monitor='val_loss',patience=3,restore_best_weights=True),callbacks.ModelCheckpoint(filepath='cat_dog_model.h5',monitor='val_accuracy',save_best_only=True,verbose=1),callbacks.ReduceLROnPlateau(monitor='val_loss',factor=0.2,patience=2,min_lr=0.00001)
    ]# 训练模型
    print("开始训练模型...")
    history = model.fit(train_ds,epochs=EPOCHS,validation_data=val_ds,callbacks=callbacks_list
    )# 评估模型
    print("在验证集上评估模型...")
    val_loss, val_acc = model.evaluate(val_ds)
    print(f"验证集准确率: {val_acc:.4f}")# 绘制训练历史
    def plot_training_history(history):plt.figure(figsize=(12, 4))# 绘制准确率曲线plt.subplot(1, 2, 1)plt.plot(history.history['accuracy'], label='训练准确率')plt.plot(history.history['val_accuracy'], label='验证准确率')plt.xlabel('Epoch')plt.ylabel('准确率')plt.legend()plt.title('训练和验证准确率')# 绘制损失曲线plt.subplot(1, 2, 2)plt.plot(history.history['loss'], label='训练损失')plt.plot(history.history['val_loss'], label='验证损失')plt.xlabel('Epoch')plt.ylabel('损失')plt.legend()plt.title('训练和验证损失')plt.tight_layout()plt.show()plot_training_history(history)# 预测示例
    def predict_example(model, dataset):plt.figure(figsize=(12, 12))for images, labels in dataset.take(1):predictions = model.predict(images)for i in range(9):ax = plt.subplot(3, 3, i + 1)plt.imshow(images[i].numpy().astype("uint8"))# 获取预测结果predicted_class = "猫" if predictions[i][0] < 0.5 else "狗"confidence = (1 - predictions[i][0]) * 100 if predicted_class == "猫" else predictions[i][0] * 100# 获取真实标签true_class = "猫" if labels[i].numpy() == 0 else "狗"plt.title(f"预测: {predicted_class} ({confidence:.1f}%)\n真实: {true_class}")plt.axis("off")plt.tight_layout()plt.show()print("模型预测示例:")
    predict_example(model, val_ds)print("训练完成! 模型已保存为 'cat_dog_model.h5'")

    我们可以直接执行这个程序了。

    程序解读

    程序能执行了,但是我们先不着急操作,为了后续能看懂执行过程和结果,我们先来解读下程序,看看都啥意思。

    1、导入必要的模块 

    1、导入必要的模块

    主要是导入tensorflow和keras,以及其他需要的库比如可视化matplotlib等等,按需导入即可

    2、防止中文显示乱码 

    2,可选,设置matplotlib的中文显示,防止显示时的乱码

    rcParams 是 Matplotlib 库中用于配置全局绘图参数的重要工具。通过修改 rcParams,你可以自定义图形的样式、字体、颜色、尺寸等属性,使所有图表保持一致的风格。

    rcParams 是一个类似字典的对象,存储了 Matplotlib 的默认配置。你可以通过以下方式访问和修改它:

    import matplotlib.pyplot as plt# 查看当前配置
    print(plt.rcParams)# 修改单个参数(示例:设置中文字体)
    plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]# 修改多个参数
    plt.rcParams.update({"figure.figsize": (10, 6),  # 图表大小"axes.grid": True,          # 显示网格线"lines.linewidth": 2,       # 线条宽度"font.size": 12             # 字体大小
    })

    我们这里用到的是字体设置

    plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]

    为啥这里在列表指定了三种字体?

    Matplotlib 会按顺序尝试列表中的字体,直到找到可用的字体,是为了防止有些字体不存在,如果所有指定字体都不存在,Matplotlib 会使用系统默认字体(可能无法正确显示中文)。

    找不到的提示:

    如果你的代码只在特定操作系统(如 Windows)上运行,且确保该系统预装了指定字体(如SimHei),也可以只指定一种字体,比如:

    plt.rcParams["font.family"] = "SimHei" # 只指定一种字体

    即使你认为环境可控,仍建议使用回退策略(指定多个字体),以提高代码的健壮性。

    3,定义必要的常量 

    3,定义必要的常量

    在深度学习中,IMAGE_SIZEBATCH_SIZE 和 EPOCHS 是三个核心超参数,控制着模型训练的流程和效率。以下是它们的具体含义和作用:


    IMAGE_SIZE(图像尺寸)

    • 含义:将输入图像统一调整为的尺寸(宽 × 高)。
    • 作用
      • 所有输入图像必须具有相同尺寸,因为深度学习模型的输入层维度是固定的。
      • 较大的尺寸保留更多细节,但增加计算量;较小的尺寸提高速度,但可能丢失关键信息。
    • 示例
      IMAGE_SIZE = (150, 150)  # 图像将被调整为150×150像素
      

    • 代码影响
      # 数据加载时调整图像尺寸
      train_ds = tf.keras.preprocessing.image_dataset_from_directory(data_dir,image_size=IMAGE_SIZE,  # 关键参数...
      )
      

    • 在将图片调整为统一尺寸时,缩放裁剪是两种常见方式,它们各有优缺点,且都会在一定程度上改变原始图像的呈现 —— 关键是根据场景选择更适合的方式,以减少对关键信息的损失。
    • 缩放和裁剪都会改变原始图像,没有完美的方案,但可通过策略减少关键信息损失。
    • 核心原则:让处理方式匹配图像的主体位置—— 主体居中用裁剪,边缘重要用缩放 + 填充,精准场景用智能裁剪。
    • 实际开发中,可尝试两种方式并对比模型效果(如准确率),选择更适合当前数据集的方案。

    BATCH_SIZE(批次大小)

    • 含义:每次训练时同时输入模型的样本数量。
    • 作用
      • 内存优化:GPU 内存有限,无法同时处理所有数据,因此将数据分成小批次。
      • 梯度稳定性:批次越大,梯度估计越稳定,但可能陷入局部最优;批次越小,随机性越强,可能帮助跳出局部最优。
    • 示例
      BATCH_SIZE = 32  # 每次训练使用32张图像
      
    • 代码影响
      train_ds = tf.keras.preprocessing.image_dataset_from_directory(...,batch_size=BATCH_SIZE,  # 关键参数
      )
      
    • 选择技巧
      • 较大的批次(如 64、128)适合大规模数据集和强大的 GPU。
      • 较小的批次(如 16、32)适合内存有限的环境或需要更多随机性的场景。
    • 当你设置 BATCH_SIZE = 32 时,模型会同时接收 32 张图片作为输入,计算它们的前向传播和梯度。这不是通过循环逐一处理图片,而是利用硬件的并行计算能力一次性处理。

    并行计算的实现层级

    Batch 的并行处理主要通过以下两种方式实现:

    (1)硬件层面的并行

    • GPU(图形处理器)
      GPU 由数千个小型计算核心组成,适合同时处理大量数据。当输入一个 Batch 时,GPU 会将计算任务分配给不同的核心:

      • 示例:32 张图片的卷积计算会被分割到不同核心,每个核心处理一部分像素或通道,最终汇总结果。
      • 优势:充分利用 GPU 的并行架构,速度比 CPU 快数十倍。
    • CPU
      CPU 也支持多线程并行,但核心数较少(通常 4-16 核),适合小批量或简单计算。TensorFlow 默认会自动利用 CPU 的多线程能力。

    (2)框架层面的优化

    深度学习框架(如 TensorFlow、PyTorch)会自动优化 Batch 的并行计算:

    • 向量化运算
      将 Batch 中的所有样本打包成一个高维张量(例如 [32, 150, 150, 3] 表示 32 张 150×150 的 RGB 图像),通过一次矩阵运算处理整个 Batch,而非循环处理每个样本。
    • 内存优化
      框架会将 Batch 数据预加载到 GPU 内存中,减少数据传输延迟,提高并行效率。

    Batch=32 的处理时间通常不会是 Batch=1 的 32 倍,而是远小于 32 倍(例如 5-10 倍),这证明了并行计算的效率。

    注意事项:并非 Batch 越大越好

    虽然 Batch 并行能提升效率,但过大的 Batch 可能导致:

    1. 内存溢出:GPU 内存无法容纳整个 Batch。
    2. 梯度稳定性问题:大 Batch 的梯度更新方向更稳定,但可能陷入局部最优。
    3. 训练速度下降:当 Batch 超过 GPU 并行处理能力的上限时,效率反而降低。

    EPOCHS(训练轮数)

    • 含义:整个训练数据集被模型 “遍历” 的次数。
    • 作用
      • 模型需要多次遍历数据才能学习到足够的特征。
      • 过多的轮数可能导致过拟合(模型在训练数据上表现好,但泛化能力差)。
    • 示例
      EPOCHS = 15  # 整个数据集将被训练15次
      
    • 代码影响
      history = model.fit(train_ds,epochs=EPOCHS,  # 关键参数...
      )
      
    • 选择技巧
      • 通过观察训练和验证损失曲线来调整:当验证损失开始上升时,停止训练(早停策略)。
      • 复杂任务可能需要更多轮次(如 100+),简单任务可能只需几轮。

    三者关系示例

    假设你有 1000 张训练图像:

    • BATCH_SIZE = 32:每次训练使用 32 张图像。
    • 每轮(Epoch)需要的步数:1000 ÷ 32 ≈ 32步
    • EPOCHS = 15:总共需要训练 32步/轮 × 15轮 = 480步

    总结

    参数含义影响
    IMAGE_SIZE输入图像尺寸计算量、模型复杂度、特征保留程度
    BATCH_SIZE每批次样本数内存使用、训练速度、梯度稳定性
    EPOCHS训练轮数模型学习深度、过拟合风险

    调参建议

    • IMAGE_SIZE:根据模型和数据特性选择,常见值有 128、150、224。
    • BATCH_SIZE:从 32 开始尝试,根据 GPU 内存调整(如内存不足可降至 16)。
    • EPOCHS:使用早停回调(EarlyStopping)自动确定最佳轮数。

    4、数据集路径设置 

    4、定义数据集所在路径,并确认路径是否存在

    5、加载数据 

    5、加载数据

    这里通过image_dataset_from_directory接口来加载数据,并将数据分为了训练集和验证集

    关于image_dataset_from_directory这个接口,参考:tf.keras.preprocessing.image_dataset_from_directory  |  TensorFlow v2.16.1

    看名字就是“从目录中加载图片数据”

    Generates a tf.data.Dataset from image files in a directory.

    这段代码使用了 TensorFlow 的image_dataset_from_directory函数从目录结构中加载图像数据,并将其划分为训练集和验证集。以下是对代码的详细解析:

    数据加载与预处理流程

    1. 训练集加载

      train_ds = tf.keras.preprocessing.image_dataset_from_directory(data_dir,                     # 数据集根目录validation_split=0.2,         # 保留20%的数据用于验证subset="training",            # 指定加载训练集部分seed=123,                     # 随机种子,确保划分可重复image_size=IMAGE_SIZE,        # 图像统一调整的尺寸batch_size=BATCH_SIZE,        # 每个批次的样本数label_mode='binary'           # 二分类标签模式
      )
      

    2. 验证集加载

      val_ds = tf.keras.preprocessing.image_dataset_from_directory(data_dir,                     # 与训练集相同的根目录validation_split=0.2,         # 保持相同的划分比例subset="validation",          # 指定加载验证集部分seed=123,                     # 必须与训练集相同的种子image_size=IMAGE_SIZE,        # 保持相同的图像尺寸batch_size=BATCH_SIZE,        # 批次大小label_mode='binary'           # 二分类标签模式
      )
      

    参数解析

    • data_dir:数据集根目录,目录结构应为:

    • data_dir/
      ├── class_a/
      │   ├── image_001.jpg
      │   └── image_002.jpg
      └── class_b/├── image_003.jpg└── image_004.jpg
      

      每个子目录名称即为类别标签。

    • validation_split=0.2:将数据集按 8:2 比例划分为训练集和验证集。

    • subset:指定加载 "training" 或 "validation" 子集。

    • seed=123:固定随机种子,确保训练集和验证集的划分一致。

    • image_size=IMAGE_SIZE:所有图像将被调整为指定尺寸(例如(224, 224))。

    • batch_size=BATCH_SIZE:每个批次包含的样本数,影响训练速度和内存使用。

    • label_mode='binary':适用于二分类问题,生成 0/1 标签。

    注意事项

    • 目录结构要求:必须严格遵循 "根目录 / 类别 / 图像" 的层次结构。
    • 随机种子一致性:训练集和验证集必须使用相同的seed,确保数据不重叠
    • 标签编码label_mode='binary'适用于二分类,多分类需使用label_mode='categorical'

    此代码片段实现了图像数据的高效加载与划分,为后续模型训练提供了标准化的输入数据流。

    6、数据增强 

    6、数据增强

    这段代码使用 TensorFlow 的 Keras API 创建了一个数据增强流水线,通过随机变换增加训练数据的多样性。数据增强是深度学习中常用的正则化技术,尤其在图像数据有限的情况下,可以有效提升模型的泛化能力。

    data_augmentation = tf.keras.Sequential([layers.RandomFlip('horizontal'),      # 随机水平翻转图像layers.RandomRotation(0.2),           # 随机旋转图像(±20% * 2π 弧度)layers.RandomZoom(0.2),               # 随机缩放图像(±20%)
    ])
    

    各层功能详解:

    • layers.RandomFlip('horizontal')

      • 作用:以 50% 的概率对图像进行水平翻转。
      • 适用场景:适用于左右对称的图像(如自然场景、人脸),但不适用于有明确方向的图像(如文本、箭头)。
    • layers.RandomRotation(0.2)

      • 作用:随机旋转图像,范围为 -0.2×2π 到 0.2×2π 弧度(约 ±36°)。
      • 参数含义0.2 表示旋转角度的比例因子,实际角度范围为 [-0.2×2π, 0.2×2π]
    • layers.RandomZoom(0.2)

      • 作用:随机缩放图像,在宽度和高度方向上独立进行缩放。
      • 参数含义0.2 表示缩放范围为原始尺寸的 [1-0.2, 1+0.2] = [0.8, 1.2] 倍。

    数据增强层可以在两种场景下使用:

    1. 模型内部(推荐):将增强层作为模型的第一层,在训练时实时处理数据:

      model = tf.keras.Sequential([data_augmentation,                # 增强层作为输入预处理layers.Rescaling(1./255),         # 归一化layers.Conv2D(16, 3, activation='relu'),# ... 后续网络层
      ])
      
    2. 数据集处理:在数据加载后、输入模型前应用增强:

      train_ds = train_ds.map(lambda x, y: (data_augmentation(x, training=True), y))
      

    注意事项

    1. 仅在训练时生效:数据增强只在训练阶段应用(training=True),推理时自动跳过。

    2. 避免信息丢失:旋转和缩放可能导致图像边缘信息丢失,需配合 fill_mode 参数(如 'nearest' 或 'reflect')处理空白区域。

    3. 组合增强效果:多种增强操作组合使用时,可能产生复杂变换(如翻转后再旋转),需根据数据特性调整顺序和参数。

    4. 性能影响:实时增强会增加训练计算开销,可通过 dataset.cache() 缓存增强结果(但需确保内存足够)。

    5. 扩展增强选项:还可添加其他增强层,如:

      layers.RandomContrast(0.2),          # 随机调整对比度
      layers.RandomTranslation(0.1, 0.1),  # 随机平移
      
     

    数据增强通过模拟真实世界中的图像变化,帮助模型学习更鲁棒的特征,尤其适用于医学图像、自动驾驶等数据有限的场景。

    7、构建模型

    7、构建模型 

    这段代码定义了一个用于图像二分类的卷积神经网络模型,包含数据增强、特征提取、分类器三个主要部分。以下是详细解析:

    models.Sequential()创建一个线性堆叠的神经网络模型,允许按顺序添加多个层(如卷积层、全连接层、激活函数等),形成一个完整的模型结构。

    各部分功能解析

    1. 数据增强层

    • 作用:通过随机翻转、旋转和缩放增加训练数据多样性,提升模型泛化能力。
    • 注意:仅在训练时生效,推理时自动跳过。

    2. 归一化层

    • 作用:将输入图像的像素值从 [0, 255] 缩放到 [0, 1],加速模型收敛。
    • 数学公式output = input / 255

    3. 卷积特征提取层

    • 结构:3 个卷积块,每个包含:
      1. 卷积层(Conv2D):使用 3×3 卷积核提取空间特征,padding='same' 保持输出尺寸与输入相同。
      2. 激活函数(ReLU):引入非线性,增强模型表达能力。ReLU(Rectified Linear Unit)是深度学习中最常用的激活函数之一,它为神经网络引入了非线性特性,是许多成功模型(如 ResNet、VGG)的核心组件。
      3. 最大池化层(MaxPooling2D):通过 2×2 窗口下采样,减少参数并捕获主要特征。
    • 参数变化:卷积核数量从 32→64→128,逐步提取更抽象的特征。

    4. 分类器层

    • Flatten:将卷积输出的多维张量展平为一维向量。
    • 全连接层(Dense):128 个神经元,进一步处理特征。
    • Dropout(0.5):训练时随机丢弃 50% 神经元,防止过拟合。
    • 输出层:1 个神经元 + sigmoid 激活函数,输出范围 [0, 1],适用于二分类问题(如猫 / 狗、正 / 负样本)。

    模型编译

    此模型结构简单但有效,适合初学者理解 CNN 基本原理,也可作为图像二分类任务的基线模型。

    8、查看模型结构 

    8、查看模型结构

    model.summary() 是 TensorFlow/Keras 中用于查看模型结构和参数统计信息的重要工具,它能帮助你快速了解模型的整体架构、每一层的输入输出形状以及参数数量。

    作用解析

    1. 显示模型架构

    打印模型的层次结构,包括每一层的名称、类型、输出形状和参数数量。

    2. 统计参数总量

    计算模型的总参数数量、可训练参数数量和不可训练参数数量(如 BatchNormalization 中的均值 / 方差)。

    3. 检查输入输出形状

    确认每一层的输入输出维度是否符合预期,帮助调试模型结构。

    示例

    假设有以下简单模型:

    from tensorflow.keras import models, layersmodel = models.Sequential([layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),layers.MaxPooling2D((2, 2)),layers.Flatten(),layers.Dense(10, activation='softmax')
    ])model.summary()
    

    输出结果

     
    Model: "sequential"
    _________________________________________________________________Layer (type)                Output Shape              Param #   
    =================================================================conv2d (Conv2D)             (None, 26, 26, 32)        320       max_pooling2d (MaxPooling2D  (None, 13, 13, 32)       0         )                                                               flatten (Flatten)           (None, 5408)              0         dense (Dense)               (None, 10)                54090     =================================================================
    Total params: 54,410
    Trainable params: 54,410
    Non-trainable params: 0
    _________________________________________________________________
    

    输出解读

    • 层信息

      • Layer (type):层的名称和类型(如 conv2d (Conv2D))。
      • Output Shape:输出张量的形状(None 表示批量大小可变)。
      • Param #:该层的参数数量。
    • 参数计算

      • 卷积层(kernel_size × input_channels + 1) × output_channels
        例如:(3×3×1 + 1)×32 = 320
      • 全连接层(input_units + 1) × output_units
        例如:(5408 + 1)×10 = 54090
    • 总参数量

      • Trainable params:可训练的参数(如权重、偏置)。
      • Non-trainable params:不可训练的参数(如 BatchNormalization 的均值 / 方差)。

    实际用途

    • 模型调试

      • 检查层的连接是否正确(如输入输出形状是否匹配)。
      • 发现意外的参数数量(如忘记使用 padding='same' 导致特征图尺寸骤减)。
    • 资源评估

      • 参数过多的模型可能导致过拟合或内存不足(如 GPU 显存溢出)。
      • 轻量级模型(如 MobileNet)通常参数较少,适合移动设备。
    • 文档记录

      • 作为模型设计的参考文档,便于团队协作和复现。

    注意事项

    • 动态模型:如果模型包含动态操作(如 Lambda 层或自定义层),summary() 可能无法显示完整信息。
    • 多输入 / 输出模型:使用 tf.keras.utils.plot_model() 可生成更直观的图形化表示。

    输出的形状啥意思?

    在 model.summary() 的输出中,Output Shape 描述了每一层输出张量的维度结构,帮助你理解数据在模型中的流动方式。下面结合具体示例详细解析其含义和计算方式:

    基本概念

    1. 张量维度表示

    • (None, ...)None 表示该维度的大小可变,通常对应批量大小(batch size)。例如:
      • (None, 224, 224, 3) 表示批量大小不确定,每张图片为 224×224 像素,3 个通道(RGB)。
    • 固定维度:如 (32,) 表示长度为 32 的向量。

    2. 常见层的输出形状计算

    层类型输入形状输出形状计算方式
    Conv2D(None, H, W, C_in)(None, H', W', C_out)
    其中:
    H' = (H + 2×padding - kernel_size)/stride + 1
    W' 同理
    MaxPooling2D(None, H, W, C)(None, H/stride, W/stride, C)(默认 pool_size=2stride=2
    Flatten(None, H, W, C)(None, H × W × C)
    Dense(None, input_units)(None, output_units)

    示例解析

    假设我们有以下模型:

     
    from tensorflow.keras import models, layersmodel = models.Sequential([layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1), padding='same'),layers.MaxPooling2D((2, 2)),layers.Conv2D(64, (3, 3), activation='relu', padding='same'),layers.MaxPooling2D((2, 2)),layers.Flatten(),layers.Dense(10, activation='softmax')
    ])model.summary()
    
     

    输出结果

     
    Model: "sequential"
    _________________________________________________________________Layer (type)                Output Shape              Param #   
    =================================================================conv2d (Conv2D)             (None, 28, 28, 32)        320       max_pooling2d (MaxPooling2D  (None, 14, 14, 32)       0         )                                                               conv2d_1 (Conv2D)           (None, 14, 14, 64)        18496     max_pooling2d_1 (MaxPooling  (None, 7, 7, 64)         0         2D)                                                             flatten (Flatten)           (None, 3136)              0         dense (Dense)               (None, 10)                31370     =================================================================
    Total params: 50,186
    Trainable params: 50,186
    Non-trainable params: 0
    _________________________________________________________________
    

    逐层分析输出形状

    1. 输入层

    • 期望输入input_shape=(28, 28, 1)(单通道 28×28 像素图像)
    • 实际形状(None, 28, 28, 1)(批量维度 + 空间维度 + 通道维度)

    2. 第一个卷积层 conv2d

    • 参数filters=32kernel_size=(3, 3)padding='same'
    • 输出形状(None, 28, 28, 32)
      • 计算
        • padding='same' 保证输出尺寸与输入相同(28×28)。
        • 卷积核数量为 32,因此通道数变为 32。

    3. 第一个池化层 max_pooling2d

    • 参数pool_size=(2, 2)stride=2(默认)
    • 输出形状(None, 14, 14, 32)
      • 计算
        • 空间尺寸减半:28 ÷ 2 = 14。
        • 通道数不变(仍为 32)。

    4. 第二个卷积层 conv2d_1

    • 参数filters=64kernel_size=(3, 3)padding='same'
    • 输出形状(None, 14, 14, 64)
      • 计算
        • padding='same' 保持空间尺寸(14×14)。
        • 卷积核数量为 64,通道数变为 64。

    5. 第二个池化层 max_pooling2d_1

    • 参数pool_size=(2, 2)stride=2
    • 输出形状(None, 7, 7, 64)
      • 计算
        • 空间尺寸减半:14 ÷ 2 = 7。
        • 通道数不变(仍为 64)。

    6. 展平层 flatten

    • 输出形状(None, 3136)
      • 计算
        • 7×7×64 = 3136(将空间维度和通道维度展平为一维向量)。

    7. 全连接层 dense

    • 参数units=10(10 个类别)
    • 输出形状(None, 10)
      • 含义:每个样本输出 10 个值,表示属于 10 个类别的概率分布。

    关键注意点

    • 批量维度 None

      • 模型在训练 / 推理时可接受任意批量大小的输入。
      • 例如,输入 32 张图片时,实际形状为 (32, 28, 28, 1)
    • 卷积层的 padding 参数

      • padding='valid'(默认):不补零,输出尺寸缩小。
        例如:输入 (28,28) → 3×3 卷积 → 输出 (26,26)。
      • padding='same':补零使输出尺寸与输入相同。
    • 池化层的影响

      • 池化操作通过降采样(如 2×2 池化)减少空间维度,同时保留主要特征。
      • 池化不改变通道数。
    • Flatten 层的作用

      • 将多维张量展平为一维向量,便于全连接层处理。
      • 展平后的维度 = 所有空间维度和通道维度的乘积。

    常见问题排查

    • 形状不匹配错误

      • 例如,全连接层期望输入 (None, 100),但实际输入为 (None, 50)
      • 解决方法:调整卷积层 / 池化层参数,或修改全连接层的输入单元数。
    • 意外的内存占用

      • 较大的特征图尺寸(如 (None, 224, 224, 512))会显著增加内存消耗。
      • 优化方法:添加更多池化层或使用步长更大的卷积。

    通过理解输出形状,你可以:

    • 验证模型结构是否符合设计预期。
    • 诊断层连接错误(如形状不匹配)。
    • 优化模型参数(如减少不必要的特征图尺寸)。

    9、模型训练 

    9、模型训练

    上面我们处理好了数据,然后构建好了模型,接下来就要开始训练了。

    这段代码使用 model.fit() 方法训练深度学习模型,下面详细解析其功能和参数:

    核心功能

    model.fit() 是 Keras 中最常用的模型训练方法,它会:

    1. 迭代训练数据:按批次(batch)从 train_ds 中获取数据。
    2. 前向传播:将输入数据传入模型,得到预测结果。
    3. 计算损失:根据配置的损失函数(如 binary_crossentropy)计算预测与真实标签的差异。
    4. 反向传播:使用优化器(如 Adam)根据损失梯度更新模型权重。
    5. 验证评估:每个 epoch 结束后,在 validation_data 上评估模型性能。

    参数解析

    history = model.fit(train_ds,                   # 训练数据集(包含输入和标签)epochs=EPOCHS,              # 训练轮数(整个数据集遍历次数)validation_data=val_ds,     # 验证数据集(可选)callbacks=callbacks_list    # 回调函数列表(可选)
    )
    

    关键参数

    • train_ds

      • 类型:tf.data.Dataset 或类似可迭代对象。
      • 作用:提供训练数据,通常包含输入图像和对应标签。
      • 示例:通过 image_dataset_from_directory 生成的数据集。
    • epochs

      • 类型:整数。
      • 作用:指定训练的轮数。1 个 epoch 表示模型完整遍历一次训练集。
      • 注意:过大的 epochs 可能导致过拟合,需配合早停(EarlyStopping)。
    • validation_data

      • 类型:与 train_ds 类似的验证数据集。
      • 作用:每个 epoch 结束后,在验证集上评估模型性能,监控泛化能力。
    • callbacks

      • 类型:回调函数列表。
      • 作用:在训练过程中执行特定操作(如保存模型、调整学习率)。
      • 常见回调:
        callbacks_list = [tf.keras.callbacks.EarlyStopping(      # 早停:当验证指标不再提升时停止训练patience=3,                        # 容忍验证集性能下降的最大轮数restore_best_weights=True          # 恢复最佳性能的模型权重),tf.keras.callbacks.ModelCheckpoint(    # 模型检查点:定期保存模型'best_model.h5',                   # 保存路径,默认保存在当前工作目录下(即运行脚本的目录)。monitor='val_accuracy',            # 监控指标save_best_only=True                # 只保存性能最好的模型),tf.keras.callbacks.ReduceLROnPlateau(  # 学习率调度:性能停滞时降低学习率factor=0.5,                        # 学习率降低因子patience=2                         # 容忍性能不提升的轮数)
        ]
        

    返回值 history

    • 类型:History 对象,包含训练过程的详细记录。
    • 主要属性:
      history.history = {'loss': [训练损失值列表],          # 每个 epoch 的训练损失'accuracy': [训练准确率列表],      # 每个 epoch 的训练准确率(若有)'val_loss': [验证损失值列表],      # 每个 epoch 的验证损失'val_accuracy': [验证准确率列表]   # 每个 epoch 的验证准确率(若有)
      }
      

    • 用途:可视化训练过程(如绘制损失曲线、准确率曲线)。

    model.fit() 是模型训练的核心接口,通过合理配置参数(如 epochscallbacks)和监控 history,可以高效训练出性能良好的模型。

    10、评估模型 

    10、评估模型

    这段代码使用 model.evaluate() 方法在验证集上评估模型的性能,下面详细解析其功能和使用场景:

    核心功能

    model.evaluate() 的主要作用是:

    1. 批量处理数据:按批次从验证集 val_ds 中获取数据。
    2. 计算预测结果:将输入数据传入模型,得到预测值。
    3. 评估指标:根据模型编译时指定的损失函数和评估指标(如 accuracy),计算整体性能。
    4. 返回结果:返回平均损失值和指定的评估指标值。

    参数解析

    val_loss, val_acc = model.evaluate(val_ds,                 # 验证数据集batch_size=None,        # 批量大小(默认使用数据集的原有设置)verbose=1               # 日志显示模式(0=静默,1=进度条,2=每轮一行)
    )
    

    关键参数

    • val_ds

      • 类型:tf.data.Dataset 或类似可迭代对象。
      • 作用:提供验证数据,通常包含输入图像和对应标签。
      • 示例:通过 image_dataset_from_directory 生成的验证集。
    • batch_size

      • 类型:整数或 None
      • 作用:指定评估时的批量大小。若为 None,则使用数据集的原有设置。
      • 注意:需根据 GPU 内存调整,避免溢出。
    • verbose

      • 类型:整数(0、1、2)。
      • 作用:控制评估过程的日志显示方式:
        • 0:不显示任何信息。
        • 1:显示进度条(默认值)。
        • 2:每个 epoch 显示一行信息。

    返回值

    • 单输出模型
      返回一个列表 [loss, metric1, metric2, ...],例如:

      val_loss, val_acc = model.evaluate(val_ds)
      
    • 多输出模型
      返回字典或列表,需根据模型结构解析,例如:

      results = model.evaluate(val_ds)
      # 假设模型有两个输出,编译时指定了 loss 和 accuracy
      val_loss = results[0]
      val_acc_output1 = results[1]
      val_acc_output2 = results[2]
      

    与 model.fit() 中验证的区别

    场景model.evaluate()model.fit(validation_data=val_ds)
    执行时机训练后手动调用每个 epoch 结束后自动执行
    主要用途最终模型评估训练过程中监控泛化能力
    返回值单个评估结果包含所有 epoch 的历史记录
    是否影响训练过程

    执行过程和结果

    执行上面的程序,有如下过程日志

    2025-07-10 11:06:45.575916: I tensorflow/core/util/port.cc:113] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.
    WARNING:tensorflow:From C:\Users\admin\AppData\Local\Programs\Python\Python310\lib\site-packages\keras\src\losses.py:2976: The name tf.losses.sparse_softmax_cross_entropy is deprecated. Please use tf.compat.v1.losses.sparse_softmax_cross_entropy instead.正在加载数据集...
    Found 25000 files belonging to 2 classes.
    Using 20000 files for training.
    2025-07-10 11:06:51.795921: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
    To enable the following instructions: SSE SSE2 SSE3 SSE4.1 SSE4.2 AVX2 AVX_VNNI FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
    Found 25000 files belonging to 2 classes.
    Using 5000 files for validation.
    WARNING:tensorflow:From C:\Users\admin\AppData\Local\Programs\Python\Python310\lib\site-packages\keras\src\backend.py:873: The name tf.get_default_graph is deprecated. Please use tf.compat.v1.get_default_graph instead.WARNING:tensorflow:From C:\Users\admin\AppData\Local\Programs\Python\Python310\lib\site-packages\keras\src\layers\pooling\max_pooling2d.py:161: The name tf.nn.max_pool is deprecated. Please use tf.nn.max_pool2d instead.WARNING:tensorflow:From C:\Users\admin\AppData\Local\Programs\Python\Python310\lib\site-packages\keras\src\optimizers\__init__.py:309: The name tf.train.Optimizer is deprecated. Please use tf.compat.v1.train.Optimizer instead.模型结构:
    Model: "sequential_1"
    _________________________________________________________________Layer (type)                Output Shape              Param #
    =================================================================sequential (Sequential)     (1, 150, 150, 3)          0rescaling (Rescaling)       (1, 150, 150, 3)          0conv2d (Conv2D)             (1, 150, 150, 32)         896max_pooling2d (MaxPooling2  (1, 75, 75, 32)           0D)conv2d_1 (Conv2D)           (1, 75, 75, 64)           18496max_pooling2d_1 (MaxPoolin  (1, 37, 37, 64)           0g2D)conv2d_2 (Conv2D)           (1, 37, 37, 128)          73856max_pooling2d_2 (MaxPoolin  (1, 18, 18, 128)          0g2D)flatten (Flatten)           (1, 41472)                0dense (Dense)               (1, 128)                  5308544dropout (Dropout)           (1, 128)                  0dense_1 (Dense)             (1, 1)                    129=================================================================
    Total params: 5401921 (20.61 MB)
    Trainable params: 5401921 (20.61 MB)
    Non-trainable params: 0 (0.00 Byte)
    _________________________________________________________________
    数据样本可视化:
    开始训练模型...
    Epoch 1/15
    WARNING:tensorflow:From C:\Users\admin\AppData\Local\Programs\Python\Python310\lib\site-packages\keras\src\utils\tf_utils.py:492: The name tf.ragged.RaggedTensorValue is deprecated. Please use tf.compat.v1.ragged.RaggedTensorValue instead.WARNING:tensorflow:From C:\Users\admin\AppData\Local\Programs\Python\Python310\lib\site-packages\keras\src\engine\base_layer_utils.py:384: The name tf.executing_eagerly_outside_functions is deprecated. Please use tf.compat.v1.executing_eagerly_outside_functions instead.625/625 [==============================] - ETA: 0s - loss: 0.6551 - accuracy: 0.6087   
    Epoch 1: val_accuracy improved from -inf to 0.70480, saving model to cat_dog_model.h5
    C:\Users\admin\AppData\Local\Programs\Python\Python310\lib\site-packages\keras\src\engine\training.py:3103: UserWarning: You are saving your model as an HDF5 file via `model.save()`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')`.saving_api.save_model(
    625/625 [==============================] - 76s 119ms/step - loss: 0.6551 - accuracy: 0.6087 - val_loss: 0.5729 - val_accuracy: 0.7048 - lr: 0.0010
    Epoch 2/15
    625/625 [==============================] - ETA: 0s - loss: 0.6008 - accuracy: 0.6787  
    Epoch 2: val_accuracy improved from 0.70480 to 0.72960, saving model to cat_dog_model.h5
    625/625 [==============================] - 100s 161ms/step - loss: 0.6008 - accuracy: 0.6787 - val_loss: 0.5377 - val_accuracy: 0.7296 - lr: 0.0010
    Epoch 3/15
    625/625 [==============================] - ETA: 0s - loss: 0.5733 - accuracy: 0.7018  
    Epoch 3: val_accuracy improved from 0.72960 to 0.73460, saving model to cat_dog_model.h5
    625/625 [==============================] - 76s 121ms/step - loss: 0.5733 - accuracy: 0.7018 - val_loss: 0.5409 - val_accuracy: 0.7346 - lr: 0.0010
    Epoch 4/15
    625/625 [==============================] - ETA: 0s - loss: 0.5541 - accuracy: 0.7177  
    Epoch 4: val_accuracy improved from 0.73460 to 0.73600, saving model to cat_dog_model.h5
    625/625 [==============================] - 75s 120ms/step - loss: 0.5541 - accuracy: 0.7177 - val_loss: 0.5313 - val_accuracy: 0.7360 - lr: 0.0010
    Epoch 5/15
    625/625 [==============================] - ETA: 0s - loss: 0.5296 - accuracy: 0.7360  
    Epoch 5: val_accuracy improved from 0.73600 to 0.76960, saving model to cat_dog_model.h5
    625/625 [==============================] - 76s 121ms/step - loss: 0.5296 - accuracy: 0.7360 - val_loss: 0.4685 - val_accuracy: 0.7696 - lr: 0.0010
    Epoch 6/15
    625/625 [==============================] - ETA: 0s - loss: 0.5090 - accuracy: 0.7517  
    Epoch 6: val_accuracy improved from 0.76960 to 0.78220, saving model to cat_dog_model.h5
    625/625 [==============================] - 75s 120ms/step - loss: 0.5090 - accuracy: 0.7517 - val_loss: 0.4561 - val_accuracy: 0.7822 - lr: 0.0010
    Epoch 7/15
    625/625 [==============================] - ETA: 0s - loss: 0.4927 - accuracy: 0.7645  
    Epoch 7: val_accuracy did not improve from 0.78220
    625/625 [==============================] - 76s 121ms/step - loss: 0.4927 - accuracy: 0.7645 - val_loss: 0.4759 - val_accuracy: 0.7732 - lr: 0.0010
    Epoch 8/15
    625/625 [==============================] - ETA: 0s - loss: 0.4756 - accuracy: 0.7749  
    Epoch 8: val_accuracy improved from 0.78220 to 0.78500, saving model to cat_dog_model.h5
    625/625 [==============================] - 75s 121ms/step - loss: 0.4756 - accuracy: 0.7749 - val_loss: 0.4481 - val_accuracy: 0.7850 - lr: 0.0010
    Epoch 9/15
    625/625 [==============================] - ETA: 0s - loss: 0.4615 - accuracy: 0.7822  
    Epoch 9: val_accuracy improved from 0.78500 to 0.81540, saving model to cat_dog_model.h5
    625/625 [==============================] - 75s 120ms/step - loss: 0.4615 - accuracy: 0.7822 - val_loss: 0.4063 - val_accuracy: 0.8154 - lr: 0.0010
    Epoch 10/15
    625/625 [==============================] - ETA: 0s - loss: 0.4490 - accuracy: 0.7888  
    Epoch 10: val_accuracy did not improve from 0.81540
    625/625 [==============================] - 85s 135ms/step - loss: 0.4490 - accuracy: 0.7888 - val_loss: 0.4098 - val_accuracy: 0.8110 - lr: 0.0010
    Epoch 11/15
    625/625 [==============================] - ETA: 0s - loss: 0.4366 - accuracy: 0.7990  
    Epoch 11: val_accuracy did not improve from 0.81540
    625/625 [==============================] - 76s 122ms/step - loss: 0.4366 - accuracy: 0.7990 - val_loss: 0.4241 - val_accuracy: 0.8080 - lr: 0.0010
    Epoch 12/15
    625/625 [==============================] - ETA: 0s - loss: 0.4027 - accuracy: 0.8181  
    Epoch 12: val_accuracy improved from 0.81540 to 0.82660, saving model to cat_dog_model.h5
    625/625 [==============================] - 76s 121ms/step - loss: 0.4027 - accuracy: 0.8181 - val_loss: 0.3826 - val_accuracy: 0.8266 - lr: 2.0000e-04
    Epoch 13/15
    625/625 [==============================] - ETA: 0s - loss: 0.3900 - accuracy: 0.8260  
    Epoch 13: val_accuracy did not improve from 0.82660
    625/625 [==============================] - 75s 120ms/step - loss: 0.3900 - accuracy: 0.8260 - val_loss: 0.3808 - val_accuracy: 0.8236 - lr: 2.0000e-04
    Epoch 14/15
    625/625 [==============================] - ETA: 0s - loss: 0.3818 - accuracy: 0.8314  
    Epoch 14: val_accuracy did not improve from 0.82660
    625/625 [==============================] - 76s 122ms/step - loss: 0.3818 - accuracy: 0.8314 - val_loss: 0.3919 - val_accuracy: 0.8250 - lr: 2.0000e-04
    Epoch 15/15
    625/625 [==============================] - ETA: 0s - loss: 0.3811 - accuracy: 0.8303  
    Epoch 15: val_accuracy improved from 0.82660 to 0.83400, saving model to cat_dog_model.h5
    625/625 [==============================] - 76s 122ms/step - loss: 0.3811 - accuracy: 0.8303 - val_loss: 0.3635 - val_accuracy: 0.8340 - lr: 2.0000e-04
    在验证集上评估模型...
    157/157 [==============================] - 5s 31ms/step - loss: 0.3635 - accuracy: 0.8340
    验证集准确率: 0.8340
    训练完成! 模型已保存为 'cat_dog_model.h5'

    还有几个可视化图:

    可以看到,准确率不断上升,损失函数逐渐减小。

    我们再来看看保存的模型:

    大小有60M+

    目标预测

    之前是训练和验证模型,然后将训练好的模型保存好,接下来,我们就用已经训练好的模型来进行预测。

    以下是一个完整的 Python 脚本,可以直接加载训练好的猫狗分类模型并对新图像进行预测。脚本包含模型加载、图像预处理和预测功能,可直接运行:

    import tensorflow as tf
    import numpy as np
    from PIL import Image
    import matplotlib.pyplot as plt# 设置中文显示
    plt.rcParams["font.family"] = "SimHei"def load_model(model_path):"""加载已训练好的模型"""try:model = tf.keras.models.load_model(model_path)print(f"模型已成功加载: {model_path}")return modelexcept Exception as e:print(f"加载模型时出错: {e}")return Nonedef preprocess_image(image_path, target_size=(150, 150)):"""预处理输入图像"""try:# 打开图像img = Image.open(image_path)# 调整大小,保持和训练时一致img = img.resize(target_size)# 转换为numpy数组img_array = np.array(img)# 添加批次维度img_array = np.expand_dims(img_array, axis=0)# 归一化(如果模型训练时使用了Rescaling层,这里不需要手动归一化)# img_array = img_array / 255.0return img_array, imgexcept Exception as e:print(f"处理图像时出错: {e}")return None, Nonedef predict_image(model, img_array, class_names=['猫', '狗']):"""对图像进行预测"""if model is None or img_array is None:return None, None# 进行预测predictions = model.predict(img_array)# 对于二分类问题,获取sigmoid输出并转换为类别if predictions.shape[1] == 1:  # sigmoid激活confidence = predictions[0][0]predicted_class = 0 if confidence < 0.5 else 1confidence = 1 - confidence if predicted_class == 0 else confidenceelse:  # softmax激活confidence = np.max(predictions)predicted_class = np.argmax(predictions)return class_names[predicted_class], confidencedef visualize_prediction(img, class_name, confidence):"""可视化预测结果"""plt.figure(figsize=(6, 4))plt.imshow(img)plt.axis('off')plt.title(f"预测结果: {class_name}\n置信度: {confidence:.2%}")plt.tight_layout()plt.show()if __name__ == "__main__":# 模型路径(根据实际情况修改)model_path = r"C:\Users\admin\Desktop\cat_dog_model.h5"# 待预测的图像路径(根据实际情况修改)image_path = r"C:\Users\admin\Desktop\2.jpg"# 加载模型model = load_model(model_path)if model:# 预处理图像img_array, original_img = preprocess_image(image_path)if img_array is not None:# 进行预测class_name, confidence = predict_image(model, img_array)if class_name:print(f"预测结果: {class_name}, 置信度: {confidence:.2%}")# 可视化结果visualize_prediction(original_img, class_name, confidence)

    结果如下:

    可以看到,置信度还是很高的。

    对于猫狗分类模型,如果给一个既不是猫也不是狗的图片,会咋样?试试看

    可以看到,存在误识别

    • 原生模型局限性:标准二分类模型无法拒绝未知类别,可能产生无意义的预测。

    • 改进方向:

      • 扩展训练数据,增加 “其他” 类别。

      • 通过置信度阈值或异常检测技术识别未知样本。

      • 使用更高级的开放集识别方法。

    在实际应用中,建议结合业务需求选择合适的方案,避免对未知样本的错误分类造成不良影响。

    查看模型

    直接参考:

    Netron的基本使用介绍-CSDN博客

    http://www.dtcms.com/a/272794.html

    相关文章:

  • P1722 矩阵 II 题解 DFS深度优先遍历与卡特兰数(Catalan number)解
  • Spring Boot+Redis+Caffeine 二级缓存架构的终极实现方案、包含万级QPS下的黄金配置参数、全文超过2500字(博君一赞)
  • XGBoosting算法详解(Boosting思想的代表算法)
  • C语言<数据结构-链表>
  • LangChain RAG 实战
  • Transformers 和 PyTorch 的区别与安装指南
  • Docker 高级管理--Dockerfile镜像制作
  • Context Engineering Framework 系统详细介绍
  • 链表算法之【合并两个有序链表】
  • 牛客笔试题 除2!
  • 读取按键的四种方式
  • IMU误差模型
  • 显卡GPU的架构和工作原理
  • 输入框过滤选项列表,el-checkbox-group单选
  • JDK 1.7 vs JDK 1.8
  • 为什么域名加端口访问需要放行端口?
  • 【算法训练营Day11】二叉树part1
  • c语言初阶 指针
  • CH9121T电路及配置详解
  • 【算法笔记 day three】滑动窗口(其他类型)
  • Spring Security 技术原理与实战全景详解
  • 【OD机试题解法笔记】根据IP查找城市
  • 观众信息设置与统计(视频高级分析与统计功能)
  • 身份认证缺陷
  • Gulp实现功能及插件总结
  • java并发包下CountDownLatch、Semaphore用法
  • 【牛客刷题】活动安排
  • i.mx8 网络速率测试
  • Transformer:自注意力驱动的神经网络革命引擎
  • 网络综合实验