卷积神经网络-高级篇Advanced-CNN

news2025/1/10 20:33:18

卷积神经网络-高级篇Advanced-CNN

在基础篇中我们学习了一个简单的CNN

在这里插入图片描述

下面介绍其他几个网络结构

GoogLeNet

在这里插入图片描述

蓝色为卷积,红色是池化,黄色是softmax输出,绿色是一些拼接层。

在这个大型的网络结构中我们需要做到的是减少代码冗余(函数、类)

在这个网络结构中有很多相同重复得层,我们可以封装称为一个类

在这里插入图片描述

在GoogLeNet中把这样一些层叫做Inception

Inception概念

Inception就是把多个卷积或池化操作,放在一起组装成一个网络模块,设计神经网络时以模块为单位去组装整个网络结构。模块如下图所示

在这里插入图片描述

在未使用这种方式的网络里,我们一层往往只使用一种操作,比如卷积或者池化,而且卷积操作的卷积核尺寸也是固定大小的。但是,在实际情况下,在不同尺度的图片里,需要不同大小的卷积核,这样才能使性能最好,或者或,对于同一张图片,不同尺寸的卷积核的表现效果是不一样的,因为他们的感受野不同。所以,我们希望让网络自己去选择,Inception便能够满足这样的需求,一个Inception模块中并列提供多种卷积核的操作,网络在训练的过程中通过调节参数自己去选择使用,同时,由于网络中都需要池化操作,所以此处也把池化层并列加入网络中。

其中Concatenate是把张量拼接在一块,四条路径会产生四个张量,沿着通道拼起来

Average Pooling(平均池化):可以通过设置padding和stride来保证输入输出的大小是一致的

https://blog.csdn.net/u013289254/article/details/99080916

1*1Conv层表示将来的卷积核就是1*1的,他的个数取决于输入张量的通道。

比如有一个3*W*H的图像,通过1*1Conv层都变成1*W*H的feature map

在这里插入图片描述

他的作用主要有以下三方面:

  1. 跨通道的特征整合
  2. 特征通道的升维和降维
  3. 减少卷积核参数(简化模型)

https://blog.csdn.net/nefetaria/article/details/107977597

以下图为例,我们可以看到,计算量明显减少。

在这里插入图片描述

Inception实现

我们用代码来实现Inception

代码说明:

池化分支中首先使用average pooling,在对他进行1*1卷积

在这里插入图片描述

1*1分支中直接创建一个1*1的卷积

在这里插入图片描述

5*5分支中有两个模块,第一个是1*1的卷积,第二个模块是5*5的卷积

在这里插入图片描述

3*3分支中包含了三个模块

在这里插入图片描述

整体的代码结构如下

在这里插入图片描述

最后我们需要把这一整块进行拼接Concatenate

在这里插入图片描述

拼接Concatenate代码如下

在这里插入图片描述

我们要沿着(B,C,W,H)中dim=1,就是通道的维数拼起来

我们把上面的代码进行整合就可以得到一个Inception类,在这个类中并没有把输入通道写死,说明可以在构造网络中作为参数输入

在这里插入图片描述

网络模型代码如下:

在这里插入图片描述

先是1个卷积层(conv,maxpooling,relu),然后inceptionA模块(输出的channels是24+16+24+24=88),接下来又是一个卷积层(conv,maxpooling,relu),然后inceptionA模块,最后一个全连接层(fc)。

1408这个数据可以通过x = x.view(in_size, -1)后调用x.shape得到。

模型实验

我们把这个Inception模型放入Mnist数据集中进行实验

代码演示

import torch
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim

# prepare dataset

batch_size = 64
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])  # 归一化,均值和方差
train_dataset = datasets.MNIST(root='../dataset/mnist/', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size)
test_dataset = datasets.MNIST(root='../dataset/mnist/', train=False, download=True, transform=transform)
test_loader = DataLoader(test_dataset, shuffle=False, batch_size=batch_size)


# design model using class

