Оптимизация декартовых запросов с аффинными затратами

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

Есть сервер, к которому я обращаюсь, который работает следующим образом:

  • делается запрос по записям (r1, ...rn) и полям (f1, ...fp)
  • вы можете запросить только декартово произведение (r1,...,rp) x (f1,...fp)
  • Стоимость (время и деньги), связанные с таким запросом, аффинна размеру запроса:

T((r1, ..., rn)x(f1, ..., fp) = a + b * n * p

Без ограничения общности (просто путем нормализации) мы можем предположить, что b=1 стоимость равна:

T((r1, ...,rn)x(f1,...fp)) = a + n * p

  • Мне нужно только запросить подмножество пар (r1, f(r1)), ... (rk, f(rk)), запрос, который исходит от пользователей. Моя программа действует как посредник между пользователем и сервером (который является внешним). У меня много таких запросов (десятки тысяч в день).

Графически мы можем думать об этом как о разреженной матрице n x p, для которой я хочу покрыть ненулевые значения прямоугольной подматрицей:

   r1 r2 r3 ... rp
   ------      ___
f1 |x  x|      |x|
f2 |x   |      ---
   ------
f3
..    ______
fn    |x  x|
      ------

Наличие:

  • количество подматриц остается разумным из-за постоянной стоимости
  • все «x» должны лежать в подматрице
  • общая покрываемая площадь не должна быть слишком большой из-за линейных затрат

Я назову g коэффициентом разреженности моей задачи (количество необходимых пар от общего количества возможных пар, g = k / (n * p). Я знаю коэффициент a.

Есть очевидные наблюдения:

  • если a маленькое, лучшим решением будет запросить каждую пару (запись, поле) независимо, а общая стоимость составит: k * (a + 1) = g * n * p * (a + 1)
  • если a велико, лучшим решением будет запрос всего декартова произведения, а общая стоимость составит: a + n * p
  • второе решение лучше, как только g > g_min = 1/ (a+1) * (1 + 1 / (n * p))
  • конечно, порядки в декартовых произведениях не важны, поэтому я могу переставить строки и столбцы моей матрицы, чтобы ее было легче покрыть, например:
   f1 f2 f3
r1  x    x
r2     x 
r3  x    x

можно переупорядочить как

   f1 f3 f2
r1  x  x
r3  x  x
r2       x

И есть оптимальное решение - запросить (f1,f3) x (r1,r3) + (f2) x (r2)

  • Пробовать все решения и искать более низкую стоимость — не вариант, потому что комбинаторика взрывается:
for each permutation on rows: (n!)
   for each permutation on columns: (p!)
       for each possible covering of the n x p matrix: (time unknown, but large...)
           compute cost of the covering

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

Чтобы представить некоторые цифры, мое n находится где-то между 1 и 1000, а мое p где-то между 1 и 200. Шаблон покрытия действительно «блочный», потому что записи поступают в классы, для которых запрашиваемые поля похожи. К сожалению, я не могу получить доступ к классу записи...

Вопрос 1. Есть ли у кого-нибудь идея, умное упрощение или ссылка на статью, которая может оказаться полезной? Поскольку у меня много запросов, я ищу алгоритм, который в среднем работает хорошо (но я не могу позволить, чтобы он работал очень плохо в каком-то экстремальном случае, например, при запросе всего матрица, когда n и p велики, а запрос действительно довольно разреженный).

Вопрос 2. На самом деле проблема еще сложнее: стоимость на самом деле больше похожа на форму: a + n * (p^b) + c * n' * p', где b — константа ‹ 1 (как только запрашивается запись для поля, она не слишком дорого запрашивать другие поля), а n' * p' = n * p * (1 - g) - это количество ячеек, которые я не хочу запрашивать (потому что они недействительны, а запрос недействительных вещей требует дополнительных затрат). Я даже не мечтаю о быстром решении этой проблемы, но все же... у кого есть идеи?


comment
У вас есть оракул, который говорит вам, какие (строки, столбцы) пусты бесплатно?   -  person Jonathan Graehl    schedule 10.09.2009
comment
Вы можете указать наборы строк и полей явно, т.е. вам не нужно указывать непрерывный прямоугольник в фиксированной системе координат (конкретные перестановки строк и столбцов)?   -  person Jonathan Graehl    schedule 10.09.2009
comment
Re: мой первый вопрос, ответ да, если я правильно понимаю запросы, поступающие от части пользователей.   -  person Jonathan Graehl    schedule 10.09.2009
comment
ответ на ваш второй вопрос тоже да. Если есть перестановка рядов и столбцов, дающая менее разреженное покрытие, то вообще лучше (и я не понял вашего комментария к Артелиусу, ответил, извините. Я ограничен смежными прямоугольниками над перестановками рядов и столбцов).   -  person LeMiz    schedule 10.09.2009


Ответы (6)


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

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

Статья в Википедии дает результаты о неаппроксимации, согласно которым задача не может быть решена за полиномиальное время лучше, чем с коэффициентом 0.5 * log2(n), где n — количество наборов. В вашем случае 2^(n * p) - это (весьма пессимистичная) верхняя граница количества наборов и выходов, что вы можете найти решение только до коэффициента 0.5 * n * p за полиномиальное время (кроме N = NP и игнорирования различных затрат).

Оптимистичная нижняя граница количества наборов, игнорирующих перестановки строк и столбцов, равна 0.5 * n^2 * p^2, что дает гораздо лучший коэффициент log2(n) + log2(p) - 0.5. Как следствие, вы можете рассчитывать найти решение только в наихудшем случае n = 1000 и p = 200 с коэффициентом примерно 17 в оптимистическом случае и примерно с коэффициентом 100.000 в пессимистическом случае (по-прежнему игнорируя различные затраты).

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

person Daniel Brückner    schedule 10.09.2009
comment
Спасибо за ответ. Я прекрасно знаю, что это решение NP Hard, но я ищу решение, которое будет хорошо работать на практике. Кроме того, после тщательного изучения я думаю, что формулировка покрытия множества нетривиальна, потому что 1) функция стоимости очень конкретна 2) ограничения также. Время начать щедрость! - person LeMiz; 16.09.2009

