63

更新(2020年7月):質問は9年前ですが、それでも私は深く興味を持っています。それ以来、機械学習(RNN、CNN、GANSなど)、新しいアプローチ、新しいアプローチを可能にする安価なGPUが登場しました。 。この質問をもう一度見て、新しいアプローチがあるかどうかを確認するのは楽しいだろうと思いました。

私はプログラミング(Pythonとアルゴリズム)を学んでいて、面白いと思うプロジェクトに取り組んでいました。いくつかの基本的なPythonスクリプトを作成しましたが、作成しようとしているゲームのソリューションにアプローチする方法がわかりません。

ゲームの仕組みは次のとおりです。

ユーザーには価値のあるアイテムが与えられます。例えば、

Apple = 1
Pears = 2
Oranges  = 3

その後、好きな組み合わせ(つまり、リンゴ100個、梨20個、オレンジ1個)を選択する機会が与えられます。コンピューターが取得する唯一の出力は合計値です(この例では、現在$ 143です)。コンピュータは彼らが何を持っているかを推測しようとします。明らかに、最初のターンを正しく取得することはできません。

         Value    quantity(day1)    value(day1)
Apple      1        100                100
Pears      2         20                 40
Orange     3          1                  3
Total               121                143

次のターン、ユーザーは自分の数を変更できますが、合計数量の5%以下(または他のパーセントを選択できます。たとえば、5%を使用します)。果物の価格は(ランダムに)変化する可能性があるため、それに基づいて合計値も変化する可能性があります(簡単にするために、この例では果物の価格を変更していません)。上記の例を使用すると、ゲームの2日目に、ユーザーは3日目に$152と$164の値を返します。例を次に示します。

Quantity (day2)   %change (day2)    Value (day2)   Quantity (day3)   %change (day3)   Value(day3)
 104                                 104            106                                106
  21                                  42             23                                 46
   2                                   6              4                                 12
 127               4.96%             152            133               4.72%            164

*(テーブルが正しく表示されることを願っています。手動でスペースを空ける必要があったので、画面上でそれを行っているだけではないことを願っています。うまくいかない場合は、お知らせください。スクリーンショットをアップロードしてみます。)

私は、時間の経過とともに数量が何であるかを把握できるかどうかを確認しようとしています(ユーザーが数字を入力し続ける忍耐力があると仮定します)。現在のところ、私の唯一の制限は、合計値が5%を超えることはできないため、現在5%以内の精度にはできないため、ユーザーが永久に入力することです。

私がこれまでにしたこと

