1. 程式人生 > 實用技巧 >黃金點第四次部落格

黃金點第四次部落格

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,美化軟體介面,使視覺化結果更加清晰直觀,並根據黃金點歷史生成遊戲的文字總結。