如何在scipy.optimize.minimize()中设置容忍度?

2 投票
3 回答
10432 浏览
提问于 2025-04-18 08:21

我正在使用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 个回答

0

SLSQP 这个方法需要你的成本函数能够进行两次求导,如果不满足这个条件,它可能会出现严重的问题。即使你增加容忍度,也没什么用,除非你把容忍度调得特别大,这样你总是能得到最初的解。

你可以试试 COBYLA,不过要注意,这个方法需要你把边界条件转化为约束条件,因为 COBYLA 不接受单独的边界参数。

1

调整容忍度直到程序不崩溃,这其实是个很弱的解决办法。因为只要你的数据或函数稍微有点变化,程序就可能会崩溃。你可以看看这里,了解一下你的错误信息是什么意思。

我猜问题出在约束条件上,它们可能不太好用。求解器在处理平滑的函数时效果最好,而你的约束条件可能让问题变得相当复杂。我想到两种解决方法:

  • 把约束条件包含在问题定义中:算法尝试先计算出前 N-1 个 W 的值,然后根据这些值计算最后一个。用这种方法,你可能会发现有些权重变成了负数。
  • 把约束条件加到目标函数中。先去掉约束条件,计算出权重,然后把它们归一化,使它们的总和为 1,最后在返回值中加上 (W.sum() - 1)**2。这样做的效果是,W 的变化呈抛物线形状,求解器在这种情况下表现最好。最终结果中,权重可能不完全等于 1,但因为你在内部使用的是归一化的权重,这应该不是问题。

另一种方法是减少自由参数的数量。如果 W[17]W[18] 的含义相似,那么我们自然会期望它们的值也相似。你可以用一个平滑的函数来计算它们,比如抛物线,或者用一些正弦/余弦级数的项。这可以帮助你引入局部信息,并减少问题的维度。

2

我现在来回答这个问题,因为这个例子的源代码似乎还在网上可以找到,在这里

为了最大化夏普比率,也就是在fitness()这个函数中,我们并不需要去最小化它的倒数;其实只要最小化它的负值就可以了。

所以如果我把return 1/util换成return -util,然后运行提问者的代码——我确实这么做了——我发现对于任何TOLERANCE值,10,000次迭代都没有出现错误。

撰写回答