第5周学习笔记:ShuffleNet & EfficientNet & 迁移学习

目录

一 理论学习

1.ShuffleNet V1 & V2

1.1 ShuffleNet V1

 1.2 ShuffleNet V2

2.EfficientNet V3

3.Transformer

二 代码学习

1.使用VGG模型进行猫狗大战

2.AI艺术鉴赏挑战赛题


一 理论学习

1.ShuffleNet V1 & V2

1.1 ShuffleNet V1

ResNeXt,MobileNet等网络都使用了 group conv,但是采用密集的1x1 pointwise conv需要相当大的计算量。为此,作者指出,一个非常自然的解决方案就是把 1x1 pointwise conv 同样应用 group conv ,这样就可以进一步降低计算量。

GConv虽然能够减少参数与计算量,但GConv中不同组之间信息没有交流。为了解决这个问题,作者提出了channel shuffle的思想(bc),对每一个组生成的特征信息划分为3份,把每个group中的第i份放在一起,这样就可以融合不同group之间的channel信息了

 在ResNetX中,普通的1*1卷积占据了网络的93.4%,所以在v1版本中,作者把1*1卷积换成了group卷积

 网络结构(注意g=3的情况)

 假设输入矩阵的channel是c高度是h宽度是w,第一个1*1卷积和3*3卷积输出特征channel是m,由此计算下列网络采用的block的FLOPs

FLOPS:全大写,指每秒浮点运算次数,可以理解为计算的速度。是衡量硬件性能的一个指标.
FLOPs:s小写,指浮点运算数,理解为计算量。可以用来衡量算法/模型的复杂度。

 1.2 ShuffleNet V2

计算复杂度不能只看FLOPs

FLOPs只是间接指标,速度是最直接的评价指标,除了FLOPs以外,还应该考虑memory access cost (MAC)、并行等级(degree of parallelism),在相同的FLOPs下,并行度高的模型比并行度低的模型速度快。另外,相同的FLOPs在不同的平台上执行时间也不一样。

卷积占了模型推理的大部分时间,但其他的操作也占据了相当一部分的时间

 如何设计高效网络

1.当卷积层的输入特征矩阵与输出特征矩阵channel相等时MAC最小(保持FLOPs不变时)

 作者在FLOPs保持不变的前提下进行了实验,探究c1:c2对推理速度的影响。由下表可以看出随着两者的比值相差越大,推理速度越慢

 2.当GConv的groups增大时(保持FLOPs不变时),MAC也会增大

作者在FLOPs保持不变的前提下改变g的数值,可以看出随着g的增多,在GPU上速度下降明显,但在CPU上不明显

 3.网络设计的碎片化程度(分支)越高,速度越慢。碎片化结构对GPU不友好

 作者在FLOPs保持不变的前提下进行试验,可以看出在GPU上碎片化程度越高,速度越慢

 4.Element-wise操作带来的影响是不可忽视的,这些操作的特点是FLOPs很小但是MAC很大

 总结:

  1. 尽可能让输入和输出特征矩阵channel比值等于1
  2. 注意组卷积的计算成本
  3. 降低网络的训练程度,高效网络不要设置太多的分支结构
  4. 尽可能少使用Element-wise操作

新的block设计

c和d对应v2中步距为1和2的情况。对于每个block单元,将输入特征的channel划分为两个分支,分别是c’和c-c'。依G3,有一个分支仍然是一个独立实体。另一个分支由三个具有相同输入和输出通道的卷积组成,以满足G1。这两个1x1卷积不再使用Group卷积(G2)。

注意,ShuffleNet v1中的“Add”操作不再存在。像relu和深度卷积这样的Element-wise运算只存在于一个分支中。另外,三个连续的Element-wise操作,即“CONAT”、“Channel Shuffle”和“Channel Split”,被合并成一个单元级的操作(G4)。

 网络结构

与v1的不同在于多了一个1*1的卷积(conv5)

2.EfficientNet V3

论文提出的EfficientNet-B7在lmagenet top-1上达到了当年最高准确率84.3%,与之前准确率最高的GPipe相比,参数数量仅为其1/8.4,推理速度提升了6.1倍。

同时探索输入分辨率,网络的深度、宽度的影响

  • b:在a的基础上增大了网络的宽度(特征矩阵的channel)
  • c:在a的基础上增加网络的深度
  • d:在a的基础上增加输入图像的分辨率
  • e:同时增加宽度、深度和分辨率

 根据以往的经验,增加网络的深度depth能够得到更加丰富、复杂的特征并且能够很好的应用到其它任务中。但网络的深度过深会面临梯度消失,训练困难的问题。

增加网络的width能够获得更高细粒度的特征并且也更容易训练,但对于width很大而深度较浅的网络往往很难学习到更深层次的特征。

增加输入网络的图像分辨率能够潜在得获得更高细粒度的特征模板,但对于非常高的输入分辨率,准确率的增益也会减小。并且大分辨率图像会增加计算量。

网络结构

分辨率对应输入每个stage时特征矩阵的高宽,channel对应每个stage输出特征矩阵的channel的个数,layer是将Operator重复多少次。

 MBConv

  • 第一个升维的1x1卷积层,它的卷积核个数是输入特征矩阵channel的n倍( MBConv n)
  • 当n=1时,不要第一个升维的1x1卷积层,即Stage2中的MBConv结构都没有第一个升维的1x1卷积层(这和MobileNetV3网络类似)
  • 关于shortcut连接,仅当输入MBConv结构的特征矩阵与输出的特征矩阵shape相同时才存在
  • 注意,在源码中只有使用到shortcut连接的MBConv模块才有Dropout层

SE模块

SE模块如图所示,由一个全局平均池化,两个全连接层组成。第一个全连接层的节点个数是输入该MBConv特征矩阵channels的1/4,且使用Swish激活函数。第二个全连接层的节点个数等于Depthwise Conv层输出的特征矩阵channels,且使用Sigmoid激活函数。

 参数设置

  • width coefficient代表channel维度上的倍率因子,比如在 EfficientNetB0中Stagel的3x3卷积层所使用的卷积核个数是32,那么在B6中就是32×1.8=57.6接着取整到离它最近的8的整数倍即56,其它Stage同理。
  • depth_coefficient代表depth维度上的倍率因子(仅针对Stage2到Stage8),比如EfficientNetB0中Stage7的L=4,那么在B6中就是4×2.6=10.4,接着向上取整即11.

代码学习

