GoogLeNet详细介绍与TensorFlow实现

2024-01-26 08:32

本文主要是介绍GoogLeNet详细介绍与TensorFlow实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

      • 1. Inception 模块提出
      • 2. Inception 模块结构
      • 3. GoogLeNet网络结构
      • 4. Tensorflow实现GoogLeNet结构

GoogLeNet,是2014年ILSVRC比赛分类任务的第一名(同年分类任务的第二名是VGG),该名字是为了向 LeNet 5致敬。该网络为了提高计算效率,使用了一种叫做 Inception的网络模块,该模块可以保证在增加网络深度和宽度的前提下,计算效率不会发生明显衰减。该网络发表在论文《Going deeper with convolutions》,文中给出了关于Inception模块的提出思路,读起来相对比较晦涩难懂一点,这里我简单介绍一下其设计思路,如果理解有偏差,欢迎留言讨论!

1. Inception 模块提出

L e n e t 5 Lenet 5 Lenet5 开始,传统的卷积网络都是堆叠卷积层(也会接归一化层)、池化层然后接一个或者多个全连接层,研究人员基于这种模式设计了很多网络,它们在MNIST、CIFAR、ImageNet 数据集上都取得了很好的结果,对于较大较深的网络,则使用Dropout来缓解过拟合问题。

一般来说,提高网络的深度(增加网络层数)和宽度(增加每一层神经元的个数),是提高网络性能的最直接方式,但是由此带来的问题是:

  1. 参数增多,特别是带标签的数据有限的情况下,很容易造成过拟合。而一些数据的标记工作可能需要“专家”来完成,代价也会很昂贵,论文中举了一个ImageNet的数据标注例子,如下图

    在这里插入图片描述

    没有专业的知识,你可能很难分清两条狗的详细类型

  2. 计算量急剧增大

    增加网络深度,会造成计算量剧增,例如将两个卷积层连接在一起,第一层卷积使用 128 128 128个大小为 3 ∗ 3 ∗ 64 3*3*64 3364的卷积核,下一层卷积使用 256 256 256个大小为 3 ∗ 3 ∗ 128 3*3*128 33128的卷积核,此时网络性能可能有所提升,但是参数量增加了 4 4 4倍,如果这些新增的网络层利用率较低(大部分权重值趋于 0 0 0),则会造成计算资源的严重浪费。因此在计算资源有限的情况下,不能为了提高网络性能而一味地增加网络深度或者宽度。

为了解决这两个问题,作者认为需要引入稀疏性,包括将全连接转换成稀疏连接,以及在卷积内部也实现稀疏连接。引入稀疏性的原因是:

  1. 生物学的神经网络本身就具有稀疏性

  2. Arora等人的研究表明,如果数据集的概率分布可以用一个大的、稀疏的深度神经网络来表示,那么可以通过分析激活值的相关性统计以及将输出值相关性高的神经元聚合在一起的方式,逐层分析出该最优的网络结构

  3. Hebbian 理论:neurons that fire together, wire together,即如果两个神经元经常被同时激活,则这两个神经元的相关性极高。反之,如果两个神经元经常被分开激活,则两个神经元的相关性极低。

    关于该理论在人工神经网络引入稀疏性的理论支撑在于:

    • 如果两个神经元经常被同时激活,则这两个神经元的相关性极高,在人工神经网络中则表现为两个神经元的连接权重增大

    • 如果两个神经元经常被分开激活,则两个神经元的相关性极低,在人工神经网络中则表现为两个神经元的连接权重很小,甚至两个神经元的连接毫无意义(即权重为 0 0 0),这种无意义的神经元连接,不需要连接两个神经元,也就由此引入了稀疏性

然而,引入稀疏性带来的问题是,当前的计算设备在处理非均匀的稀疏数据结构时非常低效,即使运算量减少100倍,查询和cache misses的开销很大,计算效率依然低下。 另外,由于数值库利用CPU和GPU的硬件特性对稠密矩阵计算进行的很多优化,使得计算设备处理稠密数据和稀疏数据的性能差距更大。

现在矛盾的点是,我们既想引入稀疏性,又想使用稠密矩阵的高速计算性能。

如何引入稀疏性?

我们一般通过卷积来实现稀疏性。然而卷积是对前一层patches(每次进行卷积计算时,卷积核覆盖输入图的区域大小)的稠密连接,这句话我的理解是:

  1. 卷积既有稀疏性,也能利用稠密计算的性能

  2. 卷积过程有稠密连接,言外之意,应该考虑将这个稠密连接也转换成稀疏连接

为了让卷积过程也引入稀疏性,传统的卷积网络在特征空间中使用随机稀疏连接表,比如 L e N e t 5 LeNet5 LeNet5的结构中就采用了这种方式,其论文(Gradient-Based Learning Applied to Do cument Recognition) 提到,在 L e N e t 5 LeNet5 LeNet5网络结构中, S 2 S2 S2层(大小为 14 ∗ 14 ∗ 6 14*14*6 14146)经过卷积得到 C 3 C3 C3层(大小为 10 ∗ 10 ∗ 16 10*10*16 101016),采用了如下图所示的稀疏连接表(如 C 3 C3 C3的前 4 4 4个特征(通道)图来自 S 2 S2 S2层不同但连续的三个特征卷积得来)

在这里插入图片描述

为了能使得卷积使用稠密矩阵的计算特性, A l e x N e t AlexNet AlexNet 又移除了特征空间的稀疏连接。那么问题来了,我们能否在滤波器层面上也使用稀疏模型,并使用稠密矩阵进行计算?答案是肯定的,正是我们等下要介绍的 I n c e p t i o n Inception Inception模块。另外作者在这里还提到,大量关于稀疏矩阵运算的文献(如On two-dimensional sparse matrix par-titioning: Models, methods, and a recipe)认为可以将稀疏矩阵聚类为相对密集的子矩阵来提高计算稀疏矩阵的运行效率。

总之,我们最终的目的,就是找到一个基于卷积网络的最优稀疏模块,且该模块可以通过密集组件进行构建。

2. Inception 模块结构

最终,提出了 I n c e p t i o n Inception Inception模块的简单版本,如下图所示:

在这里插入图片描述

模型有以下特点:

  1. 使用卷积实现稀疏性
  2. 使用不同尺寸的卷积核,相当于不同的稀疏连接方式,同时还能提取多尺度特征。这里使用大小为 1 ∗ 1 、 3 ∗ 3 、 5 ∗ 5 1*1、3*3、5*5 113355的卷积核,是为了卷积时方便padding
  3. 最大池化层,由于很多优秀的模型都使用最大池化,且能取得很好的效果,因此这里也添加了最大池化模块。池化大小为 3 ∗ 3 3*3 33,为了保证和输入的宽高保持一致,也使用padding,并设置步长为 1 1 1
  4. 将卷积和池化之后的特征图连接在一起,近似最优的稀疏模型

这种模型的存在的问题是:

  1. 运算量巨大

    假设输入图的大小为 28 ∗ 28 ∗ 192 28*28*192 2828192,Inception模块的几个卷积配置为: 64 64 64 1 ∗ 1 ∗ 192 1*1*192 11192的卷积核、 128 128 128 3 ∗ 3 ∗ 192 3*3*192 33192的卷积核、 32 32 32 5 ∗ 5 ∗ 192 5*5*192 55192的卷积核进行卷积。 大卷积核的计算量也会很大,我们以 5 ∗ 5 5*5 55卷积为例,计算其运算次数,使用了 32 32 32 5 ∗ 5 ∗ 192 5*5*192 55192的卷积核进行卷积,将得到 28 ∗ 28 ∗ 32 28*28*32 282832的特征图,共有 28 ∗ 28 ∗ 32 = 25088 28*28*32=25088 282832=25088个特征值,每个特征值需要执行 5 ∗ 5 ∗ 192 = 4800 5*5*192=4800 55192=4800次乘法,总共需要 计算 25088 ∗ 4800 = 120422400 25088*4800=120422400 250884800=120422400次乘法。 这里我们值计算乘法运算次数,不计算加法次数。

  2. 由于最大池化不改变输入图的通道大小,执行完卷积核池化之后需要将特征图沿通道方向拼接在一起,随着Inception模块的堆叠,模块输出的特征图通道数越来越大,导致计算崩溃。

因此对该模块的结构进行了优化,如下图所示

在这里插入图片描述

改进后的结构在每次卷积之前,最大池化之后加入 1 ∗ 1 1*1 11的卷积。还是以假设输入图的大小为 28 ∗ 28 ∗ 192 28*28*192 2828192,Inception模块的几个卷积配置为: 64 64 64 1 ∗ 1 1*1 11的卷积核、 128 128 128 3 ∗ 3 3*3 33的卷积核、 32 32 32 5 ∗ 5 5*5 55的卷积核进行卷积为例。在 5 ∗ 5 5*5 55的卷积之前,添加 16 16 16 1 ∗ 1 1*1 11的卷积核,将得到 28 ∗ 28 ∗ 16 28*28*16 282816的特征图,此时的的乘法计算次数为 28 ∗ 28 ∗ 16 ∗ 1 ∗ 1 ∗ 192 = 2408448 28*28*16*1*1*192=2408448 28281611192=2408448, 之后需要使用 32 32 32 5 ∗ 5 5*5 55的卷积核对 28 ∗ 28 ∗ 16 28*28*16 282816的特征图进行卷积,将得到 28 ∗ 28 ∗ 32 28*28*32 282832的特征图,此时需要的乘法计算量为 28 ∗ 28 ∗ 32 ∗ 5 ∗ 5 ∗ 16 = 10035200 28*28*32*5*5*16=10035200 2828325516=10035200,总共的乘法计算量为 10035200 + 2408448 = 12443648 10035200+2408448 = 12443648 10035200+2408448=12443648,如果不添加 1 ∗ 1 1*1 11卷积其乘法计算量为 120422400 120422400 120422400, 添加之后计算量仅为之前的 1 10 \frac{1}{10} 101

3. GoogLeNet网络结构

利用Inception模块构建GoogLeNet,参数设置如下表所示

  1. 网络中所使用的卷积,包括Inception模块内的卷积,均使用Relu激活函数
  2. 输入图像大小为 224 ∗ 224 ∗ 3 224*224*3 2242243, 预处理为减去训练集平均值
  3. “#3×3 reduce” 和“#5×5 reduce” 表示在执行 3 × 3 3\times3 3×3 5 × 5 5\times5 5×5卷积之前,使用 1 × 1 1\times1 1×1卷积核的个数
  4. “pool proj”表示 m a x − p o o l i n g max-pooling maxpooling之后使用的 1 × 1 1\times1 1×1卷积核的个数
  5. ‘’deep‘’表示网络深度,所有的值加起来刚好是 22 22 22层,这里只计算权重层,pooling层无可训练参数,不计入网络深度
  6. ‘’params‘’表示某一层的待训练参数量
  7. ‘’ops‘’表示某一层的计算量
  8. 使用平均池化代替最后的全连接层,结果发现替换全连接层之后,top1的分类精度提高了 0.6 % 0.6\% 0.6%
  9. 为了方便迁移学习,在平均池化之后加了一层具有 1000 1000 1000个神经元的全连接层,使用 s o f t m a x softmax softmax激活函数
  10. 在全局池化层和全连接层之间,使用了Dropout
  11. 网络太深可能导致梯度消失,且作者发现该网络结构的浅层也能有很好的判决能力,因此在网络中添加了两个辅助分类器,防止梯度消失并增加正则化。辅助分类器的损失以 0.3 0.3 0.3的权重加入主分类器的损失中。 辅助分类器的配置如下:
    • 在inception(4a)和inception(4d)的输出位置添加两个辅助分类器,首先是使用大小为 5 ∗ 5 5*5 55,步长为 3 3 3的平均池化
    • 使用128个 1 ∗ 1 1*1 11的卷积进行降维,使用 r e l u relu relu激活函数
    • 使用具有 1024 1024 1024个神经元的全连接层,使用 r e l u relu relu激活函数
    • 使用Dropout,丢失率 70 % 70\% 70%
    • 使用具有 1000 1000 1000个神经元的全连接层,使用 s o f t m a x softmax softmax激活函数

