مقدمة في الشبكات العصبية

تنبيه : هذه المقالة مُترجمة و بالإمكانكم الإطلاع على المقاله الأصلية في مدونة فكتور جو و عنوانها
An Introduction to Neural Networks

نسمع كثيرا عن مصطلح الـ Neural Network أو الشبكات العصبية بالعربية . و هذا المصطلح قد يعطي الإنطباع بالصعوبة و  التقيد و لكن الأمر أسهل بكثير مما قد يتخيلة الكثيرين. و من خلال هذا المقال سنحاول أن نفهم ما هي الشبكات العصبية و سنقاوم كذلك ببنائها من الصفر بإستخدام البايثون.

1- حجر الأساس : العصبونات (Neurons) 

الوحدة الأساسية للشبكات العصبية ( Neural Network ) تعرف بأسم العصبونه (Neurons ) و مهمتها هي أن تأخذ بعض المدخلات و تجري عليها عدة عمليات حسابية و تنتج لنا نتيجة أو مخرج .

رسم توضيحي لتكوين العصبونه

هناك ثلاثة أمور تحدث داخل العصبونه (Neuron ) في الصورة السابقة :  

أولاً : كل مدخل تم ضربه بـ وَزن (Weight): 

 x_{1} \rightarrow x_{1} * w_{1}

 x_{2} \rightarrow x_{2} * w_{2}

ثانياً : كل المدخلات الموزونه تم جمعها مع بعضها و إضافة إنحياز (Bias ) لها : 

 \left(x_{1} * w_{1}\right)+\left(x_{2} * w_{2}\right)+b

ثالثاً : المجموع تم تمريره بدالة تنشيط (Activation Function) : 

 y=f\left(x_{1} * w_{1}+x_{2} * w_{2}+b\right)

الهدف و الحكمة من دالة التنشيط ( Activation Function ) هو تحويل مدخل عير مُقيد إلى مخرج مقيد ذا بشكل يمكن التبؤ به. و أحد أكثر دوال التنشيط رواجاً هي دالة السيجمويد (Sigmoid Function )

دالة السيحمويد

مخرجات دالة السيجمويد تتمثل في أرقام بين 0 و 1 حيث تقوم الدالة بتحويل أي رقم سلبي كبير إلى ~0 و أي رقم موجب كبير إلى ~1  فبالتالي يمكن تصور هذه الدالة بأنها تقوم بضعط أو عصر (-∞ , ∞ ) إلى ( 0 , 1 ) . 

مثال للتوضيح

فلنفترض أن لدينا عصبونه بمدخلين و كذلك نستخدم دالة السيجمويد مع المعطيات التالية

 \begin{array}{c}{\omega=[0,1]} \\ {\quad b=4}\end{array}


 \omega=[0,1] هي طريقة أخرى لكتابة  w_{1}=0, w_{2}=1 في هيئة المتجهات (Vector Form)  ، و الأن لنفترض أن مدخلات العصبونه لديها هذه القيم [X=[2,3 سنستخدم الضرب النقطي (Dot product ) لتبسيط كتابة المعادلات و سهولة قرائتها


 \begin{aligned}(w \cdot x)+b &=\left(\left(w_{1} *  x_{1}\right)+\left(w_{2} * x_{2}\right)\right)+b \\ &=0 * 2+1 * 3+4 \\  &=7 \end{aligned}

و بالتالي مُخرج العصبونه سيكون 0.999 في حال كانت قيم المدخلات X=[2,3]
و تسمى هذه العملية بالتغذية الأمامية (Feed Forward  ) .

برمجة العصبونه ( Neurons) :

في هذه الجزئية نستخدم نمباي ( NumPY ) و هي مكتبة حسابية في البايثون :

import numpy as np

def sigmoid(x):
  # دالة التفعيل
  return 1 / (1 + np.exp(-x))

class Neuron:
    def __init__(self, weights, bias):
        self.weights = weights
        self.bias = bias

    def feedforward(self, inputs):
        # ضرب الأوزان مع المدخلات ثم جمعها مع الإنحياز 
        total = np.dot(self.weights, inputs) + self.bias
        # إستخدام دالة السيجمويد
        return sigmoid(total)

# إستهلال الأوزان و الإنحياز
weights = np.array([0, 1]) # w1 = 0, w2 = 1
bias = 4                   # b = 4
n = Neuron(weights, bias)

# قيم مدخلات العصبونه
x = np.array([2, 3])       # x1 = 2, x2 = 3

# ناتج الشبكة
print(n.feedforward(x))    # 0.9990889488055994

و كما نرى فالنتيجة مشابة للنيجة التي حصلنا عليها سابقا.

2- ضم العصبونات لتكوين شبكة عصبية :  

الشبكة العصيبة (Neural Network ) ليست سوى مجموعة من العصبونات  الموصولة ببعضها البعض . و هنا مثال لشبكة عصبية بسيطة :

رسم توضيحي للشبكة العصبية

هذه الشبكة تتألف من مُدخلين ثم طبقة مخفية ( Hidden Layer ) تتألف من عصبونين ( h1  و h2 ) بالإضافة إلى طبقة المخرجات و التي تحتوي على عصبون (o1)  ، من الجدير بالملاحظة أن مُدخلات O1 هي مُخرجات كٍلا العصبونين h1  و h2 و هذا ما يشكل شبكة.

الطبقة الخفية : هي أي طبقة تقع بين طبقة المدخلات ( الطبقة الأولى) و طبقة المخرجات ( الطبقة الأخيرة ).

مثال : التغذية الأمامية (Feed Forward) :

دعونا نستخدم الشبكة أعلاه و لنتفرض أن كل العصبونات ( Neurons ) لديها نفس الأوزان  w=[0,1]  و نفس الإنحياز b = 0  و نستخدم دالة السيجمويد في جميعها . و لتمثل h1 , h2 ,o1 مخرجات العصبونات التي تمثلها . 

ماذا سيحدث لو مررنا المدخلين x = [2,3]  على هذه الشبكة ؟

 \begin{aligned} h_{1}=h_{2} &=f(w \cdot x+b) \\ &=f((0 * 2)+(1 * 3)+0) \\ &=f(3) \\ &=0.9526 \end{aligned}

\begin{aligned} o_{1} &=f\left(w \cdot\left[h_{1}, h_{2}\right]+b\right) \\ &=f\left(\left(0 * h_{1}\right)+\left(1 * h_{2}\right)+0\right) \\ &=f(0.9526) \\ &=0.7216 \end{aligned}

 وبالتالي نجد أن الشبكة العصبية ستعطينا نتيجة 0.7216 إذا ما كانت مدخلاتها x=[2,3] 

الجدير بالذكر بأن الشبكة العصبية قد تتألف من أي عدد من الطبقات و أي عدد من العصبونات في كل طبقة و لكن تبقى الفكرة نفسها نغذي الشبكة بالمُدخلات من المقدمة عن طريق العصبونات من أجل الحصول على مُخرجات في النهاية . من أجل التبسيط سنستخدم نفس الشبكة في الصورة أعلاه.

برمجة الشبكة العصبية : التعذية الأمامية

فلتقم الأن ببرمجة التقذية الأمامية للشبكة العصبية ، هذه صورة الشبكة العصبية كمرجع.

رسم توضيحي للشبكة العصبية
import numpy as np

# كود من الجزئية السابقة

class OurNeuralNetwork:
    
  '''
  :الشبكة العصبية لديها 
    - مدخلين
    - (h1, h2) طبقة خفية تحتوي على عصبونين
    - (o1) طبقة مخرجات تحتوي على عصبون واحد

  :كل عصبون لديه نفس الأوزان و الإنحياز  
    - w = [0, 1]
    - b = 0
  '''
  def __init__(self):
    
    # إستهلال قيم الأوزان و الإنحياز
    weights = np.array([0, 1])
    bias = 0

    # تم إنشاءه في الجزئية السابقة Neuron class
    self.h1 = Neuron(weights, bias)
    self.h2 = Neuron(weights, bias)
    self.o1 = Neuron(weights, bias)

  def feedforward(self, x):
    out_h1 = self.h1.feedforward(x)
    out_h2 = self.h2.feedforward(x)

    # h2 , h1 هي مخرجات  o1 مدخلات 
    out_o1 = self.o1.feedforward(np.array([out_h1, out_h2]))

    return out_o1

network = OurNeuralNetwork()
x = np.array([2, 3])
print(network.feedforward(x)) # 0.7216325609518421

و حصلنا على نفس النتيجة 0.7216 .

3- تدريب الشبكة العصبية: الجزء الأول

لدينا القياسات التالية :

الأسم  الوزن  ( كج)الطول  (سم)الجنس 
إيمان 70 165 أنثى 
محمد97 172 ذكر 
عبد الفتاح 89 170 ذكر 
أمال 57 160 أنثى 

نرغب في تدريب الشبكة  للتتنبأ بجنس الشخص بناءً على وزنه و طوله

رسم توضيحي للشبكة العصبية

سنمثل الذكر بـ 0 و الأنثى بـ 1 و كما سنقوم تحريك البيانات لنجعل قرائتها أسهل .

الأسم الوزن (ناقص 72) الطول ( ناقص 166) الجنس 
إيمان -2 -1 
محمد25 
عبد الفتاح 17 
أمال -15 -6 

هنا قمنا بإختيار قيم التحريك ( 72و 166) و هي أرقام قمت بإختيارها من عندي لجعل البيانات أسهل للقراءة و التوضيح  لكن في العادة نقوم بتحريك البيانات بناءً على المتوسط (mean ) .

الخسارة (Loss) :

قبل أن ندرب الشبكة العصبية نحن بحاجة لطريقة نخبر بها الشبكة العصبية عن جودة أدائها و إذا ما كان هناك إمكانية لتقوم بعمل أفضل . و هذه وظيفة دالة الخسارة ( Loss function )  

هنالك العديد من دوال الخسارة و هنا سنستخدم دالة تعرف بـ متوسط خطأ التربيع ( Mean Square Error MSE )  

 \mathrm{MSE}=\frac{1}{n} \sum_{i=1}^{n}\left(y_{t r u e}-y_{p r e d}\right)^{2}

فلنقم بتفصيل هذه المعادلة  

  • n  هو عدد الأمثلة ، و هنا لدينا أربعة  
  • y تمثل المتغير الذي نرغب  بالتنبؤ به 
  • Ytrue  تمثل القيمة الحقيقية للمتغير ( الجواب الصحيح ) كمثال  Ytrue بالنسبة لإيمان تعتبر 1 (أنثى) 
  • Ypred تمثل القيمة المتنبأ  بها للمتغير و هي قيمة المخرجات من الشبكة  

تعرف  \left(y_{t r u e}-y_{p r e d}\right)^{2} بـ خطا التربيع ( Squared error ) و ببساطة كل ما تقوم به دالة الخسارة هي حساب المتوسط من ناتج كل الأخطاء التربيعية و لذلك تسمى بـ متوسط خطأ التربيع ( Mean square error ) و كلما كانت تنبؤات الشبكة أفضل كلما كانت قيمة الخسارة قليلة . 

و بالتالي :  تبؤات أفضل = خسارة أقل .

مثال على حساب الخسارة:

فلنفترض أن مخرجات شبكتنا  كانت 0 بمعنى أن الشبكة واثقة بأن كل البشر هم ذكور ، في هذه الحالة ماذا ستكون الخسارة

الأسم ytrue  ypred  ytrue−ypred2   
إيمان 
مدثر 
عبد الفتاح 
أمال 

 \mathrm{MSE}=\frac{1}{4}(1+0+0+1)=0.5

و هذه الخسارة تعتبر كبيرة جداً

برمجة دالة الخسارة  :

هنا سنقوم ببرمجة دالة الخسارة :

import numpy as np

def mse_loss(y_true, y_pred):
  
  return ((y_true - y_pred) ** 2).mean()

y_true = np.array([1, 0, 0, 1])
y_pred = np.array([0, 0, 0, 0])

print(mse_loss(y_true, y_pred)) # 0.5

و النتيجة هي 0.5 .

4- تدريب الشبكة العصبية: الجزء الثاني

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

الأسم الوزن (ناقص 72) الطول ( ناقص 166) الجنس 
إيمان -2 -1 1

و بالتالي متوسط الخطأ التربيعي ( mean square error ) سيصبح الخطأ التربيعي لإيمان سيصبح 

 \begin{aligned} \mathrm{MSE} &=\frac{1}{1} \sum_{i=1}^{1}\left(y_{\text {true}}-y_{\text {pred}}\right)^{2} \\ &=\left(y_{\text {true}}-y_{\text {pred}}\right)^{2} \\ &=\left(1-y_{\text {pred}}\right)^{2} \end{aligned}

طريقة أخرى للتفكير في دالة الخسارة هي بإعتبارها دالة الأوزان و الإنحيازان .   و بالتالي دعونا نميز كل الأوزان و الإنحيازات في شبكتنا : 

الأوزان و الإنحيازات مبينة على الشبكة

و الأن يمكننا أن نكتب الخسارة (Loss) كدالة متعددة المتغيرات ( Multivariable Function  ) :  

 L\left(w_{1}, w_{2}, w_{3}, w_{4}, w_{5}, w_{6}, b_{1}, b_{2}, b_{3}\right)

فلنفترض أننا نرغب بتعديل w1  فكيف سيأثر ذلك على دالة الخسارة L ؟ جواب هذا السؤال يمكن الوصول إليه بإستخدام المشتقات الجزئية لــ  \frac{\partial L}{\partial w_{1}}

حتى نبدأ بالحساب علينا أولا أن نقوم بإعادة ترتيب المشتقة الجزئية نسبة إلى  \frac{\partial y_{p r e d}}{\partial w_{1}}

 \frac{\partial L}{\partial w_{1}}=\frac{\partial L}{\partial y_{p r e d}} * \frac{\partial y_{p r e d}}{\partial w_{1}}
هنا قمنا بإستخدام قاعدة التسلسل ( Chain rule  )

بإمكاننا حساب المشتقة  \frac{\partial L}{\partial y_{p r e d}} لأنه سبق و قمنا و بحساب  L=\left(1-y_{\text {pred}}\right)^{2}

 \frac{\partial L}{\partial y_{\text {pred}}}=\frac{\partial\left(1-y_{\text {pred}}\right)^{2}}{\partial y_{\text {pred}}}=-2\left(1-y_{\text {pred}}\right)

الأن نحن بحاجة لأن نتعامل مع \frac{\partial y_{p r e d}}{\partial w_{1}} و كما فعلنا سابقاً فلنفرض بأن h1 , h2 ,o هم مخرجات العصبونات اللاتي يمثلُهن ، و بالتالي : 

y_{\text {pred}}=o_{1}=f\left(w_{5} h_{1}+w_{6} h_{2}+b_{3}\right)

F هنا هي دالة السيجمويد للتنشيط و \frac{\partial y_{p r e d}}{\partial w_{1}} هي التنبؤ المطلوب و المعروف أيضاً بالمُخرج o1

بما أن w1  تؤثر فقط على h1 يمكننا كتابة الأتي :

 \begin{array}{c}{\frac{\partial y_{p r e d}}{\partial w_{1}}=\frac{\partial y_{p r e d}}{\partial h_{1}} * \frac{\partial h_{1}}{\partial w_{1}}} \\  {\frac{\partial y_{p r e d}}{\partial h_{1}}=\left[w_{5} * f^{\prime}\left(w_{5} h_{1}+w_{6} h_{2}+b_{3}\right)\right.}\end{array}

و سنقوم بالمثل مع \frac{\partial h_{1}}{\partial w_{1}} :

 h_{1}=f\left(w_{1} x_{1}+w_{2} x_{2}+b_{1}\right)

و بالتالي

f^{\prime}(x)=\frac{e^{-x}}{\left(1+e^{-x}\right)^{2}}=f(x) *(1-f(x))

x هنا تمثل الوزن و x تمثل الطول . هذه المرة الثانية التي نرى فيها f'(x) (  والتي تمثل المشتقة لدالة السيجمويد ) ، لذلك هيا نشتقها :

\begin{array}{c}{f(x)=\frac{1}{1+e^{-x}}} \ {f^{\prime}(x)=\frac{e^{-x}}{\left(1+e^{-x}\right)^{2}}=f(x) *(1-f(x))}\end{array}

سنستخدم هذه الصيغة لـ f'(x)  لاحقا. 

و بذلك نكون قد إنتهينا من جميع المعادلات الرياضية و حساباتها بحيث تمكنا من تقسيم  \frac{\partial L}{\partial w_{1}}   لعدة أجزاء بإمكاننا بسهولة حسابتها : 

 \frac{\partial L}{\partial w_{1}}=\frac{\partial L}{\partial y_{p r e d}} * \frac{\partial y_{p r e d}}{\partial h_{1}} * \frac{\partial h_{1}}{\partial w_{1}}

ملاحظة :نظام حساب المشتقات الجزئية عكسيا بمعنى البدء من النهاية و الرجوع للوراء يعرف بـ الإنتشار العكسي (BackPropagation)  .

مثال لحساب المشتقات الجزئية :

فلنفترض أن لدينا  إيمان فقط في بياناتنا

الأسم الوزن (ناقص 72) الطول ( ناقص 166) الجنس 
إيمان -2 -1 

فلنقم أولا بإستهلال قيم الأوزان بـ 1  و قيم الإنحيازات بـ 0 . و من ثم نقوم بعملية  التغذية الأمامية خلال الشبكة كاملة  و بالتالي سنحصل على

 \begin{aligned} h_{1} &=f\left(w_{1} x_{1}+w_{2} x_{2}+b_{1}\right) \\ &=f(-2+-1+0) \\ &=0.0474 \end{aligned}

 h_{2}=f\left(w_{3} x_{1}+w_{4} x_{2}+b_{2}\right)=0.0474

 \begin{aligned} o_{1} &=f\left(w_{5} h_{1}+w_{6} h_{2}+b_{3}\right) \\ &=f(0.0474+0.0474+0) \\ &=0.524 \end{aligned}

و هنا نجد بأن الشبكة أنتجت لنا  y_{\text {pred}}=0.524  وهذه النتيجة في المنتصف فهي لا تفضل الذكر (1) أو الأنثى (0)

و الأن نقوم بحساب المشتقة  \frac{\partial L}{\partial w_{1}} :

 \frac{\partial L}{\partial w_{1}}=\frac{\partial L}{\partial y_{p r e d}} * \frac{\partial y_{p r e d}}{\partial h_{1}} * \frac{\partial h_{1}}{\partial w_{1}}

 \begin{aligned} \frac{\partial L}{\partial y_{\text {pred}}} &=-2\left(1-y_{\text {pred}}\right) \\ &=-2(1-0.524) \\ &=-0.952 \end{aligned}

 \begin{aligned} \frac{\partial y_{\text {pred}}}{\partial h_{1}} &=w_{5} * f^{\prime}\left(w_{5} h_{1}+w_{6} h_{2}+b_{3}\right) \\ &=1 * f^{\prime}(0.0474+0.0474+0) \\ &=f(0.0948) *(1-f(0.0948)) \\ &=0.249 \end{aligned}

 \begin{aligned} \frac{\partial h_{1}}{\partial w_{1}} &=x_{1} * f^{\prime}\left(w_{1} x_{1}+w_{2} x_{2}+b_{1}\right) \\ &=-2 * f^{\prime}(-2+-1+0) \\ &=-2 * f(-3) *(1-f(-3)) \\ &=-0.0904 \end{aligned}

 \begin{aligned} \frac{\partial L}{\partial w_{1}} &=-0.952 * 0.249 *-0.0904 \\ &=0.0214 \end{aligned}

للتذكير فقد قمنا سابقاً  بإشتقاق دالة السيجمويد بهذه الطريقة   f^{\prime}(x)=f(x) *(1-f(x)) .

و هذا يخبرنا بأنه لو زدنا قيمة w1  فستزيد قيمة  الخسارة بشكل طفيف

5- تدريب النزول الإشتقاقي العشوائي ( Stochastic Gradient Descent ) :

الأن أصبح بحوزتنا جميع الأدوات المطلوبة لنقوم بتمرين شبكة عصبية ، و سنقوم بإستخدام خوارزمية تحسين (Optimization Algorthim ) تسمى النزول الإشتقاقي العشوائي ( Stochastic Gradient Descent ) أو SGD بإختصار ، و هذه الخوارزمية تقوم بإخبارنا كيف نغير قيم الوزن و الإنحياز حتى نقلل الخسارة  وهذا هو أهم شيئ بالنسبة لنا . و ببساطة هي معادلة التحديث التالية :

 w_{1} \leftarrow w_{1}-\eta \frac{\partial L}{\partial w_{1}}

 \eta هنا تمثل عدد ثابت يسمى معدل التعلم (Learning rate) و وظيفته التحكم في سرعة التدريب ، و كل ما نقوم به هو طرح قيمة  \eta \frac{\partial L}{\partial w_{1}}  من   w_{1}

  • لو كانت \frac{\partial L}{\partial w_{1}} موجبة ،  w_{1} ستنقص ، و هذا سيودي إلى إنقاص الخسارة L
  • لو كانت \frac{\partial L}{\partial w_{1}} سالبة ،  w_{1} ستزيد ، و هذا سيودي إلى إنقاص الخسارة L

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

و الطريقة التي نتبعها في عملية التدريب كالتالي :  

  1. نقوم بإختيار عينة واحدة من بياناتنا  ، و لهذا السبب تسمى الخوارزمية بالعشوائية ( Stochastic ) لأننا نعمل على عينه واحده. 
  2. نقوم بحساب المشتقات الجزئية للخسارة نسبةً إلى الأوزان و الإنحياز . كمثال (  \frac{\partial L}{\partial w_{1}}, \frac{\partial L}{\partial w_{2}}   ……إلخ )
  3. بعد ذلك نقوم بإستخدام معادلة التحديث من أجل تحديث كل وزن و إنحياز. 
  4. نكرر الخطوات من البداية.    

برمجة شبكة عصبية متكاملة :

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

الأسم الوزن (ناقص 72) الطول ( ناقص 166) الجنس 
إيمان -2 -1 
مدثر 25 
عبد الفتاح 17 
أمال -15 -6 
الشبكة العصبية مع تحديد الأوزان
import numpy as np

def sigmoid(x):
  #  f(x) = 1 / (1 + e^(-x)) دالة السيجمويد 
  return 1 / (1 + np.exp(-x))

def deriv_sigmoid(x):
  # f'(x) = f(x) * (1 - f(x)) : مشتقة السيجمويد
  fx = sigmoid(x)
  return fx * (1 - fx)

def mse_loss(y_true, y_pred):
  # بنفس الطول arrays عبارة عن  y_true و  y_pred 
  return ((y_true - y_pred) ** 2).mean()

class OurNeuralNetwork:
  '''
  :الشبكة العصبية لديها
    - مدخلين
    - (h1, h2) طبقة خفية تحتوي على عصبونين
    - (o1) طبقة مخرجات تحتوي على عصبون واحد

  '''
  def __init__(self):
    # الأوزان
    self.w1 = np.random.normal()
    self.w2 = np.random.normal()
    self.w3 = np.random.normal()
    self.w4 = np.random.normal()
    self.w5 = np.random.normal()
    self.w6 = np.random.normal()

    # الإنحياز
    self.b1 = np.random.normal()
    self.b2 = np.random.normal()
    self.b3 = np.random.normal()

  def feedforward(self, x):
    # لها عنصرين array هي  x 
    h1 = sigmoid(self.w1 * x[0] + self.w2 * x[1] + self.b1)
    h2 = sigmoid(self.w3 * x[0] + self.w4 * x[1] + self.b2)
    o1 = sigmoid(self.w5 * h1 + self.w6 * h2 + self.b3)
    return o1

  def train(self, data, all_y_trues):
    '''
   تساوي عدد الأمثلة n و  (n x 2) حجمها  numpy array البيانات هي 
   عناصر   n لها  numpy array هي all_y_trues
    تمثل البيانات  all_y_trues العناصر في 
    '''
    learn_rate = 0.1
    epochs = 1000 # عدد الدورات خلال البيانات

    for epoch in range(epochs):
      for x, y_true in zip(data, all_y_trues):
        # --- التفذية الأمامية و التي سنحتاجها لاحقاً
        sum_h1 = self.w1 * x[0] + self.w2 * x[1] + self.b1
        h1 = sigmoid(sum_h1)

        sum_h2 = self.w3 * x[0] + self.w4 * x[1] + self.b2
        h2 = sigmoid(sum_h2)

        sum_o1 = self.w5 * h1 + self.w6 * h2 + self.b3
        o1 = sigmoid(sum_o1)
        y_pred = o1

        # --- .حساب المشتقات الجزئيى
        d_L_d_ypred = -2 * (y_true - y_pred)

        # o1 العصبون
        d_ypred_d_w5 = h1 * deriv_sigmoid(sum_o1)
        d_ypred_d_w6 = h2 * deriv_sigmoid(sum_o1)
        d_ypred_d_b3 = deriv_sigmoid(sum_o1)

        d_ypred_d_h1 = self.w5 * deriv_sigmoid(sum_o1)
        d_ypred_d_h2 = self.w6 * deriv_sigmoid(sum_o1)

        # h1 العصبون
        d_h1_d_w1 = x[0] * deriv_sigmoid(sum_h1)
        d_h1_d_w2 = x[1] * deriv_sigmoid(sum_h1)
        d_h1_d_b1 = deriv_sigmoid(sum_h1)

        # h2 العصبون
        d_h2_d_w3 = x[0] * deriv_sigmoid(sum_h2)
        d_h2_d_w4 = x[1] * deriv_sigmoid(sum_h2)
        d_h2_d_b2 = deriv_sigmoid(sum_h2)

        # --- تحديث الأوزان و الإنحياز
        # h1 العصبون
        self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w1
        self.w2 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w2
        self.b1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1

        # h2 العصبون
        self.w3 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w3
        self.w4 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w4
        self.b2 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_b2

        # O1 العصبون
        self.w5 -= learn_rate * d_L_d_ypred * d_ypred_d_w5
        self.w6 -= learn_rate * d_L_d_ypred * d_ypred_d_w6
        self.b3 -= learn_rate * d_L_d_ypred * d_ypred_d_b3

      # --- epoch حساب الخسارة بعد كل
      if epoch % 10 == 0:
        y_preds = np.apply_along_axis(self.feedforward, 1, data)
        loss = mse_loss(all_y_trues, y_preds)
        print("Epoch %d loss: %.3f" % (epoch, loss))

# التغريف بالبيانات
data = np.array([
  [-2, -1],  # إيمان
  [25, 6],   # مدثر
  [17, 4],   # عبد الفتاح
  [-15, -6], # أمال
])
all_y_trues = np.array([
  1, # إيمان
  0, # مدثر
  0, # عبد الفتاح
  1, # أمال
])

# تدريب الشبكة العصبية
network = OurNeuralNetwork()
network.train(data, all_y_trues)

بوسعكم تجريب الكود هنا و كما يتوفر الكود على github

نلاحظ أن الخسارة تتناقص بشكل ثابت و تدرجي بينما تتعلم الشبكة :

الأن بمقدورنا إستخدام الشبكة للتنبؤ بالجنس :

# القيام بالتنبؤ
Hala = np.array([-7, -3]) # 65 kg, 163 cm
Khaled = np.array([20, 2])  # 92 kg, 168 cm
print("Hala: %.3f" % network.feedforward(Hala)) # 0.951 - F
print("Khaled: %.3f" % network.feedforward(Khaled)) # 0.039 - M

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


إضافة تعليق