Week 20 ํ•™์Šต ์ •๋ฆฌ

pydantic: ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ๋ฐ ์„ค์ • ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ ํŒŒ์ด์ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

pydantic์€ ํŒŒ์ด์ฌ์—์„œ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋ง๊ณผ ์„ค์ • ๊ด€๋ฆฌ๋ฅผ ์†์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ํƒ€์ž… ํžŒํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘์„ฑ๋œ ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•˜๊ณ , JSON ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™”๋ฅผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ FastAPI์™€ ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜์–ด, API ์š”์ฒญ/์‘๋‹ต ๋ฐ์ดํ„ฐ์˜ ๊ฒ€์ฆ ๋ฐ ํ™˜๊ฒฝ ์„ค์ • ๊ด€๋ฆฌ์— ํฐ ๋„์›€์„ ์ฃผ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.


1. pydantic์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ

๋ฐ์ดํ„ฐ ๊ฒ€์ฆ (Validation)

๋ฐ์ดํ„ฐ ์ง๋ ฌํ™” ๋ฐ ์—ญ์ง๋ ฌํ™”


2. ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ์˜ˆ์ œ

๋‹ค์Œ ์˜ˆ์ œ๋Š” ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ URL, 1~10 ์‚ฌ์ด์˜ ์ •์ˆ˜, ๊ทธ๋ฆฌ๊ณ  ์กด์žฌํ•˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ์ธ์ง€ ๊ฒ€์ฆํ•˜๋Š” pydantic ๋ชจ๋ธ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

from pydantic import BaseModel, HttpUrl, Field, DirectoryPath

class Validation(BaseModel):
    url: HttpUrl              # ์˜ฌ๋ฐ”๋ฅธ URL์ธ์ง€ ๊ฒ€์ฆ
    rate: int = Field(ge=1, le=10)  # 1~10 ์‚ฌ์ด์˜ ์ •์ˆ˜์ธ์ง€ ๊ฒ€์ฆ
    target_dir: DirectoryPath # ์‹ค์ œ ์กด์žฌํ•˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ์ธ์ง€ ๊ฒ€์ฆ

๊ฒ€์ฆ ์‹คํ–‰ ์˜ˆ์ œ

import os
from pydantic import ValidationError

VALID_INPUT = {
    "url": "https://content.presspage.com/uploads/2658/c800_logo-stackoverflow-square.jpg?98978",
    "rate": 4,
    "target_dir": os.path.join(os.getcwd(), "examples"),  # ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด "examples" ํด๋”๊ฐ€ ์žˆ์–ด์•ผ ํ•จ
}

INVALID_INPUT = {"url": "WRONG_URL", "rate": 11, "target_dir": "WRONG_DIR"}

# ์˜ฌ๋ฐ”๋ฅธ ์ž…๋ ฅ์„ ๊ฒ€์ฆํ•˜๋Š” ๊ฒฝ์šฐ
valid_model = Validation(**VALID_INPUT)
print("๊ฒ€์ฆ ์„ฑ๊ณต:", valid_model)

# ์ž˜๋ชป๋œ ์ž…๋ ฅ์€ ValidationError ๋ฐœ์ƒ
try:
    invalid_model = Validation(**INVALID_INPUT)
except ValidationError as exc:
    print("pydantic model input validation error:", exc.json())

์œ„ ์ฝ”๋“œ๋Š” VALID_INPUT์˜ ๊ฒฝ์šฐ ์ •์ƒ์ ์œผ๋กœ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋ฉฐ, INVALID_INPUT์˜ ๊ฒฝ์šฐ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค. ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์—๋Š” ์–ด๋–ค ํ•„๋“œ์—์„œ ์–ด๋–ค ๊ฐ’์ด ๋ฌธ์ œ์˜€๋Š”์ง€ ์ƒ์„ธํžˆ ์•ˆ๋‚ดํ•ด์ค๋‹ˆ๋‹ค.


3. ์„ค์ • ๊ด€๋ฆฌ (Configuration) with BaseSettings

ํ”„๋กœ์ ํŠธ์—์„œ ์ƒ์ˆ˜๋ฅผ ์ฝ”๋“œ์— ํ•˜๋“œ์ฝ”๋”ฉํ•˜๊ฑฐ๋‚˜ ๋ณ„๋„์˜ ํŒŒ์ผ(yaml ๋“ฑ)๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋Œ€์‹ , pydantic์˜ BaseSettings๋ฅผ ํ™œ์šฉํ•˜๋ฉด ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ๋ถ€ํ„ฐ ์„ค์ • ๊ฐ’์„ ์•ˆ์ „ํ•˜๊ฒŒ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ฐฐํฌ ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ์„ค์ •์„ ์†์‰ฝ๊ฒŒ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ์ฝ”๋“œ์— ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„ค์ • ํŒŒ์ผ ์˜ˆ์ œ

