Scikit-learn: MSE est négatif lorsqu'il est renvoyé par cross_val_score

Créé le 12 sept. 2013  ·  58Commentaires  ·  Source: scikit-learn/scikit-learn

L'erreur quadratique moyenne renvoyée par sklearn.cross_validation.cross_val_score est toujours négative. Bien qu'il s'agisse d'une décision conçue pour que la sortie de cette fonction puisse être utilisée pour la maximisation étant donné certains hyperparamètres, il est extrêmement déroutant d'utiliser directement cross_val_score. Au moins, je me suis demandé comment la moyenne d'un carré pouvait être négative et j'ai pensé que cross_val_score ne fonctionnait pas correctement ou n'utilisait pas la métrique fournie. Ce n'est qu'après avoir fouillé dans le code source de sklearn que j'ai réalisé que le signe était inversé.

Ce comportement est mentionné dans make_scorer dans scorer.py, mais il n'est pas mentionné dans cross_val_score et je pense qu'il devrait l'être, car sinon, cela fait penser que cross_val_score ne fonctionne pas correctement.

API Bug Documentation

Commentaire le plus utile

peut-être que negmse résoudrait le problème

Tous les 58 commentaires

Vous faites référence à

greater_is_better : boolean, default=True

Whether score_func is a score function (default), meaning high is good, 
or a loss function, meaning low is good. In the latter case, the scorer 
object will sign-flip the outcome of the score_func.

sur http://scikit-learn.org/stable/modules/generated/sklearn.metrics.make_scorer.html
? (juste pour référence)

Je suis d'accord que cela peut être plus clair dans la documentation cross_val_score

Merci d'avoir signalé

En effet, nous avons négligé ce problème lors de la refactorisation de Scorer. Ce qui suit est très contre-intuitif:

>>> import numpy as np
>>> from sklearn.datasets import load_boston
>>> from sklearn.linear_model import RidgeCV
>>> from sklearn.cross_validation import cross_val_score

>>> boston = load_boston()
>>> np.mean(cross_val_score(RidgeCV(), boston.data, boston.target, scoring='mean_squared_error'))
-154.53681864311497

/ cc @larsmans

BTW je ne suis pas d'accord pour dire que c'est un problème de documentation. C'est cross_val_score doit renvoyer la valeur avec le signe qui correspond au nom du score. Idéalement, le GridSearchCV(*params).fit(X, y).best_score_ devrait également être cohérent. Sinon, l'API est très déroutante.

Je conviens également qu'un changement pour retourner le MSE réel sans le signe commuté serait la meilleure option.

L'objet scorer pourrait simplement stocker le drapeau greater_is_better et chaque fois que le scorer est utilisé, le signe pourrait être retourné au cas où il serait nécessaire, par exemple dans GridSearchCV .

Je conviens que nous avons un problème de convivialité ici, mais je ne suis pas entièrement d'accord avec la solution de @ogrisel selon laquelle nous devrions

renvoie la valeur avec le signe qui correspond au nom du score

parce que c'est un hack peu fiable à long terme. Que faire si quelqu'un définit un marqueur personnalisé avec un nom tel que mse ? Et s'ils suivent le modèle de dénomination mais enveloppent le marqueur dans un décorateur qui change le nom?

L'objet scorer pourrait simplement stocker le drapeau greater_is_better et chaque fois que le scorer est utilisé, le signe pourrait être retourné au cas où cela serait nécessaire, par exemple dans GridSearchCV.

C'est ce que les buteurs ont fait à l'origine, lors du développement entre les versions 0.13 et 0.14 et cela a rendu leur définition beaucoup plus difficile. Cela rendait également le code difficile à suivre car l'attribut greater_is_better semblait disparaître dans le code de scorer, pour réapparaître au milieu du code de recherche de grille. Une classe spéciale Scorer était nécessaire pour faire quelque chose que, idéalement, une simple fonction ferait.

