当前位置: 首页 > news >正文

[AI]从0到1通过神经网络训练模型

[AI]从0到1通过神经网络训练模型

本文主要简单讲述神经网络训练的原理。
本文代码参考:https://github.com/xhh890921/mnist_network

前置知识

在人工智能发展的早期,人们普遍认为世界上的万事万物都可以通过数学公式来描述和预测。例如,直角三角形的第三边长度可以通过勾股定理精确计算出来。这种方法在解决简单问题时非常有效,比如计算物理运动轨迹或简单的数据关系。

然而,当面对复杂的现实问题时,比如让计算机识别一张图片中的物体或者判断一段文字的情感倾向,传统的基于公式的思路就显得力不从心了。因为这些问题往往涉及大量的变量和非线性关系,很难用一个明确的数学公式来表达。

于是,机器学习应运而生。它不再依赖于人工设计的复杂规则,而是通过大量数据来“训练”模型,让模型自己去发现其中的规律。深度学习则是机器学习的一个分支,它通过构建多层神经网络,模拟人脑处理信息的方式,从而能够处理更加复杂的任务,如图像识别、语音识别和自然语言处理等。

本文将训练一个三层的神经网络,用于识别图像中的数字,并对其进行分类。其核心思想仍然是寻找一个函数——这个函数能够将输入(图像)映射到正确的输出(数字类别)。虽然我们无法直接写出这个函数的具体形式,但通过训练过程,我们可以不断调整神经网络的参数,使得这个函数越来越接近真实的规律。

展平
28x28图像
784输入层
256隐藏层+ReLU
10输出层
Softmax
预测结果

维度变化:

输入张量: [batch_size, 1, 28, 28]
↓ 展平
[batch_size, 784]
↓ 隐藏层处理
[batch_size, 256]
↓ 输出层处理
[batch_size, 10]

核心组件解释

在这里插入图片描述

1. 输入层:数据预处理

  • 功能:将二维像素矩阵转换为特征向量
  • 维度变化:28×28 → 784
  • 相当于784个神经元接收像素值

2. 隐藏层:特征提取

  • 功能:精简向量,用256个神经元来表示图像信息
  • 维度变化:784 -> 256
  • 相当于用256个神经元接受并表示输入的图像数据

3. 输出层:决策生成

  • 功能:将256个神经元与10个结果神经元对应上,10个神经元中,每一个代表一个结果数字(0-9)
  • 维度变化:256 -> 10
  • 相当于将图像数据与0-9对应上,再由softmax,评估每个结果可能(如:这里的数字0-9)的概率。最后由输出层返回概率最高的结果项,作为最终结果。

项目流程

原理速递

  1. 输入层(数据预处理):我们的图片大小是 28x28,表示一张灰度图像,共有 28 行、28 列像素。每个像素值是一个数字(0~255),代表该点的亮度。在进入神经网络之前,这张图会被展平成一个一维向量:
    在这里插入图片描述
# 展平后变成长度为 784 的向量,每个元素对应一个像素点的数值。
# ✅ 类比:你可以把这一步想象成 “信息采集”,就像有 784 名登记员,每人负责记录一个像素点的信息。
x = x.view(-1, 28 * 28)  # [1, 28, 28] → [1, 784]

在这里插入图片描述
2. 隐藏层:特征提取与压缩。接下来,这个 784 维的向量会输入到第一个线性层:

  x = self.layer1(x)  # Linear(784, 256)

这一层的功能是:学习图像中重要的特征,并将其压缩成 256 个更有意义的数值。这些数值并不是像素本身,而是图像中的某些模式,比如边缘、角点、曲线等。然后通过激活函数 ReLU 增加非线性能力:

# ReLU 的作用是让模型能够识别更复杂的图案,而不是只能识别直线关系。
# ✅ 类比:你可以把这一步想象成 “工程师分析报告”,784 名登记员的数据被整理成 256 份关键报告,由工程师提炼出有用信息。
x = torch.relu(x)

在这里插入图片描述
3. 输出层:决策生成
接下来,这 256 个特征值会被输入到第二个线性层:

  x = self.layer2(x)  # Linear(256, 10)

这一步将这些 256 个特征映射到 10 个结果上,分别代表数字 0~9。
输出是一个长度为 10 的向量,例如:

  [ -3.1, 0.2, 4.5, ..., 1.2 ]

每个位置上的数值可以理解为该数字的“得分”。
最终,我们选择得分最高的那个数字作为预测结果:

  predicted_label = torch.argmax(x)

✅ 类比:你可以把这一步想象成 “评审团打分”,10 位评审专家根据工程师提供的 256 条信息,分别给自己的数字打分,最后选分数最高的数字作为最终预测结果。
在这里插入图片描述
完整流程:
假设输入是一张数字“3”的图片

# 输入图像形状
[1, 28, 28] → 展平后变成 [1, 784]# 输入隐藏层
经过 layer1 → [1, 256] (256 个特征)# 输入输出层
经过 layer2 → [1, 10] (10 个数字的得分)# 最终结果
torch.argmax([1, 10]) → 得到预测的数字标签,如 3

