定义: 通过尝试许多不同题解并给这些题解打分以确定其质量的方式来找到一个问题的最优解。

本章将给出两个例子:
1. 如何根据人们的偏好来分配有限的资源
2. 如何用最少的交叉线来可视化社会网络

组团旅游

假设家庭成员来自全国各地,并且希望在纽约会面。他们将在同一天到达,同一天离开,而且搭乘相同的交通工具往返机场。每天有很多航班,起飞时间、价格以及续航时间都不尽相同。

下面,我们将为来自不同地方去往同一地方的人安排一次较优的旅行。

在达到以上目标的前提下,我们需要考虑的因素还包括,总的票价、总的候机时间和飞行时间等。

新建一个文件,加入如下代码:

import time
import random
import mathpeople = [('Seymour','BOS'),('Franny','DAL'),('Zooey','CAK'),('Walt','MIA'),('Buddy','ORD'),('Les','OMA')]
# Laguardia
destination='LGA'

将schedule.txt中的数据载入到一个字典中,以起止点为键,以可能的航班详情明细为值。

flights={}
#
for line in file('schedule.txt'):origin,dest,depart,arrive,price=line.strip().split(',')flights.setdefault((origin,dest),[])# Add details to the list of possible flightsflights[(origin,dest)].append((depart,arrive,int(price)))

定义工具函数getminutes(),用于计算某个给定时间在一天中的分钟数。

def getminutes(t):x=time.strptime(t,'%H:%M')return x[3]*60+x[4]

描述题解

优化函数应该是通用的,能应用于许多不同类型的问题上。

首先,我们要确定潜在的题解该如何表达。

一种通用的表达方式,就是数字序列。例如:s=[1,4,3,2,7,3,6,3,2,4,5,3],表示Seymour搭乘当天第1趟航班出发,第4趟航班回家,Franny搭乘当天第3趟航班出发,第2趟航班回家…

函数printschedule用于将行程计划打印成表格,便于观察。

def printschedule(r):for d in range(len(r)/2):name=people[d][0]origin=people[d][1]out=flights[(origin,destination)][int(r[2*d])]  #纠错ret=flights[(destination,origin)][int(r[2*d+1])]print '%10s%10s %5s-%5s $%3s %5s-%5s $%3s' % (name,origin,out[0],out[1],out[2],ret[0],ret[1],ret[2])

输入以下命令:

import optimization
s=[1,4,3,2,7,3,6,3,2,4,5,3]
optimization.printschedule(s)

得到:

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

成本函数

成本函数是用优化算法解决问题的关键。

任何优化算法的目标,就是要寻找一组能使得成本函数的返回结果达到最小化输出。

考查一些在组团旅游中能被度量的变量:

价格    所有航班的总票价,也可以是考虑财务因素之后的加权平均。旅行时间    每个人在飞机上花费的总时间。等待时间在机场等待其它成员到达的时间。出发时间不能出发得太早,因为这会白白浪费旅行者的时间。汽车租用时间如果集体租用一辆汽车,他们必须按时归还以免多付一天的租金。

接下来,将找到一种方法将它们组合在一起形成一个值。

将函数schedulecost()加入到optimization类中。

def schedulecost(sol):totalprice = 0latestarrival = 0earliestdep = 24 * 60for d in range(len(sol) / 2):# 得到往返航班origin = people[d][1]outbound = flights[(origin, destination)][int(sol[2*d])]returnf = flights[(destination, origin)][int(sol[2*d + 1])]# 计算所有往返航班的总价格totalprice += outbound[2]totalprice += returnf[2]# 记录最晚到达时间和最早离开时间if latestarrival < getminutes(outbound[1]): latestarrival = getminutes(outbound[1])if earliestdep > getminutes(returnf[0]): earliestdep = getminutes(returnf[0])# 大家必须在机场等待最后一个到达者# 同样,他们也会在相同时间到达机场,等待他们的航班totalwait = 0for d in range(len(sol) / 2):origin = people[d][1]outbound = flights[(origin, destination)][int(sol[2*d])]returnf = flights[(destination, origin)][int(sol[2*d + 1])]totalwait += latestarrival - getminutes(outbound[1])totalwait += getminutes(returnf[0]) - earliestdep# 判断该题解是否需要多付一天的汽车租金if latestarrival < earliestdep: totalprice += 50return totalprice + totalwait

