یادگیری عمیق با PyTorch — راهنمای کاربردی

۷۳۰ بازدید
آخرین به‌روزرسانی: ۱۲ اردیبهشت ۱۴۰۲
زمان مطالعه: ۷ دقیقه
یادگیری عمیق با PyTorch — راهنمای کاربردی

«یادگیری عمیق» (Deep Learning)، یکی از مباحث داغ در حوزه «یادگیری ماشین» (Machine Learning) محسوب می‌شود. با توجه به ویژگی‌هایی که «زبان برنامه‌نویسی پایتون» (Python Programming Language) ارائه می‌کند و کتابخانه‌های متعدد و قدرتمند نوشته شده برای آن، استفاده از این زبان در زمینه «یادگیری ماشین» (Machine Learning) و «داده‌کاوی» (Data Mining) روز به روز در حال افزایش است. «پای‌تورچ» (PyTorch)، یکی از «کتابخانه‌های» (Library) «هوش مصنوعی» (Artificial Intelligence) توسعه داده شده برای زبان پایتون است. این کتابخانه توسط «فیس‌بوک» (Facebook) ساخته شده و توسعه داده می‌شود. کد کتابخانه PyTorch در «گیت‌هاب» (GitHub) [+] موجود است و در حال حاضر بیش از ۲۲ هزار ستاره دارد. این کتابخانه از سال ۲۰۱۷ تاکنون شاهد تغییرات زیادی بوده است و کاربران آن به تدریج در حال افزایش هستند. در این مطلب به پیاده‌سازی یادگیری عمیق با PyTorch پرداخته شده است. اما پیش از ورود به بحث اصلی، برای درک بهتر این چارچوب و چشم‌انداز آن، تصویر زیر که بر اساس داده‌های «گوگل ترندز» (Google Trends) برای جست‌و‌جوی پای‌تورچ (PyTorch) و «تنسورفلو» (Tensorflow) ایجاد شده است بررسی می‌شوند.

تنسورفلو

با توجه به نمودار، به وضوح مشخص است که این کتابخانه ارزش یادگیری زیادی دارد. در این مطلب، برخی از مولفه‌ها و آنچه برای آغاز کار با این چارچوب مورد نیاز است بیان شده‌اند. ابتدا، چگونگی تعریف مدل بیان می‌شود. سپس، چگونگی انجام «بارگذارهای داده» (Data Loaders) مورد بررسی قرار می‌گیرد. در ادامه، نحوه «آموزش دادن» (Training) با به روز رسانی وزن‌های شبکه تشریح و در نهایت، توضحیات کوتاهی پیرامون تانسورها در پای‌تورچ ارائه می‌شود.

یادگیری عمیق با PyTorch

در این بخش، نحوه تعریف شبکه در پای‌تورچ بیان می‌شود. باید توجه داشت که PyTorch، دارای یک راهکار استاندارد برای ساخت مدل است. تعریف کلی باید درون یک «شی» (Object) قرار بگیرد که فرزندی از کلاس nn.Module است. درون این کلاس، تنها دو متد وجود دارد که باید پیاده‌سازی شوند.

این متدها عبارتند از  __init__ و forward. برای دادن ایده اساسی پیرامون چگونگی استفاده از این موارد، در ادامه یک مثال از پیاده‌سازی شبکه‌ای شامل ۳ لایه خطی ترکیب شده با دو لایه RELU ارائه شده است.

1import torch
2import torch.nn as nn
3import torch.nn.functional as F
4
5
6class Net(nn.Module):
7
8    def __init__(self):
9        super(Net, self).__init__()
10        # Defining 3 linear layers but NOT the way they should be connected
11        # Receives an array of length 240 and outputs one with length 120
12        self.fc1 = nn.Linear(240, 120)
13        # Receives an array of length 120 and outputs one with length 60
14        self.fc2 = nn.Linear(120, 60)
15        # Receives an array of length 60 and outputs one with length 10
16        self.fc3 = nn.Linear(60, 10)
17
18    def forward(self, x):
19        # Defining the way that the layers of the model should be connected
20        # Performs RELU on the output of layer 'self.fc1 = nn.Linear(240, 120)'
21        x = F.relu(self.fc1(x))
22        # Performs RELU on the output of layer 'self.fc2 = nn.Linear(120, 60)'
23        x = F.relu(self.fc2(x))
24        # Passes the array through the last linear layer 'self.fc3 = nn.Linear(60, 10)'
25        x = self.fc3(x)
26        return x
27
28
29net = Net()

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

__init__

درست مانند دیگر کلاس‌های پایتون، روش __init__برای تعریف ویژگی‌های کلاس استفاده می‌شود و هر مقداری را که کاربر بخواهد به سرعت ایجاد می‌کند. در زمینه پای‌تورچ، همیشه باید متد ()super را برای مقداردهی اولیه به کلاس پدر فراخوانی کرد.

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