これがこれまでの私の解決策です(それほど多くはありません)。基本的に、私はすべての値を取り、それらのすべての可能な組み合わせを理解します(私はこの部分を完了しました)。次に、可能なすべてのコンボを取得し、それらを辞書としてデータベースに配置します(たとえば、$ 143の場合、辞書エントリ{apple:143、Pears:0、Oranges:0}..{appleまで:0、Pears:1、Oranges:47}。新しい番号を取得するたびにこれを行うので、すべての可能性のリストがあります。

これが私が立ち往生しているところです。上記のルールを使用する際に、どうすれば最善の解決策を見つけることができますか?2日間のデータを自動的に比較し、前日のデータと5%を超える分散がある可能性を排除する適応度関数が必要になると思います。

質問:

それで、ユーザーが合計を変更し、すべての確率のリストを持っているという私の質問ですが、これにどのようにアプローチする必要がありますか?何を学ぶ必要がありますか?そこに適用可能なアルゴリズムや使用できる理論はありますか?または、私の間違いを理解するのに役立つように、この目標を実現するために追加できるルールを提案できますか(現在の状態でない場合。果物を追加して、少なくとも3つ選択する必要があると言っていました)。 ?また、遺伝的アルゴリズムについては漠然としか理解していませんが、何か使えるものがあれば、ここで使えると思いました。

私は非常に学びたいと思っているので、アドバイスやヒントをいただければ幸いです(このゲームは不可能だと言わないでください)。

更新:これを解決するのは難しいというフィードバックを得る。だから私は、プレイヤーがしていることを妨げない(ゲームは彼らにとって同じままである)が、毎日果物の価値が(ランダムに)価格を変える別の条件をゲームに追加すると思いました。それは解決を容易にしますか?5%の動きと特定の果物の価値の変化の範囲内であるため、時間の経過とともに可能性が高い組み合わせはごくわずかです。

1日目は何でも可能で、十分に近い範囲を取得することはほとんど不可能ですが、果物の価格が変化し、ユーザーは5%の変更しか選択できないため、範囲を(時間の経過とともに)狭くしたり狭くしたりしないでください。上記の例では、価格が十分に変動する場合、推測できる範囲を与える解決策を総当たり攻撃できると思いますが、この範囲を狭め続けるためのより洗練された解決策や他の解決策があるかどうかを調べようとしています時間。

UPDATE2:読んで質問した後、これは隠れマルコフ/ビタビ問題であり、果物の価格の変化と合計(最後のデータポイントを最も重いものに重み付け)を追跡していると思います。しかし、関係をどのように適用するかはわかりません。これは事実であり、間違っている可能性があると思いますが、少なくとも、これはある種の機械学習の問題であると考え始めています。

更新3:ユーザーが生成したデータを自動化するのに役立つテストケース(数値が小さい)とジェネレーターを作成し、そこからグラフを作成して、より可能性が高いものを確認しようとしています。

これがコードと、ユーザーが実際に実を結ぶ量についての合計値とコメントです。

#!/usr/bin/env python
import itertools

# Fruit price data
fruitPriceDay1 = {'Apple':1, 'Pears':2, 'Oranges':3}
fruitPriceDay2 = {'Apple':2, 'Pears':3, 'Oranges':4}
fruitPriceDay3 = {'Apple':2, 'Pears':4, 'Oranges':5}

# Generate possibilities for testing (warning...will not scale with large numbers)
def possibilityGenerator(target_sum, apple, pears, oranges):
    allDayPossible = {}
    counter = 1
    apple_range = range(0, target_sum + 1, apple)
    pears_range = range(0, target_sum + 1, pears)
    oranges_range = range(0, target_sum + 1, oranges)
    for i, j, k in itertools.product(apple_range, pears_range, oranges_range):
        if i + j + k == target_sum:
            currentPossible = {}

            #print counter
            #print 'Apple', ':', i/apple, ',', 'Pears', ':', j/pears, ',', 'Oranges', ':', k/oranges
            currentPossible['apple'] = i/apple
            currentPossible['pears'] = j/pears
            currentPossible['oranges'] = k/oranges

            #print currentPossible
            allDayPossible[counter] = currentPossible
            counter = counter +1
    return allDayPossible

# Total sum being returned by user for value of fruits
totalSumDay1=26 # Computer does not know this but users quantities are apple: 20, pears 3, oranges 0 at the current prices of the day
totalSumDay2=51 # Computer does not know this but users quantities are apple: 21, pears 3, oranges 0 at the current prices of the day
totalSumDay3=61 # Computer does not know this but users quantities are apple: 20, pears 4, oranges 1 at the current prices of the day
graph = {}
graph['day1'] = possibilityGenerator(totalSumDay1, fruitPriceDay1['Apple'], fruitPriceDay1['Pears'], fruitPriceDay1['Oranges'] )
graph['day2'] = possibilityGenerator(totalSumDay2, fruitPriceDay2['Apple'], fruitPriceDay2['Pears'], fruitPriceDay2['Oranges'] )
graph['day3'] = possibilityGenerator(totalSumDay3, fruitPriceDay3['Apple'], fruitPriceDay3['Pears'], fruitPriceDay3['Oranges'] )

# Sample of dict = 1 : {'oranges': 0, 'apple': 0, 'pears': 0}..70 : {'oranges': 8, 'apple': 26, 'pears': 13}
print graph
4

7 に答える 7

23

グラフ理論と確率を組み合わせます。

1日目に、実行可能なすべてのソリューションのセットを作成します。A1 = {a1(1)、a1(2)、...、a1(n)}として設定された解を示しましょう。

2日目には、ソリューションセットA2を再度作成できます。

ここで、A2の各要素について、A1の各要素から到達できるかどうかを確認する必要があります(x%の許容誤差が与えられた場合)。その場合-A2(n)をA1(m)に接続します。A1(m)のどのノードからも到達できない場合は、このノードを削除できます。

基本的に、接続された有向非巡回グラフを作成しています。

グラフ内のすべてのパスが同じように発生する可能性があります。正確な解は、AmからAm + 1(AmのノードからAm + 1のノード)に単一のエッジがある場合にのみ見つけることができます。

確かに、一部のノードは他のノードよりも多くのパスに表示されます。各ノードの確率は、このノードを含むパスの数に基づいて直接推定できます。

このノードにつながるパスの数に等しい重みを各ノードに割り当てることにより、すべての履歴を保持する必要はなく、前日のみを保持する必要があります。

また、非負の値の線形ジフェンヒドラ方程式を見てください-私が少し前に尋ねた質問。受け入れられた答えは、各ステップですべてのコンボを列挙するための優れた方法です。

于 2011-10-08T11:06:58.987 に答える
10

免責事項:質問の重要な部分を読み間違えたため、一時的に回答を削除し、質問を注意深く読み直した後、回答を大幅に変更しました。同様のトピックとアルゴリズムを引き続き参照しながら、C#の問題のいくつかを自分で解決しようとした後、答えは大幅に改善されました。

ハリウッド版

  • 問題は、動的制約充足問題(DCSP)であり、制約充足問題(CSP)のバリエーションです。
  • 値と数量の範囲が小さくない場合は、モンテカルロを使用して特定の日の潜在的な解決策を見つけます。それ以外の場合は、ブルートフォースを使用してすべての潜在的な解決策を見つけます。
  • 潜在的なソリューションセットを制限するために、前日にカスケードで適用された制約記録(DCSPに関連)を使用します。
  • 確率に基づいて、指を交差させ、狙いを定めて撃ちます(推測)。
  • (オプション)ブルース・ウィリスが勝ちます。

元のバージョン

まず、ここで2つの主な問題が発生していることを述べたいと思います。

  1. 考えられる解決策の膨大な数。アイテムの数と合計値だけを知っていると、たとえば3と143の場合、多くの可能な解決策が得られます。さらに、無効なソリューションを必然的に試行せずに、アルゴリズムが有効なソリューションを選択することは容易ではありません(合計は143に等しくありません)。

  2. 与えられた日のDiに対して可能な解決策が見つかった場合、{D i + 1 .. D i+n }によって与えられる追加情報を使用して潜在的な解決策を排除する方法を見つける必要があります。

次の例のためにいくつかの基盤を築きましょう:

  • ゲーム全体で同じアイテム値を維持しましょう。ランダムにすることも、ユーザーが選択することもできます。
  • 可能なアイテム値は、[1-10]の非常に限られた範囲にバインドされ、2つのアイテムが同じ値を持つことはできません。
  • アイテムの数量が100を超えることはできません。つまり、[0-100]です。

これをより簡単に解決するために、私は自由に1つの制約を変更しました。これにより、アルゴリズムの収束が速くなります。

  • 「合計数量」ルールは、このルールによって上書きされます。[1〜10]の範囲内のアイテムを、合計で1日でいくつでも追加または削除できます。ただし、同じ数のアイテムを合計で2回以上追加または削除することはできません。これにより、ゲームのライフサイクルは最大20日になります。

このルールにより、ソリューションをより簡単に除外できます。また、範囲が狭くない場合、元の問題やルールと同じように、バックトラッキングアルゴリズムはまだ役に立たなくなります。

私の謙虚な意見では、このルールはゲームの本質ではなく、コンピューターが問題を解決できるようにするファシリテーターにすぎません。

問題1:潜在的な解決策を見つける

手始めに、問題1は、モンテカルロアルゴリズムを使用して解決し、一連の潜在的な解決策を見つけることができます。手法は単純です。アイテムの値と数量の乱数を(それぞれの許容範囲内で)生成します。必要な数のアイテムに対してこのプロセスを繰り返します。ソリューションが受け入れられるかどうかを確認します。これは、アイテムに明確な値があり、合計が目標の合計(たとえば、143)と等しいかどうかを確認することを意味します。

この手法には実装が簡単であるという利点がありますが、いくつかの欠点があります。

  • ユーザーのソリューションが結果に表示されることは保証されていません。
  • 「ミス」がたくさんあります。たとえば、私たちの制約を考えると、1,000の潜在的な解決策を見つけるのに多かれ少なかれ3,000,000回の試行が必要です。
  • 時間がかかります。怠惰なラップトップでは約4〜5秒です。

これらの欠点を回避する方法は?上手...

  • 範囲を小さい値に制限し、
  • ユーザーのソリューションがソリューションセットに表示される可能性が高くなるように、適切な数の潜在的なソリューションを見つけます。
  • ヒューリスティックを使用して、より簡単に解決策を見つけます(これについては後で詳しく説明します)。

範囲を制限すればするほど、モンテカルロアルゴリズムの有用性が低下することに注意してください。これは、妥当な時間内にすべてを反復するのに十分な有効なソリューションがほとんどないためです。制約{3、[1-10]、[0-100]}の場合、約741,000,000の有効な解があります(目標の合計値に制約されません)。そこでモンテカルロを使用できます。{3、[1-5]、[0-10]}の場合、約80,000しかありません。モンテカルロを使用する必要はありません。ブルートフォースforループは問題なく機能します。

問題1は、制約充足問題(またはCSP)と呼ばれるものだと思います。

問題2:潜在的な解決策のセットを制限する

問題1がCSPであるという事実を踏まえて、問題2、および一般的な問題を動的CSP(またはDCSP)と呼びます。

[DCSP]は、問題の元の定式化が何らかの方法で変更された場合に役立ちます。これは通常、考慮すべき一連の制約が環境によって変化するためです。DCSPは、一連の静的CSPと見なされ、それぞれが前のCSPの変換であり、変数と制約を追加(制限)または削除(緩和)できます。

この問題に役立つ可能性のあるCSPで使用される1つの手法は、制約記録と呼ばれます。

  • 環境が変更されるたびに(ユーザーがD i + 1の値を入力)、新しい制約に関する情報を見つけます。追加と削除の制約に「使用される」可能性のある数量はどれくらいですか。
  • カスケードの前日ごとに制約を適用します。波打つ効果は、考えられる解決策を大幅に減らす可能性があります。

これを機能させるには、可能な解決策の新しいセットを毎日入手する必要があります。ブルートフォースまたはモンテカルロのいずれかを使用します。次に、 DiDi-1の解を比較し、制約に違反することなく前日の解を引き継ぐことができる解のみを保持します。

おそらく、どのソリューションが他のソリューションにつながるか(おそらく有向グラフで)の履歴を保持する必要があります。制約の記録により、可能な追加-削除量を記憶し、それに基づいてソリューションを拒否できます。

ソリューションをさらに改善するために実行できる他の多くのステップがあります。ここにいくつかのアイデアがあります:

  • 以前のソリューションで見つかったアイテムと値の組み合わせの制約を記録します。他のソリューションをすぐに拒否します(アイテムの値は変更してはならないため)。ソリューション固有の制約を使用して既存のソリューションごとに小さいソリューションセットを見つけて、無効なソリューションを早期に拒否することもできます。
  • D 1ソリューションセットにユーザーのソリューションが含まれていない場合を「修復」するために、毎日いくつかの「ミュータント」で完全な履歴のソリューションを生成します。遺伝的アルゴリズムを使用して、既存のソリューションセットに基づいて変異集団を見つけることができます。)
  • 解決策を簡単に見つけるためにヒューリスティックを使用します(たとえば、有効な解決策が見つかったら、周りの量を置き換えて、この解決策のバリエーションを見つけてみてください)。
  • 一部のユーザーアクションを予測するために、行動ヒューリスティックを使用します(たとえば、すべてのアイテムに同じ量、極端なパターンなど)
  • ユーザーが新しい数量を入力している間、いくつかの計算を続けます。

