Unity/Barracuda & Sentis

[Unity Sentis] 유니티 센티스 텐서(Tensor)에 대한 이해

pnltoen 2023. 11. 22.
반응형

Sentis Tensor

Unity Technologies


서론

 

센티스로 다른 모델을 적용해보고 있습니다. 그 과정에서 유니티에서 텐서를 조작하는 과정이 익숙하지 않아 포스팅을 남깁니다. 본 포스팅은 기본적으로 Unity의 Sentis 1.3.0 버전의 문서를 베이스로 작성되었습니다. 패키지가 업데이트 되는 과정에서 Class 및 함수명이 변경될 수 있음을 말씀드립니다. 

 

Sentis overview | Sentis | 1.3.0-pre.1

Sentis overview This is an experimental package in an open beta program available to Unity users via the package manager. The features and documentation might change before it is verified for release, so it is not ready for production use. Visit the Unity

docs.unity3d.com

 

본문

 

기본적으로 텐서에 대한 개념은 다른 문서 또는 Sentis/Tensor Fundamentals in Sentis를 참고해보시길 바랍니다.

 

Copyright : Unity Sentis Documentation

 

Tensor fundamentals in Sentis | Sentis | 1.3.0-pre.1

Tensor fundamentals in Sentis In Sentis, you input and output data in multi-dimensional arrays called tensors. Tensors in Sentis work similarly to tensors in TensorFlow, PyTorch, and other machine learning frameworks. Tensors in Sentis can have up to 8 dim

docs.unity3d.com

 

Tensor의 선언

 

Sentis에서 Tensor는 갖고 있는 데이터 타입에 따라 TensorInt, TensorFloat로 나뉩니다. 각각을 선언하기 위해서는 각 텐서의 형태와 데이터에 대해서 선언해야 합니다.

 

    void Start()
    {
        TensorShape _shape = new TensorShape(1, 2, 5);
        float[] _floatData = new float[] { 1.1f, 2.1f, 2.2f, 3.1f, 3.2f, 3.3f, 4.1f, 4.2f, 4.3f, 4.4f };
        TensorFloat _test = new TensorFloat(_shape, _floatData);
    }

 

위와 같이 TensorShape를 통해 1x2x5 총 10개의 데이터를 담을 수 있는 TensorShape을 _shape 변수명으로 선언하였습니다. 이 후 float[]를 통해 실수 데이터 값을 넣을 수 있는 array를 선언 후 _test 변수명으로 생성한 TensorFloat에 담아주었습니다.

 

선언한 텐서를 출력하기에 앞서, 텐서의 값에 접근할 수 있도록 바꿔줘야 합니다. MakeReadable() 함수를 호출할 경우 CPU에 올려서 이를 진행합니다.

    void Start()
    {
        TensorShape _shape = new TensorShape(1, 2, 5);
        float[] _floatData = new float[] { 1.1f, 2.1f, 2.2f, 3.1f, 3.2f, 3.3f, 4.1f, 4.2f, 4.3f, 4.4f };
        TensorFloat _test = new TensorFloat(_shape, _floatData);
        print("TensorFloat's shape : " + _test.shape); // Shape 출력 = 1,2,5
        print(_test[3]); // 4번 째 인덱스 출력 = 3.1
        _test.PrintDataPart(5); // 5개의 data 값 출력 1.1, 2.1, 2.2, 3.1, 3.2
    }

 

각각의 결과는 주석과 같이 표기됩니다. 마찬가지로 TensorInt 또한 동일하게 동작합니다.

    void Start()
    {
        TensorShape _shape = new TensorShape(1, 2, 5);
        int[] _intData = new int[] { 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 };
        TensorInt _test = new TensorInt(_shape, _intData);
        print("TensorFloat's shape : " + _test.shape); // Shape 출력 = 1,2,5
        print(_test[3]); // 4번 째 인덱스 출력 = 3
        _test.PrintDataPart(5); // 5개의 data 값 출력 1,2,2,3,3
    }

 

Sentis GPU || CPU 설정

 

기본적으로 이러한 방식으로 텐서를 선언할 경우 Sentis는 CPU를 사용합니다. 이를 Device Type을 통해 확인해보도록 하겠습니다.

    void Start()
    {
        Debug.Log("Device Type : " + _test.tensorOnDevice.deviceType); //CPU
    }

 

 Sentis에서는 Pin 함수를 통해 모델을 excute()하지 않더라도 텐서를 cpu 그리고 gpu에 올릴 수 있도록 지원합니다.

 

물론 이와 같은 Pin 방식이 아니더라도 Sentis 모델을 Execute() 할 때 backendtype을 설정할 수 있습니다. 관련된 내용은 공식 문서에 쉽게 설명되어 있으니 문서(Run an imported model)를 확인해보시는 것을 추천드립니다.

 

Get output from a model | Sentis | 1.3.0-pre.1

Get output from a model Get the tensor output Use PeekOutput to access the output of the tensor. PeekOutput returns a Tensor object, so you usually need to cast it to a TensorFloat or a TensorInt. For example: worker.Execute(inputTensor); TensorFloat outpu

docs.unity3d.com

 

Copyright : Sentis Documentation

 

Reshape

Sentis에서 Reshape과 관련해서 크게 2가지 즉, ShallowReshape과 onnx에서 사용하는 operators를 지원합니다.

 

ShallowReshape

 

