1. 首页
  2. 机器学习

如何开发深层卷积神经网络对狗和猫的照片分类

狗与猫的数据集是标准的计算机视觉数据集,涉及将照片分类为包含狗或猫。

尽管这个问题听起来很简单,但只有在最近几年使用深度学习卷积神经网络才有效解决了这个问题。在有效解决数据集的同时,它可以用作学习和练习如何开发,评估和使用卷积深度学习神经网络从头开始进行图像分类的基础。

这包括如何开发健壮的测试工具以评估模型的性能,如何探索对模型的改进,如何保存模型并随后加载模型以对新数据进行预测。

在本教程中,您将发现如何开发卷积神经网络来对狗和猫的照片进行分类。

完成本教程后,您将知道:

  • 如何加载和准备猫和狗的照片以进行建模。
  • 如何从头开始开发用于照片分类的卷积神经网络并提高模型性能。
  • 如何使用转移学习开发用于照片分类的模型。

本教程分为5个部分。他们是:

狗与猫数据集准备

开发基线CNN模型

开发模型改进

  1. 探索转学
  2. 如何最终确定模型并做出预测

狗与猫数据集准备

可以从Kaggle网站免费下载该数据集,尽管我相信您必须拥有一个Kaggle帐户。

如果您没有Kaggle帐户,请先注册。

通过访问“狗与猫”数据页面下载数据集,然后单击“全部下载”按钮。

这会将850 MB的文件“ dogs-vs-cats.zip ”下载到您的工作站。

解压缩该文件,您将看到train.ziptrain1.zip和一个.csv文件。解压缩train.zip文件,因为我们将仅关注此数据集。

现在,您将拥有一个名为“ train / ”的文件夹,其中包含25,000个猫和狗的.jpg文件。照片用文件名标记,并带有“”或“”字样。文件命名约定如下:

 

情节猫和狗的照片

查看目录中的几张随机照片,您会发现这些照片是彩色的,并且具有不同的形状和大小。

例如,让我们在单个图中加载并绘制狗的前九张照片。

下面列出了完整的示例。

运行示例将创建一个图,显示数据集中狗的前九张照片。

我们可以看到有些照片是横向格式的,有些是肖像格式的,有些是方形的。

“狗与猫”数据集中的狗的前九张照片的情节

“狗与猫”数据集中的狗的前九张照片的情节

我们可以更新示例并将其更改为绘制猫的照片。下面列出了完整的示例。

再次,我们可以看到照片都是不同尺寸的。

我们还可以看到一张照片,其中几乎看不见猫(左下角),另一只有两只猫(右下角)。这表明,适合此问题的任何分类器都必须具有鲁棒性。

猫与猫数据集中的猫的前九张照片图解

猫与猫数据集中的猫的前九张照片图解

选择标准照片尺寸

在建模之前,必须对照片进行重塑,以使所有图像具有相同的形状。这通常是一个小的正方形图像。

有多种方法可以实现此目的,尽管最常见的方法是简单的调整大小操作,该操作会拉伸和变形每个图像的纵横比,并将其强制为新形状。

我们可以加载所有照片,并查看照片宽度和高度的分布,然后设计一个新的照片尺寸,以最好地反映我们在实践中最有可能看到的图像。

输入越小,意味着模型的训练速度就越快,通常这种担忧主导着图像尺寸的选择。在这种情况下,我们将采用这种方法并选择200×200像素的固定大小。

预处理照片尺寸(可选)

如果我们想将所有图像加载到内存中,我们可以估计它将需要大约12 GB的RAM。

即25,000张图像,每张200x200x3像素,或3,000,000,000个32位像素值。

我们可以加载所有图像,调整它们的形状,并将它们存储为单个NumPy数组。这可能适合许多现代机器上的RAM,但并非全部都适合,尤其是当您只有8 GB的数据时。

我们可以编写自定义代码以将图像加载到内存中,并在加载过程中调整它们的大小,然后将其保存以备建模。

下面的示例使用Keras图像处理API将所有25,000张照片加载到训练数据集中,并将它们重塑为200×200平方照片。还根据文件名为每张照片确定标签。然后保存照片和标签的元组

运行该示例可能需要大约一分钟的时间才能将所有图像加载到内存中,并打印已加载数据的形状以确认已正确加载。

