Merry99's picture
Spaces์šฉ ์ฝ”๋“œ๋งŒ ํฌํ•จ (๋ชจ๋ธ ํŒŒ์ผ ์ œ์™ธ)
2b83ee8

/model API

๋ชจ๋ธ ๋‹ค์šด๋กœ๋“œ ์—”๋“œํฌ์ธํŠธ๋Š” ์ตœ์‹  ๋ชจ๋ธ๊ณผ ํŠน์ • ๋ฒ„์ „์˜ ๋ชจ๋ธ์„ ๋ชจ๋‘ ์ œ๊ณตํ•˜๋ฉฐ, ์‘๋‹ต ํ—ค๋”๋ฅผ ํ†ตํ•ด ์‹ค์ œ ๋ฒ„์ „ ์ •๋ณด๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์š”์ฒญ ํ˜•์‹

GET /model
GET /model?version={๋ฒˆํ˜ธ}
GET /model?version={๋ฒˆํ˜ธ}&filename={ํŒŒ์ผ๋ช…}
ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž… ์„ค๋ช…
version (์„ ํƒ) int ์ƒ๋žตํ•˜๊ฑฐ๋‚˜ ๋นˆ ๊ฐ’์ด๋ฉด ์ตœ์‹  ๋ชจ๋ธ. ์ง€์ •ํ•˜๋ฉด ํ•ด๋‹น ๋ฒ„์ „ ํ™•์ธ ํ›„ ๋‹ค์šด๋กœ๋“œ.
filename (์„ ํƒ) string ๋‚ด๋ ค๋ฐ›์„ ํŒŒ์ผ๋ช…. ๊ธฐ๋ณธ๊ฐ’์€ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ HF_E2E_MODEL_FILE (๊ธฐ๋ณธ cnn_gru_fatigue.tflite).

์‘๋‹ต

  • ๋ณธ๋ฌธ: ์š”์ฒญํ•œ ๋ชจ๋ธ ๋ฐ”์ด๋„ˆ๋ฆฌ (์˜ˆ: .tflite, .keras, ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋“ฑ)
  • ํ—ค๋”:
    • X-Model-Version: ์‹ค์ œ ๋‹ค์šด๋กœ๋“œ๋œ ๋ชจ๋ธ ๋ฒ„์ „
    • X-Model-Filename: ๋ฐ˜ํ™˜๋œ ํŒŒ์ผ๋ช…
  • ์—๋Ÿฌ:
    • 404 โ€“ ์š”์ฒญํ•œ ๋ฒ„์ „์ด ํ˜„์žฌ model_version๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ manifest์— ์กด์žฌํ•˜์ง€ ์•Š์„ ๋•Œ
    • 500 โ€“ Hugging Face Hub ๋‹ค์šด๋กœ๋“œ ์‹คํŒจ ๋“ฑ ๋‚ด๋ถ€ ์˜ค๋ฅ˜

๋™์ž‘ ๊ทœ์น™

  1. ์„œ๋ฒ„๋Š” training_state.json์˜ model_version ๊ฐ’์„ ์ฝ์–ด ํ˜„์žฌ ํ—ˆ์šฉ ๊ฐ€๋Šฅํ•œ ์ตœ๋Œ€ ๋ฒ„์ „์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  2. version์„ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ตœ์‹  ๋ชจ๋ธ(ํ˜„์žฌ ๋ฒ„์ „)์„ ๋‹ค์šด๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.
  3. version์„ ์ง€์ •ํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ ํ˜„์žฌ model_version ์ดํ•˜์ธ์ง€ ํ™•์ธํ•œ ๋’ค, ๋™์ผํ•œ ํŒŒ์ผ๋ช…์„ ๋‚ด๋ ค์ค๋‹ˆ๋‹ค(๋ฒ„์ „๋ณ„๋กœ ํŒŒ์ผ๋ช…์„ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค).
  4. ์š”์ฒญํ•œ ๋ฒ„์ „์ด ํ˜„์žฌ ๋ฒ„์ „๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด 404๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ ์˜ˆ์‹œ

