【问题标题】:How to design an optimal CNN? [closed]如何设计一个最优的CNN? [关闭]
【发布时间】:2021-07-06 11:49:27
【问题描述】:

我正在攻读博士学位。项目,其目标是减少地球上的CO2 排放。

我有一个数据集,并且能够成功实现CNN,它提供了80% 的准确性(最坏的情况)。但是,我工作的领域要求非常高,我的印象是,使用经过优化的 CNN 可以获得更高的准确度。

专家如何设计CNN's?我如何在Inception 模块、Dropout 正则化、Batch Normalization、卷积滤波器大小、卷积通道的大小和深度、全连接层数、激活神经元等之间进行选择?人们如何以科学的方式驾驭这个大型优化问题?组合是无穷无尽的。是否有任何现实生活中的例子可以解决这个问题,解决它的全部复杂性(不仅仅是优化一些超参数)?

希望我的数据集不会太大,所以我正在考虑的 CNN 模型应该有很少的参数。

【问题讨论】:

  • 我投票结束这个问题,因为它与 help center 中定义的编程无关,而是关于 ML 理论和/或方法 - 请参阅 machine-learning @ 中的介绍和注意事项987654322@.
  • @M.Innat 只是因为我们都是这里的志愿者,我们只有那么多的时间和资源可以腾出,所以这样的帖子总是会溜走。感谢您引起我们的注意并继续关注 - 它也将很快关闭。
  • @M.Innat 事实上,最近 Meta 上甚至有一个讨论到 gift wrap our (good) machine learning theory questions for Cross Validated
  • @M.Innat 链接问题现在也已关闭。希望现在情况很清楚,并且在提问和回答时请牢记网站规则和指南(我过去曾犯过自己回答此类问题的错误,当时这些规则在我自己的脑海中还不是很清楚)。
  • @desertnaut 是的,我明白了。我会记住这一点。谢谢你。 -)

标签: tensorflow machine-learning computer-vision conv-neural-network hyperparameters


【解决方案1】:

专家如何设计 CNN?如何在初始模块、Dropout 正则化、批量标准化、卷积滤波器大小、卷积通道的大小和深度、全连接层数、激活神经元等之间进行选择?人们如何以科学的方式驾驭这个大型优化问题?组合无穷无尽。

您确实说过,组合的数量很大。如果没有正确接近,您可能会一事无成。一位伟大的人说机器学习是一门艺术,而不是科学。结果取决于数据。以下是关于您上述问题的一些提示。

  • Log Everything:在训练时间,保存每个实验的必要日志,例如训练损失、验证损失、权重文件、执行时间、可视化等。其中一些可以用@保存987654329@、ModelCheckpoint 等。TensorBoard 是检查训练日志和可视化等的绝佳工具。

  • 强大的验证策略:这非常重要。要构建稳定的交叉验证 (CV),我们必须对数据和面临的挑战有充分的了解。我们将检查并确保验证集训练集测试集具有相似分布。我们将努力确保我们的模型在我们的 CV测试集both(如果 gt 可用于测试集)。基本上,随机划分数据通常不足以满足这一点。了解数据以及我们如何在不引入数据泄漏的情况下对数据进行分区是避免过度拟合的关键。

  • 仅更改一项:在实验期间,一次更改一件事并保存这些更改的观察结果 (logs)。例如:将图像大小从224(例如)逐渐更改为更高并观察结果。我们应该从一个小的组合开始。在尝试图像大小时,修复其他的,如model 架构、learning rate 等。learning rate 部分或model 架构也是如此。但是,稍后当我们得到一些有希望的组合时,我们可能还需要更改多个。在 kaggle 比赛中,这些是人们会遵循的非常常见的方法。下面是一个非常简单的例子。但它不受任何限制。


但是,正如您所说,您的博士学位。项目是减少地球上的二氧化碳排放量。据我了解,这些问题更多的是特定于应用程序的问题,而不是特定于算法的问题。因此,我们认为最好从公认的预训练模型中获益。

如果我们想自己写CNN,我们应该花点时间写。从一个非常简单的开始,例如:

Conv2D (16,  3, 'relu') - > MaxPool (2)
Conv2D (32,  3, 'relu') - > MaxPool (2) 
Conv2D (64,  3, 'relu') - > MaxPool (2)
Conv2D (128, 3, 'relu') - > MaxPool (2)

这里我们逐渐增加深度但减少特征维度。到最后一层,会出现更多的语义信息。在堆叠Conv2D 层时,通常的做法是按16, 32, 64, 128 等顺序增加通道深度。如果我们想在我们的网络中估算InceptionResidual Block,我认为,我们应该先做一些基本的数学运算什么特征属性会由此产生,等等。按照这样的概念,我们可能还希望看看SENetResNeSt 等方法。关于Dropout,如果我们观察到我们的模型在训练,那么我们应该添加一些。在最后一层,我们可能希望在Flatten 层(FCC)上选择GlobalAveragePooling。我们现在大概可以理解,要获得令人满意的CNN 模型,需要进行大量的消融研究。

