从零开始:手动实现神经网络识别手写数字(完整代码讲解)

文章目录

我将用最基础的知识,一步步讲解如何手动实现一个能识别手写数字的神经网络。我们会像搭积木一样,从最简单的部分开始,逐渐构建完整的系统。

第一部分:准备工作

1.1 导入必要的工具

python 复制代码
import torch
import torch.utils.data as Data
import numpy as np
import pandas as pd

基础解释:

torch:就像我们的"数学工具箱",提供了处理数字和矩阵的工具

Data:用来管理和组织数据的工具

numpy和pandas:处理表格数据的工具

1.2 设置计算设备

python 复制代码
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用设备: {device}")

基础解释:

• 计算机有两种主要计算设备:CPU(普通处理器)和GPU(图形处理器,更适合做大量数学计算)

第二部分:准备数据

2.1 创建自定义数据集类

python 复制代码
class MNISTDataset(Data.Dataset):
    def __init__(self, csv_file):
        # 1. 读取CSV文件
        data = pd.read_csv(csv_file).values
        
        # 2. 分离标签和图像数据
        self.labels = data[:, 0]  # 第一列是标签(0-9)
        self.images = data[:, 1:]  # 后面784列是像素值
        
        # 3. 归一化处理:把0-255的像素值变成0-1之间的小数
        # 为什么要归一化?让所有数据在相同的尺度上,学习更稳定
        self.images = self.images.astype(np.float32) / 255.0
        
        # 4. 转换为PyTorch张量
        self.images = torch.from_numpy(self.images)
        self.labels = torch.from_numpy(self.labels)
    
    def __len__(self):
        # 返回数据集的大小(有多少张图片)
        return len(self.labels)
    
    def __getitem__(self, idx):
        # 根据索引返回一张图片和对应的标签
        return self.images[idx], self.labels[idx]

基础解释:

Dataset类就像是一个"数据管家",它知道如何存取数据

__init__:初始化,读取数据并做预处理

__len__:告诉别人我有多少数据

__getitem__:根据编号(idx)取出对应的数据

2.2 创建数据加载器

python 复制代码
# 设置批次大小:一次处理64张图片
batch_size = 64

# 创建训练集(注意:路径要改成你自己的)
train_dataset = MNISTDataset('mnist_train.csv')
train_loader = Data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)

# 创建测试集
test_dataset = MNISTDataset('mnist_test.csv')
test_loader = Data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

基础解释:

batch_size=64:一次处理64张图片,就像老师一次批改64份作业

shuffle=True:打乱顺序,让学习更全面

DataLoader:数据加载器,帮我们自动分批处理数据

第三部分:构建神经网络

3.1 定义网络结构并初始化参数

python 复制代码
# 定义网络结构:[输入层, 隐藏层1, 隐藏层2, 隐藏层3, 隐藏层4, 输出层]
layer_sizes = [28*28, 128, 128, 128, 64, 10]

# 存储权重和偏置的列表
weights = []  # 权重:决定信号的重要性
biases = []   # 偏置:调节神经元的激活程度

# 初始化每一层的参数
for in_size, out_size in zip(layer_sizes[:-1], layer_sizes[1:]):
    # 初始化权重:使用He初始化方法,让学习起步更稳定
    W = torch.randn(in_size, out_size, device=device) * torch.sqrt(torch.tensor(2.0 / in_size))
    
    # 初始化偏置:全部设为0
    b = torch.zeros(out_size, device=device)
    
    weights.append(W)
    biases.append(b)

基础解释:

layer_sizes:定义网络的"形状"

28*28=784:输入层,因为图片有784个像素

128, 128, 128, 64:隐藏层,可以提取特征

10:输出层,对应10个数字(0-9)

weights:权重矩阵,决定上一层每个信号对当前神经元的重要性

• ·biases·:偏置向量,让神经元更容易或更难被激活

3.2 前向传播函数

