SciPy, optimizacija s uvjetima

SciPy, optimizacija s uvjetima

SciPy (izgovara se kao saj paj) je matematički paket koji se temelji na numpy-u i uključuje biblioteke napisane na C-u i Fortranu. Uz SciPy, interaktivna Python sesija postaje potpuna okolina za obradu podataka, poput MATLAB-a, IDL-a, Octave-a, R-a ili SciLab-a.

U ovom članku razmotrit ćemo osnovne tehnike matematičkog programiranja — rješavanje problema uvjetne optimizacije za skalarne funkcije više varijabli pomoću paketa scipy.optimize. Algoritmi bezuvjetne optimizacije već su obrađeni u prethodnom članku. Detaljna i aktualna pomoć o funkcijama scipy uvijek se može dobiti pomoću naredbe help(), Shift+Tab ili u službene dokumentacije.

Uvod

Opći интерфейс za rješavanje problema i uvjetne i bezuvjetne optimizacije u paketu scipy.optimize pruža funkcija minimize(). Međutim, poznato je da ne postoji univerzalni način za rješavanje svih problema, stoga izbor adekvatne metode kao i uvijek pada na teret istraživača.
Pogodan algoritam optimizacije određuje se pomoću argumenta funkcije minimize(..., method="").
Za uvjetnu optimizaciju funkcija više varijabli dostupne su implementacije sljedećih metoda:

  • trust-constr — traženje lokalnog minimuma u povjerenstvenom području. Članak na wiki, članak na habru;
  • SLSQP — sekvencijsko kvadratno programiranje s ograničenjima, Newtonova metoda rješavanja sustava Lagrangea. Članak na viki.
  • TNC — Truncated Newton Constrained, ograničen broj iteracija, dobar za nelinearne funkcije s velikim brojem neovisnih varijabla. Članak na wiki.
  • L-BFGS-B — metoda četvorke Broyden-Fletcher-Goldfarb-Shanno, implementirana s smanjenom potrošnjom memorije korištenjem parcijalnog učitavanja vektora iz Hesseove matrice. Članak na wiki, članak na habru.
  • COBYLA — COBYLA Constrained Optimization By Linear Approximation, ograničena optimizacija s linearnom aproksimacijom (bez izračunavanja gradijenta). Članak na wiki.

Ovisno o odabranoj metodi, različito se postavljaju uvjeti i ograničenja za rješavanje problema:

  • objektom klase Bounds za metode L-BFGS-B, TNC, SLSQP, trust-constr;
  • popisom (min, max) za iste metode L-BFGS-B, TNC, SLSQP, trust-constr;
  • objektom ili popisom objekata LinearConstraint, NonlinearConstraint za metode COBYLA, SLSQP, trust-constr;
  • rječnikom ili popisom rječnika {'type':str, 'fun':callable, 'jac':callable,opt, 'args':sequence,opt} za metode COBYLA, SLSQP.

Plan članka:
1) Razmotrite primjenu algoritma uvjetne optimizacije u povjerenoj oblasti (method=»trust-constr») s ograničenjima koja su zadana u obliku objekata Bounds, LinearConstraint, NonlinearConstraint ;
2) Razmotrite sekvencijsko programiranje metodom najmanjih kvadrata (method=»SLSQP») s ograničenjima koja su zadana u obliku rječnika {'type', 'fun', 'jac', 'args'};
3) Razmotrite primjer optimizacije proizvodnje na primjeru web-studija.

Uvjetna optimizacija method=»trust-constr»

Implementacija metode trust-constr temelji se na EQSQP za probleme s ograničenjima u obliku jednadžbe i na TRIP za probleme s ograničenjima u obliku nejednakosti. Ove metode su implementirane algoritmima pretraživanja lokalnog minimuma u povjerenoj oblasti i dobro su prilagođene za velike probleme.

Matematička formulacija problema pronalaženja minimuma u općem obliku:

SciPy, optimizacija s uvjetima

SciPy, optimizacija s uvjetima

SciPy, optimizacija s uvjetima

