본문으로 건너뛰기

모델과 데이터셋 추적하기

여기에서 Colab 노트북으로 시도해 보세요 →

이 노트북에서는 W&B 아티팩트를 사용하여 ML 실험 파이프라인을 추적하는 방법을 보여드립니다.

비디오 튜토리얼을 따라 해보세요!

🤔 아티팩트는 무엇이고 왜 중요한가요?

"아티팩트"는 그리스 암포라 🏺와 같이 생성된 오브젝트입니다 -- 프로세스의 출력물입니다. ML에서 가장 중요한 아티팩트는 데이터셋모델입니다.

그리고 코로나도의 십자가처럼, 이 중요한 아티팩트는 박물관에 속합니다! 즉, 카탈로그화되어 조직화되어야 합니다 그래서 당신, 당신의 팀, 그리고 ML 커뮤니티 전체가 그것들로부터 배울 수 있습니다. 결국, 트레이닝을 추적하지 않는 이들은 그것을 반복하게 됩니다.

우리의 아티팩트 API를 사용하여, 당신은 W&B Run의 출력물로 아티팩트를 로그하거나 Run에 입력으로 아티팩트를 사용할 수 있습니다, 이 다이어그램에서처럼, 여기서 트레이닝 run은 데이터셋을 입력으로 받아 모델을 생성합니다.

하나의 run이 다른 run의 출력을 입력으로 사용할 수 있기 때문에, 아티팩트와 Run은 방향성 그래프 -- 실제로는 이분 DAG! -- 를 형성하며, 노드는 아티팩트Run에 대한 것이고 화살표는 Run이 소비하거나 생성하는 아티팩트를 연결합니다.

0️⃣ 설치 및 가져오기

아티팩트는 우리의 Python 라이브러리의 일부이며, 버전 0.9.2부터 시작됩니다.

대부분의 ML Python 스택과 마찬가지로, pip를 통해 사용할 수 있습니다.

# wandb 버전 0.9.2+와 호환됩니다
!pip install wandb -qqq
!apt install tree
import os
import wandb

1️⃣ 데이터셋 로그하기

먼저, 몇 가지 아티팩트를 정의해 보겠습니다.

이 예제는 PyTorch의 "기본 MNIST 예제"를 기반으로 하지만, TensorFlow, 다른 프레임워크에서도 마찬가지로 쉽게 수행할 수 있으며, 순수 Python에서도 가능합니다.

우리는 데이터셋으로 시작합니다:

  • 파라미터를 선택하기 위한 train 세트,
  • 하이퍼파라미터를 선택하기 위한 validation 세트,
  • 최종 모델을 평가하기 위한 test 세트

아래 첫 번째 셀은 이 세 가지 데이터셋을 정의합니다.

import random 

import torch
import torchvision
from torch.utils.data import TensorDataset
from tqdm.auto import tqdm

# 결정적 동작을 보장합니다
torch.backends.cudnn.deterministic = True
random.seed(0)
torch.manual_seed(0)
torch.cuda.manual_seed_all(0)

# 장치 설정
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# 데이터 파라미터
num_classes = 10
input_shape = (1, 28, 28)

# MNIST 미러 목록에서 느린 미러 제거
torchvision.datasets.MNIST.mirrors = [mirror for mirror in torchvision.datasets.MNIST.mirrors
if not mirror.startswith("http://yann.lecun.com")]

def load(train_size=50_000):
"""
# 데이터를 로드합니다
"""

# 데이터, 트레인과 테스트 세트로 분할됩니다
train = torchvision.datasets.MNIST("./", train=True, download=True)
test = torchvision.datasets.MNIST("./", train=False, download=True)
(x_train, y_train), (x_test, y_test) = (train.data, train.targets), (test.data, test.targets)

# 하이퍼파라미터 튜닝을 위한 검증 세트 분리
x_train, x_val = x_train[:train_size], x_train[train_size:]
y_train, y_val = y_train[:train_size], y_train[train_size:]

training_set = TensorDataset(x_train, y_train)
validation_set = TensorDataset(x_val, y_val)
test_set = TensorDataset(x_test, y_test)

datasets = [training_set, validation_set, test_set]

return datasets

이는 이 예제에서 반복되는 패턴을 설정합니다: 데이터를 생성하는 코드 주위로 데이터를 로그하는 코드를 래핑합니다. 이 경우, loading 데이터의 코드는 로그하고 load_and_log하는 데이터의 코드와 분리됩니다.

이것은 좋은 관행입니다!

이 데이터셋을 아티팩트로 로그하려면, 우리는 단지

  1. wandb.init으로 Run을 생성해야 합니다 (L4),
  2. 데이터셋에 대한 아티팩트를 생성해야 합니다 (L10), 그리고
  3. 관련된 파일들을 저장하고 로그해야 합니다 (L20, L23).

아래 코드 셀의 예제를 확인한 다음, 더 자세한 내용을 알아보기 위해 이후 섹션을 확장하세요.

def load_and_log():

# 🚀 run을 시작하고, 이를 라벨링할 타입과 그것이 속할 프로젝트를 지정합니다
with wandb.init(project="artifacts-example", job_type="load-data") as run:

datasets = load() # 데이터셋을 로드하는 별도의 코드
names = ["training", "validation", "test"]

# 🏺 우리의 아티팩트를 생성합니다
raw_data = wandb.Artifact(
"mnist-raw", type="dataset",
description="Raw MNIST 데이터셋, 트레인/밸/테스트로 분할됨",
metadata={"source": "torchvision.datasets.MNIST",
"sizes": [len(dataset) for dataset in datasets]})

for name, data in zip(names, datasets):
# 🐣 새 파일을 아티팩트에 저장하고, 그 내용에 무언가를 작성합니다.
with raw_data.new_file(name + ".pt", mode="wb") as file:
x, y = data.tensors
torch.save((x, y), file)

# ✍️ W&B에 아티팩트를 저장합니다.
run.log_artifact(raw_data)

load_and_log()

🚀 wandb.init

Artifact를 생성할 Run을 만들 때, 이것이 어떤 프로젝트에 속하는지를 명시해야 합니다.

당신의 워크플로우에 따라, 프로젝트는 car-that-drives-itself처럼 크거나 iterative-architecture-experiment-117처럼 작을 수 있습니다.

👍 규칙: 가능하다면, Artifact를 공유하는 모든 Run을 단일 프로젝트 내에 유지하세요. 이것은 사물을 단순하게 유지하지만, 걱정하지 마세요 -- 아티팩트는 프로젝트 간에 이동 가능합니다!

모든 다양한 종류의 작업을 추적하기 위해, Run을 만들 때 job_type을 제공하는 것이 유용합니다. 이것은 당신의 아티팩트 그래프를 깔끔하게 유지하는 데 도움이 됩니다.

👍 규칙: job_type은 설명적이어야 하며 파이프라인의 단일 단계에 해당해야 합니다. 여기서는 데이터를 load하는 것과 데이터를 preprocess하는 것을 구분합니다.

🏺 wandb.Artifact

무언가를 아티팩트로 로그하려면, 먼저 아티팩트 오브젝트를 만들어야 합니다.

모든 아티팩트에는 이름이 있습니다 -- 그것이 첫 번째 인수가 설정하는 것입니다.

👍 규칙: 이름은 설명적이지만 기억하기 쉽고 입력하기 쉬워야 합니다 -- 우리는 하이픈으로 구분된 이름을 사용하는 것을 좋아하며 코드에서 변수 이름에 해당합니다.

또한 유형이 있습니다. Runjob_type과 마찬가지로, 이것은 Run아티팩트의 그래프를 구성하는 데 사용됩니다.

👍 규칙: 유형은 단순해야 합니다: dataset이나 model처럼 mnist-data-YYYYMMDD보다는 간단해야 합니다.

설명과 일부 메타데이터를 사전 형태로 첨부할 수도 있습니다. 메타데이터는 JSON으로 직렬화될 수 있어야 합니다.

👍 규칙: 메타데이터는 가능한 한 설명적이어야 합니다.

🐣 artifact.new_file 및 ✍️ run.log_artifact

아티팩트 오브젝트를 만든 후, 그것에 파일을 추가해야 합니다.

그렇습니다: 파일들이라고 복수형으로 말했습니다. 아티팩트는 디렉토리처럼 구조화되어 있으며, 파일과 하위 디렉토리가 있습니다.

👍 규칙: 의미가 있을 때마다, 아티팩트의 내용을 여러 파일로 분할하세요. 이것은 확장할 때 도움이 됩니다!

new_file 메소드를 사용하여 파일을 동시에 작성하고 아티팩트에 첨부합니다. 아래에서는 add_file 메소드를 사용할 것입니다. 이 두 단계를 분리합니다.

모든 파일을 추가한 후, wandb.ailog_artifact 해야 합니다.

출력에 몇 가지 URL이 나타났음을 알 수 있습니다, Run 페이지 URL을 포함합니다. 그것은 Run의 결과를 볼 수 있는 곳입니다, 로그된 모든 아티팩트를 포함합니다.

아래에서는 Run 페이지의 다른 구성 요소를 더 잘 활용하는 몇 가지 예제를 볼 것입니다.

2️⃣ 로그된 데이터셋 아티팩트 사용하기

W&B의 아티팩트는 박물관의 아티팩트와 달리, 저장뿐만 아니라 사용되도록 설계되었습니다.

그것이 어떤 모습인지 살펴보겠습니다.

아래 셀은 원시 데이터셋을 입력으로 받아 preprocess된 데이터셋을 생성하는 파이프라인 단계를 정의합니다: 정규화되고 올바르게 형태가 지정됩니다.

다시 한번 wandb와 인터페이스하는 코드와 preprocess의 핵심 코드를 분리했음을 알 수 있습니다.

def preprocess(dataset, normalize=True, expand_dims=True):
"""
## 데이터 준비
"""
x, y = dataset.tensors

if normalize:
# 이미지를 [0, 1] 범위로 스케일링합니다
x = x.type(torch.float32) / 255

if expand_dims:
# 이미지가 (1, 28, 28) 형태를 가지도록 합니다
x = torch.unsqueeze(x, 1)

return TensorDataset(x, y)

이제 wandb.Artifact 로깅으로 이 preprocess 단계를 계측하는 코드입니다.

아래 예제에서는 아티팩트사용하는 것이 새롭습니다, 그리고 로그하는 것은 마지막 단계와 같습니다. 아티팩트Run의 입력과 출력 모두입니다!

새로운 job_type, preprocess-data를 사용하여 이것이 이전 것과 다른 종류의 작업임을 명확히 합니다.

def preprocess_and_log(steps):

with wandb.init(project="artifacts-example", job_type="preprocess-data") as run:

processed_data = wandb.Artifact(
"mnist-preprocess", type="dataset",
description="전처리된 MNIST 데이터셋",
metadata=steps)

# ✔️ 사용할 아티팩트를 선언합니다
raw_data_artifact = run.use_artifact('mnist-raw:latest')

# 📥 필요하다면, 아티팩트를 다운로드합니다
raw_dataset = raw_data_artifact.download()

for split in ["training", "validation", "test"]:
raw_split = read(raw_dataset, split)
processed_dataset = preprocess(raw_split, **steps)

with processed_data.new_file(split + ".pt", mode="wb") as file:
x, y = processed_dataset.tensors
torch.save((x, y), file)

run.log_artifact(processed_data)


def read(data_dir, split):
filename = split + ".pt"
x, y = torch.load(os.path.join(data_dir, filename))

return TensorDataset(x, y)

여기서 주목해야 할 것은 preprocess 단계의 stepspreprocessed_data메타데이터로 저장된다는 것입니다.

실험을 재현 가능하게 만들려면, 많은 메타데이터를 캡처하는 것이 좋은 생각입니다!

또한, 우리의 데이터셋이 "`대규

4️⃣ 로그된 모델 아티팩트 사용하기

데이터셋use_artifact를 호출할 수 있듯이, 우리는 initialized_model에도 그것을 호출하여 다른 Run에서 사용할 수 있습니다.

이번에는 모델트레이닝해봅시다.

더 자세한 내용은 우리의 Colab에서 확인하세요. PyTorch와 함께 W&B를 구성하는 방법에 대해.

import torch.nn.functional as F

def train(model, train_loader, valid_loader, config):
optimizer = getattr(torch.optim, config.optimizer)(model.parameters())
model.train()
example_ct = 0
for epoch in range(config.epochs):
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = F.cross_entropy(output, target)
loss.backward()
optimizer.step()

example_ct += len(data)

if batch_idx % config.batch_log_interval == 0:
print('트레이닝 에포크: {} [{}/{} ({:.0%})]\t손실: {:.6f}'.format(
epoch, batch_idx * len(data), len(train_loader.dataset),
batch_idx / len(train_loader), loss.item()))

train_log(loss, example_ct, epoch)

# 에포크마다 검증 세트에서 모델 평가
loss, accuracy = test(model, valid_loader)
test_log(loss, accuracy, example_ct, epoch)


def test(model, test_loader):
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += F.cross_entropy(output, target, reduction='sum') # 배치 손실 합산
pred = output.argmax(dim=1, keepdim=True) # 최대 로그-확률의 인덱스를 가져옴
correct += pred.eq(target.view_as(pred)).sum()

test_loss /= len(test_loader.dataset)

accuracy = 100. * correct / len(test_loader.dataset)

return test_loss, accuracy


def train_log(loss, example_ct, epoch):
loss = float(loss)

# 마법이 일어나는 곳
wandb.log({"epoch": epoch, "train/loss": loss}, step=example_ct)
print(f"예시 " + str(example_ct).zfill(5) + f"개 후 손실: {loss:.3f}")


def test_log(loss, accuracy, example_ct, epoch):
loss = float(loss)
accuracy = float(accuracy)

# 마법이 일어나는 곳
wandb.log({"epoch": epoch, "validation/loss": loss, "validation/accuracy": accuracy}, step=example_ct)
print(f"예시 " + str(example_ct).zfill(5) + f"개 후 손실/정확도: {loss:.3f}/{accuracy:.3f}")

이번에는 두 개의 별도의 Artifact을 생성하는 Run을 실행합니다.

첫 번째가 모델 트레이닝을 마치면, 두 번째테스트 데이터셋에서 trained-model Artifact의 성능을 평가함으로써 그것을 사용합니다.

또한, 네트워크가 가장 혼란스러워하는 32개의 예시들 -- categorical_crossentropy가 가장 높은 것들을 찾아냅니다.

이것은 데이터셋과 모델에 대한 문제를 진단하는 좋은 방법입니다!

def evaluate(model, test_loader):
"""
## 트레이닝된 모델 평가하기
"""

loss, accuracy = test(model, test_loader)
highest_losses, hardest_examples, true_labels, predictions = get_hardest_k_examples(model, test_loader.dataset)

return loss, accuracy, highest_losses, hardest_examples, true_labels, predictions

def get_hardest_k_examples(model, testing_set, k=32):
model.eval()

loader = DataLoader(testing_set, 1, shuffle=False)

# 데이터셋의 각 항목에 대한 손실과 예측값을 가져옴
losses = None
predictions = None
with torch.no_grad():
for data, target in loader:
data, target = data.to(device), target.to(device)
output = model(data)
loss = F.cross_entropy(output, target)
pred = output.argmax(dim=1, keepdim=True)

if losses is None:
losses = loss.view((1, 1))
predictions = pred
else:
losses = torch.cat((losses, loss.view((1, 1))), 0)
predictions = torch.cat((predictions, pred), 0)

argsort_loss = torch.argsort(losses, dim=0)

highest_k_losses = losses[argsort_loss[-k:]]
hardest_k_examples = testing_set[argsort_loss[-k:]][0]
true_labels = testing_set[argsort_loss[-k:]][1]
predicted_labels = predictions[argsort_loss[-k:]]

return highest_k_losses, hardest_k_examples, true_labels, predicted_labels

이 로깅 함수들은 새로운 Artifact 기능을 추가하지 않으므로, 우리는 그것들에 대해 언급하지 않을 것입니다: 우리는 단지 Artifact사용하고, 다운로드하고, 로그합니다.

from torch.utils.data import DataLoader

def train_and_log(config):

with wandb.init(project="artifacts-example", job_type="train", config=config) as run:
config = wandb.config

data = run.use_artifact('mnist-preprocess:latest')
data_dir = data.download()

training_dataset = read(data_dir, "training")
validation_dataset = read(data_dir, "validation")

train_loader = DataLoader(training_dataset, batch_size=config.batch_size)
validation_loader = DataLoader(validation_dataset, batch_size=config.batch_size)

model_artifact = run.use_artifact("convnet:latest")
model_dir = model_artifact.download()
model_path = os.path.join(model_dir, "initialized_model.pth")
model_config = model_artifact.metadata
config.update(model_config)

model = ConvNet(**model_config)
model.load_state_dict(torch.load(model_path))
model = model.to(device)

train(model, train_loader, validation_loader, config)

model_artifact = wandb.Artifact(
"trained-model", type="model",
description="트레이닝된 NN 모델",
metadata=dict(model_config))

torch.save(model.state_dict(), "trained_model.pth")
model_artifact.add_file("trained_model.pth")
wandb.save("trained_model.pth")

run.log_artifact(model_artifact)

return model


def evaluate_and_log(config=None):

with wandb.init(project="artifacts-example", job_type="report", config=config) as run:
data = run.use_artifact('mnist-preprocess:latest')
data_dir = data.download()
testing_set = read(data_dir, "test")

test_loader = torch.utils.data.DataLoader(testing_set, batch_size=128, shuffle=False)

model_artifact = run.use_artifact("trained-model:latest")
model_dir = model_artifact.download()
model_path = os.path.join(model_dir, "trained_model.pth")
model_config = model_artifact.metadata

model = ConvNet(**model_config)
model.load_state_dict(torch.load(model_path))
model.to(device)

loss, accuracy, highest_losses, hardest_examples, true_labels, preds = evaluate(model, test_loader)

run.summary.update({"loss": loss, "accuracy": accuracy})

wandb.log({"high-loss-examples":
[wandb.Image(hard_example, caption=str(int(pred)) + "," + str(int(label)))
for hard_example, pred, label in zip(hardest_examples, preds, true_labels)]})
train_config = {"batch_size": 128,
"epochs": 5,
"batch_log_interval": 25,
"optimizer": "Adam"}

model = train_and_log(train_config)
evaluate_and_log()

🔁 그래프 뷰

Artifacttype을 변경했음을 주목하세요: 이 Run들은 데이터셋이 아닌 모델을 사용했습니다. 모델을 생성하는 Run들은 Artifacts 페이지의 그래프 뷰에서 데이터셋을 생성하는 것들과 분리됩니다.

확인해보세요! 이전처럼, Run 페이지로 가서, 왼쪽 사이드바에서 "Artifacts" 탭을 선택하고, Artifact을 선택한 다음, "Graph View" 탭을 클릭하십시오.

💣 터진 그래프

"Explode"라고 표시된 버튼을 주목했을 겁니다. 그것을 클릭하지 마세요, 왜냐하면 그것은 W&B 본사에 있는 겸손한 저자의 책상 아래에 작은 폭탄을 설치할 것이기 때문이죠!

농담입니다. 그것은 그래프를 훨씬 더 부드러운 방법으로 "폭발"시킵니다: ArtifactRuntype의 수준에서 분리되어, 노드들은 데이터셋load-data가 아니라, 데이터셋:mnist-raw:v1load-data:sunny-smoke-1 등이 됩니다.

이것은 당신의 파이프라인에 대한 완전한 통찰을 제공합니다, 로그된 메트릭, 메타데이터 등이 모두 당신의 손끝에 있습니다 -- 당신이 우리와 함께 로그하기로 선택한 것에 의해서만 제한됩니다.

다음은 무엇인가요?

다음 튜토리얼에서는 W&B 모델로 모델 변경 사항을 소통하고 모델 개발 생명주기를 관리하는 방법을 배우게 됩니다:

👉 모델 개발 생명주기 추적하기

Was this page helpful?👍👎