Redes Neuronales en JavaScript con Brain.js

Especialmente en los últimos años, las redes neuronales (NN) realmente han despegado como una forma práctica y eficiente de resolver problemas que no se pueden resolver fácilmente...

Introducción

Especialmente en los últimos años, las redes neuronales (NN) realmente han despegado como una forma práctica y eficiente de resolver problemas que no pueden resolverse fácilmente mediante un algoritmo, como la detección de rostros, el reconocimiento de voz y el diagnóstico médico. Esto se debe en gran parte a los descubrimientos recientes sobre cómo entrenar y ajustar mejor una red, así como a la velocidad cada vez mayor de las computadoras.

Recientemente, un estudiante del Imperial College London creó un NN llamado [Jirafa] (http://www.technologyreview.com/view/541276/deep-learning-machine-teaches-itself-chess-in-72-hours-plays -en-maestro-internacional/) que podría ser entrenado en solo 72 horas para jugar al ajedrez al mismo nivel que un Maestro Internacional FIDE. Las computadoras que juegan al ajedrez a este nivel no son realmente nuevas, pero la forma en que se creó este programa es nueva. Estos programas suelen tardar años en construirse y se ajustan con la ayuda de un verdadero gran maestro, mientras que Giraffe, por otro lado, se creó simplemente utilizando una red neuronal profunda, algo de creatividad y un gran conjunto de datos de partidas de ajedrez. Este es otro ejemplo más de la promesa que las redes neuronales han estado mostrando recientemente, y solo van a mejorar.

Cerebro.js

La desventaja de las NN, y de la inteligencia artificial en general, es que el campo es muy matemático, lo que tiende a asustar a las personas antes de que comiencen. La naturaleza altamente técnica de los NN y toda la jerga que la acompaña dificulta que los no iniciados se aprovechen. Aquí es donde entra en juego Cerebro.js. Brain.js hace un gran trabajo al simplificar el proceso de creación y entrenamiento de un NN utilizando la facilidad de uso de JavaScript y limitando la API a solo unas pocas opciones y llamadas a métodos.

No me malinterpreten, aún necesita conocer algunos de los conceptos detrás de los NN, pero esta simplificación lo hace mucho menos desalentador.

Por ejemplo, para entrenar una red para que se aproxime a la función XOR (que es uno de los ejemplos estándar de NN), todo lo que necesita es:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var brain = require('brain');

var net = new brain.NeuralNetwork();

net.train([{input: [0, 0], output: [0]},
           {input: [0, 1], output: [1]},
           {input: [1, 0], output: [1]},
           {input: [1, 1], output: [0]}]);

var output = net.run([1, 0]);  // [0.933]

Este código simplemente crea una nueva red (net), entrena la red usando una serie de ejemplos, y luego ejecuta la red con una entrada de [1, 0], lo que da como resultado correctamente [0.933] (también conocido como 1).

Si bien Brain.js no tiene muchas opciones que le permitan personalizar sus redes, la API acepta suficientes parámetros para que sea útil para aplicaciones simples. Puede establecer la cantidad y el tamaño de sus capas ocultas, el umbral de error, la tasa de aprendizaje y más:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var net = new brain.NeuralNetwork({
    hiddenLayers: [128,64]
});

net.train({
    errorThresh: 0.005,  // error threshold to reach before completion
    iterations: 20000,   // maximum training iterations 
    log: true,           // console.log() progress periodically 
    logPeriod: 10,       // number of iterations between logging 
    learningRate: 0.3    // learning rate 
});

Consulte la documentación para obtener una lista completa de opciones.

Si bien está limitado en los tipos de redes que puede construir, eso no significa que no pueda hacer nada significativo. Toma este proyecto por ejemplo. El autor reunió un montón de imágenes captcha para su conjunto de datos, usó un procesamiento de imágenes simple para preprocesar las imágenes y luego usó Brain.js para crear una red neuronal que identifica a cada carácter individual.

Ventajas

Como ya mencioné, Brain.js es excelente para crear rápidamente un NN simple en un lenguaje de alto nivel donde puede aprovechar la gran cantidad de bibliotecas de código abierto. Con un buen conjunto de datos y unas pocas líneas de código, puede crear una funcionalidad realmente interesante.

Las bibliotecas altamente científicas/computacionales escritas en JavaScript como esta tienden a ser bastante criticadas, pero personalmente creo que Brain.js tiene su lugar en JS siempre que tenga las expectativas y la aplicación correctas. Por ejemplo, JS es el lenguaje dominante (¿único?) que se ejecuta en el lado del cliente en el navegador, entonces, ¿por qué no aprovechar esta biblioteca para cosas como juegos en el navegador, ubicación de anuncios (aburrido, lo sé) o reconocimiento de caracteres?

Desventajas

Si bien definitivamente podemos obtener algo de valor de una biblioteca como esta, no es perfecta. Como mencioné, la biblioteca limita la arquitectura de su red a un punto en el que solo puede hacer aplicaciones simples. No hay muchas posibilidades de capas softmax u otra estructura. Sería bueno tener al menos la opción de una mayor personalización de la arquitectura en lugar de ocultarte todo.

Probablemente mi mayor queja es que la biblioteca está escrita en JavaScript puro. El entrenamiento de un NN es un proceso lento que puede requerir miles de iteraciones (es decir, millones o miles de millones de operaciones) para entrenar en millones de puntos de datos. JavaScript no es un lenguaje rápido de ninguna manera y realmente debería tener complementos para cosas como esta para acelerar los cálculos durante el entrenamiento. El captcha cracker de arriba tomó un tiempo de entrenamiento sorprendentemente bajo de 20 minutos, pero todo lo que se necesita son algunas entradas más y algunos datos más para aumentar el tiempo de entrenamiento en unas pocas horas o incluso días, como verá en mi ejemplo a continuación. .

Desafortunadamente, esta biblioteca ya ha sido abandonada por su autor (la descripción de Github está precedida de "[SIN MANTENIMIENTO]"). Si bien entiendo que puede ser difícil mantener las demandas de una biblioteca popular de código abierto, aún es decepcionante verlo, y solo podemos esperar que alguien calificado pueda llenar el vacío. Estoy seguro de que ya se está trabajando en una buena bifurcación, pero puede ser necesario buscar un poco para encontrarla.

Ejemplo

Aquí te mostraré un ejemplo un poco más complicado de cómo usar Brain. En este ejemplo, creé un NN que puede reconocer un solo dígito escrito a mano (0-9). El conjunto de datos que estoy usando es el popular conjunto de datos MNIST, que contiene más de 50 000 muestras de dígitos escritos a mano. Este tipo de problema se conoce como reconocimiento óptico de caracteres (LOC), que es una aplicación popular de NN.

El reconocimiento funciona tomando una imagen en escala de grises de 28x28 de un dígito escrito a mano y emitiendo el dígito que la red cree que "vio". Esto significa que tendremos 784 entradas (una para cada píxel) con valores entre 0 y 255, y habrá 10 salidas (una para cada dígito). Cada salida tendrá un valor de 0-1, actuando esencialmente como el nivel de confianza de que ese dígito en particular es la respuesta correcta. El valor de confianza más alto es entonces nuestra respuesta.

En el código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
var brain = require('brain');
var fs = require('fs');

var getMnistData = function(content) {
    var lines = content.toString().split('\n');

    var data = [];
    for (var i = 0; i < lines.length; i++) {
        var input = lines[i].split(',').map(Number);

        var output = Array.apply(null, Array(10)).map(Number.prototype.valueOf, 0);
        output[input.shift()] = 1;

        data.push({
            input: input,
            output: output
        });
    }

    return data;
};

fs.readFile(__dirname + '/train.csv', function (err, trainContent) {
    if (err) {
        console.log('Error:', err);
    }

    var trainData = getMnistData(trainContent);

    console.log('Got ' + trainData.length + ' samples');

    var net = new brain.NeuralNetwork({hiddenLayers: [784, 392, 196]});

    net.train(trainData, {
        errorThresh: 0.025,
        log: true,
        logPeriod: 1,
        learningRate: 0.1
    });
});

El archivo train.csv es solo un CSV con una imagen por línea. El primer valor es el dígito que se muestra en la imagen y los siguientes 784 valores son los datos de píxeles.

El número de capas y nodos que elegí fue un poco arbitrario y probablemente innecesariamente alto para esta aplicación de OCR. Sin embargo, cuando no puede aprovechar cosas como softmaxes o pooling, es posible que tenga más suerte aumentando la cantidad de capas y el número de nodos.

El entrenamiento tomó fácilmente más de una hora para obtener algunos resultados decentes. Si bien esto era de esperar, me decepcionó un poco tener que esperar tanto para probar una nueva estructura de red o nuevos parámetros de aprendizaje. Una aplicación simple como esta no debería tomar tanto tiempo, pero ese es el precio que paga por una implementación de JavaScript.

Para probar la red, cargué otro archivo, test.csv, y lo usé como referencia para comparar redes. De esa manera, tenemos una mejor idea del rendimiento, ya que estamos probando entradas en las que la red aún no ha sido entrenada.

Así es como probé la red (solo muestro las partes relevantes):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Require packages...

fs.readFile(__dirname + '/test.csv', function (err, testContent) {
    if (err) {
        console.log('Error:', err);
    }

    // Load training data...

    // Train network...

    // Test it out
    var testData = getMnistData(testContent);

    var numRight = 0;

    console.log('Neural Network tests:');
    for (i = 0; i < testData.length; i++) {
        var resultArr = net.run(testData[i].input);
        var result = resultArr.indexOf(Math.max.apply(Math, resultArr));
        var actual = testData[i].output.indexOf(Math.max.apply(Math, testData[i].output));

        var str = '(' + i + ') GOT: ' + result + ', ACTUAL: ' + actual;
        str += result === actual ? '' : ' -- WRONG!';

        numRight += result === actual ? 1 : 0;

        console.log(str);
    }

    console.log('Got', numRight, 'out of 350, or ' + String(100*(numRight/350)) + '%');
});

Conclusión

Si bien existen algunas deficiencias, en general creo que Brain.js puede ser muy útil y agregar mucho valor a las aplicaciones JavaScript/Node. Su facilidad de uso debería permitir que casi cualquier persona comience con las redes neuronales.

En caso de que quieras algo con más flexibilidad, o te moleste la falta de soporte para Brain.js, echa un vistazo a sináptico, que permite mucho más personalización en su arquitectura de red. No tiene la popularidad/atención que tiene Brain, pero parece ser la siguiente mejor opción para redes neuronales en JavaScript.

¿Cuál es tu experiencia con Brain.js? ¿Hay algún otro paquete de IA que recomiendes? ¡Cuéntanos en los comentarios!