레슨 9 / 10·5개 토픽
모델 저장과 최적화
학습된 모델을 저장하고 불러오는 방법과 브라우저 환경에서의 성능 최적화 기법을 다룹니다. 메모리 관리, 백엔드 선택, 배치 처리 등 실전에서 필수적인 최적화 전략을 학습합니다.
model.save() 저장 방식
javascript
import * as tf from '@tensorflow/tfjs';
// 1. localStorage 저장 (소규모 모델, ~5MB)
await model.save('localstorage://my-model');
// 2. IndexedDB 저장 (대규모 모델 권장, ~수백MB)
await model.save('indexeddb://my-model');
// 3. 파일 다운로드 (model.json + weights.bin)
await model.save('downloads://my-model');
// 4. HTTP 서버 업로드
await model.save('http://localhost:3000/api/save-model');
// 저장된 모델 목록 확인
const models = await tf.io.listModels();
console.log(models);
// { 'localstorage://my-model': { dateSaved, modelTopologyBytes, ... } }
// 저장된 모델 삭제
await tf.io.removeModel('localstorage://my-model');
// 모델 복사
await tf.io.copyModel(
'localstorage://my-model',
'indexeddb://my-model-backup'
);tf.loadLayersModel() 모델 로드
javascript
// localStorage에서 로드
const model = await tf.loadLayersModel('localstorage://my-model');
// IndexedDB에서 로드
const model2 = await tf.loadLayersModel('indexeddb://my-model');
// HTTP URL에서 로드 (정적 호스팅)
const model3 = await tf.loadLayersModel(
'https://example.com/models/my-model/model.json'
);
// 로드 진행 상태 모니터링
const model4 = await tf.loadLayersModel(
'https://example.com/models/my-model/model.json',
{
onProgress: (fraction) => {
console.log("로딩: " + (fraction * 100).toFixed(0) + "%");
progressBar.style.width = (fraction * 100) + "%";
},
}
);
// GraphModel 로드 (TF SavedModel 변환 모델)
const graphModel = await tf.loadGraphModel(
'https://example.com/models/tfjs-model/model.json'
);tf.tidy() 메모리 관리
javascript
// tf.tidy()로 중간 텐서 자동 해제
const result = tf.tidy(() => {
const a = tf.tensor([1, 2, 3]);
const b = tf.tensor([4, 5, 6]);
const c = a.add(b); // 중간 텐서
const d = c.mul(tf.scalar(2)); // 중간 텐서
return d; // 반환값만 유지, a, b, c는 자동 해제
});
// 메모리 상태 확인
console.log(tf.memory());
// { numTensors: 1, numDataBuffers: 1, numBytes: 12, ... }
// 주의: tf.tidy() 안에서는 async 사용 불가
// 비동기 작업 시 수동 dispose() 사용
async function predictWithCleanup(model, input) {
const tensor = tf.tensor(input);
const prediction = model.predict(tensor);
const result = await prediction.data();
// 수동 메모리 해제
tensor.dispose();
prediction.dispose();
return result;
}
// 메모리 누수 디버깅
const before = tf.memory().numTensors;
// ... 작업 수행 ...
const after = tf.memory().numTensors;
if (after > before) {
console.warn("텐서 누수 감지:", after - before, "개");
}tf.setBackend() 백엔드 선택
javascript
// 사용 가능한 백엔드 확인
console.log("현재 백엔드:", tf.getBackend());
// 백엔드 변경 (우선순위: webgl > wasm > cpu)
await tf.setBackend('webgl'); // GPU 가속 (기본, 가장 빠름)
await tf.setBackend('wasm'); // WebAssembly (GPU 없는 환경)
await tf.setBackend('cpu'); // CPU (가장 느림, 폴백)
// WASM 백엔드 사용 시 추가 설정
import '@tensorflow/tfjs-backend-wasm';
import { setWasmPaths } from '@tensorflow/tfjs-backend-wasm';
setWasmPaths('https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-wasm/dist/');
await tf.setBackend('wasm');
await tf.ready();
// 백엔드별 성능 벤치마크
async function benchmark(backendName) {
await tf.setBackend(backendName);
await tf.ready();
const input = tf.randomNormal([1, 224, 224, 3]);
const start = performance.now();
for (let i = 0; i < 10; i++) {
const pred = model.predict(input);
await pred.data();
pred.dispose();
}
const elapsed = performance.now() - start;
input.dispose();
console.log(backendName + ": " + (elapsed / 10).toFixed(1) + "ms/회");
}배치 처리와 성능 최적화
javascript
// 1. 배치 예측으로 처리량 향상
async function batchPredict(model, images) {
// 개별 이미지를 배치로 합치기
const batch = tf.stack(images.map(img =>
tf.browser.fromPixels(img).resizeBilinear([224, 224]).div(255.0)
));
const predictions = model.predict(batch);
const results = await predictions.array();
batch.dispose();
predictions.dispose();
return results;
}
// 2. tf.nextFrame()으로 UI 블로킹 방지
async function trainWithUIUpdate(model, xs, ys, epochs) {
for (let i = 0; i < epochs; i++) {
const history = await model.fit(xs, ys, {
epochs: 1,
batchSize: 32,
});
// UI 업데이트를 위해 프레임 양보
await tf.nextFrame();
statusEl.textContent =
"에포크 " + (i + 1) + "/" + epochs +
" - loss: " + history.history.loss[0].toFixed(4);
}
}
// 3. WebGL 텍스처 캐싱
tf.env().set('WEBGL_DELETE_TEXTURE_THRESHOLD', 0);
// 4. 모델 워밍업 (첫 추론은 느리므로 더미 입력으로 사전 실행)
async function warmupModel(model, inputShape) {
const dummy = tf.zeros([1, ...inputShape]);
const warmup = model.predict(dummy);
await warmup.data();
dummy.dispose();
warmup.dispose();
console.log("모델 워밍업 완료");
}💡
tf.tidy()는 동기 코드에서만 사용할 수 있으며, 비동기 작업에서는 dispose()를 직접 호출해야 합니다. WebGL 백엔드가 가장 빠르지만 모바일에서는 WASM이 더 안정적일 수 있습니다. tf.memory()로 텐서 누수를 주기적으로 확인하고, tf.nextFrame()으로 긴 연산 중 UI 프리징을 방지하세요.