العديد من تطبيقات الانحدار المتدرج في TensorFlow

نشرت: 2022-03-11

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

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

حالات استخدام TensorFlow

  • مثال 1: الانحدار الخطي مع الانحدار المتدرج في TensorFlow 2.0
    • ما هو الانحدار المتدرج؟
  • مثال 2: متجهات وحدة الانتشار القصوى
  • مثال 3: إنشاء مدخلات ذكاء اصطناعي معادية
  • الأفكار النهائية: تحسين الانحدار المتدرج
  • الانحدار المتدرج في TensorFlow: من البحث عن الحد الأدنى إلى مهاجمة أنظمة الذكاء الاصطناعي

مثال 1: الانحدار الخطي مع الانحدار المتدرج في TensorFlow 2.0

مثال 1 مفكرة

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

ما هو الانحدار المتدرج؟

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

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

هذا ما يبدو عليه في مشكلة انحدار خطي بسيطة. لدينا عينة من الارتفاعات (h) والأوزان (w) لـ 150 ذكرًا بالغًا ، ونبدأ بتخمين غير كامل للانحدار والانحراف المعياري لهذا الخط. بعد حوالي 15 تكرارًا من النسب المتدرج ، وصلنا إلى حل شبه مثالي.

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

لنرى كيف أنتجنا الحل أعلاه باستخدام TensorFlow 2.0.

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

w-subscript-i ، القيمة المسبقة تساوي alpha dot-product h-subscript-i plus beta.

نريد إيجاد المعلمات α و (الميل والاعتراض) التي تقلل من متوسط ​​الخطأ التربيعي (الخسارة) بين التنبؤات والقيم الحقيقية. لذا فإن دالة الخسارة (في هذه الحالة ، "متوسط ​​الخطأ التربيعي" أو MSE) تبدو كما يلي:

يساوي MSE واحدًا على N مضروبًا في المجموع من i يساوي واحدًا إلى N من مربع الفرق بين w-subscript-i و true و w-subscript-i ، pred.

يمكننا أن نرى كيف يبحث متوسط ​​الخطأ التربيعي عن سطرين غير كاملين ، ثم بالحل الدقيق (α = 6.04 ، β = -230.5).

ثلاث نسخ من نفس مخطط الطول والوزن المبعثر ، ولكل منها خط ملائم مختلف. الأول لديه w = 4.00 * h + -120.0 وخسارة 1057.0 ؛ الخط أسفل البيانات وأقل حدة منه. الثانية لديها w = 2.00 * h + 70.0 وخسارة 720.8 ؛ الخط بالقرب من الجزء العلوي من نقاط البيانات ، وحتى أقل حدة. الطائر لديه w = 60.4 * h + -230.5 وخسارة 127.1 ؛ يمر الخط عبر نقاط البيانات بحيث تظهر متجمعة بشكل متساوٍ حوله.

دعنا نضع هذه الفكرة موضع التنفيذ مع TensorFlow. أول شيء يجب فعله هو ترميز دالة الخسارة باستخدام وظائف الموترات و tf.* .

 def calc_mean_sq_error(heights, weights, slope, intercept): predicted_wgts = slope * heights + intercept errors = predicted_wgts - weights mse = tf.reduce_mean(errors**2) return mse

هذا يبدو واضحًا جدًا. جميع المعامِلات الجبرية القياسية مثقلة بالموترات ، لذلك علينا فقط التأكد من أن المتغيرات التي نقوم بتحسينها هي موترات ، ونستخدم طرق tf.* لأي شيء آخر.

بعد ذلك ، كل ما علينا فعله هو وضع هذا في حلقة نزول متدرج:

 def run_gradient_descent(heights, weights, init_slope, init_icept, learning_rate): # Any values to be part of gradient calcs need to be vars/tensors tf_slope = tf.Variable(init_slope, dtype='float32') tf_icept = tf.Variable(init_icept, dtype='float32') # Hardcoding 25 iterations of gradient descent for i in range(25): # Do all calculations under a "GradientTape" which tracks all gradients with tf.GradientTape() as tape: tape.watch((tf_slope, tf_icept)) # This is the same mean-squared-error calculation as before predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors**2) # Auto-diff magic! Calcs gradients between loss calc and params dloss_dparams = tape.gradient(loss, [tf_slope, tf_icept]) # Gradients point towards +loss, so subtract to "descend" tf_slope = tf_slope - learning_rate * dloss_dparams[0] tf_icept = tf_icept - learning_rate * dloss_dparams[1]

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

  1. ينشئ TensorFlow رسمًا بيانيًا حسابيًا لكل عملية حسابية يتم إجراؤها تحت tf.GradientTape() .
  2. يعرف TensorFlow كيفية حساب المشتقات (التدرجات) لكل عملية ، بحيث يمكنه تحديد كيفية تأثير أي متغير في الرسم البياني للحساب على أي متغير آخر.