Sentis는 직접적인 Tensor 수정이 불가능합니다. 따라서 기존의 데이터를 복사하고 이를 새로운 Shape에 넣어주는 함수입니다. Reshaoe과 관련해서 가장 많이 쓰이는 예시로는 파이토치에서의 ACHW 그리고 유니티 센티스의 ACWH 차이가 있습니다. 파이토치는 일반적으로 높이x너비를 사용하는 반면 센티스는 너비x높이순으로 출력을 수정해야 추 후 RenderTexture에 넣을 수 있습니다.

 

다시 첫번째 예제로 돌아오도록 하겠습니다.

 

    void Start()
    {
        TensorShape _shape = new TensorShape(1, 2, 5);
        TensorShape _reshape = new TensorShape(1, 5, 2);
        int[] _intData = new int[] { 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 };
        TensorInt _test = new TensorInt(_shape, _intData);
        TensorInt _reshapedtest = _test.ShallowReshape(_reshape) as TensorInt;
        print("ShallowReshaped tensor's shape : " + _reshapedtest.shape); // Shape 출력 = 1,5,2
    }

 

Ops.transpose

 

    void Start()
    {
        TensorShape _shape = new TensorShape(1, 2, 5);
        int[] _intData = new int[] { 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 };
        TensorInt _test = new TensorInt(_shape, _intData);
        allocator = new TensorCachingAllocator();
        ops = WorkerFactory.CreateOps(BackendType.GPUCompute, allocator);
        TensorInt _transposedTest = ops.Transpose(_test, new int[] {0, 2, 1});
        print("Transposed tensor's shape : " + _transposedTest.shape); // Shape 출력 = 1,5,2
    }

 

값을 한번 읽어 보려고 동일하게 print를 사용하면 아래의 오류가 나타나게 됩니다. 이를 위해서는 MakeReadable() 함수를 실행해야합니다. 이 단계에서 GPU에서 처리된 텐서는 CPU로 내려오게 됩니다.

 

InvalidOperationException: Tensor data cannot be read from, use .MakeReadable() to allow reading from tensor.

 

    void Start()
    {
        TensorShape _shape = new TensorShape(1, 2, 5);
        //TensorShape _reshape = new TensorShape(1, 5, 2);
        int[] _intData = new int[] { 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 };
        TensorInt _test = new TensorInt(_shape, _intData);
        allocator = new TensorCachingAllocator();
        ops = WorkerFactory.CreateOps(BackendType.GPUCompute, allocator);
        TensorInt _transposedTest = ops.Transpose(_test, new int[] {0, 2, 1});
        print("Transposed tensor's shape : " + _transposedTest.shape); // Shape 출력 = 1,5,2
        Debug.Log("_transposed's Device Type (After making as readable) : " + _transposedTest.tensorOnDevice.deviceType); //GPU
        _transposedTest.MakeReadable();
        Debug.Log("_transposed's Device Type (After making as readable) : " + _transposedTest.tensorOnDevice.deviceType); //CPU
        _transposedTest.PrintDataPart(10); //InvalidOperationException: Tensor data cannot be read from, use .MakeReadable() to allow reading from tensor.
    }

 

에디터에서는 다음과 같이 정상적으로 출력되는 것을 확인하실 수 있습니다.

 

 

이 외에 센티스에서 지원하는 operator의 경우 공식문서 (Supported ONNX operators)를 통해 확인하실 수 있습니다.

 

Supported ONNX operators | Sentis | 1.3.0-pre.1

Supported ONNX operators The table shows which Open Neural Network Exchange (ONNX) operators Sentis supports, and which data types Sentis supports for each back end type. When you import a model, each ONNX operator in the model graph becomes a Sentis layer

docs.unity3d.com

 

TextureConverter

텐서를 Texture2D 또는 RenderTexture로 그리고 Texture2D 또는 RenderTexture를 텐서로 변환해주는 클래스입니다. 이 후 ToTensor 그리고 RenderToTexture 함수를 사용해서 각각 텐서로 그리고 텍스처로 변환해줄 수 있습니다.

 

        FloatTensor t_content = TextureConverter.ToTensor(_content, 800, 800, 3);
        TensorFloat t_output = _Engine.Execute(t_content).PeekOutput() as TensorFloat;
        TensorShape _newShape = new TensorShape(1, 19, 400, 400);
        TensorFloat _reshapedoutput = t_output.ShallowReshape(_newShape) as TensorFloat;
        TextureConverter.RenderToTexture(_reshapedoutput, _result);

 

위는 예시 코드입니다. _content에 Texture2D 또는 RenderTexture를 넣고 [width, height, channel] 값을 조절합니다. 위의 코드에서는 각각 800, 800, 3으로 조정하였습니다.

 

이 후 _Engine.execute(t_content).PeekOutput()를 통해 Tensor로 output을 얻고 as TensorFloat를 통해 TensorFloat로 캐스팅합니다. 마찬가지로 Output의 shape을 설정하고 TextureConverter.RenderToTexture를 통해 이미지로 내보냅니다.

 

해당 부분에서 사용한 모델은 다음과 같습니다.

 

(좌측 이미지) Input shape, (우측 이미지) Output shape

 

결론

지금까지 유니티 센티스에서 텐서의 개념 및 기본적인 활용법에 대해 알아보았습니다. Tensorflow 및 Pytorch와 비슷한 내용이 많아서 이해자체는 어렵지 않게 될 것으로 생각이 됩니다. 이 후로는 파이토치 기본기를 더 쌓고 몇몇 모델의 Inference 하는 방법에 대해 알아보고자 합니다.

반응형

댓글