注意:运行此示例假定您具有超过12 GB的RAM。如果没有足够的RAM,则可以跳过此示例。它仅作为演示提供。

运行结束时,将创建两个名为“ dogs_vs_cats_photos.npy ”和“ dogs_vs_cats_labels.npy ”的文件,其中包含所有调整大小后的图像及其相关的类标签。这些文件在一起的大小仅为12 GB,并且加载速度明显快于单个映像。

准备好的数据可以直接加载;例如:

 

将照片预处理成标准目录

或者,我们可以使用Keras ImageDataGenerator类和flow_from_directory() API逐步加载图像。这将执行较慢,但将在更多计算机上运行。

该API倾向于将数据划分为单独的train /test /目录,并希望在每个目录下具有每个类的子目录,例如,train / dog /train / cat /子目录,并且用于测试的子目录也是如此。然后将图像组织在子目录下。

我们可以编写一个脚本来创建具有此首选结构的数据集的副本。我们将随机选择25%的图像(或6,250张)用于测试数据集中。

首先,我们需要创建目录结构,如下所示:

我们可以使用makedirs()函数在Python中创建目录,并使用循环为train /test /目录创建dog /cat /子目录。

接下来,我们可以枚举数据集中的所有图像文件,然后根据它们的文件名将它们复制到dogs /cats /子目录中。

此外,我们可以随机决定将25%的图像保留到测试数据集中。这是通过固定伪随机数生成器的种子来一致地完成的,这样,每次运行代码时,我们都会获得相同的数据分割。

下面列出了完整的代码示例,并假定您已将下载的train.zip中的图像压缩到train /中的当前工作目录中。

运行该示例后,现在将具有一个完全符合设计的新数据集_dogs_vs_cats /目录,其中包含train /val /子文件夹以及其他dogs / can cat /子目录。

开发基线CNN模型

在本节中,我们可以为狗对猫数据集开发基线卷积神经网络模型。

基线模型将建立可与我们所有其他模型进行比较的最低模型性能,以及可作为研究和改进基础的模型体系结构。

一个良好的起点是VGG模型的一般架构原理。这些是一个很好的起点,因为它们在ILSVRC 2014竞赛中获得了最佳性能,并且该架构的模块化结构易于理解和实施。有关VGG模型的更多详细信息,请参见2015年论文“用于大规模图像识别的超深度卷积网络”。

该体系结构包括使用3×3小型滤波器堆叠卷积层,然后是最大池化层。这些层一起形成一个块,并且可以重复这些块,其中每个块中的过滤器数量会随着网络的深度而增加,例如对于模型的前四个块来说是32、64、128、256。卷积层上使用填充,以确保输出要素图的高度和宽度形状与输入匹配。

我们可以在“狗与猫”问题上探索这种架构,并将具有这种架构的模型与1、2和3个块进行比较。

每层将使用ReLU激活功能和He权重初始化,这通常是最佳做法。例如,可以在Keras中定义3块VGG样式的体系结构,其中每个块具有单个卷积和池化层,如下所示:

我们可以创建一个名为define_model()的函数,该函数将定义一个模型并将其返回以适合数据集。然后可以自定义此功能以定义不同的基线模型,例如具有1个,2个或3个VGG样式块的模型版本。

该模型将适合于随机梯度下降,我们将从0.001的保守学习率和0.9的动量开始。

问题是二进制分类任务,需要预测0或1的一个值。将使用具有1个节点和S型激活的输出层,并且将使用二进制交叉熵损失函数来优化模型。

以下是define_model()函数的一个示例,该函数用于使用一个vgg样式的块为狗与猫的问题定义卷积神经网络模型。

可以调用它根据需要准备模型,例如:

接下来,我们需要准备数据。

这涉及首先定义ImageDataGenerator的实例,该实例会将像素值缩放到0-1的范围。

接下来,需要为训练和测试数据集准备迭代器

我们可以在数据生成器上使用flow_from_directory()函数,并为每个train /test /目录创建一个迭代器。我们必须通过“ class_mode ”参数指定该问题是二进制分类问题,并通过“ target_size ”参数加载大小为200×200像素的图像。我们将批量大小固定为64。

