知识图谱作为一种高效的数据表示方法,能够将大数据中分散的信息连接成结构化、可查询的格式,显著提升数据发现效率。实践表明,采用知识图谱技术可将数据探索时间减少多达70%,从而极大地优化数据分析流程。
本文将基于相关理论知识和方法构建一个完整的端到端项目,系统展示如何利用知识图谱方法对大规模数据进行处理和分析。
环境配置与依赖安装
首先,我们需要安装必要的Python库以支持后续的开发工作。以下是所需的关键依赖包:
# 安装库(仅运行一次)
pipinstallopenairdflibspacypyvisdatasetsscikit-learnmatplotlibtqdmpandas
安装完成后,为确保所有依赖正常加载,可能需要重启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
至此,我们已完成开发环境的准备工作,所有必要的库已成功导入。
数据集概述与加载
本项目使用CNN/DailyMail数据集作为研究对象。该数据集包含超过30万篇新闻文章及其对应的人工撰写摘要,是进行实体、关系和事件提取的理想资源。
使用Hugging Face datasets库加载数据集:
# 使用特定版本可以帮助保持一致性
cnn_dm_dataset=load_dataset("cnn_dailymail", "3.0.0")
我们选择版本"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 ...'}
通过输出可知,该数据集包含311,971篇新闻文章,这是一个相当庞大的语料库。从中提取有价值的洞察确实是一项具有挑战性的任务,而知识图谱正是解决此类问题的有效工具。
数据获取与预处理
构建知识图谱时,直接处理整个包含30万多篇文章的数据集既不高效也不切实际,因为并非所有内容都具有相关性。更为合理的做法是将数据按主题或领域分割为子集,如技术新闻、体育新闻等,分别构建相应的知识图谱。
在大数据处理流程的早期阶段,将数据分解为更易管理的部分是一项关键步骤。对于新闻文章数据集,我们可以采用基于关键词的方法进行初步筛选。
首先定义与技术公司收购相关的关键词集合:
# 定义与技术公司收购相关的关键词
ACQUISITION_KEYWORDS= ["acquire", "acquisition", "merger", "buyout", "purchased by", "acquired by", "takeover"]
TECH_KEYWORDS= ["technology", "software", "startup", "app", "platform", "digital", "AI", "cloud"]
这些关键词是在文本分析中常见的术语,我们预先定义它们以简化处理流程。接下来,使用这些关键词从训练集中筛选相关文章:
# 仅取训练集
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)
现在检查过滤后的文章数量并查看其中一个样本:
# 打印过滤后文章的总数
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...
通过关键词筛选,我们将数据集缩减至约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', '')
})
这些清洗步骤基于对数据的观察和分析,旨在保留关键信息的同时最大限度地减少每篇文章的大小,为后续的知识图谱构建做好准备。
使用核心NLP技术定义实体标签
步骤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
打印实体计数情况:
print(entity_counts)
### OUTPUT ###
spaCy Entity Counts:
ORG: 2314
GPE: 1253
PERSON: 524
NORP: 3341
CARDINAL: 7542
DATE: 6344
...
为了更直观地了解数据中的实体分布情况,我们可以绘制这些标签的柱状图:
# 提取标签和计数
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() # 显示图形
目前我们使用的是SpaCy的小型模型(en_core_web_sm),如需提取更深入、更准确的实体标签,可以考虑切换到更大的模型。这些实体标签将作为指导,帮助我们的大语言模型(如Microsoft的Phi-4)从文章中提取相关实体。
步骤2:实体(节点)提取
实体作为知识图谱中的节点,需要从文本中精确提取。为此,我们需要定义一个系统提示(指导LLM如何处理使用SpaCy提取的实体类型)、用户提示(即文章内容)以及其他必要组件。
首先,建立与LLM的连接:
# 使用提供的配置初始化OpenAI客户端
client = OpenAI(
base_url="YOUR LLM API Provider link",
api_key="LLM API KEY"
)
接下来,创建一个辅助函数,用于打包请求并发送给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()
现在,我们需要创建一个系统提示。我们将使用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\": []}}."
)
此系统提示将指导LLM以有效的JSON格式输出实体数据。在创建主处理循环前,我们需要一个JSON解析器函数,将文本输出转换为有效的JSON格式:
def parse_llm_entity_json_output(llm_output_str):
"""
解析LLM的JSON字符串并返回实体列表。
假设格式为:{"entities": [{"text": "...", "type": "..."}]}
Args:
llm_output_str (str): 来自LLM的JSON字符串。
Returns:
list: 提取的实体或如果解析失败则返回空列表。
"""
if not llm_output_str:
return [] # 如果没有输出则返回空列表
# 如果存在markdown代码块则移除
if llm_output_str.startswith("```json"):
llm_output_str = llm_output_str[7:].rstrip("```").strip()
try:
data = json.loads(llm_output_str)
return data.get("entities", []) # 返回实体列表,如果未找到则为空
except json.JSONDecodeError:
return [] # JSON错误时返回空列表
现在,创建一个循环,对数据集中的每篇文章应用这个系统提示:
# 定义我们的实体提取LLM
TEXT_GEN_MODEL_NAME = "microsoft/phi-4"
# 遍历有限数量的清理后文章以
# 使用LLM提取实体
for i, article_data in enumerate(cleaned_articles):
article_id = article_data['id']
article_text = article_data['cleaned_text']
# 调用LLM提取实体
llm_response_content = call_llm(
llm_ner_system_prompt,
article_text,
TEXT_GEN_MODEL_NAME
)
# 将LLM的响应解析为实体列表
extracted_llm_entities = []
if llm_response_content:
extracted_llm_entities = parse_llm_entity_json_output(llm_response_content)
# 将结果与文章一起存储
articles_with_llm_entities.append({
"id": article_id,
"cleaned_text": article_text,
"summary": article_data['summary'],
"llm_extracted_entities": extracted_llm_entities
})
此循环将处理我们的65,000篇新闻文章,从中提取实体。处理完成后,我们可以查看一篇文章的提取实体:
# 打印一篇样本文章的实体
print(articles_with_llm_entities[4212]['llm_extracted_entities'])
### OUTPUT ###
Extracted 20 entities for article ID 4cf51ce937a.
Sample entities: [
{
"text": "United Nations",
"type": "ORG"
},
{
"text": "Algiers",
"type": "GPE"
},
{
"text": "CNN",
"type": "ORG"
}
...
至此,我们已成功从65,000多篇新闻文章中提取了实体,这些实体将作为知识图谱中的节点。然而,要构建一个完整的知识图谱,我们还需要定义这些节点之间的关系(边),这将在下一步中进行。
步骤3:关系(边)提取
为构建完整的知识图谱,除了识别实体(节点)外,还需明确这些实体间的关系(边)。这些关系将形成知识图谱的连接结构,使图谱能够表达复杂的语义信息。例如,我们需要确定:
- 哪家公司收购了哪家公司
- 收购交易的价格
- 收购公告的具体时间
我们将使用与实体提取相同的LLM调用函数,但需要重新定义一个专注于关系提取的系统提示:
# 关系提取的系统提示
# 我们要求一个带有"relationships"键的JSON对象。
llm_re_system_prompt = (
"You are an expert system for extracting relationships between entities from text, "
"specifically focusing on **technology company acquisitions**. "
"Given an article text and a list of pre-extracted named entities (each with 'text' and 'type'), "
"your task is to identify and extract relationships. "
"The 'subject_text' and 'object_text' in your output MUST be exact text spans of entities found in the provided 'Extracted Entities' list. "
"The 'subject_type' and 'object_type' MUST correspond to the types of those entities from the provided list. "
"Output ONLY a valid JSON object with a single key 'relationships'. The value of 'relationships' MUST be a list of JSON objects. "
"Each relationship object must have these keys: 'subject_text', 'subject_type', 'predicate' (one of the types listed above), 'object_text', 'object_type'. "
"Example: {\"relationships\": [{\"subject_text\": \"Innovatech Ltd.\", \"subject_type\": \"ORG\", \"predicate\": \"ACQUIRED\", \"object_text\": \"Global Solutions Inc.\", \"object_type\": \"ORG\"}]} "
"If no relevant relationships of the specified types are found between the provided entities, the 'relationships' list should be empty: {\"relationships\": []}."
)
在这个系统提示中,我们指导LLM以特定的JSON格式输出关系数据,格式示例如下:
{
"relationships": [
{
"subject_text": "Innovatech Ltd.",
"subject_type": "ORG",
"predicate": "ACQUIRED",
"object_text": "Global Solutions Inc.",
"object_type": "ORG"
}
]
}
与实体提取类似,我们需要一个解析函数来处理LLM返回的关系JSON数据:
def parse_llm_relationship_json_output(llm_output_str_rels):
"""
解析LLM的JSON字符串以提取关系。
预期格式:
{"relationships": [{"subject_text": ..., "predicate": ..., "object_text": ...}]}
Args:
llm_output_str_rels (str): 来自LLM的JSON字符串。
Returns:
list: 提取的关系或如果解析失败则返回空列表。
"""
if not llm_output_str_rels:
return [] # 如果没有输出则返回空列表
# 如果存在markdown代码块则移除
if llm_output_str_rels.startswith("```json"):
llm_output_str_rels = llm_output_str_rels[7:].rstrip("```").strip()
try:
data = json.loads(llm_output_str_rels)
return data.get("relationships", []) # 返回关系列表,如果未找到则为空
except json.JSONDecodeError:
return [] # JSON错误时返回空列表
现在,我们将使用这个系统提示和JSON解析器,对每篇文章进行处理以提取实体间的关系:
# 遍历每篇文章的实体数据
for i, article_entity_data in enumerate(articles_with_llm_entities):
# 从文章数据中提取文章id、清理后的文本和提取的实体
article_id_rels = article_entity_data['id']
article_text_rels = article_entity_data['cleaned_text']
current_entities = article_entity_data['llm_extracted_entities']
# 将实体列表序列化为JSON字符串以包含在提示中
entities_json_for_prompt = json.dumps(current_entities)
# 构建用户提示,请求LLM提取关系
user_prompt_for_re = (
f"Article Text:\n```\n{article_text_rels}\n```\n\n"
f"Extracted Entities (use these exact texts for subjects/objects of relationships):\n```json\n{entities_json_for_prompt}\n```\n\n"
"Identify and extract relationships between these entities based on the system instructions."
)
# 调用LLM基于提示获取关系提取
llm_response_rels_content = call_llm(
llm_re_system_prompt,
user_prompt_for_re,
TEXT_GEN_MODEL_NAME
)
# 初始化一个空列表来存储提取的关系
extracted_llm_rels = []
# 如果LLM响应不为空,从JSON响应中解析提取的关系
if llm_response_rels_content:
extracted_llm_rels = parse_llm_relationship_json_output(llm_response_rels_content)
# 将原始文章数据与提取的关系一起添加到结果列表中
articles_with_llm_relations.append({
**article_entity_data, # 保留原始文章数据(id、文本、实体等)
"llm_extracted_relationships": extracted_llm_rels # 添加提取的关系
})
处理完成后,我们可以查看一篇文章中提取的关系样本:
# 打印一篇样本文章的关系
print(f"Extracted {len(articles_with_llm_relations[1234]['llm_extracted_relationships'])} relationships using LLM.")
print(" Sample LLM relationships:", articles_with_llm_relations[1234]['llm_extracted_relationships'][:2])
### OUTPUT ###
Extracted 3 relationships using LLM.
Sample LLM relationships: [
{
"subject_text": "Microsoft Corp.",
"subject_type": "ORG",
"predicate": "ACQUIRED",
"object_text": "Nuance Communications Inc.",
"object_type": "ORG"
},
{
"subject_text": "Nuance Communications Inc.",
"subject_type": "ORG",
"predicate": "HAS_PRICE",
"object_text": "$19.7 billion",
"object_type": "MONEY"
}
]
至此,我们已成功从文章数据集中提取了实体(节点)和关系(边),完成了构建知识图谱所需的基本元素。
步骤4:实体规范化
在处理非结构化文本时,同一实体可能以多种不同形式出现。例如,"Microsoft Corp."、"Microsoft"和"MSFT"实际上都指代同一家公司。如果在知识图谱中将这些视为独立节点,将导致重要连接的丢失。
实体规范化(也称为实体消歧或实体解析)旨在解决这一问题,确保相同实体的不同表述被识别为同一节点。虽然将实体链接到像Wikidata这样的大型外部知识库是一项复杂任务,但我们可以采用简化的方法:
- 文本规范化:清理实体文本,例如移除组织名称中常见的后缀如"Inc."、"Ltd."、"Corp."等,将"Microsoft Corp."转换为"Microsoft"。
- URI生成:为每个规范化的实体及其类型创建唯一标识符(URI)。例如,类型为"ORG"的"Microsoft"将获得特定URI,后续出现的同一实体将使用相同的URI。
首先,创建一个函数来规范化实体文本:
def normalize_entity_text_for_uri(entity_text, entity_type):
"""
规范化实体文本,主要通过去除组织的常见后缀。
"""
normalized_text = entity_text.strip()
if entity_type == 'ORG':
# 从组织名称中删除的常见后缀列表
# 这个列表可以根据您的数据进行扩展
suffixes_to_remove = [
'Inc.', 'Incorporated', 'Ltd.', 'Limited', 'LLC', 'L.L.C.',
'Corp.', 'Corporation', 'PLC', 'Co.', 'Company',
'Group', 'Holdings', 'Solutions', 'Technologies', 'Systems'
]
# 按长度排序以先删除较长的匹配项(例如,先删除"Corp."再删除"Co.")
suffixes_to_remove.sort(key=len, reverse=True)
for suffix in suffixes_to_remove:
# 不区分大小写地检查文本是否以后缀结尾
if normalized_text.lower().endswith(" " + suffix.lower()) or normalized_text.lower() == suffix.lower():
# 在原始大小写字符串中找到后缀的开始
suffix_start_index = normalized_text.lower().rfind(suffix.lower())
# 切片字符串以删除后缀
normalized_text = normalized_text[:suffix_start_index].strip()
# 一旦删除了后缀,我们就跳出循环以避免不小心过度剥离
# 例如 "The The Co." -> "The The" 而不是 "The"
break
# 删除可能留下的任何尾随逗号或句点
normalized_text = re.sub(r'[,.]*$', '', normalized_text).strip()
# 删除有时被NER捕获的所有格,如's或s'
if normalized_text.endswith("'s") or normalized_text.endswith("s'"):
normalized_text = normalized_text[:-2].strip()
# 如果规范化导致空字符串,恢复为原始字符串(应该很少见)
return normalized_text if normalized_text else entity_text
接下来,我们将处理所有实体,规范化其文本并为每个唯一实体创建URI:
# 导入必要的模块
import hashlib
# 最终输出列表,用于存储带有处理过的实体信息的文章
articles_with_normalized_entities_and_uris = []
# 用于跟踪唯一实体并分配稳定URI的字典
unique_entities_map = {}
# 为知识图谱URI定义基本命名空间
EX = Namespace("http://example.org/kg/")
SCHEMA = Namespace("http://schema.org/")
print("KG Namespace EX defined.")
# 处理每篇文章
for article in tqdm(articles_with_llm_relations, desc="Normalizing & URI Gen"):
processed = []
# 如果可用,提取并处理每个实体
for ent in article.get('llm_extracted_entities', []):
text = ent['text']
type_raw = ent['type']
# 规范化类型(例如,"ORG (Organization)" → "ORG")
type_simple = type_raw.split()[0].upper()
# 为URI生成规范化实体文本
norm_text = normalize_entity_text_for_uri(text, type_simple)
key = (norm_text, type_simple)
# 如果是新实体,分配唯一URI
if key not in unique_entities_map:
# 清理并截断文本以使其URI安全
safe_text = re.sub(r'[^a-zA-Z0-9_]', '_', norm_text.replace(' ', '_'))[:50]
# 如果清理使名称为空,回退到哈希
if not safe_text:
safe_text = f"entity_{hashlib.md5(norm_text.encode()).hexdigest()[:8]}"
# 生成完整URI
unique_entities_map[key] = EX[f"{safe_text}_{type_simple}"]
# 将规范化字段和URI添加到实体中
processed.append({
**ent,
'normalized_text': norm_text,
'simple_type': type_simple,
'uri': unique_entities_map[key]
})
# 将处理后的实体列表添加到文章中
articles_with_normalized_entities_and_uris.append({
**article,
"processed_entities": processed
})
处理完成后可以查看一些规范化实体的样本:
# 显示一篇文章的前3个处理后的实体
print("Example of processed entities from the first article (sample):")
for ent in articles_with_normalized_entities_and_uris[2222]['processed_entities'][:3]:
print(f" Original: '{ent['text']}' ({ent['type']})") # 原始实体文本和原始类型
print(f" Normalized: '{ent['normalized_text']}' (Simple Type: {ent['simple_type']})") # 清理后的文本和类型
print(f" URI: <{ent['uri']}>") # 为实体生成的URI
### OUTPUT ###
Example of processed entities from the first article (sample):
Original: 'Inabix Corp.' (ORG)
Normalized: 'Inabix' (Simple Type: ORG)
URI: <http://example.org/kg/Inabix_ORG>
Original: 'Nuance Communications Inc.' (ORG)
Normalized: 'Nuance Communications' (Simple Type: ORG)
URI: <http://example.org/kg/Nuance_Communications_ORG>
Original: '$73.1 billion' (MONEY)
Normalized: '$73.1 billion' (Simple Type: MONEY)
URI: <http://example.org/kg/73_1_billion_MONEY>
至此,我们的数据集中每个唯一实体都有了规范化的名称和唯一的URI。这些处理后的实体,结合之前提取的关系,已准备好转换为RDF三元组,用于构建知识图谱。
步骤5:本体对齐
为了赋予知识图谱更加正式的结构,我们需要进行模式设计或本体对齐。这一步骤相当于为图谱提供一个蓝图或字典:
- 模式(Schema)定义了可以存在于图谱中的实体类型和关系类型。
- 本体(Ontology)则更加正式,可包含更丰富的描述、规则和约束,如"一家公司可以被另一家公司收购,但一个人不能被一座建筑物收购"。
对于本项目,我们将进行简化版本的模式对齐。我们需要告诉图谱每个URI的实际类型,例如,
ex:Microsoft_ORG
不仅是一个随机字符串,它代表一个组织;而
ex:Satya_Nadella_PERSON
代表一个人。
我们将创建一个函数,将实体的简化类型(如"ORG"、"PERSON"、"MONEY")映射到正式的RDF类。在RDF世界中,RDF类相当于一个分类或类型:
def get_rdf_class_for_entity_type(simple_entity_type_str):
"""
将简单实体类型字符串(例如,'ORG')映射到RDF类URI。
尽可能使用Schema.org,否则默认为我们自定义的EX命名空间。
"""
type_to_rdf_class_map = {
'ORG': SCHEMA.Organization,
'PERSON': SCHEMA.Person,
'MONEY': SCHEMA.PriceSpecification, # Schema.org使用这个表示货币金额
'DATE': SCHEMA.Date, # 表示日期
'PRODUCT': SCHEMA.Product,
'GPE': SCHEMA.Place, # 地缘政治实体(很好地映射到Place)
'LOC': SCHEMA.Place, # 一般位置
'EVENT': SCHEMA.Event,
'NORP': SCHEMA.Nationality, # 国籍、宗教或政治团体
'CARDINAL': XSD.integer, # 基数数字通常只是字面整数
# 或者如果有更多上下文,可以映射到schema:QuantitativeValue。
# 对于类型化节点,除非是复杂值,否则不太常见。
# 通常,基数数字成为属性的字面值。
# 如果您的LLM识别了其他相关的'simple_type',可以添加更多映射
}
# 使用.get()在我们的映射中没有类型时提供回退
# 如果不在映射中,在我们的EX命名空间中创建一个类
rdf_class = type_to_rdf_class_map.get(simple_entity_type_str.upper(), EX[simple_entity_type_str.upper()])
return rdf_class
让我们测试这个函数,看看我们的实体类型会映射到哪些RDF类:
print("Example RDF Class mappings for our entity types:")
sample_type1 = 'ORG'
rdf_class1 = get_rdf_class_for_entity_type(sample_type1)
print(f" Entity Type '{sample_type1}' maps to RDF Class: <{rdf_class1}>")
sample_type2 = 'MONEY'
rdf_class2 = get_rdf_class_for_entity_type(sample_type2)
print(f" Entity Type '{sample_type2}' maps to RDF Class: <{rdf_class2}>")
sample_type3 = 'INVESTMENT_ROUND' # 一个假设的自定义类型
rdf_class3 = get_rdf_class_for_entity_type(sample_type3)
print(f" Entity Type '{sample_type3}' (custom) maps to RDF Class: <{rdf_class3}>")
### OUTPUT ###
Custom KG Namespace EX re-defined for clarity.
RDF Namespace: http://www.w3.org/1999/02/22-rdf-syntax-ns#
RDFS Namespace: http://www.w3.org/2000/01/rdf-schema#
SCHEMA Namespace (Schema.org): http://schema.org/
EX Namespace (Custom): http://example.org/kg/
Example RDF Class mappings for our entity types:
Entity Type 'ORG' maps to RDF Class: <http://schema.org/Organization>
Entity Type 'MONEY' maps to RDF Class: <http://schema.org/PriceSpecification>
Entity Type 'INVESTMENT_ROUND' (custom) maps to RDF Class: <http://example.org/kg/INVESTMENT_ROUND>
在构建知识图谱时,每个实体URI(如
ex:Microsoft_ORG
)将被明确声明为其相应RDF类(如
schema:Organization
)的实例。这是通过使用
rdf:type
谓语形成三元组完成的,例如:
<ex:Microsoft_ORG> <rdf:type> <schema:Organization>
步骤6:RDF三元组构建
RDF三元组是使用资源描述框架(RDF)构建知识图谱的基本单元。每个三元组由三部分组成:
- 主语:我们谈论的实体(实体URI)
- 谓语:关于主语的描述(属性或关系URI)
- 宾语:与主语相关的值或其他实体(另一个实体URI或如名称、日期、数字等字面值)
可以将其视为一个简单的语句:
# 原始形式
<主语> <谓语> <宾语>
# 示例
<ex:Microsoft_ORG> <rdf:type> <schema:Organization>
(Microsoft是一个组织)
我们生成三元组的方法如下:
- 为每篇文章创建URI,将其标记为
schema:Article
,并添加其摘要 - 为每个实体使用其URI,分配
rdf:type
、rdfs:label
和可选的skos:altLabel
,并通过schema:mentions
将其链接到文章 - 为每个关系获取主语和宾语URI,将谓语映射到RDF属性,并添加相应三元组
首先,我们定义一个关系谓语的辅助函数,类似于实体类型的映射函数:
def get_rdf_predicate_uri(predicate_string_from_llm):
"""
将谓语字符串(例如,'ACQUIRED','HAS_PRICE')
转换为我们的EX命名空间中的适当RDF属性URI。
"""
# 清理:大写,将空格替换为下划线
sanitized_predicate = predicate_string_from_llm.strip().replace(" ", "_").upper()
return EX[sanitized_predicate]
现在,创建RDF图谱并填充它:
# 初始化RDF图谱和命名空间
kg = Graph()
SKOS = Namespace("http://www.w3.org/2004/02/skos/core#")
kg.bind("ex", EX)
kg.bind("schema", SCHEMA)
kg.bind("rdfs", RDFS)
kg.bind("skos", SKOS)
total_triples_added = 0
for article in tqdm(articles_with_normalized_entities_and_uris):
# 为文章创建URI
article_uri = EX[f"article_{article['id'].replace('-', '_')}"]
kg.add((article_uri, RDF.type, SCHEMA.Article))
# 添加摘要或回退标签
label = article.get('summary') or f"Article {article['id']}"
pred = SCHEMA.headline if article.get('summary') else RDFS.label
kg.add((article_uri, pred, Literal(label, lang='en')))
total_triples_added += 2
entity_map = {}
# 处理实体
for e in article.get('processed_entities', []):
uri = e['uri']
kg.add((uri, RDF.type, get_rdf_class_for_entity_type(e['simple_type'])))
kg.add((uri, RDFS.label, Literal(e['normalized_text'], lang='en')))
if e['text'] != e['normalized_text']:
kg.add((uri, SKOS.altLabel, Literal(e['text'], lang='en')))
kg.add((article_uri, SCHEMA.mentions, uri))
entity_map[e['text']] = uri
total_triples_added += 3 + (1 if e['text'] != e['normalized_text'] else 0)
# 处理关系
for r in article.get('llm_extracted_relationships', []):
s_uri = entity_map.get(r.get('subject_text'))
o_uri = entity_map.get(r.get('object_text'))
if s_uri and o_uri:
predicate_uri = get_rdf_predicate_uri(r.get('predicate'))
kg.add((s_uri, predicate_uri, o_uri))
total_triples_added += 1
print(f"Total RDF triples added to Knowledge Graph: {total_triples_added}")
处理完成后,我们可以查看一些生成的三元组样本:
print("Sample of first 5 triples from the Knowledge Graph (N3 format):")
for i, triple in enumerate(list(kg)[:5]):
print(f" {triple[0]} {triple[1]} {triple[2]} .")
### OUTPUT ###
Sample of first 5 triples from the Knowledge Graph (N3 format):
ex:article_02002614879655690596592a07ba827b1651f065 rdf:type schema:Article .
ex:article_02002614879655690596592a07ba827b1651f065 schema:headline "SAN DIEGO, California (CNN) -- You must know whats really driving the immigration debate because its not what you hear on TV Dont be fooled This has nothing to do with national security It has nothing to do with Mexican immigrants It has nothing to do with illegal immigrants The real driver of this debate is the insecurity of American working-class white men All the other stuff is just window dressing..." .
ex:Microsoft_ORG rdf:type schema:Organization .
ex:Microsoft_ORG ex:ACQUIRED ex:Nuance_Communications_ORG .
至此,我们的知识图谱已填充了RDF三元组。每个实体都有类型、标签,并通过有意义的关系相互连接。我们已成功将非结构化的新闻文本转换为结构化的、机器可读的知识图谱。
步骤7:嵌入生成
我们构建的知识图谱虽然具有良好的结构,包含实体(节点)和关系(边),但目前主要是符号性的。例如,图谱知道
ex:Microsoft_ORG
是一个
schema:Organization
,但它本质上无法理解Microsoft与Apple或Google之间的语义相似性,除非我们明确指定。
知识图谱嵌入(KGE)能够解决这一问题,它为图谱中的每个实体和关系生成密集向量表示,捕获它们的语义特性。这些嵌入使以下任务成为可能:
- 链接预测(建议缺失的关系)
- 实体相似性分析(比较实体)
- 类比推理(例如,"Satya Nadella之于Microsoft就像Tim Cook之于?")
- 知识图谱补全(填补图谱中的空白)
从头开始训练完整的KGE模型(如TransE、ComplEx或DistMult)可能相当复杂。在本教程中,我们采用更简便的方法,使用预训练的嵌入模型。
首先,定义一个函数,使用LLM客户端获取文本嵌入:
EMBEDDING_MODEL_NAME = "BAAI/bge-multilingual-gemma2"
def get_text_embeddings(list_of_texts_to_embed, embedding_model):
"""
使用指定的嵌入模型获取文本列表的嵌入向量。
Args:
list_of_texts_to_embed: 要嵌入的文本字符串列表
embedding_model: 要使用的嵌入模型名称
Returns:
返回包含文本到嵌入向量映射的字典
"""
embeddings_map = {}
# 批处理请求以提高效率
for i in range(0, len(list_of_texts_to_embed), 20):
batch = list_of_texts_to_embed[i:i+20]
response = client.embeddings.create(
model=embedding_model,
input=batch
)
# 提取响应中的嵌入向量
for j, text in enumerate(batch):
embeddings_map[text] = response.data[j].embedding
return embeddings_map
接下来,从我们的
unique_entities_map
中收集所有唯一的、规范化的实体标签,并为每个唯一的
normalized_text
生成嵌入向量:
# 收集所有规范化的实体文本
all_normalized_entity_texts = []
entity_uri_to_normalized_text = {}
for key, uri in unique_entities_map.items():
norm_text, _ = key
all_normalized_entity_texts.append(norm_text)
entity_uri_to_normalized_text[uri] = norm_text
print(f"Found {len(all_normalized_entity_texts)} unique normalized entity texts to embed")
# 获取嵌入
entity_text_to_embedding_vector = get_text_embeddings(all_normalized_entity_texts, EMBEDDING_MODEL_NAME)
# 将嵌入映射到URI
entity_uri_to_embedding_vector = {}
for uri, norm_text in entity_uri_to_normalized_text.items():
entity_uri_to_embedding_vector[uri] = entity_text_to_embedding_vector[norm_text]
print(f"Generated embeddings for {len(entity_uri_to_embedding_vector)} entities")
通过这个过程,我们的每个唯一实体(或更准确地说,它们的名称)都拥有一个存储在
entity_uri_to_embedding_vector
中的密集向量表示,这个向量捕获了实体的语义含义。
步骤8:链接预测
当前的知识图谱仅包含从文本中明确陈述或提取的事实。然而,可能存在一些未直接提及但可以通过推理发现的连接或相似性。链接预测(也称为知识发现)正是解决这一问题的技术。
在知识图谱中,链接预测是识别实体(节点)之间潜在关系(边)的任务。这种技术能够发现隐藏的关联,丰富图谱的语义表达能力,提供更完整的知识表示。
链接预测的一种简单方法是基于嵌入相似性。我们可以使用以下步骤:
- 选择一个源实体(例如,一家公司)
- 找到与该实体相似的其他实体
- 分析图谱中已存在的关系模式
- 预测可能存在但尚未明确提取的关系
让我们通过一个示例来展示这个过程:
# 选择一个示例公司
target_company = "Microsoft"
target_company_uri = None
# 在规范化实体中查找目标公司
for key, uri in unique_entities_map.items():
norm_text, entity_type = key
if norm_text.lower() == target_company.lower() and entity_type == "ORG":
target_company_uri = uri
break
if target_company_uri:
# 获取目标公司的向量
target_vector = entity_uri_to_embedding_vector[target_company_uri]
# 计算所有其他组织实体与目标的相似度
similarity_scores = {}
for key, uri in unique_entities_map.items():
norm_text, entity_type = key
if entity_type == "ORG" and uri != target_company_uri:
if uri in entity_uri_to_embedding_vector:
vector = entity_uri_to_embedding_vector[uri]
# 计算余弦相似度
similarity = cosine_similarity([target_vector], [vector])[0][0]
similarity_scores[uri] = (norm_text, similarity)
# 显示最相似的5家公司
print(f"Top 5 companies most similar to {target_company}:")
for uri, (name, score) in sorted(similarity_scores.items(), key=lambda x: x[1][1], reverse=True)[:5]:
print(f" {name}: {score:.4f}")
# 分析这些相似公司的已知关系
print("\nAnalyzing relationships for similar companies:")
for uri, (name, _) in sorted(similarity_scores.items(), key=lambda x: x[1][1], reverse=True)[:3]:
print(f"\nRelationships involving {name}:")
# 查找所有以此公司为主语的关系
for s, p, o in kg.triples((uri, None, None)):
if p != RDF.type and p != RDFS.label and p != SKOS.altLabel:
o_label = None
for _, _, label in kg.triples((o, RDFS.label, None)):
o_label = label
break
if o_label:
print(f" {name} {p.split('/')[-1]} {o_label}")
基于这些相似性分析,我们可以生成可能的新关系,丰富知识图谱:
print("\nSuggested new relationships based on similarity:")
# 获取目标公司的已知关系
target_relationships = []
for s, p, o in kg.triples((target_company_uri, None, None)):
if p != RDF.type and p != RDFS.label and p != SKOS.altLabel:
target_relationships.append((p, o))
# 为相似公司建议类似关系
for uri, (name, score) in sorted(similarity_scores.items(), key=lambda x: x[1][1], reverse=True)[:3]:
if score > 0.75: # 只考虑高相似度的公司
for pred, obj in target_relationships:
# 检查该关系是否已存在
if not any(kg.triples((uri, pred, obj))):
obj_label = None
for _, _, label in kg.triples((obj, RDFS.label, None)):
obj_label = label
break
if obj_label:
print(f" Suggested: {name} {pred.split('/')[-1]} {obj_label} (confidence: {score:.4f})")
通过这种方式,我们能够基于实体之间的语义相似性和已知关系模式,预测和建议潜在的新关系,从而扩展和丰富知识图谱。
步骤9:知识图谱序列化
知识图谱的持久化存储是确保其可重用性和共享性的关键步骤。RDF知识图谱的存储方式多种多样,但最基础且通用的方法是将图谱序列化为标准格式文件。本项目采用Turtle(
.ttl
)格式,这是一种人类可读的RDF序列化格式,广泛应用于语义网技术领域。
下面定义一个通用函数,将我们构建的
rdflib.Graph
对象序列化到文件系统中:
def save_knowledge_graph_to_file(graph_object, output_filepath="my_knowledge_graph.ttl", rdf_format="turtle"):
"""
将RDF图谱对象序列化并保存到指定格式的文件中。
Args:
graph_object: 要序列化的rdflib.Graph对象
output_filepath: 输出文件路径
rdf_format: RDF序列化格式,默认为turtle
Returns:
bool: 操作成功返回True,否则返回False
"""
if not graph_object or len(graph_object) == 0:
print("知识图谱为空。没有内容可保存。")
return False
try:
graph_object.serialize(destination=output_filepath, format=rdf_format)
print(f"包含{len(graph_object)}个三元组的知识图谱成功保存到:{output_filepath}(格式:{rdf_format})")
return True
except Exception as e:
print(f"保存知识图谱到{output_filepath}时出错:{e}")
return False
现在,调用此函数将我们的知识图谱保存为Turtle格式文件:
# 为保存的知识图谱定义文件名
KG_FILENAME = "tech_acquisitions_knowledge_graph.ttl"
was_saved = save_knowledge_graph_to_file(kg,
output_filepath=KG_FILENAME,
rdf_format="turtle")
Turtle格式是一种简洁且可读性强的RDF表示方法。如果在文本编辑器中打开生成的
.ttl
文件,您将看到类似以下内容的结构:
@prefix ex: <http://example.org/kg/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix schema: <http://schema.org/> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
...
ex:Microsoft_ORG a schema:Organization ;
rdfs:label "Microsoft"@en ;
ex:ACQUIRED ex:Nuance_Communications_ORG .
ex:Nuance_Communications_ORG a schema:Organization ;
rdfs:label "Nuance Communications"@en ;
skos:altLabel "Nuance Communications Inc."@en .
在Turtle语法中,文档开始部分定义命名空间前缀,随后是三元组表示。分号(
;
)用于在同一主语下列出多个谓语-宾语对,而句点(
.
)则表示一个主语的语句组结束。这种格式既人类可读又便于机器处理,是知识图谱交换的理想格式。
步骤10:知识图谱查询与分析
知识图谱的核心价值在于其灵活的查询能力。对于RDF知识图谱,SPARQL(SPARQL Protocol and RDF Query Language)是专门设计用于查询和操作RDF数据的标准查询语言。通过SPARQL,我们可以从知识图谱中提取特定模式、关系或属性,执行复杂的分析,甚至修改图谱结构。
首先,创建一个辅助函数,用于执行SPARQL查询并格式化结果:
def execute_and_print_sparql_query(graph, query, title="SPARQL Query"):
"""
在知识图谱上执行SPARQL查询并格式化输出结果
Args:
graph: 要查询的rdflib.Graph对象
query: SPARQL查询字符串
title: 查询标题,用于输出显示
Returns:
list: 包含查询结果的字典列表
"""
print(f"\n--- {title} ---\nQuery:\n{query}")
# 运行查询并格式化结果
results = graph.query(query)
results_list = [{str(var): str(val) for var, val in row.asdict().items()} for row in results]
# 打印结果(最多10个)
print(f"\nResults ({len(results_list)} found):")
for i, result in enumerate(results_list[:10]):
print(f" {i+1}: {result}")
if len(results_list) > 10:
print(f" ... (以及其他{len(results_list) - 10}个结果)")
return results_list
现在,让我们通过几个示例SPARQL查询,展示如何从知识图谱中提取有价值的信息:
示例1:查询图谱中的所有组织机构
# 查询所有组织机构
sparql_query_organizations = """
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <http://schema.org/>
SELECT DISTINCT ?org_label ?org_uri
WHERE {
?org_uri rdf:type schema:Organization .
?org_uri rdfs:label ?org_label .
}
ORDER BY ?org_label
LIMIT 10
"""
org_results = execute_and_print_sparql_query(kg, sparql_query_organizations, "Query 1: List Organizations")
执行结果:
Results (10 found):
1: {'org_label': 'Accenture', 'org_uri': 'http://example.org/kg/Accenture_ORG'}
2: {'org_label': 'Adobe', 'org_uri': 'http://example.org/kg/Adobe_ORG'}
3: {'org_label': 'Advanced Micro Devices', 'org_uri': 'http://example.org/kg/Advanced_Micro_Devices_ORG'}
...
示例2:查询所有公司收购关系
# 查询公司收购关系
sparql_query_acquisitions = """
PREFIX ex: <http://example.org/kg/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <http://schema.org/>
SELECT ?acquiring_company_label ?acquired_company_label
WHERE {
?acquiring_company rdf:type schema:Organization .
?acquiring_company rdfs:label ?acquiring_company_label .
?acquiring_company ex:ACQUIRED ?acquired_company .
?acquired_company rdf:type schema:Organization .
?acquired_company rdfs:label ?acquired_company_label .
}
ORDER BY ?acquiring_company_label
"""
acq_results = execute_and_print_sparql_query(kg, sparql_query_acquisitions,
"Query 2: List Company Acquisitions")
执行结果:
Results (7 found):
1: {'acquiring_company_label': 'Microsoft', 'acquired_company_label': 'Nuance Communications'}
2: {'acquiring_company_label': 'Salesforce', 'acquired_company_label': 'Slack Technologies'}
3: {'acquiring_company_label': 'Google', 'acquired_company_label': 'Fitbit'}
...
SPARQL的强大之处在于其表达能力和灵活性。通过组合不同的图模式、过滤条件和聚合函数,我们可以构建复杂的查询,从知识图谱中提取精确的信息。例如,我们可以查询特定时间段内的收购活动、特定金额以上的交易,或者识别出最活跃的收购方。
SPARQL还支持联合查询、可选匹配、过滤、聚合和排序等高级功能,使其成为知识图谱分析的强大工具。
步骤11:交互式可视化
数据可视化对于理解复杂的知识结构至关重要。对于知识图谱,网络图是最直观的可视化方式,能够同时展示实体(节点)和关系(边)。本项目采用
pyvis
库创建交互式网络可视化,该库基于
vis.js
构建,提供丰富的交互功能。
以下函数用于从RDF图谱生成交互式HTML可视化:
def visualize_kg(graph, filename="kg_viz.html", num_triples=50):
"""
为RDF知识图谱创建交互式网络可视化
Args:
graph: 要可视化的rdflib.Graph对象
filename: 输出的HTML文件名
num_triples: 要包含在可视化中的三元组数量上限
Returns:
Network: pyvis.network.Network对象
"""
# 创建pyvis网络对象
net = Network(height="600px", width="100%", directed=True)
# 收集三元组(限制在主语和宾语都是URI的情况)
triples = [(s, p, o) for s, p, o in graph if isinstance(s, URIRef) and isinstance(o, URIRef)][:num_triples]
nodes = set() # 用于跟踪已添加的节点
# 向可视化添加节点和边
for s, p, o in tqdm(triples, desc="Visualizing"):
for node in (s, o):
if node not in nodes:
# 获取节点标签和类型
label = graph.value(node, RDFS.label) or node.n3(graph.namespace_manager)
if isinstance(label, Literal):
label = str(label)
ntype = graph.value(node, RDF.type)
group = ntype.n3(graph.namespace_manager).split(":")[-1] if ntype else "Unknown"
# 添加节点
net.add_node(str(node), label=label, group=group, title=str(node))
nodes.add(node)
# 添加边,使用谓语作为标签
pred_label = p.n3(graph.namespace_manager).split(":")[-1]
net.add_edge(str(s), str(o), label=pred_label, title=pred_label, arrows="to")
# 设置可视化参数
net.set_options("""
{
"physics": {
"forceAtlas2Based": {
"gravitationalConstant": -50,
"centralGravity": 0.01,
"springLength": 100,
"springConstant": 0.08
},
"solver": "forceAtlas2Based",
"stabilization": {
"iterations": 100
}
},
"interaction": {
"navigationButtons": true,
"keyboard": true
}
}
""")
# 保存可视化到HTML文件
net.save_graph(filename)
print(f"可视化已保存到 {filename}")
return net
执行可视化函数:
# 定义可视化文件名
VIZ_FILENAME = "tech_acquisitions_kg_visualization.html"
# 调用可视化函数
kg_visualization_net = visualize_kg(kg, filename=VIZ_FILENAME, num_triples=75)
执行此代码后,将在当前工作目录中生成一个HTML文件。在Web浏览器中打开该文件,您将看到一个交互式的知识图谱可视化,如下图所示:
这个交互式可视化具有以下特点:
- 节点分组:不同类型的实体(如组织、人物、金额等)使用不同的颜色分组
- 关系标签:边上显示关系类型(如"ACQUIRED"、"HAS_PRICE"等)
- 交互功能:支持缩放、平移、节点拖拽以及悬停查看详细信息
- 物理模拟:使用力导向算法自动布局,相关节点自然聚集
通过这种可视化,我们可以直观地理解知识图谱的结构,发现实体之间的关系模式,识别中心节点和关键连接。例如,我们可以轻松看出哪些公司是主要的收购方,哪些公司被多次提及,以及各种收购关系的网络结构。
展望与后续发展
完成知识图谱的构建、查询和可视化后,我们已经实现了从非结构化文本到结构化知识的完整转换。然而,这仅仅是知识图谱应用的起点。以下是一些值得探索的后续发展方向:
高级查询与推理
利用SPARQL的全部功能,构建更复杂的查询以回答业务问题:
# 示例:查询收购金额超过10亿美元的交易
advanced_query = """
PREFIX ex: <http://example.org/kg/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <http://schema.org/>
SELECT ?acquiring ?acquiring_label ?acquired ?acquired_label ?price_label
WHERE {
?acquiring ex:ACQUIRED ?acquired .
?acquiring rdfs:label ?acquiring_label .
?acquired rdfs:label ?acquired_label .
?acquired ex:HAS_PRICE ?price .
?price rdfs:label ?price_label .
FILTER(CONTAINS(LCASE(?price_label), "billion"))
}
ORDER BY ?acquiring_label
"""
billion_acquisitions = execute_and_print_sparql_query(kg, advanced_query,
"Advanced Query: Billion-Dollar Acquisitions")
知识图谱扩展与完善
- 链接预测器训练:基于现有实体嵌入,训练专门的链接预测模型,可以预测图谱中尚未明确表达的潜在关系。
- 与外部知识图谱对齐:将实体映射到Wikidata或DBpedia等公共知识库,丰富现有知识图谱的语义内容:
def align_entity_with_wikidata(entity_name, entity_type):
"""
尝试将实体与Wikidata实体对齐
"""
from SPARQLWrapper import SPARQLWrapper, JSON
sparql = SPARQLWrapper("https://query.wikidata.org/sparql")
# 构建查询
query = f"""
SELECT ?item ?itemLabel WHERE {{
?item rdfs:label "{entity_name}"@en.
?item wdt:P31 ?type.
SERVICE wikibase:label {{ bd:serviceParam wikibase:language "en". }}
}}
LIMIT 5
"""
sparql.setQuery(query)
sparql.setReturnFormat(JSON)
results = sparql.query().convert()
return results["results"]["bindings"]
- 集成更多数据源:整合财务报告、社交媒体数据、专利信息等多样化数据源,构建更全面的企业知识图谱。
应用开发与部署
基于构建的知识图谱,可以开发各种实用应用:
- 知识图谱搜索引擎:创建一个Web应用,允许用户通过自然语言查询或结构化方式探索技术收购数据:
def create_kg_search_app():
"""
使用Streamlit创建知识图谱搜索应用的示例代码框架
"""
import streamlit as st
from rdflib import Graph
st.title("技术收购知识图谱搜索")
# 加载知识图谱
@st.cache_resource
def load_knowledge_graph():
g = Graph()
g.parse("tech_acquisitions_knowledge_graph.ttl", format="turtle")
return g
kg = load_knowledge_graph()
# 搜索界面
search_type = st.selectbox("选择搜索类型", ["公司", "收购关系", "金额范围"])
if search_type == "公司":
company_name = st.text_input("输入公司名称")
if company_name and st.button("搜索"):
# 执行SPARQL查询
pass
- 知识图谱推荐系统:根据公司间的相似性和关系,构建推荐系统,预测潜在的收购目标或合作伙伴。
- 趋势分析与可视化仪表板:创建分析仪表板,展示技术收购领域的趋势、主要参与者和市场动态。
知识图谱维护与更新
设计自动化流程,持续更新知识图谱:
- 增量更新机制:定期从新闻源获取最新文章,仅处理新内容并将提取的知识集成到现有图谱中。
- 质量评估与修正:实现自动化验证程序,识别并修正知识图谱中的错误或不一致。
- 版本控制:建立知识图谱的版本控制系统,跟踪内容变化并支持回滚操作。
通过这些拓展,我们可以将初始的知识图谱转变为一个功能丰富、持续更新的知识系统,为商业智能、投资分析、市场研究等领域提供深刻洞察。知识图谱的真正价值在于其能够连接分散信息、发现隐藏关系以及支持智能决策的能力。
总结
本文通过11个系统化步骤,展示了如何从非结构化新闻文本构建技术收购领域的知识图谱:
- 数据获取与预处理:从大型新闻数据集中筛选并清洗相关文章
- 实体标签检测:利用SpaCy确定目标实体类型
- 实体提取:使用大语言模型从文本中识别关键实体
- 关系提取:发现实体间的语义关联
- 实体规范化:统一不同表述的同一实体
- 本体对齐:将实体映射到标准化的语义模型
- RDF三元组构建:生成知识图谱的基础单元
- 知识图谱嵌入:为实体创建语义向量表示
- 知识图谱存储:序列化并持久化RDF图谱
- SPARQL查询分析:提取结构化知识
- 交互式可视化:直观呈现知识网络
通过这一过程,我们将大量分散、非结构化的信息转化为互联、可查询的知识网络,实现了数据到知识的转换。这种方法不仅适用于技术收购领域,还可扩展到金融、医疗、学术研究等众多领域,为数据分析和知识发现提供了强大工具。
知识图谱技术通过连接数据点创造额外价值,使信息检索更加精确,推理能力更加强大,为复杂问题的解决提供了新的途径。随着语言模型和知识表示技术的不断进步,这一领域仍有广阔的发展空间和应用前景。
https://avoid.overfit.cn/post/04162c51bc204c77965b36b10119d623
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。