インスタンスのランダム生成の使い方

インスタンスのランダム生成の使い方#

Problemには、 問題のスキーマ(つまり、プレースホルダー)に基づいたインスタンスデータのランダム生成が行えるメソッドがあります。本節では、そのランダム生成における設定などを説明します。

メソッドは2つあり、以下の通りになります。

  • Problem.generate_random_dataset は、Problem.eval() に渡すインスタンスデータを返します。

  • Problem.generate_random_instance は、 同じくデータを生成するが、 OMMX インスタンスとして返します。 generate_random_datasetで取得したインスタンスデータをそのままProblem.eval()に渡すのと同じです。

上記メソッドの生成に関する引数は共通で、defaultoptionsseed の3つです。さらにはgenerate_random_instanceProblem.evalの引数も対応しています(prune_unused_dec_varsconstraint_detectionなど)。

seedパラメータは、乱数生成の初期化に使われる乱数シードです。インスタンス生成に再現性を求める場合に使えます。

defaultoptionsは、インスタンスデータの値の範囲を決めるものです。options では、辞書で問題の各Placeholder に対してそれぞれの値の範囲を指定することができます。defaultは、特定の範囲指定がない値のためのデフォルト範囲となります。いずれも必須ではありません。optionsで全プレースホルダーの範囲を指定している場合はdefaultを渡す必要がありませんし、defaultだけでシンプルなデータを生成できる場合もあります。しかし、生成されたインスタンスが実行可能解を持つ保証はないので注意してください。

実際に引数の指定方法を見ていきましょう。

defaultoptionsの指定方法#

defaultoptions は両方とも以下の指定方法を対応としています。

  • 固定値

  • Python 組み込みのrange

  • jijmodeling.generation 配下の関数から取得されたオブジェクト

  • タプルや辞書などを使った範囲指定。詳細はgenerate_random_dataset()の API リファレンスを参照してください

固定値が指定された場合、その数値がそのままプレースホルダーの値に使われます(ランダムな値は使いません)。 Python の rangejijmodeling.generation などの範囲が指定された場合、その範囲がランダム生成の境界になります。

指定方法の例を見てみましょう。

import jijmodeling as jm

problem = jm.Problem("my problem")
x = problem.BinaryVar("x")
A = problem.Natural("A")
B = problem.Natural("B")
problem += A * x + B

problem.generate_random_dataset(default={"value": range(1, 10)})
{'B': 7, 'A': 3}

上記の例では、optionsがないため、 ABの値の生成時は、defaultが参照されます。rangeの区間内でそれぞれ別の値が生成されます。

今回使った Python 組み込みのrangeは左閉右開になっています(つまり、range(1,4)の場合、1、2、3が入って、4は入らない)。jijmodeling.generation では、 開区間のjijmodeling.generation.open()や左有界(上界が無限大)のjijmodeling.generation.at_least()など、違うスタイルの範囲を定義するための関数を提供しています。 Python 組み込みのrangejijmodeling.generation.closed_open()と同じになります。

生成された値はプレースホルダーの型に則します。つまり、自然数のプレースホルダーがあった場合、(-10, 10) など負数を含む範囲を渡しても、生成されるのは自然数のみです。

それでは、optionsを使って、ABに別々の範囲を指定したい場合をみてみましょう。プレースホルダーの名前がキー、値が該当プレースホルダーの生成オプションになります。生成オプションは複数あるので、辞書である必要があります。スカラーの場合はvalueオプションのみで十分です。他のオプションは下記で説明します。

problem.generate_random_dataset(default={"value": range(1, 10)}, options={"A": {"value": range(50, 100)}})
{'A': 65, 'B': 8}

このコードでは、optionsを通じて"A"の値の範囲を指定しています。"B"optionsで指定していないため、前述の通りdefaultを参照しています。もちろん、次のようにどちらもoptionsで指定することも可能です。

problem.generate_random_dataset(options = {"A": {"value": range(50, 100)}, "B": {"value": range(1, 10)}})
{'A': 81, 'B': 6}

