SciPy, אופטימיזציה עם תנאים

SciPy, אופטימיזציה עם תנאים

SciPy (מבוטא sai pie) היא חבילת מתמטיקה מבוססת numpy הכוללת גם ספריות C ו-Fortran. SciPy הופך את הפעלת Python האינטראקטיבית שלך לסביבת מדעי נתונים שלמה כמו MATLAB, IDL, Octave, R או SciLab.

במאמר זה, נבחן את הטכניקות הבסיסיות של תכנות מתמטי - פתרון בעיות אופטימיזציה מותנית עבור פונקציה סקלרית של מספר משתנים באמצעות החבילה scipy.optimize. אלגוריתמי אופטימיזציה בלתי מוגבלים כבר נדונו ב המאמר האחרון. תמיד ניתן לקבל עזרה מפורטת ועדכנית יותר על פונקציות scipy באמצעות הפקודה help(), Shift+Tab או ב- תיעוד רשמי.

מבוא

ממשק נפוץ לפתרון בעיות אופטימיזציה מותנות ובלתי מוגבלות בחבילת scipy.optimize מסופק על ידי הפונקציה minimize(). עם זאת, ידוע כי אין שיטה אוניברסלית לפתרון כל הבעיות, ולכן הבחירה בשיטה הולמת, כמו תמיד, נופלת על כתפי החוקר.
אלגוריתם האופטימיזציה המתאים מצוין באמצעות ארגומנט הפונקציה minimize(..., method="").
עבור אופטימיזציה מותנית של פונקציה של מספר משתנים, יישומים של השיטות הבאות זמינות:

  • trust-constr - חפש מינימום מקומי באזור הביטחון. מאמר בוויקי, מאמר על Habré;
  • SLSQP - תכנות ריבועי רציף עם אילוצים, שיטה ניוטונית לפתרון מערכת לגראנז'. מאמר ויקי.
  • TNC - Truncated Newton Constrained, מספר מוגבל של איטרציות, טוב לפונקציות לא ליניאריות עם מספר רב של משתנים בלתי תלויים. מאמר בוויקי.
  • L-BFGS-B - שיטה מצוות Broyden–Fletcher–Goldfarb–Shanno, המיושמת עם צריכת זיכרון מופחתת עקב טעינה חלקית של וקטורים מהמטריצה ​​ההסיאנית. מאמר בוויקי, מאמר על Habré.
  • COBYLA — MARE מוגבל אופטימיזציה על ידי קירוב ליניארי, אופטימיזציה מוגבלת עם קירוב ליניארי (ללא חישוב שיפוע). מאמר בוויקי.

בהתאם לשיטה שנבחרה, התנאים וההגבלות לפתרון הבעיה נקבעים באופן שונה:

  • אובייקט כיתה Bounds עבור שיטות L-BFGS-B, TNC, SLSQP, trust-constr;
  • הרשימה (min, max) עבור אותן שיטות L-BFGS-B, TNC, SLSQP, trust-constr;
  • חפץ או רשימה של חפצים LinearConstraint, NonlinearConstraint עבור COBYLA, SLSQP, אמון-constr שיטות;
  • מילון או רשימת מילונים {'type':str, 'fun':callable, 'jac':callable,opt, 'args':sequence,opt} עבור שיטות COBYLA, SLSQP.

מתווה מאמר:
1) שקול את השימוש באלגוריתם אופטימיזציה מותנה באזור האמון (שיטה="trust-constr") עם אילוצים שצוינו כאובייקטים Bounds, LinearConstraint, NonlinearConstraint ;
2) שקול תכנות רציף בשיטת הריבועים הקטנים ביותר (שיטה = "SLSQP") עם הגבלות שצוינו בצורה של מילון {'type', 'fun', 'jac', 'args'};
3) נתח דוגמה לאופטימיזציה של מוצרים מיוצרים באמצעות הדוגמה של סטודיו אינטרנט.

שיטת אופטימיזציה מותנית = "trust-constr"

יישום השיטה trust-constr מבוסס על EQSQP לבעיות עם אילוצים של צורת השוויון והלאה TRIP לבעיות עם אילוצים בצורה של אי שוויון. שתי השיטות מיושמות על ידי אלגוריתמים למציאת מינימום מקומי באזור הביטחון ומתאימות היטב לבעיות בקנה מידה גדול.

ניסוח מתמטי של הבעיה של מציאת מינימום בצורה כללית:

SciPy, אופטימיזציה עם תנאים

SciPy, אופטימיזציה עם תנאים

SciPy, אופטימיזציה עם תנאים

עבור אילוצי שוויון קפדניים, הגבול התחתון מוגדר שווה לגבול העליון SciPy, אופטימיזציה עם תנאים.
עבור אילוץ חד כיווני, הגבול העליון או התחתון מוגדר np.inf עם השלט המתאים.
יהיה צורך למצוא את המינימום של פונקציית רוזנברוק ידועה של שני משתנים:

SciPy, אופטימיזציה עם תנאים

במקרה זה, ההגבלות הבאות נקבעות על תחום ההגדרה שלו:

SciPy, אופטימיזציה עם תנאים

SciPy, אופטימיזציה עם תנאים

SciPy, אופטימיזציה עם תנאים

SciPy, אופטימיזציה עם תנאים

SciPy, אופטימיזציה עם תנאים

SciPy, אופטימיזציה עם תנאים

במקרה שלנו, יש פתרון ייחודי בנקודה SciPy, אופטימיזציה עם תנאים, שרק ההגבלה הראשונה והרביעית תקפות.
הבה נעבור על ההגבלות מלמטה למעלה ונראה כיצד נוכל לכתוב אותן ב-scipy.
הגבלות SciPy, אופטימיזציה עם תנאים и SciPy, אופטימיזציה עם תנאים בואו נגדיר את זה באמצעות האובייקט Bounds.

from scipy.optimize import Bounds
bounds = Bounds ([0, -0.5], [1.0, 2.0])

הגבלות SciPy, אופטימיזציה עם תנאים и SciPy, אופטימיזציה עם תנאים בוא נכתוב את זה בצורה לינארית:

SciPy, אופטימיזציה עם תנאים

בואו נגדיר את האילוצים האלה כאובייקט LinearConstraint:

import numpy as np
from scipy.optimize import LinearConstraint
linear_constraint = LinearConstraint ([[1, 2], [2, 1]], [-np.inf, 1], [1, 1])

ולבסוף האילוץ הלא ליניארי בצורת מטריצה:

SciPy, אופטימיזציה עם תנאים

אנו מגדירים את המטריצה ​​היעקוביאנית עבור אילוץ זה ושילוב ליניארי של המטריצה ​​ההסיאנית עם וקטור שרירותי SciPy, אופטימיזציה עם תנאים:

SciPy, אופטימיזציה עם תנאים

SciPy, אופטימיזציה עם תנאים

כעת נוכל להגדיר אילוץ לא ליניארי כאובייקט NonlinearConstraint:

from scipy.optimize import NonlinearConstraint

def cons_f(x):
     return [x[0]**2 + x[1], x[0]**2 - x[1]]

def cons_J(x):
     return [[2*x[0], 1], [2*x[0], -1]]

def cons_H(x, v):
     return v[0]*np.array([[2, 0], [0, 0]]) + v[1]*np.array([[2, 0], [0, 0]])

nonlinear_constraint = NonlinearConstraint(cons_f, -np.inf, 1, jac=cons_J, hess=cons_H)

אם הגודל גדול, ניתן לציין מטריצות גם בצורה דלילה:

from scipy.sparse import csc_matrix

def cons_H_sparse(x, v):
     return v[0]*csc_matrix([[2, 0], [0, 0]]) + v[1]*csc_matrix([[2, 0], [0, 0]])

nonlinear_constraint = NonlinearConstraint(cons_f, -np.inf, 1,
                                            jac=cons_J, hess=cons_H_sparse)

או כאובייקט LinearOperator:

from scipy.sparse.linalg import LinearOperator

def cons_H_linear_operator(x, v):
    def matvec(p):
        return np.array([p[0]*2*(v[0]+v[1]), 0])
    return LinearOperator((2, 2), matvec=matvec)

nonlinear_constraint = NonlinearConstraint(cons_f, -np.inf, 1,
                                jac=cons_J, hess=cons_H_linear_operator)

בעת חישוב המטריצה ​​ההסיאנית SciPy, אופטימיזציה עם תנאים דורש הרבה מאמץ, אתה יכול להשתמש בכיתה HessianUpdateStrategy. האסטרטגיות הבאות זמינות: BFGS и SR1.

from scipy.optimize import BFGS

nonlinear_constraint = NonlinearConstraint(cons_f, -np.inf, 1, jac=cons_J, hess=BFGS())

ניתן לחשב את ההסיאן גם באמצעות הבדלים סופיים:

nonlinear_constraint = NonlinearConstraint (cons_f, -np.inf, 1, jac = cons_J, hess = '2-point')

ניתן לחשב את המטריצה ​​היעקוביאנית עבור אילוצים גם באמצעות הבדלים סופיים. עם זאת, במקרה זה לא ניתן לחשב את המטריצה ​​ההסיאנית באמצעות הבדלים סופיים. יש להגדיר את ה-Hessian כפונקציה או באמצעות המחלקה HessianUpdateStrategy.

nonlinear_constraint = NonlinearConstraint (cons_f, -np.inf, 1, jac = '2-point', hess = BFGS ())

הפתרון לבעיית האופטימיזציה נראה כך:

from scipy.optimize import minimize
from scipy.optimize import rosen, rosen_der, rosen_hess, rosen_hess_prod

x0 = np.array([0.5, 0])
res = minimize(rosen, x0, method='trust-constr', jac=rosen_der, hess=rosen_hess,
                constraints=[linear_constraint, nonlinear_constraint],
                options={'verbose': 1}, bounds=bounds)
print(res.x)

`gtol` termination condition is satisfied.
Number of iterations: 12, function evaluations: 8, CG iterations: 7, optimality: 2.99e-09, constraint violation: 1.11e-16, execution time: 0.033 s.
[0.41494531 0.17010937]

במידת הצורך, ניתן להגדיר את הפונקציה לחישוב ה-Hssian באמצעות המחלקה LinearOperator

def rosen_hess_linop(x):
    def matvec(p):
        return rosen_hess_prod(x, p)
    return LinearOperator((2, 2), matvec=matvec)

res = minimize(rosen, x0, method='trust-constr', jac=rosen_der, hess=rosen_hess_linop,
                 constraints=[linear_constraint, nonlinear_constraint],
                 options={'verbose': 1}, bounds=bounds)

print(res.x)

או מכפלת ההסיאן ווקטור שרירותי דרך הפרמטר hessp:

res = minimize(rosen, x0, method='trust-constr', jac=rosen_der, hessp=rosen_hess_prod,
                constraints=[linear_constraint, nonlinear_constraint],
                options={'verbose': 1}, bounds=bounds)
print(res.x)

לחלופין, ניתן להעריך את הנגזרת הראשונה והשנייה של הפונקציה המבוצעת אופטימיזציה. לדוגמה, ניתן לקרב את ההסיאן באמצעות הפונקציה SR1 (קירוב מעין ניוטוני). ניתן להעריך את הגרדיאנט על ידי הבדלים סופיים.

from scipy.optimize import SR1
res = minimize(rosen, x0, method='trust-constr',  jac="2-point", hess=SR1(),
               constraints=[linear_constraint, nonlinear_constraint],
               options={'verbose': 1}, bounds=bounds)
print(res.x)

שיטת אופטימיזציה מותנית = "SLSQP"

שיטת SLSQP נועדה לפתור בעיות של מזעור פונקציה בצורה:

SciPy, אופטימיזציה עם תנאים

SciPy, אופטימיזציה עם תנאים

SciPy, אופטימיזציה עם תנאים

SciPy, אופטימיזציה עם תנאים

שם SciPy, אופטימיזציה עם תנאים и SciPy, אופטימיזציה עם תנאים - קבוצות של מדדים של ביטויים המתארים הגבלות בצורה של שוויון או אי שוויון. SciPy, אופטימיזציה עם תנאים - קבוצות של גבול תחתון ועליון עבור תחום ההגדרה של הפונקציה.

אילוצים ליניאריים ולא ליניאריים מתוארים בצורה של מילונים עם מפתחות type, fun и jac.

ineq_cons = {'type': 'ineq',
             'fun': lambda x: np.array ([1 - x [0] - 2 * x [1],
                                          1 - x [0] ** 2 - x [1],
                                          1 - x [0] ** 2 + x [1]]),
             'jac': lambda x: np.array ([[- 1.0, -2.0],
                                          [-2 * x [0], -1.0],
                                          [-2 * x [0], 1.0]])
            }

eq_cons = {'type': 'eq',
           'fun': lambda x: np.array ([2 * x [0] + x [1] - 1]),
           'jac': lambda x: np.array ([2.0, 1.0])
          }

החיפוש אחר המינימום מתבצע באופן הבא:

x0 = np.array([0.5, 0])
res = minimize(rosen, x0, method='SLSQP', jac=rosen_der,
               constraints=[eq_cons, ineq_cons], options={'ftol': 1e-9, 'disp': True},
               bounds=bounds)

print(res.x)

Optimization terminated successfully.    (Exit mode 0)
            Current function value: 0.34271757499419825
            Iterations: 4
            Function evaluations: 5
            Gradient evaluations: 4
[0.41494475 0.1701105 ]

דוגמה לאופטימיזציה

בקשר למעבר למבנה הטכנולוגי החמישי, בואו נסתכל על אופטימיזציה של ייצור באמצעות דוגמה של סטודיו אינטרנט, שמביא לנו הכנסה קטנה אך יציבה. בואו נדמיין את עצמנו כמנהלים של גלריה שמייצרת שלושה סוגי מוצרים:

  • x0 - מכירת דפי נחיתה, מ-10 tr.
  • x1 - אתרי אינטרנט ארגוניים, מ-20 tr.
  • x2 - חנויות מקוונות, מ-30 tr.

צוות העבודה הידידותי שלנו כולל ארבעה צעירים, שניים בינוניים ואחד בכיר. קרן זמן העבודה החודשית שלהם:

  • יוני: 4 * 150 = 600 чел * час,
  • אמצעים: 2 * 150 = 300 чел * час,
  • סניור: 150 чел * час.

תן לג'וניור הזמין הראשון להשקיע (0, 1, 2) שעות בפיתוח ופריסה של אתר אחד מסוג (x10, x20, x30), בינוני - (7, 15, 20), בכיר - (5, 10, 15 ) שעות של התקופה הטובה בחייך.

כמו כל דירקטור רגיל, אנחנו רוצים למקסם את הרווחים החודשיים. הצעד הראשון להצלחה הוא לרשום את הפונקציה האובייקטיבית value כסכום ההכנסה ממוצרים המיוצרים בחודש:

def value(x):
    return - 10*x[0] - 20*x[1] - 30*x[2]

זו אינה שגיאה; כאשר מחפשים את המקסימום, פונקציית המטרה ממוזערת עם הסימן ההפוך.

השלב הבא הוא לאסור על העובדים שלנו לעבוד יתר על המידה ולהכניס הגבלות על שעות העבודה:

SciPy, אופטימיזציה עם תנאים

מה שווה ערך:

SciPy, אופטימיזציה עם תנאים

ineq_cons = {'type': 'ineq',
             'fun': lambda x: np.array ([600 - 10 * x [0] - 20 * x [1] - 30 * x[2],
                                         300 - 7  * x [0] - 15 * x [1] - 20 * x[2],
                                         150 - 5  * x [0] - 10 * x [1] - 15 * x[2]])
            }

הגבלה רשמית היא שתפוקת המוצר חייבת להיות חיובית בלבד:

bnds = Bounds ([0, 0, 0], [np.inf, np.inf, np.inf])

ולבסוף, ההנחה הוורודה ביותר היא שבגלל המחיר הנמוך והאיכות הגבוהה, עומד לנו כל הזמן תור של לקוחות מרוצים. אנו יכולים לבחור בעצמנו את נפחי הייצור החודשיים, בהתבסס על פתרון בעיית האופטימיזציה המוגבלה scipy.optimize:

x0 = np.array([10, 10, 10])
res = minimize(value, x0, method='SLSQP', constraints=ineq_cons, bounds=bnds)
print(res.x)

[7.85714286 5.71428571 3.57142857]

בואו נעגל בצורה רופפת למספרים שלמים ונחשב את העומס החודשי של החותרים עם חלוקה מיטבית של מוצרים x = (8, 6, 3) :

  • יוני: 8 * 10 + 6 * 20 + 3 * 30 = 290 чел * час;
  • אמצעים: 8 * 7 + 6 * 15 + 3 * 20 = 206 чел * час;
  • סניור: 8 * 5 + 6 * 10 + 3 * 15 = 145 чел * час.

מסקנה: על מנת שהמנהל יקבל את המקסימום הראוי שלו, אופטימלי ליצור 8 דפי נחיתה, 6 אתרים בינוניים ו-3 חנויות בחודש. במקרה זה, הקשיש חייב לחרוש מבלי להרים את מבטו מהמכונה, עומס האמצעים יהיה כ-2/3, הצעירים פחות מחצי.

מסקנה

המאמר מתאר את הטכניקות הבסיסיות לעבודה עם החבילה scipy.optimize, משמש לפתרון בעיות מזעור מותנה. באופן אישי אני משתמש scipy אך ורק למטרות אקדמיות, וזו הסיבה שהדוגמה שניתנה היא בעלת אופי קומי כל כך.

ניתן למצוא הרבה תיאוריות ודוגמאות וירטואליות, למשל, בספרו של I.L. Akulich "תכנות מתמטי בדוגמאות ובעיות". עוד יישום הארדקור scipy.optimize לבנות מבנה תלת מימדי מקבוצת תמונות (מאמר על Habré) ניתן לצפות ב ספר בישול מוצק.

מקור המידע העיקרי הוא docs.scipy.orgאלה המעוניינים לתרום לתרגום של סעיפים זה ואחרים scipy ברוך הבא ל GitHub.

תודה מפיסטופיס להשתתפות בהכנת הפרסום.

מקור: www.habr.com

קנה אירוח אמין לאתרים עם הגנת DDoS, שרתי VPS VDS 🔥 קנה אחסון אתרים אמין עם הגנת DDoS, שרתי VPS VDS | ProHoster