在这里插入图片描述

1. 环境准备

uv venv --python 3.11# 初始化虚拟环境配置
source .venv/bin/activate# 安装python依赖,如:
uv pip install torch
uv pip install torchvision

在这里插入图片描述

2. 数据下载

原理

运行download_data.py,下载需要训练、验证所用的图片
在这里插入图片描述

下载后,会新增两个目录,一个是测试数据,一个是训练数据
在这里插入图片描述

两个目录下面都是数字图片:
在这里插入图片描述

代码

download_data.py

# 这个程序的功能会先将MNIST数据下载下来,然后再保存为.png的格式。from torchvision.datasets import MNIST
from torchvision.transforms import ToTensor# 注意,这里得到的train_data和test_data已经直接可以用于训练了!
# 不一定要继续后面的保存图像。
train_data = MNIST(root='./data', train=True, download=True, transform=ToTensor())
test_data = MNIST(root='./data', train=False, download=True, transform=ToTensor())# 继续将手写数字图像“保存”下来
# 输出两个文件夹,train和test,分别保存训练和测试数据。
# train和test文件夹中,分别有0、1、2、3、4、5、6、7、8、9;
# 这10个子文件夹保存每种数字的数据from torchvision.transforms import ToPILImagetrain_data = [(ToPILImage()(img), label) for img, label in train_data]
test_data = [(ToPILImage()(img), label) for img, label in test_data]import os
import secrets
def save_images(dataset, folder_name):root_dir = os.path.join('./train_images', folder_name)os.makedirs(root_dir, exist_ok=True)for i in range(len(dataset)):img, label = dataset[i]label_dir = os.path.join(root_dir, str(label))os.makedirs(label_dir, exist_ok=True)random_filename = secrets.token_hex(8) + '.png'img.save(os.path.join(label_dir, random_filename))save_images(train_data, 'train')
save_images(test_data, 'test')

3. 模型训练

原理

模型训练这里包含两部分model.py和train.py。
model.py:定义模型结构,实现核心能力,对输入的图片进行识别。
train.py:读取train_images/train目录下的png图片,喂给模型,进行训练。(模型会读取每张图片,然后转换为向量,提取特征向量,最后输出结果,然后与我们实际结果进行对比,计算损失值,反馈给模型)

1.model.py中定义模型转换能力,实现输入层、隐藏层。

模型主要包含两个线性层和一个ReLU激活函数,能够将一张手写数字图片(28x28像素)分类为0~9中的一个数字。
Q:为什么要把图像展平?
A:图像通常以二维结构(如28x28)保存,但在全连接神经网络中,每个神经元都要与所有输入相连。所以我们需要使用 x.view(-1, 28*28) 将图像“压平”成一个一维向量(长度为784),以便输入到线性层中。
Q:线性层的本质是什么?
A:每个线性层本质上是一个矩阵乘法加上偏置项: y = W ⋅ x + b y = W \cdot x + b y=Wx+b
这些参数(W 和 b)会在训练过程中不断调整,使得模型能更好地区分不同的数字。
其实模型训练就是为了训练一个复杂的函数,我们的输入被转换为向量,然后通过函数的计算,进行输出,通过不断的训练我们需要让输出更接近真实值。

两个线性层:
在这里插入图片描述
一个ReLU激活函数:
在这里插入图片描述
运行train.py,训练模型,模型会读取对应目录下的图片,比如下面这张图片。
在这里插入图片描述
然后模型会将该图片展平,将其转换为784个向量传给第一个线性层。

# 将图像展平
x = x.view(-1, 28 * 28) # 使用view函数,将x展平
x = self.layer1(x)  # 将x输入至layer1

第一个线性层会将784个向量进行特征提取,并用256个向量来表示其结果,将输出的这256个中间向量通过ReLU激活后,传递给第二个线性层

# 线性层1,输入层和隐藏层之间的线性层
self.layer1 = nn.Linear(784, 256)
x = torch.relu(x)  # 使用relu激活
self.layer2(x) # 输入至layer2计算结果

第二个线性层会将接受到的256个向量,映射到10个结果向量中。这10个结果向量分别对应我们的结果数字0-9。

# 线性层2,隐藏层和输出层之间的线性层
self.layer2 = nn.Linear(256, 10)

2.train.py中实现模型的训练,定义训练的轮数等。

主要实现将模型预测值与真实结果进行差值计算,通过损失函数计算其损失值(损失函数可代表模型的准度,损失值越大,模型越不准,效果就越差),然后根据损失函数动态调整训练参数,这种由输出结果倒逼输入的情况也就是所谓的反向转播。
在这里插入图片描述
效果:
在这里插入图片描述

代码

train.py