随机搜索

它不是一种很好的优化算法,但它对于我们理解其它算法的原理是十分有帮助的,是用来评估其它算法优劣的基线(baseline)。

函数randomoptimize()接受两个参数。

1.Domain,为一个由二元组构成的列表,它指定了每个变量的取值范围(最大/最小值)。本例中,由于往返航班都是10,故而domain为(0,9)

2.costf,成本函数,本例中为schedulecost。

本例中将随机产生1000次猜测

def randomoptimize(domain, costf):best = 999999999bestr = Nonefor i in range(0, 1000):# 创建一个随机题解r = [float(random.randint(domain[i][0], domain[i][1]))for i in range(len(domain))]# 得到花销cost = costf(r)# 与目前最优解比较if cost < best:best = costbestr = rreturn r

输入以下命令:

reload(optimization)
domain=[(0,8)]*(len(optimization.people)*2)
s=optimization.randomoptimize(domain,optimization.schedulecost)
print optimization.schedulecost(s)
print optimization.printschedule(s)

可以找到一个表现尚可的解(也许不是最优的):

6444Seymour       BOS 17:11-18:30 $108 17:03-18:03 $103Franny       DAL  6:12-10:22 $230  9:49-13:51 $229Zooey       CAK  8:27-10:45 $139 18:17-21:04 $259Walt       MIA  9:15-12:29 $225 16:50-19:26 $304Buddy       ORD  6:05- 8:32 $174  9:11-10:42 $172Les       OMA  9:15-12:03 $ 99 16:35-18:56 $144

爬山法

随机尝试没有充分利用已发现的优解,相当低效。

一种替代方法便是爬山法。爬山法以一个随机解开始,然后在其临近的解集中寻找更好的解。

先从一个随机的行程安排开始,然后再找与之相邻的行程安排。在本例中,即对每个相邻行程安排都进行成本计算,具有最低成本的安排将成为新的题解。重复直到不再变化为止。

将hillclimb函数加入optimization类中:

def hillclimb(domain, costf):# 创建一个随机题解sol = [random.randint(domain[i][0], domain[i][1])for i in range(len(domain))]# 循环while 1:# 创建邻居解neighbors = []for j in range(len(domain)):# 在每个方向相对于原值偏离if sol[j] > domain[j][0]:neighbors.append(sol[0:j] + [(sol[j] + 1) if ((sol[j] + 1)<domain[j][1]) else domain[j][0]] + sol[j + 1:])if sol[j] < domain[j][1]:neighbors.append(sol[0:j] + [(sol[j] - 1) if ((sol[j] - 1)>domain[j][0]) else domain[j][1]] + sol[j + 1:])# 在相邻点中寻找最优解current = costf(sol)best = currentfor j in range(len(neighbors)):cost = costf(neighbors[j])if cost < best:best = costsol = neighbors[j]# 直到不再变化为止if best == current:breakreturn sol

执行该函数:

reload(optimization)
s=optimization.hillclimb(domain,optimization.schedulecost)
print optimization.schedulecost(s)
print optimization.printschedule(s)

得到结果:

3554Seymour       BOS 18:34-19:36 $136  8:23-10:28 $149Franny       DAL 18:26-21:29 $464  9:49-13:51 $229Zooey       CAK 18:35-20:28 $204  8:19-11:16 $122Walt       MIA 18:23-21:35 $134  6:33- 9:14 $172Buddy       ORD 19:50-22:24 $269  6:03- 8:43 $219Les       OMA 20:05-22:06 $261  8:04-10:59 $136

