
Codificação ordinal de variáveis nominais
Introdução
Ao trabalhar com dados categóricos no aprendizado de máquina, frequentemente nos deparamos com variáveis nominais. Embora essas variáveis possam fornecer informações valiosas para a modelagem, muitos algoritmos de aprendizado de máquina, especialmente aqueles que operam exclusivamente com dados numéricos, não conseguem processá-las diretamente. Para resolver este problema, normalmente convertemos variáveis nominais em ordinais. Neste artigo, abordaremos as complexidades da transformação de variáveis nominais em ordinais. Examinaremos a fundamentação por trás dessas transformações, discutiremos diferentes métodos para atribuir valores ordinais e destacaremos as potenciais vantagens e desvantagens de cada abordagem. Além disso, demonstraremos esses métodos principalmente através de código em Python, e implementaremos também dois métodos universais de transformação em puro MQL5.
Entendendo variáveis nominais e ordinais
As variáveis nominais são dados categóricos sem nenhuma ordem ou posto inerente entre as categorias. Exemplos relacionados a conjuntos de dados financeiros de séries temporais podem incluir:
- Tipos de barras de preço (por exemplo, barra pino, spinning top, martelo)
- Dias da semana (por exemplo, segunda-feira, terça-feira, quarta-feira)
Essas variáveis são puramente qualitativas, o que significa que não há uma hierarquia ou sequência implícita entre as categorias. Por exemplo, a formação de uma barra pino não é intrinsecamente superior a um spinning top, assim como uma barra bullish não é melhor que uma barra bearish.
Nos cálculos numéricos, uma prática comum é atribuir números inteiros arbitrários às diferentes categorias. No entanto, quando esses números inteiros são utilizados como entradas para algoritmos de aprendizado de máquina, existe o risco de que os valores atribuídos possam distorcer as informações presentes nos dados originais. O algoritmo pode, erroneamente, presumir que valores maiores implicam algum tipo de relacionamento ou classificação, mesmo que isso não tenha sido intencional.
Por outro lado, as variáveis ordinais são dados categóricos com uma ordem ou classificação intrínseca entre suas categorias. Exemplos incluem:
- Força da tendência (por exemplo, tendência forte, tendência moderada, tendência fraca)
- Volatilidade (por exemplo, alta volatilidade, baixa volatilidade)
Compreender essa diferença ajuda a perceber por que atribuir números inteiros diretamente a categorias nominais nem sempre é apropriado. Para uma compreensão mais clara, criaremos um conjunto de dados contendo variáveis categóricas, que serão transformadas em formato ordinal usando diferentes métodos nas próximas seções. Coletaremos dados diários das barras (abertura, máxima, mínima, fechamento) do Bitcoin e geraremos variáveis nominais que serão usadas para prever o retorno do dia seguinte. A primeira variável nominal classifica as barras como bullish ou bearish. A segunda variável nominal possui quatro categorias distintas e agrupa as barras com base na proporção entre o tamanho do corpo da vela e seu tamanho total. Para a última variável nominal, serão criadas três categorias:
- Quando a barra atual e a anterior são ambas bullish, e a mínima e a máxima da barra atual são superiores à mínima e máxima da barra anterior, classificamos a barra atual como "máxima mais alta".
- O cenário oposto é classificado como "mínima mais baixa".
- Qualquer outro padrão de duas barras é atribuído à terceira categoria.
Abaixo está o código em Python que gera esse conjunto de dados.
# Copyright 2024, MetaQuotes Ltd. # https://www.mql5.com # imports from datetime import datetime import MetaTrader5 as mt5 import pandas as pd import numpy as np import pytz import os from category_encoders import OrdinalEncoder, OneHotEncoder, BinaryEncoder,TargetEncoder, CountEncoder, HashingEncoder, LeaveOneOutEncoder,JamesSteinEncoder if not mt5.initialize(): print("initialize() failed ") mt5.shutdown() exit() #set up timezone infomation tz=pytz.timezone("Etc/UTC") #use time zone to set correct date for history data extraction startdate = datetime(2023,12,31,hour=23,minute=59,second=59,tzinfo=tz) stopdate = datetime(2017,12,31,hour=23,minute=59,second=59,tzinfo=tz) #list the symbol symbol = "BTCUSD" #get price history prices = pd.DataFrame(mt5.copy_rates_range(symbol,mt5.TIMEFRAME_D1,stopdate,startdate)) if len(prices) < 1: print(" Error downloading rates history ") mt5.shutdown() exit() #shutdown mt5 tether mt5.shutdown() #drop unnecessary columns prices.drop(labels=["time","tick_volume","spread","real_volume"],axis=1,inplace=True) #initialize categorical features prices["bar_type"] = np.where(prices["close"]>=prices["open"],"bullish","bearish") prices["body_type"] = np.empty((len(prices),),dtype='str') prices["bar_pattern"] = np.empty((len(prices),),dtype='str') #set feature values for i in np.arange(len(prices)): bodyratio = np.abs(prices.iloc[i,3]-prices.iloc[i,0])/np.abs(prices.iloc[i,1]-prices.iloc[i,2]) if bodyratio >= 0.75: prices.iloc[i,5] = ">=0.75" elif bodyratio < 0.75 and bodyratio >= 0.5: prices.iloc[i,5]=">=0.5<0.75" elif bodyratio < 0.5 and bodyratio >= 0.25: prices.iloc[i,5]=">=0.25<0.5" else: prices.iloc[i,5]="<0.25" if i < 1: prices.iloc[i,6] = None continue if(prices.iloc[i,4]=="bullish" and prices.iloc[i-1,4]=="bullish") and (prices.iloc[i,1]>prices.iloc[i-1,1]) and (prices.iloc[i,2]>prices.iloc[i-1,2]): prices.iloc[i,6] = "higherHigh" elif(prices.iloc[i,4]=="bearish" and prices.iloc[i-1,4]=="bearish") and (prices.iloc[i,2]<prices.iloc[i-1,2]) and (prices.iloc[i,1]<prices.iloc[i-1,1]): prices.iloc[i,6] = "lowerLow" else : prices.iloc[i,6] = "flat" #calculate target look_ahead = 1 prices["target"] = np.log(prices["close"]) prices["target"] = prices["target"].diff(look_ahead) prices["target"] = prices["target"].shift(-look_ahead) #drop rows with NA values prices.dropna(axis=0,inplace=True,ignore_index=True) print("Full feature matrix \n",prices.head())
Observe que, na linguagem Python, as categorias recebem nomes textuais, enquanto nos trechos de código em MQL5 apresentados a seguir, utilizam-se números inteiros para diferenciar as categorias.
//get relative shift of is and oos sets int trainstart,trainstop; trainstart=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TrainingSampleStartDate); trainstop=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TrainingSampleStopDate); //check for errors from ibarshift calls if(trainstart<0 || trainstop<0) { Print(ErrorDescription(GetLastError())); return; } //---set the size of the sample sets size_insample=(trainstop - trainstart) + 1; //---check for input errors if(size_insample<=0) { Print("Invalid inputs "); return; } //--- if(!predictors.Resize(size_insample,3)) { Print("ArrayResize error ",ErrorDescription(GetLastError())); return; } //--- if(!prices.CopyRates(SetSymbol,tf,COPY_RATES_VERTICAL|COPY_RATES_OHLC,TrainingSampleStartDate,TrainingSampleStopDate)) { Print("Copyrates error ",ErrorDescription(GetLastError())); return; } //--- targets = log(prices.Col(3)); targets = np::diff(targets); //--- double bodyratio = 0.0; for(ulong i = 0; i<prices.Rows(); i++) { if(prices[i][3]<prices[i][0]) predictors[i][0] = 0.0; else predictors[i][0] = 1.0; bodyratio = MathAbs(prices[i][3]-prices[i][0])/MathAbs(prices[i][1]-prices[i][2]); if(bodyratio >=0.75) predictors[i][1] = 0.0; else if(bodyratio<0.75 && bodyratio>=0.5) predictors[i][1] = 1.0; else if(bodyratio<0.5 && bodyratio>=0.25) predictors[i][1] = 2.0; else predictors[i][1] = 3.0; if(i<1) { predictors[i][2] = 0.0; continue; } if(predictors[i][0]==1.0 && predictors[i-1][0]==1.0 && prices[i][1]>prices[i-1][1] && prices[i][2]>prices[i-1][2]) predictors[i][2] = 2.0; else if(predictors[i][0]==0.0 && predictors[i-1][0]==0.0 && prices[i][2]<prices[i-1][2] && prices[i][1]>prices[i-1][1]) predictors[i][2] = 1.0; else predictors[i][2] = 0.0; } targets = np::sliceVector(targets,1); prices = np::sliceMatrixRows(prices,1,predictors.Rows()-1); predictors = np::sliceMatrixRows(predictors,1,predictors.Rows()-1); matrix fullFeatureMatrix(predictors.Rows(),predictors.Cols()+prices.Cols()); if(!np::matrixCopyCols(fullFeatureMatrix,prices,0,prices.Cols()) || !np::matrixCopyCols(fullFeatureMatrix,predictors,prices.Cols())) { Print("Failed to merge matrices"); return; }
Abaixo está um trecho desse conjunto de dados.
Por que converter variáveis nominais em ordinais
Alguns algoritmos de aprendizado de máquina, como árvores de decisão, podem lidar diretamente com dados nominais. No entanto, outros — especialmente modelos lineares, como regressão logística ou redes neurais — exigem dados numéricos. Transformar variáveis nominais em ordinais pode torná-las utilizáveis por esses modelos, permitindo que os algoritmos aprendam de forma mais eficiente a partir dos dados. Embora as variáveis ordinais representem categorias, elas também fornecem uma sequência ou ordem clara, oferecendo aos algoritmos mais contexto para entender as relações existentes. Quando uma variável nominal compartilha informações significativas com a variável alvo, muitas vezes é vantajoso aumentar seu nível de mensuração. Caso uma variável nominal possua valores numéricos significativos, esses podem ser utilizados diretamente como entradas para o modelo. Contudo, mesmo quando os valores não possuem uma interpretação numérica intrínseca, podemos frequentemente atribuir valores ordinais com base em sua relação com a variável alvo.
Ao converter uma variável nominal em uma escala ordinal, criamos uma percepção de ordem ou classificação entre as categorias. Isso pode aumentar a capacidade do modelo de capturar padrões subjacentes e relações entre a variável e o alvo. Embora teoricamente seja possível elevar uma variável nominal ao mesmo nível de mensuração da variável alvo, na prática, muitas vezes é suficiente convertê-la para uma escala ordinal. Essa abordagem fornece um equilíbrio entre preservar o conteúdo informacional da variável e minimizar o ruído. Nas próximas seções, abordaremos métodos comuns para transformar variáveis nominais em valores ordinais, assim como recomendações importantes para preservar a integridade dos dados.
Métodos de transformação de variáveis nominais
Começaremos pelo método mais simples: a codificação ordinal. Nesse método, simplesmente atribuímos um valor inteiro a cada categoria. Como mencionado anteriormente, isso cria uma classificação ou ordem entre as categorias. Caso o profissional esteja familiarizado com os dados e saiba de antemão como as categorias se relacionam com a variável alvo, esse método pode ser suficiente. Contudo, a codificação ordinal não deve ser utilizada em aprendizado não supervisionado, pois pode facilmente levar a distorções, implicando uma ordem onde ela não existe.
Para transformar nossas variáveis nominais em Python, utilizaremos o pacote category_encoders. Esse pacote fornece uma ampla gama de implementações para codificação categórica, tornando-o adequado para a maioria das tarefas. Os leitores podem encontrar informações adicionais sobre o projeto no repositório do GitHub.
Para transformar variáveis em formato ordinal numérico, precisamos do objeto OrdinalEncoder.
#Ordinal encoding ord_encoder = OrdinalEncoder(cols = ["bar_type","body_type","bar_pattern"]) ordinal_data = ord_encoder.fit_transform(prices) print(" ordinal encoding\n ", ordinal_data.head())
Dados transformados:
Métodos adequados para transformação em algoritmos de aprendizado não supervisionado incluem a codificação binária, a codificação one-hot e a codificação por frequência. A codificação one-hot transforma cada categoria em uma coluna binária, onde a presença da categoria é indicada pelo valor 1 e a ausência por 0. A principal desvantagem desse método é que ele aumenta significativamente o número de variáveis de entrada. Para cada categoria da variável categórica, uma nova variável é criada. Por exemplo, ao codificarmos os meses do ano, acabaríamos com 11 variáveis adicionais de entrada.
O objeto 'OneHotEncoder' realiza a codificação one-hot no pacote category_encoders.
#One-Hot encoding onehot_encoder = OneHotEncoder(cols = ["bar_type","body_type","bar_pattern"]) onehot_data = onehot_encoder.fit_transform(prices) print(" ordinal encoding\n ", onehot_data.head())
Dados transformados:
A codificação binária é uma alternativa mais eficiente, especialmente ao lidar com um grande número de categorias. Nesse método, cada categoria é inicialmente convertida em um número inteiro único, e depois esse inteiro é representado em formato binário. Essa representação binária é distribuída por múltiplas colunas, o que normalmente resulta em menos colunas do que a codificação one-hot. Por exemplo, para codificar 12 meses, seriam necessárias apenas 4 colunas binárias. A codificação binária funciona bem em cenários onde a variável categórica possui muitas categorias únicas e desejamos limitar o número de variáveis de entrada.
Codificação binária do nosso conjunto de dados BTCUSD.
#Binary encoding binary_encoder = BinaryEncoder(cols = ["bar_type","body_type","bar_pattern"]) binary_data = binary_encoder.fit_transform(prices) print(" binary encoding\n ", binary_data.head())
Dados transformados:
A codificação por frequência transforma uma variável categórica substituindo cada categoria pela frequência com que ela aparece no conjunto de dados. Em vez de criar várias colunas, cada categoria é substituída pela proporção ou contagem de sua ocorrência. Essa abordagem é útil quando há uma relação significativa entre a frequência da categoria e a variável alvo, pois permite preservar informações valiosas em um formato mais compacto. No entanto, se certas categorias predominarem no conjunto de dados, isso pode gerar viés. Essa técnica é frequentemente usada como primeiro passo em pipelines mais complexos de criação de características em cenários de aprendizado não supervisionado.
Aqui utilizaremos o objeto 'CountEncoder'.
#Frequency encoding freq_encoder = CountEncoder(cols = ["bar_type","body_type","bar_pattern"]) freq_data = freq_encoder.fit_transform(prices) print(" frequency encoding\n ", freq_data.head())
Dados transformados:
A codificação binária, a one-hot e a codificação por frequência são métodos universais que podem ser aplicados à maioria dos dados categóricos sem o risco de efeitos colaterais indesejados que possam afetar os resultados do aprendizado. Essas transformações possuem a característica importante de serem independentes da variável alvo.
Entretanto, em alguns casos, o algoritmo de aprendizado de máquina pode se beneficiar de transformações que refletem diretamente a relação da variável com a variável alvo. Esses métodos utilizam a variável alvo para transformar dados categóricos em valores numéricos, atribuindo-lhes certo grau associativo, o que potencialmente aumenta o poder preditivo do modelo.
Um desses métodos é a codificação alvo, também conhecida como codificação pela média. Nessa abordagem, cada categoria é substituída pelo valor médio da variável alvo para aquela categoria. Por exemplo, se estivermos prevendo a probabilidade de uma ação fechar em alta (alvo binário), poderíamos substituir cada categoria em uma variável nominal, como intervalos de volume de negociação, pela probabilidade média de fechamento em alta para cada intervalo. Codificação alvo pode ser especialmente eficiente para variáveis categóricas com alta cardinalidade, pois agrega informações úteis sem aumentar a dimensionalidade do conjunto de dados. Esse método captura a relação entre a variável categórica e o alvo, sendo eficiente em situações de aprendizado supervisionado. É mais eficaz quando as categorias têm uma forte correlação com o alvo. Mas também deve ser combinada com procedimentos para mitigar o sobreajuste, garantindo uma melhor generalização.
#Target encoding target_encoder = TargetEncoder(cols = ["bar_type","body_type","bar_pattern"]) target_data = target_encoder.fit_transform(prices[["open","high","low","close","bar_type","body_type","bar_pattern"]], prices["target"]) print(" target encoding\n ", target_data.head())
Dados transformados:
Outro método dependente do alvo é a codificação com exclusão única (Leave-One-Out encoding). Funciona de maneira semelhante à codificação alvo, porém ajusta a codificação excluindo o valor da variável alvo da linha atual ao calcular a média para aquela categoria. Isso ajuda a reduzir o sobreajuste, especialmente em conjuntos de dados pequenos ou quando algumas categorias estão excessivamente representadas. Codificação com exclusão única assegura que a transformação permaneça independente da linha que está sendo processada, preservando assim a integridade do processo de aprendizado.
#LeaveOneOut encoding oneout_encoder = LeaveOneOutEncoder(cols = ["bar_type","body_type","bar_pattern"]) oneout_data = oneout_encoder.fit_transform(prices[["open","high","low","close","bar_type","body_type","bar_pattern"]], prices["target"]) print(" LeaveOneOut encoding\n ", oneout_data.head())
Dados transformados:
Codificação pelo método de James-Stein é uma abordagem bayesiana de codificação que ajusta a estimativa da média alvo para cada categoria em direção à média geral, dependendo da quantidade de dados disponível para cada categoria. Esse método é especialmente útil em conjuntos de dados com baixa representatividade, onde métodos tradicionais, como codificação alvo ou codificação com exclusão única, podem levar ao sobreajuste, particularmente em conjuntos de dados pequenos ou ao lidar com categorias com distribuições extremamente desiguais. Ao ajustar as médias das categorias com base na média global, a codificação James-Stein reduz o risco de influência excessiva de valores extremos no modelo. Isso resulta em estimativas mais estáveis e confiáveis, tornando-o uma alternativa eficaz ao trabalhar com dados esparsos ou categorias com poucas observações.
#James Stein encoding james_encoder = JamesSteinEncoder(cols = ["bar_type","body_type","bar_pattern"]) james_data = james_encoder.fit_transform(prices[["open","high","low","close","bar_type","body_type","bar_pattern"]], prices["target"]) print(" James Stein encoding\n ", james_data.head())
Dados transformados:
A biblioteca category_encoders oferece uma ampla gama de métodos de codificação adaptados para diferentes tipos de dados categóricos e tarefas de aprendizado de máquina. O método apropriado de codificação depende da natureza dos dados, do algoritmo de aprendizado de máquina utilizado e das exigências específicas da tarefa proposta. Assim, a codificação one-hot é um método universal adequado para muitos casos de uso, especialmente com variáveis nominais. A codificação alvo, a codificação com exclusão única e o método de James-Stein devem ser utilizados quando há necessidade de destacar a relação entre a variável e o valor alvo. Por fim, a codificação binária e a codificação por hashing são métodos úteis quando o objetivo é reduzir a dimensionalidade mantendo informações relevantes.
Convertendo uma variável nominal em ordinal usando MQL5
Nesta seção, implementaremos dois métodos de codificação de variáveis nominais em MQL5. Ambos estão encapsulados em classes declaradas no arquivo de cabeçalho nom2ord.mqh. A classe COneHotEncoder implementa a codificação one-hot de conjuntos de dados e inclui dois métodos principais com os quais o usuário deve se familiarizar.
//+------------------------------------------------------------------+ //| one hot encoder class | //+------------------------------------------------------------------+ class COneHotEncoder { private: vector m_mapping[]; ulong m_cat_cols[]; ulong m_vars,m_cols; public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ COneHotEncoder(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ ~COneHotEncoder(void) { ArrayFree(m_mapping); } //+------------------------------------------------------------------+ //| map categorical features of a training dataset | //+------------------------------------------------------------------+ bool fit(matrix &in_data,ulong &cols[]) { m_cols = in_data.Cols(); matrix data = np::selectMatrixCols(in_data,cols); if(data.Cols()!=ulong(cols.Size())) { Print(__FUNCTION__, " invalid input data "); return false; } m_vars = ulong(cols.Size()); if(ArrayCopy(m_cat_cols,cols)<=0 || !ArraySort(m_cat_cols)) { Print(__FUNCTION__, " ArrayCopy or ArraySort failure ", GetLastError()); return false; } if(ArrayResize(m_mapping,int(m_vars))<0) { Print(__FUNCTION__, " Vector array resize failure ", GetLastError()); return false; } for(ulong i = 0; i<m_vars; i++) { vector unique = data.Col(i); m_mapping[i] = np::unique(unique); } return true; } //+------------------------------------------------------------------+ //| Transform abitrary feature matrix to learned category m_mapping | //+------------------------------------------------------------------+ matrix transform(matrix &in_data) { if(in_data.Cols()!=m_cols) { Print(__FUNCTION__," Column dimension of input not equal to ", m_cols); return matrix::Zeros(1,1); } matrix out,input_copy; matrix data = np::selectMatrixCols(in_data,m_cat_cols); if(data.Cols()!=ulong(m_cat_cols.Size())) { Print(__FUNCTION__, " invalid input data "); return matrix::Zeros(1,1); } ulong unchanged_feature_cols[]; for(ulong i = 0; i<in_data.Cols(); i++) { int found = ArrayBsearch(m_cat_cols,i); if(m_cat_cols[found]!=i) { if(!unchanged_feature_cols.Push(i)) { Print(__FUNCTION__, " Failed array insertion ", GetLastError()); return matrix::Zeros(1,1); } } } input_copy = unchanged_feature_cols.Size()?np::selectMatrixCols(in_data,unchanged_feature_cols):input_copy; ulong numcols = 0; vector cumsum = vector::Zeros(ulong(MathMin(m_vars,data.Cols()))); for(ulong i = 0; i<cumsum.Size(); i++) { cumsum[i] = double(numcols); numcols+=m_mapping[i].Size(); } out = matrix::Zeros(data.Rows(),numcols); for(ulong i = 0;i<data.Rows(); i++) { vector row = data.Row(i); for(ulong col = 0; col<row.Size(); col++) { for(ulong k = 0; k<m_mapping[col].Size(); k++) { if(MathAbs(row[col]-m_mapping[col][k])<=1.e-15) { out[i][ulong(cumsum[col])+k]=1.0; break; } } } } matrix newfeaturematrix(out.Rows(),input_copy.Cols()+out.Cols()); if((input_copy.Cols()>0 && !np::matrixCopyCols(newfeaturematrix,input_copy,0,input_copy.Cols())) || !np::matrixCopyCols(newfeaturematrix,out,input_copy.Cols())) { Print(__FUNCTION__, " Failed matrix copy "); return matrix::Zeros(1,1); } return newfeaturematrix; } }; //+------------------------------------------------------------------+
O primeiro método é fit(), que deve ser chamado após a criação de uma instância da classe. Esse método requer dois inputs: uma matriz de características (dados de treinamento) e um array. A matriz de características pode representar um conjunto completo de dados, incluindo variáveis categóricas e não categóricas. Nesse caso, o array deve conter os índices das colunas das variáveis nominais na matriz. Caso seja passado um array vazio, assume-se que todas as variáveis são nominais. Após a execução bem-sucedida do método fit() e seu retorno true, é possível chamar o método transform() para obter o conjunto de dados transformado. Esse método exige uma matriz contendo o mesmo número de colunas que a matriz fornecida ao método fit(). Se as dimensões não corresponderem, será exibida uma mensagem de erro.
Vamos examinar como funciona a classe COneHotEncoder usando uma demonstração com o conjunto de dados BTCUSD preparado anteriormente neste artigo. O trecho abaixo ilustra o processo de transformação. Esse código foi retirado do script OneHotEncoding_demo.mq5 do MetaTrader 5.
//+------------------------------------------------------------------+ //| OneHotEncoding_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<np.mqh> #include<nom2ord.mqh> #include<ErrorDescription.mqh> //--- input parameters input datetime TrainingSampleStartDate=D'2023.12.31'; input datetime TrainingSampleStopDate=D'2017.12.31'; input ENUM_TIMEFRAMES tf = PERIOD_D1; input string SetSymbol="BTCUSD"; //+------------------------------------------------------------------+ //|global integer variables | //+------------------------------------------------------------------+ int size_insample, //training set size size_observations, //size of of both training and testing sets combined price_handle=INVALID_HANDLE; //log prices indicator handle //+------------------------------------------------------------------+ //|double global variables | //+------------------------------------------------------------------+ matrix prices; //array for log transformed prices vector targets; //differenced prices kept here matrix predictors; //flat array arranged as matrix of all predictors ie size_observations by size_predictors //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //get relative shift of is and oos sets int trainstart,trainstop; trainstart=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TrainingSampleStartDate); trainstop=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TrainingSampleStopDate); //check for errors from ibarshift calls if(trainstart<0 || trainstop<0) { Print(ErrorDescription(GetLastError())); return; } //---set the size of the sample sets size_insample=(trainstop - trainstart) + 1; //---check for input errors if(size_insample<=0) { Print("Invalid inputs "); return; } //--- if(!predictors.Resize(size_insample,3)) { Print("ArrayResize error ",ErrorDescription(GetLastError())); return; } //--- if(!prices.CopyRates(SetSymbol,tf,COPY_RATES_VERTICAL|COPY_RATES_OHLC,TrainingSampleStartDate,TrainingSampleStopDate)) { Print("Copyrates error ",ErrorDescription(GetLastError())); return; } //--- targets = log(prices.Col(3)); targets = np::diff(targets); //--- double bodyratio = 0.0; for(ulong i = 0; i<prices.Rows(); i++) { if(prices[i][3]<prices[i][0]) predictors[i][0] = 0.0; else predictors[i][0] = 1.0; bodyratio = MathAbs(prices[i][3]-prices[i][0])/MathAbs(prices[i][1]-prices[i][2]); if(bodyratio >=0.75) predictors[i][1] = 0.0; else if(bodyratio<0.75 && bodyratio>=0.5) predictors[i][1] = 1.0; else if(bodyratio<0.5 && bodyratio>=0.25) predictors[i][1] = 2.0; else predictors[i][1] = 3.0; if(i<1) { predictors[i][2] = 0.0; continue; } if(predictors[i][0]==1.0 && predictors[i-1][0]==1.0 && prices[i][1]>prices[i-1][1] && prices[i][2]>prices[i-1][2]) predictors[i][2] = 2.0; else if(predictors[i][0]==0.0 && predictors[i-1][0]==0.0 && prices[i][2]<prices[i-1][2] && prices[i][1]>prices[i-1][1]) predictors[i][2] = 1.0; else predictors[i][2] = 0.0; } targets = np::sliceVector(targets,1); prices = np::sliceMatrixRows(prices,1,predictors.Rows()-1); predictors = np::sliceMatrixRows(predictors,1,predictors.Rows()-1); matrix fullFeatureMatrix(predictors.Rows(),predictors.Cols()+prices.Cols()); if(!np::matrixCopyCols(fullFeatureMatrix,prices,0,prices.Cols()) || !np::matrixCopyCols(fullFeatureMatrix,predictors,prices.Cols())) { Print("Failed to merge matrices"); return; } if(predictors.Rows()!=targets.Size()) { Print(" Error in aligning data structures "); return; } COneHotEncoder enc; ulong selectedcols[] = {4,5,6}; if(!enc.fit(fullFeatureMatrix,selectedcols)) return; matrix transformed = enc.transform(fullFeatureMatrix); Print(" Original predictors \n", fullFeatureMatrix); Print(" transformed predictors \n", transformed); } //+------------------------------------------------------------------+
Matriz de características antes:
RQ 0 16:40:41.760 OneHotEncoding_demo (BTCUSD,D1) Original predictors ED 0 16:40:41.761 OneHotEncoding_demo (BTCUSD,D1) [[13743,13855,12362.69,13347,0,2,0] RN 0 16:40:41.761 OneHotEncoding_demo (BTCUSD,D1) [13348,15381,12535.67,14689,1,2,0] DG 0 16:40:41.761 OneHotEncoding_demo (BTCUSD,D1) [14232.48,15408,14110.57,15130,1,1,2] HH 0 16:40:41.761 OneHotEncoding_demo (BTCUSD,D1) [15114,15370,13786.18,15139,1,3,0] QN 0 16:40:41.761 OneHotEncoding_demo (BTCUSD,D1) [15055.8,16894,14349.84,16725,1,1,2] RP 0 16:40:41.761 OneHotEncoding_demo (BTCUSD,D1) [15699.53,16474,15672.99,16186,1,1,0] OI 0 16:40:41.761 OneHotEncoding_demo (BTCUSD,D1) [16187,16258,13639.83,14900,0,2,0] ML 0 16:40:41.761 OneHotEncoding_demo (BTCUSD,D1) [14884,15334,13777.33,14405,0,2,0] GS 0 16:40:41.761 OneHotEncoding_demo (BTCUSD,D1) [14405,14876,12969.58,14876,1,3,0] KF 0 16:40:41.761 OneHotEncoding_demo (BTCUSD,D1) [14876,14927,12417.22,13245,0,1,0] ON 0 16:40:41.761 OneHotEncoding_demo (BTCUSD,D1) [12776.79,14078.5,12355.38,13681,1,1,0…]]
Matriz de características depois:
PH 0 16:40:41.762 OneHotEncoding_demo (BTCUSD,D1) transformed predictors KP 0 16:40:41.762 OneHotEncoding_demo (BTCUSD,D1) [[13743,13855,12362.69,13347,1,0,1,0,0,0,1,0,0] KP 0 16:40:41.762 OneHotEncoding_demo (BTCUSD,D1) [13348,15381,12535.67,14689,0,1,1,0,0,0,1,0,0] NF 0 16:40:41.762 OneHotEncoding_demo (BTCUSD,D1) [14232.48,15408,14110.57,15130,0,1,0,1,0,0,0,1,0] JI 0 16:40:41.762 OneHotEncoding_demo (BTCUSD,D1) [15114,15370,13786.18,15139,0,1,0,0,1,0,1,0,0] CL 0 16:40:41.762 OneHotEncoding_demo (BTCUSD,D1) [15055.8,16894,14349.84,16725,0,1,0,1,0,0,0,1,0] RL 0 16:40:41.762 OneHotEncoding_demo (BTCUSD,D1) [15699.53,16474,15672.99,16186,0,1,0,1,0,0,1,0,0] IS 0 16:40:41.762 OneHotEncoding_demo (BTCUSD,D1) [16187,16258,13639.83,14900,1,0,1,0,0,0,1,0,0] GG 0 16:40:41.762 OneHotEncoding_demo (BTCUSD,D1) [14884,15334,13777.33,14405,1,0,1,0,0,0,1,0,0] QK 0 16:40:41.762 OneHotEncoding_demo (BTCUSD,D1) [14405,14876,12969.58,14876,0,1,0,0,1,0,1,0,0] PL 0 16:40:41.762 OneHotEncoding_demo (BTCUSD,D1) [14876,14927,12417.22,13245,1,0,0,1,0,0,1,0,0] GS 0 16:40:41.762 OneHotEncoding_demo (BTCUSD,D1) [12776.79,14078.5,12355.38,13681,0,1,0,1,0,0,1,0,0.]]
O segundo método de transformação implementado em MQL5 funciona em dois modos, ambos variações da codificação alvo modificada para mitigar os efeitos do sobreajuste. Esse método está encapsulado na classe CNomOrd, definida em nom2ord.mqh. A classe usa os métodos familiares fit() e transform() para transformar variáveis sem reduzir a dimensionalidade dos dados categóricos de entrada.
public: //+------------------------------------------------------------------+ //| constructor | //+------------------------------------------------------------------+ CNomOrd(void) { } //+------------------------------------------------------------------+ //| destructor | //+------------------------------------------------------------------+ ~CNomOrd(void) { } //+------------------------------------------------------------------+ //| fit mapping to training data | //+------------------------------------------------------------------+ bool fit(matrix &preds_in, ulong &cols[], vector &target) { m_dim_reduce = 0; mapped = false; //--- if(cols.Size()==0 && preds_in.Cols()) { m_pred = int(preds_in.Cols()); if(!np::arange(m_colindices,m_pred,ulong(0),ulong(1))) { Print(__FUNCTION__, " arange error "); return mapped; } } else { m_pred = int(cols.Size()); } //--- m_rows = int(preds_in.Rows()) ; m_cols = int(preds_in.Cols()); //--- if(ArrayResize(m_mean_rankings,m_pred)<0 || ArrayResize(m_rankings,m_rows)<0 || ArrayResize(m_indices,m_rows)<0 || ArrayResize(m_mapping,m_pred)<0 || ArrayResize(m_class_counts,m_pred)<0 || !m_median.Resize(m_pred) || !m_class_ids.Resize(m_rows,m_pred) || !shuffle_target.Resize(m_rows,2) || (cols.Size()>0 && ArrayCopy(m_colindices,cols)<0) || !ArraySort(m_colindices)) { Print(__FUNCTION__, " Memory allocation failure ", GetLastError()); return mapped; } //--- for(uint col = 0; col<m_colindices.Size(); col++) { vector var = preds_in.Col(m_colindices[col]); m_mapping[col] = np::unique(var); m_class_counts[col] = vector::Zeros(m_mapping[col].Size()); for(ulong i = 0; i<var.Size(); i++) { for(ulong j = 0; j<m_mapping[col].Size(); j++) { if(MathAbs(var[i]-m_mapping[col][j])<=1.e-15) { m_class_ids[i][col]=double(j); ++m_class_counts[col][j]; break; } } } } m_target = target; for(uint i = 0; i<m_colindices.Size(); i++) { vector cid = m_class_ids.Col(i); vector ccounts = m_class_counts[i]; m_mean_rankings[i] = train(cid,ccounts,m_target,m_median[i]); } mapped = true; return mapped; } //+------------------------------------------------------------------+ //| transform nominal to ordinal based on learned mapping | //+------------------------------------------------------------------+ matrix transform(matrix &data_in) { if(m_dim_reduce) { Print(__FUNCTION__, " Invalid method call, Use fitTransform() or call fit() "); return matrix::Zeros(1,1); } if(!mapped) { Print(__FUNCTION__, " Invalid method call, training either failed or was not done. Call fit() first. "); return matrix::Zeros(1,1); } if(data_in.Cols()!=ulong(m_cols)) { Print(__FUNCTION__, " Unexpected input data shape, doesnot match training data "); return matrix::Zeros(1,1); } //--- matrix out = data_in; //--- for(uint col = 0; col<m_colindices.Size(); col++) { vector var = data_in.Col(m_colindices[col]); for(ulong i = 0; i<var.Size(); i++) { for(ulong j = 0; j<m_mapping[col].Size(); j++) { if(MathAbs(var[i]-m_mapping[col][j])<=1.e-15) { out[i][m_colindices[col]]=m_mean_rankings[col][j]; break; } } } } //--- return out; }
O método fit() requer um vetor adicional como entrada, representando a respectiva variável alvo. O esquema de codificação difere da codificação alvo padrão para minimizar o sobreajuste que frequentemente ocorre devido a valores atípicos na distribuição das variáveis alvo. Para abordar esse problema, a classe utiliza uma transformação baseada em percentis. Os valores da variável alvo são convertidos em uma escala baseada em percentis, onde o valor mínimo recebe um posto percentil de 0, o máximo 100, e os valores intermediários são escalados proporcionalmente. Essa abordagem preserva efetivamente a relação ordinal entre os valores, reduzindo simultaneamente a influência de valores atípicos.
//+------------------------------------------------------------------+ //| test for a genuine relationship between predictor and target | //+------------------------------------------------------------------+ double score(int reps, vector &test_target,ulong selectedVar=0) { if(!mapped) { Print(__FUNCTION__, " Invalid method call, training either failed or was not done. Call fit() first. "); return -1.0; } if(m_dim_reduce==0 && selectedVar>=ulong(m_colindices.Size())) { Print(__FUNCTION__, " invalid predictor selection "); return -1.0; } if(test_target.Size()!=m_rows) { Print(__FUNCTION__, " invalid targets parameter, Does not match shape of training data. "); return -1.0; } int i, j, irep, unif_error ; double dtemp, min_neg, max_neg, min_pos, max_pos, medn ; dtemp = 0.0; min_neg = 0.0; max_neg = -DBL_MIN; min_pos = DBL_MAX; max_pos = DBL_MIN ; vector id = (m_dim_reduce)?m_class_ids.Col(0):m_class_ids.Col(selectedVar); vector cc = (m_dim_reduce)?m_class_counts[0]:m_class_counts[selectedVar]; int nclasses = int(cc.Size()); if(reps < 1) reps = 1 ; for(irep=0 ; irep<reps ; irep++) { if(!shuffle_target.Col(test_target,0)) { Print(__FUNCTION__, " error filling shuffle_target column ", GetLastError()); return -1.0; } if(irep) { i = m_rows ; while(i > 1) { j = (int)(MathRandomUniform(0.0,1.0,unif_error) * i) ; if(unif_error) { Print(__FUNCTION__, " mathrandomuniform() error ", unif_error); return -1.0; } if(j >= i) j = i - 1 ; dtemp = shuffle_target[--i][0] ; shuffle_target[i][0] = shuffle_target[j][0] ; shuffle_target[j][0] = dtemp ; } } vector totrain = shuffle_target.Col(0); vector m_ranks = train(id,cc,totrain,medn) ; if(irep == 0) { for(i=0 ; i<nclasses ; i++) { if(i == 0) min_pos = max_pos = m_ranks[i] ; else { if(m_ranks[i] > max_pos) max_pos = m_ranks[i] ; if(m_ranks[i] < min_pos) min_pos = m_ranks[i] ; } } // For i<nclasses orig_max_class = max_pos - min_pos ; count_max_class = 1 ; } else { for(i=0 ; i<nclasses ; i++) { if(i == 0) min_pos = max_pos = m_ranks[i]; else { if(m_ranks[i] > max_pos) max_pos = m_ranks[i] ; if(m_ranks[i] < min_pos) min_pos = m_ranks[i] ; } } // For i<nclasses if(max_pos - min_pos >= orig_max_class) ++count_max_class ; } } if(reps <= 1) return -1.0; return double(count_max_class)/ double(reps); }
O método score() é usado para avaliar a significância estatística da relação implícita entre a variável ordinal e o alvo. Um teste de permutação baseado no método de Monte Carlo é utilizado para embaralhar repetidamente os dados da variável alvo, recalculando cada vez a relação observada. Ao comparar a relação observada com a distribuição das relações obtidas por permutações aleatórias, podemos estimar a probabilidade de que a relação observada seja apenas fruto do acaso. Para quantificar a relação observada, calculamos a diferença entre o percentil médio máximo e mínimo da variável alvo em todas as categorias da variável nominal. Dessa forma, o método score() retorna um valor de probabilidade que serve como resultado de um teste de hipótese, onde a hipótese nula afirma que a diferença observada poderia ocorrer ao acaso devido à ausência de relação entre a variável nominal e o alvo.
Vejamos como isso funciona, aplicando a classe ao nosso conjunto de dados BTCUSD. Esta demonstração é apresentada no script MQL5: TargetBasedNominalVariableConversion_demo.mq5.
CNomOrd enc; ulong selectedcols[] = {4,5,6}; if(!enc.fit(fullFeatureMatrix,selectedcols,targets)) return; matrix transformed = enc.transform(fullFeatureMatrix); Print(" Original predictors \n", fullFeatureMatrix); Print(" transformed predictors \n", transformed); for(uint i = 0; i<selectedcols.Size(); i++) Print(" Probability that predicator at ", selectedcols[i] , " is associated with target ", enc.score(10000,targets,ulong(i)));
Dados transformados:
IQ 0 16:44:25.680 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) transformed predictors LM 0 16:44:25.680 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) [[13743,13855,12362.69,13347,52.28360492434251,50.66453470243147,50.45172139701621] CN 0 16:44:25.680 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) [13348,15381,12535.67,14689,47.85025875164135,50.66453470243147,50.45172139701621] IH 0 16:44:25.680 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) [14232.48,15408,14110.57,15130,47.85025875164135,49.77386885151457,48.16372967916465] FF 0 16:44:25.680 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) [15114,15370,13786.18,15139,47.85025875164135,49.23046392011166,50.45172139701621] HR 0 16:44:25.680 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) [15055.8,16894,14349.84,16725,47.85025875164135,49.77386885151457,48.16372967916465] EM 0 16:44:25.680 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) [15699.53,16474,15672.99,16186,47.85025875164135,49.77386885151457,50.45172139701621] RK 0 16:44:25.680 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) [16187,16258,13639.83,14900,52.28360492434251,50.66453470243147,50.45172139701621] LG 0 16:44:25.680 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) [14884,15334,13777.33,14405,52.28360492434251,50.66453470243147,50.45172139701621] QD 0 16:44:25.680 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) [14405,14876,12969.58,14876,47.85025875164135,49.23046392011166,50.45172139701621] HP 0 16:44:25.680 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) [14876,14927,12417.22,13245,52.28360492434251,49.77386885151457,50.45172139701621] PO 0 16:44:25.680 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) [12776.79,14078.5,12355.38,13681,47.85025875164135,49.77386885151457,50.45172139701621…]]
Valores-p que avaliam a relação entre as variáveis transformadas e o alvo.
NS 0 16:44:29.287 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) Probability that predicator at 4 is associated with target 0.0005 IR 0 16:44:32.829 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) Probability that predicator at 5 is associated with target 0.7714 JS 0 16:44:36.406 TargetBasedNominalVariableConversion_demo (BTCUSD,D1) Probability that predicator at 6 is associated with target 0.749
Os resultados mostram que, entre todas as variáveis categóricas, apenas a classificação bullish/bearish apresenta uma relação significativa com o alvo. Por outro lado, o padrão de duas barras e o tamanho do corpo da vela não mostram qualquer correspondência evidente com o alvo. Isso indica que essas variáveis transformadas pouco se diferenciam de uma variável aleatória.
A última demonstração mostra como utilizar a classe para transformar um conjunto de variáveis nominais combinado com redução de dimensionalidade. Essa funcionalidade é obtida ao chamar a função fitTransform(), que recebe os mesmos parâmetros do método fit() e retorna uma matriz contendo uma única variável representativa obtida a partir das variáveis nominais transformadas. Essa operação reduz sequencialmente qualquer número de variáveis nominais em uma única variável ordinal.
//+------------------------------------------------------------------+ //| categorical conversion with dimensionality reduction | //+------------------------------------------------------------------+ matrix fitTransform(matrix &preds_in, ulong &cols[], vector &target) { //--- if(preds_in.Cols()<2) { if(!fit(preds_in,cols,target)) { Print(__FUNCTION__, " error at ", __LINE__); return matrix::Zeros(1,1); } //--- return transform(preds_in); } //--- m_dim_reduce = 1; mapped = false; //--- if(cols.Size()==0 && preds_in.Cols()) { m_pred = int(preds_in.Cols()); if(!np::arange(m_colindices,m_pred,ulong(0),ulong(1))) { Print(__FUNCTION__, " arange error "); return matrix::Zeros(1,1); } } else { m_pred = int(cols.Size()); } //--- m_rows = int(preds_in.Rows()) ; m_cols = int(preds_in.Cols()); //--- if(ArrayResize(m_mean_rankings,1)<0 || ArrayResize(m_rankings,m_rows)<0 || ArrayResize(m_indices,m_rows)<0 || ArrayResize(m_mapping,m_pred)<0 || ArrayResize(m_class_counts,1)<0 || !m_median.Resize(m_pred) || !m_class_ids.Resize(m_rows,m_pred) || !shuffle_target.Resize(m_rows,2) || (cols.Size()>0 && ArrayCopy(m_colindices,cols)<0) || !ArraySort(m_colindices)) { Print(__FUNCTION__, " Memory allocation failure ", GetLastError()); return matrix::Zeros(1,1); } //--- for(uint col = 0; col<m_colindices.Size(); col++) { vector var = preds_in.Col(m_colindices[col]); m_mapping[col] = np::unique(var); for(ulong i = 0; i<var.Size(); i++) { for(ulong j = 0; j<m_mapping[col].Size(); j++) { if(MathAbs(var[i]-m_mapping[col][j])<=1.e-15) { m_class_ids[i][col]=double(j); break; } } } } m_class_counts[0] = vector::Zeros(ulong(m_colindices.Size())); if(!m_class_ids.Col(m_class_ids.ArgMax(1),0)) { Print(__FUNCTION__, " failed to insert new class id values ", GetLastError()); return matrix::Zeros(1,1); } for(ulong i = 0; i<m_class_ids.Rows(); i++) ++m_class_counts[0][ulong(m_class_ids[i][0])]; m_target = target; vector cid = m_class_ids.Col(0); m_mean_rankings[0] = train(cid,m_class_counts[0],m_target,m_median[0]); mapped = true; ulong unchanged_feature_cols[]; for(ulong i = 0; i<preds_in.Cols(); i++) { int found = ArrayBsearch(m_colindices,i); if(m_colindices[found]!=i) { if(!unchanged_feature_cols.Push(i)) { Print(__FUNCTION__, " Failed array insertion ", GetLastError()); return matrix::Zeros(1,1); } } } matrix out(preds_in.Rows(),unchanged_feature_cols.Size()+1); ulong nfeatureIndex = unchanged_feature_cols.Size(); if(nfeatureIndex) { matrix input_copy = np::selectMatrixCols(preds_in,unchanged_feature_cols); if(!np::matrixCopyCols(out,input_copy,0,nfeatureIndex)) { Print(__FUNCTION__, " failed to copy matrix columns "); return matrix::Zeros(1,1); } } for(ulong i = 0; i<out.Rows(); i++) { ulong r = ulong(m_class_ids[i][0]); if(r>=m_mean_rankings[0].Size()) { Print(__FUNCTION__, " critical error , index out of bounds "); return matrix::Zeros(1,1); } out[i][nfeatureIndex] = m_mean_rankings[0][r]; } return out; }
O script TargetBasedNominalVariableConversionWithDimReduc_demo ilustra como esse processo é implementado.
CNomOrd enc; ulong selectedcols[] = {4,5,6}; matrix transformed = enc.fitTransform(fullFeatureMatrix,selectedcols,targets); Print(" Original predictors \n", fullFeatureMatrix); Print(" transformed predictors \n", transformed); Print(" Probability that predicator is associated with target ", enc.score(10000,targets));
Variável transformada.
JR 0 16:51:06.137 TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1) transformed predictors JO 0 16:51:06.137 TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1) [[13743,13855,12362.69,13347,49.36939702213909] NS 0 16:51:06.137 TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1) [13348,15381,12535.67,14689,49.36939702213909] OP 0 16:51:06.137 TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1) [14232.48,15408,14110.57,15130,49.36939702213909] RM 0 16:51:06.137 TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1) [15114,15370,13786.18,15139,50.64271980734179] RL 0 16:51:06.137 TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1) [15055.8,16894,14349.84,16725,49.36939702213909] ON 0 16:51:06.137 TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1) [15699.53,16474,15672.99,16186,49.36939702213909] DO 0 16:51:06.137 TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1) [16187,16258,13639.83,14900,49.36939702213909] JN 0 16:51:06.137 TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1) [14884,15334,13777.33,14405,49.36939702213909] EN 0 16:51:06.137 TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1) [14405,14876,12969.58,14876,50.64271980734179] PI 0 16:51:06.137 TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1) [14876,14927,12417.22,13245,50.64271980734179] FK 0 16:51:06.137 TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1) [12776.79,14078.5,12355.38,13681,49.36939702213909…]] NQ 0 16:51:09.741 TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1) Probability that predicator is associated with target 0.4981
A avaliação da relação entre a nova variável e a variável alvo resulta em um valor-p relativamente alto, indicando uma das limitações da redução de dimensionalidade: a perda de informação. Portanto, esse método deve ser utilizado com cautela.
Considerações finais
Transformar variáveis nominais em ordinais para aprendizado de máquina é um processo poderoso, mas complexo. Ele permite que os modelos lidem significativamente com dados categóricos, mas exige cuidado para evitar desvios ou introduzir distorções. Ao utilizar métodos de transformação apropriados — seja ordenação manual, codificação baseada em frequência, clusterização ou codificação alvo — você pode garantir que seus modelos de aprendizado de máquina processem variáveis nominais de forma eficaz, mantendo a integridade dos dados. Este artigo apresentou uma visão geral de alguns métodos comuns para transformar variáveis nominais. Vale ressaltar que existem métodos mais avançados disponíveis. O objetivo principal deste texto é familiarizar os profissionais com o conceito de codificação ordinal e com os fatores que devem ser considerados ao selecionar o método mais apropriado para cada caso específico. Ao compreender o significado dos diferentes métodos de codificação, os profissionais podem tomar decisões mais fundamentadas, aumentando a eficiência e a interpretabilidade de seus modelos de aprendizado de máquina. Todos os arquivos de código mencionados no artigo estão anexados abaixo.
Arquivo | Descrição |
---|---|
MQL5/scripts/CategoricalVariableConversion.py | Script em Python com exemplos de transformação categórica |
MQL5/scripts/CategoricalVariableConversion.ipynb | Notebook Jupyter do script Python mencionado acima |
MQL5/scripts/OneHotEncoding_demo.mq5 | Script de demonstração para transformação de variáveis nominais usando codificação one-hot em MQL5 |
MQL5/scripts/TargetBasedNominalVariableConversion_demo.mq5 | Script de demonstração para transformação de variáveis nominais usando método personalizado de codificação alvo |
MQL5/scripts/TargetBasedNominalVariableConversionWithDimReduc_demo.mq5 | Script de demonstração para transformação de variáveis nominais usando método personalizado de codificação alvo com redução de dimensionalidade |
MQL5/include/nom2ord.mqh | Arquivo de cabeçalho contendo a definição das classes CNomOrd e COneHotEncoder |
MQL5/include/np.mqh | Arquivo de cabeçalho com funções utilitárias para vetores e matrizes |
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/16056





- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso