هندسة خوارزمية التحسين مع HorusLP في بایثون| الجزء الأول

هندسة خوارزمية التحسين

هندسة خوارزمية التحسين مع HorusLP

HorusLP هي أداة Python جديدة مصممة للمساعدة في هندسة سير عمل تطوير الخوارزمية. يوفر إطارًا معماريًا لتطوير خوارزمية التحسين.

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

نظرًا لتزايد تعقيد التكنولوجيا الأساسية ، تم إنشاء مجموعة جديدة من الأدوات لمساعدة الباحثين والمطورين على العمل بشكل أكثر إنتاجية. تسمح هذه الأدوات مثل AMPL و CVXPY و PuLP للمطورين بتحديد وبناء وتشغيل خوارزميات التحسين بسرعة والواجهة مع مجموعة متنوعة من الحلول.

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

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

المشكلات التي تواجه مطوري خوارزمية التحسين

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

هندسة خوارزمية التحسين مع HorusLP في بایثون

في معظم التخصصات يمكن للمطور التخفيف من هذه المشكلة عن طريق اختيار إطار عمل أو مكتبة تساعد في البنية. في الواجهة الأمامية للويب يختار العديد من المطورين React أو Angular؛ في عالم تطوير API ، يمكن لمهندسي البرمجيات الاختيار من بين Django أو ASP.NET MVC أو Play من بين أشياء أخرى كثيرة. ومع ذلك عندما يتعلق الأمر بمطور خوارزمية التحسين المتواضع هناك عدد قليل جدًا من أدوات الهندسة للمساعدة في إدارة التعقيد المعماري. يُترك المطور لإدارة المتغيرات والقيود والأهداف المختلفة بمفرده. علاوة على ذلك يصعب بشكل عام استبطان خوارزميات أبحاث العمليات مما يؤدي إلى تفاقم المشكلة.

الغرض الرئيسي من HorusLP هو توفير إطار معماري لتطوير خوارزميات التحسين. من خلال توفير الاتساق الهيكلي يسهل إطار العمل التنظيم ويسمح للمطورين بالتركيز على أفضل ما يفعلونه: بناء الخوارزميات.

تحديات سير العمل النموذجية للتحسين

هناك عدة مصادر رئيسية للتعقيد عند تطوير خوارزميات OR:

التعقيد من المتغيرات

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

التعقيد من القيود

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

التعقيد من الأهداف

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

التعقيد من التصحيح (debugging)

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

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

برنامج HorusLP التعليمي: خوارزمية التحسين ونظرة عامة على واجهة برمجة التطبيقات

بدون مزيد من اللغط دعنا نتعمق ونرى ما يمكن أن تقدمه لك مكتبة HorusLP!

نظرًا لأن HorusLP يعتمد على Python و PuLP فسنرغب في تثبيتهما باستخدام النقطة. قم بتشغيل ما يلي في سطر الأوامر:

Pip install horuslp pulp

بمجرد اكتمال التثبيت دعنا نمضي قدمًا ونفتح ملف Python. سنقوم بتنفيذ مشكلة الحقيبة (knapsack).

هندسة خوارزمية التحسين مع HorusLP في بایثون

تحتوي مكتبة HorusLP على واجهة برمجة تطبيقات توضيحية بسيطة جدًا وقليل جدًا من النماذج المعيارية. لنبدأ باستيراد الفئات والمتغيرات الضرورية:

from horuslp.core.Variables import BinaryVariable # we will be using binary variables, so we will import the BinaryVariable class
from horuslp.core import Constraint, VariableManager, Problem, ObjectiveComponent # We will also need to import the constraint class, variable manager class, the main problem class, and the objective class to define the objective. 
from horuslp.core.constants import MAXIMIZE  # since we're maximizing the resulting value, we want to import this constant

بمجرد استيراد جميع المتغيرات دعنا نحدد المتغيرات التي نحتاجها لهذه المشكلة. نقوم بذلك عن طريق إنشاء فئة فرعية لمدير المتغيرات وتعبئتها بالمتغيرات الثنائية:

class KnapsackVariables(VariableManager):
    vars = [
        BinaryVariable('camera'), # the first argument is the name of the variable
        BinaryVariable('figurine'),
        BinaryVariable('cider'),
        BinaryVariable('horn')
    ]

الآن بعد أن تم تحديد المتغيرات دعنا نحدد القيود. نقوم بإنشاء قيود عن طريق تصنيف فئة القيد الرئيسية وتنفيذ داله “define” الخاصة بها.

class SizeConstraint(Constraint):
    def define(self, camera, figurine, cider, horn):
        return 2 * camera + 4 * figurine + 7 * cider + 10 * horn <= 15

في دالة التعريف يمكنك طلب المتغيرات المطلوبة بالاسم. سيحدد إطار العمل المتغير في مدير المتغير ويمرره إلى وظيفة التعريف.

بعد تنفيذ القيد يمكننا تنفيذ الهدف. نظرًا لأنه هدف بسيط سنستخدم ObjectiveComponent.

class ValueObjective(ObjectiveComponent):
    def define(self, camera, figurine, cider, horn):
        return 5 * camera + 7 * figurine + 2 * cider + 10 * horn

دالة define لها إعداد مشابه جدًا لوظيفة تعريف فئة القيد. وبدلاً من إرجاع تعبير القيد فإننا نعيد تعبير أفيني.

الآن بعد أن تم تحديد المتغيرات والقيود والأهداف دعنا نحدد النموذج:

class KnapsackProblem(Problem):
    variables = KnapsackVariables
    objective = ValueObjective
    constraints = [SizeConstraint]
    sense = MAXIMIZE

لبناء النموذج نقوم بإنشاء فئة هي فئة فرعية من فئة المشكلة وتحديد المتغيرات والأهداف والقيود والحس. مع تحديد المشكلة يمكننا حل المشكلة:

prob = KnapsackProblem()
prob.solve()

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

prob.print_results()
print(prob.result_variables)

قم بتشغيل البرنامج النصي وسترى الإخراج التالي:

KnapsackProblem: Optimal
camera 0.0
figurine 1.0
cider 0.0
horn 1.0
ValueObjective: 17.00
SizeConstraint: 14.00
{'camera': 0.0, 'figurine': 1.0, 'cider': 0.0, 'horn': 1.0}

يجب أن ترى حالة المشكلة والقيمة النهائية للمتغيرات والقيمة الهدف وقيمة تعبير القيد. نرى أيضًا القيم الناتجة للمتغيرات كقاموس.

وإليكم الأمر أول مشكلة HorusLP في حوالي 35 سطرًا!

في الأمثلة القادمة سوف نستكشف بعض الميزات الأكثر تعقيدًا لمكتبة HorusLP.

إستخدام VariableGroups

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

قم بتغيير عبارات الاستيراد كما يلي:

from horuslp.core.Variables import BinaryVariableGroup
from horuslp.core import Constraint, VariableManager, Problem, ObjectiveComponent
from horuslp.core.constants import MAXIMIZE

نحتاج الآن أيضًا إلى تغيير الإعلانات المتغيرة على الحقيبة كما يلي:

class KnapsackVariables(VariableManager):
    vars = [
        BinaryVariableGroup('objects', [
            'camera',
            'figurine',
            'cider',
            'horn'
        ])
    ]

المتغير الأول هو اسم مجموعة المتغيرات والمتغير الثاني هو قائمة بأسماء المتغيرات داخل تلك المجموعة.

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

class SizeConstraint(Constraint):
    def define(self, objects):
        return 2 * objects['camera'] + 4 * objects['figurine'] + 7 * objects['cider'] + 10 * objects['horn'] <= 15

class ValueObjective(ObjectiveComponent):
    def define(self, objects):
        return 5 * objects['camera'] + 7 * objects['figurine'] + 2 * objects['cider'] + 10 * objects['horn']

يمكننا الآن استخدام نفس تعريف المشكلة وتشغيل الأوامر:

class KnapsackProblem(Problem):
    variables = KnapsackVariables
    objective = ValueObjective
    constraints = [SizeConstraint]
    sense = MAXIMIZE


prob = KnapsackProblem()
prob.solve()
prob.print_results()
print(prob.result_variables)

يجب أن ترى هذا في الإخراج الخاص بك:

KnapsackProblem: Optimal
objects[camera] 0.0
objects[figurine] 1.0
objects[cider] 0.0
objects[horn] 1.0
ValueObjective: 17.00
SizeConstraint: 14.00
{'objects': {'camera': 0.0, 'figuring': 1.0, 'cider': 0.0, 'horn': 1.0}}

