Publicidad:
La Coctelera

un rato de sol

i will work harder, ja ja ja, no ahora en serio.

Categoría: semantic web

8 Enero 2009

Creando individuos en la ABox: validaciones en una semántica de mundo abierto

Más cosas que voy implementando en mi biblioteca OWL para Clojure.

En otro post, mostré como se podía definir la TBox, con un procedimiento similar al de usar migraciones para definir el esquema de una base de datos relacional. Ahora vamos mostrar lo que sería el equivalente ontológico a insertar filas en las tablas del esquema relacional, es decir a insertar aserciones en la ABox.

Creamos una TBox de prueba:

 (describe-tbox
   (describe-owl-class :test "Person")
   (describe-owl-datatype-property :test "name" (xsd-string))
   (describe-owl-datatype-property :test "age" (xsd-decimal))
   (describe-owl-class-has-property :test "Person" :test "name")
   (describe-owl-class-has-property :test "Person" :test "age"))
 

Una vez guardada la TBox en un repositorio e iniciada (con alias :test-Person, :test-name y :test-age para los recursos), podremos crear individuos en la ABox que sean tipos de las clases OWL contenidas en la TBox, para ello usamos la función (abox-create-individual)

 (abox-create-individual :test-Person
                         "http://test.com/abox/people#"
                         { :test-name "Helena",
                           :test-age 24 })
 

Esta función crea un nuevo individuo con URI "http://test.com/abox/people#IDENTIFIER", donde IDENTIFIER es un identificador universal único (UUID) que se genera con la llamada a la función. La función inserta los tripletes RDF necesarios para asertar que el individuo recién creado es del tipo :test-Person, así como de todas sus superclases (en este caso owl:Thing). Además da valores a las propiedaded test:name y test:age.

Los tripletes RDF equivalentes serían (con notación Turtle):

@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
 @prefix test: <http://test.com/tbox/people#> .
 @prefix owl: <http://www.w3.org/2002/07/owl#> .
 @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
 
 <"http://test.com/abox/people#IDENTIFIER"> rdf:type test:Person ;
                                            rdf:type owl:Thing ; 
                                            test:age "24"^^xsd:decimal ;
                                            test:name "Helena"^^xsd:string .
 

A la hora de crear individuos en la ABox, hay que tener en cuenta que se pueden crear individuos con valores sólo para algunas propiedades, o con propiedades no definidas en la clase OWL o ni siquiera en la TBox. Una de las características de las lógicas descriptivas, la base formal de OWL, es que tienen una semántica denominada de "mundo abierto" (ver el "Cambridge Handbook of DL"). Siguiendo una semántica "open world", si una información no se ha asertado en la ontología, no quiere decir que no exista, sino que todavía no se conoce. Lo mismo sucede para la información extra. Es interesante comparar esta semántica con una semántica de "mundo cerrado", como la que siguen las bases de datos relacionales, donde si un dato no está en la tabla, significa un error, no desconocimiento. Esta situación se agrava cuando contamos con pocos constructores OWL y no hay soporte para un razonador, si no que se trabaja solo con las aserciones explícitas de la ontología.

Una forma de poner restricciones a lo que insertamos en la ABox, sin recurrir a hacer un test de consistencia con un reasoner tras realizar las aserciones, es permitir añadir validaciones a los recursos descritos en la TBox. Estas validaciones se realizan cuando se quiere hacer una aserción en la ABox para ver si la aserción está permitida o no, controlando qué es válido y qué no es válido asertar. Aún así, esta solución no deja de ser una pequeña chapuza, ya que perdemos la información de las validaciones que no está contenida en la ontología, por lo que siempre que sea posible, se debe modelar correctamente la ontología, insertando la información posible y después añadir las validaciones.

Estas validaciones, son predicados y se pueden realizar en el registro de la TBox. y pueden ir asociadas a cualquier acción y recurso. Por ejemplo:

 (tbox-register-name! :owl-thing (owl-Thing) :connection)
 (tbox-register-name! :test-Person :test "Person"  :connection)
 (tbox-register-name! :test-age :test "age"  :connection)
 (tbox-register-name! :test-name :test "name"  :connection)
 
 (tbox-register-validation-on! :create 
                               :test-age 
                               (fn [age]
                                   (> (:value age) 18)))
 