从上图中可以看出,有可能只是得到一个局部最优解。解决这一问题的方法被称为随机重复爬山法,即让爬山法以多个随机生成的初始解为起点运行若干次,希望其中的一个解能逼近全局最小值。

模拟退火算法

算法的最关键部分在于:如果新的成本值更低,则新的题解就会成为当前题解。这与爬山法很相似。不过,如果成本值更高,则新的题解仍将可能成为当前题解。这就从一定程度上避免了局部最小值的问题。

它之所以管用,不仅因为它总是会接受一个更优的解,而且因为他在退火过程的开始阶段会接受表现较差的解。随着算法的不断进行,其越来越不可能接受较差的解,直到最后它只会接受更优的解。其数学公式如下:

​ p=e (-(highcost–lowcost)/temperature)

由公式可见,温度开始非常高,指数总将接近于0,所以概率几乎为1,随着温度的递减,高低成本值之间的差异越来越重要——差异越大,概率越低,因此该算法只倾向于稍差的解而不是非常差的解。

def annealingoptimize(domain,costf,T=10000.0,cool=0.95,step=1):# 随机初始化值vec=[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)# 创建一个代表题解的新列表,改变其中的一个值vecb=vec[:]vecb[i]+=dirif vecb[i]<domain[i][0]: vecb[i]=domain[i][0]elif vecb[i]>domain[i][1]: vecb[i]=domain[i][1]# 计算当前成本和新的成本ea=costf(vec)eb=costf(vecb)p=pow(math.e,(-eb-ea)/T)# 判断是否为更好的解,或者趋向最优解可能的临界解?if (eb<ea or random.random()<p):vec=vecb      # 降低温度T=T*coolreturn vec

运行如下代码:

reload(optimization)
s=optimization.annealingoptimize(domain,optimization.schedulecost)
print optimization.schedulecost(s)
optimization.printschedule(s)

输出结果:

3376Seymour       BOS 12:34-15:02 $109 10:33-12:03 $ 74Franny       DAL 10:30-14:57 $290 10:51-14:16 $256Zooey       CAK  8:27-10:45 $139 13:37-15:33 $142Walt       MIA 11:28-14:40 $248 15:23-18:49 $150Buddy       ORD  9:42-11:32 $169 10:33-13:11 $132Les       OMA  9:15-12:03 $ 99 15:07-17:21 $129

遗传算法

运行过程:先随机生成一组解,称之为种群。优化过程的每一步,算法会计算整个种群的成本函数,从而得到一个有关题解的有序列表。

Solution Cost
[7, 5, 2, 3, 1, 6, 1, 6, 7, 1, 0, 3] 4394
[7, 2, 2, 2, 3, 3, 2, 3, 5, 2, 0, 8] 4661
[0, 4, 0, 3, 8, 8, 4, 4, 8, 5, 6, 1] 7845
[5, 8, 0, 2, 8, 8, 8, 2, 1, 6, 6, 8] 8088

在对题解进行排序之后,我们将创建出一个新的种群,称之为下一代。

首先,将当前种群中位于最顶端的题解加入其所在的新种群中,称为精英选拔。

新种群中的余下部分是由修改最优解后形成的全新解组成。

两种修改题解的方法:

  • 变异

    较为简单。通常是对一个现有解进行微小的、简单的、随机的改变。

    [7, 5, 2, 3, 1, 6, 1, 6, 7, 1, 0, 3]—–>[7, 5, 2, 3, 1, 6, 1, 5, 7, 1, 0, 3]

    [7, 2, 2, 2, 3, 3, 2, 3, 5, 2, 0, 8]—–>[7, 2, 2, 2, 3, 3, 2, 3, 5, 2, 1, 8]

  • 交叉(配对)

    选取最优解中的两个解,然后将它们按某种方式进行结合。

    [7, 5, 2, 3, 1, 6, 1, 6,| 7, 1, 0, 3]————>

    ​ [7, 5, 2, 3, 1, 6, 1, 6, |5, 2, 0, 8]

    [7, 2, 2, 2, 3, 3, 2, 3,| 5, 2, 0, 8]————————————–>

