C++에서 TensorFlow C API를 활용하면 Python 인터프리터 없이도 머신러닝 모델을 서빙할 수 있습니다. 이는 임베디드 시스템, 성능 최적화가 필요한 애플리케이션, 경량 서버 환경에서 유용합니다.
TensorFlow는 주로 Python 환경에서 사용되지만, TensorFlow C API를 활용하면 C++에서도 모델을 로드하고 추론을 실행할 수 있습니다. 본 기사에서는 TensorFlow C API를 활용하여 모델을 서빙하는 방법을 자세히 설명합니다.
우리는 다음과 같은 내용을 다룰 것입니다:
- TensorFlow C API 개요
- C++ 코드에서 TensorFlow 모델 로드 및 실행
- 입력 데이터 전처리 및 텐서 변환
- 성능 최적화 기법
- REST API 서버 구축 및 실전 예제
이를 통해 TensorFlow 기반 머신러닝 모델을 C++ 환경에서 효율적으로 서빙하는 방법을 익힐 수 있습니다.
TensorFlow C API 개요
TensorFlow C API는 TensorFlow의 핵심 기능을 C 및 C++ 환경에서 사용할 수 있도록 제공하는 인터페이스입니다. 이를 활용하면 Python 없이도 TensorFlow 모델을 로드하고 실행할 수 있으며, C++ 기반 애플리케이션에서 고성능 머신러닝 서빙을 구현할 수 있습니다.
TensorFlow C API의 특징
TensorFlow C API는 다음과 같은 특징을 갖습니다:
- 경량화된 인터페이스: Python 인터프리터 없이 TensorFlow 실행 가능
- 고성능 서빙: 멀티스레딩과 메모리 관리 최적화 가능
- C 및 C++과의 완벽한 호환: 다양한 시스템에서 모델 배포 가능
- 직접적인 텐서 연산 지원: 텐서 생성, 변환 및 모델 실행 기능 제공
TensorFlow C API의 주요 기능
TensorFlow C API를 이용하면 다음과 같은 작업을 수행할 수 있습니다:
- SavedModel 및 Frozen Graph 로드
- 텐서 생성 및 입력 데이터 변환
- 세션 실행을 통한 모델 추론
- 결과 텐서 변환 및 후처리
- GPU 가속 및 성능 최적화 적용
TensorFlow C API 사용의 이점
- Python 의존성 제거: Python 환경이 필요하지 않으므로 임베디드 시스템, C++ 서버, 모바일 애플리케이션에서도 쉽게 적용 가능
- 높은 성능: Python보다 직접적인 메모리 관리가 가능해 실행 속도가 향상됨
- 경량 배포 가능: Python 런타임을 포함할 필요 없이 독립 실행 파일 형태로 배포 가능
TensorFlow C API를 활용하면 C++ 프로젝트에서 머신러닝 모델을 효율적으로 서빙할 수 있습니다. 다음 섹션에서는 TensorFlow 모델을 C++에서 로드하는 방법을 살펴보겠습니다.
TensorFlow 모델을 C++에서 로드하는 방법
TensorFlow C API를 사용하여 C++ 환경에서 모델을 로드하려면 SavedModel 형식 또는 Frozen Graph 형식을 사용할 수 있습니다. 이 섹션에서는 두 가지 방법을 모두 다루고, C++ 코드에서 모델을 불러오는 구체적인 절차를 설명합니다.
1. TensorFlow 모델 형식 개요
TensorFlow에서는 모델을 다양한 형식으로 저장할 수 있습니다. C++에서 사용할 수 있는 대표적인 두 가지 형식은 다음과 같습니다:
- SavedModel:
- TensorFlow에서 공식적으로 지원하는 표준 모델 형식
- 서빙에 필요한 메타데이터 및 가중치 포함
saved_model.pb
파일과 변수 디렉토리 포함- Frozen Graph:
- 불필요한 연산을 제거하고 모델을 단일
.pb
파일로 변환한 형식 - 가중치와 연산 그래프가 통합된 상태
- 배포 시 크기가 작고 성능이 최적화됨
2. TensorFlow C API를 활용한 모델 로드
C++에서 TensorFlow 모델을 로드하려면 TensorFlow C API의 TF_LoadSessionFromSavedModel
또는 TF_GraphImportGraphDef
함수를 사용합니다.
SavedModel 로드
#include <tensorflow/c/c_api.h>
#include <iostream>
int main() {
// TensorFlow 환경 초기화
TF_Status* status = TF_NewStatus();
TF_Graph* graph = TF_NewGraph();
TF_SessionOptions* session_options = TF_NewSessionOptions();
// 모델 디렉토리 경로 설정
const char* model_path = "my_model/saved_model";
// 세션 생성
TF_Session* session = TF_LoadSessionFromSavedModel(
session_options, nullptr, model_path,
{TF_TAGSERVING}, 1, graph, nullptr, status);
if (TF_GetCode(status) != TF_OK) {
std::cerr << "Error loading model: " << TF_Message(status) << std::endl;
return -1;
}
std::cout << "Model loaded successfully!" << std::endl;
// 자원 해제
TF_DeleteSession(session, status);
TF_DeleteSessionOptions(session_options);
TF_DeleteGraph(graph);
TF_DeleteStatus(status);
return 0;
}
Frozen Graph 로드
#include <tensorflow/c/c_api.h>
#include <iostream>
#include <fstream>
#include <vector>
// GraphDef 파일을 읽어 들이는 함수
std::vector<char> ReadBinaryProto(const char* file_path) {
std::ifstream file(file_path, std::ios::binary);
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<char> buffer(size);
file.read(buffer.data(), size);
return buffer;
}
int main() {
TF_Status* status = TF_NewStatus();
TF_Graph* graph = TF_NewGraph();
// GraphDef 파일 읽기
const char* model_path = "frozen_model.pb";
std::vector<char> buffer = ReadBinaryProto(model_path);
TF_Buffer* graph_def = TF_NewBufferFromString(buffer.data(), buffer.size());
TF_ImportGraphDefOptions* opts = TF_NewImportGraphDefOptions();
// Graph 불러오기
TF_GraphImportGraphDef(graph, graph_def, opts, status);
if (TF_GetCode(status) != TF_OK) {
std::cerr << "Error loading frozen graph: " << TF_Message(status) << std::endl;
return -1;
}
std::cout << "Frozen graph loaded successfully!" << std::endl;
// 자원 해제
TF_DeleteImportGraphDefOptions(opts);
TF_DeleteBuffer(graph_def);
TF_DeleteGraph(graph);
TF_DeleteStatus(status);
return 0;
}
3. 모델 로드 시 고려해야 할 사항
- TensorFlow 라이브러리 초기화: TensorFlow C API를 사용하려면 동적 라이브러리(
libtensorflow.so
또는tensorflow.dll
)를 올바르게 로드해야 합니다. - 메모리 관리:
TF_DeleteGraph
,TF_DeleteSession
등을 활용하여 메모리를 해제해야 합니다. - 그래프 입력/출력 이름 확인: 모델을 로드한 후 올바른 입력 및 출력 텐서 이름을 확인해야 합니다.
4. 모델 로드 검증
모델을 성공적으로 로드한 후 TF_GraphOperationByName
을 사용하여 입력 및 출력 노드가 존재하는지 확인할 수 있습니다.
TF_Operation* input_op = TF_GraphOperationByName(graph, "input_node");
if (!input_op) {
std::cerr << "Error: Input node not found!" << std::endl;
}
이제 TensorFlow C API를 사용하여 C++에서 모델을 로드하는 방법을 익혔습니다. 다음 섹션에서는 입력 데이터를 변환하여 모델에 전달하는 방법을 살펴보겠습니다.
TensorFlow C API로 입력 데이터를 전처리하는 방법
C++에서 TensorFlow C API를 사용하여 모델을 실행하려면, 입력 데이터를 올바른 형식으로 변환한 후 TF_Tensor 객체로 전달해야 합니다. 이 과정에서 데이터 정규화, 형식 변환, 차원 조정 등의 전처리 작업이 필요합니다.
이 섹션에서는 입력 데이터를 텐서로 변환하는 방법과 TF_Tensor를 생성하고 메모리를 관리하는 방법을 설명합니다.
1. 입력 데이터와 TensorFlow 텐서의 구조
TensorFlow의 TF_Tensor는 다차원 데이터를 표현하는 기본 구조입니다.
입력 데이터는 float, int32, uint8 등의 형식으로 변환한 후, TF_Tensor로 래핑하여 모델에 전달해야 합니다.
예를 들어, 이미지 데이터를 모델에 전달하려면:
- OpenCV 또는 다른 라이브러리로 이미지를 로드
- RGB 또는 Grayscale 변환
- 크기 조정 (예: 224×224)
- 정규화 (0~1 범위로 변환)
- TF_Tensor로 변환
2. TF_Tensor를 생성하는 방법
#include <tensorflow/c/c_api.h>
#include <iostream>
#include <vector>
// 새로운 TF_Tensor를 생성하는 함수
TF_Tensor* CreateTensor(TF_DataType data_type, const std::vector<int64_t>& dims, void* data, size_t data_size) {
TF_Tensor* tensor = TF_AllocateTensor(data_type, dims.data(), dims.size(), data_size);
memcpy(TF_TensorData(tensor), data, data_size);
return tensor;
}
위 함수는 주어진 데이터를 TF_Tensor로 변환하는 함수입니다.
- data_type: 텐서의 데이터 타입 (예: TF_FLOAT, TF_INT32)
- dims: 텐서의 차원 정보 (예:
[1, 224, 224, 3]
for 이미지) - data: 입력 데이터의 포인터
- data_size: 입력 데이터의 크기
3. 이미지 데이터를 TF_Tensor로 변환하기
OpenCV를 활용하여 이미지를 로드하고, TensorFlow 모델이 처리할 수 있도록 변환하는 코드 예제입니다.
#include <tensorflow/c/c_api.h>
#include <opencv2/opencv.hpp>
#include <vector>
// 이미지 데이터를 TF_Tensor로 변환하는 함수
TF_Tensor* ImageToTensor(const std::string& image_path) {
cv::Mat image = cv::imread(image_path, cv::IMREAD_COLOR);
if (image.empty()) {
std::cerr << "Error: Could not read image!" << std::endl;
return nullptr;
}
// 크기 조정 (모델에 맞게 조정, 예: 224x224)
cv::resize(image, image, cv::Size(224, 224));
// BGR -> RGB 변환
cv::cvtColor(image, image, cv::COLOR_BGR2RGB);
// float로 변환 및 정규화 (0~1)
image.convertTo(image, CV_32FC3, 1.0f / 255.0f);
// TensorFlow 텐서 변환 (차원: [1, 224, 224, 3])
std::vector<int64_t> dims = {1, 224, 224, 3};
return CreateTensor(TF_FLOAT, dims, image.data, image.total() * image.elemSize());
}
이 함수는:
- 이미지를 OpenCV로 불러옴
- 크기를 224×224로 조정
- BGR → RGB 변환
- 0~1 범위로 정규화
- TensorFlow가 처리할 수 있도록 TF_Tensor로 변환
4. 일반적인 숫자 데이터를 TF_Tensor로 변환
만약 입력 데이터가 이미지가 아니라 단순한 숫자 데이터라면?
예를 들어, 숫자 배열 [1.0, 2.0, 3.0, 4.0]
을 입력으로 사용하려면:
#include <tensorflow/c/c_api.h>
#include <vector>
int main() {
std::vector<float> input_data = {1.0, 2.0, 3.0, 4.0};
std::vector<int64_t> dims = {1, 4}; // [Batch, Features]
// TF_Tensor 생성
TF_Tensor* input_tensor = CreateTensor(TF_FLOAT, dims, input_data.data(), input_data.size() * sizeof(float));
// 텐서 정보 확인
std::cout << "Tensor created with size: " << TF_TensorByteSize(input_tensor) << " bytes" << std::endl;
// 메모리 해제
TF_DeleteTensor(input_tensor);
return 0;
}
이 코드는 간단한 숫자 벡터를 TF_Tensor로 변환하는 예제입니다.
5. TF_Tensor 메모리 관리
TensorFlow C API는 동적 메모리를 사용하므로, 사용이 끝난 후 반드시 메모리를 해제해야 합니다.
TF_DeleteTensor(input_tensor);
이 코드를 추가하여 메모리 누수를 방지해야 합니다.
6. 정리
- TF_Tensor를 생성하려면
TF_AllocateTensor
를 사용 - 이미지 데이터는 OpenCV로 로드한 후 정규화 및 변환 필요
- 일반 숫자 데이터는 벡터로 변환하여
TF_Tensor
로 래핑 - 사용 후 반드시
TF_DeleteTensor
로 메모리 해제
이제 입력 데이터를 텐서로 변환할 수 있습니다.
다음 단계에서는 변환한 데이터를 이용하여 C++에서 TensorFlow 모델을 실행하는 방법을 다루겠습니다.
C++ 코드에서 TensorFlow 모델 추론 실행
TensorFlow C API를 사용하여 C++에서 모델을 실행하려면,
- 모델을 로드한 후
- 입력 데이터를 TF_Tensor로 변환하여 전달하고
- TF_SessionRun을 호출하여 추론을 실행한 뒤
- 결과를 추출하는 과정이 필요합니다.
이 섹션에서는 이러한 과정을 자세히 설명하고, 예제 코드를 제공합니다.
1. TensorFlow C API에서 추론을 실행하는 흐름
TensorFlow 모델을 실행하는 과정은 다음과 같습니다:
- SavedModel 또는 Frozen Graph 로드
- 입력 데이터를 TF_Tensor로 변환
- TF_SessionRun을 호출하여 모델 실행
- 출력 텐서를 추출하여 결과 확인
- 사용한 리소스 해제
2. TensorFlow C API를 사용한 모델 추론
다음은 TensorFlow SavedModel을 사용하여 추론을 실행하는 코드 예제입니다.
#include <tensorflow/c/c_api.h>
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
// TF_Tensor 생성 함수
TF_Tensor* CreateTensor(TF_DataType data_type, const std::vector<int64_t>& dims, void* data, size_t data_size) {
TF_Tensor* tensor = TF_AllocateTensor(data_type, dims.data(), dims.size(), data_size);
memcpy(TF_TensorData(tensor), data, data_size);
return tensor;
}
// 이미지 데이터를 TF_Tensor로 변환하는 함수
TF_Tensor* ImageToTensor(const std::string& image_path) {
cv::Mat image = cv::imread(image_path, cv::IMREAD_COLOR);
if (image.empty()) {
std::cerr << "Error: Could not read image!" << std::endl;
return nullptr;
}
cv::resize(image, image, cv::Size(224, 224));
cv::cvtColor(image, image, cv::COLOR_BGR2RGB);
image.convertTo(image, CV_32FC3, 1.0f / 255.0f);
std::vector<int64_t> dims = {1, 224, 224, 3};
return CreateTensor(TF_FLOAT, dims, image.data, image.total() * image.elemSize());
}
int main() {
TF_Status* status = TF_NewStatus();
TF_Graph* graph = TF_NewGraph();
TF_SessionOptions* session_options = TF_NewSessionOptions();
// 모델 경로 설정
const char* model_path = "my_model/saved_model";
TF_Session* session = TF_LoadSessionFromSavedModel(
session_options, nullptr, model_path,
{"serve"}, 1, graph, nullptr, status);
if (TF_GetCode(status) != TF_OK) {
std::cerr << "Error loading model: " << TF_Message(status) << std::endl;
return -1;
}
// 입력 텐서 생성
TF_Tensor* input_tensor = ImageToTensor("input.jpg");
// 입력 및 출력 노드 이름 설정
TF_Operation* input_op = TF_GraphOperationByName(graph, "input_node");
TF_Operation* output_op = TF_GraphOperationByName(graph, "output_node");
if (!input_op || !output_op) {
std::cerr << "Error: Failed to find input/output nodes!" << std::endl;
return -1;
}
// 모델 실행
TF_Output input_op_tensor = {input_op, 0};
TF_Output output_op_tensor = {output_op, 0};
TF_Tensor* output_tensor = nullptr;
TF_SessionRun(
session, nullptr,
&input_op_tensor, &input_tensor, 1,
&output_op_tensor, &output_tensor, 1,
nullptr, 0, nullptr, status
);
if (TF_GetCode(status) != TF_OK) {
std::cerr << "Error during inference: " << TF_Message(status) << std::endl;
return -1;
}
// 출력 결과 확인
float* output_data = static_cast<float*>(TF_TensorData(output_tensor));
std::cout << "Prediction Result: " << output_data[0] << std::endl;
// 자원 해제
TF_DeleteTensor(input_tensor);
TF_DeleteTensor(output_tensor);
TF_DeleteSession(session, status);
TF_DeleteGraph(graph);
TF_DeleteStatus(status);
return 0;
}
3. 코드 설명
- 모델 로드
TF_LoadSessionFromSavedModel()
을 사용하여 모델을 불러옵니다.
- 입력 데이터 변환
- OpenCV를 사용하여 이미지를 로드한 후, RGB 변환 및 정규화를 수행합니다.
CreateTensor()
를 사용하여 데이터를 TF_Tensor로 변환합니다.
- 세션 실행 (TF_SessionRun)
TF_SessionRun()
을 호출하여 추론을 실행합니다.- 입력 및 출력 노드를 설정한 후, 텐서를 전달합니다.
- 결과 추출
- 출력 텐서를
TF_TensorData()
를 사용하여 가져옵니다. - 예제에서는
output_data[0]
을 출력하여 결과를 확인합니다.
- 자원 해제
- 모든
TF_Tensor
,TF_Session
,TF_Graph
를 삭제하여 메모리 누수를 방지합니다.
4. TensorFlow C API 모델 실행 시 주의할 점
- 입력 및 출력 노드 이름을 확인해야 합니다.
- TensorFlow 모델을 로드한 후
saved_model_cli show --dir my_model --all
명령어를 사용하여 노드 이름을 확인할 수 있습니다. - 메모리 관리 필수
- TensorFlow C API는 자동 메모리 관리를 제공하지 않으므로
TF_DeleteTensor()
로 메모리를 수동으로 해제해야 합니다. - 출력 데이터 포맷 확인 필요
- 모델의 출력값이 벡터, 행렬 등 어떤 형태로 나오는지 확인한 후 올바르게 처리해야 합니다.
5. 정리
- TensorFlow 모델을 C++에서 로드하는 방법을 배웠습니다.
- 입력 데이터를 TF_Tensor로 변환하여 모델에 전달하는 방법을 익혔습니다.
- TF_SessionRun()을 사용하여 모델을 실행하는 방법을 익혔습니다.
- 출력 데이터를 가져오고 자원을 정리하는 방법을 배웠습니다.
다음 단계에서는 TensorFlow C API의 성능을 최적화하는 기법을 살펴보겠습니다.
TensorFlow C API를 활용한 성능 최적화 기법
TensorFlow C API를 사용하여 모델을 서빙할 때, 성능을 극대화하기 위해 여러 가지 최적화 기법을 적용할 수 있습니다.
이 섹션에서는 멀티스레딩, 그래프 최적화, GPU 가속, 텐서 메모리 관리 등 모델 실행 속도를 향상시키는 방법을 다룹니다.
1. 멀티스레딩을 활용한 병렬 처리
TensorFlow C API에서는 멀티스레딩을 사용하여 여러 요청을 동시에 처리할 수 있습니다.
이는 REST API 서버, 실시간 추론, 배치 처리 시스템에서 중요한 역할을 합니다.
멀티스레딩을 활용하여 여러 개의 추론 요청을 병렬 처리하는 코드 예제:
#include <tensorflow/c/c_api.h>
#include <thread>
#include <vector>
#include <iostream>
void RunInference(TF_Session* session, TF_Graph* graph, TF_Status* status) {
// (입력 데이터 준비 및 TF_SessionRun() 호출 코드 추가)
std::cout << "Thread " << std::this_thread::get_id() << " is running inference..." << std::endl;
}
int main() {
TF_Status* status = TF_NewStatus();
TF_Graph* graph = TF_NewGraph();
TF_SessionOptions* session_options = TF_NewSessionOptions();
const char* model_path = "my_model/saved_model";
TF_Session* session = TF_LoadSessionFromSavedModel(
session_options, nullptr, model_path,
{"serve"}, 1, graph, nullptr, status);
if (TF_GetCode(status) != TF_OK) {
std::cerr << "Error loading model: " << TF_Message(status) << std::endl;
return -1;
}
// 4개의 스레드를 생성하여 동시에 추론 실행
std::vector<std::thread> threads;
for (int i = 0; i < 4; i++) {
threads.emplace_back(RunInference, session, graph, status);
}
for (auto& th : threads) {
th.join();
}
TF_DeleteSession(session, status);
TF_DeleteGraph(graph);
TF_DeleteStatus(status);
return 0;
}
✅ 멀티스레딩의 장점
- 여러 개의 추론 요청을 동시에 처리하여 응답 속도 향상
- CPU 코어를 최대한 활용하여 모델 실행 성능 극대화
2. 그래프 최적화 및 불필요한 노드 제거
TensorFlow 모델을 최적화된 그래프로 변환하면 실행 속도를 향상시킬 수 있습니다.
이 과정은 불필요한 노드를 제거하고, 연산을 단순화하여 실행 속도를 최적화하는 역할을 합니다.
✅ 그래프 최적화를 수행하는 방법:
- SavedModel을 Frozen Graph로 변환
tf.compat.v1.graph_util.convert_variables_to_constants
를 사용하여 변수 변수를 상수로 변환- 불필요한 노드 및 연산 제거
아래는 Python 코드로 최적화된 Frozen Graph 생성하는 방법입니다.
import tensorflow as tf
model_dir = "my_model/saved_model"
output_node_names = ["output_node"]
# SavedModel 로드
loaded_model = tf.saved_model.load(model_dir)
# 그래프 변환
concrete_func = loaded_model.signatures["serving_default"]
frozen_func = tf.function(concrete_func).get_concrete_function()
frozen_func = tf.compat.v1.graph_util.convert_variables_to_constants_v2(frozen_func)
# 최적화된 모델 저장
tf.io.write_graph(frozen_func.graph, "optimized_model", "frozen_graph.pb", as_text=False)
이렇게 생성된 frozen_graph.pb
를 C++에서 로드하면 성능이 최적화된 모델을 실행할 수 있습니다.
3. GPU 가속을 활용한 고속 추론
TensorFlow C API는 GPU를 활용한 가속 실행을 지원합니다.
GPU를 활성화하면 CNN 기반 모델(ResNet, MobileNet 등)과 같은 대규모 연산이 포함된 모델의 속도를 크게 향상시킬 수 있습니다.
✅ GPU를 활용한 모델 실행을 위한 환경 설정:
- TensorFlow GPU 버전을 설치
CUDA
및cuDNN
드라이버 설치TF_SessionOptions
에서 GPU 사용 설정
GPU를 활성화하는 C++ 코드 예제:
TF_SessionOptions* session_options = TF_NewSessionOptions();
TF_SetConfig(session_options, gpu_options, sizeof(gpu_options), status);
✅ GPU 가속의 장점
- CPU 대비 10배 이상 빠른 속도로 추론 가능
- CNN 기반 딥러닝 모델 실행 시 대량의 행렬 연산을 효과적으로 처리
4. TF_Tensor 메모리 최적화
TensorFlow C API에서는 TF_Tensor의 메모리 관리를 신경 써야 합니다.
메모리 누수가 발생하지 않도록 TF_DeleteTensor()를 적절히 사용해야 합니다.
✅ 메모리 관리 방법
TF_AllocateTensor()
로 할당한 후 사용이 끝나면 반드시TF_DeleteTensor()
호출- 여러 번 호출되는 경우 동일한 텐서를 재사용하여 불필요한 할당 방지
TF_Tensor* tensor = TF_AllocateTensor(TF_FLOAT, dims, 2, data_size);
// (추론 실행)
TF_DeleteTensor(tensor); // 사용 후 메모리 해제
✅ TF_Tensor 메모리 최적화 팁
- 반복적으로 사용되는 텐서는 재사용
- 불필요한 메모리 할당 최소화
- 배치 처리 시 하나의 TF_Tensor를 공유하여 속도 개선
5. 배치 크기 조정 및 병렬 실행
한 번에 처리할 입력 데이터(batch size)를 조정하면 성능을 최적화할 수 있습니다.
TensorFlow C API를 사용하여 배치 크기를 조정하면 GPU 및 CPU 활용도를 높일 수 있습니다.
✅ 배치 크기 조정 예제
std::vector<int64_t> dims = {16, 224, 224, 3}; // 16개의 이미지를 한 번에 처리
TF_Tensor* input_tensor = CreateTensor(TF_FLOAT, dims, image_data, image_size);
✅ 배치 크기를 조절하면:
- 대량 데이터 처리 속도가 향상
- GPU를 활용할 경우 메모리 사용량 최적화
6. 정리
TensorFlow C API의 성능을 최적화하는 방법:
- 멀티스레딩 활용 → 여러 요청을 병렬 처리하여 추론 속도 개선
- 그래프 최적화 → Frozen Graph를 사용하여 불필요한 연산 제거
- GPU 가속 활성화 → CNN 모델 실행 속도를 극대화
- TF_Tensor 메모리 관리 최적화 → 불필요한 메모리 사용 방지
- 배치 크기 조정 → 여러 개의 입력을 한 번에 처리하여 성능 향상
이제 최적화된 TensorFlow C API 환경에서 고속 추론이 가능합니다.
다음 섹션에서는 실전 예제: 이미지 분류 모델을 서빙하는 방법을 다루겠습니다.
실전 예제: TensorFlow C API를 활용한 이미지 분류 모델 서빙
이제 TensorFlow C API를 사용하여 실제 이미지 분류 모델을 서빙하는 방법을 살펴보겠습니다.
이 실전 예제에서는 이미지 데이터를 입력받아 모델을 실행하고, 예측된 결과를 출력하는 과정을 다룹니다.
✅ 구현 목표:
- C++에서 TensorFlow 모델을 로드
- 이미지를 입력하여 모델을 실행
- 예측된 클래스 출력
1. 이미지 분류 모델 준비
이 예제에서는 사전 훈련된 MobileNet V2 모델을 사용합니다.
MobileNet V2는 경량 모델로, 실시간 이미지 분류 및 모바일 디바이스에서 사용하기 적합합니다.
✅ MobileNet V2 모델 다운로드 및 변환
사전 학습된 모델을 TensorFlow C API에서 사용하려면 SavedModel 형식으로 변환해야 합니다.
다음 Python 코드를 실행하여 MobileNet V2 모델을 변환합니다.
import tensorflow as tf
# MobileNet V2 모델 로드
model = tf.keras.applications.MobileNetV2(weights="imagenet")
# SavedModel 형식으로 저장
model.save("mobilenet_v2_saved_model")
이제 "mobilenet_v2_saved_model"
디렉토리 안에 모델이 저장됩니다.
이 모델을 C++에서 로드하여 사용할 수 있습니다.
2. C++ 코드로 이미지 분류 모델 서빙
아래 C++ 코드에서는:
- TensorFlow 모델을 로드
- 이미지를 입력하여 전처리
- 모델 실행 후 예측 결과 출력
#include <tensorflow/c/c_api.h>
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
// TensorFlow 텐서 생성 함수
TF_Tensor* CreateTensor(TF_DataType data_type, const std::vector<int64_t>& dims, void* data, size_t data_size) {
TF_Tensor* tensor = TF_AllocateTensor(data_type, dims.data(), dims.size(), data_size);
memcpy(TF_TensorData(tensor), data, data_size);
return tensor;
}
// 이미지 데이터를 TensorFlow 텐서로 변환
TF_Tensor* ImageToTensor(const std::string& image_path) {
cv::Mat image = cv::imread(image_path, cv::IMREAD_COLOR);
if (image.empty()) {
std::cerr << "Error: Cannot read image!" << std::endl;
return nullptr;
}
// 크기 조정 및 RGB 변환
cv::resize(image, image, cv::Size(224, 224));
cv::cvtColor(image, image, cv::COLOR_BGR2RGB);
// float 변환 및 정규화
image.convertTo(image, CV_32FC3, 1.0f / 255.0f);
std::vector<int64_t> dims = {1, 224, 224, 3};
return CreateTensor(TF_FLOAT, dims, image.data, image.total() * image.elemSize());
}
int main() {
TF_Status* status = TF_NewStatus();
TF_Graph* graph = TF_NewGraph();
TF_SessionOptions* session_options = TF_NewSessionOptions();
// 모델 로드
const char* model_path = "mobilenet_v2_saved_model";
TF_Session* session = TF_LoadSessionFromSavedModel(
session_options, nullptr, model_path,
{"serve"}, 1, graph, nullptr, status);
if (TF_GetCode(status) != TF_OK) {
std::cerr << "Error loading model: " << TF_Message(status) << std::endl;
return -1;
}
// 입력 이미지 변환
TF_Tensor* input_tensor = ImageToTensor("dog.jpg");
// 입력 및 출력 노드 이름 설정
TF_Operation* input_op = TF_GraphOperationByName(graph, "input_1");
TF_Operation* output_op = TF_GraphOperationByName(graph, "Predictions/Softmax");
if (!input_op || !output_op) {
std::cerr << "Error: Failed to find input/output nodes!" << std::endl;
return -1;
}
// 모델 실행
TF_Output input_op_tensor = {input_op, 0};
TF_Output output_op_tensor = {output_op, 0};
TF_Tensor* output_tensor = nullptr;
TF_SessionRun(
session, nullptr,
&input_op_tensor, &input_tensor, 1,
&output_op_tensor, &output_tensor, 1,
nullptr, 0, nullptr, status
);
if (TF_GetCode(status) != TF_OK) {
std::cerr << "Error during inference: " << TF_Message(status) << std::endl;
return -1;
}
// 출력 결과 확인 (Softmax 확률 값)
float* output_data = static_cast<float*>(TF_TensorData(output_tensor));
int class_index = std::distance(output_data, std::max_element(output_data, output_data + 1000));
std::cout << "Predicted class: " << class_index << std::endl;
// 자원 해제
TF_DeleteTensor(input_tensor);
TF_DeleteTensor(output_tensor);
TF_DeleteSession(session, status);
TF_DeleteGraph(graph);
TF_DeleteStatus(status);
return 0;
}
3. 코드 설명
✅ 모델 로드 및 실행
TF_LoadSessionFromSavedModel()
로 MobileNet V2 모델을 로드TF_GraphOperationByName()
를 사용하여 입력 및 출력 노드 이름 설정TF_SessionRun()
을 사용하여 모델 실행
✅ 이미지 전처리
- OpenCV로 이미지를 불러와 크기(224×224) 조정 및 RGB 변환
ImageToTensor()
함수에서 0~1 범위로 정규화 후 TF_Tensor로 변환
✅ 출력값 확인
- 모델의 출력값은 Softmax 확률 분포(1000개 클래스)
std::max_element()
를 사용하여 가장 높은 확률 값을 가진 예측 클래스 인덱스 찾기
4. 실행 결과 예시
이미지 "dog.jpg"
를 입력하면 다음과 같은 결과가 출력됩니다.
Predicted class: 208 # (Golden Retriever)
✅ 클래스 인덱스는 ImageNet 클래스 목록을 참고하여 변환 가능
5. 실전 환경에서 응용
이 코드를 확장하여 다음과 같은 응용이 가능합니다.
- REST API 서버 구축 → TensorFlow C API를 활용한 AI 서빙 엔진
- 멀티스레딩 적용 → 실시간 이미지 분류 서비스
- GPU 최적화 → TensorFlow GPU를 활용하여 속도 향상
- 배치 처리 → 여러 개의 이미지를 한 번에 분류
6. 정리
✅ TensorFlow C API를 활용한 이미지 분류 모델 서빙 방법을 학습
✅ SavedModel을 로드하고 OpenCV로 입력 이미지를 전처리
✅ C++에서 모델을 실행하고 예측된 클래스 결과를 출력
이제 C++ 환경에서 TensorFlow C API를 활용하여 머신러닝 모델을 서빙할 수 있습니다.
다음 섹션에서는 TensorFlow C API 기반 REST API 서버 구축 방법을 살펴보겠습니다.
TensorFlow C API 기반 REST API 서버 구축
머신러닝 모델을 실제 서비스에 배포하려면, REST API를 통해 웹 서버에서 호출할 수 있도록 구성해야 합니다.
이 섹션에서는 TensorFlow C API를 이용하여 REST API 서버를 구축하는 방법을 설명합니다.
✅ 구현 목표:
- C++ 기반의 REST API 서버에서 TensorFlow 모델을 실행
- 클라이언트가 HTTP 요청을 통해 이미지를 업로드하면 모델이 예측 수행
- JSON 형식으로 예측 결과 반환
1. REST API 서버를 위한 C++ 프레임워크
C++에서 REST API 서버를 구축하는 방법은 여러 가지가 있습니다.
TensorFlow C API와 함께 사용하기 좋은 경량 웹 서버 프레임워크는 다음과 같습니다.
프레임워크 | 특징 |
---|---|
Cpp-REST-SDK (Casablanca) | Microsoft가 개발한 REST API 라이브러리 |
Crow | Express.js 스타일의 경량 C++ 웹 서버 |
Pistache | C++17 기반의 빠르고 가벼운 REST 서버 |
이 예제에서는 Crow 프레임워크를 사용하여 REST API 서버를 구축합니다.
Crow는 경량, 비동기 지원, JSON 응답 처리 가능하여 머신러닝 API에 적합합니다.
2. C++ REST API 서버 코드
아래는 TensorFlow C API와 Crow를 사용하여 REST API 서버를 구축하는 코드 예제입니다.
이 서버는 이미지 파일을 업로드하면, TensorFlow 모델을 실행하여 예측 결과를 반환합니다.
#include <tensorflow/c/c_api.h>
#include <crow.h>
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <fstream>
// TensorFlow 텐서 생성 함수
TF_Tensor* CreateTensor(TF_DataType data_type, const std::vector<int64_t>& dims, void* data, size_t data_size) {
TF_Tensor* tensor = TF_AllocateTensor(data_type, dims.data(), dims.size(), data_size);
memcpy(TF_TensorData(tensor), data, data_size);
return tensor;
}
// 이미지 데이터를 TensorFlow 텐서로 변환
TF_Tensor* ImageToTensor(const std::string& image_path) {
cv::Mat image = cv::imread(image_path, cv::IMREAD_COLOR);
if (image.empty()) {
std::cerr << "Error: Cannot read image!" << std::endl;
return nullptr;
}
// 이미지 크기 조정 및 RGB 변환
cv::resize(image, image, cv::Size(224, 224));
cv::cvtColor(image, image, cv::COLOR_BGR2RGB);
image.convertTo(image, CV_32FC3, 1.0f / 255.0f);
std::vector<int64_t> dims = {1, 224, 224, 3};
return CreateTensor(TF_FLOAT, dims, image.data, image.total() * image.elemSize());
}
// TensorFlow 모델 실행 함수
int RunInference(const std::string& image_path) {
TF_Status* status = TF_NewStatus();
TF_Graph* graph = TF_NewGraph();
TF_SessionOptions* session_options = TF_NewSessionOptions();
// 모델 로드
const char* model_path = "mobilenet_v2_saved_model";
TF_Session* session = TF_LoadSessionFromSavedModel(
session_options, nullptr, model_path,
{"serve"}, 1, graph, nullptr, status);
if (TF_GetCode(status) != TF_OK) {
std::cerr << "Error loading model: " << TF_Message(status) << std::endl;
return -1;
}
// 입력 텐서 생성
TF_Tensor* input_tensor = ImageToTensor(image_path);
// 입력 및 출력 노드 설정
TF_Operation* input_op = TF_GraphOperationByName(graph, "input_1");
TF_Operation* output_op = TF_GraphOperationByName(graph, "Predictions/Softmax");
if (!input_op || !output_op) {
std::cerr << "Error: Failed to find input/output nodes!" << std::endl;
return -1;
}
// 모델 실행
TF_Output input_op_tensor = {input_op, 0};
TF_Output output_op_tensor = {output_op, 0};
TF_Tensor* output_tensor = nullptr;
TF_SessionRun(
session, nullptr,
&input_op_tensor, &input_tensor, 1,
&output_op_tensor, &output_tensor, 1,
nullptr, 0, nullptr, status
);
if (TF_GetCode(status) != TF_OK) {
std::cerr << "Error during inference: " << TF_Message(status) << std::endl;
return -1;
}
// 출력 결과 확인
float* output_data = static_cast<float*>(TF_TensorData(output_tensor));
int class_index = std::distance(output_data, std::max_element(output_data, output_data + 1000));
// 자원 해제
TF_DeleteTensor(input_tensor);
TF_DeleteTensor(output_tensor);
TF_DeleteSession(session, status);
TF_DeleteGraph(graph);
TF_DeleteStatus(status);
return class_index;
}
int main() {
crow::SimpleApp app;
CROW_ROUTE(app, "/predict").methods("POST"_method)([](const crow::request& req) {
auto file = crow::multipart::message(req);
if (file.parts.size() != 1) {
return crow::response(400, "Invalid request. Upload one image file.");
}
// 이미지 파일 저장
std::string file_path = "uploaded_image.jpg";
std::ofstream out(file_path, std::ios::binary);
out.write(file.parts[0].body.data(), file.parts[0].body.size());
out.close();
// 모델 실행
int predicted_class = RunInference(file_path);
// JSON 응답 반환
crow::json::wvalue result;
result["predicted_class"] = predicted_class;
return crow::response(result);
});
app.port(8080).multithreaded().run();
}
3. 코드 설명
✅ Crow를 활용하여 REST API 서버 생성
/predict
엔드포인트를 정의- 클라이언트가 이미지를 업로드하면 이를 파일로 저장
✅ TensorFlow C API를 사용하여 추론 실행
- 업로드된 이미지를 TensorFlow 텐서로 변환
- 모델을 실행하고 예측된 클래스를 JSON 응답으로 반환
✅ 서버 실행
g++ -o server server.cpp -ltensorflow -lcrow -lopencv_core -lopencv_imgcodecs -lopencv_imgproc -pthread
./server
✅ 클라이언트에서 API 요청 보내기 (Python 예제)
import requests
url = "http://localhost:8080/predict"
files = {"file": open("dog.jpg", "rb")}
response = requests.post(url, files=files)
print(response.json())
✅ API 응답 예시 (JSON)
{
"predicted_class": 208
}
4. REST API 서버 확장 가능성
이 REST API 서버를 확장하면 다양한 머신러닝 애플리케이션을 만들 수 있습니다.
- 멀티스레딩 최적화 → 요청 처리 속도 향상
- Docker 컨테이너화 → 클라우드 환경에서 배포 가능
- OAuth 및 인증 추가 → 사용자별 예측 서비스 제공
5. 정리
✅ TensorFlow C API와 Crow를 사용하여 REST API 서버를 구축하는 방법을 학습
✅ C++에서 모델을 실행하고 API로 결과를 반환하는 코드 구현
✅ 클라이언트가 HTTP 요청을 통해 머신러닝 모델을 호출할 수 있도록 설계
이제 C++ 기반 머신러닝 REST API 서버를 활용하여 실제 애플리케이션에 머신러닝 모델을 배포할 수 있습니다!
다음 섹션에서는 TensorFlow C API 사용 시 발생하는 문제와 디버깅 방법을 다루겠습니다.
디버깅과 문제 해결
TensorFlow C API를 활용하여 머신러닝 모델을 서빙하는 과정에서 모델 로드 실패, 추론 오류, 메모리 누수 등의 다양한 문제가 발생할 수 있습니다.
이 섹션에서는 주요 문제와 해결 방법을 정리하고, 실전에서 활용할 수 있는 디버깅 기법을 설명합니다.
1. 모델 로드 실패 문제
✅ 오류 메시지 예시:
Error loading model: Not found: Could not find SavedModel .pb file
✅ 원인 분석:
saved_model.pb
파일이 존재하지 않거나, 경로가 잘못됨- TensorFlow 버전 차이로 인해 모델이 올바르게 로드되지 않음
✅ 해결 방법:
ls my_model/saved_model
명령어를 실행하여 모델 파일이 존재하는지 확인- Python에서
saved_model_cli show --dir my_model/saved_model --all
명령어를 실행하여 모델을 정상적으로 로드할 수 있는지 확인 - C++ 코드에서
TF_LoadSessionFromSavedModel()
에 올바른 경로가 전달되었는지 확인
2. 입력 데이터 차원 오류
✅ 오류 메시지 예시:
Invalid argument: Input tensor shape does not match model expected shape
✅ 원인 분석:
- 모델이 기대하는 입력 차원과 전달한 입력 데이터 차원이 불일치
- OpenCV로 이미지 전처리 시 크기 조정이 잘못됨
TF_Tensor
생성 시 데이터 타입이 맞지 않음
✅ 해결 방법:
TF_GraphOperationByName()
을 사용하여 입력 노드의 기대 차원을 확인- Python에서
saved_model_cli
를 실행하여 모델의 입력 차원을 확인
saved_model_cli show --dir my_model/saved_model --all
- C++에서 입력 차원을 출력하여 확인
std::cout << "Expected shape: [1, 224, 224, 3], Given: " << input_tensor_dims << std::endl;
✅ 입력 차원을 맞추는 코드 예제:
std::vector<int64_t> dims = {1, 224, 224, 3}; // 올바른 차원 설정
TF_Tensor* input_tensor = CreateTensor(TF_FLOAT, dims, image.data, image.total() * image.elemSize());
3. 메모리 누수 및 크래시 문제
✅ 증상:
- 프로그램이 장시간 실행될 때 메모리 사용량이 점진적으로 증가
TF_Tensor
또는TF_Session
을 삭제하지 않아 메모리 누수 발생
✅ 해결 방법:
모든 동적 객체(TF_Tensor
, TF_Session
, TF_Graph
)를 사용한 후 반드시 해제해야 함.
✅ 메모리 해제 코드 예제:
TF_DeleteTensor(input_tensor);
TF_DeleteTensor(output_tensor);
TF_DeleteSession(session, status);
TF_DeleteGraph(graph);
TF_DeleteStatus(status);
4. 모델 실행 속도 저하
✅ 증상:
- CPU에서 모델 실행 속도가 너무 느림
- GPU 사용이 활성화되지 않음
✅ 해결 방법:
- Frozen Graph 사용: SavedModel 대신
tf.graph_util.convert_variables_to_constants_v2
로 변환 - 배치 크기 조정: 한 번에 여러 개의 입력을 처리하도록 모델 수정
- 멀티스레딩 적용: 여러 개의 요청을 병렬 처리하도록 코드 수정
✅ 배치 처리 예제:
std::vector<int64_t> dims = {8, 224, 224, 3}; // 8개의 이미지를 동시에 처리
TF_Tensor* input_tensor = CreateTensor(TF_FLOAT, dims, batch_data, batch_size);
5. GPU 가속이 작동하지 않는 문제
✅ 오류 메시지 예시:
No GPU devices found
✅ 원인 분석:
- CUDA 및 cuDNN이 올바르게 설치되지 않음
- TensorFlow GPU 버전이 올바르게 로드되지 않음
✅ 해결 방법:
nvidia-smi
명령어를 실행하여 GPU가 정상적으로 인식되는지 확인TF_SetConfig()
를 사용하여 GPU 사용 설정 추가
TF_SessionOptions* session_options = TF_NewSessionOptions();
TF_SetConfig(session_options, gpu_options, sizeof(gpu_options), status);
6. 모델 실행 후 예측 결과가 이상함
✅ 증상:
- 모든 입력 데이터에 대해 같은 결과가 출력됨
- 예측 결과가 엉뚱한 값으로 나옴
✅ 해결 방법:
- 모델 출력 노드가 올바른지 확인
TF_Operation* output_op = TF_GraphOperationByName(graph, "Predictions/Softmax");
- 데이터 정규화 확인 (0~1 범위로 변환 필요)
image.convertTo(image, CV_32FC3, 1.0f / 255.0f);
- 출력 텐서의 값을 직접 출력하여 확인
float* output_data = static_cast<float*>(TF_TensorData(output_tensor));
for (int i = 0; i < 10; i++) {
std::cout << "Class " << i << ": " << output_data[i] << std::endl;
}
7. 디버깅을 위한 로깅 추가
TensorFlow C API는 자체적인 디버깅 기능이 부족하므로, 출력 로그를 추가하여 문제를 추적하는 것이 중요합니다.
✅ 디버깅을 위한 로그 추가 코드 예제:
TF_Operation* input_op = TF_GraphOperationByName(graph, "input_node");
if (!input_op) {
std::cerr << "Error: Input node not found!" << std::endl;
}
✅ 모델이 실행될 때 입력 텐서의 값 확인:
std::cout << "First 5 input values: ";
float* input_data = static_cast<float*>(TF_TensorData(input_tensor));
for (int i = 0; i < 5; i++) {
std::cout << input_data[i] << " ";
}
std::cout << std::endl;
✅ 출력 결과 확인:
std::cout << "Model Prediction Result: " << output_data[0] << std::endl;
8. 정리
✅ TensorFlow C API 사용 시 발생할 수 있는 주요 문제와 해결 방법을 정리
✅ 모델 로드 오류 해결 방법
✅ 입력 데이터 차원 문제 및 해결 방법
✅ 메모리 누수 방지를 위한 메모리 관리 방법
✅ 속도 최적화를 위한 GPU 활용 및 배치 처리 방법
✅ 디버깅을 위한 로깅 추가 기법
이제 TensorFlow C API를 안정적으로 사용할 수 있도록 문제 해결 및 디버깅 방법을 익혔습니다!
다음 섹션에서는 전체 내용을 요약하며, 실전 적용을 위한 최종 정리를 진행하겠습니다.
요약
이번 기사에서는 TensorFlow C API를 활용하여 C++에서 머신러닝 모델을 서빙하는 방법을 상세히 다루었습니다.
Python 없이도 TensorFlow 모델을 로드하고 실행할 수 있도록, 모델 준비부터 REST API 서버 구축, 성능 최적화 및 디버깅 방법까지 실전 예제와 함께 설명했습니다.
✅ 핵심 정리:
- TensorFlow C API 개요
- Python 없이 C++에서 TensorFlow 모델을 실행할 수 있는 경량 API
- TensorFlow 모델 로드 방법
SavedModel
과Frozen Graph
를 사용하여 C++에서 모델을 로드
- 입력 데이터 전처리 및 TF_Tensor 변환
- OpenCV를 사용하여 이미지를 전처리하고,
TF_Tensor
로 변환하여 모델에 입력
- C++ 코드에서 TensorFlow 모델 실행
TF_SessionRun()
을 사용하여 모델을 실행하고, 결과를 출력
- TensorFlow C API 성능 최적화 기법
- 멀티스레딩을 활용한 병렬 처리
- Frozen Graph 변환을 통한 그래프 최적화
- GPU 가속 활성화 및 배치 처리
- 실전 예제: 이미지 분류 모델 서빙
- MobileNet V2 모델을 활용하여 C++에서 실시간 이미지 분류 수행
- TensorFlow C API 기반 REST API 서버 구축
- Crow 프레임워크를 사용하여 REST API 서버를 구현하고 클라이언트에서 HTTP 요청으로 모델 호출
- 디버깅과 문제 해결
- 모델 로드 오류, 입력 데이터 차원 불일치, 메모리 누수 문제 해결
- 속도 저하 원인 분석 및 GPU 가속 활성화 방법
- 디버깅을 위한 로깅 및 모델 실행 결과 확인 기법
💡 실전 적용 시 고려할 점:
- C++ 환경에서 TensorFlow 모델을 실행하려면 적절한 입력 전처리와 그래프 최적화가 필수
- 멀티스레딩과 배치 처리를 활용하면 서버 응답 속도 향상 가능
- 실시간 서빙을 위해서는 REST API 서버와 GPU 가속을 병행하는 것이 효과적
이제 TensorFlow C API를 활용하여 고성능 C++ 기반 머신러닝 모델 서빙이 가능합니다!
이 기사를 바탕으로 자신만의 AI 기반 애플리케이션을 개발하고 배포할 수 있습니다. 🚀