深度学习入门——基于多层感知机的MNIST手写数字识别

种一棵树最好的时间是十年前,其次是现在。

目录

 

前言

一、数据准备

二、构建模型

三、模型精度检验


前言

最近又空闲下来,终于有时间把之前荒废的学习计划给重拾起来了!今天做的是MNIST手写数字识别项目。这可以说是深度学习的“Hello World”级项目了。在AI的帮助下,也是成功的完成了这个项目。记录下来,其中如有不正确的地方,欢迎指正。


一、数据准备

做项目最重要的是什么?数据!因此,我们首先把数据准备好。

我使用的框架是Pytorch,在Pytorch中有现成的方法直接下载。直接通过 torchvision.datasets 模块提供的接口完成。首先需要安装torchvision。

pip install torchvision

下载数据,代码如下。运行后会直接下载到data文件夹,如果没有会直接在当前文件路径新建一个。

from torchvision import datasets, transforms

# 定义数据预处理(这里仅做归一化,将像素值从 [0,255] 转为 [0,1])
transform = transforms.Compose([
    transforms.ToTensor(),  # 转为 PyTorch 张量(形状:[1,28,28])
    transforms.Normalize((0.1307,), (0.3081,))  # MNIST 全局均值和标准差(经验值)
])

# 下载训练集(6万张图)
train_dataset = datasets.MNIST(
    root='./data',  # 数据集存储路径(当前目录下的 data 文件夹)
    train=True,     # 是否为训练集(True:训练集,False:测试集)
    download=True,  # 若本地无数据则下载
    transform=transform  # 应用预处理
)

# 下载测试集(1万张图)
test_dataset = datasets.MNIST(
    root='./data',
    train=False,
    download=True,
    transform=transform
)

当然,如果使用的是tensorflow框架的话,也是有现成的方法,但是tensorflow使用起来要比Pytorch稍微难上手一点。除此之外,也可以选择直接去官方网站下载

下载完后,我们查看数据集大小,以及对各数字类别分布做一个统计,这样做的目的是为了对这个数据集有更多的了解。机器学习非常依赖数据,所以在进行模型训练前,我们应该对数据集有尽可能多的了解。代码及运行结果如下。

from torch.utils.data import DataLoader

train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)  # 训练集批量加载(打乱顺序)
test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False)   # 测试集批量加载(不打乱)
# 训练集和测试集的图片数量
print(f"训练集图片数量: {len(train_dataset)} 张")  # 输出:60000 张
print(f"测试集图片数量: {len(test_dataset)} 张")   # 输出:10000 张
import numpy as np

# 统计训练集标签分布
train_labels = [label for _, label in train_dataset]
train_label_counts = np.bincount(train_labels)  # 统计0-9每个数字的出现次数

# 统计测试集标签分布
test_labels = [label for _, label in test_dataset]
test_label_counts = np.bincount(test_labels)

# 绘制柱状图
plt.figure(figsize=(12, 5))

# 训练集子图
plt.subplot(1, 2, 1)
plt.bar(range(10), train_label_counts)
plt.title("distribution of categories in train set")
plt.xlabel("label")
plt.ylabel("number")

# 测试集子图
plt.subplot(1, 2, 2)
plt.bar(range(10), test_label_counts)
plt.title("distribution of categories in test set")
plt.xlabel("label")
plt.ylabel("number")

plt.tight_layout()
plt.show()

二、构建模型

导入相关库

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

构建模型,我这里选择搭建了一个三层全连通感知机模型。

class ThreeLayerPerceptron(nn.Module):
    def __init__(self, input_dim, hidden_dim1, hidden_dim2, output_dim):
        super(ThreeLayerPerceptron, self).__init__()
        # 第一层全连接:输入层 -> 隐藏层1
        self.fc1 = nn.Linear(input_dim, hidden_dim1)
        # 第二层全连接:隐藏层1 -> 隐藏层2
        self.fc2 = nn.Linear(hidden_dim1, hidden_dim2)
        # 第三层全连接:隐藏层2 -> 输出层
        self.fc3 = nn.Linear(hidden_dim2, output_dim)
        
    def forward(self, x):
        # 输入数据展平(如果是图像等多维输入需要此操作)
        x = x.view(x.size(0), -1)
        # 第一层:线性变换 + ReLU激活
        x = F.relu(self.fc1(x))
        # 第二层:线性变换 + ReLU激活
        x = F.relu(self.fc2(x))
        # 第三层:线性变换(输出层通常不接激活函数,用于分类时后续接softmax)
        x = self.fc3(x)
        return x

进行模型训练。我们这里是训练了5个epoch,意味着整个数据集经历了五次前向传播和反向传播。其实迭代很少了,但是这个任务比较简单,所以虽然只是经过了简单的训练,但是最后的效果还行。

# 模型参数(以MNIST为例)
input_dim = 784       # 28x28图像展平后的维度
hidden_dim1 = 256     # 第一个隐藏层神经元数
hidden_dim2 = 128     # 第二个隐藏层神经元数
output_dim = 10       # 10类数字