كيف تبدو العملية من نقاط البداية المختلفة؟

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

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

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

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

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

مثال 2: متجهات وحدة الانتشار القصوى

مثال 2 مفكرة

يعتمد هذا المثال التالي على تمرين تعليمي عميق ممتع في دورة تعليمية عميقة أخذتها العام الماضي.

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

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

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

سنبدأ بمجموعة عشوائية من 20 متجهًا كما هو موضح أعلاه ونجرب ثلاث وظائف خسارة مختلفة ، كل واحدة مع زيادة التعقيد ، لإظهار قدرات TensorFlow.

لنحدد أولاً حلقة التدريب الخاصة بنا. سنضع كل منطق TensorFlow تحت طريقة self.calc_loss() ، وبعد ذلك يمكننا ببساطة تجاوز هذه الطريقة لكل تقنية ، وإعادة تدوير هذه الحلقة.

 # Define the framework for trying different loss functions # Base class implements loop, sub classes override self.calc_loss() class VectorSpreadAlgorithm: # ... def calc_loss(self, tensor2d): raise NotImplementedError("Define this in your derived class") def one_iter(self, i, learning_rate): # self.vecs is an 20x2 tensor, representing twenty 2D vectors tfvecs = tf.convert_to_tensor(self.vecs, dtype=tf.float32) with tf.GradientTape() as tape: tape.watch(tfvecs) loss = self.calc_loss(tfvecs) # Here's the magic again. Derivative of spread with respect to # input vectors gradients = tape.gradient(loss, tfvecs) self.vecs = self.vecs - learning_rate * gradients

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

 class VectorSpread_Maximize_Min_Angle(VectorSpreadAlgorithm): def calc_loss(self, tensor2d): angle_pairs = tf.acos(tensor2d @ tf.transpose(tensor2d)) disable_diag = tf.eye(tensor2d.numpy().shape[0]) * 2 * np.pi spread_metric = tf.reduce_min(angle_pairs + disable_diag) # Convention is to return a quantity to be minimized, but we want # to maximize spread. So return negative spread return -spread_metric

بعض سحر Matplotlib سوف ينتج عنه تخيل.

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

هذا عالي الكعب (بالمعنى الحرفي للكلمة!) لكنه يعمل. يتم تحديث اثنين فقط من المتجهين العشرين في وقت واحد ، مما يؤدي إلى زيادة المسافة بينهما حتى لا يصبحوا الأقرب ، ثم التبديل إلى زيادة الزاوية بين المتجهين الأقرب. الشيء المهم الذي يجب ملاحظته هو أنه يعمل . نرى أن TensorFlow كان قادرًا على تمرير التدرجات من خلال طريقة tf.reduce_min() وطريقة tf.acos() للقيام بالشيء الصحيح.

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

 class VectorSpread_MaxMinAngle_w_Variance(VectorSpreadAlgorithm): def spread_metric(self, tensor2d): """ Assumes all rows already normalized """ angle_pairs = tf.acos(tensor2d @ tf.transpose(tensor2d)) disable_diag = tf.eye(tensor2d.numpy().shape[0]) * 2 * np.pi all_mins = tf.reduce_min(angle_pairs + disable_diag, axis=1) # Same calculation as before: find the min-min angle min_min = tf.reduce_min(all_mins) # But now also calculate the variance of the min angles vector avg_min = tf.reduce_mean(all_mins) var_min = tf.reduce_sum(tf.square(all_mins - avg_min)) # Our spread metric now includes a term to minimize variance spread_metric = min_min - 0.4 * var_min # As before, want negative spread to keep it a minimization problem return -spread_metric 

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

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

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

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

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

أولاً ، نحتاج إلى تحديد الطريقة التي تحسب القوة باستخدام طرق tf.* :

 class VectorSpread_Force(VectorSpreadAlgorithm): def force_a_onto_b(self, vec_a, vec_b): # Calc force assuming vec_b is constrained to the unit sphere diff = vec_b - vec_a norm = tf.sqrt(tf.reduce_sum(diff**2)) unit_force_dir = diff / norm force_magnitude = 1 / norm**2 force_vec = unit_force_dir * force_magnitude # Project force onto this vec, calculate how much is radial b_dot_f = tf.tensordot(vec_b, force_vec, axes=1) b_dot_b = tf.tensordot(vec_b, vec_b, axes=1) radial_component = (b_dot_f / b_dot_b) * vec_b # Subtract radial component and return result return force_vec - radial_component

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

 def calc_loss(self, tensor2d): n_vec = tensor2d.numpy().shape[0] all_force_list = [] for this_idx in range(n_vec): # Accumulate force of all other vecs onto this one this_force_list = [] for other_idx in range(n_vec): if this_idx == other_idx: continue this_vec = tensor2d[this_idx, :] other_vec = tensor2d[other_idx, :] tangent_force_vec = self.force_a_onto_b(other_vec, this_vec) this_force_list.append(tangent_force_vec) # Use list of all N-dimensional force vecs. Stack and sum. sum_tangent_forces = tf.reduce_sum(tf.stack(this_force_list)) this_force_mag = tf.sqrt(tf.reduce_sum(sum_tangent_forces**2)) # Accumulate all magnitudes, should all be zero at optimal solution all_force_list.append(this_force_mag) # We want to minimize total force sum, so simply stack, sum, return return tf.reduce_sum(tf.stack(all_force_list)) 

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

لا يعمل الحل بشكل جميل فقط (إلى جانب بعض الفوضى في الإطارات القليلة الأولى) ، ولكن الفضل الحقيقي يعود إلى TensorFlow. تضمن هذا الحل العديد من حلقات for ، وبيان if ، وشبكة ضخمة من الحسابات ، وقد نجح TensorFlow في تتبع التدرجات خلال كل ذلك بالنسبة لنا.

مثال 3: إنشاء مدخلات ذكاء اصطناعي معادية

مثال 3 مفكرة

في هذه المرحلة ، قد يفكر القراء ، "مرحبًا! لم يكن من المفترض أن يكون هذا المنشور حول التعلم العميق!" لكن من الناحية الفنية ، تشير المقدمة إلى الذهاب إلى ما هو أبعد من " تدريب نماذج التعلم العميق". في هذه الحالة ، نحن لا نتدرب ، ولكن بدلاً من ذلك نستغل بعض الخصائص الرياضية لشبكة عصبية عميقة مُدرَّبة مسبقًا لخداعها لإعطائنا نتائج خاطئة. تبين أن هذا أسهل بكثير وأكثر فاعلية مما كان يتصور. وكل ما تطلبه الأمر هو فقاعة قصيرة أخرى من كود TensorFlow 2.0.

نبدأ بإيجاد مصنف صور للهجوم. سنستخدم أحد أفضل الحلول لمسابقة Dogs vs.Cats Kaggle ؛ على وجه التحديد ، الحل الذي قدمه Kaggler "uysimty". كل الفضل لهم في تقديم نموذج قطة مقابل كلب فعال وتقديم وثائق رائعة. هذا نموذج قوي يتكون من 13 مليون معلمة عبر 18 طبقة شبكة عصبية. (القراء مدعوون لقراءة المزيد عنها في دفتر الملاحظات المقابل.)

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

ذات صلة: منطق الصوت ونماذج الذكاء الاصطناعي الرتيبة

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

خمس صور نموذجية ، لكل كلب أو قطة ، مع تصنيف ومستوى ثقة مطابقين. تتراوح مستويات الثقة المعروضة من 95 بالمائة إلى 100 بالمائة.

هذا يبدو وكأنه مصنف قوي حقا! جميع تصنيفات العينة صحيحة وثقة أعلى من 95٪. دعونا نهاجمها!

نريد إنتاج صورة من الواضح أنها قطة ولكن جعل المصنف يقرر أنها كلب يتمتع بثقة عالية. كيف يمكننا فعل ذلك؟

لنبدأ بصورة قطة تصنفها بشكل صحيح ، ثم نكتشف كيف تؤثر التعديلات الصغيرة في كل قناة لونية (القيم 0-255) لبكسل إدخال معين على إخراج المصنف النهائي. من المحتمل ألا يؤدي تعديل بكسل واحد إلى الكثير ، ولكن ربما ستحقق التعديلات التراكمية لجميع قيم 128 × 128 × 3 = 49152 بكسل هدفنا.

كيف نعرف الطريقة التي ندفع بها كل بكسل؟ أثناء التدريب العادي للشبكة العصبية ، نحاول تقليل الخسارة بين التسمية المستهدفة والتسمية المتوقعة ، باستخدام النسب المتدرج في TensorFlow لتحديث جميع المعلمات المجانية البالغ عددها 13 مليونًا في نفس الوقت. في هذه الحالة ، سنترك 13 مليون معلمة ثابتة ، ونعدل قيم البكسل للمدخل نفسه.

ما هي وظيفة الخسارة لدينا؟ حسنًا ، كم تبدو الصورة مثل قطة! إذا قمنا بحساب مشتق قيمة cat فيما يتعلق بكل بكسل إدخال ، فإننا نعرف الطريقة التي ندفع بها كل منها لتقليل احتمالية تصنيف cat.

 def adversarial_modify(victim_img, to_dog=False, to_cat=False): # We only need four gradient descent steps for i in range(4): tf_victim_img = tf.convert_to_tensor(victim_img, dtype='float32') with tf.GradientTape() as tape: tape.watch(tf_victim_img) # Run the image through the model model_output = model(tf_victim_img) # Minimize cat confidence and maximize dog confidence loss = (model_output[0] - model_output[1]) dloss_dimg = tape.gradient(loss, tf_victim_img) # Ignore gradient magnitudes, only care about sign, +1/255 or -1/255 pixels_w_pos_grad = tf.cast(dloss_dimg > 0.0, 'float32') / 255. pixels_w_neg_grad = tf.cast(dloss_dimg < 0.0, 'float32') / 255. victim_img = victim_img - pixels_w_pos_grad + pixels_w_neg_grad

يساعد سحر Matplotlib مرة أخرى على تصور النتائج.

عينة أصلية لصورة قطة مع 4 تكرارات مع التصنيفات "Cat 99.0٪" و "Cat 67.3٪" و "Dog 71.7٪" و "Dog 94.3٪" و "Dog 99.4٪" على التوالي.

رائع! بالنسبة للعين البشرية ، كل واحدة من هذه الصور متطابقة. ومع ذلك ، بعد أربع تكرارات ، أقنعنا المصنف بأنه كلب ، بثقة تصل إلى 99.4٪!

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

عينة أصلية لصورة كلب مع 4 تكرارات ، مع التصنيفات "كلب 98.4٪" ، "كلب 83.9٪" ، "كلب 54.6٪" ، "كات 90.4٪" و "كات 99.8٪ ،" على التوالي. كما كان من قبل ، فإن الاختلافات غير مرئية للعين المجردة.

نجاح! توقع المصنف هذا في الأصل بشكل صحيح عندما كان كلبًا يتمتع بثقة 98.4٪ ، وهو يعتقد الآن أنه قطة تتمتع بثقة تبلغ 99.8٪.

أخيرًا ، دعنا نلقي نظرة على عينة تصحيح صورة ونرى كيف تغيرت.

ثلاث شبكات من صفوف وأعمدة البكسل ، تعرض القيم الرقمية للقناة الحمراء لكل بكسل. يُظهر تصحيح الصورة الأيسر في الغالب مربعات مزرقة ، مع تمييز قيم 218 أو أقل ، مع بعض المربعات الحمراء (219 وما فوق) متجمعة في الزاوية اليمنى السفلية. تُظهر صفحة الصورة "المُضحية" في المنتصف تخطيطًا مُرقّمًا وملونًا بشكل متشابه جدًا. يُظهر التصحيح الأيمن للصورة الفرق العددي بين الاثنين الآخرين ، مع وجود اختلافات تتراوح فقط من -4 إلى +4 ، بما في ذلك عدة أصفار.

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

الأفكار النهائية: تحسين الانحدار المتدرج

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

هناك العديد من المحسّنين المشهورين ، بما في ذلك RMSprop و Adagrad و Adadelta ، ولكن ربما يكون آدم هو الأكثر شيوعًا. في بعض الأحيان ، يطلق عليهم "طرق معدل التعلم التكيفي" لأنها تحافظ ديناميكيًا على معدل تعلم مختلف لكل معلمة. يستخدم الكثير منهم مصطلحات الزخم والمشتقات التقريبية ذات الترتيب الأعلى ، بهدف الهروب من الحدود الدنيا المحلية وتحقيق تقارب أسرع.

في رسم متحرك مستعار من سيباستيان رودر ، يمكننا أن نرى مسار المحسّنين المختلفين ينزلون على سطح الخسارة. التقنيات اليدوية التي أظهرناها هي الأكثر قابلية للمقارنة مع "SGD". لن يكون المحسن الأفضل أداءً هو نفسه لكل سطح خاسر ؛ ومع ذلك ، فإن المحسّنين الأكثر تقدمًا يحققون أداءً أفضل عادةً من المحسّنين الأبسط.

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

ومع ذلك ، نادرًا ما يكون من المفيد أن تكون خبيرًا في المحسّنين - حتى لأولئك الحريصين على تقديم خدمات تطوير الذكاء الاصطناعي. إنه استخدام أفضل لوقت المطورين للتعرف على الزوجين ، فقط لفهم كيفية تحسين النسب المتدرجة في TensorFlow. بعد ذلك ، يمكنهم فقط استخدام Adam افتراضيًا وتجربة نماذج مختلفة فقط إذا كانت نماذجهم غير متقاربة.

للقراء المهتمين حقًا بكيفية ولماذا تعمل هذه المحسّنات ، نظرة عامة على Ruder - التي تظهر فيها الرسوم المتحركة - هي واحدة من أفضل الموارد وأكثرها شمولاً حول هذا الموضوع.

لنقم بتحديث حل الانحدار الخطي من القسم الأول لاستخدام أدوات تحسين. ما يلي هو رمز نزول التدرج الأصلي باستخدام التدرجات اليدوية.

 # Manual gradient descent operations def run_gradient_descent(heights, weights, init_slope, init_icept, learning_rate): tf_slope = tf.Variable(init_slope, dtype='float32') tf_icept = tf.Variable(init_icept, dtype='float32') for i in range(25): with tf.GradientTape() as tape: tape.watch((tf_slope, tf_icept)) predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors**2) gradients = tape.gradient(loss, [tf_slope, tf_icept]) tf_slope = tf_slope - learning_rate * gradients[0] tf_icept = tf_icept - learning_rate * gradients[1]

الآن ، إليك نفس الرمز باستخدام مُحسِّن بدلاً من ذلك. ستلاحظ أنه لا يكاد يكون هناك أي رمز إضافي (يتم تمييز الأسطر المتغيرة باللون الأزرق):

 # Gradient descent with Optimizer (RMSprop) def run_gradient_descent (heights, weights, init_slope, init_icept, learning_rate) : tf_slope = tf.Variable(init_slope, dtype= 'float32' ) tf_icept = tf.Variable(init_icept, dtype= 'float32' ) # Group trainable parameters into a list trainable_params = [tf_slope, tf_icept] # Define your optimizer (RMSprop) outside of the training loop optimizer = keras.optimizers.RMSprop(learning_rate) for i in range( 25 ): # GradientTape loop is the same with tf.GradientTape() as tape: tape.watch( trainable_params ) predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors** 2 ) # We can use the trainable parameters list directly in gradient calcs gradients = tape.gradient(loss, trainable_params ) # Optimizers always aim to *minimize* the loss function optimizer.apply_gradients(zip(gradients, trainable_params))

هذا هو! لقد حددنا مُحسِّن RMSprop خارج حلقة نزول التدرج ، ثم استخدمنا طريقة المُحسِّن.apply_gradients optimizer.apply_gradients() بعد كل حساب تدرج لتحديث المعلمات القابلة للتدريب. يتم تعريف المُحسِّن خارج الحلقة لأنه سيتعقب التدرجات التاريخية لحساب المصطلحات الإضافية مثل الزخم والمشتقات ذات الترتيب الأعلى.

دعونا نرى كيف يبدو مع مُحسِّن RMSprop .

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

تبدو رائعة! لنجربها الآن مع مُحسِّن آدم .

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

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

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

الانحدار المتدرج في TensorFlow: من البحث عن الحد الأدنى إلى مهاجمة أنظمة الذكاء الاصطناعي

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

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


شارة شريك Google Cloud.

كشريك Google Cloud Partner ، يتوفر خبراء Toptal المعتمدون من Google للشركات عند الطلب لأهم مشاريعهم.