python 复制代码
def forward(x, weights, biases):
    """
    前向传播:输入数据通过网络得到预测结果
    x: 输入数据 [batch_size, 784]
    返回:每层的激活值和原始输出
    """
    activations = [x]  # 存储每层的激活值
    zs = []           # 存储每层的线性输出(未激活)
    
    # 遍历每一层(除了输出层)
    for W, b in zip(weights[:-1], biases[:-1]):
            z = activations[-1] @ W + b
            zs.append(z)
            a = torch.clamp(z, min=0)
            activations.append(a)
    
    # 输出层:线性变换
    z_last = activations[-1] @ weights[-1] + biases[-1]
    zs.append(z_last)
    
    # Softmax激活:将输出转换为概率分布
    # 技巧:先减去最大值,防止数值溢出
    exp_z = torch.exp(z_last - z_last.max(dim=1, keepdim=True).values)
    probs = exp_z / exp_z.sum(dim=1, keepdim=True)
    activations.append(probs)
    
    return activations, zs

基础解释:

  1. 线性变换:z = x * W + b
    • 就像把输入数据通过一个"过滤器"
  2. ReLU激活:f(z) = max(0, z)
    • 负值变0,正值保留
    • 给网络添加"非线性",让它能学习复杂模式
  3. Softmax:将最后10个输出变成概率
    • 所有概率和为1
    • 值最大的对应最可能的数字

3.3 交叉熵损失函数

python 复制代码
def cross_entropy(pred, labels):
    """
    计算交叉熵损失
    pred: 预测概率 [batch_size, 10]
    labels: 真实标签 [batch_size]
    返回:损失值和one-hot编码
    """
    N = pred.shape[0]  # 批次大小
    
    # 创建one-hot编码:将标签转换为10维向量
    # 例如:标签3 -> [0,0,0,1,0,0,0,0,0,0]
    one_hot = torch.zeros_like(pred)
    one_hot[torch.arange(N), labels] = 1
    # 计算损失:-Σ(y_true * log(y_pred)) / N
    # 加1e-8防止对0取对数
    loss = - (one_hot * torch.log(pred + 1e-8)).sum() / N
    
    return loss, one_hot

基础解释:

One-hot编码:把数字标签变成"密码锁"形式

交叉熵损失:衡量预测概率和真实标签的差距

预测越准,损失越小

预测越错,损失越大

第四部分:反向传播(最核心的部分)

4.1 手动计算梯度

python 复制代码
def backward(activations, zs, weights, one_hot):
    """
    反向传播:计算损失对每个参数的梯度
    activations: 每层的激活值
    zs: 每层的线性输出
    weights: 权重列表
    one_hot: 真实标签的one-hot编码
    返回:权重梯度和偏置梯度
    """
    num_layers = len(weights)
    batch_size = activations[0].shape[0]
    # 初始化梯度列表
    grad_w = [None] * num_layers
    grad_b = [None] * num_layers
    # 输出层的梯度
    # dz_last = pred - y_true
    dz = (activations[-1] - one_hot) / batch_size   # [batch_size, 10]
    
    # 计算输出层的梯度 activations[-2] (倒数第二层的输出,即输出层的输入):
    grad_w[-1] = activations[-2].t() @ dz  # 使用倒数第二层的激活值
    grad_b[-1] = dz.sum(dim=0)  # dL/db
    # 反向传播到隐藏层
    for l in range(num_layers - 2, -1, -1):
        # 计算当前层的梯度
        # dz = (下一层的dz * 下一层的W^T) * ReLU的导数
        dz_next = dz @ weights[l+1].T
        
        # ReLU的导数:如果z>0则为1,否则为0
        relu_derivative = (zs[l] > 0).float()
        dz = dz_next * relu_derivative
        
        # 计算当前层的权重和偏置梯度
        grad_w[l] =  activations[l].t() @ dz
        grad_b[l] = dz.sum(dim=0)
    
    return grad_w, grad_b