from pydantic import BaseSettings, Field
from enum import Enum

class ConfigEnv(str, Enum):
    DEV = "dev"
    PROD = "prod"

class DBConfig(BaseSettings):
    host: str = Field(default="localhost", env="db_host")
    port: int = Field(default=3306, env="db_port")
    username: str = Field(default="user", env="db_username")
    password: str = Field(default="user", env="db_password")
    database: str = Field(default="dev", env="db_database")

class AppConfig(BaseSettings):
    env: ConfigEnv = Field(default="dev", env="env")
    db: DBConfig = DBConfig()

# ์˜ˆ๋ฅผ ๋“ค์–ด, dev_config.yaml ํŒŒ์ผ์„ ๋กœ๋“œํ•˜์—ฌ ๊ธฐ๋ณธ ์„ค์ •์„ ๊ตฌ์„ฑํ•  ์ˆ˜๋„ ์žˆ์Œ
# (์—ฌ๊ธฐ์„œ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•œ ์„ค์ • ์˜ค๋ฒ„๋ผ์ด๋”ฉ ์˜ˆ์ œ๋ฅผ ํ•จ๊ป˜ ์„ค๋ช…)

ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์„ค์ • ์˜ค๋ฒ„๋ผ์ด๋”ฉ

์•„๋ž˜ ์˜ˆ์ œ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ด์šฉํ•ด ์„ค์ • ๊ฐ’์„ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

import os

# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • (์‹ค์ œ ๋ฐฐํฌ ํ™˜๊ฒฝ์—์„œ๋Š” ์‹œ์Šคํ…œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๊ด€๋ฆฌ)
os.environ["env"] = "prod"
os.environ["db_host"] = "mysql"
os.environ["db_username"] = "admin"
os.environ["db_password"] = "PASSWORD"

# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๊ธฐ๋ฐ˜ ์„ค์ • ๊ฐ์ฒด ์ƒ์„ฑ
prod_config_with_pydantic = AppConfig()
print("ํ™˜๊ฒฝ:", prod_config_with_pydantic.env)
print("DB ์„ค์ •:", prod_config_with_pydantic.db.dict())

# cleanup: ํ•„์š” ์‹œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์ •๋ฆฌ
# os.environ.pop("env")
# os.environ.pop("db_host")
# os.environ.pop("db_username")
# os.environ.pop("db_password")

์ด์™€ ๊ฐ™์ด pydantic์˜ BaseSettings๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด YAML ํŒŒ์ผ์ด๋‚˜ ๋ณ„๋„์˜ ์„ค์ • ํŒŒ์ผ ์—†์ด๋„, ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋งŒ์œผ๋กœ ์†์‰ฝ๊ฒŒ ์„ค์ •์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด ๋ณด์•ˆ๊ณผ ์œ ์—ฐ์„ฑ์„ ๋™์‹œ์— ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๊ฒฐ๋ก 

pydantic์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ด์œ ๋กœ ๋งŽ์€ ํŒŒ์ด์ฌ ๊ฐœ๋ฐœ์ž์™€ FastAPI ์‚ฌ์šฉ์ž์—๊ฒŒ ์‚ฌ๋ž‘๋ฐ›๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.


Docker๋กœ ์ปจํ…Œ์ด๋„ˆ ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ๋ฐ ๋ฐฐํฌ

Docker๋Š” ์ปจํ…Œ์ด๋„ˆ ๊ธฐ์ˆ ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœ, ๋ฐฐํฌ, ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜๋Š” ์˜คํ”ˆ ์†Œ์Šค ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. Docker๋ฅผ ํ™œ์šฉํ•˜๋ฉด ๊ฐœ๋ฐœ ํ™˜๊ฒฝ๊ณผ ๋ฐฐํฌ ํ™˜๊ฒฝ ๊ฐ„์˜ ์ฐจ์ด๋กœ ์ธํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ฐ€์ƒ ๋จธ์‹ ๋ณด๋‹ค ๊ฐ€๋ณ๊ณ  ๋น ๋ฅธ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ด์šฉํ•ด ์ผ๊ด€๋œ ์‹คํ–‰ ํ™˜๊ฒฝ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


1. ์ปจํ…Œ์ด๋„ˆ์™€ Docker์˜ ํ•„์š”์„ฑ

์ปจํ…Œ์ด๋„ˆ๋ž€?

Docker์˜ ์—ญํ• 


