๐Understanding MLflow -MLOps์ ํ์ ๋๊ตฌ MLflow ์์๋ณด๊ธฐ?!!
๐ง (ํ๊ตญ์ด) MLflow ์์๋ณด๊ธฐ?!!
๐ ๋จธ์ ๋ฌ๋ ์คํ๋ถํฐ ๋ฐฐํฌ๊น์ง ํ ๋ฒ์ ๊ด๋ฆฌํ๋ MLOps ํ๋ซํผ!!!
์๊ณ ๋ฆฌ์ฆ ๊ฐ๋ฐ์, ํ์ดํผํ๋ผ๋ฏธํฐ์ ๋ณํ์ ๋ฐ๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋๊ฒ ์ค์ํ๋ฐ์!
๋จ์ํ ๋ฉ๋ชจ์ฅ์ ์ฐ๊ฑฐ๋ ์์ ๋ก ์ ๋ฆฌํ๋๊ฒ์ ๋๋ฌด ์์์ ์ด์ฃ !?
์ฐ๋ฆฌ๊ฐ ์ฝ๋๋ฅผ Git์ผ๋ก ๊ด๋ฆฌํ๋ฏ์ด,
MLflow๋ฅผ ํตํด ๋ค์ํ ์งํ๋ค์ ์ฒด๊ณ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์์ต๋๋ค!
์ฃผ์ ์ฐธ๊ณ ์๋ฃ:
- MLflow ๊ณต์ํ์ด์ง์ง
- Hidden Technical Debt in Machine Learning Systems (ML Technical Debt - 2015)
๐ง MLflow ๋ฑ์ฅ์ ๋ฐฐ๊ฒฝ
๋จธ์ ๋ฌ๋ ํ๋ก์ ํธ๋ ์ฝ๋๋ง์ผ๋ก๋ ๊ด๋ฆฌ๊ฐ ์ ๋ผ!
๋ฐ์ดํฐ, ํ์ดํผํ๋ผ๋ฏธํฐ, ๋ชจ๋ธ, ์ฑ๋ฅ ์งํ๊น์งโฆ ๋ชจ๋ ๊ฑธ ์ถ์ ํด์ผ ํด!!
ML ํ๋ก์ ํธ์ ๋ณต์ก์ฑ:
- 1990๋ ๋: ๋จ์ํ ํต๊ณ ๋ชจ๋ธ โ ์๋ ๊ด๋ฆฌ ๊ฐ๋ฅ
- 2000๋ ๋: ๋ณต์กํ ML ์๊ณ ๋ฆฌ์ฆ โ ์คํ ๊ด๋ฆฌ ํ์์ฑ ๋๋
- 2010๋ ๋: ๋ฅ๋ฌ๋ ํญ๋ฐ โ ์ฒด๊ณ์ ์ธ MLOps ๋๊ตฌ ํ์!
๐จ ๊ธฐ์กด ๋ฐฉ๋ฒ๋ค์ ํ๊ณ์
1๏ธโฃ ์๋ ์คํ ๊ด๋ฆฌ์ ๋ฌธ์ ๐
์ฌ๊ธฐ๊ฐ ์์ ๊ณต๊ฐ๋์ง ์๋์!?ใ ใ
- ์คํ ์ถ์ ๋ถ๊ฐ: โ์ด๋ค ํ๋ผ๋ฏธํฐ๋ก ์ด ๊ฒฐ๊ณผ๊ฐ ๋์์ง?โ
- ์ฌํ ๋ถ๊ฐ๋ฅ: โ์ ๋ฒ ์ฃผ ์คํ์ ๋ค์ ํด๋ณด์!โ โ ๋ถ๊ฐ๋ฅ
- ๊ฒฐ๊ณผ ๋น๊ต ์ด๋ ค์: Excel์ ์์ผ๋ก ๋ณต๋ถํ๋ ๋นํจ์จ
- ํ์ ์ด๋ ค์: ๊ฐ์ธ๋ณ๋ก ๋ค๋ฅธ ์คํ ๊ด๋ฆฌ ๋ฐฉ์
์์:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ์ ํต์ ์ธ ๋ฐฉ์์ ๋ฌธ์ ์ - ํ๋ผ๋ฏธํฐ ํ๋ ์ง์ฅ!
def train_model():
# a, b, c ํ๋ผ๋ฏธํฐ๋ฅผ 0~1๊น์ง 0.1์ฉ ํ
์คํธ (์ด 11x11x11 = 1331๋ฒ!)
for a in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]:
for b in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]:
for c in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]:
# ๋ชจ๋ธ ํ๋ จ
model = train(param_a=a, param_b=b, param_c=c)
accuracy = model.evaluate()
# ๊ฒฐ๊ณผ๋ฅผ ํ๋ฆฐํธ๋ง... ๐ญ
print(f"a={a}, b={b}, c={c} โ Accuracy: {accuracy}")
# ๋ฌธ์ : 1331๊ฐ์ ๊ฒฐ๊ณผ ์ค์์ ์ด๋ค ๊ฒ ์ต๊ณ ์๋์ง ๊ธฐ์ต ์๋จ!
# ๋ ๋ค๋ฅธ ๋ฌธ์ : ๋ด์ผ ์ปดํจํฐ ๊ป๋ค ์ผ๋ฉด ๋ชจ๋ ๊ฒฐ๊ณผ๊ฐ ์ฌ๋ผ์ง!
์ค์ ๋ก ๊ฒช๋ ๋ฌธ์ ๋ค:
- ๐ฑ 1331๊ฐ์ ์คํ ๊ฒฐ๊ณผ๊ฐ ํฐ๋ฏธ๋์ ์ญ ์ถ๋ ฅ๋จ
- ๐ โ์ด? a=0.7, b=0.3, c=0.9์ผ ๋ ์ฑ๋ฅ์ด ์ข์๋๋ฐโฆ ์ด๋๊ฐ์ง?โ
- ๐ป ์ปดํจํฐ ์ฌ์์ํ๋ฉด ๋ชจ๋ ๊ฒฐ๊ณผ๊ฐ ์ฌ๋ผ์ง
- ๐ ์ด๋ค ํ๋ผ๋ฏธํฐ๊ฐ ์ฑ๋ฅ์ ๊ฐ์ฅ ํฐ ์ํฅ์ ๋ฏธ์น๋์ง ์ ์ ์์
์ฌ์ค ์ ๋,,
์ฃผํผํฐ ๋ ธํธ๋ถ์์ ํด๋น ์ ์ ์ฝ๋์ ํ๋ฆฐํธ๋ฅผ ์ ์ฅํ๋ฉฐ ๋ณด๊ฑฐ๋.,
๋ฐ๋ก ๋ฉ๋ชจ์ฅ์ ์ ๋ฆฌํ๋ ๊ตฌ์๋์ ๋ฐฉ์์ ์ฐ์์ต๋๋คใ ใ
2๏ธโฃ ๋ชจ๋ธ ๋ฒ์ ๊ด๋ฆฌ์ ํ๊ณ ๐
- ๋ชจ๋ธ ํ์ผ ํผ์ฌ:
model_v1.pkl
,model_final.pkl
,model_final_final.pkl
- ํ๊ฒฝ ๋ถ์ผ์น: โ๋ด ์ปดํจํฐ์์๋ ๋๋๋ฐ?โ ๋ฌธ์
- ๋ฐฐํฌ ๋ณต์ก์ฑ: ๋ชจ๋ธ์ ์ด๋ป๊ฒ ์๋นํ ์ง ๋งค๋ฒ ๊ณ ๋ฏผ
์์:
1
2
3
4
5
6
7
8
# ํผ๋์ค๋ฌ์ด ๋ชจ๋ธ ํ์ผ๋ค,, ์ ์ด์ผ๊ธฐ์
๋๋คใ
ใ
ls models/
model_v1.pkl
model_v2.pkl
model_best.pkl
model_final.pkl
model_really_final.pkl
model_v3_actually_final.pkl # ๐
3๏ธโฃ ๋ฐฐํฌ์ ๋ชจ๋ํฐ๋ง์ ์ด๋ ค์ ๐
- ๋ฐฐํฌ ์ผ๊ด์ฑ: ๊ฐ๋ฐ ํ๊ฒฝ๊ณผ ํ๋ก๋์ ํ๊ฒฝ์ ์ฐจ์ด
- ๋ชจ๋ธ ์ฑ๋ฅ ์ถ์ : ๋ฐฐํฌ ํ ์ฑ๋ฅ ์ ํ ๊ฐ์ง ์ด๋ ค์
- ๋กค๋ฐฑ ๋ณต์ก์ฑ: ๋ฌธ์ ๋ฐ์ ์ ์ด์ ๋ชจ๋ธ๋ก ๋๋๋ฆฌ๊ธฐ ์ด๋ ค์
๐ก ํด๊ฒฐ์ฑ : MLflow๊ฐ ์คํ๋ถํฐ ๋ฐฐํฌ๊น์ง ๋ชจ๋ ๊ณผ์ ์ ํตํฉ ๊ด๋ฆฌํฉ๋๋ค!
๐ง ๊ฐ๋จํ๊ฒ MLflow ์ค์นํ๊ธฐ!!
์ด์ MLflow๋ฅผ ์ค์นํ๊ณ ์ฅ์ ์ ์ง์ ๊ฒฝํํด๋ด์!
MLflow๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ฌ ๊ฐ์ง๊ฐ ์์ง๋ง, Docker๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ์ฅ ๊ฐ๋จํ๊ณ ์์ ์ ์ ๋๋ค!
๐ณ Docker๋ก MLflow ์๋ฒ ๊ตฌ์ถํ๊ธฐ
1๋จ๊ณ: ๋ฐ์ดํฐ ์ ์ฅ์ฉ ํด๋ ์์ฑ
1
2
3
# MLflow ๋ฐ์ดํฐ ์ ์ฅ์ฉ ํด๋ ์์ฑ (์คํ ๊ฒฐ๊ณผ๊ฐ ์ปดํจํฐ ์ฌ์์ ํ์๋ ๋ณด์กด๋จ)
mkdir -p ~/mlflow/artifacts # ๋ชจ๋ธ๊ณผ ํ์ผ๋ค ์ ์ฅ
mkdir -p ~/mlflow/db # ์คํ ๋ฉํ๋ฐ์ดํฐ ์ ์ฅ
2๋จ๊ณ: MLflow ์๋ฒ Docker ์ปจํ ์ด๋ ์คํ
์ฐธ๊ณ : https://yijoon009.tistory.com/entry/
- ์ฐ์ Dockerfile์ ๋ง๋ค์ด์ฃผ๊ตฌ!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Docker-mlflow-OSError-Errno-30-Read-only-file-system-mlflow
FROM continuumio/miniconda3
# MLflow ์ค์น
RUN pip install mlflow
# ๋๋ ํ ๋ฆฌ ์์ฑ ๋ฐ ๊ถํ ์ค์
RUN mkdir -p /mlflow/db
# ํ๊ฒฝ ๋ณ์ ์ค์
ENV MLFLOW_TRACKING_URI=http://0.0.0.0:5001
ENV MLFLOW_BACKEND_STORE_URI=sqlite:///mlflow/db/mlflow.db
# ์ปจํ
์ด๋ ์คํ ์ MLflow ์๋ฒ ์คํ
ENTRYPOINT ["mlflow", "server"]
CMD ["--host", "0.0.0.0", "--port", "5001", "--backend-store-uri", "sqlite:///mlflow/db/mlflow.db"]
- ๋น๋ํ๊ตฌ!!
1
docker build --network=host -t my-mlflow-image .
1
2
3
4
5
# MLflow ์๋ฒ ์คํ (ํ ๋ฒ๋ง ์คํํ๋ฉด ๋จ!)
docker run -d -p 5001:5001 --name mlflow \
-v $(pwd)/mlflow/db:/mlflow/db \
-v $(pwd)/mlartifacts:/mlartifacts \
my-mlflow-image
3๋จ๊ณ: ์น UI ์ ์ ํ์ธ -๋ธ๋ผ์ฐ์ ์์ ๋ค์ ์ฃผ์๋ก ์ ์ : http://localhost:5001
- MLflow ๋์๋ณด๋๊ฐ ๋ณด์ด๋ฉด ์ฑ๊ณต! ๐
๐ Python์์ MLflow ์ฌ์ฉํ๊ธฐ
1๋จ๊ณ: MLflow ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
1
pip install mlflow
2๋จ๊ณ: Python ์ฝ๋์์ MLflow ์๋ฒ ์ฐ๊ฒฐ
1
2
3
4
5
6
7
8
9
10
11
12
import mlflow
import numpy as np
# MLflow ์๋ฒ ์ฐ๊ฒฐ ์ค์
mlflow.set_tracking_uri("http://localhost:5001") # Docker ์๋ฒ ์ฃผ์
# ์คํ ์์ฑ (๋ณธ์ธ๋ง์ ์คํ๋ช
์ ์
๋ ฅํ์ธ์!)
mlflow.set_experiment("my_first_mlflow_experiment")
# ์ด์ MLflow ์ฌ์ฉ ์ค๋น ์๋ฃ! โจ
print("๐ MLflow ์ฐ๊ฒฐ ์ฑ๊ณต!")
print("๐ ๋ธ๋ผ์ฐ์ ์์ http://localhost:5001 ์ ์ํ์ฌ ์คํ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ์ธ์!")
๐ฏ ์ด์ ์ค๋น ๋! ์๋ ์์๋ค์ ํจ๊ป ์คํํด๋ณด์ธ์!
๐ง MLflow์ 4๊ฐ์ง ์ฅ์ ์ง์ ์ค์ตํด๋ณด๊ธฐ!
๐๏ธ 1. MLflow Tracking ๐
ํต์ฌ ์์ด๋์ด: ๋ชจ๋ ์คํ์ ์๋์ผ๋ก ์ถ์ ํ๊ณ ๋น๊ต ๊ฐ๋ฅํ๊ฒ ์ ์ฅ
์ฃผ์ ๊ธฐ๋ฅ:
- ํ์ดํผํ๋ผ๋ฏธํฐ ์๋ ๋ก๊น
- ์ฑ๋ฅ ์งํ ์ถ์
- ์ํฐํฉํธ(๋ชจ๋ธ, ํ๋กฏ) ์ ์ฅ
- ์คํ ๊ฐ ๋น๊ต ๋ฐ ์๊ฐํ
๊ฐ๋จํ ์ค์ต:
- a, b, c๋ผ๋ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ณ๊ฒฝํด๊ฐ๋ฉฐ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ก๊น !!
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
import mlflow
import numpy as np
import pickle
import os
# ์คํ ์์
mlflow.set_tracking_uri("http://0.0.0.0:5001") # Docker ์๋ฒ ์ฃผ์
mlflow.set_experiment("param_tuning_experiment")
class SimpleModel:
"""๊ฐ๋จํ ๋ชจ๋ธ ํด๋์ค - ์ค์ ๋ก ์ ์ฅ/๋ก๋ ๊ฐ๋ฅ"""
def __init__(self, param_a, param_b, param_c):
self.param_a = param_a
self.param_b = param_b
self.param_c = param_c
self.trained = False
def train(self):
"""๋ชจ๋ธ ํ๋ จ (์ค์ ๋ก๋ ๋ณต์กํ ๊ณผ์ )"""
self.result = self.param_a + self.param_b + self.param_c
self.trained = True
return self.result
def predict(self, x):
"""์์ธก ํจ์"""
if not self.trained:
raise Exception("๋ชจ๋ธ์ด ํ๋ จ๋์ง ์์์ต๋๋ค!")
return self.result * x # ๊ฐ๋จํ ์์ธก ๋ก์ง
# ๋ช ๊ฐ ์กฐํฉ๋ง ํ
์คํธ (์ ์ฒด 1331๊ฐ๋ ๋๋ฌด ๋ง์ผ๋ ์ผ๋ถ๋ง!)
test_combinations = [
(0.1, 0.2, 0.3), (0.5, 0.5, 0.5), (0.8, 0.9, 1.0),
(1.0, 1.0, 1.0), (0.0, 0.0, 0.0), (0.3, 0.7, 0.4)
]
for a, b, c in test_combinations:
with mlflow.start_run():
# ํ๋ผ๋ฏธํฐ ๋ก๊น
mlflow.log_param("param_a", a)
mlflow.log_param("param_b", b)
mlflow.log_param("param_c", c)
# ๋ชจ๋ธ ์์ฑ ๋ฐ ํ๋ จ
model = SimpleModel(param_a=a, param_b=b, param_c=c)
result = model.train()
# ์ฑ๋ฅ ์งํ ๊ณ์ฐ
accuracy = result / 3.0 # ์ต๋๊ฐ 3.0์ผ๋ก ๋๋์ด ์ ๊ทํ
loss = 3.0 - result # ์์ค์ ๋ฐ๋๋ก
# ์ฑ๋ฅ ์งํ ๋ก๊น
mlflow.log_metric("accuracy", accuracy)
mlflow.log_metric("loss", loss)
mlflow.log_metric("sum_result", result)
# ๋ชจ๋ธ ์ ์ฅ (์ค์!)
model_path = f"simple_model_{a}_{b}_{c}.pkl"
with open(model_path, 'wb') as f:
pickle.dump(model, f)
# MLflow์ ๋ชจ๋ธ ์ํฐํฉํธ๋ก ์ ์ฅ
mlflow.log_artifact(model_path)
# ์์ ํ์ผ ์ญ์
os.remove(model_path)
# ์ถ๊ฐ ์ ๋ณด ๋ก๊น
mlflow.set_tag("experiment_type", "parameter_sum")
mlflow.set_tag("model_type", "SimpleModel")
print(f"โ
a={a}, b={b}, c={c} โ ํฉ๊ณ: {result:.1f}, ์ ํ๋: {accuracy:.3f}")
print("๐ 6๊ฐ์ ์คํ์ด ๋ชจ๋ MLflow์ ์ ์ฅ๋์์ต๋๋ค!")
print("๐ ๋ธ๋ผ์ฐ์ ์์ http://0.0.0.0:5001 ์ผ๋ก ์ ์ํด์ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ์ธ์!")
MLflow์ ๋ง๋ฒ! โจ
- ๐ฏ 1331๊ฐ์ ์คํ์ด ๋ชจ๋ ์๋์ผ๋ก ์ ์ฅ๋จ
- ๐ ์น UI์์ ํด๋ฆญ ๋ช ๋ฒ์ผ๋ก ์ต๊ณ ์ฑ๋ฅ ์กฐํฉ ์ฐพ๊ธฐ (๋น์ฐํ a=1.0, b=1.0, c=1.0!)
- ๐ a, b, c ์ค ์ด๋ค ํ๋ผ๋ฏธํฐ๊ฐ ์ฑ๋ฅ์ ๊ฐ์ฅ ํฐ ์ํฅ์ ๋ฏธ์น๋์ง ๊ทธ๋ํ๋ก ํ์ธ (๋ชจ๋ ๋์ผํ๊ฒ ๊ธฐ์ฌ)
- ๐พ ์ปดํจํฐ ๊ป๋ค ์ผ๋ ๋ชจ๋ ์คํ ๊ฒฐ๊ณผ๊ฐ ๊ทธ๋๋ก ๋ณด์กด
- ๐ค ํ์๊ณผ ์คํ ๊ฒฐ๊ณผ ์ฝ๊ฒ ๊ณต์
- ๐จ ๊ฐ๋จํ ํจ์ ์์์ง๋ง ์ค์ ๋ณต์กํ ML ๋ชจ๋ธ์๋ ๋์ผํ๊ฒ ์ ์ฉ ๊ฐ๋ฅ
๊ฐ๊ฐ์ ๋ณ์๋ณ ๊ฒฐ๊ณผ๊ฐ ์ด๋ ๋์ง๋ ๋ณผ์์์ฃ !!
๐๏ธ 2. MLflow Models ๐ค
ํต์ฌ ์์ด๋์ด: ์ ์ฅ๋ ๋ชจ๋ธ์ ์ฝ๊ฒ ๋ถ๋ฌ์์ ์ฌ์ฌ์ฉํ๊ธฐ
๊ฐ๋จํ ์ค์ต: ์์์ ์ ์ฅํ ๋ชจ๋ธ์ ๋ถ๋ฌ์์ ์ฌ์ฉํ๊ธฐ
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
import mlflow
import pickle
import os
class SimpleModel:
"""๊ฐ๋จํ ๋ชจ๋ธ ํด๋์ค - ์ค์ ๋ก ์ ์ฅ/๋ก๋ ๊ฐ๋ฅ"""
def __init__(self, param_a, param_b, param_c):
self.param_a = param_a
self.param_b = param_b
self.param_c = param_c
self.trained = False
def train(self):
"""๋ชจ๋ธ ํ๋ จ (์ค์ ๋ก๋ ๋ณต์กํ ๊ณผ์ )"""
self.result = self.param_a + self.param_b + self.param_c
self.trained = True
return self.result
def predict(self, x):
"""์์ธก ํจ์"""
if not self.trained:
raise Exception("๋ชจ๋ธ์ด ํ๋ จ๋์ง ์์์ต๋๋ค!")
return self.result * x # ๊ฐ๋จํ ์์ธก ๋ก์ง
# ์คํ ์์
mlflow.set_tracking_uri("http://0.0.0.0:5001") # Docker ์๋ฒ ์ฃผ์
mlflow.set_experiment("param_tuning_experiment")
# 1. ์ ์ฅ๋ ์คํ ๋ชฉ๋ก ํ์ธ
experiment = mlflow.get_experiment_by_name("param_tuning_experiment")
runs = mlflow.search_runs(experiment_ids=[experiment.experiment_id])
print("์ ์ฅ๋ ์คํ ๋ชฉ๋ก:")
for idx, run in runs.iterrows():
print(f"Run ID: {run['run_id'][:8]}... โ a={run['params.param_a']}, b={run['params.param_b']}, c={run['params.param_c']}")
# 2. ๊ฐ์ฅ ์ฑ๋ฅ์ด ์ข์ ๋ชจ๋ธ ์ฐพ๊ธฐ
best_run = runs.loc[runs['metrics.accuracy'].idxmax()]
best_run_id = best_run['run_id']
print(f"\n๐ ์ต๊ณ ์ฑ๋ฅ ๋ชจ๋ธ: Run ID {best_run_id[:8]}...")
print(f" ํ๋ผ๋ฏธํฐ: a={best_run['params.param_a']}, b={best_run['params.param_b']}, c={best_run['params.param_c']}")
print(f" ์ ํ๋: {best_run['metrics.accuracy']:.3f}")
# 3. ๋ชจ๋ธ ์ํฐํฉํธ ๋ค์ด๋ก๋ ๋ฐ ๋ก๋
client = mlflow.tracking.MlflowClient()
artifacts = client.list_artifacts(best_run_id)
print(f"\n๐ฆ ์ ์ฅ๋ ์ํฐํฉํธ: {[art.path for art in artifacts]}")
# ๋ชจ๋ธ ํ์ผ ๋ค์ด๋ก๋
model_artifact = [art for art in artifacts if art.path.endswith('.pkl')][0]
local_path = client.download_artifacts(best_run_id, model_artifact.path)
print(f"๐ฅ ๋ชจ๋ธ ๋ค์ด๋ก๋ ์๋ฃ: {local_path}")
# ๋ชจ๋ธ ๋ก๋
with open(local_path, 'rb') as f:
loaded_model = pickle.load(f)
print(f"โ
๋ชจ๋ธ ๋ก๋ ์ฑ๊ณต!")
print(f" ๋ชจ๋ธ ํ๋ผ๋ฏธํฐ: a={loaded_model.param_a}, b={loaded_model.param_b}, c={loaded_model.param_c}")
print(f" ๋ชจ๋ธ ๊ฒฐ๊ณผ: {loaded_model.result}")
# 4. ๋ก๋๋ ๋ชจ๋ธ๋ก ์์ธก
test_inputs = [1.0, 2.0, 0.5]
for test_input in test_inputs:
prediction = loaded_model.predict(test_input)
print(f" ์
๋ ฅ {test_input} โ ์์ธก: {prediction}")
print("\n๐ ๋ชจ๋ธ ์ ์ฅ/๋ก๋๊ฐ ์๋ฒฝํ๊ฒ ์๋ํฉ๋๋ค!")
์์ฃผ ์ ์๋๋ฉ๋๋ค!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
์ ์ฅ๋ ์คํ ๋ชฉ๋ก:
Run ID: 56d54172... โ a=0.3, b=0.7, c=0.4
Run ID: 9b3b6a75... โ a=0.0, b=0.0, c=0.0
Run ID: 682f736c... โ a=1.0, b=1.0, c=1.0
Run ID: 934b4956... โ a=0.8, b=0.9, c=1.0
Run ID: 48a5f8d4... โ a=0.5, b=0.5, c=0.5
Run ID: 9b3822ea... โ a=0.1, b=0.2, c=0.3
๐ ์ต๊ณ ์ฑ๋ฅ ๋ชจ๋ธ: Run ID 682f736c...
ํ๋ผ๋ฏธํฐ: a=1.0, b=1.0, c=1.0
์ ํ๋: 1.000
๐ฆ ์ ์ฅ๋ ์ํฐํฉํธ: ['simple_model_1.0_1.0_1.0.pkl']
Downloadingโartifacts:โ100%
โ1/1โ[00:00<00:00,โ21.29it/s]
๐ฅ ๋ชจ๋ธ ๋ค์ด๋ก๋ ์๋ฃ: /tmp/tmpf9lqcqc5/simple_model_1.0_1.0_1.0.pkl
โ
๋ชจ๋ธ ๋ก๋ ์ฑ๊ณต!
๋ชจ๋ธ ํ๋ผ๋ฏธํฐ: a=1.0, b=1.0, c=1.0
๋ชจ๋ธ ๊ฒฐ๊ณผ: 3.0
์
๋ ฅ 1.0 โ ์์ธก: 3.0
์
๋ ฅ 2.0 โ ์์ธก: 6.0
์
๋ ฅ 0.5 โ ์์ธก: 1.5
๐ ๋ชจ๋ธ ์ ์ฅ/๋ก๋๊ฐ ์๋ฒฝํ๊ฒ ์๋ํฉ๋๋ค!
๐๏ธ 3. MLflow Model Registry ๐๏ธ
ํต์ฌ ์์ด๋์ด: ๋ชจ๋ธ ๋ฒ์ ๊ด๋ฆฌํ๊ธฐ (๊ฐ๋ฐ โ ํ ์คํธ โ ํ๋ก๋์ )
์ค์ ํ์ฌ์์๋:
- None: ๋ฐฉ๊ธ ๊ฐ๋ฐํ ๋ชจ๋ธ
- Staging: ํ ์คํธ ์ค์ธ ๋ชจ๋ธ
- Production: ์ค์ ์๋น์ค์ ์ฌ์ฉ๋๋ ๋ชจ๋ธ
- Archived: ๋ ์ด์ ์ฌ์ฉํ์ง ์๋ ๋ชจ๋ธ
๊ฐ๋จํ ์ค์ต: ๋ชจ๋ธ์ Registry์ ๋ฑ๋กํ๊ณ ๊ด๋ฆฌํ๊ธฐ
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
import mlflow
import mlflow.pyfunc
import os
# MLflow ์ฐ๊ฒฐ ์ค์
mlflow.set_tracking_uri("http://0.0.0.0:5001")
mlflow.set_experiment("param_tuning_experiment_model_save")
# ์๋ ๋ชจ๋ธ ํด๋์ค
class SimpleModel:
def __init__(self, param_a, param_b, param_c):
self.param_a = param_a
self.param_b = param_b
self.param_c = param_c
self.trained = False
def train(self):
self.result = self.param_a + self.param_b + self.param_c
self.trained = True
return self.result
def predict(self, x):
if not self.trained:
raise Exception("๋ชจ๋ธ์ด ํ๋ จ๋์ง ์์์ต๋๋ค!")
return self.result * x
# MLflow์์ ์ฌ์ฉํ ์ ์๋๋ก ๋ํ ํด๋์ค ์ ์
class WrappedSimpleModel(mlflow.pyfunc.PythonModel):
def __init__(self, model):
self.model = model
def predict(self, context, model_input):
return self.model.predict(model_input)
# Run ์์
with mlflow.start_run() as run:
model = SimpleModel(1.0, 1.0, 1.0)
result = model.train()
mlflow.log_param("param_a", 1.0)
mlflow.log_param("param_b", 1.0)
mlflow.log_param("param_c", 1.0)
mlflow.log_metric("accuracy", result / 3.0)
# pyfunc ๋ชจ๋ธ๋ก ์ ์ฅ
artifact_path = "model"
wrapped_model = WrappedSimpleModel(model)
mlflow.pyfunc.log_model(
artifact_path=artifact_path,
python_model=wrapped_model
)
model_name = "MyBestModel_save"
model_uri = f"runs:/{run.info.run_id}/{artifact_path}"
# Model Registry์ ๋ฑ๋ก
registered_model = mlflow.register_model(
model_uri=model_uri,
name=model_name,
tags={"team": "data_science", "version": "v1.0"}
)
print(f"โ
๋ชจ๋ธ '{model_name}' ๋ฑ๋ก ์๋ฃ!")
print(f" ๋ฒ์ : {registered_model.version}")
print(f" Run ID: {run.info.run_id}")
# ๋ชจ๋ธ ๋ฒ์ ๊ด๋ฆฌ
from mlflow.tracking import MlflowClient
client = MlflowClient()
client.transition_model_version_stage(
name=model_name,
version=registered_model.version,
stage="Staging"
)
print("๐ฏ ๋ชจ๋ธ์ Staging์ผ๋ก ์น๊ฒฉ!")
client.transition_model_version_stage(
name=model_name,
version=registered_model.version,
stage="Production"
)
print("๐ ๋ชจ๋ธ์ Production์ผ๋ก ์น๊ฒฉ!")
# Production ๋ชจ๋ธ ์ฌ์ฉ
production_model_uri = f"models:/{model_name}/Production"
print(f"๐ฆ Production ๋ชจ๋ธ URI: {production_model_uri}")
print("โ
์ด์ ๋ค๋ฅธ ํ์์๋ ์ด ๋ชจ๋ธ์ ์์ ํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค!")
๊ฒฐ๊ณผ ํ์ธ:
1
2
3
4
5
6
7
8
9
โ
๋ชจ๋ธ 'MyBestModel_save' ๋ฑ๋ก ์๋ฃ!
๋ฒ์ : 1
Run ID: 21070f737db74159933745c7b041cf44
๐ View run tasteful-elk-947 at: http://0.0.0.0:5001/#/experiments/5/runs/21070f737db74159933745c7b041cf44
๐งช View experiment at: http://0.0.0.0:5001/#/experiments/5
๐ฏ ๋ชจ๋ธ์ Staging์ผ๋ก ์น๊ฒฉ!
๐ ๋ชจ๋ธ์ Production์ผ๋ก ์น๊ฒฉ!
๐ฆ Production ๋ชจ๋ธ URI: models:/MyBestModel_save/Production
โ
์ด์ ๋ค๋ฅธ ํ์์๋ ์ด ๋ชจ๋ธ์ ์์ ํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค!
๐๏ธ 4. MLflow Projects ๐ฆ
์๋ ๋ถ๋ถ์.. ๊ทธ๋ฅ gpt๊ฐ ์จ์ค๋ถ๋ถ์ด๊ณ ์คํ์ํด๋ดค์ด์ ~~
ํต์ฌ ์์ด๋์ด: ์คํ ํ๊ฒฝ์ ๊ทธ๋๋ก ์ฌํํ ์ ์๋๋ก ํจํค์ง
์ค์ ์ฌ์ฉ ์์:
- ๋๋ฃ๊ฐ ๋ด ์คํ์ ๊ทธ๋๋ก ์ฌํํ๊ณ ์ถ์ ๋
- ๋ค๋ฅธ ์ปดํจํฐ์์ ๋๊ฐ์ ํ๊ฒฝ์ผ๋ก ์คํํ๊ณ ์ถ์ ๋
- ํ๋ก๋์ ํ๊ฒฝ์์ ๊ฐ๋ฐ ํ๊ฒฝ๊ณผ ๋์ผํ๊ฒ ์คํํ๊ณ ์ถ์ ๋
๊ฐ๋จํ ์ค์ต: MLflow ํ๋ก์ ํธ ๋ง๋ค๊ธฐ
1๋จ๊ณ: ํ๋ก์ ํธ ๊ตฌ์กฐ ๋ง๋ค๊ธฐ
1
2
mkdir my_mlflow_project
cd my_mlflow_project
2๋จ๊ณ: MLproject ํ์ผ ๋ง๋ค๊ธฐ
1
2
3
4
5
6
7
8
9
10
11
12
# MLproject ํ์ผ
name: simple_param_tuning
conda_env: conda.yaml
entry_points:
main:
parameters:
param_a: {type: float, default: 0.5}
param_b: {type: float, default: 0.5}
param_c: {type: float, default: 0.5}
command: "python train.py --param_a {param_a} --param_b {param_b} --param_c {param_c}"
3๋จ๊ณ: conda.yaml ํ์ผ ๋ง๋ค๊ธฐ
1
2
3
4
5
6
7
8
9
10
11
# conda.yaml ํ์ผ
name: simple_env
channels:
- conda-forge
dependencies:
- python=3.9
- pip
- pip:
- mlflow
- numpy
- pandas
4๋จ๊ณ: train.py ํ์ผ ๋ง๋ค๊ธฐ
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
# train.py ํ์ผ
import mlflow
import argparse
class SimpleModel:
def __init__(self, param_a, param_b, param_c):
self.param_a = param_a
self.param_b = param_b
self.param_c = param_c
self.trained = False
def train(self):
self.result = self.param_a + self.param_b + self.param_c
self.trained = True
return self.result
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--param_a', type=float, default=0.5)
parser.add_argument('--param_b', type=float, default=0.5)
parser.add_argument('--param_c', type=float, default=0.5)
args = parser.parse_args()
# MLflow ์ค์
mlflow.set_tracking_uri("http://0.0.0.0:5001")
mlflow.set_experiment("mlflow_project_experiment")
with mlflow.start_run():
# ํ๋ผ๋ฏธํฐ ๋ก๊น
mlflow.log_param("param_a", args.param_a)
mlflow.log_param("param_b", args.param_b)
mlflow.log_param("param_c", args.param_c)
# ๋ชจ๋ธ ํ๋ จ
model = SimpleModel(args.param_a, args.param_b, args.param_c)
result = model.train()
# ์ฑ๋ฅ ์งํ ๋ก๊น
accuracy = result / 3.0
mlflow.log_metric("accuracy", accuracy)
mlflow.log_metric("sum_result", result)
print(f"โ
ํ๋ผ๋ฏธํฐ a={args.param_a}, b={args.param_b}, c={args.param_c}")
print(f" ๊ฒฐ๊ณผ: {result:.2f}, ์ ํ๋: {accuracy:.3f}")
if __name__ == "__main__":
main()
5๋จ๊ณ: ํ๋ก์ ํธ ์คํํ๊ธฐ
1
2
3
4
5
6
7
8
# ๊ธฐ๋ณธ ํ๋ผ๋ฏธํฐ๋ก ์คํ
mlflow run . --no-conda
# ์ปค์คํ
ํ๋ผ๋ฏธํฐ๋ก ์คํ
mlflow run . --no-conda -P param_a=0.8 -P param_b=0.9 -P param_c=1.0
# ๋ค๋ฅธ ์ฌ๋์ด Github์์ ์ง์ ์คํ
mlflow run https://github.com/your-username/my_mlflow_project --no-conda
์คํ ๊ฒฐ๊ณผ:
1
2
โ
ํ๋ผ๋ฏธํฐ a=0.8, b=0.9, c=1.0
๊ฒฐ๊ณผ: 2.70, ์ ํ๋: 0.900
์ฅ์ :
- ๐ ์๋ฒฝํ ์ฌํ์ฑ: ํ๊ฒฝ์ด ๋ฌ๋ผ๋ ๋๊ฐ์ ๊ฒฐ๊ณผ
- ๐ค ์ฌ์ด ๊ณต์ : Github URL๋ง ๊ณต์ ํ๋ฉด ๋
- ๐ฆ ํจํค์ง ๊ด๋ฆฌ: conda๋ก ์์กด์ฑ ์๋ ๊ด๋ฆฌ
๐ ์ถ๊ฐ!! : ์น UI์์ ๊ฒฐ๊ณผ ํ์ธํ๊ธฐ
๋ธ๋ผ์ฐ์ ์์ http://0.0.0.0:5001 ์ ์ํ๋ฉด:
- ์คํ ๋ชฉ๋ก: ๋ชจ๋ ์คํ์ ์๊ฐ์์ผ๋ก ํ์ธ
- ์ฑ๋ฅ ์งํ ๋น๊ต: accuracy, loss ๋ฑ์ ๊ทธ๋ํ๋ก ๋น๊ต
- ํ์ดํผํ๋ผ๋ฏธํฐ ๋ถ์: ์ด๋ค ํ๋ผ๋ฏธํฐ๊ฐ ์ฑ๋ฅ์ ์ํฅ์ ๋ฏธ์น๋์ง ๋ถ์
- ๋ชจ๋ธ ๋ค์ด๋ก๋: ์ ์ฅ๋ ๋ชจ๋ธ ํ์ผ ๋ค์ด๋ก๋
๊ฐ๋จํ ์ค์ต: Python์ผ๋ก ๊ฒฐ๊ณผ ๋ถ์ํ๊ธฐ
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
import matplotlib.pyplot as plt
import mlflow
# ์คํ ๊ฒฐ๊ณผ๋ฅผ DataFrame์ผ๋ก ๊ฐ์ ธ์ค๊ธฐ
mlflow.set_tracking_uri("http://0.0.0.0:5001")
experiment = mlflow.get_experiment_by_name("parameter_tuning_experiment")
runs = mlflow.search_runs(experiment_ids=[experiment.experiment_id])
print(f"๐ ์ด {len(runs)}๊ฐ์ ์คํ ์๋ฃ!")
# ๊ฐ๋จํ ์๊ฐํ
plt.figure(figsize=(12, 4))
# 1. ํ๋ผ๋ฏธํฐ a vs ์ฑ๋ฅ
plt.subplot(1, 3, 1)
plt.scatter(runs['params.param_a'].astype(float), runs['metrics.accuracy'])
plt.xlabel('Parameter A')
plt.ylabel('Accuracy')
plt.title('A vs Accuracy')
# 2. ํ๋ผ๋ฏธํฐ b vs ์ฑ๋ฅ
plt.subplot(1, 3, 2)
plt.scatter(runs['params.param_b'].astype(float), runs['metrics.accuracy'])
plt.xlabel('Parameter B')
plt.ylabel('Accuracy')
plt.title('B vs Accuracy')
# 3. ํ๋ผ๋ฏธํฐ c vs ์ฑ๋ฅ
plt.subplot(1, 3, 3)
plt.scatter(runs['params.param_c'].astype(float), runs['metrics.accuracy'])
plt.xlabel('Parameter C')
plt.ylabel('Accuracy')
plt.title('C vs Accuracy')
plt.tight_layout()
plt.show()
# ์ต๊ณ ์ฑ๋ฅ ๋ชจ๋ธ ์ฐพ๊ธฐ
best_run = runs.loc[runs['metrics.accuracy'].idxmax()]
print("๐ ์ต๊ณ ์ฑ๋ฅ ๋ชจ๋ธ:")
print(f" ๐ Accuracy: {best_run['metrics.accuracy']:.3f}")
print(f" ๐ฏ a={best_run['params.param_a']}, b={best_run['params.param_b']}, c={best_run['params.param_c']}")
๊ฒฐ๊ณผ ํด์:
- ๐ a, b, c ๋ชจ๋ ๊ฐ์ด ํด์๋ก ์ฑ๋ฅ ํฅ์ (๋น์ฐํ ํฉ์ด๋๊น!)
- ๐ฏ a=1.0, b=1.0, c=1.0์ผ ๋ ์ต๊ณ ์ฑ๋ฅ
- ๐ ๋ชจ๋ ํ๋ผ๋ฏธํฐ๊ฐ ๋์ผํ๊ฒ ์ฑ๋ฅ์ ๊ธฐ์ฌ
๐ MLflow์ ์ฅ์ ์์ฝ
์ ํต์ ๋ฐฉ์ vs MLflow
๋ฌธ์ ์ํฉ | ์ ํต์ ๋ฐฉ์ ๐ซ | MLflow ๋ฐฉ์ โจ |
---|---|---|
์คํ ์ถ์ | ํฐ๋ฏธ๋ ์ถ๋ ฅ๋ง ๋ณด๊ณ ๋ | ๋ชจ๋ ์คํ์ด ์๋ ์ ์ฅ |
๋ชจ๋ธ ๊ด๋ฆฌ | model_final_final.pkl | ์ฒด๊ณ์ ์ธ ๋ฒ์ ๊ด๋ฆฌ |
๊ฒฐ๊ณผ ๋น๊ต | ์์ ์ ์๋ ๋ณต๋ถ | ์น UI์์ ํด๋ฆญ ๋ช ๋ฒ |
ํ์ | ์ด๋ฉ์ผ๋ก ํ์ผ ์ ์ก | ๋ธ๋ผ์ฐ์ ์์ ๊ณต์ |
์ฌํ์ฑ | โ๋ด ์ปดํจํฐ์์๋ ๋๋๋ฐ?โ | ์๋ฒฝํ ํ๊ฒฝ ์ฌํ |
๐ ๊ฒฐ๋ก
MLflow๋ฅผ ํตํด ๋ ์ฒด๊ณ์ ์ด๊ณ ํจ์จ์ ์ธ ๋จธ์ ๋ฌ๋ ํ๋ก์ ํธ๋ฅผ ์์ํด๋ณด์ธ์! ๐
๐ฏ ํต์ฌ ๊ฐ์น:
- ์๊ฐ ์ ์ฝ: ์๋ ์์ ์ ์๋ํํ์ฌ ์ค์ ๋ชจ๋ธ ๊ฐ๋ฐ์ ์ง์ค
- ํ์ ํฅ์: ํ์๋ค๊ณผ ์คํ ๊ฒฐ๊ณผ๋ฅผ ์ฝ๊ฒ ๊ณต์ ํ๊ณ ์ฌํ
- ์์ ์ฑ ์ฆ๋: ์ฒด๊ณ์ ์ธ ๋ชจ๋ธ ๊ด๋ฆฌ๋ก ํ๋ก๋์ ๋ฆฌ์คํฌ ๊ฐ์
- ํ์ฅ์ฑ: ๊ฐ์ธ ํ๋ก์ ํธ๋ถํฐ ๋๊ท๋ชจ ๊ธฐ์ ๊น์ง ํ์ฅ ๊ฐ๋ฅ
๐ก ์์ ๊ถ์ฅ์ฌํญ:
- ์์ ํ๋ก์ ํธ๋ถํฐ ์์ํ์ฌ ์ ์ง์ ์ผ๋ก ํ์ฅ
- ํ ๋ด MLflow ์ฌ์ฉ ๊ฐ์ด๋๋ผ์ธ ์๋ฆฝ
- ์ ๊ธฐ์ ์ธ ๋ชจ๋ธ ์ฑ๋ฅ ๋ฆฌ๋ทฐ ํ๋ก์ธ์ค ๊ตฌ์ถ
๐ ์ถ๊ฐ ํ์ต ์๋ฃ:
๐ก ๋ง์ง๋ง ํ: MLflow๋ ๋จ์ํ ๋๊ตฌ๊ฐ ์๋๋ผ ๋จธ์ ๋ฌ๋ ํ์ ์์ฐ์ฑ์ ํ์ ์ ์ผ๋ก ๊ฐ์ ํ๋ ํ๋ซํผ์ ๋๋ค. ์์ ๊ฒ๋ถํฐ ์์ํด๋ณด์ธ์!
๐ Happy MLOps! ๐