基础解释(这是最关键的部分):

反向传播就像"找错游戏":

  1. 发现问题:输出层预测错了(pred - y_true)
  2. 追溯责任:这个错误是谁造成的?
    • 是输出层的权重W不对?
    • 还是隐藏层的计算有问题?
  3. 链式法则:像多米诺骨牌一样,从后往前追溯
    • 输出层的错误 → 隐藏层3的错误 → 隐藏层2的错误 → ...
  4. 计算梯度:每个参数应该调整多少

4.2 更新参数

python 复制代码
def update_parameters(weights, biases, grad_w, grad_b, learning_rate):
    """
    更新参数:沿着梯度下降方向调整权重和偏置
    """
    for i in range(len(weights)):
        weights[i] = weights[i] - learning_rate * grad_w[i]
        biases[i] = biases[i] - learning_rate * grad_b[i]
    
    return weights, biases

基础解释:

梯度下降:沿着"下山"的方向走一小步

learning_rate=0.01:学习率,控制步伐大小

• 太大:可能跳过最低点

• 太小:学习太慢

第五部分:训练循环

5.1 训练函数

python 复制代码
def train_one_epoch(train_loader, weights, biases, learning_rate=0.01):
    """
    训练一个epoch:遍历所有训练数据一次
    """
    total_loss = 0
    total_samples = 0
    
    for batch_idx, (images, labels) in enumerate(train_loader):
        # 将数据移到指定设备
        images = images.to(device)
        labels = labels.to(device)
        
        # 1. 前向传播:得到预测
        activations, zs = forward(images, weights, biases)
        
        # 2. 计算损失
        loss, one_hot = cross_entropy(activations[-1], labels)
        total_loss += loss.item() * images.shape
        total_samples += images.shape
        
        # 3. 反向传播:计算梯度
        grad_w, grad_b = backward(activations, zs, weights, one_hot)
        
        # 4. 更新参数
        weights, biases = update_parameters(weights, biases, grad_w, grad_b, learning_rate)
        
        # 每100个批次打印一次进度
        if batch_idx % 100 == 0:
            print(f'  批次 [{batch_idx}/{len(train_loader)}], 损失: {loss.item():.4f}')
    
    # 计算平均损失
    avg_loss = total_loss / total_samples
    return avg_loss, weights, biases

基础解释:

一个epoch = 遍历所有训练数据一次

每个批次包含:

  1. 前向传播:猜答案
  2. 计算损失:判卷
  3. 反向传播:找错误原因
  4. 更新参数:改正错误

5.2 测试函数

python 复制代码
def evaluate(test_loader, weights, biases):
    """
    评估模型在测试集上的表现
    """
    correct = 0
    total = 0
    
    with torch.no_grad():  # 不需要计算梯度
        for images, labels in test_loader:
            images = images.to(device)
            labels = labels.to(device)
            
            # 前向传播
            activations, _ = forward(images, weights, biases)
            predictions = activations[-1]
            
            # 获取预测结果:概率最大的类别
            _, predicted = torch.max(predictions, 1)
            
            # 统计正确预测的数量
            correct += (predicted == labels).sum().item()
            total += labels.size(0)
    
    accuracy = 100 * correct / total
    return accuracy

基础解释:

• torch.no_grad():测试时不需要计算梯度,节省内存

• torch.max(predictions, 1):找出每个样本概率最大的类别

• 准确率 = 正确数 / 总数 × 100%

第六部分:主程序

6.1 完整训练流程

python 复制代码
def main():
    # 设置超参数
    learning_rate = 0.01
    num_epochs = 10  # 训练轮数
    
    print("开始训练神经网络...")
    print(f"网络结构: {layer_sizes}")
    print(f"学习率: {learning_rate}")
    print(f"训练轮数: {num_epochs}")
    print("-" * 50)
    
    # 训练循环
    for epoch in range(num_epochs):
        print(f'第 {epoch+1}/{num_epochs} 轮:')
        
        # 训练一个epoch
        avg_loss, weights, biases = train_one_epoch(
            train_loader, weights, biases, learning_rate
        )
        
        # 在测试集上评估
        accuracy = evaluate(test_loader, weights, biases)
        
        print(f'  平均损失: {avg_loss:.4f}, 测试准确率: {accuracy:.2f}%')
        print("-" * 50)
    
    # 最终评估
    final_accuracy = evaluate(test_loader, weights, biases)
    print(f"训练完成!最终测试准确率: {final_accuracy:.2f}%")
    
    return weights, biases

# 运行主程序
if __name__ == "__main__":
    trained_weights, trained_biases = main()

6.2 使用训练好的模型预测

python 复制代码
def predict_single_image(image_array, weights, biases):
    """
    预测单张图片
    image_array: 形状为[784]的numpy数组或PyTorch张量
    """
    if isinstance(image_array, np.ndarray):
        image_tensor = torch.from_numpy(image_array).float().to(device)
    else:
        image_tensor = image_array.float().to(device)
    
    # 添加批次维度:[784] -> [1, 784]
    image_tensor = image_tensor.unsqueeze(0)
    
    # 前向传播
    activations, _ = forward(image_tensor, weights, biases)
    probabilities = activations[-1][0]  # 取第一个(也是唯一一个)样本
    
    # 获取预测结果和置信度
    confidence, prediction = torch.max(probabilities, 0)
    
    return prediction.item(), confidence.item(), probabilities.detach().cpu().numpy()

# 示例:随机测试一张图片
def test_random_image(test_loader, weights, biases):
    # 获取一个批次
    images, labels = next(iter(test_loader))
    
    # 取第一张图片
    test_image = images
    true_label = labels[0].item()
    
    # 预测
    pred, confidence, probs = predict_single_image(test_image, weights, biases)
    
    print(f"真实标签: {true_label}")
    print(f"预测结果: {pred} (置信度: {confidence:.2%})")
    print(f"所有数字的概率: {probs}")
    
    if pred == true_label:
        print("✓ 预测正确!")
    else:
        print("✗ 预测错误!")
    
    return test_image, true_label, pred

第七部分:运行和结果

7.1 如何运行

  1. 准备数据:下载MNIST数据集(CSV格式)
  2. 修改路径:将代码中的文件路径改为你的实际路径
  3. 运行代码:执行主程序

7.2 预期输出

bash 复制代码
使用设备: cuda (或 cpu)
开始训练神经网络...
网络结构: [784, 128, 128, 128, 64, 10]
学习率: 0.01
训练轮数: 10
--------------------------------------------------
第 1/10 轮:
   批次 [0/938], 损失: 2.3026
   ...
   平均损失: 0.3456, 测试准确率: 91.23%
--------------------------------------------------
第 2/10 轮:
   平均损失: 0.1234, 测试准确率: 94.56%
--------------------------------------------------
...
第 10/10 轮:
   平均损失: 0.0456, 测试准确率: 97.34%
--------------------------------------------------
训练完成!最终测试准确率: 97.34%

7.3 关键要点总结

  1. 数据流:
    输入图片 → 线性变换 → ReLU激活 → ... → Softmax → 输出概率
  2. 学习过程:
    前向传播(猜) → 计算损失(判卷) → 反向传播(找错) → 更新参数(改正)
  3. 为什么能学习:
    • 通过反向传播找到错误原因
    • 通过梯度下降逐步改正错误
    • 每次迭代都更准确一点
  4. 手动实现的意义:
    • 理解每个参数如何影响结果
    • 理解梯度如何计算和传播
    • 不再把神经网络当作"黑箱"

完整code

