Есть ли лучший способ угадать возможные неизвестные переменные без грубой силы, чем я делаю? Машинное обучение?

У меня есть игра со следующими правилами:

  • Пользователь получает цены на фрукты и имеет возможность покупать или продавать предметы в корзине с фруктами каждый раз.

  • Пользователь не может сделать более 10% общего изменения в своей корзине за один ход.

  • Цены на фрукты меняются каждый день, и когда их умножают на количество предметов в корзине фруктов, общая стоимость корзины меняется также и по отношению к изменениям цен на фрукты каждый день.
  • Программе предоставляется только текущая цена всех фруктов и текущая стоимость корзины (текущая цена количества фруктов * для всех предметов в корзине).
  • Основываясь на этих двух входах (все цены на фрукты и общая стоимость корзины), программа пытается угадать, какие предметы находятся в корзине.
  • Корзина не может содержать более 100 предметов, но слоты могут быть пустыми
  • Игрок может сыграть несколько оборотов.

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

Я изо всех сил пытаюсь найти ответ, но на мой взгляд, это не сложно. Если у меня есть таблица ниже. Я мог бы изучить 1-й день и получить следующие данные:

Apple   1
Pears   2
Oranges 3

Basket Value = 217

Я могу сделать подсчет салфеток и предположить, что веса в корзине: 0 яблоко, 83 груши и 17 апельсинов, равное значению корзины 217.

На следующий день меняются значения фруктов и корзин. To (яблоко = 2, груша 3, апельсины 5) со значением корзины 348. Когда я принимаю мои предполагаемые веса выше (0,83,17), я получаю общее значение 334 - неверно! Запустив это по моему сценарию, я вижу, что самое близкое совпадение - 0 яблок, 76 груш, 24 апельсина, которые хотя и равны 348, когда% изменено в его изменении на 38%, поэтому его невозможно!

Я знаю, что могу полностью переборщить, но если у меня будет 1000 плодов, это не будет масштаб. Не прыгать на какой-либо подножку, но может ли что-то вроде нейронной сети быстро исключить маловероятное, поэтому я вычисляю большие объемы данных? Я думаю, что они должны быть более масштабируемыми/быстрее, чем чистая грубая сила? Или есть ли другие решения, которые могли бы получить результат?

Вот исходные данные (помните, что программа может видеть только цены и общую стоимость корзины):

enter image description here

Вот какой код грубой силы (Спасибо @paul Hankin за более чистый пример, чем у меня):

def possibilities(value, prices):
    for i in range(0, value+1, prices[0]):
        for j in range(0, value+1-i, prices[1]):
            k = value - i - j
            if k % prices[2] == 0:
                yield i//prices[0], j//prices[1], k//prices[2]

def merge_totals(last, this, r):
    ok = []
    for t in this:
        for l in last:
            f = int(sum(l) * r)
            if all(l[i] -f <= t[i] <= l[i] + f for i in range(len(l))):
                ok.append(t)
                break
    return ok

days = [
    (217, (1, 2, 3)),
    (348, (2, 3, 5)),
    (251, (1, 2, 4)),
]

ps = None
for i, d in enumerate(days):
    new_ps = list(possibilities(*d))
    if ps is None:
        ps = new_ps
    ps = merge_totals(ps, new_ps, 0.10)

    print('Day %d' % (i+1))
    for p in ps:
        print('Day %d,' % (i+1), 'apples: %s, pears: %s, oranges: %s' % p)
    print

Обновление - информация до сих пор потрясающая. Имеет ли смысл разбить проблему на две проблемы? Один из них генерирует возможности, в то время как другой находит связь между возможностями (не более 10% дневного изменения). Исключив возможности, не могли ли они также использоваться, чтобы помочь только создать возможности, которые возможны, для начала? Я не уверен, что подход все же, но я чувствую, что обе проблемы разные, но тесно связаны. Твои мысли?

Обновление 2 - есть много вопросов об изменении%. Это общий объем предметов в корзине, которые могут измениться. Чтобы использовать пример игры, представьте, что в магазине говорится: вы можете продавать/возвращать/покупать фрукты, но они не могут превышать 10% от вашего последнего счета. Поэтому, хотя изменение цен на фрукты может привести к изменению стоимости корзины, пользователь не может предпринять никаких действий, которые повлияли бы на него более чем на 10%. Поэтому, если значение равно 100, они могут вносить изменения, которые создают его до 110, но не более.

Ответ 1

