Scikit-learn: MSE es negativo cuando es devuelto por cross_val_score

Creado en 12 sept. 2013  ·  58Comentarios  ·  Fuente: scikit-learn/scikit-learn

El error cuadrático medio devuelto por sklearn.cross_validation.cross_val_score es siempre negativo. Si bien es una decisión diseñada para que la salida de esta función se pueda usar para la maximización dados algunos hiperparámetros, es extremadamente confuso cuando se usa cross_val_score directamente. Al menos me pregunté cómo es posible que la media de un cuadrado sea negativa y pensé que cross_val_score no funcionaba correctamente o no usaba la métrica proporcionada. Solo después de indagar en el código fuente de sklearn me di cuenta de que el letrero estaba volteado.

Este comportamiento se menciona en make_scorer en scorer.py, sin embargo, no se menciona en cross_val_score y creo que debería serlo, porque de lo contrario hace que la gente piense que cross_val_score no está funcionando correctamente.

API Bug Documentation

Comentario más útil

tal vez negmse resolvería el problema

Todos 58 comentarios

Te refieres a

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.

en http://scikit-learn.org/stable/modules/generated/sklearn.metrics.make_scorer.html
? (solo como referencia)

Estoy de acuerdo en que puede ser más claro en los documentos cross_val_score

Gracias por informar

De hecho, pasamos por alto ese problema al hacer la refactorización de Scorer. Lo siguiente es muy contrario a la intuición:

>>> 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

Por cierto, no estoy de acuerdo en que sea un problema de documentación. Es cross_val_score debe devolver el valor con el signo que coincide con el nombre de la puntuación. Idealmente, GridSearchCV(*params).fit(X, y).best_score_ debería ser coherente. De lo contrario, la API es muy confusa.

También estoy de acuerdo en que un cambio para devolver el MSE real sin el letrero cambiado sería la mejor opción.

El objeto anotador podría simplemente almacenar la bandera greater_is_better y siempre que se use el marcador, el signo se podría voltear en caso de que sea necesario, por ejemplo, en GridSearchCV .

Estoy de acuerdo en que tenemos un problema de usabilidad aquí, pero no estoy completamente de acuerdo con la solución de @ogrisel de que deberíamos

devuelve el valor con el signo que coincide con el nombre de la puntuación

porque es un truco poco confiable a largo plazo. ¿Qué pasa si alguien define un marcador personalizado con un nombre como mse ? ¿Qué pasa si siguen el patrón de nomenclatura pero envuelven al anotador en un decorador que cambia el nombre?

El objeto anotador podría simplemente almacenar la bandera mayor_es_better y siempre que se use el anotador, el letrero se podría voltear en caso de que sea necesario, por ejemplo, en GridSearchCV.

Esto es lo que hicieron originalmente los anotadores, durante el desarrollo entre las versiones 0.13 y 0.14 y dificultó mucho su definición. También hizo que el código fuera difícil de seguir porque el atributo greater_is_better parecía desaparecer en el código del anotador, solo para reaparecer en medio del código de búsqueda de la cuadrícula. Se necesitaba una clase especial Scorer para hacer algo que, idealmente, haría una función simple.

Creo que si queremos optimizar las puntuaciones, deberían _maximizarse_. En aras de la facilidad de uso, creo que podríamos introducir un parámetro score_is_loss["auto", True, False] que solo cambia la _ visualización_ de las puntuaciones y puede utilizar una heurística basada en los nombres integrados.

Esa fue una respuesta apresurada porque tuve que bajarme del tren. Lo que quise decir con "pantalla" es realmente el valor de retorno de cross_val_score . Creo que los marcadores deben ser simples y uniformes y los algoritmos siempre deben maximizar.

Esto introduce una asimetría entre los marcadores integrados y personalizados.

Haga ping a @GaelVaroquaux.

Me gusta la solución score_is_loss, o algo por el estilo ... el cambio de signo para que coincida con el nombre de la puntuación parece difícil de mantener podría causar problemas como mencionó @larsmans