python 复制代码
import torch
import torch.utils.data as Data
import numpy as np
import pandas as pd
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用设备: {device}")
class MNISTDataset(Data.Dataset):
    def __init__(self, csv_file):
        # 1. 读取CSV文件
        data = pd.read_csv(csv_file).values
        
        # 2. 分离标签和图像数据
        self.labels = data[:, 0]  # 第一列是标签(0-9)
        self.images = data[:, 1:]  # 后面784列是像素值
        
        # 3. 归一化处理:把0-255的像素值变成0-1之间的小数
        # 为什么要归一化?让所有数据在相同的尺度上,学习更稳定
        self.images = self.images.astype(np.float32) / 255.0
        
        # 4. 转换为PyTorch张量
        self.images = torch.from_numpy(self.images)
        self.labels = torch.from_numpy(self.labels)
    
    def __len__(self):
        # 返回数据集的大小(有多少张图片)
        return len(self.labels)
    
    def __getitem__(self, idx):
        # 根据索引返回一张图片和对应的标签
        return self.images[idx], self.labels[idx]
# 设置批次大小:一次处理64张图片
batch_size = 64

# 创建训练集(注意:路径要改成你自己的)
train_dataset = MNISTDataset('mnist_train.csv')
train_loader = Data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)

# 创建测试集
test_dataset = MNISTDataset('mnist_test.csv')
test_loader = Data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# 定义网络结构:[输入层, 隐藏层1, 隐藏层2, 隐藏层3, 隐藏层4, 输出层]
layer_sizes = [28*28, 128, 128, 128, 64, 10]

# 存储权重和偏置的列表
weights = []  # 权重:决定信号的重要性
biases = []   # 偏置:调节神经元的激活程度

# 初始化每一层的参数
for in_size, out_size in zip(layer_sizes[:-1], layer_sizes[1:]):
    # 初始化权重:使用He初始化方法,让学习起步更稳定
    W = torch.randn(in_size, out_size, device=device) * torch.sqrt(torch.tensor(2.0 / in_size))
    
    # 初始化偏置:全部设为0
    b = torch.zeros(out_size, device=device)
    
    weights.append(W)
    biases.append(b)


def forward(x, weights, biases):
    """
    前向传播:输入数据通过网络得到预测结果
    x: 输入数据 [batch_size, 784]
    返回:每层的激活值和原始输出
    """
    activations = [x]  # 存储每层的激活值
    zs = []           # 存储每层的线性输出(未激活)
    
    # 遍历每一层(除了输出层)
    for W, b in zip(weights[:-1], biases[:-1]):
            z = activations[-1] @ W + b
            zs.append(z)
            a = torch.clamp(z, min=0)
            activations.append(a)
    
    # 输出层:线性变换
    z_last = activations[-1] @ weights[-1] + biases[-1]
    zs.append(z_last)
    
    # Softmax激活:将输出转换为概率分布
    # 技巧:先减去最大值,防止数值溢出
    exp_z = torch.exp(z_last - z_last.max(dim=1, keepdim=True).values)
    probs = exp_z / exp_z.sum(dim=1, keepdim=True)
    activations.append(probs)
    
    return activations, zs
def cross_entropy(pred, labels):
    """
    计算交叉熵损失
    pred: 预测概率 [batch_size, 10]
    labels: 真实标签 [batch_size]
    返回:损失值和one-hot编码
    """
    N = pred.shape[0]  # 批次大小
    
    # 创建one-hot编码:将标签转换为10维向量
    # 例如:标签3 -> [0,0,0,1,0,0,0,0,0,0]
    one_hot = torch.zeros_like(pred)
    one_hot[torch.arange(N), labels] = 1
    # 计算损失:-Σ(y_true * log(y_pred)) / N
    # 加1e-8防止对0取对数
    loss = - (one_hot * torch.log(pred + 1e-8)).sum() / N
    
    return loss, one_hot
    