Я ненавижу вас подвести, но я действительно не думаю, что нейронная сеть вообще поможет в решении этой проблемы, а ИМО - лучший ответ на ваш вопрос - совет "не тратьте время на попытки нейронных сетей".

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

Нейронные сети имеют ограничения, и одна заключается в том, что они не справляются с очень логичными проблемами. Это объясняется тем, что ответы обычно не являются "гладкими". Если вы возьмете изображение и немного измените несколько пикселей, содержимое изображения останется прежним. Если вы возьмете аудиоклип и вставьте несколько миллисекунд шума, нейронная сеть, вероятно, все еще сможет понять, что сказал. Но в вашей проблеме измените однодневное "общее значение корзины" всего на 1 единицу, и ваш ответ (-ы) резко изменится.

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

Интересно, что часть вашей проблемы может быть сведена к целочисленной линейной программе (ILP). Рассмотрим один день, когда вам дается корзина B и каждая стоимость c_i, для i=1 i=n (если n - общее количество различных фруктов). Скажем, цены большие, поэтому не очевидно, что вы можете "заполнить" корзину фруктами с удельными расходами. В этой ситуации может быть сложно найти даже одно решение. Сформулированный как ILP, это эквивалентно обнаружению целочисленных значений x_i таких, что:

sum_i (x_i*c_i) = x_1*c_1 + x_2*c_2 + ... + x_n*c_n = B

и x_i >= 0 для всех 1 <= я <= n (не может иметь отрицательных плодов), а sum_i x_i <= 100 (может иметь не более 100 плодов).

Хорошей новостью является то, что существуют приличные решения ILP - вы можете просто передать вышеуказанные формулы, и решатель сделает все возможное, чтобы найти одно решение. Вы даже можете добавить "целевую функцию", которую решатель будет максимизировать или минимизировать - минимизация sum_i x_i приводит к минимизации общего количества фруктов в корзине. Плохая новость заключается в том, что ILP NP-complete, поэтому почти нет надежды найти эффективное решение для большого количества фруктов (что равно числу переменных x_i).

Я считаю, что наилучшим подходом к лучшему является попытка подхода к ILP, но также введение некоторых ограничений в сценарий. Например, что, если бы все фрукты имели другое большое количество стоимости? Это имеет приятное свойство: если вы найдете одно решение, вы можете перечислить множество других связанных решений. Если яблоко стоит m а апельсин стоит n, где m и n - относительно простые, то вы можете "торговать" n*x яблоками для m*x апельсинов без изменения общей суммы корзины для любого целого x>0 (до тех пор, пока у вас есть достаточно яблок и апельсинов для начала). Если вы выберете все фрукты, чтобы иметь разные затраты на простые числа, тогда все затраты будут попарно взаимно простыми. Я думаю, что такой подход приведет к относительно небольшим решениям на данный день.

Вы также можете рассмотреть другие ограничения, такие как "в корзине не может быть более 5 плодов одного вида" (добавьте ограничение x_i <= 5) или "в нем может быть не более 5 различных видов фруктов в корзина "(но это сложнее кодировать как ограничение ILP). Добавление таких ограничений облегчит решение решения ILP для решения проблемы.

Конечно, вышеупомянутое обсуждение сосредоточено на одном дне, и у вас есть данные за несколько дней. Если самая трудная часть проблемы - найти какое-либо решение для любого дня вообще (что происходит, если ваши цены большие), то использование решения ILP даст вам большой импульс. Если решения легко найти (что происходит, если у вас очень дешевый плод, который может "заполнить" вашу корзину), а самая сложная часть проблемы - найти решения, которые являются "согласованными" в течение нескольких дней, тогда Подход ИЛП может быть не лучшим образом, и в целом эта проблема кажется гораздо более трудной.

Изменение: и, как упоминалось в комментариях, для некоторых интерпретаций ограничения "10% изменения" вы даже можете кодировать всю многодневную проблему как ILP.

Ответ 2

Мне кажется, что ваш подход разумный, но зависит ли он от размера чисел в реальной игре. Здесь полная реализация, намного более эффективная, чем ваша (но у нее много возможностей для улучшения). Он хранит список возможностей за предыдущий день, а затем фильтрует текущие дневные суммы до тех, которые находятся в пределах 5% от некоторой возможности с предыдущего дня, и распечатывает их в день.

def possibilities(value, prices):
    for i in range(0, value+1, prices[0]):
        for j in range(0, value+1-i, prices[1]):
            k = value - i - j
            if k % prices[2] == 0:
                yield i//prices[0], j//prices[1], k//prices[2]

