OpenNRE 学习报告:基于深度学习的神经关系抽取模型分析与实际场景调用

摘要

本次作业学习的是基于深度学习的 NRE 模型。NRE 的全称是 Neural Relation Extraction,即神经关系抽取。关系抽取是自然语言处理中的一个重要任务,它的目标是从普通文本中识别两个实体之间的语义关系,并将非结构化文本转化为结构化知识。例如在句子 “Bill Gates founded Microsoft.” 中,可以抽取出三元组:

(Bill Gates, founder of, Microsoft)

本次学习选择 THUNLP 开源的 OpenNRE 项目作为实践对象。OpenNRE 是一个面向神经关系抽取的开源工具包,支持 CNN、PCNN、BERT 等不同编码器,也支持句子级关系抽取和远程监督关系抽取。通过阅读项目代码和官方示例,我主要学习了它的代码结构、模型结构、训练流程、推理调用方法,并设计了一个“企业新闻知识图谱构建”的实际应用场景。


1. 为什么要学习关系抽取?

在自然语言处理中,很多文本都是非结构化的。例如新闻、论文、百科、公告、社交媒体内容等,都包含大量有价值的信息,但机器很难直接理解这些信息。

关系抽取的作用就是把文本中的实体关系识别出来,进一步转化为结构化形式。

例如:

Steve Jobs founded Apple in 1976.

如果已经识别出实体:

Steve Jobs:人物
Apple:公司

那么关系抽取模型需要判断这两个实体之间的关系是:

founder / founded by / 创始人关系

最后可以形成知识图谱中的三元组:

(Steve Jobs, founder_of, Apple)

我的理解是,关系抽取相当于从自然语言中“挖事实”。它不是简单地判断一句话的情感,也不是单纯做文本分类,而是要理解两个实体在句子中的具体语义联系。


2. OpenNRE 项目简介

OpenNRE 是清华大学 THUNLP 开源的神经关系抽取工具包。它的主要目标是为关系抽取任务提供统一、可扩展的模型实现框架。

OpenNRE 的特点主要有以下几个:

  1. 支持多种关系抽取模型;
  2. 支持监督式关系抽取和远程监督关系抽取;
  3. 支持 CNN、PCNN、BERT 等编码器;
  4. 提供统一的数据读取、训练、验证、测试和推理接口;
  5. 提供了一些预训练模型,可以直接用于句子级关系抽取实验。

从学习角度看,OpenNRE 比较适合作为 NRE 入门项目。因为它既包含传统 CNN 关系抽取结构,也包含基于 BERT 的关系抽取结构,可以比较清楚地看到关系抽取模型从传统深度学习方法到预训练语言模型方法的发展。


3. 环境配置与项目安装

3.1 克隆项目

首先从 GitHub 克隆 OpenNRE 项目:

git clone https://github.com/thunlp/OpenNRE.git
cd OpenNRE

如果网络较慢,可以使用浅克隆:

git clone https://github.com/thunlp/OpenNRE.git --depth 1

3.2 安装依赖

进入项目目录后,安装依赖:

pip install -r requirements.txt

然后安装 OpenNRE:

python setup.py install

如果后续需要修改源码并调试,可以使用开发模式安装:

python setup.py develop

3.3 注意事项

OpenNRE 的部分代码依赖 PyTorch、transformers 等深度学习库。由于不同电脑的 CUDA、PyTorch 版本可能不同,因此实际安装时需要根据自己的显卡环境选择合适的 PyTorch 版本。

如果只是做模型调用实验,也可以先在 CPU 上运行;如果要训练 BERT 模型,建议使用 GPU。


4. OpenNRE 代码结构分析

OpenNRE 项目的整体结构如下:

OpenNRE/
├── benchmark/
├── example/
├── opennre/
│   ├── encoder/
│   ├── framework/
│   ├── model/
│   ├── module/
│   ├── tokenization/
│   ├── pretrain.py
│   └── __init__.py
├── pretrain/
├── tests/
├── requirements.txt
├── setup.py
└── README.md

下面对主要目录进行分析。


4.1 benchmark:数据集相关目录

benchmark/ 目录主要用于下载和管理数据集。OpenNRE 支持 Wiki80、NYT10、FewRel 等关系抽取数据集。

这些数据集一般包括:

train.txt      训练集
val.txt        验证集
test.txt       测试集
rel2id.json    关系标签到 id 的映射

其中 rel2id.json 非常重要,因为模型最终输出的是类别编号,必须通过这个文件映射成具体的关系名称。


4.2 example:训练示例目录

example/ 目录中包含多个训练脚本,例如:

train_supervised_cnn.py
train_supervised_bert.py
train_bag_cnn.py
train_bag_bert.py

这些脚本分别对应不同训练方式:

train_supervised_cnn.py   使用 CNN 做监督式句子级关系抽取
train_supervised_bert.py  使用 BERT 做监督式句子级关系抽取
train_bag_cnn.py          使用 CNN/PCNN 做 bag-level 远程监督关系抽取
train_bag_bert.py         使用 BERT 做 bag-level 远程监督关系抽取

从作业学习角度看,example/ 是最容易入门的部分。因为它已经把训练流程写好了,只需要修改参数就可以训练不同模型。


4.3 opennre:核心源码目录

opennre/ 是项目最核心的目录,里面包含模型、编码器、训练框架和基础模块。

它的结构大致如下:

opennre/
├── encoder/
├── framework/
├── model/
├── module/
├── tokenization/
└── pretrain.py

我的理解是,OpenNRE 的整体思想是把一个关系抽取系统拆成三层:

输入数据
  ↓
encoder:把文本编码成向量
  ↓
model:根据向量预测关系类别
  ↓
framework:负责训练、验证、测试和推理

这种结构很清晰,也方便替换不同模块。例如可以把 CNNEncoder 换成 BERTEncoder,也可以把普通 softmax 分类器换成 bag-level attention 模型。


5. 模型结构分析

OpenNRE 中的关系抽取模型可以从两个层面理解:

  1. Sentence Encoder:句子编码器;
  2. Relation Model:关系分类模型。

句子编码器负责把输入句子转化成向量,关系分类模型负责根据这个向量判断实体之间的关系。


5.1 输入格式

OpenNRE 的句子级关系抽取输入通常是一个字典:

{
    "text": "Steve Jobs founded Apple in Cupertino.",
    "h": {"pos": (0, 10)},
    "t": {"pos": (19, 24)}
}

其中:

text:输入句子
h:头实体 head entity
t:尾实体 tail entity
pos:实体在句子中的起止位置

例如:

Steve Jobs founded Apple in Cupertino.

实体位置为:

Steve Jobs: (0, 10)
Apple: (19, 24)

模型需要根据句子和实体位置判断两者之间的关系。


5.2 CNNEncoder 结构

CNNEncoder 是传统深度学习关系抽取模型中的典型编码器。它主要由词向量、位置向量、卷积层和池化层组成。

其流程可以表示为:

输入句子
  ↓
Word Embedding
  ↓
Position Embedding
  ↓
向量拼接
  ↓
CNN 卷积
  ↓
激活函数
  ↓
Max Pooling
  ↓
句子向量表示

在关系抽取任务中,只知道词本身是不够的,还需要知道每个词相对于头实体和尾实体的位置。因此 CNNEncoder 通常会使用两个位置向量:

Position Embedding 1:每个词相对于头实体的位置
Position Embedding 2:每个词相对于尾实体的位置

这样模型不仅知道句子中有哪些词,还知道这些词距离两个实体分别有多远。

我的理解是,CNNEncoder 的优点是速度快、结构简单、训练成本低;缺点是对复杂上下文和长距离依赖的理解能力不如 BERT。


5.3 BERTEncoder 结构

BERTEncoder 使用预训练语言模型 BERT 对句子进行编码。相比 CNN,BERT 的优势是能够根据上下文动态表示词语含义。

例如同一个词在不同句子中的含义可能不同,BERT 可以根据上下文生成不同的表示。这一点对关系抽取非常重要,因为实体之间的关系往往依赖上下文表达。

BERTEncoder 的流程如下:

输入句子和实体位置
  ↓
Tokenizer 分词
  ↓
加入实体标记
  ↓
输入 BERT
  ↓
获得上下文向量
  ↓
取 [CLS] 向量或实体位置向量
  ↓
关系分类

其中 [CLS] 可以看作整句话的语义表示。模型可以用 [CLS] 向量预测实体关系。


5.4 BERTEntityEncoder 结构