class EfficientNet(nn.Module):
    def __init__(self,
                 width_coefficient: float, #宽度方向的倍率因子
                 depth_coefficient: float,
                 num_classes: int = 1000,  # 分类个数
                 dropout_rate: float = 0.2,
                 drop_connect_rate: float = 0.2,
                 block: Optional[Callable[..., nn.Module]] = None, #MBConv模块
                 norm_layer: Optional[Callable[..., nn.Module]] = None
                 ):
        super(EfficientNet, self).__init__()

        # 以基准网络构建的
        # kernel_size, in_channel, out_channel, exp_ratio, strides, use_SE, drop_connect_rate, repeats
        default_cnf = [[3, 32, 16, 1, 1, True, drop_connect_rate, 1],
                       [3, 16, 24, 6, 2, True, drop_connect_rate, 2],
                       [5, 24, 40, 6, 2, True, drop_connect_rate, 2],
                       [3, 40, 80, 6, 2, True, drop_connect_rate, 3],
                       [5, 80, 112, 6, 1, True, drop_connect_rate, 3],
                       [5, 112, 192, 6, 2, True, drop_connect_rate, 4],
                       [3, 192, 320, 6, 1, True, drop_connect_rate, 1]]

        def round_repeats(repeats):  # 重复多少次,仅针对stage2-8
            """Round number of repeats based on depth multiplier."""
            return int(math.ceil(depth_coefficient * repeats))

        if block is None:
            block = InvertedResidual  # MBconv

        if norm_layer is None:
            norm_layer = partial(nn.BatchNorm2d, eps=1e-3, momentum=0.1)

        adjust_channels = partial(InvertedResidualConfig.adjust_channels,
                                  width_coefficient=width_coefficient)

        # build inverted_residual_setting  为MB模块的配置文件传入默认参数
        bneck_conf = partial(InvertedResidualConfig,
                             width_coefficient=width_coefficient)

        #-------创建MB模块配置信息
        b = 0  # 统计搭建MBBlock的次数
        num_blocks = float(sum(round_repeats(i[-1]) for i in default_cnf)) # 获取网络中所有MB模块的重复次数
        inverted_residual_setting = []  # 用来存储MBconv的配置文件
        for stage, args in enumerate(default_cnf):
            cnf = copy.copy(args)
            for i in range(round_repeats(cnf.pop(-1))): # 取出并删掉每一行最后一个元素
                if i > 0: # 不等于0时
                    # strides equal 1 except first cnf
                    cnf[-3] = 1  # strides
                    cnf[1] = cnf[2]  # input_channel equal output_channel

               # b=0时dropoutratio也=0,随着堆叠的block越来越多,dropoutratio会慢慢增大逼近0.2
                cnf[-1] = args[-2] * b / num_blocks  # update dropout ratio
                index = str(stage + 1) + chr(i + 97)  # 1a, 2a, 2b, ... 记录顺序
                inverted_residual_setting.append(bneck_conf(*cnf, index))
                b += 1
        #-------MB模块配置信息

        # create layers
        layers = OrderedDict()

        # first conv
        layers.update({"stem_conv": ConvBNActivation(in_planes=3,  #RGB图片
                                                     out_planes=adjust_channels(32),
                                                     kernel_size=3,
                                                     stride=2,
                                                     norm_layer=norm_layer)})

        # building inverted residual blocks
        for cnf in inverted_residual_setting:  #遍历MB配置文件列表
            layers.update({cnf.index: block(cnf, norm_layer)})

        # build top
        last_conv_input_c = inverted_residual_setting[-1].out_c
        last_conv_output_c = adjust_channels(1280)  #宽度范围调整
        layers.update({"top": ConvBNActivation(in_planes=last_conv_input_c,
                                               out_planes=last_conv_output_c,
                                               kernel_size=1,
                                               norm_layer=norm_layer)})

        self.features = nn.Sequential(layers)   #stage1-stage8+stage9的1*1卷积
        self.avgpool = nn.AdaptiveAvgPool2d(1)

        classifier = []
        if dropout_rate > 0:
            classifier.append(nn.Dropout(p=dropout_rate, inplace=True))
        classifier.append(nn.Linear(last_conv_output_c, num_classes))
        self.classifier = nn.Sequential(*classifier)

        # initial weights  初始化权重
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode="fan_out")
                if m.bias is not None:
                    nn.init.zeros_(m.bias)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.ones_(m.weight)
                nn.init.zeros_(m.bias)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.zeros_(m.bias)

    def _forward_impl(self, x: Tensor) -> Tensor:
        x = self.features(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)

        return x

    def forward(self, x: Tensor) -> Tensor:
        return self._forward_impl(x)

3.Transformer

Self-Attention

假设输入的序列长度为2,输入就两个节点x1​,x2​,然后通过Input Embedding(f(x))将输入映射到a1​,a2​。紧接着分别将 a1​,a2​分别通过三个变换矩阵Wq​,Wk​,Wv​(这三个参数是可训练的,是共享的)得到对应的qi,ki,vi(原文的WqWkWv是通过全连接层实现的)。

下图假设a1是(1,1),Wq是(1 1;0 1),q1=a1*Wq=(1,2),q2=a2*Wq=(1,1)

  • q代表query,后续会去和每一个k进行匹配
  • k代表key,后续会被每个q匹配
  • v代表从a中提取得到的信息

接下来把求得的Q和每一个K进行match。先拿q^1和每个k进行match,点乘操作,接着除以√d​得到对应的α,其中d代表向量k^i的长度(k中有多少个元素),在本示例中等于2,除以√d​的原因在论文中的解释是“进行点乘后的数值很大,导致通过softmax后梯度变的很小”,所以通过除以√d来进行缩放。

再将α11和α12进行softmax操作,得到对应的针对每个v的权重,

 这个过程也可以用矩阵乘法的形式进行书写

 再进行进一步操作,上面已经计算得到 α,即针对每个v的权重,接着进行加权得到最终结果:

 以上步骤总结为这个公式:

 Multi-Head Attention

实际使用中基本使用的还是Multi-Head Attention模块。

首先还是和Self-Attention模块一样将ai​分别通过Wq,Wk,Wv得到对应的q^i, k^i, v^i,然后再根据使用的head的数目h进一步把得到的 q^i, k^i, v^i均分成h份。比如下图中假设 h=2然后 q^1拆分成q^{1,1} 和q^{1,2},那么q^{1,1}就属于head1,q^{1,2}属于head2。
 

 把所有的k和v都进行拆分,将第二个数值为1的(q11,k11,q21,k21....)全部归属为head1,