Za ograničenja stroge jednakosti, donja granica se postavlja jednaka gornjoj SciPy, optimizacija s uvjetima.
Za jednostrano ograničenje, gornja ili donja granica se postavlja np.inf s odgovarajućim znakom.
Neka je potrebno pronaći minimum poznate funkcije Rosenbrock od dva varijable:

SciPy, optimizacija s uvjetima

Pri tome su zadana sljedeća ograničenja na njezinu oblast definicije:

SciPy, optimizacija s uvjetima

SciPy, optimizacija s uvjetima

SciPy, optimizacija s uvjetima

SciPy, optimizacija s uvjetima

SciPy, optimizacija s uvjetima

SciPy, optimizacija s uvjetima

U našem slučaju postoji jedinstveno rješenje u točki SciPy, optimizacija s uvjetima, za koju su valjana samo prva i četvrta ograničenja.
Proći ćemo kroz ograničenja od dna prema vrhu i razmotriti kako ih možemo zapisati u scipy.
Ograničenja SciPy, optimizacija s uvjetima i SciPy, optimizacija s uvjetima određujemo pomoću objekta Bounds.

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

Ograničenja SciPy, optimizacija s uvjetima i SciPy, optimizacija s uvjetima zapisujemo u linearnoj formi:

SciPy, optimizacija s uvjetima

Odredit ćemo ta ograničenja u obliku objekta LinearConstraint:

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

I konačno, nelinearno ograničenje u matričnom obliku:

SciPy, optimizacija s uvjetima

Odredimo Jacobi matricu za ovo ograničenje i linearne kombinacije Hessove matrice s proizvoljnim vektorom SciPy, optimizacija s uvjetima:

SciPy, optimizacija s uvjetima

SciPy, optimizacija s uvjetima

Sada nelinearno ograničenje možemo odrediti kao 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)

Ako je veličina velika, matrice se mogu zadati i u rijetkom obliku:

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)

ili kao objekt LinearOperator:

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

Kada izračunavanje Hesijanove matrice SciPy, optimizacija s uvjetima zahtijeva velike troškove, može se upotrijebiti klasa HessianUpdateStrategy. Dostupne su sljedeće strategije: BFGS i SR1.

iz scipy.optimize import BFGS

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

Hesijan se također može izračunati pomoću konačnih razlika:

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

Jacobi matrica za ograničenja također se može izračunati pomoću konačnih razlika. Međutim, u ovom slučaju Hesijan se ne može izračunati pomoću konačnih razlika. Hesijan mora biti definiran kao funkcija ili putem klase HessianUpdateStrategy.

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

Rješenje optimizacijskog problema izgleda ovako:

iz scipy.optimize import minimize
iz 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` uvjet za prekid je zadovoljen.
Broj različitih iteracija: 12, evaluacije funkcije: 8, CG iteracije: 7, optimalnost: 2.99e-09, kršenje ograničenja: 1.11e-16, vrijeme izvođenja: 0.033 s.
[0.41494531 0.17010937]

Ako je potrebno, funkciju za izračunavanje Hesijana može se definirati pomoću klase 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)

ili proizvod Hesijana i proizvoljnog vektora kroz parametar 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)

Alternativno, prva i druga derivacija optimizirane funkcije mogu se približno izračunati. Na primjer, Hesijan može biti aproksimiran pomoću funkcije SR1 (kvazi-Njutonske aproksimacije). Gradient se može aproksimirati konačnim razlikama.

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

Kondicionalna optimizacija method=»SLSQP»

Metoda SLSQP namijenjena je rješavanju problema minimizacije funkcije u obliku:

SciPy, optimizacija s uvjetima

SciPy, optimizacija s uvjetima

SciPy, optimizacija s uvjetima

SciPy, optimizacija s uvjetima

Gdje SciPy, optimizacija s uvjetima i SciPy, optimizacija s uvjetima — skupovi indeksa izraza koji opisuju ograničenja u obliku jednakosti ili nejednakosti. SciPy, optimizacija s uvjetima — skupovi donjih i gornjih granica za domen funkcije.

Linearna i nelinearna ograničenja opisuju se u obliku rečnika sa ključevima type, fun i 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])
          }

Postupak traženja minimuma je sledeći:

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)

Optimizacija je uspešno završena.    (Izlazni mod 0)
            Trenutna vrednost funkcije: 0.34271757499419825
            Iteracije: 4
            Evaluacije funkcije: 5
            Evaluacije gradijenta: 4
[0.41494475 0.1701105 ]

Primer optimizacije

U svetlu prelaska na peti tehnološki ciklus, razmotrićemo optimizaciju proizvodnje na primeru veb-studija koja nam donosi mali, ali stabilan prihod. Zamislimo se kao direktor galerije koja proizvodi tri vrste proizvoda:

  • x0 — prodajni landing strani, od 10.000 kn.
  • x1 — korporativne veb stranice, od 20.000 kn.
  • x2 — internet prodavnice, od 30.000 kn.

Naš tim se sastoji od četiri juniora, dva midla i jednog seniora. Fond njihovog radnog vremena za mesec:

  • juniori: 4 * 150 = 600 čovek-sati,
  • midlovi: 2 * 150 = 300 čovek-sati,
  • senior: 150 čovek-sati.

Neka za razvoj i implementaciju jednog tipa veb sajta (x0, x1, x2) prvi deo juniora potroši (10, 20, 30) sati, midl — (7, 15, 20), senior — (5, 10, 15) sati svog najboljeg vremena.

Kao što svakom normalnom direktoru priliči, želimo maksimizovati mesečni profit. Prvi korak ka uspehu — zapisujemo ciljnu funkciju value kao zbir prihoda od proizvedene robe tokom meseca:

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

To nije greška, prilikom traženja maksimuma, ciljana funkcija se minimizira sa obrnutim znakom.

Sledeći korak — zabranjujemo prekovremeni rad svojim zaposlenima i uvodimo ograničenja na fond radnog vremena:

SciPy, optimizacija s uvjetima

Što je ekvivalentno:

SciPy, optimizacija s uvjetima

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

Formalno ograničenje - proizvodnja mora biti isključivo pozitivna:

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

I konačno, najoptimističnija pretpostavka - zbog niske cijene i visoke kvalitete, konstantno se formira red zadovoljnih klijenata. Možemo sami odabrati mjesečne obujme proizvodnje, temeljeći se na rješavanju zadatka uvjetne optimizacije s 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]

Neslužbeno ćemo zaokružiti na cijele brojeve i izračunati mjesečnu opterećenost veslača pri optimalnoj raspodjeli proizvoda x = (8, 6, 3) :

  • juniori: 8 * 10 + 6 * 20 + 3 * 30 = 290 ljudi * sati;
  • midlovi: 8 * 7 + 6 * 15 + 3 * 20 = 206 ljudi * sati;
  • senior: 8 * 5 + 6 * 10 + 3 * 15 = 145 ljudi * sati.

Zaključak: kako bi direktor ostvario svoj zasluženi maksimum, optimalno je proizvoditi 8 odredišnih stranica, 6 srednje velikih web stranica i 3 trgovine mjesečno. Senior bi trebao raditi neprekidno, opterećenje mid-level bi iznosilo otprilike 2/3, dok bi juniori imali manje od polovice.

Zaključak

U članku su izložene osnovne tehnike rada s paketom scipy.optimize, koje se koriste za rješavanje zadataka uvjetne minimalizacije. Osobno koristim scipy isključivo u akademske svrhe, pa primjer koji slijedi ima za cilj duhovitost.

Mnogo teorije i raritetnih primjera može se pronaći, na primjer, u knjizi I.L. Akuliča "Matematičko programiranje u primjerima i zadacima". Teži korištenje scipy.optimize za izradu 3D strukture prema skupu slika (članak na habru) može se vidjeti u scipy-cookbook.

Glavni izvor informacija - docs.scipy.org, oni koji žele doprinijeti prevodu ovog i drugih odjeljaka scipy dobrodošli su na GitHub.

Hvala mephistopheies za sudjelovanje u pripremi publikacije.

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster