使用Tensorflow和CNN进行猫狗图片训练的实战总结
关于Tensorflow可参考:认识Scikit-learn/PyTorch/TensorFlow这几个AI框架_python ai框架-CSDN博客
关于CNN可参考:
认识神经网络和深度学习-CSDN博客
CNN和图像识别有什么关系
CNN(卷积神经网络,Convolutional Neural Network)是图像识别领域中最核心、最主流的技术,二者的关系可以概括为:CNN 通过其独特的卷积机制,解决了图像识别中的关键难题,成为现代图像识别系统的 “引擎”。
一、图像识别的核心挑战
图像识别的目标是让计算机 “看懂” 图像(如判断图像中的物体类别、位置等),但原始图像存在两个核心难题:
数据维度高:一张普通的彩色图像(如 224×224 像素)包含 224×224×3=150,528 个像素值,直接用传统神经网络处理会导致参数爆炸(计算量过大)。
空间关联性强:图像的语义信息(如 “猫的耳朵”“汽车轮子”)依赖像素的局部空间关系(相邻像素共同构成特征),而非孤立的像素值。
二、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 的典型案例
- 图像分类:如 CIFAR-10、ImageNet 等数据集上的分类任务。
- 目标检测:结合 Faster R-CNN、YOLO 等算法,在 TensorFlow 中实现物体定位与识别。
- 语义分割:使用 U-Net、DeepLab 等模型对图像进行像素级分类。
- 人脸识别:通过 CNN 提取人脸特征,实现身份验证。
- 自动驾驶:处理车载摄像头图像,识别道路、车辆和行人。
三、实践案例:用 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 进行猫狗识别的完整示例,使用国内镜像源加载数据,并确保代码可以直接执行。这个示例包含数据加载、模型构建、训练和评估的完整流程,同时添加了详细的注释以便理解。
这个代码示例具有以下特点:
国内源加载:使用 try-except 结构尝试多种加载方式,优先使用国内镜像源。
完整流程:包含数据加载、预处理、模型构建、训练、评估和可视化的完整流程。
兼容性:移除了可能导致兼容性问题的参数,确保代码可直接执行。
可视化:包含数据样本和模型预测结果的可视化,方便理解。
模型优化:使用 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.jpg
、dog.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 中,模型的构建分为两个阶段:
- 定义结构:通过
Sequential()
或函数式 API 定义网络层。- 构建模型:通过输入数据(或手动调用
build()
)让模型确定各层的输入输出形状。如果你在模型构建前(例如调用
model.summary()
或保存模型时)访问模型属性,就会触发此错误。常见触发场景
- 直接调用
model.summary()
:在未输入任何数据前调用。- 保存空模型:尝试保存未训练的模型。
- 访问模型参数:如
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_SIZE
、BATCH_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 可能导致:
- 内存溢出:GPU 内存无法容纳整个 Batch。
- 梯度稳定性问题:大 Batch 的梯度更新方向更稳定,但可能陷入局部最优。
- 训练速度下降:当 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
函数从目录结构中加载图像数据,并将其划分为训练集和验证集。以下是对代码的详细解析:数据加载与预处理流程
训练集加载:
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' # 二分类标签模式 )
验证集加载:
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]
倍。数据增强层可以在两种场景下使用:
模型内部(推荐):将增强层作为模型的第一层,在训练时实时处理数据:
model = tf.keras.Sequential([data_augmentation, # 增强层作为输入预处理layers.Rescaling(1./255), # 归一化layers.Conv2D(16, 3, activation='relu'),# ... 后续网络层 ])
数据集处理:在数据加载后、输入模型前应用增强:
train_ds = train_ds.map(lambda x, y: (data_augmentation(x, training=True), y))
注意事项
仅在训练时生效:数据增强只在训练阶段应用(
training=True
),推理时自动跳过。避免信息丢失:旋转和缩放可能导致图像边缘信息丢失,需配合
fill_mode
参数(如'nearest'
或'reflect'
)处理空白区域。组合增强效果:多种增强操作组合使用时,可能产生复杂变换(如翻转后再旋转),需根据数据特性调整顺序和参数。
性能影响:实时增强会增加训练计算开销,可通过
dataset.cache()
缓存增强结果(但需确保内存足够)。扩展增强选项:还可添加其他增强层,如:
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 个卷积块,每个包含:
- 卷积层(Conv2D):使用 3×3 卷积核提取空间特征,
padding='same'
保持输出尺寸与输入相同。- 激活函数(ReLU):引入非线性,增强模型表达能力。
ReLU
(Rectified Linear Unit)是深度学习中最常用的激活函数之一,它为神经网络引入了非线性特性,是许多成功模型(如 ResNet、VGG)的核心组件。- 最大池化层(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=2
,stride=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=32
,kernel_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=64
,kernel_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 中最常用的模型训练方法,它会:
- 迭代训练数据:按批次(batch)从
train_ds
中获取数据。- 前向传播:将输入数据传入模型,得到预测结果。
- 计算损失:根据配置的损失函数(如
binary_crossentropy
)计算预测与真实标签的差异。- 反向传播:使用优化器(如
Adam
)根据损失梯度更新模型权重。- 验证评估:每个 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()
是模型训练的核心接口,通过合理配置参数(如epochs
、callbacks
)和监控history
,可以高效训练出性能良好的模型。
10、评估模型
10、评估模型
这段代码使用
model.evaluate()
方法在验证集上评估模型的性能,下面详细解析其功能和使用场景:核心功能
model.evaluate()
的主要作用是:
- 批量处理数据:按批次从验证集
val_ds
中获取数据。- 计算预测结果:将输入数据传入模型,得到预测值。
- 评估指标:根据模型编译时指定的损失函数和评估指标(如
accuracy
),计算整体性能。- 返回结果:返回平均损失值和指定的评估指标值。
参数解析
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博客