web-dev-qa-db-ja.com

私がしているよりも、力ずくで未知の変数を推測するより良い方法はありますか?機械学習?

次のルールのゲームがあります。

  • ユーザーは果物の価格を与えられ、毎ターン自分のフルーツバスケットのアイテムを売買する機会があります。

  • ユーザーは、1ターンでバスケットを10%以上変更することはできません。

  • 果物の価格は毎日変化し、フルーツバスケット内のアイテムの数量を掛けると、バスケットの合計値が果物の価格と比較して変化します。
  • プログラムには、すべての果物の現在の価格とバスケットの現在の値(果物の現在の価格*バスケット内のすべてのアイテムの数量)のみが与えられます。
  • これらの2つの入力(すべての果物の価格とバスケットの合計値)に基づいて、プログラムはバスケットにあるアイテムを推測しようとします。
  • バスケットには10​​0個を超えるアイテムを入れることはできませんが、スロットは空にできます
  • プレイヤーはいくつかのターンをプレイできます。

私の目標は、数千の新しい果物がある場合に、可能な限り計算コストをかけずに正確に推測し(読み取り:力ずく)、スケーリングすることです。

答えを見つけるのに苦労していますが、頭の中で難しいことではありません。下の表があれば。 1日目を勉強して、次のデータを得ることができました。

Apple   1
Pears   2
Oranges 3

Basket Value = 217

ナプキン計算の裏返しを行うと、バスケットの重みは次のようになります。バスケット0はリンゴ、梨は83、オレンジはバスケットの値217に相当します。

翌日、果物とバスケットの値が変わります。 (Apple = 2、Pear 3、Oranges 5)のバスケットの値は348です。仮定した重みを(0,83,17)にすると、合計値は334になりますが、正しくありません。これをスクリプトで実行すると、最も近い一致は0リンゴ、76ナシ、24オレンジであることがわかります。ただし、ファクターの%の変化が38%の変化である場合、これは348なので、不可能です。

私はこれを完全に総当たりできることを知っていますが、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

更新-これまでの情報は素晴らしいです。問題を2つの問題に分けることは意味がありますか? 1つは可能性を生み出す一方で、もう1つは可能性間の関係を見つけます(1日の変化は10%以下)。可能性を排除することによって、それはまた、可能性のある可能性を生成することだけを助けるのにも使用できませんでしたか?アプローチはまだわかりませんが、両方の問題は異なるが密接に関連していると感じています。あなたの考え?

更新2-変化率について多くの質問があります。これは、変更可能なバスケット内のアイテムの合計量です。ゲームの例を使用するために、店が言っていると想像してください-あなたは果物を売る/返す/買うことができますが、彼らはあなたの最後の請求書の10%を超えることはできません。したがって、果物の価格の変化によってバスケットの価値が変化する可能性がありますが、ユーザーは10%を超える影響を与えるような行動を取ることはできません。したがって、値が100の場合、110に到達するように変更を加えることができますが、それ以上にすることはできません。

29
Lostsoul

私はあなたを失望させたくありませんが、ニューラルネットがこの問題にまったく役立つとは思いません。IMOがあなたの質問に対する最良の答えは、「ニューラルネットを試す時間を無駄にしないこと」です。

ニューラルネットワークが適用可能かどうかを判断する簡単な経験則は、「平均的な成人の人間がこの問題を数秒でかなりうまく解決できるか」と考えることです。 「この画像の内容」、「この質問に答える」、「このオーディオクリップを転記する」などの問題の場合、答えはyesです。しかし、あなたの問題では、答えは最も明確ですいいえ

ニューラルネットワークには制限があり、1つは高度に論理的な問題をうまく処理できないことです。これは、答えが一般に「滑らか」ではないためです。画像を取り、少数のピクセルをわずかに変更しても、画像の内容は同じです。オーディオクリップを取り、数ミリ秒のノイズを挿入しても、ニューラルネットはおそらく何と言っているかを理解することができます。しかし、あなたの問題では、1日の「合計バスケット値」を1ユニットだけ変更すると、回答が大幅に変わります。

あなたの問題を解決する唯一の方法は、「古典的な」アルゴリズム的アプローチを使うことです。現在述べられているように、ブルートフォースよりも優れたアルゴリズムはない可能性があり、多くを除外することができない場合があります。たとえば、すべての果物の価格が同じというプロパティが毎日ある場合はどうでしょうか。果物の総数が一定である限り、各果物の数は変動する可能性があるため、可能性の数は果物の数において指数関数的です。 「可能性のリストを作成する」ことが目標である場合、このリストは場合によっては指数関数的に大きくなる可能性があるため、指数時間よりも優れたアルゴリズムはありません。

