Q_Learning算法的一個簡單的應用。

想必很多人對Q_Learning比較熟悉。但是Q_Learning重在Learning。那麼Q_Learning以何種方式去Learning呢?我們可以想象一下一個正在森林中摸索前進的迷途小羔羊。周圍能見度很低,只能走一步看一步。它無法預知距離自己太遠的山路,但是它記憶力比較好,曾經走過的路,遇到過的環境它都記得。那麼這些“經驗”卻是小羔羊能夠不斷摸索前進的寶貴“知識”,羔羊遇到了與之前碰到的相似的環境或者一樣的環境的時候,它會選擇它的記憶中能夠使得自己順利通過的走法,因爲之前這樣走沒有什麼問題,那麼現在還是像這樣走,問題也許也不太大,雖然可能還會有更好的走法(這需要羔羊進一步的學習。)Q_Learning算法與之一樣,一個陌生的環境中,我們往往只關心我們所處的當前環境下要採取怎樣的行動,這個行動使得我們獲益最大而不考慮更遠的情況。

下面就一篇博客上提到的英文原文(Step-By-Step Tutorial 點擊打開鏈接)中提到的一個具體的場景爲例,來舉個Q_Learning算法的實際應用的例子。英文原文大家可以在網上直接下載,原文中只給出算法原型,以及部分分析過程,以及最後的收斂狀態。並沒有給出具體的實現程序。學過算法的人應該知道,能不能根據別人的算法編制出能夠解決實際問題的程序,在某種程度上能夠體現出子集對該算法的理解程度。所以對於比較熟悉Q_Learning算法的讀者來說,這篇博客純屬扯淡,但是對於剛剛入門,想要學習並且真正理解Q_Learning算法的人來說可能還有點用。我使用Q_Learning算法基本上實現了Step-By-Step Tutorial中提出的簡單場景中提煉出來的基本問題。java代碼以及運行結果如下:

package Q_Learning;
public class Q_Learning {
private int[][] R;//當前狀態轉移到下一狀態下的reward
private int[][] Q;//某個狀態下,採取某個動作之後可預見的將來的回報,評估當前狀態下動作選擇的好壞。
public int[][] getR() {
return R;
}
public void setR(int[][] r) {
R = r;
}
public int[][] getQ() {
return Q;
}
public void setQ(int[][] q) {
Q = q;
}
//獲取R值
public int GetCertainR(int Rx,int Ry){
return R[Rx][Ry];
}
//獲取Q值
public int GetCerTainQ(int Qx,int Qy){
return Q[Qx][Qy];
}
//更新Q值
public void UpdateQq(int Currentstate,int BestAction,int BestQ){
Q[Currentstate][BestAction]=R[Currentstate][BestAction]+(int)(0.8*BestQ);
}
//尋找最優Q值對應的動作,輸入的是當前狀態,尋找可以到達的下一個記憶狀態的最優值
public boolean ChooseTheBestAction(int Currentstate){
//看當前狀態下的未來最大回報對應的動作值
int next_avaliable_state[]=new int[R[0].length];//當前狀態的下個狀態,以及到達下一個狀態的動作。這裏有點特殊,需要注意下。
int MaxQ[]=new int[R[0].length];//當前狀態的下個狀態的的最優Q值
int BestNextState=0,BeatQ=0,ActionSequence=0;
int count=0;
int CRstate=Currentstate;
boolean EndLearning=false;
while(!EndLearning){
count=0;
for(int temp=0;temp<MaxQ.length;temp++){
MaxQ[temp]=-1;
next_avaliable_state[temp]=-1;
}
System.out.print("currentstate"+":");
System.out.println(Currentstate+"\t");
for(int the_next_avaliable_state=0;the_next_avaliable_state<R[0].length;the_next_avaliable_state++){
if(R[CRstate][the_next_avaliable_state]!=-1){//兩個狀態之間能夠進行轉移,也就是可選的動作。
//找到記憶中下一個狀態的最優Q值及其對應的下標
next_avaliable_state[count]=the_next_avaliable_state;//一個可達的狀態
System.out.print("nextstate"+":");
System.out.println(the_next_avaliable_state+"\t");
//該狀態下的最大Q值
for(int the_qvalue_index=0;the_qvalue_index<Q[0].length;the_qvalue_index++){
if(MaxQ[count]<Q[the_next_avaliable_state][the_qvalue_index]){
MaxQ[count]=Q[the_next_avaliable_state][the_qvalue_index];
}
}
count++;
}
}
//在所有的最優Q值中選出一個最優的Q值及其對應的動作
BeatQ=MaxQ[0];
for(int getthebestq_a=0;getthebestq_a<MaxQ.length;getthebestq_a++){
if(BeatQ<=MaxQ[getthebestq_a]){
BeatQ=MaxQ[getthebestq_a];
BestNextState=next_avaliable_state[getthebestq_a];
}
//把上面的for去掉,註釋的這兩行也能夠運行。只限於這個程序,其他情況下不一定行。原因:看運行結果。
//BeatQ=BeatQ<=MaxQ[getthebestq_a]?MaxQ[getthebestq_a]:BeatQ;
//BestNextState=BestNextState<=next_avaliable_state[getthebestq_a]?next_avaliable_state[getthebestq_a]:BestNextState;
}
System.out.print("BestNextState"+":");
System.out.println(BestNextState+"\t");
/**
* 如果當前的狀態下有很多可以供選擇的動作,這裏就在這些可以選擇的動作裏面
* 隨機的選擇一個動作執行,更新當前狀態下可轉移到的狀態的動作的Q值,這裏需要注意一下,如果我們訓練的次數足夠多,那麼
* 總會把當前狀態可達狀態下的所偶遇Q值全部都更新一下。
* **/
ActionSequence=0;
for(int AccessAction=0;AccessAction<next_avaliable_state.length;AccessAction++){
if(next_avaliable_state[AccessAction]!=-1)
ActionSequence+=1;
}
int AccessActionindex=(int)(Math.random()*ActionSequence);//隨機選擇一個動作
UpdateQq(CRstate,next_avaliable_state[AccessActionindex],MaxQ[AccessActionindex]);
if(next_avaliable_state[AccessActionindex]!=5){//目標是拿到將來最高的回報,在state=5處,回報值最大,反之最小。
CRstate=BestNextState;//把下一個狀態作爲當前狀態
}else{
EndLearning=true;
}
}
return true;
}
}


