Jäähdytysmenetelmä1 (engl. simulated annealing, SA) on satunnaisuuteen ja todennäköisyyksiin perustuva globaali optimointistrategia. Menetelmän perusajatus on ottaa satunnaisia askelia parametriavaruudessa ja hyväksyä uusi luku minimiksi todennäköisyysfunktion avulla. Todennäköisyysfunktio antaa arvon 1 mikäli uusi arvo on pienempi kuin entinen, mutta antaa myös todennäköisyyden valita huonompi arvo jollakin todennäköisyydellä, joka pienenee iteraatioiden funktiona. Tällä tavalla algoritmi pystyy pakenemaan lokaalista minimistä.
using Plots
Tarkastellaan funktiota \(f(x) = x^2 - 10\cos(5x) + 10\), jolla on lukuisia lokaaleja optimipisteitä sekä tasan yksi globaali optimipiste origossa.
= x -> x^2 - 10*cos(5*x) + 10
f = plot(-5:0.01:5, f)
p "figs/function_1d.svg")
savefig( p
Kaikki gradienttipohjaiset optimointimenetelmät epäonnistuvat tällaisen funktion optimoinnissa, mikäli alkuarvaus ei ole todella hyvä.
Lähdetään naputtelemaan SA koodi. Ensin tarvitaan transitiomalli, eli mitenkä tässä parametriavaruudessa olisi tarkoitus tepastella. Valitaan esimerkiksi normaalijakaumasta satunnainen luku.
using Distributions
function transition_model(x)
return x + rand(Normal())
end
transition_model (generic function with 1 method)
Sen lisäksi tarvitaan todennäköisyysfunktio. Yleensä \(P(e, e', T)\) määritellään niin että \(P = 1\), jos \(e' < e\) ja muutoin \(\exp(-(e'-e)/T)\), joten menemme sillä. Tässä \(e\) on “energia” joka minimoituu.
function acceptance_probability(f, f_new, T)
if f_new < f
return 1.0
end
return exp(-(f_new-f)/T)
end
acceptance_probability (generic function with 1 method)
Lisäksi tarvitaan jäähtymismalli, joka pienentää lämpötilaa iteraatioiden funktiona. Otetaan tässä yksinkertaisin mahdollinen, eli lämpötila pienenee lineaarisesti.
function temperature(fraction)
return 1 - fraction
end
temperature (generic function with 1 method)
Eli: transitiomallin ehdottama uusi \(x\) valitaan uudeksi minimiksi, mikäli \(f(x_{new}) < f(x)\), mutta myös siinä tapauksessa, että funktion arvo pisteessä \(x_{new}\) on isompi, jollakin todennäköisyydellä joka pienenee iteraatioiden funktiona.
Varsinainen pääalgoritmi tekee satunnaisia siirtymiä parametriavaruudessa ja hyväksyy tai hylkää uuden minimin perustuen todennäköisyysmalliin. Tallennetaan trace jotta voidaan tutkia konvergenssia:
function optimize_sa(f, x0::T, acceptance_probability,
, temperature; k_max=10) where T
transition_model
= x0
x = Int[]
accepted = Int[]
rejected = T[]
trace for k=1:k_max
= transition_model(x)
x_new !(trace, x_new)
push= temperature(k/k_max)
temp if acceptance_probability(f(x), f(x_new), temp) > rand()
= x_new
x !(accepted, k)
pushelse
!(rejected, k)
pushend
end
return trace, accepted, rejected
end
optimize_sa (generic function with 1 method)
Ajetaan optimointialgoritmia esimerkiksi 300 iteraatiota esimerkkifunktiolle:
using Random
Random.seed!(0)
, accepted, rejected = optimize_sa(f, 5.0,
trace, transition_model,
acceptance_probability; k_max=300)
temperature
= 1:length(trace)
iters = iters[accepted]
ia = iters[rejected]
ir = trace[accepted]
xa = trace[rejected]
xr end] xa[
0.012187171992082857
Vaikuttaisi että optimi on löytynyt. Algoritmille ominaista on että se ei anna tarkkaa ratkaisua vaan jotakin sen läheltä. Loput voidaan sitten hoitaa esimerkiksi Newtonin menetelmällä.
, xa,
scatter(ia=:o, label="Accepted",
marker="Trace of x")
title= scatter!(ir, xr,
p =:x, label="Rejected")
marker"figs/trace_x.svg")
savefig( p
, f.(xa),
scatter(ia=:o, label="Accepted",
marker="Function f")
title= scatter!(ir, f.(xr),
p =:x, label="Rejected")
marker"figs/function_f.svg")
savefig( p
Jäljestä näkee että optimi on löytynyt aika lyhyen iteroinnin jälkeen, alle 50 iteraatiossa. Optimoinnin puolessa välissä näkee kuinka algoritmi on ottanut pienen satunnaishypyn joka ei ole itse asiassa parantanut tulosta.
Case study: Challengerin katastrofi.
https://tieku.fi/maailmankaikkeus/avaruuslennot/challengerin-katastrofi-astronautit-tiesivat-kuolevansa 28. tammikuuta 1986 avaruussukkula Challenger muuttui taivaalla jättimäiseksi tulipalloksi.
Räjähdyksen syyksi selvisi yksinkertainen virhe – ja pian myös tiedettiin, että mukana olleet seitsemän astronauttia olivat vielä hengissä sekunteja ensimmäisten räjähdysten vavisuttaessa sukkulaa.
Vian syyksi paljastui O-renkaat, jotka eivät kestäneet Floridan hyyviä pakkaskelejä. Analysoidaan nyt tuosta saatua dataa logistisella mallilla.
= """
data 6 0 66 50 1
6 1 70 50 2
6 0 69 50 3
6 0 68 50 4
6 0 67 50 5
6 0 72 50 6
6 0 73 100 7
6 0 70 100 8
6 1 57 200 9
6 1 63 200 10
6 1 70 200 11
6 0 78 200 12
6 0 67 200 13
6 2 53 200 14
6 0 67 200 15
6 0 75 200 16
6 0 70 200 17
6 0 81 200 18
6 0 76 200 19
6 0 79 200 20
6 2 75 200 21
6 0 76 200 22
6 1 58 200 23
"""
function read_data(data)
= split(strip(data), "\n")
lines = length(lines)
N = zeros(N)
x = zeros(N)
y for i in 1:N
= split(lines[i], " ")
vals = parse(Int, vals[3])
x[i] = parse(Int, vals[2])
y[i] end
return x, y
end
, y = read_data(data)
xfor i in 1:length(y)
if y[i] != 0.0
= 1.0
y[i] end
end
Todennäköisyys, että O-rengas on vaurioitunut, on
\[\begin{equation} p\left(x\right)=\frac{1}{1+\exp\left(-k\left(x-x\_{0}\right)\right)} \end{equation}\]
Logistinen kustannus on \[\begin{equation} L\left(\theta\right)=-\frac{1}{m}\sum\_{i=1}^{m}\left[\hat{y}^{\left(i\right)}\log\left(p\left(\theta\right)\right)+\left(1-\hat{y}^{\left(i\right)}\right)\log\left(1-p\left(\theta\right)\right)\right] \end{equation}\]
function loss(θ; x=x, y=y)
, x0 = θ
k@assert length(x) == length(y)
= 0.0
J = length(x)
N for i in 1:N
= k*(x[i] - x0)
z = 1.0 / (1.0 + exp(-z))
a = -1.0 * (y[i] * log(a) + (1.0 - y[i]) * log(1.0 - a))
L += L
J end
return J / N
end
loss (generic function with 1 method)
Kevyt visualisointi jotta nähdään miltä funktio näyttää:
function do_plot()
= -3.0:0.01:0.0
ks = collect(50.0 : 0.1 : 100.0)
ws
= []
px = []
py for ki in ks
for wi in ws
= loss((ki, wi))
l if (isnan(l) || isinf(l))
!(px, ki)
push!(py, wi)
pushbreak
end
end
end
= contour(ks, ws, (x, y) -> loss((x, y)))
p !(px, py, color=:black, lw=1.5, label=nothing)
plot!(-3.0, 0.0)
xlims!(50.0, 90.0)
ylims!("Parameter k")
xlabel!("Parameter x0")
ylabel!("Logistic cost")
titlereturn p
end
= do_plot()
p "figs/logistic_loss.svg")
savefig( p
Nyt sitten optimoidaan. Eli minimoidaan logistista kustannusfunktiota. Jäähdytysmenetelmällä.
function transition_model(θ)
, x0 = θ
k= k + 1/π * rand(Normal())
k_new = x0 + π * rand(Normal())
x0_new return (k=k_new, x0=x0_new)
end
transition_model (generic function with 1 method)
using Random
Random.seed!(0)
, accepted, rejected = optimize_sa(loss,
trace=-3.0, x0=50.0),
(k, transition_model,
acceptance_probability; k_max=1000)
temperature
= [o.k for o in trace]
trace_k = [o.x0 for o in trace]
trace_x0 = 1:length(trace)
iters = iters[accepted]
ia = iters[rejected]
ir = trace_k[accepted]
ka = trace_x0[accepted]
x0a = trace_k[rejected]
kr = trace_x0[rejected]
x0r end] trace[accepted][
(k = -0.23451914148538833, x0 = 65.48773395385525)
, x0a,
scatter(ia=:o, label="Accepted",
marker="Trace of x0")
title= scatter!(ir, x0r,
p =:x, label="Rejected")
marker"figs/trace_x0.svg")
savefig( p
, ka,
scatter(ia=:o, label="Accepted",
marker="Trace of k",
title=:topleft)
legend= scatter!(ir, kr,
p =:x, label="Rejected")
marker"figs/trace_k.svg")
savefig( p
do_plot()!(ka, x0a, color=:blue, markercolor=:blue,
plot=:o, alpha=0.3, label="Accepted")
marker= plot!(kr, x0r, color=:red, markercolor=:red,
p =:x, alpha=0.3, label="Rejected", legend=:topleft)
marker"figs/trace_x0_and_k.svg")
savefig( p
Lyhyen konvergenssin jälkeen jää algoritmi pyörimään minimin tuntumaan. Valitulla menetelmällä ratkaisuksi saadaan
= trace[accepted][end] opt
(k = -0.23451914148538833, x0 = 65.48773395385525)
= (k = -0.23, x0 = 64.75)
opt2 = collect(minimum(x) : 0.1 : maximum(x))
xc = 1.0 / (1.0 + exp(-opt.k*(x - opt.x0)))
val(x) = val.(xc)
yc , yc, lw=1.5, color=:black, label="fitted model")
plot(xc= scatter!(x, y, color=:white, label="data")
p !("Challenger space shuttle O-ring failures")
title!("Ambient temperature (Fahrenheit)")
xlabel!("Probability to damage")
ylabel!(-0.1, 1.1)
ylims"figs/challenger_model.svg")
savefig( p
= do_plot()
p = -3.0:0.01:0.0
ks = collect(50.0 : 0.1 : 100.0)
ws = contour!(p, ks, ws, (x,y) -> loss((x, y)), levels=[0.45, 0.48, 0.55, 0.70, 1.0])
p = scatter!(p, [opt.k], [opt.x0], color=:red, label="minimum", legend=:topleft)
p "figs/challenger_logistic_cost_optimized.svg")
savefig( p
┌ Warning: Multiple series with different levels share a colorbar. Colorbar may not reflect all series correctly.
└ @ Plots /home/jukka/.julia/packages/Plots/7U0ob/src/backends/gr.jl:514
┌ Warning: Multiple series with different levels share a colorbar. Colorbar may not reflect all series correctly.
└ @ Plots /home/jukka/.julia/packages/Plots/7U0ob/src/backends/gr.jl:514