:從目錄結(jié)構(gòu)到數(shù)據(jù)加載的避坑指南)
1. 從文件夾結(jié)構(gòu)到訓(xùn)練數(shù)據(jù)ImageFolder 到底解決了什么問題搞深度學(xué)習(xí)的人都有一個共識模型結(jié)構(gòu)再花哨如果數(shù)據(jù)管道沒搭好訓(xùn)練過程就是一場災(zāi)難。我見過太多人把大量時間花在調(diào)網(wǎng)絡(luò)層數(shù)、換損失函數(shù)上結(jié)果卡在第一步——數(shù)據(jù)加載。尤其是做圖像分類任務(wù)時手頭一堆圖片散落在不同文件夾里怎么高效地喂給模型這本身就是個值得認(rèn)真對待的工程問題。torchvision.datasets.ImageFolder就是在這個環(huán)節(jié)出場的。它的核心價值可以用一句話概括把符合特定目錄結(jié)構(gòu)的圖像文件夾自動映射成圖像, 類別標(biāo)簽的數(shù)據(jù)集對象。你不需要寫任何遍歷文件、解析路徑、分配標(biāo)簽的代碼只要文件夾組織對了它就能直接工作。這個類適合誰用如果你正在做圖像分類任務(wù)數(shù)據(jù)來源是本地磁盤上的圖片文件類別信息可以通過文件夾名稱區(qū)分那 ImageFolder 基本就是你的第一選擇。它特別適合以下場景自己爬取或整理的圖片數(shù)據(jù)集、從公開數(shù)據(jù)集解壓出來的標(biāo)準(zhǔn)分類目錄、以及需要快速搭建 baseline 的實驗階段。但這里有個前提條件必須說清楚ImageFolder 對目錄結(jié)構(gòu)有嚴(yán)格要求。它期望的格式是這樣的root/ ├── class_a/ │ ├── img_001.jpg │ ├── img_002.jpg │ └── ... ├── class_b/ │ ├── img_001.jpg │ ├── img_002.jpg │ └── ... └── class_c/ ├── img_001.jpg └── ...根目錄下每個子文件夾代表一個類別子文件夾的名字就是類別名稱里面的圖片文件屬于該類。ImageFolder 會自動掃描這個結(jié)構(gòu)按文件夾名的字母順序給類別編號class_to_idx然后為每張圖片分配對應(yīng)的整數(shù)標(biāo)簽。我剛開始用的時候犯過一個低級錯誤把圖片直接放在根目錄下沒有分子文件夾。結(jié)果 ImageFolder 直接報錯說找不到有效類別。后來才明白它的設(shè)計哲學(xué)就是用目錄結(jié)構(gòu)表達(dá)類別信息這個約定必須遵守。還有一個容易忽略的點ImageFolder 返回的標(biāo)簽是整數(shù)索引不是類別名稱字符串。如果你后續(xù)需要把預(yù)測結(jié)果映射回可讀的類別名得自己保存dataset.classes或dataset.class_to_idx。這個細(xì)節(jié)在模型部署和結(jié)果展示時非常關(guān)鍵我后面會詳細(xì)講怎么處理。2. 目錄結(jié)構(gòu)踩坑實錄那些年我遇到過的報錯與排查過程2.1 類別文件夾里混入了非圖片文件這是最常見的問題之一。假設(shè)你的class_a文件夾里除了 jpg 圖片還有一個desktop.ini或者.DS_Store文件macOS 系統(tǒng)自動生成的ImageFolder 默認(rèn)會嘗試用 PIL 打開這些文件然后拋出UnidentifiedImageError。我第一次遇到這個報錯時完全懵了因為從文件管理器看文件夾里明明只有圖片。后來用ls -la才發(fā)現(xiàn)隱藏文件的存在。解決辦法有兩個一是手動清理這些系統(tǒng)文件二是在創(chuàng)建 ImageFolder 時傳入is_valid_file參數(shù)來過濾。import os from torchvision.datasets import ImageFolder def is_image_file(filename): valid_extensions (.jpg, .jpeg, .png, .bmp, .tiff, .webp) return filename.lower().endswith(valid_extensions) dataset ImageFolder( root./data/train, is_valid_fileis_image_file )這個is_valid_file參數(shù)接收一個函數(shù)返回 True 表示該文件是有效圖片。實測下來加上這個過濾之后那些系統(tǒng)生成的隱藏文件就不會再干擾了。2.2 類別文件夾命名帶來的排序陷阱ImageFolder 給類別分配標(biāo)簽的依據(jù)是文件夾名稱的字母順序。注意是字符串排序不是自然數(shù)排序。這意味著如果你有class_1到class_10這樣的文件夾排序結(jié)果會是class_1, class_10, class_2, class_3, ..., class_9而不是你期望的 1, 2, 3, ..., 10。這個坑我在一個多類別項目里踩過當(dāng)時發(fā)現(xiàn)模型訓(xùn)練時標(biāo)簽完全對不上排查了半天才意識到是文件夾命名的問題。建議類別文件夾統(tǒng)一使用零填充命名比如class_01、class_02或者直接用有意義的英文類別名避免純數(shù)字后綴。如果你已經(jīng)有一批數(shù)據(jù)且不想重命名文件夾那就在訓(xùn)練前打印dataset.class_to_idx確認(rèn)映射關(guān)系把這個字典保存下來后續(xù)推理時按同樣的映射解析結(jié)果。2.3 圖片格式損壞導(dǎo)致的間歇性崩潰有些圖片文件從網(wǎng)上下載下來時就是不完整的文件擴(kuò)展名是.jpg但實際內(nèi)容損壞。這種情況下ImageFolder 在__getitem__時才會嘗試打開圖片所以報錯發(fā)生在訓(xùn)練循環(huán)中而不是數(shù)據(jù)集初始化時。這種間歇性崩潰特別難排查因為錯誤發(fā)生在 DataLoader 的多進(jìn)程 worker 里堆棧信息往往不完整。我的做法是寫一個預(yù)檢查腳本在訓(xùn)練前遍歷所有圖片嘗試用 PIL 打開并驗證from PIL import Image import os def validate_images(root_dir): bad_files [] for dirpath, _, filenames in os.walk(root_dir): for fname in filenames: fpath os.path.join(dirpath, fname) try: with Image.open(fpath) as img: img.verify() except Exception as e: bad_files.append((fpath, str(e))) return bad_files bad validate_images(./data/train) for fpath, err in bad: print(f損壞文件: {fpath} - {err})跑一遍這個腳本把所有損壞文件列出來刪掉或修復(fù)能省去后續(xù)大量調(diào)試時間。2.4 子文件夾層級過深或存在嵌套ImageFolder 只掃描根目錄下的一級子文件夾作為類別。如果你的目錄結(jié)構(gòu)是這樣的root/ ├── train/ │ ├── class_a/ │ └── class_b/ └── val/ ├── class_a/ └── class_b/那你的root應(yīng)該設(shè)為./root/train或./root/val而不是./root。如果直接指向./rootImageFolder 會把train和val當(dāng)成兩個類別這顯然不是你想要的結(jié)果。我一般會在代碼里加一個斷言來檢查類別數(shù)量是否符合預(yù)期dataset ImageFolder(root./data/train, transformtrain_transform) print(f檢測到類別數(shù): {len(dataset.classes)}) print(f類別列表: {dataset.classes}) assert len(dataset.classes) EXPECTED_NUM_CLASSES, 類別數(shù)量不符請檢查目錄結(jié)構(gòu)這個簡單的檢查能幫你快速發(fā)現(xiàn)目錄結(jié)構(gòu)問題避免在訓(xùn)練了很久之后才發(fā)現(xiàn)標(biāo)簽全錯了。3. transform 管道的設(shè)計邏輯為什么順序不能隨便調(diào)3.1 transform 在 ImageFolder 中的執(zhí)行時機(jī)ImageFolder 的__getitem__方法做的事情很清晰用 PIL 打開圖片 → 如果有 transform 就應(yīng)用 → 返回處理后的張量和標(biāo)簽。關(guān)鍵點在于transform 是在每次取數(shù)據(jù)時實時執(zhí)行的而不是在初始化時預(yù)處理所有圖片。這個設(shè)計的好處是內(nèi)存占用小你可以處理遠(yuǎn)大于內(nèi)存容量的數(shù)據(jù)集。代價是每個 epoch 都要重復(fù)做同樣的圖像變換CPU 壓力會比較大。實際項目中如果數(shù)據(jù)集不大且 CPU 是瓶頸可以考慮預(yù)先處理好圖片存成張量文件但這屬于優(yōu)化范疇初期用實時 transform 完全夠用。3.2 標(biāo)準(zhǔn) transform 管道的構(gòu)建順序一個典型的訓(xùn)練用 transform 管道長這樣from torchvision import transforms train_transform transforms.Compose([ transforms.Resize(256), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees15), 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]) ])這個順序不是隨便排的每一步都有講究。Resize和RandomCrop放在最前面是因為幾何變換在 PIL 圖像上做比在張量上做更高效。ToTensor()必須放在所有 PIL 層面的操作之后因為它把 PIL Image 轉(zhuǎn)成 PyTorch 張量后續(xù)的Normalize只能作用于張量。Normalize放在最后是硬性要求它接收的 mean 和 std 是 ImageNet 的統(tǒng)計值。如果你用的是自己數(shù)據(jù)集從頭訓(xùn)練理論上應(yīng)該計算自己數(shù)據(jù)集的均值和標(biāo)準(zhǔn)差但實踐中直接用 ImageNet 的值效果也不會差太多除非你的數(shù)據(jù)分布和自然圖像差異極大。3.3 驗證集 transform 的關(guān)鍵差異驗證集和測試集的 transform 管道必須去掉所有隨機(jī)性操作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]) ])RandomCrop換成CenterCropRandomHorizontalFlip和RandomRotation直接去掉。原因很簡單驗證階段需要的是穩(wěn)定、可復(fù)現(xiàn)的評估結(jié)果隨機(jī)增強(qiáng)會讓每次評估的結(jié)果產(chǎn)生波動無法準(zhǔn)確反映模型性能。我見過有人在驗證集上也用了隨機(jī)增強(qiáng)然后困惑為什么驗證準(zhǔn)確率每次都不一樣。這不是模型的問題是評估管道的問題。3.4 數(shù)據(jù)增強(qiáng)的度怎么把握數(shù)據(jù)增強(qiáng)是把雙刃劍。增強(qiáng)太弱模型容易過擬合增強(qiáng)太強(qiáng)模型學(xué)到的分布偏離真實分布反而降低性能。我的經(jīng)驗是水平翻轉(zhuǎn)大多數(shù)自然圖像場景都可以用p0.5 是安全值。但如果你做的是文字識別、醫(yī)學(xué)影像左右有明確語義差異就不要用。隨機(jī)裁剪配合 Resize 使用裁剪比例不要太小否則會丟失關(guān)鍵信息。一般從原圖面積的 0.6~1.0 之間裁剪比較合理。顏色抖動brightness、contrast、saturation 的參數(shù)控制在 0.2~0.3 之間再大就可能讓圖片失真嚴(yán)重。隨機(jī)旋轉(zhuǎn)15 度以內(nèi)比較安全超過 30 度很多圖像的內(nèi)容就不合理了。一個實用技巧先把增強(qiáng)后的圖片可視化出來看看。如果人眼都覺得增強(qiáng)后的圖片不像正常圖片那模型大概率也學(xué)不好。4. 從 ImageFolder 到 DataLoader批量加載的工程細(xì)節(jié)4.1 DataLoader 的核心參數(shù)配置ImageFolder 本身只是一個 Dataset 對象真正在訓(xùn)練循環(huán)里干活的是 DataLoader。它的參數(shù)配置直接影響訓(xùn)練效率和穩(wěn)定性from torch.utils.data import DataLoader train_loader DataLoader( datasettrain_dataset, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue, drop_lastTrue, persistent_workersTrue )逐個說這些參數(shù)的選擇邏輯。batch_size取決于顯存大小一般從 32 或 64 起步顯存不夠就往下調(diào)。shuffleTrue在訓(xùn)練時必須開否則模型會學(xué)到樣本順序帶來的虛假模式。num_workers控制數(shù)據(jù)加載的并行進(jìn)程數(shù)設(shè)置成 CPU 核心數(shù)的 2~4 倍比較常見但也不是越大越好太多 worker 反而會因為進(jìn)程切換開銷降低效率。pin_memoryTrue在 GPU 訓(xùn)練時建議開啟它會把數(shù)據(jù)加載到鎖頁內(nèi)存加速 CPU 到 GPU 的傳輸。drop_lastTrue在訓(xùn)練時建議開啟避免最后一個不完整的 batch 影響 BatchNorm 的統(tǒng)計。persistent_workersTrue在 num_workers 0 時有用它讓 worker 進(jìn)程在 epoch 之間保持存活減少反復(fù)創(chuàng)建銷毀的開銷。4.2 num_workers 設(shè)置的實測經(jīng)驗num_workers 這個參數(shù)我踩過不少坑。設(shè)成 0 意味著在主進(jìn)程里加載數(shù)據(jù)調(diào)試方便但速度慢。設(shè)成大于 0 的值會啟用多進(jìn)程加載但有幾個注意事項在 Windows 系統(tǒng)上多進(jìn)程 DataLoader 需要把訓(xùn)練代碼放在if __name__ __main__:保護(hù)塊里否則會無限遞歸創(chuàng)建進(jìn)程。這個坑我在 Windows 上第一次用 DataLoader 時踩得很慘程序直接卡死。另外num_workers 設(shè)得過大時如果每個 worker 都要打開大量文件可能會遇到文件句柄耗盡的問題。我一般會先用較小的值比如 4跑一遍觀察 GPU 利用率。如果 GPU 利用率經(jīng)常掉到 50% 以下說明數(shù)據(jù)加載是瓶頸可以適當(dāng)增加 num_workers如果 GPU 利用率穩(wěn)定在 90% 以上就沒必要再加了。4.3 自定義 collate_fn 處理異常樣本默認(rèn)的 collate_fn 會把一個 batch 的數(shù)據(jù)堆疊成張量。但如果某個樣本在 transform 過程中出錯比如圖片損壞整個 batch 都會失敗。一個更健壯的做法是自定義 collate_fn跳過異常樣本def safe_collate(batch): batch [item for item in batch if item is not None] if len(batch) 0: return None return torch.utils.data.dataloader.default_collate(batch)配合在 Dataset 的__getitem__里加 try-except返回 None 表示該樣本無效。這樣即使有個別損壞圖片也不會中斷整個訓(xùn)練過程。當(dāng)然更好的做法還是訓(xùn)練前就把損壞文件清理干凈這只是最后一道防線。4.4 訓(xùn)練和驗證的 DataLoader 差異驗證集的 DataLoader 和訓(xùn)練集有幾個關(guān)鍵區(qū)別參數(shù)訓(xùn)練集驗證集shuffleTrueFalsedrop_lastTrueFalsebatch_size32/64/128可以更大transform含隨機(jī)增強(qiáng)僅確定性變換驗證集不需要 shuffle因為評估結(jié)果與順序無關(guān)而且關(guān)閉 shuffle 后結(jié)果可復(fù)現(xiàn)。drop_lastFalse確保所有驗證樣本都被評估到。batch_size 可以設(shè)得比訓(xùn)練集大因為驗證時不需要反向傳播顯存占用小很多。5. 類別不平衡與樣本權(quán)重ImageFolder 之外的補(bǔ)充手段5.1 用 WeightedRandomSampler 處理不平衡數(shù)據(jù)ImageFolder 本身不提供類別平衡機(jī)制。如果你的數(shù)據(jù)集里某些類別樣本特別少模型會傾向于預(yù)測多數(shù)類。這時候需要引入WeightedRandomSamplerimport numpy as np from torch.utils.data import WeightedRandomSampler targets [label for _, label in train_dataset] class_counts np.bincount(targets) class_weights 1.0 / class_counts sample_weights [class_weights[t] for t in targets] sampler WeightedRandomSampler( weightssample_weights, num_sampleslen(sample_weights), replacementTrue ) train_loader DataLoader( train_dataset, batch_size32, samplersampler, # 注意用了 sampler 就不能再設(shè) shuffleTrue num_workers4 )這個采樣器的邏輯是給每個樣本分配一個權(quán)重權(quán)重與它所屬類別的樣本數(shù)量成反比。這樣少數(shù)類樣本被抽到的概率更高每個 batch 里各類別的比例會更均衡。有個細(xì)節(jié)要注意用了 sampler 之后DataLoader 的 shuffle 參數(shù)必須去掉或設(shè)為 False否則會報錯。因為 sampler 和 shuffle 是互斥的。5.2 從 class_to_idx 反查類別分布在決定是否需要用采樣器之前先看看數(shù)據(jù)分布from collections import Counter targets [label for _, label in train_dataset] counter Counter(targets) idx_to_class {v: k for k, v in train_dataset.class_to_idx.items()} for idx, count in sorted(counter.items()): print(f{idx_to_class[idx]}: {count} 張)如果最多類別和最少類別的樣本數(shù)差距在 3 倍以內(nèi)一般不需要特殊處理。差距超過 10 倍就必須用采樣器或類別權(quán)重來干預(yù)了。5.3 類別權(quán)重在損失函數(shù)中的使用除了采樣器另一種方式是在損失函數(shù)里給不同類別加權(quán)import torch.nn as nn class_counts np.bincount(targets) weights 1.0 / class_counts weights weights / weights.sum() * len(class_counts) class_weights torch.FloatTensor(weights).to(device) criterion nn.CrossEntropyLoss(weightclass_weights)這種方式不改變數(shù)據(jù)采樣分布而是在計算 loss 時讓少數(shù)類的錯誤被放大。兩種方式可以結(jié)合使用也可以只用一種。我的經(jīng)驗是如果類別極度不平衡比如 100:1采樣器效果更明顯如果只是輕度不平衡比如 5:1損失加權(quán)就夠了。6. 推理階段的標(biāo)簽映射與結(jié)果還原6.1 保存和加載 class_to_idx 映射訓(xùn)練完成后dataset.class_to_idx這個字典必須保存下來。推理時模型輸出的是類別索引你需要用它反查類別名稱import json # 訓(xùn)練時保存 with open(class_mapping.json, w) as f: json.dump(train_dataset.class_to_idx, f) # 推理時加載 with open(class_mapping.json, r) as f: class_to_idx json.load(f) idx_to_class {v: k for k, v in class_to_idx.items()} # 假設(shè)模型輸出 logits predicted_idx torch.argmax(logits, dim1).item() predicted_class idx_to_class[predicted_idx] print(f預(yù)測類別: {predicted_class})這個映射文件是模型部署的必備產(chǎn)物和模型權(quán)重同等重要。我見過有人只保存了模型權(quán)重部署時忘了類別映射結(jié)果預(yù)測結(jié)果全是錯的。6.2 單張圖片推理的完整流程推理時的 transform 必須和驗證集完全一致不能有任何隨機(jī)性from PIL import Image import torch def predict_single_image(image_path, model, transform, idx_to_class, device): model.eval() image Image.open(image_path).convert(RGB) input_tensor transform(image).unsqueeze(0).to(device) with torch.no_grad(): logits model(input_tensor) probabilities torch.softmax(logits, dim1) confidence, predicted_idx torch.max(probabilities, dim1) predicted_class idx_to_class[predicted_idx.item()] confidence_score confidence.item() return predicted_class, confidence_score注意unsqueeze(0)這一步它給單張圖片增加了一個 batch 維度因為模型期望的輸入是 4 維張量batch, channel, height, width。這個細(xì)節(jié)新手很容易漏掉導(dǎo)致維度不匹配的報錯。6.3 批量推理與結(jié)果導(dǎo)出如果要對整個測試集做推理并導(dǎo)出結(jié)果可以復(fù)用 ImageFolder 加 DataLoader 的方式test_dataset ImageFolder(root./data/test, transformval_transform) test_loader DataLoader(test_dataset, batch_size64, shuffleFalse, num_workers4) all_predictions [] all_labels [] model.eval() with torch.no_grad(): for images, labels in test_loader: images images.to(device) logits model(images) preds torch.argmax(logits, dim1).cpu().numpy() all_predictions.extend(preds) all_labels.extend(labels.numpy()) # 導(dǎo)出結(jié)果 import pandas as pd results pd.DataFrame({ true_label: [idx_to_class[l] for l in all_labels], predicted_label: [idx_to_class[p] for p in all_predictions] }) results.to_csv(predictions.csv, indexFalse)這里有個容易忽略的點test_dataset的class_to_idx必須和訓(xùn)練時一致。如果測試集的文件夾結(jié)構(gòu)和訓(xùn)練集不同或者類別文件夾命名不一致映射關(guān)系就會錯亂。所以我在實際項目中會強(qiáng)制要求訓(xùn)練集和測試集使用完全相同的類別文件夾命名。7. 性能優(yōu)化與常見問題速查7.1 數(shù)據(jù)加載成為瓶頸時的排查思路訓(xùn)練時如果發(fā)現(xiàn) GPU 利用率忽高忽低大概率是數(shù)據(jù)加載拖了后腿。排查步驟先用num_workers0跑一個 epoch記錄時間作為基準(zhǔn)逐步增加 num_workers4, 8, 16觀察每個 epoch 的耗時變化如果增加 worker 后耗時不再下降說明瓶頸不在數(shù)據(jù)加載可能在模型計算或 GPU 傳輸檢查 transform 管道里是否有特別耗時的操作比如大尺寸圖片的旋轉(zhuǎn)我遇到過一次數(shù)據(jù)加載特別慢的情況最后發(fā)現(xiàn)是RandomRotation在大尺寸圖片上做雙三次插值單張圖要花 200ms。解決辦法是先 Resize 到較小尺寸再做旋轉(zhuǎn)速度直接提升了一個數(shù)量級。7.2 內(nèi)存不足的幾種表現(xiàn)和應(yīng)對內(nèi)存問題分兩種CPU 內(nèi)存不足和 GPU 顯存不足。CPU 內(nèi)存不足通常發(fā)生在 num_workers 設(shè)得太大時每個 worker 都會復(fù)制一份數(shù)據(jù)集索引和緩存。解決辦法是降低 num_workers或者用persistent_workersFalse讓 worker 在 epoch 之間釋放。GPU 顯存不足則要降低 batch_size或者用梯度累積來模擬大 batchaccumulation_steps 4 optimizer.zero_grad() for i, (images, labels) in enumerate(train_loader): images, labels images.to(device), labels.to(device) outputs model(images) loss criterion(outputs, labels) / accumulation_steps loss.backward() if (i 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()梯度累積讓模型在多個小 batch 上累積梯度后再更新參數(shù)效果等價于用更大的 batch 訓(xùn)練但顯存占用不變。7.3 常見報錯速查表報錯信息原因解決方案FileNotFoundError: Found no valid file for the classes類別文件夾為空或只有非圖片文件檢查文件夾內(nèi)容添加 is_valid_file 過濾UnidentifiedImageError圖片文件損壞或格式不支持預(yù)檢查并清理損壞文件RuntimeError: DataLoader worker exited unexpectedlyworker 進(jìn)程崩潰通常是內(nèi)存不足降低 num_workers 或 batch_sizeValueError: num_samples should be a positive integerWeightedRandomSampler 的 num_samples 設(shè)置錯誤設(shè)為 len(dataset) 或正整數(shù)CUDA out of memory顯存不足降低 batch_size 或用梯度累積這張表里的問題我基本都遇到過尤其是第一個和第二個在整理新數(shù)據(jù)集時幾乎必然出現(xiàn)。建議把預(yù)檢查腳本作為數(shù)據(jù)準(zhǔn)備流程的標(biāo)準(zhǔn)步驟能省去大量調(diào)試時間。7.4 關(guān)于 ImageFolder 的適用邊界最后說一個判斷標(biāo)準(zhǔn)什么時候不該用 ImageFolder。如果你的數(shù)據(jù)標(biāo)簽不是通過文件夾結(jié)構(gòu)組織的比如標(biāo)簽存在 CSV 文件里、或者一張圖對應(yīng)多個標(biāo)簽多標(biāo)簽分類、或者做的是目標(biāo)檢測和分割任務(wù)那 ImageFolder 就不適用了。這些場景需要自定義 Dataset 類繼承torch.utils.data.Dataset并實現(xiàn)__len__和__getitem__。ImageFolder 的定位很明確單標(biāo)簽、文件夾組織、圖像分類。在這個范圍內(nèi)它是最省事的方案超出這個范圍就別硬套了自定義 Dataset 更靈活。我在實際項目中用 ImageFolder 處理過幾萬到幾十萬張圖片的數(shù)據(jù)集只要目錄結(jié)構(gòu)規(guī)范、預(yù)處理到位它的穩(wěn)定性和效率完全夠用。真正花時間的從來不是 ImageFolder 本身而是數(shù)據(jù)清洗、目錄整理和 transform 調(diào)優(yōu)這些周邊工作。把這幾塊做好了ImageFolder 就是一條順暢的數(shù)據(jù)管道讓你能把精力集中在模型設(shè)計和訓(xùn)練策略上。