然后,我们可以使用训练迭代器(train_it)拟合模型,并在训练过程中将测试迭代器(test_it)用作验证数据集。

必须指定训练迭代器和测试迭代器的步骤数。这是将组成一个时期的批次数量。可以通过每个迭代器的长度来指定,这将是训练目录和测试目录中图像的总数除以批处理大小(64)。

该模型适合20个纪元,少量用于检查模型是否可以学习该问题。

拟合后,可以直接在测试数据集上评估最终模型,并报告分类准确性。

最后,我们可以创建训练期间收集的历史图,并将其存储在从fit_generator()调用返回的“ history ”目录中。

历史记录包含每个时期结束时测试和训练数据集上的模型准确性和损失。这些度量在训练时期上的线图提供了学习曲线,我们可以用来了解模型是过度拟合,欠拟合还是具有良好拟合。

下面的summary_diagnostics()函数采用历史记录目录,并创建一个带有损失线形图的单一图形,以及另一个用于精度的线形图。然后,根据脚本名称将图形保存到文件名中。如果我们希望评估模型在不同文件中的多种变化并为每个文件自动创建线图,这将很有帮助。

我们可以将所有这些结合到一个简单的测试工具中,以测试模型配置。

下面列出了在猫和猫数据集上评估一个整体基线模型的完整示例。

现在我们有了一个测试工具,让我们看一下对三个简单基线模型的评估。

一区块VGG模型

单块VGG模型具有一个包含32个滤波器的单个卷积层,后跟一个最大池化层。

上一节中定义了该模型的define_model()函数,但出于完整性考虑,下面再次提供。

首先运行此示例,将打印训练数据集和测试数据集的大小,确认数据集已正确加载。

然后对模型进行拟合和评估,在现代GPU硬件上大约需要20分钟。

 

注意:由于算法或评估程序的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑运行该示例几次并比较平均结果。

 

在这种情况下,我们可以看到该模型在测试数据集上达到了约72%的精度。

还创建了一个图,显示了在火车(蓝色)和测试(橙色)数据集上的损耗线图和另一个模型精度的线图。

查看此图,我们可以看到该模型在约12个历元时过度拟合了训练数据集。

狗和猫数据集上具有一个VGG块的基线模型的损失和准确性学习曲线的线图

狗和猫数据集上具有一个VGG块的基线模型的损失和准确性学习曲线的线图

两块VGG模型

两块VGG模型扩展了一个块模型,并添加了具有64个过滤器的第二个块。

为了完整起见,下面提供了此模型的define_model()函数。

再次运行此示例将打印训练和测试数据集的大小,确认数据集已正确加载。

对模型进行拟合和评估,并报告测试数据集的性能。

 

注意:由于算法或评估程序的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑运行该示例几次并比较平均结果。

 

在这种情况下,我们可以看到该模型的性能从一个块的约72%改进为两个块的约76%的性能方面的小幅改进。

回顾学习曲线的图,我们可以再次看到,该模型似乎已经过拟合训练数据集,也许很快,在这种情况下,大约是在八个训练时期。

这可能是模型容量增加的结果,并且我们可以预期,过快拟合的趋势将在下一个模型中继续。

狗和猫数据集上具有两个VGG块的基线模型的损失和准确性学习曲线的线图

狗和猫数据集上具有两个VGG块的基线模型的损失和准确性学习曲线的线图

三块VGG模型

三块VGG模型扩展了两个块模型,并添加了具有128个过滤器的第三个块。

上一节中定义了该模型的define_model()函数,但出于完整性考虑,下面再次提供。

运行此示例将打印训练和测试数据集的大小,并确认数据集已正确加载。

对模型进行拟合和评估,并报告测试数据集的性能。

 

注意:由于算法或评估程序的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑运行该示例几次并比较平均结果。

 

在这种情况下,我们可以看到性能进一步提高,从两个块的约76%提高到三个块的约80%精度。这个结果很好,因为它与论文中使用SVM以约82%的准确度报告的最新技术水平相近。

回顾学习曲线的图,我们可以看到类似的过度拟合趋势,在这种情况下,可能会推迟到第五或第六个时代。

狗和猫数据集上具有三个VGG块的基线模型的损失和准确性学习曲线的线图