通过上述方法就能得到每个headi​对应的Qi​,Ki​,Vi​参数,接下来针对每个head使用和Self-Attention中相同的方法即可得到对应的结果。

接着将每个head得到的结果(第一个数字相同的)进行concat拼接,比如下图中 b11( head1​得到的b1​)和b12​( head2​得到的 b1​)拼接在一起,b21​( head1​得到的 b2​)和b22​(head2​得到的b2​)拼接在一起。

接着将拼接后的结果通过W^O(可学习的参数)进行融合,如下图所示,融合后得到最终的结果  b1​,b2​。

 位置编码

如果把a2和a3的位置进行调换,对结果b1​是没有任何影响的。

 为了引入位置信息,在原论文中引入了位置编码。如下图所示,位置编码是直接加在输入的a={a1​,...,an​}中的,即pe={pe1​,...,pen​}和a={a1​,...,an​}拥有相同的维度大小。关于位置编码在原论文中有提出两种方案,一种是原论文中使用的固定编码,即论文中给出的sine and cosine functions方法,按照该方法可计算出位置编码;另一种是可训练的位置编码,作者尝试了两种方法发现结果差不多(但在ViT论文中使用的是可训练的位置编码)。

二 代码学习

1.使用VGG模型进行猫狗大战

数据预处理

创建 VGG Model

直接使用预训练好的 VGG 模型。同时,为了展示 VGG 模型对本数据的预测结果,还下载了 ImageNet 1000 个类的 JSON 文件。

对输入的5个图片利用VGG模型进行预测,同时,使用softmax对结果进行处理,随后展示了识别结果

需要把最后的 nn.Linear 层由1000类,替换为2类。为了在训练中冻结前面层的参数,需要设置 required_grad=False。这样,反向传播训练梯度时,前面层的权重就不会自动更新了。训练中,只会更新最后一层的参数。

print(model_vgg)

model_vgg_new = model_vgg;

for param in model_vgg_new.parameters():
    param.requires_grad = False
model_vgg_new.classifier._modules['6'] = nn.Linear(4096, 2)
model_vgg_new.classifier._modules['7'] = torch.nn.LogSoftmax(dim = 1)

model_vgg_new = model_vgg_new.to(device)

print(model_vgg_new.classifier)

 训练并测试全连接层

包括三个步骤:第1步,创建损失函数和优化器;第2步,训练模型;

第3步,测试模型

2.AI艺术鉴赏挑战赛题

参考排行榜第一名的代码,采用torchvision中预训练好的efficientnet_b3模型,对网站提供的数据集进行训练

def dictmap(data, num=1):
    label = []
    fnames = []
    for line in data:
        name = int(line.split('.')[0].split('/')[num])
        fnames.append(name)
        label.append(file['label'][name])

    lab_dict = dict(zip(fnames, label))

    return lab_dict, label


def default_loader(path):
    return Image.open(path).convert('RGB')


def Train_Testloader(data):
    np.random.shuffle(data)
    lab_dict, label = dictmap(data, num=1)
    train_size = int((len(data)) * 0.8)
    # test_size = len(data_jpg) - train_size
    train_idx = data[:train_size]
    test_idx = data[train_size:]
    train_dataset = MyDataSet(train_idx, lab_dict, loader=default_loader, mode='train')
    test_dataset = MyDataSet(test_idx, lab_dict, loader=default_loader, mode='test')

    train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True, num_workers=6, pin_memory=True)
    test_loader = DataLoader(test_dataset, batch_size=8, shuffle=False, num_workers=6, pin_memory=True)

    return train_loader, test_loader


