scipy-optimize-minimise не выполняет оптимизацию - CONVERGENCE: NORM_OF_PROJECTED_GRADIENT _ ‹= _ PGTOL

Я пытаюсь минимизировать функцию, определенную следующим образом:

utility(decision) = decision * (risk - cost)

где переменные имеют следующий вид:

решение = двоичный массив

риск = массив чисел с плавающей запятой

стоимость = постоянная

Я знаю, что решение примет форму:

решение = 1 if (риск ›= порог)

решение = 0 в противном случае

Поэтому, чтобы минимизировать эту функцию, я могу предположить, что я преобразую полезность функции, чтобы она зависела только от этого порога. Мой прямой перевод на scipy следующий:

def utility(threshold,risk,cost):

     selection_list = [float(risk[i]) >= threshold for i in range(len(risk))]
     v = np.array(risk.astype(float)) - cost

     total_utility = np.dot(v, selection_list)

     return -1.0*total_utility

result = minimize(fun=utility, x0=0.2, args=(r,c),bounds=[(0,1)], options={"disp":True} )

Это дает мне следующий результат:

fun: array([-17750.44298655])  hess_inv: <1x1 LbfgsInvHessProduct with
dtype=float64>
jac: array([0.])   
message: b'CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL'
nfev: 2
nit: 0    status: 0   success: True
x: array([0.2])

Однако я знаю, что результат неправильный, потому что в этом случае он должен быть равен cost. Кроме того, независимо от того, какой x0 я использую, он всегда возвращает его как результат. Глядя на результаты, я заметил, что jacobian = 0 и некорректно вычисляет 1 итерацию.

Более подробно изучить функцию. Я рисую его и наблюдаю, что он не выпуклый в пределах границ, но мы ясно видим минимум на 0,1. Однако, как бы я ни настраивал границы, чтобы они находились только в выпуклой части, результат все тот же.

Функциональный график

Что я мог сделать, чтобы свести к минимуму эту функцию?


person user13040634    schedule 17.03.2020    source источник
comment
Вы дважды определили свою utility функцию. Полагаю, ваш вопрос в основном касается второго, не так ли?   -  person Ingo    schedule 17.03.2020
comment
Да, это то же самое, поскольку мы говорим: полезность = решение * (риск - стоимость). В коде, который я пишу: selection_list = solution v = риск - стоимость, полезность = точечный продукт {v, selection_list} (который является элементом умножения -wise и sum) Следовательно, оба выражения технически одно и то же. :)   -  person user13040634    schedule 19.03.2020


Ответы (1)


Сообщение об ошибке сообщает вам, что градиент был в какой-то момент слишком мал и, следовательно, численно равен нулю. Вероятно, это связано с пороговым значением, которое вы используете при вычислении своего selection_list. Там вы говорите float(risk[i]) >= threshold, у которого почти везде есть производная 0. Следовательно, почти каждое начальное значение будет давать вам предупреждение.

Решением может быть некоторое сглаживание операции определения порога. Поэтому вместо float(risk[i]) >= threshold вы должны использовать непрерывную функцию:

def g(x):
    return 1./(1+np.exp(-x))

С помощью этой функции вы можете выразить операцию определения порога как g((risk[i] - threshold)/a), который является параметром a. Чем больше a, тем эта модифицированная функция ошибок ближе к тому, что вы делаете до сих пор. Где-то вроде a=20 у вас, вероятно, будет примерно то же самое, что у вас есть сейчас. Следовательно, вы должны получить последовательность решений, в которой вы начинаете с a=1, а затем берете это решение как начальное значение для той же проблемы с a=2, принимаете это решение как начальное значение для проблемы с a=4 и так далее. В какой-то момент вы заметите, что изменение a больше не меняет решение, и все готово.

person Ingo    schedule 17.03.2020