2. Docker ๊ธฐ๋ณธ ๋ช…๋ น์–ด ์ •๋ฆฌ


3. Docker ์„ค์น˜ ๋ฐ Docker Hub

Docker ์„ค์น˜

Docker Hub


4. Docker Image ์ƒ์„ฑ

Docker ์ด๋ฏธ์ง€๋Š” Dockerfile์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋นŒ๋“œํ•ฉ๋‹ˆ๋‹ค. Dockerfile์—๋Š” ์ด๋ฏธ์ง€ ๋นŒ๋“œ์— ํ•„์š”ํ•œ ๋ชจ๋“  ์„ค์ • ์ •๋ณด๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

Dockerfile ์ฃผ์š” ๋ช…๋ น์–ด

์˜ˆ์‹œ Dockerfile

FROM python:3.9.13-slim

WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY . /code
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

Docker Image ๋นŒ๋“œ ๋ฐ ์‹คํ–‰


5. Docker Image Push

Docker Hub์— ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œ(ํ‘ธ์‹œ)ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ๋กœ๊ทธ์ธ:

    $ docker login
    
  2. ํƒœ๊ทธ ๋ณ€๊ฒฝ:

    $ docker tag image_name:tag_name my_id/image_name:tag_name
    
  3. ํ‘ธ์‹œ:

    $ docker push my_id/image_name:tag_name
    

6. Docker Image ์ตœ์ ํ™”

ML ๋ชจ๋ธ์ด ํฌํ•จ๋œ Docker image๋Š” ์ข…์ข… ์šฉ๋Ÿ‰์ด ํฌ๊ธฐ ๋•Œ๋ฌธ์— ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์ตœ์ ํ™” ๋ฐฉ๋ฒ•

  1. ์ž‘์€ ๋ฒ ์ด์Šค ์ด๋ฏธ์ง€ ์‚ฌ์šฉ:
    • Python ์ด๋ฏธ์ง€ ์ค‘ slim ๋˜๋Š” alpine์„ ์‚ฌ์šฉ
      (์˜ˆ: FROM python:3.9.13-slim)
  2. Multi-stage build:
    • ๋นŒ๋“œ์— ํ•„์š”ํ•œ ๋‹จ๊ณ„์™€ ์ตœ์ข… ์ด๋ฏธ์ง€์—์„œ ํ•„์š”ํ•œ ๋ถ€๋ถ„์„ ๋ถ„๋ฆฌํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ํŒŒ์ผ ์ œ๊ฑฐ
  3. ์ปจํ…Œ์ด๋„ˆ ํŒจํ‚ค์ง• ์ตœ์ ํ™”:
    • .dockerignore ํŒŒ์ผ๋กœ ๋นŒ๋“œ ์‹œ ํฌํ•จํ•˜์ง€ ์•Š์„ ํŒŒ์ผ ์ง€์ •
    • ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ์„ฑ์ด ๋‚ฎ์€ ๋ช…๋ น์–ด๋Š” Dockerfile ์ƒ๋‹จ์— ๋ฐฐ์น˜ํ•ด ์บ์‹ฑ ํ™œ์šฉ

7. Docker Compose ์‚ฌ์šฉํ•˜๊ธฐ

์—ฌ๋Ÿฌ Docker container๋ฅผ ๋™์‹œ์— ์‹คํ–‰ํ•ด์•ผ ํ•  ๋•Œ Docker Compose๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปจํ…Œ์ด๋„ˆ์™€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋™์‹œ์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ์‹œ docker-compose.yml ํŒŒ์ผ

version: '3'

services:
  db:
    image: mysql:5.7.12
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: my_database
    ports:
      - "3306:3306"

  app:
    build:
      context: .
    environment:
      DB_URL: mysql+mysqldb://root:root@db:3306/my_database?charset=utf8mb4
    ports:
      - "8000:8000"
    depends_on:
      - db
    restart: always

๊ฒฐ๋ก 

Docker๋ฅผ ์ด์šฉํ•˜๋ฉด ๊ฐœ๋ฐœ ํ™˜๊ฒฝ๊ณผ ๋ฐฐํฌ ํ™˜๊ฒฝ์˜ ์ฐจ์ด๋ฅผ ๊ทน๋ณตํ•˜๋ฉฐ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ผ๊ด€๋œ ํ™˜๊ฒฝ์—์„œ ์†์‰ฝ๊ฒŒ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


ํด๋ผ์šฐ๋“œ ์ปดํ“จํŒ…๊ณผ GCP ํ™œ์šฉ ๊ฐ€์ด๋“œ

ํด๋ผ์šฐ๋“œ๋Š” ์ธํ„ฐ๋„ท์„ ํ†ตํ•ด IT ์ž์›(์„œ๋ฒ„, ์ €์žฅ์†Œ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ๋„คํŠธ์›Œํ‚น, ์†Œํ”„ํŠธ์›จ์–ด ๋“ฑ)์„ ์ œ๊ณตํ•˜๋Š” ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. AWS, GCP, Azure์™€ ๊ฐ™์ด ๋‹ค์–‘ํ•œ ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค ์ œ๊ณต์—…์ฒด๊ฐ€ ์žˆ์œผ๋ฉฐ, ๊ฐ ์—…์ฒด๋งˆ๋‹ค ์„œ๋ฒ„, ์„œ๋ฒ„๋ฆฌ์Šค ์ปดํ“จํŒ…, ์˜ค๋ธŒ์ ํŠธ ์Šคํ† ๋ฆฌ์ง€, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋“ฑ ๊ณตํ†ต ๊ฐœ๋…์€ ์œ ์‚ฌํ•˜์ง€๋งŒ ์ด๋ฆ„์ด๋‚˜ ์„ธ๋ถ€ ๊ธฐ๋Šฅ์ด ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

์ด๋ฒˆ ํฌ์ŠคํŠธ์—์„œ๋Š” ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค์˜ ๊ธฐ๋ณธ ๊ฐœ๋…, ํŠนํžˆ object storage์™€ database์˜ ์ฐจ์ด, ๊ทธ๋ฆฌ๊ณ  Google Cloud Platform(GCP)์˜ ๋Œ€ํ‘œ ๊ธฐ๋Šฅ(Compute Engine, Cloud Storage, ๋ฐฉํ™”๋ฒฝ, Cloud Composer)์„ ์‹ค์ œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ Python์„ ํ™œ์šฉํ•œ ํŒŒ์ผ ์—…๋กœ๋“œ/๋‹ค์šด๋กœ๋“œ ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.


1. ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค์˜ ๊ธฐ๋ณธ ๊ฐœ๋…

์ฃผ์š” ์„œ๋น„์Šค ๋น„๊ต

์„œ๋น„์Šค AWS GCP Azure
Server Elastic Compute (EC2) Compute Engine Virtual Machine
Serverless Lambda Cloud Function Azure Function
Stateless container ECS Cloud Run Container Instance
Object storage S3 Cloud Storage Blob Storage
Database Amazon RDS Cloud SQL Azure SQL
Data Warehouse Redshift BigQuery Synapse Analytics
AI platform SageMaker Vertex AI Azure Machine Learning
Kubernetes EKS GKE AKS

2. Google Cloud Platform (GCP) ์‚ฌ์šฉ๋ฒ•

Pasted image 20250311145442.png
GCP๋Š” ๋Œ€ํ‘œ์ ์ธ ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค ์ œ๊ณต์—…์ฒด๋กœ, ๋‹ค์–‘ํ•œ ์ปดํ“จํŒ… ๋ฐ ์Šคํ† ๋ฆฌ์ง€ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

2-1. Compute Engine

๋˜ํ•œ, GCP์—์„œ๋Š” ์ด๋ฏธ ๊ตฌ์ถ•๋œ Docker ์ด๋ฏธ์ง€ ๊ธฐ๋ฐ˜์˜ ์„œ๋ฒ„๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์›ํ•˜๋Š” ํ™˜๊ฒฝ(์˜ˆ: PyTorch ๋“ฑ)์„ ๊ฒ€์ƒ‰ ํ›„ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2-2. Cloud Storage

Python์„ ์ด์šฉํ•œ ํŒŒ์ผ ์—…๋กœ๋“œ/๋‹ค์šด๋กœ๋“œ ์˜ˆ์ œ

  1. Google Cloud Storage ํŒŒ์ด์ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜:

    pip install google-cloud-storage
    
  2. ์„œ๋น„์Šค ๊ณ„์ •๊ณผ ํ‚ค ์ƒ์„ฑ:

    • GCP Cloud Console์˜ ์„œ๋น„์Šค ๊ณ„์ • ๋งŒ๋“ค๊ธฐ ํŽ˜์ด์ง€์—์„œ ๋Œ€์ƒ ํ”„๋กœ์ ํŠธ ์„ ํƒ
    • ์„œ๋น„์Šค ๊ณ„์ • ์ƒ์„ฑ ํ›„, ์—ญํ• ์„ โ€œ์†Œ์œ ์žโ€๋กœ ์„ค์ •
    • ์„œ๋น„์Šค ๊ณ„์ • ์ƒ์„ธ ์ •๋ณด์—์„œ ํ‚ค ์ถ”๊ฐ€(ํ˜•์‹: JSON) ํ›„ ๋‹ค์šด๋กœ๋“œ
  3. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •:

    export GOOGLE_APPLICATION_CREDENTIALS="key_path"
    
  4. ํŒŒ์ด์ฌ ์ฝ”๋“œ ์˜ˆ์ œ:

    from google.cloud import storage
    
    # ์ดˆ๊ธฐํ™”
    bucket_name = "bucket1"
    storage_client = storage.Client()
    bucket = storage_client.bucket(bucket_name)
    
    # ํŒŒ์ผ ์—…๋กœ๋“œ
    upload_file_path = "/your/directory/your_file"
    blob = bucket.blob("your_file")
    blob.upload_from_filename(upload_file_path)
    
    # ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ
    download_destination = "/your/directory/your_file_downloaded"
    blob.download_to_filename(download_destination)
    

