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

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

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

Решение реальной проблемы

Рассмотрим фактическую и весьма актуальную проблему.

Пандемия растет. Больницы должны быстро организовываться для лечения больных.

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

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

Однако у такого подхода есть недостатки:

  • Модель нуждается в обучении, следовательно, необходимы исторические данные по предыдущим случаям,
  • Было бы потрачено много времени на очистку и консолидацию набора данных,
  • Потребуется протестировать различные архитектуры с помощью длительных тренировок.

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

Заявление об ограничении ответственности: цель этой публикации - кратко рассказать о CSP. Теория и постановка проблемы будут сильно упущены. Для более строгого подхода см. [2] [3] [4].

Абстрагирование от проблемы

В этом посте будет мягкое введение в программирование с ограничениями с целью разрешить этот пример. Эта карта пандемии (1) иллюстрирует результат нашего алгоритма, который сопоставляет инфицированных людей с больницами. Существует несколько структур для решения ограничений. Инструменты оптимизации Google (также известные как OR-Tools) - это программный пакет с открытым исходным кодом для решения задач комбинаторной оптимизации. Наша проблема будет смоделирована с использованием этого фреймворка на Python.

from ortools.sat.python import cp_model


Параметры

А пока давайте упростим задачу до 4 параметров (1):

  • Расположение зараженных людей
  • Степень тяжести зараженных людей
  • Расположение больниц
  • Количество коек в каждой больнице

Давайте определим эти параметры в Python:

# Number of hospitals
n_hospitals = 3
# Number of infected people
n_patients = 200
# Number of beds in every hospital
n_beds_in_hospitals = [30,50,20]
# Location of infected people -- random integer tuple (x,y)
patients_loc = [(randint(0, 100), randint(0, 100)) for _ in range(n_patients)]
# Location of hospitals -- random integer tuple (x,y)
hospitals_loc = [(randint(0, 100), randint(0, 100)) for _ in range(n_hospitals)]  
# Illness severity -- 1 = mild -> 5 = severe
patients_severity = [randint(1, 5) for _ in range(n_patients)]

Переменные

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

  • Пусть Я будет множеством больниц
  • Пусть Jᵢ будет набором коек в больнице i
  • Пусть K будет набором пациентов.

Определим как наше индексированное семейство переменных:

Если в больнице i, кровать j занимает человек k, тогда xᵢⱼₖ = 1. Чтобы связать каждую койку в больнице с больным человеком, цель состоит в том, чтобы найти набор переменных, который удовлетворяет всем нашим ограничениям.

Мы можем добавить эти переменные в нашу модель:

model = cp_model.CpModel()
x = {}
for i in range(n_hospitals):
  for j in range(n_beds_in_hospitals[i]):
    for k in range(n_patients):
      x[(i,j,k)] = model.NewBoolVar("x(%d,%d,%d)" % (i,j,k))

Жесткие ограничения

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

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

Давайте сосредоточимся на первом жестком ограничении. Для каждой койки j в каждой больнице i:

  • Либо есть уникальный пациент k,
  • Либо кровать пуста.

Следовательно, это можно выразить следующим образом:

Наш решатель является комбинаторным решателем оптимизации, он может обрабатывать только целочисленные ограничения. Следовательно, необходимо превратить в целочисленное уравнение:

Затем это неравенство можно добавить к нашей модели.

# Each bed must host at most one person
for i in range(n_hospitals):
  for j in range(n_beds_in_hospitals[i]):
    model.Add(sum(x[(i,j,k)] for k in range(n_patients)) <= 1)

Затем второе жесткое ограничение: для каждого пациента k:

  • Либо он находится в уникальной койке j в уникальной больнице i,
  • Либо он дома.

Таким же образом можно перевести в целочисленное неравенство:

Наконец, это ограничение можно добавить в модель.

# Each person must be placed in at most one bed
for k in range(n_patients):
  inner_sum = []
  for i in range(n_hospitals):
    inner_sum.append(sum(x[(i,j,k)] for j in range(n_beds_in_hospitals[i]))) 
  model.Add(sum(inner_sum) <= 1)

Мягкие ограничения

Далее, есть мягкие ограничения. Это очень желательно: наше решение должно пытаться удовлетворить их как можно больше, но они не являются необходимыми для поиска решения:

  • Каждого больного следует уложить в кровать,
  • С каждым человеком должен работать ближайший госпиталь,
  • Больных в тяжелом состоянии следует лечить в первую очередь, когда не хватает коек.

В то время как жесткие ограничения моделируются как равенства или неравенства, мягкие ограничения - это выражения, которые необходимо минимизировать или максимизировать.

Пусть Ω - множество всех решений, удовлетворяющих жестким ограничениям.

Каждого больного следует уложить в кровать, чтобы максимально увеличить количество занятых коек.

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

При нехватке коек в первую очередь следует обращаться с больными в тяжелом состоянии, чтобы максимально повысить общую тяжесть состояния всех обслуживаемых пациентов. Обозначая sev (k) степень тяжести пациента k:

Тогда мы можем свести все мягкие ограничения к одной цели:

Тогда нужно быть осторожным: эти мягкие ограничения не относятся к одной и той же области.

  • Ограничение максимизации пациентов варьируется от 0 до n, где n - количество пациентов,
  • Ограничение серьезности варьируется от 0 до 5n.
  • Ограничение расстояния варьируется от 0 до максимального евклидова расстояния для всех i и k.

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

Вот соответствующий код:

# Integer distance function
idist = lambda xy1, xy2: int(((xy1[0]-xy2[0])**2 + (xy1[1]-xy2[1])**2)**0.5)
# Gain factors (1/penalty factors)
gain_max_patients = 140
gain_severity = int(140/5)
gain_distance = -1
# Maximization objective
soft_csts = []
for i in range(n_hospitals):
  for j in range(n_beds_in_hospitals[i]):
    for k in range(n_patients):
      factor = \
        gain_max_patients \
        + gain_distance * idist(hospitals_loc[i], patients_loc[k]) \
        + gain_severity * patients_severity[k]
      soft_csts.append(factor * x[(i,j,k)])
model.Maximize(sum(soft_csts))

Решатель

Теперь мы можем запустить решатель. Он попытается найти оптимальное решение в указанный срок. Если ему не удастся найти оптимальное решение, он вернет ближайшее неоптимальное решение.

solver = cp_model.CpSolver()
solver.parameters.max_time_in_seconds = 60.0
status = solver.Solve(model)

В нашем случае решающая программа возвращает оптимальное решение за 2,5 секунды (2).

Заключение

Чтобы создать это решение, достаточно 1 часа исследований и 30 минут программирования.

Для аналога Deep Learning можно спрогнозировать несколько дней очистки данных, по крайней мере, день для тестирования различных архитектур и еще один день для обучения.

Более того, модель CP-SAT очень устойчива, если хорошо смоделирована. Ниже представлены результаты с различными параметрами моделирования (3). Результаты по-прежнему согласованы во многих различных случаях, и с увеличением параметров моделирования (3000 пациентов, 1000 коек) вывод решения занял немногим менее 3 минут.

Конечно, CSP вряд ли применимы к таким темам, как компьютерное зрение и NLP, где глубокое обучение иногда является лучшим подходом. Тем не менее, в логистике, составлении графиков и планировании это часто правильный путь.

Шумиха вокруг Deep Learning вдохновила некоторых людей попробовать несколько безумных ходов, чтобы получить признание. Иногда лучше вернуться к основам, прочитав несколько обзоров по проблеме, над которой вы работаете.

Особая благодарность Лорану Перрону и его команде по исследованию операций в Google за их фантастическую работу и за время, которое они потратили на ответы на технические вопросы о StackOverflow, GitHub и группах Google.

Антуан Чемпион, 1 апреля 2020 г.

использованная литература

[1] Цзинчао Чен, Решение кубика Рубика с помощью SAT-решателей, arXiv: 1105.1436, 2011.

[2] Биере А., Хеул М. и ван Маарен Х. Справочник по выполнимости, том 185. IOS Press, 2009a

[3] Кнут, Д. Э., Искусство компьютерного программирования, Том 4, Часть 6: Удовлетворенность. Эддисон-Уэсли Профессионал, 2015

[4] Випин Кумар, Алгоритмы для задач удовлетворения ограничений: обзор, AI Magazine Volume 13, Issue 1, 1992.