これらすべてを考慮して、ソリューションの発生とヒューリスティックに基づいてランク付けシステムを見つけ、候補ソリューションを決定してください。

于 2011-10-10T19:29:31.493 に答える
8

この問題を解決することは不可能です。

これの最大比率だけでなく、アイテムの数がどの比率で増加したかを正確に知っているとしましょう。

ユーザーにはN個の果物があり、推測にはD日あります。

毎日、N個の新しい変数を取得し、合計でD*N個の変数を取得します。

毎日、2つの方程式しか生成できません。1つの方程式はn_item*priceの合計であり、もう1つの方程式は既知の比率に基づいています。それらがすべて独立している場合、合計で最大2*Dの方程式があります。

すべてのN>2に対して2*D <N * D

于 2011-10-08T15:02:56.613 に答える
4

私はゲームをプレイするためのプログラムを書きました。もちろん、人間側を自動化する必要がありましたが、本物の人間と対戦したときにアプローチを無効にしないようにすべてを行ったと思います。

私は機械学習の観点からこれにアプローチし、問題を隠れマルコフモデルとして扱いました。このモデルでは、合計金額が観測値でした。私の解決策は、粒子フィルターを使用することです。このソリューションは、NumPyとSciPyを使用してPython2.7で記述されています。

コメントで明示的に、またはコードで暗黙的に行った仮定を述べました。また、コードを自動化して実行するために、いくつかの追加の制約を設定しました。スピードよりも理解しやすさを重視したので、特に最適化されていません。

各反復は、現在の真の量と推測を出力します。簡単に確認できるように、出力をファイルにパイプするだけです。興味深い拡張は、2D(2つの果物の場合)または3D(3つの果物の場合)のいずれかのグラフに出力をプロットすることです。そうすれば、粒子フィルターが溶液に焦点を合わせているのを見ることができます。

アップデート:

微調整後に更新されたパラメーターを含めるようにコードを編集しました。matplotlibを使用した(pylab経由の)プロット呼び出しが含まれています。プロットはLinux-Gnomeで機能しますが、マイレージは異なる場合があります。プロットのサポートのために、デフォルトのNUM_FRUITSを2に設定しました。すべてのpylab呼び出しをコメントアウトして、プロットを削除し、NUM_FRUITSを任意に変更できるようにします。

UnknownQuantities X Price=TotalPriceで表される現在のfxnをうまく見積もることができます。2D(2フルーツ)ではこれは線であり、3D(3フルーツ)では平面になります。粒子フィルターが正しい量に確実に焦点を合わせるにはデータが少なすぎるようです。履歴情報を実際にまとめるには、粒子フィルターの上にもう少し賢いものが必要です。粒子フィルターを2次または3次に変換してみることができます。

アップデート2:

私は自分のコードをたくさんいじっています。私はたくさんのことを試し、今から作成する最終的なプログラムを提示します(このアイデアに燃え始めています)。

変更点:

パーティクルは、整数ではなく浮動小数点を使用するようになりました。これが意味のある効果をもたらしたかどうかはわかりませんが、より一般的な解決策です。整数への丸めは、推測を行う場合にのみ行われます。

プロットでは、真の量が緑の四角で示され、現在の推測が赤の四角で示されます。現在信じられている粒子は青い点で示されています(私たちがどれだけ信じているかによってサイズが決まります)。これにより、アルゴリズムがどの程度うまく機能しているかを簡単に確認できます。(プロットもテストされ、Win 7 64ビットで動作しています)。

数量変更と価格変更のオン/オフのパラメータを追加しました。もちろん、両方の「オフ」は面白くありません。

それはかなり良い仕事をしますが、すでに述べたように、それは本当に難しい問題なので、正確な答えを得るのは難しいです。CHANGE_QUANTITIESをオフにすると、最も単純なケースが生成されます。CHANGE_QUANTITIESをオフにして2つのフルーツを実行すると、問題の難しさを理解できます。それが正解にどれだけ早く焦点を合わせるかを見てから、果物の数を増やすにつれてそれがどれほど難しくなるかを見てください。

CHANGE_QUANTITIESをオンのままにして、MAX_QUANTITY_CHANGEを非常に小さい値(.001)から「大きい」値(.05)に調整することで、難易度を把握することもできます。

それが苦労する1つの状況は、次元(1つの果物の量)がゼロに近づく場合です。パーティクルの平均を使用して推測しているため、常にゼロのようなハード境界から離れてスキューします。

一般的に、これは素晴らしい粒子フィルターのチュートリアルになります。


from __future__ import division
import random
import numpy
import scipy.stats
import pylab

# Assume Guesser knows prices and total
# Guesser must determine the quantities

# All of pylab is just for graphing, comment out if undesired
#   Graphing only graphs first 2 FRUITS (first 2 dimensions)

NUM_FRUITS = 3
MAX_QUANTITY_CHANGE = .01 # Maximum percentage change that total quantity of fruit can change per iteration
MAX_QUANTITY = 100 # Bound for the sake of instantiating variables
MIN_QUANTITY_TOTAL = 10 # Prevent degenerate conditions where quantities all hit 0
MAX_FRUIT_PRICE = 1000 # Bound for the sake of instantiating variables
NUM_PARTICLES = 5000
NEW_PARTICLES = 500 # Num new particles to introduce each iteration after guessing
NUM_ITERATIONS = 20 # Max iterations to run
CHANGE_QUANTITIES = True
CHANGE_PRICES = True

'''
  Change individual fruit quantities for a random amount of time
  Never exceed changing fruit quantity by more than MAX_QUANTITY_CHANGE
'''
def updateQuantities(quantities):
  old_total = max(sum(quantities), MIN_QUANTITY_TOTAL)
  new_total = old_total
  max_change = int(old_total * MAX_QUANTITY_CHANGE)

  while random.random() > .005: # Stop Randomly    
    change_index = random.randint(0, len(quantities)-1)
    change_val = random.randint(-1*max_change,max_change)

    if quantities[change_index] + change_val >= 0: # Prevent negative quantities
      quantities[change_index] += change_val
      new_total += change_val

      if abs((new_total / old_total) - 1) > MAX_QUANTITY_CHANGE:
        quantities[change_index] -= change_val # Reverse the change

def totalPrice(prices, quantities):
  return sum(prices*quantities)

def sampleParticleSet(particles, fruit_prices, current_total, num_to_sample):
  # Assign weight to each particle using observation (observation is current_total)
  # Weight is the probability of that particle (guess) given the current observation
  # Determined by looking up the distance from the hyperplane (line, plane, hyperplane) in a
  #   probability density fxn for a normal distribution centered at 0 
  variance = 2
  distances_to_current_hyperplane = [abs(numpy.dot(particle, fruit_prices)-current_total)/numpy.linalg.norm(fruit_prices) for particle in particles]
  weights = numpy.array([scipy.stats.norm.pdf(distances_to_current_hyperplane[p], 0, variance) for p in range(0,NUM_PARTICLES)])

  weight_sum = sum(weights) # No need to normalize, as relative weights are fine, so just sample un-normalized

  # Create new particle set weighted by weights
  belief_particles = []
  belief_weights = []
  for p in range(0, num_to_sample):
    sample = random.uniform(0, weight_sum)
    # sum across weights until we exceed our sample, the weight we just summed is the index of the particle we'll use
    p_sum = 0
    p_i = -1
    while p_sum < sample:
      p_i += 1
      p_sum += weights[p_i]
    belief_particles.append(particles[p_i])
    belief_weights.append(weights[p_i])

  return belief_particles, numpy.array(belief_weights)

'''
  Generates new particles around the equation of the current prices and total (better particle generation than uniformly random)
'''
def generateNewParticles(current_total, fruit_prices, num_to_generate):
  new_particles = []
  max_values = [int(current_total/fruit_prices[n]) for n in range(0,NUM_FRUITS)]
  for p in range(0, num_to_generate):
    new_particle = numpy.array([random.uniform(1,max_values[n]) for n in range(0,NUM_FRUITS)])
    new_particle[-1] = (current_total - sum([new_particle[i]*fruit_prices[i] for i in range(0, NUM_FRUITS-1)])) / fruit_prices[-1]
    new_particles.append(new_particle)
  return new_particles


# Initialize our data structures:
# Represents users first round of quantity selection
fruit_prices = numpy.array([random.randint(1,MAX_FRUIT_PRICE) for n in range(0,NUM_FRUITS)])
fruit_quantities = numpy.array([random.randint(1,MAX_QUANTITY) for n in range(0,NUM_FRUITS)])
current_total = totalPrice(fruit_prices, fruit_quantities)
success = False

particles = generateNewParticles(current_total, fruit_prices, NUM_PARTICLES) #[numpy.array([random.randint(1,MAX_QUANTITY) for n in range(0,NUM_FRUITS)]) for p in range(0,NUM_PARTICLES)]
guess = numpy.average(particles, axis=0)
guess = numpy.array([int(round(guess[n])) for n in range(0,NUM_FRUITS)])

print "Truth:", str(fruit_quantities)
print "Guess:", str(guess)

pylab.ion()
pylab.draw()
pylab.scatter([p[0] for p in particles], [p[1] for p in particles])
pylab.scatter([fruit_quantities[0]], [fruit_quantities[1]], s=150, c='g', marker='s')
pylab.scatter([guess[0]], [guess[1]], s=150, c='r', marker='s')
pylab.xlim(0, MAX_QUANTITY)
pylab.ylim(0, MAX_QUANTITY)
pylab.draw()

if not (guess == fruit_quantities).all():
  for i in range(0,NUM_ITERATIONS):
    print "------------------------", i

    if CHANGE_PRICES:
      fruit_prices = numpy.array([random.randint(1,MAX_FRUIT_PRICE) for n in range(0,NUM_FRUITS)])

    if CHANGE_QUANTITIES:
      updateQuantities(fruit_quantities)
      map(updateQuantities, particles) # Particle Filter Prediction

    print "Truth:", str(fruit_quantities)
    current_total = totalPrice(fruit_prices, fruit_quantities)

    # Guesser's Turn - Particle Filter:
    # Prediction done above if CHANGE_QUANTITIES is True

    # Update
    belief_particles, belief_weights = sampleParticleSet(particles, fruit_prices, current_total, NUM_PARTICLES-NEW_PARTICLES)
    new_particles = generateNewParticles(current_total, fruit_prices, NEW_PARTICLES)

    # Make a guess:
    guess = numpy.average(belief_particles, axis=0, weights=belief_weights) # Could optimize here by removing outliers or try using median
    guess = numpy.array([int(round(guess[n])) for n in range(0,NUM_FRUITS)]) # convert to integers
    print "Guess:", str(guess)

    pylab.cla()
    #pylab.scatter([p[0] for p in new_particles], [p[1] for p in new_particles], c='y') # Plot new particles
    pylab.scatter([p[0] for p in belief_particles], [p[1] for p in belief_particles], s=belief_weights*50) # Plot current particles
    pylab.scatter([fruit_quantities[0]], [fruit_quantities[1]], s=150, c='g', marker='s') # Plot truth
    pylab.scatter([guess[0]], [guess[1]], s=150, c='r', marker='s') # Plot current guess
    pylab.xlim(0, MAX_QUANTITY)
    pylab.ylim(0, MAX_QUANTITY)
    pylab.draw()

    if (guess == fruit_quantities).all():
      success = True
      break

    # Attach new particles to existing particles for next run:
    belief_particles.extend(new_particles)
    particles = belief_particles
else:
  success = True

if success:
  print "Correct Quantities guessed"
else:
  print "Unable to get correct answer within", NUM_ITERATIONS, "iterations"