問題の一部を 整数線形プログラム (ILP)に削減できるのは興味深いことです。 c_iからi=1までのバスケットの合計Bと各果物のコストi=nが与えられた1日を考えます(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 <= i <= n(否定的な果物を持つことはできません)、およびsum_i x_i <= 100(最大100個の果物を持つことができます)。

良いニュースは、まともなILPソルバーが存在することです。上記の数式を渡すだけで、ソルバーは単一のソリューションを見つけるために最善を尽くします。ソルバーが最大化または最小化する「目的関数」を追加することもできます。sum_i x_iを最小化すると、バスケット内の果物の総数を最小化する効果があります。悪いニュースは、ILPはNP完全であるため、多数の果物(変数の数x_iに等しい)の効率的な解決策を見つけることはほとんど不可能です。

今後の最善のアプローチはILPアプローチを試すことですが、シナリオにいくつかの制約を導入することもできます。たとえば、すべての果物の素数コストが異なる場合はどうなりますか?これには、1つの解決策を見つけた場合、他の関連する解決策の束を列挙できるという、Niceプロパティがあります。 Appleがmで、オレンジがnである場合、mnは比較的素数の場合、 n*xのリンゴをm*xのリンゴに「取引」できます。バスケットの合計を変更せずに、任意の整数x>0を使用できます(最初に十分なリンゴとオレンジがある場合)。すべてを選択した場合異なる素数コストを持つ果物を作ると、すべてのコストはペアワイズで比較的素数になります。このアプローチでは、特定の日のソリューションが比較的少なくなると思います。

また、「バスケットには1種類の果物が5個を超えることはできない」(制約x_i <= 5を追加する)、「最大5つの異なる種類の果物が存在する可能性がある」など、他の制約も考慮する必要があります。バスケットで」(ただし、これはILP制約としてエンコードするのが困難です)。これらの種類の制約を追加すると、ILPソルバーがソリューションを見つけやすくなります。

もちろん、上記の議論は1日に焦点を当てており、数日分のデータがあります。問題の最も困難な部分が一日の解決策を見つけることである場合(価格が高い場合に発生します)、ILPソルバーを使用すると大幅に向上します。解決策が見つけやすく(バスケットを「いっぱいにする」ことができる非常に低コストの果物がある場合に起こります)、問題の最も難しい部分は、複数の日にわたって「一貫した」解決策を見つけることです。 ILPアプローチは最適ではない可能性があり、一般に、この問題は、推論するのがはるかに難しいようです。

編集:そして、コメントで述べたように、「10%変更」制約の一部の解釈については、複数日にわたる問題全体をILPとしてエンコードすることもできます。

27
pkpnd

あなたのアプローチは合理的であるように私には思えますが、それが実際のゲームの数字のサイズに依存するかどうかは異なります。これは、あなたよりはるかに効率的な完全な実装です(ただし、改善の余地は十分あります)。これは、前日の可能性のリストを保持し、当日の金額を前日からの可能性の5%以内のものにフィルターし、それらを1日ごとに出力します。

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
6
Paul Hankin

答えではありませんが、「%変化」の意味に関する1つの情報(各項目のカウントの変化の合計後方に計算)を、ピクセルの非信者がよりアクセスしやすくする試みヒープ:

 | 1日目        ! 2日目変更! 3日目変更! 4日目の変更
 | $/1 | #| $!$/1 | #| %| $!$/1 | #| %| $!$/1 | #| %| $ 
りんご| 1 | 20 | 20! 2 | 21 | 4.76 | 42! 1 | 21 | 0 | 21! 1 | 22 | 4.55 | 22 
梨| 2 | 43 | 86! 3 | 42 | 2.38 | 126! 2 | 43 | 2.33 | 86! 2 | 43 | 0 | 86 
オレンジ| 3 | 37 | 111! 5 | 36 | 2.78 | 180! 4 | 36 | 0 | 144! 3 | 35 | 2.86 | 105 
合計| 100 | 217! 100 | 9.92 | 348! 100 | 2.33 | 251! 100 | 7.40 | 213 
5
greybeard

問題のフレーミング

この問題は 組み合わせ最適化問題 として説明できます。有限のオブジェクトセット(果物アイテムの可能なすべての組み合わせ)から最適なオブジェクト(果物アイテムの組み合わせ)を見つけようとしています。適切な類推と変換を行うことで、このフルーツバスケットの問題をよく知られており、広く研究されています(1897年以降) ナップサック問題

このクラスの最適化問題の解決は NP-hard です。答える決定問題 "Xの値を持つ果物アイテムの組み合わせを見つけることができますか?"NP-complete です。 。フルーツアイテムが何千もある最悪のシナリオを考慮したいので、最善の策は メタヒューリスティック を使用することです 進化的計算 のように。

提案されたソリューション

進化的計算 は、生物学にヒントを得たメタヒューリスティクスのファミリーです。それらは、フィットネス関数に基づいて最も適合する候補ソリューションを修正および混合(進化)し、多くの反復にわたって最も適合しないソリューションを破棄することによって機能します。ソリューションの適合性が高いほど、同様のソリューションを再現し、次世代(イテレーション)まで生き残る可能性が高くなります。最終的に、ローカルまたはグローバルな最適解が見つかります。

これらの方法は、検索スペースが大きすぎて従来の閉形式の数学的ソリューションでカバーできない場合に必要な妥協策を提供します。これらのアルゴリズムの確率論的な性質により、アルゴリズムの実行が異なるとローカル最適化が異なる可能性があり、グローバル最適が見つかる保証はありません。複数の有効なソリューションがあるので、オッズは私たちのケースでは良好です。

Python(DEAP) フレームワークの分散型進化アルゴリズムを使用して、問題に ナップザック問題の例 を追加してみましょう。以下のコードでは、 100以上のアイテムがあるバスケットに強いペナルティを適用します。これにより、それらの適合性が大幅に低下し、1世代または2世代で人口プールから削除されます。 制約を処理する他の方法 も有効です。

#    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()   
5
Wesam

整数線形計画法

これは、マルチステップ整数プログラムとして自然に設定されます。前のステップからの{apples、pears、oranges}の保有量は、制約が必要な保有量の相対的変化の計算を考慮に入れています。ここでは最適という概念はありませんが、「ターンオーバー」制約を目的に変えて、何が起こるかを確認できます。

提供されたソリューションは、上記のチャートのソリューションを改善し、バスケットの保有量の全体的な変化という意味では最小限です。

コメント-

  • テーブルの「変化率」列をどのように計算したかわかりません。 20リンゴから21リンゴへの1日目から2日目への変更は4.76%の変更ですか?

  • すべての日において、果物の総保有量は正確に100です。保有量の合計が100以下であるという制約があります。違反はありません。確認したいだけです。

ortoolsの整数最適化ルーチンを使用して、これを整数線形プログラムとして設定できます。私は長い間ILPソルバーを使用していませんでしたが、これは私が考えるフレークのようなものです(solver.OPTIMALフラグはnevertrue itおもちゃの問題でもそうです。さらに、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
_

ここでは2つの提案をします。1つは絶対エラーの_l1_ノルムを最小化するもので、もう1つは_l0_ normです。

_l1_ソリューションはabs(h1,(d+1) - h1,d)/h1 + ... + abs(h3,(d+1) - h3,d)/h3)の最小値を検出し、持ち株の相対的な変化の合計が最小化されている場合、持ち株の各相対的な変化の制約が10%未満であることを期待しています。

これが(整数要件を除いて)線形プログラムにならないようにする唯一のことは、非線形目的関数です。問題ありません。スラック変数を導入してすべてを線形にすることができます。 _l1_の定式化では、6つの追加のスラック変数が導入され、フルーツごとに2つ、および6つの追加の不等式制約があります。 _l0_の定式化では、1つのスラック変数と6つの追加の不等式制約が導入されています。

これは2ステップのプロセスです。たとえば、| 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:]
_