def merge_totals(last, this, r):
    ok = []
    for t in this:
        for l in last:
            f = int(sum(l) * r)
            if all(l[i] -f <= t[i] <= l[i] + f for i in range(len(l))):
                ok.append(t)
                break
    return ok

days = [
    (26, (1, 2, 3)),
    (51, (2, 3, 4)),
    (61, (2, 4, 5)),
]

ps = None
for i, d in enumerate(days):
    new_ps = list(possibilities(*d))
    if ps is None:
        ps = new_ps
    ps = merge_totals(ps, new_ps, 0.05)

    print('Day %d' % (i+1))
    for p in ps:
        print('apples: %s, pears: %s, oranges: %s' % p)
    print

Ответ 3

Проблемная рамка

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

Решение этого класса задач оптимизации NP-hard. Решение проблемы ответа "Можем ли мы найти комбинацию фруктовых изделий со значением X?" является NP-полным. Поскольку вы хотите учитывать худший сценарий, когда у вас есть тысячи фруктовых предметов, лучше всего использовать метаэвристику, например, эволюционные вычисления.

Предложенное решение

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

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

пример

Позвольте использовать распределенные эволюционные алгоритмы в структуре Python (DEAP) и модифицировать их пример проблемы Knapsack для нашей проблемы. В приведенном ниже кодеке мы применяем сильное наказание за корзины с элементами 100+. Это серьезно сократит их пригодность и выведет их из пула населения в течение одного или двух поколений. Существуют и другие способы обработки ограничений, которые также действительны.

#    This file is part of DEAP.
#
#    DEAP is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Lesser General Public License as
#    published by the Free Software Foundation, either version 3 of
#    the License, or (at your option) any later version.
#
#    DEAP is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#    GNU Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with DEAP. If not, see <http://www.gnu.org/licenses/>.

import random

import numpy as np

from deap import algorithms
from deap import base
from deap import creator
from deap import tools

IND_INIT_SIZE = 5 # Calls to 'individual' function
MAX_ITEM = 100   # Max 100 fruit items in basket
NBR_ITEMS = 50   # Start with 50 items in basket
FRUIT_TYPES = 10 # Number of fruit types (apples, bananas, ...)

# Generate a dictionary of random fruit prices.
fruit_price = {i: random.randint(1, 5)  for i in range(FRUIT_TYPES)}

# Create fruit items dictionary.  The key is item ID, and the 
# value is a (weight, price) tuple.  Weight is always 1 here.
items = {}
# Create random items and store them in the items' dictionary.
for i in range(NBR_ITEMS):
    items[i] = (1, fruit_price[i])

# Create fitness function and an individual (solution candidate)
# A solution candidate in our case is a collection of fruit items.
creator.create("Fitness", base.Fitness, weights=(-1.0, 1.0))
creator.create("Individual", set, fitness=creator.Fitness)

toolbox = base.Toolbox()

# Randomly initialize the population (a set of candidate solutions)
toolbox.register("attr_item", random.randrange, NBR_ITEMS)
toolbox.register("individual", tools.initRepeat, creator.Individual, 
toolbox.attr_item, IND_INIT_SIZE)


def evalBasket(individual):
    """Evaluate the value of the basket and
    apply constraints penalty.
    """
    value = 0 # Total value of the basket
    for item in individual:
        value += items[item][1]

    # Heavily penalize baskets with 100+ items
    if len(individual) > MAX_ITEM:
        return 10000, 0
    return len(individual), value  # (items in basket, value of basket)


def cxSet(ind1, ind2):
    """Apply a crossover operation on input sets.
    The first child is the intersection of the two sets,
    the second child is the difference of the two sets.
    This is one way to evolve new candidate solutions from
    existing ones.  Think of it as parents mixing their genes
    to produce a child.
    """
    temp = set(ind1)                # Used in order to keep type
    ind1 &= ind2                    # Intersection (inplace)
    ind2 ^= temp                    # Symmetric Difference (inplace)
    return ind1, ind2


def mutSet(individual):
    """Mutation that pops or add an element.
    In nature, gene mutations help offspring express new traits
    not found in their ancestors.  That could be beneficial or 
    harmful.  Survival of the fittest at play here.
    """
    if random.random() < 0.5:  # 50% chance of mutation
        if len(individual) > 0:
            individual.remove(random.choice(sorted(tuple(individual))))
    else:
        individual.add(random.randrange(NBR_ITEMS))
    return individual,

