SciPy, optimering med betingelser

SciPy, optimering med betingelser

SciPy (udtales som 'sai pai') er et matematisk bibliotek baseret på numpy, som også inkluderer C- og Fortran-biblioteker. Med SciPy bliver en interaktiv Python-session en lige så fuldgyldig databehandlingsmiljø som MATLAB, IDL, Octave, R eller SciLab.

I denne artikel vil vi se på de grundlæggende teknikker inden for matematisk programmering — løsningen af betingede optimeringsopgaver for en skalar funktion af flere variabler ved hjælp af pakken scipy.optimize. De ubetingede optimeringsalgoritmer er allerede blevet beskrevet i den forrige artikel. Mere detaljeret og opdateret information om scipy-funktioner kan altid fås ved hjælp af kommandoen help(), Shift+Tab eller i den officielle dokumentation.

Introduktion

Det overordnede interface til løsning af både betingede og ubetingede optimeringsopgaver i pakken scipy.optimize leveres af funktionen minimize(). Det er dog kendt, at der ikke findes én universel metode til at løse alle opgaver, så valget af en passende metode hviler altid på forskerens skuldre.
Den passende optimeringsalgoritme angives med argumentet til funktionen minimize(..., method="").
For betinget optimering af en funktion af flere variabler er følgende metoder tilgængelige:

  • trust-constr — lokalisering af et minimum i et tillidsområde. Artikel på wiki, artikel på habr;
  • SLSQP — sekventiel kvadratisk programmering med begrænsninger, Newtons metode til løsning af Lagrangesystemet. Artikel på wiki.
  • TNC — Truncated Newton Constrained, et begrænset antal iterationer, godt til ikke-lineære funktioner med mange uafhængige variabler. Artikel på wiki.
  • L-BFGS-B — metoden fra Broyden–Fletcher–Goldfarb–Shanno familien, implementeret med reduceret hukommelsesforbrug ved delvis indlæsning af vektorer fra Hessians matrix. Artikel på wiki, artikel på habr.
  • COBYLA — COBYLA Constrained Optimization By Linear Approximation, begrænset optimering med lineær tilnærmelse (uden beregning af gradient). Artikel på wiki.

Afhængigt af den valgte metode angives betingelser og begrænsninger på forskellige måder:

  • ved hjælp af objektet af klassen Bounds for metoderne L-BFGS-B, TNC, SLSQP, trust-constr;
  • som en liste (min, max) for de samme metoder L-BFGS-B, TNC, SLSQP, trust-constr;
  • ved hjælp af objektet eller listen af objekter LinearConstraint, NonlinearConstraint for metoderne COBYLA, SLSQP, trust-constr;
  • ved hjælp af et dictionary eller en liste af dictionaries {'type':str, 'fun':callable, 'jac':callable,opt, 'args':sequence,opt} for metoderne COBYLA, SLSQP.

Artikelens plan:
1) Overvej anvendelsen af den betingede optimeringsalgoritme i tillidsområdet (method=»trust-constr») med begrænsninger angivet som objekter. Bounds, LinearConstraint, NonlinearConstraint ;
2) Overvej sekventiel programmering ved hjælp af mindste kvadraters metode (method=»SLSQP») med begrænsninger angivet i form af en ordbog {'type', 'fun', 'jac', 'args'};
3) Gennemgå et eksempel på optimering af produktionen med et webbureau som eksempel.

Betinget optimering method=»trust-constr»

Implementeringen af metoden trust-constr er baseret på EQSQP til opgaver med lighedsbegrænsninger og på TRIP til opgaver med ulighedsbegrænsninger. Begge metoder er implementeret med algoritmer til lokal minima søgning i et tillidsområde og er velegnede til storskala opgaver.

Matematisk formulering af problemet med at finde minimum i generel form:

SciPy, optimering med betingelser

SciPy, optimering med betingelser

SciPy, optimering med betingelser

