知识图谱作为一种高效的数据表示方法,能够将大数据中分散的信息连接成结构化、可查询的格式,显著提升数据发现效率。实践表明,采用知识图谱技术可将数据探索时间减少多达70%,从而极大地优化数据分析流程。

从零构建知识图谱:使用大语言模型处理复杂数据的11步实践指南_知识图谱

本文将基于相关理论知识和方法构建一个完整的端到端项目,系统展示如何利用知识图谱方法对大规模数据进行处理和分析。

环境配置与依赖安装

首先,我们需要安装必要的Python库以支持后续的开发工作。以下是所需的关键依赖包:

# 安装库(仅运行一次)  
 pipinstallopenairdflibspacypyvisdatasetsscikit-learnmatplotlibtqdmpandas
  • 1.
  • 2.

安装完成后,为确保所有依赖正常加载,可能需要重启Jupyter内核或运行环境。接下来,我们导入所有必要的库:

# Import necessary libraries  
importos  
importre  
importjson  
fromcollectionsimportCounter  
importmatplotlib.pyplotasplt  
fromtqdm.autoimporttqdm  
importpandasaspd  
importtime  

# NLP and KG libraries  
importspacy  
fromrdflibimportGraph, Literal, Namespace, URIRef  
fromrdflib.namespaceimportRDF, RDFS, XSD, SKOS# 添加SKOS用于altLabel  

# OpenAI client for LLM  
fromopenaiimportOpenAI  

# Visualization  
frompyvis.networkimportNetwork  

# Hugging Face datasets library  
fromdatasetsimportload_dataset  

# For embedding similarity  
importnumpyasnp  
 fromsklearn.metrics.pairwiseimportcosine_similarity
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

至此,我们已完成开发环境的准备工作,所有必要的库已成功导入。

数据集概述与加载

本项目使用CNN/DailyMail数据集作为研究对象。该数据集包含超过30万篇新闻文章及其对应的人工撰写摘要,是进行实体、关系和事件提取的理想资源。

使用Hugging Face datasets库加载数据集:

# 使用特定版本可以帮助保持一致性  
 cnn_dm_dataset=load_dataset("cnn_dailymail", "3.0.0")
  • 1.
  • 2.

我们选择版本"3.0.0",这是该数据集的最新稳定版本。下面打印数据集的基本信息:

# 计算记录总数  
total_records=len(cnn_dm_dataset["train"]) +len(cnn_dm_dataset["validation"]) +len(cnn_dm_dataset["test"])  

# 打印总数和样本记录  
print(f"Total number of records in the dataset: {total_records}\n")  
print("Sample record from the training dataset:")  
print(cnn_dm_dataset["train"][0])  

#### OUTPUT ####  
Totalnumberofrecordsinthedataset: 311971  

Samplerecordfromthetrainingdataset:  
 {'article': 'LONDON, England (Reuters) -- Harry Potter star Daniel ...'}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

通过输出可知,该数据集包含311,971篇新闻文章,这是一个相当庞大的语料库。从中提取有价值的洞察确实是一项具有挑战性的任务,而知识图谱正是解决此类问题的有效工具。

数据获取与预处理

构建知识图谱时,直接处理整个包含30万多篇文章的数据集既不高效也不切实际,因为并非所有内容都具有相关性。更为合理的做法是将数据按主题或领域分割为子集,如技术新闻、体育新闻等,分别构建相应的知识图谱。

在大数据处理流程的早期阶段,将数据分解为更易管理的部分是一项关键步骤。对于新闻文章数据集,我们可以采用基于关键词的方法进行初步筛选。

首先定义与技术公司收购相关的关键词集合:

# 定义与技术公司收购相关的关键词  
 ACQUISITION_KEYWORDS= ["acquire", "acquisition", "merger", "buyout", "purchased by", "acquired by", "takeover"]  
 TECH_KEYWORDS= ["technology", "software", "startup", "app", "platform", "digital", "AI", "cloud"]
  • 1.
  • 2.
  • 3.

这些关键词是在文本分析中常见的术语,我们预先定义它们以简化处理流程。接下来,使用这些关键词从训练集中筛选相关文章:

# 仅取训练集  
cnn_dm_dataset_train=cnn_dm_dataset['train']  

# 初始化一个空列表来存储过滤后的文章  
filtered_articles= []  

