戰(zhàn):神經(jīng)網(wǎng)絡(luò)代碼全流程解析)
簡(jiǎn)介一套針對(duì)時(shí)裝數(shù)據(jù)集的神經(jīng)網(wǎng)絡(luò)分類實(shí)現(xiàn)代碼與配套說(shuō)明文檔面向深度學(xué)習(xí)初學(xué)者及需要完成圖像分類課程作業(yè)的學(xué)生覆蓋數(shù)據(jù)預(yù)處理、模型構(gòu)建、訓(xùn)練評(píng)估到調(diào)參優(yōu)化的完整流程。壓縮包共2個(gè)文件包含一個(gè)可直接運(yùn)行的Python腳本和一份Word說(shuō)明文檔腳本以卷積神經(jīng)網(wǎng)絡(luò)Conv2D、池化層結(jié)合全連接層的方式實(shí)現(xiàn)分類文檔則對(duì)歸一化、one-hot編碼、交叉熵?fù)p失、Adam優(yōu)化器等關(guān)鍵環(huán)節(jié)進(jìn)行了講解整體大小僅559KB。已有297人學(xué)習(xí)很適合對(duì)照代碼逐步理解時(shí)裝數(shù)據(jù)集中10類衣物圖像的識(shí)別過(guò)程。借助壓縮包內(nèi)的代碼和文檔讀者能夠快速掌握加載并處理圖像數(shù)據(jù)、搭建并編譯模型、訓(xùn)練與評(píng)估模型的具體寫(xiě)法也可參考代碼結(jié)構(gòu)完成自己的作業(yè)或進(jìn)一步嘗試調(diào)整學(xué)習(xí)率、批量大小等超參數(shù)進(jìn)行優(yōu)化實(shí)驗(yàn)。1. 時(shí)裝分類代碼包到底解決了什么從一份作業(yè)代碼看懂神經(jīng)網(wǎng)絡(luò)全流程拿到「神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)fashion數(shù)據(jù)分類代碼.zip」的時(shí)候多數(shù)人以為它就是一份交差的作業(yè)解壓出來(lái)一個(gè) homework6 文件夾、一個(gè) main.py、一份 Word 文檔。實(shí)際上這份資源把神經(jīng)網(wǎng)絡(luò)做圖像分類的完整流程——加載數(shù)據(jù)、預(yù)處理、建模、訓(xùn)練、評(píng)估、調(diào)參——壓縮進(jìn)了一個(gè)能直接跑的最小閉環(huán)里。Fashion-MNIST 是 Zalando 做的服裝圖片數(shù)據(jù)集10 個(gè)類別、6 萬(wàn)張訓(xùn)練圖、1 萬(wàn)張測(cè)試圖每張 28×28 灰度比手寫(xiě)數(shù)字 MNIST 更難也更接近真實(shí)圖像分類的難度。對(duì)剛接觸深度學(xué)習(xí)的人來(lái)說(shuō)把它跑通、改參數(shù)、看準(zhǔn)確率變化比看十篇教程都管用。這份代碼包適合三類人正在做圖像分類作業(yè)的學(xué)生、想用真實(shí)數(shù)據(jù)集練手 TensorFlow 的初學(xué)者、以及需要一份基線代碼快速驗(yàn)證想法的工程師。對(duì)已經(jīng)跑過(guò)幾個(gè)模型的人這份代碼的參考價(jià)值在于結(jié)構(gòu)規(guī)范數(shù)據(jù)預(yù)處理、模型定義、訓(xùn)練評(píng)估被分得很清楚你能直接替換成自己的數(shù)據(jù)集。核心算法是前饋神經(jīng)網(wǎng)絡(luò)和卷積神經(jīng)網(wǎng)絡(luò)的對(duì)比實(shí)現(xiàn)源碼基于 TensorFlow/Keras 寫(xiě)成訓(xùn)練入口在 main.pyWord 文檔則把每一行的意圖解釋了一遍。2. Fashion-MNIST 數(shù)據(jù)預(yù)處理reshape、歸一化與 one-hot 編碼的落地寫(xiě)法2.1 先看數(shù)據(jù)長(zhǎng)什么樣shape 與類別分布決定后面所有代碼解壓后先別急著跑 main.py第一步應(yīng)該打開(kāi) Python 交互環(huán)境把這個(gè)數(shù)據(jù)集的實(shí)際結(jié)構(gòu)打印出來(lái)。Fashion-MNIST 在 Keras 里自帶加載函數(shù)不需要手動(dòng)下載第一次運(yùn)行會(huì)自動(dòng)從網(wǎng)絡(luò)獲取之后緩存在本地。from tensorflow.keras.datasets import fashion_mnist (x_train, y_train), (x_test, y_test) fashion_mnist.load_data() print(x_train shape:, x_train.shape) # (60000, 28, 28) print(y_train shape:, y_train.shape) # (60000,) print(x_test shape:, x_test.shape) # (10000, 28, 28) print(類別取值范圍:, y_train.min(), -, y_train.max())這里的輸出值得逐行確認(rèn)x_train是四維結(jié)構(gòu)第一維 60000 是樣本數(shù)后兩維 28×28 是像素矩陣沒(méi)有通道維。y_train是整數(shù)索引0 到 9 共 10 類分別對(duì)應(yīng) T 恤、褲子、套頭衫、裙子、外套、涼鞋、襯衫、運(yùn)動(dòng)鞋、包、短靴。類別順序有時(shí)候會(huì)記混建議打印出前幾張圖的標(biāo)簽和像素范圍確認(rèn)數(shù)據(jù)沒(méi)加載錯(cuò)。import matplotlib.pyplot as plt print(像素范圍:, x_train[0].min(), -, x_train[0].max()) plt.imshow(x_train[0], cmapgray) plt.title(flabel: {y_train[0]}) plt.show()如果打印出的像素范圍是 0 到 255就說(shuō)明還沒(méi)歸一化坦率說(shuō)這一步是最容易被跳過(guò)的。另外注意x_train是三維數(shù)組直接塞進(jìn)全連接層會(huì)報(bào)維度錯(cuò)誤這也是后面要先 reshape 的原因。我一般會(huì)在加載完數(shù)據(jù)后立刻把 shape 寫(xiě)進(jìn)注釋里防止隔幾天回來(lái)忘了維度。2.2 reshape 與歸一化除以 255 不是玄學(xué)是收斂速度的分水嶺神經(jīng)網(wǎng)絡(luò)對(duì)輸入特征的尺度非常敏感。28×28 灰度圖的像素值范圍是 0 到 255如果原樣輸入大數(shù)值特征會(huì)在前向傳播中主導(dǎo)梯度更新導(dǎo)致 loss 震蕩甚至不收斂。歸一化就是把每個(gè)像素除以 255把范圍壓到 0 到 1 之間。這一步看起來(lái)簡(jiǎn)單但跳過(guò)的后果在訓(xùn)練曲線上非常明顯loss 下降慢、準(zhǔn)確率長(zhǎng)期在 80% 附近卡住。全連接網(wǎng)絡(luò)的輸入需要把 28×28 拉平成 784 維向量卷積網(wǎng)絡(luò)則保留 28×28 并在末尾加一個(gè)通道維度# 全連接網(wǎng)絡(luò)MLP版拉平成向量 x_train_flat x_train.reshape(-1, 784) / 255.0 x_test_flat x_test.reshape(-1, 784) / 255.0 # 卷積網(wǎng)絡(luò)CNN版保留空間結(jié)構(gòu)加灰度通道 x_train_cnn x_train.reshape(-1, 28, 28, 1) / 255.0 x_test_cnn x_test.reshape(-1, 28, 28, 1) / 255.0reshape(-1, 784)里的-1表示自動(dòng)推斷樣本數(shù)784 是 28×28 的乘積。卷積版本最后加的 1 是灰度通道數(shù)如果是三通道彩色圖這里就是 3。兩個(gè)版本的數(shù)據(jù)要分開(kāi)存因?yàn)楹竺婺P徒拥妮斎刖S度完全不同。歸一化統(tǒng)一用浮點(diǎn)除法注意 Python 3 里整數(shù)除法要用/而不是//否則 255 整除后全部變成 0。提示 像素值縮放到 0-1 區(qū)間是最常見(jiàn)的做法但也可以嘗試標(biāo)準(zhǔn)化(x - mean) / std。對(duì) Fashion-MNIST 這種圖像數(shù)據(jù)集簡(jiǎn)單除以 255 已經(jīng)足夠數(shù)據(jù)量小、特征明顯。 /提示2.3 標(biāo)簽編碼為什么不能直接把 0-9 整數(shù)喂給模型分類任務(wù)的輸出層用 softmax 輸出 10 個(gè)概率值標(biāo)簽就需要變成 10 維的 one-hot 向量。比如類別 3 變成[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]。如果直接拿整數(shù)標(biāo)簽訓(xùn)練模型會(huì)認(rèn)為類別之間有大小關(guān)系——5 比 3 大、9 比 7 大這完全誤導(dǎo)了分類學(xué)習(xí)。from tensorflow.keras.utils import to_categorical y_train_oh to_categorical(y_train, num_classes10) y_test_oh to_categorical(y_test, num_classes10) print(原始標(biāo)簽:, y_train[0]) # 例如 9 print(one-hot標(biāo)簽:, y_train_oh[0]) # [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]to_categorical的num_classes參數(shù)必須和模型輸出層神經(jīng)元數(shù)量一致否則訓(xùn)練時(shí)會(huì)報(bào)維度不匹配。另外模型評(píng)估階段計(jì)算準(zhǔn)確率時(shí)model.predict返回的是 10 個(gè)概率值需要用np.argmax取最大概率的索引再和原始整數(shù)標(biāo)簽比較。這個(gè) argmax 環(huán)節(jié)是初學(xué)者最容易漏的漏掉之后準(zhǔn)確率算出來(lái)會(huì)非常難看原因是拿概率向量和整數(shù)標(biāo)簽做了逐元素比較。3. 模型構(gòu)建從 784 維輸入到 10 類輸出前饋網(wǎng)絡(luò)與卷積網(wǎng)絡(luò)的搭配3.1 全連接基線一個(gè)隱藏層的前饋網(wǎng)絡(luò)能跑到多少Fashion-MNIST 的難度比手寫(xiě)數(shù)字高不少M(fèi)NIST 用全連接網(wǎng)絡(luò)輕松到 98%Fashion-MNIST 同樣的結(jié)構(gòu)大概只在 88% 左右。但這不妨礙先用全連接網(wǎng)絡(luò)搭一個(gè)基線它能驗(yàn)證數(shù)據(jù)預(yù)處理和訓(xùn)練流程是否正確。一個(gè)典型的前饋網(wǎng)絡(luò)結(jié)構(gòu)是 784 → 128 → 10隱藏層用 ReLU 激活輸出層用 softmax。在 Keras 里用 Sequential 寫(xiě)這個(gè)網(wǎng)絡(luò)每一層的神經(jīng)元數(shù)量和激活函數(shù)是核心參數(shù)。隱藏層 128 是經(jīng)驗(yàn)值太少學(xué)不到特征太多在小數(shù)據(jù)集上容易過(guò)擬合。不要一上來(lái)就堆 512、1024先把基線跑通再逐步加寬。from tensorflow.keras import Sequential from tensorflow.keras.layers import Dense, Dropout from tensorflow.keras.optimizers import Adam model_mlp Sequential([ Dense(128, activationrelu, input_shape(784,)), Dropout(0.3), Dense(10, activationsoftmax) ]) model_mlp.compile( optimizerAdam(learning_rate0.001), losscategorical_crossentropy, metrics[accuracy] ) model_mlp.summary()input_shape(784,)這里的逗號(hào)不能漏它表示輸入是一個(gè) 784 維的一維向量。Dropout 層的作用是隨機(jī)丟棄 30% 的神經(jīng)元防止網(wǎng)絡(luò)死記硬背訓(xùn)練集。learning_rate0.001是我常用的默認(rèn)值如果 loss 不降或者降得太猛再調(diào)整到 0.0001 或 0.01。summary()會(huì)打印每一層的參數(shù)量全連接層的參數(shù)是輸入維度 × 神經(jīng)元數(shù)量 偏置第一層就是 784×128128約十萬(wàn)個(gè)參數(shù)量級(jí)在可控范圍內(nèi)。3.2 卷積特征提取Conv2D 與 MaxPooling2D 的每一維都是為什么CNN 比全連接網(wǎng)絡(luò)強(qiáng)在參數(shù)共享和局部感受野。一張 28×28 的圖像全連接第一層每個(gè)神經(jīng)元要連 784 個(gè)輸入而卷積核只用 3×39 個(gè)權(quán)重在圖像上滑動(dòng)參數(shù)少得多而且能捕捉到邊緣、紋理這類局部特征。對(duì)服裝這種有明顯紋理和輪廓的圖片卷積層的特征提取能力遠(yuǎn)超前饋網(wǎng)絡(luò)。一個(gè)常用的 Fashion-MNIST CNN 結(jié)構(gòu)是兩層卷積加池化再接全連接層。我給出的是這份作業(yè)代碼里最常見(jiàn)的搭建方式也是能穩(wěn)定跑到 92% 以上的配置from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten model_cnn Sequential([ Conv2D(32, (3, 3), activationrelu, input_shape(28, 28, 1)), MaxPooling2D((2, 2)), Conv2D(64, (3, 3), activationrelu), MaxPooling2D((2, 2)), Flatten(), Dense(128, activationrelu), Dropout(0.5), Dense(10, activationsoftmax) ])Conv2D(32, (3, 3))的第一個(gè)參數(shù) 32 是卷積核數(shù)量也叫輸出通道數(shù)可以理解為提取 32 種不同特征第二個(gè)參數(shù)是卷積核尺寸3×3 是最常用的5×5 感受野更大但參數(shù)更多。MaxPooling2D((2, 2))把 2×2 區(qū)域取最大值圖像尺寸縮小一半同時(shí)保留最顯著的特征。第一層池化后是 14×14第二層后是 7×7Flatten 把它拉平成 7×7×643136 維向量再接全連接層。這里有一個(gè)輸入數(shù)據(jù)對(duì)齊的問(wèn)題CNN 的input_shape(28, 28, 1)對(duì)應(yīng)之前預(yù)處理的x_train_cnn如果誤把x_train_flat喂進(jìn)來(lái)第一層就會(huì)報(bào)維度錯(cuò)誤。作業(yè)代碼里通常會(huì)寫(xiě)清楚哪個(gè)變量喂給哪個(gè)模型但自己改的時(shí)候經(jīng)常會(huì)把這兩組數(shù)據(jù)搞混。3.3 輸出層與損失函數(shù)softmax 和交叉熵是配對(duì)使用的輸出層的Dense(10, activationsoftmax)是全篇代碼的關(guān)鍵落點(diǎn)。softmax 把 10 個(gè)神經(jīng)元的輸出變成和為 1 的概率分布哪個(gè)類別的概率最大模型就預(yù)測(cè)哪個(gè)類。配合categorical_crossentropy損失函數(shù)梯度更新會(huì)更穩(wěn)定。如果標(biāo)簽做成了 one-hot就用categorical_crossentropy如果標(biāo)簽還是整數(shù)用sparse_categorical_crossentropy也能訓(xùn)練但作業(yè)代碼里大多是 one-hot 方案。這是一個(gè)容易踩的坑標(biāo)簽編碼方式與損失函數(shù)不匹配時(shí)訓(xùn)練不報(bào)錯(cuò)但準(zhǔn)確率異常因?yàn)?Keras 內(nèi)部做了不同的比較方式。建議代碼里寫(xiě)死 one-hot 編碼損失函數(shù)固定用categorical_crossentropy排查問(wèn)題也更方便。隱藏層的激活函數(shù)用 ReLU 而不是 sigmoid 或 tanh原因是 ReLU 的計(jì)算簡(jiǎn)單、梯度不會(huì)飽和深層網(wǎng)絡(luò)也能穩(wěn)定傳播。卷積層里的 ReLU 還會(huì)把負(fù)值特征直接砍掉相當(dāng)于做了一個(gè)隱式的特征篩選。提示 關(guān)于 CNN 的參數(shù)量可以對(duì)比全連接網(wǎng)絡(luò)約 10 萬(wàn)參數(shù)CNN 只有約 5 萬(wàn)參數(shù)但準(zhǔn)確率反而高 4 到 5 個(gè)點(diǎn)。這就是「參數(shù)共享 局部連接」帶來(lái)的效果。 /提示4. 訓(xùn)練閉環(huán)損失函數(shù)、優(yōu)化器與準(zhǔn)確率評(píng)估的配合要點(diǎn)4.1 編譯配置交叉熵搭配 Adam 的選型理由模型定義完compile是訓(xùn)練前最后一個(gè)關(guān)鍵步驟。損失函數(shù)選categorical_crossentropy優(yōu)化器選 Adam評(píng)估指標(biāo)選accuracy。Adam 的優(yōu)點(diǎn)是自適應(yīng)學(xué)習(xí)率它會(huì)在訓(xùn)練過(guò)程中根據(jù)梯度大小動(dòng)態(tài)調(diào)整每個(gè)參數(shù)的學(xué)習(xí)步長(zhǎng)相比 SGD 需要手動(dòng)調(diào)整學(xué)習(xí)率和動(dòng)量Adam 對(duì)新手更友好。from tensorflow.keras.optimizers import Adam model_cnn.compile( optimizerAdam(learning_rate0.001), losscategorical_crossentropy, metrics[accuracy] )learning_rate這個(gè)參數(shù)值得多說(shuō)一句0.001 是 Adam 的默認(rèn)值對(duì)大多數(shù)圖像分類任務(wù)都?jí)蛴?。如果?xùn)練時(shí) loss 出現(xiàn)劇烈震蕩說(shuō)明學(xué)習(xí)率偏大降到 0.0003 或 0.0001如果 loss 下降得極其緩慢可以試著提到 0.003。不要用太小的學(xué)習(xí)率硬撐著跑幾十個(gè) epoch浪費(fèi)時(shí)間且收益很低。metrics[accuracy]會(huì)在訓(xùn)練過(guò)程中同步計(jì)算準(zhǔn)確率Keras 會(huì)自動(dòng)把預(yù)測(cè)結(jié)果和標(biāo)簽做argmax再比較。這里需要注意訓(xùn)練時(shí)的準(zhǔn)確率是 one-hot 標(biāo)簽的匹配結(jié)果和我們手動(dòng)用整數(shù)標(biāo)簽算出來(lái)的準(zhǔn)確率在數(shù)值上是一致的但代碼路徑不同別混著用。4.2 fit 訓(xùn)練參數(shù)batch_size、epochs 和過(guò)擬合信號(hào)訓(xùn)練過(guò)程的參數(shù)直接影響模型的收斂速度和最終效果。batch_size每次喂給網(wǎng)絡(luò)多少?gòu)垐Depochs跑幾輪完整數(shù)據(jù)集validation_split從訓(xùn)練集里切出多少比例做驗(yàn)證。這三者的關(guān)系是batch 越小梯度更新越頻繁訓(xùn)練越不穩(wěn)定但可能跳出局部最優(yōu)batch 越大訓(xùn)練越平滑但顯存占用高。64 是經(jīng)驗(yàn)平衡點(diǎn)。history model_cnn.fit( x_train_cnn, y_train_oh, batch_size64, epochs20, validation_split0.2, verbose1 )validation_split0.2表示把 60000 張訓(xùn)練圖里的 12000 張拿出來(lái)當(dāng)驗(yàn)證集不參與梯度更新。每次 epoch 結(jié)束后Keras 會(huì)在驗(yàn)證集上計(jì)算 loss 和準(zhǔn)確率這個(gè)結(jié)果比訓(xùn)練集的準(zhǔn)確率更能反映泛化能力。verbose1會(huì)打印進(jìn)度條verbose0安靜模式作業(yè)里推薦保持 1 方便觀察。訓(xùn)練中最常見(jiàn)的信號(hào)是訓(xùn)練準(zhǔn)確率持續(xù)上升但驗(yàn)證準(zhǔn)確率開(kāi)始下降這就是過(guò)擬合的典型曲線。遇到這種情況優(yōu)先改 Dropout 的比例0.3 調(diào)到 0.5或者減小 epochs。不要只盯訓(xùn)練集準(zhǔn)確率那個(gè)數(shù)字高得再漂亮也沒(méi)有實(shí)際意義。Fashion-MNIST 的 CNN 模型在 8 到 12 個(gè) epoch 之后驗(yàn)證準(zhǔn)確率就趨于平穩(wěn)20 個(gè) epoch 足夠觀察到完整趨勢(shì)。4.3 評(píng)估與持久化evaluate 的返回值對(duì)應(yīng)什么訓(xùn)練完成后需要拿測(cè)試集做最終評(píng)估。測(cè)試集是模型沒(méi)見(jiàn)過(guò)的 1 萬(wàn)張圖它的準(zhǔn)確率才是作業(yè)報(bào)告里真正要寫(xiě)的數(shù)字。evaluate返回兩個(gè)值第一個(gè)是 loss第二個(gè)是 accuracytest_loss, test_acc model_cnn.evaluate(x_test_cnn, y_test_oh, verbose0) print(f測(cè)試集 loss: {test_loss:.4f}) print(f測(cè)試集準(zhǔn)確率: {test_acc:.4f})如果這里打印出的準(zhǔn)確率和訓(xùn)練時(shí)差不多說(shuō)明模型泛化正常如果測(cè)試準(zhǔn)確率比驗(yàn)證準(zhǔn)確率低很多可能存在數(shù)據(jù)分布差異或者是訓(xùn)練集 shuffle 不夠。verbose0不打印進(jìn)度條適合批量評(píng)估時(shí)使用。訓(xùn)練完的模型也要保存否則下次跑還得重新訓(xùn)練。作業(yè)里常見(jiàn)的是把整個(gè)模型存成 h5 文件model_cnn.save(fashion_mnist_cnn.h5) # 重新加載 from tensorflow.keras.models import load_model loaded_model load_model(fashion_mnist_cnn.h5)保存格式上.h5是 Keras 默認(rèn)格式把模型結(jié)構(gòu)和權(quán)重打包在一起。加載后可以直接predict和evaluate不需要重新定義結(jié)構(gòu)。這里建議給文件加上模型名字和準(zhǔn)確率比如fashion_cnn_9215.h5避免后面實(shí)驗(yàn)多個(gè)模型時(shí)分辨不清。加載模型之后順手用loaded_model.summary()確認(rèn)結(jié)構(gòu)沒(méi)變這是一個(gè)值得養(yǎng)成的習(xí)慣。5. 避坑指南跑通 main.py 之前必看的五個(gè)排查點(diǎn)5.1 啟動(dòng)報(bào)錯(cuò)找不到 msvcp140.dll 或 TensorFlow 導(dǎo)入失敗現(xiàn)象安裝好 TensorFlow 后運(yùn)行 main.py直接彈出找不到 msvcp140.dll或者提示ImportError: DLL load failed。原因TensorFlow 是編譯型庫(kù)依賴 Microsoft Visual C Redistributable 提供的運(yùn)行時(shí)組件。系統(tǒng)缺失這個(gè)運(yùn)行庫(kù)時(shí)Python 無(wú)法加載 TensorFlow 的 C 擴(kuò)展。操作系統(tǒng)重新安裝或者純凈環(huán)境里最常見(jiàn)。解決去微軟官網(wǎng)下載 VC Redistributable 最新版安裝x64 版本必裝。裝完重啟終端再跑一次。如果問(wèn)題變成找不到cudart64_*.dll那是 GPU 版 TensorFlow 缺 CUDA 運(yùn)行庫(kù)把 CUDA Toolkit 裝上即可。CPU 版 TensorFlow 一般不依賴 CUDA。5.2 維度錯(cuò)誤input_shape 與數(shù)據(jù) shape 對(duì)不上現(xiàn)象fit階段報(bào)錯(cuò)ValueError: Input 0 of layer sequential is incompatible with the layer提示期望形狀和實(shí)際形狀不一致。原因預(yù)處理時(shí)生成了兩個(gè)版本的數(shù)據(jù)x_train_flat是 (60000, 784)x_train_cnn是 (60000, 28, 28, 1)??赡苁怯?CNN 模型訓(xùn)練時(shí)誤喂了x_train_flat或者全連接網(wǎng)絡(luò)喂了帶通道維的數(shù)據(jù)。解決檢查模型定義里的input_shape和喂進(jìn)去的數(shù)據(jù)。訓(xùn)練 CNN 用x_train_cnn訓(xùn)練 MLP 用x_train_flat。在fit前加一行打印print(x_train_cnn.shape)確認(rèn)維度是四維再繼續(xù)。還有一個(gè)容易忽略的點(diǎn)數(shù)據(jù)是 (60000, 28, 28) 但忘記加通道維直接 reshape 成 (60000, 28, 28) 而不是 (60000, 28, 28, 1)一樣會(huì)報(bào)錯(cuò)。5.3 準(zhǔn)確率一直在 10% 左右徘徊現(xiàn)象訓(xùn)練了很多輪準(zhǔn)確率始終在 0.1 附近打轉(zhuǎn)和隨機(jī)猜測(cè)無(wú)異。原因最常見(jiàn)的是歸一化沒(méi)做像素值 0-255 直接進(jìn)網(wǎng)絡(luò)梯度更新異常其次是標(biāo)簽 one-hot 編碼寫(xiě)錯(cuò)了比如num_classes設(shè)成了其他數(shù)值還有一種情況是模型最后沒(méi)有softmax激活輸出值不是概率分布。解決回到預(yù)處理代碼確認(rèn)像素值已經(jīng)除以 255打印y_train_oh.shape確認(rèn)是 (60000, 10)檢查輸出層是Dense(10, activationsoftmax)而不是linear。這三個(gè)檢查點(diǎn)五分鐘能查完如果都正常把學(xué)習(xí)率調(diào)到 0.0001 再試一輪。5.4 訓(xùn)練集 99% 測(cè)試集 85%過(guò)擬合的典型信號(hào)現(xiàn)象訓(xùn)練準(zhǔn)確率接近 100%驗(yàn)證和測(cè)試準(zhǔn)確率只有 85% 左右差值超過(guò) 10 個(gè)點(diǎn)。原因模型容量過(guò)大把訓(xùn)練集里的噪聲和細(xì)節(jié)都背下來(lái)了碰到新數(shù)據(jù)就露餡。Fashion-MNIST 本身類別間有相似性比如襯衫和 T 恤模型在訓(xùn)練集上做出了過(guò)強(qiáng)擬合。解決增大 Dropout 比例從 0.3 調(diào)到 0.5 甚至 0.6減小隱藏層神經(jīng)元數(shù)量比如 128 降到 64加入早停機(jī)制——在驗(yàn)證準(zhǔn)確率連續(xù) 3 個(gè) epoch 不提升時(shí)自動(dòng)停止訓(xùn)練Keras 原生支持EarlyStopping回調(diào)。這套組合拳能把測(cè)試準(zhǔn)確率從 85% 拉回到 90% 以上。from tensorflow.keras.callbacks import EarlyStopping early_stop EarlyStopping( monitorval_accuracy, patience3, restore_best_weightsTrue ) history model_cnn.fit( x_train_cnn, y_train_oh, batch_size64, epochs50, validation_split0.2, callbacks[early_stop], verbose1 )patience3表示連續(xù) 3 個(gè) epoch 驗(yàn)證準(zhǔn)確率不提升就停止restore_best_weightsTrue會(huì)把權(quán)重回滾到驗(yàn)證準(zhǔn)確率最高的那一步避免保存過(guò)擬合后的權(quán)重。5.5 測(cè)試集上的分類結(jié)果和直覺(jué)不符現(xiàn)象模型整體準(zhǔn)確率還行但查看具體樣本時(shí)明明很像運(yùn)動(dòng)鞋的圖被分成了短靴或者把包識(shí)別成了裙子。原因Fashion-MNIST 的類別之間存在真實(shí)的重疊比如短靴和運(yùn)動(dòng)鞋輪廓接近襯衫和 T 恤差別僅在袖子長(zhǎng)度人眼都可能看錯(cuò)。模型沒(méi)有額外的語(yǔ)義信息只能根據(jù)像素分布判斷。解決看混淆矩陣找到哪些類別對(duì)經(jīng)常被搞混再?zèng)Q定是否需要增加對(duì)應(yīng)類別的訓(xùn)練數(shù)據(jù)、或者調(diào)整類別權(quán)重。這一步不是 bug是這個(gè)數(shù)據(jù)集本身的難點(diǎn)。后面的第六節(jié)會(huì)具體演示怎么定位這些問(wèn)題。注意 排查問(wèn)題的一個(gè)通用原則先確認(rèn)數(shù)據(jù)正確再確認(rèn)模型結(jié)構(gòu)最后才調(diào)訓(xùn)練參數(shù)。順序反過(guò)來(lái)的話經(jīng)常會(huì)在模型寫(xiě)錯(cuò)的情況下白調(diào)半天學(xué)習(xí)率。 /注意6. 用混淆矩陣定位分類邊界找出模型真正沒(méi)學(xué)會(huì)的類別準(zhǔn)確率只是一個(gè)數(shù)字模型到底在哪兩類之間猶豫要靠混淆矩陣才能看清楚。Keras 的evaluate只給一個(gè)總分但作業(yè)和實(shí)際項(xiàng)目里分析具體錯(cuò)分才是調(diào)優(yōu)的前提。import numpy as np from sklearn.metrics import confusion_matrix import matplotlib.pyplot as plt import seaborn as sns y_pred np.argmax(model_cnn.predict(x_test_cnn), axis1) class_names [T恤, 褲子, 套頭衫, 裙子, 外套, 涼鞋, 襯衫, 運(yùn)動(dòng)鞋, 包, 短靴] cm confusion_matrix(y_test, y_pred) plt.figure(figsize(10, 8)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsclass_names, yticklabelsclass_names) plt.xlabel(預(yù)測(cè)類別) plt.ylabel(真實(shí)類別) plt.show()predict返回的是概率矩陣np.argmax(..., axis1)按行取最大值索引得到的就是每個(gè)樣本的預(yù)測(cè)類別?;煜仃嚨男惺钦鎸?shí)標(biāo)簽列是預(yù)測(cè)標(biāo)簽對(duì)角線越深越好。觀察非對(duì)角線的高亮位置如果「襯衫」這一行大量落在「T 恤」列上說(shuō)明模型分不清這兩類的袖長(zhǎng)差異「短靴」和「運(yùn)動(dòng)鞋」互錯(cuò)說(shuō)明鞋底輪廓特征學(xué)得不夠。一個(gè)具體做法是讓模型按置信度排序輸出預(yù)測(cè)結(jié)果找到那些置信度高但預(yù)測(cè)錯(cuò)的樣本——這些是模型的盲區(qū)比隨機(jī)抽樣更有分析價(jià)值。從那以后我每次跑完訓(xùn)練都強(qiáng)制走一遍混淆矩陣加錯(cuò)誤樣本可視化哪怕是時(shí)間緊也至少看一眼對(duì)角線這份代碼包里的 main.py 結(jié)構(gòu)完整跑通后把第五節(jié)和第六節(jié)的片段加進(jìn)去就是一個(gè)比原作業(yè)更有參考價(jià)值的調(diào)優(yōu)腳本希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取