# -*- coding: utf-8 -*- from myutil import consolePrintWithLineNumber as c from myutil import filePrint ''' Created on 2015. 9. 3. @author: Administrator ''' ''' 5장 최적화 ''' ''' 122p 5-1절 단체여행 - 부제:서울에서 만나요!!! 한국식으로 명칭을 변경한다. LGA : 서울 BOS : 대전 DAL : 부산 CAK : 군산 ORD : 강릉 OMA : 세종 MIA : 대구 CAK : 전주 도시 구간별로 각 10개의 시간표가 있다. ''' import time import random import math people = [('아빠', '대전'), ('엄마', '부산'), ('누나', '군산'), ('남동생', '대구'), ('여동생', '강릉'), ('나', '세종')] # Laguardia destination = '서울' total_flights = {} for line in open('schedule2.txt', 'r', encoding='UTF-8'): origin, dest, depart, arrive, price = line.strip().split(',') total_flights.setdefault((origin, dest), []) total_flights[(origin, dest)].append((depart, arrive, int(price))) # 6:05 시간대를 분으로 환산 - 나중에 시간대 비교에 사용 def getminutes(t): x = time.strptime(t, '%H:%M') return x[3] * 60 + x[4] c(total_flights) # 124p 5-2 해답표현하기 def printschedule(r): for d in range(int(len(r) / 2)): # len(r) / 2 결과가 float이다. 그런데 range(.)함수는 int를 받아야 한다. 그래서 int(.)로 변환하는 코드 추가 name = people[d][0] origin = people[d][1] out = total_flights[(origin, destination)][int(r[d])] ret = total_flights[(destination, origin)][int(r[d + 1])] c('%10s%10s %5s-%5s $%3s %5s-%5s $%3s' % (name, origin, out[0], out[1], out[2], ret[0], ret[1], ret[2])) # 1, 4 : 첫번째 사람이 자신에게 해당하는 비행기중 2번째 출국, 5번째 귀국행 비행기를 사용한다. sol = [1, 4, 3, 2, 7, 3, 6, 3, 2, 4, 5, 3] printschedule(sol) # 125p 5-3 비용함수 - 비용을 최소화하자!! - 비용을 수치화한다. def schedulecost(sol): total_price = 0 latest_arrival = 0 # 0시(하루의 처음) earliest_dep = 24 * 60 # 24시(하루의 마지막) for d in range(int(len(sol) / 2)): origin = people[d][1] outbound = total_flights[(origin, destination)][int(sol[d])] # 출국 비행기 정보 returnf = total_flights[(destination, origin)][int(sol[d + 1])] # 귀국 비행기 정보 ''' outbound[0] : 출발시각 outbound[1] : 도착시각 outbound[2] : 가격 ''' total_price += outbound[2] # 가격 total_price += returnf[2] # 가격 if latest_arrival < getminutes(outbound[1]): latest_arrival = getminutes(outbound[1]) # 가장 늦은 도착(destination에) if earliest_dep > getminutes(returnf[0]): earliest_dep = getminutes(returnf[0]) # 가장 이른 출발(자기 origin으로) ''' 공식적인 일정 : 다같이 모였을 때 latest_arrival ~ earliest_dep 예) 도착(latest_arrival) 공식일정(18:10~12:10) 출발(earliest_dep) 아빠 14:10 14:10 엄마 16:10 ---------------12:10 누나 13:10 15:00 남동생 17:00 17:00 여동생 16:20 16:00 나 18:10---------------- 15:20 ''' # 모든 사람은 가장 늦게 공항에 도착하는 사람을 기다려야 한다. # 또 이들은 출발 비행편을 기다려야 한다. # 공식일정 이외의 시간들은 기다리는 시간이다.....;;; 이 시간을 최소화 해야 한다. totalwait = 0 for d in range(int(len(sol) / 2)): origin = people[d][1] outbound = total_flights[(origin, destination)][int(sol[d])] returnf = total_flights[(destination, origin)][int(sol[d + 1])] totalwait += latest_arrival - getminutes(outbound[1]) totalwait += getminutes(returnf[0]) - earliest_dep # ??? 항상 True 아닌가? 렌탈 하루 연장?? # latest_arrival - 18:10 # earliest_dep - 12:10 if latest_arrival > earliest_dep: total_price += 50 # total_price(달러), totalwait(분) return total_price + totalwait c(schedulecost(sol)) # 128p 5-4 무작위 검색 def randomoptimize(domain, costfunction): best = 999999999 bestr = None for i in range(0, 1000): # 50000으로 하면 best 결과값으로 2855도 나온다.(1000으로 하면 보통 3000...) 낮을수록 좋음 # random.randint(0,8)은 0부터 8까지 정수 r = [float(random.randint(domain[i][0], domain[i][1])) for i in range(len(domain))] # len(domain)는 12 # c(r) # [6.0, 4.0, 1.0, 6.0, 2.0, 2.0, 0.0, 0.0, 4.0, 7.0, 4.0, 6.0] cost = costfunction(r) if cost < best: best = cost bestr = r # ??? return best, bestr # (0, 9)은 min, max 범위. 여기서는 사람마다 모두 0,8로 min, max값이 같은 값이지만, 실제로는 사람마다 탈 수 있는 비행기의 시간대 개수가 다르므로 min, max 가 사람마다 다른 값을 가질듯... domain = [(0, 9)] * (len(people) * 2) # [(0, 9), (0, 9), (0, 9), (0, 9), (0, 9), (0, 9), (0, 9), (0, 9), (0, 9), (0, 9), (0, 9), (0, 9)] best_cost, sol = randomoptimize(domain, schedulecost) # randomoptimize안에 이미 cost 값을 계산하므로 schedulecost 함수를 실행할 필요가 없다. c(best_cost, sol) printschedule(sol) ''' 129p 5-5 언덕등반 > 별로 효과를 모르겠다. 실행할 때마다 값이 다르게 나오니... ''' def hillclimb(domain, costf): fp = filePrint('output.txt') # 무작위 해답을 하나 선정 sol = [random.randint(domain[i][0], domain[i][1]) for i in range(len(domain))] c(sol) while 1: neighbors = [] # neighbors 찾기(에러 발생 : list index out of range) # 원인 : min, max 범위가 잘못되어 있다. 소스 수정함 for j in range(len(domain)): # domain[j][0]은 0 # domain[j][0]은 9 if sol[j] == domain[j][0]: neighbors.append(sol[0:j] + [sol[j] + 1] + sol[j + 1:]) # next 이웃 elif sol[j] == domain[j][1]: neighbors.append(sol[0:j] + [sol[j] - 1] + sol[j + 1:]) # before 이웃 elif domain[j][0] < sol[j] and sol[j] < domain[j][1]: neighbors.append(sol[0:j] + [sol[j] + 1] + sol[j + 1:]) # next 이웃 neighbors.append(sol[0:j] + [sol[j] - 1] + sol[j + 1:]) # before 이웃 ''' neighbors 를 보면, 12자리의 각 하나씩 +1, -1을 한 값을 가지고 있다.(나머지11자리를 그대로 유지). 총 12*2 = 24개의 리스트이다. sol이 [7, 3, 4, 3, 6, 4, 6, 1, 2, 7, 4, 7] 이라면,,, neighbors는 [ [8, 3, 4, 3, 6, 4, 6, 1, 2, 7, 4, 7], [6, 3, 4, 3, 6, 4, 6, 1, 2, 7, 4, 7], [7, 4, 4, 3, 6, 4, 6, 1, 2, 7, 4, 7], [7, 2, 4, 3, 6, 4, 6, 1, 2, 7, 4, 7], ,,,, ] ''' cost_sol = costf(sol) best = cost_sol for j in range(len(neighbors)): # neighbors 리스트의 모든 경우에 대한 cost를 계산 cost_neighbor = costf(neighbors[j]) # fp.c(cost_neighbor) if cost_neighbor < best: best = cost_neighbor sol = neighbors[j] # neighbors로 cost를 검색해서 개선되지 않은 경우 if best == cost_sol: break # if best < 2500: # 가능한가 싶어서 테스트함 # break return best, sol best, sol = hillclimb(domain, schedulecost) c(best, sol) # 132p 5-6 시뮬레이티드 어닐링 def annealingoptimize(domain, costf, T=10000.0, cool=0.95, step=1): # 무작위 초기값 sol = [float(random.randint(domain[i][0], domain[i][1])) for i in range(len(domain))] while T > 0.1: # 한개의 인덱스 선택 i = random.randint(0, len(domain) - 1) # 변경할 방향 선택 dir = random.randint(-step, step) sol2 = sol[:] sol2[i] += dir # 범위를 넘어가면 마지막 값으로 매칭 if sol2[i] < domain[i][0]: sol2[i] = domain[i][0] elif sol2[i] > domain[i][1]: sol2[i] = domain[i][1] ea = costf(sol) eb = costf(sol2) p = pow(math.e, (-eb - ea) / T) # 더 좋거나 확률이 차단 기준 이하인 경우 if (eb < ea or random.random() < p): sol = sol2 # 온도를 줄인다. T = T * cool return sol sol = annealingoptimize(domain, schedulecost) c(schedulecost(sol)) ''' 134p 5-7 유전자 알고리즘 전혀 수학적이지 않아서 로직을 이해하기는 쉽다. 다분히 우연을 가장했지만.. 나름 성능은 괜찮다. 상위 20%만 기억하고, 나머지는 버리는 씁쓸한 알고리즘이지만... 어쩌겠나... 1등만 기억하는 ㄷㄹㅇ 세상이 생각나네-_-;;; > 그래서,,, 상위 20%가 아닌 하위 20% 내에서 돌연변이와 교배연산을 시켜보았다. 결과는 나쁘지 않았다.... > 이것이야 말로 개천에서 용나는 격인가??? ''' def geneticoptimize(domain, costfunction, popsize=50, step=1, mutprob=0.2, elite=0.2, maxiter=100): # 돌연변이 생성(111222333444 -> 118222333444) def mutate(vec): i = random.randint(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:] # 교배연산(111222333, 555666777 -> 111222777) def crossover(r1, r2): i = random.randint(1, len(domain) - 2) return r1[0:i] + r2[i:] # 초기 실험 개체 생성 50개 pop = [] for i in range(popsize): vec = [random.randint(domain[i][0], domain[i][1]) for i in range(len(domain))] pop.append(vec) # 엘리트로 선발할 개체수 선정(50개의 0.2 즉, 50의 20% 를 엘리트로. 10개) topelite = int(elite * popsize) # maxiter 진화시킬 세대 수 100, 100회 진화시킴. for i in range(maxiter): cost_sol = [(costfunction(v), v) for v in pop if v != None] # pop 안에 None 이 들어있다.왜?? cost_sol.sort() ranked = [sol for (score, sol) in cost_sol] # 엘리트 선발(20% 생존) # pop = ranked[0:topelite] # 하위 20%를 생존시켜보자 pop = ranked[len(ranked) - topelite:] while len(pop) < popsize: if random.random() < mutprob: # mutprob : 개체군의 새로운 멤버에, 교배보다 돌연변이가 발생할 확률 20% num = random.randint(0, topelite) # topelite : 10 pop.append(mutate(ranked[num])) # 엘리트 10개중 임의 1개 선택해서 돌연변이 1개 생성해서 추가 else: c1 = random.randint(0, topelite) c2 = random.randint(0, topelite) pop.append(crossover(ranked[c1], ranked[c2])) # 엘리트 10개중 2개를 임의 선택해서 교배 1개 생성해서 추가 c(cost_sol[0][0]) return cost_sol[0][1] # 137p geneticoptimize(domain, schedulecost)
57 > {('군산', '서울'): [('6:08', '8:06', 224), ('8:27', '10:45', 139), ('9:15', '12:14', 247), ('10:53', '13:36', 189), ('12:08', '14:59', 149), ('13:40', '15:38', 137), ('15:23', '17:25', 232), ('17:08', '19:08', 262), ('18:35', '20:28', 204), ('20:30', '23:11', 114)], ('강릉', '서울'): [('6:05', '8:32', 174), ('8:25', '10:34', 157), ('9:42', '11:32', 169), ('11:01', '12:39', 260), ('12:44', '14:17', 134), ('14:22', '16:32', 126), ('15:58', '18:40', 173), ('16:43', '19:00', 246), ('18:48', '21:45', 246), ('19:50', '22:24', 269)], ('서울', '대전'): [('6:39', '8:09', 86), ('8:23', '10:28', 149), ('9:58', '11:18', 130), ('10:33', '12:03', 74), ('12:08', '14:05', 142), ('13:39', '15:30', 74), ('15:25', '16:58', 62), ('17:03', '18:03', 103), ('18:24', '20:49', 124), ('19:58', '21:23', 142)], ('대구', '서울'): [('6:25', '9:30', 335), ('7:34', '9:40', 324), ('9:15', '12:29', 225), ('11:28', '14:40', 248), ('12:05', '15:30', 330), ('14:01', '17:24', 338), ('15:34', '18:11', 326), ('17:07', '20:04', 291), ('18:23', '21:35', 134), ('19:53', '22:21', 173)], ('서울', '부산'): [('6:09', '9:49', 414), ('7:57', '11:15', 347), ('9:49', '13:51', 229), ('10:51', '14:16', 256), ('12:20', '16:34', 500), ('14:20', '17:32', 332), ('15:49', '20:10', 497), ('17:14', '20:59', 277), ('18:44', '22:42', 351), ('19:57', '23:15', 512)], ('부산', '서울'): [('6:12', '10:22', 230), ('7:53', '11:37', 433), ('9:08', '12:12', 364), ('10:30', '14:57', 290), ('12:19', '15:25', 342), ('13:54', '18:02', 294), ('15:44', '18:55', 382), ('16:52', '20:48', 448), ('18:26', '21:29', 464), ('20:07', '23:27', 473)], ('서울', '군산'): [('6:58', '9:01', 238), ('8:19', '11:16', 122), ('9:58', '12:56', 249), ('10:32', '13:16', 139), ('12:01', '13:41', 267), ('13:37', '15:33', 142), ('15:50', '18:45', 243), ('16:33', '18:15', 253), ('18:17', '21:04', 259), ('19:46', '21:45', 214)], ('대전', '서울'): [('6:17', '8:26', 89), ('8:04', '10:11', 95), ('9:45', '11:50', 172), ('11:16', '13:29', 83), ('12:34', '15:02', 109), ('13:40', '15:37', 138), ('15:27', '17:18', 151), ('17:11', '18:30', 108), ('18:34', '19:36', 136), ('20:17', '22:22', 102)], ('세종', '서울'): [('6:11', '8:31', 249), ('7:39', '10:24', 219), ('9:15', '12:03', 99), ('11:08', '13:07', 175), ('12:18', '14:56', 172), ('13:37', '15:08', 250), ('15:03', '16:42', 135), ('16:51', '19:09', 147), ('18:12', '20:17', 242), ('20:05', '22:06', 261)], ('서울', '강릉'): [('6:03', '8:43', 219), ('7:50', '10:08', 164), ('9:11', '10:42', 172), ('10:33', '13:11', 132), ('12:08', '14:47', 231), ('14:19', '17:09', 190), ('15:04', '17:23', 189), ('17:06', '20:00', 95), ('18:33', '20:22', 143), ('19:32', '21:25', 160)], ('서울', '대구'): [('6:33', '9:14', 172), ('8:23', '11:07', 143), ('9:25', '12:46', 295), ('11:08', '14:38', 262), ('12:37', '15:05', 170), ('14:08', '16:09', 232), ('15:23', '18:49', 150), ('16:50', '19:26', 304), ('18:07', '21:30', 355), ('20:27', '23:42', 169)], ('서울', '세종'): [('6:19', '8:13', 239), ('8:04', '10:59', 136), ('9:31', '11:43', 210), ('11:07', '13:24', 171), ('12:31', '14:02', 234), ('14:05', '15:47', 226), ('15:07', '17:21', 129), ('16:35', '18:56', 144), ('18:25', '20:34', 205), ('20:05', '21:44', 172)]} 67 > 아빠 대전 8:04-10:11 $ 95 12:08-14:05 $142 67 > 엄마 부산 12:19-15:25 $342 10:51-14:16 $256 67 > 누나 군산 10:53-13:36 $189 9:58-12:56 $249 67 > 남동생 대구 9:15-12:29 $225 16:50-19:26 $304 67 > 여동생 강릉 16:43-19:00 $246 10:33-13:11 $132 67 > 나 세종 11:08-13:07 $175 15:07-17:21 $129 133 > 5285 154 > (3553, [4.0, 3.0, 4.0, 3.0, 4.0, 4.0, 1.0, 0.0, 5.0, 0.0, 4.0, 5.0]) 67 > 아빠 대전 12:34-15:02 $109 10:33-12:03 $ 74 67 > 엄마 부산 10:30-14:57 $290 12:20-16:34 $500 67 > 누나 군산 12:08-14:59 $149 10:32-13:16 $139 67 > 남동생 대구 11:28-14:40 $248 12:37-15:05 $170 67 > 여동생 강릉 12:44-14:17 $134 12:08-14:47 $231 67 > 나 세종 12:18-14:56 $172 8:04-10:59 $136 165 > [5, 9, 7, 7, 6, 2, 9, 6, 1, 7, 5, 1] 214 > (2868, [7, 6, 7, 6, 6, 7, 6, 6, 1, 7, 5, 1]) 249 > 2987 304 > 4138 304 > 4138 304 > 4070 304 > 4035 304 > 4035 304 > 3917 304 > 3793 304 > 3793 304 > 3675 304 > 3616 304 > 3616 304 > 3616 304 > 3616 304 > 3579 304 > 3412 304 > 3412 304 > 3375 304 > 3375 304 > 3324 304 > 3324 304 > 3016 304 > 3324 304 > 3324 304 > 3324 304 > 3324 304 > 3324 304 > 3324 304 > 3324 304 > 3324 304 > 3016 304 > 3016 304 > 3016 304 > 3016 304 > 3016 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789 304 > 2789
언덕 등반 결과
'python 및 머신러닝 > 집단지성 프로그래밍' 카테고리의 다른 글
[Programming Collective Intelligence] - 집단지성 프로그래밍 12장 알고리즘 요약 (0) | 2016.12.09 |
---|---|
[Programming Collective Intelligence] - 집단지성 프로그래밍 4장 정리 (0) | 2015.09.04 |
[Programming Collective Intelligence] - 집단지성 프로그래밍 4장 7절 클릭 학습 (0) | 2015.09.02 |
[Programming Collective Intelligence] - 집단지성 프로그래밍 2,3,4장 정리 (0) | 2015.09.01 |
[Programming Collective Intelligence] - 집단지성 프로그래밍 4장 검색과 랭킹 (0) | 2015.08.31 |