¿Cuál es la conclusión? ¿Qué solución deberíamos buscar? :)

@tdomhan @jaquesgrobler @larsmans ¿Sabes si esto también se aplica a r2 ? Me doy cuenta de que las puntuaciones de r2 devueltas por GridSearchCV también son en su mayoría negativas para ElasticNet , Lasso y Ridge .

R² puede ser positivo o negativo, y negativo simplemente significa que su modelo está funcionando muy mal.

IIRC, @GaelVaroquaux fue un defensor de devolver un número negativo cuando greater_is_better=False .

r2 es una función de puntuación (cuanto mayor es mejor), por lo que debería ser positivo si su modelo es bueno, pero es una de las pocas métricas de rendimiento que en realidad puede ser negativa, es decir, peor que 0.

¿Cuál es el consenso sobre este tema? En mi opinión, cross_val_score es una herramienta de evaluación, no una de selección de modelo. Por tanto, debería devolver los valores originales.

Puedo solucionarlo en mi PR # 2759, ya que los cambios que hice hacen que sea realmente fácil de solucionar. El truco consiste en no voltear el letrero por adelantado, sino en acceder al atributo greater_is_better en el marcador cuando se realiza una búsqueda en la cuadrícula.

¿Cuál es el consenso sobre este tema? En mi opinión, cross_val_score es
una herramienta de evaluación, no una de selección de modelos. Por tanto, debería volver
los valores originales.

Un caso especial son los comportamientos variables que son una fuente de problemas en el software.

Simplemente creo que deberíamos cambiar el nombre de "mse" a "negated_mse" en la lista
de cadenas de puntuación aceptables.

¿Qué pasa si alguien define un marcador personalizado con un nombre como mse? ¿Qué pasa si siguen el patrón de nomenclatura pero envuelven al anotador en un decorador que cambia el nombre?

No creo que @ogrisel sugiriera usar la coincidencia de nombres, solo para ser coherente con la métrica original. Corrígeme si me equivoco @ogrisel.

Simplemente creo que deberíamos cambiar el nombre de "mse" a "negated_mse" en la lista de cadenas de puntuación aceptables.

Eso es completamente poco intuitivo si no conoce los aspectos internos de scikit-learn. Si tienes que doblar el sistema de esa manera, creo que es una señal de que hay un problema de diseño.

Eso es completamente poco intuitivo si no conoce los aspectos internos de scikit-learn.
Si tienes que doblar el sistema de esa manera, creo que es una señal de que hay una
problema de diseño.

Estoy en desacuerdo. Los humanos entienden las cosas con mucho conocimiento previo y
contexto. Son casi sistemáticos. Intentando incrustar esto en el software
proporciona una lista de compras como un conjunto de casos especiales. No solo hace
software difícil de mantener, pero también significa que las personas que no tienen
en cuenta que esas excepciones se encuentran con comportamientos sorprendentes y escriben errores
código usando la biblioteca.

¿Qué caso especial tienes en mente?

Para ser claros, creo que las puntuaciones de validación cruzada almacenadas en el objeto GridSearchCV deberían _también_ ser los valores originales (no con el signo invertido).

AFAIK, se introdujo voltear el letrero para hacer la implementación de la búsqueda en la cuadrícula un poco más simple, pero no se suponía que afectara la usabilidad.

¿Qué caso especial tienes en mente?

Bueno, el hecho de que para algunas métricas más grande es mejor, mientras que para otras
es todo lo contrario.

AFAIK, se introdujo voltear el letrero para hacer la búsqueda de cuadrícula
implementación un poco más simple, pero no se suponía que afectara
usabilidad.

No se trata de búsqueda en cuadrícula, se trata de separación de preocupaciones: puntajes
necesitan ser utilizables sin saber nada sobre ellos, o de lo contrario codificar para
lidiar con sus especificidades se extenderá a todo el código base. Ahi esta
ya hay mucho código de puntuación.

Pero eso pospone un poco el problema al código de usuario. Nadie quiere trazar "MSE negado", por lo que los usuarios tendrán que volver a colocar los letreros en su código. Esto es un inconveniente, especialmente para los informes de validación cruzada de múltiples métricas (PR # 2759), ya que necesita manejar cada métrica individualmente. Me pregunto si podemos tener lo mejor de ambos mundos: código genérico y resultados intuitivos.

Pero eso pospone un poco el problema al código de usuario. Nadie quiere
para trazar "MSE negado" para que los usuarios tengan que voltear los carteles en sus
código.

Ciertamente no es el fin del mundo. Tenga en cuenta que al leer artículos o
mirando presentaciones tengo el mismo problema: cuando el gráfico no es
bien hecho, pierdo un poco de tiempo y ancho de banda mental tratando de
calcula si más grande es mejor o no.

Esto es un inconveniente, especialmente para la validación cruzada de múltiples métricas
informes (PR # 2759), ya que necesita manejar cada métrica individualmente.

Por qué. Si acepta que siempre es más grande, mejor, hace
todo más fácil, incluida la interpretación de los resultados.

Me pregunto si podemos tener lo mejor de ambos mundos: código genérico y
resultados intuitivos.

El riesgo es tener un código muy complejo que nos ralentiza por mantenimiento
y desarrollo. Scikit-learn está ganando peso.

Si aceptas que siempre es más grande, mejor

Eso es lo que ella dijo :)

Más en serio, creo que una de las razones por las que esto confunde a la gente es porque la salida de cross_val_score no es coherente con las métricas. Si seguimos su lógica, todas las métricas en sklearn.metrics deberían seguir "cuanto más grande, mejor".

Eso es lo que ella dijo :)

¡Buena esa!

Más en serio, creo que una de las razones por las que esto confunde a la gente es porque
la salida de cross_val_score no es coherente con las métricas. Si nosotros
siga su lógica, todas las métricas en sklearn.metrics deben seguir "más grande
es mejor".

Convenido. Por eso me gusta la idea de cambiar el nombre: aparecería
a los ojos de la gente.

Más en serio, creo que una de las razones por las que esto confunde a las personas es porque la salida de cross_val_score no es coherente con las métricas.

Y esto, a su vez, hace que scoring parezca más misterioso de lo que es.

Me mordió esto hoy en 0.16.1 cuando intentaba hacer una regresión lineal. Si bien el signo de la puntuación aparentemente ya no se invierte para los clasificadores, todavía se invierte para la regresión lineal. Para aumentar la confusión, LinearRegression.score () devuelve una versión no invertida de la partitura.

Sugeriría hacerlo todo coherente y devolver la puntuación sin signo invertido también para los modelos lineales.

Ejemplo:

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 )

Esto da:

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

La validación cruzada invierte todos los signos de los modelos donde cuanto mayor es mejor. Todavía no estoy de acuerdo con esta decisión. Creo que el principal proponente fue @GaelVaroquaux y tal vez @mblondel [Te recuerdo refactorizando el código del marcador].

Oh no importa, toda la discusión está arriba.
Siento que cambiar el signo de forma predeterminada en mse y r2 es aún menos intuitivo: - /

@Huitzilo GaussianNB es un clasificador y utiliza la precisión como anotador predeterminado. LinearRegression es un regresor y utiliza la puntuación r2 como marcador predeterminado. La segunda puntuación es negativa, pero recuerde que la puntuación r2 _ puede_ ser negativa. Además, iris es un conjunto de datos multiclase. Por tanto, los objetivos son categóricos. No puedes usar un regresor.

cierto, estaba un poco confundido acerca de lo que sucede, r2 no se invierte ... solo mse lo estaría.

¿Quizás una solución a todo el problema sea cambiar el nombre de la cosa negmse ?

@mblondel por supuesto que tienes razón, lo siento. Estaba simplemente armando rápidamente un ejemplo para una regresión, y en mi exceso de confianza en los datos del iris, pensé que predecir la característica # 4 de los demás funcionaría (con R2 positivo). Pero no lo hizo, por lo tanto, R2 negativo. No hay letreros volteando aquí. OKAY. Culpa mía.

