标签:强化学习 实验 Q-learning
来自一个github上的项目,使用Q-leraning训练flappy bird,效果很好,半小时能够到几十(比人厉害多了),使用小鸟到最近的柱子的x和y值作为状态
非常简单,但是效果比较一般,鸟很多此在第0次的时候撞在柱子上。
地址
因为用到了google托管css,所以应该是要梯子的。而且在本地打开会因为file协议,导致存储的csv因为google的CORS policy被拒绝访问,解决方案是使用服务器,比如nginx或者apache之类的进行本地代理,通过http协议进行访问(主要原因是因为它拿取文件的方式用的是jQuery的get方法)
首先看看原始代码是如何实现flappy bird这个游戏的。
很明显是使用了canvas。函数startingState()返回的是游戏的初始状态所需要的参数
function startingState() {
return {
mode: "ready",
startFrame: 0,
jumpFrame: 0,
birdY: birdStartY,
curFrame: 0,
birdSprite: 0,
round: 0,
score: 0,
totalScore: 0,
maxScore: 0,
deadFlash: 0,
fps: 0,
pipeList: [],
landList: [],
};
}
而后面有gameState = startingState()
,所以后面所有拿到的游戏状态的类型与这个是一致的。
而resetState(gameState)函数则明显是重置游戏的函数
function resetState(gameState) {
var round = gameState.round;
var curFrame = gameState.curFrame;
var totalScore = gameState.totalScore;
var maxScore = gameState.maxScore;
var score = gameState.score;
var gameState = startingState();
gameState.startFrame = curFrame;
gameState.curFrame = curFrame;
gameState.round = round + 1;
gameState.totalScore = totalScore;
gameState.maxScore = maxScore;
return gameState;
}
从这两个函数可以看出游戏运行所必须的基本参数,以及核心状态gameState的各个属性。
判断状态函数分别有:
inPipeGap 判断鸟是否在Y轴与管子有接触
inPipe 判断鸟是否在X轴与管子有接触
collideGround 判断是否撞击地板
updateCollision 判断是否死亡
元素维持函数:
newPipe(curFrame,startX) 给定帧数和位置,创建新的管子
newLand(curFrame,startX) 给定帧数和位置,创建新的地板
updatePipes(gameState) 随着帧数更新管子的位置
updateLand 更新地板的位置
animation 调整Sprite Bird所用的动画
updateBird 调整鸟所在的位置(自由落体)
updateScore 更新分数
jump 开始游戏,或者重新开始游戏,或者设置跳跃帧jumpFrame,然后计算抛物线运动
drawSprite 在canvas的指定区域上绘制精灵
render 核心绘制函数
强化学习相关函数:
getQLState 拿到状态State(deltaX,deltaY),使用字符串加,进行传输
reward 奖励函数,返回Q
updateQL 强化学习的训练过程
//拿到强化学习的状态
function getQLState(gameState) {
var pipeList = gameState.pipeList;//获得当前有效地管子pipeList
var birdY = gameState.birdY; //拿到当前bird的垂直坐标
var pipeList = pipeList.filter(function (pipe) {
return birdX < pipe.curX + pipeWidth;
}).sort(function (a, b) {
return a.curX - b.curX;
});
var firstPipe = first(pipeList);//拿到鸟前面的第一根管子
var S = null;
if (firstPipe) {//将管子与鸟之间的相对距离存储,以字符串形式传回 ,qlResolution = 15,其中鸟的宽度birdX = 69
S = [Math.floor(firstPipe.curX / qlResolution),
Math.floor((firstPipe.gapTop - birdY) / qlResolution),
].join(',');
}
return S;
}
//奖励函数,非常经典的Q-learning更新过程
function reward(Q, S, S_, A, R) {
if (S && S_ && A in [0, 1] && S in Q && S_ in Q)
Q[S][A] = (1 - qlAlpha) * Q[S][A] + qlAlpha * (R + qlGamma * max(Q[S_]));
return Q;
}
//强化学习训练过程
function updateQL(gameState) {
if (!updateQL.enabled) return gameState;
if (updateQL.skip) {
updateQL.A = null;
updateQL.S = null;
}
if (!updateQL.Q) {
updateQL.Q = {};
updateQL.S = null;
}
var Q = updateQL.Q;
// 之前的状态
var S = updateQL.S;
// 之前的动作
var A = updateQL.A;
// 当前的状态
var S_ = getQLState(gameState);
//如果当前状态不在状态-动作函数Q中,进行初始化。A=0时不跳,A=1时跳
if (S_ && !(S_ in Q)) Q[S_] = [0, 0];
if (gameState.mode == "playing") {
updateQL.Q = reward(Q, S, S_, A, qlAliveReward);
updateQL.S = S_;
// current action, 0 for stay, 1 for jump
var A_ = 0;
//episilon贪心法,有episilon的概率进行探索操作,不然进行贪心操作
if (Math.random() < qlEpsilon) { // explore
A_ = Math.random() < qlExploreJumpRate ? 1 : 0;
} else if (S_ in Q) { // exploit
A_ = Q[S_][0] >= Q[S_][1] ? 0 : 1;
}
if (A_ === 1) gameState = jump(gameState);
updateQL.A = A_;
} else if (gameState.mode == "dead") {
updateQL.Q = reward(Q, S, S_, A, qlDeadReward);
updateQL.S = null;
updateQL.A = null;
// restart the game
updateQL.skip = false;
gameState = jump(gameState);
}
return gameState;
}