def backward(activations, zs, weights, one_hot):
    """
    反向传播:计算损失对每个参数的梯度
    activations: 每层的激活值
    zs: 每层的线性输出
    weights: 权重列表
    one_hot: 真实标签的one-hot编码
    返回:权重梯度和偏置梯度
    """
    num_layers = len(weights)
    batch_size = activations[0].shape[0]
    # 初始化梯度列表
    grad_w = [None] * num_layers
    grad_b = [None] * num_layers
    # 输出层的梯度
    # dz_last = pred - y_true
    dz = (activations[-1] - one_hot) / batch_size   # [batch_size, 10]
    
    # 计算输出层的梯度 activations[-2] (倒数第二层的输出,即输出层的输入):
    grad_w[-1] = activations[-2].t() @ dz  # 使用倒数第二层的激活值
    grad_b[-1] = dz.sum(dim=0)  # dL/db
    # 反向传播到隐藏层
    for l in range(num_layers - 2, -1, -1):
        # 计算当前层的梯度
        # dz = (下一层的dz * 下一层的W^T) * ReLU的导数
        dz_next = dz @ weights[l+1].T
        
        # ReLU的导数:如果z>0则为1,否则为0
        relu_derivative = (zs[l] > 0).float()
        dz = dz_next * relu_derivative
        
        # 计算当前层的权重和偏置梯度
        grad_w[l] =  activations[l].t() @ dz
        grad_b[l] = dz.sum(dim=0)
    
    return grad_w, grad_b

def train_one_epoch(train_loader, weights, biases, learning_rate=0.01):
    """
    训练一个epoch:遍历所有训练数据一次
    """
    total_loss = 0
    
    for batch_idx, (images, labels) in enumerate(train_loader):
        # 将数据移到指定设备
        images = images.to(device)
        labels = labels.to(device)
        
        # 1. 前向传播:得到预测
        activations, zs = forward(images, weights, biases)
        
        # 2. 计算损失
        loss, one_hot = cross_entropy(activations[-1], labels)
        total_loss += loss.item()

        # 3. 反向传播:计算梯度
        grad_w, grad_b = backward(activations, zs, weights, one_hot)
        # 4. 更新参数
        with torch.no_grad():
            for i in range(len(weights)):
                weights[i] -= learning_rate * grad_w[i]
                biases[i]  -= learning_rate * grad_b[i]
            
    # 计算平均损失
    avg_loss = total_loss / len(train_loader)
    print(f'  批次 [{batch_idx}/{len(train_loader)}], 损失: {avg_loss:.4f}')

    return avg_loss, weights, biases

def evaluate(test_loader, weights, biases):
    """
    评估模型在测试集上的表现
    """
    correct = 0
    total = 0
    
    with torch.no_grad():  # 不需要计算梯度
        for images, labels in test_loader:
            images = images.to(device)
            labels = labels.to(device)
            
            # 前向传播
            activations, _ = forward(images, weights, biases)
            predictions = activations[-1]
            
            # 获取预测结果:概率最大的类别
            _, predicted = torch.max(predictions, 1)
            
            # 统计正确预测的数量
            correct += (predicted == labels).sum().item()
            total += labels.size(0)
    
    accuracy = 100 * correct / total
    return accuracy

def main():
    # 设置超参数
    learning_rate = 0.01
    num_epochs = 10  # 训练轮数
    
    print("开始训练神经网络...")
    print(f"网络结构: {layer_sizes}")
    print(f"学习率: {learning_rate}")
    print(f"训练轮数: {num_epochs}")
    print("-" * 50)
    
    # 训练循环
    for epoch in range(num_epochs):
        print(f'第 {epoch+1}/{num_epochs} 轮:')
        # 训练一个epoch
        avg_loss, weights_, biases_ = train_one_epoch(
            train_loader, weights, biases, learning_rate
        )
        
        # 在测试集上评估
        accuracy = evaluate(test_loader, weights_, biases_)
        
        print(f'  平均损失: {avg_loss:.4f}, 测试准确率: {accuracy:.2f}%')
        print("-" * 50)
    
    # 最终评估
    final_accuracy = evaluate(test_loader, weights_, biases_)
    print(f"训练完成!最终测试准确率: {final_accuracy:.2f}%")
    
    return weights_, biases_
