English Русский 中文 Español Deutsch 日本語
preview
Introdução às curvas ROC (Receiver Operating Characteristic)

Introdução às curvas ROC (Receiver Operating Characteristic)

MetaTrader 5Exemplos |
38 0
Francis Dube
Francis Dube

Introdução

O gráfico Receiver Operating Characteristic (ROC) serve como um método para visualizar, organizar e selecionar classificadores com base em seu desempenho. Originados na teoria de detecção de sinais, os gráficos ROC têm sido utilizados para ilustrar o trade-off entre as taxas de verdadeiros positivos e falsos positivos de classificadores. Além de sua utilidade geral como método de visualização de desempenho, as curvas ROC apresentam propriedades que as tornam particularmente valiosas em domínios caracterizados por distribuições de classes desbalanceadas e custos de erro de classificação distintos. O que é especialmente relevante para classificadores aplicados a conjuntos de dados de séries temporais financeiras.

Embora a estrutura conceitual dos gráficos ROC seja simples, a aplicação prática revela complexidades que exigem consideração cuidadosa. Além disso, existem equívocos comuns e possíveis armadilhas em seu uso empírico. Este artigo tem como objetivo fornecer uma introdução fundamental às curvas ROC e servir como um guia prático para sua aplicação na avaliação de desempenho de classificadores.


Formulação de classificação binária

Muitas aplicações do mundo real envolvem problemas de classificação binária, nos quais as instâncias pertencem a uma de duas classes mutuamente exclusivas e coletivamente exaustivas. Um caso comum desse cenário ocorre quando uma única classe alvo é definida, e cada instância é categorizada como pertencente a essa classe ou ao seu complemento. Considere, por exemplo, a classificação de sinais de radar, onde um contorno detectado em uma tela é categorizado como um tanque (a classe alvo) ou um objeto que não é tanque. Da mesma forma, uma transação de cartão de crédito pode ser classificada como fraudulenta (a classe alvo) ou legítima.

Alvo ou não?

Essa formulação específica do problema de classificação binária, caracterizada pela identificação de uma única classe alvo, forma a base para a análise subsequente. Em vez de atribuir explicitamente instâncias a uma de duas classes distintas, a abordagem adotada aqui concentra-se em determinar se uma instância pertence à classe alvo designada. Embora a terminologia utilizada, referenciando "alvo" e seu complemento, possa evocar conotações militares, o conceito é amplamente aplicável. A classe alvo pode representar diversas entidades, como um tumor maligno, uma negociação financeira bem-sucedida ou, como mencionado anteriormente, uma transação fraudulenta de cartão de crédito. A característica essencial é a dualidade entre a classe de interesse principal e todas as demais possibilidades.


A matriz de confusão

Para um determinado classificador aplicado a um conjunto de dados de teste, quatro resultados distintos são possíveis. Se uma instância pertencente à classe positiva for classificada como positiva, ela é designada como verdadeiro positivo (TP). Por outro lado, se for classificada como negativa, ela é designada como falso negativo (FN). Da mesma forma, se uma instância pertencente à classe negativa for classificada como negativa, ela é designada como verdadeiro negativo (TN); se for classificada como positiva, ela é designada como falso positivo (FP). Esses quatro valores compõem uma matriz de confusão dois por dois, também conhecida como tabela de contingência.

Matriz de Confusão

Essa matriz encapsula a distribuição das instâncias entre esses quatro resultados e serve como base fundamental para diversas métricas de desempenho comumente utilizadas, algumas das quais incluem:

  • Taxa de Acerto ou Sensibilidade ou Recall: A proporção de instâncias alvo corretamente classificadas como alvo, calculada como TP / (TP + FN).
  • Taxa de Falso Alarme (Erro Tipo I): A proporção de instâncias não alvo incorretamente classificadas como alvo, calculada como FP / (TN + FP).
  • Taxa de Omissão (Erro Tipo II): A proporção de instâncias alvo incorretamente classificadas como não alvo, calculada como FN / (TP + FN).
  • Especificidade: A proporção de instâncias não alvo corretamente classificadas como não alvo, calculada como TN / (TN + FP).

A Wikipedia possui um gráfico útil que apresenta uma lista extensa de métricas de desempenho derivadas de uma matriz de confusão.

Tabela de Confusão


Curvas ROC

O funcionamento interno da maioria dos modelos de classificação binária realiza uma previsão numérica e, em seguida, a compara com um limiar predefinido. Se a previsão for igual ou superior a esse limiar, a instância é classificada como alvo; caso contrário, é classificada como não alvo. É evidente que o valor do limiar influencia todas as métricas de desempenho mencionadas anteriormente. Definir o limiar igual ou inferior ao menor valor possível de previsão resulta em todas as instâncias sendo classificadas como alvo, gerando uma taxa de acerto perfeita, mas uma taxa de falso alarme igualmente ruim.

Mecanismo de decisão

Por outro lado, definir o limiar acima do maior valor possível de previsão leva à classificação de todas as instâncias como não alvo, resultando em uma taxa de acerto zero e uma taxa de falso alarme perfeitamente nula. O valor ideal do limiar situa-se entre esses extremos. Ao variar sistematicamente o limiar ao longo de todo o seu intervalo, tanto a taxa de acerto quanto a taxa de falso alarme percorrem seus respectivos intervalos de 0 a 1, com uma métrica melhorando às custas da outra. A curva gerada ao plotar essas duas taxas uma contra a outra (com o limiar atuando como um parâmetro latente) é denominada curva Receiver Operating Characteristic.

A curva ROC é convencionalmente representada com a taxa de falso alarme no eixo horizontal e a taxa de acerto no eixo vertical. Considere um modelo de classificação que produz previsões numéricas aleatórias, não correlacionadas com a classe real de um caso.

Desempenho Aleatório

Nesse cenário, a taxa de acerto e a taxa de falso alarme serão, em média, equivalentes em todos os limiares, resultando em uma curva ROC que se aproxima de uma linha diagonal que se estende do canto inferior esquerdo ao canto superior direito do gráfico. Por exemplo, se um classificador atribuir aleatoriamente uma instância à classe positiva com probabilidade de 50%, espera-se que classifique corretamente 50% das instâncias positivas e negativas, resultando no ponto (0.5, 0.5) no espaço ROC.

Da mesma forma, se o classificador atribuir uma instância à classe positiva com probabilidade de 90%, espera-se que classifique corretamente 90% das instâncias positivas; no entanto, a taxa de falsos positivos aumentará concomitantemente para 90%, resultando no ponto (0.9, 0.9). Consequentemente, um classificador aleatório gerará um ponto ROC que percorre a diagonal, com sua posição determinada pela frequência com que prevê a classe positiva. Para alcançar desempenho superior ao acaso, conforme indicado por uma posição na região triangular superior do espaço ROC, o classificador deve explorar padrões informativos presentes nos dados.

Desempenho Perfeito