在这方面,我们建议您探索两个最重要的事情:(1)。阅读其中一篇关于他们构建算法的策略的预训练模型论文/博客/视频。例如:查看此EfficientNet Explained。 (2)。接下来,探索它的源代码。这会让你更有感觉,并鼓励你建立自己的巨人。


我们想以最后一个工作示例来结束这一切。请看下面的模型图,它是一个小型初始网络source。如果我们仔细观察,我们会发现,它由以下三个模块组成。

  • 转换模块
  • 初始模块
  • 下采样模块

仔细看看每个模块的配置,如filter sizestrides等。让我们试着理解和实现这个模块。在此之前,这里有两个很好的参考资料(12),供 Inception 概念刷新概念。

转化模块

从图中我们可以看出,它由一个卷积网络、一个batch normalization一个relu激活组成。此外,它生成C 次特征图,带有K x K 过滤器和S x S 步幅。为此,我们将创建一个继承 tf.keras.layers.Layer 类的类对象

class ConvModule(tf.keras.layers.Layer):
    def __init__(self, kernel_num, kernel_size, strides, padding='same'):
        super(ConvModule, self).__init__()
        # conv layer
        self.conv = tf.keras.layers.Conv2D(kernel_num, 
                        kernel_size=kernel_size, 
                        strides=strides, padding=padding)

        # batch norm layer
        self.bn   = tf.keras.layers.BatchNormalization()


    def call(self, input_tensor, training=False):
        x = self.conv(input_tensor)
        x = self.bn(x, training=training)
        x = tf.nn.relu(x)
        
        return x

初始模块

接下来是 Inception 模块。根据上图,它由两个卷积模块组成,然后合并在一起。现在我们知道要合并,这里我们需要确保输出特征映射维度(heightwidth)需要相同

class InceptionModule(tf.keras.layers.Layer):
    def __init__(self, kernel_size1x1, kernel_size3x3):
        super(InceptionModule, self).__init__()
        
        # two conv modules: they will take same input tensor 
        self.conv1 = ConvModule(kernel_size1x1, kernel_size=(1,1), strides=(1,1))
        self.conv2 = ConvModule(kernel_size3x3, kernel_size=(3,3), strides=(1,1))
        self.cat   = tf.keras.layers.Concatenate()


    def call(self, input_tensor, training=False):
        x_1x1 = self.conv1(input_tensor)
        x_3x3 = self.conv2(input_tensor)
        x = self.cat([x_1x1, x_3x3])
        return x 

您可能会注意到,我们现在根据网络(图表)对两个卷积层的确切内核大小步幅数进行了硬编码。同样在ConvModule 中,我们已经为same 设置了填充,以便两者的特征图的维度相同(self.conv1self.conv2);这是将它们连接到末尾所必需的。

同样,在此模块中,两个变量充当占位符,kernel_size1x1kernel_size3x3。这当然是为了目的。因为我们将需要不同数量的特征映射到整个模型的不同阶段。如果我们查看模型图,我们会看到InceptionModule 在模型的不同阶段采用不同数量的过滤器。

下采样模块

最后是下采样模块。下采样的主要直觉是我们希望获得更多相关的特征信息,这些信息高度代表模型的输入。因为它倾向于删除不需要的功能,以便模型可以专注于最相关的功能。有很多方法可以减少特征图(或输入)的维度。例如:使用strides 2或使用常规的pooling操作。池化操作有很多种,分别是:MaxPoolingAveragePoolingGlobalAveragePooling

从图中,我们可以看到下采样模块包含一个卷积层和一个最大池化层,它们随后合并在一起。现在,如果我们仔细查看图表(右上角),我们会看到卷积层采用3 x 3 大小的过滤器和strides 2 x 2。池化层(此处为MaxPooling)采用池化大小3 x 3strides 2 x 2。然而,公平地说,我们还确保来自它们中的每一个的维度应该相同,以便最终合并。现在,如果我们记得当我们设计ConvModule 时,我们故意将padding 参数的值设置为same。但是在这种情况下,我们需要将其设置为valid

class DownsampleModule(tf.keras.layers.Layer):
    def __init__(self, kernel_size):
        super(DownsampleModule, self).__init__()

        # conv layer
        self.conv3 = ConvModule(kernel_size, kernel_size=(3,3), 
                         strides=(2,2), padding="valid") 

        # pooling layer 
        self.pool  = tf.keras.layers.MaxPooling2D(pool_size=(3, 3), 
                         strides=(2,2))
        self.cat   = tf.keras.layers.Concatenate()

    def call(self, input_tensor, training=False):
        # forward pass 
        conv_x = self.conv3(input_tensor, training=training)
        pool_x = self.pool(input_tensor)
    
        # merged
        return self.cat([conv_x, pool_x])

好的,现在我们已经构建了所有三个模块,即:ConvModule InceptionModule DownsampleModule。让我们根据图表初始化它们的参数。