forward

این متدی است که در آن چگونگی اتصال لایه‌ها تعیین می‌شود. می‌توان در مثال بالا دید که با این متد، لایه‌هایی فراخوانی می‌شوند که در __init__ تعریف شده‌اند و سپس یک مقدار که نشانگر خروجی شبکه است نشان داده می‌شود. شایان ذکر است که چند تابع دیگر درون متد فوروارد تعریف شده‌اند که درون متد  __init__ تعریف نشده‌اند، اما به طور سنتی به عنوان لایه در نظر گرفته می‌شوند.

برای مثال، می‌توان نگاهی به تابع F.relu انداخت. این تایع، درون متد __init__تعریف نشده، زیرا هیچ پارامتر قابل آموزش دیدنی ندارد. تابع F.relu با دریافت ورودی‌های مشابه، همیشه یک خروجی دارد. آموزش دادن شبکه رفتار این تابع را تغییر نمی‌دهد. بنابراین، به عنوان یک قاعده سرانگشتی می‌توان درون متد forward همه لایه‌هایی که هیچ وزنی برای به روز رسانی شدن ندارند را قرار داد. از سوی دیگر، باید همه لایه‌هایی که دارای وزن‌های قابل به روز رسانی هستند را درون __init__ قرار داد.

بارگذاری داده‌ها: مجموعه داده و بارگذارهای داده

«مجموعه داده» (Dataset) و «بارگذارهای داده» (Data loaders) ابزارهایی در پای تورچ هستند که می‌توانند چگونگی دسترسی به داده‌ها را تعیین کنند. این کار وقتی هیجان‌انگیزتر می شود که داده‌های در چندین فایل توزیع شده باشند.

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

1import torch
2import pandas as pd
3from torch.utils.data import Dataset, DataLoader
4
5class ExampleDataset(Dataset):
6    """Example Dataset"""
7
8    def __init__(self, csv_file):
9        """ 
10        csv_file (string): Path to the csv file containing data.
11        """
12        self.data_frame = pd.read_csv(csv_file)
13
14    def __len__(self):
15        return len(self.data_frame)
16
17    def __getitem__(self, idx):
18        return self.data_frame[idx]
19
20# instantiates the dataset  
21example_dataset = ExampleDataset('my_data_file.csv')
22
23# batch size: number of samples returned per iteration
24# shuffle: Flag to shuffle the data before reading so you don't read always in the same order
25# num_workers: used to load the data in parallel
26example_data_loader = DataLoader(example_dataset, , batch_size=4, shuffle=True, num_workers=4)
27
28# Loops over the data 4 samples at a time
29for batch_index, batch in enumerate(example_data_loader):
30    print(batch_index, batch)

سه متد وجود دارد که برای پیاده‌سازی کلاس Dataset مورد نیاز هستند.

__init__

در مقداردهی اولیه، باید اطلاعات دایرکتوری و دیگر مواردی که اجازه دسترسی به آن را می‌دهند قرار داده شود. در مثال بالا، داده‌ها از یک فایل CSV فراخوانی می‌شوند. همچنین، می‌توان یک لیست از اسامی فایل را که در آن هر اسم فایل نشان‌دهنده یک نقطه داده است بارگذاری کرد. نیازی به بارگذاری داده‌ها درون __init__ نیست.

__len__

در این روش، باید از راهکاری برای دریافت اندازه کل مجموعه داده استفاده شود. برای مثال، اگر کاربر یک محموعه از تصاویر را در برخی از دایرکتوری‌ها دارد، باید راهی برای محاسبه کل تعداد فایل‌هایی که داده‌ها را می‌سازند انتخاب و پیاده‌سازی شود. در مثال مطرح شده در بالا، اندازه دیتافریم به سادگی دریافت شده است. اگرچه، این کار می‌تواند بسته به اینکه داده‌ها چگونه ذخیره شده‌اند به امری پیچیده مبدل شود.

__getitem__

این جایی است که چگونگی دریافت یک آیتم از مجموعه داده، پیاده‌سازی شده است. برای مثال، اگر چندین تصویر وجود داشته باشد، __getitem__ همان جایی است که تصویر از دیسک در حافظه بارگذاری می‌شود و به آن به عنوان چیزی که متد را باز می‌گرداند خدمات‌دهی می‌شود.

به منظور داشتن کارایی بیشتر در دسترسی به داده‌ها، از کلاس DataLoader استفاده می‌شود. این کلاس به سادگی یک دسته از داده‌ها را در زمان به صورت موازی می‌خواند، در حالیکه به صورت دلخواه داده‌ها را تغییر می‌دهد. همه این عملیات برای آموزش موثر، مفید محسوب می‌شوند.