Por outro lado, um modelo perfeito, caracterizado pela existência de um limiar que separa precisamente alvos (previsões iguais ou superiores ao limiar) de não alvos (previsões abaixo do limiar), apresenta uma curva ROC distinta. À medida que o limiar paramétrico transita de seu valor mínimo até o limiar ótimo, a taxa de falso alarme diminui de 1 para 0, enquanto a taxa de acerto permanece consistentemente em 1. Aumentos subsequentes no limiar paramétrico fazem com que a taxa de acerto diminua de 1 para 0, enquanto a taxa de falso alarme permanece em 0. Isso resulta em uma curva ROC que forma um caminho do canto superior direito para o canto superior esquerdo e, em seguida, para o canto inferior esquerdo.

Desempenho intermediário

Modelos com desempenho intermediário, apresentando acurácia entre esses extremos, produzirão curvas ROC situadas entre a diagonal e o canto superior esquerdo. O grau em que a curva se desvia da diagonal em direção ao canto superior esquerdo indica o desempenho do modelo. De forma geral, um ponto em um gráfico ROC é considerado superior a outro se apresentar uma taxa de verdadeiros positivos mais alta, uma taxa de falsos positivos mais baixa, ou ambas. Classificadores situados no lado esquerdo de um gráfico ROC, próximos à abscissa, podem ser caracterizados como conservadores.

Esses classificadores tendem a emitir classificações positivas apenas quando há evidência substancial, resultando em uma baixa taxa de falsos positivos, mas frequentemente acompanhada por uma taxa de verdadeiros positivos reduzida. Por outro lado, classificadores localizados na região superior direita do gráfico ROC, caracterizados como menos conservadores, tendem a emitir classificações positivas mesmo com pouca evidência, levando a uma alta taxa de verdadeiros positivos, mas frequentemente acompanhada por uma taxa elevada de falsos positivos. Em muitos domínios do mundo real, onde instâncias negativas predominam, o desempenho dos classificadores na região esquerda do gráfico ROC torna-se particularmente relevante.

Classificadores localizados na região triangular inferior direita do espaço ROC apresentam desempenho inferior ao acaso. Consequentemente, essa região geralmente não contém pontos em gráficos ROC. A negação de um classificador, definida como a inversão de suas decisões de classificação para todas as instâncias, faz com que suas classificações de verdadeiros positivos se tornem erros de falsos negativos e suas classificações de falsos positivos se tornem classificações de verdadeiros negativos. Portanto, qualquer classificador que produza um ponto dentro do triângulo inferior direito pode ser transformado, por meio de negação, para produzir um ponto correspondente dentro do triângulo superior esquerdo. Um classificador situado ao longo da diagonal pode ser caracterizado como não possuindo informação discernível sobre a associação de classe. Por outro lado, um classificador posicionado abaixo da diagonal pode ser interpretado como possuindo padrões informativos, porém aplicando-os de maneira incorreta.


Métricas de desempenho ROC

Embora a inspeção visual de um gráfico de curva ROC possa oferecer percepções preliminares, uma compreensão mais abrangente do desempenho do modelo é obtida por meio da análise de métricas de desempenho relevantes. Antes de uma análise detalhada, é necessário esclarecer as estruturas de dados que caracterizam uma curva ROC. Para facilitar a compreensão, é fornecido um exemplo ilustrativo.

A tabela a seguir apresenta uma amostra hipotética de métricas de desempenho ROC derivadas de um modelo de classificação. Este exemplo utiliza o conjunto de dados iris, disponível por meio da biblioteca Python Sklearn. Seguindo o paradigma alvo/não-alvo, o objetivo da classificação foi restrito à identificação de uma única variante de iris. Especificamente, se setosa for designada como o alvo, todas as outras variantes são categorizadas como não-alvo. De acordo com metodologias convencionais de classificação, o modelo foi treinado para gerar uma saída de 0 para não-alvos e 1 para alvos. Os rótulos das amostras foram transformados de acordo. Vale ressaltar que esquemas de rotulagem alternativos também são viáveis.

Para cada valor de limiar dentro do intervalo de limiares de decisão, uma matriz de confusão é gerada para calcular a taxa de verdadeiros positivos e a taxa de falsos positivos. Esses cálculos utilizam a probabilidade de saída ou o valor da função de decisão do modelo treinado, sendo o limiar correspondente responsável por determinar a associação de classe. A tabela apresentada aqui foi gerada utilizando o script ROC_curves_table_demo.mq5.