์ตœ์‹  ๋ชจ๋ธ ๋‹ค์šด๋กœ๋“œ

curl -L -o cnn_gru_fatigue_latest.tflite \
  "https://merry99-musclecare-train-ai.hf.space/model"

๋ฒ„์ „ 3 ๋ชจ๋ธ ๋‹ค์šด๋กœ๋“œ

curl -L -o cnn_gru_fatigue_v3.tflite \
  "https://merry99-musclecare-train-ai.hf.space/model?version=3"

๋ฒ„์ „ 3 ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋‹ค์šด๋กœ๋“œ

curl -L -o metadata_v3.json \
  "https://merry99-musclecare-train-ai.hf.space/model?version=3&filename=cnn_gru_fatigue_metadata.json"

ํ—ค๋” ํ™•์ธ

curl -I "https://merry99-musclecare-train-ai.hf.space/model?version=3"

์‘๋‹ต ํ—ค๋” ์˜ˆ์‹œ:

X-Model-Version: 3
X-Model-Filename: cnn_gru_fatigue.tflite

์ฃผ์˜ ์‚ฌํ•ญ

  • training_state.json์˜ model_version ๊ฐ’์ด ๊ธฐ์ค€์ด ๋˜๋ฉฐ, ๊ทธ๋ณด๋‹ค ๋†’์€ ๋ฒ„์ „์„ ์š”์ฒญํ•˜๋ฉด 404๊ฐ€ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.
  • ๋ฒ„์ „๋ณ„๋กœ ๋‹ค๋ฅธ ํŒŒ์ผ์„ ์œ ์ง€ํ•˜์ง€ ์•Š๊ณ , ๊ฐ™์€ ํŒŒ์ผ๋ช…์„ ๋‚ด๋ ค์ฃผ๋˜ ํ—ค๋”(X-Model-Version)๋กœ ์‹ค์ œ ๋ฒ„์ „์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ์‹คํŒจ(์˜ˆ: 404) ์‹œ JSON ์‘๋‹ต์ด ๋‚ด๋ ค์˜ค๋ฏ€๋กœ, ํด๋ผ์ด์–ธํŠธ๋Š” ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋จผ์ € ํ™•์ธํ•œ ๋’ค 200์ผ ๋•Œ๋งŒ body๋ฅผ ํŒŒ์ผ๋กœ ์ €์žฅํ•˜์„ธ์š”.

Flutter ์˜ˆ์‹œ (Dio):

final response = await dio.get<List<int>>(
  'https://merry99-musclecare-train-ai.hf.space/model',
  options: Options(responseType: ResponseType.bytes),
);

if (response.statusCode == 200) {
  final version = response.headers.value('X-Model-Version');
  final filename = response.headers.value('X-Model-Filename') ?? 'model.tflite';
  await File('/path/$filename').writeAsBytes(response.data!);
} else {
  final errorText = utf8.decode(response.data ?? []);
  // ์—๋Ÿฌ ์ฒ˜๋ฆฌ
}
  • Space ํ™˜๊ฒฝ ๋ณ€์ˆ˜ HF_E2E_MODEL_TOKEN, HF_E2E_MODEL_REPO_ID๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ •๋ผ ์žˆ์–ด์•ผ /model ๋ฐ /trigger๊ฐ€ ์ •์ƒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋ธ ์ž…๋ ฅ ์‚ฌ์–‘ (Flutter ์ฐธ๊ณ )

  • ์ž…๋ ฅ ํ˜•์ƒ: (batch_size, input_dim)์ด๋ฉฐ ๊ธฐ๋ณธ input_dim = 10 (FEATURE_COLUMNS) + embedding_dim.
  • FEATURE_COLUMNS: rms_acc, rms_gyro, mean_freq_acc, mean_freq_gyro, entropy_acc, entropy_gyro, jerk_mean, jerk_std, stability_index, fatigue_prev.
  • user_emb: ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์˜ embedding_dim๊ณผ ๋™์ผํ•œ ๊ธธ์ด. ๋ถ€์กฑํ•˜๋ฉด ๋’ค๋ฅผ 0.0f๋กœ ํŒจ๋”ฉ.
  • ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ(cnn_gru_fatigue_metadata.json)์˜ scaler.mean, scaler.scale๋กœ ํ‘œ์ค€ํ™”ํ•œ ๋’ค ๋ชจ๋ธ์— ์ „๋‹ฌ.

