منبع اصلی نوشتار زیر در این لینک قرار دارد

دسته‌بندی دیتاست CIFAR-10 با استفاده از شبکه‌های کانولوشن و PyTorch

در این پست می‌خواهیم دیتاست CIFAR10 را با استفاده از شبکه‌های کانولوشن ( CNN – Convolutional Neural Network) و با استفاده از فریم‌ورک یادگیری عمیق پای تورج (PyTorch) دسته‌بندی (Classification) کنیم.

دیتاست سیفار-۱۰ یا همان CIFAR-10 یک دیتاست است که از ۱۰ کلاس زیر تشکیل شده است:

  • هواپیما
  • اتومبیل
  • پرنده
  • گربه
  • گوزن
  • سگ
  • قورباغه
  • اسب
  • کشتی
  • کامیون

 

هر کلاس شامل ۵۰۰۰ داده آموزش و ۱۰۰۰ داده تست هست و کل دیتاست شامل ۶۰۰۰۰ تصویر است. حجم دیتاست چیزی در حدود ۱۶۰ مگابایت است. هر تصویر در این دیتاست ۳۲*۳۲ پیکسل است. در تصویر زیر نمونه‌ای از تصویرهای موجود در این دیتاست را مشاهده می‌کنید.

 

از آنجایی که دیتاست تصویر است از شبکه کانولوشنی استفاده می‌کنیم زیرا اینگونه شبکه‌ها نتایج بسیار خوبی روی تصویرها دارند و اطلاعات مکانی در تصویر را به خوبی یاد می‌گیرند.

از آنجایی که هدف به دست آوردن یک دقت معقول و کار با شبکه‌های عصبی کانولوشن و فریم‌ورک پایتورچ است، از یک شبکه سبک و ساده کانولوشن استفاده می‌کنیم که هم دقت نسبتا خوبی به دست بیاوریم و هم آن را به آسانی آموزش دهیم.

شبکه کانولوشنی که استفاده می‌کنیم به شکل زیر است:

  • یک لایه کانولوشنی که یک ورودی با ۳ کانال را می‌گیرد و یک خروجی با ۹ کانال می‌دهد و اندازه کرنل آن ۵*۵ است و از تابع غیر خطی ‌ReLu استفاده می‌کند.
  • یک لایه ‌Max Pooling که اندازه کرنل آن ۲*۲ است.
  • یک لایه کانولوشنی که یک ورودی با ۹ کانال را می‌گیرد و یک خروجی با ۱۸ کانال می‌دهد و اندازه کرنل آن ۳*۳ است و از تابع غیر خطی ‌ReLu استفاده می‌کند.
  • یک لایه ‌Max Pooling که اندازه کرنل آن ۲*۲ است.
  • یک لایه Fully Connected که ۱۰۰ نورون دارد و از تابع غیر خطی ReLu استفاده می‌کند.
  • یک لایه Fully Connected که ۴۰ نورون دارد و از تابع غیر خطی ReLu استفاده می‌کند.
  • یک لایه Fully Connected که ۱۰ نورون دارد که با تعداد کلاس‌ها برابر است.

 

ابتدا پکیج‌های مورد استفاده را ایمپورت می‌کنیم:

import torch
import torchvision
import numpy
import os
import torch.utils.data as datautils
import matplotlib.pyplot as plt
import torch.nn as to_nn
import torch.nn.functional as to_f
import torch.optim as optim

torch و torchvision دو پکیج برای کار کردن با شبکه‌های عصبی و انجام کارهای مربوط به بینایی کامپیوتر (ویژن) هستند.

سپس اقدام به خواندن دیتاست می‌کنیم:

# transforms that should be done of each image
# 1- convert from numpy to tensor
# 2- normalize pixel values to real value range [-1,1], for better and easier training
image_transforms = torchvision.transforms.Compose([
        torchvision.transforms.ToTensor(),
        torchvision.transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))
])


# load training and test set
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
cifar10_training_set = torchvision.datasets.CIFAR10(root='./dataset_CIFAR10', train=True, transform=image_transforms,
                                                    download=True)
cifar10_test_set = torchvision.datasets.CIFAR10(root='./dataset_CIFAR10', train=False, transform=image_transforms,
                                                    download=True)
