
Métodos de ensamble para mejorar predicciones numéricas en MQL5
Introducción
El aprendizaje automático a menudo produce múltiples modelos predictivos de calidad variable. Los profesionales normalmente evalúan estos modelos y seleccionan el de mejor rendimiento para aplicaciones del mundo real. Sin embargo, este artículo explora un enfoque alternativo: reutilizar modelos aparentemente inferiores combinando sus resultados para mejorar potencialmente el desempeño predictivo general. Examinaremos varias técnicas para combinar predicciones y demostraremos su implementación en MQL5 puro. Finalmente, compararemos estos métodos y discutiremos su idoneidad para diferentes escenarios.
Para formalizar el concepto de combinar predicciones de modelos, introduzcamos algunas notaciones clave. Considere un conjunto de entrenamiento que consta de K puntos de datos, cada uno representado como un par (xi,yi), donde xi es un vector predictor y yi es la variable de respuesta escalar correspondiente que pretendemos predecir. Supongamos que tenemos N modelos entrenados, cada uno capaz de hacer predicciones. Cuando se le presenta un predictor x, el modelo n genera una predicción denotada como f_n(x). Nuestro objetivo es construir una función de consenso f(x) que combine eficazmente estas N predicciones individuales, produciendo una predicción general más precisa que cualquier modelo individual.
Esta función de consenso, a menudo denominada conjunto o metamodelo, tiene el potencial de superar a sus modelos constituyentes. En esta exploración, profundizaremos en diversas técnicas para construir modelos de conjunto efectivos y evaluaremos su implementación práctica y desempeño en MQL5.
Conjuntos basados en predicciones promediadas
Una de las técnicas más simples para combinar predicciones numéricas es el promedio simple. Al calcular la media de múltiples predicciones, a menudo podemos lograr una estimación más precisa y sólida que confiando en un solo modelo. Este enfoque es computacionalmente eficiente y fácil de implementar, lo que lo convierte en una opción práctica para una amplia gama de aplicaciones. La simplicidad de la media aritmética es su mayor fortaleza. A diferencia de los métodos de conjunto más complejos que requieren la estimación de múltiples parámetros, el promedio es inherentemente resistente al sobreajuste. El sobreajuste se produce cuando un modelo se vincula demasiado a las características específicas de los datos de entrenamiento, lo que compromete su capacidad de generalizarse a datos no vistos.
Al evitar por completo la estimación de parámetros, el promedio simple evita este problema y garantiza un rendimiento consistente incluso en conjuntos de datos pequeños o ruidosos. Por el contrario, otras técnicas de conjunto, como exploraremos más adelante, a menudo implican ajuste y optimización de parámetros, lo que puede introducir un grado de susceptibilidad al sobreajuste. Por lo tanto, si bien el promedio puede carecer de la sofisticación de los métodos de conjunto avanzados, su confiabilidad y facilidad de uso lo convierten en una herramienta esencial en el conjunto de herramientas de aprendizaje de conjunto.
Un principio matemático fundamental, basado en la desigualdad de Cauchy-Schwarz, proporciona la base teórica para la función que define un conjunto de predicciones promediadas. Esta desigualdad establece que el cuadrado de la suma de N números siempre es menor o igual a N veces la suma de sus cuadrados.
Ahora, considere un vector de predictores x utilizado para predecir una variable dependiente y. Sustituyendo a en la desigualdad con los errores cometidos por un modelo que predice y a partir de x, entonces a_n = f_n(x) - y. Si los sumandos del lado izquierdo de esta ecuación se dividen asumiendo que f(x) es el promedio de las predicciones. Factorizando N y reemplazando el término más a la derecha de la ecuación en el lado izquierdo de la desigualdad derivada de Cauchy y posteriormente dividiendo ambos lados por N^2, llegamos a la ecuación fundamental que sustenta el promedio como método de conjunto:
Las sumas en el lado derecho de la ecuación anterior representan los errores al cuadrado de los modelos individuales. Sumando estos errores al cuadrado y dividiéndolos por el número de modelos componentes se obtiene el error cuadrático medio (Mean Squared Error, MSE) de los modelos individuales. Mientras tanto, el lado izquierdo de la ecuación representa el error al cuadrado del modelo de consenso, que se deriva de la media de las predicciones individuales.
Matemáticamente, esta desigualdad postula que, para cualquier conjunto de predictores y objetivos, el error al cuadrado de la predicción media nunca superará el error al cuadrado medio de las predicciones individuales. La igualdad sólo se logra cuando los errores de predicción de todos los modelos individuales son idénticos.
Por supuesto, este beneficio no está exento de limitaciones. La efectividad del promedio depende significativamente de la naturaleza de los modelos componentes. Si todos los modelos tienen un poder predictivo similar, promediar sus predicciones suele ser un enfoque razonable y efectivo. Sin embargo, pueden surgir problemas cuando el poder predictivo de los modelos de componentes varía ampliamente. En tales casos, el promedio puede diluir las contribuciones de los modelos más fuertes y sobreenfatizar los más débiles, lo que potencialmente reduce el desempeño predictivo general del conjunto.
El código que implementa el promedio de conjuntos está encapsulado en la clase CAvg, definida en ensemble.mqh. Esta clase, junto con todas las demás clases que implementan métodos de conjunto, depende de que el usuario proporcione una colección de modelos previamente entrenados. Estos modelos deben adherirse a la interfaz IModel, que se define de la siguiente manera:
//+------------------------------------------------------------------+ //| IModel interface defining methods for manipulation of learning | //| algorithms | //+------------------------------------------------------------------+ interface IModel { //train a model bool train(matrix &predictors,matrix&targets); //make a prediction with a trained model double forecast(vector &predictors); };
La interfaz IModel especifica dos métodos:
- train(): Este método contiene la lógica para entrenar un modelo.
- forecast(): Este método define las operaciones para realizar predicciones basadas en nuevos datos de entrada.
//+------------------------------------------------------------------+ //| Compute the simple average of the predictions | //+------------------------------------------------------------------+ class CAvg { public: CAvg(void) ; ~CAvg(void) ; double predict(vector &inputs, IModel* &models[]) ; } ; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CAvg::CAvg(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CAvg::~CAvg(void) { } //+------------------------------------------------------------------+ //| Make a prediction by using consensus from multiple models | //+------------------------------------------------------------------+ double CAvg::predict(vector &inputs, IModel* &models[]) { double output = 0.0 ; for(uint imodel=0 ; imodel<models.Size() ; imodel++) { output +=models[imodel].forecast(inputs) ; } output /= double(models.Size()) ; return output; }
La clase CAvg incluye un método predict(), que se invoca con un vector de datos de entrada y una matriz de modelos de componentes preentrenados. Este método devuelve un valor escalar que representa la predicción de consenso. En el caso de la clase CAvg, la predicción de consenso se calcula como la predicción media derivada de la matriz de modelos proporcionada. Al adherirse a este diseño, la clase CAvg garantiza flexibilidad y modularidad, permitiendo a los usuarios integrar sin problemas varios tipos de modelos en sus métodos de conjunto.
Combinaciones lineales sin restricciones de modelos predictivos
Cuando nos enfrentamos a un conjunto de modelos con cualidades predictivas muy variables, un método de conjunto que se puede adoptar es la regresión lineal simple. La idea es calcular la predicción de consenso como una suma ponderada de las predicciones del modelo de componentes, incluido un término constante para tener en cuenta cualquier sesgo.
Este método de conjunto se implementa en la clase CLinReg. Los métodos constructor, destructor y predict() comparten las mismas firmas que las de la clase CAvg descrita anteriormente.
//+------------------------------------------------------------------+ //| Compute the linear regression of the predictions | //+------------------------------------------------------------------+ class CLinReg { public: CLinReg(void) ; ~CLinReg() ; bool fit(matrix & train_vars, vector &train_targets,IModel* &models[]); double predict(vector &inputs, IModel* &models[]) ; private: OLS *m_linreg ; // The linear regression object } ; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLinReg::CLinReg(void) { m_linreg = new OLS(); } //+------------------------------------------------------------------+ //| Fit the consensus model from saved models | //+------------------------------------------------------------------+ bool CLinReg::fit(matrix &train_vars,vector &train_targets,IModel* &models[]) { matrix independent(train_vars.Rows(),models.Size()+1); for(ulong i=0 ; i<independent.Rows() ; i++) // Build the design matrix { independent[i][models.Size()] = 1.0; vector ins = train_vars.Row(i); for(uint imodel=0 ; imodel<models.Size() ; imodel++) independent[i][imodel] = models[imodel].forecast(ins) ; } return m_linreg.Fit(train_targets,independent); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLinReg::~CLinReg(void) { if(CheckPointer(m_linreg)==POINTER_DYNAMIC) delete m_linreg ; } //+------------------------------------------------------------------+ //| Predict | //+------------------------------------------------------------------+ double CLinReg::predict(vector &inputs, IModel* &models[]) { vector args = vector::Zeros(models.Size()); for(uint i = 0; i<models.Size(); i++) args[i] = models[i].forecast(inputs); return m_linreg.Predict(args); }
Sin embargo, la clase CLinReg introduce un método fit(), que especifica las operaciones para entrenar el modelo de consenso.
El método fit() toma como entrada:
- Matriz de predictores.
- Objetivos vectoriales.
- Una variedad de modelos de componentes.
Dentro de fit(), se utiliza una instancia de la clase OLS para representar el modelo de regresión de consenso. La variable matricial independiente sirve como matriz de diseño, construida a partir de los errores al cuadrado de las predicciones realizadas por los modelos de componentes individuales, aumentados por un término constante (una columna de unos). Cuando se invoca el método predict() de CLinReg, devuelve el resultado de utilizar los errores de predicción de los modelos de componentes como entradas para el modelo de regresión de consenso.
La combinación de modelos como una suma ponderada de predicciones de componentes funciona bien en escenarios específicos y poco frecuentes. Sin embargo, este enfoque a menudo se pasa por alto en aplicaciones del mundo real por dos razones principales:
- Riesgo de sobreajuste: Los pesos en el modelo de consenso son parámetros que deben optimizarse. Si el conjunto incluye muchos modelos de componentes, el proceso de optimización puede generar un sobreajuste significativo, reduciendo la capacidad del modelo para generalizarse a datos no vistos.
- Colinealidad: Si dos o más modelos producen predicciones similares, la colinealidad puede causar inestabilidad en las estimaciones de peso. Este problema surge porque los pesos de los modelos con un rendimiento similar pueden sumar una constante y esos modelos se comportan de manera similar solo para los casos encontrados durante el entrenamiento.
Sin embargo, esta suposición a menudo no se cumple en situaciones del mundo real. Cuando ocurre un caso fuera de la muestra, los modelos que previamente generaron predicciones similares pueden reaccionar de manera diferente, lo que potencialmente puede llevar al modelo de consenso a producir resultados extremos y poco confiables.
Combinaciones lineales restringidas de modelos sesgados
El uso de una regresión simple como base para combinar múltiples modelos predictivos a veces puede conducir a un modelo inestable con pesos extremos. Este problema suele surgir cuando los coeficientes de regresión tienen signos opuestos, lo cual es necesario para equilibrar los valores para que los datos se ajusten bien. Por ejemplo, un coeficiente de un par de modelos correlacionados solo puede llevarse a un valor positivo grande si su contraparte se lleva a un valor negativo pequeño. Para evitar estos valores extremos, podemos restringir los coeficientes de regresión para evitar valores negativos extremos. Este enfoque también reduce el número de grados de libertad en el proceso de optimización, lo que hace que el modelo sea más estable y menos propenso al sobreajuste.
Este método de conjunto se implementa en la clase Cbiased. Incluye los ahora familiares métodos fit() y predict() que se encuentran en otras implementaciones de conjuntos.
//+------------------------------------------------------------------+ //|Compute the optimal linear combination of the predictions | //|subject to the constraints that the weights are all nonnegative. | //|A constant term is also included. | //|This is appropriate for biased predictors | //+------------------------------------------------------------------+ class Cbiased:public PowellsMethod { public: Cbiased(void) ; ~Cbiased() ; bool fit(matrix & train_vars, vector &train_targets,IModel* &models[]); double predict(vector &inputs,IModel* &models[]) ; private: vector m_coefs ; // Computed coefficients here int biased_ncases ; int biased_nvars ; matrix biased_x ; vector biased_y ; virtual double func(vector &p,int n=0); } ; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ Cbiased::Cbiased(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ Cbiased::~Cbiased(void) { } //+------------------------------------------------------------------+ //| Function to be optimized | //+------------------------------------------------------------------+ double Cbiased::func(vector &p,int n = 0) { double err, pred,diff, penalty ; // Compute criterion err = 0.0 ; for(int i=0 ; i<biased_ncases ; i++) { pred = p[p.Size()-1] ; // Will cumulate prediction for(int j=0 ; j<biased_nvars ; j++) // For all model outputs pred += biased_x[i][j] * p[j] ; // Weight them per call diff = pred - biased_y[i] ; // Predicted minus true err += diff * diff ; // Cumulate squared error } penalty = 0.0 ; for(int j=0 ; j<biased_nvars ; j++) { if(p[j] < 0.0) penalty -= 1.e30 * p[j] ; } return err + penalty ; } //+------------------------------------------------------------------+ //| Fit the consensus model | //+------------------------------------------------------------------+ bool Cbiased::fit(matrix & train_vars, vector &train_targets,IModel* &models[]) { biased_ncases = int(train_vars.Rows()); biased_nvars = int(models.Size()); biased_x = matrix::Zeros(biased_ncases,biased_nvars); biased_y = train_targets; m_coefs = vector::Zeros(biased_nvars+1); for(int i = 0; i<biased_ncases; i++) { vector ins = train_vars.Row(i); for(int j = 0; j<biased_nvars; j++) biased_x[i][j] = models[j].forecast(ins); } m_coefs.Fill(1.0/double(biased_nvars)); m_coefs[m_coefs.Size()-1] = 0.0; int iters = Optimize(m_coefs,int(m_coefs.Size())); double sum = m_coefs.Sum(); m_coefs/=sum; return true; } //+------------------------------------------------------------------+ //| Make prediction with consensus model | //+------------------------------------------------------------------+ double Cbiased::predict(vector &inputs,IModel* &models[]) { double output=0.0; for(uint imodel=0 ; imodel<models.Size() ; imodel++) { output += m_coefs[imodel] * models[imodel].forecast(inputs); } return output; }
Sin embargo, la diferencia clave en Cbiased radica en cómo se optimizan los pesos.
La optimización de pesos se realiza utilizando el método de Powell para la minimización de funciones. Es por esto que la clase Cbiased es un descendiente de la clase PowellsMethod. La función de criterio se implementa en el método func(), que itera a través de los datos de entrenamiento, acumulando errores al cuadrado utilizando los pesos proporcionados. Para cada muestra del conjunto de datos:
- Las predicciones de los modelos de componentes se ponderan de acuerdo con los pesos actuales y un término constante.
- Se suman las diferencias al cuadrado entre las predicciones y los valores objetivo.
La función de criterio finaliza verificando si alguno de los pesos de prueba es negativo. Si algún peso es negativo se aplica una penalización. La función devuelve el error total más cualquier penalización de los pesos negativos. Este tipo de método de conjunto es más apropiado cuando se sabe que algunos modelos de componentes están sesgados. En este contexto, el sesgo se refiere a modelos que producen consistentemente predicciones que son demasiado altas o demasiado bajas en comparación con sus valores objetivo correspondientes y que a menudo muestran una tendencia notable. Al restringir los pesos, Cbiased reduce eficazmente la influencia de los modelos sesgados, lo que conduce a una predicción de conjunto más equilibrada y precisa. En la siguiente sección, presentaremos un método adecuado para conjuntos de modelos que presentan poco o ningún sesgo, donde el enfoque está en agregar predicciones de modelos con un rendimiento comparable.
Combinaciones restringidas de modelos imparciales
Cuando se sabe que un conjunto de modelos de componentes no tiene sesgo significativo en sus predicciones, no es necesario incluir un término constante en el modelo de consenso. Eliminar el término constante ayuda a reducir la propensión del modelo a sobreajustar los datos. Además, este enfoque garantiza que los pesos de los modelos nunca sean negativos, como se discutió en métodos anteriores. Además, se impone una restricción adicional a los pesos: deben sumar uno. Esta restricción proporciona dos beneficios clave:
- Garantizar un modelo de consenso imparcial: siempre que los modelos de componentes sean razonablemente imparciales, exigir que los pesos sumen uno garantiza que el modelo de consenso también siga siendo imparcial.
- Interpolación entre predicciones: La restricción de suma a uno garantiza que la predicción de consenso sea una interpolación entre las predicciones de los modelos componentes. Esto garantiza que la predicción final no se desvíe drásticamente de las predicciones individuales, evitando resultados extremos que podrían surgir de pesos extremos.
Esto se ilustra con la siguiente ecuación:
El código que implementa este método de conjunto es en gran medida idéntico a las implementaciones anteriores. La principal diferencia con la clase CUnbiased radica en que la función se minimiza.
//+------------------------------------------------------------------+ //|Compute the optimal linear combination of the predictions | //|subject to the constraints that the weights are all nonnegative | //|and they sum to one. This is appropriate for unbiased predictors.| //+------------------------------------------------------------------+ class CUnbiased:public PowellsMethod { public: CUnbiased(void) ; ~CUnbiased() ; bool fit(matrix & train_vars, vector &train_targets,IModel* &models[]); double predict(vector &inputs,IModel* &models[]) ; private: vector m_coefs ; // Computed coefficients here int unbiased_ncases ; int unbiased_nvars ; matrix unbiased_x ; vector unbiased_y ; virtual double func(vector &p,int n=0); } ; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CUnbiased::CUnbiased(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CUnbiased::~CUnbiased(void) { } //+------------------------------------------------------------------+ //| Function to be optimized | //+------------------------------------------------------------------+ double CUnbiased::func(vector &p,int n = 0) { double sum, err, pred,diff, penalty ; // Normalize weights to sum to one sum = p.Sum() ; if(sum < 1.e-60) // Should almost never happen sum = 1.e-60 ; // But be prepared to avoid division by zero vector unbiased_work = p / sum ; // Compute criterion err = 0.0 ; for(int i=0 ; i<unbiased_ncases ; i++) { pred = 0.0 ; // Will cumulate prediction for(int j=0 ; j<unbiased_nvars ; j++) // For all model outputs pred += unbiased_x[i][j] * unbiased_work[j] ; // Weight them per call diff = pred - unbiased_y[i] ; // Predicted minus true err += diff * diff ; // Cumulate squared error } penalty = 0.0 ; for(int j=0 ; j<unbiased_nvars ; j++) { if(p[j] < 0.0) penalty -= 1.e30 * p[j] ; } return err + penalty ; } //+------------------------------------------------------------------+ //| Fit the consensus model | //+------------------------------------------------------------------+ bool CUnbiased::fit(matrix & train_vars, vector &train_targets,IModel* &models[]) { unbiased_ncases = int(train_vars.Rows()); unbiased_nvars = int(models.Size()); unbiased_x = matrix::Zeros(unbiased_ncases,unbiased_nvars); unbiased_y = train_targets; m_coefs = vector::Zeros(unbiased_nvars); for(int i = 0; i<unbiased_ncases; i++) { vector ins = train_vars.Row(i); for(int j = 0; j<unbiased_nvars; j++) unbiased_x[i][j] = models[j].forecast(ins); } m_coefs.Fill(1.0/double(unbiased_nvars)); int iters = Optimize(m_coefs); double sum = m_coefs.Sum(); m_coefs/=sum; return true; } //+------------------------------------------------------------------+ //| Make prediction with consensus model | //+------------------------------------------------------------------+ double CUnbiased::predict(vector &inputs,IModel* &models[]) { double output=0.0; for(uint imodel=0 ; imodel<models.Size() ; imodel++) { output += m_coefs[imodel] * models[imodel].forecast(inputs); } return output; }
Esta función incorpora las restricciones adicionales discutidas anteriormente, específicamente la no negatividad de los pesos y el requisito de que sumen uno.
Combinaciones de modelos predictivos ponderadas por la varianza
Otro método para combinar predicciones de modelos de componentes se basa en pesos óptimos que están determinados por la precisión de predicción de cada modelo. Esta técnica implica asignar pesos más pequeños a los modelos con errores de predicción mayores y pesos más grandes a los modelos con errores de predicción menores. Este método es particularmente efectivo cuando hay una variabilidad significativa en la calidad de los modelos de componentes. Sin embargo, si los modelos están altamente correlacionados, esta técnica puede no ser ideal y debería considerarse otro método de conjunto.
La idea detrás de la ponderación según la calidad del modelo se basa en la teoría de que si los modelos son imparciales y no están correlacionados, asignar pesos inversamente proporcionales a los errores de los modelos minimizará el error cuadrático esperado.
Para lograr esto, se calcula el peso relativo de cada modelo en función del inverso de su error y luego se escalan los pesos para garantizar que sumen uno.
El conjunto de modelos ponderados por varianza se implementa en la clase CWeighted. En el método fit(), para cada muestra de entrenamiento:
- Se calcula la predicción de cada modelo de componente.
- Y el error al cuadrado de cada predicción se acumula.
//+------------------------------------------------------------------+ //| Compute the variance-weighted average of the predictions | //+------------------------------------------------------------------+ class CWeighted { public: CWeighted(void) ; ~CWeighted() ; bool fit(matrix & train_vars, vector &train_targets,IModel* &models[]); double predict(vector &inputs,IModel* &models[]) ; private: vector m_coefs ; // Computed coefficients here }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CWeighted::CWeighted(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CWeighted::~CWeighted(void) { } //+------------------------------------------------------------------+ //| Fit a consensus model | //+------------------------------------------------------------------+ bool CWeighted::fit(matrix &train_vars,vector &train_targets,IModel* &models[]) { m_coefs = vector::Zeros(models.Size()); m_coefs.Fill(1.e-60); double diff = 0.0; for(ulong i = 0; i<train_vars.Rows(); i++) { vector ins = train_vars.Row(i); for(ulong j = 0; j<m_coefs.Size(); j++) { diff = models[j].forecast(ins) - train_targets[i]; m_coefs[j] += (diff*diff); } } m_coefs=1.0/m_coefs; m_coefs/=m_coefs.Sum(); return true; } //+------------------------------------------------------------------+ //| Make a prediction with the consensus model | //+------------------------------------------------------------------+ double CWeighted::predict(vector &inputs,IModel* &models[]) { double output = 0.0; for(uint i = 0; i<models.Size(); i++) output+=m_coefs[i]*models[i].forecast(inputs); return output; }
Una vez hecho esto para todas las muestras de entrenamiento, se utiliza el error total de cada modelo para calcular su peso. Luego, estos pesos se suman y se escalan para garantizar que el peso total sume uno. Este enfoque garantiza que los modelos con menores errores tengan más influencia en la predicción final del conjunto, lo que potencialmente puede mejorar la predicción, especialmente en escenarios donde los modelos exhiben distintos grados de precisión predictiva.
Combinaciones interpoladas basadas en redes neuronales de regresión general
Los métodos de conjunto que hemos analizado hasta ahora funcionan bien cuando el modelo de consenso se entrena con datos limpios. Sin embargo, cuando los datos de entrenamiento son ruidosos, el modelo puede sufrir una generalización deficiente. Para abordar esta cuestión, un método de regresión eficaz es la red neuronal de regresión general (General Regression Neural Network, GRNN). La ventaja destacada de GRNN sobre la regresión tradicional es su menor susceptibilidad al sobreajuste. Esto se debe a que los parámetros de GRNN tienen un impacto relativamente menor en el modelo en comparación con las técnicas de regresión tradicionales. Si bien esta ganancia en generalización se produce a costa de cierta precisión, las GRNN pueden modelar relaciones complejas y no lineales, lo que proporciona una herramienta útil cuando los datos exhiben tales características.
Las GRNN producen predicciones que son interpolaciones entre los valores objetivo en los datos de entrenamiento. La interpolación está determinada por un peso que define cómo un caso fuera de la muestra difiere de los casos conocidos dentro de la muestra. Cuanto más similares sean las muestras, mayor será el peso relativo asignado. Aunque una GRNN puede describirse como una operación de suavizado debido a su interpolación de muestras desconocidas en el espacio muestral conocido, sus fundamentos teóricos se basan en la estadística.
Cuando se presenta un conjunto de datos (que incluye predicciones de modelos de componentes y sus objetivos correspondientes), la predicción de consenso de una GRNN es el error cuadrático mínimo esperado, que se da por la expectativa condicional.
Dado que la densidad conjunta de los datos de entrenamiento normalmente es desconocida, no podemos utilizar directamente la fórmula para la expectativa condicional. En lugar de ello, nos basamos en estimaciones de las densidades conjuntas derivadas de los datos de entrenamiento, lo que conduce a la forma de GRNN que se muestra a continuación.
Antes de presentar el código para el método de conjunto basado en GRNN, primero debemos analizar la implementación de GRNN. El código para GRNN se define en grnn.mqh, que contiene la definición de la clase CGrnn.
//+------------------------------------------------------------------+ //| General regression neural network | //+------------------------------------------------------------------+ class CGrnn { public: CGrnn(void); CGrnn(int num_outer, int num_inner, double start_std); ~CGrnn(void); bool fit(matrix &predictors,matrix &targets); vector predict(vector &predictors); //double get_mse(void); private: bool train(void); double execute(void); ulong m_inputs,m_outputs; int m_inner,m_outer; double m_start_std; ulong m_rows,m_cols; bool m_trained; vector m_sigma; matrix m_targets,m_preds; };
La implementación de GRNN para tareas de regresión involucra varios componentes clave. El constructor inicializa parámetros como el número de iteraciones internas y externas y la desviación estándar inicial para los pesos sigma.
//+------------------------------------------------------------------+ //| Default constructor | //+------------------------------------------------------------------+ CGrnn::CGrnn(void) { m_inner = 100; m_outer = 10; m_start_std = 3.0; } //+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGrnn::CGrnn(int num_outer,int num_inner,double start_std) { m_inner = num_inner; m_outer = num_outer; m_start_std = start_std; }
El método de ajuste almacena los datos de entrenamiento, incluidos los predictores de entrada y los valores objetivo, e inicializa los pesos sigma. Luego entrena el modelo GRNN optimizando iterativamente los pesos sigma utilizando un enfoque de recocido simulado. Durante el entrenamiento, se perturban los pesos sigma y se calcula el error de validación cruzada para los pesos perturbados. La perturbación se acepta o rechaza en función del error y de un parámetro de temperatura, y la temperatura se reduce gradualmente para centrar la búsqueda.
//+------------------------------------------------------------------+ //| Fit data to a model | //+------------------------------------------------------------------+ bool CGrnn::fit(matrix &predictors,matrix &targets) { m_targets = targets; m_preds = predictors; m_trained = false; m_rows = m_preds.Rows(); m_cols = m_preds.Cols(); m_sigma = vector::Zeros(m_preds.Cols()); if(m_targets.Rows() != m_preds.Rows()) { Print(__FUNCTION__, " invalid inputs "); return false; } m_trained = train(); return m_trained; }
El método de predicción calcula la distancia entre el vector de entrada y cada punto de datos de entrenamiento, pondera los puntos de datos de entrenamiento en función de su distancia desde la entrada y calcula la salida prevista como un promedio ponderado de los valores objetivo de los puntos de datos de entrenamiento. Los pesos sigma determinan la influencia de cada punto de datos de entrenamiento en la predicción.
//+------------------------------------------------------------------+ //| Make a prediction with a trained model | //+------------------------------------------------------------------+ vector CGrnn::predict(vector &predictors) { if(!m_trained) { Print(__FUNCTION__, " no trained model available for predictions "); return vector::Zeros(1); } if(predictors.Size() != m_cols) { Print(__FUNCTION__, " invalid inputs "); return vector::Zeros(1); } vector output = vector::Zeros(m_targets.Cols()); double diff,dist,psum=0.0; for(ulong i = 0; i<m_rows; i++) { dist = 0.0; for(ulong j = 0; j<m_cols; j++) { diff = predictors[j] - m_preds[i][j]; diff/= m_sigma[j]; dist += (diff*diff); } dist = exp(-dist); if(dist< EPS1) dist = EPS1; for(ulong k = 0; k<m_targets.Cols(); k++) output[k] += dist * m_targets[i][k]; psum += dist; } output/=psum; return output; }
La validación cruzada se utiliza para evaluar el rendimiento del modelo y optimizar los pesos sigma, mientras que el recocido simulado sirve como un algoritmo de optimización metaheurística para encontrar los pesos sigma óptimos. En última instancia, GRNN realiza una interpolación basada en kernel, donde la predicción es una interpolación ponderada entre los puntos de datos de entrenamiento.
El conjunto basado en GRNN se implementa como la clase CGenReg.
//+------------------------------------------------------------------+ //| Compute the General Regression of the predictions | //+------------------------------------------------------------------+ class CGenReg { public: CGenReg(void) ; ~CGenReg(void) ; bool fit(matrix & train_vars, vector &train_targets,IModel* &models[]); double predict(vector &inputs,IModel* &models[]) ; private: CGrnn *grnn ; // The GRNN object vector m_work ; // Work vector nmodels long vector m_targs; matrix m_vars; } ;
La clase CGenReg utiliza un objeto CGrnn para modelar relaciones complejas entre las predicciones de modelos individuales y los valores objetivo reales. En el método de ajuste, primero se almacenan los datos de entrenamiento, incluidos los valores objetivo (train_targets) y las variables de entrada (train_vars). Luego, reúne las predicciones individuales de cada modelo y crea una matriz (preds) donde cada fila representa una muestra de entrenamiento y cada columna contiene la predicción del modelo correspondiente en el conjunto. El objeto CGrnn se entrena utilizando la matriz de predicciones individuales (preds) como entrada y los valores objetivo reales (targ) como salida.
//+------------------------------------------------------------------+ //| Fit consensus model | //+------------------------------------------------------------------+ bool CGenReg::fit(matrix & train_vars, vector &train_targets,IModel* &models[]) { m_targs = train_targets; m_vars = train_vars; m_work = vector::Zeros(models.Size()); matrix targ = matrix::Zeros(train_targets.Size(),1); if(!targ.Col(train_targets,0)) { Print(__FUNCSIG__, " error adding column ", GetLastError()); return false; } matrix preds(m_vars.Rows(),models.Size()); for(ulong i = 0; i<m_vars.Rows(); i++) { vector ins = m_vars.Row(i); for(uint j = 0; j< models.Size(); j++) { preds[i][j] = models[j].forecast(ins); } } return grnn.fit(preds,targ); }
En el método de predicción, la clase recopila predicciones de cada modelo para una nueva entrada (inputs) y las almacena en un vector de trabajo (m_work). Luego, el CGrnn entrenado se utiliza para predecir el resultado final en función de estas predicciones individuales. El método devuelve el primer elemento del vector de salida previsto como predicción final.
//+------------------------------------------------------------------+ //| Make a prediction | //+------------------------------------------------------------------+ double CGenReg::predict(vector &inputs,IModel* &models[]) { vector output; for(uint i = 0; i<models.Size(); i++) m_work[i] = models[i].forecast(inputs); output = grnn.predict(m_work); return output[0]; }
Conclusión: Comparación de métodos de conjunto
Se han presentado varios métodos de conjunto y se han analizado brevemente sus fortalezas y debilidades inherentes. Para concluir este artículo, investigaremos cómo se comparan estos métodos cuando se aplican a datos reales. Esta comparación se implementa como un script de MetaTrader 5 llamado Ensemble_Demo.mq5.
El script genera varios grupos sintéticos de conjuntos de datos. El primer grupo consta de conjuntos de datos utilizados para entrenar modelos de referencia. Los modelos entrenados con dichos datos se distinguen como buenos modelos y los datos en sí se consideran limpios. Se genera un segundo grupo de conjuntos de datos para entrenar modelos malos, que se consideran inferiores a los modelos buenos entrenados con datos limpios.
El último grupo de conjuntos de datos se utiliza para entrenar modelos que se consideran sesgados. Estos modelos están sesgados en comparación con los buenos modelos mencionados anteriormente. Se combina un conjunto de datos parcial de cada grupo para simular datos ruidosos.
El script permite a los usuarios especificar cuántos modelos buenos, malos y sesgados se entrenan. El usuario también tiene control sobre la cantidad de muestras que componen los datos de entrenamiento, lo que permite evaluar cómo el tamaño de la muestra afecta el rendimiento de los métodos de conjunto. Por último, los usuarios pueden elegir entrenar un modelo de conjunto utilizando datos limpios configurando el parámetro TrainCombinedModelsOnCleanData como verdadero, o entrenarlo utilizando datos ruidosos configurandolo como falso.
Los modelos son redes neuronales de propagación hacia adelante implementadas por la clase FFNN en mlffnn.mqh.
//+------------------------------------------------------------------+ //| Class for a basic feed-forward neural network | //+------------------------------------------------------------------+ class FFNN { protected: bool m_trained; // flag noting if neural net successfully trained matrix m_weights[]; // layer weights matrix m_outputs[]; // hidden layer outputs matrix m_result; // training result uint m_epochs; // number of epochs ulong m_num_inputs; // number of input variables for nn ulong m_layers; // number of layers of neural net ulong m_hidden_layers; // number of hidden layers ulong m_hidden_layer_size[]; // node config for layers double m_learn_rate; // learning rate ENUM_ACTIVATION_FUNCTION m_act_fn; // activation function //+------------------------------------------------------------------+ //| Initialize the neural network structure | //+------------------------------------------------------------------+ virtual bool create(void) { if(m_layers - m_hidden_layers != 1) { Print(__FUNCTION__," Network structure misconfiguration "); return false; } for(ulong i = 0; i<m_layers; i++) { if(i==0) { if(!m_weights[i].Init(m_num_inputs+1,m_hidden_layer_size[i])) { Print(__FUNCTION__," ",__LINE__," ", GetLastError()); return false; } } else if(i == m_layers-1) { if(!m_weights[i].Init(m_hidden_layer_size[i-1]+1,1)) { Print(__FUNCTION__," ",__LINE__," ", GetLastError()); return false; } } else { if(!m_weights[i].Init(m_hidden_layer_size[i-1]+1,m_hidden_layer_size[i])) { Print(__FUNCTION__," ",__LINE__," ", GetLastError()); return false; } } } return true; } //+------------------------------------------------------------------+ //| Calculate output from all layers | //+------------------------------------------------------------------+ virtual matrix calculate(matrix &data) { if(data.Cols() != m_weights[0].Rows()-1) { Print(__FUNCTION__," input data not compatible with network structure "); return matrix::Zeros(0,0); } matrix temp = data; for(ulong i = 0; i<m_hidden_layers; i++) { if(!temp.Resize(temp.Rows(), m_weights[i].Rows()) || !temp.Col(vector::Ones(temp.Rows()), m_weights[i].Rows() - 1)) { Print(__FUNCTION__," ",__LINE__," ", GetLastError()); matrix::Zeros(0,0); } m_outputs[i]=temp.MatMul(m_weights[i]); if(!m_outputs[i].Activation(temp, m_act_fn)) { Print(__FUNCTION__," ",__LINE__," ", GetLastError()); return matrix::Zeros(0,0); } } if(!temp.Resize(temp.Rows(), m_weights[m_hidden_layers].Rows()) || !temp.Col(vector::Ones(temp.Rows()), m_weights[m_hidden_layers].Rows() - 1)) { Print(__FUNCTION__," ",__LINE__," ", GetLastError()); return matrix::Zeros(0,0); } return temp.MatMul(m_weights[m_hidden_layers]); } //+------------------------------------------------------------------+ //| Backpropagation method | //+------------------------------------------------------------------+ virtual bool backprop(matrix &data, matrix& targets, matrix &result) { if(targets.Rows() != result.Rows() || targets.Cols() != result.Cols()) { Print(__FUNCTION__," invalid function parameters "); return false; } matrix loss = (targets - result) * 2; matrix gradient = loss.MatMul(m_weights[m_hidden_layers].Transpose()); matrix temp; for(long i = long(m_hidden_layers-1); i>-1; i--) { if(!m_outputs[i].Activation(temp, m_act_fn)) { Print(__FUNCTION__," ",__LINE__," ", GetLastError()); return false; } if(!temp.Resize(temp.Rows(), m_weights[i+1].Rows()) || !temp.Col(vector::Ones(temp.Rows()), m_weights[i+1].Rows() - 1)) { Print(__FUNCTION__," ",__LINE__," ", GetLastError()); return false; } m_weights[i+1] = m_weights[i+1] + temp.Transpose().MatMul(loss) * m_learn_rate; if(!m_outputs[i].Derivative(temp, m_act_fn)) { Print(__FUNCTION__," ",__LINE__," ", GetLastError()); return false; } if(!gradient.Resize(gradient.Rows(), gradient.Cols() - 1)) { Print(__FUNCTION__," ",__LINE__," ", GetLastError()); return false; } loss = gradient * temp; gradient = (i>0)?loss.MatMul(m_weights[i].Transpose()):gradient; } temp = data; if(!temp.Resize(temp.Rows(), m_weights[0].Rows()) || !temp.Col(vector::Ones(temp.Rows()), m_weights[0].Rows() - 1)) { Print(__FUNCTION__," ",__LINE__," ", GetLastError()); return false; } m_weights[0] = m_weights[0] + temp.Transpose().MatMul(loss) * m_learn_rate; return true; } public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ FFNN(ulong &layersizes[], ulong num_layers = 3) { m_trained = false; m_layers = num_layers; m_hidden_layers = m_layers - 1; ArrayCopy(m_hidden_layer_size,layersizes,0,0,int(m_hidden_layers)); ArrayResize(m_weights,int(m_layers)); ArrayResize(m_outputs,int(m_hidden_layers)); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ ~FFNN(void) { } //+------------------------------------------------------------------+ //| Neural net training method | //+------------------------------------------------------------------+ bool fit(matrix &data, matrix &targets,double learning_rate, ENUM_ACTIVATION_FUNCTION act_fn, uint num_epochs) { m_learn_rate = learning_rate; m_act_fn = act_fn; m_epochs = num_epochs; m_num_inputs = data.Cols(); m_trained = false; if(!create()) return false; for(uint ep = 0; ep < m_epochs; ep++) { m_result = calculate(data); if(!backprop(data, targets,m_result)) return m_trained; } m_trained = true; return m_trained; } //+------------------------------------------------------------------+ //| Predict method | //+------------------------------------------------------------------+ matrix predict(matrix &data) { if(m_trained) return calculate(data); else return matrix::Zeros(0,0); } }; //+------------------------------------------------------------------+
La clase FFNN define un perceptrón multicapa (Multi-Layer Perceptron, MLP), un tipo de red neuronal artificial utilizada para tareas de aprendizaje supervisado. Contiene varias propiedades como:
- m_trained, un indicador booleano que indica si la red ha sido entrenada exitosamente;
- m_weights, una matriz de matrices que almacenan los pesos entre cada capa;
- m_outputs, una matriz de matrices que contienen las salidas de cada capa oculta;
- m_result, una matriz que contiene la salida final de la red después del entrenamiento;
- m_epochs, el número de épocas de entrenamiento (iteraciones);
- m_num_inputs, el número de variables de entrada para la red;
- m_layers, el número total de capas en la red, incluidas las capas de entrada y salida;
- m_hidden_layers, el número de capas ocultas en la red;
- m_hidden_layer_size, una matriz que define el número de nodos en cada capa oculta;
- m_learn_rate, la tasa de aprendizaje utilizada para las actualizaciones de peso durante el entrenamiento;
- m_act_fn, la función de activación utilizada en las capas ocultas.
La clase incluye métodos privados y públicos. Métodos privados como:
- create, que inicializa la estructura de la red asignando memoria para las matrices de peso y las salidas de la capa oculta en función de la configuración especificada;
- calculate, que propaga los datos de entrada a través de la red, aplicando pesos y funciones de activación para calcular la salida;
- backprop, que implementa el algoritmo de retropropagación, ajustando los pesos en función del error entre las salidas previstas y reales.
Los métodos públicos incluyen:
- FFNN (constructor), que inicializa la red con el número especificado de capas y tamaños de capas ocultas;
- ~FFNN (destructor), que libera los recursos asignados para la red;
- fit, que entrena la red en un conjunto de datos determinado, ajustando los pesos a través de retropropagación durante el número especificado de épocas;
- predict, que utiliza la red entrenada para generar predicciones para nuevos datos de entrada, realizando de manera efectiva una propagación hacia adelante.
En el script, la clase CMlfn implementa la interfaz IModel basada en una instancia de FFNN. A continuación se muestra una breve exposición de cómo ejecutar el script en diferentes configuraciones.
//+------------------------------------------------------------------+ //| Ensemble_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<mlffnn.mqh> #include<ensemble.mqh> #include<np.mqh> //--- input parameters input int NumGoodModels=3; input int NumBiasedModels=7; input int NumBadModels=5; input int NumSamples=20; input int NumAttempts=1; input double VarParam=3.0;//variance parameter input bool TrainCombinedModelsOnCleanData = true; //+------------------------------------------------------------------+ //| Clean up dynamic array pointers | //+------------------------------------------------------------------+ void cleanup(IModel* &array[]) { for(uint i = 0; i<array.Size(); i++) if(CheckPointer(array[i])==POINTER_DYNAMIC) delete array[i]; } //+------------------------------------------------------------------+ //| IModel implementation of Multilayered iterative algo of GMDH | //+------------------------------------------------------------------+ class CMlfn:public IModel { private: FFNN *m_mlfn; double m_learningrate; ENUM_ACTIVATION_FUNCTION m_actfun; uint m_epochs; ulong m_layer[3]; public: CMlfn(); ~CMlfn(void); void setParams(double learning_rate, ENUM_ACTIVATION_FUNCTION act_fn, uint num_epochs); bool train(matrix &predictors,matrix&targets); double forecast(vector &predictors); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CMlfn::CMlfn(void) { m_learningrate=0.01; m_actfun=AF_SOFTMAX; m_epochs= 100; m_layer[0] = 2; m_layer[1] = 2; m_layer[2] = 1; m_mlfn = new FFNN(m_layer); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CMlfn::~CMlfn(void) { if(CheckPointer(m_mlfn) == POINTER_DYNAMIC) delete m_mlfn; } //+------------------------------------------------------------------+ //| Set other hyperparameters of the model | //+------------------------------------------------------------------+ void CMlfn::setParams(double learning_rate, ENUM_ACTIVATION_FUNCTION act_fn, uint num_epochs) { m_learningrate=learning_rate; m_actfun=act_fn; m_epochs= num_epochs; } //+------------------------------------------------------------------+ //| Fit a model to the data | //+------------------------------------------------------------------+ bool CMlfn::train(matrix &predictors,matrix &targets) { return m_mlfn.fit(predictors,targets,m_learningrate,m_actfun,m_epochs); } //+------------------------------------------------------------------+ //| Make a prediction with the trained model | //+------------------------------------------------------------------+ double CMlfn::forecast(vector &predictors) { matrix preds(1,predictors.Size()); if(!preds.Row(predictors,0)) { Print(__FUNCTION__, " error inserting row ", GetLastError()); return EMPTY_VALUE; } matrix out = m_mlfn.predict(preds); return out[0][0]; } //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- if(NumSamples<1 || NumAttempts<1 || VarParam<0.0 || NumBadModels<1 || NumGoodModels<1 || NumBiasedModels<1) { Print(" Invalid User inputs "); return; } int ndone, divisor; double diff, std, temp; double computed_err_average ; double computed_err_unconstrained ; double computed_err_unbiased ; double computed_err_biased ; double computed_err_weighted ; double computed_err_bagged ; double computed_err_genreg ; CAvg average; CLinReg unconstrained; CUnbiased unbiased; Cbiased biased; CWeighted weighted; CGenReg genreg; vector computed_err_raw = vector::Zeros(NumBadModels+NumGoodModels+NumBiasedModels); std = sqrt(VarParam); divisor = 1; IModel* puremodels[]; matrix xgood[],xbad[],xbiased[],test[10]; if(ArrayResize(puremodels,NumBadModels+NumGoodModels+NumBiasedModels)<0 || ArrayResize(xgood,NumBadModels+NumGoodModels+NumBiasedModels)<0 || ArrayResize(xbad,NumBadModels+NumGoodModels+NumBiasedModels)<0 || ArrayResize(xbiased,NumBadModels+NumGoodModels+NumBiasedModels)<0) { Print(" failed puremodels array resize ", GetLastError()); return; } for(uint i = 0; i<puremodels.Size(); i++) puremodels[i] = new CMlfn(); for(uint i = 0; i<xgood.Size(); i++) xgood[i] = matrix::Zeros(NumSamples,3); for(uint i = 0; i<xbad.Size(); i++) xbad[i] = matrix::Zeros(NumSamples,3); for(uint i = 0; i<xbiased.Size(); i++) xbiased[i] = matrix::Zeros(NumSamples,3); for(uint i = 0; i<test.Size(); i++) test[i] = matrix::Zeros(NumSamples,3); computed_err_average = 0.0 ; computed_err_unconstrained = 0.0 ; computed_err_unbiased = 0.0 ; computed_err_biased = 0.0 ; computed_err_weighted = 0.0 ; computed_err_bagged = 0.0 ; computed_err_genreg = 0.0 ; vector t,v; matrix d; ndone = 1; for(uint i = 0; i<xgood.Size(); i++) { xgood[i].Random(0.0,1.0); if(!xgood[i].Col(sin(xgood[i].Col(0)) - pow(xgood[i].Col(1),2.0) + std*xgood[i].Col(2),2)) { Print(" column insertion error ", GetLastError()); cleanup(puremodels); return; } } matrix xb(xgood[0].Rows(),1); for(uint i = 0; i<xbad.Size(); i++) { xbad[i] = xgood[0]; xb.Random(0.0,1.0); if(!xbad[i].Col(xb.Col(0),2)) { Print(" column insertion error ", GetLastError()); cleanup(puremodels); return; } } for(uint i = 0; i<xbiased.Size(); i++) { xbiased[i] = xgood[0]; if(!xbiased[i].Col(xgood[0].Col(2)+1.0,2)) { Print(" column insertion error ", GetLastError()); cleanup(puremodels); return; } } for(uint i = 0; i<test.Size(); i++) { test[i].Random(0.0,1.0); if(!test[i].Col(sin(test[i].Col(0)) - pow(test[i].Col(1),2.0) + std * test[i].Col(2),2)) { Print(" column insertion error ", GetLastError()); cleanup(puremodels); return; } } for(uint imodel=0; imodel<puremodels.Size(); imodel++) { if(imodel < xgood.Size()) { t=xgood[imodel].Col(2); d=np::sliceMatrixCols(xgood[imodel],0,2); } else if(imodel >= xgood.Size() && imodel<(xgood.Size()+xbiased.Size())) { t=xbiased[imodel-xgood.Size()].Col(2); d=np::sliceMatrixCols(xbiased[imodel-xgood.Size()],0,2); } else { t=xbad[imodel - (xgood.Size()+xbiased.Size())].Col(2); d=np::sliceMatrixCols(xbad[imodel - (xgood.Size()+xbiased.Size())],0,2); } matrix tt(t.Size(),1); if(!tt.Col(t,0) || !puremodels[imodel].train(d,tt)) { Print(" failed column insertion ", GetLastError()); cleanup(puremodels); return; } temp = 0.0; for(uint i = 0; i<test.Size(); i++) { for(int j = 0; j<NumSamples; j++) { t = test[i].Row(j); v = np::sliceVector(t,0,2); diff = puremodels[imodel].forecast(v) - t[2]; temp += diff*diff; } } computed_err_raw[imodel] += temp/double(test.Size()*NumSamples); } //average matrix tdata; if(TrainCombinedModelsOnCleanData) tdata = xgood[0]; else { tdata = matrix::Zeros(NumSamples*3,3); if(!np::matrixCopyRows(tdata,xgood[0],0,NumSamples) || !np::matrixCopyRows(tdata,xbad[0],NumSamples,NumSamples*2) || !np::matrixCopyRows(tdata,xbiased[0],NumSamples*2)) { Print(" failed to create noisy dataset"); cleanup(puremodels); return; } } temp = 0.0; for(uint i = 0; i<test.Size(); i++) { for(int j = 0; j<NumSamples; j++) { t = test[i].Row(j); v = np::sliceVector(t,0,2); diff = average.predict(v,puremodels) - t[2]; temp += diff*diff; } } computed_err_average += temp/double(test.Size()*NumSamples); //unconstrained temp = 0.0; t = tdata.Col(2); d = np::sliceMatrixCols(tdata,0,2); if(!unconstrained.fit(d,t,puremodels)) { Print(" failed to fit unconstrained model "); cleanup(puremodels); } for(uint i = 0; i<test.Size(); i++) { for(int j = 0; j<NumSamples; j++) { t = test[i].Row(j); v = np::sliceVector(t,0,2); diff = unconstrained.predict(v,puremodels) - t[2]; temp += diff*diff; } } computed_err_unconstrained += temp/double(test.Size()*NumSamples); //unbiased temp = 0.0; t = tdata.Col(2); d = np::sliceMatrixCols(tdata,0,2); if(!unbiased.fit(d,t,puremodels)) { Print(" failed to fit unbiased model "); cleanup(puremodels); } for(uint i = 0; i<test.Size(); i++) { for(int j = 0; j<NumSamples; j++) { t = test[i].Row(j); v = np::sliceVector(t,0,2); diff = unbiased.predict(v,puremodels) - t[2]; temp += diff*diff; } } computed_err_unbiased += temp/double(test.Size()*NumSamples); //biased temp = 0.0; t = tdata.Col(2); d = np::sliceMatrixCols(tdata,0,2); if(!biased.fit(d,t,puremodels)) { Print(" failed to fit biased model "); cleanup(puremodels); } for(uint i = 0; i<test.Size(); i++) { for(int j = 0; j<NumSamples; j++) { t = test[i].Row(j); v = np::sliceVector(t,0,2); diff = biased.predict(v,puremodels) - t[2]; temp += diff*diff; } } computed_err_biased += temp/double(test.Size()*NumSamples); //weighted temp = 0.0; t = tdata.Col(2); d = np::sliceMatrixCols(tdata,0,2); if(!weighted.fit(d,t,puremodels)) { Print(" failed to fit weighted model "); cleanup(puremodels); } for(uint i = 0; i<test.Size(); i++) { for(int j = 0; j<NumSamples; j++) { t = test[i].Row(j); v = np::sliceVector(t,0,2); diff = weighted.predict(v,puremodels) - t[2]; temp += diff*diff; } } computed_err_weighted += temp/double(test.Size()*NumSamples); //gendreg temp = 0.0; t = tdata.Col(2); d = np::sliceMatrixCols(tdata,0,2); if(!genreg.fit(d,t,puremodels)) { Print(" failed to fit generalized regression model "); cleanup(puremodels); } for(uint i = 0; i<test.Size(); i++) { for(int j = 0; j<NumSamples; j++) { t = test[i].Row(j); v = np::sliceVector(t,0,2); diff = genreg.predict(v,puremodels) - t[2]; temp += diff*diff; } } computed_err_genreg += temp/double(test.Size()*NumSamples); temp = 0.0; PrintFormat("\n\n\nRandom DataSet%5d Raw errors:", ndone); for(uint imodel = 0; imodel<puremodels.Size() ; imodel++) { PrintFormat(" %.8lf", computed_err_raw[imodel] / ndone) ; temp += computed_err_raw[imodel] / ndone ; } PrintFormat("\n Mean raw error = %8.8lf", temp / double(puremodels.Size())) ; PrintFormat("\n Average error = %8.8lf", computed_err_average / ndone) ; PrintFormat("\n Unconstrained error = %8.8lf", computed_err_unconstrained / ndone) ; PrintFormat("\n Unbiased error = %8.8lf", computed_err_unbiased / ndone) ; PrintFormat("\n Biased error = %8.8lf", computed_err_biased / ndone) ; PrintFormat("\n Weighted error = %8.8lf", computed_err_weighted / ndone) ; PrintFormat("\n GenReg error = %8.8lf", computed_err_genreg / ndone) ; cleanup(puremodels); } //+------------------------------------------------------------------+
Al ejecutar el script con los valores predeterminados se obtiene el siguiente resultado.
MR 0 15:56:41.914 Ensemble_Demo (BTCUSD,D1) Random DataSet 1 Raw errors: KI 0 15:56:41.914 Ensemble_Demo (BTCUSD,D1) 0.38602529 HP 0 15:56:41.914 Ensemble_Demo (BTCUSD,D1) 0.36430552 CK 0 15:56:41.914 Ensemble_Demo (BTCUSD,D1) 0.36703202 OS 0 15:56:41.914 Ensemble_Demo (BTCUSD,D1) 0.51205057 EJ 0 15:56:41.914 Ensemble_Demo (BTCUSD,D1) 0.57791798 HE 0 15:56:41.914 Ensemble_Demo (BTCUSD,D1) 0.66825953 FL 0 15:56:41.914 Ensemble_Demo (BTCUSD,D1) 0.65051234 QD 0 15:56:41.914 Ensemble_Demo (BTCUSD,D1) 0.57403745 EO 0 15:56:41.914 Ensemble_Demo (BTCUSD,D1) 0.71593174 PF 0 15:56:41.914 Ensemble_Demo (BTCUSD,D1) 0.62444495 NQ 0 15:56:41.914 Ensemble_Demo (BTCUSD,D1) 0.77552594 KI 0 15:56:41.914 Ensemble_Demo (BTCUSD,D1) 0.75079339 MP 0 15:56:41.914 Ensemble_Demo (BTCUSD,D1) 0.78851743 CK 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) 0.52343272 OR 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) 0.70166082 EK 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) RE 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) Mean raw error = 0.59869651 QL 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) DE 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) Average error = 0.55224337 ML 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) QF 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) Unconstrained error = 10.21673109 KL 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) RI 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) Unbiased error = 0.55224337 GL 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) PH 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) Biased error = 0.48431477 CL 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) HH 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) Weighted error = 0.51507522 OM 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) LK 0 15:56:41.915 Ensemble_Demo (BTCUSD,D1) GenReg error = 0.33761372 KM 0 15:57:11.108 Ensemble_Demo (BTCUSD,D1) GG 0 15:57:11.108 Ensemble_Demo (BTCUSD,D1) CQ 0 15:57:11.108 Ensemble_Demo (BTCUSD,D1)
Si todos los parámetros del script se mantienen iguales que en la ejecución anterior, excepto que esta vez optamos por entrenar el modelo de consenso con datos ruidosos. Observamos el siguiente resultado:
NL 0 15:59:51.502 Ensemble_Demo (BTCUSD,D1) Random DataSet 1 Raw errors: OS 0 15:59:51.502 Ensemble_Demo (BTCUSD,D1) 0.72840629 GJ 0 15:59:51.502 Ensemble_Demo (BTCUSD,D1) 0.63345953 PE 0 15:59:51.502 Ensemble_Demo (BTCUSD,D1) 0.68442450 JL 0 15:59:51.502 Ensemble_Demo (BTCUSD,D1) 0.91936106 OD 0 15:59:51.502 Ensemble_Demo (BTCUSD,D1) 0.75230667 LO 0 15:59:51.502 Ensemble_Demo (BTCUSD,D1) 0.88366446 PF 0 15:59:51.502 Ensemble_Demo (BTCUSD,D1) 0.78226316 CQ 0 15:59:51.502 Ensemble_Demo (BTCUSD,D1) 0.87140196 II 0 15:59:51.502 Ensemble_Demo (BTCUSD,D1) 0.58672356 KP 0 15:59:51.502 Ensemble_Demo (BTCUSD,D1) 1.09990815 MK 0 15:59:51.502 Ensemble_Demo (BTCUSD,D1) 0.92548778 OR 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) 1.03795716 GJ 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) 0.80684429 GE 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) 1.24041209 GL 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) 0.92169606 NF 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) CS 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) Mean raw error = 0.85828778 RF 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) DS 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) Average error = 0.83433599 FF 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) FP 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) Unconstrained error = 23416285121251567120416768.00000000 DS 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) JR 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) Unbiased error = 0.83433599 HS 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) PP 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) Biased error = 0.74321307 LD 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) GQ 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) Weighted error = 0.83213118 PD 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) FR 0 15:59:51.503 Ensemble_Demo (BTCUSD,D1) GenReg error = 0.78697882
Las observaciones clave de estos resultados son que los métodos de conjunto generalmente superan a los modelos individuales, ya que la combinación de múltiples modelos generalmente produce mejores resultados que confiar en uno solo. Sin embargo, no existe un método universal óptimo, ya que cada uno tiene sus fortalezas y debilidades, y la elección óptima depende del conjunto de datos específicos y del problema en cuestión.
La regresión sin restricciones, si bien es potencialmente poderosa, es muy susceptible al sobreajuste, particularmente con conjuntos de datos ruidosos o pequeños. Por otro lado, GRNN se destaca en el manejo de conjuntos de datos pequeños y ruidosos al suavizar eficazmente los datos, aunque puede sacrificar algo de potencia de ajuste para conjuntos de datos más grandes y limpios.
Los métodos de regresión lineal también pueden ser efectivos, pero el sobreajuste es un problema, especialmente con conjuntos de datos ruidosos o pequeños. El promedio simple y la ponderación de la varianza son generalmente robustos y pueden ser buenas opciones cuando el conjunto de datos es ruidoso o el método óptimo es incierto. En resumen, la elección del método de conjunto debe considerarse cuidadosamente en función de las características específicas del conjunto de datos, y a menudo es beneficioso experimentar con diferentes métodos y evaluar su desempeño en un conjunto de validación para tomar una decisión informada. Se adjunta todo el código referenciado en el texto.
Nombre del archivo | Descripción |
---|---|
MQL5/include/mlffnn.mqh | Contiene la definición de la clase FFNN que implementa un peceptrón multicapa básico. |
MQL5/include/grnn.mqh | Define la clase CGrnn que implementa una red neuronal de regresión generalizada que utilizó recocido simulado. |
MQL5/include/OLS.mqh | Define la clase MCO que encapsula la regresión de mínimos cuadrados ordinarios |
MQL5/include/ensemble.mqh | Contiene la definición de seis métodos de conjunto implementados como las clases CAvg, CLinReg, Cbiased, CUnbiased, CWeighted y CGenReg |
MQL5/include/np.mqh | Contiene matriz de variación y funciones de utilidad vectorial. |
MQL5/scripts/Ensemble_Demo.mq5 | Este es un script que demuestra las clases de conjunto definidas en ensemble.mqh |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16630
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
super artículo - gracias. El tema es relevante -la investigación de los datos - se verá en mi terminal y la investigación.....
dará retroalimentación aquí.
Esta información está en mi cola para su consideración......