BERTEntityEncoder 是更适合关系抽取的一种 BERT 编码方式。它不只是使用 [CLS] 向量,而是取头实体和尾实体位置的隐藏状态,然后拼接起来作为最终表示。

流程如下:

输入句子
  ↓
标记头实体和尾实体
  ↓
BERT 编码
  ↓
取 head entity 表示
  ↓
取 tail entity 表示
  ↓
拼接两个实体表示
  ↓
关系分类

可以表示为:

relation_representation = [head_representation ; tail_representation]

我的理解是,BERTEntityEncoder 更关注实体本身,而不是只依赖整句话的 [CLS] 表示。关系抽取的核心问题就是判断两个实体之间的关系,所以直接使用两个实体的上下文表示会更加合理。


5.5 SoftmaxNN 分类模型

在句子级关系抽取中,OpenNRE 使用 SoftmaxNN 作为常见分类模型。

整体流程如下:

句子输入
  ↓
Sentence Encoder
  ↓
Dropout
  ↓
Linear 全连接层
  ↓
Softmax
  ↓
输出关系类别和置信度

假设关系类别共有 N 类,那么模型最后的线性层会输出 N 个分数,再通过 softmax 转化成概率分布。

例如:

founder_of: 0.87
place_of_birth: 0.03
employer: 0.05
others: 0.05

模型会选择概率最高的关系作为最终预测结果。


5.6 BagAttention 远程监督模型

除了句子级关系抽取,OpenNRE 还支持 bag-level 关系抽取。

在远程监督关系抽取中,一个实体对可能对应多条句子。例如:

句子1:Steve Jobs founded Apple.
句子2:Apple was started by Steve Jobs.
句子3:Steve Jobs returned to Apple in 1997.

这些句子都包含同一个实体对:

(Steve Jobs, Apple)

它们可以组成一个 bag。

BagAttention 的思想是:不是每个句子都同样重要,模型应该自动给更有用的句子更高权重,给噪声句子更低权重。

流程如下:

同一实体对的多条句子
  ↓
分别进行句子编码
  ↓
Attention 计算每个句子的权重
  ↓
加权求和得到 bag 表示
  ↓
预测实体对关系

我的理解是,BagAttention 的核心作用是降低远程监督数据中的噪声。因为远程监督数据往往不是人工逐句标注的,里面可能有错误标注,attention 可以帮助模型从多个句子中挑出更可靠的证据。


6. 训练流程分析

以监督式 BERT 关系抽取为例,OpenNRE 的训练流程可以概括为:

读取数据集
  ↓
读取 rel2id 标签映射
  ↓
构建 BERTEncoder 或 BERTEntityEncoder
  ↓
构建 SoftmaxNN 分类模型
  ↓
构建 SentenceRE 训练框架
  ↓
设置 batch_size、learning_rate、epoch 等参数
  ↓
训练模型
  ↓
在验证集上保存最优模型
  ↓
在测试集上评估效果

示例训练命令:

python example/train_supervised_bert.py \
    --pretrain_path bert-base-uncased \
    --dataset wiki80

如果使用 bag-level 模型,可以运行类似命令:

python example/train_bag_cnn.py \
    --metric auc \
    --dataset nyt10m \
    --batch_size 160 \
    --lr 0.1 \
    --weight_decay 1e-5 \
    --max_epoch 100 \
    --max_length 128 \
    --seed 42 \
    --encoder pcnn \
    --aggr att

其中几个重要参数含义如下:

--dataset:使用的数据集
--batch_size:每次训练输入的样本数量
--lr:学习率
--max_epoch:训练轮数
--max_length:句子最大长度
--encoder:使用的编码器
--aggr:bag-level 场景下的聚合方法

7. 推理调用实验

7.1 单句关系抽取代码

import opennre

# 加载预训练模型
model = opennre.get_model("wiki80_bertentity_softmax")

# 输入文本
text = "Steve Jobs founded Apple in Cupertino."

# 构造输入
item = {
    "text": text,
    "h": {"pos": (0, 10)},    # Steve Jobs
    "t": {"pos": (19, 24)}    # Apple
}

# 模型推理
relation, score = model.infer(item)

print("输入句子:", text)
print("头实体:", text[item["h"]["pos"][0]:item["h"]["pos"][1]])
print("尾实体:", text[item["t"]["pos"][0]:item["t"]["pos"][1]])
print("预测关系:", relation)
print("置信度:", score)

7.2 预期输出

实际输出会受到模型、数据集和文本表达影响,可能输出类似结果:

输入句子: Steve Jobs founded Apple in Cupertino.
头实体: Steve Jobs
尾实体: Apple
预测关系: founder
置信度: 0.87

这里需要注意,OpenNRE 官方提供的预训练模型主要面向英文数据集,因此英文句子的效果会更稳定。如果要处理中文文本,通常需要换成中文预训练语言模型,并使用中文关系抽取数据进行重新训练或微调。


8. 实际场景设计:企业新闻知识图谱构建

8.1 场景背景

在金融、商业分析和舆情监控中,每天都会产生大量企业新闻。例如:

Microsoft acquired LinkedIn in 2016.
Elon Musk founded SpaceX in 2002.
Apple is headquartered in Cupertino.

这些新闻中包含大量实体关系:

公司 — 收购 — 公司
人物 — 创立 — 公司
公司 — 总部位于 — 地点

如果人工整理这些信息,效率很低。因此可以使用 OpenNRE 自动抽取关系,并构建企业知识图谱。


8.2 系统目标

设计一个“企业新闻关系抽取系统”,输入一条新闻句子和两个实体,输出它们之间的关系。

输入:

{
  "text": "Elon Musk founded SpaceX in 2002.",
  "head": "Elon Musk",
  "tail": "SpaceX"
}

输出:

{
  "head": "Elon Musk",
  "relation": "founder",
  "tail": "SpaceX",
  "score": 0.91
}

8.3 系统流程

整个系统可以分为五个步骤:

新闻文本输入
  ↓
命名实体识别:识别人名、公司名、地点名
  ↓
实体对生成:组合候选实体对
  ↓
OpenNRE 关系抽取:预测实体之间的关系
  ↓
三元组存储:保存到知识图谱或数据库

其中 OpenNRE 负责最核心的关系判断部分。


8.4 实际调用代码设计

import opennre


def find_entity_pos(text, entity):
    """
    查找实体在文本中的起止位置
    """
    start = text.find(entity)
    if start == -1:
        raise ValueError(f"实体 {entity} 不在文本中")
    end = start + len(entity)
    return (start, end)


def extract_relation(model, text, head_entity, tail_entity, threshold=0.5):
    """
    调用 OpenNRE 进行关系抽取
    """
    item = {
        "text": text,
        "h": {"pos": find_entity_pos(text, head_entity)},
        "t": {"pos": find_entity_pos(text, tail_entity)}
    }

    relation, score = model.infer(item)

    result = {
        "head": head_entity,
        "relation": relation,
        "tail": tail_entity,
        "score": float(score)
    }

    if score < threshold:
        result["relation"] = "置信度较低,暂不采纳"

    return result


if __name__ == "__main__":
    # 加载模型
    model = opennre.get_model("wiki80_bertentity_softmax")

    # 示例新闻
    text = "Elon Musk founded SpaceX in 2002."

    # 输入实体
    head_entity = "Elon Musk"
    tail_entity = "SpaceX"

    # 抽取关系
    result = extract_relation(
        model=model,
        text=text,
        head_entity=head_entity,
        tail_entity=tail_entity,
        threshold=0.5
    )

    print(result)

8.5 调用结果示例

运行后可能得到如下结果:

{
    "head": "Elon Musk",
    "relation": "founder",
    "tail": "SpaceX",
    "score": 0.91
}

这说明模型判断 “Elon Musk” 和 “SpaceX” 之间存在 founder 类型关系。


9. 如果部署成在线接口

如果需要把这个模型部署成在线系统,可以使用 FastAPI 封装接口。

9.1 FastAPI 服务代码

from fastapi import FastAPI
from pydantic import BaseModel
import opennre

app = FastAPI()

model = opennre.get_model("wiki80_bertentity_softmax")


class RERequest(BaseModel):
    text: str
    head: str
    tail: str


def find_entity_pos(text, entity):
    start = text.find(entity)
    if start == -1:
        raise ValueError(f"实体 {entity} 不在文本中")
    return (start, start + len(entity))