trainingset_loader = datautils.DataLoader(dataset=cifar10_training_set, batch_size=4, shuffle=True, num_workers=2)
testset_loader = datautils.DataLoader(dataset=cifar10_test_set, batch_size=4, shuffle=True, num_workers=2)

در کد بالا transforms.ToTensor تابعی هست که یک آرایه عادی یا از جنس numpy را به یک Tensorتبدیل می‌کند. پایتورچ از تنسور استفاده می‌کند و از آرایه‌های عادی و numpy استفاده نمی‌کند. تفاوتشان این است که تنسور قابلیت استفاده از جی.پی.یو را دارند. تابع transforms.Normaalize با توجه ورودی‌هایی که به آن داده شده است، مقدار پیکسل‌ها را به بازه‌ی -۱ تا ۱ منتقل می‌کند و این نرمال سازی کار آموزش شبکه را آسان‌تر می‌کند. تابع compose دو تابع بالا را در کنارهم قرار می‌دهد. ‌پایتورچ تابع‌ها و امکاناتی برای خواندن بعضی از دیتاست‌های معروف دارند و یکی از این دیتاست‌ها دیتاست CIFAR10 است که در بالا از آن استفاده کرده‌ایم. در صورتی که دیتاست در مسیر مشخص شده موجود نباشد آن را اتوماتیک دانلود می‌کند. DataLoader اجازه دسترسی به دیتاهای دیتاست را به صورت mini-batch می‌دهد و کار شافل کردن را نیز انجام می‌دهد.

def matplotlib_imshow(img_tensor, title=None):
    """
    Get and image in tensor form and display it.
    :param img_tensor:
    :return:
    """
    device = torch.device("cpu")
    img_tensor_cpu = img_tensor.to(device)

    std = 0.5
    mean = 0.5

    img_tensor = std * img_tensor_cpu + mean
    # convert tensor to numpy
    img = img_tensor.numpy()
    # in pytorch image is (3, H, W)
    # normal form is (H, W, 3)
    img = img.transpose(1, 2, 0)
    plt.figure()
    plt.imshow(img)
    if title is not None:
        plt.title(title)

تابع بالا کار نمایش یک تصویر که به صورت Tensor است را انجام می‌دهد. در ابتدا مشخص شده است که تنسور قرار است با سی.پی.یو پردازش شود. سپس مقدارهای -۱ تا ۱ به بازه‌ی ۰ تا ۱ منتقل می‌شوند و بعد تنسور به یک آرایه از نوع numpy تبدیل می‌شود و در ادامه تصویر از شکل ۳HW به صورت HW3 در آورده می‌شود. دلیل این امر تفاوت تصویر در پایتورچ با سایر کتابخانه‌ها و استانداردها است و در آخر تصویر رسم می‌شود.

در ادامه چند مورد از‌ تصویر‌های موجود در مجموعه داده‌ی سیفار را رسم می‌کنیم:

# show some samples of traingset
data_iter = iter(trainingset_loader)
for i in range(3):
    images, labels = data_iter.next()
    matplotlib_imshow(img_tensor=torchvision.utils.make_grid(images), title=['gt: '+classes[i.item()] for i in labels])

خروجی کد بالا تصاویر زیر است:

gt در بالا معنی Ground Truth را می‌دهد.

 

# define a convolutional neural network
class ConvNet(to_nn.Module):

    # constructor of model
    def __init__(self):
        # constructor of parent class
        super(ConvNet, self).__init__()
        # learnable parameter of model (convolution and fully connected layers)
        self.conv1 = to_nn.Conv2d(in_channels=3, out_channels=9, kernel_size=(5, 5))
        self.conv2 = to_nn.Conv2d(in_channels=9, out_channels=18, kernel_size=(3, 3))
        self.fc1 = to_nn.Linear(in_features=18 * 6 * 6, out_features=100)
        self.fc2 = to_nn.Linear(in_features=100, out_features=40)
        self.fc3 = to_nn.Linear(in_features=40, out_features=10)

    # feedforward
    def forward(self, input):
        # convolutional layer 1
        out = self.conv1(input)
        # pooling kernel size(2,2) and relu non linearity
        out = to_f.max_pool2d(to_f.relu(out), (2, 2))
        # convolutional layer 2
        out = self.conv2(out)
        # pooling kernel size(2,2) and relu non linearity
        out = to_f.max_pool2d(to_f.relu(out), (2, 2))
        # flatten
        out = out.view(-1, 18 * 6 * 6)
        # fully connected 1 with relu non linearity
        out = to_f.relu(self.fc1(out))
        # fully connected 2 with relu non linearity
        out = to_f.relu(self.fc2(out))
        # fully connected 3
        out = self.fc3(out)
        return out

