SciPy, optimalisatie met voorwaarden

SciPy, optimalisatie met voorwaarden

SciPy (uitgesproken als 'sai pai') is een wiskundige bibliotheek, gebaseerd op numpy, die ook bibliotheken in C en Fortran omvat. Met SciPy transformeert een interactieve Python-sessie zich in een volwaardige data-analyseomgeving, zoals MATLAB, IDL, Octave, R of SciLab.

In dit artikel bekijken we de basisprincipes van wiskundige programmering — het oplossen van optimalisatieproblemen met voorwaarden voor een scalair functie van meerdere variabelen met behulp van de scipy.optimize bibliotheek. Algoritmen voor onvoorwaardelijke optimalisatie zijn al besproken in in het vorige artikel. Verdere gedetailleerde en actuele informatie over de functies van scipy kan altijd worden verkregen met behulp van de help() functie, Shift+Tab of in de officiële documentatie.

Inleiding

De algemene interface voor het oplossen van zowel voorwaardelijke als onvoorwaardelijke optimalisatieproblemen in de scipy.optimize bibliotheek wordt geleverd door de functie minimize(). Het is echter bekend dat er geen universele methode bestaat om alle problemen op te lossen, dus de keuze van de juiste methode ligt altijd bij de onderzoeker.
De geschikte optimalisatiealgoritme wordt ingesteld met behulp van het argument van de functie minimize(..., method="").
Voor voorwaardelijke optimalisatie van functies met meerdere variabelen zijn de volgende methoden beschikbaar:

  • trust-constr — het zoeken naar een lokaal minimum binnen een vertrouwensgebied. Artikel op wiki, an article on Habr;
  • SLSQP — sequentieel kwadratisch programmeren met beperkingen, de Newton-methode voor het oplossen van het Lagrange-systeem. Artikel op wiki.
  • TNC — Truncated Newton Constrained, met een beperkt aantal iteraties, goed voor niet-lineaire functies met veel onafhankelijke variabelen. Artikel op wiki.
  • L-BFGS-B — de methode van de viertien Broyden–Fletcher–Goldfarb–Shanno, geïmplementeerd met een verminderd geheugenverbruik door gedeeltelijke laadtijd van vectoren uit de Hess-matrix. Artikel op wiki, an article on Habr.
  • COBYLA — COBYLA Constrained Optimization By Linear Approximation, beperkte optimalisatie met lineaire benadering (zonder het berekenen van de gradient). Artikel op wiki.

Afhankelijk van de gekozen methode worden verschillende voorwaarden en beperkingen ingesteld voor het oplossen van het probleem:

  • met een object van de klasse Bounds voor de methoden L-BFGS-B, TNC, SLSQP, trust-constr;
  • met een lijst van (min, max) voor dezelfde methoden L-BFGS-B, TNC, SLSQP, trust-constr;
  • met een object of een lijst van objecten LinearConstraint, NonlinearConstraint voor de methoden COBYLA, SLSQP, trust-constr;
  • met een dictionary of een lijst van dictionaries {'type':str, 'fun':callable, 'jac':callable,opt, 'args':sequence,opt} voor de methoden COBYLA, SLSQP.

Artikelplan:
1) Toepassing van de voorwaardelijke optimalisatie-algoritme in een vertrouwensgebied (method=»trust-constr») met beperkingen, gedefinieerd als objecten Bounds, LinearConstraint, NonlinearConstraint ;
2) Overweeg sequentiële programmering met de methode van de kleinste kwadraten (method="SLSQP") met beperkingen die zijn gedefinieerd in de vorm van een woordenboek {'type', 'fun', 'jac', 'args'};
3) Bespreek een voorbeeld van optimalisatie van de geproduceerde output aan de hand van een webstudio.

Voorwaardelijke optimalisatie method="trust-constr"

Implementatie van de methode trust-constr is gebaseerd op EQSQP voor problemen met gelijkheidsbeperkingen en op TRIP voor problemen met ongelijkheidsbeperkingen. Beide methoden zijn geïmplementeerd met algoritmes voor het zoeken naar een lokaal minimum binnen een betrouwbaarheidsgebied en zijn goed toepasbaar op grootschalige problemen.

Wiskundige formulering van de minimumzoekopdracht in algemene vorm:

SciPy, optimalisatie met voorwaarden