import torch
from torch import nn
from torch import optimfrom model import Networkfrom torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoaderif __name__ == '__main__':# 图像的预处理transform = transforms.Compose([transforms.Grayscale(num_output_channels=1),  # 转换为单通道灰度图transforms.ToTensor()  # 转换为张量])# 读入并构造数据集train_dataset = datasets.ImageFolder(root='./train_images/train', transform=transform)print("train_dataset length: ", len(train_dataset))# 小批量的数据读入train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)print("train_loader length: ", len(train_loader))model = Network()  # 模型本身,它就是我们设计的神经网络optimizer = optim.Adam(model.parameters())  # 优化模型中的参数criterion = nn.CrossEntropyLoss()  # 分类问题,使用交叉熵损失误差# 进入模型的迭代循环for epoch in range(10):  # 外层循环,代表了整个训练数据集的遍历次数# 整个训练集要循环多少轮,是10次、20次或者100次都是可能的,# 内存循环使用train_loader,进行小批量的数据读取for batch_idx, (data, label) in enumerate(train_loader):# 内层每循环一次,就会进行一次梯度下降算法# 包括了5个步骤:output = model(data) # 1.计算神经网络的前向传播结果loss = criterion(output, label) # 2.计算output和标签label之间的损失lossloss.backward()  # 3.使用backward计算梯度optimizer.step()  # 4.使用optimizer.step更新参数optimizer.zero_grad()  # 5.将梯度清零# 这5个步骤,是使用pytorch框架训练模型的定式,初学的时候,先记住就可以了# 每迭代100个小批量,就打印一次模型的损失,观察训练的过程if batch_idx % 100 == 0:print(f"Epoch {epoch + 1}/10 "f"| Batch {batch_idx}/{len(train_loader)} "f"| Loss: {loss.item():.4f}")torch.save(model.state_dict(), 'mnist.pth') # 保存模型

4. 模型评测

原理

test.py主要实现了读取test目录下的图片用于验证结果,将test目录下的图片一张张喂给模型,然后判定模型预测结果与真实值是否一致,如果不一致则打印错误❌结果,最后输出模型准确率。

运行test.py,验证模型训练效果
在这里插入图片描述
效果:
在这里插入图片描述

代码
from model import Network
from torchvision import transforms
from torchvision import datasets
import torchif __name__ == '__main__':transform = transforms.Compose([transforms.Grayscale(num_output_channels=1),transforms.ToTensor()])# 读取测试数据集test_dataset = datasets.ImageFolder(root='./train_images/test', transform=transform)print("test_dataset length: ", len(test_dataset))model = Network()  # 定义神经网络模型model.load_state_dict(torch.load('mnist.pth')) # 加载刚刚训练好的模型文件right = 0 # 保存正确识别的数量for i, (x, y) in enumerate(test_dataset):output = model(x)  # 将其中的数据x输入到模型predict = output.argmax(1).item() # 选择概率最大标签的作为预测结果# 对比预测值predict和真实标签yif predict == y:right += 1else:# 将识别错误的样例打印了出来img_path = test_dataset.samples[i][0]print(f"wrong case: predict = {predict} y = {y} img_path = {img_path}")# 计算出测试效果sample_num = len(test_dataset)acc = right * 1.0 / sample_numprint("test accuracy = %d / %d = %.3lf" % (right, sample_num, acc))

参考项目:https://github.com/xhh890921/mnist_network

http://www.lryc.cn/news/575790.html

相关文章:

  • 128K 长文本处理实战:腾讯混元 + 云函数 SCF 构建 PDF 摘要生成器
  • C++智能指针概念及std::unique_ptr使用介绍
  • 【办公类-105-01】20250626 托小班报名表-条件格式-判断双胞胎EXCLE
  • CNN不是一个模型?
  • 跨越十年的C++演进:C++14新特性全解析
  • C++(模板与容器)
  • Java--程序控制结构(下)
  • springcloud 尚硅谷 看到9开头
  • NebulaGraph 图数据库介绍
  • 一分钟了解Transformer
  • 缓存与加速技术实践-MongoDB数据库应用
  • AI+时代已至|AI人才到底该如何培育?
  • Python打卡:Day37
  • 快速傅里叶变换(FFT)是什么?
  • 4.2_1朴素模式匹配算法
  • Webshell工具的流量特征分析(菜刀,蚁剑,冰蝎,哥斯拉)
  • LeetCode 2302.统计得分小于K的子数组数目
  • 力扣第45题-跳跃游戏2
  • [mcp-servers] docs | AI客户端-MCP服务器-AI 架构
  • linux cp与mv那个更可靠
  • 浅析阿拉伯语OCR技术的核心难点及其应用场景
  • LeetCode 2311.小于等于 K 的最长二进制子序列:贪心(先选0再选1)-好像还是比灵神写的清晰些
  • 996引擎-假人系统
  • VUE3入门很简单(3)--- watch
  • 重塑音视频叙事:Premiere文本剪辑与Podcast AI降噪的革命性工作流
  • 解决 “docker-compose: command not found“ 错误
  • C2远控篇CC++SC转换格式UUID标识MAC物理IPV4地址减少熵值
  • Selenium+Pytest自动化测试框架实战
  • 玄机抽奖Spring Web项目
  • MySQL5.7和8.0 破解root密码