精简CNN模型系列之四:ShuffleNet

介绍

牛人就是牛啊,不服不行。君不见Face++(旷视科技)在将Sun,Jian招入门下后,Face++在视觉科研领域的狂飙突进吗?今年的MS COCO大赛中他们更是一举拿下了六项比赛中的四项冠军。

ShuffleNet就是出自Face++的作品,华人世界的绝对骄傲!它同这一系列介绍的其它几个模型类似,同样致力于在保证模型精确度的基本上减少其计算量以使得训练得到的CNN模型可以部署在计算能力并不强大的移动设备上面。科技很多时候只有与市场需求接合起来才能发挥出迷人的魅力。

它直接对标的是Google同年提出的亦在移动设备上得到较好应用的MobileNet。MobileNet我们之前介绍过,主要通过使用Depthwise CNN与Pointwise CNN来减少传统CNN所需的密集计算。ShuffleNet对一般用作bottleneck模块的1x1 CNN计算作了更多思考,从而提出了使用Group-wise CNN与Shuffle module结合的思想来优化1x1 conv的bottleneck模块,有效地节省理论所需的计算,从而在一定算力的限定内,实现尽可能高的分类准确率。

ShuffleNet

结合了Channel Shuffle的Group convolutions

现在的精简CNN网络设计中使用Group convolutions已经成为一种趋势,它可有效地减少传统CNN所需的密集计算的运算量。但同时由于Groups之间彼此并不share feature map特征,这样就会导致每个filter只对限定的一部分输入特征可见,最终使得输出特征集合的表达能力大大降低(省计算与加强表达能力是个鱼与熊掌的选择问题啊)。

为了有效地对冲Groups convolution使用导致的Groups间特征互不相通的负面影响,作者提出了在Group convolution计算后对输出的output feature maps进行shuffle处理,以使得接下来的Group convolution filters可在每个group所输出的部分channels构成的集合上进行计算。

下图有力地表达了它的这一作用。

Channel_shuffle与Group-wise_convolution的结合使用

ShuffleNet Unit

当下CNN设计中都会先构成一种新颖的module,进而在对此module进行不断叠加,构成最终的新颖CNN网络。当然在构建网络时,不断阶段(而对不同大小的feature maps)可使用module的若干变形。像Inception module之于Googlenet系列网络,Depthwise Conv + Pointwise Conv组成的module之于MobileNet网络,或者Gate + residual block的组合module之于SkipNet等等无不是如此。

ShuffleNet自己也有其基本的Unit,在论文中被简单称为是ShuffleNet Unit。

我们前面已说过,ShuffleNet主要想对1x1 conv所构成的bottleneck模块进行优化,因此它的整体unit设计主要是从之前的Xception等网络中演变而来。
下图中我们能看出Groupwise convolution + shuffle的这种组合对传统1x1 conv bottleneck模块的代替使用。

ShuffleNet_unit

上图中,图a为传统的单单使用depthwise conv将3x3 conv优化过了的residual module(使用了bottleneck),在此结构中,1x1 conv将成为主要的计算消耗来源(在ResNxt中约占其94%的计算);图b中则为本文中提出的结合了groupd wise conv + shuffle而进行的1x1 conv bottleneck。对residual module中进行elementwise前所用的1x1 conv ( expand module,主要为了使得输出特征数目与原初输入特征集合数目相同)亦用了group wise conv的优化(直观上此处亦可以使用shuffle操作,但因为马上要进行接下来输出,故作用并不明显,作者实验也证明了这一点,故真正构建网络时并不在这个1x1 group wise conv之后再接shuffle操作。)。

ShuffleNet网络结构

下表中为作者尝试过的几种ShuffleNet结构与配置。可见作者试着探索了几种不同的group wise conv中group的数目。

ShuffleNet网络结构

它的构成整体上还是比较有规律的。可以看出上节介绍过的shuffleNet unit是构建它的基石。表中作者在变动groups数目时,保证了网络整体所用的计算量基本相似,这样当groups更多时,显然我们就能使用更多个channels的feature maps。结果表明,groups数目的模块可带来网络表达能力(以及最终分类准确率)的稳定提高。

另外还可以将channels数目视为网络构建的另一个可调参数。在下表中我们可以看出,随channels数目减小,网络的分类准确率开始下降(这个自然了,同样的结构下,显然愈小的CNN表达能力愈弱),但groups数目的增加却能在保证计算力相似的情况下有效提升ShuffleNet的分类能力。

Groups_number对ShuffleNet分类准确率的影响

实验结果

下表中为ShuffleNet与其它现代CNN网络在使用相似计算力的情况下在分类准确率上的比较。

ShuffleNet与其它现代CNN网络的比较

下表中反映了与其它CNN相比,ShuffleNet在达到相似分类准确率时在计算速度上所具有的优势。

ShuffleNet相对其它CNN所具有的计算优势

代码分析

下面为Shuffle操作的一种实现。可见它大多是一种memory copy操作,典型的membory bind的一种计算。它会对我们部署模型所用的mobile device的memory带宽有一定需求。

template <typename Dtype>
void ShuffleChannelLayer<Dtype>::Resize_cpu(Dtype *output, const Dtype *input, int group_row, int group_column, int len)
{
    for (int i = 0; i < group_row; ++i) // 2
    {
        for(int j = 0; j < group_column ; ++j) // 3
        {
            const Dtype* p_i = input + (i * group_column + j ) * len;
            Dtype* p_o = output + (j * group_row + i ) * len;

            caffe_copy(len, p_i, p_o);
        }
    }
}

