1. Section 01. 단체 여행
In [1]:
In [2]:
# optimization.py
# schedule.txt (http://kiwitobes.com/optimize/schedule.txt)
# ppt
# 단체여행계획 수립에 있어서 최적화된 방법을 제시
import time
import random
import math
# 가족 구성원 정보
people = [('Seymour','BOS'),
('Franny','DAL'),
('Zooey','CAK'),
('Walt','MIA'),
('Buddy','ORD'),
('Les','OMA')]
# 라구아디아 공항
destination='LGA'
# people, 가족구성원들의 정보('이름', '각자의 출발지 공항')
# 비행편데이터 샘플파일(schedule.txt)
출발지,도착지,출발시간,도착시간,가격정보
LGA,OMA,6:19,8:13,239
OMA,LGA,6:11,8:31,249
LGA,OMA,8:04,10:59,136
OMA,LGA,7:39,10:24,219
LGA,OMA,9:31,11:43,210
...
flights={} # 배열생성
#
for line in file('schedule.txt'):
origin,dest,depart,arrive,price=line.strip().split(',')
flights.setdefault((origin,dest),[]) # key 넣고
# Add details to the list of possible flights
flights[(origin,dest)].append((depart,arrive,int(price))) # append로 value 붙이고
2. In [3]:
Section 02. 해답 표현하기
In [4]:
# 키는 출발지와 목적지 데이터로
# Key(origin,dest)
# 상세 비행편 정보를 리스트값으로 하여, 딕셔너리에 로드
# value(depart,arrive,int(price))
def getminutes(t):
x=time.strptime(t,'%H:%M')
return x[3]*60+x[4]
# getminutes() : 특정 일시에서 경과한 시간을 분 단위로 계산하는 유틸리티 함수
# Next, 가족 구성원 각자가 탑승해야하는 비행기 정하기
# ppt
# 해답 표현방식 결정하기
# 다른 문제유형에도 잘 동작할 수 있도록 표현방식 선택
def printschedule(r):
for d in range(len(r)/2):
name=people[d][0]
origin=people[d][1]
out=flights[(origin,destination)][int(r[d*2])]
ret=flights[(destination,origin)][int(r[d*2+1])]
print '%10s%10s %5s-%5s $%3s %5s-%5s $%3s' % (name,origin,
out[0],out[1],out[2],
ret[0],ret[1],ret[2])
# 해답표현방식
# 숫자리스트로 표현
s=[1,4,3,2,7,3,6,3,2,4,5,3]
그날의, 첫번째 비행기 : 0
두번재 비행기 : 1
세번째 비행기 : 2
네번째 비행기 : 3
...
('Seymour','BOS')...,
[1,4,...]
세이모어는 보스턴에서 뉴욕으로가는 두번째 비행기를 타고,
그날 보스턴으로 돌아오는 다섯번째 비행기를 타고,
3. In [5]:
Section 03. 비용함수
Seymour BOS 8:04-10:11 $ 95 12:08-14:05 $142
Franny DAL 10:30-14:57 $290 9:49-13:51 $229
Zooey CAK 17:08-19:08 $262 10:32-13:16 $139
Walt MIA 15:34-18:11 $326 11:08-14:38 $262
Buddy ORD 9:42-11:32 $169 12:08-14:47 $231
Les OMA 13:37-15:08 $250 11:07-13:24 $171
# import optimization
s=[1,4,3,2,7,3,6,3,2,4,5,3]
# optimization.printschedle(s)
printschedule(s)
# 몇가지 문제점
1. 가격을 고려하지 않음
2. 레스(Les)가 오마하(Omaha)로 돌아가는 비행기편인 오전 6시까지만 놀 수 있음 (책의 예제값에서는)
# 다양한 속성들에게 가중치를 부여하는 방법과 어떤 일정이 최적인지 판단하는 방법이 필요
# 비용? 해당 문제를 푸는데 소비되는 값, 돈이 많이들수록 안 좋은 거임.
# 리턴되는 비용함수가 작을수록 최적의 함수
# 해답이 나쁠수록 큰 값을 리턴하기
# PPT
# 비용부가변수
티켓의 가격, 총 비행시간, 탑승 대기시간, 예약된 비행기를 놓쳐 발생하는 추가 비용, 자동차렌탈 추가비
용
# 변수들의 비교
: 결합하여 하나의 숫자로 만들기
최적화 함수에는 가치를 비용으로 판단
즉, 슈퍼에서 바나나, 시금치, 두부, 계란, 우유등을 "돈"이라는 단위로 환산하여 싸고 좋은것을 구매하는
것 처럼
변수들도 "비용"이라는 단위로 환산하여 가치를 판단
추가지출 항목(가정)
- 항공여행에서 시간을 줄여주는 서비스로 1분당 1달러의 비용이 소모
- 공항에서 기다리는 시간을 줄이기 위해 0.5달러 비용이 소모
- 자동차 렌탈 연장시 추가비용 소모
4. In [6]:
def schedulecost(sol):
totalprice=0
latestarrival=0 # 가장 늦은 도착시간이함 = 0, 0부터 시작해서 가장 늦은 도착시간을 찾기
earliestdep=24*60 # 가장빠른 출발시간 하루24시간*60 = 총 하루를 분단위로 표현, 하루의 끝자락부터 시
for d in range(len(sol)/2):
# 출발, 도착 비행편을 얻음
origin=people[d][1] # d는 0부터 시작, 가족 구성원들의 출발지 정보를 얻음
outbound=flights[(origin,destination)][int(sol[d*2])] # 떠나는 항공편, d*2로 2자리씩
returnf=flights[(destination,origin)][int(sol[d*2+1])] # 되돌아오는 항공편, d*2+1로
# 전체 가격은 모든 출발, 도착 비행편의 가격... 다 합치기
totalprice+=outbound[2]
totalprice+=returnf[2]
# 가장 늦은 도착 시간과 가장 이른 출발 시간을 추적하기, 변수로 인한 추가 지출을 생각해보기 위해 "추적"
if latestarrival<getminutes(outbound[1]): latestarrival=getminutes(outbound
if earliestdep>getminutes(returnf[0]): earliestdep=getminutes(returnf[0])
# 모든 사람들은 가장 늦게 도착하는 사람들을 공항에서 기다려야 함
# 또 이들은 도착하는 동시에 출발 비행편을 기다려야 함
totalwait=0
for d in range(len(sol)/2):
origin=people[d][1]
outbound=flights[(origin,destination)][int(sol[d*2])]
returnf=flights[(destination,origin)][int(sol[d*2+1])]
totalwait+=latestarrival-getminutes(outbound[1]) # 가족들 중 가장 늦게 출발하는 사람
totalwait+=getminutes(returnf[0])-earliestdep # 되돌아오는 시간 - 가족들 중 가장
# 렌탈한 자동차에 대해 추가로 비용이 지불되어야 하는지 점검. 있다면 $50 추가부담!
if latestarrival>earliestdep: totalprice+=50
# 총 비용 리턴
return totalprice+totalwait
# 비용부가변수
티켓의 가격, 총 비행시간, 탑승 대기시간, 예약된 비행기를 놓쳐 발생하는 추가 비용, 자동차렌탈 추가비
용
# 변수들의 비교
: 결합하여 하나의 숫자로 만들기
최적화 함수에는 가치를 비용으로 판단
즉, 슈퍼에서 바나나, 시금치, 두부, 계란, 우유등을 "돈"이라는 단위로 환산하여 싸고 좋은것을 구매하는
것 처럼
변수들도 "비용"이라는 단위로 환산하여 가치를 판단
추가지출 항목(가정)
- 항공여행에서 시간을 줄여주는 서비스로 1분당 1달러의 비용이 소모
- 공항에서 기다리는 시간을 줄이기 위해 0.5달러 비용이 소모
- 자동차 렌탈 연장시 추가비용 소모
5. In [7]:
Section 04. 무작위 검색
In [8]:
Out[7]:
4635
# reload(optimization)
# optimization.schedulecost(s)
schedulecost(s)
# Next, 비용 최소화
# 비용 최소화를 위한 알고리즘 소개
# ppt
# 정한 횟수만큼 무작위로 반복하여 결과를 도출하고 그 중 가장 효율적인 경우를 찾는 알고리즘
# domain
[(0, 8), (0, 8), (0, 8), (0, 8), (0, 8), (0, 8), (0, 8), (0, 8), (0, 8), (0,
8), (0, 8), (0, 8)]
- 출발 비행편 9개, 도착 비행편 9개
- 사람별로 2번씩 반복
# costf(): schedulecost()
- 함수가 인자로 전달되어 재활용이 용이
- 1000번 수행
- 많이 돌리면 돌릴 수록 경우의 수가 많아지므로 좀 더 최적화된 결과를 얻을 수 있음
def randomoptimize(domain,costf):
best=999999999 # 임의의 값으로 변수 초기화, 표현가능한 범위에서 젤 큰값
bestr=None
for i in range(0,1000): # 1000번 반복
# 무작위 해답을 생성
r=[float(random.randint(domain[i][0],domain[i][1]))
for i in range(len(domain))]
# 비용 구하기
cost=costf(r) # random값을 넣은 매개변수를 가지고 costf(schedulecost)함수를 실행
# 이제까지의 최적 값과 비교
if cost<best: # 가장 cost가 적은 경우를 저장
best=cost
bestr=r
return r
6. In [9]:
In [11]:
Section 05. 언덕등반
Out[9]:
6104
Seymour BOS 17:11-18:30 $108 15:25-16:58 $ 62
Franny DAL 9:08-12:12 $364 9:49-13:51 $229
Zooey CAK 18:35-20:28 $204 10:32-13:16 $139
Walt MIA 12:05-15:30 $330 9:25-12:46 $295
Buddy ORD 8:25-10:34 $157 18:33-20:22 $143
Les OMA 15:03-16:42 $135 15:07-17:21 $129
# reload(optimization)
# domain=[(0,8)] * (len(optimization.people)*2)
# s=optimizationrandomoptimize(domain, optimization.schedulecost)
# optimization.schedulecost(s)
domain=[(0,8)] * (len(people)*2)
s=randomoptimize(domain,schedulecost)
schedulecost(s)
# optimization.printschedule(s)
printschedule(s)
# 일반 무작위 검색방법은 이 경우에서는 비효율적
# ppt
# 무작위로 시작
1. 해답의 이웃해답을 찾음
2. 이웃해답 cost < 해답 cost
- 해답 = 이웃해답
- goto 1
3. 이웃해답 == 해답
- 해답 return
7. In [10]:
In [11]:
In [12]:
Out[11]:
5383
Seymour BOS 18:34-19:36 $136 18:24-20:49 $124
Franny DAL 18:26-21:29 $464 9:49-13:51 $229
Zooey CAK 18:35-20:28 $204 6:58- 9:01 $238
Walt MIA 11:28-14:40 $248 12:37-15:05 $170
Buddy ORD 15:58-18:40 $173 7:50-10:08 $164
Les OMA 12:18-14:56 $172 15:07-17:21 $129
def hillclimb(domain,costf):
# 무작위 해답 생성
sol=[random.randint(domain[i][0],domain[i][1]) # 무작위로 숫자 목록을 생성하여 초기해답 만
for i in range(len(domain))]
# Main loop
while 1:
# 이웃 해법의 리스트를 생성
neighbors=[]
for j in range(len(domain)): # 각 도메인의 항목마다 모든 이웃을 찾아 append()
# 각 방향별로 한 개씩 선택
if sol[j]>domain[j][0]:
neighbors.append(sol[0:j]+[sol[j]+1]+sol[j+1:])
if sol[j]<domain[j][1]:
neighbors.append(sol[0:j]+[sol[j]-1]+sol[j+1:])
# 이웃 중에서 최적 해답을 찾기
current=costf(sol) # 무작위 초기해답을 현재상태로 가정
best=current
for j in range(len(neighbors)): # 이웃을 돌아보면서
cost=costf(neighbors[j]) # 각 이웃들의 cost를 계산한다.
if cost<best:
best=cost # 더 나은 경우를 저장한다.
sol=neighbors[j]
# 개선되지 않으면, 그게바로 최적해답
if best==current: # 메인 Loop가 1이르로 계속하여 반복하다가 더 이상 curren
break
return sol
# s=optimization.hillclimb(domain, optimization.schedulecost)
# optimization.schedulecost(s)
# optimization.printschedule(s)
s=hillclimb(domain, schedulecost)
schedulecost(s)
printschedule(s)
8. Section 06. 시뮬레이티드 어닐링
In [13]:
# Hill Climb 알고리즘의 단점
# 국소최소점(local minimum) vs 전역최소점(global minimum)
# 경사를 따라가는 hillclimb알고리즘의 경우 마지막 해답은 local minimum이 된다는 한계가 있음
# 궁극적으로 최적화는 전체 어느 위치에서나 해답이 최적화된 global minimum을 지향해야 함
# ppt
# 국소최소(local minimum)를 회피하기
# 어닐링(Annealing), 합금을 가열한 후 천천히 냉각시키는 과정
- 무작위 해답부터 시작
- 온도를 표현하는 변수로 고온에서 시작하여 점차 내려간다.
# p=e^(-(highcost-lowcost)/temperature)
def annealingoptimize(domain,costf,T=10000.0,cool=0.95,step=1): # T(temperature),
# 무작위 값으로 초기화하기
vec=[float(random.randint(domain[i][0],domain[i][1])) # 무작위로 숫자 목록을
for i in range(len(domain))]
while T>0.1: # 최소 온도가 영상 0.1 정도는 되어야 한다. 온도가 거의 0에 가까울때까지의 반복
# 해답내의 무작위(0~len(domain)-1) 인덱스 중 한개를 선택
i=random.randint(0,len(domain)-1)
# 변경할 방향을 선택. -step~step 사이의 랜덤값
dir=random.randint(-step,step)
# 변경할 값 중 하나로 새로운 리스트 생성
vecb=vec[:]
vecb[i]+=dir
if vecb[i]<domain[i][0]: vecb[i]=domain[i][0] # 새로운 값 넣기
elif vecb[i]>domain[i][1]: vecb[i]=domain[i][1]
# 현재 비용과 새로운 비용을 계산
ea=costf(vec) # 현재 해답(vec)의 비용 계산
eb=costf(vecb) # 새로운 해답(vecb)의 비용 계산
p=pow(math.e,(-eb-ea)/T) # 확률계산식
# 더 좋은지, 또는 확률이 차단 기준 이하인지 판단
if (eb<ea or random.random()<p): # 새로운 해답의 비용이 더 좋거나, 확률이 차단기준 이하라면,
vec=vecb # 현재 해답으로
# 온도 내리기
T=T*cool
return vec
9. In [14]:
In [15]:
Section 07. 유전자 알고리즘
Out[14]:
3798
Seymour BOS 17:11-18:30 $108 10:33-12:03 $ 74
Franny DAL 13:54-18:02 $294 17:14-20:59 $277
Zooey CAK 8:27-10:45 $139 10:32-13:16 $139
Walt MIA 11:28-14:40 $248 12:37-15:05 $170
Buddy ORD 15:58-18:40 $173 10:33-13:11 $132
Les OMA 9:15-12:03 $ 99 11:07-13:24 $171
# p=pow(math.e,(-eb-ea)/T) (pow : 파이썬 제곱함수)
- T(온도)가 올라갈수록 데이터의 양이 많아짐
- 온도가 거의 0이 될때까지 반복
# reload(optimization)
# domain=[(0,8)] * (len(optimization.people)*2)
# s=optimizationrandomoptimize(domain, optimization.schedulecost)
# optimization.schedulecost(s)
s=annealingoptimize(domain, schedulecost)
schedulecost(s)
printschedule(s)
# 초기온도와 냉각속도를 달리하며 테스트
# ppt
# 기존과 동일한 크기인 새로운 해답은,
1. 최적 해답을 무작위로 돌연변이시키거나, 번식시켜 만듬
2. 새로운 개체군에 순서를 매긴 후 다른 갲체군을 만드는 과정을 계속반복
3. 정해진 횟수만큼 계속하거나 몇 세대에 걸쳐 개선이 없을 때까지 반복
# 인자
- popsize : 개체군의 크기
- mutprob : 개체군의 새로운 멤버에 교배보다 돌연변이가 발생할 확률
- elite : 좋은 해답으로 간주되어 다음 세대로 전달될 개체군의 비율
- maxiter : 진화시킬 세대 수
10. In [16]:
In [17]:
def geneticoptimize(domain,costf,popsize=50,step=1,
mutprob=0.2,elite=0.2,maxiter=100): # 진화시킬 새대 수가 100
# Mutation Operation, 돌연변이 연산
def mutate(vec):
i=random.randint(0,len(domain)-1) # i, 해답내의 무작위(0~len(domain)-1) 인덱스 중에서
if random.random()<0.5 and vec[i]>domain[i][0]:
return vec[0:i]+[vec[i]-step]+vec[i+1:]
elif vec[i]<domain[i][1]:
return vec[0:i]+[vec[i]+step]+vec[i+1:]
# Crossover Operation, 교배연산
def crossover(r1,r2):
i=random.randint(1,len(domain)-2) # i, 해답내의 무작위(1~len(domain)-2) 인덱스 중에서
return r1[0:i]+r2[i:]
# Build the initial population, 초기 개체군 생성
pop=[]
for i in range(popsize):
vec=[random.randint(domain[i][0],domain[i][1])
for i in range(len(domain))]
pop.append(vec) # append로 추가
# How many winners from each generation?, 각 세대의 선발된 엘리트 개체군 수를 계산
topelite=int(elite*popsize) # toplite = 0.2*50 = 10
# Main loop
for i in range(maxiter): # 진화시킬 새대 수가 100, 100번 반복
scores=[(costf(v),v) for v in pop]
scores.sort()
ranked=[v for (s,v) in scores]
# Start with the pure winners, 생존자들로 시작
pop=ranked[0:topelite]
# Add mutated and bred forms of the winners, 생존자에 돌연변이와 번식을 수행
while len(pop)<popsize:
if random.random()<mutprob:
# Mutation, 돌연변이
c=random.randint(0,topelite)
pop.append(mutate(ranked[c]))
else:
# Crossover, 교배
c1=random.randint(0,topelite)
c2=random.randint(0,topelite)
pop.append(crossover(ranked[c1],ranked[c2]))
# Print current best score
print scores[0][0]
return scores[0][1]