كتابة AlexNet من الصفر في PyTorch

مقدمة

هذه المقالة توالد بالتوالي لسلسلة حول بناء أحدث الشبكات العصبية الكونولوزية المشهورة من الصفر في PyTorch. يمكنك رؤية المقالة السابقة هنا، حيث قمنا ببناء LeNet5. في هذه المقالة، سنبني على الأقل AlexNet، وهي أحد الأبتكارات الرئيسية الأكثر توجيهًا في الرؤية الحاسوبية.

سنبدأ بالتحقيق وفهم الهياكل الهندسية لAlexNet. من ثم سنغوص مباشرة في الشيء بتحميل قاعدة البيانات الخاصة بنا، CIFAR-10، قبل تنظيم البيانات ببعض المراحل المنزلية. بعد ذلك، سنبني على الأقل AlexNet من الصفر باستخدام PyTorch ون entrainer في معلوماتنا المسبق التي تم تنظيمها. أخيرًا، ستتم اختبار النموذج المتمركز المدرب في البيانات الجديدة (للاختبار) لأغراض التقييم.

المقدمات

سيكون معرفة الشبكات العصبية مفيدة لفهم هذا المقال. هذا يشمل الاشتراك بالأطباق المختلفة للشبكات العصبية (الطبقة المدخلة، الطبقات الخفيفة، الطبقة الخارجية)، والمحركات النشطة، والخوارزميات التحسينية (أنواع من الهبوط التجزئي)، المصادفات، وهلم جرا. علاوة على ذلك، سيتوجب معرفة جديدة للغة البرمجية البيروتية ومكتبة PyTorch لفهم القطع البرمجية المعروضة في هذه المقالة.

فهم الCNNs مهم. هذا يشمل معرفة الطبقات التصاعدية، والطبقات التخفيفية، ودورهم في استخراج الميزات من البيانات المدخلة. وفهم مفاهيم مثل المسافة التوالية، التعبئة، وتأثير حجم الكرنل/الفلتر مفائيل من المفاهيم المفيدة.

AlexNet

AlexNet هو شبكة عصبية تعمل بعمق تشمل طبقات تصاعدية وتخفيفية، وتم تطويره أولًا من قبل أليكس كريشفسكي وزملاؤه في عام 2012. وتم تصميمه لتصنيف الصور لمسابقة ImageNet LSVRC-2010 حيث أدى إلى أفضل نتائج علمية. يمكنك أن تقرأ بشكل دقيق عن النموذج في الوثيقة البحثية الأصلية هنا.

دعونا نذهب إلى أهم الأمور التي ناخذها من وثيقة AlexNet. أولًا، عملت AlexNet مع صور بأعداد ثلاثة قنوات (224x224x3)، واستخدمت تخفيف أقصى مع تنشيطات ReLU عند التنقل الأدنى. وتلك الكرانلات التي استخدمت للتصاعد كانت 11×11 أو 5×5 أو 3×3 بينما كانت أبعاد كرانلات التخفيف 3×3. وتصنيفت الصور إلى 1000 فئة. وأيضًا أستخدمت عدة GPUs.

مجموعة البيانات

دعونا نبدأ بتحميل ومعالجة مباشرة البيانات. ولأغراضنا، سنستخدم مجموعة CIFAR-10. وتتكون هذه المجموعة من 60000 صورة تأكيدية 32×32 في 10 أنواع مختلفة، 6000 صورة لكل صنف. وهناك 50000 صورة تدريبية و10000 صورة اختبارية.

هذه هي الأنواع في المجموعة البياناتية، وأيضًا 10 صورة مختلفة عشوائيًا من كل واحد منها:

مصدر: source

هذه الأنواع منعزلة تمامًا عن بعضها البعض. لا يوجد تقاطع بين السيارات والشاحنات. “سيارة” تشمل السيارات العاملة، السيارات الكبيرة وأشياء من هذا القبيل. “شاحنة” تشمل من الشاحنات الكبيرة فقط. ولا تشمل الشاحنات المحمولة.

تحميل المكتبات

دعونا نبدأ بتحميل المكتبات المطلوبة وتعريف متغير device، حتى يعلم الملاحظة إستخدام الGPU لتدريب النموذج إذا كان متاحًا.

import numpy as np
import torch
import torch.nn as nn
from torchvision import datasets
from torchvision import transforms
from torch.utils.data.sampler import SubsetRandomSampler

# إعدادات الجهاز
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

تحميل المجموعة البياناتية

باستخدام torchvision (مكتبة مساعدة لمهام الرؤية الكمبيوترية) سنحمل قاعدة البيانات خاصتنا. هذه الطريقة تحتوي على بعض الوظائف المساعدة التي تجعل الاعداد الأولي سهلًا ومباشرًا. دعونا نعرف الوظائف get_train_valid_loader و get_test_loader، ومن ثم ندعمهم لتحميل ومعالجة بيانات CIFAR-10 الخاصة بنا:

def get_train_valid_loader(data_dir,
                           batch_size,
                           augment,
                           random_seed,
                           valid_size=0.1,
                           shuffle=True):
    normalize = transforms.Normalize(
        mean=[0.4914, 0.4822, 0.4465],
        std=[0.2023, 0.1994, 0.2010],
    )

    # تعريف التحويلات
    valid_transform = transforms.Compose([
            transforms.Resize((227,227)),
            transforms.ToTensor(),
            normalize,
    ])
    if augment:
        train_transform = transforms.Compose([
            transforms.RandomCrop(32, padding=4),
            transforms.RandomHorizontalFlip(),
            transforms.ToTensor(),
            normalize,
        ])
    else:
        train_transform = transforms.Compose([
            transforms.Resize((227,227)),
            transforms.ToTensor(),
            normalize,
        ])

    # تحميل قاعدة البيانات
    train_dataset = datasets.CIFAR10(
        root=data_dir, train=True,
        download=True, transform=train_transform,
    )

    valid_dataset = datasets.CIFAR10(
        root=data_dir, train=True,
        download=True, transform=valid_transform,
    )

    num_train = len(train_dataset)
    indices = list(range(num_train))
    split = int(np.floor(valid_size * num_train))

    if shuffle:
        np.random.seed(random_seed)
        np.random.shuffle(indices)

    train_idx, valid_idx = indices[split:], indices[:split]
    train_sampler = SubsetRandomSampler(train_idx)
    valid_sampler = SubsetRandomSampler(valid_idx)

    train_loader = torch.utils.data.DataLoader(
        train_dataset, batch_size=batch_size, sampler=train_sampler)

    valid_loader = torch.utils.data.DataLoader(
        valid_dataset, batch_size=batch_size, sampler=valid_sampler)

    return (train_loader, valid_loader)


def get_test_loader(data_dir,
                    batch_size,
                    shuffle=True):
    normalize = transforms.Normalize(
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225],
    )

    # تعريف التحويل
    transform = transforms.Compose([
        transforms.Resize((227,227)),
        transforms.ToTensor(),
        normalize,
    ])

    dataset = datasets.CIFAR10(
        root=data_dir, train=False,
        download=True, transform=transform,
    )

    data_loader = torch.utils.data.DataLoader(
        dataset, batch_size=batch_size, shuffle=shuffle
    )

    return data_loader

# قاعدة البيانات CIFAR10 
train_loader, valid_loader = get_train_valid_loader(data_dir = './data',                                      batch_size = 64,
                       augment = False,                                          random_seed = 1)

test_loader = get_test_loader(data_dir = './data',
                              batch_size = 64)

دعونا نفك الكود:

  • نعرف دون عمليتين get_train_valid_loader و get_test_loader لتحميل مجموعات التدريب/التحقق والاختبار على التوالي
  • نبدأ بتعريف المتغير normalize مع المتوسط والانحراف المعياري لكل من القنوات (الأحمر، الأخضر، والأزرق) في قاعدة البيانات. يمكن حسابها يدويًا، ولكنها متوفرة أيضًا على الإنترنت لأن CIFAR-10 مشهور جدًا
  • لقاعدة البيانات التدريبية الخاصة بنا، نضيف خيارًا لتزييد مجموعة البيانات بمزيد من القوة للتدريب وزيادة عدد الصور أيضًا. ملاحظة: الزيادة التكميلية تُطبق فقط على الجزء التدريبي وليس على الجزءين التحقق والاختبار لأنهم يستخدمون فقط لأغراض التقييم
  • نقسم مجموعة التدريب إلى مجموعتي التدريب والتحقق (نسبة 90:10)، ونقوم بتخصيصها عشوائيًا من الكامل
  • نحدد حجم الدفعة ونقوم بتبديل مجموعة البيانات أثناء التحميل، بحيث يكون لكل دفعة تنوع في أنواع البطاقات التي يمتلكها. هذا سيزيد من فعالية نموذجنا النهائي.
  • وأخيرًا، نستخدم محملات البيانات. قد لا يؤثر هذا على الأداء في حالة المجموعة الصغيرة مثل CIFAR-10، لكنه يمكن أن يحدث تأخرًا في الأداء في الحالة الكبيرة للمجموعات ويعتبر من الممارسات الجيدة عامًا. يسمح لمحملات البيانات لنا بتصفح البيانات بالمساويات، وتتم تحميل البيانات أثناء التصفح وليس كلها في البداية إلى رامك