@app.post("/relation_extract")
def relation_extract(req: RERequest):
    item = {
        "text": req.text,
        "h": {"pos": find_entity_pos(req.text, req.head)},
        "t": {"pos": find_entity_pos(req.text, req.tail)}
    }

    relation, score = model.infer(item)

    return {
        "head": req.head,
        "relation": relation,
        "tail": req.tail,
        "score": float(score)
    }

9.2 接口调用示例

请求:

{
  "text": "Elon Musk founded SpaceX in 2002.",
  "head": "Elon Musk",
  "tail": "SpaceX"
}

响应:

{
  "head": "Elon Musk",
  "relation": "founder",
  "tail": "SpaceX",
  "score": 0.91
}

这样就可以把 OpenNRE 作为一个后端关系抽取服务,供知识图谱系统、搜索系统或问答系统调用。


10. 我的理解与学习收获

通过本次学习,我对神经关系抽取有了更清楚的认识。

以前我容易把关系抽取理解成普通的文本分类,但实际学习后发现,两者有明显区别。普通文本分类只需要判断整句话属于哪个类别,而关系抽取必须明确给出两个实体,并判断这两个实体之间的关系。

OpenNRE 的模型结构也让我理解了一个深度学习关系抽取系统的基本组成:

实体位置标注
  ↓
句子编码器
  ↓
关系分类器
  ↓
关系标签输出

其中,CNN 模型更轻量,适合快速实验;BERT 模型语义理解能力更强,更适合复杂文本;BagAttention 模型则适合远程监督数据,可以从多个句子中综合判断实体关系。

我认为 OpenNRE 最大的优点是模块化设计清晰。它把 encoder、model、framework 分开,便于理解和扩展。对于初学者,可以直接加载预训练模型做推理;对于进一步研究,可以替换编码器、修改分类器,或者用自己的数据集重新训练。


11. 项目优点与不足

11.1 优点

第一,OpenNRE 代码结构比较清晰。编码器、模型和训练框架分开,便于阅读。

第二,支持多种关系抽取方式。既支持句子级监督式关系抽取,也支持 bag-level 远程监督关系抽取。

第三,支持 BERT 等预训练语言模型。相比传统 CNN,BERT 能更好地理解上下文语义。

第四,提供了预训练模型和示例脚本,方便快速上手。


11.2 不足

第一,项目对环境版本有一定要求。不同 PyTorch、transformers 版本可能会导致运行问题。

第二,官方预训练模型主要面向英文数据集。如果要应用到中文场景,需要额外准备中文数据和中文预训练模型。

第三,关系抽取效果依赖实体识别。如果实体位置给错,后面的关系分类也会受到影响。

第四,真实业务场景中的关系类型往往更加复杂,不能完全依赖通用数据集,需要根据具体任务重新定义关系集合并训练模型。


12. 总结

本次作业围绕 OpenNRE 学习了基于深度学习的神经关系抽取模型。OpenNRE 将关系抽取任务封装为统一框架,主要包含数据处理、句子编码、关系分类、模型训练和推理调用等部分。

从模型结构看,CNNEncoder 通过词向量和位置向量建模实体关系,结构简单、速度较快;BERTEncoder 利用预训练语言模型获得上下文语义表示,效果通常更好;BERTEntityEncoder 进一步突出头实体和尾实体的表示,更符合关系抽取任务特点;BagAttention 则适合远程监督场景,可以减少噪声句子的影响。

在实际应用方面,OpenNRE 可以用于企业新闻知识图谱构建。系统可以先识别新闻中的实体,再调用 OpenNRE 判断实体之间的关系,最后生成结构化三元组并存入数据库或知识图谱。

整体来看,OpenNRE 不只是一个模型库,更是一个完整的关系抽取实验框架。通过本次学习,我理解了 NRE 的基本任务形式、模型结构和实际调用流程,也认识到关系抽取在知识图谱、问答系统、搜索引擎和商业分析中的重要作用。


参考资料

[1] THUNLP. OpenNRE: An Open-Source Package for Neural Relation Extraction.

[2] Han, Xu, Gao, Tianyu, Yao, Yuan, Ye, Deming, Liu, Zhiyuan, Sun, Maosong. OpenNRE: An Open and Extensible Toolkit for Neural Relation Extraction. EMNLP-IJCNLP System Demonstrations, 2019.

[3] OpenNRE GitHub Repository: https://github.com/thunlp/OpenNRE

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