其完整的网络结构如下图所示:

在这里插入图片描述

4. Tensorflow实现GoogLeNet结构

#!/usr/bin/python3# @Time    : 2021/04/01 10:07
# @Author  : 
# @File    : GoogLeNet
# @Software: PyCharm
# @Description : 使用TensorFlow实现GoogLeNet网络结构import tensorflow as tf
from tensorflow.keras.layers import Layer,Conv2D,MaxPool2D,Dropout,Activation,Flatten,Dense,AvgPool2D,concatenate,Softmax,Input# 定义Inception 模块类
class Inception(Layer,):def __init__(self, cov1_1,cov_reduce3_3,cov3_3,cov_reduce5_5,cov5_5,pool_proj, **kwargs):super(Inception, self).__init__(**kwargs)self.branch1 = Conv2D(filters=cov1_1,kernel_size=1,activation='relu') self.branch2 = tf.keras.Sequential([Conv2D(filters=cov_reduce3_3,kernel_size=1,activation='relu'),Conv2D(filters=cov3_3,kernel_size=3,padding='same',activation='relu')])self.branch3 = tf.keras.Sequential([Conv2D(filters=cov_reduce5_5,kernel_size=1,activation='relu'),Conv2D(filters=cov5_5,kernel_size=5,padding='same',activation='relu')])self.branch4 = tf.keras.Sequential([MaxPool2D(pool_size=3,strides=1,padding='same'),Conv2D(filters=pool_proj,kernel_size=1,activation='relu')])def call(self,inputs):branch1 = self.branch1(inputs)branch2 = self.branch2(inputs)branch3 = self.branch3(inputs)branch4 = self.branch4(inputs)outputs = concatenate([branch1,branch2,branch3,branch4])return outputs# 定义辅助分类器
class AuxiliaryClassifier(Layer,):def __init__(self, **kwargs):super(AuxiliaryClassifier, self).__init__(**kwargs)self.avgpool = AvgPool2D(pool_size=5,strides=3)self.conv = Conv2D(filters=128,kernel_size=1,activation='relu')self.flatten = Flatten()self.fc1 = Dense(units=1024,activation="relu")self.dropout = Dropout(rate=0.7)self.fc2 = Dense(units=1000,activation="softmax")def call(self,inputs):x = self.avgpool(inputs)x = self.conv(x)x = self.flatten(x)x = self.fc1(x)x = self.dropout(x)outputs = self.fc2(x)return outputs     def GoogLeNet(model_name='GoogLeNet',im_height=224,im_width=224,class_num=1000):
#     inputs = tf.keras.Input(shape=(im_height,im_width,3),name='Inputs')inputs = Input(shape=(im_height,im_width,3),name='Inputs')# input [224,224,3]  -->  [112,112,64]x = Conv2D(filters=64,kernel_size=7,strides=2,padding='same',activation='relu',name='convolution-1')(inputs)# [112,112,64]  -->  [56,56,64]x = MaxPool2D(pool_size=3,strides=2,padding='same',name='max-pool-1')(x)# [56,56,64]  -->  [56,56,192]x = Conv2D(filters=192,kernel_size=3,strides=1,padding='same',activation='relu',name='convolution-2')(x)# [56,56,192]  -->  [28,28,192]x = MaxPool2D(pool_size=3,strides=2,padding='same',name='max-pool-2')(x)# [28,28,192] --> [28, 28, 256]x = Inception(64,96,128,16,32,32,name='inception-3a')(x)# [28, 28, 256] --> [28, 28, 480]x = Inception(128,128,192,32,96,64,name='inception-3b')(x)# [28, 28, 480] --> [14, 14, 480]x = MaxPool2D(pool_size=3,strides=2,padding='same',name='max-pool-3')(x)# [14, 14, 480] --> [14, 14, 512]x = Inception(192,96,208,16,48,64,name='inception-4a')(x)# [14, 14, 512] --> [1000]outputs1 = AuxiliaryClassifier(name='AuxiliaryClassifier-1')(x)# [14, 14, 512] --> [14, 14, 512]x = Inception(160,112,224,24,64,64,name='inception-4b')(x)# [14, 14, 512] --> [14, 14, 512]x = Inception(128,128,256,24,64,64,name='inception-4c')(x)# [14, 14, 512] --> [14, 14, 528]x = Inception(112,144,288,32,64,64,name='inception-4d')(x)# [14, 14, 528] --> [1000,]outputs2 = AuxiliaryClassifier(name='AuxiliaryClassifier-2')(x)# [14, 14, 528] --> [14, 14, 832]x = Inception(256,160,320,32,128,128,name='inception-4e')(x)# [14, 14, 832] --> [7, 7, 832]x = MaxPool2D(pool_size=3,strides=2,padding='same',name='max-pool-4')(x)# [7, 7, 832] --> [7, 7, 832]x = Inception(256,160,320,32,128,128,name='inception-5a')(x)# [7, 7, 832] --> [7, 7, 1024]x = Inception(384,192,384,48,128,128,name='inception-5b')(x)# [7, 7, 1024] --> [1, 1, 1024]x = AvgPool2D(pool_size=7,strides=1,name='avg-pool')(x)# [1, 1, 1024] --> [1024,]x = Flatten(name='flatten')(x)x = Dropout(rate=0.4,name='dropout')(x)x = Dense(units=1000,name='linear')(x)outputs = Softmax(name='softmax')(x)model = tf.keras.models.Model(inputs=inputs,outputs=[outputs1,outputs2,outputs],name='GoogLeNet')return modelmodel = GoogLeNet()
model.summary()

