电网知识图谱项目总结python代码实现RDF三元组自动化标注

Posted 白鳯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了电网知识图谱项目总结python代码实现RDF三元组自动化标注相关的知识,希望对你有一定的参考价值。

电网知识图谱项目总结(1)python代码实现RDF三元组自动化标注

文章目录

简介

本次项目是电网知识图谱相关的,我们的主要任务是三元组的标注知识图谱的构建,本篇讲述Python代码标注三元组的具体实现,相关知识图谱的构建在后期的内容中再进行总结。

总共有几百个文档(包括几百个预案),刚开始时手动标注一个文档需要两三个小时,长一点的文档得三个多小时,加上三元组关系之多,标注一两个文档头脑一片混乱,盯都盯不住了,心态直接炸裂!在手动标注了一个文档后,我尝试用代码标注,写代码的过程也是特别煎熬,毕竟每一种类型的三元组都得用代码实现,不过写多了就觉得简单了,都是一样的套路。花了一天半时间后,写完了近500行代码,若是足够规范的文档,可保证100%的标注准确率,大部分文档的标注准确率也达到了90%以上。在写代码时为某些不好处理的三元组特意做了模糊标注,所以代码标注后的三元组只需要对照文档内容核对检查修改即可,特别方便,几乎不需要新增。原来一个文档两三个小时的任务最后只需几分钟即可完成,极大地提高了效率!

文档内容

最开始的数据格式是.docx文档,每个word文档中包含了针对特定千伏变电站停电时的具体调度,下面以一个预案的具体结构和内容来作为示例。

侯家塘变35千伏其侯线停电风险预案为例,具体内容组织结构如下


  • 侯家塘变35千伏其侯线停电风险预案

    • 一、事故情况及影响

      • 得地调告:侯家塘变全所失电。
      • 侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。
      • 两回全停重要用户:江苏省金陵监狱
      • 一回停电的双电源用户:南京自来水公司汤山增压站
    • 二、处理过程

      1. 得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源

      2. 配调:

        • (1)恢复所用电

          侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关

          汤山变:侯家塘线233开关定切保护停用过流时间由1”改为1.5”

          侯家塘变:侯家塘线135开关保护全部停用合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)

          悦民变:百镇#1线118开关定切保护停用过流时间由1”改为1.5”

          雨花急修班:合上#6260开关

          侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)

        • 恢复重要用户供电

          侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)

      3. 汇报地调。

      4. 得地调通知,侯家塘变1号、2号主变已恢复供电,事故方式自行恢复。


其中标黄的内容为每个文档中的固定大纲,部分内容(如变电站等)可能不同,但模式相同,标红的是一些需要标注的部分三元组关键字。

文档内容截图如下

RDF规范

RDF三元组规范如下,共有二十多种类型,要求是将这些三元组标注出并保存在Excel表格中。

侯家塘变35千伏其侯线停电风险预案为例,该预案中包含的所有三元组如下:

35千伏侯家塘变全停事故预案  事故情况    得地调告:侯家塘变全所失电。
35千伏侯家塘变全停事故预案  事故原因    侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。
35千伏侯家塘变全停事故预案  事故影响    两回全停的重要用户有:江苏省金陵监狱
35千伏侯家塘变全停事故预案  事故影响    一回停电的双电源用户有:南京自来水公司汤山增压站
35千伏侯家塘变全停事故预案  处理过程    得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源


得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源  下一步骤    侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关
侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关    下一步骤    汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”
汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”   下一步骤    侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)     下一步骤    悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”
悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”  下一步骤    雨花急修班:合上#6260开关
雨花急修班:合上#6260开关   下一步骤    侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)    下一步骤    侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)
侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)     下一步骤    汇报地调
回报地调    下一步骤    得地调通知,侯家塘变1号、2号主变已恢复供电,事故方式自行恢复。


侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变1号接地变
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变2号接地变
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变1号所用电源
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变2号所用电源


得地调告:侯家塘变全所失电。    两回全停    江苏省金陵监狱
江苏省金陵监狱    属性    重要用户
得地调告:侯家塘变全所失电。    一回停电    南京自来水公司汤山增压站
南京自来水公司汤山增压站    属性    双电源用户
南京自来水公司汤山增压站    属性    重要用户


得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源    移出    侯家塘变10千伏负荷
得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源    保证    悦民变所用变电源


侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关    属性    恢复所供电
汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”    属性    恢复所供电
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)    属性    恢复所供电
悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”    属性    恢复所供电
雨花急修班:合上#6260开关    属性    恢复所供电
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)     属性    恢复所供电


侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关    恢复所供电    侯家塘变
汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”    恢复所供电    侯家塘变
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)    恢复所供电    侯家塘变
悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”    恢复所供电    侯家塘变
雨花急修班:合上#6260开关    恢复所供电    侯家塘变
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)     恢复所供电    侯家塘变


侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)    操作分支    侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)    操作分支    汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关    等价操作    汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变

  
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)    操作分支    侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)    操作分支    悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关    等价操作    悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电


侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关    拉开    侯家塘变1号主变101开关
侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关    拉开    侯家塘变2号主变102开关
侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关    拉开    侯家塘变10千伏出线开关
汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”   定切保护停用    汤山变侯家塘线233开关
汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”   过流时间由1”改为1.5”    汤山变侯家塘线233开关
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)     保护全部停用    侯家塘变侯家塘线135开关
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)     合上    侯家塘变侯家塘线135开关
悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”   定切保护停用    悦民变百镇#1线118开关
悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”   过流时间由1”改为1.5”    悦民变百镇#1线118开关
雨花急修班:合上#6260开关   合上    #6260开关
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)    保护全部停用    侯家塘变官塘线235开关
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)    合上    侯家塘变官塘线235开关


侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)     合上    龙尚线134开关
侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)    属性    恢复重要用户供电
侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)     恢复重要用户供电    江苏省金陵监狱
侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)     恢复重要用户供电    南京自来水公司汤山增压站供电


侯家塘变1号主变101开关    属于    侯家塘变
侯家塘变2号主变102开关    属于    侯家塘变
汤山变侯家塘线233开关     属于    汤山变
侯家塘变侯家塘线135开关    属于    侯家塘变
侯家塘变10千伏Ⅰ段母线    属于    侯家塘变
侯家塘变1号接地变    属于    侯家塘变
悦民变百镇#1线118开关    属于    悦民变
侯家塘变官塘线235开关    属于    侯家塘变
侯家塘变龙尚线134开关    属于    侯家塘变
雨花急修班#6260开关    属于    雨花急修班

标注思路