For begrænsninger med streng lighed sættes den nedre grænse lig med den øvre SciPy, optimering med betingelser.
For ensidige begrænsninger sættes den øvre eller nedre grænse np.inf med den entsprechende tegn.
Lad os sige, at vi skal finde minimum for den kendte Rosenbrok-funktion med to variable:

SciPy, optimering med betingelser

Her er der angivet følgende begrænsninger for dens definitionsområde:

SciPy, optimering med betingelser

SciPy, optimering med betingelser

SciPy, optimering med betingelser

SciPy, optimering med betingelser

SciPy, optimering med betingelser

SciPy, optimering med betingelser

I vores tilfælde er der en entydig løsning i punktet SciPy, optimering med betingelser, hvor kun den første og fjerde begrænsning er gældende.
Lad os gennemgå begrænsningerne fra bunden og se, hvordan vi kan skrive dem i scipy.
Begrænsninger SciPy, optimering med betingelser og SciPy, optimering med betingelser defineret med et Bounds-objekt.

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

Begrænsninger SciPy, optimering med betingelser og SciPy, optimering med betingelser lad os skrive det i lineær form:

SciPy, optimering med betingelser

Definer disse begrænsninger i form af et LinearConstraint-objekt:

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

Og endelig et ikke-lineært begrænsning i matrixform:

SciPy, optimering med betingelser

Lad os definere Jacobimatricen for denne begrænsning og en lineær kombination af Hessiamatricen med en vilkårlig vektor SciPy, optimering med betingelser:

SciPy, optimering med betingelser

SciPy, optimering med betingelser

Nu kan vi definere den ikke-lineære begrænsning som et objekt 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)

Hvis størrelsen er stor, kan matricerne også angives i sparsomt format:

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)

eller som et objekt LinearOperator:

fra 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)

Når beregningen af Hessian-matrixen SciPy, optimering med betingelser kræver betydelige ressourcer, kan klassen HessianUpdateStrategy. Følgende strategier er tilgængelige: BFGS og SR1.

fra scipy.optimize import BFGS

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

Hessian kan også beregnes ved hjælp af finite differencer:

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

Jacobian-matricen for restriktionerne kan også beregnes ved hjælp af finite differencer. Men i dette tilfælde kan Hessian-matrixen ikke beregnes via finite differencer. Den skal defineres som en funktion eller ved hjælp af klassen HessianUpdateStrategy.

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

Løsningen af optimeringsproblemet ser således ud:

fra scipy.optimize import minimize
fra 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 betingelse er tilfredsstillet.
Antal iterationer: 12, funktionsevalueringer: 8, CG-iterationer: 7, optimalitet: 2.99e-09, overtrædelse af restriktioner: 1.11e-16, udførelsestid: 0.033 s.
[0.41494531 0.17010937]

Om nødvendigt kan funktionen til beregning af Hessian defineres ved hjælp af klassen 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)

eller produktionen af Hessian og en vilkårlig vektor gennem parameteren 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)

Alternativt kan de første og anden afledte af den optimerede funktion beregnes approximativt. For eksempel kan Hessian blive approksimeret ved hjælp af funktionen SR1 (quasi-Newton-approximation). Gradientet kan approximativt beregnes ved hjælp af finite differencer.

fra 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)

Betinget optimering method="SLSQP"

SLSQP-metoden er designet til at løse optimeringsopgaver i form af:

SciPy, optimering med betingelser

SciPy, optimering med betingelser

SciPy, optimering med betingelser

SciPy, optimering med betingelser

Where SciPy, optimering med betingelser og SciPy, optimering med betingelser — mængder af indeksudtryk, der beskriver begrænsninger i form af lighed eller ulighed. SciPy, optimering med betingelser — mængder af nedre og øvre grænser for definitionsområdet af funktionen.

Lineære og ikke-lineære begrænsninger beskrives i form af ordbøger med nøgler. type, sjov og 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])
          }