# 初始化模型(自动适配CPU/GPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = ThreeLayerPerceptron(input_dim, hidden_dim1, hidden_dim2, output_dim).to(device)

# 定义损失函数(分类任务用交叉熵)和优化器(Adam)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
def train_model(model, train_loader, criterion, optimizer, epochs=10):
    model.train()  # 切换训练模式(启用Dropout等)
    for epoch in range(epochs):
        running_loss = 0.0
        correct = 0
        total = 0
        
        for batch_idx, (images, labels) in enumerate(train_loader):
            # 数据移动到目标设备(CPU/GPU)
            images, labels = images.to(device), labels.to(device)
            
            # 前向传播 + 计算损失
            outputs = model(images)
            loss = criterion(outputs, labels)
            
            # 反向传播 + 优化参数
            optimizer.zero_grad()  # 清空梯度
            loss.backward()        # 反向传播
            optimizer.step()       # 更新参数
            
            # 统计训练指标
            running_loss += loss.item()
            _, predicted = torch.max(outputs.data, 1)  # 取概率最大的类别
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
            
            # 每100个批量打印一次进度
            if (batch_idx+1) % 100 == 0:
                print(f"Epoch [{epoch+1}/{epochs}], Batch [{batch_idx+1}/{len(train_loader)}], "
                      f"Loss: {running_loss/100:.4f}, Acc: {100*correct/total:.2f}%")
                running_loss = 0.0  # 重置累计损失

    print("训练完成!")

# 开始训练(建议先试3-5轮,观察准确率是否提升)
train_model(model, train_loader, criterion, optimizer, epochs=5)

三、模型精度检验

测试集精度验证

ef test_model(model, test_loader):
    model.eval()  # 切换测试模式(禁用Dropout等)
    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"测试准确率: {100 * correct / total:.2f}%")

# 运行测试
test_model(model, test_loader)

测试集准确率为0.9762,结合之前的训练集准确率为0.9876,可以看到效果还是不错的。

接下来进行混淆矩阵热图可视化。几乎都集中在对角线,模型性能不错。

from sklearn.metrics import confusion_matrix
import seaborn as sns

def plot_confusion_matrix(model, test_loader):
    model.eval()
    all_labels = []
    all_preds = []
    
    with torch.no_grad():
        for images, labels in test_loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            _, preds = torch.max(outputs, 1)
            all_labels.extend(labels.cpu().numpy())
            all_preds.extend(preds.cpu().numpy())
    
    # 计算混淆矩阵
    cm = confusion_matrix(all_labels, all_preds)
    
    # 可视化
    plt.figure(figsize=(10, 8))
    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', 
                xticklabels=range(10), yticklabels=range(10))
    plt.xlabel('predicted')
    plt.ylabel('true')
    plt.title('Confusion Matrix')
    plt.show()

# 调用函数(需已定义model和test_loader)
plot_confusion_matrix(model, test_loader)

错误样本可视化,展示模型分类错误的样本,分析误分类原因

def plot_wrong_samples(model, test_loader, num_samples=9):
    model.eval()
    wrong_images = []
    wrong_labels = []
    wrong_preds = []
    
    with torch.no_grad():
        for images, labels in test_loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            _, preds = torch.max(outputs, 1)
            # 筛选错误样本
            mask = (preds != labels)
            if mask.any():
                wrong_images.extend(images[mask].cpu())
                wrong_labels.extend(labels[mask].cpu().numpy())
                wrong_preds.extend(preds[mask].cpu().numpy())
            
            if len(wrong_images) >= num_samples:
                break
    
    # 可视化前9个错误样本
    plt.figure(figsize=(12, 12))
    for i in range(num_samples):
        image = wrong_images[i].squeeze()  # 移除通道维度
        true_label = wrong_labels[i]
        pred_label = wrong_preds[i]
        
        plt.subplot(3, 3, i+1)
        plt.imshow(image, cmap='gray')
        plt.title(f'true: {true_label}, :pred {pred_label}', color='red')
        plt.axis('off')
    
    plt.tight_layout()
    plt.show()

结果如下:

计算类别级准确率,查看每个类别的分类准确率。

def plot_class_accuracy(model, test_loader):
    model.eval()
    class_correct = [0] * 10
    class_total = [0] * 10
    
    with torch.no_grad():
        for images, labels in test_loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            _, preds = torch.max(outputs, 1)
            for label, pred in zip(labels, preds):
                if label == pred:
                    class_correct[label] += 1
                class_total[label] += 1
    
    # 计算每个类别的准确率
    class_acc = [100 * class_correct[i]/class_total[i] for i in range(10)]
    
    # 绘制柱状图
    plt.figure(figsize=(10, 6))
    plt.bar(range(10), class_acc)
    plt.xticks(range(10))
    plt.xlabel('label')
    plt.ylabel('accuracy(%)')
    # plt.title('各类别分类准确率')
    plt.ylim(80, 100)  # MNIST模型通常准确率较高,调整Y轴范围
    plt.show()