1回の実行で次のようになります。

_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:]
_

これを1回実行すると、

_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完全であるため、問題が発生する可能性が最も高くなります。

また、解決策は2日目に存在しますか?上のグラフにあるように、%変化をどのように定義しますか?私が知っていれば、上記の不等式を再計算でき、一般的な解決策が得られます。

5

整数に関する論理的な問題であり、表現の問題ではないを取得しました。ニューラルネットワークは、独自の機能(記述子)とミップマップのセットを構築するため、複雑な表現(たとえば、ピクセルを含む画像、形や色が異なるオブジェクト、場合によっては非表示など)の問題に関連しています。また、整数ではなく実数を処理する問題に適しています。そして最後に、今日のように、推論とロジック、または最終的にはif/elseswitchの小さな連続などの単純なロジックは処理しませんが、実際には制御がありません。その上。

私が目にするのは、制約のある暗号のような問題に近い(10%の変更、最大100の記事)

allセットの果物の解決策

すべてのソリューションにすばやく到達する方法があります。最初に合計を素数に分解することから始めて、ブルートフォースによる解決策をいくつか見つけます。そこから、果物のセットを等しい合計で変更できます。たとえば、1つのオレンジを1 Appleおよび1つの梨を価格=(1,2,3)に交換します。このようにして、navigate力ずくで解決する必要はありません。

Algorithm(s):合計を素数で因数分解し、それらを2つ以上のグループに分割します。 2つのグループを考えてみましょう:Aを1つの共通の乗数とし、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 Appleと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()

私は(5、8、17)の価格と最大500の記事を含む合計10037でプログラムを計時しました:それは約2ms(i7 6700kで)です。 「ソリューションナビゲーション」のプロセスは非常にシンプルで、それほど時間をかけるべきではありません。

因数分解+ナビゲーション+検証プロセスを実行せずに、毎日実行するヒューリスティックがあるかもしれません。私はそれについて考えます。

4
Soleil

少し遅いのはわかっていますが、これは興味深い問題で、2セント追加するのもよいと思いました。

私のコード:

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)

これはブルートフォースソリューションと呼べるかもしれませんが、確実に機能します。毎日、バスケットの可能な構成を印刷します。

1
Adi219