SemMF - A Semantic Matching Framework
How to Create a Matching Description
Radoslaw Oldakowski
November 2006
A matching description contains all informatoin about the matching process (e.g. location of query and resource RDF Graphs, selection of nodes within these graphs, associated matchers, assinged weights, etc.) needed to set up the matching engine. It is represented in RDF using SemMF vocabulary.
This tutorial will demonstrate how to create a matching description for a simple use case scenario. Our example will be about job matching. Given a job position posting (query object) we want to find the best matching job applicant among three candidates (resource objects). A fragment of an RDF description of our simple job posing is shown below
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:jpp="http://example.org/JobPositionPosting.rdfs#"
xmlns:skills="http://example.org/it-skills.rdfs#"
xml:base="http://example.org/JobPositionPosting.rdfs#">
<jpp:JobPositionPosting rdf:ID="JPP_1">
<jpp:hasPostDetails>
[...]
</jpp:hasPostDetails>
<jpp:hasHiringOrganisation>
[...]
</jpp:hasHiringOrganisation>
<jpp:hasJobPositionLocation>
[...]
</jpp:hasJobPositionLocation>
<jpp:hasJobDetails>
<jpp:JobDetails>
<jpp:travelRequired>false</jpp:travelRequired>
<jpp:hasSalaryBase>40000</jpp:hasSalaryBase>
<jpp:requiredCompetence>
<skills:Java>
<skills:competenceLevel rdf:resource="http://example.org/it-skills.rdfs#Advanced"/>
</skills:Java>
</jpp:requiredCompetence>
<jpp:requiredCompetence>
<skills:JavaScript>
<skills:competenceLevel rdf:resource="http://example.org/it-skills.rdfs#Expert"/>
</skills:JavaScript>
</jpp:requiredCompetence>
<jpp:requiredCompetence>
<skills:Relational_databases>
<skills:competenceLevel rdf:resource="http://example.org/it-skills.rdfs#Intermediate"/>
</skills:Relational_databases>
</jpp:requiredCompetence>
</jpp:JobDetails>
</jpp:hasJobDetails>
</jpp:JobPositionPosting>
</rdf:RDF>
|
Note that, on the one hand this RDF graph contains a lot of information (post details, description of the hiring organization, and location information) which we don't need for similarity computation between this open position and an applicant's profile. On the other hand there is information which is crucial for our matching. Thus, in our matching description we will have to explicitly specify the latter. In this example there are three properties: salary information, requirement of travel, as well as required competences upon which we want to choose the best candidate.
Now we will have a look at a fragment of an RDF description of our job applicants
<rdf:RDF
xmlns:skills="http://example.org/it-skills.rdfs#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:jps="http://example.org/JobPositionSeeker.rdfs#"
xml:base="http://example.org/JobPositionSeeker.rdfs#">
<jps:JobPositionSeeker rdf:ID="JPS_1">
<jps:hasPostDetails>
[...]
</jps:hasPostDetails>
<jps:hasPostingPerson>
[...]
</jps:hasPostingPerson>
<jps:desiredSalaryBase>41000</jps:desiredSalaryBase>
<jps:travelDesired>false</jps:travelDesired>
<jps:hasSkill>
<skills:PHP>
<skills:competenceLevel rdf:resource="http://example.org/it-skills.rdfs#Intermediate"/>
</skills:PHP>
</jps:hasSkill>
<jps:hasSkill>
<skills:Oracle>
<skills:competenceLevel rdf:resource="http://example.org/it-skills.rdfs#Advanced"/>
</skills:Oracle>
</jps:hasSkill>
<jps:hasSkill>
<skills:Java>
<skills:competenceLevel rdf:resource="http://example.org/it-skills.rdfs#Expert"/>
</skills:Java>
</jps:hasSkill>
</jps:JobPositionSeeker>
<jps:JobPositionSeeker rdf:ID="JPS_2">
[...]
</jps:JobPositionSeeker>
<jps:JobPositionSeeker rdf:ID="JPS_3">
[...]
</jps:JobPositionSeeker>
</rdf:RDF>
|
The resources representing job applicants also contain both information irrelevant for similarity computation as well as the properties (i.e. salary, travel desired, and skills) upon which we want to match.
Note that, SemMF does not require both graphs to use the same vocabulary, like in this example where we have terms from http://example.org/JobPositionPosting.rdfs#
and http://example.org/JobPositionSeeker.rdfs#
. However, the values of the coresponding properties to be matched must have the same range, like in this example where terms from a common skills taxonomy (http://example.org/it-skills.rdfs#
) are used to describe both skills required and those of a candidate.
As meantioned before, a SemMF matching description is represented in RDF. In this tutorial we will be using Jena to construct an RDF graph of a matching description for our use case. So, first we include some Jena classes for manipulating RDF models as well as classes holding objects representing terms from RDF and SemMF vocabularies.
import com.hp.hpl.jena.rdf.model.Bag;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.vocabulary.RDF;
import de.fuberlin.wiwiss.semmf.vocabulary.MD;
|
We start with generating an empty Jena Model and putting inside a new Resource gmd
representing our matching description. As next we use objects form the SemMF vocabulary class to describe it
Model m = ModelFactory.createDefaultModel();
Resource gmd = m.createResource();
gmd.addProperty(RDF.type, MD.GraphMatchingDescription);
gmd.addProperty(MD.queryModelURL, "file:./doc/examples/jpp.rdf");
gmd.addProperty(MD.resModelURL, "file:./doc/examples/jps.rdf");
gmd.addProperty(MD.queryGraphURI, "http://example.org/JobPositionPosting.rdfs#JPP_1");
gmd.addProperty(MD.resGraphURIpath, "(?x <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/JobPositionSeeker.rdfs#JobPositionSeeker>)");
|
A Resource representing a matching description MUST be of type semmf:GraphMatchingDescription
and MUST have four properties:
semmf:queryModelURL
and semmf:resModelURL
- location of query and resource RDF graphs
semmf:queryGraphURI
- URI of the query graph (in our example: jpp:JPP_1
) within the RDF graph specified in MD.queryModelURL
.
semmf:resGraphURIpath
- a set of triple patterns (s p o) which SemMF engine passes to the underlying Jena RDQL engine to retrieve URIs (variable ?x) of all resource objects within the RDF graph specified in MD.resModelURL
. In our example, this simple query will return: jps:JPS_1
, jps:JPS_2
, jps:JPS_3
. Note that all URIs within a pattern have to be written in full.
In the next step we want to group the properteis to be matched into two 'clusters'. The first one (cmd_1
) should hold salary and travel information the second one (cmd_2
) description of skills. The rationale behind property grouping is that it will later allow us to sort the matching output not only by general object similary but also by each single cluster similarity, which in some scenarios might be usefull.
Bag cmds = m.createBag();
gmd.addProperty(MD.hasClusterMatchingDescriptions, cmds);
Resource cmd_1 = m.createResource();
cmd_1.addProperty(RDF.type, MD.ClusterMatchingDescription);
cmd_1.addProperty(MD.label, "job details");
cmd_1.addProperty(MD.weight, "0.25");
cmds.add(cmd_1);
Resource cmd_2 = m.createResource();
cmd_2.addProperty(RDF.type, MD.ClusterMatchingDescription);
cmd_2.addProperty(MD.label, "skills");
cmd_2.addProperty(MD.weight, "0.75");
cmds.add(cmd_2);
|
As you can see, the Resource representing a matching description (gmd
) has now another property semmf:hasClusterMatchingDescriptions
with its value being an rdf:Bag holding a collection of resources of type semmf:ClusterMatchingDescription
. Each cluster Resource gets a label (smmf:label
) and weight (semmf:weight
) indicating its influence on the overall similarity score. The sum of all cluster weights MUST equal 1!
Since the values of our object properties (salary, travel, skills) are RDF Nodes, we now have to describe how these nodes are accessed and what matching techniques shall be used. We begin with travel information and put it inside the first cluster
Bag nmds_c1 = m.createBag();
cmd_1.addProperty(MD.hasNodeMatchingDescriptions, nmds_c1);
Resource c1_nmd_1 = m.createResource();
nmds_c1.add(c1_nmd_1);
c1_nmd_1.addProperty(RDF.type, MD.NodeMatchingDescription);
c1_nmd_1.addProperty(MD.label, "travel required");
c1_nmd_1.addProperty(MD.weight, "0.3");
c1_nmd_1.addProperty(MD.queryNodePath, "(<http://example.org/JobPositionPosting.rdfs#JPP_1> <http://example.org/JobPositionPosting.rdfs#hasJobDetails> ?jobDetails) (?jobDetails <http://example.org/JobPositionPosting.rdfs#travelRequired> ?x)");
c1_nmd_1.addProperty(MD.resNodePath, "(<#graphEntryURI#> <http://example.org/JobPositionSeeker.rdfs#travelDesired> ?x)");
c1_nmd_1.addProperty(MD.reverseMatching, "false");
|
The resource representing our first cluster (cmd_1
) gets a new property semmf:hasNodeMatchingDescriptions
pointing to an rdf:Bag holding a collection of semmf:NodeMatchingDescription
s. For each NodeMatchingDescription
we provide the following information:
semmf:label
- label of the object property
semmf:weight -
weight of the object property indicating its importance inside a cluster. The sum of all weights inside each cluster MUST equal 1!
semmf:queryNodePath
- a set of triple patterns (s p o), starting from the root (here: jpp:JPP_1
) of the query graph, which is passed by SemMF engine to the underlying Jena RDQL engine to retrieve an RDF Node (variable ?x) representing a property value (here: travel information ) of the query object
semmf:resNodePath
- a set of triple patterns (s p o) which is passed by SemMF engine to the underlying Jena RDQL engine to retrieve RDF Nodes (variable ?x) representing property values (here: travel information ) of each resource object. The placeholder <#graphEntryURI#>
stands for a root concept of a given resource graph and before query execution will be replaced with its actual URI (see semmf:resGraphURIpath
).
(
semmf:reverseMatching
)
- since similarity(a,b) not always equals similarity(b,a), this parameter indicates if the matching direction should be reversed for this object property. "False" is the default value, so in this example we can also leave out this information.
Example: In most use cases we compare an object property value from a query graph (= "required" value) with the semantically corresponding value from a resource graph. But for some object properties it is reasonable to reverse the matching direction. Consider a job matching scenario in which given a description of a job applicant (query object) we want to find the best matching job position (opposite matching direction compared with our tutorial scenario). In this case, we would not be interested in determining how well the competences from a job posting match candidate's skills but the other way around we want to know how well the applicant satisfies the skills required for an open position.
Additionally, using the property semmf:useMatcher
from the SemMF vocabulary we specify which matcher shall be used for similarity computation. Since the possible values of the travel property from our example are "true" or "false" we take the build-in simple StringMatcher
Resource sm = m.createResource();
sm.addProperty(RDF.type, MD.StringMatcher);
sm.addProperty(MD.caseSensitive, "false");
c1_nmd_1.addProperty(MD.useMatcher, sm);
|
The Resource describing our matcher is of type semmf:StringMatcher
and contains one parameter semmf:caseSensitive
indicating case insensitive matching, which is the default setting for this matcher and therefore can be left out.
Now, we are done with providing matching directives for the first object property (travel). As next, we do the same for salary information and also put it inside the firist cluster into an rdf:Bag (nmds_c1
) with NodeMatchingDescription
s
Resource c1_nmd_2 = m.createResource();
nmds_c1.add(c1_nmd_2);
c1_nmd_2.addProperty(RDF.type, MD.NodeMatchingDescription);
c1_nmd_2.addProperty(MD.label, "salary");
c1_nmd_2.addProperty(MD.weight, "0.7");
c1_nmd_2.addProperty(MD.queryNodePath, "(<http://example.org/JobPositionPosting.rdfs#JPP_1> <http://example.org/JobPositionPosting.rdfs#hasJobDetails> ?jobDetails) (?jobDetails <http://example.org/JobPositionPosting.rdfs#hasSalaryBase> ?x)");
c1_nmd_2.addProperty(MD.resNodePath, "(<#graphEntryURI#> <http://example.org/JobPositionSeeker.rdfs#desiredSalaryBase> ?x)");
c1_nmd_2.addProperty(MD.reverseMatching, "false");
Resource nm = m.createResource();
nm.addProperty(RDF.type, MD.NumericMatcher);
nm.addProperty(MD.decreaseSim, "upwards");
nm.addProperty(MD.maxDevFraction, "0.25");
c1_nmd_2.addProperty(MD.useMatcher, nm);
|
This time, however, we take the integrated NumericMatcher to calculate the similarity of salay information. In RDF this matcher is represented by a resource of type semmf:NumericMatcher
having two parameters semmf:decreaseSim
and semmf:maxDevFraction
. The former indicates that with increasing property values from a resource object (desired salary base) beeing greater than ("upwards"
) the property value of the query object (= 40.000 - see RDF fragment of our job posting) the similarity should desrease until it reaches 0 at the upper bound ( 40.000 + 40.000*0.25 (semmf:maxDevFraction
) = 50.000). In our use case example this would mean, that the hiring organization offering 40.000 for the job, would consider every candidate wanting less than 40k as perfekt match (sim=1), and would be ready to pay maximum up to 50.000, beyond which sim=0. For all other candidates which desired salary base lays in the interval [40k,50k] the similarity score would be between 0 and 1.
As meantioned before we want to put the third object property (skills) into a separate cluster. Because there are no other properties inside this cluster the associated weight MUST equal 1.
Bag nmds_c2 = m.createBag();
cmd_2.addProperty(MD.hasNodeMatchingDescriptions, nmds_c2);
Resource c2_nmd_1 = m.createResource();
nmds_c2.add(c2_nmd_1);
c2_nmd_1.addProperty(RDF.type, MD.NodeMatchingDescription);
c2_nmd_1.addProperty(MD.label, "skill node");
c2_nmd_1.addProperty(MD.weight, "1");
c2_nmd_1.addProperty(MD.queryNodePath, "(<http://example.org/JobPositionPosting.rdfs#JPP_1> <http://example.org/JobPositionPosting.rdfs#hasJobDetails> ?jobDetails) (?jobDetails <http://example.org/JobPositionPosting.rdfs#requiredCompetence> ?x)");
c2_nmd_1.addProperty(MD.resNodePath, "(<#graphEntryURI#> <http://example.org/JobPositionSeeker.rdfs#hasSkill> ?x)");
c2_nmd_1.addProperty(MD.reverseMatching, "false");
|
Note that, in the above specification the path expressions provided (semmf:queryNodePath
and semmf:queryNodePath
) both lead to an RDF BlankNode representing skill information. Each of them has two RDF properties describing type of skill (instance from our simple skill taxonomy) as well as competence level. Our similarity score for skills should therefore be computed upon both. Becasue the value of the object property 'skills' is not atomic, we do not assign it any matcher yet. Instead, we have to deliver further matching derectives regarding the RDF properties hanging from those blank nodes. We do it by first adding a new property semmf:hasPropertyMatchingDescriptions
to our previously created NodeMatchingDescription (c2_nmd_1).
The value of this property is an rdf:Bag for holding a collection of semmf:PropertyMatchingDescription
s. The first resource in this Bag describes a matching specificatuion for the porpoerty 'skill type' (see code snippet below), the second one for the property 'competence level'.
Bag pmds = m.createBag();
c2_nmd_1.addProperty(MD.hasPropertyMatchingDescriptions, pmds);
Resource pmd_1 = m.createResource();
pmds.add(pmd_1);
pmd_1.addProperty(RDF.type, MD.PropertyMatchingDescription);
pmd_1.addProperty(MD.label, "skill");
pmd_1.addProperty(MD.weight, "0.8");
pmd_1.addProperty(MD.queryPropURI, RDF.type);
pmd_1.addProperty(MD.resPropURI, RDF.type);
pmd_1.addProperty(MD.reverseMatching, "false");
|
For each resource representing a semmf:PropertyMatchingDescription
we provide the following information:
semmf:label
- label of the property
semmf:weight -
weight of the property indicating its importance inside a NodeMatchingDescription
. The sum of all weights inside each NodeMatchingDescription
MUST equal 1!
semmf:queryPropURI
- a URI identyfing the property hanging from a BlankNode inside a query graph (in our example: rdf:type)
semmf:resPropeURI
- a URI identyfing the property hanging from a BlankNode inside a resource graph (in our example: rdf:type)
(
semmf:reverseMatching
)
- indicates if the matching direction should be reversed. "False" is the default value, so in this example we can also leave out this information.
Since the values of the RDF.type
property hanging from our blank nodes are concepts from a skill taxonomy, we take the TaxonomicMatcher, which computes the similarity of two concepts based on their respective position in the unterlying taxonomy instead of merely relying on an exact match of keywords like, for instance, the StringMatcher does.
Resource tm_1 = m.createResource();
tm_1.addProperty(RDF.type, MD.TaxonomicMatcher);
tm_1.addProperty(MD.simInheritance, "true");
pmd_1.addProperty(MD.useMatcher, tm_1);
Resource taxon_skills = m.createResource();
taxon_skills.addProperty(RDF.type, MD.Taxonomy);
taxon_skills.addProperty(MD.taxonomyURL, "file:./doc/examples/it-skills.rdfs");
taxon_skills.addProperty(MD.rootConceptURI, "http://example.org/it-skills.rdfs#IT_Skills");
tm_1.addProperty(MD.taxonomy, taxon_skills);
Resource emc = m.createResource();
emc.addProperty(RDF.type, MD.ExpMilestCalc);
emc.addProperty(MD.k_factor, "2");
tm_1.addProperty(MD.useMilestoneCalc, emc);
|
In RDF this kind of matcher is represented by a resource of type semmf:TaxonomicMatcher
having three parameters:
semmf:taxonomy
- a resource of type semmf:Taxonomy
describing the underlying taxonomy. Each taxonomy resource MUST provide:
semmf:taxonomyURL
- location of an RDFS or OWL description of the taxonomy
semmf:rootConceptURI
- URI of the root concept within the taxonomy. All concepts to be matched using this matcher must be descendants of root!
semmf:useMilestoneCalc
- indicates the algorithm used in calculating milestones for taxonomy levels. Since, in this example, we use an semmf:ExpMilestCalc
we also must specify its only parameter semmf:k_factor
. (See TaxonomicMatcher for further details)
(
semmf:simInheritance)
- if set to true (default setting) then
sim(queryConcept, resourceConcept = any descendant of queryConcept) = 1. This assumption seems to be reasonable because a subclass is always a kind of its superclass. Hovewer, there may be cases in which the actual distance
between a subclass and its superclass should influence similarity calculation.
In the same way as demonstrated above we provide matching information for the second property ('competence level') hanging from the blank node representing skill information
Resource pmd_2 = m.createResource();
pmds.add(pmd_2);
pmd_2.addProperty(RDF.type, MD.PropertyMatchingDescription);
pmd_2.addProperty(MD.label, "skill level");
pmd_2.addProperty(MD.weight, "0.2");
pmd_2.addProperty(MD.queryPropURI, "http://example.org/it-skills.rdfs#competenceLevel");
pmd_2.addProperty(MD.resPropURI, "http://example.org/it-skills.rdfs#competenceLevel");
pmd_2.addProperty(MD.reverseMatching, "false");
|
Since the range of this property are terms from a simple taxonomy of skill levels
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xml:base="http://example.org/it-skills.rdfs#">
<rdfs:Property rdf:ID="competenceLevel">
<rdfs:range rdf:resource="#Skill_level"/>
</rdfs:Property>
<rdfs:Class rdf:about="#Beginner">
<rdfs:subClassOf rdf:resource="#Skill_level"/>
</rdfs:Class>
<rdfs:Class rdf:about="#Intermediate">
<rdfs:subClassOf rdf:resource="#Beginner"/>
</rdfs:Class>
<rdfs:Class rdf:about="#Advanced">
<rdfs:subClassOf rdf:resource="#Intermediate"/>
</rdfs:Class>
<rdfs:Class rdf:about="#Expert">
<rdfs:subClassOf rdf:resource="#Advanced"/>
</rdfs:Class>
</rdf:RDF>
|
we specify another instance of TaxonomicMatcher by providing description of the taxonomy and milestone calculator to be used
Resource tm_2 = m.createResource();
tm_2.addProperty(RDF.type, MD.TaxonomicMatcher);
pmd_2.addProperty(MD.useMatcher, tm_2);
tm_2.addProperty(MD.simInheritance, "true");
Resource taxon_skillLevel = m.createResource();
taxon_skillLevel.addProperty(RDF.type, MD.Taxonomy);
taxon_skillLevel.addProperty(MD.taxonomyURL, "file:./doc/examples/it-skills.rdfs");
taxon_skillLevel.addProperty(MD.rootConceptURI, "http://example.org/it-skills.rdfs#Skill_level");
tm_2.addProperty(MD.taxonomy, taxon_skillLevel);
Resource lmc = m.createResource();
lmc.addProperty(RDF.type, MD.LinMilestCalc);
tm_2.addProperty(MD.useMilestoneCalc, lmc);
|
This time, hovever, we take the linear milestone calculator semmf:LinMilestCalc
(See TaxonomicMatcher for further details).
Our matching description is finished now. Finally, we write the RDF model just created to file
m.setNsPrefix("semmf", MD.NS);
m.setNsPrefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
m.setNsPrefix("ja", "http://jena.hpl.hp.com/2005/11/Assembler#");
try {
File outputFile = new File("file:doc/examples/jobMD.n3");
if (!outputFile.exists()) {
outputFile.createNewFile();
}
FileOutputStream out = new FileOutputStream(outputFile);
m.write(out, "N3");
out.close();
}
catch (IOException e) { System.out.println(e.toString()); }
|
Here you can see the RDF/XML serialization of our matching description. The output
in N3 should look something like this:
@prefix semmf: <http://semmf.ag-nbi.de/vocabulary/1.1/semmf.rdfs#> .
@prefix ja: <http://jena.hpl.hp.com/2005/11/Assembler#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix : <#> .
[] a semmf:GraphMatchingDescription ;
semmf:queryGraphURI "http://example.org/JobPositionPosting.rdfs#JPP_1" ;
semmf:queryModelURL "file:./doc/examples/jpp.rdf" ;
semmf:resGraphURIpath "(?x <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/JobPositionSeeker.rdfs#JobPositionSeeker>)" ;
semmf:resModelURL "file:./doc/examples/jps.rdf" ;
semmf:hasClusterMatchingDescriptions
[ a rdf:Bag ;
rdf:_1 [ a semmf:ClusterMatchingDescription ;
semmf:label "job details" ;
semmf:weight "0.25" ;
semmf:hasNodeMatchingDescriptions
[ a rdf:Bag ;
rdf:_1 [ a semmf:NodeMatchingDescription ;
semmf:label "travel desired" ;
semmf:weight "0.3" ;
semmf:queryNodePath "(<http://example.org/JobPositionPosting.rdfs#JPP_1> <http://example.org/JobPositionPosting.rdfs#hasJobDetails> ?jobDetails) (?jobDetails <http://example.org/JobPositionPosting.rdfs#travelDesired> ?x)" ;
semmf:resNodePath "(<#graphEntryURI#> <http://example.org/JobPositionSeeker.rdfs#travelDesired> ?x)" ;
semmf:reverseMatching "false" ;
semmf:useMatcher
[ a semmf:StringMatcher ;
semmf:caseSensitive "false"
]
] ;
rdf:_2 [ a semmf:NodeMatchingDescription ;
semmf:label "salary" ;
semmf:weight "0.7" ;
semmf:queryNodePath "(<http://example.org/JobPositionPosting.rdfs#JPP_1> <http://example.org/JobPositionPosting.rdfs#hasJobDetails> ?jobDetails) (?jobDetails <http://example.org/JobPositionPosting.rdfs#hasSalaryBase> ?x)" ;
semmf:resNodePath "(<#graphEntryURI#> <http://example.org/JobPositionSeeker.rdfs#desiredSalaryBase> ?x)" ;
semmf:reverseMatching "false" ;
semmf:useMatcher
[ a semmf:NumericMatcher ;
semmf:decreaseSim "upwards" ;
semmf:maxDevFraction "0.25"
]
]
]
] ;
rdf:_2 [ a semmf:ClusterMatchingDescription ;
semmf:label "skills" ;
semmf:weight "0.75" ;
semmf:hasNodeMatchingDescriptions
[ a rdf:Bag ;
rdf:_1 [ a semmf:NodeMatchingDescription ;
semmf:label "skill node" ;
semmf:queryNodePath "(<http://example.org/JobPositionPosting.rdfs#JPP_1> <http://example.org/JobPositionPosting.rdfs#hasJobDetails> ?jobDetails) (?jobDetails <http://example.org/JobPositionPosting.rdfs#requiredCompetence> ?x)" ;
semmf:resNodePath "(<#graphEntryURI#> <http://example.org/JobPositionSeeker.rdfs#hasSkill> ?x)" ;
semmf:reverseMatching "false" ;
semmf:weight "1" ;
semmf:hasPropertyMatchingDescriptions
[ a rdf:Bag ;
rdf:_1 [ a semmf:PropertyMatchingDescription ;
semmf:label "skill" ;
semmf:weight "0.8" ;
semmf:queryPropURI rdf:type ;
semmf:resPropURI rdf:type ;
semmf:reverseMatching "false" ;
semmf:useMatcher
[ a semmf:TaxonomicMatcher ;
semmf:taxonomy
[ a semmf:Taxonomy ;
semmf:rootConceptURI "http://example.org/it-skills.rdfs#IT_Skills" ;
semmf:taxonomyURL "file:./doc/examples/it-skills.rdfs"
] ;
semmf:milestoneCalc
[ a semmf:ExpMilestCalc ;
semmf:k_factor "2"
] ;
semmf:simInheritance "true"
]
] ;
rdf:_2 [ a semmf:PropertyMatchingDescription ;
semmf:label "skill level" ;
semmf:weight "0.2" ;
semmf:queryPropURI "http://example.org/it-skills.rdfs#competenceLevel" ;
semmf:resPropURI "http://example.org/it-skills.rdfs#competenceLevel" ;
semmf:reverseMatching "false" ;
semmf:useMatcher
[ a semmf:TaxonomicMatcher ;
semmf:taxonomy
[ a semmf:Taxonomy ;
semmf:rootConceptURI "http://example.org/it-skills.rdfs#Skill_level" ;
semmf:taxonomyURL "file:./doc/examples/it-skills.rdfs"
] ;
semmf:milestoneCalc
[ a semmf:LinMilestCalc
] ;
semmf:simInheritance "true"
]
]
]
]
]
]
] .
|
All example files from this tutorial are included in SemMF distrubution package (SemMF\doc\examples). The full working java code for this tutorial is found in de.fuberlin.wiwiss.semmf.examples.JobMatching.java.
see next: Use Case Example: Traversing the Matching Result