Scikit-learn: MSE é negativo quando retornado por cross_val_score

Criado em 12 set. 2013  ·  58Comentários  ·  Fonte: scikit-learn/scikit-learn

O erro quadrático médio retornado por sklearn.cross_validation.cross_val_score é sempre negativo. Embora seja uma decisão projetada para que a saída desta função possa ser usada para maximizar dados alguns hiperparâmetros, é extremamente confuso quando se usa cross_val_score diretamente. Pelo menos eu me perguntei como a média de um quadrado pode ser negativa e pensei que cross_val_score não estava funcionando corretamente ou não usava a métrica fornecida. Só depois de cavar no código-fonte do sklearn eu percebi que o sinal estava invertido.

Este comportamento é mencionado em make_scorer em scorer.py, porém não é mencionado em cross_val_score e eu acho que deveria ser, porque caso contrário, faz as pessoas pensarem que cross_val_score não está funcionando corretamente.

API Bug Documentation

Comentários muito úteis

talvez negmse resolvesse o problema

Todos 58 comentários

Você está se referindo 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.

em http://scikit-learn.org/stable/modules/generated/sklearn.metrics.make_scorer.html
? (apenas para referência)

Eu concordo que pode ser mais claro nos documentos cross_val_score

Obrigado por relatar

Na verdade, esquecemos esse problema ao fazer a refatoração do Scorer. O seguinte é muito contra-intuitivo:

>>> 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, não concordo que seja um problema de documentação. É cross_val_score deve retornar o valor com o sinal que corresponde ao nome da pontuação. Idealmente, o GridSearchCV(*params).fit(X, y).best_score_ deve ser consistente também. Caso contrário, a API é muito confusa.

Também concordo que uma mudança para devolver o MSE real sem o sinal alterado seria a melhor opção.

O objeto apontador pode apenas armazenar a bandeira greater_is_better e sempre que o marcador é usado, o sinal pode ser invertido caso seja necessário, por exemplo, em GridSearchCV .

Concordo que temos um problema de usabilidade aqui, mas não concordo totalmente com a solução de @ogrisel de que deveríamos

retorna o valor com o sinal que corresponde ao nome da pontuação

porque isso é um hack não confiável a longo prazo. E se alguém definir um marcador personalizado com um nome como mse ? E se eles seguirem o padrão de nomenclatura, mas envolverem o marcador em um decorador que muda o nome?

O objeto do marcador pode apenas armazenar o sinalizador maior_is_better e sempre que o marcador é usado, o sinal pode ser invertido caso seja necessário, por exemplo, em GridSearchCV.

Isso é o que os pontuadores fizeram originalmente, durante o desenvolvimento entre as versões 0,13 e 0,14, e isso tornou sua definição muito mais difícil. Também tornava o código difícil de seguir porque o atributo greater_is_better parecia desaparecer no código do marcador, apenas para reaparecer no meio do código de pesquisa da grade. Uma classe Scorer era necessária para fazer algo que, idealmente, uma função simples faria.

Acredito que, se quisermos otimizar as pontuações, elas devem ser _maximizadas_. Por uma questão de facilidade de uso, acho que podemos introduzir um parâmetro score_is_loss["auto", True, False] que apenas altera a _exibição_ das pontuações e pode usar uma heurística baseada nos nomes integrados.

Essa foi uma resposta apressada porque eu tive que descer do trem. O que quero dizer com "exibir" é realmente o valor de retorno de cross_val_score . Acho que os marcadores devem ser simples e uniformes e os algoritmos devem sempre maximizar.

Isso introduz uma assimetria entre os marcadores integrados e personalizados.

Ping @GaelVaroquaux.

Eu gosto da solução score_is_loss, ou algo parecido .. a mudança de sinal para coincidir com o nome da pontuação parece difícil de manter pode causar problemas como @larsmans mencionou

qual é a conclusão, qual solução devemos buscar? :)

@tdomhan @jaquesgrobler @larsmans Você sabe se isso se aplica a r2 também? Estou percebendo que as pontuações de r2 retornadas por GridSearchCV também são principalmente negativas para ElasticNet , Lasso e Ridge .

R² pode ser positivo ou negativo, e negativo simplesmente significa que seu modelo está funcionando muito mal.

IIRC , greater_is_better=False .

r2 é uma função de pontuação (quanto maior, melhor), isso deve ser positivo se o seu modelo for bom - mas é uma das poucas métricas de desempenho que pode ser negativa, o que significa pior que 0.

Qual é o consenso sobre este assunto? Na minha opinião, cross_val_score é uma ferramenta de avaliação, não de seleção de modelo. Deve, portanto, retornar os valores originais.

