如何在scipy.optimize.minimize()中设置容忍度?
我正在使用scipy.optimize.minimize这个工具来寻找一个高效的投资组合。
在默认设置下,我经常遇到“BaseException: Positive directional derivative for linesearch”的错误,这个错误在使用某些输入时会出现。我发现如果我把容忍度调高一些,这个问题会减少,但还是没有完全解决。有什么建议吗?
import numpy as np
import pandas as pd
import scipy.optimize
def fx(TOLERANCE):
#TOLERANCE = 1.5
def solve_weights(R, C, rf, b_):
def port_mean_var(W,R,C):
return sum(R*W), np.dot(np.dot(W, C), W)
def fitness(W, R, C, rf):
mean, var = port_mean_var(W, R, C) # calculate mean/variance of the portfolio
util = (mean - rf) / np.sqrt(var) # utility = Sharpe ratio
return 1/util # maximize the utility, minimize its inverse value
n = len(R)
W = np.ones([n])/n # start optimization with equal weights
#b_ = [(0.,1.) for i in range(n)] # weights for boundaries between 0%..100%. No leverage, no shorting
c_ = ({'type':'eq', 'fun': lambda W: sum(W)-1. }) # Sum of weights must be 100%
optimized = scipy.optimize.minimize(fitness, W, (R, C, rf),
method='SLSQP', constraints=c_,
bounds=b_, tol=TOLERANCE)
if not optimized.success:
raise BaseException(optimized.message)
return optimized.x
def mean_var_opt2(ret_df, upper_bounds=None):
R = (ret_df.mean(0)*252).values
C = (ret_df.cov()*252).values
rf = 0.0
if upper_bounds == None:
upper_bounds = pd.Series(1.0,index=ret_df.columns)
b_ = [(0.0,float(num)) for num in upper_bounds]
wgts = solve_weights(R, C, rf, b_)
return pd.Series(wgts, index=ret_df.columns)
np.random.seed(45)
rets = []
for i in range(10000):
rets.append(pd.DataFrame(np.random.randn(100,4)/100.0))
try:
for i in range(10000):
mean_var_opt2(rets[i])
except BaseException as e:
print e
finally: print "Tolerance: %s, iter: %s" % (TOLERANCE,i)
for k in [0.001, 0.01, 0.025, 0.05, 0.1, 0.5, 1.0, 5.0, 50.0, 500.0]:
fx(k)
Positive directional derivative for linesearch
Tolerance: 0.001, iter: 0
Positive directional derivative for linesearch
Tolerance: 0.01, iter: 30
Positive directional derivative for linesearch
Tolerance: 0.025, iter: 77
Inequality constraints incompatible
Tolerance: 0.05, iter: 212
Positive directional derivative for linesearch
Tolerance: 0.1, iter: 444
Positive directional derivative for linesearch
Tolerance: 0.5, iter: 444
Positive directional derivative for linesearch
Tolerance: 1.0, iter: 1026
Positive directional derivative for linesearch
Tolerance: 5.0, iter: 1026
Positive directional derivative for linesearch
Tolerance: 50.0, iter: 1026
Positive directional derivative for linesearch
Tolerance: 500.0, iter: 1026
3 个回答
SLSQP
这个方法需要你的成本函数能够进行两次求导,如果不满足这个条件,它可能会出现严重的问题。即使你增加容忍度,也没什么用,除非你把容忍度调得特别大,这样你总是能得到最初的解。
你可以试试 COBYLA
,不过要注意,这个方法需要你把边界条件转化为约束条件,因为 COBYLA
不接受单独的边界参数。
调整容忍度直到程序不崩溃,这其实是个很弱的解决办法。因为只要你的数据或函数稍微有点变化,程序就可能会崩溃。你可以看看这里,了解一下你的错误信息是什么意思。
我猜问题出在约束条件上,它们可能不太好用。求解器在处理平滑的函数时效果最好,而你的约束条件可能让问题变得相当复杂。我想到两种解决方法:
- 把约束条件包含在问题定义中:算法尝试先计算出前 N-1 个
W
的值,然后根据这些值计算最后一个。用这种方法,你可能会发现有些权重变成了负数。 - 把约束条件加到目标函数中。先去掉约束条件,计算出权重,然后把它们归一化,使它们的总和为 1,最后在返回值中加上
(W.sum() - 1)**2
。这样做的效果是,W
的变化呈抛物线形状,求解器在这种情况下表现最好。最终结果中,权重可能不完全等于 1,但因为你在内部使用的是归一化的权重,这应该不是问题。
另一种方法是减少自由参数的数量。如果 W[17]
和 W[18]
的含义相似,那么我们自然会期望它们的值也相似。你可以用一个平滑的函数来计算它们,比如抛物线,或者用一些正弦/余弦级数的项。这可以帮助你引入局部信息,并减少问题的维度。
我现在来回答这个问题,因为这个例子的源代码似乎还在网上可以找到,在这里。
为了最大化夏普比率,也就是在fitness()
这个函数中,我们并不需要去最小化它的倒数;其实只要最小化它的负值就可以了。
所以如果我把return 1/util
换成return -util
,然后运行提问者的代码——我确实这么做了——我发现对于任何TOLERANCE
值,10,000次迭代都没有出现错误。