Supposons que je dispose de certaines données pour lesquelles je souhaite adapter un modèle paramétrisé. Mon objectif est de trouver la meilleure valeur pour ce paramètre de modèle.
Je fais la sélection de modèle en utilisant un AIC / BIC / MDL type de critère) qui récompense les modèles avec une faible erreur mais pénalise également les modèles avec une grande complexité (nous ' Nous cherchons pour ainsi dire l'explication la plus simple et la plus convaincante de ces données, à la rasoir d'Occam ).
Après ce qui précède, voici un exemple du type de choses que j’obtiens pour trois critères différents (deux doivent être minimisés et un optimisé):
Visuellement, vous pouvez facilement voir la forme du coude et choisir une valeur pour le paramètre quelque part dans cette région. Le problème est que je le fais pour un grand nombre d'expériences et qu'il me faut un moyen de le trouver. valeur sans intervention.
Ma première intuition a été d'essayer de tracer une ligne à un angle de 45 degrés depuis le coin et de continuer à la déplacer jusqu'à ce qu'elle intersecte la courbe, mais c'est plus facile à dire qu'à faire :) De plus, la région d'intérêt peut manquer si la courbe est quelque peu asymétrique.
Avez-vous des idées sur la façon de mettre en œuvre cela ou de meilleures idées?
Voici les échantillons nécessaires pour reproduire l’une des parcelles ci-dessus:
curve = [8.4663 8.3457 5.4507 5.3275 4.8305 4.7895 4.6889 4.6833 4.6819 4.6542 4.6501 4.6287 4.6162 4.585 4.5535 4.5134 4.474 4.4089 4.3797 4.3494 4.3268 4.3218 4.3206 4.3206 4.3203 4.2975 4.2864 4.2821 4.2544 4.2288 4.2281 4.2265 4.2226 4.2206 4.2146 4.2144 4.2114 4.1923 4.19 4.1894 4.1785 4.178 4.1694 4.1694 4.1694 4.1556 4.1498 4.1498 4.1357 4.1222 4.1222 4.1217 4.1192 4.1178 4.1139 4.1135 4.1125 4.1035 4.1025 4.1023 4.0971 4.0969 4.0915 4.0915 4.0914 4.0836 4.0804 4.0803 4.0722 4.065 4.065 4.0649 4.0644 4.0637 4.0616 4.0616 4.061 4.0572 4.0563 4.056 4.0545 4.0545 4.0522 4.0519 4.0514 4.0484 4.0467 4.0463 4.0422 4.0392 4.0388 4.0385 4.0385 4.0383 4.038 4.0379 4.0375 4.0364 4.0353 4.0344];
plot(1:100, curve)
J'ai accepté la solution donnée par Jonas . Fondamentalement, pour chaque point p
de la courbe, nous trouvons celui avec la distance maximale d
donné par:
Un moyen rapide de trouver le coude consiste à tracer une ligne du premier au dernier point de la courbe, puis à rechercher le point de données le plus éloigné de cette ligne.
Cela dépend bien sûr un peu du nombre de points que vous avez dans la partie plate de la ligne, mais si vous testez le même nombre de paramètres à chaque fois, le résultat devrait être raisonnablement correct.
curve = [8.4663 8.3457 5.4507 5.3275 4.8305 4.7895 4.6889 4.6833 4.6819 4.6542 4.6501 4.6287 4.6162 4.585 4.5535 4.5134 4.474 4.4089 4.3797 4.3494 4.3268 4.3218 4.3206 4.3206 4.3203 4.2975 4.2864 4.2821 4.2544 4.2288 4.2281 4.2265 4.2226 4.2206 4.2146 4.2144 4.2114 4.1923 4.19 4.1894 4.1785 4.178 4.1694 4.1694 4.1694 4.1556 4.1498 4.1498 4.1357 4.1222 4.1222 4.1217 4.1192 4.1178 4.1139 4.1135 4.1125 4.1035 4.1025 4.1023 4.0971 4.0969 4.0915 4.0915 4.0914 4.0836 4.0804 4.0803 4.0722 4.065 4.065 4.0649 4.0644 4.0637 4.0616 4.0616 4.061 4.0572 4.0563 4.056 4.0545 4.0545 4.0522 4.0519 4.0514 4.0484 4.0467 4.0463 4.0422 4.0392 4.0388 4.0385 4.0385 4.0383 4.038 4.0379 4.0375 4.0364 4.0353 4.0344];
%# get coordinates of all the points
nPoints = length(curve);
allCoord = [1:nPoints;curve]'; %'# SO formatting
%# pull out first point
firstPoint = allCoord(1,:);
%# get vector between first and last point - this is the line
lineVec = allCoord(end,:) - firstPoint;
%# normalize the line vector
lineVecN = lineVec / sqrt(sum(lineVec.^2));
%# find the distance from each point to the line:
%# vector between all points and first point
vecFromFirst = bsxfun(@minus, allCoord, firstPoint);
%# To calculate the distance to the line, we split vecFromFirst into two
%# components, one that is parallel to the line and one that is perpendicular
%# Then, we take the norm of the part that is perpendicular to the line and
%# get the distance.
%# We find the vector parallel to the line by projecting vecFromFirst onto
%# the line. The perpendicular vector is vecFromFirst - vecFromFirstParallel
%# We project vecFromFirst by taking the scalar product of the vector with
%# the unit vector that points in the direction of the line (this gives us
%# the length of the projection of vecFromFirst onto the line). If we
%# multiply the scalar product by the unit vector, we have vecFromFirstParallel
scalarProduct = dot(vecFromFirst, repmat(lineVecN,nPoints,1), 2);
vecFromFirstParallel = scalarProduct * lineVecN;
vecToLine = vecFromFirst - vecFromFirstParallel;
%# distance to line is the norm of vecToLine
distToLine = sqrt(sum(vecToLine.^2,2));
%# plot the distance to the line
figure('Name','distance from curve to line'), plot(distToLine)
%# now all you need is to find the maximum
[maxDist,idxOfBestPoint] = max(distToLine);
%# plot
figure, plot(curve)
hold on
plot(allCoord(idxOfBestPoint,1), allCoord(idxOfBestPoint,2), 'or')
Au cas où quelqu'un aurait besoin d'une version Python fonctionnelle du Matlab code posté par Jonas above.
import numpy as np
curve = [8.4663, 8.3457, 5.4507, 5.3275, 4.8305, 4.7895, 4.6889, 4.6833, 4.6819, 4.6542, 4.6501, 4.6287, 4.6162, 4.585, 4.5535, 4.5134, 4.474, 4.4089, 4.3797, 4.3494, 4.3268, 4.3218, 4.3206, 4.3206, 4.3203, 4.2975, 4.2864, 4.2821, 4.2544, 4.2288, 4.2281, 4.2265, 4.2226, 4.2206, 4.2146, 4.2144, 4.2114, 4.1923, 4.19, 4.1894, 4.1785, 4.178, 4.1694, 4.1694, 4.1694, 4.1556, 4.1498, 4.1498, 4.1357, 4.1222, 4.1222, 4.1217, 4.1192, 4.1178, 4.1139, 4.1135, 4.1125, 4.1035, 4.1025, 4.1023, 4.0971, 4.0969, 4.0915, 4.0915, 4.0914, 4.0836, 4.0804, 4.0803, 4.0722, 4.065, 4.065, 4.0649, 4.0644, 4.0637, 4.0616, 4.0616, 4.061, 4.0572, 4.0563, 4.056, 4.0545, 4.0545, 4.0522, 4.0519, 4.0514, 4.0484, 4.0467, 4.0463, 4.0422, 4.0392, 4.0388, 4.0385, 4.0385, 4.0383, 4.038, 4.0379, 4.0375, 4.0364, 4.0353, 4.0344]
nPoints = len(curve)
allCoord = np.vstack((range(nPoints), curve)).T
np.array([range(nPoints), curve])
firstPoint = allCoord[0]
lineVec = allCoord[-1] - allCoord[0]
lineVecNorm = lineVec / np.sqrt(np.sum(lineVec**2))
vecFromFirst = allCoord - firstPoint
scalarProduct = np.sum(vecFromFirst * np.matlib.repmat(lineVecNorm, nPoints, 1), axis=1)
vecFromFirstParallel = np.outer(scalarProduct, lineVecNorm)
vecToLine = vecFromFirst - vecFromFirstParallel
distToLine = np.sqrt(np.sum(vecToLine ** 2, axis=1))
idxOfBestPoint = np.argmax(distToLine)
Le choix du modèle théorique de l’information est qu’il prend déjà en compte le nombre de paramètres. Par conséquent, il n'est pas nécessaire de trouver un coude, il vous suffit de trouver le minimum.
Trouver le coude de la courbe n'est pertinent que lorsque vous utilisez l'ajustement. Même alors, la méthode que vous choisissez pour sélectionner le coude consiste en quelque sorte à imposer une pénalité au nombre de paramètres. Pour sélectionner le coude, vous souhaiterez réduire la distance entre l’origine et la courbe. La pondération relative des deux dimensions dans le calcul de la distance créera un terme de pénalité inhérent. Le critère théorique de l'information définit cette mesure en fonction du nombre de paramètres et du nombre d'échantillons de données utilisés pour estimer le modèle.
Recommandation finale: Utilisez BIC et prenez le minimum.
Tout d’abord, une analyse rapide du calcul: la première dérivée f'
de chaque graphique représente la vitesse à laquelle la fonction f
en cours de représentation change. Le second dérivé f''
représente le taux auquel f'
change. Si f''
est petit, cela signifie que le graphique change de direction à un rythme modéré. Mais si f''
est grand, cela signifie que le graphique change rapidement de direction.
Vous voulez isoler les points où f''
est le plus grand sur le domaine du graphique. Ce seront des points candidats à sélectionner pour votre modèle optimal. Le point que vous choisissez devra être à vous, étant donné que vous n'avez pas précisé exactement à quel point vous valorisez la forme physique par rapport à la complexité.
Donc, une façon de résoudre ce problème serait d’adapter deux lignes auLde votre coude. Mais comme il n’ya que quelques points dans une partie de la courbe (comme je l’ai mentionné dans le commentaire), l’ajustement des lignes prend un coup, à moins que vous ne détectiez les points espacés et que vous interpoliez entre eux pour créer une série plus uniforme et alors utilise RANSAC pour trouver deux lignes correspondant auL- un peu compliqué mais pas impossible.
Voici donc une solution plus simple: les graphiques que vous avez mis en place ressemblent à ce qu'ils font grâce à la mise à l'échelle de MATLAB (évidemment). Donc tout ce que j'ai fait était de minimiser la distance entre "l'origine" et vos points en utilisant les informations d'échelle.
Remarque: L'estimation de l'origine peut être améliorée de façon spectaculaire, mais je vous laisse cela.
Voici le code:
%% Order
curve = [8.4663 8.3457 5.4507 5.3275 4.8305 4.7895 4.6889 4.6833 4.6819 4.6542 4.6501 4.6287 4.6162 4.585 4.5535 4.5134 4.474 4.4089 4.3797 4.3494 4.3268 4.3218 4.3206 4.3206 4.3203 4.2975 4.2864 4.2821 4.2544 4.2288 4.2281 4.2265 4.2226 4.2206 4.2146 4.2144 4.2114 4.1923 4.19 4.1894 4.1785 4.178 4.1694 4.1694 4.1694 4.1556 4.1498 4.1498 4.1357 4.1222 4.1222 4.1217 4.1192 4.1178 4.1139 4.1135 4.1125 4.1035 4.1025 4.1023 4.0971 4.0969 4.0915 4.0915 4.0914 4.0836 4.0804 4.0803 4.0722 4.065 4.065 4.0649 4.0644 4.0637 4.0616 4.0616 4.061 4.0572 4.0563 4.056 4.0545 4.0545 4.0522 4.0519 4.0514 4.0484 4.0467 4.0463 4.0422 4.0392 4.0388 4.0385 4.0385 4.0383 4.038 4.0379 4.0375 4.0364 4.0353 4.0344];
x_axis = 1:numel(curve);
points = [x_axis ; curve ]'; %' - SO formatting
%% Get the scaling info
f = figure(1);
plot(points(:,1),points(:,2));
ticks = get(get(f,'CurrentAxes'),'YTickLabel');
ticks = str2num(ticks);
aspect = get(get(f,'CurrentAxes'),'DataAspectRatio');
aspect = [aspect(2) aspect(1)];
close(f);
%% Get the "Origin"
O = [x_axis(1) ticks(1)];
%% Scale the data - now the scaled values look like MATLAB''s idea of
% what a good plot should look like
scaled_O = O.*aspect;
scaled_points = bsxfun(@times,points,aspect);
%% Find the closest point
del = sum((bsxfun(@minus,scaled_points,scaled_O).^2),2);
[val ind] = min(del);
best_ROC = [ind curve(ind)];
%% Display
plot(x_axis,curve,'.-');
hold on;
plot(O(1),O(2),'r*');
plot(best_ROC(1),best_ROC(2),'k*');
Résultats:
AUSSI pour la courbe Fit(maximize)
, vous devrez passer à Origin en [x_axis(1) ticks(end)]
.
Voici la solution donnée par Jonas implémentée dans R:
elbow_Finder <- function(x_values, y_values) {
# Max values to create line
max_x_x <- max(x_values)
max_x_y <- y_values[which.max(x_values)]
max_y_y <- max(y_values)
max_y_x <- x_values[which.max(y_values)]
max_df <- data.frame(x = c(max_y_x, max_x_x), y = c(max_y_y, max_x_y))
# Creating straight line between the max values
fit <- lm(max_df$y ~ max_df$x)
# Distance from point to line
distances <- c()
for(i in 1:length(x_values)) {
distances <- c(distances, abs(coef(fit)[2]*x_values[i] - y_values[i] + coef(fit)[1]) / sqrt(coef(fit)[2]^2 + 1^2))
}
# Max distance point
x_max_dist <- x_values[which.max(distances)]
y_max_dist <- y_values[which.max(distances)]
return(c(x_max_dist, y_max_dist))
}
D'une manière simple et intuitive, nous pouvons dire que
Si nous traçons deux lignes de n'importe quel point de la courbe jusqu'aux deux extrémités de la courbe, le point auquel ces deux lignes forment le plus petit angle en degrés est le point souhaité.
Ici, les deux lignes peuvent être visualisées comme les bras et le point comme le coude!
La méthode double dérivée. Cela ne semble toutefois pas bien fonctionner pour les données bruitées. Pour la sortie, il vous suffit de trouver la valeur maximale de d2 pour identifier le coude. Cette implémentation est en R.
elbow_Finder <- function(x_values, y_values) {
i_max <- length(x_values) - 1
# First and second derived
first_derived <- list()
second_derived <- list()
# First derived
for(i in 2:i_max){
slope1 <- (y_values[i+1] - y_values[i]) / (x_values[i+1] - x_values[i])
slope2 <- (y_values[i] - y_values[i-1]) / (x_values[i] - x_values[i-1])
slope_avg <- (slope1 + slope2) / 2
first_derived[[i]] <- slope_avg
}
first_derived[[1]] <- NA
first_derived[[i_max+1]] <- NA
first_derived <- unlist(first_derived)
# Second derived
for(i in 3:i_max-1){
d1 <- (first_derived[i+1] - first_derived[i]) / (x_values[i+1] - x_values[i])
d2 <- (first_derived[i] - first_derived[i-1]) / (x_values[i] - x_values[i-1])
d_avg <- (d1 + d2) / 2
second_derived[[i]] <- d_avg
}
second_derived[[1]] <- NA
second_derived[[2]] <- NA
second_derived[[i_max]] <- NA
second_derived[[i_max+1]] <- NA
second_derived <- unlist(second_derived)
return(list(d1 = first_derived, d2 = second_derived))
}
Je travaille sur la détection du genou et du coude depuis un certain temps. En aucun cas, je suis un expert. Certaines méthodes peuvent être pertinentes pour ce problème.
DFDT signifie Dynamic First Derivate Seuil. Il calcule la première dérivée et utilise un algorithme de seuillage pour détecter le point du genou/du coude. DSDT est similaire mais utilise la dérivée seconde, mon évaluation montre qu'ils ont des performances similaires.
La méthode S est une extension de la méthode L. La méthode L adapte deux lignes droites à votre courbe, l’interception entre les deux lignes correspond au point genou/coude. Le meilleur ajustement est trouvé en bouclant des points globaux, en ajustant les lignes et en évaluant le MSE (Mean Square Error). La méthode S s'adapte à 3 lignes droites, cela améliore la précision mais nécessite également quelques calculs supplémentaires.
Tout mon code est disponible sur GitHub . En outre, cet article peut vous aider à trouver plus d’informations sur le sujet. Il ne fait que quatre pages et doit donc être facile à lire. Vous pouvez utiliser le code et si vous souhaitez discuter de l'une de ces méthodes, n'hésitez pas.
Si vous le souhaitez, je l'ai converti en exercice R (pardon, mon style de codage non optimisé).
elbow.point = function(x){
elbow.curve = c(x)
nPoints = length(elbow.curve);
allCoord = cbind(c(1:nPoints),c(elbow.curve))
# pull out first point
firstPoint = allCoord[1,]
# get vector between first and last point - this is the line
lineVec = allCoord[nPoints,] - firstPoint;
# normalize the line vector
lineVecN = lineVec / sqrt(sum(lineVec^2));
# find the distance from each point to the line:
# vector between all points and first point
vecFromFirst = lapply(c(1:nPoints), function(x){
allCoord[x,] - firstPoint
})
vecFromFirst = do.call(rbind, vecFromFirst)
rep.row<-function(x,n){
matrix(rep(x,each=n),nrow=n)
}
scalarProduct = matrix(nrow = nPoints, ncol = 2)
scalarProduct[,1] = vecFromFirst[,1] * rep.row(lineVecN,nPoints)[,1]
scalarProduct[,2] = vecFromFirst[,2] * rep.row(lineVecN,nPoints)[,2]
scalarProduct = as.matrix(rowSums(scalarProduct))
vecFromFirstParallel = matrix(nrow = nPoints, ncol = 2)
vecFromFirstParallel[,1] = scalarProduct * lineVecN[1]
vecFromFirstParallel[,2] = scalarProduct * lineVecN[2]
vecToLine = lapply(c(1:nPoints), function(x){
vecFromFirst[x,] - vecFromFirstParallel[x,]
})
vecToLine = do.call(rbind, vecToLine)
# distance to line is the norm of vecToLine
distToLine = as.matrix(sqrt(rowSums(vecToLine^2)))
##
which.max(distToLine)
}
l'entrée x de la fonction devrait être une liste/un vecteur avec vos valeurs