Uso de memoización para mejorar el rendimiento

Memoization, o memoización en español, es una técnica que permite ahorrar tiempo y recursos en el caso de que tengamos funciones cuyo tiempo de ejecución sea muy largo y se le llame varias veces con los mismos parámetros.

La idea es muy sencilla. En cada llamada a la función realizamos los siguientes pasos:

  1. Con los parámetros calculamos una clave única
  2. Comprobamos si ya tenemos un resultado asociado a esa clave
  3. Si lo tenemos devolvemos ese valor.
  4. Si no lo tenemos llamamos a la función
  5. Almacenamos el resultado de la función asociados a la clave calculada

La idea es calcular una clave única a partir de los parámetros que se pasan a la función y almacenar el resultado de la primera vez que se le llama para luego las las siguientes veces poder devolverlo sin calcularlo recuperándolo de la memoria.

Hay que ser consiente de que esta técnica permite una ventaja: reducir el tiempo de ejecución y pero con dos penalizaciones: la primera es que la mejora solo se consigue a partir de la segunda vez que se llama a la función, la primera vez se incrementa el coste puesto que se añade el tiempo de cálculo de la clave y de almacenar el resultado en memoria. La segunda penalización es que requiere espacio en memoria para guardar los resultados.

Esto solo funciona si la función cumple ciertas condiciones:

  • El resultado de la función depende única y exclusivamente de los parámetros que se le pasan.
  • Siempre devuelve el mismo resultado para los mismos parámetros.
  • La única funcionalidad que realiza la función es calcular el resultado.
  • Se llama varias veces a la función con los mismo parámetros.
  • El coste de calculo del resultado es mayor que el coste de calcular la clave y buscar el resultado almacenado en memoria.
  • Se intenta optimizar el tiempo de ejecución sobre el uso de memoria.

Ejemplo de implementación

Vamos a ver como implementar una clase JS para aplicar memoization.El código completo se puede encontrar en su repositorio de github.

La memoization tiene dos elementos principales, la función sobre la que se va a aplicar y como se calcula clave a partir de los argumentos que se pasan a esta función. En el constructor de la función vamos requerir estos dos argumentos, si bien el segundo tiene un valor por defecto. También es necesario inicializar la cache donde se guardaran los resultados.

    constructor (func, keyFunc) {
        this.func = func;
        this.cache = {}; 
        this.calculateKey = keyFunc || this.calculateKey;
    }

Vamos a ver un ejemplo de función que calcula la clave para almacenar el resultado. En la versión por defecto lo que se hace es generar un array con todos los parámetros y convertirlos a un cadena de texto JSON.

    calculateKey(){
        let args = Array.from(arguments);
        return JSON.stringify(args);
    }

Tenemos, la función y la forma de calcular la clave. Nos queda como gestionamos la cache. Los pasos son:

  1. Calcular la clave
  2. Recuperar el valor asociado a esa clave
  3. Si el valor existe se devuelve
  4. Si no existe se calcula
    call(){ 
        let key = this.calculateKey(...arguments);
        if(this.isInCache(key)){
            return this.cache[key];
        } else {
            let result = this.func(...arguments);
            this.add(key, result);
            return result;           
        }
    }

El código completo:

class Memo {
    constructor (func, keyFunc) {
        this.func = func;
        this.cache = {}; 
        this.calculateKey = keyFunc || this.calculateKey;
    }

    call(){ 
        let key = this.calculateKey(...arguments);
        if(this.isInCache(key)){
            return this.cache[key];
        } else {
            let result = this.func(...arguments);
            this.add(key, result);
            return result;           
        }
    }

    calculateKey(){
        let args = Array.from(arguments);
        return JSON.stringify(args);
    }

    isInCache(key){
        return key in this.cache;
    }

    add(key, value){
        this.cache[key] = value;
    }
}

Estrategias para generar la clave de la cache

Hay casos en que varias configuraciones distintas de parámetros son equivalentes. Cuando estos casos se conocen a priori, sin necesidad de calcular el resultado, una mejora puede ser cambiar la función que genera la clave para que tenga en cuenta esta particularidad. Principalmente hay dos estrategias para hacerlo: que la clave que se genera sea la misma para todos estos casos o generar todas las claves que se sabe que tienen el mismo resultado y añadirlas a la cache. Veamos algunos ejemplos.

Propiedad conmutativa: Hay casos en que puede intercambiar el valor de algunos parámetros sin que afecte al resultado. En estos casos una buena optimización es una vez calculado uno de estos casos se aplique a todos los que son equivalentes.

Por ejemplo si la función sum(a,b) realiza la suma de a y b. El resultado será el mismo aunque intercambien valores. sum(2,5) = sum(5,2). Podríamos generar ambas claves “[2,5]” y “[5,2]” y asignarles el mismo resultado.

Otra forma de hacerlo seria ordenar los parámetros de menor a mayor así sum(2,5) y sum(5,2) generarían la misma clave: “[2,5]”.

Aproximaciones de números con decimales: En muchos casos la diferencia entre dos números decimales es tan pequeña que prácticamente no va a afectar al resultado. Para evitar calcular un resultado cuando no es necesario se puede calcular la clave limitando el numero de decimales.

Por ejemplo si fijamos que número máximo de decimales sean 2, tanto sum(3.141592 , 2) como sum(3.1416 , 2) generarían la misma clave “[3.14,2]”

