3

私が抱えている問題は、null モデル作成のためのスワップ アルゴリズムを実装するために、マトリックス内のスワップ可能な要素を見つける効率的な方法を見つけようとすることです。

マトリックスは 0 と 1 で構成され、マトリックスの行と列の合計が同じままになるように要素を列間で切り替えることができるという考え方です。

たとえば、次のマトリックスがあるとします。

   c1 c2 c3 c4
r1  0  1  0  0 = 1
r2  1  0  0  1 = 2
r3  0  0  0  0 = 0
r4  1  1  1  1 = 4
   ------------
    2  2  1  2

r1 と r2 の列 c2 と c4 はそれぞれ、合計が変更されないように交換できます。

   c1 c2 c3 c4
r1  0  0  0  1 = 1
r2  1  1  0  0 = 2
r3  0  0  0  0 = 0
r4  1  1  1  1 = 4
   ------------
    2  2  1  2

偏りが生じないように、これはすべてランダムに行う必要があります。

うまくいく解決策が1つあります。行と 2 つの列をランダムに選択します。それらが 10 または 01 のパターンを生成する場合、別の行をランダムに選択し、同じ列をチェックして、逆のパターンを生成するかどうかを確認します。どちらかが失敗した場合は、最初からやり直して新しい要素を選択します。

この方法は機能しますが、正しいパターンに「ヒット」するのは約 10% の確率です。大規模なマトリックスまたは行に 1 がほとんどないマトリックスでは、「行方不明」に多くの時間を無駄にします。マトリックス内の要素を選択するよりインテリジェントな方法が必要であると考えましたが、それでもランダムに行います。

作業メソッドのコードは次のとおりです。

def isSwappable(matrix: Matrix): Tuple2[Tuple2[Int, Int], Tuple2[Int, Int]] = {
  val indices = getRowAndColIndices(matrix)

  (matrix(indices._1._1)(indices._2._1), matrix(indices._1._1)(indices._2._2)) match {
    case (1, 0) => {
      if (matrix(indices._1._2)(indices._2._1) == 0 & matrix(indices._1._2)(indices._2._2) == 1) {
        indices
      }
      else {
        isSwappable(matrix)
      }
    }
    case (0, 1) => {
      if (matrix(indices._1._2)(indices._2._1) == 1 & matrix(indices._1._2)(indices._2._2) == 0) {
        indices
      }
      else {
        isSwappable(matrix)
      }
    }
    case _ => {
      isSwappable(matrix)
    }
  }
}

def getRowAndColIndices(matrix: Matrix): Tuple2[Tuple2[Int, Int], Tuple2[Int, Int]] = {
  (getNextIndex(rnd.nextInt(matrix.size), matrix.size), getNextIndex(rnd.nextInt(matrix(0).size), matrix(0).size))
}

def getNextIndex(i: Int, constraint: Int): Tuple2[Int, Int] = {
  val newIndex = rnd.nextInt(constraint)
  newIndex match {
    case `i` => getNextIndex(i, constraint)
    case _ => (i, newIndex)
  }
}

これを処理するより効率的な方法は、使用できない行 (すべて 1 または 0) を削除してから、要素をランダムに選択することだと考えました。そこから、同じ値を持つ行の列を除外し、残りの列から選択することができました。

最初の行と列が選択されたら、必要なパターンを提供できない行を除外し、残りの行から選択します。

これはほとんどの場合機能しますが、対処方法がわからない問題は、選択できる列または行がない場合にどうなるかということです。必要なパターンを見つけようとして無限にループしたくありません。選択する行または列の空のリストを取得した場合、最初からやり直す方法が必要です。

これまでのところ、そのような作業を行っているコード (空のリストを取得するまで) は次のとおりです。

def getInformativeRowIndices(matrix: Matrix) = (
  matrix
    .zipWithIndex
    .filter(_._1.distinct.size > 1)
    .map(_._2)
    .toList
  )

def getRowsWithOppositeValueInColumn(col: Int, value: Int, matrix: Matrix) = (
  matrix
    .zipWithIndex
    .filter(_._1(col) != value)
    .map(_._2)
    .toList
  )