# 遍历数据集并基于关键词过滤文章  
forrecordincnn_dm_dataset_train:  
    # 检查任何关键词是否出现在文章文本中  
    found_keyword=False  
    forkeywordinACQUISITION_KEYWORDS:  
        ifkeyword.lower() inrecord['article'].lower():  
            found_keyword=True  
            break  # 一旦找到关键词就停止  
      
    # 如果找到关键词,将文章添加到过滤列表中  
    iffound_keyword:  
         filtered_articles.append(record)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

现在检查过滤后的文章数量并查看其中一个样本:

# 打印过滤后文章的总数  
print(f"Total number of filtered articles: {len(filtered_articles)}")  

# 打印一个过滤后文章的样本  
print("\nSample of a filtered article:")  
print(filtered_articles[0]['article'])  

### OUTPUT ####  
Totalnumberoffilteredarticles: 65249  

Sampleofafilteredarticle:  
SANDIEGO, California (CNN) --Youmustknowwhatsreallydrivingthe   
 immigrationdebate...
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

通过关键词筛选,我们将数据集缩减至约65,000篇文章。接下来需要对这些文章进行清洗处理,移除不必要的信息。这一步骤尤为重要,因为这些数据将作为输入传递给大语言模型(LLM),过多的冗余信息会影响处理效率和成本。

在新闻数据清洗过程中,我们需要移除链接、不必要的特殊字符、发布渠道名称等元素:

cleaned_articles= []  

forrecordinfiltered_articles:  
    text=record['article']  

    # 使用正则表达式进行基本清理  
    text=re.sub(r'^\(CNN\)\s*(--)?\s*', '', text)  # 删除(CNN)前缀  
    text=re.sub(r'By .*? for Dailymail\.com.*?Updated:.*', '', text, flags=re.I|re.S)  # 删除署名  
    text=re.sub(r'PUBLISHED:.*?UPDATED:.*', '', text, flags=re.I|re.S)  # 删除发布/更新  
    text=re.sub(r'Last updated at.*on.*', '', text, flags=re.I)  # 删除最后更新  
    text=re.sub(r'https?://\S+|www\.\S+', '[URL]', text)  # 替换URL  
    text=re.sub(r'<.*?>', '', text)  # 删除HTML标签  
    text=re.sub(r'\b[\w.-]+@[\w.-]+\.\w+\b', '[EMAIL]', text)  # 替换邮箱  
    text=re.sub(r'\s+', ' ', text).strip()  # 规范化空白  

    # 存储清理结果  
    cleaned_articles.append({  
        "id": record['id'],  
        "cleaned_text": text,  
        "summary": record.get('highlights', '')  
     })
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

这些清洗步骤基于对数据的观察和分析,旨在保留关键信息的同时最大限度地减少每篇文章的大小,为后续的知识图谱构建做好准备。

使用核心NLP技术定义实体标签

从零构建知识图谱:使用大语言模型处理复杂数据的11步实践指南_人工智能_02

步骤1:实体标签检测

当前我们有65,000多篇新闻文章,从中提取实体是一项具有挑战性的任务。尽管可以使用大语言模型(LLM)从每个文本段落中提取实体,但首先需要确定LLM应该关注哪些类型的实体。

如果没有明确的指导,LLM可能会从每个文本块中提取不同类型的实体,导致结果不一致。为解决这个问题,我们需要使用自然语言处理(NLP)技术来定义一个固定的实体类型集合,作为LLM的提取依据。

虽然有多种方法可以实现这一目标,包括使用嵌入和其他高级技术,但本项目将采用预训练的SpaCy模型作为基础方法。该模型将分析我们的数据,提取实体标签,然后我们将使用这些标签指导LLM提取特定类型的实体:

# 下载并加载SpaCy的英语语言模型  
# 只需运行一次)  
spacy.cli.download("en_core_web_sm")  
nlp = spacy.load("en_core_web_sm")  

# 初始化一个计数器来保存实体标签计数(例如,PERSON, ORG, DATE)  
entity_counts = Counter()  

# 遍历每篇文章并应用spaCy的命名实体识别  
for article in cleaned_articles:  
    text = article['cleaned_text']  # 获取清理后的文本  
    doc = nlp(text)  # 使用spaCy处理文本  

    # 计算文本中找到的每个实体标签  
    for ent in doc.ents:  
        entity_counts[ent.label_] += 1
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

打印实体计数情况:

print(entity_counts)  

