File size: 5,603 Bytes
2b83ee8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
## `/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`λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

### μ‚¬μš© μ˜ˆμ‹œ

#### μ΅œμ‹  λͺ¨λΈ λ‹€μš΄λ‘œλ“œ
```bash
curl -L -o cnn_gru_fatigue_latest.tflite \
  "https://merry99-musclecare-train-ai.hf.space/model"
```

#### 버전 3 λͺ¨λΈ λ‹€μš΄λ‘œλ“œ
```bash
curl -L -o cnn_gru_fatigue_v3.tflite \
  "https://merry99-musclecare-train-ai.hf.space/model?version=3"
```

#### 버전 3 메타데이터 λ‹€μš΄λ‘œλ“œ
```bash
curl -L -o metadata_v3.json \
  "https://merry99-musclecare-train-ai.hf.space/model?version=3&filename=cnn_gru_fatigue_metadata.json"
```

#### 헀더 확인
```bash
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):
```dart
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)`을 ν˜ΈμΆœν•œλ‹€.

```dart
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` λ˜λŠ” 직전 예츑치둜 μ΄ˆκΈ°ν™”ν•΄ μ£Όμ„Έμš”.
- ν”Όμ²˜ μΆ”μΆœ 둜직과 μž„λ² λ”© 차원은 λ°±μ—”λ“œ ν•™μŠ΅ νŒŒμ΄ν”„λΌμΈκ³Ό 동일해야 ν•©λ‹ˆλ‹€.