You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

Conv2d.py 13 kB

5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. from __future__ import absolute_import
  2. import numpy as np
  3. from .Node import Op
  4. from .._base import DNNL_LIB
  5. from ..cpu_links import conv2d as cpu_conv2d
  6. from ..cpu_links import conv2d_gradient_of_data as cpu_conv2d_gradient_of_data
  7. from ..cpu_links import conv2d_gradient_of_filter as cpu_conv2d_gradient_of_filter
  8. from ..gpu_links import CuDNN_conv2d
  9. from ..gpu_links import CuDNN_conv2d_gradient_of_data
  10. from ..gpu_links import CuDNN_conv2d_gradient_of_filter
  11. class Conv2dOp(Op):
  12. # nodeA : x nodeB : filter
  13. def __init__(self, node_A, node_B, padding=0, stride=1, ctx=None):
  14. super().__init__(Conv2dOp, [node_A, node_B], ctx)
  15. self.padding = padding
  16. self.stride = stride
  17. def im2col(self, X, filter_H, filter_W, padding, stride):
  18. N, C, H, W = X.shape
  19. assert (H + 2 * padding - filter_H) % stride == 0
  20. assert (W + 2 * padding - filter_W) % stride == 0
  21. out_H = (H + 2 * padding - filter_H) // stride + 1
  22. out_W = (W + 2 * padding - filter_W) // stride + 1
  23. y_row_size = C * filter_H * filter_W
  24. y_col_size = out_H * out_W
  25. y_shape = (N, y_row_size, y_col_size)
  26. Y = np.empty(y_shape, dtype=X.dtype)
  27. for batch_index in range(N):
  28. for col_index in range(y_col_size):
  29. out_y = col_index // out_W
  30. out_x = col_index % out_W
  31. in_y = out_y * stride - padding
  32. in_x = out_x * stride - padding
  33. row_idx = 0
  34. for c in range(0, C):
  35. for y in range(in_y, in_y + filter_H):
  36. for x in range(in_x, in_x + filter_W):
  37. if (x < 0 or x >= W or y < 0 or y >= H):
  38. Y[batch_index, row_idx, col_index] = 0
  39. else:
  40. Y[batch_index, row_idx,
  41. col_index] = X[batch_index, c, y, x]
  42. row_idx += 1
  43. return Y
  44. def np_conv2d(self, X, Filter, padding=0, stride=1):
  45. """Implement a conv2d as a matrix multiply after im2col."""
  46. filter_outChannel, filter_inChannel, filter_H, filter_W = Filter.shape
  47. N, C, H, W = X.shape
  48. assert (H + 2 * padding - filter_H) % stride == 0
  49. assert (W + 2 * padding - filter_W) % stride == 0
  50. out_H = (H + 2 * padding - filter_H) // stride + 1
  51. out_W = (W + 2 * padding - filter_W) // stride + 1
  52. im2col_matrix = self.im2col(X, filter_H, filter_W, padding, stride)
  53. filter_matrix = Filter.reshape(filter_outChannel, -1)
  54. return np.matmul(filter_matrix, im2col_matrix).reshape(N, filter_outChannel, out_H, out_W)
  55. def compute(self, input_vals, output_val, stream_handle=None):
  56. if self.on_cpu:
  57. if DNNL_LIB['DnnlConv2d']:
  58. cpu_conv2d(input_vals[0], input_vals[1],
  59. output_val, self.padding, self.stride)
  60. else:
  61. output_val[:] = self.np_conv2d(
  62. input_vals[0].asnumpy(), input_vals[1].asnumpy(), self.padding, self.stride)
  63. else:
  64. CuDNN_conv2d(input_vals[0], input_vals[1],
  65. output_val, self.padding, self.stride, stream_handle)
  66. def gradient(self, output_grad):
  67. return [conv2d_gradient_of_data_op(self.inputs[1], output_grad, self.padding, self.stride, ctx=self.raw_ctx),
  68. conv2d_gradient_of_filter_op(self.inputs[0], output_grad, self.padding, self.stride, ctx=self.raw_ctx)]
  69. def infer_shape(self, input_shapes):
  70. assert len(input_shapes) == 2
  71. N, _, H, W = input_shapes[0]
  72. f_O, _, f_H, f_W = input_shapes[1]
  73. padding = self.padding
  74. stride = self.stride
  75. filter_H = input_shapes[1][2]
  76. filter_W = input_shapes[1][3]
  77. out_H = (H + 2 * padding - filter_H) // stride + 1
  78. out_W = (W + 2 * padding - filter_W) // stride + 1
  79. return (N, f_O, out_H, out_W)
  80. class Conv2d_Gradient_of_DataOp(Op):
  81. # nodeA : filter nodeB : Y_gradient
  82. def __init__(self, node_A, node_B, padding=0, stride=1, ctx=None):
  83. super().__init__(Conv2d_Gradient_of_DataOp, [node_A, node_B], ctx)
  84. self.padding = padding
  85. self.stride = stride
  86. def im2col_transpose(self, N, C, H, W, filter_H, filter_W, Y, padding, stride):
  87. assert (H + 2 * padding - filter_H) % stride == 0
  88. assert (W + 2 * padding - filter_W) % stride == 0
  89. out_H = (H + 2 * padding - filter_H) // stride + 1
  90. out_W = (W + 2 * padding - filter_W) // stride + 1
  91. _, y_row_size, y_col_size = Y.shape
  92. der_X_shape = (N, C, H, W)
  93. der_X = np.zeros(der_X_shape, dtype=Y.dtype)
  94. for batch_index in range(N):
  95. for col_index in range(y_col_size):
  96. out_y = col_index // out_W
  97. out_x = col_index % out_W
  98. in_y = out_y * stride - padding
  99. in_x = out_x * stride - padding
  100. row_idx = 0
  101. for c in range(0, C):
  102. for y in range(in_y, in_y + filter_H):
  103. for x in range(in_x, in_x + filter_W):
  104. if (x < 0 or x >= W or y < 0 or y >= H):
  105. Y[batch_index, row_idx, col_index] = 0
  106. else:
  107. der_X[batch_index, c, y,
  108. x] += Y[batch_index, row_idx, col_index]
  109. row_idx += 1
  110. return der_X
  111. def np_Conv2dGradient_data(self, X_N, X_C, X_H, X_W, Filter, Y, padding=0, stride=1):
  112. filter_outChannel, filter_inChannel, filter_H, filter_W = Filter.shape
  113. Y_N, Y_C, Y_H, Y_W = Y.shape
  114. YY = Y.reshape((Y_N, Y_C, Y_H * Y_W)) # transformed to im2col Y
  115. F_filter = Filter.reshape((filter_outChannel, -1))
  116. gradient_im2col_XX = np.matmul(F_filter.T, YY)
  117. gradient_X = self.im2col_transpose(
  118. X_N, X_C, X_H, X_W, filter_H, filter_W, gradient_im2col_XX, padding, stride) # gradient of x
  119. return gradient_X
  120. def compute(self, input_vals, output_val, stream_handle=None):
  121. if self.on_cpu:
  122. if DNNL_LIB['DnnlConv2d_Gradient_of_Data']:
  123. cpu_conv2d_gradient_of_data(
  124. input_vals[0], input_vals[1], output_val, self.padding, self.stride)
  125. else:
  126. N = input_vals[1].shape[0]
  127. C = input_vals[0].shape[1]
  128. H = (input_vals[1].shape[2] - 1) * self.stride + \
  129. input_vals[0].shape[2] - 2 * self.padding
  130. W = (input_vals[1].shape[3] - 1) * self.stride + \
  131. input_vals[0].shape[3] - 2 * self.padding
  132. output_val[:] = self.np_Conv2dGradient_data(
  133. N, C, H, W, input_vals[0].asnumpy(), input_vals[1].asnumpy(), padding=self.padding, stride=self.stride)
  134. else:
  135. CuDNN_conv2d_gradient_of_data(
  136. input_vals[0], input_vals[1], output_val, padding=self.padding, stride=self.stride, stream=stream_handle)
  137. def gradient(self, output_grad):
  138. raise NotImplementedError
  139. def infer_shape(self, input_shapes):
  140. assert len(input_shapes) == 2
  141. N = input_shapes[1][0]
  142. C = input_shapes[0][1]
  143. H = (input_shapes[1][2] - 1) * self.stride + \
  144. input_shapes[0][2] - 2 * self.padding
  145. W = (input_shapes[1][3] - 1) * self.stride + \
  146. input_shapes[0][3] - 2 * self.padding
  147. return (N, C, H, W)
  148. class Conv2d_Gradient_of_FilterOp(Op):
  149. # nodeA : input_x nodeB : gradient_Y
  150. def __init__(self, input_X, gradient_Y, padding=0, stride=1, ctx=None):
  151. super().__init__(Conv2d_Gradient_of_FilterOp,
  152. [input_X, gradient_Y], ctx)
  153. self.padding = padding
  154. self.stride = stride
  155. def im2col(self, X, filter_H, filter_W, padding, stride):
  156. N, C, H, W = X.shape
  157. assert (H + 2 * padding - filter_H) % stride == 0
  158. assert (W + 2 * padding - filter_W) % stride == 0
  159. out_H = (H + 2 * padding - filter_H) // stride + 1
  160. out_W = (W + 2 * padding - filter_W) // stride + 1
  161. y_row_size = C * filter_H * filter_W
  162. y_col_size = out_H * out_W
  163. y_shape = (N, y_row_size, y_col_size)
  164. Y = np.empty(y_shape, dtype=X.dtype)
  165. for batch_index in range(N):
  166. for col_index in range(y_col_size):
  167. out_y = col_index // out_W
  168. out_x = col_index % out_W
  169. in_y = out_y * stride - padding
  170. in_x = out_x * stride - padding
  171. row_idx = 0
  172. for c in range(0, C):
  173. for y in range(in_y, in_y + filter_H):
  174. for x in range(in_x, in_x + filter_W):
  175. if (x < 0 or x >= W or y < 0 or y >= H):
  176. Y[batch_index, row_idx, col_index] = 0
  177. else:
  178. Y[batch_index, row_idx,
  179. col_index] = X[batch_index, c, y, x]
  180. row_idx += 1
  181. return Y
  182. def np_Conv2dGradient_Filter(self, filter_outChannel, filter_inChannel, filter_H, filter_W, X, Y, padding=0, stride=1):
  183. """Implement a conv2d_transpose as a matrix multiply after im2col."""
  184. X_N, X_C, X_H, X_W = X.shape
  185. Y_N, Y_C, Y_H, Y_W = Y.shape
  186. YY = Y.reshape((Y_N, Y_C, Y_H * Y_W)) # transformed to im2col Y
  187. # XX = X.reshape((X_N, X_C, X_W * X_H)) # transformed to im2col X
  188. im2col_XX = self.im2col(X, filter_H, filter_W, padding, stride)
  189. gradient_filter = np.zeros(shape=(
  190. filter_outChannel, filter_inChannel * filter_H * filter_W), dtype=Y.dtype)
  191. for i in range(X_N):
  192. gradient_filter += np.matmul(YY[i], im2col_XX[i].T)
  193. gradient_filter = gradient_filter.reshape(
  194. (filter_outChannel, filter_inChannel, filter_H, filter_W))
  195. return gradient_filter
  196. def compute(self, input_vals, output_val, stream_handle=None):
  197. if self.on_cpu:
  198. if DNNL_LIB['DnnlConv2d_Gradient_of_Filter']:
  199. cpu_conv2d_gradient_of_filter(
  200. input_vals[0], input_vals[1], output_val, self.padding, self.stride)
  201. else:
  202. f_N = input_vals[1].shape[1]
  203. f_C = input_vals[0].shape[1]
  204. f_H = input_vals[1].shape[2] + 2 * self.padding - \
  205. (input_vals[1].shape[2] - 1) * self.stride
  206. f_W = input_vals[1].shape[3] + 2 * self.padding - \
  207. (input_vals[1].shape[3] - 1) * self.stride
  208. output_val[:] = self.np_Conv2dGradient_Filter(
  209. f_N, f_C, f_H, f_W, input_vals[0].asnumpy(), input_vals[1].asnumpy(), padding=self.padding, stride=self.stride)
  210. else:
  211. CuDNN_conv2d_gradient_of_filter(
  212. input_vals[0], input_vals[1], output_val, padding=self.padding, stride=self.stride, stream=stream_handle)
  213. def gradient(self, output_grad):
  214. raise NotImplementedError
  215. def infer_shape(self, input_shapes):
  216. assert len(input_shapes) == 2
  217. f_N = input_shapes[1][1]
  218. f_C = input_shapes[0][1]
  219. f_H = input_shapes[0][2] + 2 * self.padding - \
  220. (input_shapes[1][2] - 1) * self.stride
  221. f_W = input_shapes[0][3] + 2 * self.padding - \
  222. (input_shapes[1][3] - 1) * self.stride
  223. return (f_N, f_C, f_H, f_W)
  224. def conv2d_op(node_A, node_B, padding=0, stride=1, ctx=None):
  225. """Conv2d node.
  226. Parameters:
  227. ----
  228. node_A : Node
  229. Input data node.
  230. node_B : Node
  231. Input filter node.
  232. padding :
  233. Padding size.
  234. stride :
  235. Stride size.
  236. Returns:
  237. ----
  238. A new Node instance created by Op.
  239. """
  240. return Conv2dOp(node_A, node_B, padding, stride, ctx=ctx)
  241. def conv2d_gradient_of_data_op(node_A, node_B, padding=0, stride=1, ctx=None):
  242. """Gradient node of data of conv2d.
  243. Parameters:
  244. ----
  245. node_A : Node
  246. Filter node.
  247. node_B : Node
  248. Previous gradient node.
  249. padding :
  250. Padding size.
  251. stride :
  252. Stride size.
  253. Returns:
  254. ----
  255. A new Node instance created by Op.
  256. """
  257. return Conv2d_Gradient_of_DataOp(node_A, node_B, padding, stride, ctx=ctx)
  258. def conv2d_gradient_of_filter_op(input_X, gradient_Y, padding=0, stride=1, ctx=None):
  259. """Gradient node of filters of conv2d.
  260. Parameters:
  261. ----
  262. input_X :
  263. Input data of conv2d.
  264. gradient_Y :
  265. Gradient array.
  266. padding :
  267. Padding size.
  268. stride :
  269. Stride size.
  270. Returns:
  271. ----
  272. A new Node instance created by Op.
  273. """
  274. return Conv2d_Gradient_of_FilterOp(input_X, gradient_Y, padding, stride, ctx=ctx)