模型摘要如下

Model: "GoogLeNet"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
Inputs (InputLayer)             [(None, 224, 224, 3) 0                                            
__________________________________________________________________________________________________
convolution-1 (Conv2D)          (None, 112, 112, 64) 9472        Inputs[0][0]                     
__________________________________________________________________________________________________
max-pool-1 (MaxPooling2D)       (None, 56, 56, 64)   0           convolution-1[0][0]              
__________________________________________________________________________________________________
convolution-2 (Conv2D)          (None, 56, 56, 192)  110784      max-pool-1[0][0]                 
__________________________________________________________________________________________________
max-pool-2 (MaxPooling2D)       (None, 28, 28, 192)  0           convolution-2[0][0]              
__________________________________________________________________________________________________
inception-3a (Inception)        (None, 28, 28, 256)  163696      max-pool-2[0][0]                 
__________________________________________________________________________________________________
inception-3b (Inception)        (None, 28, 28, 480)  388736      inception-3a[0][0]               
__________________________________________________________________________________________________
max-pool-3 (MaxPooling2D)       (None, 14, 14, 480)  0           inception-3b[0][0]               
__________________________________________________________________________________________________
inception-4a (Inception)        (None, 14, 14, 512)  376176      max-pool-3[0][0]                 
__________________________________________________________________________________________________
inception-4b (Inception)        (None, 14, 14, 512)  449160      inception-4a[0][0]               
__________________________________________________________________________________________________
inception-4c (Inception)        (None, 14, 14, 512)  510104      inception-4b[0][0]               
__________________________________________________________________________________________________
inception-4d (Inception)        (None, 14, 14, 528)  605376      inception-4c[0][0]               
__________________________________________________________________________________________________
inception-4e (Inception)        (None, 14, 14, 832)  868352      inception-4d[0][0]               
__________________________________________________________________________________________________
max-pool-4 (MaxPooling2D)       (None, 7, 7, 832)    0           inception-4e[0][0]               
__________________________________________________________________________________________________
inception-5a (Inception)        (None, 7, 7, 832)    1043456     max-pool-4[0][0]                 
__________________________________________________________________________________________________
inception-5b (Inception)        (None, 7, 7, 1024)   1444080     inception-5a[0][0]               
__________________________________________________________________________________________________
avg-pool (AveragePooling2D)     (None, 1, 1, 1024)   0           inception-5b[0][0]               
__________________________________________________________________________________________________
flatten (Flatten)               (None, 1024)         0           avg-pool[0][0]                   
__________________________________________________________________________________________________
dropout (Dropout)               (None, 1024)         0           flatten[0][0]                    
__________________________________________________________________________________________________
linear (Dense)                  (None, 1000)         1025000     dropout[0][0]                    
__________________________________________________________________________________________________
AuxiliaryClassifier-1 (Auxiliar (None, 1000)         3188840     inception-4a[0][0]               
__________________________________________________________________________________________________
AuxiliaryClassifier-2 (Auxiliar (None, 1000)         3190888     inception-4d[0][0]               
__________________________________________________________________________________________________
softmax (Softmax)               (None, 1000)         0           linear[0][0]                     
==================================================================================================
Total params: 13,374,120
Trainable params: 13,374,120
Non-trainable params: 0

这篇关于GoogLeNet详细介绍与TensorFlow实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/646201

相关文章

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

揭秘未来艺术:AI绘画工具全面介绍

📑前言 随着科技的飞速发展,人工智能(AI)已经逐渐渗透到我们生活的方方面面。在艺术创作领域,AI技术同样展现出了其独特的魅力。今天,我们就来一起探索这个神秘而引人入胜的领域,深入了解AI绘画工具的奥秘及其为艺术创作带来的革命性变革。 一、AI绘画工具的崛起 1.1 颠覆传统绘画模式 在过去,绘画是艺术家们通过手中的画笔,蘸取颜料,在画布上自由挥洒的创造性过程。然而,随着AI绘画工

VMware9.0详细安装

双击VMware-workstation-full-9.0.0-812388.exe文件: 直接点Next; 这里,我选择了Typical(标准安装)。 因为服务器上只要C盘,所以我选择安装在C盘下的vmware文件夹下面,然后点击Next; 这里我把√取消了,每次启动不检查更新。然后Next; 点击Next; 创建快捷方式等,点击Next; 继续Cont

20.Spring5注解介绍

1.配置组件 Configure Components 注解名称说明@Configuration把一个类作为一个loC容 器 ,它的某个方法头上如果注册7@Bean , 就会作为这个Spring容器中的Bean@ComponentScan在配置类上添加@ComponentScan注解。该注解默认会扫描该类所在的包下所有的配置类,相当于之前的 <context:component-scan>@Sc

通过SSH隧道实现通过远程服务器上外网

搭建隧道 autossh -M 0 -f -D 1080 -C -N user1@remotehost##验证隧道是否生效,查看1080端口是否启动netstat -tuln | grep 1080## 测试ssh 隧道是否生效curl -x socks5h://127.0.0.1:1080 -I http://www.github.com 将autossh 设置为服务,隧道开机启动

(超详细)YOLOV7改进-Soft-NMS(支持多种IoU变种选择)

1.在until/general.py文件最后加上下面代码 2.在general.py里面找到这代码,修改这两个地方 3.之后直接运行即可

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测 目录 时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测基本介绍程序设计参考资料 基本介绍 MATLAB实现LSTM时间序列未来多步预测-递归预测。LSTM是一种含有LSTM区块(blocks)或其他的一种类神经网络,文献或其他资料中LSTM区块可能被描述成智能网络单元,因为

vue项目集成CanvasEditor实现Word在线编辑器

CanvasEditor实现Word在线编辑器 官网文档:https://hufe.club/canvas-editor-docs/guide/schema.html 源码地址:https://github.com/Hufe921/canvas-editor 前提声明: 由于CanvasEditor目前不支持vue、react 等框架开箱即用版,所以需要我们去Git下载源码,拿到其中两个主

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现

基于Springboot + vue 的抗疫物质管理系统的设计与实现

目录 📚 前言 📑摘要 📑系统流程 📚 系统架构设计 📚 数据库设计 📚 系统功能的具体实现    💬 系统登录注册 系统登录 登录界面   用户添加  💬 抗疫列表展示模块     区域信息管理 添加物资详情 抗疫物资列表展示 抗疫物资申请 抗疫物资审核 ✒️ 源码实现 💖 源码获取 😁 联系方式 📚 前言 📑博客主页: