New TensorFlow `TFDWConv()` module (#7824)
Browse files* New TensorFlow `TFDWConv()` module
Analog to DWConv() module:
https://github.com/ultralytics/yolov5/blob/8aa2085a7e7ae20a17a7548edefbdb2960f2b29b/models/common.py#L53-L57
* Fix and new activations() function
* Update tf.py
- models/tf.py +41 -18
models/tf.py
CHANGED
@@ -70,25 +70,38 @@ class TFConv(keras.layers.Layer):
|
|
70 |
# see https://stackoverflow.com/questions/52975843/comparing-conv2d-with-padding-between-tensorflow-and-pytorch
|
71 |
|
72 |
conv = keras.layers.Conv2D(
|
73 |
-
c2,
|
74 |
-
k,
|
75 |
-
s,
|
76 |
-
'SAME' if s == 1 else 'VALID',
|
77 |
-
use_bias=
|
78 |
kernel_initializer=keras.initializers.Constant(w.conv.weight.permute(2, 3, 1, 0).numpy()),
|
79 |
bias_initializer='zeros' if hasattr(w, 'bn') else keras.initializers.Constant(w.conv.bias.numpy()))
|
80 |
self.conv = conv if s == 1 else keras.Sequential([TFPad(autopad(k, p)), conv])
|
81 |
self.bn = TFBN(w.bn) if hasattr(w, 'bn') else tf.identity
|
|
|
|
|
|
|
|
|
82 |
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
|
93 |
def call(self, inputs):
|
94 |
return self.act(self.bn(self.conv(inputs)))
|
@@ -103,10 +116,8 @@ class TFFocus(keras.layers.Layer):
|
|
103 |
|
104 |
def call(self, inputs): # x(b,w,h,c) -> y(b,w/2,h/2,4c)
|
105 |
# inputs = inputs / 255 # normalize 0-255 to 0-1
|
106 |
-
|
107 |
-
|
108 |
-
[inputs[:, ::2, ::2, :], inputs[:, 1::2, ::2, :], inputs[:, ::2, 1::2, :], inputs[:, 1::2, 1::2, :]],
|
109 |
-
3))
|
110 |
|
111 |
|
112 |
class TFBottleneck(keras.layers.Layer):
|
@@ -439,6 +450,18 @@ class AgnosticNMS(keras.layers.Layer):
|
|
439 |
return padded_boxes, padded_scores, padded_classes, valid_detections
|
440 |
|
441 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
442 |
def representative_dataset_gen(dataset, ncalib=100):
|
443 |
# Representative dataset generator for use with converter.representative_dataset, returns a generator of np arrays
|
444 |
for n, (path, img, im0s, vid_cap, string) in enumerate(dataset):
|
|
|
70 |
# see https://stackoverflow.com/questions/52975843/comparing-conv2d-with-padding-between-tensorflow-and-pytorch
|
71 |
|
72 |
conv = keras.layers.Conv2D(
|
73 |
+
filters=c2,
|
74 |
+
kernel_size=k,
|
75 |
+
strides=s,
|
76 |
+
padding='SAME' if s == 1 else 'VALID',
|
77 |
+
use_bias=not hasattr(w, 'bn'),
|
78 |
kernel_initializer=keras.initializers.Constant(w.conv.weight.permute(2, 3, 1, 0).numpy()),
|
79 |
bias_initializer='zeros' if hasattr(w, 'bn') else keras.initializers.Constant(w.conv.bias.numpy()))
|
80 |
self.conv = conv if s == 1 else keras.Sequential([TFPad(autopad(k, p)), conv])
|
81 |
self.bn = TFBN(w.bn) if hasattr(w, 'bn') else tf.identity
|
82 |
+
self.act = activations(w.act) if act else tf.identity
|
83 |
+
|
84 |
+
def call(self, inputs):
|
85 |
+
return self.act(self.bn(self.conv(inputs)))
|
86 |
|
87 |
+
|
88 |
+
class TFDWConv(keras.layers.Layer):
|
89 |
+
# Depthwise convolution
|
90 |
+
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, w=None):
|
91 |
+
# ch_in, ch_out, weights, kernel, stride, padding, groups
|
92 |
+
super().__init__()
|
93 |
+
assert isinstance(k, int), "Convolution with multiple kernels are not allowed."
|
94 |
+
|
95 |
+
conv = keras.layers.DepthwiseConv2D(
|
96 |
+
kernel_size=k,
|
97 |
+
strides=s,
|
98 |
+
padding='SAME' if s == 1 else 'VALID',
|
99 |
+
use_bias=not hasattr(w, 'bn'),
|
100 |
+
kernel_initializer=keras.initializers.Constant(w.conv.weight.permute(2, 3, 1, 0).numpy()),
|
101 |
+
bias_initializer='zeros' if hasattr(w, 'bn') else keras.initializers.Constant(w.conv.bias.numpy()))
|
102 |
+
self.conv = conv if s == 1 else keras.Sequential([TFPad(autopad(k, p)), conv])
|
103 |
+
self.bn = TFBN(w.bn) if hasattr(w, 'bn') else tf.identity
|
104 |
+
self.act = activations(w.act) if act else tf.identity
|
105 |
|
106 |
def call(self, inputs):
|
107 |
return self.act(self.bn(self.conv(inputs)))
|
|
|
116 |
|
117 |
def call(self, inputs): # x(b,w,h,c) -> y(b,w/2,h/2,4c)
|
118 |
# inputs = inputs / 255 # normalize 0-255 to 0-1
|
119 |
+
inputs = [inputs[:, ::2, ::2, :], inputs[:, 1::2, ::2, :], inputs[:, ::2, 1::2, :], inputs[:, 1::2, 1::2, :]]
|
120 |
+
return self.conv(tf.concat(inputs, 3))
|
|
|
|
|
121 |
|
122 |
|
123 |
class TFBottleneck(keras.layers.Layer):
|
|
|
450 |
return padded_boxes, padded_scores, padded_classes, valid_detections
|
451 |
|
452 |
|
453 |
+
def activations(act=nn.SiLU):
|
454 |
+
# Returns TF activation from input PyTorch activation
|
455 |
+
if isinstance(act, nn.LeakyReLU):
|
456 |
+
return lambda x: keras.activations.relu(x, alpha=0.1)
|
457 |
+
elif isinstance(act, nn.Hardswish):
|
458 |
+
return lambda x: x * tf.nn.relu6(x + 3) * 0.166666667
|
459 |
+
elif isinstance(act, (nn.SiLU, SiLU)):
|
460 |
+
return lambda x: keras.activations.swish(x)
|
461 |
+
else:
|
462 |
+
raise Exception(f'no matching TensorFlow activation found for PyTorch activation {act}')
|
463 |
+
|
464 |
+
|
465 |
def representative_dataset_gen(dataset, ncalib=100):
|
466 |
# Representative dataset generator for use with converter.representative_dataset, returns a generator of np arrays
|
467 |
for n, (path, img, im0s, vid_cap, string) in enumerate(dataset):
|