KG      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     TPR   FPR   FNR   TNR   PREC  NPREC  M_E   ACC   B_ACC THRESH
NF      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.038 0.000 0.962 1.000 1.000 0.000 0.481 0.667 0.519 0.98195
DI      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.077 0.000 0.923 1.000 1.000 0.000 0.462 0.680 0.538 0.97708
OI      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.115 0.000 0.885 1.000 1.000 0.000 0.442 0.693 0.558 0.97556
GI      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.154 0.000 0.846 1.000 1.000 0.000 0.423 0.707 0.577 0.97334
GH      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.192 0.000 0.808 1.000 1.000 0.000 0.404 0.720 0.596 0.97221
RH      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.231 0.000 0.769 1.000 1.000 0.000 0.385 0.733 0.615 0.97211
IK      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.269 0.000 0.731 1.000 1.000 0.000 0.365 0.747 0.635 0.96935
CK      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.308 0.000 0.692 1.000 1.000 0.000 0.346 0.760 0.654 0.96736
NK      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.346 0.000 0.654 1.000 1.000 0.000 0.327 0.773 0.673 0.96715
EJ      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.385 0.000 0.615 1.000 1.000 0.000 0.308 0.787 0.692 0.96645
DJ      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.423 0.000 0.577 1.000 1.000 0.000 0.288 0.800 0.712 0.96552
HJ      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.462 0.000 0.538 1.000 1.000 0.000 0.269 0.813 0.731 0.96534
NM      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.500 0.000 0.500 1.000 1.000 0.000 0.250 0.827 0.750 0.96417
GM      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.538 0.000 0.462 1.000 1.000 0.000 0.231 0.840 0.769 0.96155
CL      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.577 0.000 0.423 1.000 1.000 0.000 0.212 0.853 0.788 0.95943
LL      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.615 0.000 0.385 1.000 1.000 0.000 0.192 0.867 0.808 0.95699
NL      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.654 0.000 0.346 1.000 1.000 0.000 0.173 0.880 0.827 0.95593
KO      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.692 0.000 0.308 1.000 1.000 0.000 0.154 0.893 0.846 0.95534
NO      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.731 0.000 0.269 1.000 1.000 0.000 0.135 0.907 0.865 0.95258
NO      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.769 0.000 0.231 1.000 1.000 0.000 0.115 0.920 0.885 0.94991
EN      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.808 0.000 0.192 1.000 1.000 0.000 0.096 0.933 0.904 0.94660
CN      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.846 0.000 0.154 1.000 1.000 0.000 0.077 0.947 0.923 0.94489
OQ      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.885 0.000 0.115 1.000 1.000 0.000 0.058 0.960 0.942 0.94420
NQ      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.923 0.000 0.077 1.000 1.000 0.000 0.038 0.973 0.962 0.93619
GQ      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     0.962 0.000 0.038 1.000 1.000 0.000 0.019 0.987 0.981 0.92375
PP      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.000 0.000 1.000 1.000 0.000 0.000 1.000 1.000 0.92087
OP      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.020 0.000 0.980 0.963 0.037 0.010 0.987 0.990 0.12257
RP      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.041 0.000 0.959 0.929 0.071 0.020 0.973 0.980 0.07124
RS      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.061 0.000 0.939 0.897 0.103 0.031 0.960 0.969 0.05349
KS      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.082 0.000 0.918 0.867 0.133 0.041 0.947 0.959 0.04072
KR      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.102 0.000 0.898 0.839 0.161 0.051 0.933 0.949 0.03502
KR      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.122 0.000 0.878 0.812 0.188 0.061 0.920 0.939 0.02523
JR      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.143 0.000 0.857 0.788 0.212 0.071 0.907 0.929 0.02147
HE      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.163 0.000 0.837 0.765 0.235 0.082 0.893 0.918 0.01841
QE      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.184 0.000 0.816 0.743 0.257 0.092 0.880 0.908 0.01488
DE      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.204 0.000 0.796 0.722 0.278 0.102 0.867 0.898 0.01332
PD      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.224 0.000 0.776 0.703 0.297 0.112 0.853 0.888 0.01195
PD      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.245 0.000 0.755 0.684 0.316 0.122 0.840 0.878 0.01058
MG      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.265 0.000 0.735 0.667 0.333 0.133 0.827 0.867 0.00819
JG      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.286 0.000 0.714 0.650 0.350 0.143 0.813 0.857 0.00744
EG      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.306 0.000 0.694 0.634 0.366 0.153 0.800 0.847 0.00683
LF      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.327 0.000 0.673 0.619 0.381 0.163 0.787 0.837 0.00635
CF      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.347 0.000 0.653 0.605 0.395 0.173 0.773 0.827 0.00589
KF      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.367 0.000 0.633 0.591 0.409 0.184 0.760 0.816 0.00578
JI      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.388 0.000 0.612 0.578 0.422 0.194 0.747 0.806 0.00556
JI      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.408 0.000 0.592 0.565 0.435 0.204 0.733 0.796 0.00494
NH      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.429 0.000 0.571 0.553 0.447 0.214 0.720 0.786 0.00416
PH      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.449 0.000 0.551 0.542 0.458 0.224 0.707 0.776 0.00347
LH      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.469 0.000 0.531 0.531 0.469 0.235 0.693 0.765 0.00244
KK      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.490 0.000 0.510 0.520 0.480 0.245 0.680 0.755 0.00238
HK      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.510 0.000 0.490 0.510 0.490 0.255 0.667 0.745 0.00225
LK      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.531 0.000 0.469 0.500 0.500 0.265 0.653 0.735 0.00213
PJ      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.551 0.000 0.449 0.491 0.509 0.276 0.640 0.724 0.00192
CJ      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.571 0.000 0.429 0.481 0.519 0.286 0.627 0.714 0.00189
EM      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.592 0.000 0.408 0.473 0.527 0.296 0.613 0.704 0.00177
IM      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.612 0.000 0.388 0.464 0.536 0.306 0.600 0.694 0.00157
HM      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.633 0.000 0.367 0.456 0.544 0.316 0.587 0.684 0.00132
EL      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.653 0.000 0.347 0.448 0.552 0.327 0.573 0.673 0.00127
DL      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.673 0.000 0.327 0.441 0.559 0.337 0.560 0.663 0.00119
KL      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.694 0.000 0.306 0.433 0.567 0.347 0.547 0.653 0.00104
DO      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.714 0.000 0.286 0.426 0.574 0.357 0.533 0.643 0.00102
RO      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.735 0.000 0.265 0.419 0.581 0.367 0.520 0.633 0.00085
DN      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.755 0.000 0.245 0.413 0.587 0.378 0.507 0.622 0.00082
EN      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.776 0.000 0.224 0.406 0.594 0.388 0.493 0.612 0.00069
HN      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.796 0.000 0.204 0.400 0.600 0.398 0.480 0.602 0.00062
HQ      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.816 0.000 0.184 0.394 0.606 0.408 0.467 0.592 0.00052
DQ      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.837 0.000 0.163 0.388 0.612 0.418 0.453 0.582 0.00048
FQ      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.857 0.000 0.143 0.382 0.618 0.429 0.440 0.571 0.00044
MP      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.878 0.000 0.122 0.377 0.623 0.439 0.427 0.561 0.00028
LP      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.898 0.000 0.102 0.371 0.629 0.449 0.413 0.551 0.00026
NS      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.918 0.000 0.082 0.366 0.634 0.459 0.400 0.541 0.00015
ES      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.939 0.000 0.061 0.361 0.639 0.469 0.387 0.531 0.00012
MS      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.959 0.000 0.041 0.356 0.644 0.480 0.373 0.520 0.00007
QR      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 0.980 0.000 0.020 0.351 0.649 0.490 0.360 0.510 0.00004
CR      0       17:13:16.458    ROC_curves_table_demo (Crash 1000 Index,M5)     1.000 1.000 0.000 0.000 0.347 0.653 0.500 0.347 0.500 0.00002

Uma análise dos dados tabulados da curva ROC revela uma tendência discernível: tanto a TPR quanto a FPR apresentam uma diminuição de 1 para 0 à medida que o limiar de decisão aumenta. Dada a capacidade preditiva inerente do modelo, a FPR demonstra uma redução mais rápida do que a TPR. Notavelmente, a FPR atinge zero enquanto a TPR mantém o valor de 1. Consequentemente, a menos que os custos relativos dos erros Tipo I e Tipo II sejam substancialmente distintos, espera-se que o limiar ótimo esteja dentro do intervalo definido por esses extremos. Também é importante observar que a tabela inclui a especificidade, que é equivalente à Taxa de Verdadeiros Negativos (TNR), e representa o complemento da FPR.

Em muitos casos, a determinação do limiar ótimo pode ser realizada por inspeção direta das colunas de Taxa de Verdadeiros Positivos e Taxa de Falsos Positivos. A interpretabilidade desse processo é aprimorada pela inclusão dos tamanhos das amostras de alvos e não-alvos. Quando os custos associados aos erros Tipo I e Tipo II são aproximadamente equivalentes, o erro médio serve como uma métrica escalar adequada de desempenho. Essa métrica, denominada M_E na tabela, é calculada como a média aritmética da Taxa de Falso Alarme e da Taxa de Omissão.

No entanto, em cenários onde os custos desses tipos de erro divergem significativamente, a métrica de erro médio perde relevância. A precisão, em particular, é um indicador de desempenho crítico em diversas aplicações. Normalmente, a precisão da detecção de alvos é de maior interesse. Em certos contextos, a precisão na identificação de não-alvos também pode ser relevante. Para o objetivo de detecção precisa de alvos, a coluna rotulada PREC fornece informações pertinentes. Por outro lado, a coluna rotulada NPREC é relevante para a identificação precisa de não-alvos.

A tabela apresenta métricas adicionais de desempenho, incluindo acurácia (ACC) e acurácia balanceada (B_ACC). A acurácia é definida como a proporção de instâncias corretamente classificadas, incluindo verdadeiros positivos e verdadeiros negativos, em relação ao conjunto total de dados. A acurácia balanceada, calculada como a média aritmética entre sensibilidade e especificidade, é utilizada para mitigar o impacto do desbalanceamento de classes, condição caracterizada por uma disparidade significativa nos tamanhos das amostras das classes alvo e não-alvo. Antes da discussão sobre a área sob a curva ROC, é necessário examinar os procedimentos computacionais utilizados para gerar a tabela de métricas ROC mencionada. Especificamente, o código responsável pelo cálculo dessas métricas relacionadas à ROC será analisado.

