Premier contact avec les outils de l’Agence Bibliographique de l’Enseignement Supérieur

Dans le cadre du projet SemBib, j’ai été amené à choisir un identifiant unique pour chaque auteur. Suivant ma stratégie habituelle, j’ai commencé par utiliser des identifiants définis dans notre espace de nommage, avec notre préfixe. Ainsi, il a été possible de produire rapidement des résultats et d’inciter d’autres personnes à participer au projet.

J’applique cette méthode pour les éléments dont j’ai immédiatement besoin, ici les auteurs; je cherche à minimiser les identifiants et le vocabulaire que je définis de façon ad-hoc et à utiliser le plus de vocabulaire connus et courants, mais, dans un esprit d’approche agile, je ne veux pas bloquer de premiers résultats applicatifs par une recherche laborieuse de tous les vocabulaires pré-existants avec lesquels se lier.

Dans un deuxième temps, je cherche si un vocabulaire ou des identifiants existent afin  de créer des liens avec d’autres ensembles de données. Le principe de base est de créer de nouvelles versions de mes données -avec une compatibilité ascendante, si les données ont été publiées- notamment avec l’utilisation de owl:sameAs. Je compte consolider cette stratégie dans les prochains mois et je suis preneur d’avis à ce sujet.

Par hasard, j’ai trouvé que je suis identifié par IdRef avec le lien permanent http://www.idref.fr/157248550. Je suis donc allé voir de plus prêt de quoi il s’agit. La page à l’adresse http://www.idref.fr donne peu d’informations. Le sous-titre de IdRef est ‘Le référentiel des autorités Sudoc’, ce qui n’est pas très parlant -sauf si on sait que SuDoc est ‘Le catalogue du Système Universitaire de Documentation’. Le bas de la page contient un bandeau ‘ABES – Agence Bibliographique de l’Enseignement Supérieur’, ce qui suggère déjà un lien plus direct avec le projet SemBib et m’a incité à approfondir.

En fait, la documentation en ligne m’a appris que mon identifiant idref est http://www.idref.fr/157248550/id. Je peux obtenir une représentation XML du contenu de ma notice bibliographique enregistrée par l’ABES à l’adresse http://www.idref.fr/157248550.xml (noter qu’elle est très incomplète). Et pour la notice en JSONhttp://www.idref.fr/services/biblio/157248550.json.

En principe, la requête http://www.idref.fr/services/idref2viaf/157248550 devrait trouver mon identifiant VIAF (http://viaf.org/viaf/291343068), mais ne le trouve pas.

Trouver l’identifiant d’un chercheur

Je me suis demandé si j’allais ainsi pouvoir associer un identifiant idref à tous les chercheurs de Telecom ParisTech. Comme il y a environ 200 chercheurs et autant de doctorants à Telecom ParisTech, je voudrais automatiser cela.

Par malchance, le jour où j’ai commencé mes tests les exemples de la section 2.3 ne fonctionnaient pas (le 16/1/2017). J’ai donc posté un message à l’adresse email figurant dans la documentation. Très vite j’ai eu une réponse, avec plusieurs propositions (merci F.M. dont la réponse est largement reprise ci-dessous). Je me borne ici à décrire les solutions publiquement accessibles.

Première méthode

La première consiste à interroger le moteur de recherche Solr d’IdRef avec un Nom/ Prénom de personne (service documenté ici http://documentation.abes.fr/aideidrefdeveloppeur/ch02s01.html).  Exemple de requête : http://www.idref.fr/Sru/Solr?q=persname_t:(Moissinac AND Jean-Claude)&fl=ppn_z, affcourt_z&wt=xml

Le paramètre q contient la recherche qui va être effectuée par Solr. Ici, on fait une recherche de type ‘contient les mots’ -indiqué par le suffixe _t- sur le champs persname (nom de personne), suivi de : pour indiquer ses paramètres, ici une liste de chaîne de caractères qui vont être cherchées. Si les mots cherchés sont trouvés -c’est le cas dans l’exemple ci-dessus-, on obtient une réponse telle que:

<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">1</int>
  <lst name="params">
    <str name="fl">ppn_z</str>
    <str name="q">persname_t:(Moissinac AND Jean-Claude)</str>
  </lst>
</lst>
<result name="response" numFound="1" start="0">
  <doc>
    <str name="ppn_z">157248550</str>
  </doc>
</result>
</response>

En remplaçant la fin de la requête wt=xml par wt=json, on obtient une réponse mise en forma en JSON (au 27/1/2017: pas avec le bon MIME type).

La méthode présente un risque de ne rien obtenir si la base IDREF ne contient pas exactement les chaînes cherchées ou la possibilité de récupérer trop de choses si on ouvre trop la recherche. Par exemple, une recherche limitée au mot Moissinac donne 9 réponses qu’il va falloir discriminer. Par exemple, en allant chercher les notices bibliographiques -cf.ci-dessus- et en éliminant les notices inappropriées. Dans les 9 réponses pour ‘Moissinac’, la première est l’id 056874022 associé à la notice http://www.idref.fr/services/biblio/056874022.json où on peut, par exemple, constater que le champ « name » a la valeur « Moissinac, Bernard ». On peut, par exemple, tester les différents champs « name » par rapport à la chaîne de référence « Moissinac, Jean-Claude » avec le distance de Levenshtein. Cela devrait suffire à discriminer correctement la plupart des cas. On peut aussi avoir un contrôle spécifique sur tous les noms pour lesquels on obtient 0 ou plusieurs réponses (en supposant que lorsqu’on a une seule réponse, c’est la bonne). Nous envisagerons plus tard de faire des tests automatisés sur les autres champs de la notice.

Deuxième méthode

La seconde méthode consiste à interroger le moteur Solr de theses.fr avec un nom/prénom et une contrainte supplémentaire de lien de cette personne avec Telecom Paristech (=Paris, ENST) ou son identifiant idref 026375273 :
http://www.theses.fr/?q=personneRAs:jean-claude+moissinac%20AND%20etabSoutenances:Paris%2C+ENST&type=avancee&lng=&lng=&format=xml&fl=directeurTheseNP,directeurThesePpn

On peut obtenir une sortie xml ou json.

Les personnes recherchées doivent avoir été impliquées dans une thèse et pas forcément associées à « Paris, ENST ». La contrainte est forte. De plus, theses.fr ne semble pas connaitre nos différents noms: Telecom Paris, Télécom Paris, Télécom ParisTech… ou, en tout cas, ne pas identifier qu’il s’agit de diverses dénominations d’un même organisme. L’idéal serait de trouver une fois pour toutes l’identifiant idref de notre institution et de l’utiliser dans les critères de recherche. Nous ne traiterons pas cette question aujourd’hui.

S’appuyer sur VIAF

Le billet http://corist-shs.cnrs.fr/IDChercheurs_2016 contient des pistes: au lieu d’utiliser IDREF, s’appuyer sur ORCID ou VIAF avec lesquels IDREF a des accords d’échange.

Un tour sur les API d’ORCID et notamment l’API VIAF (https://platform.worldcat.org/api-explorer/apis/VIAF) me donne le lien suivant:

http://www.viaf.org/viaf/search?query=cql.any+=+%22Jean-Claude%20Moissinac%22&maximumRecords=5&httpAccept=application/json

me permet de trouver mon identifiant VIAF et bien d’autres (SUDOC/IDREF notamment).

Je n’ai plus qu’à décliner cette requête sur l’ensemble des noms de chercheur de Telecom ParisTech en espérant qu’il n’y aura pas trop d’ambiguïtés -qui se traduit avec un champ numberOfRecords supérieur à 1- ou d’absents -qui se traduit avec un champ numberOfRecords supérieur à 0.

 

Suite: Accès Sparql

Dans un prochain billet, nous explorerons l’accès SPARQL de l’ABES:  https://lod.abes.fr/sparql.

 

 

Publié dans Données publiques, SemBib | 2 commentaires

Premiers contacts avec l’accès SPARQL de l’éditeur Springer

Note du 14/5/2018: l’accès SPARQL mentionné dans cet article a disparu; nous allons prochainement étudier la nouvelle politique d’accès aux données de Springer

Dans le cadre du projet SemBib, je vais découvrir avec vous l’accès SPARQL public de l’éditeur scientifique Springer à l’adresse http://lod.springer.com/sparql-form/index.html. Pour un premier contact, il faut faire connaissance et quelques requêtes classiques vont nous y aider.

D’abord, découvrir les propriétés utilisées:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> 
PREFIX spr: <http://lod.springer.com/data/ontology/property/> 
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX dc: <http://purl.org/dc/elements/1.1/>
select distinct ?p ?label where {
?s ?p ?o .
OPTIONAL {
?p rdfs:label ?label .
}
}
limit 100

qui met environ 23 secondes (j’ai un peu triché: je l’ai exécutée une première fois pour voir les URI utilisées et en déduire des préfixes à définir et avoir le résultat plus compact ci-dessous, grâce aux préfixes).

Elle donne :

--------------------------------------------------------------
| p                           | label                        |
==============================================================
| rdf:type                    |                              |
| dc:creator                  |                              |
| dc:date                     |                              |
| dc:description              |                              |
| dc:publisher                |                              |
| dc:rights                   |                              |
| rdfs:label                  |                              |
| rdfs:domain                 |                              |
| rdfs:range                  |                              |
| rdfs:subPropertyOf          |                              |
| spr:hasDBLPID               | "has DBLP ID"@en             |
| spr:confSeriesName          | "conference series name"@en  |
| spr:confYear                | "conference year"@en         |
| spr:confAcronym             | "conference acronym"@en      |
| spr:confName                | "conference name"@en         |
| spr:confCity                | "conference city"@en         |
| spr:confCountry             | "conference country"@en      |
| spr:confStartDate           | "conference start date"@en   |
| spr:confEndDate             | "conference end date"@en     |
| spr:hasSeries               | "ConferenceSeries"@en        |
| spr:volumeNumber            | "Volume number"@en           |
| spr:title                   | "Title"@en                   |
| spr:subtitle                | "Subtitle"@en                |
| spr:ISBN                    | "ISBN"@en                    |
| spr:EISBN                   | "eISBN"@en                   |
| spr:bookSeriesAcronym       | "book series acronym"@en     |
| spr:hasConference           | "Conference"@en              |
| spr:bookDOI                 | "DOI"@en                     |
| spr:isIndexedByScopus       | "Is indexed by Scopus"@en    |
| spr:scopusSearchDate        | "Scopus search date"@en      |
| spr:isAvailableAt           | "Available at"@en            |
| spr:isIndexedByCompendex    | "Is indexed by Compendex"@en |
| spr:compendexSearchDate     | "Compendex search date"@en   |
| spr:confNumber              | "conference number"@en       |
| spr:copyrightYear           | "Copyright year"@en          |
| spr:firstPage               | "First page"@en              |
| spr:lastPage                | "Last page"@en               |
| spr:chapterRegistrationDate | "Registration date"@en       |
| spr:chapterOf               | "Book"@en                    |
| spr:chapterOnlineDate       | "Online date"@en             |
| spr:copyrightHolder         | "Copyright Holder"@en        |
| spr:metadataRights          | "Metadata Rights"@en         |
| spr:abstractRights          | "Abstract Rights"@en         |
| spr:bibliographyRights      | "Bibliography Rights"@en     |
| spr:bodyHtmlRights          | "Body HTML Rights"@en        |
| spr:bodyPdfRights           | "Body PDF Rights"@en         |
| spr:esmRights               | "ESM Rights"@en              |
--------------------------------------------------------------

D’une part cela nous montre que les performances ne sont pas exceptionnelles. D’autre part, on voit que pour l’essentiel Springer a défini sa propre ontologie: des données sont accessibles, mais pas vraiment reliées au reste du monde par des concepts partagés. Les données sont définies avec 47 prédicats (propriétés).

La requête suivante -sans répéter les préfixes ci-dessus définis- nous donne le nombre de ‘sujets’ distincts renseignés dans la base: 451277.

select (count(distinct ?s) as ?size) where {
?s ?p ?o .
}

et celle qui suit donne le nombre de triplets qui renseignent ces sujets: 3490865, soit environ 8 prédicats par sujet différent, ce qui est peu pour renseigner de façon détaillée des références bibliographiques. On peut supposer qu’on aura donc peu de données sur chaque référence.

select (count(?s) as ?size) where {
?s ?p ?o .
}

Le relativement faible de prédicats par sujet me suggère de chercher les plus utilisés:

select ?p (count(?p) as ?freq) where {
?s ?p ?o .
}
group by ?p
order by desc(?freq)

ce qui donne (en enlevant les moins utilisés, concernant essentiellement des questions de droits):

----------------------------------------
| p                           | freq   |
========================================
| rdf:type                    | 451316 |
| spr:bookDOI                 | 441266 |
| spr:title                   | 439864 |
| spr:chapterOf               | 381656 |
| spr:firstPage               | 381646 |
| spr:lastPage                | 381646 |
| spr:chapterRegistrationDate | 245143 |
| spr:chapterOnlineDate       | 188209 |
| spr:EISBN                   | 59611  |
| spr:isIndexedByScopus       | 59370  |
| spr:scopusSearchDate        | 59370  |
| spr:ISBN                    | 59101  |
| spr:isAvailableAt           | 59101  |
| spr:copyrightYear           | 55964  |
| spr:subtitle                | 40321  |
| spr:volumeNumber            | 34988  |
| spr:bookSeriesAcronym       | 17665  |
| spr:compendexSearchDate     | 11400  |
| spr:isIndexedByCompendex    | 11400  |
| spr:hasConference           | 9509   |
| spr:confCity                | 8487   |
| spr:confCountry             | 8487   |
| spr:confName                | 8487   |
| spr:hasSeries               | 8487   |
| spr:confEndDate             | 8479   |
| spr:confStartDate           | 8479   |
| spr:confYear                | 8479   |
| spr:confAcronym             | 8233   |
| spr:confNumber              | 8021   |
----------------------------------------

On voit que l’essentiel de l’information disponible sur un élément de la base consiste en: son type, son numéro DOI, son titre, de quoi l’élément est un chapitre, à partir de quelle page et jusqu’à quelle page. Les autres informations concernent notamment des conférences d’où peuvent provenir les documents.

Prédicats avec domain et range

On voit que les propriétés domain -qui nous donne la catégorie d’objets à laquelle s’applique le prédicat- et range -qui nous donne la catégorie des valeurs possibles pour ce prédicat- semblent renseignées pour certains prédicats .

Avec la même petite tricherie que ci-dessus pour les préfixes, la requête suivante nous donne en 15 secondes les domain et range utilisés:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> 
PREFIX spr: <http://lod.springer.com/data/ontology/property/>               
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX xs: <http://www.w3.org/2001/XMLSchema#>
PREFIX sxs: <https://www.w3.org/2001/XMLSchema#>
PREFIX dc: <http://purl.org/dc/elements/1.1/>
PREFIX spc:<http://lod.springer.com/data/ontology/class/>                       
select distinct ?p ?domain ?range  where {
?s ?p ?o .
?p rdfs:domain ?domain .
?p rdfs:range ?range .
}
limit 100

Le résultat est:

------------------------------------------------------------------------------
| p                           | domain                | range                |
==============================================================================
| spr:confSeriesName          | spc:ConferenceSeries  | rdf:langString       |
| spr:confYear                | spc:Conference        | xs:date              |
| spr:confAcronym             | spc:Conference        | rdf:langString       |
| spr:confName                | spc:Conference        | rdf:langString       |
| spr:confCity                | spc:Conference        | rdf:langString       |
| spr:confCountry             | spc:Conference        | rdf:langString       |
| spr:confStartDate           | spc:Conference        | xs:date              |
| spr:confEndDate             | spc:Conference        | xs:date              |
| spr:hasSeries               | spc:Conference        | spc:ConferenceSeries |
| spr:confNumber              | spc:Conference        | xs:int               |
| spr:volumeNumber            | spc:ProceedingsVolume | rdfs:literal         |
| spr:title                   | spc:ProceedingsVolume | rdf:langString       |
| spr:subtitle                | spc:ProceedingsVolume | rdf:langString       |
| spr:ISBN                    | spc:ProceedingsVolume | rdfs:literal         |
| spr:EISBN                   | spc:ProceedingsVolume | rdfs:literal         |
| spr:bookSeriesAcronym       | spc:ProceedingsVolume | rdfs:literal         |
| spr:hasConference           | spc:ProceedingsVolume | spc:Conference       |
| spr:bookDOI                 | spc:ProceedingsVolume | rdf:langString       |
| spr:isIndexedByScopus       | spc:ProceedingsVolume | sxs:boolean          |
| spr:scopusSearchDate        | spc:ProceedingsVolume | sxs:dateTime         |
| spr:isIndexedByCompendex    | spc:ProceedingsVolume | sxs:boolean          |
| spr:compendexSearchDate     | spc:ProceedingsVolume | sxs:dateTime         |
| spr:volumeNumber            | spc:Book              | rdfs:literal         |
| spr:title                   | spc:Book              | rdf:langString       |
| spr:subtitle                | spc:Book              | rdf:langString       |
| spr:ISBN                    | spc:Book              | rdfs:literal         |
| spr:EISBN                   | spc:Book              | rdfs:literal         |
| spr:bookSeriesAcronym       | spc:Book              | rdfs:literal         |
| spr:bookDOI                 | spc:Book              | rdf:langString       |
| spr:isIndexedByScopus       | spc:Book              | sxs:boolean          |
| spr:scopusSearchDate        | spc:Book              | sxs:dateTime         |
| spr:copyrightYear           | spc:Book              | sxs:date             |
| spr:isIndexedByCompendex    | spc:Book              | sxs:boolean          |
| spr:compendexSearchDate     | spc:Book              | sxs:dateTime         |
| spr:title                   | spc:BookChapter       | rdf:langString       |
| spr:subtitle                | spc:BookChapter       | rdf:langString       |
| spr:bookDOI                 | spc:BookChapter       | rdf:langString       |
| spr:firstPage               | spc:BookChapter       | sxs:int              |
| spr:lastPage                | spc:BookChapter       | sxs:int              |
| spr:chapterRegistrationDate | spc:BookChapter       | sxs:date             |
| spr:chapterOnlineDate       | spc:BookChapter       | sxs:date             |
| spr:chapterOf               | spc:BookChapter       | spc:Book             |
| spr:copyrightYear           | spc:BookChapter       | sxs:date             |
| spr:copyrightHolder         | spc:BookChapter       | rdf:string           |
| spr:metadataRights          | spc:BookChapter       | rdf:string           |
| spr:abstractRights          | spc:BookChapter       | rdf:string           |
| spr:bibliographyRights      | spc:BookChapter       | rdf:string           |
| spr:bodyHtmlRights          | spc:BookChapter       | rdf:string           |
| spr:bodyPdfRights           | spc:BookChapter       | rdf:string           |
| spr:esmRights               | spc:BookChapter       | rdf:string           |
------------------------------------------------------------------------------

Exploration de quelques prédicats

dc:creator

Je m’attendais à un usage de dc:creator pour les noms d’auteurs. Mais dc:creator ne prends qu’une seule valeur: « Springer »@en. Sans doute pour désigner le créateur de la base. Aucun autre prédicat ne semble pouvoir être porteur du nom des auteurs.

rdf:type

La requête suivante va nous permettre de voir la répartition des types utilisés:

select distinct ?o (count(?o) as ?typecount) where {
?s a ?o .
}
group by ?o
order by desc(?typecount)

donne

----------------------------------------------------------------
| o                                                | typecount |
================================================================
| spc:BookChapter                                  | 381657    |
| spc:Book                                         | 50102     |
| spc:ProceedingsVolume                            | 9509      |
| spc:Conference                                   | 8487      |
| spc:ConferenceSeries                             | 1477      |
| rdf:Property                                     | 39        |
| <http://www.w3.org/2002/07/owl#DatatypeProperty> | 34        |
| <http://www.w3.org/2002/07/owl#Class>            | 5         |
| <http://www.w3.org/2002/07/owl#ObjectProperty>   | 5         |
| <http://rdfs.org/ns/void#Dataset>                | 2         |
----------------------------------------------------------------

spr:bookDOI

Ce prédicat permet probablement d’associer un numéro DOI à chaque document. Par nature, il est désigne de façon unique un document. Je vais m’intéresser à la forme utilisée pour enregistrer le numéro DOI par Springer (j’ai noté, par exemple, que dans la base de Telecom ParisTech, diverses formes sont utilisées).

select ?doi  where {
?s spr:bookDOI ?doi .
}
limit 5

donne

----------------------------------
| doi                            |
==================================
| "10.1007/978-3-319-09147-1"@en |
| "10.1007/978-3-319-09147-1"@en |
| "10.1007/978-3-319-10762-2"@en |
| "10.1007/978-3-319-10762-2"@en |
| "10.1007/978-3-319-07785-7"@en |
----------------------------------

On voit une représentation homogène des numéros DOI dans la base de Springer. J’ai vérifié cela sur un plus grand nombre d’exemple.

Des prédicats au sujet des conférences

Plusieurs prédicats semblent concerner des séries de conférences. Je vais chercher combien sont concernées et quelles séries conférences ont eu le plus d’occurrences.

On trouve 1477 sujets de type spc:ConferenceSeries (cf ci-dessus les types les plus fréquents).

La requête suivante va nous donner les 20 séries de conférences qui ont le plus donné lieu à publication par Springer:

select ?name (count(distinct ?conf) as ?c) where {
?conf a spc:Conference  .
?conf spr:hasSeries ?serie  .
?serie a spc:ConferenceSeries  .
?serie spr:confSeriesName ?name
}
group by ?name
order by desc(?c)
limit 20

donne

--------------------------------------------------------------------------------------------------------
| name                                                                                            | c  |
========================================================================================================
| "International Colloquium on Automata, Languages, and Programming"@en                           | 40 |
| "International Symposium on Mathematical Foundations of Computer Science"@en                    | 38 |
| "Annual Cryptology Conference"@en                                                               | 33 |
| "Annual International Conference on the Theory and Applications of Cryptographic Techniques"@en | 33 |
| "International Conference on Applications and Theory of Petri Nets and Concurrency"@en          | 32 |
| "International Workshop on Graph-Theoretic Concepts in Computer Science"@en                     | 32 |
| "International Symposium on Distributed Computing"@en                                           | 29 |
| "International Conference on Computer Aided Verification"@en                                    | 28 |
| "International Conference on Concurrency"@en                                                    | 28 |
| "International Conference on Information Security and Cryptology"@en                            | 28 |
| "International Conference on Advanced Information Systems Engineering"@en                       | 27 |
| "International Semantic Web Conference"@en                                                      | 27 |
| "Ada-Europe International Conference on Reliable Software Technologies"@en                      | 26 |
| "European Conference on Object-Oriented Programming"@en                                         | 26 |
| "European Symposium on Programming Languages and Systems"@en                                    | 25 |
| "International Conference on Conceptual Modeling"@en                                            | 25 |
| "International Workshop on Languages and Compilers for Parallel Computing"@en                   | 25 |
| "Annual Symposium on Combinatorial Pattern Matching"@en                                         | 24 |
| "Annual Symposium on Theoretical Aspects of Computer Science"@en                                | 24 |
| "International Conference on Algorithmic Learning Theory"@en                                    | 24 |
--------------------------------------------------------------------------------------------------------

Cela donne probablement un aperçu des thématiques les plus abordées par Springer.

Actualisation de la base

Des documents scientifiques sont publiés chaque mois.

Pour me faire une idée de la fraîcheur des données disponibles ici, je fais un premier test sur un livre auquel j’ai contribué -« Multimodal Interaction with W3C Standards »- dont le DOI est: 10.1007/978-3-319-42816-1. Il n’est pas dans la base le 3/12/2016.

Quelques prédicats suggèrent des informations de date. Je vais chercher la date la plus récente présente dans la base. Je vais utiliser le prédicat de ce type le plus fréquent: spr:chapterRegistrationDate, qui donne des dates de la forme

 "2002-01-01"^^xs:date

La requête

select distinct ?date where {
?s spr:chapterRegistrationDate ?date  .
}
order by desc(?date)
limit 5

donne le résultat surprenant suivant

-------------------------
| date                  |
=========================
| "2017-09-09"^^xs:date |
| "2017-07-25"^^xs:date |
| "2017-06-14"^^xs:date |
| "2017-05-20"^^xs:date |
| "2016-12-19"^^xs:date |
-------------------------

Le dernier document enregistré l’a été dans le futur!?!

En tout cas, cela suggère que cette base est régulièrement actualisée -même si les dates affichées doivent être interprétées d’une façon que j’ignore pour le moment.

Conclusion

Cette exploration confirme ce dont j’ai l’intuition depuis le début du projet SemBib: il y a de plus en plus de sources de données bibliographiques, mais chacune a ses propres objectifs et est incomplète pour d’autres objectifs, comme ceux de Sembib.

Cela confirme aussi l’axe choisi pour Sembib: constituer un graphe de données propre à SemBib, mais interconnecté avec d’autres graphes. SemBib plaide pour une fédération de graphes bibliographiques interconnectés.

Publié dans Données publiques, SemBib, SPARQL | Laisser un commentaire

Sémantique de la virgule

Texte de Piaf - Hymne à l'amour

Il y a quelques temps, dans une rame de métro, mon regard a été attiré par une petite affiche avec le contenu suivant:

Le ciel bleu sur nous peut s’effondrer
Et la terre peut bien s’écrouler
Peu m’importe si tu m’aimes
Je me fous du monde entier

Un extrait de la chanson d’Edith Piaf « L’hymne à l’amour ».

Il m’a semblait qu’il manquait une virgule pour être cohérent avec le sens général de la chanson.

En fait, sans virgule, le texte est ambiguë. On peut le lire de deux façons diamétralement opposées, qu’on peut révéler avec l’ajout de virgules.

Une première lecture donne:

Peu m’importe si tu m’aimes, 
Je me fous du monde entier

Qui peut se lire: je me moque de savoir si tu m’aimes et de toute façon, je me fous de tout.

Une deuxième lecture, avec deux virgules donne:

Peu m’importe, si tu m’aimes, 
Je me fous du monde entier

Qui peut se lire: si tu m’aimes, rien d’autre n’a d’importance. Très différent de la première lecture.

Une subtilité difficile à saisir dans les traitements automatiques…

Publié dans Cuisine traitement de textes, Marquage sémantique | Laisser un commentaire

Thèse sur l’enrichissement sémantique de livres numériques

Vous êtes invités à la soutenance de thèse de Vincent Gros, intitulée « Modélisation d’un livre numérique adaptable par enrichissement sémantique des contenus – Réalisation par le  standard EPUB », menée dans le cadre d’un partenariat CIFRE entre Hachette Livre et Telecom ParisTech.

Elle aura lieu le 29 septembre à 15h00 à Télécom ParisTech, salle H2.

Modélisation d’un livre numérique adaptable par enrichissement sémantique des contenus – Réalisation par le standard EPUB

Résumé : Avant le fort développement du Web, le livre était la source d’information privilégiée et ce depuis l’invention de l’imprimerie. Les qualités du livres sont diverses : entre autres, les informations sont réunies en un ensemble cohérent, avec pour la plupart des ouvrages un fil conducteur du début jusqu’à la fin, qui se traduit par une lecture linéaire du contenu page après page. Mais un livre imprimé ne peut s’adapter directement aux besoins particuliers d’un individu : c’est un objet statique, imprimé une fois pour toute. L’évolution numérique a certes permis l’émergence de formats comme le standard EPUB, offrant davantage de fonctionnalités, notamment en termes d’interactivité et de multimédia. Mais le comportement des livres numériques ne diffèrent pas des principes du livre papier. Peut-on définir le livre numérique comme un objet dynamique, qui peut s’adapter aux besoins de l’utilisateur ?

Nous proposons de réfléchir à des livres dont les modes d’interaction et la présentation s’adaptent à l’utilisateur selon ses objectifs. C’est particulièrement intéressant pour les livres pratiques : les utilisateurs ne sont pas tous intéressés par les mêmes informations et n’ont pas les mêmes attentes, capacités ou centres d’intérêts. Pour cela, si le livre numérique tire ses origines depuis le livre papier, l’hypertexte, défini par Nelson dès 1965, peut en être une référence majeure. Des approches prometteuses dans la recherche viennent notamment des systèmes hypermédias adaptatifs et des documents virtuels personnalisables. Nous définissons le livre numérique comme un ensemble autonome de contenus organisé selon trois représentations : une représentation sémantique des données, une représentation logique de la structure du livre et la mise en forme esthétique du contenu présenté à l’utilisateur. Nous proposons ensuite d’intégrer un mécanisme d’adaptation du contenu au sein des livres numérique, déterminé par des caractéristiques utilisateurs. Nous implémentons cette approche en utilisant le standard EPUB et en facilitant la construction des fichiers par un workflow XML étendu par des règles de mise en correspondance entre la source et les représentations internes au livre.

 

Mots clés : Interaction homme-machine, Ingénierie documentaire, Ingénierie des connaissances, technologies XML, Web Sémantique, Hypermédia adaptatif, EPUB, chaîne de production éditoriale.

Publié dans Livre numérique, Marquage sémantique, Outils | Laisser un commentaire

Statistiques sur DBPedia-Fr

J’ai eu besoin du nombre d’entités distinctes décrites dans dbpedia-fr. Nous allons voir un problème à prendre en compte lors de l’utilisation des données liées faisant appel à des points d’accès publics.

Ma première tentative a été d’obtenir cette information avec la requête

select count(distinct ?r) where { ?r ?p ?l }

mais celle-ci échouait systématiquement sur un timeout.

Alors que si je me limitais à compter les triplets avec

select count(?r) where { ?r ?p ?l }

j’obtenais

185404575

Nandana Mihindukulasooriya m’a indiqué l’outil Loupe pour connaître des statistiques sur des jeux de données, mais je voulais pouvoir obtenir l’information directement par programme en interrogeant la source que j’utilisais: ici, DBPedia-Fr, mais potentiellement avec une méthode applicable à d’autres jeux de données.

Hugh Williams m’a indiqué que ma requête était fortement consommatrice de ressources (dans l’implémentation de Virtuoso qui héberge DBPedia et qui, semble-t-il passe par la construction d’une très grande table de hashage). Il m’a signalé que DBPedia (anglais) propose une description à jour de son jeux de données suivant les principes proposé par la note technique sur VoID: VoID pour DBPedia; cela évite que des requêtes lourdes à traiter soient effectuées de façon répétitives pour obtenir ces informations. Mais la version française ne propose pas cela à la date de rédaction de ce billet.

John Walker m’a proposé de réécrire ma requête de la façon suivante:

select (count(?s) as ?c) where { select distinct ?s where { ?s ?p []} }

il semble que cette écriture consomme moins de ressources, et j’obtiens le résultat attendu:

10515620

Pourquoi cette écriture est plus efficace que l’autre? Elle me semble sémantiquement équivalente; il faudrait donc comprendre le détail de la façon dont elle est implémentée pour imaginer quelle écriture est préférable. A ce sujet, on trouve un chapitre entier sur l’optimisation de requêtes dans le très bon livre de Bob du Charme sur SPARQL.

Alasdair J G Gray me signale le rapport Dataset Descriptions: HCLS Community Profile et en particulier la section 6.6 où des exemples de statistiques à obtenir sur des jeux de données sont présentés avec les requêtes SPARQL qui permettent de les obtenir.

Les résultats ci-dessous correspondent à l’application de ces requêtes à DBPedia-Fr.

Description Requete Resultat
Number of triples SELECT (COUNT(*) AS ?triples) { ?s ?p ?o } 185404575
Number of distinct typed entities SELECT (COUNT(DISTINCT ?s) AS ?entities) { ?s a [] } 6015375
Number of distinct subjects SELECT (COUNT(DISTINCT ?s) AS ?distinctSubjects) { ?s ?p ?o } time out
Number of distinct properties SELECT (COUNT(DISTINCT ?p) AS ?distinctProperties) { ?s ?p ?o } 20321
Number of distinct non-literal objects SELECT (COUNT(DISTINCT ?o ) AS ?distinctObjects){ ?s ?p ?o FILTER(!isLiteral(?o)) } time out
Number of distinct classes SELECT (COUNT(DISTINCT ?o) AS ?distinctClasses) { ?s a ?o } 442
Number of distinct literal objects SELECT (COUNT(DISTINCT ?o) AS ?distinctLiterals) { ?s ?p ?o filter(isLiteral(?o)) } time out
Number of graphs in the data set SELECT (COUNT(DISTINCT ?g ) AS ?graphs) { GRAPH ?g { ?s ?p ?o }} 14

Les mêmes requêtes appliquées à DBPedia donnent:

Description Requete Resultat Donnes VoID
Number of triples SELECT (COUNT(*) AS ?triples) { ?s ?p ?o } 438038746 438038866
Number of distinct typed entities SELECT (COUNT(DISTINCT ?s) AS ?entities) { ?s a [] } rien
Number of distinct subjects SELECT (COUNT(DISTINCT ?s) AS ?distinctSubjects) { ?s ?p ?o } rien 33996245
Number of distinct properties SELECT (COUNT(DISTINCT ?p) AS ?distinctProperties) { [] ?p ?o } 64119 64119
Number of distinct non-literal objects SELECT (COUNT(DISTINCT ?o ) AS ?distinctObjects){ ?s ?p ?o FILTER(!isLiteral(?o)) } rien 164615518 (?)
Number of distinct classes SELECT (COUNT(DISTINCT ?o) AS ?distinctClasses) { [] a ?o } 370680 370680
Number of distinct literal objects SELECT (COUNT(DISTINCT ?o) AS ?distinctLiterals) { ?s ?p ?o filter(isLiteral(?o)) } 29883287
Number of graphs in the data set SELECT (COUNT(DISTINCT ?g ) AS ?graphs) { GRAPH ?g { ?s ?p ?o }} 19
Number of distinct objects 164615518

Pour obtenir certains résultats, il a fallu augmenter le temps accordé à la requête pour être traitée; pour d’autres, la requête proposée dans le document ci-dessus a été un peu modifiée. DBPedia ne provoque pas de ‘time out’, mais renvoie une réponse vide quand il n’a pas pu traiter la requête (voir discussion ici). Les gestionnaires de DBPedia proposent de ne pas considérer leur serveur comme serveur de production et d’en installer une copie si on veut des résultats plus garantis; en effet, ils doivent faire face à une grande masse d’utilisateurs et ne peuvent offrir une totale garantie de service. Il en résulte que certaines requêtes, même en augmentant le délai de traitement, on n’obtient pas de résultat.

On constate néanmoins que DBPedia fait un usage beaucoup plus intensif des classes que DBPedia-Fr. Je reviendrais sur l’usage des classes dans un prochain billet.

Nous avons vu ici des contournements à certains problèmes de délai de traitement de certaines requêtes; mais ces contournements ne marchent pas toujours, même sur des requêtes qui paraissent simples et d’usage élémentaire (compter certains types d’éléments dans un jeu de données).

Mise à jour du 20/4/2018

Kingsley Idehen me pointe sur

https://medium.com/virtuoso-blog/dbpedia-usage-report-as-of-2018-01-01-8cae1b81ca71

et plus précisément à la section Virtuoso « Anytime Query » qui montre comment DBPedia ajoute un en-tête dans la réponse pour donner une alerte sur les résultats incomplets lorsque le timeout est atteint

 

Publié dans DBPedia, SPARQL, Tutoriel | Laisser un commentaire

Héberger une instance de Fuseki sur OpenShift

Nous allons voir comment héberger une instance de serveur RDF Fuseki sur OpenShift, l’hébergement de RedHat.

Vous devez avoir un compte sur OpenShift (possibilité de comptes gratuits).

Sur la console d’administration OpenShift, créez une application de type ‘Do It Yourself’. Cela va prendre quelques minutes. Ici, je nomme l’application fuseki.

Chargez l’outil RHC , outil RedHat qui facilitera la configuration (attention: à la date d’écriture de cet article, rhc ne fonctionne bien qu’avec la version 1.9.3 de ruby, pas avec les versions ultérieures).

Dans une fenêtre de ligne de commande Windows, en tapant

rhc show-app fuseki

Vous verrez les caractéristiques de l’application créée, dont l’URL du git distant utilisé pour gérer l’installation de l’application. Comme vous avez créé l’application à partir de la console web, vous devez la cloner localement. Créez un directory pour contenir l’application (fuseki pour l’exemple), puis clonez le git

git clone <url du git distant> fuseki

Allez dans ce directory.

Pour récupérer sur github une installation de base toute prête, tapez

git remote add fuseki -m master git://github.com/semfact/openshift-fuseki.git

puis

git pull -s recursive -X theirs fuseki master

Vous avez localement une version 0.2.7  de Fuseki (qu’il faudra mettre à jour).

Poussez-la sur OpenShift

git push

L’application est alors accessible à l’URL

http://fuseki-$youropenshiftaccountname.rhcloud.com

On ne va pas essayer tester de façon approfondie cette version ancienne, mais plutôt faire dès maintenant une mise à jour.

Mise à jour de Fuseki

Sur la page Apache Fuseki, on trouve un zip à télécharger; on va se limiter à la version 1.4, car la version 2.4 nécessite Java 8 qui n’est pas encore disponible en standard sur OpenShift et nécessite pas mal d’ajustements (la version 2.4 permet notamment un meilleur contrôle des autorisations). On va le décompresser dans le dossier de notre application. Cela va créer un dossier jena-fuseki-1.4.0.

Dans le dossier .openshift/action_hooks, dans le fichier start, modifier la ligne

cd $OPENSHIFT_REPO_DIR/fuseki

par

cd $OPENSHIFT_REPO_DIR/jena-fuseki1-1.4.0

 

Ajouter et commiter les modifications faites à votre application:

git add *

git commit -a -m "Ajout de la version 1.4.0 de Fuseki"

Pousser le tout sur OpenShift

git push

Cela peut être assez long.

Quand c’est terminé, retournez sur

http://fuseki-$youropenshiftaccountname.rhcloud.com

Tests avec Fuseki

Dans le fichier start du dossier .openshift/action_hooks  sont indiquées les commandes exécutées par OpenShift au lancement de votre application.

Sur la ligne

nohup java -jar fuseki-server.jar --jetty-config ../conf/jetty.xml --config ../conf/services.ttl > $OPENSHIFT_DIY_LOG_DIR/fuseki.log 2>&1 &<br>

se trouve l’option –config suivie du chemin du fichier de configuration de Fuseki.

Remplacez ce chemin par config.ttl:

nohup java -jar fuseki-server.jar --jetty-config ../conf/jetty.xml --config config.ttl > $OPENSHIFT_DIY_LOG_DIR/fuseki.log 2>&1 &

Commitez et poussez le résultat.

En allant à l’adresse, http://fuseki-$youropenshiftaccountname.rhcloud.com vous voyez l’interface d’utilisation de Fuseki. Clic sur Control Panel, puis sur books.

Apparaît une page avec une interface d’interrogation (query), une interface de mise à jour (update) et une interface de chargement de données (upload); ces deux dernières ne fonctionneront pas car, comme nous le verrons plus, la base n’est configurée qu’en lecture pour le moment.

Tapez une requête sparql dans la première interface, par exemple:

select * where { ?s ?p ?o }

et vous obtiendrez une série de triplets définis dans le dataset books.

Ces données sont accessibles sur votre instance OpenShift de Fuseki parce qu’un ‘service’ est défini dans le fichier config.ttl:

<#service2> rdf:type fuseki:Service ;
fuseki:name "books" ;
fuseki:serviceQuery "query" ;
fuseki:serviceReadGraphStore "get" ;
fuseki:dataset <#books> ;
.

<#books> rdf:type ja:RDFDataset ;
rdfs:label "Books" ;
ja:defaultGraph 
[ rdfs:label "books.ttl" ;
a ja:MemoryModel ;
ja:content [ja:externalContent <file:Data/books.ttl> ] ;
] ;
.

Le format est du Turtle générique. les spécificités sont -modérément- documentées sur le site de Fuseki.

Il est indiqué ici

  • qu’un dataset du nom de books va être mis en place,
  • qu’il sera accessible par les commandes query et get,
  • que les données sont définies dans le dataset <#books> qui suit

suit la description du dataset:

  • il est associé au label Books
  • il sera exploité en mémoire
  • le contenu initial proviendra du fichier books.ttl du sous-dossier Data du dossier de lancement de fuseki

Vous pouvez sur ce modèle exploiter vos propres données en mettant à jour sur votre instance locale le fichier Turtle de vos données et en le référençant dans le fichier config.ttl. Cela permet d’éviter de laisser votre instance OpenShift ouverte en écriture et de publier vos données via git à partir de leur représentation locale.

Pour une utilisation par logiciel, vous avez le service query, par exemple, la requête de tout à l’heure peut être appelée avec l’URL

http://fuseki-onsem.rhcloud.com/books/query?query=select+*+where+%7B+%3Fs+%3Fp+%3Fo+%7D&output=text&stylesheet=

On voit l’URL de l’instance, le nom du dataset, la commande query, suivie de paramètres, ici:

  • query: la requête proprement dite, formattée pour une URL
  • output: le format de sortie
  • stylesheet: une éventuelle feuille de style pour mettre en forme la sortie si elle est au format xml

Vous y êtes: vous avez un entrepôt de triplets en place. A vous de jouer avec vos propres données!

Publié dans Outils, SPARQL, Tutoriel | Laisser un commentaire

Utiliser NLTK sur Heroku avec Python

Sur le principe du billet « Extraire le texte de PDF avec Python« , je vais créer un service qui utilise le package NLTK. NLTK est un ensemble d’outils pour construire en Python des programmes de traitement des langues. Il nécessite donc d’utiliser Python.

Phases de base

Je résume les étapes détaillées dans le billet mentionné ci-dessus:

  • créer un dossier pour ce service
  • créer un environnement virtuel pour le développement local avec la commande
  • créer un fichier requirements.txt avec la liste des dépendances (dont nltk, voir plus bas)
  • créer un dossier nltk_service
  • dans ce dossier, créer deux fichiers: __init__.py and resources.py (vides pour l'instant)
  • démarrer un serveur local (en remplaçant pdf_service par nltk_service dans runserver.py)
  • créer un dépot git
  • ajouter les fichiers du projet au dépot git local
  • mettre à jour le dépot git local
  • se connecter à Heroku
  • créer un nouveau service sur Heroku
  • pousser le développement local vers heroku
  • lancer une instance du service
  • tester en ligne

Là, on a créé un service vide. Nous allons le compléter avec une mise en oeuvre d’une des possibilités offertes par NLTK.

Un service avec NLTK

Je veux créer un service qui trouve les mots et autres éléments qui composent un texte, puis élimine les mots creux (stopwords) -ces mots qui ne contribuent pas fortement à certaines analyses du contenu d’un texte, comme ‘le’, ‘la’, ‘les’, ‘à’ en français- et enfin, compte le nombre de mots du texte et le nombre d’occurrences de chaque mot conservé.

Pour éliminer les stopwords -ainsi que pour de nombreux autres traitements- NLTK utilise des fichiers de données définis sous la dénomination ‘NLTK Data‘. Des fichiers listant  les mots creux d’un ensemble de langues sont disponibles. Mais Heroku n’est pas très adapté à l’utilisation de gros volumes de fichiers statiques; Heroku est fait pour héberger et exécuter des programmes.

Hébergement de fichiers statiques pour Heroku

Dans l’article « Using AWS S3 to Store Static Assets and File Uploads« , il est suggéré qu’une bonne scalabilité peut être obtenue en déployant sur Amazon S3 les fichiers statiques utilisés par une application. En effet, les fichiers hébergés sur Heroku peuvent être déchargés à chaque mise en veille d’une application, puis rechargés pour une nouvelle utilisation. S’il s’agit d’un gros volume de fichiers statiques, cela peut pénaliser les temps de réponse d’une application. Le système de fichier proposé par Heroku est dit ‘éphémère’.

Normalement, les données associées à NLTK sont téléchargées avec un programme interactif qui permet de sélectionner ce qu’on va télécharger et s’assure que les données chargées seront trouvées par les outils NLTK. Dans le cas d’Heroku, nous ne pouvons pas procéder ainsi, mais plutôt chercher à utiliser un chargement en ligne de commande.

C’est pourtant la première méthode que j’ai exploré. La première idée est de charger les données en local, puis de les pousser sur Heroku, mais cela chargerait le dépot GIT qui nous sert dans nos échanges avec Heroku avec toutes les données statiques de nltk-data. Une solution est proposée ici: http://stackoverflow.com/questions/13965823/resource-corpora-wordnet-not-found-on-heroku/37558445#37558445. C’est la solution que j’ai retenu en première approche. Un essai avec toutes les données de nltk_data échoue (all). Avec juste les corpus stopwords (python -m nltk.downloader stopwords) et wordnet (python -m nltk.downloader wordnet) et le tokenizer punkt (python -m nltk.downloader punkt), le déploiement se déroule correctement.

Une autre idée est d’utiliser AWS Simple Storage Service, service de stockage sur le Cloud d’Amazon. J’explorerai cette possibilité dans un prochain billet.

Le service

from flask import request, abort
from flask.ext import restful
from flask.ext.restful import reqparse
from nk_service import app
import urllib2
import nltk.data
import io
import os
import collections
import json
from nltk.tokenize import word_tokenize
from nltk.corpus import wordnet
from nltk.corpus import stopwords

english_stops = set(stopwords.words('english'))
french_stops = set(stopwords.words('french'))
stops = english_stops.union(french_stops)

def words_counting(wordslist):
    wordscounter = {}
    wordscounter["cnt"] = collections.Counter()
    wordscounter["wordscount"] = 0
    for w in wordslist:
        word = w.lower()
        if word not in stops:
            syn = wordnet.synsets(word)
            if syn:
                lemmas = syn[0].lemmas()
                res = lemmas[0].name()
                wordscounter["cnt"][res] += 1
                wordscounter["wordscount"] += 1
    wordscounter["cnt"] = wordscounter["cnt"].most_common()
    return wordscounter

def filewords(path):
    text = urllib2.urlopen(path).read().decode('utf-8')
    wordslist = word_tokenize(text)
    jsonwords = words_counting(wordslist)
    return json.dumps(jsonwords)

@app.route('/words/<path:url>', methods=['GET'])
def get_words(url):
    return filewords(url)

L’appel de <mondomaine>/words/<url d’un fichier texte> renvoie une structure json avec un tableau des mots associés à leur nombre d’occurrence (dans le tableau cnt) et le nombre total de mots pris en compte (dans wordscount »). ceci permettra d’agréger facilement les résultats de plusieurs fichiers et de calculer des fréquences d’occurrences de mots.

Conclusion

L’utilisation de NLTK avec Heroku est validée. Il faut maintenant définir la ou les structures (a priori json) qui vont nous permettre de transporter et d’enrichir progressivement les données associées à une source ou à un ensemble de sources.

Publié dans Cuisine traitement de textes, SemBib | Laisser un commentaire

Extraire le texte de PDF avec Python

Dans le cadre de notre projet d’analyse de la production scientifique de Télécom ParisTech, je récupère beaucoup de fichiers PDF. Pour en analyser le contenu, j’ai notamment besoin d’en récupérer le texte brut. Par ailleurs, comme indiqué dans le billet Des services pour l’analyse bibliographique, j’ai fait le choix d’appuyer nos développements sur des web services.

Je vais montrer ici comment je développe un service REST d’extraction de texte brut à partir de PDF en Python que je déploie sur Heroku (note: je développe sous Windows 10).

Note: je suis fraîchement convertit à Python; mon code n’a rien d’exemplaire et mérite surement d’être nettoyé/amélioré, mais il m’a suffit comme preuve de faisabilité

Créer un dossier pour mon service

Créer un environnement virtuel pour le développement local avec la commande

virtualenv venv

Créer un fichier requirements.txt avec le contenu suivant

pdfminer==20140328
Flask==0.11
Flask-Login==0.2.11
Flask-RESTful==0.3.2
aniso8601==0.82
Jinja2==2.7.3
MarkupSafe==0.23
Werkzeug==0.9.6
gunicorn==19.4.5
itsdangerous==0.24
six==1.7.2

Le fichier indique le package pdfminer pour le traitement de fichiers pdf.

(voir http://spapas.github.io/2014/06/30/rest-flask-mongodb-heroku/ pour des explications génériques sur les dépendances du projet)

Dans le dossier qui contient le fichier requirements.txt, créer un dossier pdf_service. Dans ce dossier, créer deux fichiers: __init__.py and resources.py.

__init__.py initialise l’application Flask qui va être créée

import os
from flask import Flask, request, jsonify
from flask_restful import Resource, Api
from flask import make_response

app = Flask(__name__)

import pdf_service.resources

Et le fichier resources.py

import json
from flask import request, abort
from flask.ext import restful
from flask.ext.restful import reqparse
from pdf_service import app
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfpage import PDFPage
import urllib2
from urllib2 import Request
from StringIO import StringIO


def convert_pdf_to_txt(path):
    rsrcmgr = PDFResourceManager()
    retstr = StringIO()
    codec = 'utf-8'
    laparams = LAParams()
    device = TextConverter(rsrcmgr, retstr, codec=codec, laparams=laparams)


    open = urllib2.urlopen(Request(path)).read()
    memoryFile = StringIO(open)

    parser = PDFParser(memoryFile)

    doc = PDFDocument(parser)
    parser.set_document(doc)

    interpreter = PDFPageInterpreter(rsrcmgr, device)
    password = ""
    maxpages = 0
    caching = True
    pagenos=set()

    for page in PDFPage.get_pages(memoryFile, pagenos, maxpages=maxpages,        password=password,caching=caching, check_extractable=True):
        interpreter.process_page(page)

    text = retstr.getvalue()

    device.close()
    retstr.close()
    print(text)
    return text


@app.route('/', methods=['GET'])
def get_root():
    return "index"


@app.route('/pdftotxt/<path:url>', methods=['GET'])
def get_url(url):
    return convert_pdf_to_txt(url)

Pour démarrer un serveur local

Pour les tests, j’ai créé le fichier runserver.py

from pdf_service import app
app.run(debug=True)

lancé avec la commande

python runserver.py

Pour créer un dépot git vide

git init

Pour ajouter les fichiers locaux dans le git

git add .

Attention à créer un  .gitignore en indiquant les fichiers et directories à ignorer pour ne pas surcharger la communication

Pour alimenter le dépot git local

Qui va servir de référence pour les échanges avec Heroku

git commit -m "mon message texte"

Pour se connecter à Heroku

heroku login

puis les informations de votre compte

Pour détruire un service sur Heroku

Par exemple, pour se faire de la place, supprimer des tests devenus inutiles…

heroku apps:destroy –app <nom du service à détruire>

Pour créer le service sur heroku

heroku create

Pour pousser le développement local vers heroku

git push heroku master

(par défaut, provoque le déploiement d’un Python 2.7 et des dépendances décrites dans requirements.txt)

Pour lancer une instance du service

heroku ps:scale web=1

Pour voir ce qui tourne

heroku ps

Renommer le service

Utilisation de la ligne de commande pour renommer le service (voir https://devcenter.heroku.com/articles/renaming-apps)

Commande pour changer le nom du service (à partir de celui généré automatiquement par les outils heroku) et du dépot git associé (à faire dans le dossier où a été créé le service)

heroku apps:rename <nouveau nom>

 

Publié dans Cuisine traitement de textes, SemBib | 2 commentaires

Un pays sans guerre

J’ai vu passer la question « y a-t-il un pays qui n’a jamais été en guerre? » qui renvoie à

« World peace? These are the only 11 countries in the world that are actually free from conflict« 

et je me suis dit, voilà un bon exercice pour le web sémantique. Je pense qu’il va m’être utile pour illustrer le pouvoir et les limites des grosses masses de données rendues librement disponibles sur le web et exploitables par des machines (LOD, Linked Open Data).

Les articles ci-dessus suggèrent une dimension particulière de la question: la dimension temporelle! De quelle époque parle-t-on? J’y reviendrais. Mais, d’abord interrogeons quelques concepts: pays, guerre, région.

Pays

D’abord, voyons ce que nous avons comme pays dans DBPedia. Commençons par le concept Country

select count(distinct ?pays) where {
?pays a <http://dbpedia.org/ontology/Country> .
}

donne 3294. Surprenant! J’avais à l’esprit qu’il y a un peu moins de 200 pays dans le Monde. Une recherche google « nombre de pays dans le monde » donne d’autres indications. On trouve par exemple que l’IEP prend en compte 162 pays dans une étude récente. Et l’ONU semble en reconnaître 197. Dans ce cas, il s’agit seulement de pays constituant des états souverains.

Wikipedia nous donne des indications sur une dimension temporelle. L’article su la Liste des pays du Monde évoque l’évolution du nombre de pays au cours du temps.

Même si DBpedia recense des états aujourd’hui disparus, la disproportion est énorme. Nous allons devoir comprendre ce qui différencie le concept Country dans DBPedia et, par exemple, les pays reconnus par l’ONU et comment concilier les deux approches.

On constate par exemple sur http://dbpedia.org/resource/Venezuela que de nombreux types sont associés à cette entité, notamment: Country selon Yago (5528 entités), schema.org (5506 entités), umbel (2385 entités) et wikidata:Q6256 (5506 entités). Ces variantes ne vont surement pas nous aider.

Guerre

Voyons les conflits militaires recensés par DBPedia. J’ai retenu le concept MilitaryConflict.

select count(distinct ?conflict) where {
?conflict a <http://dbpedia.org/ontology/MilitaryConflict> .
}

donne 13354 (au 29/5/2016).

Maintenant, voyons les conflits explicitement reliés à un pays

select count(distinct ?conflict) where {
?conflict a <http://dbpedia.org/ontology/MilitaryConflict> .
?conflict ?p ?pays .
?pays a <http://dbpedia.org/ontology/Country>
}

donne 7750. Et le nombre de pays associés à ces conflits est donné par

select count(distinct ?pays) where {
?conflict a <http://dbpedia.org/ontology/MilitaryConflict> .
?conflict ?p ?pays .
?pays a <http://dbpedia.org/ontology/Country>
}

On en trouve 1115. En se limitant à visualiser 100 pays concernés, on trouve , par exemple, « Province de Québec ». Pour arriver à une forme d’agrégation en rattachant un conflit aux pays actuels, nous devrons voir si un ‘pays’ associé à un conflit peut être identifié comme étant une partie d’un pays existant aujourd’hui.

32 propriétés différentes sont utilisées pour ces liens et j’ai un peu de mal avec l’interprétation à leur donner. Certaines se comprennent assez bien. Par exemple: la propriété Place est surement utilisée pour indiquer le lieu du conflit; la propriété combatant indique probablement l’origine des combattants impliqués. Dans les deux cas, on pourra bien considérer que les pays désignés étaient impliqués dans le conflit concerné.

Un autre regard peut être porté sur les données de DBPedia en constatant que certains conflits sont associés par la propriété wordnet_type au synset https://www.w3.org/2006/03/wn/wn20/instances/synset-war-noun-1.rdf.

Epoque

Dans l’idée de préciser l’époque sur laquelle nous allons porter notre intérêt.

Y a-t-il des dates ou des époques associées aux conflits recensés par DBPedia?

On en trouve associés à une ou plusieurs dates (de début? de fin?), d’autres avec endDate et startDate, d’autres sans indications directes, mais avec des liens vers des batailles ayant des indications de date.

Lorsque ces informations sont disponibles nous allons pouvoir affirmer qu’un pays concerné était en guerre aux périodes concernées. Dans le cas contraire, nous ne pouvons rien affirmer: nous ne savons pas à quelle époque le pays a été concerné par le conflit observé.

Nous allons déjà pouvoir tenter de recenser les pays qui ont été impliqués dans un conflit recensé par DBPedia (auxquels manquent probablement des conflits contemporains).

Région

Pour simplifier, j’ai cherché à identifier les pays reconnus par l’ONU. Il y a un concept dans DBPedia qui doit pouvoir nous aider: Member State of the United Nations. Mais sur quelques exemples, je constate que ce concept est associé à des pays par la propriété dc:subject, ce qui est un peu vague. Cela me donne l’occasion de voir que des pays sont associés par la propriété rdf:type à yago:MemberStatesOfTheUnitedNations, ce qui parait plus précis.

select count(distinct ?pays) where {
?pays a <http://dbpedia.org/class/yago/MemberStatesOfTheUnitedNations> .
}

nous donne 186 pays.

Je vais m’intéresser à ces pays pour évaluer s’ils ont été associés à un conflit militaire à un moment ou à un autre.

select count(distinct ?pays) where {
?pays a <http://dbpedia.org/class/yago/MemberStatesOfTheUnitedNations> .
?conflict a <http://dbpedia.org/ontology/MilitaryConflict> .
{ ?conflict ?p1 ?pays } UNION { ?pays ?p2 ?conflict }
}

donne 139. Ce qui semble indiquer que 47 pays des Nations Unies ne sont ou n’ont pas été en rapport avec un conflit militaire à la connaissance de DBPedia pour autant qu’on puisse en juger par des connaissances directement représentées dans DBPedia (et pas des connaissances induites).

Il va nous falloir exprimer une négation: pays qui ne sont pas dans la liste des pays ayant eu conflit militaire.

Je pense que la requête

select distinct ?pays where {
?pays a yago:MemberStatesOfTheUnitedNations .
MINUS {
   ?conflict a dbo:MilitaryConflict .
   { ?pays ?p1 ?conflict } UNION {?conflict ?p2 ?pays }
}
}

va nous donner les pays n’étant reliés par aucune propriété de DBPedia avec un conflit militaire. Cela donne 15 résultats:

pays
http://dbpedia.org/resource/Côte_d’Ivoire
http://dbpedia.org/resource/Andorra
http://dbpedia.org/resource/Benin
http://dbpedia.org/resource/Kiribati
http://dbpedia.org/resource/Liechtenstein
http://dbpedia.org/resource/Malawi
http://dbpedia.org/resource/Monaco
http://dbpedia.org/resource/Tuvalu
http://dbpedia.org/resource/Vanuatu
http://dbpedia.org/resource/Zimbabwe
http://dbpedia.org/resource/Burma
http://dbpedia.org/resource/Federated_States_of_Micronesia
http://dbpedia.org/resource/Member_states_of_the_United_Nations
http://dbpedia.org/resource/Kingdom_of_the_Netherlands
http://dbpedia.org/resource/Timeline_of_the_United_Nations

2 résultats ne sont pas des pays de façon évidente (mais qu’il faudrait rendre détectable dans la requête). Reste 13 résultats qu’il va falloir regarder d’un peu plus près. Je ne vais pas tous les passer en revue, mais regarder quelques exemples significatifs.

Monaco est un tout petit état. La cité-état a été annexée par Jules César, par exemple; sans combat? sans usage de la force? Elle a réussi a garder une certaine neutralité pendant la Seconde Guerre Mondiale. A-t-elle vraiment évité tout conflit armé?

La Micronésie est constituée d’un ensemble d’îles du Pacifique. Elle a notamment été envahie par le Japon lors de la Première Guerre Mondiale. Je pense qu’elle a donc connu au moins un conflit armé. Mais, il n’apparait pas en tant que tel dans DBPedia.

Un cas intéressant est la Côte-d’Ivoire. Wikipedia sait que ce pays connait des problèmes politico-militaires (voir https://fr.wikipedia.org/wiki/Crise_politico-militaire_en_C%C3%B4te_d%27Ivoire), mais DBPedia semble l’ignorer. Peut-être la question est-elle trop récente et actuelle?

Conclusion

Nous voyons à travers cet exemple que des données sont disponibles qui peuvent contribuer à répondre à des questions -et c’est déjà un grand progrès- mais qu’il y a encore beaucoup à faire, beaucoup d’intelligence à mettre pour faire le chemin des données vers la réponse à une question. Il faut utiliser les données avec prudence en comprenant les limites des réponses obtenues. Par exemple, dans le cas exposé dans ce billet, une formulation correcte d’une réponse qu’on peut obtenir est: liste des pays des Nations Unies dont on est certain, à partir des connaissances représentées dans DBPedia, qu’ils ont été liés à un conflit militaire.

Publié dans DBPedia, Marquage sémantique, SPARQL, Tutoriel | Laisser un commentaire

Des services pour l’analyse bibliographique

Je présente ici les besoins liés à notre démarche d’analyse de la production et de la publication de documents scientifiques -essentiellement des articles- par Telecom ParisTech.

Les articles

Télécom ParisTech dispose d’une base bibliographique qui recense l’essentiel de nos publications. Pour chaque publication, nous avons le titre, le nom des auteurs, la date de publication et quelques autres metadonnées. Quelquefois figure un lien vers la publication. Ce lien n’est pas toujours renseigné et, quand il l’est, il peut présenter différentes formes:

  • il s’agit quelquefois d’un lien vers un accès direct, en ligne, à une version numérique de l’article, le plus souvent au format PDF
  • il s’agit quelquefois d’un lien vers une page web qui contient un lien permettant d’accéder, souvent de manière onéreuse, à une version numérique de l’article
  • quelquefois, il s’agit d’un lien mort

Il arrive que les liens vers les documents numériques ne soient pas exploitables par un robot pour constituer un stock de l’ensemble des documents. Nous verrons dans un prochain billet les solutions mises en oeuvre pour récupérer la plus forte proportion possible des documents.

Une évaluation sur 5 ans donne environ 4000 publications dont un quart comporte un lien, exploitable automatiquement et facilement dans un peu de la moitié des cas. Sur certains documents pèse une limitation des droits de publication qui nous empêche de les mettre en ligne. Cela a un impact significatif sur les solutions que nous allons pouvoir exploiter.

Le stockage

A la date de publication de ce billet, nous avons 420 documents nécessitant 180 Mo pour les documents sources. Si nous parvenons à récupérer les 4000 documents, nous aurons besoin d’environ 2 Go pour les documents sources.

Nous prévoyons de stocker des résultats intermédiaires de traitement; par exemple, nous allons stocker

  • le texte brut extrait des documents PDF
  • le dictionnaire des mots associés à un document
  • des metadonnées associées à un document, issues de différentes étapes du traitement

Nous sommes plusieurs à travailler sur ce projet et nous prévoyons d’y associer des étudiants pour lesquels ce type de travail peut donner lieu à des projets très formateurs. Nous devons donc rendre nos sources et nos résultats accessibles via le réseau.

J’ai un accès à un stockage en ligne illimité via une solution d’hébergement qui, par ailleurs, ne supporte que les développements en PHP. C’est cet hébergement que je vais utiliser.

Les traitements

Un très bon outil pour appliquer des traitements sur des textes en vue de les analyser est NLTK. NLTK est écrit en Python. Donc, les traitements ne vont pas pouvoir être effectués au niveau de l’hébergement de nos sources.

Pour les traitements de représentations sémantiques, nous utilisons d’une part des outils écrits en Java s’appuyant sur Apache Jena/Fuseki, d’autre part un serveur Virtuoso accessible seulement sur le réseau interne de Telecom ParisTech.

La nécessité d’héberger des services écrits en différents langages, ce qu’aucune de nos solutions directes d’hébergement n’assure, nous conduisent à adopter une solution distribuée, basée sur des web services. Nous verrons dans un prochain billet la marche suivie pour créer un service en Python, exploitant NLTK et hébergé sur Heroku.

Point de départ algorithmique

Nous nous sommes inspiré de l’article « Using Linked Data Traversal to Label Academic Communities » de Tiddi, d’Aquin et Motta (SAVE-SD 2015). Cependant, nous avons complété ou modifié la procédure proposée. Par exemple, nous devons prendre en compte des publications en plusieurs langues (au moins le français et l’anglais); nous avons aussi décidé de nous appuyer sur Wordnet, lorque cela avait du sens. Nous avons entrepris la mise en oeuvre des étapes suivantes

  • constitution d’une liste des chercheurs de l’école comportant leurs liens avec les départements et les équipes de recherche
  • récupération de la liste des publications des 5 dernières années; la base bibliographique nous donne un résultat au format BIBTEX que nous avons traduit dans une structure JSON; une réflexion doit être menée pour rendre la solution plus modulaire, par exemple, en récupérant la base année par année, en prenant en compte la nécessité de mises à jour, certaines publications étant déclarées tardivement dans la base; de plus, les données obtenues présentent une série de défauts traités manuellement dans notre première phase de travail,
  • pour chaque référence, tenter de récupérer une version numérique du document
  • pour chaque référence récupérée, extraire le texte brut du document (voir https://onsem.wp.imt.fr/2016/06/02/extraire-le-texte-de-pdf-avec-python/)
  • pour chaque référence récupérée, réunir des metadonnées (auteurs, références citées…) soit depuis le bibtex ci-dessus, soit par analyse du document,
  • pour chaque référence récupérée, identifier la langue du document
  • passer chaque texte en minuscule
  • éliminer les mots vides dans chaque texte ainsi que les chiffres et les ponctuations
  • stemmatiser et/ou lemmatiser les mots de chaque texte
  • remplacer chaque racine issue de la stemmatisation par un mot qui partage cette racine (ex: le mot le plus court)
  • filtrer la liste des mots conservés (blacklist, longueur minimale…)
  • chercher un mapping de chaque mot vers un concept du web sémantique/LOD par exemple en se référant à DBPedia, à schema.org,à Wordnet, au vocabulaire de référence de l’IEEE et celui de l’ACM…; nous devrons évaluer le nombre de termes non mappés et chercher une solution pour ces termes
  • évaluer le nombre de mots par article, par corpus (ex: corpus de Telecom ParisTech pour un intervalle d’années donné, idem pour un département, pour un auteur)
  • construire la matrice TfIdf du corpus; on met en oeuvre des méthodes qui facilitent la mise à jour de cette matrice par exemple lors de l’ajout d’un nouvel article
  • réduire cette matrice en éliminant les mots présents dans une forte proportion de documents (application d’un seuil, par exemple 25%); ces mots étant considérés comme faiblement discriminant; ils peuvent être gardés comme potentiellement représentatifs de la production globale de Télécom ParisTech
  • réduire cette matrice en éliminant les mots présents un trop petit nombre de fois sur le corpus; ces mots étant considérés comme structurants pour le corpus
  • application d’une méthode recherche de sémantique latente (LSA)
  • clustering des mots restants soit par des méthodes de clustering appliquées à la matrice, soit en regroupant les mots sur des critères structurels de Telecom ParisTech (départements, équipes, projets…)

Ces différentes étapes doivent nous fournir les données de base pour nos analyses. Dans de prochains billets, nous étudierons plus en détail certaines de ces étapes. Nous allons aussi proposer des approches s’appuyant sur les technologies du web sémantique.

 

 

Publié dans Marquage sémantique, Outils, SemBib, Virtuoso | Un commentaire

Co-auteurs des publications d’une institution scientifique, Telecom ParisTech

Graphe des coauteursCet article fait partie d’une série concernant l’analyse de la production scientifique d’une communauté scientifique à partir de ses publications. Il s’agit d’un travail entrepris par Cyril Concolato et Jean-Claude Moissinac pour donner divers points de vue sur notre production scientifique. Nous pensons que les visualisations produites peuvent aider à faire émerger des pistes d’amélioration.

Cet article s’appuie sur la base bibliographique de Telecom ParisTech sur les années 2011 à 2015. Les données obtenues de notre base bibliographique au format bibtex (obtenu à cette adresse http://biblio.telecom-paristech.fr/cgi-bin/consultform.cgi)

ont été traduites en JSON et sont accessibles à l’adresse

http://givingsense.eu/demo/tpt/data/18112015.5ans.json

Il s’agit d’une première étape vers une représentation sémantique de notre base bibliographique, accompagnée d’outils pour produire diverses visualisations de la base.

La visualisation proposée ici est un graphe pour lequel chaque noeud est un auteur d’un article et les liens relient deux auteurs qui ont au moins un article en commun. La couleur des noeuds est représentative du département de rattachement de l’auteur lorsqu’il s’agit d’un permanent. L’affichage par défaut ne comporte que les permanents. En ajoutant le paramètre filter=all sur l’url, on voit tous les co-auteurs, mais, en l’état, le graphe n’est pas vraiment lisible. En cliquant sur un noeud associé à un permanent, on passe à une page qui ne concerne que cet auteur et ses co-auteurs.

Cliquez sur la capture ci-dessus pour aller consulter le site dynamique.

N’hésitez pas à commenter ce que vous voyez dans cette représentation (ou à signaler si vous voyez un défaut: nous avons déjà du nettoyer un peu les données brutes pour les rendre exploitables).

Publié dans Marquage sémantique, SemBib, Uncategorized, Visualisation | Un commentaire

WordLift est arrivé sur ce site

Après plusieurs tentatives pour intégrer du marquage sémantique sur ce blog, aucune des méthodes essayées précédemment n’était suffisamment satisfaisante.

L’essai en cours est de très loin le plus satisfaisant: nous avons intégré l’extension WordLift à ce blog.

WordLift propose automatiquement à l’auteur une série de marquages d’un article puis introduit ces enrichissements sémantiques dans la page. Cela se traduit notamment par des mots qui deviennent cliquables. Mais aussi, de façon invisible pour les visiteurs humains, mais visible par les machines, WordLift produit un marquage exploitable sans ambiguïté. Par exemple, dans l’article

https://onsem.wp.imt.fr/2015/07/28/cors-web-semantique-et-donnees-liees/

et le texte

Le développement du web sémantique…’

la notion ‘web sémantique’ est automatiquement marquée avec les attributs microdata de la façon suivante

<span itemscope 
   itemtype="http://schema.org/Thing" 
   itemid="http://data.wordlift.it/wl044/entity/web_semantique">
<link itemprop="sameAs" href="http://rdf.freebase.com/ns/m.076k0">
<link itemprop="sameAs" href="http://yago-knowledge.org/resource/Semantic_Web">
<link itemprop="sameAs" href="http://dbpedia.org/resource/Semantic_Web">
<link itemprop="url" 
   href="https://onsem.wp.imt.fr/entity/web-semantique/" />
<a class="wl-entity-page-link" 
   href="https://onsem.wp.imt.fr/entity/web-semantique/" 
   itemprop="name" 
   content="Web sémantique">
web sémantique
</a>
</span>

Cela va indiquer à une machine qui accéderait à la page

On peut mieux se rendre compte de ce que ‘voit’ une machine en passant l’url de l’article au Structured Data Testing Tool de Google.

Publié dans DBPedia, Marquage sémantique, Outils | Un commentaire

CORS, web sémantique et données liées

Dans ce billet, j’aborde CORS et je parle des solutions pour utiliser des données provenant d’autres sites.

Le développement du web sémantique et de l’usage des données liées passe assurément par le développement de sites web qui exploitent des données rendues disponibles dans le cadre des technologies du web sémantique. L’exemple le plus connu consiste en l’usage de DBPedia pour compléter les contenus d’une page web (voir par exemple http://labs.sparna.fr/dbpedia-enrich-news-article.html).

Mais, utiliser DBPedia depuis une page Web, cela suppose d’interroger un point d’accès SPARQL (en, fr) de DBPedia depuis la page, puis d’utiliser, pour enrichir la page, les données obtenues. De façon évidente cela nécessite de contourner la règle de sécurité qui empêche d’utiliser dans une page web un contenu provenant d’un autre serveur  que le serveur d’où provient la page (sauf si c’est un contenu particulier comme un code javascript ou une image jpeg): le principe CORS. Je nommerais par la suite le serveur d’où vient la page ‘serveur source’ , et le serveur d’où on cherche à récupérer des données  ‘serveur étranger’.

J’ai lu beaucoup de choses sur CORS avant de comprendre une chose essentielle: pour contourner CORS, le serveur qui fournit les données doit mettre en oeuvre des solutions spécifiques; donc, vous ne pouvez pas utiliser n’importe quelle source, mais une source prête à coopérer avec vous. Nous verrons plus loin qu’une des solutions permet d’exploiter n’importe quelle source, mais via votre serveur qui devient la source de données pour la page Web.

Trois solutions sont connues pour interroger une source étrangère à la page:

  • la déclaration du serveur source sur le serveur étranger,
  • l’envoi des données sous forme de code javascript (JSONP),
  • l’acheminement des données étrangères via le serveur source (proxy)

Enregistrement sur un serveur de données

Dans ce cas-là, le serveur source a obtenu du serveur étranger que ce dernier enregistre le serveur source dans une liste de demandeurs autorisés.

Une fois cet enregistrement effectué, une page qui vient du serveur source va pouvoir émettre des requêtes vers le serveur étranger.

Il y a donc un préalable fort: les responsables des deux serveurs doivent avoir été en contact et le serveur de données doit avoir référencé l’autre serveur. C’est surement la solution la meilleure en terme de sécurité, mais elle est très contraignante. En particulier, il y a un délai et une grosse incertitude entre le moment où vous identifiez une source de données que vous voudriez utiliser et le moment où vous pourrez utiliser cette source (si elle vous le permet).

La façon de faire cet enregistrement dépend complètement de la nature du serveur étranger. Par exemple, j’administre shadok, un serveur Virtuoso (shadok.enst.fr/sparql) et j’ai pu y déclarer un serveur (givingsense.eu) qui va héberger des pages qui pourront faire des requêtes SPARQL sur le serveur shadok. Pour cela, j’ai ajouté http://givingsense.eu dans la liste des serveurs acceptés par shadok, en suivant les indications trouvées ici.

Un exemple élémentaire d’utilisation est visible en suivant ce lien:

http://givingsense.eu/testapp/testcors.html

JSONP

L’idée ici est simple: puisque les pages peuvent charger du javascript, envoyons leur du javascript: le serveur de données va envoyer un appel de fonction qui reçoit les données en paramètre. La page source doit définir le code d’exécution de la fonction. Cette solution est connue sous le nom de JSONP.

La page contient par exemple la définition de fonction

function myjsonp(data) { // défini la fonction callback dont le nom est passé en paramètre de la requête
queryAnswer = data;
}

L’URL suivante, qu’on peut récupérer en faisant un test directement sur l’interface de dbpedia.org/sparql

http://dbpedia.org/sparql?default-graph-uri=http%3A%2F%2Fdbpedia.org&query=select+distinct+%3FConcept+where+%7B%5B%5D+a+%3FConcept%7D+LIMIT+5&format=application%2Fsparql-results%2Bjson

 

donne pour résultat

{ "head": { "link": [], "vars": ["Concept"] },
  "results": { "distinct": false, "ordered": true, "bindings": [
    { "Concept": { "type": "uri", "value": "http://dbpedia.org/ontology/Image" }},
    { "Concept": { "type": "uri", "value": "http://www.w3.org/2002/07/owl#Thing" }},
    { "Concept": { "type": "uri", "value": "http://xmlns.com/foaf/0.1/Person" }},
    { "Concept": { "type": "uri", "value": "http://dbpedia.org/ontology/Person" }},
    { "Concept": { "type": "uri", "value": "http://www.ontologydesignpatterns.org/ont/dul/DUL.owl#Agent" }} ] } }

Mais si on fait cette requête dans une page web, elle va échouer du fait qu’elle va chercher des données sur un serveur différent de l’origine de la page.

Ajoutons à la requête ci-dessus le texte suivant

&callback=myjsonp

qui définit le paramètre callback et lui attribue la valeur myjsonp (ou tous nom que vous aurez donné à votre fonction callback). Le serveur Virtuoso, qui héberge DBPedia, va utiliser ce paramètre pour encapsuler la réponse dans un appel à la fonction myjsonp.

Cette requête pourra être exécutée si elle est interprétée par le navigateur comme étant le chargement de code javascript:

<script  type= »text/javascript » src= »…ici la requête définie ci-dessus… »></script>

Ainsi, lorsqu’il interprète cette ligne, le navigateur reçoit une fonction javascript, qu’il exécute; tel que notre fonction est définie, le résultat de la requête sparql est ainsi stocké dans la variable globale queryAnswer.

Le serveur étranger -ici Virtuoso- a du mettre en oeuvre un traitement de requête spécifique qui, au lieu de renvoyer les données, renvoie les données encapsulées dans un appel de fonction.demande des données.

Ainsi, pour DBPedia, si vous demandez d’avoir une réponse de type JSON et que vous ajoutez le paramètre callback, vous obtenez du JSONP. Pour la requête ci-dessus, le résultat reçu est:

myjsonp(

{ "head": { "link": [], "vars": ["Concept"] },
  "results": { "distinct": false, "ordered": true, "bindings": [
    { "Concept": { "type": "uri", "value": "http://dbpedia.org/ontology/Image" }},
    { "Concept": { "type": "uri", "value": "http://www.w3.org/2002/07/owl#Thing" }},
    { "Concept": { "type": "uri", "value": "http://xmlns.com/foaf/0.1/Person" }},
    { "Concept": { "type": "uri", "value": "http://dbpedia.org/ontology/Person" }},
    { "Concept": { "type": "uri", "value": "http://www.ontologydesignpatterns.org/ont/dul/DUL.owl#Agent" }} ] } })

On voit que c’est la même chose que précédemment entouré par myjsonp(…).

Un exemple est visible ici:

http://givingsense.eu/testapp/dbpediaJsonp.html

Pour obtenir un comportement dynamique, il faudra créer par code des balises script et les injecter dans la page.

Proxy

La dernière possibilité que je vais présenter est l’usage d’un proxy.

Puisque le serveur qui envoie des données doit avoir confiance dans la page qui les lui demande, une solution consiste à demander ces données au serveur qui a fourni la page.

Dans ce cas, la page envoie sa requête -par exemple, celle définie à la section précédente- comme paramètre d’un service implémenté sur le serveur source. Le service du serveur source récupère ce paramètre, exécute la requête vers le serveur étranger, reçoit le résultat qu’il renvoie à la page.

Bien sur cette solution a des inconvénients:

  • elle ajoute une charge de traitement sur le serveur source,
  • elle nécessite un transfert des données du serveur étranger vers le serveur source, puis du serveur source vers la page qui a émis la requête,
  • elle ajoute ainsi de la latence.

Mais, elle a aussi des avantages:

  • elle permet d’interroger toutes sources de données,
  • au prix d’une implémentation sous forme de proxy-cache, elle diminue la dépendance aux aléas de fonctionnement du serveur étranger, et, même, dans ce cas, la latence peut être diminuée (dans l’exemple de la requête ci-dessus, le code sparql ne serait exécuté qu’une fois sur le serveur étranger, puis le résultat stocké sur le serveur source; les exécutions suivantes de la même requête obtiendraient directement le résultat du cache sur le serveur source).

Je donnerai des éléments dans un prochain billet pour la mise en oeuvre de cette solution.

Pour aller plus loin

Vous trouverez une présentation plus approfondie des appels CORS ici

http://www.html5rocks.com/en/tutorials/cors/

Le support de JSONP par les appels Ajax proposés par jQuery

http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings

où on peut noter en particulier que jQuery convertit les requêtes de données json en requêtes jsonp lorsque la requête s’adresse à une origine différente que celle de la page courante.

Quelques compléments sur le fonctionnement de CORS:

http://margaine.com/2014/06/28/jsonp-vs-cors.html

En conclusion

Vous aurez surement besoin de la solution via le proxy pour certaines sources de données qui n’offrent aucune des deux solutions précédentes, donc autant en équiper votre serveur dès maintenant. Nous verrons des façons de le faire dans un prochain billet.

Vous verrez que de plus en plus de sources vous offriront la solution JSONP; elle est assez facile à mettre en oeuvre et donnera surement plus de dynamicité à vos pages.

Enfin, la solution déclarative est limitée aux sources de données sur lesquelles vous pouvez intervenir pour paramétrage (ou quelqu’un peut le faire pour vous): ce sera surement le cas le moins courant. S’il est possible, rappelez-vous que c’est quand même le plus sûr.

[wl_chord]

[wl_navigator]

[wl_faceted_search]

Publié dans DBPedia, SPARQL, Tutoriel, Virtuoso | Un commentaire

Tuto – Théâtre et représentation sémantique RDFa: l’exemple de l’Avare

Personnages de l'Avare et leurs liens

Personnages de l’Avare et leurs liens

J’ai entrepris de représenter des pièces de théâtre d’une façon exploitable par des logiciels. Même si les applications envisagées à court terme n’ont pas besoin de représentations sémantiques très évoluées, j’ai décidé de m’appuyer sur les techniques du web sémantique afin de:

  • bénéficier de leur environnement de production, de manipulation et d’exploitation,
  • être prêt pour des évolutions en adoptant des techniques de représentation très ouvertes.

Sur cette base, j’ai profité du travail effectué pour illustrer une façon simple d’exploiter graphiquement des annotations sémantiques présentes dans une page HTML.

Je vais commencer par proposer une représentation des personnages:

Prenons les personnages de l’Avare tels que décrits dans le texte de la pièce:

  • Harpagon, père de Cléante et d’Élise, et amoureux de Mariane.
  • Cléante, fils d’Harpagon, amant de Mariane.
  • Élise, fille d’Harpagon, amante de Valère.
  • Valère, fils d’Anselme et amant d’Élise.
  • Mariane, amante de Cléante et aimée d’Harpagon.
  • Anselme, père de Valère et de Mariane.
  • Frosine, femme d’intrigue.
  • Maître Simon, courtier.
  • Maître Jacques, cuisinier et cocher d’Harpagon.
  • La Flèche, valet de Cléante.
  • Dame Claude, servante d’Harpagon.
  • Brindavoine, La Merluche, laquais d’Harpagon.
  • Un commissaire et son clerc.

La représentation des personnages

foaf s’est imposé comme une façon simple de décrire des personnes et peut s’appliquer à des personnages de fiction comme le sont généralement les personnages d’une pièce de théâtre. Classiquement, nous utiliserons foaf: comme préfixe pour le vocabulaire foaf dans la suite de ce document.

Prenons le personnage principal et une partie de ses proches: Harpagon, Cléante et Mariane; nous pouvons créer un ‘blanknode’ qui va servir à exprimer tout ce qu’on sait sur lui; pour commencer:

@prefix rdf: <http://purl.toto/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
_:harpagon rdf:type foaf:Person ; 
 foaf:name "Harpagon" ;
 foaf:knows _:cleante ;
 foaf:knows _:elise ;
 foaf:knows _:mariane .

 

La définition de foaf implique que si foaf:knows a pour objet _:cleante alors _:cleante est aussi de type foaf:Person.

Une représentation formelle des relations entre les personnages

Le premier niveau de description concerne les rôles sociaux relatifs des personnages: xx est le père de yyy qui est le frère de zzzz qui est l’ami de www.

Une part significative de ces relations peut être décrite avec le vocabulaire décrit ici:

http://vocab.org/relationship/.html

L’URI pour ce vocabulaire est http://purl.org/vocab/relationship, avec rel comme préfixe usuel.

Nous aurons lors:

_:cleante rel:childOf _:harpagon

Mais très vite, nous voyons que l’expressivité de ce vocabulaire est très limitée: seulement avec la dizaine de personnage de la pièce, une bonne partie des relations va être exprimée de façon extrêmement rudimentaire. Par exemple, le fait que Brindavoine est un laquais d’Harpagon, va être décrit par le prédicat employedBy.

Une représentation graphique des relations entre les personnages

Pour illustrer le principe, nous allons nous limiter à la relation foaf:knows entre les personnages. Nous allons utiliser la librairie graphique javascript d3js pour produire une représentation graphique des liens foaf:knows entre les personnages.

Nous allons dessiner un graphe où chaque personnage est un noeud du graphe.

Il nous faut récupérer une liste des personnages. Nous allons utiliser la librairie green-turtle pour  utiliser les représentations sémantiques introduites à l’aide du RDFa dans la page HTML.

Cette librairie nous permet d’associer le préfixe foaf: à l’URI correspondant au vocabulaire FOAF:

document.data.setMapping(« foaf: », « http://xmlns.com/foaf/0.1/ » );

Ensuite, récupérer la liste des personnes décrites dans la page se fait simplement grâce à la ligne:

var peoples = document.getElementsByType(« foaf:Person »);

Ensuite pour chaque élément  de la table peoples, on peut récupérer sa liste de contacts avec la ligne:

var contacts = peoples[i].data.getValues(« foaf:knows »);

Pour afficher un graphe, d3 a besoin de la table de noeuds -que nous nommerons nodes-qui sera directement déduite de la table peoples que nous venons d’obtenir et d’une table des liens; chaque lien est défini par la propriété source qui est l’index du noeud source dans la table nodes et  la propriété target qui est l’index du noeud destination du lien  dans la table nodes. Nous déduisons facilement ces index des informations obtenues avec les objets contacts pour la source peoples[i]. Nous construisons ainsi une table links qui définit tous les liens présents dans le graphe.

Nous passons cette table de noeuds et cette table de liens à une procédure exploitant d3 et qui trace un graphe. Elle est directement dérivée de l’exemple http://bl.ocks.org/mbostock/4062045 modifié pour ne pas lire un fichier de données décrivant le graphe, mais utiliser la table nodes et la table links juste créées.

Dans un prochain billet, nous verrons comment construire une variété de graphes sur le même principe mais en donnant directement les relations sémantiques que l’on veut illustrer.

L’exemple complet et le détail de la construction du tableau links sont visibles ici et dans les scripts associés:

http://givingsense.eu/demo/avare/avare.rdfa.tst.htm

(si les positions des noeuds du graphe ne vous conviennent pas, vous pouvez les changer)

Ressources

Liste HTML des personnages de L’avare de Molière annotée en RDFa:

http://givingsense.eu/demo/avare/avare.rdfa.tst.htm

Fichier N3 décrivant la liste des personnages de L’avare de Molière par une extraction automatique grâce à rdf-translator:

http://givingsense.eu/demo/avare/avare.n3

Fichier JSON-LD tiré de la représentation précédente:

http://givingsense.eu/demo/avare/avare.jld

[wl_chord]

[wl_navigator]

[wl_faceted_search]

Publié dans Cultural data, DBPedia, Tutoriel, Visualisation | Marqué avec , | Laisser un commentaire

Quelques requêtes SPARQL sur DBPedia

Je propose ici quelques requêtes SPARQL qui ont servis à alimenter la réflexion qui aboutit à l’article, testées sur le point d’accès français (http://fr.dbpedia.org/sparql)

https://onsem.wp.imt.fr/2015/05/15/creer-des-connaissances-formalisees-pour-le-web-semantique-a-partir-de-dbpedia/

L’idée est de s’en resservir pour d’autres types d’éléments ou pour compléter des données dans des interfaces utilisateurs (mais ce point sera développé plus tard).

select ?p (count(?p) as ?pTotal) where {
?s dbpedia-owl:type dbpedia-fr:Synagogue .
?s ?p ?o1
} 
GROUP BY ?p
ORDER BY DESC(?pTotal)
LIMIT 200

Le résultat est visible ici.

permet de voir les 200 prédicats les plus utilisés avec des objets de type Synagogue.

Et

select count(?s) where {
?s dbpedia-owl:type dbpedia-fr:Synagogue .
} 

nous donne le nombre d’éléments de type Synagogue: 58

Ce qu’on peut cumuler, pour faire ressortir les pourcentage d’éléments qui ont certains prédicats:

select ?p (count(?p) as ?pTotal) (count(?p)*100/?sTotal as ?percent) where {
{
select ?p where {
?s dbpedia-owl:type dbpedia-fr:Synagogue .
?s ?p ?o1
}
} .
{
select (count(?s1) as ?sTotal) where {
?s1 dbpedia-owl:type dbpedia-fr:Synagogue .
} 
}
} 
GROUP BY ?p ?sTotal
ORDER BY DESC(?percent)
LIMIT 200

Le résultat est visible ici.

De façon analogue, pour les 676 éléments de type Abbaye, nous aurons:

select ?p (count(?p) as ?pTotal) (count(?p)*100/?sTotal as ?percent) where {
{
select ?p where {
?s dbpedia-owl:type dbpedia-fr:Abbaye .
?s ?p ?o1
}
} .
{
select (count(?s1) as ?sTotal) where {
?s1 dbpedia-owl:type dbpedia-fr:Abbaye .
} 
}
} 
GROUP BY ?p ?sTotal
ORDER BY DESC(?percent)
LIMIT 200

Le résultat est visible ici.

Et pour obtenir les prédicats partagés par les Synagogues et les Abbayes:

select distinct ?p  where {
{ 
select distinct ?p where
{
?s1 dbpedia-owl:type dbpedia-fr:Abbaye .
?s1 ?p ?o1 .
} 
}.
{
select distinct ?p where
{
?s2 dbpedia-owl:type dbpedia-fr:Synagogue .
?s2 ?p ?o2 .
}
}
} 
LIMIT 200

Le résultat est visible ici

Analyse

Dans les résultats obtenus, on a, pour chaque catégorie, une trentaine de prédicats qui apparaissent plus de 80 fois pour 100 éléments. Les prédicats suivants -à l’exception de prop-fr:finConst- ont une représentation de 65% ou moins.

On rencontre un ensemble de prédicats très généraux, qui doivent s’appliquer à la plupart des éléments de DBPedia:

(les préfixes usuels sont utilisés)

dbpedia-owl:wikiPageWikiLink, prop-fr:wikiPageUsesTemplate,

rdf:type, dcterms:subject, rdfs:label, rdfs:comment, dbpedia-owl:abstract,

dbpedia-owl:wikiPageExternalLink, owl:sameAs, prop-fr:type, dbpedia-owl:type, dbpedia, owl:wikiPageID, dbpedia-owl:wikiPageRevisionID,

foaf:isPrimaryTopicOf, foaf:name, foaf:depiction

http://fr.dbpedia.org/stats/degree, http://fr.dbpedia.org/stats/inDegree, http://fr.dbpedia.org/stats/outDegree, http://www.w3.org/ns/prov#wasDerivedFrom

Quelques prédicats liés au fait qu’il s’agisse d’un lieu (et donc de quelque chose qui se voit):

geo:lat, geo:long, georss:point, prop-fr:longitude, prop-fr:latitude, prop-fr:ville, dbpedia-owl:city, prop-fr:géolocalisation, dbpedia-owl:thumbnail, prop-fr:région, prop-fr:photo,

foaf:depiction, dbpedia-owl:region

Et enfin quelques propriétés directement liées au fait qu’il s’agisse d’un monument religieux:

prop-fr:nommonument, prop-fr:culte, dbpedia-owl:religiousOrder

Il y a une vingtaine de propriétés supplémentaires avec un représentativité supérieure à 20%.

La plupart des prédicats sont partagés par ces deux types de monuments. C’est donc plutôt les valeurs associées à ces deux types de monuments qui peuvent les différencier. Ces prédicats partagés peuvent être vus comme un ensemble de propriétés décrivant des édifices religieux dans DBPedia.

On notera cependant qu’il y a très peu de prédicats spécifiques de ces deux types de monuments…

Articles liés:

[wl_chord]

[wl_navigator]

[wl_faceted_search]

Publié dans Cultural data, DBPedia, SPARQL | Laisser un commentaire

Créer des connaissances formalisées pour le Web Sémantique à partir de DBPedia

Cet article est le deuxième d’une série qui a commencée sur un autre blog par l’article

https://ilot.wp.imt.fr/2014/11/26/une-exploration-de-dbpedia-au-sujet-des-monuments-avec-laide-de-sparql/

L’idée est de voir comment étendre les connaissances représentées dans DBPedia en exploitant des connaissances implicitement présentes dans DBPedia lui-même.

Transposition directe d’une requête précédente dans le même domaine

Par exemple, une légère transposition de la requête finale de l’article précédent donne, en changeant Edifice-type par Etablissement_religieux

SELECT ?s ?o (COUNT(?o) AS ?oTotal)
WHERE
{ 
?s ?p <http://fr.dbpedia.org/resource/Catégorie:Établissement_religieux>  .
?s2 <http://dbpedia.org/ontology/type> ?s .
?s2 <http://dbpedia.org/ontology/wikiPageWikiLink> ?o .
?s3 <http://fr.dbpedia.org/property/religion> ?o  
}
GROUP BY ?s ?o
ORDER BY DESC(?oTotal)
LIMIT 100

et cela nous fournit une association pertinente entre des élément Etablissements_religieux et une religion.

Partant de cela, j’ai cherché à étendre et généraliser la méthode.

Sur les conseils de Fabian Suchanek, avec Luis Galárraga et Danai Symeonidou, nous avons utilisé l’outil AMIE  pour chercher à produire des connaissances nouvelles à partir des descriptions de personnes, de lieux et d’organisations présents dans DBPedia 3.8.

Sur ce jeu de données, nous avons constaté que 81% des wikilinks n’étaient pas ‘sémantifiés’, c’est-à-dire qu’ils ne sont accompagnés d’aucun prédicat précisant le nature du lien entre deux éléments: tout ce qu’on sait, c’est que ces éléments ont un lien.

La méthode mise en oeuvre permet de trouver des règles de sémantification par une analyse statistique du jeu de données. Par exemple, si les édifices religieux sont souvent associés à une religion par un prédicat qui indique la ‘religion pratiquée’ dans l’édifice, alors AMIE permet d’établir une règle qui va permettre de qualifier un lien

<édifice religieux A> a un lien avec <religion X>

entraine qu’on suppose

<édifice religieux A> est un lieu de pratique de <religion X>

avec un certain degré de confiance qui est fournit.

 

La méthode détaillée est décrite dans l’article référencé ci-dessous. Elle nous a permis de ‘sémantifier’ 181000 liens avec une précision comprise entre 67 et 87%. La suite du travail va consister à trouver comment améliorer cette précision et exploiter les ‘connaissances’ ainsi révélées.

Ce travail a donné lieu à l’article

http://events.linkeddata.org/ldow2015/papers/ldow2015_paper_02.pdf

qui sera présenté par Luis la semaine prochaine au workshop LDOW 2015 de la conférence WWW 2015.

Publié dans DBPedia, SPARQL | 3 commentaires

Visualiser l’ontologie de DBPedia sous forme de graphe avec d3

Dans le cadre d’un projet exploitant DBPedia, j’ai ressenti le besoin de me faciliter l’exploration de l’ontologie de DBPedia.

Bien sûr, j’ai commencé par la télécharger.  J’ai pu constater qu’une fois décompressé, ‘il s’agit d’un fichier de 2,3 Mo, au format RDF/XML comportant 685 classes et 2795 propriétés.

Mon réflexe est, de plus en plus, de créer mes visualisations et mes interfaces pour le Web. Comme le fichier est un fichier de référence qui n’est pas supposé évoluer (version 2014 de l’ontologie), j’ai commencé à le mettre dans un format commode pour le Web: JSON. Je ne vais pas générer un fichier json quelconque, mais un fichier mis en forme pour les données liées: json-ld.

Le fichier étant relativement gros, je n’ai pas pu utiliser le très commode convertisseur Rdf-Translator. J’avais déjà rencontré ce problème (abordé ici et ici). Pour cela, j’ai fait appel au convertisseur rdfcat de Jena, avec la ligne de commande:

rdfcat -out JSON-LD dbpedia_2014.owl >dbpedia_2014.jsonld

qui me donne un fichier de 1,7 Mo.

Pour rendre le fichier encore plus exploitable, je suis allé faire un petit tour sur l’outil en ligne de test de json-ld: Json-ld Playground.

J’ai collé le contenu de dbpedia_2014.jsonld dans l’espace prévu pour mettre ses données, puis en cliquant sur l’onglet, j’ai constaté que le Playground n’aime pas mon fichier. Le message d’erreur m’indique que le fichier défini un terme par une chaîne de caractère vide, ce qui semble interdit. Une recherche dans le fichier m’a suggéré que c’est la ligne

«  » : « http://dbpedia.org/ontology/ »,

qui est fautive. Je l’ai remplacée par

« bug » : « http://dbpedia.org/ontology/ »,

et le problème a disparu. J’ai pu observer que le contexte défini dans le fichier pour sa mise en forme me convient.

Maintenant, je vais chercher à construire un graphe à partir de ces données. Pour cela, je vais utiliser d3. Pour commencer par un graphe simple, je vais créer un nœud du graphe pour chaque classe et un lien vers la ou les classes dont elle est une sous-classe.

Par exemple:

l’élément correspondant à la classe Abbey, a pour id

« @id » : « :Abbey »,

il est bien de type classe

« @type » : « owl:Class »,

et il est une sous-classe de Place:

« subClassOf » : « :Place »,

Une recherche d’exemples de graphes tracés avec d3 m’a amené  sur

et en particulier sur le premier exemple

http://bl.ocks.org/mbostock/1153292

(on trouve de nombreux autres exemples ici et ici)

Dans cet exemple les liens sont définis dans une table (links) qui contient un ensemble d’éléments ayant chacun une propriété ‘source’, une ‘target’ et une ‘type’. Les noeuds en sont déduits avec le code suivant:

links.forEach(function(link) {
  link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
  link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
});

Au lieu d’utiliser une table links définie directement dans le code, je veux la créer à partir de l’ontologie de DBPedia. Voilà le code -un peu simplifié- qui lit le fichier jsonld de l’ontologie et crée des éléments avec les propriétés source et target:

d3.json("data/dbpedia_2014.jsonld", function(json) {
var nodes = {};
var links = json["@graph"]; 

links.forEach(function(entity) {
    if (entity["@type"]==="owl:Class") {
      if (typeof entity.subClassOf !== 'undefined') {
              entity.source = nodes[entity.subClassOf] || 
                  (nodes[entity.subClassOf] = {name: entity.subClassOf});
              entity.target = nodes[entity["@id"]] || 
                  (nodes[entity["@id"]] = {name: entity["@id"]});
              entity.value = "";
        }
      } // else, it's a root
    } else {
      // here, we need code to process other elements than owl:Class
    }
});

Le reste du code est celui de l’exemple.

Le résultat est ici

http://givingsense.eu/demo/dbpediaGraphs/ontodbpedia.htm

Comme le graphe contient de nombreux liens et nœuds, la mise en page automatique prend un peu de temps à se calculer et se stabiliser et il vaut mieux avoir un grand écran…

Voilà, c’est tout… ou preque.

J’ai ajouté un peu de code pour simplifier le graphe et ne donner que les noeuds voisins d’un noeud indiqué par le nom de sa classe.

Par exemple:

– pour la classe :Monument

http://givingsense.eu/demo/dbpediaGraphs/ontodbpedia.htm?id=:Monument

– pour la classe :Place

http://givingsense.eu/demo/dbpediaGraphs/ontodbpedia.htm?id=:Place

Nous verrons dans un prochain billet qu’il est simple de traiter l’ontologie en json-ld pour en avoir une version monolingue, beaucoup moins grosse et définir des fonctionnalités qui aident à parcourir et découvrir l’ontologie (plier et replier des branches…)

Publié dans DBPedia, Visualisation | Laisser un commentaire

Données ouvertes: les accès Wifi de la Ville de Paris

Tutoriel d’exploitation Web d’un jeu de données JSON

J’ai décidé d’entamer une série de billets sur l’utilisation de données ouvertes. Pourquoi ce thème sur un site dédié à la sémantique: je fais un lien profond entre le web sémantique et les données ouvertes accessibles sur le web. Je ne suis pas le seul puisque cela est clairement impulsé par Tim Berners Lee, inventeur du Web et promoteur fervent du Web des Données (Linked Open Data).

Pour commencer cette série, je vais décrire un petit travail que j’ai fais pour aider des étudiants qui font un projet où ils doivent utiliser des données ouvertes. Mon but est de montrer qu’en quelques lignes de code assez facile à comprendre, on peut utiliser ces données pour les intégrer sur un site Web. Nous nous appuyons sur la librairie jQuery.

Dans la liste de données qu’ils ont choisis d’utiliser, il y a la des sites des hotspots Paris WiFi, fournie par la Mairie de Paris. Sur la page de ces données dans data.gouv.fr, on voit que 3 fichiers sont proposés. Les données sont proposées dans les formats HTML, CSV et JSON. On peut en voir la version HTML ici; il s’agit d’une visualisation sous forme de table.

cartewifi

Pour cet exemple, je vais montrer une intégration de ces données sous forme de carte dans une page web. Le format le plus naturel est donc le JSON, le plus proche du Javascript que je vais utiliser pour le code dans la page web.

Le fichier JSON fait 139 Ko. Pour commencer, je vais l’utiliser directement. On peut y accéder à cette adresse

http://parisdata.opendatasoft.com/api/records/1.0/download?dataset=liste_des_sites_des_hotspots_paris_wifi&format=json

Pour simplifier je vais charger ce fichier sur mon serveur; je le mets dans un sous-dossier nommé data du dossier où je vais créer la page web.

Ma page de base est

<html>
  <head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <title>Demo OpenData - HotSPots Paris Wifi</title>
    <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
    <script src="http://code.jquery.com/jquery-latest.js"></script>
  <script>
      $(document).ready(function() {
        $.getJSON("data/liste_des_sites_des_hotspots_paris_wifi.json").done( function(data)
            {
// ICI NOUS ALLONS METTRE DU CODE POUR UTILISER LES DONNEES 
        });
      });
  </script>
</head>
  </head>
  <body>
      <div id="map" style="margin-left:150px; width: 1200px; height: 800px;"></div>
  </body>
</html>

Cette page de base peut facilement servir de modèle pour des variantes ou d’autres jeux de données.

Les points essentiels sont:

* la ligne

    <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>

déclare que je vais utiliser la librairie d’appel de Google Maps.

* la ligne

    <script src="http://code.jquery.com/jquery-latest.js"></script>

déclare que je vais utiliser la librairie jQuery.

Ensuite, j’ai du script spécifique de mon objectif.

Les lignes

      $(document).ready(function() {
// ICI LE CODE SPECIFIQUE
        });
      });

déclare que la fonction qui est définie à l’intérieur des parenthèses de la méthode ready sera appelée dès que l’ensemble du document HTML aura été chargé et près à l’utilisation.

Cette fonction, je la défini avec l’appel suivant

$.getJSON("data/liste_des_sites_des_hotspots_paris_wifi.json").done( function(data)
{
// ICI NOUS ALLONS METTRE DU CODE POUR UTILISER LES DONNEES 
});

qui indique de charger le fichier de données que j’ai mis dans le sous-dossier data et qui définit une fonction qui sera appelée quand ces données auront été chargées.

Evidemment, c’est là que tout va se passer: exploiter les données ainsi chargées pour en faire une carte.

D’abord, quelques lignes pour créer une carte, puis l’afficher

var parisLatlng = new google.maps.LatLng(48.8550, 2.3525); // centre de paris
var myOptions = {
                  zoom: 13,
                  center: parisLatlng,
                  mapTypeId: google.maps.MapTypeId.ROADMAP
                }
var carte = new google.maps.Map(document.getElementById("map"), myOptions);
// ...               

$("#map").show(); 

La première ligne crée un ‘point géographique’ défini par sa latitude et sa longitude.

Les 5 lignes suivantes créent un objet qui défini les options de la carte: niveau de zoom, centre, type de représentation (cf doc Google Maps).

La ligne suivante crée l’objet informatique qui va gérer la carte en indiquant que cette carte va être associée à l’élément d’identificateur « map » dans le HTML de la page.

La dernière ligne s’assure que cet élément est visible.

L’élément concerné est défini par la ligne suivante dans le <body> de la page:

<div id="map" style="width: 100%; height: 90%;"></div>

Tout est en place pour utiliser les données: celles-ci sont lues, un fond de carte est préparé; reste à mettre des choses sur ce fond.

C’est le rôle de ces quelques lignes:

$.each( data, function( key, val ) {
        var geopoint = new google.maps.LatLng(val.fields.geo_coordinates[0], val.fields.geo_coordinates[1]);
        markers.push(new google.maps.Marker({
                            position: geopoint, 
                            map: carte,
                            title: val.fields.nom_site
        }));   
});

La première ligne indique que pour chaque paire (clé, valeur) rencontrée dans data, on va appeler la fonction définie dans les lignes suivantes.

La structure du fichier JSON est une table d’objets construits sur le modèle suivant:

    {
        "datasetid": "liste_des_sites_des_hotspots_paris_wifi",
        "recordid": "ac87e8d20525416fad92a63d7005086af4107966",
        "fields": {
            "ville": "PARIS",
            "adresse_complete": "4, Place De L'Hotel De Ville 75004 PARIS France",
            "nom_site": "Hotel de Ville (3eme etage)",
            "adresse": "4, Place De L'Hotel De Ville",
            "arrondissement": "04",
            "geo_coordinates": [
                48.85694,
                2.35212
            ],
            "cp": "75004"
        },
        "geometry": {
            "type": "Point",
            "coordinates": [
                2.35212,
                48.85694
            ]
        },
        "record_timestamp": "2014-08-13T20:24:53.623771"
    },

Je vais exploiter les éléments geo_cordinates et nom_site qui sont contenus dans l’élément de clé fields. L’élément geo_coordinates est une table. L’accès aux données ‘nom du site’, ‘latitude du site’, ‘longitude du site’ se fait dont avec la syntaxe:

val.fields.nom_site

val.fields.geo_coordinates[0]

val.fields.geo_coordinates[1]

Les lignes

var geopoint = new google.maps.LatLng(val.fields.geo_coordinates[0], val.fields.geo_coordinates[1]);
markers.push(new google.maps.Marker({
                            position: geopoint, 
                            map: carte,
                            title: val.fields.nom_site
}));   

Créent le point correspondant à la position du site, puis créent un marqueur qui est inésré sur la carte macarte.

Voilà, c’est fait.

Une carte des sites Paris Wifi de la ville de Paris en une trentaine de lignes HTML/Javascript.

Le code complet est ici:

http://givingsense.eu/opendata/demo/sitesWifi.htm

 

Publié dans Données publiques, Visualisation | Laisser un commentaire

Publications des ontologies de programmes scolaires français

Nous avons entrepris de publier les ontologies de programmes scolaires français sur lesquelles nous travaillons. Bien que ces ontologies doivent connaitre dans les prochains mois des évolutions significatives, nous pensons que les versions actuelles, considérées comme des versions ‘beta’, permettront des échanges fructueux. Si certains lecteurs envisagent d’en faire un usage important, nous leur suggérons de rentrer en contact avec nous, pour trouver les moyens d’éviter d’arriver à une impasse du fait des évolutions futures dans notre travail. Nous ferons d’importants efforts pour assurer la compatibilité la plus complète entre les versions succéssives de nos ontologies.

Nous ne décrirons pas ici la structure de ces ontologies, mais plutot la façon dont nous les avons publiées dans le respect d’un ensemble de bonnes pratiques.

Des règles ont été initialement proposées par Tim Berners-Lee. Elles ont, depuis, été complétées. Les règles qui nous ont servi pour la publication de notre ensemble de données sont les suivantes, par ordre d’importance décroissante:

  1. les données sont librement accessibles sur le web,
  2. elles sont dans un format ouvert ‘compréhensible’ par des machines
  3. elles respectent le modèle RDF
  4. elles présentent des liens avec d’autres ensembles de données
  5. elles sont déréférençables (voir plus loin) et auto-descriptives

Nous allons commenter ces règles point par point.

Règle 1 les données sont librement accessibles sur le web

Nos ontologies et ensembles de données sont accessibles depuis la page

http://givingsense.eu/frscol/

Nous avons choisi de les rendre publiques sous licence Creative Commons Attribution-Non commercial. Pour faire simple, pour un usage non commercial, la réutilisation de ce qui est publié là est libre, pourvu que nous soyons cités.

Afin de promouvoir ces données pour favoriser leur utilisation et leur amélioration, nous avons indiqué leur existance sur datahub.io. Par exemple, l’ontologie du système scolaire français est signalée ici:

http://datahub.io/dataset/french-school-system

Nous les publions progressivement aussi sur data.gouv.fr; voir les jeux de données que nous publions via notre profil:

https://id.data.gouv.fr/u/ilot-project/

à cette adresse:

https://www.data.gouv.fr/dataset/ontologies-descriptives-du-systeme-et-des-programmes-scolaires-francais

De plus, afin de faciliter des interrogations SPARQL sur nos ensembles de données, nous avons mis en oeuvre un ‘triple store’, basé sur un serveur Virtuoso accessible à l’adresse suivante:

http://shadok.enst.fr:8890/sparql

Chaque ontologie publiée est chargée dans un graphe séparé qui est désigné par l’URI de base de l’ontologie. Par exemple, l’ontologie du système scolaire français est contenue dans le graphe

http://givingsense.eu/frscol/FrSchoolSystem/

Règle 2 Les données sont dans un format ouvert ‘compréhensible’ par des machines

Jusqu’à présent, nos réalisations sont toutes dans des formats ouverts structurés, compréhensibles par des machines. Les ontologies que nous publions ont été crées au format RDF/XML. Dans certain cas, des versions complémentaires en JSON ont été générées à partir de la version RDF/XML.

Les ontologies utilisent la norme OWL 2.

Règle 3 Les données respectent le modèle RDF

Voir le commentaire de la règle 2: nous utilisons OWL en format RDF/XMl et donc, nous suivons le modèle RDF.

Règle 4 Les données présentent des liens avec d’autres ensembles de données

Nos jeux de données ont des liens entre eux et avec des jeux de données disponibles sur le web.

Nous avons produit des ontologies de base: une pour le système scolaire français, une décrivant les concepts généraux permettant de décrire un programme scolaire (TBOX) et une, proche d’une taxonomie, décrivant un ensemble de compétences suivant le modèle de Bloom. L’ontologie générique de programme scolaire incorpore les deux autres.

Ensuite, chaque ontologie décrivant une partie du programme d’une matière contient des ‘individus’ de chaque classe/concept définis dans les précédentes ontologies et qui constituent la description sémantique détaillée du programme (ABOX).

Nous avons également produit un gros fichier RDF contenant la description d’un ensemble de concepts de base -proches d’un ensemble de mots-clés- auwquels des portions de programm font référence.

Une ontologie spécifique décrit le programme d’Histoire des Arts. Comme l’Histoire des Arts n’a pas d’horaire propre, mais doit être étudiée à l’occasion de travaux dans chacune des autres matières, des liens existent entre ce programme et les autres programmes.

Voilà pour les liens internes.

Enfin, nous enrichissons progressivement nos descriptions par des liens vers d’autres ensembles de données. Citons parmi les cibles que nous avons identifiées ou utilisées: DBpedia, Europeana, BNF, Getty (voir aussi http://onsem.wp.imt.fr/2014/07/09/quelques-points-dacces-sparql-francais/).

Règle 5 Les donnéess sont déréférençables et auto-descriptives

Le W3C a publié des documents qui suggèrent des méthodes de publication de données liées. Le plus ancien, qui a servi de base à notre travail, est Cool URIs for the Semantic WebLe plus récent est Best Practices for Publishing Linked Data.

Nous allons décrire comment nous avons procédé pour respecter les principes énoncés.

Les données publiées sont accessibles individuellement via une URI qui les désigne de façon non ambigües. Un utilisateur qui tape une de ces URIs dans un navigateur doit obtenir une représentation de la donnée correspondante dans une page web (sa requête demande implicitement du html). Deux modèles sont proposé -à mon avis, pas forcément exclusifs l’un de l’autre- un qui désigne une donnée spécifique parmi un ensemble en désignant l’ensemble suivi d’un #, suivi d’un identificateur unique de la donnée souhaitée; l’autre modèle utilise le / au lieu du # (nous ne décrirons pas ici les spécificités de ces deux approches).

Nous avons choisi le modèle du /.

Des règles de ré-écriture d’URL, internes à notre serveur, donnent les résultats décrits ci-après.

Ainsi, par exemple, pour l’ontologie du système scolaire français, nous avons l’URI de base suivante:

http://givingsense.eu/frscol/FrSchoolSystem/

provoque l’affichage d’une description de l’ensemble de cette ontologie. Actuellement, cette documentation de l’ontologie est générée à partir de l’ontologie elle-même par l’outil disponible sur le site:

http://www.essepuntato.it/lode

Un des concepts utilisé dans cette ontologie est le ‘parcours pédagogique’ désigné par l’identificateur EducationalPathway, accessible donc par l’URI:

http://givingsense.eu/frscol/FrSchoolSystem/EducationalPathway

L’ensemble des ontologies est accessible suivant ce principe.

Lorsque ce n’est pas un utilisateur qui utilise ces mêmes URIs, mais un logiciel qui cherche à exploiter la représentation sémantique sous-jacente, par exemple avec une interrogation en SPARQL, la requête demande un contenu application/rdf+xml et la mécanique que nous avons mis en place renvoie directement l’ontologie au format RDF/XML.

L’ensemble des données est accessible ici

http://givingsense.eu/frscol/

et sera progressivement enrichi.

 

Publié dans Uncategorized | Laisser un commentaire

Quelques points d’accès SPARQL français

 

Un court billet, qui subira surement quelques mises à jour au fil du temps, pour lister des points d’accès SPARQL qui ont un rapport significatif avec la France, par exemple:

  • données émises par un organisme français
  • données concernant des ressources en français

BNF

La Bibliothèque Nationale de France a publié un grand nombre de données concernant des oeuvres, des auteurs et des artistes.

ISIDORE

Ce projet aide de nombreux organismes qui travaillent sur des données culturelles (‘Humanités Numériques’) à rendre publiques leurs données sur un point d’accès unique. De nombreux jeux de données ont déjà été publiées.

DBPEDIA français

Un groupe d’action s’est constitué pour étendre la démarche de DBPedia au Wikipedia français.

(vérifier ce qu’est  http://fr.dbpedia.org/snorql/)

EUROPEANA

Le projet européen Europeana fédère de nombreuses données culturelles européennes dont des données françaises.

GIVINGSENSE (expérimental)

Notre équipe a commencé à publier des données à titre expérimental. Tout n’est pas encore parfaitement ‘propre’. Mais certaines données peuvent déjà être utiles.

Il s’agit essentiellement de données liées aux programmes scolaires français, créées dans le cadre d’un projet dont les résultats sont commentés ici:

https://ilot.wp.imt.fr/fr/

Les données sont progressivement intégrées sur un triple store accessible ici:

http://shadok.enst.fr:8890/sparql (point d’accès Virtuoso)

Dans les données disponibles ont a par exemple:

Nous allons publier et enrichir peu à peu des ontologies représentant l’ensemble du système scolaire français.

 

Publié dans SPARQL | Marqué avec , , , , | Un commentaire