介紹一個全局最優化的方法:隨機游走算法(Random Walk)
1. 關於全局最優化求解
全局最優化是一個非常復雜的問題,目前還沒有一個通用的辦法可以對任意復雜函數求解全局最優值。上一篇文章講解了一個求解局部極小值的方法——梯度下降法。這種方法對於求解精度不高的情況是實用的,可以用局部極小值近似替代全局最小值點。但是當要求精確求解全局最小值時,梯度下降法就不適用了,需要采用其他的辦法求解。常見的求解全局最優的辦法有拉格朗日法、線性規划法、以及一些人工智能算法比如遺傳算法、粒子群算法、模擬退火算法等(可以參見我之前的博客)。而今天要講的是一個操作簡單但是不易陷入局部極小值的方法:隨機游走算法。
2. 隨機游走算法操作步驟
設f(x)f(x)是一個含有nn個變量的多元函數,x=(x1,x2,...,xn)x=(x1,x2,...,xn)為nn維向量。
- 給定初始迭代點xx,初次行走步長λλ,控制精度ϵϵ(ϵϵ是一個非常小的正數,用於控制結束算法)。
- 給定迭代控制次數NN,kk為當前迭代次數,置k=1k=1。
- 當 k<Nk<N時,隨機生成一個(−1,1)(−1,1)之間的nn維向量u=(u1,u2,⋯,un),(−1<ui<1,i=1,2,⋯,n)u=(u1,u2,⋯,un),(−1<ui<1,i=1,2,⋯,n),並將其標准化得到u′=u∑ni=1u2i√u′=u∑i=1nui2。令x1=x+λu′x1=x+λu′,完成第一步游走。
- 計算函數值,如果 f(x1)<f(x)f(x1)<f(x),即找到了一個比初始值好的點,那么kk重新置為1,將x1x1變為xx,回到第2步;否則k=k+1k=k+1,回到第3步。
- 如果連續NN次都找不到更優的值,則認為,最優解就在以當前最優解為中心,當前步長為半徑的NN維球內(如果是三維,則剛好是空間中的球體)。此時,如果λ<ϵλ<ϵ,則結束算法;否則,令λ=λ2λ=λ2,回到第1步,開始新一輪游走。
3. 隨機游走的代碼實現(使用Python)
這里使用的測試函數為f(r)=sin(r)r+1,r=(x−50)2+(y−50)2−−−−−−−−−−−−−−−−−√+e,0≤x,y≤100f(r)=sin(r)r+1,r=(x−50)2+(y−50)2+e,0≤x,y≤100,求f(r)f(r)的最大值。該函數是一個多峰函數,在(50,50)(50,50)處取得全局最大值1.15121.1512,第二最大值在其全局最大值附近,采用一般的優化方法很容易陷入局部極大值點。這里是求解函數的最大值問題,可以將其轉化為求目標函數的相反數的最小值問題。具體代碼如下:
-
#!/usr/bin/env python
-
# -*- coding: utf-8 -*-
-
# @Time : 2017/7/20 10:08
-
# @Author : Lyrichu
-
# @Email : 919987476@qq.com
-
# @File : random_walk.py
-
'''
-
@Description:使用隨機游走算法求解函數極值
-
這里求解:f = sin(r)/r + 1,r = sqrt((x-50)^2+(y-50)^2)+e,0<=x,y<=100 的最大值
-
求解f的最大值,可以轉化為求-f的最小值問題
-
'''
-
from __future__ import print_function
-
import math
-
import random
-
N = 100 # 迭代次數
-
step = 0.5 # 初始步長
-
epsilon = 0.00001
-
variables = 2 # 變量數目
-
x = [49,49] # 初始點坐標
-
walk_num = 1 # 初始化隨機游走次數
-
print("迭代次數:",N)
-
print("初始步長:",step)
-
print("epsilon:",epsilon)
-
print("變量數目:",variables)
-
print("初始點坐標:",x)
-
# 定義目標函數
-
def function(x):
-
r = math.sqrt((x[0]-50)**2 + (x[1]-50)**2) + math.e
-
f = math.sin(r)/r + 1
-
return -f
-
# 開始隨機游走
-
while(step > epsilon):
-
k = 1 # 初始化計數器
-
while(k < N):
-
u = [random.uniform(-1,1) for i in range(variables)] # 隨機向量
-
# u1 為標准化之后的隨機向量
-
u1 = [u[i]/math.sqrt(sum([u[i]**2 for i in range(variables)])) for i in range(variables)]
-
x1 = [x[i] + step*u1[i] for i in range(variables)]
-
if(function(x1) < function(x)): # 如果找到了更優點
-
k = 1
-
x = x1
-
else:
-
k += 1
-
step = step/2
-
print("第%d次隨機游走完成。" % walk_num)
-
walk_num += 1
-
print("隨機游走次數:",walk_num-1)
-
print("最終最優點:",x)
-
print("最終最優值:",function(x))
輸出結果如下:
-
迭代次數: 100
-
初始步長: 0.5
-
epsilon: 1e-05
-
變量數目: 2
-
初始點坐標: [49, 49]
-
第1次隨機游走完成。
-
第2次隨機游走完成。
-
第3次隨機游走完成。
-
......
-
第16次隨機游走完成。
-
隨機游走次數: 16
-
最終最優點: [49.99999305065255, 50.00000102537616]
-
最終最優值: -1.15111524497
基本的隨機游走算法對於初始點比較敏感,可以看出,當初始點位於最優點附件時,可以很好地達到全局最優點;如果將初始點設置得離最優點較遠,比如設置初始點為(10,10)(10,10)時,其他參數不變,得到結果為:
-
隨機游走次數: 16
-
最終最優點: [10.042835581532445, 11.648866165553416]
-
最終最優值: -1.01720848747
可以發現,隨機游走陷入了局部最優點。當然,如果增大迭代次數NN以及初始步長λλ,可以在一定程度上增加尋優能力,比如設置N=3000,λ=10.0N=3000,λ=10.0,得到結果如下:
-
迭代次數: 3000
-
初始步長: 10.0
-
epsilon: 1e-05
-
變量數目: 2
-
初始點坐標: [10, 10]
-
第1次隨機游走完成。
-
第2次隨機游走完成。
-
第3次隨機游走完成。
-
......
-
第20次隨機游走完成。
-
隨機游走次數: 20
-
最終最優點: [49.99999900055026, 50.0000023931389]
-
最終最優值: -1.15111697755
可以看出,當增大迭代次數以及初始步長之后,函數最終達到了全局最優點。但是迭代次數增加的代價則是運行時間的增加。總得來說,基本的隨機游走算法可以很好地達到全局最優點,但是有時會依賴於初始點的選擇。
4. 改進的隨機游走算法
改進的隨機游走算法的不同之處是在於第3步,原來是產生一個隨機向量uu,現在則是產生nn個隨機向量u1,u2,⋯,unu1,u2,⋯,un,nn是給定的一個正整數。將nn個ui(i=1,2,⋯,n)ui(i=1,2,⋯,n)標准化得到u′1,u′2,⋯,u′nu1′,u2′,⋯,un′,利用公式xi=x+λu′ixi=x+λui′,令min{x1,x2,⋯,xn}min{x1,x2,⋯,xn}替換原來的x1x1,其他步驟保持不變。通過這種方式改進之后,隨機游走算法的尋優能力大大提高,而且對於初始值的依賴程度也降低了。令n=10n=10,初始點為(−100,−10)(−100,−10),N=100,λ=10.0,ϵ=0.00001N=100,λ=10.0,ϵ=0.00001,改進的隨機游走算法實現代碼如下:
-
#!/usr/bin/env python
-
# -*- coding: utf-8 -*-
-
# @Time : 2017/7/20 10:48
-
# @Author : Lyrichu
-
# @Email : 919987476@qq.com
-
# @File : improve_random_walk.py
-
'''
-
@Description:改進的隨機游走算法
-
這里求解:f = sin(r)/r + 1,r = sqrt((x-50)^2+(y-50)^2)+e,0<=x,y<=100 的最大值
-
求解f的最大值,可以轉化為求-f的最小值問題
-
'''
-
from __future__ import print_function
-
import math
-
import random
-
N = 100 # 迭代次數
-
step = 10.0 # 初始步長
-
epsilon = 0.00001
-
variables = 2 # 變量數目
-
x = [-100,-10] # 初始點坐標
-
walk_num = 1 # 初始化隨機游走次數
-
n = 10 # 每次隨機生成向量u的數目
-
print("迭代次數:",N)
-
print("初始步長:",step)
-
print("每次產生隨機向量數目:",n)
-
print("epsilon:",epsilon)
-
print("變量數目:",variables)
-
print("初始點坐標:",x)
-
# 定義目標函數
-
def function(x):
-
r = math.sqrt((x[0]-50)**2 + (x[1]-50)**2) + math.e
-
f = math.sin(r)/r + 1
-
return -f
-
# 開始隨機游走
-
while(step > epsilon):
-
k = 1 # 初始化計數器
-
while(k < N):
-
# 產生n個向量u
-
x1_list = [] # 存放x1的列表
-
for i in range(n):
-
u = [random.uniform(-1,1) for i1 in range(variables)] # 隨機向量
-
# u1 為標准化之后的隨機向量
-
u1 = [u[i3]/math.sqrt(sum([u[i2]**2 for i2 in range(variables)])) for i3 in range(variables)]
-
x1 = [x[i4] + step*u1[i4] for i4 in range(variables)]
-
x1_list.append(x1)
-
f1_list = [function(x1) for x1 in x1_list]
-
f1_min = min(f1_list)
-
f1_index = f1_list.index(f1_min)
-
x11 = x1_list[f1_index] # 最小f1對應的x1
-
if(f1_min < function(x)): # 如果找到了更優點
-
k = 1
-
x = x11
-
else:
-
k += 1
-
step = step/2
-
print("第%d次隨機游走完成。" % walk_num)
-
walk_num += 1
-
print("隨機游走次數:",walk_num-1)
-
print("最終最優點:",x)
-
print("最終最優值:",function(x))
輸出結果如下:
-
迭代次數: 100
-
初始步長: 10.0
-
每次產生隨機向量數目: 10
-
epsilon: 1e-05
-
變量數目: 2
-
初始點坐標: [-100, -10]
-
第1次隨機游走完成。
-
第2次隨機游走完成。
-
第3次隨機游走完成。
-
.....
-
第20次隨機游走完成。
-
隨機游走次數: 20
-
最終最優點: [49.999997561093195, 49.99999839875969]
-
最終最優值: -1.15111685082
可以發現,即使迭代次數N=100N=100不大,初始點(−100,−10)(−100,−10)離最優點(50,50)(50,50)非常遠,改進的隨機游走算法依然可以達到最優點。這說明了改進的隨機游走算法具有更強大的尋優能力以及對於初始點更低的依賴性。