حول تطبيق مكتبة التعلم العميق في Python

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



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

معلومات عامة


عادة ، تتكون مكتبات التعلم العميق (مثل TensorFlow و PyTorch) من المكونات الموضحة في الشكل التالي.


مكونات إطار التعلم العميق

دعونا نحلل هذه المكونات.

ators عوامل التشغيل


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

ptمحسنات (محسنات)


يُعد المُحسِّنون أساس مكتبات التعلم العميق. يصفون طرق تعديل معلمات النموذج باستخدام معايير معينة ومراعاة هدف التحسين. من بين المحسنات المعروفة ، يمكن ملاحظة SGD و RMSProp و Adam.

functions وظائف الخسارة


دالات الخسارة هي تعبيرات رياضية تحليلية ومختلفة يتم استخدامها كبديل لهدف التحسين عند حل مشكلة. على سبيل المثال ، عادة ما يتم استخدام دالة التداخل والوظيفة الخطية المتقطعة في مشاكل التصنيف.

▍ مبدئي


توفر المُبدِعات قيمًا أولية لمعلمات النموذج. هذه هي القيم التي لها المعلمات في بداية التدريب. تلعب المبادرين دورًا مهمًا في تدريب الشبكات العصبية ، نظرًا لأن المعلمات الأولية غير الناجحة قد تعني أن الشبكة ستتعلم ببطء أو قد لا تتعلم على الإطلاق. هناك العديد من الطرق لتهيئة أوزان الشبكة العصبية. على سبيل المثال - يمكنك تعيين قيم عشوائية صغيرة لهم من التوزيع العادي. إليك صفحة حيث يمكنك التعرف على الأنواع المختلفة للمبدعين.

▍ المنظمون


المنظمون هي أدوات تتجنب إعادة تدريب الشبكة وتساعد الشبكة في اكتساب التعميم. يمكنك التعامل مع إعادة تدريب الشبكة بطرق صريحة أو ضمنية. تتضمن الأساليب الصريحة قيودًا هيكلية على الأوزان. على سبيل المثال ، التقليل إلى أدنى حد من L1-Norm و L2-Norm ، مما يجعل ، وفقًا لذلك ، قيم الوزن أفضل مشتتة وموزعة بشكل متساوٍ. يتم تمثيل الأساليب الضمنية من قبل مشغلين متخصصين يقومون بتحويل التمثيلات الوسيطة. يتم ذلك إما من خلال التسوية الصريحة ، على سبيل المثال ، باستخدام تقنية تسوية الحزم (BatchNorm) ، أو عن طريق تغيير اتصال الشبكة باستخدام خوارزميات DropOut و DropConnect.

عادة ما تنتمي المكونات المذكورة أعلاه إلى جزء واجهة المكتبة. هنا ، من خلال "جزء الواجهة" أعني الكيانات التي يمكن للمستخدم التفاعل معها. يقدمون له أدوات مناسبة لتصميم بنية الشبكة العصبية بكفاءة. إذا تحدثنا عن الآليات الداخلية للمكتبات ، فيمكنها تقديم الدعم للحساب التلقائي لتدرجات دالة الخسارة ، مع مراعاة المعلمات المختلفة للنموذج. تُسمى هذه التقنية عادةً التمايز التلقائي (AD).

التمايز التلقائي


توفر كل مكتبة تعليمية عميقة للمستخدم بعض إمكانات التمايز التلقائي. وهذا يمنحه الفرصة للتركيز على وصف هيكل النموذج (الرسم البياني للحسابات) ونقل مهمة حساب التدرجات إلى وحدة AD. دعنا نأخذ مثالًا يتيح لنا معرفة كيفية عمل كل شيء. افترض أننا نريد حساب المشتقات الجزئية للدالة التالية فيما يتعلق بمتغيرات الإدخال X₁ و X₁:

Y = sin (x₁) + X₁ * X₂

يوضح الشكل التالي ، الذي استعرته من هنا ، الرسم البياني للحسابات وحساب المشتقات باستخدام قاعدة السلسلة.


الرسم البياني الحسابي وحساب المشتقات من خلال قاعدة سلسلة

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

التنفيذ


في القسم السابق ، قمنا بفحص المكونات اللازمة لإنشاء مكتبة تعلم عميقة مصممة لإنشاء وتدريب شامل للشبكات العصبية. من أجل عدم تعقيد المثال ، أقوم بتقليد نمط تصميم مكتبة Caffe هنا . هنا نعلن عن فئتين مجردة - Functionو Optimizer. بالإضافة إلى ذلك ، هناك فئة Tensor، وهي بنية بسيطة تحتوي على صفيفين NumPy متعدد الأبعاد. تم تصميم أحدهما لتخزين قيم المعلمات ، والآخر - لتخزين تدرجاتها. ستكون جميع المعلمات في طبقات مختلفة (عوامل التشغيل) من النوع Tensor. قبل أن نذهب إلى أبعد من ذلك ، ألق نظرة على المخطط العام للمكتبة.


