To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
The primary repository for this project is hosted at git://github.com/rmeddis/MAP.git .
This repository is a read-only copy which is updated automatically every hour.
root / multithreshold 1.46 / bestFitPsychometicFunctions.m @ 31:c54a34161e4a
History | View | Annotate | Download (10.8 KB)
| 1 |
% ------------------------------------------------------- bestFitPsychometicFunctions |
|---|---|
| 2 |
function [psy, levelsPhaseTwoBinVector, logistic, rareEvent]= bestFitPsychometicFunctions (levelsPhaseTwo, responsesPhaseTwo) |
| 3 |
% bestFitPsychometicFunctions computes a psychometric function from a matrix of levels and responses |
| 4 |
%output |
| 5 |
% psy is the empirical probabbility of a detection associated with levels in levelsPhaseTwoBinVector |
| 6 |
% logistic is a structure with results for fitting the logistic function |
| 7 |
% the logistic fit depends on whether maximum likelihood is used or least squares |
| 8 |
% rareEvent is a structure with results for fitting the rareEvent function |
| 9 |
% this is always calculated and lest squares is always used |
| 10 |
|
| 11 |
global experiment stimulusParameters binFrequencies |
| 12 |
|
| 13 |
% Generate a psychometic function by binning the levelsPhaseTwo |
| 14 |
% x is a vector of [levelsPhaseTwo; responsesPhaseTwo] |
| 15 |
% experiment.psyBinWidth defines the width of the bin |
| 16 |
[psy, levelsPhaseTwoBinVector, binFrequencies,yesResponses, noResponses]= ... |
| 17 |
psychometricFunction(levelsPhaseTwo, responsesPhaseTwo, experiment.psyBinWidth); |
| 18 |
% [levelsPhaseTwoBinVector; binFrequencies; psy]; |
| 19 |
|
| 20 |
% undefined slope - return |
| 21 |
if isempty(psy) |
| 22 |
% slope is undefined |
| 23 |
psy=[]; levelsPhaseTwoBinVector=[]; |
| 24 |
logistic.bestThreshold=NaN; logistic.bestK=NaN; logistic.predictionsLOG=[]; logistic.predictionLevels=[]; |
| 25 |
rareEvent.bestGain=NaN; rareEvent.bestVMin=NaN; rareEvent.predictionsRE=[]; rareEvent.predictionLevels=[]; |
| 26 |
rareEvent.thresholddB= NaN; rareEvent.bestPaMindB=NaN; |
| 27 |
return |
| 28 |
end |
| 29 |
|
| 30 |
% find best fit rare event function |
| 31 |
switch experiment.paradigm |
| 32 |
case 'gapDuration' |
| 33 |
% i.e. don't attempt this but visit the function to set null results |
| 34 |
rareEvent=fitRareEvent([], responsesPhaseTwo, stimulusParameters.targetDuration); |
| 35 |
otherwise |
| 36 |
rareEvent=fitRareEvent(levelsPhaseTwo, responsesPhaseTwo, stimulusParameters.targetDuration); |
| 37 |
end |
| 38 |
|
| 39 |
% find best logistic fit |
| 40 |
logisticFunc=' 1./(1+exp(-a2.*(x-a1)));'; |
| 41 |
switch experiment.functionEstMethod |
| 42 |
% least squares estimate |
| 43 |
case {'logisticLS', 'rareEvent','peaksAndTroughs'}
|
| 44 |
[a1, a2, Euclid]=fitFunctionUsingLeastSquares(levelsPhaseTwo, responsesPhaseTwo, ... |
| 45 |
logisticFunc, experiment.possLogSlopes, experiment.meanSearchStep); |
| 46 |
% [a1, a2, Euclid]=fitLogisticUsingLS(psy, levelsPhaseTwoBinVector); %! does not give same result |
| 47 |
logistic.bestThreshold=a1; |
| 48 |
logistic.bestK=a2; |
| 49 |
logistic.predictionsLOG= ... |
| 50 |
1./(1+exp(-logistic.bestK.*(experiment.predictionLevels-logistic.bestThreshold))); |
| 51 |
logistic.predictionLevels=experiment.predictionLevels; |
| 52 |
logistic.Euclid = Euclid; |
| 53 |
% predResponses=1./(1+exp(-logistic.bestK.*(levelsPhaseTwo-logistic.bestThreshold))); |
| 54 |
% [levelsPhaseTwo' responsesPhaseTwo' predResponses' responsesPhaseTwo'-predResponses' ] |
| 55 |
|
| 56 |
% maximum likelihood fitting |
| 57 |
case 'logisticML' |
| 58 |
[a1, a2, Euclid]=fitFunctionUsingMaxLikelihood (levelsPhaseTwoBinVector,... |
| 59 |
psy, yesResponses, noResponses, logisticFunc, experiment.possLogSlopes,experiment.meanSearchStep); |
| 60 |
logistic.bestThreshold=a1; |
| 61 |
logistic.bestK=a2; |
| 62 |
logistic.predictionsLOG= ... |
| 63 |
1./(1+exp(-logistic.bestK.*(experiment.predictionLevels-logistic.bestThreshold))); |
| 64 |
logistic.predictionLevels=experiment.predictionLevels; |
| 65 |
logistic.Euclid = Euclid; |
| 66 |
end |
| 67 |
|
| 68 |
% disp(num2str([logistic.bestThreshold logistic.bestK logistic.Euclid])) |
| 69 |
% disp(num2str([ rareEvent.thresholddB rareEvent.bestGain rareEvent.bestVMin rareEvent.Euclid])) |
| 70 |
|
| 71 |
% --------------------------------------------- fitLogisticUsingLS |
| 72 |
function [mean, slope, Euclid]=fitLogisticUsingLS(p,L) |
| 73 |
|
| 74 |
%!! each bin needs to be weighted by its size |
| 75 |
|
| 76 |
idx1=find(p>0); |
| 77 |
p=p(idx1); |
| 78 |
L=L(idx1); |
| 79 |
idx1=find(p<1); |
| 80 |
p=p(idx1); |
| 81 |
L=L(idx1); |
| 82 |
|
| 83 |
if length(L)<2 |
| 84 |
mean=NaN; |
| 85 |
slope=NaN; |
| 86 |
Euclid=NaN; |
| 87 |
return |
| 88 |
end |
| 89 |
|
| 90 |
y=L; |
| 91 |
x=log(1./p -1); |
| 92 |
a =polyfit(x, y, 1); |
| 93 |
mean=a(2); |
| 94 |
slope=-1/a(1); |
| 95 |
|
| 96 |
% euclid |
| 97 |
predy=polyval(a,x); |
| 98 |
Euclid=sum((predy-y).^2); |
| 99 |
[mean slope Euclid]; |
| 100 |
|
| 101 |
% --------------------------------------------- fitFunctionUsingLeastSquares |
| 102 |
function [a1, a2, Euclid]=fitFunctionUsingLeastSquares(levelsPhaseTwo, responsesPhaseTwo, func, possSlopes, meanSearchStep) |
| 103 |
|
| 104 |
% nResponses= yesResponses+noResponses; |
| 105 |
% idx=find(not(nResponses==0)); |
| 106 |
% levelsPhaseTwo=levelsPhaseTwo(idx); |
| 107 |
% yesResponses=yesResponses(idx); |
| 108 |
% noResponses=noResponses(idx); |
| 109 |
% nResponses=nResponses(idx); |
| 110 |
|
| 111 |
% psy=yesResponses./nResponses; |
| 112 |
|
| 113 |
possThresholds=min(levelsPhaseTwo):meanSearchStep:max(levelsPhaseTwo); |
| 114 |
|
| 115 |
|
| 116 |
% create vectors representing all combinations of a1 and a2 |
| 117 |
nPossThresh=length(possThresholds); |
| 118 |
nPossSlopes=length(possSlopes); |
| 119 |
a1=repmat(possThresholds',nPossSlopes,1); |
| 120 |
a2=repmat(possSlopes,nPossThresh,1); |
| 121 |
a2=reshape(a2,nPossThresh*nPossSlopes,1); |
| 122 |
|
| 123 |
% each response is predicted by every possible threshold and slope |
| 124 |
LS=ones(size(a1)); |
| 125 |
for i=1:length(levelsPhaseTwo) |
| 126 |
x=levelsPhaseTwo(i); |
| 127 |
eval(['y=' func]); |
| 128 |
actual=responsesPhaseTwo(i); |
| 129 |
error= (actual - y).^2; |
| 130 |
LS=LS+error; |
| 131 |
end |
| 132 |
|
| 133 |
[Euclid, idx]=min(LS); |
| 134 |
|
| 135 |
a1=a1(idx); |
| 136 |
a2=a2(idx); |
| 137 |
|
| 138 |
% x=levelsPhaseTwo; |
| 139 |
% eval(['y=' func]); |
| 140 |
% Euclid= sum((psy-y).^2); |
| 141 |
|
| 142 |
% plot(levelsPhaseTwo,y,'r') |
| 143 |
% hold on |
| 144 |
% plot(levelsPhaseTwo,psy,'o') |
| 145 |
|
| 146 |
% --------------------------------------------- fitFunctionUsingMaxLikelihood |
| 147 |
function [a1, a2, Euclid]=fitFunctionUsingMaxLikelihood... |
| 148 |
(levelsPhaseTwo,psy, yesResponses, noResponses, func, possible_a2, meanSearchStep) |
| 149 |
|
| 150 |
% fitFunctionUsingMaxLikelihood fits the function in 'func' to binned yes-no data. |
| 151 |
% levelsPhaseTwo specifies the bin centers |
| 152 |
% yesResponses and noResponses are the niumber of yes and no responsesPhaseTwo in each bin |
| 153 |
% 'func' is a function of a1, b1, x; e.g. func='1./(1+exp(-a2.*(x-a1)));'; |
| 154 |
% and a2, a1 are parameters to be discovered. |
| 155 |
% If bins are empty (i.e. neither yes or no), these are eliminated |
| 156 |
% |
| 157 |
|
| 158 |
nResponses=yesResponses+noResponses; |
| 159 |
possible_a1=min(levelsPhaseTwo):meanSearchStep:max(levelsPhaseTwo); |
| 160 |
|
| 161 |
% if nargin<6 |
| 162 |
% possible_a2=-2:.05:4; |
| 163 |
% end |
| 164 |
|
| 165 |
% create vectors representing all combinations of a1 and a2 |
| 166 |
nPossThresh=length(possible_a1); |
| 167 |
nPossSlopes=length(possible_a2); |
| 168 |
a1=repmat(possible_a1',nPossSlopes,1); |
| 169 |
a2=repmat(possible_a2,nPossThresh,1); |
| 170 |
a2=reshape(a2,nPossThresh*nPossSlopes,1); |
| 171 |
|
| 172 |
cumulativeProbability=ones(1, length(a2)); |
| 173 |
cumulativeProbability=cumulativeProbability'; |
| 174 |
|
| 175 |
for i=1:length(levelsPhaseTwo) |
| 176 |
x=levelsPhaseTwo(i); |
| 177 |
eval(['y=' func]); |
| 178 |
funcVal=(1-y).^yesResponses(i); |
| 179 |
cumulativeProbability= cumulativeProbability.*funcVal; |
| 180 |
funcVal=y.^noResponses(i); |
| 181 |
cumulativeProbability= cumulativeProbability.*funcVal; |
| 182 |
end |
| 183 |
|
| 184 |
[maxProb idx]=max(cumulativeProbability); |
| 185 |
a1=a1(idx); |
| 186 |
a2=a2(idx); |
| 187 |
|
| 188 |
% x=levelsPhaseTwo; |
| 189 |
eval(['y=' func]); |
| 190 |
Euclid= sum(nResponses.*(psy-y).^2); |
| 191 |
|
| 192 |
% figure(1), clf |
| 193 |
% plot(levelsPhaseTwo,y) |
| 194 |
% hold on |
| 195 |
% plot(levelsPhaseTwo,psy,'o') |
| 196 |
|
| 197 |
|
| 198 |
|
| 199 |
% --------------------------------------------------- fitRareEvent |
| 200 |
function rareEvent=fitRareEvent(stimulusLevels, responsesPhaseTwo, duration, gains, Vmins) |
| 201 |
% least squares estimate of *rare event* function |
| 202 |
% model is: r = g P ? A ... g=events/s/Pa, A= events/s, P= pressure (Pa) |
| 203 |
% psy=1- exp(d(gP ?A )) ... d=duration |
| 204 |
% p(event)=gain*levelmPa -Vmin |
| 205 |
% 'responsesPhaseTwo' is a binary vector of subject's decision. |
| 206 |
% 'stimulusLevels' are the corresponding signal levesl (values) |
| 207 |
% duration is required to compute the expectation of an event occurring |
| 208 |
% gains is an optional list of gains to be tried |
| 209 |
% Vmins is an optional list of Vmins to be tried |
| 210 |
|
| 211 |
global experiment |
| 212 |
if nargin<5 |
| 213 |
minVmin=.1; maxVmin=10; nVmins=100; |
| 214 |
Vmins=[0 logspace(log10(minVmin),log10(maxVmin),nVmins)]; |
| 215 |
% disp(Vmins) |
| 216 |
gainMin=0.0001; gainMax=1; nGains=100; |
| 217 |
gains=[1e-5 logspace(log10(gainMin),log10(gainMax),nGains)]; |
| 218 |
end |
| 219 |
|
| 220 |
rareEvent.bestGain=NaN; |
| 221 |
rareEvent.bestVMin=NaN; |
| 222 |
rareEvent.thresholddB=0; |
| 223 |
rareEvent.bestPaMindB=NaN; |
| 224 |
rareEvent.predictionLevels=[]; |
| 225 |
rareEvent.predictionsRE=[]; |
| 226 |
rareEvent.Euclid=NaN; |
| 227 |
|
| 228 |
if isempty(stimulusLevels), return, end |
| 229 |
|
| 230 |
% expected slope is negative, rareEvent can not be used |
| 231 |
if experiment.psyFunSlope<0 |
| 232 |
return |
| 233 |
end |
| 234 |
|
| 235 |
% NB calculations in microPascals! |
| 236 |
stimulusLevelsAsPressure=28 * 10.^(stimulusLevels/20); |
| 237 |
|
| 238 |
% allGains=reshape(repmat(gains,nVmins,1), 1, nVmins*nGains); |
| 239 |
% allVmins=repmat(Vmins, 1, nGains); |
| 240 |
|
| 241 |
predictions=NaN*zeros(1,length(stimulusLevels)); |
| 242 |
gainCount=0; |
| 243 |
Euclid=inf; bestVmin=0; bestGain=0; |
| 244 |
for gain= gains |
| 245 |
gainCount=gainCount+1; |
| 246 |
VminCount=0; |
| 247 |
|
| 248 |
% 1 ? exp(-d (g P ? A)) |
| 249 |
for Vmin=Vmins |
| 250 |
VminCount=VminCount+1; |
| 251 |
% all levelsPhaseTwo are simultaneously assessed |
| 252 |
|
| 253 |
% original linear function |
| 254 |
% gP_Vmin=gain*stimulusLevelsAsPressure-Vmin; |
| 255 |
% idx=(gP_Vmin>0); |
| 256 |
% predictions(idx)= 1-exp(-duration*(gP_Vmin(idx))); |
| 257 |
% new log function log(r) =gP-A |
| 258 |
|
| 259 |
% log function |
| 260 |
gP_Vmin=gain*stimulusLevelsAsPressure-Vmin; |
| 261 |
idx=(gP_Vmin>0); |
| 262 |
predictions(idx)= 1-exp(-duration*exp(gP_Vmin(idx))); |
| 263 |
predictions(~idx)=0; |
| 264 |
|
| 265 |
% % square function |
| 266 |
% P_Vmin=stimulusLevelsAsPressure-Vmin; |
| 267 |
% idx=(P_Vmin)>0; |
| 268 |
% predictions(idx)= 1-exp(-duration*gain*(P_Vmin(idx)).^2); |
| 269 |
% predictions(~idx)=0; |
| 270 |
|
| 271 |
|
| 272 |
% NB the error is equally weighted for each stimulus/response pair |
| 273 |
% this is not the same as equal weighting for each distinct level |
| 274 |
error=(predictions - responsesPhaseTwo).^2; |
| 275 |
error=mean(error(~isnan(error))); |
| 276 |
if error<Euclid |
| 277 |
Euclid=error; |
| 278 |
bestVmin=Vmin; |
| 279 |
bestVminCount=VminCount; |
| 280 |
% bestGainCount=gainCount; |
| 281 |
bestGain=gain; |
| 282 |
end |
| 283 |
end |
| 284 |
end |
| 285 |
% disp(Vmins) |
| 286 |
|
| 287 |
% if bestGainCount==1 | bestGainCount==nGains |
| 288 |
% disp(['gain estimate ' num2str(gains(bestGainCount)) ' may be out of range']) |
| 289 |
% end |
| 290 |
|
| 291 |
[rareEvent.Euclid idx]=min(Euclid); |
| 292 |
rareEvent.bestGain=bestGain; |
| 293 |
rareEvent.bestVMin=bestVmin; |
| 294 |
rareEvent.thresholdPa=(-log(0.5)/duration + rareEvent.bestVMin)/rareEvent.bestGain; |
| 295 |
rareEvent.thresholddB=20*log10(rareEvent.thresholdPa/28); |
| 296 |
rareEvent.bestPaMindB=20*log10((rareEvent.bestVMin/rareEvent.bestGain)/28); |
| 297 |
|
| 298 |
predictionLevels= -50:1:120; |
| 299 |
rareEvent.predictionLevels=predictionLevels; |
| 300 |
stimulusLevelsAsPressure=28*10.^(predictionLevels/20); |
| 301 |
|
| 302 |
% gP_Vmin=rareEvent.bestGain*stimulusLevelsAsPressure-rareEvent.bestVMin; |
| 303 |
% rareEvent.predictionsRE= 1-exp(-duration*(gP_Vmin)); |
| 304 |
|
| 305 |
gP_Vmin=rareEvent.bestGain*stimulusLevelsAsPressure-rareEvent.bestVMin; |
| 306 |
rareEvent.predictionsRE= 1-exp(-duration*exp(gP_Vmin)); |
| 307 |
|
| 308 |
% P_Vmin=stimulusLevelsAsPressure-Vmin; |
| 309 |
% predictions= 1-exp(-duration*gain*P_Vmin.^2); |
| 310 |
% predictions(predictions<0)=0; |
| 311 |
% rareEvent.predictionsRE=predictions; |
| 312 |
|
| 313 |
% rareEvent |
| 314 |
|