1. 模板的作用
把用文本形式(String)的语言,转换成对应的一个关系(Relation),关系其实对应的是一个类(class),例如,string形式的“AB平行CD”,那么就有一个对应的class来表示,假设类名为ParallelRelation,那么这个类里面有2个成员变量,对应的平行的2个直线。这样就string类型转换成一个类,既保持了原有的信息,生成的类也方便进行推理。
总之,String –> Relation。
2. 模板的介绍
以工程中已写好的一个模板文件(xml)为例介绍模板:
2.1 Synsets标签
<synset name="ANGLE_TYPE" core_term="钝角,直角,锐角为符号" expr="!?@angleType#钝角|直角|锐角|平角"/>
Name:定义一个名字(相当于定义一个“ANGLE_TYPE”的变量,后面在模板中可以直接使用)
core_term:对该变量的一些说明,不重要
expr:分为3个部分:
- 重要性:有两个级别 “!”:重要的词;"N_I":不重要的词。
(标记重要性的原则,如果具有数学意义的词,一般标为重要(用于机器学习中使用,对模板的编写没影响)) -
命名组:格式为"?@category#",为了方便提取出具体的值,从上面可以看出,该expr可以匹配 “钝角/直角/锐角/平角” 中的任意一个词, 由于该命名组为@angleType#,相当于@angleType#中就存放着具体的一个词(联想一下java的正则表达式,当Match.find()匹配成功后,使用Match.group() 来获取具体匹配字符串)
如果你不需要该匹配出来的字符,则可以省略命名组,比如:
<synset name="AND" core_term="和,与符号" expr="N_I和|,|与"/>
3. 正则表达式:该expr具体正则表达式部分。
简单来说 synset就是我们自己定义个标签,用来处理多词同义和data提取。
2.2 Template标签
该部分也就是模板的主体部分,一个模板最基本的格式如下:
<template id="Heart_Or_Excenter_Of_Triangle"> <description>三角形的内心或外心</description> <pattern>(点|)%point%是#三角形#%triangle%的一个#心#</pattern> <pattern>#三角形#%triangle%(的)(一个|)#心#是(点|)%point%</pattern> <semantics type="JSON"> <![CDATA[{"service":"cn.tsinghuabigdata.closedShape","code":"geo.JsonCoreAndShapeRelation", "data":{"point":"%point[0]%","heart":"@Heart","shape":"%triangle%","shapeStyle":"@triangleType","shapeType":"三角形"}}]]> </semantics> </template>
属性说明:
-
Id:描述标识符,见名识义。
-
Pattern: 需要匹配某个句子的正则表达式(只是可以使用前面synsets 中定义的标签和全局标签)。
synsets 中定义的标签:用 #name# 来表示,该标签实际是匹配其对应的 expr 中的正则表达式部分。
全局定义的标签:用 %name% 来表示,一些全局通用的标签,例如 %triangle% 可以匹配一个类似 “ABC ”的字符串。(具体标签在NLPConstants类中可以查看) - Semantics:重要部分,利用 Json 进行消息传递。
其中"code": "geo.JsonCircularOnTriangleRelation",表示前面某个pattern匹配成功后,会进入JsonCircularOnTriangleRelation这个类中进行处理,其中传递的参数为"data": {"point":"%point[0]%","heart":"@Heart","shape":"%triangle%","shapeStyle":"@triangleType","shapeType":"三角形"}
这是一种JSON的格式,也就是一种key-value的形式,相当于传递了5个变量:point、heart等。
注:1. 第四个变量 key 为 shapeStyle,value 为 @triangleType ,注意我们在上面定义了一个synset:
<synset name="三角形" core_term="等边,等腰为符号" expr="!?@triangleType#等边|等腰|正|直角|R|三角形|△"/>
@triangleType 在命名组中(注意不是@triangleType#,没有#),因为在pattern中有一个#三角形#,上面expr中正则表达式匹配的成功字符串,使用名字组@triangleType 进行传递其具体的 data。
2. 第一个变量 key 为 point,value 为 %point[0]%, 和上面类似,上面是我们自己定义的标签,而这个是全局定义的标签,而在pattern中出现的%point%,会存放在一个类似数组中,我们使用 %point[n]%(从0开始)来获取第 n-1 个 point 匹配的 value。
例如:</template> <template id="TRIANGLE_BISECTOR_AND_MEDIAN"> <description>三角形中的中线和角平分线2</description> <pattern>^(在)#三角形#%triangle%(中) %line% (是)(角平分线) #AND# %line% (是)(中线)$</pattern> <semantics type="JSON"> <![CDATA[{"service":"cn.tsinghuabigdata.line","code":"geo.JsonAngleBisectorRelation", "data":{"triangle":"%triangle%","triangleType":"@triangleType","line":"%line[0]%"}}]]> <![CDATA[{"service":"cn.tsinghuabigdata.closedShape","code":"geo.JsonMidianLinesOfTriangle", "data":{"line":"%line[1]%","triangle":"%triangle%","triangleType":"@triangleType"}}]]> </semantics> </template>
pattern部分中出现了2个 %line%, 在传递值的时候,如果传递的是第一个line,则在data中使用%line[0]%。
上面还有个不同的地方是生成了2个Relation(准确的是说,通过geoJsonAngleBisectorRelation这个类处理了2次,每次的data不一样,每次都生成了relation)。
3. 怎样写一个模板
-src/main/java //源代码 -src/main/resource //资源文件 -src/test/java //测试代码 -src/test/resource //测试资源文件
3.1 首要工作
-
首先在 src/main/resource/xml 自己建一个写模板的xml文件,例如为 lz_template.xml
(最好根据你们自己写哪一部分的模板命名,例如工程里面有draw_template.xml就是写作图语句的模板) - 在建好的xml定义好一些标签,如下:
<?xml version="1.0" encoding="UTF-8"?> <templates domainId="cn.tsinghuabigdata.lz"> <tags> <tag name="lz"/> </tags> </templates>
- 建立一个模板测试类
在 src/test/.../jsontorelation/generalTest.java 有一个已经建好的测试类,不过需要添加一些内容。
把刚建立的xml添加进去,其他模板文件可以注释掉:ActionTemplate.getInstance().loadActionTemplate("xml\lz_template.xml");
然后注册自己的服务:
TreeSet<String> treeSet = new TreeSet<String>();
treeSet.add("cn.tsinghuabigdata.lz");
名字为xml的domainID
3.2 开始写模板
前提是已经知道正则表达式的基本使用语法,以编写 “AE 和 AF 分别为三角形 ABC 的中位线” 为例:
- 在 NLPConstants 查看全局标签,NLPContants 标签部分如下:
public static final POINT = "Point"; public static final TRIANGLE = "Triangle"; public static final CIRCLE = "Circle";
- 用标签替换掉里面的具体数学对象,如:
用 line 替换掉 AE 和 AF 这样具体的数学对象,用 triangle 替换掉 ABC
“AE 和 AF 分别为三角形 ABC 的中位线” 的 pattern 即为:
“%line%和%line%分别为三角形%triangle%的中位线” - 考虑句子的多样性,如:
上面的句子中,“和”可以用“与”或者“,”来替换,不影响表达意思
“AE 与 AF 分别为三角形 ABC 的中位线”
“AE , AF 分别为三角形 ABC 的中位线”
因此,还要考虑句子的各种表达形式,这就要用到前面说的 synset 标签
我们在 xml 文件中加入自己定义的标签:<synset> <synset name = "AND" core_term = "和,与符号" expr = "N_I和|,|与"/> </synset>
这样pattern就变为类:
“%line%#AND#%line%分别为三角形%triangle%的中位线”
注意:自己定义的标签用# #,NLPConstants里面的全局标签用% %. - 编写模板
- 先找出这句话里面包含的Relation
上面例子中,包含了2个三角形中线的关系(在863-model里面的relation包里面查找)
三角形中线关系为 MidianLineOfTriangleRelation - 查看创建该关系所需要的信息,主要通过构造函数查看:
public MidianLineOfTriangleRelation(Segment segment, Triangle triangle) { super(segment); AssertConstructorDataUtil.assertNotNull(triangle); this.triangle = triangle; recogniseTopBottom(); }
在MidianLineOfTriangleRelation的构造函数中,要提取出 segment 和 triangle 这2个参数.
- 继续编写模板
生成一个MidianLineOfTriangleRelation需要一个Segment和一个Triangle,通过构造函数来看
(有多个构造函数时候,选一个自己觉得最合适的)public static LineElement creatLineElement(String line) throws AssertIllegalException { LineUtil.LineType type = LineUtil.getLineType(line); if(type.equals(LineUtil.LineType.SingleLetterLine)) { return new LineElement(line,type); } else { return creatSegmentAndRayElement(line,type); } }
Segment 通过 pattern 中的 %line% 提取
public static TriangleElement creatTriangleElement(String attr, String triangle) throws AssertIllegalException { checkAssert.AssertIllegal(attr,triangle); TriangleElementUtil.TriangleType angleAttr = TriangleElementUtil.getTriangAngle(attr); List<String> points = TriangleElementUtil.getPoints(triangle); return new TriangleElement(PointElement.createPointElement(points.get(0), PointElement.createPointElement(points.get(1), PointElement.createPointElement(points.get(2), angleAttr); }
Triangle 的构造函数中,有一个参数为attr,表示三角形的属性,例如“等腰三角形”,第二个triangle是具体三角形的名字,
例如“ABC”,这样在构造Triangle的时候,除了提取出三角形的名字,还要有三角形的类别,所以继续添加提取三角形类别的
synset.<synset> <synset name = "AND" core_term = "和,与符号" expr = "N_I和|,|与"/> <synset name = "三角形" core_term = "等边,等腰为符号" expr = "!?@triangle#三角形/等边三角形/等腰三角形/直角三角形"/> </synset>
这样,模板的一切需要的东西都具备了。<?xml version="1.0" encoding="UTF-8"?> <templates domainId="cn.tsinghuabigdata.lz"> <tags> <tag name="lz"/> </tags> <synsets> <synset name="AND" core_term="和,与符号" expr="N_I和|,|与"/> <synset name="IS" core_term="是,为符号" expr="N_I是|为"/> <synset name="三角形" core_term="等边,等腰为符号" expr="!?@triangleType#等边|等腰|正|直角|R|三角形|△"/> </synsets> <template id="Middle_Line_Of_Triangle"> <description>三角形的中线</description> <pattern>^%line%#AND#%line%(分别|)#IS##三角形#%Triangle%(中位)(线)$</pattern> <semantics type="JSON"> <![CDATA[{"service":"cn.tsinghuabigdata.closedShape","code":"geo.JsonMidianLinesOfTriangle", "data":{"line":"%line[0]%","triangle":"%triangle%","triangleType":"@triangleType"}}]]> <![CDATA[{"service":"cn.tsinghuabigdata.closedShape","code":"geo.JsonMidianLinesOfTriangle", "data":{"line":"%line[1]%","triangle":"%triangle%","triangleType":"@triangleType"}}]]> </semantics> </template>
目前 JsonMidianLinesOfTriangle 还没有建,现在开始建。
-
在863-nlu的 jsontorelation 包中,在对应的 子包 中建立一个类,用来处理模板提取的信息并生成relation
例如:上面例子,在 geo 包中创建一个JsonMidianLinesOfTriangle类public class JsonMidianLinesOfTriangle { public static IRelation JsonToRelation(JsonInfo root, GlobalInfomation globalInformation) { Irelation iRelation = null; return iRelation; } }
里面的 JsonToRelation 的函数名和参数是固定的
JsonObject jsonObject = root.getJsonObject().getAsJsonObject("data"); String line = jsonObject.get("line").toString().replace("",""); String triangle = jsonObject.get("triangle").toString().replace("",""); String triangleType = jsonObject.get("triangleType").toString().replace("","");
上面的代码在模板那边,通过“data”传过来的json数据格式的参数
(上面已经有函数处理,例如 String line = TransToRelationUntil.getStringDataFromJsonObject(jsonObject, "line"); 来提取line的data)下面该生成 MidianLineOfTriangleRelation,并返回
try { Triangle triangle1 = TriangleElement.createTriangleElement(triangleType,triangle).transToData(); Segment segment = (Segment)LineElement.creatLineElement(line).transToData(); iRelation = new MidianLineOfTriangleRelation(segment, triangle1); iRelation.setPosition(root.getLineBegin(), root.getLinePosBegin(), root.getLineEnd(), root.getLinePosEnd()); } catch(Exception e) { e.printStackTrace(); }
生成对应的 Segment 和 Triangle, 最后生成 MidianLineOfTriangleRelation.
写完后的完整代码如下:
public class JsonMidianLinesOfTriangle { public static IRelation JsonToRelation(JsonInfo root, GlobalInfomation globalInformation) { Irelation iRelation = null; if(root == null) { return iRelation; } JsonObject jsonObject = root.getJsonObject().getAsJsonObject("data"); String line = jsonObject.get("line").toString().replace("",""); String triangle = jsonObject.get("triangle").toString().replace("",""); String triangleType = jsonObject.get("triangleType").toString().replace("",""); try { Triangle triangle1 = TriangleElement.createTriangleElement(triangleType,triangle).transToData(); Segment segment = (Segment)LineElement.creatLineElement(line).transToData(); iRelation = new MidianLineOfTriangleRelation(segment, triangle1); iRelation.setPosition(root.getLineBegin(), root.getLinePosBegin(), root.getLineEnd(), root.getLinePosEnd()); } catch(Exception e) { e.printStackTrace(); } return iRelation; } }
- 测试
在generalTest中,把taggedText 改为" AE&&line 和 AF&&line 分别是三角形 ABC&&triangle 的中位线";
taggerTest = " AE&&line 和 AF&&line 分别是三角形 ABC&&triangle 的中位线"这里需要说明的是,由于这个测试没有加入nlp,所以命名识别没有加入,需要我们手动加入tag
比如我们测试的句子为 AE和AF分别是三角形ABC的中位线,通过NLPConstants中查询,AE和AF在命名识别中会识别为line
所以在AE和AF后面加入 &&line(注意空格的位置)。运行结果:
R_MidianLineOfTriangle:AE/△BAC
R_MidianLineOfTriangle:AF/△BAC最后的最后,记得编写单元测试,通过工程中已有的单元测试编写即可(其实和generalTest类似)。