Я уверен, что где-то есть действительно хороший алгоритм для этого, но вот мои собственные интуитивные идеи:

  1. Подход с броском прямоугольников:

    • Determine a "roughly optimal" rectangle size based on a.
    • Поместите эти прямоугольники (возможно, случайным образом) на требуемые точки, пока все точки не будут покрыты.
    • Теперь возьмите каждый прямоугольник и максимально уменьшите его, не «теряя» никаких точек данных.
    • Найдите прямоугольники, расположенные близко друг к другу, и решите, будет ли их объединение дешевле, чем хранение их по отдельности.
  2. Расти

    • Start off with each point in its own 1x1 rectangle.
    • Найдите все прямоугольники в n строках/столбцах (где n может быть основано на a); посмотрите, сможете ли вы объединить их в один прямоугольник бесплатно (или с отрицательной стоимостью: D).
    • Повторить.
  3. Сокращать

    • Start off with one big rectangle, that covers ALL points.
    • Найдите подпрямоугольник, у которого пара общих сторон с большим, но в котором очень мало точек.
    • Вырежьте его из большого, получив два меньших прямоугольника.
    • Повторить.
  4. Четырехместный

    • Divide the plane into 4 rectangles. For each of these, see if you get a better cost by recursing further, or by just including the whole rectangle.
    • Теперь возьмите свои прямоугольники и посмотрите, сможете ли вы объединить любой из них с небольшими/бесплатными затратами.\

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

person Artelius    schedule 10.09.2009
comment
Вы не ограничены прямоугольниками. - person Jonathan Graehl; 10.09.2009
comment
@wrang-wrang: да, я. @ Artelius, да, это правда, может быть лучше иметь перекрывающиеся прямоугольники, чем строго не перекрывающиеся. В настоящее время я тестирую модифицированную версию вашего решения «Grow». Я начинаю с прямоугольника 1x1, затем объединяю два менее затратных (с точки зрения разреженности) прямоугольника и повторяю. Он дает линейный список кластеризаций, на которых я беру минимальную стоимость в этом списке. Но настоящая проблема не здесь, а в перестановках, которые я могу сделать в строках и столбцах, что заставляет комбинаторику взрываться (n! * p!, без учета симметрии) - person LeMiz; 10.09.2009
comment
Ах, так r1, ..., rn не обязательно должны быть последовательными? Я думаю, моя голова взорвется. - person Artelius; 10.09.2009

Хорошо, мое понимание вопроса изменилось. Новые идеи:

  • Сохраняйте каждую строку как длинную битовую строку. И пары битовых строк вместе, пытаясь найти пары, которые максимизируют количество битов 1. Объедините эти пары в более крупные группы (сортируйте и старайтесь сопоставить действительно большие пары друг с другом). Затем создайте запрос, который будет охватывать самую большую группу, а затем забудьте обо всех этих битах. Повторяйте, пока все не сделаете. Может быть, иногда переключаться со строк на столбцы.

  • Ищите все строки/столбцы с нулем или несколькими точками в них. «Удалите» их временно. Теперь вы смотрите на то, что покрывается запросом, который не включает их. Теперь, возможно, примените один из других методов и впоследствии обработайте игнорируемые строки/столбцы. Другой способ думать об этом: сначала работать с более плотными точками, а затем переходить к более разреженным.

person Artelius    schedule 10.09.2009

Поскольку ваши значения редки, может ли быть так, что многие пользователи запрашивают аналогичные значения? Возможно ли кэширование в вашем приложении? Запросы могут быть проиндексированы хэшем, который является функцией положения (x,y), чтобы вы могли легко идентифицировать кэшированные наборы, которые попадают в правильную область сетки. Хранение кэшированных наборов в дереве, например, позволит вам очень быстро найти минимальные кэшированные подмножества, которые покрывают диапазон запроса. Затем вы можете выполнить линейный поиск по небольшому подмножеству.

person ire_and_curses    schedule 10.09.2009
comment
Привет, мы, конечно, уже кешируем результаты. Настоящая проблема заключается в том, что мы действительно не знаем, как сделать запрос истекающим. Таким образом, для важных бизнес-целей запрашивающие системы имеют возможность обходить кеш для определенных значений (фактически это одна из причин разреженности запроса). - person LeMiz; 10.09.2009

Я бы рассмотрел n записей (строк) и p полей (столбцов), упомянутых в запросе пользователя, заданных как n точек в p-мерном пространстве ({0,1}^p) с i-й координатой, равной 1, если он имеет X , и определить иерархию кластеров, где самый грубый кластер находится в корне, включая все домены X. Для каждого узла в иерархии кластеризации рассмотрите продукт, который покрывает все необходимые столбцы (это строки (любой подузел) x столбцы (любой подузел)). Затем решите снизу вверх, объединять ли дочерние покрытия (оплачивая все покрытие) или оставить их как отдельные запросы. (покрытия состоят не из смежных столбцов, а именно из тех, которые необходимы, т.е. подумайте о битовом векторе)

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

person Jonathan Graehl    schedule 10.09.2009

Я немного поработал над этим, и вот очевидный, O (n ^ 3) жадный алгоритм нарушения симметрии (записи и поля обрабатываются отдельно) в псевдокоде, похожем на python.

Идея тривиальна: мы начинаем с того, что пробуем один запрос на запись, и делаем самое достойное слияние до тех пор, пока не останется ничего достойного для слияния. У этого алгоритма есть очевидный недостаток, заключающийся в том, что он не допускает перекрывающихся запросов, но я ожидаю, что он будет работать достаточно хорошо в реальном случае (с функцией стоимости a + n * (p^b) + c * n * p * (1 - g)):

# given are
# a function cost request -> positive real
# a merge function that takes two pairs of sets (f1, r1) and (f2, r2) 
# and returns ((f1 U f2), (r1 U r2))

# initialize with a request per record

requests = [({record},{field if (record, field) is needed}) for all needed records]
costs = [cost(request) for request in requests]

finished = False

while not finished: # there might be something to gain
    maximum_gain = 0
    finished = True
    this_step_merge = empty

    # loop onto all pairs of request
    for all (request1, request2) in (requests x request) such as request1 != request2:
        merged_request = merge(request1, request2)
        gain = cost(request1) + cost(request2) - cost(merged_request)

        if gain > maximum_gain:
            maximum_gain = gain
            this_step_merge = (request1, request2, merged_request)

    # if we found at least something to merge, we should continue
    if maximum_gain > 0:
        # so update the list of requests...
        request1, request2, merged_request = this_step_merge
        delete request1 from requests
        delete request2 from requests
        # ... and we are not done yet
        insert merged_request into requests
        finished = False

output requests

Это O(n3 * p), потому что:

  • после инициализации начинаем с n запросов
  • цикл while удаляет ровно один запрос из пула на каждой итерации.
  • внутренний цикл for выполняет итерацию по (ni^2 - ni)/2 различным парам запросов, при этом ni увеличивается от n до единицы в худшем случае (когда мы объединяем все в один большой запрос).

    1. Can someone help me pointing the very bad cases of the algorithm. Does it sound reasonnable to use this one ?
    2. Это O (n ^ 3), что слишком дорого для больших входных данных. Любая идея, чтобы оптимизировать его?

Заранее спасибо !

person Community    schedule 20.09.2009