مخطط UML للمكتبة

في وقت كتابة هذه المادة ، تحتوي هذه المكتبة على تنفيذ للطبقة الخطية ، ووظيفة التنشيط ReLU ، وطبقة SoftMaxLoss ، ومحسن SGD. ونتيجة لذلك ، اتضح أنه يمكن استخدام المكتبة لتدريب نماذج التصنيف التي تتكون من طبقات متصلة بالكامل واستخدام وظيفة تنشيط غير خطية. الآن دعونا نلقي نظرة على بعض التفاصيل حول الطبقات المجردة التي لدينا.توفر

فئة مجردةFunctionواجهة لمشغلي. هنا هو رمزه:

class  Function(object):
    def forward(self): 
        raise NotImplementedError
    
    def backward(self): 
        raise NotImplementedError
    
    def getParams(self): 
        return []

يتم تنفيذ جميع العوامل من خلال وراثة فئة مجردة Function. يجب على كل مشغل توفير تنفيذ الأساليب forward()و backward(). قد يحتوي عوامل التشغيل على تنفيذ طريقة اختيارية getParams()تُرجع معلماتها (إن وجدت). forward()تستقبل الطريقة بيانات الإدخال وترجع نتيجة تحويلها بواسطة عامل التشغيل. بالإضافة إلى ذلك ، يحل المشاكل الداخلية اللازمة لحساب التدرجات. backward()تقبل الطريقة المشتقات الجزئية لوظيفة الخسارة فيما يتعلق بمخرجات المشغل وتطبق حساب المشتقات الجزئية لدالة الخسارة فيما يتعلق ببيانات إدخال عامل التشغيل والمعلمات (إن وجدت). لاحظ أن الطريقةbackward()، في الجوهر ، يوفر مكتبتنا القدرة على أداء التمايز التلقائي.

للتعامل مع كل هذا بمثال محدد ، دعنا نلقي نظرة على تنفيذ الوظيفة Linear:

class Linear(Function):
    def __init__(self,in_nodes,out_nodes):
        self.weights = Tensor((in_nodes,out_nodes))
        self.bias    = Tensor((1,out_nodes))
        self.type = 'linear'

    def forward(self,x):
        output = np.dot(x,self.weights.data)+self.bias.data
        self.input = x 
        return output

    def backward(self,d_y):
        self.weights.grad += np.dot(self.input.T,d_y)
        self.bias.grad    += np.sum(d_y,axis=0,keepdims=True)
        grad_input         = np.dot(d_y,self.weights.data.T)
        return grad_input

    def getParams(self):
        return [self.weights,self.bias]

تطبق الطريقة forward()تحويل العرض Y = X*W+bوترجع النتيجة. بالإضافة إلى ذلك ، فإنه يحفظ قيمة المدخلات X، حيث يلزم حساب المشتق الجزئي dYلدالة الخسارة فيما يتعلق بقيمة الإخراج Yفي الطريقة backward(). طريقة backward()يتلقى المشتقات الجزئية، وتحسب فيما يتعلق قيمة المدخلات Xوالمعلمات Wو b. علاوة على ذلك ، تقوم بإرجاع المشتقات الجزئية المحسوبة فيما يتعلق بقيمة المدخلات X، والتي سيتم نقلها إلى الطبقة السابقة. توفر

فئة مجردة Optimizerواجهة للمحسن:

class Optimizer(object):
    def __init__(self,parameters):
        self.parameters = parameters
    
    def step(self): 
        raise NotImplementedError

    def zeroGrad(self):
        for p in self.parameters:
            p.grad = 0.

يتم تنفيذ جميع المُحسّنات عن طريق الوراثة من الفئة الأساسية Optimizer. يجب أن توفر الفئة التي تصف تحسينًا معينًا تنفيذ الطريقة step(). تعمل هذه الطريقة على تحديث معلمات النموذج باستخدام مشتقاتها الجزئية المحسوبة فيما يتعلق بالقيمة المثلى لوظيفة الخسارة. يتم توفير ارتباط إلى معلمات النموذج المختلفة في الوظيفة __init__(). يرجى ملاحظة أنه يتم تنفيذ الوظيفة العالمية لإعادة تعيين قيم التدرج في الفئة الأساسية نفسها.

الآن ، لفهم كل هذا بشكل أفضل ، ضع في اعتبارك مثالًا محددًا - تنفيذ خوارزمية أصل التدرج العشوائي (SGD) مع دعم ضبط الزخم وتقليل الأوزان:

class SGD(Optimizer):
    def __init__(self,parameters,lr=.001,weight_decay=0.0,momentum = .9):
        super().__init__(parameters)
        self.lr           = lr
        self.weight_decay = weight_decay
        self.momentum     = momentum
        self.velocity     = []
        for p in parameters:
            self.velocity.append(np.zeros_like(p.grad))

    def step(self):
        for p,v in zip(self.parameters,self.velocity):
            v = self.momentum*v+p.grad+self.weight_decay*p.data
            p.data=p.data-self.lr*v

حل المشكلة الحقيقية


الآن لدينا كل ما هو ضروري لتدريب نموذج الشبكة العصبية (العميقة) باستخدام مكتبتنا. لهذا نحن بحاجة إلى الكيانات التالية:

  • نموذج: الرسم البياني للحساب.
  • البيانات والقيمة المستهدفة: بيانات للتدريب على الشبكة.
  • وظيفة الخسارة: بديل لهدف التحسين.
  • مُحسِّن: آلية لتحديث معلمات النموذج.

يصف الكود الزائف التالي دورة اختبار نموذجية:

model # 
data,target # 
loss_fn # 
optim #,         
Repeat:#   ,    ,     
   optim.zeroGrad() #    
   output = model.forward(data) #   
   loss   = loss_fn(output,target) # 
   grad   = loss.backward() #      
   model.backward(grad) #    
   optim.step() #  

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

class Model():
    def __init__(self):
        self.computation_graph = []
        self.parameters        = []

    def add(self,layer):
        self.computation_graph.append(layer)
        self.parameters+=layer.getParams()

    def __innitializeNetwork(self):
        for f in self.computation_graph:
            if f.type=='linear':
                weights,bias = f.getParams()
                weights.data = .01*np.random.randn(weights.data.shape[0],weights.data.shape[1])
                bias.data    = 0.

    def fit(self,data,target,batch_size,num_epochs,optimizer,loss_fn):
        loss_history = []
        self.__innitializeNetwork()
        data_gen = DataGenerator(data,target,batch_size)
        itr = 0
        for epoch in range(num_epochs):
            for X,Y in data_gen:
                optimizer.zeroGrad()
                for f in self.computation_graph: X=f.forward(X)
                loss = loss_fn.forward(X,Y)
                grad = loss_fn.backward()
                for f in self.computation_graph[::-1]: grad = f.backward(grad) 
                loss_history+=[loss]
                print("Loss at epoch = {} and iteration = {}: {}".format(epoch,itr,loss_history[-1]))
                itr+=1
                optimizer.step()
        
        return loss_history
    
    def predict(self,data):
        X = data
        for f in self.computation_graph: X = f.forward(X)
        return X

يتضمن هذا الفصل الوظائف التالية:

  • : add() , . computation_graph.
  • : , , , .
  • : fit() . , .
  • : predict() , , .

نظرًا لأن هذا الفصل ليس لبنة البناء الأساسية لأنظمة التعلم العميق ، فقد قمت بتطبيقه في وحدة منفصلة utilities.py. لاحظ أن الطريقة fit()تستخدم فئة DataGeneratorيكون تنفيذها في نفس الوحدة النمطية. هذا الفصل مجرد غلاف لبيانات التدريب وينشئ حزم صغيرة لكل تكرار للتدريب.

تدريب نموذجي


الآن فكر في الجزء الأخير من الكود الذي يتم فيه تدريب نموذج الشبكة العصبية باستخدام المكتبة الموصوفة أعلاه. سأقوم بتدريب شبكة متعددة الطبقات على البيانات مرتبة في شكل حلزوني. لقد دفعني هذا المنشور. يمكن العثور على كود لتوليد هذه البيانات وتصورها في الملف utilities.py.


البيانات مع ثلاث فئات مرتبة في شكل حلزوني ، 

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

import dl_numpy as DL
import utilities

batch_size        = 20
num_epochs        = 200
samples_per_class = 100
num_classes       = 3
hidden_units      = 100
data,target       = utilities.genSpiralData(samples_per_class,num_classes)
model             = utilities.Model()
model.add(DL.Linear(2,hidden_units))
model.add(DL.ReLU())
model.add(DL.Linear(hidden_units,num_classes))
optim   = DL.SGD(model.parameters,lr=1.0,weight_decay=0.001,momentum=.9)
loss_fn = DL.SoftmaxWithLoss()
model.fit(data,target,batch_size,num_epochs,optim,loss_fn)
predicted_labels = np.argmax(model.predict(data),axis=1)
accuracy         = np.sum(predicted_labels==target)/len(target)
print("Model Accuracy = {}".format(accuracy))
utilities.plot2DDataWithDecisionBoundary(data,target,model)

توضح الصورة أدناه نفس البيانات والحدود الحاسمة للنموذج المدرّب.


حدود البيانات والقرارات للنموذج المدرب

ملخص


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

أعتقد أن كل شخص يمكن أن يفرط في المشروع، الكود الذي درسناه هنا ، وكتمرين ، أدخل فيه ما يرغبون في رؤيته فيه. فيما يلي بعض الآليات التي يمكنك محاولة تنفيذها بنفسك:

  • عوامل التشغيل: الالتفاف ، الاختزال الجزئي.
  • محسنات: آدم ، RMSProp.
  • المنظمون: BatchNorm ، DropOut.

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

القراء الأعزاء! ما مكتبات التعلم العميق التي تستخدمها؟

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


All Articles