جروكاي بيتورش

مرحبا يا هبر!

لدينا قبل طلب كتاب طال انتظاره حول مكتبة PyTorch .



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

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

الشبكات العصبية هي فئة فرعية من الرسوم البيانية الحسابية. تستقبل الرسوم البيانية الحاسوبية البيانات كمدخلات ، ثم يتم توجيه هذه البيانات (ويمكن تحويلها) في العقد حيث تتم معالجتها. في التعلم العميق ، تقوم العصب (العقد) عادةً بتحويل البيانات عن طريق تطبيق المعلمات والوظائف القابلة للتمييز عليها ، بحيث يمكن تحسين المعلمات لتقليل الخسائر من خلال طريقة النسب التدرج. بمعنى أوسع ، ألاحظ أن الدوال يمكن أن تكون عشوائية وديناميكية للرسم البياني. وبالتالي ، بينما تتوافق الشبكات العصبية بشكل جيد مع نموذج برمجة تدفق البيانات ، يركز PyTorch API على نموذج البرمجة الضروري ، وهذه الطريقة في تفسير البرامج التي يتم إنشاؤها هي أكثر دراية بكثير. هذا هو السبب في أن كود PyTorch أسهل في القراءة ، كما أنه من الأسهل الحكم على تصميم البرامج المعقدة ، والتي ، مع ذلك ، لا تتطلب حل وسط خطير على الأداء: في الواقع ، PyTorch سريعة بما فيه الكفاية وتوفر العديد من التحسينات التي لا يمكنك ، كمستخدم نهائي ، أن تقلق على الإطلاق (ومع ذلك ، إذا كنت مهتمًا بها حقًا ، فيمكنك البحث بعمق أكبر والتعرف عليها).

ما تبقى من هذه المقالة هو تحليل للمثال الرسمي على مجموعة البيانات MNIST . نحن هنا نلعب PyTorch ، لذلك أوصي بفهم المقال فقط بعد التعرف على أدلة المبتدئين الرسمية . للراحة ، يتم تقديم الرمز في شكل أجزاء صغيرة مجهزة بالتعليقات ، أي أنه لا يتم توزيعه في وظائف / ملفات منفصلة اعتدت على رؤيتها في كود معياري خالص.

واردات


 import argparse import os import torch from torch import nn, optim from torch.nn import functional as F from torch.utils.data import DataLoader from torchvision import datasets, transforms 

كل هذه واردات قياسية تمامًا ، باستثناء وحدات torchvision ، والتي تستخدم بفعالية خاصة لحل المهام المتعلقة برؤية الكمبيوتر.

تعديل


 parser = argparse.ArgumentParser(description='PyTorch MNIST Example') parser.add_argument('--batch-size', type=int, default=64, metavar='N', help='input batch size for training (default: 64)') parser.add_argument('--epochs', type=int, default=10, metavar='N', help='number of epochs to train (default: 10)') parser.add_argument('--lr', type=float, default=0.01, metavar='LR', help='learning rate (default: 0.01)') parser.add_argument('--momentum', type=float, default=0.5, metavar='M', help='SGD momentum (default: 0.5)') parser.add_argument('--no-cuda', action='store_true', default=False, help='disables CUDA training') parser.add_argument('--seed', type=int, default=1, metavar='S', help='random seed (default: 1)') parser.add_argument('--save-interval', type=int, default=10, metavar='N', help='how many batches to wait before checkpointing') parser.add_argument('--resume', action='store_true', default=False, help='resume training from checkpoint') args = parser.parse_args() use_cuda = torch.cuda.is_available() and not args.no_cuda device = torch.device('cuda' if use_cuda else 'cpu') torch.manual_seed(args.seed) if use_cuda: torch.cuda.manual_seed(args.seed) 

argparse هي الطريقة القياسية للتعامل مع وسيطات سطر الأوامر في Python.

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

من أجل استنساخ التجارب ، تحتاج إلى تعيين قيم أولية عشوائية لجميع المكونات التي تستخدم توليد الأرقام العشوائية (بما في ذلك random أو numpy ، إذا كنت تستخدمها numpy ). يرجى ملاحظة: يستخدم cuDNN خوارزميات غير حتمية ويتم تعطيله اختياريًا باستخدام torch.backends.cudnn.enabled = False .

معطيات


 data_path = os.path.join(os.path.expanduser('~'), '.torch', 'datasets', 'mnist') train_data = datasets.MNIST(data_path, train=True, download=True, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])) test_data = datasets.MNIST(data_path, train=False, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])) train_loader = DataLoader(train_data, batch_size=args.batch_size, shuffle=True, num_workers=4, pin_memory=True) test_loader = DataLoader(test_data, batch_size=args.batch_size, num_workers=4, pin_memory=True) 


نظرًا لأن نماذج torchvision تخزينها تحت ~/.torch/models/ ، أفضل تخزين torchvision torchvision تحت ~/.torch/datasets . هذه اتفاقية حقوق الطبع والنشر الخاصة بي ، لكنها ملائمة جدًا للاستخدام في المشروعات التي تم تطويرها على أساس MNIST و CIFAR-10 ، إلخ. بشكل عام ، يجب تخزين مجموعات البيانات بشكل منفصل عن الرمز إذا كنت تنوي إعادة استخدام مجموعات بيانات متعددة.

يحتوي torchvision.transforms على العديد من خيارات التحويل المريحة للصور الفردية ، مثل الاقتصاص والتطبيع.

هناك العديد من الخيارات في batch_size ، ولكن بالإضافة إلى batch_size و shuffle ، يجب عليك أيضًا أن تضع في الاعتبار num_workers و pin_memory ، فهي تساعد في زيادة الكفاءة. يستخدم num_workers > 0 لتحميل البيانات غير المتزامن ، ولا يمنع العملية الرئيسية لهذا. حالة الاستخدام النموذجي هي تحميل البيانات (على سبيل المثال ، الصور) من القرص ، وربما تحويلها ؛ كل هذا يمكن القيام به بالتوازي ، جنبا إلى جنب مع معالجة بيانات الشبكة. قد يلزم ضبط درجة المعالجة من أجل (أ) تقليل عدد العمال ، وبالتالي ، فإن كمية وحدة المعالجة المركزية وذاكرة الوصول العشوائي المستخدمة (يقوم كل عامل بتحميل مجموعة منفصلة ، بدلاً من العينات الفردية المدرجة في المجموعة) ب) تقليل طول الوقت الذي تنتظره البيانات على الشبكة. يستخدم pin_memory الذاكرة المثبتة (بدلاً من الصفحات) لتسريع أي عمليات نقل بيانات من ذاكرة الوصول العشوائي إلى وحدة معالجة الرسومات (ولا تفعل شيئًا مع رمز خاص بوحدة المعالجة المركزية).

نموذج


 class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 10, kernel_size=5) self.conv2 = nn.Conv2d(10, 20, kernel_size=5) self.conv2_drop = nn.Dropout2d() self.fc1 = nn.Linear(320, 50) self.fc2 = nn.Linear(50, 10) def forward(self, x): x = F.relu(F.max_pool2d(self.conv1(x), 2)) x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2)) x = x.view(-1, 320) x = F.relu(self.fc1(x)) x = self.fc2(x) return F.log_softmax(x, dim=1) model = Net().to(device) optimiser = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum) if args.resume: model.load_state_dict(torch.load('model.pth')) optimiser.load_state_dict(torch.load('optimiser.pth')) 

تمتد تهيئة الشبكة عادةً إلى متغيرات الأعضاء ، والطبقات التي تحتوي على معلمات التعلم ، وربما معلمات التعلم الفردية والمخازن المؤقتة غير المدربة. بعد ذلك ، باستخدام ممر مباشر ، يتم استخدامها مع وظائف من F وظيفية بحتة ولا تحتوي على معلمات. يحب بعض الأشخاص العمل مع الشبكات الوظيفية البحتة (مثل الاحتفاظ بالمعلمات واستخدام F.conv2d بدلاً من nn.Conv2d ) أو الشبكات التي تتكون تمامًا من طبقات (مثل nn.ReLU بدلاً من F.relu ).

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

يمكن لكل من الشبكات العصبية ( nn.Module ) nn.Module ( optim.Optimizer ) حفظ وتحميل حالتهما الداخلية ، ومن المستحسن القيام بذلك مع .load_state_dict(state_dict) - من الضروري إعادة تحميل حالة كليهما من أجل استئناف التدريب استنادًا إلى القواميس المحفوظة مسبقًا الدول. قد يكون حفظ الكائن بأكمله محفوفًا بالأخطاء . إذا قمت بحفظ التنسورات على وحدة معالجة الرسومات وترغب في تحميلها على وحدة المعالجة المركزية أو وحدة معالجة GPU أخرى ، فإن أسهل طريقة هي تحميلها مباشرة على وحدة المعالجة المركزية باستخدام خيار map_location ، مثل torch.load('model.pth' ، map_location='cpu' ).

فيما يلي بعض النقاط الأخرى التي لا تظهر هنا ، ولكن تجدر الإشارة إلى أنه يمكنك استخدام تدفق التحكم بتمرير مباشر (على سبيل المثال ، قد يعتمد تنفيذ if على متغير العضو أو على البيانات نفسها. بالإضافة إلى ذلك ، من المقبول تمامًا الإخراج في منتصف العملية ( print ) التنسورات ، مما يبسط عملية التصحيح إلى حد كبير. أخيرًا ، باستخدام تمرير مباشر ، يمكن استخدام الكثير من الحجج. سأوضح هذه النقطة من خلال قائمة مختصرة لا ترتبط بأي فكرة معينة:

 def forward(self, x, hx, drop=False): hx2 = self.rnn(x, hx) print(hx.mean().item(), hx.var().item()) if hx.max.item() > 10 or self.can_drop and drop: return hx else: return hx2 

تدريب


 model.train() train_losses = [] for i, (data, target) in enumerate(train_loader): data = data.to(device=device, non_blocking=True) target = target.to(device=device, non_blocking=True) optimiser.zero_grad() output = model(data) loss = F.nll_loss(output, target) loss.backward() train_losses.append(loss.item()) optimiser.step() if i % 10 == 0: print(i, loss.item()) torch.save(model.state_dict(), 'model.pth') torch.save(optimiser.state_dict(), 'optimiser.pth') torch.save(train_losses, 'train_losses.pth') 

يتم وضع وحدات الشبكة في وضع التدريب بشكل افتراضي - مما يؤثر إلى حد ما على تشغيل الوحدات ، والأهم من ذلك كله - ترقق وتطبيع الدُفعة. بطريقة أو بأخرى ، من الأفضل تعيين مثل هذه الأشياء يدويًا باستخدام .train() ، والتي تقوم بتصفية علامة "التدريب" على جميع الوحدات التابعة.

هنا ، لا تقبل طريقة non_blocking=True .to() الجهاز فحسب ، بل تقوم أيضًا بتعيين non_blocking=True ، مما يضمن النسخ غير المتزامن للبيانات إلى وحدة معالجة الرسومات من الذاكرة الملتزمة ، مما يسمح لوحدة المعالجة المركزية بالبقاء جاهزة للعمل أثناء نقل البيانات ؛ خلاف ذلك ، non_blocking=True ببساطة ليس خيارًا.

قبل إنشاء مجموعة جديدة من التدرجات باستخدام loss.backward() optimiser.step() باستخدام optimiser.step() ، يجب إعادة تعيين تدرجات المعلمات يدويًا إلى الأمثلية باستخدام optimiser.zero_grad() . بشكل افتراضي ، يقوم PyTorch بتجميع التدرجات ، وهو مناسب جدًا إذا لم يكن لديك موارد كافية لحساب جميع التدرجات التي تحتاجها في مسار واحد.

يستخدم PyTorch نظام "شريط" من التدرجات التلقائية - فهو يجمع معلومات حول العمليات وبأي ترتيب تم إجراؤه على التنسورات ، ثم يقوم بتشغيلها في الاتجاه المعاكس لإجراء تمايز بالترتيب العكسي (تمايز الوضع العكسي). هذا هو السبب في أنها فائقة المرونة وتتيح الرسوم البيانية الحسابية التعسفية. إذا لم يكن أي من هذه التنسورات يتطلب تدرجات (يجب عليك تعيين requires_grad=True ، وإنشاء موتر لهذا الغرض) ، فلن يتم حفظ أي رسم بياني! ومع ذلك ، عادة ما تحتوي الشبكات على معلمات تتطلب تدرجات ، لذلك سيتم تخزين أي حسابات يتم إجراؤها على أساس إخراج الشبكة في الرسم البياني. لذلك ، إذا كنت ترغب في حفظ البيانات الناتجة عن هذه الخطوة ، فستحتاج إلى تعطيل التدرجات يدويًا أو (طريقة أكثر شيوعًا) ، وحفظ هذه المعلومات كرقم Python (باستخدام .item() في عدد PyTorch) أو صفيف numpy . اقرأ المزيد عن autograd في الوثائق الرسمية .

تتمثل إحدى طرق اختصار الرسم البياني الحسابي في استخدام .detach() عندما يتم تمرير الحالة المخفية عند تعلم RNN بإصدار مقتطع من backpropagation-through-time. كما أنه مناسب عند التمييز بين الخسائر ، عندما يكون أحد المكونات هو إخراج شبكة أخرى ، ولكن لا ينبغي تحسين هذه الشبكة الأخرى فيما يتعلق بالخسائر. على سبيل المثال ، سأدرس الجزء التمييزي على توليد المواد الناتجة عند العمل مع GAN ، أو التدريب على السياسة في خوارزمية الممثل الناقد باستخدام الوظيفة الموضوعية كدالة أساسية (على سبيل المثال ، A2C). هناك طريقة أخرى تمنع حساب التدرجات ، وهي فعالة في تدريب GAN (تدريب الجزء param.requires_grad = False على المادة التمييزية) ونموذجية في الضبط الدقيق هي التعداد الدوري لمعلمات الشبكة التي param.requires_grad = False .

من المهم ليس فقط تسجيل النتائج في ملف وحدة التحكم / السجل ، ولكن أيضًا تعيين نقاط التحكم في معلمات النموذج (وحالة المُحسِّن) فقط في حالة. يمكنك أيضًا استخدام torch.save() لحفظ كائنات Python العادية ، أو استخدام حل قياسي آخر - pickle المدمج.

تجريب


 model.eval() test_loss, correct = 0, 0 with torch.no_grad(): for data, target in test_loader: data = data.to(device=device, non_blocking=True) target = target.to(device=device, non_blocking=True) output = model(data) test_loss += F.nll_loss(output, target, reduction='sum').item() pred = output.argmax(1, keepdim=True) correct += pred.eq(target.view_as(pred)).sum().item() test_loss /= len(test_data) acc = correct / len(test_data) print(acc, test_loss) 

استجابة إلى .train() يجب وضع الشبكات بشكل صريح في وضع التقييم باستخدام .eval() .

كما ذكر أعلاه ، عند استخدام شبكة ، يتم تجميع الرسم البياني الحسابي عادة. لمنع هذا ، استخدم no_grad السياق with torch.no_grad() .

بعض أكثر


هذا قسم إضافي ، صنعت فيه حفريات أكثر فائدة.
إليك الوثائق الرسمية التي توضح كيفية العمل مع الذاكرة.

أخطاء كودا؟ من الصعب إصلاحها ، وعادةً ما تكون متصلاً بتناقضات منطقية ، حيث يتم عرض رسائل خطأ أكثر منطقية على وحدة المعالجة المركزية من وحدة معالجة الرسومات. أفضل شيء ، إذا كنت تخطط للعمل مع GPU ، فيمكنك التبديل بسرعة بين وحدة المعالجة المركزية و GPU. نصيحة تطوير أكثر عمومية هي تنظيم الكود بحيث يمكن التحقق منه بسرعة قبل بدء مهمة كاملة. على سبيل المثال ، قم بإعداد مجموعة بيانات صغيرة أو اصطناعية ، وقم بإجراء اختبار قطار + حقبة واحدة ، إلخ. إذا كانت المسألة خطأ CUDA ، أو لا يمكنك التبديل إلى وحدة المعالجة المركزية على الإطلاق ، فاضبط CUDA_LAUNCH_BLOCKING = 1. هذا سيجعل نواة CUDA تطلق متزامن ، وستتلقى رسائل خطأ أكثر دقة.

ملاحظة على torch.multiprocessing أو مجرد تشغيل نصوص PyTorch متعددة في نفس الوقت. لأن PyTorch يستخدم مكتبات BLAS متعددة الخيوط لتسريع حسابات الجبر الخطية على وحدة المعالجة المركزية ، وعادة ما تشارك العديد من النوى. إذا كنت ترغب في القيام بعدة أشياء في نفس الوقت ، باستخدام المعالجة متعددة الخيوط أو العديد من البرامج النصية ، فقد يكون من المستحسن تقليل عددها يدويًا عن طريق تعيين متغير البيئة OMP_NUM_THREADS إلى 1 أو قيمة منخفضة أخرى. وبالتالي ، يتم تقليل احتمال الانزلاق المعالج. تحتوي الوثائق الرسمية على تعليقات أخرى بخصوص المعالجة متعددة مؤشرات الترابط.

Source: https://habr.com/ru/post/ar471228/


All Articles