Python----神经网络(《Going deeper with convolutions》论文解读和GoogLeNet网络)

一、论文《Going deeper with convolutions》

1.1、 论文基本信息

  • 论文标题: Going deeper with convolutions

  • 作者: Christian Szegedy, Wei Liu, Yangqing Jia, Pierre Sermanet, Scott Reed, Dragomir Anguelov, Dumitru Erhan, Vincent Vanhoucke, Andrew Rabinovich

  • 发表时间: 2014 年 9 月 17 日 (arXiv v1 版本)

  • 所属领域: 计算机视觉 (CV)

  • 主要贡献: 提出 Inception 架构,并在 ImageNet Large-Scale Visual Recognition Challenge 2014 (ILSVRC14) 中取得了领先的分类和检测性能

1.2、作者背景

论文作者主要来自 Google Inc.,也有部分作者来自 University of North Carolina, Chapel Hill 和 University of Michigan.作者团队在深度学习和计算机视觉领域有着深厚的积累。Christian Szegedy 等人在后续的 Inception 系列工作中继续深入研究了卷积神经网络的架构设计。

1.3、论文主要内容

  • Inception 架构: 论文的核心是提出了一种新的卷积神经网络架构,称为 Inception.

    • Inception 模块通过并行使用不同尺度的卷积核 (1x1, 3x3, 5x5) 和池化操作,能够有效地捕捉图像中的多尺度特征.

    • 1x1 卷积用于降维和增加非线性.

  • GoogLeNet 模型: 论文中提出的一个具体 Inception 架构的实现被称为 GoogLeNet.

    • GoogLeNet 是一个 22 层深的卷积神经网络.

    • GoogLeNet 在 ILSVRC14 比赛中取得了优异的成绩.

  • 性能提升: GoogLeNet 在 ILSVRC14 分类和检测任务中均取得了显著的性能提升,超越了当时的 state-of-the-art 方法.

  • 计算效率: Inception 架构的设计注重计算效率,GoogLeNet 在保持高性能的同时,参数数量和计算量都相对较小.

1.4、论文特点

多尺度特征提取: Inception 模块是论文的主要创新点,它能够并行地提取图像在不同尺度上的特征,从而提高网络的表示能力.

计算效率高: 通过 1x1 卷积进行降维,Inception 架构有效地减少了参数数量和计算量,使得更深更宽的网络成为可能.

**设计思想新颖:**Inception 架构的设计受到了 Arora 等人关于稀疏网络理论的启发.并通过密集模块来近似稀疏连接.

**工程实现精巧:**论文不仅关注模型性能,还注重工程实现上的优化,例如使用辅助分类器来改善梯度传播.使用 Polyak averaging 来提高模型稳定性.

1.5、论文作用

推动了卷积神经网络的发展: GoogLeNet 提出的 Inception 架构对后续的卷积神经网络设计产生了深远的影响.许多新的网络架构都借鉴了其多尺度特征提取和计算效率高的思想。

提高了计算机视觉任务的性能: GoogLeNet 在 ILSVRC14 比赛中取得了优异的成绩,证明了 Inception 架构在图像分类和目标检测等任务中的有效性,推动了计算机视觉领域的发展.

促进了深度学习的工程化: 论文注重计算效率和实际应用,GoogLeNet 的设计使得深度学习模型在资源受限的设备上部署成为可能,促进了深度学习的工程化应用.

1.6、论文影响

Inception 系列架构: GoogLeNet 是 Inception 系列的开山之作,后续的 Inception v2、v3、Inception-ResNet 等架构都是在其基础上发展而来,不断提升了模型的性能和效率。

1x1 卷积的广泛应用: 1x1 卷积在 GoogLeNet 中被证明是一种非常有效的降维和特征变换手段,现在已经成为现代卷积神经网络中的标准配置,例如 ResNet 中的瓶颈层.

网络架构设计的启发: GoogLeNet 的成功启发了人们对网络架构设计的更多思考,例如如何有效地组合不同类型的层,如何平衡模型性能和计算效率等.

自动化网络架构搜索 (NAS) 的发展: GoogLeNet 中对稀疏连接的探索和对网络拓扑结构的思考,也对后续的自动化网络架构搜索领域的发展产生了一定的影响.

1.7、深入探讨

1.7.1、Inception模块细节

Inception模块通过1x1卷积实现了多个关键功能。首先,1x1卷积不仅有助于降维和增加网络的非线性性,还能进行跨通道的特征变换,使得网络能够学习复杂的特征组合。此外,不同尺寸的卷积核在Inception中能够有效捕获多尺度特征:小卷积核专注于细节,而大卷积核则捕捉全局结构,从而帮助网络学习最佳特征组合。在稀疏性与效率之间的权衡上,Inception通过采用稀疏连接的设计原则,达成了在保持计算效率的同时,近似稀疏性。

1.7.2、 GoogLeNet架构

GoogLeNet的结构具有深度和复杂性,其包含22层(参数层)或27层(包括池化层),由约100个模块构成,体现了Inception在构建深度网络时的高效性能。同时,采用辅助分类器的设计,改善了深度网络中的梯度流动,充当了正则化器,增强了训练效果。此外,GoogLeNet中使用平均池化替代全连接层,这一设计灵感源自"Network in Network",不仅减少了参数量,还提高了对空间平移的鲁棒性。

1.7.3、训练方法

为了增强模型的泛化能力和鲁棒性,GoogLeNet采用了数据增强技术,如随机裁剪和颜色失真等,以增加训练数据的多样性。在学习率策略方面,通过在训练过程中逐步降低学习率,可以实现更细致的参数调整,从而提高模型的整体性能。此外,应用Polyak平均可以稳定训练过程并改善最终结果,通过对多个模型版本进行平均,提高了结果的可靠性。

1.7.4、实验结果

在实验中,GoogLeNet展示了其卓越的分类性能,实现了比AlexNet更低的top-5错误率,表明其在参数效率上的优势。在目标检测任务中,通过将R-CNN与GoogLeNet及MultiBox结合,可以显著提升目标检测的准确性和召回率,标志着其在实际应用中的有效性。

1.7.5、GoogLeNet的影响

GoogLeNet不仅为Inception架构的发展奠定了基础,催生了Inception v2、v3和Inception-ResNet等后续架构,同时也对其他深度学习网络产生了深远影响。例如,ResNet中的1x1卷积瓶颈层受到了GoogLeNet高效设计理念的启发。GoogLeNet在处理多尺度特征方面的创新,极大地影响了后续的卷积神经网络(CNN)架构,推动了现代深度学习的发展。

二、GoogLeNet网络

2.1、网络的基本介绍

GoogLeNet是一种流行的神经网络架构,它通常用于图像分类任务。该网络架构是 在2014年ImageNet挑战赛中由Google团队首次提出的,并且在那次分类比赛中获得 了第一名的成绩。

GoogLeNet的主要特点是采用了一种名为"Inception"模块的结构,该模块可以有效 地捕捉图像中的多尺度特征。 在Inception模块中,网络结构被分成多个并行的分支,每个分支都用于捕捉不同尺 度的特征。这样,GoogLeNet网络就可以更有效地学习图像的特征,并且还可以减 少网络的体积。

GoogLeNet还采用了一种名为"平均池化"的技术,该技术可以在不改变图像尺寸的情 况下对图像进行降采样。这样,GoogLeNet网络就可以更快地处理图像,并且还可 以提高网络的鲁棒性。

2.2、Inception结构

Inception结构由谷歌的研究人员在2014年提出,它的名字来源于电影《盗梦空间》,因为它设计时使用了多个并行的卷积神经网络模块,这些模块之间形成了一个 嵌套的结构,就像人在梦中穿梭于不同场景中一样。

Inception结构的思想和之前的卷积思想不同,LeNet-5模型是将不同的卷积层通过串 联连接起来的,但是Inception结构是通过串联+并联的方式将卷积层连接起来的。 Inception结构是对输入图像并行地执行多个卷积运算或池化操作,并将所有输出结 果拼接为一个非常深的特征图,且不同大小卷积核的卷积运算可以得到图像中的不同 信息,处理获取到的图像中的不同信息可以得到更好的图像特征。