SciPy, optimalisatie met voorwaarden

SciPy, optimalisatie met voorwaarden

Voor strikte gelijkheidsbeperkingen wordt de ondergrens gelijkgesteld aan de bovenste grens SciPy, optimalisatie met voorwaarden.
Voor eenzijdige beperkingen wordt de boven- of ondergrens vastgesteld np.inf met het bijbehorende teken.
Stel dat we het minimum van de bekende Rosenbrock-functie van twee variabelen moeten vinden:

SciPy, optimalisatie met voorwaarden

Er zijn de volgende beperkingen op het domein:

SciPy, optimalisatie met voorwaarden

SciPy, optimalisatie met voorwaarden

SciPy, optimalisatie met voorwaarden

SciPy, optimalisatie met voorwaarden

SciPy, optimalisatie met voorwaarden

SciPy, optimalisatie met voorwaarden

In ons geval is er één enkele oplossing in het punt SciPy, optimalisatie met voorwaarden, waarvoor alleen de eerste en vierde beperkingen waar zijn.
Laten we de beperkingen van onder naar boven doornemen en bekijken hoe we deze in scipy kunnen vastleggen.
Beperkingen SciPy, optimalisatie met voorwaarden en SciPy, optimalisatie met voorwaarden wordt gedefinieerd met behulp van een Bounds-object.

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

Beperkingen SciPy, optimalisatie met voorwaarden en SciPy, optimalisatie met voorwaarden schrijf het in lineaire vorm:

SciPy, optimalisatie met voorwaarden

Laten we deze beperkingen definiëren in de vorm van een LinearConstraint-object:

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

En tenslotte een niet-lineaire beperking in matrixvorm:

SciPy, optimalisatie met voorwaarden

Laten we de Jacobimatrix voor deze beperking en een lineaire combinatie van de Hessian-matrix met een willekeurige vector definiëren SciPy, optimalisatie met voorwaarden:

SciPy, optimalisatie met voorwaarden

SciPy, optimalisatie met voorwaarden

Nu kunnen we de niet-lineaire beperking definiëren als een object 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)

Als de grootte groot is, kunnen matrices ook in spaarzame vorm worden gedefinieerd:

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)

of als een object 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)

Wanneer de berekening van de Hessiaan SciPy, optimalisatie met voorwaarden veel middelen vereist, kan de klasse HessianUpdateStrategy. De volgende strategieën zijn beschikbaar: BFGS en SR1.

from scipy.optimize import BFGS

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

De Hessiaan kan ook worden berekend met behulp van eindige verschillen:

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

De Jacobimatrix voor de beperkingen kan ook worden berekend met behulp van eindige verschillen. In dit geval kan de Hessiaan echter niet meer met eindige verschillen worden berekend. De Hessiaan moet worden gedefinieerd als een functie of met behulp van de klasse HessianUpdateStrategy.

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

