从零开始训练一个自己的 BP 神经网络(原理+python代码)

目录

学习资料如下:

原理简介:

单层神经网络

BP 神经网络

实例

数据获取

定义网络

网络结构:

代码:

训练模型

测试模型


本文内容参考了多篇相关文章,旨在进行学习与总结。如涉及版权问题,请作者及时联系删除,本人将不胜感激并深表歉意。

学习资料如下:

1、

神经网络浅讲:从神经元到深度学习 - 计算机的潜意识 - 博客园https://www.cnblogs.com/subconscious/p/5058741.html2、

从零设计并训练一个神经网络,你就能真正理解它了_哔哩哔哩_bilibili从零设计并训练一个神经网络,你就能真正理解它了, 视频播放量 300398、弹幕量 172、点赞数 13628、投硬币枚数 5864、收藏人数 30799、转发人数 2355, 视频作者 小黑黑讲AI, 作者简介 我的唯一官网:www.dhcode.cn课程报名和咨询请找:xheiai,相关视频:什么是神经网络?看一个动画,就全明白了,1-大白话解读神经网络,十分钟动画解读为什么神经网络可以学习任何东西!附带CNN\RNN\GAN\GNN\Transformer等五大神经网络系统解读!,【AI篇】AI 的十层理解,一口气,看懂 AI 的过去、现在与未来。,从零实现一个卷积神经网络,Lenet5网络详解,【教程】Scratch做神经网络 手写数字识别(下),当人工智能神经网络识别一条狗时,内部是如何工作的?,【动画演示】神经网络为什么可以学习任何东西?,动画演示|神经网络是如何储存记忆的?!,【2025版】不愧是吴恩达教授!一口气讲透CNN、RNN、GAN、GNN、DQN、Transformer、LSTM等八大深度学习神经网络算法!简直不要太爽!https://www.bilibili.com/video/BV134421U77t?t=707.43、

【数学建模】BP神经网络是如何构建出来的?_哔哩哔哩_bilibili关注微信公众号:数学建模BOOM,回复“课程”和“群”,查看全套数模精品课程和交流群号。也可直接复制链接到浏览器查看全套的课程:https://wyp.xet.tech/s/1cOcb0, 视频播放量 39364、弹幕量 152、点赞数 679、投硬币枚数 292、收藏人数 1148、转发人数 350, 视频作者 数学建模BOOM, 作者简介 关注微信公众号:数学建模BOOM,回复“群”,获取交流群号。数模资料、视频课件都在交流群的群文件内。,相关视频:1-大白话解读神经网络,利用神经网络预测实际案例!,什么是神经网络?看一个动画,就全明白了,BP神经网络公式推导完整版,BP神经网络,4.2 [15分钟] 深度学习开端-BP神经网络,快速学会BP神经网络正向传播与反向传播数学过程(考虑偏置系数与sigmoid非线性变换) 梯度下降,神经网络适用赛题:预测&分类(美赛C题投资&图像识别),【神经网络】5-手动计算BP神经网络的权值来实现学习,专题 通过四个matlab建模案例彻底精通BP神经网络https://www.bilibili.com/video/BV1TR4y1871G?t=7.7

本文旨在建立一个简单的数字识别神经网络,并介绍相关原理,附带实验过程,强烈建议先看完资料一中的内容。

原理简介:

单层神经网络

首先讲一下简单的标准神经网络,标准神经网络格式如下:

这是一种单层神经网络(我们定义进行计算层的数量来定义神经网络层数,这里计算层只有一个,所有是单层神经网络),设输入为 x1 、 x2 、x3,权值为 w1 、w2 、w3

这里我们假设激活函数为阶跃函数:

,则很容易得到输入值经过该网络计算得到的结果:

如果  < 0,则值为 0 ,否则则为

这种单层神经网络也被称为感知机,能够解决一些简单的线性分类问题

但这是没加偏置项的情况,当输入的 xi 都为 0 时,不管权重多大,最后的结果一定是 0 。这不一定符合我们的任务需求,比如在做房价预测时,假设当面积、房间数等特征都为 0 时,理论上房子不存在,但也许你希望预测一个“基础成本”(比如土地成本),这时输出就不该是 0

可以理解为:没有偏置项时,拟合的函数是经过原点的,而有了偏置项,允许函数上下移动,更能满足现实世界的复杂需求。

加上偏置层后,公式变为:

这个偏置项也算是一个节点,称为偏置节点,只是输入和权重固定,所以还是可以表达为之前的公式:

BP 神经网络

上述结构为单层神经网络,能够解决简单的分类问题,但在处理线性不可分问题时,无法通过简单训练实现收敛,或者无法学习复杂的非线性关系

