miércoles, 15 de septiembre de 2010

Ontologías con Jena

En esta entrada veremos, mediante ejemplos muy simples, cómo podemos gestionar nuestra ontología con Jena. Los puntos que abordaremos serán: 1. Crear una ontología desde cero con la API de Jena, 2. Leer datos de una ontología, 3. Escribir datos en la ontología y 4.Cargar una ontología previamente creada.
Usaremos la versión 2.6.4 de Jena, disponible en su web oficial.
Empecemos…

Conceptos iniciales

Durante esta entrada vamos a manejar una serie de conceptos, que creo que es conveniente describir un poco, para que nadie se pierda y pueda seguir el hilo fácilmente.
En primer lugar usaremos el término “Clase“. Una Clase será un conjunto de conceptos relacionados jerarquicamente que nos permitirá identificar y clasificar elementos de un dominio de forma concisa y concreta. Podemos pensar en las clases de una ontología como las clases de un diseño de POO (Programación Orientada a Objetos). La gran diferencia es que nuetra ontología tendrá un significado semántico que no tendrá nuestro diseño de clase de POO. Por supuesto una clase tendrá subclases, superclases, clases hermanas, …, todo lo relacionado con la herencia. Toda ontología tiene siempre una clase base de la que parten todas las demás. (owl:Thing)
Usaremos el término “Instancia“. Una instancia será una representación concreta de una clase. Una instancia quedará identificada y definida por la clase a la que pertenezca y por toda la jerarquía de clase que haya por encima. Además, una instancia definirá valores concretos para todas las propiedades que tenga la clase a la que pertenece.
Aquí entra en juego otro término. “Propiedades“. Las propiedades son la forma de asignar más contenido a una clase. Por tanto, las propiedades se definen a nivel de clase. Hay tres tipos básico de propiedades: 1. Propiedades de objeto: Su valor es un objeto (Instancia) de la ontología, 2. Propiedades de Datos: Su valor es un dato de tipo primitivo (int, float, string,…) y 3. Propiedades de Anotación: Sirven para escribir anotaciones y descripciones en lenguaje natural.
No vamos a ver ningún tipo de restricción lógica en esta entrada, ya que el objetivo es mostrar una breve introducción a Jena y establecer algunos conceptos más básicos y útiles. Bueno, creo que con estos conceptos en mente, podemos empezar a ver algo de código. Usaremos el lenguaje Java.

1. Crear una ontología

Lo primero para crear una ontología es documentarnos mucho y a fondo, sobre el dominio en el que vamos a movernos. Las ontologías deben ser lo más detalladas posible, no debe haber lugar a duda. Debemos ser capaces de identificar inequívocamente un elemento (Instancia) en cualquier momento. No debe haber posibilidad de confusión a la hora de obtener información de una instancia.
Nosotros, en esta entrada, vamos a trabajar con una ontología muy sencilla y muy simple, que nos servirá a modo de ejemplo. Nuestro dominio serán los Animales. Dividiremos a los animales en dos grupos: Vertebrados e Invertebrados. Por tanto, tendremos una clase base que será Animales y dos subclases que serán Vertebrados e Invertebrados. La clase Animales, será una clase abstracta, no podrá tener instancia directas. De esta forma “obligamos” a que cualquier animal que forme parte de nuestra jerarquía sea Vertebrado o Invertebrado. Por supuesto Vertebrado e Invertebrado serán clases disjuntas: los animales que pertenezca a una, no podrán pertenecer a la otra.

La clase Animales tendrá una propiedad de tipo int, que será Peso, por tanto sus subclases, heredarán esta propiedad. Además crearemos cuatro instancias: Pulpo y Sepia (Invertebrados) y León y Leopardo (Vertebrados).
Es un ejemplo muy sencillo, hecho a modo de inicio, sin mayores pretensiones.
//Creamos nuestro modelo
OntModel model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM);

