קדמה
המאמר הזה הוא ממשיך לסדרה של בניית מהמנהגים הכי פופולריים של מערכות מוח מבניות בPyTorch. אתם יכולים לראות את המאמר הקודם פה, בו בנינו LeNet5. במאמר הזה, אנחנו נבנה את AlexNet, אחד מההתקדמויות המפתחיות ביותר במחשב הראייה.
אנחנו נתחיל בהשקפה והבנה של הארכיטקטורה של AlexNet. אחר כך, אנחנו נתקע בקוד על-ידי לטעיית מידע המערכת שלנו, CIFAR-10, לפני שימוש בשיטות העיבוד הקדם. אחר כך, אנחנו נבנה את AlexNet מאפס בשימוש בPyTorch
ונאמן אותו על המידע המקדם שלנו. לבסוף, המודל המאמן ייבדק על מידע בלתי נראה (בדיקה בדיוק) עבור מטרות בדיקה.
דרישות קדמות
ידע על מערכות מוח יעזר להבנה של המאמר הזה. זה יכלל המודעות לרצונות שונים של מערכות המוח (רצף קלט, שכבות חביבים, רצף יוצא), פונקציות הפעלה, אלגוריתמים העדכון (סוגים של היבט השיחזורי), פעולות האבדן ועוד. בנוסף, המודעות לסינTAX הפיתוח ולספקטטורת PyTorch היא חיונית עבור הבנה של הסקירות הקוד במאמר זה.
הבנה של CNNs היא בעלת חשיבות. זה כוללת ידע על שכבות המיופץ, שכבות הבצעה, והתפקידן בניקוף תכונות מהמידע הקדם. הבנה על תפיסות כמו סטריד, מלאה, וההשפעה של גודל המעטפת/פילטר היא גם מועילה.
AlexNet
AlexNet היא רשת עצבים עמוקה שנוצרה במקור על ידי Alex Krizhevsky ועמיתיו ב-2012. היא מעוצבה על מנת לסווג תמונות למרוצת תחרות ImageNet LSVRC-2010, שבה היא הגיעה לתוצאות המדעיות המתקדמות. ניתן לקרוא על המודל במאמר המקורי כאן.
בואו נעבור על התפיסות העיקריות מהמאמר אלה של AlexNet. ראשית, AlexNet פעלה עם תמונות בעלות 3 ערוצים שהיו (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
. הדבר הנחמד בPyTorch הוא שאנחנו יכולים לשלב את השכבה המוליכה, פונקציית הפעלה ומיקס פולינג לשכבה אחת בעזרת הפונקציהnn.Sequential
- אחר כך אנחנו מגדירים את השכבות המקומיות בעזרת הפונקציות הלינאריות (
nn.Linear
) ודריפוט (nn.Dropout
) בעודנו משתמשים בפונקציית הפעלה ReLu (nn.ReLU
) ומשלבים את אותם בעזרת הפונקציהnn.Sequential
- לבסוף, השכבה האחרונה שלנו מייצרת 10 עצמתים שהם ההערצות הסופיות עבור ה-10 הסוגים האחרונים של העצמים
הגדרת הגיונים
לפני ההאימון, אנחנו צריכים להגדיר מספר גיונים, כמו הפער שימושי והאופטימיzer שישתמשו בו, בנוסף לגודל האוטומביץ', קצב הלמידה ומספר העברות.
num_classes = 10
num_epochs = 20
batch_size = 64
learning_rate = 0.005
model = AlexNet(num_classes).to(device)
# הפער והאופטימיzer
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay = 0.005, momentum = 0.9)
# אימון המודל
total_step = len(train_loader)
אנו מתחילים בהגדרת היפרפרמטרים פשוטים (epochs, גודל אצווה וקצב למידה) ומאתחלים את המודל שלנו תוך שימוש במספר הקטגוריות כארגומנט, שבמקרה זה הוא 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))
בואו נראה מה הקוד עושה:
- אנחנו מתחילים בלולאה דרך מספר ה-epochs, ולאחר מכן את האצוות בנתוני האימון שלנו
- אנו ממירים את התמונות ואת התוויות בהתאם למכשיר שבו אנו משתמשים, כלומר GPU או CPU
- במעבר קדימה, אנו מבצעים חיזויים באמצעות המודל שלנו ומחשבים את ההפסד על סמך החיזויים הללו והתוויות האמיתיות שלנו
- לאחר מכן, אנו מבצעים את המעבר אחורה שבו אנו למעשה מעדכנים את המשקלים שלנו כדי לשפר את המודל
- לאחר מכן, אנו מאפסים את הגרדיאנטים לפני כל עדכון באמצעות פונקציית
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, המהגדלה על מנת 100 היבטים של קבוצת הנתונים CIFAR-10
- אתה יכול לבצע נסיונות עם משתנים שונים ולראות את השילוב הטוב ביותר שלהם עבור המודל
- סוף סוף, אתה יכול לנסות להוסיף או להסיר שכבות מהמערך כדי לראות את ההשפעה שלהם על היכולת של המודל
Source:
https://www.digitalocean.com/community/tutorials/alexnet-pytorch