
简介本资源是一份面向深度学习入门与计算机视觉实践者的Python案例源码围绕ResNet50预训练模型提取猫狗大战图像特征再以逻辑回归完成二分类任务帮助读者理解深度特征与浅层分类器的融合流程。压缩包共43个文件约907KB以25个py脚本为核心辅以11个pyc缓存、3张png图示、1个pickle特征文件及txt、json、md等说明配置目录涵盖数据构建、模型训练与工具模块结构清晰便于按步骤复现。目前已有219人学习下载。读者可据此掌握加载预训练ResNet50、移除顶层提取特征向量、训练逻辑回归并评估准确率与F1等指标的完整链路同时获得可运行的工程脚本与配置参考适合作为课程设计或入门迁移学习的实操素材。1. 从一份猫狗分类源码说起ResNet50 提特征接逻辑回归到底怎么落地很多人第一次接触迁移学习都是从猫狗大战这个经典数据集开始的。但真正让新手卡住的往往不是模型本身而是「特征提取 传统分类器」这条链路到底怎么串起来。这份ResNet50提取猫狗大战特征后用逻辑回归-python源码.zip就是冲着这个痛点来的它把 ResNet50 当作固定特征提取器把每张图压成一个 2048 维向量再喂给逻辑回归做二分类。整套流程不涉及反向传播微调训练快、显存占用低特别适合刚配好 Python 环境、想跑通一个完整深度学习案例的人。源码包里带了dogs_vs_cats_config.py、train_model.py、build_dogs_vs_cats.py等文件还有一份说明.txt和README.md结构上已经帮你把数据构建、特征提取、模型训练三段拆开了。如果你正在找一份能直接跑、又能看清每一步在干什么的 Python 源码案例设计这份资源值得拆开看看。2. 拆开源码包目录结构与 ResNet50 特征提取链路2.1 先看清文件树别急着跑 train_model.py拿到压缩包解压后根目录下大致是这么几块dogs_vs_cats/放配置和训练入口models/放网络定义和训练脚本customize/和tools/是辅助工具output/用来存中间产物。很多人一上来就python train_model.py结果报路径错误或者找不到 pickle 文件就是因为没先理清依赖顺序。常见做法是先把dogs_vs_cats_config.py打开里面通常定义了数据集根目录、图像尺寸、batch size、特征保存路径这些常量。我一般会先确认三个路径原始猫狗图片放在哪、ResNet50 预训练权重从哪加载、提取好的特征 pickle 存到哪。源码里有个dogs_vs_cats.pickle这就是特征提取完之后的缓存文件后续逻辑回归直接读它不用每次重新过一遍 CNN。# dogs_vs_cats_config.py 典型配置项 import os # 数据集根目录里面应有 train/cat、train/dog 等子文件夹 DATASET_ROOT os.path.expanduser(~/data/dogs_vs_cats) # ResNet50 输入尺寸Keras 默认 224x224 IMAGE_SIZE (224, 224) # 特征提取时的 batch size显存小就调成 8 或 16 BATCH_SIZE 32 # 提取后的特征缓存路径 FEATURE_PICKLE os.path.join(output, dogs_vs_cats.pickle) # 逻辑回归训练时的测试集比例 TEST_SPLIT 0.2这段配置里最容易被忽略的是IMAGE_SIZE。ResNet50 在 ImageNet 上预训练时用的是 224×224如果你改成 299 或 331卷积层的权重形状对不上Keras 会直接报错。另一个坑是BATCH_SIZE它只影响特征提取阶段的速度和显存不影响最终特征维度——每张图出来永远是 2048 维跟 batch 多大没关系。2.2 ResNet50 去掉顶层之后输出到底是什么ResNet50 原始结构最后是一个 1000 类的全连接层用来做 ImageNet 分类。这份源码的做法是加载imagenet权重然后include_topFalse再把全局平均池化接上得到每张图的 2048 维特征向量。这一步是整个案例的核心CNN 不再负责分类只负责把图像翻译成一组有判别力的数字。from tensorflow.keras.applications import ResNet50 from tensorflow.keras.applications.resnet50 import preprocess_input from tensorflow.keras.preprocessing.image import img_to_array, load_img import numpy as np # 加载预训练 ResNet50去掉顶层分类头 base_model ResNet50(weightsimagenet, include_topFalse, poolingavg) def extract_feature(image_path): # 加载图片并统一尺寸 img load_img(image_path, target_size(224, 224)) arr img_to_array(img) # 扩展 batch 维度变成 (1, 224, 224, 3) arr np.expand_dims(arr, axis0) # 按 ImageNet 均值方差做归一化别漏 arr preprocess_input(arr) # 前向传播拿到 2048 维向量 feature base_model.predict(arr, verbose0) return feature.flatten()poolingavg这个参数很关键。如果不加include_topFalse出来的是(None, 7, 7, 2048)的四维张量你还得自己写 GlobalAveragePooling2D。加上之后直接得到(None, 2048)省事且不容易出错。preprocess_input也不能省它把像素从 0-255 转到以 ImageNet 统计量为准的分布少了这一步准确率会明显往下掉属于血泪经验。2.3 批量提取特征并落盘成 pickle单张提取太慢源码里build_dogs_vs_cats.py干的就是批量遍历、批量预测、批量存 pickle 的活。逻辑不复杂但有几个细节决定你能不能一次跑通。import os import pickle from tensorflow.keras.preprocessing.image import load_img, img_to_array from tensorflow.keras.applications.resnet50 import preprocess_input import numpy as np def build_features(image_paths, labels, model, batch_size32): features, targets [], [] batch_imgs, batch_labels [], [] for path, label in zip(image_paths, labels): img load_img(path, target_size(224, 224)) arr img_to_array(img) batch_imgs.append(arr) batch_labels.append(label) if len(batch_imgs) batch_size: batch preprocess_input(np.array(batch_imgs)) feats model.predict(batch, verbose0) features.extend(feats) targets.extend(batch_labels) batch_imgs, batch_labels [], [] # 处理最后不足一个 batch 的剩余图片 if batch_imgs: batch preprocess_input(np.array(batch_imgs)) feats model.predict(batch, verbose0) features.extend(feats) targets.extend(batch_labels) return np.array(features), np.array(targets)这里最容易翻车的是最后那个if batch_imgs。如果图片总数不是 batch_size 的整数倍不处理剩余部分就会丢样本而且丢的往往是排序靠后的狗图或猫图导致类别不平衡。另一个细节是preprocess_input要放在np.array之后因为它内部按通道做减法传 list 进去会报类型错误。存 pickle 的时候用pickle.dump((features, targets), f)读的时候对应pickle.load别用joblib混着来源码里统一用的 pickle。3. 逻辑回归接棒从 2048 维特征到猫狗概率3.1 为什么用逻辑回归而不是直接 softmaxResNet50 提完特征之后你完全可以再接一个全连接层做微调但这份源码选择逻辑回归理由很实在特征已经是 2048 维的高层语义表示线性可分性已经很强逻辑回归训练快、可解释、不需要 GPU。对于猫狗这种二分类任务逻辑回归在 2048 维空间里找一超平面往往就能到 95% 以上的验证准确率。而且 sklearn 的LogisticRegression几行就能跑完适合教学和快速验证。from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, classification_report import pickle # 读取上一步存好的特征 with open(output/dogs_vs_cats.pickle, rb) as f: features, labels pickle.load(f) # 划分训练集和测试集stratify 保证猫狗比例一致 X_train, X_test, y_train, y_test train_test_split( features, labels, test_size0.2, random_state42, stratifylabels ) # 训练逻辑回归max_iter 调大避免收敛警告 clf LogisticRegression(max_iter1000, C1.0, solverlbfgs) clf.fit(X_train, y_train) # 评估 pred clf.predict(X_test) print(准确率:, accuracy_score(y_test, pred)) print(classification_report(y_test, pred, target_names[cat, dog]))stratifylabels这个参数建议加上。猫狗图片数量如果不完全相等随机切分可能导致测试集里猫多狗少准确率虚高。max_iter1000也是常见调整默认 100 在某些特征尺度下会不收敛sklearn 会甩一堆警告看着心烦。C是正则化强度的倒数C 越大正则越弱2048 维特征下我一般从 1.0 开始试过拟合就降到 0.1。3.2 特征归一化对逻辑回归的影响ResNet50 出来的 2048 维向量各维度数值范围差异不小。逻辑回归基于梯度下降特征尺度不统一会拖慢收敛甚至影响最终精度。源码里如果没做标准化建议自己补上。from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline # 把标准化和逻辑回归串成 pipeline clf make_pipeline( StandardScaler(), LogisticRegression(max_iter1000, C1.0) ) clf.fit(X_train, y_train)用make_pipeline的好处是标准化只在训练集上 fit然后 transform 测试集避免数据泄漏。如果你手动先对整个数据集做StandardScaler().fit_transform测试集的均值方差就泄到训练里了虽然在这个案例里影响不大但习惯要养好。标准化之后收敛速度通常快一截max_iter甚至可以降到 500。3.3 用 pickle 串起两阶段从图片到预测结果源码包里train_model.py大概率就是把特征提取和逻辑回归串起来的主入口。理解了这个链路你就能自己改造成三分类、多分类或者换别的预训练模型。import pickle from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score # 假设特征已经由 build_dogs_vs_cats.py 生成 with open(output/dogs_vs_cats.pickle, rb) as f: X, y pickle.load(f) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) clf LogisticRegression(max_iter1000) clf.fit(X_train, y_train) # 保存训练好的分类器方便后续预测新图片 with open(output/logistic_model.pkl, wb) as f: pickle.dump(clf, f) print(测试集准确率:, accuracy_score(y_test, clf.predict(X_test)))保存分类器这一步很多人漏掉导致每次预测新图都要重新训练。把clf存成 pkl之后写一个predict.py加载 ResNet50 提特征、加载逻辑回归分类两步就能输出猫狗概率。注意 ResNet50 每次加载都要几秒如果做批量预测把模型加载放在循环外面。4. 避坑与排查跑这份源码时最容易翻车的五个地方4.1 报错 “No module named tensorflow” 或 Keras 版本冲突现象import tensorflow直接失败或者from tensorflow.keras.applications import ResNet50报 AttributeError。原因通常是环境里装了独立的 Keras 而不是 TensorFlow 内置的 Keras或者 TensorFlow 版本低于 2.x。解决统一用pip install tensorflow2.10或更高版本卸载独立的keras包。如果你用 conda建一个干净环境conda create -n dogs python3.8再装 tensorflow别在 base 环境里折腾。4.2 特征提取到一半内存爆掉现象跑build_dogs_vs_cats.py时进程被 kill或者报 MemoryError。原因把所有图片一次性读进内存再批量预测猫狗大战训练集有 25000 张224×224×3 的 float 数组一张就占 600KB 左右全读进来十几个 G。解决源码里如果是生成器逐批读取就没事如果是先load_img全存 list改成边读边预测边释放。我一般会把batch_imgs控制在 32 以内每批预测完立刻置空。4.3 逻辑回归准确率只有 50% 左右现象训练完测试集准确率跟瞎猜差不多。原因通常是标签和特征没对齐比如特征列表是按 cat 在前 dog 在后排的标签却是按文件夹遍历顺序生成的两者错位。解决在build_features里确保features.append和targets.append在同一轮循环里成对出现别分开写两个循环。另一个可能是preprocess_input漏了特征分布不对逻辑回归学不到东西。4.4 pickle 文件读写报 UnpicklingError现象pickle.load时报UnpicklingError: invalid load key。原因文件没写完就中断了或者用文本模式open(..., r)读二进制 pickle。解决写的时候用wb读的时候用rb确认文件大小不为 0。如果中断过删掉重新跑一遍特征提取别试图修复半截 pickle。4.5 预测新图片时类别反了现象明明是一只猫预测成狗的概率 0.9。原因逻辑回归的classes_顺序不一定是你以为的[0, 1]对应[cat, dog]它按标签排序。解决训练完打印clf.classes_确认 0 和 1 分别代表什么。如果标签用的是字符串cat、dogclasses_会按字母序排cat在前。预测时用clf.predict_proba拿到概率后对照classes_解读别硬编码 0.5 阈值对应猫。5. 进阶玩法换模型、调阈值、做批量预测5.1 把 ResNet50 换成 MobileNetV2 或 EfficientNet这份源码的结构是「特征提取器 逻辑回归」特征提取器是可插拔的。把ResNet50(weightsimagenet, include_topFalse, poolingavg)换成MobileNetV2(weightsimagenet, include_topFalse, poolingavg)输出维度从 2048 变成 1280逻辑回归照样能跑。MobileNetV2 更轻CPU 上提取特征快很多适合没有 GPU 的环境。EfficientNetB0 输出 1280 维精度通常比 ResNet50 还高一点但输入尺寸是 224 时要注意它内部的预处理和 ResNet 不一样得用对应的preprocess_input。模型特征维度参数量适用场景ResNet50204825.6M精度优先有 GPUMobileNetV212803.5MCPU 环境追求速度EfficientNetB012805.3M精度与速度平衡换模型之后记得重新跑一遍特征提取pickle 文件要重新生成别拿旧特征配新模型。5.2 调整分类阈值观察 Precision/Recall 变化逻辑回归默认阈值 0.5但猫狗分类里如果你更在意别把狗认成猫可以把阈值调高。用predict_proba拿到狗的概率然后自己卡阈值。import numpy as np from sklearn.metrics import precision_score, recall_score # 假设 clf.classes_ [cat, dog]取 dog 的概率列 proba clf.predict_proba(X_test)[:, 1] for thresh in [0.3, 0.4, 0.5, 0.6, 0.7]: pred (proba thresh).astype(int) p precision_score(y_test, pred, pos_label1) r recall_score(y_test, pred, pos_label1) print(f阈值 {thresh}: 精确率{p:.3f}, 召回率{r:.3f})这个技巧在实际业务里很有用。比如你做一个猫狗门禁误把狗放进来比误把猫拦在外面更麻烦那就把狗的阈值调高牺牲一点召回换精确。源码里没带这个分析但加上去就十几行能让你对模型行为理解更深。5.3 写一个批量预测脚本把结果落成 CSV训练完模型总要用起来。我一般会写一个predict_folder.py遍历指定文件夹下所有图片输出文件名和预测概率存成 CSV 方便后续核对。import os import csv import pickle import numpy as np from tensorflow.keras.applications import ResNet50 from tensorflow.keras.applications.resnet50 import preprocess_input from tensorflow.keras.preprocessing.image import load_img, img_to_array # 加载特征提取器和分类器 base_model ResNet50(weightsimagenet, include_topFalse, poolingavg) with open(output/logistic_model.pkl, rb) as f: clf pickle.load(f) def predict_folder(folder, out_csv): rows [] for fname in os.listdir(folder): if not fname.lower().endswith((.jpg, .jpeg, .png)): continue path os.path.join(folder, fname) img load_img(path, target_size(224, 224)) arr preprocess_input(np.expand_dims(img_to_array(img), 0)) feat base_model.predict(arr, verbose0) prob clf.predict_proba(feat)[0] # clf.classes_ 顺序决定哪一列是 dog dog_prob prob[list(clf.classes_).index(dog)] if dog in clf.classes_ else prob[1] rows.append([fname, f{dog_prob:.4f}, dog if dog_prob 0.5 else cat]) with open(out_csv, w, newline) as f: writer csv.writer(f) writer.writerow([filename, dog_prob, prediction]) writer.writerows(rows) predict_folder(test_images, output/predictions.csv)这个脚本里clf.classes_的处理是重点。如果你训练时标签用的是 0/1classes_就是[0, 1]index(dog)会报错得改成prob[1]。我一般训练时直接用字符串标签省得后面猜。批量预测时 ResNet50 加载一次就行别在循环里反复实例化否则跑 100 张图能等好几分钟。从那以后我每次拿到这种「预训练特征 传统分类器」的源码都强制先跑一遍小样本比如 200 张图验证链路通不通再上全量数据。这样即使中间有路径错误、标签错位也能在几分钟内发现不用等半小时才看到准确率不对。希望这份拆解能帮你把这份源码顺利跑起来少走点弯路。本文还有配套的精品资源点击获取