Inception结构通常用于图像分类和识别任务,因为它能够有效地捕捉图像中的细节 信息。它的主要优势在于能够以高效的方式处理大量的数据,并且模型的参数量相对 较少,这使得它能够在不同的设备上运行。

2.3、1x1卷积核进行降维

Inception结构的四个分支中,每个分支所得到的特征矩阵的宽和高必 须是相同的,然后再将四个分支的特征矩阵沿着深度方向进行拼接。

拼接指的是:A分支宽、高、channels为112x112x32的矩阵,B分支宽、高、 channels为112x112x64的矩阵,C分支宽、高、channels为112x112x16的矩阵,D 分支宽、高、channels为112x112x8的矩阵,那么经过拼接后,得到一个 112x112x120的矩阵,120是由32+64+16+8而来。

卷积有着降维的作用,例如现在有一个输入特征矩阵为n*n*512

  1. 直接经过64个5x5的卷积核进行卷积,那么需要5x5x512x64=819200的参数量;

  2. 经过1x1的卷积再经过64个5x5的卷积,这里1x1的卷积核个数例如是24个,所以 需要的参数量为1x1x512x24+1x1x24x64=50688。

2.4、辅助分类器

辅助分类器通常与主要的分类器结合使用,以帮助模型更好地理解图像中的细节和复 杂模式。这种技术可以提高模型的泛化能力,使其更准确地预测未知的图像,在 GoogLeNet中有两个辅助分类器,分别在Inception4a和Inception4d。

两个辅助分类器的结构式完全一致的:

  1. 平均池化层,池化核大小为5x5,步长为3。

  2. 卷积层,128个卷积核大小是1x1,步长为1。

  3. 全连接层,节点个数为1024个。

  4. 在全连接之后接一个dropout,比例为70%随机失活神经元。

  5. 全连接输出,由于数据集是ImageNet,所以是1000个节点,然后经过 softmax。

注意:辅助分类器在训练的时候使用,在预测的时候不使用。

2.5、 网络的问题

尽管GoogLeNet在当时取得了很好的成绩,但它也有一些缺点。

  1. 由于其使用了Inception模块,网络的计算复杂度较高。这使得GoogLeNet的训 练速度较慢,不太适合对实时性要求较高的应用。

  2. GoogLeNet的网络结构相对复杂,不太容易理解,并且由于辅助分类器的存在, 这使得在调试和优化网络时较为困难。

总之,GoogLeNet的计算复杂度高、网络结构复杂是其主要缺点。

2.6、网络结构

在上图中,type是每一层的结构,patch size/stride是卷积或者池化的核大小/步长, output size是本层的输出尺寸,depth是这一层的卷积深度,就是卷积层数,param 是本层的参数量,ops是运算量。其它的参数是Inception结构中的对应卷积的的参数。

2.7、设计思路

python 复制代码
import torch
import torch.nn as nn
from torchsummary import summary