AlexNet من الصفر

دعونا نبدأ بالبرمجيات أولًا:

class AlexNet(nn.Module):
    def __init__(self, num_classes=10):
        super(AlexNet, self).__init__()
        self.layer1 = nn.Sequential(
            nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=0),
            nn.BatchNorm2d(96),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size = 3, stride = 2))
        self.layer2 = nn.Sequential(
            nn.Conv2d(96, 256, kernel_size=5, stride=1, padding=2),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size = 3, stride = 2))
        self.layer3 = nn.Sequential(
            nn.Conv2d(256, 384, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(384),
            nn.ReLU())
        self.layer4 = nn.Sequential(
            nn.Conv2d(384, 384, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(384),
            nn.ReLU())
        self.layer5 = nn.Sequential(
            nn.Conv2d(384, 256, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size = 3, stride = 2))
        self.fc = nn.Sequential(
            nn.Dropout(0.5),
            nn.Linear(9216, 4096),
            nn.ReLU())
        self.fc1 = nn.Sequential(
            nn.Dropout(0.5),
            nn.Linear(4096, 4096),
            nn.ReLU())
        self.fc2= nn.Sequential(
            nn.Linear(4096, num_classes))

    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.layer5(out)
        out = out.reshape(out.size(0), -1)
        out = self.fc(out)
        out = self.fc1(out)
        out = self.fc2(out)
        return out

تعريف النموذج AlexNet

دعونا نغوص في كيفية تشغيل البرمجيات السابقة:

  • أول خطوة في تعريف أي شبكة المخادع (سواء كانت شبكة مرئيات أو لا) في PyTorch هو تعريف تصنيف يحمل متغير nn.Module لأنه يحتوي على العديد من الأوامر التي سنحتاج إلى استخدامها
  • يوجد خطوتان رئيسيتان بعد ذلك. الأولى هي تكوين الطبقات التي سنستخدمها في شبكة مرئيات خاصتنا داخل __init__، والأخرى هي تحديد التسلسل فيها تلك الطبقات ستعالج الصورة. يتم تعريف هذا الخطوط داخل الوظيفة forward.
  • للهيكل نفسه، فأولاً نحدد الطبقات التصاعدية باستخدام وظيفة nn.Conv2D مع حجم الناحية المناسب والأنقاذات المدخلة / الخارجية. نقوم أيضًا بتطبيق الكسر الأقصى باستخدام وظيفة nn.MaxPool2D. شيء جيد عن بيتورش الأن هو أنه يمكننا تركيب الطبقة التصاعدية، والمؤلفة الفعالة، والكسر الأقصى في طبقة واحدة واحدة (ستتم تطبيقهم بشكل منعزل، ولكن هذا يساعد في التنظيم) باستخدام وظيفة nn.Sequential
  • ثم نحدد الطبقات المتصلة بالصفات باستخدام الخط الأولي (nn.Linear) والتسريع التقليدي (nn.Dropout) مع مؤلفة ReLu (nn.ReLU) وتركيب هذه مع الnn.Sequential وظيفة
  • في النهاية تخرج طبقتنا الأخيرة بـ 10 خلايا تلك تخرجاتنا النهائية ل10 فئات من الأشياء

إعداد المادات الرقمية

قبل التدريب ، يتوجب علينا تحديد بعض المادات الرقمية الخاصة مثل وظيفة الخسارة والمحاكد التي ستستخدم وجميع الحجم الصغير للباتches، ومعدل التعلم، وعدد المرات التدريبية.

num_classes = 10
num_epochs = 20
batch_size = 64
learning_rate = 0.005

model = AlexNet(num_classes).to(device)

# الخسارة والمحاكد
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay = 0.005, momentum = 0.9)  

# تدريب النموذج
total_step = len(train_loader)

نبدأ بتعريف المادات الرئيسية (المرات التعلمية، حجم المجموعات، ومعدل التعلم) وتشغيل نموذجنا باستخدام عدد الفئات كمادة دعم، وهو 10 مع تحويل النموذج إلى الجهاز المناسب (الCPU أو الGPU). ثم نحدد معاملتنا بالخسارة التباينية ومحاكينا بـ Adam. هناك الكثير من الخيارات لهذه الأشياء، ولكنها تميل إلى إنجاز جيد مع النموذج والبيانات المعطية. وأخيرًا، نحدد total_step للتعرف بشكل أفضل على الخطوات أثناء التعلم

التعلم

نحن على وشك البدء بتعلم نموذجنا بعد الآن:

total_step = len(train_loader)

for epoch in range(num_epochs):
    for i, (images, labels) in enumerate(train_loader):  
        # تحريك التسويرات الى الجهاز المُكون
        images = images.to(device)
        labels = labels.to(device)

        # المرور الأمامي
        outputs = model(images)
        loss = criterion(outputs, labels)

        # المرور الخلفي والتحسين
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    print ('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}' 
                   .format(epoch+1, num_epochs, i+1, total_step, loss.item()))

    # Validation
    with torch.no_grad():
        correct = 0
        total = 0
        for images, labels in valid_loader:
            images = images.to(device)
            labels = labels.to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
            del images, labels, outputs

        print('Accuracy of the network on the {} validation images: {} %'.format(5000, 100 * correct / total)) 

هل نرى ما تفعل الشيكل؟

  • نبدأ بتواتر عدد المرات التعلمية، ومن ثم المجموعات في بيانات التعلم التي نملكها
  • نحن نحول الصور والتسميات وفقاً للجهاز الذي نستخدمه، أي الGPU أو الCPU
  • في المرور الأمامي، نقوم بتخمينات إستخدام نموذجنا ونحاول خسارة وفقاً لتلك التخمينات وتسمياتنا الحقيقية
  • بعد ذلك، نقوم بالمرور الخلفي حيث نحن فعلًا نحسن قيم our weights لتحسين نموذجنا
  • ثم نضع التقاطعات في الصفر قبل كل تحسين باستخدام ما يلي optimizer.zero_grad() مادة
  • ثم، نقوم بحساب التقاطعات الجديدة بواسطة ما يلي loss.backward() مادة.
  • وأخيرًا، نحن نحدد الوزنات بواسطة معالجة optimizer.step()
  • أيضًا في نهاية كل مرحلة نستخدم مجموعة التحكم المراد للحصول على دقة النموذج أيضًا. في هذه الحالة لا نحتاج إلى ما يليق بالمستويات لذا نستخدم with torch.no_grad() للتقدم بسرعة أكبر

يمكننا رؤية الخارج بالتالي:

خسارة التدريب ودقة التحكم في المجموعة التجريبية

كما يمكننا أن نرى، تنخفض الخسارة بكل مرة تقريبًا ما يراه أن نموذجنا يتعلم بالفعل. تعلم أن هذه الخسارة تختلف فقط في المجموعة التدريبية، وإذا كانت الخسارة بسيطة جدًا قد تشير إلى التعقيد الكبير. لهذا السبب نستخدم مجموعة التجريبية أيضًا. يبدو أن دقة التحكم تزداد في المجموعة التجريبية ما يعني أن هنالك أدنى فرصة للتعقيد. دعونا نختبر نموذجنا الآن لنرى كيف يقوم بالعمل.

الاختبار

الآن نرى كيف يقوم نموذجنا بالعمل على البيانات المعدنية:

with torch.no_grad():
    correct = 0
    total = 0
    for images, labels in test_loader:
        images = images.to(device)
        labels = labels.to(device)
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
        del images, labels, outputs

    print('Accuracy of the network on the {} test images: {} %'.format(10000, 100 * correct / total))   

تلاحظ أن برمجية التجريد تبدو تمامًا كما هي لأغراض تجريبنا.

باستخدام النموذج، والتدريب لمجموعة تقريبًا 6 مرات فقط، يبدو أن نحصل على دقة ما يقارب 78.8% في المجموعة التجريبية.

دقة الاختبار

الخاتم

دعونا نختم ما قمنا به في هذا المقال:

  • نحن بدأنا بفهم الهيكلة وأنواع الطبقات المختلفة في نموذج AlexNet
  • من ثم قمنا بتحميل وتحضير المجموعة CIFAR-10 باستخدام torchvision
  • ومن ثم استخدمنا PyTorch لبناء نموذج AlexNet من الصفر
  • وأخيرًا ، تم تدريب واختبار نموذجنا على المجموعة CIFAR-10 ويبدو أن النموذج يقوم بشكل جيد في التجربة الاختبارية بعد تدريب قليل (6 مرات)

العمل المستقبلي

هذا المقال يوفر معرفة أولية جيدة وتجارب عملية، لكنك ستكتسب معرفة أكثر بالتسيير إلى الأبعاد والاكتشاف ما يمكنك إنجازه.

  • يمكنك تجربة استخدام مجموعات مختلفة. وإحدى هذه المجموعات هي CIFAR-100 وهي توسعة لمجموعة CIFAR-10 بـ 100 فئة
  • يمكنك التجربة مع متغيرات أساسية مختلفة ورؤية أفضل مزيج منهم للنموذج
  • وأخيرًا، يمكنك أن تجربة إضافة أو إزالة طبقات من المجموعة لرؤية تأثيرها على قدرة النموذج على المهارات.

Source:
https://www.digitalocean.com/community/tutorials/alexnet-pytorch