1. ONNX(NCHW)와 TFLite(NHWC)간의 Fomat문제
ONNX는 NCHW(채널, 높이, 너비) 형식의 이미지 데이터 포맷을 사용한다. 반면, TensorFlow Lite(TFLite)는 NHWC(높이, 너비, 채널) 형식의 이미지 데이터 포맷을 사용한다. 이러한 format 차이로 인해 onnx to tflite 변환 시 format issue가 발생한다. 이런 경우 직접 문제가 발생하는 layer를 찾아 shape을 수정해줘야만 한다. 본 포스팅에서는 onnx2tf
를 사용하여 해당 문제를 해결하는 방법에 대해 소개한다.
2. NETRON
onnx2tf를 소개하기에 앞서, onnx 모델의 사각화를 위해 NETRON
을 설치해준다. 본인의 컴퓨터 os에 맞는 명령어를 터미널에 입력하면 설치가 완료된다. 자세한 설치 방법은 아래 링크를 참고하면 된다.
> snap install netron
> winget install -s winget netron
설치가 완료되면, 다음과 같이 Netron으로 모델을 열어볼 수 있게 된다.
3. ONNX2TF
3-1. Install
변환은, 위에서 언급했던 문제를 중심적으로 다루고 있는 PINTO0309의 onnx2tf
라는 라이브러리를 사용한다. 개발 환경을 구축하는 방법에 대해서는 아래 도큐먼트에 자세히 설명되어 있으니 참고하면 된다.
3-2. Convert ONNX to TF, TFlite
터미널에 아래 명령어를 입력함으로써 간단하게 변환할 수 있다. 그러나 ONNX와 TFlite간의 shape 문제때문에 높은 확률로 에러가 발생한다. 해당 라이브러리는 어떤 레이어에서 문제가 발생했는지 쉽게 확인할 수 있기 때문에, 확인하고 수정해주면 된다.
> onnx2tf -i your_model.onnx
3-3. Parameter Replacement
다음은 error log의 일부이다. 현재 ADD_7271
OP에서 shape 문제가 발생했으며, input(1, 300, 2)과 output(1,2,300)의 shape이 달라 문제가 발생하고 있다는 내용이다. 이런 경우 -prf
옵션을 사용하여 해당 shape을 강제로 replace 해줌으로써 해당 문제를 해결할 수 있다. replace 정보는 json에 작성하여 -prf
의 인자로 넣어주면 된다.
Dimensions must be equal, but are 300 and 2 for '{{node tf.math.add_283/Add}} = AddV2[T=DT_FLOAT](Placeholder, tf.math.add_283/Add/y)' with input shapes: [1,300,2], [1,2,300].
Call arguments received by layer "tf.math.add_283" (type TFOpLambda):
• x=tf.Tensor(shape=(1, 300, 2), dtype=float32)
• y=tf.Tensor(shape=(1, 2, 300), dtype=float32)
• name='Add_7271'
3-3-1) OP 정보 파악
해당 문제가 발생한 OP의 정보를 제대로 파악하는 것이 중요하다. 우선, format 문제가 발생한 OP는 ADD_7271
이며, 7831
과 7808
을 input으로 받아 7832
를 output으로 내보낸다. 정황상 7831
과 7808
은 각각 바로 앞단의 Log
와 Slice
의 output이라는 것을 짐작할 수 있다. 따라서 바로 앞단 Slice Output의 name을 확인해준다(Log의 Output은 수정하지 않아도 됨).
예상대로 Slice_7270
의 output(x)이 ADD_7271
의 input(x)으로 들어가고 있었으며, 이 format이 ADD_7271
의 output(y)과 달라 에러가 발생했던 것이다. 따라서 Slice_7270
의 output인 7831
을 (1,2,300)으로 수정하면 된다는 것을 알 수 있다.
또한, 같은 단에 동일한 문제를 일으키는 OP가 있을 경우, input과 output 정보를 모두 파악하여 수정해줘야 한다. 본 포스팅에서 변환하고자 하는 model의 경우에는, 다음과 같이 6개의 slice op에서 문제가 발생하였다.
3-3-2) json 작성
json은 다음과 같은 양식으로 작성해주면 된다.
{
"format_version": 1,
"operations": [
{
"op_name": "Slice_7270",
"param_target": "outputs",
"param_name": "7831",
"post_process_transpose_perm": [0,2,1]
},
{
"op_name": "Slice_7402",
"param_target": "outputs",
"param_name": "7965",
"post_process_transpose_perm": [0,2,1]
},
{
"op_name": "Slice_7534",
"param_target": "outputs",
"param_name": "8099",
"post_process_transpose_perm": [0,2,1]
},
{
"op_name": "Slice_7666",
"param_target": "outputs",
"param_name": "8233",
"post_process_transpose_perm": [0,2,1]
},
{
"op_name": "Slice_7798",
"param_target": "outputs",
"param_name": "8367",
"post_process_transpose_perm": [0,2,1]
},
{
"op_name": "Slice_7930",
"param_target": "outputs",
"param_name": "8501",
"post_process_transpose_perm": [0,2,1]
}
]
}
-
op_name
변환하고자 하는 op의 이름
-
param_target
변환하고자 하는 parameter의 종류를 입력하면 된다(inputs, outputs, attributes 등등)
-
param_target
Netron에서 확인한 name을 입력한다.
-
post_process_transpose_perm
해당 옵션은 본인의 OP type에 맞게 입력해야 하며, 관련 내용은 공식 도큐먼트(
Parameter replacement>Replacement Supported OPs>See list of replacement specifications
)를 참고하면 된다. np.transpose처럼 바꾸고 싶은 값의 index를 입력하여 변환하면 된다.
3-3-3) convert
> onnx2tf -i your_model.onnx -prf your_replace_json.json
3-4. Accuracy check
다음 명령어로 변환된 tf와 onnx간의 output matching도 해볼 수 있다.
> onnx2tf -i your_model.onnx -prf your_replace_json.json -ois input:1,3,640,640(your shape) -cotof -cotoa 1e-1
다음과 같이 output이 변환 전후로 matching이 되는지 확인해 볼 수 있다.
3-5. Result
결과는 onnx2tf/saved_model
하위에 저장된다. onnx -> tf, tflite로 한꺼번에 변환을 해주는 것 같다.
댓글남기기