您可以使用 SPARQL 计算这种指标,尽管它有点难看。让我们假设一些数据是这样的:
prefix dcterms: <http://purl.org/dc/terms/>
prefix : <http://example.org/books/>
:book1 a :Book ; dcterms:subject :subject1 , :subject2, :subject3 .
:book2 a :Book ; dcterms:subject :subject2 , :subject3, :subject4 .
:book3 a :Book ; dcterms:subject :subject4 , :subject5 .
一共有三本书。第 1 册和第 2 册有两个共同的主题,并且各有一个主题,而另一个则没有。第 2 册和第 3 册有一个共同主题,但第 2 册有第 3 册没有的 2 个主题,而第 3 册只有第 2 册没有的主题,第 1 册和第 3 册没有共同主题。
这里的技巧是使用一些嵌套的子查询,并在嵌套的不同级别获取不同的值(C10、C01 和 C11)。最里面的查询是
select ?book1 ?book2 (count(?left) as ?c10) where {
:Book ^a ?book1, ?book2 .
FILTER( !sameTerm(?book1,?book2) )
OPTIONAL {
?book1 dcterms:subject ?left .
FILTER NOT EXISTS { ?book2 dcterms:subject ?left }
}
}
group by ?book1 ?book2
它抓取每一对不同的书并计算左书具有而右书没有的主题数量。通过将其包装在另一个查询中,我们可以获取右侧书籍具有而左侧书籍没有的主题数量。这使得查询:
select ?book1 ?book2 (count(?right) as ?c01x) (sample(?c10) as ?c10x) where {
{
select ?book1 ?book2 (count(?left) as ?c10) where {
:Book ^a ?book1, ?book2 .
FILTER( !sameTerm(?book1,?book2) )
OPTIONAL {
?book1 dcterms:subject ?left .
FILTER NOT EXISTS { ?book2 dcterms:subject ?left }
}
}
group by ?book1 ?book2
}
OPTIONAL {
?book2 dcterms:subject ?right .
FILTER NOT EXISTS { ?book1 dcterms:subject ?right }
}
}
group by ?book1 ?book2
请注意,我们仍然必须选择 ?book1 和 ?book2 和 sample(?c10) as ?c10x 才能向外传递值。 (我们必须使用?c10x,因为名称?c10 已在此范围内使用。最后,我们将其包装在另一个查询中以获取常见主题并进行计算,这给了我们:
prefix dcterms: <http://purl.org/dc/terms/>
prefix : <http://example.org/books/>
select ?book1 ?book2
(count(?both) as ?c11)
(sample(?c10x) as ?c10)
(sample(?c01x) as ?c01)
(count(?both) / (count(?both) + sample(?c10x) + sample(?c01x)) as ?sim)
where {
{
select ?book1 ?book2 (count(?right) as ?c01x) (sample(?c10) as ?c10x) where {
{
select ?book1 ?book2 (count(?left) as ?c10) where {
:Book ^a ?book1, ?book2 .
FILTER( !sameTerm(?book1,?book2) )
OPTIONAL {
?book1 dcterms:subject ?left .
FILTER NOT EXISTS { ?book2 dcterms:subject ?left }
}
}
group by ?book1 ?book2
}
OPTIONAL {
?book2 dcterms:subject ?right .
FILTER NOT EXISTS { ?book1 dcterms:subject ?right }
}
}
group by ?book1 ?book2
}
OPTIONAL {
?both ^dcterms:subject ?book1, ?book2 .
}
}
group by ?book1 ?book2
order by ?book1 ?book2
这个相当可怕的查询,应用于我们的数据,计算出这些结果:
$ arq --data data.n3 --query similarity.sparql
--------------------------------------------
| book1 | book2 | c11 | c10 | c01 | sim |
============================================
| :book1 | :book2 | 2 | 1 | 1 | 0.5 |
| :book1 | :book3 | 0 | 3 | 2 | 0.0 |
| :book2 | :book1 | 2 | 1 | 1 | 0.5 |
| :book2 | :book3 | 1 | 2 | 1 | 0.25 |
| :book3 | :book1 | 0 | 2 | 3 | 0.0 |
| :book3 | :book2 | 1 | 1 | 2 | 0.25 |
--------------------------------------------
如果FILTER( !sameTerm(?book1,?book2) ) 行被删除,那么每本书与其自身的相似度也会被计算出来,我们会看到正确的值(1.0):
$ arq --data data.n3 --query similarity.sparql
--------------------------------------------
| book1 | book2 | c11 | c10 | c01 | sim |
============================================
| :book1 | :book1 | 3 | 0 | 0 | 1.0 |
| :book1 | :book2 | 2 | 1 | 1 | 0.5 |
| :book1 | :book3 | 0 | 3 | 2 | 0.0 |
| :book2 | :book1 | 2 | 1 | 1 | 0.5 |
| :book2 | :book2 | 3 | 0 | 0 | 1.0 |
| :book2 | :book3 | 1 | 2 | 1 | 0.25 |
| :book3 | :book1 | 0 | 2 | 3 | 0.0 |
| :book3 | :book2 | 1 | 1 | 2 | 0.25 |
| :book3 | :book3 | 2 | 0 | 0 | 1.0 |
--------------------------------------------
如果您不需要保留各种 Cmn 值,那么您也许可以对此进行优化,例如,通过计算最内层查询中的 C01 和中间查询旁边的 C10,但不是投影两者都单独计算,只乘以它们的总和 (C10+C01),这样在计算 C11 的最外层查询中,您就可以执行 (C11 / (C11 + (C10+C01)))。