当前位置:   article > 正文

深度学习目标检测 RCNN F-RCNN SPP yolo-v1 v2 v3 残差网络ResNet MobileNet SqueezeNet ShuffleNet等设计思想及架构简介_shufflenet v1 yolo

shufflenet v1 yolo

本文来自 EwenWanW 的CSDN 博客,原文网址:https://blog.csdn.net/xiaoxiaowenqiang/article/details/80141622

深度学习目标检测——结构变化顺序是RCNN->SPP->Fast RCNN->Faster RCNN->YOLO->SSD->YOLO2->Mask RCNN->YOLO3。

1. RCNN 区域卷积神经网络

 RCNN 网络思想:

       1. 首先使用 基于图论和层次聚类的候选框提取算法(SS),在原图像上提取一些可能的候选框;

       2. 对得到的候选框直接使用 resize变形算法之间变形到固定的尺寸;

       3. 对变形后的候选框分别使用 CNN卷积网络提取特征 得到固定长度的特征向量;

       4. 使用SVM支持向量机 进行分类

       5. 使用回归算法,在候选框的基础上回归得到 真实的边框。

RCNN存在三个明显的问题:

  1.多个候选区域对应的图像需要预先提取,占用较大的磁盘空间;

  2.针对传统CNN需要固定尺寸的输入图像,crop/warp(归一化)产生物体截断或拉伸,会导致输入CNN的信息丢失;

  3.每个ProposalRegion都需要进入CNN网络计算,上千个Region存在大量的重叠,重复的特征提取带来巨大的计算浪费。

2.SPP网络-空间金字塔网络

既然CNN的特征提取过程如此耗时(大量的卷积计算),为什么要对每一个候选区域独立计算,而不是提取整体特征,仅在分类之前做一次Region截取呢

SPP网络思想:

     1. 基于SS算法在原图像上提取一些可能的候选框;

     2. 对整张图像进行 CNN 卷积网路提取特征;

     3. 在特征图上根据1提取到的边框提取ROI区域(考虑卷积步长);

     4. 对得到的ROI特征图,使用SPP池化,分别对整张图、2*2图、4*4图的每个子图使用最大值池化,提取1+4+16=21个特征;

 

      5. 使用SVM支持向量机 进行分类

      6. 使用回归算法,在候选框的基础上回归得到 真实的边框。

尽管SPP-Net贡献很大,仍然存在很多问题:

1. 和RCNN一样,训练过程仍然是隔离的,提取候选框ss | 计算CNN特征| SVM分类 | Bounding Box回归独立训练,

大量的中间结果需要转存,无法整体训练参数;

2. SPP-Net无法同时协调SPP-层两边的卷积层和全连接层,很大程度上限制了深度CNN的效果;

3. 在整个过程中,Proposal Region(ss算法)仍然很耗时。

3. Fast RCNN 快速-区域卷积神经网络

Fast RCNN 网络思想:

     1. ss 算法产出候选区域(region of interest, ROI);

     2. 对整张图像使用 CNN 提取出提取出feature map;

     3. ROI对应的特征区域 进入Fast RCNN网络;

     4. 对ROI使用  ROI pooling层(单层SPP,分成W*H个子图,最大值池化) 框定(固定)尺寸, 

        通过一些全连接层得到ROI 特征向量;

     5. ROI 特征向量,分别进入两个不同的全连接层,一个通过 softmax得到 类别概率,分类结果,

        另一个通过regression回归,得到 bounding box 边界框。

问题在以下方面得到改进:

  1. 卖点1 - 借鉴SPP思路,提出简化版的ROI池化层(注意,没用金字塔,替换spp),同时加入了候选框映射功能,

使得网络能够反向传播,解决了SPP的整体网络训练问题;

2. 卖点2 - 多任务Loss层;

A. SoftmaxLoss代替了SVM,证明了softmax比SVM更好的效果;

B. SmoothL1Loss取代Bouding box回归。

将分类和边框回归进行合并(又一个开创性的思路),通过多任务Loss层进一步整合深度网络, 统一了训练过程,从而提高了算法准确度。

3. 全连接层通过SVD加速,这个大家可以自己看,有一定的提升但不是革命性的;

4. 结合上面的改进,模型训练时可对所有层进行更新,除了速度提升外(训练速度是SPP的3倍,测试速度10倍),

得到了更好的检测效果(VOC07数据集mAP为70,注:mAP,mean Average Precision)。

4. Faster RCNN 更快速-区域卷积神经网络

  1. 对于提取候选框最常用的SelectiveSearch方法,提取一副图像大概需要2s的时间,
  2. 改进的EdgeBoxes算法将效率提高到了0.2s,但是这还不够。
  3. 候选框提取不一定要在原图上做,特征图上同样可以,低分辨率特征图意味着更少的计算量,
  4. 基于这个假设,MSRA的任少卿等人提出RPN(RegionProposal Network),完美解决了这个问题,我们先来看一下网络拓扑。

Faster RCNN 网络思想:

     1. 对整张图像使用 CNN 提取出提取出 特征图feature map

     2. 使用区域建议网络RPN(Region Proposal Network) 代替之前的 ss 算法,

        在特征图上产生候选区域 ROI(RPN网络与检测网络共享特征)。

     3. ROI对应的特征区域 进入Fast RCNN网络

     4. 先通过 ROI pooling层,将ROI特征区域池化到(固定)尺寸, 
        通过一些全连接层得到ROI 特征向量,

     5. ROI 特征向量,分别进入两个不同的全连接层,一个通过 softmax得到 类别概率,分类结果,另一个通过regression回归,得到 bounding box 边界框。

5.YOLO - v1 一个阶段的卷积神经网络

卷积提取特征,边框回归+softmax分类,NMS飞极大值抑制,得到边框和预测结果.

  1. 其中,卷积层用来提取图像特征,全连接层用来预测图像位置和类别概率值。
  2. YOLO网络借鉴了GoogLeNet分类网络结构。不同的是,YOLO未使用inception
  3. module,而是使用1x1卷积层(此处1x1卷积层的存在是为了跨通道信息整合)+3x3卷积层简单替代。

YOLO 算法描述为:

1)将图像划分为固定的网格(比如7*7),如果某个样本Object中心落在对应网格,该网格负责这个Object位置的回归;

2)每个网格预测包含Object位置与置信度信息,这些信息编码为一个向量;

3)网络输出层即为每个Grid的对应结果,由此实现端到端的训练。

YOLO算法的问题有以下几点:

1)7*7的网格回归特征丢失比较严重,缺乏多尺度回归依据;
    2)Loss计算方式无法有效平衡(不管是加权或者均差),Loss收敛变差,导致模型不稳定。

预测格子设置及方案:

 

 
  1. 7*7*30 (4 + 1)×2 +20 =30

  2. 每个格子输出B个bounding box(包含物体的矩形区域)信息,以及C个物体属于某种类别的概率信息。

  3. Bounding box信息包含5个数据值,分别是x,y,w,h,和confidence。

  4. 其中x,y是指当前格子预测得到的物体的bounding box的中心位置的坐标。

  5. w,h是bounding box的宽度和高度。

注意:
实际训练过程中,w和h的值使用图像的宽度和高度进行归一化到[0,1]区间内;
 
  1. x,y是bounding box中心位置相对于当前格子位置的偏移值,并且被归一化到[0,1]。

  2.  
  3. YOLO网络最终的全连接层的输出维度是 S*S*(B*5 + C)。

  4. YOLO论文中,作者训练采用的输入图像分辨率是448x448,S=7,B=2;

  5. 采用VOC 20类标注物体作为训练数据,C=20。因此输出向量为7*7*(20 + 2*5)=1470维。

 
  1. 注:

  2. 1. 由于输出层为全连接层,因此在检测时,YOLO训练模型只支持与训练图像相同的输入分辨率。

  3. 2. 虽然每个格子可以预测B个bounding box,但是最终只选择只选择交并比 IOU 最高的bounding box,最后通过NMS后,输出,

  4. 3. 即每个格子最多只预测出一个物体。当物体占画面比例较小,如图像中包含畜群或鸟群时,

  5. 每个格子包含多个物体,但却只能检测出其中一个。这是YOLO方法的一个缺陷。

yolo-v1 结构

  1. 1. 448*448*3 图像输入

  2. 2. 7*7卷积步长2 64输出 + 2*2最大值池化步长2

  3. 3. 3*3卷积步长1 192输出 + 2*2最大值池化步长2

  4. 4. 1*1卷积128输出 + 3*3卷积256输出 + 1*1卷积256输出 + 3*3卷积512输出 + 2*2最大值池化步长2

  5. 5. (1*1卷积256输出 + 3*3卷积512输出 )*4次 + 1*1卷积512输出 + 3*3卷积1024输出 + 2*2最大值池化步长2

  6. 6. (1*1卷积512输出 + 3*3卷积1024输出)*2次 + 3*3卷积1024输出 + 3*3卷积步长2 1024输出

  7. 7. 3*3卷积1024输出*2次

  8. 8. 全链接层(1*1卷积) 512输出 + 全链接层(1*1卷积)4096输出 + 全链接层(1*1卷积)1470输出

yolo-v1 tensorflow实现

