caffe模型通道剪枝channel pruning

秒速五厘米 2022-03-22 03:56 345阅读 0赞

deep compression介绍的剪枝:是将权值置0,再通过稀疏存储格式来减小模型大小。

如下, 通过通道剪枝来减少模型大小。

  1. # coding:utf-8
  2. # by chen yh
  3. import caffe
  4. import numpy as np
  5. import shutil
  6. import matplotlib.pyplot as plt
  7. '''
  8. These parameters need modification:
  9. root: root directory ;
  10. model: your caffemodel ;
  11. prototxt: your prototxt ;
  12. prune layer: need prune layer name, a list ;
  13. input layer: input of these layers is output of prune layer,each element is a list ;
  14. th : thereshold of each prune layer,a list.
  15. Please ensure lenth of prune layer, input layer and th.
  16. picture and get get_sum_l1 functions can help you find suitable threshold.
  17. '''
  18. def get_prune(net, layer, threshold): # 返回layer中低于阈值threshold的卷积核的序号
  19. weight_ori = net.params[layer][0].data
  20. # bias_ori=net.params[layer][1].data
  21. print("layer:",layer,"shape:",weight_ori.shape)
  22. #print("weight_ori:",weight_ori)
  23. sum_l1 = []
  24. for i in range(weight_ori.shape[0]):
  25. if (layer == "fc6") or (layer == "fc7") or (layer == "fc8"):
  26. sum_l1.append((i, np.sum(abs(weight_ori[i, :]))))
  27. else:
  28. sum_l1.append((i, np.sum(abs(weight_ori[i, :, :, :])))) # sum_l1存放每个卷积核的所有权重绝对值之和
  29. print("sum_l1.shape:",len(sum_l1))
  30. de_keral = [] # de_keral存放大于阈值的卷积核的序号
  31. for i in sum_l1:
  32. if i[1] > threshold:
  33. de_keral.append(i[0])
  34. print(layer + "层需要prune的卷积核有" + str(weight_ori.shape[0] - len(de_keral)) + "个,保留的卷积核有" + str(len(de_keral)) + "个")
  35. print("de_keral.shape:",len(de_keral))
  36. return de_keral
  37. def prune(net, pk, lk): # 输出两个字典,键都是修剪层的layer的名字,值分别是修剪层的weight和bias
  38. w_new = {} # 键是layer,值是保存后的weight
  39. b_new = {} # 键是layer,值是保存后的bias
  40. for l in pk.keys(): # 待剪层权重处理 w_n = w[pk[l],:,;,;]
  41. #print("pk.keys:",l)
  42. w_old = net.params[l][0].data
  43. b_old = net.params[l][1].data
  44. if (l == "fc6") or (l == "fc7"):
  45. w_n = w_old[pk[l], :]
  46. else:
  47. w_n = w_old[pk[l], :, :, :]
  48. b_n = b_old[pk[l]]
  49. w_new[l] = w_n
  50. b_new[l] = b_n
  51. # net_n.params[l][0].data[...] = w_n
  52. # net_n.params[l][1].data[...] = b_n
  53. print("player:",l,",w.shape:",w_new[l].shape,",b.shape",b_new[l].shape)
  54. for l in lk.keys(): # 以待剪层为输入的层权重处理
  55. if l not in pk.keys(): # bottom被修剪后本身没有被修剪,所以其权重只需要在原来的net上面取切片,w_n = w[:,lk[l],:,:]
  56. #print("lk.keys:", l)
  57. if (l != "conv4_3_norm"): # 对传统卷积层的处理
  58. w_o = net.params[l][0].data
  59. b_o = net.params[l][1].data
  60. b_new[l] = b_o # bias保留,因为这些层没有剪卷积核
  61. if (l == "fc6") or (l == "fc7") or (l == "fc8"):
  62. w_n = w_o[:, lk[l]]
  63. else:
  64. w_n = w_o[:, lk[l], :, :]
  65. w_new[l] = w_n
  66. else: # 对特殊层的处理,参数个数不是2
  67. w_o = net.params[l][0].data
  68. w_n = w_o[lk[l],]
  69. w_new[l] = w_n
  70. print("klayer:",l,",w.shape:",w_new[l].shape,",b.shape",b_new[l].shape)
  71. else: # pk 和 lk共有的层,也就是这层的bottom和层本身都被修剪过,所以权重不能在原来的net上切片,利用保存了的w_new取切片.
  72. w_o = w_new[l]
  73. print("lk.keys else:",l)
  74. if (l == "fc6") or (l == "fc7"):
  75. w_n = w_o[:, lk[l]]
  76. else:
  77. w_n = w_o[:, lk[l], :, :]
  78. w_new[l] = w_n
  79. print("llayer:",l,",w.shape:",w_new[l].shape)
  80. return w_new, b_new
  81. def get_prototxt(pk, pro_n): # 复制原来的prototxt,并修改修剪层的num_output,这一段代码有点绕,有空的话优化为几个单独的函数或者弄个类
  82. with open(pro_n, "r") as p:
  83. lines = p.readlines()
  84. k = 0
  85. with open(pro_n, "w") as p:
  86. while k < len(lines): # 遍历所有的lines,此处不宜用for.
  87. #print("lines[k]:",lines[k])
  88. if 'name:' in lines[k]:
  89. print("lines[k].split:",lines[k].split('"')[1])
  90. l_name = lines[k].split('"')[1] # 获取layer name
  91. if l_name in pk.keys(): # 如果name在待修剪层中,则需要修改,下面进入一个找channel的循环块.
  92. while True:
  93. if "num_output:" in lines[k]:
  94. channel_n = " num_output: " + str(len(pk[l_name])) + "\n"
  95. p.write(channel_n)
  96. k = k + 1
  97. break
  98. else:
  99. p.write(lines[k])
  100. k = k + 1
  101. else: # name不在待修剪层中,直接copy行
  102. p.write(lines[k])
  103. k = k + 1
  104. else:
  105. p.write(lines[k])
  106. k = k + 1
  107. print("deploy_rebirth_prune.prototxt已写好")
  108. def savemodel(net, net_n, w_prune, b_prune, path): # 储存修改后的caffemodel
  109. for layer in net.params.keys():
  110. if layer in w_prune.keys():
  111. print("save model-layer:",layer,"len:",w_prune[layer].shape,"len2:",net_n.params[layer][0].data[...].shape)
  112. net_n.params[layer][0].data[...] = w_prune[layer]
  113. if layer in b_prune.keys():
  114. net_n.params[layer][1].data[...] = b_prune[layer]
  115. else:
  116. weight = net.params[layer]
  117. for index, w in enumerate(weight):
  118. try:
  119. net_n.params[layer][index].data[...] = w.data
  120. except ValueError:
  121. print(layer + "层权重广播出现问题")
  122. net_n.save(path + "age_net_prune.caffemodel")
  123. print("剪枝结束,保存模型名为age_net_prune.caffemodel")
  124. def picture(net, layer): # 将某一layer所有卷积核的权重绝对值之和排序后画图
  125. weight = net.params[layer][0].data
  126. sum_l1 = []
  127. for i in range(weight.shape[0]):
  128. sum_l1.append(np.sum(abs(weight[i, :, :, :])))
  129. sum_l1.sort()
  130. x = [i for i in range(len(sum_l1))]
  131. plt.plot(x, sum_l1)
  132. plt.legend()
  133. plt.show()
  134. def get_sum_l1(net, txt_path, v): # 定向输出各个层的卷积核的权重绝对值之和到指定文件,v为保存的前多少个值
  135. with open(txt_path, "w") as t:
  136. for layer in net.params.keys():
  137. weight = net.params[layer][0].data
  138. sum_l1 = []
  139. try:
  140. for i in range(weight.shape[0]):
  141. sum_l1.append(np.sum(abs(weight[i, :, :, :])))
  142. except IndexError:
  143. print(layer + "该层非卷积层")
  144. sum_l1.sort()
  145. t.write(layer + '\n')
  146. for i in range(v):
  147. try:
  148. t.write(str(sum_l1[i]) + ' ')
  149. except IndexError:
  150. print(layer + "层没有" + str(v) + "个参数")
  151. break
  152. t.write("\n\n")
  153. if __name__ == "__main__":
  154. root = "/home/xuqiong/code/caffeprune/"
  155. model = root + "age_net_new.caffemodel"
  156. prototxt = root + "deploy_age2_new.prototxt"
  157. py = {} # 键是prune_layer,值是对应的prune的卷积核的序号,也就是p_k
  158. iy = {} # 键是以prune_layer为input的layer,值也是对应的p_k
  159. prune_layer = ["conv1", "conv2","fc6","fc7"]
  160. input_layer = [["conv2"], ["conv3"],["fc7"],["fc8"]]
  161. th = [2,22,87,4.5] # al元素的个数保持和prune_layer个数一致,阈值可以自己设
  162. #prune_layer = ["conv3"]
  163. #input_layer = [["fc6"]]
  164. #th = [20]
  165. caffe.set_mode_gpu()
  166. net = caffe.Net(prototxt, model, caffe.TEST)
  167. pro_n = root + "deploy_age2_prune.prototxt"
  168. shutil.copyfile(prototxt, pro_n)
  169. #net_n = caffe.Net(pro_n, caffe.TEST)
  170. #picture(net,"conv3")
  171. for (layer1, layer2, t) in zip(prune_layer, input_layer, th):
  172. py[layer1] = get_prune(net, layer1, threshold=t)
  173. print("need prune layer:",layer1)
  174. for m in layer2: # 以prune_layer为输入的layer可能有多个,所以input_layer每个元素是一个列表,此处对列表中每一个元素赋值
  175. iy[m] = py[layer1]
  176. w_prune, b_prune = prune(net, py, iy)
  177. print("len of w&b:", len(w_prune),len(b_prune))
  178. #print("prune w & b:",w_prune,b_prune)
  179. get_prototxt(py, pro_n)
  180. net_n = caffe.Net(pro_n, caffe.TEST)
  181. savemodel(net, net_n, w_prune, b_prune, root)
  182. '''
  183. while (raw_input("按1将生成deploy_prune_new.prototxt:")) == "1":
  184. w_prune, b_prune = prune(net, py, iy)
  185. get_prototxt(py, pro_n)
  186. while (raw_input("按1将生成剪枝后的模型:")) == "1":
  187. net_n = caffe.Net(pro_n, caffe.TEST)
  188. savemodel(net, net_n, w_prune, b_prune, root)
  189. break
  190. break
  191. '''

参考文章:https://blog.csdn.net/dlyldxwl/article/details/79502829

发表评论

表情:
评论列表 (有 0 条评论,345人围观)

还没有评论,来说两句吧...

相关阅读

    相关 GO语言:channel通道

    通道可以被认为是Goroutines通信的管道。类似于管道中的水从一端到另一端的流动,数据可以从一端发送到另一端,通过通道接收。 在前面讲Go语言的并发时候,我们就说过,当多