Документация Engee

Логистическая регрессия

Это руководство было изначально предоставлено Франсуа Пако (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)
θ♯ = value.(model[:θ])
11-element Vector{Float64}:
  0.02041320313399326
  0.1613990324656828
  0.35700771838432327
 -0.3078808218759407
 -0.39391842182094333
 -0.059140401509826857
  0.3471736591163848
 -0.8812123235444669
  0.20125660912000315
  0.5409398851901865
  0.08090419699796665

Похоже, что скорость сходимости не зависит ни от корреляции набора данных, ни от штрафа .

Регуляризированная по логистическая регрессия

Теперь сформулируем логистическую задачу с членом регуляризации . Регуляризация по обеспечивает разреженность оптимального решения итоговой задачи оптимизации. К счастью, норма реализована в 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)

Расширения

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

где  — максимальное количество ненулевых компонентов в векторе , а  — псевдонорма :

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