package Q_Learning;
public class TrainQ_Learning {
public static void main(String[] args){
Q_Learning ql=new Q_Learning();
//初始化reward
int R[][]=new int[6][6];
//初始化Q
int Q[][]=new int[6][6];
//Q的舊值
int CopyQ[][]=new int[6][6];
int Traintimes=0;
for(int setr_x=0;setr_x<6;setr_x++){
for(int setr_y=0;setr_y<6;setr_y++){
R[setr_x][setr_y]=-1;
Q[setr_x][setr_y]=0;
CopyQ[setr_x][setr_y]=0;
}
}
R[0][4]=0;
R[1][3]=0;
R[1][5]=100;
R[2][3]=0;
R[3][1]=0;
R[3][2]=0;
R[3][4]=0;
R[4][0]=0;
R[4][3]=0;
R[4][5]=100;
R[5][1]=0;
R[5][4]=0;
R[5][5]=100;
ql.setR(R);
ql.setQ(Q);
while(Traintimes<500){
//隨機選擇一個狀態
ql.ChooseTheBestAction((int)(Math.random()*6));
Traintimes++;
}
for(int finalx=0;finalx<6;finalx++){
for(int finaly=0;finaly<6;finaly++){
System.out.print(ql.GetCerTainQ(finalx,finaly)+"\t");
}
System.out.println();
}
}
/**
* R=
*  -1  -1  -1  -1  0  -1 
*  -1  -1  -1   0 -1  100 
*  -1  -1  -1   0 -1  -1 
*  -1   0   0  -1  0  -1 
*   0  -1  -1   0 -1  100
*  -1   0  -1  -1  0  100 
* 初始化時,Q隨機初始化,隨便給值就可以了,這裏全部給初值0

* 初始Q=
*   0  0  0  0  0  0
*   0  0  0  0  0  0
*   0  0  0  0  0  0
*   0  0  0  0  0  0
*   0  0  0  0  0  0
*   0  0  0  0  0  0
* **/
}

運行結果:

0 0 0 0 396 0
0 0 0 316 0 496
0 0 0 316 0 0
0 396 252 0 396 0
316 0 0 316 0 496
0 396 0 0 396 496

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