//Establecemos el NameSpace por defecto para nuestra ontología
String NS = "animales";
model.setNsPrefix(NS, "http://www.owl-ontologies.com/OntologyAnimals.owl");
Ya tenemos nuestro modelo vacío y listo para contener una ontología en lenguaje OWL y almacenarla en memoria principal. Lo siguiente será comenzar a crear nuestras clases, propiedades e instancias. El NameSpace es necesario para obtener una URI absoluta de cada elemento de la ontología. La dirección http que aparece en el ejemplo es inventada.
Vamos a crear las clases, las propiedades y las instancias:
//Clases. Hay que crearlas con el NameSpace definido
OntClass animales = model.createClass(NS+":"+"Animales");
OntClass vertebrados = model.createClass(NS+":"+"Vertebrados");
OntClass invertebrados = model.createClass(NS+":"+"Invertebrados");

vertebrados.setDisjointWith(invertebrados);
animales.addSubClass(vertebrados);
animales.addSubClass(invertebrados);

//Creamos la propiedad peso
DatatypeProperty peso = model.createDatatypeProperty(NS+":"+"Peso");
peso.addDomain(animales);//Clase a la que pertenece
peso.addRange(XSD.xint);//Tipo de la propiedad
peso.convertToFunctionalProperty();//Para que solo acepte un valor.

//Creamos las instancias y damos valor a la propiedad Peso
Individual leon = model.createIndividual(NS+":"+"Leon",vertebrados);
leon.setPropertyValue(peso, model.createTypedLiteral(250));

Individual leopardo = model.createIndividual(NS+":"+"Leopardo",vertebrados);
leopardo.setPropertyValue(peso, model.createTypedLiteral(200));

Individual pulpo = model.createIndividual(NS+":"+"Pulpo",invertebrados);
pulpo.setPropertyValue(peso, model.createTypedLiteral(10));

Individual sepia = model.createIndividual(NS+":"+"Sepia",invertebrados);
sepia.setPropertyValue(peso, model.createTypedLiteral(1));

//Almacenamos la ontología en un fichero OWL (Opcional)
File file = new File("C:\\Animales.owl");
//Hay que capturar las Excepciones
if (!file.exists()){
     file.createNewFile();
}
model.write(new PrintWriter(file));

2. Leer datos de la ontología

Llegados a este punto, ya tenemos nuestra ontología de Animales creada y almacenada en un fichero OWL en nuestro disco duro. Lo siguiente que vamos a hacer es recorrer las clases que tiene nuestra ontología y mostrar las instancias que tiene cada clase. Para ello haremos uso de los Iteradores.
//Recorremos la ontologia
for (Iterator<OntClass> i = model.listClasses();i.hasNext();){
 OntClass cls = i.next();
 System.out.print(cls.getLocalName()+": ");
 for(Iterator it = cls.listInstances(true);it.hasNext();){
  Individual ind = (Individual)it.next();
  if(ind.isIndividual()){
   System.out.print(ind.getLocalName()+" ");
  }
 }
 System.out.println();
}

3. Escribir datos en la ontología

Vamos a ver cómo podemos cambiar el valor de la propiedad peso para cualquier instancia. Si queremos añadir nuevas instancias o nuevas propiedades, simplemente tendremos que seguir los pasos del punto 1.
Para cambiar el valor de la propiedad “Peso” de cualquier instancia, simplemente debemos hacer lo siguiente:
//Obtenemos la instancia y la propiedad y le damos un nuevo valor
model.getIndividual(NS+":"+"Leon")
              .setPropertyValue(model.getProperty(NS+":"+"Peso"),
                                           model.createTypedLiteral(255));

4. Cargar una ontología previamente creada

Para cargar una ontología que tengamos previamente almacenada en el disco duro (en un fichero OWL), debemos simplemente utilizar el método read() de la clase OntModel. A este método debemos pasarle por parámetro una URI absoluta y bien construida que contenga la ruta al fichero en disco. Bueno, en realidad tenemos que pasarle un String que contenga una URI absoluta.
Simplemente sería:
OntModel model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM);
model.read("file:C:/Animales.owl","RDF/XML");
Y listo, a trabajar con nuestra ontología.

Más info

Jena | Web Oficial
API Jena | http://jena.sourceforge.net/javadoc/index.html