github代码 

  1. #-*- coding: utf-8 -*-
  2. """
  3. Yolo V1 by tensorflow
  4. 23个卷积层
  5. 输入: 448*448*3 , 输出: N*1*1470
  6. # 每张图片划分 7*7个网格 每个网格预测 2个框 每个框 5个参数 共同拥有 20个类别预测概率 7*7*(2*5+20)=1470
  7. 1. 448*448*3 图像输入
  8. 2. 7*7卷积步长2 64输出 + 2*2最大值池化步长2
  9. 3. 3*3卷积步长1 192输出 + 2*2最大值池化步长2
  10. 4. 1*1卷积128输出 + 3*3卷积256输出 + 1*1卷积256输出 + 3*3卷积512输出 + 2*2最大值池化步长2
  11. 5. (1*1卷积256输出 + 3*3卷积512输出 )*4次 + 1*1卷积512输出 + 3*3卷积1024输出 + 2*2最大值池化步长2
  12. 6. (1*1卷积512输出 + 3*3卷积1024输出)*2次 + 3*3卷积1024输出 + 3*3卷积步长2 1024输出
  13. 7. 3*3卷积1024输出*2次
  14. 8. 全链接层(1*1卷积) 512输出 + 全链接层(1*1卷积)4096输出 + 全链接层(1*1卷积)1470输出
  15. paper https://pjreddie.com/media/files/papers/yolo.pdf
  16. """
  17. import numpy as np
  18. import tensorflow as tf
  19. import cv2
  20. # Leaky ReLU激活函数
  21. def leak_relu(x, alpha=0.1):
  22. return tf.maximum(alpha * x, x)
  23. class Yolo(object):
  24. def __init__(self, weights_file, verbose=True):#打印调试信息标志verbose
  25. self.verbose = verbose
  26. # detection params
  27. self.S = 7 # cell size 分割的格子尺寸 7*7个格子
  28. self.B = 2 # boxes_per_cell 每个格子预测 的 边框数量
  29. self.classes = ["aeroplane", "bicycle", "bird", "boat", "bottle",
  30. "bus", "car", "cat", "chair", "cow", "diningtable",
  31. "dog", "horse", "motorbike", "person", "pottedplant",
  32. "sheep", "sofa", "train","tvmonitor"]# 分类的物体类别
  33. self.C = len(self.classes) # number of classes 类别总数
  34. # offset for box center (top left point of each cell) 格子框的 左上角 0 1 2 3 4 5 6
  35. self.x_offset = np.transpose(np.reshape(np.array([np.arange(self.S)]*self.S*self.B),
  36. [self.B, self.S, self.S]), [1, 2, 0])
  37. self.y_offset = np.transpose(self.x_offset, [1, 0, 2])
  38. self.threshold = 0.2 # 置信度*类别概率 得分预置 confidence scores threhold 最终显示框
  39. self.iou_threshold = 0.4 # 交并比 预置
  40. # the maximum number of boxes to be selected by non max suppression
  41. self.max_output_size = 10# 非极大值抑制 选择的框的数量
  42. self.sess = tf.Session()
  43. self._build_net()# 创建网络结构
  44. self._build_detector()# 对网络输出 转换成识别结果
  45. self._load_weights(weights_file)
  46. # In 输入: 448*448*3 , OUT 输出: N*1*1470
  47. def _build_net(self):
  48. """build the network"""
  49. if self.verbose:
  50. print("Start to build the network ...")
  51. #### 1. 448*448*3 图像输入 ####################################################################################
  52. self.images = tf.placeholder(tf.float32, [None, 448, 448, 3])# 448*448*3 rgb三通道图像
  53. #### 2. 7*7卷积步长2 64输出 + 2*2最大值池化步长2 ##################################################################
  54. net = self._conv_layer(self.images, 1, 64, 7, 2)#N*448*448*3 -> 7*7*3*64/2 -> N*224*224*64
  55. net = self._maxpool_layer(net, 1, 2, 2) # 2*2 池化核 步长2 -> 112*112*64
  56. #### 3. 3*3卷积步长1 192输出 + 2*2最大值池化步长2 #################################################################
  57. net = self._conv_layer(net, 2, 192, 3, 1)# 112*112*64 -> 3*3*64*192/1 -> 112*112*192
  58. net = self._maxpool_layer(net, 2, 2, 2) # 2*2 池化核 步长2 -> 56*56*192
  59. #### 4. 1*1卷积128输出 + 3*3卷积256输出 + 1*1卷积256输出 + 3*3卷积512输出 + 2*2最大值池化步长2######################
  60. net = self._conv_layer(net, 3, 128, 1, 1)# 56*56*192 -> 1*1*192*128/1 -> 56*56*128
  61. net = self._conv_layer(net, 4, 256, 3, 1)# 56*56*128 -> 3*3*128*256/1 -> 56*56*256
  62. net = self._conv_layer(net, 5, 256, 1, 1)# 56*56*256 -> 1*1*256*256/1 -> 56*56*256
  63. net = self._conv_layer(net, 6, 512, 3, 1)# 56*56*256 -> 3*3*256*512/1 -> 56*56*512
  64. net = self._maxpool_layer(net, 6, 2, 2) # 2*2 池化核 步长2 -> 28*28*512
  65. #### 5. (1*1卷积256输出 + 3*3卷积512输出 )*4次 + 1*1卷积512输出 + 3*3卷积1024输出 + 2*2最大值池化步长2 ##############
  66. net = self._conv_layer(net, 7, 256, 1, 1)# 28*28*512 -> 1*1*512*256/1 -> 28*28*256
  67. net = self._conv_layer(net, 8, 512, 3, 1)# 28*28*256 -> 3*3*256*512/1 -> 28*28*512
  68. net = self._conv_layer(net, 9, 256, 1, 1)# 28*28*512 -> 1*1*512*256/1 -> 28*28*256
  69. net = self._conv_layer(net, 10, 512, 3, 1)#28*28*256 -> 3*3*256*512/1 -> 28*28*512
  70. net = self._conv_layer(net, 11, 256, 1, 1)#28*28*512 -> 1*1*512*256/1 -> 28*28*256
  71. net = self._conv_layer(net, 12, 512, 3, 1)#28*28*256 -> 3*3*256*512/1 -> 28*28*512
  72. net = self._conv_layer(net, 13, 256, 1, 1)#28*28*512 -> 1*1*512*256/1 -> 28*28*256
  73. net = self._conv_layer(net, 14, 512, 3, 1)#28*28*256 -> 3*3*256*512/1 -> 28*28*512
  74. #### ###### 1*1卷积512输出 + 3*3卷积1024输出 + 2*2最大值池化步长2#############
  75. net = self._conv_layer(net, 15, 512, 1, 1)#28*28*512 -> 1*1*512*512/1 -> 28*28*512
  76. net = self._conv_layer(net, 16, 1024, 3, 1)#28*28*512-> 3*3*512*1024/1-> 28*28*1024
  77. net = self._maxpool_layer(net, 16, 2, 2) #2*2 池化核 步长2 -> 14*14*1024
  78. #### 6. (1*1卷积512输出 + 3*3卷积1024输出)*2次 + 3*3卷积1024输出 + 3*3卷积步长2 1024输出################################
  79. net = self._conv_layer(net, 17, 512, 1, 1) #14*14*1024 -> 1*1*1024*512/1 -> 14*14*512
  80. net = self._conv_layer(net, 18, 1024, 3, 1)#14*14*512 -> 3*3*512*1024/1 -> 14*14*1024
  81. net = self._conv_layer(net, 19, 512, 1, 1) #14*14*1024 -> 1*1*1024*512/1 -> 14*14*512
  82. net = self._conv_layer(net, 20, 1024, 3, 1)#14*14*512 -> 3*3*512*1024/1 -> 14*14*1024
  83. net = self._conv_layer(net, 21, 1024, 3, 1)#14*14*1024 -> 3*3*1024*1024/1-> 14*14*1024
  84. net = self._conv_layer(net, 22, 1024, 3, 2)#14*14*1024 -> 3*3*1024*1024/2-> 7*7*1024
  85. #### 7. 3*3卷积1024输出*2次 ##########################################################################################
  86. net = self._conv_layer(net, 23, 1024, 3, 1)#7*7*1024 -> 3*3*1024*1024/1-> 7*7*1024
  87. net = self._conv_layer(net, 24, 1024, 3, 1)#7*7*1024 -> 3*3*1024*1024/1-> 7*7*1024
  88. #### 8. 全链接层(1*1卷积) 512输出 + 全链接层(1*1卷积)4096输出 + 全链接层(1*1卷积)1470输出##########################
  89. net = self._flatten(net)# 7*7*1024 -> 1* 7*7*1024
  90. net = self._fc_layer(net, 25, 512, activation=leak_relu)# 1*512
  91. net = self._fc_layer(net, 26, 4096, activation=leak_relu)# 1*4096
  92. net = self._fc_layer(net, 27, self.S*self.S*(self.C+5*self.B))#1* s*s *((4+1)*b+c) = 7*7*(5*2+20) = 1*1470
  93. # 1470 = 前980 类别预测概率 + 98 边框置信度 + 196 边框1参数 + 196 边框2参数
  94. self.predicts = net
  95. def _build_detector(self):
  96. """Interpret the net output and get the predicted boxes"""
  97. # the width and height of orignal image
  98. self.width = tf.placeholder(tf.float32, name="img_w")
  99. self.height = tf.placeholder(tf.float32, name="img_h")
  100. # get class prob, confidence, boxes from net output
  101. idx1 = self.S * self.S * self.C# 总 类别预测数量 7*7*20 = 980
  102. idx2 = idx1 + self.S * self.S * self.B# 总边框数量 + 总 类别预测数量
  103. # class prediction 类别预测概率 7*7*2=98
  104. class_probs = tf.reshape(self.predicts[0, :idx1], [self.S, self.S, self.C])
  105. # confidence 置信度 0/1 * 交并比
  106. confs = tf.reshape(self.predicts[0, idx1:idx2], [self.S, self.S, self.B])
  107. # boxes -> (x, y, w, h) 7*7*1*4 + 7*7*1*4 = 196
  108. boxes = tf.reshape(self.predicts[0, idx2:], [self.S, self.S, self.B, 4])# (x,y,w,h)
  109. # convert the x, y to the coordinates relative to the top left point of the image
  110. # the predictions of w, h are the square root
  111. # multiply the width and height of image
  112. # 得到真实 矩形框 坐标中心 和 长宽尺寸
  113. boxes = tf.stack([(boxes[:, :, :, 0] + tf.constant(self.x_offset, dtype=tf.float32)) / self.S * self.width,#x小格子占比
  114. (boxes[:, :, :, 1] + tf.constant(self.y_offset, dtype=tf.float32)) / self.S * self.height,#y
  115. tf.square(boxes[:, :, :, 2]) * self.width,#w 0~1 * 图片尺寸
  116. tf.square(boxes[:, :, :, 3]) * self.height], axis=3)#h 0~1 * 图片尺寸
  117. ## 最终得分 置信度*类别预测概率 class-specific confidence scores [S, S, B, C]
  118. scores = tf.expand_dims(confs, -1) * tf.expand_dims(class_probs, 2)#增加一维
  119. scores = tf.reshape(scores, [-1, self.C]) # [S*S*B, C]#98个框 每个框 20个预测得分
  120. boxes = tf.reshape(boxes, [-1, 4]) # [S*S*B, 4]#98个框 每个框 四个 边框参数 坐标中心 和 长宽尺寸
  121. # find each box class, only select the max score
  122. box_classes = tf.argmax(scores, axis=1)# 在98个框中找到 20个得分中最高的 类别
  123. box_class_scores = tf.reduce_max(scores, axis=1)#最高的 得分
  124. # filter the boxes by the score threshold
  125. filter_mask = box_class_scores >= self.threshold#大于得分显示阈值的
  126. scores = tf.boolean_mask(box_class_scores, filter_mask)# 对应最终的得分
  127. boxes = tf.boolean_mask(boxes, filter_mask)#框的位置
  128. box_classes = tf.boolean_mask(box_classes, filter_mask)#类别
  129. # non max suppression (do not distinguish different classes)
  130. # ref: https://tensorflow.google.cn/api_docs/python/tf/image/non_max_suppression
  131. # box (x, y, w, h) -> box (x1, y1, x2, y2) 得到边框 上四条边的中心点
  132. _boxes = tf.stack([boxes[:, 0] - 0.5 * boxes[:, 2], boxes[:, 1] - 0.5 * boxes[:, 3],# x-0.5*w
  133. boxes[:, 0] + 0.5 * boxes[:, 2], boxes[:, 1] + 0.5 * boxes[:, 3]], axis=1)
  134. #非极大值抑制 筛选 剔除 重叠度高的边框
  135. nms_indices = tf.image.non_max_suppression(_boxes, scores,
  136. self.max_output_size, self.iou_threshold)
  137. self.scores = tf.gather(scores, nms_indices)
  138. self.boxes = tf.gather(boxes, nms_indices)
  139. self.box_classes = tf.gather(box_classes, nms_indices)
  140. # 卷积层 输入 id 卷积核数量 卷积核尺寸 滑动卷积步长
  141. def _conv_layer(self, x, id, num_filters, filter_size, stride):
  142. """Conv layer"""
  143. in_channels = x.get_shape().as_list()[-1]# 输入通道数量
  144. # 创建卷积权重 尺寸*尺寸*通道数 * 卷积核数量
  145. weight = tf.Variable(tf.truncated_normal([filter_size, filter_size,
  146. in_channels, num_filters], stddev=0.1))
  147. bias = tf.Variable(tf.zeros([num_filters,]))# 偏置为 输出 卷积核数量 个
  148. # padding, note: not using padding="SAME"
  149. pad_size = filter_size // 2# 填充
  150. pad_mat = np.array([[0, 0], [pad_size, pad_size], [pad_size, pad_size], [0, 0]])
  151. x_pad = tf.pad(x, pad_mat)# 在输入层 加上pad扩展边
  152. conv = tf.nn.conv2d(x_pad, weight, strides=[1, stride, stride, 1], padding="VALID")
  153. output = leak_relu(tf.nn.bias_add(conv, bias)) #Leaky ReLU激活函数
  154. if self.verbose:
  155. print(" Layer %d: type=Conv, num_filter=%d, filter_size=%d, stride=%d, output_shape=%s" \
  156. % (id, num_filters, filter_size, stride, str(output.get_shape())))
  157. return output
  158. # 全连接层
  159. def _fc_layer(self, x, id, num_out, activation=None):
  160. """fully connected layer"""
  161. num_in = x.get_shape().as_list()[-1]# 输入通道数量
  162. weight = tf.Variable(tf.truncated_normal([num_in, num_out], stddev=0.1))
  163. bias = tf.Variable(tf.zeros([num_out,]))
  164. output = tf.nn.xw_plus_b(x, weight, bias)
  165. if activation:
  166. output = activation(output)
  167. if self.verbose:
  168. print(" Layer %d: type=Fc, num_out=%d, output_shape=%s" \
  169. % (id, num_out, str(output.get_shape())))
  170. return output
  171. # 最大值池化层
  172. def _maxpool_layer(self, x, id, pool_size, stride):
  173. output = tf.nn.max_pool(x, [1, pool_size, pool_size, 1],
  174. strides=[1, stride, stride, 1], padding="SAME")
  175. if self.verbose:
  176. print(" Layer %d: type=MaxPool, pool_size=%d, stride=%d, output_shape=%s" \
  177. % (id, pool_size, stride, str(output.get_shape())))
  178. return output
  179. # 平滑
  180. def _flatten(self, x):
  181. """flatten the x"""
  182. tran_x = tf.transpose(x, [0, 3, 1, 2]) # channle first mode
  183. nums = np.product(x.get_shape().as_list()[1:])
  184. return tf.reshape(tran_x, [-1, nums])
  185. # 载入网络参数
  186. def _load_weights(self, weights_file):
  187. """Load weights from file"""
  188. if self.verbose:
  189. print("Start to load weights from file:%s" % (weights_file))
  190. saver = tf.train.Saver()
  191. saver.restore(self.sess, weights_file)
  192. def detect_from_file(self, image_file, imshow=True, deteted_boxes_file="boxes.txt",
  193. detected_image_file="detected_image.jpg"):
  194. """Do detection given a image file"""
  195. # read image
  196. image = cv2.imread(image_file)
  197. img_h, img_w, _ = image.shape
  198. scores, boxes, box_classes = self._detect_from_image(image)
  199. predict_boxes = []
  200. for i in range(len(scores)):
  201. predict_boxes.append((self.classes[box_classes[i]], boxes[i, 0],
  202. boxes[i, 1], boxes[i, 2], boxes[i, 3], scores[i]))
  203. self.show_results(image, predict_boxes, imshow, deteted_boxes_file, detected_image_file)
  204. #从图像上检测 物体
  205. def _detect_from_image(self, image):
  206. """Do detection given a cv image"""
  207. img_h, img_w, _ = image.shape#图像长宽
  208. img_resized = cv2.resize(image, (448, 448))#resize到固定尺寸 448*448
  209. img_RGB = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)# 转到 RGB 通道
  210. img_resized_np = np.asarray(img_RGB)#转换成 数组
  211. _images = np.zeros((1, 448, 448, 3), dtype=np.float32)
  212. _images[0] = (img_resized_np / 255.0) * 2.0 - 1.0# 像素值归一化到 0~1 之间
  213. scores, boxes, box_classes = self.sess.run([self.scores, self.boxes, self.box_classes],
  214. feed_dict={self.images: _images, self.width: img_w, self.height: img_h})
  215. return scores, boxes, box_classes
  216. # 打印结果 显示框选 的结果
  217. def show_results(self, image, results, imshow=True, deteted_boxes_file=None,
  218. detected_image_file=None):
  219. """Show the detection boxes"""
  220. img_cp = image.copy()#赋值原图像 因为要修改 画 矩形在上面
  221. if deteted_boxes_file:
  222. f = open(deteted_boxes_file, "w")#写文件
  223. # draw boxes
  224. for i in range(len(results)):
  225. x = int(results[i][1])#中心点坐标
  226. y = int(results[i][2])#
  227. w = int(results[i][3]) // 2# 矩形框宽度
  228. h = int(results[i][4]) // 2# 矩形框高度
  229. if self.verbose:#打印调试信息
  230. print(" class: %s, [x, y, w, h]=[%d, %d, %d, %d], confidence=%f" % (results[i][0],
  231. x, y, w, h, results[i][-1]))
  232. cv2.rectangle(img_cp, (x - w, y - h), (x + w, y + h), (0, 255, 0), 2)
  233. cv2.rectangle(img_cp, (x - w, y - h - 20), (x + w, y - h), (125, 125, 125), -1)
  234. cv2.putText(img_cp, results[i][0] + ' : %.2f' % results[i][5], (x - w + 5, y - h - 7),
  235. cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)
  236. if deteted_boxes_file:
  237. f.write(results[i][0] + ',' + str(x) + ',' + str(y) + ',' +
  238. str(w) + ',' + str(h)+',' + str(results[i][5]) + '\n')
  239. if imshow:
  240. cv2.imshow('YOLO_small detection', img_cp)
  241. cv2.waitKey(1)
  242. if detected_image_file:
  243. cv2.imwrite(detected_image_file, img_cp)
  244. if deteted_boxes_file:
  245. f.close()
  246. if __name__ == "__main__":
  247. yolo_net = Yolo("./weights/YOLO_small.ckpt")
  248. yolo_net.detect_from_file("./test/car.jpg")

6.YOLO - v2  k-means聚类得到候选框 Passthrough层结合不同尺度层特征

 

YOLO - v2 在 v1上的改进:

    1.  多尺度网格设计,新的网络Darknet-19,共32下采样补偿,如416*416输入,最终特征图尺寸为13*13;

    2. 使用聚类从数据集中得到先验框的尺寸和数量,综合速度和准确度,使用5种先验框;

    3. 每个格子预测5种框,每个框,有4个坐标参数(x,y,w,h),1个置信度,分类数量个 类别预测概率,例如20

    4. 激活函数之前使用BN批规范化。 

 

yolo-v2 结构

 

 
  1. 1. 3*3*3*32 卷积核 3通道输入 32通道输出 步长1 + 最大值池化

  2. 2. 3*3*32*64 卷积核 32通道输入 64通道输出 步长1 + 最大值池化

  3. 3. 3*3 1*1 3*3 卷积 + 最大值池化

  4. 4. 3*3 1*1 3*3 卷积 + 最大值池化

  5. 5. 3*3 1*1 3*3 1*1 3*3 卷积 + 最大值池化

  6. 6. 3*3 1*1 3*3 1*1 3*3 卷积

  7. 7. 3*3 3*3 卷积

  8. 7.5 passtrough 层 尺寸减半 通道数量变为4倍 跨层 通道合并concat

  9. 8. 3*3*(1024+64*4)*1024 卷积核 1280通道输入 1024通道输出 步长1

  10. 9. 3*3*1024* n_last_channels 卷积核 1024通道输入 n_last_channels 通道输出 步长1 检测网络输出

yolo-v2 tensorflow实现

github代码

utils.py

  1. # -*- coding: utf-8 -*-
  2. # 常用函数等核心函数 预处理图像 后端处理图像
  3. # 在图片上显示检测结果 按照预测边框的得分进行排序
  4. # 计算两个边框之间的交并比 非极大值抑制排除重复的边框
  5. """
  6. Help functions for YOLOv2
  7. """
  8. import random
  9. import colorsys
  10. import cv2
  11. import numpy as np
  12. ############## 预处理图像 ##################
  13. ############## preprocess image ##################
  14. def preprocess_image(image, image_size=(416, 416)):
  15. """Preprocess a image to inference"""
  16. image_cp = np.copy(image).astype(np.float32)# Float32格式
  17. # 图像变形固定尺寸 resize the image
  18. image_rgb = cv2.cvtColor(image_cp, cv2.COLOR_BGR2RGB)# BGR 转成 RGB
  19. image_resized = cv2.resize(image_rgb, image_size)# 变形到固定尺寸
  20. # 归一化到 0~1 normalize
  21. image_normalized = image_resized.astype(np.float32) / 255.0# 除以最大值 255 -> 0~1
  22. # 扩展一个维度来存储 批次大小 expand the batch_size dim 416*416 -> 1*416*416
  23. image_expanded = np.expand_dims(image_normalized, axis=0)
  24. return image_expanded
  25. ############## 后端处理图像 #######################
  26. def postprocess(bboxes, obj_probs, class_probs, image_shape=(416, 416),
  27. threshold=0.5):
  28. """post process the detection results 处理检测结果 """
  29. bboxes = np.reshape(bboxes, [-1, 4])# 边框值 0~1之间
  30. bboxes[:, 0::2] *= float(image_shape[1])# 乘上 图像大小 变成 实际大小 0 2 列 正比高 h
  31. bboxes[:, 1::2] *= float(image_shape[0])# 1 3列 正比 宽 W
  32. bboxes = bboxes.astype(np.int32)# 截取整数
  33. # 边框要在 图像大小之内 clip the bboxs
  34. bbox_ref = [0, 0, image_shape[1] - 1, image_shape[0] - 1]
  35. bboxes = bboxes_clip(bbox_ref, bboxes)
  36. # 预测的 目标/非目标 概率
  37. obj_probs = np.reshape(obj_probs, [-1])
  38. class_probs = np.reshape(class_probs, [len(obj_probs), -1])
  39. class_inds = np.argmax(class_probs, axis=1)#类别
  40. class_probs = class_probs[np.arange(len(obj_probs)), class_inds]#类别概率
  41. scores = obj_probs * class_probs# 分数 = 目标/非目标 概率 * 类别概率
  42. # filter bboxes with scores > threshold
  43. keep_inds = scores > threshold# 得分大于阈值 的索引
  44. bboxes = bboxes[keep_inds]# 对应的得分较好的边框
  45. scores = scores[keep_inds]# 对应的得分
  46. class_inds = class_inds[keep_inds]# 对应的预测类型
  47. # 按照得分 排序sort top K
  48. class_inds, scores, bboxes = bboxes_sort(class_inds, scores, bboxes)
  49. # 非极大值抑制 排除 重叠度较大的预测框nms
  50. class_inds, scores, bboxes = bboxes_nms(class_inds, scores, bboxes)
  51. return bboxes, scores, class_inds# 最终的 边框 得分 类别索引
  52. # 在图片上显示 检测结果
  53. def draw_detection(im, bboxes, scores, cls_inds, labels, thr=0.3):
  54. # for display
  55. ############################
  56. # Generate colors for drawing bounding boxes.
  57. hsv_tuples = [(x / float(len(labels)), 1., 1.)
  58. for x in range(len(labels))]
  59. colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
  60. colors = list(
  61. map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)),
  62. colors))
  63. random.seed(10101) # Fixed seed for consistent colors across runs.
  64. random.shuffle(colors) # Shuffle colors to decorrelate adjacent classes.
  65. random.seed(None) # Reset seed to default.
  66. # draw image
  67. imgcv = np.copy(im)
  68. h, w, _ = imgcv.shape
  69. for i, box in enumerate(bboxes):
  70. if scores[i] < thr:
  71. continue
  72. cls_indx = cls_inds[i]
  73. thick = int((h + w) / 300)# 线条粗细
  74. # 画长方形
  75. cv2.rectangle(imgcv,
  76. (box[0], box[1]), (box[2], box[3]),
  77. colors[cls_indx], thick)
  78. mess = '%s: %.3f' % (labels[cls_indx], scores[i])
  79. if box[1] < 20:
  80. text_loc = (box[0] + 2, box[1] + 15)
  81. else:
  82. text_loc = (box[0], box[1] - 10)
  83. cv2.putText(imgcv, mess, text_loc,
  84. cv2.FONT_HERSHEY_SIMPLEX, 1e-3 * h, colors[cls_indx], thick // 3)
  85. return imgcv
  86. ############## process bboxes 边框要在 图像大小之内 ##################
  87. def bboxes_clip(bbox_ref, bboxes):
  88. """Clip bounding boxes with respect to reference bbox.
  89. """
  90. bboxes = np.copy(bboxes)#实际边框 中心点 宽 高
  91. bboxes = np.transpose(bboxes)#转置
  92. bbox_ref = np.transpose(bbox_ref)
  93. bboxes[0] = np.maximum(bboxes[0], bbox_ref[0])#
  94. bboxes[1] = np.maximum(bboxes[1], bbox_ref[1])
  95. bboxes[2] = np.minimum(bboxes[2], bbox_ref[2])
  96. bboxes[3] = np.minimum(bboxes[3], bbox_ref[3])
  97. bboxes = np.transpose(bboxes)
  98. return bboxes
  99. ############## 按照预测边框的得分 进行排序 前 400个#################
  100. def bboxes_sort(classes, scores, bboxes, top_k=400):
  101. """Sort bounding boxes by decreasing order and keep only the top_k
  102. """
  103. # if priority_inside:
  104. # inside = (bboxes[:, 0] > margin) & (bboxes[:, 1] > margin) & \
  105. # (bboxes[:, 2] < 1-margin) & (bboxes[:, 3] < 1-margin)
  106. # idxes = np.argsort(-scores)
  107. # inside = inside[idxes]
  108. # idxes = np.concatenate([idxes[inside], idxes[~inside]])
  109. idxes = np.argsort(-scores)
  110. classes = classes[idxes][:top_k]#类别索引排序
  111. scores = scores[idxes][:top_k]#得分排序
  112. bboxes = bboxes[idxes][:top_k]#最优预测边框排序
  113. return classes, scores, bboxes#类别 得分 边框
  114. ################ 计算 两个边框之间 的交并比 ########################
  115. ################ 交叠部分的面积, 并集 IOU = 交集/并集
  116. def bboxes_iou(bboxes1, bboxes2):
  117. """Computing iou between bboxes1 and bboxes2.
  118. Note: bboxes1 and bboxes2 can be multi-dimensional, but should broacastable.
  119. """
  120. bboxes1 = np.transpose(bboxes1)
  121. bboxes2 = np.transpose(bboxes2)
  122. # Intersection bbox and volume.
  123. int_ymin = np.maximum(bboxes1[0], bboxes2[0])
  124. int_xmin = np.maximum(bboxes1[1], bboxes2[1])
  125. int_ymax = np.minimum(bboxes1[2], bboxes2[2])
  126. int_xmax = np.minimum(bboxes1[3], bboxes2[3])
  127. int_h = np.maximum(int_ymax - int_ymin, 0.)
  128. int_w = np.maximum(int_xmax - int_xmin, 0.)
  129. int_vol = int_h * int_w# 交叠部分的面积
  130. # 并集的面积 = S1+S2-交集.
  131. vol1 = (bboxes1[2] - bboxes1[0]) * (bboxes1[3] - bboxes1[1])
  132. vol2 = (bboxes2[2] - bboxes2[0]) * (bboxes2[3] - bboxes2[1])
  133. iou = int_vol / (vol1 + vol2 - int_vol)
  134. return iou
  135. ################# 非极大值抑制 排除重复的边框##########################
  136. ########## 输入为已排序的
  137. ########## 1. 选取得分较高的边框 在剩下的边框中 排除掉 与该边框 IOU较大的边框
  138. ########## 2. 再次选取得分较高的边框 按1的方法处理 直到边框被排除到没有
  139. ########## 3. 每次选择出来的边框 即为最优的边框
  140. def bboxes_nms(classes, scores, bboxes, nms_threshold=0.5):
  141. """Apply non-maximum selection to bounding boxes.
  142. """
  143. keep_bboxes = np.ones(scores.shape, dtype=np.bool)# 边框是否保留的标志
  144. for i in range(scores.size-1):
  145. if keep_bboxes[i]:#剩余的边框
  146. # Computer overlap with bboxes which are following.
  147. overlap = bboxes_iou(bboxes[i], bboxes[(i+1):])#计算交并比
  148. # Overlap threshold for keeping + checking part of the same class
  149. keep_overlap = np.logical_or(overlap < nms_threshold, classes[(i+1):] != classes[i])
  150. keep_bboxes[(i+1):] = np.logical_and(keep_bboxes[(i+1):], keep_overlap)
  151. idxes = np.where(keep_bboxes)
  152. return classes[idxes], scores[idxes], bboxes[idxes]

model.py

  1. #-*- coding: utf-8 -*-
  2. # yolo-v2 模型文件
  3. # dark 19 passthrough 层 跨通道合并特征
  4. """
  5. YOLOv2 implemented by Tensorflow, only for predicting
  6. 1. 3*3*3*32 卷积核 3通道输入 32通道输出 步长1 + 最大值池化
  7. 2. 3*3*32*64 卷积核 32通道输入 64通道输出 步长1 + 最大值池化
  8. 3. 3*3 1*1 3*3 卷积 + 最大值池化
  9. 4. 3*3 1*1 3*3 卷积 + 最大值池化
  10. 5. 3*3 1*1 3*3 1*1 3*3 卷积 + 最大值池化
  11. 6. 3*3 1*1 3*3 1*1 3*3 卷积
  12. 7. 3*3 3*3 卷积
  13. 7.5 passtrough 层 尺寸减半 通道数量变为4倍 跨层 通道合并concat
  14. 8. 3*3*(1024+64*4)*1024 卷积核 1280通道输入 1024通道输出 步长1
  15. 9. 3*3*1024* n_last_channels 卷积核 1024通道输入 n_last_channels 通道输出 步长1 检测网络输出
  16. """
  17. import os
  18. import numpy as np
  19. import tensorflow as tf
  20. ######## basic layers #######
  21. # 激活函数 max(0, 0.1*x)
  22. def leaky_relu(x):
  23. return tf.nn.leaky_relu(x, alpha=0.1, name="leaky_relu")
  24. # Leaky ReLU激活函数
  25. #def leak_relu(x, alpha=0.1):
  26. #return tf.maximum(alpha * x, x)
  27. # Conv2d 2d 卷积 padding延拓 + 2d卷积 + 批规范化 + 激活输出
  28. def conv2d(x, filters, size, pad=0, stride=1, batch_normalize=1,
  29. activation=leaky_relu, use_bias=False, name="conv2d"):
  30. # 对输入通道 延拓
  31. if pad > 0:
  32. x = tf.pad(x, [[0, 0], [pad, pad], [pad, pad], [0, 0]])
  33. # 2d 卷积
  34. # conv = tf.nn.conv2d(x_pad, weight, strides=[1, stride, stride, 1], padding="VALID") 需要指定4维卷积和
  35. # tf.layers.conv2d 可以省略指定输入通道数量
  36. out = tf.layers.conv2d(x, filters, size, strides=stride, padding="VALID",
  37. activation=None, use_bias=use_bias, name=name)
  38. # 批规范化
  39. if batch_normalize == 1:
  40. out = tf.layers.batch_normalization(out, axis=-1, momentum=0.9,
  41. training=False, name=name+"_bn")
  42. # 激活输出
  43. if activation:
  44. out = activation(out)
  45. return out
  46. # 最大值池化层 maxpool2d
  47. def maxpool(x, size=2, stride=2, name="maxpool"):
  48. return tf.layers.max_pooling2d(x, size, stride)
  49. # passtrougt 层 reorg layer
  50. # 按行和按列隔行采样的方法,就可以得到4个新的特征图。
  51. def reorg(x, stride):
  52. return tf.extract_image_patches(x, [1, stride, stride, 1],
  53. [1, stride, stride, 1], [1,1,1,1], padding="VALID")
  54. # 网络结构 输出通道数量 默认为 5*(5+80)=425 ,默认80类
  55. def darknet(images, n_last_channels=425):
  56. """Darknet19 for YOLOv2"""
  57. ######### 1. 3*3*3*32 卷积核 3通道输入 32通道输出 步长1 + 最大值池化 ########################
  58. net = conv2d(images, 32, 3, 1, name="conv1")
  59. net = maxpool(net, name="pool1")# 2*2的池化核 步长 2 尺寸减半
  60. ######### 2. 3*3*32*64 卷积核 32通道输入 64通道输出 步长1 + 最大值池化 ######################
  61. net = conv2d(net, 64, 3, 1, name="conv2")
  62. net = maxpool(net, name="pool2")# 2*2的池化核 步长 2 尺寸减半
  63. ######### 3. 3*3 1*1 3*3 卷积 + 最大值池化 ##################################################
  64. net = conv2d(net, 128, 3, 1, name="conv3_1")# 3*3*64*128 卷积核 64通道输入 128通道输出 步长1
  65. net = conv2d(net, 64, 1, name="conv3_2") # 1*1*128*64 卷积核 128通道输入 64通道输出 步长1
  66. net = conv2d(net, 128, 3, 1, name="conv3_3")# 3*3*64*128 卷积核 64通道输入 128通道输出 步长1
  67. net = maxpool(net, name="pool3")# 2*2的池化核 步长 2 尺寸减半
  68. ######### 4. 3*3 1*1 3*3 卷积 + 最大值池化 ###################################################
  69. net = conv2d(net, 256, 3, 1, name="conv4_1")# 3*3*128*256 卷积核 128通道输入 256通道输出 步长1
  70. net = conv2d(net, 128, 1, name="conv4_2") # 1*1*256*128 卷积核 256通道输入 128通道输出 步长1
  71. net = conv2d(net, 256, 3, 1, name="conv4_3")# 3*3*128*256 卷积核 128通道输入 256通道输出 步长1
  72. net = maxpool(net, name="pool4")# 2*2的池化核 步长 2 尺寸减半
  73. ######### 5. 3*3 1*1 3*3 1*1 3*3 卷积 + 最大值池化 ##########################################
  74. net = conv2d(net, 512, 3, 1, name="conv5_1")# 3*3*256*512 卷积核 256通道输入 512通道输出 步长1
  75. net = conv2d(net, 256, 1, name="conv5_2") # 1*1*512*256 卷积核 512通道输入 256通道输出 步长1
  76. net = conv2d(net, 512, 3, 1, name="conv5_3")# 3*3*256*512 卷积核 256通道输入 512通道输出 步长1
  77. net = conv2d(net, 256, 1, name="conv5_4") # 1*1*512*256 卷积核 512通道输入 256通道输出 步长1
  78. net = conv2d(net, 512, 3, 1, name="conv5_5")# 3*3*256*512 卷积核 256通道输入 512通道输出 步长1
  79. shortcut = net ######## 保存大尺寸的卷积特征图###########
  80. net = maxpool(net, name="pool5")# 2*2的池化核 步长 2 尺寸减半
  81. ######### 6. 3*3 1*1 3*3 1*1 3*3 卷积 ##########################################################
  82. net = conv2d(net, 1024, 3, 1, name="conv6_1")# 3*3*512*1024 卷积核 512通道输入 1024通道输出 步长1
  83. net = conv2d(net, 512, 1, name="conv6_2") # 1*1*1024*512 卷积核 1024通道输入 512通道输出 步长1
  84. net = conv2d(net, 1024, 3, 1, name="conv6_3")# 3*3*512*1024 卷积核 512通道输入 1024通道输出 步长1
  85. net = conv2d(net, 512, 1, name="conv6_4") # 1*1*1024*512 卷积核 1024通道输入 512通道输出 步长1
  86. net = conv2d(net, 1024, 3, 1, name="conv6_5")# 3*3*512*1024 卷积核 512通道输入 1024通道输出 步长1
  87. ######### 7. 3*3 3*3 卷积 ######################################################################
  88. net = conv2d(net, 1024, 3, 1, name="conv7_1")# 3*3*1024*1024 卷积核 1024通道输入 1024通道输出 步长
  89. net = conv2d(net, 1024, 3, 1, name="conv7_2")# 3*3*1024*1024 卷积核 1024通道输入 1024通道输出 步长
  90. # shortcut ???????有点问题
  91. shortcut = conv2d(shortcut, 64, 1, name="conv_shortcut")# 26*26*512 特征图 再卷积 1*1*512*64 64个输出??
  92. shortcut = reorg(shortcut, 2)# passtrough 层 尺寸减半 通道数量变为4倍
  93. net = tf.concat([shortcut, net], axis=-1)# 跨层 通道合并
  94. ######### 8. 3*3*(1024+64*4)*1024 卷积核 1280通道输入 1024通道输出 步长1 ##########################
  95. net = conv2d(net, 1024, 3, 1, name="conv8")
  96. ######### 9. 3*3*1024* n_last_channels 卷积核 1024通道输入 n_last_channels 通道输出 步长1##########
  97. ######### 检测网络输出
  98. net = conv2d(net, n_last_channels, 1, batch_normalize=0,
  99. activation=None, use_bias=True, name="conv_dec")
  100. return net
  101. if __name__ == "__main__":
  102. x = tf.random_normal([1, 416, 416, 3])#随机 0~1之间
  103. model = darknet(x)#检测
  104. saver = tf.train.Saver()
  105. with tf.Session() as sess:
  106. saver.restore(sess, "./checkpoint_dir/yolo2_coco.ckpt")#载入网络参数
  107. print(sess.run(model).shape)#打印结果

6.YOLO - v3 ResNet残差网络结构,使得网络深度更深 FPN层结合不同尺度层特征

 

YOLO - v3 在 v2上的改进:

 

    1. 借鉴ResNet残差网络结构,使得网络深度更深,新的网络Darknet-53,共53个卷积层;

    2. 借鉴FPN特征金字塔,在网络后面三个台阶处,使用FPN结构,小尺寸上采样结合大尺寸特征图;

    3. 聚类得到9种先验框,3种尺度的特征图上每个3种先验框,不同尺度,不同数量的格子每个格子预测3种边框。

代码后补

 

 

7. 残差网络  ResNet f(x) + W*x

 ResNet 网络思想:

    1. 结合不同卷积层的特征
    2. 模块为 f(x) + W*xf(x) 为 2个 3x3的卷积 
    3. 简化计算 将2个3x3的卷积层替换为 1x1 + 3x3 + 1x1 卷积 。


    新结构中的中间3x3的卷积层首先在一个降维1x1卷积层下减少了计算,

    然后在另一个1x1的卷积层下做了还原,既保持了精度又减少了计算量。

 

 

 ResNet核心模块

 
  1. ________________________________>

  2. | + f(x) + x

  3. x-----> 1x1 + 3x3 + 1x1 卷积 ----->

 

 ResNet 网络模型

 
  1. 残差网络 f(x) + W*x

  2. 50个卷积层

  3. ResNet50

  4. 2018/04/22

  5. 1 64个输出 7*7卷积核 步长 2 224*224图像输入 大小减半(+BN + RELU + MaxPol)

  6. 2 3个 3*3卷积核 64输出的残差模块 256/4 = 64 且第一个残差块的第一个卷积步长为1

  7. 3 4个 3*3卷积核 128输出的残差模块 512/4 = 128 且第一个残差块的第一个卷积步长为2

  8. 4 6个 3*3卷积核 256输出的残差模块 1024/4 = 256 且第一个残差块的第一个卷积步长为2

  9. 5 3个 3*3卷积核 512输出的残差模块 2048/4 = 512 且第一个残差块的第一个卷积步长为2

  10. 6 均值池化

  11. 7 全连接层 输出 1000 类

  12. 8 softmax分类 预测类别输出

  13. 实际中,考虑计算的成本,对残差块做了计算优化,即将2个3x3的卷积层替换为 1x1 + 3x3 + 1x1 。

  14. 新结构中的中间3x3的卷积层首先在一个降维1x

 

ResNet 网络代码

github代码

  1. #-*- coding: utf-8 -*-
  2. # 残差网络 f(x) + W*x
  3. # 核心思想
  4. """
  5. 结合不同卷积层的特征
  6. f(x) + W*x
  7. f(x) 为 23x3的卷积
  8. 实际中,考虑计算的成本,对残差块做了计算C,即将2个3x3的卷积层替换为 1x1 + 3x3 + 1x1 。
  9. 新结构中的中间3x3的卷积层首先在一个降维1x1卷积层下减少了计算,然后在另一个1x1的卷积层下做了还原,既保持了精度又减少了计算量。
  10. ________________________________>
  11. | + f(x) + x
  12. x-----> 1x1 + 3x3 + 1x1 卷积 ----->
  13. """
  14. # 网络模型
  15. """
  16. 残差网络 f(x) + W*x
  17. 50个卷积层
  18. ResNet50
  19. 2018/04/22
  20. 1 64个输出 7*7卷积核 步长 2 224*224图像输入 大小减半(+BN + RELU + MaxPol)
  21. 2 3个 3*3卷积核 64输出的残差模块 256/4 = 64 且第一个残差块的第一个卷积步长为1
  22. 3 4个 3*3卷积核 128输出的残差模块 512/4 = 128 且第一个残差块的第一个卷积步长为2
  23. 4 6个 3*3卷积核 256输出的残差模块 1024/4 = 256 且第一个残差块的第一个卷积步长为2
  24. 5 3个 3*3卷积核 512输出的残差模块 2048/4 = 512 且第一个残差块的第一个卷积步长为2
  25. 6 均值池化
  26. 7 全连接层 输出 1000 类
  27. 8 softmax分类 预测类别输出
  28. 实际中,考虑计算的成本,对残差块做了计算优化,即将2个3x3的卷积层替换为 1x1 + 3x3 + 1x1 。
  29. 新结构中的中间3x3的卷积层首先在一个降维1x1卷积层下减少了计算,然后在另一个1x1的卷积层下做了还原,既保持了精度又减少了计算量。
  30. 运行
  31. python3 ResNet50.py
  32. # python3.4 对应的1.4版本 tensorflow 安装
  33. sudo pip3 install --upgrade https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.4.0-cp34-cp34m-linux_x86_64.whl
  34. """
  35. import tensorflow as tf
  36. from tensorflow.python.training import moving_averages# 移动平均
  37. #### 使用GPU时指定 gpu设备
  38. #import os
  39. #os.environ["CUDA_VISIBLE_DEVICES"] = "1"
  40. ####### 全连接层 卷积层初始化############################
  41. fc_initializer = tf.contrib.layers.xavier_initializer
  42. conv2d_initializer = tf.contrib.layers.xavier_initializer_conv2d
  43. ######## 创建变量 create weight variable########################
  44. def create_var(name, shape, initializer, trainable=True):
  45. return tf.get_variable(name, shape=shape, dtype=tf.float32,
  46. initializer=initializer, trainable=trainable)
  47. ####### 2d卷积层 conv2d layer##################################
  48. ####### 卷积核3维 加一个输出数量 [filter_w, filter_h, input_chanels, output_chanels]
  49. def conv2d(x, num_outputs, kernel_size, stride=1, scope="conv2d"):
  50. num_inputs = x.get_shape()[-1]#输入通道数
  51. with tf.variable_scope(scope):
  52. kernel = create_var("kernel", [kernel_size, kernel_size,
  53. num_inputs, num_outputs],
  54. conv2d_initializer())
  55. return tf.nn.conv2d(x, kernel, strides=[1, stride, stride, 1],
  56. padding="SAME")
  57. ###### 全连接层 fully connected layer###########################
  58. def fc(x, num_outputs, scope="fc"):
  59. num_inputs = x.get_shape()[-1]# 输入通道数
  60. with tf.variable_scope(scope):
  61. weight = create_var("weight", [num_inputs, num_outputs],
  62. fc_initializer())
  63. bias = create_var("bias", [num_outputs,],
  64. tf.zeros_initializer())
  65. return tf.nn.xw_plus_b(x, weight, bias)
  66. ####### 批规范化 (去均值 除以方差 零均值 1方差处理)batch norm layer#########
  67. def batch_norm(x, decay=0.999, epsilon=1e-03, is_training=True,
  68. scope="scope"):
  69. x_shape = x.get_shape()
  70. num_inputs = x_shape[-1]#输入通道数
  71. reduce_dims = list(range(len(x_shape) - 1))
  72. with tf.variable_scope(scope):
  73. # 版本问题 beta = create_var("beta", [num_inputs,], initializer=tf.zeros_initializer())
  74. beta = create_var("beta", [num_inputs,], initializer=tf.zeros_initializer())
  75. gamma = create_var("gamma", [num_inputs,], initializer=tf.ones_initializer())
  76. # 移动均值for inference
  77. moving_mean = create_var("moving_mean", [num_inputs,], initializer=tf.zeros_initializer(), trainable=False)
  78. # 方差
  79. moving_variance = create_var("moving_variance", [num_inputs], initializer=tf.ones_initializer(), trainable=False)
  80. if is_training:
  81. mean, variance = tf.nn.moments(x, axes=reduce_dims)
  82. update_move_mean = moving_averages.assign_moving_average(moving_mean,
  83. mean, decay=decay)
  84. update_move_variance = moving_averages.assign_moving_average(moving_variance,
  85. variance, decay=decay)
  86. tf.add_to_collection(tf.GraphKeys.UPDATE_OPS, update_move_mean)
  87. tf.add_to_collection(tf.GraphKeys.UPDATE_OPS, update_move_variance)
  88. else:
  89. mean, variance = moving_mean, moving_variance
  90. return tf.nn.batch_normalization(x, mean, variance, beta, gamma, epsilon)
  91. ############# 均值池化层 avg pool layer###################
  92. def avg_pool(x, pool_size, scope):
  93. with tf.variable_scope(scope):
  94. return tf.nn.avg_pool(x, [1, pool_size, pool_size, 1],
  95. strides=[1, pool_size, pool_size, 1], padding="VALID")
  96. ############# 最大值池化层 max pool layer######################
  97. def max_pool(x, pool_size, stride, scope):
  98. with tf.variable_scope(scope):
  99. return tf.nn.max_pool(x, [1, pool_size, pool_size, 1],
  100. [1, stride, stride, 1], padding="SAME")
  101. ################ 残差网络 1000 类#######################
  102. class ResNet50(object):
  103. def __init__(self, inputs, num_classes=1000, is_training=True,
  104. scope="resnet50"):
  105. self.inputs =inputs# 输入数量
  106. self.is_training = is_training
  107. self.num_classes = num_classes# 类别数量
  108. with tf.variable_scope(scope):
  109. # 定义模型结构 construct the model
  110. ###### 1. 64个输出 7*7卷积核 步长 2 224*224图像输入 大小减半#########
  111. net = conv2d(inputs, 64, 7, 2, scope="conv1") # -> [batch, 112, 112, 64]
  112. ####### 先 批规范化 在 relu激活
  113. net = tf.nn.relu(batch_norm(net, is_training=self.is_training, scope="bn1"))
  114. ##### 最大值池化 步长2 大小减半
  115. net = max_pool(net, 3, 2, scope="maxpool1") # -> [batch, 56, 56, 64]
  116. ##### 2. 3个 3*3卷积核 64输出的残差模块 256/4 = 64 且第一个残差块的第一个卷积步长为1
  117. net = self._block(net, 256, 3, init_stride=1, is_training=self.is_training,scope="block2") # -> [batch, 56, 56, 256]
  118. ##### 3. 4个 3*3卷积核 128输出的残差模块 512/4 = 128 且第一个残差块的第一个卷积步长为2
  119. net = self._block(net, 512, 4, is_training=self.is_training, scope="block3")# -> [batch, 28, 28, 512]
  120. ##### 4. 6个 3*3卷积核 256输出的残差模块 1024/4 = 256且第一个残差块的第一个卷积2
  121. net = self._block(net, 1024, 6, is_training=self.is_training, scope="block4")# -> [batch, 14, 14, 1024]
  122. ##### 5. 3个 3*3卷积核 512输出的残差模块 2048/4 = 512 且第一个残差块的第一个卷积步长为2
  123. net = self._block(net, 2048, 3, is_training=self.is_training, scope="block5")# -> [batch, 7, 7, 2048]
  124. ##### 6. 均值池化
  125. net = avg_pool(net, 7, scope="avgpool5") # -> [batch, 1, 1, 2048]
  126. net = tf.squeeze(net, [1, 2], name="SpatialSqueeze") # -> [batch, 2048]
  127. ##### 7. 全连接层
  128. self.logits = fc(net, self.num_classes, "fc6") # -> [batch, num_classes]
  129. ##### 8. softmax分类 预测输出
  130. self.predictions = tf.nn.softmax(self.logits)
  131. # 残差块集合 默认卷积步长为2
  132. def _block(self, x, n_out, n, init_stride=2, is_training=True, scope="block"):
  133. with tf.variable_scope(scope):
  134. h_out = n_out // 4 #上面的调用 该参数都为实际输出通道的 4被所有这里除以4
  135. # 第一个残差模型(会涉及到 不同残差集合块 不同通道的合并) f(x) + x
  136. # 这里第一个残差块的 卷积步长 与后面的残差块的卷积步长可能不一样 所以独立出来
  137. out = self._bottleneck(x, h_out, n_out, stride=init_stride, is_training=is_training, scope="bottlencek1")
  138. for i in range(1, n):#1....(n-1)个残差块
  139. out = self._bottleneck(out, h_out, n_out, is_training=is_training, scope=("bottlencek%s" % (i + 1)))
  140. return out
  141. '''
  142. 实际中,考虑计算的成本,对残差块做了计算优化,即将2个3x3的卷积层替换为 1x1 + 3x3 + 1x1 。
  143. 新结构中的中间3x3的卷积层首先在一个降维1x1卷积层下减少了计算,然后在另一个1x1的卷积层下做了还原,既保持了精度又减少了计算量。
  144. '''
  145. # 残差模块 f(x) + x
  146. def _bottleneck(self, x, h_out, n_out, stride=None, is_training=True, scope="bottleneck"):
  147. """ A residual bottleneck unit"""
  148. n_in = x.get_shape()[-1]#输入通道
  149. if stride is None:
  150. stride = 1 if n_in == n_out else 2# 步长大小
  151. with tf.variable_scope(scope):
  152. # 经过两个3×3卷积(= 1*1 + 3*3 + 1*1)形成 f(x) 后与 x相加
  153. # 第一个卷积(2d卷积 + 批规范化 + relu激活) 1*1的卷积核
  154. h = conv2d(x, h_out, 1, stride=stride, scope="conv_1")
  155. h = batch_norm(h, is_training=is_training, scope="bn_1")
  156. h = tf.nn.relu(h)
  157. # 第二个卷积(2d卷积 + 批规范化 + relu激活) 3*3的卷积核
  158. h = conv2d(h, h_out, 3, stride=1, scope="conv_2")
  159. h = batch_norm(h, is_training=is_training, scope="bn_2")
  160. h = tf.nn.relu(h)
  161. # 第三个卷积 1*1
  162. h = conv2d(h, n_out, 1, stride=1, scope="conv_3")
  163. h = batch_norm(h, is_training=is_training, scope="bn_3")
  164. if n_in != n_out:# 当 x和 f(x)通道不一致时 对x再次进行卷积 输出和f(x)一致的通道数
  165. shortcut = conv2d(x, n_out, 1, stride=stride, scope="conv_4")
  166. shortcut = batch_norm(shortcut, is_training=is_training, scope="bn_4")
  167. else:
  168. shortcut = x
  169. return tf.nn.relu(shortcut + h)# f(x) + w*x
  170. if __name__ == "__main__":
  171. # 32个图像 224*224*3 3个通道
  172. x = tf.random_normal([32, 224, 224, 3])
  173. resnet50 = ResNet50(x)# 32个图像每个图像 输出类别个数个 预测概率
  174. print(resnet50.logits)
  175. © 2018 GitHub, Inc.

8. MobileNet 深度可分解卷积 v1

 

模型简化思想

 
  1. 3 × 3 × 3 ×16 3*3的卷积 3通道输入 16通道输出

  2. ===== 3 × 3 × 1 × 3的深度卷积(3个3*3的卷积核,每一个卷积核对输入通道分别卷积后叠加输出) 输出3通道 1d卷积

  3. ===== + 1 × 1 × 3 ×16的1 ×1点卷积 1*1卷积核 3通道输入 16通道输出

  4. 参数数量 75/432 = 0.17

  5.  
  6. 3*3*输入通道*输出通道 -> BN -> RELU

  7. =======>

  8. 3*3*1*输入通道 -> BN -> RELU -> 1*1*输入通道*输出通道 -> BN -> RELU

 

MobileNet-v1 网络结构

 
  1. """

  2. 1. 普通3d卷积层 3*3*3*round(32 * width_multiplier) 3*3卷积核 3通道输入 输出通道数量 随机确定1~32个

  3. 2. 13个 depthwise_separable_conv2d 层 3*3*1*输入通道 -> BN -> RELU -> 1*1*输入通道*输出通道 -> BN -> RELU

  4. 3. 均值池化层 7*7核 + squeeze 去掉维度为1的维

  5. 4. 全连接层 输出 -> [N, 1000]

  6. 5. softmax分类输出到 0~1之间

  7. """

MobileNet v1 代码

github代码

  1. #-*- coding: utf-8 -*-
  2. # MobileNets模型结构
  3. # 深度可分解卷积
  4. # MobileNets总共28层(1 + 2 × 13 + 1 = 28)
  5. # 核心思想:
  6. """
  7. 将标准卷积分解成一个深度卷积和一个点卷积(1 × 1卷积核)。深度卷积将每个卷积核应用到每一个通道,
  8. 而1 × 1卷积用来组合通道卷积的输出。后文证明,这种分解可以有效减少计算量,降低模型大小。
  9. 3 × 3 × 3 ×16 3*3的卷积 3通道输入 16通道输出
  10. ===== 3 × 3 × 1 × 3的深度卷积(3个3*3的卷积核,每一个卷积核对输入通道分别卷积后叠加输出) 输出3通道 1d卷积
  11. ===== + 1 × 1 × 3 ×16的1 ×1点卷积 1*1卷积核 3通道输入 16通道输出
  12. 参数数量 75/432 = 0.17
  13. 3*3*输入通道*输出通道 -> BN -> RELU
  14. =======>
  15. 3*3*1*输入通道 -> BN -> RELU -> 1*1*输入通道*输出通道 -> BN -> RELU
  16. """
  17. #网络结构:
  18. """
  19. 1. 普通3d卷积层 3*3*3*round(32 * width_multiplier) 3*3卷积核 3通道输入 输出通道数量 随机确定1~32个
  20. 2. 13个 depthwise_separable_conv2d 层 3*3*1*输入通道 -> BN -> RELU -> 1*1*输入通道*输出通道 -> BN -> RELU
  21. 3. 均值池化层 7*7核 + squeeze 去掉维度为1的维
  22. 4. 全连接层 输出 -> [N, 1000]
  23. 5. softmax分类输出到 0~1之间
  24. """
  25. import tensorflow as tf
  26. from tensorflow.python.training import moving_averages
  27. UPDATE_OPS_COLLECTION = "_update_ops_"
  28. #### 使用GPU时指定 gpu设备
  29. # import os
  30. # os.environ["CUDA_VISIBLE_DEVICES"] = "1"
  31. ################################################################
  32. # 创建变量 create variable 默认可优化训练
  33. def create_variable(name, shape, initializer,
  34. dtype=tf.float32, trainable=True):
  35. return tf.get_variable(name, shape=shape, dtype=dtype,
  36. initializer=initializer, trainable=trainable)
  37. ################################################################
  38. # 批规范化 归一化层 BN层 减均值除方差 batchnorm layer
  39. # s1 = W*x + b
  40. # s2 = (s1 - s1均值)/s1方差
  41. # s3 = beta * s2 + gamma
  42. def bacthnorm(inputs, scope, epsilon=1e-05, momentum=0.99, is_training=True):
  43. inputs_shape = inputs.get_shape().as_list()# 输出 形状尺寸
  44. params_shape = inputs_shape[-1:]# 输入参数的长度
  45. axis = list(range(len(inputs_shape) - 1))
  46. with tf.variable_scope(scope):
  47. beta = create_variable("beta", params_shape,
  48. initializer=tf.zeros_initializer())
  49. gamma = create_variable("gamma", params_shape,
  50. initializer=tf.ones_initializer())
  51. # 均值 常量 不需要训练 for inference
  52. moving_mean = create_variable("moving_mean", params_shape,
  53. initializer=tf.zeros_initializer(), trainable=False)
  54. # 方差 常量 不需要训练
  55. moving_variance = create_variable("moving_variance", params_shape,
  56. initializer=tf.ones_initializer(), trainable=False)
  57. if is_training:
  58. mean, variance = tf.nn.moments(inputs, axes=axis)# 计算均值和方差
  59. # 移动平均求 均值和 方差 考虑上一次的量 xt = a * x_t-1 +(1-a)*x_now
  60. update_move_mean = moving_averages.assign_moving_average(moving_mean,
  61. mean, decay=momentum)
  62. update_move_variance = moving_averages.assign_moving_average(moving_variance,
  63. variance, decay=momentum)
  64. tf.add_to_collection(UPDATE_OPS_COLLECTION, update_move_mean)
  65. tf.add_to_collection(UPDATE_OPS_COLLECTION, update_move_variance)
  66. else:
  67. mean, variance = moving_mean, moving_variance
  68. return tf.nn.batch_normalization(inputs, mean, variance, beta, gamma, epsilon)
  69. ################################################################
  70. ##### 实现 3*3*1*输入通道卷积
  71. # 3*3*输入通道*输出通道 -> BN -> RELU
  72. # =======>
  73. # 3*3*1*输入通道 -> BN -> RELU -> 1*1*输入通道*输出通道 -> BN -> RELU
  74. #########################
  75. # depthwise conv2d layer
  76. def depthwise_conv2d(inputs, scope, filter_size=3, channel_multiplier=1, strides=1):
  77. inputs_shape = inputs.get_shape().as_list()# 输入通道 形状尺寸 64*64* 512
  78. in_channels = inputs_shape[-1]#输入通道数量 最后一个参数 512
  79. with tf.variable_scope(scope):
  80. filter = create_variable("filter", shape=[filter_size, filter_size,
  81. in_channels, channel_multiplier],
  82. initializer=tf.truncated_normal_initializer(stddev=0.01))
  83. return tf.nn.depthwise_conv2d(inputs, filter, strides=[1, strides, strides, 1],
  84. padding="SAME", rate=[1, 1])
  85. #################################################################
  86. # 正常的卷积层 conv2d layer 输出通道 核大小
  87. def conv2d(inputs, scope, num_filters, filter_size=1, strides=1):
  88. inputs_shape = inputs.get_shape().as_list()# 输入通道 形状尺寸 64*64* 512
  89. in_channels = inputs_shape[-1]#输入通道数量 最后一个参数 512
  90. with tf.variable_scope(scope):
  91. filter = create_variable("filter", shape=[filter_size, filter_size,
  92. in_channels, num_filters],
  93. initializer=tf.truncated_normal_initializer(stddev=0.01))
  94. return tf.nn.conv2d(inputs, filter, strides=[1, strides, strides, 1],
  95. padding="SAME")
  96. ################################################################
  97. # 均值池化层 avg pool layer
  98. def avg_pool(inputs, pool_size, scope):
  99. with tf.variable_scope(scope):
  100. return tf.nn.avg_pool(inputs, [1, pool_size, pool_size, 1],
  101. strides=[1, pool_size, pool_size, 1], padding="VALID")
  102. ################################################################
  103. # 全连接层 fully connected layer
  104. def fc(inputs, n_out, scope, use_bias=True):
  105. inputs_shape = inputs.get_shape().as_list()# 输入通道 形状尺寸 1*1* 512 输入时已经被展开了
  106. n_in = inputs_shape[-1]#输入通道数量 最后一个参数 512
  107. with tf.variable_scope(scope):
  108. weight = create_variable("weight", shape=[n_in, n_out],
  109. initializer=tf.random_normal_initializer(stddev=0.01))
  110. if use_bias:#带偏置 与输出通道数量 同维度
  111. bias = create_variable("bias", shape=[n_out,],
  112. initializer=tf.zeros_initializer())
  113. return tf.nn.xw_plus_b(inputs, weight, bias)#带偏置 相乘
  114. return tf.matmul(inputs, weight)#不带偏置 相乘
  115. ################################################################
  116. ##### MobileNet模型结构定义 ####################################
  117. class MobileNet(object):
  118. def __init__(self, inputs, num_classes=1000, is_training=True,
  119. width_multiplier=1, scope="MobileNet"):
  120. """
  121. The implement of MobileNet(ref:https://arxiv.org/abs/1704.04861)
  122. :param inputs: 输入数据 4-D Tensor of [batch_size, height, width, channels]
  123. :param num_classes: 类别数量 ImageNet 1000 类物体 number of classes
  124. :param is_training: 训练模型 Boolean, whether or not the model is training
  125. :param width_multiplier: 宽度乘数 0~1 改变网络输入输出通道数量 float, controls the size of model
  126. :param scope: Optional scope for variables
  127. """
  128. self.inputs = inputs#输入数据
  129. self.num_classes = num_classes#类别数量
  130. self.is_training = is_training#训练标志
  131. self.width_multiplier = width_multiplier# 模型 输入输出通道数量 宽度乘数 因子
  132. # 定义模型结构 construct model
  133. with tf.variable_scope(scope):
  134. ######## 1. 普通3d卷积层 随机输出 通道数量 round(32 * width_multiplier) 步长2 卷积+NB+RELU
  135. ############### 3*3*3*round(32 * width_multiplier) 3*3卷积核 3通道输入 输出通道数量 随机确定1~32个
  136. net = conv2d(inputs, "conv_1", round(32 * width_multiplier), filter_size=3,
  137. strides=2) # ->[N, 112, 112, 32]
  138. net = tf.nn.relu(bacthnorm(net, "conv_1/bn", is_training=self.is_training))# NB+RELU
  139. ######## 2. 13个 depthwise_separable_conv2d 层 ####################
  140. ############### 3*3*1*输入通道 -> BN -> RELU -> 1*1*输入通道*输出通道 -> BN -> RELU
  141. ###################### a. MobileNet 核心模块 64输出 卷积步长1 尺寸不变
  142. net = self._depthwise_separable_conv2d(net, 64, self.width_multiplier,
  143. "ds_conv_2") # ->[N, 112, 112, 64]
  144. ###################### b. MobileNet 核心模块 128输出 卷积步长2 尺寸减半
  145. net = self._depthwise_separable_conv2d(net, 128, self.width_multiplier,
  146. "ds_conv_3", downsample=True) # ->[N, 56, 56, 128]
  147. ###################### c. MobileNet 核心模块 128输出 卷积步长1 尺寸不变
  148. net = self._depthwise_separable_conv2d(net, 128, self.width_multiplier,
  149. "ds_conv_4") # ->[N, 56, 56, 128]
  150. ###################### d. MobileNet 核心模块 256 输出 卷积步长2 尺寸减半
  151. net = self._depthwise_separable_conv2d(net, 256, self.width_multiplier,
  152. "ds_conv_5", downsample=True) # ->[N, 28, 28, 256]
  153. ###################### e. MobileNet 核心模块 256输出 卷积步长1 尺寸不变
  154. net = self._depthwise_separable_conv2d(net, 256, self.width_multiplier,
  155. "ds_conv_6") # ->[N, 28, 28, 256]
  156. ###################### f. MobileNet 核心模块 512 输出 卷积步长2 尺寸减半
  157. net = self._depthwise_separable_conv2d(net, 512, self.width_multiplier,
  158. "ds_conv_7", downsample=True) # ->[N, 14, 14, 512]
  159. ###################### g. MobileNet 核心模块 512输出 卷积步长1 尺寸不变
  160. net = self._depthwise_separable_conv2d(net, 512, self.width_multiplier,
  161. "ds_conv_8") # ->[N, 14, 14, 512]
  162. ###################### h. MobileNet 核心模块 512输出 卷积步长1 尺寸不变
  163. net = self._depthwise_separable_conv2d(net, 512, self.width_multiplier,
  164. "ds_conv_9") # ->[N, 14, 14, 512]
  165. ###################### i. MobileNet 核心模块 512输出 卷积步长1 尺寸不变
  166. net = self._depthwise_separable_conv2d(net, 512, self.width_multiplier,
  167. "ds_conv_10") # ->[N, 14, 14, 512]
  168. ###################### j. MobileNet 核心模块 512输出 卷积步长1 尺寸不变
  169. net = self._depthwise_separable_conv2d(net, 512, self.width_multiplier,
  170. "ds_conv_11") # ->[N, 14, 14, 512]
  171. ###################### k. MobileNet 核心模块 512输出 卷积步长1 尺寸不变
  172. net = self._depthwise_separable_conv2d(net, 512, self.width_multiplier,
  173. "ds_conv_12") # ->[N, 14, 14, 512]
  174. ###################### l. MobileNet 核心模块 1024输出 卷积步长2 尺寸减半
  175. net = self._depthwise_separable_conv2d(net, 1024, self.width_multiplier,
  176. "ds_conv_13", downsample=True) # ->[N, 7, 7, 1024]
  177. ###################### m. MobileNet 核心模块 1024输出 卷积步长1 尺寸不变
  178. net = self._depthwise_separable_conv2d(net, 1024, self.width_multiplier,
  179. "ds_conv_14") # ->[N, 7, 7, 1024]
  180. ######### 3. 均值池化层 7*7核 + squeeze 去掉维度为1的维
  181. net = avg_pool(net, 7, "avg_pool_15")# ->[N, 1, 1, 1024]
  182. net = tf.squeeze(net, [1, 2], name="SpatialSqueeze")# 去掉维度为1的维[N, 1, 1, 1024] => [N,1024]
  183. ######### 4. 全连接层 输出 -> [N, 1000]
  184. self.logits = fc(net, self.num_classes, "fc_16")# -> [N, 1000]
  185. ######### 5. softmax分类输出到 0~1之间
  186. self.predictions = tf.nn.softmax(self.logits)
  187. ###############################################################################
  188. ######## MobileNet 核心模块
  189. ######## 3*3*1*输入通道 -> BN -> RELU -> 1*1*输入通道*输出通道 -> BN -> RELU
  190. def _depthwise_separable_conv2d(self, inputs, num_filters, width_multiplier,
  191. scope, downsample=False):
  192. """depthwise separable convolution 2D function"""
  193. num_filters = round(num_filters * width_multiplier)#输出通道数量
  194. strides = 2 if downsample else 1#下采样 确定卷积步长
  195. with tf.variable_scope(scope):
  196. ####### 1. 3*3*1*输入通道 卷积 depthwise conv2d
  197. dw_conv = depthwise_conv2d(inputs, "depthwise_conv", strides=strides)
  198. ####### 2. BN 批规范化 batchnorm
  199. bn = bacthnorm(dw_conv, "dw_bn", is_training=self.is_training)
  200. ####### 3. relu激活输出
  201. relu = tf.nn.relu(bn)
  202. ####### 4. 普通卷积 1*1*输入通道*输出通道 点卷积 1*1卷积核 pointwise conv2d (1x1)
  203. pw_conv = conv2d(relu, "pointwise_conv", num_filters)
  204. ####### 5. BN 批规范化 batchnorm
  205. bn = bacthnorm(pw_conv, "pw_bn", is_training=self.is_training)
  206. ####### 6. relu激活输出
  207. return tf.nn.relu(bn)
  208. if __name__ == "__main__":
  209. # test data
  210. inputs = tf.random_normal(shape=[4, 224, 224, 3])# 4张图片 224*224 大小 3通道
  211. mobileNet = MobileNet(inputs)# 网络模型输出
  212. writer = tf.summary.FileWriter("./logs", graph=tf.get_default_graph())#
  213. init = tf.global_variables_initializer()
  214. with tf.Session() as sess:
  215. sess.run(init)
  216. pred = sess.run(mobileNet.predictions)#预测输出
  217. print(pred.shape)#打印

9. MobileNet 深度可分解卷积 v2 结构 借鉴 ResNet结构

残差模块 结构

 
  1. f(x) + W*x

  2. f(x) 为 2个 3x3的卷积

  3. 实际中,考虑计算的成本,对残差块做了计算C,即将2个3x3的卷积层替换为 1x1 + 3x3 + 1x1 。

  4. 新结构中的中间3x3的卷积层首先在一个降维1x1卷积层下减少了计算,然后在另一个1x1的卷积层下做了还原,既保持了精度又减少了计算量。

  5.  
  6. _____________________________________>

  7. | + f(x) + x

  8. x-----> 1x1 + 3x3标准 + 1x1 卷积 ----->

  9. 压缩”→“卷积提特征”→“扩张”

MobileNet v2 核心模块

 
  1. 在v1 的 Depth-wise convolution之前多了一个1*1的“扩张”层,目的是为了提升通道数,获得更多特征;

  2. 最后不采用Relu,而是Linear,目的是防止Relu破坏特征。

  3. 结合 x (中间3x3DW 步长为1结合x 步长为2时不结合x )

  4. 1. 步长为1结合x shortcut

  5. ___________________________________>

  6. | --> f(x) + x

  7. x-----> 1x1 + 3x3DW + 1x1 卷积 ----->

  8. “扩张”→“卷积提特征”→ “压缩”

  9. ResNet是:压缩”→“卷积提特征”→“扩张”,MobileNetV2则是Inverted residuals,即:“扩张”→“卷积提特征”→ “压缩”

  10.  
  11. 2. 步长为2时不结合x

  12. x-----> 1x1 + 3x3DW(步长为2) + 1x1 卷积 -----> 输出

 

MobileNet v2 结构

1. 2D卷积块  =  2D卷积层 + BN + RELU6  3*3*3*32 步长2 32个通道输出
2. 1个自适应残差深度可拆解模块  中间层扩张倍数为1为32*1  16个通道输出
3. 2个自适应残差深度可拆解模块  中间层扩张倍数为6为16*6  24个通道输出
4. 3个自适应残差深度可拆解模块  中间层扩张倍数为6为24*6  32个通道输出 
5. 4个自适应残差深度可拆解模块  中间层扩张倍数为6为32*6  64个通道输出
6. 3个自适应残差深度可拆解模块  中间层扩张倍数为6为64*6  96个通道输出
7. 3个自适应残差深度可拆解模块  中间层扩张倍数为6为96*6  160个通道输出 
8. 1个自适应残差深度可拆解模块  中间层扩张倍数为6为160*6  320个通道输出
9. 1个 1*1点卷积块 = 1*1 PW点卷积 +  BN + RELU6 1280个通道输出
10. 全局均值 池化 average_pooling2d
11. 1*1 PW点卷积 后 展开成1维
12. softmax 分类得到分类结果

MobileNet v2 代码

ops.py

  1. #-*- coding: utf-8 -*-
  2. # 使用的函数
  3. """
  4. 激活函数 relu6
  5. 批规范化BN 减均值 除以方差
  6. 2D卷积块 = 2D卷积层 + BN + RELU6
  7. 点卷积块 = 1*1 PW点卷积 + BN + RELU6
  8. DW 深度拆解卷积 depthwise_conv2d 3*3*1*输入通道卷积
  9. 自适应残差深度可拆解模块 1x1 + 3x3DW(步长为2) + 1x1 卷积
  10. 使用函数库实现 自适应残差深度可拆解模块
  11. 全剧均值 池化
  12. 展开成1维
  13. 0 填充
  14. """
  15. import tensorflow as tf
  16. weight_decay=1e-4#
  17. ################################################################################
  18. # 激活函数 relu6 = min(max(x, 0), 6) 最小值0 最大值6
  19. # relu = max(x, 0) 最小值0
  20. def relu(x, name='relu6'):
  21. return tf.nn.relu6(x, name)
  22. ################################################################################
  23. # 批规范化BN 减均值 除以方差
  24. def batch_norm(x, momentum=0.9, epsilon=1e-5, train=True, name='bn'):
  25. return tf.layers.batch_normalization(x,
  26. momentum=momentum,
  27. epsilon=epsilon,
  28. scale=True,
  29. training=train,
  30. name=name)
  31. ######################################
  32. # 2D卷积层 输出通道数量 核尺寸 stride尺寸 初始权重方差
  33. def conv2d(input_, output_dim, k_h, k_w, d_h, d_w, stddev=0.02, name='conv2d', bias=False):
  34. with tf.variable_scope(name):
  35. # 正态分布初始化权重 [核高 宽 输入通道 输出通道]
  36. w = tf.get_variable('w', [k_h, k_w, input_.get_shape()[-1], output_dim],
  37. regularizer=tf.contrib.layers.l2_regularizer(weight_decay),
  38. initializer=tf.truncated_normal_initializer(stddev=stddev))
  39. # 2d卷积
  40. conv = tf.nn.conv2d(input_, w, strides=[1, d_h, d_w, 1], padding='SAME')
  41. # 偏置
  42. if bias:
  43. biases = tf.get_variable('bias', [output_dim], initializer=tf.constant_initializer(0.0))
  44. conv = tf.nn.bias_add(conv, biases)
  45. return conv
  46. ################################################################################
  47. # 2D卷积块 = 2D卷积层 + BN + RELU6
  48. def conv2d_block(input, out_dim, k, s, is_train, name):
  49. with tf.name_scope(name), tf.variable_scope(name):
  50. net = conv2d(input, out_dim, k, k, s, s, name='conv2d')#卷积
  51. net = batch_norm(net, train=is_train, name='bn')# 批规范化
  52. net = relu(net)#激活
  53. return net
  54. ###########################
  55. # 1*1 PW点卷积 1*1的卷积核
  56. def conv_1x1(input, output_dim, name, bias=False):
  57. with tf.name_scope(name):
  58. return conv2d(input, output_dim, 1,1,1,1, stddev=0.02, name=name, bias=bias)
  59. ################################################################################
  60. # 点卷积块 = 1*1 PW点卷积 + BN + RELU6
  61. def pwise_block(input, output_dim, is_train, name, bias=False):
  62. with tf.name_scope(name), tf.variable_scope(name):
  63. out=conv_1x1(input, output_dim, bias=bias, name='pwb')
  64. out=batch_norm(out, train=is_train, name='bn')# 批规范化
  65. out=relu(out)# 激活
  66. return out
  67. ################################################################################
  68. #####DW 深度拆解卷积 depthwise_conv2d 3*3*1*输入通道卷积#
  69. def dwise_conv(input, k_h=3, k_w=3, channel_multiplier= 1, strides=[1,1,1,1],
  70. padding='SAME', stddev=0.02, name='dwise_conv', bias=False):
  71. with tf.variable_scope(name):
  72. in_channel=input.get_shape().as_list()[-1]# 输入通道数量
  73. # 正态分布初始化权重 [核高 宽 输入通道 输出通道]
  74. w = tf.get_variable('w', [k_h, k_w, in_channel, channel_multiplier],
  75. regularizer=tf.contrib.layers.l2_regularizer(weight_decay),
  76. initializer=tf.truncated_normal_initializer(stddev=stddev))
  77. # depthwise_conv2d 3*3*1*输入通道卷积 单个卷积核对所有输入通道卷积后合并
  78. conv = tf.nn.depthwise_conv2d(input, w, strides, padding, rate=None,name=None,data_format=None)
  79. # 偏置
  80. if bias:
  81. biases = tf.get_variable('bias', [in_channel*channel_multiplier], initializer=tf.constant_initializer(0.0))
  82. conv = tf.nn.bias_add(conv, biases)
  83. return conv
  84. ################################################################################
  85. # 1. 步长为1结合x shortcut
  86. # ___________________________________>
  87. # | --> f(x) + x 可能需要对 x做卷积调整 使得 通道一直 可以直接合并
  88. # x-----> 1x1 + 3x3DW + 1x1 卷积 ----->
  89. # “扩张”→“卷积提特征”→ “压缩”
  90. # ResNet是:压缩”→“卷积提特征”→“扩张”,MobileNetV2则是Inverted residuals,即:“扩张”→“卷积提特征”→ “压缩”
  91. #
  92. # 2. 步长为2时不结合x
  93. # x-----> 1x1 + 3x3DW(步长为2) + 1x1 卷积 -----> 输出
  94. # 自适应残差深度可拆解模块
  95. def res_block(input, expansion_ratio, output_dim, stride, is_train, name, bias=False, shortcut=True):
  96. with tf.name_scope(name), tf.variable_scope(name):
  97. ######## 1. pw 1*1 点卷积 #########################
  98. bottleneck_dim=round(expansion_ratio*input.get_shape().as_list()[-1])# 中间输出 通道数量随机
  99. net = conv_1x1(input, bottleneck_dim, name='pw', bias=bias)#点卷积
  100. net = batch_norm(net, train=is_train, name='pw_bn')# 批规范化
  101. net = relu(net)# 激活
  102. ######## 2. dw 深度拆解卷积 depthwise_conv2d 3*3*1*输入通道卷积 ##############
  103. net = dwise_conv(net, strides=[1, stride, stride, 1], name='dw', bias=bias)
  104. net = batch_norm(net, train=is_train, name='dw_bn')# 批规范化
  105. net = relu(net)# 激活
  106. ######## 3. 1*1 点卷积 pw & linear 无非线性激活relu ############
  107. net = conv_1x1(net, output_dim, name='pw_linear', bias=bias)
  108. net = batch_norm(net, train=is_train, name='pw_linear_bn')
  109. # element wise add, only for stride==1
  110. if shortcut and stride == 1:# 需要叠加 x 即 需要残差结构
  111. in_dim = int(input.get_shape().as_list()[-1]) # 输入通道 数量
  112. if in_dim != output_dim: # f(x) 和 x通道不一致 f(x) + w*x
  113. ins = conv_1x1(input, output_dim, name='ex_dim')
  114. net = ins + net # f(x) + w*x
  115. else: # f(x) 和 x通道一致
  116. net = input + net# f(x) + x
  117. # 不需要残差结构 直接输出 f(x)
  118. return net#
  119. ################################################################################
  120. # 使用函数库实现 自适应残差深度可拆解模块
  121. def separable_conv(input, k_size, output_dim, stride, pad='SAME', channel_multiplier=1, name='sep_conv', bias=False):
  122. with tf.name_scope(name), tf.variable_scope(name):
  123. in_channel = input.get_shape().as_list()[-1]# 输入通道数量
  124. # dw 深度拆解卷积核参数
  125. dwise_filter = tf.get_variable('dw', [k_size, k_size, in_channel, channel_multiplier],
  126. regularizer=tf.contrib.layers.l2_regularizer(weight_decay),
  127. initializer=tf.truncated_normal_initializer(stddev=0.02))
  128. # pw 1*1 点卷积核参数
  129. pwise_filter = tf.get_variable('pw', [1, 1, in_channel*channel_multiplier, output_dim],
  130. regularizer=tf.contrib.layers.l2_regularizer(weight_decay),
  131. initializer=tf.truncated_normal_initializer(stddev=0.02))
  132. strides = [1,stride, stride,1]
  133. # 使用函数库实现 自适应残差深度可拆解模块
  134. conv=tf.nn.separable_conv2d(input,dwise_filter,pwise_filter,strides,padding=pad, name=name)
  135. # 偏置
  136. if bias:
  137. biases = tf.get_variable('bias', [output_dim],initializer=tf.constant_initializer(0.0))
  138. conv = tf.nn.bias_add(conv, biases)
  139. return conv
  140. ################################################################################
  141. # 全剧均值 池化
  142. def global_avg(x):
  143. with tf.name_scope('global_avg'):
  144. net=tf.layers.average_pooling2d(x, x.get_shape()[1:-1], 1)
  145. return net
  146. ################################################################################
  147. # 展开成1维
  148. def flatten(x):
  149. #flattened=tf.reshape(input,[x.get_shape().as_list()[0], -1]) # or, tf.layers.flatten(x)
  150. return tf.contrib.layers.flatten(x)
  151. ################################################################################
  152. # 0 填充
  153. def pad2d(inputs, pad=(0, 0), mode='CONSTANT'):
  154. paddings = [[0, 0], [pad[0], pad[0]], [pad[1], pad[1]], [0, 0]]
  155. net = tf.pad(inputs, paddings, mode=mode)
  156. return net
  157. © 2018 GitHub, Inc.

MobileNet_v2_tf.py

  1. #-*- coding: utf-8 -*-
  2. # MobileNet v2 模型结构
  3. # 深度可分解卷积
  4. """
  5. 1. 2D卷积块 = 2D卷积层 + BN + RELU6 3*3*3*32 步长2 32个通道输出
  6. 2. 1个自适应残差深度可拆解模块 中间层扩张倍数为1为32*1 16个通道输出
  7. 3. 2个自适应残差深度可拆解模块 中间层扩张倍数为6为16*6 24个通道输出
  8. 4. 3个自适应残差深度可拆解模块 中间层扩张倍数为6为24*6 32个通道输出
  9. 5. 4个自适应残差深度可拆解模块 中间层扩张倍数为6为32*6 64个通道输出
  10. 6. 3个自适应残差深度可拆解模块 中间层扩张倍数为6为64*6 96个通道输出
  11. 7. 3个自适应残差深度可拆解模块 中间层扩张倍数为6为96*6 160个通道输出
  12. 8. 1个自适应残差深度可拆解模块 中间层扩张倍数为6为160*6 320个通道输出
  13. 9. 1个 1*1点卷积块 = 1*1 PW点卷积 + BN + RELU6 1280个通道输出
  14. 10. 全局均值 池化 average_pooling2d
  15. 11. 1*1 PW点卷积 后 展开成1维
  16. 12. softmax 分类得到分类结果
  17. """
  18. import tensorflow as tf
  19. from ops import *
  20. ## 模型 结构
  21. def mobilenetv2(inputs, num_classes, is_train=True, reuse=False):
  22. exp = 6 # 扩张倍数 expansion ratio
  23. with tf.variable_scope('mobilenetv2'):
  24. ####### 1. 2D卷积块 = 2D卷积层 + BN + RELU6 3*3*3*32 步长2 32个通道输出 ############
  25. net = conv2d_block(inputs, 32, 3, 2, is_train, name='conv1_1') # 步长2 size/2 尺寸减半
  26. ####### 2. 1个自适应残差深度可拆解模块 中间层扩张倍数为1为32*1 16个通道输出 ############
  27. net = res_block(net, 1, 16, 1, is_train, name='res2_1')
  28. ####### 3. 2个自适应残差深度可拆解模块 中间层扩张倍数为6为16*6 24个通道输出 ############
  29. net = res_block(net, exp, 24, 2, is_train, name='res3_1') # 步长2 size/4 尺寸减半
  30. net = res_block(net, exp, 24, 1, is_train, name='res3_2')
  31. ####### 4. 3个自适应残差深度可拆解模块 中间层扩张倍数为6为24*6 32个通道输出 ############
  32. net = res_block(net, exp, 32, 2, is_train, name='res4_1') # 步长2 size/8 尺寸减半
  33. net = res_block(net, exp, 32, 1, is_train, name='res4_2')
  34. net = res_block(net, exp, 32, 1, is_train, name='res4_3')
  35. ####### 5. 4个自适应残差深度可拆解模块 中间层扩张倍数为6为32*6 64个通道输出 ############
  36. net = res_block(net, exp, 64, 1, is_train, name='res5_1')
  37. net = res_block(net, exp, 64, 1, is_train, name='res5_2')
  38. net = res_block(net, exp, 64, 1, is_train, name='res5_3')
  39. net = res_block(net, exp, 64, 1, is_train, name='res5_4')
  40. ####### 6. 3个自适应残差深度可拆解模块 中间层扩张倍数为6为64*6 96个通道输出 ############
  41. net = res_block(net, exp, 96, 2, is_train, name='res6_1') # 步长2 size/16 尺寸减半
  42. net = res_block(net, exp, 96, 1, is_train, name='res6_2')
  43. net = res_block(net, exp, 96, 1, is_train, name='res6_3')
  44. ####### 7. 3个自适应残差深度可拆解模块 中间层扩张倍数为6为96*6 160个通道输出 ###########
  45. net = res_block(net, exp, 160, 2, is_train, name='res7_1') # 步长2 size/32 尺寸减半
  46. net = res_block(net, exp, 160, 1, is_train, name='res7_2')
  47. net = res_block(net, exp, 160, 1, is_train, name='res7_3')
  48. ####### 8. 1个自适应残差深度可拆解模块 中间层扩张倍数为6为160*6 320个通道输出 ##########
  49. net = res_block(net, exp, 320, 1, is_train, name='res8_1', shortcut=False)# 不进行残差合并 f(x)
  50. ####### 9. 1个 1*1点卷积块 = 1*1 PW点卷积 + BN + RELU6 1280个通道输出 ################
  51. net = pwise_block(net, 1280, is_train, name='conv9_1')
  52. ####### 10. 全局均值 池化 average_pooling2d #########################################
  53. net = global_avg(net)
  54. ####### 11. 1*1 PW点卷积 后 展开成1维 ###############################################
  55. logits = flatten(conv_1x1(net, num_classes, name='logits'))
  56. ####### 12. softmax 分类得到分类结果 ###############################################
  57. pred = tf.nn.softmax(logits, name='prob')
  58. return logits, pred
  59. © 2018 GitHub, Inc.

10.轻量级网络--ShuffleNet 分组点卷积+通道重排+逐通道卷积

 

10.1 ResNet 残差网络  结合不同层特征

  1. ________________________________>

  2. | ADD --> f(x) + x

  3. x-----> 1x1 + 3x3 + 1x1 卷积 ----->

10.2MobileNet 普通点卷积 + 逐通道卷积 + 普通点卷积

1. 步长为1结合x shortcut
___________________________________>
|                                    ADD -->  f(x) + x
x-----> 1x1 + 3x3DW + 1x1 卷积 ----->  
     “扩张”→“卷积提特征”→ “压缩”
ResNet是:压缩”→“卷积提特征”→“扩张”,MobileNetV2则是Inverted residuals,即:“扩张”→“卷积提特征”→ “压缩”
 
2. 步长为2时不结合x (下采样版本)
x-----> 1x1 + 3x3DW(步长为2) + 1x1 卷积 ----->   输出

 

10.3 ShuffleNet 普通点卷积 变为分组点卷积+通道重排   逐通道卷积

 

普通点卷积时间还是较长

版本1:

 
  1. ___________________________________________________________>

  2. | ADD --> f(x) + x

  3. x-----> 1x1分组点卷积 + 通道重排 + 3x3DW + 1x1分组点卷积 ----->

   

版本2:(特征图降采样)

 
  1. _____________________3*3AvgPool____________________________>

  2. |                                                           concat --> f(x) 链接 x

  3. x-----> 1x1分组点卷积 + 通道重排 + 3x3DW步长2 + 1x1分组点卷积 ----->  

layers.py

  1. #-*- coding:utf-8 -*-
  2. # 各种网络层实现
  3. #
  4. """
  5. 1. 卷积操作
  6. 2. 卷积层 卷积 + BN + RELU + droupout + maxpooling
  7. 3. 分组卷积层 每组输入通道数量平分 输出通道数量平分 卷积后 各通道 concat通道扩展合并 + BN + relu激活
  8. 4. 通道重排 channel_shuffle 分组再分组 取每个组中的一部分重新排序
  9. 5. 逐通道卷积操作 逐通道卷积 每个卷积核 只和输入数据的一个通道卷积
  10. 6. 逐通道卷积层 逐通道卷积 + BN 批规范化 + 激活
  11. 7. ShuffleNet 核心模块 1x1分组点卷积 + 通道重排 + 3x3DW + 1x1分组点卷积
  12. 8. 全连接层之前 最后的卷积之后 摊平操作 (N,H,W,C)----> (N,D) D = H*W*C
  13. 9. 全连接 操作 (N,D)*(D,output_dim) + Baise --> (N,output_dim)
  14. 10. 全连接层 全链接 + BN + 激活 + 随机失活dropout
  15. 11. 最大值池化
  16. 12. 均值池化
  17. 13. 权重参数初始化 可带有 L2 正则项
  18. 14. 参数记录 均值 方差 最大值 最小值 直方图
  19. """
  20. import tensorflow as tf
  21. import numpy as np
  22. ################################################################################################
  23. # 1. 卷积操作 Convolution layer Methods########################################################
  24. def __conv2d_p(name, x, w=None, num_filters=16, kernel_size=(3, 3), padding='SAME', stride=(1, 1),
  25. initializer=tf.contrib.layers.xavier_initializer(), l2_strength=0.0, bias=0.0):
  26. """
  27. Convolution 2D Wrapper
  28. :param name: (string) The name scope provided by the upper tf.name_scope('name') as scope.
  29. :param x: (tf.tensor) The input to the layer (N, H, W, C). 批大小 图像尺寸 通道数量
  30. :param w: (tf.tensor) pretrained weights (if None, it means no pretrained weights)
  31. :param num_filters: (integer) No. of filters (This is the output depth) 输出通道大小 卷积核数量
  32. :param kernel_size: (integer tuple) The size of the convolving kernel. 卷积核大小
  33. :param padding: (string) The amount of padding required. 填充
  34. :param stride: (integer tuple) The stride required. 卷积步长
  35. :param initializer: (tf.contrib initializer) normal or Xavier normal are recommended. 初始化器
  36. :param l2_strength:(weight decay) (float) L2 regularization parameter. L2正则化系数
  37. :param bias: (float) Amount of bias. (if not float, it means pretrained bias)#偏置
  38. :return out: The output of the layer. (N, H', W', num_filters)
  39. """
  40. with tf.variable_scope(name):
  41. stride = [1, stride[0], stride[1], 1]# 卷积步长
  42. kernel_shape = [kernel_size[0], kernel_size[1], x.shape[-1], num_filters]# 卷积核尺寸
  43. with tf.name_scope('layer_weights'):# 初始化权重
  44. if w == None:
  45. w = __variable_with_weight_decay(kernel_shape, initializer, l2_strength)# 初始化
  46. __variable_summaries(w)# 记录参数
  47. with tf.name_scope('layer_biases'):# 初始化偏置
  48. if isinstance(bias, float):
  49. bias = tf.get_variable('biases', [num_filters], initializer=tf.constant_initializer(bias))
  50. __variable_summaries(bias)# 记录参数
  51. with tf.name_scope('layer_conv2d'):
  52. conv = tf.nn.conv2d(x, w, stride, padding)# 卷积
  53. out = tf.nn.bias_add(conv, bias)# 添加偏置
  54. return out
  55. ###########################################################################################
  56. # 2. 卷积层 卷积 + BN + RELU + droupout + maxpooling########################################
  57. def conv2d(name, x, w=None, num_filters=16, kernel_size=(3, 3), padding='SAME', stride=(1, 1),
  58. initializer=tf.contrib.layers.xavier_initializer(), l2_strength=0.0, bias=0.0,
  59. activation=None, batchnorm_enabled=False, max_pool_enabled=False, dropout_keep_prob=-1,
  60. is_training=True):
  61. """
  62. This block is responsible for a convolution 2D layer followed by optional (non-linearity, dropout, max-pooling).
  63. Note that: "is_training" should be passed by a correct value based on being in either training or testing.
  64. :param name: (string) The name scope provided by the upper tf.name_scope('name') as scope.
  65. :param x: (tf.tensor) The input to the layer (N, H, W, C).
  66. :param num_filters: (integer) No. of filters (This is the output depth)
  67. :param kernel_size: (integer tuple) The size of the convolving kernel.
  68. :param padding: (string) The amount of padding required.
  69. :param stride: (integer tuple) The stride required.
  70. :param initializer: (tf.contrib initializer) The initialization scheme, He et al. normal or Xavier normal are recommended.
  71. :param l2_strength:(weight decay) (float) L2 regularization parameter.
  72. :param bias: (float) Amount of bias.
  73. :param activation: (tf.graph operator) The activation function applied after the convolution operation. If None, linear is applied.
  74. :param batchnorm_enabled: (boolean) for enabling batch normalization.
  75. :param max_pool_enabled: (boolean) for enabling max-pooling 2x2 to decrease width and height by a factor of 2.
  76. :param dropout_keep_prob: (float) for the probability of keeping neurons. If equals -1, it means no dropout
  77. :param is_training: (boolean) to diff. between training and testing (important for batch normalization and dropout)
  78. :return: The output tensor of the layer (N, H', W', C').
  79. """
  80. with tf.variable_scope(name) as scope:
  81. # 2d卷积
  82. conv_o_b = __conv2d_p('conv', x=x, w=w, num_filters=num_filters, kernel_size=kernel_size, stride=stride,
  83. padding=padding,
  84. initializer=initializer, l2_strength=l2_strength, bias=bias)
  85. # BN + 激活
  86. if batchnorm_enabled:
  87. conv_o_bn = tf.layers.batch_normalization(conv_o_b, training=is_training, epsilon=1e-5)
  88. if not activation:
  89. conv_a = conv_o_bn
  90. else:
  91. conv_a = activation(conv_o_bn)
  92. else:
  93. if not activation:
  94. conv_a = conv_o_b
  95. else:
  96. conv_a = activation(conv_o_b)
  97. # droupout 随机失活
  98. def dropout_with_keep():
  99. return tf.nn.dropout(conv_a, dropout_keep_prob)
  100. # 全部 激活
  101. def dropout_no_keep():
  102. return tf.nn.dropout(conv_a, 1.0)
  103. if dropout_keep_prob != -1:
  104. conv_o_dr = tf.cond(is_training, dropout_with_keep, dropout_no_keep)
  105. else:
  106. conv_o_dr = conv_a
  107. conv_o = conv_o_dr
  108. # 最大值池化
  109. if max_pool_enabled:
  110. conv_o = max_pool_2d(conv_o_dr)
  111. return conv_o
  112. #######################################################################################
  113. # 3. 分组卷积层 ###########################################################################
  114. ## 分组卷积 每组输入通道数量平分 输出通道数量平分 卷积后 各通道 concat通道扩展合并 + BN + relu激活
  115. def grouped_conv2d(name, x, w=None, num_filters=16, kernel_size=(3, 3), padding='SAME', stride=(1, 1),
  116. initializer=tf.contrib.layers.xavier_initializer(), num_groups=1, l2_strength=0.0, bias=0.0,
  117. activation=None, batchnorm_enabled=False, dropout_keep_prob=-1,
  118. is_training=True):
  119. with tf.variable_scope(name) as scope:
  120. sz = x.get_shape()[3].value // num_groups# 每组 通道数量 = 通道总数量/ 分组数
  121. # 分组卷积 每组输入通道数量平分 输出通道数量平分
  122. conv_side_layers = [
  123. conv2d(name + "_" + str(i), x[:, :, :, i * sz:i * sz + sz], w, num_filters // num_groups, kernel_size,
  124. padding,
  125. stride,
  126. initializer,
  127. l2_strength, bias, activation=None,
  128. batchnorm_enabled=False, max_pool_enabled=False, dropout_keep_prob=dropout_keep_prob,
  129. is_training=is_training) for i in range(num_groups)]
  130. conv_g = tf.concat(conv_side_layers, axis=-1)# 组 间 通道扩展合并
  131. # BN 批规范化 + 激活
  132. if batchnorm_enabled:
  133. conv_o_bn = tf.layers.batch_normalization(conv_g, training=is_training, epsilon=1e-5)
  134. if not activation:
  135. conv_a = conv_o_bn
  136. else:
  137. conv_a = activation(conv_o_bn)
  138. else:
  139. if not activation:
  140. conv_a = conv_g
  141. else:
  142. conv_a = activation(conv_g)
  143. return conv_a
  144. ##########################################################################
  145. # 4. 通道重排 channel_shuffle ########################################
  146. ## |||||| |||||| |||||| 分组
  147. ## || || || || || || || || || 分组再分组
  148. ## 取每个组中的一部分重新排序
  149. def channel_shuffle(name, x, num_groups):
  150. with tf.variable_scope(name) as scope:
  151. n, h, w, c = x.shape.as_list()# 批数量 特征图尺寸 通道总数量 1*10
  152. x_reshaped = tf.reshape(x, [-1, h, w, num_groups, c // num_groups])# 分组 再 分组 2*5
  153. x_transposed = tf.transpose(x_reshaped, [0, 1, 2, 4, 3])
  154. output = tf.reshape(x_transposed, [-1, h, w, c])
  155. return output
  156. """
  157. Shuffle的基本思路如下,假设输入2个group,输出5个group
  158. | group 1 | group 2 |
  159. | 1,2,3,4,5 |6,7,8,9,10 |
  160. 转化为矩阵为2*5的矩阵
  161. 1 2 3 4 5
  162. 6 7 8 9 10
  163. 转置矩阵,5*2矩阵
  164. 1 6
  165. 2 7
  166. 3 8
  167. 4 9
  168. 5 10
  169. 摊平矩阵
  170. | group 1 | group 2 | group 3 | group 4 | group 5 |
  171. | 1,6 |2,7 |3,8 |4,9 |5,10 |
  172. """
  173. ########################################################################
  174. # 5. 逐通道卷积操作 depthwise_conv#######################################
  175. # 逐通道卷积 每个卷积核 只和输入数据的一个通道卷积
  176. def __depthwise_conv2d_p(name, x, w=None, kernel_size=(3, 3), padding='SAME', stride=(1, 1),
  177. initializer=tf.contrib.layers.xavier_initializer(), l2_strength=0.0, bias=0.0):
  178. with tf.variable_scope(name):
  179. stride = [1, stride[0], stride[1], 1]# 卷积步长
  180. kernel_shape = [kernel_size[0], kernel_size[1], x.shape[-1], 1]
  181. # 初始化权重
  182. with tf.name_scope('layer_weights'):
  183. if w is None:
  184. w = __variable_with_weight_decay(kernel_shape, initializer, l2_strength)
  185. __variable_summaries(w)
  186. # 初始化偏置
  187. with tf.name_scope('layer_biases'):
  188. if isinstance(bias, float):
  189. bias = tf.get_variable('biases', [x.shape[-1]], initializer=tf.constant_initializer(bias))
  190. __variable_summaries(bias)
  191. # 逐通道卷积 + 偏置
  192. with tf.name_scope('layer_conv2d'):
  193. conv = tf.nn.depthwise_conv2d(x, w, stride, padding)# 逐通道卷积
  194. out = tf.nn.bias_add(conv, bias)# 偏置
  195. return out
  196. ###############################################################################
  197. # 6. 逐通道卷积层 ########################################################
  198. # 逐通道卷积 + BN 批规范化 + 激活
  199. def depthwise_conv2d(name, x, w=None, kernel_size=(3, 3), padding='SAME', stride=(1, 1),
  200. initializer=tf.contrib.layers.xavier_initializer(), l2_strength=0.0, bias=0.0, activation=None,
  201. batchnorm_enabled=False, is_training=True):
  202. with tf.variable_scope(name) as scope:
  203. # 逐通道卷积操作 DW CONV
  204. conv_o_b = __depthwise_conv2d_p(name='conv', x=x, w=w, kernel_size=kernel_size, padding=padding,
  205. stride=stride, initializer=initializer, l2_strength=l2_strength, bias=bias)
  206. # BN 批规范化 + 激活
  207. if batchnorm_enabled:
  208. conv_o_bn = tf.layers.batch_normalization(conv_o_b, training=is_training, epsilon=1e-5)
  209. if not activation:
  210. conv_a = conv_o_bn
  211. else:
  212. conv_a = activation(conv_o_bn)
  213. else:
  214. if not activation:
  215. conv_a = conv_o_b
  216. else:
  217. conv_a = activation(conv_o_b)
  218. return conv_a
  219. ############################################################################################################
  220. # 7. ShuffleNet unit methods 核心模块
  221. #
  222. '''
  223. ShuffleNet 普通点卷积 变为 分组点卷积+通道重排   逐通道卷积
  224. 普通点卷积时间还是较长
  225. 版本1:
  226. ___________________________________________________________>
  227. | ADD --> f(x) + x
  228. x-----> 1x1分组点卷积 + 通道重排 + 3x3DW + 1x1分组点卷积 ----->
  229.    
  230. 版本2:(特征图降采样)
  231. _____________________3*3AvgPool_________________________________>
  232. | concat --> f(x) 链接 x
  233. x-----> 1x1分组点卷积 + 通道重排 + 3x3DW步长2 + 1x1分组点卷积 ----->  
  234. '''    
  235. ############################################################################################################
  236. def shufflenet_unit(name, x, w=None, num_groups=1, group_conv_bottleneck=True, num_filters=16, stride=(1, 1),
  237. l2_strength=0.0, bias=0.0, batchnorm_enabled=True, is_training=True, fusion='add'):
  238. # Paper parameters. If you want to change them feel free to pass them as method parameters.
  239. activation = tf.nn.relu# relu 激活 max(0,x)
  240. with tf.variable_scope(name) as scope:
  241. residual = x# 残差模块的 x
  242. bottleneck_filters = (num_filters // 4) if fusion == 'add' else (num_filters - residual.get_shape()[
  243. 3].value) // 4
  244. # 分组卷积 ##############
  245. if group_conv_bottleneck:
  246. # 1*1 分组点卷积
  247. bottleneck = grouped_conv2d('Gbottleneck', x=x, w=None, num_filters=bottleneck_filters, kernel_size=(1, 1),
  248. padding='VALID',
  249. num_groups=num_groups, l2_strength=l2_strength, bias=bias,
  250. activation=activation,
  251. batchnorm_enabled=batchnorm_enabled, is_training=is_training)
  252. # 通道重排
  253. shuffled = channel_shuffle('channel_shuffle', bottleneck, num_groups)
  254. else:
  255. # 普通 点卷积
  256. bottleneck = conv2d('bottleneck', x=x, w=None, num_filters=bottleneck_filters, kernel_size=(1, 1),
  257. padding='VALID', l2_strength=l2_strength, bias=bias, activation=activation,
  258. batchnorm_enabled=batchnorm_enabled, is_training=is_training)
  259. shuffled = bottleneck
  260. # 填充
  261. padded = tf.pad(shuffled, [[0, 0], [1, 1], [1, 1], [0, 0]], "CONSTANT")
  262. # 逐通道卷积层
  263. depthwise = depthwise_conv2d('depthwise', x=padded, w=None, stride=stride, l2_strength=l2_strength,
  264. padding='VALID', bias=bias,
  265. activation=None, batchnorm_enabled=batchnorm_enabled, is_training=is_training)
  266. # 逐通道卷积层 步长为2 下采样模式
  267. if stride == (2, 2):
  268. # 残差通道也需要降采样 使用 3*3均值池化核 2*2步长下采样
  269. residual_pooled = avg_pool_2d(residual, size=(3, 3), stride=stride, padding='SAME')
  270. else:
  271. # 非下采样模式 特征图尺寸不变
  272. residual_pooled = residual
  273. # 再次通过 1*1分组点卷积 + 和残差通路 通道扩展合并 + relu激活
  274. if fusion == 'concat':
  275. group_conv1x1 = grouped_conv2d('Gconv1x1', x=depthwise, w=None,
  276. num_filters=num_filters - residual.get_shape()[3].value,
  277. kernel_size=(1, 1),
  278. padding='VALID',
  279. num_groups=num_groups, l2_strength=l2_strength, bias=bias,
  280. activation=None,
  281. batchnorm_enabled=batchnorm_enabled, is_training=is_training)
  282. return activation(tf.concat([residual_pooled, group_conv1x1], axis=-1))
  283. # 再次通过 1*1分组点卷积 + 和残差通路 通道叠加合并 + relu激活
  284. elif fusion == 'add':
  285. group_conv1x1 = grouped_conv2d('Gconv1x1', x=depthwise, w=None,
  286. num_filters=num_filters,
  287. kernel_size=(1, 1),
  288. padding='VALID',
  289. num_groups=num_groups, l2_strength=l2_strength, bias=bias,
  290. activation=None,
  291. batchnorm_enabled=batchnorm_enabled, is_training=is_training)
  292. # 通道叠加合并是 需要保证 x和F(x)的通道数量一致
  293. residual_match = residual_pooled
  294. # This is used if the number of filters of the residual block is different from that
  295. # of the group convolution.
  296. if num_filters != residual_pooled.get_shape()[3].value:
  297. # 对X 再次卷积 使得其通道数量和 F(x)的通道数量一致
  298. residual_match = conv2d('residual_match', x=residual_pooled, w=None, num_filters=num_filters,
  299. kernel_size=(1, 1),
  300. padding='VALID', l2_strength=l2_strength, bias=bias, activation=None,
  301. batchnorm_enabled=batchnorm_enabled, is_training=is_training)
  302. return activation(group_conv1x1 + residual_match)
  303. else:
  304. raise ValueError("Specify whether the fusion is \'concat\' or \'add\'")
  305. ############################################################################################################
  306. # Fully Connected layer Methods 全连接层 ########################
  307. ################################################################
  308. #########################################################
  309. # 8. 全连接层之前 最后的卷积之后 摊平 (N,H,W,C)----> (N,D) D = H*W*C
  310. ###### 卷积后得到 (N,H,W,C) ----> (N,D) ----> 全链接 (N,D)*(D,output_dim) ----> (N,output_dim)
  311. def flatten(x):
  312. """
  313. Flatten a (N,H,W,C) input into (N,D) output. Used for fully connected layers after conolution layers
  314. :param x: (tf.tensor) representing input
  315. :return: flattened output
  316. """
  317. all_dims_exc_first = np.prod([v.value for v in x.get_shape()[1:]])# D = H*W*C
  318. o = tf.reshape(x, [-1, all_dims_exc_first])
  319. return o
  320. ###################################################################
  321. # 9. 全连接 操作 (N,D)*(D,output_dim) + Baise --> (N,output_dim) ##
  322. def __dense_p(name, x, w=None, output_dim=128, initializer=tf.contrib.layers.xavier_initializer(), l2_strength=0.0,
  323. bias=0.0):
  324. """
  325. Fully connected layer
  326. :param name: (string) The name scope provided by the upper tf.name_scope('name') as scope.
  327. :param x: (tf.tensor) The input to the layer (N, D).
  328. :param output_dim: (integer) It specifies H, the output second dimension of the fully connected layer [ie:(N, H)]
  329. :param initializer: (tf.contrib initializer) The initialization scheme, He et al. normal or Xavier normal are recommended.
  330. :param l2_strength:(weight decay) (float) L2 regularization parameter.
  331. :param bias: (float) Amount of bias. (if not float, it means pretrained bias)
  332. :return out: The output of the layer. (N, H)
  333. """
  334. n_in = x.get_shape()[-1].value# 最后一个参数为 输入通道数量 (N,D) 也就是D
  335. with tf.variable_scope(name):
  336. if w == None:
  337. w = __variable_with_weight_decay([n_in, output_dim], initializer, l2_strength)
  338. __variable_summaries(w)
  339. if isinstance(bias, float):
  340. bias = tf.get_variable("layer_biases", [output_dim], tf.float32, tf.constant_initializer(bias))
  341. __variable_summaries(bias)
  342. output = tf.nn.bias_add(tf.matmul(x, w), bias)
  343. return output
  344. ##############################################################
  345. # 10. 全连接层 全链接 + BN + 激活 + 随机失活dropout
  346. def dense(name, x, w=None, output_dim=128, initializer=tf.contrib.layers.xavier_initializer(), l2_strength=0.0,
  347. bias=0.0,
  348. activation=None, batchnorm_enabled=False, dropout_keep_prob=-1,
  349. is_training=True
  350. ):
  351. """
  352. This block is responsible for a fully connected followed by optional (non-linearity, dropout, max-pooling).
  353. Note that: "is_training" should be passed by a correct value based on being in either training or testing.
  354. :param name: (string) The name scope provided by the upper tf.name_scope('name') as scope.
  355. :param x: (tf.tensor) The input to the layer (N, D).
  356. :param output_dim: (integer) It specifies H, the output second dimension of the fully connected layer [ie:(N, H)]
  357. :param initializer: (tf.contrib initializer) The initialization scheme, He et al. normal or Xavier normal are recommended.
  358. :param l2_strength:(weight decay) (float) L2 regularization parameter.
  359. :param bias: (float) Amount of bias.
  360. :param activation: (tf.graph operator) The activation function applied after the convolution operation. If None, linear is applied.
  361. :param batchnorm_enabled: (boolean) for enabling batch normalization.
  362. :param dropout_keep_prob: (float) for the probability of keeping neurons. If equals -1, it means no dropout
  363. :param is_training: (boolean) to diff. between training and testing (important for batch normalization and dropout)
  364. :return out: The output of the layer. (N, H)
  365. """
  366. with tf.variable_scope(name) as scope:
  367. # 全链接 操作 (N,D)*(D,output_dim) + Baise --> (N,output_dim)
  368. dense_o_b = __dense_p(name='dense', x=x, w=w, output_dim=output_dim, initializer=initializer,
  369. l2_strength=l2_strength,
  370. bias=bias)
  371. # BN 批规范化 + relu激活
  372. if batchnorm_enabled:
  373. dense_o_bn = tf.layers.batch_normalization(dense_o_b, training=is_training, epsilon=1e-5)
  374. if not activation:
  375. dense_a = dense_o_bn
  376. else:
  377. dense_a = activation(dense_o_bn)
  378. else:
  379. if not activation:
  380. dense_a = dense_o_b
  381. else:
  382. dense_a = activation(dense_o_b)
  383. # 随机失活
  384. def dropout_with_keep():
  385. return tf.nn.dropout(dense_a, dropout_keep_prob)
  386. def dropout_no_keep():
  387. return tf.nn.dropout(dense_a, 1.0)
  388. if dropout_keep_prob != -1:
  389. dense_o_dr = tf.cond(is_training, dropout_with_keep, dropout_no_keep)
  390. else:
  391. dense_o_dr = dense_a
  392. dense_o = dense_o_dr
  393. return dense_o
  394. ############################################################################################################
  395. # Pooling Methods 池化方法 最大值池化 均值池化
  396. ###########################################################################
  397. # 11. 最大值池化
  398. def max_pool_2d(x, size=(2, 2), stride=(2, 2), name='pooling'):
  399. """
  400. Max pooling 2D Wrapper
  401. :param x: (tf.tensor) The input to the layer (N,H,W,C).
  402. :param size: (tuple) This specifies the size of the filter as well as the stride.
  403. :param name: (string) Scope name.
  404. :return: The output is the same input but halfed in both width and height (N,H/2,W/2,C).
  405. """
  406. size_x, size_y = size
  407. stride_x, stride_y = stride
  408. return tf.nn.max_pool(x, ksize=[1, size_x, size_y, 1], strides=[1, stride_x, stride_y, 1], padding='VALID',
  409. name=name)
  410. ###############################
  411. # 12. 均值池化##############
  412. def avg_pool_2d(x, size=(2, 2), stride=(2, 2), name='avg_pooling', padding='VALID'):
  413. """
  414. Average pooling 2D Wrapper
  415. :param x: (tf.tensor) The input to the layer (N,H,W,C).
  416. :param size: (tuple) This specifies the size of the filter as well as the stride.
  417. :param name: (string) Scope name.
  418. :return: The output is the same input but halfed in both width and height (N,H/2,W/2,C).
  419. """
  420. size_x, size_y = size
  421. stride_x, stride_y = stride
  422. return tf.nn.avg_pool(x, ksize=[1, size_x, size_y, 1], strides=[1, stride_x, stride_y, 1], padding=padding,
  423. name=name)
  424. ############################################################################################################
  425. # 13. 权重参数初始化 可带有 L2 正则项
  426. #######################################
  427. def __variable_with_weight_decay(kernel_shape, initializer, wd):
  428. """
  429. Create a variable with L2 Regularization (Weight Decay)
  430. :param kernel_shape: the size of the convolving weight kernel.
  431. :param initializer: The initialization scheme, He et al. normal or Xavier normal are recommended.
  432. :param wd:(weight decay) L2 regularization parameter.
  433. :return: The weights of the kernel initialized. The L2 loss is added to the loss collection.
  434. """
  435. w = tf.get_variable('weights', kernel_shape, tf.float32, initializer=initializer)
  436. collection_name = tf.GraphKeys.REGULARIZATION_LOSSES
  437. if wd and (not tf.get_variable_scope().reuse):
  438. weight_decay = tf.multiply(tf.nn.l2_loss(w), wd, name='w_loss')
  439. tf.add_to_collection(collection_name, weight_decay)
  440. return w
  441. #######################################################################
  442. # 14. 参数记录 均值 方差 最大值 最小值 直方图
  443. # Summaries for variables
  444. def __variable_summaries(var):
  445. """
  446. Attach a lot of summaries to a Tensor (for TensorBoard visualization).
  447. :param var: variable to be summarized
  448. :return: None
  449. """
  450. with tf.name_scope('summaries'):
  451. mean = tf.reduce_mean(var)#均值
  452. tf.summary.scalar('mean', mean)
  453. with tf.name_scope('stddev'):#方差
  454. stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
  455. tf.summary.scalar('stddev', stddev)
  456. tf.summary.scalar('max', tf.reduce_max(var))#最大值
  457. tf.summary.scalar('min', tf.reduce_min(var))#最小值
  458. tf.summary.histogram('histogram', var)#直方图

ShuffleNet_tf.py

  1. #-*- coding:utf-8 -*-
  2. # 轻量级网络--ShuffleNet 分组点卷积+通道重排+逐通道卷积
  3. """
  4. 0. 图像预处理 减去均值 乘以归一化系数
  5. 1. conv1 3*3*3*24 卷积 步长2 BN RELU
  6. 2. 最大值池化 3*3 步长2
  7. 3. 一次 步长为2 非分组点卷积 concate通道扩展合并模块, 再进行3次步长为1的 add通道叠加模块
  8. 4. 一次 步长为2 分组点卷积 concate通道扩展合并模块, 再进行7次步长为1的 add通道叠加模块
  9. 5. 一次 步长为2 分组点卷积 concate通道扩展合并模块, 再进行3次步长为1的 add通道叠加模块
  10. 6. 全局均值池化层 7*7 池化核 步长1
  11. 7. 1*1点卷积 输出 类别数量个 卷积特征图
  12. 8. 摊平 到 一维
  13. """
  14. # layers
  15. """
  16. 1. 卷积操作
  17. 2. 卷积层 卷积 + BN + RELU + droupout + maxpooling
  18. 3. 分组卷积层 每组输入通道数量平分 输出通道数量平分 卷积后 各通道 concat通道扩展合并 + BN + relu激活
  19. 4. 通道重排 channel_shuffle 分组再分组 取每个组中的一部分重新排序
  20. 5. 逐通道卷积操作 逐通道卷积 每个卷积核 只和输入数据的一个通道卷积
  21. 6. 逐通道卷积层 逐通道卷积 + BN 批规范化 + 激活
  22. 7. ShuffleNet 核心模块 1x1分组点卷积 + 通道重排 + 3x3DW + 1x1分组点卷积
  23. 8. 全连接层之前 最后的卷积之后 摊平操作 (N,H,W,C)----> (N,D) D = H*W*C
  24. 9. 全连接 操作 (N,D)*(D,output_dim) + Baise --> (N,output_dim)
  25. 10. 全连接层 全链接 + BN + 激活 + 随机失活dropout
  26. 11. 最大值池化
  27. 12. 均值池化
  28. 13. 权重参数初始化 可带有 L2 正则项
  29. 14. 参数记录 均值 方差 最大值 最小值 直方图
  30. """
  31. import tensorflow as tf
  32. # ShuffleNet核心模块 2D卷积 最大值池化 均值池化 全链接层 全链接之前的摊平层
  33. from layers import shufflenet_unit, conv2d, max_pool_2d, avg_pool_2d, dense, flatten
  34. class ShuffleNet:
  35. """ShuffleNet is implemented here!"""
  36. MEAN = [103.94, 116.78, 123.68]# 个通道 像素值 减去的值 均值
  37. NORMALIZER = 0.017# 归一化比例
  38. def __init__(self, args):
  39. self.args = args
  40. self.X = None
  41. self.y = None
  42. self.logits = None
  43. self.is_training = None
  44. self.loss = None
  45. self.regularization_loss = None
  46. self.cross_entropy_loss = None
  47. self.train_op = None
  48. self.accuracy = None
  49. self.y_out_argmax = None
  50. self.summaries_merged = None
  51. # A number stands for the num_groups
  52. # Output channels for conv1 layer
  53. self.output_channels = {'1': [144, 288, 576], '2': [200, 400, 800], '3': [240, 480, 960], '4': [272, 544, 1088],
  54. '8': [384, 768, 1536], 'conv1': 24}
  55. self.__build()
  56. # 初始化输入
  57. def __init_input(self):
  58. batch_size = self.args.batch_size if self.args.train_or_test == 'train' else 1
  59. with tf.variable_scope('input'):
  60. # 输入图片 Input images 图片数量*长*宽*通道数量
  61. self.X = tf.placeholder(tf.float32,
  62. [batch_size, self.args.img_height, self.args.img_width,
  63. self.args.num_channels])
  64. # 数据对应得标签
  65. self.y = tf.placeholder(tf.int32, [batch_size])
  66. # is_training is for batch normalization and dropout, if they exist
  67. self.is_training = tf.placeholder(tf.bool)
  68. # 改变 图像大小
  69. def __resize(self, x):#双三次插值
  70. return tf.image.resize_bicubic(x, [224, 224])
  71. # 先进行一次 步长为2 的下采样 concate合并模块, 再进行多次步长为1的 add通道叠加模块
  72. def __stage(self, x, stage=2, repeat=3):
  73. if 2 <= stage <= 4:
  74. stage_layer = shufflenet_unit('stage' + str(stage) + '_0', x=x, w=None,
  75. num_groups=self.args.num_groups,
  76. group_conv_bottleneck=not (stage == 2),# stage = 2 时 先不进行分组点卷积
  77. num_filters=
  78. self.output_channels[str(self.args.num_groups)][
  79. stage - 2],
  80. stride=(2, 2),# concate通道扩展合并
  81. fusion='concat', l2_strength=self.args.l2_strength,
  82. bias=self.args.bias,
  83. batchnorm_enabled=self.args.batchnorm_enabled,
  84. is_training=self.is_training)
  85. for i in range(1, repeat + 1):
  86. stage_layer = shufflenet_unit('stage' + str(stage) + '_' + str(i),
  87. x=stage_layer, w=None,
  88. num_groups=self.args.num_groups,
  89. group_conv_bottleneck=True,# 分组点卷积
  90. num_filters=self.output_channels[
  91. str(self.args.num_groups)][stage - 2],
  92. stride=(1, 1),#ADD 通道叠加
  93. fusion='add',
  94. l2_strength=self.args.l2_strength,
  95. bias=self.args.bias,
  96. batchnorm_enabled=self.args.batchnorm_enabled,
  97. is_training=self.is_training)
  98. return stage_layer
  99. else:
  100. raise ValueError("Stage should be from 2 -> 4")
  101. # 输出
  102. def __init_output(self):
  103. with tf.variable_scope('output'):
  104. # Losses
  105. self.regularization_loss = tf.reduce_sum(tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES))
  106. self.cross_entropy_loss = tf.reduce_mean(
  107. tf.nn.sparse_softmax_cross_entropy_with_logits(logits=self.logits, labels=self.y, name='loss'))
  108. self.loss = self.regularization_loss + self.cross_entropy_loss
  109. # Optimizer
  110. update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
  111. with tf.control_dependencies(update_ops):
  112. self.optimizer = tf.train.AdamOptimizer(learning_rate=self.args.learning_rate)
  113. self.train_op = self.optimizer.minimize(self.loss)
  114. # This is for debugging NaNs. Check TensorFlow documentation.
  115. self.check_op = tf.add_check_numerics_ops()
  116. # Output and Metrics
  117. self.y_out_softmax = tf.nn.softmax(self.logits)# softmax 归一化分类
  118. self.y_out_argmax = tf.argmax(self.y_out_softmax, axis=-1, output_type=tf.int32)# 最大值得到分类结果
  119. self.accuracy = tf.reduce_mean(tf.cast(tf.equal(self.y, self.y_out_argmax), tf.float32))#准确度
  120. # 记录参数
  121. with tf.name_scope('train-summary-per-iteration'):
  122. tf.summary.scalar('loss', self.loss)
  123. tf.summary.scalar('acc', self.accuracy)
  124. self.summaries_merged = tf.summary.merge_all()
  125. def __build(self):
  126. self.__init_global_epoch()
  127. self.__init_global_step()
  128. self.__init_input()
  129. # 0. 图像预处理 减去均值 乘以归一化系数##################################
  130. with tf.name_scope('Preprocessing'):
  131. # 分割成三通道
  132. red, green, blue = tf.split(self.X, num_or_size_splits=3, axis=3)
  133. # 每个通道 减去均值 乘以归一化系数 后再concat/merge 通道扩展合并
  134. preprocessed_input = tf.concat([
  135. tf.subtract(blue, ShuffleNet.MEAN[0]) * ShuffleNet.NORMALIZER,
  136. tf.subtract(green, ShuffleNet.MEAN[1]) * ShuffleNet.NORMALIZER,
  137. tf.subtract(red, ShuffleNet.MEAN[2]) * ShuffleNet.NORMALIZER,
  138. ], 3)
  139. # 1. conv1 3*3*3*24 卷积 步长 2 BN RELU #########################################################
  140. ######## 周围填充
  141. x_padded = tf.pad(preprocessed_input, [[0, 0], [1, 1], [1, 1], [0, 0]], "CONSTANT")
  142. ######## conv
  143. conv1 = conv2d('conv1', x=x_padded, w=None, num_filters=self.output_channels['conv1'], kernel_size=(3, 3),
  144. stride=(2, 2), l2_strength=self.args.l2_strength, bias=self.args.bias,
  145. batchnorm_enabled=self.args.batchnorm_enabled, is_training=self.is_training,
  146. activation=tf.nn.relu, padding='VALID')
  147. # 2. 最大值池化 3*3 步长2 ##################################################
  148. padded = tf.pad(conv1, [[0, 0], [0, 1], [0, 1], [0, 0]], "CONSTANT")
  149. max_pool = max_pool_2d(padded, size=(3, 3), stride=(2, 2), name='max_pool')
  150. # 3. 一次 步长为2 非分组点卷积 concate通道扩展合并模块, 再进行3次步长为1的 add通道叠加模块
  151. stage2 = self.__stage(max_pool, stage=2, repeat=3)
  152. # 4. 一次 步长为2 分组点卷积 concate通道扩展合并模块, 再进行7次步长为1的 add通道叠加模块
  153. stage3 = self.__stage(stage2, stage=3, repeat=7)
  154. # 5. 一次 步长为2 分组点卷积 concate通道扩展合并模块, 再进行3次步长为1的 add通道叠加模块
  155. stage4 = self.__stage(stage3, stage=4, repeat=3)
  156. # 6. 全局均值池化层 7*7 池化核 步长1
  157. global_pool = avg_pool_2d(stage4, size=(7, 7), stride=(1, 1), name='global_pool', padding='VALID')
  158. # 7. 1*1点卷积 输出 类别数量个 卷积特征图
  159. logits_unflattened = conv2d('fc', global_pool, w=None, num_filters=self.args.num_classes,
  160. kernel_size=(1, 1),# 1*1点卷积
  161. l2_strength=self.args.l2_strength,
  162. bias=self.args.bias,
  163. is_training=self.is_training)
  164. # 8. 摊平 到 一维
  165. self.logits = flatten(logits_unflattened)
  166. # 9. 计算误差
  167. self.__init_output()
  168. def __init_global_epoch(self):
  169. """
  170. Create a global epoch tensor to totally save the process of the training
  171. :return:
  172. """
  173. with tf.variable_scope('global_epoch'):
  174. self.global_epoch_tensor = tf.Variable(-1, trainable=False, name='global_epoch')
  175. self.global_epoch_input = tf.placeholder('int32', None, name='global_epoch_input')
  176. self.global_epoch_assign_op = self.global_epoch_tensor.assign(self.global_epoch_input)
  177. def __init_global_step(self):
  178. """
  179. Create a global step variable to be a reference to the number of iterations
  180. :return:
  181. """
  182. with tf.variable_scope('global_step'):
  183. self.global_step_tensor = tf.Variable(0, trainable=False, name='global_step')
  184. self.global_step_input = tf.placeholder('int32', None, name='global_step_input')
  185. self.global_step_assign_op = self.global_step_tensor.assign(self.global_step_input)

11. SqueezeNet 1*1卷积降低通道数量

 

11.1 常用的模型压缩技术有:

 
  1. 1. 奇异值分解(singular value decomposition (SVD))

  2. 2. 网络剪枝(Network Pruning):使用网络剪枝和稀疏矩阵

  3. 3. 深度压缩(Deep compression):使用网络剪枝,数字化和huffman编码

  4. 4. 硬件加速器(hardware accelerator)

  5. 1. 贝叶斯优化

  6. 2. 模拟退火

  7. 3. 随机搜索

  8. 4. 遗传算法

  9.  

 

11.2 SqueezeNet 简化网络模型参数的 设计

使用以下三个策略来减少SqueezeNet设计参数

 
  1. 1. 使用1∗1卷积代替3∗3 卷积:参数减少为原来的1/9

  2. 2. 减少输入通道数量:这一部分使用squeeze layers来实现

  3. 3. 将欠采样操作延后,可以给卷积层提供更大的激活图:更大的激活图保留了更多的信息,可以提供更高的分类准确率

  4. 其中,1 和 2 可以显著减少参数数量,3 可以在参数数量受限的情况下提高准确率。

SqueezeNet 的核心模块 Fire Module

 
  1. 1. 只使用1∗1 卷积 filter 构建 squeeze convolution layer 减少参数 策略1

  2. 2. 使用1∗1 和3∗3 卷积 filter的组合 构建的 expand layer

  3. 3. squeeze convolution layer 中 1∗1 卷积 filter数量可调 s1

  4. expand layer 中 1∗1 卷积 filter数量可调 e1

  5. expand layer 中 3∗3 卷积 filter数量可调 e2

  6. 4. s1 < e1 + e2 减少参数 策略2

11.3  Fire Module 结构

 
  1. | -----> 1 * 1卷积------|

  2. 输入----->1 * 1卷积(全部) --> ---> concat 通道合并 -------> 输出

  3. | -----> 3 * 3卷积  ----|

与Inception module 区别

 
  1. 部分输出 ----->  1 * 1卷积------|

  2. 输入----->1 * 1卷积 ---->           ---> concat 通道合并 -------> 输出 

  3. 部分输出 ----->  3 * 3卷积  ----|

11.4 SqueezeNet 网络结构

 

 
  1. 1. SqueezeNet以卷积层(conv1)开始,

  2. 2. 接着使用8个Fire modules (fire2-9),

  3. 3. 最后以卷积层(conv10)结束卷积特征提取

  4. 4. 再通过 全局均值池化 + softmax分类得到结果

  5.  
  6. 每个fire module中的filter数量逐渐增加,

  7. 并且在conv1, fire4, fire8, 和 conv10这几层 之后 使用步长为2的max-pooling,

   即将池化层放在相对靠后的位置,这使用了以上的策略(3)

 

11.5 以fire2模块为例

 
  1. 1. maxpool1层的输出为55∗55∗96 55∗55∗96,一共有96个通道输出。

  2. 2. 之后紧接着的Squeeze层有16个1∗1∗96 卷积核,96个通道输入,16个通道输出,输出尺寸为 55*55*16

  3. 3. 之后将输出分别送到expand层中的1∗1∗16 (64个)和3∗3∗16(64个)进行处理,注意这里不对16个通道进行切分。

  4. 4. 对3∗3∗16 的卷积输入进行尺寸为1的zero padding,分别得到55∗55∗64 和 55∗55∗64 大小相同的两个feature map。

  5. 5. 将这两个feature map连接到一起得到55∗55∗128 大小的feature map。

  6.  

SqueezeNet_tf.py

  1. #-*- coding: utf-8 -*-
  2. # 论文
  3. # https://arxiv.org/pdf/1602.07360.pdf
  4. # 论文源码 caffe model
  5. # https://github.com/DeepScale/SqueezeNet
  6. """
  7. 2018/04/27
  8. SqueezeNet的工作为以下几个方面:
  9. 1. 提出了新的网络架构Fire Module,通过减少参数来进行模型压缩
  10. 2. 使用其他方法对提出的SqeezeNet模型进行进一步压缩
  11. 3. 对参数空间进行了探索,主要研究了压缩比和3∗3卷积比例的影响
  12. Fire Module 结构
  13. -----> 1 * 1卷积 RELU -----|
  14. 输入----->1 * 1卷积(全部) RELU ---> concat 通道扩展 -------> 输出
  15. -----> 3 * 3卷积 RELU ----|
  16. 网络结构
  17. 0. conv1 7*7*3*96 7*7卷积 3通道输入 96通道输出 滑动步长2 relu激活
  18. 1. 最大值池化 maxpool1 3*3池化核尺寸 滑动步长2
  19. 2. fire2 squeeze层 16个输出通道, expand层 64个输出通道
  20. 3. fire3 squeeze层 16个输出通道, expand层 64个输出通道
  21. 4. fire4 squeeze层 32个输出通道, expand层 128个输出通道
  22. 5. maxpool4 最大值池化 maxpool1 3*3池化核尺寸 滑动步长2
  23. 6. fire5 squeeze层 32个输出通道, expand层 128个输出通道
  24. 7. fire6 squeeze层 48个输出通道, expand层 192个输出通道
  25. 8. fire7 squeeze层 48个输出通道, expand层 196个输出通道
  26. 9. fire8 squeeze层 64个输出通道, expand层 256个输出通道
  27. 10. maxpool8 最大值池化 maxpool1 3*3池化核尺寸 滑动步长2
  28. 11. fire9 squeeze层 64个输出通道, expand层 256个输出通道
  29. 12. 随机失活层 dropout 神经元以0.5的概率不输出
  30. 13. conv10 类似于全连接层 1*1的点卷积 将输出通道 固定为 1000类输出 + relu激活
  31. 14. avgpool10 13*13的均值池化核尺寸 13*13*1000 ---> 1*1*1000
  32. 15. softmax归一化分类概率输出
  33. """
  34. import tensorflow as tf
  35. import numpy as np
  36. class SqueezeNet(object):
  37. def __init__(self, inputs, nb_classes=1000, is_training=True):
  38. ######## 0. conv1 7*7*3*96 7*7卷积 3通道输入 96通道输出 滑动步长2 relu激活 ###############
  39. net = tf.layers.conv2d(inputs, 96, [7, 7], strides=[2, 2],
  40. padding="SAME", activation=tf.nn.relu,
  41. name="conv1")#### 224*224*3 >>>> 112*112*96
  42. ######## 1. 最大值池化 maxpool1 3*3池化核尺寸 滑动步长2
  43. net = tf.layers.max_pooling2d(net, [3, 3], strides=[2, 2],
  44. name="maxpool1")## 112*112*96 >>>> 56*56*96
  45. ######## 2. fire2 squeeze层 16个输出通道, expand层 64个输出通道 #########################
  46. net = self._fire(net, 16, 64, "fire2")#### 56*56*96 >>>> 56*56*128 64+64=128
  47. ######## 3. fire3 squeeze层 16个输出通道, expand层 64个输出通道 #########################
  48. net = self._fire(net, 16, 64, "fire3")#### 56*56*128 >>>> 56*56*128
  49. ######## 4. fire4 squeeze层 32个输出通道, expand层 128个输出通道 #######################
  50. net = self._fire(net, 32, 128, "fire4")### 56*56*128 >>>> 56*56*256 128+128=256
  51. ######## 5. maxpool4 最大值池化 maxpool1 3*3池化核尺寸 滑动步长2
  52. net = tf.layers.max_pooling2d(net, [3, 3], strides=[2, 2],
  53. name="maxpool4")## 56*56*256 >>> 28*28*256
  54. ######## 6. fire5 squeeze层 32个输出通道, expand层 128个输出通道 #######################
  55. net = self._fire(net, 32, 128, "fire5")### 28*28*256 >>> 28*28*256
  56. ######## 7. fire6 squeeze层 48个输出通道, expand层 192个输出通道 #########################
  57. net = self._fire(net, 48, 192, "fire6")### 28*28*256 >>> 28*28*384 192+192=384
  58. ######## 8. fire7 squeeze层 48个输出通道, expand层 196个输出通道 #########################
  59. net = self._fire(net, 48, 192, "fire7")### 28*28*584 >>> 28*28*384
  60. ######## 9. fire8 squeeze层 64个输出通道, expand层 256个输出通道 #########################
  61. net = self._fire(net, 64, 256, "fire8")### 28*28*584 >>> 28*28*512 256+256=512
  62. ######## 10. maxpool8 最大值池化 maxpool1 3*3池化核尺寸 滑动步长2
  63. net = tf.layers.max_pooling2d(net, [3, 3], strides=[2, 2],
  64. name="maxpool8")## 28*28*512 >>> 14*14*512
  65. ######## 11. fire9 squeeze层 64个输出通道, expand层 256个输出通道 #########################
  66. net = self._fire(net, 64, 256, "fire9")
  67. ######## 12. 随机失活层 dropout 神经元以0.5的概率不输出######################################
  68. net = tf.layers.dropout(net, 0.5, training=is_training)
  69. ######## 13. conv10 类似于全连接层 1*1的点卷积 将输出通道 固定为 1000类输出 + relu激活 ########
  70. net = tf.layers.conv2d(net, 1000, [1, 1], strides=[1, 1],
  71. padding="SAME", activation=tf.nn.relu,
  72. name="conv10")
  73. ######## 14. avgpool10 13*13的均值池化核尺寸 13*13*1000 ---> 1*1*1000
  74. net = tf.layers.average_pooling2d(net, [13, 13], strides=[1, 1],
  75. name="avgpool10")
  76. # squeeze the axis 1*1*1000 ---> 1*1000
  77. net = tf.squeeze(net, axis=[1, 2])
  78. self.logits = net#逻辑值
  79. ######### 15. softmax归一化分类概率输出 ###################################################
  80. self.prediction = tf.nn.softmax(net)# softmax归一化分类概率输出
  81. # Fire Module 结构
  82. # -----> 1 * 1卷积------|
  83. # 输入----->1 * 1卷积(全部) ---> concat 通道合并 -------> 输出
  84. # -----> 3 * 3卷积 ----|
  85. def _fire(self, inputs, squeeze_depth, expand_depth, scope):
  86. with tf.variable_scope(scope):
  87. # squeeze 层 1 * 1卷积 + relu
  88. squeeze = tf.layers.conv2d(inputs, squeeze_depth, [1, 1],
  89. strides=[1, 1], padding="SAME",
  90. activation=tf.nn.relu, name="squeeze")
  91. # expand 层 1*1 卷积 +relu 3*3卷积 + relu
  92. expand_1x1 = tf.layers.conv2d(squeeze, expand_depth, [1, 1],
  93. strides=[1, 1], padding="SAME",
  94. activation=tf.nn.relu, name="expand_1x1")
  95. expand_3x3 = tf.layers.conv2d(squeeze, expand_depth, [3, 3],
  96. strides=[1, 1], padding="SAME",
  97. activation=tf.nn.relu, name="expand_3x3")
  98. # 通道扩展 concat
  99. return tf.concat([expand_1x1, expand_3x3], axis=3)
  100. if __name__ == "__main__":
  101. # 随机初始化测试数据 32张图片 224*224尺寸 3通道
  102. inputs = tf.random_normal([32, 224, 224, 3])
  103. # 经过 SqueezeNet网络得到输出
  104. net = SqueezeNet(inputs)
  105. # 打印预测结果
  106. print(net.prediction)

 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号