# Register evaluation, mating, mutation and selection functions
# so the framework can use them to run the simulation.
toolbox.register("evaluate", evalKnapsack)
toolbox.register("mate", cxSet)
toolbox.register("mutate", mutSet)
toolbox.register("select", tools.selNSGA2)


def main():
    random.seed(64)
    NGEN = 50
    MU = 50
    LAMBDA = 100
    CXPB = 0.7
    MUTPB = 0.2

    pop = toolbox.population(n=MU)  # Initial population size
    hof = tools.ParetoFront()    # Using Pareto front to rank fitness

    # Keep track of population fitness stats which should 
    # improve over generations (iterations).
    stats = tools.Statistics(lambda ind: ind.fitness.values)
    stats.register("avg", numpy.mean, axis=0)
    stats.register("std", numpy.std, axis=0)
    stats.register("min", numpy.min, axis=0)
    stats.register("max", numpy.max, axis=0)

    algorithms.eaMuPlusLambda(pop, toolbox, MU,LAMBDA,\
                              CXPB, MUTPB, NGEN, stats,\
                              halloffame=hof)
    return pop, stats, hof


if __name__ == "__main__":
    main()   

Ответ 4

Целочисленный подход к линейному программированию

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

Предоставленные решения улучшаются по сравнению с приведенными выше на диаграмме и минимальны в смысле общего изменения корзины.

Комментарии -

  • Я не знаю, как вы вычислили столбец "% change" в вашей таблице. Изменение с 1-го по 2-й день из 20 яблок на 21 яблок составляет 4,76%?

  • Во все дни ваши полные запасы фруктов равны 100. Существует ограничение, что сумма владений <= 100. Никакого нарушения, я просто хочу подтвердить.

Мы можем установить это как целочисленную линейную программу, используя подпрограмму оптимизации целого числа из ortools. Я долгое время не использовал ИЛП-решатель, и этот, по-моему, нелепый флагов (флаг solver.OPTIMAL никогда не бывает правдой, даже для игрушечных задач. Кроме того, ortools LP не может найти оптимального решение в случаях, когда scipy.linprog работает без заминки)

h1,d = holdings in apples (number of apples) at end of day d
h2,d = holdings in pears at end of day d
h3,d = holdings in oranges at end of day d

Я приведу здесь два предложения, которые минимизируют норму абсолютной ошибки l1, а другую - норму l0.

Решение l1 находит минимум abs(h1,(d+1) - h1,d)/h1 +... + abs(h3,(d+1) - h3,d)/h3), надеясь, что ограничение что каждое относительное изменение холдингов составляет менее 10%, если сумма относительного изменения холдингов минимизирована.

Единственное, что препятствует тому, чтобы это была линейная программа (кроме целочисленного требования), - это нелинейная целевая функция. Нет проблем, мы можем ввести слабые переменные и сделать все линейными. Для формулировки l1 вводится 6 дополнительных слабых переменных, 2 на один фрукт и 6 дополнительных ограничений неравенства. Для формулировки l0 введена 1 слабая переменная и 6 дополнительных ограничений неравенства.

Это двухэтапный процесс, например, замена | apples_new - apples_old |/| apples_old | с переменной | e | и добавлением ограничений неравенства, чтобы гарантировать, что e измеряет то, что нам хотелось бы. Затем мы заменим | e | с (e+ - e-), каждый из e+, e-> 0. Можно показать, что один из e+, e- будет равен 0 и что (e+ + e-) является абсолютным значением e. Таким образом, пара (e+, e-) может представлять собой положительное или отрицательное число. Стандартный материал, но это добавляет кучу переменных и ограничений. Я могу объяснить это немного подробнее, если это необходимо.

import numpy as np
from ortools.linear_solver import pywraplp


