【突发公共事件智能分析新范式:基于PERSIA框架与大模型的知识图谱构建实践】

从海量新闻文本中,如何快速、准确地识别关键人物、言论和行动?PERSIA框架为我们提供了一个全新的视角。

github项目地址:https://github.com/xy200303/PERSIA

1. 引言:突发公共事件分析的挑战

在信息爆炸的时代,突发公共事件(如疫情、自然灾害、安全事故等)相关信息呈现爆发式增长。传统的信息分析方法面临诸多挑战:

  • 信息碎片化:事件相关信息分散在不同媒体、不同时间段
  • 实体不统一:同一人物、机构在不同报道中的表述各异
  • 关系模糊:言论与行动之间的因果关系难以厘清
  • 分析滞后:人工分析耗时耗力,难以实时响应

为解决这些问题,我们提出了PERSIA框架(Person-centric Emergency Response Statement and Interaction Analysis Framework),并基于大语言模型和知识图谱技术,构建了一套端到端的智能分析系统。

2. PERSIA框架:人物中心的突发公共事件分析模型

2.1 框架核心思想

PERSIA框架的核心是以人物为中心 ,围绕"谁说了什么"、"谁做了什么"、"产生了什么影响"三个核心问题展开分析。这种设计源于对突发公共事件本质的深刻认识:事件由人物驱动,决策由言论体现,结果由行动塑造

2.2 四层分析结构

PERSIA框架采用四层递进的分析结构:

复制代码
第一层:人物识别与关联
    核心人物定位(决策者、专家、受影响者等)
    人物关系网络构建

第二层:言论抽取与分析
    结构化人物言论
    言论情境标注(时间、场合、对象、媒体)

第三层:行动追踪与关联
    行动类型分类(决策、执行、响应等)
    言论→行动关联分析

第四层:因果推理与事件链构建
    多级因果链挖掘
    时间序列重建

2.3 实体标准化体系

为了确保信息的一致性,我们定义了严格的实体标准化体系:

python 复制代码
# 实体标准化格式(必须严格遵守)

## 1. 机构/组织实体
【格式:[ORG]官方全称(机构类型)】
- 正确示例:[ORG]国家卫生健康委员会(政府机构)
- 错误示例:卫健委、国家卫健委

## 2. 人物实体
【格式:[PERSON]姓名(职位@所属机构)】
- 正确示例:[PERSON]马晓伟(主任@国家卫生健康委员会)
- 错误示例:马晓伟、卫健委主任马晓伟
- **注意**:无需额外标注人物角色,通过职位体现

## 3. 地区/位置实体
【格式:[LOC]标准名称(地区类型)】
- 正确示例:[LOC]湖北省(省级行政区)、[LOC]武汉市(城市)
- 错误示例:湖北、武汉

## 4. 言论实体
【格式:[STATEMENT]内容摘要[言论类型](时间)】
- 正确示例:[STATEMENT]优化疫情防控措施[政策声明](2023-01-08)
- 错误示例:将优化防控措施、政策声明
- **要求**:内容摘要20字以内,时间格式YYYY-MM-DD

## 5. 行动实体
【格式:[ACTION]行动描述[行动类型](时间)】
- 正确示例:[ACTION]实施"乙类乙管"政策[决策](2023-01-08)
- 错误示例:执行乙类乙管、决策行动
- **要求**:行动描述15字以内

## 6. 结果实体
【格式:[OUTCOME]结果描述[结果类型](时间)】
- 正确示例:[OUTCOME]疫情得到有效控制[积极结果](2023-03-15)
- 错误示例:控制疫情、结果

# 关系标签列表(只能使用以下标签)
## 主体关系
    BELONGS_TO      # 人物属于机构
    LEADS           # 人物领导机构
    COLLABORATES_WITH  # 人物间合作
## 言论关系
    SAID            # 说/表示
    ANNOUNCED       # 宣布/发布
    RESPONDED_TO    # 回应/答复
    REFUTED         # 反驳/否认
    SUGGESTED       # 建议/提议
    QUOTED          # 引用/援引
## 行动关系
    IMPLEMENTED     # 实施/执行
    PARTICIPATED_IN # 参与/出席
    CONDUCTED       # 开展/进行
    ISSUED          # 印发/下发
    INITIATED       # 发起/启动
## 因果与影响关系
    BASED_ON        # 基于/依据
    LEAD_TO         # 导致/使得
    TRIGGERED       # 触发/引起
    RESULTED_IN     # 产生结果
    CAUSED_BY       # 由...引起
    CONTRIBUTED_TO  # 促进/促成
    FOLLOWED_BY     # 随后发生
    PRECEDED_BY     # 之前发生

3. 基于大模型的PERSIA信息抽取

3.1 系统架构

我们构建了基于大语言模型的智能抽取系统,整体架构如下:

3.2 提示词工程

关键的一步是设计高质量的提示词,引导大模型按照PERSIA框架进行信息抽取:

python 复制代码
NER_PERSIA_PROMPT= """
# 角色
你是PERSIA框架专家------人物中心化的突发公共事件信息抽取系统。

# 核心约束
1. **标准化输出**:必须严格按照以下标准化格式输出
2. **禁止自创**:不能创建任何未定义的实体类型和关系标签
3. **简洁格式**:去除复杂嵌套格式,使用统一标准格式

# 实体标准化格式(必须严格遵守)

## 1. 机构/组织实体
【格式:[ORG]官方全称(机构类型)】
- 正确示例:[ORG]国家卫生健康委员会(政府机构)
- 错误示例:卫健委、国家卫健委

## 2. 人物实体
【格式:[PERSON]姓名(职位@所属机构)】
- 正确示例:[PERSON]马晓伟(主任@国家卫生健康委员会)
- 错误示例:马晓伟、卫健委主任马晓伟
- **注意**:无需额外标注人物角色,通过职位体现

## 3. 地区/位置实体
【格式:[LOC]标准名称(地区类型)】
- 正确示例:[LOC]湖北省(省级行政区)、[LOC]武汉市(城市)
- 错误示例:湖北、武汉

## 4. 言论实体
【格式:[STATEMENT]内容摘要[言论类型](时间)】
- 正确示例:[STATEMENT]优化疫情防控措施[政策声明](2023-01-08)
- 错误示例:将优化防控措施、政策声明
- **要求**:内容摘要20字以内,时间格式YYYY-MM-DD

## 5. 行动实体
【格式:[ACTION]行动描述[行动类型](时间)】
- 正确示例:[ACTION]实施"乙类乙管"政策[决策](2023-01-08)
- 错误示例:执行乙类乙管、决策行动
- **要求**:行动描述15字以内

## 6. 结果实体
【格式:[OUTCOME]结果描述[结果类型](时间)】
- 正确示例:[OUTCOME]疫情得到有效控制[积极结果](2023-03-15)
- 错误示例:控制疫情、结果

# 关系标签列表(只能使用以下标签)
## 主体关系
    BELONGS_TO      # 人物属于机构
    LEADS           # 人物领导机构
    COLLABORATES_WITH  # 人物间合作
## 言论关系
    SAID            # 说/表示
    ANNOUNCED       # 宣布/发布
    RESPONDED_TO    # 回应/答复
    REFUTED         # 反驳/否认
    SUGGESTED       # 建议/提议
    QUOTED          # 引用/援引
## 行动关系
    IMPLEMENTED     # 实施/执行
    PARTICIPATED_IN # 参与/出席
    CONDUCTED       # 开展/进行
    ISSUED          # 印发/下发
    INITIATED       # 发起/启动
## 因果与影响关系
    BASED_ON        # 基于/依据
    LEAD_TO         # 导致/使得
    TRIGGERED       # 触发/引起
    RESULTED_IN     # 产生结果
    CAUSED_BY       # 由...引起
    CONTRIBUTED_TO  # 促进/促成
    FOLLOWED_BY     # 随后发生
    PRECEDED_BY     # 之前发生
# 输出格式
每行一个三元组,严格格式:
[实体类型]实体标准描述 <SEP> 关系标签 <SEP> [实体类型]实体标准描述
**注意**:不要添加任何注释、层级标注或其他文本

# 抽取流程规范

## 第一步:识别所有实体
1. 提取机构实体:[ORG]全称(类型)
2. 提取人物实体:[PERSON]姓名(职位@机构)
3. 提取言论实体:[STATEMENT]摘要[类型](时间)
4. 提取行动实体:[ACTION]描述[类型](时间)
5. 提取结果实体:[OUTCOME]描述[类型](时间)

## 第二步:建立标准关系
1. 人物与机构:BELONGS_TO/LEADS
2. 人物与言论:SAID/ANNOUNCED/SUGGESTED
3. 言论与行动:LEAD_TO/TRIGGERED
4. 行动与结果:RESULTED_IN
5. 时间顺序:FOLLOWED_BY/PRECEDED_BY

## 第三步:严格格式化输出
- 每行一个三元组
- 使用标准实体格式
- 使用预定义关系标签
- 确保时间信息准确
- 任何行动、言论都必须指明是谁做的,谁说的

# 严格禁止行为
1. 禁止使用简称(必须用全称)
2. 禁止省略职位/机构信息
3. 禁止自创关系标签(只能用预定义标签)
4. 禁止合并多个关系(每行只一个关系)
5. 禁止输出非标准化实体(只能用定义的类型)
6. 禁止添加注释、说明或其他文本
7. 禁止使用复杂嵌套格式(如@场合>对象#媒体)
8. 禁止输出层级标记(如// 第一层)
9. 禁止出现没有主体的事件或者言论实体

# 标准化示例

## 示例1:简单情况
输入:"卫健委表示,将优化防控措施。"
正确输出:
[ORG]国家卫生健康委员会(政府机构) <SEP> SAID <SEP> [STATEMENT]将优化疫情防控措施[政策声明](当日)

错误输出:
卫健委 <SEP> 说 <SEP> 优化防控措施
// 言论层
[PERSON]马晓伟(主任@国家卫生健康委员会)(决策者) <SEP> ANNOUNCED <SEP> [STATEMENT]优化措施[声明]

## 示例2:复杂情况
输入:"钟南山院士建议老年人打疫苗。北京市随即开展了接种工作。"
正确输出:
[PERSON]钟南山(院士@中国工程院) <SEP> SUGGESTED <SEP> [STATEMENT]建议老年人接种疫苗[专家建议](当日)
[STATEMENT]建议老年人接种疫苗[专家建议](当日) <SEP> LEAD_TO <SEP> [ACTION]开展老年人疫苗接种[执行](当日)
[ORG]北京市政府(地方政府) <SEP> CONDUCTED <SEP> [ACTION]开展老年人疫苗接种[执行](当日)

错误输出:
[PERSON]钟南山(院士@中国工程院)(专家) <SEP> 建议 <SEP> [STATEMENT]老年人打疫苗[建议]
[ACTION]接种工作[执行](当日) <SEP> 由 <SEP> [ORG]北京(政府)

## 示例3:完整事件
输入:"2023年1月8日,国家卫健委主任马晓伟宣布实施'乙类乙管'政策。钟南山院士表示支持这一调整。"
正确输出:
[PERSON]马晓伟(主任@国家卫生健康委员会) <SEP> ANNOUNCED <SEP> [STATEMENT]实施"乙类乙管"政策[政策声明](2023-01-08)
[PERSON]钟南山(院士@中国工程院) <SEP> RESPONDED_TO <SEP> [STATEMENT]实施"乙类乙管"政策[政策声明](2023-01-08)
[PERSON]钟南山(院士@中国工程院) <SEP> SAID <SEP> [STATEMENT]支持政策调整[观点表达](2023-01-09)

# 完整处理流程示例

输入文本:"在国务院联防联控机制新闻发布会上,国家卫健委主任马晓伟宣布,自2023年1月8日起对新型冠状病毒感染实施'乙类乙管'。钟南山院士随后表示支持。"

**标准化输出**:
[PERSON]马晓伟(主任@国家卫生健康委员会) <SEP> BELONGS_TO <SEP> [ORG]国家卫生健康委员会(政府机构)
[PERSON]马晓伟(主任@国家卫生健康委员会) <SEP> ANNOUNCED <SEP> [STATEMENT]实施"乙类乙管"政策[政策声明](2023-01-08)
[ORG]国务院联防联控机制(协调机构) <SEP> IMPLEMENTED <SEP> [ACTION]实施"乙类乙管"政策[决策](2023-01-08)
[PERSON]钟南山(院士@中国工程院) <SEP> RESPONDED_TO <SEP> [STATEMENT]实施"乙类乙管"政策[政策声明](2023-01-08)
[PERSON]钟南山(院士@中国工程院) <SEP> SAID <SEP> [STATEMENT]支持"乙类乙管"政策[观点表达](2023-01-09)
[ACTION]实施"乙类乙管"政策[决策](2023-01-08) <SEP> RESULTED_IN <SEP> [OUTCOME]疫情防控措施优化[积极结果](2023-01-20)

# 错误检查清单
在输出前检查:
1. 所有实体是否都是预定义类型?
2. 所有关系标签是否都在列表中?
3. 是否每行只有一个三元组?
4. 是否所有机构都用全称?
5. 是否所有人物都有职位@机构?
6. 是否所有言论都有[类型](时间)?
7. 是否所有行动都有[类型](时间)?
8. 是否没有添加任何注释或说明?

# 最终输出要求
只输出符合以下格式的三元组,每行一个:
[类型]描述 <SEP> 关系 <SEP> [类型]描述

不需要说明、不需要注释、不需要层级标记
严格按照标准化格式
只使用预定义的实体类型和关系标签

现在开始,严格按照标准化要求抽取信息。
"""
"""

3.3 异步批量处理

为处理大量新闻文本,我们实现了高效的异步批量处理机制:

python 复制代码
#利用大模型进行并发抽取,结果保存到./outputs/file,支持断点恢复
CSV_PATH = "./dataset/sentences.csv"
MAX_CONCURRENT = 20  # 最大并发数
OUTPUT_PATH = "./outputs/file"
LOG_DIR = "./logs"
os.makedirs(LOG_DIR,exist_ok=True)
os.makedirs(OUTPUT_PATH, exist_ok=True)
llm_c = LLMClient(system_prompt=NER_PERSIA_PROMPT,max_concurrent_requests=MAX_CONCURRENT,history_max=-1)

async def process_text(id,text,pbar: tqdm = None):
    try:
        file_path = os.path.join(OUTPUT_PATH,str(id)+".txt")
        res=await llm_c.allm_call(text)
        with open(file_path,"w",encoding="utf-8") as f:
            f.write(res)
        if pbar:
            pbar.update(1)
            pbar.set_postfix_str(f"处理完成: {id}")
        return True
    except Exception as e:
        log_file=os.path.join(LOG_DIR,str(id)+".log")
        with open(log_file,"a",encoding="utf-8") as f:
            f.write(str(e))
        if pbar:
            pbar.set_postfix_str(f"处理失败: {id} - {str(e)[:30]}")
        return False

async def main():
    # 读取csv新闻文件
    data = pd.read_csv("./dataset/sentences.csv")
    print("数据统计:")
    print(f"总行数: {len(data)}")
    print(f"唯一文档数: {data['id'].nunique()}")
    print("前几个ID:", data['id'].unique()[:5])
    # 根据id进行聚合,组成完成的新闻描述
    group_data = data.groupby("id")
    tasks=[]
    print("\n📁 正在准备处理任务...")
    pbar = tqdm(total=len(group_data), desc="处理分组")
    for index,group in group_data:
        file_path = os.path.join(OUTPUT_PATH,str(index)+".txt")
        if os.path.exists(file_path):
            pbar.update(1)
            continue
        text = "".join(group["sentence"].tolist())
        tasks.append(asyncio.create_task(process_text(index,text,pbar=pbar)))
    # 等待所有任务完成
    results = await asyncio.gather(*tasks, return_exceptions=True)
    pbar.close()
    print("\n📊 处理结果统计:")
    success=0
    for result in results:
        if result:
            success += 1
    print("成功:",success)

if __name__ == "__main__":
    asyncio.run(main())

4. 三元组解析与知识图谱构建

4.1 三元组解析器

大模型输出的是标准化的文本三元组,我们需要将其解析为结构化数据:

python 复制代码
import json
import re
from typing import List, Dict, Any
import networkx as nx
import matplotlib.pyplot as plt
from pyvis.network import Network
import pandas as pd
def parse_triples(output_text: str) -> List[Dict]:
    """解析三元组文本为结构化数据"""
    triples = []
    lines = output_text.strip().split('\n')
    for i, line in enumerate(lines):
        if '<SEP>' not in line:
            continue

        parts = line.split(' <SEP> ')
        if len(parts) != 3:
            continue
        head, relation, tail = parts
        # 解析实体格式:[TYPE]名称(描述)
        head_match = re.match(r'\[(\w+)\](.*?)\((.*?)\)', head)
        tail_match = re.match(r'\[(\w+)\](.*?)\((.*?)\)', tail)

        if head_match and tail_match:
            triple = {
                'id': i,
                'head': {
                    'id': f"entity_{i}_head",
                    'name': head_match.group(2).strip(),
                    'type': head_match.group(1),
                    'description': head_match.group(3),
                    'label': f"{head_match.group(2)} ({head_match.group(1)})"
                },
                'relation': relation,
                'tail': {
                    'id': f"entity_{i}_tail",
                    'name': tail_match.group(2).strip(),
                    'type': tail_match.group(1),
                    'description': tail_match.group(3),
                    'label': f"{tail_match.group(2)} ({tail_match.group(1)})"
                }
            }
            triples.append(triple)
    return triples

4.2 知识图谱构建

基于解析的三元组,我们构建知识图谱网络:

python 复制代码
import hashlib
from pyvis.network import Network
import webbrowser
def truncate_text(text, max_length=15):
    """截断长文本,添加省略号"""
    if len(text) <= max_length:
        return text
    return text[:max_length] + "..."
def get_entity_hash(name):
    return name
    """为实体生成哈希键,确保相同实体得到相同哈希"""
    # 对名称进行标准化处理
    clean_name = name.strip()
    # 生成哈希值(使用MD5,取前8位)
    hash_obj = hashlib.md5(clean_name.encode())
    hash_hex = hash_obj.hexdigest()
    # 返回哈希键
    return f"{hash_hex}"

def get_shape_by_type(entity_type):
    # 根据类型设置不同形状
    if entity_type == 'PERSON':
        shape = 'dot'
        size = 25
    elif entity_type == 'ORG':
        shape = 'diamond'
        size = 30
    elif entity_type == 'LOC':
        shape = 'triangle'
        size = 30
    else:  # STATEMENT
        shape = 'square'
        size = 35
    shape="dot"
    return shape,size
# 提取[]中的内容
def extract_brackets_content(text):
    print(text)
    """提取[]括号中的内容"""
    pattern = r'\[(.*?)\]'  # 匹配[]中的内容,非贪婪匹配
    matches = re.findall(pattern, text)
    if len(matches) == 0:
        return text
    return matches[0]
def build_network(triples):
    # 创建网络图
    net = Network(height="600px", width="100%", directed=True)
    # 节点颜色映射
    node_colors = {
        'PERSON': '#FF6B6B',
        'ORG': '#4ECDC4',
        'LOC': '#FFD166',
        'STATEMENT': '#95D2B3'
    }
    # 按名称合并相同实体
    # 使用名称作为节点的唯一标识
    entity_id_map = {}  # 映射:name -> node_id
    # 先收集所有实体
    all_entities = set()
    for item in triples:
        head_entity_name = item['head']['name']
        tail_entity_name = item['tail']['name']
        head_entity_id = item['head']['id']
        tail_entity_id = item['tail']['id']
        head_entity_type = item['head']['type']
        tail_entity_type = item['tail']['type']
        if head_entity_name not in all_entities:
            all_entities.add(head_entity_name)
            entity_id_map[get_entity_hash(head_entity_name)] = head_entity_id
            shape,size=get_shape_by_type(head_entity_type)
            if head_entity_type=="STATEMENT":
                head_entity_name=extract_brackets_content(head_entity_name)
            net.add_node(
                head_entity_id,
                label=head_entity_name,
                title=f"{head_entity_name}<br>类型: {head_entity_type}",
                color=node_colors.get(head_entity_type, '#CCCCCC'),
                shape=shape,
                size=size
            )
        if tail_entity_name not in all_entities:
            all_entities.add(tail_entity_name)
            entity_id_map[get_entity_hash(tail_entity_name)] = tail_entity_id
            shape,size=get_shape_by_type(tail_entity_type)
            if tail_entity_type=="STATEMENT":
                tail_entity_name=extract_brackets_content(tail_entity_name)
            net.add_node(
                tail_entity_id,
                label=tail_entity_name,
                title=f"{tail_entity_name}<br>类型: {tail_entity_type}",
                color=node_colors.get(tail_entity_type, '#CCCCCC'),
                shape=shape,
                size=size
            )
    # 添加边
    for item in triples:
        head_name = item['head']['name']
        tail_name = item['tail']['name']

        head_id = entity_id_map[get_entity_hash(head_name)]
        tail_id = entity_id_map[get_entity_hash(tail_name)]

        net.add_edge(
            head_id,
            tail_id,
            label=item['relation'],
            arrows='to'
        )
    return net

5. 知识图谱可视化

5.1 可视化设计原则

我们设计了多层次的可视化方案:

  1. 节点编码:不同实体类型使用不同颜色和形状
  2. 关系可视化:不同类型的关系使用不同线型和颜色
  3. 时间维度:通过节点大小、颜色深浅表示时间先后
  4. 重要性排序:通过节点中心性计算,突出关键实体

5.2 交互式可视化实现

使用PyVis库实现交互式知识图谱可视化:

python 复制代码
#可视化单个数据
net=build_network(triples_list[2])
# 保存HTML文件
net.save_graph("knowledge_graph.html")
# 在浏览器中打开
webbrowser.open("knowledge_graph.html")
print("\n🌐 已在浏览器中打开知识图谱")

5.3 可视化效果展示



附录:代码资源

完整代码已开源在GitHub:https://github.com/xy200303/PERSIA

核心文件结构

复制代码
C:.
│  config.yaml
│  examples.py
│  knowledge_graph.html
│  PERSIA实体抽取.py
│  requirements.txt
│  大模型知识图谱抽取.ipynb
├─.idea
│  │  .gitignore
│  │  misc.xml
│  │  modules.xml
│  │  workspace.xml
│  │  突发公共事件抽取.iml
│  │
│  └─inspectionProfiles
│          profiles_settings.xml
├─config
│  │  config.py
│  │  __init__.py
│  │
│  └─__pycache__
│          config.cpython-311.pyc
│          __init__.cpython-311.pyc
│
├─dataset
│      sentences.csv
│
├─lib
│  ├─bindings
│  │      utils.js
│  │
│  ├─tom-select
│  │      tom-select.complete.min.js
│  │      tom-select.css
│  │
│  └─vis-9.1.2
│          vis-network.css
│          vis-network.min.js
│
├─llm
│  │  client.py
│  │  __init__.py
│  │
│  └─__pycache__
│          client.cpython-311.pyc
│          __init__.cpython-311.pyc
│
├─logs
├─models
│  │  __init__.py
│  │
│  └─__pycache__
│          __init__.cpython-311.pyc
│
├─outputs
│  └─file
└─prompts
    │  __init__.py
    │
    └─__pycache__
            __init__.cpython-311.pyc

快速开始

bash 复制代码
# 安装依赖
pip install -r requirements.txt

# 运行示例
python PERSIA实体抽取.py

本博客介绍了PERSIA框架的理论基础、技术实现和应用案例。希望通过这个框架,能够为突发公共事件的分析和理解提供新的工具和思路。欢迎在评论区交流讨论!

相关推荐
Fuly10242 小时前
如何评估LLM和Agent质量
人工智能
weisian1512 小时前
入门篇--知名企业-12-Stability AI:不止于“艺术”,这是一场开源AI的全面起义
人工智能·开源·stablility ai
五月君_3 小时前
Nuxt UI v4.3 发布:原生 AI 富文本编辑器来了,Vue 生态又添一员猛将!
前端·javascript·vue.js·人工智能·ui
wjykp3 小时前
109~111集成学习
人工智能·机器学习·集成学习
小程故事多_803 小时前
Spring AI 赋能 Java,Spring Boot 快速落地 LLM 的企业级解决方案
java·人工智能·spring·架构·aigc
xcLeigh3 小时前
AI的提示词专栏:写作助手 Prompt,从提纲到完整文章
人工智能·ai·prompt·提示词
Jinkxs3 小时前
Elasticsearch - 解决 Elasticsearch 内存占用过高的问题
大数据·elasticsearch·搜索引擎
Micro麦可乐3 小时前
分词搜索必须上Elasticsearch?试试MySQL分词查询,轻松满足大多数搜索场景的需求
大数据·mysql·elasticsearch·分词搜索·分词查询
QYR_113 小时前
热塑性复合树脂市场报告:行业现状、增长动力与未来机遇
大数据·人工智能·物联网