O código relevante está encapsulado no arquivo de cabeçalho roc_curves.mqh, que define uma série de funções e a estrutura de dados conf_stats. A estrutura conf_stats atua como um contêiner para o armazenamento de diversos parâmetros derivados da matriz de confusão.

//+------------------------------------------------------------------+
//|  confusion matrix stats                                          |
//+------------------------------------------------------------------+
struct conf_stats
  {
   double              tn;                //true negatives
   double              tp;                //true positives
   double              fn;                //false negatives
   double              fp;                //false positives
   double              num_targets;       //number of actual positive labels(target)
   double              num_non_targets;   //number of acutal negative labels(non targets)
   double            tp_rate;             //true positives rate - hit rate -  recall - sensitivity
   double            fp_rate;             //false positives rate - fall out - type 1 error
   double            fn_rate;             //false negatives rate - miss rate - type 2 error
   double            tn_rate;             //true negatives rate - specificity
   double            precision;           //precision - positive predictve value
   double            null_precision;      //null precision - false discovery rate
   double            prevalence;          //prevalence
   double            lr_plus;             //positive likelihood ratio
   double            lr_neg;              //negative likelihood ratio
   double            for_rate;            //false omission rate
   double            npv;                 //negative predictive value
   double            acc;                 //accuracy
   double            b_acc;               //balanced accuracy
   double            f1_score;            //f1 score
   double            mean_error;          //mean error
  };

A estrutura conf_stats funciona como o principal parâmetro dentro da função roc_stats(), permitindo o armazenamento dos resultados de avaliação derivados da matriz de confusão. Essa avaliação é realizada sobre um conjunto de dados composto por rótulos reais, previsões correspondentes e um limiar definido. Os rótulos reais são fornecidos como um vetor, denominado targets, servindo como o segundo parâmetro. O terceiro argumento, probas, representa o vetor de probabilidades previstas ou variáveis de decisão.

//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
bool roc_stats(conf_stats &cmat, vector &targets, vector &probas, double threshold, long target_label = 1, long non_target_label= 0)
  {
   vector all_labels = np::unique(targets);

   if(all_labels.Size()!=2 || long(all_labels[all_labels.ArgMin()])!=non_target_label || long(all_labels[all_labels.ArgMax()])!=target_label || target_label<=non_target_label)
     {
      Print(__FUNCTION__, " ", __LINE__, " invalid inputs ");
      return false;
     }
//---
   cmat.tp=cmat.fn=cmat.tn=cmat.fp = 0.0;
//---
   for(ulong i = 0; i<targets.Size(); i++)
     {
      if(probas[i]>=threshold && long(targets[i]) == target_label)
         cmat.tp++;
      else
         if(probas[i]>=threshold && long(targets[i]) == non_target_label)
            cmat.fp++;
         else
            if(probas[i]<threshold && long(targets[i]) == target_label)
               cmat.fn++;
            else
               cmat.tn++;
     }
//---
   cmat.num_targets = cmat.tp+cmat.fn;
   cmat.num_non_targets = cmat.fp+cmat.tn;
//---
   cmat.tp_rate = (cmat.tp+cmat.fn>0.0)?(cmat.tp/(cmat.tp+cmat.fn)):double("na");
   cmat.fp_rate = (cmat.tn+cmat.fp>0.0)?(cmat.fp/(cmat.tn+cmat.fp)):double("na");
   cmat.fn_rate = (cmat.tp+cmat.fn>0.0)?(cmat.fn/(cmat.tp+cmat.fn)):double("na");
   cmat.tn_rate = (cmat.tn+cmat.fp>0.0)?(cmat.tn/(cmat.tn+cmat.fp)):double("na");
   cmat.precision = (cmat.tp+cmat.fp>0.0)?(cmat.tp/(cmat.tp+cmat.fp)):double("na");
   cmat.null_precision = 1.0 - cmat.precision;
   cmat.for_rate = (cmat.tn+cmat.fn>0.0)?(cmat.fn/(cmat.tn+cmat.fn)):double("na");
   cmat.npv = 1.0 - cmat.for_rate;
   cmat.lr_plus = (cmat.fp_rate>0.0)?(cmat.tp_rate/cmat.fp_rate):double("na");
   cmat.lr_neg = (cmat.tn_rate>0.0)?(cmat.fn_rate/cmat.tn_rate):double("na");
   cmat.prevalence = (cmat.num_non_targets+cmat.num_targets>0.0)?(cmat.num_targets/(cmat.num_non_targets+cmat.num_targets)):double("na");
   cmat.acc = (cmat.num_non_targets+cmat.num_targets>0.0)?((cmat.tp+cmat.tn)/(cmat.num_non_targets+cmat.num_targets)):double("na");
   cmat.b_acc = ((cmat.tp_rate+cmat.tn_rate)/2.0);
   cmat.f1_score = (cmat.tp+cmat.fp+cmat.fn>0.0)?((2.0*cmat.tp)/(2.0*cmat.tp+cmat.fp+cmat.fn)):double("na");
   cmat.mean_error = ((cmat.fp_rate+cmat.fn_rate)/2.0);
//---
   return true;
//---
  }

O parâmetro threshold define o valor crítico utilizado para determinar a associação de classe. Os dois últimos parâmetros permitem a especificação explícita dos rótulos de alvo e não-alvo, respectivamente. A função retorna um valor booleano, indicando execução bem-sucedida com true e ocorrência de erro com false.

//+------------------------------------------------------------------+
//| roc table                                                        |
//+------------------------------------------------------------------+
matrix roc_table(vector &true_targets,matrix &probas,ulong target_probs_col = 1, long target_label = 1, long non_target_label= 0)
  {
   matrix roctable(probas.Rows(),10);

   conf_stats mts;

   vector probs = probas.Col(target_probs_col);

   if(!np::quickSort(probs,false,0,probs.Size()-1))
      return matrix::Zeros(1,1);

   for(ulong i = 0; i<roctable.Rows(); i++)
     {
      if(!roc_stats(mts,true_targets,probas.Col(target_probs_col),probs[i],target_label,non_target_label))
         return matrix::Zeros(1,1);
      roctable[i][0] = mts.tp_rate;
      roctable[i][1] = mts.fp_rate;
      roctable[i][2] = mts.fn_rate;
      roctable[i][3] = mts.tn_rate;
      roctable[i][4] = mts.precision;
      roctable[i][5] = mts.null_precision;
      roctable[i][6] = mts.mean_error;
      roctable[i][7] = mts.acc;
      roctable[i][8] = mts.b_acc;
      roctable[i][9] = probs[i];
     }
//---
   return roctable;
  }