Aún así, el letrero está invertido en el MSE que obtengo de cross_val_score .

Tal vez sea solo yo, pero encuentro esta inconsistencia muy confusa (que es lo que me metió en este problema). ¿Por qué MSE debería cambiar de signo, pero no R2?

Tal vez sea solo yo, pero encuentro esta inconsistencia muy confusa (que es lo que me metió en este problema). ¿Por qué MSE debería cambiar de signo, pero no R2?

Debido a que la semántica de la puntuación es más alta, mejor. Un MSE alto es malo.

tal vez negmse resolvería el problema

@amueller Estoy de acuerdo, hacer que el letrero sea explícito en el nombre del parámetro de puntuación definitivamente ayudaría a evitar confusiones.

Quizás la documentación en [1] también podría ser aún más explícita acerca de cómo los letreros están cambiando para algunas puntuaciones. En mi caso, necesitaba información rápidamente y solo miré la tabla bajo 3.1.1.1, pero no leí el texto (lo que explica el principio de "cuanto más grande es mejor"). En mi humilde opinión, agregar un comentario para mse, la mediana y el error absoluto medio en la tabla en 3.1.1.1, indicando su negación, ya ayudaría mucho, sin ningún cambio en el código real.

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

Me he encontrado con un caso muy interesante:

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

Resultados en

array([-0.17026282, -2.21315179])

Para el mismo conjunto de datos, el siguiente código

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

resulta en un valor razonable

0.353035789318

AFAIK para el modelo de regresión lineal (con intersección) no se puede obtener R ^ 2> 1 o R ^ 2 <0

Por lo tanto, el resultado de cv no se parece a R ^ 2 con un signo invertido. ¿Me equivoco en algún momento?

r2 puede ser negativo (para modelos defectuosos). No puede ser mayor que 1.

Probablemente esté sobreajustado. tratar:

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))

Pruebe con diferentes valores para la semilla entera random_state que controla la división aleatoria.

tal vez negmse resolvería el problema

+1 para 'neg_mse' (creo que el subrayado hace que las cosas sean más legibles).

¿Eso resuelve todos los problemas? ¿Hay otros puntajes donde mayor no es mejor?

Existen:

  • log_loss
  • mean_absolute_error
  • median_absolute_error

Según doc/modules/model_evaluation.rst , deberían ser todos.

¿Y hinge_loss supongo?

Agregar el prefijo neg_ a todas esas pérdidas se siente incómodo.

Una idea sería devolver las puntuaciones originales (sin cambio de signo) pero en lugar de devolver un ndarray, devolvemos una clase que extiende ndarray con métodos como best() , arg_best() , best_sorted() . De esta forma, los resultados no son sorprendentes y contamos con métodos convenientes para obtener los mejores resultados.

No hay un marcador para la pérdida de bisagra (y nunca he visto que se use para la evaluación).

El anotador no devuelve una matriz numpy, devuelve un flotante, ¿verdad?
podríamos devolver un objeto de puntuación que tiene un ">" personalizado pero parece un flotante.
Eso me parece más artificial que la solución anterior, que etiquetaba al anotador con un bool "lower_is_better" que luego se usó en GridSearchCV.

cross_val_score devuelve una matriz.

En realidad, los puntajes devueltos por cross_val_score generalmente no necesitan ser ordenados, solo promediados.

Otra idea es agregar un método sorted a _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 devuelve una matriz, pero los anotadores devuelven un flotante. Siento que sería extraño tener una lógica específica en cross_val_score porque le gustaría tener el mismo comportamiento en GridSearchCV y en todos los demás objetos CV.

También necesitaría un método argsort, porque en GridSearchCV desea la mejor puntuación y el mejor índice.

¿Cómo implementar "estimar las medias y las variaciones de los errores de los trabajadores a partir de las preguntas de control, luego calcular el promedio ponderado después de eliminar el sesgo estimado para las predicciones" mediante scikit-learn?