class InceptionA(torch.nn.Module):
    def __init__(self, in_channels):
        super(InceptionA, self).__init__()
        self.branch1x1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)

        self.branch5x5_1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
        self.branch5x5_2 = torch.nn.Conv2d(16, 24, kernel_size=5, padding=2)

        self.branch3x3_1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
        self.branch3x3_2 = torch.nn.Conv2d(16, 24, kernel_size=3, padding=1)
        self.branch3x3_3 = torch.nn.Conv2d(24, 24, kernel_size=3, padding=1)

        self.branch_pool = torch.nn.Conv2d(in_channels, 24, kernel_size=1)

    def forward(self, x):
        branch1x1 = self.branch1x1(x)

        branch5x5 = self.branch5x5_1(x)
        branch5x5 = self.branch5x5_2(branch5x5)

        branch3x3 = self.branch3x3_1(x)
        branch3x3 = self.branch3x3_2(branch3x3)
        branch3x3 = self.branch3x3_3(branch3x3)

        branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)
        branch_pool = self.branch_pool(branch_pool)

        outputs = [branch1x1, branch5x5, branch3x3, branch_pool]
        return torch.cat(outputs, dim=1)  # b,c,w,h  c对应的是dim=1


class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = torch.nn.Conv2d(88, 20, kernel_size=5)  # 88 = 24x3 + 16

        self.incep1 = InceptionA(in_channels=10)  # 与conv1 中的10对应
        self.incep2 = InceptionA(in_channels=20)  # 与conv2 中的20对应

        self.mp = torch.nn.MaxPool2d(2)
        self.fc = torch.nn.Linear(1408, 10)

    def forward(self, x):
        in_size = x.size(0)
        x = F.relu(self.mp(self.conv1(x)))
        x = self.incep1(x)
        x = F.relu(self.mp(self.conv2(x)))
        x = self.incep2(x)
        x = x.view(in_size, -1)
        x = self.fc(x)

        return x


model = Net()

# construct loss and optimizer
criterion = torch.nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)


# training cycle forward, backward, update

def train(epoch):
    running_loss = 0.0
    for batch_idx, data in enumerate(train_loader, 0):
        # 获得一个批次的数据和标签
        inputs, target = data
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, target)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()
        if batch_idx % 300 == 299:
            print('[%d,%5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 300))
            running_loss = 0.0


def test():
    correct = 0
    total = 0
    with torch.no_grad():
        for data in test_loader:
            images, labels = data
            outputs = model(images)
            _, predicted = torch.max(outputs.data, dim=1)  # dim = 1 列是第0个维度,行是第1个维度
            total += labels.size(0)
            correct += (predicted == labels).sum().item()  # 张量之间的比较运算
    print('accuracy on test set: %d %% ' % (100 * correct / total))


if __name__ == '__main__':
    for epoch in range(10):
        train(epoch)
        test()

结果演示

[1,  300] loss: 0.861
[1,  600] loss: 0.222
[1,  900] loss: 0.155
accuracy on test set: 95 % 
[2,  300] loss: 0.115
[2,  600] loss: 0.099
[2,  900] loss: 0.084
accuracy on test set: 97 % 
[3,  300] loss: 0.074
[3,  600] loss: 0.074
[3,  900] loss: 0.073
accuracy on test set: 98 % 
[4,  300] loss: 0.058
[4,  600] loss: 0.062
[4,  900] loss: 0.059
accuracy on test set: 98 % 
[5,  300] loss: 0.050
[5,  600] loss: 0.055
[5,  900] loss: 0.054
accuracy on test set: 98 % 
[6,  300] loss: 0.047
[6,  600] loss: 0.048
[6,  900] loss: 0.046
accuracy on test set: 98 % 
[7,  300] loss: 0.042
[7,  600] loss: 0.043
[7,  900] loss: 0.043
accuracy on test set: 98 % 
[8,  300] loss: 0.039
[8,  600] loss: 0.040
[8,  900] loss: 0.038
accuracy on test set: 98 % 
[9,  300] loss: 0.035
[9,  600] loss: 0.034
[9,  900] loss: 0.038
accuracy on test set: 98 % 
[10,  300] loss: 0.032
[10,  600] loss: 0.032
[10,  900] loss: 0.033
accuracy on test set: 98 % 

结果如下

在这里插入图片描述

ResidualNet(ResNet)

随着网络结构的加深,带来了两个问题:

  • 一是vanishing/exploding gradient,导致了训练十分难收敛,这类问题能够通过normalized initialization 和intermediate normalization layers解决;
  • 另一个是被称为degradation的退化现象。对合适的深度模型继续增加层数,模型准确率会下滑(不是overfit造成),training error和test error都会很高,相应的现象在CIFAR-10和ImageNet都有出现。

https://blog.csdn.net/u011808673/article/details/78836617

残差结构

可以看到普通直连的卷积神经网络和 ResNet 的最大区别在于,ResNet 有很多旁路的支线将输入直接连到后面的层,使得后面的层可以直接学习残差,这种结构也被称为 shortcut connections。传统的卷积层或全连接层在信息传递时,或多或少会存在信息丢失、损耗等问题。ResNet 在某种程度上解决了这个问题,通过直接将输入信息绕道传到输出,保护信息的完整性,整个网络则只需要学习输入、输出差别的那一部分,简化学习目标和难度。 同时34层 residual network 取消了最后几层 FC,通过 avg pool 直接接输出通道为1000的 Softmax,使得 ResNet 比16-19层 VGG 的计算量还低。 注意:实现部分是深度未发生变化的连接,虚线部分是深度发生变化的连接。 对应深度有变化的连接有两种解决方案:

  1. 使用 zero-pading 进行提升深度 parameter-free。
  2. 使用 1*1的卷积核提升维度 有卷积核的运算时间。

两种方法,使用下面一种方法效果更好,但是运行会更耗时,一般还是更倾向于第一种方案节约运算成本。

在这里插入图片描述

残差块

在正常的神经网络中就是一层连一层

在这里插入图片描述

在ResNet中,假定某段神经网络的输入是 x,期望输出是 H(x),即 H(x) 是期望的复杂潜在映射,但学习难度大;如果我们直接把输入 x 传到输出作为初始结果,通过下图“shortcut connections”,那么此时我们需要学习的目标就是 F(x)=H(x)-x,于是 ResNet 相当于将学习目标改变了,不再是学习一个完整的输出,而是最优解 H(X) 和全等映射 x 的差值,即残差 F(x) = H(x) - x;

在这里插入图片描述

ResNet shortcut 没有权值,传递 x 后每个模块只学习残差F(x),且网络稳定易于学习,同时证明了随着网络深度的增加,性能将逐渐变好。可以推测,当网络层数够深时,优化 Residual Function:F(x)=H(x)−x,易于优化一个复杂的非线性映射 H(x)。

ResNet网络结构

在这里插入图片描述

Residual Block 实现

值得注意的是,我们需要输入通道和输出通道一致

第一层中先做卷积在做relu(conv1(x)),第二层中做卷积conv2(y),最后返回relu(x+y)

在这里插入图片描述

ResNet代码实现

在这里插入图片描述

代码演示

import torch
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim

# prepare dataset

batch_size = 64
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])  # 归一化,均值和方差
train_dataset = datasets.MNIST(root='../dataset/mnist/', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size)
test_dataset = datasets.MNIST(root='../dataset/mnist/', train=False, download=True, transform=transform)
test_loader = DataLoader(test_dataset, shuffle=False, batch_size=batch_size)


# design model using class
class ResidualBlock(torch.nn.Module):
    def __init__(self,channels):
        super(ResidualBlock, self).__init__()
        self.channels = channels
        self.conv1 = torch.nn.Conv2d(channels, channels, kernel_size=3, padding=1)
        self.conv2 = torch.nn.Conv2d(channels, channels, kernel_size=3, padding=1)

    def forward(self,x):
        y = F.relu(self.conv1(x))
        y = self.conv2(y)
        return  F.relu(x+y)


class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = torch.nn.Conv2d(1, 16, kernel_size=5)
        self.conv2 = torch.nn.Conv2d(16, 32, kernel_size=5)
        self.mp = torch.nn.MaxPool2d(2)

        self.rBlock1 = ResidualBlock(16)
        self.rBlock2 = ResidualBlock(32)

        self.fc = torch.nn.Linear(512,10)

    def forward(self, x):
        in_size = x.size(0)
        x = self.mp(F.relu(self.conv1(x)))
        x = self.rBlock1(x)
        x = self.mp(F.relu(self.conv2(x)))
        x = self.rBlock2(x)
        x = x.view(in_size, -1)
        x = self.fc(x)
        return x


model = Net()
# 使用GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)

# construct loss and optimizer
criterion = torch.nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)


# training cycle forward, backward, update

def train(epoch):
    running_loss = 0.0
    for batch_idx, data in enumerate(train_loader, 0):
        # 获得一个批次的数据和标签
        inputs, target = data
        # 使用GPU
        inputs, target = inputs.to(device), target.to(device)

        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, target)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()
        if batch_idx % 300 == 299:
            print('[%d,%5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 2000))
            running_loss = 0.0


def test():
    correct = 0
    total = 0
    with torch.no_grad():
        for data in test_loader:
            images, labels = data
            # 使用GPU
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs.data, dim=1)  # dim = 1 列是第0个维度,行是第1个维度
            total += labels.size(0)
            correct += (predicted == labels).sum().item()  # 张量之间的比较运算
    print('accuracy on test set: %d %% [%d/%d]' % (100 * correct / total, correct, total))