例如:在养殖场随机抓两只鸡,判断这两只鸡是否有诞生小鸡的可能。这是一个典型的异或问题,只有当两只鸡的性别不同,即(0,1)、(1,0)两种情况时,才有诞生小鸡的可能,(0,0)和(1,1)都没有一点可能性。所以需要将能诞生小鸡的分为一类,不能诞生小鸡的分为一类,但你会发现,无论你画哪种直线,都无法实现上面的分类法则,如图所示:

但多层神经网络能做到这一点,比如 BP 神经网络,BP 神经网络等于叠加多个感知机来实现非线性可分,其在输入层和输出层中间添加了隐藏层,这隐藏层可以有多个层,每个层都有一个激活函数,每个层用来解决一种问题,逻辑与、逻辑并、逻辑交等等,从而具备了学习非线性关系的能力。理论上来说,只要 3 层,就能拟合任何非线性函数了。

BP 神经网络是一种前馈型神经网络,整体训练流程如下:

1、模型初始化,人为设置权重 wi 的初始值

2、准备、处理数据,对于输入数据,这里是 28×28 的单通道图像,即输入层应有 28×28x,一般为了数据更加友好,会对数据进行归一化,这里即除以 255

3、将数据输入,计算得到估计值  

4、将模型求得估计值  与实际值 y 比较,若差值达到某个阈值,则完成训练,否则,以目标的负梯度方向对参数进行调整,这里使用均方根误差:

再将算出的权重调整值加到每一层的对应权重中:

5、直到满足终止条件,否则继续对权重 wi 进行优化,重复步骤 3-5

实例

数据获取

代码:

# 这个程序的功能会先将MNIST数据下载下来,然后再保存为.png的格式。
from torchvision.datasets import MNIST
from torchvision.transforms import ToTensor
from torchvision.transforms import ToPILImage
import os
import secrets

# 下载数据
train_data = MNIST(root='./data', train=True, download=True, transform=ToTensor())
test_data = MNIST(root='./data', train=False, download=True, transform=ToTensor())

# ToPILImage 张量格式 转换为 PIL 图像格式
train_data = [(ToPILImage()(img), label) for img, label in train_data]
test_data = [(ToPILImage()(img), label) for img, label in test_data]

# 保存下来
def save_images(dataset, folder_name):
    root_dir = os.path.join('./mnist_images', folder_name)
    os.makedirs(root_dir, exist_ok=True)
    # 这10个子文件夹保存每种数字的数据
    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')

上述代码用于加载数字图片数据集和标注集,并保存到本地,标注集包括0-9,共十个数字

下载后如图所示:

下载需要 pytorch 环境,需要下载 GPU 版 pytorch 环境的可以参考这一篇:

CV -- 基于GPU版CUDA环境+Pycharm YOLOv8 目标检测_python+yolo+opencv+cuda-CSDN博客https://blog.csdn.net/2403_83182682/article/details/145644092?spm=1001.2014.3001.5502

定义网络

网络结构:

代码:

使用 pytorch 定义网络,并打印整个过程中的结构变换:

import torch
from torch import nn

# 定义神经网络Network
class Network(nn.Module):
    def __init__(self):
        # 调用父类 nn.Module 的 __init__ 方法
        super().__init__()
        # 线性层1,输入层和隐藏层之间的线性层
        self.layer1 = nn.Linear(784, 256)
        # 线性层2,隐藏层和输出层之间的线性层
        self.layer2 = nn.Linear(256, 10)

    # 在前向传播,forward函数中,输入为图像x
    def forward(self, x):
        # -1 表示自动计算 batch size,使用view函数,将x展平
        x = x.view(-1, 28 * 28)
        x = self.layer1(x)  # 将x输入至layer1,自动初始化权重
        # 引入非线性 relu 激活函数,让神经网络可以学习更复杂的模式。
        x = torch.relu(x)
        return self.layer2(x) # 输入至layer2计算结果

#手动的遍历模型中的各个结构,并计算可以训练的参数
def print_parameters(model):
    cnt = 0
    for name, layer in model.named_children(): #遍历每一层
        # 打印层的名称和该层中包含的可训练参数
        print(f"layer({name}) parameters:")
        for p in layer.parameters():
            # print("p:",p)
            print(f'\t {p.shape} has {p.numel()} parameters')
            cnt += p.numel() #将参数数量累加至cnt
    #最后打印模型总参数数量
    print('The model has %d trainable parameters\n' % (cnt))

#打印输入张量x经过每一层时的维度变化情况
def print_forward(model, x):
    print(f"x: {x.shape}") # x从一个5*28*28的输入张量
    x = x.view(-1, 28 * 28) # 经过view函数,变成了一个5*784的张量
    print(f"after view: {x.shape}")
    x = model.layer1(x) #经过第1个线性层,得到5*256的张量
    print(f"after layer1: {x.shape}")
    x = torch.relu(x) #经过relu函数,没有变化
    print(f"after relu: {x.shape}")
    x = model.layer2(x) #经过第2个线性层,得到一个5*10的结果
    print(f"after layer2: {x.shape}")

