IBM Watson y su inteligencia artificial

IBM Watson, ¿que es?

Según wikipedia:

Watson es un sistema informático de inteligencia artificial que es capaz de responder a preguntas formuladas en lenguaje natural.​ Esta desarrollado por la corporación estadounidense IBM y forma parte del proyecto del equipo de investigación DeepQA, liderado por el investigador principal David Ferruci. Lleva su nombre en honor del fundador y primer presidente de IBM, Thomas J. Watson.

Efectivamente, más inteligencia artificial, y ademas es una de las 7 grandes empresas que junto a Google(Alphabet), Apple, Sentient Technologies, Facebook, Microsoft y Amazon luchan por tener la más rápida y potente inteligencia artificial del mundo.

Gracias a la lucha de estas empresas, hoy tenemos acceso publico y gratuito (casi gratuito) a casi toda su potencia y/o casi todos sus servicios.

Muy bonito, ¿Por donde comienzo?

Para acceder a los servicios que ofrece IBM haremos clic aquí y nos registramos para tener acceso a nuestro panel de control.

Una vez finalizado el proceso de registro accederemos a nuestro dashboard y desde allí iniciaremos nuestro producto/servicio de IBM Cloud

La siguiente pantalla sera nuestro panel de control y es donde gestionamos todos los servicios, recursos, etc…, que ofrece IBM. La primera vez que entras no se mostrara ningún recurso ya que acabamos de iniciar y todavía no hemos creado nada.

Para continuar necesitamos Crear un recurso, así que vamos a pulsar sobre Crear recurso

En la siguiente pantalla podemos ver que IBM tiene todo tipo de servicios, pero nosotros vamos a centrarnos en la parte de inteligencia artificial: Watson

Cuando seleccionemos Watson se mostraran los diferentes servicios que IBM tiene relacionado con la inteligencia artificial, pero para este ejemplo vamos a elegir el servicio de reconocimiento visual ( visual recognition )

En la siguiente pantalla  aparecerá toda la información sobre el servicio, limitaciones, versiones beta, etc…

Pulsando en el botón Crear  finalizaremos el proceso.

Para entrar en profundidad y conocer todos los detalles sobre cualquier servicio, IBM ofrece una guía y la documentación API de como usar cada servicio, dentro del propio servicio.

Ahora con nuestro servicio y el api_key creado solo queda empezar a escribir un poco de código.

Probando el servicio de forma simple (solo local)

¿¡¡Pero!!?, ¿porque solo local?. Pues la respuesta es muy sencilla, el primer ejemplo es tan simple que no requiere de programación, solo es necesario realizar una llamada al API desde un formulario en HTML, y salvo que quieras que tu api_key la conozca todo el mundo, no te recomiendo esta opción.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Imagen 0</title>
</head>

<body>
    <form method="POST" enctype="multipart/form-data" action="https://gateway-a.watsonplatform.net/visual-recognition/api/v3/classify?api_key={api_key}&version=2018-03-19">
        <label>Imagen</label>
        <input type="file" name="imagen">
        <input type="submit" value="Clasificar imagen">
    </form>
    <form method="POST" enctype="multipart/form-data" action="https://gateway-a.watsonplatform.net/visual-recognition/api/v3/detect_faces?api_key={api_key}&version=2018-03-19">
        <label>Imagen</label>
        <input type="file" name="imagen">
        <input type="submit" value="Faces">
    </form>
</body>

</html>

{api_key} -> cambiar por vuestra api_key.

Y con esto ya tenemos un sistema de clasificación de objetos y reconocimiento facial dentro de una imagen, CHAS!!!!.

Para este ejemplo he puesto un vídeo para comprobar como funciona el servicio y el respuesta que nos devuelve, porque como decía antes, las api keys están en el propio código.

Ahora si!!, usando Node para realizar la consulta

Para probar el API de reconocimiento visual de IBM he creado una página que comprueba el fichero a subir.

Cuando abrimos la página desde un ordenador se abre el explorador de ficheros para seleccionar una imagen, pero si se usa el móvil nos permite usar también la cámara del móvil.

Si todo es correcto nos mostrara en la página la imagen que vamos a enviar para su clasificación.

Cuando pulsamos sobre el botón clasificar fotografía se realizara una petición ajax a un servidor creado en Node que sera el encargado de subir el fichero y enviarlo al API de reconocimiento visual de IBM.

Si todo el proceso es correcto tendríamos una respuesta en JSON con toda la información que ha podido extraer y clasificar el servicio de reconocimiento visual de IBM.

Finalmente desde la página web tramitaremos esa respuesta y mostrar los resultados.

Aunque voy a poner todo el código en la web siempre me gusta dejar un repositorio de todo lo que hago, así que desde aquí accedes al repositorio y en este enlace puedes probar la aplicación.

A continuación todo el código del proyecto:

[Página estática que se carga desde Node]

<!DOCTYPE html>
<html lang="es">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>CLASIFICADOR WATSON</title>
    <!--<link href="style.css" rel="stylesheet">-->
    <link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet">
    <style>
        body {
            font-family: 'Montserrat', sans-serif;
        }
        
        #resultado div {
            width: 100%;
            float: left;
        }
        
        .clasificacion b {
            text-transform: capitalize;
        }
        
        .clasificacion ul {
            list-style: none;
            padding: 0;
        }
        
        .loader {
            border: 10px solid #f3f3f3;
            border-radius: 50%;
            border-top: 10px solid #3498db;
            width: 60px;
            height: 60px;
            -webkit-animation: spin 2s linear infinite;
            animation: spin 2s linear infinite;
            position: absolute;
            left: calc(50% - 30px);
            top: calc(50% - 30px);
            display: none;
        }
        
        @-webkit-keyframes spin {
            0% {
                -webkit-transform: rotate(0deg);
            }
            100% {
                -webkit-transform: rotate(360deg);
            }
        }
        
        @keyframes spin {
            0% {
                transform: rotate(0deg);
            }
            100% {
                transform: rotate(360deg);
            }
        }
        
        input {
            width: 100%;
            max-width: 300px;
            box-shadow: 1px 0px 10px rgba(0, 0, 0, 0.3);
            margin: 5px;
        }
    </style>
</head>

<body>
    <main>
        <form>
            <input type="file" name="photo" accept="image/*"><br>
            <input type="submit" value="Clasificar fotografía" disabled><br>
        </form>
        <div id="resultado">
            <div class="imagen"><img></div>
            <div class="clasificacion"></div>
        </div>
        <div class="loader"></div>
    </main>
    <script>
        var WATSON = WATSON || {};

        WATSON = {
            showLoading: function(status) {
                document.querySelector(".loader").style.display = status ? "block" : "none";
            },
            ajax: function(url, evt) {
                return new Promise(function(resolve, reject) {
                    var request = new XMLHttpRequest();
                    request.open('POST', url, true);
                    request.onreadystatechange = function(aEvt) {
                        if (request.readyState == 4) {
                            var html = "";
                            if (request.status == 200) {
                                var responseJSON = JSON.parse(request.responseText);
                                html += "<h3>Imagenes procesadas: " + responseJSON.images_processed + "</h3>";
                                var clasi = responseJSON.images[0]["classifiers"][0]["classes"];
                                clasi.sort(function(a, b) {
                                    return b.score - a.score;
                                });
                                for (var propiedad in clasi) {
                                    if (clasi.hasOwnProperty(propiedad)) {
                                        html += "<ul>";
                                        typeof clasi[propiedad].class !== "undefined" ? html += "<li><b>" + clasi[propiedad].class + "</b></li>" : html += "";
                                        typeof clasi[propiedad].score !== "undefined" ? html += "<li><progress value='" + (clasi[propiedad].score * 100) + "' max='100'></progress> <b>" + clasi[propiedad].score + "</b></li>" : html += "";
                                        //typeof clasi[propiedad].type_hierarchy !== "undefined" ? html += "<b>" + clasi[propiedad].type_hierarchy + "</b><br>" : html += "<br>";
                                        html += "</ul>";
                                    }
                                }
                                resolve(html);
                            } else {
                                html += "Error loading page";
                                reject(html);

                            }
                        }
                    };
                    request.send(new FormData(evt.target));
                });
            }
        };
        window.onload = function() {
            var form = document.querySelector('form');
            var submit = document.querySelector("input[type='file']");
            form.addEventListener('submit', function(e) {
                e.preventDefault();
                WATSON.showLoading(true);
                WATSON.ajax('https://tecnops.es:17202/uploadImage', e).then(function(success) {
                    WATSON.showLoading(false);
                    document.querySelector(".clasificacion").innerHTML = success;
                }).catch(function(err) {
                    WATSON.showLoading(false);
                    document.querySelector(".clasificacion").innerHTML = err;
                });
            });

            submit.addEventListener("change", function(evt) {
                var files = evt.target.files;
                for (var data in files) {
                    if (files.hasOwnProperty(data) && files[data].type.match("image.*")) {
                        var reader = new FileReader();
                        reader.onload = (function(theFile) {
                            return function(e) {
                                var img = document.querySelector("img");
                                img.src = e.target.result;
                                img.title = escape(theFile.name);
                                img.classList.add("show");
                                document.querySelector("input[type='submit']").removeAttribute("disabled");
                            };
                        })(files[data]);
                        reader.readAsDataURL(files[data]);
                    }
                }
            }, false);
        };
    </script>
</body>

</html>

Para que funcione el API de IBM de reconocimiento visual en el servidor de Node necesitaremos instalar la librería de acceso a los servicios de IBM Watson.

Como este ejemplo esta creado en Node usaremos NPM: npm install –save watson-developer-cloud

No obstante toda la documentación de uso viene especificada en cada servicio y con pequeños ejemplos de uso. Desde aquí puedes acceder a todo el API de reconocimiento visual.

[Servidor Node que carga la página estática y realiza la llamada a el servicio de IBM]

// Dependencias para usar IBM Watson
var VisualRecognitionV3 = require('watson-developer-cloud/visual-recognition/v3');

// Crear servidor en Node
var http = require('http');

// Obtener rutas
var ruta = require("path");

// Modulo de parseo de los datos de un formulario, incluyendo la subida de ficheros (multipart/form-data).
var formidable = require('formidable');

// Sistema de ficheros
var fs = require('fs');

// Puerto para el servidor
var port = process.env.PORT || 17202;

// Creamos el servidor
http.createServer(function(req, res) {
    if (req.url == "/") {
        fs.readFile('index.html', function(error, contenido_archivo) {
            if (error) {
                res.writeHead(500, 'text/plain');
                res.end('Error interno.');
            } else {
                res.writeHead(200, { 'Content-Type': 'text/html' });
                res.end(contenido_archivo);
            }
        });
    } else if (req.url == "/uploadImage") {

        // Creamos un nuevo formulario de entrada
        var form = new formidable.IncomingForm();

        // Analiza una solicitud entrante de node.js que contiene datos de formulario (Enviando archivos y campos)
        form.parse(req, function(err, fields, files) {
            // Error
            if (err) throw err;

            // API DE IBM Watson
            // 1 - Se autentica en la API de reconocimiento visual al proporcionar la clave API para la instancia de servicio que desea utilizar. 
            var visualRecognition = new VisualRecognitionV3({
                version: '2018-03-19',
                iam_apikey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
            });

            // La función fs.createReadStream () le permite abrir una secuencia legible de una manera muy simple. Todo lo que tiene que hacer es pasar la ruta del archivo para comenzar a transmitir
            var images_file = fs.createReadStream(files.photo.path);

            // La puntuación mínima que una clase debe tener para mostrarse en la respuesta 0.0 => ignora la clasificación y devuelve todo
            var threshold = 0.0;

            // Parametros (documentacion muy bien explicada)
            var params = { images_file: images_file, threshold: threshold, accept_language: "es" };

            // Comienza la clasificación
            visualRecognition.classify(params, function(err, response) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify(response));
            });
        });
    } else {
        var codigo_html = '';
        res.writeHead(200, 'text/html');
        res.end(codigo_html);
    }
}).listen(port);
console.log(`Servidor funcionando en el puerto ${port}`);

Con esta pequeña introducción y la gran documentación que ofrece IBM espero que te animes a probar todo lo que IBM pone a nuestra disposición.

ACTUALIZACIÓN [07/03/2019]

Gracias al comentario de Antonio indicando que tenia problemas para ejecutar el proyecto, he realizado algunos cambios en el código ya que IBM Watson ha cambiado la clave api_key por iam_apikey.

Para comprobar que es correcta la solución, ademas de un vídeo con el proyecto en funcionamiento también he actualizado el repositorio de github con los cambios, he añadido el package.json con las dependencias necesarias y he modificado el código de los gist de este artículo con los últimos cambios.