golang知识图谱NLP实战第一节——整体思路
golang知识图谱NLP实战第二节——解析依存句法分析结果
golang知识图谱NLP实战第三节——实体三元组关系抽取
参考上两篇文章的开源代码,这里不重复贴出了,感谢他们开源精神。
CONLL标注格式包含10列,分别为:
———————————————————————————
ID FORM LEMMA CPOSTAG POSTAG FEATS HEAD DEPREL PHEAD PDEPREL
———————————————————————————
只用到前8列,其含义分别为:
1 ID 当前词在句子中的序号,1开始.
2 FORM 当前词语或标点
3 LEMMA 当前词语(或标点)的原型或词干,在中文中,此列与FORM相同
4 CPOSTAG 当前词语的词性(粗粒度)
5 POSTAG 当前词语的词性(细粒度)
6 FEATS 句法特征,在本次评测中,此列未被使用,全部以下划线代替。
7 HEAD 当前词语的中心词
8 DEPREL 当前词语与中心词的依存关系
1 主谓关系 SBV subject-verb 我送她一束花 (我 <-- 送)
2 动宾关系 VOB 直接宾语,verb-object 我送她一束花 (送 --> 花)
3 间宾关系 IOB 间接宾语,indirect-object 我送她一束花 (送 --> 她)
4 前置宾语 FOB 前置宾语,fronting-object 他什么书都读 (书 <-- 读)
5 兼语 DBL double 他请我吃饭 (请 --> 我)
6 定中关系 ATT attribute 红苹果 (红 <-- 苹果)
7 状中结构 ADV adverbial 非常美丽 (非常 <-- 美丽)
8 动补结构 CMP complement 做完了作业 (做 --> 完)
9 并列关系 COO coordinate 大山和大海 (大山 --> 大海)
10 介宾关系 POB preposition-object 在贸易区内 (在 --> 内)
11 左附加关系 LAD left adjunct 大山和大海 (和 <-- 大海)
12 右附加关系 RAD right adjunct 孩子们 (孩子 --> 们)
13 独立结构 IS independent structure 两个单句在结构上彼此独立
14 核心关系 HED head 指整个句子的核心
开源的代码都是java的,我要将它们读懂——写出思路——尝试用go编出来
刘小绪的代码思路是:将hanlp输出来的依存句法分析结果转成下列格式的map,称之为dict——为分词结果中的每个分词,配上依存句法分析结果……(这样讲其实也不准确,就是为了方便下面的逻辑分析出三元体)
[{},
{主谓关系=[1 刘小绪 刘小绪 nh nr _ 2 主谓关系 _ _],
动宾关系=[3 四川 四川 ns ns _ 2 动宾关系 _ _]},
{}]
然后用extract函数进行逻辑分析出实体三元组。extract函数输入分词结果,dict和循环i,对于每个dict的一行dic来进行逻辑分析,来瞅一下:
/**
* @param parser 句法依存分析
* @param dict 词语依存字典
* @param i 词语索引
* @return 三元组列表
*/
private static Set extract(List parser,
List
这一段就能得出这样的结果了:
刘小绪,洗,衣服
刘小绪,洗干净了,衣服
我用go进行仿照,因为他用了java语言的map数组,而go似乎没找到map数组,我只好用struct数组。首先将依存句法结果解析成struct(代码中的Hanlp),然后构造类似java的dict(代码中的Dict),然后循环dict,进行逻辑分析,就得到三元组结果了。
package main
import (
"fmt"
"strings"
)
type Dict struct {
SBV []Hanlp
VOB []Hanlp
IOB []Hanlp
FOB []Hanlp
DBL []Hanlp
ATT []Hanlp
ADV []Hanlp
CMP []Hanlp
COO []Hanlp
POB []Hanlp
LAD []Hanlp
RAD []Hanlp
IS []Hanlp
HED []Hanlp
}
type Hanlp struct {
ID string `json:"id"`
FORM string `json:"form"`
LEMMA string `json:"lemma"`
CPOSTAG string `json:"cpostag"`
POSTAG string `json:"postag"`
FEATS string `json:"feats"`
HEAD string `json:"head"`
DEPREL string `json:"deprel"`
}
type Ltp2 struct {
Ltptwo []Ltp1
}
type Ltp1 struct { //这个办法不行!
Ltpone []Ltp
}
type Ltp struct {
Id int64 `json:"id"`
Cont string `json:"cont"`
Pos string `json:"pos"`
Ne string `json:"ne"`
Parent int64 `json:"parent"`
Relate string `json:"relate"`
Semparent int64 `json:"semparent"`
Semrelate string `json:"semrelate"`
Arg []Arg1 `json:"arg"`
Sem []Sem1 `json:"sem"`
}
type Sem1 struct {
Id int64 `json:"id"`
Parent int64 `json:"parent"`
Relate string `json:"relate"`
}
type Arg1 struct {
Id int64 `json:"id"`
Type string `json:"type"`
Beg int64 `json:"beg"`
End int64 `json:"end"`
}
func main() {
// jsonHanlpStr := `1 房顶 房顶 n n _ 2 定中关系 _ _
//2 上 上 nd f _ 3 状中结构 _ _
//3 站 站 v v _ 0 核心关系 _ _
//4 着 着 u u _ 3 右附加关系 _ _
//5 一 一 m m _ 6 定中关系 _ _
//6 只 只 q q _ 7 定中关系 _ _
//7 小鸟 小鸟 n n _ 3 动宾关系 _ _`
// jsonHanlpStr := `1 刘海 刘海 nh nr _ 2 主谓关系 _ _
//2 打扫 打扫 v v _ 0 核心关系 _ _
//3 房间 房间 n n _ 4 定中关系 _ _
//4 卫生 卫生 a an _ 2 动宾关系 _ _`
jsonHanlpStr := `1 刘小绪 刘小绪 nh nr _ 2 主谓关系 _ _
2 洗 洗 v v _ 0 核心关系 _ _
3 干净 干净 a a _ 2 动补结构 _ _
4 了 了 u u _ 2 右附加关系 _ _
5 衣服 衣服 n n _ 2 动宾关系 _ _`
hanlp := make([]Hanlp, 0)
aa := make([]Hanlp, 1)
array := strings.Split(jsonHanlpStr, "\n")
for _, v := range array {
array1 := strings.Split(v, " ")
//for _, w := range array1 {
aa[0].ID = array1[0]
aa[0].FORM = array1[1]
aa[0].LEMMA = array1[2]
aa[0].CPOSTAG = array1[3]
aa[0].POSTAG = array1[4]
aa[0].FEATS = array1[5]
aa[0].HEAD = array1[6]
aa[0].DEPREL = array1[7]
hanlp = append(hanlp, aa...)
}
// fmt.Println(hanlp)
//刘小绪 --(主谓关系)--> 生于
//生于 --(核心关系)--> ##核心##
//四川 --(动宾关系)--> 生于
// [{},
// {主谓关系=[1 刘小绪 刘小绪 nh nr _ 2 主谓关系 _ _],
// 动宾关系=[3 四川 四川 ns ns _ 2 动宾关系 _ _]},
// {}]
for _, w := range hanlp {
dict := make([]Dict, 1)
for _, x := range hanlp {
//找出x.head.lemma
//hanlp[x.HEAD].LEMMA
bb := make([]Hanlp, 1)
for _, i := range hanlp {
//fmt.Println(i.ID)
if x.HEAD == i.ID {
//temp = i.LEMMA
//temp[0] = i
bb[0] = i
//fmt.Println(i)
}
}
if w.LEMMA == bb[0].LEMMA {
list1 := make([]Hanlp, 1)
list1[0] = x
switch x.DEPREL {
case "主谓关系":
dict[0].SBV = list1
case "动宾关系":
dict[0].VOB = list1
case "间宾关系":
dict[0].IOB = list1
case "前置宾语":
dict[0].FOB = list1
case "兼语":
dict[0].DBL = list1
case "定中关系":
dict[0].ATT = list1
case "状中结构":
dict[0].ADV = list1
case "动补结构":
dict[0].CMP = list1
case "并列关系":
dict[0].COO = list1
case "介宾关系":
dict[0].POB = list1
case "左附加关系":
dict[0].LAD = list1
case "右附加关系":
dict[0].RAD = list1
case "独立结构":
dict[0].IS = list1
case "核心关系":
dict[0].HED = list1
}
bb = make([]Hanlp, 0)
}
}
// fmt.Println(dict)
//[]
//[{[{1 刘海 刘海 nh nr _ 2 主谓关系}] [] [] [] [] [] [] [] [] [] [] [] [] []} {[] [{4 卫生 卫生 a an _ 2 动宾关系}] [] [] [] [] [] [] [] [] [] [] [] []}]
//[]
//[{[] [] [] [] [] [{3 房间 房间 n n _ 4 定中关系}] [] [] [] [] [] [] [] []}]
for _, ww := range dict {
//主谓宾关系:刘小绪生于四川
if len(ww.SBV) > 0 && len(ww.VOB) > 0 {
entity1 := ww.SBV[0].LEMMA
entity2 := ww.VOB[0].LEMMA
relation := w.LEMMA
fmt.Println(entity1 + "," + relation + "," + entity2)
}
// 动补结构:刘小绪洗干净了衣服
// _, ok = maps["主谓关系"]
// _, ok1 = maps["动宾关系"]
// _, ok2 := maps["动补结构"]
if len(ww.SBV) > 0 && len(ww.VOB) > 0 && len(ww.CMP) > 0 {
entity1 := ww.SBV[0].LEMMA
complement := ww.CMP[0].LEMMA
entity2 := ww.VOB[0].LEMMA
if len(ww.RAD) > 0 { //右附加关系
subjoin := ww.RAD[0].LEMMA
relation := w.LEMMA + complement + subjoin
fmt.Println(entity1 + "," + relation + "," + entity2)
} else {
relation := w.LEMMA + complement
fmt.Println(entity1 + "," + relation + "," + entity2)
}
}
}
}
解析成struct的结果为:
[{1 刘小绪 刘小绪 nh nr _ 2 主谓关系} {2 洗 洗 v v _ 0 核心关系} {3 干净 干净 a a _ 2 动补结构} {4 了 了 u u _ 2 右附加关系} {5 衣服 衣服 n n _ 2 动宾关系}]
构造的dict是这样子的:
[{[{1 刘小绪 刘小绪 nh nr _ 2 主谓关系}] [{5 衣服 衣服 n n _ 2 动宾关系}] [] [] [] [] [] [{3 干净 干净 a a _ 2 动补结构}] [] [] [] [{4 了 了 u u _ 2 右附加关系}] [] []}]