Hablando de nuevo sobre los mapas de Kohonen

Mykola Demko | 7 junio, 2016

Introducción

Este artículo es la continuación de un artículo publicado con anterioridad: "Utilizar Mapas con Función de Auto-Organización (Mapas Kohonen) en MetaTrader 5". Gran parte del material ha sido rediseñado y adaptado para facilitar su uso en terceros proyectos. En general, el artículo tiene como objetivo ayudar a los principiantes y los programadores con experiencia a conectar a sus proyectos el algoritmo de red neuronal de los mapas Kohonen, para que ellos mismos introduzcan cambios en el algoritmo básico. Este artículo se utiliza sólo una muestra de patrones, pero se hace hincapié en la diversidad de ejemplos de uso del código.


Teoría de los mapas de Kohonen

Los mapas de Kohonen son una red de una sola capa, en la que cada neurona está conectada a todos los componentes de un vector de entrada n-dimensional (patrón). El vector de entrada (patrón) es la descripción de uno de los objetos que se van a clasificar.

En la red de Kohonen se usa aprendizaje no supervisado. Para el aprendizaje de la red se aplican mecanismos de competencia. Cuando se aplica un patrón a la entrada, gana la neurona cuyo vector se diferencie menos del patrón de entrada. Para la neurona ganadora se ejecuta la siguiente relación:

Fórmula 1

donde:

  • n - número de neuronas,
  • j - número de neuronas del ganador,
  • d (x,w) - distancia entre los vectores x y w.

La más común es usar una medida euclídea como distancia.

Fórmula 2

En esta implementación, la búsqueda de la neurona ganadora se produce en la función BestMatchingNode de la clase CSOM_Net_Base.

Alrededor de la neurona ganadora se genera un entorno (neighborhood) o rango de aprendizaje (radius of learning). El rango de aprendizaje determina qué neuronas se someten al aprendizaje en esta iteración. Resulta máximo al comienzo del aprendizaje, y disminuye al aumentar el número de iteraciones del aprendizaje, de modo que en la última etapa del rango de aprendizaje solo entra la neurona-ganadora.

Entorno de la neurona ganadora

Los pesos de las neuronas que se encuentran dentro del rango de aprendizaje, se adaptan de acuerdo con la regla de Kohonen:

Regla de Kohonen

donde:

  • x - patrón de entrada,
  • k - número de iteraciones del aprendizaje,
  • ni(k) - coeficiente de aprendizaje de la neurona i-ésima del rango de aprendizaje en la iteración k del aprenidizaje.

Los pesos de las neuronas localizadas fuera del rango de aprendizaje no se someten a adaptación. En esta implementación, la adaptación de los pesos de las neuronas tiene lugar en la función AdjustWeights de la clase CSOMNode.

El coeficiente de velocidad de aprendizaje ni(k) se divide en dos partes:

  • en la función de vecindad ni (d, k)

función de vecindad

  • y la función de velocidad de aprendizaje a(k)

función de velocidad de aprendizaje

donde A y B son las constantes seleccionadas.

Esta función es inversamente proporcional al número de ciclo de aprendizaje, por lo que en la fase inicial del aprendizaje, la velocidad y el rango de aprendizaje son bastante grandes, se promedian los patrones. Luego, al final del aprendizaje tiene lugar un ajuste final de los pesos con respecto a los patrones de entrada.

En general, la dinámica de adaptación de una neurona particular, puede representarse como una convergencia en un gradiente:

Descenso de gradiente

Durante el aprendizaje de la red de Kohonen, surge el problema de las llamadas "neuronas muertas". Las neuronas con coeficientes de peso iniciales significativamente alejados de los patrones de entrada, nunca vencen a la competencia, independientemente del tiempo que continúe el aprendizaje. Este problema de sistema del algoritmo de aprendizaje de Kohonen en esta implementación se ha solucionado con dos arreglos.

En primer lugar, se ha modificado la función de elección aleatoria de patrón del conjunto de entrenamiento. En lugar de rand()/N, con el que no hay garantías de que en N iteraciones vayan a darse todos los valaores, se usa la clase C_PRNG_UD. Este garantiza una elección aleatoria distrubuida uniformemente. En segundo lugar, la inicialización de los pesos de las neuronas tiene lugar con valores aleatorios, pero sólo en el intervalo que se encuentra en los patrones.

De esta forma, si aparacen neuronas que nunca hayan ganado a la competencia, "neuronas muertas", entonces estas como mínimo estarán en los alrededores del vencedor y también se someterán a aprendizaje, jugando el papel de puentes entre patrones durante la clasificación.


Implementación de la red de Kohonen y ejemplos de uso

Ahora vamos a analizar la implementación programática de los mapas de Kohonen. La propia implementación se basa en dos dimensiones: la dimensión de los patrones m_dimension y el número de nodos m_total_nodes. Sin embargo, los mapas se presentan como tridemensinales, donde la cantidad de nodos m_total_nodes se pliega en un rectángulo m_xcells * m_ycells. Por eso los nodos nodes guardan no solo la información, sino también las ubicaciones en una superficie.

class CSOMNode
  {
protected:
   
   //--- coordenadas de la zona del nodo
   int               m_x1;
   int               m_y1;
   int               m_x2;
   int               m_y2;
   
   //--- coordenadas del centro del nodo
   double            m_x;
   double            m_y;
   
   //--- peso del nodo
   double            m_weights[];
   ...

La clase CSOMNode está incluida en la composición de la clase básica CSOM_Net_Base en forma de matriz unidimensional de los ejemplares de la clase CSOMNode m_som_nodes[]. Esta es precisamente la red de Kohonen. El resto de las funciones sirven para el aprendizaje y la explotación de la red.

En el artículo anterior se hizo hincapié en la diversidad de ejemplos de los patrones y en la demostración de la variedad gráfica de la interfaz. En este se analizará solo un ejemplo de patrones, pero en cinco variantes de conexión. He rehecho el código fuente de tal forma que sea cómodo de usar al incluirse en proyectos ajenos. La implementación se divide en cinco clases.

class CSOM_Net_Base
class CSOM_Net_Data   : public CSOM_Net_Base
class CSOM_Net_Train  : public CSOM_Net_Data
class CSOM_Net_Img    : public CSOM_Net_Train
class CSOM_Net_Demo   : public CSOM_Net_Img

Como se puede ver, las clases están conectadas en forma de cascada. De esta forma, si algunas funciones no son requeridas en el proyecto conectado, entonces bastará con no conectar las clases-derivadas, y todas las funciones posteriores serán cortadas.

La lista de funciones públicas se muestra más abajo:

class CSOM_Net_Base
  {
public:
   //--- nodo público de la red de Kohonen
   CSOMNode         *public_node;
   //--- la función transmite el nodo ind establecido a la parte pública public_node
   void              GetNode(int ind){ public_node=m_som_nodes[ind].GetObjPointer(); };
   //--- la función retorna la dimensión de los patrones
   int               GetDimension(){return(m_dimension);};
   //--- función de carga de la red desde un archivo                 
   bool              DownloadNet(string file_name);
   //--- la función encuentra el mejor nodo de la red según el vector establecido usando una máscara
   int               BestMatchingNode(const double &vector[]);
   //--- la función encuentra el mejor nodo de la red según el vector establecido cortando conforme al tamaño dimension
   int               BestMatchingNode(const double &vector[],int dimension);
   //--- función de rellenado de la máscara de bits para buscar nodos parecidos a los patrones (la máscara determina según qué campos buscar)
   bool              InitSetByteMap(int num,bool value);
  };
  
class CSOM_Net_Data : public CSOM_Net_Base
  {
public:
   //--- función de carga de datos para el aprendizaje desde el archivo establecido
   bool              LoadPatternDataFromFile(string filename);
   //--- función de añadido de un vector al conjunto de aprendizaje
   void              AddVectorToPatternsSet(double &vector[],string title);
   //--- función de copiado del patrón del conjunto de entrenamiento    
   bool              GetPatterns(int ind_pattern,double &vector[]);
   //--- función de retorno del título del patrón del conjunto de entrenamiento  
   string            GetTitlesPatterns(int ind_pattern);
   //--- función de retorno de la cantidad de patrones
   int               GetTotalsPatterns();
  }; 
  
class CSOM_Net_Train : public CSOM_Net_Data
  {
public:
   //--- función de inicialización de la red, obtención de parámetros
   void              InitParameters(int iterations,int xcells,int ycells);
   //--- función de aprendizaje de la red
   void              Train();
   //--- función de guardado de la red en un archivo 
   void              SaveNet(string file_name);
   //--- funciones virtuales (el cuerpo se describe en el descendiente CSOM_Net_Img) 
   virtual void      Render(){};
   virtual void      ShowBMP(bool back){};
  }; 
  
class CSOM_Net_Img : public CSOM_Net_Train
  {
public:
   //--- función de inicialización de la red, obtención de parámetros
   void              InitParameters(int iterations,int xcells,int ycells,
                                    int bmpwidth,int bmpheight,
                                    bool p_HexagonalCell,bool p_ShowBorders,bool p_ShowTitles,
                                    int p_ColorScheme,int p_MaxPictures);
   //--- función de representación del estado de la red
   void              Render();
   //--- función de muestra de la imagen bmp en el gráfico  
   void              ShowBMP(bool back);
   //--- función de guardado del recurso en un archivo bmp
   void              SaveBMP();
   //--- función de desinicialización, elimina una imagen BMP del gráfico
   void              NetDeinit();
  };
  
class CSOM_Net_Demonstration : public CSOM_Net_Img
  {
public:
   //--- función - ejemplo de uso del descendiente para la ampliación de posibilidades
   void              ShowTrainPatterns();
  };

En los archivos adjuntos hay cinco ejemplos con variantes de uso de los mapas de Kohonen. Los ejemplos están numerados en orden de cascada de clases. Veamos los ejemplos por orden.


Creación de una red, clase básica

El primer ejemplo para su trabajo exige de un archivo con la extensión somnet, se usa para guardar una red entrenada con anterioridad. En el ejemplo se analiza la variante de conexión de la red entrenada al asesor para reconocer los patrones desde la implementación programática. Para que sea más sencillo, los datos del archivo se obtienen con la ayuda de la función FileReadArray. Esto no necesita de sistemas de lectura complicados y de reconocimiento de líneas (parsers). Además, es más rápido resetear y obtener los datos con la ayuda de matrices.

En el ejemplo Sample1_SOM_Net_Base se descarga la red entrenada desde el archivo binario indicado en el ejemplo (sin extensión):

input string SOM_Net="SOM\\SOM_Net";

La extensión somnet se inserta de forma automática. Esto se hace para que no se pueda confundir y cargar un archivo inadecuado para reconocer el algoritmo.

La carga de la red sucede en la función DownloadNet(string file_name). Tras estudiar detenidamente el cuerpo de la función, queda claro el formato del archivo somnet. Al inicio del archivo está escrita la parte del encabezamiento del archivo (HEADER), que consta de tres celdas de memoria del tipo double (como toda la matriz obtenida del archivo). El primer campo guarda la dimensión de los patrones más seis. Los seis campos son necesarios para guardar las coordenadas del nodo. Pero dado que seis es un valor constante, podremos obtener con facilidad la dimensión de los patrones restando seis al primer campo del título de la matriz. El segundo y tercer campo guardan las variables m_xcells y m_ycells, el tamaño de X e Y respectivamente en la representación visual de la red. A partir de ellos, se puede obtener la cantidad de nodos en la red mediante multiplicación.

Después hay que conseguir los datos de la red, vector a vector, hasta que la red no se encuentre cargada.

Vamos a continuar analizando el ejemplo. Después de cargar la red, se llenan con constantes el patrón vector[]. La incialización con constantes se muestra como ejemplo. La función BestMatchingNode encuentra el índice del nodo más parecido. Después de ello, el nodo se transmite a la parte pública para tener acceso seguro a la clase CSOMNode.

Nos detendremos con más detalle en la función BestMatchingNode. La función tiene tres variantes de uso. Al llamar al reinicio BestMatchingNode(const double &vector[]) sin inicializar las máscaras, se realizará la búsqueda por todos los campos de los vectores, puesto que la propia función analizará las máscaras por unidades, es decir, permitirá la búsqueda por todos los campos. Si se llama por anticipado la inicialización de la máscara InitSetByteMap(int num,bool value) para cada campo, entonces se activará el filtrado, en qué campos buscar y en qué campos no. De esta forma, se logra la posibilidad de buscar el nodo mediante información completa. Si se usa otra recarga BestMatchingNode(const double &vector[],int dimension), entonces el filtrado se realizará por orden hasta el campo con el número transmitido a la función con el parámetro dimension.


Carga de los datos de los patrones

El segundo ejemplo Sample2_SOM_Net_Data muestra una conexión de la siguiente extensión de la funcionalidad, la clase CSOM_Net_Data. La clase se anuncia como descendiente de la clase CSOM_Net_Base, por eso le son accesibles los descendientes, más sus propias funciones. La clase ha sido creada como una introducción a la funcionalidad del parser para la carga de patrones. Los patrones obtenidos por el parser pueden en lo sucesivo ser usados para la carga de patrones, tanto de aprendizaje, como de reconocimiento.

El nombre del archivo con los patrones se define con el parámetro:

input string DataFileName="SOM\\optim.csv";

Preste atención: el nombre del archivo se indica con la extensión, es decir, se trata del nombre completo del archivo. El propio archivo con el parser se abre como un archivo con la bandera FILE_CSV.

Después se llaman las funciones de igual forma que en el ejemplo anterior: carga de la red, carga del archivo, parseo del archivo, reconocimiento en el ciclo de patrones e impresión.

Aquí merece la pena estudiar el formato del archivo con los patrones, sobre el que se ha construido el parser.

La primera línea del archivo deberá rellenarse con los nombres de las columnas Title. Se trata de una condición obligatoria, si el encabezamiento del archivo no se rellena, entonces el parser cortará el ejemplo superior, que se encuentra en la primera línea, y creará a partir de él los nombres de las columnas.

Archivo con los patrones

Las columnas en el archivo se usan para guardar los datos de los patrones de una profundidad. Por consiguiente, las líneas se usan para guardar patrones aparte. En otras palabras, los vectores se ubican horizontalmente, mientras que los mismos campos de todos los vectores lo hacen de verticalmente.

El parser, durante su funcionamiento, no solo ubica los datos de los patrones en la matriz incorporada m_patterns_sets_array, sino que también reconoce los encabezamientos de los campos, los guarda en la matriz m_som_titles (declarada en la clase-descendiente), y también rellena con números las líneas de las matrices m_patterns_titles. De esta forma, encontrar el patrón necesario en el archivo según su línea no supondrá dificultad alguna.


Aprendizaje de la red

El tercer ejemplo, Sample3_SOM_Net_Train, es el más interesante. En primer lugar, en él está conectada la segunda clase básica, la clase de aprendizaje de los mapas de Kohonen CSOM_Net_Train. En segundo lugar, esta última clase es suficiente para el aprendizaje automático sin visualización, las clases siguientes solo activan el envoltorio gráfico. Es decir todo lo descrito en el apartado "Teoría de los mapas de Kohonen" se ha implementado en tres clases. En cuarto lugar, en la clase ha sido organizada una "bifuración de herencia".

Para las clases posteriores será imprescindible que en las funciones de aprendizaje de la red Train() sean llamadas las funciones de cálculo y dibujado de las imágenes bmp, que representan las etapas de aprendizaje. Pero puesto que en el tercer ejemplo no hay gráfico, entonces el cuerpo de estas funciones ya no es necesario aquí. Para resolver esta contradicción, las funciones Render() y ShowBMP(bool back) son declaradas como virtuales (virtual) y tienen los cuerpos vacíos. El código necesario se define como descendiente de CSOM_Net_Img.

Ahora vamos a pasar directamente al aprendizaje. Antes del aprendizaje, necesitaremos transmitir los parámetros. Para ello existe la función de servicio InitParameters(int iterations,int xcells,int ycells), en las que se transmiten los parámetros: cantidad de iteraciones de aprendizaje - iterations y el tamaño de la red, dividido en dos parámetros - xcells, ycells (tamaño de X e Y, repectivamente).

En la función Train() se ha organizado un ciclo de aprendizaje. Antes de la llamada al ciclo de aprendizaje, será necesario analizar la red. Es decir, analizar la clase de las cifras equitativamente distribuidas, determinar los tamaños de las matrices, calcular los máximos y mínimos según las columnas de los patrones, e inicializar los nodos con datos aleatorios de este diapasón.

La propia iteración de aprendizaje se muestra en una función aparte TrainIterations(int &p_iter) para que sea más sencillo aprender el código y que el acceso durante la mejora sea más sencillo. Y es que esta implementación ha sido escrita para que otros programadores puedan entender su esencia de forma sencilla e introducir las correcciones necesarias. En la función se ha implementado el algoritmo descrito en el apartado "Teoría de los mapas de Kohonen", por eso no nos vamos a detener en el mismo.

En general, el tercer ejemplo muestra la secuencia de conexión de la red de la clase CSOM_Net_Train, que es precisamente: carga del archivo con los patrones de aprendizaje, transmisión de los parámetros, llamada del aprendizaje, guardado de la red en un archivo.

Al describir la función de lectura del archivo, se recordó más arriba que para comprender el formato, merece la pena estudiar simultáneamente la función de guardado y la de lectura.

void CSOM_Net_Train::SaveNet(string file_name)
bool CSOM_Net_Base::DownloadNet(string file_name)

Representación binaria del archivo somnet

Entre los datos de la red, predomina el tipo double. Por eso la red se guarda en una matriz unidimensional del tipo double. El resto de los datos se convierte a este tipo.

En primer lugar, en la red se conserva el encabezamiento. Son los cinco primeros campos double, en los que se guardan los datos imprescindibles sobre la red.

  1. 6+dimension_node — longitud del patrón más 6 (se necesitan seis campos adicionales en el nodo para guardar las coordenadas).
  2. m_xcells — cantidad de nodos en horizontal.
  3. m_ycells — cantidad de nodos en vertical.
  4. m_xsize — tamaño de bmp en horizontal.
  5. m_ysize — tamaño de bmp en vertical.

A continuación, se realiza el copiado en la red de nodos. Primero vienen los 6 datos sobre las coordenadas del nodo, y después los pesos del mismo. Así, nodo tras nodo, se copian todos los datos.

Al final de la red se añade una línea con la enumeración Titles, transformada de forma binaria al formato double.

Y bien, ¿qué obtenemos? Por el encabezamiento, podemos conocer el tamaño del patrón y la cantidad de nodos. Gracias a esta información también se puede saber dónde comienza y acaban los datos de los nodos. Desde el último punto de los nodos hasta el final del archivo se guardan los Titles de la red. Se ubican al final del archivo, puesto que no es posible prever qué cantidad de memoria necesitan.


Envoltorio gráfico

El cuarto ejemplo Sample4_SOM_Net_Img revela la conexión de un envoltorio gráfico. Para su llamada, en el artículo anterior era necesario conectar la biblioteca gráfica cintbmp.mqh, escrita por Dmitry Fedoseev. Pero he tenido que rehacerla para quitar todas las llamadas a WinAPI DLL. Esto permite usar el código en el Mercado. En el archivo actualizado se guarda información sobre el autor, pero yo he cambiado el nombre a cintbmp2.mqh.

En el código se han modificado solo las funciones de guardado del archivo en el directorio Image, desde el que luego se cargan los archivos bmp. Ahora todos los archivos se registran solo para ser guardados, y no para representarse. Para la representación se cargan directamente en los recursos. De esta forma, es posible evitar la llamada de las bibliotecas DLL.

La secuencia de la llamada de funciones es similar al ejemplo anterior. Pero puesto que ahora está activado el envoltorio gráfico, para que funcione serán necesarios parámetros adicionales en los ajustes. Por eso, la función de inicialización declarada en la anterior clase-descendiente ha sido reiniciada para ubicar los nuevos parámetros para el envoltorio gráfico.

void   InitParameters(int iterations,int xcells,int ycells,
                       int bmpwidth,int bmpheight,
                       bool p_HexagonalCell,bool p_ShowBorders,bool p_ShowTitles,
                       int p_ColorScheme,int p_MaxPictures);

Los parámetros se transmiten a través de la interfaz de funciones, para que no se anuncien variables globales en el código de clases. Esto complicaría la conexión a terceros proyectos donde los nombres de las variables pueden ser otros.

Después sigue la llamada de la función de aprendizaje Train() del ejemplar de la clase CSOM_Net_Train. Pero como ahora en los cuerpos de las funciones Render() y ShowBMP(bool back) existe un código para trabajar con el gráfico, esto provocará la representación del proceso de aprendizaje en cada centésima iteración. Después de salir de Train(), estas funciones se llaman para representar los últimos cambios.

Finalización del aprendizaje


Ampliación de la funcionalidad

El quinto ejemplo, Sample5_SOM_Net_Player, enseña una muestra de ampliación de clases. No es básico para la red, sino que solo muestra lo sencillo que es desarrollar el código existente. Para ello es suficiente con declarar la clase como descendiente de una de las clases básicas.

¿Para qué debemos escribir una clase-descendiente? Para que en ella estén disponibles todos las funciones (ocultadas con el comando protected) de todas las clases de las que se hereda nuestra clase. Por ejemplo, tras conectarse como descendiente de la clase CSOM_Net_Img, obtenemos acceso a todas las funciones y datos declarados como protected y public de todas las clases anteriores. De esta forma, es posible componer un programa a nuestra discreción, dependiendo de lo que queramos obtener.

Y bien, la extensión de la clase CSOM_Net_ Player es un panel gráfico de control de la neurored de Kohonen. Para escribirla, he conectado el archivo IncGUI_v3.mqh con la biblioteca gráfica de Dmitry Fedoseev, pero con una ligera modificiación. La mejora ha afectado a las etiquetas gráficas (no a un lado, sino encima de los objetos) y al color de dichas etiquetas. No he hecho correcciones en el archivo original, sino que he declarado los descendientes de las clases de la biblioteca.

Aunque la creación de un panel gráfico ocupa la mayor parte del código, no supone ninguna complicación. Se trata de un trabajo común y ordinario de introducción de código. Quisiera detenerme con mayor detalle en la organización de la interacción del panel gráfico con la neurored de Kohonen y el envoltorio gráfico.

Lo primero que me gustaría destacar es que el inicio de la red se divide en dos partes. Una de las partes controla el aprendizaje y guarda la red instruida en un archivo binario somnet. La segunda parte carga la red desde un archivo y busca los nodos adecuados según los patrones cargados. Las dos partes son independientes, aunque usan los mismos campos de entrada de datos: el campo de entrada de la ruta al archivo con patrones y el campo de entrada de la ruta al archivo con la neurored.

Sin embargo, los modos son verdaderamente independientes. Y los patrones para el aprendizaje no se toman obligatoriamente del mismo archivo que los patrones para el reconocimiento. Lo mismo sucede con la neurored. Puede instruir la red en un archivo, después pasar el modo a "operativo" y cargar otra red para el reconociemiento.

Importante: el paso entre modos resetea el modo a estado cero.

Bien, los modos son independientes. ¿Pero cómo se ha conseguido lograr una respuesta tan rápida del programa al cambio del estado de los botones? El problema es que el programa no usa un ciclo para el aprendizaje o la búsqueda de patrones. Se organiza basándose en los eventos.

Cuando el programa entra en el modo de aprendizaje, entonces realiza la reinicialización, carga los parámetros, prepara la memoria, y efectúa la primera iteración, después de lo cual envía a sí mismo un evento de usuario con el número 333. La entrada al manejador de eventos se filtra clicando con el ratón sobre el objeto, finalizando la edición del objeto y según el evento de usuario 333.

De esta forma, si no ha habido interrupciones (si el estado de los botones "Start" y "Aprendizaje" no ha cambiado, y seguimos teniendo acceso a la entrada al aprendizaje), tiene lugar una nueva iteración del aprendizaje y se envía un nuevo mensaje 333. Y así sucesivamente, hasta que la red no aprenda e interrumpa por sí mismo el ciclo, o el usuario no detenga el aprendizaje con los botones.

Este mismo esquema de interrupción se ha implementado en el modo de búsqueda de patrones.

He intentado hacer la interfaz lo más comprensible posible. Básicamente, repite las variables input de los ejemplos anteriores. Pero aun así, mostraremos el valor de ciertos botones y campos de entrada:

Botones del reproductor

Para iniciar el panel, será necesario:

  • Repartir los ficheros del archivo zip por los directorios.
  • Los ficheros con los patrones van el directorio MQL5\Files\SOM\.
  • Los ficheros con imágenes bmp de botones van en el directorio MQL5\Images\SOM\.
  • Los ficheros con los programas van en el directorio MQL5\Projects\SOM\.
  • Compilar todos los ficheros mq5.


Conclusión

Hace cuarenta años, las neuroredes constituían la vanguardia del pensamiento científico. Unos veinte años atrás, una persona que estuviera familiarizada con los algoritmos de las neuroredes, era un especialista único. Ahora la palabra "neurored" no asusta a nadie. Los algoritmos de lógica difusa, de las neuroredes, se han plantado con firmeza en el trading, y resulta que ya no son tan complejos. Espero que este artículo ayude a dispersar más aún la aureola de misterio que rodea este tema y que normalice el uso de los mapas de Kohonen.