آموزش دادن: به روز رسانی وزن‌های شبکه

تاکنون، مدل و مجموعه داده تعریف شدند. اکنون نیاز به ساخت چیزی است که امکان یادگیری را برای شبکه فراهم می‌کند. در PyTorch، این کار با استفاده از یک بهینه‌ساز (optimizer) انجام می‌شود. بهینه‌ساز در طول همه وزن‌ها حرکت و آن‌ها را به روز رسانی می‌کند. این یعنی به طور خودکار به همه لایه‌هایی که در متد  __init__ از کلاس شبکه تعریف شده‌اند دسترسی دارد و آن‌ها را با استفاده از یک نرخ یادگیری و الگوریتم مشخص، بسته به نوع بهینه‌سازی که انتخاب می‌شود، به روز رسانی می‌کند.

بهینه‌ساز باید معیارهایی برای استفاده به منظور بهینه‌سازی داشته باشد. این همان جایی است که نیاز به تعریف تابع «زیان» (Loss) مطرح می‌شود. در این کتابخانه، چندین نوع مختلف از تابع‌های هزینه پیاده‌سازی شده است که برای جزئیات بیشتر پیرامون آن‌ها می‌توان به این لینک (+) مراجعه کرد. در ادامه، یک مثال پایه‌ای از چگونگی مقداردهی اولیه به شبکه، انتخاب یک بهینه‌ساز و سپس حلقه زدن در داده‌ها و به روز رسانی وزن‌های آن‌ها آورده شده است.

1import torch.optim as optim
2import torch.nn as nn
3# instantiate your network that should be defined by you
4net = Net()
5# create your optimizer
6optimizer = optim.SGD(net.parameters(), lr=0.01)
7# define your criterion for optimization
8criterion = nn.MSELoss()
9# dat_set comes from somewhere
10for data in data_set:
11  # zero the gradient buffers
12  optimizer.zero_grad()  
13  # Passes the data through your network
14  output = net.forward(data)
15  # calculates the loss
16  loss = criterion(output, target)
17  # Propagates the loss back
18  loss.backward()
19  # Updates all the weights of the network
20  optimizer.step()

نکاتی پیرامون تانسورها و گرادیان‌ها

هر چیزی که در کتابخانه‌های «شبکه‌های عصبی» (Neural Network) پایتون به وقوع می‌پیوندد، در واقع عملیات «تانسورها» (Tensor) است. PyTorch نیز از این قاعده مستثنی نیست. تانسورها مانند آرایه‌ها هستند، اما می‌توانند در هر ابعادی باشند.

این یعنی یک آرایه تک‌بُعدی و یک ماتریس نیز تانسور هستند. ارائه یک تصویر مانند WxHx3 که در آن ۳ برای RGB است نیز، یک تانسور محسوب می‌شود. در هسته کتابخانه پای‌تورچ، برای ساخت عملیات روی تانسورها و محاسبه گرادیان روی این عملیات (یعنی عملیات تانسور) تلاش می‌شود.

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

خلاصه

هنگام کار با چارچوب پای‌تورچ، گام‌هایی که باید دنبال کرد به صورت زیر هستند:

  1. تعریف کلاس شبکه با قرار دادن لایه‌ها با وزن‌هایی که درون متد  __init__ می‌توانند به روز رسانی شوند. سپس، چگونگی جریان داشتن داده‌ها در لایه‌ها درون متد forward باید تعریف شود.
  2. چگونگی بارگذاری داده‌ها با استفاده از کلاس Dataset باید تعریف شود. سپس، از کلاس DataLoader برای حلقه زدن در داده‌ها استفاده می‌شود.
  3. یک بهینه‌ساز و تابع زیان باید انتخاب شود. در طول داده‌های آموزش باید حلقه زده شود و به بهینه‌ساز امکان به روز رسانی وزن‌های شبکه داده شود.

ساخت یک مدل خوب ممکن است نیاز به تمرین و تجربه زیاد داشته باشد. با در نظر گرفتن این موضوع، باید گفت پای‌تورچ یک کتابخانه بسیار خوب محسوب می‌شود که تا این لحظه بسیاری از عملیات را برای کاربر پیاده‌سازی کرده است.

اگر نوشته بالا برای شما مفید بود، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

^^

بر اساس رای ۱۳ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
medium
۲ دیدگاه برای «یادگیری عمیق با PyTorch — راهنمای کاربردی»

با تشکر از این مقاله خوب ، سئوال target چه هست در کد؟



با سلام و احترام خدمت شما مخاطب گرامی مجله فرادرس؛

منظور از target، برچسب داده‌های واقعی هست.

از همراهی شما سپاس‌گزاریم.

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *