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

Home PDF

لنناقش مباشرة جوهر العمل العصبي. بمعنى آخر، خوارزمية الانتشار العكسي (backpropagation):

  1. إدخال x: قم بتعيين التنشيط المقابل \(a^{1}\) للطبقة المدخلة.
  2. التغذية الأمامية: لكل l=2,3,…,L احسب \(z^{l} = w^l a^{l-1}+b^l\) و \(a^{l} = \sigma(z^{l})\)
  3. خطأ الإخراج \(\delta^{L}\): احسب المتجه \(\delta^{L} = \nabla_a C \odot \sigma'(z^L)\)
  4. نشر الخطأ للخلف: لكل l=L−1,L−2,…,2، احسب \(\delta^{l} = ((w^{l+1})^T \delta^{l+1}) \odot \sigma'(z^{l})\)
  5. الإخراج: يتم إعطاء تدرج دالة التكلفة بواسطة \(\frac{\partial C}{\partial w^l_{jk}} = a^{l-1}_k \delta^l_j\) و \(\frac{\partial C}{\partial b^l_j} = \delta^l_j\)

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

الإدخال

هناك 5 مراحل. المرحلة الأولى هي الإدخال. هنا نستخدم الأرقام المكتوبة بخط اليد كمدخلات. مهمتنا هي التعرف عليها. كل رقم مكتوب بخط اليد يحتوي على 784 بكسل، أي 28*28. في كل بكسل، هناك قيمة تدرج رمادي تتراوح من 0 إلى 255. لذا، فإن التفعيل يعني أننا نستخدم بعض الدوال لتفعيلها، لتغيير قيمتها الأصلية إلى قيمة جديدة لتسهيل المعالجة.

لنفترض أن لدينا الآن 1000 صورة، كل صورة تحتوي على 784 بكسل. نقوم الآن بتدريب البرنامج على التعرف على الرقم الذي تعرضه هذه الصور. لدينا الآن 100 صورة لاختبار تأثير هذا التعلم. إذا استطاع البرنامج التعرف على أرقام 97 صورة، نقول إن دقته هي 97%.

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

نتيجة تدريب دفعة واحدة يجب أن تنعكس على 10 خلايا عصبية. هنا، تمثل الخلايا العصبية العشرة الأرقام من 0 إلى 9، وقيمتها تتراوح من 0 إلى 1 للإشارة إلى مدى ثقتها في دقتها.

والإدخال يتكون من 784 خلية عصبية. كيف يمكننا تقليل 784 خلية عصبية إلى 10 خلايا عصبية؟ إليكم الأمر. لنفترض أن لدينا طبقتين. ماذا تعني الطبقة؟ هذه هي الطبقة الأولى، لدينا فيها 784 خلية عصبية. وفي الطبقة الثانية، لدينا 10 خلايا عصبية.

نعطي كل خلية عصبية في الـ784 خلية عصبية وزنًا، لنقل،

\[w_1, w_2, w_3, w_4, ... , w_{784}\]

وأعطِ الطبقة الأولى تحيزًا، أي \(b_1\).

وبالتالي، بالنسبة للعصبون الأول في الطبقة الثانية، تكون قيمته:

\[w_1*a_1 + w_2*a_2+...+ w_{784}*a_{784}+b_1\]

لكن هذه الأوزان والانحياز هي لـ \(neuron^2_{1}\) (العصبون الأول في الطبقة الثانية). بالنسبة لـ \(neuron^2_{2}\)، نحتاج إلى مجموعة أخرى من الأوزان والانحياز.

ماذا عن دالة السيجمويد؟ نستخدم دالة السيجمويد لتحويل القيمة السابقة إلى نطاق من 0 إلى 1.

\[\begin{eqnarray} \sigma(z) \equiv \frac{1}{1+e^{-z}} \end{eqnarray}\] \[\begin{eqnarray} \frac{1}{1+\exp(-\sum_j w_j x_j-b)} \end{eqnarray}\]

نستخدم أيضًا دالة السيجمويد (sigmoid) لتفعيل الطبقة الأولى. بهذا، نقوم بتحويل قيمة التدرج الرمادي إلى نطاق من 0 إلى 1. لذا الآن، كل عصبون في كل طبقة له قيمة تتراوح من 0 إلى 1.

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

لدينا 784 * 10 أوزان و10 انحيازات. في الطبقة الثانية، لكل خلية عصبية، سنستخدم 784 وزنًا وانحيازًا واحدًا لحساب قيمتها. الكود هنا يشبه،

    def __init__(self, sizes):
        self.num_layers = len(sizes)
        self.sizes = sizes
        self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
        self.weights = [np.random.randn(y, x)
                        for x, y in zip(sizes[:-1], sizes[1:])]

التغذية الأمامية (Feedforward)

التغذية الأمامية: لكل ( l = 2, 3, \ldots, L ) احسب
[ z^{l} = w^l a^{l-1} + b^l ]
و
[ a^{l} = \sigma(z^{l}) ]

لاحظ هنا أننا نستخدم قيمة الطبقة الأخيرة، وهي \(a^{l-1}\)، بالإضافة إلى وزن الطبقة الحالية \(w^l\) والانحياز الخاص بها \(b^l\)، لإجراء دالة السيجمويد للحصول على قيمة الطبقة الحالية \(a^{l}\).

الكود:

        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        # التغذية الأمامية
        activation = x
        activations = [x] 
        zs = [] 
        for b, w in zip(self.biases, self.weights):
            z = np.dot(w, activation)+b
            zs.append(z)
            activation = sigmoid(z)
            activations.append(activation)

خطأ الإخراج

خطأ الإخراج \(\delta^{L}\): حساب المتجه \(\delta^{L} = \nabla_a C \odot \sigma'(z^L)\)

لنرى ما يعنيه الرمز \(\nabla\).

دِل، أو نابلا، هو عامل يستخدم في الرياضيات (وخاصة في حساب المتجهات) كعامل تفاضلي متجه، وعادة ما يتم تمثيله بالرمز نابلا ∇.

\[\begin{eqnarray} w_k & \rightarrow & w_k' = w_k-\eta \frac{\partial C}{\partial w_k} \\ b_l & \rightarrow & b_l' = b_l-\eta \frac{\partial C}{\partial b_l} \end{eqnarray}\]

هنا \(\eta\) هو معدل التعلم. نستخدم المشتقة التي تربط C بالأوزان والانحياز، وهي معدل التغير بينهما. وهذا هو sigmoid_prime في الأسفل.

الكود:

        delta = self.cost_derivative(activations[-1], y) * \
            sigmoid_prime(zs[-1])
        nabla_b[-1] = delta
        nabla_w[-1] = np.dot(delta, activations[-2].transpose())

تم ترجمة الكود أعلاه إلى العربية كما يلي:

        delta = self.cost_derivative(activations[-1], y) * \
            sigmoid_prime(zs[-1])
        nabla_b[-1] = delta
        nabla_w[-1] = np.dot(delta, activations[-2].transpose())

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

    def cost_derivative(self, output_activations, y):
        return (output_activations-y)

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

نشر الخطأ للخلف (Backpropagate the error)

انتشار الخطأ للخلف: لكل طبقة ( l = L-1, L-2, \ldots, 2 )، احسب \(\delta^{l} = ((w^{l+1})^T \delta^{l+1}) \odot \sigma'(z^{l})\)

     for l in range(2, self.num_layers):
            z = zs[-l]
            sp = sigmoid_prime(z)
            delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
            nabla_b[-l] = delta
            nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
        return (nabla_b, nabla_w)

ملاحظة: الكود أعلاه مكتوب بلغة Python ويستخدم مكتبة NumPy. لا يتم ترجمة الكود البرمجي، حيث أنه يجب أن يبقى كما هو لضمان استمرارية العمل بشكل صحيح.

الإخراج

الناتج: يتم إعطاء تدرج دالة التكلفة بواسطة \(\frac{\partial C}{\partial w^l_{jk}} = a^{l-1}_k \delta^l_j\) و \(\frac{\partial C}{\partial b^l_j} = \delta^l_j\)

    def update_mini_batch(self, mini_batch, eta):
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        for x, y in mini_batch:
            delta_nabla_b, delta_nabla_w = self.backprop(x, y)
            nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
            nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
        self.weights = [w-(eta/len(mini_batch))*nw
                        for w, nw in zip(self.weights, nabla_w)]
        self.biases = [b-(eta/len(mini_batch))*nb
                       for b, nb in zip(self.biases, nabla_b)]

النهائي

إنها مقالة قصيرة. وفي معظمها، تعرض فقط الكود والصيغ الرياضية. ولكن هذا جيد بالنسبة لي. قبل كتابتها، لم أكن أفهمها بوضوح. بعد الكتابة أو مجرد نسخ المقاطع من الكود والكتاب، فهمت معظمها. بعد اكتساب الثقة من المعلم Yin Wang، وقراءة حوالي 30% من كتاب الشبكات العصبية والتعلم العميق، والاستماع إلى محاضرات Andrej Karpathy في ستانفورد ودورات Andrew Ng، والمناقشة مع صديقي Qi، والتعديل على مكتبات Anaconda وnumpy وTheano لجعل الكود يعمل منذ سنوات، فهمتها الآن.

إحدى النقاط الرئيسية هي الأبعاد. يجب أن نعرف أبعاد كل رمز ومتغير. وهي تقوم فقط بالحسابات القابلة للتفاضل. لننهي باقتباس من Yin Wang:

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

لا يوجد ‘ذكاء’ في الذكاء الاصطناعي، ولا ‘عصبي’ في الشبكات العصبية، ولا ‘تعلم’ في تعلم الآلة، ولا ‘عمق’ في التعلم العميق. ما يعمل حقًا في هذا المجال يُسمى ‘حساب التفاضل’. لذلك أفضل أن أسمي هذا المجال ‘الحوسبة التفاضلية’، وعملية بناء النماذج تُسمى ‘البرمجة التفاضلية’.


Back 2025.01.18 Donate