์ž์„ธํ•œ ๋‚ด์šฉ์€ ๊ตฌ๊ธ€ ํด๋ผ์šฐ๋“œ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.


3. ๋ฐฉํ™”๋ฒฝ ์„ค์ •

GCP์—์„œ๋Š” VM ์ธ์Šคํ„ด์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ฐฉํ™”๋ฒฝ ๊ทœ์น™์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ๋ฐฉํ™”๋ฒฝ ๊ทœ์น™ ๋งŒ๋“ค๊ธฐ:

    • ์™ผ์ชฝ ๋ฉ”๋‰ด์˜ ๋„คํŠธ์›Œํ‚น > VPC ๋„คํŠธ์›Œํฌ > ๋ฐฉํ™”๋ฒฝ ํด๋ฆญ
    • ์ƒ๋‹จ ๋ฐฉํ™”๋ฒฝ ๊ทœ์น™ ๋งŒ๋“ค๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ
    • ์ด๋ฆ„๊ณผ ๋Œ€์ƒ ํƒœ๊ทธ ์„ค์ •, ์†Œ์Šค ํ•„ํ„ฐ๋ฅผ IPv4 ๋ฒ”์œ„(0.0.0.0/0)๋กœ ์ง€์ •, ํ”„๋กœํ† ์ฝœ TCP์˜ ํฌํŠธ(์˜ˆ: 8888) ์„ค์ • ํ›„ ์ƒ์„ฑ
  2. ๋ฐฉํ™”๋ฒฝ ๊ทœ์น™ ์ ์šฉ:

    • ๊ทœ์น™์„ ์ ์šฉํ•  VM ์ธ์Šคํ„ด์Šค๋ฅผ ์„ ํƒ ํ›„ ์ˆ˜์ • ๋ฒ„ํŠผ ํด๋ฆญ
    • ๋ฐฉํ™”๋ฒฝ ์„ค์ •์—์„œ Allow HTTP traffic, Allow HTTPS traffic ์ฒดํฌ
    • ๋„คํŠธ์›Œํฌ ํƒœ๊ทธ์— ์ƒ์„ฑํ•œ ๋Œ€์ƒ ํƒœ๊ทธ๋ฅผ ๋“ฑ๋กํ•˜๋ฉด ํ•ด๋‹น ์„œ๋ฒ„์— ๊ทœ์น™์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

4. Cloud Composer (GCP์˜ Airflow)

Cloud Composer๋Š” GCP์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ด€๋ฆฌํ˜• Apache Airflow ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ํŒŒ์ดํ”„๋ผ์ธ๊ณผ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์†์‰ฝ๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. Cloud Composer ์ƒ์„ฑ:

    • GCP ์™ผ์ชฝ ๋ฉ”๋‰ด์—์„œ Composer ์„ ํƒ
    • ์ƒ๋‹จ ๋งŒ๋“ค๊ธฐ > Composer 2 ํด๋ฆญ
    • ์ด๋ฆ„, ์„œ๋น„์Šค ๊ณ„์ •, ์ด๋ฏธ์ง€ ๋ฒ„์ „(์•ˆ์ •์ ์ธ composer-2.5.5-airflow-2.6.3 ๊ถŒ์žฅ) ์„ ํƒ
    • ๊ณ ๊ธ‰ ๊ตฌ์„ฑ์—์„œ Airflow ๊ตฌ์„ฑ ์žฌ์ •์˜(์˜ˆ: webserver์˜ dag_dir_list_interval์„ 30์ดˆ ๋“ฑ) ์ž…๋ ฅ ํ›„ ์ƒ์„ฑ
  2. Composer ํ™˜๊ฒฝ ํ™•์ธ:

    • ์ƒ์„ฑ๋œ Composer ํ™˜๊ฒฝ ๋ชฉ๋ก์—์„œ Airflow ์›น์„œ๋ฒ„ ๋งํฌ๋ฅผ ํด๋ฆญํ•˜๋ฉด Airflow UI์— ์ ‘์†
    • DAG ํด๋” ๋งํฌ๋ฅผ ํด๋ฆญํ•˜๋ฉด ์—ฐ๊ฒฐ๋œ Cloud Storage ๋ฒ„ํ‚ท์œผ๋กœ ์ด๋™, ์—ฌ๊ธฐ์„œ DAG ํŒŒ์ผ(์˜ˆ: 01-bash-operator.py)์„ ์ €์žฅํ•˜๋ฉด ์ž๋™์œผ๋กœ ์‹คํ–‰๋จ

๊ฒฐ๋ก 

ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค๋Š” IT ์ž์›์„ ์†์‰ฝ๊ฒŒ ์ œ๊ณตํ•˜์—ฌ ๊ฐœ๋ฐœ, ๋ฐฐํฌ, ์šด์˜์˜ ํšจ์œจ์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•ฉ๋‹ˆ๋‹ค.


MLflow๋กœ ๋จธ์‹ ๋Ÿฌ๋‹ ๋ผ์ดํ”„์‚ฌ์ดํด ๊ด€๋ฆฌํ•˜๊ธฐ

AI ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•  ๋•Œ๋Š” ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ยท๋ผ๋ฒจ๋งยท์ •์ œ๋ถ€ํ„ฐ ๋‹ค์–‘ํ•œ ๋ชจ๋ธ๊ณผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์‹คํ—˜์„ ์ง„ํ–‰ํ•˜๊ณ , ๊ฐ€์žฅ ์„ฑ๋Šฅ์ด ์ข‹์€ ๋ชจ๋ธ์„ ๋ฐฐํฌํ•˜๋Š” ์ผ๋ จ์˜ ๊ณผ์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ชจ๋“  ๊ณผ์ •์€ ๋ฉ”ํƒ€ ๋ฐ์ดํ„ฐ, ๋ชจ๋ธ ์•„ํ‹ฐํŒฉํŠธ, ๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉํ•œ feature๋‚˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ผผ๊ผผํžˆ ๊ธฐ๋กํ•˜๊ณ  ๊ด€๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
MLflow๋Š” ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ์˜ ์‹คํ—˜, ๋ฐฐํฌ, ๊ด€๋ฆฌ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๋„์™€์ฃผ๋Š” ํ”Œ๋žซํผ์œผ๋กœ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.


MLflow์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ


1. MLflow ์„ค์น˜ ๋ฐ UI ์‹คํ–‰

์„ค์น˜

MLflow๋Š” ์•„๋ž˜ ๋ช…๋ น์–ด๋กœ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

$ pip install mlflow==2.10.0

MLflow UI ์‹คํ–‰

๋‹ค์Œ ๋ช…๋ น์–ด๋กœ MLflow ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•œ ํ›„ ๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:8080์„ ์ ‘์†ํ•˜๋ฉด MLflow ์›นํŽ˜์ด์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

$ mlflow server --host 127.0.0.1 --port 8080

2. Experiment ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ

Experiment๋ž€?

Experiment๋Š” ํ•˜๋‚˜์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ, ์—ฌ๋Ÿฌ run(์‹คํ–‰)์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด 'hand_bone_segmentation' ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๋ฉด, ํ•ด๋‹น experiment ๋‚ด์—์„œ ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ๋ชจ๋ธ ํ•™์Šต(run) ๊ธฐ๋ก์ด ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

Experiment ์ƒ์„ฑ

์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์ด์šฉํ•ด ์ƒˆ๋กœ์šด experiment๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

$ mlflow experiments create --experiment-name hand_bone_segmentation

experiment๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜๋ฉด, MLflow๋Š” mlruns๋ผ๋Š” ํด๋”์— ๊ด€๋ จ ๊ธฐ๋ก์„ ์ €์žฅํ•˜๊ณ , ์›น UI์—์„œ๋„ ํ•ด๋‹น experiment๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒ์„ฑ๋œ experiment ๋ชฉ๋ก์€ ๋‹ค์Œ ๋ช…๋ น์–ด๋กœ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

$ mlflow experiments search

3. ํ”„๋กœ์ ํŠธ ๋ฉ”ํƒ€ ์ •๋ณด ์ €์žฅ

์‹คํ—˜ ๊ธฐ๋ก ์™ธ์—๋„ ํ”„๋กœ์ ํŠธ์˜ ์‹คํ–‰ ํ™˜๊ฒฝ, ์˜์กด์„ฑ ๋“ฑ์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด MLProject ํŒŒ์ผ๊ณผ python_env.yaml ํŒŒ์ผ์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

MLProject ํŒŒ์ผ ์˜ˆ์‹œ

name: project1

python_env: python_env.yaml

entry_points:
  main:
    parameters:
      regularization: {type: float, default: 0.1}
    command: "python train.py"

python_env.yaml ํŒŒ์ผ ์˜ˆ์‹œ

python: "3.9.13"

# ๋นŒ๋“œ ์‹œ ํ•„์š”ํ•œ ์˜์กด์„ฑ
build_dependencies:
  - pip
  - setuptools
  - wheel==0.37.1
  
# ํ”„๋กœ์ ํŠธ ์‹คํ–‰ ์‹œ ํ•„์š”ํ•œ ์˜์กด์„ฑ
dependencies:
  - mlflow==2.10.0
  - scikit-learn==1.4.0
  - pandas==2.2.0
  - numpy==1.26.3
  - matplotlib==3.8.2

์ด๋ ‡๊ฒŒ ๋ฉ”ํƒ€ ์ •๋ณด๋ฅผ ๊ธฐ๋กํ•ด๋‘๋ฉด, ์‹คํ—˜ ์žฌํ˜„์„ฑ๊ณผ ๊ด€๋ฆฌ๊ฐ€ ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค.


4. Experiment ์‹คํ–‰ (Run)

์‹ค์ œ ์‹คํ—˜์„ ์‹คํ–‰ํ•  ๋•Œ๋Š” MLflow๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ปค๋งจ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

$ mlflow run experiment_directory --experiment-name hand_bone_segmentation

์‹คํ–‰(run)์ด ์™„๋ฃŒ๋˜๋ฉด MLflow UI์— run ๊ธฐ๋ก์ด ์ถ”๊ฐ€๋˜๊ณ , ์•„๋ž˜์™€ ๊ฐ™์€ ์ •๋ณด๊ฐ€ ์ž๋™์œผ๋กœ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.


5. ๋กœ๊น… ๋ฐ Autolog

train.py ๋‚ด์—์„œ ์ง์ ‘ ๋กœ๊น…ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, MLflow๋Š” ๋‹ค์–‘ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์— ๋Œ€ํ•ด ์ž๋™ ๋กœ๊น…(autolog) ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด scikit-learn์˜ ๊ฒฝ์šฐ:

import mlflow
import mlflow.sklearn

# ๊ฐœ๋ณ„ ํ•ญ๋ชฉ ๋กœ๊น…
mlflow.log_param("l1_ratio", 0.1)

# autolog ํ™œ์„ฑํ™”
mlflow.sklearn.autolog()

with mlflow.start_run() as run:
    model.fit(x, y)

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด MLflow๊ฐ€ ์ž๋™์œผ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ, ๋ฉ”ํŠธ๋ฆญ, ๋ชจ๋ธ ์•„ํ‹ฐํŒฉํŠธ๋ฅผ ๊ธฐ๋กํ•ด์ค๋‹ˆ๋‹ค.


6. ๋ชจ๋ธ ์•„ํ‹ฐํŒฉํŠธ ๋‹ค์šด๋กœ๋“œ

ํŠน์ • run์—์„œ ์ €์žฅ๋œ ๋ชจ๋ธ ์•„ํ‹ฐํŒฉํŠธ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๋ ค๋ฉด, ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from mlflow import artifacts

def download_model(run_id, model_name="model"):
    artifact_uri = f"runs:/{run_id}/{model_name}"
    artifacts.download_artifacts(artifact_uri, dst_path=".")

์ด ํ•จ์ˆ˜์— run id์™€ ๋ชจ๋ธ ์ด๋ฆ„์„ ์ „๋‹ฌํ•˜๋ฉด, ํ•ด๋‹น ๋ชจ๋ธ ํŒŒ์ผ์„ ๋กœ์ปฌ์— ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๊ฒฐ๋ก 

MLflow๋Š” ๋จธ์‹ ๋Ÿฌ๋‹ ์‹คํ—˜์˜ ์‹คํ–‰, ๊ธฐ๋ก, ๊ด€๋ฆฌ, ๋ฐฐํฌ๋ฅผ ํ†ตํ•ฉํ•˜์—ฌ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค.


๋ชจ๋ธ ํ‰๊ฐ€: Offline๊ณผ Online ํ‰๊ฐ€๋ฅผ ํ†ตํ•œ ์ตœ์  AI ์„œ๋น„์Šค ์„œ๋น™

AI ์„œ๋น„์Šค๋ฅผ ๋ฐฐํฌํ•  ๋•Œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์š”์†Œ ์ค‘ ํ•˜๋‚˜๋Š” ์ตœ์ ์˜ ์„ฑ๋Šฅ์„ ๊ฐ–์ถ˜ ๋ชจ๋ธ์„ ์„œ๋น™ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ๋ชจ๋ธ์˜ ์„ฑ๋Šฅ์„ ์ง€์†์ ์œผ๋กœ ํ‰๊ฐ€ํ•˜๊ณ  ๊ฐœ์„ ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ํ‰๊ฐ€ ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ํฌ๊ฒŒ Offline๊ณผ Online ํ‰๊ฐ€๋กœ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค. ์ด๋ฒˆ ํฌ์ŠคํŠธ์—์„œ๋Š” ๋‘ ํ‰๊ฐ€ ๋ฐฉ์‹์˜ ๊ฐœ๋…, ๋ฐฉ๋ฒ•๋ก , ์žฅ๋‹จ์ ์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด๊ณ , ์–ด๋–ป๊ฒŒ ์ง€์†์ ์œผ๋กœ ๋ชจ๋ธ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.


1. Offline ๋ชจ๋ธ ํ‰๊ฐ€

Offline ํ‰๊ฐ€๋ž€ ๋ชจ๋ธ์„ ๋ฐฐํฌํ•˜๊ธฐ ์ „, ํ•™์Šต ๊ฒฐ๊ณผ๊ฐ€ ๋‚ด๋Š” ์„ฑ๋Šฅ์„ ๊ณผ๊ฑฐ ๋ฐ์ดํ„ฐ ์…‹(hold-out ๋ฐ์ดํ„ฐ, k-fold, bootstrap ๋“ฑ)์„ ์ด์šฉํ•ด ์‹คํ—˜์ ์œผ๋กœ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์ž…๋‹ˆ๋‹ค.
์ฃผ์š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

1-1. Hold-out Validation

1-2. k-Fold Cross Validation

1-3. Bootstrap Resampling

Offline ํ‰๊ฐ€๋Š” ์ฃผ๋กœ ๋ฐฐํฌ ์ „ ๋ชจ๋ธ์˜ ์„ฑ๋Šฅ์„ ๊ฐ๊ด€์ ์œผ๋กœ ๊ฒ€์ฆํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋ฉฐ, ๋‹ค์–‘ํ•œ ํ‰๊ฐ€ ๋ฐฉ๋ฒ•์„ ํ†ตํ•ด ๋ชจ๋ธ์˜ ์ผ๋ฐ˜ํ™” ๋Šฅ๋ ฅ์„ ํ™•๋ณดํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.


2. Online ๋ชจ๋ธ ํ‰๊ฐ€

Online ํ‰๊ฐ€๋Š” ๋ชจ๋ธ์„ ์‹ค์ œ ์„œ๋น„์Šค ํ™˜๊ฒฝ์— ๋ฐฐํฌํ•œ ํ›„, ์‹ค์‹œ๊ฐ„ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ฑ๋Šฅ์„ ํ‰๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ์šด์˜ ํ™˜๊ฒฝ์—์„œ์˜ ์„ฑ๋Šฅ, ์‚ฌ์šฉ์ž ๋ฐ˜์‘, ํŠธ๋žœ์žญ์…˜ ๋“ฑ ๋‹ค์–‘ํ•œ ์š”์†Œ๋ฅผ ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2-1. A/B Test

Pasted image 20250311150337.png

2-2. Canary Test

2-3. Shadow Test


3. ์ง€์†์  ๊ฐœ์„ ์˜ ์ค‘์š”์„ฑ

Offline ํ‰๊ฐ€์™€ Online ํ‰๊ฐ€๋ฅผ ๋ฐ˜๋ณตํ•˜๋ฉด์„œ ๋ชจ๋ธ์„ ์ง€์†์ ์œผ๋กœ ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์ด ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋ฐ˜๋ณต์  ํ‰๊ฐ€์™€ ํ”ผ๋“œ๋ฐฑ ๊ณผ์ •์„ ํ†ตํ•ด ์ตœ์ ์˜ ๋ชจ๋ธ์„ ์„œ๋น™ํ•จ์œผ๋กœ์จ, AI ์„œ๋น„์Šค์˜ ํ’ˆ์งˆ๊ณผ ์‚ฌ์šฉ์ž ๋งŒ์กฑ๋„๋ฅผ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๊ฒฐ๋ก 

๋ชจ๋ธ ํ‰๊ฐ€ ์ „๋žต์€ AI ์„œ๋น„์Šค์˜ ์„ฑ๊ณต์ ์ธ ๋ฐฐํฌ์™€ ์šด์˜์— ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค.