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에 맞는 명령어를 터미널에 입력하면 설치가 완료된다. 자세한 설치 방법은 아래 링크를 참고하면 된다.

links : https://github.com/lutzroeder/netron#install

[Linux]
> snap install netron
[Window]
> winget install -s winget netron

설치가 완료되면, 다음과 같이 Netron으로 모델을 열어볼 수 있게 된다.



3. ONNX2TF

3-1. Install

변환은, 위에서 언급했던 문제를 중심적으로 다루고 있는 PINTO0309의 onnx2tf라는 라이브러리를 사용한다. 개발 환경을 구축하는 방법에 대해서는 아래 도큐먼트에 자세히 설명되어 있으니 참고하면 된다.

link : https://github.com/PINTO0309/onnx2tf#environment


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이며, 78317808을 input으로 받아 7832를 output으로 내보낸다. 정황상 78317808은 각각 바로 앞단의 LogSlice의 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이 되는지 확인해 볼 수 있다.

Untitled


3-5. Result

결과는 onnx2tf/saved_model 하위에 저장된다. onnx -> tf, tflite로 한꺼번에 변환을 해주는 것 같다.

댓글남기기