class Inception(nn.Module):
    """
    Inception 模块的实现。
    这个模块并行地应用不同尺寸的卷积和池化操作。
    """

    def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj):
        super(Inception, self).__init__()

        # 1x1 卷积分支
        self.branch1 = nn.Conv2d(in_channels, ch1x1, kernel_size=1)

        # 1x1 卷积 -> 3x3 卷积分支
        self.branch2 = nn.Sequential(
            nn.Conv2d(in_channels, ch3x3red, kernel_size=1),
            nn.ReLU(inplace=True),  # 原地ReLU激活,节省内存
            nn.Conv2d(ch3x3red, ch3x3, kernel_size=3, padding=1),  # padding=1 保持尺寸
            nn.ReLU(inplace=True)
        )

        # 1x1 卷积 -> 5x5 卷积分支
        self.branch3 = nn.Sequential(
            nn.Conv2d(in_channels, ch5x5red, kernel_size=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(ch5x5red, ch5x5, kernel_size=5, padding=2),  # padding=2 保持尺寸
            nn.ReLU(inplace=True)
        )

        # 3x3 池化 -> 1x1 卷积分支
        self.branch4 = nn.Sequential(
            nn.MaxPool2d(kernel_size=3, stride=1, padding=1),  # stride=1, padding=1 保持尺寸
            nn.Conv2d(in_channels, pool_proj, kernel_size=1),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        branch1 = self.branch1(x)
        branch2 = self.branch2(x)
        branch3 = self.branch3(x)
        branch4 = self.branch4(x)
        out = torch.cat([branch1, branch2, branch3, branch4], 1)  # 在通道维度上连接
        return out


class GoodNet(nn.Module):
    """
    一个简化的 GoogLeNet 网络实现。
    注意:这并非严格意义上的 GoogLeNet,而是为了演示 Inception 模块的使用。
    """

    def __init__(self, num_classier=1000):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),  # 初始卷积层,padding 保持尺寸
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),  # 最大池化层,padding 影响输出尺寸

            nn.Conv2d(64, 64, kernel_size=1, stride=1, padding=0),  # 1x1 卷积,减少通道数
            nn.ReLU(inplace=True),

            nn.Conv2d(64, 192, kernel_size=3, stride=1, padding=1),  # 3x3 卷积
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),

            # Inception modules
            Inception(192, 64, 96, 128, 16, 32, 32),  # Inception 模块,参数来自 GoogLeNet
            Inception(256, 128, 128, 192, 32, 96, 64),  # Inception 模块
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),  # 添加MaxPool2d
            Inception(480, 192, 96, 208, 16, 48, 64),  # Inception 模块
            Inception(512, 160, 112, 224, 24, 64, 64),  # Inception 模块
            Inception(512, 128, 128, 256, 24, 64, 64),  # Inception 模块
            Inception(512, 112, 144, 288, 32, 64, 64),  # Inception 模块
            Inception(528, 256, 160, 320, 32, 128, 128),  # Inception 模块

            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),  # 最大池化层
            Inception(832, 256, 160, 320, 32, 128, 128),  # Inception 模块
            Inception(832, 384, 192, 384, 48, 128, 128),  # Inception 模块

            nn.AvgPool2d(kernel_size=7, stride=1, padding=0),  # 全局平均池化
            nn.Dropout(0.4)  # Dropout 正则化
        )
        self.fc = nn.Linear(1024, num_classier)  # 全连接层,用于分类

    def forward(self, x):
        x = self.features(x)  # 前向传播通过特征提取器
        # x = x.view(x.size(0), -1)  # 展平特征图,准备输入全连接层
        x=torch.flatten(x,1)
        x = self.fc(x)  # 前向传播通过全连接层
        return x


if __name__ == '__main__':
    model = GoodNet()  # 创建模型实例
    print(summary(model, (3, 224, 224)))  # 打印模型结构和参数信息