if __name__ == '__main__':
    model = Network() #定义一个Network模型
    print(model) #将其打印,观察打印结果可以了解模型的结构
    print("")

    print_parameters(model) #将模型的参数打印出来
    #打印输入张量x经过每一层维度的变化情况
    x = torch.zeros([5, 28, 28])
    print_forward(model, x)

输入的图像是大小为 28×28 的灰度图像,每次训练时输入 5 张图像作为一个批次。为了适配全连接层的输入要求,每张图像会被展平成一个 784 维的一维向量。

在定义好神经网络结构后,模型会自动初始化每一层的权重矩阵和偏置项。输入数据首先经过第一个线性层nn.Linear(784, 256),该层对输入进行线性变换,输出一个形状为 (5, 256) 的张量,表示每张图像被映射到 256 维的特征空间。

随后,该输出会传入第二层线性层nn.Linear(256, 10),进一步进行变换,最终得到一个形状为 (5, 10) 的输出张量,表示每张图像在 10 个类别上的预测得分。

运行后打印模型结构:

Network(
  (layer1): Linear(in_features=784, out_features=256, bias=True)
  (layer2): Linear(in_features=256, out_features=10, bias=True)
)

layer(layer1) parameters:
	 torch.Size([256, 784]) has 200704 parameters
	 torch.Size([256]) has 256 parameters
layer(layer2) parameters:
	 torch.Size([10, 256]) has 2560 parameters
	 torch.Size([10]) has 10 parameters
The model has 203530 trainable parameters

x: torch.Size([5, 28, 28])
after view: torch.Size([5, 784])
after layer1: torch.Size([5, 256])
after relu: torch.Size([5, 256])
after layer2: torch.Size([5, 10])

训练模型

import torch
from torch import nn
from torch import optim
from model import Network  # 前面定义的模型

from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader

if __name__ == '__main__':
    # 图像的预处理
    transform = transforms.Compose([
        transforms.Grayscale(num_output_channels=1),  # 转换为单通道灰度图
        transforms.ToTensor()  # 转换为张量并自动归一化
    ])

    # 读入数据集并应用 transform
    train_dataset = datasets.ImageFolder(root='./mnist_images/train', transform=transform)
    print("train_dataset length: ", len(train_dataset))

    # 小批量的数据读入,shuffle=True 打乱数据顺序,防止模型记住顺序
    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):  # 外层循环,代表了整个训练数据集的遍历次数
        # 内存循环使用train_loader,进行小批量的数据读取
        for batch_idx, (data, label) in enumerate(train_loader):
            # 内层每循环一次,就会进行一次梯度下降算法
            # 包括了5个步骤:
            output = model(data) # 1.计算神经网络的前向传播结果
            loss = criterion(output, label) # 2.计算output和标签label之间的损失loss
            loss.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') # 保存模型

将训练集每 64 张图片分为一批,总共有  9800 张图片,分为 152 批训练,然后循环 10 轮,训练完成后模型保存至 mnist.pth

测试模型

from model import Network
from torchvision import transforms
from torchvision import datasets
import torch

if __name__ == '__main__':
    transform = transforms.Compose([
        transforms.Grayscale(num_output_channels=1),
        transforms.ToTensor()
    ])
    # 读取测试数据集
    test_dataset = datasets.ImageFolder(root='./mnist_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和真实标签y
        if predict == y:
            right += 1
        else:
            # 将识别错误的样例打印了出来
            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_num
    print("test accuracy = %d / %d = %.3lf" % (right, sample_num, acc))

加载测试集图片依次应用模型,并对比结果与真实值差异,保存差异值,打印并计数,算出模型整体准确率。

结果:

wrong case: predict = 5 y = 9 img_path = ./mnist_images/test\9\d49c3b8b4ff6153e.png
wrong case: predict = 7 y = 9 img_path = ./mnist_images/test\9\e2eabc1f4af0928c.png
wrong case: predict = 4 y = 9 img_path = ./mnist_images/test\9\e3763ad6230f4fe5.png
wrong case: predict = 5 y = 9 img_path = ./mnist_images/test\9\ec6c3a2fa9af3286.png
wrong case: predict = 7 y = 9 img_path = ./mnist_images/test\9\f4a08ca3756a8e9c.png
wrong case: predict = 3 y = 9 img_path = ./mnist_images/test\9\f52aa330abf33706.png
test accuracy = 9815 / 10000 = 0.982

进程已结束,退出代码为 0

模型准确率为 98.2%

本文内容参考了多篇相关文章,旨在进行学习与总结。如涉及版权问题,请作者及时联系删除,本人将不胜感激并深表歉意。

感谢您的观看!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值