[英]How do I iterate between 32 binary options?
我有一個優化問題,其中有5個變量:A,B1,B2,C1,C2。 我正在嘗試優化這5個變量,以獲得我能得到的最小平方根值。 我有一些可以正常工作的優化技術,但是特別是這給我帶來了一些麻煩。
我想探索更改變量的所有32個選項,並選擇最小的RSS值。 我的意思是每個變量可以是+/-增量。 每個選擇都會導致2個選擇,其中5個變量就是32個選擇。 (2 ^ 5)
為了澄清起見,我沒有將變量A,B1,B2等相互添加,而是將它們遞增/遞減任意數量。 A +/- X,B1 +/- X2等。我試圖弄清楚我的5個變量的遞增/遞減組合將返回最低的平方根值。
A
+/ \-
B1 B1
+/\- +/\-
B2 B2 B2 B2
依此類推,直到完成所有5個級別。 我什至不知道從哪里開始嘗試解決這個問題。 哪種數據結構最適合存儲此數據? 它是迭代的還是遞歸的解決方案。 我不需要解決問題的答案,而是需要尋找或開始的地方。 再次感謝您抽出寶貴時間研究此問題。
為了澄清進一步的混亂,這是我的優化方法。 我有5個變量和5個增量,每個增量都與一個變量匹配。 (a,b,c,d,e,f)--->(incA,incB,incC,indD,incE,incF)
我想找到+/- incX到X的最佳組合(x是5個變量之一),即:解決方案可能類似於:a + incA,B-incB,c + incC,d + incD,e + INCE,f-incF。 閱讀完以下所有答案后,有32種組合的可能性,我確定了這種可能的算法。 (請參見下面的答案)根據需要進行編輯和提問。 這不是一個完美的算法,它是為了澄清和易於理解,我知道可以將其簡化。
//Settign all possible solutions to be iterated through later.
double[] levelA = new double[2];
double[] levelB = new double[2];
double[] levelC = new double[2];
double[] levelD = new double[2];
double[] levelE = new double[2];
levelA[0] = a + incA;
levelA[1] = a - incA;
levelB[0] = b + incB;
levelB[1] = b - incB;
levelC[0] = c + incC;
levelC[1] = c - incC;
levelD[0] = d + incD;
levelD[1] = d - incD;
levelE[0] = e + incE;
levelE[1] = e - incE;
double[] rootSumAnswers = new double[32];
int count = 0;
for(int i = 0; i < 2; i ++)
{
for(int k = 0; k < 2; k++)
{
for(int j = 0; j < 2; j ++)
{
for(int n = 0; n < 2; n++)
{
for(int m = 0; m < 2; m++)
{
rootSumAnswers[count++] = calcRootSum(levelA[i], levelB[k], levelC[j], levelD[n], levelE[m]);
}
}
}
}
}
//Finally, i find the minimum of my root sum squares and make that change permanent, and do this again.
您可以使用以下函數生成一個包含所有可能的操作集(例如{-,-,-,-},{-,-,-,+},{-,-,+,-}等)的集合。將輸出布爾數組的列表,其中true和false表示+和-。 vars
參數指示要組合的變量數。 請注意,對於5個變量,只有16個組合(您聲明的不是32個),因為在組合5個變量時(假設第一個變量不能取反)只有4個運算符:
public List<bool[]> GetOperators(int vars)
{
var result = new List<bool[]>();
for (var i = 0; i < 1 << vars-1; i++)
{
var item = new bool[vars - 1];
for (var j = 0; j < vars-1; j++)
{
item[j] = ((i >> j) & 1) != 0;
}
result.Add(item);
}
return result;
}
有了此列表后,就可以使用它以所有可能的方式組合變量。 首先,我們定義一個輔助函數,以接受一組變量和單個bool[]
組合並將其應用(假定組合中存在正確數量的元素以用於傳遞的變量數):
private double Combine(double[] vars, bool[] combination)
{
var sum = vars[0];
for (var i = 1; i< vars.Length; i++)
{
sum = combination[i - 1] ? sum + vars[i] : sum - vars[i];
}
return sum;
}
您還可以使用以下方法很好地格式化組合:
private string FormatCombination(double[] vars, bool[] combination)
{
var result = vars[0].ToString("0.00##");
for (var i = 1; i < vars.Length; i++)
{
result = string.Format("{0} {1} {2:0.00##}", result, combination[i - 1] ? "+" : "-", vars[i]);
}
return result;
}
將它們放在一起以測試所有可能的組合:
var vars = new []
{
1.23, // A
0.02, // B1
0.11, // B2
0.05, // C1
1.26 // C2
};
foreach (var combination in GetOperators(vars.Length))
{
var combined = Combine(vars, combination);
// Perform your operations on "combined" here...
Console.WriteLine("{0} = {1}", FormatCombination(vars, combination), combined);
}
這將輸出:
1.23 - 0.02 - 0.11 - 0.05 - 1.26 = -0.21 1.23 + 0.02 - 0.11 - 0.05 - 1.26 = -0.17 1.23 - 0.02 + 0.11 - 0.05 - 1.26 = 0.01 1.23 + 0.02 + 0.11 - 0.05 - 1.26 = 0.05 1.23 - 0.02 - 0.11 + 0.05 - 1.26 = -0.11 1.23 + 0.02 - 0.11 + 0.05 - 1.26 = -0.07 1.23 - 0.02 + 0.11 + 0.05 - 1.26 = 0.11 1.23 + 0.02 + 0.11 + 0.05 - 1.26 = 0.15 1.23 - 0.02 - 0.11 - 0.05 + 1.26 = 2.31 1.23 + 0.02 - 0.11 - 0.05 + 1.26 = 2.35 1.23 - 0.02 + 0.11 - 0.05 + 1.26 = 2.53 1.23 + 0.02 + 0.11 - 0.05 + 1.26 = 2.57 1.23 - 0.02 - 0.11 + 0.05 + 1.26 = 2.41 1.23 + 0.02 - 0.11 + 0.05 + 1.26 = 2.45 1.23 - 0.02 + 0.11 + 0.05 + 1.26 = 2.63 1.23 + 0.02 + 0.11 + 0.05 + 1.26 = 2.67
編輯:
根據您對問題的更改,我已經更新了答案。 正如其他人提到的那樣,可能不必使用諸如此類的完整搜索,但是無論如何,您可能會發現該方法很有用。
GetOperators()
略有變化,以返回2 n個組合(而不是像以前一樣2 n-1 ):
public List<bool[]> GetOperators(int vars)
{
var result = new List<bool[]>();
for (var i = 0; i < 1 << vars; i++)
{
var item = new bool[vars];
for (var j = 0; j < vars; j++)
{
item[j] = ((i >> j) & 1) != 0;
}
result.Add(item);
}
return result;
}
除要使用的變量和組合外, Combine()
方法已更改為采用一組增量。 對於組合的每個元素,如果為true
,則將增量添加到變量,如果為false,則將其減去:
private double[] Combine(double[] vars, double[] increments, bool[] combination)
{
// Assuming here that vars, increments and combination all have the same number of elements
var result = new double[vars.Length];
for (var i = 0; i < vars.Length; i++)
{
result[i] = combination[i] ? vars[i] + increments[i] : vars[i] - increments[i];
}
// Returns an array of the vars and increments combined per the given combination
// E.g. if there are 5 vars and the combination is: {true, false, true, true, false}
// The result will be {var1 + inc1, var2 - inc2, var3 + inc3, var4 + inc4, var 5 - inc5}
return result;
}
並且FormatCombination()
也已更新以顯示新的組合樣式:
private string FormatCombination(double[] vars, double[] increments, bool[] combination)
{
var result = new List<string>(vars.Length);
var combined = Combine(vars, increments, combination);
for (var i = 0; i < vars.Length; i++)
{
result.Add(string.Format("{0:0.00##} {1} {2:0.00##} = {3:0.00##}", vars[i], combination[i] ? "+" : "-", increments[i], combined[i]));
}
return string.Join(", ", result.ToArray());
}
放在一起:
var vars = new[]
{
1.23, // A
0.02, // B
0.11, // C
0.05, // D
1.26, // E
};
var increments = new[]
{
0.04, // incA
0.11, // incB
0.01, // incC
0.37, // incD
0.85, // incD
};
foreach (var combination in GetOperators(vars.Length))
{
var combined = Combine(vars, increments, combination);
// Perform operation on combined here...
Console.WriteLine(FormatCombination(vars, increments, combination));
}
輸出(節略):
1.23 - 0.04 = 1.19, 0.02 - 0.11 = -0.09, 0.11 - 0.01 = 0.10, 0.05 - 0.37 = -0.32, 1.26 - 0.85 = 0.41 1.23 + 0.04 = 1.27, 0.02 - 0.11 = -0.09, 0.11 - 0.01 = 0.10, 0.05 - 0.37 = -0.32, 1.26 - 0.85 = 0.41 1.23 - 0.04 = 1.19, 0.02 + 0.11 = 0.13, 0.11 - 0.01 = 0.10, 0.05 - 0.37 = -0.32, 1.26 - 0.85 = 0.41 1.23 + 0.04 = 1.27, 0.02 + 0.11 = 0.13, 0.11 - 0.01 = 0.10, 0.05 - 0.37 = -0.32, 1.26 - 0.85 = 0.41 ...
您根本不需要在這里進行任何搜索。 對於每個變量,請選擇A +/- incA
具有最小絕對值的符號(+或-)(換句話說,該符號會產生A +/- inc A
的最小平方 )。 這最小化了平方和中的所有項,因此最小化了整個和。
假設您要通過選擇合適的符號+
或-
來最小化sqrt(A*A +/- B1*B1 +/- B2*B2 +/- C1*C1 +/- C2*C2)
,只需對32個可能的符號進行迭代組合,然后選擇產生最小結果的組合。 要遍歷所有組合,請注意,如果遍歷整數0到31,並查看它們的二進制表示形式,則會發現5個零和一的所有組合。 進一步觀察,您可以發現特定整數n在二進制數字k處是否包含1,您只需要檢查n的按位與和2的冪次冪是否非零。 因此,使用偽代碼
min_sign_a = 1
min_sign_b1 = 1
min_sign_b2 = 1
min_sign_c1 = 1
min_sign_c2 = 1
min_sum = a*a + b1*b1 + b2*b2 + c1*c1 + c2*c2
for(i in 1,...,31)
sign_a = (i & 1) ? -1 : 1
sign_b1 = (i & 2) ? -1 : 1
sign_b2 = (i & 4) ? -1 : 1
sign_c1 = (i & 8) ? -1 : 1
sign_c2 = (i & 16) ? -1 : 1
sum = sign_a*a*a + sign_b1*b1*b1 + sign_b2*b2*b2 + sign_c1*c1*c1 + sign_c2*c2*c2
if (sum < min_sum)
min_sign_a = sign_a
min_sign_b1 = sign_b1
min_sign_b2 = sign_b2
min_sign_c1 = sign_c1
min_sign_c2 = sign_c2
end
end
在這里,“&”代表按位與。 i的第k個位中的一個給第k個變量一個負號,一個零給它一個正號。 情況“ i = 0”,即所有符號在循環開始之前都為正,以避免必須處理循環的第一次運行中未初始化的min_sum
。
由於我的第一個答案已被刪除,因為它是偽代碼而不是c#。所以我將抽象集更改為堆棧...
我認為一種不錯的遞歸方法有效:
int FindBest(int increment, Stack<int> decided_vars, Stack<int> free_vars)
{
if free_vars.size() == 0
{
return PerformCalculation(decided_vars);
}
int new_element = free_vars.Pop()
new_element += increment;
//check one case
decided_vars.Push(new_element)
int result1 = FindBest(increment, decided_vars, free_vars)
//reset sets for second case
decided_vars.Pop();
new_element -= 2 * increment;
decicded_vars.Push(new_element);
int result2 = FindBest(increment, decided_vars, free_vars);
//reset sets as they were to give back to parent
decided_vars.Pop()
free_vars.Push( new_element + increment )
return max(result1, result2);
}
您可以將PerformCalculation定義為要最大化/最小化的函數。
優化多個參數的一個好方法是Nelder-Mead方法或Downhill Simplex方法 。 它以一種非常自然的方式“遍歷”參數空間,而無需測試每個排列。 另請參見下坡單純形法 (帶有很好的圖形顯示原理)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.