본 아티클에서는 Unity 6와 Sentis를 통해 딥러닝 모델을 사용하는 방법을 알아봅니다. Part 1 에서는 기존에 학습된 모델을 선정하고 Unity Sentis로 임포트하여 사용하는 방법을 소개합니다. Part 2 에서는 Meta Quest 3 환경에서 XR Hands를 통해 인식된 손 제스처를 MLP(Multi-Layer Perceptron) 기반 Custom Classifier로 학습하고 활용하는 튜토리얼을 함께 제공합니다.
기존 딥러닝 모델의 추론환경
딥러닝 모델 학습 환경은 Python을 기반으로 하고, 모델의 학습과 추론을 위해서는 고성능 GPU가 탑재된 로컬 PC나 서버가 필수적입니다. 이렇게 학습된 모델을 다양한 플랫폼에 온디바이스 형태로 배포할 때는 제약이 따릅니다. 특히 모바일 기기, 게임 콘솔, 웹 브라우저 등 각 플랫폼마다 서로 다른 환경의 추론 코드를 구현해야 한다는 어려움이 있습니다. 물론 상용 API를 활용하는 방법도 있지만, 민감한 데이터가 외부 서버로 전송되어 처리되기 때문에 데이터 보안에 대한 우려가 있습니다. 이는 특히 개인정보나 기업의 기밀 데이터를 다룰 때 중요한 고려사항이 될 수 있습니다.
Sentis가 제공하는 On-Device 딥러닝 추론환경
Unity 6에서는 딥러닝 모델의 추론을 지원하는 패키지인 Sentis를 정식으로 제공합니다. Sentis는 표준 딥러닝 모델 포맷인 ONNX를 채택하여 다양한 프레임워크에서 개발된 모델들과의 호환성을 보장합니다. 특히 Sentis는 하나의 코드베이스로 데스크탑, 게임 콘솔, 모바일 기기, 웹 브라우저 등 다양한 플랫폼에 쉽게 배포할 수 있다는 장점을 가지고 있습니다. 더불어 Compute Shader와 Compute Buffer를 활용할 수 있어 GPU 가속을 통한 고성능 연산이 가능하며, Functional API를 통해 커스텀 모델을 빠르게 제작할 수 있습니다.
Sentis는 다양한 예제도 함께 제공하고 있습니다. Sentis Sample Github와 HuggingFace Unity Repository를 통해서 다양한 사전 학습된 모델들과 C# 기반의 샘플 코드를 다운로드할 수 있습니다.
추후에는 Microsoft의 DirectML, Apple의 Core ML/MPS Graph, Google의 Neural Network API 등 주요 플랫폼의 머신러닝 프레임워크들과도 연동되어, Neural Processing Units(NPU)를 활용한 추론도 지원할 예정입니다.
Machine Learning Pipeline에서 Sentis의 역할
일반적으로 머신러닝은 Training Process와 Inference Process라는 두 가지 과정으로 구성됩니다.
Training Process는 데이터셋을 수집하고, 이를 전처리한 뒤 모델을 학습시키는 단계를 의미합니다. 더불어 학습된 모델의 성능을 평가하고 실제 환경에 배포하는 단계까지 포함됩니다.
Inference Process는 이미 학습이 완료된 모델을 실제로 사용하는 단계를 의미합니다. 새로운 데이터를 학습된 모델에 입력하면, 모델이 이를 추론하여 필요한 결과값을 출력하는 방식입니다.
Unity의 Sentis는 이러한 머신러닝 파이프라인 중에서 Inference Process에 초점을 맞춘 패키지입니다. Sentis는 모델을 처음부터 학습시키는 용도가 아닌, 이미 다른 환경에서 학습이 완료된 모델을 Unity 환경에서 효과적으로 실행하는 데 특화되어 있습니다. 따라서 개발자들은 학습된 머신러닝 모델을 Sentis를 이용하여 Unity 기반의 게임이나 애플리케이션에 쉽게 통합하여 사용할 수 있습니다.
Sentis로 할 수 있는 Task
학습된 모델 파일을 ONNX 파일로 변환하여 Sentis에 성공적으로 가져오는 경우, Sentis는 딥러닝 모델이 지원하는 Task를 그대로 수행할 수 있습니다. 예를 들면, 컴퓨터 비전, 언어 모델, 오디오, 강화학습 분야에서의 Task를 수행할 수 있습니다.
컴퓨터 비전(Computer Vision): Object Detection(물체 감지), Image Segmentation(이미지 분할), Image Classification(이미지 분류), Depth Estimation(깊이 추정), Hand Gesture Detection(손동작 감지), Handwriting Detection(필적 감지), Mask Generation(마스크 생성), Image Generation(이미지 생성)
언어(Language): Story Generation(스토리 생성), Text Summarization(텍스트 요약), Sentence Similarity(문장 유사도), Token Classification(토큰 분류), Zero-shot Classification(제로샷 분류)
오디오(Audio): Text-to-Speech(텍스트 음성 변환), Speech-to-Text(음성 텍스트 변환), Audio Classification(음성 분류), Audio-to-Audio(음성 변환), Sound Generation(소리 생성)
강화학습(Reinforcement Learning): Board Game Opponent(보드게임 AI), Time Series Forecasting(시계열 예측), Sensor Data Classification(센서 데이터 분류)
Sentis의 사용 과정은 크게 4가지 단계로 나눌 수 있습니다.
AI 모델 선택하기
Unity로 임포트 및 모델 최적화하기
추론 코드작성하기
런타임 플랫폼에 배포하기
이제 각 단계별 내용과 팁을 상세히 살펴보겠습니다.
1. AI 모델 선택하기
Unity 공식 예제는 Sentis Sample Github와 HuggingFace Unity Repository에서 다양한 사전 학습된 모델들과 C# 기반의 샘플 코드를 다운로드할 수 있습니다. 또는 직접 학습한 모델을 ONNX로 변환하여 커스텀 모델을 Sentis에서 사용하는 것도 가능합니다.
Unity에서 공식제공되는 모델 사용하기
우선 Sentis Sample GitHub에서 제공되는 Sentis 공식 프로젝트는 다음과 같습니다.
객체인식: Blaze Face/Hand/Pose Detection(얼굴, 손, 자세를 인식하는 모델)
숫자인식: Digit Recognition(필기체 숫자를 인식하는 모델)
강화학습: Board-game AI(오델로 게임을 하는 강화학습 모델)
비전: Depth Estimation(RGB에서 Depth Map을 추출하는 모델)
3D 생성: Protein Folding(단백질 서열을 추출하는 모델)
시뮬레이션: Star Simulation(별의 움직임을 시뮬레이션하는 모델)
HuggingFace Unity Repository에서는 다음과 같은 예제들을 제공하고 있습니다.
객체인식: sentis-blaze-face(얼굴 감지), sentis-blaze-hand(손 감지), sentis-blaze-pose(자세 감지), sentis-hand-landmark(손 관절점 감지), sentis-iris-landmark(눈동자 감지), sentis-face-landmarks(얼굴 특징점 감지), sentis-yolotinyv7(실시간 객체 감지), sentis-YOLOv8n(실시간 객체 감지)
객체분류: sentis-mobilenet-v2(이미지 분류), sentis-MNIST-12(숫자 인식)
비전: sentis-MiDaS(깊이맵 추정)
자연어: sentis-MiniLM-v6(문장 임베딩), sentis-phi-1_5(언어 모델), sentis-tiny-stories(이야기 생성)
오디오: sentis-audio-frequency-to-16khz(주파수 변환), sentis-whisper-tiny(음성 인식: STT), sentis-jets-text-to-speech(음성 합성: TTS), sentis-MusicGen(음악 생성)
3D 생성: sentis-alphafold-v1(단백질 구조 예측)
강화학습: sentis-othello(오델로 게임), sentis-neural-cellular-automata(패턴 생성), ML-Agents-PushBlock(블록 밀기), MLAgents-SoccerTwos(다중 축구 게임), ML-Agents-Worm(지렁이 게임), ML-Agents-Walker(이족 보행), ML-Agents-Pyramids(피라미드 게임)
그 외에도 학습된 모델(pre-trained model)을 다음 사이트에서 받아볼 수 있습니다.
ONNX Model Zoo: ONNX에서 공식으로 제공하는 모델 저장소
PINTO Model Zoo: 개인 개발자가 정리한 ONNX 모델 저장소
Keras Code Examples: 데이터셋을 이용한 학습 과정과 추론 과정을 튜토리얼 형식으로 제공
Kaggle Models: Kaggle에서 Task별로 자주 사용되는 모델의 Model Card, Code를 제공
PyTorch Hub: PyTorch를 사용하는 Task별 대표 모델에 대해 간단한 설치와 사용 방법을 제공
직접 트레이닝한 모델을 ONNX로 변경해서 사용하기
Python 환경에서 직접 학습한 딥러닝 모델을 Unity Sentis에서 사용하기 위해서는 우선 ONNX 형식으로 변환하는 과정이 필요합니다. Sentis는 ONNX Opset 버전 7부터 15까지의 Operator를 지원하므로, 이 범위 내에서 변환을 진행해야 합니다.
PyTorch로 개발된 모델의 경우, torch.onnx.export 함수를 사용하여 간단히 ONNX 형식으로 변환할 수 있습니다. (참고문서)
# Input to the model
x = torch.randn(batch_size, 1, 224, 224, requires_grad=True)
torch_out = torch_model(x)
# Export the model
torch.onnx.export(
torch_model, # model being run
x, # model input (or a tuple for multiple inputs)
"super_resolution.onnx", # where to save the model (can be a file or file-like object)
export_params=True, # store the trained parameter weights inside the model file
opset_version=10, # the ONNX version to export the model to
do_constant_folding=True, # whether to execute constant folding for optimization
input_names = ['input'], # the model's input names
output_names = ['output']) # the model's output names
TensorFlow의 경우에는 tf2onnx 패키지를 이용하면 학습된 .tf 모델을 ONNX로 쉽게 변환할 수 있습니다. (참고문서)
pip install onnxruntime
pip install git+https://github.com/onnx/tensorflow-onnx
python -m tf2onnx.convert --saved-model model.tf --output model.onnx --opset 15 --verbose
2. Unity로 임포트 및 모델 최적화
Unity로 모델을 임포트하는 방법은 매우 간단합니다. 단순히 Drag and Drop으로 모델을 가져올 수 있습니다. ONNX 모델을 그대로 사용할 수도 있지만, 모델 직렬화(.sentis)를 하여 사용하는 것을 추천드립니다. 모델 직렬화를 사용하고 양자화와 Fixed Dimension을 활용하면 Unity에서 머신러닝 모델의 성능과 효율성을 크게 향상시킬 수 있습니다.
모델 직렬화를 통해 .onnx 파일을 .sentis 형식으로 변환하면 모델의 확장성이 개선됩니다. 이는 디스크 공간을 절약하고 에디터에서의 모델 로딩 속도를 단축하며, Unity 내에서 모델을 한 번 더 검증하여 사용합니다. 직렬화된 모델은 StreamingAssets를 통해 파일 공유가 용이해집니다. 변환 방법은 ONNX 파일을 선택한 후, Inspector 상에서 "Serialize To StreamingAssets"을 클릭하면 됩니다.
(참고: Manual > Serialize a Model)
양자화 모델을 사용하여 모델의 용량을 최대 75%까지 줄일 수 있습니다. 이는 직렬화된 .sentis 파일에 대해서만 지원되며, 부동소수점 가중치만 양자화하여 구현됩니다. Sentis 양자화 모델은 추론 속도에 영향을 주지 않으며, 양자화로 인해 결과값의 정확도가 다소 감소할 수 있습니다.
(참고: Manual > Quantize a Model)
모델에서 Fixed Dimension을 사용하면 추론 속도를 더욱 향상시킬 수 있습니다. Sentis는 고정된 입력값을 처리하는 데 최적화되어 있기 때문에 추론 속도가 빠릅니다. Dynamic Dimension은 최적화가 어려워 성능 저하가 발생할 수 있습니다.
모델의 아키텍처나 가중치에 대한 보안이 필요한 경우에는 AES 암호화를 통해 모델을 암호화하여 배포하고, 다운로드한 모델에 대해 AES 복호화하여 사용할 수 있습니다.
(참고: Manual > Encrypt a model)
3. 추론 코드작성
Sentis의 추론 코드는 크게 8단계로 구성되어 있습니다. 각 단계별 간단한 설명과 최적화를 위한 팁도 함께 소개하겠습니다.
Unity.Sentis 네임스페이스를 사용합니다.
모델 불러오기: .sentis 또는 .onnx 파일을 ModelAsset 형식으로 불러오거나 파일 경로를 통해 직접 불러올 수 있습니다.
모델 편집하기: Functional API를 사용하여 모델의 출력에 레이어를 손쉽게 추가할 수 있습니다.
Functional API를 활용하면 pre-trained 모델에서 전처리가 필요한 경우, 일부 레이어가 누락된 경우(예: ReLU, LeakyReLU, Sigmoid 등)나 Bounding Box에 대한 후처리가 필요한 경우(예: Non-Max Suppression)에 유용하게 사용할 수 있습니다. 현재 기본적으로 제공하는 레이어에 대해서만 사용이 가능하고, 그 외의 레이어를 사용하고자 할 때는 C# 스크립트나 Command Buffer를 통해 직접 정의해야 합니다.
(참고: Manual > Edit a model)
추론 엔진 생성하기: CPU, GPUCompute, GPUPixel 등 실행 환경에 적합한 Backend를 선택할 수 있습니다.
Graphics API를 선정할 때는 여러 성능적 요소들을 고려해야 합니다. 일반적으로 GPUCompute 방식이 가장 빠른 성능을 보여주지만, 모델의 특성에 따라 CPU가 더 나은 성능을 보일 수 있습니다. 따라서 시스템에서 SystemInfo.supportsComputeShaders를 통해 GPU 지원 여부를 먼저 확인하는 것이 중요합니다.
CPU: Burst 컴파일러, Job 시스템
GPUCompute: Direct3D, Vulkan, OpenGL, Metal 지원
GPUPixel: Compute Shader를 사용하지 않는 경우에 한하여 사용 가능
GPU를 지원하지 않는 레이어가 모델에 포함되어 있다면, CPU 폴백이 발생하여 성능이 저하될 수 있습니다. 이러한 CPU 폴백 현상은 Graphics API가 특정 레이어의 GPU 연산을 지원하지 않아 발생하며, 모델 수행 중 CPU와 GPU 간의 잦은 메모리 복사로 인해 성능 저하가 일어날 수 있습니다.
웹 환경의 경우, CPU에서 Burst 컴파일러가 WebAssembly 코드에서 성능 저하를 보일 수 있으므로, GPUCompute 방식의 사용이 권장됩니다. 이는 웹 환경에서의 최적화된 성능을 위한 중요한 고려사항입니다.
모델 입력값 설정하기: 모델이 요구하는 입력값 이름에 맞춰 Tensor를 생성하여 데이터를 입력합니다.
추론 엔진 실행하기: 더미 실행을 통해 첫 실행 시 발생하는 지연 시간을 방지할 수 있습니다.
딥러닝 모델의 실행 시 초기화 단계에서는 반드시 1회의 더미 실행(warm-up)이 필요합니다. 이는 첫 번째 스케줄링이 항상 느리게 실행되는 특성 때문인데, 초기 실행 시에는 필요한 코드와 셰이더들을 컴파일하고 메모리에 처음으로 할당하는 과정이 이루어지며, 이 현상은 모든 딥러닝 추론 라이브러리에서 공통적으로 나타납니다. 따라서 초기화 단계에 미리 더미 실행을 해주면 필요한 모든 리소스들이 미리 메모리에 로드되고 초기화되어, 이후 실행에 대해서는 원래의 정상적인 속도로 빠르게 응답할 수 있게 됩니다.
FPS 개선을 위해 모델을 레이어 단위로 쪼개서 실행하는 방법을 활용할 수 있습니다. 일반적인 Schedule은 모든 레이어를 한 번에 실행하지만, ScheduleIterable을 사용하면 IEnumerator를 통해 레이어 단위로 모델을 나눠서 실행할 수 있습니다.
(참고: Manual > Split inference over multiple frames)
GPU에서 CPU로 메모리 다운로드하기: 동기 또는 비동기 방식을 활용하여 모델을 더욱 효율적으로 실행할 수 있습니다.
Unity 6에서 정식으로 지원하는 async/await 기능을 활용하여 메인 스레드 블로킹을 효과적으로 방지할 수 있습니다. 특히 스케줄링 이후 데이터가 CPU로 다운로드되는 과정에서 발생할 수 있는 메인 스레드 블로킹 문제를 해결할 수 있습니다. 이러한 async/await 패턴을 적용하면 메인 스레드의 안정적인 실행을 보장할 수 있어, 결과적으로 안정적인 FPS 유지에 큰 도움이 됩니다.
동기 방식: ReadbackAndClone()
비동기 방식: await ReadbackAndCloneAsync()
(참고: Manual > Read output from a model asynchronously)
변수 해제하기: 사용이 완료된 변수를 적절히 해제하여 메모리 누수를 방지합니다.
메모리 누수를 방지하기 위해 동적으로 생성한 Tensor는 반드시 메모리 해제를 해주어야 합니다. 이를 위해 using var 문을 사용하여 Tensor의 수명 범위를 명확하게 제한하거나, Dispose() 메서드를 직접 호출하여 메모리를 해제할 수 있습니다.
4. 런타임 플랫폼에 배포
StreamingAssets 폴더를 통해 모델을 배포할 경우, 데스크톱/Android/iOS/WebGL에서의 접근 방법이 상이합니다. 따라서 각 플랫폼별로 모델 파일을 가져오는 방법은 다음 코드를 활용하면 쉽게 구현할 수 있습니다.
public async Task<Model> GetModelFromStreamingAssetsPathAsync(string modelName)
{
Model model = null;
string fullPath = Path.Combine(Application.streamingAssetsPath, modelName);
Debug.Log(fullPath);
#if ((UNITY_ANDROID || UNITY_IOS || UNITY_WEBGL) && !UNITY_EDITOR)
try
{
#if UNITY_IOS
fullPath = "file://" + fullPath;
#endif
using var loadingRequest = UnityWebRequest.Get(fullPath);
var operation = loadingRequest.SendWebRequest();
await operation;
if (loadingRequest.result == UnityWebRequest.Result.Success)
{
#if (UNITY_ANDROID || UNITY_IOS)
var persistentPath = Path.Combine(Application.persistentDataPath, modelName);
var directory = Path.GetDirectoryName(persistentPath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
await File.WriteAllBytesAsync(persistentPath, loadingRequest.downloadHandler.data);
model = ModelLoader.Load(persistentPath);
#elif UNITY_WEBGL
using var memoryStream = new MemoryStream(loadingRequest.downloadHandler.data);
model = ModelLoader.Load(memoryStream);
#endif
}
else
{
Debug.LogError($"Failed to load from StreamingAssets: {loadingRequest.error}");
return null;
}
}
catch (System.Exception e)
{
Debug.LogError($"Error in GetStreamingAssetsPath: {e.Message}");
return null;
}
#else
Debug.Log("WebGL:" + fullPath);
model = ModelLoader.Load(fullPath);
#endif
return model;
}
앞서 살펴본 내용들은 일반적으로 학습된 모델(pre-trained)을 이용한 전체적인 추론 과정에 대해 살펴보았습니다. Part 2에서는 실제로 MLP(Multi-Layer Perceptron)을 이용하여 Custom Hand Gesture Classifier를 직접 학습하는 튜토리얼에 대해 설명하겠습니다.
환경설정
본 튜토리얼의 개발 환경은 다음과 같습니다.
Meta Quest 3
Unity 6 (6000.0.23f1)
Sentis 2.1.0
Mixed Reality (MR) Template
데이터셋
해당 예제에서는 XR Hands를 통해 Joint 데이터, Finger 형태, Orientation, Hand 형태, Hand 포즈 등의 손가락 위치와 각도 정보를 얻을 수 있습니다. Palm Rotation 정보는 각 손바닥의 Euler Angle 값을 사용하였고, 손가락별 정보값은 0~1 범위의 FullCurl/BaseCurl/TipCurl/Pinch/Spread 값을 사용하였습니다. 예를 들어, 우리는 양손에 대해 각 손가락별 정보를 활용하여 아래와 같은 제스처를 정의할 수 있습니다. (참고문서)
본 모델의 학습을 위해 다음과 같은 손가락의 정보를 사용하였습니다. 결과적으로 트레이닝에 사용될 데이터셋의 구성은 클래스 정보(1) + 왼손 정보(28) + 오른손 정보(28)를 더한 총 57개의 값으로 구성된 데이터를 누적하여 데이터셋으로 사용합니다. 해당 데이터셋은 XR 상에서 직접 캡처를 통해 생성하여 다음과 같은 코드를 통해 각 클래스별로 데이터를 CSV 파일로 누적하여 저장하였습니다.
그리고 우리가 학습할 핸드 제스처는 American Sign Language에서 사용하는 “I”, “Love”, “Unity”, “Six”라는 4개의 단어를 학습하도록 하였습니다.
(참고: https://www.handspeak.com/)
Training 코드 및 Inference 코드 작성
Training은 단순한 MLP(Multi-Layer Perceptron) 모델을 이용하여 간단히 분류기를 설계하였습니다. 데이터는 왼손(28) + 오른손(28)을 4개의 클래스에 대해 각각 2,000개씩 저장하여 사용하였습니다. 출력은 4개의 클래스에 대한 확률값을 계산하여 가장 높은 확률을 가지는 클래스에 대해 검출 결과를 확인할 수 있습니다.
생성된 모델의 정보는 다음과 같습니다.
입력: 56개의 데이터(왼손 28개 + 오른손 28개 정보값)
출력: 4개의 클래스에 대한 확률값
트레이닝 시간: 3분 이내
ONNX 모델 용량: 98KB
자세한 트레이닝 코드는 아래의 Colab 코드를 통해 확인하실 수 있습니다. [링크]
추론 단계에서는 모델에 Softmax 레이어가 포함되어 있지 않았기 때문에, Functional API를 통해 레이어를 추가하여 모델을 변경하였습니다. 그리고 ReadbackAndCloneAsync 함수를 통해 메인 스레드 블로킹이 일어나지 않도록 구현하였습니다.
구현 결과
확률이 0.8 이상인 경우에 대해서만 해당 제스처를 인식한 것으로 처리하였고, 결과적으로 “I Love Unity 6”라는 수어를 잘 표현하는 것을 확인할 수 있었습니다. 튜토리얼에 사용한 전체 코드는 Repository에서 확인하실 수 있습니다.
정리
지금까지 Sentis의 사용 방법과 간단한 튜토리얼에 대해 알아보았습니다. 이제 Unity 6에서 공식 지원하는 Sentis 패키지를 통해 AI 기능을 애플리케이션에 쉽게 적용하실 수 있게 되었습니다. 앞으로 Sentis를 사용하여 더 창의적이고 혁신적인 AI 기능을 적용해 보시기 바랍니다. 더 궁금한 사항이 있으시면 Unity Discussion의 Sentis Group을 참고하시거나, 기술 지원팀에 문의 사항을 남겨주시기 바랍니다.