狗和猫数据集上具有三个VGG块的基线模型的损失和准确性学习曲线的线图

讨论

我们使用基于VGG的体系结构探索了三种不同的模型。

结果可以总结如下,尽管鉴于算法的随机性质,我们必须在这些结果中假设一些差异:

  • VGG 1:72.331%
  • VGG 2:76.646%
  • VGG 3:80.184%

随着容量的增加,我们看到了性能改善的趋势,但是在运行的早期和早期,也出现了过拟合的情况。

结果表明该模型可能会受益于正则化技术。这可能包括诸如辍学,权重衰减和数据增强之类的技术。后者还可以通过鼓励模型学习通过扩展训练数据集学习位置进一步不变的特征来提高性能。

开发模型改进

在上一节中,我们使用VGG样式的模块开发了基线模型,并发现了随着模型容量的增加而性能提高的趋势。

在本节中,我们将从具有三个VGG块(即VGG 3)的基线模型开始,并探索对该模型的一些简单改进。

通过在训练过程中查看模型的学习曲线,该模型显示出过度拟合的强烈迹象。我们可以探索两种方法来尝试解决这种过度拟合问题:辍学正则化和数据扩充。

预计这两种方法都会减慢训练过程中的改进速度,并有望解决训练数据集的过拟合问题。因此,我们会将训练时期的数量从20个增加到50个,以便为模型提供更多的优化空间。

辍学正则化

辍学正则化是对深度神经网络进行正则化的一种计算便宜的方法。

辍学是通过概率性地删除或“放弃”对某个层的输入来进行的,该输入可以是数据样本中的输入变量或来自上一层的激活。它具有模拟具有非常不同的网络结构的大量网络的效果,进而使网络中的节点通常对输入更为健壮。

以下是define_model()函数,用于添加了Dropout的基线模型的更新版本。在这种情况下,在每个VGG块之后应用20%的丢失,在模型的分类器部分中的完全连接层之后应用50%的较大丢失率。

为了完整起见,下面列出了基线模型的完整代码列表,以及在狗对猫数据集上添加了辍学信息的信息。

运行示例首先适合模型,然后在保留的测试数据集上报告模型性能。

注意:由于算法或评估程序的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑运行该示例几次并比较平均结果。

在这种情况下,我们可以看到模型性能从基线模型的约80%的准确度小幅提升到增加了辍学率的约81%。

回顾学习曲线,我们可以看到辍学对训练和测试集上模型的改进率都有影响。

尽管性能可能会在运行结束时开始停顿,但过拟合已被减少或延迟。

结果表明,进一步的训练时期可能会导致模型的进一步改进。在VGG阻滞之后,除了训练时期的增加以外,探索一个略高的辍学率也可能很有趣。

狗和猫数据集缺失的基线模型的损失和准确性学习曲线的线图

狗和猫数据集缺失的基线模型的损失和准确性学习曲线的线图

图像数据扩充

图像数据增强是一种可通过在数据集中创建图像的修改版本来人工扩展训练数据集大小的技术。

在更多数据上训练深度学习神经网络模型可以得到更熟练的模型,并且增强技术可以创建图像的变体,从而可以提高拟合模型将其学到的知识概括为新图像的能力。

数据增强还可以充当正则化技术,在训练数据中添加噪声,并鼓励模型学习相同的特征,而不会改变它们在输入中的位置。

对猫和猫的输入照片进行较小的更改可能会对此问题有用,例如小移位和水平翻转。可以将这些扩充指定为用于训练数据集的ImageDataGenerator的参数。增强不应用于测试数据集,因为我们希望评估模型在未修改照片上的性能。

这就要求我们为火车和测试数据集有一个单独的ImageDataGenerator实例,然后是从各自的数据生成器创建的火车和测试集的迭代器。例如:

在这种情况下,训练数据集中的照片将通过小的(10%)随机水平和垂直偏移以及随机水平翻转来增强,从而创建照片的镜像。训练和测试步骤中的照片将以相同的方式缩放其像素值。

为了完整起见,下面列出了带有狗和猫数据集的训练数据的基线模型的完整代码列表。

运行示例首先适合模型,然后在保留的测试数据集上报告模型性能。