Posso consertar no meu PR # 2759, pois as alterações que fiz o tornam muito fácil de consertar. O truque é não inverter o sinal antecipadamente, mas, em vez disso, acessar o atributo greater_is_better no marcador ao fazer a pesquisa em grade.

Qual é o consenso sobre este assunto? Na minha opinião, cross_val_score é
uma ferramenta de avaliação, não uma de seleção de modelo. Deve, portanto, retornar
os valores originais.

Casos especiais são os comportamentos variáveis ​​que são uma fonte de problemas no software.

Eu simplesmente acho que devemos renomear "mse" para "negated_mse" na lista
de strings de pontuação aceitáveis.

E se alguém definir um marcador personalizado com um nome como mse? E se eles seguirem o padrão de nomenclatura, mas envolverem o marcador em um decorador que muda o nome?

Não acho que @ogrisel estava sugerindo o uso de correspondência de nomes, apenas para ser consistente com a métrica original. Corrija-me se estiver errado @ogrisel.

Eu simplesmente acho que devemos renomear "mse" para "negated_mse" na lista de strings de pontuação aceitáveis.

Isso é completamente não intuitivo se você não conhece os detalhes do scikit-learn. Se você tiver que dobrar o sistema assim, acho que é um sinal de que há um problema de design.

Isso é completamente não intuitivo se você não conhece os detalhes do scikit-learn.
Se você tiver que dobrar o sistema assim, acho que é um sinal de que há um
problema de design.

Discordo. Os humanos entendem as coisas com muito conhecimento prévio e
contexto. Eles são quase sistemáticos. Tentando incorporar isso no software
dá lista de compras como um conjunto de casos especiais. Não só faz
software difícil de manter, mas também significa que as pessoas que não têm
em mente, essas exceções geram comportamentos surpreendentes e geram erros
código usando a biblioteca.

Que caso especial você tem em mente?

Para ser claro, acho que as pontuações de validação cruzada armazenadas no objeto GridSearchCV devem _também_ ser os valores originais (não com o sinal invertido).

AFAIK, inverter o sinal foi introduzido para tornar a implementação da pesquisa em grade um pouco mais simples, mas não deveria afetar a usabilidade.

Que caso especial você tem em mente?

Bem, o fato de que para algumas métricas, maior é melhor, enquanto para outras
é o contrário.

AFAIK, lançando o sinal foi introduzido de modo a fazer a pesquisa da grade
implementação um pouco mais simples, mas não deveria afetar
usabilidade.

Não se trata de pesquisa em grade, é sobre separação de preocupações: pontuações
precisam ser utilizáveis ​​sem saber nada sobre eles, ou então codificar para
lidar com suas especificidades se espalhará para toda a base de código. Há sim
já muito código de pontuação.

Mas isso está adiando um pouco o problema para o código do usuário. Ninguém quer traçar um "MSE negado", então os usuários terão que virar as placas de volta em seu código. Isso é inconveniente, especialmente para relatórios de validação cruzada de várias métricas (PR # 2759), pois você precisa lidar com cada métrica individualmente. Eu me pergunto se podemos ter o melhor dos dois mundos: código genérico e resultados intuitivos.

Mas isso está adiando um pouco o problema para o código do usuário. Ninguém quer
para traçar "MSE negado" para que os usuários tenham que virar as placas de volta em seus
código.

Certamente não é o fim do mundo. Observe que ao ler jornais ou
olhando para apresentações eu tenho o mesmo problema: quando o gráfico não é
bem feito, eu perco um pouco de tempo e largura de banda mental tentando
descobrir se maior é melhor ou não.

Isso é inconveniente, especialmente para validação cruzada de múltiplas métricas
relatórios (PR # 2759), pois você precisa lidar com cada métrica individualmente.

Por quê. Se você apenas aceitar que é sempre maior, melhor, isso torna
tudo mais fácil, inclusive a interpretação dos resultados.

Eu me pergunto se podemos ter o melhor dos dois mundos: código genérico e
resultados intuitivos.

O risco é ter um código muito complexo que nos retarda para manutenção
e o desenvolvimento. O Scikit-learn está ganhando peso.

Se você apenas aceitar que é sempre maior, melhor

Isso é o que ela disse :)

Mais seriamente, acho que um dos motivos pelos quais isso está confundindo as pessoas é porque a saída de cross_val_score não é consistente com as métricas. Se seguirmos sua lógica, todas as métricas em sklearn.metrics devem seguir "quanto maior, melhor".

Isso é o que ela disse :)

Agradável!

Mais a sério, acho que uma das razões pelas quais isso confunde as pessoas é porque
a saída de cross_val_score não é consistente com as métricas. Se nós
siga sua lógica, todas as métricas em sklearn.metrics devem seguir "maior
é melhor".

Acordado. É por isso que gosto da ideia de mudar o nome: iria aparecer
aos olhos das pessoas.

Mais seriamente, acho que uma das razões pelas quais isso está confundindo as pessoas é porque a saída de cross_val_score não é consistente com as métricas.

E isso, por sua vez, faz scoring parecer mais misterioso do que é.

Fui mordido por isso hoje em 0.16.1 ao tentar fazer regressão linear. Embora o sinal da pontuação aparentemente não seja mais invertido para os classificadores, ele ainda é invertido para a regressão linear. Para aumentar a confusão, LinearRegression.score () retorna uma versão não invertida da partitura.

Eu sugiro tornar tudo consistente e retornar a pontuação não invertida para modelos lineares também.

Exemplo:

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 )

Isto dá:

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

A validação cruzada inverte todos os sinais de modelos, onde maior é melhor. Ainda discordo dessa decisão. Acho que os principais proponentes disso foram @GaelVaroquaux e talvez @mblondel [lembrei que você scorer ].

Oh, não importa, toda a discussão está acima.
Acho que inverter o sinal por padrão no mse e r2 é ainda menos intuitivo: - /

@Huitzilo GaussianNB é um classificador e usa a precisão como marcador padrão. LinearRegression é um regressor e usa a pontuação r2 como pontuador padrão. A segunda pontuação é negativa, mas lembre-se de que a pontuação r2 _pode_ ser negativa. Além disso, iris é um conjunto de dados multiclasse. Portanto, os alvos são categóricos. Você não pode usar um regressor.

certo, eu estava um pouco confuso sobre o que acontece, r2 não é invertido ... apenas mse seria.

Talvez uma solução para todo o problema seja renomear a coisa negmse ?

@mblondel, claro que você está certo, desculpe. Eu estava rapidamente criando um exemplo para uma regressão e, em meu excesso de confiança nos dados da íris, pensei que prever o recurso nº 4 dos outros funcionaria (com R2 positivo). Mas não fez, portanto, R2 negativo. Nenhum sinal virando aqui. ESTÁ BEM. Foi mal.

Ainda assim, o sinal está invertido no MSE que recebo de cross_val_score .

Talvez seja só eu, mas acho essa inconsistência muito confusa (que é o que me colocou neste problema). Por que o sinal MSE deve ser invertido, mas não R2?

Talvez seja só eu, mas acho essa inconsistência muito confusa (que é o que me colocou neste problema). Por que o sinal MSE deve ser invertido, mas não R2?

Porque a semântica da pontuação é maior, melhor. MSE alto é ruim.

talvez negmse resolvesse o problema

@amueller : Concordo, tornar o sinal invertido explícito no nome do parâmetro de pontuação ajudaria definitivamente a evitar confusão.

Talvez a documentação em [1] também possa ser ainda mais explícita sobre como os sinais estão mudando para algumas partituras. No meu caso, precisava de informações rapidamente e apenas olhei para a tabela em 3.1.1.1, mas não li o texto (o que explica o princípio "maior é melhor"). IMHO, adicionar um comentário para mse, mediana e erro absoluto médio na tabela em 3.1.1.1, indicando sua negação, já ajudaria muito, sem qualquer alteração no código real.

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

Encontrei um caso muito interessante:

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

Resulta em

array([-0.17026282, -2.21315179])

Para o mesmo conjunto de dados, o código a seguir

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

resulta em um valor razoável

0.353035789318

AFAIK para modelo de regressão linear (com interceptação) não se pode obter R ^ 2> 1 ou R ^ 2 <0

Assim, o resultado cv não se parece com R ^ 2 com um sinal invertido. Eu estou errado em algum ponto?

r2 pode ser negativo (para modelos ruins). Não pode ser maior que 1.

Você provavelmente está com overfitting. experimentar:

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

Tente com valores diferentes para a semente de inteiro random_state que controla a divisão aleatória.

talvez negmse resolvesse o problema

+1 para 'neg_mse' (acho que o sublinhado torna as coisas mais legíveis).

Isso resolve todos os problemas? Existem outras pontuações em que foi maior ou melhor?

Tem:

  • log_loss
  • mean_absolute_error
  • median_absolute_error

De acordo com doc/modules/model_evaluation.rst , deveriam ser todos eles.

E hinge_loss eu acho?

Adicionar o prefixo neg_ a todas essas perdas parece estranho.

Uma ideia seria retornar as pontuações originais (sem mudança de sinal), mas em vez de retornar um ndarray, retornamos uma classe que estende ndarray com métodos como best() , arg_best() , best_sorted() . Desta forma, os resultados não são surpreendentes e temos métodos convenientes para obter os melhores resultados.

Não há marcador de perda de dobradiça (e nunca o vi sendo usado para avaliação).

O marcador não retorna um array numpy, ele retorna um float, certo?
poderíamos retornar um objeto de pontuação que possui um ">" personalizado, mas se parece com um flutuador.
Isso parece mais artificial para mim do que a solução anterior, que era marcar o marcador com um bool "lower_is_better" que foi então usado no GridSearchCV.

cross_val_score retorna uma matriz.

Na verdade, as pontuações retornadas por cross_val_score geralmente não precisam ser classificadas, apenas calculadas.

Outra ideia é adicionar um 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 retorna uma matriz, mas os marcadores retornam um float. Eu acho que seria estranho ter uma lógica específica em cross_val_score porque você gostaria de ter o mesmo comportamento em GridSearchCV e em todos os outros objetos CV.

Você também precisaria de um método argsort, porque em GridSearchCV você deseja a melhor pontuação e o melhor índice.

Como implementar "estimar as médias e variâncias dos erros dos trabalhadores a partir das questões de controle e, em seguida, calcular a média ponderada após remover o viés estimado para as previsões" pelo scikit-learn?

IIRC discutimos isso no sprint (no verão passado ?!) e decidimos ir com neg_mse (ou era neg-mse ) e descontinuar todos os marcadores / strings onde temos um sinal negativo agora.
Este ainda é o consenso? Devemos fazer isso antes de 0,18 então.
Ping @GaelVaroquaux @agramfort @jnothman @ogrisel @raghavrv

sim, concordamos em neg_mse AFAIK

Era neg_mse

Nos também precisamos:

  • neg_log_loss
  • neg_mean_absolute_error
  • neg_median_absolute_error

modelo = Sequencial ()
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 (alfa = 0,1)
model.add (Dense (8, kernel_initializer = keras.initializers.he_normal (seed = 2)))
keras.layers.LeakyReLU (alfa = 0,1)
model.add (Dense (4, kernel_initializer = keras.initializers.he_normal (seed = 2)))
keras.layers.LeakyReLU (alfa = 0,1)
model.add (Dense (1, kernel_initializer = keras.initializers.he_normal (seed = 2)))
keras.layers.LeakyReLU (alfa = 0,2)
adag = RMSprop (lr = 0,0002)
model.compile (loss = loss.mean_squared_error,
optimizer = adag
)
history = model.fit (X_train, Y_train, epochs = 2000,
batch_size = 20, shuffle = True)

Como fazer a validação cruzada do código acima? Eu quero deixar um método de validação cruzada para ser usado nisso.

@shreyassks, este não é o lugar correto para sua pergunta, mas eu verificaria: https://keras.io/scikit-learn-api . Envolva sua rede em um estimador scikit-learn seguida, use w / model_selection.cross_val_score

Sim. Eu concordo totalmente! Isso também aconteceu com Brier_score_loss, funciona perfeitamente bem usando Brier_score_loss, mas fica confuso quando se trata do GridSearchCV, o Brier_score_loss negativo retorna. Pelo menos, seria melhor produzir algo como, porque Brier_score_loss é uma perda (quanto menor, melhor), a função de pontuação aqui inverte o sinal para torná-lo negativo.

A ideia é que cross_val_score deve focar inteiramente no valor absoluto do resultado. No meu conhecimento, a importância do sinal negativo (-) obtido para MSE (erro quadrático médio) em cross_val_score não é predefinida. Vamos esperar pela versão atualizada do sklearn onde esse problema foi resolvido.

Para caso de uso de regressão:
model_score = cross_val_score (model, df_input, df_target, scoring = 'neg_mean_squared_error', cv = 3)
Estou recebendo os valores como:

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

Regressão linear:
[-5.94898085 -9.30931808 -1.15760676]
-5,4719685646934275

Laço:
[-7,22363814 -10,47734135 -2,20807684]
-6.6363521107522345

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

Então qual é o melhor ?
SVR?

Para caso de uso de regressão:
Estou obtendo resultados diferentes quando uso
(1) "cross_val_score" com pontuação = 'neg_mean_squared_error'
e
(2) Para as mesmas entradas quando uso "GridSearchCV" e verifico o 'best_score_'

Para modelos de regressão, qual é o melhor?

  • "cross_val_score" com pontuação = 'neg_mean_squared_error'
    (OU)
  • use "GridSearchCV" e verifique o 'best_score_'

@pritishban
Você está fazendo uma pergunta de uso. O rastreador de problemas é principalmente para bugs e novos recursos. Para questões de uso, é recomendado tentar Stack Overflow ou a Mailing List .

Esta página foi útil?
0 / 5 - 0 avaliações