:從CNN原理到PyTorch實現(xiàn)詳解)
1. 項目概述一次從理論到實踐的CNN圖像分類實戰(zhàn)李宏毅老師的機(jī)器學(xué)習(xí)課程在圈內(nèi)一直以理論扎實、作業(yè)硬核著稱。2023年的HW03作業(yè)聚焦于卷積神經(jīng)網(wǎng)絡(luò)CNN進(jìn)行圖像分類這不僅是課程的一個關(guān)鍵里程碑也是很多同學(xué)從“看懂公式”到“跑通模型”的第一次深度實戰(zhàn)。我花了大約一周的時間從理解題目、數(shù)據(jù)預(yù)處理、模型搭建、調(diào)參優(yōu)化到最終提交整個過程踩了不少坑也積累了一些心得。這篇內(nèi)容就來詳細(xì)拆解HW03的每一個環(huán)節(jié)分享可復(fù)現(xiàn)的代碼和那些在官方文檔里找不到的“實戰(zhàn)經(jīng)驗”。無論你是正在苦戰(zhàn)這份作業(yè)的同學(xué)還是想通過一個具體項目來鞏固CNN和PyTorch的初學(xué)者相信都能從中找到直接的參考和啟發(fā)。這份作業(yè)的核心任務(wù)是利用CNN對食物圖片進(jìn)行分類共11個類別。它模擬了一個真實的機(jī)器學(xué)習(xí)項目流程數(shù)據(jù)加載與探索、模型架構(gòu)設(shè)計、訓(xùn)練策略制定、結(jié)果分析與提交。難點不在于模型的復(fù)雜性通常一個幾層的CNN即可而在于如何正確地處理數(shù)據(jù)、組織代碼、調(diào)試模型以及理解訓(xùn)練過程中的各種現(xiàn)象。接下來我會按照實際操作的順序逐一拆解。2. 作業(yè)整體思路與核心設(shè)計解析2.1 任務(wù)目標(biāo)與環(huán)境搭建作業(yè)的目標(biāo)非常明確給定一個食物圖片數(shù)據(jù)集訓(xùn)練集、驗證集和測試集你需要構(gòu)建一個CNN模型在驗證集上獲得盡可能高的準(zhǔn)確率并對測試集進(jìn)行預(yù)測生成提交文件。數(shù)據(jù)集的圖片尺寸統(tǒng)一但可能存在光照、角度、背景等差異這正是一個典型的圖像分類任務(wù)。首先環(huán)境是基石。我強(qiáng)烈建議使用Python 3.8和PyTorch 1.12的環(huán)境。Anaconda虛擬環(huán)境管理是首選它能避免包版本沖突。除了PyTorch你還需要安裝torchvision用于圖像處理和預(yù)訓(xùn)練模型、pandas、numpy、matplotlib和tqdm用于進(jìn)度條。使用CUDA版本的PyTorch可以極大加速訓(xùn)練過程只要你的顯卡支持。注意在Windows系統(tǒng)上安裝PyTorch的CUDA版本時務(wù)必通過PyTorch官網(wǎng)提供的命令進(jìn)行安裝明確指定CUDA版本如cu117對應(yīng)CUDA 11.7并提前在系統(tǒng)中安裝對應(yīng)版本的NVIDIA驅(qū)動和CUDA Toolkit。版本不匹配是新手最常見的環(huán)境報錯原因。2.2 數(shù)據(jù)加載與預(yù)處理策略數(shù)據(jù)是模型的“糧食”處理得好壞直接決定模型的上限。作業(yè)提供的數(shù)據(jù)通常以文件夾形式組織每個子文件夾代表一個類別里面存放著該類別的圖片。torchvision.datasets.ImageFolder是處理這種結(jié)構(gòu)的神器它能自動根據(jù)文件夾結(jié)構(gòu)生成標(biāo)簽。預(yù)處理transforms是關(guān)鍵一步。我們需要將圖片轉(zhuǎn)換為模型能處理的張量Tensor并進(jìn)行歸一化。常見的操作組合如下from torchvision import transforms # 訓(xùn)練集的預(yù)處理通常包含數(shù)據(jù)增強(qiáng) train_transform transforms.Compose([ transforms.RandomHorizontalFlip(p0.5), # 隨機(jī)水平翻轉(zhuǎn) transforms.RandomRotation(degrees15), # 隨機(jī)旋轉(zhuǎn) transforms.ColorJitter(brightness0.2, contrast0.2), # 顏色抖動 transforms.Resize((128, 128)), # 調(diào)整大小根據(jù)你的輸入尺寸定 transforms.ToTensor(), # 轉(zhuǎn)換為Tensor并歸一化到[0,1] transforms.Normalize(mean[0.485, 0.456, 0.406], # ImageNet的均值 std[0.229, 0.224, 0.225]) # ImageNet的標(biāo)準(zhǔn)差 ]) # 驗證集和測試集的預(yù)處理不進(jìn)行數(shù)據(jù)增強(qiáng)只需Resize和歸一化 test_transform transforms.Compose([ transforms.Resize((128, 128)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])這里有幾個重要的設(shè)計考量數(shù)據(jù)增強(qiáng)Data Augmentation僅對訓(xùn)練集使用。通過隨機(jī)翻轉(zhuǎn)、旋轉(zhuǎn)、顏色調(diào)整等可以人為增加數(shù)據(jù)的多樣性相當(dāng)于讓模型看到了更多可能的圖片變體能有效防止過擬合提升模型的泛化能力。這是提升小數(shù)據(jù)集性能的必備技巧。歸一化參數(shù)為什么使用ImageNet的均值和標(biāo)準(zhǔn)差因為許多預(yù)訓(xùn)練模型是在ImageNet上訓(xùn)練的其卷積核已經(jīng)適應(yīng)了這種數(shù)據(jù)分布。即使我們不使用預(yù)訓(xùn)練模型使用這個通用的統(tǒng)計值也是一個不錯的起點它有助于穩(wěn)定訓(xùn)練過程。如果你的數(shù)據(jù)集與ImageNet差異極大可以計算自己數(shù)據(jù)集的均值和標(biāo)準(zhǔn)差但作業(yè)數(shù)據(jù)通常接近自然圖像直接用問題不大。輸入尺寸(128, 128)是一個示例你可以根據(jù)計算資源調(diào)整。更大的尺寸如224 256可能帶來更好的性能但也會顯著增加顯存消耗和訓(xùn)練時間。需要在性能和效率間權(quán)衡。使用ImageFolder和DataLoader加載數(shù)據(jù)from torchvision import datasets from torch.utils.data import DataLoader train_dataset datasets.ImageFolder(root./data/train, transformtrain_transform) valid_dataset datasets.ImageFolder(root./data/valid, transformtest_transform) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue, num_workers4, pin_memoryTrue) valid_loader DataLoader(valid_dataset, batch_size64, shuffleFalse, num_workers4, pin_memoryTrue)batch_size一次訓(xùn)練所選取的樣本數(shù)。太大可能導(dǎo)致顯存不足太小則梯度更新噪聲大、訓(xùn)練慢。64或32是常見的起點。shuffle訓(xùn)練集必須打亂防止模型學(xué)習(xí)到數(shù)據(jù)順序。num_workers用于數(shù)據(jù)加載的子進(jìn)程數(shù)可以加快數(shù)據(jù)讀取速度。在Windows上有時設(shè)為0可避免問題。pin_memory當(dāng)使用GPU時設(shè)置為True可以將數(shù)據(jù)鎖頁內(nèi)存加速數(shù)據(jù)從CPU到GPU的傳輸。3. CNN模型架構(gòu)設(shè)計與實現(xiàn)細(xì)節(jié)3.1 從零搭建一個基礎(chǔ)CNN模型對于HW03我們完全可以自己搭建一個輕量級的CNN。一個典型的模式是多個“卷積層 - 激活層 - 池化層”的堆疊最后接全連接層進(jìn)行分類。下面是一個示例模型import torch.nn as nn import torch.nn.functional as F class MyCNN(nn.Module): def __init__(self, num_classes11): super(MyCNN, self).__init__() # 卷積塊1: 輸入3通道輸出16通道 self.conv1 nn.Conv2d(3, 16, kernel_size3, padding1) # padding1保持尺寸 self.bn1 nn.BatchNorm2d(16) # 批歸一化加速收斂 self.pool nn.MaxPool2d(2, 2) # 2x2最大池化尺寸減半 # 卷積塊2 self.conv2 nn.Conv2d(16, 32, kernel_size3, padding1) self.bn2 nn.BatchNorm2d(32) # 卷積塊3 self.conv3 nn.Conv2d(32, 64, kernel_size3, padding1) self.bn3 nn.BatchNorm2d(64) # 全連接層 # 假設(shè)輸入圖片是128x128經(jīng)過3次pooling后是16x16 (128 - 64 - 32 - 16) self.fc1 nn.Linear(64 * 16 * 16, 512) # 需要根據(jù)實際尺寸計算 self.dropout nn.Dropout(p0.5) # Dropout防止過擬合 self.fc2 nn.Linear(512, num_classes) def forward(self, x): x self.pool(F.relu(self.bn1(self.conv1(x)))) x self.pool(F.relu(self.bn2(self.conv2(x)))) x self.pool(F.relu(self.bn3(self.conv3(x)))) # 將特征圖展平成一維向量 x x.view(x.size(0), -1) x F.relu(self.fc1(x)) x self.dropout(x) x self.fc2(x) return x關(guān)鍵設(shè)計點解析卷積核與通道數(shù)卷積核大小常用3x3這是VGG網(wǎng)絡(luò)推廣的高效選擇。通道數(shù)如16, 32, 64逐層增加讓網(wǎng)絡(luò)能夠?qū)W習(xí)到從低級邊緣、紋理到高級物體部件的越來越復(fù)雜的特征。Paddingpadding1配合kernel_size3可以保持特征圖的空間尺寸不變output_size input_size這樣在計算經(jīng)過池化后的尺寸時更簡單。批歸一化BatchNorm這是現(xiàn)代深度網(wǎng)絡(luò)的標(biāo)配。它對每一批batch的數(shù)據(jù)進(jìn)行歸一化減均值、除標(biāo)準(zhǔn)差使得中間層的輸出分布更加穩(wěn)定。這帶來了三大好處允許使用更大的學(xué)習(xí)率、減少對參數(shù)初始化的依賴、有一定的正則化效果。通常放在卷積層之后、激活函數(shù)之前。激活函數(shù)ReLURectified Linear Unit是最常用的因為它計算簡單且能緩解梯度消失問題。池化層最大池化MaxPooling用于下采樣逐步減少特征圖尺寸增加感受野同時提供一定的平移不變性。2x2池化是最常見的。全連接層與Dropout將卷積學(xué)習(xí)到的空間特征映射到樣本標(biāo)記空間。在第一個全連接層后加入Dropout隨機(jī)“丟棄”一部分神經(jīng)元置零是防止過擬合的強(qiáng)大正則化手段。p0.5是一個常用值。展平操作在進(jìn)入全連接層前必須將多維的特征圖“拉平”成一維向量。x.view(x.size(0), -1)中的-1表示自動計算該維度的大小。實操心得計算全連接層輸入維度是新手最容易出錯的地方。一個可靠的方法是先寫一個print(x.shape)在view操作之前運(yùn)行一次前向傳播用一個小批量數(shù)據(jù)查看展平前的x的形狀例如可能是[batch_size, 64, 16, 16]那么展平后的維度就是64*16*1616384。將這個值填入nn.Linear的第一個參數(shù)。3.2 使用預(yù)訓(xùn)練模型進(jìn)行遷移學(xué)習(xí)如果你的目標(biāo)是獲得更高的分?jǐn)?shù)遷移學(xué)習(xí)幾乎是必選項。其思想是利用在超大規(guī)模數(shù)據(jù)集如ImageNet上預(yù)訓(xùn)練好的模型權(quán)重作為我們模型的起點然后針對我們的食物分類任務(wù)進(jìn)行微調(diào)Fine-tuning。torchvision.models提供了豐富的預(yù)訓(xùn)練模型如ResNet, VGG, EfficientNet等。以ResNet18為例import torchvision.models as models class PretrainedModel(nn.Module): def __init__(self, num_classes11): super(PretrainedModel, self).__init__() # 加載預(yù)訓(xùn)練的ResNet18并獲取其特征提取部分去掉最后的全連接層 backbone models.resnet18(pretrainedTrue) # 凍結(jié)所有卷積層的參數(shù)在初始階段不更新它們 for param in backbone.parameters(): param.requires_grad False # 替換最后的全連接層以適應(yīng)我們的11分類任務(wù) num_features backbone.fc.in_features backbone.fc nn.Linear(num_features, num_classes) self.model backbone def forward(self, x): return self.model(x)微調(diào)策略詳解凍結(jié)Freeze與解凍Unfreeze一開始我們凍結(jié)了預(yù)訓(xùn)練模型的所有層requires_gradFalse這意味著在訓(xùn)練初期只有我們新替換的全連接層fc的參數(shù)會被更新。這是為了讓模型先適應(yīng)新任務(wù)的新“頭部”。訓(xùn)練幾個epoch后可以解凍所有層或部分深層卷積層用較小的學(xué)習(xí)率進(jìn)行整體微調(diào)。這種分階段訓(xùn)練策略非常有效。學(xué)習(xí)率設(shè)置對于新添加的層fc可以使用一個相對較大的學(xué)習(xí)率如0.01對于預(yù)訓(xùn)練層如果解凍了應(yīng)該使用一個非常小的學(xué)習(xí)率如0.001的十分之一以免破壞已經(jīng)學(xué)到的寶貴特征。模型選擇ResNet18/34比較輕量訓(xùn)練快ResNet50/101性能更強(qiáng)但更耗資源。EfficientNet系列在精度和效率上平衡得更好。根據(jù)你的硬件和時間選擇。4. 訓(xùn)練流程的完整實現(xiàn)與調(diào)參技巧4.1 訓(xùn)練循環(huán)的構(gòu)建有了模型和數(shù)據(jù)接下來就是編寫訓(xùn)練循環(huán)。這是PyTorch訓(xùn)練的標(biāo)準(zhǔn)模板但細(xì)節(jié)決定成敗。import torch import torch.optim as optim from tqdm import tqdm device torch.device(cuda if torch.cuda.is_available() else cpu) model MyCNN().to(device) # 或 PretrainedModel().to(device) criterion nn.CrossEntropyLoss() # 多分類任務(wù)使用交叉熵?fù)p失 optimizer optim.Adam(model.parameters(), lr0.001) # Adam優(yōu)化器 scheduler optim.lr_scheduler.StepLR(optimizer, step_size5, gamma0.1) # 學(xué)習(xí)率調(diào)度器 num_epochs 30 best_acc 0.0 for epoch in range(num_epochs): # 訓(xùn)練階段 model.train() train_loss 0.0 train_correct 0 train_total 0 # 使用tqdm包裝數(shù)據(jù)加載器顯示進(jìn)度條 pbar tqdm(train_loader, descfEpoch {epoch1}/{num_epochs} [Train]) for images, labels in pbar: images, labels images.to(device), labels.to(device) # 前向傳播 outputs model(images) loss criterion(outputs, labels) # 反向傳播與優(yōu)化 optimizer.zero_grad() # 清空過往梯度至關(guān)重要 loss.backward() # 反向傳播計算梯度 optimizer.step() # 更新參數(shù) # 統(tǒng)計 train_loss loss.item() * images.size(0) _, predicted torch.max(outputs.data, 1) train_total labels.size(0) train_correct (predicted labels).sum().item() # 更新進(jìn)度條信息 pbar.set_postfix({Loss: loss.item()}) train_loss train_loss / len(train_dataset) train_acc 100.0 * train_correct / train_total # 驗證階段 model.eval() valid_loss 0.0 valid_correct 0 valid_total 0 with torch.no_grad(): # 關(guān)閉梯度計算節(jié)省內(nèi)存和計算 for images, labels in valid_loader: images, labels images.to(device), labels.to(device) outputs model(images) loss criterion(outputs, labels) valid_loss loss.item() * images.size(0) _, predicted torch.max(outputs.data, 1) valid_total labels.size(0) valid_correct (predicted labels).sum().item() valid_loss valid_loss / len(valid_dataset) valid_acc 100.0 * valid_correct / valid_total # 學(xué)習(xí)率調(diào)度 scheduler.step() # 打印日志 print(fEpoch [{epoch1}/{num_epochs}], fTrain Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%, fValid Loss: {valid_loss:.4f}, Valid Acc: {valid_acc:.2f}%) # 保存最佳模型 if valid_acc best_acc: best_acc valid_acc torch.save(model.state_dict(), best_model.pth) print(f - Best model saved with acc: {best_acc:.2f}%)核心環(huán)節(jié)拆解.train()和.eval()模式model.train()會啟用Dropout和BatchNorm的訓(xùn)練行為如用當(dāng)前batch的統(tǒng)計量進(jìn)行歸一化。model.eval()則會關(guān)閉這些行為使用訓(xùn)練階段累積的移動平均統(tǒng)計量進(jìn)行歸一化這對驗證和測試的一致性至關(guān)重要。optimizer.zero_grad()PyTorch的梯度是累加的。如果在每次backward()前不清零梯度梯度會不斷累積導(dǎo)致更新方向錯誤。這是最常見的錯誤之一。with torch.no_grad()在驗證和測試時我們不需要計算梯度。這個上下文管理器可以禁用自動求導(dǎo)大幅減少內(nèi)存消耗并加速計算。損失和準(zhǔn)確率計算損失是每個樣本損失的平均。注意在累加總損失時我們乘以了images.size(0)即當(dāng)前batch的大小最后再除以數(shù)據(jù)集總大小這是為了得到整個epoch的平均損失即使最后一個batch可能不滿。模型保存我們保存驗證集上性能最好的模型state_dict而不是最后一個epoch的模型。這可以防止模型在訓(xùn)練后期過擬合導(dǎo)致驗證集性能下降。4.2 超參數(shù)調(diào)優(yōu)與訓(xùn)練監(jiān)控訓(xùn)練深度學(xué)習(xí)模型很大程度上是在調(diào)參。以下是一些核心超參數(shù)和經(jīng)驗值超參數(shù)常見范圍/選擇作用與調(diào)參心得學(xué)習(xí)率 (lr)1e-4 到 1e-2最重要的參數(shù)。太大導(dǎo)致震蕩不收斂太小則收斂慢。Adam優(yōu)化器下從3e-4開始嘗試是個好選擇。使用學(xué)習(xí)率調(diào)度器如StepLR, ReduceLROnPlateau在訓(xùn)練中后期降低學(xué)習(xí)率有助于模型收斂到更優(yōu)的局部最優(yōu)點。批大小 (batch_size)32, 64, 128受限于GPU顯存。更大的batch_size使梯度估計更準(zhǔn)確訓(xùn)練更穩(wěn)定但可能降低泛化能力。通常設(shè)為能占滿顯存的最大2的冪次。優(yōu)化器 (Optimizer)Adam, AdamW, SGDAdam自適應(yīng)學(xué)習(xí)率對初始學(xué)習(xí)率不敏感通常作為默認(rèn)選擇收斂快。SGD with momentum配合學(xué)習(xí)率衰減最終性能可能更好但需要更多調(diào)參。AdamW解決了Adam的權(quán)重衰減問題現(xiàn)在被認(rèn)為是更優(yōu)的選擇尤其是配合預(yù)訓(xùn)練模型。權(quán)重衰減 (Weight Decay)1e-4, 1e-5一種L2正則化防止模型權(quán)重過大緩解過擬合。對于Adam使用AdamW并設(shè)置weight_decay參數(shù)。Epoch數(shù)20-100觀察訓(xùn)練/驗證損失曲線。當(dāng)驗證損失連續(xù)多個epoch不再下降甚至上升時應(yīng)提前停止Early Stopping防止過擬合。訓(xùn)練監(jiān)控技巧繪制損失/準(zhǔn)確率曲線這是診斷訓(xùn)練過程最直觀的工具。理想情況是訓(xùn)練損失穩(wěn)步下降驗證損失先降后升過擬合拐點。如果訓(xùn)練損失都不降可能是學(xué)習(xí)率太小、模型能力不足或數(shù)據(jù)有問題。使用TensorBoard或Weights Biases這些工具可以實時可視化損失、準(zhǔn)確率、權(quán)重分布、梯度直方圖等對于復(fù)雜調(diào)參和實驗管理非常有幫助。5. 測試集預(yù)測、結(jié)果分析與常見問題排查5.1 生成提交文件訓(xùn)練出最佳模型后我們需要在測試集上運(yùn)行生成符合Kaggle或課程平臺要求的提交文件通常是CSV格式。import pandas as pd from PIL import Image import os # 加載最佳模型 model.load_state_dict(torch.load(best_model.pth)) model.eval() test_data_path ./data/test submission [] # 注意測試集可能沒有標(biāo)簽需要按文件名順序讀取 test_image_names sorted(os.listdir(test_data_path)) # 確保順序一致 with torch.no_grad(): for img_name in tqdm(test_image_names, descPredicting): img_path os.path.join(test_data_path, img_name) # 用PIL打開圖片并應(yīng)用與驗證集相同的預(yù)處理 image Image.open(img_path).convert(RGB) image test_transform(image).unsqueeze(0).to(device) # 增加batch維度 output model(image) _, predicted torch.max(output, 1) # 假設(shè)文件名就是ID或者從文件名中提取ID submission.append([img_name.split(.)[0], predicted.item()]) # 保存ID和預(yù)測類別 # 創(chuàng)建DataFrame并保存為CSV df pd.DataFrame(submission, columns[Id, Category]) df.to_csv(submission.csv, indexFalse) print(Submission file saved to submission.csv)重要提示測試集的預(yù)處理必須與驗證集完全一致相同的Resize尺寸、相同的歸一化均值標(biāo)準(zhǔn)差。任何不一致都會導(dǎo)致模型性能的不可預(yù)測下降。5.2 結(jié)果分析與模型診斷提交后你會得到一個在測試集或公開驗證集上的分?jǐn)?shù)。如果分?jǐn)?shù)不理想如何排查過擬合Overfitting訓(xùn)練準(zhǔn)確率遠(yuǎn)高于驗證準(zhǔn)確率。對策增加數(shù)據(jù)增強(qiáng)的強(qiáng)度加大Dropout比率添加更多的正則化如權(quán)重衰減使用更簡單的模型收集更多數(shù)據(jù)。欠擬合Underfitting訓(xùn)練和驗證準(zhǔn)確率都很低。對策增加模型復(fù)雜度更多層、更多通道減少正則化延長訓(xùn)練時間檢查數(shù)據(jù)預(yù)處理是否有誤如歸一化參數(shù)錯了嘗試使用預(yù)訓(xùn)練模型。訓(xùn)練不穩(wěn)定Loss震蕩或NaN檢查學(xué)習(xí)率學(xué)習(xí)率可能太高嘗試降低一個數(shù)量級。檢查數(shù)據(jù)數(shù)據(jù)中是否有損壞的圖片或異常的標(biāo)簽歸一化后數(shù)據(jù)值是否在合理范圍如-3到3之間檢查梯度可以添加梯度裁剪torch.nn.utils.clip_grad_norm_防止梯度爆炸。驗證集性能停滯嘗試學(xué)習(xí)率調(diào)度使用ReduceLROnPlateau在驗證損失停滯時自動降低學(xué)習(xí)率。解凍預(yù)訓(xùn)練層如果使用遷移學(xué)習(xí)且還凍結(jié)著嘗試解凍后面幾層進(jìn)行微調(diào)。集成Ensemble訓(xùn)練多個不同初始化或不同結(jié)構(gòu)的模型對它們的預(yù)測結(jié)果進(jìn)行平均或投票這是提升分?jǐn)?shù)的“大殺器”但會增加計算成本。5.3 進(jìn)階優(yōu)化思路如果基礎(chǔ)模型已經(jīng)跑通想沖擊更高分?jǐn)?shù)可以嘗試以下方向更強(qiáng)大的數(shù)據(jù)增強(qiáng)除了基本的翻轉(zhuǎn)旋轉(zhuǎn)可以嘗試RandAugment或AutoAugment這類自動搜索或預(yù)設(shè)的增強(qiáng)策略包它們組合了多種增強(qiáng)方式效果顯著。模型集成訓(xùn)練多個模型如ResNet18, ResNet50, EfficientNet-B0在預(yù)測時取它們輸出的概率平均能有效提升魯棒性和準(zhǔn)確率。測試時增強(qiáng)Test Time Augmentation, TTA對一張測試圖片進(jìn)行多種增強(qiáng)如原圖、水平翻轉(zhuǎn)、垂直翻轉(zhuǎn)等分別預(yù)測然后對結(jié)果取平均。這相當(dāng)于給了模型多次“觀察”的機(jī)會通常能提升一點性能。標(biāo)簽平滑Label Smoothing在計算損失時不直接使用硬標(biāo)簽如[0,0,1,0]而是使用平滑后的軟標(biāo)簽如[0.01, 0.01, 0.96, 0.01]這可以減輕模型對標(biāo)簽的過度自信有正則化效果可能提升泛化能力。混合精度訓(xùn)練使用torch.cuda.amp進(jìn)行自動混合精度訓(xùn)練可以在幾乎不損失精度的情況下大幅減少顯存占用從而允許使用更大的batch_size或更大的模型同時還能加速訓(xùn)練。完成HW03的整個過程遠(yuǎn)比單純實現(xiàn)一個CNN類要豐富。它涵蓋了數(shù)據(jù)管道構(gòu)建、模型設(shè)計、訓(xùn)練調(diào)試、結(jié)果分析這一完整機(jī)器學(xué)習(xí)閉環(huán)。最大的收獲不是調(diào)出了一個高分的模型而是學(xué)會了如何系統(tǒng)地診斷和解決訓(xùn)練中遇到的各種問題。當(dāng)你看到自己的模型在驗證集上的準(zhǔn)確率一點點爬升最終生成那個可以提交的CSV文件時那種親手搭建的系統(tǒng)跑通了的成就感是只看理論無法比擬的。希望這份詳細(xì)的解析和代碼能幫你更順暢地完成這次實戰(zhàn)少走一些我走過的彎路。