Evrişimli sinir ağları (Convolutional neural networks)
Bu bölümde görüntü verileri üzerinde sınıflandırma yapmak için kullanacağımız evrişimsel bir sinir ağı oluşturacağız. Evrişimli sinir ağları, örneğin iki boyutlu görüntüler gibi birden fazla boyutu olan verilerle çalışmak için mükemmeldir. Bu örnekte, görüntüleri boyutsallıklarına saygı gösterecek şekilde işlemek için evrişimli katmanları ve havuz katmanlarını kullanıyoruz.
Veri kümesi
10 farklı nesnenin 60000 görüntüsünü içeren CIFAR10 görüntü veri kümesini kullanacağız.
Veri setini, torchvision kitaplığından indireceğiz. Veri kümesi, doğrudan PyTorch katmanlarıyla kullanamadığımız
PIL görüntülerini içerir. Bu nedenle, görüntüleri tensörlere dönüştürmek için ToTensor()
dönüşümünü kullanıyoruz.
Bu, görüntüdeki her pikseli, o pikselin RGB değerlerini temsil eden [0,1] aralığında üç sayıdan oluşan bir tensöre dönüştürecektir.
Ayrıca değerleri [-0.5, 0.5] aralığına kaydırmak için Normalize()
adlı ikinci bir dönüşüm uygulayacağız.
import torchvision
import torchvision.transforms as transforms
# veriler dönüştürülmeden yüklenecek. Görüntü nesneleri şeklinde olacak.
train_dataset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True)
first_element = train_dataset[0]
image, label = first_element
print("Verinin değişimden önceki şekli")
print(type(image), image)
# İki dönüşümü uygulayacak tek bir dönüşüm oluşturun:
# 1. ToTensor(): Her görüntüdeki her pikseli RGB değerleri için üç
# sayıdan oluşan bir tensöre dönüştürün.
# 2. Normalize(): değerleri [0,1] aralığından [-0.5, 0.5] değerine kaydırır
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# veriler yüklenir ve dönüşümler uygulanır
train_dataset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
test_dataset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform)
first_element = train_dataset[0]
image, label = first_element
print(type(image), image.shape)
print("Her görüntü 32x32 pikseldir ve her pikselin üç değeri vardır")
import matplotlib.pyplot as plt
import numpy as np
# Bu işlev, tensör temsilini kullanarak görüntüyü gösterecektir.
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
print("İlk görüntü bir kurbağa görüntüsü!")
imshow(image)
Çıktı
Verinin değişimden önceki şekli
<class 'PIL.Image.Image'> <PIL.Image.Image image mode=RGB size=32x32 at 0x29F41017130>
<class 'torch.Tensor'> torch.Size([3, 32, 32])
Her görüntü 32x32 pikseldir ve her pikselin üç değeri vardır

Veri yükleyiciler (Dataloaders)
Eğitim ve test döngüleri sırasında kullanacağımız veri kümelerini taşıyabilmek için veri yükleyicileri oluşturuyoruz.
import torch
# her parti 4 görüntüden oluşacaktır
batch_size = 4
# veri yükleyici örnekleri karıştıracak
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size,
shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size,
shuffle=False)
dataiter = iter(train_loader)
images, labels = dataiter.next()
print(images.shape)
Çıktı
torch.Size([4, 3, 32, 32])
Evrişimli sinir ağı modeli
Modelimiz, görüntü veri tensörlerini alacak ve bunları evrişim katmanları ve havuz katmanları aracılığıyla işleyecektir.
Daha sonra, tahmin etmek istediğimiz sınıflar için verileri doğrusal katmanlardan alacağız ve son skorları elde edeceğiz.
Bu bölümde, önce evrişim katmanını ve havuzlama katmanını göstereceğiz, ardından torch.nn.Module
sınıfından miras alarak
tam sinir ağı modelini oluşturacağız.
Evrişimsel katmanlar
Evrişimsel katmanlar çok boyutlu verileri alır ve çok boyutlu bir çıktı üretmek için bir evrişim kullanır. Aşağıdaki örnek, modelimizde kullanacağımız ilk evrişimsel katmanı göstermektedir. Ancak, aşağıdaki örnekte, netlik için 32x32 yerine 9x9 boyutlarında bir giriş görüntüsü kullanıyoruz. Katman, görüntüdeki her 5x5 piksel grubunun üzerinden geçerek ve bunları çıktıda tek bir piksele dönüştürerek 5x5’lik bir filtre eğitir. Ayrıca filtre, girişteki her pikselin üç rengini (kanalını) kullanacak ve çıktıdaki her piksel için 6 çıkış kanalı üretecektir.