En este caso, comprobamos que al crear un nuevo individuo que da valor a la propiedad test:age, el valor de la edad es mayor que 18. En este caso la validación es para un propiedad, y se comprobaría cuando se realizase una acción sobre cualquier clase con esa propiedad, de esta manera se pueden reutilizar las validaciones entre diferentes clases. Cuando la validación es a nivel de clase, pero no común para las propiedad compartida entre clases, se puede registrar la validación a nivel de clase:

 (tbox-register-name! :test-AdultPerson :test "AdultPerson" :connection)
 (tbox-register-validation-on! :create
                               :test-AdultPerson
                               (fn [properties] 
                                   (> (:value (:test-age properties)) 18)))
 

Así se comprobaría el predicado cuando se crea un individuo de la clase test:AdultPerson, pero no cuando ce crea cualquier otro individuo de una clase relacionada con la propiedad test:age.

Con las validaciones se pueden ofrecer una semántica para casi todo: propiedades obligatorias, formato correcto de valores ... siempre que no sean excusa para perder información que podría haberse asertado algún constructor de OWL, como allValuesFrom, etc.

Tags: clojure, owl

servido por Antonio sin comentarios compártelo

5 Enero 2009

Una capa de persistencia de datos semántica basada en OWL

Hoy en día la norma para desarrollar la capa de persistencia de una aplicación web consiste en desarrollar un esquema para algún sistema de base de datos relacional en el que almacenar los datos.
Esto supone básicamente darle golpes a tus objetos hasta convertirlos en conjuntos de enteros, cadenas, etc que después se vuelven a encajar en tablas, indicando relaciones entre esas tablas duplicando valores en columnas especiales de esas tablas. Estamos tan acostumbrados a hacerlo de esta manera que ni nos damos cuenta de la gran distancia que media entre nuestro modelo objetual, con el que trabajamos en nuestro código, y el nivel de abstracción mucho más bajo con el que almacenamos una representación persistente de esos datos.

Sin embargo, esto no tendría porque ser así. Cuando creamos y modelamos nuestro dominio de datos para la aplicación, no hacemos más que crear una ontología, una descripción de los recursos del dominio y sus relaciones, y en los últimos años se ha estandarizado un lenguaje para la descripción de ontologías por parte de la W3C: OWL.

OWL nace del mundo de la lógica descriptiva y permite describir las dos partes que componen una ontología:

  • Una TBox: o terminological box, donde describimos una terminología, los tipos de recursos y relaciones, por ejemplo, existen cosas llamadas personas, existe una relación entre una cosa y un número llamada tener edad, etc...
  • Una ABox: o assertional box, donde hacemos aserciones usando los términos de la TBox, por ejemplo: Juan es una persona, Juan tiene una edad de 15, etc...

A continuación mostraré una biblioteca para describir una TBox en Clojure lisp.

Nuestra primera tarea es tener un sitio donde almacenar la TBox, para ello debemos tener una conexión con un repositorio. Con el siguiente código creamos un repositorio en memoria y lo registramos con un nombre para usarlo posteriormente:

  (register-repository! :test (init-memory-repository!))
 

A continuación podemos pasar a describir nuestra TBox para la ontología que vamos a describir, la famosa aplicación para escribir blogs, para ello usamos las funciones (describe-owl-*):

 (write-graph-in-repository
    (describe-tbox
       ;; algunas propiedades de datos
       (describe-owl-datatype-property :superblog "title" (xsd-string))
       (describe-owl-datatype-property :superblog "content" (xsd-string))
       (describe-owl-datatype-property :superblog "name" (xsd-string))
                 
       ;; algunas clases
       (describe-owl-class :superblog "Blog")
       (describe-owl-class :superblog "Post")
       (describe-owl-class :superblog "Author")
                
       ;; asignamos propiedades a clases
       (describe-owl-class-has-property :superblog "Blog" :superblog "title")
       (describe-owl-class-has-property :superblog "Post" :superblog "title")
       (describe-owl-class-has-property :superblog "Post" :superblog "content")
       (describe-owl-class-has-property :superblog "Author" :superblog "name")
 
       ;; algunas propiedades de objetos
       (describe-owl-object-property :superblog "contains")
       (describe-owl-object-property :superblog "writes")
 
       ;; relaciones entre clases
       (describe-owl-class-has-property :superblog "Blog" :superblog "contains")
       (describe-owl-property-range :superblog "contains" :superblog "Post")
       (describe-owl-class-has-property :superblog "Author" :superblog "writes")
       (describe-owl-property-range :superblog "writes" :superblog "Post"))
 
    ;; seleccionamos la conexion con el repositorio anteriormente registrado
    (connection! :test))
 