pylab.ioff()
pylab.show()
于 2011-10-13T23:20:37.350 に答える
2

最初のルールについて:

私の学年から、5%の変更を抽象化すると、以前と同じ値である3つの未知の値(申し訳ありませんが英語の数学の語彙がわかりません)を持つ方程式が毎日あります。日。3日目には、3つの方程式、3つの未知の値があり、解は直接である必要があります。

3つの要素の値が十分に異なる場合、毎日の5%の変化は忘れられる可能性があると思います。これは、おっしゃるように、近似値を使用して数値を四捨五入するためです。

適合したルールの場合:

この場合、未知数が多すぎて値が変化するため、私が知っている直接的な解決策はありません。私はこれについてLiorを信頼します。彼のアプローチはうまく見えます!(価格と数量の範囲が限られている場合。)

于 2011-10-12T20:49:50.743 に答える
1

答えがかなり長くなっていることに気づいたので、コードを一番上に移動しました(これはおそらくほとんどの人が興味を持っていることです)。その下には2つのことがあります。

  1. (深い)ニューラルネットワークがこの問題への良いアプローチではない理由の説明、および
  2. 与えられた情報で人間の選択を一意に決定できない理由の説明。

どちらかのトピックに興味のある方は、以下をご覧ください。残りの皆さんのために、ここにコードがあります。


考えられるすべての解決策を見つけるコード

答えのさらに下で説明するように、あなたの問題は劣決定です。平均的な場合、考えられる解決策はたくさんあり、この数は日数が増えるにつれて少なくとも指数関数的に増加します。これは、元の問題と拡張された問題の両方に当てはまります。それでも、すべてのソリューションを(ある意味で)効率的に見つけることができます( NP困難なので、あまり期待しないでください)。

ここでは、バックトラッキング(1960年代からのものであるため、正確には最新ではありません)が最適なアルゴリズムです。Pythonでは、再帰ジェネレーターとして記述できます。これは実際には非常にエレガントです。

def backtrack(pos, daily_total, daily_item_value, allowed_change, iterator_bounds, history=None):
    if pos == len(daily_total):
        yield np.array(history)
        return

    it = [range(start, stop, step) for start, stop, step in iterator_bounds[pos][:-1]]
    for partial_basket in product(*it):
        if history is None:
            history = [partial_basket]
        else:
            history.append(partial_basket)

        # ensure we only check items that match the total basket value
        # for that day
        partial_value = np.sum(np.array(partial_basket) * daily_item_value[pos, :-1])
        if (daily_total[pos] - partial_value) % daily_item_value[pos, -1] != 0:
            history.pop()
            continue

        last_item = (daily_total[pos] - partial_value) // daily_item_value[pos, -1]
        if last_item < 0:
            history.pop()
            continue

        basket = np.array([*partial_basket] + [int(last_item)])
        basket_value = np.sum(basket * daily_item_value[pos])
        history[-1] = basket
        if len(history) > 1:
            # ensure that today's basket stays within yesterday's range
            previous_basket = history[-2]
            previous_basket_count = np.sum(previous_basket)
            current_basket_count = np.sum(basket)
            if (np.abs(current_basket_count - previous_basket_count) > allowed_change * previous_basket_count):
                history.pop()
                continue

        yield from backtrack(pos + 1, daily_total, daily_item_value, allowed_change, iterator_bounds, history)
        history.pop()

このアプローチは、基本的にすべての可能な候補を大きなツリーに構造化し、制約に違反するたびにプルーニングを使用して深さ優先探索を実行します。リーフノードに遭遇するたびに、結果が得られます。

ツリー検索(一般的に)は並列化できますが、それはここでは範囲外です。それは、多くの追加の洞察なしにソリューションを読みにくくします。同じことがコードの一定のオーバーヘッドを減らすためにも当てはまります。たとえば、変数に制約if ...: continueを適用し、iterator_boundsチェックを少なくします。

この回答の最後に、完全なコード例(ゲームの人間側のシミュレーターを含む)を配置しました。


この問題に対する最新の機械学習

質問は9歳ですが、それでも私は深く興味を持っています。それ以来、機械学習(RNN、CNN、GANSなど)、新しいアプローチ、新しいアプローチを可能にする安価なGPUが登場しました。この質問をもう一度見て、新しいアプローチがあるかどうかを確認するのは楽しいだろうと思いました。

ディープニューラルネットワークの世界に対するあなたの熱意が本当に好きです。残念ながら、いくつかの理由により、ここでは適用されません。

  1. 正確さ)ゲームのように正確な解決策が必要な場合、NNはそれを提供できません。
  2. 整数制約)現在主流のNNトレーニング方法は最急降下法に基づいているため、問題を微分可能にするか、微分可能になるように再定式化できる必要があります。整数に制限すると、クレードルのGDメソッドが強制終了されます。進化的アルゴリズムを試して、パラメーター化を検索できます。これは存在しますが、これらの方法は現在あまり確立されていません。
  3. 非凸性)一般的な定式化では、NNのトレーニングは局所的な方法です。つまり、アルゴリズムが収束している場合、正確に1つの(局所的に最適な)解が見つかります。平均的なケースでは、ゲームには元のバージョンと拡張バージョンの両方に対して多くの可能な解決策があります。これは、平均して、人間の選択(バスケット)を理解できないことを意味するだけでなく、NNが多くのソリューションのどれを見つけるかを制御できないことも意味します。現在のNNのサクセスストーリーは同じ運命をたどりますが、特定の解決策ではなく、いくつかの解決策しか求めていないため、実際には気にしない傾向があります。いくつかの大丈夫な解決策は、まったく解決策がないことから地獄を打ち負かします。
  4. エキスパートドメイン知識)このゲームでは、最適化/学習を改善するために利用できるドメイン知識がたくさんあります。NNで任意のドメイン知識を最大限に活用することは簡単ではなく、このゲームでは、カスタムMLモデル(ニューラルネットワークではない)を構築する方が簡単で効率的です。

