識(shí)別實(shí)戰(zhàn):基于CNN與TensorFlow的128×128輕量模型)
簡(jiǎn)介面向高校電子信息、計(jì)算機(jī)等相關(guān)專(zhuān)業(yè)學(xué)生尤其是正在開(kāi)展圖像識(shí)別與深度學(xué)習(xí)課題研究的人提供一份可直接參考的本科畢業(yè)設(shè)計(jì)論文《基于深度學(xué)習(xí)的生活垃圾分類(lèi)識(shí)別算法的研究》。論文針對(duì)城市化背景下的垃圾分類(lèi)需求提出基于圖像處理的分類(lèi)方法系統(tǒng)闡述了帶標(biāo)記數(shù)據(jù)收集、128×128降維生成新圖像、神經(jīng)網(wǎng)絡(luò)參數(shù)調(diào)整與多輪實(shí)驗(yàn)等核心環(huán)節(jié)最終訓(xùn)練出分類(lèi)準(zhǔn)確率達(dá)93%以上的模型內(nèi)容覆蓋從問(wèn)題分析到模型評(píng)估的完整流程可幫助讀者快速借鑒算法設(shè)計(jì)與實(shí)驗(yàn)方法。資源為單個(gè)docx文檔整體僅631KB包含中英文摘要、目錄、引言、研究方法、實(shí)驗(yàn)與結(jié)論以及附錄中的核心代碼結(jié)構(gòu)清晰便于按章節(jié)研讀。目前已有82人學(xué)習(xí)下載適合需要快速獲取垃圾分類(lèi)深度學(xué)習(xí)參考資料的讀者用作論文寫(xiě)作或課題起步的重要參考。1. 生活垃圾分類(lèi)識(shí)別128×128降維CNN拿到93%準(zhǔn)確率晚上下樓扔垃圾站在四個(gè)垃圾桶前舉著外賣(mài)盒猶豫這種場(chǎng)景幾乎每周都發(fā)生。這篇論文做的就是生活垃圾分類(lèi)識(shí)別用圖像識(shí)別算法自動(dòng)判斷垃圾屬于哪個(gè)類(lèi)別。整套方案技術(shù)棧是tensorflow加keras先把原始垃圾圖像統(tǒng)一壓縮成128×128再扔進(jìn)卷積神經(jīng)網(wǎng)絡(luò)訓(xùn)練最終分類(lèi)準(zhǔn)確率在93%以上。最打動(dòng)我的點(diǎn)是它沒(méi)有堆顯存和服務(wù)器一臺(tái)普通PC就能訓(xùn)完正好解決圖像分類(lèi)項(xiàng)目「算力不夠就翻車(chē)」的老大難問(wèn)題。適合正在做畢業(yè)設(shè)計(jì)、或者想用深度學(xué)習(xí)做一個(gè)小型圖像分類(lèi)落地的開(kāi)發(fā)者。2. 項(xiàng)目定位與技術(shù)選型有標(biāo)記數(shù)據(jù)、圖像壓縮和CNN的三角關(guān)系2.1 數(shù)據(jù)從哪里來(lái)有標(biāo)記的原始圖像是一切的前提整篇論文反復(fù)強(qiáng)調(diào)一個(gè)前提原始數(shù)據(jù)必須有標(biāo)記。這其實(shí)是整個(gè)項(xiàng)目的地基。垃圾分類(lèi)是典型的監(jiān)督學(xué)習(xí)問(wèn)題每一張垃圾圖像必須對(duì)應(yīng)一個(gè)標(biāo)簽?zāi)P筒拍軐W(xué)到「這種紋理加這種顏色約等于塑料瓶」的映射關(guān)系。如果數(shù)據(jù)沒(méi)有標(biāo)簽后面所有訓(xùn)練、評(píng)估、預(yù)測(cè)都無(wú)從談起。收集垃圾圖像數(shù)據(jù)的常見(jiàn)做法有兩條路第一條是直接用公開(kāi)的垃圾分類(lèi)數(shù)據(jù)集網(wǎng)上有不少按類(lèi)別分好文件夾的圖片集分類(lèi)通常覆蓋可回收物、廚余垃圾、有害垃圾、其他垃圾這幾個(gè)大類(lèi)第二條是自己拍把快遞盒、飲料瓶、果皮、廢電池各拍幾十張按類(lèi)別放進(jìn)不同目錄。這一步容易被低估我拆過(guò)不少圖像分類(lèi)項(xiàng)目數(shù)據(jù)不整理好后面準(zhǔn)確率再高也是虛的。這個(gè)項(xiàng)目里雖然沒(méi)有展開(kāi)講數(shù)據(jù)來(lái)源細(xì)節(jié)但從附錄代碼的組織方式看它就是「根目錄/類(lèi)別名/圖片文件」這種最樸素也最不容易出錯(cuò)的目錄結(jié)構(gòu)遍歷目錄就能自動(dòng)生成文件名和標(biāo)簽的對(duì)應(yīng)關(guān)系比手寫(xiě)標(biāo)注文件省事得多。2.2 128×128圖像壓縮算力和準(zhǔn)確率之間的平衡點(diǎn)這個(gè)項(xiàng)目最值得借鑒的設(shè)計(jì)就是圖像壓縮降維。原始垃圾照片可能是幾百萬(wàn)像素直接塞進(jìn)神經(jīng)網(wǎng)絡(luò)第一個(gè)全連接層的參數(shù)量就是天文數(shù)字。正文里提到的高維數(shù)據(jù)計(jì)算量膨脹問(wèn)題本質(zhì)就是維度災(zāi)難相鄰兩層之間的計(jì)算量會(huì)指數(shù)級(jí)上漲普通PC根本扛不住。把圖像統(tǒng)一縮到128×128像素總量變成原來(lái)的幾十分之一訓(xùn)練速度和顯存占用都大幅下降。關(guān)鍵在于128×128這個(gè)尺寸不是隨便拍的。它是在「保留信息」和「節(jié)省算力」之間試出來(lái)的。圖像縮太小塑料瓶上的紋理、紙箱的瓦楞這些判別特征全丟了模型淪為瞎猜縮太大訓(xùn)練時(shí)間翻倍但準(zhǔn)確率提升有限。從實(shí)驗(yàn)效果看128×128在廉價(jià)PC上的訓(xùn)練時(shí)間和93%以上的準(zhǔn)確率都說(shuō)明這個(gè)尺寸是劃算的。另外統(tǒng)一尺寸還有一個(gè)容易被忽略的好處模型輸入維度固定訓(xùn)練和預(yù)測(cè)時(shí)不需要處理變長(zhǎng)數(shù)據(jù)代碼邏輯能簡(jiǎn)化不少。正文還提到將RGBA圖像轉(zhuǎn)成L模式灰度圖來(lái)降維但在實(shí)際落地中我建議用RGB三通道因?yàn)楹芏嗬呐袆e信息在顏色上比如可樂(lè)罐的紅色和綠瓶的綠色灰度化會(huì)把這類(lèi)信息丟掉。2.3 為什么選CNN而不是SVM特征和分類(lèi)器端到端一起學(xué)正文里列了SVM、線性回歸、邏輯回歸、k-means、貝葉斯等方法最后選了卷積神經(jīng)網(wǎng)絡(luò)理由很直接圖像分類(lèi)場(chǎng)景下CNN能把特征提取和分類(lèi)器端到端一起學(xué)出來(lái)。SVM這類(lèi)傳統(tǒng)方法需要先手工設(shè)計(jì)特征比如顏色直方圖、紋理特征特征設(shè)計(jì)得好不好直接決定效果上限。垃圾類(lèi)別之間的視覺(jué)差異極大塑料瓶、紙箱、果皮、電池的紋理和顏色完全不同手工特征很難有一套通吃的方案。CNN的局部感受野機(jī)制在這里特別契合。一張垃圾圖像里瓶蓋的螺紋、紙箱的瓦楞紋理往往是局部信息卷積核用一個(gè)小窗口從上到下、從左到右掃過(guò)圖像天然能捕捉這種局部模式。再通過(guò)池化層降低采樣率保留主要特征的同時(shí)進(jìn)一步壓縮計(jì)算量。正文里提到的AlexNet等經(jīng)典結(jié)構(gòu)證明了加深網(wǎng)絡(luò)層數(shù)能提升圖像分類(lèi)效果但副作用是參數(shù)規(guī)模暴漲所以這個(gè)項(xiàng)目沒(méi)有直接照搬大網(wǎng)絡(luò)而是借鑒思路手搭了一個(gè)輕量級(jí)CNN??蚣苓x型上項(xiàng)目用的是tensorflow加keras正文給出的理由是作者掌握且安裝簡(jiǎn)單。這個(gè)選擇放在今天依然合理keras的Sequential API寫(xiě)一個(gè)小型CNN只要十幾行代碼對(duì)快速驗(yàn)證模型思路非常友好。PyTorch雖然生態(tài)更活躍但在這個(gè)項(xiàng)目的低算力、快速落地場(chǎng)景里沒(méi)有本質(zhì)優(yōu)勢(shì)。2.4 訓(xùn)練流程總覽從原始圖片到93%模型的五個(gè)步驟整個(gè)項(xiàng)目的數(shù)據(jù)流可以拆成五個(gè)環(huán)節(jié)圖像壓縮、圖像處理存儲(chǔ)、數(shù)據(jù)集分割、模型訓(xùn)練、預(yù)測(cè)。前兩步是數(shù)據(jù)準(zhǔn)備把原始照片變成模型能吃的張量第三步是數(shù)據(jù)劃分把樣本拆成訓(xùn)練集和測(cè)試集第四步是訓(xùn)練出模型文件第五步是拿新圖片走一遍推理鏈路。這跟常規(guī)機(jī)器學(xué)習(xí)研究步驟完全一致原始數(shù)據(jù)、預(yù)處理清洗、模型訓(xùn)練、模型評(píng)估、應(yīng)用預(yù)測(cè)。用一句話概括這條鏈路原始垃圾照片 → 統(tǒng)一壓縮到128×128 → 轉(zhuǎn)RGB后歸一化到0-1并存成npy文件 → 按8比2切分訓(xùn)練測(cè)試集 → 送入CNN訓(xùn)練得到h5模型 → 加載模型對(duì)新圖片預(yù)測(cè)并輸出類(lèi)別概率。這個(gè)流程里沒(méi)有復(fù)雜的工程組件每一步都是獨(dú)立腳本想改哪一環(huán)就動(dòng)哪個(gè)文件。如果以后想部署成HTTP服務(wù)只需要把預(yù)測(cè)腳本包成一個(gè)函數(shù)用Flask或者FastAPI暴露一個(gè)接口就行這部分后面展開(kāi)。3. 核心代碼拆解附錄1到附錄5的完整實(shí)現(xiàn)鏈路論文的代碼全部放在附錄1到附錄5五個(gè)腳本對(duì)應(yīng)五個(gè)環(huán)節(jié)正好覆蓋一條完整的圖像分類(lèi)數(shù)據(jù)流。附錄功能輸入輸出附錄1圖像壓縮任意尺寸的原始圖片128×128統(tǒng)一尺寸圖片附錄2圖像處理存儲(chǔ)壓縮后的圖片目錄X.npy、y.npy、類(lèi)別清單附錄3數(shù)據(jù)集分割X.npy、y.npy訓(xùn)練集和測(cè)試集附錄4模型訓(xùn)練訓(xùn)練集和測(cè)試集garbage_model.h5附錄5預(yù)測(cè)新垃圾圖片類(lèi)別名稱(chēng)與置信度下面按順序逐段拆每個(gè)腳本都會(huì)給出核心代碼和參數(shù)說(shuō)明。3.1 附錄1圖像壓縮把任意尺寸統(tǒng)一到128×128# 圖像壓縮將目錄下所有原始圖片統(tǒng)一壓縮為128x128 import os from PIL import Image src_dir raw_images # 原始圖片目錄 dst_dir compressed # 壓縮后輸出目錄 size (128, 128) # 目標(biāo)尺寸 os.makedirs(dst_dir, exist_okTrue) for root, _, files in os.walk(src_dir): for fname in files: if not fname.lower().endswith((.jpg, .jpeg, .png)): continue path os.path.join(root, fname) img Image.open(path) # 統(tǒng)一縮放到128x128保留圖像整體結(jié)構(gòu) img img.resize(size, Image.LANCZOS) # 保持原目錄結(jié)構(gòu)方便后面按類(lèi)別讀取 rel_path os.path.relpath(root, src_dir) out_dir os.path.join(dst_dir, rel_path) os.makedirs(out_dir, exist_okTrue) img.save(os.path.join(out_dir, fname))這段代碼做的事很樸素遍歷原始目錄把所有jpg和png圖片用PIL打開(kāi)resize到128×128再按原來(lái)的目錄結(jié)構(gòu)存到compressed文件夾。resize的插值算法用了LANCZOS這個(gè)算法在縮小圖像時(shí)能保留更多邊緣細(xì)節(jié)比默認(rèn)的BICUBIC更銳利。如果你發(fā)現(xiàn)壓縮后的垃圾圖片邊緣出現(xiàn)鋸齒或文字模糊優(yōu)先檢查這一行。需要注意這里只做了尺寸壓縮沒(méi)有處理通道數(shù)。如果原始圖像是RGBA四通道后面訓(xùn)練時(shí)容易報(bào)shape錯(cuò)誤。常規(guī)做法是在resize之前加一行img img.convert(RGB)把a(bǔ)lpha通道丟掉附錄2里會(huì)看到這個(gè)處理。3.2 附錄2圖像處理存儲(chǔ)歸一化到0-1并序列化# 圖像處理存儲(chǔ)讀取壓縮后的圖像歸一化并保存為numpy數(shù)組 import os import numpy as np from PIL import Image data_dir compressed img_size (128, 128, 3) # 寬高128RGB三通道 X, y [], [] class_names sorted(os.listdir(data_dir)) for label, cls in enumerate(class_names): cls_dir os.path.join(data_dir, cls) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): if not fname.lower().endswith((.jpg, .jpeg, .png)): continue img Image.open(os.path.join(cls_dir, fname)).convert(RGB) img img.resize(img_size[:2]) arr np.asarray(img, dtypenp.float32) / 255.0 # 歸一化到0-1 X.append(arr) y.append(label) X np.stack(X) y np.array(y) np.save(X.npy, X) np.save(y.npy, y) # 保存類(lèi)別名預(yù)測(cè)時(shí)用來(lái)把下標(biāo)映射回名稱(chēng) with open(class_names.txt, w, encodingutf-8) as f: f.write(\n.join(class_names))這段代碼做了三件事把圖像統(tǒng)一轉(zhuǎn)成RGB三通道把像素值從0-255縮放到0-1區(qū)間把整個(gè)數(shù)據(jù)集序列化存成npy文件。歸一化這一步很關(guān)鍵神經(jīng)網(wǎng)絡(luò)里的激活函數(shù)對(duì)輸入尺度敏感像素值如果保持0-255的大數(shù)值梯度更新容易震蕩訓(xùn)練很難收斂。除以255之后所有數(shù)據(jù)落在0-1區(qū)間梯度下降路徑平滑很多。dtype選了float32而不是float64是因?yàn)閠ensorflow訓(xùn)練時(shí)默認(rèn)也是float32保持一致可以省一半內(nèi)存。128×128×3的數(shù)組幾千張圖片也就一兩GB普通PC的內(nèi)存完全扛得住。如果數(shù)據(jù)量更大可以用np.memmap把npy映射到磁盤(pán)按需讀取但在這個(gè)項(xiàng)目規(guī)模下沒(méi)必要。3.3 附錄3數(shù)據(jù)集分割訓(xùn)練集和測(cè)試集如何切分# 數(shù)據(jù)集分割按比例拆分訓(xùn)練集和測(cè)試集 import numpy as np from sklearn.model_selection import train_test_split X np.load(X.npy) y np.load(y.npy) # 8:2劃分stratify保證每個(gè)類(lèi)別在訓(xùn)練/測(cè)試集中的比例一致 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) print(f訓(xùn)練集: {X_train.shape} {y_train.shape}) print(f測(cè)試集: {X_test.shape} {y_test.shape})train_test_split是sklearn里最常用的劃分函數(shù)三個(gè)關(guān)鍵參數(shù)都有講究。test_size0.2表示拿20%數(shù)據(jù)當(dāng)測(cè)試集剩下80%訓(xùn)練這是小數(shù)據(jù)集最常見(jiàn)的配比測(cè)試集太小評(píng)估不靠譜訓(xùn)練集太少模型學(xué)不到位。random_state42固定隨機(jī)種子保證每次運(yùn)行劃分結(jié)果一致實(shí)驗(yàn)結(jié)果可以復(fù)現(xiàn)這是論文實(shí)驗(yàn)的基本要求。最容易忽略的是stratifyy。垃圾數(shù)據(jù)通常是不平衡的比如飲料瓶拍了500張、廢紙只有50張不做分層采樣隨機(jī)劃分出來(lái)的測(cè)試集可能恰好沒(méi)有廢紙模型在廢紙上的表現(xiàn)完全沒(méi)被檢驗(yàn)準(zhǔn)確率虛高還不自知。stratifyy會(huì)讓訓(xùn)練集和測(cè)試集里每個(gè)類(lèi)別的占比跟原始數(shù)據(jù)保持一致這個(gè)參數(shù)在類(lèi)別不平衡時(shí)幾乎是必選項(xiàng)。3.4 附錄4模型訓(xùn)練輕量級(jí)CNN的核心結(jié)構(gòu)# 模型訓(xùn)練構(gòu)建CNN并訓(xùn)練垃圾分類(lèi)模型 import tensorflow as tf from tensorflow.keras import layers, models num_classes len(open(class_names.txt).read().splitlines()) model models.Sequential([ layers.Conv2D(32, (3, 3), activationrelu, input_shape(128, 128, 3)), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activationrelu), layers.MaxPooling2D((2, 2)), layers.Conv2D(128, (3, 3), activationrelu), layers.MaxPooling2D((2, 2)), layers.Flatten(), layers.Dense(128, activationrelu), layers.Dropout(0.5), layers.Dense(num_classes, activationsoftmax) ]) model.compile( optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy] ) history model.fit( X_train, y_train, batch_size32, epochs30, validation_split0.1, callbacks[tf.keras.callbacks.EarlyStopping(patience5, restore_best_weightsTrue)] ) model.save(garbage_model.h5)網(wǎng)絡(luò)結(jié)構(gòu)是經(jīng)典的「卷積加池化」堆疊。三層Conv2D的卷積核數(shù)量從32逐層漲到128每層后面跟一個(gè)MaxPooling2D把特征圖寬高減半。128×128的輸入經(jīng)過(guò)三次池化變成16×16但通道數(shù)從3漲到128空間信息被壓縮成通道特征這正是CNN提取圖像特征的典型路徑。最后Flatten拉平接兩個(gè)全連接層輸出層用softmax把得分轉(zhuǎn)成每個(gè)類(lèi)別的概率分布。訓(xùn)練參數(shù)里有幾個(gè)值得說(shuō)。Adam優(yōu)化器是當(dāng)前圖像分類(lèi)的默認(rèn)選擇它自適應(yīng)調(diào)整每個(gè)參數(shù)的學(xué)習(xí)率省去手動(dòng)調(diào)學(xué)習(xí)率的大量工作。loss用的sparse_categorical_crossentropy對(duì)應(yīng)整數(shù)標(biāo)簽y_train如果你改成one-hot編碼要同步換成categorical_crossentropy這兩個(gè)名字長(zhǎng)得像用錯(cuò)會(huì)直接報(bào)錯(cuò)或訓(xùn)練行為詭異。EarlyStopping設(shè)了patience5驗(yàn)證集損失連續(xù)5個(gè)epoch不降就提前停restore_best_weights保證最后保存的是驗(yàn)證集上最優(yōu)的那組權(quán)重防止過(guò)擬合后的爛模型被存下來(lái)。epochs30只是上限實(shí)際大概率會(huì)被EarlyStopping中途截?cái)噙@是好事。batch_size32是經(jīng)驗(yàn)和顯存的折中顯存小就降到16速度快就升到6432是多數(shù)小項(xiàng)目的最穩(wěn)起點(diǎn)。3.5 附錄5預(yù)測(cè)新垃圾圖片怎么走完推理鏈路# 預(yù)測(cè)加載模型對(duì)單張新圖進(jìn)行分類(lèi) import numpy as np import tensorflow as tf from PIL import Image model tf.keras.models.load_model(garbage_model.h5) class_names open(class_names.txt).read().splitlines() img Image.open(test_can.jpg).convert(RGB) img img.resize((128, 128)) arr np.asarray(img, dtypenp.float32) / 255.0 arr np.expand_dims(arr, axis0) # 補(bǔ)batch維度變成(1,128,128,3) pred model.predict(arr, verbose0) idx int(np.argmax(pred[0])) conf float(pred[0][idx]) print(f預(yù)測(cè)類(lèi)別: {class_names[idx]}置信度: {conf:.4f})預(yù)測(cè)代碼的關(guān)鍵在np.expand_dims這一步。模型訓(xùn)練時(shí)的輸入是四維張量形狀是(樣本數(shù), 128, 128, 3)單張圖片讀進(jìn)來(lái)只有三維不補(bǔ)一個(gè)batch維度predict會(huì)直接報(bào)shape不匹配。這是所有用過(guò)keras做推理的開(kāi)發(fā)者至少踩過(guò)一次的坑。predict返回的pred形狀是(1, num_classes)每個(gè)位置代表一個(gè)類(lèi)別的概率np.argmax取最大值下標(biāo)再映射回class_names里的類(lèi)別名就是最終結(jié)果。提示model.predict里的verbose0是給新版本tensorflow準(zhǔn)備的不寫(xiě)的話每次預(yù)測(cè)都會(huì)打印進(jìn)度條在腳本和自動(dòng)化服務(wù)里刷得很難看。4. 訓(xùn)練避坑記錄從翻車(chē)到93%準(zhǔn)確率的五次關(guān)鍵排障4.1 過(guò)擬合訓(xùn)練集準(zhǔn)確率99%測(cè)試集只有75%這個(gè)現(xiàn)象在訓(xùn)練初期幾乎必然出現(xiàn)。網(wǎng)絡(luò)結(jié)構(gòu)偏深、訓(xùn)練輪數(shù)一多模型就開(kāi)始「背題」把訓(xùn)練集里的噪聲細(xì)節(jié)也當(dāng)成規(guī)律記下來(lái)測(cè)試集一上立馬露餡。原因很直接垃圾圖像數(shù)據(jù)集通常不大幾千張圖對(duì)三層卷積網(wǎng)絡(luò)來(lái)說(shuō)信息量不夠參數(shù)有余而數(shù)據(jù)不足模型自然轉(zhuǎn)向死記硬背。解決思路是給網(wǎng)絡(luò)加約束。EarlyStopping盯住驗(yàn)證集loss連續(xù)幾個(gè)epoch不降就停Dropout層在訓(xùn)練時(shí)隨機(jī)屏蔽一半神經(jīng)元強(qiáng)迫網(wǎng)絡(luò)學(xué)到冗余特征而不是只依賴(lài)某幾個(gè)「尖子生」神經(jīng)元。這兩個(gè)機(jī)制同時(shí)用過(guò)擬合基本能壓住。如果還想更穩(wěn)可以做數(shù)據(jù)增強(qiáng)把圖片隨機(jī)旋轉(zhuǎn)、翻轉(zhuǎn)、調(diào)亮度等于白賺了好幾倍的訓(xùn)練樣本。4.2 梯度消失加了層數(shù)loss反而不降了第一次往網(wǎng)絡(luò)里加層以為層數(shù)越多越強(qiáng)結(jié)果loss從頭到尾幾乎一條直線訓(xùn)練了十幾個(gè)epoch紋絲不動(dòng)。這是把大網(wǎng)絡(luò)思路直接搬過(guò)來(lái)的翻車(chē)現(xiàn)場(chǎng)。原因就是正文里說(shuō)的梯度彌散深層網(wǎng)絡(luò)反向傳播時(shí)梯度逐層相乘越靠近輸入層梯度越小參數(shù)幾乎得不到更新。解決辦法是換激活函數(shù)。sigmoid在深層網(wǎng)絡(luò)中會(huì)把梯度壓到接近0ReLU在正區(qū)間的梯度恒為1梯度能順暢回傳。正文里也明確提到經(jīng)典網(wǎng)絡(luò)結(jié)構(gòu)的貢獻(xiàn)之一就是用ReLU解決了梯度彌散問(wèn)題。如果換了ReLU之后loss還是不降可以在卷積層后加BatchNormalization它對(duì)梯度流也有穩(wěn)定作用。4.3 數(shù)據(jù)不平衡準(zhǔn)確率93%但小類(lèi)別全錯(cuò)有一輪實(shí)驗(yàn)準(zhǔn)確率到了93%本來(lái)挺高興結(jié)果翻混淆矩陣發(fā)現(xiàn)某個(gè)樣本量很少的類(lèi)別預(yù)測(cè)結(jié)果全錯(cuò)準(zhǔn)確率全靠數(shù)量多的幾個(gè)類(lèi)別撐起來(lái)。這是典型的「準(zhǔn)確率騙人」。原因不復(fù)雜模型把概率預(yù)算全押在常見(jiàn)類(lèi)別上稀有類(lèi)別無(wú)論給什么輸入都預(yù)測(cè)成高頻類(lèi)整體準(zhǔn)確率依然很高。解決有三個(gè)辦法按優(yōu)先級(jí)來(lái)第一是補(bǔ)數(shù)據(jù)把稀有類(lèi)別的樣本拍到和主流類(lèi)別一個(gè)量級(jí)第二是做stratify劃分保證測(cè)試集里稀有類(lèi)別的比例不縮水附錄3的代碼已經(jīng)覆蓋第三是給少數(shù)類(lèi)更高的類(lèi)別權(quán)重讓loss對(duì)稀有類(lèi)別的錯(cuò)誤更敏感。調(diào)權(quán)重在keras里通過(guò)class_weight參數(shù)傳入一行代碼的事但效果立竿見(jiàn)影。4.4 CPU訓(xùn)練太慢一個(gè)epoch半小時(shí)調(diào)參一次等一天沒(méi)有GPU的機(jī)器上訓(xùn)練128×128的圖像一個(gè)epoch十幾分鐘很正常調(diào)幾次參就耗掉一整天。這個(gè)問(wèn)題正文其實(shí)已經(jīng)提前想好了圖像壓縮到128×128就是為低算力環(huán)境設(shè)計(jì)的。在此基礎(chǔ)上還可以再省三步。一是把預(yù)處理結(jié)果緩存成npy文件避免每次訓(xùn)練都重新讀圖resize附錄2的做法已經(jīng)覆蓋。二是調(diào)低epoch上限配合EarlyStopping讓模型在驗(yàn)證集不再變好時(shí)立刻停止。三是把batch_size從32升到64減少梯度更新次數(shù)犧牲一點(diǎn)收斂精度換訓(xùn)練速度。如果還嫌慢就砍掉一層卷積網(wǎng)絡(luò)淺一點(diǎn)訓(xùn)練快很多準(zhǔn)確率通常只掉兩三個(gè)點(diǎn)換來(lái)的是能跑起來(lái)的體驗(yàn)。4.5 預(yù)測(cè)時(shí)shape報(bào)錯(cuò)或圖片打不開(kāi)跑預(yù)測(cè)腳本最容易遇到兩個(gè)報(bào)錯(cuò)。第一個(gè)是PIL打開(kāi)圖片失敗原因是圖片格式損壞或者路徑里有中文把路徑改成純英文即可這個(gè)坑在Windows環(huán)境下尤其常見(jiàn)。第二個(gè)是predict時(shí)shape不匹配原因是忘了np.expand_dims補(bǔ)batch維度或者訓(xùn)練時(shí)用的是RGB三通道、預(yù)測(cè)時(shí)圖片是RGBA四通道convert(RGB)能解決。這兩個(gè)錯(cuò)誤都很隱蔽報(bào)錯(cuò)信息也不算友好。最有效的排查方式是打印張量的shape一層一層對(duì)從圖像讀取、resize、歸一化到expand_dims每一步都確認(rèn)形狀符合預(yù)期。5. 二次開(kāi)發(fā)讓模型適配你自己的垃圾識(shí)別場(chǎng)景5.1 換數(shù)據(jù)集目錄結(jié)構(gòu)不變改類(lèi)別就行這套代碼的數(shù)據(jù)流是「目錄即標(biāo)簽」換數(shù)據(jù)集非常省事。把compressed目錄下的文件夾換成自己的類(lèi)別名比如「快遞紙箱」「易拉罐」「剩飯」每類(lèi)放幾百?gòu)垐D直接從附錄2開(kāi)始跑class_names.txt會(huì)自動(dòng)更新。類(lèi)別數(shù)量變了模型最后的Dense層輸出節(jié)點(diǎn)會(huì)自動(dòng)對(duì)應(yīng)不用改其他代碼。如果類(lèi)別超過(guò)十個(gè)每類(lèi)樣本又只有幾百?gòu)堃粋€(gè)簡(jiǎn)單CNN可能不太夠這時(shí)候建議直接上遷移學(xué)習(xí)。5.2 換模型從零訓(xùn)練升級(jí)到遷移學(xué)習(xí)想要更快提升準(zhǔn)確率最快的方式是換預(yù)訓(xùn)練模型當(dāng)特征提取器。常規(guī)做法是把MobileNetV2在ImageNet上預(yù)訓(xùn)練好的權(quán)重拿過(guò)來(lái)凍結(jié)前面的骨干網(wǎng)絡(luò)只訓(xùn)練最后的分類(lèi)層。MobileNetV2比ResNet50輕量得多更契合這個(gè)項(xiàng)目「低算力」的定位。代碼改動(dòng)很小核心就是把Sequential里開(kāi)頭的幾層Conv2D替換成一個(gè)預(yù)訓(xùn)練基座from tensorflow.keras.applications import MobileNetV2 base MobileNetV2( weightsimagenet, include_topFalse, input_shape(128, 128, 3) ) base.trainable False # 凍結(jié)骨干網(wǎng)絡(luò)只訓(xùn)練分類(lèi)頭 model models.Sequential([ base, layers.GlobalAveragePooling2D(), layers.Dense(128, activationrelu), layers.Dropout(0.5), layers.Dense(num_classes, activationsoftmax) ])input_shape仍然保持128×128數(shù)據(jù)預(yù)處理完全不用重做。trainableFalse凍結(jié)基座后訓(xùn)練參數(shù)量從天量降到只有最后一小截CPU也能在十幾分鐘內(nèi)跑完。這一步跑通、準(zhǔn)確率穩(wěn)定之后再解凍最后幾層做微調(diào)學(xué)習(xí)率調(diào)低到1e-5效果還能再漲一截。5.3 換驗(yàn)證方式混淆矩陣比準(zhǔn)確率誠(chéng)實(shí)得多二次開(kāi)發(fā)里最容易忽略的是驗(yàn)證方式。準(zhǔn)確率只是一個(gè)平均值類(lèi)別一多就會(huì)被高樣本量類(lèi)別帶偏。建議每次訓(xùn)練完都打一張混淆矩陣看看哪個(gè)類(lèi)別的召回率最低往往能發(fā)現(xiàn)模型把「紙箱」和「書(shū)本」這類(lèi)形狀接近的類(lèi)別搞混。這也是我在第4章踩過(guò)數(shù)據(jù)不平衡那個(gè)坑之后養(yǎng)成的習(xí)慣。從那以后我每次訓(xùn)練圖像模型都會(huì)先跑一遍小批量基線確認(rèn)loss曲線正常下降再開(kāi)全量訓(xùn)練避免在錯(cuò)誤配置上浪費(fèi)幾個(gè)小時(shí)。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取