El código anterior es equivalente a escribir una migración en Ruby on Rails, pero el hecho de usar OWL en vez de directamente una base de datos relacional, nos aporta una serie de beneficios, por ejemplo, la reutilización del esfuerzo empleado en modelar el dominio. En el ejemplo anterior podemos darnos cuenta de que las relaciones como título, ser autor de, etc, ya han sido modelados y estandarizados dentro del Dublin Core Metadata Initiative (http://en.wikipedia.org/wiki/Dublin_Core), ¿por qué no utilizar el soporte para el Dublin Core que incluye la biblioteca? La reutilización se basa en el concepto de kits de vocabularios, que incluyen clases y propiedades y funciones que trabajan sobre estas partículas OWL:

 (use 'com.agh.webserver.framework.persistence.rdf.vocabularies.dc)
 
 ;; cargamos la descripción del Dublin Core en el repositorio de test
 (import-vocabulary-kit :dc (connection! :test)) 
 
 ;; buscamos sinonimos entre nuestro dominio y el DC
 (write-graph-in-repository
    (describe-tbox
       (describe-owl-equivalent-properties :superblog "title" (dc-title))
       (describe-owl-equivalent-properties :superblog "writes" (dc-creator)))
    (connection! :test))
 

La descripción de la TBox de la ontología está en este momento almacenada en el repositorio, para poder usar la TBox de forma eficiente debemos iniciar la TBox al empezar a usar nuestra aplicación declarando que recursos de la ontología almacenada en el repositorio queremos utilizar:

   ;; iniciamos la TBox
  (tbox-register-name! :owl-thing (owl-Thing) :test)
  (tbox-register-name! :superblog-Post "http://superblog.com/Post" :test)
  (tbox-register-name! :superblog-Blog "http://superblog.com/Blog" :test)
  (tbox-register-name! :superblog-Author "http://superblog.com/Author" :test)
  (tbox-register-name! :superblog-content "http://superblog.com/content" :test)
  (tbox-register-name! :superblog-title "http://superblog.com/title" :test)
  (tbox-register-name! :superblog-name "http://superblog.com/name" :test)
  (tbox-register-name! :superblog-post "http://superblog.com/contains" :test)
  (tbox-register-name! :superblog-post "http://superblog.com/writes" :test)
 
  ;; iniciamos el kit de Dublin Core con los nombre por defecto que asigna la 
  :: biblioteca
  (tbox-register-vocabulary-dc :test)
 
   ;; el codigo de la aplicacion empezaria aqui
 

Ahora ya podríamos acceder a los recursos mediante su URI o mediante el nombre con el que se ha registrado. Un test que muestra la interacción entre las diferentes partes, además de la inferencia entre propiedades y subclases, podría ser:

 (deftest test-class-subclassing
   (let [orig-tbox @*tbox*
         orig-conn @*connections*
         orig-repos @*repositories-registry*
         repo (init-memory-repository!)
         graph (describe-tbox
                (describe-owl-datatype-property "http://test.com/prop_a" (xsd-float))
                (describe-owl-datatype-property "http://test.com/prop_b" (xsd-float))
                (describe-owl-class "http://test.com/class_a")
                (describe-owl-class "http://test.com/class_b")
                (describe-owl-class "http://test.com/class_c")
                (describe-owl-subclass "http://test.com/class_a" "http://test.com/class_b")
                (describe-owl-subclass "http://test.com/class_a" "http://test.com/class_c")
                (describe-owl-class-has-property "http://test.com/class_a" "http://test.com/prop_a")
                (describe-owl-class-has-property "http://test.com/class_b" "http://test.com/prop_b"))]
     (do (tbox-clear!)
         (register-repository! :test repo)
         (write-graph-in-repository graph (connection! :test))
         (tbox-register-name! :owl-thing (owl-Thing) :test)
         (tbox-register-name! :class_a "http://test.com/class_a" :test)
         (tbox-register-name! :class_b "http://test.com/class_b" :test)
         (tbox-register-name! :class_c "http://test.com/class_c" :test)
         (tbox-register-name! :prop_a "http://test.com/prop_a" :test)
         (tbox-register-name! :prop_b "http://test.com/prop_b" :test)
         (let [class-recovered (tbox-find-class-by-uri! "http://test.com/class_a")]
           (do 
               (is (= class-recovered
                      {:name :class_a
                       :uri "http://test.com/class_a"
                       :subclass-of [{:name :class_a
                                      :uri {:prefix "", :value "http://www.w3.org/2002/07/owl#Thing"}
                                      :subclass-of []
                                      :datatype-properties #{}
                                      :object-properties #{}
                                      :repository-name :test}
                                     {:name :class_b
                                      :uri {:prefix "", :value "http://test.com/class_b"}
                                      :subclass-of [{:name :owl-Thing
                                                     :uri {:prefix "", :value "http://www.w3.org/2002/07/owl#Thing"}
                                                     :subclass-of []
                                                     :datatype-properties #{}
                                                     :object-properties #{}
                                                     :repository-name :test}]
                                      :datatype-properties #{{:name nil
                                                              :uri
                                                              {:prefix "", :value "http://test.com/prop_b"}
                                                              :range {:prefix "", :value "http://www.w3.org/2001/XMLSchema#float"}
                                                              :equivalent-properties #{}
                                                              :repository-name :test}}
                                      :object-properties #{}
                                      :repository-name :test}
                                     {:name :class_c
                                      :uri {:prefix "", :value "http://test.com/class_c"}
                                      :subclass-of [{:name :owl-Thing
                                                     :uri {:prefix "", :value "http://www.w3.org/2002/07/owl#Thing"}
                                                     :subclass-of []
                                                     :datatype-properties #{}
                                                     :object-properties #{}
                                                     :repository-name :test}]
                                      :datatype-properties #{}
                                      :object-properties #{}
                                      :repository-name :test}]
                       :datatype-properties #{{:name :prop_b
                                               :uri {:prefix "", :value "http://test.com/prop_b"}
                                               :range {:prefix "", :value "http://www.w3.org/2001/XMLSchema#float"}
                                               :equivalent-properties #{}
                                               :repository-name :test}
                                              {:name :prop_a
                                               :uri
                                               {:prefix "", :value "http://test.com/prop_a"}
                                               :range {:prefix "", :value "http://www.w3.org/2001/XMLSchema#float"}
                                               :equivalent-properties #{}
                                               :repository-name :test}}
                       :object-properties #{}
                       :repository-name :test}
               (tbox-restore! orig-tbox)
               (repositories-register-restore! orig-repos)
               (connections-restore! orig-conn))))))
 

Al igual que OWL se situa sobre una pila de protocolos de la W3C para la web semántica, en concreto, una ontología OWL se codifica como un grafo RDF que a su vez se puede describir como un documento XML, la biblioteca OWL usa la bibliteca RDF que comenté anteriormente para describir los recursos en términos de constructores de un grafo RDF y recupera su información transformando ese grafo en consultas SPARQL.

En siguientes posts, veremos como usar esta TBox para hacer aserciones sobre objetos concretos dentro de la ABox.

Aunque todavía se encuentra muy lejos de ser usable para nada, si queréis hacer pruebas y juguetear con él, el código se encuentra disponible en mi repositorio de Github.

Tags: lisp, clojure, owl

servido por Antonio sin comentarios compártelo

1 Enero 2009

Una biblioteca RDF para Clojure

El código y los tests los podéis encontrar en mi repositorio de Github.
Usando Sesame como repositorio semántico. Describimos un grafo RDF:

 (def  *graph*
   (build-graph
     [(build-uri-node "http://test.com/whatever"
         [(build-relation :rdf "relation-1" (build-literal-node "testa1"))
          (build-relation :rdf "relation-2" (build-literal-node "testa2"))])
      (build-uri-node :rdf "test-subjectb"
         [(build-relation :rdf "relation-2" (build-blank-node "a" []))])])
 

El grafo que acabamos de describir es quivalente a los siguientes tripletes RDF (con notación turtle):

@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
 <http://test.com/whatever> rdf:relation-1 "testa1" .
 <http://test.com/whatever> rdf:relation-2 "testa2" .
 rdf:test-subjectb rdf:relation-2 _:a .
 

El grafo que hemos guardado en *graph* podemos manipularlo en memoria, pero para almacenarlo, debemos obtener una conexión con un repositorio. Supongamos que tenemos una base de datos llamada "clojure_sesame", podemos usarla para almacenar el grafo en esa base de datos:

 (def *connection*
      (. (init-repository "com.mysql.jdbc.Driver" 
                          "jdbc:mysql://localhost:3306/clojure_sesame" 
                          "root" 
                          "root")
          getConnection)
 (write-graph-in-repository *graph* *connection*)
 

Una funcionalidad interesante es consultar un grafo RDF de una forma similar al uso que se hace de SQL para consultar un base de datos relacional.
Para dar solución a esta necesidad se ha estandarizado SPARQL (SPARQL Protocol and RDF Query Language) por parte de la W3C (http://www.w3.org/TR/rdf-sparql-query/).

La biblioteca ofrece esta funcionalidad mediante la construcción de plantillas: grafos RDF, donde se pueden insertar constructores para nodos variables y para relaciones variables:

 (def  *template*
   (build-graph-template 
      [{:template (build-graph
                            [(build-variable-node :x
                                 [(build-variable-relation :y 
                                      (build-variable-node :z []))])])
        :filters []}])]
 

La función (build-graph-template) acepta mapas con dos claves, :template que debe contener como valor un grafo con partes variables, y :filters, una colección de filtros que se pueden aplicar a los nodos variables. Por ejemplo:

 (build-filter :x "?x < 25")
 (build-filter :y "isURI(?y)")
 

También se pueden describir partes opcionales de la plantilla con la función (build-optional-graph).

Una vez que tenemos la plantilla debemos seleccionar que variables queremos que la consulta nos devuelva. En el ejemplo anterior, tenemos una plantilla con tres variables :x :y :z, si sólo nos interesan las variables :x e :y, construimos la consulta de la siguiente manera:

 (def *result*
   (query-template-in-repository
                              :x :y
                              *template*
                              *connection*)
 

La invocación anterior es equivalente a realizar la siguiente consulta SPARQL:

 SELECT ?x ?y WHERE { ?x ?y ?z .}
 

Los resultados se devuelven como colecciones de mapas, donde cada mapa contiene una solución para :x e :y:

 ;; El resultado de la consulta
  [{:y {:prefix "", :value "http://www.w3.org/1999/02/22-rdf-syntax-ns#relation-2"}
    :x {:prefix "", :value "http://test.com/test-subjecta/whatever"}}
   {:y {:prefix "", :value "http://www.w3.org/1999/02/22-rdf-syntax-ns#relation-1"}
    :x {:prefix "", :value "http://test.com/test-subjecta/whatever"}}
   {:y {:prefix "", :value "http://www.w3.org/1999/02/22-rdf-syntax-ns#relation-2"}
    :x {:prefix "", :value "http://www.w3.org/1999/02/22-rdf-syntax-ns#test-subjectb"}}]
 

Las plantillas no solo describen patrones para consultas SPARQL, sino que se pueden sustituir las partes variables de la plantilla, si queremos transformarla en un grafo de nuevo. Usando el primer resultado de la consulta, podemos hacer:

 (template-to-graph *template* (first *result*))
 

Y obtendríamos el grafo equivalente resolviendo :x e :y (en este caso todavía quedaría la parte variable referente a :z).

También podemos usar la capacidad de sustitución de las plantillas para dar valores parciales a variables al hacer una consulta:

 (query-template-in-repository
                            :x :y {:z (build-literal-node "testa1")}
                            *template*
                            *connection*)
 

De esta manera obtendríamos una consulta equivalente a:

 SELECT ?x ?y 
 WHERE { 
   ?x ?y "testa1" .
 }
 

Esta biblioteca, ofrece un acceso a RDF de bastante bajo nivel, manipulando tripletes de forma individual, pero puede servir de base para construir una capa de persistencia semántica para aplicaciones web, quizá construida sobre OWL, el lenguaje de descripción de ontologías de la W3C.

Tags: sesame, rdf, clojure

servido por Antonio sin comentarios compártelo


Sobre mí

Avatar de Antonio

un rato de sol

Barcelona/Salamanca, España
ver perfil »
contacto »
Trabajador del metal y del acero, en la gloriosa XING AG, escribo software con el que poder ganarme el jornal. En mi tiempo libre sigo tecleando código de bonitos colores a medio camino entre lo sublime y lo terrible. Últimamente me gustan mucho los gatos.

Fotos

Antonio Garrote Hernández todavía no ha subido ninguna foto.

¡Anímale a hacerlo!

Buscar

suscríbete

Selecciona el agregador que utilices para suscribirte a este blog (también puedes obtener la URL de los feeds):

¿Qué es esto?

Crea tu blog gratis en La Coctelera