728x170
해당 글은 Tensorflow.js에 대해 메모리를 확인하는 방법과 메모리를 관리 및 개선하는 방법에 대한 글입니다.
1) Tensorflow 메모리 정리가 필요한 이유
💡 Tensorflow 메모리 정리가 필요한 이유
- 공식 사이트 내에 아래와 같은 글이 있습니다.
- "WebGL 백엔드를 사용할 때 한 가지 주의해야 할 점은 명시적인 메모리 관리가 필요하다는 것입니다. 텐서 데이터가 궁극적으로 저장되는 WebGLTextures는 브라우저에서 자동으로 가비지 모음이 되지 않습니다.”
- 즉, Tensorflow 모델 및 연산에 사용한 데이터에 대해서는 메모리에 남아있고, 이에 대한 정리를 하지않으면 메모리에 대한 부하가 발생할 수 있다는 말입니다.
- 이를 기반으로 구성한 프로젝트에서 메모리 관리를 하지 않았을 경우 수행되는 '10분당 디바이스의 메모리가 1GB'씩 증가하는 크리티컬한 문제가 있었습니다. 이에 따라 메모리 정리 이후 10분당 50 ~ 100MB로 줄이는 작업을 하여 경량화를 했을 만큼 사용하는 만큼 중요한 작업 중 하나 입니다.
2) Tensorflow 메모리 측정 방법 : tf.memory()
💡 Tensorflow 메모리를 측정하는 방법은 공식 사이트에 명시된 tf.memory() 함수를 통해서 측정이 가능합니다.
💡 함수 내에서 반환하고 있는 numTensors를 통하여서 현재 디바이스 내에 메모리상에 몇 개의 텐서를 가지고 있는지 확인을 할 수 있습니다.
💡중요한 사항 중 하나는 tf.함수() 당 1개의 메모리를 사용하는것 뿐만 아니라 Tensorflow.js를 기반으로 구성된 facemesh, blazeface와 같은 모델을 불러오는 경우에도 역시 텐서가 존재하므로 반드시 사용한 즉시 메모리를 정리해주어야 합니다.
1. tf.memory()
💡 tf.memory()
- TensorFlow.js 메모리 사용에 대한 정보를 제공하는 메서드입니다.
- 이 메서드는 TensorFlow.js 라이브러리의 메모리 관리에 대한 통계를 반환합니다. 이를 통해 현재 메모리 사용량, 할당된 메모리 양, 사용 가능한 메모리 양 등을 확인할 수 있습니다.
💡 [참고] tf.memory() 해당 메서드의 속성
속성 | 설명 |
numTensors | 현재 할당된 텐서의 개수를 나타냅니다. |
numBytes | 현재 할당된 텐서의 총 메모리 사용량(Bytes)를 나타냅니다. |
unreliable | 현재 메모리 사용량 및 할당된 메모리 양이 신뢰할 수 없는지 여부를 나타냅니다. 이 값이 true인 경우, 메모리 사용량이 실제 값보다 크거나 작을 수 있습니다. |
reasons | 메모리 사용량이나 할당된 메모리 양의 신뢰성이 떨어지는 이유에 대한 설명을 담은 배열입니다. |
numDataBuffers | 현재 할당된 데이터 버퍼의 개수를 나타냅니다. |
numBytesInGPU | GPU 메모리에서 할당된 총 메모리 사용량(Bytes)을 나타냅니다. |
💡 [적용 예시]
- 하단의 예시는 React-native 내에서 Tensorflow.js를 사용한 예시이며, 카메라에 얼굴이 측정되어서 텐서 이미지를 반환받는 구간에서의 예제로 하였습니다. 아래의 예시를 보는것 처럼 tf.Tenosr 관련된 함수를 사용하게 되면 TensorMemory를 차지하게 되는것을 확인하였습니다. (텐서 메모리가 1개가 증가하였습니다)
- tf. 메서드를 사용하는 경우 텐서 메모리가 1개씩 증가한다고 생각하시면 됩니다.
console.log(`1. 현재 시점에 메모리에 있는 텐서 수 ${tf.memory().numTensors}`)
const _imageToTensor: tf.Tensor = images.next().value; // 텐서 이미지 생성
console.log(`2. 현재 시점에 메모리에 있는 텐서 수 ${tf.memory().numTensors}`)
// console.log 결과값
// 1. 현재 시점에 메모리에 있는 텐서 수 513
// 2. 현재 시점에 메모리에 있는 텐서 수 514
3) Tensorflow 메모리 소멸 방법 -1 : tf.dispose(), tf.tidy()
💡 해당 부분은 tf.Tensor로 사용된 '단건', '다건'에 대한 메모리에 대해서 소멸을 시키는 메서드를 설명합니다.
1. tf.dispose()
💡 tf.dispose()
- TensorFlow에서 사용되는 함수로, 메모리 누수를 방지하기 위해 '단건'의 Tensor에 대해 사용됩니다.
- 이 함수는 TensorFlow의 텐서 객체 및 연산 객체를 메모리에서 해제하여 자원을 확보하는 역할을 합니다. 주로 TensorFlow 그래프나 세션을 사용한 후에 메모리를 해제해야 할 때 사용됩니다.
console.log(`1. 현재 시점에 메모리에 있는 텐서 수 ${tf.memory().numTensors}`)
const _imageToTensor: tf.Tensor = images.next().value; // 텐서 이미지 생성
console.log(`2. 현재 시점에 메모리에 있는 텐서 수 ${tf.memory().numTensors}`)
_imageToTensor.dispose();
// or
tf.dispose(_imageToTensor);
console.log(`3. 현재 시점에 메모리에 있는 텐서 수 ${tf.memory().numTensors}`)
// console.log 결과값
// 1. 현재 시점에 메모리에 있는 텐서 수 513
// 2. 현재 시점에 메모리에 있는 텐서 수 514
// 3. 현재 시점에 메모리에 있는 텐서 수 513
2. tf.tidy()
💡tf.tidy()
- TensorFlow.js에서 사용되는 함수로, 메모리 누수를 방지하기 위해 '다건'의 Tensor에 대해 사용됩니다.
- 이 함수는 TensorFlow.js의 텐서 객체 및 연산 객체를 메모리에서 해제하여 자원을 확보하는 역할을 합니다. 이를 사용하면 TensorFlow.js 코드 블록 내에서 생성된 텐서들을 자동으로 추적하고 해제해줍니다.
[적용 예시]
💡 해당 부분은 _indexingResult라는 변수와 _resizeFace 두 군데서 tf.Tensor를 사용하였습니다.
tf.dispose를 이용하여서 삭제도 가능하지만, 해당 tidy() 함수를 이용하게 되면 해당 영역 안에 사용된 메모리에 대해서 소멸을 시킨 뒤 return으로 해당 값을 전달받아서 이를 처리하도록 도와줍니다.
/**
* 텐서 메모리 관리 이전
*/
console.log(`1. 현재 시점에 메모리에 있는 텐서 수 ${tf.memory().numTensors}`)
const _indexingResult: tf.Tensor = tf.slice(
tensorImage,
[_yw1, _xw1, 0],
[_yw2 - _yw1, _xw2 - _xw1, 3]
);
const _resizeFace: tf.Tensor3D | tf.Tensor4D = _indexingResult.resizeBilinear([64, 64]);
console.log(`2. 현재 시점에 메모리에 있는 텐서 수 ${tf.memory().numTensors}`)
// console.log 결과값
// 1. 현재 시점에 메모리에 있는 텐서 수 143
// 2. 현재 시점에 메모리에 있는 텐서 수 145
/**
* 텐서 메모리 관리 이후
*/
console.log(`1. 현재 시점에 메모리에 있는 텐서 수 ${tf.memory().numTensors}`)
const _resizeFace: tf.Tensor<tf.Rank> = tf.tidy(() => {
const _indexingResult: tf.Tensor = tf.slice(
tensorImage,
[_yw1, _xw1, 0],
[_yw2 - _yw1, _xw2 - _xw1, 3]
);
return tf.Tensor3D | tf.Tensor4D = _indexingResult.resizeBilinear([64, 64]);
});
console.log(`2. 현재 시점에 메모리에 있는 텐서 수 ${tf.memory().numTensors}`)
// console.log 결과값
// 1. 현재 시점에 메모리에 있는 텐서 수 143
// 2. 현재 시점에 메모리에 있는 텐서 수 143
4) Tensorflow 메모리 소멸 방법 -2 : tf.engine().startScope(), tf.engine().endScope()
💡 단건, 다건에 대한 소멸 방법 이외에 일괄적으로 한 번에 텐서 메모리를 관리하는 방법에 대해 확인해 봅니다.
1. tf.engine()
💡 tf.engine()
- 모든 텐서와 백엔드를 추적하는 글로벌 엔진을 반환하는 함수입니다.
(It returns the global engine that keeps track of all tensors and backends.)
2. tf.engine(). startScope()
💡 tf.engine().startScope()
- 모든 텐서와 백엔드를 추적하는 글로벌 엔진을 열어두는 역할을 수행합니다.
- 텐서 메모리를 관리 시작을 알리는 메서드와 같습니다. 항상 tf.engine().endScope()와 함께 사용해야하며, 해당 함수를 시작하는 시점부터 endScope() 함수를 사용하는 시점까지 사용된 Tensor의 연산에 대한 메모리를 소멸시킵니다.
[사용 예시]
- 해당 engine()이 시작하는 시점을 tensorflow Ready와 동일한 setBackend()를 시작하는 시점에 해당 함수를 적용하였습니다.
/**
* 초기 세팅
* [기능-1] 카메라 권한 체크
* [기능-2] Tensorflow.js Ready
* [기능-3] blazeface 모델을 불러와서 측정을 수행한다
* @returns {Promise<void>}
*/
const fn_initSetting = async (): Promise<void> => {
let _isTensorReady: boolean = false;
let _blazeface: blazeface.BlazeFaceModel | null = null;
// [기능-1] 카메라 권한 체크
const { status } = (await Camera.requestCameraPermissionsAsync())
if (status !== 'granted') {
throw Error(`[-] 카메라의 권한 요청이 승인 되지 않았습니다:`);
}
// [기능-2] Tensorflow.js Ready
await tf.setBackend('rn-webgl')
.then(() => {
_isTensorReady = true;
tf.engine().startScope(); // 모든 텐서와 백엔드를 추적하는 엔진 영역을 만듬.
console.log("[+] Load Tensorflow.js....");
})
.catch((error) => {
throw Error(`[-] Tensorflow Ready Error: ${error}`);
});
// [기능-3] blazeface 모델을 불러와서 측정을 수행한다
await blazeface.load()
.then((_blazefaceModel) => {
_blazeface = _blazefaceModel
})
.catch((error) => {
throw Error(`[-] Blazeface Model Load Error: ${error}`);
});
// [기능-4] 로드 된 값 State에 세팅
setInitSetting({
isTfReady: _isTensorReady,
blazefaceModel: _blazeface
});
}
3. tf.engine(). endScope()
💡 tf.engine(). endScope()
- 모든 텐서와 백엔드를 추적하는 글로벌 엔진을 내의 텐서를 모두 소멸시키는 역할을 수행합니다.
- 텐서 startScope()를 수행한 시점부터 이후에 텐서 사용한 것에 대해서 모두 메모리 상에 소멸시킵니다.
- startScope()를 수행하지 않고 endScope()를 수행할 경우 오류가 발생합니다.
[사용 예시]
해당 예제에서는 blazeface로 얼굴을 측정하는 부분을 작성한 코드입니다.
해당 부분에서 오류가 발생 시 endScope()를 수행하도록 처리를 하였습니다.
추가로 페이지 이동을 할 때도 endScope()를 수행해주셔야 합니다.
/**
* blazeFace 모델을 통하여 얼굴 측정
* @param tensorImage
* @returns {boolean} 예측 시 true, 미 예측 시 false 반환
*/
const fn_estimateBlazeFace = async (tensorImage: tf.Tensor3D): Promise<boolean> => {
let _isEstimate: boolean = false;
// 텐서 이미지가 존재하는지 체크
if (tensorImage && initSetting.blazefaceModel) {
// [기능-2] blazeface 모델을 통하여 사람 얼굴을 측정
await initSetting.blazefaceModel.estimateFaces(tensorImage, false, true)
.then(async (_predictions: blazeface.NormalizedFace[]) => {
console.log(`[+] 측정 얼굴 수 [${_predictions.length}]`);
// [CASE1-1] 얼굴을 예측 한 경우 Canvas에 얼굴을 그려준다.
if (_predictions.length > 0) {
await fn_drawRect(_predictions); //조정된 위치에 대해서 캔버스에 그려주기
_isEstimate = false;
}
// [CASE1-2] 얼굴을 예측하지 못한 경우에 Canvas의 내용을 초기화 한다.
else {
if (canvasRef.current) canvasRef.current.getContext("2d").clearRect(0, 0, CAMERA_SIZE.width, CAMERA_SIZE.height);
_isEstimate = false;
}
})
.catch((error: string) => {
tf.engine().endScope();
throw Error(`blazeface Estimate Error : ${error}`);
})
}
return _isEstimate;
}
[참고] React-native Tensorflow의 Blazeface 모델 사용 방법 작성글
[참고] Tensorflow Blazeface 적용 예시 및 메모리 관리 적용 Github : https://github.com/adjh54ir/rn-tfjs-face-detection
오늘의 결론
해당 기능을 사용하는 최종 목표는 페이지를 이동할 때 나 루프를 반복할 때, 한 디바이스에 텐서 메모리가 계속 누적이 되어 속도가 저하가 된다는 문제를 가지고 있기 때문에 해당 부분을 적용하였습니다.
오늘도 감사합니다😀
그리드형
'React & React Native > 라이브러리 활용' 카테고리의 다른 글
[RN] react-native-cli에서 expo-cli 모듈 사용하기 (0) | 2022.05.15 |
---|---|
[RN] React Native 자이로센서를 활용한 자이로스코프, 디바이스 모션 이해하기: expo-sensors (0) | 2022.05.15 |
[RN] React Native 앱 상태 관리 이해 및 구성 방법: AppState (0) | 2022.03.27 |
[RN] React Native Tensorflow의 blazeface 모델을 이용한 얼굴 감지 (6) | 2022.03.09 |
[RN] React Native expo-sqlite 이해 및 설정 방법 -1 : 환경 설정 및 DB, 테이블 생성, 기본 트랜잭션 (0) | 2022.03.01 |