SciPy 优化器忽略约束之一 [英] SciPy optimizer ignores one of the constraints

查看:63
本文介绍了SciPy 优化器忽略约束之一的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试解决一个优化问题,我需要创建一个投资组合,该投资组合具有来自基准投资组合的最小跟踪误差,并且受到一些限制:

I am trying to solve an optimization problem where I need to create a portfolio that with a minimum tracking error from benchmark portfolio and it's subject to some constraints:

import scipy.optimize as opt
import numpy as np

def random_portfolio(n):
    a = np.random.random(n)
    a /= a.sum()
    return a

portfolio_weights = [1 for i in range(20)]
portfolio_weights = [i/len(portfolio_weights) for i in portfolio_weights]

def tracking_error_function(W, port_weights):
    weight_diff = list(np.array(port_weights)-np.array(W))
    weight_diff = sum([i**2 for i in weight_diff])
    return weight_diff

def total_max_weight_constraint(weights):
    max_weights_share = sum([i for i in weights if i > 0.045])
    max_ineq = 0.36 - max_weights_share
    return max_ineq

def gen_initial_weights(n):
    max_weights = [0.089 for i in range(4)]
    rest_of_n = n - 4
    rest_of_weight = 1 - sum(max_weights)
    other_weights = [rest_of_weight/rest_of_n for i in range(rest_of_n)]
    all_weights = max_weights + other_weights
    return all_weights

initial_weights = np.asarray(gen_initial_weights(len(portfolio_weights)))

tr_err = tracking_error_function(initial_weights, portfolio_weights)  
b_ = [(0.0, 0.09) for i in range(len(initial_weights))]
c_ = ({'type': 'eq', 'fun': lambda W: sum(W) - 1},
  {'type': 'ineq', 'fun': total_max_weight_constraint})

optimized = opt.minimize(tracking_error_function, initial_weights, args=(portfolio_weights), method='SLSQP', constraints=c_, bounds=b_, options={'maxiter': 100000 })

所以我最初的猜测符合约束条件,并且基准是同等权重的.当我运行它时,结果是完全相同权重的投资组合,尽管它显然违反了第二个约束.而且,状态是成功.任何想法我做错了什么?

So my initial guess abides the constraints and the benchmark is equally-weighted. When I run it, the result is exactly equally-weighted portfolio although it is clearly violating the second constraint. Moreover, the status is success. Any ideas what i do wrong?

更新:这是一个对我来说似乎有效的解决方案

Update: This is a solution that seems to work in my case

import scipy.optimize as opt
import numpy as np
import random
import matplotlib.pyplot as plt


def random_portfolio(n):
    #random.seed(123)
    a = np.random.random(n)
    a /= a.sum()
    return a

def continous_step_function(x, cutoff):
    return x / (1 + safe_exp(-(x - cutoff) * 200000))

def safe_exp(x):
    try:
        ans = np.math.exp(x)
    except OverflowError:
        ans = float('inf')
    return ans

def gen_initial_weights(n):
    max_weights = [0.0899999 for i in range(4)]
    rest_of_n = n - 4
    rest_of_weight = 1 - sum(max_weights)
    other_weights = [rest_of_weight/rest_of_n for i in range(rest_of_n)]
    all_weights = max_weights + other_weights
    return all_weights

def tracking_error_function(W, port_weights):
    weight_diff = port_weights - W
    weight_diff = np.sum(weight_diff ** 2)

    excessive_weight = max(0,(sum([continous_step_function(i,0.045) for i in W]) - 0.36))

    return weight_diff + excessive_weight

def total_max_weight_constraint(weights):
    max_weights_share = sum([continous_step_function(i,0.045) for i in weights])
    max_ineq = 0.36 - max_weights_share
    return max_ineq

def run():
    portfolio_weights = sorted(random_portfolio(20))

    initial_weights = np.asarray(gen_initial_weights(len(portfolio_weights)))
    initial_weights = sorted(initial_weights)

    b_ = [(0.0, 0.09) for i in range(len(initial_weights))]
    c_ = ({'type': 'eq', 'fun': lambda W: sum(W) - 1},
          {'type': 'ineq', 'fun': total_max_weight_constraint}
          )

    optimized = opt.minimize(tracking_error_function, initial_weights, args=(portfolio_weights), constraints=c_,
                             bounds=b_, options={'eps': 0.00000001, 'ftol' : 0.00000001, 'iprint': 0, 'disp': 0, 'maxiter': 10000})

    result = optimized.x

    if tracking_error_function(result, portfolio_weights) > 0.05:
        print('Excessive tracking error: ')
        print('Residual error: {}'.format(tracking_error_function(result, portfolio_weights)))
        print('Target: {} {}'.format(sum(portfolio_weights), portfolio_weights))
        print('Result: {} {}'.format(sum(result), result))

    if sum([i for i in result if i > 0.045]) > 0.36:
        print('Excessive weight > .045: ')
        print('Percentage > .045: {}'.format(sum([x for x in result if x > 0.045])))
        print('Target: {} {}'.format(sum(portfolio_weights), portfolio_weights))
        print('Result: {} {}'.format(sum(result), result))

    if not all(b >= (a - 0.001) for a, b in zip(result, result[1:])):
        print('Result not continously rising: ')
        print('Target: {} {}'.format(sum(portfolio_weights), portfolio_weights))
        print('Result: {} {}'.format(sum(result), result))

def plot_output(result, target):
    plt.bar(range(len(result)), result,  color='b', width = 0.3)
    plt.plot(range(len(target)), target, color='r')
    plt.show()

推荐答案

在这种特殊情况下,最小化似乎只是忽略了不等式约束.我不知道为什么会发生这种情况 - 在测试一个更简单的示例时,等式和不等式约束一起正常工作.

It appears that the minimization simply ignores the inequality constraint in this particular case. I do not know why this happens - when testing a simpler example both equality and inequality constraints worked correctly together.

等式约束通常会导致数值优化出现问题,因为浮点数可能无法完全匹配它们.摆脱等式约束似乎可以解决手头的问题.

Equality constraints often cause problems in numeric optimization because it may be impossible for floating point numbers to match them exactly. Getting rid of the equality constraint seems to work as a workaround for the problem at hand.

约束 {'type': 'eq', 'fun': lambda W: sum(W) - 1} 强制所有 N 权重与1. 还有另一种强制执行的方法:我们可以仅优化 N-1 个权重并将它们的总和限制为 N-1.1. 然后剩余的权重由 1 - sum(other_weights) 隐式给出.这需要对代码进行一些更改:

The constraint {'type': 'eq', 'fun': lambda W: sum(W) - 1} forces all N weights to sum exactly to 1. There is another way to enforce this: We can optimize only N-1 weights and constrain their sum to be < 1. Then the remaining weight is implicitly given by 1 - sum(other_weights). This requires some changes to the code:

def expand_weights(weights):
    """This function takes N-1 weights and adds the implicit Nth weight 
       so that together their sum is 1."""
    return np.append(weights, 1 - np.sum(weights))

def tracking_error_function(W, port_weights):
    weight_diff = port_weights - expand_weights(W)
    weight_diff = np.sum(weight_diff ** 2)
    return weight_diff

def total_max_weight_constraint(weights):
    weights = expand_weights(weights)
    max_weights_share = sum([i for i in weights if i > 0.045])
    max_ineq = 0.36 - max_weights_share
    return max_ineq

我们简单地取原始初始权重并删除最后一个:

We simply take the original initial weights and remove the last one:

initial_weights = np.asarray(gen_initial_weights(len(portfolio_weights)))
initial_weights = initial_weights[:-1]

最后,约束变为:

c_ = ({'type': 'ineq', 'fun': lambda W: 1 - sum(W)},
      {'type': 'ineq', 'fun': total_max_weight_constraint})

运行优化并查看是否满足约束:

Run the optimization and see if the constraints are satisfied:

optimized = opt.minimize(tracking_error_function, initial_weights, 
                         args=(portfolio_weights), method='SLSQP', 
                         constraints=c_, bounds=b_, 
                         options={'maxiter': 100000, 'disp': 5})

assert np.allclose(1, np.sum(expand_weights(optimized.x)))  # check equality constraint
assert total_max_weight_constraint(optimized.x) > 0  # check second constraint

这篇关于SciPy 优化器忽略约束之一的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