Showing posts with label paradigmas en programación. Show all posts
Showing posts with label paradigmas en programación. Show all posts

Thursday, December 07, 2017

¿El fin del ajedrez?




En 1997 Garry Kasparov perdía un match a seis partidas contra una máquina preparada sólo para jugar al ajedrez. IBM había puesto mucho dinero y su plan era demostrar que una máquina ya tenía la suficiente capacidad para derrotar al mejor jugador del mundo en este difícil arte del ajedrez. Pero 20 años después nos enteramos que DeepMind había puesto un programa llamado AlphaGo, a jugar contra el mejor jugador del juego chino Go, y que le había derrotado sin duda ninguna. Hoy DeepMind nos da la noticia que su programa, aplicado al ajedrez, logró en sólo cuatro horas de auto entrenamiento, el nivel de súper gran maestro y para ello ha usado su técnica de redes neuronales de aprendizaje reforzado. AlphaGo ha logrado entender más de 500 años de experiencia en ajedrez en tan sólo unas horas. Algo inconcebible e impresionante.

El algoritmo desarrollado por Google y DeepMind, sintetiza todo el conocimiento del ajedrez y para demostrar este nivel, los investigadores decidieron poner como rival a StockFish, el mejor programa de ajedrez de código abierto, que está entre los tres mejores programas (incluyendo los comerciales), del mundo.

AlphaGo venció a Stockfish en un encuentro a 100 partidas, por 28-0 y 72 empates. Es decir, el poderoso StockFish no pudo ganar una sola partida. Esto solamente habla de la capacidad del algoritmo de DeepMind, el cual ni siquiera necesita bases de partidas, tablas de finales o podas alpha-beta para correr.



El artículo “Mastering Chess and Shogi by Self-Play with a General Reinforcement Learning Algorithm” describe el trabajo realizado y sin duda es un parteaguas en el mundo del ajedrez por computadora. El enfoque es tan diferente a todo lo anterior hecho en esta disciplina que habrá que estudiar cómo es que este algoritmo de Google y DeepMind se está volviendo una de las maneras más eficientes para atacar problemas que no tienen solución definitiva y que se han atacado antes a través de heurísticas

Monday, November 16, 2015

Para quien quiera programar en Prolog


Prolog (PROgramming in LOGic) es un lenguaje funcional, declarativo, que a diferencia de los lenguajes de programación imperativos, en este caso lo que hace es describir el problema y Prolog, a través de su mecanismo de inferencia (implementado por Robinson en 1968), da los resultados a la problemática definida. Parece magia en algún sentido porque ¿cómo puede ser que un programa mecánico llegue a una conclusión en base a inferencias? Pues no lo es tanto. Prolog usa hechgos y reglas para llegar a conclusiones. Por ejemplo, podemos definir los siguientes hechos:

padre(juan,manuel).
padre(pedro,manuel).

En este caso leemos "manuel es el padre de juan" y "manuel es el padre de pedro".

¿Cómo podríamos hacer la inferencia evidente de que son hermanos? Muy fácil, creando la siguiente regla:

hermanos(X,Y) :- padre(X,Z), padre(Y,Z), X=\=Y.

Lo cual se lee: X y Y son hermanos SI el padre de X es Z, el padre de Y es también Z y X no es Y. 

Tenemos que aclarar esto último (X no es Y), pues sino, el programa reportaría que juan es hermano de sí mismo o que pedro es hermano de sí mismo, lo cual lógicamente no tiene sentido.

Y quizás estamos abreviando demasiado lo que puede hacer Prolog, pero la idea es ésa: poder hacer inferencias y llegar a resultados. Es interesante aclarar que en un lenguaje como estos, muchas veces caemos en el no determinismo, es decir, no podemos saber qué clase de respuestas entregará el programa y si éste entregará acaso alguna respuesta. Un ejemplo de esto puede verse en el problema que Bertrand Russell expresara: "en un pueblo existe un barbero, el cual rasura a todos aquellos que no se rasuran a sí mismos". Y la pregunta que hace Russell: "¿Quién es el que rasura al barbero?

Este tipo de problemas se puede expresar en Prolog, a pesar de que lógicamente no parece haber un resultado. Russell -de hecho- se inventa una teoría llamada "de tipos", en donde los conjuntos están perfectamente definidos. En esta teoría, el filósofo y matemático nos dice: "la pregunta no tiene sentido, es inválida".