### OUTPUT ###  
spaCy Entity Counts:  
  ORG: 2314  
  GPE: 1253  
  PERSON: 524  
  NORP: 3341  
  CARDINAL: 7542  
  DATE: 6344  
  ...
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

为了更直观地了解数据中的实体分布情况,我们可以绘制这些标签的柱状图:

# 提取标签和计数  
labels, counts = zip(*entity_counts)  

# 绘制条形图  
plt.figure(figsize=(12, 7))  # 设置图形大小  
plt.bar(labels, counts, color='skyblue')  # 创建条形图  
plt.title("Top Entity Type Distribution (via spaCy)")  # 图表标题  
plt.ylabel("Frequency")  # Y轴标签  
plt.xlabel("Entity Label")  # X轴标签  
plt.xticks(rotation=45, ha="right")  # 旋转X轴标签以提高可见性  
plt.tight_layout()  # 调整布局以确保所有内容都适合  
plt.show()  # 显示图形
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

从零构建知识图谱:使用大语言模型处理复杂数据的11步实践指南_深度学习_03

目前我们使用的是SpaCy的小型模型(en_core_web_sm),如需提取更深入、更准确的实体标签,可以考虑切换到更大的模型。这些实体标签将作为指导,帮助我们的大语言模型(如Microsoft的Phi-4)从文章中提取相关实体。

从零构建知识图谱:使用大语言模型处理复杂数据的11步实践指南_深度学习_04

步骤2:实体(节点)提取

实体作为知识图谱中的节点,需要从文本中精确提取。为此,我们需要定义一个系统提示(指导LLM如何处理使用SpaCy提取的实体类型)、用户提示(即文章内容)以及其他必要组件。

首先,建立与LLM的连接:

# 使用提供的配置初始化OpenAI客户端  
client = OpenAI(  
    base_url="YOUR LLM API Provider link",  
    api_key="LLM API KEY"  
)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

接下来,创建一个辅助函数,用于打包请求并发送给LLM。该函数接收系统提示、用户提示(文章文本)和模型名称作为参数:

def call_llm(system_prompt, user_prompt, model_name):  
    """  
    向语言模型(LLM)发送请求,根据提供的提示提取实体。

    Args:  
        system_prompt (str): 给LLM的指示或上下文(例如,如何行为)。 
        user_prompt (str): 包含要提取实体的文本的用户输入。
        model_name (str): 要使用的LLM模型的标识符(例如,"gpt-4")。 

    Returns:  
        str: 来自LLM的JSON格式字符串响应,如果客户端不可用则为None。
    """  

    # 构建并发送聊天完成请求到LLM  
    response = client.chat.completions.create(  
        model=model_name,  
        messages=[  
            {"role": "system", "content": system_prompt},  # 系统级指令  
            {"role": "user", "content": user_prompt}       # 用户提供的输入  
        ],  
    )  

    # 提取并返回响应内容(JSON字符串)  
    return response.choices[0].message.content.strip()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

现在,我们需要创建一个系统提示。我们将使用Python的f-string格式化,动态插入从entity_counts.keys()获取的实体类型列表:

# 按频率获取前N个实体类型  
relevant_entity_labels_for_llm = [label for label, count in entity_counts.most_common(TOP_N_ENTITY_TYPES)]  
entity_types_string_for_prompt = ", ".join(relevant_entity_labels_for_llm)  

# LLM的系统提示  
# 我们指示它返回一个带有"entities"键的JSON对象  
# 其值是实体对象的列表。  
llm_ner_system_prompt = (  
    f"You are an expert Named Entity Recognition system. "  
    f"From the provided news article text, identify and extract entities. "  
    f"The entity types to focus on are: {entity_types_string_for_prompt}. "  
    f"For each identified entity, provide its exact text span from the article and its type (use one of the provided types). "  
    f"Output ONLY a valid JSON object with a single key 'entities'. The value of 'entities' MUST be a list of JSON objects, "  
    f"where each object has 'text' and 'type' keys. "  
    f"Example: {
         
         {\"entities\": [{
         
         {\"text\": \"United Nations\", \"type\": \"ORG\"}}, {
         
         {\"text\": \"Barack Obama\", \"type\": \"PERSON\"}}]}} "  
    f"If no entities of the specified types are found, the 'entities' list should be empty: {
         
         {\"entities\": []}}."  
)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

此系统提示将指导LLM以有效的JSON格式输出实体数据。在创建主处理循环前,我们需要一个JSON解析器函数,将文本输出转换为有效的JSON格式: