基于深度学习的NER模型学习报告与OpenNRE场景调用设计
目录
- 第一部分:深度学习NER模型学习报告
- [1. NER技术概述与发展历程](#1. NER技术概述与发展历程)
- [2. 经典深度学习NER模型结构详解](#2. 经典深度学习NER模型结构详解)
- [3. OpenNRE项目深度解析](#3. OpenNRE项目深度解析)
- 第二部分:实际场景调用设计
- [4. 场景概述:智能金融风控动态知识图谱系统](#4. 场景概述:智能金融风控动态知识图谱系统)
- [5. 系统架构设计](#5. 系统架构设计)
- [6. 核心模块代码实现](#6. 核心模块代码实现)
- [7. 系统运行流程](#7. 系统运行流程)
- [8. 总结与展望](#8. 总结与展望)
第一部分:深度学习NER模型学习报告
1. NER技术概述与发展历程
1.1 命名实体识别(NER)定义
命名实体识别(Named Entity Recognition, NER)是自然语言处理(NLP)中的一项基础且关键的任务,旨在从非结构化文本中识别出具有特定意义的实体(如人名、地名、组织机构名、时间、数值等),并将其分类到预定义的类别中。NER是信息抽取、知识图谱构建、问答系统、推荐系统等下游应用的核心前置步骤。
NER任务的数学形式化定义:给定一个由 N N N 个token组成的输入序列 X = ( x 1 , x 2 , . . . , x N ) X = (x_1, x_2, ..., x_N) X=(x1,x2,...,xN),NER模型需要输出一个对应的标签序列 Y = ( y 1 , y 2 , . . . , y N ) Y = (y_1, y_2, ..., y_N) Y=(y1,y2,...,yN),其中每个 y i ∈ T y_i \in \mathcal{T} yi∈T, T \mathcal{T} T 为预定义的标签集合(通常采用BIO或BIOES标注方案)。
BIO标注方案说明:
- B (Begin):实体的起始位置
- I (Inside):实体的中间位置
- O (Outside):非实体部分
例如,对于句子 "马云创立了阿里巴巴",标注结果为:马云(B-PER) 创立(O) 了(O) 阿里巴巴(B-ORG)。
1.2 NER技术发展阶段
NER技术的发展可划分为四个主要阶段:
阶段一:基于规则与词典的方法(2000年以前)
- 依赖人工编写的规则模板和领域词典
- 利用正则表达式、模式匹配识别实体
- 优点:无需标注数据,可解释性强
- 缺点:规则维护成本高,泛化能力差,难以覆盖所有情况
阶段二:基于传统机器学习的方法(2000-2013年)
- 将NER视为序列标注任务,采用统计学习模型
- 代表模型:隐马尔可夫模型(HMM)、最大熵模型(ME)、条件随机场(CRF)、支持向量机(SVM)
- 需要人工设计特征模板(如词特征、词性特征、上下文特征等)
- CRF通过建模标签之间的转移概率,有效解决了标注偏置问题
阶段三:基于深度学习的方法(2013-2018年)
- 自动学习特征表示,避免了繁琐的特征工程
- 代表架构:BiLSTM-CRF、CNN-LSTM-CRF
- 引入预训练词向量(Word2Vec、GloVe)作为初始嵌入
- 端到端训练,性能显著提升
阶段四:基于预训练语言模型的方法(2018年至今)
- 利用大规模语料预训练获得深层上下文表示
- 代表模型:BERT-NER、RoBERTa-NER、ERNIE
- 通过微调(Fine-tuning)适配下游NER任务
- 在多个基准数据集上达到SOTA性能
1.3 深度学习NER模型技术路线
输入文本
|
v
+----------------+ +----------------+ +----------------+
| 嵌入层 | -> | 编码层 | -> | 解码层 |
| (Embedding) | | (Encoder) | | (Decoder) |
| | | | | |
| - Word Embedding| | - CNN | | - Softmax |
| - Char Embedding| | - LSTM | | - CRF |
| - Position Embed| | - Transformer | | - Pointer |
| - ELMo/BERT | | - BERT | | |
+----------------+ +----------------+ +----------------+
|
v
标签序列输出
2. 经典深度学习NER模型结构详解
2.1 BiLSTM-CRF模型
BiLSTM-CRF(双向长短期记忆网络-条件随机场)是深度学习时代NER任务的黄金标准架构,自2015年被提出以来,在各类NER场景中表现出色。
2.1.1 模型架构
输入句子: x1, x2, x3, ..., xn
|
v
+--------------------------------------------------------+
| 嵌入层 (Embedding Layer) |
| Word Embedding + Char Embedding (+ POS/其他特征) |
+--------------------------------------------------------+
|
v
+--------------------------------------------------------+
| BiLSTM 编码层 |
| Forward LSTM: h1_f -> h2_f -> ... -> hn_f |
| Backward LSTM: h1_b <- h2_b <- ... <- hn_b |
| 拼接: hi = [hi_f ; hi_b] (hidden_size * 2) |
+--------------------------------------------------------+
|
v
+--------------------------------------------------------+
| CRF 解码层 |
| 发射分数 (Emission Score): P(yi|xi) |
| 转移分数 (Transition Score): P(yi|yi-1) |
| 全局最优: score(X,Y) = Σ emission_i + Σ transition_i,j |
| 解码: Viterbi算法求最优路径 |
+--------------------------------------------------------+
|
v
输出标签序列
2.1.2 核心组件详解
(1)嵌入层
嵌入层将离散的文本符号映射为连续的向量表示:
e i = w i ; c i e_i = w_i ; c_i ei=wi;ci
其中 w i ∈ R d w w_i \in \mathbb{R}^{d_w} wi∈Rdw 为词嵌入, c i ∈ R d c c_i \in \mathbb{R}^{d_c} ci∈Rdc 为字符级嵌入(通过Char-CNN或Char-LSTM获取)。
(2)BiLSTM编码层
LSTM通过门控机制解决传统RNN的梯度消失问题,其单元状态更新公式:
f t = σ ( W f ⋅ h t − 1 , x t + b f ) f_t = \sigma(W_f \cdot h_{t-1}, x_t + b_f) ft=σ(Wf⋅ht−1,xt+bf)
i t = σ ( W i ⋅ h t − 1 , x t + b i ) i_t = \sigma(W_i \cdot h_{t-1}, x_t + b_i) it=σ(Wi⋅ht−1,xt+bi)
C ~ t = tanh ( W C ⋅ h t − 1 , x t + b C ) \tilde{C}_t = \tanh(W_C \cdot h_{t-1}, x_t + b_C) C~t=tanh(WC⋅ht−1,xt+bC)
C t = f t ⊙ C t − 1 + i t ⊙ C ~ t C_t = f_t \odot C_{t-1} + i_t \odot \tilde{C}_t Ct=ft⊙Ct−1+it⊙C~t
o t = σ ( W o ⋅ h t − 1 , x t + b o ) o_t = \sigma(W_o \cdot h_{t-1}, x_t + b_o) ot=σ(Wo⋅ht−1,xt+bo)
h t = o t ⊙ tanh ( C t ) h_t = o_t \odot \tanh(C_t) ht=ot⊙tanh(Ct)
BiLSTM将前向LSTM和后向LSTM的隐状态拼接:
h t B i = h t → ; h t ← h_t^{Bi} = \\overrightarrow{h_t} ; \\overleftarrow{h_t} htBi=ht ;ht
(3)CRF解码层
CRF层建模标签序列的联合概率分布,定义给定输入序列 X X X 的标签序列 Y Y Y 的分数:
S ( X , Y ) = ∑ i = 1 n P i , y i + ∑ i = 0 n T y i , y i + 1 S(X, Y) = \sum_{i=1}^{n} P_{i,y_i} + \sum_{i=0}^{n} T_{y_i, y_{i+1}} S(X,Y)=i=1∑nPi,yi+i=0∑nTyi,yi+1
其中 P ∈ R n × ∣ T ∣ P \in \mathbb{R}^{n \times |T|} P∈Rn×∣T∣ 为BiLSTM输出的发射分数矩阵, T ∈ R ∣ T ∣ × ∣ T ∣ T \in \mathbb{R}^{|T| \times |T|} T∈R∣T∣×∣T∣ 为转移分数矩阵。
训练时最大化对数似然:
L = log e S ( X , Y g o l d ) ∑ Y ~ e S ( X , Y ~ ) = S ( X , Y g o l d ) − log ∑ Y ~ e S ( X , Y ~ ) \mathcal{L} = \log \frac{e^{S(X, Y_{gold})}}{\sum_{\tilde{Y}} e^{S(X, \tilde{Y})}} = S(X, Y_{gold}) - \log \sum_{\tilde{Y}} e^{S(X, \tilde{Y})} L=log∑Y~eS(X,Y~)eS(X,Ygold)=S(X,Ygold)−logY~∑eS(X,Y~)
2.1.3 PyTorch实现代码
python
import torch
import torch.nn as nn
from torchcrf import CRF
class BiLSTM_CRF(nn.Module):
"""
BiLSTM-CRF模型用于命名实体识别
参数说明:
vocab_size: 词汇表大小
embed_dim: 词嵌入维度
hidden_dim: LSTM隐藏层维度
num_tags: 标签类别数
num_layers: LSTM层数
dropout: Dropout比率
"""
def __init__(self, vocab_size, embed_dim, hidden_dim, num_tags,
num_layers=2, dropout=0.3):
super(BiLSTM_CRF, self).__init__()
self.hidden_dim = hidden_dim
self.num_tags = num_tags
# 嵌入层
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
# BiLSTM编码层
self.lstm = nn.LSTM(
embed_dim,
hidden_dim // 2, # 双向拼接后为hidden_dim
num_layers=num_layers,
bidirectional=True,
batch_first=True,
dropout=dropout if num_layers > 1 else 0
)
# Dropout层
self.dropout = nn.Dropout(dropout)
# 发射分数层: LSTM输出 -> tag分数
self.hidden2tag = nn.Linear(hidden_dim, num_tags)
# CRF层
self.crf = CRF(num_tags, batch_first=True)
def forward(self, x, tags=None, mask=None):
"""
前向传播
参数:
x: [batch_size, seq_len] 输入token ID
tags: [batch_size, seq_len] 真实标签 (训练时使用)
mask: [batch_size, seq_len] 有效位置掩码
返回:
训练时: 负对数似然损失
预测时: 最优标签序列
"""
# 嵌入层
embeds = self.dropout(self.embedding(x)) # [B, L, embed_dim]
# BiLSTM编码
lstm_out, _ = self.lstm(embeds) # [B, L, hidden_dim]
lstm_out = self.dropout(lstm_out)
# 发射分数
emissions = self.hidden2tag(lstm_out) # [B, L, num_tags]
if tags is not None:
# 训练: 计算CRF损失
loss = -self.crf(emissions, tags, mask=mask.byte(),
reduction='mean')
return loss
else:
# 预测: Viterbi解码
predictions = self.crf.decode(emissions, mask=mask.byte())
return predictions
2.2 BERT-NER模型
BERT(Bidirectional Encoder Representations from Transformers)通过双向Transformer编码器和预训练-微调范式,在NER任务上实现了重大突破。
2.2.1 模型架构
输入句子: [CLS] token1 token2 ... tokenN [SEP]
|
v
+---------------------------------------------+
| BERT Embedding Layer |
| Token Embedding + Segment Embedding + |
| Position Embedding |
+---------------------------------------------+
|
v
+---------------------------------------------+
| BERT Encoder (12/24 layers) |
| Multi-Head Self-Attention + |
| Feed-Forward Network + LayerNorm |
| (Transformer Block x N) |
+---------------------------------------------+
|
v
+---------------------------------------------+
| 输出层 |
| [CLS]用于分类 / 各token用于序列标注 |
| Linear + Softmax/CRF |
+---------------------------------------------+
|
v
标签序列: [CLS] B-PER I-PER O O B-ORG ...
2.2.2 BERT核心机制
(1)多头自注意力机制
Attention ( Q , K , V ) = softmax ( Q K T d k ) V \text{Attention}(Q, K, V) = \text{softmax}(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dk QKT)V
MultiHead ( X ) = Concat ( h e a d 1 , . . . , h e a d h ) W O \text{MultiHead}(X) = \text{Concat}(head_1, ..., head_h)W^O MultiHead(X)=Concat(head1,...,headh)WO
其中 h e a d i = Attention ( X W i Q , X W i K , X W i V ) head_i = \text{Attention}(XW_i^Q, XW_i^K, XW_i^V) headi=Attention(XWiQ,XWiK,XWiV)。
(2)预训练任务
- 掩码语言模型(MLM):随机遮蔽15%的token,模型预测被遮蔽的token
- 下一句预测(NSP):预测两个句子是否具有上下文连贯关系
(3)NER微调
将BERT最后一层的每个token表示 h i ∈ R d h_i \in \mathbb{R}^{d} hi∈Rd 传入线性分类层:
P ( y i ∣ x i ) = softmax ( W h i + b ) P(y_i|x_i) = \text{softmax}(Wh_i + b) P(yi∣xi)=softmax(Whi+b)
损失函数:
L = − ∑ i = 1 n log P ( y i t r u e ∣ x i ) \mathcal{L} = -\sum_{i=1}^{n} \log P(y_i^{true}|x_i) L=−i=1∑nlogP(yitrue∣xi)
2.2.3 BERT-BiLSTM-CRF融合模型
为了进一步增强BERT的序列建模能力,研究者提出了BERT-BiLSTM-CRF架构,结合了BERT的深层语义表示、BiLSTM的序列特征提取能力和CRF的全局标签约束。
python
import torch
import torch.nn as nn
from transformers import BertModel, BertTokenizer
from torchcrf import CRF
class BERT_BiLSTM_CRF(nn.Module):
"""
BERT-BiLSTM-CRF模型
结构: BERT -> BiLSTM -> CRF
优势: 融合BERT深层语义表示与BiLSTM-CRF的序列标注能力
"""
def __init__(self, bert_path, num_tags, lstm_hidden=256,
num_layers=1, dropout=0.3):
super(BERT_BiLSTM_CRF, self).__init__()
# BERT编码器
self.bert = BertModel.from_pretrained(bert_path)
self.bert_dim = self.bert.config.hidden_size # 768 for bert-base
# BiLSTM层
self.lstm = nn.LSTM(
self.bert_dim,
lstm_hidden // 2,
num_layers=num_layers,
bidirectional=True,
batch_first=True,
dropout=dropout if num_layers > 1 else 0
)
# Dropout
self.dropout = nn.Dropout(dropout)
# 发射分数层
self.hidden2tag = nn.Linear(lstm_hidden, num_tags)
# CRF层
self.crf = CRF(num_tags, batch_first=True)
def forward(self, input_ids, attention_mask, tags=None):
# BERT编码
bert_outputs = self.bert(
input_ids=input_ids,
attention_mask=attention_mask
)
sequence_output = bert_outputs.last_hidden_state # [B, L, 768]
sequence_output = self.dropout(sequence_output)
# BiLSTM
lstm_out, _ = self.lstm(sequence_output) # [B, L, lstm_hidden]
lstm_out = self.dropout(lstm_out)
# 发射分数
emissions = self.hidden2tag(lstm_out) # [B, L, num_tags]
if tags is not None:
loss = -self.crf(emissions, tags, mask=attention_mask.byte(),
reduction='mean')
return loss
else:
predictions = self.crf.decode(emissions,
mask=attention_mask.byte())
return predictions
2.3 深度学习NER模型对比分析
| 模型 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| BiLSTM-CRF | 结构简单,参数量小,推理速度快 | 对上下文理解有限,依赖预训练词向量质量 | 资源受限环境,实时性要求高的场景 |
| BERT-NER | 深层双向上下文表示,精度高 | 参数量大,推理速度慢,计算成本高 | 高精度要求,资源充足的研究场景 |
| BERT-BiLSTM-CRF | 融合深层语义与序列建模能力 | 模型复杂,训练耗时更长 | 复杂领域,长文本NER任务 |
| RoBERTa-NER | 优化了BERT预训练策略,性能更优 | 同BERT,计算成本高 | 各类NER基准评测 |
3. OpenNRE项目深度解析
3.1 项目概述
OpenNRE(Open Neural Relation Extraction)是由清华大学自然语言处理与社会人文计算实验室(THUNLP)开发的开源神经关系抽取工具包,是OpenSKL项目的重要子项目。该项目在GitHub上获得超过4.5k Stars,是关系抽取领域最具影响力的开源框架之一。
核心特点:
- 统一的模型接口设计,支持快速扩展新模型
- 覆盖句子级、包级、文档级和小样本关系抽取
- 兼容传统神经网络(CNN、PCNN)和预训练语言模型(BERT)
- 支持监督学习和远程监督(Distant Supervision)两种训练范式
- 内置多种数据集和预训练模型,开箱即用
3.2 代码结构分析
OpenNRE/
├── opennre/ # 核心代码包
│ ├── __init__.py # 包初始化,导出get_model等API
│ ├── pretrain.py # 预训练模型下载与加载
│ ├── encoder/ # 编码器模块
│ │ ├── __init__.py # 编码器注册与导出
│ │ ├── base_encoder.py # 编码器基类定义
│ │ ├── bert_encoder.py # BERT编码器实现
│ │ ├── cnn_encoder.py # CNN编码器实现
│ │ └── pcnn_encoder.py # PCNN(分段CNN)编码器实现
│ ├── model/ # 模型模块
│ │ ├── __init__.py # 模型注册与导出
│ │ ├── base_model.py # 模型基类(SentenceRE, BagRE, FewShotRE, NER)
│ │ ├── bag_attention.py # 注意力机制的包级模型
│ │ ├── bag_average.py # 平均池化的包级模型
│ │ ├── bag_one.py # 单实例包级模型
│ │ ├── sigmoid_nn.py # 多标签分类模型
│ │ └── softmax_nn.py # 单标签分类模型
│ ├── framework/ # 训练框架模块
│ │ ├── __init__.py
│ │ ├── data_loader.py # 数据加载器
│ │ ├── sentence_re.py # 句子级RE训练框架
│ │ ├── bag_re.py # 包级RE训练框架
│ │ ├── multi_label_sentence_re.py # 多标签RE框架
│ │ └── utils.py # 工具函数
│ ├── module/ # 基础网络组件
│ │ ├── nn/ # 神经网络层(CNN, LSTM等)
│ │ └── pool/ # 池化操作
│ └── tokenization/ # 分词模块
│ ├── __init__.py
│ ├── base_tokenizer.py
│ ├── bert_tokenizer.py
│ └── utils.py
├── example/ # 示例脚本
│ ├── train_supervised_bert.py # BERT监督学习训练
│ ├── train_supervised_cnn.py # CNN监督学习训练
│ ├── train_bag_bert.py # BERT包级训练
│ ├── train_bag_cnn.py # CNN包级训练
│ ├── test_multilabel_bert.py # BERT多标签测试
│ └── test_multilabel_cnn.py # CNN多标签测试
├── benchmark/ # 数据集下载脚本
├── pretrain/ # 预训练模型下载脚本
├── tests/ # 单元测试
├── setup.py # 包安装配置
├── requirements.txt # 依赖包列表
└── README.md # 项目文档
3.3 核心模块代码分析
3.3.1 编码器模块(encoder/)
编码器负责将输入文本转换为向量表示,是关系抽取的特征提取基础。
BERTEncoder(bert_encoder.py):
python
class BERTEncoder(nn.Module):
"""
BERT编码器实现
基于Hugging Face Transformers库的BertModel,
提供句子级别的向量表示。
核心方法:
forward: 输入token序列,输出BERT最后一层[CLS]或各token表示
tokenize: 将原始文本转换为模型输入格式
"""
def __init__(self, max_length, pretrain_path, blank_padding=True, mask_entity=False):
super().__init__()
self.max_length = max_length
self.blank_padding = blank_padding
self.hidden_size = 768 # BERT-base hidden dimension
self.mask_entity = mask_entity
# 加载预训练BERT
self.bert = BertModel.from_pretrained(pretrain_path)
self.tokenizer = BertTokenizer.from_pretrained(pretrain_path)
def forward(self, token, att_mask, pos1, pos2):
"""
参数:
token: [B, L] token ID矩阵
att_mask: [B, L] 注意力掩码
pos1, pos2: 头实体和尾实体的位置编码
返回:
[B, hidden_size] 句子表示向量
"""
_, x = self.bert(token, attention_mask=att_mask, return_dict=False)
return x
def tokenize(self, item):
"""
将输入数据转换为模型输入格式
参数:
item: {'text': str, 'h': {'pos': (start, end)}, 't': {'pos': (start, end)}}
返回:
token: [1, L] token ID张量
att_mask: [1, L] 注意力掩码
pos1, pos2: 位置编码
"""
# 实现token化、实体标记、填充等操作
...
CNNEncoder(cnn_encoder.py):
采用卷积神经网络提取文本局部特征,通过不同大小的卷积核捕获多粒度n-gram信息。
PCNNEncoder(pcnn_encoder.py):
分段卷积神经网络(Piecewise CNN),将句子按头实体、实体间、尾实体三部分分别进行卷积和池化,更好地保留实体位置信息。
输入句子: ... head_entity ... tail_entity ...
|
v
+----------------+----------------+----------------+
| 头实体段 | 实体间段 | 尾实体段 |
| (before head) | (between) | (after tail) |
+----------------+----------------+----------------+
| | |
v v v
Conv1 Conv2 Conv3
| | |
v v v
MaxPool1 MaxPool2 MaxPool3
| | |
+------------------+------------------+
|
v
[向量拼接] -> 句子表示
3.3.2 模型模块(model/)
模型模块定义了关系抽取的具体实现,采用继承基类的方式统一接口。
base_model.py - 基类定义:
python
class SentenceRE(nn.Module):
"""句子级关系抽取基类"""
def __init__(self):
super().__init__()
def infer(self, item):
"""
推理接口
参数:
item: {'text'或'token': ...,
'h': {'pos': [start, end]},
't': {'pos': [start, end]}}
返回:
(关系名称, 置信度分数)
"""
raise NotImplementedError
class BagRE(nn.Module):
"""包级关系抽取基类(远程监督)"""
def __init__(self):
super().__init__()
def infer(self, bag):
"""
参数:
bag: 同一实体对的句子集合
[{'text': ..., 'h': {...}, 't': {...}}, ...]
返回:
(关系, 分数)
"""
raise NotImplementedError
class NER(nn.Module):
"""命名实体识别基类"""
def __init__(self):
super().__init__()
def infer(self, item):
"""识别文本中的实体"""
raise NotImplementedError
BagAttention(bag_attention.py)- 注意力机制的包级模型:
python
class BagAttention(BagRE):
"""
基于实例级注意力机制的包级关系抽取模型
核心思想: 为包内每个句子分配注意力权重,
聚焦于包含真正关系的句子,抑制噪声句子的影响。
这是Lin et al. (2016) "Neural Relation Extraction with
Selective Attention over Instances"的实现。
"""
def __init__(self, sentence_encoder, num_class, rel2id, use_diag=True):
super().__init__()
self.sentence_encoder = sentence_encoder # 句子编码器
self.num_class = num_class
self.fc = nn.Linear(self.sentence_encoder.hidden_size, num_class)
self.softmax = nn.Softmax(dim=-1)
self.drop = nn.Dropout()
# 对角注意力机制参数
if use_diag:
self.diag = nn.Parameter(torch.ones(self.sentence_encoder.hidden_size))
def forward(self, token, scope, token_mask, ...):
"""
参数:
token: [B, L] token序列
scope: [num_bags, 2] 每个包在batch中的范围
返回:
bag_logits: [num_bags, num_class] 每个包的分类logits
"""
# 1. 编码所有句子
x = self.sentence_encoder(token, token_mask) # [B, hidden_size]
# 2. 计算注意力权重
if self.use_diag:
# 对角注意力: 为每个维度分配权重
queries = x * self.diag # [B, hidden_size]
else:
queries = x
# 3. 按包聚合(注意力加权求和)
bag_reps = []
bag_logits = []
for i, (start, end) in enumerate(scope):
bag_rep = x[start:end] # [bag_size, hidden_size]
# 注意力计算
att_scores = torch.matmul(bag_rep, queries[start:end].T)
att_weights = self.softmax(att_scores)
# 加权求和
weighted_rep = torch.sum(att_weights.unsqueeze(-1) * bag_rep, dim=0)
bag_reps.append(weighted_rep)
# 4. 分类
bag_reps = torch.stack(bag_reps)
bag_logits = self.fc(self.drop(bag_reps))
return bag_logits
SoftmaxNN(softmax_nn.py)- 单标签分类模型:
python
class SoftmaxNN(SentenceRE):
"""
基于Softmax的句子级关系抽取模型
结构: Encoder -> Dropout -> Linear -> Softmax
适用于单关系分类场景(如TACRED数据集)
"""
def __init__(self, sentence_encoder, num_class, rel2id):
super().__init__()
self.sentence_encoder = sentence_encoder
self.num_class = num_class
self.fc = nn.Linear(self.sentence_encoder.hidden_size, num_class)
self.softmax = nn.Softmax(-1)
self.rel2id = rel2id
self.drop = nn.Dropout()
def forward(self, token, att_mask):
rep = self.sentence_encoder(token, att_mask)
rep = self.drop(rep)
logits = self.fc(rep)
return logits
3.3.3 框架模块(framework/)
框架模块提供训练和评估的统一接口。
SentenceRE(sentence_re.py):
python
class SentenceRE(nn.Module):
"""
句子级关系抽取训练框架
功能:
- 训练循环管理
- 验证与早停
- 模型保存与加载
- 评估指标计算(Micro-F1, Accuracy)
"""
def __init__(self, train_loader, val_loader, model, ...):
self.train_loader = train_loader
self.val_loader = val_loader
self.model = model
self.optimizer = optim.Adam(model.parameters(), lr=lr)
def train_model(self, metric='micro_f1'):
"""执行训练循环"""
for epoch in range(max_epoch):
self.model.train()
for batch in self.train_loader:
loss = self.train_step(batch)
loss.backward()
self.optimizer.step()
# 验证
result = self.eval_model()
if result[metric] > best_result:
best_result = result[metric]
self.save_model()
数据加载器(data_loader.py):
支持JSON格式的数据集,每条数据包含:
json
{
"token": ["Bill", "Gates", "founded", "Microsoft"],
"h": {"name": "Bill Gates", "pos": [0, 2], "id": "Q5284"},
"t": {"name": "Microsoft", "pos": [3, 4], "id": "Q2283"},
"relation": "founder_of"
}
3.3.4 模型加载机制(pretrain.py)
python
def get_model(model_name):
"""
加载预训练模型
支持的模型:
'wiki80_cnn_softmax': Wiki80数据集 + CNN编码器 + Softmax分类
'wiki80_bert_softmax': Wiki80数据集 + BERT编码器 + Softmax分类
'wiki80_bertentity_softmax': BERT实体表示拼接
'tacred_bert_softmax': TACRED数据集 + BERT
加载流程:
1. 检查本地是否存在模型文件
2. 不存在则自动下载
3. 构建模型结构
4. 加载预训练权重
"""
# 模型配置字典
model_configs = {
'wiki80_cnn_softmax': {
'encoder': 'cnn',
'model': 'softmax',
'dataset': 'wiki80'
},
'wiki80_bert_softmax': {
'encoder': 'bert',
'model': 'softmax',
'dataset': 'wiki80'
}
}
# 自动构建并加载模型
...
3.4 OpenNRE快速使用指南
python
import opennre
# 加载预训练模型
model = opennre.get_model('wiki80_bert_softmax')
# GPU加速
model = model.cuda()
# 关系抽取推理
result = model.infer({
'text': 'He was the son of Máel Dúin mac Máele Fithrich, '
'and grandson of the high king Áed Uaridnach (died 612).',
'h': {'pos': (18, 46)}, # 头实体位置
't': {'pos': (78, 91)} # 尾实体位置
})
print(result) # ('father', 0.51087)
# 训练自定义模型
from opennre import encoder, model, framework
# 1. 创建编码器
sentence_encoder = encoder.BERTEncoder(
max_length=128,
pretrain_path='bert-base-uncased'
)
# 2. 创建模型
re_model = model.SoftmaxNN(
sentence_encoder=sentence_encoder,
num_class=len(rel2id),
rel2id=rel2id
)
# 3. 创建训练框架
trainer = framework.SentenceRE(
train_loader=train_loader,
val_loader=val_loader,
model=re_model,
ckpt='best_model.pth',
lr=2e-5
)
# 4. 训练
trainer.train_model(metric='micro_f1')
3.5 OpenNRE架构设计亮点
- 高度模块化设计:编码器、模型、框架三层分离,便于独立扩展
- 统一接口规范:所有模型继承统一基类,保证API一致性
- 自动下载机制:预训练模型和数据集自动管理,降低使用门槛
- 灵活的配置系统:通过命令行参数控制训练流程,无需修改代码
- 多场景覆盖:同时支持监督学习和远程监督,适配不同数据条件
第二部分:实际场景调用设计
4. 场景概述:智能金融风控动态知识图谱系统
4.1 场景背景
在金融风控领域,需要从海量非结构化文本(如新闻报道、公司公告、社交媒体、监管文件)中实时识别金融实体(公司、人物、产品、风险事件)及其关系,构建动态更新的金融知识图谱,用于风险预警、关联分析和合规监控。
核心挑战:
- 金融实体名称复杂多变(简称、别名、翻译名)
- 新实体和关系不断涌现(新成立公司、新金融产品)
- 文本噪声大(远程监督假设常不成立)
- 需要实时更新知识图谱以反映最新市场动态
4.2 设计思路
本方案融合以下技术构建一个端到端的智能金融风控系统:
| 技术组件 | 作用 |
|---|---|
| NER模型(BERT-BiLSTM-CRF) | 从文本中识别金融实体 |
| OpenNRE | 抽取实体间关系,生成知识三元组 |
| 动态知识图谱 | 实时更新实体和关系,支持时序推理 |
| 大模型(LLM) | 辅助实体消歧、关系推理、报告生成 |
| 强化学习(RL) | 智能选择高价值文本进行标注,优化知识抽取策略 |
+------------------+ +------------------+ +------------------+
| 文本输入层 | | 知识抽取层 | | 知识管理层 |
| | | | | |
| 新闻/公告/报告 | --> | NER识别实体 | --> | 动态知识图谱 |
| 社交媒体/监管文件 | | OpenNRE抽取关系 | | 时序版本管理 |
| | | LLM辅助消歧 | | 一致性校验 |
+------------------+ +------------------+ +------------------+
^
|
+------------------+
| 策略优化层 |
| |
| 强化学习智能体 |
| - 文本选择策略 |
| - 标注优先级排序 |
| - 抽取策略调整 |
+------------------+
^
|
+------------------+
| 反馈信号 |
| - 图谱质量评分 |
| - 人工校验反馈 |
| - 下游任务效果 |
+------------------+
5. 系统架构设计
5.1 整体架构
用户查询/报告请求
|
v
+-------------------------------------------------------------+
| API网关层 (FastAPI) |
| - /extract: 单文本知识抽取 |
| - /batch_extract: 批量文本处理 |
| - /query: 知识图谱查询 |
| - /risk_alert: 风险预警 |
| - /report: 生成分析报告 |
+-------------------------------------------------------------+
|
+--------------+--------------+
| |
v v
+------------------+ +------------------+
| 知识抽取服务 | | 知识查询服务 |
| | | |
| - NER模块 | | - 图数据库查询 |
| - RE模块(OpenNRE)| | - 路径推理 |
| - LLM增强模块 | | - 风险传播分析 |
+------------------+ +------------------+
| |
v v
+------------------+ +------------------+
| 动态图谱管理 | | 强化学习服务 |
| | | |
| - Neo4j图数据库 | | - 策略网络(DQN) |
| - 时序版本控制 | | - 经验回放池 |
| - 增量更新引擎 | | - 奖励计算模块 |
+------------------+ +------------------+
5.2 核心模块详细设计
5.2.1 NER模块(BERT-BiLSTM-CRF)
专门识别金融领域的实体类型:
| 实体类型 | 说明 | 示例 |
|---|---|---|
| ORG | 组织机构 | 中国工商银行、腾讯控股 |
| PER | 人物 | 马云、巴菲特 |
| FIN | 金融产品 | 沪深300指数基金、国债期货 |
| RISK | 风险事件 | 债务违约、流动性危机 |
| REG | 法规政策 | 《商业银行法》、资管新规 |
| MKT | 市场/板块 | 科创板、港股市场 |
5.2.2 RE模块(基于OpenNRE)
定义金融领域核心关系:
| 关系类型 | 说明 | 示例 |
|---|---|---|
| invest_in | 投资于 | 腾讯 -> invest_in -> 美团 |
| acquire | 收购 | 阿里 -> acquire -> 饿了么 |
| competitor | 竞争关系 | 京东 competitor 拼多多 |
| cooperate | 合作关系 | 华为 cooperate 北汽 |
| hold | 持股 | 巴菲特 hold 苹果 |
| subsidiary | 子公司 | 蚂蚁集团 subsidiary 支付宝 |
| risk_correlate | 风险关联 | 恒大 -> risk_correlate -> 地产板块 |
5.2.3 动态知识图谱模块
支持以下操作时序管理:
- 实体新增:新上市公司、新创企业
- 关系变更:股权比例变化、合作关系终止
- 属性更新:公司更名、法人变更
- 实体合并/拆分:公司重组、业务拆分
5.2.4 大模型增强模块
- 实体消歧:利用LLM的语义理解能力,解决实体名称歧义
- 关系推理:对缺失关系进行补全推理
- 报告生成:基于知识图谱自动生成风险分析报告
5.2.5 强化学习模块
- 状态空间:当前知识图谱状态、待处理文本队列
- 动作空间:选择下一条文本进行抽取、选择抽取策略
- 奖励函数:知识图谱质量提升 + 人工校验准确率
6. 核心模块代码实现
6.1 NER模块实现
python
# ner_module.py - 金融NER模块
import torch
import torch.nn as nn
from transformers import BertModel, BertTokenizer
from torchcrf import CRF
class FinancialBERT_BiLSTM_CRF(nn.Module):
"""
金融领域BERT-BiLSTM-CRF模型
实体类别: ORG, PER, FIN, RISK, REG, MKT + O
BIO标注: 共13个标签 (B-ORG, I-ORG, B-PER, I-PER, ..., O)
"""
# 标签映射
LABEL_MAP = {
'O': 0,
'B-ORG': 1, 'I-ORG': 2,
'B-PER': 3, 'I-PER': 4,
'B-FIN': 5, 'I-FIN': 6,
'B-RISK': 7, 'I-RISK': 8,
'B-REG': 9, 'I-REG': 10,
'B-MKT': 11, 'I-MKT': 12
}
ID2LABEL = {v: k for k, v in LABEL_MAP.items()}
NUM_TAGS = len(LABEL_MAP)
def __init__(self, bert_path='bert-base-chinese',
lstm_hidden=256, num_layers=1, dropout=0.3):
super().__init__()
# BERT编码器
self.bert = BertModel.from_pretrained(bert_path)
self.tokenizer = BertTokenizer.from_pretrained(bert_path)
self.bert_dim = self.bert.config.hidden_size
# BiLSTM层
self.lstm = nn.LSTM(
self.bert_dim, lstm_hidden // 2,
num_layers=num_layers,
bidirectional=True,
batch_first=True,
dropout=dropout if num_layers > 1 else 0
)
self.dropout = nn.Dropout(dropout)
self.hidden2tag = nn.Linear(lstm_hidden, self.NUM_TAGS)
self.crf = CRF(self.NUM_TAGS, batch_first=True)
def forward(self, input_ids, attention_mask, labels=None):
# BERT编码
bert_out = self.bert(input_ids=input_ids,
attention_mask=attention_mask)
sequence_output = self.dropout(bert_out.last_hidden_state)
# BiLSTM
lstm_out, _ = self.lstm(sequence_output)
lstm_out = self.dropout(lstm_out)
# 发射分数
emissions = self.hidden2tag(lstm_out)
if labels is not None:
loss = -self.crf(emissions, labels,
mask=attention_mask.byte(),
reduction='mean')
return loss
else:
predictions = self.crf.decode(emissions,
mask=attention_mask.byte())
return predictions
def extract_entities(self, text):
"""
从文本中提取金融实体
参数:
text: 输入文本字符串
返回:
entities: [(entity_text, entity_type, start, end), ...]
"""
# Tokenize
encoding = self.tokenizer(
text, return_tensors='pt',
padding=True, truncation=True,
max_length=512
)
# 预测标签
predictions = self.forward(
encoding['input_ids'],
encoding['attention_mask']
)[0] # 取第一条
# 解析标签序列,提取实体
tokens = self.tokenizer.convert_ids_to_tokens(
encoding['input_ids'][0]
)
entities = []
current_entity = []
for idx, (token, pred_id) in enumerate(zip(tokens, predictions)):
label = self.ID2LABEL[pred_id]
if label.startswith('B-'):
if current_entity:
entities.append(current_entity)
current_entity = [token, label[2:], idx, idx]
elif label.startswith('I-') and current_entity:
if current_entity[1] == label[2:]:
current_entity[3] = idx
else:
entities.append(current_entity)
current_entity = []
else:
if current_entity:
entities.append(current_entity)
current_entity = []
# 转换为字符位置
result = []
for ent in entities:
ent_text = self.tokenizer.convert_tokens_to_string(ent[0])
result.append((ent_text, ent[1], ent[2], ent[3]))
return result
class NERServer:
"""NER服务封装"""
def __init__(self, model_path=None):
self.model = FinancialBERT_BiLSTM_CRF()
if model_path:
self.model.load_state_dict(torch.load(model_path))
self.model.eval()
def predict(self, texts):
"""批量预测"""
results = []
for text in texts:
entities = self.model.extract_entities(text)
results.append({
'text': text,
'entities': entities
})
return results
6.2 RE模块(基于OpenNRE)
python
# re_module.py - 关系抽取模块
import opennre
import torch
class FinancialRelationExtractor:
"""
金融领域关系抽取器
基于OpenNRE框架,加载预训练模型或训练自定义模型
"""
# 金融关系映射
RELATION_MAP = {
'invest_in': 0, 'acquire': 1, 'competitor': 2,
'cooperate': 3, 'hold': 4, 'subsidiary': 5,
'risk_correlate': 6, 'none': 7
}
def __init__(self, model_name='wiki80_bert_softmax',
custom_model_path=None):
"""
初始化关系抽取器
参数:
model_name: OpenNRE预训练模型名称
custom_model_path: 自定义模型路径(可选)
"""
if custom_model_path:
# 加载自定义训练的模型
self.model = self._load_custom_model(custom_model_path)
else:
# 加载OpenNRE预训练模型
self.model = opennre.get_model(model_name)
self.model.eval()
def _load_custom_model(self, model_path):
"""加载自定义训练的OpenNRE模型"""
from opennre import encoder, model
# 构建模型结构
sentence_encoder = encoder.BERTEncoder(
max_length=128,
pretrain_path='bert-base-chinese'
)
re_model = model.SoftmaxNN(
sentence_encoder=sentence_encoder,
num_class=len(self.RELATION_MAP),
rel2id=self.RELATION_MAP
)
# 加载权重
re_model.load_state_dict(torch.load(model_path))
return re_model
def extract_relations(self, text, entities):
"""
从文本和实体列表中抽取关系
参数:
text: 原始文本
entities: [(entity, type, start, end), ...]
返回:
triples: [(head, relation, tail, confidence), ...]
"""
triples = []
# 两两组合实体对
for i, (h_ent, h_type, h_start, h_end) in enumerate(entities):
for j, (t_ent, t_type, t_start, t_end) in enumerate(entities):
if i == j:
continue
# 构建OpenNRE输入格式
item = {
'text': text,
'h': {
'name': h_ent,
'pos': [h_start, h_end],
'type': h_type
},
't': {
'name': t_ent,
'pos': [t_start, t_end],
'type': t_type
}
}
# 关系推理
try:
relation, confidence = self.model.infer(item)
if relation != 'none' and confidence > 0.5:
triples.append((
{'name': h_ent, 'type': h_type},
relation,
{'name': t_ent, 'type': t_type},
confidence
))
except Exception as e:
continue
return triples
def train_financial_re_model(train_data, val_data, epochs=5):
"""
训练金融领域关系抽取模型
参数:
train_data: 训练数据路径
val_data: 验证数据路径
epochs: 训练轮数
"""
from opennre import encoder, model, framework
# 1. 编码器
sentence_encoder = encoder.BERTEncoder(
max_length=128,
pretrain_path='bert-base-chinese'
)
# 2. 模型
rel2id = FinancialRelationExtractor.RELATION_MAP
re_model = model.SoftmaxNN(
sentence_encoder=sentence_encoder,
num_class=len(rel2id),
rel2id=rel2id
)
# 3. 框架
trainer = framework.SentenceRE(
train_path=train_data,
val_path=val_data,
model=re_model,
batch_size=32,
max_epoch=epochs,
lr=2e-5,
ckpt='financial_re_model.pth'
)
# 4. 训练
trainer.train_model(metric='micro_f1')
return re_model
6.3 动态知识图谱模块
python
# kg_module.py - 动态知识图谱管理
from py2neo import Graph, Node, Relationship
from datetime import datetime
import json
class DynamicKnowledgeGraph:
"""
动态金融知识图谱管理
基于Neo4j图数据库,支持实体的增删改查和时序版本管理
"""
def __init__(self, uri="bolt://localhost:7687",
user="neo4j", password="password"):
self.graph = Graph(uri, auth=(user, password))
self._init_schema()
def _init_schema(self):
"""初始化图数据库约束和索引"""
# 创建实体唯一性约束
self.graph.run("""
CREATE CONSTRAINT entity_id IF NOT EXISTS
FOR (e:Entity) REQUIRE e.entity_id IS UNIQUE
""")
# 创建索引
self.graph.run("""
CREATE INDEX entity_name IF NOT EXISTS
FOR (e:Entity) ON (e.name)
""")
def add_entity(self, name, entity_type, properties=None):
"""
添加实体(如果不存在则创建,存在则更新)
参数:
name: 实体名称
entity_type: 实体类型 (ORG, PER, FIN, RISK, REG, MKT)
properties: 其他属性字典
返回:
entity_node: 创建的实体节点
"""
entity_id = f"{entity_type}_{name}"
query = """
MERGE (e:Entity {entity_id: $entity_id})
ON CREATE SET
e.name = $name,
e.type = $entity_type,
e.created_at = $timestamp,
e.updated_at = $timestamp,
e.version = 1
ON MATCH SET
e.updated_at = $timestamp,
e.version = e.version + 1
RETURN e
"""
result = self.graph.run(query,
entity_id=entity_id,
name=name,
entity_type=entity_type,
timestamp=datetime.now().isoformat()
)
node = result.data()[0]['e']
# 添加额外属性
if properties:
prop_query = """
MATCH (e:Entity {entity_id: $entity_id})
SET e += $properties
RETURN e
"""
self.graph.run(prop_query,
entity_id=entity_id,
properties=properties
)
# 记录版本历史
self._record_version('entity', entity_id, 'add',
{'name': name, 'type': entity_type})
return node
def add_relation(self, head_name, head_type, relation_type,
tail_name, tail_type, confidence=1.0,
source_text=None):
"""
添加关系三元组
参数:
head_name: 头实体名称
head_type: 头实体类型
relation_type: 关系类型
tail_name: 尾实体名称
tail_type: 尾实体类型
confidence: 关系置信度
source_text: 来源文本
"""
head_id = f"{head_type}_{head_name}"
tail_id = f"{tail_type}_{tail_name}"
query = """
MATCH (h:Entity {entity_id: $head_id})
MATCH (t:Entity {entity_id: $tail_id})
MERGE (h)-[r:RELATES {type: $relation_type}]->(t)
ON CREATE SET
r.confidence = $confidence,
r.created_at = $timestamp,
r.source = $source_text,
r.active = true
ON MATCH SET
r.confidence = (r.confidence + $confidence) / 2,
r.updated_at = $timestamp,
r.source = coalesce(r.source, '') + '; ' + $source_text
RETURN r
"""
result = self.graph.run(query,
head_id=head_id, tail_id=tail_id,
relation_type=relation_type,
confidence=confidence,
source_text=source_text or '',
timestamp=datetime.now().isoformat()
)
# 记录版本
self._record_version('relation',
f"{head_id}-{relation_type}-{tail_id}",
'add',
{'confidence': confidence})
return result.data()[0]['r'] if result.data() else None
def update_entity_embedding(self, entity_id, embedding):
"""
更新实体的向量嵌入表示
参数:
entity_id: 实体ID
embedding: 向量嵌入 (list or numpy array)
"""
query = """
MATCH (e:Entity {entity_id: $entity_id})
SET e.embedding = $embedding
RETURN e
"""
self.graph.run(query, entity_id=entity_id,
embedding=embedding.tolist())
def query_entity_relations(self, entity_name, entity_type,
depth=2):
"""
查询实体的关联网络
参数:
entity_name: 实体名称
entity_type: 实体类型
depth: 查询深度(跳数)
返回:
subgraph: 关联子图数据
"""
entity_id = f"{entity_type}_{entity_name}"
query = f"""
MATCH path = (e:Entity {{entity_id: $entity_id}})
-[r:RELATES*1..{depth}]-(connected)
WHERE ALL(rel IN relationships(path) WHERE rel.active = true)
RETURN path
LIMIT 100
"""
result = self.graph.run(query, entity_id=entity_id)
# 解析路径结果
paths = []
for record in result:
path = record['path']
paths.append({
'nodes': [dict(n) for n in path.nodes],
'relationships': [dict(r) for r in path.relationships]
})
return paths
def detect_risk_propagation(self, risk_entity, max_depth=3):
"""
风险传播分析:从风险实体出发,分析影响范围
参数:
risk_entity: 风险实体名称
max_depth: 最大传播深度
返回:
affected_entities: 受影响实体列表
"""
query = f"""
MATCH path = (risk:Entity {{name: $risk_entity}})
-[r:RELATES*1..{max_depth}]-(affected)
WHERE ALL(rel IN relationships(path) WHERE rel.active = true)
WITH affected,
min(length(path)) as distance,
collect(DISTINCT [rel IN relationships(path) | rel.type]) as relation_types
RETURN affected.name as entity,
affected.type as type,
distance,
relation_types
ORDER BY distance
"""
result = self.graph.run(query, risk_entity=risk_entity)
return [dict(record) for record in result]
def _record_version(self, element_type, element_id,
operation, data):
"""记录版本历史"""
query = """
CREATE (v:Version {
element_type: $element_type,
element_id: $element_id,
operation: $operation,
data: $data,
timestamp: $timestamp
})
"""
self.graph.run(query,
element_type=element_type,
element_id=element_id,
operation=operation,
data=json.dumps(data),
timestamp=datetime.now().isoformat()
)
def get_entity_history(self, entity_name, entity_type):
"""获取实体变更历史"""
entity_id = f"{entity_type}_{entity_name}"
query = """
MATCH (v:Version {element_id: $entity_id})
RETURN v
ORDER BY v.timestamp DESC
"""
result = self.graph.run(query, entity_id=entity_id)
return [dict(record['v']) for record in result]
# 知识图谱增量更新引擎
class KGDeltasEngine:
"""知识图谱增量更新引擎"""
def __init__(self, kg: DynamicKnowledgeGraph):
self.kg = kg
self.pending_changes = []
def stage_entity(self, name, entity_type, properties=None):
"""暂存实体变更"""
self.pending_changes.append({
'type': 'entity',
'name': name,
'entity_type': entity_type,
'properties': properties
})
def stage_relation(self, head, head_type, relation,
tail, tail_type, confidence, source):
"""暂存关系变更"""
self.pending_changes.append({
'type': 'relation',
'head': head,
'head_type': head_type,
'relation': relation,
'tail': tail,
'tail_type': tail_type,
'confidence': confidence,
'source': source
})
def commit(self):
"""提交所有暂存的变更"""
results = []
for change in self.pending_changes:
if change['type'] == 'entity':
result = self.kg.add_entity(
change['name'],
change['entity_type'],
change.get('properties')
)
elif change['type'] == 'relation':
result = self.kg.add_relation(
change['head'],
change['head_type'],
change['relation'],
change['tail'],
change['tail_type'],
change.get('confidence', 1.0),
change.get('source')
)
results.append(result)
self.pending_changes = []
return results
6.4 大模型增强模块
python
# llm_module.py - 大模型增强模块
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
import json
class LLMEnhancer:
"""
大语言模型增强模块
利用LLM的语义理解能力进行:
- 实体消歧
- 关系推理补全
- 风险分析报告生成
"""
def __init__(self, model_path="THUDM/chatglm3-6b"):
self.tokenizer = AutoTokenizer.from_pretrained(
model_path, trust_remote_code=True
)
self.model = AutoModelForCausalLM.from_pretrained(
model_path,
torch_dtype=torch.float16,
device_map="auto",
trust_remote_code=True
)
self.model.eval()
def disambiguate_entity(self, entity_name, contexts):
"""
实体消歧
参数:
entity_name: 待消歧的实体名称
contexts: 多个可能的上下文描述
返回:
best_match: 最佳匹配的标准实体名
"""
prompt = f"""请根据上下文判断"{entity_name}"指代的具体实体:
候选上下文:
{chr(10).join(f"{i+1}. {ctx}" for i, ctx in enumerate(contexts))}
请输出最可能的标准实体名称,仅输出实体名:"""
response, _ = self.model.chat(
self.tokenizer, prompt, history=[]
)
return response.strip()
def infer_missing_relations(self, entity_info, known_relations):
"""
基于已有知识推理缺失关系
参数:
entity_info: 实体信息字典
known_relations: 已知关系列表
返回:
inferred_relations: 推理出的可能关系
"""
prompt = f"""基于以下已知信息,推理可能存在的隐含关系:
实体信息: {json.dumps(entity_info, ensure_ascii=False)}
已知关系: {json.dumps(known_relations, ensure_ascii=False)}
请推理可能存在的其他关系,以JSON格式输出:
[{{"head": "", "relation": "", "tail": "", "reason": ""}}]"""
response, _ = self.model.chat(
self.tokenizer, prompt, history=[]
)
try:
return json.loads(response)
except:
return []
def generate_risk_report(self, risk_data, kg_subgraph):
"""
生成风险分析报告
参数:
risk_data: 风险事件数据
kg_subgraph: 相关知识子图
返回:
report: 结构化风险报告
"""
prompt = f"""请基于以下信息生成风险分析报告:
风险事件: {json.dumps(risk_data, ensure_ascii=False)}
关联实体网络: {json.dumps(kg_subgraph, ensure_ascii=False)}
请生成包含以下内容的分析报告:
1. 风险事件概述
2. 影响范围分析
3. 关联风险识别
4. 应对建议
报告:"""
response, _ = self.model.chat(
self.tokenizer, prompt, history=[]
)
return response
def extract_structured_knowledge(self, text):
"""
直接使用LLM从文本中提取结构化知识
作为NER+RE管道的补充/后备方案
"""
prompt = f"""请从以下金融文本中提取实体和关系:
文本: {text}
请以JSON格式输出:
{{
"entities": [
{{"name": "", "type": "ORG/PER/FIN/RISK/REG/MKT", "position": [start, end]}}
],
"relations": [
{{"head": "", "relation": "", "tail": "", "confidence": 0.0}}
]
}}"""
response, _ = self.model.chat(
self.tokenizer, prompt, history=[]
)
try:
return json.loads(response)
except:
return {"entities": [], "relations": []}
6.5 强化学习模块
python
# rl_module.py - 强化学习优化模块
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
from collections import deque
import random
class TextSelectionEnv:
"""
文本选择环境
状态: 当前知识图谱状态特征 + 候选文本特征
动作: 选择哪条文本进行知识抽取
奖励: 抽取后图谱质量提升幅度
"""
def __init__(self, text_queue, kg, ner_model, re_model):
self.text_queue = text_queue # 待处理文本队列
self.kg = kg # 知识图谱
self.ner_model = ner_model
self.re_model = re_model
self.current_idx = 0
self.kg_quality_history = []
def reset(self):
"""重置环境"""
self.current_idx = 0
self.kg_quality_history = [self._compute_kg_quality()]
return self._get_state()
def step(self, action):
"""
执行动作
参数:
action: 选择的文本索引 (0 ~ queue_size-1)
返回:
next_state, reward, done, info
"""
# 获取选中的文本
if action >= len(self.text_queue):
action = len(self.text_queue) - 1
text = self.text_queue[action]
# 执行NER+RE抽取
entities = self.ner_model.extract_entities(text)
triples = self.re_model.extract_relations(text, entities)
# 更新知识图谱
for ent, ent_type, _, _ in entities:
self.kg.add_entity(ent, ent_type)
for head, relation, tail, conf in triples:
self.kg.add_relation(
head['name'], head['type'],
relation,
tail['name'], tail['type'],
conf, text
)
# 计算奖励(图谱质量提升)
new_quality = self._compute_kg_quality()
reward = new_quality - self.kg_quality_history[-1]
self.kg_quality_history.append(new_quality)
# 移动到下一步
self.current_idx += 1
done = (self.current_idx >= 100) # 最多处理100条
next_state = self._get_state()
info = {'new_triples': len(triples), 'kg_quality': new_quality}
return next_state, reward, done, info
def _get_state(self):
"""获取当前状态向量"""
# 状态包含: 图谱统计特征 + 队列特征
kg_stats = self._get_kg_statistics()
queue_features = self._get_queue_features()
return np.concatenate([kg_stats, queue_features])
def _compute_kg_quality(self):
"""计算知识图谱质量评分"""
# 考虑因素: 实体数量、关系密度、置信度均值、连通性
stats = self._get_kg_statistics()
entity_count = stats[0]
relation_count = stats[1]
avg_confidence = stats[2]
connectivity = stats[3]
# 综合质量分
quality = (
np.log1p(entity_count) * 0.2 +
np.log1p(relation_count) * 0.3 +
avg_confidence * 0.3 +
connectivity * 0.2
)
return quality
def _get_kg_statistics(self):
"""获取知识图谱统计信息"""
# 简化的统计特征
query = """
MATCH (e:Entity)
WITH count(e) as entity_count
MATCH ()-[r:RELATES]->()
RETURN entity_count,
count(r) as relation_count,
avg(r.confidence) as avg_confidence
"""
result = self.kg.graph.run(query).data()[0]
return np.array([
result['entity_count'] or 0,
result['relation_count'] or 0,
result['avg_confidence'] or 0.5,
0.5 # 连通性占位
], dtype=np.float32)
def _get_queue_features(self):
"""获取文本队列的特征表示"""
# 使用文本长度、来源类型等简单特征
features = np.zeros(20, dtype=np.float32)
for i, text in enumerate(self.text_queue[:10]):
features[i * 2] = len(text) / 1000.0 # 归一化长度
features[i * 2 + 1] = 0.5 # 来源类型编码
return features
class DQNAgent(nn.Module):
"""
DQN智能体:用于学习文本选择策略
网络结构: State -> Linear -> ReLU -> Linear -> ReLU -> Linear -> Q-values
"""
def __init__(self, state_dim, action_dim, hidden_dim=128):
super(DQNAgent, self).__init__()
self.network = nn.Sequential(
nn.Linear(state_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, action_dim)
)
def forward(self, state):
return self.network(state)
class RLTrainer:
"""
强化学习训练器
使用DQN算法训练文本选择策略
"""
def __init__(self, env, state_dim, action_dim, lr=1e-3,
gamma=0.99, epsilon=1.0, epsilon_decay=0.995,
buffer_size=10000, batch_size=64):
self.env = env
self.gamma = gamma
self.epsilon = epsilon
self.epsilon_decay = epsilon_decay
self.batch_size = batch_size
# DQN网络
self.policy_net = DQNAgent(state_dim, action_dim)
self.target_net = DQNAgent(state_dim, action_dim)
self.target_net.load_state_dict(self.policy_net.state_dict())
self.optimizer = optim.Adam(self.policy_net.parameters(), lr=lr)
# 经验回放
self.memory = deque(maxlen=buffer_size)
def select_action(self, state):
"""Epsilon-贪心策略选择动作"""
if random.random() < self.epsilon:
return random.randint(0, self.policy_net.network[-1].out_features - 1)
else:
with torch.no_grad():
state_tensor = torch.FloatTensor(state).unsqueeze(0)
q_values = self.policy_net(state_tensor)
return q_values.argmax().item()
def store_transition(self, state, action, reward, next_state, done):
"""存储经验"""
self.memory.append((state, action, reward, next_state, done))
def train_step(self):
"""训练一步"""
if len(self.memory) < self.batch_size:
return
# 采样经验
batch = random.sample(self.memory, self.batch_size)
states, actions, rewards, next_states, dones = zip(*batch)
states = torch.FloatTensor(np.array(states))
actions = torch.LongTensor(actions)
rewards = torch.FloatTensor(rewards)
next_states = torch.FloatTensor(np.array(next_states))
dones = torch.FloatTensor(dones)
# 计算当前Q值
current_q = self.policy_net(states).gather(1, actions.unsqueeze(1))
# 计算目标Q值
with torch.no_grad():
next_q = self.target_net(next_states).max(1)[0]
target_q = rewards + self.gamma * next_q * (1 - dones)
# 计算损失
loss = nn.MSELoss()(current_q.squeeze(), target_q)
# 优化
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
# 衰减epsilon
self.epsilon = max(0.01, self.epsilon * self.epsilon_decay)
return loss.item()
def update_target_network(self):
"""更新目标网络"""
self.target_net.load_state_dict(self.policy_net.state_dict())
def train(self, num_episodes=1000, update_freq=10):
"""
训练智能体
参数:
num_episodes: 训练轮数
update_freq: 目标网络更新频率
"""
rewards_history = []
for episode in range(num_episodes):
state = self.env.reset()
episode_reward = 0
while True:
action = self.select_action(state)
next_state, reward, done, info = self.env.step(action)
self.store_transition(state, action, reward, next_state, done)
self.train_step()
episode_reward += reward
state = next_state
if done:
break
rewards_history.append(episode_reward)
# 更新目标网络
if episode % update_freq == 0:
self.update_target_network()
if episode % 100 == 0:
avg_reward = np.mean(rewards_history[-100:])
print(f"Episode {episode}, Avg Reward: {avg_reward:.3f}, "
f"Epsilon: {self.epsilon:.3f}")
return rewards_history
6.6 主服务整合
python
# main_service.py - 主服务入口
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
from typing import List, Optional
import asyncio
from ner_module import FinancialBERT_BiLSTM_CRF, NERServer
from re_module import FinancialRelationExtractor
from kg_module import DynamicKnowledgeGraph, KGDeltasEngine
from llm_module import LLMEnhancer
from rl_module import TextSelectionEnv, RLTrainer, DQNAgent
app = FastAPI(title="智能金融风控知识图谱系统")
# 全局模型实例
ner_server = None
re_extractor = None
knowledge_graph = None
kg_engine = None
llm_enhancer = None
rl_trainer = None
class ExtractRequest(BaseModel):
text: str
use_llm_enhance: bool = True
class BatchExtractRequest(BaseModel):
texts: List[str]
use_rl_selection: bool = False
class QueryRequest(BaseModel):
entity_name: str
entity_type: str
depth: int = 2
class RiskAlertRequest(BaseModel):
risk_entity: str
max_depth: int = 3
@app.on_event("startup")
async def load_models():
"""服务启动时加载所有模型"""
global ner_server, re_extractor, knowledge_graph
global kg_engine, llm_enhancer, rl_trainer
print("正在加载NER模型...")
ner_server = NERServer(model_path="./models/financial_ner.pth")
print("正在加载RE模型...")
re_extractor = FinancialRelationExtractor(
custom_model_path="./models/financial_re.pth"
)
print("正在连接知识图谱...")
knowledge_graph = DynamicKnowledgeGraph()
kg_engine = KGDeltasEngine(knowledge_graph)
print("正在加载LLM增强模块...")
llm_enhancer = LLMEnhancer()
print("所有模型加载完成!")
@app.post("/extract")
async def extract_knowledge(request: ExtractRequest):
"""
单文本知识抽取API
流程:
1. NER识别实体
2. OpenNRE抽取关系
3. LLM增强(可选)
4. 返回结构化三元组
"""
# Step 1: NER
ner_results = ner_server.predict([request.text])
entities = ner_results[0]['entities']
# Step 2: RE
triples = re_extractor.extract_relations(
request.text, entities
)
# Step 3: LLM增强(可选)
llm_result = None
if request.use_llm_enhance:
llm_result = llm_enhancer.extract_structured_knowledge(
request.text
)
# 合并LLM识别结果
for ent in llm_result.get('entities', []):
if ent not in entities:
entities.append(ent)
# Step 4: 存储到知识图谱
for ent, ent_type, start, end in entities:
kg_engine.stage_entity(ent, ent_type)
for triple in triples:
kg_engine.stage_relation(
triple[0]['name'], triple[0]['type'],
triple[1],
triple[2]['name'], triple[2]['type'],
triple[3], request.text
)
kg_engine.commit()
return {
"text": request.text,
"entities": entities,
"triples": triples,
"llm_enhanced": llm_result is not None
}
@app.post("/batch_extract")
async def batch_extract(request: BatchExtractRequest,
background_tasks: BackgroundTasks):
"""
批量文本知识抽取API
支持强化学习智能选择文本处理顺序
"""
if request.use_rl_selection and rl_trainer:
# 使用RL选择处理顺序
env = TextSelectionEnv(
request.texts, knowledge_graph,
ner_server.model, re_extractor.model
)
results = []
state = env.reset()
for _ in range(min(len(request.texts), 100)):
action = rl_trainer.select_action(state)
next_state, reward, done, info = env.step(action)
state = next_state
if done:
break
return {"processed": len(results), "method": "rl_optimized"}
else:
# 顺序处理
results = []
for text in request.texts:
result = await extract_knowledge(
ExtractRequest(text=text)
)
results.append(result)
return {
"results": results,
"total": len(results),
"method": "sequential"
}
@app.post("/query")
async def query_knowledge_graph(request: QueryRequest):
"""
知识图谱查询API
查询指定实体的关联网络
"""
paths = knowledge_graph.query_entity_relations(
request.entity_name,
request.entity_type,
request.depth
)
return {
"entity": request.entity_name,
"type": request.entity_type,
"depth": request.depth,
"paths": paths
}
@app.post("/risk_alert")
async def risk_alert(request: RiskAlertRequest):
"""
风险预警API
从风险实体出发,分析影响范围
"""
affected = knowledge_graph.detect_risk_propagation(
request.risk_entity,
request.max_depth
)
# 生成风险报告
risk_data = {
"entity": request.risk_entity,
"affected_count": len(affected)
}
report = llm_enhancer.generate_risk_report(risk_data, affected)
return {
"risk_entity": request.risk_entity,
"affected_entities": affected,
"report": report
}
@app.get("/health")
async def health_check():
"""健康检查接口"""
return {
"status": "healthy",
"components": {
"ner": ner_server is not None,
"re": re_extractor is not None,
"kg": knowledge_graph is not None,
"llm": llm_enhancer is not None
}
}
# 启动服务
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
7. 系统运行流程
7.1 完整处理流程
+----------------+ +----------------+ +----------------+
| 数据采集 | | 知识抽取 | | 知识融合 |
| | | | | |
| 新闻API抓取 | --> | NER识别实体 | --> | 实体消歧/链接 |
| 公告文件解析 | | RE抽取关系 | | 关系去重/合并 |
| 社交媒体监控 | | LLM辅助增强 | | 置信度更新 |
+----------------+ +----------------+ +----------------+
|
+----------------+ +----------------+ |
| 应用服务 | | 知识存储 | <------------+
| | | |
| 风险预警服务 | <-- | 动态知识图谱 |
| 关联查询服务 | | (Neo4j) |
| 报告生成服务 | | |
+----------------+ +----------------+
^
|
+----------------+ +----------------+
| 策略优化 | | 反馈学习 |
| | | |
| RL选择文本 | --> | 人工校验反馈 |
| 优先级排序 | | 下游任务效果 |
| 抽取策略调整 | | 图谱质量评估 |
+----------------+ +----------------+
7.2 实时知识抽取流程示例
输入文本:
"蚂蚁集团旗下的支付宝与中国银联达成战略合作,共同推进数字人民币在移动支付场景的应用。中国人民银行此前发布的《金融科技发展规划》为此次合作提供了政策支持。"
处理步骤:
-
NER识别实体:
- 蚂蚁集团 (ORG, 0-4)
- 支付宝 (ORG, 7-10)
- 中国银联 (ORG, 14-18)
- 数字人民币 (FIN, 29-34)
- 中国人民银行 (ORG, 48-54)
- 《金融科技发展规划》 (REG, 59-69)
-
OpenNRE抽取关系:
- (蚂蚁集团, subsidiary, 支付宝, 0.95)
- (支付宝, cooperate, 中国银联, 0.88)
- (中国人民银行, issue, 《金融科技发展规划》, 0.92)
-
LLM增强推理:
- 推断出: (《金融科技发展规划》, support, 支付宝×中国银联合作, 0.78)
-
知识图谱存储:
- 创建6个实体节点
- 创建3个直接关系 + 1个LLM推理关系
- 记录版本历史
7.3 API调用示例
python
import requests
# 1. 单文本知识抽取
response = requests.post("http://localhost:8000/extract", json={
"text": "蚂蚁集团旗下的支付宝与中国银联达成战略合作...",
"use_llm_enhance": True
})
print(response.json())
# 2. 知识图谱查询
response = requests.post("http://localhost:8000/query", json={
"entity_name": "支付宝",
"entity_type": "ORG",
"depth": 2
})
print(response.json())
# 3. 风险预警
response = requests.post("http://localhost:8000/risk_alert", json={
"risk_entity": "恒大集团",
"max_depth": 3
})
print(response.json())
8. 总结与展望
8.1 方案总结
本方案设计了一个融合深度学习NER、OpenNRE关系抽取、动态知识图谱、大语言模型和强化学习的智能金融风控系统。各组件的技术要点总结:
| 组件 | 核心技术 | 作用 |
|---|---|---|
| NER | BERT-BiLSTM-CRF | 高精度识别金融实体 |
| RE | OpenNRE框架 | 灵活抽取实体关系 |
| 知识图谱 | Neo4j + 版本控制 | 动态管理知识演化 |
| 大模型 | ChatGLM等 | 增强推理和生成能力 |
| 强化学习 | DQN | 优化文本选择策略 |
8.2 创新点
- OpenNRE与NER的端到端整合:将实体识别和关系抽取无缝衔接,形成完整的知识抽取管道
- 动态知识图谱时序管理:借鉴论文CSALT-DKGE的分层学习思想,实现知识增量更新
- LLM与OpenNRE的互补设计:LLM处理长尾和模糊场景,OpenNRE处理高频确定性场景
- RL驱动的主动学习:通过强化学习优化标注资源分配,提升知识构建效率
8.3 未来改进方向
- 引入图神经网络(GNN):在知识图谱上使用R-GCN进行关系推理补全
- 多模态融合:结合表格、图像等非文本信息进行知识抽取
- 联邦学习:在保护数据隐私的前提下实现跨机构知识共享
- 持续学习:设计增量学习机制,使模型适应新领域和新关系类型
参考文献
- Huang Z, Xu W, Yu K. Bidirectional LSTM-CRF models for sequence tagging. arXiv:1508.01991, 2015.
- Devlin J, Chang M W, Lee K, et al. BERT: Pre-training of deep bidirectional transformers for language understanding. NAACL-HLT, 2019.
- Han X, Gao T, Yao Y, et al. OpenNRE: An open and extensible toolkit for neural relation extraction. EMNLP-IJCNLP, 2019.
- Lin Y, Shen S, Liu Z, et al. Neural relation extraction with selective attention over instances. ACL, 2016.
- Zeng D, Liu K, Lai S, et al. Relation classification via convolutional deep neural network. COLING, 2014.
- Zeng D, Liu K, Chen Y, et al. Distant supervision for relation extraction via piecewise convolutional neural networks. EMNLP, 2015.
- Sutton C, McCallum A. An introduction to conditional random fields. Foundations and Trends in Machine Learning, 2012.
- Schlichtkrull M, Kipf T N, Bloem P, et al. Modeling relational data with graph convolutional networks. ESWC, 2018.
- Zhang C, Li Q, Song D. Aspect-based sentiment classification with aspect-specific graph convolutional networks. EMNLP-IJCNLP, 2019.
- Sun T, Shao Y, Qiu X, et al. Learning sparse sharing architectures for multiple tasks. AAAI, 2020.