De oplossing van het optimalisatieprobleem is als volgt:

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` stopcriterium is voldaan.
Aantal iteraties: 12, functieevaluaties: 8, CG-iteraties: 7, optimaliteit: 2.99e-09, schending van de beperking: 1.11e-16, uitvoeringstijd: 0.033 s.
[0.41494531 0.17010937]

Indien nodig kan de functie voor het berekenen van de Hessiaan worden gedefinieerd met behulp van de klasse 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)

of het product van de Hessiaan en een willekeurige vector via de parameter 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)

Alternatief kunnen de eerste en tweede afgeleiden van de te optimaliseren functie benaderend worden berekend. Bijvoorbeeld, de Hessiaan kan worden benaderd met behulp van de functie SR1 (quasi-Newton benadering). De gradient kan worden benaderd met eindige verschillen.

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)

Gecodeerde optimalisatie method=»SLSQP»

De SLSQP-methode is bedoeld voor het oplossen van optimalisatieproblemen in de vorm van:

SciPy, optimalisatie met voorwaarden

SciPy, optimalisatie met voorwaarden

SciPy, optimalisatie met voorwaarden

SciPy, optimalisatie met voorwaarden

Waarbij SciPy, optimalisatie met voorwaarden en SciPy, optimalisatie met voorwaarden — verzamelingen van indexen van uitdrukkingen die beperkingen in de vorm van gelijkheden of ongelijkheden beschrijven. SciPy, optimalisatie met voorwaarden — verzamelingen van onder- en bovengrenzen voor het domein van de functie.

Lineaire en niet-lineaire beperkingen worden beschreven in de vorm van woordenboeken met sleutels type, fun en 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])
          }

De zoekactie naar de minimumwaarde gebeurt als volgt:

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)

Optimalisatie succesvol beëindigd.    (Exit mode 0)
            Huidige functiewaarde: 0.34271757499419825
            Iteraties: 4
            Functiewaarden: 5
            Gradiëntwaarden: 4
[0.41494475 0.1701105 ]

Voorbeeld van optimalisatie

Met de overgang naar de vijfde technologische structuur, beschouwen we de optimalisatie van productie aan de hand van een webstudio, die ons een klein maar stabiel inkomen oplevert. Stel, we zijn de directeur van een galerie waar drie soorten producten worden vervaardigd:

  • x0 — verkoopbare landingspagina's, vanaf 10k.
  • x1 — bedrijfswebsites, vanaf 20k.
  • x2 — webwinkels, vanaf 30k.

Onze gezellige werkgroep bestaat uit vier junioren, twee mediors en één senior. De beschikbare werkuren per maand zijn:

  • junioren: 4 * 150 = 600 persoon * uur,
  • mediors: 2 * 150 = 300 persoon * uur,
  • senior: 150 persoon * uur.

Laten we zeggen dat een junior (10, 20, 30) uren nodig heeft voor de ontwikkeling en inzet van een website van het type (x0, x1, x2), een medior (7, 15, 20) en een senior (5, 10, 15) uren van hun beste tijd.

Als elke normale directeur, willen we de maandelijkse winst maximaliseren. De eerste stap naar succes is het opschrijven van de doelstelling. value Als de som van de inkomsten van de geproduceerde producten per maand:

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

Dit is geen fout, bij het zoeken naar het maximum wordt de doelstelling geminimaliseerd met een omgekeerd teken.

De volgende stap — we verbieden het overwerken van onze werknemers en stellen beperkingen op het beschikbare aantal werkuren in:

SciPy, optimalisatie met voorwaarden

Wat equivalent is aan:

SciPy, optimalisatie met voorwaarden

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

De formele beperking is dat de productie alleen positief moet zijn:

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

En tenslotte, het meest optimistische aanname is dat, vanwege de lage prijs en hoge kwaliteit, we voortdurend een rij tevreden klanten hebben. We kunnen zelf de maandelijkse productievolumes kiezen op basis van de oplossing van de voorwaardelijke optimalisatie taak met 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]

Laten we dit globaal afronden naar hele getallen en de maandelijkse belasting van de roeiers berekenen bij de optimale productverdeling x = (8, 6, 3) :

  • junioren: 8 * 10 + 6 * 20 + 3 * 30 = 290 personen * uur;
  • mediors: 8 * 7 + 6 * 15 + 3 * 20 = 206 personen * uur;
  • senior: 8 * 5 + 6 * 10 + 3 * 15 = 145 personen * uur.

Conclusie: om ervoor te zorgen dat de directeur zijn verdiende maximum krijgt, is het optimaal om per maand 8 landingspagina's, 6 gemiddelde websites en 3 winkels te maken. De senior moet daarbij onafgebroken werken, de belasting voor de midden-vaardigen zal ongeveer 2/3 bedragen, en voor de juniors minder dan de helft.

Conclusie

In het artikel worden de belangrijkste technieken gepresenteerd voor het werken met het pakket scipy.optimize, gebruikt voor het oplossen van taken van voorwaardelijke minimalisatie. Persoonlijk gebruik ik scipy uit puur academische doeleinden, daarom heeft het gegeven voorbeeld een enigszins humoristisch karakter.

Veel theorie en zeldzame voorbeelden zijn te vinden, bijvoorbeeld in het boek van I.L. Akuliche "Wiskundige programmering in voorbeelden en taken". Voor een hardere toepassing scipy.optimize voor het construeren van een 3D-structuur op basis van een set afbeeldingen (an article on Habr) kan men kijken in scipy-cookbook.

De belangrijkste informatiebron is docs.scipy.org, degenen die willen bijdragen aan de vertaling van deze en andere secties scipy zijn welkom op GitHub.

Dank mephistopheies voor deelname aan de publicatievoorbereiding.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster