Reconocimiento de imágenes en la web utilizando TensorFlow.js
Tags: tensorflow, machinelearning
Published: 2020-05-01 00:36:35

De las primeras cosas que descubrí al empezar en el mundo del ML (Machine Learning) fue que además de la matemática, estadística y teoría que abarca esta área, también debo aprender un nuevo lenguaje de programación (Python) y sentí realmente abrumador toda esta cantidad de información que debía absorber. Investigando un poco mas, descubrí que en JavaScript hay herramientas con las que se puede lograr esto y hacer más amigable mi entrada al mundo del ML, y decidí explorar este camino, ya que JavaScript es un lenguaje que conozco, y así puedo enfocarme más en las bases teóricas (que es mi prioridad en este momento) y dejar en segundo plano aprender un nuevo lenguaje para aplicarlas.

Spoiler… resulta que hacer ML con JavaScript tiene más ventajas a parte de hacerme la vida más fácil, pero hablaremos de eso mas adelante. 🤫

En este post quiero mostrarles como con JavaScript, podemos reconocer e identificar imágenes en nuestro navegador haciendo uso de TensorFlow.js, así como las bases teóricas necesarias para lograrlo.

Para empezar, debemos hablar sobre el modelo computacional sobre el cual construiremos este proceso.

## Redes neuronales

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/i2e94l1om913dydufgfs.png)

Son __modelos computacionales que emulan el comportamiento del cerebro humano__ (o por lo menos, como se creía hasta hace unos años que este funcionaba, porque el cerebro humano hasta ahora sigue siendo todo un misterio) para lograr aprender a resolver problemas computacionales el cual, con neuronas conectadas entre sí, refuerzan sus conexiones para lograr aprender a través de su experiencia, experiencia que se adquiere con los datos que se le entrega a la red para realizar el entrenamiento.

Una red neuronal está conformada de la siguiente manera:

### Neurona

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0pu25dimmcz4kyxsk260.png)

Son la unidad básica de construcción de una red neuronal.
La neurona es una unidad que dadas ciertas entradas retorna una salida. Por ejemplo: si queremos saber si ganamos un curso, le podemos enviar a una neurona las calificaciones y si la nota final es mayor que 3, retorna 1 (ganamos la materia), de lo contrario, retorna 0.

### Capa

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/f9xgah2sjfekuaeon0ez.png)

Una capa es un conjunto de neuronas, cada capa es capaz de reconocer patrones simples.

### Red

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/i2e94l1om913dydufgfs.png)

La red neuronal es la unión de varias capas. Que trabajando en conjunto, son capaces reconocer patrones complejos.

<hr/>

Ahora que sabemos cómo esta construida una Red Neuronal, podemos hablar del tipo de redes neuronales que nos sirven para abordar un problema de reconocimiento de imágenes.

## Redes neuronales convolucionales

Hoy trabajaremos con las redes neuronales que sirven para el reconocimiento y clasificación de imágenes, este tipo de red neuronal es llamado “Red neuronal convolucional” (CNN por sus siglas en inglés) y lo que tiene de especial este tipo de red es que __asume que las entradas van a ser arreglos de grandes dimensiones, y esto le permite optimizar sus cálculos.__

### Por qué es esto necesario?

Las imágenes son matrices de pixeles de colores representados en los canales RGB (por lo general, también podrían ser Grayscale, HSV, CMYK, entre otros) y cada uno de estos valores en los pixeles serán los parámetros de entrada para la red (número entre 0 y 255 para el caso de la representación RGB), así, si tenemos una imagen por ejemplo de 32px*32px, tendríamos un total de 3072 (32*32*3) parámetros, que es un número muy grande para una imagen tan pequeña, ahora imaginen procesar una imagen en alta definición (2048*1536*3) tendríamos una imagen de 9'437.814 parámetros por cada una de las imágenes, y esto, computacionalmente es un número muy difícil de manejar, entonces el objetivo de las CNN es reducir ese número de parámetros a través de la aplicación de filtros que extraigan características relevantes de la imagen, por ejemplo, pueden haber filtros encargados de extraer bordes, sombras, esquinas…etc

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nah6fywt7bpjo229n7rp.png)

Y se acuerdan que ya habíamos hablado de que las redes neuronales están formadas por capas? Pues para el caso de las redes neuronales convolucionales, las capas que la conforman pueden ser de 3 tipos diferentes:

### Capa de convolución

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6hcvr8v6zpvk213omx1c.png)

Son las capas que le dan el nombre a la red, y se lo merecen ya que son las que realizan el trabajo computacional más pesado, están directamente conectadas con las entradas y a través de una operación llamada convolución genera mapas de activación para reconocer la imagen.

Estas capas también cuentan con una función de activación para reducir la linearidad de las imágenes (transición entre pixeles, bordes, colores…) y de esta manera aumentar su capacidad de generalización.

### Capas de pooling

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zwp5qqoi50id5kldta8n.png)

Estas capas realizan un submuestreo de las entradas, por lo cual, su misión es disminuir la cantidad de parámetros disminuyendo así el volumen de la capa.

### Capas completamente conectadas

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8bvptvg8qniad6lxhsku.png)

Son capas en las que cada neurona esta conectada con todas las salidas de la capa anterior.

Este es en realidad un acercamiento muy vago al funcionamiento de una red neuronal convolucional, ya que explicar toda la magia que en realidad sucede en estas redes tomaría incluso más de un post 😅, pero es lo necesario para entender lo que veremos a continuación.

Y ahora que sabemos cómo reconocer una imagen (en la teoría), podemos conocer la herramienta que nos va a permitir hacerlo realidad haciendo uso de JavaScript.

<hr/>

## TensorFlow

TensorFlow es uno de las frameworks más usados para Machine Learning, donde __la estructura básica de datos es el Tensor__ (un tensor es una generalización para hablar de vectores y matrices), y se especializa en realizar operaciones matemáticas con ellos.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/bxnrh1ns3tv995ytako2.png)

Una aplicación de Machine Learning es el resultado de calcular de forma repetida operaciones matemáticas complejas, y en TensorFlow estas operaciones son representadas utilizando un grafo llamado __“Data Flow Graph”__ donde cada nodo del grafo representa una operación matemática y las esquinas representan tensores a los que le serán aplicadas estas operaciones.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/r4k2w3kh403erm9vvluu.png)

Esto nos trae ventajas en cuanto a:

- __Paralelismo:__ Utilizando relaciones de dependencias entre las operaciones, es más fácil para el sistema identificar aquellas que pueden ser ejecutadas en forma paralela.
- __Ejecución distribuida:__ Ya que las esquinas contienen lo valores explícitos para el flujo de trabajo, TensorFlow tiene la capacidad de dividir el programa entre múltiples dispositivos (CPUs, GPUs, TPUs) que pueden estar ligados a diferentes máquinas. TensorFlow se encarga también de agregar los canales de comunicación necesarios entre ellos.
- __Compilación:__ El compilador de TensorFlow, puede utilizar información del grafo para generar código más rápido.
- __Portabilidad:__ La representación de data flow graph es independiente del lenguaje, por lo cual, se puede construir por ejemplo en Python y ser guardado para abrirlo en otro lenguaje como C++.

## TensorFlow.js

Y como resulta que JavaScript es la lengua madre de la internet, pues no se podía quedar por fuera de la revolución del Machine Learning, y si, como ya se lo podían imaginar, también se puede desarrollar TensorFlow en JavaScript.

### Por qué utilizar TensorFlow en el navegador?

- __Privacidad de datos:__ Ya que toda la información se va a manejar a través del navegador, la información no va a ser en ningún momento almacenada en servidores, esto nos ayuda mucho cuando nuestra aplicación hace uso de datos sensibles.
- __Aplicaciones interactivas de baja latencia:__ Debido a que no es necesario un canal de comunicación con el servidor, el tiempo de respuesta es menor.
- __No es necesario instalar drivers adicionales__
- __Interactividad con el usuario__
- __Computación en el dipositivo__

### API

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ksituewc1v7mk7h571fe.png)

- __Layers API:__ Ofrece los diferentes tipos de capas con los cuales podemos construir nuestra red.
- __Ops API:__ Ofrece las diferentes operaciones que podemos aplicar a los tensores.

TensorFlow.js ofrece 2 API, una para correr únicamente en el navegador y otra que se puede ejecutar desde Node.js:

- __Browser:__ Haciendo uso de WebGL, TensorFlow puede acceder a la GPU del computador y utilizar su capacidad de computo para realizar los cálculos necesarios.
- __Server:__ A través de Node.js se puede aprovechar la capacidad de cómputo de una CPU, GPU o TPU para realizar los cálculos.

## Manos a la obra

Ahora, te voy a presentar 3 formas de implementar esto en el navegador con TensorFlow.js

### CNN from scratch

La primer forma es haciendo uso del API ‘Layers’ que ofrece TensorFlow.js con el cual puedes construir tu red neuronal desde cero, en este caso haremos nuestro ejemplo haciendo uso del dataset ‘Iris’ cuyo objetivo es reconocer el tipo de planta de una de las tres clases de plantas ‘Iris’ (setosa, versicolor o virginica) con base en el largo y grosor de sus pétalos.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gxz0ncsunbf81d760t93.png)

Se importan las bibliotecas necesarias:

```javascript
const tf = require('@tensorflow/tfjs');
const irisTraining = require('./data/training.json');
const irisTesting = require('./data/testing.json');
```

Los datos se tranforman en tensores:

```javascript
const trainingData = tf.tensor2d(irisTraining.map(item=> [
  item.sepal_length, item.sepal_width, item.petal_length, item.petal_width
]),[130,4]);

const testingData = tf.tensor2d(irisTesting.map(item=> [
  item.sepal_length, item.sepal_width, item.petal_length, item.petal_width
]),[14,4]);

const outputData = tf.tensor2d(irisTraining.map(item => [
  item.species === 'setosa' ? 1 : 0,
  item.species === 'virginica' ? 1 : 0,
  item.species === 'versicolor' ? 1 : 0
]), [130,3]);
```

Se construye la red neuronal haciendo uso del API ‘Layers’ de TensorFlow.js:

```javascript
const model = tf.sequential();

model.add(tf.layers.dense({
  inputShape: [4],
  activation: 'sigmoid',
  units: 10
}));

model.add(tf.layers.dense({
  inputShape: [10],
  activation: 'softmax',
  units: 3
}));

model.compile({
  loss: 'categoricalCrossentropy',
  optimizer: tf.train.adam()
});
```

Se realiza el entrenamiento del modelo:

```javascript
async function train_data(){
  for(let i=0;i<15;i++){
     const res = await model.fit(trainingData,
                 outputData,{epochs: 40});
     console.log(res.history.loss[0]);
  }
}

async function main() {
  let train = await train_data();
  model.predict(testingData).print();
}

main();
```

Al ejecutar el script, obtenemos como resultado un tensor, el cual indica las probabilidades que cada elemento tiene de pertenecer a una clase

### Modelos pre-entrenados

TesnsorFlow.js ofrece una serie de modelos pre-entrenados listos para realizar predicciones o ser usados en un proceso de transfer learning.

- __PoseNet:__ Estimación de poses del cuerpo humano.
- __BodyPix:__ Segmentación de partes del cuerpo humano.
- __Toxicity:__ Calificación del impacto que puede causar un comentario.
- __Universal Sentence encoder:__ Modelo para realizar procesamiento de lenguaje natural.
- __Coco SSD:__ Detección de objetos.
- __Speech commands:__ Clasifica audios de un segundo dentro de una serie de palabras definidas.
- __KNN Classifier:__ Algoritmo de K vecinos mas cercanos para realizar transfer learning.
- __MobileNet:__ Clasificación de imagenes con las etiquetas de ImageNet.

Para este ejemplo, utilizaremos KNN Classifier y MobileNet para realizar un entrenamiento en el navegador.

```javascript
const classifier = knnClassifier.create();

async function app() {
  net = await mobilenet.load();
  await setupWebcam();

  document.getElementById('class-1').addEventListener('click', () => addExample(1));
  document.getElementById('class-2').addEventListener('click', () => addExample(2));
  document.getElementById('class-3').addEventListener('click', () => addExample(3));

  const addExample = classId => {
    const activation = net.infer(videoElement, 'conv_preds');
    classifier.addExample(activation, classId);
  }
}
```

A través de la cámara del dispositivo vamos a capturar imágenes, y cada que se presione un botón, se agregará una muestra de esa respectiva clase, con la cual se irá ajustando el modelo en tiempo real para realizar la predicción de esta misma.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/kq3ykt9s6rzpoba2z7fh.gif)

### Bibliotecas externas

La tercer forma que te quiero presentar es haciendo uso de alguna api o biblioteca externa, en este caso haremos uso de [FaceAPI](https://github.com/justadudewhohacks/face-api.js) una biblioteca para reconocimiento facial.

Importación de la biblioteca:

```html
<script defer src="face-api.min.js"></script>
```

Se cargan los modelos que vamos a utilizar:

```javascript
Promise.all([
  faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),
  faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL),
  faceapi.nets.faceRecognitionNet.loadFromUri(MODEL_URL),
  faceapi.nets.faceExpressionNet.loadFromUri(MODEL_URL),
]).then(setupWebcam);
```

Se realiza la detección y se dibujan los resultados en un elemento canvas en el navegador:

```javascript
function drawDetections(el) {
  const displaySize = faceapi.matchDimensions(canvas, videoElement, false);

  setInterval(async () => {
    const detections = await faceapi
                              .detectAllFaces(videoElement, new faceapi.TinyFaceDetectorOptions())
                              .withFaceLandmarks()
                              .withFaceExpressions();

    const resizedDetections = faceapi.resizeResults(detections, displaySize);

    canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);

    faceapi.draw.drawFaceExpressions(canvas, resizedDetections);
    faceapi.draw.drawFaceLandmarks(canvas, detections);
    faceapi.draw.drawDetections(canvas, detections);
  }, 100);
}
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dgq1cmznv00jal8qh5bm.gif)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6okb54kwqr6recbh2bzz.gif)

Puedes encontrar el código de los ejemplos realizados en este [repositorio de GitHub](https://github.com/ManuCastrillonM/cnn-in-the-browser)

## Recursos:

- https://www.tensorflow.org/js
- http://cs231n.github.io/
- https://www.tensorflow.org/guide/graphs
- https://www.tensorflow.org/js/models
- https://itnext.io/realtime-javascript-face-tracking-and-face-recognition-using-face-api-js-mtcnn-face-detector-d924dd8b5740