template <typename Dtype>
void ShuffleChannelLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
                                             const vector<Blob<Dtype>*>& top) {
    const Dtype* bottom_data = bottom[0]->cpu_data();
    Dtype* top_data = top[0]->mutable_cpu_data();

    const int num = bottom[0]->shape(0);
    const int feature_map_size = bottom[0]->count(1);
    const int sp_sz = bottom[0]->count(2);
    const int chs = bottom[0]->shape(1);

    int group_row = group_;
    int group_column = int(chs / group_row);
    CHECK_EQ(chs, (group_column * group_row)) << "Wrong group size.";

    Dtype* temp_data = temp_blob_.mutable_cpu_data();
    for(int n = 0; n < num; ++n)
    {
        Resize_cpu(temp_data+n*feature_map_size, bottom_data +n*feature_map_size, group_row, group_column, sp_sz);
    }
    caffe_copy(bottom[0]->count(), temp_blob_.cpu_data(), top_data);
}

template <typename Dtype>
void ShuffleChannelLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
                                              const vector<bool>& propagate_down,
                                              const vector<Blob<Dtype>*>& bottom) {
    if (propagate_down[0]) {
        const Dtype* top_diff = top[0]->cpu_diff();
        Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();

        const int num = bottom[0]->shape(0);
        const int feature_map_size = bottom[0]->count(1);
        const int sp_sz = bottom[0]->count(2);
        const int chs = bottom[0]->shape(1);

        int group_row = int(chs / group_);
        int group_column = group_;

        Dtype* temp_diff = temp_blob_.mutable_cpu_diff();
        for(int n = 0; n < num; ++n)
        {
            Resize_cpu(temp_diff + n * feature_map_size, top_diff+n*feature_map_size, group_row, group_column, sp_sz);
        }
        caffe_copy(top[0]->count(), temp_blob_.cpu_diff(), bottom_diff);
    }
}

下面为ShuffleNet Unit在caffe protocol中的表示。

layer {
  name: "resx2_conv1"
  type: "Convolution"
  bottom: "resx1_concat"
  top: "resx2_conv1"
  convolution_param {
    num_output: 60
    kernel_size: 1
    stride: 1
    pad: 0
    group: 3
    bias_term: false
    weight_filler {
      type: "msra"
    }
  }
}
layer {
  name: "resx2_conv1_bn"
  type: "BatchNorm"
  bottom: "resx2_conv1"
  top: "resx2_conv1"
  param {
    lr_mult: 0
    decay_mult: 0
  }
  param {
    lr_mult: 0
    decay_mult: 0
  }
  param {
    lr_mult: 0
    decay_mult: 0
  }
}
layer {
  name: "resx2_conv1_scale"
  bottom: "resx2_conv1"
  top: "resx2_conv1"
  type: "Scale"
  scale_param {
    filler {
      value: 1
    }
    bias_term: true
    bias_filler {
      value: 0
    }
  }
}
layer {
  name: "resx2_conv1_relu"
  type: "ReLU"
  bottom: "resx2_conv1"
  top: "resx2_conv1"
}
layer {
  name: "shuffle2"
  type: "ShuffleChannel"
  bottom: "resx2_conv1"
  top: "shuffle2"
  shuffle_channel_param {
    group: 3
  }
}
layer {
  name: "resx2_conv2"
  type: "ConvolutionDepthwise"
  bottom: "shuffle2"
  top: "resx2_conv2"
  convolution_param {
    num_output: 60
    kernel_size: 3
    stride: 1
    pad: 1
    bias_term: false
    weight_filler {
      type: "msra"
    }
  }
}
layer {
  name: "resx2_conv2_bn"
  type: "BatchNorm"
  bottom: "resx2_conv2"
  top: "resx2_conv2"
  param {
    lr_mult: 0
    decay_mult: 0
  }
  param {
    lr_mult: 0
    decay_mult: 0
  }
  param {
    lr_mult: 0
    decay_mult: 0
  }
}
layer {
  name: "resx2_conv2_scale"
  bottom: "resx2_conv2"
  top: "resx2_conv2"
  type: "Scale"
  scale_param {
    filler {
      value: 1
    }
    bias_term: true
    bias_filler {
      value: 0
    }
  }
}
layer {
  name: "resx2_conv3"
  type: "Convolution"
  bottom: "resx2_conv2"
  top: "resx2_conv3"
  convolution_param {
    num_output: 240
    kernel_size: 1
    stride: 1
    pad: 0
    group: 3
    bias_term: false
    weight_filler {
      type: "msra"
    }
  }
}
layer {
  name: "resx2_conv3_bn"
  type: "BatchNorm"
  bottom: "resx2_conv3"
  top: "resx2_conv3"
  param {
    lr_mult: 0
    decay_mult: 0
  }
  param {
    lr_mult: 0
    decay_mult: 0
  }
  param {
    lr_mult: 0
    decay_mult: 0
  }
}
layer {
  name: "resx2_conv3_scale"
  bottom: "resx2_conv3"
  top: "resx2_conv3"
  type: "Scale"
  scale_param {
    filler {
      value: 1
    }
    bias_term: true
    bias_filler {
      value: 0
    }
  }
}
layer {
  name: "resx2_elewise"
  type: "Eltwise"
  bottom: "resx1_concat"
  bottom: "resx2_conv3"
  top: "resx2_elewise"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "resx2_elewise_relu"
  type: "ReLU"
  bottom: "resx2_elewise"
  top: "resx2_elewise"
}

参考文献

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容