ゲームを一意に解決できない理由-パート1

最初に代替の問題を検討し、整数の要件を引き上げましょう。つまり、バスケット(N特定の日の果物の人間の選択)は分数の果物(0.3オレンジ)を持つことができます。

合計値の制約によりnp.dot(basket, daily_price) == total_value、バスケットの可能な解決策が制限されます。問題を1次元減らします。果物の量を自由に選んN-1でください。制約を満たすために、N番目の果物の値をいつでも見つけることができます。ですからN、一日の選択はあるように見えますが、実際にはN-1自由にできることしかなく、最後の選択は以前の選択によって完全に決定されます。したがって、ゲームが進行する毎日について、追加N-1の選択肢/変数を推定する必要があります。

すべての選択肢が0より大きいことを強制したい場合がありますが、それは数値を選択できる間隔を減らすだけです。実数の開区間には無限に多くの数が含まれているため、このためにオプションが不足することはありません。まだN-1選択する必要があります。

2日間の間に、バスケットの総量は最大で前日のnp.sum(basket)変化のみになります。特定の日に行うことができる選択のいくつかは、前日よりもバスケットを変更します。これに違反しないようにするために、自由に選択してから、-番目の変数を選択して、それを追加し、-変数(以前の選択から修正された)を追加することが内に収まるようにする必要があります。(注:これは不等式制約であるため、等式がある場合、つまりバスケットが正確に変化する場合にのみ、選択肢の数が減ります。最適化理論では、これはアクティブな制約として知られています。)some_percentnp.abs(np.sum(previous_basket) - np.sum(basket)) <= some_percent * np.sum(previous_basket)some_percentN-2N-1Nsome_percentsome_percent

すべての選択肢を0より大きくする必要があるという制約についてもう一度考えることができますが、これにより、N-2変数を自由に選択できる間隔が変更されるだけであるという議論が残ります。

したがって、D数日後N-1、最初の日から見積もる選択肢(変更の制約なし)と、(D-1)*(N-2)翌日ごとに見積もる選択肢が残ります。残念ながら、この数をさらに減らすための制約がなくなり、未知数の数は少なくともN-2毎日増加しています。これは本質的に、LukaRahneが「2*D < N*D for all N > 2」で意味したことです。すべて同じように可能性が高い多くの候補が見つかる可能性があります。

毎日の正確な食料価格はこれには関係ありません。それらが何らかの価値がある限り、それらは選択肢の1つを制約します。したがって、指定した方法でゲームを拡張すると、常に無限に多くのソリューションが得られる可能性があります。日数に関係なく。


ゲームがまだ一意に解決できない理由-パート2

これを修正するのに役立つ可能性のある、私たちが調べなかった制約が1つあります。それは、選択肢に整数解のみを許可することです。整数制約の問題は、処理が非常に複雑なことです。ただし、ここでの主な懸念事項は、この制約を追加することで、十分な日数が与えられた場合に問題を一意に解決できるかどうかです。このために、かなり直感的な反例があります。連続して3日あり、1日目と3日目では、合計値の制約で許可されるバスケットは1つだけであるとします。つまり、1日目と3日目のバスケットはわかっsome_percentていますが、2日目はわかりません。ここでは、合計値だけがわかっています。つまり、1日目以内で、3日目はsome_percent2日目以内です。これで十分ですか。 2日目にバスケットに何が入っているかを常に把握するための情報?

some_percent = 0.05
Day 1: basket: [3 2]  prices: [10 7]  total_value: 44
Day 2: basket: [x y]  prices: [5  5]  total_value: 25
Day 3: basket: [2 3]  prices: [9  5]  total_value: 33

Possible Solutions Day 2: [2 3], [3 2]

上記は1つの例であり、合計値の制約のおかげで2日間の値がわかっていますが、それでも2日目のバスケットの正確な構成を計算することはできません。場合によっては、一般的には不可能です。3日目以降に日数を追加しても、2日目はまったくわかりません。3日目のオプションを絞り込むのに役立つかもしれませんが(2日目のオプションを絞り込むことができます)、3日目の選択肢はすでに1つしか残っていないため、無駄です。


完全なコード

import numpy as np
from itertools import product
import tqdm


def sample_uniform(n, r):
    # check out: http://compneuro.uwaterloo.ca/files/publications/voelker.2017.pdf
    sample = np.random.rand(n + 2)
    sample_norm = np.linalg.norm(sample)
    unit_sample = (sample / sample_norm)
    change = np.floor(r * unit_sample[:-2]).astype(np.int)
    return change


def human(num_fruits, allowed_change=0.05, current_distribution=None):
    allowed_change = 0.05
    if current_distribution is None:
        current_distribution = np.random.randint(1, 50, size=num_fruits)
    yield current_distribution.copy()

    # rejection sample a suitable change
    while True:
        current_total = np.sum(current_distribution)
        maximum_change = np.floor(allowed_change * current_total)

        change = sample_uniform(num_fruits, maximum_change)
        while np.sum(change) > maximum_change:
            change = sample_uniform(num_fruits, maximum_change)

        current_distribution += change
        yield current_distribution.copy()


def prices(num_fruits, alter_prices=False):
    current_prices = np.random.randint(1, 10, size=num_fruits)
    while True:
        yield current_prices.copy()
        if alter_prices:
            current_prices = np.random.randint(1, 10, size=num_fruits)