总体来说整个过程可分为三步:

  1. 读取docx文件(import docx
  2. 标注(对str字符的处理)
  3. 写入xlsx文件(import openpyxl

在读取内容后,为了处理字符串的方便,将每段内容句首和句末的空格、制表符、标点符号等去除。

其中最麻烦的是标注代码的编写,本质就是对字符串的一些处理技巧,这个只要掌握了字符串的分割和特定字符的位置查找其实也不难实现。

在三元组分类时要考虑以下两个问题:

  • 哪几种类型的三元组放在一起处理比较好?
  • 哪些内容段包含哪几种类型的三元组?

首先根据三元组的分布对内容进行大致划分,得到固定标题下的内容,这样方便后面特定三元组的处理,缩小查找范围。如layer1layer2层下的内容,datas是所有内容,datas1二、处理过程:下的内容。在处理特定三元组的时候,看它出现在哪些特定的内容中,或者某几条内容中包含几种类型的三元组。根据大致位置和三元组关键词找到包含三元组内容的具体位置,然后根据关键字、标点符号、以及三元组特征对内容进行拆分、拼接和添加,从而得到最后的结果。

下面举两个例子方便大家更好地了解:


内容一:

一、事故情况及影响
得地调告:侯家塘变全所失电
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电
两回全停重要用户有:江苏省金陵监狱。
一回停电的双电源用户有:南京自来水公司汤山增压站。

对于上面这段内容,由于它是连续的,先不考虑每个句子中包含的其他三元组,我们发现事故情况事故原因事故影响这几个三元组放在一起处理比较好,在得到标题后依次写入即可。对应代码中的函数为def step_1(datas, name, layer1, layer2)

35千伏侯家塘变全停事故预案  事故情况    得地调告:侯家塘变全所失电。
35千伏侯家塘变全停事故预案  事故原因    侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。
35千伏侯家塘变全停事故预案  事故影响    两回全停的重要用户有:江苏省金陵监狱
35千伏侯家塘变全停事故预案  事故影响    一回停电的双电源用户有:南京自来水公司汤山增压站

内容二:

侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。

首先是事故原因三元组,这个只要得到标题就可以构成三元组。(这一条我是按照内容一中程相关的三元组一起处理的)

35千伏侯家塘变全停事故预案	事故原因	侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。

其次是失电(主)失电(被),首先定位接地变对前段话中**“1号”“2号”进行检测根据顿号拆分,得到“失电(主)“三元组,之后定位所用电源对后半句话同样进行字符定位拆分,得到”失电(被)“**三元组。对应代码中的函数为def step_3(datas, layer1)

侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变1号接地变
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变2号接地变
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变1号所用电源
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变2号所用电源

代码结构

其他的三元组处理方式与此类似,下面是整个代码的函数结构图,详解介绍了各个函数的功能和所要处理的三元组。

详细代码

import docx
import xlwt
import openpyxl
import os

switches_rdf = []

def readData(path):
    file = docx.Document(path)
    datas = []
    for p in file.paragraphs:
        cur = str(p.text.strip())
        cur.replace('\\n', '').replace('\\r', '').replace(' ', '')
        if cur != "":
            datas.append(cur)
    return datas

def printRDF(rdfs):
    if rdfs is None:
        return
    for rdf in rdfs:
        print(rdf)

# RDF 三元组使用 tuple 存储  (R, D, F)
# step_1  事故情况、事故原因、事故影响、处理过程
def step_1(datas, name, layer1, layer2):
    rdf1 = []
    for i in range(layer1 + 1, layer2):
        data = datas[i]
        if data.count("得地调告"):
            rdf1.append((name, "事故情况", data))
        elif data.count("导致"):
            rdf1.append((name, "事故原因", data))
        elif data.count("用户"):
            if data[-1]  != "无":
                rdf1.append((name, "事故影响", data))
    rdf1.append((name, "处理过程", datas[layer2 + 1][2:]))
    return rdf1


# setp_2  (?, 下一步骤, ?)
def step_2(datas, layer2):
    cycle_data = []
    for i in range(layer2 + 1, len(datas)):
        data = datas[i]
        if data.count("1、得地调要求:"):
            cycle_data.append(data[2:])
        elif data.count("2、配调:") or data.count("(1)恢复所用电") or data.count("(2)恢复重要用户供电"):
            pass
        elif data.count("3、汇报地调"):
            cycle_data.append(data[2:])
        elif data.count("4、得地调通知"):
            cycle_data.append(data[2:])
        else:
            cycle_data.append(data)
    # 使用循环加入三元组
    rdf2 = []
    for i in range(0, len(cycle_data) - 1):
        rdf2.append((cycle_data[i], "下一步骤", cycle_data[i + 1]))
    return rdf2


# step3  失电(主),失电(被)
def step_3(datas, layer1):
    data = datas[layer1 + 2]
    head_id = data.find("1号")
    head_name = data[:head_id]
    # 使用 , 划分前后部分
    data_sp = data.split(',')
    rdf3 = []
    for i in range(1, 5):
        if data_sp[0].count(str(i) + "号"):
            rdf3.append((data, "失电(主)", head_name + str(i) + "号接地变"))
            # add switches_rdf
            switches_rdf.append((head_name + str(i) + "号接地变", "属于", head_name))
    for i in range(1, 5):
        if data_sp[1].count(str(i) + "号"):
            rdf3.append((data, "失电(被)", head_name + str(i) + "号所用电源"))
    return rdf3


# step_4  两回全停、一回停电, 重要度,属性
def step_4(datas, layer1, layer2):
    s1 = datas[layer1 + 1]
    fst_s, sec_s = "", ""
    for i in range(layer1 + 1, layer2):
        if datas[i].find("两回全停") == 0:
            fst_s = datas[i]
        elif datas[i].find("一回停电") == 0:
            sec_s = datas[i]
    # 找出两回全停的地点
    fst_s1 = fst_s.split(":")
    fst_s_list = fst_s1[1].split("、")
    # 找出一回停电的地点
    sec_s1 = sec_s.split(":")
    sec_s_list = sec_s1[1].split("、")

    rdf4 = []
    # 加入 两回全停、一回停电 三元组
    for s in fst_s_list:
        if s != "无":
            rdf4.append((s1, "两回全停", s))
    for s in sec_s_list:
        if s != "无":
            rdf4.append((s1, "一回停电", s))

    # 处理属性问题 (双电源、 重要用户)
    if fst_s1[0].count("重要"):
        for s in fst_s_list:
            if s != "无":
                rdf4.append((s, "属性", "重要用户"))
    if sec_s1[0].count("重要"):
        for s in sec_s_list:
            if s != "无":
                rdf4.append((s, "属性", "重要用户"))
    if fst_s1[0].count("双电源"):
        for s in fst_s_list:
            if s != "无":
                rdf4.append((s, "属性", "双电源用户"))
    if sec_s1[0].count("双电源"):
        for s in sec_s_list:
            if s != "无":
                rdf4.append((s, "属性", "双电源用户"))

    return rdf4


# step_5 (?, 移出, ?)  , (?, 保证, ?)
def step_5(datas, layer2):
    data = datas[layer2 + 1][2:]
    parts = data[data.find(":") + 1:].split(",")
    id1 = parts[0].find("移出")
    id2 = parts[1].find("保证")
    rdf5 = []
    rdf5.append((data, "移出", parts[0][:id1]))
    rdf5.append((data, "保证", parts[1][id2 + 2:]))
    return rdf5


# step_6 (?, 属性, 恢复所供电)  (?, 恢复所供电, ?)
def step_6(datas, layer1, layer2):
    id1, id2 = 0, 0
    for i in range(layer2 + 1, len(datas))以上是关于电网知识图谱项目总结python代码实现RDF三元组自动化标注的主要内容,如果未能解决你的问题,请参考以下文章

电网知识图谱项目总结python代码实现RDF三元组自动化标注

电网知识图谱项目总结从局部文档RDF到全局知识图谱构建

电网知识图谱项目总结从局部文档RDF到全局知识图谱构建

电网知识图谱项目总结从局部文档RDF到全局知识图谱构建

知识图谱表示

知识图谱基础组件RDF、RDFS、OWL