python 复制代码
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1         [-1, 64, 112, 112]           9,472
              ReLU-2         [-1, 64, 112, 112]               0
         MaxPool2d-3           [-1, 64, 56, 56]               0
            Conv2d-4           [-1, 64, 56, 56]           4,160
              ReLU-5           [-1, 64, 56, 56]               0
            Conv2d-6          [-1, 192, 56, 56]         110,784
              ReLU-7          [-1, 192, 56, 56]               0
         MaxPool2d-8          [-1, 192, 28, 28]               0
            Conv2d-9           [-1, 64, 28, 28]          12,352
           Conv2d-10           [-1, 96, 28, 28]          18,528
             ReLU-11           [-1, 96, 28, 28]               0
           Conv2d-12          [-1, 128, 28, 28]         110,720
             ReLU-13          [-1, 128, 28, 28]               0
           Conv2d-14           [-1, 16, 28, 28]           3,088
             ReLU-15           [-1, 16, 28, 28]               0
           Conv2d-16           [-1, 32, 28, 28]          12,832
             ReLU-17           [-1, 32, 28, 28]               0
        MaxPool2d-18          [-1, 192, 28, 28]               0
           Conv2d-19           [-1, 32, 28, 28]           6,176
             ReLU-20           [-1, 32, 28, 28]               0
        Inception-21          [-1, 256, 28, 28]               0
           Conv2d-22          [-1, 128, 28, 28]          32,896
           Conv2d-23          [-1, 128, 28, 28]          32,896
             ReLU-24          [-1, 128, 28, 28]               0
           Conv2d-25          [-1, 192, 28, 28]         221,376
             ReLU-26          [-1, 192, 28, 28]               0
           Conv2d-27           [-1, 32, 28, 28]           8,224
             ReLU-28           [-1, 32, 28, 28]               0
           Conv2d-29           [-1, 96, 28, 28]          76,896
             ReLU-30           [-1, 96, 28, 28]               0
        MaxPool2d-31          [-1, 256, 28, 28]               0
           Conv2d-32           [-1, 64, 28, 28]          16,448
             ReLU-33           [-1, 64, 28, 28]               0
        Inception-34          [-1, 480, 28, 28]               0
        MaxPool2d-35          [-1, 480, 14, 14]               0
           Conv2d-36          [-1, 192, 14, 14]          92,352
           Conv2d-37           [-1, 96, 14, 14]          46,176
             ReLU-38           [-1, 96, 14, 14]               0
           Conv2d-39          [-1, 208, 14, 14]         179,920
             ReLU-40          [-1, 208, 14, 14]               0
           Conv2d-41           [-1, 16, 14, 14]           7,696
             ReLU-42           [-1, 16, 14, 14]               0
           Conv2d-43           [-1, 48, 14, 14]          19,248
             ReLU-44           [-1, 48, 14, 14]               0
        MaxPool2d-45          [-1, 480, 14, 14]               0
           Conv2d-46           [-1, 64, 14, 14]          30,784
             ReLU-47           [-1, 64, 14, 14]               0
        Inception-48          [-1, 512, 14, 14]               0
           Conv2d-49          [-1, 160, 14, 14]          82,080
           Conv2d-50          [-1, 112, 14, 14]          57,456
             ReLU-51          [-1, 112, 14, 14]               0
           Conv2d-52          [-1, 224, 14, 14]         226,016
             ReLU-53          [-1, 224, 14, 14]               0
           Conv2d-54           [-1, 24, 14, 14]          12,312
             ReLU-55           [-1, 24, 14, 14]               0
           Conv2d-56           [-1, 64, 14, 14]          38,464
             ReLU-57           [-1, 64, 14, 14]               0
        MaxPool2d-58          [-1, 512, 14, 14]               0
           Conv2d-59           [-1, 64, 14, 14]          32,832
             ReLU-60           [-1, 64, 14, 14]               0
        Inception-61          [-1, 512, 14, 14]               0
           Conv2d-62          [-1, 128, 14, 14]          65,664
           Conv2d-63          [-1, 128, 14, 14]          65,664
             ReLU-64          [-1, 128, 14, 14]               0
           Conv2d-65          [-1, 256, 14, 14]         295,168
             ReLU-66          [-1, 256, 14, 14]               0
           Conv2d-67           [-1, 24, 14, 14]          12,312
             ReLU-68           [-1, 24, 14, 14]               0
           Conv2d-69           [-1, 64, 14, 14]          38,464
             ReLU-70           [-1, 64, 14, 14]               0
        MaxPool2d-71          [-1, 512, 14, 14]               0
           Conv2d-72           [-1, 64, 14, 14]          32,832
             ReLU-73           [-1, 64, 14, 14]               0
        Inception-74          [-1, 512, 14, 14]               0
           Conv2d-75          [-1, 112, 14, 14]          57,456
           Conv2d-76          [-1, 144, 14, 14]          73,872
             ReLU-77          [-1, 144, 14, 14]               0
           Conv2d-78          [-1, 288, 14, 14]         373,536
             ReLU-79          [-1, 288, 14, 14]               0
           Conv2d-80           [-1, 32, 14, 14]          16,416
             ReLU-81           [-1, 32, 14, 14]               0
           Conv2d-82           [-1, 64, 14, 14]          51,264
             ReLU-83           [-1, 64, 14, 14]               0
        MaxPool2d-84          [-1, 512, 14, 14]               0
           Conv2d-85           [-1, 64, 14, 14]          32,832
             ReLU-86           [-1, 64, 14, 14]               0
        Inception-87          [-1, 528, 14, 14]               0
           Conv2d-88          [-1, 256, 14, 14]         135,424
           Conv2d-89          [-1, 160, 14, 14]          84,640
             ReLU-90          [-1, 160, 14, 14]               0
           Conv2d-91          [-1, 320, 14, 14]         461,120
             ReLU-92          [-1, 320, 14, 14]               0
           Conv2d-93           [-1, 32, 14, 14]          16,928
             ReLU-94           [-1, 32, 14, 14]               0
           Conv2d-95          [-1, 128, 14, 14]         102,528
             ReLU-96          [-1, 128, 14, 14]               0
        MaxPool2d-97          [-1, 528, 14, 14]               0
           Conv2d-98          [-1, 128, 14, 14]          67,712
             ReLU-99          [-1, 128, 14, 14]               0
       Inception-100          [-1, 832, 14, 14]               0
       MaxPool2d-101            [-1, 832, 7, 7]               0
          Conv2d-102            [-1, 256, 7, 7]         213,248
          Conv2d-103            [-1, 160, 7, 7]         133,280
            ReLU-104            [-1, 160, 7, 7]               0
          Conv2d-105            [-1, 320, 7, 7]         461,120
            ReLU-106            [-1, 320, 7, 7]               0
          Conv2d-107             [-1, 32, 7, 7]          26,656
            ReLU-108             [-1, 32, 7, 7]               0
          Conv2d-109            [-1, 128, 7, 7]         102,528
            ReLU-110            [-1, 128, 7, 7]               0
       MaxPool2d-111            [-1, 832, 7, 7]               0
          Conv2d-112            [-1, 128, 7, 7]         106,624
            ReLU-113            [-1, 128, 7, 7]               0
       Inception-114            [-1, 832, 7, 7]               0
          Conv2d-115            [-1, 384, 7, 7]         319,872
          Conv2d-116            [-1, 192, 7, 7]         159,936
            ReLU-117            [-1, 192, 7, 7]               0
          Conv2d-118            [-1, 384, 7, 7]         663,936
            ReLU-119            [-1, 384, 7, 7]               0
          Conv2d-120             [-1, 48, 7, 7]          39,984
            ReLU-121             [-1, 48, 7, 7]               0
          Conv2d-122            [-1, 128, 7, 7]         153,728
            ReLU-123            [-1, 128, 7, 7]               0
       MaxPool2d-124            [-1, 832, 7, 7]               0
          Conv2d-125            [-1, 128, 7, 7]         106,624
            ReLU-126            [-1, 128, 7, 7]               0
       Inception-127           [-1, 1024, 7, 7]               0
       AvgPool2d-128           [-1, 1024, 1, 1]               0
         Dropout-129           [-1, 1024, 1, 1]               0
          Linear-130                 [-1, 1000]       1,025,000
