首页 / 资讯中心 / 文章详情

TensorFlow图片分类实战:从数据准备到CNN模型训练全流程

TensorFlow图片分类实战:从数据准备到CNN模型训练全流程 ★ FEATURED ARTICLE
最近正好有个活儿需要给一批产品图片做自动分类——不是什么高深的项目就是某公司仓库里的零件照片按型号分到不同文件夹里。我以前用过几回预训练模型做迁移学习但这次图便宜直接从零搭了个TensorFlow的分类模型来训练自己的图片数据。整个过程跑下来踩了一堆坑也摸出了一些门道。趁着热乎劲儿还在我把这套流程整理成一篇可以直接照着做的实战笔记给那些也想用TensorFlow训练自己图片分类模型的朋友当个参考。这篇内容的重点不在理论而在“怎么把自己手里的图片变成TensorFlow能吃的格式然后顺利训练出一个能用的分类器”。我会从数据目录怎么摆放、图片怎么读入、模型怎么搭、训练参数怎么调到训练中经常莫名其妙的几个报错和现象一步步说清楚。适合那些已经装好了TensorFlow、但还不知道怎么处理自己数据的初学者也适合被项目逼着赶进度、想少走弯路的开发者。我不会贴大段大段的官方文档也不会堆一堆看不懂的网络结构图。所有代码都是我实际跑过的版本所有“坑”都是我实际操作中遇到并被填平的。你拿过去改改路径基本就能用。1. 训练自己的图片分类模型到底在训练什么先绕开代码和技术名词说点大实话。很多人一听说“用TensorFlow训练分类自己的图片数据”觉得是个多高大上的事情想象中要搞懂卷积神经网络、反向传播、梯度下降一堆数学基础。其实真到动手跑项目这一步这些东西大部分都被TensorFlow封装好了你要做的核心事情其实就是“把图片喂给模型告诉它每张图属于什么类别让它自己找规律”。拿我这次做的产品分类来说本质就是给模型看成千上万张零件照片每张照片都提前贴好了标签——这个属于A型号那个属于B型号。模型看多了以后自己会总结出A型号通常有哪种形状、哪种颜色特征B型号又有哪里不一样。等它训练完你再给它一张全新的、从来没见过的零件照片它就能根据之前总结的规律判断“这八成是A型号或者是B型号”。1.1 用生活类比理解“分类模型”的训练过程这个训练过程可以类比成教一个小孩子认水果。你拿着一个苹果告诉他“这是苹果”拿着一个香蕉告诉他“这是香蕉”来回教很多遍。教的时候孩子其实是在观察每个水果的颜色、形状、大小这些特征。等教得足够多了你再拿一个他从没见过的苹果他也能脱口而出“苹果”。神经网络干的事和这个几乎一模一样只不过孩子用的是眼睛和大脑模型用的是像素数值和数学计算。你说“这是苹果”的过程就是给图片打标签教很多遍就是反复迭代训练最后认出新苹果就是模型的测试和预测阶段。这么一想很多东西就通了。1.2 为什么很多人卡在“自己数据”这一步市面上关于TensorFlow分类的教程十个里面有九个用的是MNIST手写数字、CIFAR-10这类现成数据集。官方或者教程作者已经把数据下载、解压、整理、打标签、切成训练集和验证集这些脏活累活全干完了你只需要跑几行代码就能看到准确率蹭蹭往上涨。但到了自己的项目里情况完全不一样图片可能分散在各个文件夹里文件名乱七八糟尺寸大小不一有的是jpg有的是png类别数量还得自己数。很多人就是死在这一步——模型代码都背下来了但不知道怎么把自己硬盘里的图片变成模型能读的东西。所以这篇文章会重点讲数据准备这一块因为这才是“自己的图片数据”和MNIST的最大区别。2. 数据准备目录结构决定了你后面顺不顺在我见过的所有入门项目里数据目录怎么摆放直接决定了你后面写数据加载的代码是简单还是绕。TensorFlow里有一个很好用的工具你只要把图片按照“每个类别一个文件夹”的方式放好它就能自动帮你读图、标标签、做预处理几乎不需要手写任何图片读取逻辑。我最推荐的做法也是最省心的做法就是在项目根目录下建一个data文件夹里面分两个子文件夹train和validation。train里面是训练用的图片validation里面是验证用的图片。这两个文件夹内部再按类别各自建子文件夹。拿我的零件分类举例目录是这样的data/ ├── train/ │ ├── A型号/ │ │ ├── a_001.jpg │ │ ├── a_002.jpg │ │ └── ... │ ├── B型号/ │ │ ├── b_001.jpg │ │ └── ... │ └── C型号/ │ ├── c_001.jpg │ └── ... └── validation/ ├── A型号/ ├── B型号/ └── C型号/注意一个细节train和validation里面的类别子文件夹名字必须完全一致因为工具是按文件夹名字来生成标签的。名字不一样的话它会认为这是两个完全不同的类别训练和验证的标签就对不上了后面验证准确率会非常乱让你完全摸不着头脑。2.1 训练集和验证集怎么划分比例多少合适这个问题我看网上人问过很多次。训练集和验证集的比例没有绝对标准但有一个经验法则如果你的数据总量不大比如每个类别只有一两百张图那验证集可以稍微多一些比如留出20%到30%如果数据量很大每个类别有几千张那验证集留10%到15%就差不多了。我这次每个类别大概有800多张图我是从里面随机抽了20%到validation文件夹剩下80%留在train文件夹。这个比例跑下来效果不错。你要是懒得自己写脚本抽图可以直接在文件管理器里手动拉过去虽然费点劲但也不是不能用。更省事的方法是写个小脚本shutil.copy过去这里就不展开讲了。还有一个关键点验证集绝对不能和训练集有重复的图片。有些人图省事直接把训练集里的一部分图片复制到验证集这样训练的时候模型已经把验证集里的图“背”下来了验证准确率高得离谱但一到真实场景就拉胯属于典型的自欺欺人。验证集的意义是模拟“模型没见过的图片”有重复就没意义了。2.2 图片数据质量比数量更重要收集数据的时候很多人第一反应是往死里堆数据。但根据我个人体会图片质量的重要性远超数量。这里说的质量包括几个方面图片的清晰度、目标在画面中的占比、背景的干扰程度、以及类别间是否有明显的视觉差异。如果你的训练图片每张都拍得很清晰目标物居中背景干净那即使每个类别只有500张效果也可能比1000张模糊的、角度千奇百怪的图要好。当然如果你想做一个特别鲁棒的模型那确实需要模拟各种实际会遇到的场景比如光线变化、角度偏移、遮挡之类的这属于进阶玩法新手先不用考虑那么多把图拍清楚画面干净类别差异大就已经赢了。3. 从文件夹到TensorFlow数据集ImageDataGenerator的正确用法数据目录准备好了接下来就是把图片变成TensorFlow能处理的格式。这里我强烈建议直接用ImageDataGenerator它可以说是处理中小规模图片分类最舒服的工具了。为啥因为它可以一边读图片、一边做数据预处理还能顺带做数据增强训练的时候实时地把图片一小批一小批喂给模型内存不会爆炸。它不是一次性把所有图片都塞进内存这点非常重要。你自己写代码用的无非就是两套。一套是把所有图片读成数组numpy再喂给模型如果数据有几万张内存容易被干爆。另一套是自己写生成器太麻烦。ImageDataGenerator把这两种方案的缺点都规避掉了你用起来会非常轻松。3.1 基础用法代码实测可用下面这份代码是我这次项目实际用的版本我稍微简化了一下把注释写在里面。你照着改一下目录路径就行。import tensorflow as tf from tensorflow.keras.preprocessing.image import ImageDataGenerator # 训练数据生成器这里同时做了数据增强 train_datagen ImageDataGenerator( rescale1.0/255, # 像素值缩放到0-1之间让模型训练更稳定 rotation_range20, # 随机旋转20度相当于多了很多角度不同的样本 width_shift_range0.2, # 水平随机平移20% height_shift_range0.2, # 垂直随机平移20% shear_range0.2, # 剪切变换 zoom_range0.2, # 随机缩放 horizontal_flipTrue, # 随机水平翻转 fill_modenearest # 变换后空白区域用最近邻填充 ) # 验证数据生成器注意这里只做缩放不做增强 val_datagen ImageDataGenerator(rescale1.0/255) train_generator train_datagen.flow_from_directory( data/train, target_size(224, 224), # 所有图片统一resize成224x224 batch_size32, # 每批32张图 class_modecategorical, # 多分类用categorical二分类可以用binary shuffleTrue # 每个epoch都打乱顺序 ) validation_generator val_datagen.flow_from_directory( data/validation, target_size(224, 224), batch_size32, class_modecategorical, shuffleFalse # 验证集通常不打乱方便看结果 )运行这段代码后TensorFlow会打印出每个文件夹里找到多少张图片、分了几个类别比如“Found 2000 images belonging to 3 classes”。看到这个输出说明数据读取这块就通了。3.2 训练集做数据增强验证集为什么不做这里很多人不理解训练集要做那么多随机旋转、翻转、缩放验证集却只做像素缩放为什么不能一刀切都用同一个生成器道理是这样的数据增强的目的是让模型看到更多“变体”提高它的泛化能力相当于免费给它扩充了训练样本。但验证集是用来衡量模型真实水平的它应该尽量贴近真实场景。如果你在验证集上也做随机旋转、翻转那你每次评估模型的时候输入验证集的图片都是随机变换过的结果就不稳定了今天跑出一个准确率明天跑出另一个没法客观比较模型好坏。打个比方你去考试的时候考题是固定的不会因为今天心情好就把卷子里的数字全换个顺序。验证集就是那张固定的考卷不能被随意改动。我用下来这个配置其实很通用图片分类任务只要不是特别小的数据集这套增强参数基本都适用。如果你的图片本身就是正着的比如扫描件、证件照那种horizontal_flip建议关掉不然把字翻转了模型会彻底学乱。我这次零件照片无所谓正反所以开着没问题。3.3 冷知识batch_size怎么定batch_size32这个数字看起来平平无奇其实是有讲究的。它表示模型每看32张图才更新一次参数。如果batch_size太小比如2那模型每次只看2张图就更新参数梯度方向会很乱训练过程像喝醉了酒一样左摇右晃虽然也可能收敛但会很慢。如果batch_size太大比如512每次看512张图才更新一次虽然梯度方向很稳但内存容易吃紧而且有时候会陷入局部最优解。32到64是图片分类任务比较黄金的范围绝大多数情况下不用刻意去调。GPU性能很强可以试128调大之后训练速度会快一点但准确率不一定有提升。新手就老老实实用32等跑通了再折腾。4. 模型怎么搭从零搭一个够用的CNN模型数据搞定了接下来是模型部分。对于“训练分类自己的图片数据”这个需求模型选择其实就两条路一是从零搭建一个简单的卷积神经网络CNN二是用现成的预训练模型做迁移学习。我先说从零搭的情况毕竟这个最适合理解原理而且代码短。4.1 一个简单但能用的模型结构我的需求就是区分三种型号的零件不算特别复杂的任务用不着上ResNet这种几十层的大模型。我搭了一个比较经典的“卷积池化”堆叠结构from tensorflow.keras import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout model Sequential([ Conv2D(32, (3, 3), activationrelu, input_shape(224, 224, 3)), MaxPooling2D(2, 2), Conv2D(64, (3, 3), activationrelu), MaxPooling2D(2, 2), Conv2D(128, (3, 3), activationrelu), MaxPooling2D(2, 2), Flatten(), Dense(512, activationrelu), Dropout(0.5), Dense(3, activationsoftmax) # 3个类别对应3个输出节点 ]) model.compile( optimizeradam, losscategorical_crossentropy, metrics[accuracy] )这个结构就是典型的CNN三板斧卷积层负责提取特征比如边缘、纹理、形状池化层负责压缩特征图减少计算量同时保留关键信息全连接层负责把提取到的特征映射到具体类别上。三组卷积池化堆叠下来模型不算深但足以应付中等复杂度的图片分类任务了。最后一层的Dense(3, activationsoftmax)是重点这个3必须和你实际的类别数一致。如果分类数不同这里就要改。softmax的作用是输出一个概率分布比如模型判断某张图有0.1的概率是A型号、0.85的概率是B型号、0.05的概率是C型号然后取最大概率对应的类别作为预测结果。Dropout(0.5)是做随机失活训练的时候让一半的神经单元随机“罢工”防止模型死记硬背训练数据导致过拟合。这个是新手最容易忽略但特别值得加的一层尤其当你的数据集没那么大的时候。4.2 为什么不一开始就用迁移学习我在开头提了一嘴“本来想用迁移学习”但最后选择了从零搭模型这里说说原因。迁移学习确实强大——拿一个已经在千万级图片上训练好的模型比如MobileNetV2、ResNet把它的前面所有层冻住只训练最后的分类层往往几百张图片就能达到不错的效果。但我这次选择从零搭主要考虑两点。第一我的图片类别之间差异挺大的横平竖直的结构件照片特征非常明显从零训练的简单模型完全能搞定没必要请大神。第二从零训练一个简单模型能让我对训练过程、梯度变化、过拟合现象有更直观的感受这种感受是直接用预训练模型体会不到的。但如果你面临的情况是数据很少、每个类别只有一两百张图或者类别之间长得非常像那我建议别硬撑直接上迁移学习会更省力。后面有机会我再单独写一篇迁移学习的内容。4.3 模型参数怎么算出来的很多人看这堆数字觉得是玄学其实每个数字都能算出来。Conv2D(32, (3,3))表示用32个3x3的卷积核去扫描图片每一层输出的特征图数量是32、64、128这样翻倍增长。为什么递增因为越往深层特征越抽象需要更多的特征图来刻画。输入input_shape(224, 224, 3)里的3是色彩通道数彩色图片就是3RGB灰度图就是1。参数总量其实你自己也能算。第一层卷积输入是224x224x3用32个3x3的卷积核每个卷积核有3x3x327个权重加上1个偏置总共28个参数乘以32个卷积核就是896个参数。后面的层以此类推。模型训练完用model.summary()就能看到总参数量我这个模型大概几百万参数训练起来非常轻松。5. 训练模型从跑通到调出效果模型搭好数据生成器就绪接下来就是训练这一步。训练代码本身很简单一段model.fit搞定但真正让新手头疼的是怎么判断训练得好不好、什么时候该停、为什么loss一直不降。我这里把完整的训练代码和几个关键参数的意义讲清楚。5.1 训练代码与回调函数的使用from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint # 早停机制验证准确率连续5轮不提升就停止训练 early_stopping EarlyStopping( monitorval_accuracy, patience5, restore_best_weightsTrue ) # 保存模型验证准确率最高的那一轮自动存下来 model_checkpoint ModelCheckpoint( best_model.keras, monitorval_accuracy, save_best_onlyTrue ) history model.fit( train_generator, steps_per_epochtrain_generator.samples // train_generator.batch_size, epochs50, validation_datavalidation_generator, validation_stepsvalidation_generator.samples // validation_generator.batch_size, callbacks[early_stopping, model_checkpoint] )epochs50表示最多训练50轮但因为有EarlyStopping在实际训练会在验证准确率不再提升后提前终止所以不用怕设大了浪费时间。patience5的意思是验证准确率连续5轮都没比历史最高高就判定为“到头了”停止训练并恢复到最好的权重。我用这个配置实际跑到第23轮就停了验证准确率停在94%左右。ModelCheckpoint非常实用如果不加这个训练一结束模型就变成了最后一轮的权重而最后一轮往往不是最好的。有了它轮训练里准确率最高那轮的模型文件就被单独存下来了最后加载这个文件做推理就行。5.2 epochs、steps_per_epoch和batch_size之间的关系这三个参数新手非常容易搞混。batch_size是每批喂多少张图我在前面说过了。steps_per_epoch是每轮训练需要喂多少批它等于训练集总图片数除以batch_size。比如你训练集有2000张图batch_size是32那一轮大概需要63步才能把所有图“看完一遍”。epochs是“全部图片过多少遍”。每跑完一个epoch相当于模型把整个训练集过了一遍。注意epochs和steps_per_epoch都别设太离谱。如果把steps_per_epoch设得比实际需要的更大那一轮里有些图片会被重复看到设小了有些图片一轮里根本看不到。用train_generator.samples // train_generator.batch_size这个写法可以保证正好每轮把所有图片都看一遍省心。5.3 训练过程中怎么判断模型状态训练时控制台会打印每一轮的loss和accuracy还有验证集的loss和accuracy。新手最需要学会的就是对比训练集和验证集的两个指标。正常情况是训练准确率慢慢上升验证准确率也跟着慢慢上升两者差距不大这是健康的。如果训练准确率一路飙到99%验证准确率在70%就不动了甚至往下掉说明模型开始“死记硬背”训练数据了没有学到真正可泛化的特征专业说法叫过拟合。这时候你需要的是加数据增强的强度、提高Dropout比例、或者干脆减少模型层数。反过来如果训练准确率和验证准确率都不高比如60%左右晃悠那就是模型欠拟合表示它还没学会足够多的特征。解决办法是增加模型复杂度、增加训练轮数、或者检查是不是数据标签搞错了。这次训练过程中我观察到最明显的现象是前10轮准确率涨得飞快从刚起步的40%多一路涨到85%左右之后涨速明显放缓然后15轮到23轮之间在92%到94%之间徘徊。这是非常典型的学习曲线先快速上手再慢慢精进最后趋于平台期。EarlyStopping在平台期不再创新高之后自动停了非常省心。6. 训练过程踩过的坑和对应的排查思路这部分才是最有价值的地方。网上教程跑通demo简单但训练自己数据的时候问题五花八门。我把这次踩过的几个印象深刻的坑和完整的排查链路写下来你遇到了可以参考至少不用像我一样白折腾两天。6.1 验证准确率奇高但真实场景一塌糊涂这是我这次最窝火的一个坑。训练的时候验证准确率达到了96%我心里还挺美结果把模型拿来对几张新拍的照片做预测连最基本的同类零件都认错。我当时第一反应是模型有问题后来排查了一圈才发现是数据划分出了问题。问题出在哪儿呢我最初图省事train和validation是直接从同一个原始文件夹里随机拆分的但拆分的时候我只注意了“文件不重复”没注意这批零件照片虽然来自不同文件但不少是同一批次、同一角度、同一光照条件下拍的简单说就是太像了。训练集和验证集之间虽然不是同一张图但图像的底层特征几乎一样验证集在模型眼里相当于“同班同学换了件马甲”准确率当然高。但这个模型一旦碰上真实环境中不同角度、不同光线的图就露馅了。这属于典型的数据泄露问题不是模型本身的问题。排查思路其实也简单把验证集的预测错误案例打出来看看它到底错在哪如果错得离谱那大概率数据划分有问题。修复方式是重新划分数据尽量让训练集和验证集覆盖不同的拍摄批次或场景让验证集真正“难”起来。6.2 Loss值爆炸成NaN模型完全没有输出这个坑在训练到第7轮的时候突然冒出来的。损失值直接变成nan后面所有指标全部跟着崩。我一开始以为是自己模型结构写错了查半天没有收获。排查思路是这样的NaN意思是“不是一个数字”通常来自计算过程中的极端值。最常见的原因是学习率设置太高导致梯度更新幅度过大参数直接发散成无穷大。虽然我用的adam优化器自带自适应学习率但对某些数据集初始默认的学习率0.001还是可能太高。另外一个原因是数据里混入了破损图片。如果某张图是损坏的、只有几字节的空白文件ImageDataGenerator读进来会产生一堆NaN值模型一碰到就崩了。我最后排查下来问题就出在这里——数据文件夹里混着几张名字正常但内容损坏的废图之前从网上下载素材时候残留的。修复方式是两层第一从代码角度在数据读取时过滤掉无效文件或者直接把那些文件找出来删除第二如果确实想用较高学习率可以手动把adam的learning rate调低一点设置成0.0001试试。6.3 训练速度慢得离谱CPU占用100%但这GPU不动这个坑不算报错属于性能问题。我一开始训练的时候发现GPU利用率一直是0%CPU倒是拉满了一个epoch跑好几分钟慢得让人绝望。排查之后发现原因很简单TensorFlow默认情况下可能没有用上GPU或者用了GPU但数据加载成了瓶颈。ImageDataGenerator是CPU上运行的每一步都要从磁盘读32张图、做预处理、增强然后把数据搬到GPU上训练。如果数据准备速度跟不上GPU的计算速度那GPU大部分时间都在“等饭”GPU利用率自然上不去。解决思路有几个方向。最简单的是先确认TensorFlow能不能检测到GPU跑一下print(tf.config.list_physical_devices(GPU))如果列表是空的那可能是CUDA、cuDNN或者TensorFlow GPU版本的安装问题。如果GPU已经识别了但利用率还是低那就考虑把workers和use_multiprocessing参数加到model.fit里比如workers4, use_multiprocessingTrue数据加载会并行起来速度有明显提升。我加上并行参数之后每个epoch的训练时间差不多缩短了一半。当然这个对Windows系统不友好use_multiprocessingTrue在Windows上容易出幺蛾子各种报错如果你用Windows环境建议先不加这个参数用workers2就够了。6.4 类别不平衡带来的“假聪明”模型最后再分享一个不太起眼但很容易踩的坑。我的数据一开始不是均匀分配的A型号有1200张B型号有400张C型号也是1200张。训练出来的模型看起来准确率挺高但细看每类的准确率才发现B型号几乎完全被模型忽略了它把所有图都往A和C上面猜。这个现象其实就是类别不平衡导致的。模型训练的时候会更倾向于照顾样本多的类别因为这样整体损失降得最快。样本少的类别即使全猜错对整体准确率的影响也有限。解决方案有两个思路最简单的就是让每个类别的图片数量尽量接近给B型号多收集一些或者对已有的做数据增强另一个是给样本少的类别更高的权重让模型“意识到”这个类别的样本虽然少但犯错的代价更高。TensorFlow里分类权重可以通过class_weight参数传入fit方法这个参数需要的是一个字典键是类别索引值是该类别的权重系数。从我个人经验来说先做数据均衡再配合class_weight效果最稳。7. 模型训练完之后怎么验证它真的能用模型训练好了准确率看起来也凑合但千万别就此打住。我在第6部分讲过训练时的验证准确率不能完全代表真实场景的准确率所以模型训练完之后还要单独做一步“上手实测”拿一批从来没参与过训练、也没参与过验证的图片看看模型到底表现怎么样。7.1 用model.predict批量测试新图片我在训练完之后把用来训练和验证的所有图片全部放一边另找了一批新的零件照片大概有50张放在一个叫test_images的文件夹里。这50张图就是模型的“期末考卷”完全没见过。测试代码很简单from tensorflow.keras.preprocessing import image import numpy as np import os model tf.keras.models.load_model(best_model.keras) test_dir test_images # 这个字典顺序要和训练时flow_from_directory输出的类别顺序一致 class_names list(train_generator.class_indices.keys()) for img_name in os.listdir(test_dir): img_path os.path.join(test_dir, img_name) img image.load_img(img_path, target_size(224, 224)) img_array image.img_to_array(img) / 255.0 img_array np.expand_dims(img_array, axis0) pred model.predict(img_array, verbose0) predicted_class class_names[np.argmax(pred)] confidence np.max(pred) print(f{img_name}: 预测为 {predicted_class}置信度 {confidence:.2%})注意这行img_array image.img_to_array(img) / 255.0必须除以255因为训练的时候ImageDataGenerator里做了rescale1.0/255。这一步如果忘了像素值还是0到255的大数字而模型已经习惯看0到1的小数字预测结果会非常离谱。这是我见过最多的低级错误之一但是排查起来又特别隐蔽因为代码不报错就是结果不对。7.2 混淆矩阵比单一准确率诚实得多单看准确率比如“94%”感觉挺好的。但准确率很会骗人尤其在类别不平衡的时候。更靠谱的做法是打印一个混淆矩阵看看错误到底发生在哪些类别之间。下面这段代码可以直接用它会统计真实类别和预测类别的对应关系。如果矩阵显示A型号经常被误判成B型号说明这两个类别的特征存在交叉可能需要多补一些边界样本或者考虑在模型里加更多特征提取层。from sklearn.metrics import confusion_matrix import matplotlib.pyplot as plt # 先收集验证集的预测结果 validation_generator.reset() preds model.predict(validation_generator) pred_classes np.argmax(preds, axis1) true_classes validation_generator.classes cm confusion_matrix(true_classes, pred_classes) print(cm) # 可视化方便看 plt.imshow(cm, cmapplt.cm.Blues) plt.colorbar() plt.xlabel(预测类别) plt.ylabel(真实类别) plt.show()同样validation_generator.reset()这个小细节也值得一提。因为生成器是有状态的它像一个不停往外出牌的机器如果不reset第二次调predict的时候它可能已经从上次停下的位置继续出了预测结果就乱了。每次用生成器前都先重置一下可以避免很多莫名其妙的bug。我实测的混淆矩阵大概长这样A型号50张全部预测正确B型号45张里错判了3张到C型号C型号50张里错判了2张到A型号。A和B之间完全干净B和C偶尔有交叉。看到这个结果我心里就有底了模型总体可用但如果要部署到生产环境B和C之间的易混淆样本还得继续收集。7.3 导出模型与后续使用建议训练完成后模型文件会以.keras格式存在硬盘上。这个文件是模型结构和权重的合体包可以随时用tf.keras.models.load_model加载回来做预测不需要重新训练。如果要部署到Web服务或者手机端可能需要转成TensorFlow Lite格式那又是另一套流程了这里不展开。我给个建议保存模型的时候把类别映射关系class_indices也一起存下来最好写成JSON文件。因为模型本身只输出一个数字索引比如0、1、2如果你不记录这个索引对应哪个类别过几个月回来用的时候拿到一个1都不知道是什么型号。这种低级坑最容易发生在项目结束后的维护期别问我怎么知道的。8. 调整经验训练集大小时怎么知道模型还能再涨一截前面把一套完整的从零训练流程讲完了如果你的目标是“快速跑通一个能用的分类器”到这里其实已经够了。但如果你有耐心想让准确率再往上走几个点我可以分享一些调优思路。这些不是玄学都是我的实测心得。8.1 先看学习曲线再动手跑完训练后history对象里存了每一轮的准确率和损失值自己画个曲线图能直观看出模型的状态。我通常把四根线画在一张图上训练准确率、验证准确率、训练损失、验证损失。如果验证损失在最后几轮有明显抬头的趋势而训练损失还在下降那说明过拟合了。这时候别急着堆数据先想着怎么增强模型鲁棒性。如果两条准确率曲线一路上升、还没完全走平那说明训练还没到位可以适当增加epochs上限或者把早停的patience调大一点让模型多跳几步。如果验证损失从头到尾都在震荡、毫无下降趋势这往往是模型结构或者数据有问题怎么多训练都没用。这时候要回头检查前一节说的那些坑而不是盲目调参。8.2 数据增强的力度要慢慢试我在训练集上开了旋转、平移、缩放、翻转整套增强但这个力度不是越大越好。旋转角度从20度加到40度理论上每张图能变出更多花样但如果你的图片本身方向是固定的强行旋转过大的角度反而会生成一些“现实中根本不会出现”的样本干扰模型学习。我实测过零件照片角度对识别影响比较大把旋转从20度增加到30度验证准确率提升大概一个百分点继续加到40度反而掉了半个点。所以数据增强也要“恰到好处”。新手可以先用我前面给的参数跑一版然后微调其中一两个参数每次只改一个变量记录准确率变化这样最科学。8.3 预训练模型是另一个维度的跳板最后再提一嘴迁移学习。如果你的任务比较难比如类别之间肉眼看着都像或者数据实在少得可怜那从零搭CNN的模型可能到达瓶颈。这时候可以考虑换用tf.keras.applications.MobileNetV2加上include_topFalse提取特征后接一个自定义分类层效果会有一个质的提升。我自己的经验是从零CNN能达到94%换用MobileNetV2迁移学习后同样数据量能到97%左右而且训练时间更短。不过代价是代码会稍微复杂一点还得处理预训练权重下载的问题。等下一篇我再细讲迁移学习的具体玩法和调参心得。8.4 从模型结果反推数据收集方向训练结束之后多看看那些预测错的样本比盯着准确率数字有用得多。我一般会把预测置信度介于50%到80%之间的“犹豫样本”都打出来一张张看很快就知道问题出在数据还是模型。比如你发现模型经常把深色背景的A型号误判成B型号那可能不是模型的问题而是你的训练数据里A型号的深色背景样本太少。根据这个观察去收集数据比盲目地往文件夹里加图片高效得多。这也是为什么我在标题里强调的是“分类自己的图片数据”——数据这个东西只有你自己最懂模型只负责根据这些数据找规律。数据质量高、覆盖全模型自然就强数据有问题模型再怎么调也就那样了。我这次做完之后最大的体会是训练自己的图片分类模型其实有70%的时间都花在了数据整理、排查、反复测试上真正写模型代码的时间可能不到20%。这很正常也很值得。数据、模型、训练参数这三样东西前两者决定了天花板后者只是在帮你接近那个天花板。照着这篇文章的流程走你大概率能跑出一个效果不错的第一版模型剩下的细节就靠你自己在实际项目中慢慢打磨了。
阅读完成 · 觉得有帮助?
咨询建站