一个新的种群是通过对最优解进行随机变异和配对处理构造出来的,其大小通常与旧种群相同。尔后,重复这一过程——新的种群经过排序,构造一个新的种群。达到指定的迭代次数后,或者经过数代后题解都没有得到改善,整个过程就结束了。

将geneticoptimize加入optimization.py中:

# popsize 种群大小 mutprob种群新成员是由变异而非交叉得来的概率
# elite 种群被认为是优解且允许被传入下一代的部分 maxiter 需运行多少代
def geneticoptimize(domain,costf,popsize=50,step=1,mutprob=0.2,elite=0.2,maxiter=100):# 变异操作(原作中没有添加else语句从而会导致“Python: TypeError: object of type 'NoneType' has no len()”的错误)def mutate(vec):if random.random()<0.7:i=random.randint(0,len(domain)-1)if 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:]elif vec[i]==domain[i][1]:return vec[0:i]+domain[i][0]+vec[i+1:]elif vec[i]==domain[i][0]:return vec[0:i]+domain[i][1]+vec[i+1:]return vec# 交叉操作def crossover(r1,r2):i=random.randint(1,len(domain)-2)return r1[0:i]+r2[i:]# 构造初始种群pop=[]for i in range(popsize):vec=[random.randint(domain[i][0],domain[i][1])for i in range(len(domain))]pop.append(vec)# 每一代中有多少胜出者?topelite=int(elite*popsize)# 主循环for i in range(maxiter):scores=[(costf(v),v) for v in pop]scores.sort()ranked=[v for (s,v) in scores]# 从纯粹的胜出者开始pop=ranked[0:topelite]# 添加变异和配对后的胜出者while len(pop)<popsize:if random.random()<mutprob:# 变异c=random.randint(0,topelite)pop.append(mutate(ranked[c]))else:# 交叉c1=random.randint(0,topelite)c2=random.randint(0,topelite)pop.append(crossover(ranked[c1],ranked[c2]))# 打印当前最优值print scores[0][0]return scores[0][1]

运行以下语句:

s=optimization.geneticoptimize(domain,optimization.schedulecost)
print s
optimization.printschedule(s)

输出:

4971
4692
4219
...
2675
2675
2675
[3, 1, 2, 2, 3, 1, 2, 1, 2, 1, 2, 1]Seymour       BOS 11:16-13:29 $ 83  8:23-10:28 $149Franny       DAL  9:08-12:12 $364  9:49-13:51 $229Zooey       CAK 10:53-13:36 $189  8:19-11:16 $122Walt       MIA  9:15-12:29 $225  8:23-11:07 $143Buddy       ORD  9:42-11:32 $169  7:50-10:08 $164Les       OMA  9:15-12:03 $ 99  8:04-10:59 $136

下图展示了一种十分难达到最佳优化的示例。