def fruit_basket_l1_ortools():

    UPPER_BOUND = 1000

    prices = [[2,3,5],
              [1,2,4],
              [1,2,3]]

    holdings = [20,43,37]

    values = [348, 251, 213]

    for day in range(len(values)):

        solver = pywraplp.Solver('ILPSolver',
                                  pywraplp.Solver.BOP_INTEGER_PROGRAMMING)
        # solver = pywraplp.Solver('ILPSolver',
        #                          pywraplp.Solver.CLP_LINEAR_PROGRAMMING)


        c = ([1,1] * 3) + [0,0,0]

        price = prices[day]
        value = values[day]

        A_eq = [[ 0,  0, 0,  0, 0,  0, price[0], price[1], price[2]]]

        b_eq = [value]

        A_ub = [[-1*holdings[0], 1*holdings[0],              0,             0,              0,              0,  1.0,    0,    0],
                [-1*holdings[0], 1*holdings[0],              0,             0,              0,              0, -1.0,    0,    0],
                [             0,             0, -1*holdings[1], 1*holdings[1],              0,              0,    0,  1.0,    0],
                [             0,             0, -1*holdings[1], 1*holdings[1],              0,              0,    0, -1.0,    0],
                [             0,             0,              0,              0, -1*holdings[2], 1*holdings[2],    0,    0,  1.0],
                [             0,             0,              0,              0, -1*holdings[2], 1*holdings[2],    0,    0, -1.0]]

        b_ub = [1*holdings[0], -1*holdings[0], 1*holdings[1], -1*holdings[1], 1*holdings[2], -1*holdings[2]]

        num_vars             = len(c)
        num_ineq_constraints = len(A_ub)
        num_eq_constraints   = len(A_eq)                

        data = [[]] * num_vars

        data[0]  = solver.IntVar(           0, UPPER_BOUND, 'e1_p')
        data[1]  = solver.IntVar(           0, UPPER_BOUND, 'e1_n')
        data[2]  = solver.IntVar(           0, UPPER_BOUND, 'e2_p')
        data[3]  = solver.IntVar(           0, UPPER_BOUND, 'e2_n')
        data[4]  = solver.IntVar(           0, UPPER_BOUND, 'e3_p')
        data[5]  = solver.IntVar(           0, UPPER_BOUND, 'e3_n')
        data[6]  = solver.IntVar(           0, UPPER_BOUND, 'x1')
        data[7]  = solver.IntVar(           0, UPPER_BOUND, 'x2')
        data[8]  = solver.IntVar(           0, UPPER_BOUND, 'x3')

        constraints = [0] * (len(A_ub) + len(b_eq))

        # Inequality constraints
        for i in range(0,num_ineq_constraints):
            constraints[i] = solver.Constraint(-solver.infinity(), b_ub[i])
            for j in range(0,num_vars):
                constraints[i].SetCoefficient(data[j], A_ub[i][j])

        # Equality constraints
        for i in range(num_ineq_constraints, num_ineq_constraints+num_eq_constraints):
            constraints[i] = solver.Constraint(b_eq[i-num_ineq_constraints], b_eq[i-num_ineq_constraints])
            for j in range(0,num_vars):
                constraints[i].SetCoefficient(data[j], A_eq[i-num_ineq_constraints][j])

        # Objective function
        objective = solver.Objective()
        for i in range(0,num_vars):
            objective.SetCoefficient(data[i], c[i])

        # Set up as minization problem
        objective.SetMinimization()

        # Solve it
        result_status = solver.Solve()

        solution_set = [data[i].solution_value() for i in range(len(data))]

        print('DAY: {}'.format(day+1))
        print('======')
        print('SOLUTION FEASIBLE: {}'.format(solver.FEASIBLE))
        print('SOLUTION OPTIMAL:  {}'.format(solver.OPTIMAL))
        print('VALUE OF BASKET:   {}'.format(np.dot(A_eq[0], solution_set)))
        print('SOLUTION   (apples,pears,oranges): {!r}'.format(solution_set[-3:]))
        print('PCT CHANGE (apples,pears,oranges): {!r}\n\n'.format([round(100*(x-y)/y,2) for x,y in zip(solution_set[-3:], holdings)]))

        # Update holdings for the next day
        holdings = solution_set[-3:]

Один прогон дает:

DAY: 1
======
SOLUTION FEASIBLE: 1
SOLUTION OPTIMAL:  0
VALUE OF BASKET:   348.0
SOLUTION   (apples,pears,oranges): [20.0, 41.0, 37.0]
PCT CHANGE (apples,pears,oranges): [0.0, -4.65, 0.0]


DAY: 2
======
SOLUTION FEASIBLE: 1
SOLUTION OPTIMAL:  0
VALUE OF BASKET:   251.0
SOLUTION   (apples,pears,oranges): [21.0, 41.0, 37.0]
PCT CHANGE (apples,pears,oranges): [5.0, 0.0, 0.0]


DAY: 3
======
SOLUTION FEASIBLE: 1
SOLUTION OPTIMAL:  0
VALUE OF BASKET:   213.0
SOLUTION   (apples,pears,oranges): [20.0, 41.0, 37.0]
PCT CHANGE (apples,pears,oranges): [-4.76, 0.0, 0.0]

