Algoritmi

1. Reševanje problemov z iskanjem
Veliko realnih problemov je možno definirati kot množico stanj, ki opisujejo trenutni položaj pri reševanju problema, in akcij oz. potez, ki predstavljajo prehode med različnimi stanji problema. Med tovrstne probleme spadajo razni problemi na igralni plošči (npr. N­
kvadratkov, N­dam, sudoku), iskanje poti in navigacija (najkrajša pot med dvema krajema, trgovski potnik), dokazovanje matematičnih teoremov, sestavljanje kompleksnih objektov iz elementarnih sestavnih delov (tvorba polimerov v kemiji, povezovanje strojnih delov v industriji), optimizacija načrtov (načrtovanje integriranih vezij) in internetno iskanje. Omenjene probleme lahko formalno opišemo v prostorih stanj. Prostor stanj je četverica <N,A,S,G>, kjer so:
• N množica vseh stanj v katerih se problem lahko nahaja,
• A množica vseh akcij, ki so dovoljene pri reševanju problema in spreminjajo stanje problema,
• S množica vseh začetnih stanj problema,
• G množica vseh končnih stanj problema.
Zgledi: 8 kvadratkov, 8 dam, iskanje poti po zemljevidu!
Množica stanj problema je končna, če se lahko problem nahaja le v omejenem številu diskretnih stanj. Kadar stanje problema vključuje zvezne količine, je tudi množica stanj neomejena.
V določenem stanju problema je dovoljena samo podmnožica vseh možnih potez, ki povzročijo spremembo trenutnega stanja in prehod v eno izmed »sosednjih« stanj. Običajno namesto potez, ki so možne v nekem stanju n, podajamo možne naslednike od n v obliki naslednostne funkcije nasl(n), ki vrne množico vseh možnih stanj­naslednikov stanja n.
Število začetnih stanj problema je pri večini realnih problemov enako ena (t.j. reševanje problema pričnemo iz enega samega stanja, čeprav je lahko začetno stanje v različnih oblikah istega problema drugačno), število končnih stanj pa je v splošnem večje. Končna stanja so bodisi podana eksplicitno (npr. 8 kvadratkov) bodisi so podani pogoji, ki jih mora neko stanje izpolnjevati, da ga sprejmemo kot končno (npr. 8 dam). Prostor stanj za nek problem najlažje predstavimo z vizualizacijo njegove topologije v obliki grafa. Stanja problema v tem primeru postanejo vozlišča grafa, akcije pa usmerjene ali neusmerjene povezave med temi vozlišči. Začetna in končna stanja problema ustrezno označimo.
Zgled: 8­kvadratkov!
Ločimo tri oblike grafov, ki nastopijo pri predstavitvi problemov v prostoru stanj:
1. ciklični graf – najbolj splošna oblika grafa, pri katerem (ne)usmerjene povezave iz nekega vozlišča v ciklu preko ostalih vozlišč vodijo nazaj do tega vozlišča (problem 8­
kvadratkov, trgovski potnik),
2. usmerjen aciklični graf (»directed acyclic graph – DAG«) – povezave med vozlišči so usmerjene in ne povzročajo ciklov v grafu (križci­krožci, reversi)
3. drevo – začetno stanje je eno samo in predstavlja koren drevesa, iz katerega vodijo usmerjene povezave do potomcev. Ti so organizirani v plasteh oz. nivojih, tako da je začetno vozlišče na nivoju 0, njegovi nasledniki na nivoju 1, nasledniki vozlišč na nivoju i pa so vozlišča nivoja i+1. Vozliščem na dnu drevesa pravimo listi.
Pot v grafu je zaporedje povezav, ki nas pripelje iz poljubnega štartnega do poljubnega ciljnega vozlišča. Reševanje problema lahko zato definiramo kot iskanje poti od začetnega vozlišča s do enega izmed končnih vozlišč gi∈G v grafu. Pri tem je lahko množica G opisana na enega izmed dveh načinov:
1. kot lastnost ciljnega stanja gi (npr. 8­dam) ali
2. kot lastnost poti od s do gi (npr. trgovski potnik).
V prvem primeru je rešitev problema samo stanje gi, v drugem primeru pa pot med s in gi (tj. zaporedje potez, ki nas pripeljejo do cilja).
Globina d je oddaljenost vozlišča od začetnega vozlišča, merjena v številu povezav med njima. Isto vozlišče lahko dosežemo iz začetnega vozlišča po dveh ali več različnih poteh in ima lahko v tem primeru različne globine.
Vejitveni faktor b je število povezav, ki vodijo iz nekega vozlišča, in je enako številu njegovih
naslednikov. Ker za večino realnih problemov velja, da je vejitveni faktor pri različnih vozliščih drugačen, običajno zanje podajamo povprečni vejitveni faktor.
Pri nekaterih problemih nas zanima najboljša rešitev, pri čemer ugodnost rešitve merimo z njenim stroškom ali ceno. Vsaki akciji iz množice A v tem primeru priredimo strošek povezave c, reševanje problema pa definiramo kot iskanje poti med s in gi z najmanjšo skupno
ceno. Takšne probleme imenujemo optimizacijski problemi, mednje pa spada večina problemov iskanja poti (strošek vsake povezave je običajno razdalja med vozliščema).
Glede na smer iskanja ločimo:
• iskanje z veriženjem naprej (»forward chaining«), pri katerem pričnemo z iskanjem v vozlišču s in napredujemo v smeri proti končnemu vozlišču. Takšno iskanje imenujemo tudi podatkovno vodeno iskanje (»data­driven search«).
• iskanje z veriženjem nazaj (»backward chaining«), pri katerem pričnemo z iskanjem v končnem vozlišču in razvijamo pot v smeri nazaj proti začetnemu vozlišču. Za vsako vozlišče­stanje pri razvoju ugotavljamo, iz katerih stanj smo lahko prispeli vanj. Ta stanja postanejo podcilji za iskanje, iz katerih s vračamo dalje proti začetnemu vozlišču. Takšno iskanje imenujemo ciljno vodeno iskanje (goal­driven search).
• iskanje z veriženjem v obeh smereh, pri katerem vzporedno izvajamo iskanje iz začetnega vozlišča proti končnemu in v obratni smeri ter iščemo stične točke obeh iskanj.
Pri izbiri smeri iskanja je koristno uporabiti naslednje nasvete:
• verižimo v smeri od večje množice vozlišč proti manjši (npr. pri dokazovanju teoremov je na osnovi majhne množice aksiomov mogoče izpeljati precej večjo množico teoremov, zato je bolje uporabiti veriženje nazaj),
• verižimo v smeri, ki bolje ustreza načinu razmišljanja pri reševanju (npr. iskanje poti med dvema krajema na zemljevidu je možno izvajati v obeh smereh, vendar je bolj naravno razmišljanje od štarta proti cilju).
Potek iskanja pogosto ponazorimo z iskalnim drevesom, katerega koren je začetno vozlišče s. Pomembno je ločiti med iskalnim drevesom in predstavitvijo prostora stanj v obliki drevesa oz. grafa. Pri slednjem vsakemu stanju ustreza eno vozlišče grafa, medtem ko v iskalnem drevesu istemu stanju, ki smo ga dosegli po več različnih poteh, pripada prav toliko vozlišč.
2 Lokalni iskalni algoritmi in optimizacijski problemi
Neinformirane iskalne strategije sistematično preiskujejo prostor stanj in rešitev opišejo kot pot od začetnega do ciljnega stanja. Veliko problemov je takšnih, da ni pomembna pot, pač pa
samo značilnosti ciljnega stanja. Takšen je npr. problem 8 kraljic, pa tudi veliko drugih realnih problemov (načrtovanje tiskanih vezij, usmerjanje vozil, avtomatsko programiranje, menedžment portfeljev, optimizacija telekomunikacijskih omrežij, itd.).
V opisanih primerih lahko uporabimo drugo vrsto iskalnih algoritmov, ki ne hranijo poti v prostoru stanj ampak le trenutno stanje. Pri tem običajno operirajo samo s ti. zaključenimi stanji, ki predstavljajo končne rešitve problema, čeprav ne nujno pravilne oz. optimalne (npr. pri problemu trgovskega potnika z 10 mesti bi trenutno stanje lahko bil vsak seznam dolžine 10, v katerem se vsako mesto pojavi le po enkrat). Iskanje v splošnem poteka s pomikanjem v
sosednja stanja, zato se algoritmi imenujejo lokalni iskalni algoritmi. Njihovi prednosti sta:
• majhna prostorska poraba (običajno konstantna) in • pogosto najdejo dobro rešitev v neomejenih prostorih stanj, za katere so sistematične iskalne strategije neprimerne.
Lokalni iskalni algoritmi so posebej primerni za reševanje optimizacijskih problemov, pri katerih je ugodnost rešitve podana kot ocenitvena funkcija stanja. V ta namen si prostor rešitev pogosto predstavljamo kot ploskev v prostoru, kjer stanje določa položaj točke na ploskvi, ocena stanja pa »višino« točke (slika ?). Iskanje optimalne rešitve potem pomeni iskanje globalnega maksimuma na ploskvi (v primeru da ocenitvena funkcija računa strošek stanja pa globalni minimum).
Lokalni iskalni algoritem je kompleten, če vedno najde rešitev, ko ta obstaja. Algoritem je optimalen, če vedno najde globalni optimum.
2.1 Hill climbing
Hill climbing se iz trenutnega stanja vedno pomakne v smeri najboljšega naslednika, če je ta boljši od trenutnega stanja. To je enakovredno pomikanju navzgor na ploskvi prostora rešitev.
Pri tem hrani le opis trenutnega stanja in njegovo oceno, prav tako pa ne preiskuje dlje od neposrednih sosedov trenutnega stanja.
Psevdokoda algoritma je naslednja:
function hill_climbing
current_state = random()
repeat
best_next = najboljši naslednik od current_state
if ocena(best_next)<=ocena(current_state) return current_state
current_state = best_next
end
end
Kot zgled bomo opisali delovanje algoritma hill climbing na problemu 8 kraljic. Opis stanja vključuje opis položajev vseh 8 kraljic na šahovski plošči, pri čemer je vsaka kraljica postavljena v svoj stolpec. Poteza, ki tvori naslednike, je premik katere izmed kraljic na poljubno drugo mesto znotraj stolpca. Vsako stanje ima torej 7*8=56 naslednikov. Ocena stanja je število parov kraljic, ki se medsebojno napadajo (posredno ali neposredno). Optimalna rešitev problema je stanje z oceno 0, tj. globalni minimum na ploskvi rešitev.
Naj bo naključno izbrano začetno stanje to, ki je prikazano na spodnji sliki. Njegova ocena je h=17. Slika prikazuje tudi ocene vseh stanj – naslednikov, od katerih ima najboljši oceno h=12. V primeru več enako dobrih naslednikov algoritem naključno izbere enega izmed njih. Najboljša rešitev, ki jo algoritem hill climbing najde v prejšnjem primeru, je prikazana na naslednji sliki. Njena ocena je h=1 in torej ni optimalna.
Značilnost algoritma hill climbing je ta, da zelo hitro napreduje proti rešitvi (v zgledu dosežemo lokalni optimum v 5 korakih). Velika slabost algoritma je ta, da pogosto ne najde globalnega optimuma, ker obtiči v lokalnem optimumu ali na platoju. Pri problemu 8 kraljic hill climbing najde rešitev le v 14% primerov, pri čemer potrebuje v povprečju 4 korake ob uspešni rešitvi in 3 ob neuspešni.
Problem obtičanja na platoju – rami lahko rešimo s premiki v enakovredne naslednike. Pri tem je potrebno število takšnih premikov omejiti, da ne tavamo v neskončnost po platoju, ki ni rama. Procent uspešno rešenih primerov 8 kraljic pri 100 enakovrednih premikih se povzpne na 94%, vendar je povprečno število korakov pri tem 21, pri neuspešnih poskusih pa kar 64.
Obstaja veliko variant hill climbing:
• stohastični hill climbing – naključno izbere enega od boljših naslednikov, pri čermer je verjetnost izbire sorazmerna naklonu vzpona. Konvergenca k rešitvi je počasnejša, vendar je rešitev na določenih ploskvah boljša.
• hill climbing s prvim izborom (first­choice hill climbing) – je implementacija stohastičnega hill climbinga, ki tvori naslednike trenutnega stanja v naključnem vrstnem redu, dokler ne naleti na prvega boljšega. To je uporabno posebej v primerih, ko ima vsako stanje ogromno naslednikov.
• hill climbing z naključno ponovitvijo (random restart hill climbing) – izvede hill climbing iz naključno tvorjenih začetnih stanj, dokler na najde rešitve. Verjetnost najdbe rešitve se s tem približuje 1. Če je verjetnost uspeha v enem poskusu p, je v povprečju potrebnih 1/p poskusov, kar za primer 8 kraljic pomeni 7 poskusov (6 neuspešnih in 1 uspešen). Hill climbing z naključno ponovitvijo je za ta problem dejansko zelo uspešen, saj lahko reši problem 3 milijonov kraljic v času krajšem od minute.
Uspešnost algoritma hill climbing je odvisna predvsem od oblike ploskve rešitev, ki pa ima za
večino realnih problemov veliko lokalnih ekstremov.
2.2 Simulirano ohlajanje (simulated annealing)
Hill climbing nikoli ne naredi poteze v smeri navzdol na ploskvi rešitev, zato je nekompleten. Simulirano ohlajanje je kombinacija hill climbinga z naključnim pomikanjem po ploskvi, ki zagotavlja kompletnost iskanja. Ideja izhaja iz metalurgije, kjer kovino segrejemo na visoko temperaturo in jo potem počasi ohlajamo, tako da sama preide v stabilno kristalno stanje.
Izvedba algoritma je podobna izvedbi hill climbinga. Izmed vseh naslednikov simulirano ohlajanje izbere naključnega in če je ta boljši, vedno izvede korak. Če je izbrani naslednik slabši od trenutnega stanja, se korak izvede z verjetnostjo p<1. Verjetnost p upada eksponentno z neugodnostjo poteze, tj. razliko v ocenah stanj. Prav tako p upada s temperaturo T, ki je na začetku visoka in se postopoma znižuje. Dokazano je, da se ob primerni hitrosti zniževanja temperature verjetnost uspešne rešitve problema približuje 1.
Psevdokoda algoritma je naslednja:
function simulated_annealing
current_state = random()
T = T0
repeat
if T=0 return current_state
next_state = naključni naslednik od current_state
ΔE = ocena(next_state) – ocena(current_state)
if ΔE>0 current_state = next_state
else current_state = next_state z verjetnostjo e­ΔE/T
T = T ­ ΔT
end
end
2.3 Iskanje z lokalnim žarkom (local beam search)
Iskanje z lokalnim žarkom tvori k naključnih začetnih stanj. Na vsakem koraku tvori vse naslednike teh k stanj in zaključi, če katero od njih predstavlja rešitev. V nasprotnem primeru izbere k najboljših naslednikov in nadaljuje.
Na prvi pogled je iskanje z lokalnim žarkom enakovredno k hkratnim iskanjem po sistemu hill
climbing. V resnici sta algoritma precej različna. Pri iskanju z lokalnim žarkom se prenaša informacija med vzporednimi iskanji. Če eno stanje generira k dobrih naslednikov, vsa ostala pa same slabe, se bo iskanje nadaljevalo le iz naslednikov prvega stanja. Na tak način algoritem hitro opusti slaba iskanja, problem pa je v tem, da se lahko iskanje hitro skoncentrira v majhni okolici ploskve rešitev. Izboljšava je stohastično iskanje z lokalnim žarkom, pri katerem k naslednikov izberemo z verjetnostjo, ki je sorazmerna njihovi oceni. To
pa že spominja na naravno selekcijo, ki je osnova naslednjega algoritma.
2.4 Genetski algoritmi
Genetski algoritem je v bistvu varianta iskanja z žarkom, pri katerem naslednike tvorimo s kombinacijo dveh predhodnikov in ne z izpeljavo iz enega samega predhodnika.
Genetski algoritem prične iskanje iz množice k naključno tvorjenih začetnih stanj, ki jo imenujemo populacija. Opis vsakega stanja je niz znakov oz. bitov, ki ga imenujemo kromosom, posamezen znak oz. bit v nizu pa gen. Stanje v problemu 8 kraljic bi lahko npr. opisali z nizom 8 cifer med 1 in 8, ki predstavljajo položaje kraljic znotraj stolpcev. Lahko bi ga podali tudi kot niz 8*log28=24 bitov. Način zapisa niza imenujemo kodirna shema.
Stanja ocenjujemo z uporabo funkcije primernosti (fitness function), ki daje večje vrednosti za
boljša stanja. Za problem 8 kraljic bi funkcija primernosti npr. vrnila število nenapadajočih se parov kraljic.
Genetski algoritem deluje z zaporedno tvorbo naslednje generacije rešitev iz predhodne generacije. Tvorba naslednje generacije poteka v več korakih:
1. ocenitev rešitev trenutne generacije
2. selekcija stanj – staršev, ki bodo tvorili pripadnike naslednje generacije
3. križanje staršev in tvorba potomcev
4. mutacija potomcev
Selekcija je izbira stanj iz trenutne generacije, ki jih uporabimo za tvorbo potomcev. Obstaja več vrst kriterijev selekcije:
• naključna selekcija: naključna izbira kateregakoli stanja
• proporcionalna selekcija: verjetnost izbire stanja je sorazmerna oceni stanja
p(Ci ) =
N
f (Ci )
∑ f (C j )
j =1
Primer proporcionalne selekcije je ruletna selekcija, ki jo opisuje naslednji algoritem:
1. n = 1 naj bo indeks kromosoma
N
2. s = ∑ f (Ci )
i =1
3. izberi naključno število a med 0 in s
n
4. dokler ∑ f (Ci ) < a
i =1
a. n++
5. vrni Cn kot izbrani kromosom­starš
•
turnirska selekcija : naključno določimo m<k kromosomov od katerih najboljšega izberemo za starša.
• selekcija po ranku: kromosomi so urejeni po ustreznosti, izbiramo pa jih glede na zaporedni indeks in ne ustreznost samo. Primer takšne selekcije je nedeterministično linearno vzorčenje:
1. n = random(random(k))
2. izberi n­ti kot kromosom – starš
• elitizem: najustreznejših m<<k kromosomov neposredno napreduje v naslednjo generacijo
Križanje dveh kromosomov – staršev je proces, ki se zgodi z verjetnostjo pc. Verjetnost pc imenujemo odstotek križanja (crossover rate). Če do križanja ne pride, napredujeta v naslednjo generacijo kar oba starša, sicer dobimo dva potomca z izmenjavo genetskega materiala, tj. zamenjavo delov kodiranih zaporedij v obeh kromosomih. Katere dele kromosomov bomo izmenjali, določa ti. maska križanja, ki je lahko določena na več načinov:
• uniformno križanje: vsak par genov starševskih kromosomov neodvisno zamenjamo z verjetnostjo pc • enotočkovno križanje: naključno izberemo položaj v zapisu kromosoma in izmenjamo vse gene starševskih kromosomov od tega položaja naprej
• dvotočkovno križanje: naključno izberemo dva položaja v zapisu kromosomov in izmenjamo vse gene med tema dvema položejema Mutacija pomeni naključno spremembo genetskega materiala, ki se zgodi z verjetnostjo pm (običajno zelo majhna).
Zgled: Problem 8 kraljic na šahovski plošči!
Začetna populacija
Ocena
Selekcija
Križanje
Mutacija
24748552
24 (31%)
32752411
32748552
32748152
32752411
23 (29%)
24748552
24752411
24752411
24415124
20 (26%)
32752411
32752124
32252124
32543213
11 (14%)
24415124
24415411
24415417