Minimale søgning udføres som følger:

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)

Optimering afsluttet med succes.    (Exit mode 0)
            Nuværende funktionsværdi: 0.34271757499419825
            Iterationer: 4
            Funktionsvurderinger: 5
            Gradientvurderinger: 4
[0.41494475 0.1701105 ]

Eksempel på optimering

I takt med overgangen til den femte teknologiske bølge, lad os se på optimering af produktionen ved hjælp af et eksempel fra et webbureau, der giver os en lille, men stabil indkomst. Forestil dig, at vi er direktør for et galleri, hvor der produceres tre typer produkter:

  • x0 — salgssider, fra 10 t.kr.
  • x1 — virksomhedshjemmesider, fra 20 t.kr.
  • x2 — internetbutikker, fra 30 t.kr.

Vores venlige arbejdsgruppe består af fire juniorer, to mellemledere og en senior. Deres arbejdskraftfond for en måned:

  • juniorer: 4 * 150 = 600 mandetimer,
  • mellemledere: 2 * 150 = 300 mandetimer,
  • senior: 150 mandetimer.

Lad os sige, at den første junior skal bruge (10, 20, 30) timer på udvikling og implementering af en hjemmeside af typen (x0, x1, x2), mellemlederen — (7, 15, 20), senioren — (5, 10, 15) timer af deres bedste tid.

Som enhver normal direktør vil vi gerne maksimere den månedlige profit. Det første skridt mod succes er at notere den målrettede funktion value som summen af indtægterne fra de producerede produkter i løbet af en måned:

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

Dette er ikke en fejl; ved søgningen efter maksimumet minimeres den målrettede funktion med omvendt fortegn.

Næste skridt er at forbyde overarbejde for vores ansatte og indføre begrænsninger på arbeidskraftfondet:

SciPy, optimering med betingelser

Hvilket svarer til:

SciPy, optimering med betingelser

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]])
            }

Det formelle begrænsning er, at produktionen kun skal være positiv:

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

Og endelig, den mest optimistiske antagelse – på grund af den lave pris og høje kvalitet danner der konstant en kø af tilfredse kunder. Vi kan selv vælge de månedlige produktionsmængder baseret på løsningen af en betinget optimeringsopgave med 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]

Lad os afrunde til hele tal og beregne den månedlige belastning af roere ved den optimale produktionsfordeling x = (8, 6, 3) :

  • juniorer: 8 * 10 + 6 * 20 + 3 * 30 = 290 personer * timer;
  • mellemledere: 8 * 7 + 6 * 15 + 3 * 20 = 206 personer * timer;
  • senior: 8 * 5 + 6 * 10 + 3 * 15 = 145 personer * timer.

Konklusion: For at direktøren kan få sit velfortjente maksimum, er det optimalt at lave 8 landing pages, 6 mellemstore websteder og 3 butikker om måneden. Senior skal arbejde koncentreret, mens belastningen for mid-level medarbejdere vil være cirka 2/3, og juniorer mindre end halvdelen.

Konklusion

Artiklen behandler de grundlæggende metoder til arbejd med pakken scipy.optimize, der anvendes til at løse opgaver om betinget minimalisering. Personligt bruger jeg scipy udelukkende til akademiske formål, hvorfor det givne eksempel har en sjov karakter.

Meget teori og sjove eksempler kan findes, for eksempel i bogen af I.L. Akulich "Matematisk programmering i eksempler og opgaver". En mere hardcore anvendelse scipy.optimize til at opbygge en 3D-struktur på baggrund af et sæt billeder (artikel på habr) kan ses i scipy-cookbook.

Hovedkilden til information er docs.scipy.org, de, der ønsker at bidrage til oversættelsen af denne og andre sektioner scipy er velkomne til GitHub.

Tak mephistopheies for deltagelse i forberedelsen af publikationen.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster