【知识图谱(2)】电影知识图谱构建

本文的主线思路

一共六个板块:

半结构化数据源
通过不同方式处理和查询数据
通过不同方式处理和查询数据
语义搜索
知识问答
非结构化数据源
半结构化数据的获取
结构化数据到RDF以及基于Apache jena交互
实现不同任务目标
将关系型数据存入图数据库Neo4j
基于ElasticSearch的简单语义搜索
基于REfO的简单知识问答
基于Deepdive非结构化文本关系抽取
  • 1.半结构化数据的获取
    起点:这是数据处理的第一步。半结构化数据(如JSON、XML、CSV等)是原始信息来源,需要提取和转换为后续处理可以用的形式。

    1. 结构化数据到RDF以及基于Apache Jena交互
      关系:从半结构化数据提取后,你可以转化为结构化数据(如表格或关系型数据库中的数据),再将其转化为RDF(Resource Description Framework),方便在语义网或知识图谱环境中应用。
      用途:使用Apache Jena等工具,可以基于RDF进行知识存储、查询(SPARQL)和语义分析。

    1. 基于REfO的简单知识问答
      扩展:在RDF数据的基础上,你可以利用 REfO(Resource Entity and Frame Ontology)等方法对数据进行增强语义问答,支持自然语言查询。这是基于语义网的知识应用的延续。

    1. 基于ElasticSearch的简单语义搜索
      和3是并列关系:ElasticSearch语义搜索和知识问答可以独立存在,二者都基于对数据的查询,但 实现方式不同
  • 语义搜索:以关键词或语义匹配为主,适合非结构化数据和简单检索需求。
  • 知识问答:基于知识图谱和语义理解,适合复杂、逻辑性强的查询。

    1. 基于DeepDive非结构化文本关系抽取
      作为数据源补充步骤:DeepDive 的作用是从 非结构化文本(如文章、报告)中提取关系,生成可用于知识图谱或其他结构化存储的数据。这可以与RDF数据构建或Neo4j的知识图谱相结合,进一步丰富数据来源。

    1. 将关系型数据存入图数据库Neo4j
      关系:在关系型数据库中存储的结构化数据可以被转化为图模型,然后存入Neo4j。这样可以用图数据库来更高效地执行关联性分析、路径计算等任务。

整体关系

  • 串联:1 → 2 → 3/6 (通过不同方式处理和查询数据)
  • 并列:3 和 4 (知识问答和语义搜索可以分别实现不同目标)
  • 补充:5 (非结构化数据可以作为整个流程的数据源之一)

本博客只说明 1 → 2 → 3 这个链路。
在这里插入图片描述

文章目录

  • 本文的主线思路
  • 一、半结构化数据的获取
    • (一)基于scrapy构建的百度百科爬虫和互动百科爬虫
  • 二、MySql结构化数据到RDF以及基于Apache jena交互
    • (一)protégé本体的构建
    • (二)直接映射法
    • (三)R2RML 方法进行将关系型数据库的数据转换为RDF格式的数据
    • (四)Apache jena构建语义网和链接数据应用
  • 三. 基于(基于RDF的建模框架)REfO的简单知识问答
    • (一)具体实现
      • 1). 模块一 word_tagging部分
      • 2). 模块二 rules 部分
    • 3. 项目运行
  • 参考

一、半结构化数据的获取

(一)基于scrapy构建的百度百科爬虫和互动百科爬虫

也尝试进行:基于BeautifulSoup和urllib2的百度百科爬虫、微信公众号爬虫和虎嗅网爬虫。

  • 百度百科爬虫,爬取电影类数据,包含电影22219部,演员13967人。
  • 互动百科爬虫, 爬取电影类数据,包含电影13866部,演员5931 人。

参考:https://www.ljjyy.com/archives/2019/10/100591.html

基本使用
a. 创建工程结构

$scrapy startproject 工程名
$scrapy startproject baidu_baike

b. 创建一个spider(爬虫文件)
在spiders文件夹下创建

$scrapy genspider 文件名 要爬取的网址。
$scrapy genspider -t basic baidubaike baike.baidu.com
Created spider 'baidubaike' using template 'basic' in module:baidu_baike.spiders.baidubaike

生成baidubaike.py的代码

# -*- coding: utf-8 -*-
import scrapyclass BaidubaikeSpider(scrapy.Spider):name = 'baidu'allowed_domains = ['baike.baidu.com']start_urls = ['http://baike.baidu.com/']def parse(self, response):pass

c. 增加代码
(1)检查文件目录结构为:

baidu_baike scrapy.cfg
./baidu_baike(包含下列文件)init.py items.py middlewares.py pipelines.py settings.py spiders
./baidu_baike/spiders(包含下列文件)baidu_baike.py  init.py 

(2)修改item.py
baidu_baike/目录下的文件是需要我们手动修改的。其中 items.py 对需要爬取的内容进行管理,便于把抓取的内容传递进pipelines进行后期处理。现在我们对 item.py进行修改,添加要爬取的项。