class MiniInception(tf.keras.Model):
    def __init__(self, num_classes=10):
        super(MiniInception, self).__init__()

        # the first conv module
        self.conv_block = ConvModule(96, (3,3), (1,1))

        # 2 inception module and 1 downsample module
        self.inception_block1  = InceptionModule(32, 32)
        self.inception_block2  = InceptionModule(32, 48)
        self.downsample_block1 = DownsampleModule(80)
  
        # 4 inception module and 1 downsample module
        self.inception_block3  = InceptionModule(112, 48)
        self.inception_block4  = InceptionModule(96, 64)
        self.inception_block5  = InceptionModule(80, 80)
        self.inception_block6  = InceptionModule(48, 96)
        self.downsample_block2 = DownsampleModule(96)

        # 2 inception module 
        self.inception_block7 = InceptionModule(176, 160)
        self.inception_block8 = InceptionModule(176, 160)

        # average pooling
        self.avg_pool = tf.keras.layers.AveragePooling2D((7,7))

        # model tail
        self.flat      = tf.keras.layers.Flatten()
        self.classfier = tf.keras.layers.Dense(num_classes, activation='softmax')


    def call(self, input_tensor, training=True, **kwargs):
        # forward pass 
        x = self.conv_block(input_tensor)
        x = self.inception_block1(x)
        x = self.inception_block2(x)
        x = self.downsample_block1(x)

        x = self.inception_block3(x)
        x = self.inception_block4(x)
        x = self.inception_block5(x)
        x = self.inception_block6(x)
        x = self.downsample_block2(x)

        x = self.inception_block7(x)
        x = self.inception_block8(x)
        x = self.avg_pool(x)

        x = self.flat(x)
        return self.classfier(x)

每个计算块的filter个数根据模型的设计来设定(见图)。初始化所有块后(在__init__函数中),我们按照设计将它们连接起来(在call函数中)。

【讨论】:

  • 感谢@M.Innat 的反馈。这很有帮助,我对答案的深度印象深刻。我将尝试应用您提到的策略。看来CNN优化过程需要几个月的时间:)
【解决方案2】:

我认为您对所需参数数量的估计还有很长的路要走。想想更多,如果你使用迁移学习,你会得到几百万。如果你愿意,你可以努力尝试制作自己的模型,但你可能不会比从迁移学习中获得的结果更好(而且更可能没有那么好)。我强烈推荐 MobileV2 模型。现在,如果您使用 ReduceLROnPlateau 使用可调节的学习率,您可以使该模型或任何其他模型表现得更好。相关文档是 here. 我推荐的另一件事是使用 Keras 回调 EarlyStopping。文档是 here. 。将其设置为监控验证损失并设置 restore_best_weights=True。将 epoch 的数量设置为一个较大的数字,以便触发此回调并返回具有最低验证损失的 epoch 的权重的模型。我推荐的代码如下所示

height=224
width=224
img_shape=(height, width, 3)
dropout=.3
lr=.001
class_count=156 # number of classes
img_shape=(height, width, 3)
base_model=tf.keras.applications.MobileNetV2( include_top=False, input_shape=img_shape, pooling='max', weights='imagenet') 
x=base_model.output
x=keras.layers.BatchNormalization(axis=-1, momentum=0.99, epsilon=0.001 )(x)
x = Dense(512, kernel_regularizer = regularizers.l2(l = 0.016),activity_regularizer=regularizers.l1(0.006),
                bias_regularizer=regularizers.l1(0.006) ,activation='relu', kernel_initializer= tf.keras.initializers.GlorotUniform(seed=123))(x)
x=Dropout(rate=dropout, seed=123)(x)        
output=Dense(class_count, activation='softmax',kernel_initializer=tf.keras.initializers.GlorotUniform(seed=123))(x)
model=Model(inputs=base_model.input, outputs=output)
model.compile(Adamax(lr=lr), loss='categorical_crossentropy', metrics=['accuracy']) 
rlronp=tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=1, verbose=1, mode='auto', min_delta=0.0001, cooldown=0, min_lr=0)
estop=tf.keras.callbacks.EarlyStopping( monitor="val_loss", min_delta=0, patience=4,
                                       verbose=1,  mode="auto",  baseline=None,
                                        restore_best_weights=True)
callbacks=[rlronp, estop]

还要查看数据集中的余额。也就是说,比较每个类有多少训练样本。如果大多数样本/最少样本的比率>2 或 3,您可能需要采取措施来缓解这种情况。方法很多,最简单的是使用model.fit中的class_weight参数。 o 这样做你需要创建一个 class_weights 字典。下面概述了执行此操作的过程

Lets say your class distribution is
 class0 - 500 samples
 class1- 2000 samples
 class2 - 1500 samples
 class3 - 200 samples
Then your dictionary would be
class_weights={0: 2000/500, 1:2000/2000, 2: 2000/1500, 3: 2000/200}
in model.fit set class_weight=class_weights

【讨论】:

  • 感谢@Gerry P 的反馈,非常有帮助。我会尝试你提到的方法。但是,我认为我确实需要设计一个小型的定制 CNN,我们没有那么多的训练数据,而且这个问题与传统的计算机视觉任务有很大的不同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-15
  • 2010-09-16
  • 1970-01-01
  • 1970-01-01
  • 2018-10-27
相关资源
最近更新 更多