Cadenas de texto: Otro de los casos habituales es que haya cadenas de texto que son equivalentes. Un caso habitual son las mayúsculas y minúsculas, en muchos casos no influye en el resultado. En un caso así search(“juan”) y search(“Juan”) deberían de generar las dos la misma clave: “[juan]”

Datos estructurados: Un caso especial es el de los argumentos que son alguna estructura de datos y que pueden tener muchas formas equivalentes. Por ejemplo, si uno de los parámetros que recibe es un JSON con dos valores “a” y “b” estas dos formas serian la misma: {“a”:1,”b”:2} y {“b”:2,”a”:1}. Llevando el ejemplo más lejos, si la funciona solo va mirar los campos “a” y “b” el caso {“a”:1,”b”:2,”c”:3} seria idéntico. Otro caso son los arrays cuando el orden de los elementos dentro de él da igual. Estos casos son mucho más difíciles de tratar ya que no se puede poner una regla que siempre sea válida y habrá que buscar soluciones propias para cada caso.

Como ejemplo podemos ver un par de ejemplos de como calcular la clave para la cache.

El primero ordena los argumentos que se le pasan;

function calculateKeySorted(){
    let args = Array.from(arguments).sort();    
    return JSON.stringify(args);
}

El segundo convierte los argumentos a minúsculas:

function calculateKeyToLowerCase(){
    let args = Array.from(arguments);    
    return JSON.stringify(args).toLowerCase();
}

Comparar pantallas para encontrar errores visuales durante los test del software

Uno de los problemas de los test automáticos para probar aplicaciones es que no se guían por el aspecto visual de la aplicación. Puedes tener un botón torcido, un texto que no se lee o un color que no corresponde y los test serán correctos, sin embargo para el usuario es importante que el botón que tiene que pulsar sea visible o que pueda leer ese texto ilegible.

Es una tarea difícil de automatizar. Programar código para comprobar la correcta disposición de todos los elementos es algo muy costoso. Y tener una IA que detecte posible elementos erróneos puede sonar muy interesante pero es aun más costoso y complicado. Así que vamos a optar por una solución más sencilla, comparar una imagen que sabemos que esta bien con una captura de pantalla de la aplicación durante las pruebas. Luego calcularemos las diferencias entre ambas imágenes.

Hay múltiples librerías para realizar la comparación de la imagen como pixelmatch o Resemble.js. Aunque estas librerías tienen bastante funciones el algoritmo básico de comparación es muy sencillo. Se comparan uno a uno los pixel de la imagen de referencia con los de la imagen capturada, se fija un valor de umbral. Si la diferencia entre ambos pixel es mayor que el valor umbral se considera que ese pixel es distinto y se marca.

Para que funcione bien la imagen que se usa como modelo ha de ser idéntica a la que se obtiene de la captura de pantalla. Podéis estar pensando que siempre se puede reescalar una de la dos imágenes pero generalmente eso mete “ruido” en los bordes de los elementos de la imagen que el algoritmo de comparación detecta como diferencias.

Los pasos a segir son los siguientes:

  1. Captura de pantalla
  2. Cargar imagen modelo correspondiente
  3. Crear una tercera imagen comparando los pixeles de las dos anteriores. Cada pixel cuya diferencia supere cierto valor fijado se marcara como “diferente”, por ejemplo poniéndolo en rojo.
  4. Buscar si existe alguna diferencia en la imagen resultado de la comparación
  5. Si existe alguna diferencia se deja la imagen con el resultado de la comparación y se avisa al usuario para que la revise.

El principal problemas son los datos que cambian cada vez que se realiza el test. Por ejemplo: ids de elementos, fechas, horas, elementos generados en parte o completamente por procesos (pseudo)aleatorios. Estos elementos dejan “una mancha” en el resultado de la comparación. Una solución para evitar estas manchas es “taparlas” para ello vamos a usar plantillas con una zona de un color especial que cuando el algoritmo las lee ignora.

Otra técnica para ignorar “pequeñas cantidades de error” es dividir la imagen en cuadrados, por ejemplo de 16×16. En cada cuadrado hay un total de 256 pixeles, solo lanzaremos una advertencia si la cantidad de pixeles marcados como diferentes supera cierto número. Por ejemplo el 20%, o lo que es lo mismo 51 pixeles. Con esto logramos que solo diferencias “notables” se consideren.

Este es un sistema realmente simple para ayudar a resolver el problema de comprobar el aspecto visual de la aplicación o la web durante los test. Si bien no resuelve todo el problema y sigue siendo necesario un humano para verificar las imágenes marcadas como errores. Agiliza mucho el proceso de comprobar el correcto aspecto visual de la aplicación o web.

Simular la curva del olvido

Ya hemos visto como hacer algoritmos que “olvidan” con el tiempo, es decir que daban mayor relevancia a los valores recientes que a los más antiguos. Ahora vamos a fijarnos en un modelo de como el cerebro olvida para intentar simularlo. Es un modelo bastante simplificado, proviene de distintos experimentos prácticos basándose en los realizados por Hermann Ebbinghaus estos experimentos parten de memorizar una lista de palabras o silabas sin relación entre ellas. Para lograr que fuera un ejercicio puro de memoria los primeros experimentos usaban listas de silabas inventadas. El sujeto memorizaba la lista y a intervalos de tiempo se pedía recordar todas las que pudiera y se iba contando las que olvidaba.