IIRC discutimos esto en el sprint (¿el verano pasado?!) Y decidimos ir con neg_mse (o era neg-mse ) y desaprobar todos los anotadores / cadenas donde ahora tenemos un signo negativo.
¿Sigue siendo este el consenso? Deberíamos hacer eso antes de 0.18 entonces.
Ping @GaelVaroquaux @agramfort @jnothman @ogrisel @raghavrv

sí, acordamos neg_mse AFAIK

Fue neg_mse

También necesitamos:

  • neg_log_loss
  • neg_mean_absolute_error
  • neg_median_absolute_error

modelo = secuencial ()
keras.layers.Flatten ()
model.add (Denso (11, input_dim = 3, kernel_initializer = keras.initializers.he_normal (semilla = 2),
kernel_regularizer = regularizers.l2 (2)))
keras.layers.LeakyReLU (alfa = 0,1)
model.add (Denso (8, kernel_initializer = keras.initializers.he_normal (semilla = 2)))
keras.layers.LeakyReLU (alfa = 0,1)
model.add (Denso (4, kernel_initializer = keras.initializers.he_normal (semilla = 2)))
keras.layers.LeakyReLU (alfa = 0,1)
model.add (Denso (1, kernel_initializer = keras.initializers.he_normal (semilla = 2)))
keras.layers.LeakyReLU (alfa = 0,2)
adag = RMSprop (lr = 0.0002)
model.compile (pérdida = pérdidas.mean_squared_error,
optimizador = adag
)
history = model.fit (X_train, Y_train, epochs = 2000,
batch_size = 20, shuffle = True)

¿Cómo validar el código anterior? Quiero dejar un método de validación cruzada que se utilizará en esto.

@shreyassks, este no es el lugar correcto para tu pregunta, pero verificaría esto: https://keras.io/scikit-learn-api . Envuelva su red en un estimador scikit-learn luego use w / model_selection.cross_val_score

Si. ¡Estoy totalmente de acuerdo! Esto también le sucedió a Brier_score_loss, funciona perfectamente bien usando Brier_score_loss, pero se vuelve confuso cuando se trata de GridSearchCV, el resultado negativo de Brier_score_loss. Al menos, sería mejor generar algo como, debido a que Brier_score_loss es una pérdida (cuanto menor, mejor), la función de puntuación aquí voltea el signo para hacerlo negativo.

La idea es que cross_val_score debería centrarse por completo en el valor absoluto del resultado. En mi conocimiento, la importancia del signo negativo (-) obtenido para MSE (error cuadrático medio) en cross_val_score no está predefinida. Esperemos la versión actualizada de sklearn donde se solucione este problema.

Para el caso de uso de regresión:
model_score = cross_val_score (modelo, df_input, df_target, scoring = 'neg_mean_squared_error', cv = 3)
Obtengo los valores como:

RVS:
[-6.20938025 -1.397376 -1.94519]
-3.183982080147279

Regresión lineal:
[-5.94898085 -9.30931808 -1.15760676]
-5.4719685646934275

Lazo:
[-7.22363814 -10.47734135 -2.20807684]
-6.6363521107522345

Cresta:
[-5.95990385 -4.17946756 -1.36885809]
-3,8360764993832004

Entonces cual es el mejor ?
SVR?

Para el caso de uso de regresión:
Obtengo resultados diferentes cuando uso
(1) "cross_val_score" con puntuación = 'neg_mean_squared_error'
y
(2) Para las mismas entradas cuando uso "GridSearchCV" y verifico el 'best_score_'

Para los modelos de regresión, ¿cuál es mejor?

  • "cross_val_score" con scoring = 'neg_mean_squared_error'
    (O)
  • use "GridSearchCV" y marque el 'best_score_'

@pritishban
Estás haciendo una pregunta de uso. El rastreador de problemas es principalmente para errores y nuevas funciones. Para preguntas de uso, se recomienda probar Stack Overflow o la lista de correo .

¿Fue útil esta página
0 / 5 - 0 calificaciones