Формула l0 также представлена:

def fruit_basket_l0_ortools():

    UPPER_BOUND = 1000

    prices = [[2,3,5],
              [1,2,4],
              [1,2,3]]

    holdings = [20,43,37]

    values = [348, 251, 213]

    for day in range(len(values)):

        solver = pywraplp.Solver('ILPSolver',
                                  pywraplp.Solver.BOP_INTEGER_PROGRAMMING)
        # solver = pywraplp.Solver('ILPSolver',
        #                          pywraplp.Solver.CLP_LINEAR_PROGRAMMING)

        c = [1, 0, 0, 0]

        price = prices[day]
        value = values[day]

        A_eq = [[0, price[0], price[1], price[2]]]

        b_eq = [value]

        A_ub = [[-1*holdings[0],  1.0,    0,    0],
                [-1*holdings[0], -1.0,    0,    0],
                [-1*holdings[1],    0,  1.0,    0],
                [-1*holdings[1],    0, -1.0,    0],
                [-1*holdings[2],    0,    0,  1.0],
                [-1*holdings[2],    0,    0, -1.0]]


        b_ub = [holdings[0], -1*holdings[0], holdings[1], -1*holdings[1], holdings[2], -1*holdings[2]]


        num_vars             = len(c)
        num_ineq_constraints = len(A_ub)
        num_eq_constraints   = len(A_eq)                

        data = [[]] * num_vars

        data[0]  = solver.IntVar(-UPPER_BOUND, UPPER_BOUND, 'e' )
        data[1]  = solver.IntVar(           0, UPPER_BOUND, 'x1')
        data[2]  = solver.IntVar(           0, UPPER_BOUND, 'x2')
        data[3]  = solver.IntVar(           0, UPPER_BOUND, 'x3')

        constraints = [0] * (len(A_ub) + len(b_eq))

        # Inequality constraints
        for i in range(0,num_ineq_constraints):
            constraints[i] = solver.Constraint(-solver.infinity(), b_ub[i])
            for j in range(0,num_vars):
                constraints[i].SetCoefficient(data[j], A_ub[i][j])

        # Equality constraints
        for i in range(num_ineq_constraints, num_ineq_constraints+num_eq_constraints):
            constraints[i] = solver.Constraint(int(b_eq[i-num_ineq_constraints]), b_eq[i-num_ineq_constraints])
            for j in range(0,num_vars):
                constraints[i].SetCoefficient(data[j], A_eq[i-num_ineq_constraints][j])

        # Objective function
        objective = solver.Objective()
        for i in range(0,num_vars):
            objective.SetCoefficient(data[i], c[i])

        # Set up as minization problem
        objective.SetMinimization()

        # Solve it
        result_status = solver.Solve()

        solution_set = [data[i].solution_value() for i in range(len(data))]

        print('DAY: {}'.format(day+1))
        print('======')
        print('SOLUTION FEASIBLE: {}'.format(solver.FEASIBLE))
        print('SOLUTION OPTIMAL:  {}'.format(solver.OPTIMAL))
        print('VALUE OF BASKET:   {}'.format(np.dot(A_eq[0], solution_set)))
        print('SOLUTION   (apples,pears,oranges): {!r}'.format(solution_set[-3:]))
        print('PCT CHANGE (apples,pears,oranges): {!r}\n\n'.format([round(100*(x-y)/y,2) for x,y in zip(solution_set[-3:], holdings)]))

        # Update holdings for the next day
        holdings = solution_set[-3:]

Один пробег этого дает

DAY: 1
======
SOLUTION FEASIBLE: 1
SOLUTION OPTIMAL:  0
VALUE OF BASKET:   348.0
SOLUTION   (apples,pears,oranges): [33.0, 79.0, 9.0]
PCT CHANGE (apples,pears,oranges): [65.0, 83.72, -75.68]


DAY: 2
======
SOLUTION FEASIBLE: 1
SOLUTION OPTIMAL:  0
VALUE OF BASKET:   251.0
SOLUTION   (apples,pears,oranges): [49.0, 83.0, 9.0]
PCT CHANGE (apples,pears,oranges): [48.48, 5.06, 0.0]


DAY: 3
======
SOLUTION FEASIBLE: 1
SOLUTION OPTIMAL:  0
VALUE OF BASKET:   213.0
SOLUTION   (apples,pears,oranges): [51.0, 63.0, 12.0]
PCT CHANGE (apples,pears,oranges): [4.08, -24.1, 33.33]