配列プレスホルダー#

配列プレスホルダーの値の範囲指定は基本的にスカラーと同じです。プレースホルダーのshapeが完全に定義された場合、そのshapeに則した配列が生成されます。要素はすべてvalue範囲内となります(value指定がない場合、defaultが参照されます)。

problem = jm.Problem("my problem")
# この例では、数値の10にしているが、shapeをプレースホルダーにしても問題ありません。
x = problem.BinaryVar("x", shape=10)
A = problem.Natural("A", shape=10)
problem += jm.sum(10, lambda i: A[i] * x[i])

problem.generate_random_dataset(options={"A": {"value": range(1,10)}})
{'A': array([3, 5, 3, 4, 7, 8, 6, 7, 3, 2], dtype=object)}

shapeが完全に定義されていない場合は、配列の要素数をsizeキーで指定する必要があります。sizeは要素数を決めるものなので自然数でなければなりませんが、valueと同様、範囲指定に対応しています。そのため、配列の要素数がランダムなインスタンスも生成可能です。

problem = jm.Problem("my problem")
A = problem.Float("A", ndim=1)
N = A.len_at(0)
x = problem.BinaryVar("x", shape=(N,))

problem += jm.sum(N, lambda i: A[i] * x[i])

problem.generate_random_dataset(
    options={
        "A": {"value": range(-10, 10), "size": range(1,10),}
    },
)
{'A': array([0.9978742805328533, -4.978567146938855, -5.662702894672691,
        6.893214486646777, -6.1748282046331004, -1.2943721075068169],
       dtype=object)}

shapeの一部が未指定のプレースホルダーの場合(たとえばshape=(None, 10)など)、要素数が未指定になっている次元はそれぞれsizeを参照して要素数を決めます。shapeの情報が優先されるので、要素数が完全に定義されているプレースホルダーにsizeオプションをつけても意味ありません。

辞書型プレースホルダー#

カテゴリーラベル型をキーとしている辞書型のプレースホルダーの場合、optionsでそのカテゴリーラベルに該当するキーを定義する必要があります。カテゴリーラベルのオプションにkeysに文字列の配列を設定すると、キー集合を明示的に定義できます。たとえば、カテゴリーラベルCのキー集合をX、Y、Zにするには以下のように書きます。

{ "C": {"keys": ["X", "Y", "Z"]} }

C をキー集合にしている辞書型プレースホルダーは、keysに渡された各文字列に対して値を生成します。範囲指定はスカラーと配列型と同じで、defaultを渡すか、各プレースホルダーのoptionsで指定することができます。

生成したいインスタンスにとってカテゴリーラベルで実際使われる文字列がなんでもよいという場合、カテゴリーラベルのオプションにkeysではなく、sizeをすることも可能です(固定値も範囲指定も可)。その場合、その数だけの文字列が適当に生成されます。

PartialDict含め、デフォルトで全辞書型プレースホルダーで全域に対して値を生成するということになっています。PartialDictのキーを制限したいときは、そのプレースホルダーのオプションに追加でkeyssizeを渡す必要があります。 たとえば、Cがキー集合になっている D プレースホルダー(PartialDict)がある場合は次のように書きます。

{ "C": {"keys": ["X", "Y", "Z"]}, "D": {"keys": ["X", "Y"], "value": range(10, 100)} } }

上記はカテゴリーラベルにkeysを設定している時のみ使える書き方で、"D"keysがキー集合の部分集合になっていないとエラーになるのでご注意ください。

一方、プレースホルダーのオプションにsizeを使うことで、キー集合の中からその数だけ任意に選ばれます。この方法だとカテゴリーラベルがどのように定義されても構いません。

# キーが1個か2個選ばれる場合
{ "C": {"keys": ["X", "Y", "Z"]}, "D": {"size": range(1, 3), "value": range(10, 100)} } } 
# キーが1個から4個選ばれる場合
{ "C": {"size": range(3, 10)}, "D": {"size": range(1, 5), "value": range(10, 100)} } }