絡結(jié)構(gòu)逐層拆解與PyTorch實戰(zhàn))
前幾天有個剛?cè)腴T的朋友問我都這個年代了YOLO系列已經(jīng)迭代到v11Transformer在各種任務上橫掃榜單再回頭啃一個2012年的AlexNet網(wǎng)絡結(jié)構(gòu)是不是有點浪費時間我當時沒直接回答而是讓他先說說AlexNet里第一個卷積層的卷積核尺寸是多少、步長是多少、輸出特征圖是多大——他卡住了。這個場景我見過太多次很多人上手就是調(diào)庫、跑demo模型能訓起來但對網(wǎng)絡結(jié)構(gòu)內(nèi)部的信息流動毫無概念一旦遇到shape不匹配或者想改結(jié)構(gòu)就徹底懵掉。AlexNet恰恰是解決這個問題的最佳切口它的網(wǎng)絡結(jié)構(gòu)足夠簡單直白五層卷積加三層全連接沒有任何花哨的殘差連接或者注意力機制每一層的張量形狀都能手算出來。這份內(nèi)容我會帶著你把AlexNet網(wǎng)絡結(jié)構(gòu)從頭到尾拆一遍再用PyTorch把網(wǎng)絡結(jié)構(gòu)的實現(xiàn)完整寫出來順帶把訓練里踩過的坑和排查思路一起交代清楚。不管你是剛學完PyTorch張量基礎想找個完整項目練手還是已經(jīng)能跑通pytorch官方的分類示例、但對底層結(jié)構(gòu)一知半解這份內(nèi)容都值得你花時間讀完。1. 2012年的AlexNet放到今天到底還值不值得啃1.1 它在深度學習歷史上的坐標位置2012年的ImageNet大規(guī)模視覺識別挑戰(zhàn)賽上AlexNet拿下了top-5錯誤率15.3%的成績而那一年的第二名錯誤率是26.2%差了將近11個百分點。這個差距在競賽語境下是碾壓級別的也正是從這一年開始卷積神經(jīng)網(wǎng)絡正式取代了以SIFT、HOG特征加SVM分類器為主的傳統(tǒng)視覺方案成為圖像識別的主流路線。這個成績背后有兩個當時看來非常奢侈的條件一是120萬張帶標注的訓練圖片二是兩塊GTX 580顯卡提供的算力。前者讓模型有足夠的樣本去學習后者讓研究者第一次能把一個6000萬參數(shù)的深層網(wǎng)絡真正訓起來。我一直覺得理解AlexNet的歷史處境比記住它的結(jié)構(gòu)更重要。那個年代顯存只有3GB一塊卡根本放不下整個網(wǎng)絡所以論文里才出現(xiàn)了分組卷積這種把網(wǎng)絡切成兩半、分別放在兩塊卡上跑的設計。今天你在PyTorch里寫groups2只需要一個參數(shù)但當年這是一個被硬件逼出來的工程妥協(xié)。搞明白這一點你看后面的ResNet、Inception、DenseNet這些結(jié)構(gòu)演進時就能理解每一個設計決策背后的約束條件是什么而不是死記硬背網(wǎng)絡結(jié)構(gòu)圖。1.2 手寫一遍和直接調(diào)torchvision的區(qū)別torchvision.models.alexnet(pretrainedTrue)這行代碼三秒鐘就能給你一個能用的模型那我為什么還建議你手寫一遍原因很直接調(diào)庫版本和你手寫的版本在細節(jié)上是有差異的而這些差異恰恰是理解網(wǎng)絡結(jié)構(gòu)的關鍵。torchvision里的AlexNet為了適配224×224的輸入和現(xiàn)代訓練習慣把第一層的64個卷積核改成了64原論文是96并且在分類器前面加了一個AdaptiveAvgPool2d。你要是直接拿這個版本去對照論文里的結(jié)構(gòu)圖會發(fā)現(xiàn)對不上號然后就開始懷疑自己是不是看錯了資料。手寫一遍還有個隱性收益你會被迫去算每一層的輸出尺寸。我見過太多人寫網(wǎng)絡時把卷積層堆在一起最后一個Linear層的輸入維度靠試錯湊出來報錯了就改數(shù)字改到不報錯為止。這種寫法在簡單網(wǎng)絡上能蒙對但一旦網(wǎng)絡結(jié)構(gòu)復雜一點或者你想插入一個新的模塊就徹底玩不轉(zhuǎn)了。自己動手推一遍(H 2p - k) / s 1這個公式把每一層的輸出形狀寫在紙上這個習慣的價值遠超一次性的代碼復現(xiàn)。2. 拆解網(wǎng)絡結(jié)構(gòu)從227×227到1000類的那條鏈路2.1 輸入尺寸的爭論227還是224這里有個很多人忽略的細節(jié)。原始論文里寫的輸入是227×227×3但你在各種教程和開源實現(xiàn)里看到的輸入尺寸經(jīng)常是224×224包括PyTorch官方版本用的也是224。這不是誰抄錯了而是歷史遺留問題。227這個數(shù)字的來源是這樣的ImageNet的原始圖片尺寸不一論文里先把圖片縮放到256×256然后隨機裁剪出224×224的區(qū)域送進網(wǎng)絡。但227×227這個數(shù)字出現(xiàn)在論文的表格里是因為當時那個表格統(tǒng)計的是不同的輸入設定。真正影響你實現(xiàn)的是你選227還是224會直接決定后面所有層的張量形狀。選227的話第一層卷積輸出是56×56選224的話輸出是55×55。聽起來只差1但經(jīng)過三次池化之后227路線得到的特征圖是6×6224路線得到的是6×6因為(55-3)/21 27再經(jīng)過兩層池化變13再池化變6兩者最終都能對上9216這個全連接輸入維度。所以兩種輸入在PyTorch里都能跑通這也是為什么大家不太在意這個差別。提示如果你打算加載預訓練權(quán)重務必用224×224的輸入因為預訓練權(quán)重是在這個尺寸下訓練出來的。用227去加載雖然形狀能對上但特征分布會有細微偏移精度會掉一點。2.2 逐層形狀推演與手算過程我把整個前向傳播的形狀變化整理成了一張表你對照著看會非常清楚每一步發(fā)生了什么。卷積輸出尺寸的計算公式是out floor((in 2 * padding - kernel_size) / stride) 1池化層同理。層名操作卷積核/窗口步長填充輸出形狀輸入----3×227×227conv1卷積11×11, 964296×56×56pool1最大池化3×32096×27×27conv2卷積分組25×5, 25612256×27×27pool2最大池化3×320256×13×13conv3卷積3×3, 38411384×13×13conv4卷積分組23×3, 38411384×13×13conv5卷積分組23×3, 25611256×13×13pool3最大池化3×320256×6×6flatten展平---9216fc1全連接---4096fc2全連接---4096fc3全連接---1000拿第一層舉例手算過程是(227 2*2 - 11) / 4 1 220 / 4 1 55 1 56。注意這里220除以4正好是55是整除的所以沒有向下取整的損失。第二層的池化(56 - 3) / 2 1 26.5 1這里必須向下取整成26所以結(jié)果是27。這個取整操作是最容易出錯的地方PyTorch默認的floor行為和論文一致但你自己手算的時候要記得取整。2.3 分組卷積被顯存逼出來的巧妙設計分組卷積這個概念在今天看來有點陌生因為現(xiàn)在顯存動輒24G、80G很少有人還需要靠拆分網(wǎng)絡來塞進顯卡。但在AlexNet那個年代這是必需的。具體做法是把卷積核和輸入通道都分成兩組每組只處理自己那一半的輸入通道兩組之間不通信最后把輸出拼接起來??催@張對比表會更直觀層是否分組每組輸入通道每組輸出通道實際意義conv1否396輸入通道太少分不了conv2是48128兩塊卡各算一半conv3否256384跨組連接信息融合conv4是192192再次分組conv5是192128輸出前最后一次分組conv3這一層特別有意思它是唯一一個把兩組信息重新連通的卷積層。前兩層分組算完之后如果繼續(xù)分組下去兩組特征就永遠不交流了模型的表達能力會嚴重受限。所以論文在中間插了一個不分組的conv3把256個輸入通道全部連接起來做一次信息融合然后再分組往下走。這個設計思路其實和后來Inception里的分支融合、ResNet里的殘差連接有異曲同工之處都是在解決分支之間如何交流這個問題。2.4 參數(shù)量分布6000萬參數(shù)都藏在哪我算了一下每一層的參數(shù)量結(jié)果非常顛覆直覺層權(quán)重參數(shù)量占總參數(shù)比例conv134,8480.06%conv2307,2000.50%conv3884,7361.45%conv4663,5521.09%conv5442,3680.73%fc137,748,73661.9%fc216,777,21627.5%fc34,096,0006.7%五個卷積層加起來只有233萬參數(shù)占總量的3.8%而三個全連接層吃掉了剩下的96%。這個數(shù)據(jù)我第一次算出來的時候也愣了一下。原因在于卷積層的權(quán)重是共享的一個11×11×3的卷積核掃過整張圖參數(shù)量只有363個而全連接層每個神經(jīng)元都要和上一層的每一個輸出相連9216×4096這一層就堆了3775萬個參數(shù)。這個發(fā)現(xiàn)直接解釋了AlexNet為什么容易過擬合、為什么dropout這么關鍵。論文在前兩層全連接后都加了dropout概率0.5就是為了壓住這部分參數(shù)量帶來的過擬合風險。后來的網(wǎng)絡結(jié)構(gòu)演進中全連接層被逐步削減直到ResNet之后基本被全局平均池化替代本質(zhì)上就是在解決參數(shù)冗余的問題。3. PyTorch落地實現(xiàn)一份能直接跑通的代碼3.1 環(huán)境準備與版本選擇先把環(huán)境弄干凈。我的習慣是用conda單獨建一個環(huán)境避免和系統(tǒng)的Python環(huán)境互相污染conda create -n alexnet python3.10 -y conda activate alexnet現(xiàn)在裝PyTorch。這里有個高頻的踩坑點不要直接pip install torch那樣裝到的可能是CPU版本也可能因為源的問題裝到和你的CUDA不匹配的版本。先查一下你的CUDA版本nvidia-smi假設輸出顯示CUDA Version是12.1那就裝對應的版本pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121如果你機器上根本沒有NVIDIA顯卡或者只想先在CPU上把結(jié)構(gòu)跑通那就用pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu裝完之后一定要驗證一下import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else CPU only)注意如果這里報了OSError: [WinError 1114] 動態(tài)鏈接庫(DLL)初始化例程失敗絕大多數(shù)情況下是VC運行庫缺失去裝一個微軟的Visual C Redistributable就能解決。還有一種可能是conda環(huán)境和pip混裝導致的DLL沖突這時候最簡單粗暴的辦法是刪掉環(huán)境重建全程只用pip裝PyTorch。關于anaconda和PyCharm的配合我個人的建議是不要在PyCharm里創(chuàng)建虛擬環(huán)境而是在終端里用conda建好環(huán)境然后在PyCharm的項目解釋器設置里指向這個環(huán)境的python.exe。這樣環(huán)境管理會清晰很多換編輯器的時候環(huán)境照樣能用。3.2 從零搭建網(wǎng)絡層下面這份代碼是忠實還原論文結(jié)構(gòu)的版本包括分組卷積import torch import torch.nn as nn class AlexNet(nn.Module): def __init__(self, num_classes1000): super(AlexNet, self).__init__() self.features nn.Sequential( # conv1: 3 - 96, 11x11, stride 4, pad 2 nn.Conv2d(3, 96, kernel_size11, stride4, padding2), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size3, stride2), # conv2: 96 - 256, 5x5, groups2 nn.Conv2d(96, 256, kernel_size5, padding2, groups2), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size3, stride2), # conv3: 256 - 384, 3x3, 跨組連接 nn.Conv2d(256, 384, kernel_size3, padding1), nn.ReLU(inplaceTrue), # conv4: 384 - 384, groups2 nn.Conv2d(384, 384, kernel_size3, padding1, groups2), nn.ReLU(inplaceTrue), # conv5: 384 - 256, groups2 nn.Conv2d(384, 256, kernel_size3, padding1, groups2), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size3, stride2), ) self.classifier nn.Sequential( nn.Dropout(p0.5), nn.Linear(256 * 6 * 6, 4096), nn.ReLU(inplaceTrue), nn.Dropout(p0.5), nn.Linear(4096, 4096), nn.ReLU(inplaceTrue), nn.Linear(4096, num_classes), ) def forward(self, x): x self.features(x) x torch.flatten(x, 1) x self.classifier(x) return x這份代碼里有幾個地方值得單獨說。inplaceTrue這個參數(shù)是省顯存的小技巧ReLU直接修改輸入張量而不新建一個在顯存緊張的場景下能省下不少空間代價是副作用會覆蓋原始輸入調(diào)試時看不到激活前的值。torch.flatten(x, 1)里的1表示從第1維開始展平保留batch維度這是最容易寫錯的地方寫成torch.flatten(x)會把batch也拍平后面全連接層的維度就對不上了。3.3 權(quán)重初始化與形狀自檢AlexNet論文里給了一個明確的初始化方案權(quán)重用均值為0、標準差0.01的高斯分布第二、四、五層卷積和全連接層的偏置初始化為1其余層的偏置初始化為0。這個偏置為1的細節(jié)是為了給ReLU提供正輸入避免大量神經(jīng)元在訓練初期就死掉。def init_weights(m): if isinstance(m, nn.Conv2d) or isinstance(m, nn.Linear): nn.init.normal_(m.weight, mean0.0, std0.01) if m.bias is not None: nn.init.constant_(m.bias, 0) model AlexNet(num_classes1000) model.apply(init_weights) # 把第二、四、五層卷積和全連接層的bias置為1 for name, module in model.named_modules(): if name in [features.3, features.8, features.10, classifier.1, classifier.4]: nn.init.constant_(module.bias, 1)寫完一定要做形狀自檢這里一個小技巧就夠用x torch.randn(1, 3, 227, 227) out model(x) print(out.shape) # 期望 torch.Size([1, 1000]) # 統(tǒng)計參數(shù)量 total sum(p.numel() for p in model.parameters()) print(fTotal params: {total:,}) # 期望約 61,000,000 上下如果形狀報錯用逐層打印的方式定位x torch.randn(1, 3, 227, 227) for i, layer in enumerate(model.features): x layer(x) print(flayer {i}: {layer.__class__.__name__} - {x.shape})這個方法比看報錯信息快得多一眼就能看出是哪一層開始對不上的。3.4 分組卷積在PyTorch里的寫法與幾個細節(jié)groups2這個參數(shù)看起來簡單但有幾個約束條件必須記住輸入通道數(shù)和輸出通道數(shù)都必須能被groups整除。conv2里輸入96除以2得48輸出256除以2得128都整除沒問題。如果你手改網(wǎng)絡結(jié)構(gòu)時把輸出通道改成255程序會直接報錯。第一層為什么不能分組因為輸入只有3個通道3除以2不是整數(shù)PyTorch會直接拋異常所以那些Caffe版本里第一層帶分組參數(shù)的配置在PyTorch里需要把輸入先復制成兩份6通道這顯然不劃算官方實現(xiàn)也就放棄了。分組卷積的一個副作用是組間信息不流通。如果你在兩層分組卷積之間不插入不分組層模型相當于在并行訓練兩個獨立的子網(wǎng)絡表達能力會明顯下降。這就是為什么conv3必須存在。你在自己改結(jié)構(gòu)做實驗的時候如果打算用分組卷積來減少參數(shù)量一定要規(guī)劃好哪一層做融合否則精度會掉得很厲害。4. 訓練側(cè)的調(diào)參經(jīng)驗與論文超參對比4.1 論文里的超參數(shù)還原論文給的訓練配置在今天的代碼里需要做一些調(diào)整才能復現(xiàn)。我把關鍵參數(shù)整理如下超參數(shù)論文取值現(xiàn)在復現(xiàn)的建議優(yōu)化器SGDSGD暫時不用Adam動量0.90.9權(quán)重衰減0.00050.0005batch size128128顯存不夠降到64初始學習率0.010.01學習率衰減驗證誤差不降時除以10ReduceLROnPlateau訓練輪數(shù)約90輪視數(shù)據(jù)集而定dropout0.50.5這里有個細節(jié)論文里用了一個手動觸發(fā)的學習率衰減策略當驗證集錯誤率不再下降時把學習率除以10。在PyTorch里我一般用ReduceLROnPlateau來實現(xiàn)optimizer torch.optim.SGD(model.parameters(), lr0.01, momentum0.9, weight_decay5e-4) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.1, patience3, verboseTrue)每輪訓練結(jié)束后調(diào)用scheduler.step(val_loss)它就會自動判斷要不要降學習率。patience3表示驗證損失連續(xù)3輪不下降就降一次。另外論文里提到LRN層局部響應歸一化用在conv1和conv2之后但我在實際復現(xiàn)時基本都省略掉了。原因是LRN對精度的提升很小論文里說貢獻了1.4%的top-1而計算開銷不小后來的BatchNorm在這方面的效果全面碾壓LRN。如果你想忠實還原加兩行nn.LocalResponseNorm(size5, alpha1e-4, beta0.75, k2.0)也行但沒必要。4.2 數(shù)據(jù)增強與歸一化的實際做法論文的數(shù)據(jù)增強方案有兩塊一是隨機裁剪加水平翻轉(zhuǎn)二是PCA顏色抖動。第二塊在今天的實現(xiàn)里基本被顏色抖動ColorJitter替代了效果差不多但實現(xiàn)簡單得多。from torchvision import transforms train_transform transforms.Compose([ transforms.Resize(256), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) val_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])驗證集用CenterCrop而不是RandomCrop這是為了評估的穩(wěn)定性和可復現(xiàn)性。歸一化用的這組均值方差是ImageNet的統(tǒng)計值如果你換了自己的數(shù)據(jù)集最好重新統(tǒng)計一下尤其是醫(yī)學影像或者工業(yè)質(zhì)檢這類和自然圖像分布差異很大的場景直接套用ImageNet的歸一化參數(shù)會讓訓練初期很不穩(wěn)定。4.3 學習率調(diào)度與優(yōu)化器選擇我知道你可能想問為什么不用Adam。我的答案很直接在AlexNet這種結(jié)構(gòu)上SGD加動量訓練出來的模型泛化性通常更好尤其是配合dropout和權(quán)重衰減的時候。Adam收斂快但容易在小數(shù)據(jù)集上過擬合而且對權(quán)重衰減的處理方式和SGD不一樣你需要重新調(diào)超參。如果你只是想快速驗證結(jié)構(gòu)能不能跑通用Adam也無妨但如果是要認真復現(xiàn)一個結(jié)果老老實實用SGD。還有一個高頻問題batch size能不能改。能改但學習率要跟著調(diào)。經(jīng)驗法則是batch size翻倍學習率也翻倍左右線性縮放規(guī)則。比如你把batch size從128降到64學習率可以從0.01降到0.005。這是粗略估計實際還要看你的數(shù)據(jù)集大小和訓練輪數(shù)。如果你的數(shù)據(jù)集很小比如只有幾千張圖我建議直接加載預訓練權(quán)重然后微調(diào)而不是從頭訓。凍結(jié)前面幾層卷積、只訓練后面的分類器是個常用的做法for name, param in model.named_parameters(): if features in name: param.requires_grad False這樣訓練速度快很多顯存占用也小幾百張圖就能拿到不錯的效果。5. 踩坑實錄那些報錯信息和它們的解法5.1 形狀不匹配的排查套路最常見的報錯就是RuntimeError: mat1 and mat2 shapes cannot be multiplied出現(xiàn)在全連接層。根因八成是卷積輸出展平后的維度和nn.Linear的輸入維度對不上。排查順序是這樣的先確認輸入圖片尺寸對不對再逐層打印卷積輸出形狀最后反推全連接層應該填多少。我在3.3節(jié)給的那個逐層打印方法在這里特別好用比盯著報錯信息瞎猜高效得多。還有個隱蔽的坑是AdaptiveAvgPool2d。如果你從torchvision的AlexNet源碼里抄了一部分可能會帶上這個層它會讓特征圖自動縮放到指定尺寸從而掩蓋輸入尺寸不匹配的問題。用起來方便但你就不清楚真實的特征圖尺寸是多少了。學習階段我建議先不要用它等你能把固定尺寸的流程跑通了再說。5.2 分組卷積相關的報錯ValueError: in_channels must be divisible by groups這個報錯就是因為通道數(shù)不能被整除。改通道數(shù)的時候記住這個約束。另一個容易忽略的點是權(quán)重加載。如果你手寫的分組版本想去加載torchvision的預訓練權(quán)重會直接失敗因為torchvision版本沒有分組對應的參數(shù)張量形狀完全不一樣。這種情況要么放棄加載權(quán)重要么手動寫一個映射腳本把官方權(quán)重的通道切分成兩半填進去工作量不小。5.3 顯存爆炸與batch size選擇AlexNet那6000萬參數(shù)里有9600萬是發(fā)生在全連接層的中間激活值上9216×128這個中間張量如果batch size是128單個樣本就是9216個float乘以4096個輸出顯存占用很容易爆。如果你在4G或6G顯存的卡上跑把batch size降到32甚至16是必須的。另外可以開啟混合精度訓練來省顯存from torch.cuda.amp import autocast, GradScaler scaler GradScaler() with autocast(): output model(images) loss criterion(output, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()混合精度大概能把顯存占用降到原來的六成左右訓練速度也能提升代價是數(shù)值精度略有損失對分類任務來說基本無感。5.4 訓練不收斂的幾種典型癥狀訓練不動、loss一直卡在高位通常有幾個原因。學習率太大導致loss震蕩甚至發(fā)散這時候把學習率降一個數(shù)量級試試權(quán)重初始化不合理導致梯度爆炸或消失尤其是如果忘了做初始化、用了PyTorch默認的Kaiming初始化而不是論文里的0.01標準差高斯前幾輪的loss曲線會很難看數(shù)據(jù)沒有做歸一化輸入值域在0-255之間梯度會非常大收斂幾乎不可能還有一種情況是標簽出錯比如分類任務的標簽從1開始而不是從0開始CrossEntropyLoss會直接報越界或者給出錯誤結(jié)果。我把常見問題和排查方向整理成了一張表癥狀可能原因排查動作loss不下降學習率過大降一個數(shù)量級重試loss變NaN梯度爆炸加梯度裁剪、檢查學習率訓練準但驗證差過擬合加大dropout、加數(shù)據(jù)增強顯存不足batch太大降batch、開混合精度形狀報錯輸入尺寸或通道不匹配逐層打印張量形狀加載權(quán)重失敗結(jié)構(gòu)不一致分組、通道數(shù)對比參數(shù)形狀逐層檢查注意遇到報錯不要第一反應就是改代碼先把報錯信息完整讀兩遍。PyTorch的報錯信息其實寫得很詳細往往直接點出了是哪一層、哪個維度出了問題只是很多人看到紅色就慌了。5.5 一個容易被忽視的評測細節(jié)訓練完最后一輪的時候一定要用model.eval()切換到評估模式同時用torch.no_grad()包住推理過程。前者讓dropout和BatchNorm進入評估行為后者禁止梯度計算從而省顯存。很多人忘了model.eval()結(jié)果驗證精度比實際低了十幾個點還以為模型沒訓好排查半天才發(fā)現(xiàn)是這個開關沒切。這個坑我踩過不止一次現(xiàn)在寫訓練腳本第一件事就是把評估模式的代碼塊寫好訓練循環(huán)里復制粘貼。6. 從AlexNet延伸出去的一些想法6.1 LRN為什么被淘汰了LRN這個層在AlexNet里做了局部響應歸一化思路是讓相鄰通道之間形成側(cè)向抑制模擬生物神經(jīng)元的競爭機制。但后來的實踐發(fā)現(xiàn)它的收益有限計算開銷卻不小而且BatchNorm在歸一化這件事上做得更徹底、更穩(wěn)定。BatchNorm是在一個batch內(nèi)對每個通道做標準化直接解決了內(nèi)部協(xié)變量偏移問題效果遠好于LRN。現(xiàn)在你看任何一個現(xiàn)代網(wǎng)絡都找不到LRN的影子了。這個案例說明一個問題不是所有論文里的設計都值得保留理解它存在的原因和它被替代的原因比記住它的公式更重要。6.2 結(jié)構(gòu)演進的一條線索從AlexNet往后看網(wǎng)絡結(jié)構(gòu)演進有一條很清晰的線索減少全連接層的參數(shù)、增加卷積層的深度和復雜度、引入跨層連接。VGG把卷積核統(tǒng)一成3×3并堆到19層參數(shù)依然龐大ResNet用殘差連接把網(wǎng)絡推到上百層同時用全局平均池化干掉了大部分全連接參數(shù)到了MobileNet這類輕量網(wǎng)絡干脆用深度可分離卷積把參數(shù)量壓到極致。而YOLO這類檢測網(wǎng)絡backbone的設計思路其實就是在分類網(wǎng)絡的基礎上做剪裁和改造AlexNet的那套卷積加池化的堆疊方式,在YOLO的早期版本里還能看到影子。理解這條線索的好處是你學新網(wǎng)絡的時候不會覺得是全新的東西??吹結(jié)OLOv11的網(wǎng)絡結(jié)構(gòu)你能認出哪些部分是繼承自經(jīng)典分類網(wǎng)絡的backbone思想哪些部分是針對檢測任務做的專門設計。這種遷移能力靠死記硬背是練不出來的一定要從最基礎的結(jié)構(gòu)開始一行行地推。6.3 我個人的一些實踐體會我剛開始學深度學習那陣子也犯過和開頭那個朋友一樣的錯誤覺得老網(wǎng)絡過時了不值得看。后來做項目的時候遇到一個網(wǎng)絡結(jié)構(gòu)改不動的情況回頭把AlexNet重新推了一遍才發(fā)現(xiàn)問題在于自己一直沒建立起張量形狀在層間怎么流動的直覺。這個直覺建立起來之后改任何網(wǎng)絡都不慌了不管是插入一個注意力模塊還是替換backbone都能快速定位到需要改哪一層的參數(shù)。如果你正在學PyTorch我的建議是把AlexNet當做一個解剖標本自己從頭到尾寫一遍、跑一遍、改一遍。改什么都可以把卷積核尺寸改小、把分組數(shù)量改成4、把全連接層換成全局平均池化看看輸出形狀怎么變、精度怎么變。這種折騰帶來的理解深度是看十篇教程也換不來的。等你把這個網(wǎng)絡吃透了再去看那些復雜的結(jié)構(gòu)會發(fā)現(xiàn)它們不過是同一套基本操作的組合和變體沒你想的那么可怕。