Je pense que si nous voulons optimiser les scores, ils doivent être _maximisés_. Dans un souci de convivialité, je pense que nous pourrions introduire un paramètre score_is_loss["auto", True, False] qui ne change que l'affichage des scores et peut utiliser une heuristique basée sur les noms intégrés.

C'était une réponse précipitée parce que je devais descendre du train. Ce que je voulais dire par "affichage" est en fait la valeur de retour de cross_val_score . Je pense que les marqueurs doivent être simples et uniformes et que les algorithmes doivent toujours maximiser.

Cela introduit une asymétrie entre les scorers intégrés et personnalisés.

Ping @GaelVaroquaux.

J'aime la solution score_is_loss, ou quelque chose du genre .. le changement de signe pour correspondre au nom du score semble difficile à maintenir pourrait causer des problèmes comme @larsmans l'a mentionné

quelle est la conclusion, quelle solution devrions-nous opter? :)

@tdomhan @jaquesgrobler @larsmans Savez-vous si cela s'applique également à r2 ? Je remarque que les scores r2 retournés par GridSearchCV sont également principalement négatifs pour ElasticNet , Lasso et Ridge .

R² peut être positif ou négatif, et négatif signifie simplement que votre modèle fonctionne très mal.

IIRC, @GaelVaroquaux était un partisan du retour d'un nombre négatif lorsque greater_is_better=False .

r2 est une fonction de score (plus grand est meilleur), donc cela devrait être positif si votre modèle est bon - mais c'est l'une des rares mesures de performance qui peut en fait être négative, ce qui signifie pire que 0.

Quel est le consensus sur cette question? À mon avis, cross_val_score est un outil d'évaluation, pas un outil de sélection de modèle. Il doit donc renvoyer les valeurs d'origine.

Je peux le corriger dans mon PR # 2759, car les modifications que j'ai apportées le rendent vraiment facile à corriger. L'astuce consiste à ne pas retourner le signe à l'avance mais, à la place, à accéder à l'attribut greater_is_better sur le marqueur lors de la recherche dans la grille.

Quel est le consensus sur cette question? À mon avis, cross_val_score est
un outil d'évaluation, pas un outil de sélection de modèle. Il devrait donc revenir
les valeurs d'origine.

Des cas particuliers sont les comportements variables sont une source de problèmes dans les logiciels.

Je pense simplement que nous devrions renommer "mse" en "negated_mse" dans la liste
de chaînes de notation acceptables.

Que faire si quelqu'un définit un marqueur personnalisé avec un nom tel que mse? Et s'ils suivent le modèle de dénomination mais enveloppent le marqueur dans un décorateur qui change le nom?

Je ne pense pas que @ogrisel suggérait d'utiliser la correspondance de nom, juste pour être cohérent avec la métrique d'origine. Corrigez-moi si je me trompe @ogrisel.

Je pense simplement que nous devrions renommer "mse" en "negated_mse" dans la liste des chaînes de notation acceptables.

C'est complètement peu intuitif si vous ne connaissez pas les rouages ​​de scikit-learn. Si vous devez plier le système comme ça, je pense que c'est le signe qu'il y a un problème de conception.

C'est complètement peu intuitif si vous ne connaissez pas les rouages ​​de scikit-learn.
Si vous devez plier le système comme ça, je pense que c'est le signe qu'il y a un
problème de conception.

Je ne suis pas d'accord. Les humains comprennent les choses avec beaucoup de connaissances préalables et
le contexte. Ils sont presque systématiques. Essayer d'intégrer cela dans le logiciel
donne une liste de courses comme un ensemble de cas spéciaux. Non seulement cela fait
logiciel difficile à maintenir, mais cela signifie aussi que les personnes qui n'ont pas
dans l'esprit ces exceptions se heurtent à des comportements surprenants et écrivent des bogues
code en utilisant la bibliothèque.

Quel cas particulier avez-vous en tête?

Pour être clair, je pense que les scores de validation croisée stockés dans l'objet GridSearchCV doivent _aussi_ être les valeurs d'origine (pas avec le signe inversé).

AFAIK, le retournement du signe a été introduit de manière à rendre la mise en œuvre de la recherche de grille un peu plus simple mais n'était pas censé affecter la convivialité.

Quel cas particulier avez-vous en tête?

Eh bien, le fait que pour certaines métriques, c'est mieux, alors que pour d'autres
c'est le contraire.

AFAIK, le retournement du signe a été introduit afin de faire la recherche de grille
mise en œuvre un peu plus simple mais n'était pas censée affecter
utilisabilité.

Il ne s'agit pas de recherche de grille, mais de séparation des préoccupations: scores
doivent être utilisables sans rien savoir à leur sujet, ou bien coder pour
traiter leurs spécificités s'étendra à l'ensemble de la base de code. Il y a
déjà beaucoup de code de scoring.

Mais cela reporte quelque peu le problème au code utilisateur. Personne ne veut tracer "MSE annulé", les utilisateurs devront donc retourner les signes dans leur code. Cela n'est pas pratique, en particulier pour les rapports de validation croisée à plusieurs métriques (PR # 2759), car vous devez gérer chaque métrique individuellement. Je me demande si nous pouvons avoir le meilleur des deux mondes: code générique et résultats intuitifs.

Mais cela reporte quelque peu le problème au code utilisateur. Personne ne veut
pour tracer "MSE annulé" afin que les utilisateurs doivent retourner les signes dans leur
code.

Certainement pas la fin du monde. Notez que lorsque vous lisez des articles ou
en regardant des présentations, j'ai le même problème: quand le graphique n'est pas
bravo, je perds un peu de temps et de bande passante mentale en essayant de
figure si plus grand est meilleur ou pas.

Ceci n'est pas pratique, en particulier pour la validation croisée à plusieurs métriques
rapports (PR # 2759), car vous devez gérer chaque métrique individuellement.

Pourquoi. Si vous acceptez simplement que c'est toujours plus gros, c'est mieux,
tout est plus simple, y compris l'interprétation des résultats.

Je me demande si nous pouvons avoir le meilleur des deux mondes: code générique et
résultats intuitifs.

Le risque est d'avoir un code très complexe qui nous ralentit pour la maintenance
et développement. Scikit-learn prend du poids.

Si tu acceptes juste que c'est toujours plus gros, c'est mieux

C'est ce qu'elle a dit :)

Plus sérieusement, je pense qu'une des raisons pour lesquelles cela déroute les gens est que la sortie de cross_val_score n'est pas cohérente avec les métriques. Si nous suivons votre logique, toutes les métriques de sklearn.metrics devraient suivre "plus c'est grand, mieux c'est".

C'est ce qu'elle a dit :)

Joli!

Plus sérieusement, je pense que l'une des raisons pour lesquelles cela déroute les gens est que
la sortie de cross_val_score n'est pas cohérente avec les métriques. Si nous
suivez votre logique, toutes les métriques de sklearn.metrics doivent suivre "plus grand
est mieux".

D'accord. C'est pourquoi j'aime l'idée de changer le nom: cela apparaîtrait
aux yeux des gens.

Plus sérieusement, je pense que l'une des raisons pour lesquelles cela déroute les gens est que la sortie de cross_val_score n'est pas cohérente avec les métriques.

Et cela rend scoring plus mystérieux qu'il ne l'est.

J'ai été mordu par cela aujourd'hui en 0.16.1 en essayant de faire une régression linéaire. Bien que le signe du score ne soit apparemment plus inversé pour les classificateurs, il l'est toujours pour la régression linéaire. Pour ajouter à la confusion, LinearRegression.score () renvoie une version non inversée de la partition.

Je suggérerais de rendre tout cela cohérent et de renvoyer le score sans signe inversé pour les modèles linéaires également.

