強化學習Q learning算法最簡單的入門(含java實現的小例子)

一、強化學習

強化學習和遺傳算法優勝劣汰的思想類似,通過獎懲機制不斷強化好的行爲(action),弱化壞的行爲。至於什麼是好的行爲,什麼是壞的行爲,跟你要解決的具體問題有關,比如路徑規劃問題,走距離目標點較近的路線就是好的行爲,走距離目標點較遠的路線就是壞的行爲。

強化學習包含四要素:agent、環境狀態、動作和獎勵,目標是獲得最多的累計獎勵。

二、Q learning

Q learning 融合了馬爾科夫過程和動態規劃,使用貝爾曼方程求解馬爾科夫過程。今天我們不展開講馬爾科夫過程和貝爾曼方程,因爲那樣入門起來就不簡單了,今天僅從算法過程的角度理解併入門,馬爾科夫過程和貝爾曼方程是入門後再去深入研究的原理性部分。

Q learning 最重要的數據結構爲 Q 表,Q 是 quality 的縮寫。算法最終就是要學習到一張好的 Q 表,這樣我們就可以根據 Q 表對環境中的任何情況(狀態)都能給出一個好的反應(動作)。具體的,就是每次都選擇 Q 表中對應狀態下具有最大 Q 值的動作。

圖1
Q 表一般用二維數組表示,每一行表示一個狀態,每一列表示一個動作。 例如上圖所示的基於移動的二維遊戲,每個狀態(方塊)有四個動作:左移、右移、上移、下移。

q表
0 代表不能向對應方向移動的地形邊緣。

算法流程圖:
算法流程
僞碼描述:

初始化Q表
while Q表未收斂:
	初始化狀態s,開始新一輪訓練
	while s != 目標狀態
		使用策略(E-greedy)獲得動作a
		執行動作轉換到下一個狀態s1,並獲得獎勵r(s,a)
		Q[s,a] = (1-alpha)*Q[s,a]+alpha*(r(s,a)+gamma*max(Q[s1])) // update Q
		s = s1

後面我們會有一個和這個僞碼描述完全一致的 java 實現,可以對比理解。

看完僞碼描述後,我們的腦海裏可能會有兩個迫切想要了解的點。

關鍵點一:更新公式如何理解?

更新公式
獎勵由兩部分組成:

  1. 眼前的獎勵;
  2. 未來的獎勵,衰減因子 γ(0,1)\gamma\in(0,1) 表示對未來獎勵的重視程度

新的 Q 值是舊的 Q 值和獎勵的凸組合,換句話說就是新的 Q 值一定介於舊的 Q 值和獎勵之間。

因爲獎勵中包含未來的獎勵,所以如果未來收益很小,說明照此發展下去,未來不容樂觀,Q 值就會變小,下次在狀態 s 下選擇動作 a 的可能性就會變小。

相反的,如果未來收益很大,Q 值就會變大,下次在狀態 s 下選擇動作 a 的可能性就會變大。

關鍵點二:使用策略(ϵ\epsilon-greedy)獲得動作,使用什麼策略?爲什麼?

Q 表訓練完成後,我們可以每次都選擇 Q 表中對應狀態下具有最大 Q 值的動作作爲決策。但是在訓練過程中,這樣選擇動作是否合理呢?

答案是不合理,爲什麼,因爲在初始階段,Q 表中大部分的值還沒更新,普遍比較小。一旦找到一個可以到達目標狀態的策略(往往不是最優的),那麼以後每次按照最大值決策,就都會使用那一個策略,因爲其它 Q 值都比較小,沒有機會被選擇到。所以在訓練過程中過於貪婪(greedy)的使用 Q 表選擇動作,很容易陷入局部最優,並且跳不出來。

一般在訓練時,通過 epsilon(ϵ\epsilon)參數控制 agent 的貪婪程度,例如 epsilon = 0.9,表示 90% 的時間使用 Q 表做決策,10% 的時間隨機選擇動作來探索未知的環境。

三、java 實現的小例子

一個簡單的不能再簡單的小例子。

問題:最短路徑,尋找起始點A到目標點D的最短路徑。
圖
設計

package main;
import java.util.Arrays;
public class Qlearning {
	public static void main(String[] args) {
		double[][] Q = new double[][] {
			{-1,  0,  0, -1},
			{-1, -1, -1,  0},
			{-1, -1, -1,  0},
			{-1, -1, -1, -1}
		};
		
		int[][] graph = new int[][] {
			{0, 3, 2, 0},
			{0, 0, 0, 1},
			{0, 0, 0, 4},
			{0, 0, 0, 0}
		};
		
		double epsilon = 0.8;
		double alpha = 0.2;
		double gamma = 0.8;
		int MAX_EPISODES = 400; // 一般都通過設置最大迭代次數來控制訓練輪數
		for(int episode = 0; episode < MAX_EPISODES; ++episode) {
			System.out.println("第"+episode+"輪訓練...");
			int index = 0;
			while(index != 3) { // 到達目標狀態,結束循環,進行下一輪訓練
				int next;
				if(Math.random() < epsilon) next = max(Q[index]); // 通過 Q 表選擇動作
				else next = randomNext(Q[index]); // 隨機選擇可行動作
				
				int reward =5 - graph[index][next]; // 獎勵
				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;
	}

}

Q 表的更新過程:
更新過程
第三個 Q 表其實已經收斂。

Q(s,a)=(1α)Q(s,a)+α(R+γmaxQ(s))Q(s,a)=(1-\alpha)Q(s,a)+\alpha(R+\gamma\cdot \max Q(s^{'}))

(1-0.2)*4.56 + 0.2*(2+0.8*3.2)=4.56
(1-0.2)*3.16 + 0.2*(3+0.8*0.2)=3.16
(1-0.2)*3.2 + 0.2*(4-0.8)=3.2
(1-0.2)*0.2 + 0.2*(1-0.8)=0.2

對照着公式,通過手工計算可以看到所有的 Q 值已經趨於穩定,繼續迭代,Q 值也不會發生更新。

對比 Q 表的更新過程可以發現,剛開始時,還會選擇 A->C 這條路徑,因爲短期從 A->C 能夠獲得更多的獎勵,但是當接受到未來獎勵的反饋後,開始逐漸傾向於 A->B,並最終選擇 A-> B 路徑,因爲從 B->D 會獲得比從 C->D 大得多的獎勵。這也體現了強化學習延遲反饋的特徵。

參數設置對模型的影響:

  • epsilon 過大,模型不容易收斂。
  • alpha 過小,模型不容易收斂。
  • gamma 改變最終的收斂結果也會改變。(強化學習的目的就是根據累計收益做決策,如果 gamma 過小,把眼前利益看的太重,不容易得到最優解)

代碼中用到了蓄水池抽樣技術,可以等概率的選擇流式數據,如果對蓄水池抽樣感興趣可以看看這篇文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章