https://github.com/Klimmasch/AEC
Tip revision: 96e9ae2336937469a8f1602c178ea5e0cb8564b6 authored by Lukas Klimmasch on 13 August 2021, 14:16:04 UTC
Merge branch 'alternateRearing' of https://github.com/Klimmasch/AEC into alternateRearing
Merge branch 'alternateRearing' of https://github.com/Klimmasch/AEC into alternateRearing
Tip revision: 96e9ae2
OESVergErrReward.m
%%% Main script for launching experimental procedure
%@param trainTime training time in number of iterations
%@param randomizationSeed randomization seed
%@param fileDescription description of approach used as file name
%@param useLearnedFile two paramters that indicate (1) to use the
% model specified in learnedFile, (2) if the
% model simulation of this file was aborted and
% the training should just be completed
% learnedFile: previously learned policy and sparse coding model
% textureFile: texture settings files
% sparseCodingType: type of sparse coding approach
%%%
function OESMuscles(trainTime, randomizationSeed, fileDescription, useLearnedFile)
rng(randomizationSeed);
% learnedFile = '/home/klimmasch/projects/results/model_13-Apr-2016_14:04:55_100_nonhomeo_2_testTrainOn/model.mat';
learnedFile = '/home/lelais/Documents/MATLAB/results/model_18-Apr-2016_18:27:54_200000_nonhomeo_1_CACLAVar_NewHiddenUpdate_init00017-01-004_alpha10_var-5/model.mat';
% do we want to keep it that way? one could also specify the model file in
% the input parameters
% textureFile = 'Textures_celine.mat';
textureFile = 'Textures_vanHaterenTrain.mat';
sparseCodingType = 'nonhomeo';
% Plotting and saving flag
% Whether figures should be generated, saved and plotted
% additionally the relevant scripts are backed up
% plotNsave: [training, test]
% 0 = don't do it
% 1 = do it
plotNsave = [uint8(1), uint8(0)];
% Testing flag
% Whether the testing procedure shall be executed after training
% testIt: 0 = don't do it
% 1 = do it
testIt = uint8(1);
% Load model from file or instantiate and initiate new model object
if useLearnedFile(1)
if isempty(learnedFile)
sprintf('could not open the learned file! %s', learnedFile)
return;
else
model = load(learnedFile, 'model');
model = model.model;
end
else
model = config(learnedFile, textureFile, trainTime, sparseCodingType);
end
% safety check for plotting functions
if (trainTime <= model.interval)
sprintf('trainTime[%d] must be > model.interval[%d]', trainTime, model.interval)
return;
elseif (~exist(fullfile(cd, 'checkEnvironment'), 'file'))
sprintf('Rendering binary \"checkEnvironment\" not present in current dir\n\"%s\"', cd)
return;
end
% File management: either complete training with existing folder etc., or
% create a new one
if useLearnedFile(1) && useLearnedFile(2)
timeToTrain = model.trainTime - model.trainedUntil;
else
modelName = sprintf('model_%s_%i_%s_%i_%s', ...
datestr(now, 'dd-mmm-yyyy_HH:MM:SS'), ...
trainTime, ...
sparseCodingType, ...
randomizationSeed, ...
fileDescription);
% folder = '~/projects/RESULTS/';
% folder = './results/';
folder = '../results/';
mkdir(folder, modelName);
model.savePath = strcat(folder, modelName);
% make copies of relevant files
copyfile('OESVergErrReward.m', model.savePath);
copyfile('config.m', model.savePath);
copyfile('Model.m', model.savePath);
timeToTrain = model.trainTime;
end
model.notes = [model.notes fileDescription]; %just and idea to store some more information
% Image process variables
patchSize = 8;
dsRatioL = model.scModel_Large.Dsratio; %downsampling ratio (Large scale) | original 8
dsRatioS = model.scModel_Small.Dsratio; %downsampling ratio (Small scale) | original 2
% fovea = [128 128];
foveaL = patchSize + patchSize ^ 2 / 2 ^ log2(dsRatioL); %fovea size (Large scale) | 16
foveaS = patchSize + patchSize ^ 2 / 2 ^ log2(dsRatioS); %fovea size (Small scale) | 40
stOvL = patchSize / dsRatioL; %steps of overlap in the ds image | 1
stOvS = patchSize / dsRatioS; %steps of overlap in the ds image | 4
ncL = foveaL - patchSize + 1; %number of patches per column (slide of 1 px) | 9
ncS = foveaS - patchSize + 1; %number of patches per column (slide of 1 px) | 33
% Prepare index matricies for image patches
columnIndL = [];
for kc = 1:stOvL:ncL
tmpInd = (kc - 1) * ncL + 1 : stOvL : kc * ncL;
columnIndL = [columnIndL tmpInd];
end
columnIndS = [];
for kc = 1:stOvS:ncS
tmpInd = (kc - 1) * ncS + 1 : stOvS : kc * ncS;
columnIndS = [columnIndS tmpInd];
end
% Textures
texturePath = sprintf('config/%s', textureFile);
texture = load(texturePath);
texture = texture.texture;
nTextures = length(texture);
degrees = load('Degrees.mat'); %loads tabular for resulting degrees as 'results_deg'
metCosts = load('MetabolicCosts.mat'); %loads tabular for metabolic costs as 'results'
% Save model every #saveInterval training iterations
saveInterval = ceil(model.trainTime / 50); % is every percent of training ok?
% if (trainTime < saveInterval)
% saveInterval = trainTime;
% end
%%% Helper function that maps muscle activities to resulting angle
function [angle] = getAngle(command)
cmd = (command * 10) + 1; % scale commands to table entries
angle = interp2(degrees.results_deg, cmd(1), cmd(2)); % interpolate in tabular
end
%%% Helper function that maps muscle activities to resulting metabolic costs
function [tmpMetCost] = getMetCost(command)
cmd = (command * 10) + 1; % scale commands to table entries
tmpMetCost = interp2(metCosts.results, cmd(1), cmd(2)); % interpolate in tabular
end
%%% New renderer
simulator = OpenEyeSim('create');
simulator.initRenderer();
% simulator.reinitRenderer();
imgRawLeft = uint8(zeros(240, 320, 3));
imgRawRight = uint8(zeros(240, 320, 3));
%%% Generates two new images for both eyes
% texture: file path of texture input
% eyeAngle: angle of single eye (rotation from offspring)
% objDist: distance of stimulus
function refreshImages(texture, eyeAngle, objDist)
simulator.add_texture(1, texture);
simulator.set_params(1, eyeAngle, objDist);
result1 = simulator.generate_left();
result2 = simulator.generate_right();
imgRawLeft = permute(reshape(result1, ...
[size(imgRawLeft, 3), ...
size(imgRawLeft, 2), ...
size(imgRawLeft, 1)]), ...
[3, 2, 1]);
imgRawRight = permute(reshape(result2, ...
[size(imgRawRight, 3), ...
size(imgRawRight, 2), ...
size(imgRawRight, 1)]), ...
[3, 2, 1]);
end
%%% Main execution loop
t = model.trainedUntil; % this is zero in new initiated model
command = [0, 0];
% rewardFunction_prev = 0;
tic; % start time count
for iter1 = 1 : (timeToTrain / model.interval)
% pick random texture every #interval times
currentTexture = texture{(randi(nTextures, 1))};
% random depth
objDist = model.objDistMin + (model.objDistMax - model.objDistMin) * rand(1, 1);
% reset muscle activities to random values
% initialization for muscle in between borders of desired actvity
% i.e. min and max stimulus distance
command(1) = 0; % single muscle
% command(1) = model.muscleInitMin + (model.muscleInitMax - model.muscleInitMin) * rand(1, 1); % two muscles
command(2) = model.muscleInitMin + (model.muscleInitMax - model.muscleInitMin) * rand(1, 1);
angleNew = getAngle(command) * 2;
% [status, res] = system(sprintf('./checkEnvironment %s %d %d %s/left.png %s/right.png', ...
% currentTexture, objDist, angleNew, model.savePath, model.savePath));
%
% % abort execution if error occured
% if (status)
% sprintf('Error in checkEnvironment:\n%s', res)
% return;
% end
for iter2 = 1 : model.interval
t = t + 1;
% read input images and convert to gray scale
refreshImages(currentTexture, -angleNew / 2, objDist);
imgGrayLeft = .2989 * imgRawLeft(:,:,1) + .5870 * imgRawLeft(:,:,2) + .1140 * imgRawLeft(:,:,3);
imgGrayRight = .2989 * imgRawRight(:,:,1) + .5870 * imgRawRight(:,:,2) + .1140 * imgRawRight(:,:,3);
% Generate & save the anaglyph picture
% anaglyph = stereoAnaglyph(imgGrayLeft, imgGrayRight); % only for matlab 2015 or newer
imwrite(imfuse(imgGrayLeft, imgGrayRight, 'falsecolor'), [model.savePath '/anaglyph.png']); %this one works for all tested matlab
%more advanced functions that generated the anaglyphs of the foveal views
% generateAnaglyphs(imgGrayLeft, imgGrayRight, dsRatioL, dsRatioS, foveaL, foveaS, model.savePath);
% Image patch generation: left{small scale, large scale}, right{small scale, large scale}
[patchesLeftSmall] = preprocessImage(imgGrayLeft, foveaS, dsRatioS, patchSize, columnIndS);
[patchesLeftLarge] = preprocessImage(imgGrayLeft, foveaL, dsRatioL, patchSize, columnIndL);
[patchesRightSmall] = preprocessImage(imgGrayRight, foveaS, dsRatioS, patchSize, columnIndS);
[patchesRightLarge] = preprocessImage(imgGrayRight, foveaL, dsRatioL, patchSize, columnIndL);
% Image patches matrix (input to model)
currentView = {[patchesLeftLarge; patchesRightLarge] [patchesLeftSmall; patchesRightSmall]};
% Generate input feature vector from current images
[feature, reward, ~, errorLarge, errorSmall] = model.generateFR(currentView);
%%% Feedback
% Absolute command feedback # concatination
if (model.rlModel.continuous == 1)
feature = [feature; command(2) * model.lambdaMuscleFB];
end
%%% Calculate metabolic costs
metCost = getMetCost(command) * 2;
% compute desired vergence command, disparity and vergence error
fixDepth = (model.baseline / 2) / tand(angleNew / 2); %fixation depth [m]
angleDes = 2 * atand(model.baseline / (2 * objDist)); %desired vergence [deg]
anglerr = angleDes - angleNew; %vergence error [deg]
disparity = 2 * model.focalLength * tand(anglerr / 2); %current disp [px]
%%% Calculate reward function
%% Standard reward
% rewardFunction = model.lambdaRec * reward - model.lambdaMet * metCost;
% rewardFunction = (model.lambdaMet * reward) + ((1 - model.lambdaMet) * - metCost);
rewardFunction = -abs(anglerr);
%% Delta reward
% rewardFunctionReal = model.lambdaRec * reward - model.lambdaMet * metCost;
% rewardFunction = rewardFunctionReal - rewardFunction_prev;
% Stasis punishment, i.e. punish non-movement of eyes
% if (abs(rewardFunctionReal - rewardFunction_prev) < 1e-5)
% rewardFunction = rewardFunctionReal - rewardFunction_prev - 1e-5;
% end
% rewardFunction_prev = rewardFunctionReal;
%%% Weight L1 regularization
% rewardFunction = model.lambdaRec * reward ...
% - model.lambdaMet * metCost ...
% - model.lambdaV * (sum(sum(abs(model.rlModel.CCritic.v_ji)))) ...
% - model.lambdaP1 * (sum(sum(abs(model.rlModel.CActor.wp_ji)))) ...
% - model.lambdaP2 * (sum(sum(abs(model.rlModel.CActor.wp_kj))));
%%% Weight L2 regularization
% rewardFunction = model.lambdaRec * reward ...
% - model.lambdaMet * metCost ...
% - model.lambdaV * (sum(sum(model.rlModel.CCritic.v_ji .^ 2))) ...
% - model.lambdaP1 * (sum(sum(model.rlModel.CActor.wp_ji .^ 2))) ...
% - model.lambdaP2 * (sum(sum(model.rlModel.CActor.wp_kj .^ 2)));
%%% Learning
% Sparse coding models
model.scModel_Large.stepTrain(currentView{1});
model.scModel_Small.stepTrain(currentView{2});
% RL model
% Variance decay, i.e. reduction of actor's output perturbation
if ((model.rlModel.continuous == 1) && (model.rlModel.CActor.varDec > 0))
model.rlModel.CActor.variance = model.rlModel.CActor.varianceRange(1) * 2 ^ (-t / model.rlModel.CActor.varDec);
end
relativeCommand = model.rlModel.stepTrain(feature, rewardFunction, (iter2 > 1));
% add the change in muscle Activities to current ones
% command = command + relativeCommand'; %two muscles
command(1) = 0;
command(2) = command(2) + relativeCommand; %one muscle
command = checkCmd(command); %restrain motor commands to [0,1]
if (model.rlModel.continuous == 1)
angleNew = getAngle(command) * 2; %resulting angle is used for both eyes
else
angleNew = angleNew + relativeCommand;
if (angleNew > 71.5 || angleNew < 0.99) % analogous to checkCmd
command = [0, 0];
command(2) = model.muscleInitMin + (model.muscleInitMax - model.muscleInitMin) * rand(1,1);
angleNew = getAngle(command) * 2;
end
end
% generate new view (two pictures) with new vergence angle
% [status, res] = system(sprintf('./checkEnvironment %s %d %d %s/left.png %s/right.png', ...
% currentTexture, objDist, angleNew, model.savePath, model.savePath));
%
% % abort execution if error occured
% if (status)
% sprintf('Error in checkEnvironment:\n%s', res)
% return;
% end
%%%%%%%%%%%%%%%% TRACK ALL PARAMETERS %%%%%%%%%%%%%%%%%%
% save state
% model.Z(t) = objDist;
% model.fixZ(t) = fixDepth;
% model.disp_hist(t) = disparity;
model.vergerr_hist(t) = anglerr;
model.recerr_hist(t, :) = [errorLarge; errorSmall];
% model.verge_actual(t) = angleNew;
% model.verge_desired(t) = angleDes;
model.relCmd_hist(t) = relativeCommand;
model.cmd_hist(t, :) = command;
% model.reward_hist(t) = rewardFunction;
% model.feature_hist(t, :) = feature;
model.metCost_hist(t) = metCost;
if (model.rlModel.continuous == 1)
model.td_hist(t) = model.rlModel.CCritic.delta;
% model.g_hist(t) = model.rlModel.CActor.params(7);
model.weight_hist(t, 1) = model.rlModel.CCritic.params(1);
model.weight_hist(t, 2) = model.rlModel.CActor.params(1);
if (model.rlModel.rlFlavour(2) >= 4)
model.weight_hist(t, 3) = model.rlModel.CActor.params(2);
if ((model.rlModel.rlFlavour(2) == 5) || (model.rlModel.rlFlavour(2) == 7))
model.weight_hist(t, 4) = model.rlModel.CActor.params(3);
end
end
model.variance_hist(t) = model.rlModel.CActor.variance;
end
model.trainedUntil = t;
end
sprintf('Training Iteration = %d\nAbs Command =\t[%7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f]\nRel Command = \t[%7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f]\nVer Error =\t[%7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f, %7.3f]', ...
t, model.cmd_hist(t - model.interval + 1 : t, 2), model.relCmd_hist(t - model.interval + 1 : t), model.vergerr_hist(t - model.interval + 1 : t))
% Display per cent completed of training and save model
if (~mod(t, saveInterval))
sprintf('%g%% is finished', (t / timeToTrain * 100))
save(strcat(model.savePath, '/model'), 'model');
% save Basis
model.scModel_Large.saveBasis();
model.scModel_Small.saveBasis();
% TODO: DEPRECATED
% save Weights
% model.rlModel.saveWeights();
end
end
elapsedTime = toc;
% Total simulation time
model.simulatedTime = elapsedTime / 60;
sprintf('Time = %.2f [h] = %.2f [min] = %f [sec]\nFrequency = %.4f [iterations/sec]', ...
elapsedTime / 3600, elapsedTime / 60, elapsedTime, trainTime / elapsedTime)
% plot results
if (plotNsave(1) == 1)
% if useLearnedFile(2)
% model.trainTime = model.trainTime + model.trainedUntil;
% end
save(strcat(model.savePath, '/model'), 'model'); % storing simulated time
if (model.rlModel.continuous == 1)
copyfile('ReinforcementLearningCont.m', model.savePath);
else
copyfile('ReinforcementLearning.m', model.savePath);
end
switch model.rlModel.rlFlavour(1)
case 0
%% Chong's implementation
copyfile('CCriticG.m', model.savePath);
case 1
%% CRG
copyfile('CRGCritic.m', model.savePath);
case 2
%% CACLA
copyfile('CACLACritic.m', model.savePath);
end
switch model.rlModel.rlFlavour(2)
case 0
%% Chong's implementation
copyfile('CActorG.m', model.savePath);
case 1
%% CRG
copyfile('CRGActor.m', model.savePath);
case 2
%% CACLA linear
copyfile('CACLAActorLin.m', model.savePath);
case 3
%% CACLAVar linear
copyfile('CACLAVarActorLin.m', model.savePath);
case 4
%% CACLA
copyfile('CACLAActor.m', model.savePath);
case 5
%% CACLAVar
copyfile('CACLAVarActor.m', model.savePath);
case 6
%% CACLA2
copyfile('CACLAActor2.m', model.savePath);
case 7
%% CACLAVar2
copyfile('CACLAVarActor2.m', model.savePath);
end
model.allPlotSave();
end
%%% Testing procedure
if (testIt)
% testModel(model, randomizationSeed, objRange, vergRange, repeat, randStimuli, randObjRange, plotIt, saveTestResults)
testModel(model, randomizationSeed, [0.5, 1, 1.5, 2], [-3 : 0.2 : 3], [50, 50], 0, 1, plotNsave(2), 1);
end
end
%%% Saturation function that keeps motor commands in [0, 1]
% corresponding to the muscelActivity/metabolicCost tables
function [cmd] = checkCmd(cmd)
i0 = cmd < 0;
cmd(i0) = 0;
i1 = cmd > 1;
cmd(i1) = 1;
end
%%% Helper functions for image preprocessing
%% Patch generation
function [patches] = preprocessImage(img, fovea, downSampling, patchSize, columnIndicies)
% img = .2989 * img(:,:,1) + .5870 * img(:,:,2) + .1140 * img(:,:,3);
for i = 1:log2(downSampling)
img = impyramid(img, 'reduce');
end
% convert to double
img = double(img);
% cut fovea in the center
[h, w, ~] = size(img);
img = img(fix(h / 2 + 1 - fovea / 2) : fix(h / 2 + fovea / 2), ...
fix(w / 2 + 1 - fovea / 2) : fix(w / 2 + fovea / 2));
% cut patches and store them as col vectors
patches = im2col(img, [patchSize patchSize], 'sliding'); %slide window of 1 px
% take patches at steps of s (8 px)
patches = patches(:, columnIndicies); %81 patches
% pre-processing steps (0 mean, unit norm)
patches = patches - repmat(mean(patches), [size(patches, 1) 1]); %0 mean
normp = sqrt(sum(patches.^2)); %patches norm
% normalize patches to norm 1
normp(normp == 0) = eps; %regularizer
patches = patches ./ repmat(normp, [size(patches, 1) 1]); %normalized patches
end
%% Not overlapping Patch generation
function patchesNoOv = preprocessImageNoOv(img, fovea, downSampling, patchSize)
img = .2989 * img(:,:,1) + .5870 * img(:,:,2) + .1140 * img(:,:,3);
for i = 1:log2(downSampling)
img = impyramid(img, 'reduce');
end
% convert to double
img = double(img);
% cut fovea in the center
[h, w, ~] = size(img);
img = img(fix(h / 2 + 1 - fovea / 2) : fix(h / 2 + fovea / 2), ...
fix(w / 2 + 1 - fovea / 2) : fix(w / 2 + fovea / 2));
% cut patches and store them as col vectors
% no overlapping patches (for display)
patchesNoOv = im2col(img, [patchSize patchSize], 'distinct');
end
%% Generation of random vergence angles according to truncated Laplace distribution
function l = truncLaplacian(diversity, range)
% see wikipedia for the generation of random numbers according to the
% LaPlace distribution via the inversion method
r = rand;
switch r < 0.5
case 1
l = 1 / diversity * log(2 * r);
case 0
l = -1 / diversity * log(2 * (1 - r));
end
if (abs(l) > range)
l = 0;
end
end
%this function generates anaglyphs of the large and small scale fovea and
%one of the two unpreprocessed gray scale images
% TODO: adjust the sizes of the montage view
function generateAnaglyphs(leftGray, rightGray, dsRatioL, dsRatioS, foveaL, foveaS, savePath)
anaglyph = imfuse(leftGray, rightGray, 'falsecolor');
imwrite(anaglyph, [savePath '/anaglyph.png']);
%Downsampling Large
imgLeftL = leftGray(:);
imgLeftL = reshape(imgLeftL, size(leftGray));
imgRightL = rightGray(:);
imgRightL = reshape(imgRightL, size(rightGray));
for i = 1:log2(dsRatioL)
imgLeftL = impyramid(imgLeftL, 'reduce');
imgRightL = impyramid(imgRightL, 'reduce');
end
% cut fovea in the center
[h, w, ~] = size(imgLeftL);
imgLeftL = imgLeftL(fix(h / 2 + 1 - foveaL / 2) : fix(h / 2 + foveaL / 2), ...
fix(w / 2 + 1 - foveaL / 2) : fix(w / 2 + foveaL / 2));
imgRightL = imgRightL(fix(h / 2 + 1 - foveaL / 2) : fix(h / 2 + foveaL / 2), ...
fix(w / 2 + 1 - foveaL / 2) : fix(w / 2 + foveaL / 2));
%create an anaglyph of the two pictures, scale it up and save it
anaglyphL = imfuse(imgLeftL, imgRightL, 'falsecolor');
imwrite(imresize(anaglyphL, 20), [savePath '/anaglyphLargeScale.png']);
largeScaleView = imfuse(imgLeftL, imgRightL, 'montage');
imwrite(imresize(largeScaleView, 20), [savePath '/LargeScaleMontage.png']);
%Downsampling Small
imgLeftS = leftGray(:);
imgLeftS = reshape(imgLeftS, size(leftGray));
imgRightS = rightGray(:);
imgRightS = reshape(imgRightS, size(rightGray));
for i = 1:log2(dsRatioS)
imgLeftS = impyramid(imgLeftS, 'reduce');
imgRightS = impyramid(imgRightS, 'reduce');
end
% cut fovea in the center
[h, w, ~] = size(imgLeftS);
imgLeftS = imgLeftS(fix(h / 2 + 1 - foveaS / 2) : fix(h / 2 + foveaS / 2), ...
fix(w / 2 + 1 - foveaS / 2) : fix(w / 2 + foveaS / 2));
imgRightS = imgRightS(fix(h / 2 + 1 - foveaS / 2) : fix(h / 2 + foveaS / 2), ...
fix(w / 2 + 1 - foveaS / 2) : fix(w / 2 + foveaS / 2));
%create an anaglyph of the two pictures, scale it up and save it
anaglyphS = imfuse(imgLeftS, imgRightS, 'falsecolor');
imwrite(imresize(anaglyphS, 8), [savePath '/anaglyphSmallScale.png']);
smallScaleView = imfuse(imgLeftL, imgRightL, 'montage');
imwrite(imresize(smallScaleView, 8), [savePath '/smallScaleMontage.png']);
end