Académique Documents
Professionnel Documents
Culture Documents
STANISLAV KOROTKY
0
394
El tema de las redes neuronales de Kohonen ya fue abordado en algunos artículos del
sitio web mql5.com, a saber: Utilizar mapas con funciones de auto-organización (mapas
Kohonen) en MetaTrader 5 y Hablando de nuevo sobre los mapas de Kohonen. Los
lectores de estos artículos conocieron los principios generales de la construcción de redes
neuronales de este tipo y el análisis visual de los indicadores económicos usando los
mapas.
No obstante, en términos prácticos, la aplicación de la red de Kohonen precisamente
para el trading algorítmico fue limitado con un solo enfoque, es decir, con el análisis
visual de los mapas topológicos formados para los resultados de la optimización del
Asesor Experto (EA). En este caso, la opinión subjetiva -o más bien, la percepción- de una
persona y su capacidad de sacar conclusiones de la imagen resulta ser un factor
determinante, relegando a segundo plano las capacidades de la red de representar los
datos en relación con las categorías prácticas.
A los que no están familiarizados con los términos como «red», «capa», «neurona»
(nodo), «», «peso», «velocidad de aprendizaje», «rango de aprendizaje», y otras
nociones relacionadas con las redes de Kohonen, les recomiendo insistentemente leer
primero los artículos arriba mencionados. A continuación, tendremos que enfrascarnos en
este tema, así que, la repetición de las nociones básicas prolongaría significativamente
esta publicación.
Corrigiendo errores
Vamos a invocar las clases CSOM y CSOMNode publicadas en el primer artículo, tomando
en cuenta las adiciones en el segundo artículo. Los fragmentos principales en ellas son
prácticamente idénticas y heredan problemas comunes.
En primer lugar, hay que notar que, por algunas razones, las neuronas en estas clases se
indexan (se identifican y se definen a través de los parámetros de los constructores) por
coordenadas de píxel. Eso no es muy lógico y complica los cálculos y la depuración en
algunos puntos. En particular, con este enfoque, los ajustes de presentación afectan los
cálculos. Sólo imagínese: hay dos instancias de las redes absolutamente idénticas, con
tamaños iguales de la cuadrícula y aprenden usando el mismo conjunto de datos, tienen
las configuraciones iguales e inicialización del generador de números aleatorios. Sin
embargo, los resultados obtenidos son diferentes sólo porque las imágenes de una red son
más grandes que de la otra. Es un error.
Vamos a indexar las neuronas por números: cada neurona del array m_node (clase CSOM)
va a tener las coordenadas «x» y «y» que corresponden a los números de la columna y la
línea en la capa de salida de la red de Kohonen. Cada neurona va a inicializarse a través
del método CSOMNode::InitNode(x, y) en vez de CSOMNode::InitNode(x1, y1, x2, y2).
Cuando pasaremos a la visualización, las coordenadas de la neurona permanecerán
inalteradas al alterar los tamaños del mapa en píxeles.
En los códigos fuente heredados, la normalización de los datos de entrada no se usa. Sin
embargo, ella es muy importante cuando diferentes componentes (características) de los
vectores de entrada tienen un rango de valores diferente. Es así tanto para los resultados
de la optimización de los EAs, como durante el agrupamiento de los valores de diferentes
indicadores. En cuanto a los resultados de la optimización, podemos ver que los valores
con un beneficio neto de decenas de millares están juntos con los valores pequeños del
coeficiente de Sharp o con valores de un dígito del factor de recuperación.
No se puede enseñar a la red de Kohonen usando los datos de la escala tan diferente,
porque la red va a considerar prácticamente sólo los componentes grandes e ignorar los
pequeños. Usted puede ver eso en la imagen de abajo obtenida a través del programa
que será considerado paso a paso en este artículo y adjuntado al final. En el programa
existe la posibilidad de generar los vectores de entrada aleatorios en los cuales tres
componentes están determinados dentro de los rangos [0, 1000], [0, 1] y [-1, +1]. El
parámetro de entrada especial UseNormalization permite activar/desactivar la
normalización.
Resultado del aprendizaje de la red de Kohonen sin normalización de los datos de entrada
Se conocen muchos métodos de la normalización, pero el más popular, tal vez, sea la
sustracción del valor medio de toda la muestra de cada componente, y la división por la
desviación estándar (sigma, raíz cuadrada de la dispersión). Eso lleve el valor medio de
datos transformados a cero, y la desviación estándar a uno.
(1)
Esta técnica se usa en la clase actualizada CSOM, en el método Normalize. Esta claro que
primero hace falta calcular la media y la sigma para cada componente del conjunto de
datos de entrada, lo que se hace en el método InitNormalization (véase más abajo).
(2)
(3)
(4)
Puesto que los valores normalizados caen simétricamente alrededor de cero, vamos a
cambiar el principio de la inicialización de los pesos de neuronas antes del aprendizaje:
en vez del rango [0, 1], ahora será el rango [-1, +1] (véase el método
CSOMNode::InitNode). Eso aumentará la eficacia del aprendizaje de la red.
Otro aspecto a ser corregido es el cálculo de las iteraciones del aprendizaje. En las clases
fuente, la iteración se entiende como la presentación de cada vector de entrada
individual a la red. Por tanto, el número de iteraciones se corrige a base de y de acuerdo
con el tamaño de la muestra de aprendizaje. Recuerde que el principio del aprendizaje y
de la generalización de la información por la red de Kohonen supone que cada muestra se
presenta a la red muchas veces. Por ejemplo, si la muestra incluye 100 entradas,
entonces para repasar cada una de ellas 100 veces (como media), habrá que establecer el
número de iteraciones igual a 10 000. Sin embargo, si la muestra es de 10 000 entradas,
el número de iteraciones tendrá que ser 100 000 El método más conveniente y
convencional para establecer el número de, así llamadas, las «épocas del aprendizaje»,
es decir, los ciclos dentro de cada uno de los cuales todas las instancias se pasan
aletoriamente a la entrada de la red. Este número será definido en el parámetro
EpochNumber. Debido a su introducción, la duración del aprendizaje se separa
parametricamente del tamaño del conjunto de datos.
Es más aun importante, ya que el conjunto general de datos puede ser dividido en dos
componentes, en caso de necesidad: la muestra de aprendizaje y la llamada muestra de
validación. La última se aplica para monitorear la calidad del aprendizaje de la red. La
cosa es que la adaptación de la red a los datos de entrada durante el aprendizaje tiene el
«reverso de la medalla»: la red empieza a adaptarse a las particularidades específicas de
las instancias, y así, pierde la capacidad de generalizar y funcionar adecuadamente en
datos nunca vistos antes (diferentes de los que han sido usados para el aprendizaje). Y
eso que la idea del aprendizaje, normalmente, consiste en utilizar las particularidades
detectadas a través de la red en el futuro.
N ~ 5 * sqrt(M) (6)
El último problema que tenemos que corregir en los códigos fuente originales concierne
al procesamiento de la cuadrícula hexagonal. Como se sabe, los mapas de Kohonen
normalmente se construyen usando la colocación rectangular o hexagonal de las células
(neuronas), siendo que ambos modos están realizados inicialmente en los códigos fuente.
Sin embargo, la cuadrícula hexagonal se muestra en forma de las células, pero se calcula
igualmente como la rectangular. Para llegar a la raíz del error, vamos a analizar la
siguiente ilustración.
Geometría de los alrededores de la neurona en la cuadrícula rectangular y hexagonal
Aquí se muestra el entorno lógico de una neurona aleatoria (en este caso, con
coordenadas 3;3) para las cuadrículas de ambas geometrías. El radio del entorno es igual
a 1. En la cuadrícula cuadrada, la neurona tiene 4 vecinos; y en la cuadrícula hexagonal,
son 6. La implementación de la apariencia celular se consigue a través del
desplazamiento de cada segunda línea de las células a la mitad de la célula al lado. Sin
embargo, sus coordenadas internas no se alteran, y desde el punto de vista del
algoritmo, el entorno de la neurona en la cuadrícula hexagonal aparece como antes (está
marcada en color rosa).
Formalmente, el algoritmo calcula el entorno no sólo usando los vecinos adyacentes, sino
como una función radial decreciente convexa que depende de la distancia entre las
coordenadas de las células. En otras palabras, la vecindad no es una propiedad binaria de
la neurona (sea un vecino o no), sino un valor continuo que se calcula a través de la
fórmula de Gaussian:
(8)
Aquí, dji es la distancia entre las neuronas j y i (se tiene en cuenta la numeración
continua, en vez de las coordenadas X y Y); la sigma es el ancho eficaz del entorno o el
rango de aprendizaje que se reduce gradualmente durante el aprendizaje. Al principio
del aprendizaje, el entorno cubre con una «campana» un espacio más grande que las
neuronas adyacentes.
Puesto que esta fórmula depende de la distancia, ella también altera el entorno si las
coordenadas no han sido corregidas de manera correspondiente. Por tanto, las siguientes
líneas del código fuente original del método CSOM::Train:
+ (m_som_nodes[winningnode].Y() -
m_som_nodes[i].Y()) * (m_som_nodes[winningnode].Y() -
m_som_nodes[i].Y());
double shiftx = 0;
shiftx = +0.5;
shiftx = -0.5;
+ (m_node[winningnode].GetY() -
m_node[i].GetY()) * (m_node[winningnode].GetY() - m_node[i].GetY());
Este operador condicional garantiza prácticamente una cierta aceleración de los cálculos
a través del rechazo de las neuronas fuera del entorno de una sigma. No obstante, desde
el punto de vista de la calidad del aprendizaje, la fórmula de Gaussianes una forma ideal
y esta intervención no es razonable. Si realizamos la omisión de las neuronas demasiado
lejanas, entonces por tres sigmas, y no por una. Y eso es aún más crítico después de que
hayamos corregido el cálculo de la cuadrícula hexagonal, porque la distancia entre las
neuronas adyacentes ubicadas en las líneas vecinas es igual a sqrt(1*1 + 0.5*0.5) = 1.118,
es decir, más de uno. En los códigos adjuntos, este operador condicional está comentado.
Si surge la necesidad de acelerar los cálculos, utilice la siguiente opción:
Visualización
A pesar de que las redes de Kohonen se asocian principalmente con un mapa gráfico
visible, su topología y los algoritmos de aprendizaje pueden funcionar perfectamente sin
la interfaz de usuarios. Particularmente, las tareas de predecir o compactar la
información no requieren ningún análisis visual obligatorio, mientras que la clasificación
de las imágenes puede mostrar el resultado en forma de un número (número de la clase o
la probabilidad del evento). Por tanto, la funcionalidad de las redes de Kohonen fue
dividida entre dos clases. En la clase CSOM, queda sólo la parte del cálculo, la carga y el
guardado de los datos, la carga y el guardado de las redes. Adicionalmente, fue creada la
clase derivada CSOMDisplay que contiene la gráfica. Según mi parecer, es una jerarquía
más simple y más lógica de la propuesta en el artículo 2. A continuación, para las tareas
de la selección de los parámetros óptimos del EA vamos a usar CSOMDisplay, y para la
previsión, usaremos CSOM.
Nótese que el indicio tipo cuadrícula (rectangular o hexagonal) pertenece a la clase base,
puesto que influye en el cálculo de las distancias. Juntamente con el número de los
nodos por la vertical y horizontal, así como la dimensionalidad del espacio de entrada de
los datos, el tipo de la cuadrícula forma parte de la arquitectura de la red y se guarda en
el archivo. Cuando la red se carga desde el archivo, todos estos parámetros se leen
precisamente desde ahí, y no desde las configuraciones del programa. Otras
configuraciones que influyen en la representación gráfica, tales como, el tamaño de los
mapas en píxeles, visualización de los límites entre las celdas, visualización de las
leyendas, no se guardan en el archivo de la red y pueden alterarse repetidamente y
aleatoriamente para una red después de su aprendizaje.
Cabe destacar que ninguna de las clases actualizadas proporciona la interfaz gráfica de
usuario con los controles, es que todas las configuraciones se establecen a través de los
parámetros de entrada de los programas MQL. Al mismo tiempo, la clase CSOMDisplay
realiza algunos recursos útiles.
Vamos a recordar que en los ejemplos anteriores del trabajo con las redes de Kohonen,
había un parámetro de entrada MaxPictures, y él ha sido dejado en la nueva
implementación. Se pasa como maxpict al método CSOMDisplay::Init y establece el
número de los mapas (planos) de la red que se muestran en el gráfico en una línea.
operando con este parámetro junto con el tamaño unificado de las imágenes ImageW y
ImageH, podemos encontrar una opción cuando todos los mapas caben en la pantalla. No
obstante, cuando hay muchos mapas (por ejemplo, cuando necesitamos analizar muchas
configuraciones del EA), tenemos que reducir su tamaño, y eso no es muy conveniente.
En estos casos, usamos MaxPictures para activar el nuevo modo, estableciendo el
parámetro a 0.
En este modo, las visualizaciones de los mapas no se generan en el gráfico como objetos
OBJ_BITMAP_LABEL con la vinculación a las coordenadas de los píxeles, sino en forma de
los objetos OBJ_BITMAP con vinculación a la escala de tiempo. El tamaño de estos mapas
puede ser aumentado hasta la altura total del gráfico, y Usted puede ojearlos usando la
barra de desplazamiento horizontal a través del ratón, la rueda o el teclado. El número
de los mapas ya no se limita con el tamaño de la pantalla, pero tenemos que asegurarnos
de la presencia del número suficiente de las barras.
Aparte de eso, si hacemos doble clic en cualquier neurona, se resalta con el color inverso
en el mapa actual, así como en el resto de los mapas. Eso permite comparar visualmente
la actividad de las neuronas por todos los recursos simultáneamente.
Finalmente, cabe mencionar que la gráfica entera ha sido pasada a la clase
estándar CCanvas. Eso libera el código de las dependencias externas, pero tiene un
efecto secundario: las coordenadas Y ahora se calculan de arriba abajo y no de abajo
arriba como antes. Como resultado, la leyenda de los mapas con el nombre de los
componentes y los rangos de sus valores se muestra por encima de los mapas y no
debajo, pero este cambio no parece ser crítico.
Mejoras
Antes de proceder a las tareas aplicadas, hay que mejorar las clases de la red neuronal.
En adición a los mapas estándar que representan los pesos de sinapsis en los espacios 2D
de características específicas, vamos a preparar el cálculo y visualización de algunos
mapas de servicio que representan un estándar de-facto para las redes de Kohonen.
Hablando con adelantado, digamos que vamos a necesitar muchos de ellos en la fase de
los experimentos aplicados.
Vamos a definir los índices de las dimensiones adicionales (en total, serán 5).
#define EXTRA_DIMENSIONS 5
U-Matrix
double vector[];
other.GetCodeVector(vector);
return CalculateDistance(vector);
Aquí, el método GetCodeVector obtiene el array con los pesos de otra neurona, y lo envía
inmediatamente para el cálculo de la distancia de manera común.
Para obtener la distancia de la neurona unificada, hay que calcular la distancia con todas
las neuronas vecinas y promediarla. Puesto que el repaso de las neuronas vecinas es una
tarea común para varias operaciones en la cuadrícula de la red, creamos la clase base
para el repaso, y luego, implementamos los algoritmos determinados en sus
descendientes, incluyendo la suma de las distancias.
#define NBH_SQUARE_SIZE 4
#define NBH_HEXAGONAL_SIZE 6
template<typename T>
class Neighbourhood
protected:
int neighbours[];
int nbhsize;
bool hex;
int m_ycells;
public:
hex = _hex;
m_ycells = ysize;
if(hex)
nbhsize = NBH_HEXAGONAL_SIZE;
ArrayResize(neighbours, NBH_HEXAGONAL_SIZE);
// odd row
// even row
*/
else
{
nbhsize = NBH_SQUARE_SIZE;
ArrayResize(neighbours, NBH_SQUARE_SIZE);
~Neighbourhood()
ArrayResize(neighbours, 0);
if(hex)
reset();
if(k == 0 || k == 4) continue;
if(k == 1 || k == 5) continue;
return getResult();
};
Antes del ciclo se invoca el método abstracto reset, y después del ciclo, el método
abstracto getResult. El conjunto de tres métodos abstractos permite preparar y ejecutar
la iteración de los vecinos en las clases descendientes, así como generar el resultado. El
concepto de la construcción del método «loop» corresponde al patrón de la proyección
de la POO (método de plantilla). Aquí, se debe distinguir el término «plantilla» en el
nombre del patrón, de la construcción de lenguaje de las plantillas que también se usa
en la clase Neighbourhood, porque es una plantilla (es decir, es parametrizado por un
determinado tipo variable T). En particular, el propio método loop y el método getResult
devuelven el valor tipo T.
private:
int n;
double d;
public:
n = 0;
d = 0.0;
d += node1.CalculateDistance(&node2);
n++;
return d / n;
};
El tipo de trabajo es double. Gracias a la clase base, los cálculos de la distancia son
bastante transparentes.
Vamos a calcular las distancias de todo el mapa en el método CSOM::CalculateDistances.
void CSOM::CalculateDistances()
m_max[DIM_UMATRIX] = d;
m_node[i].SetDistance(d);
m_hitCount++;
double e = 0;
m_sum[i] += vector[i];
m_mse += e / m_dimension;
ArrayCopy(vector, data);
m_node[ind].RegisterPatternHit(vector);
double code[];
m_node[ind].GetCodeVector(code);
Denormalize(code);
double mse = 0;
mse /= m_dimension;
return mse;
}
Aparte del registro de una ocurrencia, este método calcula el error de cuantización para
la neurona actual y para el vector pasado. Para eso, a la neurona vencedora se le solicita
así llamado el vector de código (code vector), es decir, el array de los pesos de sinapsis,
y se calcula la suma de los cuadrados de las diferencias de los componentes entre los
pesos y el vector de entrada.
double data[];
ArrayResize(data, m_dimension);
return nmse;
Este método suma todos los errores de cuantización y los compara con la dispersión de
los datos de entrada en m_dataMSE (eso es exactamente el cálculo NMSE del que hemos
hablado antes en el contexto de la validación y la detención del aprendizaje). En este
método, se menciona la variable m_validationOffset, que se establece al crear el objeto
CSOM a base de que si se usa o no la división del conjunto de datos de entrada por el
subconjunto de aprendizaje y de validación.
Usted puede fácilmente darse cuenta de que el método CalculateStats se invoca en cada
época dentro del método Train (si la fase de la convergencia ya ha sido iniciada), y puede
juzgar por el valor devuelto si el error general de la red ha empezado a crecer, es decir,
si es el momento de parar.
La dispersión m_dataMSE se calcula de antemano a través del método:
void CSOM::CalculateDataMSE()
double data[];
m_dataMSE = 0.0;
double mse = 0;
mse /= m_dimension;
m_dataMSE += mse;
ArrayInitialize(m_max, 0);
ArrayInitialize(m_min, 0);
ArrayResize(m_mean, m_dimension);
ArrayResize(m_sigma, m_dimension);
{
double maxv = -DBL_MAX;
if(normalization)
m_mean[j] = 0;
m_sigma[j] = 0;
if(normalization)
m_mean[j] += v;
m_sigma[j] += v * v;
m_max[j] = maxv;
m_min[j] = minv;
m_mean[j] /= m_nSet;
else
m_mean[j] = 0;
m_sigma[j] = 1;
}
Volviendo a los planos adicionales, hay que notar que después de los cálculos realizados
en CSOMNode::RegisterPatternHit, cada neurona es capaz de devolver la estadística
correspondiente a través de los métodos:
return m_hitCount;
if(m_hitCount == 0) return 0;
if(m_hitCount == 0) return 0;
return MathSqrt(z);
if(m_hitCount == 0) return 0;
De esta manera, obtenemos los datos para el llenado de dos planos, con el número de las
visualizaciones de los vectores de entrada por las neuronas y con el error de
cuantización.
Respuesta de la red
El próximo plano adicional es el mapa de la salida o la respuesta de la red a una
determinada instancia. No hay que olvidar que después del envío de la señal a la red,
juntamente con la neurona vencedora, se activan todas las demás neuronas (en mayor o
en menor grado). La posibilidad de comparar la amplitud de las áreas activas de la
respuesta puede ayudar a determinar la estabilidad de la solución propuesta por la red.
m_output = CalculateDistance(vector);
return m_output;
double temp[];
ArrayCopy(temp, vector);
if(normalize) Normalize(temp);
m_min[DIM_OUTPUT] = DBL_MAX;
m_max[DIM_OUTPUT] = -DBL_MAX;
double x = m_node[i].CalculateOutput(temp);
K-Means
Existen muchos métodos de clasterización. La opción más óptima para MQL5 es usar la
versión de ALGLIB que forma parte de la biblioteca estándar. Basta con incluir el archivo
de cabecera:
#include <Math/Alglib/dataanalysis.mqh>
int info;
CMatrixDouble clusters;
int membership[];
double weights[];
m_node[i].GetCodeVector(weights);
xy[i] = weights;
if(info == 1) // ok
{
m_node[i].SetCluster(membership[i]);
A la hora de trabajar con ALGLIB, tenga en cuenta que ella usa su propio generador de
números aleatorios que considera el estado interno del objeto estático especial. Por eso,
incluso la inicialización explícita del generador estándar a través de MathSrand no
resetea sus estado. Eso es especialmente crítico para los EAs, ya que los objetos globales
dentro de ellos no se generan de nuevo al modificar las configuraciones. Como resultado,
en caso de ALGLIB, pueden surgir dificultades con la reproductibilidad de los resultados
de los cálculos, si CMath::m_state no se pone a cero en OnInit.
Tomando en cuenta las desventajas mencionadas de K-Means, es deseable tener una
opción alternativa del agrupamiento. Una solución alternativa es evidente.
Alternativa
Podemos transformar el mapa de las distancias unificadas en los clasters, por ejemplo,
de la siguiente manera.
Copiamos la información sobre todas las neuronas al array y lo ordenamos de acuerdo con
el valor de la dirección U (CSOMNode::GetDistance()) en orden ascendiente.
Para una neurona dada, vamos a verificar cíclicamente por el array si las neuronas
vecinas pertenecen a algún cláster.
Pero si usamos dos planos de servicio, ¿puede que tenga sentido analizar también el
tercer plano, es decir, con el error de cuantización? Es verdad, cuanto más grande sea el
error de cuantización en una determinada neurona, menos confianza hay que tener a la
información sobre la distancia U pequeña en ella, y viceversa.
Si recordamos cómo es la función con el error de cuantización:
if(m_hitCount == 0) return 0;
private:
int cluster;
public:
cluster = -1;
int x = node2.GetCluster();
else cluster = x;
return cluster;
};
La clase contiene el número del cláster potencial «cluster» (el número es un número
interno por eso paratremizamos la plantilla tipo int). Inicialmente, esta variable se
inicializa en -1 en el método reset, es decir, no hay cláster. Luego, a la medida de que la
clase padre llame desde su método «loop» a nuestra nueva implementación «iterate»,
obtenemos el número del cláster de cada neurona vecina, lo comparamos con «cluster» y
guardamos el valor mínimo. Lo mismo o -1, si ningún cláster ha sido encontrado, se
devuelve por el método getResult.
Como una mejora, proponemos monitorear «la altura de los picos» entre las neuronas,
(es decir, el valor node1.CalculateDistance(&node2)), y ejecutar el «fluido» del número
del clúster desde una neurona a otra sólo si la «altura» es menor que antes. La versión
final de la implementación se presenta en el código fuente.
void CSOM::Clusterize()
double array[][2];
ArrayResize(array, n);
if(m_node[i].GetHitsCount() > 0)
array[i][0] = m_node[i].GetDistance() *
MathSqrt(m_node[i].GetMSE());
else
{
array[i][0] = DBL_MAX;
array[i][1] = i;
m_node[i].SetCluster(-1);
ArraySort(array);
ArrayResize(m_clusters, 0);
m_node[(int)array[i][1]].SetCluster(r);
double vector[];
m_node[(int)array[i][1]].GetCodeVector(vector);
m_node[(int)array[i][1]].SetCluster(count++);
}
}
Desde luego, la calidad del agrupamiento tiene que ser evaluada en la práctica, y yo
supongo la presencia de los problemas topológicos desde el principio. Sin embargo,
considerando que la mayoría de los modos clásicos del agrupamiento también tienen sus
problemas y ceden ante el modo propuesto en la sencillez, la nueva solución parece
bastante atractiva.
Entre las ventajas de esta implementación, yo mencionaría que los clásteres están
ordenados por la relevancia (en K-Means mencionado antes, los clasteres son del mismo
valor), su forma es aleatoria y no hace falta establecer su número de antemano. Cabe
mencionar que la última ventaja tiene su reverso, es decir, el número de los clusters
puede ser bastante grande. Al mismo tiempo, el ordenamiento de los clusters por el
grado de la semejanza del contenido y por el error mínimo permite considerar en la
práctica sólo 5-10 primeros clusters, dejando el resto al margen.
Adelantándome, diré que después de muchas pruebas, ha sido confirmado que los centros
de los clusters encontrados por el algoritmo K-Means ofrecen los resultados peores que el
agrupamiento alternativo (por lo menos, en la tarea del análisis de los resultados de la
optimización). Debido a eso, siempre va a entenderse y aplicarse solamente este método
del agrupamiento a continuación.
Simulación
Es la hora de pasar de la teoría a la práctica y comprobar el funcionamiento de la red.
Vamos a crear un EA simple y universal con posibilidades de demostrar la funcionalidad
principal. Lo llamaremos SOM-Explorer.
DataFileName — el nombre del archivo de texto con los datos para el aprendizaje
o la simulación; la clase CSOM soporta el formato csv, pero añadiremos un poco
más tarde la lectura de los archivos set, porque el análisis de las
configuraciones de optimización de otros EAs «están en el juego»; cuando se
especifica el archivo con los datos de entrada, su nombre se usa también para el
guardado de la red tras su aprendizaje pero con otra extensión (véase más
abajo); se puede indicar o no la extensión csv; el nombre puede incluir una
carpeta dentro de MQL5/Files;
Grupo - Visualization
ImageW — tamaño de cada mapa (plano) en píxeles por la horizontal, por defecto
— 500;
ImageH — tamaño de cada mapa (plano) en píxeles por la vertical, por defecto —
500;
Grupo - Options
Solamente se trata de las configuraciones básicas. Vamos a añadir otra parte de los
parámetros específicos a la medida de que continuemos resolviendo los problemas.
¡Atención! El Asesor Experto altera los ajustes del gráfico actual— abra el gráfico
nuevo seleccionado sólo para trabajar con este EA.
El objeto de la clase CSOMDisplay ejecutará todo el trabajo en el EA.
CSOMDisplay KohonenMap;
void OnInit()
EventSetMillisecondTimer(1);
{
KohonenMap.OnChartEvent(id, lparam, dparam, sparam);
void OnTimer()
EventKillTimer();
MathSrand(RandomSeed);
if(NetFileName != "")
if(!KohonenMap.Load(NetFileName)) return;
if(DataFileName != "")
if(!KohonenMap.LoadPatterns(DataFileName))
int n = KohonenMap.GetFeatureCount();
double v[];
ArrayResize(v, n);
KohonenMap.AddPattern(v, "RANDOM");
Print("Random Input:");
ArrayPrint(v);
double y[];
ArrayPrint(y);
KohonenMap.CalculateOutput(v, true);
hasOneTestPattern = true;
Este método conviene para formar las muestras de aprendizaje desde las fuentes de los
datos con formatos no soportados, por ejemplo, desde las bases de datos, así como para
el llenado directamente desde los indicadores. En principio, en este caso, la adición de la
instancia en el conjunto de servicio se requiere solamente para su visualización posterior
en el mapa (se muestra más abajo), y para definir la neurona de la red más conveniente
basta con llamar a GetBestMatchingFeatures. Este método, entre varios métodos
GetBestMatchingXYZ disponibles, permite que el array «y» obtenga los valores
correspondientes de las instancias de la neurona vencedora. Finalmente, usando
CalculateOutput, mostramos la reacción de la red a la instancia de texto en un plano
adicional.
if(DataFileName == "")
{
// generate 3-d demo vectors with unscaled values {[0,+1000],
[0,+1], [-1,+1]}
KohonenMap.AssignFeatureTitles(titles);
double x[3];
if(!KohonenMap.LoadPatterns(DataFileName))
return;
Si los datos de entrada vienen desde un archivo, cargamos este archivo. Si surge un error,
terminamos el trabajo, porque no hay ninguna red ni los datos.
KohonenMap.SetValidationSection((int)(KohonenMap.GetDataCount()
* (1.0 - ValidationSetPercent / 100.0)));
if(ClusterNumber > 1)
KohonenMap.Clusterize(ClusterNumber);
else
KohonenMap.Clusterize();
if(!hasOneTestPattern)
double vector[];
ArrayResize(vector, KohonenMap.GetFeatureCount());
ArrayInitialize(vector, 0);
KohonenMap.CalculateOutput(vector);
A continuación, dibujamos todos los mapas en los búferes internos de los recursos
gráficos (primero, la capa de color de atrás):
if(hasOneTestPattern)
KohonenMap.ShowAllPatterns();
else
KohonenMap.ShowAllNodes(); // draw labels in cells in BMP buffers
if(ClusterNumber != 0)
Los archivos se colocan en una carpeta separada con el mismo nombre que el archivo de
la red (si está especificado) o el archivo con datos (si está especificado). Si el archivo con
datos no ha sido especificado y la red se enseñaba en los datos generados aleatorios, el
nombre para el archivo som y de la carpeta con imágenes se forma usando el prefijo SOM
y la fecha y la hora actual.
if(NetFileName == "")
KohonenMap.Save(KohonenMap.GetID());
Vamos a intentar iniciar el EA con la generación de los datos de texto aleatorios. Con
todas las configuraciones predefinidas, excepto los tamaños reducidos de las imágenes,
para que todos los planos entren en la «captura de la pantalla» - ImageW = 230, ImageH =
230, MaxPictures = 3, - obtenemos lo siguiente:
Ejemplo de los mapas de Kohonen para los vectores 3D aleatorios
Aquí, en cada neurona se muestra la información de servicio (se puede ver los detalles
apuntando con el cursor) y los clusters encontrados están marcados.
Overall NMSE=0.09420336270396877
Clusters [14]:
N0
N1
N2
N3
N4
Mapa de Kohonen para el tercer componente de los vectores 3D aleatorios y el contador de ocurrencias
U-Matrix y error de cuantización
La muestra está marcada con RANDOM. Las ayudas sobre las neuronas aparecen al
apuntar con el ratón. En el registro, se muestra aproximadamente lo siguiente:
Random Input:
Pues bien, las herramientas para trabajar con la red de Kohonen están listas. Podemos
proceder a las tareas aplicadas. Lo haremos en el segundo artículo.
Conclusión
Las implementaciones de las redes de Kohonen están disponibles para los usuarios de
MetaTrader ya algunos años. Hemos corregido algunos errores, las hemos completado con
unas herramientas útiles y hemos testeado su funcionamiento a través de un EA especial
de demostración. Los códigos fuente permiten aplicar las clases para propias tareas. Los
ejemplos de eso serán considerados a continuación.