深入解析:Deep Learning MNIST手写数字识别 Mac

Background

神经网络如何识别图片

在这里插入图片描述

从就是左上角的图片具有25个像素点(5x5),展开为一个vector变为 25x1,也就x 0 0 x_0^0x00x 24 0 x_{24}^0x240,脚标表示行,上标表示列。
a i , j k a_{i,j}^kai,jki ii表示上一层节点序号,j jj表示这一层节点序号,k kk表示网络层数,b i , j k b_{i,j}^kbi,jk同理。

输出层有10个节点对应0~9每种数字对应的可能性(i.e. 概率),且输出层所有概率总和为1,因此在输出前需要用softmax进行归一化。为了使得结论正确,需要不断调整参数,使得x 7 4 x_7^4x74趋近于1,其他输出趋近于0。从而使得神经网络问题变为一个最优化问题。

这里是一张图片的计算,而训练数据集中有几万张,因此重复几万6 次以获得一组合适的网络参数,该神经网络则具备预测的能力。

图像拆分为一维像素阵列,输入到神经网络:

  1. 通过节点像素计算公式,图像信息传播到输出层
  2. 通过 SoftMax归一化,得到概率分布
  3. 通过大量图像材料的训练,不断调整网络参数,让概率分布更接近真实值

神经网络本质=数学函数,训练的过程=调整函数中的参数

另外:

  1. 每次训练一张效率太低,因此我们每次训练一批(batch)就是可
  2. 节点计算可能通过激活函数变为非线性

MNIST数据集

手写数字图片7万张:训练集6万张+测试集1万张

MNIST资料集中每张图片

  • 大小为28x28像素
  • 每个像素的灰度值范围为0~255
  • 每张图片配有一个标记:真实值

在这里插入图片描述

Code for Mac

我的设备是macbook M3,没有GPU,所以需用的Apple的GPU加速框架 MPS

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision.datasets import MNIST
import matplotlib.pyplot as plt
# 1) 设备选择:优先 MPS(Apple GPU),否则 CPU
def get_device():
if torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")
device = get_device()
print("Using device:", device)
# 2) 模型:4层全连接
class Net
(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(28*28, 64)
self.fc2 = nn.Linear(64, 64)
self.fc3 = nn.Linear(64, 64)
self.fc4 = nn.Linear(64, 10) # 10 类
def forward(self, x):
# x: [B, 1, 28, 28] → 展平到 [B, 28*28]
x = x.view(x.size(0), -1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = F.relu(self.fc3(x))
logits = self.fc4(x) # 这里直接输出 “logits”
return logits # 交给 CrossEntropyLoss 处理
# 3) 数据加载
def get_data_loader(is_train):
to_tensor = transforms.Compose([
transforms.ToTensor(), # [0,1]
transforms.Normalize((0.1307,), (0.3081,))# 稳定训练(可选但推荐)
])
dataset = MNIST(root="./data", train=is_train, transform=to_tensor, download=True)
return DataLoader(dataset, batch_size=15, shuffle=True)
# 4) 评估:计算准确率
@torch.no_grad()
def evaluate(data_loader, net, device):
net.eval()
correct, total = 0, 0
for x, y in data_loader:
x, y = x.to(device), y.to(device)
logits = net(x) # [B,10]
pred = logits.argmax(dim=1) # [B]
correct += (pred == y).sum().item()
total += y.size(0)
return correct / total
def main():
train_loader = get_data_loader(is_train=True)
test_loader = get_data_loader(is_train=False)
net = Net().to(device)
criterion = nn.CrossEntropyLoss() # 直接搭配logits使用
optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
print("Initial accuracy:", evaluate(test_loader, net, device))
for epoch in range(2):
net.train()
for x, y in train_loader:
x, y = x.to(device), y.to(device)
optimizer.zero_grad()
logits = net(x) # 前向
loss = criterion(logits, y) # CE会内部做log-softmax
loss.backward() # 反向
optimizer.step() # 更新
acc = evaluate(test_loader, net, device)
print(f"Epoch {epoch
} | Test Acc: {acc:.4f
}")
# 随机看几张预测
net.eval()
shown = 0
for x, y in test_loader:
x, y = x.to(device), y.to(device)
logits = net(x)
pred = logits.argmax(dim=1)
for i in range(min(3, x.size(0))):
plt.figure()
plt.imshow(x[i,0].cpu(), cmap="gray")
plt.title(f"Pred: {
int(pred[i])
} | True: {
int(y[i])
}")
plt.axis("off")
shown += 1
if shown >= 3:
plt.show()
return
if __name__ == "__main__":
main()
posted @ 2025-08-12 19:50  yjbjingcha  阅读(17)  评论(0)    收藏  举报