A função roc_table() gera uma matriz que encapsula os dados da curva ROC. Os parâmetros de entrada incluem um vetor de rótulos reais, uma matriz de probabilidades previstas ou variáveis de decisão, e um inteiro sem sinal (unsigned long) que especifica o índice da coluna para determinação da associação de classe em matrizes com múltiplas colunas. Os dois últimos parâmetros definem os rótulos de alvo e não-alvo. Em caso de erro, a função retorna uma matriz 1x1 de zeros. É importante notar que tanto roc_stats() quanto roc_table() são especificamente projetadas para modelos de classificação binária. O não cumprimento desse requisito resultará em uma mensagem de erro.

//+------------------------------------------------------------------+
//| roc curve table display                                          |
//+------------------------------------------------------------------+
string roc_table_display(matrix &out)
  {
   string output,temp;

   if(out.Rows()>=10)
     {
      output = "TPR   FPR   FNR   TNR   PREC  NPREC  M_E   ACC   B_ACC THRESH";
      for(ulong i = 0; i<out.Rows(); i++)
        {
         temp = StringFormat("\n%5.3lf %5.3lf %5.3lf %5.3lf %5.3lf %5.3lf %5.3lf %5.3lf %5.3lf %5.5lf",
                             out[i][0],out[i][1],out[i][2],out[i][3],out[i][4],out[i][5],out[i][6],out[i][7],out[i][8],out[i][9]);
         StringAdd(output,temp);
        }
     }
   return output;
  }

A função roc_table_display() recebe um único parâmetro de matriz, idealmente a saída de uma chamada anterior de roc_table(), e formata os dados em uma representação textual tabular.


Área sob uma curva ROC

A Área Sob a Curva ROC (AUC) fornece uma métrica concisa de valor único para o desempenho de classificadores, abordando o excesso de detalhes da curva ROC completa. Enquanto a curva ROC representa visualmente o trade-off entre as taxas de verdadeiros e falsos positivos, a AUC quantifica a capacidade geral do modelo de distinguir entre classes. Um modelo inútil, representado por uma linha diagonal no gráfico ROC, apresenta uma AUC de 0.5, indicando desempenho aleatório. Por outro lado, um modelo perfeito, com uma curva ROC aderente ao canto superior esquerdo, atinge uma AUC de 1.0. Portanto, um valor de AUC mais alto indica melhor desempenho do modelo. O cálculo da AUC envolve a determinação da área sob a curva ROC e, devido à variação estatística inerente aos conjuntos de dados de teste, uma soma simples geralmente é suficiente para uma estimativa precisa, dispensando a necessidade de integração numérica complexa.

A AUC possui uma interpretação estatística significativa: representa a probabilidade de que um classificador atribua uma classificação mais alta a uma instância positiva selecionada aleatoriamente em comparação a uma instância negativa selecionada aleatoriamente. Essa característica estabelece uma equivalência entre a AUC e o teste de soma de postos de Wilcoxon, também conhecido como teste U de Mann-Whitney.

O teste de soma de postos de Wilcoxon, um método estatístico não paramétrico, avalia a diferença entre dois grupos independentes sem assumir normalidade. Ele opera classificando todas as observações e comparando as somas dos postos entre os grupos, avaliando assim diferenças nas medianas.

Além disso, a AUC está intrinsecamente ligada ao coeficiente de Gini. Especificamente, o coeficiente de Gini é derivado como o dobro da área entre a linha diagonal de acaso e a curva ROC. O coeficiente de Gini, uma medida de desigualdade de distribuição, quantifica o grau de dispersão dentro de uma distribuição, variando de 0 (igualdade perfeita) a 1 (desigualdade máxima). No contexto da distribuição de renda, ele reflete a disparidade nos níveis de renda. Graficamente, o coeficiente de Gini é definido como a razão entre a área entre a curva de Lorenz e a linha de igualdade perfeita e a área sob a linha de igualdade perfeita.

A função roc_auc(), definida em roc_curves.mqh, calcula a AUC para um problema de classificação binária, com a opção de limitar o cálculo a um valor máximo específico de FPR. Ela recebe como entrada os rótulos reais das classes e as probabilidades previstas, juntamente com um rótulo alvo opcional e um valor máximo de FPR. Se o valor máximo de FPR for 1.0, a função calcula a AUC padrão. Caso contrário, calcula a curva ROC, encontra o ponto na curva correspondente ao valor máximo de FPR usando searchsorted, interpola a TPR nesse ponto e, em seguida, calcula a AUC parcial até esse valor máximo de FPR utilizando a regra do trapézio. Por fim, normaliza essa AUC parcial para um valor entre 0.5 e 1.0, fornecendo efetivamente uma medida de desempenho do classificador dentro do intervalo especificado de FPR.

//+------------------------------------------------------------------+
//|  area under the curve                                            |
//+------------------------------------------------------------------+
double roc_auc(vector &true_classes, matrix &predicted_probs,long target_label=1,double max_fpr=1.0)
  {
   vector all_labels = np::unique(true_classes);

   if(all_labels.Size()!=2|| max_fpr<=0.0 || max_fpr>1.0)
     {
      Print(__FUNCTION__, " ", __LINE__, " invalid inputs ");
      return EMPTY_VALUE;
     }

   if(max_fpr == 1.0)
     {
      vector auc = true_classes.ClassificationScore(predicted_probs,CLASSIFICATION_ROC_AUC,AVERAGE_BINARY);
      return auc[0];
     }

   matrix tpr,fpr,threshs;

   if(!true_classes.ReceiverOperatingCharacteristic(predicted_probs,AVERAGE_BINARY,fpr,tpr,threshs))
     {
      Print(__FUNCTION__, " ", __LINE__, " invalid inputs ");
      return EMPTY_VALUE;
     }

   vector xp(1);
   xp[0] = max_fpr;
   vector stop;

   if(!np::searchsorted(fpr.Row(0),xp,true,stop))
     {
      Print(__FUNCTION__, " ", __LINE__, " searchsorted failed ");
      return EMPTY_VALUE;
     }

   vector xpts(2);
   vector ypts(2);

   xpts[0] = fpr[0][long(stop[0])-1];
   xpts[1] = fpr[0][long(stop[0])];
   ypts[0] = tpr[0][long(stop[0])-1];
   ypts[1] = tpr[0][long(stop[0])];

   vector vtpr = tpr.Row(0);
   vtpr = np::sliceVector(vtpr,0,long(stop[0]));
   vector vfpr = fpr.Row(0);
   vfpr = np::sliceVector(vfpr,0,long(stop[0]));

   if(!vtpr.Resize(vtpr.Size()+1) || !vfpr.Resize(vfpr.Size()+1))
     {
      Print(__FUNCTION__, " ", __LINE__, " error  ", GetLastError());
      return EMPTY_VALUE;
     }

   vfpr[vfpr.Size()-1] = max_fpr;

   vector yint = np::interp(xp,xpts,ypts);


   vtpr[vtpr.Size()-1] = yint[0];

   double direction = 1.0;

   vector dx = np::diff(vfpr);

   if(dx[dx.ArgMin()]<0.0)
      direction = -1.0;

   double partial_auc = direction*np::trapezoid(vtpr,vfpr);

   if(partial_auc == EMPTY_VALUE)
     {
      Print(__FUNCTION__, " ", __LINE__, " trapz failed ");
      return EMPTY_VALUE;
     }

   double minarea = 0.5*(max_fpr*max_fpr);
   double maxarea = max_fpr;

   return 0.5*(1+(partial_auc-minarea)/(maxarea-minarea));
  }