De estos experimentos se han sacado varias formulas que tratan de aproximar el comportamiento del olvido. Actualmente se acepta como una buena aproximación la formula:

R = e (-t/S)
  • R es el porcentaje de la palabras que se recuerda
  • e es, pues eso, el número e
  • t es el tiempo
  • S la intensidad del recuerdo
e ^-t/S En azul S = 0.5 En verde S = 1

Esta ecuación es la misma que ya vimos en el post sobre algoritmos que olvidan. Solo que allí la formula tenia otra forma:

R = e -kt

En este caso k = 1/S . Esto le confiere propiedades muy útiles que luego veremos.

Modelo Básico

Vamos a empezar por simular el caso más sencillo. Un listado de sílabas aleatorias como el del experimento. En este caso el parámetro S es para todas el mismo. Y todas tiene la misma probabilidad de ser olvidadas en el momento T.

Para saber si una palabra se sigue recordando o no en el momento T se calcula un número al azar entre 0 y 1. Si el número es mayor que el valor de e ^ -t/S esa palabra se olvida. Si es menor se recuerda. Solo queda repetir este proceso por cada palabra del documento.

Mejorando el modelo

Lo que ocurre es que pocas veces en la vida hemos de memorizar un texto de esas características. Lo normal es que sea un texto con distintos tipos de palabras. Veamos como modificamos el parámetro S de cada palabra.

Las palabras vacías o stopwords que son palabras que no aportan nada al significado del texto deberían de tener una S muy baja para que se olviden rápidamente. Para este punto necesitaremos un listado de palabras vacías.

Los nombres propios, fechas, lugares y números incrementan el valor de su S para que duren más tiempo. Este punto requiere la extracción de nombres, entidades, fechas y números. Para ello se pueden combinar distintos algoritmos con diccionarios.

Cada vez que una palabra repita en el texto se incrementa un poco el valor de su S. Salvo con las palabras vacías que no incrementan nada su valor.

Las palabras propias del tema del texto incrementan su valor de S un poco. Es decir, si el texto es sobre medicina las palabras relacionadas con la medicina se recuerdan más fácilmente. Esta parte requiere diccionarios de términos de cada tema.

El caso más complicado es el de las palabras nuevas o desconocidas que deberían tener una S menor de la que les corresponde. La mitad, por ejemplo. Este punto es difícil de simular en un ordenador, podemos hacerlo dotándole de un listado de palabras conocidas.

Este algoritmo produce como resultado un texto que pasado cierto tiempo va olvidando de forma aleatoria distintas partes con preferencia por recordar los datos del texto más relevantes y relacionados con la materia del mismo y el conocimiento previo del lector.

Simulando el aprendizaje por repetición

Una de las conclusiones que se sacaron de estos estudios es que mientras no se ha olvidado el texto el volver a memorizarlo cuesta menos tiempo, es lógico pues aún recordamos parte de él y que cada vez su recuerdo permanece más tiempo.

Esto se podría simular haciendo que cada vez que sea “lea” el texto el valor de la S calculado en el apartado anterior se aumente en un 40% o 50%. Sn = S * (1+(0.5*n)) siendo n el número de relecturas del texto.

Raspberry Pi, desactivar conexiones para ahorrar energía

Vamos a ver como poder modificar las distintas conexiones que posee una Raspberry Pi para ahorrar energía. Nos vamos a entrar en

Hay que pensarlo bien antes de usar una política de activar/deasctivar las conexiones. El restablecerlas tiene un coste enérgico por lo que estar todo el rato conectando y desconectando puede aumentar el consumo en lugar reducirlo.

Todos estos comandos se deben de ejecutar con permisos de administrador por lo que puede ser necesario usar sudo delante de ellos.

Desactivar y activar bluetooth

Desactivar bluetooth:

rfkill block bluetooth

Activar bluetooth:

rfkill unblock bluetooth

Desactivar y activar WiFi

Desactivar WiFi:

rfkill block wifi

Activar WiFi

rfkill unblock wifi

Al activarlo habrá que volver a conectarse a la red.

Conexión ethernet

En este apartado vamos a ver como de desactiva el software a nivel de sistema operativo y no como desactivar la alimentación que veremos en el apartado siguiente.

Desactivar conexión ethernet:

ifconfig eth0 down

Activar conexión ethernet:

ifconfig eth0 up

Desactivar puertos USB (y ethernet)

Los puertos USB y ehernet tienne alimentación compartida si desactivamos los USB el puerto ethernet “cae con ellos”.

Desactivar puertos USB:

echo '1-1' |sudo tee /sys/bus/usb/drivers/usb/unbind

Activar puertos USB:

echo '1-1' |sudo tee /sys/bus/usb/drivers/usb/bind

Raspberry Pi programar el encendido, apagado y reinicio

Vamos a ver como podemos configurar la Raspberry Pi para que se encienda, se apague y se reinicie cuando nosotros queramos. El reinicio y el apagado son sencillos. Para el encendido necesitaremos soluciones más imaginativas.

Para programar el apagado y reinicio usaremos el servicio cron para programar tareas. Ya vimos como funcionaba en esta entrada. Ahora solo hace falta ver que comandos tenemos que programar.

Para reiniciar la Raspberry podemos usar:

reboot

Y para apagarla:

shutdown -h now

