欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

DeepDive教程第一部分

程序员文章站 2022-05-30 22:46:04
...

本文通过一个demo,学习DeepDive教程

本文中我们的目标是将使用非结构化的输入,在关系数据库中存储提取出的结果,并且加上deepdive对每个提取结果的预测置信度

也就是写一个DeepDive应用根据一个特定的模式来提取mentions与相邻实体和属性之间的关系。

这就是关系抽取任务。我们希望从新闻文章中抽取是夫妻关系的两个人的mentions.

高级的步骤有:

  • 数据处理。首先加载原始语料,添加NLP标注,提取候选关系对,以及一个mention相互间关系的稀疏特征
  • 对数据和规则的远程监督。接下来使用不同的方法来为数据提供监督,便于机器学习模型来学习权重
  • 学习和推断。指定模型高层次的配置
  • 误差分析和调试

0.准备

0.1 声明预测什么

应该告诉DeepDive我们想要预测的随机变量,保存在DDlog这一语言编写的文件中

%%file app.ddlog
# 本程序的目的是预测给定的两个mention是否为夫妻关系
has_spouse?(
    p1_id text,
    p2_id text
).

这回生成一个has_spouse表

0.2 设定数据库

接下来,DeepDive会把所有的数据:输入、中间结果、输出都保存在关系数据库中(一般为Postgresql)

1.数据处理

本节,将会产生统计学习问题的传统输入:候选夫妻关系,这些关系通过一系列特征集合表示。我们要预测候选夫妻关系是否为真实关系

有下面四个步骤:

  1. 加载原始输入数据
  2. 添加NLP标注
  3. 提取候选关系实体对
  4. 提取每个候选对的特征

1.1 加载原始输入数据

第一个任务是下载和加载语料库,并放到数据库中的articles表

最好保存每篇文章的id和内容。通过articles表中定义schema来实现

%%file -a app.ddlog

## Input Data #################################################################
articles(
    id      text,
    content text
).

DeepDive使用一个脚本把原始语料库转换为一个名字转换的tsj文件中。
这个脚本会读取JSON格式的原始语料,然后分割成id和context两个部分,保存成TSJ形式

使用这个脚本会将原始语料库转换为数据库中的article表。由两列组成:id和content

加载到数据库中之后,可以查看数据库的数据

!deepdive query '|10 ?- articles(id, content).'

1.2 添加NLP标注

接下来使用Stanford的CoreNLP来为content添加有用的标注和结构。

这些步骤包括把文章分为句子以及单词。此外还有单词原型(lemma)、词性(POS)、实体识别(NER),句子依存关系等标注

这些在app.ddlog中定义输出的模式

%%file -a app.ddlog

## NLP markup #################################################################
sentences(
    doc_id         text,
    sentence_index int,
    tokens         json,
    lemmas         json,
    pos_tags       json,
    ner_tags       json,
    doc_offsets    json,
    dep_types      json,
    dep_tokens     json
).

这里将会生成一个sentence表

接下来定义一个DDlog函数输入doc_id 和 content ,以上面的格式输出每一行.

这个函数作为一个脚本(nlp_markup.sh)来对articles中每一行来进行NLP标注,并且把结果附加到行的后面。

在执行这个脚本之前,需要启动CoreNLP服务器。

执行完之后,查看添加NLP标注后的一个样本。
DeepDive教程第一部分
DeepDive教程第一部分

1.3 提取候选关系集

抽取所有的people

首先定义person表的schema

%%file -a app.ddlog

## Candidate mapping ##########################################################
person_mention(
    mention_id     text,
    mention_text   text,
    doc_id         text,
    sentence_index int,
    begin_index    int,
    end_index      int
).

把每一个person保存成一行,其中包含出现文档的id,出现句子的id,在句子的起始index和结束index。
这个功能通过map_person_mention.py文件实现。它会把标记为PERSON标签的连续token看做是一个实体PERSON(利用CoreNLP的工具包),保存成数据库中的一行

%%file udf/map_person_mention.py
#!/usr/bin/env python
from deepdive import *

@tsj_extractor
@returns(lambda
        mention_id       = "text",
        mention_text     = "text",
        doc_id           = "text",
        sentence_index   = "int",
        begin_index      = "int",
        end_index        = "int",
    :[])
def extract(
        doc_id         = "text",
        sentence_index = "int",
        tokens         = "text[]",
        ner_tags       = "text[]",
    ):
    """
    Finds phrases that are continuous words tagged with PERSON.
    """
    num_tokens = len(ner_tags)
    # find all first indexes of series of tokens tagged as PERSON
    first_indexes = (i for i in xrange(num_tokens) if ner_tags[i] == "PERSON" and (i == 0 or ner_tags[i-1] != "PERSON"))
    for begin_index in first_indexes:
        # find the end of the PERSON phrase (consecutive tokens tagged as PERSON)
        end_index = begin_index + 1
        while end_index < num_tokens and ner_tags[end_index] == "PERSON":
            end_index += 1
        end_index -= 1
        # generate a mention identifier
        mention_id = "%s_%d_%d_%d" % (doc_id, sentence_index, begin_index, end_index)
        mention_text = " ".join(map(lambda i: tokens[i], xrange(begin_index, end_index + 1)))
        # Output a tuple for each PERSON phrase
        yield [
            mention_id,
            mention_text,
            doc_id,
            sentence_index,
            begin_index,
            end_index,
        ]

上面的函数会把句子中所有NER标记为PERSON标签的单词以指定的格式保存成数据库中的一行。

注意上面的函数必须是生成器

最后对数据库中的sentence表使用上面的函数并且添加到person_mention表中去。

然后可以在person_mention表中查看PERSON

%%bash
deepdive query '
    name, doc, sentence, begin, end | 20
    ?- person_mention(p_id, name, doc, sentence, begin, end).
'

DeepDive教程第一部分

抽取候选配偶关系(pairs of people)

接下来在所有出现少于5个人名句子中,抽取那些有共同出现关系的人名对。并把这些人名对看做是配偶关系的候选集。

首先定义spouse_candidate表的schema

%%file -a app.ddlog

spouse_candidate(
    p1_id   text,
    p1_name text,
    p2_id   text,
    p2_name text
).

这一步不使用python脚本,而是使用DDlog操作来完成。创建一个计算PERSON数目的表,通过过滤条件连接起来。

%%file -a app.ddlog

num_people(doc_id, sentence_index, COUNT(p)) :-
    person_mention(p, _, doc_id, sentence_index, _, _).

spouse_candidate(p1, p1_name, p2, p2_name) :-
    num_people(same_doc, same_sentence, num_p),
    person_mention(p1, p1_name, same_doc, same_sentence, p1_begin, _),
    person_mention(p2, p2_name, same_doc, same_sentence, p2_begin, _),
    num_p < 5,
    p1 < p2,
    p1_name != p2_name,
    p1_begin != p2_begin.

接下来执行上面的DDLog来生成spouse_candidate表

查询spouse_candidate表

%%bash
deepdive query '
    name1, name2, doc, sentence | 20
    ?- spouse_candidate(p1, name1, p2, name2),
       person_mention(p1, _, doc, sentence, _, _).
'

DeepDive教程第一部分

1.4 提取每个候选点的特征

首先定义每个候选点的特征集合。生成一个spouse_feature表

%%file -a app.ddlog

## Feature Extraction #########################################################
 
# Feature extraction (using DDLIB via a UDF) at the relation level
spouse_feature(
    p1_id   text,
    p2_id   text,
    feature text
).

目的是用特征集合重新表示每一对spouse集合。特征可以用来表示mention的主要方面。然后让机器学习方法学习特征和结果() 之间的关联程度。

这里使用one-hot向量表示来学习对应的关系。可以想象把候选spouse存储在长度为L的数组中,只有当前的关系为1,其余为0。

DeepDive包括一个自动的特征提取库DDLib。这里使用其中的get_generic_features_relation函数来学习特征。

%%file udf/extract_spouse_features.py
#!/usr/bin/env python
from deepdive import *
import ddlib

@tsj_extractor
@returns(lambda
        p1_id   = "text",
        p2_id   = "text",
        feature = "text",
    :[])
def extract(
        p1_id          = "text",
        p2_id          = "text",
        p1_begin_index = "int",
        p1_end_index   = "int",
        p2_begin_index = "int",
        p2_end_index   = "int",
        doc_id         = "text",
        sent_index     = "int",
        tokens         = "text[]",
        lemmas         = "text[]",
        pos_tags       = "text[]",
        ner_tags       = "text[]",
        dep_types      = "text[]",
        dep_parents    = "int[]",
    ):
    """
    Uses DDLIB to generate features for the spouse relation.
    """
    # Create a DDLIB sentence object, which is just a list of DDLIB Word objects
    sent = []
    for i,t in enumerate(tokens):
        sent.append(ddlib.Word(
            begin_char_offset=None,
            end_char_offset=None,
            word=t,
            lemma=lemmas[i],
            pos=pos_tags[i],
            ner=ner_tags[i],
            dep_par=dep_parents[i] - 1,  # Note that as stored from CoreNLP 0 is ROOT, but for DDLIB -1 is ROOT
            dep_label=dep_types[i]))

    # Create DDLIB Spans for the two person mentions
    p1_span = ddlib.Span(begin_word_id=p1_begin_index, length=(p1_end_index-p1_begin_index+1))
    p2_span = ddlib.Span(begin_word_id=p2_begin_index, length=(p2_end_index-p2_begin_index+1))

    # Generate the generic features using DDLIB
    for feature in ddlib.get_generic_features_relation(sent, p1_span, p2_span):
        yield [p1_id, p2_id, feature]

需要联合person_mentionsentences 表来生成spouse_feature

%%file -a app.ddlog

function extract_spouse_features over (
        p1_id          text,
        p2_id          text,
        p1_begin_index int,
        p1_end_index   int,
        p2_begin_index int,
        p2_end_index   int,
        doc_id         text,
        sent_index     int,
        tokens         text[],
        lemmas         text[],
        pos_tags       text[],
        ner_tags       text[],
        dep_types      text[],
        dep_tokens     int[]
    ) returns rows like spouse_feature
    implementation "udf/extract_spouse_features.py" handles tsj lines.

spouse_feature += extract_spouse_features(
    p1_id, p2_id, p1_begin_index, p1_end_index, p2_begin_index, p2_end_index,
    doc_id, sent_index, tokens, lemmas, pos_tags, ner_tags, dep_types, dep_tokens
) :-
    person_mention(p1_id, _, doc_id, sent_index, p1_begin_index, p1_end_index),
    person_mention(p2_id, _, doc_id, sent_index, p2_begin_index, p2_end_index),
    sentences(doc_id, sent_index, tokens, lemmas, pos_tags, ner_tags, _, dep_types, dep_tokens).

现在查看spouse_feature中的特征。
DeepDive教程第一部分
现在我们有了输入机器学习问题的标准输入。这就是特征的集合,用特征来进行分类(是否是对偶关系)。

然而我们并没有带标签的数据来让机器学习模型进行学习,这样大部分的机器学习问题就没法用。

在DeepDive中使用远程监督(distant supervision)的思想。
主要是从混合有次要的数据集和其他启发式规则中来产生一个带有噪声的label集合。