در کد بالا یک شبکه عصبی کانولوشنی که در بالا آن را شرح دادیم را ایجاد می‌کنیم. کلاسی که می‌نویسیم کلاس پدرش، حتما باید to_nn.Module باشد. در constructor ابتدا کانستراکتور کلاس پدر را فرا می‌خوانیم سپس لایه‌هایی از شبکه که پارامترهایی قابل یادگیری دارند را تعریف می‌کنیم.

در تابع forward پردازش‌هایی که بر روی یک ورودی که به شبکه داده می‌شود را مشخص می‌کنیم. معمولا لایه‌هایی که پارامترهای قابل یادگیری‌ ندارند را در این قسمت می‌نویسیم و از نوشتن آن‌ها در سازنده‌ی کلاس خودداری می‌کنیم.

# if gpu available use gpu otherwise use cpu
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('Device: {}'.format(device))

در این قسمت از کد مشخص می‌کنیم که می‌خواهیم از CPU استفاده کنیم یا می‌خواهیم از GPU استفاده کنیم. در صورت در دسترس بودن جی.پی.یو کد بالا مشخص می‌کند که می‌خواهیم از جی.پی.یو استفاده کنیم.

# create and instance of neural network
net = ConvNet()
net.to(device)

در کد بالا از کلاس‌ شبکه‌ای که ایجاد کرده‌ایم یک شی می‌سازیم و مشخص می‌کنیم از جی.پی.یو و یا سی.پی.یو استفاده کند.

# criterion(loss) and optimization algorithm
# cross entropy loss
criterion = to_nn.CrossEntropyLoss()
# adam optimization algorithm, learning rate 0.001 and momentum 0.9
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

شبکه‌ی عصبی مان نیاز به یک Loss Function دارد، از تابع هزینه Cross Entropy استفاده می‌کنیم که معمولا برای دسته‌بندی نتایج خوبی می‌دهد. همین طور به الگوریتم بهینه‌سازی نیازی داریم و برای این کار الگوریتم Stochastic Gradient Descent را انتخاب کرده‌ایم.

# train the convolutional neural network
# train for number of epochs
for epoch in range(10):

    # loss of epoch
    running_loss = 0.0

    # iterate over all batches
    for i, data in enumerate(trainingset_loader, 0):

        images, labels = data

        # makes all gradients zero
        optimizer.zero_grad()

        # feedforward
        images = images.to(device)
        output = net(images)

        # loss
        labels = labels.to(device)
        loss = criterion(output, labels)

        # backpropagation
        loss.backward()

        # update parameters
        optimizer.step()

        running_loss += loss.item()

        if i % 1000 == 999:
            print('epoch {}, mini-batch {}, loss {}'.format(epoch, i + 1, running_loss/1000.0))
            running_loss = 0.0

print('Training Phase is done.')

در کد بالا آموزش شبکه انجام می‌پذیرد. برای ۱۰ ایپاک شبکه را آموزش می‌دهیم. zero_grad گرادیان‌های شبکه را صفر می‌کند اگر این کار را انجام ندهیم گرادیان‌ها جمع می‌شوند و اشتباه رخ می‌دهد. تابع backward هم کار backpropagation را انجام می‌دهد و گرادیان‌ها را محاسبه می‌کند. step نیز با توجه با گرادیان‌های محاسبه شده و ضریب یادگیری و … وزن‌ها را به‌روزرسانی می‌کند. در زیر بخشی از خروجی در حین آموزش را مشاهده می‌کنید:

 

 

# show qualitative result of training
data_iter = iter(testset_loader)
for i in range(3):
    images, labels_ground_truth = data_iter.next()
    images = images.to(device)
    labels_ground_truth = labels_ground_truth.to(device)
    outputs = net(images)
    _, predicted = torch.max(outputs, dim=1)
    matplotlib_imshow(img_tensor=torchvision.utils.make_grid(images), title=['predicted: '+classes[i.item()] for i in predicted])

# accuracy on test set
correct = 0
total = 0
with torch.no_grad():
    for data in testset_loader:
        images, ground_truth_labels = data
        images = images.to(device)
        ground_truth_labels = ground_truth_labels.to(device)
        outputs = net(images)
        _, predicted = torch.max(outputs, dim=1)
        total += ground_truth_labels.size(0)
        correct += (ground_truth_labels==predicted).sum().item()
print('Accuracy on test set is: {}%'.format(correct/total * 100))

# accuracy of each class in test set
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
    for data in testset_loader:
        images, labels = data
        images = images.to(device)
        labels = labels.to(device)
        outputs = net(images)
        _, predicted = torch.max(outputs, 1)
        c = (predicted == labels).squeeze()
        for i in range(4):
            label = labels[i]
            class_correct[label] += c[i].item()
            class_total[label] += 1

for i in range(10):
    print('Accuracy of {}: {}%'.format(classes[i], 100 * class_correct[i] / class_total[i]))

plt.show()

در کد بالا بعد از آموزش شبکه، پیش‌بینی شبکه برای چند ورودی که از مجموعه تست انتخاب شده‌اند نمایش داده می‌شود و سپس دقت بر روی مجموعه تست نمایش داده می‌شود.

در تصویر بالا پیشبینی شبکه آموزش دیده شده را برای چند تصویر مشاهده می‌کنید و در زیر دقت شبکه بر روی مجموعه تست را می‌بینید:

 

این شبکه را برای کلسیفیکیشن دیتاست CIFAR10 را خیلی سریع ایجاد کردیم و نتایج خوبی گرفتیم با ایجاد تغییر در ساختار و پارامترهای شبکه می‌توان دقت را بهبود بخشید.

 

در زیر کد کلی برنامه را مشاهده می‌کنید:

import torch
import torchvision
import numpy
import os
import torch.utils.data as datautils
import matplotlib.pyplot as plt
import torch.nn as to_nn
import torch.nn.functional as to_f
import torch.optim as optim


def matplotlib_imshow(img_tensor, title=None):
    """
    Get and image in tensor form and display it.
    :param img_tensor:
    :return:
    """
    device = torch.device("cpu")
    img_tensor_cpu = img_tensor.to(device)

    std = 0.5
    mean = 0.5

    img_tensor = std * img_tensor_cpu + mean
    # convert tensor to numpy
    img = img_tensor.numpy()
    # in pytorch image is (3, H, W)
    # normal form is (H, W, 3)
    img = img.transpose(1, 2, 0)
    plt.figure()
    plt.imshow(img)
    if title is not None:
        plt.title(title)


###########################################################################
#  Read Dataset and show some samples
###########################################################################

# transforms that should be done of each image
# 1- convert from numpy to tensor
# 2- normalize pixel values to real value range [-1,1], for better and easier training
image_transforms = torchvision.transforms.Compose([
        torchvision.transforms.ToTensor(),
        torchvision.transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))
])


# load training and test set
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
cifar10_training_set = torchvision.datasets.CIFAR10(root='./dataset_CIFAR10', train=True, transform=image_transforms,
                                                    download=True)
cifar10_test_set = torchvision.datasets.CIFAR10(root='./dataset_CIFAR10', train=False, transform=image_transforms,
                                                    download=True)
trainingset_loader = datautils.DataLoader(dataset=cifar10_training_set, batch_size=4, shuffle=True, num_workers=2)
testset_loader = datautils.DataLoader(dataset=cifar10_test_set, batch_size=4, shuffle=True, num_workers=2)

# show some samples of traingset
data_iter = iter(trainingset_loader)
for i in range(3):
    images, labels = data_iter.next()
    matplotlib_imshow(img_tensor=torchvision.utils.make_grid(images), title=['gt: '+classes[i.item()] for i in labels])


###########################################################################
#  Create and train a convolutional neural network for cifar-10 dataset
###########################################################################
# define a convolutional neural network
class ConvNet(to_nn.Module):

    # constructor of model
    def __init__(self):
        # constructor of parent class
        super(ConvNet, self).__init__()
        # learnable parameter of model (convolution and fully connected layers)
        self.conv1 = to_nn.Conv2d(in_channels=3, out_channels=9, kernel_size=(5, 5))
        self.conv2 = to_nn.Conv2d(in_channels=9, out_channels=18, kernel_size=(3, 3))
        self.fc1 = to_nn.Linear(in_features=18 * 6 * 6, out_features=100)
        self.fc2 = to_nn.Linear(in_features=100, out_features=40)
        self.fc3 = to_nn.Linear(in_features=40, out_features=10)

    # feedforward
    def forward(self, input):
        # convolutional layer 1
        out = self.conv1(input)
        # pooling kernel size(2,2) and relu non linearity
        out = to_f.max_pool2d(to_f.relu(out), (2, 2))
        # convolutional layer 2
        out = self.conv2(out)
        # pooling kernel size(2,2) and relu non linearity
        out = to_f.max_pool2d(to_f.relu(out), (2, 2))
        # flatten
        out = out.view(-1, 18 * 6 * 6)
        # fully connected 1 with relu non linearity
        out = to_f.relu(self.fc1(out))
        # fully connected 2 with relu non linearity
        out = to_f.relu(self.fc2(out))
        # fully connected 3
        out = self.fc3(out)
        return out


# if gpu available use gpu otherwise use cpu
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('Device: {}'.format(device))

# create and instance of neural network
net = ConvNet()
net.to(device)

# criterion(loss) and optimization algorithm
# cross entropy loss
criterion = to_nn.CrossEntropyLoss()
# adam optimization algorithm, learning rate 0.001 and momentum 0.9
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

# train the convolutional neural network
# train for number of epochs
for epoch in range(10):

    # loss of epoch
    running_loss = 0.0

    # iterate over all batches
    for i, data in enumerate(trainingset_loader, 0):

        images, labels = data

        # makes all gradients zero
        optimizer.zero_grad()

        # feedforward
        images = images.to(device)
        output = net(images)

        # loss
        labels = labels.to(device)
        loss = criterion(output, labels)

        # backpropagation
        loss.backward()

        # update parameters
        optimizer.step()

        running_loss += loss.item()

        if i % 1000 == 999:
            print('epoch {}, mini-batch {}, loss {}'.format(epoch, i + 1, running_loss/1000.0))
            running_loss = 0.0

print('Training Phase is done.')

# show qualitative result of training
data_iter = iter(testset_loader)
for i in range(3):
    images, labels_ground_truth = data_iter.next()
    images = images.to(device)
    labels_ground_truth = labels_ground_truth.to(device)
    outputs = net(images)
    _, predicted = torch.max(outputs, dim=1)
    matplotlib_imshow(img_tensor=torchvision.utils.make_grid(images), title=['predicted: '+classes[i.item()] for i in predicted])

# accuracy on test set
correct = 0
total = 0
with torch.no_grad():
    for data in testset_loader:
        images, ground_truth_labels = data
        images = images.to(device)
        ground_truth_labels = ground_truth_labels.to(device)
        outputs = net(images)
        _, predicted = torch.max(outputs, dim=1)
        total += ground_truth_labels.size(0)
        correct += (ground_truth_labels==predicted).sum().item()
print('Accuracy on test set is: {}%'.format(correct/total * 100))

# accuracy of each class in test set
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
    for data in testset_loader:
        images, labels = data
        images = images.to(device)
        labels = labels.to(device)
        outputs = net(images)
        _, predicted = torch.max(outputs, 1)
        c = (predicted == labels).squeeze()
        for i in range(4):
            label = labels[i]
            class_correct[label] += c[i].item()
            class_total[label] += 1

for i in range(10):
    print('Accuracy of {}: {}%'.format(classes[i], 100 * class_correct[i] / class_total[i]))

plt.show()

 

 

 

 

 

 



برچسب ها :