前言:21年广州荔湾区成了疫情灾区,很多人都没有工作,被居家隔离,感染病毒概率死亡率是0.005%,没有工作死亡率是100%,因此作为普通老百姓,自己开发了一个数据分析工具,叫yandas。
信息抽取旨在从非结构化自然语言文本中提取结构化知识,如实体、关系、事件等。对于给定的自然语言句子,根据预先定义的schema集合,抽取出所有满足schema约束的SPO三元组。
例如,「妻子」关系的schema定义为:
{
S_TYPE: 人物,
P: 妻子,
O_TYPE: {
@value: 人物
}
}
知识抽取是构建知识图谱的基础, 因为获取到知识的数量级以及准确度, 直接影响到知识图谱的规模和好坏。
知识抽取的数据来源往往有 3 种,分别是:
结构化的数据源
半结构化的数据源
无结构化的数据源
知识抽取就是从数据源中抽取到我们所需要的内容。
知识抽取包括三个方面的内容: 实体抽取、 关系抽取以及属性抽取。
1、实体抽取(命名实体识别,Named Entity Recognition, 简称 NER)
实体抽取。 主要指的是从自然文本中抽取到我们所需要的命名实体(例如:地名、 人名, 以及各种专有名词) 。 这个过程也叫做命名实体识别( named entity recognition, 简称 NER) 。
最早的命名实体识别过程, 都是基于规则的, 由于所有的规则都是需要人为手工的编写, 因此需要耗费大量的人力, 可扩展性也很差。
1.1 结构化数据源实体抽取:爬虫采集
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。。用这个框架可以轻松爬下来如亚马逊商品信息之类的数据。
2、关系抽取【中文:LTP工具包;英文:NLTK工具包】
在我们得到实体之后, 就考虑从文本中挖掘出实体与实体之间的语义信息, 也就是它们之间的关联关系。
关系抽取不仅是信息抽取的任务之一, 也是构建和补全知识图谱的关键所在,其研究的主要内容是从文本内容中挖掘出实体与实体之间的语义关系, 从纯文本生成关系数据的过程, 是自然语言处理(NLP) 中的关键任务。
3、属性抽取
属性指的是实体的属性。 百科是实体属性的主要来源。 FMSuchanek等人编写的启发式算法能够从维基百科的信息盒子(inforbox) 中抽取出属值对(属性-属性值) , 准确率超过 96%。 DBpedia 是现在最有影响力的知识库之一, 它从维基百科的 inforbox 中抽取了 45800 个实体、 30 亿个属性。
主谓宾关系:刘小绪 生于 四川
// 这个三元组很明显:(刘小绪,生于,四川)
动补结构:刘小绪 洗 干净 了 衣服
// 如果套用主谓宾关系就是:(刘小绪,洗,衣服)
// 但是这里描述的是一个状态,是刘小绪把衣服洗干净了
// “干净”是动词“洗”的补语,所以还应该提取出一个如下三元组
// (刘小绪,洗干净了,衣服)
状动结构:父亲 非常 喜欢 跑步
// 这句和上面很像,主谓宾关系是:父亲喜欢跑步
// “非常”用于修饰“喜欢”
// (父亲,非常喜欢,跑步)
介宾关系:刘小绪 就职 于 学校
// 如果直接把这个三元组抽取为(刘小绪,就职,学校),很别扭
// “于”和“学校”是介宾关系,它们的关系应该是:就职于
// (刘小绪,就职于,学校)
宾语前置:海洋 由 水 组成
// “海洋”是“组成”的前置宾语
// “由”是“组成”的状语
// “水”和“由”是介宾关系
// 所以上面的句子没有明确的主谓关系,需要我们判断
// 抽出的三元组应该为:(水,组成,海洋)
/**
* @param parser 句法依存分析
* @param dict 词语依存字典
* @param i 词语索引
* @return 三元组列表
*/
private static Set<String> extract2(List<CoNLLWord> parser,
List<Map<String, List<CoNLLWord>>> dict,
int i) {
CoNLLWord word = parser.get(i);
Map<String, List<CoNLLWord>> dic = dict.get(i);
Set<String> result = new HashSet<>();
// 主谓宾关系:刘小绪生于四川
if (dic.containsKey("主谓关系") && dic.containsKey("动宾关系")){
CoNLLWord entity1 = dic.get("主谓关系").get(0);
// 排除:刘小绪和李华是朋友
// entity1.ID-1 即主语在依存字典中的索引
if (dict.get(entity1.ID-1).containsKey("并列关系")){
String relation = dic.get("动宾关系").get(0).LEMMA;
CoNLLWord entity2 = dict.get(entity1.ID-1).get("并列关系").get(0);
result.add(entity1.LEMMA + "," + relation + "," + entity2.LEMMA);
}else {
CoNLLWord entity2 = dic.get("动宾关系").get(0);
String relation = word.LEMMA;
result.add(entity1.LEMMA + "," + relation + "," + entity2.LEMMA);
}
}
// 动补结构:刘小绪洗干净了衣服
if (dic.containsKey("动补结构") && dic.containsKey("主谓关系") && dic.containsKey("动宾关系")){
CoNLLWord entity1 = dic.get("主谓关系").get(0);
CoNLLWord complement = dic.get("动补结构").get(0);
CoNLLWord entity2 = dic.get("动宾关系").get(0);
if (dic.containsKey("右附加关系")){
CoNLLWord subjoin = dic.get("右附加关系").get(0);
String relation = word.LEMMA + complement.LEMMA + subjoin.LEMMA;
result.add(entity1.LEMMA + "," + relation + "," + entity2.LEMMA);
}else {
String relation = word.LEMMA + complement.LEMMA;
result.add(entity1.LEMMA + "," + relation + "," + entity2.LEMMA);
}
}
if (dic.containsKey("定中关系")){
CoNLLWord entity1 = dic.get("定中关系").get(0);
String relation = word.LEMMA;
for (Map<String, List<CoNLLWord>> tempDic:
dict) {
if (tempDic.containsKey("主谓关系") && tempDic.containsKey("动宾关系")){
if (tempDic.get("主谓关系").get(0).LEMMA.equals(relation)){
CoNLLWord entity2 = tempDic.get("动宾关系").get(0);
result.add(entity1.LEMMA + "," + relation + "," + entity2.LEMMA);
}
}
}
}
// 状动结构:父亲非常喜欢跑步
// 非常 是 跑步的状语,关系应该为非常喜欢
if (dic.containsKey("状中结构") && dic.containsKey("主谓关系") && dic.containsKey("动宾关系")){
CoNLLWord entity1 = dic.get("主谓关系").get(0);
CoNLLWord adverbial = dic.get("状中结构").get(0);
CoNLLWord entity2 = dic.get("动宾关系").get(0);
String relation = adverbial.LEMMA + word.LEMMA;
result.add(entity1.LEMMA + "," + relation + "," + entity2.LEMMA);
}
// 状动补结构:
if (dic.containsKey("状中结构") && dic.containsKey("动补结构") &&
dic.containsKey("主谓关系") && dic.containsKey("动宾关系")){
CoNLLWord entity1 = dic.get("主谓关系").get(0);
CoNLLWord adverbial = dic.get("状中结构").get(0);
CoNLLWord complement = dic.get("动补结构").get(0);
CoNLLWord entity2 = dic.get("动宾关系").get(0);
String relation = adverbial.LEMMA + word.LEMMA + complement.LEMMA;
result.add(entity1.LEMMA + "," + relation + "," + entity2.LEMMA);
}
// 定语后置:父亲是来自肯尼亚的留学生
if (word.DEPREL.equals("定中关系")){
if (dic.containsKey("动宾关系")){
CoNLLWord entity1 = word.HEAD;
String relation = word.LEMMA;
CoNLLWord entity2 = dic.get("动宾关系").get(0);
result.add(entity1.LEMMA + "," + relation + "," + entity2.LEMMA);
}
}
// 介宾关系:刘小绪就职于学校
// 于 和 学校 是介宾关系
if (dic.containsKey("主谓关系") && dic.containsKey("动补结构")){
CoNLLWord entity1 = dic.get("主谓关系").get(0);
CoNLLWord prep = dic.get("动补结构").get(0);
// 介词的索引
int prepIndex = prep.ID - 1;
Map<String, List<CoNLLWord>> prepDict = dict.get(prepIndex);
if (prepDict.containsKey("介宾关系")){
CoNLLWord entity2 = prepDict.get("介宾关系").get(0);
String relation = word.LEMMA + prep.LEMMA;
result.add(entity1.LEMMA + "," + relation + "," + entity2.LEMMA);
}
}
// 宾语前置结构:海洋由水组成
if (dic.containsKey("前置宾语")){
CoNLLWord entity2 = dic.get("前置宾语").get(0);
if (dic.containsKey("状中结构")){
CoNLLWord adverbial = dic.get("状中结构").get(0);
int prepIndex = adverbial.ID - 1;
Map<String, List<CoNLLWord>> prepDict = dict.get(prepIndex);
if (prepDict.containsKey("介宾关系")){
CoNLLWord entity1 = prepDict.get("介宾关系").get(0);
String relation = word.LEMMA;
result.add(entity1.LEMMA + "," + relation + "," + entity2.LEMMA);
}
}
}
return result;
}
补充:知识融合,知识合并
采用文本相似度计算
for(int i = 0 ;i <result5.size();i++){
for(int j = i ;j <result6.size();j++){
if (result5.get(i).equals(result6.get(j)) ) continue;
double d = SimilarityUtil.getSimilarity(result5.get(i), result6.get(j));
if(d>0.8){
result4.add(result6.get(j));
}
}
}
result2.removeAll(result4);