def getColsWithOppositeValueInSameRow(row: Int, value: Int, matrix: Matrix) = (
  matrix(row)
    .zipWithIndex
    .filter(_._1 != value)
    .map(_._2)
    .toList
  )

def process(matrix: Matrix): Tuple2[Tuple2[Int, Int], Tuple2[Int, Int]] = {
  val row1Indices = getInformativeRowIndices(matrix)
  if (row1Indices.isEmpty) sys.error("No informative rows")

  val row1 = row1Indices(rnd.nextInt(row1Indices.size))
  val col1 = rnd.nextInt(matrix(0).size)
  val colIndices = getColsWithOppositeValueInSameRow(row1, matrix(row1)(col1), matrix)
  if (colIndices.isEmpty) process(matrix)
  val col2 = colIndices(rnd.nextInt(colIndices.size))
  val row2Indices = getRowsWithOppositeValueInColumn(col1, matrix(row1)(col1), matrix)
    .intersect(getRowsWithOppositeValueInColumn(col2, matrix(row1)(col2), matrix))
  println(row2Indices)
  if (row2Indices.isEmpty) process(matrix)

  val row2 = row2Indices(rnd.nextInt(row2Indices.size))
  ((row1, row2), (col1, col2))
}

再帰的な方法は間違っていて、ここでは実際には機能しないと思います。また、私は本当にセル選択の速度を改善しようとしているので、アイデアや提案は大歓迎です.

編集:

私はこれでもう少し遊ぶ機会があり、別の解決策を考え出しましたが、マトリックス内のセルをランダムに選択するよりもはるかに高速ではないようです。また、ランダム化されていると見なすには、行列を約 30000 回連続して交換する必要があり、テストごとに 5000 個のランダム行列を生成する必要があることを付け加えておく必要があります。の重要な。

