一、Nelder-Mead算法介绍
Nelder-Mead算法主要应用于求解一些非线性(nonliner)、导函数未知的最大值或最小值问题。本文利用Nelder-Mead算法求解函数最小值问题。
当应用Nelder-Mead时,若函数有n个变量,则数据集合(simplex)需要构建n+1个元素。利用这n+1个元素,不停地替换掉函数值最大(小)的元素,同时维护更新中心点的值,当最终的函数值满足容忍条件时即可得出近似解的结果。
算法的流程如下:
1. 构建变量集合A = {
},集合中的每个元素是一个多维的集合,例如对于
,
为1维。且该集合满足
。
2. 计算中心点
,即A中每个元素对应的每一维的平均值。
3. 由于在之前的集合A中,已经可以知道最末尾的变量对应的函数值是最大的,因此对其进行替换,每一次替换后都要更新一次中心点的值。替换的方法共有四种:
(1)反射,将
以
为原点映射到对应的
倍距离位置,其中
;
(2)扩展反射,将
以
为原点映射到对应的
倍距离位置,其中
;
(3)缩小距离,将
以
为原点缩小到
倍距离位置,其中
;
(4)缩小距离,将
以
为原点缩小到
倍距离位置,其中
;
注意(3)和(4)中映射操作采用的原点不同,
的标准取值为
。在四种替换方法中选择出相对更大(小)的
,利用
替换掉之前最大的
。
注:四种替换方法从(1)到(4)执行,一旦满足替换条件则不向下继续求解替换,而是重新回归步骤1。
4. 重复上述过程,直到集合A中的值满足容忍条件即可停止,并认为已经定位到了可能的最大(小)值的点。(个人理解是会在一定情况下出现局部最优解的情况,即一直重复(1)和(2)的替换方法)。
为了使结果尽可能避免是局部最优解,在构建初始集(initial simplex)非常关键。当初始集构造范围过小时,大概率会出现局部解而非全局解,因此在构建初始集合时,要求各个元素构成的区域是非零容积。
二、Python实现
首先定义需要求解的目标函数:
def func(x, y):#目标函数
result = math.pow(x - y, 2) + math.pow(x - 2, 2) + math.pow(y - 3, 4)
return result
个人设计的存储结构是dict{
:
}
def get_dict(res_dict):#获取坐标:值的dict
for i in range(4):
x = random.randint(-10, 10)
y = random.randint(-10, 10)
result = func(x, y)
loc = (x, y)
res_dict[loc] = result
return res_dict
由于在算法中对函数值排序的作用仅在于获取最大值和最小值对应的
,因此只需要得到dict中最大和最小value对应的key。因此设计两个函数返回对应的index或对应的key。本文中返回对应的index。
def get_max():
...
return index_max
def get_min():
...
return index_min
接下来设计5个函数,分别用作求中间值mean,以及4种探索方法计算对应的z1, z2, z3和z4。此处对算法进行了一点更改,即直接比较4种方法的值获得一个相对最优的解。目前测试的结果也能达到该算法执行的结果。
result_list = []#分析四种分散方法对应的z函数值
result_list.append(get_z1(index_max, mean, res_dict))
result_list.append(get_z2(index_max, mean, res_dict))
result_list.append(get_z3(index_max, mean, res_dict))
result_list.append(get_z4(index_max, index_min, mean, res_dict))
index = 0
flag = 0
min = result_list[0][1]#得到最小的函数值
for r in result_list:#获取最小的z值对应的坐标进行替换
if result_list[flag][1] <= min:
min = result_list[index][1]
index = flag
flag += 1
else:
flag += 1
z = result_list[index][1]
z_cord = tuple(result_list[index][0])#(x,y)
最后替换掉集合中的点,不断循环更新。最后满足容忍条件时结束循环。
代码详情见:https://github.com/olddaddy/data_mining_1.git