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

发布时间:2026/10/12 18:18:22

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 准确率确认模型没坏再拿一批网上下载的、分辨率各不相同、角度倾斜、光照偏暗的真实猫狗图片做一轮压力测试看模型的泛化能力到底在什么水平。这套猫狗识别资源我拆过不少相似的但这份代码在数据队列和模型保存上的处理是最接近生产流程的值得在本地完整跑一遍。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/12 18:18:22

JavaScript Date对象五大隐形陷阱与工程化解决方案

1. 为什么Date对象是前端开发里最常被低估的“隐形地雷”我带过不少刚转行的前端学员,也参与过多个团队的技术评审。每次遇到时间处理相关的Bug,几乎都绕不开一个看似简单、实则暗流汹涌的对象:Date。它不像Promise那样有明确的异步陷阱&…

2026/10/12 18:18:22

基于Spring Boot的物流管理系统毕设实战:从表设计到答辩复盘

毕设选题的那段时间,大多数人都会经历一轮"自我怀疑":既要避开难度太高的题目以免做不完,又怕太简单的题目在答辩时拿不出手。我当时挑来挑去,最后落在"基于 Spring Boot 的物流管理系统"这个方向上。说实话&…

2026/10/12 18:13:22

百张宽表字段重名危机:Text2SQL 生成中别名引用歧义的消解策略

在企业数仓从星型模型一路野蛮生长到百张大宽表之后,Text2SQL 最大的敌人早已不是“SQL 语法生成错误”,而是漫山遍野的同名字段灾难。 当业务在对话框输入“统计各省份上月的成交金额和创建时间”时,底层的元数据检索模块(Schema…

2026/10/12 20:48:49

从零搭建音视频通话:为什么我选实时音视频SDK而非自研

1. 从零搭建一个可用的音视频通话:为什么我选了实时音视频SDK而不是自研做音视频通话这件事,听起来好像就是“打开摄像头、采集麦克风、编码、推流、拉流、解码、播放”这么一条链路,但真正动过手的人都知道,这条链路上随便一个环…

2026/10/12 20:48:49

从零手写Huffman编码:设计、实现与避坑指南

1. 从一个压缩场景说起:为什么还要手写Huffman前阵子帮一个做嵌入式日志系统的朋友看代码,他们设备端每天要往Flash里写几十万条运行日志,存储芯片容量有限,日志又不能随便丢,于是想在写入前做一层无损压缩。他一开始想…

2026/10/12 20:48:49

S/4HANA 1909实操指南:从安装、升级到Fiori与HANA调优

简介:面向SAP实施顾问、企业财务负责人及信息化决策者,这份PDF资料围绕S/4HANA本地部署版1909的联合企业会计(JVA)模块,系统梳理了其功能全景、演进路线与实施要点。内容依托石油天然气等行业的联合经营场景&#xff0…

2026/10/12 20:48:49

Node-RED 高并发流控实战:用 queue-gate 实现背压与稳定吞吐

简介:这是一份面向Node-RED开发者与IoT系统集成工程师的实用扩展节点资源,用于精细化控制消息流并实现带缓冲能力的逻辑门控。该节点弥补了标准gate节点缺乏队列管理的短板,支持open/closed/queueing三种状态切换,可按序释放、预览…

2026/10/12 20:48:49

27B大模型部署实战:显存、量化与终端配置全解析

1. 先搞清楚 27B 这个体量到底卡在哪很多人一上来就问"27B 要几张卡",这个问题本身就问偏了。27B 参数量的模型,真正决定你能不能跑起来、跑得爽的,不是卡的数量,而是显存带宽、显存容量、量化档位这三者之间的平衡。我…

2026/10/12 20:43:47

策略模式在半导体可靠性测试上位机中的应用实践

我先坦白一个判断:但凡做过半导体测试系统上位机的人,看到“策略模式”四个字,多半会先愣一下——因为这活儿听起来太“业务重、学问深”了,怎么和设计模式扯上关系?但实际上,我恰恰是在一个可靠性测试机上…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/12 12:01:26

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/12 0:04:22

绝缘子缺陷检测数据集清洗与工业级训练实战指南

简介:本资源是面向电力AI研发人员、工业视觉工程师及智能巡检系统开发者的绝缘子缺陷检测专用YOLO格式数据集,解决无人机航拍场景下绝缘子破损、污闪、积雪等9类典型缺陷的精准识别与定位难题。数据集共2139张真实巡检图像(含训练/验证/测试集…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