def predict_single_image(image_array, weights, biases):
    """
    预测单张图片
    image_array: 形状为[784]的numpy数组或PyTorch张量
    """
    if isinstance(image_array, np.ndarray):
        image_tensor = torch.from_numpy(image_array).float().to(device)
    else:
        image_tensor = image_array.float().to(device)
    
    # 添加批次维度:[784] -> [1, 784]
    image_tensor = image_tensor.unsqueeze(0)
    
    # 前向传播
    activations, _ = forward(image_tensor, weights, biases)
    probabilities = activations[-1][0]  # 取第一个(也是唯一一个)样本
    
    # 获取预测结果和置信度
    confidence, prediction = torch.max(probabilities, 0)
    
    return prediction.item(), confidence.item(), probabilities.detach().cpu().numpy()

# 示例:随机测试一张图片
def test_random_image(test_loader, weights, biases):
    # 获取一个批次
    images, labels = next(iter(test_loader))
    
    # 取第一张图片
    test_image = images[0]
    true_label = labels[0].item()
    
    # 预测图片标签
    pred, confidence, probs = predict_single_image(test_image, weights, biases)
    
    print(f"真实标签: {true_label}")
    print(f"预测结果: {pred} (置信度: {confidence:.2%})")
    print(f"所有数字的概率: {probs}")
    
    if pred == true_label:
        print("✓ 预测正确!")
    else:
        print("✗ 预测错误!")
    
    return test_image, true_label, pred
# 运行主程序
if __name__ == "__main__":
    trained_weights, trained_biases = main()
    test_random_image(test_loader, trained_weights, trained_biases)

PyTorch实现步骤

第一步:明确任务

  • 解决什么问题? → 手写数字识别(MNIST)
  • 输入是什么?输出是什么? → 输入:28×28像素图片;输出:0-9共10个类别
  • 用什么框架? → PyTorch

行动:创建一个新的Python文件,写下基本导入:

python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset

第二步:数据准备

思考数据流:

  • 数据在哪里?什么格式? → CSV文件,第一列是标签,后面784列是像素
  • 怎么读进来? → 自定义Dataset类
  • 需要预处理吗? → 归一化(/255),标准化(减均值除标准差)

行动:实现Dataset类:

python 复制代码
class MNISTDataset(Dataset):
    def __init__(self, file_path):
        # 1. 读取文件
        # 2. 分离图片和标签
        pass
    
    def __len__(self):
        # 返回数据总数
        pass
    
    def __getitem__(self, idx):
        # 1. 获取第idx个样本
        # 2. 转换为tensor
        # 3. 预处理(归一化)
        # 4. 返回(图片, 标签)
        pass

提示:先写框架,再填细节。可以从最简单的开始,比如先不归一化。

第三步:模型设计

思考架构:

  • 输入维度? → 28×28=784
  • 输出维度? → 10
  • 中间结构? → 从简单开始:784→128→64→10
  • 激活函数? → ReLU(简单有效)

行动:定义模型类:

python 复制代码
class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        # 设计层结构
        self.layers = nn.Sequential(
            nn.Linear(784, 128),
            nn.ReLU(),
            nn.Linear(128, 64),
            nn.ReLU(),
            nn.Linear(64, 10)
        )
    
    def forward(self, x):
        return self.layers(x)

关键:先实现最简单的可行版本。不要一开始就设计复杂网络。

第四步:训练配置(5分钟)

选择超参数和组件:

批量大小 → 32或64(中等大小)

学习率 → 0.01或0.001(从小开始)

训练轮数 → 5或10(先少一点,看效果)

损失函数 → nn.CrossEntropyLoss()(多分类标准选择)

