TensorFlow卷积神经网络猫狗识别实战:从数据预处理到模型保存

发布时间:2026/10/12 18:18:53
TensorFlow卷积神经网络猫狗识别实战:从数据预处理到模型保存 简介针对深度学习初学者一份详解基于Python与TensorFlow搭建卷积神经网络CNN实现猫狗识别的PDF文档系统梳理了从数据准备到模型预测的完整项目流程。单个PDF文件大小仅78KB内容紧凑不占用存储空间适合碎片时间学习已有4282人学习/浏览热度在同类入门资料中较为突出。文档以Kaggle猫狗数据集为例深入讲解get_files()函数读取图片并按文件名标注猫狗标签、将12500张猫图和12500张狗图存入列表并打乱顺序再通过图像裁剪填充与标准化处理以及基于tf.train.slice_input_producer和tf.train.batch构建批次输入队列的方法随后展开CNN设计涵盖卷积层、池化层、全连接层、Dropout防过拟合、交叉熵损失与Adam优化器并介绍Saver模型保存及验证集评估帮助读者掌握训练和调优的完整流程。文中附有核心代码片段和实现思路能帮助读者快速复现项目理解深度学习的工程细节适合作为进一步学习迁移学习或更复杂网络结构的起点。1. 卷积神经网络落地猫狗识别这份资源到底能帮你省掉多少事如果你正在找一份能直接复现的猫狗识别项目而不是那种只讲概念、代码缺胳膊少腿的教程这份基于 TensorFlow 的卷积神经网络实现应该能让你少走不少弯路。它把 Kaggle 上经典的 12500 张猫图、12500 张狗图数据集从数据读取、批次生成、网络搭建到模型训练完整串了起来核心代码分成input_data.py、model.py和训练脚本三块逻辑相当清晰。适合刚入门 CNN、想搞懂 TensorFlow 数据队列和模型保存机制的开发者也适合准备做图像分类项目但不想从零造轮子的从业者。跟完整个流程你对图片数据怎么进模型、卷积池化全连接怎么衔接、训练好的模型如何存盘会有一个非常具体的体感。2. 数据处理层get_files 与 get_batch 的读图流程2.1 get_files 按文件名打标签的逻辑这份资源的数据读取从get_files(file_dir)开始。它做的事情很直接遍历目录下所有文件根据文件名中是否包含cat或dog来区分标签猫标为0狗标为1然后把图片路径和标签各自放进数组最后用np.hstack合并、转置、打乱顺序。def get_files(file_dir): cats [] label_cats [] dogs [] label_dogs [] for file in os.listdir(file_dir): name file.split(sep.) if cat in name[0]: cats.append(file_dir file) label_cats.append(0) else: if dog in name[0]: dogs.append(file_dir file) label_dogs.append(1) image_list np.hstack((cats, dogs)) label_list np.hstack((label_cats, label_dogs)) temp np.array([image_list, label_list]) temp temp.transpose() np.random.shuffle(temp) image_list list(temp[:, 0]) label_list list(temp[:, 1]) label_list [int(i) for i in label_list] return image_list, label_list这段代码的关键不在遍历本身而在打乱顺序这一步。如果不做np.random.shuffle(temp)训练时每个 batch 里全是同一类图片梯度更新会来回震荡我实际跑的时候发现准确率会卡在 50% 附近上不去。把 image 和 label 绑在同一个临时数组里再打乱能保证路径和标签的对应关系不散。另一个细节是name[0]取的是文件名按.分割后的第一个字段像cat.1.jpg这类命名方式正好命中但如果你的图片命名是cat_1.jpgsplit(sep.)的结果就完全不同需要把分隔符改成_才能正确分类。2.2 get_batch 的队列化图片处理get_batch()是这份资源里含金量比较高的一段它把图片路径数组转成了 TensorFlow 能消费的队列数据。先tf.cast把 image 转成tf.string、label 转成tf.int32然后用tf.train.slice_input_producer建队列每读取一个样本就解码 JPEG、裁剪或填充到统一尺寸、做标准化最后通过tf.train.batch按批次输出。def get_batch(image, label, image_W, image_H, batch_size, capacity): image tf.cast(image, tf.string) label tf.cast(label, tf.int32) input_queue tf.train.slice_input_producer([image, label]) label input_queue[1] image_contents tf.read_file(input_queue[0]) image tf.image.decode_jpeg(image_contents, channels3) image tf.image.resize_image_with_crop_or_pad(image, image_W, image_H) image tf.image.per_image_standardization(image) image_batch, label_batch tf.train.batch([image, label], batch_sizebatch_size, num_threads64, capacitycapacity) label_batch tf.reshape(label_batch, [batch_size]) image_batch tf.cast(image_batch, tf.float32) return image_batch, label_batch这里值得注意的有三个参数。image_W和image_H是最终输入网络的图片尺寸资源里设置的是 208×208这个大小对 GPU 显存不算友好但 CPU 训练时倒是比较适中。capacity是队列容量资源里取 256我一般建议至少是batch_size的 4 到 8 倍太小会让队列频繁空转太大则占内存。num_threads64看起来吓人实际上如果你的机器核心数没那么多这个值并不会让训练更快反而可能因为线程切换开销导致 CPU 飙高我自己的经验是设成 4 到 8 就够用了。tf.image.per_image_standardization这一步经常被忽略它把每个通道的像素减去均值再除以方差让输入特征分布在零附近这对卷积网络的收敛速度帮助很大。你可以在训练时对比去掉这行代码的效果loss 下降会明显变慢而且初期波动更剧烈。3. 网络结构从 conv1 到 softmax 的模型长什么样3.1 conv1 到 pooling1_lrn第一层卷积核的选择model.py里的网络结构是典型的小型 CNN没有用预训练模型也没有复杂残差结构就是靠卷积、池化、LRN、全连接一层层堆出来的。第一层卷积conv1的卷积核是[3, 3, 3, 16]意思是 3×3 的窗口大小、输入通道数为 3RGB、输出 16 个 feature map。with tf.variable_scope(conv1) as scope: weights tf.get_variable(weights, shape[3, 3, 3, 16], dtypetf.float32, initializertf.truncated_normal_initializer(stddev0.1, dtypetf.float32)) biases tf.get_variable(biases, shape[16], dtypetf.float32, initializertf.constant_initializer(0.1)) conv tf.nn.conv2d(images, weights, strides[1, 1, 1, 1], paddingSAME) pre_activation tf.nn.bias_add(conv, biases) conv1 tf.nn.relu(pre_activation, namescope.name)为什么第一层就用 16 个 feature map对 208×208 的输入来说3×3 卷积滑一遍后尺寸不变SAME padding16 个通道相当于提取了 16 种不同的低级特征比如边缘、颜色块、纹理方向。这个数量对猫狗分类这种相对粗粒度的任务足够往上加到 64 或 128 也不是不行但参数量会翻好几倍在 25000 张图的规模下收益不大。stddev0.1是权重初始化的标准差如果设成 1.0深层网络很容易梯度爆炸我见过直接 loss 变成 NaN 的情况。池化层用的是max_poolksize[1, 3, 3, 1]、strides[1, 2, 2, 1]也就是 3×3 的池化窗口步长为 2能把特征图尺寸从 208×208 压到 104×104。资源里还在池化后加了一层tf.nn.lrn这个 LRN局部响应归一化在 AlexNet 时代比较流行作用是让响应比较大的神经元相对更强、响应小的被抑制。现在的 ResNet、EfficientNet 里已经不太用 LRN 了但这套代码里保留它并不会坏事只是训练速度会稍微慢一点。3.2 conv2 到 softmax从特征图到分类输出第二层卷积conv2的输入是上一层的 16 通道特征图卷积核尺寸为[3, 3, 16, 16]输出仍然是 16 个 feature map。这里输入输出通道数一样意味着这一层主要做的是空间特征组合而不是通道扩展。紧接着又是一个max_pool但这次strides[1, 1, 1, 1]池化后尺寸只从 104×104 变成 103×103 左右压缩幅度比第一层温和得多。with tf.variable_scope(local3) as scope: reshape tf.reshape(pool2, shape[batch_size, -1]) dim reshape.get_shape()[1].value weights tf.get_variable(weights, shape[dim, 128], dtypetf.float32, initializertf.truncated_normal_initializer(stddev0.005, dtypetf.float32)) biases tf.get_variable(biases, shape[128], dtypetf.float32, initializertf.constant_initializer(0.1)) local3 tf.nn.relu(tf.matmul(reshape, weights) biases, namescope.name)从pool2到local3之间有个关键操作tf.reshape(pool2, shape[batch_size, -1])-1表示自动推断维度把三维特征图展平成一维向量然后接全连接层。这里的dim是展平后的特征数量资源里用reshape.get_shape()[1].value动态获取避免了手写死数字可能造成的维度不匹配。展平、乘权重、加偏置、过 ReLU然后接第二个全连接层local4128→128最后是softmax_linear输出维度 2对应猫和狗两个类别的 logits。整个网络的参数规模大致如下层名参数 shape参数量输出尺寸conv1[3, 3, 3, 16]448208×208×16pooling1_lrn——104×104×16conv2[3, 3, 16, 16]2320104×104×16pooling2_lrn——103×103×16local3[dim, 128]about 21 万128local4[128, 128]16512128softmax_linear[128, 2]2582我实际跑下来的感觉是这个网络在 25000 张图上训练准确率能到 85% 左右但再往上走就比较吃力了原因是网络容量有限也没有做数据增强。如果你用这份代码训练到后期发现 loss 不再下降可以考虑在conv2后面再加一层[3, 3, 16, 32]的卷积然后把local3的节点数从 128 提到 256效果会有明显改观。4. 训练主控run_training 的参数设置与模型保存4.1 训练主循环step、loss 与 accuracy 的节奏训练脚本里把前面两个模块整合成了run_training()函数核心参数如下IMG_W208、IMG_H208、BATCH_SIZE32、CAPACITY256、MAX_STEP15000、learning_rate0.0001。这些参数的选择有实际考量批量大小 32 在 CPU 上跑不会让内存爆炸学习率 0.0001 对 Adam 优化器来说偏保守但换来了训练稳定性。def run_training(): train_dir /path/to/your/train_images/ logs_train_dir /path/to/your/save/model/ train, train_label input_data.get_files(train_dir) train_batch, train_label_batch input_data.get_batch(train, train_label, IMG_W, IMG_H, BATCH_SIZE, CAPACITY) train_logits model.inference(train_batch, BATCH_SIZE, N_CLASSES) train_loss model.losses(train_logits, train_label_batch) train_op model.trainning(train_loss, learning_rate) train__acc model.evaluation(train_logits, train_label_batch) summary_op tf.summary.merge_all() sess tf.Session() train_writer tf.summary.FileWriter(logs_train_dir, sess.graph) saver tf.train.Saver() sess.run(tf.global_variables_initializer()) coord tf.train.Coordinator() threads tf.train.start_queue_runners(sesssess, coordcoord)这里有几个容易忽略的点。tf.train.start_queue_runners必须调用否则slice_input_producer创建的队列不会自动填充数据sess.run会一直卡住不返回。资源里用Coordinator来管理线程生命周期这是一种相对规范的写法训练结束时先coord.request_stop()再coord.join(threads)能避免线程残留导致的程序崩溃。每隔 50 步打印一次 loss 和 accuracy这个频率在调试时比较舒服不会刷屏又能及时发现问题。我一般还会在每 200 步打印一次当前 batch 里的真实标签分布确认队列读到的不是全猫或全狗。4.2 模型保存与 checkpoint 的恢复资源在每 2000 步保存一次模型用的是saver.save(sess, checkpoint_path, global_stepstep)保存路径是logs_train_dir下的model.ckpt实际生成的文件会带上-2000、-4000这样的后缀。文件名作用model.ckpt-2000.index变量名与张量形状的索引model.ckpt-2000.data-00000-of-00001变量具体数值model.ckpt-2000.meta计算图的元数据恢复结构时用checkpoint最近一次保存的模型路径记录如果你是训练到一半想接着训加载时需要用saver.restore(sess, tf.train.latest_checkpoint(logs_train_dir))它会自动找到checkpoint文件里记录的最新模型编号。有个细节global_step参数会让保存的文件名带上步数但如果tf.train.Saver()创建时没有指定max_to_keep默认只保留最后 5 个 checkpoint磁盘紧张的项目里要注意这点可以在Saver()里加max_to_keep20或者训完手动清理旧文件。这里有一个我自己踩过的坑如果训练中断后恢复learning_rate还是按初始值继续并没有做学习率衰减。对 15000 步的训练来说后面几千步的学习率其实可以降一半甚至更少能有效减少 loss 的震荡。你可以把learning_rate定义成tf.train.exponential_decay的形式让它随global_step自动减小但资源里的原版代码没有这一步这算是第一个可以优化的点。5. 避坑这 5 个 TensorFlow 老问题最容易被新人碰到5.1 现象训练卡住不动CPU 占用率很低原因tf.train.slice_input_producer创建的队列需要start_queue_runners才能启动填充线程如果只sess.run(tf.global_variables_initializer())就进入主循环队列一直为空sess.run([train_op, train_loss, train__acc])会永久阻塞。解决训练前必须执行threads tf.train.start_queue_runners(sesssess, coordcoord)并在结束时用coord.request_stop()和coord.join(threads)收尾。资源代码里这个顺序是完整的但如果你自己重构代码时漏掉这一行就会复现这个现象。5.2 现象训练时 loss 在某个值附近反复横跳准确率上不去原因get_files打乱顺序后每个 batch 内仍然可能只是某一类图片尤其是数据集没有 shuffle 充分时。另一个常见原因是learning_rate0.0001对 Adam 来说后期偏大导致权重在小范围内震荡。解决先把get_files里的np.random.shuffle(temp)确认执行了再检查tf.train.batch是否设置了shuffleTrue。资源里用的是tf.train.batch它是按顺序取队列里的样本如果前面打乱得不够充分建议换成tf.train.shuffle_batch并设置min_after_dequeuecapacity//2这样每个 batch 都有随机性。5.3 现象图片解码报错或者尺寸对不上原因数据集中可能混入了非 JPEG 格式的图片或者图片本身就是灰度图decode_jpeg(..., channels3)在读灰度图时不会报错但读 PNG 或 BMP 会直接抛异常。解决把tf.image.decode_jpeg改成tf.image.decode_image它能自适应多种格式但要注意decode_image返回的通道数不固定需要手动tf.image.resize_image_with_crop_or_pad后再用tf.image.per_image_standardization。如果还是报错可以写一个脚本先过滤掉无法解码的图片文件常见做法是用 PIL 批量打开再转存 JPEG。5.4 现象标准化后图片显示全黑或全白原因per_image_standardization会把像素值变成零均值单位方差范围一般在 [-2, 2] 之间直接用plt.imshow会把它当成 0-1 或 0-255 范围显示结果就是一片黑或一片白。解决可视化验证时把标准化后的数据逆操作回去常见做法是img img * std mean或者直接在看图时用np.clip(img * 128 128, 0, 255)。这只影响调试不影响模型训练。5.5 现象训练后期准确率到 85% 左右就停了再训也上不去原因这个网络结构本身容量有限二分类任务在 25000 张图上的特征区分度不够强而且没有数据增强、没有 dropout、没有学习率衰减。解决按顺序做三件事。第一在local4和softmax_linear之间插入tf.nn.dropout(local4, keep_prob0.5)注意keep_prob训练时设 0.5测试时设 1.0。第二把训练集做数据增强比如随机翻转、微调亮度、随机裁剪我一般会用tf.image.random_flip_left_right和tf.image.random_brightness代码量不大但能明显改善泛化。第三把learning_rate改成指数衰减初始值 0.0001衰减率 0.9衰减步长 2000这几步做完准确率能往上走 5 到 8 个百分点。6. 模型持久化与自写负载测试给模型加一个可复现的验证程序训练脚本末尾用saver.save(sess, checkpoint_path, global_stepstep)把整个会话里的变量存盘这一步是后续做预测的前提。你会发现保存目录下多出了好几种文件其中.index记录变量名与形状、.data存具体数值、.meta存计算图结构。恢复模型的标准做法是先加载.meta重建图结构再restore恢复变量值但如果你是在同一个 Python 进程里接着训练直接用saver.restore(sess, tf.train.latest_checkpoint(logs_train_dir))更省事。def predict_one_image(sess, image_path, model_path): image_data tf.gfile.FastGFile(image_path, rb).read() image tf.image.decode_jpeg(image_data, channels3) image tf.image.resize_image_with_crop_or_pad(image, IMG_W, IMG_H) image tf.image.per_image_standardization(image) image tf.expand_dims(image, 0) image tf.cast(image, tf.float32) saver tf.train.import_meta_graph(model_path .meta) saver.restore(sess, model_path) logits sess.run(softmax_linear/softmax_linear:0, feed_dict{Placeholder:0: image.eval()}) return logits上面这段代码是我自己项目里常用的预测模板softmax_linear/softmax_linear:0是模型输出 tensor 的名字Placeholder:0是输入占位符这两个名字取决于model.py里实际定义的variable_scope和name。如果你仿照这份资源的inference()函数搭建新网络要先去 TensorBoard 的 graph 里确认节点名称否则sess.run会报找不到 tensor 的错误。预测时有一个真实的工程细节加载模型后图片的预处理必须与训练保持一致包括裁剪尺寸、标准化方法、是否翻转等任何一项不一致都会导致输出的概率分布异常。我遇到过有人在训练时用resize_image_with_crop_or_pad预测时却用tf.image.resize_images直接拉伸结果同一张图的预测分数完全对不上这不是模型问题而是数据分布变了。做完这些之后我每次拿到一套新训练好的模型都会强制自己走一遍完整的验证流程先用训练集里的图片跑 top-1 准确率确认模型没坏再拿一批网上下载的、分辨率各不相同、角度倾斜、光照偏暗的真实猫狗图片做一轮压力测试看模型的泛化能力到底在什么水平。这套猫狗识别资源我拆过不少相似的但这份代码在数据队列和模型保存上的处理是最接近生产流程的值得在本地完整跑一遍。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询