Введение

Мы знаем, что для системы линейных уравнений, в которой количество уравнений меньше числа переменных, должно быть бесконечное число решений. И что количество свободных переменных должно быть равно количеству переменных минус количество уравнений.

Но как мы можем использовать этот факт? Например, для задачи оптимизации с ограничениями линейного равенства можно было бы надеяться использовать ограничения для уменьшения количества переменных. А затем иметь возможность использовать этот новый сокращенный набор переменных в алгоритме неограниченной оптимизации.

Как мы можем этого добиться? Введите нулевое пространство.

Нулевое пространство

Давайте определим нашу недоопределенную систему из M уравнений и N переменных как:

Нулевое пространство матрицы A определяется как Z. Это набор N-M векторов-столбцов, которые «обнуляют» A. Каждый из этих независимых векторов-столбцов дает нулевой вектор при умножении на A слева.

Поскольку N-M векторов-столбцов в Z дают нулевой вектор при умножении на A, линейная комбинация этих векторов также будет делать то же самое.

Таким образом, мы сократили количество переменных с N до N-M переменных в векторе u.

Однако это справедливо только для однородной линейной системы. Это системы с нулевой правой частью. Но, имея единственное решение нашей линейной системы, мы можем преобразовать ее в однородную систему:

И мы можем сделать вывод:

Нулевое пространство в SciPy

В SciPy есть функция, которая вычисляет ортонормированный базис нулевого пространства Z с использованием разложения по сингулярным значениям. Он автоматически определит количество столбцов в Z на основе вычисленных сингулярных значений. Z будет иметь столбцы N-ранга (A). Если все строки линейно независимы, то Rank(A)=M.

import numpy as np
from scipy.linalg import null_space
np.random.seed(0)

# Our linear system
M,N = 100,200
A,b = np.random.randn(M,N),np.random.randn(M)

# Calculate Z
Z = null_space(A)

# An easy way to calculate a single solution to Ax = b:
# This method will give the solution with the minimal norm ||x||.
x0 = np.linalg.pinv(A)@b

# Function to calculate x(N) from u(N-rank(A))
x_from_u = lambda u: Z@u+x0

# Generate a new x from a random u
xnew = x_from_u(np.random.randn(Z.shape[1]))

# Check that the linear system is still solved.
assert(np.allclose(A@xnew,b))

Обсуждение

Хотя SVD является наиболее точным способом численного расчета базиса нулевого пространства, может возникнуть некоторая ошибка. Мы можем убедиться в этом, сравнив A.dot(Z) с 0. В приведенном выше коде он имеет порядок 1e-14. Но оно будет расти вместе с величиной тебя. Если u станет слишком большим, то Ax=b больше не будет в достаточной степени удовлетворяться.

Таким образом, рекомендуется убедиться, что решение x0 близко к области x, которую вы исследуете. Следующий код находит решение x0 вблизи указанной точки v.

from scipy.linalg import solve

# Minimize ||x-v||**2 subject to Ax = b. Using lagrange multipliers.
# I.e. finds a solution to Ax=b close to v.
def get_solution(v,A,b):
    M,N=A.shape[0],A.shape[1]
    mat = np.block([[np.eye(N),A.T],[A,np.zeros((M,M))]])
    y = np.concatenate([v,b])
    ret = solve(mat,y,assume_a="sym")[0:N]
    return ret

x0 = get_solution(np.ones(N),A,b)

В заключение

Функцию null_space() в SciPy можно использовать для перехода к новому, меньшему набору переменных. Если между вашими переменными существует связь, описываемая линейной системой.

Помимо прочего, его можно использовать для реализации ограничений линейного равенства без привязки к алгоритму оптимизации с ограничениями.

Это не тот метод, который часто используется, и следует опасаться числовых неточностей. Т.е. часто проверяйте, что линейная система удовлетворяется (||A(x0+Zu)-b||**2 мала).

Дополнительный пример с ограниченной линейной регрессией

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

Используя множители Лагранжа, каждое ограничение вводит новую переменную, которую необходимо решить. Полученная линейная система также больше не является положительно-определенной.

Изменяя переменные с использованием пустого пространства, мы удаляем по одной переменной для каждого ограничения. И полученная линейная система будет положительно определенной. Но, как упоминалось выше, могут возникнуть проблемы с небольшими числовыми неточностями в вычислении null_space(), которые растут по мере удаления от нашего единственного решения x0.

from scipy.linalg import null_space,solve
import numpy as np
##
## Generate some data
####

N,M,NC=200,50,20 # Number of datapoints, variables and constraints.
X=np.concatenate([np.random.randn(N,M-1),np.ones((N,1))],axis=1)
[email protected](M) +np.random.randn(N)

# Generate some constraints
A,b = np.random.randn(NC,M),np.random.randn(NC)

# Let's also add some l2-regularization...
l2_reg = np.eye(M)
l2_reg[-1,-1] = 0.0 # Only on weights, not on the bias.

# Define Q and c in the loss function.
# This is from the standard form of quadratic programming loss function.
Q=X.T@X+l2_reg
c = -X.T@y

##
## Solution using Lagrange multipliers
####
mat = np.block([[Q,A.T],[A,np.zeros((NC,NC))]])
vec = np.concatenate([-c, b])
sol1 = solve(mat,vec,assume_a="sym")[0:M]


##
## Solution using null space to change variables.
####

Z = null_space(A)
x0 = np.linalg.pinv(A)@b

#Just plug in our formula x=Zu+x0 in the loss function. And get:
Q_new = Z.T@Q@Z
c_new = Z.T@(c+Q@x0)

u = solve(Q_new,-c_new,assume_a="pos")
sol2 = Z@u+x0


##
## Check the two solutions
####
assert(np.allclose(sol1,sol2))

Рекомендации