Pues bien, el problema de Russell se puede expresar de la siguiente manera en Prolog:

rasura(X,Y) :- not(rasura(Y,Y)).

Esto se lee así: X rasura a Y SI Y no se rasura a sí mismo.

Como puede verse, es una regla recursiva, que se llama a sí misma. Si se ejecuta este programa en algún intérprete o compilador de Prolog, el resultado será simple: error por memoria insuficiente, overeflow, etcétera.

Quien tenga interés en desarrollar programas de esta naturaleza, o de entender mejor el paradigma funcional y declarativo, bien puede usar intérpretes y compiladores que son de código abierto y/o libres. Un ejemplo de ellos es SWI Prolog, el cual es una implementación muy cercana al Prolog estándar. que si mal no recuerdo, tiene ya más de 15 años de haberse propuesto.

SWI Prolog está documentado, hay ejemplos, tutoriales, comunidad de usuarios e incluso, se puede correr en el navegador. Todo esto la hace una herramienta estupenda por muchos motivos, aparte de gratuita, está muy bien cuidada. Échenle un ojo, de verdad me ha convencido.


Thursday, April 04, 2013

Paradigmas en programación... ¿verdad o mentira?


Los lenguajes de programación se basan en diferentes paradigmas. Unos son imperativos. Otros usan la lógica de predicados para funcionar (como Prolog), otros más han definido la programación orientada a objetos, con los subsecuentes beneficios que esto tiene en general para los desarrolladores. Por ello, parece ser necesario saber de qué se trata esto para poder decidir -en algún momento- cuál es el lenguaje a usar.

Curiosamente, como veremos más adelante, hay algo perturbador en esta idea de los paradigmas en los lenguajes de programación y la conclusión a la que llegaremos resulta en alguna medida, inesperada. Entremos pues en materia:

Consideremos las redes semántica, las cuales se representan comúnmente como gráficas consistiendo de nodos que se conectan a través de líneas. Los nodos representan objetos y los enlaces entre nodos representan las relaciones entre esos objetos.

Una red semántica simple puede verse en la siguiente imagen:



Nótese que los enlaces son flechas, lo cual implica una dirección. Así, el perro persigue al gato pero éste no persigue al primero. Esto podría ocurrir pero la red semántica no lo plantea explícitamente.

Las redes semánticas plantean una manera muy intuitiva de representar conocimiento sobre objetos y las relaciones entre ellos. Los datos de una red así se basan en un dominio en particular. Esto hace que el esquema sea limitado. Por ejemplo, no podemos establecer la regla de que "el perro no es un gato".

Cabe decir que muchas de las relaciones son evidentes por sí mismas, pero hacen uso del conocimiento externo que tenemos del mundo. En principio, deberíamos definir dichas relaciones para evitar ambigüedades.

Herencia

Es una relación que puede ser particular útil. La idea de la herencia se entiende fácilmente de forma intuitiva. Por ejemplo, decimos que los mamíferos dan a luz bebes vivos y podemos decir también que todos los perros son mamíferos por lo que podemos concluir que un pero da a luz mamíferos vivos. Sin embargo, no estamos diciendo nada sobre el sexo del perro y por ello la relación no necesariamente sería cierta.

La herencia nos permite especificar propiedades de una superclase y así poder definir una subclase, la cual hereda las propiedades de la superclase. En nuestro ejemplo los mamíferos son una superclase y los perros una subclase.

Frames (marcos)

Los marcos son un desarrollo de las redes semánticas y permiten expresar la idea de herencia. Un sistema de marcos consiste en un conjunto de marcos (o nodos) quienes están conectados por una relación. Cada marco describe una instancia (un marco instancia) o clase (marco clase).

En este contexto, los objetos que se representan son objetos físicos pero no necesariamente tienen que serlo. Un objeto puede ser una propiedad (un color, una forma), un sentimiento, un lugar, una situación. La idea de los objetos en este sentido es idéntica a los que se ven en la Programación Orientada a Objetos (POO).

Cada marco tiene una o más ranuras, también podrían llamárseles "propiedades, a las que se le asignan valores. Esta es la manera en que una red de marcos se construye. En lugar de simplemente tener enlaces entre marcos, cada relación se expresa con un valor en una de las ranuras.

