黃金點第四次部落格
1 上週回顧:
在已有圖形介面的基礎上,我們通過前端後端的綜合運用,改變了以前遊戲資料的 “即用即存” 的形式,而是將玩家的歷史選擇、歷史得分、歷史黃金點都放入了本地資料庫中,需要時直接呼叫,結合前端 html,js 等方法渲染視覺化分析結果(折線圖、箱型圖、柱形圖等)。但我們也發現了程式執行時還存在一點 bug ,在後續開發過程中我們會逐漸完善。
2 本週內容介紹
本週針對以下幾個方面進行優化、開發:
- 將每次使用者的輸入改為 2 個數,使用者的最終參與黃金點判定時的選擇為兩個輸入資料的平均數;
- 針對 bug 的優化
- 繼續針對需求建立預測模組(採用強化學習 Q-Learning 模型)並逐步實現
3 本週工作成果展示
3.1 AI 預測部分
考慮到程式運算執行的時間,在這裡我們只設置 7 種狀態,分別為第 4 局 ~ 第10局。由於在之前的部落格中已經寫到,如果都是 “聰明” 的玩家,那麼第 10 局之後的黃金點將會在 0.5 ~ 3 之間波動,此時預測的效果將不顯著,因此想要贏得最後累計最多的分數,在第 1 ~ 10 局將會是最好的機會。
① 設定一個一維陣列全域性變數 action, 每輪更新:
static double[] action = new double[]{ 前一輪的黃金點, 前兩輪黃金點的均值, 前三輪黃金點的均值, 前十輪中每隔1 輪進行取樣後的黃金點的均值, 前十輪中每隔 2 輪進行取樣後的黃金點的均值, 前十輪中最大的三個的GN的均值, 前十輪中最小的三個的GN的均值, };
action[0] = gold_point[i - 1]; action[1] = (gold_point[i - 1] + gold_point[i - 2]) / 2; action[2] = (gold_point[i - 1] + gold_point[i - 2] + gold_point[i - 3]) / 3; // 第四個動作:當前前10輪每隔1輪取黃金點均值 int temp = 0, temp_count = 0;for(int j = i; j >= 0; j -= 2) { temp += a[j], temp_count++; if(temp_count > 10) break; } action[3] = temp / temp_count; // 第五個動作:當前前10輪每隔2輪取黃金點均值 temp = 0, temp_count = 0; for(int j = i; j >= 0; j -= 3) { temp += a[j], temp_count++; if(temp_count > 10) break; } action[4] = temp / temp_count; // 用一個臨時陣列temp_gold_point[]把當前前10局的黃金點存入 quickSort(temp_gold_point, 0, n - 1); action[5] = (temp_gold_point[n - 1] + temp_gold_point[n - 2] + temp_gold_point[n - 3]) / 3; action[6] = (temp_gold_point[0] + temp_gold_point[1] + temp_gold_point[2]) / 3;
// 其中的快速排序演算法 public static void quickSort(double a[], int low, int high){ if(low >= high) return; double temp = a[low + high >> 1], l = low - 1, r = high + 1; while(l < r){ do i++; while(a[l] < temp); do r--; while(a[r] > temp); if(l < r) swap(a[l], a[r]); } quickSort(a, low, r), quickSort(a, r + 1, high); }
②初始化 Q 表
double[][] Q = new double[][] {
{0, 0, 0, 0, 0, 0, 0},
{-1, -1, -1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1, -1, -1}
};
這裡解釋一下我們所理解的 state 狀態:我們認為,每一個狀態就是結合當前所在輪的所知的所有資訊基礎上的狀態,假定從第 4 - 10 輪開始,共 7 輪,之後每七輪重新定義一次狀態。當前的 “預測” 功能可以基於當前狀態,在每一個狀態都可以最期望的分數。比如從第4輪開始預測,那第一行的 7 個動作都為 0,代表此時七個動作都可以選擇;第 2 ~ 7 行此時為 -1,代表當前還沒有到後面幾輪,所以 Q 表值為 -1,此時不能選擇。後面會根據當前的輪數重新設定 Q 表。
③設定獎勵
int reward = abs(action[i] - (gold_point[i - 1] + gold_point[i - 2]) / 2); // 獎勵 = 每個動作的選擇數所減去上兩輪黃金點的平均值的絕對值
④完整模組程式碼
package homework; import java.util.Arrays; import static com.sun.tools.javac.jvm.ByteCodes.swap; import static java.lang.Math.*; public class QLearning { /* 每一局遊戲都要更新,全域性變數 double[] possible_choice = new double[]{ 前一輪的黃金點, 前兩輪黃金點的均值, 前三輪黃金點的均值, 前十輪中每隔 1 輪進行取樣後的黃金點的均值, 前十輪中每隔 2 輪進行取樣後的黃金點的均值, 前十輪中最大的三個的GN的均值, 前十輪中最小的三個的GN的均值, }; */ public static void main(String[] args) { double[][] Q = new double[][] { {0, 0, 0, 0, 0, 0, 0}, {-1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1, -1, -1} }; double epsilon = 0.8; double alpha = 0.2; double gamma = 0.8; int N = 10; int MAX_EPISODES = 400; // 一般都通過設定最大迭代次數來控制訓練輪數 action[0] = gold_point[i - 1]; action[1] = (gold_point[i - 1] + gold_point[i - 2]) / 2; action[2] = (gold_point[i - 1] + gold_point[i - 2] + gold_point[i - 3]) / 3; // 第四個動作:當前前10輪每隔1輪取黃金點均值 int temp = 0, temp_count = 0; for(int j = i; j >= 0; j -= 2) { temp += a[j], temp_count++; if(temp_count > 10) break; } action[3] = temp / temp_count; // 第五個動作:當前前10輪每隔2輪取黃金點均值 temp = 0, temp_count = 0; for(int j = i; j >= 0; j -= 3) { temp += a[j], temp_count++; if(temp_count > 10) break; } action[4] = temp / temp_count; // 用一個臨時陣列temp_gold_point[]把當前前10局的黃金點存入 quickSort(temp_gold_point, 0, n - 1); action[5] = (temp_gold_point[n - 1] + temp_gold_point[n - 2] + temp_gold_point[n - 3]) / 3; action[6] = (temp_gold_point[0] + temp_gold_point[1] + temp_gold_point[2]) / 3; for(int episode = 0; episode < MAX_EPISODES; ++episode) { System.out.println("第"+episode+"輪訓練..."); int index = 0; while(index <= 9) { // 到達目標狀態,結束迴圈,進行下一輪訓練 int next; if(Math.random() < epsilon) next = max(Q[index]); // 通過 Q 表選擇動作 else next = randomNext(Q[index]); // 隨機選擇可行動作 /*if(index == 0) next = Q[index][0]; if(index == 1){ if(Math.random() < epsilon) next = max(Q[index][0], Q[index][1]); else next = randomNext(Q[index]); }*/ // 每一輪更新所有action對應的值 int reward = abs(action[i] - pre_round_gp); // 獎勵 = 每個動作所減去 Q[index][next] = (1-alpha)*Q[index][next] + alpha*(reward+gamma*maxNextQ(Q[next])); index = next; // 更新狀態 } } System.out.println(Arrays.deepToString(Q)); } private static int randomNext(double[] is) { // 蓄水池抽樣,等概率選擇流式資料 int next = 0, n = 1; for(int i = 0; i < is.length; ++i) { if(is[i] >= 0 && Math.random() < 1.0/n++) next = i; } return next; } private static int max(double[] is) { int max = 0; for(int i = 1; i < is.length; ++i) { if(is[i] > is[max]) max = i; } return max; } private static double maxNextQ(double[] is) { double max = is[0]; for(int i = 1; i < is.length; ++i) { if(is[i] > max) max = is[i]; } return max; } // 快速排序 public static void quickSort(double a[], int low, int high){ if(low >= high) return; double temp = a[(low + high) >> 1], l = low - 1, r = high + 1; while(l < r){ do l++; while(a[l] < temp); do r--; while(a[r] > temp); if(l < r) swap(a[l], a[r]); } quickSort(a, low, r), quickSort(a, r + 1, high); } }
⑤選擇操作
在訓練出較為穩定的程式碼後,我們選擇當前所在行的七個值中最大的兩個數作為輸入。看一個例子(這裡我們還沒有加入到整個遊戲中,還在改進模型中:)
假設有一個最短路問題:
那麼,獎勵 reward 和 Q 表可以設定成:
訓練過程中:
那麼,最後的路徑選擇就是 A -> B(4.56) -> D(3.20),問題解決。我們設想的黃金點思路也是這樣。不過我們是 java 語言開發,而且經過不少的資料查閱,用神經網路訓練出的 Q-Learning(DNQ)效果並不會顯著強於普通 Q-Learning 方法,所以這裡為了簡便便暫時採用該方法。
3.2 增加成立兩個輸入
4 總結
下一步我們將繼續優化預測模型,並且開始準備收尾工作:測試並修改 bug,美化軟體介面,使視覺化結果更加清晰直觀,並根據黃金點歷史生成遊戲的文字總結。