在这篇教程中,我会介绍 PyTorch 这个强大的深度学习框架,并通过使用它实现一个基于 MNIST 数据集的多层神经网络,帮助读者了解神经网络的基本原理和实现过程。我们将使用 CPU 版本的 PyTorch ,清除较为复杂的 GPU 配置过程,以便初学者更容易上手。对于需要 GPU 加速的用户,可以参考每个关键部分中的 CUDA 声明,以实现 GPU 训练和推理。
环境配置
在使用此教程前,您需要配置基础环境:
- 
安装 Python (建议使用 Python 3.8 或更高版本) 
- 
安装 PyTorch: bashpip install torch torchvision
数据集获取
MNIST 数据集包含手写数字图片,是机器学习和深度学习中的经典数据集。我们通过 torchvision.datasets.MNIST 类下载并加载数据,同时应用标准化转换,以提升模型的收敛速度和性能。
为了确保数据只被下载一次,我们在文件存储时进行检查:
            
            
              python
              
              
            
          
          import os
from torchvision import datasets, transforms
import torch
# 设定数据文件夹
DATA_DIR = './data'
if not os.path.exists(DATA_DIR):
    os.makedirs(DATA_DIR)
# 自定义数据处理模块,将图像标准化
transform = transforms.Compose([
    transforms.ToTensor(),  # 转换为线性描述
    transforms.Normalize((0.1307,), (0.3081,))  # 标准化
])
# 获取 MNIST 训练集和测试集
train_dataset = datasets.MNIST(root=DATA_DIR, train=True, transform=transform, download=True)
test_dataset = datasets.MNIST(root=DATA_DIR, train=False, transform=transform)
# 定义设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")CUDA 声明 : 使用
DataLoader后在每个批次中将数据移动到 CUDA 设备,而不是直接移动整个数据集。
构建多层神经网络
神经网络由层组成,每一层由若干个神经元构成,连接权重和偏置项用于确定输入数据如何在网络中传播。我们使用 torch.nn.Module 定义网络结构,并通过 forward 函数描述数据的前向传播过程。
            
            
              python
              
              
            
          
          import torch
import torch.nn as nn
class NeuralNetwork(nn.Module):
    """
    一个基本的多层神经网络:
    - 28x28 输入,128 个节点的一层全连
    - ReLU 激活
    - 64 节点的第二层全连
    - 10 个节点用于输出
    """
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.fc1 = nn.Linear(28*28, 128)  # 输入层到28*28图像转为向量
        self.fc2 = nn.Linear(128, 64)  # 第二层
        self.fc3 = nn.Linear(64, 10)  # 最终输出 10 个分类结果
    def forward(self, x):
        x = x.view(-1, 28*28)  # 将图像抽成一个长向量
        x = torch.relu(self.fc1(x))  # 使用 ReLU 激活函数
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x
model = NeuralNetwork()
model = model.to(device)  # 将模型移动到设备上定义损失函数和优化器
损失函数用于度量模型输出与真实标签之间的差距。优化器通过最小化损失函数调整网络参数。我们选用交叉熵损失(nn.CrossEntropyLoss)和随机梯度下降(optim.SGD)优化器。
            
            
              python
              
              
            
          
          import torch.optim as optim
criterion = nn.CrossEntropyLoss()  # 用于分类任务
optimizer = optim.SGD(model.parameters(), lr=0.01)  # 使用 SGD 优化
criterion = criterion.to(device)  # 将损失函数移动到 CUDA 设备深度学习
训练过程包括前向传播、计算损失、反向传播和参数更新。我们通过 DataLoader 将数据分批次输入模型,逐步优化模型性能。
            
            
              python
              
              
            
          
          from torch.utils.data import DataLoader
train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
# 训练过程
num_epochs = 10
for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    for images, labels in train_loader:
        images, labels = images.to(device), labels.to(device)  # 将数据移动到 CUDA 设备
        optimizer.zero_grad()  # 清空梯度
        outputs = model(images)
        loss = criterion(outputs, labels)  # 计算损失
        loss.backward()  # 进行反向传播
        optimizer.step()  # 更新参数
        running_loss += loss.item()
    print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss/len(train_loader):.4f}')测试结果
模型评估阶段不需要更新参数,因此使用 torch.no_grad() 禁止梯度计算。通过 torch.max() 找到模型预测的类别,计算准确率。
            
            
              python
              
              
            
          
          test_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)
model.eval()
correct = 0
total = 0
with torch.no_grad():  # 禁止更新参数
    for images, labels in test_loader:
        images, labels = images.to(device), labels.to(device)  # 将数据移动到 CUDA 设备
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)  # 进行分类预测
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
print(f'Test Accuracy: {100 * correct / total:.2f}%')原理认识
- ReLU 激活函数: 应用于神经网络层之间,能够有效地减少静态分布问题,并能增强网络泛化能力。
- CrossEntropyLoss: 适合于多分类任务,通过计算分类误差以寻求最优训练结果。
- SGD 优化器: 简单但有效的参数更新方法,在大部分场景中表现良好。
手写数字识别小程序
我们将使用 tkinter 创建一个简洁现代的 GUI,用户可以在画板上手写数字,然后点击按钮让模型识别数字。程序会展示预测结果,方便用户了解模型的识别能力。
            
            
              python
              
              
            
          
          import tkinter as tk
from PIL import Image, ImageDraw
import numpy as np
import torch
class DigitRecognizerApp:
    def __init__(self, model):
        self.model = model  # 加载预训练的数字识别模型
        self.window = tk.Tk()  # 创建主窗口
        self.window.title("手写数字识别")  # 设置窗口标题
        # 创建画板,用于手写数字
        self.canvas = tk.Canvas(self.window, width=280, height=280, bg='white')
        self.canvas.grid(row=0, column=0, columnspan=3)  # 将画板放置在第一行,横跨3列
        # 绑定鼠标左键拖动事件,用于绘制数字
        self.canvas.bind('<B1-Motion>', self.draw_digit)
        # 创建清除按钮
        self.clear_button = tk.Button(self.window, text='清除', command=self.clear_canvas)
        self.clear_button.grid(row=1, column=0)  # 将清除按钮放置在第二行第一列
        # 创建识别按钮
        self.recognize_button = tk.Button(self.window, text='识别', command=self.recognize_digit)
        self.recognize_button.grid(row=1, column=1)  # 将识别按钮放置在第二行第二列
        # 创建结果显示标签
        self.result_label = tk.Label(self.window, text="识别结果:", font=("Arial", 16))
        self.result_label.grid(row=1, column=2)  # 将结果显示标签放置在第二行第三列
        # 初始化图像和绘图工具
        self.image = Image.new('L', (28, 28), color=0)  # 创建一个28x28的黑色图像
        self.draw = ImageDraw.Draw(self.image)  # 创建绘图对象
    def draw_digit(self, event):
        """在画板上绘制数字,并在内存中的图像上同步绘制"""
        x, y = event.x, event.y  # 获取鼠标当前位置
        self.canvas.create_oval(x-5, y-5, x+5, y+5, fill='black')  # 在画板上绘制黑色圆点
        self.draw.ellipse([(x/10, y/10), (x/10+1, y/10+1)], fill=255)  # 在内存中的图像上绘制白色圆点
    def clear_canvas(self):
        """清除画板和内存中的图像"""
        self.canvas.delete('all')  # 清除画板上的所有内容
        self.image = Image.new('L', (28, 28), color=0)  # 重新初始化图像为黑色
        self.draw = ImageDraw.Draw(self.image)  # 重新创建绘图对象
        self.result_label.config(text="识别结果:")  # 清空识别结果
    def recognize_digit(self):
        """识别画板上的数字并显示结果"""
        image_array = np.array(self.image).reshape(1, 1, 28, 28) / 255.0  # 将图像转换为模型输入格式
        image_tensor = torch.tensor(image_array, dtype=torch.float32)  # 将图像转换为张量
        with torch.no_grad():  # 禁用梯度计算
            output = self.model(image_tensor)  # 使用模型进行预测
            predicted_digit = torch.argmax(output).item()  # 获取预测结果
            self.result_label.config(text=f"识别结果:{predicted_digit}")  # 更新结果显示标签
    def run(self):
        """运行应用程序"""
        self.window.mainloop()  # 进入主事件循环
app = DigitRecognizerApp(model)  # 传入模型
app.run()  # 运行应用程序结论
通过这次学习,读者应对 PyTorch 实现多层神经网络有了基本了解,可以尝试增加更深的层数,使用更复杂的激活函数,以改善结果。