import scrapyclass BaiduBaikeItem(scrapy.Item):# define the fields for your item here like:     # name = scrapy.Field()     # 包含演员相关属性     actor_id = scrapy.Field()actor_bio = scrapy.Field()actor_chName = scrapy.Field()actor_foreName = scrapy.Field()actor_nationality = scrapy.Field()actor_constellation = scrapy.Field()actor_birthPlace = scrapy.Field()actor_birthDay = scrapy.Field()actor_repWorks = scrapy.Field()actor_achiem = scrapy.Field()actor_brokerage = scrapy.Field()# 电影相关属性 movie_id = scrapy.Field()movie_bio = scrapy.Field()movie_chName = scrapy.Field()movie_foreName = scrapy.Field()movie_prodTime = scrapy.Field()movie_prodCompany = scrapy.Field()movie_director = scrapy.Field()movie_screenwriter = scrapy.Field()movie_genre = scrapy.Field()movie_star = scrapy.Field()movie_length = scrapy.Field()movie_rekeaseTime = scrapy.Field()movie_language = scrapy.Field()movie_achiem = scrapy.Field()

在爬虫运行过程中,我们主要爬取电影和演员两类及其对应的各项属性。
(3)建立Mysql库和修改pipelines.py
pipelines.py 用来将爬取的内容存放到MySQL数据库中。类内有初始化init()、处理爬取内容并保存process_item()、关闭数据库close_spider()三个方法。因此首先检录MySql库,然后需改pipeline.py 文件

Mysql 库内包含 演员、电影、电影类型、演员->电影、电影->类型 五张表:

    演员 :爬取内容为 ID, 简介, 中文名,外文名,国籍,星座,出生地,出生日期,代表作品,主要成就,经纪公司;电影 :ID,简介,中文名,外文名,出品时间,出品公司,导演,编剧,类型,主演,片长,上映时间,对白语言,主要成就;电影类型: 爱情,喜剧,动作,剧情,科幻,恐怖,动画,惊悚,犯罪,冒险,其他;演员->电影: 演员ID, 电影ID;电影-> 类型: 电影ID, 类型ID;
# -*- coding: utf-8 -*-# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
from __future__ import absolute_import
from __future__ import division
from __future__ import print_functionimport pymysql
from pymysql import connections
from baidu_baike import settingsclass BaiduBaikePipeline(object):def __init__(self):# 初始化并连接到mysql数据库self.conn = pymysql.connect(host=settings.HOST_IP,port=settings.PORT,user=settings.USER,passwd=settings.PASSWD,db=settings.DB_NAME,charset='utf8mb4',use_unicode=True)self.cursor = self.conn.cursor()def process_item(self, item, spider):# process info for actoractor_chName = str(item['actor_chName']).encode('utf-8')actor_foreName = str(item['actor_foreName']).encode('utf-8')movie_chName = str(item['movie_chName']).encode('utf-8')movie_foreName = str(item['movie_foreName']).encode('utf-8')if (item['actor_chName'] != None or item['actor_foreName'] != None) and item['movie_chName'] == None:actor_bio = str(item['actor_bio']).encode('utf-8')actor_nationality = str(item['actor_nationality']).encode('utf-8')actor_constellation = str(item['actor_constellation']).encode('utf-8')actor_birthPlace = str(item['actor_birthPlace']).encode('utf-8')actor_birthDay = str(item['actor_birthDay']).encode('utf-8')actor_repWorks = str(item['actor_repWorks']).encode('utf-8')actor_achiem = str(item['actor_achiem']).encode('utf-8')actor_brokerage = str(item['actor_brokerage']).encode('utf-8')self.cursor.execute("SELECT actor_chName FROM actor;")actorList = self.cursor.fetchall()if (actor_chName,) not in actorList :# get the nums of actor_id in table actorself.cursor.execute("SELECT MAX(actor_id) FROM actor")result = self.cursor.fetchall()[0]if None in result:actor_id = 1else:actor_id = result[0] + 1sql = """INSERT INTO actor(actor_id, actor_bio, actor_chName, actor_foreName, actor_nationality, actor_constellation, actor_birthPlace, actor_birthDay, actor_repWorks, actor_achiem, actor_brokerage ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""self.cursor.execute(sql, (actor_id, actor_bio, actor_chName, actor_foreName, actor_nationality, actor_constellation, actor_birthPlace, actor_birthDay, actor_repWorks, actor_achiem, actor_brokerage ))self.conn.commit()else:print("#" * 20, "Got a duplict actor!!", actor_chName)elif (item['movie_chName'] != None or item['movie_foreName'] != None) and item['actor_chName'] == None:movie_bio = str(item['movie_bio']).encode('utf-8')movie_prodTime = str(item['movie_prodTime']).encode('utf-8')movie_prodCompany = str(item['movie_prodCompany']).encode('utf-8')movie_director = str(item['movie_director']).encode('utf-8')movie_screenwriter = str(item['movie_screenwriter']).encode('utf-8')movie_genre = str(item['movie_genre']).encode('utf-8')movie_star = str(item['movie_star']).encode('utf-8')movie_length = str(item['movie_length']).encode('utf-8')movie_rekeaseTime = str(item['movie_rekeaseTime']).encode('utf-8')movie_language = str(item['movie_language']).encode('utf-8')movie_achiem = str(item['movie_achiem']).encode('utf-8')self.cursor.execute("SELECT movie_chName FROM movie;")movieList = self.cursor.fetchall()if (movie_chName,) not in movieList :self.cursor.execute("SELECT MAX(movie_id) FROM movie")result = self.cursor.fetchall()[0]if None in result:movie_id = 1else:movie_id = result[0] + 1sql = """INSERT INTO movie(  movie_id, movie_bio, movie_chName, movie_foreName, movie_prodTime, movie_prodCompany, movie_director, movie_screenwriter, movie_genre, movie_star, movie_length, movie_rekeaseTime, movie_language, movie_achiem ) VALUES ( %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""self.cursor.execute(sql, ( movie_id, movie_bio, movie_chName, movie_foreName, movie_prodTime, movie_prodCompany, movie_director, movie_screenwriter, movie_genre, movie_star, movie_length, movie_rekeaseTime, movie_language, movie_achiem ))self.conn.commit()else:print("Got a duplict movie!!", movie_chName)else:print("Skip this page because wrong category!! ")return itemdef close_spider(self, spider):self.conn.close()

(4)编写 baidu_baike.py

#!/usr/bin/env python
# coding=utf-8from __future__ import absolute_import
from __future__ import division
from __future__ import print_functionimport scrapy
from baidu_baike.items import BaiduBaikeItem
from bs4 import BeautifulSoup
import re
import urllibclass BaiduBaikeSpider(scrapy.Spider, object):# 定义爬虫名称name = 'baidu'# 设置允许的域,不以这个开头的链接不会爬取allowed_domains = ["baike.baidu.com"]# 爬虫开始的的网址start_urls = ['https://baike.baidu.com/item/%E5%88%98%E5%BE%B7%E5%8D%8E/114923']#start_urls = ['https://baike.baidu.com/item/%E4%B8%83%E5%B0%8F%E7%A6%8F']# 将返回的标签列表提取文本并返回def _get_from_findall(self, tag_list):result = []for slist in tag_list:tmp = slist.get_text()result.append(tmp)return result# 程序的核心,可以获取页面内的指定信息,并获取页面内的所有链接做进一步的爬取# response 是初始网址的返回def parse(self, response):# 分析 response来提取出页面最下部的标签信息,如果包含演员或电影则进行爬取,否则跳过page_category = response.xpath("//dd[@id='open-tag-item']/span[@class='taglist']/text()").extract()page_category = [l.strip() for l in page_category]item = BaiduBaikeItem()# tooooo ugly,,,, but can not use defaultdictfor sub_item in ['actor_bio', 'actor_chName', 'actor_foreName', 'actor_nationality', 'actor_constellation','actor_birthPlace', 'actor_birthDay', 'actor_repWorks', 'actor_achiem', 'actor_brokerage','movie_bio', 'movie_chName', 'movie_foreName', 'movie_prodTime', 'movie_prodCompany','movie_director', 'movie_screenwriter', 'movie_genre', 'movie_star', 'movie_length','movie_rekeaseTime', 'movie_language', 'movie_achiem']:item[sub_item] = None# 如果包含演员标签则认为是演员if u'演员' in page_category:print("Get a actor page")soup = BeautifulSoup(response.text, 'lxml')summary_node = soup.find("div", class_="lemma-summary")item['actor_bio'] = summary_node.get_text().replace("\n", " ")# 使用 bs4 对页面内信息进行提取并保存到对应的item内all_basicInfo_Item = soup.find_all("dt", class_="basicInfo-item name")basic_item = self._get_from_findall(all_basicInfo_Item)basic_item = [s.strip() for s in basic_item]all_basicInfo_value = soup.find_all("dd", class_="basicInfo-item value")basic_value = self._get_from_findall(all_basicInfo_value)basic_value = [s.strip() for s in basic_value]for i, info in enumerate(basic_item):info = info.replace(u"\xa0", "")if info == u'中文名':item['actor_chName'] = basic_value[i]elif info == u'外文名':item['actor_foreName'] = basic_value[i]elif info == u'国籍':item['actor_nationality'] = basic_value[i]elif info == u'星座':item['actor_constellation'] = basic_value[i]elif info == u'出生地':item['actor_birthPlace'] = basic_value[i]elif info == u'出生日期':item['actor_birthDay'] = basic_value[i]elif info == u'代表作品':item['actor_repWorks'] = basic_value[i]elif info == u'主要成就':item['actor_achiem'] = basic_value[i]elif info == u'经纪公司':item['actor_brokerage'] = basic_value[i]yield itemelif u'电影' in page_category:print("Get a movie page!!")# 使用 bs4 对页面内的链接进行提取,而后进行循环爬取soup = BeautifulSoup(response.text, 'lxml')summary_node = soup.find("div", class_="lemma-summary")item['movie_bio'] = summary_node.get_text().replace("\n", " ")all_basicInfo_Item = soup.find_all("dt", class_="basicInfo-item name")basic_item = self._get_from_findall(all_basicInfo_Item)basic_item = [s.strip() for s in basic_item]all_basicInfo_value = soup.find_all("dd", class_="basicInfo-item value")basic_value = self._get_from_findall(all_basicInfo_value)basic_value = [s.strip() for s in basic_value]for i, info in enumerate(basic_item):info = info.replace(u"\xa0", "")if info == u'中文名':item['movie_chName'] = basic_value[i]elif info == u'外文名':item['movie_foreName'] = basic_value[i]elif info == u'出品时间':item['movie_prodTime'] = basic_value[i]elif info == u'出品公司':item['movie_prodCompany'] = basic_value[i]elif info == u'导演':item['movie_director'] = basic_value[i]elif info == u'编剧':item['movie_screenwriter'] = basic_value[i]elif info == u'类型':item['movie_genre'] = basic_value[i]elif info == u'主演':item['movie_star'] = basic_value[i]elif info == u'片长':item['movie_length'] = basic_value[i]elif info == u'上映时间':item['movie_rekeaseTime'] = basic_value[i]elif info == u'对白语言':item['movie_language'] = basic_value[i]elif info == u'主要成就':item['movie_achiem'] = basic_value[i]yield itemsoup = BeautifulSoup(response.text, 'lxml')links = soup.find_all('a', href=re.compile(r"/item/"))for link in links:new_url = link["href"]new_full_url = urllib.parse.urljoin('https://baike.baidu.com/', new_url)yield scrapy.Request(new_full_url, callback=self.parse)

d. 运行

scrapy crawl 项目名
scrapy crawl baidu

本文章对半结构化数据,即百度百科和互动百科做了爬取并保存到数据库中。这样就相当于我们获得了一份结构化的数据。下篇文章将使用直接映射和D2RQ将其转化为三元组的形式。
对应数据集可在百度云下载,提取码:60n6。

Mysql数据查看
在这里插入图片描述

大概就是这种数据库关系图,这个图源头
在这里插入图片描述

二、MySql结构化数据到RDF以及基于Apache jena交互

结构化数据到RDF由两种主要方式,一个是通过direct mapping,另一个通过R2RML语言这种,基于R2RML语言的方式更为灵活(此处我们使用d2rq工具,它基于R2RML-KIT)。

(一)protégé本体的构建

本体的构建大体有两种方式:自顶向下和自底向上。

  • 开放域知识图谱的本体构建
    通常用自底向上的方法,自动地从知识图谱中抽取概念、概念层次和概念之间的关系。这也很好理解,开放的世界太过复杂,用自顶向下的方法无法考虑周全,且随着世界变化,对应的概念还在增长。

  • 领域知识图谱多采用自顶向下的方法来构建本体。一方面,相对于开放域知识图谱,领域知识图谱涉及的概念和范围都是固定或者可控的;另一方面,对于领域知识图谱,我们要求其满足较高的精度。现在大家接触到的一些语音助手背后对接的知识图谱大多都是领域知识图谱,比如音乐知识图谱、体育知识图谱、烹饪知识图谱等等。正因为是这些领域知识图谱来满足用户的大多数需求,更需要保证其精度。

本实例是一个电影领域的知识图谱,我们采用自顶向下的方法来构建本体结构。我们使用的工具 protégé(点击进入官网下载),又常常简单地拼写为“Protege”,是一个斯坦福大学开发的本体编辑和知识获取软件。开发语言采用Java,属于开放源码软件。软件不需要安装,需要配置相应的Java环境。

  1. 在Ontology IRI中填写我们新建本体资源的IRI。点击“Entities”tab标签,选择“Classes”标签。在这个界面,我们创建电影知识图谱的类/概念。注意,所有的类都是“Thing”的子类。最左边红色小方框中的按钮用于创建当前选中类的子类,中间的按钮用于创建兄弟类(平行类),最右边的按钮删除当前选中的类。我们创建了三个类,“人物”、“电影”、“类别”。右下方的界面是用于描述该类的一些特性,例如:“disjoint of"是用于表示该类与哪些类是互斥的。本例中,三个类都是互斥的。也就是说,一个实例只能是三个类中的一个。我们没有在protege中显式地定义互斥关系,读者可以自己定义。
    在这里插入图片描述接下来我们切换到"Object Properties"页面,我们在此界面创建类之间的关系,即,对象属性。这里我们创建了三个对象属性,“hasActedIn"表示某人参演了某电影,因此我们在右下方的3号矩形框中定义该属性的"domain"是人,4号框定义"range"是电影。这个很好理解,“domain"表示该属性是属于哪个类的,“range"表示该属性的取值范围。2号框表示该属性的逆属性是"hasActor”,即,有了推理机,尽管我们的RDF数据只保存了A出演了B,我们在查询的时候也能得到B的演员有A。1号方框中是一些描述该属性的词汇,我们在上一篇文章中已经介绍过,这里不再赘述。同理,我们定义另外两个属性,这里不再展示。
    在这里插入图片描述
    最后,我们切换到"Data properties”,我们在该界面创建类的属性,即,数据属性。其定义方法和对象属性类似,除了没有这么丰富的描述属性特性的词汇。其实不难理解,这些描述特性的词汇是传递、对称、反对称、自反等,表明其必定有指向其他资源或自身的边,而我们之前提到过,数据属性相当于树的叶子节点,只有入度,而没有出度。
    在这里插入图片描述其实区分数据属性和对象属性还有一个很直观的方法,我们观察其"range”,取值范围即可。对象属性的取值范围是类,而数据属性的取值范围则是字面量,如下图。
    在这里插入图片描述
    protege也支持以可视化的方式来展示本体结构。我们点击"Window"选项,在"Tabs"中选择"OntoGraf”,然后"Entities"旁边就多了一个标签页。在右侧窗口中移动元素,可以很直观地观察本体之间的关系。
    在这里插入图片描述我们简单地介绍了如何用protege自顶向下地构建知识图谱的本体结构。

根据上述流程生成的针对本项目的本体文件为struct_to_rdf/movie_actor/kg_movie_tultle.owl。

(二)直接映射法

W3C的RDB2RDF工作小组制定的两个标准之一

标准是direct mapping,即直接映射。何为直接映射?规则十分简单:

\1. 数据库的表作为本体中的类(Class)。比如我们在mysql中保存的数据,一共有5张表。那么通过映射后,我们的本体就有5个类了,而不是我们自己定义的三个类。

\2. 表的列作为属性(Property)。

\3. 表的行作为实例/资源。

\4. 表的单元格值为字面量

\5. 如果单元格所在的列是外键,那么其值为IRI,或者说实体/资源。

在实际应用中我们很少用到这种方法,尽管它是最便捷的方式。详细的解释和示例,请参考W3C的官方文档(A Direct Mapping of Relational Data to RDF)。

Direct mapping的缺点很明显,不能把数据库的数据映射到我们自己定义的本体上。

(三)R2RML 方法进行将关系型数据库的数据转换为RDF格式的数据

W3C的RDB2RDF工作小组制定的两个标准之二

RDB2RDF工作小组指定了另外一个标准——R2RML,可以让用户更灵活的编辑和设置映射规则。为了让读者有个直观地认识,我们以mysql中的数据为例,介绍怎么把person这个表映射到我们在protege中定义的Person类上,person_name映射到personName上。

@prefix rr: <R2RML: RDB to RDF Mapping Language Schema>.
@prefix : <http://www.kgdemo.com#>.<#TriplesMap1>rr:logicalTable [ rr:tableName "person" ];rr:subjectMap [rr:template "http://www.kgdemo.com/person/{person_id}";rr:class :Person;];rr:predicateObjectMap [rr:predicate :personName;rr:objectMap [ rr:column "person_name" ];].

rr:template指定实体/资源的IRI生成模板,括号中的字符串是对应表中的某个列名。在本例中指每个人物的IRI由我们预定义的前缀加人物ID组成。rr:Class声明这些实体/资源的类是我们在Ontology中定义的Person。rr:predicate指定谓语,即属性。rr:objectMap指定该属性的值是来源于哪一列。其他属性的定义类似,读者可以自己查文档尝试。关于外键的定义,读者也可以参考文档相关示例。

R2RML也支持SQL语句来对查询结果进行映射。比如,我们有一列表示某人的性别,我们可以用SQL语句选取男性的行,把这些行映射成我们定义的男性类。女性同理。这种特性大大增强了其灵活性。

下面我们介绍如何用d2rq这个工具把mysql的数据转为RDF。这个参考实践篇(二):关系数据库到RDF

D2RQ 由自己的mapping语言,R2RML-kit。它和W3C推荐的R2RML类似。你可以通过D2RQ提供的工具来根据数据库自动生成mapping文件。你可以根据自己的需求去修改定制自己的mapping文件。

(四)Apache jena构建语义网和链接数据应用

Apache Jena是一个开源的Java语义网框架(open source Semantic Web Framework for Java),用于构建语义网和链接数据应用。

TDB:用于存储RDF的组件。
Jena:提供了RDFS、OWL和通用规则推理机。
Fuseki:Jena提供的SPARQL服务器,也就是SPARQL endpoint。

  • NTriples to TDB
    TDB 是Jena用于存储RDF的组件,是属于存储层面的技术。在单机情况下,它能够提供非常高的RDF存储性能。
    (1)将RDF数据转换以TDB的方式存储,运行 ./tdbloader –loc=”…/tdb/” “path/to/NTriples” 把 NTriples 加载到TDB中。

  • Apache jena fuseki 的运行
    在Apache Jena fuseki服务器的配置文件中,可以指定本体,从而实现推理机制。该本体文件是从顶向下定义的,采用protege软件编辑生成,格式为Turtle,放在struct_to_rdf/kg_movie_tultle.owl。 该配置文件中另一个需要修改的是TDB文件夹的路径,改成上面TDB 文件夹路径即可。运行命令:fuseki-server –loc=…\apache-jena\tdb /kg_movie

  • 启动后,Fuseki网站地址: http://localhost:3030/ 测试查询,可以通过网页端和命令行方式进行SPQRQL查询。Jena fuseki开启服务后,可以通过网页端和命令行方式进行SPQRQL查询。接下来我们可以直接进行SPARQL查询,也可以把自然语言转换为 SPARQL 模板后进行查询。

三. 基于(基于RDF的建模框架)REfO的简单知识问答

REfO模型的数据可以通过TDB进行存储和管理。
Jena的TDB组件支持REfO模型的高效存储和查询操作。
总结来说,REfO为基于RDF的建模框架,TDB则是Jena中的存储解决方案,REfO可以使用TDB作为存储层来管理其RDF数据。

基于REfO的KBQA(知识图谱问题回答)实现及示例,将其应用到自己的知识图谱上。在运行KBQA代码前,应按照前面的教程将电影类知识图谱导入到Jena的TDB数据库中,并运行fuseki服务器,这样我们才能进行访问查询。

Fuseki是Jena的一个组件,用于将RDF数据存储为三元组形式,并通过SPARQL查询语言进行查询和检索。

REf (Resource Description Framework Ontology)是知识图谱中用于描述资源及其关系的本体语言。REfO基于RDF(Resource Description Framework),主要用于定义概念、属性和关系,从而构建知识图谱的框架和结构。

REfO在知识图谱中的作用,REfO在知识图谱中扮演着至关重要的角色,它定义了知识图谱中的基本概念和关系,确保数据的一致性和可理解性。通过REfO,可以明确地描述实体之间的联系,使得知识图谱的构建和管理更加规范和高效。
REfO与其他相关技术的关系,REfO与RDF紧密相关,RDF是用于表示Web资源的标准格式,而REfO则是在RDF基础上定义的具体概念和关系。RDF通过SPO(Subject-Predicate-Object)三元组来表示实体及其关系,而REfO则进一步规范了这些三元组的意义和结构‌

(一)具体实现

代码结构为

data/  utils/  get_dict.sh query.py
./data:
actorName.txt get_dict.txt movieName.txt
./utils:
init.py rules.py  word_tagging.py 

其中,

  • data 目录存放由数据库倒出生成的字典文件,用于扩展jieba分词,由 get_dict.sh 生成。
  • utils/ 内存放查询预处理的模块。
  • word_tagging.py 用于将词的文本和词性打包,视为词对象,对应:class:Word(token, pos)。
  • rules.py 内定义各种规则并将自然语言转换为SPARQL查询语言,最终以JSON返回结果。
  • query.py 为程序入口,运行它来进行简单的KBQA。

基于REfO的简单知识问答的原理很简单,就是通过REfo提供的匹配能力,在输入的自然语言问题中进行匹配查找。如果找到我们预先设定的词或词性组合,那么就认为该问题与这个词或词性组合匹配。而一个词或词性的组合又对应着一个SPARQL查询模板,这样我们就借助REfO完成了自然语言到查询模板的转换。得到查询模板后,我们就利用Jena fuseki 服务器提供的端口进行查询得到返回的结果。

1). 模块一 word_tagging部分

该部分利用jieba分词对中文句子进行分词和词性标注。将词的文本和词性进行打包,视为词对象,对应 :class:Word(token, pos)。


class Tagger:def __init__(self, dict_paths):# TODO 加载外部词典for p in dict_paths:jieba.load_userdict(p)def get_word_objects(self, sentence):"""Get :class:WOrd(token, pos)"""return [Word(bytes.decode(word.encode('utf-8')), tag) for word, tag in pseg.cut(sentence)]

2). 模块二 rules 部分

该部分为程序核心,负责将自然语言转换为SPARQL模板。

下面为rules的程序入口,customize_rules 函数:


def customize_rules():# some rules for matching# TODO: customize your own rules hereperson = (W(pos="nr") | W(pos="x") | W(pos="nrt") | W(pos="nz"))movie = (W(pos="nz"))place = (W("出生地") | W("出生"))intro = (W("简介") | W(pos="介绍"))rules = [                   Rule(condition=W(pos="r") + W("是") + person | \person + W("是") + W(pos="r"),action=who_is_question),Rule(condition=person + Star(Any(), greedy=False) + place + Star(Any(), greedy=False),action=where_is_from_question),Rule(condition=movie + Star(Any(), greedy=False) + intro + Star(Any(), greedy=False) ,action=movie_intro_question)]return rules

该函数中我们设置了一些简单的匹配规则,例如我们设置 ‘’’movie = (W(pos=”nz”))’’‘,即movie 的词性应该是nz。其中的W()是我们在继承REfO的Predicate方法的基础上扩展更新了match方法。您可以简单的把它理解为re中compile后的match,只不过多个W()间出现的顺序可以变化。这样通过多个定制的W()和Star(Any(), greedy=False)(相当于.*?)这种通配符的组合,我们就定义了一组匹配规则,当遇到符合该规则的句子时,就选取该规则后action对应的查询模板。

例如当输入为“周星驰是谁”这样的问题时,会匹配到rules 中的 第一条规则。而后执行该规则后对应的action, who_is_question。而who_is_question对应的查询模板为:


def who_is_question(x):select = u"?x0"sparql = Nonefor w in x:if w.pos == "nr" or w.pos == "x":e = u" ?a :actor_chName '{person}'. \n \?a :actor_bio ?x0".format(person=w.token)sparql = SPARQL_TEM.format(preamble=SPARQL_PREAMBLE,select=select,expression=INDENT + e)break   return sparql

有了查询模板后,我们通过SPARQLWrapper 模块的SPARQLWrapper 执行该查询,并对返回的结果进行转换得到回答。对应的代码如下:


from SPARQLWrapper import SPARQLWrapper, JSON
from utils.word_tagging import Tagger
from utils.rules import customize_rulesif __name__ == "__main__":print("init...........")sparql_base = SPARQLWrapper("http://localhost:3030/kg_movie/query")tagger = Tagger(['data/actorName.txt', 'data/movieName.txt'])rules = customize_rules()print("done \n")while True:print("Please input your question: ")default_question = input()seg_list = tagger.get_word_objects(default_question)for rule in rules:query = rule.apply(seg_list)if query:sparql_base.setQuery(query)sparql_base.setReturnFormat(JSON)results = sparql_base.query().convert()if not results["results"]["bindings"]:print("No answer found :(")continuefor result in results["results"]["bindings"]:print("Result: ", result["x0"]["value"])

3. 项目运行

运行,提问示例结果:

Please input your question: 
刘德华是哪个Result:   刘德华(Andy Lau)....。

参考

《从零开始学习知识图谱》项目源代码 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/7042.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单值二叉树(C语言详解版)

一、摘要 今天要讲的是leetcode单值二叉树&#xff0c;这里用到的C语言&#xff0c;主要提供的是思路&#xff0c;大家看了我的思路之后可以点击链接自己试一下。 二、题目简介 如果二叉树每个节点都具有相同的值&#xff0c;那么该二叉树就是单值二叉树。 只有给定的树是单…

在Spring Boot中使用SeeEmitter类实现EventStream流式编程将实时事件推送至客户端

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

基于本地事务表+MQ实现分布式事务

基于本地事务表MQ实现分布式事务 引言1、原理2、本地消息表优缺点3、本地启动rocketmq4、代码实现及验证4.1、核心代码4.2、代码执行流程4.3、项目结构4.4、项目源码 引言 本地消息表的方案最初由ebay的工程师提出&#xff0c;核心思想是将分布式事务拆分成本地事务进行处理。…

Chrome插件:图片缩放为头像(128*128)

前置条件&#xff1a; 安装有chrome谷歌浏览器的电脑 使用步骤&#xff1a; 1.打开chrome扩展插件 2.点击管理扩展程序 3.加载已解压的扩展程序 4.选择对应文件夹 5.成功后会出现一个扩展小程序 6.点击对应小程序 7.使用小程序 8.拖拽成功后会自动保存到下载 代码&#xf…

idea maven本地有jar包,但还要从远程下载

idea 中&#xff0c;java 工程执行 maven reimport&#xff0c;报jar报无法下载。 我奇了个怪&#xff0c;我明明在本地仓库有啊&#xff0c;你非得从远程下载&#xff1f; 我从供应商那里拿来的&#xff0c;远程当然没有了。 这太奇葩了吧&#xff0c;折腾好久不行。 后来…

HTML<label>标签

例子 三个带标签的单选按钮&#xff1a; <form action"/action_page.php"> <input type"radio" id"html" name"fav_language" value"HTML"> <label for"html">HTML</label><br&…

【数据结构】_不带头非循环单向链表

目录 1. 链表的概念及结构 2. 链表的分类 3. 单链表的实现 3.1 SList.h头文件 3.2 SList.c源文件 3.3 Test_SList.c测试文件 关于线性表&#xff0c;已介绍顺序表&#xff0c;详见下文&#xff1a; 【数据结构】_顺序表-CSDN博客 本文介绍链表&#xff1b; 基于顺序表…

算法刷题笔记——图论篇

这里写目录标题 理论基础图的基本概念图的种类度 连通性连通图强连通图连通分量强连通分量 图的构造邻接矩阵邻接表 图的遍历方式 深度优先搜索理论基础dfs 与 bfs 区别dfs 搜索过程深搜三部曲所有可达路径广度优先搜索理论基础广搜的使用场景广搜的过程 岛屿数量孤岛的总面积沉…

“AI视觉贴装系统:智能贴装,精准无忧

嘿&#xff0c;朋友们&#xff01;今天我要跟你们聊聊一个特别厉害的技术——AI视觉贴装系统。这可不是普通的贴装设备&#xff0c;它可是融合了人工智能、计算机视觉和自动化控制等前沿科技的“智能贴装大师”。有了它&#xff0c;那些繁琐、复杂的贴装工作变得轻松又精准。来…

vim如何设置显示空白符

:set list 显示空白符 示例&#xff1a; :set nolist 不显示空白符 示例&#xff1a; &#xff08;vim如何使设置显示空白符永久生效&#xff1a;vim如何使相关设置永久生效-CSDN博客&#xff09;

Flutter android debug 编译报错问题。插件编译报错

下面相关内容 都以 Mac 电脑为例子。 一、问题 起因&#xff1a;&#xff08;更新 Android studio 2024.2.2.13、 Flutter SDK 3.27.2&#xff09; 最近 2025年 1 月 左右&#xff0c;我更新了 Android studio 和 Flutter SDK 再运行就会出现下面的问题。当然 下面的提示只是其…

AI导航工具我开源了利用node爬取了几百条数据

序言 别因今天的懒惰&#xff0c;让明天的您后悔。输出文章的本意并不是为了得到赞美&#xff0c;而是为了让自己能够学会总结思考&#xff1b;当然&#xff0c;如果有幸能够给到你一点点灵感或者思考&#xff0c;那么我这篇文章的意义将无限放大。 背景 随着AI的发展市面上…

Android Studio打包APK

1.导出APK安装包 如果是首次打包&#xff0c;Create new 单击蓝色对话框右边文件夹&#x1f4c2;图标 &#xff0c;选择密钥保存路径&#xff0c;然后在下方File name对话框中填写您想要名称&#xff0c;再点击OK回到密钥创建对话框。 在此对话框中填写密码&#xff08;Passwo…

ssh密钥登录GitHub时一直提示“Error: Permission denied (publickey)”

起因 环境&#xff1a;Windows10 背景&#xff1a;之前就是按照官方说明创建个rsa密钥&#xff0c;在git后台添加上&#xff0c;就行了&#xff0c;近期怎么添加怎么失败&#xff0c;总是“Error: Permission denied (publickey)”的提示&#xff01; 尝试 各种尝试&#xf…

【玩转全栈】----Django连接MySQL

阅前先赞&#xff0c;养好习惯&#xff01; 目录 1、ORM框架介绍 选择建议 2、安装mysqlclient 3、创建数据库 4、修改settings&#xff0c;连接数据库 5、对数据库进行操作 创建表 删除表 添加数据 删除数据 修改&#xff08;更新&#xff09;数据&#xff1a; 获取数据 1、OR…

软件质量与测试报告5-压力测试 JMeter 与 Badboy

A&#xff0e;百度搜索引擎压力测试 通过在Badboy下执行如下的测试场景来生成压力测试的脚本&#xff1a; a) 在Badboy的地址栏里面输入www.baidu.com&#xff0c;回车&#xff1b; b) 在右下区域打开的百度的主页上输入搜索关键字JMeter&#xff0c;回车&#xff1b; c) 在…

vim如何显示行号

:set nu 显示行号 :set nonu 不显示行号 &#xff08;vim如何使设置显示行号永久生效&#xff1a;vim如何使相关设置永久生效-CSDN博客&#xff09;

Python Typing: 实战应用指南

文章目录 1. 什么是 Python Typing&#xff1f;2. 实战案例&#xff1a;构建一个用户管理系统2.1 项目描述2.2 代码实现 3. 类型检查工具&#xff1a;MyPy4. 常见的 typing 用法5. 总结 在 Python 中&#xff0c;静态类型检查越来越受到开发者的重视。typing 模块提供了一种方式…

Linux的基本指令(上)

1.ls指令 语法&#xff1a;ls [选项] [目录或文件] 功能&#xff1a;对于⽬录&#xff0c;该命令列出该⽬录下的所有⼦⽬录与⽂件。对于⽂件&#xff0c;将列出⽂件名以及其他信息。 常用选项&#xff1a; -a 列出⽬录下的所有⽂件&#xff0c;包括以 . 开头的隐含⽂件。 -d 将…

【数据分享】1929-2024年全球站点的逐日平均能见度(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff01;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 有关气象指标的监测站点数据&#xff0c;之前我们分享过1929-2024年全球气象站点…