优化问题
优化技术特别擅长于处理受多种变量的影响,存在许多的可能解的问题,以及结果因为这些变量的组合而产生很大的变化的问题。
优化算法是通过尝试许多不同的题解并给这些题解打分以确定其质量的方式来找到一个问题的最优解。优化算法的经典的应用场景是,存在大量的题解以至于我们无法对他们进行一一的尝试的情况。最简单的也是最低效的方式求解方式,就是随机去尝试随机猜测的题解,并找出最优解来。我们可以对题解进行智能化修正。
对于处理优化问题,成本函数是优化问题的关键,任何目标的优化问题就是寻找一组能够使成本函数的返回的结果达到最小化的的输入。但是一般的优化问题虽然在建立成本函数后就可以寻找最优解,但是存在着其解过多的问题,一般都是计算机需要很长很长时间处理的问题,所以就有了随即搜索、爬山搜索(和梯度下降法一样)、模拟退火算法、遗传算法等只能算法。
几种智能搜索算法
- 随机搜索不是一种很好的优化算法,但是它是我们评估其他的算法优劣的基线(baseline)
- 爬山法(梯度下降法),是对随机尝试的一种改进,因为随机优化师到处跳跃的,不会自动的寻找与已经发现的优解相近的题解,主要是这种解没有充分的利用已经发现的最优解。而爬山法则是通过搜索已知解的附近的解,将解向着最优解的方向优化,但是可能存在最后的解局限于局部范围的最小值,但是它不是全局的最优解,对于这个问题又很多的改进,比如加动量或者是随机重复爬山法。
- 模拟退火算法:是受到物理学的启发而来的一种优化算法。算法的关键之处,如果新的成本值更低,则新的题解就会成为当前的题解,这和爬上法很相似,不过如果成本更高的化,则新的题解仍然可能会成为新的题解,这是一种规避局部最小值的一种策略。成本更高的题解被接受的概率为
因为温度开始非常高,指数总数接近0,所以概率为1,随着温度的下降,高成本和低成本之间差异越来越重要,差异越大,概率越低
这里注意初始温度越高,迭代次数越多,得到的解的平均质量越好,几乎能能够得到全局最优解。
- 遗传算法:这类算法运行的过程中先随机生成一组解,称为种群,然后根据成本函数对种群进行排序,然后将位于种群中最顶端的题解加入新的种群中,这一步称为精英选拔法,新的种群中余下的部分是有修改后的最优解形成的全新的解组成。其中有两个操作一个是变异,一个是重组。
用优化算法可以解决的问题
- 组团旅游的问题(也就是航班问题)
- 描述题解,首先应该明确题解如何表达
import time
import random
import math
people = [('Seymour', 'BOS'),
('Franny', 'DAL'),
('Zooey', 'CAK'),
('Walt', 'MIA'),
('Buddy', 'ORD'),
('Les', 'OMA')]
destination = 'LGA'
flights = {}
def getminutes(t):
x = time.strptime(t, "%H:%M")
return x[3] * 60 + x[4]
# 这里是输出函数,用来解释数据
def printschedule(r):
for d in range(len(r) // 2):
name = people[d][0]
origin = people[d][1]
out = flights[(origin, destination)][r[2 * d]]
ret = flights[(destination, origin)][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]))
# 这里是载入数据即
for line in open("schedule.txt"):
origin, dest, depart, arrive, price = line.strip().split(",")
flights.setdefault((origin, dest), [])
flights[(origin, dest)].append((depart, arrive, int(price)))
- 构建成本函数 成本函数是优化算法的关键,通常通过众多的变量来鉴别方案的好坏。这里在本例子可以从价格、旅行时间、等待时间、出发时间、汽车使用时间等方面考虑。
def schedulecost(sol):
totalprice = 0
latestarrival = 0
earliestdep = 24 * 60
for 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 = 0
for 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 += 50
return totalwait + totalprice
- 确定使用优化算法,随机算法、模拟退火算法、遗传算法
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:]
# 交叉操作
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)
# 每一代中有多少胜出者
scores = None
topelite = int(elite * popsize)
bestPop = pop[0:topelite]
for i in range(maxiter):
try:
scores = [(costf(v), v) for v in pop]
except:
pass
scores.sort()
ranked = [v for (s, v) in scores]
# print("@@",np.sum(np.array(bestPop)))
# print("###",np.sum(np.array(ranked[0:topelite])))
# if (np.sum(np.array(bestPop)) == np.sum(np.array(ranked[0:topelite]))):
# break
# 选出这次迭代的最优个体
pop = ranked[0:topelite]
bestPop = pop
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]
def annealingoptimize(domain, costf, T=1000000.0, cool=0.95, step=1):
vec = [(random.randint(domain[i][0], domain[i][0])) 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] += 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)
if (eb < ea or random.random() < pow(math.e, -(eb - ea) / T)):
vec = vecb
T = T * cool
return vec
def hillclimb(domain, costf):
# 创建一个随机解
sol = [random.randint(domain[i][0], domain[i][0]) for i in range(len(domain))]
while True:
neighbors = []
for j in range(len(domain)):
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])
if cost < best:
sol = neighbors[j]
if best == current:
break
return sol
def randomoptimize(domain, costf):
best = 999999999
bestr = None
for i in range(1000):
r = [random.randint(domain[i][0], domain[i][1]) for i in range(len(domain))]
cost = costf(r)
if cost < best:
best = cost
bestr = r
return bestr
# 使用算法
s3 = geneticoptimize(domain,schedulecost)
printschedule(s3)
- 涉及偏好的优化问题(比如宿舍的分配问题)
import random
import math
import numpy as np
# The dorms, each of which has two available spaces
dorms = ['Zeus', 'Athena', 'Hercules', 'Bacchus', 'Pluto']
# People, along with their first and second choices
prefs = [('Toby', ('Bacchus', 'Hercules')),
('Steve', ('Zeus', 'Pluto')),
('Karen', ('Athena', 'Zeus')),
('Sarah', ('Zeus', 'Pluto')),
('Dave', ('Athena', 'Bacchus')),
('Jeff', ('Hercules', 'Pluto')),
('Fred', ('Pluto', 'Athena')),
('Suzie', ('Bacchus', 'Hercules')),
('Laura', ('Bacchus', 'Hercules')),
('James', ('Hercules', 'Athena'))]
domain = [(0, (len(dorms) * 2) - i - 1) for i in range(0, len(dorms) * 2)]
def printsolution(vec):
slots = []
# Create two slots for each dorm
for i in range(len(dorms)): slots += [i, i]
# Loop over each students assignment
for i in range(len(vec)):
x = int(vec[i])
# Choose the slot from the remaining ones
dorm = dorms[slots[x]]
# Show the student and assigned dorm
print(prefs[i][0], dorm)
# Remove this slot
del slots[x]
def dormcost(vec):
cost = 0
# Create list a of slots
slots = [0, 0, 1, 1, 2, 2, 3, 3, 4, 4]
# Loop over each student
for i in range(len(vec)):
x = int(vec[i])
dorm = dorms[slots[x]]
pref = prefs[i][1]
# First choice costs 0, second choice costs 1
if pref[0] == dorm:
cost += 0
elif pref[1] == dorm:
cost += 1
else:
cost += 3
# Not on the list costs 3
# Remove selected slot
del slots[x]
return cost
# 打印结果
s1 = randomoptimize(domain, dormcost)
print(dormcost(s1))
s2 = annealingoptimize(domain, dormcost)
print(dormcost(s2))
s3 = hillclimb(domain, dormcost)
print(dormcost(s3))
s4 = geneticoptimize(domain, dormcost)
print(dormcost(s4))
- 网络可视化问题:此处的网络是指的是任何彼此相连的一组事物,比如社交网络,怎么让网络更好更清晰的表达网络中的事物和事物之间的关系,这种问题可以使用遗传算法进行解决。
- 创建数据集(事物之间的关联性)
import math
import random
people = ['Charlie', 'Augustus', 'Veruca', 'Violet', 'Mike', 'Joe', 'Willy', 'Miranda']
links = [('Augustus', 'Willy'),
('Mike', 'Joe'),
('Miranda', 'Mike'),
('Violet', 'Augustus'),
('Miranda', 'Willy'),
('Charlie', 'Mike'),
('Veruca', 'Joe'),
('Miranda', 'Augustus'),
('Willy', 'Augustus'),
('Joe', 'Charlie'),
('Veruca', 'Augustus'),
('Miranda', 'Joe')]
- 成本函数(计算交叉线)
def crosscount(v):
# Convert the number list into a dictionary of person:(x,y)
loc = dict([(people[i], (v[i * 2], v[i * 2 + 1])) for i in range(0, len(people))])
total = 0
# Loop through every pair of links
for i in range(len(links)):
for j in range(i + 1, len(links)):
# Get the locations
(x1, y1), (x2, y2) = loc[links[i][0]], loc[links[i][1]]
(x3, y3), (x4, y4) = loc[links[j][0]], loc[links[j][1]]
den = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
# den==0 if the lines are parallel
if den == 0: continue
# Otherwise ua and ub are the fraction of the
# line where they cross
ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / den
ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / den
# If the fraction is between 0 and 1 for both lines
# then they cross each other
if 0 < ua < 1 and 0 < ub < 1:
total += 1
for i in range(len(people)):
for j in range(i + 1, len(people)):
# Get the locations of the two nodes
(x1, y1), (x2, y2) = loc[people[i]], loc[people[j]]
# Find the distance between them
dist = math.sqrt(math.pow(x1 - x2, 2) + math.pow(y1 - y2, 2))
# Penalize any nodes closer than 50 pixels
if dist < 50:
total += (1.0 - (dist / 50.0))
return total
- 绘制网络
from PIL import Image, ImageDraw
def drawnetwork(sol):
# Create the image
img = Image.new('RGB', (400, 400), (255, 255, 255))
draw = ImageDraw.Draw(img)
# Create the position dict
pos = dict([(people[i], (sol[i * 2], sol[i * 2 + 1])) for i in range(0, len(people))])
for (a, b) in links:
draw.line((pos[a], pos[b]), fill=(255, 0, 0))
for n, p in pos.items():
draw.text(p, n, (0, 0, 0))
img.save("123.png")
domain = [(10, 370)] * (len(people) * 2)
sol1 = randomoptimize(domain,crosscount)
sol2 = geneticoptimize(domain,crosscount)
sol3 = annealingoptimize(domain,crosscount,step=20,cool=0.99)
drawnetwork(sol2)
总结
优化算法不止用于以上的三个方面,关于优化算法关键的步骤就是确定题解的表示法以及成本函数。
优化算法也可以找到任务分解的最佳方案,从而使任务列表得以在最短的时间内完成。