模板编写

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个部分:

  1. 重要性:有两个级别 “!”:重要的词;"N_I":不重要的词。
    (标记重要性的原则,如果具有数学意义的词,一般标为重要(用于机器学习中使用,对模板的编写没影响))
  2. 命名组:格式为"?@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>

  属性说明:

  1. Id:描述标识符,见名识义。

  2. Pattern: 需要匹配某个句子的正则表达式(只是可以使用前面synsets 中定义的标签和全局标签)。

    synsets 中定义的标签:用 #name# 来表示,该标签实际是匹配其对应的 expr 中的正则表达式部分。
    全局定义的标签:用 %name% 来表示,一些全局通用的标签,例如 %triangle% 可以匹配一个类似 “ABC ”的字符串。(具体标签在NLPConstants类中可以查看)

  3. 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 首要工作

  1. 首先在 src/main/resource/xml 自己建一个写模板的xml文件,例如为 lz_template.xml
    (最好根据你们自己写哪一部分的模板命名,例如工程里面有draw_template.xml就是写作图语句的模板)

  2. 在建好的xml定义好一些标签,如下:
    <?xml version="1.0" encoding="UTF-8"?>
    <templates domainId="cn.tsinghuabigdata.lz">
        <tags>
            <tag name="lz"/>
        </tags>
    
    </templates>
  3. 建立一个模板测试类

    在 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 的中位线” 为例:

  1. 在 NLPConstants 查看全局标签,NLPContants 标签部分如下:
    public static final POINT = "Point";
    public static final TRIANGLE = "Triangle";
    public static final CIRCLE = "Circle";
  2. 用标签替换掉里面的具体数学对象,如:

    用 line 替换掉 AE 和 AF 这样具体的数学对象,用 triangle 替换掉 ABC
    “AE 和 AF 分别为三角形 ABC 的中位线” 的 pattern 即为:
    “%line%和%line%分别为三角形%triangle%的中位线”

  3. 考虑句子的多样性,如:

    上面的句子中,“和”可以用“与”或者“,”来替换,不影响表达意思
    “AE 与 AF 分别为三角形 ABC 的中位线”
    “AE , AF 分别为三角形 ABC 的中位线”
    因此,还要考虑句子的各种表达形式,这就要用到前面说的 synset 标签
    我们在 xml 文件中加入自己定义的标签:

    <synset>
        <synset name = "AND" core_term = "和,与符号" expr = "N_I和|,|与"/>
    </synset>

    这样pattern就变为类:
    “%line%#AND#%line%分别为三角形%triangle%的中位线”
    注意:自己定义的标签用# #,NLPConstants里面的全局标签用% %.

  4. 编写模板
  • 先找出这句话里面包含的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类似)。

原文地址:https://www.cnblogs.com/skyke/p/5024622.html