barromean.png
Кольца Барромео и единственное известное мне их практическое применение

Кольца Барромео — это конструкция из трёх колец, обладающая интересным свойством: эти кольца не сцеплены попарно между собой, но полная конструкция из трёх колец неразделима. Ну или если перефразировать: вся конструкция неразделима, но если любое из колец магическим образом пропадает, то оставшиеся два можно разделить. Единственное известное мне практическое применение этих колец — использование в качестве логотипа пива Ballantine. В прошлом году в моей практике повстречался интересный алгоритмический баг, который у меня ассоциируется именно с этой конструкцией.

Сразу скажу, что в последующем повествовании математика заметна упрощена. Итак, я работал над некоторой системой, которая решала задачу максимизации с помощью градиентного подъема (как градиентный спуск, но для максимизации), т. е. строила следующую последовательность

x_{k+1}=x_k+\alpha_k\nabla f(x_k).

В какой-то момент градиентный подъем было решено заменить на метод побыстрее, попробовали метод Ньютона и в итоге получили работающую систему в виде, в котором она попала мне попробовать еще её ускорить. На моё удивление любые попытки как-то улучшить сходимость известными стандартными техниками приводили к полностью противоположному результату, что-то явно было не так, но я никак не понимал что именно. После долгой ревизии я все-таки кое-что заметил, итак вот форма метода Ньютона, которая я обнаружил

x_{k+1}=x_k+[\nabla^2f(x_k)]^{-1}\nabla f(x_k).

Что-нибудь заметили?

А вот в чем проблема

неправильный знак

Проиллюстрирую её на небольшом примере: допустим мы хотим найти максимум функции

f(x)=2x^2-x^4
image.png
f(x)=2x^2-x^4

Легко проверить, что у этой функции два максимума \{-1, 1\}. Метод Ньютона по указанной выше формуле строил бы последовательность

x_{k+1}=x_k+\frac{x_k-x_k^3}{1-3x_k^2}.

Приводить дальше это выражение не буду, его уже можно анализировать: если x_k>1, то дробь в выражении выше положительна, а значит если x_0>1, то последовательность возрастает и, более того, расходится. Хмм, как-то странно для метода Ньютона. Ну да ладно, я уже достаточно написал текста для тех, кто хотел разобраться сам и не хотел спойлеров в поле зрения. Проблема в знаке дроби, там должен быть минус, можете поэкспериментировать со следующим кодом и убедиться, что при wrong_sign=True метод почти всегда расходится

def newton_method_example1(x0, wrong_sign):
    for i in range (10):
        step = (x0 - x0 ** 3) / (1 - 3 * x0 ** 2)
        x0 = x0 + (step if wrong_sign else -step)
        print(x0)

    return x0

newton_method_example1(2., True)

А вот с wrong_sign=False метод сходится, но возникает другая особенность метода Ньютона: он будет сходиться к -1, 1 или 0. Почему 0? Это локальный минимум функции и, как оказывается, метод Ньютона для поиска минимума или максимума имеет одинаковую форму в отличие от градиентного спуска. Например для функций f(x)=x^4 и f(x)=-x^4 метод Ньютона бы имел одинаковую форму

x_{k+1}=x_k-\frac{x_k}{3}

в первом случае искал бы минимум, а во втором максимум. В ситуации выше в зависимости от начальной точки мы попадём либо в максимумы -1, 1, либо в локальный минимум 0. Если быть точнее, то для выпуклых функций метод Ньютона ищет минимум, для выпуклых вверх — максимум. Функция f(x)=2x^2-x^4 меняет свою кривизну в точках \pm 1/\sqrt{3} и из-за этого возникают спецэффекты.

Итак, мы выяснили, что проблема была в знаке. Казалось бы, меняем знак и всё должно заработать, так и сделал — всё сломалось. В предыдущем примере метод Ньютона с неправильным знаком расходился, а у нас же изначально система с неправильным знаком работала, а после его замены перестала работать. Опять что-то не сходится, почему изначально всё работало, а при исправлении знака сломалось? Самое время посмотреть, что за функцию мы пытаемся оптимизировать, как оказалось это что-то вида

f(x)=-|x|^{1/4}
f(x)=-|x|^1/4
f(x)=-|x|^1/4

Интересна эта функция тем, что она выпукла, а значит метод Ньютона по идее должен искать у неё минимум. С другой стороны очевидно, что минимума у неё нет, есть максимум в нуле, в котором она еще и не дифференцируема. Что же будет, если мы применим метод Ньютона к этой функции? Давайте проверим, если x_k\neq 0, то правильный метод Ньютона будет иметь вид

x_{k+1}=x_k+\frac{4x_k}{3}=\frac{7x_k}{3}

а неправильный

x_{k+1}=x_k-\frac{4x_k}{3}=-\frac{x_k}{3}

а значит неправильный будет сходиться, а правильный не будет. В итоге вот что имеем:

  • Метод Ньютона не должен работать с неправильным знаком

  • Метод Ньютона не должен работать с неправильный функцией

  • Градиентный спуск с неправильным знаком и неправильной функцией также не работает

  • Внезапно метод Ньютона с неправильным знаком и неправильной функцией работает!

Эпилог

Как я сказал, ситуацию я довольно сильно упростил, мне понадобилось много времени, чтобы осознать происходящее в реальной системе. Пока я не увидел все три компоненты, то не осознал что и почему не работает, а они не лежали передо мной в явной форме. В описанной ситуации эффект колец Барромео негативен, но существуют и ситуации когда подобные объекты наоборот приносят пользу. Например в криптографии есть задача о разделении секрета: вам нужно научиться преобразовывать объект таким образом, чтобы его можно было разделить на N частей и при этом из любых K этих частей можно было восстановить исходный объект, но никаких K-1 частей недостаточно чтобы даже частично его восстановить. Решение такой задачи существует, например схема Шамира, она мало имеет общего с кольцами Барромео, но они всё еще представляют собой хорошую иллюстрацию эффекта.

Комментарии (0)