# 调用函数
plot_class_accuracy(model, test_loader)

其实从几个样本的结果,还有柱状图,可以看出模型对9这个数字的识别明显不如其他类别。

 


 

 

### 置换流水车间调度的定义 置换流水车间调度问题(Permutation Flow Shop Scheduling Problem, PFSP)是一种经典的车间调度问题,其中工件按照固定的顺序依次通过各个机器进行加工[^1]。在这种模式下,所有工件都遵循相同的加工顺序,即如果某个工件先于另一个工件在第一台机器上加工,则它也必须先于该工件在后续的所有机器上加工。 PFSP的目标通常是优化某些性能指标,例如最小化总完工时间(makespan)、平均流程时间或其他成本函数[^4]。为了达到这些目标,研究者们提出了多种启发式算法和元启发式算法,如遗传算法、模拟退火算法以及金枪鱼群优化算法等[^3]。 --- ### 应用场景分析 #### 制造业中的典型应用 PFSP广泛应用于制造业领域,特别是在那些需要连续生产和严格工艺路径控制的情况下。例如,在汽车制造装配线中,零部件可能需要经过焊接、喷漆等多个工序,而每个零件都需要按照特定的顺序流经这些工作站[^5]。这种情况下,合理安排工件的处理次序对于减少等待时间和提高生产线效率至关重要。 #### 多工厂协同生产环境下的扩展——分布式PFSP (DPFSP) 随着现代工业向全球化方向发展,单一厂房内的简单PFSP逐渐演变为涉及多个地理位置分布的不同生产车间的情况,这就是所谓的分布式置换流水车间调度问题(Distributed Permutation Flow Shop Scheduling Problem, DPFSP)。这类问题更加复杂,因为它不仅考虑单个工厂内部的操作序列规划,还需要兼顾不同地点之间物流运输等因素的影响。 --- ### 技术实现与案例探讨 针对上述提到的各种实际需求,研究人员开发出了许多有效的解决方案和技术手段: - **遗传算法的应用** 在解决PFSP时,可以通过构建染色体结构来表示潜在解空间,并利用交叉变异操作探索更优可能性;同时设定合理的适应度评价标准以衡量候选方案的好坏程度。 - **其他先进方法** - TSO(金枪鱼群优化): 这种新颖的方法模仿自然界生物行为规律寻找全局最优点,特别适合应对高维度复杂的组合优化难题。 以下是基于Python的一个简化版GA框架用于求解基本形式的PFSP实例: ```python import random from deap import base, creator, tools, algorithms # 参数设置 NUM_JOBS = 10 MACHINE_COUNT = 5 POP_SIZE = 50 GENS = 100 creator.create("FitnessMin", base.Fitness, weights=(-1.0,)) creator.create("Individual", list, fitness=creator.FitnessMin) toolbox = base.Toolbox() toolbox.register("indices", random.sample, range(NUM_JOBS), NUM_JOBS) toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.indices) toolbox.register("population", tools.initRepeat, list, toolbox.individual) def evalMakespan(individual): """计算给定个体对应的总工期""" schedule = [[0]*MACHINE_COUNT for _ in range(NUM_JOBS)] makespan = 0 # 初始化第一个job的时间表 job_idx = individual[0] for m in range(MACHINE_COUNT): processing_time = random.randint(1,9) # 假设随机生成加工时间 if m == 0: schedule[job_idx][m] += processing_time else: schedule[job_idx][m] = max(schedule[job_idx][m-1], makespan)+processing_time makespan = max(makespan,schedule[job_idx][m]) # 继续填充其余jobs... for i in range(1,len(individual)): prev_job = individual[i-1] curr_job = individual[i] for m in range(MACHINE_COUNT): pt = random.randint(1,9) start_t = max( schedule[curr_job][m-1] if m>0 else 0, schedule[prev_job][m]+pt if m<MACHINE_COUNT-1 else makespan ) end_t = start_t + pt schedule[curr_job][m]=end_t makespan=max(end_t,makespan) return makespan, toolbox.register("evaluate", evalMakespan) toolbox.register("mate", tools.cxOrdered) toolbox.register("mutate", tools.mutShuffleIndexes, indpb=0.05) toolbox.register("select", tools.selTournament, tournsize=3) pop = toolbox.population(n=POP_SIZE) result_pop, logbook = algorithms.eaSimple(pop, toolbox, cxpb=0.7, mutpb=0.2, ngen=GENS, verbose=False) best_ind = tools.selBest(result_pop, k=1)[0] print(f"最佳排序:{best_ind}, 总工期={evalMakespan(best_ind)}") ``` 此脚本展示了如何运用进化策略去尝试发现较佳的工作排列从而降低整个系统的完成周期长度。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值