Резюме

Формула l1 дает более разумные результаты, более низкий оборот, намного ниже. Тем не менее проверка оптимальности не выполняется на всех прогонах. Я включил линейный решатель тоже, и это не дает возможности проверить возможность, так или иначе, я не знаю, почему. Люди Google предоставляют небольшую документацию для библиотеки ortools lib, и большая ее часть предназначена для C++ lib. Но формулировка l1 может быть решением вашей проблемы, которая может масштабироваться. ILP вообще NP-complete, и ваша проблема скорее всего.

Также - существует ли решение на второй день? Как вы определяете изменение%, чтобы оно было в вашей диаграмме выше? Если бы я знал, что могу переделать неравенство выше, и у нас будет общее решение.

Ответ 5

Не ответ, но попытка сделать одну информацию о том, что означает "% изменения", может означать (сумма изменения количества отсчетов каждого элемента, вычисленного назад) более доступной для неверующих в кучи пикселей:

       | Day 1        ! Day 2   change      ! Day 3   change      ! Day 4   change
       |$/1|  # |  $  !$/1|  # |   %  |  $  !$/1|  # |   %  |  $  !$/1|  # |   %  |  $
Apples | 1 | 20 |  20 ! 2 | 21 | 4.76 |  42 ! 1 | 21 |  0   |  21 ! 1 | 22 | 4.55 |  22
Pears  | 2 | 43 |  86 ! 3 | 42 | 2.38 | 126 ! 2 | 43 | 2.33 |  86 ! 2 | 43 |  0   |  86
Oranges| 3 | 37 | 111 ! 5 | 36 | 2.78 | 180 ! 4 | 36 |  0   | 144 ! 3 | 35 | 2.86 | 105
Total  |    100 | 217 !    100 | 9.92 | 348 !    100 | 2.33 | 251 !    100 | 7.40 | 213

Ответ 6

У вас есть логическая проблема с целыми числами, а не с проблемой представления. Нейронные сети имеют отношение к проблеме со сложным представлением (например, изображение с пикселями, объекты различной формы и цвета, иногда скрытые и т.д.), Поскольку они создают свой собственный набор функций (дескрипторов) и mipmaps; они также хорошо сочетаются с проблемами, связанными с действиями, а не целыми; и последнее, как и сегодня, они не имеют дело с рассуждениями и логикой, или, в конечном счете, с простой логикой, как небольшая последовательность if/else или switch но мы на самом деле не имеем контроля над этим.

То, что я вижу, ближе к криптографической проблеме с ограничениями (10% -ное изменение, максимум 100 статей).

Решение для всех наборов фруктов

Существует способ быстро достичь всех решений. Начнем с факторизации в простые числа, тогда мы найдем несколько решений с помощью грубой силы. Оттуда мы можем изменить набор плодов с равным итогом. Например, мы обмениваем 1 апельсин на 1 яблоко и 1 грушу с ценами = (1,2,3). Таким образом, мы можем перемещаться по решениям без необходимости перебирать грубую силу.

Алгоритм (ы): вы факторизуете в простых числах итог, затем делите их на две или более группы; пусть возьмем 2 группы: пусть A - один общий множитель, а B - другой (ы). Затем вы можете добавить свои плоды, чтобы достичь общей суммы B.

Примеры:

День 1: Apple = 1, груши = 2, апельсины = 3, значение корзины = 217

День 2: Apple = 2, Груши = 3, Апельсины = 5, Значение корзины = 348

  • 217 разлагается на [7, 31], мы выбираем 31 как A (общий множитель), то пусть 7 = 3 * 2 + 1 (2 апельсина, 0 груши, 1 яблоко), вы получили ответ: 62 апельсина, 0 груш, 31 яблок. 62 + 31 <100: действителен.
  • 348 факторизуется в [2, 2, 3, 29], у вас есть несколько способов группировать ваши факторы и умножать свои плоды внутри этого. Множитель может быть 29, (или 2 * 29 и т.д.), Затем вы выбираете свои плоды, чтобы достичь 12. Пусть говорят 12 = 2 * 2 + 3 + 5. У вас (2 яблока, 1 груша, 1 апельсин) * 29, но это более 100 статей. Вы можете сплавить рекурсивно 1 яблоко и 1 грушу на 1 оранжевый, пока не окажется ниже 100 предметов, или вы можете пойти непосредственно с раствором с минимальным количеством предметов: (2 апельсина, 1 яблоко) * 29 = (58 апельсинов, 29 яблок), И наконец:

    - 87 <100 действует;

    - изменение (-4 апельсины, -2 яблоки), 6/93 = 6.45% <10% изменение: действует.

Код

Примечание: реализация 10% -ной вариации

Примечание. Я не реализовал процесс "обмена фруктами", который позволяет "навигацию по решениям",

Запустите с помощью python -O solution.py чтобы оптимизировать и удалить отладочные сообщения.

def prime_factors(n):
    i = 2
    factors = []
    while i * i <= n:
        if n % i:
            i += 1
        else:
            n //= i
            factors.append(i)
    if n > 1:
        factors.append(n)
    return factors

def possibilities(value, prices):
    for i in range(0, value + 1, prices[0]):
        for j in range(0, value + 1-i, prices[1]):
            k = value - i - j
            if k % prices[2] == 0:
                yield i//prices[0], j//prices[1], k//prices[2]

days = [
    (217, (1, 2, 3)),
    (348, (2, 3, 5)),
    (251, (1, 2, 4)),
    (213, (1, 2, 3)),
]

for set in days:
    total = set[0]
    (priceApple, pricePear, priceOrange) = set[1]

    factors = prime_factors(total)
    if __debug__:
        print(str(total) + " -> " + str(factors))

    # remove small article to help factorize (odd helper)
    evenHelper = False
    if len(factors) == 1 :
        evenHelper = True
        t1 = total - priceApple
        factors = prime_factors(t1)
        if __debug__:
            print(str(total) + " --> " + str(factors))

    # merge factors on left
    while factors[0] < priceOrange :
        factors = [factors[0] * factors[1]] + factors[2:]
        if __debug__:
            print("merging: " + str(factors))

    # merge factors on right
    if len(factors) > 2:
        multiplier = 1
        for f in factors[1:]:
            multiplier *= f
        factors = [factors[0]] + [multiplier]

    (smallTotal, multiplier) = factors
    if __debug__:
        print("final factors: " + str(smallTotal) + " (small total) , " + str(multiplier) + " (multiplier)")

    # solutions satisfying #<100
    smallMax = 100 / multiplier
    solutions = [o for o in possibilities(smallTotal, set[1]) if sum(o) < smallMax ]
    for solution in solutions:
        (a,p,o) = [i * multiplier for i in solution]

        # if we used it, we need to add back the odd helper to reach the actual solution
        if evenHelper:
            a += 1

        print(str(a) + " apple(s), " + str(p) + " pear(s), " + str(o) + " orange(s)")


    # separating solutions
    print()

Я приурочил программу к 10037 сумме с ценами (5, 8, 17) и максимальными 500 статьями: это около 2 мс (на i7 6700k). Процесс "навигации по решениям" очень прост и не должен добавлять значительного времени.

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

Ответ 7

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

Мой код:

import math

prices = [1, 2, 3]
basketVal = 217
maxFruits = 100
numFruits = len(prices)


## Get the possible baskets
def getPossibleBaskets(maxFruits, numFruits, basketVal, prices):

    possBaskets = []
    for i in range(101):
        for j in range(101):
            for k in range(101):

              if i + j + k > 100:
                  pass
              else:

                  possibleBasketVal = 0
                  for m in range(numFruits):
                      possibleBasketVal += (prices[m] * [i, j, k][m])

                      if possibleBasketVal > basketVal:
                          break

                  if possibleBasketVal == basketVal:
                      possBaskets.append([i, j, k])

    return possBaskets


firstDayBaskets = getPossibleBaskets(maxFruits, numFruits, basketVal, prices)
## Compare the baskets for percentage change and filter out the values
while True:

    prices = list(map(int, input("New Prices:\t").split()))
    basketVal = int(input("New Basket Value:\t"))
    maxFruits = int(input("Max Fruits:\t"))
    numFruits = len(prices)

    secondDayBaskets = getPossibleBaskets(maxFruits, numFruits, basketVal, prices)

    possBaskets = []
    for basket in firstDayBaskets:
        for newBasket in secondDayBaskets:
            if newBasket not in possBaskets:
                percentChange = 0
                for n in range(numFruits):
                    percentChange += (abs(basket[n] - newBasket[n]) / 100)
                if percentChange <= 10:
                    possBaskets.append(newBasket)

    firstDayBaskets = possBaskets
    secondDayBaskets = []

    print(firstDayBaskets)

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