优化问题1:组团旅游
五个人要乘坐航班去同一个地方,如何安排航班可以使总成本最低
思路:目标函数是一个10维list,[1,4,3,2,7,3,6,3,2,4,5,3],表示每个人乘坐第几趟航班往返
优化问题2:学生宿舍优化
10个学生选择5间宿舍10个床位,每人2个志愿,如何安排可以符合多数人的志愿
思路:将每个床位看作一个槽,十个学生选择十个槽
关键点
- 设置初始值
- 设置成本函数cost,即损失函数
可用算法
随机选择:完全随机,保留最优解
def randomptimize(domain, costf):
best = 999999999
bestr = None
for i in range(10000): # 我们打算随机产生1000次结果,从这1000次结果中选择一个最好的
# 很显然randint是产生在一定范围内的随机数,显然由于下一句右边等号里的for,将会产生一个循环
r = [random.randint(domain[j][0], domain[j][1]) for j in range(len(domain))]
cost = costf(r)
# 每次得到成本我们都判断一次,如果更低,我们就置换
if cost < best:
best = cost
bestr = r
return bestr
爬山法:每次更新一个值,保留最优解,容易陷入局部最优
def hillclimb(domain, costf):
# Create a random solution
sol = [random.randint(domain[i][0], domain[i][1])
for i in range(len(domain))]
# Main loop
while 1:
# Create list of neighboring solutions
neighbors = []
for j in range(len(domain)):
# 对于每个元素+1或者-1
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:])
# See what the best solution amongst the neighbors is
current = costf(sol)
best = current
for j in range(len(neighbors)):
cost = costf(neighbors[j])
if cost < best:
best = cost
sol = neighbors[j]
# If there's no improvement, then we've reached the top
if best == current:
break
return sol
模拟退火算法:即使新的成本更高,也有可能更新最优解,可避免局部最优
def annealingoptimize(domain, costf, T=10000.0, cool=0.98, step=1):
# 和爬山法一样,先产生一个随机解,然后一切的改变都从这个随机解开始
vec = [random.randint(domain[i][0], domain[i][1]) for i in range(len(domain))]
while T > 0.5:
# 产生一个随机数,决定这次改变是改变数列中的哪一个随机数
i = random.randint(0, len(domain) - 1)
# 选择一个改变的方向,也就是说是增加还是减少
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)
eb = costf(vecb)
# or后面:表示接受更差的结果。仔细想想,原来概率的表示是如此完成的,注意前一个random()产生的数是在0到1之间。
if (eb < ea or random.random() < pow(math.e, -(eb - ea) / T)):
vec = vecb
# 没经过一次循环,改变温度,温度一改变,就会改变循环的次数和接受更差解的概率
# 按一定比例降温
T = T * cool
return vec
遗传算法:交叉变异,每次保留一定的最优序列
def geneticoptimize(domain, costf, popsize=50, step=1, mutprob=0.2, elite=0.2, maxiter=100):
# 方法中还在定义方法
# 变异操作
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:]
else:
return vec
# 交叉操作:貌似用python编程是好快的说,我感觉比较复杂的句子只要两句么,还是我c/c++没学好
def crossover(r1, r2):
# 为什么减2,其实想把这个一个数字列表划分为两段,再各取一半
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):
# print pop #但是如果不加这句会使下一句出现一个bug,就是传过去的v是None,但是我讲pop全部打印出来的话,又没有问题
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] # 这里返回的是航班序列