PyTorch Ustalık Serisi — Bölüm 4
Bu bölümde evrişimli sinir ağlarını kullanarak görüntü sınıflandırma işlemi yapıyoruz.
For English:
Bilgisayarla Görme ve Evrişimli Sinir Ağları (Computer Vision and Convolutional Neural Networks)
Bilgisayarla görme, bilgisayarların görüntü ve video gibi görsel verileri tanımlamasını ve anlamasını sağlamaya odaklanan bir bilgisayar bilimi alanıdır.
Örneğin aşağıda görebileceğiniz gibi sürücüsüz arabalar, çevrenin 3 boyutlu tasvirini oluşturmak için farklı noktalara yerleştirilmiş kameralardan gelen görüntüleri kullanıyor.
Bu yazıda aşağıdakileri ele alacağız:
- Torch.vision.datasets’i kullanarak çalışmak üzere bir görsel veri kümesi edinme.
- Pytorch’taki CNN mimarisi.
- Uçtan uca çok sınıflı bir görüntü sınıflandırma problemi.
- Pytorch’ta CNN’lerle modelleme adımları.
- Pytorch ile bir CNN modeli oluşturmak, bir kayıp ve optimizer seçmek, bir modeli eğitmek ve bu modeli değerlendirmek.
Aşağıda görüntülerin nasıl kodlandığı gösterilmektedir. Görsellerin yüksekliği, genişliği, renk kanalları (kırmızı, yeşil, mavi) ve grup boyutu (batch size her gönderimin paket boyutudur) bulunur. CNN’i Pytorch ile derin öğrenme modeli olarak oluşturacağız. Çıktıda 3 kanallı suşi, biftek veya pizza olacaktır. Yani model 3 yemeği ayırt edebilecektir.
Evrişimli Sinir Ağı (CNN) nedir?
Evrişimli bir sinir ağı (CNN veya ConvNet), doğrudan verilerden öğrenen, derin öğrenmeye yönelik bir ağ mimarisidir. CNN’ler, nesneleri, sınıfları ve kategorileri tanımak için görüntülerdeki kalıpları bulma konusunda özellikle kullanışlıdır. Ayrıca ses, zaman serisi ve sinyal verilerinin sınıflandırılmasında da oldukça etkili olabilirler.
- torchvision, Pytorch ile görüntü işlemek için olan temel kitaplıktır.
- torchvision.datasets, bilgisayarlı görü için veri kümelerini ve veri yükleme işlevlerini burada alır.
- torchvision.models, kendi sorunlarınız için kullanabileceğiniz önceden eğitilmiş bilgisayarlı görme modellerine sahiptir.
- torchvision.transforms, görüntü verilerinizi kullanıma uygun olacak şekilde değiştirmek için kullanılır.
- torch.utils.data.Dataset, Pytorch için temel veri kümesi sınıfıdır ve bir veri kümesi üzerinde bir Python yinelenebiliri (iterable) oluşturur.
import torch
from torch as nn
import torchvision
from torchvision import datasets
from torchvision import transforms
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
# Below have different versions
print(torch.__version__)
print(torchvision.__version__)
Veri kümesi alma
torchvision.datasets’teki FashionMNIST veri kümesini kullanacağız:
from torchvision import datasets
train_data = datasets.FashionMNIST(
root ="data", # where to download data
train = True, # do we want the training dataset
download = True, # do we want to download
transform = torchvision.transform.ToTensor(), # how do we want to transform the data
target_transform = None # how do we want to transform the labels/targets
)
test_data = datasets.FashionMNIST(
root = "data",
train = False,
downloaded = True,
transform = ToTensor(),
target_transform = None
)
print(train_data.class_to_idx)
print(train_data.classes)
Verilerimizi Görselleştirelim:
import matplotlib.pyplot as plt
image, label = train_data[0]
plt.imshow(image.squeeze())
plt.title(label)
plt.imshow(image.squeeze(), cmap="grey")
plt.title(class_names[label])
plt.axis(False)
Daha fazla veri görselini veri görselleri ile bir 🧘 olmak için görelim:
torch.manual_seed(42)
fig = plt.figure(figsize=(9,9))
rows, cols = 4, 4
for i in range(1, rows*cols+1):
random_idx = torch.randint(0, len(train_data), size=[1]).item()
img, label = train_data[random_idx]
fig.add_subplot(rows, cols, i)
lt.imshow(img.squeeze(), cmap="gray")
plt.title(class_names[labels])
plt.axis(False)
DataLoader’ı hazırlayalım:
Şu anda verilerimiz Pytorch Veri Kümeleri biçimindedir. DataLoader veri kümemizi bir Python yinelenebilirine dönüştürür. Daha spesifik olarak, verilerimizi gruplara veya mini gruplara (batches or minibatches) dönüştürmek istiyoruz. Bunu yapıyoruz çünkü donanımlarımız tüm verileri aynı anda işleyemiyor. Ayrıca bu sinir ağımıza dönem (epoch) başına gradyanlarını güncelleme şansı verir.
from torch.utils.data import DataLoader
BATCH_SIZE = 32
# Train data ise shuffle because we want our models to not learn order
train_dataloader = DataLoader(dataset=train_data,
batch_size=BATCH_SIZE,
shuffle=True)
train_dataloader = DataLoader(dataset=test_data,
batch_size=BATCH_SIZE,
shuffle=False)
Ne oluşturduğumuzu kontrol edelim:
print(f"DataLoaders:{train_dataloader, test_dataloader}")
print(f"Length of train_dataloader:{len(train_dataloader) batches of {BATCH_SIZE}")
print(f"Length of test_dataloader:{len(test_dataloader) batches of {BATCH_SIZE}}")
Eğitim veri yükleyicisinin içinde ne var?
train_features_batch, train_labels_batch = next(iter(train_dataloader))
train_features_batch.shape, train_labels_batch.shape
Model 0: Temel Model
Bir dizi makine öğrenimi modelleme deneyi oluşturmaya başladığınızda, temel bir modelle başlamak en iyi uyöntemdir. Temel model, deneyeceğiniz ve sonraki modeller/deneylerle geliştireceğiniz basit modele denir.
Düzleştirme (flatten) katmanı, bitişik boyut aralığını bir tensöre düzleştirir.
flatten_model = nn.Flatten()
x = train_features_batch[0]
output = flatten_model(x)
Modeli oluşturalım:
from torch import nn
class FashionMNISTModelV0(nn.Module):
def __init__(self,input_shape:int,hidden_units:int,output_shape:int):
super().__init__()
self.layer_stack = nn.Sequential(
nn.Flatten(),
nn.Linear(in_features=input_shape, out_features=hidden_units),
nn.Linear(in_features=hidden_units, out_features=output_shape),
)
def forward(self, x):
return self.layer_stack(x)
torch.manual_seed(42)
model_0 = FashionMNISTModelV0(
input_shape = 784, # 28 * 28
hidden_units = 10, # unit count in hidden layer
output_shpae = len(class_nmaes) # one for every class
).to("cpu")
dummy_x = torch.rand([1, 1, 28, 28])
model_0(dummy_x)
Kayıp, optimize edici ve değerlendirme metriklerini ayarlayalım:
Çok sınıflı verilerle çalıştığımız için kayıp fonksiyonumuz nn.CorssEntropyClass olacaktır.
Optimize edici torch.optim.SGD()
Bir sınıflandırma problemi üzerinde çalıştığımızdan dolayı, değerlendirme ölçütümüz olarak accuracy’i kullanalım.
import requests
from pathlib import Path
if not Path("helper_functions.py").is_file():
request = request.get("https://raw.githubusercontent.com/mrdbourke/pytorch-deep-learning/main/helper_functions.py")
with open("helper_functions.py","wb") as f:
f.write(request.content)
from helper_functions import accuracy_fn
# Setup loss function and optimizer
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(params=model_0.parameters(), lr=0.1)
Deneylerimizi zamanlayacak bir fonksiyon oluşturma
Makine öğrenimi oldukça deneyseldir. Sıklıkla izlemek isteyeceğiniz ana şeylerden ikisi, kayıp ve doğruluk gibi değerler olan modelin performansı ve modelin ne kadar hızlı çalıştığıdır.
from timeit import default_timer() as timer
def print_train_time(start:float, end:float, device: torch.device = None):
total_time = end - start
print(f"Train time on {device}: {total_time:.3f} seconds")
return total_time
start_time = timer()
end_time = timer()
print_train_time(start=start_time, end=end_time, device="gpu")
Bir eğitim döngüsü oluşturalım ve modeli veri grupları üzerinde eğitelim:
- Dönemler arasında döngü yapın.
- Eğitim grupları arasında dolaşın, eğitim adımlarını gerçekleştirin ve grup başına eğitim kaybını hesaplayın.
- Test grupları arasında dolaşın, test adımlarını gerçekleştirin ve grup başına test kaybını hesaplayın.
- Nelerin gerçekleştiğini konsola yazdırın.
- Eğlencesine çalışma süresini kaydedin.
Not: tqdm, konsolda çalışan bir yükleme çubuğu görselleştirme kitaplığıdır. Jupyter Notebook’un yerleşik bir özelliği vardır.
from tqdm.auto import tqdm
torch.manual_seed(42)
train_time_start_on_cpu = timer()
# We will keep the epoch small for faster training time
epochs = 3
# Creating training and testing loop
for epoch in tqdm(range(epochs)):
print(f"Epoch: {epoch}\n------")
train_loss = 0
for batch, (X, y) in enumerate(train_dataloader):
model_0.train()
y_pred = model_0(X) # forward pass
# calculate loss per batch
loss = loss_fn(y_pred, y)
train_loss += loss # accumulate train loss
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch % 400 == 0:
print(f"Looked at {batch * len(X)} / {len(train_dataloader.dataset)} samples.")
# Divide total train loss by length of train dataloader
train_loss /= len(train_dataloader)
# Testing
test_loss, test_acc = 0, 0
model_0.eval()
with torch.inference_mode():
for X_test, y_test in test_dataloader:
test_pred = model_0(X_test)
test_loss += loss_fn(test_pred, y_test)
test_acc += accuracy_fn(y_true=y_test, y_pred=test_pred.argmax(dim=1))
test_loss /= len(test_dataloader)
test_acc /= len(test_dataloader)
print(f"Train loss: {train_loss:.4f} | Test loss: {test_loss:.4f}, Test ac: {test_acc:.4f}")
train_time_end_on_cpu = timer()
total_train_time_model_0 = print_train_time(start=train_time_start_on_cpu,
end=train_time_end_on_cpu,
device=str(next(model_0.parameters())))
Tahminlerde bulunalım:
torch.manual_seed(42)
def eval_model(modeL: torch.nn.Module,
data_loader: torch.utils.data.DataLoader,
loss_fn: torch.nn.Module,
accuracy_fn,
device = device):
"""Retruns a dictionary containing the results of model predicting on data_loader"""
loss, acc = 0, 0
model.eval()
with torch.inference_mode():
for X, y in tqdm(data_loader):
X, y = X.to(device), y.to(device)
y_pred = model(X)
loss += loss_fn(y_pred, y)
acc += accuracy_fn(y_true=y, y_pred=y_pred.argmax(dim=1))
loss /= len(data_loader)
acc /= len(data_loader)
return {"model_name": model.__class__.__name__, #only works when model was created with a class
"model_loss": loss.item(),
"model_acc": acc}
modeL-0_results = eval_model(model=model_0,
data_loader=test_dataloader,
loss_fn=loss_fn,
accuracy_fn=accuracy_fn)
model_0_results
GPU’muzun CUDA’da çalıştığından emin olalım.
Aşağıdaki terminal komutu mevcut CUDA GPU’larını gösterir:
!nvidia-smi
torch.cuda.is_available() # Return True or False
ModelV1: Doğrusal olmayan daha iyi bir model oluşturmak
class FashionMNISTModelV1(nn.Module):
def __init__(self, input_shape: int, hidden_units: int, output_shape:int):
super().__init__()
self.layer_stack = nn.Sequential(
nn.Flatten(), # Flatten inputs into a single vector
nn.Linear(in_features=input_shape, out_features=hidden_units),
nn.ReLU(),
nn.Linear(in_features=hidden_units, out_features=output_shape),
nn.ReLU()
)
def forward(self, x:torch.Tensor):
return self.layer_stac(x)
torch.manual_seed(42)
model_1 = FashionMNISTModelV1(input_shape=784, hidden_units=10, output_shape=len(class_names)).to(device)
next(model_1.parameters()).device()
Kayıp, optimize edici ve değerlendirme ölçümlerini oluşturalım:
from helper_funcitons import accuracy_fn
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(params=model_1.parameters(), lr=0.1)
Eğitim ve değerlendirme/test döngülerinin işlevselleştirilmesi
Eğitim döngüsü training_step ve test döngüsü test_step() için bir fonksiyon oluşturalım.
def train_step(modeL: torch.nn.Module,
data_loader: torch.utils.data.DataLoader,
loss_fn : torch.nn.Module,
optimizer: torch.optim.Optimizer,
accuracy_fn,
device: torch.device = device):
"""Performs a training with model trying to learn on data_loader."""
train_loss, train_acc = 0, 0
model.train()
for batch, (X,y) in enumerate(data_Loader):
X, y = X.to(device), y.to(device)
y_pred = model(X)
# Calculate loss and accuracy per batch
loss = loss_fn(y_pred, y)
train_loss += loss
train_acc += accuracy_fn(y_true=y, y_pred=y_pred.argmax(dim=1)) # logits to prediction labels
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch % 400 == 0:
printf(f"Looked at {batch * len(X)}/{len(train_dataloader.dataset)} samples.")
# Divide total train lsos and accuracy by length of train dataloader
train_loss /= len(train_dataloader)
train_acc /= len(data_loader)
printf(f"Train loss: {train_loss: .5f | Train acc: {train_acc: 2.f}%")
def test_step(model: torch.nn.Module,
data_loader: torch.utils.data.DataLoader,
loss_fn: torch.nn.Module,
accuracy_fn,
device: torch.device = device):
"""Performs a testing loop step on model going over data_loader."""
test_loss, test_acc = 0, 0
model.eval()
with torch.inference_mode():
for X, y in data_loader:
X, y = X.to(device), y.to(device)
test_pred = model(X)
test_loss += loss_fn(test_pred, y)
test_acc += accuracy_fn(y_true=y,y_pred=test_pred.argmax(dim=1))
# Adjust metrics and print out
test_loss /= len(data_loader)
test_acc /= len(data_loader)
printf(f"Train loss: {train_loss: .5f | Train acc: {train_acc: 2.f}%")
torch.manual_seed(42)
from timeit import default_timer as timer
train_time_start_on_gpu = timer()
epochs = 3
for epoch in tqdm(range(epochs)):
printf(f"Epochs: {epoch}\n-------")
train_step(model=model_1,
data_loader=train_dataloader,
loss_fn=loss_fn,
optimizer=optimizer,
accuracy_fn=accuracy_fn,
device=device)
test_step(model=model_1,
data_loader=test_dataloader,
loss_fn=loss_fn,
accuracy_fn=accuracy_fn,
device=device)
train_time_end_on_gpu = timer()
total_train_time_model_1 = print_train_time(start=train_time_start,
end=train_time_end_on_gpu,
device=device)
Uyarı: Bazen verilerinize/donanımınıza bağlı olarak modelinizin CPU üzerinde GPU’dan daha hızlı eğitildiğini görebilirsiniz.
- Bunun nedeni, verileri/modeli GPU’ya ve GPU’dan kopyalamanın ek yükünün, GPU tarafından sunulan bilgi işlem avantajlarından daha ağır basması olabilir.
- Kullandığınız donanım, hesaplama kapasitesi açısından GPU’dan daha iyi bir CPU’ya sahiptir.
Modellerinizin daha hızlı hesaplanmasını nasıl sağlayacağınız hakkında daha fazla bilgi için buraya bakın: https://horace.io/brrr_intro.html
model_1 sonuçları sözlüğünü alalım:
model_1_results = eval_model(model = model_1,
data_loader = test_dataloader,
loss_fn = loss_fn,
accuracy_fn = accuracy_fn,
device = device)
Evrişimli bir sinir ağı (CNN) oluşturalım
CNN’ler ConvNet’ler olarak da bilinir. CNN’ler görsel verilerdeki kalıpları bulma yetenekleriyle bilinir.
Aşağıdaki adresten deneyerek CNN’i daha iyi anlayabilirsiniz:
Yukarıdaki derin öğrenme katmanını Pytorch’ta oluşturalım:
class FashionMNISTModelV2(nn.Module):
"Model architecture that replicates the TinyVGG model from CNN explainer website."
def __init__(self, input_shape: int, hidden_units: int, output_shape:int):
super().__init__()
self.conv_block_1 = nn.Sequential(
nn.Conv2d(in_channels=input_shape,
out_channels=hidden_units,
kernel_size=3,
stride=1,
padding=1)
nn.ReLU(),
nn.Conv2d(in_channels=input_shape,
out_channels=hidden_units,
kernel_size=3,
stride=1,
padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2)
)
self.conv_block_2 = nn.Sequential(
nn.Conv2d(in_channels=input_shape,
out_channels=hidden_units,
kernel_size=3,
stride=1,
padding=1)
nn.ReLU(),
nn.Conv2d(in_channels=input_shape,
out_channels=hidden_units,
kernel_size=3,
stride=1,
padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2)
)
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(in_features=hidden_units*0,
out_features=output_shape)
)
def forward(self, x):
x = self.conv_block_1(x)
x = self.conv_block_2(x)
x = self.classifier(x)
return x
torch.manual_seed(42)
model_2 = FashionMNISTModelV2(input_shape=1, # input shape is 1 because there are no colors (e.g 3)
hidden_units=10,
output_shape=len(class_names),
kernel_size=(3,3),
stride=1,
padding=1)
conv_output = conv_layer(test_image.unsqueeze(0))
conv_output.shape
MaxPool2d()’yi kullanmak:
max_pool_layer = nn.MaxPool2d(kernel_size=2)
test_image_through_conv = conv_layer(test_image.unsqueeze(dim=0))
test_image_through_conv_and_max_pool = max_pool_layer(test_image_through_conv)
print(test_image.shape)
print(test_image.unsqueeze(0).shape)
print(test_image_through_conv)
print(test_image_through_conv_and_max_pool)
Model_2 için bir kayıp fonksiyonu ve optimize ediciyi ayarlayalım:
from helper_functions import accuracy_fn
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(params=model_2.parameters(), lr=0.2)
Eğitim ve test fonksiyonlarını kullanarak model_2'yi eğitelim ve test edelim:
torch.manuel_seed(42)
torch.cuda.manual_seed(42)
from timeit import default_timer as timer
train_time_start_model_2 = timer()
epochs = 3
for epoch in tqdm(range(epochs)):
train_step(model=model_2,
data_loader=train_dataloader,
loss_fn=loss_fn,
optimizer=optimizer,
accuracy_fn=accuracy_fn,
device=device)
test_step(model=model_2,
data_loader=test_dataloader,
loss_fn=loss_fn,
accuracy_fn=accuracy_fn,
device=device)
train_time_end_model_2 = timer()
total_train_time_model_2 = print_train_time(start=train_time_start_model_2,
end=train_time_end_model_2,
device=device)
model_2_result= eval_model(
model=model_2,
data_loader=test_dataloader,
loss_fn=loss_fn,
accuracy_fn=accuracy_fn,
device=device
)
Model sonuçlarını ve eğitim süresini karşılaştıralım:
import pandas as pd
compare_results = pd.DataFrame([model_0_results, model_1,results, model_2_results])
compare_results["training_time"] = [total_train_time_model_0,
total_train_time_model_1,
total_train_time_model_2]
print(compare_results)
En iyi modelle rastgele tahminler yapalım ve değerlendirelim:
def make_predictions(model: torch.nn.Module, data: list, device: torch.device = device):
pred_probs = []
model.to(device)
model.eval()
with torch.inference_mode():
for sample in data:
sample = torch.unsqueeze(sample, dim=0).to(device)
pred_logit = model(sample)
pred_prob = torch.softmax(pred_logit.squeeze(), dim=0)
pred_probs.append(pred_prob.cpu())
return torch.stack(pred.probs)
import random
random.seed(42)
test_sample=[]
test_labels = []
for sample, label in random.sample(list(test_data), k=9)
test_samples.append(sample)
test_samples.append(label)
plt.imshow(test_samples[0].squeeze(), cmap="gray")
plt.title(class_names[test_labels[0]])
# Converting prediction probabilities to labels
pred_probs = make_predictions(model=model_2, data=test_samples)
pred_classes = pred_probs.argmax(dim=1)
# Plot predictions
plt.figure(sigsize=(9,9))
nrows=3
ncols=3
for i, sample in enumerate(test_samples):
plt.subplot(nrows, ncols, i+1)
plt.imshow(sample.squeeze(), cmap="gray")
pred_label = class_names[pred_classes[i]]
truth_label = class_names[test_labels[i]]
title_text = f"Pred: {pred_label} | Truth : {truth_label}"
if pred_label == truth_label:
plt.title(title_text, fontsize=10, c="g")
else:
plt.title(title_text, fntsize=10, c="r")
plt.axis(False)
Daha ileri tahmin değerlendirmesi için bir karışıklık matrisi (confusion matrix) oluşturmak
- Karışıklık matrisi, sınıflandırma modellerinizi görsel olarak değerlendirmenin harika bir yoludur.
- Eğitilmiş modelimizle test veri kümesi üzerinde tahminler yapalım.
- Torchmetrics.ConfusionMatrix ile bir karışıklık matrisi oluşturun mlxtend.plotting.plot_confusion_matrix() işlevini kullanarak karışıklık matrisini çizelim.
import tqdm.auto import tqdm
# Make predicitons with trained model
y_preds = []
model_2.eval()
with torch.inference_mode():
for X, y in tqdm(test_dataloader, desc="Making predictions..."):
# Send data and targets to target device
X, y = X.to(device), y.to(device)
y_logit = model_2(X)
# Turn predictions from logits to predicition probabilities to prediction labels
y_preds = torch.softmax(y_logits.squeeze(), dim=0).argmax(dim=1)
#Put prediction on CPU for evaluation
y_preds.append(y_pred.cpu())
# Concatenate list of predictions into a tensor
y_pred_tensor = torch.cat(y_preds)
try:
import mlxtend, torchmetrics
assert int(mlxtend.__version__.split(".")[1]) >= 19, "mlextend version should be 0.19.0 or higher")
except:
!pip install -q torchmetrics -U mlxtend
import torchmetrics, mlxtend
from torchmetrics import ConfusionMatrix
from mlxtend.plotting import plot_confusion_matrix
confmat = ConfusionMatrix(num_classes=len(class_names))
confmat_tensor = confmat(preds=y_pred_tensor, target=test_data.targets)
fig, ax = plot_confusion_matrix(conf_mat=confmat_tensor.numpy(),
class_names=class_names,
figsize=(10,7)
)
Modelimizi kaydetme ve yükleme
from pathlib import Path
MODEL_PATH = Path("models")
MODEL_PATH.mkdir(parents=True, exist_ok=True)
MODEL_NAME = "computer_vision_model.pth"
MODEL_SAVE_PATH = MODEL_PATH / MODEL_NAME
print(f"Saving the model to:{MODEl_SAVE_PATH}")
torch.save(obj=model_2.state_dict(), f=MODEL_SAVE_PATH)
image_shape = [1, 28, 28]
torch.manual_seed(42)
loaded_model_2 = FashionMNISTModelV2(input_shape=1,
hidden_unit=10,
output_shape=len(class_names))
loaded_model_2.load_state_dict(torch.load(f=MODEL_SAVE_PATH))
loaded_model_2.to(device)
Yüklenen modeli değerlendirelim:
torch.manual_seed(42)
loaded_model_2_results = eval_model(
model=loaded_model_2,
data_loader=test_dataloader,
loss_fn=loss_fn,
accuracy_fn=accuracy_fn
)
Model sonuçlarının birbirine yakın olup olmadığının kontrol edelim:
torch.isclose(torch.tensor(model_2_results["model_loss"]),
torch.tensor(loaded_model_2_results["model_loss"]),
atol=1e-02)
Kaynak
[1] freeCodeCamp.org, (6 Ekim 2022), PyTorch for Deep Learning & Machine Learning — Full Course: