接上篇文章,这篇文章也是基于知乎专栏 https://zhuanlan.zhihu.com/knowledgegraph 的学习笔记。本文主要是梳理一遍专栏中实战的内容,以及就自己遇到的问题备注一下解决方案。感谢原up主与下面热心提供解决问题方案的知乎网友们。
原文:https://zhuanlan.zhihu.com/p/32389370
从博主的github上下载原始数据,在data文件夹中。把数据文件导入mysql。具体方法:
preprocess: 用编辑器打开sql文件,把所有的CHARSET=utf8mb4改为CHARSET=utf8。(避免后面不必要的麻烦)
3. 完成后产生一张名为kg_demo_movie的数据库,打开内有五张表,如图:
即上篇文章说到的owl的部分,搭建大类之间的关系,使用工具Protégé(点击进入官网下载)。
6. 选择“Window”选项选择“Tabs”,打开“OntoGraf”,再选择“OntoGraf”,拖拽标签可以让实体关系可视化。
原文:https://zhuanlan.zhihu.com/p/32552993
通过D2RQ(点击打开网址)来实现转化,下载压缩包(我下的是d2rq-0.8.1.tar.gz),解压,进入目录。
运行如下命令生成默认mapping文件:
generate-mapping -u root -o kg_demo_movie_mapping.ttl -p 123456 jdbc:mysql:///kg_demo_movie
“-u”后面是mysql的用户名,“-o”后面是保存的文件名,“-p”后面是mysql的密码(如果没有就不用输入“-p”及后面的内容),jdbc:mysql:///kg_demo_movie指定我们要映射的数据库。于是生成了默认的mapping文件:
@prefix map: <#> .
@prefix db: <> .
@prefix vocab: .
@prefix rdf: .
@prefix rdfs: .
@prefix xsd: .
@prefix d2rq: .
@prefix jdbc: .
map:database a d2rq:Database;
d2rq:jdbcDriver "com.mysql.jdbc.Driver";
d2rq:jdbcDSN "jdbc:mysql:///kg_demo_movie";
d2rq:username "root";
d2rq:password "123456";
jdbc:autoReconnect "true";
jdbc:zeroDateTimeBehavior "convertToNull";
.
# Table genre
map:genre a d2rq:ClassMap;
d2rq:dataStorage map:database;
d2rq:uriPattern "genre/@@genre.genre_id@@";
d2rq:class vocab:genre;
d2rq:classDefinitionLabel "genre";
.
map:genre__label a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:genre;
d2rq:property rdfs:label;
d2rq:pattern "genre #@@genre.genre_id@@";
.
map:genre_genre_id a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:genre;
d2rq:property vocab:genre_genre_id;
d2rq:propertyDefinitionLabel "genre genre_id";
d2rq:column "genre.genre_id";
d2rq:datatype xsd:integer;
.
map:genre_genre_name a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:genre;
d2rq:property vocab:genre_genre_name;
d2rq:propertyDefinitionLabel "genre genre_name";
d2rq:column "genre.genre_name";
.
# Table movie
map:movie a d2rq:ClassMap;
d2rq:dataStorage map:database;
d2rq:uriPattern "movie/@@movie.movie_id@@";
d2rq:class vocab:movie;
d2rq:classDefinitionLabel "movie";
.
map:movie__label a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:movie;
d2rq:property rdfs:label;
d2rq:pattern "movie #@@movie.movie_id@@";
.
map:movie_movie_id a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:movie;
d2rq:property vocab:movie_movie_id;
d2rq:propertyDefinitionLabel "movie movie_id";
d2rq:column "movie.movie_id";
d2rq:datatype xsd:integer;
.
map:movie_movie_title a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:movie;
d2rq:property vocab:movie_movie_title;
d2rq:propertyDefinitionLabel "movie movie_title";
d2rq:column "movie.movie_title";
.
map:movie_movie_introduction a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:movie;
d2rq:property vocab:movie_movie_introduction;
d2rq:propertyDefinitionLabel "movie movie_introduction";
d2rq:column "movie.movie_introduction";
.
map:movie_movie_rating a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:movie;
d2rq:property vocab:movie_movie_rating;
d2rq:propertyDefinitionLabel "movie movie_rating";
d2rq:column "movie.movie_rating";
d2rq:datatype xsd:double;
.
map:movie_movie_release_date a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:movie;
d2rq:property vocab:movie_movie_release_date;
d2rq:propertyDefinitionLabel "movie movie_release_date";
d2rq:column "movie.movie_release_date";
.
# Table movie_to_genre (n:m)
map:movie_to_genre__link a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:movie;
d2rq:property vocab:movie_to_genre;
d2rq:refersToClassMap map:genre;
d2rq:join "movie_to_genre.movie_id => movie.movie_id";
d2rq:join "movie_to_genre.genre_id => genre.genre_id";
.
# Table person
map:person a d2rq:ClassMap;
d2rq:dataStorage map:database;
d2rq:uriPattern "person/@@person.person_id@@";
d2rq:class vocab:person;
d2rq:classDefinitionLabel "person";
.
map:person__label a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property rdfs:label;
d2rq:pattern "person #@@person.person_id@@";
.
map:person_person_id a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property vocab:person_person_id;
d2rq:propertyDefinitionLabel "person person_id";
d2rq:column "person.person_id";
d2rq:datatype xsd:integer;
.
map:person_person_birth_day a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property vocab:person_person_birth_day;
d2rq:propertyDefinitionLabel "person person_birth_day";
d2rq:column "person.person_birth_day";
.
map:person_person_death_day a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property vocab:person_person_death_day;
d2rq:propertyDefinitionLabel "person person_death_day";
d2rq:column "person.person_death_day";
.
map:person_person_name a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property vocab:person_person_name;
d2rq:propertyDefinitionLabel "person person_name";
d2rq:column "person.person_name";
.
map:person_person_english_name a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property vocab:person_person_english_name;
d2rq:propertyDefinitionLabel "person person_english_name";
d2rq:column "person.person_english_name";
.
map:person_person_biography a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property vocab:person_person_biography;
d2rq:propertyDefinitionLabel "person person_biography";
d2rq:column "person.person_biography";
.
map:person_person_birth_place a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property vocab:person_person_birth_place;
d2rq:propertyDefinitionLabel "person person_birth_place";
d2rq:column "person.person_birth_place";
.
# Table person_to_movie (n:m)
map:person_to_movie__link a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:movie;
d2rq:property vocab:person_to_movie;
d2rq:refersToClassMap map:person;
d2rq:join "person_to_movie.movie_id => movie.movie_id";
d2rq:join "person_to_movie.person_id => person.person_id";
.
修改为:
@prefix map: <#> .
@prefix db: <> .
@prefix vocab: .
@prefix rdf: .
@prefix rdfs: .
@prefix xsd: .
@prefix d2rq: .
@prefix jdbc: .
@prefix : .
map:database a d2rq:Database;
d2rq:jdbcDriver "com.mysql.jdbc.Driver";
d2rq:jdbcDSN "jdbc:mysql:///kg_demo_movie?useUnicode=true&characterEncoding=utf8";
d2rq:username "root";
d2rq:password "123456";
jdbc:autoReconnect "true";
jdbc:zeroDateTimeBehavior "convertToNull";
.
# Table genre
map:genre a d2rq:ClassMap;
d2rq:dataStorage map:database;
d2rq:uriPattern "genre/@@genre.genre_id@@";
d2rq:class :Genre;
d2rq:classDefinitionLabel "genre";
.
map:genre_genre_name a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:genre;
d2rq:property :genreName;
d2rq:propertyDefinitionLabel "genre genre_name";
d2rq:column "genre.genre_name";
.
# Table movie
map:movie a d2rq:ClassMap;
d2rq:dataStorage map:database;
d2rq:uriPattern "movie/@@movie.movie_id@@";
d2rq:class :Movie;
d2rq:classDefinitionLabel "movie";
.
map:movie_movie_title a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:movie;
d2rq:property :movieTitle;
d2rq:propertyDefinitionLabel "movie movie_title";
d2rq:column "movie.movie_title";
.
map:movie_movie_introduction a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:movie;
d2rq:property :movieIntroduction;
d2rq:propertyDefinitionLabel "movie movie_introduction";
d2rq:column "movie.movie_introduction";
.
map:movie_movie_rating a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:movie;
d2rq:property :movieRating;
d2rq:propertyDefinitionLabel "movie movie_rating";
d2rq:column "movie.movie_rating";
d2rq:datatype xsd:double;
.
map:movie_movie_release_date a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:movie;
d2rq:property :movieReleaseDate;
d2rq:propertyDefinitionLabel "movie movie_release_date";
d2rq:column "movie.movie_release_date";
.
# Table movie_to_genre (n:m)
map:movie_to_genre__link a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:movie;
d2rq:property :hasGenre;
d2rq:refersToClassMap map:genre;
d2rq:join "movie_to_genre.movie_id => movie.movie_id";
d2rq:join "movie_to_genre.genre_id => genre.genre_id";
.
# Table person
map:person a d2rq:ClassMap;
d2rq:dataStorage map:database;
d2rq:uriPattern "person/@@person.person_id@@";
d2rq:class :Person;
d2rq:classDefinitionLabel "person";
.
map:person_person_birth_day a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property :personBirthDay;
d2rq:propertyDefinitionLabel "person person_birth_day";
d2rq:column "person.person_birth_day";
.
map:person_person_death_day a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property :personDeathDay;
d2rq:propertyDefinitionLabel "person person_death_day";
d2rq:column "person.person_death_day";
.
map:person_person_name a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property :personName;
d2rq:propertyDefinitionLabel "person person_name";
d2rq:column "person.person_name";
.
map:person_person_english_name a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property :personEnglishName;
d2rq:propertyDefinitionLabel "person person_english_name";
d2rq:column "person.person_english_name";
.
map:person_person_biography a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property :personBiography;
d2rq:propertyDefinitionLabel "person person_biography";
d2rq:column "person.person_biography";
.
map:person_person_birth_place a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property :personBirthPlace;
d2rq:propertyDefinitionLabel "person person_birth_place";
d2rq:column "person.person_birth_place";
.
# Table person_to_movie (n:m)
map:person_to_movie__link a d2rq:PropertyBridge;
d2rq:belongsToClassMap map:person;
d2rq:property :hasActedIn;
d2rq:refersToClassMap map:movie;
d2rq:join "person_to_movie.movie_id => movie.movie_id";
d2rq:join "person_to_movie.person_id => person.person_id";
.
主要改变有:
@prefix : .
这样的话可以用 “:”来指代这一串IRI,即
http://www.kgdemo.com#Person
可以表达为
:Person
d2rq:jdbcDSN "jdbc:mysql:///kg_demo_movie";
改为
d2rq:jdbcDSN "jdbc:mysql:///kg_demo_movie?useUnicode=true&characterEncoding=utf8";
与mysql里的数据统一字符编码。
输入如下命令把数据转化为RDF:
.\dump-rdf.bat -o kg_demo_movie.nt .\kg_demo_movie_mapping.ttl
原文:https://zhuanlan.zhihu.com/p/32880610
D2RQ的mapping文件使得我们可以以RDF形式访问关系数据库中的数据,即它把SPARQL翻译成了SQL语句进行查询。
进入D2RQ目录打开命令窗口,通过如下命令启动D2R Server:
d2r-server.bat kg_demo_movie_mapping.ttl
在浏览器输入“http://localhost:2020/”进入如下界面
红框1是我们定义的类别,点开任意类别可以看到其对应的所有实例(默认显示50个,可在mapping文件中修改服务器配置),点开某个实例可以看到其包含的所有属性。点击红框2进入endpoint。
输入框默认SPARQL查询是获取所有RDF三元组,LIMIT限制返回结果数量,点击“Go!”返回如下结果:
例1: “周星驰演出了哪些电影?”
SELECT ?n WHERE {
?s rdf:type :Person.
?s :personName ‘周星驰’.
?s :hasActedIn ?o.
?o :movieTitle ?n
}
LIMIT 10
返回如图:
SPARQL语法的具体使用请参考 https://www.w3.org/2009/Talks/0615-qbe/
例2: “英雄这部电影有哪些人参演?”
SELECT ?n WHERE {
?s rdf:type :Movie.
?s :movieTitle ‘英雄’.
?a :hasActedIn ?s.
?a :movieTitle ?n
}
LIMIT 10
返回结果如图:
例3 :“巩俐参演的评分大于7的电影有哪些?”
SELECT ?n WHERE{
?s rdf:type :Person.
?s :personName ‘巩俐’.
?s :hasActedIn ?o.
?o :movieTitle ?n.
?o :movieRating ?r.
FILTER(?r >= 7)
}
LIMIT 10
若返回‘no result’,寻找原因,先看不加filter能否正常返回结果,若不能,可能是之前的编码格式没有统一,重复上面操作再试一次,若返回了值如下图:
考虑把FILTER的内容改为:
FILTER(?r >= “7.0E0”^^xsd:double)
即在后面定义其类型为xsd:double型。
返回结果如图:
首先用下载SPARQLwrapper,这个包让我们可以方便地跟endpoint进行交互。下载完成后执行Python代码如下:
from SPARQLWrapper import SPARQLWrapper, JSON
sparql = SPARQLWrapper("http://localhost:2020/sparql")
sparql.setQuery("""
PREFIX :
PREFIX rdf:
SELECT ?n WHERE{
?s rdf:type :Person.
?s :personName '巩俐'.
?s :hasActedIn ?o.
?o :movieTitle ?n.
?o :movieRating ?r
FILTER(?r >= 7)
}
LIMIT 10
""" )
sparql.setReturnFormat(JSON)
results = sparql.query().convert()
for result in results["results"]['bindings']:
print(result['n']['value'])
返回巩俐参演的评分大于7分的电影。
http://localhost:2020/sparql 为D2RQ的默认链接,用于初始化Wrapper。
原文: https://zhuanlan.zhihu.com/p/33224431
上一节利用D2RQ来开启endpoint服务有两个缺点:
首先进入Jena官网点击“Download”分别下载apach-jena和apache-jana-fuseki,如图:
step1
下载下来后分别解压,创建一个文件夹用于存放tdb数据(这里命名为tdb),进入解压后的“apache-jena-X.X.X”文件夹内的“bat”目录,使用“tdbloader.bat”将之前我们的RDF数据以TDB的方式存储。,命令如下(原作者内容):
.\tdbloader.bat --loc="D:\apache jena\tdb" "D:\d2rq\kg_demo_movie.nt"
“–loc”后面是tdb的存储位置(改为自己的),即我们刚刚创建的文件夹,第二个是我们在第二节中最后得到的文件路径(改为自己的)。
step2
进入“apache-jena-fuseki-X.X.X”文件夹,运行“fuseki-server.bat”,然后退出。程序会为我们在当前目录自动创建“run”文件夹。将在第一节中保存的“ontology.owl”移动到“run”文件夹下的“databases”文件夹中,并将“owl”后缀名改为“ttl”。
在“run”文件夹下的“configuration”中,创建名为“fuseki_conf.ttl”的文本文件(取名没有要求),加入如下内容(原作者内容):
@prefix : .
@prefix tdb: .
@prefix rdf: .
@prefix ja: .
@prefix rdfs: .
@prefix fuseki: .
:service1 a fuseki:Service ;
fuseki:dataset <#dataset> ;
fuseki:name "kg_demo_movie" ;
fuseki:serviceQuery "query" , "sparql" ;
fuseki:serviceReadGraphStore "get" ;
fuseki:serviceReadWriteGraphStore "data" ;
fuseki:serviceUpdate "update" ;
fuseki:serviceUpload "upload" .
<#dataset> rdf:type ja:RDFDataset ;
ja:defaultGraph <#model_inf> ;
.
<#model_inf> a ja:InfModel ;
ja:baseModel <#tdbGraph> ;
#改为本体文件的路径
ja:content [ja:externalContent ] ;
#启用OWL推理机
ja:reasoner [ja:reasonerURL ] .
<#tdbGraph> rdf:type tdb:GraphTDB ;
tdb:dataset <#tdbDataset> ;
.
#改为tdb文件夹路径(存储数据用)
<#tdbDataset> rdf:type tdb:DatasetTDB ;
tdb:location "D:/apache jena/tdb" ;
.
以上,有两个需要修改路径的地方,路径中的空格用“%20”表示,再次运行“fuseki-server.bat”,出现类似如下界面(没有ERROR出现),则界面运行成功。
浏览器访问“http://localhost:3030/ ” ,点击“query”即可进行类似之前D2RQ的查询。查询电影《功夫》的所有属性:
PREFIX :
PREFIX rdf:
PREFIX rdfs:
SELECT * WHERE {
?x :movieTitle '功夫'.
?x ?p ?o.
}
(从这里开始我这边一直显示“no result”, 原因未知。)
返回结果中应有电影的“hasActor”属性,这在原本的RDF中是没有的,是由OWL推理机得到。
在“databases”文件夹下新建一个文本文件“rules.ttl”,填入如下内容:
@prefix : .
@prefix owl: .
@prefix rdf: .
@prefix xsd: .
@prefix rdfs: .
[ruleComedian: (?p :hasActedIn ?m) (?m :hasGenre ?g) (?g :genreName '喜剧') -> (?p rdf:type :Comedian)]
[ruleInverse: (?p :hasActedIn ?m) -> (?m :hasActor ?p)]
定义了一个名为“ruleComedian”的规则,它的意思是:如果有一个演员,出演了一部喜剧电影,那么他就是一位喜剧演员。修改配置文件“fuseki_conf.ttl”(原作者内容):
@prefix : .
@prefix tdb: .
@prefix rdf: .
@prefix ja: .
@prefix rdfs: .
@prefix fuseki: .
:service1 a fuseki:Service ;
fuseki:dataset <#dataset> ;
fuseki:name "kg_demo_movie" ;
fuseki:serviceQuery "query" , "sparql" ;
fuseki:serviceReadGraphStore "get" ;
fuseki:serviceReadWriteGraphStore "data" ;
fuseki:serviceUpdate "update" ;
fuseki:serviceUpload "upload" .
<#dataset> rdf:type ja:RDFDataset ;
ja:defaultGraph <#model_inf> ;
.
<#model_inf> a ja:InfModel ;
ja:baseModel <#tdbGraph> ;
#本体文件的路径
ja:content [ja:externalContent ] ;
#关闭OWL推理机
#ja:reasoner [ja:reasonerURL ] .
#开启规则推理机,并指定规则文件路径
ja:reasoner [
ja:reasonerURL ;
ja:rulesFrom ; ]
.
<#tdbGraph> rdf:type tdb:GraphTDB ;
tdb:dataset <#tdbDataset> ;
.
# 修改为tdb路径
<#tdbDataset> rdf:type tdb:DatasetTDB ;
tdb:location "D:/apache jena/tdb" ;
.
以上有三个需要修改路径的地方。
我们只能启用一种推理机。前面也提到,OWL的推理功能也可以在规则推理机里面实现,因此我们定义了“ruleInverse”来表示“hasActedIn”和“hasActor”的相反关系。
执行如下SPARQL查询,喜剧演员有哪些:
PREFIX :
PREFIX rdf:
PREFIX rdfs:
SELECT * WHERE {
?x rdf:type :Comedian.
?x :personName ?n.
}
limit 10
查询结果:
x n
file:///D:/d2rq/d2rq-0.8.1/kg_demo_movie.nt#person/111298 郑丹瑞
file:///D:/d2rq/d2rq-0.8.1/kg_demo_movie.nt#person/70591 陈欣健
file:///D:/d2rq/d2rq-0.8.1/kg_demo_movie.nt#person/116351 沈殿霞
file:///D:/d2rq/d2rq-0.8.1/kg_demo_movie.nt#person/116052 鲍汉琳
file:///D:/d2rq/d2rq-0.8.1/kg_demo_movie.nt#person/1002925 张同祖
file:///D:/d2rq/d2rq-0.8.1/kg_demo_movie.nt#person/62423 林正英
file:///D:/d2rq/d2rq-0.8.1/kg_demo_movie.nt#person/1614091 林琪欣
file:///D:/d2rq/d2rq-0.8.1/kg_demo_movie.nt#person/224929 陈法蓉
file:///D:/d2rq/d2rq-0.8.1/kg_demo_movie.nt#person/1135398 叶世荣
file:///D:/d2rq/d2rq-0.8.1/kg_demo_movie.nt#person/119426 元秋
以上。最后一节目前未能找到未成功原因,如有建议,万分感谢。