================================================================
Total params: 6,998,552
Trainable params: 6,998,552
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 66.84
Params size (MB): 26.70
Estimated Total Size (MB): 94.11
----------------------------------------------------------------
相关推荐
Allen_LVyingbo2 分钟前
数智读书笔记系列035《未来医疗:医疗4.0引领第四次医疗产业变革》
人工智能·经验分享·笔记·健康医疗
zzc9217 分钟前
时频图数据集更正程序,去除坐标轴白边及调整对应的标签值
人工智能·深度学习·数据集·标签·时频图·更正·白边
isNotNullX9 分钟前
什么是数据分析?常见方法全解析
大数据·数据库·数据仓库·人工智能·数据分析
烛阴10 分钟前
一文搞懂 Python 闭包:让你的代码瞬间“高级”起来!
前端·python
riveting18 分钟前
明远智睿H618:开启多场景智慧生活新时代
人工智能·嵌入式硬件·智能硬件·lga封装·3506
JosieBook20 分钟前
【Java编程动手学】Java中的数组与集合
java·开发语言·python
夜阑卧听风吹雨,铁马冰河入梦来32 分钟前
Spring AI 阿里巴巴学习
人工智能·学习·spring
c76944 分钟前
【文献笔记】Automatic Chain of Thought Prompting in Large Language Models
人工智能·笔记·语言模型·论文笔记
lang201509281 小时前
Reactor ConnectableFlux支持多订阅者
java·网络
在下Z.1 小时前
利用TCP协议,创建一个多人聊天室
网络·网络协议·tcp/ip