O parâmetro max_fpr facilita o cálculo da Área Sob a Curva ROC (AUC) parcial, permitindo a avaliação do desempenho do classificador dentro de um intervalo definido de valores de FPR. Essa funcionalidade é particularmente relevante em cenários onde há ênfase no desempenho em baixos limiares de FPR. Além disso, é vantajosa na análise de conjuntos de dados desbalanceados, onde a curva ROC pode ser desproporcionalmente influenciada por uma grande quantidade de verdadeiros negativos. O uso desse parâmetro permite uma avaliação focada na região relevante da curva.

Embora as curvas Receiver Operating Characteristic (ROC) forneçam uma estrutura útil para avaliação de classificadores, sua aplicação na determinação de superioridade definitiva entre classificadores requer consideração cuidadosa, especialmente ao incorporar o custo de classificações incorretas. Embora a AUC ofereça uma métrica para avaliar a qualidade do classificador, comparações de desempenho exigem uma abordagem mais refinada. Para tratar adequadamente os custos, é necessário retornar à matriz de confusão.

Paradigmas tradicionais de classificação frequentemente priorizam a maximização da acurácia, assumindo implicitamente custos uniformes para todos os erros. Em contraste, a classificação sensível a custos reconhece custos diferentes associados a diferentes tipos de erro, buscando minimizar o custo total dos erros em vez de apenas otimizar a acurácia. Por exemplo, em um classificador projetado para identificar ações promissoras para investimento, o custo de uma oportunidade perdida (falso negativo) pode ser considerado menos relevante do que o custo de um falso positivo. A atribuição de custos a cada tipo de erro resulta em uma matriz de custos. Essa matriz descreve os custos associados aos diferentes resultados de previsão, especificando penalidades ou perdas decorrentes de previsões incorretas, como falsos positivos e falsos negativos, podendo também incluir custos para previsões corretas. Uma matriz de confusão hipotética, juntamente com sua correspondente matriz de custos, é apresentada a seguir.

Matriz de Custos

A matriz de custos normalmente apresenta valores nulos na diagonal, indicando custo zero para previsões corretas, uma convenção comum, mas não obrigatória. Dada uma estrutura de custos definida para todos os tipos de erro, as informações contidas na matriz de confusão podem ser agregadas em uma única métrica de desempenho. Uma abordagem simples consiste em multiplicar a frequência de cada tipo de erro pelo seu custo correspondente e somar esses produtos. Entretanto, a eficácia dessa métrica depende de as proporções de classes do conjunto de dados refletirem com precisão aquelas encontradas em aplicações do mundo real.

Uma medida mais robusta de desempenho sensível a custos é o custo esperado por amostra. Essa métrica é obtida normalizando as contagens da matriz de confusão pelos totais de cada linha, produzindo as probabilidades condicionais das classes previstas dado a classe real. Essas probabilidades são então multiplicadas pelos respectivos custos da matriz de custos, e os resultados são somados em cada linha, resultando no custo esperado para um membro de cada classe. Em seguida, esses custos por classe são ponderados pelas proporções esperadas de amostras em cada classe e somados, produzindo o custo esperado total por amostra. A estimativa das proporções esperadas de classes em cenários reais requer compreensão da distribuição subjacente dos dados no ambiente de aplicação. Métodos para essa estimativa incluem:

  • Análise de dados históricos, onde registros existentes são examinados para fornecer estimativas confiáveis das proporções de classes. Por exemplo, em diagnósticos médicos, registros de pacientes podem indicar a prevalência de doenças.
  • Consulta a especialistas do domínio, cujo conhecimento pode refinar ou validar estimativas obtidas por outros meios.
  • Uso de técnicas de amostragem representativa quando a coleta de novos dados é possível, garantindo que a amostra reflita a distribuição real, especialmente em conjuntos de dados desbalanceados. A amostragem estratificada pode assegurar a representação adequada de todas as classes, mesmo em distribuições desiguais.

As proporções esperadas de amostras em uma classe são formalmente equivalentes às probabilidades a priori. No contexto específico de classificação binária, seja
q a probabilidade a priori de uma amostra pertencer à classe alvo. Além disso, defina p1 como a probabilidade de erro Tipo I (falso positivo), com c1 representando seu custo associado, e p2 como a probabilidade de erro Tipo II (falso negativo), com c2 representando seu custo associado. Em muitas aplicações, q pode ser determinado por meios teóricos ou empíricos, e os custos c1 e c2 são conhecidos, com precisão ou por aproximação razoável. Consequentemente, essas três quantidades podem ser tratadas como parâmetros fixos. As probabilidades de erro p1 e p2 são determinadas pelo limiar de classificação. O custo esperado é expresso pela seguinte equação.

Fórmula de custo

Reorganizando essa equação, obtém-se.

Fórmula de custo reorganizada

Demonstrando uma relação linear entre p1 e p2 para qualquer valor dado de custo esperado. Quando essa relação linear é representada graficamente na curva ROC, sua inclinação é determinada pela probabilidade a priori q e pela razão de custos c2/c1. A variação do custo esperado gera uma família de linhas paralelas, onde linhas mais baixas e à direita indicam custos mais elevados. A interseção dessa linha com a curva ROC em dois pontos indica dois limiares que produzem custos equivalentes, embora subótimos. Linhas posicionadas inteiramente acima e à esquerda da curva ROC representam desempenho inalcançável pelo modelo. A linha tangente à curva ROC indica o custo ótimo alcançável.

Essa análise demonstra que a AUC não necessariamente fornece uma medida definitiva do desempenho prático de um modelo de classificação. Embora a AUC seja uma métrica válida de qualidade geral, sua utilidade é reduzida quando custos operacionais são considerados, podendo levar a conclusões enganosas em aplicações sensíveis a custo.


Problemas de decisão com mais de duas classes

A extensão do conceito de curvas ROC para classificação multiclasse exige a adoção de estratégias específicas. Predominantemente, duas abordagens são utilizadas: One-vs-Rest (OvR), também conhecida como One-vs-All (OvA), e One-vs-One (OvO). A estratégia OvR transforma o problema multiclasse em múltiplos problemas binários ao tratar cada classe como positiva contra todas as demais combinadas, resultando em n curvas ROC para n classes. Por outro lado, o método OvO constrói problemas binários para todos os pares possíveis de classes, produzindo n(n-1)/2 curvas ROC.

Para agregar as métricas de desempenho derivadas dessas classificações binárias, utilizam-se técnicas de média. A média micro calcula TPR e FPR globalmente considerando todas as instâncias de todas as classes, atribuindo peso igual a cada instância. A média macro, por outro lado, calcula TPR e FPR para cada classe individualmente e depois realiza a média, atribuindo peso igual a cada classe, sendo especialmente útil quando o desempenho por classe é relevante. Assim, a interpretação das curvas ROC multiclasse depende da estratégia de decomposição adotada (OvR ou OvO) e do método de agregação (micro ou macro), pois essas escolhas influenciam significativamente a avaliação de desempenho resultante.


Funções nativas do MQL5 para curvas ROC e AUC