Ambos necesitan usarse con permisos de administrador por lo que es recomendable programarlos en el crontab del usuario root o de cualquiera que tenga permisos de administrador y precederlos del comando sudo.

Ahora vemos como resolvemos programar el encendido. No hay un comando que encienda al Raspberry Pi tras haberla apagado. Para ello necesitáramos un RTC con una batería que se quedase en espera para encender la Raspberry, por suerte debido a como funciona la Raspberry eso es muy sencillo. Si tienes la Raspberry sin alimentación y se la pones se enciende, no hay ningún interruptor que cambiar de on a off ni nada parecido. Aprovechando eso basta con usar un enchufe con temporizador, que se pueden comprar muy baratos. Esos enchufes llevan un reloj en el que ajustas que horas van a tener luz y que horas no.

Por ejemplo, supongamos que queremos tener una Raspberry de tal forma que se apague por la madrugada cuando no vas a hacer uso de ella, por ejemplo a la 1:00 y que se encienda a las 8:00. Lo primero es ajustar el enchufe, lo programamos para que corte la alimentación pasadas la 1:00 pero dejando un margen, la 1:15, la 1:30, sin apurar. Y para que a las 8:00 (o algo antes) vuelta a conectar la alimentación.

El último paso es configurar una tarea de cron para que lance el comando de apagar la Raspberry a la 1:00. Para evitar que cuando se corte la corriente se apague la Raspberry mal hay que dejar un margen de tiempo desde que está programado lanzar el comando hasta que el enchufe corte la corriente.

Raspberry Pi programar tareas con cron

Cron es un servicio que se usa para lanzar tareas programadas. Su configuración se realiza a través de un fichero de configuración donde se definen las tareas programadas.

Cada usuario tiene su propio fichero de configuración, este se edita con el comando:

crontab -e

Cada linea del fichero es una tarea programada. Una tarea progrmada tiene 6 parámetros (excepto si empieza por @ que solo tiene 2), los cinco primeros definen en momento cuando se lanza el comando y el sexto el comando que se lanza:

  1. Minuto en el que se ejecutará el comando, va de 0 a 59.
  2. Hora en la que se ejecutará el comando, va de 0 a 23.
  3. Día del mes en el que se ejecutará el comando, va del 1 al 31.
  4. Mes en el que se ejecutará el comando, va del 1 al 12.
  5. Día de la semana en el que se ejecutará el comando, va de 0 a 7.
  6. Comando que se ejecutará

Se pueden usar los siguientes modificadores:

  • * : cualquier valor de ese campo
  • , : define listas de valores. 1,3,5
  • – : define rangos de valores. 1-5 seria lo mismo que 1,2,3,4,5
  • / : en combinación con un rango o el asterisco define “cada cuanto”. 1-5/2 seria lo mismo que 1,3,5
  • # : Se usa en el campo día de la semana, indica que semana del mes. 1#2 el segundo lunes del mes
  • W : Se usa en el campo día de la semana, indica el primero del mes. 1W el primer lunes del mes
  • L : Se usa en el campo día de la semana, indica el último del mes. 1L el último lunes del mes
  • @ : Es un atajo para indicar momentos concretos
    • @reboot una vez al inicio del sistema
    • @yearly una vez al año “0 0 1 1 *”
    • @annually una vez al año “0 0 1 1 *”
    • @monthly una vez al mes “0 0 1 * *”
    • @weekly una vez a la semana “0 0 * * 0″
    • @daily una vez cada dia “0 0 * * *”
    • @hourly una vez cada hora “0 * * * *”

Algunos ejemplos:

30 22 * * 1-5 reboot

Reiniciar de lunes a viernes a las 22:30 (las diez de la noche)

30 10-14/2 * * * reboot

Reiniciar a las 10:30, 12:30 y 14:30 cada día

15 */6 * * * reboot

Reiniciar cada 6 horas cuando sean las y cuarto de esa hora

@hourly reboot

Reiniciar cada hora

30 10 * * 0W reboot

Reiniciar el primer domingo de cada mes

30 10 * * 7W reboot

Reiniciar el primer domingo de cada mes (el 0 y el 7 indican domingo)

Jugando con los led de la Raspberry Pi

En la place de la Raspberry Pi hay dos leds (hay mas si contamos los del conector ethernet pero esos son otra historia). Uno verde conocido como ACT LED o led0 que se enciende cuando accede a la tarjeta SD y otro rojo conocido como PWR LED o led1 que se enciende cuando tiene alimentación. Usando comandos de Raspberry Pi OS tenemos cierto control sobre ellos.

Apagar y encender los leds

Hay varias maneras, en el siguiente apartado veremos otra, pero esta es recomendable puesto que los apaga si, afectar a su funcionamiento.

Apagar ACT LED

echo 0 | sudo tee /sys/class/leds/led0/brightness

Volver a encender ACT LED

echo 1 | sudo tee /sys/class/leds/led0/brightness

Apagar PWR LED

echo 0 | sudo tee /sys/class/leds/led1/brightness

Volver a encender PWD LED

echo 1 | sudo tee /sys/class/leds/led1/brightness

En la Raspberry Pi Zero los valores son al revés, 1 para apagar, 0 para encender

Cambiar función de los led

Podemos asignar distintas funciones a cada uno de los leds jugando con el trigger que controla su comportamiento.

El led ACT LED parpadea cada segundo:

echo timer | sudo tee /sys/class/leds/led0/trigger

El led PWR LED parpadea una sola vez

echo oneshot | sudo tee /sys/class/leds/led1/trigger

La diferencia esta en si en al ruta de /sys/class/leds/led0/trigger si se usa led0 o led1. Hay diferentes valores que se pueden usar:

none                Apagado
kbd-scrolllock      Keyboard bloq despl/scroll lock
kbd-numlock         Teclado bloq num/num lock
kbd-capslock        Keyboard bloq mayus/caps lock
kbd-kanalock        Keyboard kana lock
kbd-shiftlock       Tecla shift/mayus
kbd-altgrlock       Tecla altgr
kbd-ctrllock        Tecla ctrl
kbd-altlock         Tecla alt
kbd-shiftllock      Tecla shift/mayus izquierda
kbd-shiftrlock      Tecla shift/mayus derecha
kbd-ctrlllock       Tecla ctrl izquierda
kbd-ctrlrlock       Tecla ctrl derecha
timer               Parpadea cada segundo
oneshot             Parpadea una sola vez
heartbeat           Parpadea como un latido(1-0-1-00000)
backlight           Encendido
gpio                Permite controlarlo desde GPIO
cpu0                Parpadea cuando se usa la cpu0
cpu1                Parpadea cuando se usa la cpu0
cpu2                Parpadea cuando se usa la cpu0
cpu3                Parpadea cuando se usa la cpu0
default-on          Encendido
[input]             Encendido si el voltaje es suficiente
panic               Parpadea cuando hay un kernel panic
mmc0                Parpadea al acceder a la SD
mmc1                Parpadea al acceder a la SD secundaria
rfkill0             Parpadea con la actividad del WiFi
rfkill1             Parpadea con la actividad del bluetooth

Los eventos de teclado no funcionan con las conexiones en remoto, solo con los teclados directamente conectados a la Raspberry.

Los valores por defecto del led1 es [input] y el del led1 mmc0

Los valores none y default-on permiten apagar y encender el led, pero es más recomendable usar el método visto en el apartado anterior por no afecta a la funcionalidad del led, en cambio con none y default-on el led pierde su funcionalidad original para estar encendido siempre.

Como apagar la salida HDMI en la Raspberry Pi

Si tienes conectada una pantalla a tu Raspberry puede ser interesante apagarla cuando no sea necesaria, para ahorrar energía, para no molestar con su luz, para que dure más,….

Mi consejo es realizar las pruebas de todo los que vamos a ver en este post desde otro ordenador conectándonos a la Raspberry por SSH. No hay que olvidar que el primer paso es apagar la pantalla por lo que toda la parte de volver a encenderla tendrás que hacerla a ciegas.

El modo sencillo vcgencmd

Si esta forma te funciona enhorabuena, juegas en modo fácil. Eso no quiere decir que hayas ganado la partida. Ademas es la forma recomendada por la documentación de Raspberry Pi.

vcgencmd es una utilidad de Raspberry Pi OS para leer y fijar algunos valores de la controladores de vídeo de la Raspberry

Apagar la pantalla:

vcgencmd display_power 0

Volver a encender la pantalla:

vcgencmd display_power 1

Ahora bien a partir de la Raspberry Pi 4 llevan dos salidas HDMI puedes controlarlas por separado añadiendo un 2 para la salida HDMI 0 y un 7 para la salida HDMI 1.

Apagar y encender la salidad HDMI 0:

vcgencmd display_power 0 2
vcgencmd display_power 1 2

Apagar y encender la salidad HDMI 1:

vcgencmd display_power 0 7
vcgencmd display_power 1 7

El modo difícil tvservice

Si el modo fácil no funciona vamos al siguiente nivel.

tvservice permite controlar el servicio de video de Raspberry.

Apagar la pantalla:

tvservice -o

Ahora viene lo difícil, volver a encenderla. El problema suele ser que se vuelve a encender pero no con la configuración adecuada. Mi consejo es ir probando uno por uno los siguientes comandos:

tvservice -p
fbset -depth 8
fbset -depth 16
fbset -depth 32
xrefresh

Algunas pantallas volverán a funcionar correctamente con el primer comando, otras necesitaran solo hasta el tercero y otras necesitaran todos.

El caso más extremo reboot

Generalmente con alguno de los casos anteriores se logra apagar la pantalla y el problema suele ser volver a encenderla. Hay una solución que siempre funciona pero es tan radical que no siempre puede aplicarse. Reiniciar la Raspberry Pi con el comando:

reboot

Al reiniciarse la pantalla vuelve a funcionar con normalidad. Depende el uso que se le esté dando puede ser una solución que causa más problemas de los que soluciona.

Y aún así puede que no sirva de nada

Todo esto puede que no sirva para nada dependiendo de como se comporte la pantalla. Lo ideal es que al desconectar el puerto HDMI la pantalla se apague, pero el mundo no es un lugar perfecto y algunas pantallas en lugar de apagare muestran un mensaje estilo “No Signal”, realmente es un mensaje útil cuando no sabes si lo que falla es la pantalla, el cable o el puerto HDMI, pero tras un rato en pantalla el monitor podría apagarse sin problemas. Al parecer no todos los fabricantes piensan así y la pantalla se queda encendida con lo que no logras nada.

Otro caso que da problemas son las pantalla que llevan iluminación trasera, esta puede seguir encendida aunque la pantalla se apague. Aunque en este caso si que reducimos el consumo energético.

También hay que tener ene cuanta las pantallas táctiles. Algunas siguen “leyendo” las pulsaciones en ellas aunque no vamos donde estamos pulsando.

Simular ideologías con autómatas celulares

Antes que nada y para evitar problemas aquí vamos tratar con dos ideologías ficticias y opuestas. Los profilobostios y los antifilobostios. Efectivamente no existe algo así como un filobostio es una palabra inventada. Así me evito problemas de gente que pone la ideología por delante del sentido común. Cualquier parecido con la realidad es pura coincidencia….o no.

Antes de continuar, todos los ejemplos vistos en este texto se pueden probar en esta demo en github, con el código fuente disponible.

Usaremos autómatas celulares bidimensionale para modelar la población. Cada celda será un individuo y su estado será la ideología que defiende. En cada iteración un individuo evalúa a sus vecinos y sus propia ideología y adapta su creencia. Vamos a tener en cuenta las siguientes normas:

  • Cada individuo solo tiene información de su entorno. No tiene un conocimiento extenso y completo. No por ello deja de posicionarse  dentro de la ideología y de defender su posición respecto a sus entorno.
  • Un individuo solo puede adoptar una ideología que esté en su entorno. Es decir, solo puede adoptar ideologías con las que tenga contacto y por lo tanto conozca.
  • Un individuo tiene preferencia por elegir la ideología mayoritaria en su entorno. Un individuo tiene más probabilidades de elegir la ideología de la que más información a favor posea.
  • El individuo tiene en cuenta su propia ideología

Con estas reglas se intenta simular el hecho de que un individuo generalmente no tiene información completa si no que su posición ideológica se conforma por su entorno que comprende tanto las personas con las que se relaciona como los medios con los que se informa. .

Ideología

Vamos a ver dos formas de representar la ideología.

Simple: hay dos posibles estados, rojo y azul, sin grados y sin termino medio. Define ideologías en la que estas en un bando o estas en el otro.

Extendida: la ideología cada individuo puede tener 17 estados de -8 a 8. Siendo cualquier valor mayor que 0 azul, profilobostios, cualquier valor menor de 0 rojo, antifilobostio, y si es exactamente 0 de color negro, neutro. El valor absoluto representa “lo extrema” que es la creencia de un individuo en esa ideología. Está representación permite que individuos con ideologías opuestas y cercanas al neutro sean más cercanos entre ellos que con los extremistas de su propia ideología. Un detalle importante es que los individuos neutros también influyen en los demás. En ese aspecto la neutralidad sería una posición ideológica activa y que actuaría como una tercera postura del individuo más que como una ausencia de ideología.

Mecanismos de decisión

Existen dos mecanismos de decisión.

Aleatoria: el individuo elige al azar una ideología entre sus vecinos y la suya propia. Este mecanismo respeta la idea de que es más probable que elijas la ideología que es mayoritaria en tu entorno. Solo que no siempre es así. Esta forma de elección permite simular la “el carisma”. Una persona se deja convencer por aquella ideología que más le atrae sin ponderarla de forma meditada.

Media: el individuo analiza el valor de sus vecinos y el propio y elige la creencia más cercana a la media de todas las creencias. En el caso de la ideología simple, en lugar de calcular la media elige aquella ideología con más representantes.

Obstinados

Un elemento extra en estos modelos son individuos que no cambian de opinión, son defensores acérrimos de su ideología e ignora todo lo demás.

Ideología simple con selección aleatoria

Este es el caso más sencillo de entender. Representaría una población completamente polarizada y que se deja llevar por lo “atractivo” que sea el mensaje de una ideología. Cuando se ejecuta se puede observar que hay mucho movimiento, la gente cambia de ideología rápidamente y hay veces que alguna parece dominar de forma aplastante sobre otra para que luego todo cambie rápidamente. Un hecho importante es que tras un periodo de tiempo largo siempre hay una que se impone y la otra desaparece. En este caso ambas ideologías son “igual de atractivas” así que termina ganando una de las dos al azar. Lo que si que es cierto es que el proceso se refuerza, contra más extendida este una ideología más fácil es que crezca.

Puede parecer que esto es malo, pero realmente hay ideologías que son tan atractivas y útiles que es difícil que no se extiendan como por ejemplo “todos somos iguales”, “robar está mal”. Permite tener una base ideológica estable.

Una única ideología se impone

Ideología simple con selección aleatoria y obstinados

En el caso que estamos simulando conde ambas ideologías son igual de atractivas los obstinados impiden que la ninguna ideología se imponga actuando como fuente de nuevos partidarios de su ideología.

En esta situación el poder de los obstinados depende de lo atractiva que sea la ideología que defienden. Si defienden una ideología poco atractiva se quedan atrapados llegando a un caso similar al de “Ideología simple con selección media y obstinados” que veremos más adelante. Sin embargo si defienden una ideología muy atractiva esta puede llegar a extenderse y dominar.

Los obstinados impiden que una única ideología se imponga

Ideología simple con selección media

En este caso la población se divide en grupos que comparten ideología. En estos grupos un individuo se ve rodeado de un entorno estable que reafirma su ideología.

La población se divide en grupos ideológicos cerrados

Ideología simple con selección media y obstinados

Los obstinados no producen ningún efecto, al ser una decisión que tiene en cuenta a toda al vecindad su influencia es muy reducida y muchos de ellos acaban “atrapados” con vecindades que no comparten su ideología y a la que no tienen ninguna probabilidad de convencer.