集体智慧编程(5)——优化相关推荐

  1. 《集体智慧编程》读书笔记2

    最近重读<集体智慧编程>,这本当年出版的介绍推荐系统的书,在当时看来很引领潮流,放眼现在已经成了各互联网公司必备的技术. 这次边阅读边尝试将书中的一些Python语言例子用C#来实现,利于 ...

  2. 《集体智慧编程》读书笔记10

    最近重读<集体智慧编程>,这本当年出版的介绍推荐系统的书,在当时看来很引领潮流,放眼现在已经成了各互联网公司必备的技术. 这次边阅读边尝试将书中的一些Python语言例子用C#来实现,利于 ...

  3. 《集体智慧编程》读书笔记4

    最近重读<集体智慧编程>,这本当年出版的介绍推荐系统的书,在当时看来很引领潮流,放眼现在已经成了各互联网公司必备的技术. 这次边阅读边尝试将书中的一些Python语言例子用C#来实现,利于 ...

  4. 《集体智慧编程》数学公式

    这篇博客的目的主要是为了记录这些公式,取自原书附录B. 1.欧几里得距离(Euclidean Distance) 用途:计算距离,衡量相似度 公式: 代码实现: def euclidean(p, q) ...

  5. 《集体智慧编程》——第一章导读

    为什么80%的码农都做不了架构师?>>>    什么是集体智慧 其含义是指:为了长早新的想法,而将一群人的行为.偏好或思想组合在一起. 完成这项工作的一种最为基础的方法,便是使用调查 ...

  6. 《集体智慧编程》笔记(1 / 12):集体智慧导言

    文章目录 什么是集体智慧 什么是机器学习 机器学习的局限性 真实生活中的例子 学习型算法的其他用途 小结 Netflix, Google都适用了先进算法,将来自不同人群的数据加以组合,进而得出新的结论 ...

  7. 集体智慧编程 - 优化

    优化的核心是成本函数的建立,后面的优化函数,无论是何种方法,都是对题解的适当修改,以求使成本函数的值最小,只是修改的方式不同罢了. 转载于:https://www.cnblogs.com/zhouwe ...

  8. 集体智慧编程——优化搜索算法:爬山法,模拟退火算法,遗传算法-Python实现

    在优化问题中,有两个关键点 代价函数:确定问题的形式和规模之后,根据不同的问题,选择要优化的目标.如本文涉及的两个问题中,一个优化目标是使得航班选择最优,共计12个航班,要使得总的票价最少且每个人的等 ...

  9. 《集体智慧编程》第五章 优化

    组团旅游案例中: 关于租车时间: 如果租车,那么他们应该在24小时内归还车子,但是原文中又说"家庭成员们来自全国各地,并且他们希望在纽约会面.他们将在同一天到达,并在同一天离开." ...

  10. 《集体智慧编程》第8章

    1.P175 在计算高斯函数时,代码中的默认标准差为10.0,如果默认标准差为10是得不到正文中的数据的,这里的默认值应该改为1.0 附上高斯函数的公式和图像 公式中的a代表高斯函数的最大值,b代表平 ...

最新文章

  1. 窥探try ... catch与__try ... __except的区别
  2. 寻找一个字符串中所有重复字符的索引
  3. bootstrap布局两列或者多列表单
  4. 正则表达式入门之重复匹配
  5. C语言实用算法系列之行指针
  6. JAVA 从菜鸟成长为大牛的必经之路
  7. 使用fiddler脚本修改x-frame-options
  8. 从零搭建一个 Spring Boot 开发环境!Spring Boot+Mybatis+Swagger2 环境搭建
  9. 圆柱属于能滚动的物体吗_小学人教版六年级下册数学第3章《圆柱的表面积》知识点+同步练习...
  10. instanceof和typeof
  11. Linux系统版本介绍
  12. 单调栈-leetcode-739. 每日温度
  13. Android Runtime.getRuntime().exec()
  14. C语言初学: 厘米换算英尺英寸
  15. DAOS 源码解析之 daos_api
  16. Android之解析程序包时出现问题
  17. Qt5设置应用程序图标
  18. UEFI是什么意思?UEFI和BIOS的区别是什么?
  19. 浅谈CPU位数和操作系统位数
  20. Python之路 33:进程与线程概念及基础使用

热门文章

  1. 关于OptiStruct提问的一些建议
  2. 杰控组态软件的“远程连接服务”使用要点
  3. 个性化推荐系统实践应用
  4. 2014乌云安全峰会文字版记录
  5. et文件怎么转成excel_图片转PDF怎么转?这些都是文件转换的好方法!
  6. music的matlab程序,MUSIC算法matlab程序
  7. u9系统的使用方法仓库_用友U9--INV库存管理手册.pdf
  8. 人工智能AI系列 - 问答系统
  9. C++读写txt文件
  10. 怎么找网页源文件位置_html网页源代码是什么 如何查看网页源代码经验篇