إدارة القيود المتعددة

النماذج ذات القيد الفردي نادرة نسبيًا. عند العمل مع قيود متعددة من الجيد وجود جميع القيود في مكان واحد حتى يمكن تتبعها وإدارتها بسهولة. HorusLP يجعل ذلك طبيعيًا.

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

ارجع إلى النموذج الذي طبقناه في البرنامج التعليمي الأول. أضف القيد التالي:

class MustHaveItemConstraint(Constraint):
    def define(self, camera):
        return camera >= 1

لإضافة القيد إلى النموذج نحتاج ببساطة إلى إضافته إلى تعريف المشكلة كما يلي:

class KnapsackProblem(Problem):
    variables = KnapsackVariables
    objective = ValueObjective
    constraints = [
        SizeConstraint,
        MustHaveItemConstraint # just add this line 🙂
    ]
    sense = MAXIMIZE

قم بتشغيل المشكلة وسترى الإخراج التالي:

KnapsackProblem: Optimal
camera 1.0
figurine 0.0
cider 0.0
horn 1.0
ValueObjective: 15.00
SizeConstraint: 12.00
MustHaveItemConstraint: 1.00

يجب أن ترى القيد الجديد الذي تتم طباعته في stdout والقيم المتغيرة المثلى التي تم تغييرها.

إدارة القيود التابعة والمجموعات المقيدة

غالبًا ما ترتبط القيود ببعضها البعض إما لأنها تعتمد على بعضها البعض أو لأنها تنتمي منطقيًا إلى نفس المجموعة.

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

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

class SizeConstraint(Constraint):
    dependent_constraints = [MustHaveItemConstraint]

    def define(self, camera, figurine, cider, horn):
        return 2 * camera + 4 * figurine + 7 * cider + 10 * horn <= 15

والآن لاختبار تنفيذ القيود التابعة تلقائيًا دعنا نخرج MustHaveItemConstraint من تعريف المشكلة:

class KnapsackProblem(Problem):
    variables = KnapsackVariables
    objective = ValueObjective
    constraints = [
        SizeConstraint,
    ]
    sense = MAXIMIZE

وقم بتشغيل الكود مرة أخرى وسترى ما يلي في stdout:

KnapsackProblem: Optimal
camera 1.0
figurine 0.0
cider 0.0
horn 1.0
ValueObjective: 15.00
SizeConstraint: 12.00
MustHaveItemConstraint: 1.00

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

إدارة أهداف مرجحة متعددة

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

لنفترض أننا أردنا تحيز الخوارزمية لاختيار التمثال وعصير التفاح. دعنا نعيد تشكيل الكود من القسم السابق لاستخدام CombinedObjective.

أولاً قم باستيراد فئة CombinedObjective مثل:

from horuslp.core import CombinedObjective

يمكننا تنفيذ مكون موضوعي مستقل مثل:

class ILoveCiderFigurineObjectiveComponent(ObjectiveComponent):
    def define(self, figurine, cider):
        return figurine + cider

الآن يمكننا الجمع بين هدف القيمة وهدف cider/figrine الشكل من خلال تنفيذ CombinedObjective:

class Combined(CombinedObjective):
    objectives = [
        (ILoveCiderFigurineObjectiveComponent, 2), # first argument is the objective, second argument is the weight
        (ValueObjectiveComponent, 1)
    ]

الآن دعنا نغير تعريف المشكلة على النحو التالي:

class KnapsackProblem(Problem):
    variables = KnapsackVariables
    objective = Combined
    constraints = [SizeConstraint]
    sense = MAXIMIZE

قم بتشغيل المشكلة وسترى الإخراج التالي:

KnapsackProblem: Optimal
camera 1.0
figurine 1.0
cider 1.0
horn 0.0
Combined: 18.00
ILoveCiderFigurineObjectiveComponent: 2.00 * 2
ValueObjectiveComponent: 14.00 * 1
SizeConstraint: 13.00
MustHaveItemConstraint: 1.00

سيحدد الناتج القيمة الهدف المجمعة وقيمة كل مكون من مكونات الهدف والوزن وبالطبع قيمة جميع القيود.

منشور ذات صلة
بايثون 11 Minutes

بايثون تأكل العالم!

جاسم ناظري

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

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

السلة