現在の解決策(ランダムなセル選択以外は次のとおりです。

  1. 行列からランダムに 2 行を選択します
  2. ある行を別の行から減算し、それを配列に入れます
  3. 新しい配列に 1 と -1 の両方が含まれている場合は、スワップできます

減算のロジックは次のようになります。

  0  1  0  0
- 1  0  0  1
---------------
 -1  1  0 -1

これを行うメソッドは次のようになります。

 def findSwaps(matrix: Matrix, iterations: Int): Boolean = {
   var result = false

   val mtxLength = matrix.length

   val row1 = rnd.nextInt(mtxLength)
   val row2 = getNextIndex(row1, mtxLength)

   val difference = subRows(matrix(row1), matrix(row2))

   if (difference.min == -1 & difference.max == 1) {
     val zeroOne = difference.zipWithIndex.filter(_._1 == -1).map(_._2)
     val oneZero = difference.zipWithIndex.filter(_._1 == 1).map(_._2)

     val col1 = zeroOne(rnd.nextInt(zeroOne.length))
     val col2 = oneZero(rnd.nextInt(oneZero.length))

     swap(matrix, row1, row2, col1, col2)
     result = true
   }
   result
 }

行列の行減算は次のようになります。

 def subRows(a: Array[Int], b: Array[Int]): Array[Int] = (a, b).zipped.map(_ - _)

実際のスワップは次のようになります。

 def swap(matrix: Matrix, row1: Int, row2: Int, col1: Int, col2: Int) = {

   val temp = (matrix(row1)(col1), matrix(row1)(col2))
   matrix(row1)(col1) = matrix(row2)(col1)
   matrix(row1)(col2) = matrix(row2)(col2)

   matrix(row2)(col1) = temp._1
   matrix(row2)(col2) = temp._2
   matrix
 }

これは、試行されたスワップに対して80%から90%の成功を収めているという点で、以前よりもはるかにうまく機能します(ランダムなセル選択では約10%しかありませんでした)... ランダム化された1000を生成するのにまだ約2.5分かかります行列。

速度を改善する方法についてのアイデアはありますか?

4

1 に答える 1

1

行列が大きいため、(行列サイズの 2 乗) のオーダーのストレージは (速度またはメモリのいずれかの理由で) 実行できないと仮定します。

疎行列がある場合は、セット内の各列に各 1 のインデックスを入力できます (ここではコンパクトな方法を示しますが、速度を上げるために while ループを使用して反復することをお勧めします)。

val mtx = Array(Array(0,1,0,0),Array(1,0,0,1),Array(0,0,0,0),Array(1,1,1,1))
val cols = mtx.transpose.map(x => x.zipWithIndex.filter(_._1==1).map(_._2).toSet)

次の 2 つのセットのみが空でない場合に限り、各列について、後の列に互換性のあるペア (少なくとも 1 つ) が含まれるようになりました。

def xorish(a: Set[Int], b: Set[Int]) = (a--b, b--a)

したがって、答えには、これらのセットを計算し、両方が空でないかどうかをテストすることが含まれます。

問題は、「ランダムにサンプリングする」とはどういう意味かということです。単一の1,0 ペアをランダムにサンプリングすることは、可能なスワップをランダムにサンプリングすることと同じではありませ。これを確認するには、次のことを考慮してください。

1 0       1 0
1 0       1 0
1 0       1 0
0 1       1 0
0 1       1 0
0 1       0 1

左側の 2 つの列には、9 つ​​の可能なスワップがあります。右側の 2 つは、5 つの可能なスワップしかありません。しかし、(1,0) パターンを探している場合、左側では 3 回しかサンプリングしないのに対し、右側では 5 回サンプリングします。(1,0) または (0,1) のいずれかを探している場合は、6 と 6 をサンプリングすることになり、これも確率を歪めます。これを修正する唯一の方法は、賢くならず、2 回目のランダムなサンプリングを行うことです (最初のケースでは、3/5 の確率で使用可能なスワップでうまくいきますが、2 番目のケースでは 1/5 だけです)、または基本的に、スワップの可能なすべてのペア (または少なくともペアの数) を計算し、その事前定義されたセットから選択します。

後者を実行したい場合は、同一でない列の各ペアについて、交換する 2 つのセットを計算でき、サイズと積が可能性の総数であることがわかります。すべての可能性をインスタンス化することを避けるために、作成できます

val poss = {
  for (i<-cols.indices; j <- (i+1) until cols.length) yield 
    (i, j, (cols(i)--cols(j)).toArray, (cols(j)--cols(i)).toArray)
}.filter{ case (_,_,a,b) => a.length>0 && b.length>0 }

次に、いくつあるか数えます。

val cuml = poss.map{ case (_,_,a,b) => a.size*b.size }.scanLeft(0)(_ + _).toArray

ランダムに数値を選択するために、0 から cuml.last までの数値を選択し、これがどのバケットで、バケット内のどのアイテムであるかを選択します。

def pickItem(cuml: Array[Int], poss: Seq[(Int,Int,Array[Int],Array[Int])]) = {
  val n = util.Random.nextInt(cuml.last)
  val k = {
    val i = java.util.Arrays.binarySearch(cuml,n)
    if (i<0) -i-2 else i
  }
  val j = n - cuml(k)
  val bucket = poss(k)
  (
    bucket._1, bucket._2, 
    bucket._3(j % bucket._3.size), bucket._4(j / bucket._3.size)
  )
}

(c1,c2,r1,r2)これにより、ランダムに選択されたものが返されます。

座標を取得したので、必要に応じて新しいマトリックスを作成できます。(最も効率的なのは、おそらくエントリのインプレース スワップを実行し、再試行するときに元に戻すことです。)

これは、同じ開始行列からの多数の独立したスワップに対してのみ有効であることに注意してください。代わりにこれを繰り返し実行して独立性を維持したい場合は、行列が非常に疎でない限り、最終的にランダムに行うのがおそらく最善です。その時点で、行列を標準の疎行列形式で格納するだけの価値がありますエントリ) およびそれらの操作を実行します (単一のスワップの結果はマトリックスn内のエントリの約数に限定されるため、おそらく変更可能なセットと更新戦略を使用します)。n*n

于 2012-07-16T20:12:14.293 に答える