Por ejemplo, cuando decimos que "Fido es un perro" lo que queremos decir es que "Fido es una instancia de la clase perro"  o bien, que Fido es un miembro de la clase perro". Aquí la relación "es un" (is-a) es muy importante en una representación de marcos porque nos permite expresar que alguien es miembro de una clase. Esto se conoce como una generalización porque referirse a la clase de mamíferos es más general que referirse a la clase de perros y referirse a la clase de perros es más general que referirse a Fido.

Es también útil hablar de un objeto que es parte de otro objeto.  Por ejemplo Fido tiene cola, por lo que la cola es parte de Fido. Esto se llama agregación porque Fido puede ser un agregado de las partes de un perro.

Otra relación es la asociación. Un ejemplo es la relación de "perseguir" Esto explica cómo un perro y un gato están relacionados o asociados uno con el otro.

¿Por qué usar marcos?

La ventaja principal de usar sistemas de marcos es que la información de un objeto se encuentra toda en un solo lugar (esto es clásico en el paradigma de la POO). Además, la herencia puede extenderse de la siguiente manera. Por ejemplo:

Los perros persiguen a los gatos
Los gatos persiguen a los ratones

Para expresar este tipo de información, no necesitamos saber que el perro persigue al gato o que éste último persigue a los ratones. Podemos heredar la información porque un gato en particular podría ser una instancia de la clase gatos y en nuestro ejemplo, Fido es una instancia de la clase perro.

Podemos además añadir la siguiente información:

  • Los mamíferos respiran
  • Los perros son mamíferos
  • Los gatos son mamíferos

Y tenemos una superclase, mamíferos, en donde los perros y gatos son subclases. Así, no necesitamos expresar explícitamente que los perros y gatos respiran porque se puede heredar esta información.

Herencia múltiple

Es posible en un marco heredar propiedades de otro marco. En otras palabras, una clase puede ser una subclase de dos superclases y el objeto puede tener una instancia de más de una clase. Por ejemplo, podemos decir que el hombre es un constructor de casas, y que además, es un mamífero. Esto es la herencia múltiple, pero en mi opinión, es absurda si se tiene un sistema con una jerarquía bien armada. A la fecha, de hecho, no he hallado un ejemplo de la necesidad de herencia múltiple para representar conocimiento.

Procedimientos

En la POO las clases (y de hecho los objetos), tienen métodos asociados a ellos. Esto también ocurre con los marcos. Estos tienen métodos asociados a ellos llamados procedimientos.

Un procedimiento es un conjunto de instrucciones asociados al marco que puede ser ejecutado a petición. Por ejemplo, un procedimiento lector de las propiedades puede regresar un valor particular dentro del marco. Otro procedimiento puede insertar un valor en una propiedad. Un procedimiento importante es el constructor de una instancia, el cual crea la instancia de una clase.

Y si pensamos en esto, una red semántica dirigida (los marcos, pues), son un equivalente a la programación orientada a objetos, en donde se definen las propiedades de los mismos, los métodos que pueden usar y la herencia. Solamente quedaría duda de cómo usar el polimorfismo, es decir, cuándo se debe usar un procedimiento de un objeto u otro. Evidentemente esto se puede hacer y entonces, cualquier lenguaje basado en el paradigma de la POO puede ser usado para escribir programas " inteligentes". Es más, hay una clara equivalencia entre los marcos y la programación basada en la lógica de predicados y por ende, si podemos expresar un marco en la POO o bien, en la lógica de predicados, entonces en el fondo no hay un cambio de paradigma. Lo que parece ser que realmente tenemos es un esquema ded representación de conocimiento que no distingue entre paradigmas, porque en el fondo todos los lenguajes que expresan y representan conocimiento, hacen lo mismo.

Este resultado me parece asombroso en cierta medida. Así pues, no hay necesidad de elegir un lenguaje de programación que use un paradigma u otro, cualquiera puede usarse para representar conocimiento de la misma manera.

Friday, November 02, 2012

La programación en el siglo 21


La programación -decía algún gracioso- es de esas pocas cosas divertidas que se pueden hacer con los pantalones puestos. Y la realidad es que para quienes programamos (y en ocasiones vivimos de esto), el hacer que la computadora se comporte como queramos, que dé los resultados deseados, que haga los cálculos que necesitamos, es algo que de entrada es satisfactorio, porque de alguna manera nos damos cuenta del cómo tener que explicarle a una máquina lo que queremos que haga.

Evidentemente con los años las cosas en programación han cambiado notablemente. Quizás una de los primeros ambientes RAD (Rapid Application Development) fue Visual Basic, de Microsoft, pero pronto salieron otros entornos igual de atractivos para lenguajes mucho más poderosos que el relativamente simple Basic. Por ejemplo, Borland presentó Delphi 1.0 en 1994 y a decir de Philippe Kahn, el creador de dicha empresa, no buscaban competir contra Visual Basic, sino contra PowerBuilder (Sybase), el cual aún existe.

La idea atrás de RADs es crear entornos de programación donde los desarrolladores tengan a la mano una serie de componentes comunes: botones, barras de progreso, editores de textos, menúes, listas de objetos, imágenes, etcétera, de forma tal que puedan ponerse en la aplicación usando drag & drop, es decir, tomando el componente y soltándolo en la ventana del programa. Cada componente tiene una serie de propiedades y métodos, lo cual cumple estrictamente -en la mayoría de los casos- con el paradigma de la programación orientada a objetos (POO), la cual se utiliza ya comúnmente en cualquier entorno moderno. La gracia de esto es que el desarrollador solamente tiene que asignar qué tipo de acción va a ocurrir cuando se le dé click, por ejemplo, a un botón. Así, no tenemos que armar de cero la aplicación. Basta con poner los componentes que necesitamos y programar los eventos de los mismos.

Evidentemente cualquier RAD moderno tiene mejores habilidades, por ejemplo, la de crear componentes que hagan tareas muy específicas, basándose muchas veces en las bibliotecas y componentes ya existentes. Para la mayoría de los medios ambientes de programación modernos es fácil hallar muchos componentes de terceros para un sinfín de aplicaciones en particular. Por ejemplo, yo he hallado un tablero de ajedrez con una serie de facilidades que permite crear programas asociados con las actividades del ajedrez sin necesidad de tenerme que sentar a escribir un componente propio que valide las jugadas en el tablero, que anime los movimientos de las piezas en la pantalla, etcétera. Vamos, que ya viene todo el componente con esa funcionalidad que evita perder mucho tiempo rehaciendo una y otra vez lo mismo.

Lo mejor del asunto es que este paradigma que RAD ha impuesto por las facilidades que da a los programadores, ya existe para los dispositivos móviles. La mayoría de ellos no tienen entornos de programación. Es decir, no programamos directamente sobre el teléfono celular o la tablet, sino que usamos una herramienta en la máquina de escritorio (PC, Mac, Linux), que nos permite hacer el desarrollo deseado, usando un emulador para probar la aplicación que está siendo escrita (o mejorada). De nuevo, estos entornos de programación para los dispositivos móviles contienen una serie de componentes ya pre-establecidos que permiten desarrollar rápidamente aplicaciones, además de darle a los programas un medio gráfico similar en todo el sistema en el que se está programando (iOS es el mejor ejemplo de esto).


Hay incluso empresas que hacen entornos para programar en iOS, Android o Windows Phone bajo este paradigma. Casualmente, la idea de los RADs ha permeado incluso en medios ambientes que bien podríamos considerar obsoletos. Veo PocketStudio, un ambiente muy parecido a Delphi (se programa en Pascal), para crear apps para la Palm, el cual es comercial. Hay alternativas como Palmphi, que pretende ser un entorno como Delphi, pero cuyo lenguaje nativo es C. Para PocketPC hay también muchas herramientas de desarrollo que incluso siguen vigentes: Pocket Programming Language, que usa su propio lenguaje (el cual no sé a qué se parece), NSBasic, el cual ya puede usarse incluso en tablets y teléfonos inteligentes en iOS y Android. Así, la miriada de posibilidades permite a los programadores elegir la mejor opción para cada quien.

Aparte de los entornos típicos (como el que da Google para programar para Android) o el que da Apple para iOS. Es cuestión de buscar y empezar a entender las características específicas de los entornos que queremos usar. Si usted se dedica a programar, debe considerar que el futuro será -y por un buen tiempo- los dispositivos móviles. Hay un gran margen de alternativas y posibilidades para quienes buscan vivir de crear código.