La población se divide en grupos ideológicos cerrados con unos pocos obstinados aislados

Ideología extendida con selección aleatoria

El comportamiento aquí es muy parecido al de “Ideología simple con selección aleatoria” pero con múltiples ideologías. Lo curioso de esta situación es que cada “grado” en la ideología actúa como si fuera un bando contrario a todos los demás, se convierte en un todos contra todos de 17 bandos hasta que al final uno se impone a los demás.

Cada grado de creencia se comporta como un bando diferente, al final uno se impone

Ideología extendida con selección aleatoria y obstinados

Los obstinados impiden que la población colapse en un único valor manteniendo la competición entre ideologías. Al igual que en el caso con ideología simple el poder de los obstinados depende de lo “atractiva” que sea su ideología.

Los obstinados impiden que una única ideología se imponga

Ideología extendida con selección media

En este caso la ejecución rápidamente converge a una población donde se crean grupos ideológicos estables separados por una frontera de neutrales. Si nos fijamos en el color la mayoría de los individuos son cercanos a la neutralidad. La única excepción son pequeños que se crean dentro de los grupos más grandes cuyo color es más intenso.

La neutralidad y los grupos ideológicos muy cercanos a la misma predominan. Se pueden formar pequeños grupos más alejados de la neutralidad (arriba a la derecha el circulo rojo)

Ideología extendida con selección media y obstinados

Si al caso anterior añadimos unos pocos obstinados ocurre un cambio sorprendente. La población se radicaliza. Desaparecen los neutrales y los colores se vuelven más intenso.

Con un 10% de obstinados la neutralidad se reduce y la población de polariza
Con un 20% de obstinados la neutralidad casi desaparece y los individuos se alejan de ella

Conclusiones

Obstinación contra neutralidad

En el caso de las decisiones ponderadas con ideología extendida vemos que los obstinados actúan aumentando la polarización de la población. De hecho en el caso de la ideología simple donde la polarización es máxima los obstinados no tienen efecto. Promover la aparición de estimados puede servir ambas ideologías y así reducir “el peligro de los neutrales”

Para entender este peligro vamos a incluir un nuevo elemento, una especie de tendencia global en toda la población, está tendencia se puede deber a muchas causas: modas, noticias, decisiones políticas o errores.

Para simular esta tendencia basta con sumar o restar (según la tendencia beneficie a profilobostios o antifilobostios) a cada individuo. Tras está operación los ciudadanos neutros o cercanos a la neutralidad adoptan una posición ideológica.

Es decir en un mundo moderado un suceso externo puede fácilmente inclinar la balanza hacia uno de los lados.

Si fuéramos responsables de cualquiera de las dos ideologías nos interesaría eliminar este riesgo y la mejor forma para ambos bandos es promover la aparición de obstinados. Esto les protege de los neutrales y sus cambios de opinión, cuanto más cerca este un individuo de un extremo ideológico más difícil es que cambie de opinión. Como ambos bandos se benefician de este hecho su mejor movimiento es que ambos colaboren para lograr esta situación.

Selección arbitraria e ideología única

En los casos en que la ideología no se elige de forma ponderada si no de forma arbitraria una única ideología termina imponiéndose a las demás, el tiempo que esto tarde en ocurrir dependerá del tamaño de la población y de lo “persuasiva” (probabilidad de ser elegida) que sea cada ideología y del azar. Es importante saber que una ideología no tiene porque ser superior a otra, el simple azar la convierte en ganadora.

En este modelo cada pequeña diferencia ideológica se convierte en un bando aislado que lucha contra los demás hasta que se impone. No basta con que el tablero este azul o rojo, tiene que estar en un solo tono de este color.

Sin embargo es muy sensible a los obtusos que impiden que el sistema converja en una solo ideología manteniendo el enfrentamiento activo.

La dificultad de usar algoritmos para controlar a las personas

Siempre que se habla de medidas de control de la población usando herramientas tecnológicas se centra el tema en el derecho a la privacidad y la libertad individual. Pero aquí vamos a ver porque es difícil que ese tipo de control funcione tan bien como prometen. Nos vamos a centrar en el mundo laboral que me parece el más neutral.

Trabajo como programador y una de los grandes retos de la industria es medir nuestro desempeño. No es que no existan técnicas para medirlo es que no funcionan. Los jefes piensan que sí y están felices con sus datos. Si hay algo que he aprendido es que mucha gente se siente más segura con un dato de fiabilidad cuestionable que sin datos. Sin embargo como si de un fenómeno cuántico se tratara el hecho de medir el rendimiento de la gente afecta a lo medido. Que muchas veces es lo que se desea, se instala un sistema para vigilar a los trabajadores, no tanto por los datos como para que el trabajador se sienta vigilado y trabaje más. Sin embargo el resultado puede ser el contrario, menos trabajo y de peor calidad. Los trabajadores se centran en puntuar bien aunque eso suponga empeorar su trabajo. Y no se debe a que el trabajador solo busca trabajar lo menos posible, si no que, como a todos, le gusta que le reconozcan su trabajo, cobrar más sueldo y ser ascendido. Y va a invertir su tiempo y esfuerzo en aquellas tareas que le den más posibilidades de conseguir alguno de esos tres objetivos.

