Closed taisuke-tomida closed 3 years ago
Fixed a bug in NonZero
, and it now works properly until NonMaxSuppresion
is processed. This is just a quick fix, so I haven't committed it yet.
### NonZero
elif layer.attrib['type'] == 'NonZero':
output_type = tf.int64
if not data is None and 'output_type' in data.attrib:
output_type = cast_type_ov_tf[data.attrib['output_type']]
try:
# type_spec.dtype
if tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)].type_spec.dtype != tf.bool:
input_type = tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)].type_spec.dtype
mask = tf.math.not_equal(
tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)],
tf.constant([0], dtype=input_type)
)
tf_layers_dict[layer_id] = tf.expand_dims(
tf.boolean_mask(tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)], mask),
axis=0
)
else:
temp_op = tf.cast(tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)], output_type)
mask = tf.math.not_equal(
temp_op,
tf.constant([0], dtype=output_type)
)
tf_layers_dict[layer_id] = tf.expand_dims(
tf.boolean_mask(temp_op, mask),
axis=0
)
except:
# dtype
if tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)].dtype != tf.bool:
input_type = tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)].dtype
mask = tf.math.not_equal(
tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)],
tf.constant([0], dtype=input_type)
)
tf_layers_dict[layer_id] = tf.expand_dims(
tf.boolean_mask(tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)], mask),
axis=0
)
else:
temp_op = tf.cast(tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)], output_type)
mask = tf.math.not_equal(
temp_op,
tf.constant([0], dtype=output_type)
)
tf_layers_dict[layer_id] = tf.expand_dims(
tf.boolean_mask(temp_op, mask),
axis=0
)
There is a big problem here. The result of NonZero
should exactly be a tensor of indefinite size, but OpenVINO appears to produce a fixed size output. The behavior specified in the specification does not match the specification of the actually generated model.
True = 1
False = 0
For example, NonZero( [True, False, False, True] )
should be [1, 2]
, but it produces output of size [1, 4]
. In the first place, the number of True's is not constant, so the size can be all combinations of 0 or 1 or 2 or 3 or 4. The figure below suggests that NonZero( [19248 of True/False] )
results in [1, 19248]
. However, the number of non-zero values should not be constant, so there is a problem with the specification. The NonZero
result has an almost 100% probability of not returning 19248 values. It is inevitable that you will be confused. This is because the structure of the model you see does not match the internal workings of the framework.
https://docs.openvinotoolkit.org/latest/openvino_docs_ops_condition_NonZero_3.html
As a result, TensorFlow is unable to predict the size of the tensor to be passed to NMS, resulting in the error shown in the figure below. scores.shape[0]
is the scores parameter of NMS. NMS requires score, boxes, and class probabilities with matching dimensions, but because the number of dimensions is undefined, errors cannot be avoided in the subsequent transformation process.
ERROR: list index out of range
ERROR: model_path : openvino/yolact_detect_pytorchnms11/550x550/FP32/yolact_detect_pytorchnms11.xml
ERROR: weights_path: openvino/yolact_detect_pytorchnms11/550x550/FP32/yolact_detect_pytorchnms11.bin
ERROR: layer_id : 707
ERROR: The trace log is below.
Traceback (most recent call last):
File "openvino2tensorflow.py", line 2098, in convert
class_count = scores.shape[0]
File "/usr/local/lib/python3.8/dist-packages/tensorflow/python/framework/tensor_shape.py", line 896, in __getitem__
return self._dims[key].value
IndexError: list index out of range
Do you understand what I'm saying? This is the reason why I told you in my email that I don't want you to use NonZero
a lot if possible. The post-processing implemented in PyTorch uses a lot of NonZero
to mass produce processes with indeterminate tensor sizes, which is why I have not actively implemented NMS. This is because TensorFlow's default processing cannot handle it.
Therefore, you really need to consider whether you need to do the following,
keep = (conf_scores > self.conf_thresh)
and
id = (conf_scores > self.conf_thresh)
By the way, I don't think it's necessary.
Thanks. @PINTO0309
If it says [True, False, False, True]
, it means you have to count only True.
The score received by NMS is supposed to be variable, but it is received as [19248]. Indeed, it is not right. I understood.
Therefore, you really need to consider whether you need to do the following,
As I told you a little bit in my email, I was trying to figure out how to implement NMS without using Boolean. The changes are as follows
Change the detect
function in detection.py.
The default is to use a threshold to remove candidates before passing them to NMS.
keep = (conf_scores > self.conf_thresh)
This is the process. Because of this process, Booleans were being mass produced. In addition, the NMS function also uses a threshold value to eliminate candidates in the same way. So I removed and changed all of that process.
def detect(self, batch_idx, conf_preds, decoded_boxes, mask_data, inst_data):
""" Perform nms for only the max scoring class that isn't background (class 0) """
cur_scores = conf_preds[batch_idx, 1:, :] # [80, 19248]
conf_scores, _ = torch.max(cur_scores, dim=0) # 列方向の最大値 [19248]
masks4pynms = mask_data[batch_idx, :, :]
boxes, masks, classes, scores = self.pytorch_nms(decoded_boxes, masks4pynms, conf_scores, cur_scores, self.nms_thresh, self.top_k)
return {'box': boxes, 'mask': masks, 'class': classes, 'score': scores}
The NMS functions are
def pytorch_nms(self, decode_boxes, masks, conf_scores, scores, iou_threshold:float=0.2, top_k:int=200):
print("use pytorch nms")
scores, classes = scores.max(dim=0)
keep = torchvision.ops.nms(boxes=decode_boxes, scores=conf_scores, iou_threshold=iou_threshold)
return decode_boxes[keep], masks[keep], classes[keep], scores[keep]
It's pretty heavy-handed, but I made sure to pass all candidates to torchvision.ops.nms. I have attached the converted model (ONNX) to the email. With my understanding, I can only deal with this so much. Sorry.
Thanks.
Don't worry about it. I will respond again at midnight. Thank you for the perfect issue. :smile_cat:
Please. :pray:
Only some type conversion bugs in NonZero
and NonMaxSuppression
have been fixed and versioned. Thanks to the sample you provided, the conversion is now successful up to NonMaxSuppression
.
Commit: https://github.com/PINTO0309/openvino2tensorflow/commit/177bdaa52e141356e47435846fe1a265894b600f
https://github.com/PINTO0309/openvino2tensorflow/releases/tag/v1.15.5
At the moment, it is designed to remove the process after NonMaxSuppression
, which causes an error in Gather
immediately afterwards. The fundamental tool specification after the NonMaxSuppression
conversion process needs to be revised significantly, so it will take a very long time to fix.
$ docker run -it --rm \
-v `pwd`:/home/user/workdir \
pinto0309/openvino2tensorflow:latest
$ cd workdir
$ MODEL=yolact_detect_pytorchnms11_non_boolean
$ H=550
$ W=550
$ python3 -m onnxsim ${MODEL}.onnx ${MODEL}_opt.onnx
$ $INTEL_OPENVINO_DIR/deployment_tools/model_optimizer/mo.py \
--input_model ${MODEL}_opt.onnx \
--data_type FP32 \
--output_dir openvino/${MODEL}/${H}x${W}/FP32
$ openvino2tensorflow \
--model_path openvino/${MODEL}/${H}x${W}/FP32/${MODEL}_opt.xml \
--output_saved_model \
--output_pb \
--output_no_quant_float32_tflite
I'm sorry to keep you waiting.
Thanks!! @PINTO0309 I think it's a huge step forward that the NMS conversion worked out so well! Thank you very much for your prompt response.
Finally, I have one question.
If you can accept the value, then I should be able to handle it!
Thanks
Now, I force the three NMS outputs, selected_boxes
, selected_class_idx
, and selected_scores
, to the fabricated outputs with layer IDs 99990, 99991, and 99992, respectively. This needs to be modified to connect to Gather
and other layers according to the connection information of the model being converted. This is because I originally envisioned only the optimization of the object detection model for ease of use, so there was little need for processing after NMS.
https://github.com/PINTO0309/openvino2tensorflow/blob/3211a57785cf88007efc18ebf737945d439a0316/openvino2tensorflow/openvino2tensorflow.py#L2180-L2200
For that reason, in order to be extremely optimized for TensorFlow, the NMS conversion process is replaced by a huge number of layers with my own tricks. https://github.com/PINTO0309/openvino2tensorflow/blob/3211a57785cf88007efc18ebf737945d439a0316/openvino2tensorflow/openvino2tensorflow.py#L2087-L2200
The current program is still able to obtain the converted tensors successfully, but it ignores the connections after NMS, so the program needs to be modified significantly. It's probably impossible to handle with a little work-around.
P.S. I think it is possible to get the values by deleting all layers after NMS. @taisuke-tomida
Thank you for taking the time to help me with this issue.
I think it is possible to get the values by deleting all layers after NMS.
When you say delete this layer, do you mean delete it in the Pytorch implementation? So you are saying that the output of ONNX is the output of NMS?
There are three ways to do this.
keep = torchvision.ops.nms(boxes=decode_boxes, scores=conf_scores, iou_threshold=iou_threshold)
return decode_boxes[keep], masks[keep], classes[keep], scores[keep]
or
Just do one of the above and run the conversion again.
Thank you very much for your kind attention. I will start working on it as soon as possible.
Just to confirm, are the values we can get at the moment BBox, Scores, and masks?
interpreter = tf.lite.Interpreter("./yolact_pynms_float32.tflite")
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
input_name = input_details[0]['index']
output1_name = output_details[0]['index']
output2_name = output_details[1]['index']
output3_name = output_details[2]['index']
interpreter.set_tensor(input_name, batch)
start = time.time()
interpreter.invoke()
print("inference time:", time.time() - start)
output1 = interpreter.get_tensor(output1_name)
output2 = interpreter.get_tensor(output2_name)
output3 = interpreter.get_tensor(output3_name)
print(output1)
print(output2)
print(output3)
inference time: 1.8543763160705566
[[[ 0.27205527 0.5113899 0.39761117 0.72468936]
[ 0.87256825 0.0971102 0.9575784 0.31370932]
[ 0.5089483 0.70911205 0.62713844 0.8617991 ]
...
[-0.00865072 -0.00755513 0.036653 0.06734621]
[-0.00865072 -0.00755513 0.036653 0.06734621]
[-0.00865072 -0.00755513 0.036653 0.06734621]]]
[[0 0 0 ... 0 0 0]]
[[0.8754336 0.612168 0.58365595 ... 0. 0. 0. ]]
Maybe it's like this.
↓↓↓↓↓↓↓↓ boxes coordinates
[[[ 0.27205527 0.5113899 0.39761117 0.72468936]
[ 0.87256825 0.0971102 0.9575784 0.31370932]
[ 0.5089483 0.70911205 0.62713844 0.8617991 ]
...
[-0.00865072 -0.00755513 0.036653 0.06734621]
[-0.00865072 -0.00755513 0.036653 0.06734621]
[-0.00865072 -0.00755513 0.036653 0.06734621]]]
↓↓↓↓↓↓↓↓ class id
[[0 0 0 ... 0 0 0]]
↓↓↓↓↓↓↓↓ probability
[[0.8754336 0.612168 0.58365595 ... 0. 0. 0. ]]
It looks like Mask is not being obtained because the conversion program has ignored the connection information of other OUTPUTs. There should be five outputs.
output1_name = output_details[0]['index']
output2_name = output_details[1]['index']
output3_name = output_details[2]['index']
output3_name = output_details[3]['index']
output3_name = output_details[4]['index']
Maybe my conversion was bad, but there are only three outputs.... The model has not yet deleted any layers after NMS.
You misunderstand. It's just that my openvino2tensorflow is forcing me to remove the other outputs. This is a problem with the current specification of openvino2tensorflow.
So you actually have five outputs. However, I understand that the specification does not allow you to see all the output.
Due to a problem with the current specification of openvino2tensorflow, all outputs after the fourth one are removed. Therefore, the current output of three is the correct behavior.
tf_outputs.append(tf_layers_dict['99990'])
tf_outputs.append(tf_layers_dict['99991'])
tf_outputs.append(tf_layers_dict['99992'])
There should be five outputs.
This statement of mine is after I have modified the program so that all the conversion behavior you expect can be done successfully.
I understood. I will work on it so that I can receive the value I expect. Please let me know if there is anything else I can do for you.
Thanks @PINTO0309
Thank you very much for your patience. The entire process has been overhauled and now supports consolidation of processes after NMS. You can convert Yolact. https://github.com/PINTO0309/openvino2tensorflow/releases/tag/v1.19.4
However, there are some things to keep in mind.
Gather
in the final output layer destroys the output shape, it is necessary to extrapolate Transpose
or Reshape
just before the Gather
to maintain the shape.Convertion script sample.
$ docker run -it --rm \
-v `pwd`:/home/user/workdir \
pinto0309/openvino2tensorflow:latest
$ MODEL=yolact_detect_pytorchnms11_non_boolean_opt
$ openvino2tensorflow \
--model_path ${MODEL}.xml \
--output_saved_model \
--output_pb \
--output_no_quant_float32_tflite \
--weight_replacement_config replace.json
Check out this tutorial on how to extrapolate layers. https://github.com/PINTO0309/openvino2tensorflow/tree/v1.19.4#6-7-replace-weights-or-constant-values-in-const-op-and-add-transpose-or-reshape-or-cast-just-beforeafter-the-operation-specified-by-layer_id
After converting Yolact to Float32 tflite, the structure is as shown below.
Thank you very much. @PINTO0309
I haven't checked yet, but Thanks to this result, what was impossible became possible. I had to rely on him because of my lack of knowledge, but I didn't expect him to be so responsive.
I'll be back to find something no one else has ever done, so please do let me know when you do!
The purpose of the issue has been resolved, so I will close it once. If you have another issue, please post another issue.
I introduced NMS to yolact's ONNX model in my own unique way.
However, after converting to OpenVINO, I am unable to convert to tflite.
os etc
windows10 cuda10.2
Version
python=3.6.8 tensorflow=2.5.0 onnx=1.9.0 openvino2tensorflow=1.15.3 pytorch=1.8.1+cu102
How to add an NMS
out = []
with timer.env('Detect'): batch_size = 1 num_priors = 19248
return out
Error in openvino2tensorflow
command
result
thanks