Flutter์—์„œ ์‹คํ–‰ ์ˆœ์„œ

  • ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋กœ๋“œ: JSON์—์„œ feature_columns, scaler.mean, scaler.scale, embedding_dim, input_dim์„ ์ฝ๋Š”๋‹ค.
  • ํŠน์ง• ์ถ”์ถœ: ์ธก์ • ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ์–ป์€ ์œˆ๋„์šฐ์—์„œ 10๊ฐœ ํ”ผ์ฒ˜ ๊ฐ’์„ ๊ณ„์‚ฐํ•œ๋‹ค.
  • ํ‘œ์ค€ํ™”: (value - mean) / scale์„ ์ˆ˜ํ–‰ํ•˜๋˜ scale์ด 0์ด๋ฉด 0์œผ๋กœ ๋Œ€์ฒด.
  • ์ž…๋ ฅ ๋ฒกํ„ฐ ๊ตฌ์„ฑ: [์ •๊ทœํ™”๋œ 10๊ฐœ ํ”ผ์ฒ˜, user_emb(ํŒจ๋”ฉ ํฌํ•จ)]์„ ์ด์–ด ๋ถ™์—ฌ Float32List๋กœ ๋งŒ๋“ ๋‹ค.
  • TFLite ์‹คํ–‰: ์ž…๋ ฅ์„ [1, input_dim]์œผ๋กœ reshape ํ›„ interpreter.run(input, output)์„ ํ˜ธ์ถœํ•œ๋‹ค.
final meta = await loadMetadata(); // JSON ํŒŒ์‹ฑ: scaler, embedding_dim ๋“ฑ
final features = computeFeatureVector(); // ๊ธธ์ด 10, float
final userEmb = ensureEmbeddingLength(rawEmb, meta.embeddingDim); // ํŒจ๋”ฉ

final normalized = List<double>.generate(features.length, (i) {
  final scale = meta.scalerScale[i] == 0 ? 1.0 : meta.scalerScale[i];
  return (features[i] - meta.scalerMean[i]) / scale;
});

final inputVector = Float32List.fromList([
  ...normalized,
  ...userEmb.map((e) => e.toDouble()),
]);

final outputBuffer = Float32List(1);
interpreter.run(inputVector.reshape([1, inputVector.length]), outputBuffer);
final fatigueScore = outputBuffer[0];

์ฃผ์˜

  • ์ตœ์ดˆ ์ธก์ •๋ถ€ํ„ฐ ๋ฐ”๋กœ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๋” ์ด์ƒ 5๊ฐœ ์œˆ๋„์šฐ ๋ˆ„์ ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • fatigue_prev๋Š” ์ง์ „ ์ธก์ •์˜ ํ”ผ๋กœ๋„ ์ง€ํ‘œ๋กœ, ๊ฐ’์ด ์—†๋‹ค๋ฉด 0 ๋˜๋Š” ์ง์ „ ์˜ˆ์ธก์น˜๋กœ ์ดˆ๊ธฐํ™”ํ•ด ์ฃผ์„ธ์š”.
  • ํ”ผ์ฒ˜ ์ถ”์ถœ ๋กœ์ง๊ณผ ์ž„๋ฒ ๋”ฉ ์ฐจ์›์€ ๋ฐฑ์—”๋“œ ํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ๊ณผ ๋™์ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.