def main():

    print('Start Tensorboard with "tensorboard --logdir=runs", view at http://localhost:6006/')
    tb_writer = SummaryWriter()
    if os.path.exists("./weights") is False:
        os.makedirs("./weights")

    train_loader, test_loader = Train_Testloader(data_jpg)

    
    # 是否冻结权重
    if False:
        for name, para in model.named_parameters():
            # 除最后一个卷积层和全连接层外,其他权重全部冻结
            if ("features.top" not in name) and ("classifier" not in name):
                para.requires_grad_(False)
            else:
                print("training {}".format(name))

    pg = [p for p in model.parameters() if p.requires_grad]
    optimizer = optim.SGD(pg, lr=0.01, momentum=0.9, weight_decay=1E-4)
    # Scheduler https://arxiv.org/pdf/1812.01187.pdf
    lf = lambda x: ((1 + math.cos(x * math.pi / 30)) / 2) * (1 - 0.01) + 0.01  # cosine
    scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)

    for epoch in range(30):
        # train
        mean_loss = train_one_epoch(model=model,
                                    optimizer=optimizer,
                                    data_loader=train_loader,
                                    device=device,
                                    epoch=epoch)

        scheduler.step()

        # validate
        acc = evaluate(model=model,
                       data_loader=test_loader,
                       device=device)
        print("[epoch {}] accuracy: {}".format(epoch, round(acc, 3)))
        tags = ["loss", "accuracy", "learning_rate"]
        tb_writer.add_scalar(tags[0], mean_loss, epoch)
        tb_writer.add_scalar(tags[1], acc, epoch)
        tb_writer.add_scalar(tags[2], optimizer.param_groups[0]["lr"], epoch)

        torch.save(model.state_dict(), "./weights/model-{}.pth".format(epoch))

注意更改网络最后一层全连接的类别数量

model_efficientnet_b3 = models.efficientnet_b3(pretrained=True)

num_ftrs = model_efficientnet_b3.classifier[1].in_features
class_num = 49 
model_efficientnet_b3.classifier[1]= nn.Linear(num_ftrs, class_num)
# print(model_efficientnet_b3)

model = model_efficientnet_b3.to(device)

训练过程如下:

生成csv,并在网站上提交

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是的,这是VBA学习笔记的第一篇文章,涵盖两个主题:自动添加代码和VBA修改注册表。下面是更详细的介绍: ## 自动添加代码 在VBA中,我们可以使用代码自动添加代码。这通常用于在编写宏时自动添加模板代码或辅助代码。下面是一个简单的示例,演示如何使用VBA自动添加代码: ```vb Sub AddCode() '获取当前文档中的VBProject对象 Dim vbProj As VBIDE.VBProject Set vbProj = ActiveWorkbook.VBProject '获取当前文档中的Sheet1模块 Dim sheetMod As VBIDE.CodeModule Set sheetMod = vbProj.VBComponents("Sheet1").CodeModule '添加VBA代码 sheetMod.InsertLines 1, "Sub HelloWorld()" & vbCrLf & _ " MsgBox ""Hello, World!""" & vbCrLf & _ "End Sub" End Sub ``` 上述代码将在当前文档的Sheet1模块中添加一个名为"HelloWorld"的子例程,并在调用该例程时显示一个消息框。 ## VBA修改注册表 VBA可以使用Windows API函数来修改Windows注册表。这对于配置或自定义Windows系统非常有用。下面是一个简单的示例,演示如何使用VBA修改注册表中的一个键值: ```vb Option Explicit Private Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" ( _ ByVal hKey As Long, _ ByVal lpSubKey As String, _ ByVal ulOptions As Long, _ ByVal samDesired As Long, _ ByRef phkResult As Long) As Long Private Declare Function RegSetValueEx Lib "advapi32.dll" Alias "RegSetValueExA" ( _ ByVal hKey As Long, _ ByVal lpValueName As String, _ ByVal Reserved As Long, _ ByVal dwType As Long, _ ByVal lpData As Any, _ ByVal cbData As Long) As Long Private Declare Function RegCloseKey Lib "advapi32.dll" ( _ ByVal hKey As Long) As Long Private Const HKEY_CURRENT_USER = &H80000001 Private Const KEY_SET_VALUE = &H2 Private Const REG_SZ = 1 Sub ModifyRegistryKey() Dim hKey As Long Dim result As Long '打开注册表键 result = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", 0, KEY_SET_VALUE, hKey) If result = 0 Then '设置键值的值 result = RegSetValueEx(hKey, "Hidden", 0, REG_SZ, "2", 2) '关闭注册表键 result = RegCloseKey(hKey) End If End Sub ``` 上述代码演示了如何将 "Hidden" 键的值设置为 "2",这将隐藏文件资源管理器中的隐藏文件和文件夹。请注意,修改注册表可能会导致系统不稳定,因此在进行任何修改之前,请备份您的注册表。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值