知识图谱的定义
知识图谱最先由Google提出,是用于增强其搜索引擎功能的知识库。本质上, 知识图谱旨在描述真实世界中存在的各种实体或概念及其关系,其构成一张巨大的语义网络图,节点表示实体或概念,边则由属性或关系构成。现在的知识图谱已被用来泛指各种大规模的知识库。知识图谱中包含三种节点:
知识图谱的架构
知识图谱从逻辑上可以划分为2个层次:数据层和模式层。
在知识图谱的数据层,知识以事实(fact)为单位存储在图数据库。图数据中有“实体-关系-实体”或者“实体-属性-属性值”两种三元组,所有数据构成庞大的实体关系网络。
模式层在数据层之上,是知识图谱的核心。模式层存储的是经过提炼的知识,通常采用本体库来管理知识图谱的模式层。
知识图谱的构建
知识图谱的构建包含三个阶段,分别是:信息抽取、知识融合以及知识加工。
信息抽取的关键问题是如何从异构数据源中自动抽取信息得到候选知识单元。关键技术包括:实体抽取、关系抽取和属性抽取。
知识图谱有自顶向下和自底向上2种构建方法。
自顶向下是从百科类网站等高质量数据源中提取本体和模式信息,加入到知识库中。
自底向上是从公开采集的数据中提取出资源模式,选择其中置信度较高的新模式,经人工审核后,加入到知识库中。
获取数据
非结构化数据的获取:
本次数据使用了巨潮资讯网上上市公司的深圳主板上的上市公司的公告信息。从巨潮网上查看控制台中的network发现巨潮资讯网在公告信息这一模块是通过post请求加参数来发送request,因此只要构建一个和巨潮网相同的请求的参数列表即可。然后将返回的url链接存入csv文件,通过csv文件保存的pdf文件的链接地址,通过python的pdfminer库将pdf文件下载存储为一个txt文件。
结构化数据的获取:
巨潮资讯网中有结构化的公司的高管人员信息和公司概况信息。经过分析发现高管信息和公司概况信息网页是动态加载的,里面的内容都是通过js来控制iframe进行展现的,因此通过scrapy的response.body获取的网页的返回结果没有完美所需要的数据, 所以采用python的beautifulsoup库进行信息的爬取。从巨潮公司的上市公司的公司列表页面中获取公司的股票代码,然后通过公司的股票代码从公司信息页面中获取公司的高管信息和共公司概况。
数据预处理
在用DeepDive处理原始文本时,由于原始文本中出现了一些简称之类的词,nlp语言里面并没有处理此类情况的方法,所以会造成公司实体识别不准确的情况,会降低程序处理结果的正确性,因此在将文本导入DeepDive之前对文本进行了预处理操作。
针对公告信息中大部分采用简称的方式来表示公司,所以采用了哈工大研发的ltp工具来进行公告中公司实体的识别,通过设置API参数中的pattern=ner&format=plain识别出公司名称并返回,然后用正则表达式匹配公告中的***公司(以下简称“***”)提取出公司和相应的简称之间的对应关系,然后用全称替换掉公告中的简称来进行原始文本的初始化。
抽取信息
信息抽取过程中使用了DeepDive工具来进行信息的信息的抽取, DeepDive可以从非结构化的文本中提取出结构化的文本。
DeepDive采用了standford nlp进行文本处理。根据输入文本数据,nlp模块将以句子为单位,返回每句的分词、lemma、pos、NER和句法分析的结果。将返回的数据进行数据监督和规则监督即可得到结构化的三元组关系。
在DeepDive我们想要预测的是一个名为DDlog的语言中的随机变量,将要保存的数据库表声明在app.ddlog文件中,DeepDive会编译生成相应的脚本来进行表的生成。
在DeepDive中使用Postgres数据库来存储所有的数据输入,中间过程以及输出,如果对于要求有更大规模的操作,可以使用Greenplum数据库。
DeepDive处理数据的流程:
知识融合包括2部分内容:实体链接和知识合并。
知识融合的目的是消除概念的歧义,剔除冗余和错误概念,从而保证知识的质量。
实体链接
实体链接(entity linking)是指从文本中抽取得到的实体对象,将其链接到知识库中对应的正确实体对象的操作。
实体链接的基本思想是首先根据给定的实体指称项,从知识库中选出一组候选实体对象,然后通过相似度计算将指称项链接到正确的实体对象。
实体链接的一般流程是:
从文本中通过实体抽取得到实体指称项
进行实体消岐和共指消解
在确认知识库中对应的正确实体对象后,将该实体指称项链接到知识库中对应实体
实体消歧是专门用于解决同名实体产生歧义问题的技术。例如“苹果”可以指水果,也可以指手机。通过实体消岐,然后就可以根据当前的语境,准确建立实体链接。实体消岐主要采用聚类法。聚类法消岐的常用方法有4种:1.空间向量模型(词袋模型);2.语义模型;3.社会网络模型;4.百科知识模型
共指消解主要用于解决多个指称项对应于同一实体对象的问题。例如“eason”,“陈某某”,“陈奕迅”等指称项可能指向的是同一个实体对象。代表性的解决方法是Hobbs算法和向心理论(centering theory)
知识合并
在构建知识图谱时,需要将第三方知识库产品或已有结构化数据获取知识输入。
抽取得到的结构化数据:将从巨潮资讯网上爬虫获取的结构化数据导入数据库中
抽取得到的三元组数据:将抽取得到的实体、属性和关系过滤掉冗余的部分导入到数据库中
本体构建
本体可以采用人工编辑的方式手动构建(借助 本体编辑软件),也可以采用计算机辅助,以数据驱 动的方式自动构建, 然后采用算法评估和人工审核 相结合的方式加以修正和确认。对于特定领域而言,可以采用领域专家和众包的方式人工构建本体 。
然而对于跨领域的全局本体库而言,采用人工方式工作量巨大,而且很难找符合要求的专家。因此当前主流的全局本体库产品,都是从一些特定领域的现有本体库出发,采用自动构建技术逐步扩展得到的。
在本次实验中,选用了 OWL 语言作为本体描述语言。
本体构建工具
Protege软件是斯坦福大学医学院生物信息研究中心基于Java语言开发的本体编辑和知识获取软件,或者说是本体开发工具,也是基于知识的编辑器。Protege提供了本体概念类,关系,属性和实例的构建,并且屏蔽了具体的本体描述语言,用户只需在概念层次上进行领域本体模型的构建。
Jena提供了将RDF数据存入关系数据库的接口,Model、Resource、Query等接口可以用于访问和维护数据库里的RDF数据。在处理数据时,应用程序不必直接操作数据(而是通过Jena的API),也不必知道数据库的模式。Jena提供了支持MySQL、HSQLDB、PostgreSQ、Oracle和Microsoft SQL Server的程序接口。
数据准备
金融本体构建使用的数据来源于数据抽取阶段的结构化数据和非结构化数据中抽取出的关系三元组
公司的基本信息包括:
公司全称、英文名称、注册地址、公司简称、法定代表人、公司董秘、注册资本(万元)、行业种类、邮政编码、公司电话、公司传真、公司网址、上市时间、招股时间、发行数量(万股)、发行价格(元)、发行市盈率(倍)、发行方式主承销商、上市推荐人、保荐机构
高管的基本信息包括:
姓名、职务、出生年份、性别、学历
本体建模
在Protege中,我们创建金融知识图谱的类/概念。所有的类的都是Thing的子类。我们创建了公司、行业、地区、高管四个类,在Object Properties页面创建了类之间的关系,比如公司属于什么行业,高管管理哪个公司,公司位于哪个地区,公司之间的合作、增资、子公司、持股、提供担保、更名、股权转让、购买产品、购买股权、贷款等关系。在Data properties部分创建各类的属性,例如有公司全称、英文名称、注册地址、姓名、性别等。
在本体结构中把“公司”作为金融本体中的核心。在实际情况中,“公司”也是金融领域中最核心、最基本的单位,大部分金融事件都是围绕着“公司”发生的,例如“一个公司增资另外一个公司”、“一个公司位于哪个城市”等,所以本体结构中把“公司”作为金融本体中的核心。
知识推理
知识推理是指从知识库中已有的实体关系数据出发,经过计算机推理,建立实体间的新关联,从而扩展和丰富知识网络。例如已知(A,管理,B),可以推理出(B,被管理,A)等等。知识推理的对象不局限于实体间的关系,也可以是实体的属性值、本体的概念层次关系等。例如已知(老虎,科,猫科)和(猫科,目,食肉目),可以推出(老虎,目,食肉目)。知识的推理方法可以分为2大类:基于逻辑的推理和基于图的推理。
本体的推理规则库是有一条条规则组成的。每条规则由主体(body)和头(head)组成,一条规则可以有一个主体和一个头,例如规则:[rule1:(?a fa:hasHusband ?b)(?a fa:isMotherOf ?c)->(?b fa:isFatherOf ?c)],其中规则的主体为:?a @hasHusband ?b,?a @isMotherOf ?c,头为:?b @isFatherOf ?c,也就是说有所有的主体可以推出头。?a @hasHusband ?b,?a @isMotherOf ?c,?b @isFatherOf ?c三者有一个称谓:ClauseEntry。例如方法Rule类中getbody()方法返回是一个ClauseEntry集合。他有2个元素?a @hasHusband ?b,?a @isMotherOf ?c。
规则库建立好以后,将规则库保存为*.rule文件,通过java的Rule类即可根据建立好的规则库查询规则,在Jena中通过推理的接口即可使用sparql对本体进行查询。以下是根据一个规则库查规的例子:
List rules=Rule.rulesFromURL("file:f:/family.rules");
//规则库里有[rule1: (?a fa:hasHusband ?b)(?a fa:isMotherOf ?c)->(?b fa:isFatherOf ?c)]
ClauseEntry[] body=rules.get(0).getBody(); //获取规则库中的第一个规则的主体
int j=rules.get(0).bodyLength(); //获取规则的长度
for (int i=0;i