注意:由于算法或评估程序的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑运行该示例几次并比较平均结果。

在这种情况下,通过简单的数据扩充,我们可以看到性能从基准模型的约80%上升到基准模型的约85%约5%。

回顾学习曲线,我们可以看到该模型能够继续学习,即使在运行结束时,火车和测试数据集的损失仍在减少。以100个或更多的时间段重复实验,很可能会产生更好的模型。

探索可能进一步鼓励学习与输入中位置无关的特征的其他增强(例如较小的旋转和缩放)可能会很有趣。

带有狗和猫数据集的数据增强的基线模型的损失和准确性学习曲线的线图

带有狗和猫数据集的数据增强的基线模型的损失和准确性学习曲线的线图

讨论

我们探索了对基线模型的三种不同改进。

结果可以总结如下,尽管鉴于算法的随机性质,我们必须在这些结果中假设一些差异:

  • 基准VGG3 +辍学率:81.279%
  • 基准VGG3 +数据增强:85.816

令人怀疑的是,添加正则化技术会减慢学习算法的进程并减少过度拟合,从而提高了保持数据集的性能。两种方法的结合以及训练时期数量的进一步增加可能会导致进一步的改进。

这仅仅是可以在此数据集上探索的改进类型的开始。除了调整所描述的正则化方法外,还可以探索其他正则化方法,例如体重减轻和提早停止。

