001/* 002 Copyright (C) 2007 - 2016, Jens Lehmann 003 004 This file is part of DL-Learner. 005 006 DL-Learner is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 3 of the License, or 009 (at your option) any later version. 010 011 DL-Learner is distributed in the hope that it will be useful, 012 but WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 GNU General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with this program. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package org.dllearner.algorithms; 020 021import org.apache.jena.query.ParameterizedSparqlString; 022import org.apache.jena.query.QuerySolution; 023import org.apache.jena.query.ResultSet; 024import org.apache.jena.rdf.model.RDFNode; 025import org.dllearner.core.*; 026import org.dllearner.core.config.ConfigOption; 027import org.dllearner.kb.SparqlEndpointKS; 028import org.dllearner.kb.sparql.SparqlEndpoint; 029import org.dllearner.learningproblems.AxiomScore; 030import org.dllearner.learningproblems.ClassScore; 031import org.dllearner.learningproblems.ScoreSimple; 032import org.dllearner.utilities.OwlApiJenaUtils; 033import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer; 034import org.semanticweb.owlapi.io.ToStringRenderer; 035import org.semanticweb.owlapi.model.*; 036import uk.ac.manchester.cs.owl.owlapi.OWLClassImpl; 037 038import java.util.*; 039import java.util.function.Function; 040import java.util.stream.Collectors; 041 042/** 043 * Learns subclass-relationships for a given class by using SPARQL queries. 044 * 045 * @author Lorenz Bühmann 046 * @author Jens Lehmann 047 * 048 */ 049@ComponentAnn(name = "simple subclass learner", shortName = "clsub", version = 0.1) 050public class SimpleSubclassLearner extends AbstractAxiomLearningAlgorithm<OWLSubClassOfAxiom, OWLClassAssertionAxiom, OWLClass> 051 implements ClassExpressionLearningAlgorithm { 052 053 private static final ParameterizedSparqlString SAMPLE_QUERY = new ParameterizedSparqlString( 054 "CONSTRUCT{?s a ?entity . ?s a ?cls1 .} WHERE {?s a ?entity . OPTIONAL {?s a ?cls1 . }}"); 055 056 private static final ParameterizedSparqlString CLASS_OVERLAP_BATCH_QUERY = new ParameterizedSparqlString( 057 "SELECT ?cls_other (COUNT(DISTINCT ?s) AS ?cnt) WHERE {" + 058 "?s a ?cls . ?s a ?cls_other . " + 059 "FILTER(?cls_other != ?cls) " + 060 "FILTER(?cls_other != <http://www.w3.org/2002/07/owl#NamedIndividual>) " + 061 "} GROUP BY ?cls_other"); 062 063 private static final ParameterizedSparqlString CLASS_OVERLAP_BATCH_QUERY_STRICT_OWL = new ParameterizedSparqlString( 064 "SELECT ?cls_other (COUNT(DISTINCT ?s) AS ?cnt) WHERE {" + 065 "?s a ?cls . ?s a ?cls_other . " + 066 "?cls_other a <http://www.w3.org/2002/07/owl#Class> . " + 067 "FILTER(?cls_other != ?cls) " + 068 "FILTER(?cls_other != <http://www.w3.org/2002/07/owl#NamedIndividual>) " + 069 "} GROUP BY ?cls_other"); 070 071 private static final ParameterizedSparqlString CLASS_OVERLAP_QUERY = new ParameterizedSparqlString( 072 "SELECT (COUNT(DISTINCT ?s) AS ?cnt) WHERE {" + 073 "?s a ?cls . ?s a ?cls_other . }"); 074 075 076 @ConfigOption(defaultValue = "false", description = "compute everything in a single SPARQL query") 077 private boolean batchMode = false; 078 079 @ConfigOption(defaultValue = "false") 080 private boolean strictOWLMode = false; 081 082 private List<EvaluatedDescription<? extends Score>> currentlyBestEvaluatedDescriptions; 083 084 public SimpleSubclassLearner() { 085 super.posExamplesQueryTemplate = new ParameterizedSparqlString("SELECT ?s WHERE {?s a ?cls1 . ?s a ?cls2}"); 086 super.negExamplesQueryTemplate = new ParameterizedSparqlString("SELECT ?s WHERE {?s a ?cls1 . FILTER NOT EXISTS{?s a ?cls2}}"); 087 088 axiomType = AxiomType.SUBCLASS_OF; 089 } 090 091 public SimpleSubclassLearner(SparqlEndpointKS ks) { 092 this(); 093 094 this.ks = ks; 095 } 096 097 @Override 098 public List<EvaluatedAxiom<OWLSubClassOfAxiom>> getCurrentlyBestEvaluatedAxioms() { 099 return getCurrentlyBestEvaluatedAxioms(currentlyBestEvaluatedDescriptions.size()); 100 } 101 102 @Override 103 public List<OWLClassExpression> getCurrentlyBestDescriptions(int nrOfDescriptions) { 104 return getCurrentlyBestEvaluatedDescriptions(nrOfDescriptions).stream() 105 .map(EvaluatedHypothesis::getDescription) 106 .collect(Collectors.toList()); 107 } 108 109 @Override 110 public List<? extends EvaluatedDescription<? extends Score>> getCurrentlyBestEvaluatedDescriptions(int nrOfDescriptions) { 111 int max = Math.min(currentlyBestEvaluatedDescriptions.size(), nrOfDescriptions); 112 return currentlyBestEvaluatedDescriptions.subList(0, max); 113 } 114 115 @Override 116 public List<OWLSubClassOfAxiom> getCurrentlyBestAxioms(int nrOfAxioms) { 117 return getCurrentlyBestEvaluatedAxioms(nrOfAxioms).stream() 118 .map(EvaluatedAxiom::getAxiom) 119 .collect(Collectors.toList()); 120 } 121 122 @Override 123 public List<EvaluatedAxiom<OWLSubClassOfAxiom>> getCurrentlyBestEvaluatedAxioms(int nrOfAxioms) { 124 currentlyBestAxioms = new TreeSet<>(); 125 for (EvaluatedDescription<? extends Score> ed : getCurrentlyBestEvaluatedDescriptions(nrOfAxioms)) { 126 currentlyBestAxioms.add(new EvaluatedAxiom<>(df.getOWLSubClassOfAxiom(entityToDescribe, 127 ed.getDescription()), new AxiomScore(ed.getAccuracy()))); 128 } 129 return new ArrayList<>(currentlyBestAxioms); 130 } 131 132 @Override 133 public void start() { 134 currentlyBestEvaluatedDescriptions = new ArrayList<>(); 135 super.start(); 136 } 137 138 /* (non-Javadoc) 139 * @see org.dllearner.core.AbstractAxiomLearningAlgorithm#getSampleQuery() 140 */ 141 @Override 142 protected ParameterizedSparqlString getSampleQuery() { 143 return SAMPLE_QUERY; 144 } 145 146 /* 147 * (non-Javadoc) 148 * 149 * @see 150 * org.dllearner.core.AbstractAxiomLearningAlgorithm#getExistingAxioms() 151 */ 152 @Override 153 protected void getExistingAxioms() { 154 // get existing super classes 155 SortedSet<OWLClassExpression> existingSuperClasses = reasoner.getSuperClasses(entityToDescribe, false); 156 existingSuperClasses.remove(df.getOWLThing()); 157 logger.info("Existing super classes: " + existingSuperClasses); 158 159 existingAxioms.addAll(existingSuperClasses.stream() 160 .map(sup -> df.getOWLSubClassOfAxiom(entityToDescribe, sup)) 161 .collect(Collectors.toList())); 162 } 163 164 /* 165 * (non-Javadoc) 166 * 167 * @see org.dllearner.core.AbstractAxiomLearningAlgorithm#learnAxioms() 168 */ 169 @Override 170 protected void learnAxioms() { 171 if(batchMode) { 172 runBatched(); 173 } else { 174 runIterative(); 175 } 176 177 currentlyBestEvaluatedDescriptions.forEach( 178 ed -> currentlyBestAxioms.add( 179 new EvaluatedAxiom<>(df.getOWLSubClassOfAxiom(entityToDescribe, ed.getDescription()), 180 new AxiomScore(ed.getAccuracy())))); 181 } 182 183 private void runIterative() { 184 CLASS_OVERLAP_QUERY.setIri("cls", entityToDescribe.toStringID()); 185 186 // get the candidates 187 SortedSet<OWLClass> candidates = getCandidates(); 188 189 // check for each candidate if an overlap exist 190 int i = 1; 191 for (OWLClass cls : candidates) { 192 progressMonitor.learningProgressChanged(axiomType, i++, candidates.size()); 193 194 // get the popularity of the candidate 195 int candidatePopularity = reasoner.getPopularity(cls); 196 197 if(candidatePopularity == 0){// skip empty classes 198 logger.debug("Cannot compute subclass statements for empty candidate class " + cls); 199 continue; 200 } 201 202 // get the number of instances that belong to both classes 203 CLASS_OVERLAP_QUERY.setIri("cls_other", cls.toStringID()); 204 205 ResultSet rs = executeSelectQuery(CLASS_OVERLAP_QUERY.toString()); 206 int overlap = rs.next().getLiteral("cnt").getInt(); 207 208 // compute the score 209 AxiomScore score = computeScore(popularity, overlap); 210 211 currentlyBestEvaluatedDescriptions.add(new EvaluatedDescription(cls, new ScoreSimple(score.getAccuracy()))); 212 } 213 } 214 215 /** 216 * Returns the candidate properties for comparison. 217 * @return the candidate properties 218 */ 219 private SortedSet<OWLClass> getCandidates(){ 220 // get the candidates 221 SortedSet<OWLClass> candidates = strictOWLMode ? reasoner.getOWLClasses() : reasoner.getClasses(); 222 223 // remove class to describe 224 candidates.remove(entityToDescribe); 225 226 return candidates; 227 } 228 229 private void runBatched() { 230 ParameterizedSparqlString template = strictOWLMode ? CLASS_OVERLAP_BATCH_QUERY_STRICT_OWL : CLASS_OVERLAP_BATCH_QUERY; 231 232 template.setIri("cls", entityToDescribe.toStringID()); 233 234 ResultSet rs = executeSelectQuery(template.toString()); 235 236 while (rs.hasNext()) { 237 QuerySolution qs = rs.next(); 238 239 RDFNode cls = qs.get("cls_other"); 240 if (!cls.isAnon()) { 241 OWLClass sup = OwlApiJenaUtils.asOWLEntity(cls.asNode(), EntityType.CLASS); 242 int overlap = qs.get("cnt").asLiteral().getInt(); 243 if (!sup.isOWLThing() && !entityToDescribe.equals(sup)) {//omit owl:Thing and the class to describe itself 244 currentlyBestEvaluatedDescriptions.add(new EvaluatedDescription(sup, computeScore(popularity, 245 overlap))); 246 } 247 } else { 248 logger.warn("Ignoring anonymous super class candidate: " + cls); 249 } 250 } 251 } 252 253 protected Set<OWLClassAssertionAxiom> getExamples(ParameterizedSparqlString queryTemplate, EvaluatedAxiom<OWLSubClassOfAxiom> evAxiom) { 254 OWLSubClassOfAxiom axiom = evAxiom.getAxiom(); 255 queryTemplate.setIri("cls1", axiom.getSubClass().asOWLClass().toStringID()); 256 queryTemplate.setIri("cls2", axiom.getSuperClass().asOWLClass().toStringID()); 257 258 Set<OWLClassAssertionAxiom> examples = new TreeSet<>(); 259 260 ResultSet rs = executeSelectQuery(queryTemplate.toString()); 261 262 while (rs.hasNext()) { 263 QuerySolution qs = rs.next(); 264 OWLIndividual subject = df.getOWLNamedIndividual(IRI.create(qs.getResource("s").getURI())); 265 examples.add(df.getOWLClassAssertionAxiom(axiom.getSuperClass(), subject)); 266 } 267 268 return examples; 269 } 270 271 public void setBatchMode(boolean batchMode) { 272 this.batchMode = batchMode; 273 } 274 275 public boolean isBatchMode() { 276 return batchMode; 277 } 278 279 public void setStrictOWLMode(boolean strictOWLMode) { 280 this.strictOWLMode = strictOWLMode; 281 } 282 283 public boolean isStrictOWLMode() { 284 return strictOWLMode; 285 } 286 287 public static void main(String[] args) throws Exception { 288 ToStringRenderer.getInstance().setRenderer(new DLSyntaxObjectRenderer()); 289 SparqlEndpointKS ks = new SparqlEndpointKS(SparqlEndpoint.getEndpointDBpedia()); 290 ks.init(); 291 292 SimpleSubclassLearner la = new SimpleSubclassLearner(ks); 293 la.setEntityToDescribe(new OWLClassImpl(IRI.create("http://dbpedia.org/ontology/Book"))); 294 la.setUseSampling(false); 295 la.setBatchMode(true); 296 la.setStrictOWLMode(true); 297 la.setReturnOnlyNewAxioms(true); 298 la.setProgressMonitor(new ConsoleAxiomLearningProgressMonitor()); 299 la.init(); 300 301 la.start(); 302 303 la.getCurrentlyBestEvaluatedAxioms(0.3).forEach(ax -> { 304 System.out.println("---------------\n" + ax); 305 la.getPositiveExamples(ax).stream().limit(5).forEach(System.out::println); 306 }); 307 } 308}