WIP: Implement DDPG

This commit is contained in:
Thibault Neveu
2018-06-19 18:55:06 +01:00
parent fd6bc919f7
commit e612abb172
6 changed files with 353 additions and 1 deletions
+7 -1
View File
@@ -37,11 +37,17 @@
</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@0.11.6"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.7.1/pixi.min.js"></script>
<script src="/dist/metacar.min.js"></script>
<script type="text/javascript" src="/public/js/utils.js"></script>
<script type="text/javascript" src="/public/js/viewer.js"></script>
<script type="text/javascript" src="/public/js/level2.js"></script>
<script type="text/javascript" src="/public/js/DDPG/models.js"></script>
<script type="text/javascript" src="/public/js/DDPG/memory.js"></script>
<script type="text/javascript" src="/public/js/DDPG/noise.js"></script>
<script type="text/javascript" src="/public/js/DDPG/ddpg.js"></script>
<script type="text/javascript" src="/public/js/DDPG/index.js"></script>
</body>
</html>
+40
View File
@@ -0,0 +1,40 @@
// This class is called from js/DDPG/index.js
class DDPG {
constructor(){
// Default Config
this.config = {
"stateSize": 25,
"nbActions": 2,
"layerNom": true,
"normalizeObservations": true,
"seed": 0,
"criticL2Reg": 0.01,
"batchSize": 64,
"actorLr": 0.0001,
"criticLr": 0.001,
"gamma": 0.99,
"rewardScale": 1,
"nbEpochs": 500,
"nbEpochsCycle": 800,
"nbTrainSteps": 50,
"nbRolloutStep": 100
};
// Inputs
const obsInput = tf.input({shape: [this.config.stateSize]});
const actionInput = tf.input({shape: [this.config.nbActions]});
// From js/DDPG/noise.js
this.paramNoise = new AdaptiveParamNoiseSpec();
// Buffer replay
// The baseline use 1e6 but this size should be enough
this.memory = new Memory(1000);
// Actor and Critic are from js/DDPG/models.js
this.actor = new Actor(
this.config.stateSize, this.config.nbActions, this.config.layerNom, this.config.seed);
this.critic = new Critic(
this.config.stateSize, this.config.nbActions, this.config.layerNom, this.config.seed);
this.actor.buildModel(obsInput);
this.critic.buildModel(obsInput, actionInput);
}
};
@@ -1,5 +1,8 @@
let levelUrl = metacar.level.level2;
// js/DDPG/ddpg.js
var ddpg = new DDPG();
var env = new metacar.env("canvas", levelUrl);
env.setAgentMotion(metacar.motion.ControlMotion, {});
+77
View File
@@ -0,0 +1,77 @@
class Memory {
/**
* @param maxlen (number) Buffer limit
*/
constructor(maxlen){
this.maxlen = maxlen;
this.length = 0;
this.start = 0;
this.obs0List = Array.apply(null, Array(maxlen)).map(Number.prototype.valueOf, 0);
this.obs1List = Array.apply(null, Array(maxlen)).map(Number.prototype.valueOf, 0);
this.rewardsList = Array.apply(null, Array(maxlen)).map(Number.prototype.valueOf, 0);
this.actionsList = Array.apply(null, Array(maxlen)).map(Number.prototype.valueOf, 0);
this.terminals1List = Array.apply(null, Array(maxlen)).map(Number.prototype.valueOf, 0);
}
/**
* @param idx (number)
*/
getItem(idx){
if (idx < 0 || idx >= this.length){
console.error("Memory.getItem: idx not in range.");
}
return this.data[(this.start + idx) % this.maxlen]
}
/**
* Sample a batch
* @param batchSize (number)
* @return batch []
*/
getBatch(batchSize){
const arrLength = this.obs0List.length;
const batch = {
'obs0': [],
'obs1': [],
'rewards': [],
'actions': [],
'terminals1': [],
};
for (let b=0; b < batchSize; b++){
let id = Math.floor(Math.random() * arrLength);
batch.obs0.push(this.obs0List[id]);
batch.obs1.push(this.obs1List[id]);
batch.rewards.push(this.rewardsList[id]);
batch.actions.push(this.actionsList[id]);
batch.terminals1.push(this.terminals1List[id]);
}
return batch
}
/**
* @param obs0 []
* @param action (number)
* @param reward (number)
* @param obs1 []
* @param terminal1 (boolean)
*/
append(obs0, action, reward, obs1, terminal1){
if (this.length < this.maxlen){
this.length += 1;
}
else if (this.length == this.maxlen) {
this.start = (this.start + 1) % this.maxlen;
}
else {
console.error("Memory.append: This should never be printed");
}
this.obs0List[(this.start + this.length - 1) % this.maxlen] = obs0;
this.obs1List[(this.start + this.length - 1) % this.maxlen] = action;
this.rewardsList[(this.start + this.length - 1) % this.maxlen] = reward;
this.actionsList[(this.start + this.length - 1) % this.maxlen] = obs1;
this.terminals1List[(this.start + this.length - 1) % this.maxlen] = terminal1;
}
}
+185
View File
@@ -0,0 +1,185 @@
class Actor{
/**
* @param stateSize(number)
* @param nbActions (number)
* @param layerNorm (boolean)
* @param seed (number)
*/
constructor(stateSize, nbActions, layerNorm, seed) {
this.stateSize = stateSize;
this.nbActions = nbActions;
this.layerNorm = layerNorm;
this.seed = seed;
}
/**
*
* @param obs tf.input
*/
buildModel(obs){
this.firstLayerBatchNorm = null;
this.secondLayerBatchNorm = null;
this.relu = tf.layers.thresholdedReLU();
// First layer with BatchNormalization
this.firstLayer = tf.layers.dense({
inputShape: this.stateSize,
units: 64,
kernelInitializer: tf.initializers.glorotUniform({seed: this.seed}),
activation: 'linear', // relu is add later
useBias: true,
biasInitializer: "zeros"
});
if (this.layerNorm){
// WARNING: BatchNormalization instead of layerNormalization
this.firstLayerBatchNorm = tf.layers.batchNormalization({
scale: true,
center: true
});
}
// Second layer with BatchNormalization
this.secondLayer = tf.layers.dense({
inputShape: 64,
units: 64,
kernelInitializer: tf.initializers.glorotUniform({seed: this.seed}),
activation: 'linear', // relu is add later
useBias: true,
biasInitializer: "zeros"
});
if (this.layerNorm){
// WARNING: BatchNormalization instead of layerNormalization
this.secondLayerBatchNorm = tf.layers.batchNormalization({
scale: true,
center: true
});
}
// Ouput layer
this.outputLayer = tf.layers.dense({
inputShape: 64,
units: this.nbActions,
kernelInitializer: tf.initializers.randomUniform({
minval: 0.003, maxval: 0.003, seed: this.seed}),
activation: 'tanh',
useBias: true,
biasInitializer: "zeros"
});
// Actor prediction
const predict = () => {
return tf.tidy(() => {
let l1 = this.firstLayer.apply(obs);
if (this.firstLayerBatchNorm){
l1 = this.firstLayerBatchNorm.apply(l1);
}
//l1 = this.relu.apply(l1);
let l2 = this.secondLayer.apply(l1);
if (this.secondLayerBatchNorm){
l2 = this.secondLayerBatchNorm.apply(l2);
}
//l2 = this.relu.apply(l2);
return this.outputLayer.apply(l2);
});
}
const output = predict();
this.model = tf.model({inputs: this.obs, outputs: output});
}
};
class Critic {
/**
* @param stateSize(number)
* @param nbActions (number)
* @param layerNorm (boolean)
* @param seed (number)
*/
constructor(stateSize, nbActions, layerNorm, seed) {
this.stateSize = stateSize;
this.nbActions = nbActions;
this.layerNorm = layerNorm;
}
/**
*
* @param obs tf.input
* @param action tf.input
*/
buildModel(obs, action){
this.firstLayerBatchNorm = null;
this.secondLayerBatchNorm = null;
this.relu = tf.layers.thresholdedReLU();
// First layer with BatchNormalization
this.firstLayer = tf.layers.dense({
inputShape: this.stateSize,
units: 64,
kernelInitializer: tf.initializers.glorotUniform({seed: this.seed}),
activation: 'linear', // relu is add later
useBias: true,
biasInitializer: "zeros"
});
if (this.layerNorm){
// WARNING: BatchNormalization instead of layerNormalization
this.firstLayerBatchNorm = tf.layers.batchNormalization({
scale: true,
center: true
});
}
// Second layer with BatchNormalization
this.secondLayer = tf.layers.dense({
inputShape: 64 + this.nbActions, // Previous layer + action
units: 64,
kernelInitializer: tf.initializers.glorotUniform({seed: this.seed}),
activation: 'linear', // relu is add later
useBias: true,
biasInitializer: "zeros"
});
if (this.layerNorm){
// WARNING: BatchNormalization instead of layerNormalization
this.secondLayerBatchNorm = tf.layers.batchNormalization({
scale: true,
center: true
});
}
// Ouput layer
this.outputLayer = tf.layers.dense({
inputShape: 64,
units: 1,
kernelInitializer: tf.initializers.randomUniform({
minval: 0.003, maxval: 0.003, seed: this.seed}),
activation: 'tanh',
useBias: true,
biasInitializer: "zeros"
});
// Actor prediction
const predict = () => {
return tf.tidy(() => {
let l1 = this.firstLayer.apply(obs);
l1 = l1.concat(action);
if (this.firstLayerBatchNorm){
l1 = this.firstLayerBatchNorm.apply(l1);
}
//l1 = this.relu(l1);
let l2 = this.secondLayer.apply(l1);
if (this.secondLayerBatchNorm){
l2 = this.secondLayerBatchNorm.apply(l2);
}
//l2 = this.relu.apply(l2);
return this.outputLayer.apply(l2);
});
}
const output = predict();
this.model = tf.model({inputs: this.obs, outputs: output});
}
};
+41
View File
@@ -0,0 +1,41 @@
/**
* Noise class
* The original baseline is made of three noise
* AdaptiveParamNoiseSpec, ActionNoise and NormalActionNoise
* Only AdaptiveParamNoiseSpec is implemented for now
* See "C Adapative Scaling" Page 14 in the paper.
*/
class AdaptiveParamNoiseSpec{
/**
* @param conf Object
* conf.initialStddev: 0.1 default // σ
* conf.desiredActionStddev: 0.1 default // δ
* conf.adoptionCoefficient: 1.01 default // α
*/
constructor(conf){
conf = conf || {};
this.initialStddev = conf.initialStddev || 0.1;
this.desiredActionStddev = conf.initialStddev || 0.1;
this.adoptionCoefficient = conf.adoptionCoefficient || 1.01;
this.currentStddev = conf.initialStddev;
}
/**
* The distance from the Adaptive scaling
* @param distance number
*/
adapt(distance){
// if d(π, _π_) > δ then σ = σ/α
if (distance > this.desiredActionStddev){
// Decrease σ
this.currentStddev /= this.adoptionCoefficient;
}
else{
// σ = σ*α
// Increase σ
this.currentStddev *= this.adoptionCoefficient;
}
}
};