if __name__ == '__main__':
    for epoch in range(10):
        train(epoch)
        test()

输出结果

[1,  300] loss: 0.085
[1,  600] loss: 0.025
[1,  900] loss: 0.018
accuracy on test set: 97 % [9728/10000]
[2,  300] loss: 0.014
[2,  600] loss: 0.013
[2,  900] loss: 0.011
accuracy on test set: 97 % [9781/10000]
[3,  300] loss: 0.009
[3,  600] loss: 0.009
[3,  900] loss: 0.009
accuracy on test set: 98 % [9842/10000]
[4,  300] loss: 0.007
[4,  600] loss: 0.007
[4,  900] loss: 0.007
accuracy on test set: 98 % [9868/10000]
[5,  300] loss: 0.006
[5,  600] loss: 0.006
[5,  900] loss: 0.006
accuracy on test set: 98 % [9898/10000]
[6,  300] loss: 0.006
[6,  600] loss: 0.005
[6,  900] loss: 0.005
accuracy on test set: 98 % [9896/10000]
[7,  300] loss: 0.005
[7,  600] loss: 0.005
[7,  900] loss: 0.005
accuracy on test set: 98 % [9896/10000]
[8,  300] loss: 0.004
[8,  600] loss: 0.004
[8,  900] loss: 0.004
accuracy on test set: 99 % [9906/10000]
[9,  300] loss: 0.004
[9,  600] loss: 0.004
[9,  900] loss: 0.004
accuracy on test set: 99 % [9913/10000]
[10,  300] loss: 0.003
[10,  600] loss: 0.003
[10,  900] loss: 0.004
accuracy on test set: 99 % [9909/10000]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/102045.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么Python是2023最值得学的编程语言?

对于那些从来没有学习编程小伙伴,Python 是最好的选择之一, Python 是一种清晰的语言,用缩进来表示程序的嵌套关系可谓是一种创举,把过去软性的编程风格升级为硬性的语法规定。再不需要在不同的风格间选择、再不需要为不同的风格…

阿里工作7年被裁,3个月逆袭字节跳动测试开发,有些心里话想对大家说...

被裁之路 先简单交代一下背景吧,某不知名 985 的本硕,17 年毕业加入阿里,以“人员优化”的名义无情被裁员,我失去了在阿里5年的工作。虽然有事先通风,但是我没有想到这一天会来的那么快。今天中午收到消息说我们这个组…

网络入门基础

目录 一.预备知识 1.1网络背景 1.2协议 二.网络协议 2.1协议分层 2.2OSI 7层 2.3TCP/IP五层(或四层) 三.网络传输基本流程 3.1局域网通信 3.2 跨网络通信 3.3IP地址与MAC地址 一.预备知识 1.1网络背景 独立模式:计算机之间相互独立 网络互联: 计…

[附源码]计算机毕业设计Python的网上点餐系统(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置: Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术: django python Vue 等等组成,B/S模式 pychram管理等…

[附源码]计算机毕业设计Python的汽车租赁系统(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置: Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术: django python Vue 等等组成,B/S模式 pychram管理等…

为什么要学Python编程 到底Python值不值得学

为什么要学Python编程?到底Python值不值得学​?Python在软件质量控制、提升开发效率、可移植性、组件集成、丰富库支持等各个方面均处于先进地位。同样学习编程语言,当然要选择学习业内目前先进、热门、将来应用广泛、有前途和前景的编程语言…

计算机行业真的这么吃香吗,他的真实薪资情况到底如何呢?

越来越多的人涌入计算机行业,这个行业真的这么吃香吗,他的真实薪资情况到底如何呢? 近些年来,不论是否是计算机专业的学生或者是社会人员,都选择一脚踏入这个行业。它真的这么好吗? 首先从地域而言&#…

LeetCode 1971. 寻找图中是否存在路径

【LetMeFly】1971.寻找图中是否存在路径 力扣题目链接:https://leetcode.cn/problems/find-if-path-exists-in-graph/ 有一个具有 n个顶点的 双向 图,其中每个顶点标记从 0 到 n - 1(包含 0 和 n - 1)。图中的边用一个二维整数数…

【Acwing寒假2023每日一题】4261. 孤独的照片 - 乘法原理

4261. 孤独的照片 - AcWing题库 这题看数据范围 n ≤ 不可能暴力做 会tle 1、双指针暴力模拟 tle #include <bits/stdc.h> using namespace std;int main() {int n,res0;string s;cin>>n>>s;for(int i0;i<n-2;i){int g0,h0;for(int l3;il<n;l){strin…

网络搭建与应用—Windows10上开启路由转发及添加路由

Windows10上开启路由转发及添加路由 注意&#xff1a;实验环境下主机B两个接口没有网关 操作步骤 一、主机B 开启 win10 转发功能 1、进入CMD 2、执行命令 reg add HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters /v IPEnableRouter /D 1 /f 3、进入注册表 将 HK…

【HTML】Base64编码

目录 Base64编码表 Base64编码解码 Base64位图片 uni-app图片绝对路径转base64码 微信小程序图片绝对路径转base64码 Base64是常见的传输8Bit字节码的编码方式之一&#xff0c;基于可打印字符来表示二进制数据的方法。一般用于在HTTP协议下传输二进制数据。由于网络传输只…

25岁无经验入行软件测试的感悟,写给还在迷茫中的你

转行软件测试两年了&#xff0c;这两年来&#xff0c;从刚开始对测试认识的朦朦胧胧&#xff0c;现在思路也逐渐清晰了&#xff0c;也明确了自己的发展方向。虽然对那些测试理论和测试工具以及测试技术有了一些加强&#xff0c;但是自我感觉还是不够深入。 我一直希望能真正融…

网络实验②——同Vlan下相互通信

实验要求&#xff1a; 同vlan间可互相通信对交换机配置远程管理&#xff0c;注&#xff1a;新建管理vlan&#xff0c;名称&#xff1a;guanli&#xff0c;ID&#xff1a;110 实验步骤&#xff1a; A交换机配置&#xff1a; enable config t hostname switch-A vlan 10 vla…

中望3D二次开发 控制台命令转PDF

中望3D的外部开发模式命令非常少&#xff0c;没有办法使用远程办法打开文件&#xff0c;将图纸转换为PDF&#xff08;听说以后的版本会有&#xff0c;但是在2022版本上是没有的&#xff09;&#xff1b; ps&#xff1a;远程方式&#xff0c;意思就是远程电脑必须开启中望3D软件…

周志华 机器学习初步 线性模型

周志华 《机器学习初步》 线性模型 还未更新完&#xff0c;会持续更新 文章目录周志华 《机器学习初步》 线性模型一.线性回归线性模型线性模型的特点和重要性线性模型的基本形式参考资料一.线性回归 线性模型 线性模型的特点和重要性 线性模型的重要性 人在考虑问题时&#…

客户案例:Coremail安全海外中继保障德赛集团跨境通邮安全

客户背景 广东德赛集团有限公司&#xff08;以下简称“德赛集团”&#xff09;成立于1983年&#xff0c;旗下拥有2家上市公司&#xff0c;位列中国制造行业前500强企业&#xff0c;合作伙伴和客户中有30多家是世界前500强企业。在新能源电池、汽车电子、北斗导航技术等多项技术…

vivo 云原生容器探索和落地实践

作者&#xff1a;vivo 互联网容器团队- Pan Liangbiao 本文根据潘良彪老师在“2022 vivo开发者大会"现场演讲内容整理而成。公众号回复【2022 VDC】获取互联网技术分会场议题相关资料。 2018年起&#xff0c;vivo以容器作为基础底座&#xff0c;打造了一站式云原生机器学习…

【JVM】伟大的开端—CMS

本文已收录至Github&#xff0c;推荐阅读 &#x1f449; Java随想录 微信公众号&#xff1a;Java随想录 转载请在文首注明出处&#xff0c;如发现恶意抄袭/搬运&#xff0c;会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境&#xff01; 伟大的开端—CMS …

CY5-N-羟基琥珀酰亚胺 Cyanine5 NHS ester 荧光量子产率

CY5-N-羟基琥珀酰亚胺 Cyanine5 NHS ester 荧光量子产率 Cy5 NHS酯是标记多肽&#xff0c;蛋白和寡核苷酸的活性染料。染料需要少量的有机共溶剂&#xff08;比如DMF和DMSO&#xff09;溶解后进行标记反应。此试剂可以标记溶解蛋白和各种多肽&#xff0c;寡核苷酸的氨基。对于…

Spring 之 MutablePropertyValues 和 ConstructorArgumentValues 的简单理解

1、MutablePropertyValues 概述 其实在绝大多情况下&#xff0c;MutablePropertyValues 这个类很少用&#xff0c;但是涉及到框架改造扩展可能就要使用到这个类。并且这个类在 BeanDefinition 模板中也是一个非常重要的角色。 id&#xff1a;Bean 唯一标识名称。 beanClass&am…