習(xí)實戰(zhàn):從數(shù)據(jù)解析到模型訓(xùn)練全攻略)
簡介Cifar-10數(shù)據(jù)集是計算機視覺領(lǐng)域廣泛使用的開源圖像分類數(shù)據(jù)集由加拿大高級研究院相關(guān)團隊創(chuàng)建適合深度學(xué)習(xí)初學(xué)者、算法研究人員及CNN模型開發(fā)者用于圖像識別模型訓(xùn)練與效果驗證。壓縮包共8個文件包括5個data_batch訓(xùn)練批次、1個test_batch測試批次以及batches.meta類別元數(shù)據(jù)和readme.html說明文檔整體大小162.6MB。內(nèi)部為Python版本數(shù)據(jù)可直接配合TensorFlow、PyTorch等深度學(xué)習(xí)框架讀取訓(xùn)練集和測試集劃分清晰按批次存放便于分批加載與內(nèi)存管理。數(shù)據(jù)覆蓋飛機、汽車、鳥類、貓、鹿、狗、青蛙、馬、船、卡車10個常見類別共60000張32×32彩色圖像每張圖像包含RGB三通道共3072個像素特征。由于數(shù)據(jù)規(guī)模適中既適合初學(xué)者快速搭建CNN理解卷積、池化與全連接層也適合研究者對比不同網(wǎng)絡(luò)結(jié)構(gòu)、數(shù)據(jù)增強策略和正則化方法。目前已有2666人瀏覽學(xué)習(xí)是入門計算機視覺、實踐圖像分類與模型評估流程的高性價比資源。 Cifar-10這個數(shù)據(jù)集深度學(xué)習(xí)入門繞不過去的一道坎。我最早跑它的時候還在用TensorFlow 1.x為了把數(shù)據(jù)從batch文件里摳出來還得自己寫解析函數(shù)現(xiàn)在torchvision一行代碼就能搞定但很多人反而對數(shù)據(jù)本身少了點敬畏心。這篇就結(jié)合我用Cifar-10踩過的坑和總結(jié)出的經(jīng)驗把這個數(shù)據(jù)集從里到外拆一遍從數(shù)據(jù)構(gòu)成到訓(xùn)練trick從常見報錯到后續(xù)擴展方向爭取讓新朋友少走彎路也讓老手能查漏補缺。1. 項目整體認知為什么深度學(xué)習(xí)入門首選Cifar-101.1 數(shù)據(jù)集的誕生背景與定位Cifar-10由加拿大先進研究院Canadian Institute For Advanced Research的Alex Krizhevsky、Vinod Nair和Geoffrey Hinton等人收集整理是Cifar系列里最廣為人知的一個。它和MNIST最大的區(qū)別在于圖像從28x28的單通道灰度圖變成了32x32的三通道彩色圖這個變化直接決定了模型要從看輪廓升級到看紋理、看顏色、看局部特征組合訓(xùn)練難度和模型容量需求完全不在一個量級。當(dāng)時收集這個數(shù)據(jù)集的初衷是為了給圖像識別算法提供一個比MNIST更具挑戰(zhàn)性又不至于像ImageNet那樣大到普通人根本跑不動的中間檔數(shù)據(jù)集。32x32的分辨率現(xiàn)在看來很小但在2009年那個硬件環(huán)境下這個尺寸已經(jīng)足夠讓當(dāng)時的機器學(xué)習(xí)算法非常吃力。到今天Cifar-10依然是學(xué)術(shù)界快速驗證新模型架構(gòu)、新訓(xùn)練技巧的標(biāo)準(zhǔn)試驗田幾乎每一篇圖像分類論文都會順手在Cifar-10上報個結(jié)果。1.2 數(shù)據(jù)規(guī)模與類別分布細節(jié)Cifar-10一共包含60000張圖像分10個類別每個類別6000張具體類別包括飛機、汽車、鳥、貓、鹿、狗、青蛙、馬、船、卡車。其中50000張作為訓(xùn)練集10000張作為測試集。訓(xùn)練集和測試集的劃分是官方固定好的這樣不同研究者的實驗結(jié)果才具有可比性這也是我在實際使用中反復(fù)強調(diào)的一點——不要自己重新切分訓(xùn)練集和測試集直接用官方劃分否則你跑出來的精度沒法跟論文里的數(shù)字直接對比。每張圖像尺寸是32x32x3意味著總共約3072個像素值每個像素值范圍是0-255。原始的Cifar-10數(shù)據(jù)存儲方式很有年代感用的是Python的pickle序列化格式分5個訓(xùn)練batch和1個測試batch每個batch里有一個叫data的numpy數(shù)組形狀是10000x3072另外還有l(wèi)abels列表和filenames列表。這個3072維的順序是先R通道的1024個像素、再G通道、再B通道不是常見的HWC排列第一次手動處理時特別容易在這里出錯。1.3 適用人群與實際應(yīng)用場景如果你剛學(xué)完神經(jīng)網(wǎng)絡(luò)的基礎(chǔ)理論想找一個能快速出結(jié)果但又沒那么簡單的項目練手Cifar-10是性價比最高的選擇。它不像MNIST那樣隨便一個線性模型就能到92%以上需要你真正理解卷積、池化、批歸一化、數(shù)據(jù)增強這些概念但又不需要像ImageNet那樣動輒幾十張顯卡和多周訓(xùn)練時間。在單張消費級顯卡上用PyTorch或TensorFlow從頭訓(xùn)練一個簡單的CNN模型幾分鐘就能跑完一個epoch一兩個小時就能看到比較理想的收斂效果。除了練手Cifar-10在學(xué)術(shù)界的使用場景也很豐富。模型結(jié)構(gòu)搜索NAS、知識蒸餾、對比學(xué)習(xí)、對抗樣本攻擊與防御、神經(jīng)網(wǎng)絡(luò)可解釋性分析這些方向的研究者經(jīng)常把Cifar-10作為主要的評測基準(zhǔn)之一。甚至工業(yè)界在驗證一個新的訓(xùn)練加速框架、分布式策略或推理優(yōu)化工具時也習(xí)慣先在Cifar-10上做小規(guī)模驗證因為它數(shù)據(jù)集小、迭代快能快速暴露框架層面的問題。2. 數(shù)據(jù)準(zhǔn)備實操三種加載方式詳解2.1 方式一使用PyTorch內(nèi)置API自動加載PyTorch官方torchvision包內(nèi)置了Cifar-10的下載和加載接口這是目前大多數(shù)人采用的方式也是我最推薦的入門方案。核心代碼非常簡單import torch from torchvision import datasets, transforms transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)) ]) train_set datasets.CIFAR10(root./data, trainTrue, downloadTrue, transformtransform) test_set datasets.CIFAR10(root./data, trainFalse, downloadTrue, transformtransform) train_loader torch.utils.data.DataLoader(train_set, batch_size64, shuffleTrue, num_workers4) test_loader torch.utils.data.DataLoader(test_set, batch_size64, shuffleFalse, num_workers4)這段代碼里的標(biāo)準(zhǔn)化參數(shù)是我踩過坑之后專門背下來的。Cifar-10數(shù)據(jù)集的RGB三通道均值是(0.4914, 0.4822, 0.4465)標(biāo)準(zhǔn)差是(0.2023, 0.1994, 0.2010)這三個數(shù)值是官方社區(qū)統(tǒng)計出來的不是隨便拍的。使用正確的均值方差做標(biāo)準(zhǔn)化和隨便用(0.5, 0.5, 0.5)相比在訓(xùn)練穩(wěn)定性和最終精度上區(qū)別很大特別是使用帶有動量項的優(yōu)化器時不準(zhǔn)確的標(biāo)準(zhǔn)化參數(shù)會導(dǎo)致前期loss震蕩明顯。2.2 方式二手動下載原始文件與解析如果遇到網(wǎng)絡(luò)問題導(dǎo)致自動下載失敗或者你需要在離線環(huán)境下使用Cifar-10就必須掌握手動下載和解析的方法。官方文件可以從CIFAR官網(wǎng)或者各鏡像站點獲取核心文件是cifar-10-python.tar.gz解壓后得到cifar-10-batches-py文件夾里面是5個data_batch_*文件、1個test_batch文件以及batches.meta文件。手動解析代碼可以這樣實現(xiàn)注意unpickle方法和數(shù)據(jù)維度變換import pickle import numpy as np def unpickle(file): with open(file, rb) as fo: dict_data pickle.load(fo, encodingbytes) return dict_data data unpickle(cifar-10-batches-py/data_batch_1) images data[bdata].reshape(-1, 3, 32, 32).transpose(0, 2, 3, 1) labels data[blabels]這套代碼里的reshape和transpose是核心。原始數(shù)據(jù)排布是Nx3072前1024位是R通道、中間1024位是G通道、最后1024位是B通道。必須先reshape成Nx3x32x32再transpose才能得到我們習(xí)慣的Nx32x32x3格式。很多初學(xué)者在這里直接reshape成Nx32x32x3得到的結(jié)果是完全錯亂的圖像排查半天才發(fā)現(xiàn)是通道順序問題。2.3 目錄結(jié)構(gòu)規(guī)劃與數(shù)據(jù)集復(fù)用在實際項目中數(shù)據(jù)集的目錄結(jié)構(gòu)值得提前規(guī)劃否則后期換機器、遷移代碼時容易出亂子。我習(xí)慣這樣組織project_root/ ├── data/ │ └── cifar-10-batches-py/ │ ├── batches.meta │ ├── data_batch_1 ~ data_batch_5 │ └── test_batch ├── checkpoints/ ├── logs/ └── train.pydata目錄單獨存放原始數(shù)據(jù)集checkpoints存模型權(quán)重logs存TensorBoard日志。這樣做的核心好處是代碼目錄和數(shù)據(jù)目錄分離后續(xù)做數(shù)據(jù)清洗或切換數(shù)據(jù)集不用改動訓(xùn)練腳本同時可以在多個項目間共用同一個數(shù)據(jù)目錄避免重復(fù)下載。PyTorch的datasets.CIFAR10接口在檢測到root目錄下已有完整數(shù)據(jù)時會自動跳過下載環(huán)節(jié)所以即使換了一臺機器只要把data目錄拷貝過去就能直接跑。3. 核心細節(jié)拆解從數(shù)據(jù)理解到模型設(shè)計3.1 數(shù)據(jù)特征分析與預(yù)處理策略Cifar-10的32x32低分辨率特性決定了它和ImageNet類高分辨率數(shù)據(jù)集在預(yù)處理策略上的本質(zhì)區(qū)別。因為分辨率低圖像信息量有限過度的隨機裁剪可能導(dǎo)致關(guān)鍵目標(biāo)的邊緣被切掉很大一部分反而不利于模型學(xué)習(xí)。我在實踐中常用的增強組合是隨機水平翻轉(zhuǎn)、隨機裁剪padding4、CutOut或RandomErasing這套組合在ResNet系列上能穩(wěn)定帶來2-3個百分點的提升。這里有個特別實用的細節(jié)transforms.RandomCrop(32, padding4)會在原圖四周先填充4個像素的0再隨機裁剪出32x32的區(qū)域相當(dāng)于給模型提供了一定程度的平移不變性。填充方式默認是零填充但我實測下來改為反射填充padding_modereflect在某些模型上效果會略微好一點因為它不會在邊緣制造突兀的黑色邊界保留了更自然的圖像統(tǒng)計特征。3.2 模型結(jié)構(gòu)選型與參數(shù)量考量針對Cifar-10模型選擇范圍很廣取決于你的硬件條件和訓(xùn)練預(yù)算。我做過一些系統(tǒng)對比簡單說說結(jié)論。如果你用的CPU訓(xùn)練建議用輕量的LeNet或修改版VGG-11通道數(shù)減半?yún)?shù)量在幾百萬級別單epoch時間可控。如果是單張RTX 3060或以上級別的顯卡ResNet-18是性價比最高的選擇參數(shù)量約1100萬用混合精度訓(xùn)練五分鐘左右就能跑完一個epoch50個epoch大約四小時就能收斂到93%以上的測試精度。需要強調(diào)的是Cifar-10版本的數(shù)據(jù)集尺寸小直接用為ImageNet設(shè)計的ResNet-18結(jié)構(gòu)其實有點殺雞用牛刀而且原始ResNet-18的第一個卷積層是7x7、stride2用在32x32輸入上會直接丟掉大量信息。因此社區(qū)慣例是在Cifar-10上把第一個卷積層改成3x3、stride1并且去掉后面的最大池化層。這個改動非常關(guān)鍵不改直接跑的話精度會掉一截。3.3 訓(xùn)練配置與超參數(shù)選擇的深層邏輯訓(xùn)練Cifar-10的超參數(shù)配置我整理了一份經(jīng)過反復(fù)驗證的基礎(chǔ)配置表直接抄就能跑出不錯的效果。超參數(shù)推薦值說明優(yōu)化器SGDmomentum0.9weight_decay5e-4初始學(xué)習(xí)率0.1配合Cosine退火或分段衰減Batch Size128單卡推薦過大過小都會影響收斂訓(xùn)練輪數(shù)100-200配合數(shù)據(jù)增強100輪即可到90%學(xué)習(xí)率調(diào)度CosineAnnealing比StepLR穩(wěn)定無需手動調(diào)整標(biāo)簽平滑0.1可有效抑制過擬合約提升0.3%-0.5%關(guān)于優(yōu)化器我在Cifar-10上始終推薦SGD而非Adam。原因很樸素Cifar-10數(shù)據(jù)量不大Adam雖然收斂快但容易收斂到尖銳極小值泛化性能會打折扣。SGD配合余弦退火雖然前期收斂慢些但最終測試精度通常比Adam高1-2個百分點。如果你實在喜歡Adam建議搭配SAMSharpness-Aware Minimization這類面向泛化的優(yōu)化策略但訓(xùn)練時間會顯著增加。3.4 數(shù)據(jù)增強與正則化的正確使用方法Cifar-10只有5萬張訓(xùn)練圖像對現(xiàn)代深度學(xué)習(xí)模型來說數(shù)據(jù)量偏少所以數(shù)據(jù)增強不是可選項而是必需品。我推薦的增強組合以及對應(yīng)效果如下僅使用隨機水平翻轉(zhuǎn)準(zhǔn)確率約93%增加RandomCrop(padding4)準(zhǔn)確率約94.5%繼續(xù)增加CutOut或RandomErasing準(zhǔn)確率約95%配合AutoAugment或RandAugment準(zhǔn)確率可達96%以上。提示CutOut的核心是在訓(xùn)練圖像上隨機遮擋一個方形區(qū)域迫使模型去學(xué)習(xí)目標(biāo)的整體結(jié)構(gòu)而非局部強特征。它的實現(xiàn)非常簡單但效果顯著是性價比極高的增強手段。使用RandAugment時要注意因為它的增強強度和多樣性更高建議同時加大weight_decay到1e-3否則容易過擬合。4. 完整實操流程從零訓(xùn)練一個Cifar-10分類器4.1 環(huán)境準(zhǔn)備與依賴安裝訓(xùn)練Cifar-10對硬件要求很低我在這臺只有8GB顯存的舊顯卡上也能輕松訓(xùn)練ResNet-18。軟件環(huán)境建議Python 3.8、PyTorch 1.12、torchvision 0.13如果有CUDA設(shè)備建議安裝對應(yīng)的GPU版本。安裝命令比較簡單我直接使用pip安裝pip install torch torchvision裝完之后用一小段代碼驗證環(huán)境是否正確檢查CUDA是否可用import torch print(torch.__version__) print(torch.cuda.is_available()) if torch.cuda.is_available(): print(torch.cuda.get_device_name(0))如果cuda.is_available()返回False優(yōu)先檢查PyTorch版本是否匹配你的CUDA版本。這里最常見的問題是裝了CPU版本的PyTorch雖然在Cifar-10上也能跑但訓(xùn)練速度會慢20倍以上純CPU訓(xùn)練ResNet-18一個epoch可能要十分鐘GPU只需要半分鐘。4.2 數(shù)據(jù)加載與模型構(gòu)建完整代碼我直接給出一個完整可運行的訓(xùn)練腳本核心部分包括數(shù)據(jù)加載、模型構(gòu)建和訓(xùn)練循環(huán)import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms from torchvision import models # 數(shù)據(jù)增強 transform_train transforms.Compose([ transforms.RandomCrop(32, padding4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) transform_test transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) # 數(shù)據(jù)加載 trainset torchvision.datasets.CIFAR10(root./data, trainTrue, downloadTrue, transformtransform_train) trainloader torch.utils.data.DataLoader(trainset, batch_size128, shuffleTrue, num_workers4) testset torchvision.datasets.CIFAR10(root./data, trainFalse, downloadTrue, transformtransform_test) testloader torch.utils.data.DataLoader(testset, batch_size128, shuffleFalse, num_workers4) # 構(gòu)建ResNet-18并適配32x32輸入 model models.resnet18(num_classes10) model.conv1 nn.Conv2d(3, 64, kernel_size3, stride1, padding1, biasFalse) model.maxpool nn.Identity() device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() optimizer optim.SGD(model.parameters(), lr0.1, momentum0.9, weight_decay5e-4) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max100)注意幾個關(guān)鍵點。model.conv1的替換是必要的因為原始的7x7步長為2的卷積核對于32x32的輸入來說感受野過大。model.maxpool nn.Identity()也很關(guān)鍵原始的最大池化層會讓特征圖尺寸減半對于32x32的輸入來說信息損失嚴重。做完這兩處適配模型才能正確處理Cifar-10圖像。4.3 訓(xùn)練循環(huán)與測試評估代碼訓(xùn)練循環(huán)寫法比較常規(guī)但我習(xí)慣在每個epoch結(jié)束后同時評估驗證集準(zhǔn)確率并保存最優(yōu)模型。這里額外提一下訓(xùn)練過程中最好記錄每個epoch的loss和準(zhǔn)確率方便后期繪制曲線分析模型的收斂行為。best_acc 0.0 for epoch in range(100): model.train() running_loss 0.0 correct 0 total 0 for inputs, labels in trainloader: inputs, labels inputs.to(device), labels.to(device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() _, predicted outputs.max(1) total labels.size(0) correct predicted.eq(labels).sum().item() train_acc 100.0 * correct / total train_loss running_loss / len(trainloader) # 測試 model.eval() test_correct 0 test_total 0 with torch.no_grad(): for inputs, labels in testloader: inputs, labels inputs.to(device), labels.to(device) outputs model(inputs) _, predicted outputs.max(1) test_total labels.size(0) test_correct predicted.eq(labels).sum().item() test_acc 100.0 * test_correct / test_total scheduler.step() if test_acc best_acc: best_acc test_acc torch.save(model.state_dict(), checkpoints/best_model.pth) print(fEpoch {epoch1:3d} | Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.2f}% | Test Acc: {test_acc:.2f}%)這段代碼里我把scheduler.step()放在每個epoch結(jié)束后調(diào)用對應(yīng)的是T_max100的余弦退火周期。如果你用的是ReduceLROnPlateau這類需要依賴指標(biāo)的調(diào)度器則要把step放在評估結(jié)束后并傳入對應(yīng)的監(jiān)控指標(biāo)。這里要注意的是PyTorch 1.12及以上版本在scheduler的step時機上行為有微妙變化若發(fā)現(xiàn)學(xué)習(xí)率沒有按預(yù)期下降優(yōu)先檢查scheduler的step是否被正確調(diào)用。4.4 訓(xùn)練結(jié)果分析與精度解讀按上面的配置訓(xùn)練100個epoch使用ResNet-18結(jié)構(gòu)單卡RTX 3060大約需要3-4小時最終測試精度大約在93.5%-94.5%之間。這個數(shù)字已經(jīng)超過了大多數(shù)論文中傳統(tǒng)機器學(xué)習(xí)方法的上限但在深度學(xué)習(xí)社區(qū)里還屬于入門級成績。如果你追求更好的精度可以嘗試幾個方向加大訓(xùn)練輪數(shù)到200甚至300個epoch配合更強的隨機增強將ResNet-18換成ResNet-50或Wide-ResNet但訓(xùn)練時間和顯存占用會顯著上升使用預(yù)訓(xùn)練模型微調(diào)比如在ImageNet或其它更大數(shù)據(jù)集上預(yù)訓(xùn)練的模型直接微調(diào)到Cifar-10通常能在更少的epoch內(nèi)達到更高精度。但需要說明的是用ImageNet預(yù)訓(xùn)練模型在Cifar-10上微調(diào)有數(shù)據(jù)泄漏的討論空間做學(xué)術(shù)對比時要注意說明。關(guān)于浮點精度建議在訓(xùn)練全程使用自動混合精度AMP在PyTorch中使用torch.cuda.amp非常方便對精度幾乎沒有損失但訓(xùn)練時間可以縮短近一半。對這個任務(wù)來說GPU資源占用不緊張但AMP帶來的時間收益相當(dāng)可觀。5. 常見問題與排查技巧實錄5.1 數(shù)據(jù)下載失敗與網(wǎng)絡(luò)問題在國內(nèi)網(wǎng)絡(luò)環(huán)境下從官方源下載Cifar-10經(jīng)常會中斷這是初學(xué)者遇到的第一個攔路虎。torchvision的下載源是斯坦福大學(xué)的服務(wù)器速度不穩(wěn)定是常態(tài)。解決辦法有兩個一是直接手動下載到本地然后放在root參數(shù)指定的目錄下并解壓成正確的目錄結(jié)構(gòu)二是使用鏡像源比如某些機構(gòu)提供的國內(nèi)鏡像或者使用gitee上網(wǎng)友上傳的預(yù)下載版本。我個人的建議是方案一手動下載最穩(wěn)妥。下載完成后需要注意目錄結(jié)構(gòu)要和torchvision內(nèi)部檢查的邏輯一致root指向的目錄下需要存在cifar-10-batches-py文件夾。如果你下載的是壓縮包需要先解壓。否則torchvision會以為數(shù)據(jù)未下載反復(fù)嘗試重新下載。5.2 標(biāo)準(zhǔn)化參數(shù)與數(shù)據(jù)集統(tǒng)計值不匹配很多初學(xué)者在遷移代碼時直接沿用MNIST的標(biāo)準(zhǔn)化參數(shù)(0.1307,)和(0.3081,)放到Cifar-10上結(jié)果訓(xùn)練曲線非常奇怪收斂緩慢且最終精度偏低。這是因為Cifar-10是RGB三通道圖像每個通道的均值和方差都不同必須使用對應(yīng)數(shù)據(jù)集的統(tǒng)計值。可以從訓(xùn)練集中直接計算均值方差代碼參考import torch from torchvision import datasets, transforms dataset datasets.CIFAR10(root./data, trainTrue, downloadFalse, transformtransforms.ToTensor()) means torch.zeros(3) stds torch.zeros(3) for img, _ in dataset: means img.mean(dim(1, 2)) stds img.std(dim(1, 2)) means / len(dataset) stds / len(dataset) print(fmean: {means}, std: {stds})這個計算出來的數(shù)值和官方社區(qū)統(tǒng)計的(0.4914, 0.4822, 0.4465)差別很小但用代碼算更符合實際數(shù)據(jù)分布值得掌握。5.3 訓(xùn)練loss不下降的排查思路如果你發(fā)現(xiàn)模型訓(xùn)練的loss一直徘徊在2.3左右?guī)缀醪幌陆颠@個問題大概率不是模型結(jié)構(gòu)有問題而是學(xué)習(xí)率設(shè)置不當(dāng)。Cifar-10類別數(shù)是10隨機分類的交叉熵損失就是ln(10)≈2.3026一直卡在這個值說明模型完全沒有學(xué)到任何有效特征。排查順序建議如下。先確認數(shù)據(jù)加載是否正確可以在訓(xùn)練前打印一張圖像并可視化檢查圖像是否被正確標(biāo)準(zhǔn)化、是否有異常像素值。然后檢查優(yōu)化器的學(xué)習(xí)率如果學(xué)習(xí)率過大比如大于0.5會導(dǎo)致loss震蕩甚至發(fā)散過小小于0.001會導(dǎo)致收斂極慢。最后檢查模型輸出的形狀是否正確、標(biāo)簽是否在0-9范圍內(nèi)。還有一種隱蔽的坑是梯度爆炸特別是使用不帶梯度裁剪的Transformer類模型或深層CNN時。遇到這種情況加入梯度裁剪例如torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5)通常能穩(wěn)住訓(xùn)練過程。5.4 數(shù)據(jù)加載速度過慢的優(yōu)化方案很多人在Cifar-10上訓(xùn)練時覺得瓶頸在GPU利用率不夠其實往往卡在數(shù)據(jù)加載環(huán)節(jié)。num_workers0是PyTorch默認值代表數(shù)據(jù)加載在主進程中進行會嚴重影響訓(xùn)練吞吐。我在實際使用中推薦設(shè)置num_workers4或8可以顯著提高數(shù)據(jù)加載效率。如果數(shù)據(jù)加載仍然是瓶頸可以把pin_memoryTrue加上這將顯著加快GPU和CPU之間的數(shù)據(jù)傳輸。不過需要注意如果顯存本身比較緊張pin_memory會額外占用部分鎖頁內(nèi)存需要綜合考慮。另一個優(yōu)化方向是在數(shù)據(jù)增強上使用GPU加速例如通過torchvision.transforms.v2或albumentations庫實現(xiàn)部分增強操作在GPU上執(zhí)行。5.5 實驗結(jié)果復(fù)現(xiàn)的隨機種子設(shè)置深度學(xué)習(xí)中涉及大量隨機因素包括數(shù)據(jù)加載順序、參數(shù)初始化、dropout等。為了保證實驗結(jié)果可復(fù)現(xiàn)需要在代碼開頭設(shè)置隨機種子import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark False這里有一個權(quán)衡問題。開啟cudnn.deterministic True會犧牲一定的運行速度來換取可復(fù)現(xiàn)性而cudnn.benchmark True則會在輸入尺寸固定時自動選擇最優(yōu)卷積算法速度更快但結(jié)果可能不完全一致。在實驗探索階段可以保持benchmarkTrue在需要報告精確結(jié)果或做消融對比時再開啟固定種子和確定性模式。6. 數(shù)據(jù)集對比與擴展方向思考6.1 Cifar-10、MNIST、ImageNet的定位差異這三者經(jīng)常被放在一起比較但它們的定位和目標(biāo)完全不同。MNIST基本只能驗證模型能否工作由于任務(wù)過于簡單很多算法差異在MNIST上體現(xiàn)不出來。Cifar-10是真正的入門級難關(guān)它能檢驗?zāi)P褪欠窬邆浠A(chǔ)的圖像特征提取能力且訓(xùn)練成本可控。ImageNet則屬于大規(guī)模數(shù)據(jù)集有約128萬張訓(xùn)練圖像和1000個類別用于驗證模型在大規(guī)模數(shù)據(jù)下的擴展能力和泛化性能。在實際研究工作中三者的使用場景也有明確分工。跑demo、驗證idea是否可行用MNIST做消融實驗、驗證模塊設(shè)計的有效性用Cifar-10最終驗證大規(guī)模有效性或者參加比賽用ImageNet。很多頂會論文的實驗結(jié)構(gòu)就是沿著這個邏輯層層遞進的。6.2 Cifar-10向語義分割和目標(biāo)檢測的擴展雖然Cifar-10是圖像分類數(shù)據(jù)集但在實際工程中它也可以作為入門目標(biāo)檢測和語義分割任務(wù)的前置訓(xùn)練集。Cifar-10圖像上包含單一大目標(biāo)加上人工標(biāo)注的邊界框標(biāo)簽就可以用來訓(xùn)練一個簡單的目標(biāo)檢測器。另外Cifar-10也可以作為學(xué)習(xí)自監(jiān)督對比學(xué)習(xí)SimCLR、MoCo的實驗土壤因為數(shù)據(jù)集小、訓(xùn)練快非常適合快速驗證對比學(xué)習(xí)框架中的組件效果。如果直接上真實業(yè)務(wù)場景Cifar-10的能力確實有限。以最近比較熱門的視覺關(guān)系數(shù)據(jù)集、X光安檢物品檢測數(shù)據(jù)集、無人機低空航拍三維重建數(shù)據(jù)集為例這些任務(wù)都包含復(fù)雜的背景、多尺度目標(biāo)和空間位置關(guān)系Cifar-10無法為這些場景直接提供有效數(shù)據(jù)。但如果想在正式接觸這些復(fù)雜任務(wù)前先掌握數(shù)據(jù)集處理、模型訓(xùn)練和評測的標(biāo)準(zhǔn)流程Cifar-10是絕佳的練習(xí)場它的低門檻和快速迭代特性讓試錯成本幾乎為零可以放心折騰。6.3 從Cifar-10走向更高階數(shù)據(jù)集的遷移思路不少朋友訓(xùn)練完Cifar-10后會問下一步怎么辦。我的建議是不要急著一步跨到ImageNet而是先嘗試Cifar-100它的類別數(shù)增加到了100個每類只有500張訓(xùn)練圖能有效訓(xùn)練你處理細粒度分類和類別不均衡問題的能力。再往后可以嘗試SVHN街景門牌號或者Fashion-MNIST它們在特征分布上和Cifar-10有一定差異能幫助你打破只會跑Cifar的舒適區(qū)。在代碼層面從Cifar-10遷移到Cifar-100只需要改動類別數(shù)和數(shù)據(jù)加載接口模型輸出層的num_classes從10改成100即可。這也是Cifar系列數(shù)據(jù)集的便利之處。從工程角度看真正值得投入精力的是把訓(xùn)練好的模型保存下來然后再用自己的實際業(yè)務(wù)數(shù)據(jù)做微調(diào)這是深度學(xué)習(xí)落地最主流的路徑。我見過很多初學(xué)者卡在只會跑開源數(shù)據(jù)集這一步關(guān)鍵就在于沒有跳出數(shù)據(jù)集本身的限制去思考自己手頭真正的數(shù)據(jù)長什么樣。7. 寫在最后的一點個人體會今年已經(jīng)是我在Cifar-10上反復(fù)折騰的第四個年頭了。老實說有一次我為了在一個比賽里把精度從94%刷到95%在ResNet-18上試了幾十種不同的增強組合和訓(xùn)練策略最后發(fā)現(xiàn)提升最大的不是更復(fù)雜的模型而僅僅是把訓(xùn)練輪數(shù)翻了一倍。這件事給我的啟發(fā)是在Cifar-10這種小數(shù)據(jù)集上數(shù)據(jù)工程的精細程度往往比堆模型更值得投入精力。另外一個經(jīng)驗是Cifar-10非常適合用來驗證代碼框架的正確性。不管你是想學(xué)習(xí)PyTorch Lightning還是想搞懂DeepSpeed的分布式訓(xùn)練流程都可以先在Cifar-10上跑通一個小規(guī)模實驗再遷移到正式項目里。作為試驗田它的廉價和快速是無價的。最后給新手一個實用建議第一次訓(xùn)練Cifar-10不要追求精度先追求能完整跑通一個訓(xùn)練流程并且理解每一步在干什么你后續(xù)所有深度學(xué)習(xí)進階都會在這一塊地基上穩(wěn)步生長。本文還有配套的精品資源點擊獲取