TensorBoard 텐서보드 소개
TensorBoard(텐서보드)는 오픈 소스 머신러닝 프레임워크인 TensorFlow에서 제공하는 시각화 도구이다.
텐서보드는 머신러닝 모델의 다양한 부분을 시각적으로 모니터링하고 분석할 수 있다.
텐서보드를 사용하면 모델 아키텍쳐를 보거다 메트릭을 추적 및 시각화하고 트레이닝 커브를 모니터링하는 등 모니터링하고 싶은 웬만한 것들을 전부 시각화할 수 있다.
또한 사용법도 어렵지 않고 직관적인 웹 기반 인터페이스를 제공하며 PyTorch를 비롯한 여러 프레임워크와 호환되기 때문에 많은 딥러닝 커뮤니티에서 텐서보드를 애용한다.
이번 포스팅에서는 텐서보드 사용법을 PyTorch 예제를 통해 알아보자.
TensorBoard 설치
텐서보드를 파이토치와 함께 사용하려면 텐서보드가 포함된 텐서플로 라이브러리를 설치해야 한다.
다음 명령어로 텐서플로를 설치할 수 있다.
만약 가상환경에서 파이토치를 사용 중이라면 당연히 가상환경을 활성화 한 다음 설치해야 한다.
pip install tensorflow
잘 설치가 되었는지 간단한 테스트 코드를 작성해보자. (출처: 파이토치 공식 웹)
import torch
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter()
x = torch.arange(-5, 5, 0.1).view(-1, 1)
y = -5 * x + 0.1 * torch.randn(x.size())
model = torch.nn.Linear(1, 1)
criterion = torch.nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr = 0.1)
def train_model(iter):
for epoch in range(iter):
y1 = model(x)
loss = criterion(y1, y)
writer.add_scalar("Loss/train", loss, epoch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_model(10)
writer.flush()
이제 웹에서 텐서보드를 시작해보자.
터미널에 다음을 입력하자.
참고로 아래 --logdir=runs
의 runs
는 위 코드에서 SummaryWriter
의 괄호 안에 넣은 디렉토리에 생긴 runs
폴더의 위치를 넣어주면 된다.
tensorboard --logdir=runs
웹브라우저를 열고 터미널에 나오는 URL을 복사한다음 붙여넣자. (기본값은 http://localhost:6006/)
혹은 아래 옵션으로 포트를 지정할 수 있다.
tensorboard --logdir=runs --port=원하는_포트_번호
그럼 http://localhost:원하는_포트_번호/를 입력하여 텐서보드 대시보드를 열 수 있다.
그럼 아래와 같이 러닝 커브가 시각화된 모습이 나온다.
Duplicate plugins for name projector 에러 해결
pip으로 텐서플로를 설치하고 위의 테스트 코드를 돌릴 때 이런 에러가 발생할 수 있다.
...
ValueError: Duplicate plugins for name projector
해결 방법은 터미널에서 텐서플로를 지우고 tb-nightly을 설치하여 해결할 수 있다.
일단 텐서보드를 지우고
pip uninstall tensorboard
tb-nightly을 설치한다.
pip install tb-nightly
이제 테스트 코드를 실행시켜서 텐서보드가 잘 작동하는지 확인해보자.
예제를 통해 알아보는 TensorBoard을 사용한 여러가지 데이터 모니터링
이제 실전 코드를 가지고 텐서보드 모니터링을 해보자.
사용할 예제는 본 블로그에서 예전에 소개했던 Auxiliary Classifier Generative Adversarial Network(ACGAN)이다.
재밌고 쉽운 생성모델이니 한번 보고 오면 이해하기 더 좋다.
[Machine Learning] PyTorch로 ACGAN 구현하기 (Auxiliary Conditional GAN using PyTorch)
[Machine Learning] PyTorch로 ACGAN 구현하기 (Auxiliary Conditional GAN using PyTorch)
GAN(Generative Adversarial Networks)은 실제 데이터와 매우 유사한 가짜 데이터를 생성하는 데 사용되는 딥 러닝 알고리즘이다. GAN은 두 개의 네트워크 즉, Generator(생성기)와 Discriminator(판별기)으로 구성
dykm.tistory.com
먼저 텐서보드 시각화 기능을 추가하지 않은 코드는 아래와 같다. (조만간 코드를 접었다 폈다 하는 플러그인을 추가해야겠다.)
코드는 위에 링크를 건 포스팅에서 가져온 것이다. (이미지를 저장하는 부분만 삭제했다.)
import os
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.datasets as datasets
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
# Device configuration
device = torch.device('cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu')
# Hyperparameters
latent_size = 100
num_epochs = 200
batch_size = 64
image_size = 28
num_classes = 10
lr = 0.0002
beta1 = 0.5
# MNIST dataset
transform = transforms.Compose([
transforms.Resize(image_size),
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
train_dataset = datasets.MNIST(root='data/', train=True, transform=transform, download=True)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=4)
# ACGAN Generator network
class Generator(nn.Module):
def __init__(self, latent_size=100, num_classes=10):
super(Generator, self).__init__()
self.label_emb = nn.Embedding(num_classes, num_classes)
self.fc = nn.Sequential(
nn.Linear(latent_size+num_classes, 7*7*128),
nn.BatchNorm1d(7*7*128),
nn.ReLU(True)
)
self.upconv1 = nn.Sequential(
nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(True)
)
self.upconv2 = nn.Sequential(
nn.ConvTranspose2d(64, 1, kernel_size=4, stride=2, padding=1),
nn.Tanh()
)
def forward(self, z, labels):
inputs = torch.cat([z, self.label_emb(labels)], dim=1)
x = self.fc(inputs)
x = x.view(-1, 128, 7, 7)
x = self.upconv1(x)
x = self.upconv2(x)
return x
class Discriminator(nn.Module):
def __init__(self, num_classes=10):
super(Discriminator, self).__init__()
self.label_emb = nn.Embedding(num_classes, num_classes)
self.conv1 = nn.Sequential(
nn.Conv2d(1+num_classes, 64, kernel_size=4, stride=2, padding=1),
nn.LeakyReLU(0.2, inplace=True)
)
self.conv2 = nn.Sequential(
nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1),
nn.BatchNorm2d(128),
nn.LeakyReLU(0.2, inplace=True)
)
self.fc_real_fake = nn.Linear(128*7*7, 1)
self.fc_class = nn.Linear(128*7*7, num_classes)
self.sigmoid = nn.Sigmoid()
self.softmax = nn.Softmax(dim=1)
def forward(self, x, labels):
c = self.label_emb(labels)
c = c.unsqueeze(-1).unsqueeze(-1)
c = c.repeat(1, 1, x.size(2), x.size(3))
x = torch.cat([x, c], dim=1)
x = self.conv1(x)
x = self.conv2(x)
x = x.view(-1, 128*7*7)
real_fake = self.sigmoid(self.fc_real_fake(x))
class_pred = self.softmax(self.fc_class(x))
return real_fake, class_pred
# Initialize generator and discriminator
generator = Generator(latent_size, num_classes).to(device)
discriminator = Discriminator(num_classes).to(device)
# Initialize loss functions
adversarial_loss = nn.BCELoss()
auxiliary_loss = nn.CrossEntropyLoss()
# Initialize optimizers
optimizer_G = optim.Adam(generator.parameters(), lr=lr, betas=(beta1, 0.999))
optimizer_D = optim.Adam(discriminator.parameters(), lr=lr, betas=(beta1, 0.999))
# Fixed noise settings to monitor the training process
fixed_noise = torch.randn(100, 100).to(device)
fixed_gen_label = torch.tensor([ int(i/10) for i in range(100) ], dtype=torch.int64).to(device)
# Training loop
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
batch_size = images.size(0)
images = images.to(device)
labels = labels.to(device)
# Adversarial ground truths
valid = torch.ones(batch_size, 1).to(device)
fake = torch.zeros(batch_size, 1).to(device)
# Configure input
z = torch.randn(batch_size, latent_size).to(device)
# Generate fake images
gen_labels = torch.randint(0, num_classes, (batch_size,)).to(device)
fake_images = generator(z, gen_labels)
# Train discriminator with real images
real_pred, real_aux = discriminator(images, labels)
d_loss_real = adversarial_loss(real_pred, valid)
d_aux_real = auxiliary_loss(real_aux, labels)
d_real_loss = d_loss_real + d_aux_real
# Train discriminator with fake images
fake_pred, fake_aux = discriminator(fake_images.detach(), gen_labels)
d_loss_fake = adversarial_loss(fake_pred, fake)
d_aux_fake = auxiliary_loss(fake_aux, gen_labels)
d_fake_loss = d_loss_fake + d_aux_fake
# Total discriminator loss
d_loss = 0.5 * (d_real_loss + d_fake_loss)
# Backward pass for discriminator
optimizer_D.zero_grad()
d_loss.backward()
optimizer_D.step()
# Train generator
gen_labels = torch.randint(0, num_classes, (batch_size,)).to(device)
fake_images = generator(z, gen_labels)
fake_pred, fake_aux = discriminator(fake_images, gen_labels)
g_loss_adv = adversarial_loss(fake_pred, valid)
g_loss_aux = auxiliary_loss(fake_aux, gen_labels)
g_loss = g_loss_adv + g_loss_aux
# Backward pass for generator
optimizer_G.zero_grad()
g_loss.backward()
optimizer_G.step()
# Print progress
if i % 100 == 0:
print(f"Epoch [{epoch}/{num_epochs}], Batch [{i}/{len(train_loader)}], "
f"D_loss: {d_loss.item():.4f}, G_loss: {g_loss.item():.4f}")
이제 모니터링할 요소들을 정해보자.
- d_loss
- g_loss
- output image of fixed_noise
(fixed_noise에 대한 Generator의 아웃풋(이미지), epoch에 따른 Generator의 아웃풋 모니터링 가능)
자 이제 코드를 수정해보자.
먼저 필요한 라이브러리를 추가로 가져오자.
import os
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.datasets as datasets
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from torchvision.utils import make_grid
from torch.utils.tensorboard import SummaryWriter
위 코드에서 from torchvision.utils import make_grid
을 추가한 다음,
텐서보드 사용을 위한 from torch.utils.tensorboard import SummaryWriter을
추가했다.
이제 트레이닝 루프 바로 위를 건드려보자.
주석을 빼면 사실상 두 줄만 추가하면 된다.
# Create a SummaryWriter instance
writer = SummaryWriter()
# Training loop
total_steps = 0
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
batch_size = images.size(0)
...
이제 트레이닝 루프 맨 마지막에 몇 줄을 추가하자.
...
# Print progress
if i % 100 == 0:
print(f"Epoch [{epoch}/{num_epochs}], Batch [{i}/{len(train_loader)}], "
f"D_loss: {d_loss.item():.4f}, G_loss: {g_loss.item():.4f}")
# Write the losses to TensorBoard
writer.add_scalar('Discriminator Loss', d_loss.item(), total_steps)
writer.add_scalar('Generator Loss', g_loss.item(), total_steps)
total_steps += 1
# Write the generated images to TensorBoard
with torch.no_grad():
fake_images = generator(fixed_noise, fixed_gen_label)
grid = make_grid(fake_images, nrow=10, normalize=True)
writer.add_image('Generated Images', grid, epoch)
# Close the SummaryWriter
writer.close()
writer.add_scalar('Discriminator Loss', d_loss.item(), total_steps)
를 간단히 설명을 하자면
Discriminator Loss라는 제목 아래 total_steps
을 x축으로 d_loss.item()
을 y축으로 하는 그래프를 그리겠다는 소리다.
그렇기 때문에 각 iter마다 total_steps
을 1씩 추가하는 것이다.(추가를 안 하면 계속 덮어쓰니까...)
그리고 아래 with
문에서 make_grid
을 통해 이미지를 만든 다음writer.add_image('Generated Images', grid, epoch)
을 통해
Generated Images라는 제목 아래 epoch
마다 이미지를 출력해서 텐서보드에 띄워주는 것이다.
아무래도 이 작업은 각 iter마다 실행하기엔 변화도 별로 없을 뿐더러 시간도 오래 잡아먹게 되기 때문에 각 epoch
마다 실행하는 것이다.
아무튼 이렇게 수정한 코드를 전부 써보면 아래와 같다. (아래 코드를 복붙하면 된다는 소리다.)
import os
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.datasets as datasets
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from torchvision.utils import make_grid
from torch.utils.tensorboard import SummaryWriter
# Device configuration
device = torch.device('cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu')
# Hyperparameters
latent_size = 100
num_epochs = 200
batch_size = 64
image_size = 28
num_classes = 10
lr = 0.0002
beta1 = 0.5
# MNIST dataset
transform = transforms.Compose([
transforms.Resize(image_size),
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
train_dataset = datasets.MNIST(root='data/', train=True, transform=transform, download=True)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=4)
# ACGAN Generator network
class Generator(nn.Module):
def __init__(self, latent_size=100, num_classes=10):
super(Generator, self).__init__()
self.label_emb = nn.Embedding(num_classes, num_classes)
self.fc = nn.Sequential(
nn.Linear(latent_size+num_classes, 7*7*128),
nn.BatchNorm1d(7*7*128),
nn.ReLU(True)
)
self.upconv1 = nn.Sequential(
nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(True)
)
self.upconv2 = nn.Sequential(
nn.ConvTranspose2d(64, 1, kernel_size=4, stride=2, padding=1),
nn.Tanh()
)
def forward(self, z, labels):
inputs = torch.cat([z, self.label_emb(labels)], dim=1)
x = self.fc(inputs)
x = x.view(-1, 128, 7, 7)
x = self.upconv1(x)
x = self.upconv2(x)
return x
class Discriminator(nn.Module):
def __init__(self, num_classes=10):
super(Discriminator, self).__init__()
self.label_emb = nn.Embedding(num_classes, num_classes)
self.conv1 = nn.Sequential(
nn.Conv2d(1+num_classes, 64, kernel_size=4, stride=2, padding=1),
nn.LeakyReLU(0.2, inplace=True)
)
self.conv2 = nn.Sequential(
nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1),
nn.BatchNorm2d(128),
nn.LeakyReLU(0.2, inplace=True)
)
self.fc_real_fake = nn.Linear(128*7*7, 1)
self.fc_class = nn.Linear(128*7*7, num_classes)
self.sigmoid = nn.Sigmoid()
self.softmax = nn.Softmax(dim=1)
def forward(self, x, labels):
c = self.label_emb(labels)
c = c.unsqueeze(-1).unsqueeze(-1)
c = c.repeat(1, 1, x.size(2), x.size(3))
x = torch.cat([x, c], dim=1)
x = self.conv1(x)
x = self.conv2(x)
x = x.view(-1, 128*7*7)
real_fake = self.sigmoid(self.fc_real_fake(x))
class_pred = self.softmax(self.fc_class(x))
return real_fake, class_pred
# Initialize generator and discriminator
generator = Generator(latent_size, num_classes).to(device)
discriminator = Discriminator(num_classes).to(device)
# Initialize loss functions
adversarial_loss = nn.BCELoss()
auxiliary_loss = nn.CrossEntropyLoss()
# Initialize optimizers
optimizer_G = optim.Adam(generator.parameters(), lr=lr, betas=(beta1, 0.999))
optimizer_D = optim.Adam(discriminator.parameters(), lr=lr, betas=(beta1, 0.999))
# Fixed noise settings to monitor the training process
fixed_noise = torch.randn(100, 100).to(device)
fixed_gen_label = torch.tensor([ int(i/10) for i in range(100) ], dtype=torch.int64).to(device)
# Create a SummaryWriter instance
writer = SummaryWriter()
# Training loop
total_steps = 0
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
batch_size = images.size(0)
images = images.to(device)
labels = labels.to(device)
# Adversarial ground truths
valid = torch.ones(batch_size, 1).to(device)
fake = torch.zeros(batch_size, 1).to(device)
# Configure input
z = torch.randn(batch_size, latent_size).to(device)
# Generate fake images
gen_labels = torch.randint(0, num_classes, (batch_size,)).to(device)
fake_images = generator(z, gen_labels)
# Train discriminator with real images
real_pred, real_aux = discriminator(images, labels)
d_loss_real = adversarial_loss(real_pred, valid)
d_aux_real = auxiliary_loss(real_aux, labels)
d_real_loss = d_loss_real + d_aux_real
# Train discriminator with fake images
fake_pred, fake_aux = discriminator(fake_images.detach(), gen_labels)
d_loss_fake = adversarial_loss(fake_pred, fake)
d_aux_fake = auxiliary_loss(fake_aux, gen_labels)
d_fake_loss = d_loss_fake + d_aux_fake
# Total discriminator loss
d_loss = 0.5 * (d_real_loss + d_fake_loss)
# Backward pass for discriminator
optimizer_D.zero_grad()
d_loss.backward()
optimizer_D.step()
# Train generator
gen_labels = torch.randint(0, num_classes, (batch_size,)).to(device)
fake_images = generator(z, gen_labels)
fake_pred, fake_aux = discriminator(fake_images, gen_labels)
g_loss_adv = adversarial_loss(fake_pred, valid)
g_loss_aux = auxiliary_loss(fake_aux, gen_labels)
g_loss = g_loss_adv + g_loss_aux
# Backward pass for generator
optimizer_G.zero_grad()
g_loss.backward()
optimizer_G.step()
# Print progress
if i % 100 == 0:
print(f"Epoch [{epoch}/{num_epochs}], Batch [{i}/{len(train_loader)}], "
f"D_loss: {d_loss.item():.4f}, G_loss: {g_loss.item():.4f}")
# Write the losses to TensorBoard
writer.add_scalar('Discriminator Loss', d_loss.item(), total_steps)
writer.add_scalar('Generator Loss', g_loss.item(), total_steps)
total_steps += 1
# Write the generated images to TensorBoard
with torch.no_grad():
fake_images = generator(fixed_noise, fixed_gen_label)
grid = make_grid(fake_images, nrow=10, normalize=True)
writer.add_image('Generated Images', grid, epoch)
# Close the SummaryWriter
writer.close()
코드를 실행시킨 다음 터미널에서 runs 폴더를 찾아서 아래 명령어를 실행시키자.
tensorboard --logdir=runs
그 다음 웹브라우저를 열고 http://localhost:6006을 들어가면 아래와 같이 학습을 모니터링할 수 있다. (이미지가 이상한 이유는 아직 학습이 충분하게 되지 않았기 때문. GAN 포스팅이 아니라 텐서보드 포스팅이므로 넘어가자)
참고로 아래 부분
텐서보드로 데이터를 넘겨주는 부분을
# Write the losses to TensorBoard
writer.add_scalar('Discriminator Loss', d_loss.item(), total_steps)
writer.add_scalar('Generator Loss', g_loss.item(), total_steps)
새롭게 아래와 같이 쓰면
# Write the losses to TensorBoard
writer.add_scalar('Discriminator Loss/real', d_real_loss.item(), total_steps)
writer.add_scalar('Discriminator Loss/fake', d_fake_loss.item(), total_steps)
writer.add_scalar('Generator Loss', g_loss.item(), total_steps)
아래 이미지처럼 한 제목아래 두 개의 그래프를 넣을 수도 있다.
텐서보드를 사용해 모델의 학습 과정을 모니터링하는 방법을 살펴보았다.
다음 글에선 서버에서 학습을 시킬 때 로컬 환경에서 학습을 모니터링하는 방법을 살펴보자.
'코딩 환경 > PyTorch' 카테고리의 다른 글
[PyTorch] num_workers 설명과 빠른 트레이닝을 위한 값 최적화 (0) | 2023.05.24 |
---|---|
[PyTorch] 서버에서 TensorBoard(텐서보드) 실행하고 Port Forwarding(포트 포워딩)으로 로컬에서 모니터링하는 방법 (1) | 2023.05.15 |
[PyTorch] CustomDataset 설정 및 예제 (0) | 2023.03.21 |
[PyTorch] GPU을 사용할 때 to(device)와 cuda() 차이 (2) | 2023.02.27 |
[PyTorch] MNIST 데이터셋 다운로드하고 열어보기 (0) | 2023.02.26 |