Cuando hablamos de engañar a estos sistemas suena como algo complicado, necesitarias grandes conocimientos del algoritmos, matematicas y miles de pruebas para ver como se comporta. Los propios creadores de estos sitemas te podrán decir que ellos no sabrian como engañarlos. ¿Qué posibilidades tiene una persona normal?. Muy pocas, pero cuando el sistema trata con cientos o miles de personas la cosa cambia. Una persona puede encontrar de forma accidental un comportamiento que le da una ventaja frente a la evaluación. Los seres humanos somos muy buenos adaptandonos y percibiendo patrones, es algo que hacemos sin darnos cuenta. Tambien somos buenos adoptando comportamientos de grupo, muchas veces sin ser conscientes de ello. Si algo parece funcionar lo adoptamos. Así que cualquier comportamiento beneficioso va a extenderse entre la población.

Estos sistemas se enfrentan a un reto que es difícil de resolver hasta para los humanos: Evaluar de forma objetiva a una persona en un entorno real. Para ello tienen que trabajar con métrica y simplificaciones que no siempre son fieles a la realidad. Por lo que es casi seguro que habrá errores de clasificación, buenos trabajadores que son clasificados como malos y viceversa. Incluso puede que el error sea más sutil, un trabajador puede descubrir que es mejor valorado si hace las cosas de una determinada manera que si las hace de otra. La primera manera puede ser más correcta desde el punto de vista profesional pero el trabajador va a optar por la que mayor recompensa/reconocimiento le de. Además este proceso se realimenta puesto que los trabajadores que no hagan las cosas así tendrán menos éxito en esa empresa y será más probable que sean despedidos, se vayan o adopten la otra forma.

No hace falta que haya una búsqueda consciente de los puntos débiles de algoritmo, el sistema funciona por prueba y error. Se parece mucho a las metaheurísticas basadas en población. Muchos agentes probando (aun sin querer) estrategias diferentes y los que encuentran una estrategia que mejora su valoración son beneficiados, estas estrategias se extienden por el resto de los agentes mientras que los que no las imitan reciben peor puntuación, esto crea un ciclo de mejora y propagación constante.

Una solución que puede parecer muy sencilla es que cada persona sea evaluar por los demás algo del estilo “poner estrellitas”, el problema de esto es que entonces el trabajador intentara “optimizar” la valoración del cliente. Puede dejar al cliente muy contento pero no realizar un trabajo satisfactorio. Por ejemplo puede dedicar demasiado tiempo atenderle a costa de realizar menos ventas o evitar atender a clientes “complicados”. Incluso puede derivar en una guerra entre compañeros para “quitarse de encima esos clientes”.

Otro problema que se da en este tipo de evaluaciones es “la inversión del control”. Se da cuando en una jerarquía con este tipo de control alguien de un puesto superior puede perder mas que alguien de un puesto inferior si este no realiza su trabajo. De tal forma que el puesto inferior puede llegar a forzar al superior. También puede darse el caso de que el “castigo” por no realizar su trabajo se reparta entre los puesto inferiores suponiendo menor castigo a nivel personal que en los puestos superiores. Esa es la base de las huelgas, cada uno de los trabajadores pierde el sueldo de un día, pero la empresa pierde la producción de un día. Esto puede ser un gran problema para la empresa si le supone incumplir contratos, perder clientes o tener que pagar luego horas extra para recuperar la producción perdida.

Otro elemento difícil de eliminar de estos sistemas son los prejuicios. El sistema cree ciegamente sus resultados, ni se plantea que pueda equivocarse, ni tiene ne cuenta excepciones, ni el contexto. Tampoco hay que olvidar que toda aproximación estadística comete un error de sesgo al juzgar a una sola persona. Los datos te pueden decir que el 20% de los trabajadores que visten con pantalones azules tienen bajo rendimiento pero cuando aplicas eso a una sola persona vestida con pantalones azules estas siendo injusto y quizás descartando a un buen trabajador por otro peor.

No son necesarios sistemas muy complicados para que surjan estos comportamientos. Haces años trabajé programando una herramienta interna de gestión de proyectos. Una de sus principales funciones era ayudar a automatizar los cálculos de los costes de cada proyecto y el punto del mismo. De eso dependía la parte variable del sueldo de los jefes de proyecto. El sistema era especialmente propenso a las trampas ya que todos los jefes tenían acceso a los datos y podían ver claramente los motivos de su evaluación. Además la evaluación se realizaba en un momento concreto de la semana, lo que te daba tiempo a “arreglar los datos” por último era muy fácil retocar datos sin que se notase y si te pillaban achacarlo a un error,el riesgo si te pillaban era bajo pero el beneficio en comparación era alto. Esto dio lugar a todo tipo de manipulaciones y puñaladas entre los jefes de proyectos. El ambiente no era el mejor, pero los objetivos artificiales del sistema se cumplían. El sistema en si no era nada especialmente complicado básicamente calculaba los costes y los asignaba a cada proyecto según el uso de los recursos que hacia. Sin embargo pronto me toco ir modificando el código para tener en cuenta “casos especiales”.

En resumen, a veces parece que ante el control automático solo hay dos caminos: la sumisión o la rebelión. Pero lo cierto es que el tercer camino la manipulación se nos da bastante mejor. Es más difícil controlarnos de lo que parece.Aun así existe el peligro de que estos sistemas inciten comportamientos no deseados como consecuencia de su uso.