No MQL5, o cálculo de curvas Receiver Operating Characteristic (ROC) e da Área Sob a Curva (AUC) é realizado por meio de funcionalidades específicas. O

O método vetorial ReceiverOperatingCharacteristic() é utilizado para gerar os valores necessários para a visualização da curva ROC. Esse método opera sobre um vetor que representa os rótulos reais das classes. O primeiro argumento é uma matriz de probabilidades ou valores de decisão, estruturada com colunas correspondentes ao número de classes. O segundo argumento, uma variável da enumeração ENUM_AVERAGE_MODE, especifica o método de média. É importante notar que essa função suporta apenas AVERAGE_NONE, AVERAGE_BINARY e AVERAGE_MICRO. Após execução bem-sucedida, a taxa de falsos positivos (FPR), a taxa de verdadeiros positivos (TPR) e os limiares associados são armazenados em matrizes designadas, correspondentes aos três últimos parâmetros. O método retorna um valor booleano indicando sucesso na execução.

O cálculo da AUC é realizado pelo método vetorial ClassificationScore(). Assim como o método ROC, ele é chamado sobre o vetor de rótulos reais das classes e requer uma matriz de probabilidades previstas ou valores de decisão. O segundo parâmetro, uma enumeração ENUM_CLASSIFICATION_METRIC, deve ser definido como CLASSIFICATION_ROC_AUC para especificar o cálculo da AUC. Diferentemente do método ROC, essa função aceita todos os valores de ENUM_AVERAGE_MODE. O método retorna um vetor de áreas calculadas, cuja cardinalidade depende do modo de média e do número de classes presentes no conjunto de dados.

Para ilustrar o impacto dos diferentes modos de média, foi desenvolvido o script ROC_Demo.mq5. Esse script utiliza o conjunto de dados Iris e um modelo de regressão logística para resolver problemas de classificação binária ou multiclasse configuráveis pelo usuário. O código completo do script é apresentado a seguir.

//+------------------------------------------------------------------+
//|                                                     ROC_Demo.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#include<logistic.mqh>
#include<ErrorDescription.mqh>
#include<Generic/SortedSet.mqh>
//---
enum CLASSIFICATION_TYPE
  {
   BINARY_CLASS = 0,//binary classification problem
   MULITI_CLASS//multiclass classification problem
  };
//--- input parameters
input double   Train_Test_Split = 0.5;
input int      Random_Seed = 125;
input CLASSIFICATION_TYPE classification_problem = BINARY_CLASS;
input ENUM_AVERAGE_MODE av_mode = AVERAGE_BINARY;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CHighQualityRandStateShell rngstate;
   CHighQualityRand::HQRndSeed(Random_Seed,Random_Seed+Random_Seed,rngstate.GetInnerObj());
//---
   matrix data = np::readcsv("iris.csv");
   data = np::sliceMatrixCols(data,1);
//---
   if(classification_problem == BINARY_CLASS)
      data = np::sliceMatrixRows(data,0,100);
//---
   ulong rindices[],trainset[],testset[];
   np::arange(rindices,int(data.Rows()));
//---
//---
   if(!np::shuffleArray(rindices,GetPointer(rngstate)) || ArrayCopy(trainset,rindices,0,0,int(ceil(Train_Test_Split*rindices.Size())))<0 || !ArraySort(trainset))
     {
      Print(__LINE__, "  error ", ErrorDescription(GetLastError()));
      return;
     }
//---
   CSortedSet<ulong> test_set(rindices);
//---
   test_set.ExceptWith(trainset);
//---
   test_set.CopyTo(testset);
//---
   matrix testdata = np::selectMatrixRows(data,testset);
   matrix test_predictors = np::sliceMatrixCols(testdata,0,4);
   vector test_targets = testdata.Col(4);
   matrix traindata = np::selectMatrixRows(data,trainset);
   matrix train_preditors = np::sliceMatrixCols(traindata,0,4);
   vector train_targets = traindata.Col(4);
//---
   logistic::Clogit logit;
//--
   if(!logit.fit(train_preditors,train_targets))
     {
      Print(" error training logistic model ");
      return;
     }
//---
   matrix y_probas = logit.probas(test_predictors);
   vector y_preds = logit.predict(test_predictors);
//---
   vector auc = test_targets.ClassificationScore(y_probas,CLASSIFICATION_ROC_AUC,av_mode);
//---
   if(auc.Size()>0)
      Print(" AUC ", auc);
   else
      Print(" AUC error ", ErrorDescription(GetLastError()));
//---
   matrix fpr,tpr,threshs;
   if(!test_targets.ReceiverOperatingCharacteristic(y_probas,av_mode,fpr,tpr,threshs))
     {
      Print(" ROC error ", ErrorDescription(GetLastError()));
      return;
     }
//---
   string legend;
   for(ulong i = 0; i<auc.Size(); i++)
     {
      string temp = (i!=int(auc.Size()-1))?StringFormat("%.3lf,",auc[i]):StringFormat("%.3lf",auc[i]);
      StringAdd(legend,temp);
     }

   CGraphic* roc = np::plotMatrices(fpr, tpr,"ROC",false,"FPR","TPR",legend,true,0,0,10,10,600,500);
   if(CheckPointer(roc)!=POINTER_INVALID)
     {
      Sleep(7000);
      roc.Destroy();
      delete roc;
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+

Inicialmente, um problema de classificação binária é analisado, com o modo de média definido como AVERAGE_BINARY. Esse modo calcula a curva ROC e a AUC assumindo que a classe alvo está rotulada como 1, aderindo ao paradigma fundamental alvo/não-alvo inerente à geração de curvas ROC. A saída resultante da execução do script com essas configurações demonstra que o vetor de valores AUC contém um único elemento. Além disso, as matrizes FPR, TPR e de limiar contêm cada uma uma única linha.

Saída do script ROC Demo — Classificação binária

MK      0       12:45:28.930    ROC_Demo (Crash 1000 Index,M5)   AUC [1]

Para examinar a curva ROC e a AUC associada para classes alvo diferentes do rótulo 1, o modo de média deve ser definido como AVERAGE_NONE. Nesse modo, as funções ReceiverOperatingCharacteristic() e ClassificationScore() avaliam cada classe independentemente como alvo, fornecendo resultados correspondentes. A execução do script com AVERAGE_NONE e um problema de classificação binária produz a seguinte saída. Nesse caso, o gráfico ROC exibe duas curvas, representando os gráficos das respectivas linhas nas matrizes FPR e TPR. Dois valores de AUC também são calculados.

ROC Demo Classificação binária — sem média

CK      0       16:42:43.249    ROC_Demo (Crash 1000 Index,M5)   AUC [1,1]

Os resultados são apresentados de acordo com a ordem dos rótulos das classes. Por exemplo, no problema de classificação binária apresentado no script mencionado, onde os rótulos são 0 e 1, as primeiras linhas das matrizes TPR, FPR e de limiar representam os valores da curva ROC quando o rótulo 0 é considerado a classe alvo, enquanto as segundas linhas correspondem ao rótulo 1 como alvo. Essa convenção de ordenação também se aplica aos valores de AUC. É importante observar que esse formato de resultado é consistente em estatísticas ROC calculadas a partir de conjuntos de dados multiclasse. Em cenários de classificação binária, o uso de opções de média diferentes de AVERAGE_NONE e AVERAGE_BINARY aciona métodos computacionais equivalentes ao tratamento de conjuntos de dados multiclasse. Por outro lado, em classificação multiclasse, AVERAGE_BINARY gera um erro. No entanto, AVERAGE_MICRO, AVERAGE_MACRO e AVERAGE_WEIGHTED produzem um único valor agregado de AUC.

ROC Demo multiclasse — sem média

LL      0       17:27:13.673    ROC_Demo (Crash 1000 Index,M5)   AUC [1,0.9965694682675815,0.9969135802469137]

O modo de média AVERAGE_MICRO calcula curvas ROC e AUC globalmente tratando cada elemento da matriz de classes reais codificada em one-hot como um rótulo distinto. Essa abordagem transforma o problema multiclasse em um problema de classificação binária por meio da codificação one-hot inicial dos rótulos das classes reais, gerando uma matriz com colunas correspondentes ao número de classes. Essa matriz, juntamente com a matriz de probabilidades previstas ou valores de decisão, é então achatada em uma estrutura de coluna única. As subsequentes curvas ROC e cálculos de AUC são realizados sobre essas estruturas achatadas. Essa metodologia produz métricas de desempenho abrangentes que incluem todas as classes, atribuindo peso igual a cada instância. Isso é particularmente vantajoso em cenários com grande desbalanceamento de classes, mitigando viés em direção à classe majoritária. Resultados empíricos do script de demonstração ilustram esse comportamento.

ROC Demo multiclasse — média micro

RG      0       17:26:51.955    ROC_Demo (Crash 1000 Index,M5)   AUC [0.9984000000000001]

O modo de média AVERAGE_MACRO adota uma estratégia distinta para lidar com o desbalanceamento de classes. Cada classe é avaliada de forma independente, calculando a AUC com base nas probabilidades da respectiva classe. A AUC final é então derivada como a média aritmética desses valores individuais de AUC. É importante destacar que a documentação do MQL5 indica que AVERAGE_MACRO não é suportado pelo método ReceiverOperatingCharacteristic(). O método ClassificationScore() implementa a técnica OvR para cálculo de AUC em conjuntos de dados multiclasse. A execução do script com AVERAGE_MACRO confirma o cálculo bem-sucedido da AUC, enquanto a geração da curva ROC falha conforme esperado.

ER      0       17:26:09.341    ROC_Demo (Crash 1000 Index,M5)   AUC [0.997827682838166]
NF      0       17:26:09.341    ROC_Demo (Crash 1000 Index,M5)   ROC error Wrong parameter when calling the system function

O modo de média AVERAGE_WEIGHTED opera de forma análoga ao AVERAGE_MACRO, exceto pela etapa final de agregação. Em vez de uma média aritmética simples, é calculada uma média ponderada, onde os pesos são determinados pela distribuição das classes nos rótulos reais. Essa abordagem produz um valor final de AUC que leva em consideração as classes dominantes no conjunto de dados. Assim como AVERAGE_MACRO, ReceiverOperatingCharacteristic() não suporta AVERAGE_WEIGHTED. A saída do script exemplifica esse comportamento.

IN      0       17:26:28.465    ROC_Demo (Crash 1000 Index,M5)   AUC [0.9978825995807129]
HO      0       17:26:28.465    ROC_Demo (Crash 1000 Index,M5)   ROC error Wrong parameter when calling the system function


Conclusão

As curvas ROC são ferramentas eficazes para visualização e avaliação de classificadores. Elas oferecem uma avaliação mais abrangente do desempenho de classificação em comparação com métricas escalares como acurácia, taxa de erro ou custo de erro. Ao desacoplar o desempenho do classificador do desbalanceamento de classes e dos custos de erro, elas apresentam vantagens sobre metodologias alternativas de avaliação, incluindo curvas de precisão e recall. No entanto, como qualquer métrica de avaliação, o uso adequado de gráficos ROC requer uma compreensão aprofundada de suas características e limitações inerentes. Espera-se que este artigo contribua para a ampliação do conhecimento geral sobre curvas ROC e incentive a adoção de práticas de avaliação mais eficazes na comunidade.


Nome do Arquivo 
Descrição dos Arquivos
MQL5/Scripts/ROC_Demo.mq5 Script utilizado para demonstrar funções nativas do MQL5 relacionadas a curvas ROC.
MQL5/Scripts/ ROC_curves_table_demo.mq5 Script demonstrando a geração de diversas métricas de desempenho baseadas em ROC.
MQL5/Include/logisitc.mqh
Arquivo de cabeçalho contendo a definição da classe Clogit que implementa regressão logística.
MQL5/Include/roc_curves.mqh
Cabeçalho de funções personalizadas que implementam diversas utilidades para avaliação de desempenho em classificação binária.
MQL5/Include/np.mqh
Arquivo de cabeçalho com várias funções utilitárias para vetores e matrizes.
MQL5/Files/iris.csv Arquivo CSV do conjunto de dados iris.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/17390

Arquivos anexados |
iris.csv (3.29 KB)
np.mqh (90.9 KB)
logistic.mqh (16.16 KB)
roc_curves.mqh (8.92 KB)
ROC_Demo.mq5 (3.56 KB)
Mql5.zip (22.86 KB)
Uma Nova Abordagem para Critérios Personalizados em Otimizações (Parte 1): Exemplos de Funções de Ativação Uma Nova Abordagem para Critérios Personalizados em Otimizações (Parte 1): Exemplos de Funções de Ativação
O primeiro de uma série de artigos que analisam a matemática dos Critérios Personalizados com foco específico em funções não lineares usadas em Redes Neurais, código MQL5 para implementação e o uso de offsets direcionados e corretivos.
Ciência de Dados e ML (Parte 34): Decomposição de séries temporais, desmembrando o mercado de ações até o núcleo Ciência de Dados e ML (Parte 34): Decomposição de séries temporais, desmembrando o mercado de ações até o núcleo
Em um mundo repleto de dados ruidosos e imprevisíveis, identificar padrões significativos pode ser desafiador. Neste artigo, exploraremos a decomposição sazonal, uma poderosa técnica analítica que ajuda a separar os dados em seus principais componentes: tendência, padrões sazonais e ruído. Ao decompor os dados dessa forma, podemos revelar insights ocultos e trabalhar com informações mais limpas e interpretáveis.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Criando um Painel de Administração de Trading em MQL5 (Parte IX): Organização de Código (III): Módulo de Comunicação Criando um Painel de Administração de Trading em MQL5 (Parte IX): Organização de Código (III): Módulo de Comunicação
Junte-se a nós para uma discussão aprofundada sobre os mais recentes avanços no design de interfaces em MQL5 enquanto apresentamos o Painel de Comunicações redesenhado e continuamos nossa série sobre a construção do Novo Painel de Administração utilizando princípios de modularização. Desenvolveremos a classe CommunicationsDialog passo a passo, explicando detalhadamente como herdá-la da classe Dialog. Além disso, utilizaremos arrays e a classe ListView em nosso desenvolvimento. Obtenha insights práticos para elevar suas habilidades em desenvolvimento MQL5 — leia o artigo e participe da discussão na seção de comentários!