Это руководство было изначально предоставлено Франсуа Пако (François Pacaud).
В этом руководстве показано, как решить задачу логистической регрессии с помощью JuMP. Логистическая регрессия — это известный метод машинного обучения, полезный для классификации двоичных переменных по заданному набору признаков. С этой целью мы находим оптимальную комбинацию признаков, максимизирующую (логарифмическое) правдоподобие на обучающем наборе. С современной точки зрения получившаяся задача оптимизации является выпуклой и дифференцируемой. Более того, ее можно представить в виде конуса.
Формулировка задачи логистической регрессии
Допустим, имеется множество точек обучающих данных , где для каждого есть вектор признаков и категориальное наблюдение .
Логарифмическое правдоподобие определяется по формуле:
а оптимальное значение минимизирует функцию логистических потерь:
Как правило, вместо решения предыдущей задачи оптимизации напрямую лучше добавить член регуляризации:
со штрафом и функцией нормы . Введение такого члена регуляризации позволяет избежать переобучения на обучающем наборе и обычно получить более высокую оценку при перекрестной проверке.
Переформулировка в виде задачи конической оптимизации
Если ввести вспомогательные переменные и , задача оптимизации станет эквивалентна следующему:
Теперь весь фокус в том, чтобы переформулировать ограничения с помощью экспоненциального конуса.
Действительно, переходя к экспоненциальной форме, мы видим, что для всех ограничение эквивалентно
с . Затем, добавляя две вспомогательные переменные и такие, что и , мы получаем эквивалентную формулировку:
При таких условиях коническая версия задачи логистической регрессии записывается так:
и, таким образом, содержит переменных и ограничений ( — это виртуальное ограничение, делающее нотацию понятнее). Следовательно, при получается большое количество переменных и ограничений.
Подгонка логистической регрессии с помощью конического решателя
Теперь пора перейти к реализации. В качестве конического решателя выберем SCS.
using JuMP
import Random
import SCS
В этом руководстве используются множества из MathOptInterface. JuMP по умолчанию экспортирует символ MOI как псевдоним для пакета MathOptInterface.jl. Мы рекомендуем сделать это более явным в вашем коде, добавив следующие строки:
import MathOptInterface as MOI
Random.seed!(2713);
Начнем с реализации функции для генерирования фиктивного набора данных, в котором можно было бы настроить корреляцию между переменными признаков. Функция является прямой транскрипцией той, которая используется в этой записи в блоге.
function generate_dataset(n_samples = 100, n_features = 10; shift = 0.0)
X = randn(n_samples, n_features)
w = randn(n_features)
y = sign.(X * w)
X .+= 0.8 * randn(n_samples, n_features) # добавляем шум
X .+= shift # смещаем точки в пространстве признаков
X = hcat(X, ones(n_samples, 1))
return X, y
end
generate_dataset (generic function with 3 methods)
Запишем функцию softplus для формулирования каждого ограничения с двумя экспоненциальными конусами.
function softplus(model, t, u)
z = @variable(model, [1:2], lower_bound = 0.0)
@constraint(model, sum(z) <= 1.0)
@constraint(model, [u - t, 1, z[1]] in MOI.ExponentialCone())
@constraint(model, [-t, 1, z[2]] in MOI.ExponentialCone())
end
softplus (generic function with 1 method)
Регуляризированная по логистическая регрессия
Затем с помощью функции softplus можно написать модель оптимизации. В случае регуляризации по ограничение переписывается как ограничение конуса второго порядка.
function build_logit_model(X, y, λ)
n, p = size(X)
model = Model()
@variable(model, θ[1:p])
@variable(model, t[1:n])
for i in 1:n
u = -(X[i, :]' * θ) * y[i]
softplus(model, t[i], u)
end
# Добавляем регуляризацию по ℓ2
@variable(model, 0.0 <= reg)
@constraint(model, [reg; θ] in SecondOrderCone())
# Определяем целевую функцию
@objective(model, Min, sum(t) + λ * reg)
return model
end
build_logit_model (generic function with 1 method)
Генерируем набор данных.
Будьте осторожны, так как при больших n и p решатель SCS может не сойтись.
n, p = 200, 10
X, y = generate_dataset(n, p; shift = 10.0);
# Теперь можно решить задачу логистической регрессии
λ = 10.0
model = build_logit_model(X, y, λ)
set_optimizer(model, SCS.Optimizer)
set_silent(model)
optimize!(model)
@assert is_solved_and_feasible(model)
Похоже, что скорость сходимости не зависит ни от корреляции набора данных, ни от штрафа .
Регуляризированная по логистическая регрессия
Теперь сформулируем логистическую задачу с членом регуляризации . Регуляризация по обеспечивает разреженность оптимального решения итоговой задачи оптимизации. К счастью, норма реализована в MathOptInterface как множество. Поэтому мы можем сформулировать задачу разреженной логистической регрессии с помощью множества MOI.NormOneCone.
function build_sparse_logit_model(X, y, λ)
n, p = size(X)
model = Model()
@variable(model, θ[1:p])
@variable(model, t[1:n])
for i in 1:n
u = -(X[i, :]' * θ) * y[i]
softplus(model, t[i], u)
end
# Добавляем регуляризацию по ℓ1
@variable(model, 0.0 <= reg)
@constraint(model, [reg; θ] in MOI.NormOneCone(p + 1))
# Определяем целевую функцию
@objective(model, Min, sum(t) + λ * reg)
return model
end
# Вспомогательная функция для подсчета ненулевых компонентов:
count_nonzero(v::Vector; tol = 1e-6) = sum(abs.(v) .>= tol)
# Мы решаем задачу разреженной логистической регрессии на том же наборе данных, что и раньше.
λ = 10.0
sparse_model = build_sparse_logit_model(X, y, λ)
set_optimizer(sparse_model, SCS.Optimizer)
set_silent(sparse_model)
optimize!(sparse_model)
@assert is_solved_and_feasible(sparse_model)
θ♯ = value.(sparse_model[:θ])
println(
"Number of non-zero components: ",
count_nonzero(θ♯),
" (out of ",
p,
" features)",
)
Number of non-zero components: 8 (out of 10 features)
Расширения
Прямым расширением было бы использование разреженной логистической регрессии со строгим пороговым значением, в формулировку которой, в отличие от нестрогой версии с использованием регуляризации по , добавляется явное ограничение мощности:
где — максимальное количество ненулевых компонентов в векторе , а — псевдонорма :
Ограничение мощности можно переформулировать с помощью двоичных переменных. Это позволяет решить задачу строгой разреженной регрессии с помощью любого решателя, поддерживающего частично целочисленные конические задачи.