Exemple:

from sklearn import linear_model
from sklearn.naive_bayes import GaussianNB
from sklearn import cross_validation
from sklearn import datasets
iris = datasets.load_iris()
nb = GaussianNB()
scores = cross_validation.cross_val_score(nb, iris.data, iris.target)
print("NB score:\t  %0.3f" % scores.mean() )

iris_reg_data = iris.data[:,:3]
iris_reg_target = iris.data[:,3]
lr = linear_model.LinearRegression()
scores = cross_validation.cross_val_score(lr, iris_reg_data, iris_reg_target)
print("LR score:\t %0.3f" % scores.mean() )

lrf = lr.fit(iris_reg_data, iris_reg_target)
score = lrf.score(iris_reg_data, iris_reg_target)
print("LR.score():\t  %0.3f" % score )

Cela donne:

NB score:     0.934    # sign is not flipped
LR score:    -0.755    # sign is flipped
LR.score():   0.938    # sign is not flipped

La validation croisée renverse tous les signes des modèles où plus c'est mieux. Je suis toujours en désaccord avec cette décision. Je pense que les principaux promoteurs étaient @GaelVaroquaux et peut-être @mblondel [je me suis souvenu que vous avez refactoré le code du marqueur].

Oh tant pis, toute la discussion est ci-dessus.
J'ai l'impression de retourner le signe par défaut dans mse et r2 est encore moins intuitif: - /

@Huitzilo GaussianNB est un classificateur et utilise la précision comme marqueur par défaut. LinearRegression est un régresseur et utilise le score r2 comme score par défaut. Le deuxième score est négatif, mais rappelez-vous que le score r2 _peut_ être négatif. En outre, iris est un ensemble de données multiclasses. Par conséquent, les objectifs sont catégoriques. Vous ne pouvez pas utiliser de régresseur.

à droite, j'étais un peu confus sur ce qui se passe, r2 n'est pas retourné ... seulement mse le serait.

Peut-être qu'une solution à tout le problème est de renommer la chose negmse ?

@mblondel, bien sûr, vous avez raison, désolé. J'étais juste en train de donner un exemple de régression, et dans mon excès de confiance sur les données d'iris, je pensais que prédire la fonction n ° 4 des autres fonctionnerait (avec un R2 positif). Mais il n'a pas, par conséquent, R2 négatif. Aucun signe ne se retourne ici. D'ACCORD. Ma faute.

Pourtant, le signe est retourné dans le MSE que j'obtiens de cross_val_score .

Peut-être que c'est juste moi, mais je trouve cette incohérence extrêmement déroutante (ce qui m'a amené dans ce problème). Pourquoi MSE devrait-il être inversé, mais pas R2?

Peut-être que c'est juste moi, mais je trouve cette incohérence extrêmement déroutante (ce qui m'a amené dans ce problème). Pourquoi MSE devrait-il être inversé, mais pas R2?

Parce que la sémantique du score est plus élevée, c'est mieux. Un MSE élevé est mauvais.

peut-être que negmse résoudrait le problème

@amueller Je suis d'accord, rendre le signe inversé explicite dans le nom du paramètre de score aiderait certainement à éviter toute confusion.

Peut-être que la documentation de [1] pourrait également être encore plus explicite sur la façon dont les signes retournent pour certains scores. Dans mon cas, j'avais besoin d'informations rapidement et je n'ai regardé que le tableau sous 3.1.1.1, mais je n'ai pas lu le texte (ce qui explique le principe "plus c'est gros c'est mieux"). À mon humble avis, ajouter un commentaire pour mse, erreur médiane et moyenne absolue dans le tableau sous 3.1.1.1, indiquant leur négation, aiderait déjà beaucoup, sans aucune modification du code réel.

[1] http://scikit-learn.org/stable/modules/model_evaluation.html#scoring -parameter

Je suis tombé sur un cas très intéressant:

from sklearn.cross_validation import cross_val_score
model = LinearRegression()
scores = cross_val_score(model, X, target, cv=2, scoring='r2')
scores

Résulte en

array([-0.17026282, -2.21315179])

Pour le même jeu de données, le code suivant

model = LinearRegression()
model.fit(X, target)
prediction = model.predict(X)
print r2_score(target, prediction)

donne une valeur raisonnable

0.353035789318

AFAIK pour le modèle de régression linéaire (avec interception) on ne peut pas obtenir R ^ 2> 1 ou R ^ 2 <0

Ainsi, le résultat cv ne ressemble pas à R ^ 2 avec un signe inversé. Ai-je tort à un moment donné?

r2 peut être négatif (pour les mauvais modèles). Il ne peut pas être supérieur à 1.

Vous êtes probablement surajusté. essayer:

from sklearn.cross_validation import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, target, test_size=0.2, random_state=0)
model = LinearRegression()
model.fit(X_train, y_train)
pred_train = model.predict(X_train)
print("train r2: %f" % r2_score(y_train, pred_train))

pred_test = model.predict(X_test)
print("test r2: %f" % r2_score(y_test, pred_test))

Essayez avec des valeurs différentes pour la graine entière random_state qui contrôle le fractionnement aléatoire.

peut-être que negmse résoudrait le problème

+1 pour 'neg_mse' (je pense que le trait de soulignement rend les choses plus lisibles).

Cela résout-il tous les problèmes? Y a-t-il d'autres scores plus élevés, pas mieux?

Il y a:

  • log_loss
  • mean_absolute_error
  • median_absolute_error

Selon doc/modules/model_evaluation.rst , cela devrait être tout.

Et hinge_loss je suppose?

Ajouter le préfixe neg_ à toutes ces pertes semble gênant.

Une idée serait de retourner les scores originaux (sans retournement de signe) mais au lieu de retourner un ndarray, nous retournons une classe qui étend ndarray avec des méthodes comme best() , arg_best() , best_sorted() . De cette façon, les résultats ne sont pas surprenants et nous disposons de méthodes pratiques pour récupérer les meilleurs résultats.

Il n'y a pas de marqueur pour la perte de charnière (et je ne l'ai jamais vu être utilisé pour l'évaluation).

Le scorer ne renvoie pas de tableau numpy, il renvoie un float, non?
nous pourrions renvoyer un objet score qui a un ">" personnalisé mais qui ressemble à un flotteur.
Cela me semble plus artificiel que la solution précédente, qui marquait le marqueur avec un bool "lower_is_better" qui était ensuite utilisé dans GridSearchCV.

cross_val_score renvoie un tableau.

En fait, les scores retournés par cross_val_score n'ont généralement pas besoin d'être triés, juste une moyenne.

Une autre idée est d'ajouter une méthode sorted à _BaseScorer .

my_scorer = make_scorer(my_metric, greater_is_better=False)
scores = my_scorer.sorted(scores)  # takes into account my_scorer._sign
best = scores[0]

cross_val_score renvoie un tableau, mais les scorers renvoient un float. Je pense qu'il serait étrange d'avoir une logique spécifique dans cross_val_score parce que vous aimeriez avoir le même comportement dans GridSearchCV et dans tous les autres objets CV.

Vous auriez également besoin d'une méthode argsort, car dans GridSearchCV, vous voulez le meilleur score et le meilleur index.

Comment mettre en œuvre "estimer les moyennes et les variances des erreurs des travailleurs à partir des questions de contrôle, puis calculer la moyenne pondérée après avoir supprimé le biais estimé pour les prédictions" par scikit-learn?

L'IIRC nous en avons discuté dans le sprint (l'été dernier?!) Et avons décidé d'aller avec neg_mse (ou était-ce neg-mse ) et de déprécier tous les scorers / chaînes où nous avons un signe négatif maintenant.
Est-ce toujours le consensus? Nous devrions le faire avant la 0.18 alors.
Ping @GaelVaroquaux @agramfort @jnothman @ogrisel @raghavrv

oui on s'est mis d'accord sur neg_mse AFAIK

C'était neg_mse

Nous avons aussi besoin de:

  • neg_log_loss
  • neg_mean_absolute_error
  • neg_median_absolute_error

model = séquentiel ()
keras.layers.Flatten ()
model.add (Dense (11, input_dim = 3, kernel_initializer = keras.initializers.he_normal (seed = 2),
kernel_regularizer = regularizers.l2 (2)))
keras.layers.LeakyReLU (alpha = 0,1)
model.add (Dense (8, kernel_initializer = keras.initializers.he_normal (seed = 2)))
keras.layers.LeakyReLU (alpha = 0,1)
model.add (Dense (4, kernel_initializer = keras.initializers.he_normal (seed = 2)))
keras.layers.LeakyReLU (alpha = 0,1)
model.add (Dense (1, kernel_initializer = keras.initializers.he_normal (seed = 2)))
keras.layers.LeakyReLU (alpha = 0,2)
adag = RMSprop (lr = 0,0002)
model.compile (perte = pertes.mean_squared_error,
optimiseur = adag
)
history = model.fit (X_train, Y_train, epochs = 2000,
batch_size = 20, shuffle = True)

Comment croiser valider le code ci-dessus? Je veux laisser une méthode de validation croisée à utiliser dans cela.

@shreyassks ce n'est pas le bon endroit pour votre question mais je vérifierais ceci: https://keras.io/scikit-learn-api . Enveloppez votre réseau dans un estimateur scikit-learn puis utilisez w / model_selection.cross_val_score

Oui. Je suis entièrement d'accord! Cela est également arrivé à Brier_score_loss, cela fonctionne parfaitement bien en utilisant Brier_score_loss, mais cela devient déroutant quand il vient de GridSearchCV, le retour négatif de Brier_score_loss. Au moins, il serait préférable de produire quelque chose comme, parce que Brier_score_loss est une perte (le plus bas est le meilleur), la fonction de notation inverse ici le signe pour le rendre négatif.

L'idée est que cross_val_score devrait se concentrer entièrement sur la valeur absolue du résultat. A ma connaissance, l'importance du signe négatif (-) obtenu pour MSE (erreur quadratique moyenne) dans cross_val_score n'est pas prédéfinie. Attendons la version mise à jour de sklearn où ce problème est résolu.

Pour le cas d'utilisation de la régression:
model_score = cross_val_score (modèle, df_input, df_target, scoring = 'neg_mean_squared_error', cv = 3)
J'obtiens les valeurs comme:

SVR:
[-6.20938025 -1.397376 -1.94519]
-3,183982080147279

Régression linéaire:
[-5,94898085 -9,30931808 -1,15760676]
-5,4719685646934275

Lasso:
[-7,22363814 -10,47734135 -2,20807684]
-6,6363521107522345

Crête:
[-5,95990385 -4,17946756 -1,36885809]
-3,8360764993832004

Alors, lequel est le meilleur ?
SVR?

Pour le cas d'utilisation de la régression:
J'obtiens des résultats différents lorsque j'utilise
(1) "cross_val_score" avec score = 'neg_mean_squared_error'
et
(2) Pour les mêmes entrées quand j'utilise "GridSearchCV" et coche le 'best_score_'

Pour les modèles de régression, lequel est le meilleur?

  • "cross_val_score" avec score = 'neg_mean_squared_error'
    (OU)
  • utilisez "GridSearchCV" et vérifiez le 'best_score_'

@pritishban
Vous posez une question d'utilisation. Le suivi des problèmes concerne principalement les bogues et les nouvelles fonctionnalités. Pour les questions d'utilisation, il est recommandé d'essayer Stack Overflow ou la liste de diffusion .

Cette page vous a été utile?
0 / 5 - 0 notes