基于强化学习获取走迷宫知识
从开始地点,重复按分支一直走到终点,能够获得各个场所所对应的奖赏。此时,尽量学习能够获得更多的奖赏的行动知识。
基于强化学习来解决上述问题,在各个不同分支中选择一个前进,以获取行动的选择知识为目标。这里,为了学习各个分支点的行动所对应的Q值,以树的数据结构设定Q值。如下图:
在Q学习中,用随机数作为Q值的初始值。通过随机值对Q值的初始化,基于Q值选择行动,学习不断进行。
在行动选择中,应该优先选择Q值大的行动,但是单纯地选择最大的Q值对应的行动是无法很好地进行Q学习的。如果这样做,初始化时的随机数中偶然的大的Q值所对应的行动会一直被选中,无论重复多少次动作,这以一行动 之外的行动都不可能被选中。因此,这里需要进一步处理。
例如,可以精心设计使用随机数在某种比例下随机选择行动的方法。这一方法称为
ϵ
\epsilon
ϵ-贪心算法。具体而言,预先在0到1之间赋给
ϵ
\epsilon
ϵ一个合适的常数,在进行行动选择时,在0到1之间生成一个随机数,如果这个值比
ϵ
\epsilon
ϵ小,随机选择一个行动。如果这个数比
ϵ
\epsilon
ϵ大,选择大Q值所对应的行动。这样一来,不依赖与Q值的初始值,能够对各种各样的行动学习到合适的Q值。
作为行动选择的方法,除了这里的
ϵ
\epsilon
ϵ-贪心法,还有按评价值比例概率进行选择的轮盘选择等方法。
以上设定为基础,重复进行动作,不断进行Q学习。在动作的初始状态,Q值是随机选择的,根据前面所述的过程来反复选择行动,就能获得合适的Q值。
/************************************************************/
/* qlearning.c */
/* 强化学习之q学习 */
/* 学习如何探索迷宫 */
/***********************************************************/
/* 与Visual Studio的互换性保证 */
#define _CRT_SECURE_NO_WARNINGS
/* Include头文件 */
#include <stdio.h>
#include <stdlib.h>
/* 符号常量的定义 */
#define GENMAX 1000 /* 学习的重复次数 */
#define NODENO 15 /* Q值的节点数 */
#define ALPHA 0.1 /* 学习系数 */
#define GAMMA 0.9 /* 折扣率 */
#define EPSILON 0.3 /* 确定行动选择的随机性 */
#define SEED 32767 /* 随机数的种子 */
/* 函数原型声明 */
int rand100(); /* 返回0~100的随机函数 */
int rand01(); /* 返回0、1的随机函数 */
double rand1(); /* 返回0、1的实数的随机函数 */
void printqvalue(int qvalue[NODENO]); /* 输出Q值 */
int selecta(int s, int qvalue[NODENO]); /* 行动选择 */
int updateq(int s, int qvalue[NODENO]); /* 更新Q值 */
/******************************************/
/* main()函数 */
/******************************************/
int main(){
int i;
int s; /* 状态 */
int t; /* 时刻 */
int qvalue[NODENO]; /* Q值 */
srand(SEED); /* 随机数初始化 */
/* Q值的初始化 */
for(i = 0; i < NODENO; i++){
qvalue[i] = rand100();
}
printqvalue(qvalue);
/* 学习的主体 */
for(i = 0; i < GENMAX; i++){
s = 0; /* 行动初始的状态 */
for(t = 0; t < 3; t++){ /* 到最末端为止重复进行 */
/* 行动选择 */
s = selecta(s, qvalue);
/* Q值的更新 */
qvalue[s] = updateq(s, qvalue);
}
/* Q值的输出 */
printqvalue(qvalue);
}
return 0;
}
/***********************************************/
/* updateq() 函数 */
/* 更新Q值 */
/***********************************************/
int updateq(int s, int qvalue[NODENO]){
int qv; /* 要更新的Q值 */
int qmax; /* Q值的最大值 */
/* 最某段的情形 */
if(s > 6){
if (s == 14) /* 给予奖赏 */
qv = qvalue[s] + ALPHA*(1000 - qvalue[s]);
/* 给予奖赏的节点Q值增加 */
/* 其他节点的Q值增加时 */
/* 去掉下面两行的注释 */
// else if(s == 11) /* 给予奖赏 */
// qv = qvalue[s] + ALPHA*(500 - qvalue[s]);
else /* 无奖赏 */
qv = qvalue[s];
}
/* 最末端之外 */
else{
if( (qvalue[2*s + 1]) > (qvalue[2*s + 2]) )
qmax = qvalue[2 * s + 2];
else qmax = qvalue[2 * s + 2];
qv = qvalue[s] + ALPHA * (GAMMA * qmax - qvalue[s]);
}
return qv;
}
/*************************************************/
/* selecta()函数 */
/* 行动选择 */
/************************************************/
int selecta(int olds, int qvalue[NODENO]){
int s;
if(rand1() < EPSILON){
if(rand01() == 0) s = 2 * olds + 1;
else s = 2 * olds + 2;
}else{
/* 选择Q值的最大值 */
if( (qvalue[2 * olds + 1]) > (qvalue[2 * olds +2]) )
s = 2 * olds + 2;
else s = 2 * olds + 2;
}
return s;
}
/****************************************************/
/* printqvalue()函数 */
/* 输出Q值 */
/***************************************************/
void printqvalue(int qvalue[NODENO]){
int i ;
for(i = 1; i < NODENO; i++){
print("%d\t", qvalue[i]);
}
print("\n");
}
/*******************************************************/
/* rand1()函数 */
/* 返回0~1的实数随机数 */
/*******************************************************/
double rand1(){
/* 随机数的计算 */
return (double)rand()/RAND_MAX;
}
/*********************************************************/
/* rand01() */
/* 返回0、1的随机函数 */
/*********************************************************/
int rand01(){
int rnd;
/* remove the max value of random */
while((rnd=rand() == RAND_MAX ));
/* 随机数的计算 */
return (int)((double)rnd/RAND_MAX * 2);
}
/**********************************************************/
/* rand100() */
/*返回0~100的随机整数 */
/**********************************************************/
int rand100(){
int rnd;
/* remove the max value of random */
while((rnd=rand()) == RAND_MAX);
/* 随机数的计算 */
return (int)((double)rnd / RAND_MAX * 101);
}