def play_game(num_days, num_fruits=3, alter_prices=False):
    human_choice = human(num_fruits)
    price_development = prices(num_fruits, alter_prices=alter_prices)

    history = {
        "basket": list(),
        "prices": list(),
        "total": list()
    }
    for day in range(num_days):
        choice = next(human_choice)
        price = next(price_development)
        total_price = np.sum(choice * price)

        history["basket"].append(choice)
        history["prices"].append(price)
        history["total"].append(total_price)

    return history


def backtrack(pos, daily_total, daily_item_value, allowed_change, iterator_bounds, history=None):
    if pos == len(daily_total):
        yield np.array(history)
        return

    it = [range(start, stop, step) for start, stop, step in iterator_bounds[pos][:-1]]
    for partial_basket in product(*it):
        if history is None:
            history = [partial_basket]
        else:
            history.append(partial_basket)

        # ensure we only check items that match the total basket value
        # for that day
        partial_value = np.sum(np.array(partial_basket) * daily_item_value[pos, :-1])
        if (daily_total[pos] - partial_value) % daily_item_value[pos, -1] != 0:
            history.pop()
            continue

        last_item = (daily_total[pos] - partial_value) // daily_item_value[pos, -1]
        if last_item < 0:
            history.pop()
            continue

        basket = np.array([*partial_basket] + [int(last_item)])
        basket_value = np.sum(basket * daily_item_value[pos])
        history[-1] = basket
        if len(history) > 1:
            # ensure that today's basket stays within relative tolerance
            previous_basket = history[-2]
            previous_basket_count = np.sum(previous_basket)
            current_basket_count = np.sum(basket)
            if (np.abs(current_basket_count - previous_basket_count) > allowed_change * previous_basket_count):
                history.pop()
                continue

        yield from backtrack(pos + 1, daily_total, daily_item_value, allowed_change, iterator_bounds, history)
        history.pop()


if __name__ == "__main__":
    np.random.seed(1337)
    num_fruits = 3
    allowed_change = 0.05
    alter_prices = False
    history = play_game(15, num_fruits=num_fruits, alter_prices=alter_prices)

    total_price = np.stack(history["total"]).astype(np.int)
    daily_price = np.stack(history["prices"]).astype(np.int)
    basket = np.stack(history["basket"]).astype(np.int)

    maximum_fruits = np.floor(total_price[:, np.newaxis] / daily_price).astype(np.int)
    iterator_bounds = [[[0, maximum_fruits[pos, fruit], 1] for fruit in range(num_fruits)] for pos in range(len(basket))]
    # iterator_bounds = np.array(iterator_bounds)
    # import pdb; pdb.set_trace()

    pbar = tqdm.tqdm(backtrack(0, total_price,
                               daily_price, allowed_change, iterator_bounds), desc="Found Solutions")
    for solution in pbar:
        # test price guess
        calculated_price = np.sum(np.stack(solution) * daily_price, axis=1)
        assert np.all(calculated_price == total_price)

        # test basket change constraint
        change = np.sum(np.diff(solution, axis=0), axis=1)
        max_change = np.sum(solution[:-1, ...], axis=1) * allowed_change
        assert np.all(change <= max_change)

        # indicate that we found the original solution
        if not np.any(solution - basket):
            pbar.set_description("Found Solutions (includes original)")

于 2020-07-08T09:54:32.997 に答える
0

プレイヤーが可能性の数を1に減らす組み合わせを選択すると、コンピューターが勝ちます。それ以外の場合、プレーヤーは、合計が特定のパーセンテージ内で変化するという制約のある組み合わせを選択でき、そのコンピューターが勝つことはありません。

import itertools
import numpy as np


def gen_possible_combination(total, prices):
    """
    Generates all possible combinations of numbers of items for
    given prices constraint by total
    """
    nitems = [range(total//p + 1) for p in prices]
    prices_arr = np.array(prices)
    combo = [x for x in itertools.product(
        *nitems) if np.dot(np.array(x), prices_arr) == total]

    return combo


def reduce(combo1, combo2, pct):
    """
    Filters impossible transitions which are greater than pct
    """
    combo = {}
    for x in combo1:
        for y in combo2:
            if abs(sum(x) - sum(y))/sum(x) <= pct:
                combo[y] = 1

    return list(combo.keys())


def gen_items(n, total):
    """
    Generates a list of items
    """
    nums = [0] * n
    t = 0
    i = 0
    while t < total:
        if i < n - 1:
            n1 = np.random.randint(0, total-t)
            nums[i] = n1
            t += n1
            i += 1
        else:
            nums[i] = total - t
            t = total

    return nums


def main():
    pct = 0.05
    i = 0
    done = False
    n = 3
    total_items = 26  # np.random.randint(26)
    combo = None
    while not done:
        prices = [np.random.randint(1, 10) for _ in range(n)]
        items = gen_items(n, total_items)

        total = np.dot(np.array(prices),  np.array(items))
        combo1 = gen_possible_combination(total, prices)

        if combo:
            combo = reduce(combo, combo1, pct)
        else:
            combo = combo1
        i += 1
        print(i, 'Items:', items, 'Prices:', prices, 'Total:',
              total, 'No. Possibilities:', len(combo))

        if len(combo) == 1:
            print('Solution', combo)
            break
        if np.random.random() < 0.5:
            total_items = int(total_items * (1 + np.random.random()*pct))
        else:
            total_items = int(
                np.ceil(total_items * (1 - np.random.random()*pct)))


if __name__ == "__main__":
    main()
于 2020-07-12T10:15:26.297 に答える