优化器 → optim.Adam()(比SGD更友好)或optim.SGD()

行动:

python 复制代码
# 超参数
batch_size = 64
learning_rate = 0.001
epochs = 5

# 设备选择
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 初始化
model = NeuralNetwork().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

第五步:训练循环

构建训练流程:

  • 外层循环:控制训练轮数
  • 内层循环:遍历每个批次
  • 四个核心步骤:前向传播→计算损失→反向传播→更新参数

行动:写出训练骨架:

python 复制代码
model.train()  # 设置为训练模式
for epoch in range(epochs):
    total_loss = 0
    
    for images, labels in train_loader:  # 假设train_loader已定义
        # 1. 数据移到设备
        images, labels = images.to(device), labels.to(device)
        
        # 2. 前向传播
        outputs = model(images)
        
        # 3. 计算损失
        loss = criterion(outputs, labels)
        
        # 4. 反向传播
        optimizer.zero_grad()  # 清空梯度
        loss.backward()        # 计算梯度
        
        # 5. 更新参数
        optimizer.step()
        
        total_loss += loss.item()
    
    print(f"Epoch {epoch+1}, Loss: {total_loss/len(train_loader):.4f}")

第六步:测试评估(10分钟)

思考测试与训练的不同:

  • 不需要梯度计算 → 用torch.no_grad()
  • 不需要参数更新 → 只做前向传播
  • 如何评估? → 计算准确率

行动:

python 复制代码
model.eval()  # 设置为评估模式
correct = 0
total = 0

with torch.no_grad():  # 禁用梯度计算
    for images, labels in test_loader:
        images, labels = images.to(device), labels.to(device)
        outputs = model(images)
        
        # 获取预测结果(最大值的索引)
        _, predicted = torch.max(outputs.data, 1)
        
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print(f"Accuracy: {100 * correct / total:.2f}%")

第七步:整合调试(10分钟)

  • 把各部分连接起来:
  • 创建数据加载器
  • 按顺序调用各部分
  • 运行并观察输出

完整结构:

t 复制代码
# 1. 导入
# 2. 数据集类
# 3. 模型类
# 4. 超参数设置
# 5. 数据加载器创建
# 6. 模型初始化
# 7. 训练循环
# 8. 测试评估

迭代改进的路径

复制代码
简单模型 → 跑通流程 → 增加复杂度 → 调参优化 → 添加功能
   ↓           ↓           ↓           ↓           ↓
784→10     能训练      加隐藏层    调学习率    加正则化
          能测试      加激活函数  调批量大小  加学习率调度
相关推荐
冬奇Lab2 小时前
一天一个开源项目(第78篇):MiroFish - 用群体智能引擎预测未来
人工智能·开源·资讯
冬奇Lab2 小时前
你的 Skill 真的好用吗?来自OpenAI的 Eval 系统化验证 Agent 技能方法论
人工智能·openai
数智工坊2 小时前
Transformer 全套逻辑:公式推导 + 原理解剖 + 逐行精读 - 划时代封神之作!
人工智能·深度学习·transformer
GreenTea3 小时前
AI 时代,工程师的不可替代性在哪里
前端·人工智能·后端
小程故事多_803 小时前
破除迷思,Harness Engineering从来都不是时代过渡品
人工智能·架构·prompt·aigc
热爱专研AI的学妹3 小时前
Seedance 2.0(即梦 2.0)深度解析:AI 视频正式迈入导演级精准可控时代
大数据·人工智能·阿里云·音视频
Ulyanov4 小时前
用Pyglet打造AI数字猎人:从零开始的Python游戏开发与强化学习实践
开发语言·人工智能·python
lcj09246664 小时前
磁控U位管理系统与DCIM对接实现:筑牢数据中心精细化运维底座
大数据·数据库·人工智能
swipe4 小时前
用 Nest + LangChain 打造 OpenClaw 式 Agent 定时任务系统
人工智能·llm·agent