可能值得探索学习算法的变化,例如学习率的变化,学习率时间表的使用或自适应学习率(例如Adam

替代模型架构也可能值得探讨。预期所选择的基准模型将提供比此问题所需的容量更多的容量,并且较小的模型可能会更快地训练,进而可能导致更好的性能。

探索转学

转移学习涉及使用经过相关任务训练的模型的全部或部分。

Keras提供了一系列预训练的模型,这些模型可以通过Keras Applications API完全或部分加载和使用。

迁移学习的有用模型是VGG模型之一,例如具有16层的VGG-16,该模型在开发时就在ImageNet照片分类挑战中取得了最佳成绩。

该模型包括两个主要部分,该模型的特征提取器部分由VGG块组成,该模型的分类器部分由完全连接的层和输出层组成。

我们可以使用模型的特征提取部分,并添加模型的新分类器部分,该部分针对狗和猫的数据集量身定制。具体来说,我们可以在训练过程中将所有卷积层的权重保持固定,并且仅训练新的完全连接的层,这些层将学习解释从模型中提取的特征并进行二进制分类。

这可以通过加载VGG-16模型,从模型的输出端删除完全连接的层,然后添加新的完全连接的层来解释模型输出并进行预测来实现。通过将“ include_top ”参数设置为“ False ”,可以自动删除模型的分类器部分,在这种情况下,这也需要为模型指定输入的形状(224、224、3)。这意味着加载的模型在最后一个最大池化层结束,之后我们可以手动添加Flatten层和新的clasifier层。

下面的define_model()函数实现了此功能,并返回一个可供训练的新模型。

创建完成后,我们可以像以前一样在训练数据集上训练模型。

在这种情况下,不需要太多的培训,因为只有新的完全连接的输出层才具有可训练的权重。因此,我们会将训练纪元的数量固定为10个。

VGG16模型是在特定的ImageNet挑战数据集上训练的。这样,它被配置为期望输入图像具有224×224像素的形状。从猫猫数据集中加载照片时,我们将以此为目标尺寸。

该模型还希望图像居中。也就是说,要从输入中减去在ImageNet训练数据集上计算出的每个通道(红色,绿色和蓝色)的平均像素值。Keras通过preprocess_input()函数提供了对单个照片进行此准备的功能。尽管如此,通过将“ featurewise_center ”参数设置为“ True ”,并手动指定在居中作为ImageNet训练数据集中的平均值时要使用的平均像素值,我们可以使用ImageDataGenerator达到相同的效果:[123.68、116.779、103.939] 。

下面列出了用于在狗对猫数据集上进行迁移学习的VGG模型的完整代码列表。

运行示例首先适合模型,然后在保留的测试数据集上报告模型性能。

注意:由于算法或评估程序的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑运行该示例几次并比较平均结果。

在这种情况下,我们可以看到模型在保留测试数据集上取得了非常令人印象深刻的结果,分类精度约为97%。

回顾学习曲线,我们可以看到该模型快速拟合了数据集。尽管结果表明分类器中的附加功能和/或使用正则化可能会有所帮助,但它并没有显示出过度拟合的能力。

可以对该方法进行许多改进,包括向模型的分类器部分添加辍学正则化,甚至可能微调模型的特征检测器部分中某些或所有层的权重。

狗和猫数据集上VGG16转移学习模型的损失和准确性学习曲线的线图

狗和猫数据集上VGG16转移学习模型的损失和准确性学习曲线的线图

如何最终确定模型并做出预测

只要我们有想法以及测试它们的时间和资源,模型改进的过程就可以持续进行。

在某个时候,必须选择并采用最终的模型配置。在这种情况下,我们将使事情变得简单,并使用VGG-16转移学习方法作为最终模型。

首先,我们将通过在整个训练数据集上拟合模型并将模型保存到文件中以供以后使用来最终确定模型。然后,我们将加载保存的模型,并使用它对单个图像进行预测。

准备最终数据集

最终模型通常适合所有可用数据,例如所有训练和测试数据集的组合。

在本教程中,我们将展示仅适用于训练数据集的最终模型,因为我们只有训练数据集的标签。

第一步是准备训练数据集,以便ImageDataGenerator类可以通过flow_from_directory()函数将其加载。具体来说,我们需要创建一个新目录,其中所有训练图像都组织在dog /cat /子目录中,而没有任何分开的train /test /目录。

这可以通过更新我们在教程开始时开发的脚本来实现。在这种情况下,我们将为整个训练数据集创建一个新的finalize_dogs_vs_cats /文件夹,其中包含dogscats /子文件夹。

结构如下所示:

为了完整性,下面列出了更新的脚本。

 

保存最终模型

现在,我们准备将最终模型拟合到整个训练数据集上。

flow_from_directory()必须更新加载的所有图像从新finalize_dogs_vs_cats /目录。

此外,对fit_generator()的调用不再需要指定验证数据集。

一旦适合,我们可以通过在模型上调用save()函数并将最终的文件名传入来将最终模型保存到H5文件中。

注意,保存和加载Keras模型需要在工作站上安装h5py库。

下面列出了将最终模型拟合到训练数据集并将其保存到文件的完整示例。

运行此示例之后,您现在将在当前工作目录中拥有一个名为81.jpg的大型文件,名称为“ final_model.h5 ”。

做出预测

我们可以使用保存的模型对新图像进行预测。

该模型假定新图像是彩色的,并且已经对其进行了分割,因此一张图像至少包含一只狗或猫。

下面是从狗和猫比赛的测试数据集中提取的图像。它没有标签,但我们可以清楚地看出它是狗的照片。您可以使用文件名“ sample_image.jpg ”将其保存在当前工作目录中。

狗

狗(sample_image.jpg)

 

我们将假装这是一个全新的,看不见的图像,以所需的方式进行了准备,并了解了如何使用保存的模型来预测图像所代表的整数。对于此示例,我们希望“ Dog ”类为“ 1 ”。

注意:图像的子目录(每个类别一个flow_from_directory()函数按字母顺序加载,并为每个类别分配一个整数。子目录“ cat ”位于“ dog ”之前,因此为类标签分配了整数:cat = 0,dog = 1。训练模型时,可以通过调用flow_from_directory()中的“ classes ”参数来更改此设置。

首先,我们可以加载图像并将其强制为224×224像素。然后可以调整加载的图像的大小,以在数据集中具有单个样本。像素值也必须居中以匹配模型训练期间准备数据的方式。该load_image()函数实现这一点,将返回加载图像准备进行分类。

接下来,我们可以像上一节中那样加载模型,并调用predict()函数将图像中的内容预测为“ cat ”和“ dog ”分别为“ 0 ”和“ 1 ”之间的数字。

下面列出了完整的示例。

运行示例首先加载并准备图像,加载模型,然后正确地预测加载的图像代表“”或类“ 1 ”。

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站不拥有所有权,不承担相关法律责任。如发现有侵权/违规的内容, 联系QQ1841324605,本站将立刻清除。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

联系我们

服务热线:130-0886-1890

QR code