Aşağıda, evrişim katmanının bir girdiye ne yaptığının bir gösterimi verilmiştir. Katmana dört görüntüden oluşan bir toplu iş gönderiyoruz ve dönüştürülmüş bir çıktı alıyoruz:
import torch.nn as nn
conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=5)
# input_channels = her giriş pikselindeki kanal sayısı
# output_channels = her çıkış pikselindeki kanal sayısı
# kernel_size = filtrenin genişliği ve yüksekliği
dataiter = iter(train_loader)
images, labels = dataiter.next()
print(f"katmandan önce, şekil: {images.shape}")
output = conv1(images)
print(f"katmandan sonra, şekil: {output.shape}")
Çıktı
katmandan önce, şekil: torch.Size([4, 3, 32, 32])
katmandan sonra, şekil: torch.Size([4, 6, 28, 28])
Havuz katmanları (Pooling layers)
Evrişim katmanlarını çalıştırdıktan sonra, çıktıyı daha küçük bir temsile sıkıştırmak için havuz katmanlarını kullanabiliriz. Modelimizde, bir önceki katmanın çıktısını alacak ve maksimum fonksiyonunu kullanarak sıkıştıracak bir max-pool kullanıyoruz. Aşağıda bir havuzlama katmanı örneği verilmiştir.

Aşağıda, önceki evrişim katmanının çıktısını alacak ve havuzlama uygulayacak bir maksimum havuz katmanı gösteriyoruz. Katman 2x2’lik ızgaralar alacak ve maksimum değerlerini bulacaktır. Havuzlama katmanının adımı 2’dir, bu nedenle filtre bir seferde 2 konum hareket edecektir. Bu havuzlama prosedürü, girişin tüm kanalları için gerçekleşir.
pool = nn.MaxPool2d(kernel_size=2,stride=2)
# kernel_size = filtrenin genişliği ve yüksekliği
# stride = filtreleme işlemleri arasındaki mesafe
print(f"katmandan önce, şekil: {output.shape}")
output = pool(output)
print(f"katmandan sonra, şekil: {output.shape}")
Çıktı
katmandan önce, şekil: torch.Size([4, 6, 28, 28])
katmandan sonra, şekil: torch.Size([4, 6, 14, 14])
Tam model
torch.nn.Module
sınıfından miras alarak modelimizi oluşturuyoruz. Her evrişim katmanından sonra kullanacağımız iki
evrişim katmanı ve tek bir havuz işlevi tanımlıyoruz. Ayrıca, evrişimin çıktısını alacak ve tahmin edilecek sınıf sayısı
olan sadece 10 çıktı olana kadar kademeli olarak dönüştürecek üç doğrusal katman tanımlıyoruz.
import torch.nn.functional as F
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
# ilk evrişim 5x5 boyutlarında bir filtre kullanır, piksel başına 3 giriş
# kanalı alır ve 6 çıkış kanalı üretir
self.conv1 = nn.Conv2d(3, 6, 5)
# 2x2 ızgaralı ve 2 adımlı max-pool kullanıyoruz. Havuz eğitilmediğinden,
# yalnızca bir örneğine ihtiyacımız var
self.pool1and2 = nn.MaxPool2d(2, 2)
# İkinci evrişim 5x5 boyutlarında bir filtre kullanır, ancak 6 giriş kanalı alır
# ve konum başına 16 çıkış kanalı üretir
self.conv2 = nn.Conv2d(6, 16, 5)
# Bu doğrusal katman, üzerine self.pool1and2 uygulandıktan sonra conv2'nin çıktısını
# alacaktır, bu da girdinin 16*5*5 değerine sahip olacağı anlamına gelir.
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
# Son doğrusal katman, tahmin edilecek 10 sınıf olduğundan 10 çıktı üretmelidir.
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# x -> [batch_size, 3, 32, 32]
output = self.conv1(x) # [batch_size, 6, 28, 28]
output = self.pool1and2(output) # [batch_size, 6, 14, 14]
output = F.relu(output) # [batch_size, 6, 14, 14]
output = self.conv2(output) # [batch_size, 16, 10, 10]
output = self.pool1and2(output) # [batch_size, 16, 5, 5]
output = F.relu(output) # [batch_size, 16, 5, 5]
# Doğrusal katmana beslemek için çıktıyı girdi başına tek bir satır haline getirmeliyiz.
output = output.reshape(-1, 16 * 5 * 5) # [batch_size, 16*5*5]
output = F.relu(self.fc1(output))
output = F.relu(self.fc2(output))
# Son katmandan sonra bir aktivasyon kullanmayacağız çünkü
# kayıp işlevi sigmoid aktivasyonunu otomatik olarak uygulayacaktır
output = self.fc3(output)
return output
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = CNN().to(device)
print(model)
Çıktı
CNN(
(conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
(pool1and2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(fc1): Linear(in_features=400, out_features=120, bias=True)
(fc2): Linear(in_features=120, out_features=84, bias=True)
(fc3): Linear(in_features=84, out_features=10, bias=True)
)
Optimize edici ve kayıp
Modeli eğitmek için kullanılacak optimize edici ve kayıp fonksiyonlarını tanımlıyoruz.
learning_rate = 0.001
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
Eğitim döngüsü
Modeli eğitmek, batch_size=4
değeri ile görüntü yığınları oluşturacak olan eğitim seti yükleyiciyi kullanacaktır.
Her eğitim dönemi için, modelin eğitiminde tüm eğitim grupları kullanılacaktır. Her parti için, sistem boyunca ileriye
doğru bir yayılım, ardından onu optimize etmek için geriye doğru bir yayılım gerçekleştirilecektir. Verileri işlemeden
önce cihaza taşıyoruz.
num_epochs = 5
# Train_loader'daki parti sayısı
n_total_steps = len(train_loader)
for epoch in range(num_epochs):
# Her toplu iş, bir görüntü tensörü ve bu görüntünün etiketlerini içeren
# bir tensörden oluşur.
for i, (images, labels) in enumerate(train_loader):
images = images.to(device)
labels = labels.to(device)
# Giriş tensörü şu şekildedir: [batch_size, 3, 32, 32]
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (i+1) % 2000 == 0:
print (f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{n_total_steps}], Loss: {loss.item():.4f}')
Çıktı
Epoch [1/5], Step [2000/12500], Loss: 2.3247
Epoch [1/5], Step [4000/12500], Loss: 2.3011
Epoch [1/5], Step [6000/12500], Loss: 2.3187
Epoch [1/5], Step [8000/12500], Loss: 2.2360
Epoch [1/5], Step [10000/12500], Loss: 2.3910
Epoch [1/5], Step [12000/12500], Loss: 2.0308
Epoch [2/5], Step [2000/12500], Loss: 1.4436
Epoch [2/5], Step [4000/12500], Loss: 2.0996
Epoch [2/5], Step [6000/12500], Loss: 2.1182
Epoch [2/5], Step [8000/12500], Loss: 1.8409
Epoch [2/5], Step [10000/12500], Loss: 2.2138
Epoch [2/5], Step [12000/12500], Loss: 0.8254
Epoch [3/5], Step [2000/12500], Loss: 1.8962
Epoch [3/5], Step [4000/12500], Loss: 1.1463
Epoch [3/5], Step [6000/12500], Loss: 1.5816
Epoch [3/5], Step [8000/12500], Loss: 1.1057
Epoch [3/5], Step [10000/12500], Loss: 1.2237
Epoch [3/5], Step [12000/12500], Loss: 2.3894
Epoch [4/5], Step [2000/12500], Loss: 1.1736
Epoch [4/5], Step [4000/12500], Loss: 1.8377
Epoch [4/5], Step [6000/12500], Loss: 1.8938
Epoch [4/5], Step [8000/12500], Loss: 1.6018
Epoch [4/5], Step [10000/12500], Loss: 1.0369
Epoch [4/5], Step [12000/12500], Loss: 1.3495
Epoch [5/5], Step [2000/12500], Loss: 1.0014
Epoch [5/5], Step [4000/12500], Loss: 1.1602
Epoch [5/5], Step [6000/12500], Loss: 1.0183
Epoch [5/5], Step [8000/12500], Loss: 1.2231
Epoch [5/5], Step [10000/12500], Loss: 1.8884
Epoch [5/5], Step [12000/12500], Loss: 0.8248
Değerlendirme
Son olarak, test verilerini kullanarak eğitilen modeli değerlendireceğiz.
Test verisi yığınları oluşturacak test yükleyicisini kullanıyoruz. On sınıfın her birinin doğruluğunu ve ayrıca
sistemin genel doğruluğunu hesaplıyoruz. Değerlendirmede kullanılan hesaplamanın, hesaplama ve bellek açısından daha
verimli olması için, yani hesaplama grafiği oluşturmaması için değerlendirme kodunu torch.no_grad()
işlemiyle çevreliyoruz.
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
with torch.no_grad():
n_correct = 0
n_samples = 0
n_class_correct = [0 for i in range(10)]
n_class_samples = [0 for i in range(10)]
for images, labels in test_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs, 1)
n_samples += labels.size(0)
n_correct += (predicted == labels).sum().item()
for i in range(batch_size):
label = labels[i]
pred = predicted[i]
if (label == pred):
n_class_correct[label] += 1
n_class_samples[label] += 1
acc = 100.0 * n_correct / n_samples
print(f'Ağın doğruluğu: {acc} %')
for i in range(10):
acc = 100.0 * n_class_correct[i] / n_class_samples[i]
print(f'{classes[i]} doğruluğu: {acc} %')
Çıktı
Ağın doğruluğu: 49.98 %
plane doğruluğu: 32.0 %
car doğruluğu: 71.6 %
bird doğruluğu: 30.8 %
cat doğruluğu: 27.1 %
deer doğruluğu: 37.4 %
dog doğruluğu: 45.0 %
frog doğruluğu: 71.9 %
horse doğruluğu: 55.9 %
ship doğruluğu: 65.4 %
struck doğruluğu: 62.7 %