網(wǎng)絡的手寫數(shù)字識別:PyTorch完整源碼與訓練避坑指南)
簡介基于AlexNet卷積神經(jīng)網(wǎng)絡的手寫數(shù)字識別Python項目內(nèi)含完整可運行源碼與實驗報告面向畢業(yè)設計、期末大作業(yè)和課程設計場景。代碼逐行注明關鍵邏輯從數(shù)據(jù)加載、模型搭建到訓練評估一目了然即使新手也能快速上手配套實驗報告詳細介紹了數(shù)據(jù)集準備、AlexNet網(wǎng)絡結構、訓練參數(shù)配置及結果可視化源自98分高分項目獲得導師認可下載后簡單配置環(huán)境即可部署使用。壓縮包共18個文件以10個Python腳本為核心覆蓋模型定義、數(shù)據(jù)處理、訓練測試等模塊另有4個預訓練權重文件gz格式以及項目配置、依賴清單、說明文檔等文本材料整體僅11.07MB非常輕量。目前已有390人學習適合作為深度學習圖像識別任務的入門范例也可直接用于課設答辯或畢設展示是一份結構完整、注釋規(guī)范的高分模板。1. 基于 AlexNet 卷積神經(jīng)網(wǎng)絡的手寫數(shù)字識別這份 python 源碼到底能幫你省多少事如果你正在為畢業(yè)設計或者期末大作業(yè)找一份能直接跑、能講清楚原理的深度學習項目基于 AlexNet 卷積神經(jīng)網(wǎng)絡實現(xiàn)手寫數(shù)字識別的這套 python 源碼大概率就是你想要的答案。它不是一個只有模型的半成品而是把訓練、測試、可視化、配置管理都放在同一個工程里的完整項目代碼里帶注釋結構上分了 models、train、utils、data 幾大塊新手照著 README 也能把訓練跑起來。我做過的課程設計和畢設評審里這類項目最大的價值在于模型結構不堆砌、代碼風格可讀、報告和代碼對應得上答辯時問到哪一層都能接住話。2. 從下載到跑通目錄結構、環(huán)境依賴與首次運行2.1 文件清單哪些文件是核心哪些可以直接忽略這套源碼解壓之后是一個 folder-master 目錄里面并不是所有文件都需要你逐行去讀。我一般拿到一個開源項目首先會把目錄里每個文件的作用標出來避免把時間浪費在 IDE 配置或者無關文件上。文件/目錄職責優(yōu)先級main.py程序入口負責初始化配置、啟動訓練或測試流程核心config.py集中管理超參數(shù)、路徑、設備選擇核心models/AlexNet.pyAlexNet 網(wǎng)絡定義本項目的主模型核心models/ResNet.pyResNet 版本的對照模型用于對比實驗擴展models/BasicModel.py基礎 CNN 模型可作為 baseline擴展train/dataset.py數(shù)據(jù)加載、預處理、劃分訓練集和驗證集核心train/utils/visualize.py繪制 loss 曲線、準確率曲線、樣本可視化輔助test/測試腳本加載訓練好的權重做評估核心requirements.txtPython 依賴清單部署.idea/vcs.xmlPyCharm 的 IDE 配置文件可忽略.gitignoreGit 忽略規(guī)則可忽略README.md項目說明文檔先讀從經(jīng)驗看訓練一個 MNIST 級別的手寫數(shù)字識別模型AlexNet.py、dataset.py、config.py這三個文件決定成敗。.idea是 PyCharm 自動生成的換到 VSCode 也不影響運行。2.2 環(huán)境要求與 requirements.txt 部署步驟這套代碼是基于 PyTorch 寫的所以在跑之前先把深度學習環(huán)境準備好。新手最容易在這里卡住裝完 Python 之后忘了裝 CUDA 版 PyTorch后面訓練時只能用 CPU一個 epoch 要跑幾分鐘影響調(diào)參節(jié)奏。我的習慣是先在終端里確認 Python 版本再裝依賴。python --version # 建議 3.8 及以上版本 pip install -r requirements.txtrequirements.txt里一般包含 torch、torchvision、numpy、matplotlib 這幾個核心庫。torchvision 負責下載和預處理 MNIST 數(shù)據(jù)集matplotlib 用于 visualize.py 畫圖。如果你用的是 NVIDIA 顯卡建議單獨安裝 CUDA 版本對應的 PyTorch訓練速度能快一個量級如果沒有獨立顯卡CPU 版本也能跑只是需要把config.py里的設備改成cpu。2.3 首次運行從 config.py 改參數(shù)到啟動訓練跑通這個項目不需要改任何模型代碼只需要打開config.py確認幾個關鍵參數(shù)batch_size、learning_rate、epochs、device。我第一次拿到這套源碼時直接把 epochs 改成了 10batch_size 保持 64在 CPU 上跑了大約二十分鐘驗證集準確率就到了 98% 以上。python main.py運行之后終端會逐行打印當前 epoch、loss、accuracy。如果看到 loss 在穩(wěn)步下降說明數(shù)據(jù)和模型已經(jīng)正常交互。如果報錯先看是不是缺依賴再看是不是路徑問題。項目里data目錄帶__init__.py說明數(shù)據(jù)加載是包結構不用手動去下載 MNISTdataset.py會在第一次運行時自動下載到本地。2.4 IDE 與運行方式不是必須用 PyCharm.idea目錄會讓很多人誤以為必須用 PyCharm 打開其實不是。VSCode 配置好 Python 解釋器之后一樣能跑關鍵在于main.py的當前工作目錄要指向項目根目錄否則相對路徑會找不到models和train包。我一般這樣處理cd folder-master python main.py先在終端里 cd 到項目根目錄再執(zhí)行腳本這樣最不容易出路徑問題。如果你在 IDE 里直接點運行按鈕記得檢查 Run Configuration 里的 working directory 是否指向項目根目錄。3. AlexNet 網(wǎng)絡結構拆解5 個卷積層如何適配 28×28 的 MNIST3.1 原始 AlexNet 與 MNIST 版的差異原始 AlexNet 是 2012 年 ImageNet 比賽的冠軍模型輸入是 224×224 的 RGB 三通道圖像用 5 個卷積層加 3 個全連接層提取特征。但 MNIST 手寫數(shù)字是 28×28 的灰度單通道圖像直接把原始結構搬過來并不合適。最大的問題在于感受野第一層卷積核是 11×11、步長是 4作用在 28×28 圖像上特征圖會迅速縮小信息損失太嚴重。所以這套源碼里的 AlexNet 是經(jīng)過適配的版本核心改動集中在兩點輸入通道從 3 改成 1第一層卷積核從 11×11 改成 5×5步長從 4 改成 1。這樣既保留了 AlexNet 的深層結構特性又能適配小尺寸灰度圖。這也是答辯時老師最喜歡問的一個點。3.2 models/AlexNet.py 核心代碼AlexNet.py里定義的網(wǎng)絡結構并不復雜關鍵在于每一層的參數(shù)怎么設、輸出尺寸怎么算。下面是核心代碼的簡化版本import torch.nn as nn class AlexNet(nn.Module): def __init__(self, num_classes10): super(AlexNet, self).__init__() # 輸入: (batch, 1, 28, 28) 灰度圖 self.features nn.Sequential( # conv1: 1 - 96, 5x5 卷積步長 1padding 2 nn.Conv2d(1, 96, kernel_size5, stride1, padding2), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size3, stride2), # 28 - 13 # conv2: 96 - 256, 5x5 卷積 nn.Conv2d(96, 256, kernel_size5, stride1, padding2), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size3, stride2), # 13 - 6 # conv3: 256 - 384, 3x3 卷積 nn.Conv2d(256, 384, kernel_size3, stride1, padding1), nn.ReLU(inplaceTrue), # conv4: 384 - 384, 3x3 卷積 nn.Conv2d(384, 384, kernel_size3, stride1, padding1), nn.ReLU(inplaceTrue), # conv5: 384 - 256, 3x3 卷積 nn.Conv2d(384, 256, kernel_size3, stride1, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size3, stride2), # 6 - 2 ) # 最后特征圖尺寸: 256 * 2 * 2 self.classifier nn.Sequential( nn.Dropout(p0.5), nn.Linear(256 * 2 * 2, 1024), nn.ReLU(inplaceTrue), nn.Dropout(p0.5), nn.Linear(1024, 512), nn.ReLU(inplaceTrue), nn.Linear(512, num_classes), ) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) # 展平 x self.classifier(x) return x這段代碼里有三個參數(shù)是手動調(diào)過的第一個 MaxPool2d 的 stride2 會把 28×28 降到 13×13第二個降到 6×6第三個降到 2×2。全連接層輸入維度是 256×2×2也就是 1024 個特征值。如果你照搬原始 AlexNet 的 6×6 輸出這里全連接層維度就要改成 256×6×69216顯存和計算量會大很多。Dropout 設置在訓練時隨機丟棄一半神經(jīng)元能有效降低過擬合風險在測試時 PyTorch 會自動關閉 Dropout不需要手動處理。3.3 輸入尺寸計算從卷積到全連接層維度對齊新手最容易算錯的就是卷積層輸出尺寸。公式是(輸入尺寸 - kernel_size 2 × padding) / stride 1。以第一層為例(28 - 5 2×2) / 1 1 28padding 2 讓輸出尺寸保持 28 不變。池化層則按(輸入 - kernel_size) / stride 1計算28×28 經(jīng)過 3×3 池化步長 2 變成(28-3)/2113第二次從 13 變 6第三次從 6 變 2。這個計算過程建議自己手推一遍因為實驗報告里需要寫清楚每一層的輸出形狀。答辯時老師會問「為什么全連接層第一層是 1024 而不是 9216」你只要能說出尺寸推導過程這個項目就立住了。3.4 ResNet.py 與 BasicModel.py作為對比基線這套源碼還額外給了 ResNet 和 BasicModel 兩個模型這說明作者在設計時就考慮了對照實驗。畢設和課程設計里對比實驗是很加分的部分用同一個數(shù)據(jù)集跑三組模型最后對比準確率和訓練收斂速度。BasicModel 是一個兩層卷積的簡單網(wǎng)絡作為 baselineResNet 則引入了殘差結構理論上收斂更快。我見過很多學生只跑一個模型就交報告答辯時被問「為什么不用 ResNet 試試」直接卡住。這套源碼自帶三個模型你有充分理由在報告里寫橫向?qū)Ρ葘煏J為你對模型選型有思考而不是只會調(diào)庫。4. dataset.py 與 config.py數(shù)據(jù)管線和超參數(shù)下放4.1 數(shù)據(jù)加載與預處理MNIST 的歸一化是硬要求train/dataset.py負責把 MNIST 原始數(shù)據(jù)處理成模型能接受的 Tensor。MNIST 本身是 PIL 圖像格式像素值范圍 0 到 255如果不做歸一化直接喂進網(wǎng)絡第一層卷積的輸入分布會很差訓練初期 loss 可能直接不降。這套代碼里用的是transforms.ToTensor()加transforms.Normalize((0.1307,), (0.3081,))這兩個值是 MNIST 數(shù)據(jù)集的全局均值和標準差屬于官方推薦配置。標準的 PyTorch 數(shù)據(jù)加載寫法是from torchvision import datasets, transforms from torch.utils.data import DataLoader transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_dataset datasets.MNIST( root./data, trainTrue, downloadTrue, transformtransform ) train_loader DataLoader( datasettrain_dataset, batch_size64, shuffleTrue, num_workers2 )num_workers2表示用兩個子進程做數(shù)據(jù)加載能緩解 GPU 訓練時的數(shù)據(jù)瓶頸。如果在 Windows 上跑num_workers大于 0 可能會報多進程相關的錯誤改成 0 就行。4.2 超參數(shù)集中管理config.py 是調(diào)參的后悔藥這個項目把超參數(shù)全部集中到config.py而不是散落在各個腳本里。這點我特別認可因為調(diào)參的時候你不需要去翻代碼打開一個文件改了就能跑。常見參數(shù)包括參數(shù)名推薦值說明batch_size64顯存不夠就降到 32learning_rate0.001Adam 優(yōu)化器常用初始值epochs10 ~ 15MNIST 上超過 15 個 epoch 容易過擬合devicecuda / cpu無顯卡時強制設成 cpunum_classes100~9 十個數(shù)字learning_rate的設定值直接影響收斂行為。0.001 是 Adam 優(yōu)化器的經(jīng)驗值配合交叉熵損失函數(shù)在 MNIST 上效果穩(wěn)定。如果 loss 震蕩不下降優(yōu)先把學習率降到 0.0001 再試這是最有效的后悔藥。4.3 訓練測試流程與日志輸出main.py會把訓練和測試串起來每個 epoch 結束之后在測試集上做一次評估打印當前準確率。我建議訓練過程中每 500 個 batch 就打印一次 loss這樣能提前發(fā)現(xiàn)訓練異常。比如 loss 在某個數(shù)值附近震蕩不降很可能就是學習率偏大或者數(shù)據(jù)沒歸一化。for epoch in range(config.epochs): for batch_idx, (data, target) in enumerate(train_loader): data, target data.to(device), target.to(device) optimizer.zero_grad() output model(data) loss criterion(output, target) loss.backward() optimizer.step() if batch_idx % 100 0: print(fEpoch {epoch}, Batch {batch_idx}, Loss {loss.item():.6f})訓練完成后權重會保存成.pth文件test目錄里的腳本會自動加載這個權重文件做最終評估。這里要記住保存的應該是model.state_dict()而不是整個 model 對象后者在跨環(huán)境加載時容易踩兼容性的坑。5. 訓練避坑排查Loss 不降、維度不匹配與設備失控的五個常見坑5.1 坑一全連接層維度不匹配報錯現(xiàn)象運行python main.py時拋RuntimeError: size mismatch, m1: [64 x 8192], m2: [1024 x 512]。原因卷積層輸出特征圖被展平后是 8192 維但全連接層第一層期望輸入 1024 維。多半是改了batch_size或者調(diào)整了池化層參數(shù)導致特征圖尺寸變了而全連接層維度沒有同步修改。解決在AlexNet.py的forward函數(shù)里加一行打印print(x.shape)然后根據(jù)實際的展平維度去改classifier的第一層nn.Linear的輸入尺寸。我一般會在模型里加一段注釋記下特征圖尺寸計算過程下次改參數(shù)時直接對照。5.2 坑二Loss 降不下去訓練像沒開始一樣現(xiàn)象loss 一直停留在 2.3 左右?guī)缀醪蛔兓愃齐S機猜測的水平。原因最常見的有三種。第一學習率設置過大loss 在震蕩第二輸入數(shù)據(jù)沒有歸一化像素值太大導致梯度爆炸第三模型初始化權重有問題。其中數(shù)據(jù)歸一化是最容易被忽略的。解決先檢查transforms.Normalize是否生效再看learning_rate是否在合理區(qū)間。如果用的是 SGD學習率通常是 0.01如果用的是 Adam0.001 是起點。兩個優(yōu)化器的默認學習率差一個數(shù)量級混用會踩坑。5.3 坑三CPU 與 GPU 設備不匹配現(xiàn)象報錯RuntimeError: Expected all tensors to be on the same device模型在 cuda 上數(shù)據(jù)在 cpu 上或者反過來。原因config.py里設置了device torch.device(cuda)但當前機器沒有可用 GPU或者數(shù)據(jù)沒有調(diào)用.to(device)。解決把設備選擇改成 device torch.device(cuda if torch.cuda.is_available() else cpu)然后確保每個 batch 的data和target都執(zhí)行了.to(device)。模型也要在訓練前model.to(device)。這三處只要有一處漏了就會觸發(fā)這個報錯。5.4 坑四MNIST 數(shù)據(jù)集下載失敗或卡住現(xiàn)象第一次運行腳本時終端停在Downloading ...長時間不動或者直接超時報錯。原因torchvision 默認從國外服務器下載 MNIST網(wǎng)絡不穩(wěn)定時就容易斷。解決手動從鏡像站下載mnist.pkl.gz文件放到data目錄下然后把downloadTrue改成downloadFalse。如果已經(jīng)完全下載失敗刪掉data目錄下的殘留文件再重新跑避免文件損壞。5.5 坑五訓練準確率高測試準確率卻普通現(xiàn)象訓練集最后幾個 epoch 準確率接近 99%但測試集只有 96% 左右。原因這是典型的過擬合信號模型把訓練集的特征背下來了沒有泛化到新樣本。MNIST 數(shù)據(jù)量不算小但 15 個 epoch 以上的訓練依然可能過擬合尤其在全連接層參數(shù)比較多時。解決調(diào)整config.py里的epochs或者增大AlexNet.py中 Dropout 的比例從 0.5 調(diào)到 0.7。如果做過對比實驗也可以用 ResNet 的殘差結構來抑制過擬合。報告里寫清楚調(diào)參前后對比就行。6. 進階驗證把 loss 曲線畫出來再對一張真實手寫圖做預測6.1 可視化腳本的使用方式別把visualize.py當成可有可無的裝飾實驗報告里最有說服力的就是那張 loss 下降曲線和 accuracy 上升曲線。我一般會在訓練完模型之后單獨跑一次可視化腳本生成兩張圖然后直接貼進報告。visualize.py內(nèi)部會讀取訓練過程中記錄的 loss 列表再用 matplotlib 繪制。import matplotlib.pyplot as plt def plot_loss(train_losses, val_losses): plt.figure(figsize(8, 5)) plt.plot(train_losses, labelTrain Loss) plt.plot(val_losses, labelVal Loss) plt.xlabel(Epoch) plt.ylabel(Loss) plt.legend() plt.title(Loss Curve on MNIST) plt.savefig(loss_curve.png, dpi150)如果你的訓練過程沒有手動記錄 loss 列表最簡單的辦法是在main.py的訓練循環(huán)里定義一個空列表每個 epoch 結束時把平均 lossappend進去。導出 PNG 之后圖片質(zhì)量夠清晰實驗報告直接能用。6.2 單張圖片推理流程訓練完模型除了在測試集上算準確率我建議再跑一次單張圖片推理把自己手寫的數(shù)字或測試集里的某張圖單獨拎出來預測。這段代碼可以作為實驗報告最后的驗證環(huán)節(jié)from PIL import Image import torchvision.transforms as transforms # 加載模型權重 model AlexNet(num_classes10) model.load_state_dict(torch.load(checkpoint.pth, map_locationcpu)) model.eval() # 預處理單張圖片 img Image.open(test_digit.png).convert(L).resize((28, 28)) transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) input_tensor transform(img).unsqueeze(0) # 推理 with torch.no_grad(): output model(input_tensor) pred output.argmax(dim1, keepdimTrue) print(fPredicted digit: {pred.item()})這里的model.eval()務必加上它會把 Dropout 和 BatchNorm 切換到推理模式。如果不加同一個輸入每次預測結果可能都不一樣這是新手最容易忽略的細節(jié)。我記得曾經(jīng)有個學生拿這套項目跑完之后把測試準確率直接寫在報告里就交了結果答辯時被要求現(xiàn)場用鼠標畫一個數(shù)字讓模型識別當場翻車。從那以后我每跑完一個分類模型都會強制走一遍單張推理流程確認不只是一個測試指標是真的能對新樣本做預測。這套源碼本身已經(jīng)把所有關鍵環(huán)節(jié)都準備好了你按照上面的步驟把訓練跑完、把可視化做完再順手驗證一張真實圖片整個項目就能形成一個完整閉環(huán)希望幫到你。本文還有配套的精品資源點擊獲取